This is an automated email from the ASF dual-hosted git repository. xiaoxiang781216 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 3d0947936791955606cd62bda8da59db7ffe241a Author: zhengyu16 <[email protected]> AuthorDate: Thu Sep 18 16:56:44 2025 +0800 fs/vfs: add hardlink function of pseudofs 1. add the hardlink function 2. _POSIX_LINK_MAX judgement Signed-off-by: zhengyu16 <[email protected]> --- fs/inode/fs_inoderemove.c | 14 ++++ fs/inode/fs_inodesearch.c | 9 +++ fs/inode/inode.h | 6 ++ fs/vfs/fs_chstat.c | 10 +++ fs/vfs/fs_dir.c | 44 +++++++---- fs/vfs/fs_link.c | 188 +++++++++++++++++++++++++++++++++++++++++++--- fs/vfs/fs_open.c | 12 +++ fs/vfs/fs_rename.c | 6 +- fs/vfs/fs_stat.c | 11 +++ fs/vfs/fs_unlink.c | 6 +- include/nuttx/fs/fs.h | 3 + 11 files changed, 281 insertions(+), 28 deletions(-) diff --git a/fs/inode/fs_inoderemove.c b/fs/inode/fs_inoderemove.c index 87c86937479..aad079325e5 100644 --- a/fs/inode/fs_inoderemove.c +++ b/fs/inode/fs_inoderemove.c @@ -120,6 +120,20 @@ static FAR struct inode *inode_unlink(FAR const char *path) inode->i_peer = NULL; inode->i_parent = NULL; atomic_sub(&inode->i_crefs, 1); +#ifdef CONFIG_PSEUDOFS_SOFTLINKS + if (INODE_IS_HARDLINK(inode)) + { + FAR struct inode *target; + + DEBUGASSERT(inode->i_private != NULL); + target = inode->i_private; + atomic_sub(&target->i_crefs, INODE_NLINK_INC); + if (atomic_read(&target->i_crefs) == 0) + { + inode_free(target); + } + } +#endif } errout: diff --git a/fs/inode/fs_inodesearch.c b/fs/inode/fs_inodesearch.c index 67a14cf474e..1b2f69eb643 100644 --- a/fs/inode/fs_inodesearch.c +++ b/fs/inode/fs_inodesearch.c @@ -523,6 +523,15 @@ int inode_search(FAR struct inode_search_s *desc) return ret; } } + else if (!desc->nofollow && INODE_IS_HARDLINK(inode)) + { + /* The terminating inode is a valid hard link */ + + inode = inode->i_private; + DEBUGASSERT(inode != NULL); + + desc->node = inode; + } } #endif diff --git a/fs/inode/inode.h b/fs/inode/inode.h index 2f938ac81a3..08fc7632510 100644 --- a/fs/inode/inode.h +++ b/fs/inode/inode.h @@ -98,6 +98,12 @@ # define FS_ADD_BACKTRACE(fd) #endif +#define INODE_NLINK_INC (1u << 16) /* Increment link count */ +#define INODE_CREF_INC (1u << 0) /* Increment reference count */ +#define INODE_NLINK_SHIFT 16 /* Shift to get link count */ +#define INODE_GET_NLINK(i) \ + ((atomic_read(&(i)->i_crefs) >> INODE_NLINK_SHIFT) + 1) + /**************************************************************************** * Public Types ****************************************************************************/ diff --git a/fs/vfs/fs_chstat.c b/fs/vfs/fs_chstat.c index 16b61b257e9..d5da5b91798 100644 --- a/fs/vfs/fs_chstat.c +++ b/fs/vfs/fs_chstat.c @@ -454,6 +454,16 @@ int inode_chstat(FAR struct inode *inode, return chstat_recursive(inode->u.i_link, buf, flags, ++resolve); } } + + else if (INODE_IS_HARDLINK(inode)) + { + /* The inode is a hard link. The actual inode is referenced + * by the i_private field. + */ + + DEBUGASSERT(inode->i_private != NULL); + inode = inode->i_private; + } #endif #ifdef CONFIG_SCHED_USER_IDENTITY diff --git a/fs/vfs/fs_dir.c b/fs/vfs/fs_dir.c index 40abdc40b14..dfa074b1e44 100644 --- a/fs/vfs/fs_dir.c +++ b/fs/vfs/fs_dir.c @@ -299,11 +299,12 @@ 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 *next = pdir->next; FAR struct inode *prev; /* Check if we are at the end of the list */ - if (pdir->next == NULL) + if (next == NULL) { /* End of file and error conditions are not distinguishable with * readdir. Here we return -ENOENT to signal the end of the directory. @@ -312,54 +313,67 @@ static int read_pseudodir(FAR struct fs_dirent_s *dir, return -ENOENT; } - /* Copy the inode name into the dirent structure */ + /* Copy the inode name into the dirent structure. + * If it's a hardlink, we should get the name of hardlink itself, + * not the target file name. + * But other information like type should be based on the target file. + * So we put this line before the INODE_IS_HARDLINK() check. + */ + + strlcpy(entry->d_name, next->i_name, sizeof(entry->d_name)); - strlcpy(entry->d_name, pdir->next->i_name, sizeof(entry->d_name)); +#ifdef CONFIG_PSEUDOFS_SOFTLINKS + if (INODE_IS_HARDLINK(next)) + { + DEBUGASSERT(next->i_private != NULL); + next = next->i_private; + } +#endif /* 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) + if (next->u.i_ops != NULL) { #ifndef CONFIG_DISABLE_MOUNTPOINT - if (INODE_IS_BLOCK(pdir->next)) + if (INODE_IS_BLOCK(next)) { entry->d_type = DTYPE_BLK; } - else if (INODE_IS_MTD(pdir->next)) + else if (INODE_IS_MTD(next)) { entry->d_type = DTYPE_MTD; } - else if (INODE_IS_MOUNTPT(pdir->next)) + else if (INODE_IS_MOUNTPT(next)) { entry->d_type = DTYPE_DIRECTORY; } else #endif #ifdef CONFIG_PSEUDOFS_SOFTLINKS - if (INODE_IS_SOFTLINK(pdir->next)) + if (INODE_IS_SOFTLINK(next)) { entry->d_type = DTYPE_LINK; } else #endif - if (INODE_IS_DRIVER(pdir->next)) + if (INODE_IS_DRIVER(next)) { entry->d_type = DTYPE_CHR; } - else if (INODE_IS_NAMEDSEM(pdir->next)) + else if (INODE_IS_NAMEDSEM(next)) { entry->d_type = DTYPE_SEM; } - else if (INODE_IS_MQUEUE(pdir->next)) + else if (INODE_IS_MQUEUE(next)) { entry->d_type = DTYPE_MQ; } - else if (INODE_IS_SHM(pdir->next)) + else if (INODE_IS_SHM(next)) { entry->d_type = DTYPE_SHM; } - else if (INODE_IS_PIPE(pdir->next)) + else if (INODE_IS_PIPE(next)) { entry->d_type = DTYPE_FIFO; } @@ -370,8 +384,8 @@ static int read_pseudodir(FAR struct fs_dirent_s *dir, * be both! */ - if (pdir->next->i_child != NULL || - pdir->next->u.i_ops == NULL) + if (next->i_child != NULL || + next->u.i_ops == NULL) { entry->d_type = DTYPE_DIRECTORY; } diff --git a/fs/vfs/fs_link.c b/fs/vfs/fs_link.c index e47ac41fffe..b580a9aae82 100644 --- a/fs/vfs/fs_link.c +++ b/fs/vfs/fs_link.c @@ -26,8 +26,25 @@ #include <nuttx/config.h> -#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdbool.h> #include <unistd.h> +#include <assert.h> +#include <errno.h> + +#include <nuttx/lib/lib.h> +#include <nuttx/fs/fs.h> +#include <nuttx/atomic.h> + +#include "inode/inode.h" +#include "vfs.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifdef CONFIG_PSEUDOFS_SOFTLINKS /**************************************************************************** * Public Functions @@ -37,8 +54,14 @@ * Name: link * * Description: - * The link function provides a wrapper to symlink when pseudo filesystem - * softlinks are enabled. Otherwise, hard links are unsupported. + * The link() function creates a new hard link for the existing file + * specified by 'path1'. The new link is created at the location specified + * by 'path2'. + * + * This implementation currently only supports creating hard links within + * the NuttX pseudo-filesystem (pseudofs). Hard links for files within + * mounted filesystems are not yet supported. In the future, support for + * hard links in mounted filesystems may be added. * * Input Parameters: * path1 - Points to a pathname naming an existing file. @@ -53,13 +76,160 @@ int link(FAR const char *path1, FAR const char *path2) { -#ifdef CONFIG_PSEUDOFS_SOFTLINKS - return symlink(path1, path2); -#else - (void)path1; - (void)path2; + struct inode_search_s desc_path1; + struct inode_search_s desc_path2; + FAR struct inode *target = NULL; + FAR struct inode *newinode = NULL; + int errcode; + int ret; + + if (path1 == NULL || path2 == NULL) + { + errcode = EINVAL; + goto errout; + } + + if (*path1 == '\0' || *path2 == '\0') + { + errcode = ENOENT; + goto errout; + } + + SETUP_SEARCH(&desc_path1, path1, false); + ret = inode_find(&desc_path1); + if (ret < 0) + { + errcode = -ret; + goto errout_with_search_path1; + } + + target = desc_path1.node; + + /* Check if target inode is a mountpoint. */ + + if (INODE_IS_MOUNTPT(target)) + { + /* Hard links within the mounted volume are not supported */ + + errcode = ENOSYS; + goto errout_with_target; + } + + if (INODE_GET_NLINK(target) >= _POSIX_LINK_MAX) + { + /* Too many links to the target inode */ + + errcode = EMLINK; + goto errout_with_target; + } + + /* Check that no inode exists at the 'path2' and that the path up to + * 'path2' does not lie on a mounted volume. + */ + + SETUP_SEARCH(&desc_path2, path2, true); + + ret = inode_find(&desc_path2); + if (ret >= 0) + { + newinode = desc_path2.node; + + /* Something exists at the path2 where we are trying to create the + * link. + */ + +#ifndef CONFIG_DISABLE_MOUNTPOINT + /* Check if the inode is a mountpoint. */ + + DEBUGASSERT(newinode != NULL); + if (INODE_IS_MOUNTPT(newinode)) + { + /* Hard links within the mounted volume are not supported */ + + errcode = ENOSYS; + } + else +#endif + { + /* A node already exists in the pseudofs at 'path2' */ + + errcode = EEXIST; + } + + goto errout_with_newinode; + } + + /* No inode exists that contains this path. Create a new inode in the + * pseudo-filesystem at this location. + */ + + else + { + /* Create an inode in the pseudo-filesystem at this path. */ + + inode_lock(); + ret = inode_reserve(path2, 0777, &newinode); + + if (ret >= 0) + { + /* Initialize the inode */ + + INODE_SET_HARDLINK(newinode); + newinode->i_private = target; + atomic_add(&target->i_crefs, INODE_NLINK_INC); + } + + inode_unlock(); + if (ret < 0) + { + errcode = -ret; + goto errout_with_newinode; + } + } + + /* Hard link successfully created */ + + RELEASE_SEARCH(&desc_path1); + RELEASE_SEARCH(&desc_path2); + inode_release(target); + +#ifdef CONFIG_FS_NOTIFY + notify_create(path2); +#endif + return OK; + +errout_with_newinode: + inode_release(newinode); + RELEASE_SEARCH(&desc_path2); +errout_with_target: + inode_release(target); +errout_with_search_path1: + RELEASE_SEARCH(&desc_path1); + +errout: + set_errno(errcode); + return ERROR; +} + +#else /* CONFIG_PSEUDOFS_SOFTLINKS */ + +/**************************************************************************** + * Name: link + * + * Description: + * When CONFIG_PSEUDOFS_SOFTLINKS is disabled, link() is not supported. + * The symbol is still provided so that applications referencing link() + * continue to link, but the call always fails with ENOSYS. + * + ****************************************************************************/ + +int link(FAR const char *path1, FAR const char *path2) +{ + UNUSED(path1); + UNUSED(path2); set_errno(ENOSYS); return ERROR; -#endif } + +#endif /* CONFIG_PSEUDOFS_SOFTLINKS */ diff --git a/fs/vfs/fs_open.c b/fs/vfs/fs_open.c index 82b13dec554..a6c10c98023 100644 --- a/fs/vfs/fs_open.c +++ b/fs/vfs/fs_open.c @@ -128,10 +128,22 @@ static int file_vopen(FAR struct file *filep, FAR const char *path, inode = desc.node; DEBUGASSERT(inode != NULL); +#ifdef CONFIG_PSEUDOFS_SOFTLINKS + if (INODE_IS_HARDLINK(inode)) + { + /* The inode is a hard link. The actual inode is referenced + * by the i_private field. + */ + + DEBUGASSERT(inode->i_private != NULL); + inode = inode->i_private; + } + if (desc.nofollow && INODE_IS_SOFTLINK(inode)) { return -ELOOP; } +#endif #if defined(CONFIG_BCH) && \ !defined(CONFIG_DISABLE_MOUNTPOINT) && \ diff --git a/fs/vfs/fs_rename.c b/fs/vfs/fs_rename.c index 28afc700089..4428354929a 100644 --- a/fs/vfs/fs_rename.c +++ b/fs/vfs/fs_rename.c @@ -135,7 +135,11 @@ static int pseudorename(FAR const char *oldpath, FAR struct inode *oldinode, * directory (i.e, an operation-less inode or an inode with children)? */ - if (newinode->u.i_ops == NULL || newinode->i_child != NULL) + if ((newinode->u.i_ops == NULL || newinode->i_child != NULL) +#ifdef CONFIG_PSEUDOFS_SOFTLINKS + && !INODE_IS_HARDLINK(newinode) +#endif + ) { FAR char *subdirname; diff --git a/fs/vfs/fs_stat.c b/fs/vfs/fs_stat.c index 27f1b040f08..bceba8e70d4 100644 --- a/fs/vfs/fs_stat.c +++ b/fs/vfs/fs_stat.c @@ -267,6 +267,14 @@ int inode_stat(FAR struct inode *inode, FAR struct stat *buf, int resolve) RESET_BUF(buf); +#ifdef CONFIG_PSEUDOFS_SOFTLINKS + if (INODE_IS_HARDLINK(inode)) + { + DEBUGASSERT(inode->i_private != NULL); + inode = inode->i_private; + } +#endif + /* Handle "special" nodes */ #if defined(CONFIG_FS_NAMED_SEMAPHORES) @@ -469,6 +477,9 @@ int inode_stat(FAR struct inode *inode, FAR struct stat *buf, int resolve) buf->st_ctim = inode->i_ctime; #endif buf->st_ino = inode->i_ino; +#ifdef CONFIG_PSEUDOFS_SOFTLINKS + buf->st_nlink = INODE_GET_NLINK(inode); +#endif return OK; } diff --git a/fs/vfs/fs_unlink.c b/fs/vfs/fs_unlink.c index e92a9ec99ec..b3c54d20414 100644 --- a/fs/vfs/fs_unlink.c +++ b/fs/vfs/fs_unlink.c @@ -155,11 +155,11 @@ int nx_unlink(FAR const char *pathname) } } #endif + else if ( #ifdef CONFIG_PSEUDOFS_SOFTLINKS - else if (INODE_IS_PSEUDODIR(inode) || INODE_IS_SOFTLINK(inode)) -#else - else if (INODE_IS_PSEUDODIR(inode)) + INODE_IS_SOFTLINK(inode) || INODE_IS_HARDLINK(inode) || #endif + INODE_IS_PSEUDODIR(inode)) { /* If this is a "dangling" pseudo-file node * (i.e., it has no operations) or a soft link, diff --git a/include/nuttx/fs/fs.h b/include/nuttx/fs/fs.h index a13e56b04ab..5b9eb33a945 100644 --- a/include/nuttx/fs/fs.h +++ b/include/nuttx/fs/fs.h @@ -128,6 +128,7 @@ #define FSNODEFLAG_TYPE_SOCKET 0x00000009 /* Socket */ #define FSNODEFLAG_TYPE_PIPE 0x0000000a /* Pipe */ #define FSNODEFLAG_TYPE_NAMEDEVENT 0x0000000b /* Named event group */ +#define FSNODEFLAG_TYPE_HARDLINK 0x0000000c /* Hard link */ #define INODE_IS_TYPE(i,t) \ (((i)->i_flags & FSNODEFLAG_TYPE_MASK) == (t)) @@ -144,6 +145,7 @@ #define INODE_IS_SOCKET(i) INODE_IS_TYPE(i,FSNODEFLAG_TYPE_SOCKET) #define INODE_IS_PIPE(i) INODE_IS_TYPE(i,FSNODEFLAG_TYPE_PIPE) #define INODE_IS_NAMEDEVENT(i) INODE_IS_TYPE(i,FSNODEFLAG_TYPE_NAMEDEVENT) +#define INODE_IS_HARDLINK(i) INODE_IS_TYPE(i,FSNODEFLAG_TYPE_HARDLINK) #define INODE_GET_TYPE(i) ((i)->i_flags & FSNODEFLAG_TYPE_MASK) #define INODE_SET_TYPE(i,t) \ @@ -164,6 +166,7 @@ #define INODE_SET_SOCKET(i) INODE_SET_TYPE(i,FSNODEFLAG_TYPE_SOCKET) #define INODE_SET_PIPE(i) INODE_SET_TYPE(i,FSNODEFLAG_TYPE_PIPE) #define INODE_SET_NAMEDEVENT(i) INODE_SET_TYPE(i,FSNODEFLAG_TYPE_NAMEDEVENT) +#define INODE_SET_HARDLINK(i) INODE_SET_TYPE(i,FSNODEFLAG_TYPE_HARDLINK) /* The status change flags. * These should be or-ed together to figure out what want to change.
