Skip to content

Conversation

@maximevince
Copy link
Contributor

@maximevince maximevince commented Oct 28, 2020

This PR tries to solve #162 , by providing an LFS_READONLY define.

LittleFS seems to be preferred over SPIFFS in many places and projects right now.
However, for my bootloader / firmware upgrade use-case, the code size matters a lot.
A bootloader could do with a read-only version of the file system, which should be a lot smaller in size.

With SPIFFS I am able to get a read-only version without caching compiled to around 3 KB (compiled for Cortex-M0),
while the read-write version with caching weighs in at around 13 KB, which is comparable to LittleFS.

By defining out a bunch of write-related functionality, I was able to reduce the read-only version of LittleFS to around 5 KB for that same Cortex-M0 target, which seems acceptable for bootloader use.

I have separately #ifdef'ed every function that is only needed for writing functionality.
However, I'm not sure if this is the preferred way, of if you'd rather have a single #ifdef around the whole lump of code.

Let me know if I can do anything to improve this PR.

Cheers and thanks for the awesome project! 👍

@geky geky added the v2.3 label Nov 14, 2020
@geky geky added this to the v2.3 milestone Nov 14, 2020
@geky
Copy link
Member

geky commented Nov 14, 2020

Hi @maximevince, thanks for creating a PR! Sorry being late in getting to this. This looks great.

I have separately #ifdef'ed every function that is only needed for writing functionality.
However, I'm not sure if this is the preferred way, of if you'd rather have a single #ifdef around the whole lump of code.

I hadn't thought about this, it has the nice side-effect that it is easy to know which functions are read-only in the source code 👍

@geky geky added the needs test all fixes need test coverage to prevent regression label Nov 14, 2020
@geky geky mentioned this pull request Nov 14, 2020
@geky
Copy link
Member

geky commented Nov 14, 2020

Also the CI was broken but should be fixed now. It should trigger on the next push to this branch.

@geky geky removed the needs test all fixes need test coverage to prevent regression label Nov 16, 2020
@dpgeorge
Copy link
Contributor

It's good to see support for read-only mode in littlefs. I did make a very similar patch to this (with the option called LFS_ENABLE_WRITES) but did not get time to make a PR out of it. In my use of it in a bootloader, littlefs with write support takes about 11000 bytes. In a read-only build that goes down to about 4200 bytes (on a Cortex-M7). That's very similar to what's reported above.

Will be good to see this merged!

@maximevince
Copy link
Contributor Author

It's good to see support for read-only mode in littlefs. I did make a very similar patch to this (with the option called LFS_ENABLE_WRITES) but did not get time to make a PR out of it. In my use of it in a bootloader, littlefs with write support takes about 11000 bytes. In a read-only build that goes down to about 4200 bytes (on a Cortex-M7). That's very similar to what's reported above.

Will be good to see this merged!

Alright, nice to hear from you @dpgeorge.
I believe we briefly conversed back when I was working on picoTCP and we made a port in the early days of micropython.

If you see any differences between the parts of the code I have #ifdef-ed, or where it could still be made smaller, let me know!

@maximevince
Copy link
Contributor Author

Just updated the PR with a new commit:

  • Removed write-related functions from the header altogether, and adjusted lfs.c accordingly
  • Replaced LFS_ERR_NOSYS by LFS_ERR_ROFS (-30)

I am not entirely sure what should happen to int lfs_file_sync(lfs_t *lfs, lfs_file_t *file):
Is this function at all needed for LittleFS in read-only mode? Right now I only #ifdefed the LFS_F_DIRTY portion out of it.
And I have the same question for lfs_file_flush(lfs, file), too.

@geky
Copy link
Member

geky commented Nov 18, 2020

Oh that is a good question. I'm going to say no to both, I don't see how they could be used in read-only mode.

If there some strange use case we're missing, they are easy to add back into the read-only build. It would be harder to remove them later.

if ((flags & 3) != LFS_O_RDONLY) {
#ifdef LFS_READONLY
return LFS_ERR_ROFS;
#else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would think that files can only ever be opened in LFS_O_RDONLY mode, so this entire if-block can just be excluded with the #if .

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also another similar block down below in this function which could also be #if'd out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually that's a good point, an LFS_ASSERT((flags & 3) == LFS_O_RDONLY) would work better here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a big fan of "crashing" with an assert, instead of returning an error when a lfs_file_opencfg() is called with the wrong arguments, but I'll implement it that way, if you want.

Let's say an OS tries to write to a file mounted using the LittleFS filesystem, but LittleFS is compiled for read-only. Wouldn't it be more elegant to return an error indicating it's not possible to write, instead of asserting ?

Copy link
Member

@geky geky Nov 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, it should be an assert for consistency (writing a file opened as rdonly currently asserts, for example).


Returning an error can be more graceful if there is an OS that understands that error, but for many littlefs applications this isn't the case. It's easy for the context of the error to get lost as "file open failed". Looking at POSIX errors, which littlefs follows, writing to an rdonly file only returns EINVAL. An assert takes you directly to the source of the problem, sort of like a poor-man's stack trace.

There's also a code cost for returning/handling errors, an assert can be dropped at compile time. Runtime checks can be added in the OS, but they can't be removed. This makes asserts preferred for conditions that should never happen unless there is a programmer mistake.

But you're not the first person to comment on this, maybe it would be worth adding a configuration option to change the use-error asserts to return errors. The next steps there would be to create a separate issue/PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, thanks for the detailed explanation. I can understand the reasoning indeed.

I have just pushed a new commit implementing the LFS_ASSERT for write attempts, as well as more code commented out, in case of LFS_READONLY.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One idea would be to also #if out the write-based constants, like LFS_O_WRONLY. Then it would be impossible to compile code that opened files for writing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's an even better idea! @dpgeorge with the great ideas

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I just pushed another commit implementing this idea.
I had to make some extra #ifdef sections in other functions checking for these flags, so please carefully review that.
What I also did, is marking each #endif section with a comment indicating which #ifdef it is corresponding to.

@dpgeorge
Copy link
Contributor

This is now looking almost exactly the same as the patch I made.

@geky geky changed the title Add LFS_READYONLY define, to allow smaller builds providing read-only mode Add LFS_READONLY define, to allow smaller builds providing read-only mode Nov 19, 2020
@geky geky added needs minor version new functionality only allowed in minor versions and removed next minor labels Nov 20, 2020
#endif

// internally used flags
LFS_F_DIRTY = 0x010000, // File does not match storage
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible (and worth it) also #if'ing these constants like LFS_F_DIRTY which are not supposed to be used in read-only mode?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was easy for LFS_F_DIRTY and LFS_F_WRITING.

However, I am not sure about LFS_F_ERRED.
It's documented as "An error occurred during write", but it has one non-obvious use at the end of int lfs_file_opencfg().
It seems you could end up there when e.g. lfs_malloc() fails, or when opening a file for read hits an LFS_ERR_NOENT (here) (among other possibilities).
Both of which cases do not seem to be strictly write-related.
So I conclude that this constant is needed in LFS_READONLY mode as well.

This is implemented in 35cc31e

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, it's not actually used in lfs_file_close but in the implicit lfs_file_sync. The purpose of the LFS_F_ERRED flag is to tell lfs_file_close to release resources without writing out to disk. lfs_file_opencfg immediately calls lfs_file_close if an error occurs.

You have the implicit lfs_file_sync removed in LFS_READONLY mode, so it should be safe to remove this line as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only noticed this comment now. I have ifdefed LFS_F_ERRED out completely. (99c960b)

@geky
Copy link
Member

geky commented Nov 22, 2020

This looks great, thanks for this! Will bring this in on the next minor release.

I added a Travis job, so we should see a read-only build size in the CI results now.

@geky geky added next minor and removed needs minor version new functionality only allowed in minor versions labels Nov 22, 2020
@geky geky changed the base branch from master to devel December 4, 2020 04:47
@geky geky merged commit 67a95d7 into littlefs-project:devel Dec 4, 2020
geky added a commit that referenced this pull request Dec 4, 2020
Add LFS_READONLY define, to allow smaller builds providing read-only mode
@geky geky mentioned this pull request Dec 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants