623 lines
15 KiB
C
623 lines
15 KiB
C
/****************************************************************************
|
|
* 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 <nuttx/config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/fs/ioctl.h>
|
|
|
|
#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);
|
|
fs_heap_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 = fs_heap_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;
|
|
}
|