From a4248d0ee882aa5a48a1d74ca0162032c5a7d65e Mon Sep 17 00:00:00 2001 From: Sven Hoexter Date: Sat, 14 Jan 2012 13:31:40 +0100 Subject: [PATCH] Imported Upstream version 0.9.6 --- ChangeLog | 11 +++++ fuse/main.c | 12 +++++- libexfat/cluster.c | 29 +++++++------ libexfat/exfat.h | 3 +- libexfat/exfatfs.h | 2 + libexfat/io.c | 3 +- libexfat/mount.c | 105 ++++++++++++++++++++++++++++++++++----------- libexfat/node.c | 36 ++++++++++------ libexfat/utils.c | 18 +++++--- libexfat/version.h | 2 +- 10 files changed, 161 insertions(+), 60 deletions(-) diff --git a/ChangeLog b/ChangeLog index 24539d6..2f46f63 100644 --- 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 diff --git a/fuse/main.c b/fuse/main.c index fe5666b..a813427 100644 --- a/fuse/main.c +++ b/fuse/main.c @@ -37,7 +37,7 @@ #error FUSE 2.6 or later is required #endif -const char* default_options = "allow_other,blkdev"; +const char* default_options = "ro_fallback,allow_other,blkdev"; struct exfat ef; @@ -426,6 +426,16 @@ int main(int argc, char* argv[]) return 1; } + if (ef.ro_fallback) + { + mount_options = add_option(mount_options, "ro", NULL); + if (mount_options == NULL) + { + exfat_unmount(&ef); + return 1; + } + } + mount_options = add_fuse_options(mount_options, spec); if (mount_options == NULL) { diff --git a/libexfat/cluster.c b/libexfat/cluster.c index 8bea507..7251c31 100644 --- a/libexfat/cluster.c +++ b/libexfat/cluster.c @@ -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; } diff --git a/libexfat/exfat.h b/libexfat/exfat.h index 84673dc..de2e1a2 100644 --- a/libexfat/exfat.h +++ b/libexfat/exfat.h @@ -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; }; diff --git a/libexfat/exfatfs.h b/libexfat/exfatfs.h index e7d7ee5..be75395 100644 --- a/libexfat/exfatfs.h +++ b/libexfat/exfatfs.h @@ -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 */ diff --git a/libexfat/io.c b/libexfat/io.c index 3d63fa9..25988f0 100644 --- a/libexfat/io.c +++ b/libexfat/io.c @@ -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) diff --git a/libexfat/mount.c b/libexfat/mount.c index 4240f63..295d455 100644 --- a/libexfat/mount.c +++ b/libexfat/mount.c @@ -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); diff --git a/libexfat/node.c b/libexfat/node.c index fa9e572..0920847 100644 --- a/libexfat/node.c +++ b/libexfat/node.c @@ -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) diff --git a/libexfat/utils.c b/libexfat/utils.c index 39fdd48..31b7e8c 100644 --- a/libexfat/utils.c +++ b/libexfat/utils.c @@ -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]; } diff --git a/libexfat/version.h b/libexfat/version.h index 061192f..0236f88 100644 --- a/libexfat/version.h +++ b/libexfat/version.h @@ -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 */ -- 2.39.5