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 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; }
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; }
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; }
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; }
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; }
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; }