Whamcloud - gitweb
LU-16561: find: support width in -printf directive 95/57395/4
authorCourrier Guillaume <guillaume.courrier@cea.fr>
Fri, 6 Dec 2024 15:39:07 +0000 (16:39 +0100)
committerOleg Drokin <green@whamcloud.com>
Sun, 2 Feb 2025 06:27:48 +0000 (06:27 +0000)
The width supported can be positive or negative. A negative width will align
elements on the left while positive values will align elements on the right.

We also support the 0 prefix. The man page of printf(3) indicates that the
prefix 0 should use '0' instead of spaces for padding. However, GNU-find does
not use 0 padding for all the types of values. Only '%d', '%m' and '%S' use 0
padding. This patch replicates this behavior. For instance:

$ find /mnt/lustre -printf "%13p: '%024i' '%05m'\n"
  /mnt/lustre: '      144115188193296385' '00755'
/mnt/lustre/f: '      144115205272502273' '00644'

Change-Id: I954788513cc6b43c413a3688d51c1bf1efd7d9db
Signed-off-by: Courrier Guillaume <guillaume.courrier@cea.fr>
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/57395
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Etienne AUJAMES <eaujames@ddn.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
lustre/tests/sanity.sh
lustre/utils/liblustreapi.c

index 19bf817..cb1442e 100755 (executable)
@@ -7639,6 +7639,91 @@ test_56rd() {
 }
 run_test 56rd "check lfs find --printf special files"
 
+test_56re()
+{
+       local dir=$DIR/$tdir
+       local file=$dir/a
+       local test_cases=(
+               "|%5s|" "|%05s|" "|%0s|" "|%-5s|" "|%-0s|"
+               "|%5G|" "|%05G|" "|%0G|" "|%-5G|" "|%-0G|"
+               "|%25i|" "|%025i|" "|%0i|" "|%-25i|" "|%-0i|"
+               "|%5k|" "|%05k|" "|%0k|" "|%-5k|" "|%-0k|"
+               "|%5y|" "|%05y|" "|%0y|" "|%-5y|" "|%-0y|"
+               "|%5m|" "|%05m|" "|%0m|" "|%-5m|" "|%-0m|" "|%-05m|"
+               "|%----5s|" "|%----00005s|" "|%00005s|"
+               "|%020|" "|%20|" "|%-20|" "|%-020|" "|%---020|" "|%---20|"
+       )
+
+       test_mkdir "$dir"
+       touch "$file"
+       truncate -s 37 "$file"
+
+       for format in ${test_cases[@]}; do
+               local expected=$(find "$dir" -type f -printf "$format\n")
+               local output=$($LFS find "$dir" -type f -printf "$format\n")
+
+               [[ $output == $expected ]] ||
+                       error "Unexpected output to \"$format\", expected '$expected' got '$output'"
+       done
+}
+run_test 56re "check lfs find -printf width format specifiers are consistant with regular find"
+
+test_56rf()
+{
+       local dir=$DIR/$tdir
+       local file=$dir/a
+       local test_cases
+       local poolname=testpool56rf
+       local stripe_count
+       local fid
+
+       test_mkdir "$dir"
+       pool_add $poolname || error "Could not create pool '$poolname'"
+
+       lfs setstripe -c 2 -S 4M -p $poolname "$file"
+       stripe_count=$(lfs getstripe -c "$file")
+       fid=$($LFS path2fid "$file" | sed 's/\[\(.*\)\]/\1/')
+       len=$((${#fid} + 5))
+
+       test_cases=(
+               "|%${len}LF|" "|     $fid|"
+               "|%0${len}LF|" "|     $fid|"
+               "|%0LF|" "|$fid|"
+               "|%-${len}LF|" "|$fid     |"
+               "|%-0LF|" "|$fid|"
+
+               "|%14Lp|" "|  $poolname|"
+               "|%014Lp|" "|  $poolname|"
+               "|%0Lp|" "|$poolname|"
+               "|%-14Lp|" "|$poolname  |"
+               "|%-0Lp|" "|$poolname|"
+               "|%07Lp|" "|$poolname|"
+               "|%-7Lp|" "|$poolname|"
+
+               "|%4Lc|" "|   $stripe_count|"
+               "|%04Lc|" "|   $stripe_count|"
+               "|%0Lc|" "|$stripe_count|"
+               "|%-4Lc|" "|$stripe_count   |"
+               "|%-0Lc|" "|$stripe_count|"
+
+               "|%10LS|" "|   4194304|"
+               "|%010LS|" "|   4194304|"
+               "|%0LS|" "|4194304|"
+               "|%-10LS|" "|4194304   |"
+               "|%-0LS|" "|4194304|"
+       )
+
+       for (( i = 0; i < ${#test_cases[@]}; i += 2 )); do
+               local format=${test_cases[i]}
+               local expected=${test_cases[i + 1]}
+               local output=$($LFS find "$dir" -type f -printf "$format\n")
+
+               [[ $output == $expected ]] ||
+                       error "Unexpected output to \"$format\", expected '$expected' got '$output'"
+       done
+}
+run_test 56rf "check lfs find -printf width format specifiers for lustre specific formats"
+
 test_56s() { # LU-611 #LU-9369
        [[ $OSTCOUNT -lt 2 ]] && skip_env "need at least 2 OSTs"
 
index 326b7ce..7bae5a8 100644 (file)
@@ -5526,6 +5526,56 @@ static int snprintf_access_mode(char *buffer, size_t size, __u16 mode)
        return snprintf(buffer, size, "%s", access_string);
 }
 
+static int parse_format_width(char **seq, size_t buf_size, int *width,
+                             char *padding)
+{
+       bool negative_width = false;
+       char *end = NULL;
+       int parsed = 0;
+
+       *padding = ' ';
+       *width = 0;
+
+       /* GNU find supports formats such as "%----10s" */
+       while (**seq == '-') {
+               (*seq)++;
+               parsed++;
+               negative_width = true;
+       }
+
+       /* GNU find and printf only do 0 padding on the left (width > 0)
+        * %-010m <=> %-10m.
+        */
+       if (**seq == '0' && !negative_width)
+               *padding = '0';
+
+       errno = 0;
+       *width = strtol(*seq, &end, 10);
+       if (errno != 0)
+               return -errno;
+       if (*width >= buf_size)
+               *width = buf_size - 1;
+
+       /* increase the number of processed characters */
+       parsed += end - *seq;
+       *seq = end;
+       if (negative_width)
+               *width = -*width;
+
+       /* GNU find only does 0 padding for %S, %d and %m. */
+       switch (**seq) {
+       case 'S':
+       case 'd':
+       case 'm':
+               break;
+       default:
+               *padding = ' ';
+               break;
+       }
+
+       return parsed;
+}
+
 /*
  * Interpret format specifiers beginning with '%'.
  *
@@ -5546,12 +5596,19 @@ static int printf_format_directive(char *seq, char *buffer, size_t size,
                                   int *wrote, struct find_param *param,
                                   char *path, __u32 projid, int d)
 {
-       __u16 mode = param->fp_lmd->lmd_stx.stx_mode;
        uint64_t blocks = param->fp_lmd->lmd_stx.stx_blocks;
+       __u16 mode = param->fp_lmd->lmd_stx.stx_mode;
+       char padding;
+       int width_rc;
        int rc = 1;  /* most specifiers are single character */
+       int width;
 
        *wrote = 0;
 
+       width_rc = parse_format_width(&seq, size, &width, &padding);
+       if (width_rc < 0)
+               return 0;
+
        switch (*seq) {
        case 'a': case 'A':
        case 'c': case 'C':
@@ -5584,7 +5641,7 @@ static int printf_format_directive(char *seq, char *buffer, size_t size,
        }
        case 'G':       /* GID of owner */
                *wrote = snprintf(buffer, size, "%u",
-                                  param->fp_lmd->lmd_stx.stx_gid);
+                                 param->fp_lmd->lmd_stx.stx_gid);
                break;
        case 'i':       /* inode number */
                *wrote = snprintf(buffer, size, "%llu",
@@ -5598,7 +5655,7 @@ static int printf_format_directive(char *seq, char *buffer, size_t size,
                                          path, projid, d);
                break;
        case 'm':       /* file mode in octal */
-               *wrote = snprintf(buffer, size, "%#o", (mode & (~S_IFMT)));
+               *wrote = snprintf(buffer, size, "%o", (mode & (~S_IFMT)));
                break;
        case 'M':       /* file access mode */
                *wrote = snprintf_access_mode(buffer, size, mode);
@@ -5666,11 +5723,31 @@ static int printf_format_directive(char *seq, char *buffer, size_t size,
                break;
        }
 
+       if (rc == 0)
+               /* if parsing failed, return 0 to avoid skipping width_rc */
+               return 0;
+
+       if (width > 0 && width > *wrote) {
+               /* left padding */
+               int shift = width - *wrote;
+
+               /* '\0' is added by caller if necessary */
+               memmove(buffer + shift, buffer, *wrote);
+               memset(buffer, padding, shift);
+               *wrote += shift;
+       } else if (width < 0 && -width > *wrote) {
+               /* right padding */
+               int shift = -width - *wrote;
+
+               memset(buffer + *wrote, padding, shift);
+               *wrote += shift;
+       }
+
        if (*wrote >= size)
                /* output of snprintf was truncated */
                *wrote = size - 1;
 
-       return rc;
+       return width_rc + rc;
 }
 
 /*
@@ -5706,9 +5783,10 @@ static void printf_format_string(struct find_param *param, char *path,
                        rc = printf_format_directive(fmt_char + 1, buff,
                                                  buff_size, &written, param,
                                                  path, projid, d);
-               } else if (*fmt_char == '\\')
+               } else if (*fmt_char == '\\') {
                        rc = printf_format_escape(fmt_char + 1, buff,
                                                  buff_size, &written);
+               }
 
                if (rc > 0) {
                        /* Either a '\' escape or '%' format was processed.
@@ -5717,6 +5795,8 @@ static void printf_format_string(struct find_param *param, char *path,
                        fmt_char += (rc + 1);
                        buff += written;
                        buff_size -= written;
+               } else if (rc < 0) {
+                       return;
                } else {
                        /* Regular char or invalid escape/format.
                         * Either way, copy current character.
@@ -6644,7 +6724,19 @@ static int validate_printf_fmt(char *c)
                return 0;
        }
 
+       /* GNU find supports formats such as "%----10s" */
+       while (curr == '-')
+               curr = *(++c);
+
+       if (isdigit(curr)) {
+               /* skip width format specifier */
+               while (isdigit(*c))
+                       c++;
+       }
+
+       curr = *c;
        next = *(c + 1);
+
        if ((next == '\0') || (next == '%') || (next == '\\'))
                /* Treat as single char format directive */
                goto check_single;