]> git.sven.stormbind.net Git - sven/exfat-utils.git/blobdiff - libexfat/mount.c
Update the Homepage field in d/control.
[sven/exfat-utils.git] / libexfat / mount.c
index 4240f63b7a34efece8f08f30db1feb241e64bb44..f1fb01a9a88c8ee20eb3730c6669ee905cfbfcd3 100644 (file)
@@ -2,11 +2,12 @@
        mount.c (22.10.09)
        exFAT file system implementation library.
 
        mount.c (22.10.09)
        exFAT file system implementation library.
 
-       Copyright (C) 2009, 2010  Andrew Nayenko
+       Free exFAT implementation.
+       Copyright (C) 2010-2015  Andrew Nayenko
 
 
-       This program is free software: you can redistribute it and/or modify
+       This program is free software; you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        it under the terms of the GNU General Public License as published by
-       the Free Software Foundation, either version 3 of the License, or
+       the Free Software Foundation, either version 2 of the License, or
        (at your option) any later version.
 
        This program is distributed in the hope that it will be useful,
        (at your option) any later version.
 
        This program is distributed in the hope that it will be useful,
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
 
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
 
-       You should have received a copy of the GNU General Public License
-       along with this program.  If not, see <http://www.gnu.org/licenses/>.
+       You should have received a copy of the GNU General Public License along
+       with this program; if not, write to the Free Software Foundation, Inc.,
+       51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
 #include "exfat.h"
 #include <string.h>
 #include <stdlib.h>
 #include <errno.h>
 */
 
 #include "exfat.h"
 #include <string.h>
 #include <stdlib.h>
 #include <errno.h>
+#include <inttypes.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <sys/types.h>
-#define _XOPEN_SOURCE /* for tzset() in Linux */
-#include <time.h>
 
 static uint64_t rootdir_size(const struct exfat* ef)
 {
 
 static uint64_t rootdir_size(const struct exfat* ef)
 {
-       uint64_t clusters = 0;
+       uint32_t clusters = 0;
+       uint32_t clusters_max = le32_to_cpu(ef->sb->cluster_count);
        cluster_t rootdir_cluster = le32_to_cpu(ef->sb->rootdir_cluster);
 
        cluster_t rootdir_cluster = le32_to_cpu(ef->sb->rootdir_cluster);
 
-       while (!CLUSTER_INVALID(rootdir_cluster))
+       /* Iterate all clusters of the root directory to calculate its size.
+          It can't be contiguous because there is no flag to indicate this. */
+       do
        {
        {
-               clusters++;
-               /* root directory cannot be contiguous because there is no flag
-                  to indicate this */
+               if (clusters == clusters_max) /* infinite loop detected */
+               {
+                       exfat_error("root directory cannot occupy all %d clusters",
+                                       clusters);
+                       return 0;
+               }
+               if (CLUSTER_INVALID(rootdir_cluster))
+               {
+                       exfat_error("bad cluster %#x while reading root directory",
+                                       rootdir_cluster);
+                       return 0;
+               }
                rootdir_cluster = exfat_next_cluster(ef, ef->root, rootdir_cluster);
                rootdir_cluster = exfat_next_cluster(ef, ef->root, rootdir_cluster);
+               clusters++;
        }
        }
-       return clusters * CLUSTER_SIZE(*ef->sb);
+       while (rootdir_cluster != EXFAT_CLUSTER_END);
+
+       return (uint64_t) clusters * CLUSTER_SIZE(*ef->sb);
 }
 
 static const char* get_option(const char* options, const char* option_name)
 }
 
 static const char* get_option(const char* options, const char* option_name)
@@ -63,7 +79,7 @@ static int get_int_option(const char* options, const char* option_name,
        return strtol(p, NULL, base);
 }
 
        return strtol(p, NULL, base);
 }
 
-static int match_option(const char* options, const char* option_name)
+static bool match_option(const char* options, const char* option_name)
 {
        const char* p;
        size_t length = strlen(option_name);
 {
        const char* p;
        size_t length = strlen(option_name);
@@ -71,131 +87,201 @@ static int match_option(const char* options, const char* option_name)
        for (p = strstr(options, option_name); p; p = strstr(p + 1, option_name))
                if ((p == options || p[-1] == ',') &&
                                (p[length] == ',' || p[length] == '\0'))
        for (p = strstr(options, option_name); p; p = strstr(p + 1, option_name))
                if ((p == options || p[-1] == ',') &&
                                (p[length] == ',' || p[length] == '\0'))
-                       return 1;
-       return 0;
+                       return true;
+       return false;
 }
 
 static void parse_options(struct exfat* ef, const char* options)
 {
 }
 
 static void parse_options(struct exfat* ef, const char* options)
 {
-       int sys_umask = umask(0);
        int opt_umask;
 
        int opt_umask;
 
-       umask(sys_umask); /* restore umask */
-       opt_umask = get_int_option(options, "umask", 8, sys_umask);
-       ef->dmask = get_int_option(options, "dmask", 8, opt_umask) & 0777;
-       ef->fmask = get_int_option(options, "fmask", 8, opt_umask) & 0777;
+       opt_umask = get_int_option(options, "umask", 8, 0);
+       ef->dmask = get_int_option(options, "dmask", 8, opt_umask);
+       ef->fmask = get_int_option(options, "fmask", 8, opt_umask);
 
        ef->uid = get_int_option(options, "uid", 10, geteuid());
        ef->gid = get_int_option(options, "gid", 10, getegid());
 
 
        ef->uid = get_int_option(options, "uid", 10, geteuid());
        ef->gid = get_int_option(options, "gid", 10, getegid());
 
-       ef->ro = match_option(options, "ro");
        ef->noatime = match_option(options, "noatime");
 }
 
        ef->noatime = match_option(options, "noatime");
 }
 
-static int verify_vbr_checksum(void* sector, off_t sector_size, int fd)
+static bool verify_vbr_checksum(struct exfat_dev* dev, void* sector,
+               off_t sector_size)
 {
        uint32_t vbr_checksum;
        int i;
 
 {
        uint32_t vbr_checksum;
        int i;
 
-       exfat_read_raw(sector, sector_size, 0, fd);
+       if (exfat_pread(dev, sector, sector_size, 0) < 0)
+       {
+               exfat_error("failed to read boot sector");
+               return false;
+       }
        vbr_checksum = exfat_vbr_start_checksum(sector, sector_size);
        for (i = 1; i < 11; i++)
        {
        vbr_checksum = exfat_vbr_start_checksum(sector, sector_size);
        for (i = 1; i < 11; i++)
        {
-               exfat_read_raw(sector, sector_size, i * sector_size, fd);
+               if (exfat_pread(dev, sector, sector_size, i * sector_size) < 0)
+               {
+                       exfat_error("failed to read VBR sector");
+                       return false;
+               }
                vbr_checksum = exfat_vbr_add_checksum(sector, sector_size,
                                vbr_checksum);
        }
                vbr_checksum = exfat_vbr_add_checksum(sector, sector_size,
                                vbr_checksum);
        }
-       exfat_read_raw(sector, sector_size, i * sector_size, fd);
+       if (exfat_pread(dev, sector, sector_size, i * sector_size) < 0)
+       {
+               exfat_error("failed to read VBR checksum sector");
+               return false;
+       }
        for (i = 0; i < sector_size / sizeof(vbr_checksum); i++)
                if (le32_to_cpu(((const le32_t*) sector)[i]) != vbr_checksum)
                {
                        exfat_error("invalid VBR checksum 0x%x (expected 0x%x)",
                                        le32_to_cpu(((const le32_t*) sector)[i]), vbr_checksum);
        for (i = 0; i < sector_size / sizeof(vbr_checksum); i++)
                if (le32_to_cpu(((const le32_t*) sector)[i]) != vbr_checksum)
                {
                        exfat_error("invalid VBR checksum 0x%x (expected 0x%x)",
                                        le32_to_cpu(((const le32_t*) sector)[i]), vbr_checksum);
-                       return 1;
+                       return false;
                }
                }
-       return 0;
+       return true;
+}
+
+static int commit_super_block(const struct exfat* ef)
+{
+       if (exfat_pwrite(ef->dev, ef->sb, sizeof(struct exfat_super_block), 0) < 0)
+       {
+               exfat_error("failed to write super block");
+               return 1;
+       }
+       return exfat_fsync(ef->dev);
+}
+
+static int prepare_super_block(const struct exfat* ef)
+{
+       if (le16_to_cpu(ef->sb->volume_state) & EXFAT_STATE_MOUNTED)
+               exfat_warn("volume was not unmounted cleanly");
+
+       if (ef->ro)
+               return 0;
+
+       ef->sb->volume_state = cpu_to_le16(
+                       le16_to_cpu(ef->sb->volume_state) | EXFAT_STATE_MOUNTED);
+       return commit_super_block(ef);
 }
 
 int exfat_mount(struct exfat* ef, const char* spec, const char* options)
 {
        int rc;
 }
 
 int exfat_mount(struct exfat* ef, const char* spec, const char* options)
 {
        int rc;
+       enum exfat_mode mode;
 
 
-       tzset();
+       exfat_tzset();
        memset(ef, 0, sizeof(struct exfat));
 
        memset(ef, 0, sizeof(struct exfat));
 
+       parse_options(ef, options);
+
+       if (match_option(options, "ro"))
+               mode = EXFAT_MODE_RO;
+       else if (match_option(options, "ro_fallback"))
+               mode = EXFAT_MODE_ANY;
+       else
+               mode = EXFAT_MODE_RW;
+       ef->dev = exfat_open(spec, mode);
+       if (ef->dev == NULL)
+               return -EIO;
+       if (exfat_get_mode(ef->dev) == EXFAT_MODE_RO)
+       {
+               if (mode == EXFAT_MODE_ANY)
+                       ef->ro = -1;
+               else
+                       ef->ro = 1;
+       }
+
        ef->sb = malloc(sizeof(struct exfat_super_block));
        if (ef->sb == NULL)
        {
        ef->sb = malloc(sizeof(struct exfat_super_block));
        if (ef->sb == NULL)
        {
-               exfat_error("memory allocation failed");
+               exfat_close(ef->dev);
+               exfat_error("failed to allocate memory for the super block");
                return -ENOMEM;
        }
        memset(ef->sb, 0, sizeof(struct exfat_super_block));
 
                return -ENOMEM;
        }
        memset(ef->sb, 0, sizeof(struct exfat_super_block));
 
-       parse_options(ef, options);
-
-       ef->fd = exfat_open(spec, ef->ro);
-       if (ef->fd < 0)
+       if (exfat_pread(ef->dev, ef->sb, sizeof(struct exfat_super_block), 0) < 0)
        {
        {
+               exfat_close(ef->dev);
                free(ef->sb);
                free(ef->sb);
+               exfat_error("failed to read boot sector");
                return -EIO;
        }
                return -EIO;
        }
-
-       exfat_read_raw(ef->sb, sizeof(struct exfat_super_block), 0, ef->fd);
        if (memcmp(ef->sb->oem_name, "EXFAT   ", 8) != 0)
        {
        if (memcmp(ef->sb->oem_name, "EXFAT   ", 8) != 0)
        {
-               close(ef->fd);
+               exfat_close(ef->dev);
                free(ef->sb);
                exfat_error("exFAT file system is not found");
                return -EIO;
        }
                free(ef->sb);
                exfat_error("exFAT file system is not found");
                return -EIO;
        }
-       if (ef->sb->version.major != 1 || ef->sb->version.minor != 0)
-       {
-               close(ef->fd);
-               exfat_error("unsupported exFAT version: %hhu.%hhu",
-                               ef->sb->version.major, ef->sb->version.minor);
-               free(ef->sb);
-               return -EIO;
-       }
-       if (ef->sb->fat_count != 1)
+       /* sector cannot be smaller than 512 bytes */
+       if (ef->sb->sector_bits < 9)
        {
        {
-               close(ef->fd);
+               exfat_close(ef->dev);
+               exfat_error("too small sector size: 2^%hhd", ef->sb->sector_bits);
                free(ef->sb);
                free(ef->sb);
-               exfat_error("unsupported FAT count: %hhu", ef->sb->fat_count);
                return -EIO;
        }
        /* officially exFAT supports cluster size up to 32 MB */
        if ((int) ef->sb->sector_bits + (int) ef->sb->spc_bits > 25)
        {
                return -EIO;
        }
        /* officially exFAT supports cluster size up to 32 MB */
        if ((int) ef->sb->sector_bits + (int) ef->sb->spc_bits > 25)
        {
-               close(ef->fd);
+               exfat_close(ef->dev);
+               exfat_error("too big cluster size: 2^(%hhd+%hhd)",
+                               ef->sb->sector_bits, ef->sb->spc_bits);
                free(ef->sb);
                free(ef->sb);
-               exfat_error("too big cluster size: 2^%d",
-                               (int) ef->sb->sector_bits + (int) ef->sb->spc_bits);
                return -EIO;
        }
                return -EIO;
        }
-
-       ef->zero_sector = malloc(SECTOR_SIZE(*ef->sb));
-       if (ef->zero_sector == NULL)
+       ef->zero_cluster = malloc(CLUSTER_SIZE(*ef->sb));
+       if (ef->zero_cluster == NULL)
        {
        {
-               close(ef->fd);
+               exfat_close(ef->dev);
                free(ef->sb);
                exfat_error("failed to allocate zero sector");
                return -ENOMEM;
        }
                free(ef->sb);
                exfat_error("failed to allocate zero sector");
                return -ENOMEM;
        }
-       /* use zero_sector as a temporary buffer for VBR checksum verification */
-       if (verify_vbr_checksum(ef->zero_sector, SECTOR_SIZE(*ef->sb), ef->fd) != 0)
+       /* use zero_cluster as a temporary buffer for VBR checksum verification */
+       if (!verify_vbr_checksum(ef->dev, ef->zero_cluster, SECTOR_SIZE(*ef->sb)))
        {
        {
-               free(ef->zero_sector);
-               close(ef->fd);
+               free(ef->zero_cluster);
+               exfat_close(ef->dev);
                free(ef->sb);
                return -EIO;
        }
                free(ef->sb);
                return -EIO;
        }
-       memset(ef->zero_sector, 0, SECTOR_SIZE(*ef->sb));
+       memset(ef->zero_cluster, 0, CLUSTER_SIZE(*ef->sb));
+       if (ef->sb->version.major != 1 || ef->sb->version.minor != 0)
+       {
+               free(ef->zero_cluster);
+               exfat_close(ef->dev);
+               exfat_error("unsupported exFAT version: %hhu.%hhu",
+                               ef->sb->version.major, ef->sb->version.minor);
+               free(ef->sb);
+               return -EIO;
+       }
+       if (ef->sb->fat_count != 1)
+       {
+               free(ef->zero_cluster);
+               exfat_close(ef->dev);
+               exfat_error("unsupported FAT count: %hhu", ef->sb->fat_count);
+               free(ef->sb);
+               return -EIO;
+       }
+       if (le64_to_cpu(ef->sb->sector_count) * SECTOR_SIZE(*ef->sb) >
+                       exfat_get_size(ef->dev))
+       {
+               /* this can cause I/O errors later but we don't fail mounting to let
+                  user rescue data */
+               exfat_warn("file system is larger than underlying device: "
+                               "%"PRIu64" > %"PRIu64,
+                               le64_to_cpu(ef->sb->sector_count) * SECTOR_SIZE(*ef->sb),
+                               exfat_get_size(ef->dev));
+       }
 
        ef->root = malloc(sizeof(struct exfat_node));
        if (ef->root == NULL)
        {
 
        ef->root = malloc(sizeof(struct exfat_node));
        if (ef->root == NULL)
        {
-               free(ef->zero_sector);
-               close(ef->fd);
+               free(ef->zero_cluster);
+               exfat_close(ef->dev);
                free(ef->sb);
                exfat_error("failed to allocate root node");
                return -ENOMEM;
                free(ef->sb);
                exfat_error("failed to allocate root node");
                return -ENOMEM;
@@ -206,6 +292,14 @@ int exfat_mount(struct exfat* ef, const char* spec, const char* options)
        ef->root->fptr_cluster = ef->root->start_cluster;
        ef->root->name[0] = cpu_to_le16('\0');
        ef->root->size = rootdir_size(ef);
        ef->root->fptr_cluster = ef->root->start_cluster;
        ef->root->name[0] = cpu_to_le16('\0');
        ef->root->size = rootdir_size(ef);
+       if (ef->root->size == 0)
+       {
+               free(ef->root);
+               free(ef->zero_cluster);
+               exfat_close(ef->dev);
+               free(ef->sb);
+               return -EIO;
+       }
        /* exFAT does not have time attributes for the root directory */
        ef->root->mtime = 0;
        ef->root->atime = 0;
        /* exFAT does not have time attributes for the root directory */
        ef->root->mtime = 0;
        ef->root->atime = 0;
@@ -226,33 +320,57 @@ int exfat_mount(struct exfat* ef, const char* spec, const char* options)
                goto error;
        }
 
                goto error;
        }
 
+       if (prepare_super_block(ef) != 0)
+               goto error;
+
        return 0;
 
 error:
        exfat_put_node(ef, ef->root);
        exfat_reset_cache(ef);
        free(ef->root);
        return 0;
 
 error:
        exfat_put_node(ef, ef->root);
        exfat_reset_cache(ef);
        free(ef->root);
-       free(ef->zero_sector);
-       close(ef->fd);
+       free(ef->zero_cluster);
+       exfat_close(ef->dev);
        free(ef->sb);
        return -EIO;
 }
 
        free(ef->sb);
        return -EIO;
 }
 
+static void finalize_super_block(struct exfat* ef)
+{
+       if (ef->ro)
+               return;
+
+       ef->sb->volume_state = cpu_to_le16(
+                       le16_to_cpu(ef->sb->volume_state) & ~EXFAT_STATE_MOUNTED);
+
+       /* Some implementations set the percentage of allocated space to 0xff
+          on FS creation and never update it. In this case leave it as is. */
+       if (ef->sb->allocated_percent != 0xff)
+       {
+               uint32_t free, total;
+
+               free = exfat_count_free_clusters(ef);
+               total = le32_to_cpu(ef->sb->cluster_count);
+               ef->sb->allocated_percent = ((total - free) * 100 + total / 2) / total;
+       }
+
+       commit_super_block(ef); /* ignore return code */
+}
+
 void exfat_unmount(struct exfat* ef)
 {
 void exfat_unmount(struct exfat* ef)
 {
+       exfat_flush(ef);        /* ignore return code */
        exfat_put_node(ef, ef->root);
        exfat_reset_cache(ef);
        free(ef->root);
        ef->root = NULL;
        exfat_put_node(ef, ef->root);
        exfat_reset_cache(ef);
        free(ef->root);
        ef->root = NULL;
-       free(ef->zero_sector);
-       ef->zero_sector = NULL;
+       finalize_super_block(ef);
+       exfat_close(ef->dev);   /* close descriptor immediately after fsync */
+       ef->dev = NULL;
+       free(ef->zero_cluster);
+       ef->zero_cluster = NULL;
        free(ef->cmap.chunk);
        ef->cmap.chunk = NULL;
        free(ef->cmap.chunk);
        ef->cmap.chunk = NULL;
-       if (fsync(ef->fd) < 0)
-               exfat_error("fsync failed");
-       if (close(ef->fd) < 0)
-               exfat_error("close failed");
-       ef->fd = 0;
        free(ef->sb);
        ef->sb = NULL;
        free(ef->upcase);
        free(ef->sb);
        ef->sb = NULL;
        free(ef->upcase);