Fixed issues with neighbor updates during moves

The root of the problem was some assumptions about what tags could be
sent to lfs_dir_commit.

- The first assumption is that there could be only one splice (create/delete)
  tag at a time, which is trivially broken by the core commit in lfs_rename.

- The second assumption is that there is at most one create and one delete in
  a single commit. This is less obvious but turns out to not be true in
  the case that we rename a file such that it overwrites another file in
  the same directory (1 delete for source file, 1 delete for destination).

- The third assumption was that there was an ordering to the
  delete/creates passed to lfs_dir_commit. It may be possible to force all
  deletes to follow creates by rearranging the tags in lfs_rename, but
  this risks overflowing tag ids.

The way the lfs_dir_commit first collected the "deletetag" and "createtag"
broke all three of these assumptions. And because we lose the ordering
information we can no longer apply the directory changes to open files
correctly. The file ids may be shifted in a way that doesn't reflect the
actual operations on disk.

These problems were made worst by lfs_dir_commit cleaning up moves
implicitly, which also creates deletes implicitly. While cleaning up moves
in lfs_dir_commit may save some code size, it makes the commit logic much more
difficult to implement correctly.

This bug turned into pulling out a dead tree stump, roots and all.

I ended up reworking how lfs_dir_commit updates open files so that it
has less assumptions, now it just traverses the commit tags multiple
times in order to update file ids after a successful commit in the
correct order.

This also got rid of the dir copy by carefully updating split dirs
after all files have an up-to-date copy of the original dir.

I also just removed the implicit move cleanup. It turns out the only
commits that can occur before we have cleaned up the move is in
lfs_fs_relocate, so it was simple enough to explicitly handle this case
when we update our parent and pred during a relocate.

Cases where we may need to fix moves:
- In lfs_rename when we move a file/dir
- In lfs_demove if we lose power
- In lfs_fs_relocate if we have to relocate our parent and we find it
  had a pending move (or else the move will be outdated)
- In lfs_fs_relocate if we have to relocate our predecessor and we find it
  had a pending move (or else the move will be outdated)

Note the two cases in lfs_fs_relocate may be recursive. But
lfs_fs_relocate can only trigger other lfs_fs_relocates so it's not
possible for pending moves to spill out into other filesystem commits

And of couse, I added several tests to cover these situations. Hopefully
the rename-with-open-files logic should be fairly locked down now.

found with initial fix by eastmoutain
This commit is contained in:
Christopher Haster
2020-01-20 17:35:45 -06:00
parent 9453ebd15d
commit f4b6a6b328
4 changed files with 1003 additions and 117 deletions

View File

@@ -349,7 +349,7 @@ code = '''
assert(strcmp(info.name, "hello") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 5+8+6);
count += 1;
count += 1;
}
if (lfs_stat(&lfs, "c/hello", &info) == 0) {
assert(strcmp(info.name, "hello") == 0);
@@ -375,6 +375,7 @@ code = '''
lfs_rename(&lfs, "c/hello", "d/hello") => 0;
} else if (lfs_stat(&lfs, "d/hello", &info) == 0) {
// success
lfs_unmount(&lfs) => 0;
break;
} else {
// create file
@@ -807,7 +808,7 @@ code = '''
if (lfs_stat(&lfs, "b/hi", &info) == 0) {
assert(strcmp(info.name, "hi") == 0);
assert(info.type == LFS_TYPE_DIR);
count += 1;
count += 1;
}
if (lfs_stat(&lfs, "c/hi", &info) == 0) {
assert(strcmp(info.name, "hi") == 0);
@@ -830,6 +831,7 @@ code = '''
} else if (lfs_stat(&lfs, "c/hi", &info) == 0) {
lfs_rename(&lfs, "c/hi", "d/hi") => 0;
} else if (lfs_stat(&lfs, "d/hi", &info) == 0) {
lfs_unmount(&lfs) => 0;
break; // success
} else {
// create dir and rename for atomicity
@@ -955,3 +957,839 @@ code = '''
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
# Other specific corner cases
[[case]] # create + delete in same commit with neighbors
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
// littlefs keeps files sorted, so we know the order these will be in
lfs_file_open(&lfs, &file, "/1.move_me",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/0.before",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.1", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/2.in_between",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.2", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/4.after",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.3", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_t files[3];
lfs_file_open(&lfs, &files[0], "0.before",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[1], "2.in_between",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[2], "4.after",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_write(&lfs, &files[0], "test.4", 7) => 7;
lfs_file_write(&lfs, &files[1], "test.5", 7) => 7;
lfs_file_write(&lfs, &files[2], "test.6", 7) => 7;
// rename file while everything is open, this triggers both
// a create and delete simultaneously
lfs_rename(&lfs, "/1.move_me", "/3.move_me") => 0;
lfs_file_close(&lfs, &files[0]) => 0;
lfs_file_close(&lfs, &files[1]) => 0;
lfs_file_close(&lfs, &files[2]) => 0;
// check that nothing was corrupted
lfs_dir_open(&lfs, &dir, "/") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "0.before") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "2.in_between") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "3.move_me") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "4.after") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_file_open(&lfs, &file, "/0.before", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.4") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/2.in_between", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.5") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/4.after", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.6") == 0);
lfs_file_close(&lfs, &file) => 0;
// now move back
lfs_file_open(&lfs, &files[0], "0.before",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[1], "2.in_between",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[2], "4.after",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_write(&lfs, &files[0], "test.7", 7) => 7;
lfs_file_write(&lfs, &files[1], "test.8", 7) => 7;
lfs_file_write(&lfs, &files[2], "test.9", 7) => 7;
// rename file while everything is open, this triggers both
// a create and delete simultaneously
lfs_rename(&lfs, "/3.move_me", "/1.move_me") => 0;
lfs_file_close(&lfs, &files[0]) => 0;
lfs_file_close(&lfs, &files[1]) => 0;
lfs_file_close(&lfs, &files[2]) => 0;
// and check that nothing was corrupted again
lfs_dir_open(&lfs, &dir, "/") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "0.before") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "1.move_me") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "2.in_between") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "4.after") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_file_open(&lfs, &file, "/0.before", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.7") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/2.in_between", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.8") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/4.after", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.9") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
# Other specific corner cases
[[case]] # create + delete + delete in same commit with neighbors
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
// littlefs keeps files sorted, so we know the order these will be in
lfs_file_open(&lfs, &file, "/1.move_me",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/3.move_me",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "remove me",
sizeof("remove me")) => sizeof("remove me");
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/0.before",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.1", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/2.in_between",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.2", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/4.after",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.3", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_t files[3];
lfs_file_open(&lfs, &files[0], "0.before",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[1], "2.in_between",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[2], "4.after",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_write(&lfs, &files[0], "test.4", 7) => 7;
lfs_file_write(&lfs, &files[1], "test.5", 7) => 7;
lfs_file_write(&lfs, &files[2], "test.6", 7) => 7;
// rename file while everything is open, this triggers both
// a create and delete simultaneously
lfs_rename(&lfs, "/1.move_me", "/3.move_me") => 0;
lfs_file_close(&lfs, &files[0]) => 0;
lfs_file_close(&lfs, &files[1]) => 0;
lfs_file_close(&lfs, &files[2]) => 0;
// check that nothing was corrupted
lfs_dir_open(&lfs, &dir, "/") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "0.before") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "2.in_between") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "3.move_me") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "4.after") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_file_open(&lfs, &file, "/0.before", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.4") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/2.in_between", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.5") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/4.after", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.6") == 0);
lfs_file_close(&lfs, &file) => 0;
// now move back
lfs_file_open(&lfs, &file, "/1.move_me",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "remove me",
sizeof("remove me")) => sizeof("remove me");
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &files[0], "0.before",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[1], "2.in_between",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[2], "4.after",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_write(&lfs, &files[0], "test.7", 7) => 7;
lfs_file_write(&lfs, &files[1], "test.8", 7) => 7;
lfs_file_write(&lfs, &files[2], "test.9", 7) => 7;
// rename file while everything is open, this triggers both
// a create and delete simultaneously
lfs_rename(&lfs, "/3.move_me", "/1.move_me") => 0;
lfs_file_close(&lfs, &files[0]) => 0;
lfs_file_close(&lfs, &files[1]) => 0;
lfs_file_close(&lfs, &files[2]) => 0;
// and check that nothing was corrupted again
lfs_dir_open(&lfs, &dir, "/") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "0.before") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "1.move_me") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "2.in_between") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "4.after") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_file_open(&lfs, &file, "/0.before", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.7") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/2.in_between", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.8") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/4.after", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.9") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
[[case]] # create + delete in different dirs with neighbors
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
// littlefs keeps files sorted, so we know the order these will be in
lfs_mkdir(&lfs, "/dir.1") => 0;
lfs_mkdir(&lfs, "/dir.2") => 0;
lfs_file_open(&lfs, &file, "/dir.1/1.move_me",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/dir.2/1.move_me",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "remove me",
sizeof("remove me")) => sizeof("remove me");
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/dir.1/0.before",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.1", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/dir.1/2.after",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.2", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/dir.2/0.before",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.3", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/dir.2/2.after",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.4", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_t files[4];
lfs_file_open(&lfs, &files[0], "/dir.1/0.before",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[1], "/dir.1/2.after",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[2], "/dir.2/0.before",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[3], "/dir.2/2.after",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_write(&lfs, &files[0], "test.5", 7) => 7;
lfs_file_write(&lfs, &files[1], "test.6", 7) => 7;
lfs_file_write(&lfs, &files[2], "test.7", 7) => 7;
lfs_file_write(&lfs, &files[3], "test.8", 7) => 7;
// rename file while everything is open, this triggers both
// a create and delete as it overwrites the destination file
lfs_rename(&lfs, "/dir.1/1.move_me", "/dir.2/1.move_me") => 0;
lfs_file_close(&lfs, &files[0]) => 0;
lfs_file_close(&lfs, &files[1]) => 0;
lfs_file_close(&lfs, &files[2]) => 0;
lfs_file_close(&lfs, &files[3]) => 0;
// check that nothing was corrupted
lfs_dir_open(&lfs, &dir, "/") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "dir.1") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "dir.2") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_dir_open(&lfs, &dir, "/dir.1") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "0.before") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "2.after") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_dir_open(&lfs, &dir, "/dir.2") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "0.before") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "1.move_me") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "2.after") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_file_open(&lfs, &file, "/dir.1/0.before", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.5") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/dir.1/2.after", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.6") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/dir.2/0.before", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.7") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/dir.2/2.after", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.8") == 0);
lfs_file_close(&lfs, &file) => 0;
// now move back
lfs_file_open(&lfs, &file, "/dir.1/1.move_me",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "remove me",
sizeof("remove me")) => sizeof("remove me");
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &files[0], "/dir.1/0.before",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[1], "/dir.1/2.after",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[2], "/dir.2/0.before",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[3], "/dir.2/2.after",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_write(&lfs, &files[0], "test.9", 7) => 7;
lfs_file_write(&lfs, &files[1], "test.a", 7) => 7;
lfs_file_write(&lfs, &files[2], "test.b", 7) => 7;
lfs_file_write(&lfs, &files[3], "test.c", 7) => 7;
// rename file while everything is open, this triggers both
// a create and delete simultaneously
lfs_rename(&lfs, "/dir.2/1.move_me", "/dir.1/1.move_me") => 0;
lfs_file_close(&lfs, &files[0]) => 0;
lfs_file_close(&lfs, &files[1]) => 0;
lfs_file_close(&lfs, &files[2]) => 0;
lfs_file_close(&lfs, &files[3]) => 0;
// and check that nothing was corrupted again
lfs_dir_open(&lfs, &dir, "/") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "dir.1") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "dir.2") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_dir_open(&lfs, &dir, "/dir.1") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "0.before") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "1.move_me") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "2.after") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_dir_open(&lfs, &dir, "/dir.2") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "0.before") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "2.after") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_file_open(&lfs, &file, "/dir.1/0.before", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.9") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/dir.1/2.after", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.a") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/dir.2/0.before", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.b") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/dir.2/2.after", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.c") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
[[case]] # move fix in relocation
in = "lfs.c"
define.RELOCATIONS = 'range(0x3+1)'
define.LFS_ERASE_CYCLES = 0xffffffff
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "/parent") => 0;
lfs_mkdir(&lfs, "/parent/child") => 0;
lfs_file_open(&lfs, &file, "/parent/1.move_me",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "move me",
sizeof("move me")) => sizeof("move me");
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/0.before",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.1", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/2.after",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.2", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/child/0.before",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.3", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/child/2.after",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.4", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_t files[4];
lfs_file_open(&lfs, &files[0], "/parent/0.before",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[1], "/parent/2.after",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[2], "/parent/child/0.before",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[3], "/parent/child/2.after",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_write(&lfs, &files[0], "test.5", 7) => 7;
lfs_file_write(&lfs, &files[1], "test.6", 7) => 7;
lfs_file_write(&lfs, &files[2], "test.7", 7) => 7;
lfs_file_write(&lfs, &files[3], "test.8", 7) => 7;
// force specific directories to relocate
if (RELOCATIONS & 0x1) {
lfs_dir_open(&lfs, &dir, "/parent");
lfs_testbd_setwear(&cfg, dir.m.pair[0], 0xffffffff) => 0;
lfs_testbd_setwear(&cfg, dir.m.pair[1], 0xffffffff) => 0;
lfs_dir_close(&lfs, &dir) => 0;
}
if (RELOCATIONS & 0x2) {
lfs_dir_open(&lfs, &dir, "/parent/child");
lfs_testbd_setwear(&cfg, dir.m.pair[0], 0xffffffff) => 0;
lfs_testbd_setwear(&cfg, dir.m.pair[1], 0xffffffff) => 0;
lfs_dir_close(&lfs, &dir) => 0;
}
// ok, now we move the file, this creates a move that needs to be
// fixed, possibly in a metadata-pair that needs to be relocated
//
// the worst case is if we need to relocate and we need to implicit
// fix the move in our parent before it falls out of date
lfs_rename(&lfs, "/parent/1.move_me", "/parent/child/1.move_me") => 0;
lfs_file_close(&lfs, &files[0]) => 0;
lfs_file_close(&lfs, &files[1]) => 0;
lfs_file_close(&lfs, &files[2]) => 0;
lfs_file_close(&lfs, &files[3]) => 0;
// check that nothing was corrupted
lfs_dir_open(&lfs, &dir, "/parent") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "0.before") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "2.after") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "child") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_dir_open(&lfs, &dir, "/parent/child") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "0.before") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "1.move_me") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == sizeof("move me"));
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "2.after") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_file_open(&lfs, &file, "/parent/0.before", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.5") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/2.after", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.6") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/child/0.before", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.7") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/child/2.after", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.8") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
[[case]] # move fix in relocation with predecessor
in = "lfs.c"
define.RELOCATIONS = 'range(0x7+1)'
define.LFS_ERASE_CYCLES = 0xffffffff
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "/parent") => 0;
lfs_mkdir(&lfs, "/parent/child") => 0;
lfs_mkdir(&lfs, "/parent/sibling") => 0;
lfs_file_open(&lfs, &file, "/parent/sibling/1.move_me",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "move me",
sizeof("move me")) => sizeof("move me");
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/sibling/0.before",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.1", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/sibling/2.after",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.2", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/child/0.before",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.3", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/child/2.after",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_write(&lfs, &file, "test.4", 7) => 7;
lfs_file_close(&lfs, &file) => 0;
lfs_file_t files[4];
lfs_file_open(&lfs, &files[0], "/parent/sibling/0.before",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[1], "/parent/sibling/2.after",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[2], "/parent/child/0.before",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &files[3], "/parent/child/2.after",
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_write(&lfs, &files[0], "test.5", 7) => 7;
lfs_file_write(&lfs, &files[1], "test.6", 7) => 7;
lfs_file_write(&lfs, &files[2], "test.7", 7) => 7;
lfs_file_write(&lfs, &files[3], "test.8", 7) => 7;
// force specific directories to relocate
if (RELOCATIONS & 0x1) {
lfs_dir_open(&lfs, &dir, "/parent");
lfs_testbd_setwear(&cfg, dir.m.pair[0], 0xffffffff) => 0;
lfs_testbd_setwear(&cfg, dir.m.pair[1], 0xffffffff) => 0;
lfs_dir_close(&lfs, &dir) => 0;
}
if (RELOCATIONS & 0x2) {
lfs_dir_open(&lfs, &dir, "/parent/sibling");
lfs_testbd_setwear(&cfg, dir.m.pair[0], 0xffffffff) => 0;
lfs_testbd_setwear(&cfg, dir.m.pair[1], 0xffffffff) => 0;
lfs_dir_close(&lfs, &dir) => 0;
}
if (RELOCATIONS & 0x4) {
lfs_dir_open(&lfs, &dir, "/parent/child");
lfs_testbd_setwear(&cfg, dir.m.pair[0], 0xffffffff) => 0;
lfs_testbd_setwear(&cfg, dir.m.pair[1], 0xffffffff) => 0;
lfs_dir_close(&lfs, &dir) => 0;
}
// ok, now we move the file, this creates a move that needs to be
// fixed, possibly in a metadata-pair that needs to be relocated
//
// and now relocations can force us to need to fix our move in either
// the parent or child before things break
lfs_rename(&lfs,
"/parent/sibling/1.move_me",
"/parent/child/1.move_me") => 0;
lfs_file_close(&lfs, &files[0]) => 0;
lfs_file_close(&lfs, &files[1]) => 0;
lfs_file_close(&lfs, &files[2]) => 0;
lfs_file_close(&lfs, &files[3]) => 0;
// check that nothing was corrupted
lfs_dir_open(&lfs, &dir, "/parent") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "child") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "sibling") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_dir_open(&lfs, &dir, "/parent/sibling") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "0.before") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "2.after") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_dir_open(&lfs, &dir, "/parent/child") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "0.before") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "1.move_me") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == sizeof("move me"));
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, "2.after") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == 7);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_file_open(&lfs, &file, "/parent/sibling/0.before", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.5") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/sibling/2.after", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.6") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/child/0.before", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.7") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "/parent/child/2.after", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 7) => 7;
assert(strcmp((char*)buffer, "test.8") == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''