Remove FBSDID, move $ rcs id back to a comment field.
[dragonfly.git] / usr.bin / stat / stat.c
1 /*
2  * Copyright (c) 2002 The NetBSD Foundation, Inc.
3  * All rights reserved.
4  *
5  * This code is derived from software contributed to The NetBSD Foundation
6  * by Andrew Brown.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *      This product includes software developed by the NetBSD
19  *      Foundation, Inc. and its contributors.
20  * 4. Neither the name of The NetBSD Foundation nor the names of its
21  *    contributors may be used to endorse or promote products derived
22  *    from this software without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
25  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
26  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
28  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34  * POSSIBILITY OF SUCH DAMAGE.
35  *
36  * $NetBSD: stat.c,v 1.10 2003/05/08 13:05:38 atatat Exp $
37  * $FreeBSD: src/usr.bin/stat/stat.c,v 1.5 2003/05/11 23:02:09 dougb Exp $
38  * $DragonFly: src/usr.bin/stat/stat.c,v 1.4 2003/08/14 18:28:45 dillon Exp $
39  */
40
41 #include <sys/cdefs.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
44
45 #include <ctype.h>
46 #include <err.h>
47 #include <grp.h>
48 #include <limits.h>
49 #include <pwd.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <time.h>
54 #include <unistd.h>
55
56 #define DEF_FORMAT \
57         "%d %i %Sp %l %Su %Sg %r %z %a %m %c %k %b %N"
58 #define RAW_FORMAT      "%d %i %#p %l %u %g %r %z %a %m %c  %k %b %N"
59 #define LS_FORMAT       "%Sp %l %Su %Sg %Z %m %N%SY"
60 #define LSF_FORMAT      "%Sp %l %Su %Sg %Z %m %N%T%SY"
61 #define SHELL_FORMAT \
62         "st_dev=%d st_ino=%i st_mode=%#p st_nlink=%l " \
63         "st_uid=%u st_gid=%g st_rdev=%r st_size=%z " \
64         "st_atime=%a st_mtime=%m st_ctime=%c " \
65         "st_blksize=%k st_blocks=%b"
66 #define LINUX_FORMAT \
67         "  File: \"%N\"%n" \
68         "  Size: %-11z  FileType: %HT%n" \
69         "  Mode: (%04OLp/%.10Sp)         Uid: (%5u/%8Su)  Gid: (%5g/%8Sg)%n" \
70         "Device: %Hd,%Ld   Inode: %i    Links: %l%n" \
71         "Access: %a%n" \
72         "Modify: %m%n" \
73         "Change: %c"
74
75 #define TIME_FORMAT     "%b %e %T %Y"
76
77 #define FLAG_POUND      0x01
78 #define FLAG_SPACE      0x02
79 #define FLAG_PLUS       0x04
80 #define FLAG_ZERO       0x08
81 #define FLAG_MINUS      0x10
82
83 /*
84  * These format characters must all be unique, except the magic one.
85  */
86 #define FMT_MAGIC       '%'
87 #define FMT_DOT         '.'
88
89 #define SIMPLE_NEWLINE  'n'
90 #define SIMPLE_TAB      't'
91 #define SIMPLE_PERCENT  '%'
92 #define SIMPLE_NUMBER   '@'
93
94 #define FMT_POUND       '#'
95 #define FMT_SPACE       ' '
96 #define FMT_PLUS        '+'
97 #define FMT_ZERO        '0'
98 #define FMT_MINUS       '-'
99
100 #define FMT_DECIMAL     'D'
101 #define FMT_OCTAL       'O'
102 #define FMT_UNSIGNED    'U'
103 #define FMT_HEX         'X'
104 #define FMT_FLOAT       'F'
105 #define FMT_STRING      'S'
106
107 #define FMTF_DECIMAL    0x01
108 #define FMTF_OCTAL      0x02
109 #define FMTF_UNSIGNED   0x04
110 #define FMTF_HEX        0x08
111 #define FMTF_FLOAT      0x10
112 #define FMTF_STRING     0x20
113
114 #define HIGH_PIECE      'H'
115 #define MIDDLE_PIECE    'M'
116 #define LOW_PIECE       'L'
117
118 #define SHOW_st_dev     'd'
119 #define SHOW_st_ino     'i'
120 #define SHOW_st_mode    'p'
121 #define SHOW_st_nlink   'l'
122 #define SHOW_st_uid     'u'
123 #define SHOW_st_gid     'g'
124 #define SHOW_st_rdev    'r'
125 #define SHOW_st_atime   'a'
126 #define SHOW_st_mtime   'm'
127 #define SHOW_st_ctime   'c'
128 #define SHOW_st_size    'z'
129 #define SHOW_st_blocks  'b'
130 #define SHOW_st_blksize 'k'
131 #define SHOW_st_flags   'f'
132 #define SHOW_st_gen     'v'
133 #define SHOW_symlink    'Y'
134 #define SHOW_filetype   'T'
135 #define SHOW_filename   'N'
136 #define SHOW_sizerdev   'Z'
137
138 void    usage(const char *);
139 void    output(const struct stat *, const char *,
140             const char *, int, int, int);
141 int     format1(const struct stat *,    /* stat info */
142             const char *,               /* the file name */
143             const char *, int,          /* the format string itself */
144             char *, size_t,             /* a place to put the output */
145             int, int, int, int,         /* the parsed format */
146             int, int);
147
148 char *timefmt;
149 int linkfail;
150
151 #define addchar(s, c, nl) \
152         do { \
153                 (void)fputc((c), (s)); \
154                 (*nl) = ((c) == '\n'); \
155         } while (0/*CONSTCOND*/)
156
157 int
158 main(int argc, char *argv[])
159 {
160         struct stat st;
161         int ch, rc, errs, am_readlink;
162         int lsF, fmtchar, usestat, fn, nonl, quiet;
163         char *statfmt, *options, *synopsis;
164
165         am_readlink = 0;
166         lsF = 0;
167         fmtchar = '\0';
168         usestat = 0;
169         nonl = 0;
170         quiet = 0;
171         linkfail = 0;
172         statfmt = NULL;
173         timefmt = NULL;
174
175         if (strcmp(getprogname(), "readlink") == 0) {
176                 am_readlink = 1;
177                 options = "n";
178                 synopsis = "[-n] [file ...]";
179                 statfmt = "%Y";
180                 fmtchar = 'f';
181                 quiet = 1;
182         } else {
183                 options = "f:FlLnqrst:x";
184                 synopsis = "[-FlLnqrsx] [-f format] [-t timefmt] [file ...]";
185         }
186
187         while ((ch = getopt(argc, argv, options)) != -1)
188                 switch (ch) {
189                 case 'F':
190                         lsF = 1;
191                         break;
192                 case 'L':
193                         usestat = 1;
194                         break;
195                 case 'n':
196                         nonl = 1;
197                         break;
198                 case 'q':
199                         quiet = 1;
200                         break;
201                 case 'f':
202                         statfmt = optarg;
203                         /* FALLTHROUGH */
204                 case 'l':
205                 case 'r':
206                 case 's':
207                 case 'x':
208                         if (fmtchar != 0)
209                                 errx(1, "can't use format '%c' with '%c'",
210                                     fmtchar, ch);
211                         fmtchar = ch;
212                         break;
213                 case 't':
214                         timefmt = optarg;
215                         break;
216                 default:
217                         usage(synopsis);
218                 }
219
220         argc -= optind;
221         argv += optind;
222         fn = 1;
223
224         if (fmtchar == '\0') {
225                 if (lsF)
226                         fmtchar = 'l';
227                 else {
228                         fmtchar = 'f';
229                         statfmt = DEF_FORMAT;
230                 }
231         }
232
233         if (lsF && fmtchar != 'l')
234                 errx(1, "can't use format '%c' with -F", fmtchar);
235
236         switch (fmtchar) {
237         case 'f':
238                 /* statfmt already set */
239                 break;
240         case 'l':
241                 statfmt = lsF ? LSF_FORMAT : LS_FORMAT;
242                 break;
243         case 'r':
244                 statfmt = RAW_FORMAT;
245                 break;
246         case 's':
247                 statfmt = SHELL_FORMAT;
248                 break;
249         case 'x':
250                 statfmt = LINUX_FORMAT;
251                 if (timefmt == NULL)
252                         timefmt = "%c";
253                 break;
254         default:
255                 usage(synopsis);
256                 /*NOTREACHED*/
257         }
258
259         if (timefmt == NULL)
260                 timefmt = TIME_FORMAT;
261
262         errs = 0;
263         do {
264                 if (argc == 0)
265                         rc = fstat(STDIN_FILENO, &st);
266                 else if (usestat)
267                         rc = stat(argv[0], &st);
268                 else
269                         rc = lstat(argv[0], &st);
270
271                 if (rc == -1) {
272                         errs = 1;
273                         linkfail = 1;
274                         if (!quiet)
275                                 warn("%s: stat",
276                                     argc == 0 ? "(stdin)" : argv[0]);
277                 }
278                 else
279                         output(&st, argv[0], statfmt, fn, nonl, quiet);
280
281                 argv++;
282                 argc--;
283                 fn++;
284         } while (argc > 0);
285
286         return (am_readlink ? linkfail : errs);
287 }
288
289 void
290 usage(const char *synopsis)
291 {
292
293         (void)fprintf(stderr, "usage: %s %s\n", getprogname(), synopsis);
294         exit(1);
295 }
296
297 /* 
298  * Parses a format string.
299  */
300 void
301 output(const struct stat *st, const char *file,
302     const char *statfmt, int fn, int nonl, int quiet)
303 {
304         int flags, size, prec, ofmt, hilo, what;
305         char buf[PATH_MAX];
306         const char *subfmt;
307         int nl, t, i;
308
309         nl = 1;
310         while (*statfmt != '\0') {
311
312                 /*
313                  * Non-format characters go straight out.
314                  */
315                 if (*statfmt != FMT_MAGIC) {
316                         addchar(stdout, *statfmt, &nl);
317                         statfmt++;
318                         continue;
319                 }
320
321                 /*
322                  * The current format "substring" starts here,
323                  * and then we skip the magic.
324                  */
325                 subfmt = statfmt;
326                 statfmt++;
327
328                 /*
329                  * Some simple one-character "formats".
330                  */
331                 switch (*statfmt) {
332                 case SIMPLE_NEWLINE:
333                         addchar(stdout, '\n', &nl);
334                         statfmt++;
335                         continue;
336                 case SIMPLE_TAB:
337                         addchar(stdout, '\t', &nl);
338                         statfmt++;
339                         continue;
340                 case SIMPLE_PERCENT:
341                         addchar(stdout, '%', &nl);
342                         statfmt++;
343                         continue;
344                 case SIMPLE_NUMBER: {
345                         char num[12], *p;
346
347                         snprintf(num, sizeof(num), "%d", fn);
348                         for (p = &num[0]; *p; p++)
349                                 addchar(stdout, *p, &nl);
350                         statfmt++;
351                         continue;
352                 }
353                 }
354
355                 /*
356                  * This must be an actual format string.  Format strings are
357                  * similar to printf(3) formats up to a point, and are of
358                  * the form:
359                  *
360                  *      %       required start of format
361                  *      [-# +0] opt. format characters
362                  *      size    opt. field width
363                  *      .       opt. decimal separator, followed by
364                  *      prec    opt. precision
365                  *      fmt     opt. output specifier (string, numeric, etc.)
366                  *      sub     opt. sub field specifier (high, middle, low)
367                  *      datum   required field specifier (size, mode, etc)
368                  *
369                  * Only the % and the datum selector are required.  All data
370                  * have reasonable default output forms.  The "sub" specifier
371                  * only applies to certain data (mode, dev, rdev, filetype).
372                  * The symlink output defaults to STRING, yet will only emit
373                  * the leading " -> " if STRING is explicitly specified.  The
374                  * sizerdev datum will generate rdev output for character or
375                  * block devices, and size output for all others.
376                  */
377                 flags = 0;
378                 do {
379                         if      (*statfmt == FMT_POUND)
380                                 flags |= FLAG_POUND;
381                         else if (*statfmt == FMT_SPACE)
382                                 flags |= FLAG_SPACE;
383                         else if (*statfmt == FMT_PLUS)
384                                 flags |= FLAG_PLUS;
385                         else if (*statfmt == FMT_ZERO)
386                                 flags |= FLAG_ZERO;
387                         else if (*statfmt == FMT_MINUS)
388                                 flags |= FLAG_MINUS;
389                         else
390                                 break;
391                         statfmt++;
392                 } while (1/*CONSTCOND*/);
393
394                 size = -1;
395                 if (isdigit((unsigned)*statfmt)) {
396                         size = 0;
397                         while (isdigit((unsigned)*statfmt)) {
398                                 size = (size * 10) + (*statfmt - '0');
399                                 statfmt++;
400                                 if (size < 0)
401                                         goto badfmt;
402                         }
403                 }
404
405                 prec = -1;
406                 if (*statfmt == FMT_DOT) {
407                         statfmt++;
408
409                         prec = 0;
410                         while (isdigit((unsigned)*statfmt)) {
411                                 prec = (prec * 10) + (*statfmt - '0');
412                                 statfmt++;
413                                 if (prec < 0)
414                                         goto badfmt;
415                         }
416                 }
417
418 #define fmtcase(x, y)           case (y): (x) = (y); statfmt++; break
419 #define fmtcasef(x, y, z)       case (y): (x) = (z); statfmt++; break
420                 switch (*statfmt) {
421                         fmtcasef(ofmt, FMT_DECIMAL,     FMTF_DECIMAL);
422                         fmtcasef(ofmt, FMT_OCTAL,       FMTF_OCTAL);
423                         fmtcasef(ofmt, FMT_UNSIGNED,    FMTF_UNSIGNED);
424                         fmtcasef(ofmt, FMT_HEX,         FMTF_HEX);
425                         fmtcasef(ofmt, FMT_FLOAT,       FMTF_FLOAT);
426                         fmtcasef(ofmt, FMT_STRING,      FMTF_STRING);
427                 default:
428                         ofmt = 0;
429                         break;
430                 }
431
432                 switch (*statfmt) {
433                         fmtcase(hilo, HIGH_PIECE);
434                         fmtcase(hilo, MIDDLE_PIECE);
435                         fmtcase(hilo, LOW_PIECE);
436                 default:
437                         hilo = 0;
438                         break;
439                 }
440
441                 switch (*statfmt) {
442                         fmtcase(what, SHOW_st_dev);
443                         fmtcase(what, SHOW_st_ino);
444                         fmtcase(what, SHOW_st_mode);
445                         fmtcase(what, SHOW_st_nlink);
446                         fmtcase(what, SHOW_st_uid);
447                         fmtcase(what, SHOW_st_gid);
448                         fmtcase(what, SHOW_st_rdev);
449                         fmtcase(what, SHOW_st_atime);
450                         fmtcase(what, SHOW_st_mtime);
451                         fmtcase(what, SHOW_st_ctime);
452                         fmtcase(what, SHOW_st_size);
453                         fmtcase(what, SHOW_st_blocks);
454                         fmtcase(what, SHOW_st_blksize);
455                         fmtcase(what, SHOW_st_flags);
456                         fmtcase(what, SHOW_st_gen);
457                         fmtcase(what, SHOW_symlink);
458                         fmtcase(what, SHOW_filetype);
459                         fmtcase(what, SHOW_filename);
460                         fmtcase(what, SHOW_sizerdev);
461                 default:
462                         goto badfmt;
463                 }
464 #undef fmtcasef
465 #undef fmtcase
466
467                 t = format1(st,
468                      file,
469                      subfmt, statfmt - subfmt,
470                      buf, sizeof(buf),
471                      flags, size, prec, ofmt, hilo, what);
472
473                 for (i = 0; i < t && i < sizeof(buf); i++)
474                         addchar(stdout, buf[i], &nl);
475
476                 continue;
477
478         badfmt:
479                 errx(1, "%.*s: bad format",
480                     (int)(statfmt - subfmt + 1), subfmt);
481         }
482
483         if (!nl && !nonl)
484                 (void)fputc('\n', stdout);
485         (void)fflush(stdout);
486 }
487
488 /*
489  * Arranges output according to a single parsed format substring.
490  */
491 int
492 format1(const struct stat *st,
493     const char *file,
494     const char *fmt, int flen,
495     char *buf, size_t blen,
496     int flags, int size, int prec, int ofmt,
497     int hilo, int what)
498 {
499         u_int64_t data;
500         char *sdata, lfmt[24], tmp[20];
501         char smode[12], sid[12], path[PATH_MAX + 4];
502         struct passwd *pw;
503         struct group *gr;
504         const struct timespec *tsp;
505         struct timespec ts;
506         struct tm *tm;
507         int l, small, formats;
508
509         tsp = NULL;
510         formats = 0;
511         small = 0;
512
513         /*
514          * First, pick out the data and tweak it based on hilo or
515          * specified output format (symlink output only).
516          */
517         switch (what) {
518         case SHOW_st_dev:
519         case SHOW_st_rdev:
520                 small = (sizeof(st->st_dev) == 4);
521                 data = (what == SHOW_st_dev) ? st->st_dev : st->st_rdev;
522                 sdata = (what == SHOW_st_dev) ?
523                     devname(st->st_dev, S_IFBLK) :
524                     devname(st->st_rdev, 
525                     S_ISCHR(st->st_mode) ? S_IFCHR :
526                     S_ISBLK(st->st_mode) ? S_IFBLK :
527                     0U);
528                 if (sdata == NULL)
529                         sdata = "???";
530                 if (hilo == HIGH_PIECE) {
531                         data = major(data);
532                         hilo = 0;
533                 }
534                 else if (hilo == LOW_PIECE) {
535                         data = minor((unsigned)data);
536                         hilo = 0;
537                 }
538                 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX |
539                     FMTF_STRING;
540                 if (ofmt == 0)
541                         ofmt = FMTF_UNSIGNED;
542                 break;
543         case SHOW_st_ino:
544                 small = (sizeof(st->st_ino) == 4);
545                 data = st->st_ino;
546                 sdata = NULL;
547                 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX;
548                 if (ofmt == 0)
549                         ofmt = FMTF_UNSIGNED;
550                 break;
551         case SHOW_st_mode:
552                 small = (sizeof(st->st_mode) == 4);
553                 data = st->st_mode;
554                 strmode(st->st_mode, smode);
555                 sdata = smode;
556                 l = strlen(sdata);
557                 if (sdata[l - 1] == ' ')
558                         sdata[--l] = '\0';
559                 if (hilo == HIGH_PIECE) {
560                         data >>= 12;
561                         sdata += 1;
562                         sdata[3] = '\0';
563                         hilo = 0;
564                 }
565                 else if (hilo == MIDDLE_PIECE) {
566                         data = (data >> 9) & 07;
567                         sdata += 4;
568                         sdata[3] = '\0';
569                         hilo = 0;
570                 }
571                 else if (hilo == LOW_PIECE) {
572                         data &= 0777;
573                         sdata += 7;
574                         sdata[3] = '\0';
575                         hilo = 0;
576                 }
577                 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX |
578                     FMTF_STRING;
579                 if (ofmt == 0)
580                         ofmt = FMTF_OCTAL;
581                 break;
582         case SHOW_st_nlink:
583                 small = (sizeof(st->st_dev) == 4);
584                 data = st->st_nlink;
585                 sdata = NULL;
586                 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX;
587                 if (ofmt == 0)
588                         ofmt = FMTF_UNSIGNED;
589                 break;
590         case SHOW_st_uid:
591                 small = (sizeof(st->st_uid) == 4);
592                 data = st->st_uid;
593                 if ((pw = getpwuid(st->st_uid)) != NULL)
594                         sdata = pw->pw_name;
595                 else {
596                         snprintf(sid, sizeof(sid), "(%ld)", (long)st->st_uid);
597                         sdata = sid;
598                 }
599                 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX |
600                     FMTF_STRING;
601                 if (ofmt == 0)
602                         ofmt = FMTF_UNSIGNED;
603                 break;
604         case SHOW_st_gid:
605                 small = (sizeof(st->st_gid) == 4);
606                 data = st->st_gid;
607                 if ((gr = getgrgid(st->st_gid)) != NULL)
608                         sdata = gr->gr_name;
609                 else {
610                         snprintf(sid, sizeof(sid), "(%ld)", (long)st->st_gid);
611                         sdata = sid;
612                 }
613                 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX |
614                     FMTF_STRING;
615                 if (ofmt == 0)
616                         ofmt = FMTF_UNSIGNED;
617                 break;
618         case SHOW_st_atime:
619                 tsp = &st->st_atimespec;
620                 /* FALLTHROUGH */
621         case SHOW_st_mtime:
622                 if (tsp == NULL)
623                         tsp = &st->st_mtimespec;
624                 /* FALLTHROUGH */
625         case SHOW_st_ctime:
626                 if (tsp == NULL)
627                         tsp = &st->st_ctimespec;
628                 /* FALLTHROUGH */
629         case SHOW_st_size:
630                 small = (sizeof(st->st_size) == 4);
631                 data = st->st_size;
632                 sdata = NULL;
633                 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX;
634                 if (ofmt == 0)
635                         ofmt = FMTF_UNSIGNED;
636                 break;
637         case SHOW_st_blocks:
638                 small = (sizeof(st->st_blocks) == 4);
639                 data = st->st_blocks;
640                 sdata = NULL;
641                 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX;
642                 if (ofmt == 0)
643                         ofmt = FMTF_UNSIGNED;
644                 break;
645         case SHOW_st_blksize:
646                 small = (sizeof(st->st_blksize) == 4);
647                 data = st->st_blksize;
648                 sdata = NULL;
649                 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX;
650                 if (ofmt == 0)
651                         ofmt = FMTF_UNSIGNED;
652                 break;
653         case SHOW_st_flags:
654                 small = (sizeof(st->st_flags) == 4);
655                 data = st->st_flags;
656                 sdata = NULL;
657                 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX;
658                 if (ofmt == 0)
659                         ofmt = FMTF_UNSIGNED;
660                 break;
661         case SHOW_st_gen:
662                 small = (sizeof(st->st_gen) == 4);
663                 data = st->st_gen;
664                 sdata = NULL;
665                 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX;
666                 if (ofmt == 0)
667                         ofmt = FMTF_UNSIGNED;
668                 break;
669         case SHOW_symlink:
670                 small = 0;
671                 data = 0;
672                 if (S_ISLNK(st->st_mode)) {
673                         snprintf(path, sizeof(path), " -> ");
674                         l = readlink(file, path + 4, sizeof(path) - 4 - 1);
675                         if (l == -1) {
676                                 linkfail = 1;
677                                 l = 0;
678                                 path[0] = '\0';
679                         }
680                         path[l + 4] = '\0';
681                         sdata = path + (ofmt == FMTF_STRING ? 0 : 4);
682                 }
683                 else {
684                         linkfail = 1;
685                         sdata = "";
686                 }
687                 formats = FMTF_STRING;
688                 if (ofmt == 0)
689                         ofmt = FMTF_STRING;
690                 break;
691         case SHOW_filetype:
692                 small = 0;
693                 data = 0;
694                 sdata = smode;
695                 sdata[0] = '\0';
696                 if (hilo == 0 || hilo == LOW_PIECE) {
697                         switch (st->st_mode & S_IFMT) {
698                         case S_IFIFO:   (void)strcat(sdata, "|");       break;
699                         case S_IFDIR:   (void)strcat(sdata, "/");       break;
700                         case S_IFREG:
701                                 if (st->st_mode &
702                                     (S_IXUSR | S_IXGRP | S_IXOTH))
703                                         (void)strcat(sdata, "*");
704                                 break;
705                         case S_IFLNK:   (void)strcat(sdata, "@");       break;
706                         case S_IFSOCK:  (void)strcat(sdata, "=");       break;
707                         case S_IFWHT:   (void)strcat(sdata, "%");       break;
708                         }
709                         hilo = 0;
710                 }
711                 else if (hilo == HIGH_PIECE) {
712                         switch (st->st_mode & S_IFMT) {
713                         case S_IFIFO:   sdata = "Fifo File";            break;
714                         case S_IFCHR:   sdata = "Character Device";     break;
715                         case S_IFDIR:   sdata = "Directory";            break;
716                         case S_IFBLK:   sdata = "Block Device";         break;
717                         case S_IFREG:   sdata = "Regular File";         break;
718                         case S_IFLNK:   sdata = "Symbolic Link";        break;
719                         case S_IFSOCK:  sdata = "Socket";               break;
720                         case S_IFWHT:   sdata = "Whiteout File";        break;
721                         default:        sdata = "???";                  break;
722                         }
723                         hilo = 0;
724                 }
725                 formats = FMTF_STRING;
726                 if (ofmt == 0)
727                         ofmt = FMTF_STRING;
728                 break;
729         case SHOW_filename:
730                 small = 0;
731                 data = 0;
732                 if (file == NULL)
733                         (void)strncpy(path, "(stdin)", sizeof(path));
734                 else
735                         (void)strncpy(path, file, sizeof(path));
736                 sdata = path;
737                 formats = FMTF_STRING;
738                 if (ofmt == 0)
739                         ofmt = FMTF_STRING;
740                 break;
741         case SHOW_sizerdev:
742                 if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) {
743                         char majdev[20], mindev[20];
744                         int l1, l2;
745
746                         l1 = format1(st,
747                             file,
748                             fmt, flen,
749                             majdev, sizeof(majdev),
750                             flags, size, prec,
751                             ofmt, HIGH_PIECE, SHOW_st_rdev);
752                         l2 = format1(st,
753                             file,
754                             fmt, flen,
755                             mindev, sizeof(mindev),
756                             flags, size, prec,
757                             ofmt, LOW_PIECE, SHOW_st_rdev);
758                         return (snprintf(buf, blen, "%.*s,%.*s",
759                             l1, majdev, l2, mindev));
760                 }
761                 else {
762                         return (format1(st,
763                             file,
764                             fmt, flen,
765                             buf, blen,
766                             flags, size, prec,
767                             ofmt, 0, SHOW_st_size));
768                 }
769                 /*NOTREACHED*/
770         default:
771                 errx(1, "%.*s: bad format", (int)flen, fmt);
772         }
773
774         /*
775          * If a subdatum was specified but not supported, or an output
776          * format was selected that is not supported, that's an error.
777          */
778         if (hilo != 0 || (ofmt & formats) == 0)
779                 errx(1, "%.*s: bad format", (int)flen, fmt);
780
781         /*
782          * Assemble the format string for passing to printf(3).
783          */
784         lfmt[0] = '\0';
785         (void)strcat(lfmt, "%");
786         if (flags & FLAG_POUND)
787                 (void)strcat(lfmt, "#");
788         if (flags & FLAG_SPACE)
789                 (void)strcat(lfmt, " ");
790         if (flags & FLAG_PLUS)
791                 (void)strcat(lfmt, "+");
792         if (flags & FLAG_MINUS)
793                 (void)strcat(lfmt, "-");
794         if (flags & FLAG_ZERO)
795                 (void)strcat(lfmt, "0");
796
797         /*
798          * Only the timespecs support the FLOAT output format, and that
799          * requires work that differs from the other formats.
800          */ 
801         if (ofmt == FMTF_FLOAT) {
802                 /*
803                  * Nothing after the decimal point, so just print seconds.
804                  */
805                 if (prec == 0) {
806                         if (size != -1) {
807                                 (void)snprintf(tmp, sizeof(tmp), "%d", size);
808                                 (void)strcat(lfmt, tmp);
809                         }
810                         (void)strcat(lfmt, "d");
811                         return (snprintf(buf, blen, lfmt, ts.tv_sec));
812                 }
813
814                 /*
815                  * Unspecified precision gets all the precision we have:
816                  * 9 digits.
817                  */
818                 if (prec == -1)
819                         prec = 9;
820
821                 /*
822                  * Adjust the size for the decimal point and the digits
823                  * that will follow.
824                  */
825                 size -= prec + 1;
826
827                 /*
828                  * Any leftover size that's legitimate will be used.
829                  */
830                 if (size > 0) {
831                         (void)snprintf(tmp, sizeof(tmp), "%d", size);
832                         (void)strcat(lfmt, tmp);
833                 }
834                 (void)strcat(lfmt, "d");
835
836                 /*
837                  * The stuff after the decimal point always needs zero
838                  * filling.
839                  */
840                 (void)strcat(lfmt, ".%0");
841
842                 /*
843                  * We can "print" at most nine digits of precision.  The
844                  * rest we will pad on at the end.
845                  */
846                 (void)snprintf(tmp, sizeof(tmp), "%dd", prec > 9 ? 9 : prec);
847                 (void)strcat(lfmt, tmp);
848
849                 /*
850                  * For precision of less that nine digits, trim off the
851                  * less significant figures.
852                  */
853                 for (; prec < 9; prec++)
854                         ts.tv_nsec /= 10;
855
856                 /*
857                  * Use the format, and then tack on any zeroes that
858                  * might be required to make up the requested precision.
859                  */
860                 l = snprintf(buf, blen, lfmt, ts.tv_sec, ts.tv_nsec);
861                 for (; prec > 9 && l < blen; prec--, l++)
862                         (void)strcat(buf, "0");
863                 return (l);
864         }
865
866         /*
867          * Add on size and precision, if specified, to the format.
868          */
869         if (size != -1) {
870                 (void)snprintf(tmp, sizeof(tmp), "%d", size);
871                 (void)strcat(lfmt, tmp);
872         }
873         if (prec != -1) {
874                 (void)snprintf(tmp, sizeof(tmp), ".%d", prec);
875                 (void)strcat(lfmt, tmp);
876         }
877
878         /*
879          * String output uses the temporary sdata.
880          */
881         if (ofmt == FMTF_STRING) {
882                 if (sdata == NULL)
883                         errx(1, "%.*s: bad format", (int)flen, fmt);
884                 (void)strcat(lfmt, "s");
885                 return (snprintf(buf, blen, lfmt, sdata));
886         }
887
888         /*
889          * Ensure that sign extension does not cause bad looking output
890          * for some forms.
891          */
892         if (small && ofmt != FMTF_DECIMAL)
893                 data = (u_int32_t)data;
894
895         /*
896          * The four "numeric" output forms.
897          */
898         (void)strcat(lfmt, "ll");
899         switch (ofmt) {
900         case FMTF_DECIMAL:      (void)strcat(lfmt, "d");        break;
901         case FMTF_OCTAL:                (void)strcat(lfmt, "o");        break;
902         case FMTF_UNSIGNED:     (void)strcat(lfmt, "u");        break;
903         case FMTF_HEX:          (void)strcat(lfmt, "x");        break;
904         }
905
906         return (snprintf(buf, blen, lfmt, data));
907 }