From dbe4348df2ba88986e13163ef13d09fb7247589d Mon Sep 17 00:00:00 2001 From: Sven Hoexter Date: Sat, 14 Jan 2012 13:30:12 +0100 Subject: [PATCH] Imported Upstream version 0.9.6 --- ChangeLog | 11 +++++ 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 +- mkfs/main.c | 46 ++++++++++---------- 10 files changed, 172 insertions(+), 83 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/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 */ diff --git a/mkfs/main.c b/mkfs/main.c index e6d49e1..8deca15 100644 --- a/mkfs/main.c +++ b/mkfs/main.c @@ -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"); -- 2.39.5