A write at an offset that ends before the end of the file trims the
file at that point. This was chosen on purpose in commit cb8af8af5ba0
("fs: fat: support write with non-zero offset") to stay compatible with
the interface of the fatwrite command, which replaces the whole file.

Callers that update a part of a file, like the EFI file protocol, need
the data after the written range to stay.

Move the body of file_fat_write_at() to a static function that takes a
keep_tail flag. With the flag set, set_contents() does not free the
clusters after the written range, and the size in the directory entry
only grows. Keep file_fat_write_at() as a wrapper with the current
behaviour and add file_fat_overwrite() for the new one.

Signed-off-by: Cedric Hombourger <[email protected]>
---
 fs/fat/fat_write.c | 32 ++++++++++++++++++++++++--------
 include/fat.h      |  2 ++
 2 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c
index 
a1d28b4e305766b8fb8ee097fafab235faf8ad52..03cd6d368b01d7b4d08502e1783e85f61c39e323
 100644
--- a/fs/fat/fat_write.c
+++ b/fs/fat/fat_write.c
@@ -925,10 +925,12 @@ static int check_overflow(fsdata *mydata, __u32 clustnum, 
loff_t size)
  * the file associated with 'dentptr'
  * Update the number of bytes written in *gotsize and return 0
  * or return -1 on fatal errors.
+ * Unless 'keep_tail' is set, a write that ends before the end of the file
+ * truncates the file there.
  */
 static int
 set_contents(fsdata *mydata, dir_entry *dentptr, loff_t pos, __u8 *buffer,
-            loff_t maxsize, loff_t *gotsize)
+            loff_t maxsize, loff_t *gotsize, bool keep_tail)
 {
        unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
        __u32 curclust = START(dentptr);
@@ -1045,7 +1047,7 @@ set_contents(fsdata *mydata, dir_entry *dentptr, loff_t 
pos, __u8 *buffer,
        if (filesize <= cur_pos) {
                /* no more write */
                newclust = get_fatent(mydata, endclust);
-               if (!IS_LAST_CLUST(newclust, mydata->fatsize)) {
+               if (!keep_tail && !IS_LAST_CLUST(newclust, mydata->fatsize)) {
                        /* truncate the rest */
                        clear_fatent(mydata, newclust);
 
@@ -1469,8 +1471,8 @@ static int normalize_longname(char *l_filename, const 
char *filename)
        return 0;
 }
 
-int file_fat_write_at(const char *filename, loff_t pos, void *buffer,
-                     loff_t size, loff_t *actwrite)
+static int fat_write_at(const char *filename, loff_t pos, void *buffer,
+                       loff_t size, loff_t *actwrite, bool keep_tail)
 {
        dir_entry *retdent;
        fsdata datablock = { .fatbuf = NULL, };
@@ -1535,7 +1537,8 @@ int file_fat_write_at(const char *filename, loff_t pos, 
void *buffer,
                }
 
                /* Update file size in a directory entry */
-               retdent->size = cpu_to_le32(pos + size);
+               if (!keep_tail || pos + size > FAT2CPU32(retdent->size))
+                       retdent->size = cpu_to_le32(pos + size);
                /* Update change date */
                dentry_set_time(retdent);
        } else {
@@ -1552,7 +1555,8 @@ int file_fat_write_at(const char *filename, loff_t pos, 
void *buffer,
                retdent = itr->dent;
        }
 
-       ret = set_contents(mydata, retdent, pos, buffer, size, actwrite);
+       ret = set_contents(mydata, retdent, pos, buffer, size, actwrite,
+                          keep_tail);
        if (ret < 0) {
                printf("Error: writing contents\n");
                ret = -EIO;
@@ -1578,6 +1582,18 @@ exit:
        return ret;
 }
 
+int file_fat_write_at(const char *filename, loff_t pos, void *buffer,
+                     loff_t size, loff_t *actwrite)
+{
+       return fat_write_at(filename, pos, buffer, size, actwrite, false);
+}
+
+int file_fat_overwrite(const char *filename, void *buffer, loff_t offset,
+                      loff_t maxsize, loff_t *actwrite)
+{
+       return fat_write_at(filename, offset, buffer, maxsize, actwrite, true);
+}
+
 int file_fat_write(const char *filename, void *buffer, loff_t offset,
                   loff_t maxsize, loff_t *actwrite)
 {
@@ -1877,7 +1893,7 @@ int fat_mkdir(const char *dirname)
                set_start_cluster(mydata, &dotdent[1], itr->start_clust);
 
        ret = set_contents(mydata, retdent, 0, (__u8 *)dotdent,
-                          bytesperclust, &actwrite);
+                          bytesperclust, &actwrite, false);
        if (ret < 0) {
                printf("Error: writing contents\n");
                goto exit;
@@ -1885,7 +1901,7 @@ int fat_mkdir(const char *dirname)
        /* Write twice for "." */
        set_start_cluster(mydata, &dotdent[0], START(retdent));
        ret = set_contents(mydata, retdent, 0, (__u8 *)dotdent,
-                          bytesperclust, &actwrite);
+                          bytesperclust, &actwrite, false);
        if (ret < 0) {
                printf("Error: writing contents\n");
                goto exit;
diff --git a/include/fat.h b/include/fat.h
index 
40da0370a4442edad872661408c856b7dac0787c..499e1924838049568466d8d4329c24ac4c26b087
 100644
--- a/include/fat.h
+++ b/include/fat.h
@@ -225,6 +225,8 @@ int fat_register_device(struct blk_desc *dev_desc, int 
part_no);
 
 int file_fat_write(const char *filename, void *buf, loff_t offset, loff_t len,
                   loff_t *actwrite);
+int file_fat_overwrite(const char *filename, void *buf, loff_t offset,
+                      loff_t len, loff_t *actwrite);
 int fat_read_file(const char *filename, void *buf, loff_t offset, loff_t len,
                  loff_t *actread);
 int fat_opendir(const char *filename, struct fs_dir_stream **dirsp);

-- 
2.47.3

Reply via email to