Subject: bug#23422: stat -c %N returns strange results for file names including <TAB>

From: Pádraig Brady <[email-masked]>
Date: Thu, 3 Nov 2016 16:04:00 +0000

unarchive 23422
stop

On 05/05/16 09:23, Pádraig Brady wrote:
> Both points are valid.
> %N had a defacto style on Linux until now, but it was not documented.
> %N is not a standard style as it produces unquoted output on BSD for example.
> 
> For the next version of coreutils we'll document the default format
> and allow selection of the quoting style used.

Patch to do the above is attached.

thanks,
Pádraig

>From 7dc9336df1a87c7481aa72af7c3c058c7d5e9e9a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email-masked]>
Date: Thu, 3 Nov 2016 15:36:48 +0000
Subject: [PATCH] stat: make --format=%N honor the QUOTING_STYLE env var

* doc/coreutils.texi (stat invocation): Describe the
QUOTING_STYLE values now supported.
* src/stat.c (getenv_quoting_style): A new function called
from main, that sets the default quoting style for quotearg.
(main): Call getenv_quoting_style() when %N specified.
* tests/misc/stat-fmt.sh: Add a test case.
* NEWS: Mention the improvement.
Fixes http://bugs.gnu.org/23422
---
 NEWS                   |  3 +++
 doc/coreutils.texi     | 10 +++++++++-
 src/stat.c             | 41 ++++++++++++++++++++++++++++++++++++-----
 tests/misc/stat-fmt.sh | 15 ++++++++++++++-
 4 files changed, 62 insertions(+), 7 deletions(-)

diff --git a/NEWS b/NEWS
index 9548de8..844e233 100644
--- a/NEWS
+++ b/NEWS
@@ -85,6 +85,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   tail -f uses polling for "prl_fs" and "smb2", inotify for "m1fs",
   and attempts inotify for "wslfs".
 
+  stat --format=%N for quoting file names now honors the
+  same QUOTING_STYLE environment variable values as ls.
+
 ** New Features
 
   date now accepts the --debug option, to annotate the parsed date string,
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 95419b4..927e552 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -7724,6 +7724,7 @@ Use style @var{word} to quote file names and other strings that may
 contain arbitrary characters.  The @var{word} should
 be one of the following:
 
+@macro quotingStyles
 @table @samp
 @item literal
 Output strings as-is; this is the same as the @option{-N} or
@@ -7761,6 +7762,8 @@ surrounding quotation marks appropriate for the locale, and quote
 @t{'like this'} instead of @t{"like
 this"} in the default C locale.  This looks nicer on many displays.
 @end table
+@end macro
+@quotingStyles
 
 You can specify the default value of the @option{--quoting-style} option
 with the environment variable @env{QUOTING_STYLE}@.  If that environment
@@ -11775,7 +11778,7 @@ The valid @var{format} directives for files with @option{--format} and
 @item %i - Inode number
 @item %m - Mount point (See note below)
 @item %n - File name
-@item %N - Quoted file name with dereference if symbolic link
+@item %N - Quoted file name with dereference if symbolic link (see below)
 @item %o - Optimal I/O transfer size hint
 @item %s - Total size, in bytes
 @item %t - Major device type in hex (see below)
@@ -11797,6 +11800,11 @@ to control the zero padding of the output with the @samp{#} and @samp{0}
 printf flags. For example to pad to at least 3 wide while making larger
 numbers unambiguously octal, you can use @samp{%#03a}.
 
+The @samp{%N} format can be set with the environment variable
+@env{QUOTING_STYLE}@.  If that environment variable is not set,
+the default value is @samp{shell-escape}.  Valid quoting styles are:
+@quotingStyles
+
 The @samp{%t} and @samp{%T} formats operate on the st_rdev member of
 the stat(2) structure, and are only defined for character and block
 special files.  On some systems or file types, st_rdev may be used to
diff --git a/src/stat.c b/src/stat.c
index 53a6cb7..a43eca4 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -59,6 +59,7 @@
 #include "system.h"
 
 #include "areadlink.h"
+#include "argmatch.h"
 #include "die.h"
 #include "error.h"
 #include "file-type.h"
@@ -997,6 +998,32 @@ neg_to_zero (struct timespec ts)
   return z;
 }
 
+/* Set the quoting style default if the environment variable
+   QUOTING_STYLE is set.  */
+
+static void
+getenv_quoting_style (void)
+{
+  char const *q_style = getenv ("QUOTING_STYLE");
+  if (q_style)
+    {
+      int i = ARGMATCH (q_style, quoting_style_args, quoting_style_vals);
+      if (0 <= i)
+        set_quoting_style (NULL, quoting_style_vals[i]);
+      else
+        {
+          set_quoting_style (NULL, shell_escape_always_quoting_style);
+          error (0, 0, _("ignoring invalid value of environment "
+                         "variable QUOTING_STYLE: %s"), quote (q_style));
+        }
+    }
+  else
+    set_quoting_style (NULL, shell_escape_always_quoting_style);
+}
+
+/* Equivalent to quotearg(), but explicit to avoid syntax checks.  */
+#define quoteN(x) quotearg_style (get_quoting_style (NULL), x)
+
 /* Print stat info.  Return zero upon success, nonzero upon failure.  */
 static bool
 print_stat (char *pformat, size_t prefix_len, unsigned int m,
@@ -1013,7 +1040,7 @@ print_stat (char *pformat, size_t prefix_len, unsigned int m,
       out_string (pformat, prefix_len, filename);
       break;
     case 'N':
-      out_string (pformat, prefix_len, quoteaf (filename));
+      out_string (pformat, prefix_len, quoteN (filename));
       if (S_ISLNK (statbuf->st_mode))
         {
           char *linkname = areadlink_with_size (filename, statbuf->st_size);
@@ -1024,7 +1051,7 @@ print_stat (char *pformat, size_t prefix_len, unsigned int m,
               return true;
             }
           printf (" -> ");
-          out_string (pformat, prefix_len, quoteaf (linkname));
+          out_string (pformat, prefix_len, quoteN (linkname));
           free (linkname);
         }
       break;
@@ -1602,11 +1629,15 @@ main (int argc, char *argv[])
     }
 
   if (format)
-    format2 = format;
+    {
+      if (strstr (format, "%N"))
+        getenv_quoting_style ();
+      format2 = format;
+    }
   else
     {
-      format = default_format (fs, terse, false);
-      format2 = default_format (fs, terse, true);
+      format = default_format (fs, terse, /* device= */ false);
+      format2 = default_format (fs, terse, /* device= */ true);
     }
 
   for (i = optind; i < argc; i++)
diff --git a/tests/misc/stat-fmt.sh b/tests/misc/stat-fmt.sh
index baa7ca7..1245ddb 100755
--- a/tests/misc/stat-fmt.sh
+++ b/tests/misc/stat-fmt.sh
@@ -20,11 +20,24 @@
 print_ver_ stat

-
+# ensure that stat properly handles a format string ending with %
 for i in $(seq 50); do
   fmt=$(printf "%${i}s" %)
   out=$(stat --form="$fmt" .)
   test "$out" = "$fmt" || fail=1
 done
 
+# ensure QUOTING_STYLE is honored by %N
+touch "'" || framework_failure_
+# Default since v8.25
+stat -c%N \' >> out || fail=1
+# Default before v8.25
+QUOTING_STYLE=locale stat -c%N \' >> out || fail=1
+cat <<\EOF >exp
+"'"
+'\''
+EOF
+compare exp out || fail=1
+
+
 Exit $fail
-- 
2.5.5