OSDN Git Service

Relicensed the code from GPLv3+ to GPLv2+.
[android-x86/external-exfat.git] / libexfat / mount.c
index ae7e098..addb263 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-2013  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 <fcntl.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <sys/types.h>
-#include <sys/stat.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)
 {
@@ -65,7 +63,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);
@@ -73,8 +71,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)
@@ -90,69 +88,100 @@ 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* block, off_t block_size, int fd)
+static int 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(block, block_size, 0, fd);
-       vbr_checksum = exfat_vbr_start_checksum(block, block_size);
+       exfat_pread(dev, sector, sector_size, 0);
+       vbr_checksum = exfat_vbr_start_checksum(sector, sector_size);
        for (i = 1; i < 11; i++)
        {
        for (i = 1; i < 11; i++)
        {
-               exfat_read_raw(block, block_size, i * block_size, fd);
-               vbr_checksum = exfat_vbr_add_checksum(block, block_size, vbr_checksum);
+               exfat_pread(dev, sector, sector_size, i * sector_size);
+               vbr_checksum = exfat_vbr_add_checksum(sector, sector_size,
+                               vbr_checksum);
        }
        }
-       exfat_read_raw(block, block_size, i * block_size, fd);
-       for (i = 0; i < block_size / sizeof(vbr_checksum); i++)
-               if (((const uint32_t*) block)[i] != vbr_checksum)
+       exfat_pread(dev, sector, sector_size, i * sector_size);
+       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)",
                {
                        exfat_error("invalid VBR checksum 0x%x (expected 0x%x)",
-                                       ((const uint32_t*) block)[i], vbr_checksum);
+                                       le32_to_cpu(((const le32_t*) sector)[i]), vbr_checksum);
                        return 1;
                }
        return 0;
 }
 
                        return 1;
                }
        return 0;
 }
 
+static int commit_super_block(const struct exfat* ef)
+{
+       exfat_pwrite(ef->dev, ef->sb, sizeof(struct exfat_super_block), 0);
+       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 = open(spec, ef->ro ? O_RDONLY : O_RDWR);
-       if (ef->fd < 0)
-       {
-               free(ef->sb);
-               exfat_error("failed to open `%s'", spec);
-               return -EIO;
-       }
-
-       exfat_read_raw(ef->sb, sizeof(struct exfat_super_block), 0, ef->fd);
+       exfat_pread(ef->dev, ef->sb, sizeof(struct exfat_super_block), 0);
        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;
        }
        if (ef->sb->version.major != 1 || ef->sb->version.minor != 0)
        {
                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_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);
@@ -160,44 +189,45 @@ 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);
+               exfat_close(ef->dev);
                free(ef->sb);
                exfat_error("unsupported FAT count: %hhu", ef->sb->fat_count);
                return -EIO;
        }
        /* officially exFAT supports cluster size up to 32 MB */
                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->block_bits + (int) ef->sb->bpc_bits > 25)
+       if ((int) ef->sb->sector_bits + (int) ef->sb->spc_bits > 25)
        {
        {
-               close(ef->fd);
+               exfat_close(ef->dev);
                free(ef->sb);
                exfat_error("too big cluster size: 2^%d",
                free(ef->sb);
                exfat_error("too big cluster size: 2^%d",
-                               (int) ef->sb->block_bits + (int) ef->sb->bpc_bits);
+                               (int) ef->sb->sector_bits + (int) ef->sb->spc_bits);
                return -EIO;
        }
 
                return -EIO;
        }
 
-       ef->zero_block = malloc(BLOCK_SIZE(*ef->sb));
-       if (ef->zero_block == NULL)
+       ef->zero_cluster = malloc(CLUSTER_SIZE(*ef->sb));
+       if (ef->zero_cluster == NULL)
        {
        {
-               close(ef->fd);
+               exfat_close(ef->dev);
                free(ef->sb);
                free(ef->sb);
-               exfat_error("failed to allocate zero block");
+               exfat_error("failed to allocate zero sector");
                return -ENOMEM;
        }
                return -ENOMEM;
        }
-       /* use zero_block as a temporary buffer for VBR checksum verification */
-       if (verify_vbr_checksum(ef->zero_block, BLOCK_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)) != 0)
        {
        {
-               free(ef->zero_block);
-               close(ef->fd);
+               free(ef->zero_cluster);
+               exfat_close(ef->dev);
                free(ef->sb);
                return -EIO;
        }
                free(ef->sb);
                return -EIO;
        }
-       memset(ef->zero_block, 0, BLOCK_SIZE(*ef->sb));
+       memset(ef->zero_cluster, 0, CLUSTER_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_block);
-               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;
@@ -228,33 +258,56 @@ 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_block);
-       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);
+}
+
 void exfat_unmount(struct exfat* ef)
 {
        exfat_put_node(ef, ef->root);
        exfat_reset_cache(ef);
        free(ef->root);
        ef->root = NULL;
 void exfat_unmount(struct exfat* ef)
 {
        exfat_put_node(ef, ef->root);
        exfat_reset_cache(ef);
        free(ef->root);
        ef->root = NULL;
-       free(ef->zero_block);
-       ef->zero_block = 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);