attrs: (Re)implemented file-attached custom attributes
Unlike lfsr_setattr/getattr/etc, file-attached custom attributes are
RAM-backed snapshots attached to, well, files, that can be committed
atomically along with the file's contents. Great for power-loss
resilience, but boy does it make a mess of an API.
This API was really where custom attributes needed some TLC.
The biggest change is how file-attached custom attributes interact with
file sync broadcasting.
A common complaint from users is that setting custom attributes did not
update attributes in open file handles. This behavior is _very_
inconsistent with other filesystems and created a lot of confusion.
Since we're nailing down littlefs's snapshot/broadcasting model as a
part of larger changes, it makes sense to also nail down how custom
attributes interact.
In the new model:
- Custom attributes are still in-RAM snapshots. Updates do not
immediately take effect, even across write calls.
- On lfsr_file_sync or lfsr_file_close, custom attributes are written
atomically to disk and broadcasted to all open file handles.
- lfsr_setattr/removeattr also take part in attribute broadcasting. When
called, lfsr_setattr/removeattr updates the attribute on disk and
broadcasts the attribute changes to all open file handles.
- Desynced files do _not_ recieve any attribute broadcasts in the same
way they do not recieve any data broadcasts.
This should hopefully make littlefs behave much more consistently with
other filesystems, while still maintaining a well-defined snapshot and
power-loss properties.
---
The lfs_attr struct also gained several new fields:
// Custom attribute structure, used to describe custom attributes
// committed atomically during file writes.
struct lfs_attr {
// Type of attribute
//
// Note some of this range is reserved:
// 0x00-0x7f - Free for custom attributes
// 0x80-0xff - May be assigned a standard attribute
uint8_t type;
// Flags that control how attr is read/written/removed
uint8_t flags;
// Pointer the buffer where the attr will be read/written
void *buffer;
// Size of the attr buffer in bytes, this can be set to
// LFS_ERR_NOATTR to remove the attr
lfs_ssize_t buffer_size;
// Optional pointer to a mutable attr size, updated on read/write,
// set to LFS_ERR_NOATTR if attr does not exist
//
// Defaults to buffer_size if NULL
lfs_ssize_t *size;
};
Which are useful for several new features:
- lfs_attr now supports LFS_A_RDONLY/WRONLY/RDWR modes.
One of the blockers for attribute broadcasting was in-ROM attributes,
where broadcast updates would hard-fault. But now if you mark in-ROM
attributes as WRONLY, and in-RAM attributes as RDWR, this problem goes
away.
- When opened, lfs_attr now optionally writes the attribute size to the
indirect size field.
No more hacky zero padding and not knowing an attribute's size.
Note this follows the same rules as lfsr_getattr, so it does truncate
if the buffer is too small.
The size field can also be set to NULL, in which case lfs_attr
defaults to the buffer_size. This can be quite useful for pure
ROM-backed attributes.
- Missing attributes are now represented with size=LFS_ERR_NOATTR.
No more zero-sized vs missing attribute ambiguity.
This also makes it possible to remove attributes via lfs_attr, by
setting the size to LFS_ERR_NOATTR manually.
This does lead to a bit of a quirk where buffer_size can be
LFS_ERR_NOATTR, which is a bit weird but at least consistent.
- Changes to lfs_attrs will now always trigger file syncs by default.
Previously, if you changed an attribute, you had to also change the
file's contents for it to get written to disk. As pointed out by users
this is both surprising and difficult to work around.
Solving this is quite tricky since there's no real signalling
mechanism between attribute buffers and littlefs. The best I could
come up with is to read attributes from disk during lfsr_file_sync to
see if anything changed.
At the very least, the new flag LFS_A_LAZY restores the old behavior
in case the extra reads in lfsr_file_sync are problematic.
Though I suspect _most_ calls to lfsr_file_sync immediately follow
intentional changes to a file. It would be interesting to know of
examples where this is not the case...
These new fields do increase the size of lfs_attr, which is a downside,
but thanks to flags fitting in type's padding, this is only an increase
from 3 words (12 bytes) -> 4 words (16 bytes).
---
Other implementation notes:
- I did try to implement LFS_A_CREAT/EXCL in lfs_attr but this proved
to be too messy and inconsistent, so I dropped the idea for now.
The idea was to error with NOATTR/EXIST if the lfs_attr flag in
incompatible with what's on disk, but this led to a lot of complexity
for what is a pretty niche use case.
It's also inconsistent with rdonly attrs, which do _not_ error with
NOATTR during lfsr_file_opencfg, because that would be kind of
annoying.
- Having both `struct lfs_attr` and `lfsr_attr_t` to represent different
things in the codebase is both fragile and confusing. One of these
needs to change, probably `lfsr_attr_t`.
If only I could think of a good name...
One of the nice side-effects of the now-dropped uattr/sattr split was
avoiding this conflict.
- We still need more tests related to how custom attributes interact
with other filesystem operations, but I wanted to get what is
currently working committed, see the TODOs in test_attrs.toml.
All of the new bells and whistles unfortunately do add up.
lfsr_file_sync is also the root of our current stack hot-path, so the
additional attr also adds a bit of stack:
code stack
before: 37116 2608
after: 38104 (+2.7%) 2624 (+0.6%)
Still, having a consistent and flexible API is well worth it.
Though I do think at some point we should add a compile-time option to
opt-out of custom attributes (LFS_NO_ATTR?).
This commit is contained in:
@@ -1419,6 +1419,7 @@ enum lfsr_tag {
|
||||
LFSR_TAG_SHRUBCOMMIT = 0x0801,
|
||||
LFSR_TAG_SHRUBTRUNK = 0x0802,
|
||||
LFSR_TAG_MOVE = 0x0803,
|
||||
LFSR_TAG_UATTRS = 0x0804,
|
||||
|
||||
// some in-device only tag modifiers
|
||||
LFSR_TAG_RM = 0x8000,
|
||||
@@ -2394,6 +2395,11 @@ typedef struct lfsr_data_name {
|
||||
#define LFSR_ATTR_ATTRS(_tag, _weight, _attrs, _attr_count) \
|
||||
LFSR_ATTR_(_tag, _weight, (const lfsr_attr_t*){_attrs}, _attr_count)
|
||||
|
||||
// chain a list of user attributes, these require a bit of last-minute
|
||||
// reencoding during commits
|
||||
#define LFSR_ATTR_UATTRS(_tag, _weight, _attrs, _attr_count) \
|
||||
LFSR_ATTR_(_tag, _weight, (const struct lfs_attr*){_attrs}, _attr_count)
|
||||
|
||||
// a move of all attrs from an mdir entry
|
||||
#define LFSR_ATTR_MOVE(_tag, _weight, _mdir) \
|
||||
LFSR_ATTR_(_tag, _weight, (const lfsr_mdir_t*){_mdir}, 0)
|
||||
@@ -2419,6 +2425,41 @@ typedef struct lfsr_shrubcommit lfsr_shrubcommit_t;
|
||||
LFSR_ATTR_(_tag, _weight, (const lfsr_shrub_t*){_shrub}, 0)
|
||||
|
||||
|
||||
// operations on custom attribute lists
|
||||
//
|
||||
// a slightly different struct because it's user facing
|
||||
|
||||
static inline lfs_ssize_t lfsr_uattr_size(const struct lfs_attr *attr) {
|
||||
// we default to the buffer_size if a mutable size is not provided
|
||||
if (attr->size) {
|
||||
return *attr->size;
|
||||
} else {
|
||||
return attr->buffer_size;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool lfsr_uattr_isnoattr(const struct lfs_attr *attr) {
|
||||
return lfsr_uattr_size(attr) == LFS_ERR_NOATTR;
|
||||
}
|
||||
|
||||
static lfs_scmp_t lfsr_uattr_cmp(lfs_t *lfs, const struct lfs_attr *attr,
|
||||
const lfsr_data_t *data) {
|
||||
// note data=NULL => NOATTR
|
||||
if (!data) {
|
||||
return (lfsr_uattr_isnoattr(attr)) ? LFS_CMP_EQ : LFS_CMP_GT;
|
||||
} else {
|
||||
if (lfsr_uattr_isnoattr(attr)) {
|
||||
return LFS_CMP_LT;
|
||||
} else {
|
||||
return lfsr_data_cmp(lfs, *data,
|
||||
attr->buffer,
|
||||
lfsr_uattr_size(attr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//struct lfsr_attr_from {
|
||||
// const lfsr_rbyd_t *rbyd;
|
||||
// const struct lfsr_attr *attrs;
|
||||
@@ -7117,6 +7158,11 @@ static inline bool lfsr_o_iszombie(uint32_t flags) {
|
||||
return flags & LFS_O_ZOMBIE;
|
||||
}
|
||||
|
||||
// custom attr flags
|
||||
static inline bool lfsr_a_islazy(uint32_t flags) {
|
||||
return flags & LFS_A_LAZY;
|
||||
}
|
||||
|
||||
// traversal flags
|
||||
static inline bool lfsr_t_ismtreeonly(uint32_t flags) {
|
||||
return flags & LFS_T_MTREEONLY;
|
||||
@@ -8135,6 +8181,57 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
}
|
||||
}
|
||||
|
||||
// custom attributes need to be reencoded into our tag format
|
||||
} else if (lfsr_tag_key(attrs[i].tag) == LFSR_TAG_UATTRS) {
|
||||
const struct lfs_attr *attrs_ = attrs[i].cat;
|
||||
lfs_size_t attr_count_ = attrs[i].count;
|
||||
|
||||
for (lfs_size_t j = 0; j < attr_count_; j++) {
|
||||
// skip readonly attrs and lazy attrs
|
||||
if (lfsr_o_isrdonly(attrs_[j].flags)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// first lets check if the attr changed, we don't want
|
||||
// to append attrs unless we have to
|
||||
lfsr_data_t data;
|
||||
int err = lfsr_mdir_lookup(lfs, mdir,
|
||||
LFSR_TAG_ATTR(attrs_[j].type),
|
||||
&data);
|
||||
if (err && err != LFS_ERR_NOENT) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// does disk match our attr?
|
||||
lfs_scmp_t cmp = lfsr_uattr_cmp(lfs, &attrs_[j],
|
||||
(err != LFS_ERR_NOENT) ? &data : NULL);
|
||||
if (cmp < 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
if (cmp == LFS_CMP_EQ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// append the custom attr
|
||||
err = lfsr_rbyd_appendattr(lfs, &mdir->rbyd,
|
||||
rid - lfs_smax(start_rid, 0),
|
||||
// removing or updating?
|
||||
(lfsr_uattr_isnoattr(&attrs_[j]))
|
||||
? LFSR_ATTR(
|
||||
LFSR_TAG_RM
|
||||
| LFSR_TAG_ATTR(attrs_[j].type), 0,
|
||||
LFSR_DATA_NULL())
|
||||
: LFSR_ATTR(
|
||||
LFSR_TAG_ATTR(attrs_[j].type), 0,
|
||||
LFSR_DATA_BUF(
|
||||
attrs_[j].buffer,
|
||||
lfsr_uattr_size(&attrs_[j]))));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// write out normal tags normally
|
||||
} else {
|
||||
LFS_ASSERT(!lfsr_tag_isinternal(attrs[i].tag));
|
||||
@@ -11149,10 +11246,39 @@ int lfsr_setattr(lfs_t *lfs, const char *path, uint8_t type,
|
||||
|
||||
// commit our attr
|
||||
lfs_alloc_ckpoint(lfs);
|
||||
return lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS(
|
||||
err = lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS(
|
||||
LFSR_ATTR(
|
||||
LFSR_TAG_ATTR(type), 0,
|
||||
LFSR_DATA_BUF(buffer, size))));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// update any opened files tracking custom attrs
|
||||
for (lfsr_omdir_t *o = lfs->omdirs; o; o = o->next) {
|
||||
if (!(lfsr_o_type(o->flags) == LFS_TYPE_REG
|
||||
&& o->mdir.mid == mdir.mid
|
||||
&& !lfsr_o_isdesync(o->flags))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lfsr_file_t *file = (lfsr_file_t*)o;
|
||||
for (lfs_size_t i = 0; i < file->cfg->attr_count; i++) {
|
||||
if (!(file->cfg->attrs[i].type == type
|
||||
&& !lfsr_o_iswronly(file->cfg->attrs[i].flags))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lfs_size_t d = lfs_min(size, file->cfg->attrs[i].buffer_size);
|
||||
memcpy(file->cfg->attrs[i].buffer, buffer, d);
|
||||
if (file->cfg->attrs[i].size) {
|
||||
*file->cfg->attrs[i].size = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfsr_removeattr(lfs_t *lfs, const char *path, uint8_t type) {
|
||||
@@ -11172,10 +11298,36 @@ int lfsr_removeattr(lfs_t *lfs, const char *path, uint8_t type) {
|
||||
|
||||
// commit our removal
|
||||
lfs_alloc_ckpoint(lfs);
|
||||
return lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS(
|
||||
err = lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS(
|
||||
LFSR_ATTR(
|
||||
LFSR_TAG_RM | LFSR_TAG_ATTR(type), 0,
|
||||
LFSR_DATA_NULL())));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// update any opened files tracking custom attrs
|
||||
for (lfsr_omdir_t *o = lfs->omdirs; o; o = o->next) {
|
||||
if (!(lfsr_o_type(o->flags) == LFS_TYPE_REG
|
||||
&& o->mdir.mid == mdir.mid
|
||||
&& !lfsr_o_isdesync(o->flags))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lfsr_file_t *file = (lfsr_file_t*)o;
|
||||
for (lfs_size_t i = 0; i < file->cfg->attr_count; i++) {
|
||||
if (!(file->cfg->attrs[i].type == type
|
||||
&& !lfsr_o_iswronly(file->cfg->attrs[i].flags))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file->cfg->attrs[i].size) {
|
||||
*file->cfg->attrs[i].size = LFS_ERR_NOATTR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -11292,6 +11444,43 @@ static int lfsr_file_fetch(lfs_t *lfs, lfsr_file_t *file) {
|
||||
file->buffer.size = size;
|
||||
}
|
||||
|
||||
// try to fetch any custom attributes
|
||||
for (lfs_size_t i = 0; i < file->cfg->attr_count; i++) {
|
||||
// skip writeonly attrs
|
||||
if (lfsr_o_iswronly(file->cfg->attrs[i].flags)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// lookup the attr
|
||||
lfsr_data_t data;
|
||||
int err = lfsr_mdir_lookup(lfs, &file->o.o.mdir,
|
||||
LFSR_TAG_ATTR(file->cfg->attrs[i].type),
|
||||
&data);
|
||||
if (err && err != LFS_ERR_NOENT) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// read the attr, if it exists
|
||||
if (err == LFS_ERR_NOENT
|
||||
// awkward case here if buffer_size is LFS_ERR_NOATTR
|
||||
|| file->cfg->attrs[i].buffer_size == LFS_ERR_NOATTR) {
|
||||
if (file->cfg->attrs[i].size) {
|
||||
*file->cfg->attrs[i].size = LFS_ERR_NOATTR;
|
||||
}
|
||||
} else {
|
||||
lfs_ssize_t d = lfsr_data_read(lfs, &data,
|
||||
file->cfg->attrs[i].buffer,
|
||||
file->cfg->attrs[i].buffer_size);
|
||||
if (d < 0) {
|
||||
return d;
|
||||
}
|
||||
|
||||
if (file->cfg->attrs[i].size) {
|
||||
*file->cfg->attrs[i].size = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -11324,6 +11513,13 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
|
||||
LFS_ASSERT(!lfsr_o_isrdonly(flags) || !lfsr_o_iscreat(flags));
|
||||
LFS_ASSERT(!lfsr_o_isrdonly(flags) || !lfsr_o_isexcl(flags));
|
||||
LFS_ASSERT(!lfsr_o_isrdonly(flags) || !lfsr_o_istrunc(flags));
|
||||
for (lfs_size_t i = 0; i < cfg->attr_count; i++) {
|
||||
// these flags require a writable attr
|
||||
LFS_ASSERT(!lfsr_o_isrdonly(cfg->attrs[i].flags)
|
||||
|| !lfsr_o_iscreat(cfg->attrs[i].flags));
|
||||
LFS_ASSERT(!lfsr_o_isrdonly(cfg->attrs[i].flags)
|
||||
|| !lfsr_o_isexcl(cfg->attrs[i].flags));
|
||||
}
|
||||
|
||||
if (!lfsr_o_isrdonly(flags)) {
|
||||
// prepare our filesystem for writing
|
||||
@@ -12810,30 +13006,34 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
LFS_ASSERT(!lfsr_o_isorphan(file->o.o.flags)
|
||||
|| lfsr_o_isunsync(file->o.o.flags));
|
||||
|
||||
// don't write to disk if already in-sync
|
||||
if (lfsr_o_isunsync(file->o.o.flags)) {
|
||||
// commit any changes to our file's metadata
|
||||
lfsr_attr_t attrs[2];
|
||||
lfs_size_t attr_count = 0;
|
||||
lfsr_data_t name_data;
|
||||
uint8_t buf[LFSR_BTREE_DSIZE];
|
||||
// build a commit of any pending file metadata
|
||||
lfsr_attr_t attrs[3];
|
||||
lfs_size_t attr_count = 0;
|
||||
lfsr_data_t name_data;
|
||||
uint8_t buf[LFSR_BTREE_DSIZE];
|
||||
|
||||
// not created yet? need to convert orphan to normal file
|
||||
if (lfsr_o_isorphan(file->o.o.flags)) {
|
||||
err = lfsr_mdir_lookup(lfs, &file->o.o.mdir, LFSR_TAG_ORPHAN,
|
||||
&name_data);
|
||||
if (err) {
|
||||
// we must have an orphan at this point
|
||||
LFS_ASSERT(err != LFS_ERR_NOENT);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
attrs[attr_count++] = LFSR_ATTR_CAT_(
|
||||
LFSR_TAG_SUB | LFSR_TAG_REG, 0,
|
||||
&name_data, 1);
|
||||
// not created yet? need to convert orphan to normal file
|
||||
if (lfsr_o_isorphan(file->o.o.flags)) {
|
||||
err = lfsr_mdir_lookup(lfs, &file->o.o.mdir, LFSR_TAG_ORPHAN,
|
||||
&name_data);
|
||||
if (err) {
|
||||
// orphan flag but no orphan tag?
|
||||
LFS_ASSERT(err != LFS_ERR_NOENT);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// commit the file state
|
||||
attrs[attr_count++] = LFSR_ATTR_CAT_(
|
||||
LFSR_TAG_SUB | LFSR_TAG_REG, 0,
|
||||
&name_data, 1);
|
||||
}
|
||||
|
||||
// pending file changes?
|
||||
if (lfsr_o_isunsync(file->o.o.flags)) {
|
||||
// make sure data is on-disk before committing metadata
|
||||
err = lfsr_bd_sync(lfs);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// null? no attr?
|
||||
if (lfsr_o_isunflush(file->o.o.flags) && file->buffer.size == 0) {
|
||||
@@ -12858,13 +13058,54 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
} else {
|
||||
LFS_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
// make sure data is on-disk before committing metadata
|
||||
err = lfsr_bd_sync(lfs);
|
||||
if (err) {
|
||||
goto failed;
|
||||
// pending custom attributes?
|
||||
//
|
||||
// this gets real messy, since users can change custom attributes
|
||||
// whenever they want without informing littlefs, the best we can do
|
||||
// is read from disk to manually check if any attributes changed
|
||||
bool uattrs = lfsr_o_isunsync(file->o.o.flags);
|
||||
if (!uattrs) {
|
||||
for (lfs_size_t i = 0; i < file->cfg->attr_count; i++) {
|
||||
// skip readonly attrs and lazy attrs
|
||||
if (lfsr_o_isrdonly(file->cfg->attrs[i].flags)
|
||||
|| lfsr_a_islazy(file->cfg->attrs[i].flags)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// lookup the attr
|
||||
lfsr_data_t data;
|
||||
err = lfsr_mdir_lookup(lfs, &file->o.o.mdir,
|
||||
LFSR_TAG_ATTR(file->cfg->attrs[i].type),
|
||||
&data);
|
||||
if (err && err != LFS_ERR_NOENT) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// does disk match our attr?
|
||||
lfs_scmp_t cmp = lfsr_uattr_cmp(lfs, &file->cfg->attrs[i],
|
||||
(err != LFS_ERR_NOENT) ? &data : NULL);
|
||||
if (cmp < 0) {
|
||||
err = cmp;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (cmp != LFS_CMP_EQ) {
|
||||
uattrs = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (uattrs) {
|
||||
// need to append custom attributes
|
||||
attrs[attr_count++] = LFSR_ATTR_UATTRS(
|
||||
LFSR_TAG_UATTRS, 0,
|
||||
file->cfg->attrs, file->cfg->attr_count);
|
||||
}
|
||||
|
||||
// pending metadata? looks like we need to write to disk
|
||||
if (attr_count > 0) {
|
||||
// checkpoint the allocator
|
||||
lfs_alloc_ckpoint(lfs);
|
||||
|
||||
@@ -12907,6 +13148,38 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
file->buffer.buffer,
|
||||
file->buffer.size);
|
||||
file_->buffer.size = file->buffer.size;
|
||||
|
||||
// update any custom attrs
|
||||
for (lfs_size_t i = 0; i < file->cfg->attr_count; i++) {
|
||||
if (lfsr_o_isrdonly(file->cfg->attrs[i].flags)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (lfs_size_t j = 0; j < file_->cfg->attr_count; j++) {
|
||||
if (!(file_->cfg->attrs[j].type
|
||||
== file->cfg->attrs[i].type
|
||||
&& !lfsr_o_iswronly(
|
||||
file_->cfg->attrs[j].flags))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lfsr_uattr_isnoattr(&file->cfg->attrs[i])) {
|
||||
if (file_->cfg->attrs[j].size) {
|
||||
*file_->cfg->attrs[j].size = LFS_ERR_NOATTR;
|
||||
}
|
||||
} else {
|
||||
lfs_size_t d = lfs_min(
|
||||
lfsr_uattr_size(&file->cfg->attrs[i]),
|
||||
file_->cfg->attrs[j].buffer_size);
|
||||
memcpy(file_->cfg->attrs[j].buffer,
|
||||
file->cfg->attrs[i].buffer,
|
||||
d);
|
||||
if (file_->cfg->attrs[j].size) {
|
||||
*file_->cfg->attrs[j].size = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clobber entangled traversals
|
||||
|
||||
@@ -152,8 +152,12 @@ enum lfs_type {
|
||||
#define LFS_SEEK_END 2 // Seek relative to the end of the file
|
||||
|
||||
// Custom attribute flags
|
||||
#define LFS_A_RDONLY 0 // Open an attr as read only
|
||||
#define LFS_A_WRONLY 1 // Open an attr as write only
|
||||
#define LFS_A_RDWR 2 // Open an attr as read and write
|
||||
#define LFS_A_CREAT 0x04 // Create an attr if it does not exist
|
||||
#define LFS_A_EXCL 0x08 // Fail if an attr already exists
|
||||
#define LFS_A_LAZY 0x10 // Only write attr if file changed
|
||||
|
||||
// Filesystem format flags
|
||||
#define LFS_F_RDWR 0 // Format the filesystem as read and write
|
||||
@@ -481,6 +485,33 @@ struct lfs_tinfo {
|
||||
// lfs_size_t size;
|
||||
//};
|
||||
|
||||
// Custom attribute structure, used to describe custom attributes
|
||||
// committed atomically during file writes.
|
||||
struct lfs_attr {
|
||||
// Type of attribute
|
||||
//
|
||||
// Note some of this range is reserved:
|
||||
// 0x00-0x7f - Free for custom attributes
|
||||
// 0x80-0xff - May be assigned a standard attribute
|
||||
uint8_t type;
|
||||
|
||||
// Flags that control how attr is read/written/removed
|
||||
uint8_t flags;
|
||||
|
||||
// Pointer the buffer where the attr will be read/written
|
||||
void *buffer;
|
||||
|
||||
// Size of the attr buffer in bytes, this can be set to
|
||||
// LFS_ERR_NOATTR to remove the attr
|
||||
lfs_ssize_t buffer_size;
|
||||
|
||||
// Optional pointer to a mutable attr size, updated on read/write,
|
||||
// set to LFS_ERR_NOATTR if attr does not exist
|
||||
//
|
||||
// Defaults to buffer_size if NULL
|
||||
lfs_ssize_t *size;
|
||||
};
|
||||
|
||||
// Optional configuration provided during lfs_file_opencfg
|
||||
struct lfs_file_config {
|
||||
// Optional statically allocated file buffer. Must be buffer_size.
|
||||
@@ -507,6 +538,15 @@ struct lfs_file_config {
|
||||
//
|
||||
// // Number of custom attributes in the list
|
||||
// lfs_size_t attr_count;
|
||||
|
||||
// Optional list of custom attributes attached to the file. If readable,
|
||||
// these attributes will be kept up to date with the attributes on-disk.
|
||||
// If writeable, these attributes will be written to disk atomically on
|
||||
// every file sync or close.
|
||||
struct lfs_attr *attrs;
|
||||
|
||||
// Number of custom attributes in the list
|
||||
lfs_size_t attr_count;
|
||||
};
|
||||
|
||||
|
||||
|
||||
+1020
-24
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user