Skip to content

Commit

Permalink
Added lfs_fs_stat for access to filesystem status/configuration
Browse files Browse the repository at this point in the history
Currently this includes:

- minor_version - on-disk minor version
- block_usage - estimated number of in-use blocks
- name_max - configurable name limit
- file_max - configurable file limit
- attr_max - configurable attr limit

These are currently the only configuration operations that need to be
written to disk. Other configuration is either needed to mount, such as
block_size, or does not change the on-disk representation, such as
read/prog_size.

This also includes the current block usage, which is common in other
filesystems, though a more expensive to find in littlefs. I figure it's
not unreasonable to make lfs_fs_stat no worse than block allocation,
hopefully this isn't a mistake. It may be worth caching the current
usage after the most recent lookahead scan.

More configuration may be added to this struct in the future.
  • Loading branch information
geky committed Jun 6, 2023
1 parent 66f0756 commit 87bbf1d
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 20 deletions.
60 changes: 58 additions & 2 deletions lfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -4324,11 +4324,9 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
"v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16,
major_version, minor_version,
LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR);
#ifndef LFS_READONLY
// note this bit is reserved on disk, so fetching more gstate
// will not interfere here
lfs_fs_prepsuperblock(lfs, true);
#endif
}

// check superblock configuration
Expand Down Expand Up @@ -4421,6 +4419,49 @@ static int lfs_rawunmount(lfs_t *lfs) {


/// Filesystem filesystem operations ///
static int lfs_fs_rawstat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) {
// if the superblock is up-to-date, we must be on the most recent
// minor version of littlefs
if (!lfs_gstate_needssuperblock(&lfs->gstate)) {
fsinfo->minor_version = LFS_DISK_VERSION_MINOR;

// otherwise we need to read the minor version on disk
} else {
// fetch the superblock
lfs_mdir_t dir;
int err = lfs_dir_fetch(lfs, &dir, lfs->root);
if (err) {
return err;
}

lfs_superblock_t superblock;
lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
&superblock);
if (tag < 0) {
return tag;
}
lfs_superblock_fromle32(&superblock);

// read the minor version
fsinfo->minor_version = (0xffff & (superblock.version >> 0));
}

// find the current block usage
lfs_ssize_t usage = lfs_fs_rawsize(lfs);
if (usage < 0) {
return usage;
}
fsinfo->block_usage = usage;

// other on-disk configuration, we cache all of these for internal use
fsinfo->name_max = lfs->name_max;
fsinfo->file_max = lfs->file_max;
fsinfo->attr_max = lfs->attr_max;

return 0;
}

int lfs_fs_rawtraverse(lfs_t *lfs,
int (*cb)(void *data, lfs_block_t block), void *data,
bool includeorphans) {
Expand Down Expand Up @@ -4934,6 +4975,7 @@ static lfs_ssize_t lfs_fs_rawsize(lfs_t *lfs) {
return size;
}


#ifdef LFS_MIGRATE
////// Migration from littelfs v1 below this //////

Expand Down Expand Up @@ -6053,6 +6095,20 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) {
return err;
}

int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) {
int err = LFS_LOCK(lfs->cfg);
if (err) {
return err;
}
LFS_TRACE("lfs_fs_stat(%p, %p)", (void*)lfs, (void*)fsinfo);

err = lfs_fs_rawstat(lfs, fsinfo);

LFS_TRACE("lfs_fs_stat -> %d", err);
LFS_UNLOCK(lfs->cfg);
return err;
}

lfs_ssize_t lfs_fs_size(lfs_t *lfs) {
int err = LFS_LOCK(lfs->cfg);
if (err) {
Expand Down
27 changes: 27 additions & 0 deletions lfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,27 @@ struct lfs_info {
char name[LFS_NAME_MAX+1];
};

// Filesystem info structure
struct lfs_fsinfo {
// On-disk minor version.
uint16_t minor_version;

// Number of blocks in use, this is the same as lfs_fs_size.
//
// Note: block_usage is best effort. If files share COW structures, the
// calculated block_usage may be larger than the actual contents on-disk.
lfs_size_t block_usage;

// Upper limit on the length of file names in bytes.
lfs_size_t name_max;

// Upper limit on the size of files in bytes.
lfs_size_t file_max;

// Upper limit on the size of custom attributes in bytes.
lfs_size_t attr_max;
};

// Custom attribute structure, used to describe custom attributes
// committed atomically during file writes.
struct lfs_attr {
Expand Down Expand Up @@ -659,6 +680,12 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);

/// Filesystem-level filesystem operations

// Find on-disk info about the filesystem
//
// Fills out the fsinfo structure based on the filesystem found on-disk.
// Returns a negative error code on failure.
int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo);

// Finds the current size of the filesystem
//
// Note: Result is best effort. If files share COW structures, the returned
Expand Down
122 changes: 104 additions & 18 deletions tests/test_compat.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@ code = '''
#define LFSP_VERSION LFS_VERSION
#define LFSP_VERSION_MAJOR LFS_VERSION_MAJOR
#define LFSP_VERSION_MINOR LFS_VERSION_MINOR
#define LFSP_DISK_VERSION LFS_DISK_VERSION
#define LFSP_DISK_VERSION_MAJOR LFS_DISK_VERSION_MAJOR
#define LFSP_DISK_VERSION_MINOR LFS_DISK_VERSION_MINOR
#define lfsp_t lfs_t
#define lfsp_config lfs_config
#define lfsp_format lfs_format
#define lfsp_mount lfs_mount
#define lfsp_unmount lfs_unmount
#define lfsp_fsinfo lfs_fsinfo
#define lfsp_fs_stat lfs_fs_stat
#define lfsp_dir_t lfs_dir_t
#define lfsp_info lfs_info
#define LFSP_TYPE_REG LFS_TYPE_REG
Expand Down Expand Up @@ -74,6 +79,12 @@ code = '''
// now test the new mount
lfs_t lfs;
lfs_mount(&lfs, cfg) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfs_fsinfo fsinfo;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR);

lfs_unmount(&lfs) => 0;
'''

Expand Down Expand Up @@ -102,6 +113,11 @@ code = '''
lfs_t lfs;
lfs_mount(&lfs, cfg) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfs_fsinfo fsinfo;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR);

// can we list the directories?
lfs_dir_t dir;
lfs_dir_open(&lfs, &dir, "/") => 0;
Expand Down Expand Up @@ -166,6 +182,11 @@ code = '''
lfs_t lfs;
lfs_mount(&lfs, cfg) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfs_fsinfo fsinfo;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR);

// can we list the files?
lfs_dir_t dir;
lfs_dir_open(&lfs, &dir, "/") => 0;
Expand Down Expand Up @@ -251,6 +272,11 @@ code = '''
lfs_t lfs;
lfs_mount(&lfs, cfg) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfs_fsinfo fsinfo;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR);

// can we list the directories?
lfs_dir_t dir;
lfs_dir_open(&lfs, &dir, "/") => 0;
Expand Down Expand Up @@ -343,6 +369,11 @@ code = '''
lfs_t lfs;
lfs_mount(&lfs, cfg) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfs_fsinfo fsinfo;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR);

// write another COUNT/2 dirs
for (lfs_size_t i = COUNT/2; i < COUNT; i++) {
char name[8];
Expand Down Expand Up @@ -420,6 +451,11 @@ code = '''
lfs_t lfs;
lfs_mount(&lfs, cfg) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfs_fsinfo fsinfo;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR);

// write half COUNT files
prng = 42;
for (lfs_size_t i = 0; i < COUNT; i++) {
Expand Down Expand Up @@ -537,6 +573,11 @@ code = '''
lfs_t lfs;
lfs_mount(&lfs, cfg) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfs_fsinfo fsinfo;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFSP_DISK_VERSION_MINOR);

// write half COUNT files
prng = 42;
for (lfs_size_t i = 0; i < COUNT; i++) {
Expand Down Expand Up @@ -651,6 +692,12 @@ code = '''
memcpy(&cfgp, cfg, sizeof(cfgp));
lfsp_t lfsp;
lfsp_mount(&lfsp, &cfgp) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfsp_fsinfo fsinfo;
lfsp_fs_stat(&lfsp, &fsinfo) => 0;
assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR);

lfsp_unmount(&lfsp) => 0;
'''

Expand Down Expand Up @@ -679,6 +726,11 @@ code = '''
lfsp_t lfsp;
lfsp_mount(&lfsp, &cfgp) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfs_fsinfo fsinfo;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR);

// can we list the directories?
lfsp_dir_t dir;
lfsp_dir_open(&lfsp, &dir, "/") => 0;
Expand Down Expand Up @@ -743,6 +795,11 @@ code = '''
lfsp_t lfsp;
lfsp_mount(&lfsp, &cfgp) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfsp_fsinfo fsinfo;
lfsp_fs_stat(&lfsp, &fsinfo) => 0;
assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR);

// can we list the files?
lfsp_dir_t dir;
lfsp_dir_open(&lfsp, &dir, "/") => 0;
Expand Down Expand Up @@ -828,6 +885,11 @@ code = '''
lfsp_t lfsp;
lfsp_mount(&lfsp, &cfgp) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfsp_fsinfo fsinfo;
lfsp_fs_stat(&lfsp, &fsinfo) => 0;
assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR);

// can we list the directories?
lfsp_dir_t dir;
lfsp_dir_open(&lfsp, &dir, "/") => 0;
Expand Down Expand Up @@ -920,6 +982,11 @@ code = '''
lfsp_t lfsp;
lfsp_mount(&lfsp, &cfgp) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfsp_fsinfo fsinfo;
lfsp_fs_stat(&lfsp, &fsinfo) => 0;
assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR);

// write another COUNT/2 dirs
for (lfs_size_t i = COUNT/2; i < COUNT; i++) {
char name[8];
Expand Down Expand Up @@ -997,6 +1064,11 @@ code = '''
lfsp_t lfsp;
lfsp_mount(&lfsp, &cfgp) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfsp_fsinfo fsinfo;
lfsp_fs_stat(&lfsp, &fsinfo) => 0;
assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR);

// write half COUNT files
prng = 42;
for (lfs_size_t i = 0; i < COUNT; i++) {
Expand Down Expand Up @@ -1114,6 +1186,11 @@ code = '''
lfsp_t lfsp;
lfsp_mount(&lfsp, &cfgp) => 0;

// we should be able to read the version using lfs_fs_stat
struct lfsp_fsinfo fsinfo;
lfsp_fs_stat(&lfsp, &fsinfo) => 0;
assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR);

// write half COUNT files
prng = 42;
for (lfs_size_t i = 0; i < COUNT; i++) {
Expand Down Expand Up @@ -1316,45 +1393,54 @@ code = '''

// mount should still work
lfs_mount(&lfs, cfg) => 0;

struct lfs_fsinfo fsinfo;
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR-1);

lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
uint8_t buffer[8];
lfs_file_read(&lfs, &file, buffer, 8) => 8;
assert(memcmp(buffer, "testtest", 8) == 0);
lfs_file_close(&lfs, &file) => 0;

// minor version should be unchanged
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR-1);

lfs_unmount(&lfs) => 0;

// if we write, we need to bump the minor version
lfs_mount(&lfs, cfg) => 0;

lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR-1);

lfs_file_open(&lfs, &file, "test", LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_write(&lfs, &file, "teeeeest", 8) => 8;
lfs_file_close(&lfs, &file) => 0;

// minor version should have changed
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
lfs_dir_get(&lfs, &mdir, LFS_MKTAG(0x7ff, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
&superblock)
=> LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock));
lfs_superblock_fromle32(&superblock);
assert((superblock.version >> 16) & 0xffff == LFS_DISK_VERSION_MAJOR);
assert((superblock.version >> 0) & 0xffff == LFS_DISK_VERSION_MINOR);
// minor version should be changed
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR);

lfs_unmount(&lfs) => 0;

// and of course mount should still work
lfs_mount(&lfs, cfg) => 0;

// minor version should have changed
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR);

lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, 8) => 8;
assert(memcmp(buffer, "teeeeest", 8) == 0);
lfs_file_close(&lfs, &file) => 0;

// minor version should have changed
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
lfs_dir_get(&lfs, &mdir, LFS_MKTAG(0x7ff, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
&superblock)
=> LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock));
lfs_superblock_fromle32(&superblock);
assert((superblock.version >> 16) & 0xffff == LFS_DISK_VERSION_MAJOR);
assert((superblock.version >> 0) & 0xffff == LFS_DISK_VERSION_MINOR);
// yep, still changed
lfs_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.minor_version == LFS_DISK_VERSION_MINOR);

lfs_unmount(&lfs) => 0;
'''

0 comments on commit 87bbf1d

Please sign in to comment.