Browse Source

Added sticky-bit for preventing file syncs after write errors

Short story, files are no longer committed to directories during
file sync/close if the last write did not complete successfully.
This avoids a set of interesting user-experience issues related
to the end-of-life behaviour of the filesystem.

As a filesystem approaches end-of-life, the chances of running into
LFS_ERR_NOSPC grows rather quickly. Since this condition occurs after
at the end of a devices life, it's likely that operating in these
conditions hasn't been tested thoroughly.

In the specific case of file-writes, you can hit an LFS_ERR_NOSPC after
parts of the file have been written out. If the program simply continues
and closes the file, the file is written out half completed. Since
littlefs has a strong garuntee the prevents half-writes, it's unlikely
this state of the file would be expected.

To make things worse, since close is also responsible for memory
cleanup, it's actually _impossible_ to continue working as it was
without leaking memory.

By prevent the file commits, end-of-life behaviour should at least retain
a previous copy of the filesystem without any surprises.
Christopher Haster 8 năm trước cách đây
mục cha
commit
843e3c6c75
3 tập tin đã thay đổi với 28 bổ sung10 xóa
  1. 8 1
      lfs.c
  2. 1 0
      lfs.h
  3. 19 9
      tests/test_alloc.sh

+ 8 - 1
lfs.c

@@ -1403,7 +1403,9 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
         return err;
     }
 
-    if ((file->flags & LFS_F_DIRTY) && !lfs_pairisnull(file->pair)) {
+    if ((file->flags & LFS_F_DIRTY) &&
+            !(file->flags & LFS_F_ERRED) &&
+            !lfs_pairisnull(file->pair)) {
         // update dir entry
         lfs_dir_t cwd;
         int err = lfs_dir_fetch(lfs, &cwd, file->pair);
@@ -1537,6 +1539,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
                         file->head, file->size,
                         file->pos-1, &file->block, &file->off);
                 if (err) {
+                    file->flags |= LFS_F_ERRED;
                     return err;
                 }
 
@@ -1550,6 +1553,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
                     file->block, file->pos,
                     &file->block, &file->off);
             if (err) {
+                file->flags |= LFS_F_ERRED;
                 return err;
             }
 
@@ -1565,6 +1569,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
                 if (err == LFS_ERR_CORRUPT) {
                     goto relocate;
                 }
+                file->flags |= LFS_F_ERRED;
                 return err;
             }
 
@@ -1572,6 +1577,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
 relocate:
             err = lfs_file_relocate(lfs, file);
             if (err) {
+                file->flags |= LFS_F_ERRED;
                 return err;
             }
         }
@@ -1584,6 +1590,7 @@ relocate:
         lfs_alloc_ack(lfs);
     }
 
+    file->flags &= ~LFS_F_ERRED;
     return size;
 }
 

+ 1 - 0
lfs.h

@@ -75,6 +75,7 @@ enum lfs_open_flags {
     LFS_F_DIRTY   = 0x10000, // File does not match storage
     LFS_F_WRITING = 0x20000, // File has been written since last flush
     LFS_F_READING = 0x40000, // File has been read since last flush
+    LFS_F_ERRED   = 0x80000, // An error occured during write
 };
 
 // File seek flags

+ 19 - 9
tests/test_alloc.sh

@@ -121,6 +121,7 @@ tests/test.py << TEST
     size = strlen("exhaustion");
     memcpy(buffer, "exhaustion", size);
     lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    lfs_file_sync(&lfs, &file[0]) => 0;
 
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
@@ -142,6 +143,7 @@ tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_RDONLY);
     size = strlen("exhaustion");
+    lfs_file_size(&lfs, &file[0]) => size;
     lfs_file_read(&lfs, &file[0], buffer, size) => size;
     memcmp(buffer, "exhaustion", size) => 0;
     lfs_file_close(&lfs, &file[0]) => 0;
@@ -166,6 +168,7 @@ tests/test.py << TEST
     size = strlen("exhaustion");
     memcpy(buffer, "exhaustion", size);
     lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    lfs_file_sync(&lfs, &file[0]) => 0;
 
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
@@ -187,6 +190,7 @@ tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_RDONLY);
     size = strlen("exhaustion");
+    lfs_file_size(&lfs, &file[0]) => size;
     lfs_file_read(&lfs, &file[0], buffer, size) => size;
     memcmp(buffer, "exhaustion", size) => 0;
     lfs_file_close(&lfs, &file[0]) => 0;
@@ -196,14 +200,14 @@ TEST
 echo "--- Dir exhaustion test ---"
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
-    lfs_stat(&lfs, "exhaustion", &info) => 0;
-    lfs_size_t fullsize = info.size;
     lfs_remove(&lfs, "exhaustion") => 0;
 
     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
-    for (lfs_size_t i = 0; i < fullsize - 2*512; i += size) {
+    for (lfs_size_t i = 0;
+            i < (cfg.block_count-6)*(cfg.block_size-8);
+            i += size) {
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
     }
     lfs_file_close(&lfs, &file[0]) => 0;
@@ -214,7 +218,11 @@ tests/test.py << TEST
     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_APPEND);
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
-    lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    for (lfs_size_t i = 0;
+            i < (cfg.block_size-8);
+            i += size) {
+        lfs_file_write(&lfs, &file[0], buffer, size) => size;
+    }
     lfs_file_close(&lfs, &file[0]) => 0;
 
     lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
@@ -224,14 +232,14 @@ TEST
 echo "--- Chained dir exhaustion test ---"
 tests/test.py << TEST
     lfs_mount(&lfs, &cfg) => 0;
-    lfs_stat(&lfs, "exhaustion", &info) => 0;
-    lfs_size_t fullsize = info.size;
-
     lfs_remove(&lfs, "exhaustion") => 0;
+
     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
-    for (lfs_size_t i = 0; i < fullsize - 19*512; i += size) {
+    for (lfs_size_t i = 0;
+            i < (cfg.block_count-24)*(cfg.block_size-8);
+            i += size) {
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
     }
     lfs_file_close(&lfs, &file[0]) => 0;
@@ -247,7 +255,9 @@ tests/test.py << TEST
     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
     size = strlen("blahblahblahblah");
     memcpy(buffer, "blahblahblahblah", size);
-    for (lfs_size_t i = 0; i < fullsize - 20*512; i += size) {
+    for (lfs_size_t i = 0;
+            i < (cfg.block_count-26)*(cfg.block_size-8);
+            i += size) {
         lfs_file_write(&lfs, &file[0], buffer, size) => size;
     }
     lfs_file_close(&lfs, &file[0]) => 0;