diff --git a/fs/inode/fs_inode.c b/fs/inode/fs_inode.c index e77c157415d..b8f1bda45b0 100644 --- a/fs/inode/fs_inode.c +++ b/fs/inode/fs_inode.c @@ -40,6 +40,7 @@ #include #include +#include #include #include #include @@ -167,6 +168,45 @@ static int _inode_compare(FAR const char *fname, } } +/**************************************************************************** + * Name: _inode_dereference + * + * Description: + * If the inode is a soft link, then (1) get the name of the full path of + * the soft link, (2) recursively look-up the inode referenced by the soft + * link, and (3) return the inode referenced by the soft link. + * + * Assumptions: + * The caller holds the g_inode_sem semaphore + * + ****************************************************************************/ + +#ifdef CONFIG_PSEUDOFS_SOFTLINKS +static inline FAR struct inode * +_inode_dereference(FAR struct inode *node, FAR struct inode **peer, + FAR struct inode **parent, FAR const char **relpath) +{ + unsigned int count = 0; + + /* An infinite loop is avoided only by the loop count. + * + * REVISIT: The ELOOP error should be reported to the application in that + * case but there is no simple mechanism to do that. + */ + + while (node != NULL && INODE_IS_SOFTLINK(node)) + { + node = inode_search_nofollow(node->u.i_link, peer, parent, relpath); + if (++count > SYMLOOP_MAX) + { + return NULL; + } + } + + return node; +} +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -275,28 +315,40 @@ void inode_semgive(void) } /**************************************************************************** - * Name: inode_search + * Name: inode_search and inode_search_nofollow * * Description: * Find the inode associated with 'path' returning the inode references * and references to its companion nodes. * + * Both versions will follow soft links in path leading up to the terminal + * node. inode_search() will deference that terminal node, + * inode_search_nofollow will not. + * * Assumptions: * The caller holds the g_inode_sem semaphore * ****************************************************************************/ +#ifdef CONFIG_PSEUDOFS_SOFTLINKS + +FAR struct inode *inode_search_nofollow(FAR const char **path, + FAR struct inode **peer, + FAR struct inode **parent, + FAR const char **relpath) +#else FAR struct inode *inode_search(FAR const char **path, FAR struct inode **peer, FAR struct inode **parent, FAR const char **relpath) +#endif { FAR const char *name = *path + 1; /* Skip over leading '/' */ FAR struct inode *node = g_root_inode; FAR struct inode *left = NULL; FAR struct inode *above = NULL; - while (node) + while (node != NULL) { int result = _inode_compare(name, node); @@ -319,6 +371,24 @@ FAR struct inode *inode_search(FAR const char **path, else if (result > 0) { +#ifdef CONFIG_PSEUDOFS_SOFTLINKS + /* If the inode in the is a soft link and this is the inode at + * at the head of the peer list and not the final node in the + * path), then (1) get the name of the full path of the soft + * link, (2) recursively look-up the inode referenced by the + * soft link, and (3) use the peer of that inode instead. + */ + + FAR const char *nextname = inode_nextname(name); + if (*nextname != '\0') + { + node = _inode_dereference(node, NULL, &above, relpath); + if (node == NULL) + { + break; + } + } +#endif left = node; node = node->i_peer; } @@ -327,15 +397,15 @@ FAR struct inode *inode_search(FAR const char **path, else { - /* Now there are three more possibilities: - * (1) This is the node that we are looking for or, + /* Now there are three remaining possibilities: + * (1) This is the node that we are looking for. * (2) The node we are looking for is "below" this one. * (3) This node is a mountpoint and will absorb all request * below this one */ name = inode_nextname(name); - if (!*name || INODE_IS_MOUNTPT(node)) + if (*name == '\0' || INODE_IS_MOUNTPT(node)) { /* Either (1) we are at the end of the path, so this must be the * node we are looking for or else (2) this node is a mountpoint @@ -347,15 +417,38 @@ FAR struct inode *inode_search(FAR const char **path, *relpath = name; } +#ifdef CONFIG_PSEUDOFS_SOFTLINKS + /* NOTE that if the terminal inode is a soft link, it is not + * deferenced in this case. The raw inode is returned. + * + * In that case a wrapper function will perform that operation. + */ +#endif break; } else { - /* More to go, keep looking at the next level "down" */ + /* More to go.. */ + +#ifdef CONFIG_PSEUDOFS_SOFTLINKS + /* If this intermediate inode in the is a soft link, then (1) + * get the name of the full path of the soft link, (2) recursively + * look-up the inode referenced by the sof link, and (3) + * continue searching with that inode instead. + */ + + node = _inode_dereference(node, NULL, NULL, relpath); + if (node == NULL) + { + break; + } +#endif + + /* Keep looking at the next level "down" */ above = node; left = NULL; - node = node->i_child; + node = node->i_child; } } } @@ -389,6 +482,33 @@ FAR struct inode *inode_search(FAR const char **path, return node; } +#ifdef CONFIG_PSEUDOFS_SOFTLINKS +FAR struct inode *inode_search(FAR const char **path, + FAR struct inode **peer, + FAR struct inode **parent, + FAR const char **relpath) +{ + /* Lookup the terminal inode */ + + FAR struct inode *node = inode_search_nofollow(path, peer, parent, relpath); + + /* Did we find it? */ + + if (node != NULL) + { + /* Yes.. If the terminal inode in the is a soft link, then (1) get + * the name of the full path of the soft link, (2) recursively + * look-up the inode referenced by the soft link, and (3) + * return that inode instead. + */ + + return _inode_dereference(node, peer, parent, relpath); + } + + return node; +} +#endif + /**************************************************************************** * Name: inode_free * diff --git a/fs/inode/fs_inodefind.c b/fs/inode/fs_inodefind.c index 3ff249d348d..38f15b595ef 100644 --- a/fs/inode/fs_inodefind.c +++ b/fs/inode/fs_inodefind.c @@ -49,19 +49,23 @@ ****************************************************************************/ /**************************************************************************** - * Name: inode_find + * Name: inode_find and indode_find_nofollow * * Description: * This is called from the open() logic to get a reference to the inode * associated with a path. * + * Both versions will follow soft links in path leading up to the terminal + * node. inode_find() will deference that terminal node, + * indode_find_nofollow no follow will not. + * ****************************************************************************/ FAR struct inode *inode_find(FAR const char *path, FAR const char **relpath) { FAR struct inode *node; - if (path == NULL || path[0] == '\0' || path[0] != '/') + if (path == NULL || *path != '/') { return NULL; } @@ -71,7 +75,8 @@ FAR struct inode *inode_find(FAR const char *path, FAR const char **relpath) */ inode_semtake(); - node = inode_search(&path, (FAR struct inode**)NULL, (FAR struct inode**)NULL, relpath); + node = inode_search(&path, (FAR struct inode**)NULL, + (FAR struct inode**)NULL, relpath); if (node) { node->i_crefs++; @@ -81,3 +86,30 @@ FAR struct inode *inode_find(FAR const char *path, FAR const char **relpath) return node; } +#ifdef CONFIG_PSEUDOFS_SOFTLINKS +FAR struct inode *inode_find_nofollow(FAR const char *path, + FARconst char **relpath) +{ + FAR struct inode *node; + + if (path == NULL || *path != '/') + { + return NULL; + } + + /* Find the node matching the path. If found, increment the count of + * references on the node. + */ + + inode_semtake(); + node = inode_search_nofollow(&path, (FAR struct inode**)NULL, + (FAR struct inode**)NULL, relpath); + if (node) + { + node->i_crefs++; + } + + inode_semgive(); + return node; +} +#endif diff --git a/fs/inode/inode.h b/fs/inode/inode.h index f909d74ef17..31532490c78 100644 --- a/fs/inode/inode.h +++ b/fs/inode/inode.h @@ -112,14 +112,18 @@ void inode_semtake(void); void inode_semgive(void); /**************************************************************************** - * Name: inode_search + * Name: inode_search and inode_search_nofollow * * Description: * Find the inode associated with 'path' returning the inode references * and references to its companion nodes. * + * Both versions will follow soft links in path leading up to the terminal + * node. inode_search() will deference that terminal node, + * inode_search_nofollow will not. + * * Assumptions: - * The caller holds the tree_sem + * The caller holds the g_inode_sem semaphore * ****************************************************************************/ @@ -128,6 +132,15 @@ FAR struct inode *inode_search(FAR const char **path, FAR struct inode **parent, FAR const char **relpath); +#ifdef CONFIG_PSEUDOFS_SOFTLINKS +FAR struct inode *inode_search_nofollow(FAR const char **path, + FAR struct inode **peer, + FAR struct inode **parent, + FAR const char **relpath) +#else +# define inode_search_nofollow(p,l,a,r) inode_search(p,l,a,r) +#endif + /**************************************************************************** * Name: inode_free * @@ -205,15 +218,26 @@ FAR struct inode *inode_unlink(FAR const char *path); int inode_remove(FAR const char *path); /**************************************************************************** - * Name: inode_find + * Name: inode_find and indode_find_nofollow * * Description: * This is called from the open() logic to get a reference to the inode * associated with a path. * + * Both versions will follow soft links in path leading up to the terminal + * node. inode_find() will deference that terminal node, + * indode_find_nofollow no follow will not. + * ****************************************************************************/ -FAR struct inode *inode_find(FAR const char *path, const char **relpath); +FAR struct inode *inode_find(FAR const char *path, FAR const char **relpath); + +#ifdef CONFIG_PSEUDOFS_SOFTLINKS +FAR struct inode *inode_find_nofollow(FAR const char *path, + FARconst char **relpath); +#else +# define inode_find_nofollow(p,r) inode_find(p,r) +#endif /**************************************************************************** * Name: inode_addref diff --git a/fs/vfs/fs_softlink.c b/fs/vfs/fs_softlink.c index 483bcb4ffab..225bf5b61b3 100644 --- a/fs/vfs/fs_softlink.c +++ b/fs/vfs/fs_softlink.c @@ -88,7 +88,15 @@ int link(FAR const char *path1, FAR const char *path2) int errcode; int ret; - DEBUGASSERT(path1 != NULL && path2 != NULL && *path2 != '\0'); + /* Both paths must be absolute. We need only check path2 here. path1 will + * be checked by inode find. + */ + + if (path2 == NULL || *path2 != '/') + { + errode = EINVAL; + goto errout; + } /* Check that no inode exists at the 'path2' and that the path up to 'path2' * does not lie on a mounted volume. @@ -129,7 +137,7 @@ int link(FAR const char *path1, FAR const char *path2) if (newpath2 == NULL) { errcode = ENOMEM; - goto errout; + goto errout; } /* Create an inode in the pseudo-filesystem at this path. diff --git a/fs/vfs/fs_unlink.c b/fs/vfs/fs_unlink.c index 9de8ea2f2f7..ea754fed5c6 100644 --- a/fs/vfs/fs_unlink.c +++ b/fs/vfs/fs_unlink.c @@ -85,9 +85,11 @@ int unlink(FAR const char *pathname) int errcode; int ret; - /* Get an inode for this file */ + /* Get an inode for this file (without deference the final node in the path + * which may be a symbolic link) + */ - inode = inode_find(pathname, &relpath); + inode = inode_find_nofollow(pathname, &relpath); if (!inode) { /* There is no inode that includes in this path */ @@ -124,17 +126,17 @@ int unlink(FAR const char *pathname) #endif #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS - /* If this is a "dangling" pseudo-file node (i.e., it has operations) then rm - * should remove the node. + /* If this is a "dangling" pseudo-file node (i.e., it has no operations) + * or a soft link, then rm should remove the node. */ - if (!INODE_IS_SPECIAL(inode) && inode->u.i_ops) + if (!INODE_IS_SPECIAL(inode)) { /* If this is a pseudo-file node (i.e., it has no operations) - * then rmdir should remove the node. + * then unlink should remove the node. */ - if (inode->u.i_ops) + if (inode->u.i_ops != NULL) { inode_semtake(); diff --git a/include/limits.h b/include/limits.h index 3479e595953..27c616b2a73 100644 --- a/include/limits.h +++ b/include/limits.h @@ -111,6 +111,12 @@ * * _POSIX_SEM_NSEMS_MAX Max number of open semaphores per task * _POSIX_SEM_VALUE_MAX Max value a semaphore may have + * + * Required for symbolic links + * _POSIX_SYMLOOP_MAX Maximum number of symbolic links that can be + * reliably traversed in the resolution of a pathname + * in the absence of a loop. + * */ #define _POSIX_ARG_MAX 4096 @@ -143,6 +149,10 @@ #define _POSIX_RTSIG_MAX 31 #define _POSIX_SIGQUEUE_MAX 32 +/* Required for symbolic links */ + +#define _POSIX_SYMLOOP_MAX 100 + /* Required for POSIX timers. * * _POSIX_DELAYTIMER_MAX is the number of timer expiration overruns. @@ -205,6 +215,8 @@ #define RTSIG_MAX _POSIX_RTSIG_MAX #define SIGQUEUE_MAX _POSIX_SIGQUEUE_MAX +#define SYMLOOP_MAX _POSIX_SYMLOOP_MAX + #define DELAYTIMER_MAX _POSIX_DELAYTIMER_MAX #define TIMER_MAX _POSIX_TIMER_MAX #define CLOCKRES_MIN _POSIX_CLOCKRES_MIN diff --git a/include/nuttx/fs/fs.h b/include/nuttx/fs/fs.h index 8ec3bc83da7..cf568658024 100644 --- a/include/nuttx/fs/fs.h +++ b/include/nuttx/fs/fs.h @@ -125,7 +125,7 @@ * descriptor instead. * * This case is when SUSv1 pseudo-terminals are used (CONFIG_PSEUDOTERM_SUSV1=y). - * In this case, the output is encoded and decoded using these macros in + * In this case, the output is encoded and decoded using these macros in * order to support (a) returning file descriptor 0 (which really should * not happen), and (b) avoiding confusion if some other open method returns * a positive, non-zero value which is not a file descriptor.