libbb: introduce and use strftime_[YYYYMMDD]HHMMSS()
[platform/upstream/busybox.git] / coreutils / stat.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * stat -- display file or file system status
4  *
5  * Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation.
6  * Copyright (C) 2005 by Erik Andersen <andersen@codepoet.org>
7  * Copyright (C) 2005 by Mike Frysinger <vapier@gentoo.org>
8  * Copyright (C) 2006 by Yoshinori Sato <ysato@users.sourceforge.jp>
9  *
10  * Written by Michael Meskes
11  * Taken from coreutils and turned into a busybox applet by Mike Frysinger
12  *
13  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
14  */
15
16 //usage:#define stat_trivial_usage
17 //usage:       "[OPTIONS] FILE..."
18 //usage:#define stat_full_usage "\n\n"
19 //usage:       "Display file (default) or filesystem status\n"
20 //usage:        IF_FEATURE_STAT_FORMAT(
21 //usage:     "\n        -c fmt  Use the specified format"
22 //usage:        )
23 //usage:     "\n        -f      Display filesystem status"
24 //usage:     "\n        -L      Follow links"
25 //usage:     "\n        -t      Display info in terse form"
26 //usage:        IF_SELINUX(
27 //usage:     "\n        -Z      Print security context"
28 //usage:        )
29 //usage:        IF_FEATURE_STAT_FORMAT(
30 //usage:       "\n\nValid format sequences for files:\n"
31 //usage:       " %a     Access rights in octal\n"
32 //usage:       " %A     Access rights in human readable form\n"
33 //usage:       " %b     Number of blocks allocated (see %B)\n"
34 //usage:       " %B     The size in bytes of each block reported by %b\n"
35 //usage:       " %d     Device number in decimal\n"
36 //usage:       " %D     Device number in hex\n"
37 //usage:       " %f     Raw mode in hex\n"
38 //usage:       " %F     File type\n"
39 //usage:       " %g     Group ID of owner\n"
40 //usage:       " %G     Group name of owner\n"
41 //usage:       " %h     Number of hard links\n"
42 //usage:       " %i     Inode number\n"
43 //usage:       " %n     File name\n"
44 //usage:       " %N     File name, with -> TARGET if symlink\n"
45 //usage:       " %o     I/O block size\n"
46 //usage:       " %s     Total size, in bytes\n"
47 //usage:       " %t     Major device type in hex\n"
48 //usage:       " %T     Minor device type in hex\n"
49 //usage:       " %u     User ID of owner\n"
50 //usage:       " %U     User name of owner\n"
51 //usage:       " %x     Time of last access\n"
52 //usage:       " %X     Time of last access as seconds since Epoch\n"
53 //usage:       " %y     Time of last modification\n"
54 //usage:       " %Y     Time of last modification as seconds since Epoch\n"
55 //usage:       " %z     Time of last change\n"
56 //usage:       " %Z     Time of last change as seconds since Epoch\n"
57 //usage:       "\nValid format sequences for file systems:\n"
58 //usage:       " %a     Free blocks available to non-superuser\n"
59 //usage:       " %b     Total data blocks in file system\n"
60 //usage:       " %c     Total file nodes in file system\n"
61 //usage:       " %d     Free file nodes in file system\n"
62 //usage:       " %f     Free blocks in file system\n"
63 //usage:        IF_SELINUX(
64 //usage:       " %C     Security context in selinux\n"
65 //usage:        )
66 //usage:       " %i     File System ID in hex\n"
67 //usage:       " %l     Maximum length of filenames\n"
68 //usage:       " %n     File name\n"
69 //usage:       " %s     Block size (for faster transfer)\n"
70 //usage:       " %S     Fundamental block size (for block counts)\n"
71 //usage:       " %t     Type in hex\n"
72 //usage:       " %T     Type in human readable form"
73 //usage:        )
74
75 #include "libbb.h"
76
77 #define OPT_FILESYS     (1 << 0)
78 #define OPT_TERSE       (1 << 1)
79 #define OPT_DEREFERENCE (1 << 2)
80 #define OPT_SELINUX     (1 << 3)
81
82 #if ENABLE_FEATURE_STAT_FORMAT
83 typedef bool (*statfunc_ptr)(const char *, const char *);
84 #else
85 typedef bool (*statfunc_ptr)(const char *);
86 #endif
87
88 static const char *file_type(const struct stat *st)
89 {
90         /* See POSIX 1003.1-2001 XCU Table 4-8 lines 17093-17107
91          * for some of these formats.
92          * To keep diagnostics grammatical in English, the
93          * returned string must start with a consonant.
94          */
95         if (S_ISREG(st->st_mode))  return st->st_size == 0 ? "regular empty file" : "regular file";
96         if (S_ISDIR(st->st_mode))  return "directory";
97         if (S_ISBLK(st->st_mode))  return "block special file";
98         if (S_ISCHR(st->st_mode))  return "character special file";
99         if (S_ISFIFO(st->st_mode)) return "fifo";
100         if (S_ISLNK(st->st_mode))  return "symbolic link";
101         if (S_ISSOCK(st->st_mode)) return "socket";
102 #ifdef S_TYPEISMQ
103         if (S_TYPEISMQ(st))        return "message queue";
104 #endif
105 #ifdef S_TYPEISSEM
106         if (S_TYPEISSEM(st))       return "semaphore";
107 #endif
108 #ifdef S_TYPEISSHM
109         if (S_TYPEISSHM(st))       return "shared memory object";
110 #endif
111 #ifdef S_TYPEISTMO
112         if (S_TYPEISTMO(st))       return "typed memory object";
113 #endif
114         return "weird file";
115 }
116
117 static const char *human_time(time_t t)
118 {
119         /* Old
120         static char *str;
121         str = ctime(&t);
122         str[strlen(str)-1] = '\0';
123         return str;
124         */
125         /* coreutils 6.3 compat: */
126
127         /*static char buf[sizeof("YYYY-MM-DD HH:MM:SS.000000000")] ALIGN1;*/
128 #define buf bb_common_bufsiz1
129
130         strcpy(strftime_YYYYMMDDHHMMSS(buf, sizeof(buf), &t), ".000000000");
131         return buf;
132 #undef buf
133 }
134
135 /* Return the type of the specified file system.
136  * Some systems have statfvs.f_basetype[FSTYPSZ]. (AIX, HP-UX, and Solaris)
137  * Others have statfs.f_fstypename[MFSNAMELEN]. (NetBSD 1.5.2)
138  * Still others have neither and have to get by with f_type (Linux).
139  */
140 static const char *human_fstype(uint32_t f_type)
141 {
142         static const struct types {
143                 uint32_t type;
144                 const char *const fs;
145         } humantypes[] = {
146                 { 0xADFF,     "affs" },
147                 { 0x1Cd1,     "devpts" },
148                 { 0x137D,     "ext" },
149                 { 0xEF51,     "ext2" },
150                 { 0xEF53,     "ext2/ext3" },
151                 { 0x3153464a, "jfs" },
152                 { 0x58465342, "xfs" },
153                 { 0xF995E849, "hpfs" },
154                 { 0x9660,     "isofs" },
155                 { 0x4000,     "isofs" },
156                 { 0x4004,     "isofs" },
157                 { 0x137F,     "minix" },
158                 { 0x138F,     "minix (30 char.)" },
159                 { 0x2468,     "minix v2" },
160                 { 0x2478,     "minix v2 (30 char.)" },
161                 { 0x4d44,     "msdos" },
162                 { 0x4006,     "fat" },
163                 { 0x564c,     "novell" },
164                 { 0x6969,     "nfs" },
165                 { 0x9fa0,     "proc" },
166                 { 0x517B,     "smb" },
167                 { 0x012FF7B4, "xenix" },
168                 { 0x012FF7B5, "sysv4" },
169                 { 0x012FF7B6, "sysv2" },
170                 { 0x012FF7B7, "coh" },
171                 { 0x00011954, "ufs" },
172                 { 0x012FD16D, "xia" },
173                 { 0x5346544e, "ntfs" },
174                 { 0x1021994,  "tmpfs" },
175                 { 0x52654973, "reiserfs" },
176                 { 0x28cd3d45, "cramfs" },
177                 { 0x7275,     "romfs" },
178                 { 0x858458f6, "romfs" },
179                 { 0x73717368, "squashfs" },
180                 { 0x62656572, "sysfs" },
181                 { 0, "UNKNOWN" }
182         };
183
184         int i;
185
186         for (i = 0; humantypes[i].type; ++i)
187                 if (humantypes[i].type == f_type)
188                         break;
189         return humantypes[i].fs;
190 }
191
192 /* "man statfs" says that statfsbuf->f_fsid is a mess */
193 /* coreutils treats it as an array of ints, most significant first */
194 static unsigned long long get_f_fsid(const struct statfs *statfsbuf)
195 {
196         const unsigned *p = (const void*) &statfsbuf->f_fsid;
197         unsigned sz = sizeof(statfsbuf->f_fsid) / sizeof(unsigned);
198         unsigned long long r = 0;
199
200         do
201                 r = (r << (sizeof(unsigned)*8)) | *p++;
202         while (--sz > 0);
203         return r;
204 }
205
206 #if ENABLE_FEATURE_STAT_FORMAT
207 static void strcatc(char *str, char c)
208 {
209         int len = strlen(str);
210         str[len++] = c;
211         str[len] = '\0';
212 }
213
214 static void printfs(char *pformat, const char *msg)
215 {
216         strcatc(pformat, 's');
217         printf(pformat, msg);
218 }
219
220 /* print statfs info */
221 static void FAST_FUNC print_statfs(char *pformat, const char m,
222                 const char *const filename, const void *data
223                 IF_SELINUX(, security_context_t scontext))
224 {
225         const struct statfs *statfsbuf = data;
226         if (m == 'n') {
227                 printfs(pformat, filename);
228         } else if (m == 'i') {
229                 strcat(pformat, "llx");
230                 printf(pformat, get_f_fsid(statfsbuf));
231         } else if (m == 'l') {
232                 strcat(pformat, "lu");
233                 printf(pformat, (unsigned long) statfsbuf->f_namelen);
234         } else if (m == 't') {
235                 strcat(pformat, "lx");
236                 printf(pformat, (unsigned long) statfsbuf->f_type); /* no equiv */
237         } else if (m == 'T') {
238                 printfs(pformat, human_fstype(statfsbuf->f_type));
239         } else if (m == 'b') {
240                 strcat(pformat, "llu");
241                 printf(pformat, (unsigned long long) statfsbuf->f_blocks);
242         } else if (m == 'f') {
243                 strcat(pformat, "llu");
244                 printf(pformat, (unsigned long long) statfsbuf->f_bfree);
245         } else if (m == 'a') {
246                 strcat(pformat, "llu");
247                 printf(pformat, (unsigned long long) statfsbuf->f_bavail);
248         } else if (m == 's' || m == 'S') {
249                 strcat(pformat, "lu");
250                 printf(pformat, (unsigned long) statfsbuf->f_bsize);
251         } else if (m == 'c') {
252                 strcat(pformat, "llu");
253                 printf(pformat, (unsigned long long) statfsbuf->f_files);
254         } else if (m == 'd') {
255                 strcat(pformat, "llu");
256                 printf(pformat, (unsigned long long) statfsbuf->f_ffree);
257 # if ENABLE_SELINUX
258         } else if (m == 'C' && (option_mask32 & OPT_SELINUX)) {
259                 printfs(pformat, scontext);
260 # endif
261         } else {
262                 strcatc(pformat, 'c');
263                 printf(pformat, m);
264         }
265 }
266
267 /* print stat info */
268 static void FAST_FUNC print_stat(char *pformat, const char m,
269                 const char *const filename, const void *data
270                 IF_SELINUX(, security_context_t scontext))
271 {
272 #define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
273         struct stat *statbuf = (struct stat *) data;
274         struct passwd *pw_ent;
275         struct group *gw_ent;
276
277         if (m == 'n') {
278                 printfs(pformat, filename);
279         } else if (m == 'N') {
280                 strcatc(pformat, 's');
281                 if (S_ISLNK(statbuf->st_mode)) {
282                         char *linkname = xmalloc_readlink_or_warn(filename);
283                         if (linkname == NULL)
284                                 return;
285                         printf("'%s' -> '%s'", filename, linkname);
286                         free(linkname);
287                 } else {
288                         printf(pformat, filename);
289                 }
290         } else if (m == 'd') {
291                 strcat(pformat, "llu");
292                 printf(pformat, (unsigned long long) statbuf->st_dev);
293         } else if (m == 'D') {
294                 strcat(pformat, "llx");
295                 printf(pformat, (unsigned long long) statbuf->st_dev);
296         } else if (m == 'i') {
297                 strcat(pformat, "llu");
298                 printf(pformat, (unsigned long long) statbuf->st_ino);
299         } else if (m == 'a') {
300                 strcat(pformat, "lo");
301                 printf(pformat, (unsigned long) (statbuf->st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)));
302         } else if (m == 'A') {
303                 printfs(pformat, bb_mode_string(statbuf->st_mode));
304         } else if (m == 'f') {
305                 strcat(pformat, "lx");
306                 printf(pformat, (unsigned long) statbuf->st_mode);
307         } else if (m == 'F') {
308                 printfs(pformat, file_type(statbuf));
309         } else if (m == 'h') {
310                 strcat(pformat, "lu");
311                 printf(pformat, (unsigned long) statbuf->st_nlink);
312         } else if (m == 'u') {
313                 strcat(pformat, "lu");
314                 printf(pformat, (unsigned long) statbuf->st_uid);
315         } else if (m == 'U') {
316                 pw_ent = getpwuid(statbuf->st_uid);
317                 printfs(pformat, (pw_ent != NULL) ? pw_ent->pw_name : "UNKNOWN");
318         } else if (m == 'g') {
319                 strcat(pformat, "lu");
320                 printf(pformat, (unsigned long) statbuf->st_gid);
321         } else if (m == 'G') {
322                 gw_ent = getgrgid(statbuf->st_gid);
323                 printfs(pformat, (gw_ent != NULL) ? gw_ent->gr_name : "UNKNOWN");
324         } else if (m == 't') {
325                 strcat(pformat, "lx");
326                 printf(pformat, (unsigned long) major(statbuf->st_rdev));
327         } else if (m == 'T') {
328                 strcat(pformat, "lx");
329                 printf(pformat, (unsigned long) minor(statbuf->st_rdev));
330         } else if (m == 's') {
331                 strcat(pformat, "llu");
332                 printf(pformat, (unsigned long long) statbuf->st_size);
333         } else if (m == 'B') {
334                 strcat(pformat, "lu");
335                 printf(pformat, (unsigned long) 512); //ST_NBLOCKSIZE
336         } else if (m == 'b') {
337                 strcat(pformat, "llu");
338                 printf(pformat, (unsigned long long) statbuf->st_blocks);
339         } else if (m == 'o') {
340                 strcat(pformat, "lu");
341                 printf(pformat, (unsigned long) statbuf->st_blksize);
342         } else if (m == 'x') {
343                 printfs(pformat, human_time(statbuf->st_atime));
344         } else if (m == 'X') {
345                 strcat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu");
346                 /* note: (unsigned long) would be wrong:
347                  * imagine (unsigned long64)int32 */
348                 printf(pformat, (long) statbuf->st_atime);
349         } else if (m == 'y') {
350                 printfs(pformat, human_time(statbuf->st_mtime));
351         } else if (m == 'Y') {
352                 strcat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu");
353                 printf(pformat, (long) statbuf->st_mtime);
354         } else if (m == 'z') {
355                 printfs(pformat, human_time(statbuf->st_ctime));
356         } else if (m == 'Z') {
357                 strcat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu");
358                 printf(pformat, (long) statbuf->st_ctime);
359 # if ENABLE_SELINUX
360         } else if (m == 'C' && (option_mask32 & OPT_SELINUX)) {
361                 printfs(pformat, scontext);
362 # endif
363         } else {
364                 strcatc(pformat, 'c');
365                 printf(pformat, m);
366         }
367 }
368
369 static void print_it(const char *masterformat,
370                 const char *filename,
371                 void FAST_FUNC (*print_func)(char*, char, const char*, const void* IF_SELINUX(, security_context_t scontext)),
372                 const void *data
373                 IF_SELINUX(, security_context_t scontext))
374 {
375         /* Create a working copy of the format string */
376         char *format = xstrdup(masterformat);
377         /* Add 2 to accomodate our conversion of the stat '%s' format string
378          * to the printf '%llu' one.  */
379         char *dest = xmalloc(strlen(format) + 2 + 1);
380         char *b;
381
382         b = format;
383         while (b) {
384                 /* Each iteration finds next %spec,
385                  * prints preceding string and handles found %spec
386                  */
387                 size_t len;
388                 char *p = strchr(b, '%');
389                 if (!p) {
390                         /* coreutils 6.3 always prints newline at the end */
391                         /*fputs(b, stdout);*/
392                         puts(b);
393                         break;
394                 }
395
396                 /* dest = "%<modifiers>" */
397                 len = 1 + strspn(p + 1, "#-+.I 0123456789");
398                 memcpy(dest, p, len);
399                 dest[len] = '\0';
400
401                 /* print preceding string */
402                 *p = '\0';
403                 fputs(b, stdout);
404
405                 p += len;
406                 b = p + 1;
407                 switch (*p) {
408                 case '\0':
409                         b = NULL;
410                         /* fall through */
411                 case '%':
412                         bb_putchar('%');
413                         break;
414                 default:
415                         /* Completes "%<modifiers>" with specifier and printfs */
416                         print_func(dest, *p, filename, data IF_SELINUX(,scontext));
417                         break;
418                 }
419         }
420
421         free(format);
422         free(dest);
423 }
424 #endif  /* FEATURE_STAT_FORMAT */
425
426 /* Stat the file system and print what we find.  */
427 #if !ENABLE_FEATURE_STAT_FORMAT
428 #define do_statfs(filename, format) do_statfs(filename)
429 #endif
430 static bool do_statfs(const char *filename, const char *format)
431 {
432         struct statfs statfsbuf;
433 #if !ENABLE_FEATURE_STAT_FORMAT
434         const char *format;
435 #endif
436 #if ENABLE_SELINUX
437         security_context_t scontext = NULL;
438
439         if (option_mask32 & OPT_SELINUX) {
440                 if ((option_mask32 & OPT_DEREFERENCE
441                      ? lgetfilecon(filename, &scontext)
442                      : getfilecon(filename, &scontext)
443                     ) < 0
444                 ) {
445                         bb_simple_perror_msg(filename);
446                         return 0;
447                 }
448         }
449 #endif
450         if (statfs(filename, &statfsbuf) != 0) {
451                 bb_perror_msg("can't read file system information for '%s'", filename);
452                 return 0;
453         }
454
455 #if ENABLE_FEATURE_STAT_FORMAT
456         if (format == NULL) {
457 # if !ENABLE_SELINUX
458                 format = (option_mask32 & OPT_TERSE
459                         ? "%n %i %l %t %s %b %f %a %c %d\n"
460                         : "  File: \"%n\"\n"
461                           "    ID: %-8i Namelen: %-7l Type: %T\n"
462                           "Block size: %-10s\n"
463                           "Blocks: Total: %-10b Free: %-10f Available: %a\n"
464                           "Inodes: Total: %-10c Free: %d");
465 # else
466                 format = (option_mask32 & OPT_TERSE
467                         ? (option_mask32 & OPT_SELINUX ? "%n %i %l %t %s %b %f %a %c %d %C\n":
468                         "%n %i %l %t %s %b %f %a %c %d\n")
469                         : (option_mask32 & OPT_SELINUX ?
470                         "  File: \"%n\"\n"
471                         "    ID: %-8i Namelen: %-7l Type: %T\n"
472                         "Block size: %-10s\n"
473                         "Blocks: Total: %-10b Free: %-10f Available: %a\n"
474                         "Inodes: Total: %-10c Free: %d"
475                         "  S_context: %C\n":
476                         "  File: \"%n\"\n"
477                         "    ID: %-8i Namelen: %-7l Type: %T\n"
478                         "Block size: %-10s\n"
479                         "Blocks: Total: %-10b Free: %-10f Available: %a\n"
480                         "Inodes: Total: %-10c Free: %d\n")
481                         );
482 # endif /* SELINUX */
483         }
484         print_it(format, filename, print_statfs, &statfsbuf IF_SELINUX(, scontext));
485 #else /* FEATURE_STAT_FORMAT */
486         format = (option_mask32 & OPT_TERSE
487                 ? "%s %llx %lu "
488                 : "  File: \"%s\"\n"
489                   "    ID: %-8llx Namelen: %-7lu ");
490         printf(format,
491                filename,
492                get_f_fsid(&statfsbuf),
493                statfsbuf.f_namelen);
494
495         if (option_mask32 & OPT_TERSE)
496                 printf("%lx ", (unsigned long) statfsbuf.f_type);
497         else
498                 printf("Type: %s\n", human_fstype(statfsbuf.f_type));
499
500 # if !ENABLE_SELINUX
501         format = (option_mask32 & OPT_TERSE
502                 ? "%lu %llu %llu %llu %llu %llu\n"
503                 : "Block size: %-10lu\n"
504                   "Blocks: Total: %-10llu Free: %-10llu Available: %llu\n"
505                   "Inodes: Total: %-10llu Free: %llu\n");
506         printf(format,
507                (unsigned long) statfsbuf.f_bsize,
508                (unsigned long long) statfsbuf.f_blocks,
509                (unsigned long long) statfsbuf.f_bfree,
510                (unsigned long long) statfsbuf.f_bavail,
511                (unsigned long long) statfsbuf.f_files,
512                (unsigned long long) statfsbuf.f_ffree);
513 # else
514         format = (option_mask32 & OPT_TERSE
515                 ? (option_mask32 & OPT_SELINUX ? "%lu %llu %llu %llu %llu %llu %C\n" : "%lu %llu %llu %llu %llu %llu\n")
516                 : (option_mask32 & OPT_SELINUX
517                         ?       "Block size: %-10lu\n"
518                                 "Blocks: Total: %-10llu Free: %-10llu Available: %llu\n"
519                                 "Inodes: Total: %-10llu Free: %llu"
520                                 "S_context: %C\n"
521                         :       "Block size: %-10lu\n"
522                                 "Blocks: Total: %-10llu Free: %-10llu Available: %llu\n"
523                                 "Inodes: Total: %-10llu Free: %llu\n"
524                         )
525                 );
526         printf(format,
527                 (unsigned long) statfsbuf.f_bsize,
528                 (unsigned long long) statfsbuf.f_blocks,
529                 (unsigned long long) statfsbuf.f_bfree,
530                 (unsigned long long) statfsbuf.f_bavail,
531                 (unsigned long long) statfsbuf.f_files,
532                 (unsigned long long) statfsbuf.f_ffree,
533                 scontext);
534
535         if (scontext)
536                 freecon(scontext);
537 # endif
538 #endif  /* FEATURE_STAT_FORMAT */
539         return 1;
540 }
541
542 /* stat the file and print what we find */
543 #if !ENABLE_FEATURE_STAT_FORMAT
544 #define do_stat(filename, format) do_stat(filename)
545 #endif
546 static bool do_stat(const char *filename, const char *format)
547 {
548         struct stat statbuf;
549 #if ENABLE_SELINUX
550         security_context_t scontext = NULL;
551
552         if (option_mask32 & OPT_SELINUX) {
553                 if ((option_mask32 & OPT_DEREFERENCE
554                      ? lgetfilecon(filename, &scontext)
555                      : getfilecon(filename, &scontext)
556                     ) < 0
557                 ) {
558                         bb_simple_perror_msg(filename);
559                         return 0;
560                 }
561         }
562 #endif
563         if ((option_mask32 & OPT_DEREFERENCE ? stat : lstat) (filename, &statbuf) != 0) {
564                 bb_perror_msg("can't stat '%s'", filename);
565                 return 0;
566         }
567
568 #if ENABLE_FEATURE_STAT_FORMAT
569         if (format == NULL) {
570 # if !ENABLE_SELINUX
571                 if (option_mask32 & OPT_TERSE) {
572                         format = "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o";
573                 } else {
574                         if (S_ISBLK(statbuf.st_mode) || S_ISCHR(statbuf.st_mode)) {
575                                 format =
576                                         "  File: %N\n"
577                                         "  Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
578                                         "Device: %Dh/%dd\tInode: %-10i  Links: %-5h"
579                                         " Device type: %t,%T\n"
580                                         "Access: (%04a/%10.10A)  Uid: (%5u/%8U)   Gid: (%5g/%8G)\n"
581                                         "Access: %x\n" "Modify: %y\n" "Change: %z\n";
582                         } else {
583                                 format =
584                                         "  File: %N\n"
585                                         "  Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
586                                         "Device: %Dh/%dd\tInode: %-10i  Links: %h\n"
587                                         "Access: (%04a/%10.10A)  Uid: (%5u/%8U)   Gid: (%5g/%8G)\n"
588                                         "Access: %x\n" "Modify: %y\n" "Change: %z\n";
589                         }
590                 }
591 # else
592                 if (option_mask32 & OPT_TERSE) {
593                         format = (option_mask32 & OPT_SELINUX ?
594                                 "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o %C\n"
595                                 :
596                                 "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\n"
597                                 );
598                 } else {
599                         if (S_ISBLK(statbuf.st_mode) || S_ISCHR(statbuf.st_mode)) {
600                                 format = (option_mask32 & OPT_SELINUX ?
601                                         "  File: %N\n"
602                                         "  Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
603                                         "Device: %Dh/%dd\tInode: %-10i  Links: %-5h"
604                                         " Device type: %t,%T\n"
605                                         "Access: (%04a/%10.10A)  Uid: (%5u/%8U)   Gid: (%5g/%8G)\n"
606                                         "   S_Context: %C\n"
607                                         "Access: %x\n" "Modify: %y\n" "Change: %z\n"
608                                         :
609                                         "  File: %N\n"
610                                         "  Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
611                                         "Device: %Dh/%dd\tInode: %-10i  Links: %-5h"
612                                         " Device type: %t,%T\n"
613                                         "Access: (%04a/%10.10A)  Uid: (%5u/%8U)   Gid: (%5g/%8G)\n"
614                                         "Access: %x\n" "Modify: %y\n" "Change: %z\n"
615                                         );
616                         } else {
617                                 format = (option_mask32 & OPT_SELINUX ?
618                                         "  File: %N\n"
619                                         "  Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
620                                         "Device: %Dh/%dd\tInode: %-10i  Links: %h\n"
621                                         "Access: (%04a/%10.10A)  Uid: (%5u/%8U)   Gid: (%5g/%8G)\n"
622                                         "S_Context: %C\n"
623                                         "Access: %x\n" "Modify: %y\n" "Change: %z\n"
624                                         :
625                                         "  File: %N\n"
626                                         "  Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
627                                         "Device: %Dh/%dd\tInode: %-10i  Links: %h\n"
628                                         "Access: (%04a/%10.10A)  Uid: (%5u/%8U)   Gid: (%5g/%8G)\n"
629                                         "Access: %x\n" "Modify: %y\n" "Change: %z\n"
630                                         );
631                         }
632                 }
633 # endif
634         }
635         print_it(format, filename, print_stat, &statbuf IF_SELINUX(, scontext));
636 #else   /* FEATURE_STAT_FORMAT */
637         if (option_mask32 & OPT_TERSE) {
638                 printf("%s %llu %llu %lx %lu %lu %llx %llu %lu %lx %lx %lu %lu %lu %lu"
639                        IF_NOT_SELINUX("\n"),
640                        filename,
641                        (unsigned long long) statbuf.st_size,
642                        (unsigned long long) statbuf.st_blocks,
643                        (unsigned long) statbuf.st_mode,
644                        (unsigned long) statbuf.st_uid,
645                        (unsigned long) statbuf.st_gid,
646                        (unsigned long long) statbuf.st_dev,
647                        (unsigned long long) statbuf.st_ino,
648                        (unsigned long) statbuf.st_nlink,
649                        (unsigned long) major(statbuf.st_rdev),
650                        (unsigned long) minor(statbuf.st_rdev),
651                        (unsigned long) statbuf.st_atime,
652                        (unsigned long) statbuf.st_mtime,
653                        (unsigned long) statbuf.st_ctime,
654                        (unsigned long) statbuf.st_blksize
655                 );
656 # if ENABLE_SELINUX
657                 if (option_mask32 & OPT_SELINUX)
658                         printf(" %lc\n", *scontext);
659                 else
660                         bb_putchar('\n');
661 # endif
662         } else {
663                 char *linkname = NULL;
664                 struct passwd *pw_ent;
665                 struct group *gw_ent;
666
667                 gw_ent = getgrgid(statbuf.st_gid);
668                 pw_ent = getpwuid(statbuf.st_uid);
669
670                 if (S_ISLNK(statbuf.st_mode))
671                         linkname = xmalloc_readlink_or_warn(filename);
672                 if (linkname) {
673                         printf("  File: '%s' -> '%s'\n", filename, linkname);
674                         free(linkname);
675                 } else {
676                         printf("  File: '%s'\n", filename);
677                 }
678
679                 printf("  Size: %-10llu\tBlocks: %-10llu IO Block: %-6lu %s\n"
680                        "Device: %llxh/%llud\tInode: %-10llu  Links: %-5lu",
681                        (unsigned long long) statbuf.st_size,
682                        (unsigned long long) statbuf.st_blocks,
683                        (unsigned long) statbuf.st_blksize,
684                        file_type(&statbuf),
685                        (unsigned long long) statbuf.st_dev,
686                        (unsigned long long) statbuf.st_dev,
687                        (unsigned long long) statbuf.st_ino,
688                        (unsigned long) statbuf.st_nlink);
689                 if (S_ISBLK(statbuf.st_mode) || S_ISCHR(statbuf.st_mode))
690                         printf(" Device type: %lx,%lx\n",
691                                (unsigned long) major(statbuf.st_rdev),
692                                (unsigned long) minor(statbuf.st_rdev));
693                 else
694                         bb_putchar('\n');
695                 printf("Access: (%04lo/%10.10s)  Uid: (%5lu/%8s)   Gid: (%5lu/%8s)\n",
696                        (unsigned long) (statbuf.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)),
697                        bb_mode_string(statbuf.st_mode),
698                        (unsigned long) statbuf.st_uid,
699                        (pw_ent != NULL) ? pw_ent->pw_name : "UNKNOWN",
700                        (unsigned long) statbuf.st_gid,
701                        (gw_ent != NULL) ? gw_ent->gr_name : "UNKNOWN");
702 # if ENABLE_SELINUX
703                 printf("   S_Context: %lc\n", *scontext);
704 # endif
705                 printf("Access: %s\n", human_time(statbuf.st_atime));
706                 printf("Modify: %s\n", human_time(statbuf.st_mtime));
707                 printf("Change: %s\n", human_time(statbuf.st_ctime));
708         }
709 #endif  /* FEATURE_STAT_FORMAT */
710         return 1;
711 }
712
713 int stat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
714 int stat_main(int argc UNUSED_PARAM, char **argv)
715 {
716         IF_FEATURE_STAT_FORMAT(char *format = NULL;)
717         int i;
718         int ok;
719         unsigned opts;
720         statfunc_ptr statfunc = do_stat;
721
722         opt_complementary = "-1"; /* min one arg */
723         opts = getopt32(argv, "ftL"
724                 IF_SELINUX("Z")
725                 IF_FEATURE_STAT_FORMAT("c:", &format)
726         );
727         if (opts & OPT_FILESYS) /* -f */
728                 statfunc = do_statfs;
729 #if ENABLE_SELINUX
730         if (opts & OPT_SELINUX) {
731                 selinux_or_die();
732         }
733 #endif
734         ok = 1;
735         argv += optind;
736         for (i = 0; argv[i]; ++i)
737                 ok &= statfunc(argv[i] IF_FEATURE_STAT_FORMAT(, format));
738
739         return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
740 }