ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node, const void* buffer, size_t size, off64_t offset) { cluster_t cluster; const char* bufp = buffer; off64_t lsize, loffset, remainder; if (offset > node->size) if (exfat_truncate(ef, node, offset, true) != 0) return -1; if (offset + size > node->size) if (exfat_truncate(ef, node, offset + size, false) != 0) return -1; 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 writing", cluster); return -1; } loffset = offset % CLUSTER_SIZE(*ef->sb); remainder = size; while (remainder > 0) { if (CLUSTER_INVALID(cluster)) { exfat_error("invalid cluster 0x%x while writing", cluster); return -1; } lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder); if (exfat_pwrite(ef->dev, bufp, lsize, exfat_c2o(ef, cluster) + loffset) < 0) { exfat_error("failed to write cluster %#x", cluster); ef->was_dirty = true; // Leaves the volume "mounted", to force chkdsk on it. return -1; } bufp += lsize; loffset = 0; remainder -= lsize; cluster = exfat_next_cluster(ef, node, cluster); } exfat_update_mtime(node); return size - remainder; }
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 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 int fuse_exfat_truncate(const char* path, off64_t size) { struct exfat_node* node; int rc; exfat_debug("[%s] %s, %"PRId64, __func__, path, size); rc = exfat_lookup(&ef, &node, path); if (rc != 0) return rc; rc = exfat_truncate(&ef, node, size); exfat_put_node(&ef, node); return rc; }
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; }
/** * 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; }
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 int fuse_exfat_truncate(const char* path, fbx_off_t size) { struct exfat_node* node; int rc; exfat_debug("[%s] %s, %"PRId64, __func__, path, size); rc = exfat_lookup(&ef, &node, path); if (rc != 0) return rc; rc = exfat_truncate(&ef, node, size, true); if (rc != 0) { exfat_flush_node(&ef, node); /* ignore return code */ exfat_put_node(&ef, node); return rc; } rc = exfat_flush_node(&ef, node); exfat_put_node(&ef, node); return rc; }
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); } }