Skip to content

Commit

Permalink
Add lfs_file_enumattr() function
Browse files Browse the repository at this point in the history
Allows file attributes to be efficiently enumerated,
required for filesystem backup, etc.
  • Loading branch information
mikee47 committed Feb 19, 2022
1 parent f3fbbb3 commit 7b003a6
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 22 deletions.
158 changes: 136 additions & 22 deletions lfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -3192,17 +3192,25 @@ static lfs_soff_t lfs_file_rawsize(lfs_t *lfs, lfs_file_t *file) {
return file->ctz.size;
}

static lfs_ssize_t lfs_file_rawgetattr(lfs_t *lfs, lfs_file_t* file,
uint8_t type, void *buffer, lfs_size_t size) {
// If attribute is registered then retrieve cached value
static const struct lfs_attr* lfs_file_find_attribute(lfs_file_t* file, uint8_t type) {
for (unsigned i = 0; i < file->cfg->attr_count; i++) {
struct lfs_attr* const attr = &file->cfg->attrs[i];
if(type == attr->type) {
LFS_ASSERT(size <= attr->size);
memcpy(buffer, attr->buffer, size);
return attr->size;
return attr;
}
}
return NULL;
}

static lfs_ssize_t lfs_file_rawgetattr(lfs_t *lfs, lfs_file_t* file,
uint8_t type, void *buffer, lfs_size_t size) {
// If attribute is registered then retrieve cached value
const struct lfs_attr* attr = lfs_file_find_attribute(file, type);
if(attr != NULL) {
LFS_ASSERT(size <= attr->size);
memcpy(buffer, attr->buffer, lfs_min(size, attr->size));
return attr->size;
}

lfs_stag_t tag = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x7ff, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_USERATTR + type,
Expand All @@ -3228,17 +3236,15 @@ static int lfs_file_rawsetattr(lfs_t *lfs, lfs_file_t* file,
LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY);

// If attribute is registered then set cached value for consistency
for (unsigned i = 0; i < file->cfg->attr_count; i++) {
struct lfs_attr* const attr = &file->cfg->attrs[i];
if(type == attr->type) {
LFS_ASSERT(size == attr->size);
// Little cost here to check if value is different
if(memcmp(attr->buffer, buffer, size) != 0) {
memcpy(attr->buffer, buffer, size);
file->flags |= LFS_F_DIRTY;
}
return 0;
const struct lfs_attr* attr = lfs_file_find_attribute(file, type);
if(attr != NULL) {
LFS_ASSERT(size == attr->size);
// Little cost here to check if value is different
if(memcmp(attr->buffer, buffer, size) != 0) {
memcpy(attr->buffer, buffer, size);
file->flags |= LFS_F_DIRTY;
}
return 0;
}

// Otherwise commit attribute to storage
Expand All @@ -3250,19 +3256,110 @@ static int lfs_file_rawsetattr(lfs_t *lfs, lfs_file_t* file,
#ifndef LFS_READONLY
static int lfs_file_rawremoveattr(lfs_t *lfs, lfs_file_t* file, uint8_t type) {
// Cannot remove registered attributes
for (unsigned i = 0; i < file->cfg->attr_count; i++) {
struct lfs_attr* const attr = &file->cfg->attrs[i];
LFS_ASSERT(type != attr->type);
if(type == attr->type) {
return LFS_ERR_EXIST;
}
if(lfs_file_find_attribute(file, type) != NULL) {
LFS_ASSERT(false);
return LFS_ERR_EXIST;
}

return lfs_dir_commit(lfs, &file->m, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_USERATTR + type, file->id, 0x3ff), NULL}));
}
#endif

int lfs_file_rawenumattr(lfs_t* lfs, lfs_file_t* file,
lfs_attr_callback_t callback, struct lfs_attr_enum_t* e)
{
size_t count = 0;

// Set of flags to avoid returning old versions of an attribute
uint8_t found_attrs[256 / 8] = {0};

// Enumerate registered attributes first as they may have been updated
for(unsigned i = 0; i < file->cfg->attr_count; i++) {
struct lfs_attr* const attr = &file->cfg->attrs[i];
memcpy(e->buffer, attr->buffer, lfs_min(attr->size, e->bufsize));
++count;
if(!callback(e, attr->type, attr->size)) {
return count;
}
uint8_t offset = attr->type / 8;
uint8_t mask = 1 << (attr->type % 8);
found_attrs[offset] |= mask;
}

// Enumerate on-disk attributes

lfs_mdir_t* dir = &file->m;
lfs_off_t off = dir->off;
lfs_tag_t ntag = dir->etag;
lfs_stag_t gdiff = 0;

lfs_tag_t gmask = LFS_MKTAG(LFS_TYPE_USERATTR, 0x3ff, 0);
lfs_size_t gsize = lfs_min(e->bufsize, lfs->attr_max);
lfs_tag_t gtag = LFS_MKTAG(LFS_TYPE_USERATTR + 0, file->id, gsize);

if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair) &&
lfs_tag_id(gmask) != 0 &&
lfs_tag_id(lfs->gdisk.tag) <= lfs_tag_id(gtag)) {
// synthetic moves
gdiff -= LFS_MKTAG(0, 1, 0);
}

// iterate over dir block backwards (for faster lookups)
while (off >= sizeof(lfs_tag_t) + lfs_tag_dsize(ntag)) {
off -= lfs_tag_dsize(ntag);
lfs_tag_t tag = ntag;
int err = lfs_bd_read(lfs,
NULL, &lfs->rcache, sizeof(ntag),
dir->pair[0], off, &ntag, sizeof(ntag));
if (err) {
return err;
}

ntag = (lfs_frombe32(ntag) ^ tag) & 0x7fffffff;

if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE
&& lfs_tag_id(tag) <= lfs_tag_id(gtag - gdiff)) {
if (tag == (LFS_MKTAG(LFS_TYPE_CREATE, 0, 0) |
(LFS_MKTAG(0, 0x3ff, 0) & (gtag - gdiff)))) {
// found where we were created
break;
}

// move around splices
gdiff += LFS_MKTAG(0, lfs_tag_splice(tag), 0);
}

if ((gmask & tag) != (gmask & (gtag - gdiff))) {
continue;
}
if (lfs_tag_isdelete(tag)) {
continue;
}

// Skip old versions of attributes
uint8_t attrnum = lfs_tag_chunk(tag);
uint8_t offset = attrnum / 8;
uint8_t mask = 1 << (attrnum % 8);
if(found_attrs[offset] & mask) {
continue;
}
found_attrs[offset] |= mask;

lfs_size_t diff = lfs_min(lfs_tag_size(tag), gsize);
err = lfs_bd_read(lfs,
NULL, &lfs->rcache, diff,
dir->pair[0], off + sizeof(tag), e->buffer, diff);
if (err) {
return err;
}

callback(e, attrnum, lfs_tag_size(tag));
++count;
}

return count;
}

/// General fs operations ///
static int lfs_rawstat(lfs_t *lfs, const char *path, struct lfs_info *info,
Expand Down Expand Up @@ -5405,6 +5502,23 @@ int lfs_file_removeattr(lfs_t *lfs, lfs_file_t* file, uint8_t type) {
}
#endif

int lfs_file_enumattr(lfs_t* lfs, lfs_file_t* file,
lfs_attr_callback_t callback, struct lfs_attr_enum_t* e)
{
int err = LFS_LOCK(lfs->cfg);
if(err) {
return err;
}
LFS_TRACE("lfs_file_enumattr(%p, %p, %p)", (void*)lfs, (void*)file, (void*)e);
LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));

err = lfs_file_rawenumattr(lfs, file, callback, e);

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

#ifndef LFS_READONLY
int lfs_mkdir(lfs_t *lfs, const char *path) {
int err = LFS_LOCK(lfs->cfg);
Expand Down
18 changes: 18 additions & 0 deletions lfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,24 @@ int lfs_file_setattr(lfs_t *lfs, lfs_file_t* file,
// See lfs_removeattr. Fails if attribute is cached.
int lfs_file_removeattr(lfs_t *lfs, lfs_file_t* file, uint8_t type);

// Parameters used during attribute enumeration
struct lfs_attr_enum_t {
void* param;
void* buffer;
size_t bufsize;
};

// Callback to receive details for each file attribute
//
// Return true to continue enumeration, false to stop
typedef bool (*lfs_attr_callback_t)
(struct lfs_attr_enum_t* e, uint8_t type, lfs_size_t size);

// Enumerate file attributes
//
// Invokes a callback for each attribute found
int lfs_file_enumattr(lfs_t *lfs, lfs_file_t* file,
lfs_attr_callback_t callback, struct lfs_attr_enum_t* e);

/// Directory operations ///

Expand Down

0 comments on commit 7b003a6

Please sign in to comment.