void exfat_put_node(struct exfat* ef, struct exfat_node* node) { if (--node->references < 0) { char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1]; exfat_get_name(node, buffer, sizeof(buffer) - 1); exfat_bug("reference counter of `%s' is below zero", buffer); } if (node->references == 0) { /* FIXME handle I/O error */ if (exfat_flush_node(ef, node) != 0) exfat_bug("node flush failed"); if (node->flags & EXFAT_ATTRIB_UNLINKED) { /* free all clusters and node structure itself */ exfat_truncate(ef, node, 0, true); free(node); } /* FIXME handle I/O error */ if (exfat_flush(ef) != 0) exfat_bug("flush failed"); } }
static int grow_file(struct exfat* ef, struct exfat_node* node, uint32_t current, uint32_t difference) { cluster_t previous; cluster_t next; uint32_t allocated = 0; if (difference == 0) exfat_bug("zero clusters count passed"); if (node->start_cluster != EXFAT_CLUSTER_FREE) { /* get the last cluster of the file */ previous = exfat_advance_cluster(ef, node, current - 1); if (CLUSTER_INVALID(previous)) { exfat_error("invalid cluster 0x%x while growing", previous); return -EIO; } } else { if (node->fptr_index != 0) exfat_bug("non-zero pointer index (%u)", node->fptr_index); /* file does not have clusters (i.e. is empty), allocate the first one for it */ previous = allocate_cluster(ef, 0); if (CLUSTER_INVALID(previous)) return -ENOSPC; node->fptr_cluster = node->start_cluster = previous; allocated = 1; /* file consists of only one cluster, so it's contiguous */ node->flags |= EXFAT_ATTRIB_CONTIGUOUS; } while (allocated < difference) { next = allocate_cluster(ef, previous + 1); if (CLUSTER_INVALID(next)) { if (allocated != 0) shrink_file(ef, node, current + allocated, allocated); return -ENOSPC; } if (next != previous - 1 && IS_CONTIGUOUS(*node)) { /* it's a pity, but we are not able to keep the file contiguous anymore */ make_noncontiguous(ef, node->start_cluster, previous); node->flags &= ~EXFAT_ATTRIB_CONTIGUOUS; node->flags |= EXFAT_ATTRIB_DIRTY; } set_next_cluster(ef, IS_CONTIGUOUS(*node), previous, next); previous = next; allocated++; } set_next_cluster(ef, IS_CONTIGUOUS(*node), previous, EXFAT_CLUSTER_END); return 0; }
static void dirck(struct exfat* ef, const char* path) { struct exfat_node* parent; struct exfat_node* node; struct exfat_iterator it; int rc; size_t path_length; char* entry_path; if (exfat_lookup(ef, &parent, path) != 0) exfat_bug("directory '%s' is not found", path); if (!(parent->flags & EXFAT_ATTRIB_DIR)) exfat_bug("'%s' is not a directory (0x%x)", path, parent->flags); if (nodeck(ef, parent) != 0) { exfat_put_node(ef, parent); return; } path_length = strlen(path); entry_path = malloc(path_length + 1 + UTF8_BYTES(EXFAT_NAME_MAX) + 1); if (entry_path == NULL) { exfat_put_node(ef, parent); exfat_error("out of memory"); return; } strcpy(entry_path, path); strcat(entry_path, "/"); rc = exfat_opendir(ef, parent, &it); if (rc != 0) { free(entry_path); exfat_put_node(ef, parent); return; } while ((node = exfat_readdir(ef, &it))) { exfat_get_name(node, entry_path + path_length + 1, UTF8_BYTES(EXFAT_NAME_MAX)); exfat_debug("%s: %s, %"PRIu64" bytes, cluster %u", entry_path, IS_CONTIGUOUS(*node) ? "contiguous" : "fragmented", node->size, node->start_cluster); if (node->flags & EXFAT_ATTRIB_DIR) { directories_count++; dirck(ef, entry_path); } else { files_count++; nodeck(ef, node); } exfat_put_node(ef, node); } exfat_closedir(ef, &it); exfat_put_node(ef, parent); free(entry_path); }
static int shrink_directory(struct exfat* ef, struct exfat_node* dir, off64_t deleted_offset) { const struct exfat_node* node; const struct exfat_node* last_node; uint64_t entries = 1; /* a directory always has at leat 1 entry (EOD) */ uint64_t new_size; struct exfat_entry eod; off64_t eod_offset; int rc; if (!(dir->flags & EXFAT_ATTRIB_DIR)) exfat_bug("attempted to shrink a file"); if (!(dir->flags & EXFAT_ATTRIB_CACHED)) exfat_bug("attempted to shrink uncached directory"); for (last_node = node = dir->child; node; node = node->next) { if (deleted_offset < node->entry_offset) { /* there are other entries after the removed one, no way to shrink this directory */ return 0; } if (last_node->entry_offset < node->entry_offset) last_node = node; } if (last_node) { /* offset of the last entry */ entries += last_node->entry_offset / sizeof(struct exfat_entry); /* two subentries with meta info */ entries += 2; /* subentries with file name */ entries += DIV_ROUND_UP(utf16_length(last_node->name), EXFAT_ENAME_MAX); } new_size = DIV_ROUND_UP(entries * sizeof(struct exfat_entry), CLUSTER_SIZE(*ef->sb)) * CLUSTER_SIZE(*ef->sb); if (new_size == dir->size) return 0; rc = exfat_truncate(ef, dir, new_size); if (rc != 0) return rc; /* put EOD entry at the end of the last cluster */ memset(&eod, 0, sizeof(eod)); eod_offset = new_size - sizeof(struct exfat_entry); if (last_node) exfat_write_raw(&eod, sizeof(eod), co2o(ef, last_node->entry_cluster, eod_offset), ef->fd); else exfat_write_raw(&eod, sizeof(eod), co2o(ef, dir->start_cluster, eod_offset), ef->fd); return 0; }
static void free_cluster(struct exfat* ef, cluster_t cluster) { if (CLUSTER_INVALID(cluster)) exfat_bug("freeing invalid cluster 0x%x", cluster); if (cluster - EXFAT_FIRST_DATA_CLUSTER >= ef->cmap.size) exfat_bug("freeing non-existing cluster 0x%x (0x%x)", cluster, ef->cmap.size); BMAP_CLR(ef->cmap.chunk, cluster - EXFAT_FIRST_DATA_CLUSTER); ef->cmap.dirty = true; }
static int shrink_file(struct exfat* ef, struct exfat_node* node, uint32_t current, uint32_t difference) { cluster_t previous; cluster_t next; if (difference == 0) exfat_bug("zero difference passed"); if (node->start_cluster == EXFAT_CLUSTER_FREE) exfat_bug("unable to shrink empty file (%u clusters)", current); if (current < difference) exfat_bug("file underflow (%u < %u)", current, difference); /* crop the file */ if (current > difference) { cluster_t last = exfat_advance_cluster(ef, node, current - difference - 1); if (CLUSTER_INVALID(last)) { exfat_error("invalid cluster 0x%x while shrinking", last); return -EIO; } previous = exfat_next_cluster(ef, node, last); if (!set_next_cluster(ef, IS_CONTIGUOUS(*node), last, EXFAT_CLUSTER_END)) return -EIO; } else { previous = node->start_cluster; node->start_cluster = EXFAT_CLUSTER_FREE; } node->fptr_index = 0; node->fptr_cluster = node->start_cluster; /* free remaining clusters */ while (difference--) { if (CLUSTER_INVALID(previous)) { exfat_error("invalid cluster 0x%x while freeing after shrink", previous); return -EIO; } next = exfat_next_cluster(ef, node, previous); if (!set_next_cluster(ef, IS_CONTIGUOUS(*node), previous, EXFAT_CLUSTER_FREE)) return -EIO; free_cluster(ef, previous); previous = next; } return 0; }
static int shrink_directory(struct exfat* ef, struct exfat_node* dir, off64_t deleted_offset) { const struct exfat_node* node; const struct exfat_node* last_node; uint64_t entries = 0; uint64_t new_size; int rc; if (!(dir->flags & EXFAT_ATTRIB_DIR)) exfat_bug("attempted to shrink a file"); if (!(dir->flags & EXFAT_ATTRIB_CACHED)) exfat_bug("attempted to shrink uncached directory"); for (last_node = node = dir->child; node; node = node->next) { if (deleted_offset < node->entry_offset) { /* there are other entries after the removed one, no way to shrink this directory */ return 0; } if (last_node->entry_offset < node->entry_offset) last_node = node; } if (last_node) { /* offset of the last entry */ entries += last_node->entry_offset / sizeof(struct exfat_entry); /* two subentries with meta info */ entries += 2; /* subentries with file name */ entries += DIV_ROUND_UP(utf16_length(last_node->name), EXFAT_ENAME_MAX); } new_size = DIV_ROUND_UP(entries * sizeof(struct exfat_entry), CLUSTER_SIZE(*ef->sb)) * CLUSTER_SIZE(*ef->sb); if (new_size == 0) /* directory always has at least 1 cluster */ new_size = CLUSTER_SIZE(*ef->sb); if (new_size == dir->size) return 0; rc = exfat_truncate(ef, dir, new_size, true); if (rc != 0) return rc; return 0; }
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 erase_range(struct exfat* ef, struct exfat_node* node, uint64_t begin, uint64_t end) { uint64_t cluster_boundary; cluster_t cluster; if (begin >= end) return 0; cluster_boundary = (begin | (CLUSTER_SIZE(*ef->sb) - 1)) + 1; cluster = exfat_advance_cluster(ef, node, begin / CLUSTER_SIZE(*ef->sb)); if (CLUSTER_INVALID(cluster)) { exfat_error("invalid cluster 0x%x while erasing", cluster); return -EIO; } /* erase from the beginning to the closest cluster boundary */ erase_raw(ef, MIN(cluster_boundary, end) - begin, exfat_c2o(ef, cluster) + begin % CLUSTER_SIZE(*ef->sb)); /* erase whole clusters */ while (cluster_boundary < end) { cluster = exfat_next_cluster(ef, node, cluster); /* the cluster cannot be invalid because we have just allocated it */ if (CLUSTER_INVALID(cluster)) exfat_bug("invalid cluster 0x%x after allocation", cluster); erase_raw(ef, CLUSTER_SIZE(*ef->sb), exfat_c2o(ef, cluster)); cluster_boundary += CLUSTER_SIZE(*ef->sb); } return 0; }
int exfat_truncate(struct exfat* ef, struct exfat_node* node, uint64_t size) { uint32_t c1 = bytes2clusters(ef, node->size); uint32_t c2 = bytes2clusters(ef, size); int rc = 0; if (node->references == 0 && node->parent) exfat_bug("no references, node changes can be lost"); if (node->size == size) return 0; if (c1 < c2) rc = grow_file(ef, node, c1, c2 - c1); else if (c1 > c2) rc = shrink_file(ef, node, c1, c1 - c2); if (rc != 0) return rc; rc = erase_range(ef, node, node->size, size); if (rc != 0) return rc; exfat_update_mtime(node); node->size = size; node->flags |= EXFAT_ATTRIB_DIRTY; return 0; }
/* * Cluster to sector. */ static off_t c2s(const struct exfat* ef, cluster_t cluster) { if (cluster < EXFAT_FIRST_DATA_CLUSTER) exfat_bug("invalid cluster number %u", cluster); return le32_to_cpu(ef->sb->cluster_sector_start) + ((off_t) (cluster - EXFAT_FIRST_DATA_CLUSTER) << ef->sb->spc_bits); }
void 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 (ef->ro) exfat_bug("unable to flush node to read-only FS"); if (node->parent == NULL) return; /* 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); exfat_read_raw(&meta1, sizeof(meta1), meta1_offset, ef->fd); 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); exfat_read_raw(&meta2, sizeof(meta2), meta2_offset, ef->fd); 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); exfat_write_raw(&meta1, sizeof(meta1), meta1_offset, ef->fd); exfat_write_raw(&meta2, sizeof(meta2), meta2_offset, ef->fd); node->flags &= ~EXFAT_ATTRIB_DIRTY; }
cluster_t exfat_next_cluster(const struct exfat* ef, const struct exfat_node* node, cluster_t cluster) { le32_t next; loff_t fat_offset; if (cluster < EXFAT_FIRST_DATA_CLUSTER) exfat_bug("bad cluster 0x%x", cluster); if (IS_CONTIGUOUS(*node)) return cluster + 1; fat_offset = s2o(ef, le32_to_cpu(ef->sb->fat_sector_start)) + cluster * sizeof(cluster_t); /* FIXME handle I/O error */ if (exfat_pread(ef->dev, &next, sizeof(next), fat_offset) < 0) exfat_bug("failed to read the next cluster after %#x", cluster); return le32_to_cpu(next); }
void exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size, off_t offset) { #ifdef USE_UBLIO if (ublio_pwrite(dev->ufh, buffer, size, offset) != size) #else if (pwrite(dev->fd, buffer, size, offset) != size) #endif exfat_bug("failed to write %zu bytes to file at %"PRIu64, size, (uint64_t) offset); }
void exfat_pread(struct exfat_dev* dev, void* buffer, size_t size, off_t offset) { #ifdef USE_UBLIO if (ublio_pread(dev->ufh, buffer, size, offset) != size) #else if (pread(dev->fd, buffer, size, offset) != size) #endif exfat_bug("failed to read %zu bytes from file at %"PRIu64, size, (uint64_t) offset); }
off64_t get_position(const struct fs_object* object) { const struct fs_object** pp; off64_t position = 0; for (pp = objects; *pp; pp++) { position = ROUND_UP(position, (*pp)->get_alignment()); if (*pp == object) return position; position += (*pp)->get_size(); } exfat_bug("unknown object"); }
cluster_t exfat_next_cluster(const struct exfat* ef, const struct exfat_node* node, cluster_t cluster) { le32_t next; off_t fat_offset; if (cluster < EXFAT_FIRST_DATA_CLUSTER) exfat_bug("bad cluster 0x%x", cluster); if (IS_CONTIGUOUS(*node)) return cluster + 1; fat_offset = s2o(ef, le32_to_cpu(ef->sb->fat_sector_start)) + cluster * sizeof(cluster_t); exfat_pread(ef->dev, &next, sizeof(next), fat_offset); return le32_to_cpu(next); }
/** * This function must be called on rmdir and unlink (after the last * exfat_put_node()) to free clusters. */ int exfat_cleanup_node(struct exfat* ef, struct exfat_node* node) { int rc = 0; if (node->references != 0) exfat_bug("unable to cleanup a node with %d references", node->references); if (node->flags & EXFAT_ATTRIB_UNLINKED) { /* free all clusters and node structure itself */ rc = exfat_truncate(ef, node, 0, true); /* free the node even in case of error or its memory will be lost */ free(node); } return rc; }
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; } exfat_read_raw(it->chunk, CLUSTER_SIZE(*ef->sb), exfat_c2o(ef, it->cluster), ef->fd); return 0; }
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); } } }
void exfat_put_node(struct exfat* ef, struct exfat_node* node) { if (--node->references < 0) { char buffer[EXFAT_NAME_MAX + 1]; exfat_get_name(node, buffer, EXFAT_NAME_MAX); exfat_bug("reference counter of `%s' is below zero", buffer); } if (node->references == 0) { if (node->flags & EXFAT_ATTRIB_DIRTY) exfat_flush_node(ef, node); if (node->flags & EXFAT_ATTRIB_UNLINKED) { /* free all clusters and node structure itself */ exfat_truncate(ef, node, 0); free(node); } if (ef->cmap.dirty) exfat_flush_cmap(ef); } }
void exfat_read_raw(void* buffer, size_t size, off_t offset, int fd) { if (pread(fd, buffer, size, offset) != size) exfat_bug("failed to read %zu bytes from file at %"PRIu64, size, (uint64_t) offset); }
void exfat_write_raw(const void* buffer, size_t size, off_t offset, int fd) { if (pwrite(fd, buffer, size, offset) != size) exfat_bug("failed to write %zu bytes to file at %"PRIu64, size, (uint64_t) offset); }