/**************************************************************************** * fs/mnemofs/mnemofs.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 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 "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 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); /**************************************************************************** * Private Data ****************************************************************************/ /**************************************************************************** * 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 */ mnemofs_close, /* close */ mnemofs_read, /* read */ mnemofs_write, /* write */ mnemofs_seek, /* seek */ mnemofs_ioctl, /* ioctl */ NULL, /* mmap */ mnemofs_truncate, /* truncate */ 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 */ mnemofs_rename, /* rename */ 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 comitting 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", "Curretn 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; /* Emtpy 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; }