This is an automated email from the ASF dual-hosted git repository.

jiuzhudong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 450e760d16e81a75112450e858de64955d20b8f2
Author: yinshengkai <[email protected]>
AuthorDate: Tue Dec 23 22:01:29 2025 +0800

    fs/fat: add support for FIOC_FILEPATH ioctl
    
    This patch adds support for the FIOC_FILEPATH ioctl command in the
    FAT filesystem, allowing applications to retrieve the full path of
    an open file descriptor.
    
    Key features:
    - Implements fat_getfilepath() to construct file paths by traversing
      parent directories using ".." entries
    - Adds fat_findlfnstart() helper to locate the start of LFN sequences
    - Supports both FAT12/16 and FAT32 filesystem types
    - Handles both regular directories and root directory traversal
    
    This functionality is useful for debugging, logging, and applications
    that need to track file paths at runtime.
    
    Signed-off-by: yinshengkai <[email protected]>
---
 fs/fat/fs_fat32.c | 413 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 413 insertions(+)

diff --git a/fs/fat/fs_fat32.c b/fs/fat/fs_fat32.c
index fc10b867a1a..dab463f3dad 100644
--- a/fs/fat/fs_fat32.c
+++ b/fs/fat/fs_fat32.c
@@ -1323,6 +1323,400 @@ errout_with_lock:
   return ret;
 }
 
+/****************************************************************************
+ * Name: fat_findlfnstart
+ *
+ * Description:
+ *   Given a short filename directory entry position, find the start of
+ *   the LFN sequence (if any). Returns the index of the first LFN entry
+ *   or the original index if no LFN exists.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_FAT_LFN
+static int fat_findlfnstart(FAR struct fat_mountpt_s *fs,
+                            off_t sector, uint16_t index,
+                            FAR off_t *lfnsector, FAR uint16_t *lfnindex)
+{
+  FAR uint8_t *direntry;
+  uint8_t seqno;
+  int ret;
+
+  *lfnsector = sector;
+  *lfnindex = index;
+  seqno = 1;
+
+  while (index > 0 || sector > fs->fs_database)
+    {
+      uint16_t previndex;
+      off_t prevsector;
+
+      /* Move to previous entry */
+
+      if (index > 0)
+        {
+          previndex = index - 1;
+          prevsector = sector;
+        }
+      else
+        {
+          /* Need to go to previous sector */
+
+          prevsector = sector - 1;
+          if (prevsector < fs->fs_database)
+            {
+              break;
+            }
+
+          previndex = DIRSEC_NDIRS(fs) - 1;
+        }
+
+      /* Read the previous sector */
+
+      ret = fat_fscacheread(fs, prevsector);
+      if (ret < 0)
+        {
+          return ret;
+        }
+
+      direntry = &fs->fs_buffer[DIRSEC_BYTENDX(fs, previndex)];
+
+      /* Check if this is an LFN entry */
+
+      if (DIR_GETATTRIBUTES(direntry) != LDDIR_LFNATTR)
+        {
+          break;
+        }
+
+      /* Verify sequence number */
+
+      if ((LDIR_GETSEQ(direntry) & LDIR0_SEQ_MASK) != seqno)
+        {
+          break;
+        }
+
+      /* Update position to this LFN entry */
+
+      *lfnsector = prevsector;
+      *lfnindex = previndex;
+      sector = prevsector;
+      index = previndex;
+
+      /* Check if this is the last (first appearing) LFN entry */
+
+      if ((LDIR_GETSEQ(direntry) & LDIR0_LAST) != 0)
+        {
+          break;
+        }
+
+      seqno++;
+    }
+
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: fat_getfilepath
+ *
+ * Description:
+ *   Build the relative file path by traversing parent directories.
+ *   Uses ".." directory entries to walk up the directory tree.
+ *
+ ****************************************************************************/
+
+static int fat_getfilepath(FAR struct fat_mountpt_s *fs,
+                           FAR struct fat_file_s *ff,
+                           FAR char *path, size_t pathlen)
+{
+  struct fat_dirent_s fdir;
+  FAR uint8_t *direntry;
+  char names[PATH_MAX];
+  struct dirent entry;
+  size_t totallen = 0;
+  off_t currdircluster;
+  off_t parentcluster;
+  size_t pathbaselen;
+  uint16_t dirindex;
+  size_t namelen;
+  off_t dirsector;
+  int ret;
+
+  names[0] = '\0';
+  dirsector = ff->ff_dirsector;
+  dirindex = ff->ff_dirindex;
+
+  /* Remove trailing slash from path if present */
+
+  pathbaselen = strlen(path);
+  if (pathbaselen > 0 && path[pathbaselen - 1] == '/')
+    {
+      path[pathbaselen - 1] = '\0';
+      pathbaselen--;
+    }
+
+  /* Loop to traverse parent directories */
+
+  while (dirsector != 0)
+    {
+      off_t readsector = dirsector;
+      uint16_t readindex = dirindex;
+      off_t currsector;
+      off_t searchcluster;
+      unsigned int idx = 0;
+      bool found = false;
+      bool fat1x_root = false;
+
+#ifdef CONFIG_FAT_LFN
+      /* Find the start of LFN sequence for this entry */
+
+      ret = fat_findlfnstart(fs, dirsector, dirindex,
+                             &readsector, &readindex);
+      if (ret < 0)
+        {
+          return ret;
+        }
+#endif
+
+      /* Read the directory sector containing this entry */
+
+      ret = fat_fscacheread(fs, readsector);
+      if (ret < 0)
+        {
+          return ret;
+        }
+
+      /* Get the directory entry and extract the filename */
+
+      memset(&fdir, 0, sizeof(fdir));
+      fdir.dir.fd_currsector = readsector;
+      fdir.dir.fd_index = readindex;
+
+      ret = fat_dirname2path(fs, (FAR struct fs_dirent_s *)&fdir, &entry);
+      if (ret < 0)
+        {
+          return ret;
+        }
+
+      /* Skip "." and ".." entries - these are special directory entries */
+
+      if (entry.d_name[0] == '.' &&
+          (entry.d_name[1] == '\0' ||
+           (entry.d_name[1] == '.' && entry.d_name[2] == '\0')))
+        {
+          /* This is "." or "..", skip to root check */
+
+          break;
+        }
+
+      /* Prepend this name to the accumulated path */
+
+      namelen = strlen(entry.d_name);
+      if (totallen + namelen + 2 > sizeof(names))
+        {
+          return -ENAMETOOLONG;
+        }
+
+      if (totallen > 0)
+        {
+          memmove(names + namelen + 1, names, totallen + 1);
+          names[namelen] = '/';
+          memcpy(names, entry.d_name, namelen);
+          totallen += namelen + 1;
+        }
+      else
+        {
+          memcpy(names, entry.d_name, namelen + 1);
+          totallen = namelen;
+        }
+
+      /* Determine the cluster of the directory containing this entry */
+
+      if (dirsector < fs->fs_database)
+        {
+          /* Already in root directory (FAT12/16), done */
+
+          break;
+        }
+
+      currdircluster = ((dirsector - fs->fs_database) /
+                        fs->fs_fatsecperclus) + 2;
+
+      /* Check if we're already at root directory (FAT32), done */
+
+      if (fs->fs_type == FSTYPE_FAT32 && currdircluster == fs->fs_rootbase)
+        {
+          break;
+        }
+
+      /* Read the first sector of current directory to get ".." entry */
+
+      ret = fat_fscacheread(fs, fat_cluster2sector(fs, currdircluster));
+      if (ret < 0)
+        {
+          return ret;
+        }
+
+      /* ".." entry is at index 1, get parent cluster */
+
+      direntry = &fs->fs_buffer[DIR_SIZE];
+      parentcluster = ((off_t)DIR_GETFSTCLUSTHI(direntry) << 16) |
+                      DIR_GETFSTCLUSTLO(direntry);
+
+      /* If parent cluster is 0 or root, search in root directory */
+
+      if (parentcluster == 0 ||
+          (fs->fs_type == FSTYPE_FAT32 && parentcluster == fs->fs_rootbase))
+        {
+          /* Root directory */
+
+          if (fs->fs_type == FSTYPE_FAT32)
+            {
+              /* FAT32: root directory starts at fs_rootbase cluster */
+
+              searchcluster = fs->fs_rootbase;
+              currsector = fat_cluster2sector(fs, fs->fs_rootbase);
+              if (currsector < 0)
+                {
+                  return (int)currsector;
+                }
+            }
+          else
+            {
+              /* FAT12/16: root directory is before data area */
+
+              fat1x_root = true;
+              searchcluster = 0;
+              currsector = fs->fs_rootbase;
+            }
+        }
+      else
+        {
+          /* Regular parent directory */
+
+          searchcluster = parentcluster;
+          currsector = fat_cluster2sector(fs, parentcluster);
+          if (currsector < 0)
+            {
+              return (int)currsector;
+            }
+        }
+
+      /* Search in parent directory for entry pointing to current dir */
+
+      while (!found)
+        {
+          ret = fat_fscacheread(fs, currsector);
+          if (ret < 0)
+            {
+              return ret;
+            }
+
+          for (; idx < DIRSEC_NDIRS(fs); idx++)
+            {
+              off_t cluster;
+#ifdef CONFIG_FAT_LFN
+              uint8_t attr;
+#endif
+
+              direntry = &fs->fs_buffer[idx * DIR_SIZE];
+
+              if (direntry[0] == DIR0_ALLEMPTY)
+                {
+                  return -ENOENT;
+                }
+
+              if (direntry[0] == DIR0_EMPTY)
+                {
+                  continue;
+                }
+
+#ifdef CONFIG_FAT_LFN
+              /* Skip LFN entries */
+
+              attr = DIR_GETATTRIBUTES(direntry);
+              if (attr == LDDIR_LFNATTR)
+                {
+                  continue;
+                }
+
+              if ((attr & FATATTR_DIRECTORY) == 0)
+#else
+              if ((DIR_GETATTRIBUTES(direntry) & FATATTR_DIRECTORY) == 0)
+#endif
+                {
+                  continue;
+                }
+
+              /* Skip "." and ".." */
+
+              if (direntry[0] == '.')
+                {
+                  continue;
+                }
+
+              cluster = ((off_t)DIR_GETFSTCLUSTHI(direntry) << 16) |
+                        DIR_GETFSTCLUSTLO(direntry);
+
+              if (cluster == currdircluster)
+                {
+                  dirsector = currsector;
+                  dirindex = idx;
+                  found = true;
+                  break;
+                }
+            }
+
+          if (!found)
+            {
+              idx = 0;
+              currsector++;
+
+              /* Check if we need to move to next cluster/sector */
+
+              if (fat1x_root)
+                {
+                  /* FAT12/16 root directory: check sector limit */
+
+                  if (currsector >= fs->fs_database)
+                    {
+                      return -ENOENT;
+                    }
+                }
+              else if ((currsector - fat_cluster2sector(fs, searchcluster))
+                        >= fs->fs_fatsecperclus)
+                {
+                  /* Move to next cluster */
+
+                  searchcluster = fat_getcluster(fs, searchcluster);
+                  if (searchcluster < 2 ||
+                      searchcluster >= fs->fs_nclusters + 2)
+                    {
+                      return -ENOENT;
+                    }
+
+                  currsector = fat_cluster2sector(fs, searchcluster);
+                }
+            }
+        }
+    }
+
+  /* Copy the constructed path to output */
+
+  if (pathbaselen + totallen + 2 > pathlen)
+    {
+      return -ENAMETOOLONG;
+    }
+
+  if (totallen > 0)
+    {
+      path[pathbaselen] = '/';
+      memcpy(path + pathbaselen + 1, names, totallen + 1);
+    }
+
+  return OK;
+}
+
 /****************************************************************************
  * Name: fat_ioctl
  ****************************************************************************/
@@ -1368,6 +1762,25 @@ static int fat_ioctl(FAR struct file *filep, int cmd, 
unsigned long arg)
       return ret;
     }
 
+  switch (cmd)
+    {
+      case FIOC_FILEPATH:
+        {
+          FAR char *path = (FAR char *)(uintptr_t)arg;
+          ret = inode_getpath(filep->f_inode, path, PATH_MAX);
+          if (ret >= 0)
+            {
+              ret = fat_getfilepath(fs, ff, path, PATH_MAX);
+            }
+
+          nxmutex_unlock(&fs->fs_lock);
+          return ret;
+        }
+
+      default:
+        break;
+    }
+
   /* ioctl calls are just passed through to the contained block driver */
 
   nxmutex_unlock(&fs->fs_lock);

Reply via email to