summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorAKASHI Takahiro <takahiro.akashi@linaro.org>2018-09-11 15:59:06 +0900
committerAlexander Graf <agraf@suse.de>2018-09-23 21:55:29 +0200
commitcb8af8af5ba03ae8e0a7315b66bfcc46d5c55627 (patch)
tree996a60ec6a177a5b036d5b7d2a8c92d3d6c27242 /fs
parent704df6aa0a284627940e1b15dc6f1aa528753c4b (diff)
fs: fat: support write with non-zero offset
In this patch, all the necessary code for allowing for a file offset at write is implemented. What plays a major roll here is get_set_cluster(), which, in contrast to its counterpart, set_cluster(), only operates on already-allocated clusters, overwriting with data. So, with a file offset specified, set_contents() seeks and writes data with set_get_cluster() until the end of a file, and, once it reaches there, continues writing with set_cluster() for the rest. Please note that a file will be trimmed as a result of write operation if write ends before reaching file's end. This is an intended behavior in order to maintain compatibility with the current interface. Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org> Signed-off-by: Alexander Graf <agraf@suse.de>
Diffstat (limited to 'fs')
-rw-r--r--fs/fat/fat_write.c288
1 files changed, 273 insertions, 15 deletions
diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c
index c22d8c7a46..651c7866de 100644
--- a/fs/fat/fat_write.c
+++ b/fs/fat/fat_write.c
@@ -450,6 +450,121 @@ set_cluster(fsdata *mydata, __u32 clustnum, __u8 *buffer,
return 0;
}
+static __u8 tmpbuf_cluster[MAX_CLUSTSIZE] __aligned(ARCH_DMA_MINALIGN);
+
+/*
+ * Read and modify data on existing and consecutive cluster blocks
+ */
+static int
+get_set_cluster(fsdata *mydata, __u32 clustnum, loff_t pos, __u8 *buffer,
+ loff_t size, loff_t *gotsize)
+{
+ unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
+ __u32 startsect;
+ loff_t wsize;
+ int clustcount, i, ret;
+
+ *gotsize = 0;
+ if (!size)
+ return 0;
+
+ assert(pos < bytesperclust);
+ startsect = clust_to_sect(mydata, clustnum);
+
+ debug("clustnum: %d, startsect: %d, pos: %lld\n",
+ clustnum, startsect, pos);
+
+ /* partial write at beginning */
+ if (pos) {
+ wsize = min(bytesperclust - pos, size);
+ ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster);
+ if (ret != mydata->clust_size) {
+ debug("Error reading data (got %d)\n", ret);
+ return -1;
+ }
+
+ memcpy(tmpbuf_cluster + pos, buffer, wsize);
+ ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster);
+ if (ret != mydata->clust_size) {
+ debug("Error writing data (got %d)\n", ret);
+ return -1;
+ }
+
+ size -= wsize;
+ buffer += wsize;
+ *gotsize += wsize;
+
+ startsect += mydata->clust_size;
+
+ if (!size)
+ return 0;
+ }
+
+ /* full-cluster write */
+ if (size >= bytesperclust) {
+ clustcount = lldiv(size, bytesperclust);
+
+ if (!((unsigned long)buffer & (ARCH_DMA_MINALIGN - 1))) {
+ wsize = clustcount * bytesperclust;
+ ret = disk_write(startsect,
+ clustcount * mydata->clust_size,
+ buffer);
+ if (ret != clustcount * mydata->clust_size) {
+ debug("Error writing data (got %d)\n", ret);
+ return -1;
+ }
+
+ size -= wsize;
+ buffer += wsize;
+ *gotsize += wsize;
+
+ startsect += clustcount * mydata->clust_size;
+ } else {
+ for (i = 0; i < clustcount; i++) {
+ memcpy(tmpbuf_cluster, buffer, bytesperclust);
+ ret = disk_write(startsect,
+ mydata->clust_size,
+ tmpbuf_cluster);
+ if (ret != mydata->clust_size) {
+ debug("Error writing data (got %d)\n",
+ ret);
+ return -1;
+ }
+
+ size -= bytesperclust;
+ buffer += bytesperclust;
+ *gotsize += bytesperclust;
+
+ startsect += mydata->clust_size;
+ }
+ }
+ }
+
+ /* partial write at end */
+ if (size) {
+ wsize = size;
+ ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster);
+ if (ret != mydata->clust_size) {
+ debug("Error reading data (got %d)\n", ret);
+ return -1;
+ }
+ memcpy(tmpbuf_cluster, buffer, wsize);
+ ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster);
+ if (ret != mydata->clust_size) {
+ debug("Error writing data (got %d)\n", ret);
+ return -1;
+ }
+
+ size -= wsize;
+ buffer += wsize;
+ *gotsize += wsize;
+ }
+
+ assert(!size);
+
+ return 0;
+}
+
/*
* Find the first empty cluster
*/
@@ -578,26 +693,158 @@ set_contents(fsdata *mydata, dir_entry *dentptr, loff_t pos, __u8 *buffer,
unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
__u32 curclust = START(dentptr);
__u32 endclust = 0, newclust = 0;
- loff_t actsize;
+ loff_t cur_pos, offset, actsize, wsize;
*gotsize = 0;
- filesize = maxsize;
+ filesize = pos + maxsize;
debug("%llu bytes\n", filesize);
- if (curclust) {
- /*
- * release already-allocated clusters anyway
- */
- if (clear_fatent(mydata, curclust)) {
- printf("Error: clearing FAT entries\n");
+ if (!filesize) {
+ if (!curclust)
+ return 0;
+ if (!CHECK_CLUST(curclust, mydata->fatsize) ||
+ IS_LAST_CLUST(curclust, mydata->fatsize)) {
+ clear_fatent(mydata, curclust);
+ set_start_cluster(mydata, dentptr, 0);
+ return 0;
+ }
+ debug("curclust: 0x%x\n", curclust);
+ debug("Invalid FAT entry\n");
+ return -1;
+ }
+
+ if (!curclust) {
+ assert(pos == 0);
+ goto set_clusters;
+ }
+
+ /* go to cluster at pos */
+ cur_pos = bytesperclust;
+ while (1) {
+ if (pos <= cur_pos)
+ break;
+ if (IS_LAST_CLUST(curclust, mydata->fatsize))
+ break;
+
+ newclust = get_fatent(mydata, curclust);
+ if (!IS_LAST_CLUST(newclust, mydata->fatsize) &&
+ CHECK_CLUST(newclust, mydata->fatsize)) {
+ debug("curclust: 0x%x\n", curclust);
+ debug("Invalid FAT entry\n");
return -1;
}
+
+ cur_pos += bytesperclust;
+ curclust = newclust;
+ }
+ if (IS_LAST_CLUST(curclust, mydata->fatsize)) {
+ assert(pos == cur_pos);
+ goto set_clusters;
}
- curclust = find_empty_cluster(mydata);
- set_start_cluster(mydata, dentptr, curclust);
+ assert(pos < cur_pos);
+ cur_pos -= bytesperclust;
+ /* overwrite */
+ assert(IS_LAST_CLUST(curclust, mydata->fatsize) ||
+ !CHECK_CLUST(curclust, mydata->fatsize));
+
+ while (1) {
+ /* search for allocated consecutive clusters */
+ actsize = bytesperclust;
+ endclust = curclust;
+ while (1) {
+ if (filesize <= (cur_pos + actsize))
+ break;
+
+ newclust = get_fatent(mydata, endclust);
+
+ if (IS_LAST_CLUST(newclust, mydata->fatsize))
+ break;
+ if (CHECK_CLUST(newclust, mydata->fatsize)) {
+ debug("curclust: 0x%x\n", curclust);
+ debug("Invalid FAT entry\n");
+ return -1;
+ }
+
+ actsize += bytesperclust;
+ endclust = newclust;
+ }
+
+ /* overwrite to <curclust..endclust> */
+ if (pos < cur_pos)
+ offset = 0;
+ else
+ offset = pos - cur_pos;
+ wsize = min(cur_pos + actsize, filesize) - pos;
+ if (get_set_cluster(mydata, curclust, offset,
+ buffer, wsize, &actsize)) {
+ printf("Error get-and-setting cluster\n");
+ return -1;
+ }
+ buffer += wsize;
+ *gotsize += wsize;
+ cur_pos += offset + wsize;
+
+ if (filesize <= cur_pos)
+ break;
+
+ /* CHECK: newclust = get_fatent(mydata, endclust); */
+
+ if (IS_LAST_CLUST(newclust, mydata->fatsize))
+ /* no more clusters */
+ break;
+
+ curclust = newclust;
+ }
+
+ if (filesize <= cur_pos) {
+ /* no more write */
+ newclust = get_fatent(mydata, endclust);
+ if (!IS_LAST_CLUST(newclust, mydata->fatsize)) {
+ /* truncate the rest */
+ clear_fatent(mydata, newclust);
+
+ /* Mark end of file in FAT */
+ if (mydata->fatsize == 12)
+ newclust = 0xfff;
+ else if (mydata->fatsize == 16)
+ newclust = 0xffff;
+ else if (mydata->fatsize == 32)
+ newclust = 0xfffffff;
+ set_fatent_value(mydata, endclust, newclust);
+ }
+
+ return 0;
+ }
+
+ curclust = endclust;
+ filesize -= cur_pos;
+ assert(!(cur_pos % bytesperclust));
+
+set_clusters:
+ /* allocate and write */
+ assert(!pos);
+
+ /* Assure that curclust is valid */
+ if (!curclust) {
+ curclust = find_empty_cluster(mydata);
+ set_start_cluster(mydata, dentptr, curclust);
+ } else {
+ newclust = get_fatent(mydata, curclust);
+
+ if (IS_LAST_CLUST(newclust, mydata->fatsize)) {
+ newclust = determine_fatent(mydata, curclust);
+ set_fatent_value(mydata, curclust, newclust);
+ curclust = newclust;
+ } else {
+ debug("error: something wrong\n");
+ return -1;
+ }
+ }
+
+ /* TODO: already partially written */
if (check_overflow(mydata, curclust, filesize)) {
printf("Error: no space left: %llu\n", filesize);
return -1;
@@ -849,6 +1096,16 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer,
goto exit;
}
+ /* A file exists */
+ if (pos == -1)
+ /* Append to the end */
+ pos = FAT2CPU32(retdent->size);
+ if (pos > retdent->size) {
+ /* No hole allowed */
+ ret = -EINVAL;
+ goto exit;
+ }
+
/* Update file size in a directory entry */
retdent->size = cpu_to_le32(pos + size);
} else {
@@ -869,6 +1126,12 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer,
goto exit;
}
+ if (pos) {
+ /* No hole allowed */
+ ret = -EINVAL;
+ goto exit;
+ }
+
memset(itr->dent, 0, sizeof(*itr->dent));
/* Set short name to set alias checksum field in dir_slot */
@@ -918,10 +1181,5 @@ exit:
int file_fat_write(const char *filename, void *buffer, loff_t offset,
loff_t maxsize, loff_t *actwrite)
{
- if (offset != 0) {
- printf("Error: non zero offset is currently not supported.\n");
- return -EINVAL;
- }
-
return file_fat_write_at(filename, offset, buffer, maxsize, actwrite);
}