/**************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 cromfs_operations = { cromfs_open, /* open */ cromfs_close, /* close */ cromfs_read, /* read */ NULL, /* write */ NULL, /* seek */ cromfs_ioctl, /* ioctl */ NULL, /* truncate */ NULL, /* mmap */ 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 && filep->f_inode != 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 = (FAR struct cromfs_file_s *)kmm_zalloc(sizeof(struct cromfs_file_s)); if (ff == NULL) { return -ENOMEM; } /* Create a file buffer to support partial sector accesses */ ff->ff_buffer = (FAR uint8_t *)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 && filep->f_inode != 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 && filep->f_inode != 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 = (FAR struct cromfs_volume_s *)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 = (FAR uint8_t *)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 && filep->f_inode != 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. */ memset(buf, 0, sizeof(struct statfs)); 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 */