X-Git-Url: https://git.sven.stormbind.net/?p=sven%2Fexfatprogs.git;a=blobdiff_plain;f=lib%2Flibexfat.c;fp=lib%2Flibexfat.c;h=d7c1df1ca655d34400c30d9e5aef80e54f63d8dc;hp=c1c9b037c612a4e6bc24704aace55e6a4aaa529c;hb=03290761e3849db9b13d2d3b176b36ab31c395bb;hpb=dcb4e80c267579d1a68e3f34f6b750e2940dbcc2 diff --git a/lib/libexfat.c b/lib/libexfat.c index c1c9b03..d7c1df1 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -19,64 +19,57 @@ #include "exfat_ondisk.h" #include "libexfat.h" #include "version.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) +#include "exfat_fs.h" +#include "exfat_dir.h" unsigned int print_level = EXFAT_INFO; -static inline void set_bit(int nr, void *addr) +void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, + clus_t start_clus, clus_t count) { - unsigned long mask = BIT_MASK(nr); - unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); + clus_t clus; - *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); + if (!exfat_heap_clus(exfat, start_clus) || + !exfat_heap_clus(exfat, start_clus + count - 1)) + return; - *p &= ~mask; + clus = start_clus; + while (clus < start_clus + count) { + exfat_bitmap_set(bitmap, clus); + clus++; + } } -static inline void set_bit_le(int nr, void *addr) +static int exfat_bitmap_find_bit(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next, + int bit) { - set_bit(nr ^ BITOP_LE_SWIZZLE, addr); + 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) == bit) { + *next = start_clu; + return 0; + } + start_clu++; + } + return 1; } -static inline void clear_bit_le(int nr, void *addr) +int exfat_bitmap_find_zero(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next) { - clear_bit(nr ^ BITOP_LE_SWIZZLE, addr); + return exfat_bitmap_find_bit(exfat, bmap, + start_clu, next, 0); } -void exfat_set_bit(struct exfat_blk_dev *bd, char *bitmap, - unsigned int clu) +int exfat_bitmap_find_one(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next) { - int b; - - b = clu & ((bd->sector_size << 3) - 1); - - 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); + return exfat_bitmap_find_bit(exfat, bmap, + start_clu, next, 1); } wchar_t exfat_bad_char(wchar_t w) @@ -374,6 +367,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 + @@ -405,75 +404,98 @@ 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; - - 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; - } - fsync(bd->dev_fd); + memcpy(pvol->vol_label, volume_label, volume_label_len); + pvol->vol_char_cnt = volume_label_len/2; + 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); - return 0; + + free(pvol); + + return err; } 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) { @@ -487,7 +509,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) { @@ -546,6 +569,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 +643,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; @@ -656,3 +691,166 @@ 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, clus_t clus, clus_t *next) +{ + off_t offset; + + *next = EXFAT_EOF_CLUSTER; + + if (!exfat_heap_clus(exfat, clus)) + return -EINVAL; + + 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_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; + + 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)); +} + +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 && + (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; +}