From 6fe2fcb02c7fbbe00f7188e33bb65a38dc7ebcc3 Mon Sep 17 00:00:00 2001 From: Courrier Guillaume Date: Fri, 6 Dec 2024 16:39:07 +0100 Subject: [PATCH] LU-16561: find: support width in -printf directive 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 Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/57395 Tested-by: jenkins Tested-by: Maloo Reviewed-by: Etienne AUJAMES Reviewed-by: Andreas Dilger Reviewed-by: Oleg Drokin --- lustre/tests/sanity.sh | 85 ++++++++++++++++++++++++++++++++++++ lustre/utils/liblustreapi.c | 102 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 182 insertions(+), 5 deletions(-) diff --git a/lustre/tests/sanity.sh b/lustre/tests/sanity.sh index 19bf817..cb1442e 100755 --- a/lustre/tests/sanity.sh +++ b/lustre/tests/sanity.sh @@ -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" diff --git a/lustre/utils/liblustreapi.c b/lustre/utils/liblustreapi.c index 326b7ce..7bae5a8 100644 --- a/lustre/utils/liblustreapi.c +++ b/lustre/utils/liblustreapi.c @@ -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; -- 1.8.3.1