| /**************************************************************************** |
| * fs/cromfs/fs_cromfs.c |
| * |
| * 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. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <sys/types.h> |
| #include <sys/statfs.h> |
| #include <sys/stat.h> |
| |
| #include <inttypes.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <lzf.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/fs/fs.h> |
| #include <nuttx/fs/ioctl.h> |
| |
| #include "cromfs.h" |
| |
| #if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_CROMFS) |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define CROMFS_MAX_LINKS 64 |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct cromfs_dir_s |
| { |
| struct fs_dirent_s cr_base; /* VFS directory structure */ |
| uint32_t cr_firstoffset; /* Offset to the first entry in the directory */ |
| uint32_t cr_curroffset; /* Current offset into the directory contents */ |
| }; |
| |
| /* This structure represents an open, regular file */ |
| |
| struct cromfs_file_s |
| { |
| FAR const struct cromfs_node_s *ff_node; /* The open file node */ |
| uint32_t ff_offset; /* Cached block offset (zero means none) */ |
| uint16_t ff_ulen; /* Length of decompressed data in cache */ |
| FAR uint8_t *ff_buffer; /* Cached, decompressed data */ |
| }; |
| |
| /* This is the form of the callback from cromfs_foreach_node(): */ |
| |
| typedef CODE int (*cromfs_foreach_t)(FAR const struct cromfs_volume_s *fs, |
| FAR const struct cromfs_node_s *node, |
| uint32_t offset, FAR void *arg); |
| |
| /* The cromfs_nodeinfo_s structure is an abbreviated version of |
| * cromfs_node_s structure. |
| */ |
| |
| struct cromfs_nodeinfo_s |
| { |
| uint16_t ci_mode; /* File type, attributes, and access mode bits */ |
| uint32_t ci_size; /* Size of the uncompressed data (in bytes) */ |
| uint32_t ci_child; /* Value associated with the directory file type */ |
| }; |
| |
| /* This is the form of the argument provided to the cromfs_compare_node() |
| * callback. |
| */ |
| |
| struct cromfs_comparenode_s |
| { |
| FAR struct cromfs_nodeinfo_s *info; /* Location to return the node info */ |
| FAR const char *relpath; /* Full relative path */ |
| FAR const char *segment; /* Reference to start of the |
| * path segment. */ |
| uint32_t offset; /* Physical offset in ROM */ |
| uint16_t seglen; /* Length of the next path segment */ |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Helpers */ |
| |
| static FAR void *cromfs_offset2addr(FAR const struct cromfs_volume_s *fs, |
| uint32_t offset); |
| static uint32_t cromfs_addr2offset(FAR const struct cromfs_volume_s *fs, |
| FAR const void *addr); |
| static int cromfs_follow_link(FAR const struct cromfs_volume_s *fs, |
| FAR const struct cromfs_node_s **ppnode, bool follow, |
| FAR struct cromfs_node_s *newnode); |
| static int cromfs_foreach_node(FAR const struct cromfs_volume_s *fs, |
| FAR const struct cromfs_node_s *node, |
| bool follow, cromfs_foreach_t callback, FAR void *arg); |
| static uint16_t cromfs_seglen(FAR const char *relpath); |
| static int cromfs_child_node(FAR const struct cromfs_volume_s *fs, |
| FAR const struct cromfs_node_s *node, |
| FAR struct cromfs_nodeinfo_s *info); |
| static int cromfs_compare_node(FAR const struct cromfs_volume_s *fs, |
| FAR const struct cromfs_node_s *node, uint32_t offset, |
| FAR void *arg); |
| static int cromfs_find_node(FAR const struct cromfs_volume_s *fs, |
| FAR const char *relpath, |
| FAR struct cromfs_nodeinfo_s *info, |
| FAR uint32_t *offset); |
| |
| /* Common file system methods */ |
| |
| static int cromfs_open(FAR struct file *filep, const char *relpath, |
| int oflags, mode_t mode); |
| static int cromfs_close(FAR struct file *filep); |
| static ssize_t cromfs_read(FAR struct file *filep, |
| char *buffer, size_t buflen); |
| static int cromfs_ioctl(FAR struct file *filep, |
| int cmd, unsigned long arg); |
| |
| static int cromfs_dup(FAR const struct file *oldp, |
| FAR struct file *newp); |
| static int cromfs_fstat(FAR const struct file *filep, |
| FAR struct stat *buf); |
| |
| static int cromfs_opendir(FAR struct inode *mountpt, |
| FAR const char *relpath, FAR struct fs_dirent_s **dir); |
| static int cromfs_closedir(FAR struct inode *mountpt, |
| FAR struct fs_dirent_s *dir); |
| static int cromfs_readdir(FAR struct inode *mountpt, |
| FAR struct fs_dirent_s *dir, |
| FAR struct dirent *entry); |
| static int cromfs_rewinddir(FAR struct inode *mountpt, |
| FAR struct fs_dirent_s *dir); |
| |
| static int cromfs_bind(FAR struct inode *blkdriver, |
| FAR const void *data, FAR void **handle); |
| static int cromfs_unbind(FAR void *handle, FAR struct inode **blkdriver, |
| unsigned int flags); |
| static int cromfs_statfs(FAR struct inode *mountpt, |
| FAR struct statfs *buf); |
| |
| static int cromfs_stat(FAR struct inode *mountpt, |
| FAR const char *relpath, FAR struct stat *buf); |
| |
| /**************************************************************************** |
| * Public Data |
| ****************************************************************************/ |
| |
| /* See fs_mount.c -- this structure is explicitly extern'ed there. |
| * We use the old-fashioned kind of initializers so that this will compile |
| * with any compiler. |
| */ |
| |
| const struct mountpt_operations g_cromfs_operations = |
| { |
| cromfs_open, /* open */ |
| cromfs_close, /* close */ |
| cromfs_read, /* read */ |
| NULL, /* write */ |
| NULL, /* seek */ |
| cromfs_ioctl, /* ioctl */ |
| NULL, /* mmap */ |
| NULL, /* truncate */ |
| NULL, /* poll */ |
| |
| NULL, /* sync */ |
| cromfs_dup, /* dup */ |
| cromfs_fstat, /* fstat */ |
| NULL, /* fchstat */ |
| |
| cromfs_opendir, /* opendir */ |
| cromfs_closedir, /* closedir */ |
| cromfs_readdir, /* readdir */ |
| cromfs_rewinddir, /* rewinddir */ |
| |
| cromfs_bind, /* bind */ |
| cromfs_unbind, /* unbind */ |
| cromfs_statfs, /* statfs */ |
| |
| NULL, /* unlink */ |
| NULL, /* mkdir */ |
| NULL, /* rmdir */ |
| NULL, /* rename */ |
| cromfs_stat, /* stat */ |
| NULL /* chstat */ |
| }; |
| |
| /* The CROMFS uses a global, in-memory instance of the file system image |
| * rather than a ROMDISK as does, same the ROMFS file system. This is |
| * primarily because the compression logic needs contiguous, in-memory |
| * data. One consequence of this is that there can only only be a single |
| * CROMFS file system in the build. |
| * |
| * This is the address of the single CROMFS file system image: |
| */ |
| |
| extern const struct cromfs_volume_s g_cromfs_image; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: cromfs_offset2addr |
| * |
| * Description: |
| * Convert an offset into an address in the CROMFS flat memory image. |
| * |
| ****************************************************************************/ |
| |
| static FAR void *cromfs_offset2addr(FAR const struct cromfs_volume_s *fs, |
| uint32_t offset) |
| { |
| /* Zero offset is a special case: It corresponds to a NULL address */ |
| |
| if (offset == 0 || offset >= fs->cv_fsize) |
| { |
| return NULL; |
| } |
| |
| /* The root node lies at the beginning of the CROMFS image so we can |
| * convert the offset into the image by simply adding the the address |
| * of the root node. |
| */ |
| |
| DEBUGASSERT(offset < fs->cv_fsize); |
| return (FAR void *)((FAR uint8_t *)fs + offset); |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_addr2offset |
| * |
| * Description: |
| * Convert a CROMFS flat image address into the file system offset. |
| * |
| ****************************************************************************/ |
| |
| static uint32_t cromfs_addr2offset(FAR const struct cromfs_volume_s *fs, |
| FAR const void *addr) |
| { |
| uintptr_t start; |
| uintptr_t target; |
| uint32_t offset; |
| |
| /* NULL is a specials case: It corresponds to offset zero */ |
| |
| if (addr == NULL) |
| { |
| return 0; |
| } |
| |
| /* Make sure that the address is "after" the start of file system image. */ |
| |
| start = (uintptr_t)fs; |
| target = (uintptr_t)addr; |
| DEBUGASSERT(target >= start); |
| |
| /* Get the non-zero offset and make sure that the offset is within the file |
| * system image. |
| */ |
| |
| offset = target - start; |
| DEBUGASSERT(offset < fs->cv_fsize); |
| return offset; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_follow_link |
| * |
| * Description: |
| * If the node it a hardlink, then follow the node to the final target |
| * node which will be a directory or a file. |
| * |
| ****************************************************************************/ |
| |
| static int cromfs_follow_link(FAR const struct cromfs_volume_s *fs, |
| FAR const struct cromfs_node_s **ppnode, |
| bool follow, |
| FAR struct cromfs_node_s *newnode) |
| { |
| FAR const struct cromfs_node_s *linknode; |
| FAR const struct cromfs_node_s *node = *ppnode; |
| FAR const char *name; |
| int i; |
| |
| /* Loop while we are redirected by hardlinks */ |
| |
| for (i = 0; i < CROMFS_MAX_LINKS; i++) |
| { |
| /* Check for a hard link */ |
| |
| if ((node->cn_mode & S_IFMT) != S_IFLNK) |
| { |
| return OK; |
| } |
| |
| /* Get the link target node */ |
| |
| linknode = (FAR const struct cromfs_node_s *) |
| cromfs_offset2addr(fs, node->u.cn_link); |
| DEBUGASSERT(linknode != NULL); |
| |
| /* Special case: Don't follow either "." or ".." These will generate |
| * loops in both cases. |
| * |
| * REVISIT: This kludge is necessary due to an issue in gencromfs: |
| * The "." entry and ".." refer to the first entry in the directory |
| * list, ".", instead of to the directory entry itself. Hence, we |
| * cannot traverse "." or ".." to determine that these are |
| * directories. NOTE also that there is no root directory entry for |
| * the top "." to refer to. |
| */ |
| |
| name = (FAR const char *)cromfs_offset2addr(fs, node->cn_name); |
| if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) |
| { |
| /* We assume this is a "." directory opener. Create a directory |
| * node on the stack. |
| */ |
| |
| newnode->cn_mode = S_IFDIR | (node->cn_mode & ~S_IFMT); |
| newnode->cn_pad = 0; |
| newnode->cn_name = node->cn_name; |
| newnode->cn_size = 0; |
| newnode->cn_peer = node->cn_peer; |
| newnode->u.cn_child = node->u.cn_child; |
| |
| /* Switch from the original read-only in ROM to the writable copy |
| * in on the stack. |
| */ |
| |
| *ppnode = newnode; |
| return OK; |
| } |
| |
| /* Copy the origin node file name into the writable node copy */ |
| |
| newnode->cn_name = node->cn_name; |
| newnode->cn_pad = 0; |
| |
| /* Copy all attributes of the target node, but retain the hard link |
| * file name and, possibly, the peer node reference. |
| */ |
| |
| newnode->cn_mode = linknode->cn_mode; |
| newnode->cn_size = linknode->cn_size; |
| newnode->u.cn_link = linknode->u.cn_link; |
| |
| /* Copy the peer node offset, changing the peer only if we are |
| * following the hard link in the traversal. |
| */ |
| |
| newnode->cn_peer = follow ? linknode->cn_peer : node->cn_peer; |
| |
| /* Switch from the original read-only in ROM to the writable copy in |
| * on the stack. |
| */ |
| |
| *ppnode = newnode; |
| node = newnode; |
| } |
| |
| return -ELOOP; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_foreach_node |
| * |
| * Description: |
| * Visit each node in the file system, performing the requested callback |
| * for each node. The attributes of the hard link are replaced with the |
| * attributes of the target node. Optionally, traversal can be forced to |
| * follow the hard link paths. |
| * |
| ****************************************************************************/ |
| |
| static int cromfs_foreach_node(FAR const struct cromfs_volume_s *fs, |
| FAR const struct cromfs_node_s *node, |
| bool follow, cromfs_foreach_t callback, |
| FAR void *arg) |
| { |
| FAR const struct cromfs_node_s *pnode; |
| struct cromfs_node_s newnode; |
| uint32_t offset; |
| int ret = OK; |
| |
| /* Traverse all entries in this directory (i.e., following the 'peer' |
| * links). |
| */ |
| |
| pnode = node; |
| offset = cromfs_addr2offset(fs, node); |
| |
| while (pnode != NULL) |
| { |
| /* Follow any hard links */ |
| |
| ret = cromfs_follow_link(fs, &pnode, follow, &newnode); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Perform the callback for the node */ |
| |
| ret = callback(fs, pnode, offset, arg); |
| if (ret != OK) |
| { |
| return ret; |
| } |
| |
| offset = pnode->cn_peer; |
| pnode = (FAR const struct cromfs_node_s *) |
| cromfs_offset2addr(fs, offset); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_seglen |
| ****************************************************************************/ |
| |
| static uint16_t cromfs_seglen(FAR const char *relpath) |
| { |
| FAR char *delimiter; |
| int len; |
| |
| delimiter = strchr(relpath, '/'); |
| if (delimiter == NULL) |
| { |
| len = strlen(relpath); |
| } |
| else |
| { |
| len = (int)((uintptr_t)delimiter - (uintptr_t)relpath); |
| } |
| |
| DEBUGASSERT((unsigned int)len <= UINT16_MAX); |
| return (uint16_t)len; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_child_node |
| ****************************************************************************/ |
| |
| static int cromfs_child_node(FAR const struct cromfs_volume_s *fs, |
| FAR const struct cromfs_node_s *node, |
| FAR struct cromfs_nodeinfo_s *info) |
| { |
| FAR const struct cromfs_node_s *pnode; |
| FAR const struct cromfs_node_s *child; |
| struct cromfs_node_s newnode; |
| uint32_t offset; |
| int ret; |
| |
| /* Get the child node referred by the directory entry */ |
| |
| offset = node->u.cn_child; |
| child = (FAR const struct cromfs_node_s *)cromfs_offset2addr(fs, offset); |
| |
| /* Get the attributes of the child node by following the hard link. This |
| * first node under the directory will be the hard link ".". |
| */ |
| |
| pnode = child; |
| ret = cromfs_follow_link(fs, &pnode, false, &newnode); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| info->ci_mode = pnode->cn_mode; |
| info->ci_size = pnode->cn_size; |
| info->ci_child = offset; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_compare_node |
| ****************************************************************************/ |
| |
| static int cromfs_compare_node(FAR const struct cromfs_volume_s *fs, |
| FAR const struct cromfs_node_s *node, |
| uint32_t offset, FAR void *arg) |
| { |
| FAR struct cromfs_comparenode_s *cpnode; |
| FAR const struct cromfs_node_s *child; |
| FAR char *name; |
| int namlen; |
| int ret; |
| |
| DEBUGASSERT(fs != NULL && node != NULL && arg != NULL); |
| cpnode = (FAR struct cromfs_comparenode_s *)arg; |
| |
| /* Get the name of the node */ |
| |
| name = (FAR char *)cromfs_offset2addr(fs, node->cn_name); |
| namlen = strlen(name); |
| |
| finfo("Compare %s to %s[0-%" PRIu16 "]\n", name, cpnode->segment, |
| cpnode->seglen); |
| |
| /* If the lengths of the name does not match the length of the next path |
| * segment, then this is not the node we are looking for. |
| */ |
| |
| if (namlen != cpnode->seglen) |
| { |
| return 0; /* Keep looking */ |
| } |
| |
| /* The are the same length... are they the same? */ |
| |
| if (strncmp(name, cpnode->segment, namlen) == 0) |
| { |
| FAR const char *segment = cpnode->segment; |
| |
| /* Got it! Was this the last segment of the path? If so, then |
| * the segment length is equal to the length of the remaining |
| * relpath and we should find a NUL terminator at that location. |
| */ |
| |
| if (segment[namlen] == '\0') |
| { |
| /* We have it. Save the terminal node with the final match |
| * and return 1 to stop the traversal. |
| */ |
| |
| #if 1 /* REVISIT: This seems to work, but I don't fully follow the logic. */ |
| if (S_ISDIR(node->cn_mode)) |
| { |
| /* This first node under the directory will be the hard |
| * link ".". |
| */ |
| |
| ret = cromfs_child_node(fs, node, cpnode->info); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| } |
| else |
| { |
| cpnode->info->ci_mode = node->cn_mode; |
| cpnode->info->ci_size = node->cn_size; |
| cpnode->info->ci_child = node->u.cn_child; |
| } |
| #else |
| { |
| /* This first node under the directory will be the hard |
| * link ".". |
| */ |
| |
| ret = cromfs_child_node(fs, node, cpnode->info); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| } |
| #endif |
| |
| cpnode->offset = offset; |
| return 1; |
| } |
| |
| /* A special case is if the path ends in "/". In this case I suppose |
| * we need to interpret the as matching as long as it is a directory? |
| */ |
| |
| if (segment[namlen] == '/' && segment[namlen = 1] == '\0') |
| { |
| cpnode->info->ci_mode = node->cn_mode; |
| cpnode->info->ci_size = node->cn_size; |
| cpnode->info->ci_child = node->u.cn_child; |
| return S_ISDIR(node->cn_mode) ? 1 : -ENOENT; |
| } |
| |
| /* If this is a valid, non-terminal segment on the path, then it must |
| * be a directory. |
| */ |
| |
| if (!S_ISDIR(node->cn_mode)) |
| { |
| /* Terminate the traversal with an error */ |
| |
| return -ENOTDIR; |
| } |
| |
| /* Set up to continue the traversal in the sub-directory. NOTE that |
| * this recurses and could potentially eat up a lot of stack. |
| */ |
| |
| child = (FAR const struct cromfs_node_s *) |
| cromfs_offset2addr(fs, node->u.cn_child); |
| segment = cpnode->segment + cpnode->seglen; |
| |
| /* Skip over any '/' delimiter */ |
| |
| while (*segment == '/') |
| { |
| segment++; |
| } |
| |
| cpnode->segment = segment; |
| cpnode->seglen = cromfs_seglen(segment); |
| DEBUGASSERT(cpnode->seglen > 0); |
| |
| /* Then recurse */ |
| |
| return cromfs_foreach_node(fs, child, true, cromfs_compare_node, |
| cpnode); |
| } |
| |
| return 0; /* Keep looking in this directory */ |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_find_node |
| * |
| * Description: |
| * Find the CROMFS node at the provide mountpoint relative path. |
| * |
| ****************************************************************************/ |
| |
| static int cromfs_find_node(FAR const struct cromfs_volume_s *fs, |
| FAR const char *relpath, |
| FAR struct cromfs_nodeinfo_s *info, |
| FAR uint32_t *offset) |
| { |
| struct cromfs_comparenode_s cpnode; |
| FAR const struct cromfs_node_s *root; |
| int ret; |
| |
| finfo("relpath: %s\n", relpath); |
| |
| /* Get the root node. The root is the entry "." which is a hard link. */ |
| |
| root = (FAR const struct cromfs_node_s *) |
| cromfs_offset2addr(fs, fs->cv_root); |
| |
| /* NULL or empty string refers to the root node */ |
| |
| if (relpath == NULL || relpath[0] == '\0') |
| { |
| struct cromfs_node_s newnode; |
| |
| /* Get the attributes of the root node by following the hard link. |
| * We do this even though the attributes of the root node are well |
| * defined. |
| */ |
| |
| ret = cromfs_follow_link(fs, &root, false, &newnode); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| info->ci_mode = root->cn_mode; |
| info->ci_size = root->cn_size; |
| info->ci_child = root->u.cn_child; |
| *offset = fs->cv_root; |
| return OK; |
| } |
| |
| /* Not the root directory. Relative so it should not begin with '/'. */ |
| |
| if (relpath[0] == '/') |
| { |
| return -EINVAL; |
| } |
| |
| /* Set up for the traversal */ |
| |
| cpnode.info = info; |
| cpnode.relpath = relpath; |
| cpnode.segment = relpath; |
| cpnode.offset = fs->cv_root; |
| cpnode.seglen = (uint16_t)cromfs_seglen(relpath); |
| |
| ret = cromfs_foreach_node(fs, root, false, cromfs_compare_node, &cpnode); |
| if (ret > 0) |
| { |
| *offset = cpnode.offset; |
| return OK; |
| } |
| else if (ret == OK) |
| { |
| return -ENOENT; |
| } |
| else |
| { |
| return ret; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_open |
| ****************************************************************************/ |
| |
| static int cromfs_open(FAR struct file *filep, FAR const char *relpath, |
| int oflags, mode_t mode) |
| { |
| FAR struct inode *inode; |
| FAR const struct cromfs_volume_s *fs; |
| struct cromfs_nodeinfo_s info; |
| FAR struct cromfs_file_s *ff; |
| uint32_t offset; |
| int ret; |
| |
| finfo("Open: %s\n", relpath); |
| |
| /* Sanity checks */ |
| |
| DEBUGASSERT(filep->f_priv == NULL); |
| |
| /* Get the mountpoint inode reference from the file structure and the |
| * volume private data from the inode structure |
| */ |
| |
| inode = filep->f_inode; |
| fs = inode->i_private; |
| |
| DEBUGASSERT(fs != NULL); |
| |
| /* CROMFS is read-only. Any attempt to open with any kind of write |
| * access is not permitted. |
| */ |
| |
| if ((oflags & O_WRONLY) != 0 || (oflags & O_RDONLY) == 0) |
| { |
| ferr("ERROR: Only O_RDONLY supported\n"); |
| return -EACCES; |
| } |
| |
| /* Locate the node for this relative path */ |
| |
| ret = cromfs_find_node(fs, relpath, &info, &offset); |
| if (ret < 0) |
| { |
| /* Nothing exists at that relative path (or a really bad error |
| * occurred) |
| */ |
| |
| return ret; |
| } |
| |
| /* Verify that the node is a regular file */ |
| |
| if (!S_ISREG(info.ci_mode)) |
| { |
| return -EISDIR; |
| } |
| |
| /* Create an instance of the file private date to describe the opened |
| * file. |
| */ |
| |
| ff = kmm_zalloc(sizeof(struct cromfs_file_s)); |
| if (ff == NULL) |
| { |
| return -ENOMEM; |
| } |
| |
| /* Create a file buffer to support partial sector accesses */ |
| |
| ff->ff_buffer = kmm_malloc(fs->cv_bsize); |
| if (!ff->ff_buffer) |
| { |
| kmm_free(ff); |
| return -ENOMEM; |
| } |
| |
| /* Save the node in the open file instance */ |
| |
| ff->ff_node = (FAR const struct cromfs_node_s *) |
| cromfs_offset2addr(fs, offset); |
| |
| /* Save the index as the open-specific state in filep->f_priv */ |
| |
| filep->f_priv = (FAR void *)ff; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_close |
| ****************************************************************************/ |
| |
| static int cromfs_close(FAR struct file *filep) |
| { |
| FAR struct cromfs_file_s *ff; |
| |
| finfo("Closing\n"); |
| DEBUGASSERT(filep->f_priv != NULL); |
| |
| /* Get the open file instance from the file structure */ |
| |
| ff = filep->f_priv; |
| DEBUGASSERT(ff->ff_node != NULL && ff->ff_buffer != NULL); |
| |
| /* Free all resources consumed by the opened file */ |
| |
| kmm_free(ff->ff_buffer); |
| kmm_free(ff); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_read |
| ****************************************************************************/ |
| |
| static ssize_t cromfs_read(FAR struct file *filep, FAR char *buffer, |
| size_t buflen) |
| { |
| FAR struct inode *inode; |
| FAR const struct cromfs_volume_s *fs; |
| FAR struct cromfs_file_s *ff; |
| FAR struct lzf_header_s *currhdr; |
| FAR struct lzf_header_s *nexthdr; |
| FAR uint8_t *dest; |
| FAR const uint8_t *src; |
| off_t fpos; |
| size_t remaining; |
| uint32_t blkoffs; |
| uint16_t ulen; |
| uint16_t clen; |
| unsigned int copysize; |
| unsigned int copyoffs; |
| |
| finfo("Read %zu bytes from offset %jd\n", buflen, (intmax_t)filep->f_pos); |
| DEBUGASSERT(filep->f_priv != NULL); |
| |
| /* Get the mountpoint inode reference from the file structure and the |
| * volume private data from the inode structure |
| */ |
| |
| inode = filep->f_inode; |
| fs = inode->i_private; |
| DEBUGASSERT(fs != NULL); |
| |
| /* Get the open file instance from the file structure */ |
| |
| ff = (FAR struct cromfs_file_s *)filep->f_priv; |
| DEBUGASSERT(ff->ff_node != NULL && ff->ff_buffer != NULL); |
| |
| /* Check for a read past the end of the file */ |
| |
| if (filep->f_pos > ff->ff_node->cn_size) |
| { |
| /* Start read position is past the end of file. Return the end-of- |
| * file indication. |
| */ |
| |
| return 0; |
| } |
| else if ((filep->f_pos + buflen) > ff->ff_node->cn_size) |
| { |
| /* The final read position is past the end of file. Truncate the |
| * read length. |
| */ |
| |
| buflen = ff->ff_node->cn_size - filep->f_pos; |
| } |
| |
| /* Find the compressed block containing the current offset, f_pos */ |
| |
| dest = (FAR uint8_t *)buffer; |
| remaining = buflen; |
| fpos = filep->f_pos; |
| blkoffs = 0; |
| ulen = 0; |
| nexthdr = (FAR struct lzf_header_s *) |
| cromfs_offset2addr(fs, ff->ff_node->u.cn_blocks); |
| |
| /* Look until we find the compressed block containing the start of the |
| * requested data. |
| */ |
| |
| while (remaining > 0) |
| { |
| /* Search for the next block containing the fpos file offset. This is |
| * real search on the first time through but the remaining blocks |
| * should be contiguous so that the logic should not loop. |
| * |
| */ |
| |
| do |
| { |
| uint32_t blksize; |
| |
| /* Go to the next block */ |
| |
| currhdr = nexthdr; |
| blkoffs += ulen; |
| |
| if (currhdr->lzf_type == LZF_TYPE0_HDR) |
| { |
| FAR struct lzf_type0_header_s *hdr0 = |
| (FAR struct lzf_type0_header_s *)currhdr; |
| |
| ulen = (uint16_t)hdr0->lzf_len[0] << 8 | |
| (uint16_t)hdr0->lzf_len[1]; |
| blksize = (uint32_t)ulen + LZF_TYPE0_HDR_SIZE; |
| } |
| else |
| { |
| FAR struct lzf_type1_header_s * hdr1 = |
| (FAR struct lzf_type1_header_s *)currhdr; |
| |
| ulen = (uint16_t)hdr1->lzf_ulen[0] << 8 | |
| (uint16_t)hdr1->lzf_ulen[1]; |
| clen = (uint16_t)hdr1->lzf_clen[0] << 8 | |
| (uint16_t)hdr1->lzf_clen[1]; |
| blksize = (uint32_t)clen + LZF_TYPE1_HDR_SIZE; |
| } |
| |
| nexthdr = (FAR struct lzf_header_s *) |
| ((FAR uint8_t *)currhdr + blksize); |
| } |
| while (fpos >= (blkoffs + ulen)); |
| |
| /* Check if we need to decompress the next block into the user |
| * buffer. |
| */ |
| |
| if (currhdr->lzf_type == LZF_TYPE0_HDR) |
| { |
| /* Just copy the uncompressed data copy data from image to the |
| * user buffer. |
| */ |
| |
| copyoffs = (blkoffs >= filep->f_pos) ? 0 : filep->f_pos - blkoffs; |
| DEBUGASSERT(ulen > copyoffs); |
| copysize = ulen - copyoffs; |
| |
| if (copysize > remaining) /* Clip to the size really needed */ |
| { |
| copysize = remaining; |
| } |
| |
| src = (FAR const uint8_t *)currhdr + LZF_TYPE0_HDR_SIZE; |
| memcpy(dest, &src[copyoffs], copysize); |
| |
| finfo("blkoffs=%" PRIu32 " ulen=%" PRIu16 " copysize=%u\n", |
| blkoffs, ulen, copysize); |
| } |
| else |
| { |
| /* If the source of the data is at the beginning of the compressed |
| * data buffer and if the uncompressed data would not overrun the |
| * buffer, then we can decompress directly into the user buffer. |
| */ |
| |
| if (filep->f_pos <= blkoffs && ulen <= remaining) |
| { |
| uint32_t voloffs; |
| |
| copyoffs = 0; |
| copysize = ulen; |
| |
| /* Get the address and offset in the CROMFS image to obtain |
| * the data. Check if we already have this offset in the |
| * cache. |
| */ |
| |
| src = (FAR const uint8_t *)currhdr + LZF_TYPE1_HDR_SIZE; |
| voloffs = cromfs_addr2offset(fs, src); |
| if (voloffs != ff->ff_offset) |
| { |
| unsigned int decomplen; |
| |
| decomplen = lzf_decompress(src, clen, dest, fs->cv_bsize); |
| |
| ff->ff_offset = voloffs; |
| ff->ff_ulen = decomplen; |
| } |
| |
| finfo("voloffs=%" PRIu32 " blkoffs=%" PRIu32 |
| " ulen=%" PRIu16 " ff_offset=%" PRIu32 " copysize=%u\n", |
| voloffs, blkoffs, ulen, ff->ff_offset, copysize); |
| DEBUGASSERT(ff->ff_ulen >= copysize); |
| } |
| else |
| { |
| uint32_t voloffs; |
| |
| /* No, we will need to decompress into the our intermediate |
| * decompression buffer. |
| */ |
| |
| copyoffs = (blkoffs >= filep->f_pos) ? |
| 0 : filep->f_pos - blkoffs; |
| DEBUGASSERT(ulen > copyoffs); |
| copysize = ulen - copyoffs; |
| |
| if (copysize > remaining) /* Clip to the size really needed */ |
| { |
| copysize = remaining; |
| } |
| |
| DEBUGASSERT((copyoffs + copysize) <= fs->cv_bsize); |
| |
| src = (FAR const uint8_t *)currhdr + LZF_TYPE1_HDR_SIZE; |
| voloffs = cromfs_addr2offset(fs, src); |
| if (voloffs != ff->ff_offset) |
| { |
| unsigned int decomplen; |
| |
| decomplen = lzf_decompress(src, clen, ff->ff_buffer, |
| fs->cv_bsize); |
| |
| ff->ff_offset = voloffs; |
| ff->ff_ulen = decomplen; |
| } |
| |
| finfo("voloffs=%" PRIu32 " blkoffs=%" PRIu32 " ulen=%" PRIu16 |
| " clen=%" PRIu16 " ff_offset=%" PRIu32 |
| " copyoffs=%u copysize=%u\n", |
| voloffs, blkoffs, ulen, clen, ff->ff_offset, |
| copyoffs, copysize); |
| DEBUGASSERT(ff->ff_ulen >= (copyoffs + copysize)); |
| |
| /* Then copy to user buffer */ |
| |
| memcpy(dest, &ff->ff_buffer[copyoffs], copysize); |
| } |
| } |
| |
| /* Adjust pointers counts and offset */ |
| |
| dest += copysize; |
| remaining -= copysize; |
| fpos += copysize; |
| } |
| |
| /* Update the file pointer */ |
| |
| filep->f_pos = fpos; |
| return buflen; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_ioctl |
| ****************************************************************************/ |
| |
| static int cromfs_ioctl(FAR struct file *filep, int cmd, unsigned long arg) |
| { |
| finfo("cmd: %d arg: %08lx\n", cmd, arg); |
| |
| /* No IOCTL commands yet supported */ |
| |
| return -ENOTTY; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_dup |
| * |
| * Description: |
| * Duplicate open file data in the new file structure. |
| * |
| ****************************************************************************/ |
| |
| static int cromfs_dup(FAR const struct file *oldp, FAR struct file *newp) |
| { |
| FAR struct cromfs_volume_s *fs; |
| FAR struct cromfs_file_s *oldff; |
| FAR struct cromfs_file_s *newff; |
| |
| finfo("Dup %p->%p\n", oldp, newp); |
| DEBUGASSERT(oldp->f_priv != NULL && oldp->f_inode != NULL && |
| newp->f_priv == NULL && newp->f_inode != NULL); |
| |
| /* Recover our private data from the struct file instance */ |
| |
| fs = oldp->f_inode->i_private; |
| DEBUGASSERT(fs != NULL); |
| |
| /* Get the open file instance from the file structure */ |
| |
| oldff = oldp->f_priv; |
| DEBUGASSERT(oldff->ff_node != NULL && oldff->ff_buffer != NULL); |
| |
| /* Allocate and initialize an new open file instance referring to the |
| * same node. |
| */ |
| |
| newff = kmm_zalloc(sizeof(struct cromfs_file_s)); |
| if (newff == NULL) |
| { |
| return -ENOMEM; |
| } |
| |
| /* Create a file buffer to support partial sector accesses */ |
| |
| newff->ff_buffer = kmm_malloc(fs->cv_bsize); |
| if (newff->ff_buffer == NULL) |
| { |
| kmm_free(newff); |
| return -ENOMEM; |
| } |
| |
| /* Save the node in the open file instance */ |
| |
| newff->ff_node = oldff->ff_node; |
| |
| /* Copy the index from the old to the new file structure */ |
| |
| newp->f_priv = newff; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_fstat |
| * |
| * Description: |
| * Obtain information about an open file associated with the file |
| * descriptor 'fd', and will write it to the area pointed to by 'buf'. |
| * |
| ****************************************************************************/ |
| |
| static int cromfs_fstat(FAR const struct file *filep, FAR struct stat *buf) |
| { |
| FAR struct inode *inode; |
| FAR struct cromfs_volume_s *fs; |
| FAR struct cromfs_file_s *ff; |
| uint32_t fsize; |
| uint32_t bsize; |
| |
| /* Sanity checks */ |
| |
| DEBUGASSERT(filep->f_priv != NULL); |
| |
| /* Get the mountpoint inode reference from the file structure and the |
| * volume private data from the inode structure |
| */ |
| |
| ff = filep->f_priv; |
| DEBUGASSERT(ff->ff_node != NULL && ff->ff_buffer != NULL); |
| |
| inode = filep->f_inode; |
| fs = inode->i_private; |
| |
| /* Return the stat info */ |
| |
| fsize = ff->ff_node->cn_size; |
| bsize = fs->cv_bsize; |
| |
| buf->st_mode = ff->ff_node->cn_mode; |
| buf->st_size = fsize; |
| buf->st_blksize = bsize; |
| buf->st_blocks = (fsize + (bsize - 1)) / bsize; |
| buf->st_atime = 0; |
| buf->st_mtime = 0; |
| buf->st_ctime = 0; |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_opendir |
| * |
| * Description: |
| * Open a directory for read access |
| * |
| ****************************************************************************/ |
| |
| static int cromfs_opendir(FAR struct inode *mountpt, FAR const char *relpath, |
| FAR struct fs_dirent_s **dir) |
| { |
| FAR const struct cromfs_volume_s *fs; |
| FAR struct cromfs_dir_s *cdir; |
| FAR struct cromfs_nodeinfo_s info; |
| uint32_t offset; |
| int ret; |
| |
| finfo("relpath: %s\n", relpath); |
| |
| /* Sanity checks */ |
| |
| DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); |
| |
| /* Recover our private data from the inode instance */ |
| |
| fs = mountpt->i_private; |
| |
| /* Locate the node for this relative path */ |
| |
| ret = cromfs_find_node(fs, relpath, &info, &offset); |
| if (ret < 0) |
| { |
| /* Nothing exists at that relative path (or a really bad error |
| * occurred) |
| */ |
| |
| return ret; |
| } |
| |
| /* Verify that the node is a directory */ |
| |
| if (!S_ISDIR(info.ci_mode)) |
| { |
| return -ENOTDIR; |
| } |
| |
| cdir = kmm_zalloc(sizeof(*cdir)); |
| if (cdir == NULL) |
| { |
| return -ENOMEM; |
| } |
| |
| /* Set the start node and next node to the first entry in the directory */ |
| |
| cdir->cr_firstoffset = info.ci_child; |
| cdir->cr_curroffset = info.ci_child; |
| *dir = &cdir->cr_base; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_closedir |
| * |
| * Description: close directory. |
| * |
| ****************************************************************************/ |
| |
| static int cromfs_closedir(FAR struct inode *mountpt, |
| FAR struct fs_dirent_s *dir) |
| { |
| DEBUGASSERT(mountpt != NULL); |
| kmm_free(dir); |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_readdir |
| * |
| * Description: Read the next directory entry |
| * |
| ****************************************************************************/ |
| |
| static int cromfs_readdir(FAR struct inode *mountpt, |
| FAR struct fs_dirent_s *dir, |
| FAR struct dirent *entry) |
| { |
| FAR const struct cromfs_volume_s *fs; |
| FAR const struct cromfs_node_s *node; |
| FAR struct cromfs_dir_s *cdir; |
| struct cromfs_node_s newnode; |
| FAR char *name; |
| uint32_t offset; |
| int ret; |
| |
| finfo("mountpt: %p dir: %p\n", mountpt, dir); |
| |
| /* Sanity checks */ |
| |
| DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); |
| |
| /* Recover our private data from the inode instance */ |
| |
| fs = mountpt->i_private; |
| cdir = (FAR struct cromfs_dir_s *)dir; |
| |
| /* Have we reached the end of the directory */ |
| |
| offset = cdir->cr_curroffset; |
| if (offset == 0) |
| { |
| /* We signal the end of the directory by returning the |
| * special error -ENOENT |
| */ |
| |
| finfo("Entry %" PRIu32 ": End of directory\n", offset); |
| return -ENOENT; |
| } |
| |
| /* Convert the offset to a node address (assuming that everything is in- |
| * memory) |
| */ |
| |
| node = (FAR const struct cromfs_node_s *)cromfs_offset2addr(fs, offset); |
| if (node == NULL) |
| { |
| /* We signal the end of the directory by returning the |
| * special error -ENOENT |
| */ |
| |
| finfo("Entry %" PRIu32 ": End of directory\n", offset); |
| return -ENOENT; |
| } |
| |
| /* Get the attributes of the node by following the hard link. */ |
| |
| ret = cromfs_follow_link(fs, &node, false, &newnode); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Save the filename and file type */ |
| |
| name = (FAR char *)cromfs_offset2addr(fs, node->cn_name); |
| finfo("Entry %" PRIu32 ": %s\n", offset, name); |
| strlcpy(entry->d_name, name, sizeof(entry->d_name)); |
| |
| switch (node->cn_mode & S_IFMT) |
| { |
| case S_IFDIR: /* Directory */ |
| entry->d_type = DTYPE_DIRECTORY; |
| break; |
| |
| case S_IFREG: /* Regular file */ |
| entry->d_type = DTYPE_FILE; |
| break; |
| |
| case S_IFIFO: /* FIFO */ |
| entry->d_type = DTYPE_FIFO; |
| break; |
| |
| case S_IFCHR: /* Character driver */ |
| entry->d_type = DTYPE_CHR; |
| break; |
| |
| case S_IFBLK: /* Block driver */ |
| entry->d_type = DTYPE_BLK; |
| break; |
| |
| case S_IFMQ: /* Message queue */ |
| entry->d_type = DTYPE_MQ; |
| break; |
| |
| case S_IFSEM: /* Semaphore */ |
| entry->d_type = DTYPE_SEM; |
| break; |
| |
| case S_IFSHM: /* Shared memory */ |
| entry->d_type = DTYPE_SHM; |
| break; |
| |
| case S_IFMTD: /* MTD driver */ |
| entry->d_type = DTYPE_MTD; |
| break; |
| |
| case S_IFSOCK: /* Socket */ |
| entry->d_type = DTYPE_SOCK; |
| break; |
| |
| default: |
| DEBUGPANIC(); |
| entry->d_type = DTYPE_UNKNOWN; |
| break; |
| } |
| |
| /* Set up the next directory entry offset. NOTE that we could use the |
| * standard f_pos instead of our own private fb_index. |
| */ |
| |
| cdir->cr_curroffset = node->cn_peer; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_rewindir |
| * |
| * Description: Reset directory read to the first entry |
| * |
| ****************************************************************************/ |
| |
| static int cromfs_rewinddir(FAR struct inode *mountpt, |
| FAR struct fs_dirent_s *dir) |
| { |
| FAR struct cromfs_dir_s *cdir; |
| |
| finfo("mountpt: %p dir: %p\n", mountpt, dir); |
| |
| cdir = (FAR struct cromfs_dir_s *)dir; |
| cdir->cr_curroffset = cdir->cr_firstoffset; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_bind |
| * |
| * Description: This implements a portion of the mount operation. This |
| * function allocates and initializes the mountpoint private data and |
| * binds the blockdriver inode to the filesystem private data. The final |
| * binding of the private data (containing the blockdriver) to the |
| * mountpoint is performed by mount(). |
| * |
| ****************************************************************************/ |
| |
| static int cromfs_bind(FAR struct inode *blkdriver, const void *data, |
| void **handle) |
| { |
| finfo("blkdriver: %p data: %p handle: %p\n", blkdriver, data, handle); |
| |
| DEBUGASSERT(blkdriver == NULL && handle != NULL); |
| DEBUGASSERT(g_cromfs_image.cv_magic == CROMFS_MAGIC); |
| |
| /* Return the new file system handle */ |
| |
| *handle = (FAR void *)&g_cromfs_image; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_unbind |
| * |
| * Description: This implements the filesystem portion of the umount |
| * operation. |
| * |
| ****************************************************************************/ |
| |
| static int cromfs_unbind(FAR void *handle, FAR struct inode **blkdriver, |
| unsigned int flags) |
| { |
| finfo("handle: %p blkdriver: %p flags: %02x\n", |
| handle, blkdriver, flags); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_statfs |
| * |
| * Description: Return filesystem statistics |
| * |
| ****************************************************************************/ |
| |
| static int cromfs_statfs(struct inode *mountpt, struct statfs *buf) |
| { |
| FAR struct cromfs_volume_s *fs; |
| |
| finfo("mountpt: %p buf: %p\n", mountpt, buf); |
| |
| /* Sanity checks */ |
| |
| DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); |
| |
| /* Recover our private data from the inode instance */ |
| |
| fs = mountpt->i_private; |
| |
| /* Fill in the statfs info. */ |
| |
| buf->f_type = CROMFS_MAGIC; |
| buf->f_namelen = NAME_MAX; |
| buf->f_bsize = fs->cv_bsize; |
| buf->f_blocks = fs->cv_nblocks; |
| buf->f_files = fs->cv_nnodes; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cromfs_stat |
| * |
| * Description: Return information about a file or directory |
| * |
| ****************************************************************************/ |
| |
| static int cromfs_stat(FAR struct inode *mountpt, FAR const char *relpath, |
| FAR struct stat *buf) |
| { |
| FAR const struct cromfs_volume_s *fs; |
| struct cromfs_nodeinfo_s info; |
| uint32_t offset; |
| int ret; |
| |
| finfo("mountpt: %p relpath: %s buf: %p\n", mountpt, relpath, buf); |
| |
| /* Sanity checks */ |
| |
| DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL && buf != NULL); |
| memset(buf, 0, sizeof(struct stat)); |
| |
| /* Recover our private data from the inode instance */ |
| |
| fs = mountpt->i_private; |
| |
| /* Locate the node for this relative path */ |
| |
| ret = cromfs_find_node(fs, relpath, &info, &offset); |
| if (ret >= 0) |
| { |
| /* Return the struct stat info associate with this node */ |
| |
| buf->st_mode = info.ci_mode; |
| buf->st_size = info.ci_size; |
| buf->st_blksize = fs->cv_bsize; |
| buf->st_blocks = (info.ci_size + (fs->cv_bsize - 1)) / fs->cv_bsize; |
| ret = OK; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| #endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_CROMFS */ |