2011-10-08 14:50:08 +00:00
|
|
|
/*
|
|
|
|
* File : dfs.c
|
|
|
|
* This file is part of Device File System in RT-Thread RTOS
|
2012-07-10 09:25:19 +00:00
|
|
|
* COPYRIGHT (C) 2004-2012, RT-Thread Development Team
|
2011-10-08 14:50:08 +00:00
|
|
|
*
|
2013-06-26 22:30:40 +08:00
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
2011-10-08 14:50:08 +00:00
|
|
|
*
|
|
|
|
* Change Logs:
|
|
|
|
* Date Author Notes
|
|
|
|
* 2005-02-22 Bernard The first version.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <dfs.h>
|
|
|
|
#include <dfs_fs.h>
|
|
|
|
#include <dfs_file.h>
|
2017-10-15 22:44:53 +08:00
|
|
|
#include "dfs_private.h"
|
2011-10-08 14:50:08 +00:00
|
|
|
|
|
|
|
/* Global variables */
|
2017-10-15 22:44:53 +08:00
|
|
|
const struct dfs_filesystem_ops *filesystem_operation_table[DFS_FILESYSTEM_TYPES_MAX];
|
2011-10-08 14:50:08 +00:00
|
|
|
struct dfs_filesystem filesystem_table[DFS_FILESYSTEMS_MAX];
|
|
|
|
|
|
|
|
/* device filesystem lock */
|
|
|
|
static struct rt_mutex fslock;
|
|
|
|
|
|
|
|
#ifdef DFS_USING_WORKDIR
|
|
|
|
char working_directory[DFS_PATH_MAX] = {"/"};
|
|
|
|
#endif
|
|
|
|
|
|
|
|
struct dfs_fd fd_table[DFS_FD_MAX];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @addtogroup DFS
|
|
|
|
*/
|
2012-07-10 09:25:19 +00:00
|
|
|
|
2011-10-08 14:50:08 +00:00
|
|
|
/*@{*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* this function will initialize device file system.
|
|
|
|
*/
|
2013-06-23 07:57:22 +08:00
|
|
|
int dfs_init(void)
|
2011-10-08 14:50:08 +00:00
|
|
|
{
|
2012-12-26 01:12:13 +00:00
|
|
|
/* clear filesystem operations table */
|
2017-10-15 22:44:53 +08:00
|
|
|
memset((void *)filesystem_operation_table, 0, sizeof(filesystem_operation_table));
|
2012-12-26 01:12:13 +00:00
|
|
|
/* clear filesystem table */
|
2017-10-15 22:44:53 +08:00
|
|
|
memset(filesystem_table, 0, sizeof(filesystem_table));
|
2012-12-26 01:12:13 +00:00
|
|
|
/* clean fd table */
|
2017-10-15 22:44:53 +08:00
|
|
|
memset(fd_table, 0, sizeof(fd_table));
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
/* create device filesystem lock */
|
|
|
|
rt_mutex_init(&fslock, "fslock", RT_IPC_FLAG_FIFO);
|
2011-10-08 14:50:08 +00:00
|
|
|
|
|
|
|
#ifdef DFS_USING_WORKDIR
|
2012-12-26 01:12:13 +00:00
|
|
|
/* set current working directory */
|
2017-10-15 22:44:53 +08:00
|
|
|
memset(working_directory, 0, sizeof(working_directory));
|
2012-12-26 01:12:13 +00:00
|
|
|
working_directory[0] = '/';
|
2011-10-08 14:50:08 +00:00
|
|
|
#endif
|
2017-10-15 22:44:53 +08:00
|
|
|
|
|
|
|
#ifdef RT_USING_DFS_DEVFS
|
|
|
|
{
|
|
|
|
extern int devfs_init(void);
|
|
|
|
|
|
|
|
/* if enable devfs, initialize and mount it as soon as possible */
|
|
|
|
devfs_init();
|
|
|
|
|
|
|
|
dfs_mount(NULL, "/dev", "devfs", 0, 0);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return 0;
|
2011-10-08 14:50:08 +00:00
|
|
|
}
|
2017-10-15 22:44:53 +08:00
|
|
|
INIT_PREV_EXPORT(dfs_init);
|
2011-10-08 14:50:08 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* this function will lock device file system.
|
|
|
|
*
|
|
|
|
* @note please don't invoke it on ISR.
|
|
|
|
*/
|
2011-12-23 02:11:55 +00:00
|
|
|
void dfs_lock(void)
|
2011-10-08 14:50:08 +00:00
|
|
|
{
|
2012-12-26 01:12:13 +00:00
|
|
|
rt_err_t result;
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
result = rt_mutex_take(&fslock, RT_WAITING_FOREVER);
|
|
|
|
if (result != RT_EOK)
|
|
|
|
{
|
|
|
|
RT_ASSERT(0);
|
|
|
|
}
|
2011-10-08 14:50:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* this function will lock device file system.
|
|
|
|
*
|
|
|
|
* @note please don't invoke it on ISR.
|
|
|
|
*/
|
2011-12-23 02:11:55 +00:00
|
|
|
void dfs_unlock(void)
|
2011-10-08 14:50:08 +00:00
|
|
|
{
|
2012-12-26 01:12:13 +00:00
|
|
|
rt_mutex_release(&fslock);
|
2011-10-08 14:50:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @ingroup Fd
|
|
|
|
* This function will allocate a file descriptor.
|
|
|
|
*
|
|
|
|
* @return -1 on failed or the allocated file descriptor.
|
|
|
|
*/
|
|
|
|
int fd_new(void)
|
|
|
|
{
|
2012-12-26 01:12:13 +00:00
|
|
|
struct dfs_fd *d;
|
|
|
|
int idx;
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
/* lock filesystem */
|
|
|
|
dfs_lock();
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
/* find an empty fd entry */
|
|
|
|
for (idx = 0; idx < DFS_FD_MAX && fd_table[idx].ref_count > 0; idx++);
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
/* can't find an empty fd entry */
|
|
|
|
if (idx == DFS_FD_MAX)
|
|
|
|
{
|
2017-10-22 22:14:55 +08:00
|
|
|
idx = -(1 + DFS_FD_OFFSET);
|
2012-12-26 01:12:13 +00:00
|
|
|
goto __result;
|
|
|
|
}
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
d = &(fd_table[idx]);
|
|
|
|
d->ref_count = 1;
|
2012-12-29 06:28:28 +00:00
|
|
|
d->magic = DFS_FD_MAGIC;
|
2011-10-08 14:50:08 +00:00
|
|
|
|
|
|
|
__result:
|
2012-12-26 01:12:13 +00:00
|
|
|
dfs_unlock();
|
2017-10-15 22:44:53 +08:00
|
|
|
return idx + DFS_FD_OFFSET;
|
2011-10-08 14:50:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @ingroup Fd
|
|
|
|
*
|
|
|
|
* This function will return a file descriptor structure according to file
|
|
|
|
* descriptor.
|
|
|
|
*
|
|
|
|
* @return NULL on on this file descriptor or the file descriptor structure
|
|
|
|
* pointer.
|
|
|
|
*/
|
2011-12-23 02:11:55 +00:00
|
|
|
struct dfs_fd *fd_get(int fd)
|
2011-10-08 14:50:08 +00:00
|
|
|
{
|
2012-12-26 01:12:13 +00:00
|
|
|
struct dfs_fd *d;
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2017-10-15 22:44:53 +08:00
|
|
|
fd = fd - DFS_FD_OFFSET;
|
2012-12-26 01:12:13 +00:00
|
|
|
if (fd < 0 || fd >= DFS_FD_MAX)
|
2017-10-15 22:44:53 +08:00
|
|
|
return NULL;
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
dfs_lock();
|
|
|
|
d = &fd_table[fd];
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2012-12-29 06:28:28 +00:00
|
|
|
/* check dfs_fd valid or not */
|
|
|
|
if (d->magic != DFS_FD_MAGIC)
|
|
|
|
{
|
|
|
|
dfs_unlock();
|
2017-10-15 22:44:53 +08:00
|
|
|
return NULL;
|
2012-12-29 06:28:28 +00:00
|
|
|
}
|
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
/* increase the reference count */
|
|
|
|
d->ref_count ++;
|
|
|
|
dfs_unlock();
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
return d;
|
2011-10-08 14:50:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @ingroup Fd
|
|
|
|
*
|
|
|
|
* This function will put the file descriptor.
|
|
|
|
*/
|
2011-12-23 02:11:55 +00:00
|
|
|
void fd_put(struct dfs_fd *fd)
|
2011-10-08 14:50:08 +00:00
|
|
|
{
|
2017-10-15 22:44:53 +08:00
|
|
|
RT_ASSERT(fd != NULL);
|
2012-06-29 06:03:11 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
dfs_lock();
|
|
|
|
fd->ref_count --;
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
/* clear this fd entry */
|
|
|
|
if (fd->ref_count == 0)
|
|
|
|
{
|
2017-10-15 22:44:53 +08:00
|
|
|
memset(fd, 0, sizeof(struct dfs_fd));
|
2012-12-26 01:12:13 +00:00
|
|
|
}
|
|
|
|
dfs_unlock();
|
2017-10-15 22:44:53 +08:00
|
|
|
}
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2013-04-01 02:37:13 +08:00
|
|
|
/**
|
2011-10-08 14:50:08 +00:00
|
|
|
* @ingroup Fd
|
|
|
|
*
|
|
|
|
* This function will return whether this file has been opend.
|
2013-04-01 02:37:13 +08:00
|
|
|
*
|
2011-10-08 14:50:08 +00:00
|
|
|
* @param pathname the file path name.
|
|
|
|
*
|
|
|
|
* @return 0 on file has been open successfully, -1 on open failed.
|
|
|
|
*/
|
2011-12-23 02:11:55 +00:00
|
|
|
int fd_is_open(const char *pathname)
|
2011-10-08 14:50:08 +00:00
|
|
|
{
|
2012-12-26 01:12:13 +00:00
|
|
|
char *fullpath;
|
|
|
|
unsigned int index;
|
|
|
|
struct dfs_filesystem *fs;
|
|
|
|
struct dfs_fd *fd;
|
|
|
|
|
2017-10-15 22:44:53 +08:00
|
|
|
fullpath = dfs_normalize_path(NULL, pathname);
|
|
|
|
if (fullpath != NULL)
|
2012-12-26 01:12:13 +00:00
|
|
|
{
|
|
|
|
char *mountpath;
|
|
|
|
fs = dfs_filesystem_lookup(fullpath);
|
2017-10-15 22:44:53 +08:00
|
|
|
if (fs == NULL)
|
2012-12-26 01:12:13 +00:00
|
|
|
{
|
|
|
|
/* can't find mounted file system */
|
2017-10-15 22:44:53 +08:00
|
|
|
free(fullpath);
|
2012-12-26 01:12:13 +00:00
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get file path name under mounted file system */
|
|
|
|
if (fs->path[0] == '/' && fs->path[1] == '\0')
|
|
|
|
mountpath = fullpath;
|
2013-04-01 02:37:13 +08:00
|
|
|
else
|
2012-12-26 01:12:13 +00:00
|
|
|
mountpath = fullpath + strlen(fs->path);
|
|
|
|
|
|
|
|
dfs_lock();
|
2017-10-15 22:44:53 +08:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
for (index = 0; index < DFS_FD_MAX; index++)
|
|
|
|
{
|
|
|
|
fd = &(fd_table[index]);
|
2017-10-15 22:44:53 +08:00
|
|
|
if (fd->fops == NULL)
|
2012-12-26 01:12:13 +00:00
|
|
|
continue;
|
|
|
|
|
2017-10-15 22:44:53 +08:00
|
|
|
if (fd->fops == fs->ops->fops && strcmp(fd->path, mountpath) == 0)
|
2012-12-26 01:12:13 +00:00
|
|
|
{
|
|
|
|
/* found file in file descriptor table */
|
|
|
|
rt_free(fullpath);
|
|
|
|
dfs_unlock();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dfs_unlock();
|
|
|
|
|
|
|
|
rt_free(fullpath);
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
2011-10-08 14:50:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* this function will return a sub-path name under directory.
|
|
|
|
*
|
|
|
|
* @param directory the parent directory.
|
|
|
|
* @param filename the filename.
|
|
|
|
*
|
|
|
|
* @return the subdir pointer in filename
|
|
|
|
*/
|
2011-12-23 02:11:55 +00:00
|
|
|
const char *dfs_subdir(const char *directory, const char *filename)
|
2011-10-08 14:50:08 +00:00
|
|
|
{
|
2012-12-26 01:12:13 +00:00
|
|
|
const char *dir;
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
if (strlen(directory) == strlen(filename)) /* it's a same path */
|
2017-10-15 22:44:53 +08:00
|
|
|
return NULL;
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
dir = filename + strlen(directory);
|
|
|
|
if ((*dir != '/') && (dir != filename))
|
|
|
|
{
|
|
|
|
dir --;
|
|
|
|
}
|
2012-07-10 09:25:19 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
return dir;
|
2011-10-08 14:50:08 +00:00
|
|
|
}
|
2015-05-02 08:55:08 +08:00
|
|
|
RTM_EXPORT(dfs_subdir);
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2013-04-01 02:37:13 +08:00
|
|
|
/**
|
2012-12-26 01:12:13 +00:00
|
|
|
* this function will normalize a path according to specified parent directory
|
|
|
|
* and file name.
|
2011-10-08 14:50:08 +00:00
|
|
|
*
|
|
|
|
* @param directory the parent path
|
|
|
|
* @param filename the file name
|
|
|
|
*
|
2011-12-31 04:05:40 +00:00
|
|
|
* @return the built full file path (absolute path)
|
2011-10-08 14:50:08 +00:00
|
|
|
*/
|
2011-12-23 02:11:55 +00:00
|
|
|
char *dfs_normalize_path(const char *directory, const char *filename)
|
2011-10-08 14:50:08 +00:00
|
|
|
{
|
2012-12-26 01:12:13 +00:00
|
|
|
char *fullpath;
|
|
|
|
char *dst0, *dst, *src;
|
2011-10-08 14:50:08 +00:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
/* check parameters */
|
2017-10-15 22:44:53 +08:00
|
|
|
RT_ASSERT(filename != NULL);
|
2011-10-08 14:50:08 +00:00
|
|
|
|
|
|
|
#ifdef DFS_USING_WORKDIR
|
2017-10-15 22:44:53 +08:00
|
|
|
if (directory == NULL) /* shall use working directory */
|
2012-12-26 01:12:13 +00:00
|
|
|
directory = &working_directory[0];
|
2011-10-08 14:50:08 +00:00
|
|
|
#else
|
2017-10-15 22:44:53 +08:00
|
|
|
if ((directory == NULL) && (filename[0] != '/'))
|
2012-12-26 01:12:13 +00:00
|
|
|
{
|
|
|
|
rt_kprintf(NO_WORKING_DIR);
|
2012-07-10 09:25:19 +00:00
|
|
|
|
2017-10-15 22:44:53 +08:00
|
|
|
return NULL;
|
2012-12-26 01:12:13 +00:00
|
|
|
}
|
2011-10-08 14:50:08 +00:00
|
|
|
#endif
|
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
if (filename[0] != '/') /* it's a absolute path, use it directly */
|
|
|
|
{
|
|
|
|
fullpath = rt_malloc(strlen(directory) + strlen(filename) + 2);
|
|
|
|
|
2017-10-15 22:44:53 +08:00
|
|
|
if (fullpath == NULL)
|
|
|
|
return NULL;
|
2013-04-01 02:31:24 +08:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
/* join path and file name */
|
2013-04-01 02:37:13 +08:00
|
|
|
rt_snprintf(fullpath, strlen(directory) + strlen(filename) + 2,
|
2012-12-26 01:12:13 +00:00
|
|
|
"%s/%s", directory, filename);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fullpath = rt_strdup(filename); /* copy string */
|
2013-04-01 02:37:13 +08:00
|
|
|
|
2017-10-15 22:44:53 +08:00
|
|
|
if (fullpath == NULL)
|
|
|
|
return NULL;
|
2012-12-26 01:12:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
src = fullpath;
|
|
|
|
dst = fullpath;
|
2013-04-01 02:37:13 +08:00
|
|
|
|
2012-12-26 01:12:13 +00:00
|
|
|
dst0 = dst;
|
|
|
|
while (1)
|
|
|
|
{
|
|
|
|
char c = *src;
|
|
|
|
|
|
|
|
if (c == '.')
|
|
|
|
{
|
|
|
|
if (!src[1]) src ++; /* '.' and ends */
|
|
|
|
else if (src[1] == '/')
|
|
|
|
{
|
|
|
|
/* './' case */
|
|
|
|
src += 2;
|
|
|
|
|
|
|
|
while ((*src == '/') && (*src != '\0'))
|
|
|
|
src ++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (src[1] == '.')
|
|
|
|
{
|
|
|
|
if (!src[2])
|
|
|
|
{
|
|
|
|
/* '..' and ends case */
|
|
|
|
src += 2;
|
|
|
|
goto up_one;
|
|
|
|
}
|
|
|
|
else if (src[2] == '/')
|
|
|
|
{
|
|
|
|
/* '../' case */
|
|
|
|
src += 3;
|
|
|
|
|
|
|
|
while ((*src == '/') && (*src != '\0'))
|
|
|
|
src ++;
|
|
|
|
goto up_one;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* copy up the next '/' and erase all '/' */
|
|
|
|
while ((c = *src++) != '\0' && c != '/')
|
|
|
|
*dst ++ = c;
|
|
|
|
|
|
|
|
if (c == '/')
|
|
|
|
{
|
|
|
|
*dst ++ = '/';
|
|
|
|
while (c == '/')
|
|
|
|
c = *src++;
|
|
|
|
|
|
|
|
src --;
|
|
|
|
}
|
|
|
|
else if (!c)
|
|
|
|
break;
|
|
|
|
|
|
|
|
continue;
|
2011-10-08 14:50:08 +00:00
|
|
|
|
|
|
|
up_one:
|
2012-12-26 01:12:13 +00:00
|
|
|
dst --;
|
|
|
|
if (dst < dst0)
|
|
|
|
{
|
2013-04-01 02:37:13 +08:00
|
|
|
rt_free(fullpath);
|
2017-10-15 22:44:53 +08:00
|
|
|
return NULL;
|
2012-12-26 01:12:13 +00:00
|
|
|
}
|
|
|
|
while (dst0 < dst && dst[-1] != '/')
|
|
|
|
dst --;
|
|
|
|
}
|
|
|
|
|
|
|
|
*dst = '\0';
|
|
|
|
|
|
|
|
/* remove '/' in the end of path if exist */
|
|
|
|
dst --;
|
|
|
|
if ((dst != fullpath) && (*dst == '/'))
|
|
|
|
*dst = '\0';
|
|
|
|
|
|
|
|
return fullpath;
|
2011-10-08 14:50:08 +00:00
|
|
|
}
|
2015-05-02 08:55:08 +08:00
|
|
|
RTM_EXPORT(dfs_normalize_path);
|
2017-12-06 02:55:23 +08:00
|
|
|
#ifdef RT_USING_FINSH
|
2017-10-15 22:44:53 +08:00
|
|
|
#include <finsh.h>
|
|
|
|
int list_fd(void)
|
|
|
|
{
|
|
|
|
int index;
|
|
|
|
|
|
|
|
rt_enter_critical();
|
|
|
|
|
|
|
|
for (index = 0; index < DFS_FD_MAX; index ++)
|
|
|
|
{
|
|
|
|
struct dfs_fd *fd = &(fd_table[index]);
|
|
|
|
|
|
|
|
if (fd->fops)
|
|
|
|
{
|
|
|
|
rt_kprintf("--fd: %d--", index);
|
|
|
|
if (fd->type == FT_DIRECTORY) rt_kprintf("[dir]\n");
|
|
|
|
if (fd->type == FT_REGULAR) rt_kprintf("[file]\n");
|
|
|
|
if (fd->type == FT_SOCKET) rt_kprintf("[socket]\n");
|
|
|
|
if (fd->type == FT_USER) rt_kprintf("[user]\n");
|
|
|
|
rt_kprintf("refcount=%d\n", fd->ref_count);
|
|
|
|
rt_kprintf("magic=0x%04x\n", fd->magic);
|
|
|
|
if (fd->path)
|
|
|
|
{
|
|
|
|
rt_kprintf("path: %s\n", fd->path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rt_exit_critical();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
MSH_CMD_EXPORT(list_fd, list file descriptor);
|
2017-12-06 02:55:23 +08:00
|
|
|
#endif
|
2011-10-08 14:50:08 +00:00
|
|
|
/*@}*/
|
|
|
|
|