From 731826cc8d9467fa1c419b6f65ab926711bdf2f9 Mon Sep 17 00:00:00 2001 From: James Youngman Date: Sat, 31 Oct 2015 23:24:28 +0000 Subject: [PATCH] find: adaptive column alignment (resolves Savannah bug #45780) * lib/listfile.c (list_file): For aligned fields, use the number of characters output to deduce whether our current idea of the maximum width of each field is too small. When this happens, increase the field width. Keep track of the field width in a static variable for each field. Do this for the inode number, number of blocks, owner, group, major and minor device numbers, and the file size. Use mbswidth in some places to count characters. * find/print.c (do_fprintf): Mention the potential portability problems in casting ino_t to uintmax_t. * bootstrap.conf (gnulib_modules): Add mbswidth. * po/Makevars (XGETTEXT_OPTIONS): updated by running bootstrap. *NEWS: Mention this bugfix. --- NEWS | 2 + bootstrap.conf | 1 + find/print.c | 1311 ++++++++++++++++++++++++------------------------ lib/listfile.c | 523 +++++++++++-------- po/Makevars | 12 +- 5 files changed, 991 insertions(+), 858 deletions(-) diff --git a/NEWS b/NEWS index 6fc71d64..f1cabec1 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,8 @@ GNU findutils NEWS - User visible changes. -*- outline -*- (allout) * Major changes in release 4.5.15-git, 2014-MM-DD ** Bug Fixes +#45780: inode column is badly aligned when running 'find -ls' + #45585: unclear description of -newerXY in manual page. #45505: give a more explicit error message when the argument to -regex diff --git a/bootstrap.conf b/bootstrap.conf index 94c4fc78..5617046d 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -118,6 +118,7 @@ gnulib_modules=" math mbrtowc mbscasestr + mbswidth mbsstr mktime modechange diff --git a/find/print.c b/find/print.c index 345933e9..6973426d 100644 --- a/find/print.c +++ b/find/print.c @@ -76,12 +76,12 @@ Return the address of the `next' pointer of the new segment. */ struct segment ** make_segment (struct segment **segment, - char *format, - int len, - int kind, - char format_char, - char aux_format_char, - struct predicate *pred) + char *format, + int len, + int kind, + char format_char, + char aux_format_char, + struct predicate *pred) { enum EvaluationCost mycost = NeedsNothing; char *fmt; @@ -109,89 +109,89 @@ make_segment (struct segment **segment, assert (0 == aux_format_char); *fmt = '\0'; if (mycost > pred->p_cost) - pred->p_cost = NeedsNothing; + pred->p_cost = NeedsNothing; return &(*segment)->next; } assert (kind == KIND_FORMAT); switch (format_char) { - case '%': /* literal % */ + case '%': /* literal % */ *fmt++ = '%'; break; - case 'l': /* object of symlink */ + case 'l': /* object of symlink */ pred->need_stat = true; mycost = NeedsLinkName; *fmt++ = 's'; break; - case 'y': /* file type */ + case 'y': /* file type */ pred->need_type = true; mycost = NeedsType; *fmt++ = 's'; break; - case 'i': /* inode number */ + case 'i': /* inode number */ pred->need_inum = true; mycost = NeedsInodeNumber; *fmt++ = 's'; break; - case 'a': /* atime in `ctime' format */ - case 'A': /* atime in user-specified strftime format */ - case 'B': /* birth time in user-specified strftime format */ - case 'c': /* ctime in `ctime' format */ - case 'C': /* ctime in user-specified strftime format */ - case 'F': /* file system type */ - case 'g': /* group name */ - case 'M': /* mode in `ls -l' format (eg., "drwxr-xr-x") */ - case 's': /* size in bytes */ - case 't': /* mtime in `ctime' format */ - case 'T': /* mtime in user-specified strftime format */ - case 'u': /* user name */ + case 'a': /* atime in `ctime' format */ + case 'A': /* atime in user-specified strftime format */ + case 'B': /* birth time in user-specified strftime format */ + case 'c': /* ctime in `ctime' format */ + case 'C': /* ctime in user-specified strftime format */ + case 'F': /* file system type */ + case 'g': /* group name */ + case 'M': /* mode in `ls -l' format (eg., "drwxr-xr-x") */ + case 's': /* size in bytes */ + case 't': /* mtime in `ctime' format */ + case 'T': /* mtime in user-specified strftime format */ + case 'u': /* user name */ pred->need_stat = true; mycost = NeedsStatInfo; *fmt++ = 's'; break; - case 'S': /* sparseness */ + case 'S': /* sparseness */ pred->need_stat = true; mycost = NeedsStatInfo; *fmt++ = 'g'; break; - case 'Y': /* symlink pointed file type */ + case 'Y': /* symlink pointed file type */ pred->need_stat = true; - mycost = NeedsType; /* true for amortised effect */ + mycost = NeedsType; /* true for amortised effect */ *fmt++ = 's'; break; - case 'f': /* basename of path */ - case 'h': /* leading directories part of path */ - case 'p': /* pathname */ - case 'P': /* pathname with ARGV element stripped */ + case 'f': /* basename of path */ + case 'h': /* leading directories part of path */ + case 'p': /* pathname */ + case 'P': /* pathname with ARGV element stripped */ *fmt++ = 's'; break; - case 'Z': /* SELinux security context */ + case 'Z': /* SELinux security context */ mycost = NeedsAccessInfo; *fmt++ = 's'; break; - case 'H': /* ARGV element file was found under */ + case 'H': /* ARGV element file was found under */ *fmt++ = 's'; break; /* Numeric items that one might expect to honour * #, 0, + flags but which do not. */ - case 'G': /* GID number */ - case 'U': /* UID number */ - case 'b': /* size in 512-byte blocks (NOT birthtime in ctime fmt)*/ + case 'G': /* GID number */ + case 'U': /* UID number */ + case 'b': /* size in 512-byte blocks (NOT birthtime in ctime fmt)*/ case 'D': /* Filesystem device on which the file exits */ - case 'k': /* size in 1K blocks */ - case 'n': /* number of links */ + case 'k': /* size in 1K blocks */ + case 'n': /* number of links */ pred->need_stat = true; mycost = NeedsStatInfo; *fmt++ = 's'; @@ -199,11 +199,11 @@ make_segment (struct segment **segment, /* Numeric items that DO honour #, 0, + flags. */ - case 'd': /* depth in search tree (0 = ARGV element) */ + case 'd': /* depth in search tree (0 = ARGV element) */ *fmt++ = 'd'; break; - case 'm': /* mode as octal number (perms only) */ + case 'm': /* mode as octal number (perms only) */ *fmt++ = 'o'; pred->need_stat = true; mycost = NeedsStatInfo; @@ -309,12 +309,12 @@ get_format_specifer_length(char ch) bool insert_fprintf (struct format_val *vec, - const struct parser_table *entry, - char *format) + const struct parser_table *entry, + char *format) { char *segstart = format; - char *fmt_editpos; /* Current address in scanning `format'. */ - struct segment **segmentp; /* Address of current segment. */ + char *fmt_editpos; /* Current address in scanning `format'. */ + struct segment **segmentp; /* Address of current segment. */ struct predicate *our_pred; our_pred = insert_primary_withpred (entry, pred_fprintf, format); @@ -330,119 +330,119 @@ insert_fprintf (struct format_val *vec, for (fmt_editpos = segstart; *fmt_editpos; fmt_editpos++) { if (fmt_editpos[0] == '\\' && fmt_editpos[1] == 'c') - { - make_segment (segmentp, segstart, fmt_editpos - segstart, - KIND_STOP, 0, 0, - our_pred); - if (our_pred->need_stat && (our_pred->p_cost < NeedsStatInfo)) - our_pred->p_cost = NeedsStatInfo; - return true; - } + { + make_segment (segmentp, segstart, fmt_editpos - segstart, + KIND_STOP, 0, 0, + our_pred); + if (our_pred->need_stat && (our_pred->p_cost < NeedsStatInfo)) + our_pred->p_cost = NeedsStatInfo; + return true; + } else if (*fmt_editpos == '\\') - { - size_t readpos = 1; - if (!fmt_editpos[readpos]) - { - error (0, 0, _("warning: escape `\\' followed by nothing at all")); - --readpos; - /* (*fmt_editpos) is already '\\' and that's a reasonable result. */ - } - else if (is_octal_char(fmt_editpos[readpos])) - { - size_t consumed = 0; - *fmt_editpos = parse_octal_escape(fmt_editpos + readpos, &consumed); - readpos += consumed; - } - else - { - const char val = parse_escape_char(fmt_editpos[readpos]); - if (val) - { - fmt_editpos[0] = val; - } - else - { - error (0, 0, _("warning: unrecognized escape `\\%c'"), - fmt_editpos[readpos]); - fmt_editpos += readpos; - continue; - } - } - segmentp = make_segment (segmentp, - segstart, fmt_editpos - segstart + 1, - KIND_PLAIN, 0, 0, - our_pred); - segstart = fmt_editpos + readpos + 1; /* Move past the escape. */ - fmt_editpos += readpos; /* Incremented immediately by `for'. */ - } + { + size_t readpos = 1; + if (!fmt_editpos[readpos]) + { + error (0, 0, _("warning: escape `\\' followed by nothing at all")); + --readpos; + /* (*fmt_editpos) is already '\\' and that's a reasonable result. */ + } + else if (is_octal_char(fmt_editpos[readpos])) + { + size_t consumed = 0; + *fmt_editpos = parse_octal_escape(fmt_editpos + readpos, &consumed); + readpos += consumed; + } + else + { + const char val = parse_escape_char(fmt_editpos[readpos]); + if (val) + { + fmt_editpos[0] = val; + } + else + { + error (0, 0, _("warning: unrecognized escape `\\%c'"), + fmt_editpos[readpos]); + fmt_editpos += readpos; + continue; + } + } + segmentp = make_segment (segmentp, + segstart, fmt_editpos - segstart + 1, + KIND_PLAIN, 0, 0, + our_pred); + segstart = fmt_editpos + readpos + 1; /* Move past the escape. */ + fmt_editpos += readpos; /* Incremented immediately by `for'. */ + } else if (fmt_editpos[0] == '%') - { - size_t len; - if (fmt_editpos[1] == 0) - { - /* Trailing %. We don't like those. */ - error (EXIT_FAILURE, 0, - _("error: %s at end of format string"), fmt_editpos); - } + { + size_t len; + if (fmt_editpos[1] == 0) + { + /* Trailing %. We don't like those. */ + error (EXIT_FAILURE, 0, + _("error: %s at end of format string"), fmt_editpos); + } - if (fmt_editpos[1] == '%') /* %% produces just %. */ - len = 1; - else - len = get_format_flags_length(fmt_editpos); - fmt_editpos += len; + if (fmt_editpos[1] == '%') /* %% produces just %. */ + len = 1; + else + len = get_format_flags_length(fmt_editpos); + fmt_editpos += len; - len = get_format_specifer_length (fmt_editpos[0]); - if (len && (fmt_editpos[len-1])) - { - const char fmt2 = (len == 2) ? fmt_editpos[1] : 0; - segmentp = make_segment (segmentp, segstart, - fmt_editpos - segstart, - KIND_FORMAT, fmt_editpos[0], fmt2, - our_pred); - fmt_editpos += (len - 1); - } - else - { - if (strchr ("{[(", fmt_editpos[0])) - { - error (EXIT_FAILURE, 0, - _("error: the format directive `%%%c' is reserved for future use"), - (int)fmt_editpos[0]); - /*NOTREACHED*/ - } + len = get_format_specifer_length (fmt_editpos[0]); + if (len && (fmt_editpos[len-1])) + { + const char fmt2 = (len == 2) ? fmt_editpos[1] : 0; + segmentp = make_segment (segmentp, segstart, + fmt_editpos - segstart, + KIND_FORMAT, fmt_editpos[0], fmt2, + our_pred); + fmt_editpos += (len - 1); + } + else + { + if (strchr ("{[(", fmt_editpos[0])) + { + error (EXIT_FAILURE, 0, + _("error: the format directive `%%%c' is reserved for future use"), + (int)fmt_editpos[0]); + /*NOTREACHED*/ + } - if (len == 2 && !fmt_editpos[1]) - { - error (0, 0, - _("warning: format directive `%%%c' " - "should be followed by another character"), - fmt_editpos[0]); - } - else - { - /* An unrecognized % escape. Print the char after the %. */ - error (0, 0, - _("warning: unrecognized format directive `%%%c'"), - fmt_editpos[0]); - } - segmentp = make_segment (segmentp, - segstart, fmt_editpos + 1 - segstart, - KIND_PLAIN, 0, 0, - our_pred); - } - segstart = fmt_editpos + 1; - } + if (len == 2 && !fmt_editpos[1]) + { + error (0, 0, + _("warning: format directive `%%%c' " + "should be followed by another character"), + fmt_editpos[0]); + } + else + { + /* An unrecognized % escape. Print the char after the %. */ + error (0, 0, + _("warning: unrecognized format directive `%%%c'"), + fmt_editpos[0]); + } + segmentp = make_segment (segmentp, + segstart, fmt_editpos + 1 - segstart, + KIND_PLAIN, 0, 0, + our_pred); + } + segstart = fmt_editpos + 1; + } } if (fmt_editpos > segstart) make_segment (segmentp, segstart, fmt_editpos - segstart, KIND_PLAIN, 0, 0, - our_pred); + our_pred); return true; } static bool scan_for_digit_differences (const char *p, const char *q, - size_t *first, size_t *n) + size_t *first, size_t *n) { bool seen = false; size_t i; @@ -450,30 +450,30 @@ scan_for_digit_differences (const char *p, const char *q, for (i=0; p[i] && q[i]; i++) { if (p[i] != q[i]) - { - if (!isdigit ((unsigned char)q[i]) || !isdigit ((unsigned char)q[i])) - return false; + { + if (!isdigit ((unsigned char)q[i]) || !isdigit ((unsigned char)q[i])) + return false; - if (!seen) - { - *first = i; - *n = 1; - seen = 1; - } - else - { - if (i-*first == *n) - { - /* Still in the first sequence of differing digits. */ - ++*n; - } - else - { - /* More than one differing contiguous character sequence. */ - return false; - } - } - } + if (!seen) + { + *first = i; + *n = 1; + seen = 1; + } + else + { + if (i-*first == *n) + { + /* Still in the first sequence of differing digits. */ + ++*n; + } + else + { + /* More than one differing contiguous character sequence. */ + return false; + } + } + } } if (p[i] || q[i]) { @@ -527,57 +527,57 @@ do_time_format (const char *fmt, const struct tm *p, const char *ns, size_t ns_s * condition. */ size_t buf_used = strftime (buf, buf_size, timefmt, p); - if (buf_used /* Conforming POSIX system */ - && (buf_used < buf_size)) /* Solaris workaround */ - { - char *altbuf; - size_t i = 0, n = 0; - size_t final_len = (buf_used - + 1u /* for \0 */ - + ns_size); - buf = xrealloc (buf, final_len); - buf_size = final_len; - altbuf = xmalloc (final_len); - strftime (altbuf, buf_size, timefmt, &altered_time); + if (buf_used /* Conforming POSIX system */ + && (buf_used < buf_size)) /* Solaris workaround */ + { + char *altbuf; + size_t i = 0, n = 0; + size_t final_len = (buf_used + + 1u /* for \0 */ + + ns_size); + buf = xrealloc (buf, final_len); + buf_size = final_len; + altbuf = xmalloc (final_len); + strftime (altbuf, buf_size, timefmt, &altered_time); - /* Find the seconds digits; they should be the only changed part. - * In theory the result of the two formatting operations could differ in - * more than just one sequence of decimal digits (for example %X might - * in theory return a spelled-out time like "thirty seconds past noon"). - * When that happens, we just avoid inserting the nanoseconds field. - */ - if (scan_for_digit_differences (buf, altbuf, &i, &n) - && (2==n) && !isdigit ((unsigned char)buf[i+n])) - { - const size_t end_of_seconds = i + n; - const size_t suffix_len = buf_used-(end_of_seconds)+1; + /* Find the seconds digits; they should be the only changed part. + * In theory the result of the two formatting operations could differ in + * more than just one sequence of decimal digits (for example %X might + * in theory return a spelled-out time like "thirty seconds past noon"). + * When that happens, we just avoid inserting the nanoseconds field. + */ + if (scan_for_digit_differences (buf, altbuf, &i, &n) + && (2==n) && !isdigit ((unsigned char)buf[i+n])) + { + const size_t end_of_seconds = i + n; + const size_t suffix_len = buf_used-(end_of_seconds)+1; - /* Move the tail (including the \0). Note that this - * is a move of an overlapping memory block, so we - * must use memmove instead of memcpy. Then insert - * the nanoseconds (but not its trailing \0). - */ - assert (end_of_seconds + ns_size + suffix_len == final_len); - memmove (buf+end_of_seconds+ns_size, - buf+end_of_seconds, - suffix_len); - memcpy (buf+i+n, ns, ns_size); - } - else - { - /* No seconds digits. No need to insert anything. */ - } - /* The first character of buf is the underscore, which we actually - * don't want. - */ - free (timefmt); - free (altbuf); - return buf+1; - } + /* Move the tail (including the \0). Note that this + * is a move of an overlapping memory block, so we + * must use memmove instead of memcpy. Then insert + * the nanoseconds (but not its trailing \0). + */ + assert (end_of_seconds + ns_size + suffix_len == final_len); + memmove (buf+end_of_seconds+ns_size, + buf+end_of_seconds, + suffix_len); + memcpy (buf+i+n, ns, ns_size); + } + else + { + /* No seconds digits. No need to insert anything. */ + } + /* The first character of buf is the underscore, which we actually + * don't want. + */ + free (timefmt); + free (altbuf); + return buf+1; + } else - { - buf = x2nrealloc (buf, &buf_size, sizeof *buf); - } + { + buf = x2nrealloc (buf, &buf_size, sizeof *buf); + } } } @@ -609,10 +609,10 @@ format_date (struct timespec ts, int kind) */ enum { NS_BUF_LEN = 32, - DATE_LEN_PERCENT_APLUS=21 /* length of result of %A+ (it's longer than %c)*/ + DATE_LEN_PERCENT_APLUS=21 /* length of result of %A+ (it's longer than %c)*/ }; static char buf[128u+10u + MAX(DATE_LEN_PERCENT_APLUS, - MAX (LONGEST_HUMAN_READABLE + 2, NS_BUF_LEN+64+200))]; + MAX (LONGEST_HUMAN_READABLE + 2, NS_BUF_LEN+64+200))]; char ns_buf[NS_BUF_LEN]; /* -.9999999990 (- sign can happen!)*/ int charsprinted, need_ns_suffix; struct tm *tm; @@ -647,17 +647,17 @@ format_date (struct timespec ts, int kind) /* %a, %c, and %t are handled in ctime_format() */ switch (kind) - { - case 'S': - case 'T': - case 'X': - case '@': - need_ns_suffix = 1; - break; - default: - need_ns_suffix = 0; - break; - } + { + case 'S': + case 'T': + case 'X': + case '@': + need_ns_suffix = 1; + break; + default: + need_ns_suffix = 0; + break; + } } if (need_ns_suffix) @@ -681,11 +681,11 @@ format_date (struct timespec ts, int kind) { tm = localtime (&ts.tv_sec); if (tm) - { - char *s = do_time_format (fmt, tm, ns_buf, charsprinted); - if (s) - return s; - } + { + char *s = do_time_format (fmt, tm, ns_buf, charsprinted); + if (s) + return s; + } } /* If we get to here, either the format was %@, or we have fallen back to it @@ -700,11 +700,11 @@ format_date (struct timespec ts, int kind) * widest possible unsigned type. */ char *p = human_readable (ts.tv_sec < 0 ? -w : w, buf + 1, - human_ceiling, 1, 1); + human_ceiling, 1, 1); assert (p > buf); assert (p < (buf + (sizeof buf))); if (ts.tv_sec < 0) - *--p = '-'; /* XXX: Ugh, relying on internal details of human_readable(). */ + *--p = '-'; /* XXX: Ugh, relying on internal details of human_readable(). */ /* Add the nanoseconds part. Because we cannot enforce a * particlar implementation of human_readable, we cannot assume @@ -712,21 +712,21 @@ format_date (struct timespec ts, int kind) * that there is enough space remaining in the buffer. */ if (need_ns_suffix) - { - len = strlen (p); - used = (p-buf) + len; /* Offset into buf of current end */ - assert (sizeof buf > used); /* Ensure we can perform subtraction safely. */ - remaining = sizeof buf - used - 1u; /* allow space for NUL */ + { + len = strlen (p); + used = (p-buf) + len; /* Offset into buf of current end */ + assert (sizeof buf > used); /* Ensure we can perform subtraction safely. */ + remaining = sizeof buf - used - 1u; /* allow space for NUL */ - if (strlen (ns_buf) >= remaining) - { - error (0, 0, - "charsprinted=%ld but remaining=%lu: ns_buf=%s", - (long)charsprinted, (unsigned long)remaining, ns_buf); - } - assert (strlen (ns_buf) < remaining); - strcat (p, ns_buf); - } + if (strlen (ns_buf) >= remaining) + { + error (0, 0, + "charsprinted=%ld but remaining=%lu: ns_buf=%s", + (long)charsprinted, (unsigned long)remaining, ns_buf); + } + assert (strlen (ns_buf) < remaining); + strcat (p, ns_buf); + } return p; } } @@ -764,15 +764,15 @@ ctime_format (struct timespec ts) /* wkday mon mday hh:mm:ss.nnnnnnnnn yyyy */ nout = snprintf (resultbuf, TIME_BUF_LEN, - "%3s %3s %2d %02d:%02d:%02d.%09ld0 %04d", - weekdays[ptm->tm_wday], - months[ptm->tm_mon], - ptm->tm_mday, - ptm->tm_hour, - ptm->tm_min, - ptm->tm_sec, - (long int)ts.tv_nsec, - 1900 + ptm->tm_year); + "%3s %3s %2d %02d:%02d:%02d.%09ld0 %04d", + weekdays[ptm->tm_wday], + months[ptm->tm_mon], + ptm->tm_mday, + ptm->tm_hour, + ptm->tm_min, + ptm->tm_sec, + (long int)ts.tv_nsec, + 1900 + ptm->tm_year); assert (nout < TIME_BUF_LEN); return resultbuf; @@ -780,7 +780,7 @@ ctime_format (struct timespec ts) else { /* The time cannot be represented as a struct tm. - Output it as an integer. */ + Output it as an integer. */ return format_date (ts, '@'); } } @@ -791,9 +791,9 @@ file_sparseness (const struct stat *p) if (0 == p->st_size) { if (0 == ST_NBLOCKS(*p)) - return 1.0; + return 1.0; else - return ST_NBLOCKS(*p) < 0 ? -HUGE_VAL : HUGE_VAL; + return ST_NBLOCKS(*p) < 0 ? -HUGE_VAL : HUGE_VAL; } else { @@ -816,10 +816,10 @@ checked_fprintf (struct format_val *dest, const char *fmt, ...) static void checked_print_quoted (struct format_val *dest, - const char *format, const char *s) + const char *format, const char *s) { int rv = print_quoted (dest->stream, dest->quote_opts, dest->dest_is_tty, - format, s); + format, s); if (rv < 0) nonfatal_nontarget_file_error (errno, dest->filename); } @@ -847,51 +847,51 @@ mode_to_filetype (mode_t m) { #define HANDLE_TYPE(t,letter) if (m==t) { return letter; } #ifdef S_IFREG - HANDLE_TYPE(S_IFREG, "f"); /* regular file */ + HANDLE_TYPE(S_IFREG, "f"); /* regular file */ #endif #ifdef S_IFDIR - HANDLE_TYPE(S_IFDIR, "d"); /* directory */ + HANDLE_TYPE(S_IFDIR, "d"); /* directory */ #endif #ifdef S_IFLNK - HANDLE_TYPE(S_IFLNK, "l"); /* symbolic link */ + HANDLE_TYPE(S_IFLNK, "l"); /* symbolic link */ #endif #ifdef S_IFSOCK - HANDLE_TYPE(S_IFSOCK, "s"); /* Unix domain socket */ + HANDLE_TYPE(S_IFSOCK, "s"); /* Unix domain socket */ #endif #ifdef S_IFBLK - HANDLE_TYPE(S_IFBLK, "b"); /* block device */ + HANDLE_TYPE(S_IFBLK, "b"); /* block device */ #endif #ifdef S_IFCHR - HANDLE_TYPE(S_IFCHR, "c"); /* character device */ + HANDLE_TYPE(S_IFCHR, "c"); /* character device */ #endif #ifdef S_IFIFO - HANDLE_TYPE(S_IFIFO, "p"); /* FIFO */ + HANDLE_TYPE(S_IFIFO, "p"); /* FIFO */ #endif #ifdef S_IFDOOR - HANDLE_TYPE(S_IFDOOR, "D"); /* Door (e.g. on Solaris) */ + HANDLE_TYPE(S_IFDOOR, "D"); /* Door (e.g. on Solaris) */ #endif - return "U"; /* Unknown */ + return "U"; /* Unknown */ } static void do_fprintf (struct format_val *dest, - struct segment *segment, - const char *pathname, - const struct stat *stat_buf) + struct segment *segment, + const char *pathname, + const struct stat *stat_buf) { char hbuf[LONGEST_HUMAN_READABLE + 1]; const char *cp; switch (segment->segkind) { - case KIND_PLAIN: /* Plain text string (no % conversion). */ + case KIND_PLAIN: /* Plain text string (no % conversion). */ /* trusted */ checked_fwrite(segment->text, 1, segment->text_len, dest); break; - case KIND_STOP: /* Terminate argument and flush output. */ + case KIND_STOP: /* Terminate argument and flush output. */ /* trusted */ checked_fwrite (segment->text, 1, segment->text_len, dest); checked_fflush (dest); @@ -899,355 +899,360 @@ do_fprintf (struct format_val *dest, case KIND_FORMAT: switch (segment->format_char[0]) - { - case 'a': /* atime in `ctime' format. */ - /* UNTRUSTED, probably unexploitable */ - checked_fprintf (dest, segment->text, ctime_format (get_stat_atime (stat_buf))); - break; - case 'b': /* size in 512-byte blocks */ - /* UNTRUSTED, probably unexploitable */ - checked_fprintf (dest, segment->text, - human_readable ((uintmax_t) ST_NBLOCKS (*stat_buf), - hbuf, human_ceiling, - ST_NBLOCKSIZE, 512)); - break; - case 'c': /* ctime in `ctime' format */ - /* UNTRUSTED, probably unexploitable */ - checked_fprintf (dest, segment->text, ctime_format (get_stat_ctime (stat_buf))); - break; - case 'd': /* depth in search tree */ - /* UNTRUSTED, probably unexploitable */ - checked_fprintf (dest, segment->text, state.curdepth); - break; - case 'D': /* Device on which file exists (stat.st_dev) */ - /* trusted */ - checked_fprintf (dest, segment->text, - human_readable ((uintmax_t) stat_buf->st_dev, hbuf, - human_ceiling, 1, 1)); - break; - case 'f': /* base name of path */ - /* sanitised */ - { - char *base = base_name (pathname); - checked_print_quoted (dest, segment->text, base); - free (base); - } - break; - case 'F': /* file system type */ - /* trusted */ - checked_print_quoted (dest, segment->text, filesystem_type (stat_buf, pathname)); - break; - case 'g': /* group name */ - /* trusted */ - /* (well, the actual group is selected by the user but - * its name was selected by the system administrator) - */ - { - struct group *g; + { + case 'a': /* atime in `ctime' format. */ + /* UNTRUSTED, probably unexploitable */ + checked_fprintf (dest, segment->text, ctime_format (get_stat_atime (stat_buf))); + break; + case 'b': /* size in 512-byte blocks */ + /* UNTRUSTED, probably unexploitable */ + checked_fprintf (dest, segment->text, + human_readable ((uintmax_t) ST_NBLOCKS (*stat_buf), + hbuf, human_ceiling, + ST_NBLOCKSIZE, 512)); + break; + case 'c': /* ctime in `ctime' format */ + /* UNTRUSTED, probably unexploitable */ + checked_fprintf (dest, segment->text, ctime_format (get_stat_ctime (stat_buf))); + break; + case 'd': /* depth in search tree */ + /* UNTRUSTED, probably unexploitable */ + checked_fprintf (dest, segment->text, state.curdepth); + break; + case 'D': /* Device on which file exists (stat.st_dev) */ + /* trusted */ + checked_fprintf (dest, segment->text, + human_readable ((uintmax_t) stat_buf->st_dev, hbuf, + human_ceiling, 1, 1)); + break; + case 'f': /* base name of path */ + /* sanitised */ + { + char *base = base_name (pathname); + checked_print_quoted (dest, segment->text, base); + free (base); + } + break; + case 'F': /* file system type */ + /* trusted */ + checked_print_quoted (dest, segment->text, filesystem_type (stat_buf, pathname)); + break; + case 'g': /* group name */ + /* trusted */ + /* (well, the actual group is selected by the user but + * its name was selected by the system administrator) + */ + { + struct group *g; - g = getgrgid (stat_buf->st_gid); - if (g) - { - segment->text[segment->text_len] = 's'; - checked_fprintf (dest, segment->text, g->gr_name); - break; - } - else - { - /* Do nothing. */ - /*FALLTHROUGH*/ - } - } - /*FALLTHROUGH*/ /*...sometimes, so 'G' case.*/ + g = getgrgid (stat_buf->st_gid); + if (g) + { + segment->text[segment->text_len] = 's'; + checked_fprintf (dest, segment->text, g->gr_name); + break; + } + else + { + /* Do nothing. */ + /*FALLTHROUGH*/ + } + } + /*FALLTHROUGH*/ /*...sometimes, so 'G' case.*/ - case 'G': /* GID number */ - /* UNTRUSTED, probably unexploitable */ - checked_fprintf (dest, segment->text, - human_readable ((uintmax_t) stat_buf->st_gid, hbuf, - human_ceiling, 1, 1)); - break; - case 'h': /* leading directories part of path */ - /* sanitised */ - { - cp = strrchr (pathname, '/'); - if (cp == NULL) /* No leading directories. */ - { - /* If there is no slash in the pathname, we still - * print the string because it contains characters - * other than just '%s'. The %h expands to ".". - */ - checked_print_quoted (dest, segment->text, "."); - } - else - { - char *s = strdup (pathname); - s[cp - pathname] = 0; - checked_print_quoted (dest, segment->text, s); - free (s); - } - } - break; + case 'G': /* GID number */ + /* UNTRUSTED, probably unexploitable */ + checked_fprintf (dest, segment->text, + human_readable ((uintmax_t) stat_buf->st_gid, hbuf, + human_ceiling, 1, 1)); + break; + case 'h': /* leading directories part of path */ + /* sanitised */ + { + cp = strrchr (pathname, '/'); + if (cp == NULL) /* No leading directories. */ + { + /* If there is no slash in the pathname, we still + * print the string because it contains characters + * other than just '%s'. The %h expands to ".". + */ + checked_print_quoted (dest, segment->text, "."); + } + else + { + char *s = strdup (pathname); + s[cp - pathname] = 0; + checked_print_quoted (dest, segment->text, s); + free (s); + } + } + break; - case 'H': /* ARGV element file was found under */ - /* trusted */ - { - char *s = xmalloc (state.starting_path_length+1); - memcpy (s, pathname, state.starting_path_length); - s[state.starting_path_length] = 0; - checked_fprintf (dest, segment->text, s); - free (s); - } - break; + case 'H': /* ARGV element file was found under */ + /* trusted */ + { + char *s = xmalloc (state.starting_path_length+1); + memcpy (s, pathname, state.starting_path_length); + s[state.starting_path_length] = 0; + checked_fprintf (dest, segment->text, s); + free (s); + } + break; - case 'i': /* inode number */ - /* UNTRUSTED, but not exploitable I think */ - checked_fprintf (dest, segment->text, - human_readable ((uintmax_t) stat_buf->st_ino, hbuf, - human_ceiling, - 1, 1)); - break; - case 'k': /* size in 1K blocks */ - /* UNTRUSTED, but not exploitable I think */ - checked_fprintf (dest, segment->text, - human_readable ((uintmax_t) ST_NBLOCKS (*stat_buf), - hbuf, human_ceiling, - ST_NBLOCKSIZE, 1024)); - break; - case 'l': /* object of symlink */ - /* sanitised */ + case 'i': /* inode number */ + /* UNTRUSTED, but not exploitable I think */ + /* POSIX does not guarantee that ino_t is unsigned or even + * integral (except as an XSI extension), but we'll work on + * fixing that if we ever get a report of a system where + * ino_t is indeed a signed integral type or a non-integral + * arithmetic type. */ + checked_fprintf (dest, segment->text, + human_readable ((uintmax_t) stat_buf->st_ino, hbuf, + human_ceiling, + 1, 1)); + break; + case 'k': /* size in 1K blocks */ + /* UNTRUSTED, but not exploitable I think */ + checked_fprintf (dest, segment->text, + human_readable ((uintmax_t) ST_NBLOCKS (*stat_buf), + hbuf, human_ceiling, + ST_NBLOCKSIZE, 1024)); + break; + case 'l': /* object of symlink */ + /* sanitised */ #ifdef S_ISLNK - { - char *linkname = 0; + { + char *linkname = 0; - if (S_ISLNK (stat_buf->st_mode)) - { - linkname = areadlinkat (state.cwd_dir_fd, state.rel_pathname); - if (linkname == NULL) - { - nonfatal_target_file_error (errno, pathname); - state.exit_status = 1; - } - } - if (linkname) - { - checked_print_quoted (dest, segment->text, linkname); - } - else - { - /* We still need to honour the field width etc., so this is - * not a no-op. - */ - checked_print_quoted (dest, segment->text, ""); - } - free (linkname); - } -#endif /* S_ISLNK */ - break; + if (S_ISLNK (stat_buf->st_mode)) + { + linkname = areadlinkat (state.cwd_dir_fd, state.rel_pathname); + if (linkname == NULL) + { + nonfatal_target_file_error (errno, pathname); + state.exit_status = 1; + } + } + if (linkname) + { + checked_print_quoted (dest, segment->text, linkname); + } + else + { + /* We still need to honour the field width etc., so this is + * not a no-op. + */ + checked_print_quoted (dest, segment->text, ""); + } + free (linkname); + } +#endif /* S_ISLNK */ + break; - case 'M': /* mode as 10 chars (eg., "-rwxr-x--x" */ - /* UNTRUSTED, probably unexploitable */ - { - char modestring[16] ; - filemodestring (stat_buf, modestring); - modestring[10] = '\0'; - checked_fprintf (dest, segment->text, modestring); - } - break; + case 'M': /* mode as 10 chars (eg., "-rwxr-x--x" */ + /* UNTRUSTED, probably unexploitable */ + { + char modestring[16] ; + filemodestring (stat_buf, modestring); + modestring[10] = '\0'; + checked_fprintf (dest, segment->text, modestring); + } + break; - case 'm': /* mode as octal number (perms only) */ - /* UNTRUSTED, probably unexploitable */ - { - /* Output the mode portably using the traditional numbers, - even if the host unwisely uses some other numbering - scheme. But help the compiler in the common case where - the host uses the traditional numbering scheme. */ - mode_t m = stat_buf->st_mode; - bool traditional_numbering_scheme = - (S_ISUID == 04000 && S_ISGID == 02000 && S_ISVTX == 01000 - && S_IRUSR == 00400 && S_IWUSR == 00200 && S_IXUSR == 00100 - && S_IRGRP == 00040 && S_IWGRP == 00020 && S_IXGRP == 00010 - && S_IROTH == 00004 && S_IWOTH == 00002 && S_IXOTH == 00001); - checked_fprintf (dest, segment->text, - (traditional_numbering_scheme - ? m & MODE_ALL - : ((m & S_ISUID ? 04000 : 0) - | (m & S_ISGID ? 02000 : 0) - | (m & S_ISVTX ? 01000 : 0) - | (m & S_IRUSR ? 00400 : 0) - | (m & S_IWUSR ? 00200 : 0) - | (m & S_IXUSR ? 00100 : 0) - | (m & S_IRGRP ? 00040 : 0) - | (m & S_IWGRP ? 00020 : 0) - | (m & S_IXGRP ? 00010 : 0) - | (m & S_IROTH ? 00004 : 0) - | (m & S_IWOTH ? 00002 : 0) - | (m & S_IXOTH ? 00001 : 0)))); - } - break; + case 'm': /* mode as octal number (perms only) */ + /* UNTRUSTED, probably unexploitable */ + { + /* Output the mode portably using the traditional numbers, + even if the host unwisely uses some other numbering + scheme. But help the compiler in the common case where + the host uses the traditional numbering scheme. */ + mode_t m = stat_buf->st_mode; + bool traditional_numbering_scheme = + (S_ISUID == 04000 && S_ISGID == 02000 && S_ISVTX == 01000 + && S_IRUSR == 00400 && S_IWUSR == 00200 && S_IXUSR == 00100 + && S_IRGRP == 00040 && S_IWGRP == 00020 && S_IXGRP == 00010 + && S_IROTH == 00004 && S_IWOTH == 00002 && S_IXOTH == 00001); + checked_fprintf (dest, segment->text, + (traditional_numbering_scheme + ? m & MODE_ALL + : ((m & S_ISUID ? 04000 : 0) + | (m & S_ISGID ? 02000 : 0) + | (m & S_ISVTX ? 01000 : 0) + | (m & S_IRUSR ? 00400 : 0) + | (m & S_IWUSR ? 00200 : 0) + | (m & S_IXUSR ? 00100 : 0) + | (m & S_IRGRP ? 00040 : 0) + | (m & S_IWGRP ? 00020 : 0) + | (m & S_IXGRP ? 00010 : 0) + | (m & S_IROTH ? 00004 : 0) + | (m & S_IWOTH ? 00002 : 0) + | (m & S_IXOTH ? 00001 : 0)))); + } + break; - case 'n': /* number of links */ - /* UNTRUSTED, probably unexploitable */ - checked_fprintf (dest, segment->text, - human_readable ((uintmax_t) stat_buf->st_nlink, - hbuf, - human_ceiling, - 1, 1)); - break; + case 'n': /* number of links */ + /* UNTRUSTED, probably unexploitable */ + checked_fprintf (dest, segment->text, + human_readable ((uintmax_t) stat_buf->st_nlink, + hbuf, + human_ceiling, + 1, 1)); + break; - case 'p': /* pathname */ - /* sanitised */ - checked_print_quoted (dest, segment->text, pathname); - break; + case 'p': /* pathname */ + /* sanitised */ + checked_print_quoted (dest, segment->text, pathname); + break; - case 'P': /* pathname with ARGV element stripped */ - /* sanitised */ - if (state.curdepth > 0) - { - cp = pathname + state.starting_path_length; - if (*cp == '/') - /* Move past the slash between the ARGV element - and the rest of the pathname. But if the ARGV element - ends in a slash, we didn't add another, so we've - already skipped past it. */ - cp++; - } - else - { - cp = ""; - } - checked_print_quoted (dest, segment->text, cp); - break; + case 'P': /* pathname with ARGV element stripped */ + /* sanitised */ + if (state.curdepth > 0) + { + cp = pathname + state.starting_path_length; + if (*cp == '/') + /* Move past the slash between the ARGV element + and the rest of the pathname. But if the ARGV element + ends in a slash, we didn't add another, so we've + already skipped past it. */ + cp++; + } + else + { + cp = ""; + } + checked_print_quoted (dest, segment->text, cp); + break; - case 's': /* size in bytes */ - /* UNTRUSTED, probably unexploitable */ - checked_fprintf (dest, segment->text, - human_readable ((uintmax_t) stat_buf->st_size, - hbuf, human_ceiling, 1, 1)); - break; + case 's': /* size in bytes */ + /* UNTRUSTED, probably unexploitable */ + checked_fprintf (dest, segment->text, + human_readable ((uintmax_t) stat_buf->st_size, + hbuf, human_ceiling, 1, 1)); + break; - case 'S': /* sparseness */ - /* UNTRUSTED, probably unexploitable */ - checked_fprintf (dest, segment->text, file_sparseness (stat_buf));; - break; + case 'S': /* sparseness */ + /* UNTRUSTED, probably unexploitable */ + checked_fprintf (dest, segment->text, file_sparseness (stat_buf));; + break; - case 't': /* mtime in `ctime' format */ - /* UNTRUSTED, probably unexploitable */ - checked_fprintf (dest, segment->text, - ctime_format (get_stat_mtime (stat_buf))); - break; + case 't': /* mtime in `ctime' format */ + /* UNTRUSTED, probably unexploitable */ + checked_fprintf (dest, segment->text, + ctime_format (get_stat_mtime (stat_buf))); + break; - case 'u': /* user name */ - /* trusted */ - /* (well, the actual user is selected by the user on systems - * where chown is not restricted, but the user name was - * selected by the system administrator) - */ - { - struct passwd *p; + case 'u': /* user name */ + /* trusted */ + /* (well, the actual user is selected by the user on systems + * where chown is not restricted, but the user name was + * selected by the system administrator) + */ + { + struct passwd *p; - p = getpwuid (stat_buf->st_uid); - if (p) - { - segment->text[segment->text_len] = 's'; - checked_fprintf (dest, segment->text, p->pw_name); - break; - } - /* else fallthru */ - } - /* FALLTHROUGH*/ /* .. to case U */ + p = getpwuid (stat_buf->st_uid); + if (p) + { + segment->text[segment->text_len] = 's'; + checked_fprintf (dest, segment->text, p->pw_name); + break; + } + /* else fallthru */ + } + /* FALLTHROUGH*/ /* .. to case U */ - case 'U': /* UID number */ - /* UNTRUSTED, probably unexploitable */ - checked_fprintf (dest, segment->text, - human_readable ((uintmax_t) stat_buf->st_uid, hbuf, - human_ceiling, 1, 1)); - break; + case 'U': /* UID number */ + /* UNTRUSTED, probably unexploitable */ + checked_fprintf (dest, segment->text, + human_readable ((uintmax_t) stat_buf->st_uid, hbuf, + human_ceiling, 1, 1)); + break; - /* %Y: type of file system entry like `ls -l`: - * (d,-,l,s,p,b,c,n) n=nonexistent (symlink) - */ - case 'Y': /* in case of symlink */ - /* trusted */ - { + /* %Y: type of file system entry like `ls -l`: + * (d,-,l,s,p,b,c,n) n=nonexistent (symlink) + */ + case 'Y': /* in case of symlink */ + /* trusted */ + { #ifdef S_ISLNK - if (S_ISLNK (stat_buf->st_mode)) - { - struct stat sbuf; - /* If we would normally follow links, do not do so. - * If we would normally not follow links, do so. - */ - if ((following_links () ? optionp_stat : optionl_stat) - (state.rel_pathname, &sbuf) != 0) - { - if ( errno == ENOENT ) - { - checked_fprintf (dest, segment->text, "N"); - break; - } - else if ( errno == ELOOP ) - { - checked_fprintf (dest, segment->text, "L"); - break; - } - else - { - checked_fprintf (dest, segment->text, "?"); - error (0, errno, "%s", - safely_quote_err_filename (0, pathname)); - /* exit_status = 1; - return ; */ - break; - } - } - checked_fprintf (dest, segment->text, - mode_to_filetype (sbuf.st_mode & S_IFMT)); - } + if (S_ISLNK (stat_buf->st_mode)) + { + struct stat sbuf; + /* If we would normally follow links, do not do so. + * If we would normally not follow links, do so. + */ + if ((following_links () ? optionp_stat : optionl_stat) + (state.rel_pathname, &sbuf) != 0) + { + if ( errno == ENOENT ) + { + checked_fprintf (dest, segment->text, "N"); + break; + } + else if ( errno == ELOOP ) + { + checked_fprintf (dest, segment->text, "L"); + break; + } + else + { + checked_fprintf (dest, segment->text, "?"); + error (0, errno, "%s", + safely_quote_err_filename (0, pathname)); + /* exit_status = 1; + return ; */ + break; + } + } + checked_fprintf (dest, segment->text, + mode_to_filetype (sbuf.st_mode & S_IFMT)); + } #endif /* S_ISLNK */ - else - { - checked_fprintf (dest, segment->text, - mode_to_filetype (stat_buf->st_mode & S_IFMT)); - } - } - break; + else + { + checked_fprintf (dest, segment->text, + mode_to_filetype (stat_buf->st_mode & S_IFMT)); + } + } + break; - case 'y': - /* trusted */ - { - checked_fprintf (dest, segment->text, - mode_to_filetype (stat_buf->st_mode & S_IFMT)); - } - break; + case 'y': + /* trusted */ + { + checked_fprintf (dest, segment->text, + mode_to_filetype (stat_buf->st_mode & S_IFMT)); + } + break; - case 'Z': /* SELinux security context */ - { - security_context_t scontext; - int rv = (*options.x_getfilecon) (state.cwd_dir_fd, state.rel_pathname, - &scontext); - if (rv < 0) - { - /* If getfilecon fails, there will in the general case - still be some text to print. We just make %Z expand - to an empty string. */ - checked_fprintf (dest, segment->text, ""); + case 'Z': /* SELinux security context */ + { + security_context_t scontext; + int rv = (*options.x_getfilecon) (state.cwd_dir_fd, state.rel_pathname, + &scontext); + if (rv < 0) + { + /* If getfilecon fails, there will in the general case + still be some text to print. We just make %Z expand + to an empty string. */ + checked_fprintf (dest, segment->text, ""); - error (0, errno, _("getfilecon failed: %s"), - safely_quote_err_filename (0, pathname)); - state.exit_status = 1; - } - else - { - checked_fprintf (dest, segment->text, scontext); - freecon (scontext); - } - } - break; + error (0, errno, _("getfilecon failed: %s"), + safely_quote_err_filename (0, pathname)); + state.exit_status = 1; + } + else + { + checked_fprintf (dest, segment->text, scontext); + freecon (scontext); + } + } + break; - case 0: - case '%': - checked_fprintf (dest, segment->text); - break; - } + case 0: + case '%': + checked_fprintf (dest, segment->text); + break; + } /* end of KIND_FORMAT case */ break; } @@ -1262,61 +1267,61 @@ pred_fprintf (const char *pathname, struct stat *stat_buf, struct predicate *pre for (segment = dest->segment; segment; segment = segment->next) { if ( (KIND_FORMAT == segment->segkind) && segment->format_char[1]) /* Component of date. */ - { - struct timespec ts; - int valid = 0; + { + struct timespec ts; + int valid = 0; - switch (segment->format_char[0]) - { - case 'A': - ts = get_stat_atime (stat_buf); - valid = 1; - break; - case 'B': - ts = get_stat_birthtime (stat_buf); - if ('@' == segment->format_char[1]) - valid = 1; - else - valid = (ts.tv_nsec >= 0); - break; - case 'C': - ts = get_stat_ctime (stat_buf); - valid = 1; - break; - case 'T': - ts = get_stat_mtime (stat_buf); - valid = 1; - break; - default: - assert (0); - abort (); - } - /* We trust the output of format_date not to contain - * nasty characters, though the value of the date - * is itself untrusted data. - */ - if (valid) - { - /* trusted */ - checked_fprintf (dest, segment->text, - format_date (ts, segment->format_char[1])); - } - else - { - /* The specified timestamp is not available, output - * nothing for the timestamp, but use the rest (so that - * for example find foo -printf '[%Bs] %p\n' can print - * "[] foo"). - */ - /* trusted */ - checked_fprintf (dest, segment->text, ""); - } - } + switch (segment->format_char[0]) + { + case 'A': + ts = get_stat_atime (stat_buf); + valid = 1; + break; + case 'B': + ts = get_stat_birthtime (stat_buf); + if ('@' == segment->format_char[1]) + valid = 1; + else + valid = (ts.tv_nsec >= 0); + break; + case 'C': + ts = get_stat_ctime (stat_buf); + valid = 1; + break; + case 'T': + ts = get_stat_mtime (stat_buf); + valid = 1; + break; + default: + assert (0); + abort (); + } + /* We trust the output of format_date not to contain + * nasty characters, though the value of the date + * is itself untrusted data. + */ + if (valid) + { + /* trusted */ + checked_fprintf (dest, segment->text, + format_date (ts, segment->format_char[1])); + } + else + { + /* The specified timestamp is not available, output + * nothing for the timestamp, but use the rest (so that + * for example find foo -printf '[%Bs] %p\n' can print + * "[] foo"). + */ + /* trusted */ + checked_fprintf (dest, segment->text, ""); + } + } else - { - /* Print a segment which is not a date. */ - do_fprintf (dest, segment, pathname, stat_buf); - } + { + /* Print a segment which is not a date. */ + do_fprintf (dest, segment, pathname, stat_buf); + } } return true; } diff --git a/lib/listfile.c b/lib/listfile.c index 8e7bd61a..b49523e1 100644 --- a/lib/listfile.c +++ b/lib/listfile.c @@ -39,6 +39,7 @@ #include "error.h" #include "filemode.h" #include "human.h" +#include "mbswidth.h" #include "idcache.h" #include "pathmax.h" #include "stat-size.h" @@ -57,7 +58,7 @@ #define HAVE_MAJOR #endif -#ifdef major /* Might be defined in sys/types.h. */ +#ifdef major /* Might be defined in sys/types.h. */ #define HAVE_MAJOR #endif #ifndef HAVE_MAJOR @@ -81,6 +82,30 @@ static bool print_name (register const char *p, FILE *stream, int literal_control_chars); +/* We have some minimum field sizes, though we try to widen these fields on systems + * where we discover examples where the field width we started with is not enough. */ +static int inode_number_width = 9; +static int block_size_width = 6; +static int nlink_width = 3; +static int owner_width = 8; +static int group_width = 8; +/* We don't print st_author even if the system has it. */ +static int major_device_number_width = 3; +static int minor_device_number_width = 3; +static int file_size_width = 8; + +static bool print_num(FILE *stream, unsigned long num, int *width) +{ + const int chars_out = fprintf (stream, "%*lu", *width, num); + if (chars_out >= 0) + { + if (*width < chars_out) + *width = chars_out; + return true; + } + return false; +} + /* NAME is the name to print. RELNAME is the path to access it from the current directory. @@ -92,13 +117,13 @@ static bool print_name (register const char *p, FILE *stream, int literal_contro void list_file (const char *name, - int dir_fd, - char *relname, - const struct stat *statp, - time_t current_time, - int output_block_size, - int literal_control_chars, - FILE *stream) + int dir_fd, + char *relname, + const struct stat *statp, + time_t current_time, + int output_block_size, + int literal_control_chars, + FILE *stream) { char modebuf[12]; struct tm const *when_local; @@ -106,7 +131,9 @@ list_file (const char *name, char const *group_name; char hbuf[LONGEST_HUMAN_READABLE + 1]; bool output_good = true; + int chars_out; int failed_at = 000; + int inode_field_width; #if HAVE_ST_DM_MODE /* Cray DMF: look at the file's migrated, not real, status */ @@ -115,222 +142,310 @@ list_file (const char *name, strmode (statp->st_mode, modebuf); #endif - if (fprintf (stream, "%6s ", - human_readable ((uintmax_t) statp->st_ino, hbuf, - human_ceiling, - 1u, 1u)) < 0) + chars_out = fprintf (stream, "%*s", inode_number_width, + human_readable ((uintmax_t) statp->st_ino, hbuf, + human_ceiling, + 1u, 1u)); + if (chars_out < 0) { output_good = false; failed_at = 100; } - + else if (chars_out > inode_number_width) + { + inode_number_width = chars_out; + } if (output_good) { - if (fprintf (stream, "%4s ", - human_readable ((uintmax_t) ST_NBLOCKS (*statp), hbuf, - human_ceiling, - ST_NBLOCKSIZE, output_block_size)) < 0) - { - output_good = false; - failed_at = 200; - } + if (EOF == putc(' ', stream)) + { + output_good = false; + failed_at = 150; + } + chars_out = fprintf (stream, "%*s", + block_size_width, + human_readable ((uintmax_t) ST_NBLOCKS (*statp), hbuf, + human_ceiling, + ST_NBLOCKSIZE, output_block_size)); + if (chars_out < 0) + { + output_good = false; + failed_at = 200; + } + else + { + if (chars_out > block_size_width) + block_size_width = chars_out; + } } if (output_good) { + if (EOF == putc(' ', stream)) + { + output_good = false; + failed_at = 250; + } /* modebuf includes the space between the mode and the number of links, - as the POSIX "optional alternate access method flag". */ + as the POSIX "optional alternate access method flag". */ if (fprintf (stream, "%s%3lu ", modebuf, (unsigned long) statp->st_nlink) < 0) - { - output_good = false; - failed_at = 300; - } + { + output_good = false; + failed_at = 300; + } } if (output_good) { + if (EOF == putc(' ', stream)) + { + output_good = false; + failed_at = 250; + } user_name = getuser (statp->st_uid); if (user_name) - { - output_good = (fprintf (stream, "%-8s ", user_name) >= 0); - if (!output_good) - failed_at = 400; - } + { + int len = mbswidth (user_name, 0); + if (len > owner_width) + owner_width = len; + output_good = (fprintf (stream, "%-*s ", owner_width, user_name) >= 0); + if (!output_good) + failed_at = 400; + } else - { - output_good = (fprintf (stream, "%-8lu ", (unsigned long) statp->st_uid) >= 0); - if (!output_good) - failed_at = 450; - } + { + chars_out = fprintf (stream, "%-8lu ", (unsigned long) statp->st_uid); + if (chars_out > owner_width) + owner_width = chars_out; + output_good = (chars_out > 0); + if (!output_good) + failed_at = 450; + } } if (output_good) { group_name = getgroup (statp->st_gid); if (group_name) - { - output_good = (fprintf (stream, "%-8s ", group_name) >= 0); - if (!output_good) - failed_at = 500; - } + { + int len = mbswidth (group_name, 0); + if (len > group_width) + group_width = len; + output_good = (fprintf (stream, "%-*s ", group_width, group_name) >= 0); + if (!output_good) + failed_at = 500; + } else - { - output_good = (fprintf (stream, "%-8lu ", (unsigned long) statp->st_gid) >= 0); - if (!output_good) - failed_at = 550; - } + { + chars_out = fprintf (stream, "%-*lu", + group_width, (unsigned long) statp->st_gid); + if (chars_out > group_width) + group_width = chars_out; + output_good = (chars_out >= 0); + if (output_good) + { + if (EOF == putc(' ', stream)) + { + output_good = false; + failed_at = 525; + } + } + else + { + if (!output_good) + failed_at = 550; + } + } } if (output_good) { if (S_ISCHR (statp->st_mode) || S_ISBLK (statp->st_mode)) - { + { #ifdef HAVE_STRUCT_STAT_ST_RDEV - if (fprintf (stream, "%3lu, %3lu ", - (unsigned long) major (statp->st_rdev), - (unsigned long) minor (statp->st_rdev)) < 0) - { - output_good = false; - failed_at = 600; - } + if (!print_num (stream, + (unsigned long) major (statp->st_rdev), + &major_device_number_width)) + { + output_good = false; + failed_at = 600; + } + if (output_good) + { + if (fprintf (stream, ", ") < 0) + { + output_good = false; + failed_at = 625; + } + } + if (output_good) + { + if (!print_num (stream, + (unsigned long) minor (statp->st_rdev), + &minor_device_number_width)) + { + output_good = false; + failed_at = 650; + } + } #else - if (fprintf (stream, " ") < 0) - { - output_good = false; - failed_at = 700; - } + if (fprintf (stream, "%*s %*s", + major_device_number_width, + minor_device_number_width) < 0) + { + output_good = false; + failed_at = 700; + } #endif - } + } else - { - if (fprintf (stream, "%8s ", - human_readable ((uintmax_t) statp->st_size, hbuf, - human_ceiling, - 1, - output_block_size < 0 ? output_block_size : 1)) < 0) - { - output_good = false; - failed_at = 800; - } - } + { + const int blocksize = output_block_size < 0 ? output_block_size : 1; + chars_out = fprintf (stream, "%*s", + file_size_width, + human_readable ((uintmax_t) statp->st_size, hbuf, + human_ceiling, + 1, blocksize)); + if (chars_out < 0) + { + output_good = false; + failed_at = 800; + } + else + { + if (chars_out > file_size_width) + { + file_size_width = chars_out; + } + } + } + } + + if (output_good) + { + if (EOF == putc(' ', stream)) + { + output_good = false; + failed_at = 850; + } } if (output_good) { if ((when_local = localtime (&statp->st_mtime))) - { - char init_bigbuf[256]; - char *buf = init_bigbuf; - size_t bufsize = sizeof init_bigbuf; + { + char init_bigbuf[256]; + char *buf = init_bigbuf; + size_t bufsize = sizeof init_bigbuf; - /* Use strftime rather than ctime, because the former can produce - locale-dependent names for the month (%b). + /* Use strftime rather than ctime, because the former can produce + locale-dependent names for the month (%b). - Output the year if the file is fairly old or in the future. - POSIX says the cutoff is 6 months old; - approximate this by 6*30 days. - Allow a 1 hour slop factor for what is considered "the future", - to allow for NFS server/client clock disagreement. */ - char const *fmt = - ((current_time - 6 * 30 * 24 * 60 * 60 <= statp->st_mtime - && statp->st_mtime <= current_time + 60 * 60) - ? "%b %e %H:%M" - : "%b %e %Y"); + Output the year if the file is fairly old or in the future. + POSIX says the cutoff is 6 months old; + approximate this by 6*30 days. + Allow a 1 hour slop factor for what is considered "the future", + to allow for NFS server/client clock disagreement. */ + char const *fmt = + ((current_time - 6 * 30 * 24 * 60 * 60 <= statp->st_mtime + && statp->st_mtime <= current_time + 60 * 60) + ? "%b %e %H:%M" + : "%b %e %Y"); - while (!strftime (buf, bufsize, fmt, when_local)) - buf = alloca (bufsize *= 2); + while (!strftime (buf, bufsize, fmt, when_local)) + buf = alloca (bufsize *= 2); - if (fprintf (stream, "%s ", buf) < 0) - { - output_good = false; - failed_at = 900; - } - } + if (fprintf (stream, "%s ", buf) < 0) + { + output_good = false; + failed_at = 900; + } + } else - { - /* The time cannot be represented as a local time; - print it as a huge integer number of seconds. */ - int width = 12; + { + /* The time cannot be represented as a local time; + print it as a huge integer number of seconds. */ + int width = 12; - if (statp->st_mtime < 0) - { - char const *num = human_readable (- (uintmax_t) statp->st_mtime, - hbuf, human_ceiling, 1, 1); - int sign_width = width - strlen (num); - if (fprintf (stream, "%*s%s ", - sign_width < 0 ? 0 : sign_width, "-", num) < 0) - { - output_good = false; - failed_at = 1000; - } - } - else - { - if (fprintf (stream, "%*s ", width, - human_readable ((uintmax_t) statp->st_mtime, hbuf, - human_ceiling, - 1, 1)) < 0) - { - output_good = false; - failed_at = 1100; - } - } - } + if (statp->st_mtime < 0) + { + char const *num = human_readable (- (uintmax_t) statp->st_mtime, + hbuf, human_ceiling, 1, 1); + int sign_width = width - strlen (num); + if (fprintf (stream, "%*s%s ", + sign_width < 0 ? 0 : sign_width, "-", num) < 0) + { + output_good = false; + failed_at = 1000; + } + } + else + { + if (fprintf (stream, "%*s ", width, + human_readable ((uintmax_t) statp->st_mtime, hbuf, + human_ceiling, + 1, 1)) < 0) + { + output_good = false; + failed_at = 1100; + } + } + } } if (output_good) { output_good = print_name (name, stream, literal_control_chars); if (!output_good) - { - failed_at = 1200; - } + { + failed_at = 1200; + } } if (output_good) { if (S_ISLNK (statp->st_mode)) - { - char *linkname = areadlinkat (dir_fd, relname); - if (linkname) - { - if (fputs (" -> ", stream) < 0) - { - output_good = false; - failed_at = 1300; - } - if (output_good) - { - output_good = print_name (linkname, stream, literal_control_chars); - if (!output_good) - { - failed_at = 1350; - } - } - } - else - { - /* POSIX requires in the case of find that if we issue a - * diagnostic we should have a nonzero status. However, - * this function doesn't have a way of telling the caller to - * do that. However, since this function is only used when - * processing "-ls", we're already using an extension. - */ - error (0, errno, "%s", name); - } - free (linkname); - } + { + char *linkname = areadlinkat (dir_fd, relname); + if (linkname) + { + if (fputs (" -> ", stream) < 0) + { + output_good = false; + failed_at = 1300; + } + if (output_good) + { + output_good = print_name (linkname, stream, literal_control_chars); + if (!output_good) + { + failed_at = 1350; + } + } + } + else + { + /* POSIX requires in the case of find that if we issue a + * diagnostic we should have a nonzero status. However, + * this function doesn't have a way of telling the caller to + * do that. However, since this function is only used when + * processing "-ls", we're already using an extension. + */ + error (0, errno, "%s", name); + } + free (linkname); + } if (output_good) - { - if (EOF == putc ('\n', stream)) - { - output_good = false; - if (!output_good) - { - failed_at = 1400; - } - } - } + { + if (EOF == putc ('\n', stream)) + { + output_good = false; + if (!output_good) + { + failed_at = 1400; + } + } + } } if (!output_good) { @@ -355,53 +470,53 @@ print_name_with_quoting (register const char *p, FILE *stream) { int fprintf_result = -1; switch (c) - { - case '\\': - fprintf_result = fprintf (stream, "\\\\"); - break; + { + case '\\': + fprintf_result = fprintf (stream, "\\\\"); + break; - case '\n': - fprintf_result = fprintf (stream, "\\n"); - break; + case '\n': + fprintf_result = fprintf (stream, "\\n"); + break; - case '\b': - fprintf_result = fprintf (stream, "\\b"); - break; + case '\b': + fprintf_result = fprintf (stream, "\\b"); + break; - case '\r': - fprintf_result = fprintf (stream, "\\r"); - break; + case '\r': + fprintf_result = fprintf (stream, "\\r"); + break; - case '\t': - fprintf_result = fprintf (stream, "\\t"); - break; + case '\t': + fprintf_result = fprintf (stream, "\\t"); + break; - case '\f': - fprintf_result = fprintf (stream, "\\f"); - break; + case '\f': + fprintf_result = fprintf (stream, "\\f"); + break; - case ' ': - fprintf_result = fprintf (stream, "\\ "); - break; + case ' ': + fprintf_result = fprintf (stream, "\\ "); + break; - case '"': - fprintf_result = fprintf (stream, "\\\""); - break; + case '"': + fprintf_result = fprintf (stream, "\\\""); + break; - default: - if (c > 040 && c < 0177) - { - if (EOF == putc (c, stream)) - return false; - fprintf_result = 1; /* otherwise it's used uninitialized. */ - } - else - { - fprintf_result = fprintf (stream, "\\%03o", (unsigned int) c); - } - } + default: + if (c > 040 && c < 0177) + { + if (EOF == putc (c, stream)) + return false; + fprintf_result = 1; /* otherwise it's used uninitialized. */ + } + else + { + fprintf_result = fprintf (stream, "\\%03o", (unsigned int) c); + } + } if (fprintf_result < 0) - return false; + return false; } return true; } diff --git a/po/Makevars b/po/Makevars index e2b1e1dd..d43c5cb4 100644 --- a/po/Makevars +++ b/po/Makevars @@ -8,7 +8,17 @@ subdir = po top_builddir = .. # These options get passed to xgettext. -XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ --from-code=UTF-8 +XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ \ + \ + --flag=_:1:pass-c-format\ + --flag=N_:1:pass-c-format\ + --flag=error:3:c-format --flag=error_at_line:5:c-format\ +\ + --from-code=UTF-8\ + --flag=asprintf:2:c-format --flag=vasprintf:2:c-format\ + --flag=asnprintf:3:c-format --flag=vasnprintf:3:c-format\ + --flag=wrapf:1:c-format\ + $${end_of_xgettext_options+} # This is the copyright holder that gets inserted into the header of the # $(DOMAIN).pot file. Set this to the copyright holder of the surrounding