static void reset_cache(struct exfat* ef, struct exfat_node* node) { char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1]; while (node->child) { struct exfat_node* p = node->child; reset_cache(ef, p); tree_detach(p); free(p); } node->flags &= ~EXFAT_ATTRIB_CACHED; if (node->references != 0) { exfat_get_name(node, buffer, sizeof(buffer) - 1); exfat_warn("non-zero reference counter (%d) for '%s'", node->references, buffer); } if (node != ef->root && (node->flags & EXFAT_ATTRIB_DIRTY)) { exfat_get_name(node, buffer, sizeof(buffer) - 1); exfat_bug("node '%s' is dirty", buffer); } while (node->references) exfat_put_node(ef, node); }
static int prepare_super_block(const struct exfat* ef) { if (le16_to_cpu(ef->sb->volume_state) & EXFAT_STATE_MOUNTED) exfat_warn("volume was not unmounted cleanly"); if (ef->ro) return 0; #if defined(__AROS__) || defined(AMIGA) return 0; #else ef->sb->volume_state = cpu_to_le16( le16_to_cpu(ef->sb->volume_state) | EXFAT_STATE_MOUNTED); return commit_super_block(ef); #endif }
void exfat_put_node(struct exfat* ef, struct exfat_node* node) { char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1]; --node->references; if (node->references < 0) { exfat_get_name(node, buffer, sizeof(buffer) - 1); exfat_bug("reference counter of '%s' is below zero", buffer); } else if (node->references == 0 && node != ef->root) { if (node->flags & EXFAT_ATTRIB_DIRTY) { exfat_get_name(node, buffer, sizeof(buffer) - 1); exfat_warn("dirty node '%s' with zero references", buffer); } } }
static void reset_cache(struct exfat* ef, struct exfat_node* node) { while (node->child) { struct exfat_node* p = node->child; reset_cache(ef, p); tree_detach(p); free(p); } node->flags &= ~EXFAT_ATTRIB_CACHED; if (node->references != 0) { char buffer[EXFAT_NAME_MAX + 1]; exfat_get_name(node, buffer, EXFAT_NAME_MAX); exfat_warn("non-zero reference counter (%d) for `%s'", node->references, buffer); } while (node->references) exfat_put_node(ef, node); }
static void reset_cache(struct exfat* ef, struct exfat_node* node) { struct exfat_node* child; struct exfat_node* next; for (child = node->child; child; child = next) { reset_cache(ef, child); next = child->next; free(child); } if (node->references != 0) { char buffer[EXFAT_NAME_MAX + 1]; exfat_get_name(node, buffer, EXFAT_NAME_MAX); exfat_warn("non-zero reference counter (%d) for `%s'", node->references, buffer); } while (node->references--) exfat_put_node(ef, node); node->child = NULL; node->flags &= ~EXFAT_ATTRIB_CACHED; }
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; }
/* * Reads one entry in directory at position pointed by iterator and fills * node structure. */ static int readdir(struct exfat* ef, const struct exfat_node* parent, struct exfat_node** node, struct iterator* it) { int rc = -EIO; const struct exfat_entry* entry; const struct exfat_entry_meta1* meta1; const struct exfat_entry_meta2* meta2; const struct exfat_entry_name* file_name; const struct exfat_entry_upcase* upcase; const struct exfat_entry_bitmap* bitmap; const struct exfat_entry_label* label; uint8_t continuations = 0; le16_t* namep = NULL; uint16_t reference_checksum = 0; uint16_t actual_checksum = 0; uint64_t valid_size = 0; *node = NULL; for (;;) { if (it->offset >= parent->size) { if (continuations != 0) { exfat_error("expected %hhu continuations", continuations); goto error; } return -ENOENT; /* that's OK, means end of directory */ } entry = get_entry_ptr(ef, it); switch (entry->type) { case EXFAT_ENTRY_FILE: if (continuations != 0) { exfat_error("expected %hhu continuations before new entry", continuations); goto error; } meta1 = (const struct exfat_entry_meta1*) entry; continuations = meta1->continuations; /* each file entry must have at least 2 continuations: info and name */ if (continuations < 2) { exfat_error("too few continuations (%hhu)", continuations); goto error; } if (continuations > 1 + DIV_ROUND_UP(EXFAT_NAME_MAX, EXFAT_ENAME_MAX)) { exfat_error("too many continuations (%hhu)", continuations); goto error; } reference_checksum = le16_to_cpu(meta1->checksum); actual_checksum = exfat_start_checksum(meta1); *node = allocate_node(); if (*node == NULL) { rc = -ENOMEM; goto error; } /* new node has zero reference counter */ (*node)->entry_cluster = it->cluster; (*node)->entry_offset = it->offset; init_node_meta1(*node, meta1); namep = (*node)->name; break; case EXFAT_ENTRY_FILE_INFO: if (continuations < 2) { exfat_error("unexpected continuation (%hhu)", continuations); goto error; } meta2 = (const struct exfat_entry_meta2*) entry; if (meta2->flags & ~(EXFAT_FLAG_ALWAYS1 | EXFAT_FLAG_CONTIGUOUS)) { exfat_error("unknown flags in meta2 (0x%hhx)", meta2->flags); goto error; } init_node_meta2(*node, meta2); actual_checksum = exfat_add_checksum(entry, actual_checksum); valid_size = le64_to_cpu(meta2->valid_size); /* empty files must be marked as non-contiguous */ if ((*node)->size == 0 && (meta2->flags & EXFAT_FLAG_CONTIGUOUS)) { exfat_error("empty file marked as contiguous (0x%hhx)", meta2->flags); goto error; } /* directories must be aligned on at cluster boundary */ if (((*node)->flags & EXFAT_ATTRIB_DIR) && (*node)->size % CLUSTER_SIZE(*ef->sb) != 0) { exfat_error("directory has invalid size %"PRIu64" bytes", (*node)->size); goto error; } --continuations; break; case EXFAT_ENTRY_FILE_NAME: if (continuations == 0) { exfat_error("unexpected continuation"); goto error; } file_name = (const struct exfat_entry_name*) entry; actual_checksum = exfat_add_checksum(entry, actual_checksum); memcpy(namep, file_name->name, MIN(EXFAT_ENAME_MAX, ((*node)->name + EXFAT_NAME_MAX - namep)) * sizeof(le16_t)); namep += EXFAT_ENAME_MAX; if (--continuations == 0) { if (!check_node(*node, actual_checksum, reference_checksum, valid_size)) goto error; if (!fetch_next_entry(ef, parent, it)) goto error; return 0; /* entry completed */ } break; case EXFAT_ENTRY_UPCASE: if (ef->upcase != NULL) break; upcase = (const struct exfat_entry_upcase*) entry; if (CLUSTER_INVALID(le32_to_cpu(upcase->start_cluster))) { exfat_error("invalid cluster 0x%x in upcase table", le32_to_cpu(upcase->start_cluster)); goto error; } if (le64_to_cpu(upcase->size) == 0 || le64_to_cpu(upcase->size) > 0xffff * sizeof(uint16_t) || le64_to_cpu(upcase->size) % sizeof(uint16_t) != 0) { exfat_error("bad upcase table size (%"PRIu64" bytes)", le64_to_cpu(upcase->size)); goto error; } ef->upcase = malloc(le64_to_cpu(upcase->size)); if (ef->upcase == NULL) { exfat_error("failed to allocate upcase table (%"PRIu64" bytes)", le64_to_cpu(upcase->size)); rc = -ENOMEM; goto error; } ef->upcase_chars = le64_to_cpu(upcase->size) / sizeof(le16_t); if (exfat_pread(ef->dev, ef->upcase, le64_to_cpu(upcase->size), exfat_c2o(ef, le32_to_cpu(upcase->start_cluster))) < 0) { exfat_error("failed to read upper case table " "(%"PRIu64" bytes starting at cluster %#x)", le64_to_cpu(upcase->size), le32_to_cpu(upcase->start_cluster)); goto error; } break; case EXFAT_ENTRY_BITMAP: bitmap = (const struct exfat_entry_bitmap*) entry; ef->cmap.start_cluster = le32_to_cpu(bitmap->start_cluster); if (CLUSTER_INVALID(ef->cmap.start_cluster)) { exfat_error("invalid cluster 0x%x in clusters bitmap", ef->cmap.start_cluster); goto error; } ef->cmap.size = le32_to_cpu(ef->sb->cluster_count) - EXFAT_FIRST_DATA_CLUSTER; if (le64_to_cpu(bitmap->size) < DIV_ROUND_UP(ef->cmap.size, 8)) { exfat_error("invalid clusters bitmap size: %"PRIu64 " (expected at least %u)", le64_to_cpu(bitmap->size), DIV_ROUND_UP(ef->cmap.size, 8)); goto error; } /* FIXME bitmap can be rather big, up to 512 MB */ ef->cmap.chunk_size = ef->cmap.size; ef->cmap.chunk = malloc(BMAP_SIZE(ef->cmap.chunk_size)); if (ef->cmap.chunk == NULL) { exfat_error("failed to allocate clusters bitmap chunk " "(%"PRIu64" bytes)", le64_to_cpu(bitmap->size)); rc = -ENOMEM; goto error; } if (exfat_pread(ef->dev, ef->cmap.chunk, BMAP_SIZE(ef->cmap.chunk_size), exfat_c2o(ef, ef->cmap.start_cluster)) < 0) { exfat_error("failed to read clusters bitmap " "(%"PRIu64" bytes starting at cluster %#x)", le64_to_cpu(bitmap->size), ef->cmap.start_cluster); goto error; } break; case EXFAT_ENTRY_LABEL: label = (const struct exfat_entry_label*) entry; if (label->length > EXFAT_ENAME_MAX) { exfat_error("too long label (%hhu chars)", label->length); goto error; } if (utf16_to_utf8(ef->label, label->name, sizeof(ef->label) - 1, EXFAT_ENAME_MAX) != 0) goto error; break; default: if (!(entry->type & EXFAT_ENTRY_VALID)) break; /* deleted entry, ignore it */ if (!(entry->type & EXFAT_ENTRY_OPTIONAL)) { exfat_error("unknown entry type %#hhx", entry->type); goto error; } /* optional entry, warn and skip */ exfat_warn("unknown entry type %#hhx", entry->type); if (continuations == 0) { exfat_error("unexpected continuation"); goto error; } --continuations; break; } if (!fetch_next_entry(ef, parent, it)) goto error; } /* we never reach here */ error: free(*node); *node = NULL; return rc; }
int exfat_mount(struct exfat* ef, const char* spec, const char* options) { int rc; enum exfat_mode mode; exfat_tzset(); memset(ef, 0, sizeof(struct exfat)); parse_options(ef, options); if (match_option(options, "ro")) mode = EXFAT_MODE_RO; else if (match_option(options, "ro_fallback")) mode = EXFAT_MODE_ANY; else mode = EXFAT_MODE_RW; ef->dev = exfat_open(spec, mode); if (ef->dev == NULL) return -EIO; if (exfat_get_mode(ef->dev) == EXFAT_MODE_RO) { if (mode == EXFAT_MODE_ANY) ef->ro = -1; else ef->ro = 1; } ef->sb = malloc(sizeof(struct exfat_super_block)); if (ef->sb == NULL) { exfat_close(ef->dev); exfat_error("failed to allocate memory for the super block"); return -ENOMEM; } memset(ef->sb, 0, sizeof(struct exfat_super_block)); if (exfat_pread(ef->dev, ef->sb, sizeof(struct exfat_super_block), 0) < 0) { exfat_close(ef->dev); free(ef->sb); exfat_error("failed to read boot sector"); return -EIO; } if (memcmp(ef->sb->oem_name, "EXFAT ", 8) != 0) { exfat_close(ef->dev); free(ef->sb); exfat_error("exFAT file system is not found"); return -EIO; } /* sector cannot be smaller than 512 bytes */ if (ef->sb->sector_bits < 9) { exfat_close(ef->dev); exfat_error("too small sector size: 2^%hhd", ef->sb->sector_bits); free(ef->sb); return -EIO; } /* officially exFAT supports cluster size up to 32 MB */ if ((int) ef->sb->sector_bits + (int) ef->sb->spc_bits > 25) { exfat_close(ef->dev); exfat_error("too big cluster size: 2^(%hhd+%hhd)", ef->sb->sector_bits, ef->sb->spc_bits); free(ef->sb); return -EIO; } ef->zero_cluster = malloc(CLUSTER_SIZE(*ef->sb)); if (ef->zero_cluster == NULL) { exfat_close(ef->dev); free(ef->sb); exfat_error("failed to allocate zero sector"); return -ENOMEM; } /* use zero_cluster as a temporary buffer for VBR checksum verification */ if (!verify_vbr_checksum(ef->dev, ef->zero_cluster, SECTOR_SIZE(*ef->sb))) { free(ef->zero_cluster); exfat_close(ef->dev); free(ef->sb); return -EIO; } memset(ef->zero_cluster, 0, CLUSTER_SIZE(*ef->sb)); if (ef->sb->version.major != 1 || ef->sb->version.minor != 0) { free(ef->zero_cluster); exfat_close(ef->dev); exfat_error("unsupported exFAT version: %hhu.%hhu", ef->sb->version.major, ef->sb->version.minor); free(ef->sb); return -EIO; } if (ef->sb->fat_count != 1) { free(ef->zero_cluster); exfat_close(ef->dev); exfat_error("unsupported FAT count: %hhu", ef->sb->fat_count); free(ef->sb); return -EIO; } if (le64_to_cpu(ef->sb->sector_count) * SECTOR_SIZE(*ef->sb) > exfat_get_size(ef->dev)) { /* this can cause I/O errors later but we don't fail mounting to let user rescue data */ exfat_warn("file system is larger than underlying device: " "%"PRIu64" > %"PRIu64, le64_to_cpu(ef->sb->sector_count) * SECTOR_SIZE(*ef->sb), exfat_get_size(ef->dev)); } ef->root = malloc(sizeof(struct exfat_node)); if (ef->root == NULL) { free(ef->zero_cluster); exfat_close(ef->dev); free(ef->sb); exfat_error("failed to allocate root node"); return -ENOMEM; } memset(ef->root, 0, sizeof(struct exfat_node)); ef->root->flags = EXFAT_ATTRIB_DIR; ef->root->start_cluster = le32_to_cpu(ef->sb->rootdir_cluster); ef->root->fptr_cluster = ef->root->start_cluster; ef->root->name[0] = cpu_to_le16('\0'); ef->root->size = rootdir_size(ef); if (ef->root->size == 0) { free(ef->root); free(ef->zero_cluster); exfat_close(ef->dev); free(ef->sb); return -EIO; } /* exFAT does not have time attributes for the root directory */ ef->root->mtime = 0; ef->root->atime = 0; /* always keep at least 1 reference to the root node */ exfat_get_node(ef->root); rc = exfat_cache_directory(ef, ef->root); if (rc != 0) goto error; if (ef->upcase == NULL) { exfat_error("upcase table is not found"); goto error; } if (ef->cmap.chunk == NULL) { exfat_error("clusters bitmap is not found"); goto error; } if (prepare_super_block(ef) != 0) goto error; return 0; error: exfat_put_node(ef, ef->root); exfat_reset_cache(ef); free(ef->root); free(ef->zero_cluster); exfat_close(ef->dev); free(ef->sb); return -EIO; }
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", spec); 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", spec); 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'", spec); 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; } #ifdef __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 #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; }