Revision | Date | Authors | Mbed OS version | Comments |
---|---|---|---|---|
1.0 | 20 September 2018 | David Saada (@davidsaada) | 5.11+ | Initial revision |
FileSystemStore is a lightweight implementation of the KVStore interface over file systems.
FileSystemStore assumes the underlying file system qualities for resilience and file validation. This means that if the underlying file system has no protection against power failures, then neither would FileSystemStore have.
When initializing this class, it is assumed that the underlying FileSystem is initialized and mounted.
FileSystemStore implements the get/set interface using files, where a single file represents each key. A key is represented by the file name, and its value is stored as file data. Therefore, FileSystemStore imitates the get/set actions using simple file operations. Set is achieved using open-write-close, get using open-read-close and so on.
All files are concentrated under a single directory, whose name is hard coded. So actions such as "reset" are mapped to the deletion of all files under this directory, and iteration actions use file system APIs to traverse the directory.
When storing the data, it is stored with a preceding 16-byte metadata header. Metadata includes flags and other parameters for basic validity checks.
Fields are:
FileSystemStore fully implements the KVStore interface over a file system. As such, it uses the FileSystem class interface for file operations.
Functionality, as defined by KVStore, includes the following:
FileSystemStore has the following header:
class FileSystemStore : KVStore { public: FileSystemStore(FileSystem *fs); virtual ~FileSystemStore(); // Initialization and reset virtual int init(); virtual int deinit(); virtual int reset(); // Core API virtual int set(const char *key, const void *buffer, size_t size, uint32_t create_flags); virtual int get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size = NULL, size_t offset = 0); virtual int get_info(const char *key, info_t *info); virtual int remove(const char *key); // Incremental set API virtual int set_start(set_handle_t *handle, const char *key, size_t final_data_size, uint32_t create_flags); virtual int set_add_data(set_handle_t handle, const void *value_data, size_t data_size); virtual int set_finalize(set_handle_t handle); // Key iterator virtual int iterator_open(iterator_t *it, const char *prefix = NULL); virtual int iterator_next(iterator_t it, char *key, size_t key_size); virtual int iterator_close(iterator_t it); private: Mutex _mutex; FileSystem *_fs; bool _is_initialized; }
// Key metadata typedef struct { uint32_t magic; uint16_t metadata_size; uint16_t revision; uint32_t flags; } key_metadata_t; // incremental set handle typedef struct { char *key; uint32_t create_flags; } inc_set_handle_t; // iterator handle typedef struct { void *dir_handle; char *prefix; } key_iterator_handle_t;
init function
Header:
virtual int init();
Pseudo code:
_is_initialized
, return OK._mutex
._is_initialized
to true._mutex
.deinit functionHeader:
virtual int deinit();
Pseudo code:
_is_initialized
, return OK._mutex
._is_initialized
to false._mutex
.reset function
Header:
virtual int reset();
Pseudo code:
_mutex
._num_keys
to 0._mutex
.set function
Header:
virtual int set(const char *key, const void *buffer, size_t size, uint32_t create_flags);
Pseudo code:
_is_initialized
, return "not initialized" error.set_start
with all fields and a local set_handle_t
variable.set_add_data
with buffer
and size
.set_finalize
.get function
Header:
virtual int get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size = NULL, size_t offset = 0);
Pseudo code:
_is_initialized
, return "not initialized" error._mutex
.stat
API, extract file size.key
for reading to achieve a file handle._mutex
and return "not found" error.key_metadata_t
structure.size
API, achieve file size.offset
+ metadata size.actual_size
as the minimum of buffer size and remainder of data.buffer
, size is actual_size
._mutex
.get_info function
Header:
virtual int get_info(const char *key, info_t *info);
Pseudo code:
_is_initialized
, return "not initialized" error.key
under the FileSystemStore directory. If not existing, return "not found" error._mutex
.key
for reading to achieve a file handle._mutex
, and return "not found" error.size
API, achieve file size.key_metadata_t
structure.info
structure with all relevant fields.remove function
Header:
virtual int remove(const char *key);
Pseudo code:
_is_initialized
, return "not initialized" error._mutex
.key
for reading, and read data into a key_metadata_t
structure.key
._mutex
.set_start function
Header:
virtual int set_start(set_handle_t *handle, const char *key, size_t final_data_size, uint32_t create_flags);
Pseudo code:
key
under the FileSystemStore directory. If not existing, increase _num_keys
by 1._mutex
.key_metadata_t
structure.inc_set_handle_t
structure into handle
.key
in handle
.create_flags
in handle
.key_metadata_t
structure with all relevant values (create_flags
from handle).key
for writing to achieve a file handle.set_add_data function
Header:
virtual int set_add_data(set_handle_t handle, const void *value_data, size_t data_size);
Pseudo code:
key
for appending to achieve a file handle.value_data
to the file.set_finalize function
Header:
virtual int set_finalize(set_handle_t handle);
Pseudo code:
key
in handle
and then handle
._mutex
.iterator_open function
Header:
virtual int iterator_open(iterator_t *it, const char *prefix = NULL);
Pseudo code:
_mutex
.key_iterator_handle_t
structure into it
.prefix
into same field in iterator.open
API, open FileSystemStore directory, and store dir handle in the handle's dir_handle
field._mutex
.iterator_next function
Header:
virtual int iterator_next(iterator_t it, char *key, size_t key_size);
Pseudo code:
_mutex
.read
API on handle's dir_handle
field, read next file in directory.key
, and return OK.read
API on handle's dir_handle
field, read next file in directory._mutex
.iterator_close function
Header:
virtual int iterator_close(iterator_t it);
Pseudo code:
close
API on dir_handle
close handle.prefix
field in iterator and structure allocated at it
.The following example code shows standard use of the FileSystemStore class :
Standard usage example
// External file system of LittleFS type. Should be initialized. extern LittleFileSystem fs; // Instantiate fsstore with our file system FileSystemStore fsstore(&fs); int res; // Initialize fsstore res = fsstore.init(); // Add "Key1" const char *val1 = "Value of key 1"; const char *val2 = "Updated value of key 1"; res = fsstore.set("Key1", val1, sizeof(val1), 0); // Update value of "Key1" res = fsstore.set("Key1", val2, sizeof(val2), 0); uint_8 value[32]; size_t actual_size; // Get value of "Key1". Value should return the updated value. res = fsstore.get("Key1", value, sizeof(value), &actual_size); // Remove "Key1" res = fsstore.remove("Key1"); // Incremental write, if need to generate large data with a small buffer const int data_size = 1024; char buf[8]; KVSTore::set_handle_t handle; res = fsstore.set_start(&handle, "Key2", data_size, 0); for (int i = 0; i < data_size / sizeof(buf); i++) { memset(buf, i, sizeof(buf)); res = fsstore.set_add_data(handle, buf, sizeof(buf)); } res = fsstore.set_finalize(handle); // Iterate over all keys starting with "Key" res = 0; KVSTore::iterator_t it; fsstore.iterator_open(&it, "Key*"); char key[KVSTore::KV_MAX_KEY_LENGTH]; while (!res) { res = fsstore.iterator_next(&it, key, sizeof(key)); } res = fsstore.iterator_close(&it); // Deinitialize FileSystemStore res = fsstore.deinit();