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
