trylink: produce even more info about final link stage
[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 tarball for details.
14  */
15
16 #include "libbb.h"
17
18 /* vars to control behavior */
19 #define OPT_FILESYS             (1<<0)
20 #define OPT_TERSE               (1<<1)
21 #define OPT_DEREFERENCE (1<<2)
22 #define OPT_SELINUX             (1<<3)
23
24 static char buf[sizeof("YYYY-MM-DD HH:MM:SS.000000000")] ALIGN1;
25
26 static char const * file_type(struct stat const *st)
27 {
28         /* See POSIX 1003.1-2001 XCU Table 4-8 lines 17093-17107
29          * for some of these formats.
30          * To keep diagnostics grammatical in English, the
31          * returned string must start with a consonant.
32          */
33         if (S_ISREG(st->st_mode))  return st->st_size == 0 ? "regular empty file" : "regular file";
34         if (S_ISDIR(st->st_mode))  return "directory";
35         if (S_ISBLK(st->st_mode))  return "block special file";
36         if (S_ISCHR(st->st_mode))  return "character special file";
37         if (S_ISFIFO(st->st_mode)) return "fifo";
38         if (S_ISLNK(st->st_mode))  return "symbolic link";
39         if (S_ISSOCK(st->st_mode)) return "socket";
40         if (S_TYPEISMQ(st))        return "message queue";
41         if (S_TYPEISSEM(st))       return "semaphore";
42         if (S_TYPEISSHM(st))       return "shared memory object";
43 #ifdef S_TYPEISTMO
44         if (S_TYPEISTMO(st))       return "typed memory object";
45 #endif
46         return "weird file";
47 }
48
49 static char const *human_time(time_t t)
50 {
51         /* Old
52         static char *str;
53         str = ctime(&t);
54         str[strlen(str)-1] = '\0';
55         return str;
56         */
57         /* coreutils 6.3 compat: */
58
59         strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S.000000000", localtime(&t));
60         return buf;
61 }
62
63 /* Return the type of the specified file system.
64  * Some systems have statfvs.f_basetype[FSTYPSZ]. (AIX, HP-UX, and Solaris)
65  * Others have statfs.f_fstypename[MFSNAMELEN]. (NetBSD 1.5.2)
66  * Still others have neither and have to get by with f_type (Linux).
67  */
68 static char const *human_fstype(long f_type)
69 {
70         int i;
71         static const struct types {
72                 long type;
73                 const char *const fs;
74         } humantypes[] = {
75                 { 0xADFF,     "affs" },
76                 { 0x1Cd1,     "devpts" },
77                 { 0x137D,     "ext" },
78                 { 0xEF51,     "ext2" },
79                 { 0xEF53,     "ext2/ext3" },
80                 { 0x3153464a, "jfs" },
81                 { 0x58465342, "xfs" },
82                 { 0xF995E849, "hpfs" },
83                 { 0x9660,     "isofs" },
84                 { 0x4000,     "isofs" },
85                 { 0x4004,     "isofs" },
86                 { 0x137F,     "minix" },
87                 { 0x138F,     "minix (30 char.)" },
88                 { 0x2468,     "minix v2" },
89                 { 0x2478,     "minix v2 (30 char.)" },
90                 { 0x4d44,     "msdos" },
91                 { 0x4006,     "fat" },
92                 { 0x564c,     "novell" },
93                 { 0x6969,     "nfs" },
94                 { 0x9fa0,     "proc" },
95                 { 0x517B,     "smb" },
96                 { 0x012FF7B4, "xenix" },
97                 { 0x012FF7B5, "sysv4" },
98                 { 0x012FF7B6, "sysv2" },
99                 { 0x012FF7B7, "coh" },
100                 { 0x00011954, "ufs" },
101                 { 0x012FD16D, "xia" },
102                 { 0x5346544e, "ntfs" },
103                 { 0x1021994,  "tmpfs" },
104                 { 0x52654973, "reiserfs" },
105                 { 0x28cd3d45, "cramfs" },
106                 { 0x7275,     "romfs" },
107                 { 0x858458f6, "romfs" },
108                 { 0x73717368, "squashfs" },
109                 { 0x62656572, "sysfs" },
110                 { 0, "UNKNOWN" }
111         };
112         for (i = 0; humantypes[i].type; ++i)
113                 if (humantypes[i].type == f_type)
114                         break;
115         return humantypes[i].fs;
116 }
117
118 #if ENABLE_FEATURE_STAT_FORMAT
119 /* print statfs info */
120 static void print_statfs(char *pformat, const size_t buf_len, const char m,
121                          const char *const filename, void const *data
122                          USE_SELINUX(, security_context_t scontext))
123 {
124         struct statfs const *statfsbuf = data;
125         if (m == 'n') {
126                 strncat(pformat, "s", buf_len);
127                 printf(pformat, filename);
128         } else if (m == 'i') {
129                 strncat(pformat, "Lx", buf_len);
130                 printf(pformat, statfsbuf->f_fsid);
131         } else if (m == 'l') {
132                 strncat(pformat, "lu", buf_len);
133                 printf(pformat, statfsbuf->f_namelen);
134         } else if (m == 't') {
135                 strncat(pformat, "lx", buf_len);
136                 printf(pformat, (unsigned long) (statfsbuf->f_type)); /* no equiv */
137         } else if (m == 'T') {
138                 strncat(pformat, "s", buf_len);
139                 printf(pformat, human_fstype(statfsbuf->f_type));
140         } else if (m == 'b') {
141                 strncat(pformat, "jd", buf_len);
142                 printf(pformat, (intmax_t) (statfsbuf->f_blocks));
143         } else if (m == 'f') {
144                 strncat(pformat, "jd", buf_len);
145                 printf(pformat, (intmax_t) (statfsbuf->f_bfree));
146         } else if (m == 'a') {
147                 strncat(pformat, "jd", buf_len);
148                 printf(pformat, (intmax_t) (statfsbuf->f_bavail));
149         } else if (m == 's' || m == 'S') {
150                 strncat(pformat, "lu", buf_len);
151                 printf(pformat, (unsigned long) (statfsbuf->f_bsize));
152         } else if (m == 'c') {
153                 strncat(pformat, "jd", buf_len);
154                 printf(pformat, (intmax_t) (statfsbuf->f_files));
155         } else if (m == 'd') {
156                 strncat(pformat, "jd", buf_len);
157                 printf(pformat, (intmax_t) (statfsbuf->f_ffree));
158 #if ENABLE_SELINUX
159         } else if (m == 'C' && (option_mask32 & OPT_SELINUX)) {
160                 strncat(pformat, "s", buf_len);
161                 printf(scontext);
162 #endif
163         } else {
164                 strncat(pformat, "c", buf_len);
165                 printf(pformat, m);
166         }
167 }
168
169 /* print stat info */
170 static void print_stat(char *pformat, const size_t buf_len, const char m,
171                        const char *const filename, void const *data
172                            USE_SELINUX(, security_context_t scontext))
173 {
174 #define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
175         struct stat *statbuf = (struct stat *) data;
176         struct passwd *pw_ent;
177         struct group *gw_ent;
178
179         if (m == 'n') {
180                 strncat(pformat, "s", buf_len);
181                 printf(pformat, filename);
182         } else if (m == 'N') {
183                 strncat(pformat, "s", buf_len);
184                 if (S_ISLNK(statbuf->st_mode)) {
185                         char *linkname = xmalloc_readlink_or_warn(filename);
186                         if (linkname == NULL) {
187                                 bb_perror_msg("cannot read symbolic link '%s'", filename);
188                                 return;
189                         }
190                         /*printf("\"%s\" -> \"%s\"", filename, linkname); */
191                         printf(pformat, filename);
192                         printf(" -> ");
193                         printf(pformat, linkname);
194                 } else {
195                         printf(pformat, filename);
196                 }
197         } else if (m == 'd') {
198                 strncat(pformat, "ju", buf_len);
199                 printf(pformat, (uintmax_t) statbuf->st_dev);
200         } else if (m == 'D') {
201                 strncat(pformat, "jx", buf_len);
202                 printf(pformat, (uintmax_t) statbuf->st_dev);
203         } else if (m == 'i') {
204                 strncat(pformat, "ju", buf_len);
205                 printf(pformat, (uintmax_t) statbuf->st_ino);
206         } else if (m == 'a') {
207                 strncat(pformat, "lo", buf_len);
208                 printf(pformat, (unsigned long) (statbuf->st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)));
209         } else if (m == 'A') {
210                 strncat(pformat, "s", buf_len);
211                 printf(pformat, bb_mode_string(statbuf->st_mode));
212         } else if (m == 'f') {
213                 strncat(pformat, "lx", buf_len);
214                 printf(pformat, (unsigned long) statbuf->st_mode);
215         } else if (m == 'F') {
216                 strncat(pformat, "s", buf_len);
217                 printf(pformat, file_type(statbuf));
218         } else if (m == 'h') {
219                 strncat(pformat, "lu", buf_len);
220                 printf(pformat, (unsigned long) statbuf->st_nlink);
221         } else if (m == 'u') {
222                 strncat(pformat, "lu", buf_len);
223                 printf(pformat, (unsigned long) statbuf->st_uid);
224         } else if (m == 'U') {
225                 strncat(pformat, "s", buf_len);
226                 setpwent();
227                 pw_ent = getpwuid(statbuf->st_uid);
228                 printf(pformat, (pw_ent != 0L) ? pw_ent->pw_name : "UNKNOWN");
229         } else if (m == 'g') {
230                 strncat(pformat, "lu", buf_len);
231                 printf(pformat, (unsigned long) statbuf->st_gid);
232         } else if (m == 'G') {
233                 strncat(pformat, "s", buf_len);
234                 setgrent();
235                 gw_ent = getgrgid(statbuf->st_gid);
236                 printf(pformat, (gw_ent != 0L) ? gw_ent->gr_name : "UNKNOWN");
237         } else if (m == 't') {
238                 strncat(pformat, "lx", buf_len);
239                 printf(pformat, (unsigned long) major(statbuf->st_rdev));
240         } else if (m == 'T') {
241                 strncat(pformat, "lx", buf_len);
242                 printf(pformat, (unsigned long) minor(statbuf->st_rdev));
243         } else if (m == 's') {
244                 strncat(pformat, "ju", buf_len);
245                 printf(pformat, (uintmax_t) (statbuf->st_size));
246         } else if (m == 'B') {
247                 strncat(pformat, "lu", buf_len);
248                 printf(pformat, (unsigned long) 512); //ST_NBLOCKSIZE
249         } else if (m == 'b') {
250                 strncat(pformat, "ju", buf_len);
251                 printf(pformat, (uintmax_t) statbuf->st_blocks);
252         } else if (m == 'o') {
253                 strncat(pformat, "lu", buf_len);
254                 printf(pformat, (unsigned long) statbuf->st_blksize);
255         } else if (m == 'x') {
256                 strncat(pformat, "s", buf_len);
257                 printf(pformat, human_time(statbuf->st_atime));
258         } else if (m == 'X') {
259                 strncat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu", buf_len);
260                 printf(pformat, (unsigned long) statbuf->st_atime);
261         } else if (m == 'y') {
262                 strncat(pformat, "s", buf_len);
263                 printf(pformat, human_time(statbuf->st_mtime));
264         } else if (m == 'Y') {
265                 strncat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu", buf_len);
266                 printf(pformat, (unsigned long) statbuf->st_mtime);
267         } else if (m == 'z') {
268                 strncat(pformat, "s", buf_len);
269                 printf(pformat, human_time(statbuf->st_ctime));
270         } else if (m == 'Z') {
271                 strncat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu", buf_len);
272                 printf(pformat, (unsigned long) statbuf->st_ctime);
273 #if ENABLE_SELINUX
274         } else if (m == 'C' && (option_mask32 & OPT_SELINUX)) {
275                 strncat(pformat, "s", buf_len);
276                 printf(pformat, scontext);
277 #endif
278         } else {
279                 strncat(pformat, "c", buf_len);
280                 printf(pformat, m);
281         }
282 }
283
284 static void print_it(char const *masterformat, char const *filename,
285                      void (*print_func) (char *, size_t, char, char const *, void const *
286                                                                  USE_SELINUX(, security_context_t scontext)),
287                                          void const *data USE_SELINUX(, security_context_t scontext) )
288 {
289         char *b;
290
291         /* create a working copy of the format string */
292         char *format = xstrdup(masterformat);
293
294         /* Add 2 to accomodate our conversion of the stat '%s' format string
295          * to the printf '%llu' one.  */
296         size_t n_alloc = strlen(format) + 2 + 1;
297         char *dest = xmalloc(n_alloc);
298
299         b = format;
300         while (b) {
301                 size_t len;
302                 char *p = strchr(b, '%');
303                 if (!p) {
304                         /* coreutils 6.3 always print <cr> at the end */
305                         /*fputs(b, stdout);*/
306                         puts(b);
307                         break;
308                 }
309                 *p++ = '\0';
310                 fputs(b, stdout);
311
312                 len = strspn(p, "#-+.I 0123456789");
313                 dest[0] = '%';
314                 memcpy(dest + 1, p, len);
315                 dest[1 + len] = 0;
316                 p += len;
317
318                 b = p + 1;
319                 switch (*p) {
320                 case '\0':
321                         b = NULL;
322                         /* fall through */
323                 case '%':
324                         putchar('%');
325                         break;
326                 default:
327                         print_func(dest, n_alloc, *p, filename, data USE_SELINUX(,scontext));
328                         break;
329                 }
330         }
331
332         free(format);
333         free(dest);
334 }
335 #endif
336
337 /* Stat the file system and print what we find.  */
338 static bool do_statfs(char const *filename, char const *format)
339 {
340         struct statfs statfsbuf;
341 #if ENABLE_SELINUX
342         security_context_t scontext = NULL;
343
344         if (option_mask32 & OPT_SELINUX) {
345                 if ((option_mask32 & OPT_DEREFERENCE
346                      ? lgetfilecon(filename, &scontext)
347                      : getfilecon(filename, &scontext)
348                     ) < 0
349                 ) {
350                         bb_perror_msg(filename);
351                         return 0;
352                 }
353         }
354 #endif
355         if (statfs(filename, &statfsbuf) != 0) {
356                 bb_perror_msg("cannot read file system information for '%s'", filename);
357                 return 0;
358         }
359
360 #if ENABLE_FEATURE_STAT_FORMAT
361         if (format == NULL)
362 #if !ENABLE_SELINUX
363                 format = (option_mask32 & OPT_TERSE
364                         ? "%n %i %l %t %s %b %f %a %c %d\n"
365                         : "  File: \"%n\"\n"
366                           "    ID: %-8i Namelen: %-7l Type: %T\n"
367                           "Block size: %-10s\n"
368                           "Blocks: Total: %-10b Free: %-10f Available: %a\n"
369                           "Inodes: Total: %-10c Free: %d");
370         print_it(format, filename, print_statfs, &statfsbuf USE_SELINUX(, scontext));
371 #else
372         format = (option_mask32 & OPT_TERSE
373                         ? (option_mask32 & OPT_SELINUX ? "%n %i %l %t %s %b %f %a %c %d %C\n":
374                         "%n %i %l %t %s %b %f %a %c %d\n")
375                         : (option_mask32 & OPT_SELINUX ?
376                         "  File: \"%n\"\n"
377                         "    ID: %-8i Namelen: %-7l Type: %T\n"
378                         "Block size: %-10s\n"
379                         "Blocks: Total: %-10b Free: %-10f Available: %a\n"
380                         "Inodes: Total: %-10c Free: %d"
381                         "  S_context: %C\n":
382                         "  File: \"%n\"\n"
383                         "    ID: %-8i Namelen: %-7l Type: %T\n"
384                         "Block size: %-10s\n"
385                         "Blocks: Total: %-10b Free: %-10f Available: %a\n"
386                         "Inodes: Total: %-10c Free: %d\n")
387                         );
388         print_it(format, filename, print_statfs, &statfsbuf USE_SELINUX(, scontext));
389 #endif /* SELINUX */
390 #else /* FEATURE_STAT_FORMAT */
391         format = (option_mask32 & OPT_TERSE
392                 ? "%s %llx %lu "
393                 : "  File: \"%s\"\n"
394                   "    ID: %-8Lx Namelen: %-7lu ");
395         printf(format,
396                filename,
397                statfsbuf.f_fsid,
398                statfsbuf.f_namelen);
399
400         if (option_mask32 & OPT_TERSE)
401                 printf("%lx ", (unsigned long) (statfsbuf.f_type));
402         else
403                 printf("Type: %s\n", human_fstype(statfsbuf.f_type));
404
405 #if !ENABLE_SELINUX
406         format = (option_mask32 & OPT_TERSE
407                 ? "%lu %ld %ld %ld %ld %ld\n"
408                 : "Block size: %-10lu\n"
409                   "Blocks: Total: %-10jd Free: %-10jd Available: %jd\n"
410                   "Inodes: Total: %-10jd Free: %jd\n");
411         printf(format,
412                (unsigned long) (statfsbuf.f_bsize),
413                (intmax_t) (statfsbuf.f_blocks),
414                (intmax_t) (statfsbuf.f_bfree),
415                (intmax_t) (statfsbuf.f_bavail),
416                (intmax_t) (statfsbuf.f_files),
417                (intmax_t) (statfsbuf.f_ffree));
418 #else
419         format = (option_mask32 & OPT_TERSE
420                 ? (option_mask32 & OPT_SELINUX ? "%lu %ld %ld %ld %ld %ld %C\n":
421                 "%lu %ld %ld %ld %ld %ld\n")
422                 : (option_mask32 & OPT_SELINUX ?
423                 "Block size: %-10lu\n"
424                 "Blocks: Total: %-10jd Free: %-10jd Available: %jd\n"
425                 "Inodes: Total: %-10jd Free: %jd"
426                 "S_context: %C\n":
427                 "Block size: %-10lu\n"
428                 "Blocks: Total: %-10jd Free: %-10jd Available: %jd\n"
429                 "Inodes: Total: %-10jd Free: %jd\n"));
430         printf(format,
431                (unsigned long) (statfsbuf.f_bsize),
432                (intmax_t) (statfsbuf.f_blocks),
433                (intmax_t) (statfsbuf.f_bfree),
434                (intmax_t) (statfsbuf.f_bavail),
435                (intmax_t) (statfsbuf.f_files),
436                (intmax_t) (statfsbuf.f_ffree),
437                 scontext);
438
439         if (scontext)
440                 freecon(scontext);
441 #endif
442 #endif  /* FEATURE_STAT_FORMAT */
443         return 1;
444 }
445
446 /* stat the file and print what we find */
447 static bool do_stat(char const *filename, char const *format)
448 {
449         struct stat statbuf;
450 #if ENABLE_SELINUX
451         security_context_t scontext = NULL;
452
453         if (option_mask32 & OPT_SELINUX) {
454                 if ((option_mask32 & OPT_DEREFERENCE
455                      ? lgetfilecon(filename, &scontext)
456                      : getfilecon(filename, &scontext)
457                     ) < 0
458                 ) {
459                         bb_perror_msg(filename);
460                         return 0;
461                 }
462         }
463 #endif
464         if ((option_mask32 & OPT_DEREFERENCE ? stat : lstat) (filename, &statbuf) != 0) {
465                 bb_perror_msg("cannot stat '%s'", filename);
466                 return 0;
467         }
468
469 #if ENABLE_FEATURE_STAT_FORMAT
470         if (format == NULL) {
471 #if !ENABLE_SELINUX
472                 if (option_mask32 & OPT_TERSE) {
473                         format = "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o";
474                 } else {
475                         if (S_ISBLK(statbuf.st_mode) || S_ISCHR(statbuf.st_mode)) {
476                                 format =
477                                         "  File: \"%N\"\n"
478                                         "  Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
479                                         "Device: %Dh/%dd\tInode: %-10i  Links: %-5h"
480                                         " Device type: %t,%T\n"
481                                         "Access: (%04a/%10.10A)  Uid: (%5u/%8U)   Gid: (%5g/%8G)\n"
482                                         "Access: %x\n" "Modify: %y\n" "Change: %z\n";
483                         } else {
484                                 format =
485                                         "  File: \"%N\"\n"
486                                         "  Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
487                                         "Device: %Dh/%dd\tInode: %-10i  Links: %h\n"
488                                         "Access: (%04a/%10.10A)  Uid: (%5u/%8U)   Gid: (%5g/%8G)\n"
489                                         "Access: %x\n" "Modify: %y\n" "Change: %z\n";
490                         }
491                 }
492 #else
493                 if (option_mask32 & OPT_TERSE) {
494                         format = (option_mask32 & OPT_SELINUX ?
495                                   "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o %C\n":
496                                   "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\n");
497                 } else {
498                         if (S_ISBLK(statbuf.st_mode) || S_ISCHR(statbuf.st_mode)) {
499                                 format = (option_mask32 & OPT_SELINUX ?
500                                           "  File: \"%N\"\n"
501                                           "  Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
502                                           "Device: %Dh/%dd\tInode: %-10i  Links: %-5h"
503                                           " Device type: %t,%T\n"
504                                           "Access: (%04a/%10.10A)  Uid: (%5u/%8U)   Gid: (%5g/%8G)\n"
505                                           "   S_Context: %C\n"
506                                           "Access: %x\n" "Modify: %y\n" "Change: %z\n":
507                                           "  File: \"%N\"\n"
508                                           "  Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
509                                           "Device: %Dh/%dd\tInode: %-10i  Links: %-5h"
510                                           " Device type: %t,%T\n"
511                                           "Access: (%04a/%10.10A)  Uid: (%5u/%8U)   Gid: (%5g/%8G)\n"
512                                           "Access: %x\n" "Modify: %y\n" "Change: %z\n");
513                         } else {
514                                 format = (option_mask32 & OPT_SELINUX ?
515                                           "  File: \"%N\"\n"
516                                           "  Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
517                                           "Device: %Dh/%dd\tInode: %-10i  Links: %h\n"
518                                           "Access: (%04a/%10.10A)  Uid: (%5u/%8U)   Gid: (%5g/%8G)\n"
519                                           "S_Context: %C\n"
520                                           "Access: %x\n" "Modify: %y\n" "Change: %z\n":
521                                           "  File: \"%N\"\n"
522                                           "  Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
523                                           "Device: %Dh/%dd\tInode: %-10i  Links: %h\n"
524                                           "Access: (%04a/%10.10A)  Uid: (%5u/%8U)   Gid: (%5g/%8G)\n"
525                                           "Access: %x\n" "Modify: %y\n" "Change: %z\n");
526                         }
527                 }
528 #endif
529         }
530         print_it(format, filename, print_stat, &statbuf USE_SELINUX(, scontext));
531 #else   /* FEATURE_STAT_FORMAT */
532         if (option_mask32 & OPT_TERSE) {
533                 printf("%s %ju %ju %lx %lu %lu %jx %ju %lu %lx %lx %lu %lu %lu %lu"
534                        SKIP_SELINUX("\n"),
535                        filename,
536                        (uintmax_t) (statbuf.st_size),
537                        (uintmax_t) statbuf.st_blocks,
538                        (unsigned long) statbuf.st_mode,
539                        (unsigned long) statbuf.st_uid,
540                        (unsigned long) statbuf.st_gid,
541                        (uintmax_t) statbuf.st_dev,
542                        (uintmax_t) statbuf.st_ino,
543                        (unsigned long) statbuf.st_nlink,
544                        (unsigned long) major(statbuf.st_rdev),
545                        (unsigned long) minor(statbuf.st_rdev),
546                        (unsigned long) statbuf.st_atime,
547                        (unsigned long) statbuf.st_mtime,
548                        (unsigned long) statbuf.st_ctime,
549                        (unsigned long) statbuf.st_blksize
550                 );
551 #if ENABLE_SELINUX
552                 if (option_mask32 & OPT_SELINUX)
553                         printf(" %lc\n", *scontext);
554                 else
555                         putchar('\n');
556 #endif
557         } else {
558                 char *linkname = NULL;
559
560                 struct passwd *pw_ent;
561                 struct group *gw_ent;
562                 setgrent();
563                 gw_ent = getgrgid(statbuf.st_gid);
564                 setpwent();
565                 pw_ent = getpwuid(statbuf.st_uid);
566
567                 if (S_ISLNK(statbuf.st_mode))
568                         linkname = xmalloc_readlink_or_warn(filename);
569                 if (linkname)
570                         printf("  File: \"%s\" -> \"%s\"\n", filename, linkname);
571                 else
572                         printf("  File: \"%s\"\n", filename);
573
574                 printf("  Size: %-10ju\tBlocks: %-10ju IO Block: %-6lu %s\n"
575                        "Device: %jxh/%jud\tInode: %-10ju  Links: %-5lu",
576                        (uintmax_t) (statbuf.st_size),
577                        (uintmax_t) statbuf.st_blocks,
578                        (unsigned long) statbuf.st_blksize,
579                        file_type(&statbuf),
580                        (uintmax_t) statbuf.st_dev,
581                        (uintmax_t) statbuf.st_dev,
582                        (uintmax_t) statbuf.st_ino,
583                        (unsigned long) statbuf.st_nlink);
584                 if (S_ISBLK(statbuf.st_mode) || S_ISCHR(statbuf.st_mode))
585                         printf(" Device type: %lx,%lx\n",
586                                (unsigned long) major(statbuf.st_rdev),
587                                (unsigned long) minor(statbuf.st_rdev));
588                 else
589                         putchar('\n');
590                 printf("Access: (%04lo/%10.10s)  Uid: (%5lu/%8s)   Gid: (%5lu/%8s)\n",
591                        (unsigned long) (statbuf.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)),
592                        bb_mode_string(statbuf.st_mode),
593                        (unsigned long) statbuf.st_uid,
594                        (pw_ent != 0L) ? pw_ent->pw_name : "UNKNOWN",
595                        (unsigned long) statbuf.st_gid,
596                        (gw_ent != 0L) ? gw_ent->gr_name : "UNKNOWN");
597 #if ENABLE_SELINUX
598                 printf("   S_Context: %lc\n", *scontext);
599 #endif
600                 printf("Access: %s\n" "Modify: %s\n" "Change: %s\n",
601                        human_time(statbuf.st_atime),
602                        human_time(statbuf.st_mtime),
603                        human_time(statbuf.st_ctime));
604         }
605 #endif  /* FEATURE_STAT_FORMAT */
606         return 1;
607 }
608
609 int stat_main(int argc, char **argv);
610 int stat_main(int argc, char **argv)
611 {
612         char *format = NULL;
613         int i;
614         int ok = 1;
615         bool (*statfunc)(char const *, char const *) = do_stat;
616
617         getopt32(argc, argv, "ftL"
618                 USE_SELINUX("Z")
619                 USE_FEATURE_STAT_FORMAT("c:", &format)
620         );
621
622         if (option_mask32 & OPT_FILESYS) /* -f */
623                 statfunc = do_statfs;
624         if (argc == optind)           /* files */
625                 bb_show_usage();
626
627 #if ENABLE_SELINUX
628         if (option_mask32 & OPT_SELINUX) {
629                 selinux_or_die();
630         }
631 #endif  /* ENABLE_SELINUX */
632         for (i = optind; i < argc; ++i)
633                 ok &= statfunc(argv[i], format);
634
635         return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
636 }