mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 00:32:38 +01:00 
			
		
		
		
	Added simple custom attributes
A much requested feature (mostly because of littlefs's notable lack of timestamps), this commits adds support for user-specified custom attributes. Planned (though underestimated) since v1, custom attributes provide a route for OSs and applications to provide their own metadata in littlefs, without limiting portability. However, unlike custom attributes that can be found on much more powerful PC filesystems, these custom attributes are very limited, intended for only a handful of bytes for very important metadata. Each attribute has only a single byte to identify the attribute, and the size of all attributes attached to a file is limited to 64 bytes. Custom attributes can be accessed through the lfs_getattr, lfs_setattr, and lfs_removeattr functions.
This commit is contained in:
		
							
								
								
									
										224
									
								
								lfs.c
									
									
									
									
									
								
							
							
						
						
									
										224
									
								
								lfs.c
									
									
									
									
									
								
							| @@ -913,7 +913,7 @@ static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { | ||||
| static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, | ||||
|         lfs_entry_t *entry, const char **path) { | ||||
|     const char *pathname = *path; | ||||
|     size_t pathlen; | ||||
|     lfs_size_t pathlen; | ||||
|  | ||||
|     while (true) { | ||||
|     nextname: | ||||
| @@ -940,7 +940,7 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, | ||||
|  | ||||
|         // skip if matched by '..' in name | ||||
|         const char *suffix = pathname + pathlen; | ||||
|         size_t sufflen; | ||||
|         lfs_size_t sufflen; | ||||
|         int depth = 1; | ||||
|         while (true) { | ||||
|             suffix += strspn(suffix, "/"); | ||||
| @@ -1019,6 +1019,142 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Internal attribute operations /// | ||||
| static int lfs_dir_getinfo(lfs_t *lfs, | ||||
|         lfs_dir_t *dir, const lfs_entry_t *entry, struct lfs_info *info) { | ||||
|     memset(info, 0, sizeof(*info)); | ||||
|     info->type = 0xf & entry->d.type; | ||||
|     if (entry->d.type == (LFS_STRUCT_CTZ | LFS_TYPE_REG)) { | ||||
|         info->size = entry->d.u.file.size; | ||||
|     } else if (entry->d.type == (LFS_STRUCT_INLINE | LFS_TYPE_REG)) { | ||||
|         info->size = lfs_entry_elen(entry); | ||||
|     } | ||||
|  | ||||
|     if (lfs_paircmp(entry->d.u.dir, lfs->root) == 0) { | ||||
|         strcpy(info->name, "/"); | ||||
|     } else { | ||||
|         int err = lfs_dir_get(lfs, dir, | ||||
|                 entry->off + entry->size - entry->d.nlen, | ||||
|                 info->name, entry->d.nlen); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static int lfs_dir_getattr(lfs_t *lfs, | ||||
|         lfs_dir_t *dir, const lfs_entry_t *entry, | ||||
|         uint8_t type, void *buffer, lfs_size_t size) { | ||||
|     // search for attribute in attribute region | ||||
|     lfs_off_t off = sizeof(dir->d) + lfs_entry_elen(entry); | ||||
|     lfs_off_t i = 0; | ||||
|     while (i < lfs_entry_alen(entry)) { | ||||
|         lfs_attr_t attr; | ||||
|         int err = lfs_dir_get(lfs, dir, | ||||
|                 entry->off+off+i, &attr.d, sizeof(attr.d)); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         if (attr.d.type != type) { | ||||
|             i += attr.d.len; | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         if (attr.d.len > size) { | ||||
|             return LFS_ERR_RANGE; | ||||
|         } | ||||
|  | ||||
|         err = lfs_dir_get(lfs, dir, | ||||
|                 entry->off+off+i+sizeof(attr.d), buffer, attr.d.len); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         return attr.d.len; | ||||
|     } | ||||
|  | ||||
|     return LFS_ERR_NODATA; | ||||
| } | ||||
|  | ||||
| static int lfs_dir_setattr(lfs_t *lfs, | ||||
|         lfs_dir_t *dir, lfs_entry_t *entry, | ||||
|         uint8_t type, const void *buffer, lfs_size_t size) { | ||||
|     // search for attribute in attribute region | ||||
|     lfs_off_t off = sizeof(dir->d) + lfs_entry_elen(entry); | ||||
|     lfs_off_t i = 0; | ||||
|     lfs_size_t oldlen = 0; | ||||
|     while (i < lfs_entry_alen(entry)) { | ||||
|         lfs_attr_t attr; | ||||
|         int err = lfs_dir_get(lfs, dir, | ||||
|                 entry->off+off+i, &attr.d, sizeof(attr.d)); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         if (attr.d.type != type) { | ||||
|             i += attr.d.len; | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         oldlen = attr.d.len; | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     // make sure the attribute fits | ||||
|     if (lfs_entry_elen(entry) - oldlen + size > lfs->attrs_size || | ||||
|         (0x7fffffff & dir->d.size) - oldlen + size > lfs->cfg->block_size) { | ||||
|         return LFS_ERR_NOSPC; | ||||
|     } | ||||
|  | ||||
|     lfs_attr_t attr; | ||||
|     attr.d.type = type; | ||||
|     attr.d.len = size; | ||||
|     int err = lfs_dir_set(lfs, dir, entry, (struct lfs_region[]){ | ||||
|             {LFS_FROM_MEM, off+i, &attr.d, sizeof(attr.d)}, | ||||
|             {LFS_FROM_MEM, off+i, buffer, size}, | ||||
|             {LFS_FROM_DROP, off+i, NULL, -oldlen}}, 3); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static int lfs_dir_removeattr(lfs_t *lfs, | ||||
|         lfs_dir_t *dir, lfs_entry_t *entry, uint8_t type) { | ||||
|     // search for attribute in attribute region | ||||
|     lfs_off_t off = sizeof(dir->d) + lfs_entry_elen(entry); | ||||
|     lfs_off_t i = 0; | ||||
|     while (i < lfs_entry_alen(entry)) { | ||||
|         lfs_attr_t attr; | ||||
|         int err = lfs_dir_get(lfs, dir, | ||||
|                 entry->off+off+i, &attr.d, sizeof(attr.d)); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         if (attr.d.type != type) { | ||||
|             i += attr.d.len; | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         err = lfs_dir_set(lfs, dir, entry, (struct lfs_region[]){ | ||||
|                 {LFS_FROM_DROP, off+i, | ||||
|                     NULL, -(sizeof(attr.d)+attr.d.len)}}, 1); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     return LFS_ERR_NODATA; | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| /// Top level directory operations /// | ||||
| int lfs_mkdir(lfs_t *lfs, const char *path) { | ||||
| @@ -1179,16 +1315,7 @@ int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     info->type = 0xf & entry.d.type; | ||||
|     if (entry.d.type == (LFS_STRUCT_CTZ | LFS_TYPE_REG)) { | ||||
|         info->size = entry.d.u.file.size; | ||||
|     } else if (entry.d.type == (LFS_STRUCT_INLINE | LFS_TYPE_REG)) { | ||||
|         info->size = lfs_entry_elen(&entry); | ||||
|     } | ||||
|  | ||||
|     int err = lfs_dir_get(lfs, dir, | ||||
|             entry.off + entry.size - entry.d.nlen, | ||||
|             info->name, entry.d.nlen); | ||||
|     int err = lfs_dir_getinfo(lfs, dir, &entry, info); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
| @@ -2048,26 +2175,7 @@ int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     memset(info, 0, sizeof(*info)); | ||||
|     info->type = 0xf & entry.d.type; | ||||
|     if (entry.d.type == (LFS_STRUCT_CTZ | LFS_TYPE_REG)) { | ||||
|         info->size = entry.d.u.file.size; | ||||
|     } else if (entry.d.type == (LFS_STRUCT_INLINE | LFS_TYPE_REG)) { | ||||
|         info->size = lfs_entry_elen(&entry); | ||||
|     } | ||||
|  | ||||
|     if (lfs_paircmp(entry.d.u.dir, lfs->root) == 0) { | ||||
|         strcpy(info->name, "/"); | ||||
|     } else { | ||||
|         err = lfs_dir_get(lfs, &cwd, | ||||
|                 entry.off + entry.size - entry.d.nlen, | ||||
|                 info->name, entry.d.nlen); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return 0; | ||||
|     return lfs_dir_getinfo(lfs, &cwd, &entry, info); | ||||
| } | ||||
|  | ||||
| int lfs_remove(lfs_t *lfs, const char *path) { | ||||
| @@ -2263,6 +2371,58 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { | ||||
| } | ||||
|  | ||||
|  | ||||
| /// Attribute operations /// | ||||
| int lfs_getattr(lfs_t *lfs, const char *path, | ||||
|         uint8_t type, void *buffer, lfs_size_t size) { | ||||
|     lfs_dir_t cwd; | ||||
|     int err = lfs_dir_fetch(lfs, &cwd, lfs->root); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     lfs_entry_t entry; | ||||
|     err = lfs_dir_find(lfs, &cwd, &entry, &path); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     return lfs_dir_getattr(lfs, &cwd, &entry, type, buffer, size); | ||||
| } | ||||
|  | ||||
| int lfs_setattr(lfs_t *lfs, const char *path, | ||||
|         uint8_t type, const void *buffer, lfs_size_t size) { | ||||
|     lfs_dir_t cwd; | ||||
|     int err = lfs_dir_fetch(lfs, &cwd, lfs->root); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     lfs_entry_t entry; | ||||
|     err = lfs_dir_find(lfs, &cwd, &entry, &path); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     return lfs_dir_setattr(lfs, &cwd, &entry, type, buffer, size); | ||||
| } | ||||
|  | ||||
| int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) { | ||||
|     lfs_dir_t cwd; | ||||
|     int err = lfs_dir_fetch(lfs, &cwd, lfs->root); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     lfs_entry_t entry; | ||||
|     err = lfs_dir_find(lfs, &cwd, &entry, &path); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     return lfs_dir_removeattr(lfs, &cwd, &entry, type); | ||||
| } | ||||
|  | ||||
|  | ||||
| /// Filesystem operations /// | ||||
| static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { | ||||
|     lfs->cfg = cfg; | ||||
|   | ||||
							
								
								
									
										32
									
								
								lfs.h
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								lfs.h
									
									
									
									
									
								
							| @@ -89,6 +89,8 @@ enum lfs_error { | ||||
|     LFS_ERR_NOSPC       = -28,  // No space left on device | ||||
|     LFS_ERR_NOMEM       = -12,  // No more memory available | ||||
|     LFS_ERR_NAMETOOLONG = -36,  // File name too long | ||||
|     LFS_ERR_NODATA      = -61,  // No data/attr available | ||||
|     LFS_ERR_RANGE       = -34,  // Result not representable | ||||
| }; | ||||
|  | ||||
| // File types | ||||
| @@ -253,6 +255,13 @@ typedef struct lfs_entry { | ||||
|     } d; | ||||
| } lfs_entry_t; | ||||
|  | ||||
| typedef struct lfs_attr { | ||||
|     struct lfs_disk_attr { | ||||
|         uint8_t type; | ||||
|         uint8_t len; | ||||
|     } d; | ||||
| } lfs_attr_t; | ||||
|  | ||||
| typedef struct lfs_cache { | ||||
|     lfs_block_t block; | ||||
|     lfs_off_t off; | ||||
| @@ -379,6 +388,29 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); | ||||
| // Returns a negative error code on failure. | ||||
| int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); | ||||
|  | ||||
| // Get a custom attribute | ||||
| // | ||||
| // Attributes are identified by an 8-bit type and are limited to at | ||||
| // most LFS_ATTRS_SIZE bytes. | ||||
| // Returns the size of the attribute, or a negative error code on failure. | ||||
| int lfs_getattr(lfs_t *lfs, const char *path, | ||||
|         uint8_t type, void *buffer, lfs_size_t size); | ||||
|  | ||||
| // Set a custom attribute | ||||
| // | ||||
| // Attributes are identified by an 8-bit type and are limited to at | ||||
| // most LFS_ATTRS_SIZE bytes. | ||||
| // 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); | ||||
|  | ||||
| // Remove a custom attribute | ||||
| // | ||||
| // Attributes are identified by an 8-bit type and are limited to at | ||||
| // most LFS_ATTRS_SIZE bytes. | ||||
| // Returns a negative error code on failure. | ||||
| int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type); | ||||
|  | ||||
|  | ||||
| /// File operations /// | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user