fs: fat: support write with non-zero offset
authorAKASHI Takahiro <takahiro.akashi@linaro.org>
Tue, 11 Sep 2018 06:59:06 +0000 (15:59 +0900)
committerAlexander Graf <agraf@suse.de>
Sun, 23 Sep 2018 19:55:29 +0000 (21:55 +0200)
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>
fs/fat/fat_write.c

index c22d8c7a46a12c4b0b644d3a32f60ae650569c16..651c7866debc598b33db97fdc1db1ae9259a0aeb 100644 (file)
@@ -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);
 }