Subject: bug+patch: du output misaligned on different terminals

From: "L. A. Walsh" <[email-masked]>
Date: Mon, 27 Apr 2015 12:11:22 -0700

This is a fix/work-around for (RFE#19849 (bug#19849) which was 
about addingg options to expand tabs and/or set a tabsize
for output from 'du' so output would line up as intended.

Without that enhancement, the current output is "messed
up" on terminals/consoles that don't use hard-coded-constant
widths for tabs (like many or most of the Xterm & linux
consoles).

Adding the switches is more work than I want to chew
off right now, but the misaligned output made for difficult
reading (besides looking bad), especially w/a monospace font
where it is clear that the columns were meant to lineup.
So I threw together a quick patch against the current 
git source (changes limited to 'du.c').

If someone would look it over, try it, or such and apply it
to the current coreutils source tree (it's in patch form
against 'src/du.c') for some soon future release, (at least
until such time as the above mentioned RFE can be addressed).

123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 
The current du output (example from my tmp dir) on a
term w/o hard-coded-constant expansion looks like:

Ishtar:tools/coreutils/work/src> /usr/bin/du /tmp/t*
4 /tmp/t
1160  /tmp/t1
680 /tmp/t2
4 /tmp/tab2.patch
20  /tmp/tabs
4 /tmp/tmpf
4 /tmp/topcmds
24  /tmp/topcmds-hlps
24  /tmp/topcmds2
8 /tmp/topcmds2.txt
4 /tmp/tq1
32  /tmp/tt
32  /tmp/tt

*Without* the assumption of hard-coded or fixed tabs (using 
a 8-spaces/tab as seems to be the implementors assumption /
intention), the output columns, again, line-up vertically:

Ishtar:tools/coreutils/work/src> ./du /tmp/t*       
4       /tmp/t
1160    /tmp/t1
680     /tmp/t2
4       /tmp/tab2.patch
20      /tmp/tabs
4       /tmp/tmpf
4       /tmp/topcmds
24      /tmp/topcmds-hlps
24      /tmp/topcmds2
8       /tmp/topcmds2.txt
4       /tmp/tq1
32      /tmp/tt

While not addressing the RFE, at least the original output format
should look the same on all terminals

Thanks,
Linda Walsh

(FWIW - the patch may be used under the same license as the rest
of 'du.c').

--- src/du.c	2015-04-01 17:31:02.000000000 -0700
+++ src/du.c	2015-04-26 16:16:43.829602843 -0700
@@ -1,3 +1,4 @@
+
 /* du -- summarize disk usage
    Copyright (C) 1988-2015 Free Software Foundation, Inc.
 
@@ -370,52 +371,86 @@
 
 /* FIXME: this code is nearly identical to code in date.c  */
 /* Display the date and time in WHEN according to the format specified
-   in FORMAT.  */
+   in FORMAT.
+   Note modificatino to return # of chars (exclusive of NUL)  */
 
-static void
+static int
 show_date (const char *format, struct timespec when)
 {
   struct tm *tm = localtime (&when.tv_sec);
+  int slen = 0;
   if (! tm)
     {
       char buf[INT_BUFSIZE_BOUND (intmax_t)];
       char *when_str = timetostr (when.tv_sec, buf);
       error (0, 0, _("time %s is out of range"), when_str);
+      slen=strlen(when_str);
       fputs (when_str, stdout);
-      return;
+      return slen;
     }
 
-  fprintftime (stdout, format, tm, 0, when.tv_nsec);
+  slen += fprintftime (stdout, format, tm, 0, when.tv_nsec);
+  return slen;
 }
 
 /* Print N_BYTES.  Convert it to a readable value before printing.  */
 
-static void
+static int
 print_only_size (uintmax_t n_bytes)
 {
   char buf[LONGEST_HUMAN_READABLE + 1];
-  fputs ((n_bytes == UINTMAX_MAX
+	char * outp;
+  fputs (outp = ((n_bytes == UINTMAX_MAX
           ? _("Infinity")
           : human_readable (n_bytes, buf, human_output_opts,
-                            1, output_block_size)),
+                            1, output_block_size))),
          stdout);
+	return strlen(outp);
+}
+
+
+/* using buf of len 'n', return a nul-term. string w/# spaces to ins for tab.
+ * If tabsize is 0, a 0-len string will be returned.
+ */
+
+static char *
+tabnspaces(char * buf, int n, int cur_output_len, int tabsize)
+{
+  char * obuf = buf;
+  int spaces =  tabsize ? tabsize - (cur_output_len % tabsize) : 0;
+
+  if (!n) return NULL;
+
+  /* pre-dec 'n' to reserve space for nul */
+  while(--n > 0  &&  spaces-- > 0) *obuf++ = ' ';
+  *obuf = '\0';
+  return buf;
 }
 
 /* Print size (and optionally time) indicated by *PDUI, followed by STRING.  */
 
+int term_tabsize = 8; /* default for now */
+
 static void
 print_size (const struct duinfo *pdui, const char *string)
 {
-  print_only_size (opt_inodes
-                   ? pdui->inodes
-                   : pdui->size);
+	int print_len = print_only_size (opt_inodes
+                                   ? pdui->inodes
+                                   : pdui->size);
+  char Xtab[1024];  /* Xpanded tab */
+  char * myspaces;
 
   if (opt_time)
     {
-      putchar ('\t');
-      show_date (time_format, pdui->tmax);
+  		myspaces = tabnspaces(Xtab, sizeof(Xtab), print_len, term_tabsize);
+			print_len+=strlen(myspaces);
+      fputs (myspaces, stdout);
+      print_len+= show_date (time_format, pdui->tmax);
     }
-  printf ("\t%s%c", string, opt_nul_terminate_output ? '\0' : '\n');
+ 
+  myspaces = tabnspaces(Xtab, sizeof(Xtab), print_len, term_tabsize);
+  fputs(myspaces, stdout);
+  printf ("%s%c", string, opt_nul_terminate_output ? '\0' : '\n');
   fflush (stdout);
 }