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.
This commit is contained in:
James Youngman 2015-10-31 23:24:28 +00:00
parent f4dd5bdbd8
commit 731826cc8d
5 changed files with 991 additions and 858 deletions

2
NEWS
View File

@ -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 <dir> -ls'
#45585: unclear description of -newerXY in manual page.
#45505: give a more explicit error message when the argument to -regex

View File

@ -118,6 +118,7 @@ gnulib_modules="
math
mbrtowc
mbscasestr
mbswidth
mbsstr
mktime
modechange

View File

@ -1001,6 +1001,11 @@ do_fprintf (struct format_val *dest,
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,

View File

@ -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"
@ -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.
@ -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,29 +142,50 @@ list_file (const char *name,
strmode (statp->st_mode, modebuf);
#endif
if (fprintf (stream, "%6s ",
chars_out = fprintf (stream, "%*s", inode_number_width,
human_readable ((uintmax_t) statp->st_ino, hbuf,
human_ceiling,
1u, 1u)) < 0)
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 ",
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)) < 0)
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". */
if (fprintf (stream, "%s%3lu ", modebuf, (unsigned long) statp->st_nlink) < 0)
@ -149,16 +197,27 @@ list_file (const char *name,
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);
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);
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;
}
@ -169,32 +228,70 @@ list_file (const char *name,
group_name = getgroup (statp->st_gid);
if (group_name)
{
output_good = (fprintf (stream, "%-8s ", group_name) >= 0);
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);
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 ",
if (!print_num (stream,
(unsigned long) major (statp->st_rdev),
(unsigned long) minor (statp->st_rdev)) < 0)
&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)
if (fprintf (stream, "%*s %*s",
major_device_number_width,
minor_device_number_width) < 0)
{
output_good = false;
failed_at = 700;
@ -203,15 +300,33 @@ list_file (const char *name,
}
else
{
if (fprintf (stream, "%8s ",
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,
output_block_size < 0 ? output_block_size : 1)) < 0)
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;
}
}

View File

@ -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