Files
littlefs/tests
Christopher Haster 5055a40d8b Made LFS_O_EXCL error if file is open but uncreated
One of the unexpected side-effects of lazy file creation is that
suddenly LFS_O_EXCL doesn't make sense.

The standard definition: "Fail if the file exists", is easy enough to
implement, but doesn't really match what the user expects.

The user expects one of these calls to fail:

  lfsr_file_open(&lfs, &file_a, "file.txt",
          LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
  lfsr_file_open(&lfs, &file_b, "file.txt",
          LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;

But because we create files lazily (to prevent zero-length files after
powerloss), these both succeed.

---

I considered deferring the "file exists" check until we actually would
create the file, but while this _technically_ satisfies the
exclusitivity requirement, I decided against it as I think it just makes
the API way too confusing:

  lfsr_file_open(&lfs, &file_a, "file.txt",
          LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
  lfsr_file_open(&lfs, &file_b, "file.txt",
          LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
  lfsr_file_close(&lfs, &file_a) => 0;
  lfsr_file_close(&lfs, &file_b) => LFS_ERR_EXIST;

---

Instead, a simpler, more pragmatic approach: Fail if the file exists
_or_ if the file is open in a mode that will create the file:

  lfsr_file_open(&lfs, &file_a, "file.txt",
          LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
  lfsr_file_open(&lfs, &file_b, "file.txt",
          LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST;

This explicitly does _not_ error on zombie/desync files:

  lfsr_file_open(&lfs, &file_a, "file.txt",
          LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
  lfsr_file_desync(&lfs, &file_a) => 0;
  lfsr_file_open(&lfs, &file_b, "file.txt",
          LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;

And it does mean we aren't necessarily guaranteeing the file will be
created, but I think this does more-or-less what the user expects:

- open(a) -> desync(a) -> open(b) -> resync(a) is roughly equivalent to
  opening a after creating b, which is perfectly fine with LFS_O_EXCL.

- open(a) -> open(b) (errors) -> desync(a) is one way to not actually
  create the file, but is somewhat similar to removing the file after
  creation.

  If you're using desync files you should probably have a good
  understanding of littlefs's sync model anyways.

And of course the user can always sync immediately after open to
guarantee file creation, while opting into the possibility of
zero-length files after powerloss.

Code changes:

           code          stack          ctx
  before: 38084           2624          752
  after:  38128 (+0.1%)   2624 (+0.0%)  752 (+0.0%)
2025-01-28 14:41:45 -06:00
..
2024-08-20 19:59:08 -05:00