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.

Reply via email to