Whamcloud - gitweb
LU-17000 utils: handle_yaml_no_op() has wrong signature
[fs/lustre-release.git] / lustre / tests / statx.c
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 /*
3  * Copyright (c) 2019, DDN Storage Corporation.
4  */
5 /*
6  * This file is part of Lustre, http://www.lustre.org/
7  */
8 /*
9  *
10  * Test for Lustre statx().
11  * It uses some code in coreutils ('ls.c' and 'stat.c') for reference.
12  *
13  * Author: Qian Yingjin <qian@ddn.com>
14  */
15 #define _ATFILE_SOURCE
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <getopt.h>
20 #include <limits.h>
21 #include <unistd.h>
22 #include <ctype.h>
23 #include <errno.h>
24 #include <time.h>
25 #include <sys/time.h>
26 #include <pwd.h>
27 #include <grp.h>
28 #include <dirent.h>
29 #include <sys/syscall.h>
30 #include <sys/types.h>
31 #include <sys/param.h>
32 #include <sys/sysmacros.h>
33 #include <inttypes.h>
34 #include <fcntl.h>
35 #include <locale.h>
36 #include <linux/lustre/lustre_user.h>
37
38 #ifdef HAVE_SELINUX
39 #include <selinux/selinux.h>
40 #endif
41
42 /* Factor out some of the common --help and --version processing code. */
43
44 /* These enum values cannot possibly conflict with the option values
45  * ordinarily used by commands, including CHAR_MAX + 1, etc.  Avoid
46  * CHAR_MIN - 1, as it may equal -1, the getopt end-of-options value.
47  */
48 enum {
49         PRINTF_OPTION = (CHAR_MAX + 1),
50         GETOPT_HELP_CHAR = (CHAR_MIN - 2),
51         GETOPT_VERSION_CHAR = (CHAR_MIN - 3)
52 };
53
54 static bool o_quiet;
55
56 #ifdef __NR_statx
57 #ifndef HAVE_STATX
58
59 #define AT_STATX_SYNC_TYPE      0x6000
60 #define AT_STATX_FORCE_SYNC     0x2000
61 #define AT_STATX_DONT_SYNC      0x4000
62
63 static __attribute__((unused))
64 ssize_t statx(int dfd, const char *filename, int flags,
65               unsigned int mask, struct statx *buffer)
66 {
67         return syscall(__NR_statx, dfd, filename, flags, mask, buffer);
68 }
69 #endif /* HAVE_STATX */
70
71 #define xstrdup(str) strdup(str)
72 static inline
73 char *xasprintf(const char *fmt, const char *old_fmt, const char *str)
74 {
75         char *tmp = NULL;
76
77         if (asprintf(&tmp, fmt, old_fmt, str) < 0) {
78                 fprintf(stderr, "asprintf allocation failed\n");
79                 exit(1);
80         }
81
82         return tmp;
83 }
84
85
86 /* coreutils/lib/intprops.h */
87 #define _GL_SIGNED_TYPE_OR_EXPR(t) TYPE_SIGNED(__typeof__(t))
88
89 /* Bound on length of the string representing an unsigned integer
90  * value representable in B bits.  log10 (2.0) < 146/485.  The
91  * smallest value of B where this bound is not tight is 2621.
92  */
93 #define INT_BITS_STRLEN_BOUND(b) (((b) * 146 + 484) / 485)
94
95 /* The width in bits of the integer type or expression T.
96  * Do not evaluate T.
97  * Padding bits are not supported; this is checked at compile-time below.
98  */
99 #define TYPE_WIDTH(t) (sizeof(t) * CHAR_BIT)
100
101 /* Bound on length of the string representing an integer type or expression T.
102  * Subtract 1 for the sign bit if T is signed, and then add 1 more for
103  * a minus sign if needed.
104  *
105  * Because _GL_SIGNED_TYPE_OR_EXPR sometimes returns 1 when its argument is
106  * unsigned, this macro may overestimate the true bound by one byte when
107  * applied to unsigned types of size 2, 4, 16, ... bytes.
108  */
109 #define INT_STRLEN_BOUND(t)                                     \
110         (INT_BITS_STRLEN_BOUND(TYPE_WIDTH(t) - _GL_SIGNED_TYPE_OR_EXPR(t)) \
111         + _GL_SIGNED_TYPE_OR_EXPR(t))
112
113 /* Bound on buffer size needed to represent an integer type or expression T,
114  * including the terminating null.
115  */
116 #define INT_BUFSIZE_BOUND(t) (INT_STRLEN_BOUND(t) + 1)
117
118 /* The maximum and minimum values for the integer type T.  */
119 #define TYPE_MINIMUM(t) ((t)~TYPE_MAXIMUM(t))
120 #define TYPE_MAXIMUM(t)                                         \
121         ((t) (!TYPE_SIGNED(t)                                   \
122         ? (t)-1                                         \
123         : ((((t)1 << (TYPE_WIDTH(t) - 2)) - 1) * 2 + 1)))
124
125 static bool o_dir_list;
126 static bool long_format; /* use a long listing format */
127
128 /* Current time in seconds and nanoseconds since 1970, updated as
129  * needed when deciding whether a file is recent.
130  */
131 static struct timespec current_time;
132
133 /* FIXME: these are used by printf.c, too */
134 #define isodigit(c) ('0' <= (c) && (c) <= '7')
135 #define octtobin(c) ((c) - '0')
136 #define hextobin(c) ((c) >= 'a' && (c) <= 'f' ? (c) - 'a' + 10 : \
137                      (c) >= 'A' && (c) <= 'F' ? (c) - 'A' + 10 : (c) - '0')
138
139 #define ISDIGIT(c) ((unsigned int)(c) - '0' <= 9)
140
141 /* True if the real type T is signed.  */
142 #define TYPE_SIGNED(t) (!((t)0 < (t)-1))
143
144 static char const digits[] = "0123456789";
145
146 /* Flags that are portable for use in printf, for at least one
147  * conversion specifier; make_format removes unportable flags as
148  * needed for particular specifiers.  The glibc 2.2 extension "I" is
149  * listed here; it is removed by make_format because it has undefined
150  * behavior elsewhere and because it is incompatible with
151  * out_epoch_sec.
152  */
153 static char const printf_flags[] = "'-+ #0I";
154
155 /* Formats for the --terse option. */
156 static char const fmt_terse_fs[] = "%n %i %l %t %s %S %b %f %a %c %d\n";
157 static char const fmt_terse_regular[] = "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %W %o\n";
158 #ifdef HAVE_SELINUX
159 static char const fmt_terse_selinux[] = "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %W %o %C\n";
160 #endif
161 static char *format;
162
163 /* Whether to follow symbolic links;  True for --dereference (-L).  */
164 static bool follow_links;
165
166 /* Whether to interpret backslash-escape sequences.
167  * True for --printf=FMT, not for --format=FMT (-c).
168  */
169 static bool interpret_backslash_escapes;
170
171 /* The trailing delimiter string:
172  * "" for --printf=FMT, "\n" for --format=FMT (-c).
173  */
174 static char const *trailing_delim = "";
175
176 /* The representation of the decimal point in the current locale.  */
177 static char const *decimal_point;
178 static size_t decimal_point_len;
179
180 /* Convert a possibly-signed character to an unsigned character.  This is
181  * a bit safer than casting to unsigned char, since it catches some type
182  * errors that the cast doesn't.
183  */
184 static inline unsigned char to_uchar(char ch)
185 {
186         return ch;
187 }
188
189 static void usage(char *prog)
190 {
191         printf("Usage: %s [options] <FILE>...\n", prog);
192         printf("Display file status via statx() syscall.\n"
193                "List information about the FILE "
194                "(the current diretory by default) via statx() syscall.\n"
195                "options:\n"
196                "\t-L --dereference   follow links\n"
197                "\t--cached=MODE      specify how to use cached attributes;\n"
198                "\t                     See MODE below\n"
199                "\t-c --format=FORMAT use the specified FORMAT instead of the "
200                "default;\n"
201                "\t                     output a newline after each use of "
202                "FORMAT\n"
203                "\t-t, --terse        print the information in terse form\n"
204                "\t-D --dir           list information about the FILE (ls)\n"
205                "\t-l                 use a long listing format\n"
206                "\t-q --quiet         do not display results, test only\n\n"
207                "The --cached MODE argument can be; always, never, or default.\n"
208                "`always` will use cached attributes if available, while\n"
209                "`never` will try to synchronize with the latest attributes,\n"
210                "and `default` will leave it up to the underlying file system.\n"
211                "\n"
212                "The valid format sequences for files (without --file-system):\n"
213                "\n"
214                "\t%%a  access rights in octal (note '#' and '0' printf flags)\n"
215                "\t%%A   access rights in human readable form\n"
216                "\t%%b   number of blocks allocated (see %%B)\n"
217                "\t%%B   the size in bytes of each block reported by %%b\n"
218                "\t%%C   SELinux security context string\n"
219                "\t%%d   device number in decimal\n"
220                "\t%%D   device number in hex\n"
221                "\t%%f   raw mode in hex\n"
222                "\t%%F   file type\n"
223                "\t%%g   group ID of owner\n"
224                "\t%%G   group name of owner\n"
225                "\t%%h   number of hard links\n"
226                "\t%%i   inode number\n"
227                "\t%%m   mount point\n"
228                "\t%%n   file name\n"
229                "\t%%N   quoted file name with dereference if symbolic link\n"
230                "\t%%o   optimal I/O transfer size hint\n"
231                "\t%%p   Mask to show what's supported in stx_attributes\n"
232                "\t%%r   Flags conveying information about the file: "
233                "stx_attributes\n"
234                "\t%%s   total size, in bytes\n"
235                "\t%%t   major device type in hex, for character/block device "
236                "special files\n"
237                "\t%%T   minor device type in hex, for character/block device "
238                "special files\n"
239                "\t%%u   user ID of owner\n"
240                "\t%%U   user name of owner\n"
241                "\t%%w   time of file birth, human-readable; - if unknown\n"
242                "\t%%W   time of file birth, seconds since Epoch; 0 if unknown\n"
243                "\t%%x   time of last access, human-readable\n"
244                "\t%%X   time of last access, seconds since Epoch\n"
245                "\t%%y   time of last data modification, human-readable\n"
246                "\t%%Y   time of last data modification, seconds since Epoch\n"
247                "\t%%z   time of last status change, human-readable\n"
248                "\t%%Z   time of last status change, seconds since Epoch\n");
249         exit(0);
250 }
251
252 /* gnulib/lib/filemode.c */
253 /* Return a character indicating the type of file described by
254  * file mode BITS:
255  * '-' regular file
256  * 'b' block special file
257  * 'c' character special file
258  * 'C' high performance ("contiguous data") file
259  * 'd' directory
260  * 'D' door
261  * 'l' symbolic link
262  * 'm' multiplexed file (7th edition Unix; obsolete)
263  * 'n' network special file (HP-UX)
264  * 'p' fifo (named pipe)
265  * 'P' port
266  * 's' socket
267  * 'w' whiteout (4.4BSD)
268  * '?' some other file type
269  */
270 static char ftypelet(mode_t bits)
271 {
272         /* These are the most common, so test for them first.*/
273         if (S_ISREG(bits))
274                 return '-';
275         if (S_ISDIR(bits))
276                 return 'd';
277
278         /* Other letters standardized by POSIX 1003.1-2004.*/
279         if (S_ISBLK(bits))
280                 return 'b';
281         if (S_ISCHR(bits))
282                 return 'c';
283         if (S_ISLNK(bits))
284                 return 'l';
285         if (S_ISFIFO(bits))
286                 return 'p';
287
288         /* Other file types (though not letters) standardized by POSIX.*/
289         if (S_ISSOCK(bits))
290                 return 's';
291
292         return '?';
293 }
294
295 /* Like filemodestring, but rely only on MODE.*/
296 static void strmode(mode_t mode, char *str)
297 {
298         str[0] = ftypelet(mode);
299         str[1] = mode & 0400 ? 'r' : '-';
300         str[2] = mode & 0200 ? 'w' : '-';
301         str[3] = (mode & S_ISUID
302                         ? (mode & 0100 ? 's' : 'S')
303                         : (mode & 0100 ? 'x' : '-'));
304         str[4] = mode & 0040 ? 'r' : '-';
305         str[5] = mode & 0020 ? 'w' : '-';
306         str[6] = (mode & S_ISGID
307                         ? (mode & 0010 ? 's' : 'S')
308                         : (mode & 0010 ? 'x' : '-'));
309         str[7] = mode & 0004 ? 'r' : '-';
310         str[8] = mode & 0002 ? 'w' : '-';
311         str[9] = (mode & S_ISVTX
312                         ? (mode & 0001 ? 't' : 'T')
313                         : (mode & 0001 ? 'x' : '-'));
314         str[10] = ' ';
315         str[11] = '\0';
316 }
317
318 /* filemodestring - fill in string STR with an ls-style ASCII
319  * representation of the st_mode field of file stats block STATP.
320  * 12 characters are stored in STR.
321  * The characters stored in STR are:
322  *
323  * 0    File type, as in ftypelet above, except that other letters are used
324  *      for files whose type cannot be determined solely from st_mode:
325  *
326  *          'F' semaphore
327  *          'M' migrated file (Cray DMF)
328  *          'Q' message queue
329  *          'S' shared memory object
330  *          'T' typed memory object
331  *
332  * 1    'r' if the owner may read, '-' otherwise.
333  *
334  * 2    'w' if the owner may write, '-' otherwise.
335  *
336  * 3    'x' if the owner may execute, 's' if the file is
337  *      set-user-id, '-' otherwise.
338  *      'S' if the file is set-user-id, but the execute
339  *      bit isn't set.
340  *
341  * 4    'r' if group members may read, '-' otherwise.
342  *
343  * 5    'w' if group members may write, '-' otherwise.
344  *
345  * 6    'x' if group members may execute, 's' if the file is
346  *      set-group-id, '-' otherwise.
347  *      'S' if it is set-group-id but not executable.
348  *
349  * 7    'r' if any user may read, '-' otherwise.
350  *
351  * 8    'w' if any user may write, '-' otherwise.
352  *
353  * 9    'x' if any user may execute, 't' if the file is "sticky"
354  *      (will be retained in swap space after execution), '-'
355  *      otherwise.
356  *      'T' if the file is sticky but not executable.
357  *
358  * 10   ' ' for compatibility with 4.4BSD strmode,
359  *      since this interface does not support ACLs.
360  *
361  * 11   '\0'.
362  */
363 static void filemodestring(struct statx const *stxp, char *str)
364 {
365         strmode(stxp->stx_mode, str);
366 }
367
368 /* gnulib/lib/file-type.c */
369 static char const *file_type(struct statx const *stx)
370 {
371         /* See POSIX 1003.1-2001 XCU Table 4-8 lines 17093-17107 for some of
372          * these formats.
373          *
374          * To keep diagnostics grammatical in English, the returned string
375          * must start with a consonant.
376          */
377         /* Do these three first, as they're the most common.  */
378         if (S_ISREG(stx->stx_mode))
379                 return stx->stx_size == 0 ? "regular empty file" :
380                                             "regular file";
381
382         if (S_ISDIR(stx->stx_mode))
383                 return "directory";
384
385         if (S_ISLNK(stx->stx_mode))
386                 return "symbolic link";
387
388         /* The remaining are in alphabetical order.  */
389         if (S_ISBLK(stx->stx_mode))
390                 return "block special file";
391
392         if (S_ISCHR(stx->stx_mode))
393                 return "character special file";
394
395         if (S_ISFIFO(stx->stx_mode))
396                 return "fifo";
397
398         if (S_ISSOCK(stx->stx_mode))
399                 return "socket";
400
401         return "weird file";
402 }
403
404 /* gnulib/lib/areadlink-with-size.c */
405 /* SYMLINK_MAX is used only for an initial memory-allocation sanity
406  * check, so it's OK to guess too small on hosts where there is no
407  * arbitrary limit to symbolic link length.
408  */
409 #ifndef SYMLINK_MAX
410 #define SYMLINK_MAX 1024
411 #endif
412
413 #define MAXSIZE (SIZE_MAX < SSIZE_MAX ? SIZE_MAX : SSIZE_MAX)
414
415 /* Call readlink to get the symbolic link value of FILE.
416  * SIZE is a hint as to how long the link is expected to be;
417  * typically it is taken from st_size.  It need not be correct.
418  * Return a pointer to that NUL-terminated string in malloc'd storage.
419  * If readlink fails, malloc fails, or if the link value is longer
420  * than SSIZE_MAX, return NULL (caller may use errno to diagnose).
421  */
422 static char *areadlink_with_size(char const *file, size_t size)
423 {
424         /* Some buggy file systems report garbage in st_size.  Defend
425          * against them by ignoring outlandish st_size values in the initial
426          * memory allocation.
427          */
428         size_t symlink_max = SYMLINK_MAX;
429         size_t INITIAL_LIMIT_BOUND = 8 * 1024;
430         size_t initial_limit = (symlink_max < INITIAL_LIMIT_BOUND ?
431                                 symlink_max + 1 : INITIAL_LIMIT_BOUND);
432         enum { stackbuf_size = 128 };
433         /* The initial buffer size for the link value. */
434         size_t buf_size = (size == 0 ? stackbuf_size : size < initial_limit ?
435                            size + 1 : initial_limit);
436
437         while (1) {
438                 ssize_t r;
439                 size_t link_length;
440                 char stackbuf[stackbuf_size];
441                 char *buf = stackbuf;
442                 char *buffer = NULL;
443
444                 if (!(size == 0 && buf_size == stackbuf_size)) {
445                         buf = buffer = malloc(buf_size);
446                         if (!buffer)
447                                 return NULL;
448                 }
449
450                 r = readlink(file, buf, buf_size);
451                 link_length = r;
452
453                 /* On AIX 5L v5.3 and HP-UX 11i v2 04/09, readlink returns -1
454                  * with errno == ERANGE if the buffer is too small.
455                  */
456                 if (r < 0 && errno != ERANGE) {
457                         int saved_errno = errno;
458
459                         free(buffer);
460                         errno = saved_errno;
461                         return NULL;
462                 }
463
464                 if (link_length < buf_size) {
465                         buf[link_length] = 0;
466                         if (!buffer) {
467                                 buffer = malloc(link_length + 1);
468                                 if (buffer)
469                                         return memcpy(buffer, buf,
470                                                       link_length + 1);
471                         } else if (link_length + 1 < buf_size) {
472                                 /* Shrink BUFFER before returning it. */
473                                 char *shrinked_buffer;
474
475                                 shrinked_buffer = realloc(buffer,
476                                                           link_length + 1);
477                                 if (shrinked_buffer != NULL)
478                                         buffer = shrinked_buffer;
479                         }
480                         return buffer;
481                 }
482
483                 free(buffer);
484                 if (buf_size <= MAXSIZE / 2) {
485                         buf_size *= 2;
486                 } else if (buf_size < MAXSIZE) {
487                         buf_size = MAXSIZE;
488                 } else {
489                         errno = ENOMEM;
490                         return NULL;
491                 }
492         }
493 }
494
495 /* coreutils/src/stat.c */
496 /* Output a single-character \ escape.  */
497 static void print_esc_char(char c)
498 {
499         switch (c) {
500         case 'a':                       /* Alert. */
501                 c = '\a';
502                 break;
503         case 'b':                       /* Backspace. */
504                 c = '\b';
505                 break;
506         case 'e':                       /* Escape. */
507                 c = '\x1B';
508                 break;
509         case 'f':                       /* Form feed. */
510                 c = '\f';
511                 break;
512         case 'n':                       /* New line. */
513                 c = '\n';
514                 break;
515         case 'r':                       /* Carriage return. */
516                 c = '\r';
517                 break;
518         case 't':                       /* Horizontal tab. */
519                 c = '\t';
520                 break;
521         case 'v':                       /* Vertical tab. */
522                 c = '\v';
523                 break;
524         case '"':
525         case '\\':
526                 break;
527         default:
528                 printf("warning: unrecognized escape '\\%c'", c);
529                 break;
530         }
531         putchar (c);
532 }
533
534 static size_t format_code_offset(char const *directive)
535 {
536         size_t len = strspn(directive + 1, printf_flags);
537         char const *fmt_char = directive + len + 1;
538
539         fmt_char += strspn(fmt_char, digits);
540         if (*fmt_char == '.')
541                 fmt_char += 1 + strspn(fmt_char + 1, digits);
542
543         return fmt_char - directive;
544 }
545
546 static unsigned int fmt_to_mask(char fmt)
547 {
548         switch (fmt) {
549         case 'N':
550                 return STATX_MODE;
551         case 'd':
552         case 'D':
553                 return STATX_MODE;
554         case 'i':
555                 return STATX_INO;
556         case 'a':
557         case 'A':
558                 return STATX_MODE;
559         case 'f':
560                 return STATX_MODE|STATX_TYPE;
561         case 'F':
562                 return STATX_TYPE;
563         case 'h':
564                 return STATX_NLINK;
565         case 'u':
566         case 'U':
567                 return STATX_UID;
568         case 'g':
569         case 'G':
570                 return STATX_GID;
571         case 'm':
572                 return STATX_MODE|STATX_INO;
573         case 's':
574                 return STATX_SIZE;
575         case 't':
576         case 'T':
577                 return STATX_MODE;
578         case 'b':
579                 return STATX_BLOCKS;
580         case 'w':
581         case 'W':
582                 return STATX_BTIME;
583         case 'x':
584         case 'X':
585                 return STATX_ATIME;
586         case 'y':
587         case 'Y':
588                 return STATX_MTIME;
589         case 'z':
590         case 'Z':
591                 return STATX_CTIME;
592         }
593         return 0;
594 }
595
596 static unsigned int format_to_mask(char const *format)
597 {
598         unsigned int mask = 0;
599         char const *b;
600
601         for (b = format; *b; b++) {
602                 if (*b != '%')
603                         continue;
604
605                 b += format_code_offset(b);
606                 if (*b == '\0')
607                         break;
608                 mask |= fmt_to_mask(*b);
609         }
610
611         return mask;
612 }
613
614 static char *human_access(struct statx const *stxbuf)
615 {
616         static char modebuf[12];
617
618         filemodestring(stxbuf, modebuf);
619         modebuf[10] = 0;
620         return modebuf;
621 }
622
623 static inline struct timespec
624 statx_timestamp_to_timespec(struct statx_timestamp tsx)
625 {
626         struct timespec ts;
627
628         ts.tv_sec = tsx.tv_sec;
629         ts.tv_nsec = tsx.tv_nsec;
630
631         return ts;
632 }
633
634 static int timespec_cmp(struct timespec a, struct timespec b)
635 {
636         if (a.tv_sec < b.tv_sec)
637                 return -1;
638         if (a.tv_sec > b.tv_sec)
639                 return 1;
640
641         return a.tv_nsec - b.tv_nsec;
642 }
643
644 static char *human_time(const struct statx_timestamp *ts)
645 {
646         /* STR must be at least INT_BUFSIZE_BOUND (intmax_t) big, either
647          * because localtime_rz fails, or because the time zone is truly
648          * outlandish so that %z expands to a long string.
649          */
650         static char str[INT_BUFSIZE_BOUND(intmax_t)
651                 + INT_STRLEN_BOUND(int) /* YYYY */
652                 + 1 /* because YYYY might equal INT_MAX + 1900 */
653                 + sizeof "-MM-DD HH:MM:SS.NNNNNNNNN +"];
654         struct tm tm;
655         time_t tim;
656         int len;
657         int len2;
658
659         tim = ts->tv_sec;
660         if (!localtime_r(&tim, &tm)) {
661                 perror("localtime_r");
662                 exit(EXIT_FAILURE);
663         }
664
665         if (o_dir_list && long_format) {
666                 struct timespec when_timespec;
667                 struct timespec six_months_ago;
668                 bool recent;
669
670                 when_timespec = statx_timestamp_to_timespec(*ts);
671                 /* If the file appears to be in the future, update the current
672                  * time, in case the file happens to have been modified since
673                  * the last time we checked the clock.
674                  */
675                 if (timespec_cmp(current_time, when_timespec) < 0) {
676                         struct timeval tv;
677
678                         gettimeofday(&tv, NULL);
679                         current_time.tv_sec = tv.tv_sec;
680                         current_time.tv_nsec = tv.tv_usec * 1000;
681                 }
682
683                 /* Consider a time to be recent if it is within the past six
684                  * months.
685                  * A Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952
686                  * seconds on the average.  Write this value as an integer
687                  * constant to avoid floating point hassles.
688                  */
689                 six_months_ago.tv_sec = current_time.tv_sec - 31556952 / 2;
690                 six_months_ago.tv_nsec = current_time.tv_nsec;
691
692                 recent = (timespec_cmp(six_months_ago, when_timespec) < 0 &&
693                           (timespec_cmp(when_timespec, current_time) < 0));
694
695                 /* We assume here that all time zones are offset from UTC by a
696                  * whole number of seconds.
697                  */
698                 len = strftime(str, sizeof(str),
699                                recent ? "%b %e %H:%M" : "%b %e %Y", &tm);
700                 if (len == 0) {
701                         perror("strftime");
702                         exit(EXIT_FAILURE);
703                 }
704
705                 return str;
706         }
707
708         len = strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S", &tm);
709         if (len == 0) {
710                 perror("strftime");
711                 exit(EXIT_FAILURE);
712         }
713
714         len2 = snprintf(str + len, sizeof(str) - len, ".%09u ", ts->tv_nsec);
715         len = strftime(str + len + len2, sizeof(str) - len - len2, "%z", &tm);
716         if (len == 0) {
717                 perror("strftime2");
718                 exit(1);
719         }
720
721         return str;
722 }
723
724 /* PFORMAT points to a '%' followed by a prefix of a format, all of
725  * size PREFIX_LEN.  The flags allowed for this format are
726  * ALLOWED_FLAGS; remove other printf flags from the prefix, then
727  * append SUFFIX.
728  */
729 static void make_format(char *pformat, size_t prefix_len,
730                         char const *allowed_flags, char const *suffix)
731 {
732         char *dst = pformat + 1;
733         char const *src;
734         char const *srclim = pformat + prefix_len;
735
736         for (src = dst; src < srclim && strchr(printf_flags, *src); src++)
737                 if (strchr(allowed_flags, *src))
738                         *dst++ = *src;
739         while (src < srclim)
740                 *dst++ = *src++;
741         strcpy(dst, suffix);
742 }
743
744 static void out_string(char *pformat, size_t prefix_len, char const *arg)
745 {
746         make_format(pformat, prefix_len, "-", "s");
747         printf(pformat, arg);
748 }
749
750 static int out_int(char *pformat, size_t prefix_len, intmax_t arg)
751 {
752         make_format(pformat, prefix_len, "'-+ 0", PRIdMAX);
753         return printf(pformat, arg);
754 }
755
756 static int out_uint(char *pformat, size_t prefix_len, uintmax_t arg)
757 {
758         make_format(pformat, prefix_len, "'-0", PRIuMAX);
759         return printf(pformat, arg);
760 }
761
762 static void out_uint_o(char *pformat, size_t prefix_len, uintmax_t arg)
763 {
764         make_format(pformat, prefix_len, "-#0", PRIoMAX);
765         printf(pformat, arg);
766 }
767
768 static void out_uint_x(char *pformat, size_t prefix_len, uintmax_t arg)
769 {
770         make_format(pformat, prefix_len, "-#0", PRIxMAX);
771         printf(pformat, arg);
772 }
773
774 static int out_minus_zero(char *pformat, size_t prefix_len)
775 {
776         make_format(pformat, prefix_len, "'-+ 0", ".0f");
777         return printf(pformat, -0.25);
778 }
779
780 /* Output the number of seconds since the Epoch, using a format that
781  * acts like printf's %f format.
782  */
783 static void out_epoch_sec(char *pformat, size_t prefix_len,
784                           struct timespec arg)
785 {
786         char *dot = memchr(pformat, '.', prefix_len);
787         size_t sec_prefix_len = prefix_len;
788         int width = 0;
789         int precision = 0;
790         bool frac_left_adjust = false;
791
792         if (dot) {
793                 sec_prefix_len = dot - pformat;
794                 pformat[prefix_len] = '\0';
795
796                 if (ISDIGIT(dot[1])) {
797                         long int lprec = strtol(dot + 1, NULL, 10);
798
799                         precision = (lprec <= INT_MAX ? lprec : INT_MAX);
800                 } else {
801                         precision = 9;
802                 }
803
804                 if (precision && ISDIGIT(dot[-1])) {
805                         /* If a nontrivial width is given, subtract the width
806                          * of the decimal point and PRECISION digits that will
807                          * be output later.
808                          */
809                         char *p = dot;
810
811                         *dot = '\0';
812
813                         do
814                                 --p;
815                         while (ISDIGIT(p[-1]));
816
817                         long int lwidth = strtol(p, NULL, 10);
818
819                         width = (lwidth <= INT_MAX ? lwidth : INT_MAX);
820                         if (width > 1) {
821                                 p += (*p == '0');
822                                 sec_prefix_len = p - pformat;
823
824                                 int w_d = (decimal_point_len < width ?
825                                            width - decimal_point_len : 0);
826
827                                 if (w_d > 1) {
828                                         int w = w_d - precision;
829
830                                         if (w > 1) {
831                                                 char *dst = pformat;
832                                                 char const *src = dst;
833
834                                                 for (; src < p; src++) {
835                                                         if (*src == '-')
836                                                                 frac_left_adjust = true;
837                                                         else
838                                                                 *dst++ = *src;
839                                                 }
840                                                 sec_prefix_len =
841                                                         (dst - pformat
842                         + (frac_left_adjust ? 0 : sprintf(dst, "%d", w)));
843                                         }
844                                 }
845                         }
846                 }
847         }
848
849         int divisor = 1;
850         int i;
851
852         for (i = precision; i < 9; i++)
853                 divisor *= 10;
854
855         int frac_sec = arg.tv_nsec / divisor;
856         int int_len;
857
858
859         if (TYPE_SIGNED(time_t)) {
860                 bool minus_zero = false;
861
862                 if (arg.tv_sec < 0 && arg.tv_nsec != 0) {
863                         int frac_sec_modulus = 1000000000 / divisor;
864
865                         frac_sec = (frac_sec_modulus - frac_sec
866                                     - (arg.tv_nsec % divisor != 0));
867                         arg.tv_sec += (frac_sec != 0);
868                         minus_zero = (arg.tv_sec == 0);
869                 }
870                 int_len = (minus_zero ?
871                            out_minus_zero(pformat, sec_prefix_len) :
872                            out_int(pformat, sec_prefix_len, arg.tv_sec));
873         } else {
874                 int_len = out_uint(pformat, sec_prefix_len, arg.tv_sec);
875         }
876
877         if (precision) {
878                 int prec = (precision < 9 ? precision : 9);
879                 int trailing_prec = precision - prec;
880                 int ilen = (int_len < 0 ? 0 : int_len);
881                 int trailing_width = (ilen < width &&
882                                       decimal_point_len < width - ilen ?
883                                       width - ilen - decimal_point_len - prec :
884                                       0);
885
886                 printf("%s%.*d%-*.*d", decimal_point, prec, frac_sec,
887                         trailing_width, trailing_prec, 0);
888         }
889 }
890
891 /* Print the context information of FILENAME, and return true iff the
892  * context could not be obtained.
893  */
894 static int out_file_context(char *pformat, size_t prefix_len,
895                             char const *filename)
896 {
897         char *scontext = NULL;
898         int rc = 0;
899
900 #ifdef HAVE_SELINUX
901         if ((follow_links ? getfilecon(filename, &scontext) :
902                             lgetfilecon(filename, &scontext)) < 0) {
903                 printf("failed to get security context of %s: %s\n",
904                        filename, strerror(errno));
905                 scontext = NULL;
906                 rc  = -errno;
907         }
908 #endif
909
910         strcpy(pformat + prefix_len, "s");
911         printf(pformat, (scontext ? scontext : "?"));
912 #ifdef HAVE_SELINUX
913         if (scontext)
914                 freecon(scontext);
915 #endif
916         return rc;
917 }
918
919 /* Map a TS with negative TS.tv_nsec to {0,0}.  */
920 static inline struct timespec neg_to_zero(struct timespec ts)
921 {
922         if (ts.tv_nsec >= 0)
923                 return ts;
924
925         struct timespec z = {0, 0};
926
927         return z;
928 }
929
930 /* All the mode bits that can be affected by chmod.  */
931 #define CHMOD_MODE_BITS \
932         (S_ISUID | S_ISGID | S_ISVTX | 0700 | 0070 | 0007)
933
934 /* Print statx info.  Return zero upon success, nonzero upon failure.  */
935 static int print_statx(char *pformat, size_t prefix_len, unsigned int m,
936                        int fd, char const *filename, struct statx const *stx)
937 {
938         struct passwd *pw_ent;
939         struct group *gw_ent;
940         int rc = 0;
941         int ret;
942
943         switch (m) {
944         case 'n':
945                 out_string(pformat, prefix_len,
946                            o_dir_list ? strrchr(filename, '/') + 1 : filename);
947                 break;
948         case 'N':
949                 out_string(pformat, prefix_len,
950                            o_dir_list ? strrchr(filename, '/')  + 1 : filename);
951                 if (S_ISLNK(stx->stx_mode)) {
952                         char *linkname;
953
954                         linkname = areadlink_with_size(filename, stx->stx_size);
955                         if (linkname == NULL) {
956                                 printf("cannot read symbolic link %s: %s",
957                                        filename, strerror(errno));
958                                 return -errno;
959                         }
960                         printf(" -> ");
961                         out_string(pformat, prefix_len, linkname);
962                         free(linkname);
963                 }
964                 break;
965         case 'd':
966                 out_uint(pformat, prefix_len, makedev(stx->stx_dev_major,
967                                                       stx->stx_dev_minor));
968                 break;
969         case 'D':
970                 out_uint_x(pformat, prefix_len, makedev(stx->stx_dev_major,
971                                                         stx->stx_dev_minor));
972                 break;
973         case 'i':
974                 out_uint(pformat, prefix_len, stx->stx_ino);
975                 break;
976         case 'a':
977                 out_uint_o(pformat, prefix_len,
978                            stx->stx_mode & CHMOD_MODE_BITS);
979                 break;
980         case 'A':
981                 out_string(pformat, prefix_len, human_access(stx));
982                 break;
983         case 'f':
984                 out_uint_x(pformat, prefix_len, stx->stx_mode);
985                 break;
986         case 'F':
987                 out_string(pformat, prefix_len, file_type(stx));
988                 break;
989         case 'h':
990                 out_uint(pformat, prefix_len, stx->stx_nlink);
991                 break;
992         case 'u':
993                 out_uint(pformat, prefix_len, stx->stx_uid);
994                 break;
995         case 'U':
996                 pw_ent = getpwuid(stx->stx_uid);
997                 out_string(pformat, prefix_len,
998                            pw_ent ? pw_ent->pw_name : "UNKNOWN");
999                 break;
1000         case 'g':
1001                 out_uint(pformat, prefix_len, stx->stx_gid);
1002                 break;
1003         case 'G':
1004                 gw_ent = getgrgid(stx->stx_gid);
1005                 out_string(pformat, prefix_len,
1006                            gw_ent ? gw_ent->gr_name : "UNKNOWN");
1007                 break;
1008         case 'm':
1009                 /*
1010                  * fail |= out_mount_point(filename, pformat, prefix_len,
1011                  *                         statbuf);
1012                  */
1013                 if (!rc)
1014                         rc = -ENOTSUP;
1015                 break;
1016         case 's':
1017                 out_int(pformat, prefix_len, stx->stx_size);
1018                 break;
1019         case 't':
1020                 out_uint_x(pformat, prefix_len,
1021                            major(makedev(stx->stx_rdev_major,
1022                                          stx->stx_rdev_minor)));
1023                 break;
1024         case 'T':
1025                 out_uint_x(pformat, prefix_len,
1026                            minor(makedev(stx->stx_rdev_major,
1027                                          stx->stx_rdev_minor)));
1028                 break;
1029         case 'B':
1030                 out_uint(pformat, prefix_len, S_BLKSIZE);
1031                 break;
1032         case 'b':
1033                 out_uint(pformat, prefix_len, stx->stx_blocks);
1034                 break;
1035         case 'o':
1036                 out_uint(pformat, prefix_len, stx->stx_blksize);
1037                 break;
1038         case 'p':
1039                 out_uint_x(pformat, prefix_len, stx->stx_attributes_mask);
1040                 break;
1041         case 'r':
1042                 out_uint_x(pformat, prefix_len, stx->stx_attributes);
1043                 break;
1044         case 'w':
1045                 if (stx->stx_btime.tv_nsec < 0)
1046                         out_string(pformat, prefix_len, "-");
1047                 else
1048                         out_string(pformat, prefix_len,
1049                                    human_time(&stx->stx_btime));
1050                 break;
1051         case 'W':
1052                 out_epoch_sec(pformat, prefix_len,
1053                               neg_to_zero(statx_timestamp_to_timespec(
1054                                               stx->stx_btime)));
1055                 break;
1056         case 'x':
1057                 out_string(pformat, prefix_len,
1058                            human_time(&stx->stx_atime));
1059                 break;
1060         case 'X':
1061                 out_epoch_sec(pformat, prefix_len,
1062                               neg_to_zero(statx_timestamp_to_timespec(
1063                                               stx->stx_atime)));
1064                 break;
1065         case 'y':
1066                 out_string(pformat, prefix_len,
1067                            human_time(&stx->stx_mtime));
1068                 break;
1069         case 'Y':
1070                 out_epoch_sec(pformat, prefix_len,
1071                               neg_to_zero(statx_timestamp_to_timespec(
1072                                               stx->stx_mtime)));
1073                 break;
1074         case 'z':
1075                 out_string(pformat, prefix_len,
1076                            human_time(&stx->stx_ctime));
1077                 break;
1078         case 'Z':
1079                 out_epoch_sec(pformat, prefix_len,
1080                               neg_to_zero(statx_timestamp_to_timespec(
1081                                               stx->stx_ctime)));
1082                 break;
1083         case 'C':
1084                 ret = out_file_context(pformat, prefix_len, filename);
1085                 if (!rc && ret)
1086                         rc = ret;
1087                 break;
1088         default:
1089                 fputc('?', stdout);
1090                 break;
1091         }
1092
1093         return rc;
1094 }
1095
1096 static int print_it(int fd, char const *filename,
1097                     int (*print_func)(char *, size_t, unsigned int,
1098                                       int, char const *, struct statx const *),
1099                     void const *data)
1100 {
1101         /* Add 2 to accommodate our conversion of the stat '%s' format string
1102          * to the longer printf '%llu' one.
1103          */
1104         enum {
1105                 MAX_ADDITIONAL_BYTES = (MAX(sizeof(PRIdMAX),
1106                                         MAX(sizeof(PRIoMAX),
1107                                             MAX(sizeof(PRIuMAX),
1108                                                 sizeof(PRIxMAX)))) - 1)
1109         };
1110         size_t n_alloc;
1111         char *dest;
1112         char const *b;
1113         int rc = 0;
1114
1115         if (o_quiet)
1116                 return 0;
1117
1118         n_alloc = strlen(format) + MAX_ADDITIONAL_BYTES + 1;
1119         dest = malloc(n_alloc);
1120         if (dest == NULL)
1121                 return -ENOMEM;
1122
1123         for (b = format; *b; b++) {
1124                 switch (*b) {
1125                 case '%': {
1126                         size_t len = format_code_offset(b);
1127                         char const *fmt_char = b + len;
1128                         int ret;
1129
1130                         memcpy(dest, b, len);
1131                         b += len;
1132
1133                         switch (*fmt_char) {
1134                         case '\0':
1135                                 --b;
1136                         case '%':
1137                                 if (len > 1) {
1138                                         dest[len] = *fmt_char;
1139                                         dest[len + 1] = '\0';
1140                                         printf("%s: invalid directive", dest);
1141                                         return -EINVAL;
1142                                 }
1143                                 putchar('%');
1144                                 break;
1145                         default:
1146                                 ret = print_func(dest, len, to_uchar(*fmt_char),
1147                                                  fd, filename, data);
1148                                 if (rc == 0 && ret)
1149                                         rc = ret;
1150                                 break;
1151                         }
1152                         break;
1153                 }
1154                 case '\\':
1155                         if (!interpret_backslash_escapes) {
1156                                 putchar ('\\');
1157                                 break;
1158                         }
1159                         ++b;
1160                         if (isodigit(*b)) {
1161                                 int esc_value = octtobin(*b);
1162                                 int esc_length = 1; /* number of octal digits */
1163
1164                                 for (++b; esc_length < 3 && isodigit(*b);
1165                                      ++esc_length, ++b) {
1166                                         esc_value = esc_value * 8 +
1167                                                     octtobin(*b);
1168                                 }
1169                                 putchar(esc_value);
1170                                 --b;
1171                         } else if (*b == 'x' && isxdigit(to_uchar(b[1]))) {
1172                                 /* Value of \xhh escape. */
1173                                 int esc_value = hextobin(b[1]);
1174                                 /* A hexadecimal \xhh escape sequence must have
1175                                  * 1 or 2 hex. digits.
1176                                  */
1177
1178                                 ++b;
1179                                 if (isxdigit(to_uchar(b[1]))) {
1180                                         ++b;
1181                                         esc_value = esc_value * 16 +
1182                                                     hextobin(*b);
1183                                 }
1184                                 putchar(esc_value);
1185                         } else if (*b == '\0') {
1186                                 printf("warning: backslash at end of format");
1187                                 putchar('\\');
1188                                 /* Arrange to exit the loop.  */
1189                                 --b;
1190                         } else {
1191                                 print_esc_char(*b);
1192                         }
1193                         break;
1194
1195                 default:
1196                         putchar(*b);
1197                         break;
1198                 }
1199         }
1200         free(dest);
1201
1202         fputs(trailing_delim, stdout);
1203
1204         return rc;
1205 }
1206
1207 /* Return an allocated format string in static storage that
1208  * corresponds to whether FS and TERSE options were declared.
1209  */
1210 static char *default_format(bool fs, bool terse, bool device)
1211 {
1212         char *format;
1213
1214         if (fs) {
1215                 if (terse) {
1216                         format = xstrdup(fmt_terse_fs);
1217                 } else {
1218                         /* TRANSLATORS: This string uses format specifiers from
1219                          * 'stat --help' with --file-system, and NOT from
1220                          * printf.
1221                          */
1222                         format = xstrdup(
1223                         "  File: \"%n\"\n"
1224                         "    ID: %-8i Namelen: %-7l Type: %T\n"
1225                         "Block size: %-10s Fundamental block size: %S\n"
1226                         "Blocks: Total: %-10b Free: %-10f Available: %a\n"
1227                         "Inodes: Total: %-10c Free: %d\n");
1228                 }
1229         } else /* ! fs */ {
1230                 if (terse) {
1231 #ifdef HAVE_SELINUX
1232                         if (is_selinux_enabled() > 0)
1233                                 format = xstrdup(fmt_terse_selinux);
1234                         else
1235 #endif
1236                                 format = xstrdup(fmt_terse_regular);
1237                 } else {
1238                         char *temp;
1239
1240                         /* TRANSLATORS: This string uses format specifiers from
1241                          * 'stat --help' without --file-system, and NOT from
1242                          * printf.
1243                          */
1244                         format = xstrdup("\
1245   File: %N\n\
1246   Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n\
1247 ");
1248
1249                         temp = format;
1250                         if (device) {
1251                                 /* TRANSLATORS: This string uses format
1252                                  * specifiers from 'stat --help' without
1253                                  * --file-system, and NOT from printf.
1254                                  */
1255                                 format = xasprintf("%s%s", format, "\
1256 " "Device: %Dh/%dd\tInode: %-10i  Links: %-5h Device type: %t,%T\n\
1257 ");
1258                         } else {
1259                                 /* TRANSLATORS: This string uses format
1260                                  * specifiers from 'stat --help' without
1261                                  * --file-system, and NOT from printf.
1262                                  */
1263                                 format = xasprintf("%s%s", format, "\
1264 " "Device: %Dh/%dd\tInode: %-10i  Links: %h\n\
1265 ");
1266                         }
1267                         free(temp);
1268
1269                         temp = format;
1270                         /* TRANSLATORS: This string uses format specifiers from
1271                          * 'stat --help' without --file-system, and NOT from
1272                          * printf.
1273                          */
1274                         format = xasprintf("%s%s", format, "\
1275 " "Access: (%04a/%10.10A)  Uid: (%5u/%8U)   Gid: (%5g/%8G)\n\
1276 ");
1277                         free(temp);
1278
1279 #ifdef HAVE_SELINUX
1280                         if (is_selinux_enabled() > 0) {
1281                                 temp = format;
1282                                 /* TRANSLATORS: This string uses format
1283                                  * specifiers from 'stat --help' without
1284                                  * --file-system, and NOT from printf.
1285                                  */
1286                                 format = xasprintf("%s%s", format,
1287                                                    "Context: %C\n");
1288                                 free(temp);
1289                         }
1290 #endif
1291                         temp = format;
1292                         /* TRANSLATORS: This string uses format specifiers from
1293                          * 'stat --help' without --file-system, and NOT from
1294                          * printf.
1295                          */
1296                         format = xasprintf("%s%s", format,
1297                                            "Access: %x\n"
1298                                            "Modify: %y\n"
1299                                            "Change: %z\n"
1300                                            " Birth: %w\n");
1301                         free(temp);
1302                 }
1303         }
1304         return format;
1305 }
1306
1307 static char *list_long_format(void)
1308 {
1309         char *format;
1310
1311         format = xstrdup("\
1312 " "%10.10A %h %8U %8G %-10s %y %N\
1313 ");
1314
1315         return format;
1316 }
1317
1318 static int do_statx(char const *filename, unsigned int request_mask, int flags)
1319 {
1320         const char *pathname = filename;
1321         struct statx stx = { 0, };
1322         int fd;
1323
1324         if (strcmp(filename, "-") == 0)
1325                 fd = 0;
1326         else
1327                 fd = AT_FDCWD;
1328
1329         if (fd != AT_FDCWD) {
1330                 pathname = "";
1331                 flags |= AT_EMPTY_PATH;
1332         }
1333
1334         fd = statx(fd, pathname, flags, request_mask, &stx);
1335         if (fd < 0) {
1336                 if (flags & AT_EMPTY_PATH)
1337                         printf("cannot stat standard input\n");
1338                 else
1339                         printf("cannot statx %s: %s\n",
1340                                filename, strerror(errno));
1341
1342                 return -errno;
1343         }
1344
1345         return print_it(fd, filename, print_statx, &stx);
1346 }
1347
1348 /* Return true if FILE should be ignored. */
1349 static bool file_ignored(char const *name)
1350 {
1351         return name[0] == '.';
1352 }
1353
1354 static int do_dir_list(char const *dirname, unsigned int request_mask,
1355                        int flags)
1356 {
1357         DIR *dir;
1358         struct dirent *ent;
1359         char fullname[PATH_MAX];
1360         size_t size = sizeof(fullname);
1361         int namelen;
1362         int rc = 0;
1363
1364         dir = opendir(dirname);
1365         if (!dir) {
1366                 rc = -errno;
1367                 printf("lsx: cannot open directory '%s': %s\n",
1368                        dirname, strerror(errno));
1369                 return rc;
1370         }
1371
1372         while ((ent = readdir(dir)) != NULL) {
1373                 int ret;
1374
1375                 /* skip "." and ".." */
1376                 if (file_ignored(ent->d_name))
1377                         continue;
1378
1379                 /* ls -1 */
1380                 if (!format) {
1381                         if (o_quiet)
1382                                 continue;
1383
1384                         printf("%s", ent->d_name);
1385                         putchar('\n');
1386                 } else {
1387                         if (strlen(ent->d_name) + strlen(dirname) + 1 >=
1388                             sizeof(fullname)) {
1389                                 errno = ENAMETOOLONG;
1390                                 fprintf(stderr,
1391                                         "lsx: ignored too long path: %s/%s\n",
1392                                         dirname, ent->d_name);
1393                                 if (!rc)
1394                                         rc = -ENAMETOOLONG;
1395                                 continue;
1396                         }
1397                         namelen = snprintf(fullname, size, "%s/%s",
1398                                            dirname, ent->d_name);
1399                         if (namelen >= size)
1400                                 fullname[size - 1] = '\0';
1401
1402                         ret = do_statx(fullname, request_mask, flags);
1403                         if (!ret)
1404                                 putchar('\n');
1405                         else if (rc == 0)
1406                                 rc = ret;
1407                 }
1408         }
1409
1410         closedir(dir);
1411         return rc;
1412 }
1413
1414 int main(int argc, char **argv)
1415 {
1416         static struct option options[] = {
1417                 {"dereference", no_argument, NULL, 'L'},
1418                 {"format", required_argument, NULL, 'c'},
1419                 {"printf", required_argument, NULL, PRINTF_OPTION},
1420                 {"terse", no_argument, NULL, 't'},
1421                 {"cached", required_argument, NULL, 0},
1422                 {"dir", no_argument, NULL, 'D'},
1423                 {"long-format", no_argument, NULL, 'l'},
1424                 {"quiet", no_argument, NULL, 'q'},
1425                 {"help", no_argument, NULL, GETOPT_HELP_CHAR},
1426                 {"version", no_argument, NULL, GETOPT_VERSION_CHAR},
1427                 {NULL, 0, NULL, 0}
1428         };
1429         bool terse = false;
1430         unsigned int request_mask;
1431         int flags = AT_SYMLINK_NOFOLLOW;
1432         struct lconv const *locale = localeconv();
1433         int c;
1434         int rc = 0;
1435         int i = 0;
1436
1437         decimal_point = locale->decimal_point[0] ? locale->decimal_point : ".";
1438         decimal_point_len = strlen(decimal_point);
1439         current_time.tv_sec = TYPE_MINIMUM(time_t);
1440         current_time.tv_nsec = -1;
1441
1442         while ((c = getopt_long(argc, argv, "c:DqLlt", options, NULL)) != EOF) {
1443                 switch (c) {
1444                 case 'L':
1445                         flags &= ~AT_SYMLINK_NOFOLLOW;
1446                         follow_links = true;
1447                         break;
1448                 case PRINTF_OPTION:
1449                         format = optarg;
1450                         interpret_backslash_escapes = true;
1451                         trailing_delim = "";
1452                         break;
1453                 case 'c':
1454                         format = optarg;
1455                         interpret_backslash_escapes = false;
1456                         trailing_delim = "\n";
1457                         break;
1458                 case 'q':
1459                         o_quiet = true;
1460                         break;
1461                 case 'l':
1462                         o_dir_list = true;
1463                         long_format = true;
1464                         break;
1465                 case 'D':
1466                         o_dir_list = true;
1467                         break;
1468                 case 't':
1469                         terse = true;
1470                         break;
1471                 case 0:
1472                         if (strcmp(optarg, "never") == 0) {
1473                                 flags &= ~AT_STATX_SYNC_TYPE;
1474                                 flags |= AT_STATX_FORCE_SYNC;
1475                         } else if (strcmp(optarg, "always") == 0) {
1476                                 flags &= ~AT_STATX_SYNC_TYPE;
1477                                 flags |= AT_STATX_DONT_SYNC;
1478                         } else if (strcmp(optarg, "default") == 0) {
1479                                 flags &= ~AT_STATX_SYNC_TYPE;
1480                                 flags |= AT_SYMLINK_NOFOLLOW;
1481                         } else {
1482                                 printf("%s: invalid cached mode: %s\n",
1483                                        argv[0], optarg);
1484                                 return -EINVAL;
1485                         }
1486                         break;
1487                 case GETOPT_HELP_CHAR:
1488                         usage(argv[0]);
1489                 case GETOPT_VERSION_CHAR:
1490                         if (!o_quiet)
1491                                 printf("Lustre statx: version 0.1\n");
1492                         return 0;
1493                 default:
1494                         printf("%s: unknown option '-%c'\n",
1495                                argv[0], optopt);
1496                         return -EINVAL;
1497                 }
1498         }
1499
1500         if (format) {
1501                 request_mask = format_to_mask(format);
1502         } else {
1503                 request_mask = STATX_ALL;
1504                 if (o_dir_list)
1505                         format = long_format ? list_long_format() : NULL;
1506                 else
1507                         format = default_format(false, terse, false);
1508         }
1509
1510         if (optind == argc) {
1511                 if (o_dir_list)
1512                         return do_dir_list(".", request_mask, flags);
1513
1514                 printf("statx: missing operand\n"
1515                        "Try 'stat --help' for more information.\n");
1516                 return 0;
1517         }
1518
1519         for (i = optind; i < argc; i++) {
1520                 int ret;
1521
1522                 if (o_dir_list)
1523                         ret = do_dir_list(argv[i], request_mask, flags);
1524                 else
1525                         ret = do_statx(argv[i], request_mask, flags);
1526
1527                 if (rc == 0 && ret)
1528                         rc = ret;
1529         }
1530
1531         return rc;
1532 }
1533 #else
1534 int main(int argc, char **argv)
1535 {
1536         static struct option options[] = {
1537                 {"version", no_argument, NULL, GETOPT_VERSION_CHAR},
1538                 {"quiet", no_argument, NULL, 'q'},
1539                 {NULL, 0, NULL, 0}
1540         };
1541         int c;
1542
1543         while ((c = getopt_long(argc, argv, "q", options, NULL)) != EOF) {
1544                 switch (c) {
1545                 case 'q':
1546                         o_quiet = true;
1547                         break;
1548                 case GETOPT_VERSION_CHAR:
1549                         if (!o_quiet)
1550                                 printf("statx: Not support statx() syscall\n");
1551                         return -ENOTSUP;
1552                 }
1553         }
1554         printf("Skip: system does not support statx syscall.\n");
1555         return 0;
1556 }
1557 #endif /* __NR_statx */