attrs: (Re)implemented lfsr_setattr/getattr/etc
These functions provide simple access to littlefs's custom attributes,
which are small pieces of user-specified metadata that can be attached
to files, dirs, root, etc:
- lfsr_getattr - Reads an attribute
- lfsr_sizeattr - Gets the size of an attribute
- lfsr_setattr - Writes an attribute
- lfsr_removeattr - Removes an attribute
You may notice these functions look quite a bit different from their
previous incarnations. This is because the custom attribute API is
getting an overhaul based on feedback provided by users
The previous API had some real design flaws that interfered with
usability, but now that things have had some time to settle (6 years!),
hopefully most of the pain points are clear.
Notable changes:
- lfsr_getattr's return value is now limited by buffer size.
The intention of the previous API, where lfsr_getattr always returns
the attr size, even if it's larger than the buffer, was to allow users
to find the attr size without an infinitely large buffer.
In defense of this design, Linux's getxattr does something somewhat
similar, returning the attr size when the buffer size equals zero.
Though getxattr does truncate when buffer size is non-zero, which is
probably safer.
But, let's be honest, this multipurpose abuse of lfsr_getattr's return
value is inconsistent with other read functions and potentially
dangerous for users.
I think one of the reasons for this API in Linux-land is the limited
syscall numbers discouraging new functions, but we have no such
limitation here! We might as well add a dedicated function for
this: lfsr_sizeattr.
- No more padding with zeros!
This was a cludge to get around the lack of returned size in custom
attributes attached to files, but is inconsistent with other read
functions, so needs to go.
In general, inconsistencies violate user assumptions, and are usually
a sign of a bad API.
- lfsr_setattr now takes flags.
This gives lfsr_setattr more flexiblity in how it operates, and may
make future extensions easier.
lfsr_setattr currently supports two flags, which may look a bit
familiar:
LFS_A_CREAT 0x04 // Create an attr if it does not exist
LFS_A_EXCL 0x08 // Fail if an attr already exists
One long-term idea is to eventually add a simple lfsr_set function to
make it easier to create small files, so this sort of design overlap
between lfsr_setattr and lfsr_file_open is hopefully a good thing.
---
Code-wise, these function are really not that bad. Adding functions adds
code, but these are just small wrappers over our internal lookup/commit
functions:
code stack
before: 36556 2608
after: 37116 (+1.5%) 2608 (+0.0%)
Of course the real cost of custom attributes is how they interact with
open files, a detail which is conveniently missing for now...
This commit is contained in:
@@ -9417,7 +9417,7 @@ static int lfsr_mtree_pathlookup(lfs_t *lfs, const char *path,
|
||||
lfsr_mdir_t *mdir_, lfsr_tag_t *tag_,
|
||||
lfsr_did_t *did_, const char **name_, lfs_size_t *name_size_) {
|
||||
// setup root
|
||||
lfsr_mdir_t mdir = {.mid = -1};
|
||||
lfsr_mdir_t mdir = lfs->mroot;
|
||||
lfsr_tag_t tag = LFSR_TAG_DIR;
|
||||
lfsr_did_t did = LFSR_DID_ROOT;
|
||||
|
||||
@@ -11053,6 +11053,132 @@ int lfsr_dir_rewind(lfs_t *lfs, lfsr_dir_t *dir) {
|
||||
|
||||
|
||||
|
||||
/// Custom attribute stuff ///
|
||||
|
||||
static int lfsr_lookupattr(lfs_t *lfs, const char *path, uint8_t type,
|
||||
lfsr_mdir_t *mdir_, lfsr_data_t *data_) {
|
||||
// lookup our entry
|
||||
lfsr_tag_t tag;
|
||||
int err = lfsr_mtree_pathlookup(lfs, path,
|
||||
mdir_, &tag, NULL, NULL, NULL);
|
||||
if (err && err != LFS_ERR_EXIST
|
||||
&& err != LFS_ERR_INVAL) {
|
||||
return err;
|
||||
}
|
||||
// doesn't exist? note orphans don't really exist
|
||||
if (!err || tag == LFSR_TAG_ORPHAN) {
|
||||
return LFS_ERR_NOENT;
|
||||
}
|
||||
|
||||
// lookup our attr
|
||||
err = lfsr_mdir_lookup(lfs, mdir_, LFSR_TAG_ATTR(type),
|
||||
data_);
|
||||
if (err) {
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
return LFS_ERR_NOATTR;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
lfs_ssize_t lfsr_getattr(lfs_t *lfs, const char *path, uint8_t type,
|
||||
void *buffer, lfs_size_t size) {
|
||||
// lookup our attr
|
||||
lfsr_mdir_t mdir;
|
||||
lfsr_data_t data;
|
||||
int err = lfsr_lookupattr(lfs, path, type,
|
||||
&mdir, &data);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// read the attr
|
||||
return lfsr_data_read(lfs, &data, buffer, size);
|
||||
}
|
||||
|
||||
lfs_ssize_t lfsr_sizeattr(lfs_t *lfs, const char *path, uint8_t type) {
|
||||
// lookup our attr
|
||||
lfsr_mdir_t mdir;
|
||||
lfsr_data_t data;
|
||||
int err = lfsr_lookupattr(lfs, path, type,
|
||||
&mdir, &data);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// return the attr size
|
||||
return lfsr_data_size(data);
|
||||
}
|
||||
|
||||
int lfsr_setattr(lfs_t *lfs, const char *path, uint8_t type,
|
||||
const void *buffer, lfs_size_t size,
|
||||
uint32_t flags) {
|
||||
// unknown flags?
|
||||
LFS_ASSERT((flags & ~(
|
||||
LFS_A_CREAT
|
||||
| LFS_O_EXCL)) == 0);
|
||||
|
||||
// prepare our filesystem for writing
|
||||
int err = lfsr_fs_mkconsistent(lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// lookup our attr
|
||||
lfsr_mdir_t mdir;
|
||||
lfsr_data_t data;
|
||||
err = lfsr_lookupattr(lfs, path, type,
|
||||
&mdir, &data);
|
||||
if (err && err != LFS_ERR_NOATTR) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// doesn't exist?
|
||||
if (!lfsr_o_iscreat(flags)
|
||||
&& err == LFS_ERR_NOATTR) {
|
||||
return LFS_ERR_NOATTR;
|
||||
|
||||
// does exist?
|
||||
} else if (lfsr_o_iscreat(flags)
|
||||
&& lfsr_o_isexcl(flags)
|
||||
&& err != LFS_ERR_NOATTR) {
|
||||
return LFS_ERR_EXIST;
|
||||
}
|
||||
|
||||
// commit our attr
|
||||
lfs_alloc_ckpoint(lfs);
|
||||
return lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS(
|
||||
LFSR_ATTR(
|
||||
LFSR_TAG_ATTR(type), 0,
|
||||
LFSR_DATA_BUF(buffer, size))));
|
||||
}
|
||||
|
||||
int lfsr_removeattr(lfs_t *lfs, const char *path, uint8_t type) {
|
||||
// prepare our filesystem for writing
|
||||
int err = lfsr_fs_mkconsistent(lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// lookup our attr
|
||||
lfsr_mdir_t mdir;
|
||||
err = lfsr_lookupattr(lfs, path, type,
|
||||
&mdir, NULL);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// commit our removal
|
||||
lfs_alloc_ckpoint(lfs);
|
||||
return lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS(
|
||||
LFSR_ATTR(
|
||||
LFSR_TAG_RM | LFSR_TAG_ATTR(type), 0,
|
||||
LFSR_DATA_NULL())));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// File operations ///
|
||||
|
||||
@@ -151,6 +151,10 @@ enum lfs_type {
|
||||
#define LFS_SEEK_CUR 1 // Seek relative to the current file position
|
||||
#define LFS_SEEK_END 2 // Seek relative to the end of the file
|
||||
|
||||
// Custom attribute flags
|
||||
#define LFS_A_CREAT 0x04 // Create an attr if it does not exist
|
||||
#define LFS_A_EXCL 0x08 // Fail if an attr already exists
|
||||
|
||||
// Filesystem format flags
|
||||
#define LFS_F_RDWR 0 // Format the filesystem as read and write
|
||||
#ifdef LFS_CKPROGS
|
||||
@@ -893,38 +897,39 @@ int lfsr_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
|
||||
|
||||
// Get a custom attribute
|
||||
//
|
||||
// Custom attributes are uniquely identified by an 8-bit type and limited
|
||||
// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than
|
||||
// the buffer, it will be padded with zeros. If the stored attribute is larger,
|
||||
// then it will be silently truncated. If no attribute is found, the error
|
||||
// LFS_ERR_NOATTR is returned and the buffer is filled with zeros.
|
||||
//
|
||||
// Returns the size of the attribute, or a negative error code on failure.
|
||||
// Note, the returned size is the size of the attribute on disk, irrespective
|
||||
// of the size of the buffer. This can be used to dynamically allocate a buffer
|
||||
// or check for existence.
|
||||
// Returns the number of bytes read, or a negative error code on failure.
|
||||
// Note this may be less than the on-disk attr size if the buffer is not
|
||||
// large enough.
|
||||
//lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path,
|
||||
// uint8_t type, void *buffer, lfs_size_t size);
|
||||
lfs_ssize_t lfsr_getattr(lfs_t *lfs, const char *path, uint8_t type,
|
||||
void *buffer, lfs_size_t size);
|
||||
|
||||
// Get a custom attribute's size
|
||||
//
|
||||
// Returns the size of the attribute, or a negative error code on failure.
|
||||
lfs_ssize_t lfsr_sizeattr(lfs_t *lfs, const char *path, uint8_t type);
|
||||
|
||||
#ifndef LFS_READONLY
|
||||
// Set custom attributes
|
||||
//
|
||||
// Custom attributes are uniquely identified by an 8-bit type and limited
|
||||
// to LFS_ATTR_MAX bytes. If an attribute is not found, it will be
|
||||
// implicitly created.
|
||||
// The flags field controls the exact behavior if the attribute is or
|
||||
// isn't found.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
//int lfs_setattr(lfs_t *lfs, const char *path,
|
||||
// uint8_t type, const void *buffer, lfs_size_t size);
|
||||
int lfsr_setattr(lfs_t *lfs, const char *path, uint8_t type,
|
||||
const void *buffer, lfs_size_t size,
|
||||
uint32_t flags);
|
||||
#endif
|
||||
|
||||
#ifndef LFS_READONLY
|
||||
// Removes a custom attribute
|
||||
//
|
||||
// If an attribute is not found, nothing happens.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
//int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type);
|
||||
int lfsr_removeattr(lfs_t *lfs, const char *path, uint8_t type);
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
+10
-1
@@ -1047,6 +1047,15 @@ class Config:
|
||||
|
||||
self.config[tag] = (j+d, data)
|
||||
|
||||
# also read any custom attributes in the mroot
|
||||
tag = TAG_ATTR
|
||||
while True:
|
||||
done, rid, tag, w, j, d, data, _ = mroot.lookup(-1, tag+0x1)
|
||||
if done or rid != -1 or (tag & 0xfe00) != TAG_ATTR:
|
||||
break
|
||||
|
||||
self.config[tag] = (j+d, data)
|
||||
|
||||
# accessors for known config
|
||||
@ft.cached_property
|
||||
def magic(self):
|
||||
@@ -1144,7 +1153,7 @@ class Config:
|
||||
elif tag == TAG_FILELIMIT:
|
||||
return 'filelimit %d' % self.file_limit
|
||||
else:
|
||||
return 'config 0x%02x %d' % (tag, len(data))
|
||||
return tagrepr(tag, size=len(data))
|
||||
|
||||
for tag, (j, data) in sorted(self.config.items()):
|
||||
yield crepr(tag, data), tag, j, data
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+48
-1
@@ -201,7 +201,54 @@ code = '''
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# check for LFS_F_EXCL errors
|
||||
# check for ENOENT errors
|
||||
[cases.test_files_noent]
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
|
||||
// try to open a file that doesn't exist, this should error
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
|
||||
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => LFS_ERR_NOENT;
|
||||
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDWR) => LFS_ERR_NOENT;
|
||||
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
}
|
||||
|
||||
// make sure open didn't quietly create a file
|
||||
|
||||
// check our file with stat
|
||||
struct lfs_info info;
|
||||
lfsr_stat(&lfs, "hello", &info) => LFS_ERR_NOENT;
|
||||
|
||||
// and with dir read
|
||||
lfsr_dir_t dir;
|
||||
lfsr_dir_open(&lfs, &dir, "/") => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
assert(info.size == 0);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// try reading our file
|
||||
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => LFS_ERR_NOENT;
|
||||
}
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# check for EEXIST errors
|
||||
[cases.test_files_excl]
|
||||
defines.REMOUNT = [false, true]
|
||||
code = '''
|
||||
|
||||
Reference in New Issue
Block a user