Merge commit 'upstream/0.9.6'
authorSven Hoexter <sven@timegate.de>
Sat, 14 Jan 2012 12:30:13 +0000 (13:30 +0100)
committerSven Hoexter <sven@timegate.de>
Sat, 14 Jan 2012 12:30:13 +0000 (13:30 +0100)
ChangeLog
libexfat/cluster.c
libexfat/exfat.h
libexfat/exfatfs.h
libexfat/io.c
libexfat/mount.c
libexfat/node.c
libexfat/utils.c
libexfat/version.h
mkfs/main.c

index 24539d6..2f46f63 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+0.9.6 (2012-01-14)
+
+* Fixed write performance regression introduced in 0.9.4.
+* Mount in read-only mode if the device is write-protected.
+* Set ctime to mtime to ensure we don't break programs that rely on ctime
+(e.g. rsync considered that all files are outdated) [Eldad Zack].
+* Indicate that FS in not clean when it was not cleanly unmounted.
+* Utilities are now compatible with GNU/Hurd.
+* Fixed several memory leaks that could occur on error handling paths.
+* Improved handling of corrupted file systems.
+
 0.9.5 (2011-05-15)
 
 * Fixed erasing of the root directory cluster when creating a new FS with
index 8bea507..7251c31 100644 (file)
@@ -325,37 +325,38 @@ static int shrink_file(struct exfat* ef, struct exfat_node* node,
 
 static void erase_raw(struct exfat* ef, size_t size, off_t offset)
 {
-       exfat_write_raw(ef->zero_sector, size, offset, ef->fd);
+       exfat_write_raw(ef->zero_cluster, size, offset, ef->fd);
 }
 
 static int erase_range(struct exfat* ef, struct exfat_node* node,
                uint64_t begin, uint64_t end)
 {
-       uint64_t sector_boundary;
+       uint64_t cluster_boundary;
        cluster_t cluster;
 
        if (begin >= end)
                return 0;
 
-       sector_boundary = (node->size | (SECTOR_SIZE(*ef->sb) - 1)) + 1;
+       cluster_boundary = (begin | (CLUSTER_SIZE(*ef->sb) - 1)) + 1;
        cluster = exfat_advance_cluster(ef, node,
-                       node->size / CLUSTER_SIZE(*ef->sb));
+                       begin / CLUSTER_SIZE(*ef->sb));
        if (CLUSTER_INVALID(cluster))
        {
                exfat_error("invalid cluster in file");
                return -EIO;
        }
-       /* erase from the beginning to the closest sector boundary */
-       erase_raw(ef, MIN(sector_boundary, end) - node->size,
-                       exfat_c2o(ef, cluster) + node->size % CLUSTER_SIZE(*ef->sb));
-       /* erase whole sectors */
-       while (sector_boundary < end)
+       /* erase from the beginning to the closest cluster boundary */
+       erase_raw(ef, MIN(cluster_boundary, end) - begin,
+                       exfat_c2o(ef, cluster) + begin % CLUSTER_SIZE(*ef->sb));
+       /* erase whole clusters */
+       while (cluster_boundary < end)
        {
-               if (sector_boundary % CLUSTER_SIZE(*ef->sb) == 0)
-                       cluster = exfat_next_cluster(ef, node, cluster);
-               erase_raw(ef, SECTOR_SIZE(*ef->sb),
-                       exfat_c2o(ef, cluster) + sector_boundary % CLUSTER_SIZE(*ef->sb));
-               sector_boundary += SECTOR_SIZE(*ef->sb);
+               cluster = exfat_next_cluster(ef, node, cluster);
+               /* the cluster cannot be invalid because we have just allocated it */
+               if (CLUSTER_INVALID(cluster))
+                       exfat_bug("invalid cluster in file");
+               erase_raw(ef, CLUSTER_SIZE(*ef->sb), exfat_c2o(ef, cluster));
+               cluster_boundary += CLUSTER_SIZE(*ef->sb);
        }
        return 0;
 }
index 84673dc..de2e1a2 100644 (file)
@@ -87,11 +87,12 @@ struct exfat
        cmap;
        char label[EXFAT_ENAME_MAX * 6 + 1]; /* a character can occupy up to
                                                                                        6 bytes in UTF-8 */
-       void* zero_sector;
+       void* zero_cluster;
        int dmask, fmask;
        uid_t uid;
        gid_t gid;
        int ro;
+       int ro_fallback;
        int noatime;
 };
 
index e7d7ee5..be75395 100644 (file)
@@ -31,6 +31,8 @@ typedef uint32_t cluster_t;           /* cluster number */
 #define EXFAT_CLUSTER_BAD 0xfffffff7 /* cluster contains bad sector */
 #define EXFAT_CLUSTER_END 0xffffffff /* final cluster of file or directory */
 
+#define EXFAT_STATE_MOUNTED 2
+
 struct exfat_super_block
 {
        uint8_t jump[3];                                /* 0x00 jmp and nop instructions */
index 3d63fa9..25988f0 100644 (file)
@@ -39,7 +39,8 @@ int exfat_open(const char* spec, int ro)
        fd = open(spec, ro ? O_RDONLY : O_RDWR);
        if (fd < 0)
        {
-               exfat_error("failed to open `%s'", spec);
+               exfat_error("failed to open `%s' in read-%s mode", spec,
+                               ro ? "only" : "write");
                return -1;
        }
        if (fstat(fd, &stbuf) != 0)
index 4240f63..295d455 100644 (file)
@@ -116,6 +116,30 @@ static int verify_vbr_checksum(void* sector, off_t sector_size, int fd)
        return 0;
 }
 
+static int commit_super_block(const struct exfat* ef)
+{
+       exfat_write_raw(ef->sb, sizeof(struct exfat_super_block), 0, ef->fd);
+       if (fsync(ef->fd) < 0)
+       {
+               exfat_error("fsync failed");
+               return 1;
+       }
+       return 0;
+}
+
+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;
@@ -123,23 +147,29 @@ int exfat_mount(struct exfat* ef, const char* spec, const char* options)
        tzset();
        memset(ef, 0, sizeof(struct exfat));
 
-       ef->sb = malloc(sizeof(struct exfat_super_block));
-       if (ef->sb == NULL)
-       {
-               exfat_error("memory allocation failed");
-               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)
        {
-               free(ef->sb);
-               return -EIO;
+               if (ef->ro || !match_option(options, "ro_fallback"))
+                       return -EIO;
+               ef->fd = exfat_open(spec, 1);
+               if (ef->fd < 0)
+                       return -EIO;
+               exfat_warn("device is write-protected, mounting read-only");
+               ef->ro_fallback = ef->ro = 1;
        }
 
+       ef->sb = malloc(sizeof(struct exfat_super_block));
+       if (ef->sb == NULL)
+       {
+               close(ef->fd);
+               exfat_error("failed to allocate memory for the super block");
+               return -ENOMEM;
+       }
+       memset(ef->sb, 0, sizeof(struct exfat_super_block));
+
        exfat_read_raw(ef->sb, sizeof(struct exfat_super_block), 0, ef->fd);
        if (memcmp(ef->sb->oem_name, "EXFAT   ", 8) != 0)
        {
@@ -173,28 +203,29 @@ int exfat_mount(struct exfat* ef, const char* spec, const char* options)
                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);
                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->zero_cluster, SECTOR_SIZE(*ef->sb),
+                       ef->fd) != 0)
        {
-               free(ef->zero_sector);
+               free(ef->zero_cluster);
                close(ef->fd);
                free(ef->sb);
                return -EIO;
        }
-       memset(ef->zero_sector, 0, SECTOR_SIZE(*ef->sb));
+       memset(ef->zero_cluster, 0, CLUSTER_SIZE(*ef->sb));
 
        ef->root = malloc(sizeof(struct exfat_node));
        if (ef->root == NULL)
        {
-               free(ef->zero_sector);
+               free(ef->zero_cluster);
                close(ef->fd);
                free(ef->sb);
                exfat_error("failed to allocate root node");
@@ -226,33 +257,57 @@ int exfat_mount(struct exfat* ef, const char* spec, const char* options)
                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);
-       free(ef->zero_sector);
+       free(ef->zero_cluster);
        close(ef->fd);
        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;
-       free(ef->zero_sector);
-       ef->zero_sector = NULL;
-       free(ef->cmap.chunk);
-       ef->cmap.chunk = NULL;
-       if (fsync(ef->fd) < 0)
-               exfat_error("fsync failed");
-       if (close(ef->fd) < 0)
+       finalize_super_block(ef);
+       if (close(ef->fd) < 0)  /* close descriptor immediately after fsync */
                exfat_error("close failed");
        ef->fd = 0;
+       free(ef->zero_cluster);
+       ef->zero_cluster = NULL;
+       free(ef->cmap.chunk);
+       ef->cmap.chunk = NULL;
        free(ef->sb);
        ef->sb = NULL;
        free(ef->upcase);
index fa9e572..0920847 100644 (file)
@@ -108,6 +108,12 @@ static int fetch_next_entry(struct exfat* ef, const struct exfat_node* parent,
        /* fetch the next cluster if needed */
        if ((it->offset & (CLUSTER_SIZE(*ef->sb) - 1)) == 0)
        {
+               if (it->offset >= parent->size)
+               {
+                       exfat_error("missing EOD entry (0x%"PRIx64", 0x%"PRIx64")",
+                                       it->offset, parent->size);
+                       return 1;
+               }
                it->cluster = exfat_next_cluster(ef, parent, it->cluster);
                if (CLUSTER_INVALID(it->cluster))
                {
@@ -166,6 +172,7 @@ static const struct exfat_entry* get_entry_ptr(const struct exfat* ef,
 static int readdir(struct exfat* ef, const struct exfat_node* parent,
                struct exfat_node** node, struct iterator* it)
 {
+       int rc = -EIO;
        const struct exfat_entry* entry;
        const struct exfat_entry_meta1* meta1;
        const struct exfat_entry_meta2* meta2;
@@ -211,13 +218,16 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        if (continuations < 2)
                        {
                                exfat_error("too few continuations (%hhu)", continuations);
-                               return -EIO;
+                               goto error;
                        }
                        reference_checksum = le16_to_cpu(meta1->checksum);
                        actual_checksum = exfat_start_checksum(meta1);
                        *node = allocate_node();
                        if (*node == NULL)
-                               return -ENOMEM;
+                       {
+                               rc = -ENOMEM;
+                               goto error;
+                       }
                        /* new node has zero reference counter */
                        (*node)->entry_cluster = it->cluster;
                        (*node)->entry_offset = it->offset;
@@ -290,7 +300,7 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                {
                                        exfat_error("invalid checksum (0x%hx != 0x%hx)",
                                                        actual_checksum, reference_checksum);
-                                       return -EIO;
+                                       goto error;
                                }
                                if (fetch_next_entry(ef, parent, it) != 0)
                                        goto error;
@@ -305,7 +315,7 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        if (CLUSTER_INVALID(le32_to_cpu(upcase->start_cluster)))
                        {
                                exfat_error("invalid cluster in upcase table");
-                               return -EIO;
+                               goto error;
                        }
                        if (le64_to_cpu(upcase->size) == 0 ||
                                le64_to_cpu(upcase->size) > 0xffff * sizeof(uint16_t) ||
@@ -313,14 +323,15 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        {
                                exfat_error("bad upcase table size (%"PRIu64" bytes)",
                                                le64_to_cpu(upcase->size));
-                               return -EIO;
+                               goto error;
                        }
                        ef->upcase = malloc(le64_to_cpu(upcase->size));
                        if (ef->upcase == NULL)
                        {
                                exfat_error("failed to allocate upcase table (%"PRIu64" bytes)",
                                                le64_to_cpu(upcase->size));
-                               return -ENOMEM;
+                               rc = -ENOMEM;
+                               goto error;
                        }
                        ef->upcase_chars = le64_to_cpu(upcase->size) / sizeof(le16_t);
 
@@ -333,7 +344,7 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        if (CLUSTER_INVALID(le32_to_cpu(bitmap->start_cluster)))
                        {
                                exfat_error("invalid cluster in clusters bitmap");
-                               return -EIO;
+                               goto error;
                        }
                        ef->cmap.size = le32_to_cpu(ef->sb->cluster_count) -
                                EXFAT_FIRST_DATA_CLUSTER;
@@ -342,7 +353,7 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                exfat_error("invalid clusters bitmap size: %"PRIu64
                                                " (expected at least %u)",
                                                le64_to_cpu(bitmap->size), (ef->cmap.size + 7) / 8);
-                               return -EIO;
+                               goto error;
                        }
                        ef->cmap.start_cluster = le32_to_cpu(bitmap->start_cluster);
                        /* FIXME bitmap can be rather big, up to 512 MB */
@@ -352,7 +363,8 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        {
                                exfat_error("failed to allocate clusters bitmap chunk "
                                                "(%"PRIu64" bytes)", le64_to_cpu(bitmap->size));
-                               return -ENOMEM;
+                               rc = -ENOMEM;
+                               goto error;
                        }
 
                        exfat_read_raw(ef->cmap.chunk, le64_to_cpu(bitmap->size),
@@ -364,11 +376,11 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        if (label->length > EXFAT_ENAME_MAX)
                        {
                                exfat_error("too long label (%hhu chars)", label->length);
-                               return -EIO;
+                               goto error;
                        }
                        if (utf16_to_utf8(ef->label, label->name,
                                                sizeof(ef->label), EXFAT_ENAME_MAX) != 0)
-                               return -EIO;
+                               goto error;
                        break;
 
                default:
@@ -388,7 +400,7 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
 error:
        free(*node);
        *node = NULL;
-       return -EIO;
+       return rc;
 }
 
 int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
index 39fdd48..31b7e8c 100644 (file)
@@ -41,7 +41,9 @@ void exfat_stat(const struct exfat* ef, const struct exfat_node* node,
                CLUSTER_SIZE(*ef->sb) / 512;
        stbuf->st_mtime = node->mtime;
        stbuf->st_atime = node->atime;
-       stbuf->st_ctime = 0; /* unapplicable */
+       /* set ctime to mtime to ensure we don't break programs that rely on ctime
+          (e.g. rsync) */
+       stbuf->st_ctime = node->mtime;
 }
 
 #define SEC_IN_MIN 60ll
@@ -258,14 +260,20 @@ void exfat_humanize_bytes(uint64_t value, struct exfat_human_bytes* hb)
        size_t i;
        const char* units[] = {"bytes", "KB", "MB", "GB", "TB", "PB"};
        uint64_t divisor = 1;
+       uint64_t temp = 0;
 
-       for (i = 0; i < sizeof(units) / sizeof(units[0]) - 1; i++)
+       for (i = 0; i < sizeof(units) / sizeof(units[0]) - 1; i++, divisor *= 1024)
        {
-               if ((value + divisor / 2) / divisor < 1024)
+               temp = (value + divisor / 2) / divisor;
+
+               if (temp == 0)
+                       break;
+               if (temp / 1024 * 1024 == temp)
+                       continue;
+               if (temp < 10240)
                        break;
-               divisor *= 1024;
        }
-       hb->value = (value + divisor / 2) / divisor;
+       hb->value = temp;
        hb->unit = units[i];
 }
 
index 061192f..0236f88 100644 (file)
@@ -23,6 +23,6 @@
 
 #define EXFAT_VERSION_MAJOR 0
 #define EXFAT_VERSION_MINOR 9
-#define EXFAT_VERSION_PATCH 5
+#define EXFAT_VERSION_PATCH 6
 
 #endif /* ifndef VERSION_H_INCLUDED */
index e6d49e1..8deca15 100644 (file)
@@ -102,9 +102,14 @@ static int init_sb(off_t volume_size, int sector_bits, int spc_bits,
 static int erase_device(int fd)
 {
        off_t erase_size;
-       off_t erase_sectors;
+       off_t erase_blocks;
+       long block_size;
+       void* block;
        off_t i;
-       void* sector;
+
+       block_size = sysconf(_SC_PAGESIZE);
+       if (block_size < 1)
+               block_size = 0x1000;
 
        erase_size = ((uint64_t)
                        le32_to_cpu(sb.fat_sector_start) +
@@ -116,7 +121,7 @@ static int erase_device(int fd)
        erase_size = ROUND_UP(erase_size, rootdir_alignment());
        erase_size += rootdir_size();
 
-       erase_sectors = erase_size / SECTOR_SIZE(sb);
+       erase_blocks = DIV_ROUND_UP(erase_size, block_size);
 
        if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
        {
@@ -124,29 +129,29 @@ static int erase_device(int fd)
                return 1;
        }
 
-       sector = malloc(SECTOR_SIZE(sb));
-       if (sector == NULL)
+       block = malloc(block_size);
+       if (block == NULL)
        {
-               exfat_error("failed to allocate erase sector");
+               exfat_error("failed to allocate erase block");
                return 1;
        }
-       memset(sector, 0, SECTOR_SIZE(sb));
+       memset(block, 0, block_size);
 
-       for (i = 0; i < erase_sectors; i++)
+       for (i = 0; i < erase_blocks; i++)
        {
-               if (write(fd, sector, SECTOR_SIZE(sb)) == -1)
+               if (write(fd, block, block_size) == -1)
                {
-                       free(sector);
-                       exfat_error("failed to erase sector %"PRIu64, i);
+                       free(block);
+                       exfat_error("failed to erase block %"PRIu64, i);
                        return 1;
                }
-               if (i * 100 / erase_sectors != (i + 1) * 100 / erase_sectors)
+               if (i * 100 / erase_blocks != (i + 1) * 100 / erase_blocks)
                {
-                       printf("\b\b\b%2"PRIu64"%%", (i + 1) * 100 / erase_sectors);
+                       printf("\b\b\b%2"PRIu64"%%", (i + 1) * 100 / erase_blocks);
                        fflush(stdout);
                }
        }
-       free(sector);
+       free(block);
        return 0;
 }
 
@@ -275,15 +280,8 @@ static int mkfs(const char* spec, int sector_bits, int spc_bits,
 {
        int fd;
        off_t volume_size;
-       char spec_abs[PATH_MAX];
-
-       if (realpath(spec, spec_abs) == NULL)
-       {
-               exfat_error("failed to get absolute path for `%s'", spec);
-               return 1;
-       }
 
-       fd = exfat_open(spec_abs, 0);
+       fd = exfat_open(spec, 0);
        if (fd < 0)
                return 1;
 
@@ -336,13 +334,13 @@ static int mkfs(const char* spec, int sector_bits, int spc_bits,
        if (fsync(fd) < 0)
        {
                close(fd);
-               exfat_error("fsync failed for `%s'", spec_abs);
+               exfat_error("fsync failed");
                return 1;
        }
        puts("done.");
        if (close(fd) < 0)
        {
-               exfat_error("close failed for `%s'", spec_abs);
+               exfat_error("close failed");
                return 1;
        }
        printf("File system created successfully.\n");