/**************************************************************************** * fs/vfs/fs_dir.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 "inode/inode.h" #include "fs_heap.h" /**************************************************************************** * Private Types ****************************************************************************/ /* For the root pseudo-file system, we need retain only the 'next' inode * need for the next readdir() operation. We hold a reference on this * inode so we know that it will persist until closedir is called. */ struct fs_pseudodir_s { struct fs_dirent_s dir; FAR struct inode *next; }; /**************************************************************************** * Private Functions Prototypes ****************************************************************************/ static int dir_open(FAR struct file *filep); static int dir_close(FAR struct file *filep); static ssize_t dir_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static off_t dir_seek(FAR struct file *filep, off_t offset, int whence); static int dir_ioctl(FAR struct file *filep, int cmd, unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations g_dir_fileops = { dir_open, /* open */ dir_close, /* close */ dir_read, /* read */ NULL, /* write */ dir_seek, /* seek */ dir_ioctl, /* ioctl */ }; static struct inode g_dir_inode = { NULL, NULL, NULL, 1, 0, { &g_dir_fileops }, }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: open_mountpoint * * Description: * Handle the case where the inode to be opened is within a mountpoint. * * Input Parameters: * inode -- the inode of the mountpoint to open * relpath -- the relative path within the mountpoint to open * dir -- the dirent structure to be initialized * * Returned Value: * On success, 0 is returned; Otherwise, a negative errno is returned. * ****************************************************************************/ #ifndef CONFIG_DISABLE_MOUNTPOINT static int open_mountpoint(FAR struct inode *inode, FAR const char *relpath, FAR struct fs_dirent_s **dir) { int ret; /* The inode itself as the 'root' of mounted volume. The actually * directory is at relpath into the mounted filesystem. * * Verify that the mountpoint inode supports the opendir() method. */ if (inode->u.i_mops == NULL || inode->u.i_mops->opendir == NULL || inode->u.i_mops->readdir == NULL) { return -ENOSYS; } ret = inode->u.i_mops->opendir(inode, relpath, dir); if (ret >= 0) { (*dir)->fd_root = inode; } return ret; } #endif /**************************************************************************** * Name: open_pseudodir * * Description: * Handle the case where the inode to be opened is within the top-level * pseudo-file system. * * Input Parameters: * inode -- the inode of the mountpoint to open * dir -- the dirent structure to be initialized * * Returned Value: * On success, 0 is returned; Otherwise, a negative errno is returned. * ****************************************************************************/ static int open_pseudodir(FAR struct inode *inode, FAR struct fs_dirent_s **dir) { FAR struct fs_pseudodir_s *pdir; pdir = fs_heap_zalloc(sizeof(*pdir)); if (pdir == NULL) { return -ENOMEM; } *dir = &pdir->dir; pdir->dir.fd_root = inode; /* Save the inode where we start */ pdir->next = inode->i_child; /* This next node for readdir */ inode_addref(inode->i_child); return 0; } /**************************************************************************** * Name: seek_pseudodir ****************************************************************************/ static off_t seek_pseudodir(FAR struct file *filep, off_t offset) { FAR struct fs_pseudodir_s *pdir = filep->f_priv; FAR struct inode *curr; FAR struct inode *prev; off_t pos; /* Determine a starting point for the seek. If the seek * is "forward" from the current position, then we will * start at the current position. Otherwise, we will * "rewind" to the root dir. */ if (offset < filep->f_pos) { pos = 0; curr = pdir->dir.fd_root->i_child; } else { pos = filep->f_pos; curr = pdir->next; } /* Traverse the peer list starting at the 'root' of the * the list until we find the node at 'offset". If devices * are being registered and unregistered, then this can * be a very unpredictable operation. */ inode_lock(); for (; curr != NULL && pos != offset; pos++, curr = curr->i_peer); /* Now get the inode to vist next time that readdir() is called */ prev = pdir->next; /* The next node to visit (might be null) */ pdir->next = curr; if (curr != NULL) { /* Increment the reference count on this next node */ atomic_fetch_add(&curr->i_crefs, 1); } inode_unlock(); if (prev != NULL) { inode_release(prev); } return pos; } /**************************************************************************** * Name: seek_mountptdir ****************************************************************************/ #ifndef CONFIG_DISABLE_MOUNTPOINT static off_t seek_mountptdir(FAR struct file *filep, off_t offset) { FAR struct fs_dirent_s *dir = filep->f_priv; FAR struct inode *inode = dir->fd_root; struct dirent entry; off_t pos; /* Determine a starting point for the seek. If the seek * is "forward" from the current position, then we will * start at the current position. Otherwise, we will * "rewind" to the root dir. */ if (offset < filep->f_pos) { if (inode->u.i_mops->rewinddir != NULL) { inode->u.i_mops->rewinddir(inode, dir); pos = 0; } else { return -ENOTSUP; } } else { pos = filep->f_pos; } /* This is a brute force approach... we will just read * directory entries until we are at the desired position. */ while (pos < offset) { int ret; ret = inode->u.i_mops->readdir(inode, dir, &entry); if (ret < 0) { return ret; } /* Increment the position on each successful read */ pos++; } return pos; } #endif /**************************************************************************** * Name: read_pseudodir ****************************************************************************/ static int read_pseudodir(FAR struct fs_dirent_s *dir, FAR struct dirent *entry) { FAR struct fs_pseudodir_s *pdir = (FAR struct fs_pseudodir_s *)dir; FAR struct inode *prev; /* Check if we are at the end of the list */ if (pdir->next == NULL) { /* End of file and error conditions are not distinguishable with * readdir. Here we return -ENOENT to signal the end of the directory. */ return -ENOENT; } /* Copy the inode name into the dirent structure */ strlcpy(entry->d_name, pdir->next->i_name, sizeof(entry->d_name)); /* If the node has file operations, we will say that it is a file. */ entry->d_type = DTYPE_UNKNOWN; if (pdir->next->u.i_ops != NULL) { #ifndef CONFIG_DISABLE_MOUNTPOINT if (INODE_IS_BLOCK(pdir->next)) { entry->d_type = DTYPE_BLK; } else if (INODE_IS_MTD(pdir->next)) { entry->d_type = DTYPE_MTD; } else if (INODE_IS_MOUNTPT(pdir->next)) { entry->d_type = DTYPE_DIRECTORY; } else #endif #ifdef CONFIG_PSEUDOFS_SOFTLINKS if (INODE_IS_SOFTLINK(pdir->next)) { entry->d_type = DTYPE_LINK; } else #endif if (INODE_IS_DRIVER(pdir->next)) { entry->d_type = DTYPE_CHR; } else if (INODE_IS_NAMEDSEM(pdir->next)) { entry->d_type = DTYPE_SEM; } else if (INODE_IS_MQUEUE(pdir->next)) { entry->d_type = DTYPE_MQ; } else if (INODE_IS_SHM(pdir->next)) { entry->d_type = DTYPE_SHM; } else if (INODE_IS_PIPE(pdir->next)) { entry->d_type = DTYPE_FIFO; } } /* If the node has child node(s) or no operations, then we will say that * it is a directory rather than a special file. NOTE: that the node can * be both! */ if (pdir->next->i_child != NULL || pdir->next->u.i_ops == NULL) { entry->d_type = DTYPE_DIRECTORY; } /* Now get the inode to visit next time that readdir() is called */ inode_lock(); prev = pdir->next; pdir->next = prev->i_peer; /* The next node to visit */ if (pdir->next != NULL) { /* Increment the reference count on this next node */ atomic_fetch_add(&pdir->next->i_crefs, 1); } inode_unlock(); if (prev != NULL) { inode_release(prev); } return 0; } static int dir_open(FAR struct file *filep) { FAR struct fs_dirent_s *dir = filep->f_priv; return dir_allocate(filep, dir->fd_path); } static int dir_close(FAR struct file *filep) { FAR struct fs_dirent_s *dir = filep->f_priv; FAR struct inode *inode = dir->fd_root; FAR char *relpath = dir->fd_path; int ret = 0; /* This is the 'root' inode of the directory. This means different * things with different filesystems. */ #ifndef CONFIG_DISABLE_MOUNTPOINT /* The way that we handle the close operation depends on what kind of * root inode we have open. */ if (INODE_IS_MOUNTPT(inode)) { /* The node is a file system mointpoint. Verify that the * mountpoint supports the closedir() method (not an error if it * does not) */ if (inode->u.i_mops->closedir != NULL) { ret = inode->u.i_mops->closedir(inode, dir); } } else #endif { FAR struct fs_pseudodir_s *pdir = filep->f_priv; /* The node is part of the root pseudo file system, release * our contained reference to the 'next' inode. */ if (pdir->next != NULL) { inode_release(pdir->next); } /* Then release the container */ fs_heap_free(dir); } /* Release our references on the contained 'root' inode */ inode_release(inode); lib_free(relpath); return ret; } static ssize_t dir_read(FAR struct file *filep, FAR char *buffer, size_t buflen) { FAR struct fs_dirent_s *dir = filep->f_priv; #ifndef CONFIG_DISABLE_MOUNTPOINT FAR struct inode *inode = dir->fd_root; #endif int ret; /* Verify that we were provided with a valid directory structure */ if (buffer == NULL || buflen < sizeof(struct dirent)) { return -EINVAL; } /* The way we handle the readdir depends on the type of inode * that we are dealing with. */ #ifndef CONFIG_DISABLE_MOUNTPOINT if (INODE_IS_MOUNTPT(inode)) { ret = inode->u.i_mops->readdir(inode, dir, (FAR struct dirent *)buffer); } else #endif { /* The node is part of the root pseudo file system */ ret = read_pseudodir(dir, (FAR struct dirent *)buffer); } /* ret < 0 is an error. Special case: ret = -ENOENT is end of file */ if (ret < 0) { if (ret == -ENOENT) { ret = 0; } return ret; } filep->f_pos++; return sizeof(struct dirent); } static off_t dir_seek(FAR struct file *filep, off_t offset, int whence) { off_t pos = 0; /* The way we handle the readdir depends on the type of inode * that we are dealing with. */ if (whence == SEEK_SET) { #ifndef CONFIG_DISABLE_MOUNTPOINT FAR struct fs_dirent_s *dir = filep->f_priv; if (INODE_IS_MOUNTPT(dir->fd_root)) { pos = seek_mountptdir(filep, offset); } else #endif { pos = seek_pseudodir(filep, offset); } } else if (whence == SEEK_CUR) { return filep->f_pos; } else { return -EINVAL; } if (pos >= 0) { filep->f_pos = pos; } return pos; } static int dir_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct fs_dirent_s *dir = filep->f_priv; int ret = OK; if (cmd == FIOC_FILEPATH) { strlcpy((FAR char *)(uintptr_t)arg, dir->fd_path, PATH_MAX); } else if (cmd != BIOC_FLUSH) { ret = -ENOTTY; } return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: dir_allocate * * Description: * Allocate a directory instance and bind it to f_priv of filep. * ****************************************************************************/ int dir_allocate(FAR struct file *filep, FAR const char *relpath) { FAR struct fs_dirent_s *dir; FAR struct inode *inode = filep->f_inode; char path_prefix[PATH_MAX]; int ret; /* Is this a node in the pseudo filesystem? Or a mountpoint? */ #ifndef CONFIG_DISABLE_MOUNTPOINT if (INODE_IS_MOUNTPT(inode)) { /* Open the directory at the relative path */ ret = open_mountpoint(inode, relpath, &dir); if (ret < 0) { return ret; } } else #endif { ret = open_pseudodir(inode, &dir); if (ret < 0) { return ret; } } inode_getpath(inode, path_prefix, sizeof(path_prefix)); ret = asprintf(&dir->fd_path, "%s%s/", path_prefix, relpath); if (ret < 0) { dir->fd_path = NULL; return ret; } filep->f_inode = &g_dir_inode; filep->f_priv = dir; inode_addref(&g_dir_inode); return ret; }