diff --git a/Documentation/applications/testing/nand_sim/index.rst b/Documentation/applications/testing/nand_sim/index.rst index 61472adf0b9..fb0ac4847ea 100644 --- a/Documentation/applications/testing/nand_sim/index.rst +++ b/Documentation/applications/testing/nand_sim/index.rst @@ -152,15 +152,18 @@ is still the wrapper, not the actual upper half, as the upper half methods may call the other methods as well internally and we might want to log them as well*. -Registering Device & Daemon -=========================== +Registering Device & Background Task +==================================== This wrapper is then registered using ``register_mtddriver``, and this -whole thing is converted to be a daemon, so that the device can keep running -in the background. +whole thing is launched as a background task, so that the device can keep +running after the ``nand`` command returns to the shell. -Making it a daemon is achieved by using ``fork()``, killing the parent, and -using ``daemon()`` in child. +The ``nand`` command starts a dedicated NuttX task with ``task_create()``, +waits until the device registration finishes, and then returns control to +the shell. This keeps the simulator alive in the background without relying +on ``fork()``, whose post-fork execution model is not appropriate for this +kind of long-running initialization in the flat build used by the simulator. Known Issues ============ @@ -168,4 +171,4 @@ Known Issues * ECC is not implemented yet. * MLC NAND Flash is not implemented yet. * Due to the fixed name of the device, there can't be more than one instance - of this virtual device. \ No newline at end of file + of this virtual device. diff --git a/drivers/mtd/mtd_nandram.c b/drivers/mtd/mtd_nandram.c index 1b92dc8e6a6..0a719b62237 100644 --- a/drivers/mtd/mtd_nandram.c +++ b/drivers/mtd/mtd_nandram.c @@ -332,12 +332,10 @@ int nand_ram_eraseblock(FAR struct nand_raw_s *raw, off_t block) int nand_ram_rawread(FAR struct nand_raw_s *raw, off_t block, unsigned int page, FAR void *data, FAR void *spare) { - int ret; uint32_t read_page; struct nand_ram_data_s *read_page_data; struct nand_ram_spare_s *read_page_spare; - ret = OK; read_page = (block << NAND_RAM_LOG_PAGES_PER_BLOCK) + page; read_page_data = nand_ram_flash_data + read_page; read_page_spare = nand_ram_flash_spare + read_page; @@ -349,14 +347,6 @@ int nand_ram_rawread(FAR struct nand_raw_s *raw, off_t block, nand_ram_ins_i, "rawread", read_page); nand_ram_status(); - if (nand_ram_flash_spare[read_page].bad != NAND_RAM_BLOCK_GOOD) - { - ret = -EFAULT; - NAND_RAM_LOG("[LOWER %" PRIu64 " | %s] Failed: %s\n", - nand_ram_ins_i, "rawread", EFAULT_STR); - goto errout; - } - nand_ram_flash_spare[read_page].n_read++; if (data != NULL) @@ -378,11 +368,9 @@ int nand_ram_rawread(FAR struct nand_raw_s *raw, off_t block, } NAND_RAM_LOG("[LOWER %" PRIu64 " | %s] Done\n", nand_ram_ins_i, "rawread"); - -errout: nxmutex_unlock(&nand_ram_dev_mut); - return ret; + return OK; } /**************************************************************************** @@ -444,7 +432,7 @@ int nand_ram_rawwrite(FAR struct nand_raw_s *raw, off_t block, if (spare != NULL) { - memcpy((FAR void *)write_page_spare, data, NAND_RAM_SPARE_SIZE); + memcpy((FAR void *)write_page_spare, spare, NAND_RAM_SPARE_SIZE); } NAND_RAM_LOG("[LOWER %" PRIu64 " | %s] Done\n", nand_ram_ins_i, diff --git a/drivers/mtd/mtd_nandwrapper.c b/drivers/mtd/mtd_nandwrapper.c index 248db39e4a0..2844c3f7c8d 100644 --- a/drivers/mtd/mtd_nandwrapper.c +++ b/drivers/mtd/mtd_nandwrapper.c @@ -272,6 +272,7 @@ int nand_wrapper_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) { int ret; + FAR uint8_t *erasestate; FAR struct nand_wrapper_dev_s *nand_dev; nand_dev = (struct nand_wrapper_dev_s *)dev; @@ -282,7 +283,28 @@ int nand_wrapper_ioctl(FAR struct mtd_dev_s *dev, int cmd, ", Arg : %zu\n", nand_wrapper_ins_i, "ioctl", cmd, arg); DEBUGASSERT(nand_dev && nand_dev->under.mtd.ioctl); - ret = nand_dev->under.mtd.ioctl(dev, cmd, arg); + if (cmd == MTDIOC_ERASESTATE) + { + erasestate = (FAR uint8_t *)arg; + if (erasestate == NULL) + { + ret = -EINVAL; + } + else + { + /* The wrapped bread path exposes blank NAND pages as zero-filled + * buffers after ECC handling, so report the same visible erase + * state to higher layers. + */ + + *erasestate = 0x00; + ret = OK; + } + } + else + { + ret = nand_dev->under.mtd.ioctl(dev, cmd, arg); + } if (ret >= 0) { diff --git a/fs/mnemofs/CMakeLists.txt b/fs/mnemofs/CMakeLists.txt index 059b0ec1024..fa304483a54 100644 --- a/fs/mnemofs/CMakeLists.txt +++ b/fs/mnemofs/CMakeLists.txt @@ -49,15 +49,6 @@ # ############################################################################## if(CONFIG_FS_MNEMOFS) - target_sources( - fs - PRIVATE mnemofs_blkalloc.c - mnemofs_ctz.c - mnemofs_fsobj.c - mnemofs_journal.c - mnemofs_lru.c - mnemofs_master.c - mnemofs_rw.c - mnemofs_util.c - mnemofs.c) + target_sources(fs PRIVATE mnemofs.c mnemofs_alloc.c mnemofs_ctz.c + mnemofs_dirent.c mnemofs_file.c mnemofs_rw.c) endif() diff --git a/fs/mnemofs/Kconfig b/fs/mnemofs/Kconfig index 5b917613791..833665531c4 100644 --- a/fs/mnemofs/Kconfig +++ b/fs/mnemofs/Kconfig @@ -9,44 +9,3 @@ config FS_MNEMOFS depends on !DISABLE_MOUNTPOINT && MTD_NAND ---help--- Build the mnemofs NAND flash file system. - -if FS_MNEMOFS -config MNEMOFS_EXTRA_DEBUG - bool "MNEMOFS Extra Debug Logs" - default n - depends on FS_MNEMOFS - ---help--- - Prints extra log information related to mnemofs. - -config MNEMOFS_JOURNAL_NBLKS - int "MNEMOFS Journal Block Count" - default 20 - range 4 65536 - depends on FS_MNEMOFS - ---help--- - Number of blocks that mnemofs will use for the journal. Specifying - this will only work on formatting a NAND flash using mnemofs. If the - device is already formatted, the on-flash journal block count will - be considered instead. Two additional blocks will be allocated for - the master blocks. - -config MNEMOFS_NLRU - int "MNEMOFS LRU Node Count" - default 20 - range 1 255 - depends on FS_MNEMOFS - ---help--- - Number of nodes used by mnemofs for LRU. The higher the value is, - the lesser would be the wear on device with higher RAM - consumption. - -config MNEMOFS_NLRUDELTA - int "MNEMOFS LRU Delta Count" - default 20 - range 1 255 - depends on FS_MNEMOFS - ---help--- - Number of deltas used by mnemofs for LRU for every node. The higher - the value is, the lesser would be the wear on device with higher RAM - consumption. -endif # FS_MNEMOFS diff --git a/fs/mnemofs/Make.defs b/fs/mnemofs/Make.defs index b3daca808da..c30d4c0cf95 100644 --- a/fs/mnemofs/Make.defs +++ b/fs/mnemofs/Make.defs @@ -54,15 +54,12 @@ ifeq ($(CONFIG_FS_MNEMOFS),y) # Add the mnemofs C files to the build -CSRCS += mnemofs_blkalloc.c -CSRCS += mnemofs_ctz.c -CSRCS += mnemofs_fsobj.c -CSRCS += mnemofs_journal.c -CSRCS += mnemofs_lru.c -CSRCS += mnemofs_master.c -CSRCS += mnemofs_rw.c -CSRCS += mnemofs_util.c CSRCS += mnemofs.c +CSRCS += mnemofs_alloc.c +CSRCS += mnemofs_ctz.c +CSRCS += mnemofs_dirent.c +CSRCS += mnemofs_file.c +CSRCS += mnemofs_rw.c # Add the mnemofs directory to the build diff --git a/fs/mnemofs/mnemofs.c b/fs/mnemofs/mnemofs.c index bf1a4326780..5a3065cc2cf 100644 --- a/fs/mnemofs/mnemofs.c +++ b/fs/mnemofs/mnemofs.c @@ -50,134 +50,1878 @@ * ****************************************************************************/ -/* mnemofs has these components: - * - Block Allocator - * - Journal - * - LRU - * - Master Node - * - * All the files and directories are stored as a reversed CTZ skip list, a - * data structure defined by littlefs. CTZs list have multiple nodes - * connected by pointers stored in the nodes. So, each node has a pointer - * area, which is at the end of the CTZ node (CTZ block), and a data area, - * which forms the rest of the area, which stores the actual file data. The - * size of the data area can be deterministically found given the index of - * the node in the CTZ list. In mnemofs, each CTZ node/CTZ block takes up one - * page in the NAND flash. - * - * Due to the presence of a data area, the mnemofs VFS methods can only view - * an abstracted view of the CTZ list they are interacting with. VFS methods - * work with CTZ methods (mnemofs_ctz.c), and can not see the existence of - * the pointer area, nor do they have to care about it. They work with - * offsets in the data area from the beginning of the list, and the CTZ - * methods take care of converting that offset into actual NAND flash - * coordinates (block number, or page number). - * - * More info in mnemofs_ctz.c, mnemofs_lru.c, mnemofs_journal.c and - * mnemofs_master.c. - */ - -/* TODO: - * - LRU and journal store multiple deltas/commits together respectively. - * These are supposed to be applied together atomically. - * - Journal moves. - * - Return values of all functions. - */ - /**************************************************************************** * Included Files ****************************************************************************/ -#include -#include -#include +#include #include -#include -#include -#include +#include + +#include +#include #include #include "mnemofs.h" -#include "fs_heap.h" - -/**************************************************************************** - * Pre-processor Definitions - ****************************************************************************/ - -/**************************************************************************** - * Private Types - ****************************************************************************/ /**************************************************************************** * Private Function Prototypes ****************************************************************************/ -static int mnemofs_open(FAR struct file *filep, FAR const char *relpath, - int oflags, mode_t mode); -static int mnemofs_close(FAR struct file *filep); +static int mnemofs_bind(FAR struct inode *driver, FAR const void *data, + FAR void **handle); +static int mnemofs_unbind(FAR void *handle, FAR struct inode **driver, + unsigned int flags); +static int mnemofs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf); +static int mnemofs_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode); +static int mnemofs_close(FAR struct file *filep); static ssize_t mnemofs_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static ssize_t mnemofs_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); -static off_t mnemofs_seek(FAR struct file *filep, off_t offset, - int whence); -static int mnemofs_ioctl(FAR struct file *filep, int cmd, - unsigned long arg); -static int mnemofs_truncate(FAR struct file *filep, off_t length); - -static int mnemofs_sync(FAR struct file *filep); -static int mnemofs_dup(FAR const struct file *oldp, - FAR struct file *newp); -static int mnemofs_fstat(FAR const struct file *filep, - FAR struct stat *buf); - -static int mnemofs_opendir(FAR struct inode *mountpt, - FAR const char *relpath, - FAR struct fs_dirent_s **dir); -static int mnemofs_closedir(FAR struct inode *mountpt, - FAR struct fs_dirent_s *dir); -static int mnemofs_readdir(FAR struct inode *mountpt, - FAR struct fs_dirent_s *dir, - FAR struct dirent *entry); -static int mnemofs_rewinddir(FAR struct inode *mountpt, - FAR struct fs_dirent_s *dir); - -static int mnemofs_bind(FAR struct inode *driver, FAR const void *data, - FAR void** handle); - -static int mnemofs_unbind(FAR void *handle, FAR struct inode **driver, - unsigned int flags); -static int mnemofs_statfs(FAR struct inode *mountpt, - FAR struct statfs *buf); - -static int mnemofs_unlink(FAR struct inode *mountpt, - FAR const char *relpath); -static int mnemofs_mkdir(FAR struct inode *mountpt, - FAR const char *relpath, mode_t mode); -static int mnemofs_rmdir(FAR struct inode *mountpt, - FAR const char *relpath); -static int mnemofs_rename(FAR struct inode *mountpt, - FAR const char *oldrelpath, - FAR const char *newrelpath); -static int mnemofs_stat(FAR struct inode *mountpt, - FAR const char *relpath, FAR struct stat *buf); +static off_t mnemofs_seek(FAR struct file *filep, off_t offset, int whence); +static int mnemofs_ioctl(FAR struct file *filep, int cmd, unsigned long arg); +static int mnemofs_truncate(FAR struct file *filep, off_t length); +static int mnemofs_sync(FAR struct file *filep); +static int mnemofs_dup(FAR const struct file *oldp, FAR struct file *newp); +static int mnemofs_fstat(FAR const struct file *filep, FAR struct stat *buf); +static int mnemofs_opendir(FAR struct inode *mountpt, + FAR const char *relpath, + FAR struct fs_dirent_s **dir); +static int mnemofs_closedir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir); +static int mnemofs_readdir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir, + FAR struct dirent *entry); +static int mnemofs_rewinddir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir); +static int mnemofs_unlink(FAR struct inode *mountpt, + FAR const char *relpath); +static int mnemofs_mkdir(FAR struct inode *mountpt, FAR const char *relpath, + mode_t mode); +static int mnemofs_rmdir(FAR struct inode *mountpt, FAR const char *relpath); +static int mnemofs_rename(FAR struct inode *mountpt, + FAR const char *oldrelpath, + FAR const char *newrelpath); +static int mnemofs_stat(FAR struct inode *mountpt, FAR const char *relpath, + FAR struct stat *buf); /**************************************************************************** - * Private Data + * Pre-processor Definitions ****************************************************************************/ +#define MFS_MOUNTOPT_NONE 0 +#define MFS_MOUNTOPT_AUTOFORMAT 1 +#define MFS_MOUNTOPT_FORCEFORMAT 2 + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mnemofs_mountpt_to_sb + * + * Description: + * Return the mounted file system state associated with one mountpoint + * inode. + * + * Input Parameters: + * mountpt - The mountpoint inode to inspect. + * + * Returned Value: + * A pointer to the mounted file system state is returned on success. + * NULL is returned if mountpt is NULL. + * + ****************************************************************************/ + +static FAR struct mfs_sb_s *mnemofs_mountpt_to_sb(FAR struct inode *mountpt) +{ + if (mountpt == NULL) + { + return NULL; + } + + return mountpt->i_private; +} + +/**************************************************************************** + * Name: mnemofs_file_to_sb + * + * Description: + * Return the mounted file system state associated with one VFS file + * structure. + * + * Input Parameters: + * filep - The VFS file structure to inspect. + * + * Returned Value: + * A pointer to the mounted file system state is returned on success. + * NULL is returned if no file-system state can be resolved. + * + ****************************************************************************/ + +static FAR struct mfs_sb_s *mnemofs_file_to_sb(FAR const struct file *filep) +{ + FAR struct mfs_ofd_s *ofd; + + if (filep == NULL) + { + return NULL; + } + + ofd = filep->f_priv; + if (ofd != NULL) + { + return ofd->sb; + } + + if (filep->f_inode == NULL) + { + return NULL; + } + + return filep->f_inode->i_private; +} + +/**************************************************************************** + * Name: mnemofs_lock + * + * Description: + * Acquire the per-mount mnemofs mutex. + * + * Input Parameters: + * sb - The mounted file system instance. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * invalid input or lock failure. + * + ****************************************************************************/ + +static int mnemofs_lock(FAR struct mfs_sb_s *sb) +{ + if (sb == NULL) + { + return -EINVAL; + } + + return nxmutex_lock(&sb->lock); +} + +/**************************************************************************** + * Name: mnemofs_unlock + * + * Description: + * Flush and invalidate the shared read/write buffer, then release the + * per-mount mutex. + * + * Input Parameters: + * sb - The mounted file system instance. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mnemofs_unlock(FAR struct mfs_sb_s *sb) +{ + int ret; + + DEBUGASSERT(sb != NULL); + ret = mfs_rwbuf_sync(sb); + if (ret < 0) + { + ferr("Could not sync read/write buffer: %d\n", ret); + } + + mfs_rwbuf_invalidate(sb); + nxmutex_unlock(&sb->lock); +} + +/**************************************************************************** + * Name: mnemofs_dirmeta_checksum + * + * Description: + * Compute the checksum stored in an on-flash directory metadata record. + * + * Input Parameters: + * dirmeta - The directory metadata record to checksum. + * + * Returned Value: + * The computed checksum value is returned. + * + ****************************************************************************/ + +static uint8_t mnemofs_dirmeta_checksum( + FAR const struct mfs_dirmeta_s *dirmeta) +{ + return crc8((FAR const uint8_t *)dirmeta, sizeof(*dirmeta) - 1); +} + +/**************************************************************************** + * Name: mnemofs_dirmeta_is_valid + * + * Description: + * Verify the magic and checksum stored in a directory metadata record. + * + * Input Parameters: + * dirmeta - The directory metadata record to validate. + * + * Returned Value: + * true is returned if dirmeta contains a valid directory metadata record. + * false is returned otherwise. + * + ****************************************************************************/ + +static bool mnemofs_dirmeta_is_valid( + FAR const struct mfs_dirmeta_s *dirmeta) +{ + if (dirmeta == NULL) + { + return false; + } + + return dirmeta->magic == MFS_DIR_MAGIC && + dirmeta->checksum == mnemofs_dirmeta_checksum(dirmeta); +} + +/**************************************************************************** + * Name: mnemofs_superblock_init + * + * Description: + * Initialize one in-memory superblock image for writing to flash. + * + * Input Parameters: + * sb - The mounted file system instance. + * rootdir - The root directory block to store. + * superblock - The location to receive the initialized image. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mnemofs_superblock_init(FAR const struct mfs_sb_s *sb, + mfs_t rootdir, + FAR struct mfs_superblock_s *superblock) +{ + DEBUGASSERT(sb != NULL && superblock != NULL); + + memset(superblock, 0, sizeof(*superblock)); + superblock->magic = MNEMOFS_SUPER_MAGIC; + superblock->npages = MFS_PAGE_COUNT(sb); + superblock->pagesize = MFS_PAGE_SIZE(sb); + superblock->pagesperblock = MFS_PAGES_PER_BLOCK(sb); + superblock->nblocks = MFS_BLOCK_COUNT(sb); + superblock->rootdir = rootdir; + superblock->version = sb->version; +} + +/**************************************************************************** + * Name: mnemofs_superblock_is_valid + * + * Description: + * Verify that an on-flash superblock matches the current device geometry + * and references a plausible root directory block. + * + * Input Parameters: + * sb - The mounted file system instance. + * superblock - The on-flash superblock image to validate. + * + * Returned Value: + * true is returned if the superblock is valid. false is returned + * otherwise. + * + ****************************************************************************/ + +static bool mnemofs_superblock_is_valid( + FAR const struct mfs_sb_s *sb, + FAR const struct mfs_superblock_s *superblock) +{ + if (sb == NULL || superblock == NULL) + { + return false; + } + + return superblock->magic == MNEMOFS_SUPER_MAGIC && + superblock->npages == MFS_PAGE_COUNT(sb) && + superblock->pagesize == MFS_PAGE_SIZE(sb) && + superblock->pagesperblock == MFS_PAGES_PER_BLOCK(sb) && + superblock->nblocks == MFS_BLOCK_COUNT(sb) && + superblock->rootdir < MFS_BLOCK_COUNT(sb); +} + +/**************************************************************************** + * Name: mnemofs_find_next_good_block + * + * Description: + * Scan forward from start until a non-bad block is found. + * + * Input Parameters: + * sb - The mounted file system instance. + * start - The first block number to inspect. + * block - The location to receive the next usable block. + * + * Returned Value: + * Zero (OK) is returned on success. -ENOSPC is returned if no good block + * exists at or after start. A negated errno value is returned on other + * failures. + * + ****************************************************************************/ + +static int mnemofs_find_next_good_block(FAR struct mfs_sb_s *sb, mfs_t start, + FAR mfs_t *block) +{ + int ret; + + if (sb == NULL || block == NULL) + { + return -EINVAL; + } + + for (; start < MFS_BLOCK_COUNT(sb); start++) + { + ret = mfs_is_bad_block(sb, start); + if (ret < 0) + { + return ret; + } + + if (ret == 0) + { + * block = start; + return OK; + } + } + + return -ENOSPC; +} + +/**************************************************************************** + * Name: mnemofs_find_initial_rootdir + * + * Description: + * Find the first usable root-directory block after the chosen superblock. + * + * Input Parameters: + * sb - The mounted file system instance. + * superblock - The block number reserved for the superblock. + * rootdir - The location to receive the candidate root directory + * block. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_find_initial_rootdir(FAR struct mfs_sb_s *sb, + mfs_t superblock, + FAR mfs_t *rootdir) +{ + if (sb == NULL || rootdir == NULL) + { + return -EINVAL; + } + + return mnemofs_find_next_good_block(sb, superblock + 1, rootdir); +} + +/**************************************************************************** + * Name: mnemofs_validate_rootdir + * + * Description: + * Verify that rootdir is usable and contains a valid self-parenting + * directory metadata page. + * + * Input Parameters: + * sb - The mounted file system instance. + * superblock - The superblock block number. + * rootdir - The root directory block number to validate. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_validate_rootdir(FAR struct mfs_sb_s *sb, + mfs_t superblock, mfs_t rootdir) +{ + struct mfs_dirmeta_s dirmeta; + ssize_t nread; + mfs_t page; + int ret; + + if (sb == NULL) + { + return -EINVAL; + } + + if (rootdir <= superblock || rootdir >= MFS_BLOCK_COUNT(sb)) + { + return -EINVAL; + } + + ret = mfs_is_bad_block(sb, rootdir); + if (ret < 0) + { + return ret; + } + + if (ret != 0) + { + return -EINVAL; + } + + page = MFS_BLOCK_TO_PAGE(sb, rootdir); + nread = mfs_read_page(sb, page, sb->rwbuf); + if (nread < 0) + { + return nread; + } + + if (nread != 1) + { + return -EIO; + } + + memcpy(&dirmeta, sb->rwbuf, sizeof(dirmeta)); + if (!mnemofs_dirmeta_is_valid(&dirmeta) || dirmeta.parent != rootdir) + { + return -EINVAL; + } + + return OK; +} + +/**************************************************************************** + * Name: mnemofs_read_superblock + * + * Description: + * Read and validate the on-flash superblock, then return its root + * directory block number. + * + * Input Parameters: + * sb - The mounted file system instance. + * superblock - The block number that stores the superblock. + * rootdir - The location to receive the validated root directory + * block. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_read_superblock(FAR struct mfs_sb_s *sb, mfs_t superblock, + FAR mfs_t *rootdir) +{ + struct mfs_superblock_s onflash; + ssize_t nread; + mfs_t page; + int ret; + + if (sb == NULL || rootdir == NULL) + { + return -EINVAL; + } + + page = MFS_BLOCK_TO_PAGE(sb, superblock); + nread = mfs_read_page(sb, page, sb->rwbuf); + if (nread < 0) + { + return nread; + } + + if (nread != 1) + { + return -EIO; + } + + memcpy(&onflash, sb->rwbuf, sizeof(onflash)); + if (!mnemofs_superblock_is_valid(sb, &onflash)) + { + return -EINVAL; + } + + ret = mnemofs_validate_rootdir(sb, superblock, onflash.rootdir); + if (ret < 0) + { + return ret; + } + + if (onflash.version > MFS_FORMAT_VERSION_MAJOR) + { + ferr("higher version present on flash: %u > %u\n", + onflash.version, MFS_FORMAT_VERSION_MAJOR); + return -EPROTONOSUPPORT; + } + + sb->version = onflash.version; + * rootdir = onflash.rootdir; + return OK; +} + +/**************************************************************************** + * Name: mnemofs_write_superblock + * + * Description: + * Write a freshly initialized superblock image to flash. + * + * Input Parameters: + * sb - The mounted file system instance. + * superblock - The block number that stores the superblock. + * rootdir - The root directory block number to record. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_write_superblock(FAR struct mfs_sb_s *sb, + mfs_t superblock, mfs_t rootdir) +{ + struct mfs_superblock_s onflash; + int ret; + ssize_t nwritten; + mfs_t page; + + if (sb == NULL) + { + return -EINVAL; + } + + mnemofs_superblock_init(sb, rootdir, &onflash); + ret = mfs_rwbuf_prepare_write(sb); + if (ret < 0) + { + return ret; + } + + memset(sb->rwbuf, 0, MFS_PAGE_SIZE(sb)); + memcpy(sb->rwbuf, &onflash, sizeof(onflash)); + + page = MFS_BLOCK_TO_PAGE(sb, superblock); + nwritten = mfs_write_page(sb, page, sb->rwbuf); + if (nwritten < 0) + { + return nwritten; + } + + return nwritten == 1 ? OK : -EIO; +} + +/**************************************************************************** + * Name: mnemofs_format_rootdir + * + * Description: + * Write the metadata page for a freshly formatted root directory block. + * + * Input Parameters: + * sb - The mounted file system instance. + * rootdir - The root directory block number to initialize. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_format_rootdir(FAR struct mfs_sb_s *sb, mfs_t rootdir) +{ + struct mfs_dirmeta_s dirmeta; + int ret; + ssize_t nwritten; + mfs_t page; + + if (sb == NULL) + { + return -EINVAL; + } + + memset(&dirmeta, 0, sizeof(dirmeta)); + dirmeta.magic = MFS_DIR_MAGIC; + dirmeta.parent = rootdir; + dirmeta.checksum = mnemofs_dirmeta_checksum(&dirmeta); + + ret = mfs_rwbuf_prepare_write(sb); + if (ret < 0) + { + return ret; + } + + memset(sb->rwbuf, 0, MFS_PAGE_SIZE(sb)); + memcpy(sb->rwbuf, &dirmeta, sizeof(dirmeta)); + + page = MFS_BLOCK_TO_PAGE(sb, rootdir); + nwritten = mfs_write_page(sb, page, sb->rwbuf); + if (nwritten < 0) + { + return nwritten; + } + + return nwritten == 1 ? OK : -EIO; +} + +/**************************************************************************** + * Name: mnemofs_erase_good_blocks + * + * Description: + * Erase every usable block on the backing device while leaving bad blocks + * untouched. + * + * Input Parameters: + * sb - The mounted file system instance. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_erase_good_blocks(FAR struct mfs_sb_s *sb) +{ + mfs_t block; + int bad; + int ret; + + if (sb == NULL) + { + return -EINVAL; + } + + for (block = 0; block < MFS_BLOCK_COUNT(sb); block++) + { + bad = mfs_is_bad_block(sb, block); + if (bad < 0) + { + return bad; + } + + if (bad != 0) + { + continue; + } + + ret = mfs_erase_blocks(sb, block, 1); + if (ret < 0) + { + return ret; + } + + if (ret != 1) + { + return -EIO; + } + } + + return OK; +} + +/**************************************************************************** + * Name: mnemofs_format + * + * Description: + * Format the device as a fresh mnemofs volume with the specified + * superblock and root directory blocks. + * + * Input Parameters: + * sb - The mounted file system instance. + * superblock - The superblock block number. + * rootdir - The root directory block number. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_format(FAR struct mfs_sb_s *sb, mfs_t superblock, + mfs_t rootdir) +{ + int ret; + + sb->version = MFS_FORMAT_VERSION_MAJOR; + ret = mnemofs_erase_good_blocks(sb); + if (ret < 0) + { + return ret; + } + + ret = mnemofs_format_rootdir(sb, rootdir); + if (ret < 0) + { + return ret; + } + + return mnemofs_write_superblock(sb, superblock, rootdir); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mfs_update_rootdir + * + * Description: + * Persist a replacement root directory block into both the root directory + * metadata page and the on-flash superblock. + * + * Input Parameters: + * sb - The mounted file system instance. + * rootdir - The new root directory block number. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_update_rootdir(FAR struct mfs_sb_s *sb, mfs_t rootdir) +{ + int ret; + + if (sb == NULL) + { + ferr("invalid sb\n"); + return -EINVAL; + } + + ret = mnemofs_format_rootdir(sb, rootdir); + if (ret < 0) + { + ferr("mnemofs_format_rootdir failed: %d\n", ret); + return ret; + } + + ret = mnemofs_write_superblock(sb, sb->superblock, rootdir); + if (ret < 0) + { + ferr("mnemofs_write_superblock failed: %d\n", ret); + return ret; + } + + sb->rootdir = rootdir; + finfo("updated rootdir\n"); + return OK; +} + +/**************************************************************************** + * Name: mnemofs_bind + * + * Description: + * Bind an MTD device to mnemofs. This captures geometry, allocates the + * shared read/write buffer, parses mount options, validates or formats + * the on-flash superblock, and initializes allocator state for the + * mounted volume. + * + * Input Parameters: + * driver - The backing MTD inode. + * data - Optional mount options string. Supported options (strings) are + * "autoformat" and "forceformat". + * handle - To be updated with the file system's state (sb). + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_bind(FAR struct inode *driver, FAR const void *data, + FAR void **handle) +{ + FAR struct mfs_sb_s *sb; + FAR const char *mountopt; + int mountoptid; + mfs_t superblock; + mfs_t rootdir; + int ret; + + if (driver == NULL) + { + ferr("The driver is NULL\n"); + return -EINVAL; + } + + if (handle == NULL) + { + ferr("The handle is NULL\n"); + return -EINVAL; + } + + if (!INODE_IS_MTD(driver) || driver->u.i_mtd == NULL) + { + ferr("The required driver not found.\n"); + return -ENODEV; + } + + sb = kmm_zalloc(sizeof(*sb)); + if (sb == NULL) + { + ferr("Could not allocated sb\n"); + return -ENOMEM; + } + + finfo("sb allocated: %p\n", sb); + + ret = nxmutex_init(&sb->lock); + if (ret < 0) + { + ferr("Could not initialize fs mutex\n"); + goto errout_with_sb; + } + + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("Could not acquire fs mutex\n"); + goto errout_with_mutex; + } + + sb->driver = driver; + sb->mtd = driver->u.i_mtd; + ret = MTD_IOCTL(sb->mtd, MTDIOC_GEOMETRY, + (unsigned long)(uintptr_t)&sb->geo); + if (ret < 0) + { + ferr("Could not get device geometry\n"); + goto errout_with_lock; + } + + sb->erasestate = 0xff; + ret = MTD_IOCTL(sb->mtd, MTDIOC_ERASESTATE, + (unsigned long)(uintptr_t)&sb->erasestate); + if (ret < 0 && ret != -ENOTTY) + { + ferr("Could not get erase state\n"); + goto errout_with_lock; + } + + if (sb->geo.blocksize == 0 || sb->geo.erasesize == 0 || + sb->geo.erasesize % sb->geo.blocksize != 0) + { + ferr("Invalid geometry\n"); + + ret = -EINVAL; + goto errout_with_lock; + } + + sb->pagesperblk = sb->geo.erasesize / sb->geo.blocksize; + if (sb->pagesperblk < 2) + { + ferr("Number of pages is too low in MTD device\n"); + ret = -EINVAL; + goto errout_with_lock; + } + + sb->rwbuf = kmm_malloc(MFS_PAGE_SIZE(sb)); + if (sb->rwbuf == NULL) + { + ferr("Could not allocated read/write buffer for fs\n"); + ret = -ENOMEM; + goto errout_with_lock; + } + + memset(sb->rwbuf, 0, MFS_PAGE_SIZE(sb)); + sb->rwpage = MFS_LOCATION_INVALID; + sb->rwvalid = false; + sb->rwdirty = false; + sb->version = MFS_FORMAT_VERSION_MAJOR; + + mountopt = data; + mountoptid = MFS_MOUNTOPT_NONE; + if (mountopt != NULL && mountopt[0] != '\0') + { + if (strcmp(mountopt, "autoformat") == 0) + { + finfo("Mount option set: autoformat\n"); + mountoptid = MFS_MOUNTOPT_AUTOFORMAT; + } + else if (strcmp(mountopt, "forceformat") == 0) + { + finfo("Mount option set: forceformat\n"); + mountoptid = MFS_MOUNTOPT_FORCEFORMAT; + } + else + { + ferr("Invalid mount option\n"); + ret = -EINVAL; + goto errout_with_lock; + } + } + else + { + finfo("Mount option set: none (default)\n"); + } + + ret = mnemofs_find_next_good_block(sb, 0, &superblock); + if (ret < 0) + { + ferr("Could not locate superblock\n"); + goto errout_with_lock; + } + + sb->superblock = superblock; + if (mountoptid == MFS_MOUNTOPT_FORCEFORMAT) + { + /* Force format the disk with mnemofs */ + + ret = mnemofs_find_initial_rootdir(sb, superblock, &rootdir); + if (ret < 0) + { + ferr("Could not locate initial root directory block\n"); + goto errout_with_lock; + } + + sb->rootdir = rootdir; + ret = mnemofs_format(sb, superblock, rootdir); + if (ret < 0) + { + ferr("Could not force format volume\n"); + goto errout_with_lock; + } + } + else + { + ret = mnemofs_read_superblock(sb, superblock, &rootdir); + if (ret < 0) + { + if (ret == -EINVAL && mountoptid == MFS_MOUNTOPT_AUTOFORMAT) + { + ret = mnemofs_find_initial_rootdir(sb, superblock, &rootdir); + if (ret < 0) + { + ferr("Could not locate initial root directory block\n"); + goto errout_with_lock; + } + + sb->rootdir = rootdir; + ret = mnemofs_format(sb, superblock, rootdir); + if (ret < 0) + { + ferr("Could not auto format volume\n"); + goto errout_with_lock; + } + } + else + { + if (ret != -EPROTONOSUPPORT) + { + ferr("Volume is not formatted as mnemofs\n"); + } + + goto errout_with_lock; + } + } + else + { + sb->rootdir = rootdir; + } + } + + ret = mfs_alloc_init(sb); + if (ret < 0) + { + ferr("Could not initialize page allocator\n"); + goto errout_with_lock; + } + + list_initialize(&sb->ofiles); + * handle = sb; + mnemofs_unlock(sb); + return OK; + +errout_with_lock: + mfs_alloc_uninit(sb); + mnemofs_unlock(sb); + kmm_free(sb->rwbuf); +errout_with_mutex: + nxmutex_destroy(&sb->lock); +errout_with_sb: + kmm_free(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_unbind + * + * Description: + * Tear down a mounted mnemofs instance once no open files remain. + * + * Input Parameters: + * handle - The bound file system state. + * driver - The location to receive the backing MTD inode. + * flags - Unbind flags from the VFS. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_unbind(FAR void *handle, FAR struct inode **driver, + unsigned int flags) +{ + FAR struct mfs_sb_s *sb = handle; + int ret; + + switch (flags) + { + default: + break; + } + + if (sb == NULL || driver == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + if (!list_is_empty(&sb->ofiles)) + { + ferr("open files still active\n"); + ret = -EBUSY; + goto errout_with_lock; + } + + * driver = sb->driver; + mfs_alloc_uninit(sb); + mnemofs_unlock(sb); + kmm_free(sb->rwbuf); + nxmutex_destroy(&sb->lock); + kmm_free(sb); + return OK; + +errout_with_lock: + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_statfs + * + * Description: + * Return file-system-wide statistics for one mounted mnemofs instance. + * + * Input Parameters: + * mountpt - The mountpoint inode. + * buf - The location to receive the translated statfs data. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf) +{ + FAR struct mfs_sb_s *sb; + int ret; + + if (buf == NULL) + { + ferr("invalid statfs buffer\n"); + return -EINVAL; + } + + sb = mnemofs_mountpt_to_sb(mountpt); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + memset(buf, 0, sizeof(*buf)); + buf->f_type = MNEMOFS_SUPER_MAGIC; + buf->f_bsize = MFS_PAGE_SIZE(sb); + buf->f_blocks = MFS_BLOCK_COUNT(sb); + buf->f_bfree = MFS_BLOCK_COUNT(sb) > sb->superblock + 1 ? + MFS_BLOCK_COUNT(sb) - sb->superblock - 1 : 0; + buf->f_bavail = buf->f_bfree; + buf->f_namelen = MFS_NAME_MAX; + mnemofs_unlock(sb); + return OK; +} + +/**************************************************************************** + * Name: mnemofs_open + * + * Description: + * VFS open wrapper for regular files in mnemofs. + * + * Input Parameters: + * filep - The VFS file structure to initialize. + * relpath - The relative path to open. + * oflags - Open flags. + * mode - Create mode used with O_CREAT. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_file_to_sb(filep); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_file_open(sb, filep, relpath, oflags, mode); + if (ret < 0) + { + ferr("mfs_file_open failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_close + * + * Description: + * VFS close wrapper for regular files in mnemofs. + * + * Input Parameters: + * filep - The VFS file structure to close. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_close(FAR struct file *filep) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_file_to_sb(filep); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_file_close(sb, filep); + if (ret < 0) + { + ferr("mfs_file_close failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_read + * + * Description: + * VFS read wrapper for regular files in mnemofs. + * + * Input Parameters: + * filep - The VFS file structure to read from. + * buffer - The caller's read buffer. + * buflen - The number of bytes requested. + * + * Returned Value: + * A non-negative byte count is returned on success. A negated errno + * value is returned on failure. + * + ****************************************************************************/ + +static ssize_t mnemofs_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct mfs_sb_s *sb; + ssize_t ret; + + sb = mnemofs_file_to_sb(filep); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %zd\n", ret); + return ret; + } + + ret = mfs_file_read(sb, filep, buffer, buflen); + if (ret < 0) + { + ferr("mfs_file_read failed: %zd\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_write + * + * Description: + * VFS write wrapper for regular files in mnemofs. + * + * Input Parameters: + * filep - The VFS file structure to write to. + * buffer - The source bytes to write. + * buflen - The number of bytes to write. + * + * Returned Value: + * A non-negative byte count is returned on success. A negated errno + * value is returned on failure. + * + ****************************************************************************/ + +static ssize_t mnemofs_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen) +{ + FAR struct mfs_sb_s *sb; + ssize_t ret; + + sb = mnemofs_file_to_sb(filep); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %zd\n", ret); + return ret; + } + + ret = mfs_file_write(sb, filep, buffer, buflen); + if (ret < 0) + { + ferr("mfs_file_write failed: %zd\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_seek + * + * Description: + * VFS seek wrapper for regular files in mnemofs. + * + * Input Parameters: + * filep - The VFS file structure to reposition. + * offset - The seek offset. + * whence - The seek base selector. + * + * Returned Value: + * The new file position is returned on success. A negated errno value is + * returned on failure. + * + ****************************************************************************/ + +static off_t mnemofs_seek(FAR struct file *filep, off_t offset, int whence) +{ + FAR struct mfs_sb_s *sb; + off_t ret; + + sb = mnemofs_file_to_sb(filep); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %lld\n", (long long)ret); + return ret; + } + + ret = mfs_file_seek(sb, filep, offset, whence); + if (ret < 0) + { + ferr("mfs_file_seek failed: %lld\n", (long long)ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_ioctl + * + * Description: + * VFS ioctl wrapper for regular files in mnemofs. + * + * Input Parameters: + * filep - The VFS file structure to operate on. + * cmd - The ioctl command. + * arg - The ioctl argument. + * + * Returned Value: + * Zero (OK) or another command-specific value is returned on success. A + * negated errno value is returned on failure. + * + ****************************************************************************/ + +static int mnemofs_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_file_to_sb(filep); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_file_ioctl(sb, filep, cmd, arg); + if (ret < 0) + { + ferr("mfs_file_ioctl failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_truncate + * + * Description: + * VFS truncate wrapper for regular files in mnemofs. + * + * Input Parameters: + * filep - The VFS file structure to truncate. + * length - The new stored file length. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_truncate(FAR struct file *filep, off_t length) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_file_to_sb(filep); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_file_truncate(sb, filep, length); + if (ret < 0) + { + ferr("mfs_file_truncate failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_sync + * + * Description: + * VFS sync wrapper for regular files in mnemofs. + * + * Input Parameters: + * filep - The VFS file structure to sync. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_sync(FAR struct file *filep) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_file_to_sb(filep); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_file_sync(sb, filep); + if (ret < 0) + { + ferr("mfs_file_sync failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_dup + * + * Description: + * VFS dup wrapper for regular files in mnemofs. + * + * Input Parameters: + * oldp - The existing VFS file structure. + * newp - The new VFS file structure to initialize. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_dup(FAR const struct file *oldp, FAR struct file *newp) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_file_to_sb(oldp); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_file_dup(sb, oldp, newp); + if (ret < 0) + { + ferr("mfs_file_dup failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_fstat + * + * Description: + * VFS fstat wrapper for regular files in mnemofs. + * + * Input Parameters: + * filep - The VFS file structure to inspect. + * buf - The location to receive the translated stat data. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_fstat(FAR const struct file *filep, FAR struct stat *buf) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_file_to_sb(filep); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_file_fstat(sb, filep, buf); + if (ret < 0) + { + ferr("mfs_file_fstat failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_opendir + * + * Description: + * VFS opendir wrapper for directories in mnemofs. + * + * Input Parameters: + * mountpt - The mountpoint inode. + * relpath - The relative path of the directory to open. + * dir - The location to receive the opened directory stream. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_opendir(FAR struct inode *mountpt, + FAR const char *relpath, + FAR struct fs_dirent_s **dir) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_mountpt_to_sb(mountpt); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_dir_opendir(sb, relpath, dir); + if (ret < 0) + { + ferr("mfs_dir_opendir failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_closedir + * + * Description: + * VFS closedir wrapper for directories in mnemofs. + * + * Input Parameters: + * mountpt - The mountpoint inode. + * dir - The directory stream to close. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_closedir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_mountpt_to_sb(mountpt); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_dir_closedir(sb, dir); + if (ret < 0) + { + ferr("mfs_dir_closedir failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_readdir + * + * Description: + * VFS readdir wrapper for directories in mnemofs. + * + * Input Parameters: + * mountpt - The mountpoint inode. + * dir - The directory stream to read. + * entry - The location to receive the next dirent record. + * + * Returned Value: + * Zero (OK) is returned on success. -ENOENT is returned at end of + * directory. A negated errno value is returned on other failures. + * + ****************************************************************************/ + +static int mnemofs_readdir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir, + FAR struct dirent *entry) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_mountpt_to_sb(mountpt); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_dir_readdir(sb, dir, entry); + if (ret < 0 && ret != -ENOENT) + { + ferr("mfs_dir_readdir failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_rewinddir + * + * Description: + * VFS rewinddir wrapper for directories in mnemofs. + * + * Input Parameters: + * mountpt - The mountpoint inode. + * dir - The directory stream to rewind. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_rewinddir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_mountpt_to_sb(mountpt); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_dir_rewinddir(sb, dir); + if (ret < 0) + { + ferr("mfs_dir_rewinddir failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_unlink + * + * Description: + * VFS unlink wrapper for regular files in mnemofs. + * + * Input Parameters: + * mountpt - The mountpoint inode. + * relpath - The relative path of the file to remove. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_unlink(FAR struct inode *mountpt, FAR const char *relpath) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_mountpt_to_sb(mountpt); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_dir_unlink(sb, relpath); + if (ret < 0) + { + ferr("mfs_dir_unlink failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_mkdir + * + * Description: + * VFS mkdir wrapper for directories in mnemofs. + * + * Input Parameters: + * mountpt - The mountpoint inode. + * relpath - The relative path of the directory to create. + * mode - The requested directory mode. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_mkdir(FAR struct inode *mountpt, FAR const char *relpath, + mode_t mode) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_mountpt_to_sb(mountpt); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_dir_mkdir(sb, relpath, mode); + if (ret < 0) + { + ferr("mfs_dir_mkdir failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_rmdir + * + * Description: + * VFS rmdir wrapper for directories in mnemofs. + * + * Input Parameters: + * mountpt - The mountpoint inode. + * relpath - The relative path of the directory to remove. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_rmdir(FAR struct inode *mountpt, FAR const char *relpath) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_mountpt_to_sb(mountpt); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_dir_rmdir(sb, relpath); + if (ret < 0) + { + ferr("mfs_dir_rmdir failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_rename + * + * Description: + * VFS rename wrapper for file system objects in mnemofs. + * + * Input Parameters: + * mountpt - The mountpoint inode. + * oldrelpath - The current relative path. + * newrelpath - The replacement relative path. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_rename(FAR struct inode *mountpt, + FAR const char *oldrelpath, + FAR const char *newrelpath) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_mountpt_to_sb(mountpt); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_dir_rename(sb, oldrelpath, newrelpath); + if (ret < 0) + { + ferr("mfs_dir_rename failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + +/**************************************************************************** + * Name: mnemofs_stat + * + * Description: + * VFS stat wrapper for file system objects in mnemofs. + * + * Input Parameters: + * mountpt - The mountpoint inode. + * relpath - The relative path to inspect. + * buf - The location to receive the translated stat data. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mnemofs_stat(FAR struct inode *mountpt, FAR const char *relpath, + FAR struct stat *buf) +{ + FAR struct mfs_sb_s *sb; + int ret; + + sb = mnemofs_mountpt_to_sb(mountpt); + ret = mnemofs_lock(sb); + if (ret < 0) + { + ferr("mnemofs_lock failed: %d\n", ret); + return ret; + } + + ret = mfs_dir_stat(sb, relpath, buf); + if (ret < 0) + { + ferr("mfs_dir_stat failed: %d\n", ret); + } + + mnemofs_unlock(sb); + return ret; +} + /**************************************************************************** * Public Data ****************************************************************************/ -/**************************************************************************** - * Name: g_mnemofs_operations - * - * Description: - * The global list of VFS methods implemented by mnemofs - * - ****************************************************************************/ - const struct mountpt_operations g_mnemofs_operations = { mnemofs_open, /* open */ @@ -191,21 +1935,17 @@ const struct mountpt_operations g_mnemofs_operations = NULL, /* poll */ NULL, /* readv */ NULL, /* writev */ - mnemofs_sync, /* sync */ mnemofs_dup, /* dup */ mnemofs_fstat, /* fstat */ NULL, /* fchstat */ - mnemofs_opendir, /* opendir */ mnemofs_closedir, /* closedir */ mnemofs_readdir, /* readdir */ mnemofs_rewinddir, /* rewinddir */ - mnemofs_bind, /* bind */ mnemofs_unbind, /* unbind */ mnemofs_statfs, /* statfs */ - mnemofs_unlink, /* unlink */ mnemofs_mkdir, /* mkdir */ mnemofs_rmdir, /* rmdir */ @@ -213,2463 +1953,3 @@ const struct mountpt_operations g_mnemofs_operations = mnemofs_stat, /* stat */ NULL /* chstat */ }; - -/**************************************************************************** - * Private Functions - ****************************************************************************/ - -/**************************************************************************** - * Name: mnemofs_open - * - * Description: - * Open a file given its path and populate its file pointer. The file - * pointer's private field is filled with information about the open file. - * See `open(2)` for details on the work and parameters of this function. - * - * File pointers' private member have two parts...`mfs_ofd_d` is the actual - * entry for the file descriptor (fd), while the inner part is a reference - * counted shared portion formed from `mfs_ocom_s`. This way, multiple fds - * can point to the same file without problems. The shared portion is - * shared among the fds of the same file, while the upper part is different - * for each fd, regardless of whether they belong to the same file or not. - * A list of upper parts of files is stored in `sb` as a kernel linked - * list. - * - * Input Parameters: - * filep - File pointer. - * relpath - Relative path to the file from the mount root. - * oflags - Flags for opening the file. - * mode - Access Mode (User|Group|All) if creating the file. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int mnemofs_open(FAR struct file *filep, FAR const char *relpath, - int oflags, mode_t mode) -{ - int ret = OK; - int flags; - FAR const char *child = NULL; - FAR struct inode *inode; - struct mfs_pitr_s pitr; - FAR struct mfs_sb_s *sb; - FAR struct mfs_ofd_s *f; - FAR struct mfs_ocom_s *fcom; - FAR struct mfs_dirent_s *dirent = NULL; - - MFS_LOG("OPEN", "Entry."); - MFS_LOG("OPEN", "Requested path is %s.", relpath); - MFS_LOG("OPEN", "Flags set as 0x%x.", oflags); - MFS_LOG("OPEN", "Mode set as 0x%x.", mode); - - inode = filep->f_inode; - DEBUGASSERT(inode != NULL); - sb = inode->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (predict_false(ret < 0)) - { - MFS_LOG("OPEN", "Failed to acquire mutex."); - goto errout; - } - else - { - MFS_EXTRA_LOG("OPEN", "Mutex acquired."); - } - - f = fs_heap_zalloc(sizeof(*f)); - if (predict_false(f == NULL)) - { - MFS_LOG("OPEN", "Failed to allocate file structure."); - ret = -ENOMEM; - goto errout_with_lock; - } - else - { - MFS_EXTRA_LOG("OPEN", "Allocated file structure at %p.", f); - } - - fcom = fs_heap_zalloc(sizeof(*fcom)); - if (predict_false(fcom == NULL)) - { - MFS_LOG("OPEN", "Failed to allocate common file structure."); - ret = -ENOMEM; - goto errout_with_f; - } - else - { - MFS_EXTRA_LOG("OPEN", "Allocated common file structure at %p.", fcom); - } - - f->com = fcom; - f->com->refcount++; - - /* Check creation flags. */ - - flags = mfs_get_patharr(sb, relpath, &f->com->path, &f->com->depth); - MFS_EXTRA_LOG("OPEN", "Retrieved flags are 0x%x.", flags); - - if ((flags & MFS_EXIST) == 0) - { - MFS_EXTRA_LOG("OPEN", "Path doesn't exists."); - - if ((flags & MFS_P_ISDIR) != 0) - { - MFS_EXTRA_LOG("OPEN", "Parent is a directory."); - - if ((oflags & O_CREAT) != 0) - { - MFS_EXTRA_LOG("OPEN", "Creation flag is set."); - - /* Add direntry to parent's directory file. */ - - f->com->new_ent = true; - - mfs_pitr_init(sb, f->com->path, f->com->depth, &pitr, true); - child = mfs_path2childname(relpath); - - MFS_EXTRA_LOG("OPEN", "The final child is \"%s\".", child); - - mfs_pitr_appendnew(sb, f->com->path, f->com->depth, &pitr, - child, mode); - mfs_pitr_free(&pitr); - - MFS_EXTRA_LOG("OPEN", "Added child direntry."); - - /* OK */ - - MFS_EXTRA_LOG("OPEN", "All OK"); - } - } - else - { - MFS_LOG("OPEN", "Ancestor is not a directory."); - - ret = -ENOTDIR; - goto errout_with_fcom; - } - } - else - { - MFS_EXTRA_LOG("OPEN", "Path exists."); - - if ((flags & MFS_ISFILE) != 0) - { - MFS_EXTRA_LOG("OPEN", "Path points to a file."); - - /* NOTE: O_DIRECTORY is not supported. Use opendir. */ - - if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) - { - MFS_LOG("OPEN", "O_CREAT and O_EXCL flag are set."); - MFS_LOG("OPEN", "Operation failed as file exists with O_EXCL" - "flag set."); - - ret = -EEXIST; - goto errout_with_fcom; - } - else - { - /* OK */ - - MFS_EXTRA_LOG("OPEN", "All OK"); - } - } - else - { - MFS_EXTRA_LOG("OPEN", "Path points to a directory."); - - ret = -EISDIR; - goto errout_with_fcom; - } - } - - /* Check r/w permission flags. */ - - /* TODO: Update mtime and atime. */ - - mfs_pitr_init(sb, f->com->path, f->com->depth, &pitr, true); - mfs_pitr_readdirent(sb, f->com->path, &pitr, &dirent); - - if (dirent != NULL) - { - MFS_EXTRA_LOG_DIRENT(dirent); - - DEBUGASSERT((flags & MFS_EXIST) != 0); - - MFS_EXTRA_LOG("OPEN", "Direntry exists at %p.", dirent); - - if ((oflags & O_WRONLY) != 0 && (dirent->mode & O_WRONLY) == 0) - { - MFS_LOG("OPEN", "Write is not allowed."); - - ret = -EACCES; - goto errout_with_dirent; - } - - /* man page says: The argument flags must include one of the following - * access modes: O_RDONLY, O_WRONLY, or O_RDWR. - */ - - mfs_free_dirent(dirent); - dirent = NULL; - } - else - { - DEBUGASSERT((flags & MFS_EXIST) == 0); - } - - f->com->sz = mfs_get_fsz(sb, f->com->path, f->com->depth); - f->com->off = 0; - f->com->oflags = oflags; - - mfs_pitr_free(&pitr); - - MFS_EXTRA_LOG("OPEN", "Direntry processing is done."); - - /* Check Offset flags. */ - - if ((oflags & (O_TRUNC | O_WRONLY)) == (O_TRUNC | O_WRONLY) || - (oflags & (O_TRUNC | O_RDWR)) == (O_TRUNC | O_RDWR)) - { - /* Truncate to size 0. If write and truncate are mentioned only - * then it's truncated. Else, the truncate flag is ignored. - */ - - MFS_EXTRA_LOG("OPEN", "O_TRUNC is set."); - - ret = mfs_lru_del(sb, 0, f->com->sz, f->com->path, f->com->depth); - if (predict_false(ret < 0)) - { - MFS_LOG("OPEN", "Could not truncate file."); - goto errout_with_dirent; - } - else - { - MFS_EXTRA_LOG("OPEN", "File truncated."); - } - } - - if ((oflags & O_APPEND) != 0) - { - MFS_EXTRA_LOG("OPEN", "Append flag is set."); - f->com->off = f->com->sz; - } - - MFS_EXTRA_LOG_F(f); - - list_add_tail(&MFS_OFILES(sb), &f->list); - filep->f_priv = f; - MFS_EXTRA_LOG("OPEN", "File structure is set at %p.", filep); - - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("OPEN", "Mutex released."); - - finfo("Mnemofs open exited with %d.", ret); - return ret; - -errout_with_dirent: - if (dirent != NULL) - { - mfs_free_dirent(dirent); - } - - mfs_free_patharr(fcom->path); - -errout_with_fcom: - fs_heap_free(fcom); - -errout_with_f: - fs_heap_free(f); - -errout_with_lock: - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("OPEN", "Mutex released."); - -errout: - MFS_LOG("OPEN", "Exit | Return: %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_close - * - * Description: - * Closes a file, and frees up the allocated space for the open file's - * representation in the file pointer's private field. mnemofs also syncs - * up the on-flash data after closing of a file (unlike a typical fs as - * mentioned in `man`). This is to be prepared for random power loss at all - * possible times. See `close(2)` for details on the work and parameters - * of this function. - * - * Input Parameters: - * filep - File pointer. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int mnemofs_close(FAR struct file *filep) -{ - int ret = OK; - FAR struct inode *inode; - FAR struct mfs_sb_s *sb; - FAR struct mfs_ofd_s *f; - - MFS_LOG("CLOSE", "Entry."); - MFS_LOG("CLOSE", "File structure is at %p.", filep); - - inode = filep->f_inode; - DEBUGASSERT(inode != NULL); - sb = inode->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (predict_false(ret < 0)) - { - MFS_LOG("CLOSE", "Failed to acquire mutex."); - goto errout; - } - else - { - MFS_EXTRA_LOG("CLOSE", "Mutex acquired."); - } - - f = filep->f_priv; - DEBUGASSERT(f != NULL); - - MFS_EXTRA_LOG_F(f); - - /* Flushing in-memory data to on-flash journal. */ - - f->com->refcount--; - - MFS_EXTRA_LOG("CLOSE", "Reference Counter updated."); - MFS_EXTRA_LOG_F(f); - - if (f->com->refcount == 0) - { - MFS_EXTRA_LOG("CLOSE", "Reference Counter is 0."); - - ret = mnemofs_flush(sb); - if (predict_false(ret < 0)) - { - MFS_LOG("CLOSE", "Could not flush file system."); - - /* This could be problematic. - * TODO: For now, this is same as no-op. - */ - - f->com->refcount++; /* Revert to old refcount. */ - goto errout_with_lock; - } - else - { - MFS_EXTRA_LOG("CLOSE", "File system flushed."); - } - - fs_heap_free(f->com->path); - MFS_EXTRA_LOG("CLOSE", "Freed file structure path."); - - fs_heap_free(f->com); - MFS_EXTRA_LOG("CLOSE", "Freed common file structure."); - } - - list_delete(&f->list); - MFS_EXTRA_LOG("CLOSE", "Removed file structure from open files list."); - - fs_heap_free(f); - MFS_EXTRA_LOG("CLOSE", "Freed file structure."); - - filep->f_priv = NULL; - -errout_with_lock: - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("CLOSE", "Mutex released."); - -errout: - MFS_LOG("CLOSE", "Exit | Return: %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_read - * - * Description: - * This reads an open file at the current offset into the provided `buffer` - * and puts in at most `buflen` bytes of data from the file. See `read(2)` - * for details on the work and parameters of this function. - * - * Files (and directories) are just inverted CTZ skip lists. The CTZ - * methods provide a seamless interface to abstract away the data structure - * to provide an interface which allows the caller to only focus on the - * offset of the data from the start of the file, and not worry about the - * underlying details, pointers, blocks/pages, etc. - * - * Input Parameters: - * filep - File pointer. - * buffer - Buffer where to put read data. - * buflen - Length of the buffer. - * - * Returned Value: - * 0 - OK - * <= 0 - Bytes read. - * - ****************************************************************************/ - -static ssize_t mnemofs_read(FAR struct file *filep, FAR char *buffer, - size_t buflen) -{ - ssize_t ret = 0; - FAR struct inode *inode; - FAR struct mfs_sb_s *sb; - FAR struct mfs_ofd_s *f; - - MFS_LOG("READ", "Entry."); - MFS_LOG("READ", "File structure is at %p.", filep); - MFS_LOG("READ", "Buffer is at %p.", buffer); - MFS_LOG("READ", "Length of the buffer is %zu.", buflen); - - inode = filep->f_inode; - DEBUGASSERT(inode != NULL); - sb = inode->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (predict_false(ret < 0)) - { - MFS_LOG("READ", "Failed to acquire mutex."); - goto errout; - } - else - { - MFS_EXTRA_LOG("READ", "Mutex acquired."); - } - - f = filep->f_priv; - DEBUGASSERT(f != NULL); - - MFS_EXTRA_LOG_F(f); - - /* Check if allowed to read. */ - - if ((f->com->oflags & O_RDONLY) == 0) - { - MFS_EXTRA_LOG("READ", "Not allowed to read file."); - - ret = -EINVAL; - goto errout_with_lock; - } - - /* Read data in CTZ from the current offset. */ - - buflen = MIN(buflen, f->com->sz - f->com->off); /* TODO: Need to consider - * if this needs to be - * lower down the chain. - */ - - MFS_EXTRA_LOG("READ", "Final buffer length is %zu.", buflen); - - ret = mfs_lru_rdfromoff(sb, f->com->off, f->com->path, f->com->depth, - buffer, buflen); - if (ret < 0) - { - MFS_EXTRA_LOG("READ", "Could not read."); - goto errout_with_lock; - } - - ret = buflen; - - /* Update offset. */ - - f->com->off += buflen; - MFS_EXTRA_LOG("READ", "File structure offset updated."); - MFS_EXTRA_LOG_F(f); - -errout_with_lock: - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("READ", "Mutex released."); - -errout: - MFS_LOG("READ", "Exit | Return: %zd.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_write - * - * Description: - * This writes to an open file at the current offset from the provided - * `buffer` and puts in at most `buflen` bytes of data from the buffer. See - * `write(2)` for details on the work and parameters of this function. - * - * Input Parameters: - * filep - File pointer. - * buffer - Buffer from where to write data. - * buflen - Length of the buffer. - * - * Returned Value: - * 0 - OK - * <= 0 - Bytes written. - * - ****************************************************************************/ - -static ssize_t mnemofs_write(FAR struct file *filep, FAR const char *buffer, - size_t buflen) -{ - ssize_t ret = OK; - FAR struct inode *inode; - FAR struct mfs_sb_s *sb; - FAR struct mfs_ofd_s *f; - - MFS_LOG("WRITE", "Entry."); - MFS_LOG("WRITE", "File structure is at %p.", filep); - MFS_LOG("WRITE", "Buffer is at %p.", buffer); - MFS_LOG("WRITE", "Length of the buffer is %zu.", buflen); - - inode = filep->f_inode; - DEBUGASSERT(inode != NULL); - sb = inode->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (predict_false(ret < 0)) - { - MFS_LOG("WRITE", "Failed to acquire mutex."); - goto errout; - } - else - { - MFS_EXTRA_LOG("WRITE", "Mutex acquired."); - } - - f = filep->f_priv; - DEBUGASSERT(f != NULL); - - MFS_EXTRA_LOG_F(f); - - /* Check if allowed to write. */ - - if ((f->com->oflags & O_WRONLY) == 0) - { - MFS_EXTRA_LOG("WRITE", "Write not allowed."); - ret = -EINVAL; - goto errout_with_lock; - } - - /* Write data to CTZ at the current offset. */ - - ret = mfs_lru_wr(sb, f->com->off, buflen, f->com->path, f->com->depth, - buffer); - if (predict_false(ret < 0)) - { - MFS_LOG("WRITE", "Could not write to LRU."); - goto errout_with_lock; - } - else - { - MFS_EXTRA_LOG("WRITE", "Completed writing to LRU."); - } - - /* Update offset and size. */ - - f->com->off += buflen; - f->com->sz = MAX(f->com->sz, f->com->off); - ret = buflen; - - MFS_EXTRA_LOG("WRITE", "Updated file offset and size."); - MFS_EXTRA_LOG_F(f); - -errout_with_lock: - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("WRITE", "Mutex released."); - -errout: - MFS_LOG("WRITE", "Exit | Return: %zd.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_seek - * - * Description: - * Repositions the current offset of the file pointed by the file - * descriptor. See `lseek(2)` for details on the work and parameters of - * this function. - * - * The new offset may be beyond the current file size. If that's the case, - * then on any subsequent writes, it will be such that there are bytes with - * '\0' in the gap/hole. In mnemofs, the whole hole situation is that the - * LRU doesn't know about the existence of holes, but committing a file - * from LRU to the journal does write all the holes to the flash. - * - * Input Parameters: - * filep - File pointer. - * offset - Offset. - * whence - From where the offset should be applied. - * - * Returned Value: - * 0 - OK - * <= 0 - Final position. - * - ****************************************************************************/ - -static off_t mnemofs_seek(FAR struct file *filep, off_t offset, int whence) -{ - int ret = OK; - mfs_t pos; - FAR struct inode *inode; - FAR struct mfs_sb_s *sb; - FAR struct mfs_ofd_s *f; - - MFS_LOG("SEEK", "Entry."); - MFS_LOG("SEEK", "Offset: %u, Whence: %d", offset, whence); - - inode = filep->f_inode; - DEBUGASSERT(inode != NULL); - sb = inode->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - MFS_LOG("SEEK", "Failed to acquire mutex."); - goto errout; - } - else - { - MFS_EXTRA_LOG("SEEK", "Mutex acquired."); - } - - f = filep->f_priv; - DEBUGASSERT(f != NULL); - - MFS_EXTRA_LOG("SEEK", "File offset: %u, Command offset: %u, Whence %d.", - f->com->off, offset, whence); - - pos = f->com->off; - switch (whence) - { - case SEEK_SET: - pos = offset; - break; - - case SEEK_CUR: - pos += offset; - break; - - case SEEK_END: - pos = f->com->sz + offset; - break; - - default: - ret = -EINVAL; - goto errout_with_lock; - } - - MFS_EXTRA_LOG("SEEK", "Proposed final position: %" PRIu32, pos); - - /* Check bounds of the position data type. */ - - if (pos > f->com->off && offset < 0) - { - MFS_LOG("SEEK", "Proposed final position (%" PRIu32 ") out of bounds.", - pos); - ret = -EINVAL; - goto errout_with_lock; - } - - f->com->off = pos; - ret = pos; - - MFS_EXTRA_LOG("SEEK", "Final position: %" PRIu32, pos); - -errout_with_lock: - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("SEEK", "Mutex released."); - -errout: - MFS_LOG("SEEK", "Exit | Return: %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_ioctl - * - * Description: - * Allows caller to directly control the underlying storage device. See - * `ioctl(2)` for details on the work and parameters of this function. - * - * Input Parameters: - * filep - File pointer. - * cmd - Command. - * arg - Arguments to the command. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int mnemofs_ioctl(FAR struct file *filep, int cmd, unsigned long arg) -{ - int ret = OK; - FAR struct inode *inode; - FAR struct inode *drv; - FAR struct mfs_sb_s *sb; - - finfo("Mnemofs ioctl with cmd %" PRIu32 "and arg %zu.", cmd, arg); - - inode = filep->f_inode; - sb = inode->i_private; - drv = sb->drv; - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - goto errout; - } - - finfo("Lock acquired."); - - if (INODE_IS_MTD(drv)) - { - ret = MTD_IOCTL(drv->u.i_mtd, cmd, arg); - } - else - { - finfo("Not an MTD driver."); - ret = -ENOTTY; - } - - nxmutex_unlock(&MFS_LOCK(sb)); - finfo("Lock released."); - -errout: - finfo("Mnemofs ioctl exited with %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_truncate - * - * Description: - * Truncates file specified to specified length. See `truncate(2)` for - * details on the work and parameters of this function. - * - * Input Parameters: - * filep - File pointer. - * length - Final size of the truncated file. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * If the length specified is bigger than the file, it won't increase the - * file size in itself. Only when there is a subsequent write to the offset - * will there be an increase in the file size. This can be changed, but is - * kept for efficiency. - * - ****************************************************************************/ - -static int mnemofs_truncate(FAR struct file *filep, off_t length) -{ - int ret = OK; - FAR struct inode *inode; - FAR struct mfs_sb_s *sb; - FAR struct mfs_ofd_s *f; - - finfo("Mnemofs truncate to length %d.", length); - - inode = filep->f_inode; - DEBUGASSERT(inode != NULL); - sb = inode->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - goto errout; - } - - finfo("Lock acquired."); - - f = filep->f_priv; - DEBUGASSERT(f != NULL); - - if (length < f->com->sz) - { - ret = mfs_lru_del(sb, length, f->com->sz - length, f->com->path, - f->com->depth); - if (predict_false(ret < 0)) - { - finfo("Error during truncate. Ret: %d.", ret); - goto errout_with_lock; - } - } - -errout_with_lock: - nxmutex_unlock(&MFS_LOCK(sb)); - finfo("Lock released."); - -errout: - finfo("Mnemofs truncate exited with %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_sync - * - * Description: - * Causes all pending modifications that are in memory for a file - * descriptor to be written to the device. See `sync(2)` for details on - * the work and parameters of this function. - * - * For mnemofs, this means that the LRU entry for the file corresponding to - * the fd is committed to the journal. - * - * Input Parameters: - * filep - File pointer. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int mnemofs_sync(FAR struct file *filep) -{ - int ret = OK; - FAR struct inode *inode; - FAR struct mfs_sb_s *sb; - FAR struct mfs_ofd_s *f; - - finfo("Mnemofs sync."); - - inode = filep->f_inode; - DEBUGASSERT(inode != NULL); - sb = inode->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - goto errout; - } - - finfo("Lock acquired."); - - f = filep->f_priv; - DEBUGASSERT(f != NULL); - - ret = mnemofs_flush(sb); - - nxmutex_unlock(&MFS_LOCK(sb)); - finfo("Lock released."); - -errout: - finfo("Mnemofs sync exited with %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_dup - * - * Description: - * Creates a duplicate file descriptor. The new and old fds must be - * interchangeable and they share file offsets and file status flags. - * See `dup(2)` for details on the work and parameters of this function. - * - * The file descriptors have file pointers behind them. In mnemofs, - * the file pointers have different upper halves. But duplicate file - * pointers share a reference counted common part among themselves. - * - * Input Parameters: - * oldp - Old file pointer. - * newp - New file pointer. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int mnemofs_dup(FAR const struct file *oldp, FAR struct file *newp) -{ - int ret = OK; - FAR struct inode *inode; - FAR struct mfs_sb_s *sb; - FAR struct mfs_ofd_s *of; - FAR struct mfs_ofd_s *nf; - - finfo("Mnemofs dup."); - - inode = oldp->f_inode; - DEBUGASSERT(inode != NULL); - sb = inode->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - goto errout; - } - - finfo("Lock acquired."); - - of = oldp->f_priv; - DEBUGASSERT(of != NULL); - - nf = fs_heap_zalloc(sizeof(*nf)); - if (predict_false(nf == NULL)) - { - finfo("No memory left."); - ret = -ENOMEM; - goto errout_with_lock; - } - - nf->com = of->com; - - /* Refcount limit. */ - - if (nf->com->refcount == UINT8_MAX) - { - finfo("Refcount limit reached."); - ret = -EMFILE; - goto errout_with_nf; - } - - nf->com->refcount++; - - /* Add the new upper half to the list of open fds. */ - - list_add_tail(&MFS_OFILES(sb), &nf->list); - newp->f_priv = nf; - finfo("New file descriptor added to the end of the list of open files."); - - nxmutex_unlock(&MFS_LOCK(sb)); - finfo("Lock released."); - return ret; - -errout_with_nf: - fs_heap_free(nf); - -errout_with_lock: - nxmutex_unlock(&MFS_LOCK(sb)); - finfo("Lock released."); - -errout: - finfo("Mnemofs dup exited with %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_fstat - * - * Description: - * Get file's status. See `fstat(2)` for details on the work and - * parameters of this function. - * - * In mnemofs, most of the metadata of a file is stored in the directory - * entry of the file inside the parent directories. The directory entries - * are stored in the form of a CTZ, and this is what a directory refers to. - * - * Input Parameters: - * filep - File pointer. - * buf - Stat result. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int mnemofs_fstat(FAR const struct file *filep, FAR struct stat *buf) -{ - int ret = OK; - struct mfs_pitr_s pitr; - FAR struct inode *inode; - FAR struct mfs_sb_s *sb; - FAR struct mfs_ofd_s *f; - FAR struct mfs_dirent_s *dirent = NULL; - - finfo("Mnemofs fstat."); - - inode = filep->f_inode; - DEBUGASSERT(inode != NULL); - sb = inode->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - goto errout; - } - - finfo("Lock acquired."); - - f = filep->f_priv; - DEBUGASSERT(f != NULL); - - mfs_pitr_init(sb, f->com->path, f->com->depth, &pitr, true); - mfs_pitr_adv_tochild(&pitr, f->com->path); - mfs_pitr_readdirent(sb, f->com->path, &pitr, &dirent); - mfs_pitr_free(&pitr); - - DEBUGASSERT(dirent != NULL); - - buf->st_mode = dirent->mode; - buf->st_nlink = 1; /* mnemofs doesn't yet support links. */ - buf->st_size = dirent->sz; - buf->st_atim = dirent->st_atim; - buf->st_ctim = dirent->st_ctim; - buf->st_blksize = sb->pg_sz; - buf->st_blocks = ((dirent->sz + (sb->pg_sz - 1)) / sb->pg_sz); /* Ceil */ - - mfs_free_dirent(dirent); - - nxmutex_unlock(&MFS_LOCK(sb)); - finfo("Lock released."); - -errout: - finfo("Mnemofs fstat exited with %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_opendir - * - * Description: - * Opens a directory for traversal. See `opendir(3)` for details on the - * work and parameters of this function. - * - * In mnemofs, the directories are in the form of a CTZ list like files, - * but the content in the list is the directory entries of its children. - * There is mfs_fsdirent->count which exists due to the fact that the first - * entry in readdir is `.`, second is `..` and then the actual contents. To - * save space, this count never goes above 2 because it's not needed as - * above 2, the offset in the CTZ list is sufficient for traversal. - * - * Input Parameters: - * mountpt - Mount point of the file system. - * relpath - Relative path of the directory. - * dir - To populate this with open directory data structure. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * There is no attempt to `unlink` or `rmdir` when a directory is open. - * This will mess up the iterator, and mnemofs does not yet store a list - * of open directories to prevent unlink or rmdir when open or something - * like that. This is yet to be tested if mnemofs can handle such a case. - * This bug is marked in code in `readdir`. - * - ****************************************************************************/ - -static int mnemofs_opendir(FAR struct inode *mountpt, - FAR const char *relpath, - FAR struct fs_dirent_s **dir) -{ - int ret = OK; - int flags; - mfs_t depth; - FAR struct mfs_sb_s *sb; - FAR struct mfs_path_s *path; - FAR struct mfs_pitr_s *pitr; - FAR struct mfs_fsdirent_s *fsdirent; - - MFS_LOG("OPENDIR", "Entry."); - MFS_LOG("OPENDIR", "Requested path is \"%s\".", relpath); - - DEBUGASSERT(mountpt != NULL); - sb = mountpt->i_private; - DEBUGASSERT(mountpt->i_private); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - MFS_LOG("OPENDIR", "Failed to acquire mutex."); - goto errout; - } - else - { - MFS_EXTRA_LOG("OPENDIR", "Mutex acquired."); - } - - flags = mfs_get_patharr(sb, relpath, &path, &depth); - if ((flags & MFS_ISDIR) == 0) - { - MFS_LOG("OPENDIR", "Not a directory."); - ret = -ENOTDIR; - goto errout_with_lock; - } - else - { - MFS_EXTRA_LOG("OPENDIR", "Path is at %p.", path); - MFS_EXTRA_LOG("OPENDIR", "Path is at %" PRIu32, depth); - MFS_EXTRA_LOG("OPENDIR", "Retrieved flags is %d.", flags); - } - - ret = mfs_lru_getupdatedinfo(sb, path, depth); - if (predict_false(ret < 0)) - { - MFS_LOG("OPENDIR", "Failed to get updated information from LRU."); - goto errout_with_path; - } - else - { - MFS_EXTRA_LOG("OPENDIR", "Got updated information from LRU."); - } - - pitr = fs_heap_zalloc(sizeof(*pitr)); - if (predict_false(pitr == NULL)) - { - MFS_LOG("OPENDIR", "Could not allocate space for pitr."); - ret = -ENOMEM; - goto errout_with_path; - } - else - { - MFS_EXTRA_LOG("OPENDIR", "Space allocated for pitr at %p.", pitr); - } - - fsdirent = fs_heap_zalloc(sizeof(*fsdirent)); - if (predict_false(fsdirent == NULL)) - { - MFS_LOG("OPENDIR", "Could not allocate space for FS Direntry."); - ret = -ENOMEM; - goto errout_with_pitr; - } - else - { - MFS_EXTRA_LOG("OPENDIR", "Space allocated for FS Direntry at %p.", - fsdirent); - } - - ret = mfs_pitr_init(sb, path, depth, pitr, false); - if (predict_false(ret < 0)) - { - MFS_LOG("OPENDIR", "Failed to initialize pitr."); - goto errout_with_fsdirent; - } - else - { - MFS_EXTRA_LOG("OPENDIR", "Pitr initialized successfully."); - } - - fsdirent->idx = 0; - fsdirent->path = path; - fsdirent->depth = depth; - fsdirent->pitr = pitr; - - MFS_EXTRA_LOG_FSDIRENT(fsdirent); - - *dir = (FAR struct fs_dirent_s *) fsdirent; - MFS_EXTRA_LOG("OPENDIR", "Directory structure is %p.", dir); - - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("OPENDIR", "Mutex released."); - - MFS_LOG("OPENDIR", "Exit | Return: %d.", ret); - return ret; - -errout_with_fsdirent: - fs_heap_free(fsdirent); - -errout_with_pitr: - fs_heap_free(pitr); - -errout_with_path: - mfs_free_patharr(path); - -errout_with_lock: - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("OPENDIR", "Mutex released."); - -errout: - MFS_LOG("OPENDIR", "Exit | Return: %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_closedir - * - * Description: - * Closes an opened directory. See `closedir(3)` for details on the - * work and parameters of this function. - * - * Input Parameters: - * mountpt - Mount point of the file system. - * dir - Open directory data structure. - * - * Returned Value: - * 0 - OK - * - ****************************************************************************/ - -static int mnemofs_closedir(FAR struct inode *mountpt, - FAR struct fs_dirent_s *dir) -{ - struct mfs_fsdirent_s *fsdirent = (struct mfs_fsdirent_s *) dir; - - MFS_LOG("CLOSEDIR", "Entry."); - MFS_LOG("CLOSEDIR", "FS Direntry at %p.", fsdirent); - - MFS_EXTRA_LOG_FSDIRENT(fsdirent); - - mfs_free_patharr(fsdirent->path); - mfs_pitr_free(fsdirent->pitr); - fs_heap_free(fsdirent->pitr); - fs_heap_free(fsdirent); - - MFS_LOG("CLOSEDIR", "Exit | Return: %d.", OK); - return OK; -} - -/**************************************************************************** - * Name: mnemofs_readdir - * - * Description: - * Gives the directory entry of where the directory iterator currently - * points to and advances the iterator to the next directory entry. See - * `readdir(3)` for details on the work and parameters of this function. - * - * The first two entries are `.` and `..` respectively. The way mnemofs - * keeps a track of the current directory entry is through offset in the - * directory entry file. This offset is an unsigned integer, and to know - * when to return the "dots" and when to return actual directory entries, - * a counter is used that gives `.` when 0, `..` when 1, and the actual - * directory entries when 2. The offset increases only when counter is 2. - * The counter never increases above 2. - * - * Input Parameters: - * mountpt - Mount point of the file system. - * dir - Open directory data structure. - * entry - To populate structure with directory entry. - * - * Returned Value: - * 0 - OK - * - ****************************************************************************/ - -static int mnemofs_readdir(FAR struct inode *mountpt, - FAR struct fs_dirent_s *dir, - FAR struct dirent *entry) -{ - int ret = OK; - FAR struct mfs_sb_s *sb; - FAR struct mfs_dirent_s *dirent; - FAR struct mfs_fsdirent_s *fsdirent = (FAR struct mfs_fsdirent_s *) dir; - - MFS_LOG("READDIR", "Entry."); - MFS_LOG("READDIR", "FS Direntry at %p.", fsdirent); - MFS_EXTRA_LOG_FSDIRENT(fsdirent); - - DEBUGASSERT(mountpt != NULL); - sb = mountpt->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - MFS_LOG("READDIR", "Failed to acquire mutex."); - goto errout; - } - else - { - MFS_EXTRA_LOG("READDIR", "Mutex acquired."); - } - - MFS_EXTRA_LOG("READDIR", "Current direntry index is %" PRIu8, - fsdirent->idx); - - if (fsdirent->idx == 0) - { - /* . */ - - MFS_EXTRA_LOG("READDIR", "Direntry for \".\""); - - snprintf(entry->d_name, NAME_MAX + 1, "."); - entry->d_type = DTYPE_DIRECTORY; - fsdirent->idx++; - - MFS_EXTRA_LOG("READDIR", "Direntry index updated to %" PRIu8, - fsdirent->idx); - goto errout_with_lock; - } - else if (fsdirent->idx == 1) - { - /* .. */ - - MFS_EXTRA_LOG("READDIR", "Direntry for \"..\""); - - snprintf(entry->d_name, NAME_MAX + 1, ".."); - entry->d_type = DTYPE_DIRECTORY; - fsdirent->idx++; - - MFS_EXTRA_LOG("READDIR", "Direntry index updated to %" PRIu8, - fsdirent->idx); - goto errout_with_lock; - } - else - { - MFS_EXTRA_LOG("READDIR", "Direntry for regular directory items."); - } - - /* TODO: Need to think why *exactly* below line is needed. The LRU node - * seems to contain wrong size during opendir, but updating it here - * updates it to correct size, even though this updatedinfo is also - * called in opendir? - */ - - ret = mfs_lru_getupdatedinfo(sb, fsdirent->path, fsdirent->depth); - if (predict_false(ret < 0)) - { - MFS_LOG("READDIR", "Failed to get updated information from LRU."); - goto errout_with_lock; - } - else - { - MFS_EXTRA_LOG("READDIR", "Got updated information from LRU."); - } - - ret = mfs_pitr_readdirent(sb, fsdirent->path, fsdirent->pitr, &dirent); - if (predict_false(ret < 0)) - { - MFS_LOG("READDIR", "Could not read direntry."); - goto errout_with_lock; - } - else if (dirent == NULL) - { - MFS_LOG("READDIR", "No more direntries left."); - MFS_LOG("READDIR", "End of directory."); - - ret = -ENOENT; - goto errout_with_lock; - } - else - { - MFS_LOG("READDIR", "Direntry retrieved."); - MFS_EXTRA_LOG_DIRENT(dirent); - } - - memset(entry->d_name, 0, NAME_MAX + 1); - MFS_EXTRA_LOG("READDIR", "Resetting entry name."); - - memcpy(entry->d_name, dirent->name, dirent->namelen); - MFS_EXTRA_LOG("READDIR", "Setting entry name to be \"%.*s\".", - dirent->namelen, dirent->name); - - entry->d_type = (S_ISDIR(dirent->mode) ? DTYPE_DIRECTORY: DTYPE_FILE); - MFS_EXTRA_LOG("READDIR", "Setting entry d_type to %" PRIu8, entry->d_type); - - mfs_pitr_adv_bydirent(fsdirent->pitr, dirent); - mfs_free_dirent(dirent); - -errout_with_lock: - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("READDIR", "Mutex released."); - -errout: - MFS_LOG("READDIR", "Exit | Return: %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_rewinddir - * - * Description: - * Rewinds the directory iterator to the start of the directory. See - * `rewinddir(3)` for details on the work and parameters of this function. - * - * In mnemofs, this means resetting offset of CTZ list of directory to 0 and - * counter to 0; - * - * Input Parameters: - * mountpt - Mount point of the file system. - * dir - Open directory data structure. - * - * Returned Value: - * 0 - OK - * - ****************************************************************************/ - -static int mnemofs_rewinddir(FAR struct inode *mountpt, - FAR struct fs_dirent_s *dir) -{ - int ret = OK; - FAR struct mfs_sb_s *sb; - struct mfs_fsdirent_s *fsdirent = (struct mfs_fsdirent_s *) dir; - - MFS_LOG("REWINDDIR", "Entry."); - MFS_LOG("REWINDDIR", "FS Direntry at %p.", fsdirent); - MFS_EXTRA_LOG_FSDIRENT(fsdirent); - - DEBUGASSERT(mountpt != NULL); - sb = mountpt->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - MFS_LOG("REWINDDIR", "Failed to acquire mutex."); - goto errout; - } - else - { - MFS_EXTRA_LOG("REWINDDIR", "Mutex acquired."); - } - - mfs_pitr_reset(fsdirent->pitr); - - fsdirent->idx = 0; - MFS_EXTRA_LOG("REWINDDIR", "Direntry index reset to 0."); - - MFS_EXTRA_LOG_FSDIRENT(fsdirent); - - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("REWINDDIR", "Mutex released."); - -errout: - MFS_LOG("REWINDDIR", "Exit | Return: %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_bind - * - * Description: - * Mounts the file system. This is responsible for initializing all the - * in-memory data structures, which may including scanning the storage - * for serialized information, or formatting the storage, depending on the - * options provided during mount. - * - * See `mount(2)` and `mount(8)` for more information. - * - * In mnemofs, the superblock is not stored on disk yet. It does not have - * any information about the current state of the device, but rather just - * the information about the storage device, which is obtained from the - * driver anyway. To know if the device is formatted, the entire device - * is scanned, block by block, for the existence of the first block of the - * journal. This is quicker than searching page by page, as there are much - * fewer blocks than pages. The first 8 bytes of the first journal block - * are a special sequence. The journal contains the location of the master - * node (and vice versa, but it's easier to find/use journal and then - * master node). - * - * Input Parameters: - * driver - MTD driver - * data - Mount options - * handle - To be updated with file system information. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int mnemofs_bind(FAR struct inode *driver, FAR const void *data, - FAR void** handle) -{ - int ret = OK; - bool format = false; - FAR char buf[8]; - mfs_t i = 0; - mfs_t j = 0; - mfs_t mnblk1; - mfs_t mnblk2; - mfs_t jrnl_blk; - FAR struct mfs_sb_s *sb = NULL; - struct mtd_geometry_s geo; - - MFS_LOG("BIND", "Entry."); - - MFS_EXTRA_LOG("BIND", "Resetting temporary buffer."); - memset(buf, 0, 8); - - MFS_EXTRA_LOG("BIND", "Allocating superblock in memory."); - sb = fs_heap_zalloc(sizeof(*sb)); - if (!sb) - { - MFS_LOG("BIND", "SB in-memory allocation error."); - ret = -ENOMEM; - goto errout; - } - else - { - MFS_EXTRA_LOG("BIND", "Superblock allocated at %p", sb); - } - - /* Currently only supports NAND flashes (MTD devices). */ - - if (driver && INODE_IS_MTD(driver)) - { - if (!driver || !driver->u.i_mtd || !driver->u.i_mtd->ioctl) - { - MFS_LOG("BIND", "Unsupported device."); - ret = -ENODEV; - goto errout_with_sb; - } - else - { - MFS_EXTRA_LOG("BIND", "Device is of MTD type."); - } - - ret = MTD_IOCTL(driver->u.i_mtd, MTDIOC_GEOMETRY, - (unsigned long) &geo); - - MFS_LOG("BIND", "MTD Driver Geometry read."); - MFS_EXTRA_LOG("BIND", "MTD Driver Geometry details." - " Page size: %d, Block size: %d," - " Pages/Block: %d, Blocks: %d\n", - geo.blocksize, geo.erasesize, - geo.erasesize / geo.blocksize, geo.neraseblocks); - } - else - { - MFS_LOG("BIND", "Device is not an MTD device."); - ret = -ENODEV; - goto errout_with_sb; - } - - ret = nxmutex_init(&MFS_LOCK(sb)); - if (predict_false(ret < 0)) - { - MFS_LOG("BIND", "FS-wide Mutex failed to initialize."); - goto errout_with_sb; - } - else - { - MFS_EXTRA_LOG("BIND", "FS-wide Mutex Initialized."); - } - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - MFS_LOG("BIND", "Mutex failed to lock. Return %d.", ret); - goto errout_with_lockinit; - } - else - { - MFS_EXTRA_LOG("BIND", "Mutex acquired."); - } - - sb->drv = driver; - sb->pg_sz = geo.blocksize; - sb->blk_sz = geo.erasesize; - sb->n_blks = geo.neraseblocks; - sb->pg_in_blk = MFS_BLKSZ(sb) / sb->pg_sz; -#ifdef CONFIG_MNEMOFS_JOURNAL_NBLKS - MFS_JRNL(sb).n_blks = CONFIG_MNEMOFS_JOURNAL_NBLKS; -#else - MFS_JRNL(sb).n_blks = MIN(5, MFS_NBLKS(sb) / 2); -#endif - sb->log_blk_sz = log2(MFS_BLKSZ(sb)); - sb->log_pg_sz = log2(sb->pg_sz); - sb->log_pg_in_blk = log2(sb->pg_in_blk); - sb->log_n_blks = log2(MFS_NBLKS(sb)); - MFS_FLUSH(sb) = false; - - list_initialize(&MFS_OFILES(sb)); - - MFS_EXTRA_LOG("BIND", "SB initialized in-memory."); - MFS_EXTRA_LOG("BIND", "SB Details."); - MFS_EXTRA_LOG("BIND", "\tDriver: %p", driver); - MFS_EXTRA_LOG("BIND", "\tPage Size: %" PRIu32, sb->pg_sz); - MFS_EXTRA_LOG("BIND", "\tLog Page Size: %" PRIu8, sb->log_pg_sz); - MFS_EXTRA_LOG("BIND", "\tBlock Size: %" PRIu32, sb->blk_sz); - MFS_EXTRA_LOG("BIND", "\tLog Block Size: %" PRIu8, - sb->log_blk_sz); - MFS_EXTRA_LOG("BIND", "\tPages Per Block: %" PRIu16, - sb->pg_in_blk); - MFS_EXTRA_LOG("BIND", "\tBlocks: %" PRIu32, sb->n_blks); - MFS_EXTRA_LOG("BIND", "\tLog Blocks: %" PRIu8, sb->log_n_blks); - MFS_EXTRA_LOG("BIND", "\tJournal Blocks: %" PRIu16, - MFS_JRNL(sb).n_blks); - MFS_EXTRA_LOG("BIND", "\tFlush State: %" PRIu8, MFS_FLUSH(sb)); - - sb->rw_buf = fs_heap_zalloc(MFS_PGSZ(sb)); - if (predict_false(sb->rw_buf == NULL)) - { - MFS_LOG("BIND", "RW Buffer in-memory allocation error."); - goto errout_with_lock; - } - else - { - MFS_EXTRA_LOG("BIND", "RW Buffer allocated."); - } - - /* TODO: Format the superblock in Block 0. */ - - srand(time(NULL)); - - if (!MFS_STRLITCMP(data, "autoformat")) - { - MFS_LOG("BIND", "Autoformat is ON."); - - /* Look for journal and maybe hopefully, the master node - * if it comes first. - */ - - MFS_LOG("BIND", "Checking for valid mnemofs formatting."); - - for (i = 0; i < MFS_NBLKS(sb); i++) - { - MFS_EXTRA_LOG("BIND", "Checking start of Block %" PRIu32, - i + 1); - mfs_read_page(sb, buf, 8, MFS_BLK2PG(sb, i), 0); - - for (j = 0; j < 8; j++) - { - MFS_EXTRA_LOG("BIND", "\tBlock %" PRIu32 - ", Offset %" PRIu32 ": %x", i, j, buf[j]); - } - - if (!MFS_STRLITCMP(buf, MFS_JRNL_MAGIC)) - { - MFS_LOG("BIND", "Found Journal at Block %" PRIu32, - i + 1); - - ret = mfs_jrnl_init(sb, i); - if (predict_false(ret < 0)) - { - MFS_LOG("BIND", "Error initializing journal."); - goto errout_with_rwbuf; - } - else - { - MFS_LOG("BIND", "Journal initialized."); - } - - ret = mfs_mn_init(sb, i); - if (predict_false(ret < 0)) - { - MFS_LOG("BIND", "Error initializing masternode."); - goto errout_with_rwbuf; - } - else - { - MFS_LOG("BIND", "Master node initialized."); - } - - break; - } - - MFS_EXTRA_LOG("BIND", "Resetting temporary buffer."); - memset(buf, 0, 8); - } - - if (predict_false(sb->mn.pg == 0)) - { - MFS_LOG("BIND", "Journal not found on device."); - MFS_LOG("BIND", "Device needs formatting."); - - format = true; - memset(&MFS_JRNL(sb), 0, sizeof(struct mfs_jrnl_state_s)); - memset(&MFS_MN(sb), 0, sizeof(struct mfs_mn_s)); - } - else - { - MFS_LOG("BIND", "Device already formatted."); - - mfs_lru_init(sb); - mfs_ba_init(sb); - } - } - - if (format || !strncmp(data, "forceformat", 12)) - { - /* Format. */ - - if (format) - { - MFS_LOG("BIND", "Device format necessary."); - } - else - { - MFS_EXTRA_LOG("BIND", "Device formatting configured."); - } - - mfs_ba_fmt(sb); - mfs_lru_init(sb); - - mnblk1 = 0; - mnblk2 = 0; - - ret = mfs_jrnl_fmt(sb, &mnblk1, &mnblk2, &jrnl_blk); - if (predict_false(ret < 0)) - { - MFS_LOG("BIND", "Error formatting Journal"); - goto errout_with_rwbuf; - } - else - { - MFS_LOG("BIND", "Journal format completed."); - } - - ret = mfs_mn_fmt(sb, mnblk1, mnblk2, jrnl_blk); - if (predict_false(ret < 0)) - { - MFS_LOG("BIND", "Error formatting Master Node"); - goto errout_with_rwbuf; - } - else - { - MFS_LOG("BIND", "Master node format completed."); - } - - MFS_LOG("BIND", "Device formatted."); - } - - *handle = (FAR void *)sb; - MFS_LOG("BIND", "Mount Successful. Super Block %p.", sb); - - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_LOG("BIND", "Mutex released."); - - MFS_LOG("BIND", "Exit | Return: %d.", ret); - return ret; - -errout_with_rwbuf: - fs_heap_free(sb->rw_buf); - MFS_LOG("BIND", "RW Buffer freed."); - -errout_with_lock: - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("BIND", "Mutex released."); - -errout_with_sb: - fs_heap_free(sb); - MFS_LOG("BIND", "Superblock freed."); - -errout_with_lockinit: - nxmutex_destroy(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("BIND", "Mutex destroyed."); - -errout: - MFS_LOG("BIND", "Exit | Return: %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_unbind - * - * Description: - * Unmounts the file system. - * - * See `umount(2)` and `umount(8)` for more information. - * - * Input Parameters: - * handle - File system information. - * driver - To be populated with the MTD driver - * flags - Flags for unmounting. - * - * Returned Value: - * 0 - OK - * - ****************************************************************************/ - -static int mnemofs_unbind(FAR void *handle, FAR struct inode **driver, - unsigned int flags) -{ - FAR struct mfs_sb_s *sb; - - MFS_LOG("UNBIND", "Entry."); - - DEBUGASSERT(handle); - sb = handle; - MFS_LOG("UNBIND", "Superblock %p.", sb); - - *driver = sb->drv; - MFS_LOG("UNBIND", "Driver %p.", driver); - - mfs_jrnl_free(sb); - mfs_ba_free(sb); - - nxmutex_destroy(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("UNBIND", "Mutex destroyed."); - - fs_heap_free(sb->rw_buf); - MFS_LOG("UNBIND", "RW Buffer freed."); - - fs_heap_free(sb); - MFS_LOG("UNBIND", "Superblock freed."); - - MFS_LOG("UNBIND", "Exit."); - return OK; -} - -/**************************************************************************** - * Name: mnemofs_statfs - * - * Description: - * Get file system statistics. See `statfs(2)` for more information. - * - * Input Parameters: - * mountpt - Mount point of the file system. - * buf - To populate with file system statistics. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int mnemofs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf) -{ - int ret = OK; - FAR struct mfs_sb_s *sb; - - finfo("Mnemofs statfs."); - - DEBUGASSERT(mountpt != NULL); - sb = mountpt->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - goto errout; - } - - finfo("Lock acquired."); - - buf->f_type = MNEMOFS_SUPER_MAGIC; - buf->f_bsize = sb->pg_sz; - buf->f_blocks = MFS_NBLKS(sb) * sb->pg_in_blk; - buf->f_bavail = mfs_ba_getavailpgs(sb); - buf->f_namelen = UINT8_MAX; - - nxmutex_unlock(&MFS_LOCK(sb)); - finfo("Lock released."); - -errout: - finfo("Mnemofs statfs exited with %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_unlink - * - * Description: - * Remove a file's entry from the file system. This might delete the file - * as well. See `unlink(2)` for more information. - * - * In mnemofs, the directory entry of the file is removed from its parent's - * directory file (CTZ list). The pages used by the file are marked for - * delete to the block allocator. When the master node is moved, and if the - * blocks of which the pages are part of is ready for erase, then the - * block allocator erases those blocks. - * - * Input Parameters: - * mountpt - Mount point of the file system. - * buf - To populate with file system statistics. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int mnemofs_unlink(FAR struct inode *mountpt, FAR const char *relpath) -{ - int ret = OK; - int flags; - mfs_t depth; - FAR struct mfs_sb_s *sb; - FAR struct mfs_path_s *path; - - finfo("Mnemofs unlink at path \"%s\".", relpath); - - DEBUGASSERT(mountpt != NULL); - sb = mountpt->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - goto errout; - } - - finfo("Lock acquired."); - - flags = mfs_get_patharr(sb, relpath, &path, &depth); - if ((flags & MFS_ISFILE) == 0) - { - ret = -EISDIR; - goto errout_with_lock; - } - - mfs_pitr_rm(sb, path, depth, true); - - mfs_free_patharr(path); - -errout_with_lock: - nxmutex_unlock(&MFS_LOCK(sb)); - finfo("Lock released."); - -errout: - finfo("Mnemofs unlink exited with %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_mkdir - * - * Description: - * Create a directory at given relpath. See `mkdir(2)` for more - * information. - * - * In mnemofs, the directory entry of the file is appended to its parent's - * directory file (CTZ list). - * - * Input Parameters: - * mountpt - Mount point of the file system. - * relpath - Relative path of the new directory. - * mode - Mode of the new directory (ACL). - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int mnemofs_mkdir(FAR struct inode *mountpt, FAR const char *relpath, - mode_t mode) -{ - int ret = OK; - int flags; - mfs_t depth; - struct mfs_pitr_s pitr; - FAR struct mfs_sb_s *sb; - FAR struct mfs_path_s *path; - - MFS_LOG("MKDIR", "Entry."); - MFS_LOG("MKDIR", "New directory at \"%s\".", relpath); - - mode |= S_IFDIR; - - MFS_LOG("MKDIR", "Mode is 0x%x.", mode); - - DEBUGASSERT(mountpt != NULL); - sb = mountpt->i_private; - MFS_EXTRA_LOG("MKDIR", "Superblock is %p.", sb); - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - goto errout; - } - else - { - MFS_EXTRA_LOG("MKDIR", "Mutex lock acquired."); - } - - flags = mfs_get_patharr(sb, relpath, &path, &depth); - MFS_EXTRA_LOG("MKDIR", "Retrieved flags are 0x%x.", flags); - MFS_EXTRA_LOG("MKDIR", "Path received is at %p.", path); - MFS_EXTRA_LOG("MKDIR", "Depth of path is %" PRIu32 ".", depth); - - if ((flags & MFS_EXIST) != 0) - { - MFS_LOG("MKDIR", "The requested directory already exists."); - ret = -EEXIST; - goto errout_with_path; - } - else - { - MFS_LOG("MKDIR", "The requested directory does not exist."); - if ((flags & MFS_P_EXIST) != 0) - { - MFS_EXTRA_LOG("MKDIR", "Parent exists."); - if ((flags & MFS_P_ISDIR) != 0) - { - /* OK */ - - MFS_EXTRA_LOG("MKDIR", "Parent is all right."); - } - else - { - MFS_EXTRA_LOG("MKDIR", "Ancestor is not a directory."); - ret = -ENOTDIR; - goto errout_with_path; - } - } - else - { - MFS_EXTRA_LOG("MKDIR", "Parent not found."); - ret = -ENOENT; - goto errout_with_path; - } - } - - memset(&path[depth - 1], 0, sizeof(struct mfs_path_s)); - MFS_EXTRA_LOG("MKDIR", "Resetting, at index %u, path array %p.", - depth - 1, path); - - mfs_pitr_init(sb, path, depth, &pitr, true); - MFS_EXTRA_LOG("MKDIR", "The path contains the child."); - MFS_EXTRA_LOG("MKDIR", "Parent iterator initialized."); - MFS_EXTRA_LOG("MKDIR", "\tDepth of parent %" PRIu32 ".", - pitr.depth); - MFS_EXTRA_LOG("MKDIR", "\tCurrent iteration offset %" PRIu32 ".", - pitr.c_off); - MFS_EXTRA_LOG("MKDIR", "\tParent's offset %" PRIu32 ".", - pitr.p.off); - MFS_EXTRA_LOG("MKDIR", "\tParent's size %" PRIu32 ".", pitr.p.sz); - MFS_EXTRA_LOG("MKDIR", "\tParent's CTZ (%" PRIu32 ", %" PRIu32 ")" - , pitr.p.ctz.idx_e, pitr.p.ctz.pg_e); - - /* The last incomplete direntry will be added by mfs_pitr_appendnew. */ - - ret = mfs_pitr_appendnew(sb, path, depth, &pitr, relpath, mode); - if (predict_false(ret < 0)) - { - MFS_LOG("MKDIR", "Could not append direntry. Return %d", ret); - goto errout_with_path; - } - else - { - MFS_EXTRA_LOG("MKDIR", "Direntry append successful."); - MFS_EXTRA_LOG("MKDIR", "\tDepth of parent %" PRIu32 ".", pitr.depth); - MFS_EXTRA_LOG("MKDIR", "\tCurrent iteration offset %" PRIu32 ".", - pitr.c_off); - MFS_EXTRA_LOG("MKDIR", "\tParent's offset %" PRIu32 ".", pitr.p.off); - MFS_EXTRA_LOG("MKDIR", "\tParent's size %" PRIu32 ".", pitr.p.sz); - MFS_EXTRA_LOG("MKDIR", "\tParent's CTZ (%" PRIu32 ", %" PRIu32 ")", - pitr.p.ctz.idx_e, pitr.p.ctz.pg_e); - } - - mfs_pitr_free(&pitr); - - MFS_LOG("MKDIR", "Directory created at \"%s\".", relpath); - - mfs_free_patharr(path); - - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("MKDIR", "Mutex released."); - - MFS_LOG("MKDIR", "Exit | Return: %d.", ret); - return ret; - -errout_with_path: - mfs_free_patharr(path); - - mfs_pitr_free(&pitr); - - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("MKDIR", "Mutex released."); - - /* TODO: The flush operation does not work properly, and causes memory - * leaks by most likely not flushing out anything and keeping it in - * memory. - */ - -errout: - MFS_LOG("MKDIR", "Exit | Return: %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_rmdir - * - * Description: - * Removes a directory from given relpath. See `rmdir(2)` for more - * information. - * - * Input Parameters: - * mountpt - Mount point of the file system. - * relpath - Relative path of the new directory. - * mode - Mode of the new directory (ACL). - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int mnemofs_rmdir(FAR struct inode *mountpt, FAR const char *relpath) -{ - int ret = OK; - int flags; - mfs_t depth; - struct mfs_pitr_s pitr; - FAR struct mfs_sb_s *sb; - FAR struct mfs_path_s *path; - - MFS_LOG("RMDIR", "Entry"); - MFS_LOG("RMDIR", "Directory \"%s\" to be removed.", relpath); - - DEBUGASSERT(mountpt != NULL); - sb = mountpt->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - MFS_LOG("RMDIR", "Mutex could not be acquired."); - goto errout; - } - else - { - MFS_EXTRA_LOG("RMDIR", "Mutex acquired."); - } - - flags = mfs_get_patharr(sb, relpath, &path, &depth); - if ((flags & MFS_ISDIR) == 0) - { - MFS_LOG("RMDIR", "FS Object is not a directory."); - ret = -ENOTDIR; - goto errout_with_lock; - } - else - { - MFS_EXTRA_LOG("RMDIR", "Path is at %p with depth %" PRIu32, path, - depth); - MFS_EXTRA_LOG("RMDIR", "FS Object is a directory."); - MFS_EXTRA_LOG("RMDIR", "Retrieved flags are 0x%x.", flags); - } - - mfs_pitr_init(sb, path, depth, &pitr, true); - mfs_pitr_adv_tochild(&pitr, path); - - if (!mfs_obj_isempty(sb, path, &pitr)) - { - MFS_EXTRA_LOG("RMDIR", "Directory is not empty."); - ret = -ENOTEMPTY; - goto errout_with_pitr; - } - - mfs_pitr_free(&pitr); - - mfs_pitr_rm(sb, path, depth, true); - -errout_with_pitr: - mfs_free_patharr(path); - -errout_with_lock: - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("RMDIR", "Mutex released."); - -errout: - MFS_LOG("RMDIR", "Exit | Return: %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_rename - * - * Description: - * Moves a file or directory between paths. See `rename(2)` for more - * information. - * - * In mnemofs, this involves erasing a direntry from old path, and adding - * it to the new path. There are some further complications regarding - * directories and files, as mentioned in `rename(2)`. - * - * Input Parameters: - * mountpt - Mount point of the file system. - * relpath - Relative path of the new directory. - * mode - Mode of the new directory (ACL). - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int mnemofs_rename(FAR struct inode *mountpt, - FAR const char *oldrelpath, - FAR const char *newrelpath) -{ - int ret = OK; - int oflags; - int nflags; - bool nexists; - bool odir = false; - bool ndir = false; - mfs_t odepth; - mfs_t ndepth; - struct mfs_pitr_s opitr; - struct mfs_pitr_s npitr; - FAR struct mfs_sb_s *sb; - FAR struct mfs_path_s *opath; - FAR struct mfs_path_s *npath; - FAR struct mfs_dirent_s *odirent = NULL; - - finfo("Mnemofs rename \"%s\" to \"%s\".", oldrelpath, newrelpath); - - DEBUGASSERT(mountpt != NULL); - sb = mountpt->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - goto errout; - } - - finfo("Lock acquired."); - - oflags = mfs_get_patharr(sb, oldrelpath, &opath, &odepth); - if ((oflags & MFS_EXIST) == 0) - { - ret = -ENOENT; - goto errout_with_opath; - } - - nflags = mfs_get_patharr(sb, newrelpath, &npath, &ndepth); - if ((nflags & MFS_P_EXIST) == 0) - { - ret = -ENONET; - goto errout_with_npath; - } - - odir = ((oflags & MFS_ISDIR) != 0); - ndir = ((nflags & MFS_ISDIR) != 0); - nexists = ((nflags & MFS_EXIST) != 0); - - if (nexists && odir && !ndir) - { - ret = -ENOTDIR; - goto errout_with_npath; - } - - if (nexists && !odir && ndir) - { - ret = -EISDIR; - goto errout_with_npath; - } - - mfs_pitr_init(sb, npath, ndepth, &npitr, nexists); - mfs_pitr_init(sb, opath, odepth, &opitr, true); - - /* If new path already exists, remove the direntry. If it's a non-empty - * directory, then raise error. - */ - - if (nexists) - { - mfs_pitr_adv_tochild(&opitr, opath); - mfs_pitr_adv_tochild(&npitr, npath); - - mfs_pitr_readdirent(sb, opath, &opitr, &odirent); - if (ndir && !mfs_obj_isempty(sb, npath, &npitr)) - { - ret = -ENOTEMPTY; - goto errout_with_pitr; - } - - mfs_pitr_reset(&npitr); - - mfs_pitr_rm(sb, npath, ndepth, false); - } - - mfs_pitr_adv_tochild(&npitr, npath); - - mfs_pitr_appenddirent(sb, npath, ndepth, &npitr, odirent); - mfs_pitr_rmdirent(sb, opath, odepth, &opitr, odirent); - -errout_with_pitr: - mfs_free_dirent(odirent); - mfs_pitr_free(&opitr); - mfs_pitr_free(&npitr); - -errout_with_npath: - mfs_free_patharr(npath); - -errout_with_opath: - mfs_free_patharr(opath); - nxmutex_unlock(&MFS_LOCK(sb)); - finfo("Lock released."); - -errout: - finfo("Mnemofs rename exited with ret %d.", ret); - return ret; -} - -/**************************************************************************** - * Name: mnemofs_stat - * - * Description: - * Get stats of a file. See `stat(2)` for more information. - * - * In mnemofs, most of the relevant information is stored in the dirent - * of the file in its parent's directory file. The stats of the root are - * available in the master node. - * - * Input Parameters: - * mountpt - Mount point of the file system. - * relpath - Relative path of the new directory. - * buf - File stats to populate. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int mnemofs_stat(FAR struct inode *mountpt, FAR const char *relpath, - FAR struct stat *buf) -{ - int ret = OK; - int flags; - mfs_t depth; - struct mfs_pitr_s pitr; - FAR struct mfs_sb_s *sb; - FAR struct mfs_path_s *path; - FAR struct mfs_dirent_s *dirent = NULL; - - MFS_LOG("STAT", "Entry."); - MFS_LOG("STAT", "Requested path is \"%s\".", relpath); - - DEBUGASSERT(mountpt != NULL); - sb = mountpt->i_private; - DEBUGASSERT(sb != NULL); - - ret = nxmutex_lock(&MFS_LOCK(sb)); - if (ret < 0) - { - MFS_LOG("STAT", "Could not acquire mutex."); - goto errout; - } - else - { - MFS_EXTRA_LOG("STAT", "Mutex acquired."); - } - - MFS_EXTRA_LOG_MN(&MFS_MN(sb)); - - flags = mfs_get_patharr(sb, relpath, &path, &depth); - if ((flags & MFS_EXIST) == 0) - { - MFS_LOG("STAT", "File does not exist."); - ret = -ENOENT; - goto errout_with_path; - } - else - { - MFS_EXTRA_LOG("STAT", "Path is at %p.", path); - MFS_EXTRA_LOG("STAT", "Depth is %" PRIu32, depth); - MFS_EXTRA_LOG("STAT", "Retrieved flags are 0x%x.", flags); - } - - ret = mfs_lru_getupdatedinfo(sb, path, depth); - if (predict_false(ret < 0)) - { - MFS_LOG("STAT", "Could not get updated information."); - goto errout_with_path; - } - else - { - MFS_EXTRA_LOG("STAT", "Updated information received from LRU."); - } - - mfs_pitr_init(sb, path, depth, &pitr, true); - mfs_pitr_adv_tochild(&pitr, path); - - ret = mfs_pitr_readdirent(sb, path, &pitr, &dirent); - if (predict_false(ret < 0)) - { - MFS_LOG("STAT", "Could not read from direntry."); - goto errout_with_path; - } - else if (dirent == NULL) - { - MFS_LOG("STAT", "No entry found."); - ret = -ENOENT; - goto errout_with_path; - } - else - { - MFS_EXTRA_LOG("STAT", "Direntry read successfully."); - } - - MFS_EXTRA_LOG_DIRENT(dirent); - - buf->st_nlink = 1; - buf->st_blksize = sb->pg_sz; - buf->st_size = dirent->sz; - buf->st_mode = dirent->mode; - buf->st_atim = dirent->st_atim; - buf->st_ctim = dirent->st_ctim; - buf->st_mtim = dirent->st_mtim; - buf->st_blocks = dirent->ctz.idx_e + 1; - - mfs_free_dirent(dirent); - mfs_pitr_free(&pitr); - -errout_with_path: - mfs_free_patharr(path); - - nxmutex_unlock(&MFS_LOCK(sb)); - MFS_EXTRA_LOG("STAT", "Mutex released."); - -errout: - MFS_LOG("STAT", "Exit | Return: %d.", ret); - return ret; -} - -/**************************************************************************** - * Public Functions - ****************************************************************************/ - -int mnemofs_flush(FAR struct mfs_sb_s *sb) -{ - int ret = OK; - bool change; - - /* Empty the LRU, and maybe the journal as well. */ - - finfo("Flush operation started."); - - for (; ; ) - { - change = false; - if (!mfs_lru_isempty(sb)) - { - finfo("LRU needs to be flushed."); - - change = true; - ret = mfs_lru_flush(sb); - if (predict_false(ret < 0)) - { - goto errout; - } - } - - if (!mfs_jrnl_isempty(sb) && - MFS_JRNL(sb).log_cblkidx >= MFS_JRNL_LIM(sb)) - { - finfo("Journal needs to be flushed."); - - change = true; - - ret = mfs_jrnl_flush(sb); - if (predict_false(ret < 0)) - { - goto errout; - } - } - - if (!change) - { - break; - } - - finfo("Finished Iteration."); - } - -errout: - return ret; -} diff --git a/fs/mnemofs/mnemofs.h b/fs/mnemofs/mnemofs.h index 201f8719463..dbe571920c1 100644 --- a/fs/mnemofs/mnemofs.h +++ b/fs/mnemofs/mnemofs.h @@ -57,2021 +57,242 @@ * Included Files ****************************************************************************/ -#include +#include +#include +#include +#include #include #include +#include #include +#include +#include +#include + /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ -#define MFS_JRNL_MAGIC "-mfs!j!-" -#define MFS_MN_MAGIC "-mfs!m!-" +typedef uint32_t mfs_t; +typedef uint64_t mfs_time_t; -#define MFS_CEILDIVIDE(num, denom) (((num) + ((denom) - 1)) / (denom)) -#define MFS_UPPER8(num) (((num) + 7) & (-8)) +#define MFS_DIRENT_MAGIC 131 +#define MFS_DIR_MAGIC 197 +#define MFS_NAME_MAX 30 +#define MFS_NAME_LEN (MFS_NAME_MAX + 1) +#define MFS_LOCATION_INVALID 0 +#define MFS_DIRENT_OFF_INVALID UINT32_MAX +#define MFS_FORMAT_VERSION_MAJOR 1 -#define MFS_BLK2PG(sb, blk) ((blk) << (sb)->log_pg_in_blk) -#define MFS_PG2BLK(sb, pg) ((pg) >> (sb)->log_pg_in_blk) -#define MFS_PG2BLKPGOFF(sb, pg) ((pg) % (1 << (sb)->log_pg_in_blk)) - -#define MFS_LOCK(sb) ((sb)->fs_lock) -#define MFS_PGSZ(sb) ((sb)->pg_sz) -#define MFS_LRU(sb) ((sb)->lru) -#define MFS_BLKSZ(sb) ((sb)->blk_sz) -#define MFS_MN(sb) ((sb)->mn) -#define MFS_JRNL(sb) ((sb)->j_state) -#define MFS_LOGPGSZ(sb) (MFS_CEILDIVIDE((sb)->log_pg_sz, 8)) -#define MFS_PGINBLK(sb) ((sb)->pg_in_blk) -#define MFS_MTD(sb) ((sb)->drv->u.i_mtd) -#define MFS_RWBUF(sb) ((sb)->rw_buf) -#define MFS_BA(sb) ((sb)->ba_state) -#define MFS_NBLKS(sb) ((sb)->n_blks) -#define MFS_OFILES(sb) ((sb)->of) -#define MFS_FLUSH(sb) ((sb)->flush) -#define MFS_NPGS(sb) (MFS_NBLKS(sb) * MFS_PGINBLK(sb)) - -#define MFS_HASHSZ 16 -#define MFS_CTZ_SZ(l) ((l)->sz) -#define MFS_DIRENTSZ(dirent) ((2 * 2) + 4 + (16 * 3) + 8 + 1 \ - + (dirent)->namelen) - -#define MFS_JRNL_LIM(sb) (MFS_JRNL(sb).n_blks / 2) -#define MFS_TRAVERSE_INITSZ 8 - -#define MFS_LOG(fn, fmt, ...) finfo("[mnemofs | " fn "] " fmt, ##__VA_ARGS__) -#ifdef CONFIG_MNEMOFS_EXTRA_DEBUG -#define MFS_EXTRA_LOG(fn, fmt, ...) MFS_LOG(fn, fmt, ##__VA_ARGS__) -#else -#define MFS_EXTRA_LOG(fmt, ...) { } -#endif -#define MFS_STRLITCMP(a, lit) strncmp(a, lit, strlen(lit)) +#define MFS_PAGE_SIZE(sb) ((sb)->geo.blocksize) +#define MFS_BLOCK_SIZE(sb) ((sb)->geo.erasesize) +#define MFS_BLOCK_COUNT(sb) ((sb)->geo.neraseblocks) +#define MFS_PAGES_PER_BLOCK(sb) ((sb)->pagesperblk) +#define MFS_PAGE_COUNT(sb) \ + (MFS_BLOCK_COUNT(sb) * MFS_PAGES_PER_BLOCK(sb)) +#define MFS_BLOCK_TO_PAGE(sb, blk) \ + ((blk) * MFS_PAGES_PER_BLOCK(sb)) +#define MFS_DIRENTS_PER_PAGE(sb) \ + (MFS_PAGE_SIZE(sb) / sizeof(struct mfs_direntry_s)) /**************************************************************************** * Public Types ****************************************************************************/ -typedef uint32_t mfs_t; - -enum +begin_packed_struct struct mfs_direntry_s { - MFS_PG_USED, - MFS_PG_FREE, - MFS_BLK_BAD, - MFS_BLK_ERASABLE, - MFS_BLK_FREE, - MFS_BLK_USED, -}; + uint8_t magic; + uint16_t mode; + mfs_t oldloc; + mfs_t newloc; + uint16_t namehash; + char name[MFS_NAME_MAX]; + mfs_time_t ctime; + mfs_time_t mtime; + union + { + uint32_t size; + uint32_t reserved; + } meta; + uint8_t checksum; +} end_packed_struct; -enum MFS_PATH_FLAGS +begin_packed_struct struct mfs_dirmeta_s { - MFS_ISDIR = (1 << 0), /* Path is a directory. */ - MFS_ISFILE = (1 << 1), /* Path is a file. */ - MFS_EXIST = (1 << 2), /* Path Exists */ - MFS_FINPATH = (1 << 3), /* File in midele of path before bottom most - * child. Not reachable. - */ - MFS_P_EXIST = (1 << 4), /* Parent of the bottom most element exists. */ - MFS_P_ISDIR = (1 << 5), /* Parent is a directory. */ -}; + uint8_t magic; + mfs_t parent; + uint8_t checksum; +} end_packed_struct; -struct mfs_ctz_s +begin_packed_struct struct mfs_superblock_s { - mfs_t pg_e; - mfs_t idx_e; -}; + uint32_t magic; + uint32_t npages; + uint32_t pagesize; + uint32_t pagesperblock; + uint32_t nblocks; + mfs_t rootdir; + uint16_t version; +} end_packed_struct; -struct mfs_path_s +struct mfs_dirloc_s { - struct mfs_ctz_s ctz; - mfs_t off; - mfs_t sz; -}; - -struct mfs_ba_state_s -{ - mfs_t s_blk; /* Start block */ - mfs_t c_pg; /* Current page */ - FAR mfs_t *k_del; /* Delete counter for blocks. */ - size_t n_bmap_upgs; - FAR uint8_t *bmap_upgs; /* Bitmap of used pages. */ -}; - -struct mfs_mn_s -{ - mfs_t pg; /* Only mblk1's pg will be used here. */ - mfs_t jrnl_blk; /* Start of journal. */ - mfs_t mblk_idx; /* Index for next MN entry inside blk. */ - struct mfs_ctz_s root_ctz; - mfs_t root_sz; - struct timespec ts; - mode_t root_mode; - struct timespec root_st_atim; - struct timespec root_st_ctim; - struct timespec root_st_mtim; -}; - -struct mfs_jrnl_state_s -{ - mfs_t jrnl_start; - mfs_t mblk1; - mfs_t mblk2; - mfs_t n_logs; - mfs_t log_cpg; /* Current (last) page */ - mfs_t log_cblkidx; /* Current (last) block index. */ - mfs_t log_spg; /* First log's page */ - mfs_t log_sblkidx; /* First jrnl blk index. TODO: jrnlarr > 1 blk. */ - mfs_t jrnlarr_pg; - mfs_t jrnlarr_pgoff; - uint16_t n_blks; /* TODO: Does not include the master node. */ + mfs_t page; + mfs_t offset; }; struct mfs_sb_s { - FAR uint8_t *rw_buf; - FAR struct inode *drv; - mutex_t fs_lock; - mfs_t sb_blk; /* Block number of the superblock */ - mfs_t pg_sz; - uint8_t log_pg_sz; - mfs_t blk_sz; - uint8_t log_blk_sz; - mfs_t n_blks; - uint8_t log_n_blks; - uint16_t pg_in_blk; - uint8_t log_pg_in_blk; - struct mfs_mn_s mn; /* Master Node */ - struct mfs_jrnl_state_s j_state; /* Journal State */ - struct mfs_ba_state_s ba_state; /* Block Allocator State */ - struct list_node lru; - struct list_node of; /* open files. */ - bool flush; + FAR struct inode *driver; + FAR struct mtd_dev_s *mtd; + struct mtd_geometry_s geo; + uint8_t erasestate; + FAR uint8_t *rwbuf; + mfs_t rwpage; + bool rwvalid; + bool rwdirty; + FAR uint8_t *freepages; + FAR uint8_t *delpages; + size_t bitmapsize; + mfs_t nextpage; + mfs_t pagesperblk; + mfs_t superblock; + mfs_t rootdir; + uint16_t version; + mutex_t lock; + struct list_node ofiles; }; -/* This is for *dir VFS methods. */ - -struct mfs_fsdirent_s +struct mfs_dirfs_s { - struct fs_dirent_s base; /* VFS directory structure */ - uint8_t idx; /* This only goes from 0 for ., 1 for .. and - * 2 for others. - */ - FAR struct mfs_pitr_s *pitr; - FAR struct mfs_path_s *path; - mfs_t depth; + struct fs_dirent_s dir; + mfs_t location; + struct mfs_direntry_s direntry; + struct mfs_dirloc_s nextloc; }; -/* LRU Delta */ - -struct mfs_delta_s +struct mfs_ctz_s { - struct list_node list; - mfs_t off; - mfs_t n_b; - FAR char *upd; + mfs_t page; + mfs_t index; }; -/* LRU Node */ - -struct mfs_node_s +struct mfs_ofd_common_s { - struct list_node list; - struct list_node delta; - mfs_t n_list; - mfs_t depth; - mfs_t sz; - mfs_t range_min; - mfs_t range_max; - struct timespec st_mtim; - struct timespec st_atim; - struct timespec st_ctim; - FAR struct mfs_path_s *path; + struct mfs_direntry_s direntry; + struct mfs_dirloc_s direntloc; + FAR char *relpath; + mfs_t fileloc; + off_t pos; + unsigned int refs; }; -/* Common Part Open File Descriptor */ - -struct mfs_ocom_s -{ - bool new_ent; - mfs_t sz; /* Current file size. */ - mfs_t off; - mfs_t depth; - uint8_t refcount; - int oflags; - FAR struct mfs_path_s *path; -}; - -/* Open part for file descriptors. */ - struct mfs_ofd_s { - struct list_node list; - FAR struct mfs_ocom_s *com; + struct list_node entry; + FAR struct mfs_sb_s *sb; + FAR struct mfs_ofd_common_s *common; }; -struct mfs_dirent_s -{ - uint16_t name_hash; /* Should be at start to improve efficiency. */ - uint16_t mode; - mfs_t sz; - struct timespec st_atim; /* Time of last access */ - struct timespec st_mtim; /* Time of last modification */ - struct timespec st_ctim; /* Time of last status change */ - struct mfs_ctz_s ctz; - uint8_t namelen; - char name[]; -}; - -/* Parent iterator */ - -struct mfs_pitr_s -{ - struct mfs_path_s p; /* Parent's path representation */ - mfs_t depth; - mfs_t c_off; /* Current iteration offset. */ -}; - -/* TODO: depth >= 1 */ - -/* IMP TODO: sizeof(x) != size of buffer required to store it. Need to fix. */ - -/**************************************************************************** - * Public Data - ****************************************************************************/ - -#ifdef __cplusplus -#define EXTERN extern "C" -extern "C" -{ -#else -#define EXTERN extern -#endif - -/**************************************************************************** - * Inline Functions - ****************************************************************************/ - -/**************************************************************************** - * Name: mfs_blkremsz - * - * Description: - * Given a page and a page offset, it returns the bytes left in the entire - * block after the offset location. - * - * Input Parameters: - * sb - Superblock instance of the device. - * pg - Page number. - * pgoff - Page offset. - * - * Returned Value: - * Bytes left in the block. - * - ****************************************************************************/ - -static mfs_t inline mfs_blkremsz(FAR const struct mfs_sb_s * const sb, - mfs_t pg, mfs_t pgoff) -{ - return MFS_BLKSZ(sb) - (MFS_PG2BLKPGOFF(sb, pg) * sb->pg_sz + pgoff); -} - -static inline mfs_t mfs_ctz(const uint32_t x) -{ - if (predict_false(x == 0)) - { - /* Special case, since we're using this for the CTZ skip list. The 0th - * block has no pointers. - */ - - return 0; - } -#if defined(__GNUC__) - return __builtin_ctz(x); -#else - uint32_t c; - - /* Credits: - * http://graphics.stanford.edu/~seander/bithacks.html - * #ZerosOnRightBinSearch - */ - - if (x & 0x1) - { - /* special case for odd x (assumed to happen half of the time) */ - - c = 0; - } - else - { - c = 1; - if ((x & 0xffff) == 0) - { - x >>= 16; - c += 16; - } - - if ((x & 0xff) == 0) - { - x >>= 8; - c += 8; - } - - if ((x & 0xf) == 0) - { - x >>= 4; - c += 4; - } - - if ((x & 0x3) == 0) - { - x >>= 2; - c += 2; - } - - c -= x & 0x1; - } - - return c; -#endif -} - -/**************************************************************************** - * Name: mfs_clz - * - * Description: - * Count Leading Zeros. Returns the number of leading zeros in a 32-bit - * integer. - * - * Input Parameters: - * x - 32-bit integer to check. - * - * Returned Value: - * The number of leading zeros. - * - ****************************************************************************/ - -static inline mfs_t mfs_clz(const uint32_t x) -{ - if (predict_false(x == UINT32_MAX)) - { - /* Special case, since we're using this for the CTZ skip list. The 0th - * block has no pointers. - */ - - return 0; - } -#if defined(__GNUC__) - return __builtin_clz(x); -#else - uint32_t n = 0; - uint32_t x_tmp = x; - - if (x_tmp == 0) - { - return 32; - } - - if (x_tmp <= 0x0000ffff) - { - n += 16; - x_tmp <<= 16; - } - - if (x_tmp <= 0x00ffffff) - { - n += 8; - x_tmp <<= 8; - } - - if (x_tmp <= 0x0fffffff) - { - n += 4; - x_tmp <<= 4; - } - - if (x_tmp <= 0x3fffffff) - { - n += 2; - x_tmp <<= 2; - } - - if (x_tmp <= 0x7fffffff) - { - n += 1; - } - - return n; -#endif -} - -static inline mfs_t mfs_popcnt(mfs_t x) -{ -#if defined(__GNUC__) - return __builtin_popcount(x); -#else -/* Can be found at: - * http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan - */ - - mfs_t c; - for (c = 0; x; c++) - { - x &= x - 1; - } - -#endif -} - -static inline void MFS_EXTRA_LOG_DIRENT(FAR const struct mfs_dirent_s * const - dirent) -{ - MFS_EXTRA_LOG("EXTRA_LOG_DIRENT", "Direntry details."); - MFS_EXTRA_LOG("EXTRA_LOG_DIRENT", "\tDirent location %p", dirent); - MFS_EXTRA_LOG("EXTRA_LOG_DIRENT", "\tMode is %" PRIu16, dirent->mode); - MFS_EXTRA_LOG("EXTRA_LOG_DIRENT", "\tName is \"%.*s\"", dirent->namelen, - dirent->name); - MFS_EXTRA_LOG("EXTRA_LOG_DIRENT", "\tNamelen is %" PRIu32, - dirent->namelen); - MFS_EXTRA_LOG("EXTRA_LOG_DIRENT", "\tName Hash is %" PRIu16, - dirent->name_hash); - MFS_EXTRA_LOG("EXTRA_LOG_DIRENT", "\tSize is %" PRIu16, - dirent->sz); - - /* TODO: Timespecs */ -} - -static inline void MFS_EXTRA_LOG_FSDIRENT(FAR const struct mfs_fsdirent_s * - const fsdirent) -{ - MFS_EXTRA_LOG("EXTRA_LOG_FSDIRENT", "FS Direntry details."); - MFS_EXTRA_LOG("EXTRA_LOG_FSDIRENT", "\tDirentry depth %" PRIu32, - fsdirent->depth); - MFS_EXTRA_LOG("EXTRA_LOG_FSDIRENT", "\tRead index %" PRIu32, - fsdirent->idx); - MFS_EXTRA_LOG("EXTRA_LOG_FSDIRENT", "\tPath %p.", fsdirent->path); - MFS_EXTRA_LOG("EXTRA_LOG_FSDIRENT", "\tPitr %p.", fsdirent->pitr); - MFS_EXTRA_LOG("EXTRA_LOG_FSDIRENT", "\tDepth %" PRIu32, fsdirent->depth); -} - -static inline void MFS_EXTRA_LOG_PITR(FAR const struct mfs_pitr_s * const - pitr) -{ - MFS_EXTRA_LOG("EXTRA_LOG_PITR", "Pitr details."); - MFS_EXTRA_LOG("EXTRA_LOG_PITR", "\tDepth %" PRIu32, pitr->depth); - MFS_EXTRA_LOG("EXTRA_LOG_PITR", "\tCurrent Offset %" PRIu32, pitr->c_off); - MFS_EXTRA_LOG("EXTRA_LOG_PITR", "\tParent CTZ (%" PRIu32 ", %" PRIu32 ")", - pitr->p.ctz.idx_e, pitr->p.ctz.pg_e); - MFS_EXTRA_LOG("EXTRA_LOG_PITR", "\tParent Size %" PRIu32, pitr->p.sz); - MFS_EXTRA_LOG("EXTRA_LOG_PITR", "\tParent Offset %" PRIu32, pitr->p.off); -} - -static inline void MFS_EXTRA_LOG_MN(FAR const struct mfs_mn_s * const mn) -{ - MFS_EXTRA_LOG("EXTRA_LOG_MN", "Master node details."); - MFS_EXTRA_LOG("EXTRA_LOG_MN", "\tFirst journal block is %" PRIu32, - mn->jrnl_blk); - MFS_EXTRA_LOG("EXTRA_LOG_MN", "\tNext MN entry index %" PRIu32, - mn->mblk_idx); - MFS_EXTRA_LOG("EXTRA_LOG_MN", "\tRoot CTZ (%" PRIu32 ", %" PRIu32 ")", - mn->root_ctz.idx_e, mn->root_ctz.pg_e); - MFS_EXTRA_LOG("EXTRA_LOG_MN", "\tRoot Size %" PRIu32, mn->root_sz); - MFS_EXTRA_LOG("EXTRA_LOG_MN", "\tRoot Mode is %u.", mn->root_mode); - - /* TODO: Timespecs */ -} - -static inline void MFS_EXTRA_LOG_F(FAR struct mfs_ofd_s *f) -{ - MFS_EXTRA_LOG("EXTRA_LOG_F", "File structure details."); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\tList details."); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\t\tPrevious node %p.", f->list.prev); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\t\tNext node %p.", f->list.next); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\tCommon structure details."); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\t\tDepth: %" PRIu32, f->com->depth); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\t\tNew Entry: %s.", - f->com->new_ent ? "true" : "false"); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\t\tOffset: %" PRIu32, f->com->off); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\t\tFlags: 0x%x.", f->com->oflags); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\t\tReference Counter: %" PRIu8, - f->com->refcount); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\t\tFile Size: %" PRIu32, f->com->sz); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\t\tPath details."); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\t\t\tOffset: %" PRIu32, f->com->path->off); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\t\t\tSize: %" PRIu32, f->com->path->sz); - MFS_EXTRA_LOG("EXTRA_LOG_F", "\t\t\tCTZ (%" PRIu32 ", %" PRIu32 ").", - f->com->path->ctz.idx_e, f->com->path->ctz.pg_e); -} +static_assert(sizeof(struct mfs_direntry_s) == 64, + "mnemofs direntry layout mismatch"); +static_assert(sizeof(struct mfs_dirmeta_s) == 6, + "mnemofs dirmeta layout mismatch"); +static_assert(sizeof(struct mfs_superblock_s) == 26, + "mnemofs superblock layout mismatch"); /**************************************************************************** * Public Function Prototypes ****************************************************************************/ -/* mnemofs.c */ - -int mnemofs_flush(FAR struct mfs_sb_s *sb); - -/* mnemofs_journal.c */ - -/**************************************************************************** - * Name: mfs_jrnl_init - * - * Description: - * Initializes journal when it's already formatted into the device. - * - * Input Parameters: - * sb - Superblock instance of the device. - * blk - First block of the journal. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * Does not initialize the master node. - * - ****************************************************************************/ - -int mfs_jrnl_init(FAR struct mfs_sb_s * const sb, mfs_t blk); - -/**************************************************************************** - * Name: mfs_jrnl_fmt - * - * Description: - * Format a journal to the device. - * - * Input Parameters: - * sb - Superblock instance of the device. - * blk1 - First master block for the journal. - * blk2 - Second master block for the journal. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * If blk1 == 0 and blk2 == 0, this means that this will also format in the - * master blocks. If this is not satisfied, the provided values will be - * taken to denote the master nodes. - * - * Does not format the master node. - * - ****************************************************************************/ - -int mfs_jrnl_fmt(FAR struct mfs_sb_s * const sb, mfs_t *blk1, mfs_t *blk2, - FAR mfs_t *jrnl_blk); - -/**************************************************************************** - * Name: mfs_jrnl_free - * - * Description: - * Free the journal. - * - * Input Parameters: - * sb - Superblock instance of the device. - * - ****************************************************************************/ - -void mfs_jrnl_free(FAR struct mfs_sb_s * const sb); - -/**************************************************************************** - * Name: mfs_jrnl_blkidx2blk - * - * Description: - * Gets the block number of a journal block at an index. - * - * Input Parameters: - * sb - Superblock instance of the device. - * blk_idx - Index of the block who's block number to find. - * - * Returned Value: - * Block number. - * - * Assumptions/Limitations: - * Assumes valid index. This index can also include master blocks. Also - * assumes, for now, that the entire journal array can fit in the first - * block. - * - ****************************************************************************/ - -mfs_t mfs_jrnl_blkidx2blk(FAR const struct mfs_sb_s * const sb, - const mfs_t blk_idx); - -/**************************************************************************** - * Name: mfs_jrnl_updatedinfo - * - * Description: - * Update the path information from the journal. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - "Base state" path. - * depth - Path depth. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * This applies updates over data that is already gathered from the data - * section of the flash. The data section is the "base state" over which - * the updates in the journal are applied. - * - ****************************************************************************/ - -int mfs_jrnl_updatedinfo(FAR const struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth); - -/**************************************************************************** - * Name: mfs_jrnl_wrlog - * - * Description: - * Write a log for LRU node when its popped from the LRU. - * - * Input Parameters: - * sb - Superblock instance of the device. - * node - LRU node. - * loc_new - New location of the CTZ skip list. - * sz_new - New size of the CTZ skip list. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * When a node from LRU is flushed, it's written to the data in any space - * that is available according to the block allocator, and the new location - * is recorded as a log along with the old location. Any log of this same - * CTZ skip list in the journal will use this "snapshot" of the location, - * ie. the updated path will be used until another log of the same CTZ - * skip list is stored, after which the path will be updated again, and - * so on. - * - ****************************************************************************/ - -int mfs_jrnl_wrlog(FAR struct mfs_sb_s * const sb, - FAR const struct mfs_node_s *node, - const struct mfs_ctz_s loc_new, const mfs_t sz_new); - -/**************************************************************************** - * Name: mfs_jrnl_flush - * - * Description: - * Flush the entire journal. This is the entry point of the entire flush - * operation and includes messing with the master node as well. - * - * Input Parameters: - * sb - Superblock instance of the device. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -int mfs_jrnl_flush(FAR struct mfs_sb_s * const sb); - -/**************************************************************************** - * Name: mfs_jrnl_isempty - * - * Description: - * Check if the journal is empty. - * - * Input Parameters: - * sb - Superblock instance of the device. - * - * Returned Value: - * True is the journal is empty, false otherwise. - * - ****************************************************************************/ - -bool mfs_jrnl_isempty(FAR const struct mfs_sb_s * const sb); - -struct mfs_jrnl_log_s -{ - /* A field denoting the size of the below elements is added here on-flash. - */ - - mfs_t depth; - mfs_t sz_new; - struct mfs_ctz_s loc_new; - struct timespec st_mtim_new; - struct timespec st_atim_new; - struct timespec st_ctim_new; - FAR struct mfs_path_s *path; - uint16_t hash; -}; - -int mfs_jrnl_rdlog(FAR const struct mfs_sb_s *const sb, - FAR mfs_t *blkidx, FAR mfs_t *pg_in_blk, - FAR struct mfs_jrnl_log_s *log); - -void mfs_jrnl_log_free(FAR const struct mfs_jrnl_log_s * const log); - -/* mnemofs_blkalloc.c */ - -/**************************************************************************** - * Name: mfs_ba_init - * - * Description: - * Formats and initializes the block allocator. - * - * Input Parameters: - * sb - Superblock instance of the device. - * - * Returned Value: - * 0 - OK - * -ENOMEM - No memory left. - * - ****************************************************************************/ - -int mfs_ba_fmt(FAR struct mfs_sb_s * const sb); - -/**************************************************************************** - * Name: mfs_ba_init - * - * Description: - * Initializes the block allocator. - * - * Input Parameters: - * sb - Superblock instance of the device. - * - * Returned Value: - * 0 - OK - * -ENOMEM - No memory left. - * - ****************************************************************************/ - -int mfs_ba_init(FAR struct mfs_sb_s * const sb); - -/**************************************************************************** - * Name: mfs_ba_getpg - * - * Description: - * Returns an allocated page. - * - * Input Parameters: - * sb - Superblock instance of the device. - * - * Returned Value: - * 0 - No more space left - * > 0 - Page number - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -mfs_t mfs_ba_getpg(FAR struct mfs_sb_s * const sb); - -/**************************************************************************** - * Name: mfs_ba_getblk - * - * Description: - * Returns an allocated block. - * - * Input Parameters: - * sb - Superblock instance of the device. - * - * Returned Value: - * 0 - No more space left - * > 0 - Block number - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -mfs_t mfs_ba_getblk(FAR struct mfs_sb_s * const sb); - -/**************************************************************************** - * Name: mfs_ba_pgmarkdel - * - * Description: - * Mark a page as being ready for deletion. - * - * Input Parameters: - * sb - Superblock instance of the device. - * pg - Page number. - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -void mfs_ba_pgmarkdel(FAR struct mfs_sb_s * const sb, mfs_t pg); - -/**************************************************************************** - * Name: mfs_ba_blkmarkdel - * - * Description: - * Mark a block as being ready for deletion. - * - * Input Parameters: - * sb - Superblock instance of the device. - * blk - Block number. - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -void mfs_ba_blkmarkdel(FAR struct mfs_sb_s * const sb, mfs_t blk); - -/**************************************************************************** - * Name: mfs_ba_delmarked - * - * Description: - * Delete all marked for deletion blocks. - * - * Input Parameters: - * sb - Superblock instance of the device. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -int mfs_ba_delmarked(FAR struct mfs_sb_s * const sb); - -/**************************************************************************** - * Name: mfs_ba_markusedpg - * - * Description: - * Marked page as being used. - * - * Input Parameters: - * sb - Superblock instance of the device. - * pg - Page number - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -void mfs_ba_markusedpg(FAR struct mfs_sb_s * const sb, mfs_t pg); - -/**************************************************************************** - * Name: mfs_ba_markusedblk - * - * Description: - * Marked block as being used. - * - * Input Parameters: - * sb - Superblock instance of the device. - * blk - Block number - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -void mfs_ba_markusedblk(FAR struct mfs_sb_s * const sb, mfs_t blk); - -/**************************************************************************** - * Name: mfs_ba_getavailpgs - * - * Description: - * Get number of available pages. - * - * Input Parameters: - * sb - Superblock instance of the device. - * pg - Page number - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -mfs_t mfs_ba_getavailpgs(FAR const struct mfs_sb_s * const sb); - -/**************************************************************************** - * Name: mfs_ba_free - * - * Description: - * Free the block allocator - * - * Input Parameters: - * sb - Superblock instance of the device. - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -void mfs_ba_free(FAR struct mfs_sb_s * const sb); - -/* mnemofs_rw.c */ - -/**************************************************************************** - * Name: mfs_isbadblk - * - * Description: - * Is a block bad. - * - * Input Parameters: - * sb - Superblock instance of the device. - * blk - Block Number - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -int mfs_isbadblk(FAR const struct mfs_sb_s * const sb, mfs_t blk); - -/**************************************************************************** - * Name: mfs_markbadblk - * - * Description: - * Mark a block as bad. - * - * Input Parameters: - * sb - Superblock instance of the device. - * blk - Block Number - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -int mfs_markbadblk(FAR const struct mfs_sb_s * const sb, mfs_t blk); - -/**************************************************************************** - * Name: mfs_write_page - * - * Description: - * Write a page. - * - * Input Parameters: - * sb - Superblock instance of the device. - * data - Buffer - * datalen - Length of buffer. - * pg - Page number. - * pgoff - Offset into the page. - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -ssize_t mfs_write_page(FAR const struct mfs_sb_s * const sb, - FAR const char *data, const mfs_t datalen, - const off_t page, const mfs_t pgoff); - -/**************************************************************************** - * Name: mfs_read_page - * - * Description: - * Read a page. - * - * Input Parameters: - * sb - Superblock instance of the device. - * data - Buffer - * datalen - Length of buffer. - * pg - Page number. - * pgoff - Offset into the page. - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -ssize_t mfs_read_page(FAR const struct mfs_sb_s * const sb, - FAR char *data, const mfs_t datalen, const off_t page, - const mfs_t pgoff); - -/**************************************************************************** - * Name: mfs_erase_blk - * - * Description: - * Erase a block. - * - * Input Parameters: - * sb - Superblock instance of the device. - * blk - Block Number - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -int mfs_erase_blk(FAR const struct mfs_sb_s * const sb, const off_t blk); - -/**************************************************************************** - * Name: mfs_erase_nblks - * - * Description: - * Erase consecutive blocks. - * - * Input Parameters: - * sb - Superblock instance of the device. - * blk - Block Number - * - * Assumptions/Limitations: - * This assumes a locked environment when called. - * - ****************************************************************************/ - -int mfs_erase_nblks(FAR const struct mfs_sb_s * const sb, const off_t blk, - const size_t n); - -/* mnemofs_util.c */ - -/**************************************************************************** - * Name: mfs_arrhash - * - * Description: - * Returns an 8-bit hash of an entire array of data. - * - * Input Parameters: - * arr - Data array. - * len - Length of the array. - * - * Returned Value: - * 16-bit hash of the array. - * - ****************************************************************************/ - -uint16_t mfs_hash(FAR const char *arr, ssize_t len); - -/**************************************************************************** - * Name: mfs_ser_8 - * - * Description: - * Serialize a 8 bit type into output. - * - * Input Parameters: - * n - 8 bit to serialize - * out - Output array where to serialize. - * - * Returned Value: - * Pointer to byte after the end of serialized value. - * - ****************************************************************************/ - -FAR char *mfs_ser_8(const uint8_t n, FAR char * const out); - -/**************************************************************************** - * Name: mfs_deser_8 - * - * Description: - * Deserialize a 8 bit type from input. - * - * Input Parameters: - * in - Input array from where to deserialize. - * n - 8 bit to deserialize - * - * Returned Value: - * Pointer to byte after the end of serialized value. - * - ****************************************************************************/ - -FAR const char *mfs_deser_8(FAR const char * const in, uint8_t *n); - -/**************************************************************************** - * Name: mfs_ser_str - * - * Description: - * Serialize a string into output. - * - * Input Parameters: - * str - String to serialize - * len - Length of string - * out - Output array where to serialize. - * - * Returned Value: - * Pointer to byte after the end of serialized value. - * - ****************************************************************************/ - -FAR char *mfs_ser_str(FAR const char * const str, const mfs_t len, - FAR char * const out); - -/**************************************************************************** - * Name: mfs_deser_str - * - * Description: - * Deserialize a string from input. - * - * Input Parameters: - * in - Input array from where to deserialize. - * str - String to deserialize - * len - Length of string - * - * Returned Value: - * Pointer to byte after the end of serialized value. - * - ****************************************************************************/ - -FAR const char *mfs_deser_str(FAR const char * const in, - FAR char * const str, const mfs_t len); - -/**************************************************************************** - * Name: mfs_ser_mfs - * - * Description: - * Serialize a mfs_t type into output. - * - * Input Parameters: - * n - mfs_t to serialize - * out - Output array where to serialize. - * - * Returned Value: - * Pointer to byte after the end of serialized value. - * - ****************************************************************************/ - -FAR char *mfs_ser_mfs(const mfs_t n, FAR char * const out); - -/**************************************************************************** - * Name: mfs_deser_mfs - * - * Description: - * Deserialize a mfs_t type from input.. - * - * Input Parameters: - * in - Input array from where to deserialize. - * n - mfs_t to deserialize - * - * Returned Value: - * Pointer to byte after the end of serialized value. - * - ****************************************************************************/ - -FAR const char *mfs_deser_mfs(FAR const char * const in, - FAR mfs_t * const n); - -/**************************************************************************** - * Name: mfs_ser_ctz - * - * Description: - * Serialize a mfs_ctz_store_s type into output. - * - * Input Parameters: - * x - mfs_ctz_s to serialize - * out - Output array where to serialize. - * - * Returned Value: - * Pointer to byte after the end of serialized value. - * - ****************************************************************************/ - -FAR char *mfs_ser_ctz(FAR const struct mfs_ctz_s * const x, - FAR char * const out); - -/**************************************************************************** - * Name: mfs_deser_ctz - * - * Description: - * Deserialize a mfs_ctz_store_s type into output. - * - * Input Parameters: - * in - Input array from where to deserialize. - * x - mfs_ctz_s to deserialize - * - * Returned Value: - * Pointer to byte after the end of serialized value. - * - ****************************************************************************/ - -FAR const char *mfs_deser_ctz(FAR const char * const in, - FAR struct mfs_ctz_s * const x); - -FAR char *mfs_ser_path(FAR const struct mfs_path_s * const x, - FAR char * const out); - -FAR const char *mfs_deser_path(FAR const char * const in, - FAR struct mfs_path_s * const x); - -/**************************************************************************** - * Name: mfs_ser_timespec - * - * Description: - * Serialize timespec. - * - * Input Parameters: - * x - Value to serialize - * out - Output array where to serialize. - * - * Returned Value: - * Pointer to byte after the end of serialized value. - * - ****************************************************************************/ - -FAR char *mfs_ser_timespec(FAR const struct timespec * const x, - FAR char * const out); - -/**************************************************************************** - * Name: mfs_deser_timespec - * - * Description: - * Deserialize timespec. - * - * Input Parameters: - * in - Input array from where to deserialize. - * x - Value to deserialize - * - * Returned Value: - * Pointer to byte after the end of serialized value. - * - ****************************************************************************/ - -FAR const char *mfs_deser_timespec(FAR const char * const in, - FAR struct timespec * const x); - -/**************************************************************************** - * Name: mfs_ser_16 - * - * Description: - * Serialize 16 bit values. - * - * Input Parameters: - * x - Value to serialize - * out - Output array where to serialize. - * - * Returned Value: - * Pointer to byte after the end of serialized value. - * - ****************************************************************************/ - -FAR char *mfs_ser_16(const uint16_t n, FAR char * const out); - -/**************************************************************************** - * Name: mfs_deser_16 - * - * Description: - * Deserialize 16 bit value. - * - * Input Parameters: - * in - Input array from where to deserialize. - * x - Value to deserialize - * - * Returned Value: - * Pointer to byte after the end of serialized value. - * - ****************************************************************************/ - -FAR const char *mfs_deser_16(FAR const char * const in, FAR uint16_t *n); - -/**************************************************************************** - * Name: mfs_v2n - * - * Description: - * v2n(n) math function. - * - * Input Parameters: - * n - Number. - * - * Returned Value: - * v2n(n). - * - ****************************************************************************/ - -mfs_t mfs_v2n(mfs_t n); - -/**************************************************************************** - * Name: mfs_set_msb - * - * Description: - * The mosr significant set bit location. - * - * Input Parameters: - * n - Number. - * - * Returned Value: - * Number after setting the bit. - * - ****************************************************************************/ - -mfs_t mfs_set_msb(mfs_t n); - -bool mfs_ctz_eq(FAR const struct mfs_ctz_s * const a, - FAR const struct mfs_ctz_s * const b); - -bool mfs_path_eq(FAR const struct mfs_path_s * const a, - FAR const struct mfs_path_s * const b); - -/* mnemofs_ctz.c */ - -/**************************************************************************** - * Name: mfs_ctz_rdfromoff_new - * - * Description: - * Read from a specific offset into the data in a CTZ file. New version. - * - * Input Parameters: - * sb - Superblock instance of the device. - * ctz - CTZ list. - * data_off - Offset into the data. - * len - Length of the buffer. - * buf - Buffer to store read contents. - * - * Assumptions/Limitations: - * The CTZ list provided should be the updated location from the journal. - * - ****************************************************************************/ - -int mfs_ctz_rdfromoff(FAR const struct mfs_sb_s * const sb, - const struct mfs_ctz_s ctz, mfs_t data_off, - mfs_t len, FAR char * buf); - -/**************************************************************************** - * Name: mfs_ctz_wrtnode - * - * Description: - * Write an LRU node to the flash. It also adds a log of it to the journal. - * - * Input Parameters: - * sb - Superblock instance of the device. - * node - LRU node - * - * Assumptions/Limitations: - * - Assumes path is updated by journal. This will also write corresponding - * journal log. - * - * - This is the most computationally heavy part of the entire file system - * from a human POV, and one of the most for MCU or computer (as init - * methods are heavier). - * - ****************************************************************************/ - -int mfs_ctz_wrtnode(FAR struct mfs_sb_s * const sb, - FAR const struct mfs_node_s * const node, - FAR struct mfs_ctz_s *new_loc); - -/**************************************************************************** - * Name: mfs_ctz_travel - * - * Description: - * From CTZ block at page `pg_src` and index `idx_src`, give the page - * number of index `idx_dest`. - * - * The source is preferably the last CTZ block in the CTZ list, but it can - * realistically be any CTZ block in the CTZ list whose position is known. - * However, `idx_dest <= idx_src` has to be followed. Takes O(log(n)) - * complexity to travel. - * - * Input Parameters: - * sb - Superblock instance of the device. - * idx_src - Index of the source ctz block. - * pg_src - Page number of the source ctz block. - * idx_dest - Index of the destination ctz block. - * - * Returned Value: - * The page number corresponding to `idx_dest`. - * - * Assumptions/Limitations: - * `idx_dest <= idx_src`. - * - ****************************************************************************/ - -mfs_t mfs_ctz_travel(FAR const struct mfs_sb_s * const sb, - mfs_t idx_src, mfs_t pg_src, mfs_t idx_dest); - -/* mnemofs_lru.c */ - -/**************************************************************************** - * Name: mfs_lru_ctzflush - * - * Description: - * Flush the updates of a CTZ list inside the LRU to the flash. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - CTZ representation of the relpath. - * depth - Depth of path. - * - ****************************************************************************/ - -int mfs_lru_ctzflush(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, const mfs_t depth); - -/**************************************************************************** - * Name: mfs_lru_del - * - * Description: - * Delete instruction to LRU. - * - * Input Parameters: - * sb - Superblock instance of the device. - * off - Offset into the data. - * bytes - Number of bytes to delete. - * path - CTZ representation of the relpath. - * depth - Depth of path. - * - ****************************************************************************/ - -int mfs_lru_del(FAR struct mfs_sb_s * const sb, const mfs_t data_off, - mfs_t bytes, FAR struct mfs_path_s * const path, - const mfs_t depth); - -/**************************************************************************** - * Name: mfs_lru_wr - * - * Description: - * Write to LRU - * - * Input Parameters: - * sb - Superblock instance of the device. - * data_off - Offset into the data. - * bytes - Number of bytes to delete. - * path - CTZ representation of the relpath. - * depth - Depth of path. - * buf - Buffer. - * - ****************************************************************************/ - -int mfs_lru_wr(FAR struct mfs_sb_s * const sb, const mfs_t data_off, - mfs_t bytes, FAR struct mfs_path_s * const path, - const mfs_t depth, FAR const char *buf); - -/**************************************************************************** - * Name: mfs_lru_init - * - * Description: - * Initialize LRU. - * - * Input Parameters: - * sb - Superblock instance of the device. - * - ****************************************************************************/ - -void mfs_lru_init(FAR struct mfs_sb_s * const sb); - -/**************************************************************************** - * Name: mfs_lru_rdfromoff - * - * Description: - * Read updated data from an offset upto a certain number of bytes. - * - * Input Parameters: - * sb - Superblock instance of the device. - * data_off - Offset into the data. - * path - CTZ representation of the relpath. - * depth - Depth of path. - * buf - Buffer to populate with data. - * buflen - Length of data to read. - * - ****************************************************************************/ - -int mfs_lru_rdfromoff(FAR const struct mfs_sb_s * const sb, - const mfs_t data_off, - FAR struct mfs_path_s * const path, const mfs_t depth, - FAR char *buf, const mfs_t buflen); - -/**************************************************************************** - * Name: mfs_lru_getupdatedinfo - * - * Description: - * Update information of the path from the LRU. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - CTZ representation of the relpath. - * depth - Depth of path. - * - * Assumptions/Limitations: - * The will update path. - * - ****************************************************************************/ - -int mfs_lru_getupdatedinfo(FAR const struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth); - -/**************************************************************************** - * Name: mfs_lru_updatectz - * - * Description: - * Update CTZ location of an fs object. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - Old CTZ representation of the relpath. - * depth - Depth of path. - * new_ctz - New CTZ location. - * - * Assumptions/Limitations: - * The update the CTZ portion of the direntry in the parent. - * This updates the path as well. - * - ****************************************************************************/ - -int mfs_lru_updatectz(FAR struct mfs_sb_s * sb, - FAR struct mfs_path_s * const path, const mfs_t depth, - const struct mfs_ctz_s new_ctz, mfs_t new_sz); - -bool mfs_lru_isempty(FAR struct mfs_sb_s * const sb); -int mfs_lru_flush(FAR struct mfs_sb_s * const sb); - -/* mnemofs_master.c */ - -/**************************************************************************** - * Name: mfs_mn_init - * - * Description: - * Initialize master node by reading from the flash. - * - * Input Parameters: - * sb - Superblock instance of the device. - * jrnl_blk - First block of the journal. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * The journal will have to be initialized before this. - * - ****************************************************************************/ - -int mfs_mn_init(FAR struct mfs_sb_s * const sb, const mfs_t jrnl_blk); - -/**************************************************************************** - * Name: mfs_mn_fmt - * - * Description: - * Format a master node. - * - * Input Parameters: - * sb - Superblock instance of the device. - * jrnl_blk - First block of the journal. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * The journal will have to be formatted before this. - * - ****************************************************************************/ - -int mfs_mn_fmt(FAR struct mfs_sb_s * const sb, const mfs_t blk1, - const mfs_t blk2, const mfs_t jrnl_blk); - -/**************************************************************************** - * Name: mfs_mn_move - * - * Description: - * Move the master node. - * - * Input Parameters: - * sb - Superblock instance of the device. - * root - New location of the root of the file system. - * root_sz - New size of the CTZ list of the root of the file system. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * This is called when the root is updated to a new location. - * - ****************************************************************************/ - -int mfs_mn_move(FAR struct mfs_sb_s * const sb, struct mfs_ctz_s root, - const mfs_t root_sz); - -int mfs_mn_sync(FAR struct mfs_sb_s *sb, - FAR struct mfs_path_s * const new_loc, - const mfs_t blk1, const mfs_t blk2, const mfs_t jrnl_blk); - -/* mnemofs_fsobj.c */ - -/**************************************************************************** - * Name: mfs_path2childname - * - * Description: - * Given a path, point to the start of the name of the last fs object in - * that path (child). - * - * Input Parameters: - * relpath - Path. - * - * Returned Value: - * The pointer pointing to where the name of the child starts. - * - * Assumptions/Limitations: - * This does not allocate a new array for the name, but rather just points - * to the place in `relpath` where the name starts. - * - ****************************************************************************/ - -FAR const char * mfs_path2childname(FAR const char *relpath); - -/**************************************************************************** - * Name: mfs_get_fsz - * - * Description: - * Get updated file size of a file. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - CTZ representation of the path. - * depth - Depth of the path. - * - * Returned Value: - * The size of the file. - * - ****************************************************************************/ - -mfs_t mfs_get_fsz(FAR struct mfs_sb_s * const sb, - FAR const struct mfs_path_s * const path, - const mfs_t depth); - -/**************************************************************************** - * Name: mfs_get_patharr - * - * Description: - * Takes a relpath, and returns the CTZ location of every file system - * object that occurs in the relpath. This also returns a bit flag - * consisting of items from `MFS_PATH_FLAGS`. - * - * Input Parameters: - * sb - Superblock instance of the device. - * relpath - Path of the file. - * path - To populate with CTZ representation of the path. - * depth - To populate with depth of the path. - * - * Returned Value: - * 0 - OK - * < - Error - * - * Assumptions/Limitations: - * This allocates the `path` array in heap, and transfers the ownership - * of this array to the caller. It's the caller's responsibility to use - * this with `mfs_free_patharr`. - * - ****************************************************************************/ - -int mfs_get_patharr(FAR const struct mfs_sb_s * const sb, - FAR const char * relpath, FAR struct mfs_path_s **path, - FAR mfs_t *depth); - -/**************************************************************************** - * Name: mfs_free_patharr - * - * Description: - * Frees up a CTZ representation of the relpath. - * - * Input Parameters: - * path - CTZ representation of the relpath. - * - ****************************************************************************/ - -void mfs_free_patharr(FAR struct mfs_path_s *path); - -/**************************************************************************** - * Name: mfs_obj_isempty - * - * Description: - * Checks if a fs object is empty by reading its directory entry. - * - * Input Parameters: - * sb - Superblock instance of the device. - * pitr - Parent iterator pointing to the fs object. - * - * Returned Value: - * Is the file or directory pointer by the direntry empty. - * - * Assumptions/Limitations: - * The `pitr` should point to the fs object being checked. - * - ****************************************************************************/ - -bool mfs_obj_isempty(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s *path, - FAR struct mfs_pitr_s * const pitr); - -/**************************************************************************** - * Name: mfs_pitr_init - * - * Description: - * Initialize a parent iterator (pitr). This iterator starts at the start - * of the parent's directory file, and is used to iterate over the - * directory entries (direntries) the file has. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - CTZ representation of the relpath. - * depth - Depth of CTZ representation of the path. - * pitr - To initialize Parent iterator. - * child - If the child exists in the path. - * - * Assumptions/Limitations: - * - The `pitr` should point to the fs object being checked. - * - * - If the `child` is set to true, it assumes the child is present in the - * directory file. If not, it assumes it's not present in the path (due - * to whatever circumstances like it's not known if the child exists in - * the parent or not, search in parent file, etc.). - * - * - This assumes the pitr is initialized and freed in the same locked - * context. If, at all, this is not the case, then at the start of every - * new locked context till it's freed, this needs to be synced. - * - * - This also assumes that a CTZ will not change location before the pitr - * is destroyed. If at all a CTZ changes location, the pitr needs to be - * updated. - * - * - This contains a pitr with all fields set to 0 when it's invalid. - * - ****************************************************************************/ - -int mfs_pitr_init(FAR const struct mfs_sb_s * const sb, - FAR const struct mfs_path_s * const path, - const mfs_t depth, FAR struct mfs_pitr_s * const pitr, - bool child); - -/**************************************************************************** - * Name: mfs_pitr_free - * - * Description: - * Free an initialized pitr. - * - * Input Parameters: - * pitr - Parent iterator. - * - * Assumptions/Limitations: - * It's mainly for symbolic purpose, and the only thing it does is make all - * the members reset to 0. - * - ****************************************************************************/ - -void mfs_pitr_free(FAR const struct mfs_pitr_s * const pitr); - -/**************************************************************************** - * Name: mfs_pitr_adv - * - * Description: - * Advance a pitr to the next direntry. - * - * Input Parameters: - * sb - Superblock instance of the device. - * pitr - Parent iterator. - * - * Assumptions/Limitations: - * This is best used if we have not already read the value of a dirent. If - * we have, then we do it the `mfs_pitr_adv_dirent` way. - * - ****************************************************************************/ - -int mfs_pitr_adv(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s *path, - FAR struct mfs_pitr_s * const pitr); - -/**************************************************************************** - * Name: mfs_pitr_adv_bydirent - * - * Description: - * Advance a pitr to the next direntry by using current direntry. - * - * Input Parameters: - * pitr - Parent iterator. - * dirent - Current directory entry. - * - * Assumptions/Limitations: - * This is best used if we have already got the dirent by which we want to - * advance entry (the one pointed by pitr). If we don't, then we do it the - * `mfs_pitr_adv` way. - * - ****************************************************************************/ - -void mfs_pitr_adv_bydirent(FAR struct mfs_pitr_s * const pitr, - FAR const struct mfs_dirent_s * const dirent); - -/**************************************************************************** - * Name: mfs_pitr_adv_off - * - * Description: - * Advance a pitr by an offset. - * - * Input Parameters: - * pitr - Parent iterator. - * - * Assumptions/Limitations: - * This is best used if we have already got the offset by which we want to - * advance entry. This assumes that once the offset is applied, the new - * pitr location is valid, and start of a direntry. - * - ****************************************************************************/ - -void mfs_pitr_adv_off(FAR struct mfs_pitr_s * const pitr, - const mfs_t off); - -/**************************************************************************** - * Name: mfs_pitr_adv_tochild - * - * Description: - * Advance the pitr to point to the child mentioned in the path. - * - * Input Parameters: - * pitr - Parent iterator. - * path - CTZ representation of the relpath - * - * Assumptions/Limitations: - * This assumes that the pitr is initialized for the immediate parent of the - * child. - * - ****************************************************************************/ - -void mfs_pitr_adv_tochild(FAR struct mfs_pitr_s * const pitr, - FAR const struct mfs_path_s * const path); - -/**************************************************************************** - * Name: mfs_pitr_reset - * - * Description: - * Reset a pitr to point to the start of the CTZ file. - * - * Input Parameters: - * pitr - Parent iterator. - * - ****************************************************************************/ - -void mfs_pitr_reset(FAR struct mfs_pitr_s * const pitr); - -/**************************************************************************** - * Name: mfs_pitr_readdirent - * - * Description: - * Reads the direntry pointed to by the pitr. - * - * Input Parameters: - * sb - Superblock instance of the device. - * pitr - Parent Iterator. - * dirent - Direntry to populate. - * - * Returned Value: - * Length of the name of the FS object in the direntry. - * - * Assumptions/Limitations: - * This alloctes `dirent`, and transfers the ownership to the caller. It's - * the caller's responsibility to get it freed using mfs_free_dirent. - * - ****************************************************************************/ - -int mfs_pitr_readdirent(FAR const struct mfs_sb_s * const sb, - FAR struct mfs_path_s *path, - FAR struct mfs_pitr_s * const pitr, - FAR struct mfs_dirent_s **dirent); - -/**************************************************************************** - * Name: mfs_pitr_readdirent - * - * Description: - * Free an allocated direntry. - * - * Input Parameters: - * dirent - Direntry. - * - ****************************************************************************/ - -void mfs_free_dirent(FAR struct mfs_dirent_s *dirent); - -/**************************************************************************** - * Name: mfs_searchfopen - * - * Description: - * Checks if a file is already open. - * - * In mnemofs, this is done by iterating through the kernel list of open - * files. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - CTZ representation of the relpath. - * depth - Depth of path. - * - * Returned Value: - * True - File is already open. - * False - File is not open. - * - ****************************************************************************/ - -bool mfs_searchfopen(FAR const struct mfs_sb_s * const sb, - FAR const struct mfs_path_s * const path, - const mfs_t depth); - -/**************************************************************************** - * Name: mfs_pitr_appenddirent - * - * Description: - * Appeend a direntry at the end of the directory file of parent that's - * initialized in pitr. - * - * In mnemofs, this is done by iterating through the kernel list of open - * files. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - CTZ representation of the relpath. - * depth - Depth of path. - * pitr - Parent Iterator. - * dirent - Directory entry to be added. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -int mfs_pitr_appenddirent(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth, - FAR struct mfs_pitr_s * const pitr, - FAR const struct mfs_dirent_s * const dirent); - -/**************************************************************************** - * Name: mfs_pitr_appendnew - * - * Description: - * Appeend a new entry at the end of the directory file of parent that's - * initialized in pitr. This creates the direntry. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - CTZ representation of the relpath. - * depth - Depth of path. - * pitr - Parent Iterator. - * relpath - Relative path of the fs object. - * mode - Mode of the file/directory. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * This assumes that the fs object desired to be appended appears at the - * end of the entire relpath. - * - ****************************************************************************/ - -int mfs_pitr_appendnew(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, const mfs_t depth, - FAR struct mfs_pitr_s * const pitr, - FAR const char * const relpath, const mode_t mode); - -/**************************************************************************** - * Name: mfs_pitr_rmdirent - * - * Description: - * Removes the dirent from its parent's directory file. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - CTZ representation of the relpath. - * depth - Depth of path. - * pitr - Parent Iterator. - * dirent - Directory entry to be added. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -int mfs_pitr_rmdirent(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth, FAR struct mfs_pitr_s * const pitr, - FAR const struct mfs_dirent_s * const dirent); - -/**************************************************************************** - * Name: mfs_pitr_rm - * - * Description: - * Removes the fs object pointed by path from its parent's directory file. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - CTZ representation of the relpath. - * depth - Depth of path. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * This does not require any form of pitr initialization. It does it inside - * itself. - * - ****************************************************************************/ - -int mfs_pitr_rm(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth, bool rm_child); - -int mfs_pitr_traversefs(FAR struct mfs_sb_s * sb, const struct mfs_ctz_s ctz, - int type); - -#undef EXTERN -#ifdef __cplusplus -} -#endif +int mfs_is_bad_block(FAR const struct mfs_sb_s *sb, mfs_t blk); +int mfs_rwbuf_sync(FAR struct mfs_sb_s *sb); +void mfs_rwbuf_invalidate(FAR struct mfs_sb_s *sb); +int mfs_rwbuf_prepare_write(FAR struct mfs_sb_s *sb); +void mfs_rwbuf_discard_page(FAR struct mfs_sb_s *sb, mfs_t page); +void mfs_rwbuf_discard_block(FAR struct mfs_sb_s *sb, mfs_t block); +ssize_t mfs_write_page(FAR struct mfs_sb_s *sb, mfs_t page, + FAR const uint8_t *buffer); +ssize_t mfs_read_page(FAR struct mfs_sb_s *sb, mfs_t page, + FAR uint8_t *buffer); +int mfs_erase_blocks(FAR struct mfs_sb_s *sb, mfs_t startblk, + size_t nblocks); +int mfs_update_rootdir(FAR struct mfs_sb_s *sb, mfs_t rootdir); + +int mfs_alloc_init(FAR struct mfs_sb_s *sb); +void mfs_alloc_uninit(FAR struct mfs_sb_s *sb); +int mfs_alloc_page(FAR struct mfs_sb_s *sb, FAR mfs_t *page); +int mfs_alloc_block(FAR struct mfs_sb_s *sb, FAR mfs_t *block); +int mfs_release_page(FAR struct mfs_sb_s *sb, mfs_t page); +int mfs_report_page_deleted(FAR struct mfs_sb_s *sb, mfs_t page); +int mfs_report_block_deleted(FAR struct mfs_sb_s *sb, mfs_t block); + +mfs_t mfs_ctz_unit_data_area(FAR struct mfs_sb_s *sb, mfs_t index); +int mfs_ctz_traverse(FAR struct mfs_sb_s *sb, mfs_t startidx, + mfs_t endidx, mfs_t startpage, FAR mfs_t *endpage); +int mfs_ctz_index_from_off(FAR struct mfs_sb_s *sb, mfs_t offset, + FAR mfs_t *index, FAR mfs_t *pageoff); +int mfs_ctz_fill_next_ptrs(FAR struct mfs_sb_s *sb, mfs_t page, + mfs_t index, FAR uint8_t *buffer); + +mfs_t mfs_page_to_block(FAR const struct mfs_sb_s *sb, mfs_t page); + +int mfs_dirent_traverse(FAR struct mfs_sb_s *sb, FAR const char *relpath, + FAR mfs_t *location, + FAR struct mfs_direntry_s *direntry, + FAR struct mfs_dirloc_s *dirloc); +int mfs_dir_create_file(FAR struct mfs_sb_s *sb, FAR const char *relpath, + mode_t mode, FAR mfs_t *location, + FAR struct mfs_direntry_s *direntry, + FAR struct mfs_dirloc_s *dirloc); +int mfs_dir_update_file(FAR struct mfs_sb_s *sb, mfs_t oldloc, mfs_t newloc, + FAR const struct mfs_dirloc_s *dirloc, + FAR struct mfs_direntry_s *direntry, + off_t length); +int mfs_dir_truncate_file(FAR struct mfs_sb_s *sb, mfs_t location, + FAR const struct mfs_dirloc_s *dirloc, + FAR struct mfs_direntry_s *direntry, + off_t length); + +bool mfs_file_is_open(FAR const struct mfs_sb_s *sb, mfs_t fileloc); + +int mfs_file_open(FAR struct mfs_sb_s *sb, FAR struct file *filep, + FAR const char *relpath, int oflags, mode_t mode); +int mfs_file_close(FAR struct mfs_sb_s *sb, FAR struct file *filep); +ssize_t mfs_file_read(FAR struct mfs_sb_s *sb, FAR struct file *filep, + FAR char *buffer, size_t buflen); +ssize_t mfs_file_write(FAR struct mfs_sb_s *sb, FAR struct file *filep, + FAR const char *buffer, size_t buflen); +off_t mfs_file_seek(FAR struct mfs_sb_s *sb, FAR struct file *filep, + off_t offset, int whence); +int mfs_file_ioctl(FAR struct mfs_sb_s *sb, FAR struct file *filep, + int cmd, unsigned long arg); +int mfs_file_truncate(FAR struct mfs_sb_s *sb, FAR struct file *filep, + off_t length); +int mfs_file_sync(FAR struct mfs_sb_s *sb, FAR struct file *filep); +int mfs_file_dup(FAR struct mfs_sb_s *sb, FAR const struct file *oldp, + FAR struct file *newp); +int mfs_file_fstat(FAR struct mfs_sb_s *sb, FAR const struct file *filep, + FAR struct stat *buf); + +int mfs_dir_opendir(FAR struct mfs_sb_s *sb, FAR const char *relpath, + FAR struct fs_dirent_s **dir); +int mfs_dir_closedir(FAR struct mfs_sb_s *sb, + FAR struct fs_dirent_s *dir); +int mfs_dir_readdir(FAR struct mfs_sb_s *sb, FAR struct fs_dirent_s *dir, + FAR struct dirent *entry); +int mfs_dir_rewinddir(FAR struct mfs_sb_s *sb, + FAR struct fs_dirent_s *dir); +int mfs_dir_unlink(FAR struct mfs_sb_s *sb, FAR const char *relpath); +int mfs_dir_mkdir(FAR struct mfs_sb_s *sb, FAR const char *relpath, + mode_t mode); +int mfs_dir_rmdir(FAR struct mfs_sb_s *sb, FAR const char *relpath); +int mfs_dir_rename(FAR struct mfs_sb_s *sb, FAR const char *oldrelpath, + FAR const char *newrelpath); +int mfs_dir_stat(FAR struct mfs_sb_s *sb, FAR const char *relpath, + FAR struct stat *buf); + +extern const struct mountpt_operations g_mnemofs_operations; #endif /* __FS_MNEMOFS_MNEMOFS_H */ diff --git a/fs/mnemofs/mnemofs_alloc.c b/fs/mnemofs/mnemofs_alloc.c new file mode 100644 index 00000000000..44cda72ea26 --- /dev/null +++ b/fs/mnemofs/mnemofs_alloc.c @@ -0,0 +1,1343 @@ +/**************************************************************************** + * fs/mnemofs/mnemofs_alloc.c + * + * SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Alternatively, the contents of this file may be used under the terms of + * the BSD-3-Clause license: + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2024 Saurav Pal + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of its contributors may + * be used to endorse or promote products derived from this software + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include "fs_heap.h" +#include "mnemofs.h" + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static bool mfs_bitmap_get(FAR const uint8_t *bitmap, mfs_t page); +static void mfs_bitmap_set(FAR uint8_t *bitmap, mfs_t page); +static void mfs_bitmap_clear(FAR uint8_t *bitmap, mfs_t page); +static mfs_t mfs_first_data_block(FAR const struct mfs_sb_s *sb); +static mfs_t mfs_first_data_page(FAR const struct mfs_sb_s *sb); +static mfs_t mfs_next_data_block(FAR const struct mfs_sb_s *sb, mfs_t block); +static mfs_t mfs_next_data_page(FAR const struct mfs_sb_s *sb, mfs_t page); +static int mfs_alloc_validate_block(FAR const struct mfs_sb_s *sb, + mfs_t block); +static int mfs_alloc_validate_page(FAR const struct mfs_sb_s *sb, + mfs_t page); +static void mfs_alloc_mark_page_used(FAR struct mfs_sb_s *sb, mfs_t page); +static void mfs_alloc_mark_page_free(FAR struct mfs_sb_s *sb, mfs_t page); +static void mfs_alloc_mark_page_deleted(FAR struct mfs_sb_s *sb, mfs_t page); +static void mfs_alloc_mark_block_used(FAR struct mfs_sb_s *sb, mfs_t block); +static void mfs_alloc_mark_block_free(FAR struct mfs_sb_s *sb, mfs_t block); +static void mfs_alloc_mark_block_deleted(FAR struct mfs_sb_s *sb, + mfs_t block); +static bool mfs_alloc_block_is_free(FAR const struct mfs_sb_s *sb, + mfs_t block); +static bool mfs_alloc_block_is_deleted(FAR const struct mfs_sb_s *sb, + mfs_t block); +static int mfs_alloc_reclaim_block(FAR struct mfs_sb_s *sb, mfs_t block); +static int mfs_alloc_page_erased(FAR struct mfs_sb_s *sb, mfs_t page, + FAR bool *erased); +static FAR char *mfs_alloc_child_path(FAR const char *parent, + FAR const char *name); +static int mfs_alloc_reserve_live_tree(FAR struct mfs_sb_s *sb, + FAR const char *relpath); +static int mfs_alloc_choose_start_page(FAR struct mfs_sb_s *sb); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mfs_bitmap_get + * + * Description: + * Return the state of one bit in a page bitmap. + * + * Input Parameters: + * bitmap - The bitmap to inspect. + * page - The page number whose bit is requested. + * + * Returned Value: + * true is returned if the bit is set. false is returned otherwise. + * + ****************************************************************************/ + +static bool mfs_bitmap_get(FAR const uint8_t *bitmap, mfs_t page) +{ + return (bitmap[page >> 3] & (1u << (page & 7))) != 0; +} + +/**************************************************************************** + * Name: mfs_bitmap_set + * + * Description: + * Set one bit in a page bitmap. + * + * Input Parameters: + * bitmap - The bitmap to modify. + * page - The page number whose bit should be set. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_bitmap_set(FAR uint8_t *bitmap, mfs_t page) +{ + bitmap[page >> 3] |= (1u << (page & 7)); +} + +/**************************************************************************** + * Name: mfs_bitmap_clear + * + * Description: + * Clear one bit in a page bitmap. + * + * Input Parameters: + * bitmap - The bitmap to modify. + * page - The page number whose bit should be cleared. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_bitmap_clear(FAR uint8_t *bitmap, mfs_t page) +{ + bitmap[page >> 3] &= ~(1u << (page & 7)); +} + +/**************************************************************************** + * Name: mfs_first_data_block + * + * Description: + * Return the first block that may hold live file system data. + * + * Input Parameters: + * sb - The mounted file system instance. + * + * Returned Value: + * The first usable data block is returned on success. + * MFS_LOCATION_INVALID is returned if the geometry is not usable. + * + ****************************************************************************/ + +static mfs_t mfs_first_data_block(FAR const struct mfs_sb_s *sb) +{ + if (sb == NULL || sb->superblock + 1 >= MFS_BLOCK_COUNT(sb)) + { + return MFS_LOCATION_INVALID; + } + + return sb->superblock + 1; +} + +/**************************************************************************** + * Name: mfs_first_data_page + * + * Description: + * Return the first page that may hold live file system data. + * + * Input Parameters: + * sb - The mounted file system instance. + * + * Returned Value: + * The first usable data page is returned on success. + * MFS_LOCATION_INVALID is returned if no usable data block exists. + * + ****************************************************************************/ + +static mfs_t mfs_first_data_page(FAR const struct mfs_sb_s *sb) +{ + mfs_t block; + + block = mfs_first_data_block(sb); + if (block == MFS_LOCATION_INVALID) + { + return MFS_LOCATION_INVALID; + } + + return MFS_BLOCK_TO_PAGE(sb, block); +} + +/**************************************************************************** + * Name: mfs_next_data_block + * + * Description: + * Return the next usable data block in circular allocation order. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The current block in the scan. + * + * Returned Value: + * The next data block is returned on success. + * MFS_LOCATION_INVALID is returned if no usable data range exists. + * + ****************************************************************************/ + +static mfs_t mfs_next_data_block(FAR const struct mfs_sb_s *sb, mfs_t block) +{ + mfs_t firstblock; + + firstblock = mfs_first_data_block(sb); + if (firstblock == MFS_LOCATION_INVALID) + { + return MFS_LOCATION_INVALID; + } + + block++; + if (block >= MFS_BLOCK_COUNT(sb)) + { + return firstblock; + } + + if (block < firstblock) + { + return firstblock; + } + + return block; +} + +/**************************************************************************** + * Name: mfs_next_data_page + * + * Description: + * Return the next usable data page in circular allocation order. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The current page in the scan. + * + * Returned Value: + * The next data page is returned on success. + * MFS_LOCATION_INVALID is returned if no usable data range exists. + * + ****************************************************************************/ + +static mfs_t mfs_next_data_page(FAR const struct mfs_sb_s *sb, mfs_t page) +{ + mfs_t firstpage; + + firstpage = mfs_first_data_page(sb); + if (firstpage == MFS_LOCATION_INVALID) + { + return MFS_LOCATION_INVALID; + } + + if (page == MFS_LOCATION_INVALID) + { + return firstpage; + } + + page++; + if (page >= MFS_PAGE_COUNT(sb)) + { + return firstpage; + } + + if (mfs_page_to_block(sb, page) < mfs_first_data_block(sb)) + { + return firstpage; + } + + return page; +} + +/**************************************************************************** + * Name: mfs_alloc_validate_block + * + * Description: + * Validate that block lies inside the allocator-managed data range. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The block number to validate. + * + * Returned Value: + * Zero (OK) is returned if block is valid. -EINVAL is returned + * otherwise. + * + ****************************************************************************/ + +static int mfs_alloc_validate_block(FAR const struct mfs_sb_s *sb, + mfs_t block) +{ + mfs_t firstblock; + + if (sb == NULL || sb->freepages == NULL || sb->delpages == NULL) + { + ferr("allocator not initialized\n"); + return -EINVAL; + } + + firstblock = mfs_first_data_block(sb); + if (firstblock == MFS_LOCATION_INVALID) + { + ferr("no usable data block\n"); + return -EINVAL; + } + + if (block == MFS_LOCATION_INVALID || block >= MFS_BLOCK_COUNT(sb) || + block < firstblock) + { + ferr("invalid block\n"); + return -EINVAL; + } + + return OK; +} + +/**************************************************************************** + * Name: mfs_alloc_validate_page + * + * Description: + * Validate that page lies inside the allocator-managed data range. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The page number to validate. + * + * Returned Value: + * Zero (OK) is returned if page is valid. -EINVAL is returned + * otherwise. + * + ****************************************************************************/ + +static int mfs_alloc_validate_page(FAR const struct mfs_sb_s *sb, mfs_t page) +{ + mfs_t block; + + if (sb == NULL || sb->freepages == NULL || sb->delpages == NULL) + { + ferr("allocator not initialized\n"); + return -EINVAL; + } + + if (page == MFS_LOCATION_INVALID || page >= MFS_PAGE_COUNT(sb)) + { + ferr("invalid page\n"); + return -EINVAL; + } + + block = mfs_page_to_block(sb, page); + if (block == MFS_LOCATION_INVALID) + { + ferr("page has no valid block\n"); + return -EINVAL; + } + + return mfs_alloc_validate_block(sb, block); +} + +/**************************************************************************** + * Name: mfs_alloc_mark_page_used + * + * Description: + * Mark one page as allocated in the allocator bitmaps. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The page number to mark. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_alloc_mark_page_used(FAR struct mfs_sb_s *sb, mfs_t page) +{ + mfs_bitmap_clear(sb->freepages, page); + mfs_bitmap_clear(sb->delpages, page); +} + +/**************************************************************************** + * Name: mfs_alloc_mark_page_free + * + * Description: + * Mark one page as free in the allocator bitmaps. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The page number to mark. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_alloc_mark_page_free(FAR struct mfs_sb_s *sb, mfs_t page) +{ + mfs_bitmap_set(sb->freepages, page); + mfs_bitmap_clear(sb->delpages, page); +} + +/**************************************************************************** + * Name: mfs_alloc_mark_page_deleted + * + * Description: + * Mark one page as pending deletion in the allocator bitmaps. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The page number to mark. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_alloc_mark_page_deleted(FAR struct mfs_sb_s *sb, mfs_t page) +{ + mfs_bitmap_clear(sb->freepages, page); + mfs_bitmap_set(sb->delpages, page); +} + +/**************************************************************************** + * Name: mfs_alloc_mark_block_used + * + * Description: + * Mark every page in one block as allocated. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The block number to mark. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_alloc_mark_block_used(FAR struct mfs_sb_s *sb, mfs_t block) +{ + mfs_t page; + mfs_t endpage; + + page = MFS_BLOCK_TO_PAGE(sb, block); + endpage = page + MFS_PAGES_PER_BLOCK(sb); + for (; page < endpage; page++) + { + mfs_alloc_mark_page_used(sb, page); + } +} + +/**************************************************************************** + * Name: mfs_alloc_mark_block_free + * + * Description: + * Mark every page in one block as free. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The block number to mark. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_alloc_mark_block_free(FAR struct mfs_sb_s *sb, mfs_t block) +{ + mfs_t page; + mfs_t endpage; + + page = MFS_BLOCK_TO_PAGE(sb, block); + endpage = page + MFS_PAGES_PER_BLOCK(sb); + for (; page < endpage; page++) + { + mfs_alloc_mark_page_free(sb, page); + } +} + +/**************************************************************************** + * Name: mfs_alloc_mark_block_deleted + * + * Description: + * Mark every page in one block as pending deletion. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The block number to mark. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_alloc_mark_block_deleted(FAR struct mfs_sb_s *sb, + mfs_t block) +{ + mfs_t page; + mfs_t endpage; + + page = MFS_BLOCK_TO_PAGE(sb, block); + endpage = page + MFS_PAGES_PER_BLOCK(sb); + for (; page < endpage; page++) + { + mfs_alloc_mark_page_deleted(sb, page); + } +} + +/**************************************************************************** + * Name: mfs_alloc_block_is_free + * + * Description: + * Check whether every page in block is currently marked free. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The block number to inspect. + * + * Returned Value: + * true is returned if the whole block is free. false is returned + * otherwise. + * + ****************************************************************************/ + +static bool mfs_alloc_block_is_free(FAR const struct mfs_sb_s *sb, + mfs_t block) +{ + mfs_t page; + mfs_t endpage; + + page = MFS_BLOCK_TO_PAGE(sb, block); + endpage = page + MFS_PAGES_PER_BLOCK(sb); + for (; page < endpage; page++) + { + if (!mfs_bitmap_get(sb->freepages, page)) + { + return false; + } + } + + return true; +} + +/**************************************************************************** + * Name: mfs_alloc_block_is_deleted + * + * Description: + * Check whether every page in block is currently marked deleted. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The block number to inspect. + * + * Returned Value: + * true is returned if the whole block is pending deletion. false is + * returned otherwise. + * + ****************************************************************************/ + +static bool mfs_alloc_block_is_deleted(FAR const struct mfs_sb_s *sb, + mfs_t block) +{ + mfs_t page; + mfs_t endpage; + + page = MFS_BLOCK_TO_PAGE(sb, block); + endpage = page + MFS_PAGES_PER_BLOCK(sb); + for (; page < endpage; page++) + { + if (!mfs_bitmap_get(sb->delpages, page)) + { + return false; + } + } + + return true; +} + +/**************************************************************************** + * Name: mfs_alloc_reclaim_block + * + * Description: + * Erase one block if every page in it has been marked deleted, then + * return it to the free bitmap. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The block number to reclaim. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * validation or erase failure. + * + ****************************************************************************/ + +static int mfs_alloc_reclaim_block(FAR struct mfs_sb_s *sb, mfs_t block) +{ + int ret; + + ret = mfs_alloc_validate_block(sb, block); + if (ret < 0) + { + ferr("mfs_alloc_validate_block failed: %d\n", ret); + return ret; + } + + if (!mfs_alloc_block_is_deleted(sb, block)) + { + return OK; + } + + ret = mfs_erase_blocks(sb, block, 1); + if (ret < 0) + { + ferr("mfs_erase_blocks failed: %d\n", ret); + return ret; + } + + if (ret != 1) + { + ferr("short erase: %d\n", ret); + return -EIO; + } + + mfs_alloc_mark_block_free(sb, block); + return OK; +} + +/**************************************************************************** + * Name: mfs_alloc_page_erased + * + * Description: + * Read a page and report whether it is still in the erased state. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The page number to inspect. + * erased - The location to receive the erased-state result. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mfs_alloc_page_erased(FAR struct mfs_sb_s *sb, mfs_t page, + FAR bool *erased) +{ + ssize_t nread; + size_t i; + + if (sb == NULL || erased == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + nread = mfs_read_page(sb, page, sb->rwbuf); + if (nread < 0) + { + ferr("mfs_read_page failed: %zd\n", nread); + return nread; + } + + if (nread != 1) + { + ferr("short read: %zd\n", nread); + return -EIO; + } + + * erased = true; + for (i = 0; i < MFS_PAGE_SIZE(sb); i++) + { + if (sb->rwbuf[i] != sb->erasestate) + { + * erased = false; + break; + } + } + + return OK; +} + +/**************************************************************************** + * Name: mfs_alloc_child_path + * + * Description: + * Allocate `parent/name` as one new relative path string. + * + * Input Parameters: + * parent - The parent path prefix. + * name - The child name to append. + * + * Returned Value: + * A newly allocated path string is returned on success. NULL is returned + * on invalid input or allocation failure. + * + ****************************************************************************/ + +static FAR char *mfs_alloc_child_path(FAR const char *parent, + FAR const char *name) +{ + FAR char *path; + size_t parentlen; + size_t namelen; + + if (parent == NULL || name == NULL) + { + ferr("invalid args\n"); + return NULL; + } + + parentlen = strlen(parent); + namelen = strlen(name); + path = kmm_malloc(parentlen + namelen + 2); + if (path == NULL) + { + ferr("kmm_malloc failed\n"); + return NULL; + } + + if (parentlen == 0) + { + memcpy(path, name, namelen + 1); + return path; + } + + memcpy(path, parent, parentlen); + path[parentlen] = '/'; + memcpy(path + parentlen + 1, name, namelen + 1); + return path; +} + +/**************************************************************************** + * Name: mfs_alloc_reserve_live_tree + * + * Description: + * Walk the live directory tree rooted at relpath and mark each reachable + * page or block as allocated in the allocator bitmaps. + * + * Input Parameters: + * sb - The mounted file system instance. + * relpath - The relative path that roots the subtree to reserve. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * traversal, allocation, or directory-read failure. + * + ****************************************************************************/ + +static int mfs_alloc_reserve_live_tree(FAR struct mfs_sb_s *sb, + FAR const char *relpath) +{ + struct mfs_direntry_s direntry; + struct mfs_dirloc_s dirloc; + FAR struct fs_dirent_s *dir = NULL; + FAR char *childpath = NULL; + FAR char *dirpath = NULL; + struct dirent entry; + mfs_t location; + int ret; + int closeret; + + ret = mfs_dirent_traverse(sb, relpath, &location, &direntry, &dirloc); + if (ret < 0) + { + ferr("mfs_dirent_traverse failed: %d\n", ret); + return ret; + } + + if (!S_ISDIR(direntry.mode)) + { + mfs_alloc_mark_page_used(sb, location); + return OK; + } + + mfs_alloc_mark_block_used(sb, location); + ret = mfs_dir_opendir(sb, relpath, &dir); + if (ret < 0) + { + ferr("mfs_dir_opendir failed: %d\n", ret); + return ret; + } + + for (; ; ) + { + ret = mfs_dir_readdir(sb, dir, &entry); + if (ret == -ENOENT) + { + ret = OK; + goto errout_with_dir; + } + + if (ret < 0) + { + ferr("mfs_dir_readdir failed: %d\n", ret); + goto errout_with_dir; + } + + childpath = mfs_alloc_child_path(relpath, entry.d_name); + if (childpath == NULL) + { + ferr("mfs_alloc_child_path failed\n"); + ret = -ENOMEM; + goto errout_with_dir; + } + + ret = mfs_alloc_reserve_live_tree(sb, childpath); + kmm_free(childpath); + childpath = NULL; + if (ret < 0) + { + ferr("reserve child failed: %d\n", ret); + goto errout_with_dir; + } + } + +errout_with_dir: + kmm_free(childpath); + if (dir != NULL) + { + dirpath = dir->fd_path; + closeret = mfs_dir_closedir(sb, dir); + fs_heap_free(dirpath); + if (ret >= 0) + { + ret = closeret; + } + } + + if (ret < 0) + { + ferr("failed: %d\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: mfs_alloc_choose_start_page + * + * Description: + * Choose a random starting block for future allocation scans. The first + * usable data block found from that random point becomes the next + * allocation position. + * + * Input Parameters: + * sb - The mounted file system instance. + * + * Returned Value: + * Zero (OK) is returned on success. -ENOSPC is returned if no usable + * data block exists. A negated errno value is returned on other + * failures. + * + ****************************************************************************/ + +static int mfs_alloc_choose_start_page(FAR struct mfs_sb_s *sb) +{ + mfs_t firstblock; + mfs_t nblocks; + mfs_t start; + mfs_t block; + mfs_t i; + int bad; + + if (sb == NULL) + { + ferr("invalid sb\n"); + return -EINVAL; + } + + firstblock = mfs_first_data_block(sb); + if (firstblock == MFS_LOCATION_INVALID) + { + ferr("no usable data block\n"); + return -ENOSPC; + } + + nblocks = MFS_BLOCK_COUNT(sb) - firstblock; + start = firstblock + (arc4random() % nblocks); + + for (i = 0, block = start; i < nblocks; i++) + { + bad = mfs_is_bad_block(sb, block); + if (bad < 0) + { + ferr("mfs_is_bad_block failed: %d\n", bad); + return bad; + } + + if (bad == 0) + { + sb->nextpage = MFS_BLOCK_TO_PAGE(sb, block); + return OK; + } + + block = mfs_next_data_block(sb, block); + } + + ferr("no usable start block\n"); + return -ENOSPC; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mfs_alloc_init + * + * Description: + * Initialize allocator state for the mounted volume. This builds the + * free and pending-delete bitmaps, reserves the currently live file + * system tree, and selects a random starting point for future scans. + * + * Input Parameters: + * sb - The mounted file system instance. + * + * Returned Value: + * Zero (OK) is returned on success. -ENOMEM or -ENOSPC may be returned + * for resource exhaustion. A negated errno value is returned on other + * failures. + * + ****************************************************************************/ + +int mfs_alloc_init(FAR struct mfs_sb_s *sb) +{ + mfs_t firstblock; + mfs_t block; + mfs_t page; + mfs_t endpage; + bool erased; + int ret; + int bad; + + if (sb == NULL || sb->rwbuf == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + firstblock = mfs_first_data_block(sb); + if (firstblock == MFS_LOCATION_INVALID) + { + ferr("no usable data block\n"); + return -ENOSPC; + } + + sb->bitmapsize = (MFS_PAGE_COUNT(sb) + 7) / 8; + sb->nextpage = MFS_LOCATION_INVALID; + sb->freepages = kmm_zalloc(sb->bitmapsize); + if (sb->freepages == NULL) + { + ferr("free bitmap allocation failed\n"); + ret = -ENOMEM; + goto errout; + } + + sb->delpages = kmm_zalloc(sb->bitmapsize); + if (sb->delpages == NULL) + { + ferr("deleted bitmap allocation failed\n"); + ret = -ENOMEM; + goto errout; + } + + for (block = 0; block < MFS_BLOCK_COUNT(sb); block++) + { + bad = mfs_is_bad_block(sb, block); + if (bad < 0) + { + ferr("mfs_is_bad_block failed: %d\n", bad); + ret = bad; + goto errout; + } + + if (bad != 0 || block < firstblock) + { + mfs_alloc_mark_block_used(sb, block); + continue; + } + + page = MFS_BLOCK_TO_PAGE(sb, block); + endpage = page + MFS_PAGES_PER_BLOCK(sb); + for (; page < endpage; page++) + { + ret = mfs_alloc_page_erased(sb, page, &erased); + if (ret < 0) + { + ferr("mfs_alloc_page_erased failed: %d\n", ret); + goto errout; + } + + if (erased) + { + mfs_alloc_mark_page_free(sb, page); + } + } + } + + ret = mfs_alloc_reserve_live_tree(sb, ""); + if (ret < 0) + { + ferr("mfs_alloc_reserve_live_tree failed: %d\n", ret); + goto errout; + } + + ret = mfs_alloc_choose_start_page(sb); + if (ret < 0) + { + ferr("mfs_alloc_choose_start_page failed: %d\n", ret); + goto errout; + } + + return OK; + +errout: + ferr("failed: %d\n", ret); + mfs_alloc_uninit(sb); + return ret; +} + +/**************************************************************************** + * Name: mfs_alloc_uninit + * + * Description: + * Release allocator-owned bitmap state. + * + * Input Parameters: + * sb - The mounted file system instance. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +void mfs_alloc_uninit(FAR struct mfs_sb_s *sb) +{ + if (sb == NULL) + { + return; + } + + kmm_free(sb->freepages); + kmm_free(sb->delpages); + sb->freepages = NULL; + sb->delpages = NULL; + sb->bitmapsize = 0; + sb->nextpage = MFS_LOCATION_INVALID; +} + +/**************************************************************************** + * Name: mfs_alloc_page + * + * Description: + * Allocate one page using a circular scan that begins at the current + * allocator position. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The location to receive the allocated page number. + * + * Returned Value: + * Zero (OK) is returned on success. -ENOSPC is returned if no free page + * is available. A negated errno value is returned on other failures. + * + ****************************************************************************/ + +int mfs_alloc_page(FAR struct mfs_sb_s *sb, FAR mfs_t *page) +{ + mfs_t firstblock; + mfs_t candidate; + mfs_t npages; + mfs_t i; + + if (sb == NULL || page == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + * page = MFS_LOCATION_INVALID; + if (sb->freepages == NULL || sb->delpages == NULL || + sb->nextpage == MFS_LOCATION_INVALID) + { + ferr("allocator not initialized\n"); + return -EINVAL; + } + + firstblock = mfs_first_data_block(sb); + if (firstblock == MFS_LOCATION_INVALID) + { + ferr("no usable data block\n"); + return -ENOSPC; + } + + npages = (MFS_BLOCK_COUNT(sb) - firstblock) * + MFS_PAGES_PER_BLOCK(sb); + candidate = sb->nextpage; + + for (i = 0; i < npages; i++) + { + if (mfs_bitmap_get(sb->freepages, candidate)) + { + mfs_alloc_mark_page_used(sb, candidate); + * page = candidate; + sb->nextpage = mfs_next_data_page(sb, candidate); + return OK; + } + + candidate = mfs_next_data_page(sb, candidate); + } + + ferr("no free page\n"); + return -ENOSPC; +} + +/**************************************************************************** + * Name: mfs_alloc_block + * + * Description: + * Allocate one whole directory block. The scan follows the allocator's + * circular position, but accepts only blocks whose every page is free. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The location to receive the allocated block number. + * + * Returned Value: + * Zero (OK) is returned on success. -ENOSPC is returned if no free + * block is available. A negated errno value is returned on other + * failures. + * + ****************************************************************************/ + +int mfs_alloc_block(FAR struct mfs_sb_s *sb, FAR mfs_t *block) +{ + mfs_t firstblock; + mfs_t candidate; + mfs_t nblocks; + mfs_t i; + + if (sb == NULL || block == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + * block = MFS_LOCATION_INVALID; + if (sb->freepages == NULL || sb->delpages == NULL || + sb->nextpage == MFS_LOCATION_INVALID) + { + ferr("allocator not initialized\n"); + return -EINVAL; + } + + firstblock = mfs_first_data_block(sb); + if (firstblock == MFS_LOCATION_INVALID) + { + ferr("no usable data block\n"); + return -ENOSPC; + } + + candidate = mfs_page_to_block(sb, sb->nextpage); + if (candidate == MFS_LOCATION_INVALID) + { + ferr("invalid next page\n"); + return -EINVAL; + } + + if (sb->nextpage != MFS_BLOCK_TO_PAGE(sb, candidate)) + { + candidate = mfs_next_data_block(sb, candidate); + } + + nblocks = MFS_BLOCK_COUNT(sb) - firstblock; + for (i = 0; i < nblocks; i++) + { + if (mfs_alloc_block_is_free(sb, candidate)) + { + mfs_alloc_mark_block_used(sb, candidate); + * block = candidate; + sb->nextpage = MFS_BLOCK_TO_PAGE(sb, + mfs_next_data_block(sb, + candidate)); + return OK; + } + + candidate = mfs_next_data_block(sb, candidate); + } + + ferr("no free block\n"); + return -ENOSPC; +} + +/**************************************************************************** + * Name: mfs_release_page + * + * Description: + * Return a page to the free bitmap immediately. This is intended for + * rollback of allocations that were not yet committed into directory + * state. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The page number to release. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_release_page(FAR struct mfs_sb_s *sb, mfs_t page) +{ + int ret; + + ret = mfs_alloc_validate_page(sb, page); + if (ret < 0) + { + ferr("mfs_alloc_validate_page failed: %d\n", ret); + return ret; + } + + mfs_rwbuf_discard_page(sb, page); + mfs_alloc_mark_page_free(sb, page); + return OK; +} + +/**************************************************************************** + * Name: mfs_report_page_deleted + * + * Description: + * Mark one committed page as obsolete. If this makes every page in the + * containing block obsolete, then the block is erased immediately and + * returned to the free bitmap. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The page number that became obsolete. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_report_page_deleted(FAR struct mfs_sb_s *sb, mfs_t page) +{ + mfs_t block; + int ret; + + ret = mfs_alloc_validate_page(sb, page); + if (ret < 0) + { + ferr("mfs_alloc_validate_page failed: %d\n", ret); + return ret; + } + + if (mfs_bitmap_get(sb->freepages, page)) + { + ferr("page already free\n"); + return -EINVAL; + } + + mfs_rwbuf_discard_page(sb, page); + block = mfs_page_to_block(sb, page); + mfs_alloc_mark_page_deleted(sb, page); + ret = mfs_alloc_reclaim_block(sb, block); + if (ret < 0) + { + ferr("mfs_alloc_reclaim_block failed: %d\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: mfs_report_block_deleted + * + * Description: + * Mark every page in a committed block as obsolete and reclaim the block + * immediately once all pages in it are scheduled for deletion. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The block number that became obsolete. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_report_block_deleted(FAR struct mfs_sb_s *sb, mfs_t block) +{ + int ret; + + ret = mfs_alloc_validate_block(sb, block); + if (ret < 0) + { + ferr("mfs_alloc_validate_block failed: %d\n", ret); + return ret; + } + + mfs_rwbuf_discard_block(sb, block); + mfs_alloc_mark_block_deleted(sb, block); + ret = mfs_alloc_reclaim_block(sb, block); + if (ret < 0) + { + ferr("mfs_alloc_reclaim_block failed: %d\n", ret); + } + + return ret; +} diff --git a/fs/mnemofs/mnemofs_blkalloc.c b/fs/mnemofs/mnemofs_blkalloc.c deleted file mode 100644 index fdf972125b3..00000000000 --- a/fs/mnemofs/mnemofs_blkalloc.c +++ /dev/null @@ -1,580 +0,0 @@ -/**************************************************************************** - * fs/mnemofs/mnemofs_blkalloc.c - * - * SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. The - * ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Alternatively, the contents of this file may be used under the terms of - * the BSD-3-Clause license: - * - * SPDX-License-Identifier: BSD-3-Clause - * - * Copyright (c) 2024 Saurav Pal - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the author nor the names of its contributors may - * be used to endorse or promote products derived from this software - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - ****************************************************************************/ - -/* mnemofs block allocator takes some inspiration from littlefs's block - * allocator. - * - * It has two primary jobs...provide a block and ensure wear levelling. The - * block allocator of mnemofs tries to provide a block that will more or less - * ensure wear levelling. We'll call the block allocator as BA. - * - * The block allocator starts at a random block in the device and starts a - * circular allocation from there, ie. it allocated sequentially till it - * reaches the end, at which point it cycles back to the beginning and then - * continues allocating sequentially. If a page is requested it will check if - * the page has been written to (being used). If a page is being written to - * but all the pages in a block are ready to be erased, then the block is - * erased and page is allocated. If none of these two conditions match, it - * moves on to check the next page and so on. If the block that contains the - * page is a bad block, the BA skips all the pages in the entire block. - * - * The BA can also grant a request for an entire block. If the BA is - * currently in the middle of a block, it will skip the remaining pages till - * it reaches the start of the next block. These pages won't be reflected as - * being used, and can be allocated the next time the BA cycles back to these - * pages. Even though skipped pages will be eventually utilized later anyway, - * block allocation requests are made by very few critical data structures - * in mnemofs, and they all do it in bulk, and thus skipped pages are - * minimal. - */ - -/**************************************************************************** - * Included Files - ****************************************************************************/ - -#include -#include -#include -#include - -#include "mnemofs.h" -#include "fs_heap.h" - -/**************************************************************************** - * Pre-processor Definitions - ****************************************************************************/ - -#define BMAP_GET(bmap, idx, off) (((bmap)[(idx)] & (1 << (off))) != 0) -#define BMAP_SET(bmap, idx, off) ((bmap)[(idx)] |= (1 << (off))) -#define DEL_ARR_BLK(sb, blk) (MFS_BA((sb)).k_del[(blk) * sizeof(size_t)]) -#define DEL_ARR_PG(sb, pg) (DEL_ARR_BLK(sb, MFS_PG2BLK((sb), (pg)))) - -/**************************************************************************** - * Private Types - ****************************************************************************/ - -/**************************************************************************** - * Private Function Prototypes - ****************************************************************************/ - -static inline void pg2bmap(mfs_t pg, FAR mfs_t *idx, FAR uint8_t *off); -static int is_pg_writeable(FAR struct mfs_sb_s * const sb, mfs_t pg, - FAR mfs_t *idx, FAR uint8_t *off); -static int is_blk_writeable(FAR struct mfs_sb_s * const sb, - const mfs_t blk); - -/**************************************************************************** - * Private Data - ****************************************************************************/ - -/**************************************************************************** - * Public Data - ****************************************************************************/ - -/**************************************************************************** - * Private Functions - ****************************************************************************/ - -/**************************************************************************** - * Name: pg2bmap - * - * Description: - * Gets the bitmap location of a page. The page in the bitmap will be in - * bmap[idx] byte at (1 << off) position in the byte. - * - * Input Parameters: - * pg - Page number to check. - * idx - Populated later with the index of page in MFS_BA(sb).bmap_upgs - * off - Populated later with the offset of page in MFS_BA(sb).bmap_upgs - * - * Assumptions/Limitations: - * Does not check validity of the index. - * - ****************************************************************************/ - -static inline void pg2bmap(mfs_t pg, FAR mfs_t *idx, FAR uint8_t *off) -{ - /* The compiler should automatically use shift operation for division. */ - - *idx = pg / 8; - *off = pg % 8; -} - -/**************************************************************************** - * Name: is_pg_writeable - * - * Description: - * Checks if a page is writeable by checking if the page is either free, or - * it's being used but the entire block is ready for erase. - * - * Input Parameters: - * sb - Superblock instance of the device. - * pg - Page number to check. - * idx - Populated later with the index of page in MFS_BA(sb).bmap_upgs - * off - Populated later with the offset of page in MFS_BA(sb).bmap_upgs - * - * Returned Value: - * MFS_BLK_BAD - If the block of the page is a bad block. - * MFS_PG_USED - If the page is being used. - * MFS_BLK_ERASABLE - If page can be allocated, but block needs erase. - * MFS_PG_FREE - If the page is free. - * -ENOSYS - Not supported. - * - * Assumptions/Limitations: - * Assumes this is run in a locked environment. - * - ****************************************************************************/ - -static int is_pg_writeable(FAR struct mfs_sb_s * const sb, mfs_t pg, - FAR mfs_t *idx, FAR uint8_t *off) -{ - int blkbad_status; - - /* Bad block check. */ - - blkbad_status = mfs_isbadblk(sb, MFS_PG2BLK(sb, pg)); - if (predict_false(blkbad_status == -ENOSYS)) - { - return blkbad_status; - } - - if (predict_false(blkbad_status < 0) || blkbad_status == 1) - { - return MFS_BLK_BAD; - } - - pg2bmap(MFS_BA(sb).c_pg, idx, off); - - if (BMAP_GET(MFS_BA(sb).bmap_upgs, *idx, *off)) - { - if (DEL_ARR_PG(sb, MFS_BA(sb).c_pg) == MFS_PGINBLK(sb)) - { - return MFS_BLK_ERASABLE; - } - else - { - return MFS_PG_USED; - } - } - else - { - return MFS_PG_FREE; - } -} - -/**************************************************************************** - * Name: is_blk_writeable - * - * Description: - * Checks if an entire block is allocatable, either because none of the - * pages in it have been allocated, or because the entire block can be - * erased. - * - * Input Parameters: - * sb - Superblock instance of the device. - * pg - Page number to check. - * idx - Populated later with the index of page in MFS_BA(sb).bmap_upgs - * off - Populated later with the offset of page in MFS_BA(sb).bmap_upgs - * - * Returned Value: - * MFS_BLK_BAD - If the block is a bad block. - * MFS_BLK_USED - If the block is being used. - * MFS_BLK_ERASABLE - If block can be allocated, but block needs erase. - * MFS_BLK_FREE - If the block is free. - * - * Assumptions/Limitations: - * Assumes this is run in a locked environment. - * - ****************************************************************************/ - -static int is_blk_writeable(FAR struct mfs_sb_s * const sb, const mfs_t blk) -{ - int blkbad_status; - mfs_t i; - mfs_t pg = MFS_BLK2PG(sb, blk); - mfs_t idx; - uint8_t off; - - /* Bad block check. */ - - blkbad_status = mfs_isbadblk(sb, blk); - if (predict_false(blkbad_status == -ENOSYS)) - { - return blkbad_status; - } - - if (predict_false(blkbad_status < 0) || blkbad_status == 1) - { - return MFS_BLK_BAD; - } - - for (i = 0; i < MFS_PGINBLK(sb); i++) - { - pg2bmap(pg + i, &idx, &off); - - if (BMAP_GET(MFS_BA(sb).bmap_upgs, idx, off)) - { - if (DEL_ARR_PG(sb, MFS_BA(sb).c_pg) == MFS_PGINBLK(sb)) - { - return MFS_BLK_ERASABLE; - } - else - { - return MFS_BLK_USED; - } - } - } - - return MFS_BLK_FREE; -} - -/**************************************************************************** - * Public Functions - ****************************************************************************/ - -int mfs_ba_fmt(FAR struct mfs_sb_s * const sb) -{ - int ret = OK; - - /* We need at least 5 blocks, as one is occupied by superblock, at least - * one for the journal, 2 for journal's master blocks, and at least one for - * actual data. - */ - - if (MFS_NBLKS(sb) < 5) - { - ret = -ENOSPC; - goto errout; - } - - memset(&MFS_BA(sb), 0, sizeof(MFS_BA(sb))); - - MFS_BA(sb).s_blk = rand() % MFS_NBLKS(sb); - if (MFS_PG2BLK(sb, MFS_BA(sb).s_blk) == sb->sb_blk) - { - MFS_BA(sb).s_blk++; - MFS_BA(sb).s_blk %= MFS_NBLKS(sb); - } - - MFS_BA(sb).c_pg = MFS_BLK2PG(sb, MFS_BA(sb).s_blk); - - /* MFS_BA(sb).k_del_elemsz = ((log + 7) & (-8)) / 8; */ - - MFS_BA(sb).k_del = fs_heap_zalloc(sizeof(size_t) * MFS_NBLKS(sb)); - if (predict_false(MFS_BA(sb).k_del == NULL)) - { - ret = -ENOMEM; - goto errout; - } - - MFS_BA(sb).n_bmap_upgs = MFS_UPPER8(MFS_NPGS(sb)); - - MFS_BA(sb).bmap_upgs = fs_heap_zalloc(MFS_BA(sb).n_bmap_upgs); - if (predict_false(MFS_BA(sb).bmap_upgs == NULL)) - { - ret = -ENOMEM; - goto errout_with_k_del; - } - - /* TODO: Do not start from journal blocks. */ - - finfo("mnemofs: Block Allocator initialized, starting at page %d.\n", - MFS_BLK2PG(sb, MFS_BA(sb).s_blk)); - return ret; - -errout_with_k_del: - fs_heap_free(MFS_BA(sb).k_del); - -errout: - return ret; -} - -int mfs_ba_init(FAR struct mfs_sb_s * const sb) -{ - /* TODO: Ensure journal and master node are initialized before this. */ - - int ret = OK; - - ret = mfs_ba_fmt(sb); - if (predict_false(ret < 0)) - { - goto errout; - } - - /* Traverse the FS tree. */ - - ret = mfs_pitr_traversefs(sb, MFS_MN(sb).root_ctz, MFS_ISDIR); - if (predict_false(ret < 0)) - { - goto errout_with_ba; - } - - return ret; - -errout_with_ba: - mfs_ba_free(sb); - -errout: - return ret; -} - -void mfs_ba_free(FAR struct mfs_sb_s * const sb) -{ - fs_heap_free(MFS_BA(sb).k_del); - fs_heap_free(MFS_BA(sb).bmap_upgs); - - finfo("Block Allocator Freed."); -} - -mfs_t mfs_ba_getpg(FAR struct mfs_sb_s * const sb) -{ - bool inc = true; - bool found = false; - mfs_t i = MFS_BA(sb).c_pg; - mfs_t pg = 0; - mfs_t idx; - mfs_t tpgs = MFS_NBLKS(sb) * MFS_PGINBLK(sb); - uint8_t off; - - for (; i != tpgs; i++) - { - switch (is_pg_writeable(sb, MFS_BA(sb).c_pg, &idx, &off)) - { - case MFS_PG_USED: - finfo("Used %d\n", MFS_BA(sb).c_pg); - break; - - case MFS_PG_FREE: - finfo("Free %d\n", MFS_BA(sb).c_pg); - pg = MFS_BA(sb).c_pg; - mfs_ba_markusedpg(sb, pg); - found = true; - break; - - case MFS_BLK_BAD: - finfo("Bad %d\n", MFS_BA(sb).c_pg); - - /* Skip pages to next block. */ - - MFS_BA(sb).c_pg = MFS_BLK2PG(sb, - (MFS_PG2BLK(sb, MFS_BA(sb).c_pg) + 1) % - MFS_NBLKS(sb)); - inc = false; - break; - - case MFS_BLK_ERASABLE: - finfo("Erasable %d\n", MFS_BA(sb).c_pg); - pg = MFS_BA(sb).c_pg; - mfs_erase_blk(sb, MFS_PG2BLK(sb, MFS_BA(sb).c_pg)); - DEL_ARR_PG(sb, MFS_BA(sb).c_pg) = 0; - mfs_ba_markusedpg(sb, pg); - found = true; - break; - - case -ENOSYS: - - /* TODO: Manually check for bad blocks. */ - - return 0; - } - - if (inc) - { - MFS_BA(sb).c_pg++; - MFS_BA(sb).c_pg %= tpgs; - } - else - { - i--; - inc = true; - } - - if (found) - { - break; - } - } - - if (!found) - { - DEBUGASSERT(pg == 0); - finfo("No more pages found. Page: %u.", pg); - } - - return pg; -} - -mfs_t mfs_ba_getblk(FAR struct mfs_sb_s * const sb) -{ - bool found = false; - mfs_t i = 0; - mfs_t blk; - mfs_t ret = 0; - - blk = MFS_PG2BLK(sb, MFS_BA(sb).c_pg); - if (MFS_BA(sb).c_pg % MFS_PGINBLK(sb)) - { - /* Skipped pages are not updated in used. */ - - blk++; - blk %= MFS_NBLKS(sb); - i++; - } - - for (; i < MFS_NBLKS(sb); i++) - { - switch (is_blk_writeable(sb, blk)) - { - case MFS_BLK_BAD: - break; - - case MFS_BLK_USED: - break; - - case MFS_BLK_ERASABLE: - mfs_ba_blkmarkdel(sb, blk); - mfs_ba_markusedblk(sb, blk); - found = true; - break; - - case MFS_BLK_FREE: - mfs_ba_markusedblk(sb, blk); - found = true; - break; - - case -ENOSYS: - - /* TODO: Manually check for bad blocks. */ - - return 0; - } - - if (found) - { - break; - } - - blk++; - blk %= MFS_NBLKS(sb); - } - - if (found) - { - ret = blk; - MFS_BA(sb).c_pg = MFS_BLK2PG(sb, (++blk) % MFS_NBLKS(sb)); - } - - finfo("Block number: %u. Found: %d.", ret, found); - - return ret; -} - -void mfs_ba_pgmarkdel(FAR struct mfs_sb_s * const sb, mfs_t pg) -{ - DEL_ARR_PG(sb, MFS_BA(sb).c_pg)++; -} - -void mfs_ba_blkmarkdel(FAR struct mfs_sb_s * const sb, mfs_t blk) -{ - DEL_ARR_BLK(sb, blk) = MFS_PGINBLK(sb); -} - -int mfs_ba_delmarked(FAR struct mfs_sb_s * const sb) -{ - int ret = OK; - mfs_t i; - - for (i = 1; i < MFS_NBLKS(sb); i++) - { - if (DEL_ARR_BLK(sb, i) == MFS_PGINBLK(sb)) - { - ret = mfs_erase_blk(sb, i); - - if (ret != OK) - { - return ret; - } - } - } - - return ret; -} - -/* Mark a page as being used. Used by master node during initial format and - */ - -void mfs_ba_markusedpg(FAR struct mfs_sb_s * const sb, mfs_t pg) -{ - mfs_t idx; - uint8_t off; - - pg2bmap(pg, &idx, &off); - BMAP_SET(MFS_BA(sb).bmap_upgs, idx, off); /* Set as used */ -} - -void mfs_ba_markusedblk(FAR struct mfs_sb_s * const sb, mfs_t blk) -{ - mfs_t i = 0; - mfs_t pg = MFS_BLK2PG(sb, blk); - - for (i = 0; i < MFS_PGINBLK(sb); i++) - { - mfs_ba_markusedpg(sb, pg + i); - } -} - -mfs_t mfs_ba_getavailpgs(FAR const struct mfs_sb_s * const sb) -{ - /* TODO */ - - return 0; -} diff --git a/fs/mnemofs/mnemofs_ctz.c b/fs/mnemofs/mnemofs_ctz.c index 5f84dcd4427..96595d77ce6 100644 --- a/fs/mnemofs/mnemofs_ctz.c +++ b/fs/mnemofs/mnemofs_ctz.c @@ -50,613 +50,490 @@ * ****************************************************************************/ -/**************************************************************************** - * In mnemofs, the files and directories use the CTZ skip list data structure - * defined by littlefs. These are reverse skip lists with a specific number - * of pointers for each block. The number of pointers for a block at index - * `x` is `ctz(x) + 1`. There are no pointers if the index is 0. - * - * The pointers all point to some CTZ block other than the CTZ block they are - * part of. The `k`th pointer of a CTZ block at index `x` points to the - * CTZ block at index `x - 2^k`. - * - * For example, CTZ block at index 2 has 2 pointers, and they point to the - * block at index 1, and index 0 respectively. - * - * File/Dir Ptr - * | - * V - * +------+ +------+ +------+ +------+ +------+ +------+ - * | |<--| |---| |---| |---| | | | - * | Node |<--| Node |---| Node |<--| Node |---| Node | | Node | - * | 0 |<--| 1 |<--| 2 |<--| 3 |<--| 4 |<--| 5 | - * +------+ +------+ +------+ +------+ +------+ +------+ - * - * In mnemofs, each CTZ block is stored in a page on the flash. All code in - * this entire file will call CTZ blocks as blocks to honour the original - * naming, and will specify wherever it deviates from this assumption. - * - * Littlefs's design documentation lists all the benefits that this data - * structure brings to the table when it comes to storing large pieces of - * data that will be modified considerably frequently, while being in a - * Copy On Write (CoW) environment. - * - * In mnemofs, the CTZ methods only interface with the underlying R/W methods - * , journal on the lower side and on the upper side, the LRU, and ensures - * that whatever data it provides considers both the on-flash data, as well - * the journal logs. - * - * The pointers are stored such that the first pointer, which points to - * (x - 2^0), is stored at the very end of the CTZ block. The second pointer - * is stored second last, and so on. - * - ****************************************************************************/ - /**************************************************************************** * Included Files ****************************************************************************/ -#include -#include +#include #include -#include -#include -#include +#include +#include #include "mnemofs.h" -#include "fs_heap.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ -#define MFS_CTZ_PTRSZ (sizeof(mfs_t)) - -/**************************************************************************** - * Private Types - ****************************************************************************/ +#define MFS_CTZ_PTR_SIZE sizeof(mfs_t) /**************************************************************************** * Private Function Prototypes ****************************************************************************/ -static mfs_t ctz_idx_nptrs(const mfs_t idx); -static void ctz_off2loc(FAR const struct mfs_sb_s * const sb, mfs_t off, - FAR mfs_t *idx, FAR mfs_t *pgoff); -static mfs_t ctz_blkdatasz(FAR const struct mfs_sb_s * const sb, - const mfs_t idx); -static void ctz_copyidxptrs(FAR const struct mfs_sb_s * const sb, - struct mfs_ctz_s ctz, const mfs_t idx, - FAR char *buf); - -/**************************************************************************** - * Private Data - ****************************************************************************/ - -/**************************************************************************** - * Public Data - ****************************************************************************/ +static int mfs_ctz_nptrs(mfs_t index); +static int mfs_ctz_read_ptr(FAR struct mfs_sb_s *sb, mfs_t page, + unsigned int pow, FAR uint8_t *scratch, + FAR mfs_t *ptrpage); +static void mfs_ctz_write_ptr(FAR struct mfs_sb_s *sb, FAR uint8_t *buffer, + unsigned int pow, mfs_t page); +static int mfs_ctz_follow(FAR struct mfs_sb_s *sb, mfs_t startidx, + mfs_t endidx, mfs_t startpage, + FAR uint8_t *scratch, + FAR mfs_t *endpage); /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** - * Name: ctz_idx_nptrs + * Name: mfs_ctz_nptrs * * Description: - * Gives the numbers of pointers that a CTZ block of given index should - * have. + * Return the number of backward pointers stored in CTZ unit index. * * Input Parameters: - * idx - Index of the ctz block. + * index - The CTZ unit index to inspect. * * Returned Value: - * The number of pointers in the CTZ block. + * The number of pointers stored in the unit is returned. * ****************************************************************************/ -static mfs_t ctz_idx_nptrs(const mfs_t idx) +static int mfs_ctz_nptrs(mfs_t index) { - mfs_t ret; + int nptrs; - ret = (idx == 0) ? 0 : mfs_ctz(idx) + 1; - finfo("Number of pointers for %u index is %u.", idx, ret); - return ret; + if (index == 0) + { + return 0; + } + + nptrs = 1; + while ((index & 1) == 0) + { + nptrs++; + index >>= 1; + } + + return nptrs; } /**************************************************************************** - * Name: ctz_off2loc + * Name: mfs_ctz_read_ptr * * Description: - * Converts ctz offset (which is the offset of the data stored in the ctz - * list, which is unaware of the presence of pointers) into the CTZ - * block index and the offset in that CTZ block. + * Read one backward pointer from the pointer area of a CTZ unit. * * Input Parameters: - * sb - Superblock instance of the device. - * off - Offset of the data stored in the CTZ list. - * idx - Index of the CTZ block, to be populated. - * pgoff - Offset inside the CTZ block, to be populated. - * - ****************************************************************************/ - -static void ctz_off2loc(FAR const struct mfs_sb_s * const sb, mfs_t off, - FAR mfs_t *idx, FAR mfs_t *pgoff) -{ - const mfs_t wb = sizeof(mfs_t); - const mfs_t den = MFS_PGSZ(sb) - 2 * wb; - - if (off < den) - { - *idx = 0; - *pgoff = off; - return; - } - - if (idx != NULL) - { - *idx = (off - wb * (__builtin_popcount((off / den) - 1) + 2)) / den; - } - - if (pgoff != NULL) - { - *pgoff = off - den * (*idx) - wb * __builtin_popcount(*idx) - - (ctz_idx_nptrs(*idx) * wb); - } - - finfo("Offset %u. Calculated index %u and page offset %u.", off, *idx, - *pgoff); -} - -/**************************************************************************** - * Name: ctz_blkdatasz - * - * Description: - * The size of data in B that can be fit inside a CTZ block at index `idx`. - * - * Input Parameters: - * sb - Superblock instance of the device. - * idx - Index of the ctz block. + * sb - The mounted file system instance. + * page - The CTZ unit page to read. + * pow - The pointer slot number to decode. + * scratch - A page-sized scratch buffer. + * ptrpage - The location to receive the decoded pointer target page. * * Returned Value: - * The size of data in the CTZ block. + * Zero (OK) is returned on success. A negated errno value is returned if + * the inputs are invalid, the page cannot be read, or the decoded pointer + * is outside the valid page range. * ****************************************************************************/ -static mfs_t ctz_blkdatasz(FAR const struct mfs_sb_s * const sb, - const mfs_t idx) +static int mfs_ctz_read_ptr(FAR struct mfs_sb_s *sb, mfs_t page, + unsigned int pow, FAR uint8_t *scratch, + FAR mfs_t *ptrpage) { - mfs_t ret; + size_t offset; + ssize_t nread; + mfs_t raw; - ret = MFS_PGSZ(sb) - (ctz_idx_nptrs(idx) * MFS_LOGPGSZ(sb)); - finfo("Block data size for index %u is %u.", idx, ret); - return ret; + if (sb == NULL || scratch == NULL || ptrpage == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (page == MFS_LOCATION_INVALID || page >= MFS_PAGE_COUNT(sb)) + { + ferr("invalid page\n"); + return -EINVAL; + } + + if ((pow + 1) * MFS_CTZ_PTR_SIZE > MFS_PAGE_SIZE(sb)) + { + ferr("invalid pointer slot\n"); + return -EINVAL; + } + + nread = mfs_read_page(sb, page, scratch); + if (nread < 0) + { + ferr("mfs_read_page failed: %zd\n", nread); + return nread; + } + + if (nread != 1) + { + ferr("short read: %zd\n", nread); + return -EIO; + } + + offset = MFS_PAGE_SIZE(sb) - ((pow + 1) * MFS_CTZ_PTR_SIZE); + memcpy(&raw, scratch + offset, sizeof(raw)); + * ptrpage = le32toh(raw); + if (*ptrpage == MFS_LOCATION_INVALID || *ptrpage >= MFS_PAGE_COUNT(sb)) + { + ferr("invalid pointer target\n"); + return -EIO; + } + + return OK; } /**************************************************************************** - * Name: ctz_copyidxptrs + * Name: mfs_ctz_write_ptr * * Description: - * This is used for cases when you want to expand a CTZ list from any point - * in the list. If we want to expand the CTZ list from a particular index, - * say `start_idx`, while keeping all indexes before it untouched, we - * would need to first allocate new blocks on the flash, and then copy - * the pointers to the location. - * - * Usage of this function is, the caller needs to first allocate a CTZ - * block (a page on flash), allocate buffer which is the size of a CTZ - * block (a page on flash), and use this method to copy the pointers to the - * buffer, then write the data to the flash. + * Encode one backward pointer into the pointer area of a CTZ unit buffer. * * Input Parameters: - * sb - Superblock instance of the device. - * ctz - CTZ list to use as a reference. - * idx - Index of the block who's supposed pointers are to be copied. - * buf - Buffer representing the entire CTZ block where pointers are - * copied to. + * sb - The mounted file system instance. + * buffer - The page-sized CTZ unit buffer to modify. + * pow - The pointer slot number to write. + * page - The target page to encode. * - * Assumptions/Limitations: - * This assumes `idx` is not more than `ctz->idx_e + 1`. + * Returned Value: + * None. * ****************************************************************************/ -static void ctz_copyidxptrs(FAR const struct mfs_sb_s * const sb, - struct mfs_ctz_s ctz, const mfs_t idx, - FAR char *buf) +static void mfs_ctz_write_ptr(FAR struct mfs_sb_s *sb, FAR uint8_t *buffer, + unsigned int pow, mfs_t page) { - mfs_t i; - mfs_t n_ptrs; - mfs_t prev_pg; - mfs_t prev_idx; + size_t offset; + mfs_t raw; - if (idx == 0) + DEBUGASSERT(sb != NULL && buffer != NULL); + DEBUGASSERT((pow + 1) * MFS_CTZ_PTR_SIZE <= MFS_PAGE_SIZE(sb)); + + offset = MFS_PAGE_SIZE(sb) - ((pow + 1) * MFS_CTZ_PTR_SIZE); + raw = htole32(page); + memcpy(buffer + offset, &raw, sizeof(raw)); +} + +/**************************************************************************** + * Name: mfs_ctz_follow + * + * Description: + * Walk backward through a CTZ chain from startidx/startpage until endidx + * is reached. + * + * Input Parameters: + * sb - The mounted file system instance. + * startidx - The index of the known CTZ unit. + * endidx - The target earlier index to reach. + * startpage - The page of the known CTZ unit. + * scratch - A page-sized scratch buffer. + * endpage - The location to receive the page at endidx. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * invalid input or if any pointer read fails. + * + ****************************************************************************/ + +static int mfs_ctz_follow(FAR struct mfs_sb_s *sb, mfs_t startidx, + mfs_t endidx, mfs_t startpage, + FAR uint8_t *scratch, + FAR mfs_t *endpage) +{ + mfs_t diff; + mfs_t page; + mfs_t idx; + unsigned int pow; + unsigned int maxpow; + int ret; + + if (sb == NULL || scratch == NULL || endpage == NULL) { - /* No pointers for first block. */ - - return; + ferr("invalid args\n"); + return -EINVAL; } - n_ptrs = ctz_idx_nptrs(idx); - - if (idx != ctz.idx_e + 1) + if (startpage == MFS_LOCATION_INVALID || startpage >= MFS_PAGE_COUNT(sb) || + startidx < endidx) { - /* We travel to the second last "known" CTZ block. */ - - ctz.pg_e = mfs_ctz_travel(sb, ctz.idx_e, ctz.pg_e, idx - 1); - ctz.idx_e = idx - 1; + ferr("invalid traversal request\n"); + return -EINVAL; } - buf += MFS_PGSZ(sb); /* Go to buf + pg_sz */ - - DEBUGASSERT(idx == ctz.idx_e + 1); - - finfo("Copying %u pointers for CTZ (%u, %u) at index %u.", n_ptrs, - ctz.idx_e, ctz.pg_e, idx); - - for (i = 0; i < n_ptrs; i++) + page = startpage; + idx = startidx; + while (idx > endidx) { - if (predict_false(i == 0)) + diff = idx - endidx; + pow = (sizeof(diff) * 8) - 1 - __builtin_clz(diff); + maxpow = __builtin_ctz(idx); + if (pow > maxpow) { - prev_idx = ctz.idx_e; - prev_pg = ctz.pg_e; - } - else - { - prev_pg = mfs_ctz_travel(sb, prev_idx, prev_pg, prev_idx - 1); - prev_idx--; + pow = maxpow; } - ctz.idx_e = prev_idx; + DEBUGASSERT(pow < (unsigned int)mfs_ctz_nptrs(idx)); + ret = mfs_ctz_read_ptr(sb, page, pow, scratch, &page); + if (ret < 0) + { + ferr("mfs_ctz_read_ptr failed: %d\n", ret); + return ret; + } - /* Do buf + pg_sz - (idx * sizeof(mfs_t)) iteratively. */ - - buf -= MFS_CTZ_PTRSZ; - mfs_ser_mfs(prev_pg, buf); - - finfo("Copied %u page number to %uth pointer.", prev_pg, i); + idx -= (mfs_t)1 << pow; } + + * endpage = page; + return OK; } /**************************************************************************** * Public Functions ****************************************************************************/ -int mfs_ctz_rdfromoff(FAR const struct mfs_sb_s * const sb, - const struct mfs_ctz_s ctz, mfs_t data_off, - mfs_t len, FAR char * buf) +/**************************************************************************** + * Name: mfs_ctz_traverse + * + * Description: + * Traverse a CTZ chain from one known unit index/page to an earlier + * index. + * + * Input Parameters: + * sb - The mounted file system instance. + * startidx - The index of the known CTZ unit. + * endidx - The earlier index to reach. + * startpage - The page of the known CTZ unit. + * endpage - The location to receive the resolved page. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * invalid input, allocation failure, or traversal failure. + * + ****************************************************************************/ + +int mfs_ctz_traverse(FAR struct mfs_sb_s *sb, mfs_t startidx, mfs_t endidx, + mfs_t startpage, FAR mfs_t *endpage) { - int ret = OK; - mfs_t i; - mfs_t cur_pg; - mfs_t cur_idx; - mfs_t cur_pgoff; - mfs_t end_idx; - mfs_t end_pgoff; - mfs_t pg_rd_sz; + FAR uint8_t *scratch; + int ret; - finfo("Reading (%u, %u) CTZ from %u offset for %u bytes.", ctz.idx_e, - ctz.pg_e, data_off, len); - - if (ctz.idx_e == 0 && ctz.pg_e == 0) + if (sb == NULL || endpage == NULL || startpage == MFS_LOCATION_INVALID || + startpage >= MFS_PAGE_COUNT(sb) || startidx < endidx) { - goto errout; + ferr("invalid args\n"); + return -EINVAL; } - ctz_off2loc(sb, data_off + len, &cur_idx, &cur_pgoff); - ctz_off2loc(sb, data_off, &end_idx, &end_pgoff); - - DEBUGASSERT(ctz.idx_e < cur_idx); /* TODO: Need to consider this. For now, there is a temporary fix in read(). */ - if (ctz.idx_e < end_idx) + scratch = kmm_malloc(MFS_PAGE_SIZE(sb)); + if (scratch == NULL) { - goto errout; + ferr("kmm_malloc failed\n"); + return -ENOMEM; } - cur_pg = mfs_ctz_travel(sb, ctz.idx_e, ctz.pg_e, cur_idx); - - if (predict_false(cur_pg == 0)) + ret = mfs_ctz_follow(sb, startidx, endidx, startpage, scratch, endpage); + if (ret < 0) { - goto errout; + ferr("mfs_ctz_follow failed: %d\n", ret); } - /* O(n) read by reading in reverse. */ - - finfo("Started reading. Current Idx: %u, End Idx: %u.", cur_idx, end_idx); - - if (cur_idx != end_idx) - { - for (i = cur_idx; i >= end_idx; i--) - { - finfo("Current index %u, Current Page %u.", i, cur_pg); - - if (predict_false(i == cur_idx)) - { - pg_rd_sz = cur_pgoff; - ret = mfs_read_page(sb, buf - pg_rd_sz, pg_rd_sz, cur_pg, - 0); - cur_pgoff = 0; - } - else if (predict_false(i == end_idx)) - { - pg_rd_sz = ctz_blkdatasz(sb, i) - end_pgoff; - ret = mfs_read_page(sb, buf - pg_rd_sz, pg_rd_sz, cur_pg, - end_pgoff); - } - else - { - pg_rd_sz = ctz_blkdatasz(sb, i); - ret = mfs_read_page(sb, buf - pg_rd_sz, pg_rd_sz, cur_pg, - 0); - } - - if (predict_false(ret == 0)) - { - ret = -EINVAL; - goto errout; - } - - buf -= pg_rd_sz; - } - - cur_pg = mfs_ctz_travel(sb, cur_idx, cur_pg, cur_idx - 1); - if (predict_false(cur_pg == 0)) - { - ret = -EINVAL; - goto errout; - } - } - else - { - ret = mfs_read_page(sb, buf, len, cur_pg, end_pgoff); - if (predict_false(ret < 0)) - { - goto errout; - } - - ret = OK; - } - - finfo("Reading finished."); - -errout: + kmm_free(scratch); return ret; } -int mfs_ctz_wrtnode(FAR struct mfs_sb_s * const sb, - FAR const struct mfs_node_s * const node, - FAR struct mfs_ctz_s *new_loc) +/**************************************************************************** + * Name: mfs_ctz_unit_data_area + * + * Description: + * Return the number of payload bytes available in CTZ unit index after + * its backward pointers are reserved. + * + * Input Parameters: + * sb - The mounted file system instance. + * index - The CTZ unit index to inspect. + * + * Returned Value: + * The number of payload bytes available in the unit is returned. + * + ****************************************************************************/ + +mfs_t mfs_ctz_unit_data_area(FAR struct mfs_sb_s *sb, mfs_t index) { - int ret = OK; - bool written = false; - mfs_t prev; - mfs_t rem_sz; - mfs_t new_pg; - mfs_t cur_pg; - mfs_t cur_idx; - mfs_t cur_pgoff; - mfs_t lower; - mfs_t upper; - mfs_t upper_og; - mfs_t lower_upd; - mfs_t upper_upd; - mfs_t del_bytes; - FAR char *buf = NULL; - FAR char *tmp = NULL; - struct mfs_ctz_s ctz; - FAR struct mfs_delta_s *delta; - - finfo("Write LRU node %p at depth %u.", node, node->depth); - - /* Traverse common CTZ blocks. */ - - ctz_off2loc(sb, node->range_min, &cur_idx, &cur_pgoff); - ctz = node->path[node->depth - 1].ctz; - cur_pg = mfs_ctz_travel(sb, ctz.idx_e, ctz.pg_e, cur_idx); - - /* So, till cur_idx - 1, the CTZ blocks are common. */ - - buf = fs_heap_zalloc(MFS_PGSZ(sb)); - if (predict_false(buf == NULL)) - { - ret = -ENOMEM; - goto errout; - } - - /* Initially, there might be some offset in cur_idx CTZ blocks that is - * unmodified as well. - */ - - finfo("Initial read."); - tmp = buf; - mfs_read_page(sb, tmp, cur_pgoff, cur_pg, 0); - tmp += cur_pgoff; - - /* Modifications. */ - - prev = 0; - rem_sz = node->sz; - lower = node->range_min; - del_bytes = 0; - - /* [lower, upper) range. Two pointer approach. Window gets narrower - * for every delete falling inside it. - */ - - while (rem_sz > 0) - { - upper = MIN(prev + lower + ctz_blkdatasz(sb, cur_idx), rem_sz); - upper_og = upper; - - finfo("Remaining Size %" PRIu32 ". Lower %" PRIu32 ", Upper %" PRIu32 - ", Current Offset %zd.", rem_sz, lower, upper, tmp - buf); - - /* Retrieving original data. */ - - ret = mfs_ctz_rdfromoff(sb, ctz, lower + del_bytes, upper - lower, - tmp); - if (predict_false(ret < 0)) - { - goto errout_with_buf; - } - - list_for_every_entry(&node->delta, delta, struct mfs_delta_s, list) - { - finfo("Checking delta %p in node %p. Offset %" PRIu32 ", bytes %" - PRIu32, delta, node, delta->off, delta->n_b); - - lower_upd = MAX(lower, delta->off); - upper_upd = MIN(upper, delta->off + delta->n_b); - - if (lower_upd >= upper_upd) - { - /* Skip this delta. */ - - continue; - } - - if (delta->upd == NULL) - { - finfo("Node type: Delete"); - - /* Delete */ - - del_bytes += upper_upd - lower_upd; - memmove(tmp + (lower_upd - lower), tmp + (upper_upd - lower), - upper - upper_upd); - upper -= upper_upd; - } - else - { - finfo("Node type: Update"); - - /* Update */ - - memcpy(tmp + (lower_upd - lower), - delta->upd + (lower_upd - delta->off), - upper_upd - lower_upd); - } - } - - /* rem_sz check for final write. */ - - if (upper == upper_og || rem_sz == upper - lower) - { - prev = 0; - - /* Time to write a page for new CTZ list. */ - - new_pg = mfs_ba_getpg(sb); - if (predict_false(new_pg == 0)) - { - ret = -ENOSPC; - goto errout_with_buf; - } - - ctz_copyidxptrs(sb, ctz, cur_idx, buf); - - ret = mfs_write_page(sb, buf, MFS_PGSZ(sb), new_pg, 0); - if (predict_false(ret == 0)) - { - ret = -EINVAL; - goto errout_with_buf; - } - - memset(buf, 0, MFS_PGSZ(sb)); - tmp = buf; - ctz.idx_e = cur_idx; - ctz.pg_e = new_pg; - cur_idx++; - - written = true; - - finfo("Written data to page %" PRIu32, new_pg); - } - else - { - tmp += upper - lower; - - written = false; - } - - prev = upper - lower; - rem_sz -= upper - lower; - lower = upper; - } - - DEBUGASSERT(written); - - /* TODO: Need to verify for cases where the delete extends outside, etc. */ - - /* Write log. Assumes journal has enough space due to the limit. */ - - finfo("Writing log."); - *new_loc = ctz; - ret = mfs_jrnl_wrlog(sb, node, ctz, node->sz); - if (predict_false(ret < 0)) - { - goto errout_with_buf; - } - -errout_with_buf: - fs_heap_free(buf); - -errout: - return ret; + return (mfs_t)MFS_PAGE_SIZE(sb) - ((mfs_t)mfs_ctz_nptrs(index) * 4); } -mfs_t mfs_ctz_travel(FAR const struct mfs_sb_s * const sb, - mfs_t idx_src, mfs_t pg_src, mfs_t idx_dest) +/**************************************************************************** + * Name: mfs_ctz_index_from_off + * + * Description: + * Convert a logical file offset into the CTZ unit index and byte offset + * inside that unit. + * + * Input Parameters: + * sb - The mounted file system instance. + * offset - The logical file offset to translate. + * index - The location to receive the CTZ unit index. + * pageoff - The location to receive the byte offset within that unit. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * invalid input or overflow. + * + ****************************************************************************/ + +int mfs_ctz_index_from_off(FAR struct mfs_sb_s *sb, mfs_t offset, + FAR mfs_t *index, + FAR mfs_t *pageoff) { - char buf[4]; - mfs_t pg; mfs_t idx; - mfs_t pow; - mfs_t diff; - mfs_t max_pow; + mfs_t area; - /* Rising phase. */ - - max_pow = (sizeof(mfs_t) * 8) - mfs_clz(idx_src ^ idx_dest); - idx = idx_src; - pow = 1; - pg = pg_src; - - for (pow = mfs_ctz(idx); pow < max_pow - 1; pow = mfs_ctz(idx)) + if (sb == NULL || index == NULL || pageoff == NULL) { - mfs_read_page(sb, buf, 4, pg, MFS_PGSZ(sb) - (4 * pow)); - mfs_deser_mfs(buf, &pg); - idx -= (1 << pow); + ferr("invalid args\n"); + return -EINVAL; + } - if (pg == 0) + idx = 0; + while (true) + { + area = mfs_ctz_unit_data_area(sb, idx); + if (area == 0) { - return 0; + ferr("zero data area\n"); + return -EINVAL; } - } - if (idx == idx_dest) - { - return pg; - } - - /* Falling phase. */ - - diff = idx - idx_dest; - - for (pow = mfs_set_msb(diff); diff != 0; pow = mfs_set_msb(diff)) - { - mfs_read_page(sb, buf, 4, pg, MFS_PGSZ(sb) - (4 * pow)); - mfs_deser_mfs(buf, &pg); - idx -= (1 << pow); - diff -= (1 << pow); - - if (pg == 0) + if (offset < area) { - return 0; + * index = idx; + * pageoff = offset; + return OK; } + + offset -= area; + if (idx == UINT32_MAX) + { + ferr("offset overflow\n"); + return -EOVERFLOW; + } + + idx++; } - - finfo("Travel from index %" PRIu32 " at page %" PRIu32 " to index %" PRIu32 - " at page %" PRIu32 ".", idx_src, pg_src, idx_dest, pg); - - return pg; +} + +/**************************************************************************** + * Name: mfs_ctz_fill_next_ptrs + * + * Description: + * Populate the pointer area in a page-sized buffer as though the buffer + * were the CTZ unit immediately after the known unit at `page`/`index`. + * + * For every pointer required by unit `index + 1`, this walks backward from + * the known unit `index` to the target `(index + 1) - 2^pow`. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The location of the known CTZ unit at `index`. + * index - The index of the known CTZ unit. + * buffer - Caller-supplied page-sized buffer to receive the speculative + * pointer bytes. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_ctz_fill_next_ptrs(FAR struct mfs_sb_s *sb, mfs_t page, + mfs_t index, FAR uint8_t *buffer) +{ + FAR uint8_t *scratch; + mfs_t targetidx; + mfs_t targetpage; + mfs_t nextidx; + unsigned int pow; + unsigned int nptrs; + int ret; + + if (sb == NULL || buffer == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (page == MFS_LOCATION_INVALID || page >= MFS_PAGE_COUNT(sb)) + { + ferr("invalid page\n"); + return -EINVAL; + } + + if (index == UINT32_MAX) + { + ferr("index overflow\n"); + return -EOVERFLOW; + } + + scratch = kmm_malloc(MFS_PAGE_SIZE(sb)); + if (scratch == NULL) + { + ferr("kmm_malloc failed\n"); + return -ENOMEM; + } + + nextidx = index + 1; + nptrs = mfs_ctz_nptrs(nextidx); + for (pow = 0; pow < nptrs; pow++) + { + targetidx = index - (((mfs_t)1 << pow) - 1); + ret = mfs_ctz_follow(sb, index, targetidx, page, scratch, &targetpage); + if (ret < 0) + { + ferr("mfs_ctz_follow failed: %d\n", ret); + goto errout; + } + + mfs_ctz_write_ptr(sb, buffer, pow, targetpage); + } + + kmm_free(scratch); + return OK; + +errout: + kmm_free(scratch); + return ret; +} + +/**************************************************************************** + * Name: mfs_page_to_block + * + * Description: + * Convert a page number into the block that contains it. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The page number to translate. + * + * Returned Value: + * The containing block number is returned on success. + * MFS_LOCATION_INVALID is returned if the inputs are invalid. + * + ****************************************************************************/ + +mfs_t mfs_page_to_block(FAR const struct mfs_sb_s *sb, mfs_t page) +{ + if (sb == NULL || page == MFS_LOCATION_INVALID || + page >= MFS_PAGE_COUNT(sb)) + { + return MFS_LOCATION_INVALID; + } + + return page / MFS_PAGES_PER_BLOCK(sb); } diff --git a/fs/mnemofs/mnemofs_dirent.c b/fs/mnemofs/mnemofs_dirent.c new file mode 100644 index 00000000000..0011699cfe1 --- /dev/null +++ b/fs/mnemofs/mnemofs_dirent.c @@ -0,0 +1,3249 @@ +/**************************************************************************** + * fs/mnemofs/mnemofs_dirent.c + * + * SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Alternatively, the contents of this file may be used under the terms of + * the BSD-3-Clause license: + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2024 Saurav Pal + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of its contributors may + * be used to endorse or promote products derived from this software + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include "fs_heap.h" +#include "mnemofs.h" + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static FAR const char *mfs_direntry_name(FAR const char *relpath); +static size_t mfs_direntry_namelen( + FAR const struct mfs_direntry_s *direntry); +static bool mfs_direntry_name_equal( + FAR const struct mfs_direntry_s *direntry, + FAR const char *name); +static uint32_t mfs_deterministic_hash(FAR const uint8_t *arr, size_t sz); +static uint8_t mfs_folded_hash8(FAR const uint8_t *arr, size_t sz); +static uint8_t mfs_dirmeta_checksum(FAR const struct mfs_dirmeta_s *dirmeta); +static bool mfs_dirmeta_is_valid(FAR const struct mfs_dirmeta_s *dirmeta); +static void mfs_dirmeta_init(FAR struct mfs_dirmeta_s *dirmeta, + mfs_t parent); +static int mfs_dirmeta_read(FAR struct mfs_sb_s *sb, mfs_t directory, + FAR struct mfs_dirmeta_s *dirmeta); +static int mfs_dirmeta_write(FAR struct mfs_sb_s *sb, mfs_t directory, + mfs_t parent); +static FAR const char *mfs_skip_slashes(FAR const char *path); +static int mfs_next_path_component(FAR const char *path, FAR char *name, + FAR const char **next); +static FAR char *mfs_dir_pathdup(FAR const char *relpath); +static void mfs_dirloc_invalidate(FAR struct mfs_dirloc_s *dirloc); +static bool mfs_dirloc_is_valid(FAR const struct mfs_sb_s *sb, mfs_t block, + FAR const struct mfs_dirloc_s *dirloc); +static void mfs_dirloc_start(FAR const struct mfs_sb_s *sb, mfs_t block, + FAR struct mfs_dirloc_s *dirloc); +static int mfs_dirloc_next(FAR const struct mfs_sb_s *sb, mfs_t block, + FAR struct mfs_dirloc_s *dirloc); +static int mfs_direntry_read(FAR struct mfs_sb_s *sb, + FAR const struct mfs_dirloc_s *dirloc, + FAR struct mfs_direntry_s *direntry); +static void mfs_root_direntry(FAR const struct mfs_sb_s *sb, + FAR struct mfs_direntry_s *direntry); +static void mfs_stat_from_direntry(FAR const struct mfs_sb_s *sb, + mfs_t location, + FAR const struct mfs_direntry_s *direntry, + FAR struct stat *buf); +static void mfs_direntry_to_dirent(FAR const struct mfs_direntry_s *direntry, + FAR struct dirent *entry); +static int mfs_dirloc_parent_block(FAR const struct mfs_sb_s *sb, + FAR const struct mfs_dirloc_s *dirloc, + FAR mfs_t *block); +static int mfs_dir_follow(FAR struct mfs_sb_s *sb, mfs_t directory, + FAR const struct mfs_dirloc_s *startloc, + FAR const struct mfs_direntry_s *startentry, + FAR struct mfs_direntry_s *latest, + FAR struct mfs_dirloc_s *latestloc); +static int mfs_dir_lookup(FAR struct mfs_sb_s *sb, mfs_t directory, + FAR const char *name, FAR mfs_t *location, + FAR struct mfs_direntry_s *direntry, + FAR struct mfs_dirloc_s *dirloc); +static int mfs_dir_lookup_location(FAR struct mfs_sb_s *sb, mfs_t directory, + mfs_t location, + FAR struct mfs_direntry_s *direntry, + FAR struct mfs_dirloc_s *dirloc); +static int mfs_path_parent_dir(FAR struct mfs_sb_s *sb, + FAR const char *relpath, + FAR mfs_t *directory); +static int mfs_parent_dir(FAR struct mfs_sb_s *sb, mfs_t location, + FAR const struct mfs_dirloc_s *dirloc, + FAR mfs_t *directory); +static int mfs_same_directory(FAR struct mfs_sb_s *sb, + FAR const char *newrelpath, + mfs_t location, + FAR const struct mfs_dirloc_s *dirloc, + FAR mfs_t *directory); +static uint16_t mfs_direntry_namehash(FAR const char *name, size_t namelen); +static uint8_t mfs_direntry_checksum( + FAR const struct mfs_direntry_s *direntry); +static bool mfs_direntry_is_valid(FAR const struct mfs_direntry_s *direntry); +static int mfs_direntry_init(FAR struct mfs_direntry_s *direntry, + mode_t mode, FAR const char *relpath); +static int mfs_direntry_set_name(FAR struct mfs_direntry_s *direntry, + FAR const char *relpath); +static void mfs_direntry_seal(FAR struct mfs_direntry_s *direntry); +static int mfs_direntry_add_noflush( + FAR struct mfs_sb_s *sb, mfs_t directory, + FAR const struct mfs_direntry_s *direntry); +static int mfs_dir_reparent_children(FAR struct mfs_sb_s *sb, + mfs_t directory, mfs_t parent); +static int mfs_dir_refresh_open_files(FAR struct mfs_sb_s *sb, + mfs_t olddir, mfs_t newdir); +static int mfs_direntry_flush(FAR struct mfs_sb_s *sb, mfs_t directory, + FAR const struct mfs_direntry_s *direntry); +static int mfs_direntry_add(FAR struct mfs_sb_s *sb, mfs_t directory, + FAR const struct mfs_direntry_s *direntry); +static int mfs_dir_is_empty(FAR struct mfs_sb_s *sb, mfs_t directory); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mfs_direntry_name + * + * Description: + * Return a pointer to the final path component in relpath. + * + * Input Parameters: + * relpath - The relative path to inspect. + * + * Returned Value: + * A pointer to the last path component is returned on success. NULL is + * returned if relpath is NULL. + * + ****************************************************************************/ + +static FAR const char *mfs_direntry_name(FAR const char *relpath) +{ + FAR const char *name; + + if (relpath == NULL) + { + return NULL; + } + + name = strrchr(relpath, '/'); + return name == NULL ? relpath : name + 1; +} + +/**************************************************************************** + * Name: mfs_direntry_namelen + * + * Description: + * Return the bounded length of the name stored in a direntry. + * + * Input Parameters: + * direntry - The direntry whose name length is requested. + * + * Returned Value: + * The string length of the direntry name is returned. Zero is returned + * if direntry is NULL. + * + ****************************************************************************/ + +static size_t mfs_direntry_namelen(FAR const struct mfs_direntry_s *direntry) +{ + if (direntry == NULL) + { + return 0; + } + + return strnlen(direntry->name, sizeof(direntry->name)); +} + +/**************************************************************************** + * Name: mfs_direntry_name_equal + * + * Description: + * Compare the stored direntry name with a caller-provided name. + * + * Input Parameters: + * direntry - The direntry to compare. + * name - The candidate name. + * + * Returned Value: + * true is returned if both names are valid and equal. false is returned + * if either input is invalid or the names differ. + * + ****************************************************************************/ + +static bool mfs_direntry_name_equal( + FAR const struct mfs_direntry_s *direntry, + FAR const char *name) +{ + size_t dirnamelen; + size_t namelen; + + if (direntry == NULL || name == NULL) + { + return false; + } + + namelen = strnlen(name, MFS_NAME_LEN); + if (namelen == 0 || namelen > MFS_NAME_MAX) + { + return false; + } + + dirnamelen = mfs_direntry_namelen(direntry); + return dirnamelen == namelen && + memcmp(direntry->name, name, namelen) == 0; +} + +/**************************************************************************** + * Name: mfs_deterministic_hash + * + * Description: + * Compute a stable 32-bit hash over a byte array. + * + * Input Parameters: + * arr - The byte array to hash. + * sz - The number of bytes in arr. + * + * Returned Value: + * The computed hash value is returned. Zero is returned if arr is NULL. + * + ****************************************************************************/ + +static uint32_t mfs_deterministic_hash(FAR const uint8_t *arr, size_t sz) +{ + uint32_t distance; + uint32_t hash; + uint32_t product; + uint32_t reverse; + uint32_t shift_amount; + uint32_t term1; + uint32_t term2; + size_t midpoint; + size_t reverse_shift; + size_t i; + + if (arr == NULL) + { + return 0; + } + + midpoint = sz / 2; + hash = 0; + + for (i = 0; i < sz; i++) + { + term1 = arr[i] + (uint32_t)i; + reverse = arr[sz - i - 1]; + reverse_shift = sz - i - 1; + term2 = reverse_shift >= 32 ? 0 : reverse >> reverse_shift; + product = term1 * term2; + distance = (uint32_t)(midpoint > i ? midpoint - i : i - midpoint); + shift_amount = i % 32; + hash += (product ^ distance) << shift_amount; + } + + return hash; +} + +/**************************************************************************** + * Name: mfs_folded_hash8 + * + * Description: + * Fold the deterministic 32-bit hash down to one byte. + * + * Input Parameters: + * arr - The byte array to hash. + * sz - The number of bytes in arr. + * + * Returned Value: + * The folded 8-bit hash value is returned. + * + ****************************************************************************/ + +static uint8_t mfs_folded_hash8(FAR const uint8_t *arr, size_t sz) +{ + return (uint8_t)(mfs_deterministic_hash(arr, sz) % + ((uint32_t)UINT8_MAX + 1)); +} + +/**************************************************************************** + * Name: mfs_dirmeta_checksum + * + * Description: + * Compute the checksum stored in a directory metadata record. + * + * Input Parameters: + * dirmeta - The directory metadata record to checksum. + * + * Returned Value: + * The computed checksum value is returned. + * + ****************************************************************************/ + +static uint8_t mfs_dirmeta_checksum(FAR const struct mfs_dirmeta_s *dirmeta) +{ + return mfs_folded_hash8((FAR const uint8_t *)dirmeta, + sizeof(*dirmeta) - 1); +} + +/**************************************************************************** + * Name: mfs_dirmeta_is_valid + * + * Description: + * Verify the magic and checksum stored in a directory metadata record. + * + * Input Parameters: + * dirmeta - The directory metadata record to validate. + * + * Returned Value: + * true is returned if dirmeta contains a valid directory metadata + * record. false is returned otherwise. + * + ****************************************************************************/ + +static bool mfs_dirmeta_is_valid(FAR const struct mfs_dirmeta_s *dirmeta) +{ + if (dirmeta == NULL) + { + return false; + } + + if (dirmeta->magic != MFS_DIR_MAGIC) + { + return false; + } + + if (dirmeta->checksum != mfs_dirmeta_checksum(dirmeta)) + { + return false; + } + + return true; +} + +/**************************************************************************** + * Name: mfs_dirmeta_init + * + * Description: + * Initialize one in-memory directory metadata record. + * + * Input Parameters: + * dirmeta - The metadata record to initialize. + * parent - The parent directory block number to store. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_dirmeta_init(FAR struct mfs_dirmeta_s *dirmeta, + mfs_t parent) +{ + if (dirmeta == NULL) + { + return; + } + + memset(dirmeta, 0, sizeof(*dirmeta)); + dirmeta->magic = MFS_DIR_MAGIC; + dirmeta->parent = parent; + dirmeta->checksum = mfs_dirmeta_checksum(dirmeta); +} + +/**************************************************************************** + * Name: mfs_dirmeta_read + * + * Description: + * Read directory metadata from the first page of a directory block. + * + * Input Parameters: + * sb - The mounted file system instance. + * directory - The directory block number. + * dirmeta - The location to receive the decoded metadata. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mfs_dirmeta_read(FAR struct mfs_sb_s *sb, mfs_t directory, + FAR struct mfs_dirmeta_s *dirmeta) +{ + mfs_t page; + ssize_t nread; + + if (sb == NULL || dirmeta == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (directory == MFS_LOCATION_INVALID || directory >= MFS_BLOCK_COUNT(sb)) + { + ferr("invalid directory block\n"); + return -EINVAL; + } + + page = MFS_BLOCK_TO_PAGE(sb, directory); + nread = mfs_read_page(sb, page, sb->rwbuf); + if (nread < 0) + { + ferr("mfs_read_page failed: %zd\n", nread); + return nread; + } + + if (nread != 1) + { + ferr("short read: %zd\n", nread); + return -EIO; + } + + memcpy(dirmeta, sb->rwbuf, sizeof(*dirmeta)); + if (!mfs_dirmeta_is_valid(dirmeta)) + { + ferr("invalid directory metadata\n"); + return -EIO; + } + + return OK; +} + +/**************************************************************************** + * Name: mfs_dirmeta_write + * + * Description: + * Write directory metadata into the first page of a directory block. + * + * Input Parameters: + * sb - The mounted file system instance. + * directory - The directory block number. + * parent - The parent directory block number. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mfs_dirmeta_write(FAR struct mfs_sb_s *sb, mfs_t directory, + mfs_t parent) +{ + struct mfs_dirmeta_s dirmeta; + mfs_t page; + int ret; + ssize_t nwritten; + + if (sb == NULL) + { + ferr("invalid sb\n"); + return -EINVAL; + } + + if (directory == MFS_LOCATION_INVALID || directory >= MFS_BLOCK_COUNT(sb)) + { + ferr("invalid directory block\n"); + return -EINVAL; + } + + mfs_dirmeta_init(&dirmeta, parent); + ret = mfs_rwbuf_prepare_write(sb); + if (ret < 0) + { + ferr("mfs_rwbuf_prepare_write failed: %d\n", ret); + return ret; + } + + memset(sb->rwbuf, 0, MFS_PAGE_SIZE(sb)); + memcpy(sb->rwbuf, &dirmeta, sizeof(dirmeta)); + + page = MFS_BLOCK_TO_PAGE(sb, directory); + nwritten = mfs_write_page(sb, page, sb->rwbuf); + if (nwritten < 0) + { + ferr("mfs_write_page failed: %zd\n", nwritten); + return nwritten; + } + + if (nwritten != 1) + { + ferr("short write: %zd\n", nwritten); + return -EIO; + } + + return OK; +} + +/**************************************************************************** + * Name: mfs_skip_slashes + * + * Description: + * Advance past any leading slash characters in path. + * + * Input Parameters: + * path - The path pointer to normalize. + * + * Returned Value: + * A pointer to the first non-slash character is returned. If path is + * NULL, then NULL is returned. + * + ****************************************************************************/ + +static FAR const char *mfs_skip_slashes(FAR const char *path) +{ + while (path != NULL && *path == '/') + { + path++; + } + + return path; +} + +/**************************************************************************** + * Name: mfs_next_path_component + * + * Description: + * Decode one path component and advance next to the following component. + * + * Input Parameters: + * path - The path string to parse. + * name - The buffer that receives the decoded component. + * next - The location that receives the pointer to the remaining path. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned + * if the inputs are invalid or the component exceeds MFS_NAME_MAX. + * + ****************************************************************************/ + +static int mfs_next_path_component(FAR const char *path, FAR char *name, + FAR const char **next) +{ + size_t len; + + if (path == NULL || name == NULL || next == NULL) + { + return -EINVAL; + } + + memset(name, 0, MFS_NAME_LEN); + path = mfs_skip_slashes(path); + if (path == NULL) + { + return -EINVAL; + } + + if (*path == '\0') + { + * next = path; + return OK; + } + + len = strcspn(path, "/"); + if (len > MFS_NAME_MAX) + { + return -ENAMETOOLONG; + } + + memcpy(name, path, len); + name[len] = '\0'; + * next = mfs_skip_slashes(path + len); + return OK; +} + +/**************************************************************************** + * Name: mfs_dir_pathdup + * + * Description: + * Duplicate a directory path string for an open directory handle. + * + * Input Parameters: + * relpath - The relative path to duplicate. + * + * Returned Value: + * A heap-allocated copy of relpath is returned on success. If relpath is + * empty, then a copy of "/" is returned. NULL is returned on allocation + * failure. + * + ****************************************************************************/ + +static FAR char *mfs_dir_pathdup(FAR const char *relpath) +{ + FAR const char *src; + FAR char *dst; + size_t len; + + src = relpath; + if (src == NULL || *src == '\0') + { + src = "/"; + } + + len = strlen(src) + 1; + dst = fs_heap_malloc(len); + if (dst == NULL) + { + return NULL; + } + + memcpy(dst, src, len); + return dst; +} + +/**************************************************************************** + * Name: mfs_dirloc_invalidate + * + * Description: + * Mark a dirloc as invalid. + * + * Input Parameters: + * dirloc - The dirloc to invalidate. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_dirloc_invalidate(FAR struct mfs_dirloc_s *dirloc) +{ + if (dirloc == NULL) + { + return; + } + + dirloc->page = MFS_LOCATION_INVALID; + dirloc->offset = MFS_DIRENT_OFF_INVALID; +} + +/**************************************************************************** + * Name: mfs_dirloc_is_valid + * + * Description: + * Check whether a dirloc refers to a valid slot inside one directory + * block. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The directory block that should own dirloc. + * dirloc - The dirloc to validate. + * + * Returned Value: + * true is returned if dirloc references a valid direntry slot inside the + * directory block. false is returned otherwise. + * + ****************************************************************************/ + +static bool mfs_dirloc_is_valid(FAR const struct mfs_sb_s *sb, mfs_t block, + FAR const struct mfs_dirloc_s *dirloc) +{ + mfs_t firstpage; + mfs_t lastpage; + + if (sb == NULL || dirloc == NULL) + { + return false; + } + + if (block == MFS_LOCATION_INVALID || block >= MFS_BLOCK_COUNT(sb)) + { + return false; + } + + if (dirloc->page == MFS_LOCATION_INVALID || + dirloc->offset == MFS_DIRENT_OFF_INVALID) + { + return false; + } + + if (dirloc->offset + sizeof(struct mfs_direntry_s) > MFS_PAGE_SIZE(sb)) + { + return false; + } + + firstpage = MFS_BLOCK_TO_PAGE(sb, block) + 1; + lastpage = MFS_BLOCK_TO_PAGE(sb, block) + MFS_PAGES_PER_BLOCK(sb) - 1; + return firstpage <= lastpage && + dirloc->page >= firstpage && dirloc->page <= lastpage; +} + +/**************************************************************************** + * Name: mfs_dirloc_start + * + * Description: + * Initialize a dirloc so it points at the first direntry slot in a + * directory block. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The directory block to scan. + * dirloc - The dirloc to initialize. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_dirloc_start(FAR const struct mfs_sb_s *sb, mfs_t block, + FAR struct mfs_dirloc_s *dirloc) +{ + if (dirloc == NULL) + { + return; + } + + if (sb == NULL || MFS_PAGES_PER_BLOCK(sb) < 2) + { + mfs_dirloc_invalidate(dirloc); + return; + } + + dirloc->page = MFS_BLOCK_TO_PAGE(sb, block) + 1; + dirloc->offset = 0; +} + +/**************************************************************************** + * Name: mfs_dirloc_next + * + * Description: + * Advance a dirloc to the next direntry slot within the same directory + * block. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The directory block being scanned. + * dirloc - The dirloc to advance. + * + * Returned Value: + * Zero (OK) is returned on success. -ENOENT is returned if dirloc is + * already at the end of the directory block or otherwise invalid. + * + ****************************************************************************/ + +static int mfs_dirloc_next(FAR const struct mfs_sb_s *sb, mfs_t block, + FAR struct mfs_dirloc_s *dirloc) +{ + mfs_t nextoff; + mfs_t lastpage; + + if (!mfs_dirloc_is_valid(sb, block, dirloc)) + { + return -ENOENT; + } + + nextoff = dirloc->offset + sizeof(struct mfs_direntry_s); + if (nextoff + sizeof(struct mfs_direntry_s) <= MFS_PAGE_SIZE(sb)) + { + dirloc->offset = nextoff; + return OK; + } + + lastpage = MFS_BLOCK_TO_PAGE(sb, block) + MFS_PAGES_PER_BLOCK(sb) - 1; + if (dirloc->page >= lastpage) + { + mfs_dirloc_invalidate(dirloc); + return -ENOENT; + } + + dirloc->page++; + dirloc->offset = 0; + return OK; +} + +/**************************************************************************** + * Name: mfs_direntry_read + * + * Description: + * Read one direntry record from the location identified by dirloc. + * + * Input Parameters: + * sb - The mounted file system instance. + * dirloc - The direntry location to read. + * direntry - The location that receives the decoded direntry. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned if + * dirloc is invalid or the underlying page read fails. + * + ****************************************************************************/ + +static int mfs_direntry_read(FAR struct mfs_sb_s *sb, + FAR const struct mfs_dirloc_s *dirloc, + FAR struct mfs_direntry_s *direntry) +{ + ssize_t nread; + + if (sb == NULL || dirloc == NULL || direntry == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (dirloc->page == MFS_LOCATION_INVALID || + dirloc->offset + sizeof(*direntry) > MFS_PAGE_SIZE(sb)) + { + ferr("invalid dirloc\n"); + return -EINVAL; + } + + nread = mfs_read_page(sb, dirloc->page, sb->rwbuf); + if (nread < 0) + { + ferr("mfs_read_page failed: %zd\n", nread); + return nread; + } + + if (nread != 1) + { + ferr("short read: %zd\n", nread); + return -EIO; + } + + memcpy(direntry, sb->rwbuf + dirloc->offset, sizeof(*direntry)); + return OK; +} + +/**************************************************************************** + * Name: mfs_page_is_erased + * + * Description: + * Check whether a page buffer is still entirely in the device erase + * state. + * + * Input Parameters: + * sb - The mounted file system instance. + * pagebuf - The page buffer to inspect. + * + * Returned Value: + * true is returned if every byte matches sb->erasestate. false is + * returned otherwise. + * + ****************************************************************************/ + +static bool mfs_page_is_erased(FAR const struct mfs_sb_s *sb, + FAR const uint8_t *pagebuf) +{ + size_t i; + + if (sb == NULL || pagebuf == NULL) + { + return false; + } + + for (i = 0; i < MFS_PAGE_SIZE(sb); i++) + { + if (pagebuf[i] != sb->erasestate) + { + return false; + } + } + + return true; +} + +/**************************************************************************** + * Name: mfs_root_direntry + * + * Description: + * Build the synthetic direntry that represents the mounted root + * directory. + * + * Input Parameters: + * sb - The mounted file system instance. + * direntry - The location that receives the synthetic root direntry. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_root_direntry(FAR const struct mfs_sb_s *sb, + FAR struct mfs_direntry_s *direntry) +{ + memset(direntry, 0, sizeof(*direntry)); + direntry->magic = MFS_DIRENT_MAGIC; + direntry->mode = S_IFDIR | S_IRWXU | S_IRGRP | S_IXGRP | + S_IROTH | S_IXOTH; + direntry->newloc = sb->rootdir; + direntry->meta.size = MFS_BLOCK_SIZE(sb); + mfs_direntry_seal(direntry); +} + +/**************************************************************************** + * Name: mfs_stat_from_direntry + * + * Description: + * Populate a struct stat from one live direntry record. + * + * Input Parameters: + * sb - The mounted file system instance. + * location - The live object location. + * direntry - The direntry that describes the object. + * buf - The location that receives the converted stat data. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_stat_from_direntry(FAR const struct mfs_sb_s *sb, + mfs_t location, + FAR const struct mfs_direntry_s *direntry, + FAR struct stat *buf) +{ + memset(buf, 0, sizeof(*buf)); + buf->st_ino = location; + buf->st_mode = (mode_t)direntry->mode; + buf->st_nlink = 1; + buf->st_size = S_ISDIR(direntry->mode) ? + MFS_BLOCK_SIZE(sb) : direntry->meta.size; + buf->st_atim.tv_sec = direntry->mtime; + buf->st_mtim.tv_sec = direntry->mtime; + buf->st_ctim.tv_sec = direntry->ctime; + buf->st_blksize = MFS_PAGE_SIZE(sb); + buf->st_blocks = (buf->st_size + buf->st_blksize - 1) / + buf->st_blksize; +} + +/**************************************************************************** + * Name: mfs_direntry_to_dirent + * + * Description: + * Convert one direntry record into a POSIX dirent. + * + * Input Parameters: + * direntry - The direntry to convert. + * entry - The location that receives the dirent view. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_direntry_to_dirent(FAR const struct mfs_direntry_s *direntry, + FAR struct dirent *entry) +{ + size_t namelen; + + memset(entry, 0, sizeof(*entry)); + namelen = mfs_direntry_namelen(direntry); + if (namelen >= sizeof(entry->d_name)) + { + namelen = sizeof(entry->d_name) - 1; + } + + memcpy(entry->d_name, direntry->name, namelen); + entry->d_name[namelen] = '\0'; + entry->d_type = S_ISDIR(direntry->mode) ? DTYPE_DIRECTORY : DTYPE_FILE; +} + +/**************************************************************************** + * Name: mfs_dirloc_parent_block + * + * Description: + * Derive the parent directory block number from a direntry location. + * + * Input Parameters: + * sb - The mounted file system instance. + * dirloc - The direntry location inside the parent directory block. + * block - The location that receives the parent block number. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned if + * the inputs are invalid or dirloc does not map to a valid block. + * + ****************************************************************************/ + +static int mfs_dirloc_parent_block(FAR const struct mfs_sb_s *sb, + FAR const struct mfs_dirloc_s *dirloc, + FAR mfs_t *block) +{ + if (sb == NULL || dirloc == NULL || block == NULL) + { + return -EINVAL; + } + + * block = mfs_page_to_block(sb, dirloc->page); + if (*block == MFS_LOCATION_INVALID) + { + return -EINVAL; + } + + return OK; +} + +/**************************************************************************** + * Name: mfs_dir_follow + * + * Description: + * Follow one directory-entry history chain from its first record to the + * latest reachable record. + * + * Input Parameters: + * sb - The mounted file system instance. + * directory - The directory block that owns the chain. + * startloc - The location of the first record in the chain. + * startentry - The first record in the chain. + * latest - The location to receive the latest record contents. + * latestloc - The location to receive the latest record address. + * + * Returned Value: + * Zero (OK) is returned on success. -ENOENT is returned when the chain + * does not lead to a live location. A negated errno value is returned on + * other failures. + * + ****************************************************************************/ + +static int mfs_dir_follow(FAR struct mfs_sb_s *sb, mfs_t directory, + FAR const struct mfs_dirloc_s *startloc, + FAR const struct mfs_direntry_s *startentry, + FAR struct mfs_direntry_s *latest, + FAR struct mfs_dirloc_s *latestloc) +{ + struct mfs_dirloc_s scanloc; + struct mfs_direntry_s current; + struct mfs_direntry_s candidate; + int ret; + + if (sb == NULL || startloc == NULL || startentry == NULL || + latest == NULL || latestloc == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + memcpy(¤t, startentry, sizeof(current)); + memcpy(latest, startentry, sizeof(*latest)); + memcpy(latestloc, startloc, sizeof(*latestloc)); + + /* A tombstone ends this history chain. Later direntries with + * oldloc == MFS_LOCATION_INVALID are fresh heads, not successors. + */ + + if (current.newloc == MFS_LOCATION_INVALID) + { + return -ENOENT; + } + + scanloc = *startloc; + ret = mfs_dirloc_next(sb, directory, &scanloc); + while (ret == OK) + { + ret = mfs_direntry_read(sb, &scanloc, &candidate); + if (ret < 0) + { + ferr("mfs_direntry_read failed: %d\n", ret); + return ret; + } + + if (mfs_direntry_is_valid(&candidate) && + candidate.oldloc == current.newloc) + { + memcpy(¤t, &candidate, sizeof(current)); + memcpy(latest, &candidate, sizeof(*latest)); + memcpy(latestloc, &scanloc, sizeof(*latestloc)); + + if (current.newloc == MFS_LOCATION_INVALID) + { + return -ENOENT; + } + } + + ret = mfs_dirloc_next(sb, directory, &scanloc); + } + + return OK; +} + +/**************************************************************************** + * Name: mfs_dir_lookup + * + * Description: + * Locate the latest live direntry for one name inside a directory block. + * The search scans only chain starting points and then follows each chain + * according to the oldloc/newloc rule. + * + * Input Parameters: + * sb - The mounted file system instance. + * directory - The directory block number. + * name - The entry name to search for. + * location - The location to receive the latest object location. + * direntry - The location to receive the latest direntry contents. + * dirloc - The location to receive the latest direntry address. + * + * Returned Value: + * Zero (OK) is returned on success. -ENOENT is returned if the named + * entry is not found. A negated errno value is returned on other + * failures. + * + ****************************************************************************/ + +static int mfs_dir_lookup(FAR struct mfs_sb_s *sb, mfs_t directory, + FAR const char *name, FAR mfs_t *location, + FAR struct mfs_direntry_s *direntry, + FAR struct mfs_dirloc_s *dirloc) +{ + struct mfs_dirloc_s scanloc; + struct mfs_direntry_s candidate; + struct mfs_direntry_s latest; + struct mfs_dirloc_s latestloc; + uint16_t namehash; + int ret; + + if (sb == NULL || name == NULL || location == NULL || + direntry == NULL || dirloc == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (directory == MFS_LOCATION_INVALID || directory >= MFS_BLOCK_COUNT(sb)) + { + return -ENOENT; + } + + namehash = mfs_direntry_namehash(name, strlen(name)); + mfs_dirloc_start(sb, directory, &scanloc); + + while (mfs_dirloc_is_valid(sb, directory, &scanloc)) + { + ret = mfs_direntry_read(sb, &scanloc, &candidate); + if (ret < 0) + { + ferr("mfs_direntry_read failed: %d\n", ret); + return ret; + } + + if (mfs_direntry_is_valid(&candidate) && + candidate.oldloc == MFS_LOCATION_INVALID) + { + ret = mfs_dir_follow(sb, directory, &scanloc, &candidate, + &latest, &latestloc); + if (ret < 0 && ret != -ENOENT) + { + ferr("mfs_dir_follow failed: %d\n", ret); + } + + if (ret == OK && + latest.namehash == namehash && + mfs_direntry_name_equal(&latest, name)) + { + * location = latest.newloc; + memcpy(direntry, &latest, sizeof(*direntry)); + memcpy(dirloc, &latestloc, sizeof(*dirloc)); + return OK; + } + } + + ret = mfs_dirloc_next(sb, directory, &scanloc); + if (ret < 0) + { + break; + } + } + + return -ENOENT; +} + +/**************************************************************************** + * Name: mfs_dir_lookup_location + * + * Description: + * Locate the latest live direntry whose resolved location matches the + * requested location. + * + * Input Parameters: + * sb - The mounted file system instance. + * directory - The directory block number to search. + * location - The live object location to match. + * direntry - The location that receives the latest direntry contents. + * dirloc - The location that receives the latest direntry address. + * + * Returned Value: + * Zero (OK) is returned on success. -ENOENT is returned if no live + * direntry resolves to location. A negated errno value is returned on + * other failures. + * + ****************************************************************************/ + +static int mfs_dir_lookup_location(FAR struct mfs_sb_s *sb, mfs_t directory, + mfs_t location, + FAR struct mfs_direntry_s *direntry, + FAR struct mfs_dirloc_s *dirloc) +{ + struct mfs_dirloc_s scanloc; + struct mfs_direntry_s candidate; + struct mfs_direntry_s latest; + struct mfs_dirloc_s latestloc; + int ret; + + if (sb == NULL || direntry == NULL || dirloc == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (directory == MFS_LOCATION_INVALID || + directory >= MFS_BLOCK_COUNT(sb) || + location == MFS_LOCATION_INVALID) + { + return -ENOENT; + } + + mfs_dirloc_start(sb, directory, &scanloc); + while (mfs_dirloc_is_valid(sb, directory, &scanloc)) + { + ret = mfs_direntry_read(sb, &scanloc, &candidate); + if (ret < 0) + { + ferr("mfs_direntry_read failed: %d\n", ret); + return ret; + } + + if (mfs_direntry_is_valid(&candidate) && + candidate.oldloc == MFS_LOCATION_INVALID) + { + ret = mfs_dir_follow(sb, directory, &scanloc, &candidate, + &latest, &latestloc); + if (ret == OK && latest.newloc == location) + { + memcpy(direntry, &latest, sizeof(*direntry)); + memcpy(dirloc, &latestloc, sizeof(*dirloc)); + return OK; + } + + if (ret < 0 && ret != -ENOENT) + { + ferr("mfs_dir_follow failed: %d\n", ret); + return ret; + } + } + + ret = mfs_dirloc_next(sb, directory, &scanloc); + if (ret < 0) + { + break; + } + } + + return -ENOENT; +} + +/**************************************************************************** + * Name: mfs_path_parent_dir + * + * Description: + * Resolve the parent directory block for a relative path. + * + * Input Parameters: + * sb - The mounted file system instance. + * relpath - The relative path whose parent is requested. + * directory - The location to receive the parent directory block number. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mfs_path_parent_dir(FAR struct mfs_sb_s *sb, + FAR const char *relpath, + FAR mfs_t *directory) +{ + FAR const char *path; + FAR const char *next; + struct mfs_direntry_s direntry; + struct mfs_dirloc_s dirloc; + char component[MFS_NAME_LEN]; + mfs_t current; + int ret; + + if (sb == NULL || relpath == NULL || directory == NULL) + { + return -EINVAL; + } + + memset(component, 0, sizeof(component)); + path = mfs_skip_slashes(relpath); + if (path == NULL || *path == '\0') + { + return -EINVAL; + } + + current = sb->rootdir; + for (; ; ) + { + ret = mfs_next_path_component(path, component, &next); + if (ret < 0) + { + return ret; + } + + if (component[0] == '\0') + { + return -EINVAL; + } + + if (*next == '\0') + { + * directory = current; + return OK; + } + + ret = mfs_dir_lookup(sb, current, component, ¤t, + &direntry, &dirloc); + if (ret < 0) + { + return ret; + } + + if (!S_ISDIR(direntry.mode)) + { + return -ENOTDIR; + } + + path = next; + } +} + +/**************************************************************************** + * Name: mfs_parent_dir + * + * Description: + * Resolve the parent directory block for a live file system object. If + * the direntry location is known, then the parent directory can be + * derived directly from that page. Otherwise the function falls back to + * reading the directory metadata stored in the object's own block. + * + * Input Parameters: + * sb - The mounted file system instance. + * location - The live object location. + * dirloc - Optional latest direntry location for that object. + * directory - The location to receive the parent directory block number. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mfs_parent_dir(FAR struct mfs_sb_s *sb, mfs_t location, + FAR const struct mfs_dirloc_s *dirloc, + FAR mfs_t *directory) +{ + struct mfs_dirmeta_s dirmeta; + int ret; + + if (sb == NULL || directory == NULL) + { + return -EINVAL; + } + + if (location == sb->rootdir) + { + return -EBUSY; + } + + if (dirloc != NULL) + { + ret = mfs_dirloc_parent_block(sb, dirloc, directory); + if (ret == OK) + { + return OK; + } + } + + ret = mfs_dirmeta_read(sb, location, &dirmeta); + if (ret < 0) + { + return ret; + } + + if (dirmeta.parent == MFS_LOCATION_INVALID || + dirmeta.parent >= MFS_BLOCK_COUNT(sb)) + { + return -EIO; + } + + * directory = dirmeta.parent; + return OK; +} + +/**************************************************************************** + * Name: mfs_same_directory + * + * Description: + * Verify that location and newrelpath belong to the same parent + * directory. + * + * Input Parameters: + * sb - The mounted file system instance. + * newrelpath - The replacement path to check. + * location - The live object location being examined. + * dirloc - Optional latest direntry location for location. + * directory - The location that receives the shared parent directory. + * + * Returned Value: + * Zero (OK) is returned if both objects share the same parent directory. + * -EXDEV is returned if the parent directories differ. A negated errno + * value is returned on other failures. + * + ****************************************************************************/ + +static int mfs_same_directory(FAR struct mfs_sb_s *sb, + FAR const char *newrelpath, + mfs_t location, + FAR const struct mfs_dirloc_s *dirloc, + FAR mfs_t *directory) +{ + mfs_t olddir; + mfs_t newdir; + int ret; + + if (sb == NULL || newrelpath == NULL || directory == NULL) + { + return -EINVAL; + } + + ret = mfs_parent_dir(sb, location, dirloc, &olddir); + if (ret < 0) + { + return ret; + } + + ret = mfs_path_parent_dir(sb, newrelpath, &newdir); + if (ret < 0) + { + return ret; + } + + if (olddir != newdir) + { + return -EXDEV; + } + + * directory = olddir; + return OK; +} + +/**************************************************************************** + * Name: mfs_direntry_namehash + * + * Description: + * Compute the 16-bit name hash stored in direntry records. + * + * Input Parameters: + * name - The name to hash. + * namelen - The number of characters in name to hash. + * + * Returned Value: + * The computed 16-bit hash value is returned. Zero is returned if name + * is NULL. + * + ****************************************************************************/ + +static uint16_t mfs_direntry_namehash(FAR const char *name, size_t namelen) +{ + FAR const uint8_t *arr; + uint32_t ret; + uint32_t idx_mult; + uint32_t elem; + size_t i; + + if (name == NULL) + { + return 0; + } + + arr = (FAR const uint8_t *)name; + ret = 0; + + for (i = 0; i < namelen; i++) + { + idx_mult = ((uint32_t)(i + 1) % UINT16_MAX) + 1; + elem = ((uint32_t)arr[i] + 1) % ((uint32_t)UINT16_MAX + 1); + elem = (uint32_t)(((uint64_t)elem * idx_mult * idx_mult) % + ((uint32_t)UINT16_MAX + 1)); + ret = (ret + elem) % ((uint32_t)UINT16_MAX + 1); + } + + return (uint16_t)ret; +} + +/**************************************************************************** + * Name: mfs_direntry_checksum + * + * Description: + * Compute the checksum stored in one direntry record. + * + * Input Parameters: + * direntry - The direntry to checksum. + * + * Returned Value: + * The computed checksum value is returned. + * + ****************************************************************************/ + +static uint8_t +mfs_direntry_checksum(FAR const struct mfs_direntry_s *direntry) +{ + return mfs_folded_hash8((FAR const uint8_t *)direntry, + sizeof(*direntry) - 1); +} + +/**************************************************************************** + * Name: mfs_direntry_is_valid + * + * Description: + * Verify the checksum, mode, and stored name fields of a direntry + * record. + * + * Input Parameters: + * direntry - The direntry to validate. + * + * Returned Value: + * true is returned if direntry contains a valid record. false is + * returned otherwise. + * + ****************************************************************************/ + +static bool mfs_direntry_is_valid(FAR const struct mfs_direntry_s *direntry) +{ + size_t namelen; + + if (direntry == NULL) + { + return false; + } + + if (direntry->magic != MFS_DIRENT_MAGIC) + { + return false; + } + + if (direntry->checksum != mfs_direntry_checksum(direntry)) + { + return false; + } + + if (!S_ISDIR(direntry->mode) && !S_ISREG(direntry->mode)) + { + return false; + } + + namelen = mfs_direntry_namelen(direntry); + if (namelen == 0) + { + return false; + } + + if (direntry->namehash != mfs_direntry_namehash(direntry->name, namelen)) + { + return false; + } + + return true; +} + +/**************************************************************************** + * Name: mfs_direntry_init + * + * Description: + * Initialize a brand-new direntry record from one relative path and mode. + * + * Input Parameters: + * direntry - The direntry to initialize. + * mode - The file type and permission bits to store. + * relpath - The relative path whose final component becomes the name. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned if + * the inputs are invalid or the final path component is too long. + * + ****************************************************************************/ + +static int mfs_direntry_init(FAR struct mfs_direntry_s *direntry, + mode_t mode, FAR const char *relpath) +{ + FAR const char *name; + size_t namelen; + + if (direntry == NULL) + { + return -EINVAL; + } + + if (!S_ISDIR(mode) && !S_ISREG(mode)) + { + return -EINVAL; + } + + name = mfs_direntry_name(relpath); + if (name == NULL || *name == '\0') + { + return -EINVAL; + } + + namelen = strnlen(name, MFS_NAME_LEN); + if (namelen == 0 || namelen > MFS_NAME_MAX) + { + return -ENAMETOOLONG; + } + + memset(direntry, 0, sizeof(*direntry)); + direntry->magic = MFS_DIRENT_MAGIC; + direntry->mode = (uint16_t)mode; + memcpy(direntry->name, name, namelen); + direntry->namehash = mfs_direntry_namehash(direntry->name, namelen); + mfs_direntry_seal(direntry); + return OK; +} + +/**************************************************************************** + * Name: mfs_direntry_set_name + * + * Description: + * Replace the stored direntry name with the final component of relpath. + * + * Input Parameters: + * direntry - The direntry to update. + * relpath - The relative path whose final component becomes the new + * name. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned if + * the inputs are invalid or the final path component is too long. + * + ****************************************************************************/ + +static int mfs_direntry_set_name(FAR struct mfs_direntry_s *direntry, + FAR const char *relpath) +{ + FAR const char *name; + size_t namelen; + + if (direntry == NULL) + { + return -EINVAL; + } + + name = mfs_direntry_name(relpath); + if (name == NULL || *name == '\0') + { + return -EINVAL; + } + + namelen = strnlen(name, MFS_NAME_LEN); + if (namelen == 0 || namelen > MFS_NAME_MAX) + { + return -ENAMETOOLONG; + } + + memset(direntry->name, 0, sizeof(direntry->name)); + memcpy(direntry->name, name, namelen); + direntry->namehash = mfs_direntry_namehash(direntry->name, namelen); + mfs_direntry_seal(direntry); + return OK; +} + +/**************************************************************************** + * Name: mfs_direntry_seal + * + * Description: + * Refresh the checksum field after a direntry record is modified. + * + * Input Parameters: + * direntry - The direntry to seal. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_direntry_seal(FAR struct mfs_direntry_s *direntry) +{ + if (direntry == NULL) + { + return; + } + + direntry->checksum = mfs_direntry_checksum(direntry); +} + +/**************************************************************************** + * Name: mfs_direntry_add_noflush + * + * Description: + * Append one new direntry record into the first blank page found in the + * directory block. Directory metadata occupies the first page, so the + * scan begins at the second page. Even though legacy images may contain + * multiple packed direntries per page, new writes only consume fully + * erased pages so one NAND page is never programmed twice. + * + * Input Parameters: + * sb - The mounted file system instance. + * directory - The directory block number. + * direntry - The new direntry record to store. + * + * Returned Value: + * Zero (OK) is returned on success. -ENOSPC is returned when the + * directory block has no blank page left for a new direntry. A negated + * errno value is returned on other failures. + * + ****************************************************************************/ + +static int mfs_direntry_add_noflush( + FAR struct mfs_sb_s *sb, mfs_t directory, + FAR const struct mfs_direntry_s *direntry) +{ + struct mfs_direntry_s candidate; + mfs_t page; + mfs_t endpage; + size_t offset; + ssize_t nread; + ssize_t nwritten; + bool occupied; + + if (sb == NULL || direntry == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (directory == MFS_LOCATION_INVALID || directory >= MFS_BLOCK_COUNT(sb)) + { + ferr("invalid directory block\n"); + return -EINVAL; + } + + if (!mfs_direntry_is_valid(direntry)) + { + ferr("invalid direntry\n"); + return -EINVAL; + } + + page = MFS_BLOCK_TO_PAGE(sb, directory) + 1; + endpage = MFS_BLOCK_TO_PAGE(sb, directory) + MFS_PAGES_PER_BLOCK(sb); + for (; page < endpage; page++) + { + nread = mfs_read_page(sb, page, sb->rwbuf); + if (nread < 0) + { + ferr("mfs_read_page failed: %zd\n", nread); + return nread; + } + + if (nread != 1) + { + ferr("short read: %zd\n", nread); + return -EIO; + } + + occupied = false; + for (offset = 0; + offset + sizeof(candidate) <= MFS_PAGE_SIZE(sb); + offset += sizeof(candidate)) + { + memcpy(&candidate, sb->rwbuf + offset, sizeof(candidate)); + if (mfs_direntry_is_valid(&candidate)) + { + occupied = true; + break; + } + } + + if (occupied || !mfs_page_is_erased(sb, sb->rwbuf)) + { + continue; + } + + memcpy(sb->rwbuf, direntry, sizeof(*direntry)); + nwritten = mfs_write_page(sb, page, sb->rwbuf); + if (nwritten < 0) + { + ferr("mfs_write_page failed: %zd\n", nwritten); + return nwritten; + } + + if (nwritten != 1) + { + ferr("short write: %zd\n", nwritten); + return -EIO; + } + + return OK; + } + + ferr("directory full\n"); + return -ENOSPC; +} + +/**************************************************************************** + * Name: mfs_dir_reparent_children + * + * Description: + * Rewrite the metadata page of each live child directory so its stored + * parent block number becomes parent. + * + * Input Parameters: + * sb - The mounted file system instance. + * directory - The directory block whose live children are scanned. + * parent - The parent block number to store into child metadata. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned if + * scanning or rewriting a child directory metadata page fails. + * + ****************************************************************************/ + +static int mfs_dir_reparent_children(FAR struct mfs_sb_s *sb, + mfs_t directory, mfs_t parent) +{ + struct mfs_dirloc_s scanloc; + struct mfs_direntry_s candidate; + struct mfs_direntry_s latest; + struct mfs_dirloc_s latestloc; + int ret; + + if (sb == NULL) + { + ferr("invalid sb\n"); + return -EINVAL; + } + + if (directory == MFS_LOCATION_INVALID || + directory >= MFS_BLOCK_COUNT(sb) || + parent == MFS_LOCATION_INVALID || + parent >= MFS_BLOCK_COUNT(sb)) + { + ferr("invalid directory or parent\n"); + return -EINVAL; + } + + mfs_dirloc_start(sb, directory, &scanloc); + while (mfs_dirloc_is_valid(sb, directory, &scanloc)) + { + ret = mfs_direntry_read(sb, &scanloc, &candidate); + if (ret < 0) + { + ferr("mfs_direntry_read failed: %d\n", ret); + return ret; + } + + if (mfs_direntry_is_valid(&candidate) && + candidate.oldloc == MFS_LOCATION_INVALID) + { + ret = mfs_dir_follow(sb, directory, &scanloc, &candidate, + &latest, &latestloc); + if (ret == OK && S_ISDIR(latest.mode)) + { + ret = mfs_dirmeta_write(sb, latest.newloc, parent); + if (ret < 0) + { + ferr("mfs_dirmeta_write failed: %d\n", ret); + return ret; + } + } + else if (ret < 0 && ret != -ENOENT) + { + ferr("mfs_dir_follow failed: %d\n", ret); + return ret; + } + } + + ret = mfs_dirloc_next(sb, directory, &scanloc); + if (ret < 0) + { + break; + } + } + + return OK; +} + +/**************************************************************************** + * Name: mfs_dir_refresh_open_files + * + * Description: + * Update open regular-file descriptors after a directory block has been + * relocated. + * + * Input Parameters: + * sb - The mounted file system instance. + * olddir - The original directory block number. + * newdir - The replacement directory block number. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned if + * scanning the replacement directory fails. + * + ****************************************************************************/ + +static int mfs_dir_refresh_open_files(FAR struct mfs_sb_s *sb, + mfs_t olddir, mfs_t newdir) +{ + struct mfs_dirloc_s scanloc; + struct mfs_direntry_s candidate; + struct mfs_direntry_s latest; + struct mfs_dirloc_s latestloc; + FAR struct list_node *node; + FAR struct mfs_ofd_s *ofd; + int ret; + + if (sb == NULL) + { + ferr("invalid sb\n"); + return -EINVAL; + } + + if (olddir == MFS_LOCATION_INVALID || olddir >= MFS_BLOCK_COUNT(sb) || + newdir == MFS_LOCATION_INVALID || newdir >= MFS_BLOCK_COUNT(sb)) + { + ferr("invalid directory block\n"); + return -EINVAL; + } + + mfs_dirloc_start(sb, newdir, &scanloc); + while (mfs_dirloc_is_valid(sb, newdir, &scanloc)) + { + ret = mfs_direntry_read(sb, &scanloc, &candidate); + if (ret < 0) + { + ferr("mfs_direntry_read failed: %d\n", ret); + return ret; + } + + if (mfs_direntry_is_valid(&candidate) && + candidate.oldloc == MFS_LOCATION_INVALID) + { + ret = mfs_dir_follow(sb, newdir, &scanloc, &candidate, + &latest, &latestloc); + if (ret == OK && S_ISREG(latest.mode)) + { + list_for_every(&sb->ofiles, node) + { + ofd = list_entry(node, struct mfs_ofd_s, entry); + if (ofd->common == NULL || + ofd->common->direntloc.page == MFS_LOCATION_INVALID || + mfs_page_to_block(sb, + ofd->common->direntloc.page) != + olddir || + (ofd->common->fileloc != latest.oldloc && + ofd->common->fileloc != latest.newloc)) + { + continue; + } + + memcpy(&ofd->common->direntry, &latest, + sizeof(ofd->common->direntry)); + memcpy(&ofd->common->direntloc, &latestloc, + sizeof(ofd->common->direntloc)); + ofd->common->fileloc = latest.newloc; + } + } + else if (ret < 0 && ret != -ENOENT) + { + ferr("mfs_dir_follow failed: %d\n", ret); + return ret; + } + } + + ret = mfs_dirloc_next(sb, newdir, &scanloc); + if (ret < 0) + { + break; + } + } + + return OK; +} + +/**************************************************************************** + * Name: mfs_direntry_flush + * + * Description: + * Relocate a full directory block into a freshly allocated block and then + * append direntry into the new copy. + * + * Input Parameters: + * sb - The mounted file system instance. + * directory - The original directory block number. + * direntry - The new direntry record to append. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned if + * allocation, copying, or parent bookkeeping fails. + * + ****************************************************************************/ + +static int mfs_direntry_flush(FAR struct mfs_sb_s *sb, mfs_t directory, + FAR const struct mfs_direntry_s *direntry) +{ + struct mfs_dirloc_s scanloc; + struct mfs_direntry_s candidate; + struct mfs_direntry_s latest; + struct mfs_direntry_s copied; + struct mfs_direntry_s parententry; + struct mfs_dirloc_s latestloc; + struct mfs_dirmeta_s dirmeta; + mfs_t newdir; + mfs_t parent; + int ret; + int cleanup; + bool moved_root; + bool children_reparented; + + if (sb == NULL || direntry == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (directory == MFS_LOCATION_INVALID || + directory >= MFS_BLOCK_COUNT(sb) || + !mfs_direntry_is_valid(direntry)) + { + ferr("invalid directory or direntry\n"); + return -EINVAL; + } + + moved_root = directory == sb->rootdir; + if (moved_root) + { + parent = directory; + } + else + { + ret = mfs_dirmeta_read(sb, directory, &dirmeta); + if (ret < 0) + { + ferr("mfs_dirmeta_read failed: %d\n", ret); + return ret; + } + + parent = dirmeta.parent; + } + + ret = mfs_alloc_block(sb, &newdir); + if (ret < 0) + { + ferr("mfs_alloc_block failed: %d\n", ret); + return ret; + } + + children_reparented = false; + if (!moved_root) + { + ret = mfs_dirmeta_write(sb, newdir, parent); + if (ret < 0) + { + ferr("mfs_dirmeta_write failed: %d\n", ret); + goto errout_with_newdir; + } + } + + mfs_dirloc_start(sb, directory, &scanloc); + while (mfs_dirloc_is_valid(sb, directory, &scanloc)) + { + ret = mfs_direntry_read(sb, &scanloc, &candidate); + if (ret < 0) + { + ferr("mfs_direntry_read failed: %d\n", ret); + goto errout_with_newdir; + } + + if (mfs_direntry_is_valid(&candidate) && + candidate.oldloc == MFS_LOCATION_INVALID) + { + ret = mfs_dir_follow(sb, directory, &scanloc, &candidate, + &latest, &latestloc); + if (ret == OK) + { + memcpy(&copied, &latest, sizeof(copied)); + copied.oldloc = MFS_LOCATION_INVALID; + mfs_direntry_seal(&copied); + ret = mfs_direntry_add_noflush(sb, newdir, &copied); + if (ret < 0) + { + ferr("copy direntry add failed: %d\n", ret); + goto errout_with_newdir; + } + } + else if (ret != -ENOENT) + { + ferr("mfs_dir_follow failed: %d\n", ret); + goto errout_with_newdir; + } + } + + ret = mfs_dirloc_next(sb, directory, &scanloc); + if (ret < 0) + { + break; + } + } + + ret = mfs_direntry_add_noflush(sb, newdir, direntry); + if (ret < 0) + { + ferr("append to new directory failed: %d\n", ret); + goto errout_with_newdir; + } + + ret = mfs_dir_reparent_children(sb, newdir, newdir); + if (ret < 0) + { + ferr("mfs_dir_reparent_children failed: %d\n", ret); + cleanup = mfs_dir_reparent_children(sb, newdir, directory); + if (cleanup < 0) + { + ferr("child reparent rollback failed: %d\n", cleanup); + ret = cleanup; + } + + goto errout_with_newdir; + } + + children_reparented = true; + if (moved_root) + { + ret = mfs_update_rootdir(sb, newdir); + if (ret < 0) + { + ferr("mfs_update_rootdir failed: %d\n", ret); + goto errout_with_children; + } + } + else + { + ret = mfs_dir_lookup_location(sb, parent, directory, &parententry, + &latestloc); + if (ret < 0) + { + ferr("mfs_dir_lookup_location failed: %d\n", ret); + goto errout_with_children; + } + + parententry.oldloc = directory; + parententry.newloc = newdir; + mfs_direntry_seal(&parententry); + + ret = mfs_direntry_add(sb, parent, &parententry); + if (ret < 0) + { + ferr("parent update direntry add failed: %d\n", ret); + goto errout_with_children; + } + } + + ret = mfs_dir_refresh_open_files(sb, directory, newdir); + if (ret < 0) + { + ferr("mfs_dir_refresh_open_files failed: %d\n", ret); + return ret; + } + + ret = mfs_report_block_deleted(sb, directory); + if (ret < 0) + { + ferr("mfs_report_block_deleted failed: %d\n", ret); + } + + return ret; + +errout_with_children: + if (children_reparented) + { + cleanup = mfs_dir_reparent_children(sb, newdir, directory); + if (cleanup < 0) + { + ferr("child reparent rollback failed: %d\n", cleanup); + ret = cleanup; + } + } + +errout_with_newdir: + cleanup = mfs_report_block_deleted(sb, newdir); + if (cleanup < 0) + { + ferr("newdir cleanup failed: %d\n", cleanup); + } + + return cleanup < 0 ? cleanup : ret; +} + +/**************************************************************************** + * Name: mfs_direntry_add + * + * Description: + * Append one direntry record to a directory, relocating the directory + * block if it is already full. + * + * Input Parameters: + * sb - The mounted file system instance. + * directory - The directory block number. + * direntry - The new direntry record to append. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mfs_direntry_add(FAR struct mfs_sb_s *sb, mfs_t directory, + FAR const struct mfs_direntry_s *direntry) +{ + int ret; + + ret = mfs_direntry_add_noflush(sb, directory, direntry); + if (ret != -ENOSPC) + { + if (ret < 0) + { + ferr("mfs_direntry_add_noflush failed: %d\n", ret); + } + + return ret; + } + + ret = mfs_direntry_flush(sb, directory, direntry); + if (ret < 0) + { + ferr("mfs_direntry_flush failed: %d\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: mfs_dir_is_empty + * + * Description: + * Determine whether a directory contains any live entry chains. + * + * Input Parameters: + * sb - The mounted file system instance. + * directory - The directory block number. + * + * Returned Value: + * Zero (OK) is returned if the directory is empty. -ENOTEMPTY is + * returned if a live entry exists. A negated errno value is returned on + * other failures. + * + ****************************************************************************/ + +static int mfs_dir_is_empty(FAR struct mfs_sb_s *sb, mfs_t directory) +{ + struct mfs_dirloc_s scanloc; + struct mfs_direntry_s candidate; + struct mfs_direntry_s latest; + struct mfs_dirloc_s latestloc; + int ret; + + if (sb == NULL) + { + return -EINVAL; + } + + if (directory == MFS_LOCATION_INVALID || directory >= MFS_BLOCK_COUNT(sb)) + { + return -EINVAL; + } + + mfs_dirloc_start(sb, directory, &scanloc); + while (mfs_dirloc_is_valid(sb, directory, &scanloc)) + { + ret = mfs_direntry_read(sb, &scanloc, &candidate); + if (ret < 0) + { + return ret; + } + + if (mfs_direntry_is_valid(&candidate) && + candidate.oldloc == MFS_LOCATION_INVALID) + { + ret = mfs_dir_follow(sb, directory, &scanloc, &candidate, + &latest, &latestloc); + if (ret == OK) + { + return -ENOTEMPTY; + } + + if (ret != -ENOENT) + { + return ret; + } + } + + ret = mfs_dirloc_next(sb, directory, &scanloc); + if (ret < 0) + { + break; + } + } + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mfs_dirent_traverse + * + * Description: + * Resolve a relative path to its latest live direntry and location. + * + * Input Parameters: + * sb - The mounted file system instance. + * relpath - The relative path to resolve. + * location - The location to receive the resolved object location. + * direntry - The location to receive the resolved direntry contents. + * dirloc - The location to receive the resolved direntry address. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_dirent_traverse(FAR struct mfs_sb_s *sb, FAR const char *relpath, + FAR mfs_t *location, + FAR struct mfs_direntry_s *direntry, + FAR struct mfs_dirloc_s *dirloc) +{ + FAR const char *path; + FAR const char *next; + char component[MFS_NAME_LEN]; + mfs_t current; + int ret; + + if (sb == NULL || location == NULL || direntry == NULL || dirloc == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + memset(component, 0, sizeof(component)); + if (relpath == NULL) + { + ferr("relpath is NULL\n"); + return -EINVAL; + } + + path = mfs_skip_slashes(relpath); + if (path == NULL || *path == '\0') + { + * location = sb->rootdir; + mfs_root_direntry(sb, direntry); + mfs_dirloc_invalidate(dirloc); + return OK; + } + + current = sb->rootdir; + for (; ; ) + { + ret = mfs_next_path_component(path, component, &next); + if (ret < 0) + { + ferr("mfs_next_path_component failed: %d\n", ret); + return ret; + } + + if (component[0] == '\0') + { + ferr("empty path component\n"); + return -EINVAL; + } + + ret = mfs_dir_lookup(sb, current, component, ¤t, + direntry, dirloc); + if (ret < 0) + { + if (ret != -ENOENT) + { + ferr("mfs_dir_lookup failed: %d\n", ret); + } + + return ret; + } + + if (*next == '\0') + { + * location = current; + return OK; + } + + if (!S_ISDIR(direntry->mode)) + { + ferr("non-directory in path: %s\n", component); + return -ENOTDIR; + } + + path = next; + } +} + +/**************************************************************************** + * Name: mfs_dir_create_file + * + * Description: + * Create a new empty regular file. The function allocates a data page, + * emits the initial direntry record, and then resolves the freshly + * created path back to its live state. + * + * Input Parameters: + * sb - The mounted file system instance. + * relpath - The relative path of the file to create. + * mode - The requested file mode. + * location - The location to receive the created file location. + * direntry - The location to receive the created direntry contents. + * dirloc - The location to receive the created direntry address. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_dir_create_file(FAR struct mfs_sb_s *sb, FAR const char *relpath, + mode_t mode, FAR mfs_t *location, + FAR struct mfs_direntry_s *direntry, + FAR struct mfs_dirloc_s *dirloc) +{ + struct mfs_direntry_s newentry; + struct mfs_direntry_s existing; + struct mfs_dirloc_s existingloc; + mfs_t directory; + mfs_t page; + int ret; + int cleanup; + + if (sb == NULL || relpath == NULL || location == NULL || + direntry == NULL || dirloc == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + ret = mfs_path_parent_dir(sb, relpath, &directory); + if (ret < 0) + { + ferr("mfs_path_parent_dir failed: %d\n", ret); + return ret; + } + + ret = mfs_dir_lookup(sb, directory, mfs_direntry_name(relpath), &page, + &existing, &existingloc); + if (ret == OK) + { + ferr("file already exists: %s\n", relpath); + return -EEXIST; + } + + if (ret != -ENOENT) + { + ferr("mfs_dir_lookup failed: %d\n", ret); + return ret; + } + + ret = mfs_direntry_init(&newentry, (mode & ~S_IFMT) | S_IFREG, relpath); + if (ret < 0) + { + ferr("mfs_direntry_init failed: %d\n", ret); + return ret; + } + + ret = mfs_alloc_page(sb, &page); + if (ret < 0) + { + ferr("mfs_alloc_page failed: %d\n", ret); + return ret; + } + + newentry.newloc = page; + newentry.meta.size = 0; + mfs_direntry_seal(&newentry); + + ret = mfs_direntry_add(sb, directory, &newentry); + if (ret < 0) + { + ferr("mfs_direntry_add failed: %d\n", ret); + goto errout_with_page; + } + + ret = mfs_dirent_traverse(sb, relpath, location, direntry, dirloc); + if (ret < 0) + { + ferr("verify traverse failed: %d\n", ret); + goto errout_with_page; + } + + finfo("created file %s\n", relpath); + return OK; + +errout_with_page: + cleanup = mfs_release_page(sb, page); + if (cleanup < 0) + { + ferr("page cleanup failed: %d\n", cleanup); + } + + return cleanup < 0 ? cleanup : ret; +} + +/**************************************************************************** + * Name: mfs_dir_update_file + * + * Description: + * Emit a new direntry record that updates a regular file's stored + * location and size. + * + * Input Parameters: + * sb - The mounted file system instance. + * oldloc - The current file location. + * newloc - The revised file location. + * dirloc - The latest direntry location for the file. + * direntry - The latest direntry contents. Updated in-place on success. + * length - The new stored file size. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_dir_update_file(FAR struct mfs_sb_s *sb, mfs_t oldloc, mfs_t newloc, + FAR const struct mfs_dirloc_s *dirloc, + FAR struct mfs_direntry_s *direntry, + off_t length) +{ + struct mfs_direntry_s newentry; + mfs_t directory; + int ret; + + if (sb == NULL || dirloc == NULL || direntry == NULL || length < 0) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (!S_ISREG(direntry->mode)) + { + ferr("direntry is not regular file\n"); + return -EINVAL; + } + + if (oldloc == MFS_LOCATION_INVALID || newloc == MFS_LOCATION_INVALID) + { + ferr("invalid file location\n"); + return -EINVAL; + } + + if ((uint64_t)length > UINT32_MAX) + { + ferr("file length too large\n"); + return -EFBIG; + } + + if (oldloc == newloc && direntry->meta.size == (uint32_t)length) + { + return OK; + } + + ret = mfs_parent_dir(sb, oldloc, dirloc, &directory); + if (ret < 0) + { + ferr("mfs_parent_dir failed: %d\n", ret); + return ret; + } + + memcpy(&newentry, direntry, sizeof(newentry)); + newentry.oldloc = oldloc; + newentry.newloc = newloc; + newentry.meta.size = (uint32_t)length; + mfs_direntry_seal(&newentry); + + ret = mfs_direntry_add(sb, directory, &newentry); + if (ret < 0) + { + ferr("mfs_direntry_add failed: %d\n", ret); + return ret; + } + + memcpy(direntry, &newentry, sizeof(*direntry)); + return OK; +} + +/**************************************************************************** + * Name: mfs_dir_truncate_file + * + * Description: + * Emit a new direntry record that updates the stored size of a regular + * file without moving the file's current location. + * + * Input Parameters: + * sb - The mounted file system instance. + * location - The current file location. + * dirloc - The latest direntry location for the file. + * direntry - The latest direntry contents. Updated in-place on success. + * length - The new stored file size. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_dir_truncate_file(FAR struct mfs_sb_s *sb, mfs_t location, + FAR const struct mfs_dirloc_s *dirloc, + FAR struct mfs_direntry_s *direntry, + off_t length) +{ + return mfs_dir_update_file(sb, location, location, dirloc, direntry, + length); +} + +/**************************************************************************** + * Name: mfs_dir_opendir + * + * Description: + * Open a directory stream positioned at the first direntry slot in the + * resolved directory block. + * + * Input Parameters: + * sb - The mounted file system instance. + * relpath - The relative path of the directory to open. + * dir - The location to receive the opened directory stream. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_dir_opendir(FAR struct mfs_sb_s *sb, FAR const char *relpath, + FAR struct fs_dirent_s **dir) +{ + FAR struct mfs_dirfs_s *mfsdir; + struct mfs_dirloc_s dirloc; + int ret; + + if (sb == NULL || dir == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + mfsdir = fs_heap_zalloc(sizeof(*mfsdir)); + if (mfsdir == NULL) + { + ferr("fs_heap_zalloc failed\n"); + return -ENOMEM; + } + + ret = mfs_dirent_traverse(sb, relpath, &mfsdir->location, + &mfsdir->direntry, &dirloc); + if (ret < 0) + { + ferr("mfs_dirent_traverse failed: %d\n", ret); + goto errout_with_mfsdir; + } + + if (!S_ISDIR(mfsdir->direntry.mode)) + { + ferr("path is not directory\n"); + ret = -ENOTDIR; + goto errout_with_mfsdir; + } + + mfsdir->dir.fd_path = mfs_dir_pathdup(relpath); + if (mfsdir->dir.fd_path == NULL) + { + ferr("mfs_dir_pathdup failed\n"); + ret = -ENOMEM; + goto errout_with_mfsdir; + } + + mfs_dirloc_start(sb, mfsdir->location, &mfsdir->nextloc); + * dir = &mfsdir->dir; + return OK; + +errout_with_mfsdir: + fs_heap_free(mfsdir); + return ret; +} + +/**************************************************************************** + * Name: mfs_dir_closedir + * + * Description: + * Release one directory stream previously opened by mfs_dir_opendir(). + * + * Input Parameters: + * sb - The mounted file system instance. + * dir - The directory stream to release. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * invalid input. + * + ****************************************************************************/ + +int mfs_dir_closedir(FAR struct mfs_sb_s *sb, FAR struct fs_dirent_s *dir) +{ + if (sb == NULL || dir == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + fs_heap_free(dir); + return OK; +} + +/**************************************************************************** + * Name: mfs_dir_readdir + * + * Description: + * Return the next live directory entry from an open directory stream. + * + * Input Parameters: + * sb - The mounted file system instance. + * dir - The directory stream to advance. + * entry - The location to receive the translated dirent record. + * + * Returned Value: + * Zero (OK) is returned on success. -ENOENT is returned when the end of + * the directory is reached. A negated errno value is returned on other + * failures. + * + ****************************************************************************/ + +int mfs_dir_readdir(FAR struct mfs_sb_s *sb, FAR struct fs_dirent_s *dir, + FAR struct dirent *entry) +{ + FAR struct mfs_dirfs_s *mfsdir; + struct mfs_dirloc_s marked; + struct mfs_dirloc_s nextloc; + struct mfs_direntry_s candidate; + struct mfs_direntry_s latest; + struct mfs_dirloc_s latestloc; + int ret; + + if (sb == NULL || dir == NULL || entry == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + mfsdir = (FAR struct mfs_dirfs_s *)dir; + while (mfs_dirloc_is_valid(sb, mfsdir->location, &mfsdir->nextloc)) + { + marked = mfsdir->nextloc; + nextloc = marked; + ret = mfs_dirloc_next(sb, mfsdir->location, &nextloc); + if (ret < 0) + { + mfs_dirloc_invalidate(&nextloc); + } + + ret = mfs_direntry_read(sb, &marked, &candidate); + if (ret < 0) + { + ferr("mfs_direntry_read failed: %d\n", ret); + return ret; + } + + mfsdir->nextloc = nextloc; + if (!mfs_direntry_is_valid(&candidate) || + candidate.oldloc != MFS_LOCATION_INVALID) + { + continue; + } + + ret = mfs_dir_follow(sb, mfsdir->location, &marked, &candidate, + &latest, &latestloc); + if (ret == -ENOENT) + { + continue; + } + + if (ret < 0) + { + ferr("mfs_dir_follow failed: %d\n", ret); + return ret; + } + + memcpy(&mfsdir->direntry, &latest, sizeof(mfsdir->direntry)); + mfs_direntry_to_dirent(&latest, entry); + return OK; + } + + memset(entry, 0, sizeof(*entry)); + return -ENOENT; +} + +/**************************************************************************** + * Name: mfs_dir_rewinddir + * + * Description: + * Reset an open directory stream so the next read begins at the first + * direntry slot again. + * + * Input Parameters: + * sb - The mounted file system instance. + * dir - The directory stream to rewind. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * invalid input. + * + ****************************************************************************/ + +int mfs_dir_rewinddir(FAR struct mfs_sb_s *sb, FAR struct fs_dirent_s *dir) +{ + FAR struct mfs_dirfs_s *mfsdir; + + if (sb == NULL || dir == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + mfsdir = (FAR struct mfs_dirfs_s *)dir; + mfs_dirloc_start(sb, mfsdir->location, &mfsdir->nextloc); + return OK; +} + +/**************************************************************************** + * Name: mfs_dir_unlink + * + * Description: + * Remove a regular file by appending a tombstone direntry and then + * reporting the file page as deleted. + * + * Input Parameters: + * sb - The mounted file system instance. + * relpath - The relative path of the file to remove. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_dir_unlink(FAR struct mfs_sb_s *sb, FAR const char *relpath) +{ + struct mfs_direntry_s direntry; + struct mfs_dirloc_s dirloc; + mfs_t directory; + mfs_t location; + int ret; + + if (sb == NULL || relpath == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + ret = mfs_dirent_traverse(sb, relpath, &location, &direntry, &dirloc); + if (ret < 0) + { + ferr("mfs_dirent_traverse failed: %d\n", ret); + return ret; + } + + if (S_ISDIR(direntry.mode)) + { + ferr("path is directory: %s\n", relpath); + return -EISDIR; + } + + if (mfs_file_is_open(sb, location)) + { + ferr("file is open: %s\n", relpath); + return -EBUSY; + } + + ret = mfs_parent_dir(sb, location, &dirloc, &directory); + if (ret < 0) + { + ferr("mfs_parent_dir failed: %d\n", ret); + return ret; + } + + direntry.oldloc = location; + direntry.newloc = MFS_LOCATION_INVALID; + mfs_direntry_seal(&direntry); + + ret = mfs_direntry_add(sb, directory, &direntry); + if (ret < 0) + { + ferr("mfs_direntry_add failed: %d\n", ret); + return ret; + } + + ret = mfs_report_page_deleted(sb, location); + if (ret < 0) + { + ferr("mfs_report_page_deleted failed: %d\n", ret); + return ret; + } + + finfo("removed file %s\n", relpath); + return OK; +} + +/**************************************************************************** + * Name: mfs_dir_mkdir + * + * Description: + * Create a new directory block, initialize its metadata page, and emit + * the initial direntry record in the parent directory. + * + * Input Parameters: + * sb - The mounted file system instance. + * relpath - The relative path of the directory to create. + * mode - The requested directory mode. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_dir_mkdir(FAR struct mfs_sb_s *sb, FAR const char *relpath, + mode_t mode) +{ + struct mfs_direntry_s direntry; + struct mfs_direntry_s existing; + struct mfs_dirloc_s existingloc; + mfs_t directory; + mfs_t block; + int ret; + int cleanup; + + if (sb == NULL || relpath == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + ret = mfs_path_parent_dir(sb, relpath, &directory); + if (ret < 0) + { + ferr("mfs_path_parent_dir failed: %d\n", ret); + return ret; + } + + ret = mfs_dir_lookup(sb, directory, mfs_direntry_name(relpath), &block, + &existing, &existingloc); + if (ret == OK) + { + ferr("directory already exists: %s\n", relpath); + return -EEXIST; + } + + if (ret != -ENOENT) + { + ferr("mfs_dir_lookup failed: %d\n", ret); + return ret; + } + + ret = mfs_direntry_init(&direntry, (mode & ~S_IFMT) | S_IFDIR, relpath); + if (ret < 0) + { + ferr("mfs_direntry_init failed: %d\n", ret); + return ret; + } + + ret = mfs_alloc_block(sb, &block); + if (ret < 0) + { + ferr("mfs_alloc_block failed: %d\n", ret); + return ret; + } + + ret = mfs_dirmeta_write(sb, block, directory); + if (ret < 0) + { + ferr("mfs_dirmeta_write failed: %d\n", ret); + goto errout_with_block; + } + + direntry.newloc = block; + direntry.meta.size = MFS_BLOCK_SIZE(sb); + mfs_direntry_seal(&direntry); + + ret = mfs_direntry_add(sb, directory, &direntry); + if (ret < 0) + { + ferr("mfs_direntry_add failed: %d\n", ret); + goto errout_with_block; + } + + finfo("created dir %s\n", relpath); + return OK; + +errout_with_block: + cleanup = mfs_report_block_deleted(sb, block); + if (cleanup < 0) + { + ferr("block cleanup failed: %d\n", cleanup); + } + + return cleanup < 0 ? cleanup : ret; +} + +/**************************************************************************** + * Name: mfs_dir_rmdir + * + * Description: + * Remove an empty directory by appending a tombstone direntry and then + * reporting the directory block as deleted. + * + * Input Parameters: + * sb - The mounted file system instance. + * relpath - The relative path of the directory to remove. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_dir_rmdir(FAR struct mfs_sb_s *sb, FAR const char *relpath) +{ + struct mfs_direntry_s direntry; + struct mfs_dirloc_s dirloc; + mfs_t directory; + mfs_t location; + int ret; + + if (sb == NULL || relpath == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + ret = mfs_dirent_traverse(sb, relpath, &location, &direntry, &dirloc); + if (ret < 0) + { + ferr("mfs_dirent_traverse failed: %d\n", ret); + return ret; + } + + if (!S_ISDIR(direntry.mode)) + { + ferr("path is not directory: %s\n", relpath); + return -ENOTDIR; + } + + ret = mfs_dir_is_empty(sb, location); + if (ret < 0) + { + ferr("mfs_dir_is_empty failed: %d\n", ret); + return ret; + } + + ret = mfs_parent_dir(sb, location, &dirloc, &directory); + if (ret < 0) + { + ferr("mfs_parent_dir failed: %d\n", ret); + return ret; + } + + direntry.oldloc = location; + direntry.newloc = MFS_LOCATION_INVALID; + mfs_direntry_seal(&direntry); + + ret = mfs_direntry_add(sb, directory, &direntry); + if (ret < 0) + { + ferr("mfs_direntry_add failed: %d\n", ret); + return ret; + } + + ret = mfs_report_block_deleted(sb, location); + if (ret < 0) + { + ferr("mfs_report_block_deleted failed: %d\n", ret); + return ret; + } + + finfo("removed dir %s\n", relpath); + return OK; +} + +/**************************************************************************** + * Name: mfs_dir_rename + * + * Description: + * Rename a file system object within the same parent directory by + * appending a new direntry record with the revised name. + * + * Input Parameters: + * sb - The mounted file system instance. + * oldrelpath - The current relative path. + * newrelpath - The replacement relative path. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_dir_rename(FAR struct mfs_sb_s *sb, FAR const char *oldrelpath, + FAR const char *newrelpath) +{ + struct mfs_direntry_s direntry; + struct mfs_dirloc_s dirloc; + mfs_t directory; + mfs_t location; + int ret; + + if (sb == NULL || oldrelpath == NULL || newrelpath == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + ret = mfs_dirent_traverse(sb, oldrelpath, &location, &direntry, &dirloc); + if (ret < 0) + { + ferr("mfs_dirent_traverse failed: %d\n", ret); + return ret; + } + + if (S_ISREG(direntry.mode) && mfs_file_is_open(sb, location)) + { + ferr("file is open: %s\n", oldrelpath); + return -EBUSY; + } + + ret = mfs_same_directory(sb, newrelpath, location, &dirloc, &directory); + if (ret < 0) + { + ferr("mfs_same_directory failed: %d\n", ret); + return ret; + } + + ret = mfs_direntry_set_name(&direntry, newrelpath); + if (ret < 0) + { + ferr("mfs_direntry_set_name failed: %d\n", ret); + return ret; + } + + direntry.oldloc = location; + direntry.newloc = location; + mfs_direntry_seal(&direntry); + ret = mfs_direntry_add(sb, directory, &direntry); + if (ret < 0) + { + ferr("mfs_direntry_add failed: %d\n", ret); + return ret; + } + + finfo("renamed %s -> %s\n", oldrelpath, newrelpath); + return OK; +} + +/**************************************************************************** + * Name: mfs_dir_stat + * + * Description: + * Resolve one path and translate its live direntry into a struct stat. + * + * Input Parameters: + * sb - The mounted file system instance. + * relpath - The relative path to inspect. + * buf - The location to receive the translated stat data. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_dir_stat(FAR struct mfs_sb_s *sb, FAR const char *relpath, + FAR struct stat *buf) +{ + struct mfs_direntry_s direntry; + struct mfs_dirloc_s dirloc; + mfs_t location; + int ret; + + if (sb == NULL || relpath == NULL || buf == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + ret = mfs_dirent_traverse(sb, relpath, &location, &direntry, &dirloc); + if (ret < 0) + { + ferr("mfs_dirent_traverse failed: %d\n", ret); + return ret; + } + + mfs_stat_from_direntry(sb, location, &direntry, buf); + return OK; +} diff --git a/fs/mnemofs/mnemofs_file.c b/fs/mnemofs/mnemofs_file.c new file mode 100644 index 00000000000..e55a55fabe9 --- /dev/null +++ b/fs/mnemofs/mnemofs_file.c @@ -0,0 +1,1732 @@ +/**************************************************************************** + * fs/mnemofs/mnemofs_file.c + * + * SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Alternatively, the contents of this file may be used under the terms of + * the BSD-3-Clause license: + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2024 Saurav Pal + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of its contributors may + * be used to endorse or promote products derived from this software + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "inode/inode.h" +#include "mnemofs.h" + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static FAR struct mfs_ofd_s *mfs_file_ofd(FAR const struct mfs_sb_s *sb, + FAR const struct file *filep); +static void mfs_file_stat(FAR const struct mfs_sb_s *sb, + FAR const struct mfs_ofd_common_s *com, + FAR struct stat *buf); +static int mfs_file_bounds_from_size(FAR struct mfs_sb_s *sb, mfs_t filesize, + FAR mfs_t *eofidx, + FAR mfs_t *eofpgoff, + FAR mfs_t *finalidx); +static mfs_t mfs_file_unit_off(FAR struct mfs_sb_s *sb, mfs_t index); +static int mfs_file_unit_page(FAR struct mfs_sb_s *sb, mfs_t headpage, + mfs_t headidx, mfs_t targetidx, + FAR mfs_t *targetpage); +static int mfs_file_apply_range(FAR struct mfs_sb_s *sb, mfs_t headpage, + mfs_t headidx, mfs_t startidx, + mfs_t endidx, + int (*action)(FAR struct mfs_sb_s *, + mfs_t)); +static int mfs_file_reset_empty(FAR struct mfs_sb_s *sb, + FAR mfs_t *fileloc, + FAR const struct mfs_dirloc_s *dirloc, + FAR struct mfs_direntry_s *direntry); +static ssize_t mfs_file_read_from_off(FAR struct mfs_sb_s *sb, + FAR struct mfs_ofd_s *ofd, + mfs_t offset, FAR char *buf, + size_t buflen); +static ssize_t mfs_file_write_from_off(FAR struct mfs_sb_s *sb, + FAR struct mfs_ofd_s *ofd, + mfs_t offset, + FAR const char *buf, size_t buflen, + FAR mfs_t *newloc, + FAR mfs_t *newsize); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mfs_file_ofd + * + * Description: + * Return the open-file descriptor state associated with filep for this + * mounted file system. + * + * Input Parameters: + * sb - The mounted file system instance. + * filep - The VFS file structure to inspect. + * + * Returned Value: + * A pointer to the matching open-file descriptor is returned on success. + * NULL is returned if no matching descriptor exists. + * + ****************************************************************************/ + +static FAR struct mfs_ofd_s *mfs_file_ofd(FAR const struct mfs_sb_s *sb, + FAR const struct file *filep) +{ + FAR struct mfs_ofd_s *ofd; + + if (sb == NULL || filep == NULL) + { + return NULL; + } + + ofd = filep->f_priv; + if (ofd == NULL || ofd->sb != sb) + { + return NULL; + } + + return ofd; +} + +/**************************************************************************** + * Name: mfs_file_stat + * + * Description: + * Populate a struct stat from one open-file descriptor snapshot. + * + * Input Parameters: + * sb - The mounted file system instance. + * com - The shared open-file descriptor state. + * buf - The location to receive the translated stat data. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void mfs_file_stat(FAR const struct mfs_sb_s *sb, + FAR const struct mfs_ofd_common_s *com, + FAR struct stat *buf) +{ + memset(buf, 0, sizeof(*buf)); + buf->st_ino = com->fileloc; + buf->st_mode = (mode_t)com->direntry.mode; + buf->st_nlink = 1; + buf->st_size = com->direntry.meta.size; + buf->st_atim.tv_sec = com->direntry.mtime; + buf->st_mtim.tv_sec = com->direntry.mtime; + buf->st_ctim.tv_sec = com->direntry.ctime; + buf->st_blksize = MFS_PAGE_SIZE(sb); + buf->st_blocks = (buf->st_size + buf->st_blksize - 1) / + buf->st_blksize; +} + +/**************************************************************************** + * Name: mfs_file_bounds_from_size + * + * Description: + * Convert a stored file size into CTZ end-of-file indices used by the + * file read and write paths. + * + * Input Parameters: + * sb - The mounted file system instance. + * filesize - The file size to decode. + * eofidx - The location to receive the CTZ unit index at EOF. + * eofpgoff - The location to receive the byte offset inside that unit. + * finalidx - Optional location to receive the final allocated unit index. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mfs_file_bounds_from_size(FAR struct mfs_sb_s *sb, mfs_t filesize, + FAR mfs_t *eofidx, + FAR mfs_t *eofpgoff, + FAR mfs_t *finalidx) +{ + int ret; + + if (sb == NULL || eofidx == NULL || eofpgoff == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + ret = mfs_ctz_index_from_off(sb, filesize, eofidx, eofpgoff); + if (ret < 0) + { + ferr("mfs_ctz_index_from_off failed: %d\n", ret); + return ret; + } + + if (finalidx != NULL) + { + if (filesize == 0 || *eofpgoff != 0) + { + * finalidx = *eofidx; + } + else + { + DEBUGASSERT(*eofidx > 0); + * finalidx = *eofidx - 1; + } + } + + return OK; +} + +/**************************************************************************** + * Name: mfs_file_unit_off + * + * Description: + * Return the logical file offset where CTZ unit index begins. + * + * Input Parameters: + * sb - The mounted file system instance. + * index - The CTZ unit index to translate. + * + * Returned Value: + * The logical file offset of the unit is returned. + * + ****************************************************************************/ + +static mfs_t mfs_file_unit_off(FAR struct mfs_sb_s *sb, mfs_t index) +{ + mfs_t off; + mfs_t idx; + mfs_t datasz; + + DEBUGASSERT(sb != NULL); + + off = 0; + for (idx = 0; idx < index; idx++) + { + datasz = mfs_ctz_unit_data_area(sb, idx); + DEBUGASSERT(datasz > 0); + off += datasz; + } + + return off; +} + +/**************************************************************************** + * Name: mfs_file_unit_page + * + * Description: + * Resolve the page that stores targetidx within one CTZ file chain. + * + * Input Parameters: + * sb - The mounted file system instance. + * headpage - The current head page of the file chain. + * headidx - The index of headpage. + * targetidx - The CTZ unit index to resolve. + * targetpage - The location to receive the resolved page. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * invalid input or traversal failure. + * + ****************************************************************************/ + +static int mfs_file_unit_page(FAR struct mfs_sb_s *sb, mfs_t headpage, + mfs_t headidx, mfs_t targetidx, + FAR mfs_t *targetpage) +{ + int ret; + + if (sb == NULL || targetpage == NULL || headpage == MFS_LOCATION_INVALID || + targetidx > headidx) + { + ferr("invalid args\n"); + return -EINVAL; + } + + ret = mfs_ctz_traverse(sb, headidx, targetidx, headpage, targetpage); + if (ret < 0) + { + ferr("mfs_ctz_traverse failed: %d\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: mfs_file_apply_range + * + * Description: + * Resolve each CTZ unit in an inclusive index range and apply action to + * its page. + * + * Input Parameters: + * sb - The mounted file system instance. + * headpage - The current head page of the file chain. + * headidx - The index of headpage. + * startidx - The first CTZ unit index in the range. + * endidx - The last CTZ unit index in the range. + * action - The callback to invoke for each resolved page. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * invalid input, traversal failure, or callback failure. + * + ****************************************************************************/ + +static int mfs_file_apply_range(FAR struct mfs_sb_s *sb, mfs_t headpage, + mfs_t headidx, mfs_t startidx, + mfs_t endidx, + int (*action)(FAR struct mfs_sb_s *, + mfs_t)) +{ + mfs_t idx; + mfs_t page; + int ret; + + if (sb == NULL || action == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (startidx > endidx) + { + return OK; + } + + if (endidx > headidx) + { + ferr("invalid range\n"); + return -EINVAL; + } + + idx = startidx; + while (true) + { + ret = mfs_file_unit_page(sb, headpage, headidx, idx, &page); + if (ret < 0) + { + ferr("mfs_file_unit_page failed: %d\n", ret); + return ret; + } + + ret = action(sb, page); + if (ret < 0) + { + ferr("range action failed: %d\n", ret); + return ret; + } + + if (idx == endidx) + { + return OK; + } + + idx++; + } +} + +/**************************************************************************** + * Name: mfs_file_reset_empty + * + * Description: + * Replace one regular file with a fresh empty single-page image and mark + * the old committed pages obsolete. + * + * Input Parameters: + * sb - The mounted file system instance. + * fileloc - The current file head location. Updated on success. + * dirloc - The latest known direntry location for the file. + * direntry - The latest direntry contents. Updated in-place on success. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +static int mfs_file_reset_empty(FAR struct mfs_sb_s *sb, + FAR mfs_t *fileloc, + FAR const struct mfs_dirloc_s *dirloc, + FAR struct mfs_direntry_s *direntry) +{ + FAR uint8_t *pagebuf; + mfs_t oldloc; + mfs_t oldsize; + mfs_t old_eof_idx; + mfs_t old_eof_pgoff; + mfs_t old_final_idx; + mfs_t newloc; + ssize_t nwritten; + int ret; + int cleanup; + + if (sb == NULL || fileloc == NULL || dirloc == NULL || direntry == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (!S_ISREG(direntry->mode)) + { + ferr("direntry is not regular file\n"); + return -EINVAL; + } + + oldloc = *fileloc; + oldsize = direntry->meta.size; + if (oldloc == MFS_LOCATION_INVALID) + { + ferr("invalid file location\n"); + return -EINVAL; + } + + if (oldsize == 0) + { + return OK; + } + + ret = mfs_file_bounds_from_size(sb, oldsize, &old_eof_idx, &old_eof_pgoff, + &old_final_idx); + if (ret < 0) + { + ferr("old bounds decode failed: %d\n", ret); + return ret; + } + + UNUSED(old_eof_idx); + UNUSED(old_eof_pgoff); + + pagebuf = kmm_zalloc(MFS_PAGE_SIZE(sb)); + if (pagebuf == NULL) + { + ferr("kmm_zalloc failed\n"); + return -ENOMEM; + } + + newloc = MFS_LOCATION_INVALID; + ret = mfs_alloc_page(sb, &newloc); + if (ret < 0) + { + ferr("mfs_alloc_page failed: %d\n", ret); + goto errout_with_buf; + } + + nwritten = mfs_write_page(sb, newloc, pagebuf); + if (nwritten < 0) + { + ferr("mfs_write_page failed: %zd\n", nwritten); + ret = nwritten; + goto errout_with_page; + } + + if (nwritten != 1) + { + ferr("short write: %zd\n", nwritten); + ret = -EIO; + goto errout_with_page; + } + + ret = mfs_dir_update_file(sb, oldloc, newloc, dirloc, direntry, 0); + if (ret < 0) + { + ferr("mfs_dir_update_file failed: %d\n", ret); + goto errout_with_page; + } + + * fileloc = newloc; + kmm_free(pagebuf); + + ret = mfs_file_apply_range(sb, oldloc, old_final_idx, 0, old_final_idx, + mfs_report_page_deleted); + if (ret < 0) + { + ferr("old page cleanup failed: %d\n", ret); + } + + return ret; + +errout_with_page: + cleanup = mfs_release_page(sb, newloc); + if (cleanup < 0) + { + ferr("page cleanup failed: %d\n", cleanup); + ret = cleanup; + } + +errout_with_buf: + kmm_free(pagebuf); + return ret; +} + +/**************************************************************************** + * Name: mfs_file_read_from_off + * + * Description: + * Read file data starting at one logical offset without updating the + * caller's open-file position. + * + * Input Parameters: + * sb - The mounted file system instance. + * ofd - The open-file descriptor state. + * offset - The logical file offset to start reading from. + * buf - The caller's buffer. + * buflen - The number of bytes requested. + * + * Returned Value: + * A non-negative byte count is returned on success. A negated errno + * value is returned on failure. + * + ****************************************************************************/ + +static ssize_t mfs_file_read_from_off(FAR struct mfs_sb_s *sb, + FAR struct mfs_ofd_s *ofd, + mfs_t offset, FAR char *buf, + size_t buflen) +{ + mfs_t filesize; + mfs_t final_idx; + mfs_t eof_idx; + mfs_t eof_pgoff; + mfs_t s_idx; + mfs_t s_pgoff; + mfs_t e_idx; + mfs_t e_pgoff; + mfs_t c_idx; + mfs_t c_pgoff; + mfs_t c_pg; + mfs_t readlen; + mfs_t off; + mfs_t datasz; + mfs_t rd_sz; + ssize_t nread; + int ret; + + if (sb == NULL || ofd == NULL || ofd->sb != sb || ofd->common == NULL || + (buf == NULL && buflen != 0)) + { + ferr("invalid args\n"); + return -EINVAL; + } + + filesize = ofd->common->direntry.meta.size; + if (buflen == 0 || offset >= filesize) + { + return 0; + } + + readlen = MIN((mfs_t)buflen, filesize - offset); + ret = mfs_file_bounds_from_size(sb, filesize, &eof_idx, &eof_pgoff, + &final_idx); + if (ret < 0) + { + ferr("mfs_file_bounds_from_size failed: %d\n", ret); + return ret; + } + + ret = mfs_ctz_index_from_off(sb, offset, &s_idx, &s_pgoff); + if (ret < 0) + { + ferr("start offset decode failed: %d\n", ret); + return ret; + } + + ret = mfs_ctz_index_from_off(sb, offset + readlen - 1, &e_idx, &e_pgoff); + if (ret < 0) + { + ferr("end offset decode failed: %d\n", ret); + return ret; + } + + UNUSED(eof_idx); + UNUSED(eof_pgoff); + UNUSED(e_pgoff); + + c_idx = s_idx; + c_pgoff = s_pgoff; + off = 0; + while (off < readlen) + { + datasz = mfs_ctz_unit_data_area(sb, c_idx); + if (datasz == 0 || c_pgoff > datasz) + { + ferr("invalid data area\n"); + return -EINVAL; + } + + rd_sz = MIN(datasz - c_pgoff, readlen - off); + ret = mfs_file_unit_page(sb, ofd->common->fileloc, final_idx, c_idx, + &c_pg); + if (ret < 0) + { + ferr("mfs_file_unit_page failed: %d\n", ret); + return ret; + } + + nread = mfs_read_page(sb, c_pg, sb->rwbuf); + if (nread < 0) + { + ferr("mfs_read_page failed: %zd\n", nread); + return nread; + } + + if (nread != 1) + { + ferr("short read: %zd\n", nread); + return -EIO; + } + + memcpy(buf + off, sb->rwbuf + c_pgoff, rd_sz); + off += rd_sz; + + if (c_idx == e_idx) + { + break; + } + + c_idx++; + c_pgoff = 0; + } + + return off; +} + +/**************************************************************************** + * Name: mfs_file_write_from_off + * + * Description: + * Materialize a rewritten CTZ file image for a write at one logical + * offset without updating directory state yet. + * + * Input Parameters: + * sb - The mounted file system instance. + * ofd - The open-file descriptor state. + * offset - The logical file offset to start writing at. + * buffer - The source bytes to write. + * buflen - The number of bytes to write. + * newloc - The location to receive the new file head page. + * newsize - The location to receive the resulting file size. + * + * Returned Value: + * A non-negative byte count is returned on success. A negated errno + * value is returned on failure. + * + ****************************************************************************/ + +static ssize_t mfs_file_write_from_off(FAR struct mfs_sb_s *sb, + FAR struct mfs_ofd_s *ofd, + mfs_t offset, + FAR const char *buffer, size_t buflen, + FAR mfs_t *newloc, + FAR mfs_t *newsize) +{ + FAR uint8_t *pagebuf; + mfs_t oldsize; + mfs_t writeend; + mfs_t filesize; + mfs_t old_eof_idx; + mfs_t old_eof_pgoff; + mfs_t old_final_idx; + mfs_t new_eof_idx; + mfs_t new_eof_pgoff; + mfs_t new_final_idx; + mfs_t first_new_idx; + mfs_t s_idx; + mfs_t s_pgoff; + mfs_t prev_page; + mfs_t last_new_page; + mfs_t last_new_idx; + mfs_t cur_idx; + mfs_t cur_page; + mfs_t cur_old_page; + mfs_t unit_off; + mfs_t unit_end; + mfs_t copy_start; + mfs_t copy_end; + mfs_t datasz; + ssize_t nread; + ssize_t nwritten; + int ret; + int cleanup; + + if (sb == NULL || ofd == NULL || ofd->sb != sb || ofd->common == NULL || + newloc == NULL || newsize == NULL || + (buffer == NULL && buflen != 0)) + { + ferr("invalid args\n"); + return -EINVAL; + } + + * newloc = MFS_LOCATION_INVALID; + * newsize = ofd->common->direntry.meta.size; + + if (buflen == 0) + { + * newloc = ofd->common->fileloc; + * newsize = ofd->common->direntry.meta.size; + return 0; + } + + if ((uint64_t)offset + (uint64_t)buflen > UINT32_MAX) + { + ferr("write would overflow file size\n"); + return -EFBIG; + } + + oldsize = ofd->common->direntry.meta.size; + writeend = offset + (mfs_t)buflen; + filesize = MAX(oldsize, writeend); + + ret = mfs_ctz_index_from_off(sb, offset, &s_idx, &s_pgoff); + if (ret < 0) + { + ferr("start offset decode failed: %d\n", ret); + return ret; + } + + ret = mfs_file_bounds_from_size(sb, oldsize, &old_eof_idx, &old_eof_pgoff, + &old_final_idx); + if (ret < 0) + { + ferr("old bounds decode failed: %d\n", ret); + return ret; + } + + ret = mfs_file_bounds_from_size(sb, filesize, &new_eof_idx, &new_eof_pgoff, + &new_final_idx); + if (ret < 0) + { + ferr("new bounds decode failed: %d\n", ret); + return ret; + } + + UNUSED(old_eof_pgoff); + UNUSED(new_eof_idx); + UNUSED(new_eof_pgoff); + UNUSED(s_pgoff); + + first_new_idx = MIN(s_idx, old_eof_idx); + prev_page = MFS_LOCATION_INVALID; + last_new_page = MFS_LOCATION_INVALID; + last_new_idx = MFS_LOCATION_INVALID; + unit_off = mfs_file_unit_off(sb, first_new_idx); + + if (first_new_idx > 0) + { + ret = mfs_file_unit_page(sb, ofd->common->fileloc, old_final_idx, + first_new_idx - 1, &prev_page); + if (ret < 0) + { + ferr("mfs_file_unit_page failed: %d\n", ret); + return ret; + } + } + + pagebuf = kmm_malloc(MFS_PAGE_SIZE(sb)); + if (pagebuf == NULL) + { + ferr("kmm_malloc failed\n"); + return -ENOMEM; + } + + cur_idx = first_new_idx; + while (true) + { + datasz = mfs_ctz_unit_data_area(sb, cur_idx); + if (datasz == 0) + { + ferr("invalid data area\n"); + ret = -EINVAL; + goto errout; + } + + unit_end = unit_off + datasz; + memset(pagebuf, 0, MFS_PAGE_SIZE(sb)); + + if (cur_idx > 0) + { + ret = mfs_ctz_fill_next_ptrs(sb, prev_page, cur_idx - 1, pagebuf); + if (ret < 0) + { + ferr("mfs_ctz_fill_next_ptrs failed: %d\n", ret); + goto errout; + } + } + + if (cur_idx <= old_final_idx) + { + ret = mfs_file_unit_page(sb, ofd->common->fileloc, old_final_idx, + cur_idx, &cur_old_page); + if (ret < 0) + { + ferr("mfs_file_unit_page failed: %d\n", ret); + goto errout; + } + + nread = mfs_read_page(sb, cur_old_page, sb->rwbuf); + if (nread < 0) + { + ferr("mfs_read_page failed: %zd\n", nread); + ret = nread; + goto errout; + } + + if (nread != 1) + { + ferr("short read: %zd\n", nread); + ret = -EIO; + goto errout; + } + } + + if (cur_idx <= old_final_idx) + { + copy_start = unit_off; + copy_end = MIN(unit_end, MIN(offset, oldsize)); + if (copy_start < copy_end) + { + memcpy(pagebuf + (copy_start - unit_off), sb->rwbuf, + copy_end - copy_start); + } + } + + copy_start = MAX(unit_off, offset); + copy_end = MIN(unit_end, writeend); + if (copy_start < copy_end) + { + memcpy(pagebuf + (copy_start - unit_off), + buffer + (copy_start - offset), copy_end - copy_start); + } + + if (cur_idx <= old_final_idx) + { + copy_start = MAX(unit_off, writeend); + copy_end = MIN(unit_end, oldsize); + if (copy_start < copy_end) + { + memcpy(pagebuf + (copy_start - unit_off), + sb->rwbuf + (copy_start - unit_off), + copy_end - copy_start); + } + } + + ret = mfs_alloc_page(sb, &cur_page); + if (ret < 0) + { + ferr("mfs_alloc_page failed: %d\n", ret); + goto errout; + } + + nwritten = mfs_write_page(sb, cur_page, pagebuf); + if (nwritten < 0) + { + ferr("mfs_write_page failed: %zd\n", nwritten); + ret = nwritten; + cleanup = mfs_release_page(sb, cur_page); + if (cleanup < 0) + { + ferr("mfs_release_page failed: %d\n", cleanup); + ret = cleanup; + } + + goto errout; + } + + if (nwritten != 1) + { + ferr("short write: %zd\n", nwritten); + ret = -EIO; + cleanup = mfs_release_page(sb, cur_page); + if (cleanup < 0) + { + ferr("mfs_release_page failed: %d\n", cleanup); + ret = cleanup; + } + + goto errout; + } + + prev_page = cur_page; + last_new_page = cur_page; + last_new_idx = cur_idx; + + if (cur_idx == new_final_idx) + { + break; + } + + unit_off = unit_end; + cur_idx++; + } + + kmm_free(pagebuf); + * newloc = last_new_page; + * newsize = filesize; + return (ssize_t)buflen; + +errout: + kmm_free(pagebuf); + if (last_new_page != MFS_LOCATION_INVALID) + { + cleanup = mfs_file_apply_range(sb, last_new_page, last_new_idx, + first_new_idx, last_new_idx, + mfs_release_page); + if (cleanup < 0) + { + ferr("rollback release failed: %d\n", cleanup); + return cleanup; + } + } + + ferr("failed: %d\n", ret); + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mfs_file_is_open + * + * Description: + * Check whether a live file location already has an open descriptor. + * + * Input Parameters: + * sb - The mounted file system instance. + * fileloc - The file location to test. + * + * Returned Value: + * true is returned if the file is already open. false is returned + * otherwise. + * + ****************************************************************************/ + +bool mfs_file_is_open(FAR const struct mfs_sb_s *sb, mfs_t fileloc) +{ + FAR struct list_node *node; + FAR struct mfs_ofd_s *ofd; + + if (sb == NULL || fileloc == MFS_LOCATION_INVALID) + { + return false; + } + + list_for_every(&sb->ofiles, node) + { + ofd = list_entry(node, struct mfs_ofd_s, entry); + if (ofd->common != NULL && ofd->common->fileloc == fileloc) + { + return true; + } + } + + return false; +} + +/**************************************************************************** + * Name: mfs_file_open + * + * Description: + * Open a regular file. If O_CREAT is present and the path is absent, + * then an initial direntry record and page allocation are performed + * before the open-file state is created. + * + * Input Parameters: + * filep - The file structure to initialize. + * relpath - The relative path of the file. + * oflags - Open flags. + * mode - Create mode used with O_CREAT. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_file_open(FAR struct mfs_sb_s *sb, FAR struct file *filep, + FAR const char *relpath, int oflags, mode_t mode) +{ + FAR struct mfs_ofd_s *ofd; + FAR struct mfs_ofd_common_s *common; + FAR char *pathdup; + struct mfs_direntry_s direntry; + struct mfs_dirloc_s dirloc; + mfs_t location; + int ret; + + if (sb == NULL || filep == NULL || relpath == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if ((oflags & O_CREAT) != 0) + { + ret = mfs_dirent_traverse(sb, relpath, &location, &direntry, &dirloc); + if (ret == OK) + { + if (!S_ISREG(direntry.mode)) + { + ferr("path is directory: %s\n", relpath); + return -EISDIR; + } + + if ((oflags & O_EXCL) != 0) + { + ferr("file exists with O_EXCL: %s\n", relpath); + return -EEXIST; + } + } + else + { + if (ret != -ENOENT) + { + ferr("mfs_dirent_traverse failed: %d\n", ret); + return ret; + } + + if ((mode & S_IFMT) == S_IFDIR) + { + ferr("create requested directory mode: %s\n", relpath); + return -EISDIR; + } + + ret = mfs_dir_create_file(sb, relpath, mode, &location, + &direntry, &dirloc); + if (ret < 0) + { + ferr("mfs_dir_create_file failed: %d\n", ret); + return ret; + } + } + } + else + { + ret = mfs_dirent_traverse(sb, relpath, &location, &direntry, &dirloc); + if (ret < 0) + { + ferr("mfs_dirent_traverse failed: %d\n", ret); + return ret; + } + + if (!S_ISREG(direntry.mode)) + { + ferr("path is directory: %s\n", relpath); + return -EISDIR; + } + } + + common = kmm_zalloc(sizeof(*common)); + if (common == NULL) + { + ferr("common allocation failed\n"); + return -ENOMEM; + } + + ofd = kmm_zalloc(sizeof(*ofd)); + if (ofd == NULL) + { + ferr("ofd allocation failed\n"); + ret = -ENOMEM; + goto errout_with_common; + } + + pathdup = kmm_malloc(strlen(relpath) + 1); + if (pathdup == NULL) + { + ferr("path allocation failed\n"); + ret = -ENOMEM; + goto errout_with_ofd; + } + + strlcpy(pathdup, relpath, strlen(relpath) + 1); + + if ((oflags & O_TRUNC) != 0 && (oflags & O_ACCMODE) != O_RDONLY) + { + ret = mfs_file_reset_empty(sb, &location, &dirloc, &direntry); + if (ret < 0) + { + ferr("mfs_file_reset_empty failed: %d\n", ret); + goto errout_with_pathdup; + } + } + + memcpy(&common->direntry, &direntry, sizeof(common->direntry)); + memcpy(&common->direntloc, &dirloc, sizeof(common->direntloc)); + common->relpath = pathdup; + common->fileloc = location; + common->refs = 1; + + ofd->sb = sb; + ofd->common = common; + list_clear_node(&ofd->entry); + list_add_tail(&sb->ofiles, &ofd->entry); + filep->f_priv = ofd; + filep->f_pos = 0; + return OK; + +errout_with_pathdup: + kmm_free(pathdup); +errout_with_ofd: + kmm_free(ofd); +errout_with_common: + kmm_free(common); + return ret; +} + +/**************************************************************************** + * Name: mfs_file_close + * + * Description: + * Close one open-file descriptor, releasing shared state when the last + * reference disappears. + * + * Input Parameters: + * sb - The mounted file system instance. + * filep - The VFS file structure to close. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * invalid input. + * + ****************************************************************************/ + +int mfs_file_close(FAR struct mfs_sb_s *sb, FAR struct file *filep) +{ + FAR struct mfs_ofd_s *ofd; + + ofd = mfs_file_ofd(sb, filep); + if (ofd == NULL || ofd->common == NULL) + { + ferr("invalid file handle\n"); + return -EINVAL; + } + + list_delete(&ofd->entry); + ofd->common->refs--; + if (ofd->common->refs == 0) + { + kmm_free(ofd->common->relpath); + kmm_free(ofd->common); + } + + kmm_free(ofd); + filep->f_priv = NULL; + return OK; +} + +/**************************************************************************** + * Name: mfs_file_read + * + * Description: + * Read from an open regular file at its current file position and advance + * that position by the number of bytes read. + * + * Input Parameters: + * sb - The mounted file system instance. + * filep - The VFS file structure to read from. + * buffer - The caller's read buffer. + * buflen - The number of bytes requested. + * + * Returned Value: + * A non-negative byte count is returned on success. A negated errno + * value is returned on failure. + * + ****************************************************************************/ + +ssize_t mfs_file_read(FAR struct mfs_sb_s *sb, FAR struct file *filep, + FAR char *buffer, size_t buflen) +{ + FAR struct mfs_ofd_s *ofd; + ssize_t ret; + + ofd = mfs_file_ofd(sb, filep); + if (ofd == NULL || ofd->common == NULL || (buffer == NULL && buflen != 0)) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (ofd->common->pos < 0) + { + ferr("negative file position\n"); + return -EINVAL; + } + + if ((uint64_t)ofd->common->pos > UINT32_MAX) + { + return 0; + } + + ret = mfs_file_read_from_off(sb, ofd, (mfs_t)ofd->common->pos, buffer, + buflen); + if (ret < 0) + { + ferr("mfs_file_read_from_off failed: %zd\n", ret); + } + + if (ret > 0) + { + ofd->common->pos += ret; + filep->f_pos = ofd->common->pos; + } + + return ret; +} + +/**************************************************************************** + * Name: mfs_file_write + * + * Description: + * Write to an open regular file, update directory state to the new CTZ + * chain, and advance the current file position. + * + * Input Parameters: + * sb - The mounted file system instance. + * filep - The VFS file structure to write to. + * buffer - The source bytes to write. + * buflen - The number of bytes to write. + * + * Returned Value: + * A non-negative byte count is returned on success. A negated errno + * value is returned on failure. + * + ****************************************************************************/ + +ssize_t mfs_file_write(FAR struct mfs_sb_s *sb, FAR struct file *filep, + FAR const char *buffer, size_t buflen) +{ + FAR struct mfs_ofd_s *ofd; + off_t offset; + mfs_t writeoff; + mfs_t newloc; + mfs_t newsize; + mfs_t oldloc; + mfs_t oldsize; + mfs_t s_idx; + mfs_t s_pgoff; + mfs_t old_eof_idx; + mfs_t old_eof_pgoff; + mfs_t old_final_idx; + mfs_t new_eof_idx; + mfs_t new_eof_pgoff; + mfs_t new_final_idx; + mfs_t first_new_idx; + int cleanupret; + int updateret; + ssize_t ret; + + ofd = mfs_file_ofd(sb, filep); + if (ofd == NULL || ofd->common == NULL || (buffer == NULL && buflen != 0)) + { + ferr("invalid args\n"); + return -EINVAL; + } + + offset = ofd->common->pos; + if ((filep->f_oflags & O_APPEND) != 0) + { + offset = ofd->common->direntry.meta.size; + } + + if (offset < 0) + { + ferr("negative file position\n"); + return -EINVAL; + } + + if ((uint64_t)offset > UINT32_MAX) + { + ferr("file offset too large\n"); + return -EFBIG; + } + + writeoff = (mfs_t)offset; + oldloc = ofd->common->fileloc; + oldsize = ofd->common->direntry.meta.size; + + ret = mfs_file_write_from_off(sb, ofd, writeoff, buffer, buflen, &newloc, + &newsize); + if (ret <= 0) + { + if (ret < 0) + { + ferr("mfs_file_write_from_off failed: %zd\n", ret); + } + + return ret; + } + + if (newloc == MFS_LOCATION_INVALID) + { + ferr("write returned invalid new location\n"); + ret = -EIO; + goto errout; + } + + updateret = mfs_dir_update_file(sb, oldloc, newloc, + &ofd->common->direntloc, + &ofd->common->direntry, (off_t)newsize); + if (updateret < 0) + { + ferr("mfs_dir_update_file failed: %d\n", updateret); + ret = updateret; + goto errout_with_newpages; + } + + ofd->common->fileloc = newloc; + ofd->common->pos = offset + ret; + filep->f_pos = ofd->common->pos; + + cleanupret = mfs_ctz_index_from_off(sb, writeoff, &s_idx, &s_pgoff); + if (cleanupret < 0) + { + ferr("start offset decode failed: %d\n", cleanupret); + ret = cleanupret; + goto errout; + } + + cleanupret = mfs_file_bounds_from_size(sb, oldsize, &old_eof_idx, + &old_eof_pgoff, &old_final_idx); + if (cleanupret < 0) + { + ferr("old bounds decode failed: %d\n", cleanupret); + ret = cleanupret; + goto errout; + } + + UNUSED(s_pgoff); + UNUSED(old_eof_pgoff); + first_new_idx = MIN(s_idx, old_eof_idx); + cleanupret = mfs_file_apply_range(sb, oldloc, old_final_idx, + first_new_idx, old_final_idx, + mfs_report_page_deleted); + if (cleanupret < 0) + { + ferr("old page cleanup failed: %d\n", cleanupret); + ret = cleanupret; + } + + return ret; + +errout_with_newpages: + cleanupret = mfs_ctz_index_from_off(sb, writeoff, &s_idx, &s_pgoff); + if (cleanupret >= 0) + { + cleanupret = mfs_file_bounds_from_size(sb, newsize, + &new_eof_idx, + &new_eof_pgoff, + &new_final_idx); + } + + if (cleanupret >= 0) + { + cleanupret = mfs_file_bounds_from_size(sb, oldsize, + &old_eof_idx, + &old_eof_pgoff, + &old_final_idx); + } + + if (cleanupret >= 0) + { + UNUSED(s_pgoff); + UNUSED(old_eof_pgoff); + UNUSED(new_eof_idx); + UNUSED(new_eof_pgoff); + first_new_idx = MIN(s_idx, old_eof_idx); + cleanupret = mfs_file_apply_range(sb, newloc, new_final_idx, + first_new_idx, new_final_idx, + mfs_release_page); + } + + if (cleanupret < 0) + { + ferr("rollback cleanup failed: %d\n", cleanupret); + } + + return cleanupret < 0 ? cleanupret : ret; + +errout: + ferr("failed: %zd\n", ret); + return ret; +} + +/**************************************************************************** + * Name: mfs_file_seek + * + * Description: + * Update the current logical file position for an open file. + * + * Input Parameters: + * sb - The mounted file system instance. + * filep - The VFS file structure to reposition. + * offset - The seek offset argument. + * whence - The seek base selector. + * + * Returned Value: + * The new file position is returned on success. A negated errno value is + * returned on failure. + * + ****************************************************************************/ + +off_t mfs_file_seek(FAR struct mfs_sb_s *sb, FAR struct file *filep, + off_t offset, int whence) +{ + FAR struct mfs_ofd_s *ofd; + off_t nextpos; + + ofd = mfs_file_ofd(sb, filep); + if (ofd == NULL || ofd->common == NULL) + { + ferr("invalid file handle\n"); + return -EINVAL; + } + + if (whence == SEEK_SET) + { + nextpos = offset; + } + else if (whence == SEEK_CUR) + { + nextpos = ofd->common->pos + offset; + } + else if (whence == SEEK_END) + { + nextpos = ofd->common->direntry.meta.size + offset; + } + else + { + ferr("invalid whence\n"); + return -EINVAL; + } + + if (nextpos < 0) + { + ferr("negative target position\n"); + return -EINVAL; + } + + ofd->common->pos = nextpos; + filep->f_pos = nextpos; + return nextpos; +} + +/**************************************************************************** + * Name: mfs_file_ioctl + * + * Description: + * Handle file-specific ioctl requests for an open mnemofs file. + * + * Input Parameters: + * sb - The mounted file system instance. + * filep - The VFS file structure to operate on. + * cmd - The ioctl command. + * arg - The ioctl argument. + * + * Returned Value: + * Zero (OK) or another command-specific value is returned on success. A + * negated errno value is returned on failure. + * + ****************************************************************************/ + +int mfs_file_ioctl(FAR struct mfs_sb_s *sb, FAR struct file *filep, + int cmd, unsigned long arg) +{ + FAR struct mfs_ofd_s *ofd; + FAR char *path; + size_t len; + int ret; + + ofd = mfs_file_ofd(sb, filep); + if (ofd == NULL || ofd->common == NULL) + { + ferr("invalid file handle\n"); + return -EINVAL; + } + + switch (cmd) + { + case FIOC_FILEPATH: + { + path = (FAR char *)(uintptr_t)arg; + if (path == NULL || ofd->common->relpath == NULL) + { + ferr("invalid filepath buffer\n"); + return -EINVAL; + } + + ret = inode_getpath(filep->f_inode, path, PATH_MAX); + if (ret < 0) + { + ferr("inode_getpath failed: %d\n", ret); + return ret; + } + + len = strlen(path); + if (len > 0 && path[len - 1] != '/') + { + path[len++] = '/'; + path[len] = '\0'; + } + + if (strlcat(path, ofd->common->relpath, PATH_MAX) >= PATH_MAX) + { + ferr("path too long\n"); + return -ENAMETOOLONG; + } + + return OK; + } + + case BIOC_FLUSH: + { + if (sb->mtd == NULL) + { + ferr("missing mtd\n"); + return -ENODEV; + } + + ret = MTD_IOCTL(sb->mtd, BIOC_FLUSH, arg); + if (ret < 0) + { + ferr("BIOC_FLUSH failed: %d\n", ret); + } + + return ret; + } + + default: + { + ferr("unsupported ioctl: %d\n", cmd); + return -ENOTTY; + } + } +} + +/**************************************************************************** + * Name: mfs_file_truncate + * + * Description: + * Update a file's stored length by emitting a new direntry record that + * points to the same file location with a different size. + * + * Input Parameters: + * filep - The open file instance. + * length - The new stored file size. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_file_truncate(FAR struct mfs_sb_s *sb, FAR struct file *filep, + off_t length) +{ + FAR struct mfs_ofd_s *ofd; + int ret; + + ofd = mfs_file_ofd(sb, filep); + if (ofd == NULL || ofd->common == NULL || length < 0) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (length == 0) + { + ret = mfs_file_reset_empty(sb, &ofd->common->fileloc, + &ofd->common->direntloc, + &ofd->common->direntry); + } + else + { + ret = mfs_dir_truncate_file(sb, ofd->common->fileloc, + &ofd->common->direntloc, + &ofd->common->direntry, length); + } + + if (ret < 0) + { + ferr("truncate failed: %d\n", ret); + return ret; + } + + if (ofd->common->pos > length) + { + ofd->common->pos = length; + filep->f_pos = length; + } + + return OK; +} + +/**************************************************************************** + * Name: mfs_file_sync + * + * Description: + * Validate one open-file descriptor for sync. mnemofs updates metadata + * eagerly, so no extra flush work is needed here. + * + * Input Parameters: + * sb - The mounted file system instance. + * filep - The VFS file structure to sync. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * invalid input. + * + ****************************************************************************/ + +int mfs_file_sync(FAR struct mfs_sb_s *sb, FAR struct file *filep) +{ + FAR struct mfs_ofd_s *ofd; + + ofd = mfs_file_ofd(sb, filep); + if (ofd == NULL || ofd->common == NULL) + { + ferr("invalid file handle\n"); + return -EINVAL; + } + + return OK; +} + +/**************************************************************************** + * Name: mfs_file_dup + * + * Description: + * Duplicate one open-file descriptor so both VFS file structures share + * the same underlying open-file state. + * + * Input Parameters: + * sb - The mounted file system instance. + * oldp - The existing VFS file structure. + * newp - The new VFS file structure to initialize. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_file_dup(FAR struct mfs_sb_s *sb, FAR const struct file *oldp, + FAR struct file *newp) +{ + FAR struct mfs_ofd_s *oldofd; + FAR struct mfs_ofd_s *newofd; + + oldofd = mfs_file_ofd(sb, oldp); + if (oldofd == NULL || oldofd->common == NULL || newp == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + newofd = kmm_zalloc(sizeof(*newofd)); + if (newofd == NULL) + { + ferr("ofd allocation failed\n"); + return -ENOMEM; + } + + newofd->sb = oldofd->sb; + newofd->common = oldofd->common; + newofd->common->refs++; + list_clear_node(&newofd->entry); + list_add_tail(&newofd->sb->ofiles, &newofd->entry); + newp->f_priv = newofd; + newp->f_pos = newofd->common->pos; + return OK; +} + +/**************************************************************************** + * Name: mfs_file_fstat + * + * Description: + * Return file status information for one open regular file. + * + * Input Parameters: + * sb - The mounted file system instance. + * filep - The VFS file structure to inspect. + * buf - The location to receive the translated stat data. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned on + * failure. + * + ****************************************************************************/ + +int mfs_file_fstat(FAR struct mfs_sb_s *sb, FAR const struct file *filep, + FAR struct stat *buf) +{ + FAR struct mfs_ofd_s *ofd; + + ofd = mfs_file_ofd(sb, filep); + if (ofd == NULL || ofd->common == NULL || buf == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + mfs_file_stat(sb, ofd->common, buf); + return OK; +} diff --git a/fs/mnemofs/mnemofs_fsobj.c b/fs/mnemofs/mnemofs_fsobj.c deleted file mode 100644 index e6c43814125..00000000000 --- a/fs/mnemofs/mnemofs_fsobj.c +++ /dev/null @@ -1,1136 +0,0 @@ -/**************************************************************************** - * fs/mnemofs/mnemofs_fsobj.c - * - * SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. The - * ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Alternatively, the contents of this file may be used under the terms of - * the BSD-3-Clause license: - * - * SPDX-License-Identifier: BSD-3-Clause - * - * Copyright (c) 2024 Saurav Pal - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the author nor the names of its contributors may - * be used to endorse or promote products derived from this software - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - ****************************************************************************/ - -/**************************************************************************** - * In mnemofs, all the FS object methods (ie. methods in this file), - * interface directly with the LRU. To these methods, only the methods - * exposed by the LRU are visible, nothing else. The LRU will give them the - * most updated data, which includes data from the flash, the updates from - * the journal and the LRU deltas as well. - * - * TODO: The above mentioned concept. - ****************************************************************************/ - -/**************************************************************************** - * Included Files - ****************************************************************************/ - -#include -#include -#include - -#include "mnemofs.h" -#include "fs_heap.h" - -/**************************************************************************** - * Pre-processor Definitions - ****************************************************************************/ - -/**************************************************************************** - * Private Types - ****************************************************************************/ - -/**************************************************************************** - * Private Function Prototypes - ****************************************************************************/ - -static mfs_t nobjs_in_path(FAR const char * relpath); -static const char *next_child(FAR const char *relpath); -static const char *last_child(FAR const char *relpath); -static FAR char *mfs_ser_dirent(FAR const struct mfs_dirent_s * const x, - FAR char * const out); - -static FAR const char *mfs_deser_dirent(FAR const char * const in, - FAR struct mfs_dirent_s * const x); - -static int search_ctz_by_name(FAR const struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth, FAR const char * const name, - const mfs_t namelen, FAR mfs_t *off, - FAR struct mfs_dirent_s **dirent); - -/**************************************************************************** - * Private Data - ****************************************************************************/ - -const struct mfs_path_s empty_fsobj = -{ - 0 -}; - -/**************************************************************************** - * Public Data - ****************************************************************************/ - -/**************************************************************************** - * Private Functions - ****************************************************************************/ - -/**************************************************************************** - * Name: nobjs_in_path - * - * Description: - * Count number of file system objects in path. This includes root in the - * count. - * - * Input Parameters: - * relpath - Relative Path. - * - * Returned Value: - * The number of file system objects in the path including the root. - * - ****************************************************************************/ - -static mfs_t nobjs_in_path(FAR const char *relpath) -{ - mfs_t count; - - /* If mount point is "/hi", then operations on "/hi/bye" and "/hi/hello/a" - * give respective relpaths as "bye" and "hello/a". Since mnemofs counts - * the root as 1 FS object, these respectively contain 2 and 3 FS objects - * in the path. The last FS object might be a file or a directory, but - * everything else is a directory. The number of FS objects in the path - * is the depth of the FS object the path refers to. - */ - - if (*relpath == 0) - { - return 1; - } - - count = 2; - - while (*relpath != 0) - { - if (*relpath == '/') - { - count++; - } - - relpath++; - } - - return count; -} - -/**************************************************************************** - * Name: next_child - * - * Description: - * Give the pointer to next child that appears in the path. - * - * Input Parameters: - * relpath - Relative Path. - * - * Returned Value: - * The pointer to the next child. This is not allocated, but points to the - * inside relpath itself. - * - ****************************************************************************/ - -static const char *next_child(FAR const char *path) -{ - mfs_t inc = 0; - FAR const char *tmp = path; - - MFS_EXTRA_LOG("NEXT_CHILD", "Requested string is \"%s\" (%p),", path, - path); - - while (*path != 0) - { - if (*path == '/') - { - MFS_EXTRA_LOG("NEXT_CHILD", "Length is %" PRIu32, inc); - DEBUGASSERT(inc == path - tmp); - return path + 1; - } - - path++; - inc++; - } - - MFS_EXTRA_LOG("NEXT_CHILD", "Length is %" PRIu32, inc); - DEBUGASSERT(inc == path - tmp); - MFS_EXTRA_LOG("NEXT_CHILD", "Last FS Object in string."); - return path; -} - -/**************************************************************************** - * Name: next_child - * - * Description: - * Give the pointer to next child that appears in the path. - * - * Input Parameters: - * relpath - Relative Path. - * - * Returned Value: - * The pointer to the next child. This is not allocated, but points to the - * inside relpath itself. - * - ****************************************************************************/ - -static const char *last_child(FAR const char *relpath) -{ - const mfs_t len = strlen(relpath); - mfs_t i; - - for (i = len - 1; i > 0; i--) - { - if (relpath[i - 1] == '/') - { - return relpath + i; - } - } - - return relpath; -} - -/**************************************************************************** - * Name: mfs_ser_dirent - * - * Description: - * Serialize a direntry. - * - * Input Parameters: - * x - Direntry. - * out - Buffer to populate. - * - * Returned Value: - * Pointer to after the end of serialized content in out. - * - ****************************************************************************/ - -static FAR char *mfs_ser_dirent(FAR const struct mfs_dirent_s * const x, - FAR char * const out) -{ - FAR char *o = out; - - o = mfs_ser_16(x->name_hash, o); - o = mfs_ser_16(x->mode, o); - o = mfs_ser_mfs(x->sz, o); - o = mfs_ser_timespec(&x->st_atim, o); - o = mfs_ser_timespec(&x->st_mtim, o); - o = mfs_ser_timespec(&x->st_ctim, o); - o = mfs_ser_ctz(&x->ctz, o); - o = mfs_ser_8(x->namelen, o); - o = mfs_ser_str(x->name, x->namelen, o); - - return o; -} - -/**************************************************************************** - * Name: mfs_deser_dirent - * - * Description: - * Deserialize a direntry. - * - * Input Parameters: - * in - Buffer. - * x - Direntry to populate. - * - * Returned Value: - * Pointer to after the end of deserialized content in in. - * - ****************************************************************************/ - -static FAR const char *mfs_deser_dirent(FAR const char * const in, - FAR struct mfs_dirent_s * const x) -{ - FAR const char *i = in; - - i = mfs_deser_16(i, &x->name_hash); - i = mfs_deser_16(i, &x->mode); - i = mfs_deser_mfs(i, &x->sz); - i = mfs_deser_timespec(i, &x->st_atim); - i = mfs_deser_timespec(i, &x->st_mtim); - i = mfs_deser_timespec(i, &x->st_ctim); - i = mfs_deser_ctz(i, &x->ctz); - i = mfs_deser_8(i, &x->namelen); - i = mfs_deser_str(i, x->name, x->namelen); - - return i; -} - -int pitr_traverse(FAR struct mfs_sb_s *sb, FAR struct mfs_path_s *path, - mfs_t depth, FAR mfs_t *cap) -{ - int ret = OK; - mfs_t i; - mfs_t pg; - struct mfs_pitr_s pitr; - struct mfs_ctz_s ctz; - FAR struct mfs_dirent_s *dirent = NULL; - - /* TODO: Double traversal can be made faster into a single traversal. */ - - ctz = path[depth - 1].ctz; - - if (ctz.idx_e == 0 && ctz.pg_e == 0) - { - /* Not a valid one. TODO: Does this happens? */ - - goto errout; - } - - for (i = ctz.idx_e; i > 0; i--) - { - mfs_ba_markusedpg(sb, pg); - - pg = mfs_ctz_travel(sb, i, pg, i - 1); - if (pg == 0) - { - break; - } - } - - memset(path + depth, 0, *cap - depth); - - if (depth == *cap) - { - *cap = (*cap * 3) / 2; /* Don't want to double it for memory. */ - - path = fs_heap_realloc(path, (*cap) * sizeof(struct mfs_path_s)); - if (predict_false(path == NULL)) - { - ret = -ENOMEM; - goto errout; - } - } - - mfs_pitr_init(sb, path, depth, &pitr, true); - - while (true) - { - mfs_pitr_readdirent(sb, path, &pitr, &dirent); - if (dirent == NULL) - { - break; - } - - if (S_ISDIR(dirent->mode)) - { - path[(depth + 1) - 1].ctz = dirent->ctz; - - ret = pitr_traverse(sb, path, depth + 1, cap); - if (predict_false(ret < 0)) - { - mfs_free_dirent(dirent); - goto errout; - } - } - - mfs_pitr_adv_bydirent(&pitr, dirent); - mfs_free_dirent(dirent); - } - -errout: - return ret; -} - -/**************************************************************************** - * Public Function Prototypes - ****************************************************************************/ - -FAR const char *mfs_path2childname(FAR const char *relpath) -{ - FAR const char *last = relpath + strlen(relpath) - 1; - - while (last >= relpath && *last != '/') - { - last--; - } - - return last + 1; -} - -mfs_t mfs_get_fsz(FAR struct mfs_sb_s * const sb, - FAR const struct mfs_path_s * const path, - const mfs_t depth) -{ - mfs_t sz; - - if (depth == 0) - { - /* Master node. */ - - return 0; - } - else if (depth == 1) - { - sz = MFS_MN(sb).root_sz; /* Updated size. */ - - /* Journal updated to the root creates a new master node entry. TODO - * this and moving of the journal. - */ - - finfo("File size got as %u for root.", sz); - return sz; - } - - return path[depth - 1].sz; -} - -bool mfs_obj_isempty(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s *path, - FAR struct mfs_pitr_s * const pitr) -{ - bool ret; - FAR struct mfs_dirent_s *dirent = NULL; - - mfs_pitr_readdirent(sb, path, pitr, &dirent); - ret = (dirent->sz == 0); - mfs_free_dirent(dirent); - - return ret; -} - -void mfs_free_dirent(FAR struct mfs_dirent_s *dirent) -{ - fs_heap_free(dirent); - - finfo("Dirent freed."); -} - -bool mfs_searchfopen(FAR const struct mfs_sb_s * const sb, - FAR const struct mfs_path_s * const path, - const mfs_t depth) -{ - FAR struct mfs_ofd_s *ofd = NULL; - - list_for_every_entry(&sb->of, ofd, struct mfs_ofd_s, list) - { - if (ofd->com->depth != depth) - { - continue; - } - - if (mfs_path_eq(&ofd->com->path[depth - 1], &path[depth - 1])) - { - return true; - } - } - - return false; -} - -int mfs_pitr_rmdirent(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth, - FAR struct mfs_pitr_s * const pitr, - FAR const struct mfs_dirent_s * const dirent) -{ - int ret = OK; - struct mfs_pitr_s p_pitr; - FAR struct mfs_dirent_s *p_dirent = NULL; - - mfs_pitr_init(sb, path, depth - 1, &p_pitr, true); - mfs_pitr_adv_tochild(&p_pitr, path); - mfs_pitr_readdirent(sb, path, &p_pitr, &p_dirent); - - ret = mfs_lru_del(sb, pitr->c_off, MFS_DIRENTSZ(dirent), path, depth); - - mfs_free_dirent(p_dirent); - mfs_pitr_free(&p_pitr); - - return ret; -} - -int mfs_pitr_rm(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth, bool rm_child) -{ - int ret = OK; - struct mfs_pitr_s pitr; - FAR struct mfs_dirent_s *dirent = NULL; - - /* TODO: MFS_LOG */ - - mfs_pitr_init(sb, path, depth, &pitr, true); - mfs_pitr_readdirent(sb, path, &pitr, &dirent); - - ret = mfs_pitr_rmdirent(sb, path, depth, &pitr, dirent); - if (predict_false(ret < 0)) - { - goto errout; - } - - if (rm_child) - { - ret = mfs_lru_del(sb, 0, path[depth - 1].sz, path, depth); - if (predict_false(ret < 0)) - { - goto errout; - } - } - -errout: - mfs_free_dirent(dirent); - mfs_pitr_free(&pitr); - - return ret; -} - -int mfs_pitr_init(FAR const struct mfs_sb_s * const sb, - FAR const struct mfs_path_s * const path, - const mfs_t depth, FAR struct mfs_pitr_s * const pitr, - bool child) -{ - /* Ensure updated CTZ location from the journal before this. */ - - int ret = OK; - const int diff = child ? 1 : 0; - const mfs_t p_depth = depth - diff; - - if (predict_false(depth < diff)) - { - ret = -EINVAL; - goto errout; - } - - pitr->c_off = 0; - pitr->depth = p_depth; - - if (predict_true(p_depth != 0)) - { - pitr->p = path[p_depth - 1]; - } - else - { - /* 0 or garbage value is fine for master node, not required. */ - - pitr->p.ctz.idx_e = 0; - pitr->p.ctz.pg_e = 0; - pitr->p.off = 0; - pitr->p.sz = 1; /* For 1 traversal to get root. */ - } - - finfo("Pitr initialized at depth %u, with CTZ (%u, %u) and size %u.", - p_depth, pitr->p.ctz.idx_e, pitr->p.ctz.pg_e, pitr->p.sz); - -errout: - return ret; -} - -void mfs_pitr_free(FAR const struct mfs_pitr_s * const pitr) -{ - MFS_EXTRA_LOG("MFS_PITR_FREE", "Parent iterator at %p freed.", pitr); - MFS_EXTRA_LOG_PITR(pitr); -} - -void mfs_pitr_adv_off(FAR struct mfs_pitr_s * const pitr, - const mfs_t off) -{ - pitr->c_off += off; - - finfo("Pitr at depth %u with CTZ (%u, %u) advanced by %u to %u offset.", - pitr->depth, pitr->p.ctz.idx_e, pitr->p.ctz.pg_e, off, pitr->c_off); -} - -void mfs_pitr_adv_bydirent(FAR struct mfs_pitr_s * const pitr, - FAR const struct mfs_dirent_s * const dirent) -{ - mfs_pitr_adv_off(pitr, MFS_DIRENTSZ(dirent)); - - finfo("Pitr at depth %u with CTZ (%u, %u) advanced by %u to %u offset.", - pitr->depth, pitr->p.ctz.idx_e, pitr->p.ctz.pg_e, - MFS_DIRENTSZ(dirent), pitr->c_off); -} - -void mfs_pitr_adv_tochild(FAR struct mfs_pitr_s * const pitr, - FAR const struct mfs_path_s * const path) -{ - /* (pitr->depth + 1) - 1 is the child's index. */ - - MFS_EXTRA_LOG("MFS_PITR_ADV_TOCHILD", "Advance pitr to child's offset."); - MFS_EXTRA_LOG_PITR(pitr); - MFS_EXTRA_LOG("MFS_PITR_ADV_TOCHILD", "New offset %" PRIu32, pitr->c_off); - pitr->c_off = path[pitr->depth].off; -} - -int mfs_pitr_readdirent(FAR const struct mfs_sb_s * const sb, - FAR struct mfs_path_s *path, - FAR struct mfs_pitr_s * const pitr, - FAR struct mfs_dirent_s **dirent) -{ - int ret = OK; - mfs_t sz; - const mfs_t len = sizeof(struct mfs_dirent_s) \ - + NAME_MAX + 1; - char rd[len]; - FAR struct mfs_dirent_s *d = NULL; - FAR struct mfs_dirent_s *tmp = NULL; - - if (dirent == NULL) - { - return ret; - } - - *dirent = NULL; - memset(rd, 0, len); - - d = fs_heap_zalloc(len); - if (predict_false(d == NULL)) - { - ret = -ENOMEM; - goto errout; - } - else if (pitr->c_off >= pitr->p.sz) - { - goto errout_with_d; - } - else if (pitr->depth == 0) - { - d->name[0] = 0; - d->namelen = 0; - d->ctz = MFS_MN(sb).root_ctz; - d->mode = MFS_MN(sb).root_mode; - d->name_hash = mfs_hash(d->name, d->namelen); - d->st_atim = MFS_MN(sb).root_st_atim; - d->st_ctim = MFS_MN(sb).root_st_ctim; - d->st_mtim = MFS_MN(sb).root_st_mtim; - d->sz = MFS_MN(sb).root_sz; - - pitr->c_off = 1; /* To prevent infinite loop. */ - } - else - { - ret = mfs_lru_rdfromoff(sb, pitr->c_off, path, pitr->depth, rd, len); - if (predict_false(ret < 0)) - { - goto errout_with_d; - } - - mfs_deser_dirent(rd, d); - } - - sz = MFS_DIRENTSZ(d); - tmp = fs_heap_realloc(d, sz); - if (predict_true(tmp != NULL)) - { - d = tmp; - } - - *dirent = d; - DEBUGASSERT(pitr->depth == 0 || strcmp(d->name, "")); - finfo("Read direntry at %u offset, %u depth for CTZ (%u, %u). " \ - "Direntry name: \"%.*s\" with name length %u and size %u.", - pitr->c_off, pitr->depth, pitr->p.ctz.idx_e, pitr->p.ctz.pg_e, - d->namelen, d->name, d->namelen, d->sz); - - return ret; - -errout_with_d: - fs_heap_free(d); - - if (ret < 0) - { - finfo("Direntry could not be allocated."); - } - else if (*dirent == NULL) - { - finfo("No direntry found."); - } - -errout: - return ret; -} - -int mfs_pitr_adv(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s *path, - FAR struct mfs_pitr_s * const pitr) -{ - int ret = OK; - FAR struct mfs_dirent_s *dirent; - - ret = mfs_pitr_readdirent(sb, path, pitr, &dirent); - if (predict_false(ret < 0)) - { - goto errout; - } - - mfs_pitr_adv_bydirent(pitr, dirent); - mfs_free_dirent(dirent); - - finfo("Pitr for CTZ (%u, %u) advanced to offset %u.", - pitr->p.ctz.idx_e, pitr->p.ctz.pg_e, pitr->c_off); - -errout: - return ret; -} - -static int search_ctz_by_name(FAR const struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth, FAR const char * const name, - const mfs_t namelen, FAR mfs_t *off, - FAR struct mfs_dirent_s **dirent) -{ - /* NOTE: depth is of the parent here. */ - - /* Applies LRU updates. */ - - int ret = OK; - bool found = false; - uint16_t name_hash; - struct mfs_pitr_s pitr; - FAR struct mfs_dirent_s *nd; - - *dirent = NULL; - - if (depth == 0) - { - DEBUGASSERT(namelen == 0); - - nd = fs_heap_zalloc(sizeof(struct mfs_dirent_s)); - if (predict_false(nd == NULL)) - { - ret = -ENOMEM; - goto errout; - } - - *off = 0; - nd->namelen = namelen; - nd->ctz = MFS_MN(sb).root_ctz; - nd->mode = MFS_MN(sb).root_mode; - nd->name_hash = mfs_hash(nd->name, nd->namelen); - nd->st_atim = MFS_MN(sb).root_st_atim; - nd->st_ctim = MFS_MN(sb).root_st_ctim; - nd->st_mtim = MFS_MN(sb).root_st_mtim; - nd->sz = MFS_MN(sb).root_sz; - - *dirent = nd; - goto errout; - } - - name_hash = mfs_hash(name, namelen); - - ret = mfs_lru_getupdatedinfo(sb, path, depth); - if (predict_false(ret < 0)) - { - goto errout; - } - - ret = mfs_pitr_init(sb, path, depth, &pitr, false); - if (predict_false(ret < 0)) - { - goto errout; - } - - for (; ; ) - { - /* Readdirent takes care of LRU updates. */ - - ret = mfs_pitr_readdirent(sb, path, &pitr, &nd); - if (predict_false(ret < 0 || nd == NULL)) - { - ret = -ENONET; - goto errout; - } - - if (nd->name_hash == name_hash && - !strncmp(nd->name, name, MIN(nd->namelen, namelen))) - { - found = true; - path[depth].sz = nd->sz; - path[depth].ctz = nd->ctz; - path[depth].off = pitr.c_off; - *off = pitr.c_off; - break; - } - - mfs_pitr_adv_bydirent(&pitr, nd); - mfs_free_dirent(nd); - } - -errout: - if (found) - { - finfo("Searched \"%.*s\" direntry inside CTZ (%u, %u) at depth %u," - " size %u.", namelen, name, path[depth - 1].ctz.idx_e, - path[depth - 1].ctz.pg_e, depth, path[depth - 1].sz); - *dirent = nd; - } - else - { - ret = -ENOENT; - finfo("Can not find requested direntry in parent. Ret: %d.", ret); - } - - return ret; -} - -int mfs_get_patharr(FAR const struct mfs_sb_s * const sb, - FAR const char *relpath, FAR struct mfs_path_s **path, - FAR mfs_t *depth) -{ - int ret = OK; - int ret_flags = 0; - mfs_t i; - mfs_t sz; - mfs_t off; - mfs_t n_objs; - mfs_t name_len; - FAR const char *cur = NULL; - FAR const char *next = NULL; - struct mfs_ctz_s ctz; - FAR struct mfs_path_s *np = NULL; - FAR struct mfs_dirent_s *dirent = NULL; - - MFS_LOG("MFS_GET_PATHARR", "Entry."); - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Relpath is \"%s\".", relpath); - - *path = NULL; - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Path is %p.", path); - - n_objs = nobjs_in_path(relpath); - MFS_EXTRA_LOG("MFS_GET_PATHARR", "There are %" PRIu32 " objects in path.", - n_objs); - - np = fs_heap_zalloc(n_objs * sizeof(struct mfs_path_s)); - if (predict_false(np == NULL)) - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Could not allocate Path array."); - ret = -ENOMEM; - goto errout; - } - else - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Path array is allocated at %p.", np); - } - - DEBUGASSERT(*cur != '/'); /* Relpath should not start with a '/' */ - - ctz = MFS_MN(sb).root_ctz; - sz = MFS_MN(sb).root_sz; - np[0].sz = sz; - np[0].ctz = ctz; - np[0].off = 0; - cur = relpath; - next = next_child(cur); - - DEBUGASSERT(*next != 0 || n_objs == 2); - - name_len = *next == 0 ? next - cur : next - cur - 1; - - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Root Master Node."); - MFS_EXTRA_LOG("MFS_GET_PATHARR", "\tCTZ is (%" PRIu32 ", %" PRIu32 ")", - MFS_MN(sb).root_ctz.idx_e, MFS_MN(sb).root_ctz.pg_e); - MFS_EXTRA_LOG("MFS_GET_PATHARR", "\tSize is %" PRIu32, sz); - - if (predict_false(n_objs == 1)) - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "There is only one object (root)."); - ret_flags |= MFS_ISDIR | MFS_EXIST; - - /* This will not go into the loop. */ - } - else if (predict_false(n_objs == 2)) - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "There are only 2 objects."); - ret_flags |= MFS_P_EXIST | MFS_P_ISDIR; - } - - /* MFS_MN(sb).root_* is always up to date, no need for journal update. */ - - for (i = 1; i < n_objs; i++) - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Looking at depth %" PRIu32, i); - - /* np[i] is the fs object at depth i + 1. */ - - /* Need to update journal for every level in the path as, for eg., the - * child can be deleted, etc. Same goes for LRU, which is taken care of - * by search_ctz_by_name function. - */ - - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Current String is \"%.*s\" (%p)", - name_len, cur, cur); - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Name length is %" PRIu32, name_len); - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Next String is \"%s\"", next); - - ret = search_ctz_by_name(sb, np, i, cur, name_len, &off, &dirent); - if (predict_false(ret < 0)) - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Could not find CTZ."); - goto errout_with_ret_flags; - } - else - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Found CTZ."); - MFS_EXTRA_LOG("MFS_GET_PATHARR", "New Offset is %" PRIu32, off); - MFS_EXTRA_LOG_DIRENT(dirent); - } - - if (i < n_objs - 2 && !S_ISDIR(dirent->mode)) - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Depth %" PRIu32 " contains file", - i); - ret_flags |= MFS_FINPATH; - goto errout_with_ret_flags; - } - else if (i == n_objs - 2) - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Parent exists."); - ret_flags |= MFS_P_EXIST; - if (S_ISDIR(dirent->mode)) - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Parent is a directory."); - ret_flags |= MFS_P_ISDIR; - } - else - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Parent is a file."); - ret_flags |= MFS_FINPATH; - goto errout_with_ret_flags; - } - } - else if (i == n_objs - 1) - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Child exists."); - ret_flags |= MFS_EXIST; - if (S_ISDIR(dirent->mode)) - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Child is a directory."); - ret_flags |= MFS_ISDIR; - } - else - { - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Child is a file."); - ret_flags |= MFS_ISFILE; - } - } - else - { - /* OK */ - } - - np[i].ctz = dirent->ctz; - np[i].off = off; - np[i].sz = dirent->sz; - - ctz = dirent->ctz; - - mfs_free_dirent(dirent); - - cur = next; - next = next_child(cur); - name_len = *next == 0 ? next - cur : next - cur - 1; - - DEBUGASSERT(cur != next); - } - - ret = ret_flags; - *depth = n_objs; - *path = np; - - MFS_EXTRA_LOG("MFS_GET_PATHARR", "Child is a file."); - - MFS_LOG("MKDIR", "Exit | Flags: %u, Depth %u.", ret, n_objs); - return ret; - -errout_with_ret_flags: - ret = ret_flags; - *depth = n_objs; - *path = np; - - /* mfs_free_patharr(np) : All callers will free np (ie. path) when done - * with it. - */ - -errout: - MFS_LOG("MKDIR", "Exit | Flags: %u, Depth %u.", ret, n_objs); - return ret; -} - -void mfs_free_patharr(FAR struct mfs_path_s *path) -{ - MFS_EXTRA_LOG("MFS_FREE_PATHARR", "Path array at %p freed.", path); - fs_heap_free(path); -} - -void mfs_pitr_reset(FAR struct mfs_pitr_s * const pitr) -{ - pitr->c_off = 0; - - finfo("Pitr for CTZ (%u, %u) reset.", - pitr->p.ctz.idx_e, pitr->p.ctz.pg_e); -} - -int mfs_pitr_appenddirent(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth, - FAR struct mfs_pitr_s * const pitr, - FAR const struct mfs_dirent_s * const dirent) -{ - /* Depth is depth of the child to be appended. */ - - int ret = OK; - const mfs_t len = MFS_DIRENTSZ(dirent); - char wd[len]; - - if (pitr->depth == 0) - { - /* Root is the only child of the master node. */ - - ret = -EINVAL; - goto errout; - } - - /* TODO: If the parent directory is newly formed (ie. size is 0), then - * allocate space for it. This can be done better. Just allocate page when - * its created and added first to LRU, and then add a check to ensure it - * doesn't get re-allocated when written. A field like "new" would be - * helpful in the LRU node for this. - */ - - memset(wd, 0, len); - - mfs_ser_dirent(dirent, wd); - ret = mfs_lru_wr(sb, pitr->p.sz, len, path, pitr->depth, wd); - if (predict_false(ret < 0)) - { - goto errout; - } - -errout: - return ret; -} - -int mfs_pitr_appendnew(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, const mfs_t depth, - FAR struct mfs_pitr_s * const pitr, - FAR const char * const relpath, const mode_t mode) -{ - /* Depth is depth of the child to be appended. */ - - int ret = OK; - FAR const char *cur = last_child(relpath); - FAR const char *next = next_child(cur); - const mfs_t len = *next == 0 ? next - cur : next - cur - 1; - struct timespec ts; - FAR struct mfs_dirent_s *d = NULL; - - DEBUGASSERT(depth > 0); - - d = fs_heap_zalloc(sizeof(struct mfs_dirent_s) + len); - if (predict_false(d == NULL)) - { - ret = -ENOMEM; - goto errout; - } - - clock_gettime(CLOCK_REALTIME, &ts); - - d->ctz = empty_fsobj.ctz; - d->mode = mode; - d->st_atim = ts; - d->st_mtim = ts; - d->st_ctim = ts; - d->namelen = len; - strncpy(d->name, cur, d->namelen); - d->name_hash = mfs_hash(d->name, d->namelen); - d->sz = 0; - - /* Add the new direntry in this path. */ - - path[depth - 1].ctz = d->ctz; - path[depth - 1].off = pitr->p.sz; - path[depth - 1].sz = d->sz; - - ret = mfs_pitr_appenddirent(sb, path, depth, pitr, d); - if (predict_false(ret < 0)) - { - goto errout_with_d; - } - - finfo("Direntry appended to Pitr with %u depth, and CTZ (%u, %u). " \ - "Direntry name: \"%.*s\" with name length %u at offset %u.", - pitr->depth, pitr->p.ctz.idx_e, pitr->p.ctz.pg_e, d->namelen, - d->name, d->namelen, path[depth - 1].off); - -errout_with_d: - mfs_free_dirent(d); - -errout: - return ret; -} - -/* Only for initialization of the block allocator. */ - -int mfs_pitr_traversefs(FAR struct mfs_sb_s * sb, const struct mfs_ctz_s ctz, - int type) -{ - /* type takes in MFS_ISFILE & MFS_ISDIR. */ - - int ret = OK; - mfs_t capacity; - FAR struct mfs_path_s *path = NULL; - - capacity = MFS_TRAVERSE_INITSZ; - path = fs_heap_zalloc(capacity * sizeof(struct mfs_path_s)); - if (predict_false(path == NULL)) - { - ret = -ENOMEM; - goto errout; - } - - path[0].off = 0; - path[0].ctz = MFS_MN(sb).root_ctz; - path[0].sz = MFS_MN(sb).root_sz; - - ret = pitr_traverse(sb, path, 1, &capacity); - - mfs_free_patharr(path); - -errout: - return ret; -} diff --git a/fs/mnemofs/mnemofs_journal.c b/fs/mnemofs/mnemofs_journal.c deleted file mode 100644 index 8a3159259fa..00000000000 --- a/fs/mnemofs/mnemofs_journal.c +++ /dev/null @@ -1,887 +0,0 @@ -/**************************************************************************** - * fs/mnemofs/mnemofs_journal.c - * - * SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. The - * ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Alternatively, the contents of this file may be used under the terms of - * the BSD-3-Clause license: - * - * SPDX-License-Identifier: BSD-3-Clause - * - * Copyright (c) 2024 Saurav Pal - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the author nor the names of its contributors may - * be used to endorse or promote products derived from this software - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - ****************************************************************************/ - -/**************************************************************************** - * In mnemofs, the journal stores the path, depth and the new location of the - * CTZ file called logs, and also the location of the master block. The first - * n blocks of the journal store the logs, while the last two blocks contain - * master nodes, and the blocks are called as master blocks. The two master - * blocks are identical copies for backup. - * - * Due to LRU, and the structure of mnemofs, the first n blocks of the - * journal get filled up much faster than the master blocks, and move more. - * There will be certain point where the entire journal (the n+2 blocks) - * move, but mostly, its the first n blocks that move. - * - * The first block starts with an 8 byte magic sequence, a 2 bytes long - * number denoting number of blocks in the journal, and then follows up - * with an array containing the block numbers of all blocks in the journal - * including the first block. Then the logs start. - * - * The logs take up size in multiples of pages. There might be unitilzed - * space at the end of a log. - * - * All logs are followed by a byte-long hash of the log. - ****************************************************************************/ - -/**************************************************************************** - * Included Files - ****************************************************************************/ - -#include -#include -#include -#include - -#include "mnemofs.h" -#include "fs_heap.h" - -/**************************************************************************** - * Pre-processor Definitions - ****************************************************************************/ - -#define MFS_JRNL_SUFFIXSZ (8 + 2) /* 8 byte magic sequence + no. of blks */ -#define MFS_LOGSZ(depth) (sizeof(mfs_t) * 2 + sizeof(struct mfs_ctz_s) + \ - sizeof(struct mfs_path_s) * depth + \ - sizeof(struct timespec) * 3 + sizeof(uint16_t)) - -/**************************************************************************** - * Private Types - ****************************************************************************/ - -/* NOTE: Even if higher level functions use path as struct mfs_path_s, - * journal only uses struct mfs_ctz_s to avoid problems during write and - * read of logs. The offsets in struct mfs_path_s will be applied by the - * search methods of higher functions. - */ - -/**************************************************************************** - * Private Function Prototypes - ****************************************************************************/ - -FAR static const char *deser_log(FAR const char * const in, - FAR struct mfs_jrnl_log_s * const x); -FAR static char *ser_log(FAR const struct mfs_jrnl_log_s * const x, - FAR char * const out); - -/**************************************************************************** - * Private Data - ****************************************************************************/ - -/**************************************************************************** - * Public Data - ****************************************************************************/ - -/**************************************************************************** - * Private Functions - ****************************************************************************/ - -/**************************************************************************** - * Name: mfs_jrnl_rdlog - * - * Description: - * Read a log to the journal from given location, and update location to - * point to next log location. - * - * Input Parameters: - * sb - Superblock instance of the device. - * blkidx - Journal Block Index of the current block. - * pg_in_blk - Page offset in the block. - * log - To populate with the log. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * This function does NOT care about start of the journal, or even, if - * the initial requested area is inside the journal. It will malfunction - * if not used properly. Usually this is used in an iterative manner, and - * hence the first time blkidx and pg_in_blk are initialized, they should - * be derived from the values in MFS_JRNL(sb) respectively. - * - * This updates the blkidx and pg_in_blk to point to the next log, and - * returns an -ENOSPC when end of journal is reached in traversal. - * - * Free the log after use. - * - ****************************************************************************/ - -int mfs_jrnl_rdlog(FAR const struct mfs_sb_s *const sb, - FAR mfs_t *blkidx, FAR mfs_t *pg_in_blk, - FAR struct mfs_jrnl_log_s *log) -{ - int ret = OK; - char tmp[4]; - mfs_t log_sz; - mfs_t jrnl_pg; - mfs_t jrnl_blk; - FAR char *buf = NULL; - - DEBUGASSERT(*pg_in_blk % MFS_PGSZ(sb) == 0); - - jrnl_blk = mfs_jrnl_blkidx2blk(sb, *blkidx); - jrnl_pg = MFS_BLK2PG(sb, jrnl_blk) + *pg_in_blk; - - /* First 4 bytes contain the size of the entire log. */ - - ret = mfs_read_page(sb, tmp, 4, jrnl_pg, 0); - if (predict_false(ret < 0)) - { - goto errout; - } - - mfs_deser_mfs(tmp, &log_sz); - if (log_sz == 0) - { - ret = -ENOSPC; - goto errout; - } - - buf = fs_heap_zalloc(log_sz); - if (predict_false(buf == NULL)) - { - ret = -ENOMEM; - goto errout; - } - - ret = mfs_read_page(sb, buf, log_sz, jrnl_pg, 4); - if (predict_false(ret < 0)) - { - goto errout_with_buf; - } - - ret = OK; - - if (predict_false(deser_log(buf, log) == NULL)) - { - ret = -ENOMEM; - goto errout_with_buf; - } - - (*pg_in_blk)++; - - if (*pg_in_blk >= MFS_PGINBLK(sb)) - { - *pg_in_blk = 0; - (*blkidx)++; - } - -errout_with_buf: - fs_heap_free(buf); - -errout: - return ret; -} - -/**************************************************************************** - * Name: ser_log - * - * Description: - * Serialize a log. - * - * Input Parameters: - * n - Log to serialize - * out - Output array where to serialize. - * - * Returned Value: - * Pointer to byte after the end of serialized value. - * - * Assumptions/Limitations: - * This assumes the out buffer has enough space to hold the inline path. - * - * This doesn't require the hash to be pre-calculated. - * - ****************************************************************************/ - -FAR static char *ser_log(FAR const struct mfs_jrnl_log_s * const x, - FAR char * const out) -{ - FAR char *o = out; - mfs_t i; - - o = mfs_ser_mfs(x->depth, o); - o = mfs_ser_mfs(x->sz_new, o); - o = mfs_ser_ctz(&x->loc_new, o); - o = mfs_ser_timespec(&x->st_mtim_new, o); - o = mfs_ser_timespec(&x->st_atim_new, o); - o = mfs_ser_timespec(&x->st_ctim_new, o); - - for (i = 0; i < x->depth; i++) - { - o = mfs_ser_path(&x->path[i], o); - } - - o = mfs_ser_16(mfs_hash(out, o - out), o); - return o; -} - -/**************************************************************************** - * Name: deser_log - * - * Description: - * Deserialize a log. - * - * Input Parameters: - * in - Input array from where to deserialize. - * n - Log to deserialize - * - * Returned Value: - * NULL - Error. - * Pointer to byte after the end of serialized value. - * - * Assumptions/Limitations: - * This allocates space for the path, and the log should freed after use. - * - ****************************************************************************/ - -FAR static const char *deser_log(FAR const char * const in, - FAR struct mfs_jrnl_log_s * const x) -{ - FAR const char *i = in; - mfs_t k; - - i = mfs_deser_mfs(i, &x->depth); - i = mfs_deser_mfs(i, &x->sz_new); - i = mfs_deser_ctz(i, &x->loc_new); - i = mfs_deser_timespec(i, &x->st_mtim_new); - i = mfs_deser_timespec(i, &x->st_atim_new); - i = mfs_deser_timespec(i, &x->st_ctim_new); - - /* Allocates path. Deallocate using mfs_jrnl_log_free. */ - - x->path = fs_heap_zalloc(sizeof(struct mfs_jrnl_log_s) * x->depth); - if (predict_false(x->path == NULL)) - { - return NULL; - } - - for (k = 0; k < x->depth; k++) - { - i = mfs_deser_path(i, &x->path[k]); - } - - i = mfs_deser_16(i, &x->hash); - return i; -} - -/**************************************************************************** - * Name: mfs_jrnl_log_free - * - * Description: - * Free the log. - * - * Input Parameters: - * log - Log - * - * Assumptions/Limitations: - * This allocates space for the path, and the log should freed after use. - * - ****************************************************************************/ - -void mfs_jrnl_log_free(FAR const struct mfs_jrnl_log_s * const log) -{ - fs_heap_free(log->path); -} - -/**************************************************************************** - * Public Functions - ****************************************************************************/ - -int mfs_jrnl_init(FAR struct mfs_sb_s * const sb, mfs_t blk) -{ - char buftmp[2]; - int ret = OK; - mfs_t sz; - mfs_t blkidx; - mfs_t pg_in_blk; - struct mfs_jrnl_log_s log; - - /* Magic sequence is already used to find the block, so not required. */ - - mfs_read_page(sb, buftmp, 2, MFS_BLK2PG(sb, blk), 8); - mfs_deser_16(buftmp, &MFS_JRNL(sb).n_blks); - - if (MFS_JRNL(sb).n_blks == 0) - { - ret = -EINVAL; - goto errout; - } - - sz = MFS_JRNL_SUFFIXSZ + ((CONFIG_MNEMOFS_JOURNAL_NBLKS + 2) * 4); - - MFS_JRNL(sb).jrnl_start = blk; - MFS_JRNL(sb).log_cpg = (sz + (MFS_PGSZ(sb) - 1)) / MFS_PGSZ(sb); - MFS_JRNL(sb).log_spg = MFS_JRNL(sb).log_cpg; - MFS_JRNL(sb).log_cblkidx = 0; - MFS_JRNL(sb).log_sblkidx = MFS_JRNL(sb).log_cblkidx; - MFS_JRNL(sb).jrnlarr_pg = MFS_BLK2PG(sb, blk); /* Assuming pgsz > 10 */ - MFS_JRNL(sb).jrnlarr_pgoff = MFS_JRNL_SUFFIXSZ; - - /* Number of logs */ - - MFS_JRNL(sb).n_logs = 0; - blkidx = MFS_JRNL(sb).log_sblkidx; - pg_in_blk = MFS_JRNL(sb).log_spg % MFS_PGINBLK(sb); - - while (true) - { - ret = mfs_jrnl_rdlog(sb, &blkidx, &pg_in_blk, &log); - if (predict_false(ret < 0 && ret != -ENOSPC)) - { - goto errout; - } - else if (ret == -ENOSPC) - { - ret = OK; - break; - } - - /* Assumes checking the depth is enough to check if it's empty, as - * theoretically there are no blocks with depth 0, as root has a - * depth of 1. - */ - - if (log.depth == 0) - { - DEBUGASSERT(log.path == NULL); - break; - } - - MFS_JRNL(sb).n_logs++; - mfs_jrnl_log_free(&log); - } - - /* Master node */ - - MFS_JRNL(sb).mblk1 = mfs_jrnl_blkidx2blk(sb, MFS_JRNL(sb).n_blks); - MFS_JRNL(sb).mblk2 = mfs_jrnl_blkidx2blk(sb, MFS_JRNL(sb).n_blks + 1); - - /* TODO: Read all pages in master blocks to find the last master node - * update. - */ - -errout: - return ret; -} - -int mfs_jrnl_fmt(FAR struct mfs_sb_s * const sb, FAR mfs_t *blk1, - FAR mfs_t *blk2, FAR mfs_t *jrnl_blk) -{ - int i; - int ret = OK; - mfs_t sz; - mfs_t pg; - mfs_t blk; - mfs_t alloc_blk; - FAR char *tmp; - FAR char *buf = NULL; - - /* TODO: Replace jrnl_blk with MFS_JRNL(sb).jrnl_start if possible. */ - - /* Write magic sequence, size of jrnlarr, and then the jrnlarr. */ - - sz = MFS_JRNL_SUFFIXSZ + ((CONFIG_MNEMOFS_JOURNAL_NBLKS + 2) * 4); - - buf = fs_heap_zalloc(sz); - if (predict_false(buf == NULL)) - { - ret = -ENOMEM; - goto errout; - } - - if (*blk1 == 0 && *blk2 == 0) - { - *blk1 = mfs_ba_getblk(sb); - if (predict_false(blk1 == 0)) - { - ret = -ENOSPC; - goto errout_with_buf; - } - - finfo("Allocated Master Block 1: %d.", *blk1); - - *blk2 = mfs_ba_getblk(sb); - if (predict_false(blk2 == 0)) - { - ret = -ENOSPC; - goto errout_with_buf; - } - - finfo("Allocated Master Block 1: %d.", *blk2); - finfo("New locations for Master Blocks %d & %d.", *blk1, *blk2); - } - - tmp = buf; - - tmp = mfs_ser_str(MFS_JRNL_MAGIC, 8, tmp); - tmp = mfs_ser_16(CONFIG_MNEMOFS_JOURNAL_NBLKS, tmp); - for (i = 0; i < CONFIG_MNEMOFS_JOURNAL_NBLKS; i++) - { - alloc_blk = mfs_ba_getblk(sb); - tmp = mfs_ser_mfs(alloc_blk, tmp); - - if (predict_false(i == 0)) - { - blk = alloc_blk; - *jrnl_blk = alloc_blk; - MFS_JRNL(sb).jrnl_start = alloc_blk; - } - - finfo("Allocated Journal Block %d at Block %d.", i, alloc_blk); - } - - tmp = mfs_ser_mfs(*blk1, tmp); - tmp = mfs_ser_mfs(*blk2, tmp); - - finfo("All Journal Blocks allocated."); - - pg = MFS_BLK2PG(sb, blk); - ret = mfs_write_page(sb, buf, sz, pg, 0); /* Assuming array fits in a - * single page. - */ - if (predict_false(ret < 0)) - { - goto errout_with_buf; - } - - ret = OK; /* We reach here, we OK. */ - - finfo("Written magic sequence, size and journal array into the journal."); - - MFS_JRNL(sb).n_logs = 0; - MFS_JRNL(sb).n_blks = CONFIG_MNEMOFS_JOURNAL_NBLKS; - MFS_JRNL(sb).log_cpg = pg + 1; /* Assumes 1 page for jrnl_arr. */ - MFS_JRNL(sb).log_cblkidx = 0; - MFS_JRNL(sb).log_spg = MFS_JRNL(sb).log_cpg; - MFS_JRNL(sb).log_sblkidx = MFS_JRNL(sb).log_cblkidx; - MFS_JRNL(sb).jrnlarr_pg = MFS_BLK2PG(sb, blk); - MFS_JRNL(sb).jrnlarr_pgoff = MFS_JRNL_SUFFIXSZ; - MFS_JRNL(sb).mblk1 = *blk1; - MFS_JRNL(sb).mblk2 = *blk2; - -errout_with_buf: - fs_heap_free(buf); - -errout: - return ret; -} - -void mfs_jrnl_free(FAR struct mfs_sb_s * const sb) -{ - if (!mfs_jrnl_isempty(sb) && - MFS_JRNL(sb).log_cblkidx >= MFS_JRNL_LIM(sb)) - { - mfs_jrnl_flush(sb); - } - - finfo("Journal Freed."); -} - -mfs_t mfs_jrnl_blkidx2blk(FAR const struct mfs_sb_s * const sb, - const mfs_t blk_idx) -{ - int ret = OK; - mfs_t pg; - mfs_t idx; - mfs_t blk; - mfs_t pgoff; - char buf[4]; - - pg = MFS_JRNL(sb).jrnlarr_pg; - pgoff = MFS_JRNL(sb).jrnlarr_pgoff; - blk = MFS_PG2BLK(sb, pg); - pgoff += blk_idx * 4; - - if (pgoff > MFS_PGSZ(sb)) - { - pg += pgoff / MFS_PGSZ(sb); - pgoff %= MFS_PGSZ(sb); - } - - /* No pg overflow. The blocks have to be big enough. */ - - DEBUGASSERT(pg < (MFS_BLK2PG(sb, blk) + MFS_PGINBLK(sb))); - - ret = mfs_read_page(sb, buf, 4, pg, pgoff); - if (predict_false(ret < 0)) - { - return 0; - } - - mfs_deser_mfs(buf, &idx); - - /* FUTURE TODO: Make it such that the entire jrnlarr doesn't need to be in - * a single block. - */ - - return idx; -} - -int mfs_jrnl_updatedinfo(FAR const struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth) -{ - int ret = OK; - mfs_t blkidx; - mfs_t counter = 0; - mfs_t pg_in_block; - struct mfs_jrnl_log_s tmplog; - - /* TODO: Allow optional filling of updated timestamps, etc. */ - - DEBUGASSERT(depth > 0); - - blkidx = MFS_JRNL(sb).log_sblkidx; - pg_in_block = MFS_JRNL(sb).log_spg % MFS_PGINBLK(sb); - - while (blkidx < MFS_JRNL(sb).n_blks && counter < MFS_JRNL(sb).n_logs) - { - ret = mfs_jrnl_rdlog(sb, &blkidx, &pg_in_block, &tmplog); - if (predict_false(ret < 0 && ret != -ENOSPC)) - { - goto errout; - } - else if (ret == -ENOSPC) - { - break; - } - - DEBUGASSERT(tmplog.depth > 0); - - if (tmplog.depth > depth) - { - /* Not suitable. */ - } - else - { - DEBUGASSERT(tmplog.depth > 0); - - if (mfs_path_eq(&tmplog.path[tmplog.depth - 1], - &path[tmplog.depth - 1])) - { - path[tmplog.depth - 1].ctz = tmplog.loc_new; - path[tmplog.depth - 1].sz = tmplog.sz_new; - } - } - - mfs_jrnl_log_free(&tmplog); - counter++; - } - -errout: - return ret; -} - -int mfs_jrnl_wrlog(FAR struct mfs_sb_s * const sb, - FAR const struct mfs_node_s *node, - const struct mfs_ctz_s loc_new, const mfs_t sz_new) -{ - int ret = OK; - mfs_t jrnl_pg; - FAR char *buf = NULL; - FAR char *tmp = NULL; - const mfs_t log_sz = sizeof(mfs_t) + MFS_LOGSZ(node->depth); - struct mfs_jrnl_log_s log; - - buf = fs_heap_zalloc(log_sz); /* For size before log. */ - if (predict_false(buf == NULL)) - { - ret = -ENOMEM; - goto errout; - } - - /* Serialize */ - - log.depth = node->depth; - log.sz_new = sz_new; - log.loc_new = loc_new; - log.st_mtim_new = node->st_mtim; - log.st_atim_new = node->st_atim; - log.st_ctim_new = node->st_ctim; - log.path = node->path; /* Fine as temporarily usage. */ - - tmp = buf; - tmp = mfs_ser_mfs(log_sz - sizeof(mfs_t), tmp); /* First 4 bytes have sz */ - tmp = ser_log(&log, tmp); - - /* Store */ - - jrnl_pg = MFS_JRNL(sb).log_cpg; - - /* TODO: It assumes it takes only one page per log. */ - - ret = mfs_write_page(sb, buf, log_sz, jrnl_pg, 0); - if (predict_false(ret < 0)) - { - goto errout_with_buf; - } - - ret = OK; - - jrnl_pg++; - - if (jrnl_pg % MFS_PGINBLK(sb) == 0) - { - MFS_JRNL(sb).log_cblkidx++; - } - - MFS_JRNL(sb).log_cpg = jrnl_pg; - MFS_JRNL(sb).n_logs++; - -errout_with_buf: - fs_heap_free(buf); - -errout: - return ret; -} - -int mfs_jrnl_flush(FAR struct mfs_sb_s * const sb) -{ - /* When a file or a directory is deleted. - * - * It will be modified to an entry in the LRU which details the deletion - * of all bytes from the child... as in, offset 0, deleted bytes is the - * size of the file. - * - * The new "location" can be used as (0, 0) to signify a deletion, even in - * its journal log. - * - * Also ensure if the size gets updated to 0. - * - * Then the flush operation problem will be solved for removal of files or - * directories. - * - * Move operation will not empty the child, but only the parent from the - * old parent. - */ - - /* Time complexity is going to be horrendous. Hint: O(n^2). HOWEVER, as - * littlefs points out....if n is constant, it's essentially a O(k), or - * O(1) :D - */ - - /* TODO: Need to consider how the LRU and Journal interact with each other - * for newly created fs object's entries. - */ - - /* We're using updatectz to update the LRU inside the journal. Think - * about how that might affect the iteration attempts. - */ - - int ret = OK; - mfs_t blkidx = MFS_JRNL(sb).log_sblkidx; - mfs_t log_itr = 0; - mfs_t pg_in_blk = MFS_JRNL(sb).log_spg \ - % MFS_PGINBLK(sb); - mfs_t tmp_blkidx; - mfs_t tmp_pg_in_blk; - mfs_t mn_blk1; - mfs_t mn_blk2; - mfs_t i; - mfs_t jrnl_blk; - mfs_t blk; - struct mfs_jrnl_log_s log; - struct mfs_jrnl_log_s tmp_log; - FAR struct mfs_path_s *path = NULL; - struct mfs_jrnl_state_s j_state; - struct mfs_mn_s mn_state; - - while (log_itr < MFS_JRNL(sb).n_logs) - { - ret = mfs_jrnl_rdlog(sb, &blkidx, &pg_in_blk, &log); - if (predict_false(ret < 0)) - { - DEBUGASSERT(ret != -ENOSPC); /* While condition is sufficient. */ - goto errout; - } - - if (log.loc_new.pg_e == 0 && log.loc_new.idx_e == 0) - { - /* Entry is deleted, do not bother with it. */ - - break; - } - - tmp_blkidx = blkidx; - tmp_pg_in_blk = pg_in_blk; - - path = fs_heap_zalloc(log.depth * sizeof(struct mfs_path_s)); - if (predict_false(path == NULL)) - { - goto errout; - } - - memcpy(path, log.path, log.depth * sizeof(struct mfs_path_s)); - path[log.depth - 1].ctz = log.loc_new; - - for (; ; ) - { - ret = mfs_jrnl_rdlog(sb, &tmp_blkidx, &tmp_pg_in_blk, &tmp_log); - if (ret == -ENOSPC) - { - break; - } - else if (predict_false(ret < 0)) - { - mfs_jrnl_log_free(&log); - mfs_free_patharr(path); - goto errout; - } - - if (tmp_log.depth > log.depth) - { - mfs_jrnl_log_free(&tmp_log); - continue; - } - - if (!mfs_path_eq(&path[tmp_log.depth - 1], - &tmp_log.path[tmp_log.depth - 1])) - { - mfs_jrnl_log_free(&tmp_log); - continue; - } - - path[tmp_log.depth - 1] = tmp_log.path[tmp_log.depth - 1]; - - if (tmp_log.loc_new.pg_e == 0 && tmp_log.loc_new.idx_e == 0) - { - /* Entry is deleted, do not bother with it. */ - - break; - } - } - - if (log.depth == 1) - { - MFS_MN(sb).root_ctz = path[log.depth - 1].ctz; - MFS_MN(sb).root_sz = path[log.depth - 1].sz; - - /* TODO: Other parameters. */ - } - else - { - ret = mfs_lru_updatectz(sb, path, log.depth, - path[log.depth - 1].ctz, - path[log.depth - 1].sz); - if (predict_false(ret < 0)) - { - mfs_free_patharr(path); - mfs_jrnl_log_free(&log); - goto errout; - } - } - - mfs_free_patharr(path); - mfs_jrnl_log_free(&log); - } - - if (MFS_MN(sb).mblk_idx == MFS_PGINBLK(sb)) - { - mn_blk1 = 0; - mn_blk2 = 0; - } - else - { - /* FUTURE TODO: Save the two block numbers in master node structure to - * be faster. - */ - - mn_blk1 = mfs_jrnl_blkidx2blk(sb, MFS_JRNL(sb).n_blks); - mn_blk2 = mfs_jrnl_blkidx2blk(sb, MFS_JRNL(sb).n_blks + 1); - } - - /* Reallocate journal. */ - - j_state = MFS_JRNL(sb); - mn_state = MFS_MN(sb); - - ret = mfs_jrnl_fmt(sb, &mn_blk1, &mn_blk2, &jrnl_blk); - if (predict_false(ret < 0)) - { - MFS_JRNL(sb) = j_state; - goto errout; - } - - /* Write master node entry. */ - - ret = mfs_mn_sync(sb, &path[0], mn_blk1, mn_blk2, jrnl_blk); - if (predict_false(ret < 0)) - { - MFS_MN(sb) = mn_state; - goto errout; - } - - /* Mark all old blocks of journal (and master blocks) as deletable. */ - - for (i = 0; i < MFS_JRNL(sb).n_blks + 2; i++) - { - blk = mfs_jrnl_blkidx2blk(sb, i); - mfs_ba_blkmarkdel(sb, blk); - } - - /* Delete outdated blocks. */ - - ret = mfs_ba_delmarked(sb); - if (predict_false(ret < 0)) - { - goto errout; - } - -errout: - return ret; -} - -bool mfs_jrnl_isempty(FAR const struct mfs_sb_s * const sb) -{ - return MFS_JRNL(sb).n_logs == 0; -} diff --git a/fs/mnemofs/mnemofs_lru.c b/fs/mnemofs/mnemofs_lru.c deleted file mode 100644 index fbed366c60d..00000000000 --- a/fs/mnemofs/mnemofs_lru.c +++ /dev/null @@ -1,1068 +0,0 @@ -/**************************************************************************** - * fs/mnemofs/mnemofs_lru.c - * - * SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. The - * ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Alternatively, the contents of this file may be used under the terms of - * the BSD-3-Clause license: - * - * SPDX-License-Identifier: BSD-3-Clause - * - * Copyright (c) 2024 Saurav Pal - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the author nor the names of its contributors may - * be used to endorse or promote products derived from this software - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - ****************************************************************************/ - -/**************************************************************************** - * LRU (Least Recently Used) cache takes in all the changes the user wants - * to do to the on-flash storage, and stores them in memory. When a - * significant amount of changes are accumulated, the LRU writes the new - * information to the flash. - * - * LRU is a kernel list of nodes. Each node represents a CTZ list. Each node - * contains a kernel list of changes requested for the CTZ list, called as - * deltas. - * - * When LRU is full the last node is flushed (it can be explicitly flushed as - * well) and all the changes are written at once on the flash, and the new - * location is noted down in the journal, and an entry for the location - * update is added to the LRU for the parent. - ****************************************************************************/ - -/**************************************************************************** - * Included Files - ****************************************************************************/ - -#include -#include -#include - -#include "mnemofs.h" -#include "fs_heap.h" - -/**************************************************************************** - * Pre-processor Definitions - ****************************************************************************/ - -/**************************************************************************** - * Private Types - ****************************************************************************/ - -enum -{ - MFS_LRU_UPD, - MFS_LRU_DEL, -}; - -/**************************************************************************** - * Private Function Prototypes - ****************************************************************************/ - -static void lru_nodesearch(FAR const struct mfs_sb_s * const sb, - FAR const struct mfs_path_s * const path, - const mfs_t depth, FAR struct mfs_node_s **node); -static bool lru_islrufull(FAR struct mfs_sb_s * const sb); -static bool lru_isnodefull(FAR struct mfs_sb_s * const sb, - FAR struct mfs_node_s *node); -static int lru_nodeflush(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth, FAR struct mfs_node_s *node, - bool rm_node); -static int lru_wrtooff(FAR struct mfs_sb_s * const sb, const mfs_t data_off, - mfs_t bytes, int op, - FAR struct mfs_path_s * const path, - const mfs_t depth, FAR const char *buf); -static int lru_updatesz(FAR struct mfs_sb_s * sb, - FAR struct mfs_path_s * const path, - const mfs_t depth, const mfs_t new_sz); -static void lru_node_free(FAR struct mfs_node_s *node); - -/**************************************************************************** - * Private Data - ****************************************************************************/ - -/**************************************************************************** - * Public Data - ****************************************************************************/ - -/**************************************************************************** - * Private Functions - ****************************************************************************/ - -/**************************************************************************** - * Name: lru_nodesearch - * - * Description: - * Searches a node by the `path` and `depth` in the LRU. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - CTZ representation of the relpath. - * depth - Depth of `path`. - * node - To populate with the node corresponding to the CTZ. - * - ****************************************************************************/ - -static void lru_nodesearch(FAR const struct mfs_sb_s * const sb, - FAR const struct mfs_path_s * const path, - const mfs_t depth, FAR struct mfs_node_s **node) -{ - bool found = false; - mfs_t i; - FAR struct mfs_node_s *n; - - *node = NULL; - - list_for_every_entry(&MFS_LRU(sb), n, struct mfs_node_s, list) - { - /* We need this loop to specifically check the parents in case these - * entries are all new, and have not been allocated any pages for - * being stored in the flash. Also we know that the root (depth 1) will - * be at least common in their paths. - */ - - DEBUGASSERT(depth > 0); - - if (n->depth != depth) - { - continue; - } - - found = true; - for (i = n->depth; i >= 1 && found; i--) - { - if (path[i - 1].ctz.idx_e == 0 && path[i - 1].ctz.pg_e == 0 && - mfs_ctz_eq(&n->path[i - 1].ctz, &path[i - 1].ctz) && - n->path[i - 1].off == path[i - 1].off) - { - /* OK */ - } - else if (path[i - 1].ctz.pg_e != 0 && - mfs_ctz_eq(&n->path[i - 1].ctz, &path[i - 1].ctz)) - { - /* OK */ - } - else - { - found = false; - } - } - - if (found) - { - *node = n; - break; - } - } - - if (found) - { - finfo("Node search ended with match of node %p at depth %u" - " for CTZ of %" PRIu32 " size with range [%" PRIu32 ", %" PRIi32 - ").", n, n->depth, n->sz, n->range_min, n->range_max); - } - else - { - finfo("Node search ended without match."); - *node = NULL; - } -} - -/**************************************************************************** - * Name: lru_islrufull - * - * Description: - * Check whether the number of nodes in the LRU has reaches its limit. - * - * Input Parameters: - * sb - Superblock instance of the device. - * - * Returned Value: - * true - LRU is full - * false - LRU is not full. - * - * Assumptions/Limitations: - * When the journal is being flushed, LRU memory limiters will be turned - * off. - * - ****************************************************************************/ - -static bool lru_islrufull(FAR struct mfs_sb_s * const sb) -{ - return !MFS_FLUSH(sb) && list_length(&MFS_LRU(sb)) == CONFIG_MNEMOFS_NLRU; -} - -/**************************************************************************** - * Name: lru_isnodefull - * - * Description: - * Check whether the number of deltas in an LRU node has reaches its limit. - * - * Input Parameters: - * node - LRU node. - * - * Returned Value: - * true - LRU node is full - * false - LRU node is not full. - * - * Assumptions/Limitations: - * When the journal is being flushed, LRU memory limiters will be turned - * off. - * - ****************************************************************************/ - -static bool lru_isnodefull(FAR struct mfs_sb_s * const sb, - FAR struct mfs_node_s *node) -{ - return !MFS_FLUSH(sb) && node->n_list == CONFIG_MNEMOFS_NLRUDELTA; -} - -/**************************************************************************** - * Name: lru_free_delta - * - * Description: - * Free a node's delta. - * - * Input Parameters: - * delta - LRU delta. - * - ****************************************************************************/ - -static void lru_free_delta(FAR struct mfs_delta_s *delta) -{ - fs_heap_free(delta->upd); - fs_heap_free(delta); -} - -/**************************************************************************** - * Name: lru_nodeflush - * - * Description: - * Clear out the deltas in a node by writing them to the flash, and adding - * a log about it to the journal. Does not flush the journal, and assumes - * enough space is in the journal to handle a log. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - CTZ representation of the relpath. - * depth - Depth of `path`. - * node - LRU node to flush. - * rm_node - To remove node out of LRU (true), or just clear the deltas - * (false). - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int lru_nodeflush(FAR struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth, FAR struct mfs_node_s *node, - bool rm_node) -{ - int ret = OK; - struct mfs_ctz_s loc; - FAR struct mfs_delta_s *delta = NULL; - FAR struct mfs_delta_s *tmp = NULL; - - if (predict_false(node == NULL)) - { - return -EINVAL; - } - - ret = mfs_ctz_wrtnode(sb, node, &loc); - if (predict_false(ret < 0)) - { - goto errout; - } - - /* Free deltas after flush. */ - - finfo("Removing Deltas."); - - list_for_every_entry_safe(&node->delta, delta, tmp, struct mfs_delta_s, - list) - { - list_delete_init(&delta->list); - lru_free_delta(delta); - } - - if (rm_node) - { - finfo("Deleting node. Old size: %zu.", list_length(&MFS_LRU(sb))); - list_delete_init(&node->list); - finfo("Deleted node. New size: %zu.", list_length(&MFS_LRU(sb))); - } - else - { - /* Reset node stats. */ - - finfo("Resetting node."); - memset(node, 0, sizeof(struct mfs_node_s)); - node->range_min = UINT32_MAX; - } - - finfo("Updating CTZ in parent."); - ret = mfs_lru_updatectz(sb, node->path, node->depth, loc, node->sz); - - if (rm_node) - { - finfo("Freeing node."); - lru_node_free(node); - } - -errout: - return ret; -} - -/**************************************************************************** - * Name: lru_wrtooff - * - * Description: - * Write to offset in LRU. - * - * Input Parameters: - * sb - Superblock instance of the device. - * data_off - Offset into the data in the CTZ skip list. - * bytes - Number of bytes to write. - * ctz_sz - Size of the CTZ skip list. - * op - Operation (MFS_LRU_UPD or MFS_LRU_DEL). - * path - CTZ representation of the path. - * depth - Depth of path. - * buf - Buffer containing data. - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - ****************************************************************************/ - -static int lru_wrtooff(FAR struct mfs_sb_s * const sb, const mfs_t data_off, - mfs_t bytes, int op, - FAR struct mfs_path_s * const path, const mfs_t depth, - FAR const char *buf) -{ - int ret = OK; - bool found = true; - mfs_t old_sz; - FAR struct mfs_node_s *node = NULL; - FAR struct mfs_node_s *last_node = NULL; - FAR struct mfs_delta_s *delta = NULL; - - DEBUGASSERT(depth > 0); - - lru_nodesearch(sb, path, depth, &node); - - if (node == NULL) - { - node = fs_heap_zalloc(sizeof(*node)); - if (predict_false(node == NULL)) - { - found = false; - ret = -ENOMEM; - goto errout; - } - - node->path = fs_heap_zalloc(depth * sizeof(struct mfs_path_s)); - if (predict_false(node->path == NULL)) - { - found = false; - ret = -ENOMEM; - goto errout_with_node; - } - - node->sz = path[depth - 1].sz; - node->depth = depth; - node->n_list = 0; - node->range_max = 0; - node->range_min = UINT32_MAX; - list_initialize(&node->delta); - memcpy(node->path, path, depth * sizeof(struct mfs_path_s)); - found = false; - - finfo("Node not found. Allocated at %p.", node); - } - - if (!found) - { - if (lru_islrufull(sb)) - { - finfo("LRU is full, need to flush a node."); - last_node = list_container_of(list_peek_tail(&MFS_LRU(sb)), - struct mfs_node_s, list); - list_delete_init(&last_node->list); - list_add_tail(&MFS_LRU(sb), &node->list); - finfo("LRU flushing node complete, now only %zu nodes", - list_length(&MFS_LRU(sb))); - } - else - { - list_add_tail(&MFS_LRU(sb), &node->list); - finfo("Node inserted into LRU, and it now %zu node(s).", - list_length(&MFS_LRU(sb))); - } - } - else if (found && lru_isnodefull(sb, node)) - { - /* This can be optimized further if needed, but for now, for safety of - * the data, I think it's better to flush the entire thing. It won't - * flush ALL of it, just, whatever's required. - */ - - ret = mnemofs_flush(sb); - if (predict_false(ret < 0)) - { - goto errout_with_node; - } - } - - /* Add delta to node. */ - - finfo("Adding delta to the node."); - delta = fs_heap_zalloc(sizeof(*delta)); - if (predict_false(delta == NULL)) - { - ret = -ENOMEM; - goto errout_with_node; - } - - finfo("Delta allocated."); - - if (op == MFS_LRU_UPD) - { - delta->upd = fs_heap_zalloc(bytes); - if (predict_false(delta->upd == NULL)) - { - ret = -ENOMEM; - goto errout_with_delta; - } - - finfo("Delta is of the update type, has %u bytes at offset %u.", - bytes, data_off); - } - - delta->n_b = bytes; - delta->off = data_off; - list_add_tail(&node->delta, &delta->list); - if (op == MFS_LRU_UPD) - { - memcpy(delta->upd, buf, bytes); - } - - node->n_list++; - node->range_min = MIN(node->range_min, data_off); - node->range_max = MAX(node->range_max, data_off + bytes); - - old_sz = node->sz; - node->sz = MAX(node->range_max, path[depth - 1].sz); - node->path[node->depth - 1].sz = node->sz; - - if (old_sz != node->sz) - { - ret = lru_updatesz(sb, node->path, node->depth, node->sz); - if (predict_false(ret < 0)) - { - goto errout_with_delta; - } - } - - finfo("Delta attached to node %p. Now there are %zu nodes and the" - " node has %zu deltas. Node with range [%" PRIu32 ", %" - PRIu32 ").", node, list_length(&MFS_LRU(sb)), - list_length(&node->delta), node->range_min, node->range_max); - - return ret; - -errout_with_delta: - list_delete(&delta->list); - fs_heap_free(delta); - -errout_with_node: - if (!found && node != NULL) - { - list_delete(&node->list); - fs_heap_free(node); - } - -errout: - return ret; -} - -/**************************************************************************** - * Name: lru_updatesz - * - * Description: - * Updates size of an fs object in its parent. - * - * Input Parameters: - * sb - Superblock instance of the device. - * path - CTZ representation of the path. - * depth - Depth of path. - * new_sz - New size - * - * Returned Value: - * 0 - OK - * < 0 - Error - * - * Assumptions/Limitations: - * Adds an entry for the target's parent to update the child's size, and - * updates the size in path of everyone that has this child. - * - ****************************************************************************/ - -static int lru_updatesz(FAR struct mfs_sb_s * sb, - FAR struct mfs_path_s * const path, - const mfs_t depth, const mfs_t new_sz) -{ - int ret = OK; - struct mfs_node_s *n = NULL; - mfs_t i; - bool found; - char buf[sizeof(mfs_t)]; - - DEBUGASSERT(depth > 0); - - list_for_every_entry(&MFS_LRU(sb), n, struct mfs_node_s, list) - { - if (n->depth < depth) - { - continue; - } - - found = false; - for (i = depth; i >= 1; i--) - { - if (path[i - 1].ctz.idx_e == 0 && path[i - 1].ctz.pg_e == 0 && - mfs_ctz_eq(&n->path[i - 1].ctz, &path[i - 1].ctz) && - n->path[i - 1].off == path[i - 1].off) - { - found = true; - } - else if (path[i - 1].ctz.pg_e != 0 && - mfs_ctz_eq(&n->path[i - 1].ctz, &path[i - 1].ctz)) - { - found = true; - } - else - { - break; - } - } - - if (found) - { - n->path[depth - 1].sz = new_sz; - } - } - - if (depth == 1) - { - MFS_MN(sb).root_sz = new_sz; - goto errout; - } - - memset(buf, 0, sizeof(mfs_t)); - mfs_ser_mfs(new_sz, buf); - - /* This function will be used by mfs_lru_wr itself, but given that if - * there is no change in size, this won't cause an infinite loop (or, in - * reality, a recursion till it reaches the top of the tree), this should - * be fine. - */ - - ret = mfs_lru_wr(sb, path[depth - 2].off + offsetof(struct mfs_dirent_s, - sz), sizeof(mfs_t), path, depth - 1, buf); - if (predict_false(ret < 0)) - { - goto errout; - } - - path[depth - 1].sz = new_sz; - -errout: - return ret; -} - -static void lru_node_free(FAR struct mfs_node_s *node) -{ - mfs_free_patharr(node->path); - fs_heap_free(node); -} - -static bool lru_sort_cmp(FAR struct mfs_node_s * const node, - FAR struct mfs_node_s * const pivot) -{ - return node->depth < pivot->depth; -} - -static void lru_sort(FAR struct mfs_sb_s * const sb, - FAR struct list_node *left, - FAR struct list_node *right) -{ - FAR struct mfs_node_s *node = NULL; - FAR struct mfs_node_s *next = NULL; - FAR struct mfs_node_s *pivot = NULL; - FAR struct list_node *aend = NULL; /* After end. */ - FAR struct list_node *bfirst = NULL; /* Before first. */ - - if (left == right) - { - return; - } - - /* If left or right is NULL, it means that refers to MFS_LRU(sb). */ - - aend = right->next; - bfirst = left->prev; - - node = list_container_of(left, struct mfs_node_s, list); - pivot = list_container_of(right, struct mfs_node_s, list); - - if (node->list.next == &pivot->list) - { - /* Only two items in the window...node and pivot, so insertion sort. */ - - if (lru_sort_cmp(node, pivot)) - { - /* Add node after the pivot. */ - - list_delete_init(&node->list); - list_add_after(&pivot->list, &node->list); - - DEBUGASSERT(pivot->list.prev == bfirst); - } - - DEBUGASSERT(!lru_sort_cmp(node, pivot)); - - return; - } - - list_for_every_entry_safe_from(&MFS_LRU(sb), node, next, struct mfs_node_s, - list) - { - if (node == pivot) - { - break; - } - - if (lru_sort_cmp(node, pivot)) - { - /* Add node after the pivot. */ - - list_delete_init(&node->list); - list_add_after(&pivot->list, &node->list); - } - } - - if (bfirst->next != &pivot->list) - { - lru_sort(sb, bfirst->next, pivot->list.prev); - } - - if (aend->prev != &pivot->list) - { - lru_sort(sb, pivot->list.next, aend->prev); - } -} - -/**************************************************************************** - * Public Functions - ****************************************************************************/ - -int mfs_lru_flush(FAR struct mfs_sb_s * const sb) -{ - int ret = OK; - FAR struct mfs_node_s *tmp = NULL; - FAR struct mfs_node_s *tmp2 = NULL; - FAR struct mfs_node_s *node = NULL; - FAR struct mfs_node_s *next = NULL; - -/* Modified quick sort in linked lists. What is wanted is like inverted - * topological sort, but all the files (no children) are at the front, - * their depths don't matter. BUT, when it comes to directories, they need - * to be sorted in a decreasing order of their depths to reduce updates due - * to CoW. This will trickle up to the root, such that the root will be the - * last to get updated, and then the master node. - * - * However, since passing the mode all the way requires a lot of change, and - * is a redundant piece of information in most cases, the quick sort can - * simply be done on the basis of depth, and this adventure can be left as a - * TODO. - * - * This involves recursion, but given the LRU size is a constant, the depth - * of recursion will be log2(n). For an LRU size of even 128 (which is quite - * big), the stack depth for this will be 7. - */ - - finfo("Sorting the LRU. No. of nodes: %zu", list_length(&MFS_LRU(sb))); - - lru_sort(sb, MFS_LRU(sb).next, MFS_LRU(sb).prev); - MFS_FLUSH(sb) = true; - - list_for_every_entry_safe(&MFS_LRU(sb), node, next, struct mfs_node_s, - list) - { - finfo("Current node depth: %u.", node->depth); - - if (node->depth != 1) - { - finfo("Checking for parent."); - - /* Ensuring parent is either present, or inserted into the LRU. - * No need of doing this before removing current node from LRU, - * however, this allows us to possibly skip allocating path again - * after freeing the current node. - */ - - /* We can not rely on normal LRU node insertions, as they will - * not be inserted in a sorted manner, and would need the entire - * LRU to be sorted again, so we insert it manually. - */ - - lru_nodesearch(sb, node->path, node->depth - 1, &tmp); - - if (tmp == NULL) - { - finfo("Adding parent to LRU"); - - tmp = fs_heap_zalloc(sizeof(struct mfs_node_s)); - if (predict_false(tmp == NULL)) - { - ret = -ENOMEM; - goto errout; - } - - tmp->range_max = 0; - tmp->range_min = UINT32_MAX; - - /* TODO: Time fields. in tmp. */ - - tmp->depth = node->depth - 1; - tmp->path = fs_heap_zalloc((node->depth - 1) - * sizeof(struct mfs_path_s)); - if (predict_false(tmp->path == NULL)) - { - ret = -ENOMEM; - goto errout_with_tmp; - } - - memcpy(tmp->path, node->path, - sizeof(struct mfs_path_s) * tmp->depth); - list_initialize(&tmp->list); - list_initialize(&tmp->delta); - - /* Insert into sorted. */ - - list_for_every_entry(&MFS_LRU(sb), tmp2, struct mfs_node_s, - list) - { - if (!lru_sort_cmp(tmp, tmp2)) - { - list_add_before(&tmp2->list, &tmp->list); - - if (tmp2->list.prev == &node->list) - { - next = tmp2; - } - - break; - } - } - } - else - { - finfo("Parent already in LRU."); - } - } - else - { - finfo("Root node from LRU."); - } - - /* Parent gets updated inside the LRU in the function below. */ - - finfo("Flushing node."); - ret = lru_nodeflush(sb, node->path, node->depth, node, true); - if (predict_true(ret < 0)) - { - goto errout; - } - } - - return ret; - -errout_with_tmp: - lru_node_free(node); - -errout: - MFS_FLUSH(sb) = false; - return ret; -} - -int mfs_lru_del(FAR struct mfs_sb_s * const sb, const mfs_t data_off, - mfs_t bytes, FAR struct mfs_path_s * const path, - const mfs_t depth) -{ - return lru_wrtooff(sb, data_off, bytes, MFS_LRU_DEL, path, depth, NULL); -} - -int mfs_lru_wr(FAR struct mfs_sb_s * const sb, const mfs_t data_off, - mfs_t bytes, FAR struct mfs_path_s * const path, - const mfs_t depth, FAR const char *buf) -{ - return lru_wrtooff(sb, data_off, bytes, MFS_LRU_UPD, path, depth, buf); -} - -void mfs_lru_init(FAR struct mfs_sb_s * const sb) -{ - list_initialize(&MFS_LRU(sb)); - - finfo("LRU Initialized\n"); -} - -int mfs_lru_rdfromoff(FAR const struct mfs_sb_s * const sb, - const mfs_t data_off, - FAR struct mfs_path_s * const path, const mfs_t depth, - FAR char *buf, const mfs_t buflen) -{ - /* Requires updated path from the journal. */ - - int ret = OK; - mfs_t upper; - mfs_t lower; - mfs_t rem_sz; - mfs_t upper_og; - mfs_t upper_upd; - mfs_t lower_upd; - FAR char *tmp; - struct mfs_ctz_s ctz; - FAR struct mfs_node_s *node = NULL; - FAR struct mfs_delta_s *delta = NULL; - - /* Node is NOT supposed to be freed by the caller, it's a reference to - * the actual node in the LRU and freeing it could break the entire LRU. - */ - - tmp = buf; - ctz = path[depth - 1].ctz; - lower = data_off; - upper_og = lower + buflen; - upper = upper_og; - rem_sz = buflen; - - lru_nodesearch(sb, path, depth, &node); - if (node == NULL) - { - mfs_ctz_rdfromoff(sb, ctz, 0, buflen, tmp); - goto errout; - } - - while (rem_sz > 0) - { - mfs_ctz_rdfromoff(sb, ctz, lower, rem_sz, tmp); - - list_for_every_entry(&node->delta, delta, struct mfs_delta_s, list) - { - if (delta->upd == NULL) - { - /* Delete */ - - lower_upd = MAX(lower, delta->off); - upper_upd = MIN(upper, delta->off + delta->n_b); - - if (lower_upd >= upper_upd) - { - /* Outside range */ - } - else - { - memmove(tmp + (lower - lower_upd), - tmp + (upper_upd - lower), upper - upper_upd); - - upper -= (upper_upd - lower_upd); - } - } - else - { - /* Update */ - - lower_upd = MAX(lower, delta->off); - upper_upd = MIN(upper, delta->off + delta->n_b); - - if (lower_upd >= upper_upd) - { - /* Outside range */ - } - else - { - memcpy(tmp + (lower_upd - lower), - delta->upd + (lower_upd - delta->off), - upper_upd - lower_upd); - } - } - } - - tmp += upper - lower; - rem_sz -= upper - lower; - lower = upper; - upper = upper_og; - } - -errout: - return ret; -} - -int mfs_lru_getupdatedinfo(FAR const struct mfs_sb_s * const sb, - FAR struct mfs_path_s * const path, - const mfs_t depth) -{ - int ret = OK; - bool found; - mfs_t i; - FAR struct mfs_node_s *node = NULL; - - DEBUGASSERT(depth > 0); - - list_for_every_entry(&MFS_LRU(sb), node, struct mfs_node_s, list) - { - /* TODO: When a directory is newly created, and still in the LRU, its - * CTZ is (0, 0), and this can match others as well if at same depth, - * so, in these cases, match the parents, and so on up. - */ - - DEBUGASSERT(node->depth > 0); - - if (node->depth > depth) - { - continue; - } - - /* We need this loop to specifically check the parents in case these - * entries are all new, and have not been allocated any pages for - * being stored in the flash. Also we know that the root (depth 1) will - * be at least common in their paths. - */ - - found = true; - for (i = node->depth; i >= 1 && found; i--) - { - if (path[i - 1].ctz.idx_e == 0 && path[i - 1].ctz.pg_e == 0 && - mfs_ctz_eq(&node->path[i - 1].ctz, &path[i - 1].ctz) && - node->path[i - 1].off == path[i - 1].off) - { - /* OK */ - } - else if (path[i - 1].ctz.pg_e != 0 && - mfs_ctz_eq(&node->path[i - 1].ctz, &path[i - 1].ctz)) - { - /* OK */ - } - else - { - found = false; - } - } - - if (found) - { - path[node->depth - 1].sz = node->path[node->depth - 1].sz; - } - } - - return ret; -} - -int mfs_lru_updatectz(FAR struct mfs_sb_s * sb, - FAR struct mfs_path_s * const path, const mfs_t depth, - const struct mfs_ctz_s new_ctz, mfs_t new_sz) -{ - int ret = OK; - char buf[sizeof(struct mfs_ctz_s)]; - FAR struct mfs_node_s *node = NULL; - - /* TODO: Other attributes like time stamps to be updated as well. */ - - list_for_every_entry(&MFS_LRU(sb), node, struct mfs_node_s, list) - { - if (node->depth >= depth && - mfs_ctz_eq(&node->path[depth - 1].ctz, &path[depth - 1].ctz)) - { - node->path[depth - 1].ctz = new_ctz; - node->path[depth - 1].sz = path[depth - 1].sz; - } - } - - if (depth == 1) - { - MFS_MN(sb).root_sz = new_sz; - MFS_MN(sb).root_ctz = new_ctz; - - goto errout; - } - - /* Write to LRU. */ - - memset(buf, 0, sizeof(struct mfs_ctz_s)); - mfs_ser_ctz(&new_ctz, buf); - ret = mfs_lru_wr(sb, path[depth - 1].off + offsetof(struct mfs_dirent_s, - ctz), sizeof(struct mfs_ctz_s), path, depth - 1, buf); - if (predict_false(ret < 0)) - { - goto errout; - } - - ret = lru_updatesz(sb, path, depth, new_sz); - if (predict_false(ret < 0)) - { - goto errout; - } - - path[depth - 1].ctz = new_ctz; - path[depth - 1].sz = new_sz; - -errout: - return ret; -} - -bool mfs_lru_isempty(FAR struct mfs_sb_s * const sb) -{ - return list_length(&MFS_LRU(sb)) == 0; -} diff --git a/fs/mnemofs/mnemofs_master.c b/fs/mnemofs/mnemofs_master.c deleted file mode 100644 index 54c1b2da0ec..00000000000 --- a/fs/mnemofs/mnemofs_master.c +++ /dev/null @@ -1,453 +0,0 @@ -/**************************************************************************** - * fs/mnemofs/mnemofs_master.c - * - * SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. The - * ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Alternatively, the contents of this file may be used under the terms of - * the BSD-3-Clause license: - * - * SPDX-License-Identifier: BSD-3-Clause - * - * Copyright (c) 2024 Saurav Pal - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the author nor the names of its contributors may - * be used to endorse or promote products derived from this software - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - ****************************************************************************/ - -/**************************************************************************** - * In mnemofs, the master node points to the root of the file system. It - * contains the information about the root, and when the root is updated, - * the master node needs to point to the updated location, and thus, needs to - * update the master node. - * - * Master nodes sit at the very end of the journal. The last two blocks of - * the journal are called master blocks, and they are filled with a new - * entry for a master node every time it is updated. They are filled in a - * sequential manner, and thus, the latest master node can be found easily. - * The two master blocks contain identical information, and exist to be as a - * backup. - * - * The stored master nodes are basically `struct mfs_mn_s` without the - * redundant `pg` member. - * - * The master node also points to the start of the journal, and thus, when - * the journal moves, a new master node entry is added. - * - * A master node update, when written to the file system, marks the end of - * an update of the file system tree. Thus, at this point, any obsolete data - * that can be erased, will be erased by the block allocator. Only after - * writing the master block is the file system tree updated. Before this, - * the old file system tree is accessible through the older master node, and - * can be accessed again during power loss. - ****************************************************************************/ - -/**************************************************************************** - * Included Files - ****************************************************************************/ - -#include -#include - -#include "mnemofs.h" - -/**************************************************************************** - * Pre-processor Definitions - ****************************************************************************/ - -/**************************************************************************** - * Private Types - ****************************************************************************/ - -/**************************************************************************** - * Private Function Prototypes - ****************************************************************************/ - -static FAR char *ser_mn(const struct mfs_mn_s mn, - FAR char * const out); -static FAR const char *deser_mn(FAR const char * const in, - FAR struct mfs_mn_s *mn, FAR uint16_t *hash); - -/**************************************************************************** - * Private Data - ****************************************************************************/ - -/**************************************************************************** - * Public Data - ****************************************************************************/ - -/**************************************************************************** - * Private Functions - ****************************************************************************/ - -/**************************************************************************** - * Name: ser_mn - * - * Description: - * Serialize master node. - * - * Input Parameters: - * mn - Master node. - * out - Out buffer. - * - * Returned Value: - * Pointer to the end of the serialized data in `out`. - * - * Assumptions/Limitations: - * Out should contain enough space for `mn` and 1 byte extra for the hash. - * - ****************************************************************************/ - -static FAR char *ser_mn(const struct mfs_mn_s mn, FAR char * const out) -{ - FAR char *tmp = out; - - tmp = mfs_ser_mfs(mn.jrnl_blk, tmp); - tmp = mfs_ser_mfs(mn.mblk_idx, tmp); - tmp = mfs_ser_ctz(&mn.root_ctz, tmp); - tmp = mfs_ser_mfs(mn.root_sz, tmp); - tmp = mfs_ser_timespec(&mn.ts, tmp); - tmp = mfs_ser_16(mfs_hash(out, tmp - out), tmp); - - /* TODO: Update this, and the make a macro for size of MN. */ - - return tmp; -} - -/**************************************************************************** - * Name: ser_mn - * - * Description: - * Deserialize master node. - * - * Input Parameters: - * in - In buffer. - * mn - Master node to populate. - * hash - Stored hash (of serialized data) to populate. - * - * Returned Value: - * Pointer to the end of the serialized data in `in`. - * - * Assumptions/Limitations: - * In should contain enough space for `mn` and 1 byte extra for the hash. - * - ****************************************************************************/ - -static FAR const char *deser_mn(FAR const char * const in, - FAR struct mfs_mn_s *mn, FAR uint16_t *hash) -{ - FAR const char *tmp = in; - - tmp = mfs_deser_mfs(tmp, &mn->jrnl_blk); - tmp = mfs_deser_mfs(tmp, &mn->mblk_idx); - tmp = mfs_deser_ctz(tmp, &mn->root_ctz); - tmp = mfs_deser_mfs(tmp, &mn->root_sz); - tmp = mfs_deser_timespec(tmp, &mn->ts); - tmp = mfs_deser_16(tmp, hash); - - /* TODO: Update this, and the make a macro for size of MN. */ - - return tmp; -} - -/**************************************************************************** - * Public Functions - ****************************************************************************/ - -int mfs_mn_init(FAR struct mfs_sb_s * const sb, const mfs_t jrnl_blk) -{ - int ret = OK; - bool found = false; - mfs_t i = 0; - mfs_t mblk1; - mfs_t blkidx; - mfs_t pg_in_blk; - mfs_t jrnl_blk_tmp; - uint16_t hash; - struct mfs_mn_s mn; - const mfs_t sz = sizeof(struct mfs_mn_s) - sizeof(mn.pg); - char buftmp[4]; - char buf[sz + 1]; - struct mfs_jrnl_log_s log; - - mblk1 = mfs_jrnl_blkidx2blk(sb, MFS_JRNL(sb).n_blks); - - mn.jrnl_blk = jrnl_blk; - mn.mblk_idx = 0; - mn.pg = MFS_BLK2PG(sb, mblk1); - - for (i = 0; i < MFS_PGINBLK(sb); i++) - { - mfs_read_page(sb, buftmp, 4, mn.pg, 0); - mfs_deser_mfs(buftmp, &jrnl_blk_tmp); - - if (jrnl_blk_tmp == 0) - { - break; - } - - found = true; - mn.mblk_idx++; - mn.pg++; - } - - if (found == false) - { - ret = -EINVAL; - goto errout; - } - - if (i == MFS_PGINBLK(sb)) - { - ret = -ENOSPC; - goto errout; - } - else - { - mn.pg--; - } - - mfs_read_page(sb, buf, sz + 1, mn.pg, 0); - deser_mn(buf, &mn, &hash); - if (hash != mfs_hash(buf, sz)) - { - ret = -EINVAL; - goto errout; - } - - blkidx = MFS_JRNL(sb).log_sblkidx; - pg_in_blk = MFS_JRNL(sb).log_spg % MFS_PGINBLK(sb); - - while (true) - { - ret = mfs_jrnl_rdlog(sb, &blkidx, &pg_in_blk, &log); - if (predict_false(ret < 0 && ret != -ENOSPC)) - { - goto errout; - } - else if (ret == -ENOSPC) - { - ret = OK; - break; - } - - /* Assumes checking the depth is enough to check if it's empty, as - * theoretically there are no blocks with depth 0, as root has a - * depth of 1. - */ - - if (log.depth == 0) - { - DEBUGASSERT(log.path == NULL); - break; - } - - if (log.depth == 1) - { - mn.root_ctz = log.loc_new; - mn.root_sz = log.sz_new; - } - - mfs_jrnl_log_free(&log); - } - - /* FUTURE TODO: Recovery in case of hash not matching, or page not - * readable. - */ - - mn.root_mode = 0777 | S_IFDIR; - - MFS_MN(sb) = mn; -errout: - return ret; -} - -int mfs_mn_fmt(FAR struct mfs_sb_s * const sb, const mfs_t mblk1, - const mfs_t mblk2, const mfs_t jrnl_blk) -{ - int ret = OK; - mfs_t pg; - struct mfs_mn_s mn; - struct timespec ts; - const mfs_t sz = sizeof(struct mfs_mn_s) - sizeof(mn.pg); - char buf[sz + 1]; - - clock_gettime(CLOCK_REALTIME, &ts); - - memset(buf, 0, sz + 1); - - pg = mfs_ba_getpg(sb); - if (predict_false(pg == 0)) - { - ret = -ENOSPC; - goto errout; - } - - finfo("Root formatted to be at Page %u", pg); - - mn.root_ctz.idx_e = 0; - mn.root_ctz.pg_e = pg; - mn.jrnl_blk = jrnl_blk; - mn.mblk_idx = 0; - mn.pg = MFS_BLK2PG(sb, mblk1); - mn.root_sz = 0; - mn.ts = ts; - mn.root_st_atim = ts; - mn.root_st_ctim = ts; - mn.root_st_mtim = ts; - mn.root_mode = 0777 | S_IFDIR; - - /* Serialize. */ - - ser_mn(mn, buf); - - ret = mfs_write_page(sb, buf, sz, MFS_BLK2PG(sb, mblk1), 0); - if (predict_false(ret < 0)) - { - goto errout; - } - else - { - ret = OK; - } - - ret = mfs_write_page(sb, buf, sz, MFS_BLK2PG(sb, mblk2), 0); - if (predict_false(ret < 0)) - { - goto errout; - } - else - { - ret = OK; - } - - mn.mblk_idx = 1; - MFS_MN(sb) = mn; - finfo("Master node written. Now at page %d, timestamp %lld.%.9ld.", - MFS_MN(sb).pg, (long long)MFS_MN(sb).ts.tv_sec, - MFS_MN(sb).ts.tv_nsec); - -errout: - return ret; -} - -int mfs_mn_move(FAR struct mfs_sb_s * const sb, struct mfs_ctz_s root, - const mfs_t root_sz) -{ - int ret = OK; - struct mfs_mn_s mn; - const mfs_t sz = sizeof(struct mfs_mn_s) - sizeof(mn.pg); - char buf[sz + 1]; - - if (MFS_MN(sb).mblk_idx == MFS_PGINBLK(sb) - 1) - { - /* TODO: Move journal. Master blocks are full. */ - } - - mn = MFS_MN(sb); - - mn.root_ctz = root; - mn.root_sz = root_sz; - mn.mblk_idx++; /* TODO */ - mn.pg++; - - ser_mn(mn, buf); - - ret = mfs_write_page(sb, buf, sz + 1, mn.pg, 0); - if (predict_false(ret < 0)) - { - goto errout; - } - - MFS_MN(sb) = mn; - -errout: - return ret; -} - -int mfs_mn_sync(FAR struct mfs_sb_s *sb, - FAR struct mfs_path_s * const new_loc, - const mfs_t blk1, const mfs_t blk2, const mfs_t jrnl_blk) -{ - int ret = OK; - struct timespec ts; - struct mfs_mn_s mn; - const mfs_t sz = sizeof(struct mfs_mn_s) - sizeof(mn.pg); - char buf[sz + 1]; - - mn = MFS_MN(sb); - - clock_gettime(CLOCK_REALTIME, &ts); - - if (mn.mblk_idx == MFS_PGINBLK(sb)) - { - /* New blocks have been already allocated by the journal. */ - - mn.mblk_idx = 0; - mn.pg = MFS_BLK2PG(sb, blk1); - } - - mn.ts = ts; - mn.root_sz = new_loc->sz; - mn.root_ctz = new_loc->ctz; - mn.root_mode = 0777 | S_IFDIR; - - /* TODO: Root timestamps. */ - - /* Serialize. */ - - ser_mn(mn, buf); - - ret = mfs_write_page(sb, buf, sz, MFS_BLK2PG(sb, blk1) + mn.mblk_idx, 0); - if (predict_false(ret < 0)) - { - goto errout; - } - - ret = mfs_write_page(sb, buf, sz, MFS_BLK2PG(sb, blk2) + mn.mblk_idx, 0); - if (predict_false(ret < 0)) - { - goto errout; - } - - mn.mblk_idx++; - MFS_MN(sb) = mn; - -errout: - return ret; -} diff --git a/fs/mnemofs/mnemofs_rw.c b/fs/mnemofs/mnemofs_rw.c index bc596937e86..7b21805bb01 100644 --- a/fs/mnemofs/mnemofs_rw.c +++ b/fs/mnemofs/mnemofs_rw.c @@ -54,129 +54,503 @@ * Included Files ****************************************************************************/ -#include +#include +#include #include "mnemofs.h" -/**************************************************************************** - * Pre-processor Definitions - ****************************************************************************/ - -/**************************************************************************** - * Private Types - ****************************************************************************/ - -/**************************************************************************** - * Private Function Prototypes - ****************************************************************************/ - -/**************************************************************************** - * Private Data - ****************************************************************************/ - -/**************************************************************************** - * Public Data - ****************************************************************************/ - /**************************************************************************** * Private Functions ****************************************************************************/ +/**************************************************************************** + * Name: mfs_rwbuf_check_page + * + * Description: + * Verify that page belongs to a good NAND block before it is accessed + * through the shared read/write buffer. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The page number to validate. + * + * Returned Value: + * Zero (OK) is returned if page is valid and its block is not marked bad. + * A negated errno value is returned on invalid input or if the page maps + * to a bad block. + * + ****************************************************************************/ + +static int mfs_rwbuf_check_page(FAR struct mfs_sb_s *sb, mfs_t page) +{ + mfs_t block; + int ret; + + if (sb == NULL || sb->mtd == NULL) + { + return -EINVAL; + } + + if (page >= MFS_PAGE_COUNT(sb)) + { + return -EINVAL; + } + + block = page / MFS_PAGES_PER_BLOCK(sb); + + ret = mfs_is_bad_block(sb, block); + if (ret < 0) + { + return ret; + } + + return ret == 0 ? OK : -EIO; +} + +/**************************************************************************** + * Name: mfs_rwbuf_load + * + * Description: + * Load one page into the shared read/write buffer, syncing any dirty + * contents already cached there first. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The page number to load. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned if + * validation, syncing, or the underlying read fails. + * + ****************************************************************************/ + +static int mfs_rwbuf_load(FAR struct mfs_sb_s *sb, mfs_t page) +{ + ssize_t nread; + int ret; + + if (sb == NULL || sb->rwbuf == NULL) + { + return -EINVAL; + } + + if (sb->rwvalid && sb->rwpage == page) + { + return OK; + } + + ret = mfs_rwbuf_sync(sb); + if (ret < 0) + { + return ret; + } + + ret = mfs_rwbuf_check_page(sb, page); + if (ret < 0) + { + return ret; + } + + nread = MTD_BREAD(sb->mtd, page, 1, sb->rwbuf); + if (nread < 0) + { + return (int)nread; + } + + if (nread != 1) + { + return -EIO; + } + + sb->rwpage = page; + sb->rwvalid = true; + sb->rwdirty = false; + return OK; +} + /**************************************************************************** * Public Functions ****************************************************************************/ -int mfs_isbadblk(FAR const struct mfs_sb_s * const sb, mfs_t blk) +/**************************************************************************** + * Name: mfs_is_bad_block + * + * Description: + * Query the backing MTD device to learn whether blk is marked bad. + * + * Input Parameters: + * sb - The mounted file system instance. + * blk - The block number to test. + * + * Returned Value: + * Zero (OK) is returned if blk is usable. A positive non-zero value may + * be returned if the MTD reports blk as bad. A negated errno value is + * returned on invalid input or device failure. + * + ****************************************************************************/ + +int mfs_is_bad_block(FAR const struct mfs_sb_s *sb, mfs_t blk) { - if (predict_false(blk > MFS_NBLKS(sb))) + int ret; + + if (sb == NULL || sb->mtd == NULL) { + ferr("invalid args\n"); return -EINVAL; } - return MTD_ISBAD(MFS_MTD(sb), blk); + if (blk >= MFS_BLOCK_COUNT(sb)) + { + ferr("invalid block\n"); + return -EINVAL; + } + + ret = MTD_ISBAD(sb->mtd, blk); + if (ret < 0 && ret != -ENOSYS) + { + ferr("MTD_ISBAD failed: %d\n", ret); + } + + return ret == -ENOSYS ? 0 : ret; } -int mfs_markbadblk(FAR const struct mfs_sb_s * const sb, mfs_t blk) +/**************************************************************************** + * Name: mfs_rwbuf_sync + * + * Description: + * Flush dirty contents from the shared read/write buffer to its cached + * page. + * + * Input Parameters: + * sb - The mounted file system instance. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned if + * the buffered page cannot be written back. + * + ****************************************************************************/ + +int mfs_rwbuf_sync(FAR struct mfs_sb_s *sb) { - if (predict_false(blk > MFS_NBLKS(sb))) + ssize_t nwritten; + + if (sb == NULL) { + ferr("invalid sb\n"); return -EINVAL; } - return MTD_MARKBAD(MFS_MTD(sb), blk); + if (sb->rwbuf == NULL || !sb->rwvalid || !sb->rwdirty) + { + return OK; + } + + nwritten = MTD_BWRITE(sb->mtd, sb->rwpage, 1, sb->rwbuf); + if (nwritten < 0) + { + ferr("MTD_BWRITE failed: %zd\n", nwritten); + return (int)nwritten; + } + + if (nwritten != 1) + { + ferr("short write: %zd\n", nwritten); + return -EIO; + } + + sb->rwdirty = false; + return OK; } -/* NOTE: These functions do not update the block allocator's state nor do - * they enforce it. - */ +/**************************************************************************** + * Name: mfs_rwbuf_invalidate + * + * Description: + * Drop any cached page association from the shared read/write buffer. + * + * Input Parameters: + * sb - The mounted file system instance. + * + * Returned Value: + * None. + * + ****************************************************************************/ -ssize_t mfs_write_page(FAR const struct mfs_sb_s * const sb, - FAR const char *data, const mfs_t datalen, - const off_t page, const mfs_t pgoff) +void mfs_rwbuf_invalidate(FAR struct mfs_sb_s *sb) { - int ret = OK; - - if (predict_false(page > MFS_NPGS(sb) || pgoff >= MFS_PGSZ(sb))) + if (sb == NULL) { + return; + } + + sb->rwpage = MFS_LOCATION_INVALID; + sb->rwvalid = false; + sb->rwdirty = false; +} + +/**************************************************************************** + * Name: mfs_rwbuf_prepare_write + * + * Description: + * Sync any dirty buffered page and then invalidate the cache so the next + * write starts from a clean state. + * + * Input Parameters: + * sb - The mounted file system instance. + * + * Returned Value: + * Zero (OK) is returned on success. A negated errno value is returned if + * syncing the buffered page fails. + * + ****************************************************************************/ + +int mfs_rwbuf_prepare_write(FAR struct mfs_sb_s *sb) +{ + int ret; + + if (sb == NULL) + { + ferr("invalid sb\n"); return -EINVAL; } - memcpy(MFS_RWBUF(sb) + pgoff, data, MIN(datalen, MFS_PGSZ(sb) - pgoff)); - - ret = MTD_BWRITE(MFS_MTD(sb), page, 1, MFS_RWBUF(sb)); - if (predict_false(ret < 0)) + ret = mfs_rwbuf_sync(sb); + if (ret < 0) { - goto errout_with_reset; + ferr("mfs_rwbuf_sync failed: %d\n", ret); + return ret; } -errout_with_reset: - memset(MFS_RWBUF(sb), 0, MFS_PGSZ(sb)); + mfs_rwbuf_invalidate(sb); + return OK; +} + +/**************************************************************************** + * Name: mfs_rwbuf_discard_page + * + * Description: + * Invalidate the shared read/write buffer if it currently caches page. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The page whose cached state should be discarded. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +void mfs_rwbuf_discard_page(FAR struct mfs_sb_s *sb, mfs_t page) +{ + if (sb == NULL || !sb->rwvalid) + { + return; + } + + if (sb->rwpage == page) + { + mfs_rwbuf_invalidate(sb); + } +} + +/**************************************************************************** + * Name: mfs_rwbuf_discard_block + * + * Description: + * Invalidate the shared read/write buffer if it currently caches any page + * that belongs to block. + * + * Input Parameters: + * sb - The mounted file system instance. + * block - The block whose cached state should be discarded. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +void mfs_rwbuf_discard_block(FAR struct mfs_sb_s *sb, mfs_t block) +{ + if (sb == NULL || block >= MFS_BLOCK_COUNT(sb) || !sb->rwvalid) + { + return; + } + + if (sb->rwpage / MFS_PAGES_PER_BLOCK(sb) == block) + { + mfs_rwbuf_invalidate(sb); + } +} + +/**************************************************************************** + * Name: mfs_write_page + * + * Description: + * Stage one page for writing through the shared read/write buffer. The + * page becomes dirty and is written later by mfs_rwbuf_sync(). + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The page number to update. + * buffer - The page-sized data to stage. + * + * Returned Value: + * One is returned on success. A negated errno value is returned if the + * inputs are invalid, the page cannot be cached, or an earlier dirty page + * cannot be synced. + * + ****************************************************************************/ + +ssize_t mfs_write_page(FAR struct mfs_sb_s *sb, mfs_t page, + FAR const uint8_t *buffer) +{ + int ret; + + if (sb == NULL || sb->mtd == NULL || sb->rwbuf == NULL || buffer == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (page >= MFS_PAGE_COUNT(sb)) + { + ferr("invalid page\n"); + return -EINVAL; + } + + if (!sb->rwvalid || sb->rwpage != page) + { + ret = mfs_rwbuf_sync(sb); + if (ret < 0) + { + ferr("mfs_rwbuf_sync failed: %d\n", ret); + return ret; + } + + ret = mfs_rwbuf_check_page(sb, page); + if (ret < 0) + { + ferr("mfs_rwbuf_check_page failed: %d\n", ret); + return ret; + } + + sb->rwpage = page; + sb->rwvalid = true; + sb->rwdirty = false; + } + + if (buffer != sb->rwbuf) + { + memcpy(sb->rwbuf, buffer, MFS_PAGE_SIZE(sb)); + } + + sb->rwdirty = true; + return 1; +} + +/**************************************************************************** + * Name: mfs_read_page + * + * Description: + * Load one page through the shared read/write buffer and copy it to the + * caller if needed. + * + * Input Parameters: + * sb - The mounted file system instance. + * page - The page number to read. + * buffer - The page-sized buffer that receives the data. + * + * Returned Value: + * One is returned on success. A negated errno value is returned if the + * inputs are invalid or the page cannot be loaded. + * + ****************************************************************************/ + +ssize_t mfs_read_page(FAR struct mfs_sb_s *sb, mfs_t page, + FAR uint8_t *buffer) +{ + int ret; + + if (sb == NULL || sb->mtd == NULL || sb->rwbuf == NULL || buffer == NULL) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (page >= MFS_PAGE_COUNT(sb)) + { + ferr("invalid page\n"); + return -EINVAL; + } + + ret = mfs_rwbuf_load(sb, page); + if (ret < 0) + { + ferr("mfs_rwbuf_load failed: %d\n", ret); + return ret; + } + + if (buffer != sb->rwbuf) + { + memcpy(buffer, sb->rwbuf, MFS_PAGE_SIZE(sb)); + } + + return 1; +} + +/**************************************************************************** + * Name: mfs_erase_blocks + * + * Description: + * Erase a contiguous range of blocks and discard any cached page that + * falls inside that range. + * + * Input Parameters: + * sb - The mounted file system instance. + * startblk - The first block to erase. + * nblocks - The number of blocks to erase. + * + * Returned Value: + * The underlying MTD erase result is returned on success. A negated + * errno value is returned on invalid input. + * + ****************************************************************************/ + +int mfs_erase_blocks(FAR struct mfs_sb_s *sb, mfs_t startblk, + size_t nblocks) +{ + int ret; + + if (sb == NULL || sb->mtd == NULL || nblocks == 0) + { + ferr("invalid args\n"); + return -EINVAL; + } + + if (startblk >= MFS_BLOCK_COUNT(sb) || + nblocks > MFS_BLOCK_COUNT(sb) || + startblk > MFS_BLOCK_COUNT(sb) - nblocks) + { + ferr("invalid erase range\n"); + return -EINVAL; + } + + if (sb->rwvalid) + { + mfs_t endblk = startblk + nblocks; + mfs_t curblk = sb->rwpage / MFS_PAGES_PER_BLOCK(sb); + + if (curblk >= startblk && curblk < endblk) + { + mfs_rwbuf_invalidate(sb); + } + } + + ret = MTD_ERASE(sb->mtd, startblk, nblocks); + if (ret < 0) + { + ferr("MTD_ERASE failed: %d\n", ret); + } return ret; } - -ssize_t mfs_read_page(FAR const struct mfs_sb_s * const sb, - FAR char *data, const mfs_t datalen, const off_t page, - const mfs_t pgoff) -{ - int ret = OK; - - if (predict_false(page > MFS_NPGS(sb) || pgoff >= MFS_PGSZ(sb))) - { - return -EINVAL; - } - - ret = MTD_BREAD(MFS_MTD(sb), page, 1, MFS_RWBUF(sb)); - if (predict_false(ret < 0)) - { - goto errout_with_reset; - } - - memcpy(data, MFS_RWBUF(sb) + pgoff, MIN(datalen, MFS_PGSZ(sb) - pgoff)); - -errout_with_reset: - memset(MFS_RWBUF(sb), 0, MFS_PGSZ(sb)); - - return ret; -} - -int mfs_erase_blk(FAR const struct mfs_sb_s * const sb, const off_t blk) -{ - if (predict_false(blk > MFS_NBLKS(sb))) - { - return -EINVAL; - } - - return MTD_ERASE(MFS_MTD(sb), blk, 1); -} - -int mfs_erase_nblks(FAR const struct mfs_sb_s * const sb, const off_t blk, - const size_t n) -{ - if (predict_false(blk + n > MFS_NBLKS(sb))) - { - return -EINVAL; - } - - return MTD_ERASE(MFS_MTD(sb), blk, n); -} diff --git a/fs/mnemofs/mnemofs_util.c b/fs/mnemofs/mnemofs_util.c deleted file mode 100644 index b98661ee88e..00000000000 --- a/fs/mnemofs/mnemofs_util.c +++ /dev/null @@ -1,263 +0,0 @@ -/**************************************************************************** - * fs/mnemofs/mnemofs_util.c - * - * SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. The - * ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Alternatively, the contents of this file may be used under the terms of - * the BSD-3-Clause license: - * - * SPDX-License-Identifier: BSD-3-Clause - * - * Copyright (c) 2024 Saurav Pal - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the author nor the names of its contributors may - * be used to endorse or promote products derived from this software - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - ****************************************************************************/ - -/**************************************************************************** - * Included Files - ****************************************************************************/ - -#include "mnemofs.h" - -#include -#include - -/**************************************************************************** - * Pre-processor Definitions - ****************************************************************************/ - -/**************************************************************************** - * Private Types - ****************************************************************************/ - -/**************************************************************************** - * Private Function Prototypes - ****************************************************************************/ - -/**************************************************************************** - * Private Data - ****************************************************************************/ - -/**************************************************************************** - * Public Data - ****************************************************************************/ - -/**************************************************************************** - * Private Functions - ****************************************************************************/ - -/**************************************************************************** - * Public Functions - ****************************************************************************/ - -uint16_t mfs_hash(FAR const char *arr, ssize_t len) -{ - ssize_t l = 0; - ssize_t r = len - 1; - uint32_t hash = 0; - - while (l <= r) - { - hash += arr[l] * arr[r] * (l + 1) * (r + 1); - l++; - r--; - hash %= (1 << MFS_HASHSZ); - } - - finfo("Hash calculated for size %zd to be %" PRIi32, len, hash); - - return hash; -} - -FAR char *mfs_ser_8(const uint8_t n, FAR char * const out) -{ - *out = n; - return out + 1; -} - -FAR const char *mfs_deser_8(FAR const char * const in, FAR uint8_t *n) -{ - *n = in[0]; - return in + 1; -} - -FAR char *mfs_ser_16(const uint16_t n, FAR char * const out) -{ - memcpy(out, &n, 2); - return out + 2; -} - -FAR const char *mfs_deser_16(FAR const char * const in, FAR uint16_t *n) -{ - memcpy(n, in, 2); - return in + 2; -} - -FAR char *mfs_ser_str(FAR const char * const str, const mfs_t len, - FAR char * const out) -{ - memcpy(out, str, len); - return out + len; -} - -FAR const char *mfs_deser_str(FAR const char * const in, - FAR char * const str, const mfs_t len) -{ - memcpy(str, in, len); - str[len] = 0; - return in + len; -} - -FAR char *mfs_ser_mfs(const mfs_t n, FAR char * const out) -{ - memcpy(out, &n, 4); - return out + 4; -} - -FAR const char *mfs_deser_mfs(FAR const char * const in, FAR mfs_t * const n) -{ - memcpy(n, in, 4); - return in + 4; -} - -FAR char *mfs_ser_64(const uint64_t n, FAR char * const out) -{ - memcpy(out, &n, 8); - return out + 8; -} - -FAR const char *mfs_deser_64(FAR const char * const in, - FAR uint64_t * const n) -{ - memcpy(n, in, 8); - return in + 8; -} - -FAR char *mfs_ser_ctz(FAR const struct mfs_ctz_s * const x, - FAR char * const out) -{ - FAR char *o = out; - - o = mfs_ser_mfs(x->pg_e, o); - o = mfs_ser_mfs(x->idx_e, o); - return o; -} - -FAR const char *mfs_deser_ctz(FAR const char * const in, - FAR struct mfs_ctz_s * const x) -{ - FAR const char *i = in; - - i = mfs_deser_mfs(i, &x->pg_e); - i = mfs_deser_mfs(i, &x->idx_e); - return i; -} - -FAR char *mfs_ser_path(FAR const struct mfs_path_s * const x, - FAR char * const out) -{ - FAR char *o = out; - - o = mfs_ser_ctz(&x->ctz, o); - o = mfs_ser_mfs(x->off, o); - o = mfs_ser_mfs(x->sz, o); - return o; -} - -FAR const char *mfs_deser_path(FAR const char * const in, - FAR struct mfs_path_s * const x) -{ - FAR const char *i = in; - - i = mfs_deser_ctz(i, &x->ctz); - i = mfs_deser_mfs(i, &x->off); - i = mfs_deser_mfs(i, &x->sz); - return i; -} - -FAR char *mfs_ser_timespec(FAR const struct timespec * const x, - FAR char * const out) -{ - FAR char *o = out; - - o = mfs_ser_64(x->tv_sec, o); - o = mfs_ser_64(x->tv_nsec, o); - - return o; -} - -FAR const char *mfs_deser_timespec(FAR const char * const in, - FAR struct timespec * const x) -{ - uint64_t tmp; - FAR const char *i = in; - - i = mfs_deser_64(i, &tmp); - x->tv_sec = tmp; - i = mfs_deser_64(i, &tmp); - x->tv_nsec = tmp; - - return i; -} - -mfs_t mfs_v2n(mfs_t n) -{ - /* https://math.stackexchange.com/a/1835555 */ - - return (n & (~(n - 1))); -} - -mfs_t mfs_set_msb(mfs_t n) -{ - return 31 - mfs_clz(n); -} - -bool mfs_ctz_eq(FAR const struct mfs_ctz_s * const a, - FAR const struct mfs_ctz_s * const b) -{ - return a->idx_e == b->idx_e && a->pg_e == b->pg_e; -} - -bool mfs_path_eq(FAR const struct mfs_path_s * const a, - FAR const struct mfs_path_s * const b) -{ - return mfs_ctz_eq(&a->ctz, &b->ctz) && (a->off == b->off) - && (a->sz == b->sz); -}