static int commit_super_block(const struct exfat* ef) { if (exfat_pwrite(ef->dev, ef->sb, sizeof(struct exfat_super_block), 0) < 0) { exfat_error("failed to write super block"); return 1; } return exfat_fsync(ef->dev); }
ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node, void* buffer, size_t size, off64_t offset) { cluster_t cluster; char* bufp = buffer; off64_t lsize, loffset, remainder; if (offset >= node->size) return 0; if (size == 0) return 0; cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb)); if (CLUSTER_INVALID(cluster)) { exfat_error("invalid cluster 0x%x while reading", cluster); return -1; } loffset = offset % CLUSTER_SIZE(*ef->sb); remainder = MIN(size, node->size - offset); while (remainder > 0) { if (CLUSTER_INVALID(cluster)) { exfat_error("invalid cluster 0x%x while reading", cluster); return -1; } lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder); if (exfat_pread(ef->dev, bufp, lsize, exfat_c2o(ef, cluster) + loffset) < 0) { exfat_error("failed to read cluster %#x", cluster); return -1; } bufp += lsize; loffset = 0; remainder -= lsize; cluster = exfat_next_cluster(ef, node, cluster); } if (!ef->ro && !ef->noatime) exfat_update_atime(node); return MIN(size, node->size - offset) - remainder; }
static int fuse_exfat_readdir(const char* path, void* buffer, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info* fi) { struct exfat_node* parent; struct exfat_node* node; struct exfat_iterator it; int rc; char name[UTF8_BYTES(EXFAT_NAME_MAX) + 1]; exfat_debug("[%s] %s", __func__, path); rc = exfat_lookup(&ef, &parent, path); if (rc != 0) return rc; if (!(parent->flags & EXFAT_ATTRIB_DIR)) { exfat_put_node(&ef, parent); exfat_error("'%s' is not a directory (0x%x)", path, parent->flags); return -ENOTDIR; } filler(buffer, ".", NULL, 0); filler(buffer, "..", NULL, 0); rc = exfat_opendir(&ef, parent, &it); if (rc != 0) { exfat_put_node(&ef, parent); exfat_error("failed to open directory '%s'", path); return rc; } while ((node = exfat_readdir(&ef, &it))) { exfat_get_name(node, name, sizeof(name) - 1); exfat_debug("[%s] %s: %s, %"PRId64" bytes, cluster 0x%x", __func__, name, IS_CONTIGUOUS(*node) ? "contiguous" : "fragmented", node->size, node->start_cluster); filler(buffer, name, NULL, 0); exfat_put_node(&ef, node); } exfat_closedir(&ef, &it); exfat_put_node(&ef, parent); return 0; }
static int uct_write(struct exfat_dev* dev) { if (exfat_write(dev, upcase_table, sizeof(upcase_table)) < 0) { exfat_error("failed to write upcase table of %zu bytes", sizeof(upcase_table)); return 1; } return 0; }
int exfat_close(struct exfat_dev* dev) { int rc = 0; #ifdef USE_UBLIO if (ublio_close(dev->ufh) != 0) { exfat_error("failed to close ublio"); rc = -EIO; } #endif if (close(dev->fd) != 0) { exfat_error("failed to close device: %s", strerror(errno)); rc = -EIO; } free(dev); return rc; }
ssize_t exfat_write(struct exfat* ef, struct exfat_node* node, const void* buffer, size_t size, off_t offset) { cluster_t cluster; const char* bufp = buffer; off_t lsize, loffset, remainder; if (offset + size > node->size) { int rc = exfat_truncate(ef, node, offset + size); if (rc != 0) return rc; } if (size == 0) return 0; cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb)); if (CLUSTER_INVALID(cluster)) { exfat_error("got invalid cluster"); return -1; } loffset = offset % CLUSTER_SIZE(*ef->sb); remainder = size; while (remainder > 0) { if (CLUSTER_INVALID(cluster)) { exfat_error("got invalid cluster"); return -1; } lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder); exfat_write_raw(bufp, lsize, exfat_c2o(ef, cluster) + loffset, ef->fd); bufp += lsize; loffset = 0; remainder -= lsize; cluster = exfat_next_cluster(ef, node, cluster); } exfat_update_mtime(node); return size - remainder; }
static cluster_t fat_write_entry(struct exfat_dev* dev, cluster_t cluster, cluster_t value) { le32_t fat_entry = cpu_to_le32(value); if (exfat_write(dev, &fat_entry, sizeof(fat_entry)) < 0) { exfat_error("failed to write FAT entry 0x%x", value); return 0; } return cluster + 1; }
static struct exfat_node* allocate_node(void) { struct exfat_node* node = malloc(sizeof(struct exfat_node)); if (node == NULL) { exfat_error("failed to allocate node"); return NULL; } memset(node, 0, sizeof(struct exfat_node)); return node; }
static char* add_fsname_option(char* options, const char* spec) { char spec_abs[PATH_MAX]; if (realpath(spec, spec_abs) == NULL) { free(options); exfat_error("failed to get absolute path for `%s'", spec); return NULL; } return add_option(options, "fsname", spec_abs); }
ssize_t exfat_read(const struct exfat* ef, struct exfat_node* node, void* buffer, size_t size, off_t offset) { cluster_t cluster; char* bufp = buffer; off_t lsize, loffset, remainder; if (offset >= node->size) return 0; if (size == 0) return 0; cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb)); if (CLUSTER_INVALID(cluster)) { exfat_error("got invalid cluster"); return -1; } loffset = offset % CLUSTER_SIZE(*ef->sb); remainder = MIN(size, node->size - offset); while (remainder > 0) { if (CLUSTER_INVALID(cluster)) { exfat_error("got invalid cluster"); return -1; } lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder); exfat_read_raw(bufp, lsize, exfat_c2o(ef, cluster) + loffset, ef->fd); bufp += lsize; loffset = 0; remainder -= lsize; cluster = exfat_next_cluster(ef, node, cluster); } if (!ef->ro && !ef->noatime) exfat_update_atime(node); return size - remainder; }
int exfat_fsync(struct exfat_dev* dev) { #ifdef USE_UBLIO if (ublio_fsync(dev->ufh) != 0) #else if (fsync(dev->fd) != 0) #endif { exfat_error("fsync failed"); return 1; } return 0; }
static int erase_object(struct exfat_dev* dev, const void* block, size_t block_size, off64_t start, off64_t size) { const off64_t block_count = DIV_ROUND_UP(size, block_size); off64_t i; if (exfat_seek(dev, start, SEEK_SET) == (off64_t) -1) { exfat_error("seek to 0x%"PRIx64" failed", start); return 1; } for (i = 0; i < size; i += block_size) { if (exfat_write(dev, block, MIN(size - i, block_size)) < 0) { exfat_error("failed to erase block %"PRIu64"/%"PRIu64 " at 0x%"PRIx64, i + 1, block_count, start); return 1; } } return 0; }
static int opendir(struct exfat* ef, const struct exfat_node* dir, struct iterator* it) { if (!(dir->flags & EXFAT_ATTRIB_DIR)) exfat_bug("not a directory"); it->cluster = dir->start_cluster; it->offset = 0; it->contiguous = IS_CONTIGUOUS(*dir); it->chunk = malloc(CLUSTER_SIZE(*ef->sb)); if (it->chunk == NULL) { exfat_error("out of memory"); return -ENOMEM; } if (exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb), exfat_c2o(ef, it->cluster)) < 0) { exfat_error("failed to read directory cluster %#x", it->cluster); return -EIO; } return 0; }
static uint32_t setup_volume_serial(uint32_t user_defined) { struct timeval now; if (user_defined != 0) return user_defined; if (gettimeofday(&now, NULL) != 0) { exfat_error("failed to form volume id"); return 0; } return (now.tv_sec << 20) | now.tv_usec; }
static bool erase_entry(struct exfat* ef, struct exfat_node* node) { cluster_t cluster = node->entry_cluster; off_t offset = node->entry_offset; int name_entries = DIV_ROUND_UP(utf16_length(node->name), EXFAT_ENAME_MAX); uint8_t entry_type; entry_type = EXFAT_ENTRY_FILE & ~EXFAT_ENTRY_VALID; if (exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset)) < 0) { exfat_error("failed to erase meta1 entry"); return false; } if (!next_entry(ef, node->parent, &cluster, &offset)) return false; entry_type = EXFAT_ENTRY_FILE_INFO & ~EXFAT_ENTRY_VALID; if (exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset)) < 0) { exfat_error("failed to erase meta2 entry"); return false; } while (name_entries--) { if (!next_entry(ef, node->parent, &cluster, &offset)) return false; entry_type = EXFAT_ENTRY_FILE_NAME & ~EXFAT_ENTRY_VALID; if (exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset)) < 0) { exfat_error("failed to erase name entry"); return false; } } return true; }
static bool verify_vbr_checksum(struct exfat_dev* dev, void* sector, fbx_off_t sector_size) { uint32_t vbr_checksum; int i; if (exfat_pread(dev, sector, sector_size, 0) < 0) { exfat_error("failed to read boot sector"); return false; } vbr_checksum = exfat_vbr_start_checksum(sector, sector_size); for (i = 1; i < 11; i++) { if (exfat_pread(dev, sector, sector_size, i * sector_size) < 0) { exfat_error("failed to read VBR sector"); return false; } vbr_checksum = exfat_vbr_add_checksum(sector, sector_size, vbr_checksum); } if (exfat_pread(dev, sector, sector_size, i * sector_size) < 0) { exfat_error("failed to read VBR checksum sector"); return false; } for (i = 0; i < sector_size / sizeof(vbr_checksum); i++) if (le32_to_cpu(((const le32_t*) sector)[i]) != vbr_checksum) { exfat_error("invalid VBR checksum 0x%x (expected 0x%x)", le32_to_cpu(((const le32_t*) sector)[i]), vbr_checksum); return false; } return true; }
int exfat_flush(struct exfat* ef) { if (ef->cmap.dirty) { if (exfat_pwrite(ef->dev, ef->cmap.chunk, BMAP_SIZE(ef->cmap.chunk_size), exfat_c2o(ef, ef->cmap.start_cluster)) < 0) { exfat_error("failed to write clusters bitmap"); return -EIO; } ef->cmap.dirty = false; } return 0; }
static bool next_entry(struct exfat* ef, const struct exfat_node* parent, cluster_t* cluster, off_t* offset) { *offset += sizeof(struct exfat_entry); if (*offset % CLUSTER_SIZE(*ef->sb) == 0) { *cluster = exfat_next_cluster(ef, parent, *cluster); if (CLUSTER_INVALID(*cluster)) { exfat_error("invalid cluster %#x while getting next entry", *cluster); return false; } } return true; }
static char* add_user_option(char* options) { struct passwd* pw; if (getuid() == 0) return options; pw = getpwuid(getuid()); if (pw == NULL || pw->pw_name == NULL) { free(options); exfat_error("failed to determine username"); return NULL; } return add_option(options, "user", pw->pw_name); }
static int create(struct exfat_dev* dev) { const struct fs_object** pp; off64_t position = 0; for (pp = objects; *pp; pp++) { position = ROUND_UP(position, (*pp)->get_alignment()); if (exfat_seek(dev, position, SEEK_SET) == (off64_t) -1) { exfat_error("seek to 0x%"PRIx64" failed", position); return 1; } if ((*pp)->write(dev) != 0) return 1; position += (*pp)->get_size(); } return 0; }
static int fetch_next_entry(struct exfat* ef, const struct exfat_node* parent, struct iterator* it) { /* move iterator to the next entry in the directory */ it->offset += sizeof(struct exfat_entry); /* fetch the next cluster if needed */ if ((it->offset & (CLUSTER_SIZE(*ef->sb) - 1)) == 0) { it->cluster = exfat_next_cluster(ef, parent, it->cluster); if (CLUSTER_INVALID(it->cluster)) { exfat_error("invalid cluster while reading directory"); return 1; } exfat_read_raw(it->chunk, CLUSTER_SIZE(*ef->sb), exfat_c2o(ef, it->cluster), ef->fd); } return 0; }
static char* add_fsname_option(char* options, const char* spec) { /* escaped string cannot be more than twice as big as the original one */ char* escaped = malloc(strlen(spec) * 2 + 1); if (escaped == NULL) { free(options); exfat_error("failed to allocate escaped string for %s", spec); return NULL; } /* on some platforms (e.g. Android, Solaris) device names can contain commas */ escape(escaped, spec); options = add_option(options, "fsname", escaped); free(escaped); return options; }
static bool set_next_cluster(const struct exfat* ef, bool contiguous, cluster_t current, cluster_t next) { loff_t fat_offset; le32_t next_le32; if (contiguous) return true; fat_offset = s2o(ef, le32_to_cpu(ef->sb->fat_sector_start)) + current * sizeof(cluster_t); next_le32 = cpu_to_le32(next); if (exfat_pwrite(ef->dev, &next_le32, sizeof(next_le32), fat_offset) < 0) { exfat_error("failed to write the next cluster %#x after %#x", next, current); return false; } return true; }
static cluster_t allocate_cluster(struct exfat* ef, cluster_t hint) { cluster_t cluster; hint -= EXFAT_FIRST_DATA_CLUSTER; if (hint >= ef->cmap.chunk_size) hint = 0; cluster = find_bit_and_set(ef->cmap.chunk, hint, ef->cmap.chunk_size); if (cluster == EXFAT_CLUSTER_END) cluster = find_bit_and_set(ef->cmap.chunk, 0, hint); if (cluster == EXFAT_CLUSTER_END) { exfat_error("no free space left"); return EXFAT_CLUSTER_END; } ef->cmap.dirty = true; return cluster; }
static int check_size(off64_t volume_size) { const struct fs_object** pp; off64_t position = 0; for (pp = objects; *pp; pp++) { position = ROUND_UP(position, (*pp)->get_alignment()); position += (*pp)->get_size(); } if (position > volume_size) { struct exfat_human_bytes vhb; exfat_humanize_bytes(volume_size, &vhb); exfat_error("too small device (%"PRIu64" %s)", vhb.value, vhb.unit); return 1; } return 0; }
static int fetch_next_entry(struct exfat* ef, const struct exfat_node* parent, struct iterator* it) { /* move iterator to the next entry in the directory */ it->offset += sizeof(struct exfat_entry); /* fetch the next cluster if needed */ if ((it->offset & (CLUSTER_SIZE(*ef->sb) - 1)) == 0) { /* reached the end of directory; the caller should check this condition too */ if (it->offset >= parent->size) return 0; it->cluster = exfat_next_cluster(ef, parent, it->cluster); if (CLUSTER_INVALID(it->cluster)) { exfat_error("invalid cluster 0x%x while reading directory", it->cluster); return 1; } exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb), exfat_c2o(ef, it->cluster)); } return 0; }
static char* add_option(char* options, const char* name, const char* value) { size_t size; if (value) size = strlen(options) + strlen(name) + strlen(value) + 3; else size = strlen(options) + strlen(name) + 2; options = realloc(options, size); if (options == NULL) { exfat_error("failed to reallocate options string"); return NULL; } strcat(options, ","); strcat(options, name); if (value) { strcat(options, "="); strcat(options, value); } return options; }
int exfat_flush_node(struct exfat* ef, struct exfat_node* node) { cluster_t cluster; off64_t offset; off64_t meta1_offset, meta2_offset; struct exfat_entry_meta1 meta1; struct exfat_entry_meta2 meta2; if (!(node->flags & EXFAT_ATTRIB_DIRTY)) return 0; /* no need to flush */ if (ef->ro) exfat_bug("unable to flush node to read-only FS"); if (node->parent == NULL) return 0; /* do not flush unlinked node */ cluster = node->entry_cluster; offset = node->entry_offset; meta1_offset = co2o(ef, cluster, offset); next_entry(ef, node->parent, &cluster, &offset); meta2_offset = co2o(ef, cluster, offset); if (exfat_pread(ef->dev, &meta1, sizeof(meta1), meta1_offset) < 0) { exfat_error("failed to read meta1 entry on flush"); return -EIO; } if (meta1.type != EXFAT_ENTRY_FILE) exfat_bug("invalid type of meta1: 0x%hhx", meta1.type); meta1.attrib = cpu_to_le16(node->flags); exfat_unix2exfat(node->mtime, &meta1.mdate, &meta1.mtime, &meta1.mtime_cs); exfat_unix2exfat(node->atime, &meta1.adate, &meta1.atime, NULL); if (exfat_pread(ef->dev, &meta2, sizeof(meta2), meta2_offset) < 0) { exfat_error("failed to read meta2 entry on flush"); return -EIO; } if (meta2.type != EXFAT_ENTRY_FILE_INFO) exfat_bug("invalid type of meta2: 0x%hhx", meta2.type); meta2.size = meta2.real_size = cpu_to_le64(node->size); meta2.start_cluster = cpu_to_le32(node->start_cluster); meta2.flags = EXFAT_FLAG_ALWAYS1; /* empty files must not be marked as contiguous */ if (node->size != 0 && IS_CONTIGUOUS(*node)) meta2.flags |= EXFAT_FLAG_CONTIGUOUS; /* name hash remains unchanged, no need to recalculate it */ meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name); if (exfat_pwrite(ef->dev, &meta1, sizeof(meta1), meta1_offset) < 0) { exfat_error("failed to write meta1 entry on flush"); return -EIO; } if (exfat_pwrite(ef->dev, &meta2, sizeof(meta2), meta2_offset) < 0) { exfat_error("failed to write meta2 entry on flush"); return -EIO; } node->flags &= ~EXFAT_ATTRIB_DIRTY; return 0; }
int main(int argc, char* argv[]) { const char* spec = NULL; char** pp; int spc_bits = -1; const char* volume_label = NULL; uint32_t volume_serial = 0; uint64_t first_sector = 0; struct exfat_dev* dev; printf("mkexfatfs %u.%u.%u\n", EXFAT_VERSION_MAJOR, EXFAT_VERSION_MINOR, EXFAT_VERSION_PATCH); for (pp = argv + 1; *pp; pp++) { if (strcmp(*pp, "-s") == 0) { pp++; if (*pp == NULL) usage(argv[0]); spc_bits = logarithm2(atoi(*pp)); if (spc_bits < 0) { exfat_error("invalid option value: `%s'", *pp); return 1; } } else if (strcmp(*pp, "-n") == 0) { pp++; if (*pp == NULL) usage(argv[0]); volume_label = *pp; } else if (strcmp(*pp, "-i") == 0) { pp++; if (*pp == NULL) usage(argv[0]); volume_serial = strtol(*pp, NULL, 16); } else if (strcmp(*pp, "-p") == 0) { pp++; if (*pp == NULL) usage(argv[0]); first_sector = strtoll(*pp, NULL, 10); } else if (strcmp(*pp, "-v") == 0) { puts("Copyright (C) 2011-2013 Andrew Nayenko"); return 0; } else if (spec == NULL) spec = *pp; else usage(argv[0]); } if (spec == NULL) usage(argv[0]); dev = exfat_open(spec, EXFAT_MODE_RW); if (dev == NULL) return 1; if (setup(dev, 9, spc_bits, volume_label, volume_serial, first_sector) != 0) { exfat_close(dev); return 1; } if (exfat_close(dev) != 0) return 1; printf("File system created successfully.\n"); return 0; }
struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode) { struct exfat_dev* dev; struct stat stbuf; #ifdef USE_UBLIO struct ublio_param up; #endif dev = malloc(sizeof(struct exfat_dev)); if (dev == NULL) { exfat_error("failed to allocate memory for device structure"); return NULL; } switch (mode) { case EXFAT_MODE_RO: dev->fd = open_ro(spec); if (dev->fd == -1) { free(dev); exfat_error("failed to open '%s' in read-only mode: %s", spec, strerror(errno)); return NULL; } dev->mode = EXFAT_MODE_RO; break; case EXFAT_MODE_RW: dev->fd = open_rw(spec); if (dev->fd == -1) { free(dev); exfat_error("failed to open '%s' in read-write mode: %s", spec, strerror(errno)); return NULL; } dev->mode = EXFAT_MODE_RW; break; case EXFAT_MODE_ANY: dev->fd = open_rw(spec); if (dev->fd != -1) { dev->mode = EXFAT_MODE_RW; break; } dev->fd = open_ro(spec); if (dev->fd != -1) { dev->mode = EXFAT_MODE_RO; exfat_warn("'%s' is write-protected, mounting read-only", spec); break; } free(dev); exfat_error("failed to open '%s': %s", spec, strerror(errno)); return NULL; } if (fstat(dev->fd, &stbuf) != 0) { close(dev->fd); free(dev); exfat_error("failed to fstat '%s'", spec); return NULL; } if (!S_ISBLK(stbuf.st_mode) && !S_ISCHR(stbuf.st_mode) && !S_ISREG(stbuf.st_mode)) { close(dev->fd); free(dev); exfat_error("'%s' is neither a device, nor a regular file", spec); return NULL; } #if defined(__APPLE__) if (!S_ISREG(stbuf.st_mode)) { uint32_t block_size = 0; uint64_t blocks = 0; if (ioctl(dev->fd, DKIOCGETBLOCKSIZE, &block_size) != 0) { close(dev->fd); free(dev); exfat_error("failed to get block size"); return NULL; } if (ioctl(dev->fd, DKIOCGETBLOCKCOUNT, &blocks) != 0) { close(dev->fd); free(dev); exfat_error("failed to get blocks count"); return NULL; } dev->size = blocks * block_size; } else #elif defined(__OpenBSD__) if (!S_ISREG(stbuf.st_mode)) { struct disklabel lab; struct partition* pp; char* partition; if (ioctl(dev->fd, DIOCGDINFO, &lab) == -1) { close(dev->fd); free(dev); exfat_error("failed to get disklabel"); return NULL; } /* Don't need to check that partition letter is valid as we won't get this far otherwise. */ partition = strchr(spec, '\0') - 1; pp = &(lab.d_partitions[*partition - 'a']); dev->size = DL_GETPSIZE(pp) * lab.d_secsize; if (pp->p_fstype != FS_NTFS) exfat_warn("partition type is not 0x07 (NTFS/exFAT); " "you can fix this with fdisk(8)"); } else #endif { /* works for Linux, FreeBSD, Solaris */ dev->size = exfat_seek(dev, 0, SEEK_END); if (dev->size <= 0) { close(dev->fd); free(dev); exfat_error("failed to get size of '%s'", spec); return NULL; } if (exfat_seek(dev, 0, SEEK_SET) == -1) { close(dev->fd); free(dev); exfat_error("failed to seek to the beginning of '%s'", spec); return NULL; } } #ifdef USE_UBLIO memset(&up, 0, sizeof(struct ublio_param)); up.up_blocksize = 256 * 1024; up.up_items = 64; up.up_grace = 32; up.up_priv = &dev->fd; dev->pos = 0; dev->ufh = ublio_open(&up); if (dev->ufh == NULL) { close(dev->fd); free(dev); exfat_error("failed to initialize ublio"); return NULL; } #endif return dev; }