Use the STATX_SUBVOL mask to request the stx_subvol field from
statx(), and detect subvolume roots by comparing a child directory's
subvolume ID with its parent's.  When the IDs differ, the directory
is a subvolume root and is coloured using the new 'sv' indicator
(default: cyan on blue, 36;34).

* src/ls.c: (STATX_SUBVOL): Define fallback for pre-6.11 kernels.
(parent_subvol, parent_has_subvol): New file-scope variables.
(calc_req_mask): Request STATX_SUBVOL when colouring.
(do_statx): Add subvol_out param.
(stat_for_subvol): New helper.
(print_dir): Stat parent directory for its subvol ID.
(gobble_file): Compare child vs parent subvol ID to detect roots.
For command-line args, stat the parent directory on demand.
* src/dircolors.hin: Add SUBVOL entry.
* src/dircolors.c: Add "sv" indicator type.

Signed-off-by: Mark Harmstone <[email protected]>
---
 src/dircolors.c   |   4 +-
 src/dircolors.hin |   1 +
 src/ls.c          | 102 ++++++++++++++++++++++++++++++++++++++--------
 3 files changed, 88 insertions(+), 19 deletions(-)

diff --git a/src/dircolors.c b/src/dircolors.c
index bd7706f05..bf5b379fb 100644
--- a/src/dircolors.c
+++ b/src/dircolors.c
@@ -57,7 +57,7 @@ static char const *const slack_codes[] =
   "CHR", "CHAR", "DOOR", "EXEC", "LEFT", "LEFTCODE", "RIGHT", "RIGHTCODE",
   "END", "ENDCODE", "SUID", "SETUID", "SGID", "SETGID", "STICKY",
   "OTHER_WRITABLE", "OWR", "STICKY_OTHER_WRITABLE", "OWT", "CAPABILITY",
-  "MULTIHARDLINK", "CLRTOEOL", NULL
+  "MULTIHARDLINK", "CLRTOEOL", "SUBVOL", NULL
 };
 
 static char const *const ls_codes[] =
@@ -65,7 +65,7 @@ static char const *const ls_codes[] =
   "no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi",
   "so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec",
   "su", "su", "sg", "sg", "st", "ow", "ow", "tw", "tw", "ca", "mh", "cl",
-  NULL
+  "sv", NULL
 };
 static_assert (countof (slack_codes) == countof (ls_codes));
 
diff --git a/src/dircolors.hin b/src/dircolors.hin
index a470767b2..6524f3f3c 100644
--- a/src/dircolors.hin
+++ b/src/dircolors.hin
@@ -77,6 +77,7 @@ CAPABILITY 00 # regular file with capability (very expensive 
to lookup)
 STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w)
 OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky
 STICKY 37;44   # dir with the sticky bit set (+t) and not other-writable
+SUBVOL 36;44   # subvolume
 
 # This is for regular files with execute permission:
 EXEC 01;32
diff --git a/src/ls.c b/src/ls.c
index 70ddba10d..ccd9001a1 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -123,6 +123,11 @@
                       : (ls_mode == LS_MULTI_COL \
                          ? "dir" : "vdir"))
 
+/* STATX_SUBVOL was added in Linux 6.11.  */
+#ifndef STATX_SUBVOL
+# define STATX_SUBVOL 0x00008000U
+#endif
+
 #define AUTHORS \
   proper_name ("Richard M. Stallman"), \
   proper_name ("David MacKenzie")
@@ -234,6 +239,9 @@ struct fileinfo
     /* For color listings, true if a regular file has capability info.  */
     bool has_capability;
 
+    /* For color listings, true if a directory is a subvolume root.  */
+    bool is_subvol;
+
     /* Whether file name needs quoting. tri-state with -1 == unknown.  */
     int quoted;
 
@@ -573,6 +581,11 @@ static bool print_with_color;
 
 static bool print_hyperlink;
 
+/* The subvolume ID of the parent directory currently being listed.
+   Used to detect subvolume root entries by comparison.  */
+static uint64_t parent_subvol;
+static bool parent_has_subvol;
+
 /* Whether we used any colors in the output so far.  If so, we will
    need to restore the default color later.  If not, we will need to
    call prep_non_filename_text before using color for the first time. */
@@ -601,7 +614,7 @@ enum indicator_no
     C_FIFO, C_SOCK,
     C_BLK, C_CHR, C_MISSING, C_ORPHAN, C_EXEC, C_DOOR, C_SETUID, C_SETGID,
     C_STICKY, C_OTHER_WRITABLE, C_STICKY_OTHER_WRITABLE, C_CAP, 
C_MULTIHARDLINK,
-    C_CLR_TO_EOL
+    C_CLR_TO_EOL, C_SUBVOL
   };
 
 static char const indicator_name[][2]=
@@ -612,7 +625,7 @@ static char const indicator_name[][2]=
     {'b','d'}, {'c','d'}, {'m','i'}, {'o','r'}, {'e','x'},
     {'d','o'}, {'s','u'}, {'s','g'},
     {'s','t'}, {'o','w'}, {'t','w'}, {'c','a'}, {'m','h'},
-    {'c','l'}
+    {'c','l'}, {'s','v'}
   };
 
 struct color_ext_type
@@ -662,6 +675,8 @@ static struct bin_str color_indicator[] =
     { 0, NULL },                       /* mh: disabled by default */
     { 3, ((char const [])
           {'\033','[','K'}) },         /* cl: clear to end of line */
+    { 5, ((char const [])
+          {'3','6',';','4','4'}) },    /* sv: subvolume: cyan on blue */
   };
 
 /* A list mapping file extensions to corresponding display sequence.  */
@@ -1196,12 +1211,15 @@ calc_req_mask (void)
       unreachable ();
     }
 
+  if (print_with_color)
+    mask |= STATX_SUBVOL;
+
   return mask;
 }
 
 static int
 do_statx (int fd, char const *name, struct stat *st, int flags,
-          unsigned int mask)
+          unsigned int mask, uint64_t *subvol_out)
 {
   struct statx stx;
   bool want_btime = mask & STATX_BTIME;
@@ -1218,50 +1236,60 @@ do_statx (int fd, char const *name, struct stat *st, 
int flags,
           else
             st->st_mtim.tv_sec = st->st_mtim.tv_nsec = -1;
         }
+      if (subvol_out && (stx.stx_mask & STATX_SUBVOL))
+        *subvol_out = stx.stx_subvol;
     }
 
   return ret;
 }
 
 static int
-do_stat (char const *name, struct stat *st)
+do_stat (char const *name, struct stat *st, uint64_t *subvol_out)
 {
-  return do_statx (AT_FDCWD, name, st, 0, calc_req_mask ());
+  return do_statx (AT_FDCWD, name, st, 0, calc_req_mask (), subvol_out);
 }
 
 static int
-do_lstat (char const *name, struct stat *st)
+do_lstat (char const *name, struct stat *st, uint64_t *subvol_out)
 {
-  return do_statx (AT_FDCWD, name, st, AT_SYMLINK_NOFOLLOW, calc_req_mask ());
+  return do_statx (AT_FDCWD, name, st, AT_SYMLINK_NOFOLLOW, calc_req_mask (),
+                   subvol_out);
 }
 
 static int
 stat_for_mode (char const *name, struct stat *st)
 {
-  return do_statx (AT_FDCWD, name, st, 0, STATX_MODE);
+  return do_statx (AT_FDCWD, name, st, 0, STATX_MODE, NULL);
+}
+
+static int
+stat_for_subvol (char const *name, uint64_t *subvol_out)
+{
+  struct stat st;
+  return do_statx (AT_FDCWD, name, &st, 0, STATX_SUBVOL, subvol_out);
 }
 
 /* dev+ino should be static, so no need to sync with backing store */
 static int
 stat_for_ino (char const *name, struct stat *st)
 {
-  return do_statx (AT_FDCWD, name, st, 0, STATX_INO);
+  return do_statx (AT_FDCWD, name, st, 0, STATX_INO, NULL);
 }
 
 static int
 fstat_for_ino (int fd, struct stat *st)
 {
-  return do_statx (fd, "", st, AT_EMPTY_PATH, STATX_INO);
+  return do_statx (fd, "", st, AT_EMPTY_PATH, STATX_INO, NULL);
 }
 #else
 static int
-do_stat (char const *name, struct stat *st)
+do_stat (char const *name, struct stat *st, MAYBE_UNUSED uint64_t *subvol_out)
 {
   return stat (name, st);
 }
 
 static int
-do_lstat (char const *name, struct stat *st)
+do_lstat (char const *name, struct stat *st, MAYBE_UNUSED uint64_t *subvol_out)
 {
   return lstat (name, st);
 }
@@ -1272,6 +1300,13 @@ stat_for_mode (char const *name, struct stat *st)
   return stat (name, st);
 }
 
+static int
+stat_for_subvol (MAYBE_UNUSED char const *name,
+                 MAYBE_UNUSED uint64_t *subvol_out)
+{
+  return -1;
+}
+
 static int
 stat_for_ino (char const *name, struct stat *st)
 {
@@ -2997,6 +3032,18 @@ print_dir (char const *name, char const *realname, bool 
command_line_arg)
 
   clear_files ();
 
+  /* Get the parent directory's subvolume ID for detecting subvolume roots.  */
+  parent_has_subvol = false;
+  if (print_with_color && is_colored (C_SUBVOL))
+    {
+      uint64_t subvol = 0;
+      if (stat_for_subvol (name, &subvol) == 0 && subvol != 0)
+        {
+          parent_subvol = subvol;
+          parent_has_subvol = true;
+        }
+    }
+
   if (recursive || print_dir_name)
     {
       if (!first)
@@ -3349,7 +3396,8 @@ gobble_file (char const *name, enum filetype type, ino_t 
inode,
       || ((type == directory || type == unknown) && print_with_color
           && (is_colored (C_OTHER_WRITABLE)
               || is_colored (C_STICKY)
-              || is_colored (C_STICKY_OTHER_WRITABLE)))
+              || is_colored (C_STICKY_OTHER_WRITABLE)
+              || is_colored (C_SUBVOL)))
       /* When dereferencing symlinks, the inode and type must come from
          stat, but readdir provides the inode and type of lstat.  */
       || ((print_inode || format_needs_type)
@@ -3386,6 +3434,7 @@ gobble_file (char const *name, enum filetype type, ino_t 
inode,
   else
     {
       int err;
+      uint64_t child_subvol = 0;
 
       if (print_hyperlink)
         {
@@ -3399,7 +3448,7 @@ gobble_file (char const *name, enum filetype type, ino_t 
inode,
       switch (dereference)
         {
         case DEREF_ALWAYS:
-          err = do_stat (full_name, &f->stat);
+          err = do_stat (full_name, &f->stat, &child_subvol);
           do_deref = true;
           break;
 
@@ -3408,7 +3457,7 @@ gobble_file (char const *name, enum filetype type, ino_t 
inode,
           if (command_line_arg)
             {
               bool need_lstat;
-              err = do_stat (full_name, &f->stat);
+              err = do_stat (full_name, &f->stat, &child_subvol);
               do_deref = true;
 
               if (dereference == DEREF_COMMAND_LINE_ARGUMENTS)
@@ -3429,7 +3478,7 @@ gobble_file (char const *name, enum filetype type, ino_t 
inode,
           FALLTHROUGH;
 
         case DEREF_NEVER:
-          err = do_lstat (full_name, &f->stat);
+          err = do_lstat (full_name, &f->stat, &child_subvol);
           do_deref = false;
           break;
 
@@ -3456,6 +3505,23 @@ gobble_file (char const *name, enum filetype type, ino_t 
inode,
 
       f->stat_ok = true;
       f->filetype = type = d_type_filetype[IFTODT (f->stat.st_mode)];
+
+      /* A directory is a subvolume root if its subvol ID differs from
+         its parent's.  For command-line args, stat the parent now.  */
+      if (child_subvol != 0 && S_ISDIR (f->stat.st_mode))
+        {
+          if (command_line_arg)
+            {
+              uint64_t p_subvol = 0;
+              char *parent = dir_name (full_name);
+              stat_for_subvol (parent, &p_subvol);
+              f->is_subvol = (p_subvol != 0 && p_subvol != child_subvol);
+              free (parent);
+            }
+          else
+            f->is_subvol = (parent_has_subvol
+                            && parent_subvol != child_subvol);
+        }
     }
 
   if (type == directory && command_line_arg && !immediate_dirs)
@@ -4960,7 +5026,9 @@ get_color_indicator (const struct fileinfo *f, bool 
symlink_target)
         {
           type = C_DIR;
 
-          if ((mode & S_ISVTX) && (mode & S_IWOTH)
+          if (f->is_subvol && is_colored (C_SUBVOL))
+            type = C_SUBVOL;
+          else if ((mode & S_ISVTX) && (mode & S_IWOTH)
               && is_colored (C_STICKY_OTHER_WRITABLE))
             type = C_STICKY_OTHER_WRITABLE;
           else if ((mode & S_IWOTH) != 0 && is_colored (C_OTHER_WRITABLE))
-- 
2.52.0


Reply via email to