Skip to content

Commit

Permalink
Added sticky-bit for preventing file syncs after write errors
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
geky committed Nov 16, 2017
1 parent 2612e1b commit 843e3c6
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 10 deletions.
9 changes: 8 additions & 1 deletion lfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -1565,13 +1569,15 @@ 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;
}

break;
relocate:
err = lfs_file_relocate(lfs, file);
if (err) {
file->flags |= LFS_F_ERRED;
return err;
}
}
Expand All @@ -1584,6 +1590,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
lfs_alloc_ack(lfs);
}

file->flags &= ~LFS_F_ERRED;
return size;
}

Expand Down
1 change: 1 addition & 0 deletions lfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 19 additions & 9 deletions tests/test_alloc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down

0 comments on commit 843e3c6

Please sign in to comment.