From d9332503465b3d11df6c31622e7f4315eb857c95 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Fri, 7 May 2021 15:45:36 +0900 Subject: mkfs: set 0x80 to DriveSelect of boot sector Without this value, Windows 10 does not recognize partitions as exfat formated partitions. Signed-off-by: Hyunchul Lee --- mkfs/mkfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c index 1837669..4788c37 100644 --- a/mkfs/mkfs.c +++ b/mkfs/mkfs.c @@ -72,6 +72,7 @@ static void exfat_setup_boot_sector(struct pbr *ppbr, /* fs_version[0] : minor and fs_version[1] : major */ pbsx->fs_version[0] = 0; pbsx->fs_version[1] = 1; + pbsx->phy_drv_no = 0x80; memset(pbsx->reserved2, 0, 7); memset(ppbr->boot_code, 0, 390); -- cgit v1.2.3 From 1e3163a68d3b73ad8d55df12c5a8b639003de188 Mon Sep 17 00:00:00 2001 From: Christophe Vu-Brugier Date: Tue, 11 May 2021 09:40:49 +0200 Subject: mkfs: fix boot sector checksum when the sector size is 4 KB "4K native" HDDs advertise a physical sector size and a logical sector size of 4 KB. A sector size of 4 KB is supported according to the exFAT specification. From section 3.1.14: > The valid range of values for [BytesPerSectorShift] shall be: > * At least 9 (sector size of 512 bytes), which is the smallest > sector possible for an exFAT volume > * At most 12 (sector size of 4096 bytes), which is the memory page > size of CPUs common in personal computers Unfortunately, several bugs in `mkfs.exfat` prevent it from properly formatting devices whose logical sector size is 4 KB. The Linux kernel reports the following error at `mount` time: $ sudo mount /dev/vda /mnt/ [ 262.063000] exFAT-fs (vda): Invalid boot checksum (boot checksum : 0x8455546b, checksum : 0xfedcaae5) [ 262.068991] exFAT-fs (vda): invalid boot region [ 262.071618] exFAT-fs (vda): failed to recognize exfat type mount: /mnt: wrong fs type, bad option, bad superblock on /dev/vda, missing codepage or helper program, or other error. There are 3 issues in `mkfs.exfat:` 1. it truncates some volume structures that should fill an entire sector (4 KB for 4Kn HDDs) to 512 bytes when writing them. 2. it computes the main and backup boot checksums using only 512 bytes for some sectors. Hence the error reported by Linux. 3. it writes the extended boot signature at offset 510 bytes whatever the sector size is. This is fixed by using `bd->sector_size` to allocate, clear, and checksum the sectors. Moreover, the extended boot signature is now placed in the last two bytes of the extended boot sectors. This patch fixes issue #163. Signed-off-by: Christophe Vu-Brugier --- include/exfat_ondisk.h | 11 ----------- lib/libexfat.c | 8 ++------ mkfs/mkfs.c | 30 ++++++++++++++++++++---------- 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/include/exfat_ondisk.h b/include/exfat_ondisk.h index 74e3fb3..b3fc1fe 100644 --- a/include/exfat_ondisk.h +++ b/include/exfat_ondisk.h @@ -131,17 +131,6 @@ struct pbr { __le16 signature; }; -/* Extended Boot Sector */ -struct exbs { - __u8 zero[510]; - __le16 signature; -}; - -/* Extended Boot Record (8 sectors) */ -struct expbr { - struct exbs eb[8]; -}; - #define VOLUME_LABEL_MAX_LEN 11 #define ENTRY_NAME_MAX 15 diff --git a/lib/libexfat.c b/lib/libexfat.c index 13dfbaf..eed0a99 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -559,7 +559,6 @@ static int exfat_update_boot_checksum(struct exfat_blk_dev *bd, bool is_backup) { unsigned int checksum = 0; int ret, sec_idx, backup_sec_idx = 0; - int sector_size = bd->sector_size; unsigned char *buf; buf = malloc(bd->sector_size); @@ -581,13 +580,10 @@ static int exfat_update_boot_checksum(struct exfat_blk_dev *bd, bool is_backup) goto free_buf; } - if (sec_idx == BOOT_SEC_IDX) { + if (sec_idx == BOOT_SEC_IDX) is_boot_sec = true; - sector_size = sizeof(struct pbr); - } else if (sec_idx >= EXBOOT_SEC_IDX && sec_idx < OEM_SEC_IDX) - sector_size = sizeof(struct exbs); - boot_calc_checksum(buf, sector_size, is_boot_sec, + boot_calc_checksum(buf, bd->sector_size, is_boot_sec, &checksum); } diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c index 4788c37..8c81221 100644 --- a/mkfs/mkfs.c +++ b/mkfs/mkfs.c @@ -110,12 +110,12 @@ static int exfat_write_boot_sector(struct exfat_blk_dev *bd, if (is_backup) sec_idx += BACKUP_BOOT_SEC_IDX; - ppbr = malloc(sizeof(struct pbr)); + ppbr = malloc(bd->sector_size); if (!ppbr) { exfat_err("Cannot allocate pbr: out of memory\n"); return -1; } - memset(ppbr, 0, sizeof(struct pbr)); + memset(ppbr, 0, bd->sector_size); exfat_setup_boot_sector(ppbr, bd, ui); @@ -127,7 +127,7 @@ static int exfat_write_boot_sector(struct exfat_blk_dev *bd, goto free_ppbr; } - boot_calc_checksum((unsigned char *)ppbr, sizeof(struct pbr), + boot_calc_checksum((unsigned char *)ppbr, bd->sector_size, true, checksum); free_ppbr: @@ -138,26 +138,36 @@ free_ppbr: static int exfat_write_extended_boot_sectors(struct exfat_blk_dev *bd, unsigned int *checksum, bool is_backup) { - struct exbs eb; + char *peb; + __le16 *peb_signature; + int ret = 0; int i; unsigned int sec_idx = EXBOOT_SEC_IDX; + peb = malloc(bd->sector_size); + if (!peb) + return -1; + if (is_backup) sec_idx += BACKUP_BOOT_SEC_IDX; - memset(&eb, 0, sizeof(struct exbs)); - eb.signature = cpu_to_le16(PBR_SIGNATURE); + memset(peb, 0, bd->sector_size); + peb_signature = (__le16*) (peb + bd->sector_size - 2); + *peb_signature = cpu_to_le16(PBR_SIGNATURE); for (i = 0; i < EXBOOT_SEC_NUM; i++) { - if (exfat_write_sector(bd, &eb, sec_idx++)) { + if (exfat_write_sector(bd, peb, sec_idx++)) { exfat_err("extended boot sector write failed\n"); - return -1; + ret = -1; + goto free_peb; } - boot_calc_checksum((unsigned char *) &eb, sizeof(struct exbs), + boot_calc_checksum((unsigned char *) peb, bd->sector_size, false, checksum); } - return 0; +free_peb: + free(peb); + return ret; } static int exfat_write_oem_sector(struct exfat_blk_dev *bd, -- cgit v1.2.3 From 91ad75d68ad2e79fce099ee166426d13e372b5ef Mon Sep 17 00:00:00 2001 From: Luca Stefani Date: Wed, 12 May 2021 11:35:14 +0200 Subject: libexfat: Remove unused parameter in exfat_show_volume_serial This method won't ever use user input, so it's safe to assume it'll never be required As a bonus it fixes Android build Signed-off-by: Luca Stefani --- include/libexfat.h | 3 +-- label/label.c | 2 +- lib/libexfat.c | 3 +-- tune/tune.c | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/include/libexfat.h b/include/libexfat.h index 0357d77..7b56798 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -107,8 +107,7 @@ int exfat_write_sector(struct exfat_blk_dev *bd, void *buf, int exfat_write_checksum_sector(struct exfat_blk_dev *bd, unsigned int checksum, bool is_backup); char *exfat_conv_volume_label(struct exfat_dentry *vol_entry); -int exfat_show_volume_serial(struct exfat_blk_dev *bd, - struct exfat_user_input *ui); +int exfat_show_volume_serial(struct exfat_blk_dev *bd); int exfat_set_volume_serial(struct exfat_blk_dev *bd, struct exfat_user_input *ui); unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd, diff --git a/label/label.c b/label/label.c index fd77a54..66c54e8 100644 --- a/label/label.c +++ b/label/label.c @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) if (serial_mode) { /* Mode to change or display volume serial */ if (flags == EXFAT_GET_VOLUME_SERIAL) { - ret = exfat_show_volume_serial(&bd, &ui); + ret = exfat_show_volume_serial(&bd); } else if (flags == EXFAT_SET_VOLUME_SERIAL) { ui.volume_serial = strtoul(argv[3], NULL, 0); ret = exfat_set_volume_serial(&bd, &ui); diff --git a/lib/libexfat.c b/lib/libexfat.c index eed0a99..8f2ea0f 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -528,8 +528,7 @@ free: return ret; } -int exfat_show_volume_serial(struct exfat_blk_dev *bd, - struct exfat_user_input *ui) +int exfat_show_volume_serial(struct exfat_blk_dev *bd) { struct pbr *ppbr; int ret; diff --git a/tune/tune.c b/tune/tune.c index ab04e69..bec9bb1 100644 --- a/tune/tune.c +++ b/tune/tune.c @@ -102,7 +102,7 @@ int main(int argc, char *argv[]) /* Mode to change or display volume serial */ if (flags == EXFAT_GET_VOLUME_SERIAL) { - ret = exfat_show_volume_serial(&bd, &ui); + ret = exfat_show_volume_serial(&bd); goto close_fd_out; } else if (flags == EXFAT_SET_VOLUME_SERIAL) { ret = exfat_set_volume_serial(&bd, &ui); -- cgit v1.2.3 From a13479bc46f87d89e874621e09f05878db402ec9 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 13 May 2021 14:46:22 +0900 Subject: exfatprogs: tune: use sector size extracted from the boot sector Eric Sandeen reported exfat dump image is damaged when tune.exfat was executed against exfat dump image created in 4K native device. # fsck/fsck.exfat /root/test.img exfatprogs version : 1.1.1 /root/test.img: clean. directories 1, files 0 # tune/tune.exfat -I 0x1234 /root/test.img exfatprogs version : 1.1.1 New volume serial : 0x1234 # fsck/fsck.exfat /root/test.img exfatprogs version : 1.1.1 checksum of boot region is not correct. 0x3eedc5, but expected 0xe59577e3 boot region is corrupted. try to restore the region from backup. Fix (y/N)? n This patch read boot sector with 4KB(for 4K native) size, and use the sector size extracted from the boot sector. Reported-by: Eric Sandeen Signed-off-by: Namjae Jeon --- include/libexfat.h | 4 +++- label/label.c | 2 +- lib/libexfat.c | 29 ++++++++++++++++------------- tune/tune.c | 2 +- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/include/libexfat.h b/include/libexfat.h index 7b56798..fecd7f2 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -43,6 +43,8 @@ #define EXFAT_GET_VOLUME_SERIAL 0x03 #define EXFAT_SET_VOLUME_SERIAL 0x04 +#define EXFAT_MAX_SECTOR_SIZE 4096 + enum { BOOT_SEC_IDX = 0, EXBOOT_SEC_IDX, @@ -107,7 +109,7 @@ int exfat_write_sector(struct exfat_blk_dev *bd, void *buf, int exfat_write_checksum_sector(struct exfat_blk_dev *bd, unsigned int checksum, bool is_backup); char *exfat_conv_volume_label(struct exfat_dentry *vol_entry); -int exfat_show_volume_serial(struct exfat_blk_dev *bd); +int exfat_show_volume_serial(int fd); int exfat_set_volume_serial(struct exfat_blk_dev *bd, struct exfat_user_input *ui); unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd, diff --git a/label/label.c b/label/label.c index 66c54e8..b41e827 100644 --- a/label/label.c +++ b/label/label.c @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) if (serial_mode) { /* Mode to change or display volume serial */ if (flags == EXFAT_GET_VOLUME_SERIAL) { - ret = exfat_show_volume_serial(&bd); + ret = exfat_show_volume_serial(bd.dev_fd); } else if (flags == EXFAT_SET_VOLUME_SERIAL) { ui.volume_serial = strtoul(argv[3], NULL, 0); ret = exfat_set_volume_serial(&bd, &ui); diff --git a/lib/libexfat.c b/lib/libexfat.c index 8f2ea0f..1613863 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -358,26 +358,27 @@ off_t exfat_get_root_entry_offset(struct exfat_blk_dev *bd) { struct pbr *bs; int nbytes; - unsigned int cluster_size; + unsigned int cluster_size, sector_size; off_t root_clu_off; - bs = (struct pbr *)malloc(sizeof(struct pbr)); + bs = (struct pbr *)malloc(EXFAT_MAX_SECTOR_SIZE); if (!bs) { exfat_err("failed to allocate memory\n"); return -ENOMEM; } - nbytes = exfat_read(bd->dev_fd, bs, sizeof(struct pbr), 0); - if (nbytes != sizeof(struct pbr)) { + nbytes = exfat_read(bd->dev_fd, bs, EXFAT_MAX_SECTOR_SIZE, 0); + if (nbytes != EXFAT_MAX_SECTOR_SIZE) { exfat_err("boot sector read failed: %d\n", errno); free(bs); return -1; } - cluster_size = (1 << bs->bsx.sect_per_clus_bits) * bd->sector_size; - root_clu_off = le32_to_cpu(bs->bsx.clu_offset) * bd->sector_size + - le32_to_cpu(bs->bsx.root_cluster - EXFAT_RESERVED_CLUSTERS) - * cluster_size; + sector_size = 1 << bs->bsx.sect_size_bits; + cluster_size = (1 << bs->bsx.sect_per_clus_bits) * sector_size; + root_clu_off = le32_to_cpu(bs->bsx.clu_offset) * sector_size + + le32_to_cpu(bs->bsx.root_cluster - EXFAT_RESERVED_CLUSTERS) * + cluster_size; free(bs); return root_clu_off; @@ -528,19 +529,19 @@ free: return ret; } -int exfat_show_volume_serial(struct exfat_blk_dev *bd) +int exfat_show_volume_serial(int fd) { struct pbr *ppbr; int ret; - ppbr = malloc(bd->sector_size); + ppbr = malloc(EXFAT_MAX_SECTOR_SIZE); if (!ppbr) { exfat_err("Cannot allocate pbr: out of memory\n"); return -1; } /* read main boot sector */ - ret = exfat_read_sector(bd, (char *)ppbr, BOOT_SEC_IDX); + ret = exfat_read(fd, (char *)ppbr, EXFAT_MAX_SECTOR_SIZE, 0); if (ret < 0) { exfat_err("main boot sector read failed\n"); ret = -1; @@ -600,20 +601,22 @@ int exfat_set_volume_serial(struct exfat_blk_dev *bd, int ret; struct pbr *ppbr; - ppbr = malloc(bd->sector_size); + ppbr = malloc(EXFAT_MAX_SECTOR_SIZE); if (!ppbr) { exfat_err("Cannot allocate pbr: out of memory\n"); return -1; } /* read main boot sector */ - ret = exfat_read_sector(bd, (char *)ppbr, BOOT_SEC_IDX); + ret = exfat_read(bd->dev_fd, (char *)ppbr, EXFAT_MAX_SECTOR_SIZE, + BOOT_SEC_IDX); if (ret < 0) { exfat_err("main boot sector read failed\n"); ret = -1; goto free_ppbr; } + bd->sector_size = 1 << ppbr->bsx.sect_size_bits; ppbr->bsx.vol_serial = ui->volume_serial; /* update main boot sector */ diff --git a/tune/tune.c b/tune/tune.c index bec9bb1..a53be59 100644 --- a/tune/tune.c +++ b/tune/tune.c @@ -102,7 +102,7 @@ int main(int argc, char *argv[]) /* Mode to change or display volume serial */ if (flags == EXFAT_GET_VOLUME_SERIAL) { - ret = exfat_show_volume_serial(&bd); + ret = exfat_show_volume_serial(bd.dev_fd); goto close_fd_out; } else if (flags == EXFAT_SET_VOLUME_SERIAL) { ret = exfat_set_volume_serial(&bd, &ui); -- cgit v1.2.3 From 3b3025ca1d49b6a7232db2427abd05601369478f Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 13 May 2021 15:33:31 +0900 Subject: exfatprogs: libexfat: fix wrong bd->num_sectors calculation Fix wrong bd->num_sectors calculation, use bd->sector_size for this. Reported-by: Eric Sandeen Signed-off-by: Namjae Jeon --- lib/libexfat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libexfat.c b/lib/libexfat.c index 1613863..4148169 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -196,7 +196,7 @@ int exfat_get_blk_dev_info(struct exfat_user_input *ui, if (ioctl(fd, BLKSSZGET, &bd->sector_size) < 0) bd->sector_size = DEFAULT_SECTOR_SIZE; bd->sector_size_bits = sector_size_bits(bd->sector_size); - bd->num_sectors = blk_dev_size / DEFAULT_SECTOR_SIZE; + bd->num_sectors = blk_dev_size / bd->sector_size; bd->num_clusters = blk_dev_size / ui->cluster_size; exfat_debug("Block device name : %s\n", ui->dev_name); -- cgit v1.2.3 From 478f39f0168992c7ca8e8bc9e99d10436bfc463b Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Wed, 12 May 2021 17:43:35 +0900 Subject: fsck: use BytesPerSectorShift of boot sector to verify boot region instead of the block device's sector size, use BytesPerSectorShift of boot sector to read and verify boot region. Reported-by: Eric Sandeen Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 146 ++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 97 insertions(+), 49 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 747a771..00d1ca7 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -584,40 +584,39 @@ off_t exfat_c2o(struct exfat *exfat, unsigned int clus) exfat->bs->bsx.sect_per_clus_bits)); } -static int boot_region_checksum(struct exfat_blk_dev *bd, int bs_offset) +static int boot_region_checksum(int dev_fd, + int bs_offset, unsigned int sect_size) { void *sect; unsigned int i; uint32_t checksum; int ret = 0; - unsigned int size; - size = bd->sector_size; - sect = malloc(size); + sect = malloc(sect_size); if (!sect) return -ENOMEM; checksum = 0; for (i = 0; i < 11; i++) { - if (exfat_read(bd->dev_fd, sect, size, - bs_offset * size + i * size) != - (ssize_t)size) { + if (exfat_read(dev_fd, sect, sect_size, + bs_offset * sect_size + i * sect_size) != + (ssize_t)sect_size) { exfat_err("failed to read boot region\n"); ret = -EIO; goto out; } - boot_calc_checksum(sect, size, i == 0, &checksum); + boot_calc_checksum(sect, sect_size, i == 0, &checksum); } - if (exfat_read(bd->dev_fd, sect, size, - bs_offset * size + 11 * size) != - (ssize_t)size) { + if (exfat_read(dev_fd, sect, sect_size, + bs_offset * sect_size + 11 * sect_size) != + (ssize_t)sect_size) { exfat_err("failed to read a boot checksum sector\n"); ret = -EIO; goto out; } - for (i = 0; i < size/sizeof(checksum); i++) { + for (i = 0; i < sect_size/sizeof(checksum); i++) { if (le32_to_cpu(((__le32 *)sect)[i]) != checksum) { exfat_err("checksum of boot region is not correct. %#x, but expected %#x\n", le32_to_cpu(((__le32 *)sect)[i]), checksum); @@ -658,7 +657,8 @@ static int exfat_mark_volume_dirty(struct exfat *exfat, bool dirty) } static int read_boot_region(struct exfat_blk_dev *bd, struct pbr **pbr, - int bs_offset) + int bs_offset, unsigned int sect_size, + bool verbose) { struct pbr *bs; int ret = -EINVAL; @@ -671,57 +671,66 @@ static int read_boot_region(struct exfat_blk_dev *bd, struct pbr **pbr, } if (exfat_read(bd->dev_fd, bs, sizeof(*bs), - bs_offset * bd->sector_size) != (ssize_t)sizeof(*bs)) { + bs_offset * sect_size) != (ssize_t)sizeof(*bs)) { exfat_err("failed to read a boot sector\n"); ret = -EIO; goto err; } if (memcmp(bs->bpb.oem_name, "EXFAT ", 8) != 0) { - exfat_err("failed to find exfat file system.\n"); + if (verbose) + exfat_err("failed to find exfat file system\n"); goto err; } - ret = boot_region_checksum(bd, bs_offset); + ret = boot_region_checksum(bd->dev_fd, bs_offset, sect_size); if (ret < 0) goto err; ret = -EINVAL; if (EXFAT_SECTOR_SIZE(bs) < 512 || EXFAT_SECTOR_SIZE(bs) > 4 * KB) { - exfat_err("too small or big sector size: %d\n", - EXFAT_SECTOR_SIZE(bs)); + if (verbose) + exfat_err("too small or big sector size: %d\n", + EXFAT_SECTOR_SIZE(bs)); goto err; } if (EXFAT_CLUSTER_SIZE(bs) > 32 * MB) { - exfat_err("too big cluster size: %d\n", EXFAT_CLUSTER_SIZE(bs)); + if (verbose) + exfat_err("too big cluster size: %d\n", + EXFAT_CLUSTER_SIZE(bs)); goto err; } if (bs->bsx.fs_version[1] != 1 || bs->bsx.fs_version[0] != 0) { - exfat_err("unsupported exfat version: %d.%d\n", - bs->bsx.fs_version[1], bs->bsx.fs_version[0]); + if (verbose) + exfat_err("unsupported exfat version: %d.%d\n", + bs->bsx.fs_version[1], bs->bsx.fs_version[0]); goto err; } if (bs->bsx.num_fats != 1) { - exfat_err("unsupported FAT count: %d\n", bs->bsx.num_fats); + if (verbose) + exfat_err("unsupported FAT count: %d\n", + bs->bsx.num_fats); goto err; } if (le64_to_cpu(bs->bsx.vol_length) * EXFAT_SECTOR_SIZE(bs) > bd->size) { - exfat_err("too large sector count: %" PRIu64 ", expected: %llu\n", - le64_to_cpu(bs->bsx.vol_length), - bd->num_sectors); + if (verbose) + exfat_err("too large sector count: %" PRIu64 ", expected: %llu\n", + le64_to_cpu(bs->bsx.vol_length), + bd->num_sectors); goto err; } if (le32_to_cpu(bs->bsx.clu_count) * EXFAT_CLUSTER_SIZE(bs) > bd->size) { - exfat_err("too large cluster count: %u, expected: %u\n", - le32_to_cpu(bs->bsx.clu_count), - bd->num_clusters); + if (verbose) + exfat_err("too large cluster count: %u, expected: %u\n", + le32_to_cpu(bs->bsx.clu_count), + bd->num_clusters); goto err; } @@ -732,31 +741,31 @@ err: return ret; } -static int restore_boot_region(struct exfat_blk_dev *bd) +static int restore_boot_region(struct exfat_blk_dev *bd, unsigned int sect_size) { int i; char *sector; int ret; - sector = malloc(bd->sector_size); + sector = malloc(sect_size); if (!sector) return -ENOMEM; for (i = 0; i < 12; i++) { - if (exfat_read(bd->dev_fd, sector, bd->sector_size, - BACKUP_BOOT_SEC_IDX * bd->sector_size + - i * bd->sector_size) != - (ssize_t)bd->sector_size) { + if (exfat_read(bd->dev_fd, sector, sect_size, + BACKUP_BOOT_SEC_IDX * sect_size + + i * sect_size) != + (ssize_t)sect_size) { ret = -EIO; goto free_sector; } if (i == 0) ((struct pbr *)sector)->bsx.perc_in_use = 0xff; - if (exfat_write(bd->dev_fd, sector, bd->sector_size, - BOOT_SEC_IDX * bd->sector_size + - i * bd->sector_size) != - (ssize_t)bd->sector_size) { + if (exfat_write(bd->dev_fd, sector, sect_size, + BOOT_SEC_IDX * sect_size + + i * sect_size) != + (ssize_t)sect_size) { ret = -EIO; goto free_sector; } @@ -775,24 +784,63 @@ free_sector: static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs) { + struct pbr *boot_sect; + unsigned int sect_size; int ret; - ret = read_boot_region(exfat->blk_dev, bs, BOOT_SEC_IDX); + /* First, find out the exfat sector size */ + boot_sect = malloc(sizeof(*boot_sect)); + if (boot_sect == NULL) + return -ENOMEM; + + if (exfat_read(exfat->blk_dev->dev_fd, boot_sect, + sizeof(*boot_sect), 0) != (ssize_t)sizeof(*boot_sect)) { + exfat_err("failed to read Main boot sector\n"); + return -EIO; + } + + sect_size = 1 << boot_sect->bsx.sect_size_bits; + free(boot_sect); + + /* check boot regions */ + ret = read_boot_region(exfat->blk_dev, bs, + BOOT_SEC_IDX, sect_size, true); if (ret == -EINVAL && exfat_repair_ask(exfat, ER_BS_BOOT_REGION, "boot region is corrupted. try to restore the region from backup" )) { - ret = read_boot_region(exfat->blk_dev, bs, BACKUP_BOOT_SEC_IDX); - if (ret < 0) { - exfat_err("backup boot region is also corrupted\n"); - return ret; + const unsigned int sector_sizes[] = {512, 4096, 1024, 2048}; + unsigned int i; + + if (sect_size >= 512 && sect_size <= EXFAT_MAX_SECTOR_SIZE) { + ret = read_boot_region(exfat->blk_dev, bs, + BACKUP_BOOT_SEC_IDX, sect_size, + false); + if (!ret) + goto restore; } - ret = restore_boot_region(exfat->blk_dev); - if (ret < 0) { - exfat_err("failed to restore boot region from backup\n"); - free(*bs); - *bs = NULL; - return ret; + + for (i = 0; i < sizeof(sector_sizes)/sizeof(sector_sizes[0]); i++) { + if (sector_sizes[i] == sect_size) + continue; + + ret = read_boot_region(exfat->blk_dev, bs, + BACKUP_BOOT_SEC_IDX, + sector_sizes[i], false); + if (!ret) { + sect_size = sector_sizes[i]; + goto restore; + } } + exfat_err("backup boot region is also corrupted\n"); + } + + return ret; +restore: + ret = restore_boot_region(exfat->blk_dev, sect_size); + if (ret) { + exfat_err("failed to restore boot region from backup\n"); + free(*bs); + *bs = NULL; } return ret; } -- cgit v1.2.3 From 7037652fe3c35f3aa5de61b8f70884abcf5ad852 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Sat, 15 May 2021 10:13:41 +0900 Subject: dump: use BytesPerSectorShift of boot sector dump.exfat show the wrong information because it used the device's sector size instead of the Exfat's sector size. Reported-by: Eric Sandeen Signed-off-by: Hyunchul Lee --- dump/dump.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/dump/dump.c b/dump/dump.c index 85d5101..7ede550 100644 --- a/dump/dump.c +++ b/dump/dump.c @@ -76,15 +76,15 @@ static int exfat_show_ondisk_all_info(struct exfat_blk_dev *bd) unsigned char *bitmap; char *volume_label; - ppbr = malloc(bd->sector_size); + ppbr = malloc(EXFAT_MAX_SECTOR_SIZE); if (!ppbr) { exfat_err("Cannot allocate pbr: out of memory\n"); return -ENOMEM; } /* read main boot sector */ - ret = exfat_read_sector(bd, (char *)ppbr, BOOT_SEC_IDX); - if (ret < 0) { + if (exfat_read(bd->dev_fd, (char *)ppbr, EXFAT_MAX_SECTOR_SIZE, + 0) != (ssize_t)EXFAT_MAX_SECTOR_SIZE) { exfat_err("main boot sector read failed\n"); ret = -EIO; goto free_ppbr; @@ -107,12 +107,8 @@ static int exfat_show_ondisk_all_info(struct exfat_blk_dev *bd) goto free_ppbr; } - if (bd->sector_size != 1 << pbsx->sect_size_bits) { - exfat_err("bogus sector size : %u (sector size bits : %u)\n", - bd->sector_size, pbsx->sect_size_bits); - ret = -EINVAL; - goto free_ppbr; - } + bd->sector_size_bits = pbsx->sect_size_bits; + bd->sector_size = 1 << pbsx->sect_size_bits; clu_offset = le32_to_cpu(pbsx->clu_offset); total_clus = le32_to_cpu(pbsx->clu_count); -- cgit v1.2.3 From 03375cdb77c9167d7406408cded6268d72f68a27 Mon Sep 17 00:00:00 2001 From: Luca Stefani Date: Tue, 18 May 2021 11:26:04 +0200 Subject: travis: Run fsck with dirs/files This gives a rough idea of the time spent while checking the filesystem Signed-off-by: Luca Stefani --- .travis.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6ab3cc2..90aff89 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,8 +35,13 @@ script: - i=1;while [ $i -le 10000 ];do sudo touch file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done - sync - sudo rm -rf * - - i=1;while [ $i -le 10000 ];do sudo mkdir file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done + - i=1;while [ $i -le 10000 ];do sudo mkdir dir$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done - sync - sudo rm -rf * + - i=1;while [ $i -le 10000 ];do sudo touch file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done + - i=1;while [ $i -le 10000 ];do sudo mkdir dir$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done + - sync + - sudo fsck.exfat /dev/loop22 + - sudo find . -delete - sudo fsck.exfat /dev/loop22 - cd - -- cgit v1.2.3 From fd5da68ac69b8f28d7ef24a9ca7c95edd6a1d655 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 20 May 2021 11:07:28 +0900 Subject: libexfat: fix an endian issue in exfat_get_root_entry_offset Fix an endian issue in exfat_get_root_entry_offset Signed-off-by: Hyunchul Lee --- lib/libexfat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libexfat.c b/lib/libexfat.c index 4148169..c54a7c8 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -377,7 +377,7 @@ off_t exfat_get_root_entry_offset(struct exfat_blk_dev *bd) sector_size = 1 << bs->bsx.sect_size_bits; cluster_size = (1 << bs->bsx.sect_per_clus_bits) * sector_size; root_clu_off = le32_to_cpu(bs->bsx.clu_offset) * sector_size + - le32_to_cpu(bs->bsx.root_cluster - EXFAT_RESERVED_CLUSTERS) * + (le32_to_cpu(bs->bsx.root_cluster) - EXFAT_RESERVED_CLUSTERS) * cluster_size; free(bs); -- cgit v1.2.3 From b19fb3d641ef8ba4a138798601b4e4697b209dce Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 20 May 2021 11:51:02 +0900 Subject: exfatprogs: release 1.1.2 version exfatprogs 1.1.2 - released 2021-05-20 ====================================== CHANGES : * mkfs.exfat: set 0x80 to DriveSelect of the boot sector BUG FIXES : * Fix issues on 4KB logical sector devices * Fix issues when the sector size of of a file system is different from that of a block device. Signed-off-by: Hyunchul Lee --- NEWS | 11 +++++++++++ include/version.h | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 9c4e72b..e2d53dc 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,14 @@ +exfatprogs 1.1.2 - released 2021-05-20 +====================================== + +CHANGES : + * mkfs.exfat: set 0x80 to DriveSelect of the boot sector + +BUG FIXES : + * Fix issues on 4KB logical sector devices + * Fix issues when the sector size of of a file system is different from + that of a block device. + exfatprogs 1.1.1 - released 2021-04-21 ====================================== diff --git a/include/version.h b/include/version.h index ae6291f..0f8a463 100644 --- a/include/version.h +++ b/include/version.h @@ -5,6 +5,6 @@ #ifndef _VERSION_H -#define EXFAT_PROGS_VERSION "1.1.1" +#define EXFAT_PROGS_VERSION "1.1.2" #endif /* !_VERSION_H */ -- cgit v1.2.3 From 7e30487e6d92f98e1ab91b037f5285453359ec9a Mon Sep 17 00:00:00 2001 From: a1346054 <36859588+a1346054@users.noreply.github.com> Date: Sat, 14 Aug 2021 07:43:39 +0000 Subject: exfatprogs: use correct license file Use the license file from https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt instead of the current one, which differs in some whitespace and hence has a different hash sum. Signed-off-by: a1346054 <36859588+a1346054@users.noreply.github.com> --- COPYING | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/COPYING b/COPYING index 941c87d..d159169 100644 --- a/COPYING +++ b/COPYING @@ -56,7 +56,7 @@ patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. - GNU GENERAL PUBLIC LICENSE + GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains @@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS - How to Apply These Terms to Your New Programs + How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it -- cgit v1.2.3 From 24c651701a5d09056a233acda7f59740753fbd34 Mon Sep 17 00:00:00 2001 From: a1346054 <36859588+a1346054@users.noreply.github.com> Date: Sat, 14 Aug 2021 08:44:13 +0000 Subject: exfatprogs: fix test script issues identified through shellcheck * use correct bash from $PATH instead of hardcoded path * use correct function notation in bash * avoid deprecated `` notation for subshells and use $() instead * quote variables to avoid splitting * whitespace and style consolidation Signed-off-by: a1346054 <36859588+a1346054@users.noreply.github.com> --- tests/test_fsck.sh | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/tests/test_fsck.sh b/tests/test_fsck.sh index 3a59d7f..f954814 100755 --- a/tests/test_fsck.sh +++ b/tests/test_fsck.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash TESTCASE_DIR=$1 IMAGE_FILE=exfat.img @@ -6,15 +6,15 @@ FSCK_PROG=../build/sbin/fsck.exfat FSCK_OPTS=-y PASS_COUNT=0 -function cleanup { +cleanup() { echo "" echo "Passed ${PASS_COUNT} of ${TEST_COUNT}" exit } if [ $# -eq 0 ]; then - TESTCASE_DIRS=`find -mindepth 1 -maxdepth 1 -type d` - TEST_COUNT=`find -mindepth 1 -maxdepth 1 -type d | wc -l` + TESTCASE_DIRS=$(find . -mindepth 1 -maxdepth 1 -type d) + TEST_COUNT=$(find . -mindepth 1 -maxdepth 1 -type d | wc -l) else TESTCASE_DIRS=$@ TEST_COUNT=$# @@ -22,54 +22,53 @@ fi for TESTCASE_DIR in $TESTCASE_DIRS do - if [ ! -e ${TESTCASE_DIR}/${IMAGE_FILE}.tar.xz ]; then + if [ ! -e "${TESTCASE_DIR}/${IMAGE_FILE}.tar.xz" ]; then TEST_COUNT=$((TEST_COUNT - 1)) continue fi - echo "Running $TESTCASE_DIR" + echo "Running ${TESTCASE_DIR}" echo "-----------------------------------" # Set up image file as loop device - tar -C . -xf $TESTCASE_DIR/$IMAGE_FILE.tar.xz - losetup -f $IMAGE_FILE - DEV_FILE=`losetup -j $IMAGE_FILE | awk '{print $1}' | sed 's/://g'` + tar -C . -xf "${TESTCASE_DIR}/${IMAGE_FILE}.tar.xz" + DEV_FILE=$(losetup -f "${IMAGE_FILE}" --show) # Run fsck for repair $FSCK_PROG $FSCK_OPTS $DEV_FILE - if [ "$?" -ne "1" ]; then + if [ "$?" -ne 1 ]; then echo "" - echo "Failed to repair $TESTCASE_DIR" - losetup -d $DEV_FILE + echo "Failed to repair ${TESTCASE_DIR}" + losetup -d "${DEV_FILE}" cleanup fi echo "" # Run fsck again $FSCK_PROG -n $DEV_FILE - if [ "$?" -ne "0" ]; then + if [ "$?" -ne 0 ]; then echo "" - echo "Failed, corrupted $TESTCASE_DIR" - losetup -d $DEV_FILE + echo "Failed, corrupted ${TESTCASE_DIR}" + losetup -d "${DEV_FILE}" cleanup fi - if [ -e "$TESTCASE_DIR/exfat.img.expected.xz" ]; then - EXPECTED_FILE=$IMAGE_FILE.expected - unxz -cfk "$TESTCASE_DIR/$EXPECTED_FILE.xz" > $EXPECTED_FILE - diff <(xxd $IMAGE_FILE) <(xxd $EXPECTED_FILE) - if [ "$?" -ne "0" ]; then + if [ -e "${TESTCASE_DIR}/exfat.img.expected.xz" ]; then + EXPECTED_FILE=${IMAGE_FILE}.expected + unxz -cfk "${TESTCASE_DIR}/${EXPECTED_FILE}.xz" > "${EXPECTED_FILE}" + diff <(xxd "${IMAGE_FILE}") <(xxd "${EXPECTED_FILE}") + if [ "$?" -ne 0 ]; then echo "" - echo "Failed $TESTCASE_DIR" - losetup -d $DEV_FILE + echo "Failed ${TESTCASE_DIR}" + losetup -d "${DEV_FILE}" cleanup fi fi echo "" - echo "Passed $TESTCASE_DIR" + echo "Passed ${TESTCASE_DIR}" PASS_COUNT=$((PASS_COUNT + 1)) - losetup -d $DEV_FILE + losetup -d "${DEV_FILE}" done cleanup -- cgit v1.2.3 From 707d6a44fa0bafca79878476fc21ba6762ee30c2 Mon Sep 17 00:00:00 2001 From: a1346054 <36859588+a1346054@users.noreply.github.com> Date: Sat, 14 Aug 2021 08:46:34 +0000 Subject: exfatprogs: fix spelling in a few files Signed-off-by: a1346054 <36859588+a1346054@users.noreply.github.com> --- README.md | 2 +- fsck/fsck.c | 2 +- include/libexfat.h | 2 +- include/list.h | 3 --- mkfs/mkfs.c | 4 ++-- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4db98fd..b3fbdd0 100644 --- a/README.md +++ b/README.md @@ -116,4 +116,4 @@ If you have any issues, please create [issues][1] or contact to [Namjae Jeon](ma ## Contributor information * Please base your pull requests on the `exfat-next` branch. -* Make sure you add 'Signed-Off' information to your commits (e. g. `git commit --signoff`). +* Make sure you add 'Signed-Off' information to your commits (e.g. `git commit --signoff`). diff --git a/fsck/fsck.c b/fsck/fsck.c index 00d1ca7..5f07a1d 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -261,7 +261,7 @@ static void exfat_free_dir_list(struct exfat *exfat) /* * get references of ancestors that include @child until the count of - * ancesters is not larger than @count and the count of characters of + * ancestors is not larger than @count and the count of characters of * their names is not larger than @max_char_len. * return true if root is reached. */ diff --git a/include/libexfat.h b/include/libexfat.h index fecd7f2..53a82a1 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -34,7 +34,7 @@ #define VOLUME_LABEL_BUFFER_SIZE (VOLUME_LABEL_MAX_LEN*MB_LEN_MAX+1) -/* Upcase tabel macro */ +/* Upcase table macro */ #define EXFAT_UPCASE_TABLE_SIZE (5836) /* Flags for tune.exfat and exfatlabel */ diff --git a/include/list.h b/include/list.h index 30a32de..cc93668 100644 --- a/include/list.h +++ b/include/list.h @@ -84,7 +84,6 @@ static inline void list_add_tail(struct list_head *new, struct list_head *head) __list_add(new, head->prev, head); } - /* * Delete a list entry by making the prev/next entries * point to each other. @@ -111,8 +110,6 @@ static inline void list_del(struct list_head *entry) entry->prev = LIST_POISON2; } - - /** * list_del_init - deletes entry from list and reinitialize it. * @entry: the element to delete from the list. diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c index 8c81221..ded1e20 100644 --- a/mkfs/mkfs.c +++ b/mkfs/mkfs.c @@ -46,14 +46,14 @@ static void exfat_setup_boot_sector(struct pbr *ppbr, struct bsx64 *pbsx = &ppbr->bsx; unsigned int i; - /* Fill exfat BIOS paramemter block */ + /* Fill exfat BIOS parameter block */ pbpb->jmp_boot[0] = 0xeb; pbpb->jmp_boot[1] = 0x76; pbpb->jmp_boot[2] = 0x90; memcpy(pbpb->oem_name, "EXFAT ", 8); memset(pbpb->res_zero, 0, 53); - /* Fill exfat extend BIOS paramemter block */ + /* Fill exfat extend BIOS parameter block */ pbsx->vol_offset = cpu_to_le64(bd->offset / bd->sector_size); pbsx->vol_length = cpu_to_le64(bd->size / bd->sector_size); pbsx->fat_offset = cpu_to_le32(finfo.fat_byte_off / bd->sector_size); -- cgit v1.2.3 From 12e6f3190c4918b7b15347ac50afba19981c7182 Mon Sep 17 00:00:00 2001 From: a1346054 <36859588+a1346054@users.noreply.github.com> Date: Tue, 17 Aug 2021 21:24:35 +0000 Subject: exfatprogs: fix additional issues in scripts * some variables such as $? don't need to be quoted ever, and in fact shouldn't --- tests/test_fsck.sh | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/test_fsck.sh b/tests/test_fsck.sh index f954814..35f81e1 100755 --- a/tests/test_fsck.sh +++ b/tests/test_fsck.sh @@ -20,8 +20,7 @@ else TEST_COUNT=$# fi -for TESTCASE_DIR in $TESTCASE_DIRS -do +for TESTCASE_DIR in $TESTCASE_DIRS; do if [ ! -e "${TESTCASE_DIR}/${IMAGE_FILE}.tar.xz" ]; then TEST_COUNT=$((TEST_COUNT - 1)) continue @@ -35,8 +34,8 @@ do DEV_FILE=$(losetup -f "${IMAGE_FILE}" --show) # Run fsck for repair - $FSCK_PROG $FSCK_OPTS $DEV_FILE - if [ "$?" -ne 1 ]; then + $FSCK_PROG $FSCK_OPTS "$DEV_FILE" + if [ $? -ne 1 ]; then echo "" echo "Failed to repair ${TESTCASE_DIR}" losetup -d "${DEV_FILE}" @@ -45,8 +44,8 @@ do echo "" # Run fsck again - $FSCK_PROG -n $DEV_FILE - if [ "$?" -ne 0 ]; then + $FSCK_PROG -n "$DEV_FILE" + if [ $? -ne 0 ]; then echo "" echo "Failed, corrupted ${TESTCASE_DIR}" losetup -d "${DEV_FILE}" @@ -57,7 +56,7 @@ do EXPECTED_FILE=${IMAGE_FILE}.expected unxz -cfk "${TESTCASE_DIR}/${EXPECTED_FILE}.xz" > "${EXPECTED_FILE}" diff <(xxd "${IMAGE_FILE}") <(xxd "${EXPECTED_FILE}") - if [ "$?" -ne 0 ]; then + if [ $? -ne 0 ]; then echo "" echo "Failed ${TESTCASE_DIR}" losetup -d "${DEV_FILE}" -- cgit v1.2.3 From 5c22bb8620b86e37cded363719b1cf80afd9c880 Mon Sep 17 00:00:00 2001 From: Christophe Vu-Brugier Date: Tue, 12 Oct 2021 15:49:08 +0200 Subject: mkfs: replace lseek() + write() with pwrite() This reduces the number of system calls issued and may improve robustness because the return value of lseek() was never checked. Signed-off-by: Christophe Vu-Brugier --- lib/libexfat.c | 6 ++---- mkfs/mkfs.c | 10 ++++------ mkfs/upcase.c | 3 +-- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/libexfat.c b/lib/libexfat.c index c54a7c8..c1c9b03 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -475,8 +475,7 @@ int exfat_read_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off) int ret; unsigned long long offset = sec_off * bd->sector_size; - lseek(bd->dev_fd, offset, SEEK_SET); - ret = read(bd->dev_fd, buf, bd->sector_size); + ret = pread(bd->dev_fd, buf, bd->sector_size, offset); if (ret < 0) { exfat_err("read failed, sec_off : %u\n", sec_off); return -1; @@ -490,8 +489,7 @@ int exfat_write_sector(struct exfat_blk_dev *bd, void *buf, int bytes; unsigned long long offset = sec_off * bd->sector_size; - lseek(bd->dev_fd, offset, SEEK_SET); - bytes = write(bd->dev_fd, buf, bd->sector_size); + bytes = pwrite(bd->dev_fd, buf, bd->sector_size, offset); if (bytes != (int)bd->sector_size) { exfat_err("write failed, sec_off : %u, bytes : %d\n", sec_off, bytes); diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c index ded1e20..b663cb8 100644 --- a/mkfs/mkfs.c +++ b/mkfs/mkfs.c @@ -235,9 +235,9 @@ static int write_fat_entry(int fd, __le32 clu, unsigned long long offset) { int nbyte; + off_t fat_entry_offset = finfo.fat_byte_off + (offset * sizeof(__le32)); - lseek(fd, finfo.fat_byte_off + (offset * sizeof(__le32)), SEEK_SET); - nbyte = write(fd, (__u8 *) &clu, sizeof(__le32)); + nbyte = pwrite(fd, (__u8 *) &clu, sizeof(__le32), fat_entry_offset); if (nbyte != sizeof(int)) { exfat_err("write failed, offset : %llu, clu : %x\n", offset, clu); @@ -321,8 +321,7 @@ static int exfat_create_bitmap(struct exfat_blk_dev *bd) for (i = 0; i < finfo.used_clu_cnt - EXFAT_FIRST_CLUSTER; i++) exfat_set_bit(bd, bitmap, i); - lseek(bd->dev_fd, finfo.bitmap_byte_off, SEEK_SET); - nbytes = write(bd->dev_fd, bitmap, finfo.bitmap_byte_len); + nbytes = pwrite(bd->dev_fd, bitmap, finfo.bitmap_byte_len, finfo.bitmap_byte_off); if (nbytes != finfo.bitmap_byte_len) { exfat_err("write failed, nbytes : %d, bitmap_len : %d\n", nbytes, finfo.bitmap_byte_len); @@ -359,8 +358,7 @@ static int exfat_create_root_dir(struct exfat_blk_dev *bd, ed[2].upcase_start_clu = cpu_to_le32(finfo.ut_start_clu); ed[2].upcase_size = cpu_to_le64(EXFAT_UPCASE_TABLE_SIZE); - lseek(bd->dev_fd, finfo.root_byte_off, SEEK_SET); - nbytes = write(bd->dev_fd, ed, dentries_len); + nbytes = pwrite(bd->dev_fd, ed, dentries_len, finfo.root_byte_off); if (nbytes != dentries_len) { exfat_err("write failed, nbytes : %d, dentries_len : %d\n", nbytes, dentries_len); diff --git a/mkfs/upcase.c b/mkfs/upcase.c index 8d5ef1a..f86fa23 100644 --- a/mkfs/upcase.c +++ b/mkfs/upcase.c @@ -506,8 +506,7 @@ int exfat_create_upcase_table(struct exfat_blk_dev *bd) { int nbytes; - lseek(bd->dev_fd, finfo.ut_byte_off, SEEK_SET); - nbytes = write(bd->dev_fd, upcase_table, EXFAT_UPCASE_TABLE_SIZE); + nbytes = pwrite(bd->dev_fd, upcase_table, EXFAT_UPCASE_TABLE_SIZE, finfo.ut_byte_off); if (nbytes != EXFAT_UPCASE_TABLE_SIZE) return -1; -- cgit v1.2.3 From c86ccdda79a672a779f4590b73ad49424e4f4c0c Mon Sep 17 00:00:00 2001 From: Christophe Vu-Brugier Date: Mon, 18 Oct 2021 16:33:59 +0200 Subject: mkfs: ensure that the cluster size is greater or equal than the sector size Signed-off-by: Christophe Vu-Brugier --- mkfs/mkfs.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c index ded1e20..030197b 100644 --- a/mkfs/mkfs.c +++ b/mkfs/mkfs.c @@ -440,6 +440,11 @@ static int exfat_build_mkfs_info(struct exfat_blk_dev *bd, unsigned long long total_clu_cnt; int clu_len; + if (ui->cluster_size < bd->sector_size) { + exfat_err("cluster size (%u bytes) is smaller than sector size (%u bytes)\n", + ui->cluster_size, bd->sector_size); + return -1; + } if (ui->boundary_align < bd->sector_size) { exfat_err("boundary alignment is too small (min %d)\n", bd->sector_size); -- cgit v1.2.3 From 2dc478ff6092e44f7c6c3fde7812efa9811da38b Mon Sep 17 00:00:00 2001 From: Christophe Vu-Brugier Date: Tue, 19 Oct 2021 14:07:50 +0200 Subject: mkfs: prevent an integer overflow when computing the FAT length The FAT length field on disk is 4 bytes. The computation of the FAT length, roughly "num_cluster * 4" can easily overflow those 4 bytes if the number of cluster is high. This patch adds a sanity check before computing the FAT length. This bug was observed when formatting a large HDD (16 TB) with a small cluster size (8 KiB). `mkfs.exfat` succeeded but Linux refused to mount the file system and reported an error with the FAT length ("bogus fat length"). Signed-off-by: Christophe Vu-Brugier --- mkfs/mkfs.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c index 030197b..cb931d5 100644 --- a/mkfs/mkfs.c +++ b/mkfs/mkfs.c @@ -452,8 +452,12 @@ static int exfat_build_mkfs_info(struct exfat_blk_dev *bd, } finfo.fat_byte_off = round_up(bd->offset + 24 * bd->sector_size, ui->boundary_align) - bd->offset; - finfo.fat_byte_len = round_up((bd->num_clusters * sizeof(int)), - ui->cluster_size); + /* Prevent integer overflow when computing the FAT length */ + if (bd->num_clusters > UINT32_MAX / 4) { + exfat_err("cluster size (%u bytes) is too small\n", ui->cluster_size); + return -1; + } + finfo.fat_byte_len = round_up((bd->num_clusters * 4), ui->cluster_size); finfo.clu_byte_off = round_up(bd->offset + finfo.fat_byte_off + finfo.fat_byte_len, ui->boundary_align) - bd->offset; if (bd->size <= finfo.clu_byte_off) { -- cgit v1.2.3 From 831e2e8a6bff1ca100e2faab21c8f51cd767307c Mon Sep 17 00:00:00 2001 From: yijiangqiu1 Date: Mon, 8 Nov 2021 15:46:27 +0800 Subject: fsck: fix double free of exfat pointer in function init_exfat(exfat,bs), if it fails to calloc memory, it will call function free_exfat and return -ENOMEM. Thus it will goto err in main function and call free_exfat(exfat) again. As follows: main ->init_exfat(exfat,bs) ->free_exfat(exfat) return -ENOMEM ->free_exfat(exfat). Let exfat = NULL if we failed to init exfat Signed-off-by: yijiangqiu1 --- fsck/fsck.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 5f07a1d..2cfacf3 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -1596,8 +1596,10 @@ int main(int argc, char * const argv[]) goto err; ret = init_exfat(exfat, bs); - if (ret) + if (ret) { + exfat = NULL; goto err; + } if (exfat_mark_volume_dirty(exfat, true)) { ret = -EIO; -- cgit v1.2.3 From 6ebabb7f07a25b00908a8bc914d871cf4d753959 Mon Sep 17 00:00:00 2001 From: yijiangqiu1 Date: Mon, 8 Nov 2021 15:49:29 +0800 Subject: fsck: fix Out-of-Bounds Accesses in function bytes_to_human_readable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In function bytes_to_human_readable, if bytes > 1024PB, the variable named i will be 6 and in “snprintf(buf, sizeof(buf), "%u.%02u %s", quoti, remain, units[i]);”, function will access units[6] . This will cause Out-of-Bounds Accesses. Signed-off-by: yijiangqiu1 --- fsck/fsck.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fsck/fsck.c b/fsck/fsck.c index 2cfacf3..6131d13 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -1477,6 +1477,11 @@ static char *bytes_to_human_readable(size_t bytes) shift += 10; } + if (i >= sizeof(units)/sizeof(units[0])) { + i = i - 1; + shift = shift - 10; + } + quoti = (unsigned int)(bytes / (1ULL << shift)); remain = 0; if (shift > 0) { -- cgit v1.2.3 From 9c164ba60ebc49381016fc4c73862e602aa0643f Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 11 Nov 2021 15:17:50 +0900 Subject: exfatprogs: release 1.1.3 version exfatprogs 1.1.3 - released 2021-11-11 ====================================== CHANGES : * mkfs.exfat: ensure that the cluster size is greater than or equal than the sector size. * mkfs.exfat: replace lseek() + write() with pwrite(). BUG FIXES : * mkfs.exfat: prevent an integer overflow when computing the FAT length. * fsck.exfat: fix a double free memory error. Signed-off-by: Hyunchul Lee --- NEWS | 13 +++++++++++++ include/version.h | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index e2d53dc..4f5ad6c 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,16 @@ +exfatprogs 1.1.3 - released 2021-11-11 +====================================== + +CHANGES : + * mkfs.exfat: ensure that the cluster size is greater than or + equal than the sector size. + * mkfs.exfat: replace lseek() + write() with pwrite(). + +BUG FIXES : + * mkfs.exfat: prevent an integer overflow when computing the FAT + length. + * fsck.exfat: fix a double free memory error. + exfatprogs 1.1.2 - released 2021-05-20 ====================================== diff --git a/include/version.h b/include/version.h index 0f8a463..da9be8d 100644 --- a/include/version.h +++ b/include/version.h @@ -5,6 +5,6 @@ #ifndef _VERSION_H -#define EXFAT_PROGS_VERSION "1.1.2" +#define EXFAT_PROGS_VERSION "1.1.3" #endif /* !_VERSION_H */ -- cgit v1.2.3 From 772ce2001e846f2d1a0ef8bed4870a9e7fd54bb6 Mon Sep 17 00:00:00 2001 From: Sven Hoexter Date: Wed, 8 Dec 2021 20:41:37 +0100 Subject: manpages: correct inaccuracies in the exfatlabel manpage Update the synopsis to match the actual behaviour, the serial_value must be provided after the device, like the volume label_string. Also there is no lower case -v option, change to upper case -V. Rewrote the description slightly, and corrected some lower/upper case mistakes along the way. Signed-off-by: Sven Hoexter --- manpages/exfatlabel.8 | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/manpages/exfatlabel.8 b/manpages/exfatlabel.8 index f3274bb..40c8d56 100644 --- a/manpages/exfatlabel.8 +++ b/manpages/exfatlabel.8 @@ -5,9 +5,8 @@ exfatlabel \- Get or Set volume label or volume serial of an exFAT filesystem .B exfatlabel [ .B \-i -.I volume-label ] [ -.B \-v +.B \-V ] .I device [ @@ -21,15 +20,14 @@ Print or set volume label of an existing exFAT filesystem. If there is a .I label_string -in argument of exfatlabel, It will be written to volume label -field on given device. If not, exfatlabel will just print out -after reading volume label field from given device. If -i or ---volume-serial is given, It can be switched to volume serial -mode. +in the argument of exfatlabel, it will be written to the volume +label field on a given device. If not, exfatlabel will just print +it after reading the volume label field from the given device. If -i +or --volume-serial is given, it will switch to volume serial mode. .PP .SH OPTIONS .TP -.BI \-i +.BI \-i\ \-\-volume-serial Switch to volume serial mode. .TP .B \-V -- cgit v1.2.3 From d58057bd9b2f4a24b874a69975ea186466937360 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 9 Dec 2021 08:56:12 +0900 Subject: fsck: introduce the option "b" to repair the main boot sector If the option "b" is given, try to recover the main boot sector even if exfat is not found. otherwise warn it and just exit. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 17 ++++++++++++++++- fsck/fsck.h | 1 + manpages/fsck.exfat.8 | 5 +++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 6131d13..c06197f 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -77,6 +77,7 @@ static struct option opts[] = { {"verbose", no_argument, NULL, 'v' }, {"help", no_argument, NULL, 'h' }, {"?", no_argument, NULL, '?' }, + {"ignore-bad-fs", no_argument, NULL, 'b' }, {NULL, 0, NULL, 0 } }; @@ -88,6 +89,7 @@ static void usage(char *name) fprintf(stderr, "\t-n | --repair-no No repair\n"); fprintf(stderr, "\t-p | --repair-auto Repair automatically\n"); fprintf(stderr, "\t-a Repair automatically\n"); + fprintf(stderr, "\t-b | --ignore-bad-fs Try to recover even if exfat is not found\n"); fprintf(stderr, "\t-V | --version Show version\n"); fprintf(stderr, "\t-v | --verbose Print debug\n"); fprintf(stderr, "\t-h | --help Show help\n"); @@ -796,9 +798,17 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs) if (exfat_read(exfat->blk_dev->dev_fd, boot_sect, sizeof(*boot_sect), 0) != (ssize_t)sizeof(*boot_sect)) { exfat_err("failed to read Main boot sector\n"); + free(boot_sect); return -EIO; } + if (memcmp(boot_sect->bpb.oem_name, "EXFAT ", 8) != 0 && + !(exfat->options & FSCK_OPTS_IGNORE_BAD_FS_NAME)) { + exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n"); + free(boot_sect); + return -ENOTSUP; + } + sect_size = 1 << boot_sect->bsx.sect_size_bits; free(boot_sect); @@ -1530,7 +1540,7 @@ int main(int argc, char * const argv[]) exfat_err("failed to init locale/codeset\n"); opterr = 0; - while ((c = getopt_long(argc, argv, "arynpVvh", opts, NULL)) != EOF) { + while ((c = getopt_long(argc, argv, "arynpbVvh", opts, NULL)) != EOF) { switch (c) { case 'n': if (ui.options & FSCK_OPTS_REPAIR_ALL) @@ -1553,6 +1563,9 @@ int main(int argc, char * const argv[]) usage(argv[0]); ui.options |= FSCK_OPTS_REPAIR_AUTO; break; + case 'b': + ui.options |= FSCK_OPTS_IGNORE_BAD_FS_NAME; + break; case 'V': version_only = true; break; @@ -1576,6 +1589,8 @@ int main(int argc, char * const argv[]) if (ui.options & FSCK_OPTS_REPAIR_WRITE) ui.ei.writeable = true; else { + if (ui.options & FSCK_OPTS_IGNORE_BAD_FS_NAME) + usage(argv[0]); ui.options |= FSCK_OPTS_REPAIR_NO; ui.ei.writeable = false; } diff --git a/fsck/fsck.h b/fsck/fsck.h index 6c91fac..56d3b3b 100644 --- a/fsck/fsck.h +++ b/fsck/fsck.h @@ -54,6 +54,7 @@ enum fsck_ui_options { FSCK_OPTS_REPAIR_AUTO = 0x08, FSCK_OPTS_REPAIR_WRITE = 0x0b, FSCK_OPTS_REPAIR_ALL = 0x0f, + FSCK_OPTS_IGNORE_BAD_FS_NAME = 0x10, }; struct exfat { diff --git a/manpages/fsck.exfat.8 b/manpages/fsck.exfat.8 index 83f7815..b93baaa 100644 --- a/manpages/fsck.exfat.8 +++ b/manpages/fsck.exfat.8 @@ -14,6 +14,8 @@ fsck.exfat \- check an exFAT filesystem ] [ .B \-y ] [ +.B \-b +] [ .B \-v ] .I device @@ -46,6 +48,9 @@ Prints the version number and exits. .TP .B \-y Repair the filesystem answering yes to all questions. +.TP +.B \-b +Try to repair the filesystem even if the exFAT filesystem is not found. .SH SEE ALSO .BR fsck (8), .BR fstab (5), -- cgit v1.2.3 From c0c202560bec628692e100d12b0a49c4e808bd27 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 9 Dec 2021 10:17:09 +0900 Subject: exfatprogs: if exfat is not found, warn it and exit If exfat is not found, make dump, label, tune warn it and exit. Signed-off-by: Hyunchul Lee --- dump/dump.c | 6 ++++++ lib/libexfat.c | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/dump/dump.c b/dump/dump.c index 7ede550..3d77bb9 100644 --- a/dump/dump.c +++ b/dump/dump.c @@ -90,6 +90,12 @@ static int exfat_show_ondisk_all_info(struct exfat_blk_dev *bd) goto free_ppbr; } + if (memcmp(ppbr->bpb.oem_name, "EXFAT ", 8) != 0) { + exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n"); + ret = -EINVAL; + goto free_ppbr; + } + pbsx = &ppbr->bsx; if (pbsx->sect_size_bits < EXFAT_MIN_SECT_SIZE_BITS || diff --git a/lib/libexfat.c b/lib/libexfat.c index c1c9b03..42e3fdc 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -374,6 +374,12 @@ off_t exfat_get_root_entry_offset(struct exfat_blk_dev *bd) return -1; } + if (memcmp(bs->bpb.oem_name, "EXFAT ", 8) != 0) { + exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n"); + free(bs); + return -1; + } + sector_size = 1 << bs->bsx.sect_size_bits; cluster_size = (1 << bs->bsx.sect_per_clus_bits) * sector_size; root_clu_off = le32_to_cpu(bs->bsx.clu_offset) * sector_size + @@ -546,6 +552,12 @@ int exfat_show_volume_serial(int fd) goto free_ppbr; } + if (memcmp(ppbr->bpb.oem_name, "EXFAT ", 8) != 0) { + exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n"); + ret = -1; + goto free_ppbr; + } + exfat_info("volume serial : 0x%x\n", ppbr->bsx.vol_serial); free_ppbr: @@ -614,6 +626,12 @@ int exfat_set_volume_serial(struct exfat_blk_dev *bd, goto free_ppbr; } + if (memcmp(ppbr->bpb.oem_name, "EXFAT ", 8) != 0) { + exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n"); + ret = -1; + goto free_ppbr; + } + bd->sector_size = 1 << ppbr->bsx.sect_size_bits; ppbr->bsx.vol_serial = ui->volume_serial; -- cgit v1.2.3 From 428b9e5b8bf55e28914af0425a047b215acac68f Mon Sep 17 00:00:00 2001 From: Pavel Reichl Date: Thu, 5 May 2022 19:49:00 +0200 Subject: fsck: Refactor bytes_to_human_readable() After recent fix there was an unnecessary extra if statement and also the for loop contained needless break statement. Signed-off-by: Pavel Reichl --- fsck/fsck.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index c06197f..ba454e6 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -1479,19 +1479,12 @@ static char *bytes_to_human_readable(size_t bytes) static const char * const units[] = {"B", "KB", "MB", "GB", "TB", "PB"}; static char buf[15*4]; unsigned int i, shift, quoti, remain; + i = sizeof(units) / sizeof(units[0]) - 1; - shift = 0; - for (i = 0; i < sizeof(units)/sizeof(units[0]); i++) { - if (bytes / (1ULL << (shift + 10)) == 0) - break; - shift += 10; - } - - if (i >= sizeof(units)/sizeof(units[0])) { - i = i - 1; - shift = shift - 10; - } + while (i && (bytes >> i * 10) == 0) + i--; + shift = i * 10; quoti = (unsigned int)(bytes / (1ULL << shift)); remain = 0; if (shift > 0) { -- cgit v1.2.3 From 8069e05285423841d781a4f3e65067ac91ed3b83 Mon Sep 17 00:00:00 2001 From: Pavel Reichl Date: Fri, 6 May 2022 16:37:49 +0200 Subject: README: Add checkpatch note into contribution section Signed-off-by: Pavel Reichl --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b3fbdd0..4a0a772 100644 --- a/README.md +++ b/README.md @@ -117,3 +117,4 @@ If you have any issues, please create [issues][1] or contact to [Namjae Jeon](ma ## Contributor information * Please base your pull requests on the `exfat-next` branch. * Make sure you add 'Signed-Off' information to your commits (e.g. `git commit --signoff`). +* Please check your code contribution using kernel dev-tool script [checkpatch](https://docs.kernel.org/dev-tools/checkpatch.html). -- cgit v1.2.3 From 88a334ac2f98affcc526e861c2ed1b8bd2c34b3c Mon Sep 17 00:00:00 2001 From: Pavel Reichl Date: Wed, 11 May 2022 23:20:43 +0200 Subject: exfatprogs: fix some minor code issues * Add checking of function return value * Fix potentially overflowing expression Signed-off-by: Pavel Reichl --- fsck/de_iter.c | 9 ++++++--- lib/libexfat.c | 13 ++++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/fsck/de_iter.c b/fsck/de_iter.c index bc95c49..587b027 100644 --- a/fsck/de_iter.c +++ b/fsck/de_iter.c @@ -82,6 +82,9 @@ static int read_ahead_next_blocks(struct exfat_de_iter *iter, offset >= iter->ra_begin_offset) { ret = get_next_clus(exfat, iter->parent, p_clus, &ra_p_clus); + if (ret) + return ret; + if (ra_p_clus == EXFAT_EOF_CLUSTER) return -EIO; @@ -172,10 +175,10 @@ static ssize_t read_block(struct exfat_de_iter *iter, unsigned int block) ret = get_next_clus(exfat, iter->parent, prev_desc->p_clus, &desc->p_clus); desc->offset = 0; - if (!ret && desc->p_clus == EXFAT_EOF_CLUSTER) - return EOF; - else if (ret) + if (ret) return ret; + else if (desc->p_clus == EXFAT_EOF_CLUSTER) + return EOF; } } diff --git a/lib/libexfat.c b/lib/libexfat.c index 42e3fdc..ee48d3a 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -470,7 +470,12 @@ int exfat_set_volume_label(struct exfat_blk_dev *bd, exfat_err("volume entry write failed: %d\n", errno); return -1; } - fsync(bd->dev_fd); + + if (fsync(bd->dev_fd) == -1) { + exfat_err("failed to sync volume entry: %d, %s\n", errno, + strerror(errno)); + return -1; + } exfat_info("new label: %s\n", label_input); return 0; @@ -479,7 +484,8 @@ int exfat_set_volume_label(struct exfat_blk_dev *bd, int exfat_read_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off) { int ret; - unsigned long long offset = sec_off * bd->sector_size; + unsigned long long offset = + (unsigned long long)sec_off * bd->sector_size; ret = pread(bd->dev_fd, buf, bd->sector_size, offset); if (ret < 0) { @@ -493,7 +499,8 @@ int exfat_write_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off) { int bytes; - unsigned long long offset = sec_off * bd->sector_size; + unsigned long long offset = + (unsigned long long)sec_off * bd->sector_size; bytes = pwrite(bd->dev_fd, buf, bd->sector_size, offset); if (bytes != (int)bd->sector_size) { -- cgit v1.2.3 From 3c6edd718ac72ea9e3444e005ab7f45f60746022 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Fri, 26 Mar 2021 08:16:37 +0900 Subject: fsck: add -Wextra compile option Add -Wextra compile option Signed-off-by: Hyunchul Lee --- fsck/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fsck/Makefile.am b/fsck/Makefile.am index 57a0ede..604cac2 100644 --- a/fsck/Makefile.am +++ b/fsck/Makefile.am @@ -1,4 +1,4 @@ -AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common +AM_CFLAGS = -Wall -Wextra -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common fsck_exfat_LDADD = $(top_builddir)/lib/libexfat.a sbin_PROGRAMS = fsck.exfat -- cgit v1.2.3 From f4aa96dedd1b057adea8d25341287dcee9d8e533 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 3 May 2021 16:30:39 +0900 Subject: fsck: tests: allow of running testcases with an image file Allow of running testcases with an image file, instead of a loopback device. Signed-off-by: Hyunchul Lee --- tests/test_fsck.sh | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/test_fsck.sh b/tests/test_fsck.sh index 35f81e1..936db54 100755 --- a/tests/test_fsck.sh +++ b/tests/test_fsck.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash TESTCASE_DIR=$1 +NEED_LOOPDEV=$2 IMAGE_FILE=exfat.img -FSCK_PROG=../build/sbin/fsck.exfat +FSCK_PROG=fsck.exfat FSCK_OPTS=-y PASS_COUNT=0 @@ -31,14 +32,20 @@ for TESTCASE_DIR in $TESTCASE_DIRS; do # Set up image file as loop device tar -C . -xf "${TESTCASE_DIR}/${IMAGE_FILE}.tar.xz" - DEV_FILE=$(losetup -f "${IMAGE_FILE}" --show) + if [ $NEED_LOOPDEV ]; then + DEV_FILE=$(losetup -f "${IMAGE_FILE}" --show) + else + DEV_FILE=$IMAGE_FILE + fi # Run fsck for repair $FSCK_PROG $FSCK_OPTS "$DEV_FILE" if [ $? -ne 1 ]; then echo "" echo "Failed to repair ${TESTCASE_DIR}" - losetup -d "${DEV_FILE}" + if [ $NEED_LOOPDEV ]; then + losetup -d "${DEV_FILE}" + fi cleanup fi @@ -48,7 +55,9 @@ for TESTCASE_DIR in $TESTCASE_DIRS; do if [ $? -ne 0 ]; then echo "" echo "Failed, corrupted ${TESTCASE_DIR}" - losetup -d "${DEV_FILE}" + if [ $NEED_LOOPDEV ]; then + losetup -d "${DEV_FILE}" + fi cleanup fi @@ -59,7 +68,9 @@ for TESTCASE_DIR in $TESTCASE_DIRS; do if [ $? -ne 0 ]; then echo "" echo "Failed ${TESTCASE_DIR}" - losetup -d "${DEV_FILE}" + if [ $NEED_LOOPDEV ]; then + losetup -d "${DEV_FILE}" + fi cleanup fi fi @@ -68,6 +79,8 @@ for TESTCASE_DIR in $TESTCASE_DIRS; do echo "Passed ${TESTCASE_DIR}" PASS_COUNT=$((PASS_COUNT + 1)) - losetup -d "${DEV_FILE}" + if [ $NEED_LOOPDEV ]; then + losetup -d "${DEV_FILE}" + fi done cleanup -- cgit v1.2.3 From 6faa78e7e75b7503a83c3f3f1626be159da1ea07 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Fri, 9 Apr 2021 13:07:45 +0900 Subject: fsck: don't stop traveling directory entries even if there is an unknown entry or corrupted file entry set, keep traveling entries. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index ba454e6..0dced9d 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -1220,7 +1220,7 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir) ret = read_file(de_iter, &node, &dentry_count); if (ret < 0) { exfat_stat.error_count++; - goto err; + break; } else if (ret) { exfat_stat.error_count++; exfat_stat.fixed_count++; @@ -1259,11 +1259,10 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir) case EXFAT_LAST: goto out; default: - if (IS_EXFAT_DELETED(dentry->type)) - break; - exfat_err("unknown entry type. 0x%x\n", dentry->type); - ret = -EINVAL; - goto err; + if (!IS_EXFAT_DELETED(dentry->type)) + exfat_err("unknown entry type. 0x%x\n", + dentry->type); + break; } exfat_de_iter_advance(de_iter, dentry_count); -- cgit v1.2.3 From e818b535db63c1f9c163661e55bf4068d6c16aaa Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 3 May 2021 16:32:08 +0900 Subject: fsck: subtract the count of fixed files from the count of corrupted files Subtract the count of fixed files from the count of corrupted files. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 0dced9d..9d89813 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -1511,7 +1511,8 @@ static void exfat_show_info(struct exfat *exfat, const char *dev_name, exfat_stat.dir_count, exfat_stat.file_count); if (errors || exfat->dirty) printf("%s: files corrupted %ld, files fixed %ld\n", dev_name, - exfat_stat.error_count, exfat_stat.fixed_count); + exfat_stat.error_count - exfat_stat.fixed_count, + exfat_stat.fixed_count); } int main(int argc, char * const argv[]) -- cgit v1.2.3 From 4e29696e73c16a4572ca22a4a43185c5e838523a Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 6 May 2021 17:25:32 +0900 Subject: fsck: print "corrupted" if there are still corrupted files Print "corrupted" if there are still corrupted files. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 9d89813..e3b603b 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -1496,9 +1496,10 @@ static char *bytes_to_human_readable(size_t bytes) return buf; } -static void exfat_show_info(struct exfat *exfat, const char *dev_name, - int errors) +static void exfat_show_info(struct exfat *exfat, const char *dev_name) { + bool clean; + exfat_info("sector size: %s\n", bytes_to_human_readable(1 << exfat->bs->bsx.sect_size_bits)); exfat_info("cluster size: %s\n", @@ -1506,10 +1507,12 @@ static void exfat_show_info(struct exfat *exfat, const char *dev_name, exfat_info("volume size: %s\n", bytes_to_human_readable(exfat->blk_dev->size)); + clean = exfat_stat.error_count == 0 || + exfat_stat.error_count == exfat_stat.fixed_count; printf("%s: %s. directories %ld, files %ld\n", dev_name, - errors ? "checking stopped" : "clean", + clean ? "clean" : "corrupted", exfat_stat.dir_count, exfat_stat.file_count); - if (errors || exfat->dirty) + if (exfat_stat.error_count) printf("%s: files corrupted %ld, files fixed %ld\n", dev_name, exfat_stat.error_count - exfat_stat.fixed_count, exfat_stat.fixed_count); @@ -1639,12 +1642,13 @@ int main(int argc, char * const argv[]) exfat_mark_volume_dirty(exfat, false); out: - exfat_show_info(exfat, ui.ei.dev_name, ret); + exfat_show_info(exfat, ui.ei.dev_name); err: - if (ret == -EINVAL) - exit_code = FSCK_EXIT_ERRORS_LEFT; - else if (ret) + if (ret && ret != -EINVAL) exit_code = FSCK_EXIT_OPERATION_ERROR; + else if (ret == -EINVAL || + exfat_stat.error_count != exfat_stat.fixed_count) + exit_code = FSCK_EXIT_ERRORS_LEFT; else if (exfat->dirty) exit_code = FSCK_EXIT_CORRECTED; else -- cgit v1.2.3 From 05bc35b1c7d9e12776211f03fecb4ffdd362d8c5 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 3 May 2021 15:25:51 +0900 Subject: fsck: handle bad clusters properly in check_clus_chain() Truncate a NotFat file if a next cluster is BAD, And even if a next cluster is out of range, allocate the cluster to a file Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index e3b603b..7c469af 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -480,19 +480,31 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) /* This cluster is allocated or not */ if (get_next_clus(exfat, node, clus, &next)) goto truncate_file; - if (!node->is_contiguous) { - if (!heap_clus(exfat, next) && - next != EXFAT_EOF_CLUSTER) { + if (next == EXFAT_BAD_CLUSTER) { + if (repair_file_ask(&exfat->de_iter, node, + ER_FILE_INVALID_CLUS, + "BAD cluster. truncate to %" + PRIu64 " bytes", + count * exfat->clus_size)) + goto truncate_file; + else + return -EINVAL; + } else if (!node->is_contiguous) { + if (next != EXFAT_EOF_CLUSTER && + !heap_clus(exfat, next)) { if (repair_file_ask(&exfat->de_iter, node, ER_FILE_INVALID_CLUS, - "broken cluster chain. " - "truncate to %" + "broken cluster chain. truncate to %" PRIu64 " bytes", - count * exfat->clus_size)) + (count + 1) * exfat->clus_size)) { + count++; + prev = clus; + EXFAT_BITMAP_SET(exfat->alloc_bitmap, + clus - EXFAT_FIRST_CLUSTER); goto truncate_file; - - else + } else { return -EINVAL; + } } } -- cgit v1.2.3 From d46254931505ea7aa17bf5519c7ddcc3c9c3bb87 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 24 Feb 2022 10:57:00 +0900 Subject: fsck: not cut cluster chain if one of them is marked as free Not cut a cluster chain if FAT entries are valid, Even if one of them is marked as free in the bitmap. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 7c469af..b5ee1c3 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -467,13 +467,10 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) if (!EXFAT_BITMAP_GET(exfat->disk_bitmap, clus - EXFAT_FIRST_CLUSTER)) { - if (repair_file_ask(&exfat->de_iter, node, - ER_FILE_INVALID_CLUS, - "cluster is marked as free. truncate to %" PRIu64 " bytes", - count * exfat->clus_size)) - goto truncate_file; - - else + if (!repair_file_ask(&exfat->de_iter, node, + ER_FILE_INVALID_CLUS, + "cluster %#x is marked as free", + clus)) return -EINVAL; } @@ -544,8 +541,10 @@ truncate_file: /* remaining clusters will be freed while FAT is compared with * alloc_bitmap. */ - if (!node->is_contiguous && heap_clus(exfat, prev)) - return set_fat(exfat, prev, EXFAT_EOF_CLUSTER); + if (!node->is_contiguous && heap_clus(exfat, prev)) { + if (set_fat(exfat, prev, EXFAT_EOF_CLUSTER)) + return -EIO; + } return 1; } -- cgit v1.2.3 From f5f0c944aeea2024f24629d2bdd797bebe369296 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 3 May 2021 15:28:00 +0900 Subject: fsck: fix infinite loop in read_children() if reading file dentry set is failed, we must skip the first directory entry. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index b5ee1c3..69be851 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -958,8 +958,8 @@ static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node) return valid ? ret : -EINVAL; } -static int read_file_dentries(struct exfat_de_iter *iter, - struct exfat_inode **new_node, int *skip_dentries) +static int read_file_dentry_set(struct exfat_de_iter *iter, + struct exfat_inode **new_node, int *skip_dentries) { struct exfat_dentry *file_de, *stream_de, *name_de; struct exfat_inode *node; @@ -1026,7 +1026,7 @@ static int read_file_dentries(struct exfat_de_iter *iter, *new_node = node; return 0; err: - *skip_dentries = 0; + *skip_dentries = 1; *new_node = NULL; free_exfat_inode(node); return ret; @@ -1040,7 +1040,7 @@ static int read_file(struct exfat_de_iter *de_iter, *new_node = NULL; - ret = read_file_dentries(de_iter, &node, dentry_count); + ret = read_file_dentry_set(de_iter, &node, dentry_count); if (ret) return ret; -- cgit v1.2.3 From ac0cea6cab031dfc0eeb10acb65747cc84e56822 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Wed, 24 Aug 2022 12:59:12 +0900 Subject: fsck: split exfat into exfat and exfat_fsck To move shared code to lib diretory, split exfat structure into exfat and exfat_fsck structure. Signed-off-by: Hyunchul Lee --- fsck/de_iter.c | 4 +- fsck/fsck.c | 284 +++++++++++++++++++++++++++++++++------------------------ fsck/fsck.h | 16 ++-- fsck/repair.c | 29 +++--- fsck/repair.h | 4 +- 5 files changed, 193 insertions(+), 144 deletions(-) diff --git a/fsck/de_iter.c b/fsck/de_iter.c index 587b027..a9fcdd9 100644 --- a/fsck/de_iter.c +++ b/fsck/de_iter.c @@ -203,7 +203,7 @@ static ssize_t read_block(struct exfat_de_iter *iter, unsigned int block) } int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, - struct exfat_inode *dir) + struct exfat_inode *dir, struct buffer_desc *bd) { iter->exfat = exfat; iter->parent = dir; @@ -216,7 +216,7 @@ int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, iter->ra_partial_size = MIN(iter->ra_partial_size, 8 * KB); if (!iter->buffer_desc) - iter->buffer_desc = exfat->buffer_desc; + iter->buffer_desc = bd; if (iter->parent->size == 0) return EOF; diff --git a/fsck/fsck.c b/fsck/fsck.c index 69be851..4a4d69a 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -65,6 +65,7 @@ struct path_resolve_ctx { char local_path[PATH_MAX * MB_LEN_MAX + 1]; }; +struct exfat_fsck exfat_fsck; struct exfat_stat exfat_stat; struct path_resolve_ctx path_resolve_ctx; @@ -125,10 +126,6 @@ static struct exfat_inode *alloc_exfat_inode(__u16 attr) node->last_pclus = EXFAT_EOF_CLUSTER; node->attr = attr; - if (attr & ATTR_SUBDIR) - exfat_stat.dir_count++; - else - exfat_stat.file_count++; return node; } @@ -186,10 +183,8 @@ static void inode_free_ancestors(struct exfat_inode *child) return; } -static void free_exfat(struct exfat *exfat) +void exfat_free_exfat(struct exfat *exfat) { - int i; - if (exfat) { if (exfat->bs) free(exfat->bs); @@ -197,21 +192,20 @@ static void free_exfat(struct exfat *exfat) free(exfat->alloc_bitmap); if (exfat->disk_bitmap) free(exfat->disk_bitmap); - for (i = 0; i < 2; i++) { - if (exfat->buffer_desc[i].buffer) - free(exfat->buffer_desc[i].buffer); - if (exfat->buffer_desc[i].dirty) - free(exfat->buffer_desc[i].dirty); - } free(exfat); } } -static int init_exfat(struct exfat *exfat, struct pbr *bs) +struct exfat *exfat_alloc_exfat(struct exfat_blk_dev *blk_dev, struct pbr *bs) { - int i; + struct exfat *exfat; + + exfat = (struct exfat *)calloc(1, sizeof(*exfat)); + if (!exfat) + return NULL; INIT_LIST_HEAD(&exfat->dir_list); + exfat->blk_dev = blk_dev; exfat->bs = bs; exfat->clus_count = le32_to_cpu(bs->bsx.clu_count); exfat->clus_size = EXFAT_CLUSTER_SIZE(bs); @@ -232,22 +226,47 @@ static int init_exfat(struct exfat *exfat, struct pbr *bs) goto err; } - /* allocate cluster buffers */ - for (i = 0; i < 2; i++) { - exfat->buffer_desc[i].buffer = - (char *)malloc(exfat->clus_size); - if (!exfat->buffer_desc[i].buffer) + return exfat; +err: + exfat_free_exfat(exfat); + return NULL; +} + +void exfat_free_buffer(struct buffer_desc *bd, int count) +{ + int i; + + for (i = 0; i < count; i++) { + if (bd[i].buffer) + free(bd[i].buffer); + if (bd[i].dirty) + free(bd[i].dirty); + } + free(bd); +} + +struct buffer_desc *exfat_alloc_buffer(int count, + unsigned int clu_size, unsigned int sect_size) +{ + struct buffer_desc *bd; + int i; + + bd = (struct buffer_desc *)calloc(sizeof(*bd), count); + if (!bd) + return NULL; + + for (i = 0; i < count; i++) { + bd[i].buffer = (char *)malloc(clu_size); + if (!bd[i].buffer) goto err; - exfat->buffer_desc[i].dirty = - (char *)calloc( - (exfat->clus_size / exfat->sect_size), 1); - if (!exfat->buffer_desc[i].dirty) + bd[i].dirty = (char *)calloc(clu_size / sect_size, 1); + if (!bd[i].dirty) goto err; } - return 0; + return bd; err: - free_exfat(exfat); - return -ENOMEM; + exfat_free_buffer(bd, count); + return NULL; } static void exfat_free_dir_list(struct exfat *exfat) @@ -358,7 +377,7 @@ static int resolve_path_parent(struct path_resolve_ctx *ctx, ({ \ resolve_path_parent(&path_resolve_ctx, \ (iter)->parent, inode); \ - exfat_repair_ask((iter)->exfat, code, \ + exfat_repair_ask(&exfat_fsck, code, \ "ERROR: %s: " fmt, \ path_resolve_ctx.local_path, \ ##__VA_ARGS__); \ @@ -410,8 +429,10 @@ static int set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus) return 0; } -static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) +static int check_clus_chain(struct exfat_de_iter *de_iter, + struct exfat_inode *node) { + struct exfat *exfat = de_iter->exfat; struct exfat_dentry *stream_de; clus_t clus, prev, next; uint64_t count, max_count; @@ -426,9 +447,9 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) /* the first cluster is wrong */ if ((node->size == 0 && node->first_clus != EXFAT_FREE_CLUSTER) || - (node->size > 0 && !heap_clus(exfat, node->first_clus))) { - if (repair_file_ask(&exfat->de_iter, node, - ER_FILE_FIRST_CLUS, "first cluster is wrong")) + (node->size > 0 && !heap_clus(exfat, node->first_clus))) { + if (repair_file_ask(de_iter, node, + ER_FILE_FIRST_CLUS, "first cluster is wrong")) goto truncate_file; else return -EINVAL; @@ -438,11 +459,11 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) if (count >= max_count) { if (node->is_contiguous) break; - if (repair_file_ask(&exfat->de_iter, node, - ER_FILE_SMALLER_SIZE, - "more clusters are allocated. " - "truncate to %" PRIu64 " bytes", - count * exfat->clus_size)) + if (repair_file_ask(de_iter, node, + ER_FILE_SMALLER_SIZE, + "more clusters are allocated. truncate to %" + PRIu64 " bytes", + count * exfat->clus_size)) goto truncate_file; else return -EINVAL; @@ -453,13 +474,12 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) * the other file, or there is a loop in cluster chain. */ if (EXFAT_BITMAP_GET(exfat->alloc_bitmap, - clus - EXFAT_FIRST_CLUSTER)) { - if (repair_file_ask(&exfat->de_iter, node, - ER_FILE_DUPLICATED_CLUS, - "cluster is already allocated for " - "the other file. truncated to %" - PRIu64 " bytes", - count * exfat->clus_size)) + clus - EXFAT_FIRST_CLUSTER)) { + if (repair_file_ask(de_iter, node, + ER_FILE_DUPLICATED_CLUS, + "cluster is already allocated for the other file. truncated to %" + PRIu64 " bytes", + count * exfat->clus_size)) goto truncate_file; else return -EINVAL; @@ -478,7 +498,7 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) if (get_next_clus(exfat, node, clus, &next)) goto truncate_file; if (next == EXFAT_BAD_CLUSTER) { - if (repair_file_ask(&exfat->de_iter, node, + if (repair_file_ask(de_iter, node, ER_FILE_INVALID_CLUS, "BAD cluster. truncate to %" PRIu64 " bytes", @@ -489,11 +509,11 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) } else if (!node->is_contiguous) { if (next != EXFAT_EOF_CLUSTER && !heap_clus(exfat, next)) { - if (repair_file_ask(&exfat->de_iter, node, - ER_FILE_INVALID_CLUS, - "broken cluster chain. truncate to %" - PRIu64 " bytes", - (count + 1) * exfat->clus_size)) { + if (repair_file_ask(de_iter, node, + ER_FILE_INVALID_CLUS, + "broken cluster chain. truncate to %" + PRIu64 " bytes", + (count + 1) * exfat->clus_size)) { count++; prev = clus; EXFAT_BITMAP_SET(exfat->alloc_bitmap, @@ -507,16 +527,16 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) count++; EXFAT_BITMAP_SET(exfat->alloc_bitmap, - clus - EXFAT_FIRST_CLUSTER); + clus - EXFAT_FIRST_CLUSTER); prev = clus; clus = next; } if (count < max_count) { - if (repair_file_ask(&exfat->de_iter, node, - ER_FILE_LARGER_SIZE, "less clusters are allocated. " - "truncates to %" PRIu64 " bytes", - count * exfat->clus_size)) + if (repair_file_ask(de_iter, node, ER_FILE_LARGER_SIZE, + "less clusters are allocated. truncates to %" + PRIu64 " bytes", + count * exfat->clus_size)) goto truncate_file; else return -EINVAL; @@ -528,15 +548,15 @@ truncate_file: if (!heap_clus(exfat, prev)) node->first_clus = EXFAT_FREE_CLUSTER; - exfat_de_iter_get_dirty(&exfat->de_iter, 1, &stream_de); + exfat_de_iter_get_dirty(de_iter, 1, &stream_de); if (count * exfat->clus_size < - le64_to_cpu(stream_de->stream_valid_size)) + le64_to_cpu(stream_de->stream_valid_size)) stream_de->stream_valid_size = cpu_to_le64( - count * exfat->clus_size); + count * exfat->clus_size); if (!heap_clus(exfat, prev)) stream_de->stream_start_clu = EXFAT_FREE_CLUSTER; stream_de->stream_size = cpu_to_le64( - count * exfat->clus_size); + count * exfat->clus_size); /* remaining clusters will be freed while FAT is compared with * alloc_bitmap. @@ -646,9 +666,6 @@ static int exfat_mark_volume_dirty(struct exfat *exfat, bool dirty) { uint16_t flags; - if (!(exfat->options & FSCK_OPTS_REPAIR_WRITE)) - return 0; - flags = le16_to_cpu(exfat->bs->bsx.vol_flags); if (dirty) flags |= 0x02; @@ -795,7 +812,9 @@ free_sector: return ret; } -static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs) +static int exfat_boot_region_check(struct exfat_blk_dev *blkdev, + struct pbr **bs, + bool ignore_bad_fs_name) { struct pbr *boot_sect; unsigned int sect_size; @@ -806,7 +825,7 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs) if (boot_sect == NULL) return -ENOMEM; - if (exfat_read(exfat->blk_dev->dev_fd, boot_sect, + if (exfat_read(blkdev->dev_fd, boot_sect, sizeof(*boot_sect), 0) != (ssize_t)sizeof(*boot_sect)) { exfat_err("failed to read Main boot sector\n"); free(boot_sect); @@ -814,7 +833,7 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs) } if (memcmp(boot_sect->bpb.oem_name, "EXFAT ", 8) != 0 && - !(exfat->options & FSCK_OPTS_IGNORE_BAD_FS_NAME)) { + !ignore_bad_fs_name) { exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n"); free(boot_sect); return -ENOTSUP; @@ -824,16 +843,17 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs) free(boot_sect); /* check boot regions */ - ret = read_boot_region(exfat->blk_dev, bs, + ret = read_boot_region(blkdev, bs, BOOT_SEC_IDX, sect_size, true); - if (ret == -EINVAL && exfat_repair_ask(exfat, ER_BS_BOOT_REGION, - "boot region is corrupted. try to restore the region from backup" + if (ret == -EINVAL && + exfat_repair_ask(&exfat_fsck, ER_BS_BOOT_REGION, + "boot region is corrupted. try to restore the region from backup" )) { const unsigned int sector_sizes[] = {512, 4096, 1024, 2048}; unsigned int i; if (sect_size >= 512 && sect_size <= EXFAT_MAX_SECTOR_SIZE) { - ret = read_boot_region(exfat->blk_dev, bs, + ret = read_boot_region(blkdev, bs, BACKUP_BOOT_SEC_IDX, sect_size, false); if (!ret) @@ -844,7 +864,7 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs) if (sector_sizes[i] == sect_size) continue; - ret = read_boot_region(exfat->blk_dev, bs, + ret = read_boot_region(blkdev, bs, BACKUP_BOOT_SEC_IDX, sector_sizes[i], false); if (!ret) { @@ -857,7 +877,7 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs) return ret; restore: - ret = restore_boot_region(exfat->blk_dev, sect_size); + ret = restore_boot_region(blkdev, sect_size); if (ret) { exfat_err("failed to restore boot region from backup\n"); free(*bs); @@ -913,7 +933,7 @@ static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node) uint16_t checksum; bool valid = true; - ret = check_clus_chain(exfat, node); + ret = check_clus_chain(iter, node); if (ret < 0) return ret; @@ -1050,6 +1070,10 @@ static int read_file(struct exfat_de_iter *de_iter, return -EINVAL; } + if (node->attr & ATTR_SUBDIR) + exfat_stat.dir_count++; + else + exfat_stat.file_count++; *new_node = node; return ret; } @@ -1083,19 +1107,19 @@ static bool read_volume_label(struct exfat_de_iter *iter) return true; } -static void exfat_bitmap_set_range(struct exfat *exfat, - clus_t start_clus, clus_t count) +static void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, + clus_t start_clus, clus_t count) { clus_t clus; if (!heap_clus(exfat, start_clus) || - !heap_clus(exfat, start_clus + count)) + !heap_clus(exfat, start_clus + count)) return; clus = start_clus; while (clus < start_clus + count) { - EXFAT_BITMAP_SET(exfat->alloc_bitmap, - clus - EXFAT_FIRST_CLUSTER); + EXFAT_BITMAP_SET(bitmap, + clus - EXFAT_FIRST_CLUSTER); clus++; } } @@ -1106,6 +1130,10 @@ static bool read_bitmap(struct exfat_de_iter *iter) struct exfat *exfat; exfat = iter->exfat; + + if (heap_clus(exfat, exfat->disk_bitmap_clus)) + return true; + if (exfat_de_iter_get(iter, 0, &dentry)) return false; @@ -1128,9 +1156,10 @@ static bool read_bitmap(struct exfat_de_iter *iter) exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu); exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8); - exfat_bitmap_set_range(exfat, le64_to_cpu(dentry->bitmap_start_clu), - DIV_ROUND_UP(exfat->disk_bitmap_size, - exfat->clus_size)); + exfat_bitmap_set_range(exfat, exfat->alloc_bitmap, + le64_to_cpu(dentry->bitmap_start_clu), + DIV_ROUND_UP(exfat->disk_bitmap_size, + exfat->clus_size)); if (exfat_read(exfat->blk_dev->dev_fd, exfat->disk_bitmap, exfat->disk_bitmap_size, @@ -1191,24 +1220,26 @@ static bool read_upcase_table(struct exfat_de_iter *iter) return false; } - exfat_bitmap_set_range(exfat, le32_to_cpu(dentry->upcase_start_clu), - DIV_ROUND_UP(le64_to_cpu(dentry->upcase_size), - exfat->clus_size)); + exfat_bitmap_set_range(exfat, exfat->alloc_bitmap, + le32_to_cpu(dentry->upcase_start_clu), + DIV_ROUND_UP(le64_to_cpu(dentry->upcase_size), + exfat->clus_size)); free(upcase); return true; } -static int read_children(struct exfat *exfat, struct exfat_inode *dir) +static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir) { - int ret; + struct exfat *exfat = fsck->exfat; struct exfat_inode *node = NULL; struct exfat_dentry *dentry; - int dentry_count; struct exfat_de_iter *de_iter; + int dentry_count; + int ret; - de_iter = &exfat->de_iter; - ret = exfat_de_iter_init(de_iter, exfat, dir); + de_iter = &fsck->de_iter; + ret = exfat_de_iter_init(de_iter, exfat, dir, fsck->buffer_desc); if (ret == EOF) return 0; else if (ret) @@ -1288,8 +1319,9 @@ err: return ret; } -static int write_dirty_fat(struct exfat *exfat) +static int write_dirty_fat(struct exfat_fsck *fsck) { + struct exfat *exfat = fsck->exfat; struct buffer_desc *bd; off_t offset; ssize_t len; @@ -1299,7 +1331,7 @@ static int write_dirty_fat(struct exfat *exfat) clus = 0; last_clus = le32_to_cpu(exfat->bs->bsx.clu_count) + 2; - bd = exfat->buffer_desc; + bd = fsck->buffer_desc; idx = 0; offset = le32_to_cpu(exfat->bs->bsx.fat_offset) * exfat->sect_size; @@ -1352,8 +1384,9 @@ static int write_dirty_fat(struct exfat *exfat) return 0; } -static int write_dirty_bitmap(struct exfat *exfat) +static int write_dirty_bitmap(struct exfat_fsck *fsck) { + struct exfat *exfat = fsck->exfat; struct buffer_desc *bd; off_t offset, last_offset, bitmap_offset; ssize_t len; @@ -1366,7 +1399,7 @@ static int write_dirty_bitmap(struct exfat *exfat) read_size = exfat->clus_size; write_size = exfat->sect_size; - bd = exfat->buffer_desc; + bd = fsck->buffer_desc; idx = 0; while (offset < last_offset) { @@ -1396,13 +1429,13 @@ static int write_dirty_bitmap(struct exfat *exfat) return 0; } -static int reclaim_free_clusters(struct exfat *exfat) +static int reclaim_free_clusters(struct exfat_fsck *fsck) { - if (write_dirty_fat(exfat)) { + if (write_dirty_fat(fsck)) { exfat_err("failed to write fat entries\n"); return -EIO; } - if (write_dirty_bitmap(exfat)) { + if (write_dirty_bitmap(fsck)) { exfat_err("failed to write bitmap\n"); return -EIO; } @@ -1416,8 +1449,9 @@ static int reclaim_free_clusters(struct exfat *exfat) * 2. free all of file exfat_nodes. * 3. if the directory does not have children, free its exfat_node. */ -static int exfat_filesystem_check(struct exfat *exfat) +static int exfat_filesystem_check(struct exfat_fsck *fsck) { + struct exfat *exfat = fsck->exfat; struct exfat_inode *dir; int ret = 0, dir_errors; @@ -1429,7 +1463,8 @@ static int exfat_filesystem_check(struct exfat *exfat) list_add(&exfat->root->list, &exfat->dir_list); while (!list_empty(&exfat->dir_list)) { - dir = list_entry(exfat->dir_list.next, struct exfat_inode, list); + dir = list_entry(exfat->dir_list.next, + struct exfat_inode, list); if (!(dir->attr & ATTR_SUBDIR)) { fsck_err(dir->parent, dir, @@ -1439,7 +1474,7 @@ static int exfat_filesystem_check(struct exfat *exfat) goto out; } - dir_errors = read_children(exfat, dir); + dir_errors = read_children(fsck, dir); if (dir_errors) { resolve_path(&path_resolve_ctx, dir); exfat_debug("failed to check dentries: %s\n", @@ -1454,7 +1489,7 @@ static int exfat_filesystem_check(struct exfat *exfat) out: exfat_free_dir_list(exfat); exfat->root = NULL; - if (exfat->dirty_fat && reclaim_free_clusters(exfat)) + if (fsck->dirty_fat && reclaim_free_clusters(fsck)) return -EIO; return ret; } @@ -1479,6 +1514,7 @@ static int exfat_root_dir_check(struct exfat *exfat) root->size = clus_count * exfat->clus_size; exfat->root = root; + exfat_stat.dir_count++; exfat_debug("root directory: start cluster[0x%x] size[0x%" PRIx64 "]\n", root->first_clus, root->size); return 0; @@ -1507,8 +1543,9 @@ static char *bytes_to_human_readable(size_t bytes) return buf; } -static void exfat_show_info(struct exfat *exfat, const char *dev_name) +static void exfat_show_info(struct exfat_fsck *fsck, const char *dev_name) { + struct exfat *exfat = fsck->exfat; bool clean; exfat_info("sector size: %s\n", @@ -1533,7 +1570,6 @@ int main(int argc, char * const argv[]) { struct fsck_user_input ui; struct exfat_blk_dev bd; - struct exfat *exfat = NULL; struct pbr *bs = NULL; int c, ret, exit_code; bool version_only = false; @@ -1602,6 +1638,8 @@ int main(int argc, char * const argv[]) ui.ei.writeable = false; } + exfat_fsck.options = ui.options; + snprintf(ui.ei.dev_name, sizeof(ui.ei.dev_name), "%s", argv[optind]); ret = exfat_get_blk_dev_info(&ui.ei, &bd); if (ret < 0) { @@ -1609,39 +1647,41 @@ int main(int argc, char * const argv[]) return FSCK_EXIT_OPERATION_ERROR; } - exfat = (struct exfat *)calloc(1, sizeof(*exfat)); - if (!exfat) { - exfat_err("failed to allocate exfat\n"); - ret = -ENOMEM; + ret = exfat_boot_region_check(&bd, &bs, + ui.options & FSCK_OPTS_IGNORE_BAD_FS_NAME ? + true : false); + if (ret) goto err; - } - exfat->blk_dev = &bd; - exfat->options = ui.options; - ret = exfat_boot_region_check(exfat, &bs); - if (ret) + exfat_fsck.exfat = exfat_alloc_exfat(&bd, bs); + if (!exfat_fsck.exfat) { + ret = -ENOMEM; goto err; + } - ret = init_exfat(exfat, bs); - if (ret) { - exfat = NULL; + exfat_fsck.buffer_desc = exfat_alloc_buffer(2, + exfat_fsck.exfat->clus_size, + exfat_fsck.exfat->sect_size); + if (!exfat_fsck.buffer_desc) { + ret = -ENOMEM; goto err; } - if (exfat_mark_volume_dirty(exfat, true)) { + if ((exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE) && + exfat_mark_volume_dirty(exfat_fsck.exfat, true)) { ret = -EIO; goto err; } exfat_debug("verifying root directory...\n"); - ret = exfat_root_dir_check(exfat); + ret = exfat_root_dir_check(exfat_fsck.exfat); if (ret) { exfat_err("failed to verify root directory.\n"); goto out; } exfat_debug("verifying directory entries...\n"); - ret = exfat_filesystem_check(exfat); + ret = exfat_filesystem_check(&exfat_fsck); if (ret) goto out; @@ -1650,22 +1690,26 @@ int main(int argc, char * const argv[]) ret = -EIO; goto out; } - exfat_mark_volume_dirty(exfat, false); + if (exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE) + exfat_mark_volume_dirty(exfat_fsck.exfat, false); out: - exfat_show_info(exfat, ui.ei.dev_name); + exfat_show_info(&exfat_fsck, ui.ei.dev_name); err: if (ret && ret != -EINVAL) exit_code = FSCK_EXIT_OPERATION_ERROR; else if (ret == -EINVAL || exfat_stat.error_count != exfat_stat.fixed_count) exit_code = FSCK_EXIT_ERRORS_LEFT; - else if (exfat->dirty) + else if (exfat_fsck.dirty) exit_code = FSCK_EXIT_CORRECTED; else exit_code = FSCK_EXIT_NO_ERRORS; - free_exfat(exfat); + if (exfat_fsck.buffer_desc) + exfat_free_buffer(exfat_fsck.buffer_desc, 2); + if (exfat_fsck.exfat) + exfat_free_exfat(exfat_fsck.exfat); close(bd.dev_fd); return exit_code; } diff --git a/fsck/fsck.h b/fsck/fsck.h index 56d3b3b..c324593 100644 --- a/fsck/fsck.h +++ b/fsck/fsck.h @@ -58,9 +58,6 @@ enum fsck_ui_options { }; struct exfat { - enum fsck_ui_options options; - bool dirty:1; - bool dirty_fat:1; struct exfat_blk_dev *blk_dev; struct pbr *bs; char volume_label[VOLUME_LABEL_BUFFER_SIZE]; @@ -69,14 +66,21 @@ struct exfat { clus_t clus_count; unsigned int clus_size; unsigned int sect_size; - struct exfat_de_iter de_iter; - struct buffer_desc buffer_desc[2]; /* cluster * 2 */ char *alloc_bitmap; char *disk_bitmap; clus_t disk_bitmap_clus; unsigned int disk_bitmap_size; }; +struct exfat_fsck { + struct exfat *exfat; + struct exfat_de_iter de_iter; + struct buffer_desc *buffer_desc; /* cluster * 2 */ + enum fsck_ui_options options; + bool dirty:1; + bool dirty_fat:1; +}; + #define EXFAT_CLUSTER_SIZE(pbr) (1 << ((pbr)->bsx.sect_size_bits + \ (pbr)->bsx.sect_per_clus_bits)) #define EXFAT_SECTOR_SIZE(pbr) (1 << (pbr)->bsx.sect_size_bits) @@ -88,7 +92,7 @@ int get_next_clus(struct exfat *exfat, struct exfat_inode *node, /* de_iter.c */ int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, - struct exfat_inode *dir); + struct exfat_inode *dir, struct buffer_desc *bd); int exfat_de_iter_get(struct exfat_de_iter *iter, int ith, struct exfat_dentry **dentry); int exfat_de_iter_get_dirty(struct exfat_de_iter *iter, diff --git a/fsck/repair.c b/fsck/repair.c index c79d379..205a5f9 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -57,19 +57,19 @@ static struct exfat_repair_problem *find_problem(er_problem_code_t prcode) return NULL; } -static bool ask_repair(struct exfat *exfat, struct exfat_repair_problem *pr) +static bool ask_repair(struct exfat_fsck *fsck, struct exfat_repair_problem *pr) { bool repair = false; char answer[8]; - if (exfat->options & FSCK_OPTS_REPAIR_NO || - pr->flags & ERF_DEFAULT_NO) + if (fsck->options & FSCK_OPTS_REPAIR_NO || + pr->flags & ERF_DEFAULT_NO) repair = false; - else if (exfat->options & FSCK_OPTS_REPAIR_YES || - pr->flags & ERF_DEFAULT_YES) + else if (fsck->options & FSCK_OPTS_REPAIR_YES || + pr->flags & ERF_DEFAULT_YES) repair = true; else { - if (exfat->options & FSCK_OPTS_REPAIR_ASK) { + if (fsck->options & FSCK_OPTS_REPAIR_ASK) { do { printf(". %s (y/N)? ", prompts[pr->prompt_type]); @@ -83,8 +83,8 @@ static bool ask_repair(struct exfat *exfat, struct exfat_repair_problem *pr) return false; } } while (1); - } else if (exfat->options & FSCK_OPTS_REPAIR_AUTO && - pr->flags & ERF_PREEN_YES) + } else if (fsck->options & FSCK_OPTS_REPAIR_AUTO && + pr->flags & ERF_PREEN_YES) repair = true; } @@ -93,8 +93,8 @@ static bool ask_repair(struct exfat *exfat, struct exfat_repair_problem *pr) return repair; } -bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode, - const char *desc, ...) +bool exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode, + const char *desc, ...) { struct exfat_repair_problem *pr = NULL; va_list ap; @@ -109,11 +109,12 @@ bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode, vprintf(desc, ap); va_end(ap); - if (ask_repair(exfat, pr)) { + if (ask_repair(fsck, pr)) { if (pr->prompt_type & ERP_TRUNCATE) - exfat->dirty_fat = true; - exfat->dirty = true; + fsck->dirty_fat = true; + fsck->dirty = true; return true; - } else + } else { return false; + } } diff --git a/fsck/repair.h b/fsck/repair.h index f7286b9..e927e79 100644 --- a/fsck/repair.h +++ b/fsck/repair.h @@ -18,7 +18,7 @@ typedef unsigned int er_problem_code_t; -bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode, - const char *fmt, ...); +bool exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode, + const char *fmt, ...); #endif -- cgit v1.2.3 From d40e54a4d73b90b5f7ce43766525c2c3b8c55a20 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Wed, 24 Aug 2022 14:10:36 +0900 Subject: lib: move shared code to lib For adding dumping filesystem tools, Move shared code to lib directory. Signed-off-by: Hyunchul Lee --- fsck/Makefile.am | 2 +- fsck/de_iter.c | 316 ------------------------------------------ fsck/fsck.c | 391 ++++------------------------------------------------ fsck/fsck.h | 74 +--------- fsck/repair.c | 4 +- fsck/repair.h | 1 + include/exfat_dir.h | 36 +++++ include/exfat_fs.h | 73 ++++++++++ include/libexfat.h | 16 ++- lib/Makefile.am | 2 +- lib/exfat_dir.c | 317 ++++++++++++++++++++++++++++++++++++++++++ lib/exfat_fs.c | 296 +++++++++++++++++++++++++++++++++++++++ lib/libexfat.c | 62 +++++++++ 13 files changed, 837 insertions(+), 753 deletions(-) delete mode 100644 fsck/de_iter.c create mode 100644 include/exfat_dir.h create mode 100644 include/exfat_fs.h create mode 100644 lib/exfat_dir.c create mode 100644 lib/exfat_fs.c diff --git a/fsck/Makefile.am b/fsck/Makefile.am index 604cac2..519b13a 100644 --- a/fsck/Makefile.am +++ b/fsck/Makefile.am @@ -3,4 +3,4 @@ fsck_exfat_LDADD = $(top_builddir)/lib/libexfat.a sbin_PROGRAMS = fsck.exfat -fsck_exfat_SOURCES = fsck.c repair.c fsck.h de_iter.c repair.h +fsck_exfat_SOURCES = fsck.c repair.c fsck.h repair.h diff --git a/fsck/de_iter.c b/fsck/de_iter.c deleted file mode 100644 index a9fcdd9..0000000 --- a/fsck/de_iter.c +++ /dev/null @@ -1,316 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2020 Hyunchul Lee - */ -#include -#include -#include -#include - -#include "exfat_ondisk.h" -#include "libexfat.h" -#include "fsck.h" - -static ssize_t write_block(struct exfat_de_iter *iter, unsigned int block) -{ - off_t device_offset; - struct exfat *exfat = iter->exfat; - struct buffer_desc *desc; - unsigned int i; - - desc = &iter->buffer_desc[block & 0x01]; - device_offset = exfat_c2o(exfat, desc->p_clus) + desc->offset; - - for (i = 0; i < iter->read_size / iter->write_size; i++) { - if (desc->dirty[i]) { - if (exfat_write(exfat->blk_dev->dev_fd, - desc->buffer + i * iter->write_size, - iter->write_size, - device_offset + i * iter->write_size) - != (ssize_t)iter->write_size) - return -EIO; - desc->dirty[i] = 0; - } - } - return 0; -} - -static int read_ahead_first_blocks(struct exfat_de_iter *iter) -{ -#ifdef POSIX_FADV_WILLNEED - struct exfat *exfat = iter->exfat; - clus_t clus_count; - unsigned int size; - - clus_count = iter->parent->size / exfat->clus_size; - - if (clus_count > 1) { - iter->ra_begin_offset = 0; - iter->ra_next_clus = 1; - size = exfat->clus_size; - } else { - iter->ra_begin_offset = 0; - iter->ra_next_clus = 0; - size = iter->ra_partial_size; - } - return posix_fadvise(exfat->blk_dev->dev_fd, - exfat_c2o(exfat, iter->parent->first_clus), size, - POSIX_FADV_WILLNEED); -#else - return -ENOTSUP; -#endif -} - -/** - * read the next fragment in advance, and assume the fragment - * which covers @clus is already read. - */ -static int read_ahead_next_blocks(struct exfat_de_iter *iter, - clus_t clus, unsigned int offset, clus_t p_clus) -{ -#ifdef POSIX_FADV_WILLNEED - struct exfat *exfat = iter->exfat; - off_t device_offset; - clus_t clus_count, ra_clus, ra_p_clus; - unsigned int size; - int ret = 0; - - clus_count = iter->parent->size / exfat->clus_size; - if (clus + 1 < clus_count) { - ra_clus = clus + 1; - if (ra_clus == iter->ra_next_clus && - offset >= iter->ra_begin_offset) { - ret = get_next_clus(exfat, iter->parent, - p_clus, &ra_p_clus); - if (ret) - return ret; - - if (ra_p_clus == EXFAT_EOF_CLUSTER) - return -EIO; - - device_offset = exfat_c2o(exfat, ra_p_clus); - size = ra_clus + 1 < clus_count ? - exfat->clus_size : iter->ra_partial_size; - ret = posix_fadvise(exfat->blk_dev->dev_fd, - device_offset, size, - POSIX_FADV_WILLNEED); - iter->ra_next_clus = ra_clus + 1; - iter->ra_begin_offset = 0; - } - } else { - if (offset >= iter->ra_begin_offset && - offset + iter->ra_partial_size <= - exfat->clus_size) { - device_offset = exfat_c2o(exfat, p_clus) + - offset + iter->ra_partial_size; - ret = posix_fadvise(exfat->blk_dev->dev_fd, - device_offset, iter->ra_partial_size, - POSIX_FADV_WILLNEED); - iter->ra_begin_offset = - offset + iter->ra_partial_size; - } - } - - return ret; -#else - return -ENOTSUP; -#endif -} - -static int read_ahead_next_dir_blocks(struct exfat_de_iter *iter) -{ -#ifdef POSIX_FADV_WILLNEED - struct exfat *exfat = iter->exfat; - struct list_head *current; - struct exfat_inode *next_inode; - off_t offset; - - if (list_empty(&exfat->dir_list)) - return -EINVAL; - - current = exfat->dir_list.next; - if (iter->parent == list_entry(current, struct exfat_inode, list) && - current->next != &exfat->dir_list) { - next_inode = list_entry(current->next, struct exfat_inode, - list); - offset = exfat_c2o(exfat, next_inode->first_clus); - return posix_fadvise(exfat->blk_dev->dev_fd, offset, - iter->ra_partial_size, - POSIX_FADV_WILLNEED); - } - - return 0; -#else - return -ENOTSUP; -#endif -} - -static ssize_t read_block(struct exfat_de_iter *iter, unsigned int block) -{ - struct exfat *exfat = iter->exfat; - struct buffer_desc *desc, *prev_desc; - off_t device_offset; - ssize_t ret; - - desc = &iter->buffer_desc[block & 0x01]; - if (block == 0) { - desc->p_clus = iter->parent->first_clus; - desc->offset = 0; - } - - /* if the buffer already contains dirty dentries, write it */ - if (write_block(iter, block)) - return -EIO; - - if (block > 0) { - if (block > iter->parent->size / iter->read_size) - return EOF; - - prev_desc = &iter->buffer_desc[(block-1) & 0x01]; - if (prev_desc->offset + 2 * iter->read_size <= - exfat->clus_size) { - desc->p_clus = prev_desc->p_clus; - desc->offset = prev_desc->offset + iter->read_size; - } else { - ret = get_next_clus(exfat, iter->parent, - prev_desc->p_clus, &desc->p_clus); - desc->offset = 0; - if (ret) - return ret; - else if (desc->p_clus == EXFAT_EOF_CLUSTER) - return EOF; - } - } - - device_offset = exfat_c2o(exfat, desc->p_clus) + desc->offset; - ret = exfat_read(exfat->blk_dev->dev_fd, desc->buffer, - iter->read_size, device_offset); - if (ret <= 0) - return ret; - - /* - * if a buffer is filled with dentries, read blocks ahead of time, - * otherwise read blocks of the next directory in advance. - */ - if (desc->buffer[iter->read_size - 32] != EXFAT_LAST) - read_ahead_next_blocks(iter, - (block * iter->read_size) / exfat->clus_size, - (block * iter->read_size) % exfat->clus_size, - desc->p_clus); - else - read_ahead_next_dir_blocks(iter); - return ret; -} - -int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, - struct exfat_inode *dir, struct buffer_desc *bd) -{ - iter->exfat = exfat; - iter->parent = dir; - iter->write_size = exfat->sect_size; - iter->read_size = exfat->clus_size <= 4*KB ? exfat->clus_size : 4*KB; - if (exfat->clus_size <= 32 * KB) - iter->ra_partial_size = MAX(4 * KB, exfat->clus_size / 2); - else - iter->ra_partial_size = exfat->clus_size / 4; - iter->ra_partial_size = MIN(iter->ra_partial_size, 8 * KB); - - if (!iter->buffer_desc) - iter->buffer_desc = bd; - - if (iter->parent->size == 0) - return EOF; - - read_ahead_first_blocks(iter); - if (read_block(iter, 0) != (ssize_t)iter->read_size) { - exfat_err("failed to read directory entries.\n"); - return -EIO; - } - - iter->de_file_offset = 0; - iter->next_read_offset = iter->read_size; - iter->max_skip_dentries = 0; - return 0; -} - -int exfat_de_iter_get(struct exfat_de_iter *iter, - int ith, struct exfat_dentry **dentry) -{ - off_t next_de_file_offset; - ssize_t ret; - unsigned int block; - - next_de_file_offset = iter->de_file_offset + - ith * sizeof(struct exfat_dentry); - block = (unsigned int)(next_de_file_offset / iter->read_size); - - if (next_de_file_offset + sizeof(struct exfat_dentry) > - iter->parent->size) - return EOF; - /* the dentry must be in current, or next block which will be read */ - if (block > iter->de_file_offset / iter->read_size + 1) - return -ERANGE; - - /* read next cluster if needed */ - if (next_de_file_offset >= iter->next_read_offset) { - ret = read_block(iter, block); - if (ret != (ssize_t)iter->read_size) - return ret; - iter->next_read_offset += iter->read_size; - } - - if (ith + 1 > iter->max_skip_dentries) - iter->max_skip_dentries = ith + 1; - - *dentry = (struct exfat_dentry *) - (iter->buffer_desc[block & 0x01].buffer + - next_de_file_offset % iter->read_size); - return 0; -} - -int exfat_de_iter_get_dirty(struct exfat_de_iter *iter, - int ith, struct exfat_dentry **dentry) -{ - off_t next_file_offset; - unsigned int block; - int ret, sect_idx; - - ret = exfat_de_iter_get(iter, ith, dentry); - if (!ret) { - next_file_offset = iter->de_file_offset + - ith * sizeof(struct exfat_dentry); - block = (unsigned int)(next_file_offset / iter->read_size); - sect_idx = (int)((next_file_offset % iter->read_size) / - iter->write_size); - iter->buffer_desc[block & 0x01].dirty[sect_idx] = 1; - } - - return ret; -} - -int exfat_de_iter_flush(struct exfat_de_iter *iter) -{ - if (write_block(iter, 0) || write_block(iter, 1)) - return -EIO; - return 0; -} - -/* - * @skip_dentries must be the largest @ith + 1 of exfat_de_iter_get - * since the last call of exfat_de_iter_advance - */ -int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries) -{ - if (skip_dentries != iter->max_skip_dentries) - return -EINVAL; - - iter->max_skip_dentries = 0; - iter->de_file_offset = iter->de_file_offset + - skip_dentries * sizeof(struct exfat_dentry); - return 0; -} - -off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter) -{ - return iter->de_file_offset; -} diff --git a/fsck/fsck.c b/fsck/fsck.c index 4a4d69a..02e3f2b 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -15,8 +15,10 @@ #include "exfat_ondisk.h" #include "libexfat.h" -#include "fsck.h" #include "repair.h" +#include "exfat_fs.h" +#include "exfat_dir.h" +#include "fsck.h" struct fsck_user_input { struct exfat_user_input ei; @@ -59,12 +61,6 @@ struct exfat_stat { long fixed_count; }; -struct path_resolve_ctx { - struct exfat_inode *ancestors[255]; - __le16 utf16_path[PATH_MAX + 2]; - char local_path[PATH_MAX * MB_LEN_MAX + 1]; -}; - struct exfat_fsck exfat_fsck; struct exfat_stat exfat_stat; struct path_resolve_ctx path_resolve_ctx; @@ -100,282 +96,16 @@ static void usage(char *name) #define fsck_err(parent, inode, fmt, ...) \ ({ \ - resolve_path_parent(&path_resolve_ctx, \ + exfat_resolve_path_parent(&path_resolve_ctx, \ parent, inode); \ exfat_err("ERROR: %s: " fmt, \ path_resolve_ctx.local_path, \ ##__VA_ARGS__); \ }) -static struct exfat_inode *alloc_exfat_inode(__u16 attr) -{ - struct exfat_inode *node; - int size; - - size = offsetof(struct exfat_inode, name) + NAME_BUFFER_SIZE; - node = (struct exfat_inode *)calloc(1, size); - if (!node) { - exfat_err("failed to allocate exfat_node\n"); - return NULL; - } - - node->parent = NULL; - INIT_LIST_HEAD(&node->children); - INIT_LIST_HEAD(&node->sibling); - INIT_LIST_HEAD(&node->list); - - node->last_pclus = EXFAT_EOF_CLUSTER; - node->attr = attr; - return node; -} - -static void free_exfat_inode(struct exfat_inode *node) -{ - free(node); -} - -static void inode_free_children(struct exfat_inode *dir, bool file_only) -{ - struct exfat_inode *node, *i; - - list_for_each_entry_safe(node, i, &dir->children, sibling) { - if (file_only) { - if (!(node->attr & ATTR_SUBDIR)) { - list_del(&node->sibling); - free_exfat_inode(node); - } - } else { - list_del(&node->sibling); - list_del(&node->list); - free_exfat_inode(node); - } - } -} - -static void inode_free_file_children(struct exfat_inode *dir) -{ - inode_free_children(dir, true); -} - -/* delete @child and all ancestors that does not have - * children - */ -static void inode_free_ancestors(struct exfat_inode *child) -{ - struct exfat_inode *parent; - - if (!list_empty(&child->children)) - return; - - do { - if (!(child->attr & ATTR_SUBDIR)) { - exfat_err("not directory.\n"); - return; - } - - parent = child->parent; - list_del(&child->sibling); - free_exfat_inode(child); - - child = parent; - } while (child && list_empty(&child->children)); - - return; -} - -void exfat_free_exfat(struct exfat *exfat) -{ - if (exfat) { - if (exfat->bs) - free(exfat->bs); - if (exfat->alloc_bitmap) - free(exfat->alloc_bitmap); - if (exfat->disk_bitmap) - free(exfat->disk_bitmap); - free(exfat); - } -} - -struct exfat *exfat_alloc_exfat(struct exfat_blk_dev *blk_dev, struct pbr *bs) -{ - struct exfat *exfat; - - exfat = (struct exfat *)calloc(1, sizeof(*exfat)); - if (!exfat) - return NULL; - - INIT_LIST_HEAD(&exfat->dir_list); - exfat->blk_dev = blk_dev; - exfat->bs = bs; - exfat->clus_count = le32_to_cpu(bs->bsx.clu_count); - exfat->clus_size = EXFAT_CLUSTER_SIZE(bs); - exfat->sect_size = EXFAT_SECTOR_SIZE(bs); - - /* TODO: bitmap could be very large. */ - exfat->alloc_bitmap = (char *)calloc(1, - EXFAT_BITMAP_SIZE(exfat->clus_count)); - if (!exfat->alloc_bitmap) { - exfat_err("failed to allocate bitmap\n"); - goto err; - } - - exfat->disk_bitmap = (char *)malloc( - EXFAT_BITMAP_SIZE(exfat->clus_count)); - if (!exfat->disk_bitmap) { - exfat_err("failed to allocate bitmap\n"); - goto err; - } - - return exfat; -err: - exfat_free_exfat(exfat); - return NULL; -} - -void exfat_free_buffer(struct buffer_desc *bd, int count) -{ - int i; - - for (i = 0; i < count; i++) { - if (bd[i].buffer) - free(bd[i].buffer); - if (bd[i].dirty) - free(bd[i].dirty); - } - free(bd); -} - -struct buffer_desc *exfat_alloc_buffer(int count, - unsigned int clu_size, unsigned int sect_size) -{ - struct buffer_desc *bd; - int i; - - bd = (struct buffer_desc *)calloc(sizeof(*bd), count); - if (!bd) - return NULL; - - for (i = 0; i < count; i++) { - bd[i].buffer = (char *)malloc(clu_size); - if (!bd[i].buffer) - goto err; - bd[i].dirty = (char *)calloc(clu_size / sect_size, 1); - if (!bd[i].dirty) - goto err; - } - return bd; -err: - exfat_free_buffer(bd, count); - return NULL; -} - -static void exfat_free_dir_list(struct exfat *exfat) -{ - struct exfat_inode *dir, *i; - - list_for_each_entry_safe(dir, i, &exfat->dir_list, list) { - inode_free_file_children(dir); - list_del(&dir->list); - free_exfat_inode(dir); - } -} - -/* - * get references of ancestors that include @child until the count of - * ancestors is not larger than @count and the count of characters of - * their names is not larger than @max_char_len. - * return true if root is reached. - */ -bool get_ancestors(struct exfat_inode *child, - struct exfat_inode **ancestors, int count, - int max_char_len, - int *ancestor_count) -{ - struct exfat_inode *dir; - int name_len, char_len; - int root_depth, depth, i; - - root_depth = 0; - char_len = 0; - max_char_len += 1; - - dir = child; - while (dir) { - name_len = exfat_utf16_len(dir->name, NAME_BUFFER_SIZE); - if (char_len + name_len > max_char_len) - break; - - /* include '/' */ - char_len += name_len + 1; - root_depth++; - - dir = dir->parent; - } - - depth = MIN(root_depth, count); - - for (dir = child, i = depth - 1; i >= 0; dir = dir->parent, i--) - ancestors[i] = dir; - - *ancestor_count = depth; - return dir == NULL; -} - -static int resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child) -{ - int depth, i; - int name_len; - __le16 *utf16_path; - static const __le16 utf16_slash = cpu_to_le16(0x002F); - static const __le16 utf16_null = cpu_to_le16(0x0000); - size_t in_size; - - ctx->local_path[0] = '\0'; - - get_ancestors(child, - ctx->ancestors, - sizeof(ctx->ancestors) / sizeof(ctx->ancestors[0]), - PATH_MAX, - &depth); - - utf16_path = ctx->utf16_path; - for (i = 0; i < depth; i++) { - name_len = exfat_utf16_len(ctx->ancestors[i]->name, - NAME_BUFFER_SIZE); - memcpy((char *)utf16_path, (char *)ctx->ancestors[i]->name, - name_len * 2); - utf16_path += name_len; - memcpy((char *)utf16_path, &utf16_slash, sizeof(utf16_slash)); - utf16_path++; - } - - if (depth > 0) - utf16_path--; - memcpy((char *)utf16_path, &utf16_null, sizeof(utf16_null)); - utf16_path++; - - in_size = (utf16_path - ctx->utf16_path) * sizeof(__le16); - return exfat_utf16_dec(ctx->utf16_path, in_size, - ctx->local_path, sizeof(ctx->local_path)); -} - -static int resolve_path_parent(struct path_resolve_ctx *ctx, - struct exfat_inode *parent, struct exfat_inode *child) -{ - int ret; - struct exfat_inode *old; - - old = child->parent; - child->parent = parent; - - ret = resolve_path(ctx, child); - child->parent = old; - return ret; -} - #define repair_file_ask(iter, inode, code, fmt, ...) \ ({ \ - resolve_path_parent(&path_resolve_ctx, \ + exfat_resolve_path_parent(&path_resolve_ctx, \ (iter)->parent, inode); \ exfat_repair_ask(&exfat_fsck, code, \ "ERROR: %s: " fmt, \ @@ -383,52 +113,6 @@ static int resolve_path_parent(struct path_resolve_ctx *ctx, ##__VA_ARGS__); \ }) -static inline bool heap_clus(struct exfat *exfat, clus_t clus) -{ - return clus >= EXFAT_FIRST_CLUSTER && - (clus - EXFAT_FIRST_CLUSTER) < exfat->clus_count; -} - -int get_next_clus(struct exfat *exfat, struct exfat_inode *node, - clus_t clus, clus_t *next) -{ - off_t offset; - - *next = EXFAT_EOF_CLUSTER; - - if (!heap_clus(exfat, clus)) - return -EINVAL; - - if (node->is_contiguous) { - *next = clus + 1; - return 0; - } - - offset = (off_t)le32_to_cpu(exfat->bs->bsx.fat_offset) << - exfat->bs->bsx.sect_size_bits; - offset += sizeof(clus_t) * clus; - - if (exfat_read(exfat->blk_dev->dev_fd, next, sizeof(*next), offset) - != sizeof(*next)) - return -EIO; - *next = le32_to_cpu(*next); - return 0; -} - -static int set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus) -{ - off_t offset; - - offset = le32_to_cpu(exfat->bs->bsx.fat_offset) << - exfat->bs->bsx.sect_size_bits; - offset += sizeof(clus_t) * clus; - - if (exfat_write(exfat->blk_dev->dev_fd, &next_clus, sizeof(next_clus), - offset) != sizeof(next_clus)) - return -EIO; - return 0; -} - static int check_clus_chain(struct exfat_de_iter *de_iter, struct exfat_inode *node) { @@ -447,7 +131,7 @@ static int check_clus_chain(struct exfat_de_iter *de_iter, /* the first cluster is wrong */ if ((node->size == 0 && node->first_clus != EXFAT_FREE_CLUSTER) || - (node->size > 0 && !heap_clus(exfat, node->first_clus))) { + (node->size > 0 && !exfat_heap_clus(exfat, node->first_clus))) { if (repair_file_ask(de_iter, node, ER_FILE_FIRST_CLUS, "first cluster is wrong")) goto truncate_file; @@ -495,7 +179,7 @@ static int check_clus_chain(struct exfat_de_iter *de_iter, } /* This cluster is allocated or not */ - if (get_next_clus(exfat, node, clus, &next)) + if (exfat_get_next_clus(exfat, node, clus, &next)) goto truncate_file; if (next == EXFAT_BAD_CLUSTER) { if (repair_file_ask(de_iter, node, @@ -508,7 +192,7 @@ static int check_clus_chain(struct exfat_de_iter *de_iter, return -EINVAL; } else if (!node->is_contiguous) { if (next != EXFAT_EOF_CLUSTER && - !heap_clus(exfat, next)) { + !exfat_heap_clus(exfat, next)) { if (repair_file_ask(de_iter, node, ER_FILE_INVALID_CLUS, "broken cluster chain. truncate to %" @@ -545,7 +229,7 @@ static int check_clus_chain(struct exfat_de_iter *de_iter, return 0; truncate_file: node->size = count * exfat->clus_size; - if (!heap_clus(exfat, prev)) + if (!exfat_heap_clus(exfat, prev)) node->first_clus = EXFAT_FREE_CLUSTER; exfat_de_iter_get_dirty(de_iter, 1, &stream_de); @@ -553,7 +237,7 @@ truncate_file: le64_to_cpu(stream_de->stream_valid_size)) stream_de->stream_valid_size = cpu_to_le64( count * exfat->clus_size); - if (!heap_clus(exfat, prev)) + if (!exfat_heap_clus(exfat, prev)) stream_de->stream_start_clu = EXFAT_FREE_CLUSTER; stream_de->stream_size = cpu_to_le64( count * exfat->clus_size); @@ -561,8 +245,8 @@ truncate_file: /* remaining clusters will be freed while FAT is compared with * alloc_bitmap. */ - if (!node->is_contiguous && heap_clus(exfat, prev)) { - if (set_fat(exfat, prev, EXFAT_EOF_CLUSTER)) + if (!node->is_contiguous && exfat_heap_clus(exfat, prev)) { + if (exfat_set_fat(exfat, prev, EXFAT_EOF_CLUSTER)) return -EIO; } return 1; @@ -577,7 +261,7 @@ static bool root_get_clus_count(struct exfat *exfat, struct exfat_inode *node, *clus_count = 0; do { - if (!heap_clus(exfat, clus)) { + if (!exfat_heap_clus(exfat, clus)) { exfat_err("/: bad cluster. 0x%x\n", clus); return false; } @@ -592,7 +276,7 @@ static bool root_get_clus_count(struct exfat *exfat, struct exfat_inode *node, EXFAT_BITMAP_SET(exfat->alloc_bitmap, clus - EXFAT_FIRST_CLUSTER); - if (get_next_clus(exfat, node, clus, &clus) != 0) { + if (exfat_get_next_clus(exfat, node, clus, &clus) != 0) { exfat_err("/: broken cluster chain\n"); return false; } @@ -602,21 +286,6 @@ static bool root_get_clus_count(struct exfat *exfat, struct exfat_inode *node, return true; } -static off_t exfat_s2o(struct exfat *exfat, off_t sect) -{ - return sect << exfat->bs->bsx.sect_size_bits; -} - -off_t exfat_c2o(struct exfat *exfat, unsigned int clus) -{ - if (clus < EXFAT_FIRST_CLUSTER) - return ~0L; - - return exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset) + - ((off_t)(clus - EXFAT_FIRST_CLUSTER) << - exfat->bs->bsx.sect_per_clus_bits)); -} - static int boot_region_checksum(int dev_fd, int bs_offset, unsigned int sect_size) { @@ -999,14 +668,14 @@ static int read_file_dentry_set(struct exfat_de_iter *iter, } *new_node = NULL; - node = alloc_exfat_inode(le16_to_cpu(file_de->file_attr)); + node = exfat_alloc_inode(le16_to_cpu(file_de->file_attr)); if (!node) return -ENOMEM; if (file_de->file_num_ext < 2) { exfat_err("too few secondary count. %d\n", file_de->file_num_ext); - free_exfat_inode(node); + exfat_free_inode(node); return -EINVAL; } @@ -1048,7 +717,7 @@ static int read_file_dentry_set(struct exfat_de_iter *iter, err: *skip_dentries = 1; *new_node = NULL; - free_exfat_inode(node); + exfat_free_inode(node); return ret; } @@ -1066,7 +735,7 @@ static int read_file(struct exfat_de_iter *de_iter, ret = check_inode(de_iter, node); if (ret < 0) { - free_exfat_inode(node); + exfat_free_inode(node); return -EINVAL; } @@ -1112,8 +781,8 @@ static void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, { clus_t clus; - if (!heap_clus(exfat, start_clus) || - !heap_clus(exfat, start_clus + count)) + if (!exfat_heap_clus(exfat, start_clus) || + !exfat_heap_clus(exfat, start_clus + count)) return; clus = start_clus; @@ -1131,7 +800,7 @@ static bool read_bitmap(struct exfat_de_iter *iter) exfat = iter->exfat; - if (heap_clus(exfat, exfat->disk_bitmap_clus)) + if (exfat_heap_clus(exfat, exfat->disk_bitmap_clus)) return true; if (exfat_de_iter_get(iter, 0, &dentry)) @@ -1147,7 +816,7 @@ static bool read_bitmap(struct exfat_de_iter *iter) le64_to_cpu(dentry->bitmap_size)); return false; } - if (!heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) { + if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) { exfat_err("invalid start cluster of allocate bitmap. 0x%x\n", le32_to_cpu(dentry->bitmap_start_clu)); return false; @@ -1183,7 +852,7 @@ static bool read_upcase_table(struct exfat_de_iter *iter) if (exfat_de_iter_get(iter, 0, &dentry)) return false; - if (!heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) { + if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) { exfat_err("invalid start cluster of upcase table. 0x%x\n", le32_to_cpu(dentry->upcase_start_clu)); return false; @@ -1273,7 +942,7 @@ static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir) list_add_tail(&node->sibling, &dir->children); list_add_tail(&node->list, &exfat->dir_list); } else - free_exfat_inode(node); + exfat_free_inode(node); break; case EXFAT_VOLUME: if (!read_volume_label(de_iter)) { @@ -1313,7 +982,7 @@ out: exfat_de_iter_flush(de_iter); return 0; err: - inode_free_children(dir, false); + exfat_free_children(dir, false); INIT_LIST_HEAD(&dir->children); exfat_de_iter_flush(de_iter); return ret; @@ -1476,15 +1145,15 @@ static int exfat_filesystem_check(struct exfat_fsck *fsck) dir_errors = read_children(fsck, dir); if (dir_errors) { - resolve_path(&path_resolve_ctx, dir); + exfat_resolve_path(&path_resolve_ctx, dir); exfat_debug("failed to check dentries: %s\n", path_resolve_ctx.local_path); ret = dir_errors; } list_del(&dir->list); - inode_free_file_children(dir); - inode_free_ancestors(dir); + exfat_free_file_children(dir); + exfat_free_ancestors(dir); } out: exfat_free_dir_list(exfat); @@ -1499,7 +1168,7 @@ static int exfat_root_dir_check(struct exfat *exfat) struct exfat_inode *root; clus_t clus_count; - root = alloc_exfat_inode(ATTR_SUBDIR); + root = exfat_alloc_inode(ATTR_SUBDIR); if (!root) { exfat_err("failed to allocate memory\n"); return -ENOMEM; @@ -1508,7 +1177,7 @@ static int exfat_root_dir_check(struct exfat *exfat) root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster); if (!root_get_clus_count(exfat, root, &clus_count)) { exfat_err("failed to follow the cluster chain of root\n"); - free_exfat_inode(root); + exfat_free_inode(root); return -EINVAL; } root->size = clus_count * exfat->clus_size; diff --git a/fsck/fsck.h b/fsck/fsck.h index c324593..34a695a 100644 --- a/fsck/fsck.h +++ b/fsck/fsck.h @@ -7,46 +7,6 @@ #include "list.h" -typedef __u32 clus_t; - -struct exfat_inode { - struct exfat_inode *parent; - struct list_head children; - struct list_head sibling; - struct list_head list; - clus_t first_clus; - clus_t last_lclus; - clus_t last_pclus; - __u16 attr; - uint64_t size; - bool is_contiguous; - __le16 name[0]; /* only for directory */ -}; - -#define EXFAT_NAME_MAX 255 -#define NAME_BUFFER_SIZE ((EXFAT_NAME_MAX+1)*2) - -struct buffer_desc { - clus_t p_clus; - unsigned int offset; - char *buffer; - char *dirty; -}; - -struct exfat_de_iter { - struct exfat *exfat; - struct exfat_inode *parent; - struct buffer_desc *buffer_desc; /* cluster * 2 */ - clus_t ra_next_clus; - unsigned int ra_begin_offset; - unsigned int ra_partial_size; - unsigned int read_size; /* cluster size */ - unsigned int write_size; /* sector size */ - off_t de_file_offset; - off_t next_read_offset; - int max_skip_dentries; -}; - enum fsck_ui_options { FSCK_OPTS_REPAIR_ASK = 0x01, FSCK_OPTS_REPAIR_YES = 0x02, @@ -57,20 +17,8 @@ enum fsck_ui_options { FSCK_OPTS_IGNORE_BAD_FS_NAME = 0x10, }; -struct exfat { - struct exfat_blk_dev *blk_dev; - struct pbr *bs; - char volume_label[VOLUME_LABEL_BUFFER_SIZE]; - struct exfat_inode *root; - struct list_head dir_list; - clus_t clus_count; - unsigned int clus_size; - unsigned int sect_size; - char *alloc_bitmap; - char *disk_bitmap; - clus_t disk_bitmap_clus; - unsigned int disk_bitmap_size; -}; +struct exfat; +struct exfat_inode; struct exfat_fsck { struct exfat *exfat; @@ -81,24 +29,6 @@ struct exfat_fsck { bool dirty_fat:1; }; -#define EXFAT_CLUSTER_SIZE(pbr) (1 << ((pbr)->bsx.sect_size_bits + \ - (pbr)->bsx.sect_per_clus_bits)) -#define EXFAT_SECTOR_SIZE(pbr) (1 << (pbr)->bsx.sect_size_bits) - -/* fsck.c */ off_t exfat_c2o(struct exfat *exfat, unsigned int clus); -int get_next_clus(struct exfat *exfat, struct exfat_inode *node, - clus_t clus, clus_t *next); - -/* de_iter.c */ -int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, - struct exfat_inode *dir, struct buffer_desc *bd); -int exfat_de_iter_get(struct exfat_de_iter *iter, - int ith, struct exfat_dentry **dentry); -int exfat_de_iter_get_dirty(struct exfat_de_iter *iter, - int ith, struct exfat_dentry **dentry); -int exfat_de_iter_flush(struct exfat_de_iter *iter); -int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries); -off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter); #endif diff --git a/fsck/repair.c b/fsck/repair.c index 205a5f9..941c86b 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -8,8 +8,10 @@ #include "exfat_ondisk.h" #include "libexfat.h" -#include "fsck.h" #include "repair.h" +#include "exfat_fs.h" +#include "exfat_dir.h" +#include "fsck.h" struct exfat_repair_problem { er_problem_code_t prcode; diff --git a/fsck/repair.h b/fsck/repair.h index e927e79..fbdc607 100644 --- a/fsck/repair.h +++ b/fsck/repair.h @@ -17,6 +17,7 @@ #define ER_FILE_ZERO_NOFAT 0x00002007 typedef unsigned int er_problem_code_t; +struct exfat_fsck; bool exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode, const char *fmt, ...); diff --git a/include/exfat_dir.h b/include/exfat_dir.h new file mode 100644 index 0000000..ce010d2 --- /dev/null +++ b/include/exfat_dir.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2022 Hyunchul Lee + */ + +#ifndef _DIR_H_ +#define _DIR_H_ + +struct exfat; +struct exfat_inode; +struct buffer_desc; + +struct exfat_de_iter { + struct exfat *exfat; + struct exfat_inode *parent; + struct buffer_desc *buffer_desc; /* cluster * 2 */ + __u32 ra_next_clus; + unsigned int ra_begin_offset; + unsigned int ra_partial_size; + unsigned int read_size; /* cluster size */ + unsigned int write_size; /* sector size */ + off_t de_file_offset; + off_t next_read_offset; + int max_skip_dentries; +}; + +int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, + struct exfat_inode *dir, struct buffer_desc *bd); +int exfat_de_iter_get(struct exfat_de_iter *iter, + int ith, struct exfat_dentry **dentry); +int exfat_de_iter_get_dirty(struct exfat_de_iter *iter, + int ith, struct exfat_dentry **dentry); +int exfat_de_iter_flush(struct exfat_de_iter *iter); +int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries); + +#endif diff --git a/include/exfat_fs.h b/include/exfat_fs.h new file mode 100644 index 0000000..b31c2b6 --- /dev/null +++ b/include/exfat_fs.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021 Hyunchul Lee + */ +#ifndef _EXFAT_FS_H_ +#define _EXFAT_FS_H_ + +#include "list.h" + +struct exfat_inode { + struct exfat_inode *parent; + struct list_head children; + struct list_head sibling; + struct list_head list; + clus_t first_clus; + clus_t last_lclus; + clus_t last_pclus; + __u16 attr; + uint64_t size; + bool is_contiguous; + __le16 name[0]; /* only for directory */ +}; + +#define EXFAT_NAME_MAX 255 +#define NAME_BUFFER_SIZE ((EXFAT_NAME_MAX + 1) * 2) + +struct exfat { + struct exfat_blk_dev *blk_dev; + struct pbr *bs; + char volume_label[VOLUME_LABEL_BUFFER_SIZE]; + struct exfat_inode *root; + struct list_head dir_list; + clus_t clus_count; + unsigned int clus_size; + unsigned int sect_size; + char *alloc_bitmap; + char *disk_bitmap; + clus_t disk_bitmap_clus; + unsigned int disk_bitmap_size; +}; + +struct path_resolve_ctx { + struct exfat_inode *ancestors[255]; + __le16 utf16_path[PATH_MAX + 2]; + char local_path[PATH_MAX * MB_LEN_MAX + 1]; +}; + +struct buffer_desc { + __u32 p_clus; + unsigned int offset; + char *buffer; + char *dirty; +}; + +struct exfat *exfat_alloc_exfat(struct exfat_blk_dev *blk_dev, struct pbr *bs); +void exfat_free_exfat(struct exfat *exfat); + +struct exfat_inode *exfat_alloc_inode(__u16 attr); +void exfat_free_inode(struct exfat_inode *node); + +void exfat_free_children(struct exfat_inode *dir, bool file_only); +void exfat_free_file_children(struct exfat_inode *dir); +void exfat_free_ancestors(struct exfat_inode *child); +void exfat_free_dir_list(struct exfat *exfat); + +int exfat_resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child); +int exfat_resolve_path_parent(struct path_resolve_ctx *ctx, + struct exfat_inode *parent, struct exfat_inode *child); + +struct buffer_desc *exfat_alloc_buffer(int count, + unsigned int clu_size, unsigned int sect_size); +void exfat_free_buffer(struct buffer_desc *bd, int count); +#endif diff --git a/include/libexfat.h b/include/libexfat.h index 53a82a1..fbedfec 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -10,6 +10,8 @@ #include #include +typedef __u32 clus_t; + #define KB (1024) #define MB (1024*1024) #define GB (1024UL*1024UL*1024UL) @@ -45,6 +47,10 @@ #define EXFAT_MAX_SECTOR_SIZE 4096 +#define EXFAT_CLUSTER_SIZE(pbr) (1 << ((pbr)->bsx.sect_size_bits + \ + (pbr)->bsx.sect_per_clus_bits)) +#define EXFAT_SECTOR_SIZE(pbr) (1 << (pbr)->bsx.sect_size_bits) + enum { BOOT_SEC_IDX = 0, EXBOOT_SEC_IDX, @@ -79,6 +85,9 @@ struct exfat_user_input { unsigned int volume_serial; }; +struct exfat; +struct exfat_inode; + void show_version(void); void exfat_set_bit(struct exfat_blk_dev *bd, char *bitmap, @@ -114,7 +123,12 @@ int exfat_set_volume_serial(struct exfat_blk_dev *bd, struct exfat_user_input *ui); unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd, unsigned int clu_off, unsigned int clu); - +int exfat_get_next_clus(struct exfat *exfat, struct exfat_inode *node, + clus_t clus, clus_t *next); +int exfat_set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus); +off_t exfat_s2o(struct exfat *exfat, off_t sect); +off_t exfat_c2o(struct exfat *exfat, unsigned int clus); +bool exfat_heap_clus(struct exfat *exfat, clus_t clus); /* * Exfat Print diff --git a/lib/Makefile.am b/lib/Makefile.am index 5ea12db..d732cfd 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,4 +1,4 @@ AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common noinst_LIBRARIES = libexfat.a -libexfat_a_SOURCES = libexfat.c +libexfat_a_SOURCES = libexfat.c exfat_fs.c exfat_dir.c diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c new file mode 100644 index 0000000..6257842 --- /dev/null +++ b/lib/exfat_dir.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Hyunchul Lee + */ +#include +#include +#include +#include + +#include "exfat_ondisk.h" +#include "libexfat.h" +#include "exfat_fs.h" +#include "exfat_dir.h" + +static ssize_t write_block(struct exfat_de_iter *iter, unsigned int block) +{ + off_t device_offset; + struct exfat *exfat = iter->exfat; + struct buffer_desc *desc; + unsigned int i; + + desc = &iter->buffer_desc[block & 0x01]; + device_offset = exfat_c2o(exfat, desc->p_clus) + desc->offset; + + for (i = 0; i < iter->read_size / iter->write_size; i++) { + if (desc->dirty[i]) { + if (exfat_write(exfat->blk_dev->dev_fd, + desc->buffer + i * iter->write_size, + iter->write_size, + device_offset + i * iter->write_size) + != (ssize_t)iter->write_size) + return -EIO; + desc->dirty[i] = 0; + } + } + return 0; +} + +static int read_ahead_first_blocks(struct exfat_de_iter *iter) +{ +#ifdef POSIX_FADV_WILLNEED + struct exfat *exfat = iter->exfat; + clus_t clus_count; + unsigned int size; + + clus_count = iter->parent->size / exfat->clus_size; + + if (clus_count > 1) { + iter->ra_begin_offset = 0; + iter->ra_next_clus = 1; + size = exfat->clus_size; + } else { + iter->ra_begin_offset = 0; + iter->ra_next_clus = 0; + size = iter->ra_partial_size; + } + return posix_fadvise(exfat->blk_dev->dev_fd, + exfat_c2o(exfat, iter->parent->first_clus), size, + POSIX_FADV_WILLNEED); +#else + return -ENOTSUP; +#endif +} + +/** + * read the next fragment in advance, and assume the fragment + * which covers @clus is already read. + */ +static int read_ahead_next_blocks(struct exfat_de_iter *iter, + clus_t clus, unsigned int offset, clus_t p_clus) +{ +#ifdef POSIX_FADV_WILLNEED + struct exfat *exfat = iter->exfat; + off_t device_offset; + clus_t clus_count, ra_clus, ra_p_clus; + unsigned int size; + int ret = 0; + + clus_count = iter->parent->size / exfat->clus_size; + if (clus + 1 < clus_count) { + ra_clus = clus + 1; + if (ra_clus == iter->ra_next_clus && + offset >= iter->ra_begin_offset) { + ret = exfat_get_next_clus(exfat, iter->parent, + p_clus, &ra_p_clus); + if (ret) + return ret; + + if (ra_p_clus == EXFAT_EOF_CLUSTER) + return -EIO; + + device_offset = exfat_c2o(exfat, ra_p_clus); + size = ra_clus + 1 < clus_count ? + exfat->clus_size : iter->ra_partial_size; + ret = posix_fadvise(exfat->blk_dev->dev_fd, + device_offset, size, + POSIX_FADV_WILLNEED); + iter->ra_next_clus = ra_clus + 1; + iter->ra_begin_offset = 0; + } + } else { + if (offset >= iter->ra_begin_offset && + offset + iter->ra_partial_size <= + exfat->clus_size) { + device_offset = exfat_c2o(exfat, p_clus) + + offset + iter->ra_partial_size; + ret = posix_fadvise(exfat->blk_dev->dev_fd, + device_offset, iter->ra_partial_size, + POSIX_FADV_WILLNEED); + iter->ra_begin_offset = + offset + iter->ra_partial_size; + } + } + + return ret; +#else + return -ENOTSUP; +#endif +} + +static int read_ahead_next_dir_blocks(struct exfat_de_iter *iter) +{ +#ifdef POSIX_FADV_WILLNEED + struct exfat *exfat = iter->exfat; + struct list_head *current; + struct exfat_inode *next_inode; + off_t offset; + + if (list_empty(&exfat->dir_list)) + return -EINVAL; + + current = exfat->dir_list.next; + if (iter->parent == list_entry(current, struct exfat_inode, list) && + current->next != &exfat->dir_list) { + next_inode = list_entry(current->next, struct exfat_inode, + list); + offset = exfat_c2o(exfat, next_inode->first_clus); + return posix_fadvise(exfat->blk_dev->dev_fd, offset, + iter->ra_partial_size, + POSIX_FADV_WILLNEED); + } + + return 0; +#else + return -ENOTSUP; +#endif +} + +static ssize_t read_block(struct exfat_de_iter *iter, unsigned int block) +{ + struct exfat *exfat = iter->exfat; + struct buffer_desc *desc, *prev_desc; + off_t device_offset; + ssize_t ret; + + desc = &iter->buffer_desc[block & 0x01]; + if (block == 0) { + desc->p_clus = iter->parent->first_clus; + desc->offset = 0; + } + + /* if the buffer already contains dirty dentries, write it */ + if (write_block(iter, block)) + return -EIO; + + if (block > 0) { + if (block > iter->parent->size / iter->read_size) + return EOF; + + prev_desc = &iter->buffer_desc[(block-1) & 0x01]; + if (prev_desc->offset + 2 * iter->read_size <= + exfat->clus_size) { + desc->p_clus = prev_desc->p_clus; + desc->offset = prev_desc->offset + iter->read_size; + } else { + ret = exfat_get_next_clus(exfat, iter->parent, + prev_desc->p_clus, &desc->p_clus); + desc->offset = 0; + if (ret) + return ret; + else if (desc->p_clus == EXFAT_EOF_CLUSTER) + return EOF; + } + } + + device_offset = exfat_c2o(exfat, desc->p_clus) + desc->offset; + ret = exfat_read(exfat->blk_dev->dev_fd, desc->buffer, + iter->read_size, device_offset); + if (ret <= 0) + return ret; + + /* + * if a buffer is filled with dentries, read blocks ahead of time, + * otherwise read blocks of the next directory in advance. + */ + if (desc->buffer[iter->read_size - 32] != EXFAT_LAST) + read_ahead_next_blocks(iter, + (block * iter->read_size) / exfat->clus_size, + (block * iter->read_size) % exfat->clus_size, + desc->p_clus); + else + read_ahead_next_dir_blocks(iter); + return ret; +} + +int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, + struct exfat_inode *dir, struct buffer_desc *bd) +{ + iter->exfat = exfat; + iter->parent = dir; + iter->write_size = exfat->sect_size; + iter->read_size = exfat->clus_size <= 4*KB ? exfat->clus_size : 4*KB; + if (exfat->clus_size <= 32 * KB) + iter->ra_partial_size = MAX(4 * KB, exfat->clus_size / 2); + else + iter->ra_partial_size = exfat->clus_size / 4; + iter->ra_partial_size = MIN(iter->ra_partial_size, 8 * KB); + + if (!iter->buffer_desc) + iter->buffer_desc = bd; + + if (iter->parent->size == 0) + return EOF; + + read_ahead_first_blocks(iter); + if (read_block(iter, 0) != (ssize_t)iter->read_size) { + exfat_err("failed to read directory entries.\n"); + return -EIO; + } + + iter->de_file_offset = 0; + iter->next_read_offset = iter->read_size; + iter->max_skip_dentries = 0; + return 0; +} + +int exfat_de_iter_get(struct exfat_de_iter *iter, + int ith, struct exfat_dentry **dentry) +{ + off_t next_de_file_offset; + ssize_t ret; + unsigned int block; + + next_de_file_offset = iter->de_file_offset + + ith * sizeof(struct exfat_dentry); + block = (unsigned int)(next_de_file_offset / iter->read_size); + + if (next_de_file_offset + sizeof(struct exfat_dentry) > + iter->parent->size) + return EOF; + /* the dentry must be in current, or next block which will be read */ + if (block > iter->de_file_offset / iter->read_size + 1) + return -ERANGE; + + /* read next cluster if needed */ + if (next_de_file_offset >= iter->next_read_offset) { + ret = read_block(iter, block); + if (ret != (ssize_t)iter->read_size) + return ret; + iter->next_read_offset += iter->read_size; + } + + if (ith + 1 > iter->max_skip_dentries) + iter->max_skip_dentries = ith + 1; + + *dentry = (struct exfat_dentry *) + (iter->buffer_desc[block & 0x01].buffer + + next_de_file_offset % iter->read_size); + return 0; +} + +int exfat_de_iter_get_dirty(struct exfat_de_iter *iter, + int ith, struct exfat_dentry **dentry) +{ + off_t next_file_offset; + unsigned int block; + int ret, sect_idx; + + ret = exfat_de_iter_get(iter, ith, dentry); + if (!ret) { + next_file_offset = iter->de_file_offset + + ith * sizeof(struct exfat_dentry); + block = (unsigned int)(next_file_offset / iter->read_size); + sect_idx = (int)((next_file_offset % iter->read_size) / + iter->write_size); + iter->buffer_desc[block & 0x01].dirty[sect_idx] = 1; + } + + return ret; +} + +int exfat_de_iter_flush(struct exfat_de_iter *iter) +{ + if (write_block(iter, 0) || write_block(iter, 1)) + return -EIO; + return 0; +} + +/* + * @skip_dentries must be the largest @ith + 1 of exfat_de_iter_get + * since the last call of exfat_de_iter_advance + */ +int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries) +{ + if (skip_dentries != iter->max_skip_dentries) + return -EINVAL; + + iter->max_skip_dentries = 0; + iter->de_file_offset = iter->de_file_offset + + skip_dentries * sizeof(struct exfat_dentry); + return 0; +} + +off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter) +{ + return iter->de_file_offset; +} diff --git a/lib/exfat_fs.c b/lib/exfat_fs.c new file mode 100644 index 0000000..84ff72d --- /dev/null +++ b/lib/exfat_fs.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022 Hyunchul Lee + */ + +#include +#include +#include +#include + +#include "exfat_ondisk.h" +#include "libexfat.h" + +#include "exfat_fs.h" +#include "exfat_dir.h" + +#ifdef WORDS_BIGENDIAN +typedef __u8 bitmap_t; +#else +typedef __u32 bitmap_t; +#endif + +#define BITS_PER (sizeof(bitmap_t) * 8) +#define BIT_MASK(__c) (1 << ((__c) % BITS_PER)) +#define BIT_ENTRY(__c) ((__c) / BITS_PER) + +#define EXFAT_BITMAP_SIZE(__c_count) \ + (DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t)) +#define EXFAT_BITMAP_GET(__bmap, __c) \ + (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] & BIT_MASK(__c)) +#define EXFAT_BITMAP_SET(__bmap, __c) \ + (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] |= \ + BIT_MASK(__c)) + +struct exfat_inode *exfat_alloc_inode(__u16 attr) +{ + struct exfat_inode *node; + int size; + + size = offsetof(struct exfat_inode, name) + NAME_BUFFER_SIZE; + node = (struct exfat_inode *)calloc(1, size); + if (!node) { + exfat_err("failed to allocate exfat_node\n"); + return NULL; + } + + node->parent = NULL; + INIT_LIST_HEAD(&node->children); + INIT_LIST_HEAD(&node->sibling); + INIT_LIST_HEAD(&node->list); + + node->last_pclus = EXFAT_EOF_CLUSTER; + node->attr = attr; + return node; +} + +void exfat_free_inode(struct exfat_inode *node) +{ + free(node); +} + +void exfat_free_children(struct exfat_inode *dir, bool file_only) +{ + struct exfat_inode *node, *i; + + list_for_each_entry_safe(node, i, &dir->children, sibling) { + if (file_only) { + if (!(node->attr & ATTR_SUBDIR)) { + list_del(&node->sibling); + exfat_free_inode(node); + } + } else { + list_del(&node->sibling); + list_del(&node->list); + exfat_free_inode(node); + } + } +} + +void exfat_free_file_children(struct exfat_inode *dir) +{ + exfat_free_children(dir, true); +} + +/* delete @child and all ancestors that does not have + * children + */ +void exfat_free_ancestors(struct exfat_inode *child) +{ + struct exfat_inode *parent; + + if (!list_empty(&child->children)) + return; + + do { + if (!(child->attr & ATTR_SUBDIR)) { + exfat_err("not directory.\n"); + return; + } + + parent = child->parent; + list_del(&child->sibling); + exfat_free_inode(child); + + child = parent; + } while (child && list_empty(&child->children)); +} + +void exfat_free_dir_list(struct exfat *exfat) +{ + struct exfat_inode *dir, *i; + + list_for_each_entry_safe(dir, i, &exfat->dir_list, list) { + exfat_free_file_children(dir); + list_del(&dir->list); + exfat_free_inode(dir); + } +} + +void exfat_free_exfat(struct exfat *exfat) +{ + if (exfat) { + if (exfat->bs) + free(exfat->bs); + if (exfat->alloc_bitmap) + free(exfat->alloc_bitmap); + if (exfat->disk_bitmap) + free(exfat->disk_bitmap); + free(exfat); + } +} + +struct exfat *exfat_alloc_exfat(struct exfat_blk_dev *blk_dev, struct pbr *bs) +{ + struct exfat *exfat; + + exfat = (struct exfat *)calloc(1, sizeof(*exfat)); + if (!exfat) + return NULL; + + INIT_LIST_HEAD(&exfat->dir_list); + exfat->blk_dev = blk_dev; + exfat->bs = bs; + exfat->clus_count = le32_to_cpu(bs->bsx.clu_count); + exfat->clus_size = EXFAT_CLUSTER_SIZE(bs); + exfat->sect_size = EXFAT_SECTOR_SIZE(bs); + + /* TODO: bitmap could be very large. */ + exfat->alloc_bitmap = (char *)calloc(1, + EXFAT_BITMAP_SIZE(exfat->clus_count)); + if (!exfat->alloc_bitmap) { + exfat_err("failed to allocate bitmap\n"); + goto err; + } + + exfat->disk_bitmap = (char *)malloc(EXFAT_BITMAP_SIZE(exfat->clus_count)); + if (!exfat->disk_bitmap) { + exfat_err("failed to allocate bitmap\n"); + goto err; + } + + return exfat; +err: + exfat_free_exfat(exfat); + return NULL; +} + +struct buffer_desc *exfat_alloc_buffer(int count, + unsigned int clu_size, unsigned int sect_size) +{ + struct buffer_desc *bd; + int i; + + bd = (struct buffer_desc *)calloc(sizeof(*bd), count); + if (!bd) + return NULL; + + for (i = 0; i < count; i++) { + bd[i].buffer = (char *)malloc(clu_size); + if (!bd[i].buffer) + goto err; + bd[i].dirty = (char *)calloc(clu_size / sect_size, 1); + if (!bd[i].dirty) + goto err; + } + return bd; +err: + exfat_free_buffer(bd, count); + return NULL; +} + +void exfat_free_buffer(struct buffer_desc *bd, int count) +{ + int i; + + for (i = 0; i < count; i++) { + if (bd[i].buffer) + free(bd[i].buffer); + if (bd[i].dirty) + free(bd[i].dirty); + } + free(bd); +} + +/* + * get references of ancestors that include @child until the count of + * ancesters is not larger than @count and the count of characters of + * their names is not larger than @max_char_len. + * return true if root is reached. + */ +static bool get_ancestors(struct exfat_inode *child, + struct exfat_inode **ancestors, int count, + int max_char_len, + int *ancestor_count) +{ + struct exfat_inode *dir; + int name_len, char_len; + int root_depth, depth, i; + + root_depth = 0; + char_len = 0; + max_char_len += 1; + + dir = child; + while (dir) { + name_len = exfat_utf16_len(dir->name, NAME_BUFFER_SIZE); + if (char_len + name_len > max_char_len) + break; + + /* include '/' */ + char_len += name_len + 1; + root_depth++; + + dir = dir->parent; + } + + depth = MIN(root_depth, count); + + for (dir = child, i = depth - 1; i >= 0; dir = dir->parent, i--) + ancestors[i] = dir; + + *ancestor_count = depth; + return !dir; +} + +int exfat_resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child) +{ + int depth, i; + int name_len; + __le16 *utf16_path; + static const __le16 utf16_slash = cpu_to_le16(0x002F); + static const __le16 utf16_null = cpu_to_le16(0x0000); + size_t in_size; + + ctx->local_path[0] = '\0'; + + get_ancestors(child, + ctx->ancestors, + sizeof(ctx->ancestors) / sizeof(ctx->ancestors[0]), + PATH_MAX, + &depth); + + utf16_path = ctx->utf16_path; + for (i = 0; i < depth; i++) { + name_len = exfat_utf16_len(ctx->ancestors[i]->name, + NAME_BUFFER_SIZE); + memcpy((char *)utf16_path, (char *)ctx->ancestors[i]->name, + name_len * 2); + utf16_path += name_len; + memcpy((char *)utf16_path, &utf16_slash, sizeof(utf16_slash)); + utf16_path++; + } + + if (depth > 0) + utf16_path--; + memcpy((char *)utf16_path, &utf16_null, sizeof(utf16_null)); + utf16_path++; + + in_size = (utf16_path - ctx->utf16_path) * sizeof(__le16); + return exfat_utf16_dec(ctx->utf16_path, in_size, + ctx->local_path, sizeof(ctx->local_path)); +} + +int exfat_resolve_path_parent(struct path_resolve_ctx *ctx, + struct exfat_inode *parent, struct exfat_inode *child) +{ + int ret; + struct exfat_inode *old; + + old = child->parent; + child->parent = parent; + + ret = exfat_resolve_path(ctx, child); + child->parent = old; + return ret; +} diff --git a/lib/libexfat.c b/lib/libexfat.c index ee48d3a..b6084ee 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -19,6 +19,7 @@ #include "exfat_ondisk.h" #include "libexfat.h" #include "version.h" +#include "exfat_fs.h" #define BITS_PER_LONG (sizeof(long) * CHAR_BIT) @@ -681,3 +682,64 @@ unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd, return clu_off_sectnr * bd->sector_size + (clu - EXFAT_RESERVED_CLUSTERS) * bd->cluster_size; } + +int exfat_get_next_clus(struct exfat *exfat, struct exfat_inode *node, + clus_t clus, clus_t *next) +{ + off_t offset; + + *next = EXFAT_EOF_CLUSTER; + + if (!exfat_heap_clus(exfat, clus)) + return -EINVAL; + + if (node->is_contiguous) { + *next = clus + 1; + return 0; + } + + offset = (off_t)le32_to_cpu(exfat->bs->bsx.fat_offset) << + exfat->bs->bsx.sect_size_bits; + offset += sizeof(clus_t) * clus; + + if (exfat_read(exfat->blk_dev->dev_fd, next, sizeof(*next), offset) + != sizeof(*next)) + return -EIO; + *next = le32_to_cpu(*next); + return 0; +} + +int exfat_set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus) +{ + off_t offset; + + offset = le32_to_cpu(exfat->bs->bsx.fat_offset) << + exfat->bs->bsx.sect_size_bits; + offset += sizeof(clus_t) * clus; + + if (exfat_write(exfat->blk_dev->dev_fd, &next_clus, sizeof(next_clus), + offset) != sizeof(next_clus)) + return -EIO; + return 0; +} + +off_t exfat_s2o(struct exfat *exfat, off_t sect) +{ + return sect << exfat->bs->bsx.sect_size_bits; +} + +off_t exfat_c2o(struct exfat *exfat, unsigned int clus) +{ + if (clus < EXFAT_FIRST_CLUSTER) + return ~0L; + + return exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset) + + ((off_t)(clus - EXFAT_FIRST_CLUSTER) << + exfat->bs->bsx.sect_per_clus_bits)); +} + +bool exfat_heap_clus(struct exfat *exfat, clus_t clus) +{ + return clus >= EXFAT_FIRST_CLUSTER && + (clus - EXFAT_FIRST_CLUSTER) < exfat->clus_count; +} -- cgit v1.2.3 From cba94136deb98eae024587220de1fa7ff32ef311 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Wed, 24 Aug 2022 16:18:49 +0900 Subject: lib: move bitmap functions to lib Move fsck's bitmap functions to library to avoid duplication. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 35 ----------------------------- include/libexfat.h | 24 ++++++++++++++++---- lib/exfat_fs.c | 18 --------------- lib/libexfat.c | 64 ++++++++++-------------------------------------------- mkfs/mkfs.c | 4 ++-- 5 files changed, 34 insertions(+), 111 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 02e3f2b..f9f0dca 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -27,24 +27,6 @@ struct fsck_user_input { #define EXFAT_MAX_UPCASE_CHARS 0x10000 -#ifdef WORDS_BIGENDIAN -typedef __u8 bitmap_t; -#else -typedef __u32 bitmap_t; -#endif - -#define BITS_PER (sizeof(bitmap_t) * 8) -#define BIT_MASK(__c) (1 << ((__c) % BITS_PER)) -#define BIT_ENTRY(__c) ((__c) / BITS_PER) - -#define EXFAT_BITMAP_SIZE(__c_count) \ - (DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t)) -#define EXFAT_BITMAP_GET(__bmap, __c) \ - (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] & BIT_MASK(__c)) -#define EXFAT_BITMAP_SET(__bmap, __c) \ - (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] |= \ - BIT_MASK(__c)) - #define FSCK_EXIT_NO_ERRORS 0x00 #define FSCK_EXIT_CORRECTED 0x01 #define FSCK_EXIT_NEED_REBOOT 0x02 @@ -776,23 +758,6 @@ static bool read_volume_label(struct exfat_de_iter *iter) return true; } -static void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, - clus_t start_clus, clus_t count) -{ - clus_t clus; - - if (!exfat_heap_clus(exfat, start_clus) || - !exfat_heap_clus(exfat, start_clus + count)) - return; - - clus = start_clus; - while (clus < start_clus + count) { - EXFAT_BITMAP_SET(bitmap, - clus - EXFAT_FIRST_CLUSTER); - clus++; - } -} - static bool read_bitmap(struct exfat_de_iter *iter) { struct exfat_dentry *dentry; diff --git a/include/libexfat.h b/include/libexfat.h index fbedfec..a0c89f6 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -88,12 +88,28 @@ struct exfat_user_input { struct exfat; struct exfat_inode; +#ifdef WORDS_BIGENDIAN +typedef __u8 bitmap_t; +#else +typedef __u32 bitmap_t; +#endif + +#define BITS_PER (sizeof(bitmap_t) * 8) +#define BIT_MASK(__c) (1 << ((__c) % BITS_PER)) +#define BIT_ENTRY(__c) ((__c) / BITS_PER) + +#define EXFAT_BITMAP_SIZE(__c_count) \ + (DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t)) +#define EXFAT_BITMAP_GET(__bmap, __c) \ + (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] & BIT_MASK(__c)) +#define EXFAT_BITMAP_SET(__bmap, __c) \ + (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] |= \ + BIT_MASK(__c)) +void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, + clus_t start_clus, clus_t count); + void show_version(void); -void exfat_set_bit(struct exfat_blk_dev *bd, char *bitmap, - unsigned int clu); -void exfat_clear_bit(struct exfat_blk_dev *bd, char *bitmap, - unsigned int clu); wchar_t exfat_bad_char(wchar_t w); void boot_calc_checksum(unsigned char *sector, unsigned short size, bool is_boot_sec, __le32 *checksum); diff --git a/lib/exfat_fs.c b/lib/exfat_fs.c index 84ff72d..9347614 100644 --- a/lib/exfat_fs.c +++ b/lib/exfat_fs.c @@ -14,24 +14,6 @@ #include "exfat_fs.h" #include "exfat_dir.h" -#ifdef WORDS_BIGENDIAN -typedef __u8 bitmap_t; -#else -typedef __u32 bitmap_t; -#endif - -#define BITS_PER (sizeof(bitmap_t) * 8) -#define BIT_MASK(__c) (1 << ((__c) % BITS_PER)) -#define BIT_ENTRY(__c) ((__c) / BITS_PER) - -#define EXFAT_BITMAP_SIZE(__c_count) \ - (DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t)) -#define EXFAT_BITMAP_GET(__bmap, __c) \ - (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] & BIT_MASK(__c)) -#define EXFAT_BITMAP_SET(__bmap, __c) \ - (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] |= \ - BIT_MASK(__c)) - struct exfat_inode *exfat_alloc_inode(__u16 attr) { struct exfat_inode *node; diff --git a/lib/libexfat.c b/lib/libexfat.c index b6084ee..896cecf 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -21,63 +21,23 @@ #include "version.h" #include "exfat_fs.h" -#define BITS_PER_LONG (sizeof(long) * CHAR_BIT) - -#ifdef WORDS_BIGENDIAN -#define BITOP_LE_SWIZZLE ((BITS_PER_LONG - 1) & ~0x7) -#else -#define BITOP_LE_SWIZZLE 0 -#endif - -#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) -#define BIT_WORD(nr) ((nr) / BITS_PER_LONG) - unsigned int print_level = EXFAT_INFO; -static inline void set_bit(int nr, void *addr) -{ - unsigned long mask = BIT_MASK(nr); - unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); - - *p |= mask; -} - -static inline void clear_bit(int nr, void *addr) -{ - unsigned long mask = BIT_MASK(nr); - unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); - - *p &= ~mask; -} - -static inline void set_bit_le(int nr, void *addr) +void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, + clus_t start_clus, clus_t count) { - set_bit(nr ^ BITOP_LE_SWIZZLE, addr); -} - -static inline void clear_bit_le(int nr, void *addr) -{ - clear_bit(nr ^ BITOP_LE_SWIZZLE, addr); -} - -void exfat_set_bit(struct exfat_blk_dev *bd, char *bitmap, - unsigned int clu) -{ - int b; + clus_t clus; - b = clu & ((bd->sector_size << 3) - 1); + if (!exfat_heap_clus(exfat, start_clus) || + !exfat_heap_clus(exfat, start_clus + count)) + return; - set_bit_le(b, bitmap); -} - -void exfat_clear_bit(struct exfat_blk_dev *bd, char *bitmap, - unsigned int clu) -{ - int b; - - b = clu & ((bd->sector_size << 3) - 1); - - clear_bit_le(b, bitmap); + clus = start_clus; + while (clus < start_clus + count) { + EXFAT_BITMAP_SET(bitmap, + clus - EXFAT_FIRST_CLUSTER); + clus++; + } } wchar_t exfat_bad_char(wchar_t w) diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c index b5b4957..cc1f62d 100644 --- a/mkfs/mkfs.c +++ b/mkfs/mkfs.c @@ -314,12 +314,12 @@ static int exfat_create_bitmap(struct exfat_blk_dev *bd) char *bitmap; unsigned int i, nbytes; - bitmap = calloc(finfo.bitmap_byte_len, sizeof(*bitmap)); + bitmap = calloc(EXFAT_BITMAP_SIZE(finfo.total_clu_cnt), sizeof(*bitmap)); if (!bitmap) return -1; for (i = 0; i < finfo.used_clu_cnt - EXFAT_FIRST_CLUSTER; i++) - exfat_set_bit(bd, bitmap, i); + EXFAT_BITMAP_SET(bitmap, i); nbytes = pwrite(bd->dev_fd, bitmap, finfo.bitmap_byte_len, finfo.bitmap_byte_off); if (nbytes != finfo.bitmap_byte_len) { -- cgit v1.2.3 From 139e7a93543372e28712dbe8a315a5dd1c34c82b Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 29 Mar 2021 09:53:47 +0900 Subject: fsck: add dentry set lookup function The specified dentry set can be found using exfat_lookup_dentry_set() with parent. Signed-off-by: Hyunchul Lee --- include/exfat_dir.h | 24 ++++++++++ lib/exfat_dir.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 140 insertions(+), 9 deletions(-) diff --git a/include/exfat_dir.h b/include/exfat_dir.h index ce010d2..746d818 100644 --- a/include/exfat_dir.h +++ b/include/exfat_dir.h @@ -24,6 +24,26 @@ struct exfat_de_iter { int max_skip_dentries; }; +struct exfat_lookup_filter { + struct { + uint8_t type; + /* return 0 if matched, return 1 if not matched, + * otherwise return errno + */ + int (*filter)(struct exfat_de_iter *iter, + void *param, int *dentry_count); + void *param; + } in; + struct { + struct exfat_dentry *dentry_set; + int dentry_count; + /* device offset where the dentry_set locates, or + * the empty slot locates or EOF if not found. + */ + off_t dentry_d_offset; + } out; +}; + int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, struct exfat_inode *dir, struct buffer_desc *bd); int exfat_de_iter_get(struct exfat_de_iter *iter, @@ -32,5 +52,9 @@ int exfat_de_iter_get_dirty(struct exfat_de_iter *iter, int ith, struct exfat_dentry **dentry); int exfat_de_iter_flush(struct exfat_de_iter *iter); int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries); +off_t exfat_de_iter_device_offset(struct exfat_de_iter *iter); + +int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent, + struct exfat_lookup_filter *filter); #endif diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c index 6257842..5742e2f 100644 --- a/lib/exfat_dir.c +++ b/lib/exfat_dir.c @@ -6,12 +6,24 @@ #include #include #include +#include #include "exfat_ondisk.h" #include "libexfat.h" #include "exfat_fs.h" #include "exfat_dir.h" +static struct path_resolve_ctx path_resolve_ctx; + +#define fsck_err(parent, inode, fmt, ...) \ +({ \ + exfat_resolve_path_parent(&path_resolve_ctx, \ + parent, inode); \ + exfat_err("ERROR: %s: " fmt, \ + path_resolve_ctx.local_path, \ + ##__VA_ARGS__); \ +}) + static ssize_t write_block(struct exfat_de_iter *iter, unsigned int block) { off_t device_offset; @@ -216,8 +228,7 @@ int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, iter->ra_partial_size = exfat->clus_size / 4; iter->ra_partial_size = MIN(iter->ra_partial_size, 8 * KB); - if (!iter->buffer_desc) - iter->buffer_desc = bd; + iter->buffer_desc = bd; if (iter->parent->size == 0) return EOF; @@ -296,13 +307,9 @@ int exfat_de_iter_flush(struct exfat_de_iter *iter) return 0; } -/* - * @skip_dentries must be the largest @ith + 1 of exfat_de_iter_get - * since the last call of exfat_de_iter_advance - */ int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries) { - if (skip_dentries != iter->max_skip_dentries) + if (skip_dentries > iter->max_skip_dentries) return -EINVAL; iter->max_skip_dentries = 0; @@ -311,7 +318,107 @@ int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries) return 0; } -off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter) +off_t exfat_de_iter_device_offset(struct exfat_de_iter *iter) { - return iter->de_file_offset; + struct buffer_desc *bd; + unsigned int block; + + if ((uint64_t)iter->de_file_offset >= iter->parent->size) + return EOF; + + block = iter->de_file_offset / iter->read_size; + bd = &iter->buffer_desc[block & 0x01]; + return exfat_c2o(iter->exfat, bd->p_clus) + bd->offset + + iter->de_file_offset % iter->read_size; +} + +/* + * try to find the dentry set matched with @filter. this function + * doesn't verify the dentry set. + * + * if found, return 0. if not found, return EOF. otherwise return errno. + */ +int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent, + struct exfat_lookup_filter *filter) +{ + struct buffer_desc *bd = NULL; + struct exfat_dentry *dentry = NULL; + off_t free_offset = 0; + struct exfat_de_iter de_iter; + int dentry_count; + int retval; + bool last_is_free = false; + + bd = exfat_alloc_buffer(2, exfat->clus_size, exfat->sect_size); + if (!bd) + return -ENOMEM; + + retval = exfat_de_iter_init(&de_iter, exfat, parent, bd); + if (retval == EOF || retval) + goto out; + + filter->out.dentry_set = NULL; + while (1) { + retval = exfat_de_iter_get(&de_iter, 0, &dentry); + if (retval == EOF) { + break; + } else if (retval) { + fsck_err(parent->parent, parent, + "failed to get a dentry. %d\n", retval); + goto out; + } + + dentry_count = 1; + if (dentry->type == filter->in.type) { + retval = 0; + if (filter->in.filter) + retval = filter->in.filter(&de_iter, + filter->in.param, + &dentry_count); + + if (retval == 0) { + struct exfat_dentry *d; + int i; + + filter->out.dentry_set = calloc(dentry_count, + sizeof(struct exfat_dentry)); + if (!filter->out.dentry_set) { + retval = -ENOMEM; + goto out; + } + for (i = 0; i < dentry_count; i++) { + exfat_de_iter_get(&de_iter, i, &d); + memcpy(filter->out.dentry_set + i, d, + sizeof(struct exfat_dentry)); + } + filter->out.dentry_count = dentry_count; + goto out; + } else if (retval < 0) { + goto out; + } + last_is_free = false; + } else if ((dentry->type == EXFAT_LAST || + IS_EXFAT_DELETED(dentry->type))) { + if (!last_is_free) { + free_offset = exfat_de_iter_device_offset(&de_iter); + last_is_free = true; + } + } else { + last_is_free = false; + } + + exfat_de_iter_advance(&de_iter, dentry_count); + } + +out: + if (retval == 0) + filter->out.dentry_d_offset = + exfat_de_iter_device_offset(&de_iter); + else if (retval == EOF && last_is_free) + filter->out.dentry_d_offset = free_offset; + else + filter->out.dentry_d_offset = EOF; + if (bd) + exfat_free_buffer(bd, 2); + return retval; } -- cgit v1.2.3 From 303936af072f66b4f458d484c33abbeac1bf0c4d Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Tue, 30 Mar 2021 09:33:35 +0900 Subject: fsck: read bitmap using lookup function Read bitmap using lookup function. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 53 +++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index f9f0dca..a71c7d9 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -758,19 +758,21 @@ static bool read_volume_label(struct exfat_de_iter *iter) return true; } -static bool read_bitmap(struct exfat_de_iter *iter) +static int read_bitmap(struct exfat *exfat) { + struct exfat_lookup_filter filter = { + .in.type = EXFAT_BITMAP, + .in.filter = NULL, + .in.param = NULL, + }; struct exfat_dentry *dentry; - struct exfat *exfat; - - exfat = iter->exfat; + int retval; - if (exfat_heap_clus(exfat, exfat->disk_bitmap_clus)) - return true; - - if (exfat_de_iter_get(iter, 0, &dentry)) - return false; + retval = exfat_lookup_dentry_set(exfat, exfat->root, &filter); + if (retval) + return retval; + dentry = filter.out.dentry_set; exfat_debug("start cluster %#x, size %#" PRIx64 "\n", le32_to_cpu(dentry->bitmap_start_clu), le64_to_cpu(dentry->bitmap_size)); @@ -779,12 +781,12 @@ static bool read_bitmap(struct exfat_de_iter *iter) DIV_ROUND_UP(exfat->clus_count, 8)) { exfat_err("invalid size of allocation bitmap. 0x%" PRIx64 "\n", le64_to_cpu(dentry->bitmap_size)); - return false; + return -EINVAL; } if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) { exfat_err("invalid start cluster of allocate bitmap. 0x%x\n", le32_to_cpu(dentry->bitmap_start_clu)); - return false; + return -EINVAL; } exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu); @@ -794,14 +796,14 @@ static bool read_bitmap(struct exfat_de_iter *iter) le64_to_cpu(dentry->bitmap_start_clu), DIV_ROUND_UP(exfat->disk_bitmap_size, exfat->clus_size)); + free(filter.out.dentry_set); if (exfat_read(exfat->blk_dev->dev_fd, exfat->disk_bitmap, exfat->disk_bitmap_size, exfat_c2o(exfat, exfat->disk_bitmap_clus)) != (ssize_t)exfat->disk_bitmap_size) - return false; - - return true; + return -EIO; + return 0; } static bool read_upcase_table(struct exfat_de_iter *iter) @@ -916,14 +918,6 @@ static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir) goto err; } break; - case EXFAT_BITMAP: - if (!read_bitmap(de_iter)) { - exfat_err( - "failed to verify allocation bitmap\n"); - ret = -EINVAL; - goto err; - } - break; case EXFAT_UPCASE: if (!read_upcase_table(de_iter)) { exfat_err( @@ -935,10 +929,12 @@ static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir) case EXFAT_LAST: goto out; default: - if (!IS_EXFAT_DELETED(dentry->type)) - exfat_err("unknown entry type. 0x%x\n", - dentry->type); - break; + if (IS_EXFAT_DELETED(dentry->type) || + dentry->type == EXFAT_BITMAP) + break; + exfat_err("unknown entry type. 0x%x\n", dentry->type); + ret = -EINVAL; + goto err; } exfat_de_iter_advance(de_iter, dentry_count); @@ -1151,6 +1147,11 @@ static int exfat_root_dir_check(struct exfat *exfat) exfat_stat.dir_count++; exfat_debug("root directory: start cluster[0x%x] size[0x%" PRIx64 "]\n", root->first_clus, root->size); + + if (read_bitmap(exfat)) { + exfat_err("failed to read bitmap\n"); + return -EINVAL; + } return 0; } -- cgit v1.2.3 From cf8644512f5ef6bf4345d245eb80dd3f91f530c9 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 1 Apr 2021 17:21:38 +0900 Subject: fsck: load upcase table using lookup function Load upcase table using lookup function and decompress the table for calculating the hash value of a file name. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 95 ++++++++++++++++++++++++++++++++++++++++-------------- include/exfat_fs.h | 1 + include/libexfat.h | 1 + 3 files changed, 73 insertions(+), 24 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index a71c7d9..1899540 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -806,23 +806,56 @@ static int read_bitmap(struct exfat *exfat) return 0; } -static bool read_upcase_table(struct exfat_de_iter *iter) +static int decompress_upcase_table(const __le16 *in_table, size_t in_len, + __u16 *out_table, size_t out_len) { - struct exfat_dentry *dentry; - struct exfat *exfat; + size_t i, k; + uint16_t ch; + + if (in_len > out_len) + return -E2BIG; + + for (k = 0; k < out_len; k++) + out_table[k] = k; + + for (i = 0, k = 0; i < in_len && k < out_len; i++) { + ch = le16_to_cpu(in_table[i]); + + if (ch == 0xFFFF && i + 1 < in_len) { + uint16_t len = le16_to_cpu(in_table[++i]); + + k += len; + } else { + out_table[k++] = ch; + } + } + return 0; +} + +static int read_upcase_table(struct exfat *exfat) +{ + struct exfat_lookup_filter filter = { + .in.type = EXFAT_UPCASE, + .in.filter = NULL, + .in.param = NULL, + }; + struct exfat_dentry *dentry = NULL; + __le16 *upcase = NULL; + int retval; ssize_t size; - __le16 *upcase; __le32 checksum; - exfat = iter->exfat; + retval = exfat_lookup_dentry_set(exfat, exfat->root, &filter); + if (retval) + return retval; - if (exfat_de_iter_get(iter, 0, &dentry)) - return false; + dentry = filter.out.dentry_set; if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) { exfat_err("invalid start cluster of upcase table. 0x%x\n", le32_to_cpu(dentry->upcase_start_clu)); - return false; + retval = -EINVAL; + goto out; } size = (ssize_t)le64_to_cpu(dentry->upcase_size); @@ -830,21 +863,23 @@ static bool read_upcase_table(struct exfat_de_iter *iter) size == 0 || size % sizeof(__le16)) { exfat_err("invalid size of upcase table. 0x%" PRIx64 "\n", le64_to_cpu(dentry->upcase_size)); - return false; + retval = -EINVAL; + goto out; } upcase = (__le16 *)malloc(size); if (!upcase) { exfat_err("failed to allocate upcase table\n"); - return false; + retval = -ENOMEM; + goto out; } if (exfat_read(exfat->blk_dev->dev_fd, upcase, size, exfat_c2o(exfat, le32_to_cpu(dentry->upcase_start_clu))) != size) { exfat_err("failed to read upcase table\n"); - free(upcase); - return false; + retval = -EIO; + goto out; } checksum = 0; @@ -852,8 +887,8 @@ static bool read_upcase_table(struct exfat_de_iter *iter) if (le32_to_cpu(dentry->upcase_checksum) != checksum) { exfat_err("corrupted upcase table %#x (expected: %#x)\n", checksum, le32_to_cpu(dentry->upcase_checksum)); - free(upcase); - return false; + retval = -EINVAL; + goto out; } exfat_bitmap_set_range(exfat, exfat->alloc_bitmap, @@ -861,8 +896,21 @@ static bool read_upcase_table(struct exfat_de_iter *iter) DIV_ROUND_UP(le64_to_cpu(dentry->upcase_size), exfat->clus_size)); - free(upcase); - return true; + exfat->upcase_table = calloc(1, + sizeof(uint16_t) * EXFAT_UPCASE_TABLE_CHARS); + if (!exfat->upcase_table) { + retval = -EIO; + goto out; + } + + decompress_upcase_table(upcase, size / 2, + exfat->upcase_table, EXFAT_UPCASE_TABLE_CHARS); +out: + if (dentry) + free(dentry); + if (upcase) + free(upcase); + return retval; } static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir) @@ -918,19 +966,13 @@ static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir) goto err; } break; + case EXFAT_BITMAP: case EXFAT_UPCASE: - if (!read_upcase_table(de_iter)) { - exfat_err( - "failed to verify upcase table\n"); - ret = -EINVAL; - goto err; - } break; case EXFAT_LAST: goto out; default: - if (IS_EXFAT_DELETED(dentry->type) || - dentry->type == EXFAT_BITMAP) + if (IS_EXFAT_DELETED(dentry->type)) break; exfat_err("unknown entry type. 0x%x\n", dentry->type); ret = -EINVAL; @@ -1152,6 +1194,11 @@ static int exfat_root_dir_check(struct exfat *exfat) exfat_err("failed to read bitmap\n"); return -EINVAL; } + + if (read_upcase_table(exfat)) { + exfat_err("failed to read upcase table\n"); + return -EINVAL; + } return 0; } diff --git a/include/exfat_fs.h b/include/exfat_fs.h index b31c2b6..820dbe6 100644 --- a/include/exfat_fs.h +++ b/include/exfat_fs.h @@ -37,6 +37,7 @@ struct exfat { char *disk_bitmap; clus_t disk_bitmap_clus; unsigned int disk_bitmap_size; + __u16 *upcase_table; }; struct path_resolve_ctx { diff --git a/include/libexfat.h b/include/libexfat.h index a0c89f6..7ed1d9e 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -37,6 +37,7 @@ typedef __u32 clus_t; #define VOLUME_LABEL_BUFFER_SIZE (VOLUME_LABEL_MAX_LEN*MB_LEN_MAX+1) /* Upcase table macro */ +#define EXFAT_UPCASE_TABLE_CHARS (0x10000) #define EXFAT_UPCASE_TABLE_SIZE (5836) /* Flags for tune.exfat and exfatlabel */ -- cgit v1.2.3 From 25a85939ddb8c62ccd4cd2b6cdb5f0703ae6473c Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Wed, 26 May 2021 17:19:57 +0900 Subject: fsck: read volume label in exfat_root_dir_check() Read the volume label in exfat_root_dir_check() using lookup function and remove this read from read_children(). Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 1899540..aab59a7 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -729,33 +729,43 @@ static int read_file(struct exfat_de_iter *de_iter, return ret; } -static bool read_volume_label(struct exfat_de_iter *iter) +static int read_volume_label(struct exfat *exfat) { - struct exfat *exfat; struct exfat_dentry *dentry; + int err; __le16 disk_label[VOLUME_LABEL_MAX_LEN]; + struct exfat_lookup_filter filter = { + .in.type = EXFAT_VOLUME, + .in.filter = NULL, + }; - exfat = iter->exfat; - if (exfat_de_iter_get(iter, 0, &dentry)) - return false; + err = exfat_lookup_dentry_set(exfat, exfat->root, &filter); + if (err) + return err; + + dentry = filter.out.dentry_set; if (dentry->vol_char_cnt == 0) - return true; + goto out; if (dentry->vol_char_cnt > VOLUME_LABEL_MAX_LEN) { exfat_err("too long label. %d\n", dentry->vol_char_cnt); - return false; + err = -EINVAL; + goto out; } memcpy(disk_label, dentry->vol_label, sizeof(disk_label)); if (exfat_utf16_dec(disk_label, dentry->vol_char_cnt*2, exfat->volume_label, sizeof(exfat->volume_label)) < 0) { exfat_err("failed to decode volume label\n"); - return false; + err = -EINVAL; + goto out; } exfat_info("volume label [%s]\n", exfat->volume_label); - return true; +out: + free(filter.out.dentry_set); + return err; } static int read_bitmap(struct exfat *exfat) @@ -960,12 +970,6 @@ static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir) exfat_free_inode(node); break; case EXFAT_VOLUME: - if (!read_volume_label(de_iter)) { - exfat_err("failed to verify volume label\n"); - ret = -EINVAL; - goto err; - } - break; case EXFAT_BITMAP: case EXFAT_UPCASE: break; @@ -1190,6 +1194,9 @@ static int exfat_root_dir_check(struct exfat *exfat) exfat_debug("root directory: start cluster[0x%x] size[0x%" PRIx64 "]\n", root->first_clus, root->size); + if (read_volume_label(exfat)) + exfat_err("failed to read volume label\n"); + if (read_bitmap(exfat)) { exfat_err("failed to read bitmap\n"); return -EINVAL; -- cgit v1.2.3 From cab0c62ce4135f646b57606c1d73cb83b2925fb3 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Tue, 6 Apr 2021 10:37:26 +0900 Subject: fsck: add file dentry set lookup function Add a lookup function to find the specified file dentry set. Signed-off-by: Hyunchul Lee --- include/exfat_dir.h | 2 ++ lib/exfat_dir.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/include/exfat_dir.h b/include/exfat_dir.h index 746d818..109262e 100644 --- a/include/exfat_dir.h +++ b/include/exfat_dir.h @@ -56,5 +56,7 @@ off_t exfat_de_iter_device_offset(struct exfat_de_iter *iter); int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent, struct exfat_lookup_filter *filter); +int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent, + const char *name, struct exfat_lookup_filter *filter_out); #endif diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c index 5742e2f..466c119 100644 --- a/lib/exfat_dir.c +++ b/lib/exfat_dir.c @@ -422,3 +422,67 @@ out: exfat_free_buffer(bd, 2); return retval; } + +static int filter_lookup_file(struct exfat_de_iter *de_iter, + void *param, int *dentry_count) +{ + struct exfat_dentry *file_de, *stream_de, *name_de; + __le16 *name; + int retval, name_len; + int i; + + retval = exfat_de_iter_get(de_iter, 0, &file_de); + if (retval || file_de->type != EXFAT_FILE) + return 1; + + retval = exfat_de_iter_get(de_iter, 1, &stream_de); + if (retval || stream_de->type != EXFAT_STREAM) + return 1; + + name = (__le16 *)param; + name_len = (int)exfat_utf16_len(name, PATH_MAX); + + if (file_de->dentry.file.num_ext < + 1 + (name_len + ENTRY_NAME_MAX - 1) / ENTRY_NAME_MAX) + return 1; + + for (i = 2; i <= file_de->dentry.file.num_ext && name_len > 0; i++) { + int len; + + retval = exfat_de_iter_get(de_iter, i, &name_de); + if (retval || name_de->type != EXFAT_NAME) + return 1; + + len = MIN(name_len, ENTRY_NAME_MAX); + if (memcmp(name_de->dentry.name.unicode_0_14, + name, len * 2) != 0) + return 1; + + name += len; + name_len -= len; + } + + *dentry_count = i; + return 0; +} + +int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent, + const char *name, struct exfat_lookup_filter *filter_out) +{ + int retval; + __le16 utf16_name[PATH_MAX + 2] = {0, }; + + retval = (int)exfat_utf16_enc(name, utf16_name, sizeof(utf16_name)); + if (retval < 0) + return retval; + + filter_out->in.type = EXFAT_FILE; + filter_out->in.filter = filter_lookup_file; + filter_out->in.param = utf16_name; + + retval = exfat_lookup_dentry_set(exfat, parent, filter_out); + if (retval < 0) + return retval; + + return 0; +} -- cgit v1.2.3 From 2ea5c975161f468b32eeff1585e32e6fe077415b Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Tue, 6 Apr 2021 16:08:01 +0900 Subject: fsck: add function to create a file Add exfat_create_file() to create a file. Signed-off-by: Hyunchul Lee --- include/exfat_dir.h | 2 + include/exfat_ondisk.h | 6 ++- include/libexfat.h | 2 + lib/exfat_dir.c | 143 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/libexfat.c | 17 ++++++ 5 files changed, 168 insertions(+), 2 deletions(-) diff --git a/include/exfat_dir.h b/include/exfat_dir.h index 109262e..6ca9c17 100644 --- a/include/exfat_dir.h +++ b/include/exfat_dir.h @@ -58,5 +58,7 @@ int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent, struct exfat_lookup_filter *filter); int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent, const char *name, struct exfat_lookup_filter *filter_out); +int exfat_create_file(struct exfat *exfat, struct exfat_inode *parent, + const char *name, unsigned short attr); #endif diff --git a/include/exfat_ondisk.h b/include/exfat_ondisk.h index b3fc1fe..79f0883 100644 --- a/include/exfat_ondisk.h +++ b/include/exfat_ondisk.h @@ -156,8 +156,10 @@ struct exfat_dentry { __le16 access_date; __u8 create_time_ms; __u8 modify_time_ms; - __u8 access_time_ms; - __u8 reserved2[9]; + __u8 create_tz; + __u8 modify_tz; + __u8 access_tz; + __u8 reserved2[7]; } __attribute__((packed)) file; /* file directory entry */ struct { __u8 flags; diff --git a/include/libexfat.h b/include/libexfat.h index 7ed1d9e..714f605 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -145,6 +145,8 @@ int exfat_get_next_clus(struct exfat *exfat, struct exfat_inode *node, int exfat_set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus); off_t exfat_s2o(struct exfat *exfat, off_t sect); off_t exfat_c2o(struct exfat *exfat, unsigned int clus); +int exfat_o2c(struct exfat *exfat, off_t device_offset, + unsigned int *clu, unsigned int *offset); bool exfat_heap_clus(struct exfat *exfat, clus_t clus); /* diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c index 466c119..387a1e0 100644 --- a/lib/exfat_dir.c +++ b/lib/exfat_dir.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "exfat_ondisk.h" #include "libexfat.h" @@ -486,3 +487,145 @@ int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent, return 0; } + +static void unix_time_to_exfat_time(time_t unix_time, __u8 *tz, __le16 *date, + __le16 *time, __u8 *time_ms) +{ + struct tm tm; + __u16 t, d; + + gmtime_r(&unix_time, &tm); + d = ((tm.tm_year - 80) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday; + t = (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); + + *tz = 0x80; + *date = cpu_to_le16(d); + *time = cpu_to_le16(t); + if (time_ms) + *time_ms = (tm.tm_sec & 1) * 100; +} + +static __u16 exfat_calc_name_chksum(struct exfat *exfat, __le16 *name, int len) +{ + int i; + __le16 ch; + __u16 chksum = 0; + + for (i = 0; i < len; i++) { + ch = exfat->upcase_table[le16_to_cpu(name[i])]; + ch = cpu_to_le16(ch); + + chksum = ((chksum << 15) | (chksum >> 1)) + (ch & 0xFF); + chksum = ((chksum << 15) | (chksum >> 1)) + (ch >> 8); + } + return chksum; +} + +int exfat_build_file_dentry_set(struct exfat *exfat, const char *name, + unsigned short attr, struct exfat_dentry **dentry_set, + int *dentry_count) +{ + struct exfat_dentry *d_set; + __le16 utf16_name[PATH_MAX + 2]; + int retval; + int d_count, name_len, i; + __le16 e_date, e_time; + __u8 tz, e_time_ms; + + memset(utf16_name, 0, sizeof(utf16_name)); + retval = exfat_utf16_enc(name, utf16_name, sizeof(utf16_name)); + if (retval < 0) + return retval; + + name_len = retval / 2; + d_count = 2 + ((name_len + ENTRY_NAME_MAX - 1) / ENTRY_NAME_MAX); + d_set = calloc(d_count, sizeof(struct exfat_dentry)); + if (!d_set) + return -ENOMEM; + + d_set[0].type = EXFAT_FILE; + d_set[0].dentry.file.num_ext = d_count - 1; + d_set[0].dentry.file.attr = cpu_to_le16(attr); + + unix_time_to_exfat_time(time(NULL), &tz, + &e_date, &e_time, &e_time_ms); + + d_set[0].dentry.file.create_date = e_date; + d_set[0].dentry.file.create_time = e_time; + d_set[0].dentry.file.create_time_ms = e_time_ms; + d_set[0].dentry.file.create_tz = tz; + + d_set[0].dentry.file.modify_date = e_date; + d_set[0].dentry.file.modify_time = e_time; + d_set[0].dentry.file.modify_time_ms = e_time_ms; + d_set[0].dentry.file.modify_tz = tz; + + d_set[0].dentry.file.access_date = e_date; + d_set[0].dentry.file.access_time = e_time; + d_set[0].dentry.file.access_tz = tz; + + d_set[1].type = EXFAT_STREAM; + d_set[1].dentry.stream.flags = 0x01; + d_set[1].dentry.stream.name_len = (__u8)name_len; + d_set[1].dentry.stream.name_hash = + cpu_to_le16(exfat_calc_name_chksum(exfat, utf16_name, name_len)); + + for (i = 2; i < d_count; i++) { + d_set[i].type = EXFAT_NAME; + memcpy(d_set[i].dentry.name.unicode_0_14, + utf16_name + (i - 2) * ENTRY_NAME_MAX * 2, + ENTRY_NAME_MAX * 2); + } + + *dentry_set = d_set; + *dentry_count = d_count; + return 0; +} + +int exfat_create_file(struct exfat *exfat, struct exfat_inode *parent, + const char *name, unsigned short attr) +{ + struct exfat_dentry *dentry_set; + int dentry_count; + int retval; + unsigned int clu, offset, set_len; + struct exfat_lookup_filter filter; + + retval = exfat_lookup_file(exfat, parent, name, + &filter); + if (retval == 0) { + struct exfat_dentry *dentry; + + dentry = filter.out.dentry_set; + if ((le16_to_cpu(dentry->dentry.file.attr) & attr) != attr) + retval = -EEXIST; + + free(filter.out.dentry_set); + return retval; + } + + retval = exfat_build_file_dentry_set(exfat, name, attr, + &dentry_set, &dentry_count); + if (retval < 0) + return retval; + + retval = exfat_o2c(exfat, filter.out.dentry_d_offset, &clu, &offset); + if (retval) + goto out; + + set_len = dentry_count * sizeof(struct exfat_dentry); + if (offset + set_len > exfat->clus_size) { + retval = -ENOSPC; + goto out; + } + + if (exfat_write(exfat->blk_dev->dev_fd, dentry_set, + set_len, filter.out.dentry_d_offset) != + (ssize_t)set_len) { + retval = -EIO; + goto out; + } +out: + free(dentry_set); + return 0; +} diff --git a/lib/libexfat.c b/lib/libexfat.c index 896cecf..9b79a84 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -698,6 +698,23 @@ off_t exfat_c2o(struct exfat *exfat, unsigned int clus) exfat->bs->bsx.sect_per_clus_bits)); } +int exfat_o2c(struct exfat *exfat, off_t device_offset, + unsigned int *clu, unsigned int *offset) +{ + off_t heap_offset; + + heap_offset = exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset)); + if (device_offset < heap_offset) + return -ERANGE; + + *clu = (unsigned int)((device_offset - heap_offset) / + exfat->clus_size) + EXFAT_FIRST_CLUSTER; + if (!exfat_heap_clus(exfat, *clu)) + return -ERANGE; + *offset = (device_offset - heap_offset) % exfat->clus_size; + return 0; +} + bool exfat_heap_clus(struct exfat *exfat, clus_t clus) { return clus >= EXFAT_FIRST_CLUSTER && -- cgit v1.2.3 From 216d0fc5a560a2ac952432288ad2a6f090035e5f Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 12 Apr 2021 15:29:45 +0900 Subject: fsck: change cluster argument of bitmap operations Change the cluster argument of bitmap operations to start with EXFAT_FIRST_CLUSTER instead of 0. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 28 +++++++++++----------------- include/libexfat.h | 20 +++++++++++++++----- lib/libexfat.c | 5 ++--- mkfs/mkfs.c | 7 ++++--- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index aab59a7..0b14b1a 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -139,8 +139,7 @@ static int check_clus_chain(struct exfat_de_iter *de_iter, * This cluster is already allocated. it may be shared with * the other file, or there is a loop in cluster chain. */ - if (EXFAT_BITMAP_GET(exfat->alloc_bitmap, - clus - EXFAT_FIRST_CLUSTER)) { + if (exfat_bitmap_get(exfat->alloc_bitmap, clus)) { if (repair_file_ask(de_iter, node, ER_FILE_DUPLICATED_CLUS, "cluster is already allocated for the other file. truncated to %" @@ -151,9 +150,8 @@ static int check_clus_chain(struct exfat_de_iter *de_iter, return -EINVAL; } - if (!EXFAT_BITMAP_GET(exfat->disk_bitmap, - clus - EXFAT_FIRST_CLUSTER)) { - if (!repair_file_ask(&exfat->de_iter, node, + if (!exfat_bitmap_get(exfat->disk_bitmap, clus)) { + if (!repair_file_ask(de_iter, node, ER_FILE_INVALID_CLUS, "cluster %#x is marked as free", clus)) @@ -182,8 +180,8 @@ static int check_clus_chain(struct exfat_de_iter *de_iter, (count + 1) * exfat->clus_size)) { count++; prev = clus; - EXFAT_BITMAP_SET(exfat->alloc_bitmap, - clus - EXFAT_FIRST_CLUSTER); + exfat_bitmap_set(exfat->alloc_bitmap, + clus); goto truncate_file; } else { return -EINVAL; @@ -192,8 +190,7 @@ static int check_clus_chain(struct exfat_de_iter *de_iter, } count++; - EXFAT_BITMAP_SET(exfat->alloc_bitmap, - clus - EXFAT_FIRST_CLUSTER); + exfat_bitmap_set(exfat->alloc_bitmap, clus); prev = clus; clus = next; } @@ -248,15 +245,13 @@ static bool root_get_clus_count(struct exfat *exfat, struct exfat_inode *node, return false; } - if (EXFAT_BITMAP_GET(exfat->alloc_bitmap, - clus - EXFAT_FIRST_CLUSTER)) { + if (exfat_bitmap_get(exfat->alloc_bitmap, clus)) { exfat_err("/: cluster is already allocated, or " "there is a loop in cluster chain\n"); return false; } - EXFAT_BITMAP_SET(exfat->alloc_bitmap, - clus - EXFAT_FIRST_CLUSTER); + exfat_bitmap_set(exfat->alloc_bitmap, clus); if (exfat_get_next_clus(exfat, node, clus, &clus) != 0) { exfat_err("/: broken cluster chain\n"); @@ -1027,10 +1022,9 @@ static int write_dirty_fat(struct exfat_fsck *fsck) for (i = clus ? clus : EXFAT_FIRST_CLUSTER; i < clus + clus_count; i++) { - if (!EXFAT_BITMAP_GET(exfat->alloc_bitmap, - i - EXFAT_FIRST_CLUSTER) && - ((clus_t *)bd[idx].buffer)[i - clus] != - EXFAT_FREE_CLUSTER) { + if (!exfat_bitmap_get(exfat->alloc_bitmap, i) && + ((clus_t *)bd[idx].buffer)[i - clus] != + EXFAT_FREE_CLUSTER) { ((clus_t *)bd[idx].buffer)[i - clus] = EXFAT_FREE_CLUSTER; bd[idx].dirty[(i - clus) / diff --git a/include/libexfat.h b/include/libexfat.h index 714f605..cac8851 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -101,11 +101,21 @@ typedef __u32 bitmap_t; #define EXFAT_BITMAP_SIZE(__c_count) \ (DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t)) -#define EXFAT_BITMAP_GET(__bmap, __c) \ - (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] & BIT_MASK(__c)) -#define EXFAT_BITMAP_SET(__bmap, __c) \ - (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] |= \ - BIT_MASK(__c)) + +static inline bool exfat_bitmap_get(char *bmap, clus_t c) +{ + clus_t cc = c - EXFAT_FIRST_CLUSTER; + + return ((bitmap_t *)(bmap))[BIT_ENTRY(cc)] & BIT_MASK(cc); +} + +static inline void exfat_bitmap_set(char *bmap, clus_t c) +{ + clus_t cc = c - EXFAT_FIRST_CLUSTER; + + (((bitmap_t *)(bmap))[BIT_ENTRY(cc)] |= BIT_MASK(cc)); +} + void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, clus_t start_clus, clus_t count); diff --git a/lib/libexfat.c b/lib/libexfat.c index 9b79a84..d805b50 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -29,13 +29,12 @@ void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, clus_t clus; if (!exfat_heap_clus(exfat, start_clus) || - !exfat_heap_clus(exfat, start_clus + count)) + !exfat_heap_clus(exfat, start_clus + count - 1)) return; clus = start_clus; while (clus < start_clus + count) { - EXFAT_BITMAP_SET(bitmap, - clus - EXFAT_FIRST_CLUSTER); + exfat_bitmap_set(bitmap, clus); clus++; } } diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c index cc1f62d..511662b 100644 --- a/mkfs/mkfs.c +++ b/mkfs/mkfs.c @@ -314,12 +314,13 @@ static int exfat_create_bitmap(struct exfat_blk_dev *bd) char *bitmap; unsigned int i, nbytes; - bitmap = calloc(EXFAT_BITMAP_SIZE(finfo.total_clu_cnt), sizeof(*bitmap)); + bitmap = calloc(round_up(finfo.bitmap_byte_len, sizeof(bitmap_t)), + sizeof(*bitmap)); if (!bitmap) return -1; - for (i = 0; i < finfo.used_clu_cnt - EXFAT_FIRST_CLUSTER; i++) - EXFAT_BITMAP_SET(bitmap, i); + for (i = EXFAT_FIRST_CLUSTER; i < finfo.used_clu_cnt; i++) + exfat_bitmap_set(bitmap, i); nbytes = pwrite(bd->dev_fd, bitmap, finfo.bitmap_byte_len, finfo.bitmap_byte_off); if (nbytes != finfo.bitmap_byte_len) { -- cgit v1.2.3 From 896f1bc9f6424a129be53bcac9c0e1d102f42b80 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 12 Apr 2021 15:50:43 +0900 Subject: fsck: add function to get an FAT entry Add get_next_clus to get an FAT entry. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 4 ++-- include/libexfat.h | 5 +++-- lib/exfat_dir.c | 8 ++++---- lib/libexfat.c | 23 ++++++++++++++++------- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 0b14b1a..6c71a4a 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -159,7 +159,7 @@ static int check_clus_chain(struct exfat_de_iter *de_iter, } /* This cluster is allocated or not */ - if (exfat_get_next_clus(exfat, node, clus, &next)) + if (exfat_get_inode_next_clus(exfat, node, clus, &next)) goto truncate_file; if (next == EXFAT_BAD_CLUSTER) { if (repair_file_ask(de_iter, node, @@ -253,7 +253,7 @@ static bool root_get_clus_count(struct exfat *exfat, struct exfat_inode *node, exfat_bitmap_set(exfat->alloc_bitmap, clus); - if (exfat_get_next_clus(exfat, node, clus, &clus) != 0) { + if (exfat_get_inode_next_clus(exfat, node, clus, &clus) != 0) { exfat_err("/: broken cluster chain\n"); return false; } diff --git a/include/libexfat.h b/include/libexfat.h index cac8851..246d472 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -150,8 +150,9 @@ int exfat_set_volume_serial(struct exfat_blk_dev *bd, struct exfat_user_input *ui); unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd, unsigned int clu_off, unsigned int clu); -int exfat_get_next_clus(struct exfat *exfat, struct exfat_inode *node, - clus_t clus, clus_t *next); +int exfat_get_next_clus(struct exfat *exfat, clus_t clus, clus_t *next); +int exfat_get_inode_next_clus(struct exfat *exfat, struct exfat_inode *node, + clus_t clus, clus_t *next); int exfat_set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus); off_t exfat_s2o(struct exfat *exfat, off_t sect); off_t exfat_c2o(struct exfat *exfat, unsigned int clus); diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c index 387a1e0..ade3956 100644 --- a/lib/exfat_dir.c +++ b/lib/exfat_dir.c @@ -94,8 +94,8 @@ static int read_ahead_next_blocks(struct exfat_de_iter *iter, ra_clus = clus + 1; if (ra_clus == iter->ra_next_clus && offset >= iter->ra_begin_offset) { - ret = exfat_get_next_clus(exfat, iter->parent, - p_clus, &ra_p_clus); + ret = exfat_get_inode_next_clus(exfat, iter->parent, + p_clus, &ra_p_clus); if (ret) return ret; @@ -186,8 +186,8 @@ static ssize_t read_block(struct exfat_de_iter *iter, unsigned int block) desc->p_clus = prev_desc->p_clus; desc->offset = prev_desc->offset + iter->read_size; } else { - ret = exfat_get_next_clus(exfat, iter->parent, - prev_desc->p_clus, &desc->p_clus); + ret = exfat_get_inode_next_clus(exfat, iter->parent, + prev_desc->p_clus, &desc->p_clus); desc->offset = 0; if (ret) return ret; diff --git a/lib/libexfat.c b/lib/libexfat.c index d805b50..2f8c78e 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -642,8 +642,7 @@ unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd, (clu - EXFAT_RESERVED_CLUSTERS) * bd->cluster_size; } -int exfat_get_next_clus(struct exfat *exfat, struct exfat_inode *node, - clus_t clus, clus_t *next) +int exfat_get_next_clus(struct exfat *exfat, clus_t clus, clus_t *next) { off_t offset; @@ -652,11 +651,6 @@ int exfat_get_next_clus(struct exfat *exfat, struct exfat_inode *node, if (!exfat_heap_clus(exfat, clus)) return -EINVAL; - if (node->is_contiguous) { - *next = clus + 1; - return 0; - } - offset = (off_t)le32_to_cpu(exfat->bs->bsx.fat_offset) << exfat->bs->bsx.sect_size_bits; offset += sizeof(clus_t) * clus; @@ -668,6 +662,21 @@ int exfat_get_next_clus(struct exfat *exfat, struct exfat_inode *node, return 0; } +int exfat_get_inode_next_clus(struct exfat *exfat, struct exfat_inode *node, + clus_t clus, clus_t *next) +{ + *next = EXFAT_EOF_CLUSTER; + + if (node->is_contiguous) { + if (!exfat_heap_clus(exfat, clus)) + return -EINVAL; + *next = clus + 1; + return 0; + } + + return exfat_get_next_clus(exfat, clus, next); +} + int exfat_set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus) { off_t offset; -- cgit v1.2.3 From 073f2c33302bf9d9df6d96605f7def02c1ede0b1 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 3 May 2021 14:56:53 +0900 Subject: fsck: add file offset to exfat_lookup_filter Make exfat_lookup_dentry_set store the file offset of a found directory entry to the exfat_lookup_filter strcuture. Signed-off-by: Hyunchul Lee --- include/exfat_dir.h | 4 +++- lib/exfat_dir.c | 40 +++++++++++++++++++++++++++------------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/include/exfat_dir.h b/include/exfat_dir.h index 6ca9c17..930918e 100644 --- a/include/exfat_dir.h +++ b/include/exfat_dir.h @@ -37,10 +37,11 @@ struct exfat_lookup_filter { struct { struct exfat_dentry *dentry_set; int dentry_count; + off_t file_offset; /* device offset where the dentry_set locates, or * the empty slot locates or EOF if not found. */ - off_t dentry_d_offset; + off_t dev_offset; } out; }; @@ -53,6 +54,7 @@ int exfat_de_iter_get_dirty(struct exfat_de_iter *iter, int exfat_de_iter_flush(struct exfat_de_iter *iter); int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries); off_t exfat_de_iter_device_offset(struct exfat_de_iter *iter); +off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter); int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent, struct exfat_lookup_filter *filter); diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c index ade3956..29bd133 100644 --- a/lib/exfat_dir.c +++ b/lib/exfat_dir.c @@ -231,6 +231,10 @@ int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, iter->buffer_desc = bd; + iter->de_file_offset = 0; + iter->next_read_offset = iter->read_size; + iter->max_skip_dentries = 0; + if (iter->parent->size == 0) return EOF; @@ -240,9 +244,6 @@ int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, return -EIO; } - iter->de_file_offset = 0; - iter->next_read_offset = iter->read_size; - iter->max_skip_dentries = 0; return 0; } @@ -333,6 +334,11 @@ off_t exfat_de_iter_device_offset(struct exfat_de_iter *iter) iter->de_file_offset % iter->read_size; } +off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter) +{ + return iter->de_file_offset; +} + /* * try to find the dentry set matched with @filter. this function * doesn't verify the dentry set. @@ -344,7 +350,7 @@ int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent, { struct buffer_desc *bd = NULL; struct exfat_dentry *dentry = NULL; - off_t free_offset = 0; + off_t free_file_offset = 0, free_dev_offset = 0; struct exfat_de_iter de_iter; int dentry_count; int retval; @@ -401,7 +407,10 @@ int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent, } else if ((dentry->type == EXFAT_LAST || IS_EXFAT_DELETED(dentry->type))) { if (!last_is_free) { - free_offset = exfat_de_iter_device_offset(&de_iter); + free_file_offset = + exfat_de_iter_file_offset(&de_iter); + free_dev_offset = + exfat_de_iter_device_offset(&de_iter); last_is_free = true; } } else { @@ -412,13 +421,18 @@ int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent, } out: - if (retval == 0) - filter->out.dentry_d_offset = + if (retval == 0) { + filter->out.file_offset = + exfat_de_iter_file_offset(&de_iter); + filter->out.dev_offset = exfat_de_iter_device_offset(&de_iter); - else if (retval == EOF && last_is_free) - filter->out.dentry_d_offset = free_offset; - else - filter->out.dentry_d_offset = EOF; + } else if (retval == EOF && last_is_free) { + filter->out.file_offset = free_file_offset; + filter->out.dev_offset = free_dev_offset; + } else { + filter->out.file_offset = exfat_de_iter_file_offset(&de_iter); + filter->out.dev_offset = EOF; + } if (bd) exfat_free_buffer(bd, 2); return retval; @@ -609,7 +623,7 @@ int exfat_create_file(struct exfat *exfat, struct exfat_inode *parent, if (retval < 0) return retval; - retval = exfat_o2c(exfat, filter.out.dentry_d_offset, &clu, &offset); + retval = exfat_o2c(exfat, filter.out.dev_offset, &clu, &offset); if (retval) goto out; @@ -620,7 +634,7 @@ int exfat_create_file(struct exfat *exfat, struct exfat_inode *parent, } if (exfat_write(exfat->blk_dev->dev_fd, dentry_set, - set_len, filter.out.dentry_d_offset) != + set_len, filter.out.dev_offset) != (ssize_t)set_len) { retval = -EIO; goto out; -- cgit v1.2.3 From e966e5f3349a2289c08e1dee3489fc717beab583 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 3 May 2021 15:22:44 +0900 Subject: fsck: add function to append file dentry set Add function to append file dentry sets. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 26 +-- include/exfat_dir.h | 16 ++ include/exfat_fs.h | 15 +- include/exfat_ondisk.h | 1 + include/libexfat.h | 8 + lib/exfat_dir.c | 444 ++++++++++++++++++++++++++++++++++++++++--------- lib/exfat_fs.c | 12 +- lib/libexfat.c | 17 ++ 8 files changed, 433 insertions(+), 106 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 6c71a4a..537df66 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -532,38 +532,20 @@ restore: return ret; } -static void dentry_calc_checksum(struct exfat_dentry *dentry, - __le16 *checksum, bool primary) +static uint16_t file_calc_checksum(struct exfat_de_iter *iter) { - unsigned int i; - uint8_t *bytes; - - bytes = (uint8_t *)dentry; - - *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[0]; - *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[1]; - - i = primary ? 4 : 2; - for (; i < sizeof(*dentry); i++) { - *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[i]; - } -} - -static __le16 file_calc_checksum(struct exfat_de_iter *iter) -{ - __le16 checksum; + uint16_t checksum; struct exfat_dentry *file_de, *de; int i; checksum = 0; exfat_de_iter_get(iter, 0, &file_de); - dentry_calc_checksum(file_de, &checksum, true); + exfat_calc_dentry_checksum(file_de, &checksum, true); for (i = 1; i <= file_de->file_num_ext; i++) { exfat_de_iter_get(iter, i, &de); - dentry_calc_checksum(de, &checksum, false); + exfat_calc_dentry_checksum(de, &checksum, false); } - return checksum; } diff --git a/include/exfat_dir.h b/include/exfat_dir.h index 930918e..8e55000 100644 --- a/include/exfat_dir.h +++ b/include/exfat_dir.h @@ -8,6 +8,7 @@ struct exfat; struct exfat_inode; +struct exfat_dentry_loc; struct buffer_desc; struct exfat_de_iter { @@ -60,7 +61,22 @@ int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent, struct exfat_lookup_filter *filter); int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent, const char *name, struct exfat_lookup_filter *filter_out); + int exfat_create_file(struct exfat *exfat, struct exfat_inode *parent, const char *name, unsigned short attr); +int exfat_update_file_dentry_set(struct exfat *exfat, + struct exfat_dentry *dset, int dcount, + const char *name, + clus_t start_clu, clus_t ccount); +int exfat_build_file_dentry_set(struct exfat *exfat, const char *name, + unsigned short attr, struct exfat_dentry **dentry_set, + int *dentry_count); +int exfat_add_dentry_set(struct exfat *exfat, struct exfat_dentry_loc *loc, + struct exfat_dentry *dset, int dcount, + bool need_next_loc); +void exfat_calc_dentry_checksum(struct exfat_dentry *dentry, + uint16_t *checksum, bool primary); +uint16_t exfat_calc_name_hash(struct exfat *exfat, + __le16 *name, int len); #endif diff --git a/include/exfat_fs.h b/include/exfat_fs.h index 820dbe6..47b3253 100644 --- a/include/exfat_fs.h +++ b/include/exfat_fs.h @@ -7,17 +7,20 @@ #include "list.h" +struct exfat_dentry; + struct exfat_inode { struct exfat_inode *parent; struct list_head children; struct list_head sibling; struct list_head list; clus_t first_clus; - clus_t last_lclus; - clus_t last_pclus; __u16 attr; uint64_t size; bool is_contiguous; + struct exfat_dentry *dentry_set; + int dentry_count; + off_t dev_offset; __le16 name[0]; /* only for directory */ }; @@ -38,6 +41,14 @@ struct exfat { clus_t disk_bitmap_clus; unsigned int disk_bitmap_size; __u16 *upcase_table; + clus_t start_clu; + char *zero_cluster; +}; + +struct exfat_dentry_loc { + struct exfat_inode *parent; + off_t file_offset; + off_t dev_offset; }; struct path_resolve_ctx { diff --git a/include/exfat_ondisk.h b/include/exfat_ondisk.h index 79f0883..d1786bf 100644 --- a/include/exfat_ondisk.h +++ b/include/exfat_ondisk.h @@ -39,6 +39,7 @@ #define DENTRY_SIZE_BITS 5 /* exFAT allows 8388608(256MB) directory entries */ #define MAX_EXFAT_DENTRIES 8388608 +#define MIN_FILE_DENTRIES 3 /* dentry types */ #define MSDOS_DELETED 0xE5 /* deleted mark */ diff --git a/include/libexfat.h b/include/libexfat.h index 246d472..ec57eb1 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -116,8 +116,16 @@ static inline void exfat_bitmap_set(char *bmap, clus_t c) (((bitmap_t *)(bmap))[BIT_ENTRY(cc)] |= BIT_MASK(cc)); } +static inline void exfat_bitmap_clear(char *bmap, clus_t c) +{ + clus_t cc = c - EXFAT_FIRST_CLUSTER; + (((bitmap_t *)(bmap))[BIT_ENTRY(cc)] &= ~BIT_MASK(cc)); +} + void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, clus_t start_clus, clus_t count); +int exfat_bitmap_find_zero(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next); void show_version(void); diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c index 29bd133..55f1eb6 100644 --- a/lib/exfat_dir.c +++ b/lib/exfat_dir.c @@ -502,28 +502,43 @@ int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent, return 0; } -static void unix_time_to_exfat_time(time_t unix_time, __u8 *tz, __le16 *date, - __le16 *time, __u8 *time_ms) +void exfat_calc_dentry_checksum(struct exfat_dentry *dentry, + uint16_t *checksum, bool primary) { - struct tm tm; - __u16 t, d; + unsigned int i; + uint8_t *bytes; - gmtime_r(&unix_time, &tm); - d = ((tm.tm_year - 80) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday; - t = (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); + bytes = (uint8_t *)dentry; - *tz = 0x80; - *date = cpu_to_le16(d); - *time = cpu_to_le16(t); - if (time_ms) - *time_ms = (tm.tm_sec & 1) * 100; + *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[0]; + *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[1]; + + i = primary ? 4 : 2; + for (; i < sizeof(*dentry); i++) + *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[i]; +} + +static uint16_t calc_dentry_set_checksum(struct exfat_dentry *dset, int dcount) +{ + uint16_t checksum; + int i; + + if (dcount < MIN_FILE_DENTRIES) + return 0; + + checksum = 0; + exfat_calc_dentry_checksum(&dset[0], &checksum, true); + for (i = 1; i < dcount; i++) + exfat_calc_dentry_checksum(&dset[i], &checksum, false); + return checksum; } -static __u16 exfat_calc_name_chksum(struct exfat *exfat, __le16 *name, int len) +uint16_t exfat_calc_name_hash(struct exfat *exfat, + __le16 *name, int len) { int i; __le16 ch; - __u16 chksum = 0; + uint16_t chksum = 0; for (i = 0; i < len; i++) { ch = exfat->upcase_table[le16_to_cpu(name[i])]; @@ -535,14 +550,31 @@ static __u16 exfat_calc_name_chksum(struct exfat *exfat, __le16 *name, int len) return chksum; } +static void unix_time_to_exfat_time(time_t unix_time, __u8 *tz, __le16 *date, + __le16 *time, __u8 *time_ms) +{ + struct tm tm; + __u16 t, d; + + gmtime_r(&unix_time, &tm); + d = ((tm.tm_year - 80) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday; + t = (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); + + *tz = 0x80; + *date = cpu_to_le16(d); + *time = cpu_to_le16(t); + if (time_ms) + *time_ms = (tm.tm_sec & 1) * 100; +} + int exfat_build_file_dentry_set(struct exfat *exfat, const char *name, unsigned short attr, struct exfat_dentry **dentry_set, int *dentry_count) { - struct exfat_dentry *d_set; + struct exfat_dentry *dset; __le16 utf16_name[PATH_MAX + 2]; int retval; - int d_count, name_len, i; + int dcount, name_len, i; __le16 e_date, e_time; __u8 tz, e_time_ms; @@ -552,94 +584,344 @@ int exfat_build_file_dentry_set(struct exfat *exfat, const char *name, return retval; name_len = retval / 2; - d_count = 2 + ((name_len + ENTRY_NAME_MAX - 1) / ENTRY_NAME_MAX); - d_set = calloc(d_count, sizeof(struct exfat_dentry)); - if (!d_set) + dcount = 2 + DIV_ROUND_UP(name_len, ENTRY_NAME_MAX); + dset = calloc(1, dcount * DENTRY_SIZE); + if (!dset) return -ENOMEM; - d_set[0].type = EXFAT_FILE; - d_set[0].dentry.file.num_ext = d_count - 1; - d_set[0].dentry.file.attr = cpu_to_le16(attr); + dset[0].type = EXFAT_FILE; + dset[0].dentry.file.num_ext = dcount - 1; + dset[0].dentry.file.attr = cpu_to_le16(attr); unix_time_to_exfat_time(time(NULL), &tz, &e_date, &e_time, &e_time_ms); - d_set[0].dentry.file.create_date = e_date; - d_set[0].dentry.file.create_time = e_time; - d_set[0].dentry.file.create_time_ms = e_time_ms; - d_set[0].dentry.file.create_tz = tz; - - d_set[0].dentry.file.modify_date = e_date; - d_set[0].dentry.file.modify_time = e_time; - d_set[0].dentry.file.modify_time_ms = e_time_ms; - d_set[0].dentry.file.modify_tz = tz; - - d_set[0].dentry.file.access_date = e_date; - d_set[0].dentry.file.access_time = e_time; - d_set[0].dentry.file.access_tz = tz; - - d_set[1].type = EXFAT_STREAM; - d_set[1].dentry.stream.flags = 0x01; - d_set[1].dentry.stream.name_len = (__u8)name_len; - d_set[1].dentry.stream.name_hash = - cpu_to_le16(exfat_calc_name_chksum(exfat, utf16_name, name_len)); - - for (i = 2; i < d_count; i++) { - d_set[i].type = EXFAT_NAME; - memcpy(d_set[i].dentry.name.unicode_0_14, - utf16_name + (i - 2) * ENTRY_NAME_MAX * 2, + dset[0].dentry.file.create_date = e_date; + dset[0].dentry.file.create_time = e_time; + dset[0].dentry.file.create_time_ms = e_time_ms; + dset[0].dentry.file.create_tz = tz; + + dset[0].dentry.file.modify_date = e_date; + dset[0].dentry.file.modify_time = e_time; + dset[0].dentry.file.modify_time_ms = e_time_ms; + dset[0].dentry.file.modify_tz = tz; + + dset[0].dentry.file.access_date = e_date; + dset[0].dentry.file.access_time = e_time; + dset[0].dentry.file.access_tz = tz; + + dset[1].type = EXFAT_STREAM; + dset[1].dentry.stream.flags = 0x01; + dset[1].dentry.stream.name_len = (__u8)name_len; + dset[1].dentry.stream.name_hash = + cpu_to_le16(exfat_calc_name_hash(exfat, utf16_name, name_len)); + + for (i = 2; i < dcount; i++) { + dset[i].type = EXFAT_NAME; + memcpy(dset[i].dentry.name.unicode_0_14, + utf16_name + (i - 2) * ENTRY_NAME_MAX, ENTRY_NAME_MAX * 2); } - *dentry_set = d_set; - *dentry_count = d_count; + dset[0].dentry.file.checksum = + cpu_to_le16(calc_dentry_set_checksum(dset, dcount)); + + *dentry_set = dset; + *dentry_count = dcount; return 0; } -int exfat_create_file(struct exfat *exfat, struct exfat_inode *parent, - const char *name, unsigned short attr) +int exfat_update_file_dentry_set(struct exfat *exfat, + struct exfat_dentry *dset, int dcount, + const char *name, + clus_t start_clu, clus_t ccount) { - struct exfat_dentry *dentry_set; - int dentry_count; - int retval; - unsigned int clu, offset, set_len; - struct exfat_lookup_filter filter; + int i, name_len; + __le16 utf16_name[PATH_MAX + 2]; - retval = exfat_lookup_file(exfat, parent, name, - &filter); - if (retval == 0) { - struct exfat_dentry *dentry; + if (dset[0].type != EXFAT_FILE || dcount < MIN_FILE_DENTRIES) + return -EINVAL; + + if (name) { + name_len = (int)exfat_utf16_enc(name, + utf16_name, sizeof(utf16_name)); + if (name_len < 0) + return name_len; + + name_len /= 2; + if (dcount != 2 + DIV_ROUND_UP(name_len, ENTRY_NAME_MAX)) + return -EINVAL; + + dset[1].dentry.stream.name_len = (__u8)name_len; + dset[1].dentry.stream.name_hash = + exfat_calc_name_hash(exfat, utf16_name, name_len); + + for (i = 2; i < dcount; i++) { + dset[i].type = EXFAT_NAME; + memcpy(dset[i].dentry.name.unicode_0_14, + utf16_name + (i - 2) * ENTRY_NAME_MAX, + ENTRY_NAME_MAX * 2); + } + } - dentry = filter.out.dentry_set; - if ((le16_to_cpu(dentry->dentry.file.attr) & attr) != attr) - retval = -EEXIST; + dset[1].dentry.stream.valid_size = cpu_to_le64(ccount * exfat->clus_size); + dset[1].dentry.stream.size = cpu_to_le64(ccount * exfat->clus_size); + if (start_clu) + dset[1].dentry.stream.start_clu = cpu_to_le32(start_clu); - free(filter.out.dentry_set); - return retval; + dset[0].dentry.file.checksum = + cpu_to_le16(calc_dentry_set_checksum(dset, dcount)); + return 0; +} + +static int find_free_cluster(struct exfat *exfat, + clus_t start, clus_t *new_clu) +{ + clus_t end = le32_to_cpu(exfat->bs->bsx.clu_count) + + EXFAT_FIRST_CLUSTER; + + if (!exfat_heap_clus(exfat, start)) + return -EINVAL; + + while (start < end) { + if (exfat_bitmap_find_zero(exfat, exfat->alloc_bitmap, + start, new_clu)) + break; + if (!exfat_bitmap_get(exfat->disk_bitmap, *new_clu)) + return 0; + start = *new_clu + 1; } - retval = exfat_build_file_dentry_set(exfat, name, attr, - &dentry_set, &dentry_count); - if (retval < 0) - return retval; + end = start; + start = EXFAT_FIRST_CLUSTER; + while (start < end) { + if (exfat_bitmap_find_zero(exfat, exfat->alloc_bitmap, + start, new_clu)) + goto out_nospc; + if (!exfat_bitmap_get(exfat->disk_bitmap, *new_clu)) + return 0; + start = *new_clu + 1; + } - retval = exfat_o2c(exfat, filter.out.dev_offset, &clu, &offset); - if (retval) - goto out; +out_nospc: + *new_clu = EXFAT_EOF_CLUSTER; + return -ENOSPC; +} - set_len = dentry_count * sizeof(struct exfat_dentry); - if (offset + set_len > exfat->clus_size) { - retval = -ENOSPC; - goto out; +static int exfat_map_cluster(struct exfat *exfat, struct exfat_inode *inode, + off_t file_off, clus_t *mapped_clu) +{ + clus_t clu, next, count, last_count; + + if (!exfat_heap_clus(exfat, inode->first_clus)) + return -EINVAL; + + clu = inode->first_clus; + next = EXFAT_EOF_CLUSTER; + count = 1; + if (file_off == EOF) + last_count = DIV_ROUND_UP(inode->size, exfat->clus_size); + else + last_count = file_off / exfat->clus_size + 1; + + while (true) { + if (count * exfat->clus_size > inode->size) + return -EINVAL; + + if (count == last_count) { + *mapped_clu = clu; + return 0; + } + + if (exfat_get_inode_next_clus(exfat, inode, clu, &next)) + return -EINVAL; + + if (!exfat_heap_clus(exfat, clu)) + return -EINVAL; + + clu = next; + count++; + } + return -EINVAL; +} + +static int exfat_write_dentry_set(struct exfat *exfat, + struct exfat_dentry *dset, int dcount, + off_t dev_off, off_t *next_dev_off) +{ + clus_t clus; + unsigned int clus_off, dent_len, first_half_len, sec_half_len; + off_t first_half_off, sec_half_off = 0; + + if (exfat_o2c(exfat, dev_off, &clus, &clus_off)) + return -ERANGE; + + dent_len = dcount * DENTRY_SIZE; + first_half_len = MIN(dent_len, exfat->clus_size - clus_off); + sec_half_len = dent_len - first_half_len; + + first_half_off = dev_off; + if (sec_half_len) { + clus_t next_clus; + + if (exfat_get_next_clus(exfat, clus, &next_clus)) + return -EIO; + if (!exfat_heap_clus(exfat, next_clus)) + return -EINVAL; + sec_half_off = exfat_c2o(exfat, next_clus); + } + + if (exfat_write(exfat->blk_dev->dev_fd, dset, first_half_len, + first_half_off) != (ssize_t)first_half_len) + return -EIO; + + if (sec_half_len) { + dset = (struct exfat_dentry *)((char *)dset + first_half_len); + if (exfat_write(exfat->blk_dev->dev_fd, dset, sec_half_len, + sec_half_off) != (ssize_t)sec_half_len) + return -EIO; + } + + if (next_dev_off) { + if (sec_half_len) + *next_dev_off = sec_half_off + sec_half_len; + else + *next_dev_off = first_half_off + first_half_len; } + return 0; +} + +static int exfat_alloc_cluster(struct exfat *exfat, struct exfat_inode *inode, + clus_t *new_clu) +{ + clus_t last_clu; + int err; + bool need_dset = inode != exfat->root; - if (exfat_write(exfat->blk_dev->dev_fd, dentry_set, - set_len, filter.out.dev_offset) != - (ssize_t)set_len) { - retval = -EIO; + if ((need_dset && !inode->dentry_set) || inode->is_contiguous) + return -EINVAL; + + err = find_free_cluster(exfat, exfat->start_clu, new_clu); + if (err) { + exfat->start_clu = EXFAT_FIRST_CLUSTER; + exfat_err("failed to find an free cluster\n"); + return -ENOSPC; + } + exfat->start_clu = *new_clu; + + if (exfat_set_fat(exfat, *new_clu, EXFAT_EOF_CLUSTER)) + return -EIO; + + /* zero out the new cluster */ + if (exfat_write(exfat->blk_dev->dev_fd, exfat->zero_cluster, + exfat->clus_size, exfat_c2o(exfat, *new_clu)) != + (ssize_t)exfat->clus_size) { + exfat_err("failed to fill new cluster with zeroes\n"); + return -EIO; + } + + if (inode->size) { + err = exfat_map_cluster(exfat, inode, EOF, &last_clu); + if (err) { + exfat_err("failed to get the last cluster\n"); + return err; + } + + if (exfat_set_fat(exfat, last_clu, *new_clu)) + return -EIO; + + if (need_dset) { + err = exfat_update_file_dentry_set(exfat, + inode->dentry_set, + inode->dentry_count, + NULL, 0, + DIV_ROUND_UP(inode->size, + exfat->clus_size) + 1); + if (err) + return -EINVAL; + } + } else { + if (need_dset) { + err = exfat_update_file_dentry_set(exfat, + inode->dentry_set, + inode->dentry_count, + NULL, *new_clu, 1); + if (err) + return -EINVAL; + } + } + + if (need_dset && exfat_write_dentry_set(exfat, inode->dentry_set, + inode->dentry_count, + inode->dev_offset, NULL)) + return -EIO; + + exfat_bitmap_set(exfat->alloc_bitmap, *new_clu); + if (inode->size == 0) + inode->first_clus = *new_clu; + inode->size += exfat->clus_size; + return 0; +} + +int exfat_add_dentry_set(struct exfat *exfat, struct exfat_dentry_loc *loc, + struct exfat_dentry *dset, int dcount, + bool need_next_loc) +{ + struct exfat_inode *parent = loc->parent; + off_t dev_off, next_dev_off; + + if (parent->is_contiguous || + (uint64_t)loc->file_offset > parent->size || + (unsigned int)dcount * DENTRY_SIZE > exfat->clus_size) + return -EINVAL; + + dev_off = loc->dev_offset; + if ((uint64_t)loc->file_offset + dcount * DENTRY_SIZE > parent->size) { + clus_t new_clus; + + if (exfat_alloc_cluster(exfat, parent, &new_clus)) + return -EIO; + if ((uint64_t)loc->file_offset == parent->size - exfat->clus_size) + dev_off = exfat_c2o(exfat, new_clus); + } + + if (exfat_write_dentry_set(exfat, dset, dcount, dev_off, &next_dev_off)) + return -EIO; + + if (need_next_loc) { + loc->file_offset += dcount * DENTRY_SIZE; + loc->dev_offset = next_dev_off; + } + return 0; +} + +int exfat_create_file(struct exfat *exfat, struct exfat_inode *parent, + const char *name, unsigned short attr) +{ + struct exfat_dentry *dset; + int err, dcount; + struct exfat_lookup_filter filter; + struct exfat_dentry_loc loc; + + err = exfat_lookup_file(exfat, parent, name, &filter); + if (err == 0) { + dset = filter.out.dentry_set; + dcount = filter.out.dentry_count; + if ((le16_to_cpu(dset->dentry.file.attr) & attr) != attr) + err = -EEXIST; goto out; } + + err = exfat_build_file_dentry_set(exfat, name, attr, + &dset, &dcount); + if (err) + return err; + + loc.parent = parent; + loc.file_offset = filter.out.file_offset; + loc.dev_offset = filter.out.dev_offset; + err = exfat_add_dentry_set(exfat, &loc, dset, dcount, false); out: - free(dentry_set); - return 0; + free(dset); + return err; } diff --git a/lib/exfat_fs.c b/lib/exfat_fs.c index 9347614..c5ebe8b 100644 --- a/lib/exfat_fs.c +++ b/lib/exfat_fs.c @@ -31,13 +31,14 @@ struct exfat_inode *exfat_alloc_inode(__u16 attr) INIT_LIST_HEAD(&node->sibling); INIT_LIST_HEAD(&node->list); - node->last_pclus = EXFAT_EOF_CLUSTER; node->attr = attr; return node; } void exfat_free_inode(struct exfat_inode *node) { + if (node->dentry_set) + free(node->dentry_set); free(node); } @@ -108,6 +109,8 @@ void exfat_free_exfat(struct exfat *exfat) free(exfat->alloc_bitmap); if (exfat->disk_bitmap) free(exfat->disk_bitmap); + if (exfat->zero_cluster) + free(exfat->zero_cluster); free(exfat); } } @@ -141,6 +144,13 @@ struct exfat *exfat_alloc_exfat(struct exfat_blk_dev *blk_dev, struct pbr *bs) goto err; } + exfat->zero_cluster = calloc(1, exfat->clus_size); + if (!exfat->zero_cluster) { + exfat_err("failed to allocate a zero-filled cluster buffer\n"); + goto err; + } + + exfat->start_clu = EXFAT_FIRST_CLUSTER; return exfat; err: exfat_free_exfat(exfat); diff --git a/lib/libexfat.c b/lib/libexfat.c index 2f8c78e..b093eb6 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -39,6 +39,23 @@ void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, } } +int exfat_bitmap_find_zero(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next) +{ + clus_t last_clu; + + last_clu = le32_to_cpu(exfat->bs->bsx.clu_count) + + EXFAT_FIRST_CLUSTER; + while (start_clu < last_clu) { + if (!exfat_bitmap_get(bmap, start_clu)) { + *next = start_clu; + return 0; + } + start_clu++; + } + return 1; +} + wchar_t exfat_bad_char(wchar_t w) { return (w < 0x0020) -- cgit v1.2.3 From 30bde40a04c1f2507fd11e6461a88fedce0d8922 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Fri, 4 Jun 2021 10:13:44 +0900 Subject: fsck: change repair message format Add the device offset of a corrupted directory entry set to the end of the repair message. And fix the path of root directory, "/" isn't shown in error messages Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 13 ++++++++----- lib/exfat_fs.c | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 537df66..d40a470 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -90,9 +90,10 @@ static void usage(char *name) exfat_resolve_path_parent(&path_resolve_ctx, \ (iter)->parent, inode); \ exfat_repair_ask(&exfat_fsck, code, \ - "ERROR: %s: " fmt, \ - path_resolve_ctx.local_path, \ - ##__VA_ARGS__); \ + "ERROR: %s: " fmt " at %#" PRIx64, \ + path_resolve_ctx.local_path, \ + ##__VA_ARGS__, \ + exfat_de_iter_device_offset(iter)); \ }) static int check_clus_chain(struct exfat_de_iter *de_iter, @@ -115,7 +116,9 @@ static int check_clus_chain(struct exfat_de_iter *de_iter, if ((node->size == 0 && node->first_clus != EXFAT_FREE_CLUSTER) || (node->size > 0 && !exfat_heap_clus(exfat, node->first_clus))) { if (repair_file_ask(de_iter, node, - ER_FILE_FIRST_CLUS, "first cluster is wrong")) + ER_FILE_FIRST_CLUS, + "size %#" PRIx64 ", but the first cluster %#x", + node->size, node->first_clus)) goto truncate_file; else return -EINVAL; @@ -575,7 +578,7 @@ static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node) if (node->size == 0 && node->is_contiguous) { if (repair_file_ask(iter, node, ER_FILE_ZERO_NOFAT, - "empty, but has no Fat chain\n")) { + "empty, but has no Fat chain")) { exfat_de_iter_get_dirty(iter, 1, &dentry); dentry->stream_flags &= ~EXFAT_SF_CONTIGUOUS; ret = 1; diff --git a/lib/exfat_fs.c b/lib/exfat_fs.c index c5ebe8b..e961547 100644 --- a/lib/exfat_fs.c +++ b/lib/exfat_fs.c @@ -263,7 +263,7 @@ int exfat_resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child) utf16_path++; } - if (depth > 0) + if (depth > 1) utf16_path--; memcpy((char *)utf16_path, &utf16_null, sizeof(utf16_null)); utf16_path++; -- cgit v1.2.3 From d6c8a3fa08f0b1d46bb1026aa2fd65a62cbabcd0 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 3 May 2021 16:12:07 +0900 Subject: fsck: Add rescue option to assign orphaned cluster to files If the "s" option is given, fsck tries to create files in LOST+FOUND and assigns orphaned clusters which are marked as allocated but does not belong to any files to theses files. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 307 +++++++++++++++++++++++++++++++++-------------------- fsck/fsck.h | 1 + include/exfat_fs.h | 3 +- include/libexfat.h | 2 + lib/exfat_fs.c | 30 ++++-- lib/libexfat.c | 21 +++- 6 files changed, 234 insertions(+), 130 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index d40a470..1e1d84d 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -52,6 +52,7 @@ static struct option opts[] = { {"repair-yes", no_argument, NULL, 'y' }, {"repair-no", no_argument, NULL, 'n' }, {"repair-auto", no_argument, NULL, 'p' }, + {"rescue", no_argument, NULL, 's' }, {"version", no_argument, NULL, 'V' }, {"verbose", no_argument, NULL, 'v' }, {"help", no_argument, NULL, 'h' }, @@ -69,6 +70,7 @@ static void usage(char *name) fprintf(stderr, "\t-p | --repair-auto Repair automatically\n"); fprintf(stderr, "\t-a Repair automatically\n"); fprintf(stderr, "\t-b | --ignore-bad-fs Try to recover even if exfat is not found\n"); + fprintf(stderr, "\t-s | --rescue Assign orphaned clusters to files\n"); fprintf(stderr, "\t-V | --version Show version\n"); fprintf(stderr, "\t-v | --verbose Print debug\n"); fprintf(stderr, "\t-h | --help Show help\n"); @@ -975,126 +977,45 @@ err: return ret; } -static int write_dirty_fat(struct exfat_fsck *fsck) +/* write bitmap segments for clusters which are marked + * as free, but allocated to files. + */ +static int write_bitmap(struct exfat_fsck *fsck) { struct exfat *exfat = fsck->exfat; - struct buffer_desc *bd; - off_t offset; - ssize_t len; - size_t read_size, write_size; - clus_t clus, last_clus, clus_count, i; - unsigned int idx; - - clus = 0; - last_clus = le32_to_cpu(exfat->bs->bsx.clu_count) + 2; - bd = fsck->buffer_desc; - idx = 0; - offset = le32_to_cpu(exfat->bs->bsx.fat_offset) * - exfat->sect_size; - read_size = exfat->clus_size; - write_size = exfat->sect_size; - - while (clus < last_clus) { - clus_count = MIN(read_size / sizeof(clus_t), last_clus - clus); - len = exfat_read(exfat->blk_dev->dev_fd, bd[idx].buffer, - clus_count * sizeof(clus_t), offset); - if (len != (ssize_t)(sizeof(clus_t) * clus_count)) { - exfat_err("failed to read fat entries, %zd\n", len); - return -EIO; - } + bitmap_t *disk_b, *alloc_b, *ohead_b; + off_t dev_offset; + unsigned int i, bitmap_bytes, byte_offset, write_bytes; - /* TODO: read ahead */ - - for (i = clus ? clus : EXFAT_FIRST_CLUSTER; - i < clus + clus_count; i++) { - if (!exfat_bitmap_get(exfat->alloc_bitmap, i) && - ((clus_t *)bd[idx].buffer)[i - clus] != - EXFAT_FREE_CLUSTER) { - ((clus_t *)bd[idx].buffer)[i - clus] = - EXFAT_FREE_CLUSTER; - bd[idx].dirty[(i - clus) / - (write_size / sizeof(clus_t))] = true; - } - } + dev_offset = exfat_c2o(exfat, exfat->disk_bitmap_clus); + bitmap_bytes = EXFAT_BITMAP_SIZE(le32_to_cpu(exfat->bs->bsx.clu_count)); - for (i = 0; i < read_size; i += write_size) { - if (bd[idx].dirty[i / write_size]) { - if (exfat_write(exfat->blk_dev->dev_fd, - &bd[idx].buffer[i], write_size, - offset + i) != - (ssize_t)write_size) { - exfat_err("failed to write " - "fat entries\n"); - return -EIO; + disk_b = (bitmap_t *)exfat->disk_bitmap; + alloc_b = (bitmap_t *)exfat->alloc_bitmap; + ohead_b = (bitmap_t *)exfat->ohead_bitmap; - } - bd[idx].dirty[i / write_size] = false; - } + for (i = 0; i < bitmap_bytes / sizeof(bitmap_t); i++) + ohead_b[i] = alloc_b[i] | disk_b[i]; + + i = 0; + while (i < bitmap_bytes / sizeof(bitmap_t)) { + if (ohead_b[i] == disk_b[i]) { + i++; + continue; } - idx ^= 0x01; - clus = clus + clus_count; - offset += len; - } - return 0; -} + byte_offset = ((i * sizeof(bitmap_t)) / 512) * 512; + write_bytes = MIN(512, bitmap_bytes - byte_offset); -static int write_dirty_bitmap(struct exfat_fsck *fsck) -{ - struct exfat *exfat = fsck->exfat; - struct buffer_desc *bd; - off_t offset, last_offset, bitmap_offset; - ssize_t len; - ssize_t read_size, write_size, i, size; - int idx; - - offset = exfat_c2o(exfat, exfat->disk_bitmap_clus); - last_offset = offset + exfat->disk_bitmap_size; - bitmap_offset = 0; - read_size = exfat->clus_size; - write_size = exfat->sect_size; - - bd = fsck->buffer_desc; - idx = 0; - - while (offset < last_offset) { - len = MIN(read_size, last_offset - offset); - if (exfat_read(exfat->blk_dev->dev_fd, bd[idx].buffer, - len, offset) != (ssize_t)len) + if (exfat_write(exfat->blk_dev->dev_fd, + (char *)ohead_b + byte_offset, write_bytes, + dev_offset + byte_offset) != (ssize_t)write_bytes) return -EIO; - /* TODO: read-ahead */ - - for (i = 0; i < len; i += write_size) { - size = MIN(write_size, len - i); - if (memcmp(&bd[idx].buffer[i], - exfat->alloc_bitmap + bitmap_offset + i, - size)) { - if (exfat_write(exfat->blk_dev->dev_fd, - exfat->alloc_bitmap + bitmap_offset + i, - size, offset + i) != size) - return -EIO; - } - } - - idx ^= 0x01; - offset += len; - bitmap_offset += len; + i = (byte_offset + write_bytes) / sizeof(bitmap_t); } return 0; -} -static int reclaim_free_clusters(struct exfat_fsck *fsck) -{ - if (write_dirty_fat(fsck)) { - exfat_err("failed to write fat entries\n"); - return -EIO; - } - if (write_dirty_bitmap(fsck)) { - exfat_err("failed to write bitmap\n"); - return -EIO; - } - return 0; } /* @@ -1143,9 +1064,6 @@ static int exfat_filesystem_check(struct exfat_fsck *fsck) } out: exfat_free_dir_list(exfat); - exfat->root = NULL; - if (fsck->dirty_fat && reclaim_free_clusters(fsck)) - return -EIO; return ret; } @@ -1153,12 +1071,11 @@ static int exfat_root_dir_check(struct exfat *exfat) { struct exfat_inode *root; clus_t clus_count; + int err; root = exfat_alloc_inode(ATTR_SUBDIR); - if (!root) { - exfat_err("failed to allocate memory\n"); + if (!root) return -ENOMEM; - } root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster); if (!root_get_clus_count(exfat, root, &clus_count)) { @@ -1176,18 +1093,139 @@ static int exfat_root_dir_check(struct exfat *exfat) if (read_volume_label(exfat)) exfat_err("failed to read volume label\n"); - if (read_bitmap(exfat)) { + err = read_bitmap(exfat); + if (err) { exfat_err("failed to read bitmap\n"); return -EINVAL; } - if (read_upcase_table(exfat)) { + err = read_upcase_table(exfat); + if (err) { exfat_err("failed to read upcase table\n"); return -EINVAL; } + + root->dev_offset = 0; + err = exfat_build_file_dentry_set(exfat, " ", ATTR_SUBDIR, + &root->dentry_set, &root->dentry_count); + if (err) { + exfat_free_inode(root); + return -ENOMEM; + } + return 0; +} + +static int read_lostfound(struct exfat *exfat, struct exfat_inode **lostfound) +{ + struct exfat_lookup_filter filter; + struct exfat_inode *inode; + int err; + + err = exfat_lookup_file(exfat, exfat->root, "LOST+FOUND", &filter); + if (err) + return err; + + inode = exfat_alloc_inode(ATTR_SUBDIR); + if (!inode) { + free(filter.out.dentry_set); + return -ENOMEM; + } + + inode->dentry_set = filter.out.dentry_set; + inode->dentry_count = filter.out.dentry_count; + inode->dev_offset = filter.out.dev_offset; + + inode->first_clus = + le32_to_cpu(filter.out.dentry_set[1].dentry.stream.start_clu); + inode->size = + le64_to_cpu(filter.out.dentry_set[1].dentry.stream.size); + + *lostfound = inode; return 0; } +/* Create temporary files under LOST+FOUND and assign orphan + * chains of clusters to these files. + */ +static int rescue_orphan_clusters(struct exfat_fsck *fsck) +{ + struct exfat *exfat = fsck->exfat; + struct exfat_inode *lostfound; + bitmap_t *disk_b, *alloc_b, *ohead_b; + struct exfat_dentry *dset; + clus_t clu_count, clu, s_clu, e_clu; + int err, dcount; + unsigned int i; + char name[] = "FILE0000000.CHK"; + struct exfat_dentry_loc loc; + struct exfat_lookup_filter lf = { + .in.type = EXFAT_INVAL, + .in.filter = NULL, + }; + + err = read_lostfound(exfat, &lostfound); + if (err) { + exfat_err("failed to find LOST+FOUND\n"); + return err; + } + + /* get the last empty region of LOST+FOUND */ + err = exfat_lookup_dentry_set(exfat, lostfound, &lf); + if (err && err != EOF) { + exfat_err("failed to find the last empty slot in LOST+FOUND\n"); + goto out; + } + + loc.parent = lostfound; + loc.file_offset = lf.out.file_offset; + loc.dev_offset = lf.out.dev_offset; + + /* build a template dentry set */ + err = exfat_build_file_dentry_set(exfat, name, 0, &dset, &dcount); + if (err) { + exfat_err("failed to create a temporary file in LOST+FOUNDn"); + goto out; + } + dset[1].dentry.stream.flags |= EXFAT_SF_CONTIGUOUS; + + clu_count = le32_to_cpu(exfat->bs->bsx.clu_count); + + /* find clusters which are not marked as free, but not allocated to + * any files. + */ + disk_b = (bitmap_t *)exfat->disk_bitmap; + alloc_b = (bitmap_t *)exfat->alloc_bitmap; + ohead_b = (bitmap_t *)exfat->ohead_bitmap; + for (i = 0; i < EXFAT_BITMAP_SIZE(clu_count) / sizeof(bitmap_t); i++) + ohead_b[i] = disk_b[i] & ~alloc_b[i]; + + /* create temporary files and allocate contiguous orphan clusters + * to each file. + */ + for (clu = EXFAT_FIRST_CLUSTER; clu < clu_count + EXFAT_FIRST_CLUSTER && + exfat_bitmap_find_one(exfat, exfat->ohead_bitmap, clu, &s_clu) == 0;) { + if (exfat_bitmap_find_zero(exfat, exfat->ohead_bitmap, s_clu, &e_clu)) + e_clu = clu_count + EXFAT_FIRST_CLUSTER; + clu = e_clu; + + snprintf(name, sizeof(name), "FILE%07d.CHK", + (unsigned int)(loc.file_offset >> 5)); + err = exfat_update_file_dentry_set(exfat, dset, dcount, + name, s_clu, e_clu - s_clu); + if (err) + continue; + err = exfat_add_dentry_set(exfat, &loc, dset, dcount, true); + if (err) + continue; + } + + free(dset); + err = 0; +out: + exfat_free_inode(lostfound); + return err; +} + static char *bytes_to_human_readable(size_t bytes) { static const char * const units[] = {"B", "KB", "MB", "GB", "TB", "PB"}; @@ -1251,7 +1289,7 @@ int main(int argc, char * const argv[]) exfat_err("failed to init locale/codeset\n"); opterr = 0; - while ((c = getopt_long(argc, argv, "arynpbVvh", opts, NULL)) != EOF) { + while ((c = getopt_long(argc, argv, "arynpbsVvh", opts, NULL)) != EOF) { switch (c) { case 'n': if (ui.options & FSCK_OPTS_REPAIR_ALL) @@ -1277,6 +1315,9 @@ int main(int argc, char * const argv[]) case 'b': ui.options |= FSCK_OPTS_IGNORE_BAD_FS_NAME; break; + case 's': + ui.options |= FSCK_OPTS_RESCUE_CLUS; + break; case 'V': version_only = true; break; @@ -1300,7 +1341,8 @@ int main(int argc, char * const argv[]) if (ui.options & FSCK_OPTS_REPAIR_WRITE) ui.ei.writeable = true; else { - if (ui.options & FSCK_OPTS_IGNORE_BAD_FS_NAME) + if (ui.options & (FSCK_OPTS_IGNORE_BAD_FS_NAME | + FSCK_OPTS_RESCUE_CLUS)) usage(argv[0]); ui.options |= FSCK_OPTS_REPAIR_NO; ui.ei.writeable = false; @@ -1348,11 +1390,42 @@ int main(int argc, char * const argv[]) goto out; } + if (exfat_fsck.options & FSCK_OPTS_RESCUE_CLUS) { + ret = exfat_create_file(exfat_fsck.exfat, + exfat_fsck.exfat->root, + "LOST+FOUND", + ATTR_SUBDIR); + if (ret) { + exfat_err("failed to create lost+found directory\n"); + goto out; + } + + if (fsync(exfat_fsck.exfat->blk_dev->dev_fd) != 0) { + ret = -EIO; + exfat_err("failed to sync()\n"); + goto out; + } + } + exfat_debug("verifying directory entries...\n"); ret = exfat_filesystem_check(&exfat_fsck); if (ret) goto out; + if (exfat_fsck.options & FSCK_OPTS_RESCUE_CLUS) { + rescue_orphan_clusters(&exfat_fsck); + exfat_fsck.dirty = true; + exfat_fsck.dirty_fat = true; + } + + if (exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE) { + ret = write_bitmap(&exfat_fsck); + if (ret) { + exfat_err("failed to write bitmap\n"); + goto out; + } + } + if (ui.ei.writeable && fsync(bd.dev_fd)) { exfat_err("failed to sync\n"); ret = -EIO; diff --git a/fsck/fsck.h b/fsck/fsck.h index 34a695a..53003f6 100644 --- a/fsck/fsck.h +++ b/fsck/fsck.h @@ -15,6 +15,7 @@ enum fsck_ui_options { FSCK_OPTS_REPAIR_WRITE = 0x0b, FSCK_OPTS_REPAIR_ALL = 0x0f, FSCK_OPTS_IGNORE_BAD_FS_NAME = 0x10, + FSCK_OPTS_RESCUE_CLUS = 0x20, }; struct exfat; diff --git a/include/exfat_fs.h b/include/exfat_fs.h index 47b3253..c1cfff0 100644 --- a/include/exfat_fs.h +++ b/include/exfat_fs.h @@ -36,8 +36,9 @@ struct exfat { clus_t clus_count; unsigned int clus_size; unsigned int sect_size; - char *alloc_bitmap; char *disk_bitmap; + char *alloc_bitmap; + char *ohead_bitmap; clus_t disk_bitmap_clus; unsigned int disk_bitmap_size; __u16 *upcase_table; diff --git a/include/libexfat.h b/include/libexfat.h index ec57eb1..b98f2b6 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -126,6 +126,8 @@ void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, clus_t start_clus, clus_t count); int exfat_bitmap_find_zero(struct exfat *exfat, char *bmap, clus_t start_clu, clus_t *next); +int exfat_bitmap_find_one(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next); void show_version(void); diff --git a/lib/exfat_fs.c b/lib/exfat_fs.c index e961547..baf2d12 100644 --- a/lib/exfat_fs.c +++ b/lib/exfat_fs.c @@ -72,21 +72,17 @@ void exfat_free_ancestors(struct exfat_inode *child) { struct exfat_inode *parent; - if (!list_empty(&child->children)) - return; - - do { - if (!(child->attr & ATTR_SUBDIR)) { - exfat_err("not directory.\n"); + while (child && list_empty(&child->children)) { + if (!child->parent || !(child->attr & ATTR_SUBDIR)) return; - } parent = child->parent; list_del(&child->sibling); exfat_free_inode(child); child = parent; - } while (child && list_empty(&child->children)); + } + return; } void exfat_free_dir_list(struct exfat *exfat) @@ -94,6 +90,8 @@ void exfat_free_dir_list(struct exfat *exfat) struct exfat_inode *dir, *i; list_for_each_entry_safe(dir, i, &exfat->dir_list, list) { + if (!dir->parent) + continue; exfat_free_file_children(dir); list_del(&dir->list); exfat_free_inode(dir); @@ -109,6 +107,12 @@ void exfat_free_exfat(struct exfat *exfat) free(exfat->alloc_bitmap); if (exfat->disk_bitmap) free(exfat->disk_bitmap); + if (exfat->ohead_bitmap) + free(exfat->ohead_bitmap); + if (exfat->upcase_table) + free(exfat->upcase_table); + if (exfat->root) + exfat_free_inode(exfat->root); if (exfat->zero_cluster) free(exfat->zero_cluster); free(exfat); @@ -138,7 +142,15 @@ struct exfat *exfat_alloc_exfat(struct exfat_blk_dev *blk_dev, struct pbr *bs) goto err; } - exfat->disk_bitmap = (char *)malloc(EXFAT_BITMAP_SIZE(exfat->clus_count)); + exfat->ohead_bitmap = + calloc(1, EXFAT_BITMAP_SIZE(exfat->clus_count)); + if (!exfat->ohead_bitmap) { + exfat_err("failed to allocate bitmap\n"); + goto err; + } + + exfat->disk_bitmap = + calloc(1, EXFAT_BITMAP_SIZE(exfat->clus_count)); if (!exfat->disk_bitmap) { exfat_err("failed to allocate bitmap\n"); goto err; diff --git a/lib/libexfat.c b/lib/libexfat.c index b093eb6..92e5e91 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -39,15 +39,16 @@ void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, } } -int exfat_bitmap_find_zero(struct exfat *exfat, char *bmap, - clus_t start_clu, clus_t *next) +static int exfat_bitmap_find_bit(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next, + int bit) { clus_t last_clu; last_clu = le32_to_cpu(exfat->bs->bsx.clu_count) + EXFAT_FIRST_CLUSTER; while (start_clu < last_clu) { - if (!exfat_bitmap_get(bmap, start_clu)) { + if (!!exfat_bitmap_get(bmap, start_clu) == bit) { *next = start_clu; return 0; } @@ -56,6 +57,20 @@ int exfat_bitmap_find_zero(struct exfat *exfat, char *bmap, return 1; } +int exfat_bitmap_find_zero(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next) +{ + return exfat_bitmap_find_bit(exfat, bmap, + start_clu, next, 0); +} + +int exfat_bitmap_find_one(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next) +{ + return exfat_bitmap_find_bit(exfat, bmap, + start_clu, next, 1); +} + wchar_t exfat_bad_char(wchar_t w) { return (w < 0x0020) -- cgit v1.2.3 From 1c3f9737f6d9a886397421779fa2291da5d0b4b3 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 3 May 2021 16:14:48 +0900 Subject: fsck: enable repairng the cluster chain which contains invalid clusters Enabling repairing the cluster chain which contains invalid clusters. Signed-off-by: Hyunchul Lee --- fsck/repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fsck/repair.c b/fsck/repair.c index 941c86b..a9125f6 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -39,7 +39,7 @@ static struct exfat_repair_problem problems[] = { {ER_BS_BOOT_REGION, 0, ERP_FIX}, {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_FIX}, {ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX}, - {ER_FILE_INVALID_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE}, + {ER_FILE_INVALID_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, {ER_FILE_FIRST_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE}, {ER_FILE_SMALLER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE}, {ER_FILE_LARGER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE}, -- cgit v1.2.3 From 316961824c2565d3277abc3a882b14e6fcaec98d Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 3 May 2021 16:17:07 +0900 Subject: fsck: enable reparing the cluster chain which contains a loop Enable repairing the cluster chain which contains a loop. Signed-off-by: Hyunchul Lee --- fsck/repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fsck/repair.c b/fsck/repair.c index a9125f6..0110c97 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -43,7 +43,7 @@ static struct exfat_repair_problem problems[] = { {ER_FILE_FIRST_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE}, {ER_FILE_SMALLER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE}, {ER_FILE_LARGER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE}, - {ER_FILE_DUPLICATED_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE}, + {ER_FILE_DUPLICATED_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, {ER_FILE_ZERO_NOFAT, ERF_PREEN_YES, ERP_FIX}, }; -- cgit v1.2.3 From 8ca6723727181d411338ad0b4dd1e237b9500426 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 3 May 2021 16:18:17 +0900 Subject: fsck: tests: add a testcase for cluster chains which contain invalid cluster numbers Add a testcase for cluster chains which contain invalid cluster numbers. Signed-off-by: Hyunchul Lee --- tests/bad_num_chain/config | 1 + tests/bad_num_chain/exfat.img.tar.xz | Bin 0 -> 4064 bytes 2 files changed, 1 insertion(+) create mode 100644 tests/bad_num_chain/config create mode 100644 tests/bad_num_chain/exfat.img.tar.xz diff --git a/tests/bad_num_chain/config b/tests/bad_num_chain/config new file mode 100644 index 0000000..f62cec1 --- /dev/null +++ b/tests/bad_num_chain/config @@ -0,0 +1 @@ +#OPTS: -s diff --git a/tests/bad_num_chain/exfat.img.tar.xz b/tests/bad_num_chain/exfat.img.tar.xz new file mode 100644 index 0000000..68b8605 Binary files /dev/null and b/tests/bad_num_chain/exfat.img.tar.xz differ -- cgit v1.2.3 From f4a2cab30534b2bc110e2985aff2659c2a1bdd76 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 3 May 2021 16:28:54 +0900 Subject: fsck: tests: add a testcase for cluster chains which containa a loop Add a testcase for cluster chains which contains a loop Signed-off-by: Hyunchul Lee --- tests/loop_chain/config | 1 + tests/loop_chain/exfat.img.tar.xz | Bin 0 -> 4052 bytes 2 files changed, 1 insertion(+) create mode 100644 tests/loop_chain/config create mode 100644 tests/loop_chain/exfat.img.tar.xz diff --git a/tests/loop_chain/config b/tests/loop_chain/config new file mode 100644 index 0000000..f62cec1 --- /dev/null +++ b/tests/loop_chain/config @@ -0,0 +1 @@ +#OPTS: -s diff --git a/tests/loop_chain/exfat.img.tar.xz b/tests/loop_chain/exfat.img.tar.xz new file mode 100644 index 0000000..c863cdd Binary files /dev/null and b/tests/loop_chain/exfat.img.tar.xz differ -- cgit v1.2.3 From 4e23746ea70e9d83528aa445a105089ede49ce13 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 6 May 2021 17:33:10 +0900 Subject: fsck: enable reparing the first cluster of file is bad Enable repairing the first cluster of file is bad. Signed-off-by: Hyunchul Lee --- fsck/repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fsck/repair.c b/fsck/repair.c index 0110c97..a99c92b 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -40,7 +40,7 @@ static struct exfat_repair_problem problems[] = { {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_FIX}, {ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX}, {ER_FILE_INVALID_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, - {ER_FILE_FIRST_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE}, + {ER_FILE_FIRST_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, {ER_FILE_SMALLER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE}, {ER_FILE_LARGER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE}, {ER_FILE_DUPLICATED_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, -- cgit v1.2.3 From 76319a58954e96f324b25a5c71cdcdcbba834b08 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 6 May 2021 17:06:28 +0900 Subject: fsck: tests: add a testcase for the bad first cluster of a file Add a testcase for the bad first cluster of a file. Signed-off-by: Hyunchul Lee --- tests/bad_first_clu/exfat.img.tar.xz | Bin 0 -> 3232 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/bad_first_clu/exfat.img.tar.xz diff --git a/tests/bad_first_clu/exfat.img.tar.xz b/tests/bad_first_clu/exfat.img.tar.xz new file mode 100644 index 0000000..3dc29ec Binary files /dev/null and b/tests/bad_first_clu/exfat.img.tar.xz differ -- cgit v1.2.3 From 0a8bd4e1e86e7ece2a5929ca75295a231b6efef3 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 6 May 2021 20:25:48 +0900 Subject: fsck: enable reparing the mismatch between size and chains Enable repairing the mismatch between size and chains. Signed-off-by: Hyunchul Lee --- fsck/repair.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fsck/repair.c b/fsck/repair.c index a99c92b..0721067 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -41,8 +41,8 @@ static struct exfat_repair_problem problems[] = { {ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX}, {ER_FILE_INVALID_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, {ER_FILE_FIRST_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, - {ER_FILE_SMALLER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE}, - {ER_FILE_LARGER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE}, + {ER_FILE_SMALLER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE}, + {ER_FILE_LARGER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE}, {ER_FILE_DUPLICATED_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, {ER_FILE_ZERO_NOFAT, ERF_PREEN_YES, ERP_FIX}, }; -- cgit v1.2.3 From 45d53d60512770bc88de0b9229773c02726258be Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 6 May 2021 20:27:33 +0900 Subject: fsck: tests: add a testcase for the mismatch between size and chain Add a testcase for the mismatch between size and chain. Signed-off-by: Hyunchul Lee --- tests/bad_file_size/exfat.img.tar.xz | Bin 0 -> 4052 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/bad_file_size/exfat.img.tar.xz diff --git a/tests/bad_file_size/exfat.img.tar.xz b/tests/bad_file_size/exfat.img.tar.xz new file mode 100644 index 0000000..df7ff14 Binary files /dev/null and b/tests/bad_file_size/exfat.img.tar.xz differ -- cgit v1.2.3 From 4b08e7a51ba52645f72e57933884ca361d531e66 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Sat, 8 May 2021 13:03:22 +0900 Subject: fsck: tests: add a testcase for the mismatch between bitmap and chains Add a testcase for the mismatch between bitmap and chains. Signed-off-by: Hyunchul Lee --- tests/bad_bitmap/exfat.img.tar.xz | Bin 0 -> 4056 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/bad_bitmap/exfat.img.tar.xz diff --git a/tests/bad_bitmap/exfat.img.tar.xz b/tests/bad_bitmap/exfat.img.tar.xz new file mode 100644 index 0000000..df09d10 Binary files /dev/null and b/tests/bad_bitmap/exfat.img.tar.xz differ -- cgit v1.2.3 From b8cf7571b5b8ec1f0fd9eb87f69add0b2597c0a6 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 13 May 2021 18:02:07 +0900 Subject: fsck: tests: add a testcase for two chains which contain a same cluster Add a testcase for two chains which contains a same cluster. Signed-off-by: Hyunchul Lee --- tests/duplicate_clu/exfat.img.tar.xz | Bin 0 -> 4048 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/duplicate_clu/exfat.img.tar.xz diff --git a/tests/duplicate_clu/exfat.img.tar.xz b/tests/duplicate_clu/exfat.img.tar.xz new file mode 100644 index 0000000..bf3fdd8 Binary files /dev/null and b/tests/duplicate_clu/exfat.img.tar.xz differ -- cgit v1.2.3 From 58a13e25e3f52833ed10fcaedd55b61c33507c8f Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Wed, 26 May 2021 16:43:39 +0900 Subject: fsck: repair unknown directory entries if unknown directory entries are found, Clear InUse bit in EntryType field. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 31 ++++++++++++++++++++++--------- fsck/repair.c | 1 + fsck/repair.h | 1 + 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 1e1d84d..9c2508f 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -89,9 +89,13 @@ static void usage(char *name) #define repair_file_ask(iter, inode, code, fmt, ...) \ ({ \ - exfat_resolve_path_parent(&path_resolve_ctx, \ - (iter)->parent, inode); \ - exfat_repair_ask(&exfat_fsck, code, \ + if (inode) \ + exfat_resolve_path_parent(&path_resolve_ctx, \ + (iter)->parent, inode); \ + else \ + exfat_resolve_path(&path_resolve_ctx, \ + (iter)->parent); \ + exfat_repair_ask(&exfat_fsck, code, \ "ERROR: %s: " fmt " at %#" PRIx64, \ path_resolve_ctx.local_path, \ ##__VA_ARGS__, \ @@ -951,18 +955,27 @@ static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir) } else exfat_free_inode(node); break; + case EXFAT_LAST: + goto out; case EXFAT_VOLUME: case EXFAT_BITMAP: case EXFAT_UPCASE: - break; - case EXFAT_LAST: - goto out; + if (dir == exfat->root) + break; + /* fallthrough */ default: if (IS_EXFAT_DELETED(dentry->type)) break; - exfat_err("unknown entry type. 0x%x\n", dentry->type); - ret = -EINVAL; - goto err; + if (repair_file_ask(de_iter, NULL, ER_DE_UNKNOWN, + "unknown entry type %#x at %07" PRIx64, + dentry->type, + exfat_de_iter_file_offset(de_iter))) { + struct exfat_dentry *dentry; + + exfat_de_iter_get_dirty(de_iter, 0, &dentry); + dentry->type &= EXFAT_DELETE; + } + break; } exfat_de_iter_advance(de_iter, dentry_count); diff --git a/fsck/repair.c b/fsck/repair.c index 0721067..97c3ed6 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -38,6 +38,7 @@ static struct exfat_repair_problem problems[] = { {ER_BS_CHECKSUM, ERF_PREEN_YES, ERP_FIX}, {ER_BS_BOOT_REGION, 0, ERP_FIX}, {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_FIX}, + {ER_DE_UNKNOWN, ERF_PREEN_YES, ERP_FIX}, {ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX}, {ER_FILE_INVALID_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, {ER_FILE_FIRST_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, diff --git a/fsck/repair.h b/fsck/repair.h index fbdc607..4f36547 100644 --- a/fsck/repair.h +++ b/fsck/repair.h @@ -8,6 +8,7 @@ #define ER_BS_CHECKSUM 0x00000001 #define ER_BS_BOOT_REGION 0x00000002 #define ER_DE_CHECKSUM 0x00001001 +#define ER_DE_UNKNOWN 0x00001002 #define ER_FILE_VALID_SIZE 0x00002001 #define ER_FILE_INVALID_CLUS 0x00002002 #define ER_FILE_FIRST_CLUS 0x00002003 -- cgit v1.2.3 From c8504184a01cb859975fe3ff996c1377eb2875d1 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Fri, 4 Jun 2021 11:01:37 +0900 Subject: fsck: repair corrupted dentry sets Repair the following corrupted dentry sets: * invalid File dentry's SetChecksum. * invalid File dentry's SecondaryCount. * Absent Stream Extension dentry. * mismatch between the file size and the number of clusters. * invalid NameHash. * mismatch between the length of name and NameLength. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 158 ++++++++++++++++++++++++++++++++++++++++++--------------- fsck/repair.c | 12 ++++- fsck/repair.h | 6 +++ lib/exfat_fs.c | 8 +-- 4 files changed, 137 insertions(+), 47 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 9c2508f..8512a65 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -603,36 +603,87 @@ static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node) checksum = file_calc_checksum(iter); exfat_de_iter_get(iter, 0, &dentry); if (checksum != le16_to_cpu(dentry->file_checksum)) { - if (repair_file_ask(iter, node, ER_DE_CHECKSUM, - "the checksum of a file is wrong")) { - exfat_de_iter_get_dirty(iter, 0, &dentry); - dentry->file_checksum = cpu_to_le16(checksum); - ret = 1; - } else - valid = false; + exfat_de_iter_get_dirty(iter, 0, &dentry); + dentry->file_checksum = cpu_to_le16(checksum); + ret = 1; } return valid ? ret : -EINVAL; } +static int check_name_dentry_set(struct exfat_de_iter *iter, + struct exfat_inode *inode) +{ + struct exfat_dentry *stream_de; + size_t name_len; + __u16 hash; + + exfat_de_iter_get(iter, 1, &stream_de); + + name_len = exfat_utf16_len(inode->name, NAME_BUFFER_SIZE); + if (stream_de->stream_name_len != name_len) { + if (repair_file_ask(iter, NULL, ER_DE_NAME_LEN, + "the name length of a file is wrong")) { + exfat_de_iter_get_dirty(iter, 1, &stream_de); + stream_de->stream_name_len = (__u8)name_len; + } else { + return -EINVAL; + } + } + + hash = exfat_calc_name_hash(iter->exfat, inode->name, (int)name_len); + if (cpu_to_le16(hash) != stream_de->stream_name_hash) { + if (repair_file_ask(iter, NULL, ER_DE_NAME_HASH, + "the name hash of a file is wrong")) { + exfat_de_iter_get_dirty(iter, 1, &stream_de); + stream_de->stream_name_hash = cpu_to_le16(hash); + } else { + return -EINVAL; + } + } + return 0; +} + static int read_file_dentry_set(struct exfat_de_iter *iter, struct exfat_inode **new_node, int *skip_dentries) { - struct exfat_dentry *file_de, *stream_de, *name_de; - struct exfat_inode *node; + struct exfat_dentry *file_de, *stream_de, *dentry; + struct exfat_inode *node = NULL; int i, ret; - - /* TODO: mtime, atime, ... */ + bool need_delete = false; + uint16_t checksum; ret = exfat_de_iter_get(iter, 0, &file_de); if (ret || file_de->type != EXFAT_FILE) { - exfat_err("failed to get file dentry. %d\n", ret); + exfat_err("failed to get file dentry\n"); return -EINVAL; } + + checksum = file_calc_checksum(iter); + if (checksum != le16_to_cpu(file_de->file_checksum)) { + if (repair_file_ask(iter, NULL, ER_DE_CHECKSUM, + "the checksum of a file is wrong")) + need_delete = true; + *skip_dentries = 1; + goto skip_dset; + } + + if (file_de->file_num_ext < 2) { + if (repair_file_ask(iter, NULL, ER_DE_SECONDARY_COUNT, + "a file has too few secondary count. %d", + file_de->file_num_ext)) + need_delete = true; + *skip_dentries = 1; + goto skip_dset; + } + ret = exfat_de_iter_get(iter, 1, &stream_de); if (ret || stream_de->type != EXFAT_STREAM) { - exfat_err("failed to get stream dentry. %d\n", ret); - return -EINVAL; + if (repair_file_ask(iter, NULL, ER_DE_STREAM, + "failed to get stream dentry")) + need_delete = true; + *skip_dentries = 2; + goto skip_dset; } *new_node = NULL; @@ -640,24 +691,28 @@ static int read_file_dentry_set(struct exfat_de_iter *iter, if (!node) return -ENOMEM; - if (file_de->file_num_ext < 2) { - exfat_err("too few secondary count. %d\n", - file_de->file_num_ext); - exfat_free_inode(node); - return -EINVAL; - } - for (i = 2; i <= file_de->file_num_ext; i++) { - ret = exfat_de_iter_get(iter, i, &name_de); - if (ret || name_de->type != EXFAT_NAME) { - exfat_err("failed to get name dentry. %d\n", ret); - ret = -EINVAL; - goto err; + ret = exfat_de_iter_get(iter, i, &dentry); + if (ret || dentry->type != EXFAT_NAME) { + if (i > 2 && repair_file_ask(iter, NULL, ER_DE_NAME, + "failed to get name dentry")) { + exfat_de_iter_get_dirty(iter, 0, &file_de); + file_de->file_num_ext = i - 1; + break; + } + *skip_dentries = i + 1; + goto skip_dset; } memcpy(node->name + - (i-2) * ENTRY_NAME_MAX, name_de->name_unicode, - sizeof(name_de->name_unicode)); + (i - 2) * ENTRY_NAME_MAX, dentry->name_unicode, + sizeof(dentry->name_unicode)); + } + + ret = check_name_dentry_set(iter, node); + if (ret) { + *skip_dentries = file_de->file_num_ext + 1; + goto skip_dset; } node->first_clus = le32_to_cpu(stream_de->stream_start_clu); @@ -666,27 +721,41 @@ static int read_file_dentry_set(struct exfat_de_iter *iter, node->size = le64_to_cpu(stream_de->stream_size); if (node->size < le64_to_cpu(stream_de->stream_valid_size)) { + *skip_dentries = file_de->file_num_ext + 1; if (repair_file_ask(iter, node, ER_FILE_VALID_SIZE, - "valid size %" PRIu64 " greater than size %" PRIu64, - le64_to_cpu(stream_de->stream_valid_size), - node->size)) { + "valid size %" PRIu64 " greater than size %" PRIu64, + le64_to_cpu(stream_de->stream_valid_size), + node->size)) { exfat_de_iter_get_dirty(iter, 1, &stream_de); stream_de->stream_valid_size = stream_de->stream_size; } else { - ret = -EINVAL; - goto err; + *skip_dentries = file_de->file_num_ext + 1; + goto skip_dset; } } *skip_dentries = (file_de->file_num_ext + 1); *new_node = node; return 0; -err: - *skip_dentries = 1; +skip_dset: + if (need_delete) { + exfat_de_iter_get_dirty(iter, 0, &dentry); + dentry->type &= EXFAT_DELETE; + } + for (i = 1; i < *skip_dentries; i++) { + exfat_de_iter_get(iter, i, &dentry); + if (dentry->type == EXFAT_FILE) + break; + if (need_delete) { + exfat_de_iter_get_dirty(iter, i, &dentry); + dentry->type &= EXFAT_DELETE; + } + } + *skip_dentries = i; *new_node = NULL; exfat_free_inode(node); - return ret; + return need_delete ? 1 : -EINVAL; } static int read_file(struct exfat_de_iter *de_iter, @@ -948,12 +1017,17 @@ static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir) exfat_stat.fixed_count++; } - if ((node->attr & ATTR_SUBDIR) && node->size) { - node->parent = dir; - list_add_tail(&node->sibling, &dir->children); - list_add_tail(&node->list, &exfat->dir_list); - } else - exfat_free_inode(node); + if (node) { + if ((node->attr & ATTR_SUBDIR) && node->size) { + node->parent = dir; + list_add_tail(&node->sibling, + &dir->children); + list_add_tail(&node->list, + &exfat->dir_list); + } else { + exfat_free_inode(node); + } + } break; case EXFAT_LAST: goto out; diff --git a/fsck/repair.c b/fsck/repair.c index 97c3ed6..65f4a9f 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -27,18 +27,26 @@ struct exfat_repair_problem { /* Prompt types */ #define ERP_FIX 0x00000001 #define ERP_TRUNCATE 0x00000002 +#define ERP_DELETE 0x00000003 static const char *prompts[] = { "Repair", "Fix", "Truncate", + "Delete", }; static struct exfat_repair_problem problems[] = { {ER_BS_CHECKSUM, ERF_PREEN_YES, ERP_FIX}, {ER_BS_BOOT_REGION, 0, ERP_FIX}, - {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_FIX}, - {ER_DE_UNKNOWN, ERF_PREEN_YES, ERP_FIX}, + {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_DELETE}, + {ER_DE_UNKNOWN, ERF_PREEN_YES, ERP_DELETE}, + {ER_DE_FILE, ERF_PREEN_YES, ERP_DELETE}, + {ER_DE_SECONDARY_COUNT, ERF_PREEN_YES, ERP_DELETE}, + {ER_DE_STREAM, ERF_PREEN_YES, ERP_DELETE}, + {ER_DE_NAME, ERF_PREEN_YES, ERP_DELETE}, + {ER_DE_NAME_HASH, ERF_PREEN_YES, ERP_FIX}, + {ER_DE_NAME_LEN, ERF_PREEN_YES, ERP_FIX}, {ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX}, {ER_FILE_INVALID_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, {ER_FILE_FIRST_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, diff --git a/fsck/repair.h b/fsck/repair.h index 4f36547..4e9a6bf 100644 --- a/fsck/repair.h +++ b/fsck/repair.h @@ -9,6 +9,12 @@ #define ER_BS_BOOT_REGION 0x00000002 #define ER_DE_CHECKSUM 0x00001001 #define ER_DE_UNKNOWN 0x00001002 +#define ER_DE_FILE 0x00001010 +#define ER_DE_SECONDARY_COUNT 0x00001011 +#define ER_DE_STREAM 0x00001020 +#define ER_DE_NAME 0x00001030 +#define ER_DE_NAME_HASH 0x00001031 +#define ER_DE_NAME_LEN 0x00001032 #define ER_FILE_VALID_SIZE 0x00002001 #define ER_FILE_INVALID_CLUS 0x00002002 #define ER_FILE_FIRST_CLUS 0x00002003 diff --git a/lib/exfat_fs.c b/lib/exfat_fs.c index baf2d12..41518b0 100644 --- a/lib/exfat_fs.c +++ b/lib/exfat_fs.c @@ -37,9 +37,11 @@ struct exfat_inode *exfat_alloc_inode(__u16 attr) void exfat_free_inode(struct exfat_inode *node) { - if (node->dentry_set) - free(node->dentry_set); - free(node); + if (node) { + if (node->dentry_set) + free(node->dentry_set); + free(node); + } } void exfat_free_children(struct exfat_inode *dir, bool file_only) -- cgit v1.2.3 From 5187a929a0bd59cd4adb31a4cd570cc15426514f Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Fri, 4 Jun 2021 11:06:42 +0900 Subject: fsck: tests: add testcase for repairing corrupted directory entries Add a testcase for repairing corrupted directory entries. Signed-off-by: Hyunchul Lee --- tests/bad_dentries/exfat.img.tar.xz | Bin 0 -> 9628 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/bad_dentries/exfat.img.tar.xz diff --git a/tests/bad_dentries/exfat.img.tar.xz b/tests/bad_dentries/exfat.img.tar.xz new file mode 100644 index 0000000..32643ca Binary files /dev/null and b/tests/bad_dentries/exfat.img.tar.xz differ -- cgit v1.2.3 From 0422e7b759355926edb10a634d2bf3e36774596c Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 7 Jun 2021 14:39:23 +0900 Subject: fsck: repair corrupted root if the cluster chain of root is broken or cyclic, repair the chain. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 61 +++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 8512a65..52b3f1b 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -240,36 +240,60 @@ truncate_file: return 1; } -static bool root_get_clus_count(struct exfat *exfat, struct exfat_inode *node, - clus_t *clus_count) +static int root_check_clus_chain(struct exfat *exfat, + struct exfat_inode *node, + clus_t *clus_count) { - clus_t clus; + clus_t clus, next, prev = EXFAT_EOF_CLUSTER; + + if (!exfat_heap_clus(exfat, node->first_clus)) + goto out_trunc; clus = node->first_clus; *clus_count = 0; do { - if (!exfat_heap_clus(exfat, clus)) { - exfat_err("/: bad cluster. 0x%x\n", clus); - return false; - } - if (exfat_bitmap_get(exfat->alloc_bitmap, clus)) { - exfat_err("/: cluster is already allocated, or " - "there is a loop in cluster chain\n"); - return false; + if (exfat_repair_ask(&exfat_fsck, + ER_FILE_DUPLICATED_CLUS, + "ERROR: the cluster chain of root is cyclic")) + goto out_trunc; + return -EINVAL; } exfat_bitmap_set(exfat->alloc_bitmap, clus); - if (exfat_get_inode_next_clus(exfat, node, clus, &clus) != 0) { - exfat_err("/: broken cluster chain\n"); - return false; + if (exfat_get_inode_next_clus(exfat, node, clus, &next)) { + exfat_err("ERROR: failed to read the fat entry of root"); + goto out_trunc; } + if (next != EXFAT_EOF_CLUSTER && !exfat_heap_clus(exfat, next)) { + if (exfat_repair_ask(&exfat_fsck, + ER_FILE_INVALID_CLUS, + "ERROR: the cluster chain of root is broken")) { + if (next != EXFAT_BAD_CLUSTER) { + prev = clus; + (*clus_count)++; + } + goto out_trunc; + } + return -EINVAL; + } + + prev = clus; + clus = next; (*clus_count)++; } while (clus != EXFAT_EOF_CLUSTER); - return true; + + return 0; +out_trunc: + if (!exfat_heap_clus(exfat, prev)) { + exfat_err("ERROR: the start cluster of root is wrong\n"); + return -EINVAL; + } + node->size = *clus_count * exfat->clus_size; + return exfat_set_fat(exfat, prev, EXFAT_EOF_CLUSTER); } static int boot_region_checksum(int dev_fd, @@ -1157,22 +1181,23 @@ out: static int exfat_root_dir_check(struct exfat *exfat) { struct exfat_inode *root; - clus_t clus_count; + clus_t clus_count = 0; int err; root = exfat_alloc_inode(ATTR_SUBDIR); if (!root) return -ENOMEM; + exfat->root = root; root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster); - if (!root_get_clus_count(exfat, root, &clus_count)) { + if (root_check_clus_chain(exfat, root, &clus_count)) { exfat_err("failed to follow the cluster chain of root\n"); exfat_free_inode(root); + exfat->root = NULL; return -EINVAL; } root->size = clus_count * exfat->clus_size; - exfat->root = root; exfat_stat.dir_count++; exfat_debug("root directory: start cluster[0x%x] size[0x%" PRIx64 "]\n", root->first_clus, root->size); -- cgit v1.2.3 From 2d1d8749a07c23adff48114d682eb2722712da7c Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Mon, 7 Jun 2021 14:40:55 +0900 Subject: fsck: tests: testcases for repairing root Add a testcases for repairing root. Signed-off-by: Hyunchul Lee --- tests/bad_root/exfat.img.tar.xz | Bin 0 -> 4528 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/bad_root/exfat.img.tar.xz diff --git a/tests/bad_root/exfat.img.tar.xz b/tests/bad_root/exfat.img.tar.xz new file mode 100644 index 0000000..de0066f Binary files /dev/null and b/tests/bad_root/exfat.img.tar.xz differ -- cgit v1.2.3 From a3da451b6235cde4401ab1783b0f950ba2a669ac Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Tue, 2 Aug 2022 08:31:03 +0900 Subject: tests: some modification of test_fsck.sh Add '-s' option to fsck option, And remove comparision between repaired image and expected image in test_fsck.sh Signed-off-by: Hyunchul Lee --- tests/file_invalid_clus/exfat.img.expected.xz | Bin 4048 -> 0 bytes .../large_file_invalid_clus/exfat.img.expected.xz | Bin 48520 -> 0 bytes tests/test_fsck.sh | 27 +++++++-------------- 3 files changed, 9 insertions(+), 18 deletions(-) delete mode 100644 tests/file_invalid_clus/exfat.img.expected.xz delete mode 100644 tests/large_file_invalid_clus/exfat.img.expected.xz diff --git a/tests/file_invalid_clus/exfat.img.expected.xz b/tests/file_invalid_clus/exfat.img.expected.xz deleted file mode 100644 index 08e992e..0000000 Binary files a/tests/file_invalid_clus/exfat.img.expected.xz and /dev/null differ diff --git a/tests/large_file_invalid_clus/exfat.img.expected.xz b/tests/large_file_invalid_clus/exfat.img.expected.xz deleted file mode 100644 index b31e710..0000000 Binary files a/tests/large_file_invalid_clus/exfat.img.expected.xz and /dev/null differ diff --git a/tests/test_fsck.sh b/tests/test_fsck.sh index 936db54..c53d8f3 100755 --- a/tests/test_fsck.sh +++ b/tests/test_fsck.sh @@ -4,13 +4,18 @@ TESTCASE_DIR=$1 NEED_LOOPDEV=$2 IMAGE_FILE=exfat.img FSCK_PROG=fsck.exfat -FSCK_OPTS=-y +FSCK_PROG_2=fsck.exfat +FSCK_OPTS="-y -s" PASS_COUNT=0 cleanup() { echo "" echo "Passed ${PASS_COUNT} of ${TEST_COUNT}" - exit + if [ ${PASS_COUNT} -ne ${TEST_COUNT} ]; then + exit 1 + else + exit 0 + fi } if [ $# -eq 0 ]; then @@ -40,7 +45,7 @@ for TESTCASE_DIR in $TESTCASE_DIRS; do # Run fsck for repair $FSCK_PROG $FSCK_OPTS "$DEV_FILE" - if [ $? -ne 1 ]; then + if [ $? -ne 1 ] && [ $? -ne 0 ]; then echo "" echo "Failed to repair ${TESTCASE_DIR}" if [ $NEED_LOOPDEV ]; then @@ -51,7 +56,7 @@ for TESTCASE_DIR in $TESTCASE_DIRS; do echo "" # Run fsck again - $FSCK_PROG -n "$DEV_FILE" + $FSCK_PROG_2 "$DEV_FILE" if [ $? -ne 0 ]; then echo "" echo "Failed, corrupted ${TESTCASE_DIR}" @@ -61,20 +66,6 @@ for TESTCASE_DIR in $TESTCASE_DIRS; do cleanup fi - if [ -e "${TESTCASE_DIR}/exfat.img.expected.xz" ]; then - EXPECTED_FILE=${IMAGE_FILE}.expected - unxz -cfk "${TESTCASE_DIR}/${EXPECTED_FILE}.xz" > "${EXPECTED_FILE}" - diff <(xxd "${IMAGE_FILE}") <(xxd "${EXPECTED_FILE}") - if [ $? -ne 0 ]; then - echo "" - echo "Failed ${TESTCASE_DIR}" - if [ $NEED_LOOPDEV ]; then - losetup -d "${DEV_FILE}" - fi - cleanup - fi - fi - echo "" echo "Passed ${TESTCASE_DIR}" PASS_COUNT=$((PASS_COUNT + 1)) -- cgit v1.2.3 From b9806defcc54b8ef37b024447d4d6debfcb89443 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 25 Aug 2022 16:42:30 +0900 Subject: exfatprogs: add exfat2img to dump a partition Add exfat2img to dump metadata of an exFAT filesystem. It dump boot sector, FAT, bitmap, all metadata which can reach from root directory. exfat2img -o sda1.dump /dev/sda1 exfat2img -o - /dev/sda1 | bzip2 > sda1.dump.bz When restoring a partition from a dump image generated from stdout, exfat2img should be used like the following: bzip2 -dc sda1.dump.bz2 | exfat2img -o /dev/sdb1 - Signed-off-by: Hyunchul Lee --- Android.bp | 1 + Makefile.am | 6 +- configure.ac | 1 + exfat2img/Makefile.am | 6 + exfat2img/exfat2img.c | 1059 +++++++++++++++++++++++++++++++++++++++++++++++++ manpages/exfat2img.8 | 31 ++ 6 files changed, 1102 insertions(+), 2 deletions(-) create mode 100644 exfat2img/Makefile.am create mode 100644 exfat2img/exfat2img.c create mode 100644 manpages/exfat2img.8 diff --git a/Android.bp b/Android.bp index 9f8716b..d0e2594 100644 --- a/Android.bp +++ b/Android.bp @@ -9,6 +9,7 @@ cc_library_headers { "tune", "label", "dump", + "exfat2img", ], } diff --git a/Makefile.am b/Makefile.am index 44f8635..3e36f55 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,7 +2,7 @@ ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = lib mkfs fsck tune label dump +SUBDIRS = lib mkfs fsck tune label dump exfat2img # manpages dist_man8_MANS = \ @@ -10,7 +10,8 @@ dist_man8_MANS = \ manpages/tune.exfat.8 \ manpages/mkfs.exfat.8 \ manpages/exfatlabel.8 \ - manpages/dump.exfat.8 + manpages/dump.exfat.8 \ + manpages/exfat2img.8 # other stuff EXTRA_DIST = \ @@ -22,4 +23,5 @@ EXTRA_DIST = \ fsck/Android.bp \ label/Android.bp \ dump/Android.bp \ + exfat2img/Android.bp \ README.md diff --git a/configure.ac b/configure.ac index 0544309..bc20774 100644 --- a/configure.ac +++ b/configure.ac @@ -32,6 +32,7 @@ AC_CONFIG_FILES([ tune/Makefile label/Makefile dump/Makefile + exfat2img/Makefile ]) AC_OUTPUT diff --git a/exfat2img/Makefile.am b/exfat2img/Makefile.am new file mode 100644 index 0000000..8b5cee7 --- /dev/null +++ b/exfat2img/Makefile.am @@ -0,0 +1,6 @@ +AM_CFLAGS = -Wall -Wextra -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common +exfat2img_LDADD = $(top_builddir)/lib/libexfat.a + +sbin_PROGRAMS = exfat2img + +exfat2img_SOURCES = exfat2img.c diff --git a/exfat2img/exfat2img.c b/exfat2img/exfat2img.c new file mode 100644 index 0000000..bad7639 --- /dev/null +++ b/exfat2img/exfat2img.c @@ -0,0 +1,1059 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Hyunchul Lee + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "exfat_ondisk.h" +#include "libexfat.h" +#include "exfat_fs.h" +#include "exfat_dir.h" + +#define EXFAT_MAX_UPCASE_CHARS 0x10000 + +struct exfat2img_hdr { + __le32 magic; + __le32 major_version; + __le32 minor_version; + __le32 data_offset; + __le32 heap_clus_offset; + __le32 cluster_size; + __le32 cluster_count; + __le32 reserved[20]; +} __packed; + +#define EI_MAGIC 0xB67598DB +#define EI_CC_PAYLOAD_LEN 4 + +enum { + EI_CC_INVALID, + EI_CC_COPY_1, + EI_CC_COPY_2, /* followed by cluster count(4-byte) */ + EI_CC_SKIP_1, + EI_CC_SKIP_2, /* followed by cluster count(4-byte) */ +}; + +struct exfat2img { + int out_fd; + bool is_stdout; + off_t stdout_offset; + bool save_cc; + struct exfat_blk_dev bdev; + struct exfat *exfat; + struct buffer_desc *dump_bdesc; + struct buffer_desc *scan_bdesc; + struct exfat_de_iter de_iter; +}; + +struct exfat_stat { + long dir_count; + long file_count; + long error_count; + uint64_t written_bytes; +}; + +static struct exfat2img_hdr ei_hdr; +static struct exfat2img ei; +static struct exfat_stat exfat_stat; +static struct path_resolve_ctx path_resolve_ctx; + +static struct option opts[] = { + {"output", required_argument, NULL, 'o' }, + {"version", no_argument, NULL, 'V' }, + {"help", no_argument, NULL, 'h' }, + {NULL, 0, NULL, 0 } +}; + +static void usage(const char *name) +{ + fprintf(stderr, "Usage: %s [image-file]\n", name); + fprintf(stderr, "\t-o | --output Specify destination file\n"); + fprintf(stderr, "\t-V | --version Show version\n"); + fprintf(stderr, "\t-h | --help Show help\n"); + exit(EXIT_FAILURE); +} + +#define ei_err(parent, inode, fmt, ...) \ +({ \ + exfat_resolve_path_parent(&path_resolve_ctx, \ + parent, inode); \ + exfat_err("ERROR: %s: " fmt, \ + path_resolve_ctx.local_path, \ + ##__VA_ARGS__); \ +}) + +static void free_exfat2img(struct exfat2img *ei) +{ + if (ei->exfat) + exfat_free_exfat(ei->exfat); + if (ei->dump_bdesc) + exfat_free_buffer(ei->dump_bdesc, 2); + if (ei->scan_bdesc) + exfat_free_buffer(ei->scan_bdesc, 2); + if (ei->out_fd) + close(ei->out_fd); + if (ei->bdev.dev_fd) + close(ei->bdev.dev_fd); +} + +static int create_exfat2img(struct exfat2img *ei, + struct pbr *bs, + const char *out_path) +{ + int err; + + ei->exfat = exfat_alloc_exfat(&ei->bdev, bs); + if (!ei->exfat) + return -ENOMEM; + + ei->dump_bdesc = exfat_alloc_buffer(2, + ei->exfat->clus_size, + ei->exfat->sect_size); + if (!ei->dump_bdesc) { + err = -ENOMEM; + goto err; + } + + ei->scan_bdesc = exfat_alloc_buffer(2, + ei->exfat->clus_size, + ei->exfat->sect_size); + if (!ei->scan_bdesc) { + err = -ENOMEM; + goto err; + } + + if (strcmp(out_path, "-")) { + ei->out_fd = open(out_path, O_CREAT | O_TRUNC | O_RDWR, 0664); + } else { + ei->is_stdout = true; + ei->out_fd = fileno(stdout); + ei->save_cc = true; + } + if (ei->out_fd < 0) { + exfat_err("failed to open %s: %s\n", out_path, + strerror(errno)); + err = -errno; + goto err; + } + + return 0; +err: + free_exfat2img(ei); + return err; +} + +static int read_boot_sect(struct exfat_blk_dev *bdev, struct pbr **bs) +{ + struct pbr *pbr; + int err = 0; + unsigned int sect_size, clu_size; + + pbr = malloc(sizeof(struct pbr)); + + if (exfat_read(bdev->dev_fd, pbr, sizeof(*pbr), 0) != + (ssize_t)sizeof(*pbr)) { + exfat_err("failed to read a boot sector\n"); + err = -EIO; + goto err; + } + + err = -EINVAL; + if (memcmp(pbr->bpb.oem_name, "EXFAT ", 8) != 0) { + exfat_err("failed to find exfat file system\n"); + goto err; + } + + sect_size = 1 << pbr->bsx.sect_size_bits; + clu_size = 1 << (pbr->bsx.sect_size_bits + + pbr->bsx.sect_per_clus_bits); + + if (sect_size < 512 || sect_size > 4 * KB) { + exfat_err("too small or big sector size: %d\n", + sect_size); + goto err; + } + + if (clu_size < sect_size || clu_size > 32 * MB) { + exfat_err("too small or big cluster size: %d\n", + clu_size); + goto err; + } + + *bs = pbr; + return 0; +err: + free(pbr); + return err; +} + +/** + * @end: excluded. + */ +static ssize_t dump_range(struct exfat2img *ei, off_t start, off_t end) +{ + struct exfat *exfat = ei->exfat; + size_t len, total_len = 0; + ssize_t ret; + + if (ei->is_stdout) { + unsigned int sc, sc_offset; + unsigned int ec, ec_offset; + + if (exfat_o2c(ei->exfat, start, &sc, &sc_offset) < 0) + return -ERANGE; + if (exfat_o2c(ei->exfat, end - 1, &ec, &ec_offset) < 0) + return -ERANGE; + exfat_bitmap_set_range(ei->exfat, exfat->alloc_bitmap, + sc, ec - sc + 1); + return end - start; + } + + while (start < end) { + len = (size_t)MIN(end - start, exfat->clus_size); + + ret = exfat_read(exfat->blk_dev->dev_fd, + ei->dump_bdesc[0].buffer, + len, start); + if (ret != (ssize_t)len) { + exfat_err("failed to read %llu bytes at %llu\n", + (unsigned long long)len, + (unsigned long long)start); + return -EIO; + } + + ret = pwrite(ei->out_fd, ei->dump_bdesc[0].buffer, + len, start); + if (ret != (ssize_t)len) { + exfat_err("failed to write %llu bytes at %llu\n", + (unsigned long long)len, + (unsigned long long)start); + return -EIO; + } + + start += len; + total_len += len; + exfat_stat.written_bytes += len; + } + return total_len; +} + +static int dump_sectors(struct exfat2img *ei, + off_t start_sect, + off_t end_sect_excl) +{ + struct exfat *exfat = ei->exfat; + off_t s, e; + + s = exfat_s2o(exfat, start_sect); + e = exfat_s2o(exfat, end_sect_excl); + return dump_range(ei, s, e) <= 0 ? -EIO : 0; +} + +static int dump_clusters(struct exfat2img *ei, + clus_t start_clus, + clus_t end_clus_excl) +{ + struct exfat *exfat = ei->exfat; + off_t s, e; + + s = exfat_c2o(exfat, start_clus); + e = exfat_c2o(exfat, end_clus_excl); + return dump_range(ei, s, e) <= 0 ? -EIO : 0; +} + +static int dump_directory(struct exfat2img *ei, + struct exfat_inode *inode, size_t size, + clus_t *out_clus_count) +{ + struct exfat *exfat = ei->exfat; + clus_t clus, possible_count; + uint64_t max_count; + size_t dump_size; + off_t start_off, end_off; + + if (size == 0) + return -EINVAL; + + if (!(inode->attr & ATTR_SUBDIR)) + return -EINVAL; + + clus = inode->first_clus; + *out_clus_count = 0; + max_count = DIV_ROUND_UP(inode->size, exfat->clus_size); + + possible_count = (256 * MB) >> (exfat->bs->bsx.sect_per_clus_bits + + exfat->bs->bsx.sect_size_bits); + possible_count = MIN(possible_count, exfat->clus_count); + + while (exfat_heap_clus(exfat, clus) && *out_clus_count < possible_count) { + dump_size = MIN(size, exfat->clus_size); + start_off = exfat_c2o(exfat, clus); + end_off = start_off + DIV_ROUND_UP(dump_size, 512) * 512; + + if (dump_range(ei, start_off, end_off) < 0) + return -EIO; + + *out_clus_count += 1; + size -= dump_size; + if (size == 0) + break; + + if (inode->is_contiguous) { + if (*out_clus_count >= max_count) + break; + } + if (exfat_get_inode_next_clus(exfat, inode, clus, &clus)) + return -EINVAL; + } + return 0; +} + +static int dump_root(struct exfat2img *ei) +{ + struct exfat *exfat = ei->exfat; + struct exfat_inode *root; + clus_t clus_count = 0; + + root = exfat_alloc_inode(ATTR_SUBDIR); + if (!root) + return -ENOMEM; + + root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster); + dump_directory(ei, root, (size_t)-1, &clus_count); + root->size = clus_count * exfat->clus_size; + + ei->exfat->root = root; + return 0; +} + +static int read_file_dentry_set(struct exfat_de_iter *iter, + struct exfat_inode **new_node, int *skip_dentries) +{ + struct exfat_dentry *file_de, *stream_de, *dentry; + struct exfat_inode *node = NULL; + int i, ret; + + ret = exfat_de_iter_get(iter, 0, &file_de); + if (ret || file_de->type != EXFAT_FILE) { + exfat_debug("failed to get file dentry\n"); + return -EINVAL; + } + + ret = exfat_de_iter_get(iter, 1, &stream_de); + if (ret || stream_de->type != EXFAT_STREAM) { + exfat_debug("failed to get stream dentry\n"); + *skip_dentries = 2; + goto skip_dset; + } + + *new_node = NULL; + node = exfat_alloc_inode(le16_to_cpu(file_de->file_attr)); + if (!node) + return -ENOMEM; + + for (i = 2; i <= file_de->file_num_ext; i++) { + ret = exfat_de_iter_get(iter, i, &dentry); + if (ret || dentry->type != EXFAT_NAME) + break; + memcpy(node->name + + (i - 2) * ENTRY_NAME_MAX, dentry->name_unicode, + sizeof(dentry->name_unicode)); + } + + node->first_clus = le32_to_cpu(stream_de->stream_start_clu); + node->is_contiguous = + ((stream_de->stream_flags & EXFAT_SF_CONTIGUOUS) != 0); + node->size = le64_to_cpu(stream_de->stream_size); + + *skip_dentries = i; + *new_node = node; + return 0; +skip_dset: + *new_node = NULL; + exfat_free_inode(node); + return -EINVAL; +} + +static int read_file(struct exfat_de_iter *de_iter, + struct exfat_inode **new_node, int *dentry_count) +{ + struct exfat_inode *node; + int ret; + + *new_node = NULL; + + ret = read_file_dentry_set(de_iter, &node, dentry_count); + if (ret) + return ret; + + if (node->attr & ATTR_SUBDIR) + exfat_stat.dir_count++; + else + exfat_stat.file_count++; + *new_node = node; + return ret; +} + +static int read_bitmap(struct exfat2img *ei, struct exfat_de_iter *iter) +{ + struct exfat *exfat = ei->exfat; + struct exfat_dentry *dentry; + int ret; + + ret = exfat_de_iter_get(iter, 0, &dentry); + if (ret || dentry->type != EXFAT_BITMAP) { + exfat_debug("failed to get bimtap dentry\n"); + return -EINVAL; + } + + exfat_debug("start cluster %#x, size %#" PRIx64 "\n", + le32_to_cpu(dentry->bitmap_start_clu), + le64_to_cpu(dentry->bitmap_size)); + + if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) { + exfat_err("invalid start cluster of allocate bitmap. 0x%x\n", + le32_to_cpu(dentry->bitmap_start_clu)); + return -EINVAL; + } + + exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu); + exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8); + + return dump_clusters(ei, + exfat->disk_bitmap_clus, + exfat->disk_bitmap_clus + + DIV_ROUND_UP(exfat->disk_bitmap_size, + exfat->clus_size)); +} + +static int read_upcase_table(struct exfat2img *ei, + struct exfat_de_iter *iter) +{ + struct exfat *exfat = ei->exfat; + struct exfat_dentry *dentry = NULL; + int retval; + ssize_t size; + + retval = exfat_de_iter_get(iter, 0, &dentry); + if (retval || dentry->type != EXFAT_UPCASE) { + exfat_debug("failed to get upcase dentry\n"); + return -EINVAL; + } + + if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) { + exfat_err("invalid start cluster of upcase table. 0x%x\n", + le32_to_cpu(dentry->upcase_start_clu)); + return -EINVAL; + } + + size = EXFAT_MAX_UPCASE_CHARS * sizeof(__le16); + return dump_clusters(ei, le32_to_cpu(dentry->upcase_start_clu), + le32_to_cpu(dentry->upcase_start_clu) + + DIV_ROUND_UP(size, exfat->clus_size)); +} + +static int read_children(struct exfat2img *ei, struct exfat_inode *dir, + off_t *end_file_offset) +{ + struct exfat *exfat = ei->exfat; + struct exfat_inode *node = NULL; + struct exfat_dentry *dentry; + struct exfat_de_iter *de_iter; + int dentry_count; + int ret; + + *end_file_offset = 0; + de_iter = &ei->de_iter; + ret = exfat_de_iter_init(de_iter, exfat, dir, ei->scan_bdesc); + if (ret == EOF) + return 0; + else if (ret) + return ret; + + while (1) { + ret = exfat_de_iter_get(de_iter, 0, &dentry); + if (ret == EOF) { + break; + } else if (ret) { + ei_err(dir->parent, dir, + "failed to get a dentry. %d\n", ret); + goto err; + } + dentry_count = 1; + + switch (dentry->type) { + case EXFAT_FILE: + ret = read_file(de_iter, &node, &dentry_count); + if (ret < 0) { + exfat_stat.error_count++; + break; + } + + if (node) { + if ((node->attr & ATTR_SUBDIR) && node->size) { + node->parent = dir; + list_add_tail(&node->sibling, + &dir->children); + list_add_tail(&node->list, + &exfat->dir_list); + } else { + exfat_free_inode(node); + } + } + break; + case EXFAT_LAST: + goto out; + case EXFAT_BITMAP: + if (dir == exfat->root) { + ret = read_bitmap(ei, de_iter); + if (ret) + exfat_debug("failed to read bitmap\n"); + } + break; + case EXFAT_UPCASE: + if (dir == exfat->root) { + ret = read_upcase_table(ei, de_iter); + if (ret) + exfat_debug("failed to upcase table\n"); + } + break; + case EXFAT_VOLUME: + default: + break; + } + + ret = exfat_de_iter_advance(de_iter, dentry_count); + } +out: + *end_file_offset = exfat_de_iter_file_offset(de_iter); + exfat_de_iter_flush(de_iter); + return 0; +err: + exfat_free_children(dir, false); + INIT_LIST_HEAD(&dir->children); + exfat_de_iter_flush(de_iter); + return ret; +} + +static int dump_filesystem(struct exfat2img *ei) +{ + struct exfat *exfat = ei->exfat; + struct exfat_inode *dir; + int ret = 0, dir_errors; + clus_t clus_count; + off_t end_file_offset; + + if (!exfat->root) { + exfat_err("root is NULL\n"); + return -ENOENT; + } + + list_add(&exfat->root->list, &exfat->dir_list); + + while (!list_empty(&exfat->dir_list)) { + dir = list_entry(exfat->dir_list.next, + struct exfat_inode, list); + clus_count = 0; + + if (!(dir->attr & ATTR_SUBDIR)) { + ei_err(dir->parent, dir, + "failed to travel directories. the node is not directory\n"); + ret = -EINVAL; + goto out; + } + + dir_errors = read_children(ei, dir, &end_file_offset); + if (!dir_errors) { + dump_directory(ei, dir, (size_t)end_file_offset, + &clus_count); + } else if (dir_errors) { + dump_directory(ei, dir, (size_t)-1, + &clus_count); + exfat_resolve_path(&path_resolve_ctx, dir); + exfat_debug("failed to check dentries: %s\n", + path_resolve_ctx.local_path); + ret = dir_errors; + } + + list_del(&dir->list); + exfat_free_ancestors(dir); + } +out: + exfat_free_dir_list(exfat); + return ret; +} + +static int dump_bytes_to_stdout(struct exfat2img *ei, + off_t start, off_t end_excl, bool fill_zero) +{ + struct exfat *exfat = ei->exfat; + size_t len; + ssize_t ret; + + if (start != ei->stdout_offset) { + exfat_err("try to skip for stdout at %llu, expected: %llu\n", + (unsigned long long)start, + (unsigned long long)ei->stdout_offset); + return -EINVAL; + } + + while (start < end_excl) { + len = (size_t)MIN(end_excl - start, exfat->clus_size); + if (!fill_zero) { + ret = exfat_read(exfat->blk_dev->dev_fd, + ei->dump_bdesc[0].buffer, + len, start); + if (ret != (ssize_t)len) { + exfat_err("failed to read %llu bytes at %llu\n", + (unsigned long long)len, + (unsigned long long)start); + return -EIO; + } + + ret = write(ei->out_fd, ei->dump_bdesc[0].buffer, len); + if (ret != (ssize_t)len) { + exfat_err("failed to write %llu bytes at %llu\n", + (unsigned long long)len, + (unsigned long long)start); + return -EIO; + } + } else { + ret = write(ei->out_fd, exfat->zero_cluster, len); + if (ret != (ssize_t)len) { + exfat_err("failed to write %llu bytes at %llu\n", + (unsigned long long)len, + (unsigned long long)start); + return -EIO; + } + } + + start += len; + ei->stdout_offset += len; + exfat_stat.written_bytes += len; + } + return 0; +} + +static int dump_clusters_to_stdout(struct exfat2img *ei, + unsigned int start_clu, unsigned int end_clu, + bool fill_zero) +{ + unsigned int clu, clu_count; + unsigned char cc; + unsigned int cc_clu_count, cc_len; + off_t start_off, end_off_excl; + char buf[1 + EI_CC_PAYLOAD_LEN]; + + clu = start_clu; + clu_count = end_clu - start_clu + 1; + + if (ei->save_cc) { + /* if the count of clusters is less than 5, use SKIP_1 or COPY_2 */ + cc_clu_count = clu_count < 5 ? 1 : clu_count; + cc_len = cc_clu_count == 1 ? 1 : 1 + EI_CC_PAYLOAD_LEN; + if (fill_zero) + cc = cc_clu_count == 1 ? EI_CC_SKIP_1 : EI_CC_SKIP_2; + else + cc = cc_clu_count == 1 ? EI_CC_COPY_1 : EI_CC_COPY_2; + } else { + cc = EI_CC_INVALID; + cc_clu_count = clu_count; + } + + while (clu <= end_clu) { + if (cc != EI_CC_INVALID) { + buf[0] = cc; + *((__le32 *)&buf[1]) = + cpu_to_le32(cc_clu_count); + if (write(ei->out_fd, buf, cc_len) != (ssize_t)cc_len) { + exfat_err("failed to write cc %d : %u\n for %u ~ %u clusters\n", + cc, cc_clu_count, + start_clu, start_clu + cc_clu_count - 1); + } + } + + if (cc == EI_CC_COPY_1 || cc == EI_CC_COPY_2) { + start_off = exfat_c2o(ei->exfat, clu); + end_off_excl = exfat_c2o(ei->exfat, clu + cc_clu_count); + + if (dump_bytes_to_stdout(ei, start_off, end_off_excl, + false) < 0) + return -EIO; + } else { + ei->stdout_offset += (off_t)cc_clu_count * ei->exfat->clus_size; + } + clu += cc_clu_count; + } + + return 0; +} + +static int dump_to_stdout(struct exfat2img *ei) +{ + struct exfat *exfat = ei->exfat; + off_t start_off, end_off; + unsigned int clu, last_clu, next_clu; + unsigned int start_clu, end_clu; + + start_off = 0; + end_off = exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset)); + if (dump_bytes_to_stdout(ei, start_off, end_off, false) < 0) { + exfat_err("failed to dump boot sectors and FAT tables\n"); + return -EIO; + } + + clu = EXFAT_FIRST_CLUSTER; + last_clu = clu + exfat->clus_count; + while (clu < last_clu) { + /* read and write clusters for allocated ones */ + start_clu = 0; + while (clu < last_clu && + exfat_bitmap_get(exfat->alloc_bitmap, clu)) { + if (!start_clu) + start_clu = clu; + end_clu = clu; + clu++; + } + + if (start_clu) { + if (dump_clusters_to_stdout(ei, start_clu, end_clu, false) < 0) { + start_off = exfat_c2o(exfat, start_clu); + end_off = exfat_c2o(exfat, end_clu); + exfat_err("failed to dump range from %llx to %llx\n", + (unsigned long long)start_off, + (unsigned long long)end_off); + return -EIO; + } + } + + /* exit if all of the remaining clusters are free */ + if (clu >= last_clu) + break; + if (exfat_bitmap_find_one(exfat, exfat->alloc_bitmap, + clu, &next_clu)) + next_clu = EXFAT_FIRST_CLUSTER + exfat->clus_count; + + /* write zeroes for free clusters */ + start_clu = clu; + end_clu = next_clu - 1; + if (dump_clusters_to_stdout(ei, start_clu, end_clu, true) < 0) { + start_off = exfat_c2o(exfat, start_clu); + end_off = exfat_c2o(exfat, end_clu); + exfat_err("failed to dump zero range from %llx to %llx\n", + (unsigned long long)start_off, + (unsigned long long)end_off); + return -EIO; + } + + clu = next_clu; + } + + return 0; +} + +static int dump_header(struct exfat2img *ei) +{ + struct exfat *exfat = ei->exfat; + + ei_hdr.magic = cpu_to_le32(EI_MAGIC); + ei_hdr.major_version = cpu_to_le32(1); + ei_hdr.minor_version = cpu_to_le32(0); + ei_hdr.data_offset = cpu_to_le32(sizeof(struct exfat2img_hdr)); + ei_hdr.heap_clus_offset = + cpu_to_le32(le32_to_cpu(exfat->bs->bsx.clu_offset) * + exfat->sect_size); + ei_hdr.cluster_size = cpu_to_le32(exfat->clus_size); + ei_hdr.cluster_count = cpu_to_le32(exfat->clus_count); + + if (write(ei->out_fd, &ei_hdr, sizeof(ei_hdr)) != (ssize_t)sizeof(ei_hdr)) { + exfat_err("failed to write exfat2img header\n"); + return -EIO; + } + return 0; +} + +static ssize_t read_stream(int fd, void *buf, size_t len) +{ + size_t read_len = 0; + ssize_t ret; + + while (read_len < len) { + ret = read(fd, buf, len - read_len); + if (ret < 0) { + if (errno != -EAGAIN && errno != -EINTR) + return -1; + ret = 0; + } else if (ret == 0) { + return 0; + } + buf += (size_t)ret; + read_len += (size_t)ret; + } + return read_len; +} + +static int restore_from_stdin(struct exfat2img *ei) +{ + int in_fd, ret; + unsigned char cc; + unsigned int clu, end_clu; + unsigned int cc_clu_count; + unsigned int clus_size; + __le32 t_cc_clu_count; + off_t out_start_off, out_end_off_excl; + off_t in_start_off; + size_t len; + + in_fd = fileno(stdin); + if (in_fd < 0) { + exfat_err("failed to get fd from stdin\n"); + return in_fd; + } + + if (read_stream(in_fd, &ei_hdr, sizeof(ei_hdr)) != (ssize_t)sizeof(ei_hdr)) { + exfat_err("failed to read a header\n"); + return -EIO; + } + + if (le32_to_cpu(ei_hdr.magic) != EI_MAGIC) { + exfat_err("header has invalid magic %#x, expected %#x\n", + le32_to_cpu(ei_hdr.magic), EI_MAGIC); + return -EINVAL; + } + + clus_size = le32_to_cpu(ei_hdr.cluster_size); + + ei->out_fd = ei->bdev.dev_fd; + ei->dump_bdesc = exfat_alloc_buffer(2, clus_size, 512); + if (!ei->dump_bdesc) + return -ENOMEM; + + /* restore boot regions, and FAT tables */ + in_start_off = le32_to_cpu(ei_hdr.data_offset); + out_start_off = 0; + out_end_off_excl = le32_to_cpu(ei_hdr.heap_clus_offset); + while (out_start_off < out_end_off_excl) { + len = MIN(out_end_off_excl - out_start_off, clus_size); + if (read_stream(in_fd, ei->dump_bdesc[0].buffer, len) != (ssize_t)len) { + exfat_err("failed to read first meta region. %llu ~ %llu\n", + (unsigned long long)in_start_off, + (unsigned long long)in_start_off + len); + ret = -EIO; + goto out; + } + + if (pwrite(ei->out_fd, ei->dump_bdesc[0].buffer, len, out_start_off) + != (ssize_t)len) { + exfat_err("failed to write first meta region. %llu ~ %llu\n", + (unsigned long long)out_start_off, + (unsigned long long)out_start_off + len); + ret = -EIO; + goto out; + } + + out_start_off += len; + in_start_off += len; + } + + /* restore heap clusters */ + clu = 0; + while (clu < le32_to_cpu(ei_hdr.cluster_count)) { + if (read_stream(in_fd, &cc, sizeof(cc)) != (ssize_t)sizeof(cc)) { + exfat_err("failed to read cc at %llu\n", + (unsigned long long)in_start_off); + ret = -EIO; + goto out; + } + in_start_off += 1; + + if (cc == EI_CC_COPY_2 || cc == EI_CC_SKIP_2) { + if (read_stream(in_fd, &t_cc_clu_count, EI_CC_PAYLOAD_LEN) != + (ssize_t)EI_CC_PAYLOAD_LEN) { + exfat_err("failed to read cc cluster count at %llu\n", + (unsigned long long)in_start_off); + ret = -EIO; + goto out; + } + cc_clu_count = le32_to_cpu(t_cc_clu_count); + in_start_off += EI_CC_PAYLOAD_LEN; + } else if (cc == EI_CC_COPY_1 || cc == EI_CC_SKIP_1) { + cc_clu_count = 1; + } else { + exfat_err("unexpected cc %d at %llu\n", + cc, (unsigned long long)in_start_off); + ret = -EINVAL; + goto out; + } + + if (cc == EI_CC_COPY_1 || cc == EI_CC_COPY_2) { + end_clu = clu + cc_clu_count; + while (clu < end_clu) { + if (read_stream(in_fd, ei->dump_bdesc[0].buffer, + clus_size) != (ssize_t)clus_size) { + exfat_err("failed to read range %llu ~ %llu\n", + (unsigned long long)in_start_off, + (unsigned long long)in_start_off + clus_size); + ret = -EIO; + goto out; + } + if (pwrite(ei->out_fd, ei->dump_bdesc[0].buffer, + clus_size, out_start_off) != (ssize_t)clus_size) { + exfat_err("failed to write range %llu ~ %llu\n", + (unsigned long long)out_start_off, + (unsigned long long)out_start_off + clus_size); + ret = -EIO; + goto out; + } + + out_start_off += clus_size; + in_start_off += clus_size; + clu++; + } + } else { + out_start_off += (off_t)cc_clu_count * clus_size; + in_start_off += (off_t)cc_clu_count * clus_size; + if (lseek(ei->out_fd, out_start_off, SEEK_SET) == (off_t)-1) { + exfat_err("failed to seek to %llu\n", + (unsigned long long)out_start_off); + ret = -EIO; + goto out; + } + clu += cc_clu_count; + } + } +out: + fsync(ei->out_fd); + exfat_free_buffer(ei->dump_bdesc, 2); + return ret; +} + +int main(int argc, char * const argv[]) +{ + int err = 0, c; + const char *in_path, *out_path = NULL, *blkdev_path; + struct pbr *bs; + struct exfat_user_input ui; + off_t last_sect; + bool restore; + + print_level = EXFAT_ERROR; + + opterr = 0; + while ((c = getopt_long(argc, argv, "o:Vh", opts, NULL)) != EOF) { + switch (c) { + case 'o': + out_path = optarg; + break; + case 'V': + show_version(); + return 0; + case 'h': + /* Fall through */ + default: + usage(argv[0]); + break; + } + } + + show_version(); + if (!(optind == argc - 1 && out_path) && + !(optind == argc - 2 && !out_path)) + usage(argv[0]); + + in_path = argv[optind++]; + if (!out_path) + out_path = argv[optind++]; + + if (!strcmp(in_path, "-")) { + restore = true; + blkdev_path = out_path; + } else { + restore = false; + blkdev_path = in_path; + } + + memset(&ui, 0, sizeof(ui)); + snprintf(ui.dev_name, sizeof(ui.dev_name), "%s", blkdev_path); + if (restore) + ui.writeable = true; + else + ui.writeable = false; + + if (exfat_get_blk_dev_info(&ui, &ei.bdev)) { + exfat_err("failed to open %s\n", ui.dev_name); + return EXIT_FAILURE; + } + + if (restore) + return restore_from_stdin(&ei); + + err = read_boot_sect(&ei.bdev, &bs); + if (err) { + close(ei.bdev.dev_fd); + return EXIT_FAILURE; + } + + err = create_exfat2img(&ei, bs, out_path); + if (err) + return EXIT_FAILURE; + + if (!ei.is_stdout) { + err = dump_sectors(&ei, 0, le32_to_cpu(ei.exfat->bs->bsx.clu_offset)); + if (err) { + exfat_err("failed to dump boot sectors, fat\n"); + goto out; + } + + last_sect = (off_t)le32_to_cpu(ei.exfat->bs->bsx.clu_offset) + + (le32_to_cpu(ei.exfat->bs->bsx.clu_count) << + ei.exfat->bs->bsx.sect_per_clus_bits) - 1; + err = dump_sectors(&ei, last_sect, last_sect + 1); + if (err) { + exfat_err("failed to dump last sector\n"); + goto out; + } + } + + err = dump_root(&ei); + if (err) { + exfat_err("failed to dump root\n"); + goto out; + } + + dump_filesystem(&ei); + + if (ei.is_stdout) { + err = dump_header(&ei); + if (err) + goto out; + err = dump_to_stdout(&ei); + if (err) + goto out; + } else { + err = fsync(ei.out_fd); + if (err) { + exfat_err("failed to fsync %s. %d\n", out_path, errno); + goto out; + } + close(ei.out_fd); + } + + printf("%ld files found, %ld directories dumped, %llu kbytes written\n", + exfat_stat.file_count, + exfat_stat.dir_count, + (unsigned long long)DIV_ROUND_UP(exfat_stat.written_bytes, 1024)); + +out: + free_exfat2img(&ei); + return err == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/manpages/exfat2img.8 b/manpages/exfat2img.8 new file mode 100644 index 0000000..1c7f288 --- /dev/null +++ b/manpages/exfat2img.8 @@ -0,0 +1,31 @@ +.TH exfat2img 8 +.SH NAME +exfat2img \- dump metadata of an exFAT filesystem +.SH SYNOPSIS +.B exfat2img +[ +.B \-o \fIpath\fB\ +] [ +.B \-V +] +.I device +.br +.B exfat2img \-V +.SH DESCRIPTION +.B exfat2img +dump metadata of exFAT filesystems for debugging. \fBexfat2img\fP dump boot sector, File Allcation Table, Bitmap and all metadata which can reach from root directory. + +.SH OPTIONS +.TP +.BI \-o\ \-\-output +Specify output result file. If filesystem to which output file is written does not support sparse file, you should use '-' in place of \fIpath\fP. +When restoring a partition from a dump image generated from stdout, exfat2img should be used. See Examples. +.TP +.B \-V +Prints the version number and exits. + +.SH EXAMPLES +.PP +.EX +.RB "$" " exfat2img -o - /dev/sda1 | bzip2 > sda1.dump.bz2" +.RB "$" " bzip2 -dc sda1.dump.bz2 | exfat2img -o /dev/sdb1 -" -- cgit v1.2.3 From d45d4da28836db69c2e9dd69191b6771aadbc2e2 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Thu, 1 Sep 2022 14:43:32 +0900 Subject: exfatprogs: modify .travis.yml to run fsck testcases Modify .travis.yml to run testcases for fsck repair in tests directory. Signed-off-by: Hyunchul Lee --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 90aff89..2d44bd1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ notifications: before_script: - sudo apt-get install linux-headers-$(uname -r) + - sudo apt-get install xz-utils - git clone --branch=exfat-next https://github.com/namjaejeon/exfat_oot - ./.travis_get_mainline_kernel - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH @@ -21,11 +22,16 @@ script: - ./configure > /dev/null - make -j$((`nproc`+1)) > /dev/null - sudo make install > /dev/null + # build & install exfat - cd exfat_oot - make > /dev/null - sudo make install > /dev/null - sudo modprobe exfat - sudo mkdir -p /mnt/test + - cd .. + # run fsck repair testcases + - cd tests + - sudo ./test_fsck.sh # create file/director test - truncate -s 10G test.img - sudo losetup /dev/loop22 test.img -- cgit v1.2.3 From afd6e9e47ee545ed10a36157907220ba8ee0724f Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 7 Sep 2022 14:56:07 +0800 Subject: mkfs: zero the reserved fields of directory entries From MS exfat specification, the reserved field shall initialize to zero and should not use for any purpose. This commit zeroes the reserved fields of Allocation Bitmap, Up-case Table, Volume Label entries. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- mkfs/mkfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c index 511662b..f9e5bb4 100644 --- a/mkfs/mkfs.c +++ b/mkfs/mkfs.c @@ -337,7 +337,7 @@ static int exfat_create_bitmap(struct exfat_blk_dev *bd) static int exfat_create_root_dir(struct exfat_blk_dev *bd, struct exfat_user_input *ui) { - struct exfat_dentry ed[3]; + struct exfat_dentry ed[3] = {0}; int dentries_len = sizeof(struct exfat_dentry) * 3; int nbytes; -- cgit v1.2.3 From c195fdb1042920f7e0921234f20f8a4c48b1eb91 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Sun, 11 Sep 2022 06:48:57 +0900 Subject: exfatprogs: change Copyright holder in some files Change copyright holder from me to LG Electronics, because LG Electronics supports this work. Signed-off-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- exfat2img/exfat2img.c | 4 +++- include/exfat_dir.h | 4 +++- include/exfat_fs.h | 4 +++- lib/exfat_dir.c | 4 +++- lib/exfat_fs.c | 4 +++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/exfat2img/exfat2img.c b/exfat2img/exfat2img.c index bad7639..fd176e0 100644 --- a/exfat2img/exfat2img.c +++ b/exfat2img/exfat2img.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2021 Hyunchul Lee + * Copyright (C) 2021 LG Electronics. + * + * Author(s): Hyunchul Lee */ #include diff --git a/include/exfat_dir.h b/include/exfat_dir.h index 8e55000..617f98e 100644 --- a/include/exfat_dir.h +++ b/include/exfat_dir.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* - * Copyright (C) 2022 Hyunchul Lee + * Copyright (C) 2021 LG Electronics. + * + * Author(s): Hyunchul Lee */ #ifndef _DIR_H_ diff --git a/include/exfat_fs.h b/include/exfat_fs.h index c1cfff0..d35b12c 100644 --- a/include/exfat_fs.h +++ b/include/exfat_fs.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* - * Copyright (C) 2021 Hyunchul Lee + * Copyright (C) 2021 LG Electronics. + * + * Author(s): Hyunchul Lee */ #ifndef _EXFAT_FS_H_ #define _EXFAT_FS_H_ diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c index 55f1eb6..2eda805 100644 --- a/lib/exfat_dir.c +++ b/lib/exfat_dir.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2020 Hyunchul Lee + * Copyright (C) 2021 LG Electronics. + * + * Author(s): Hyunchul Lee */ #include #include diff --git a/lib/exfat_fs.c b/lib/exfat_fs.c index 41518b0..d563c61 100644 --- a/lib/exfat_fs.c +++ b/lib/exfat_fs.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Hyunchul Lee + * Copyright (C) 2021 LG Electronics. + * + * Author(s): Hyunchul Lee */ #include -- cgit v1.2.3 From 6d52fb4a0f6a2df85381ec92fa11c9810044641e Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Wed, 14 Sep 2022 16:35:09 +0900 Subject: exfat2img: add usage examples to manpage Add usage examples to manpage. Signed-off-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- manpages/exfat2img.8 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/manpages/exfat2img.8 b/manpages/exfat2img.8 index 1c7f288..bceaa08 100644 --- a/manpages/exfat2img.8 +++ b/manpages/exfat2img.8 @@ -19,13 +19,18 @@ dump metadata of exFAT filesystems for debugging. \fBexfat2img\fP dump boot sect .TP .BI \-o\ \-\-output Specify output result file. If filesystem to which output file is written does not support sparse file, you should use '-' in place of \fIpath\fP. -When restoring a partition from a dump image generated from stdout, exfat2img should be used. See Examples. +Because a dump image generated from stdout has a special format, when restoring a partition from it, exfat2img should be used. See Examples. .TP .B \-V Prints the version number and exits. .SH EXAMPLES .PP +Dump metadata into a sparse file. +.EX +.RB "$" " exfat2img -o sda1.dump /dev/sda1 + +Dump metadata into standard out and restore a partition .EX .RB "$" " exfat2img -o - /dev/sda1 | bzip2 > sda1.dump.bz2" .RB "$" " bzip2 -dc sda1.dump.bz2 | exfat2img -o /dev/sdb1 -" -- cgit v1.2.3 From 13faa669612d575325ca568ebdf9cd6a0293f0b8 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Wed, 14 Sep 2022 16:36:29 +0900 Subject: fsck: update manpage Add the types of corrupt which can be repaired and usage examples to manpage. Signed-off-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- manpages/fsck.exfat.8 | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/manpages/fsck.exfat.8 b/manpages/fsck.exfat.8 index b93baaa..3b247d5 100644 --- a/manpages/fsck.exfat.8 +++ b/manpages/fsck.exfat.8 @@ -24,7 +24,29 @@ fsck.exfat \- check an exFAT filesystem .SH DESCRIPTION .B fsck.exfat checks an exFAT filesystem and repairs the filesystem -depending on the options passed. +depending on the options passed. The following corruptions can be repaired, and see the option, '-s'. +.IP - +Boot sector has invalid parameters. If backup boot sector is valid, replace the boot sector with it. +.IP - +Cluster is marked as free but belonged to a file, or vise versa. The bitmap for the cluster is marked properly. +.IP - +File size is abnormally large considering the count of clusters. The size is changed. +.IP - +File size is abnormally small considering the count of clusters. The remaining clusters are deleted. +.IP - +File's cluster chain has an invalid cluster number. The number are changed to EOF, and the file size is also changed. +.IP - +File's cluster chain contains a loop. The loop is broken. +.IP - +Files share the same cluster. Cluster chains for files except one are broken. +.IP - +Start cluster number is invalid. The cluster number and file size are changed to 0. +.IP - +Checksum value of direcotry entry set is invalid. Directory entry set is deleted. +.IP - +Bad hash value of a file name. The hash value is changed properly. +.IP - +Fields of directory entry set have invalid values. Directory entry set is deleted. .PP .SH OPTIONS .TP @@ -40,6 +62,9 @@ Repair the filesystem without user interaction if it can be done safely. .BI \-r Repair the filesystem interactively. .TP +.BI \-s +Create files in /LOST+FOUND for orphan clusters. These files have clusters allocated but not belonged to any files when reparing the filesystem. clusters unused and contiguous in bitmap are allocated to the same file. +.TP .BI \-v Prints verbose debugging information while checking the exFAT filesystem. .TP @@ -51,6 +76,13 @@ Repair the filesystem answering yes to all questions. .TP .B \-b Try to repair the filesystem even if the exFAT filesystem is not found. + +.SH EXAMPLES +.PP +repair a corrupted device and create files in /LOST+FOUND, which have clusters allocated but not belonged to any files when reparing the device. +.EX +.RB "$" " fsck.exfat -p -s /dev/sda1" + .SH SEE ALSO .BR fsck (8), .BR fstab (5), -- cgit v1.2.3 From 5c14e2044b72eb19e60cfec0fd9064626917f3ca Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Wed, 14 Sep 2022 16:45:10 +0900 Subject: exfatprogs: add usage examples to README.md Add usage examples for fsck.exfat and exfat2img to README.md Signed-off-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a0a772..155dc21 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,10 @@ Usage example: Usage example: 1. check the consistency. fsck.exfat /dev/sda1 - 2. repair and fix.(preparing) + 2. repair a corrupted device and create files in /LOST+FOUND, which have clusters allocated but not belonged to any files when reparing the device. + fsck.exfat -p -s /dev/sda1 + 3. repair a corrupted device in the same way above, but answering yes to all questions. + fsck.exfat -y -s /dev/sda1 - tune.exfat: Adjust tunable filesystem parameters on an exFAT filesystem @@ -87,6 +90,16 @@ Usage example: Usage example: dump.exfat /dev/sda1 +- exfat2img: + Dump metadata of an exFAT filesystem + +Usage example: + 1. Dump metadata into a sparse file + exfat2img -o sda1.dump /dev/sda1 + 2. Dump metadata into standard out and restore a partition + exfat2img -o - /dev/sda1 | bzip2 > sda1.dump.bz2 + bzip2 -dc sda1.dump.bz2 | exfat2img -o /dev/sdb1 - + ``` ## Benchmarks -- cgit v1.2.3 From b8a9b3282123d69b37fd5edc0f430a5043ccca6f Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 14 Sep 2022 21:48:41 +0900 Subject: tune: label: fix bitmap entry corruption when adding new volume label When adding new label using tune.exfat or exfatlabel against exfat device formatted by some camera vendor, bitmap entry in root entry is corrupted by overwriting. the format utils of vendor doesn't add volume entry. tune and exfatlabel of exfatprogs assumes that the first entry of the root entry is a volume entry and try overwrite it. This patch lookup and updates the volume entry in the root entry. And adds a new entry to an empty slot if it doesn't exist. Signed-off-by: Namjae Jeon --- exfat2img/exfat2img.c | 44 ------------ fsck/fsck.c | 41 +---------- include/libexfat.h | 7 +- label/label.c | 40 +++++++++-- lib/libexfat.c | 186 +++++++++++++++++++++++++++++++++++++------------- tune/tune.c | 35 ++++++++-- 6 files changed, 209 insertions(+), 144 deletions(-) diff --git a/exfat2img/exfat2img.c b/exfat2img/exfat2img.c index fd176e0..81e86d2 100644 --- a/exfat2img/exfat2img.c +++ b/exfat2img/exfat2img.c @@ -154,50 +154,6 @@ err: return err; } -static int read_boot_sect(struct exfat_blk_dev *bdev, struct pbr **bs) -{ - struct pbr *pbr; - int err = 0; - unsigned int sect_size, clu_size; - - pbr = malloc(sizeof(struct pbr)); - - if (exfat_read(bdev->dev_fd, pbr, sizeof(*pbr), 0) != - (ssize_t)sizeof(*pbr)) { - exfat_err("failed to read a boot sector\n"); - err = -EIO; - goto err; - } - - err = -EINVAL; - if (memcmp(pbr->bpb.oem_name, "EXFAT ", 8) != 0) { - exfat_err("failed to find exfat file system\n"); - goto err; - } - - sect_size = 1 << pbr->bsx.sect_size_bits; - clu_size = 1 << (pbr->bsx.sect_size_bits + - pbr->bsx.sect_per_clus_bits); - - if (sect_size < 512 || sect_size > 4 * KB) { - exfat_err("too small or big sector size: %d\n", - sect_size); - goto err; - } - - if (clu_size < sect_size || clu_size > 32 * MB) { - exfat_err("too small or big cluster size: %d\n", - clu_size); - goto err; - } - - *bs = pbr; - return 0; -err: - free(pbr); - return err; -} - /** * @end: excluded. */ diff --git a/fsck/fsck.c b/fsck/fsck.c index 52b3f1b..06cb129 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -808,45 +808,6 @@ static int read_file(struct exfat_de_iter *de_iter, return ret; } -static int read_volume_label(struct exfat *exfat) -{ - struct exfat_dentry *dentry; - int err; - __le16 disk_label[VOLUME_LABEL_MAX_LEN]; - struct exfat_lookup_filter filter = { - .in.type = EXFAT_VOLUME, - .in.filter = NULL, - }; - - err = exfat_lookup_dentry_set(exfat, exfat->root, &filter); - if (err) - return err; - - dentry = filter.out.dentry_set; - - if (dentry->vol_char_cnt == 0) - goto out; - - if (dentry->vol_char_cnt > VOLUME_LABEL_MAX_LEN) { - exfat_err("too long label. %d\n", dentry->vol_char_cnt); - err = -EINVAL; - goto out; - } - - memcpy(disk_label, dentry->vol_label, sizeof(disk_label)); - if (exfat_utf16_dec(disk_label, dentry->vol_char_cnt*2, - exfat->volume_label, sizeof(exfat->volume_label)) < 0) { - exfat_err("failed to decode volume label\n"); - err = -EINVAL; - goto out; - } - - exfat_info("volume label [%s]\n", exfat->volume_label); -out: - free(filter.out.dentry_set); - return err; -} - static int read_bitmap(struct exfat *exfat) { struct exfat_lookup_filter filter = { @@ -1202,7 +1163,7 @@ static int exfat_root_dir_check(struct exfat *exfat) exfat_debug("root directory: start cluster[0x%x] size[0x%" PRIx64 "]\n", root->first_clus, root->size); - if (read_volume_label(exfat)) + if (exfat_read_volume_label(exfat)) exfat_err("failed to read volume label\n"); err = read_bitmap(exfat); diff --git a/include/libexfat.h b/include/libexfat.h index b98f2b6..0623501 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -145,9 +145,8 @@ ssize_t exfat_utf16_enc(const char *in_str, __u16 *out_str, size_t out_size); ssize_t exfat_utf16_dec(const __u16 *in_str, size_t in_len, char *out_str, size_t out_size); off_t exfat_get_root_entry_offset(struct exfat_blk_dev *bd); -int exfat_show_volume_label(struct exfat_blk_dev *bd, off_t root_clu_off); -int exfat_set_volume_label(struct exfat_blk_dev *bd, - char *label_input, off_t root_clu_off); +int exfat_read_volume_label(struct exfat *exfat); +int exfat_set_volume_label(struct exfat *exfat, char *label_input); int exfat_read_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off); int exfat_write_sector(struct exfat_blk_dev *bd, void *buf, @@ -169,6 +168,8 @@ off_t exfat_c2o(struct exfat *exfat, unsigned int clus); int exfat_o2c(struct exfat *exfat, off_t device_offset, unsigned int *clu, unsigned int *offset); bool exfat_heap_clus(struct exfat *exfat, clus_t clus); +int exfat_root_clus_count(struct exfat *exfat); +int read_boot_sect(struct exfat_blk_dev *bdev, struct pbr **bs); /* * Exfat Print diff --git a/label/label.c b/label/label.c index b41e827..8cd5748 100644 --- a/label/label.c +++ b/label/label.c @@ -13,6 +13,7 @@ #include "exfat_ondisk.h" #include "libexfat.h" +#include "exfat_fs.h" static void usage(void) { @@ -39,7 +40,6 @@ int main(int argc, char *argv[]) struct exfat_blk_dev bd; struct exfat_user_input ui; bool version_only = false; - off_t root_clu_off; int serial_mode = 0; int flags = 0; @@ -96,15 +96,43 @@ int main(int argc, char *argv[]) ret = exfat_set_volume_serial(&bd, &ui); } } else { - /* Mode to change or display volume label */ - root_clu_off = exfat_get_root_entry_offset(&bd); - if (root_clu_off < 0) + struct exfat *exfat; + struct pbr *bs; + + ret = read_boot_sect(&bd, &bs); + if (ret) + goto close_fd_out; + + exfat = exfat_alloc_exfat(&bd, bs); + if (!exfat) { + free(bs); + ret = -ENOMEM; goto close_fd_out; + } + exfat->root = exfat_alloc_inode(ATTR_SUBDIR); + if (!exfat->root) { + ret = -ENOMEM; + goto free_exfat; + } + + exfat->root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster); + if (exfat_root_clus_count(exfat)) { + exfat_err("failed to follow the cluster chain of root\n"); + exfat_free_inode(exfat->root); + ret = -EINVAL; + goto free_exfat; + } + + /* Mode to change or display volume label */ if (flags == EXFAT_GET_VOLUME_LABEL) - ret = exfat_show_volume_label(&bd, root_clu_off); + ret = exfat_read_volume_label(exfat); else if (flags == EXFAT_SET_VOLUME_LABEL) - ret = exfat_set_volume_label(&bd, argv[2], root_clu_off); + ret = exfat_set_volume_label(exfat, argv[2]); + +free_exfat: + if (exfat) + exfat_free_exfat(exfat); } close_fd_out: diff --git a/lib/libexfat.c b/lib/libexfat.c index 92e5e91..d7c1df1 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -20,6 +20,7 @@ #include "libexfat.h" #include "version.h" #include "exfat_fs.h" +#include "exfat_dir.h" unsigned int print_level = EXFAT_INFO; @@ -403,74 +404,91 @@ char *exfat_conv_volume_label(struct exfat_dentry *vol_entry) return volume_label; } -int exfat_show_volume_label(struct exfat_blk_dev *bd, off_t root_clu_off) +int exfat_read_volume_label(struct exfat *exfat) { - struct exfat_dentry *vol_entry; - char *volume_label; - int nbytes; + struct exfat_dentry *dentry; + int err; + __le16 disk_label[VOLUME_LABEL_MAX_LEN]; + struct exfat_lookup_filter filter = { + .in.type = EXFAT_VOLUME, + .in.filter = NULL, + }; - vol_entry = malloc(sizeof(struct exfat_dentry)); - if (!vol_entry) { - exfat_err("failed to allocate memory\n"); - return -ENOMEM; - } + err = exfat_lookup_dentry_set(exfat, exfat->root, &filter); + if (err) + return err; - nbytes = exfat_read(bd->dev_fd, vol_entry, - sizeof(struct exfat_dentry), root_clu_off); - if (nbytes != sizeof(struct exfat_dentry)) { - exfat_err("volume entry read failed: %d\n", errno); - free(vol_entry); - return -1; - } + dentry = filter.out.dentry_set; - volume_label = exfat_conv_volume_label(vol_entry); - if (!volume_label) { - free(vol_entry); - return -EINVAL; + if (dentry->vol_char_cnt == 0) + goto out; + + if (dentry->vol_char_cnt > VOLUME_LABEL_MAX_LEN) { + exfat_err("too long label. %d\n", dentry->vol_char_cnt); + err = -EINVAL; + goto out; } - exfat_info("label: %s\n", volume_label); + memcpy(disk_label, dentry->vol_label, sizeof(disk_label)); + if (exfat_utf16_dec(disk_label, dentry->vol_char_cnt*2, + exfat->volume_label, sizeof(exfat->volume_label)) < 0) { + exfat_err("failed to decode volume label\n"); + err = -EINVAL; + goto out; + } - free(volume_label); - free(vol_entry); - return 0; + exfat_info("label: %s\n", exfat->volume_label); +out: + free(filter.out.dentry_set); + return err; } -int exfat_set_volume_label(struct exfat_blk_dev *bd, - char *label_input, off_t root_clu_off) +int exfat_set_volume_label(struct exfat *exfat, char *label_input) { - struct exfat_dentry vol; - int nbytes; + struct exfat_dentry *pvol; + struct exfat_dentry_loc loc; __u16 volume_label[VOLUME_LABEL_MAX_LEN]; - int volume_label_len; + int volume_label_len, dcount, err; + + struct exfat_lookup_filter filter = { + .in.type = EXFAT_VOLUME, + .in.filter = NULL, + }; + + err = exfat_lookup_dentry_set(exfat, exfat->root, &filter); + if (!err) { + pvol = filter.out.dentry_set; + dcount = filter.out.dentry_count; + memset(pvol->vol_label, 0, sizeof(pvol->vol_label)); + } else { + pvol = calloc(sizeof(struct exfat_dentry), 1); + if (!pvol) + return -ENOMEM; + + dcount = 1; + pvol->type = EXFAT_VOLUME; + } volume_label_len = exfat_utf16_enc(label_input, volume_label, sizeof(volume_label)); if (volume_label_len < 0) { exfat_err("failed to encode volume label\n"); + free(pvol); return -1; } - vol.type = EXFAT_VOLUME; - memset(vol.vol_label, 0, sizeof(vol.vol_label)); - memcpy(vol.vol_label, volume_label, volume_label_len); - vol.vol_char_cnt = volume_label_len/2; + memcpy(pvol->vol_label, volume_label, volume_label_len); + pvol->vol_char_cnt = volume_label_len/2; - nbytes = exfat_write(bd->dev_fd, &vol, sizeof(struct exfat_dentry), - root_clu_off); - if (nbytes != sizeof(struct exfat_dentry)) { - exfat_err("volume entry write failed: %d\n", errno); - return -1; - } + loc.parent = exfat->root; + loc.file_offset = filter.out.file_offset; + loc.dev_offset = filter.out.dev_offset; + err = exfat_add_dentry_set(exfat, &loc, pvol, dcount, false); + exfat_info("new label: %s\n", label_input); - if (fsync(bd->dev_fd) == -1) { - exfat_err("failed to sync volume entry: %d, %s\n", errno, - strerror(errno)); - return -1; - } + free(pvol); - exfat_info("new label: %s\n", label_input); - return 0; + return err; } int exfat_read_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off) @@ -760,3 +778,79 @@ bool exfat_heap_clus(struct exfat *exfat, clus_t clus) return clus >= EXFAT_FIRST_CLUSTER && (clus - EXFAT_FIRST_CLUSTER) < exfat->clus_count; } + +int exfat_root_clus_count(struct exfat *exfat) +{ + struct exfat_inode *node = exfat->root; + clus_t clus, next; + int clus_count = 0; + + if (!exfat_heap_clus(exfat, node->first_clus)) + return -EIO; + + clus = node->first_clus; + do { + if (exfat_bitmap_get(exfat->alloc_bitmap, clus)) + return -EINVAL; + + exfat_bitmap_set(exfat->alloc_bitmap, clus); + + if (exfat_get_inode_next_clus(exfat, node, clus, &next)) { + exfat_err("ERROR: failed to read the fat entry of root"); + return -EIO; + } + + if (next != EXFAT_EOF_CLUSTER && !exfat_heap_clus(exfat, next)) + return -EINVAL; + + clus = next; + clus_count++; + } while (clus != EXFAT_EOF_CLUSTER); + + node->size = clus_count * exfat->clus_size; + return 0; +} + +int read_boot_sect(struct exfat_blk_dev *bdev, struct pbr **bs) +{ + struct pbr *pbr; + int err = 0; + unsigned int sect_size, clu_size; + + pbr = malloc(sizeof(struct pbr)); + + if (exfat_read(bdev->dev_fd, pbr, sizeof(*pbr), 0) != + (ssize_t)sizeof(*pbr)) { + exfat_err("failed to read a boot sector\n"); + err = -EIO; + goto err; + } + + err = -EINVAL; + if (memcmp(pbr->bpb.oem_name, "EXFAT ", 8) != 0) { + exfat_err("failed to find exfat file system\n"); + goto err; + } + + sect_size = 1 << pbr->bsx.sect_size_bits; + clu_size = 1 << (pbr->bsx.sect_size_bits + + pbr->bsx.sect_per_clus_bits); + + if (sect_size < 512 || sect_size > 4 * KB) { + exfat_err("too small or big sector size: %d\n", + sect_size); + goto err; + } + + if (clu_size < sect_size || clu_size > 32 * MB) { + exfat_err("too small or big cluster size: %d\n", + clu_size); + goto err; + } + + *bs = pbr; + return 0; +err: + free(pbr); + return err; +} diff --git a/tune/tune.c b/tune/tune.c index a53be59..135f624 100644 --- a/tune/tune.c +++ b/tune/tune.c @@ -13,6 +13,7 @@ #include "exfat_ondisk.h" #include "libexfat.h" +#include "exfat_fs.h" static void usage(void) { @@ -49,7 +50,8 @@ int main(int argc, char *argv[]) bool version_only = false; int flags = 0; char label_input[VOLUME_LABEL_BUFFER_SIZE]; - off_t root_clu_off; + struct exfat *exfat = NULL; + struct pbr *bs; init_user_input(&ui); @@ -109,16 +111,39 @@ int main(int argc, char *argv[]) goto close_fd_out; } - root_clu_off = exfat_get_root_entry_offset(&bd); - if (root_clu_off < 0) + ret = read_boot_sect(&bd, &bs); + if (ret) goto close_fd_out; + exfat = exfat_alloc_exfat(&bd, bs); + if (!exfat) { + free(bs); + ret = -ENOMEM; + goto close_fd_out; + } + + exfat->root = exfat_alloc_inode(ATTR_SUBDIR); + if (!exfat->root) { + ret = -ENOMEM; + goto close_fd_out; + } + + exfat->root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster); + if (exfat_root_clus_count(exfat)) { + exfat_err("failed to follow the cluster chain of root\n"); + exfat_free_inode(exfat->root); + ret = -EINVAL; + goto close_fd_out; + } + if (flags == EXFAT_GET_VOLUME_LABEL) - ret = exfat_show_volume_label(&bd, root_clu_off); + ret = exfat_read_volume_label(exfat); else if (flags == EXFAT_SET_VOLUME_LABEL) - ret = exfat_set_volume_label(&bd, label_input, root_clu_off); + ret = exfat_set_volume_label(exfat, label_input); close_fd_out: close(bd.dev_fd); + if (exfat) + exfat_free_exfat(exfat); out: return ret; } -- cgit v1.2.3 From d1aa86dcd56ded4a61cbd1acdf30f9d8d949712d Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 30 Sep 2022 23:21:04 +0900 Subject: fsck: don't error print if there is no volume entry windows and some camera vendor doesn't create volume lable entry on exfat format. When running fsck.exfat, unneeded error message is coming because there is no volume lable entry. If there is no volume entry, It is not problem, So fsck should not print an error print. Signed-off-by: Namjae Jeon --- fsck/fsck.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 06cb129..5721877 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -1163,8 +1163,10 @@ static int exfat_root_dir_check(struct exfat *exfat) exfat_debug("root directory: start cluster[0x%x] size[0x%" PRIx64 "]\n", root->first_clus, root->size); - if (exfat_read_volume_label(exfat)) + err = exfat_read_volume_label(exfat); + if (err && err != EOF) exfat_err("failed to read volume label\n"); + err = 0; err = read_bitmap(exfat); if (err) { -- cgit v1.2.3 From 272c4f78f3fbc628f2cf113351fac2e8a86d5459 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 18 Oct 2022 22:43:59 +0900 Subject: fsck: add support for renaming dot and dotdot files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User report that "." and ".." files can be created from windows cmd. e.g. echo "hello world" > "\\?\D:\.." Problem is that user can check same dot and dotdot name entry from linux. $ ls -al /mnt/test total 104 drwxr-xr-x 3 root root 32768 10월 9 14:18 . drwxr-xr-x 3 root root 32768 10월 9 14:18 . drwxr-xr-x 10 root root 4096 5월 24 12:06 .. drwxr-xr-x 10 root root 4096 5월 24 12:06 .. FSCK in exfatprogs can rename this entries to other valid names. This patch suggest to user to rename them with two option. (name given from users and auto rename). Signed-off-by: Namjae Jeon --- fsck/fsck.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++ fsck/repair.c | 104 +++++++++++++++++++++++------------- fsck/repair.h | 5 +- include/exfat_dir.h | 2 + lib/exfat_dir.c | 1 + 5 files changed, 222 insertions(+), 40 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 5721877..219d723 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -668,6 +668,147 @@ static int check_name_dentry_set(struct exfat_de_iter *iter, return 0; } +static int check_bad_char(char w) +{ + return (w < 0x0020) || (w == '*') || (w == '?') || (w == '<') || + (w == '>') || (w == '|') || (w == '"') || (w == ':') || + (w == '/') || (w == '\\'); +} + +static char *get_rename_from_user(struct exfat_de_iter *iter) +{ + char *rename = malloc(ENTRY_NAME_MAX + 2); + + if (!rename) + return NULL; + +retry: + /* +2 means LF(Line Feed) and NULL terminator */ + memset(rename, 0x1, ENTRY_NAME_MAX + 2); + printf("New name: "); + if (fgets(rename, ENTRY_NAME_MAX + 2, stdin)) { + int i, len, err; + struct exfat_lookup_filter filter; + + len = strlen(rename); + /* Remove LF in filename */ + rename[len - 1] = '\0'; + for (i = 0; i < len - 1; i++) { + if (check_bad_char(rename[i])) { + printf("filename contain invalid character(%c)\n", rename[i]); + goto retry; + } + } + + exfat_de_iter_flush(iter); + err = exfat_lookup_file(iter->exfat, iter->parent, rename, &filter); + if (!err) { + printf("file(%s) already exists, retry to insert name\n", rename); + goto retry; + } + } + + return rename; +} + +static char *generate_rename(struct exfat_de_iter *iter) +{ + char *rename; + + if (iter->dot_name_num > DOT_NAME_NUM_MAX) + return NULL; + + rename = malloc(ENTRY_NAME_MAX + 1); + if (!rename) + return NULL; + + while (1) { + struct exfat_lookup_filter filter; + int err; + + snprintf(rename, ENTRY_NAME_MAX + 1, "FILE%07d.CHK", + iter->dot_name_num++); + err = exfat_lookup_file(iter->exfat, iter->parent, rename, + &filter); + if (!err) + continue; + break; + } + + return rename; +} + +const __le16 MSDOS_DOT[ENTRY_NAME_MAX] = {cpu_to_le16(46), 0, }; +const __le16 MSDOS_DOTDOT[ENTRY_NAME_MAX] = {cpu_to_le16(46), cpu_to_le16(46), 0, }; + +static int handle_dot_dotdot_filename(struct exfat_de_iter *iter, + struct exfat_dentry *dentry, + int strm_name_len) +{ + char *filename; + char error_msg[150]; + int num; + + if (!memcmp(dentry->name_unicode, MSDOS_DOT, strm_name_len * 2)) + filename = "."; + else if (!memcmp(dentry->name_unicode, MSDOS_DOTDOT, + strm_name_len * 2)) + filename = ".."; + else + return 0; + + sprintf(error_msg, "ERROR: '%s' filename is not allowed.\n" + " [1] Insert the name you want to rename.\n" + " [2] Automatically renames filename.\n" + " [3] Bypass this check(No repair)\n", filename); +ask_again: + num = exfat_repair_ask(&exfat_fsck, ER_DE_DOT_NAME, + error_msg); + if (num) { + __le16 utf16_name[ENTRY_NAME_MAX]; + char *rename = NULL; + __u16 hash; + struct exfat_dentry *stream_de; + int name_len, ret; + + switch (num) { + case 1: + rename = get_rename_from_user(iter); + break; + case 2: + rename = generate_rename(iter); + break; + case 3: + break; + default: + exfat_info("select 1 or 2 number instead of %d\n", num); + goto ask_again; + } + + if (!rename) + return -EINVAL; + + exfat_info("%s filename is renamed to %s\n", filename, rename); + + exfat_de_iter_get_dirty(iter, 2, &dentry); + + memset(utf16_name, 0, sizeof(utf16_name)); + ret = exfat_utf16_enc(rename, utf16_name, sizeof(utf16_name)); + free(rename); + if (ret < 0) + return ret; + + memcpy(dentry->name_unicode, utf16_name, ENTRY_NAME_MAX * 2); + name_len = exfat_utf16_len(utf16_name, ENTRY_NAME_MAX * 2); + hash = exfat_calc_name_hash(iter->exfat, utf16_name, (int)name_len); + exfat_de_iter_get_dirty(iter, 1, &stream_de); + stream_de->stream_name_len = (__u8)name_len; + stream_de->stream_name_hash = cpu_to_le16(hash); + } + + return 0; +} + static int read_file_dentry_set(struct exfat_de_iter *iter, struct exfat_inode **new_node, int *skip_dentries) { @@ -739,6 +880,15 @@ static int read_file_dentry_set(struct exfat_de_iter *iter, goto skip_dset; } + if (file_de->file_num_ext == 2 && stream_de->stream_name_len <= 2) { + ret = handle_dot_dotdot_filename(iter, dentry, + stream_de->stream_name_len); + if (ret < 0) { + *skip_dentries = file_de->file_num_ext + 1; + goto skip_dset; + } + } + node->first_clus = le32_to_cpu(stream_de->stream_start_clu); node->is_contiguous = ((stream_de->stream_flags & EXFAT_SF_CONTIGUOUS) != 0); diff --git a/fsck/repair.c b/fsck/repair.c index 65f4a9f..92b1c3f 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "exfat_ondisk.h" #include "libexfat.h" @@ -17,6 +18,9 @@ struct exfat_repair_problem { er_problem_code_t prcode; unsigned int flags; unsigned int prompt_type; + unsigned int default_number; + unsigned int bypass_number; + unsigned int max_number; }; /* Problem flags */ @@ -28,32 +32,35 @@ struct exfat_repair_problem { #define ERP_FIX 0x00000001 #define ERP_TRUNCATE 0x00000002 #define ERP_DELETE 0x00000003 +#define ERP_RENAME 0x00000004 static const char *prompts[] = { "Repair", "Fix", "Truncate", "Delete", + "Select", }; static struct exfat_repair_problem problems[] = { - {ER_BS_CHECKSUM, ERF_PREEN_YES, ERP_FIX}, - {ER_BS_BOOT_REGION, 0, ERP_FIX}, - {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_DELETE}, - {ER_DE_UNKNOWN, ERF_PREEN_YES, ERP_DELETE}, - {ER_DE_FILE, ERF_PREEN_YES, ERP_DELETE}, - {ER_DE_SECONDARY_COUNT, ERF_PREEN_YES, ERP_DELETE}, - {ER_DE_STREAM, ERF_PREEN_YES, ERP_DELETE}, - {ER_DE_NAME, ERF_PREEN_YES, ERP_DELETE}, - {ER_DE_NAME_HASH, ERF_PREEN_YES, ERP_FIX}, - {ER_DE_NAME_LEN, ERF_PREEN_YES, ERP_FIX}, - {ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX}, - {ER_FILE_INVALID_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, - {ER_FILE_FIRST_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, - {ER_FILE_SMALLER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE}, - {ER_FILE_LARGER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE}, - {ER_FILE_DUPLICATED_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, - {ER_FILE_ZERO_NOFAT, ERF_PREEN_YES, ERP_FIX}, + {ER_BS_CHECKSUM, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}, + {ER_BS_BOOT_REGION, 0, ERP_FIX, 0, 0, 0}, + {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0}, + {ER_DE_UNKNOWN, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0}, + {ER_DE_FILE, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0}, + {ER_DE_SECONDARY_COUNT, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0}, + {ER_DE_STREAM, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0}, + {ER_DE_NAME, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0}, + {ER_DE_NAME_HASH, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}, + {ER_DE_NAME_LEN, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}, + {ER_DE_DOT_NAME, ERF_PREEN_YES, ERP_RENAME, 2, 3, 4}, + {ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}, + {ER_FILE_INVALID_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, + {ER_FILE_FIRST_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, + {ER_FILE_SMALLER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, + {ER_FILE_LARGER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, + {ER_FILE_DUPLICATED_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, + {ER_FILE_ZERO_NOFAT, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}, }; static struct exfat_repair_problem *find_problem(er_problem_code_t prcode) @@ -68,64 +75,85 @@ static struct exfat_repair_problem *find_problem(er_problem_code_t prcode) return NULL; } -static bool ask_repair(struct exfat_fsck *fsck, struct exfat_repair_problem *pr) +static int ask_repair(struct exfat_fsck *fsck, struct exfat_repair_problem *pr) { - bool repair = false; + int repair = 0; char answer[8]; if (fsck->options & FSCK_OPTS_REPAIR_NO || pr->flags & ERF_DEFAULT_NO) - repair = false; + repair = 0; else if (fsck->options & FSCK_OPTS_REPAIR_YES || pr->flags & ERF_DEFAULT_YES) - repair = true; + repair = 1; else { if (fsck->options & FSCK_OPTS_REPAIR_ASK) { do { - printf(". %s (y/N)? ", - prompts[pr->prompt_type]); + if (pr->prompt_type & ERP_RENAME) { + printf("%s (Number: ?) ", + prompts[pr->prompt_type]); + } else { + printf(". %s (y/N)? ", + prompts[pr->prompt_type]); + } fflush(stdout); - if (fgets(answer, sizeof(answer), stdin)) { + if (!fgets(answer, sizeof(answer), stdin)) + continue; + + if (pr->prompt_type & ERP_RENAME) { + unsigned int number = atoi(answer); + + if (number > 0 && number < pr->max_number) + return number; + } else { if (strcasecmp(answer, "Y\n") == 0) - return true; - else if (strcasecmp(answer, "\n") == 0 - || strcasecmp(answer, "N\n") == 0) - return false; + return 1; + else if (strcasecmp(answer, "\n") == 0 || + strcasecmp(answer, "N\n") == 0) + return 0; } } while (1); } else if (fsck->options & FSCK_OPTS_REPAIR_AUTO && pr->flags & ERF_PREEN_YES) - repair = true; + repair = 1; } - printf(". %s (y/N)? %c\n", prompts[pr->prompt_type], - repair ? 'y' : 'n'); + if (pr->prompt_type & ERP_RENAME) { + int print_num = repair ? pr->default_number : pr->bypass_number; + + printf("%s (Number : %d)\n", prompts[pr->prompt_type], + print_num); + repair = print_num; + } else { + printf(". %s (y/N)? %c\n", prompts[pr->prompt_type], + repair ? 'y' : 'n'); + } return repair; } -bool exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode, - const char *desc, ...) +int exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode, + const char *desc, ...) { struct exfat_repair_problem *pr = NULL; va_list ap; + int repair; pr = find_problem(prcode); if (!pr) { exfat_err("unknown problem code. %#x\n", prcode); - return false; + return 0; } va_start(ap, desc); vprintf(desc, ap); va_end(ap); - if (ask_repair(fsck, pr)) { + repair = ask_repair(fsck, pr); + if (repair) { if (pr->prompt_type & ERP_TRUNCATE) fsck->dirty_fat = true; fsck->dirty = true; - return true; - } else { - return false; } + return repair; } diff --git a/fsck/repair.h b/fsck/repair.h index 4e9a6bf..dc3cc50 100644 --- a/fsck/repair.h +++ b/fsck/repair.h @@ -15,6 +15,7 @@ #define ER_DE_NAME 0x00001030 #define ER_DE_NAME_HASH 0x00001031 #define ER_DE_NAME_LEN 0x00001032 +#define ER_DE_DOT_NAME 0x00001033 #define ER_FILE_VALID_SIZE 0x00002001 #define ER_FILE_INVALID_CLUS 0x00002002 #define ER_FILE_FIRST_CLUS 0x00002003 @@ -26,7 +27,7 @@ typedef unsigned int er_problem_code_t; struct exfat_fsck; -bool exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode, - const char *fmt, ...); +int exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode, + const char *fmt, ...); #endif diff --git a/include/exfat_dir.h b/include/exfat_dir.h index 617f98e..3bc5b51 100644 --- a/include/exfat_dir.h +++ b/include/exfat_dir.h @@ -25,6 +25,8 @@ struct exfat_de_iter { off_t de_file_offset; off_t next_read_offset; int max_skip_dentries; +#define DOT_NAME_NUM_MAX 9999999 + unsigned int dot_name_num; }; struct exfat_lookup_filter { diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c index 2eda805..499b672 100644 --- a/lib/exfat_dir.c +++ b/lib/exfat_dir.c @@ -236,6 +236,7 @@ int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, iter->de_file_offset = 0; iter->next_read_offset = iter->read_size; iter->max_skip_dentries = 0; + iter->dot_name_num = 0; if (iter->parent->size == 0) return EOF; -- cgit v1.2.3 From a1bcd263d3f73681ac755296b2804c820175b1ac Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sat, 15 Oct 2022 00:31:36 +0900 Subject: fsck: tests: add testcase for renaming dot or dotdot entry add testcase for renaming dot or dotdot entry. Signed-off-by: Namjae Jeon --- tests/rename_dot_entry/exfat.img.tar.xz | Bin 0 -> 7336 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/rename_dot_entry/exfat.img.tar.xz diff --git a/tests/rename_dot_entry/exfat.img.tar.xz b/tests/rename_dot_entry/exfat.img.tar.xz new file mode 100644 index 0000000..6e50a72 Binary files /dev/null and b/tests/rename_dot_entry/exfat.img.tar.xz differ -- cgit v1.2.3 From 71e1a3a03fe456e1e521a1beb4a77b2d8526b5a0 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Tue, 18 Oct 2022 16:09:09 +0900 Subject: exfatprogs: update README.md Update the description about exfat2img. Signed-off-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 155dc21..d640c06 100644 --- a/README.md +++ b/README.md @@ -94,11 +94,7 @@ Usage example: Dump metadata of an exFAT filesystem Usage example: - 1. Dump metadata into a sparse file - exfat2img -o sda1.dump /dev/sda1 - 2. Dump metadata into standard out and restore a partition - exfat2img -o - /dev/sda1 | bzip2 > sda1.dump.bz2 - bzip2 -dc sda1.dump.bz2 | exfat2img -o /dev/sdb1 - + exfat2img -o sda1.dump /dev/sda1 ``` -- cgit v1.2.3 From ecd3924f75b058ff640b4179b70abbd8063e87a7 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 28 Oct 2022 10:00:46 +0900 Subject: exfatprogs: add missing updating Android.bp Add missing Android.bp in exfat2img and update it in fsck/ and lib/. Signed-off-by: Namjae Jeon --- exfat2img/Android.bp | 11 +++++++++++ fsck/Android.bp | 1 - lib/Android.bp | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 exfat2img/Android.bp diff --git a/exfat2img/Android.bp b/exfat2img/Android.bp new file mode 100644 index 0000000..3679276 --- /dev/null +++ b/exfat2img/Android.bp @@ -0,0 +1,11 @@ +// Copyright 2021 The Android Open Source Project + +cc_binary { + name: "exfat2img", + + srcs: [ + "exfat2img.c", + ], + defaults: ["exfatprogs-defaults"], + static_libs: ["libexfat"], +} diff --git a/fsck/Android.bp b/fsck/Android.bp index f267ad4..7473fea 100644 --- a/fsck/Android.bp +++ b/fsck/Android.bp @@ -4,7 +4,6 @@ cc_binary { name: "fsck.exfat", srcs: [ - "de_iter.c", "fsck.c", "repair.c", ], diff --git a/lib/Android.bp b/lib/Android.bp index 0ccf6aa..f97aff5 100644 --- a/lib/Android.bp +++ b/lib/Android.bp @@ -5,6 +5,8 @@ cc_library_static { srcs: [ "libexfat.c", + "exfat_fs.c", + "exfat_dir.c", ], defaults: ["exfatprogs-defaults"], } -- cgit v1.2.3 From 13f0e262f074b14145e204c6bcb9fcc1a3eaeb59 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 27 Oct 2022 22:47:37 +0900 Subject: exfatprogs: release 1.2.0 version exfatprogs 1.2.0 - released 2022-10-28 ====================================== CHANGES : * fsck.exfat: Keep traveling files even if there is a corrupted directory entry set. * fsck.exfat: Introduce the option "b" to recover a boot sector even if an exFAT filesystem is not found. * fsck.exfat: Introduce the option "s" to create files in "/LOST+FOUND", which have clusters allocated but was not belonged to any files. * fsck.exfat: Rename '.' and '..' entry name to the one user want. NEW FEATURES : * fsck.exfat: Repair corruptions of an exFAT filesystem. Please refer to fsck.exfat manpage to see what kind of corruptions can be repaired. * exfat2img: Dump metadata of an exFAT filesystem. Please refer to exfat2img manpage to see how to use it. BUG FIXES: * fsck.exfat: Fix an infinite loop while traveling files. * tune.exfat: Fix bitmap entry corruption when adding new volume lablel. Signed-off-by: Namjae Jeon --- NEWS | 23 +++++++++++++++++++++++ include/version.h | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 4f5ad6c..f845397 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,26 @@ +exfatprogs 1.2.0 - released 2022-10-28 +====================================== + +CHANGES : + * fsck.exfat: Keep traveling files even if there is a corrupted +directory entry set. + * fsck.exfat: Introduce the option "b" to recover a boot sector even +if an exFAT filesystem is not found. + * fsck.exfat: Introduce the option "s" to create files in +"/LOST+FOUND", which have clusters allocated but was not belonged to +any files. + * fsck.exfat: Rename '.' and '..' entry name to the one user want. + +NEW FEATURES : + * fsck.exfat: Repair corruptions of an exFAT filesystem. Please refer +to fsck.exfat manpage to see what kind of corruptions can be repaired. + * exfat2img: Dump metadata of an exFAT filesystem. Please refer to +exfat2img manpage to see how to use it. + +BUG FIXES: + * fsck.exfat: Fix an infinite loop while traveling files. + * tune.exfat: Fix bitmap entry corruption when adding new volume lablel. + exfatprogs 1.1.3 - released 2021-11-11 ====================================== diff --git a/include/version.h b/include/version.h index da9be8d..b0348d6 100644 --- a/include/version.h +++ b/include/version.h @@ -5,6 +5,6 @@ #ifndef _VERSION_H -#define EXFAT_PROGS_VERSION "1.1.3" +#define EXFAT_PROGS_VERSION "1.2.0" #endif /* !_VERSION_H */ -- cgit v1.2.3 From 470b4a6c87052d76e7422588ec6b8806d941228a Mon Sep 17 00:00:00 2001 From: Sven Hoexter Date: Fri, 28 Oct 2022 14:54:40 +0200 Subject: manpages: fix spelling mistake in fsck.exfat.8 Signed-off-by: Sven Hoexter Signed-off-by: Namjae Jeon --- manpages/fsck.exfat.8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manpages/fsck.exfat.8 b/manpages/fsck.exfat.8 index 3b247d5..5faecf0 100644 --- a/manpages/fsck.exfat.8 +++ b/manpages/fsck.exfat.8 @@ -42,7 +42,7 @@ Files share the same cluster. Cluster chains for files except one are broken. .IP - Start cluster number is invalid. The cluster number and file size are changed to 0. .IP - -Checksum value of direcotry entry set is invalid. Directory entry set is deleted. +Checksum value of directory entry set is invalid. Directory entry set is deleted. .IP - Bad hash value of a file name. The hash value is changed properly. .IP - -- cgit v1.2.3 From fcd68a50e104b7f3bea3d893eaa883db033e10de Mon Sep 17 00:00:00 2001 From: Erichong Date: Tue, 27 Dec 2022 13:11:00 +0800 Subject: fsck: Add the capability to fix zero size directory Add the capability to fix the directory which size=0 and starts with 0 cluster. This kind of directory often made by Paragon ufsd dirver, and it will cause failure when we try to it using linux exfat driver. The way to fix it, we should find a cluster and assign it to the directory's first cluster and modify the size to 1 (Like exfat driver does during mkdir). Signed-off-by: Erichong Signed-off-by: Namjae Jeon --- fsck/fsck.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- fsck/repair.c | 1 + fsck/repair.h | 2 +- include/exfat_dir.h | 1 + lib/exfat_dir.c | 4 ++-- 5 files changed, 51 insertions(+), 7 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 219d723..cd9ee9a 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -103,21 +103,31 @@ static void usage(char *name) }) static int check_clus_chain(struct exfat_de_iter *de_iter, - struct exfat_inode *node) + struct exfat_inode *node) { struct exfat *exfat = de_iter->exfat; struct exfat_dentry *stream_de; - clus_t clus, prev, next; + clus_t clus, prev, next, new_clus; uint64_t count, max_count; + int err; clus = node->first_clus; prev = EXFAT_EOF_CLUSTER; count = 0; max_count = DIV_ROUND_UP(node->size, exfat->clus_size); - if (node->size == 0 && node->first_clus == EXFAT_FREE_CLUSTER) + if (node->size == 0 && node->first_clus == EXFAT_FREE_CLUSTER) { + /* locate a cluster for the empty dir if the dir starts with EXFAT_FREE_CLUSTER */ + if (node->attr & ATTR_SUBDIR) { + if (repair_file_ask(de_iter, node, + ER_DE_FIRST_CLUS, + "size %#" PRIx64 ", but the first cluster %#x", + node->size, node->first_clus)) + goto allocate_cluster; + return -EINVAL; + } return 0; - + } /* the first cluster is wrong */ if ((node->size == 0 && node->first_clus != EXFAT_FREE_CLUSTER) || (node->size > 0 && !exfat_heap_clus(exfat, node->first_clus))) { @@ -215,6 +225,38 @@ static int check_clus_chain(struct exfat_de_iter *de_iter, } return 0; +allocate_cluster: + exfat_de_iter_get_dirty(de_iter, 1, &stream_de); + err = exfat_find_free_cluster(exfat, exfat->start_clu, &new_clus); + if (err) { + exfat->start_clu = EXFAT_FIRST_CLUSTER; + exfat_err("failed to find a free cluster\n"); + return -ENOSPC; + } + exfat->start_clu = new_clus; + + if (exfat_set_fat(exfat, new_clus, EXFAT_EOF_CLUSTER)) + return -EIO; + + /* zero out the new cluster */ + if (exfat_write(exfat->blk_dev->dev_fd, exfat->zero_cluster, + exfat->clus_size, exfat_c2o(exfat, new_clus)) != + (ssize_t)exfat->clus_size) { + exfat_err("failed to fill new cluster with zeroes\n"); + return -EIO; + } + + /* modify the number of cluster form 0 to 1 */ + count = 1; + stream_de->stream_start_clu = cpu_to_le32(new_clus); + stream_de->stream_size = cpu_to_le64(count * exfat->clus_size); + stream_de->stream_valid_size = cpu_to_le64(count * exfat->clus_size); + stream_de->dentry.stream.flags |= EXFAT_SF_CONTIGUOUS; + node->first_clus = new_clus; + node->size = count * exfat->clus_size; + node->is_contiguous = true; + exfat_bitmap_set(exfat->alloc_bitmap, new_clus); + return 1; truncate_file: node->size = count * exfat->clus_size; if (!exfat_heap_clus(exfat, prev)) diff --git a/fsck/repair.c b/fsck/repair.c index 92b1c3f..ab46f85 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -61,6 +61,7 @@ static struct exfat_repair_problem problems[] = { {ER_FILE_LARGER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, {ER_FILE_DUPLICATED_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, {ER_FILE_ZERO_NOFAT, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}, + {ER_DE_FIRST_CLUS, ERF_PREEN_YES, ERP_FIX, 0, 0, 0} }; static struct exfat_repair_problem *find_problem(er_problem_code_t prcode) diff --git a/fsck/repair.h b/fsck/repair.h index dc3cc50..ea89747 100644 --- a/fsck/repair.h +++ b/fsck/repair.h @@ -23,7 +23,7 @@ #define ER_FILE_LARGER_SIZE 0x00002005 #define ER_FILE_DUPLICATED_CLUS 0x00002006 #define ER_FILE_ZERO_NOFAT 0x00002007 - +#define ER_DE_FIRST_CLUS 0x00002008 typedef unsigned int er_problem_code_t; struct exfat_fsck; diff --git a/include/exfat_dir.h b/include/exfat_dir.h index 3bc5b51..12e1546 100644 --- a/include/exfat_dir.h +++ b/include/exfat_dir.h @@ -75,6 +75,7 @@ int exfat_update_file_dentry_set(struct exfat *exfat, int exfat_build_file_dentry_set(struct exfat *exfat, const char *name, unsigned short attr, struct exfat_dentry **dentry_set, int *dentry_count); +int exfat_find_free_cluster(struct exfat *exfat, clus_t start, clus_t *new_clu); int exfat_add_dentry_set(struct exfat *exfat, struct exfat_dentry_loc *loc, struct exfat_dentry *dset, int dcount, bool need_next_loc); diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c index 499b672..c079403 100644 --- a/lib/exfat_dir.c +++ b/lib/exfat_dir.c @@ -677,7 +677,7 @@ int exfat_update_file_dentry_set(struct exfat *exfat, return 0; } -static int find_free_cluster(struct exfat *exfat, +int exfat_find_free_cluster(struct exfat *exfat, clus_t start, clus_t *new_clu) { clus_t end = le32_to_cpu(exfat->bs->bsx.clu_count) + @@ -804,7 +804,7 @@ static int exfat_alloc_cluster(struct exfat *exfat, struct exfat_inode *inode, if ((need_dset && !inode->dentry_set) || inode->is_contiguous) return -EINVAL; - err = find_free_cluster(exfat, exfat->start_clu, new_clu); + err = exfat_find_free_cluster(exfat, exfat->start_clu, new_clu); if (err) { exfat->start_clu = EXFAT_FIRST_CLUSTER; exfat_err("failed to find an free cluster\n"); -- cgit v1.2.3 From 15704469c0ee2ef1dc7a753c40db629418a40e2c Mon Sep 17 00:00:00 2001 From: Erichong Date: Thu, 22 Dec 2022 12:43:30 +0800 Subject: tests: Add testcase for empty directory starts with zero cluster. Signed-off-by: Erichong Signed-off-by: Namjae Jeon --- tests/directory_no_clu/exfat.img.tar.xz | Bin 0 -> 3900 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/directory_no_clu/exfat.img.tar.xz diff --git a/tests/directory_no_clu/exfat.img.tar.xz b/tests/directory_no_clu/exfat.img.tar.xz new file mode 100644 index 0000000..6c92ca5 Binary files /dev/null and b/tests/directory_no_clu/exfat.img.tar.xz differ -- cgit v1.2.3 From 1bd2214fb123e811b14690b359c4de427d66aa72 Mon Sep 17 00:00:00 2001 From: brunosampaio08 Date: Mon, 6 Feb 2023 18:10:58 -0300 Subject: exfat2img: fix LLVM void pointer arithmetic error What: On LLVM and Clang with [-Werror,-Wno-pointer-arith] void pointer arithmetic is not allowed and will cause a compilation error. Fix: Fix it by casting buf to (char *) before doing arithmetic. Char * is 1 byte long, which is the same that GCC defines for void pointer arithmetic calculations so final result is the same. Signed-off-by: brunosampaio08 Signed-off-by: Namjae Jeon --- exfat2img/exfat2img.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exfat2img/exfat2img.c b/exfat2img/exfat2img.c index 81e86d2..be1f0f3 100644 --- a/exfat2img/exfat2img.c +++ b/exfat2img/exfat2img.c @@ -754,7 +754,7 @@ static ssize_t read_stream(int fd, void *buf, size_t len) } else if (ret == 0) { return 0; } - buf += (size_t)ret; + buf = (char *)buf + (size_t)ret; read_len += (size_t)ret; } return read_len; -- cgit v1.2.3 From 4355a84c3b1edf55561c5ada71e2b4ed309260c7 Mon Sep 17 00:00:00 2001 From: Erichong Date: Fri, 21 Apr 2023 15:15:38 +0800 Subject: lib: do not allow exfat_c2o() to return negative value assert that clus should be >= EXFAT_FIRST_CLUSTER, instead of return a negative value. Acked-by: Hyunchul Lee Signed-off-by: Erichong Signed-off-by: Namjae Jeon --- lib/libexfat.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/libexfat.c b/lib/libexfat.c index d7c1df1..4fd4ac6 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "exfat_ondisk.h" #include "libexfat.h" @@ -748,8 +749,7 @@ off_t exfat_s2o(struct exfat *exfat, off_t sect) off_t exfat_c2o(struct exfat *exfat, unsigned int clus) { - if (clus < EXFAT_FIRST_CLUSTER) - return ~0L; + assert(clus >= EXFAT_FIRST_CLUSTER); return exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset) + ((off_t)(clus - EXFAT_FIRST_CLUSTER) << -- cgit v1.2.3 From 6bfcd9f5920ebad7f2168ad2622214fad42b0e4a Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Wed, 17 May 2023 09:49:40 +0900 Subject: fsck: fix assertion in exfat_c2o exfat_c2o() is called with the cluster 0, and fsck is aborted with the stack backtrace below due to the assertion in exfat_c2o(). exfat_c2o write_block exfat_de_iter_flush This is caused by that write_block() could be called for the cluster which is not read yet. Make exfat_c2o is called only when the cluster is dirty. Signed-off-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- lib/exfat_dir.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c index c079403..7c145f4 100644 --- a/lib/exfat_dir.c +++ b/lib/exfat_dir.c @@ -35,10 +35,11 @@ static ssize_t write_block(struct exfat_de_iter *iter, unsigned int block) unsigned int i; desc = &iter->buffer_desc[block & 0x01]; - device_offset = exfat_c2o(exfat, desc->p_clus) + desc->offset; for (i = 0; i < iter->read_size / iter->write_size; i++) { if (desc->dirty[i]) { + device_offset = exfat_c2o(exfat, desc->p_clus) + + desc->offset; if (exfat_write(exfat->blk_dev->dev_fd, desc->buffer + i * iter->write_size, iter->write_size, -- cgit v1.2.3 From cb607485dc370ae66889f8a495ae1aa10caa9a8d Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 16 May 2023 22:43:25 +0900 Subject: exfatprogs: release 1.2.1 version exfatprogs 1.2.1 - released 2023-05-17 ====================================== CHANGES : * fsck.exfat: Repair zero size directory. * fsck.exfat: Four small clean-ups. Signed-off-by: Namjae Jeon --- NEWS | 7 +++++++ include/version.h | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index f845397..f371d62 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,10 @@ +exfatprogs 1.2.1 - released 2023-05-17 +====================================== + +CHANGES : + * fsck.exfat: Repair zero size directory. + * fsck.exfat: Four small clean-ups. + exfatprogs 1.2.0 - released 2022-10-28 ====================================== diff --git a/include/version.h b/include/version.h index b0348d6..f0b250c 100644 --- a/include/version.h +++ b/include/version.h @@ -5,6 +5,6 @@ #ifndef _VERSION_H -#define EXFAT_PROGS_VERSION "1.2.0" +#define EXFAT_PROGS_VERSION "1.2.1" #endif /* !_VERSION_H */ -- cgit v1.2.3