]> git.sven.stormbind.net Git - sven/exfat-utils.git/blobdiff - libexfat/mount.c
Imported Upstream version 1.1.1
[sven/exfat-utils.git] / libexfat / mount.c
index 4240f63b7a34efece8f08f30db1feb241e64bb44..2ebf43625315701e99eba3a543250420a44ce60c 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-2014  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)
 {
@@ -39,6 +40,12 @@ static uint64_t rootdir_size(const struct exfat* ef)
                   to indicate this */
                rootdir_cluster = exfat_next_cluster(ef, ef->root, rootdir_cluster);
        }
                   to indicate this */
                rootdir_cluster = exfat_next_cluster(ef, ef->root, rootdir_cluster);
        }
+       if (rootdir_cluster != EXFAT_CLUSTER_END)
+       {
+               exfat_error("bad cluster %#x while reading root directory",
+                               rootdir_cluster);
+               return 0;
+       }
        return clusters * CLUSTER_SIZE(*ef->sb);
 }
 
        return clusters * CLUSTER_SIZE(*ef->sb);
 }
 
@@ -63,7 +70,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,8 +78,8 @@ 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)
@@ -88,69 +95,140 @@ static void parse_options(struct exfat* ef, const char* options)
        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;
        }
+       ef->zero_cluster = malloc(CLUSTER_SIZE(*ef->sb));
+       if (ef->zero_cluster == NULL)
+       {
+               exfat_close(ef->dev);
+               free(ef->sb);
+               exfat_error("failed to allocate zero sector");
+               return -ENOMEM;
+       }
+       /* 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_cluster);
+               exfat_close(ef->dev);
+               free(ef->sb);
+               return -EIO;
+       }
+       memset(ef->zero_cluster, 0, CLUSTER_SIZE(*ef->sb));
        if (ef->sb->version.major != 1 || ef->sb->version.minor != 0)
        {
        if (ef->sb->version.major != 1 || ef->sb->version.minor != 0)
        {
-               close(ef->fd);
+               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);
                exfat_error("unsupported exFAT version: %hhu.%hhu",
                                ef->sb->version.major, ef->sb->version.minor);
                free(ef->sb);
@@ -158,44 +236,40 @@ int exfat_mount(struct exfat* ef, const char* spec, const char* options)
        }
        if (ef->sb->fat_count != 1)
        {
        }
        if (ef->sb->fat_count != 1)
        {
-               close(ef->fd);
-               free(ef->sb);
+               free(ef->zero_cluster);
+               exfat_close(ef->dev);
                exfat_error("unsupported FAT count: %hhu", ef->sb->fat_count);
                exfat_error("unsupported FAT count: %hhu", ef->sb->fat_count);
+               free(ef->sb);
                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);
-               free(ef->sb);
+               free(ef->zero_cluster);
+               exfat_close(ef->dev);
                exfat_error("too big cluster size: 2^%d",
                                (int) ef->sb->sector_bits + (int) ef->sb->spc_bits);
                exfat_error("too big cluster size: 2^%d",
                                (int) ef->sb->sector_bits + (int) ef->sb->spc_bits);
-               return -EIO;
-       }
-
-       ef->zero_sector = malloc(SECTOR_SIZE(*ef->sb));
-       if (ef->zero_sector == NULL)
-       {
-               close(ef->fd);
                free(ef->sb);
                free(ef->sb);
-               exfat_error("failed to allocate zero sector");
-               return -ENOMEM;
+               return -EIO;
        }
        }
-       /* 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)
+       if (le64_to_cpu(ef->sb->sector_count) * SECTOR_SIZE(*ef->sb) >
+                       exfat_get_size(ef->dev))
        {
        {
-               free(ef->zero_sector);
-               close(ef->fd);
+               free(ef->zero_cluster);
+               exfat_error("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));
+               exfat_close(ef->dev);
                free(ef->sb);
                return -EIO;
        }
                free(ef->sb);
                return -EIO;
        }
-       memset(ef->zero_sector, 0, SECTOR_SIZE(*ef->sb));
 
        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 +280,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 +308,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);