// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2019 Namjae Jeon */ #include #include #include #include #include #include #include #include #include #include #include #include #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) 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) { 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; 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); } wchar_t exfat_bad_char(wchar_t w) { return (w < 0x0020) || (w == '*') || (w == '?') || (w == '<') || (w == '>') || (w == '|') || (w == '"') || (w == ':') || (w == '/') || (w == '\\'); } void boot_calc_checksum(unsigned char *sector, unsigned short size, bool is_boot_sec, __le32 *checksum) { unsigned int index; if (is_boot_sec) { for (index = 0; index < size; index++) { if ((index == 106) || (index == 107) || (index == 112)) continue; *checksum = ((*checksum & 1) ? 0x80000000 : 0) + (*checksum >> 1) + sector[index]; } } else { for (index = 0; index < size; index++) { *checksum = ((*checksum & 1) ? 0x80000000 : 0) + (*checksum >> 1) + sector[index]; } } } void show_version(void) { printf("exfatprogs version : %s\n", EXFAT_PROGS_VERSION); } static inline unsigned int sector_size_bits(unsigned int size) { unsigned int bits = 8; do { bits++; size >>= 1; } while (size > 256); return bits; } static void exfat_set_default_cluster_size(struct exfat_blk_dev *bd, struct exfat_user_input *ui) { if (256 * MB >= bd->size) ui->cluster_size = 4 * KB; else if (32 * GB >= bd->size) ui->cluster_size = 32 * KB; else ui->cluster_size = 128 * KB; } void init_user_input(struct exfat_user_input *ui) { memset(ui, 0, sizeof(struct exfat_user_input)); ui->writeable = true; ui->quick = true; } int exfat_get_blk_dev_info(struct exfat_user_input *ui, struct exfat_blk_dev *bd) { int fd, ret = -1; off_t blk_dev_size; struct stat st; unsigned long long blk_dev_offset = 0; fd = open(ui->dev_name, ui->writeable ? O_RDWR|O_EXCL : O_RDONLY); if (fd < 0) { exfat_err("open failed : %s, %s\n", ui->dev_name, strerror(errno)); return -1; } blk_dev_size = lseek(fd, 0, SEEK_END); if (blk_dev_size <= 0) { exfat_err("invalid block device size(%s)\n", ui->dev_name); ret = blk_dev_size; close(fd); goto out; } if (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode)) { char pathname[sizeof("/sys/dev/block/4294967295:4294967295/start")]; FILE *fp; snprintf(pathname, sizeof(pathname), "/sys/dev/block/%u:%u/start", major(st.st_rdev), minor(st.st_rdev)); fp = fopen(pathname, "r"); if (fp != NULL) { if (fscanf(fp, "%llu", &blk_dev_offset) == 1) { /* * Linux kernel always reports partition offset * in 512-byte units, regardless of sector size */ blk_dev_offset <<= 9; } fclose(fp); } } bd->dev_fd = fd; bd->offset = blk_dev_offset; bd->size = blk_dev_size; if (!ui->cluster_size) exfat_set_default_cluster_size(bd, ui); if (!ui->boundary_align) ui->boundary_align = DEFAULT_BOUNDARY_ALIGNMENT; 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_clusters = blk_dev_size / ui->cluster_size; exfat_debug("Block device name : %s\n", ui->dev_name); exfat_debug("Block device offset : %llu\n", bd->offset); exfat_debug("Block device size : %llu\n", bd->size); exfat_debug("Block sector size : %u\n", bd->sector_size); exfat_debug("Number of the sectors : %llu\n", bd->num_sectors); exfat_debug("Number of the clusters : %u\n", bd->num_clusters); ret = 0; bd->dev_fd = fd; out: return ret; } ssize_t exfat_read(int fd, void *buf, size_t size, off_t offset) { return pread(fd, buf, size, offset); } ssize_t exfat_write(int fd, void *buf, size_t size, off_t offset) { return pwrite(fd, buf, size, offset); } size_t exfat_utf16_len(const __le16 *str, size_t max_size) { size_t i = 0; while (le16_to_cpu(str[i]) && i < max_size) i++; return i; } ssize_t exfat_utf16_enc(const char *in_str, __u16 *out_str, size_t out_size) { size_t mbs_len, out_len, i; wchar_t *wcs; mbs_len = mbstowcs(NULL, in_str, 0); if (mbs_len == (size_t)-1) { if (errno == EINVAL || errno == EILSEQ) exfat_err("invalid character sequence in current locale\n"); return -errno; } wcs = calloc(mbs_len+1, sizeof(wchar_t)); if (!wcs) return -ENOMEM; /* First convert multibyte char* string to wchar_t* string */ if (mbstowcs(wcs, in_str, mbs_len+1) == (size_t)-1) { if (errno == EINVAL || errno == EILSEQ) exfat_err("invalid character sequence in current locale\n"); free(wcs); return -errno; } /* Convert wchar_t* string (sequence of code points) to UTF-16 string */ for (i = 0, out_len = 0; i < mbs_len; i++) { if (2*(out_len+1) > out_size || (wcs[i] >= 0x10000 && 2*(out_len+2) > out_size)) { exfat_err("input string is too long\n"); free(wcs); return -E2BIG; } /* Encode code point above Plane0 as UTF-16 surrogate pair */ if (wcs[i] >= 0x10000) { out_str[out_len++] = cpu_to_le16(((wcs[i] - 0x10000) >> 10) + 0xD800); wcs[i] = ((wcs[i] - 0x10000) & 0x3FF) + 0xDC00; } out_str[out_len++] = cpu_to_le16(wcs[i]); } free(wcs); return 2*out_len; } ssize_t exfat_utf16_dec(const __u16 *in_str, size_t in_len, char *out_str, size_t out_size) { size_t wcs_len, out_len, c_len, i; char c_str[MB_LEN_MAX]; wchar_t *wcs; mbstate_t ps; wchar_t w; wcs = calloc(in_len/2+1, sizeof(wchar_t)); if (!wcs) return -ENOMEM; /* First convert UTF-16 string to wchar_t* string */ for (i = 0, wcs_len = 0; i < in_len/2; i++, wcs_len++) { wcs[wcs_len] = le16_to_cpu(in_str[i]); /* * If wchar_t can store code point above Plane0 * then unpack UTF-16 surrogate pair to code point */ #if WCHAR_MAX >= 0x10FFFF if (wcs[wcs_len] >= 0xD800 && wcs[wcs_len] <= 0xDBFF && i+1 < in_len/2) { w = le16_to_cpu(in_str[i+1]); if (w >= 0xDC00 && w <= 0xDFFF) { wcs[wcs_len] = 0x10000 + ((wcs[wcs_len] - 0xD800) << 10) + (w - 0xDC00); i++; } } #endif } memset(&ps, 0, sizeof(ps)); /* And then convert wchar_t* string to multibyte char* string */ for (i = 0, out_len = 0, c_len = 0; i <= wcs_len; i++) { c_len = wcrtomb(c_str, wcs[i], &ps); /* * If character is non-representable in current locale then * try to store it as Unicode replacement code point U+FFFD */ if (c_len == (size_t)-1 && errno == EILSEQ) c_len = wcrtomb(c_str, 0xFFFD, &ps); /* If U+FFFD is also non-representable, try question mark */ if (c_len == (size_t)-1 && errno == EILSEQ) c_len = wcrtomb(c_str, L'?', &ps); /* If also (7bit) question mark fails then we cannot do more */ if (c_len == (size_t)-1) { exfat_err("invalid UTF-16 sequence\n"); free(wcs); return -errno; } if (out_len+c_len > out_size) { exfat_err("input string is too long\n"); free(wcs); return -E2BIG; } memcpy(out_str+out_len, c_str, c_len); out_len += c_len; } free(wcs); /* Last iteration of above loop should have produced null byte */ if (c_len == 0 || out_str[out_len-1] != 0) { exfat_err("invalid UTF-16 sequence\n"); return -errno; } return out_len-1; } off_t exfat_get_root_entry_offset(struct exfat_blk_dev *bd) { struct pbr *bs; int nbytes; unsigned int cluster_size; off_t root_clu_off; bs = (struct pbr *)malloc(sizeof(struct pbr)); 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)) { 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; free(bs); return root_clu_off; } char *exfat_conv_volume_label(struct exfat_dentry *vol_entry) { char *volume_label; __le16 disk_label[VOLUME_LABEL_MAX_LEN]; volume_label = malloc(VOLUME_LABEL_BUFFER_SIZE); if (!volume_label) return NULL; memcpy(disk_label, vol_entry->vol_label, sizeof(disk_label)); memset(volume_label, 0, VOLUME_LABEL_BUFFER_SIZE); if (exfat_utf16_dec(disk_label, vol_entry->vol_char_cnt*2, volume_label, VOLUME_LABEL_BUFFER_SIZE) < 0) { exfat_err("failed to decode volume label\n"); free(volume_label); return NULL; } return volume_label; } int exfat_show_volume_label(struct exfat_blk_dev *bd, off_t root_clu_off) { struct exfat_dentry *vol_entry; char *volume_label; int nbytes; vol_entry = malloc(sizeof(struct exfat_dentry)); if (!vol_entry) { exfat_err("failed to allocate memory\n"); return -ENOMEM; } 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; } volume_label = exfat_conv_volume_label(vol_entry); if (!volume_label) { free(vol_entry); return -EINVAL; } exfat_info("label: %s\n", volume_label); free(volume_label); free(vol_entry); return 0; } int exfat_set_volume_label(struct exfat_blk_dev *bd, char *label_input, off_t root_clu_off) { struct exfat_dentry vol; int nbytes; __u16 volume_label[VOLUME_LABEL_MAX_LEN]; int volume_label_len; 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"); 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); exfat_info("new label: %s\n", label_input); return 0; } 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); if (ret < 0) { exfat_err("read failed, sec_off : %u\n", sec_off); return -1; } return 0; } 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; lseek(bd->dev_fd, offset, SEEK_SET); bytes = write(bd->dev_fd, buf, bd->sector_size); if (bytes != (int)bd->sector_size) { exfat_err("write failed, sec_off : %u, bytes : %d\n", sec_off, bytes); return -1; } return 0; } int exfat_write_checksum_sector(struct exfat_blk_dev *bd, unsigned int checksum, bool is_backup) { __le32 *checksum_buf; int ret = 0; unsigned int i; unsigned int sec_idx = CHECKSUM_SEC_IDX; checksum_buf = malloc(bd->sector_size); if (!checksum_buf) return -1; if (is_backup) sec_idx += BACKUP_BOOT_SEC_IDX; for (i = 0; i < bd->sector_size / sizeof(int); i++) checksum_buf[i] = cpu_to_le32(checksum); ret = exfat_write_sector(bd, checksum_buf, sec_idx); if (ret) { exfat_err("checksum sector write failed\n"); goto free; } free: free(checksum_buf); return ret; } int exfat_show_volume_serial(struct exfat_blk_dev *bd, struct exfat_user_input *ui) { struct pbr *ppbr; int ret; ppbr = malloc(bd->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); if (ret < 0) { exfat_err("main boot sector read failed\n"); ret = -1; goto free_ppbr; } exfat_info("volume serial : 0x%x\n", ppbr->bsx.vol_serial); free_ppbr: free(ppbr); return ret; } 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); if (!buf) { exfat_err("Cannot allocate pbr: out of memory\n"); return -1; } if (is_backup) backup_sec_idx = BACKUP_BOOT_SEC_IDX; for (sec_idx = BOOT_SEC_IDX; sec_idx < CHECKSUM_SEC_IDX; sec_idx++) { bool is_boot_sec = false; ret = exfat_read_sector(bd, buf, sec_idx + backup_sec_idx); if (ret < 0) { exfat_err("sector(%d) read failed\n", sec_idx); ret = -1; goto free_buf; } 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, &checksum); } ret = exfat_write_checksum_sector(bd, checksum, is_backup); free_buf: free(buf); return ret; } int exfat_set_volume_serial(struct exfat_blk_dev *bd, struct exfat_user_input *ui) { int ret; struct pbr *ppbr; ppbr = malloc(bd->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); if (ret < 0) { exfat_err("main boot sector read failed\n"); ret = -1; goto free_ppbr; } ppbr->bsx.vol_serial = ui->volume_serial; /* update main boot sector */ ret = exfat_write_sector(bd, (char *)ppbr, BOOT_SEC_IDX); if (ret < 0) { exfat_err("main boot sector write failed\n"); ret = -1; goto free_ppbr; } /* update backup boot sector */ ret = exfat_write_sector(bd, (char *)ppbr, BACKUP_BOOT_SEC_IDX); if (ret < 0) { exfat_err("backup boot sector write failed\n"); ret = -1; goto free_ppbr; } ret = exfat_update_boot_checksum(bd, 0); if (ret < 0) { exfat_err("main checksum update failed\n"); goto free_ppbr; } ret = exfat_update_boot_checksum(bd, 1); if (ret < 0) exfat_err("backup checksum update failed\n"); free_ppbr: free(ppbr); exfat_info("New volume serial : 0x%x\n", ui->volume_serial); return ret; } unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd, unsigned int clu_off_sectnr, unsigned int clu) { return clu_off_sectnr * bd->sector_size + (clu - EXFAT_RESERVED_CLUSTERS) * bd->cluster_size; }