1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2002 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Andrew Brown.
9 *
10 * Copyright (c) 2025-2026 Klara, Inc.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 */
33
34 #include <sys/param.h>
35 #include <sys/mount.h>
36 #include <sys/stat.h>
37
38 #include <ctype.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <grp.h>
43 #include <limits.h>
44 #include <locale.h>
45 #include <paths.h>
46 #include <pwd.h>
47 #include <stdbool.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <time.h>
52 #include <unistd.h>
53
54 #define DEF_FORMAT \
55 "%d %i %Sp %l %Su %Sg %r %z \"%Sa\" \"%Sm\" \"%Sc\" \"%SB\" " \
56 "%k %b %#Xf %N"
57 #define RAW_FORMAT "%d %i %#p %l %u %g %r %z %a %m %c %B " \
58 "%k %b %f %N"
59 #define LS_FORMAT "%Sp %l %Su %Sg %Z %Sm %N%SY"
60 #define LSF_FORMAT "%Sp %l %Su %Sg %Z %Sm %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 st_birthtime=%B " \
65 "st_blksize=%k st_blocks=%b st_flags=%f"
66 #define LINUX_FORMAT \
67 " File: \"%N\"%n" \
68 " Size: %-11z FileType: %HT%n" \
69 " Mode: (%OMp%03OLp/%.10Sp) Uid: (%5u/%8Su) Gid: (%5g/%8Sg)%n" \
70 "Device: %Hd,%Ld Inode: %i Links: %l%n" \
71 "Access: %Sa%n" \
72 "Modify: %Sm%n" \
73 "Change: %Sc%n" \
74 " Birth: %SB"
75
76 #define TIME_FORMAT "%b %e %T %Y"
77
78 #define FLAG_POUND 0x01
79 #define FLAG_SPACE 0x02
80 #define FLAG_PLUS 0x04
81 #define FLAG_ZERO 0x08
82 #define FLAG_MINUS 0x10
83
84 /*
85 * These format characters must all be unique, except the magic one.
86 */
87 #define FMT_MAGIC '%'
88 #define FMT_DOT '.'
89
90 #define SIMPLE_NEWLINE 'n'
91 #define SIMPLE_TAB 't'
92 #define SIMPLE_PERCENT '%'
93 #define SIMPLE_NUMBER '@'
94
95 #define FMT_POUND '#'
96 #define FMT_SPACE ' '
97 #define FMT_PLUS '+'
98 #define FMT_ZERO '0'
99 #define FMT_MINUS '-'
100
101 #define FMT_DECIMAL 'D'
102 #define FMT_OCTAL 'O'
103 #define FMT_UNSIGNED 'U'
104 #define FMT_HEX 'X'
105 #define FMT_FLOAT 'F'
106 #define FMT_STRING 'S'
107
108 #define FMTF_DECIMAL 0x01
109 #define FMTF_OCTAL 0x02
110 #define FMTF_UNSIGNED 0x04
111 #define FMTF_HEX 0x08
112 #define FMTF_FLOAT 0x10
113 #define FMTF_STRING 0x20
114
115 #define HIGH_PIECE 'H'
116 #define MIDDLE_PIECE 'M'
117 #define LOW_PIECE 'L'
118
119 #define SHOW_realpath 'R'
120 #define SHOW_st_dev 'd'
121 #define SHOW_st_ino 'i'
122 #define SHOW_st_mode 'p'
123 #define SHOW_st_nlink 'l'
124 #define SHOW_st_uid 'u'
125 #define SHOW_st_gid 'g'
126 #define SHOW_st_rdev 'r'
127 #define SHOW_st_atime 'a'
128 #define SHOW_st_mtime 'm'
129 #define SHOW_st_ctime 'c'
130 #define SHOW_st_btime 'B'
131 #define SHOW_st_size 'z'
132 #define SHOW_st_blocks 'b'
133 #define SHOW_st_blksize 'k'
134 #define SHOW_st_flags 'f'
135 #define SHOW_st_gen 'v'
136 #define SHOW_symlink 'Y'
137 #define SHOW_filetype 'T'
138 #define SHOW_filename 'N'
139 #define SHOW_sizerdev 'Z'
140
141 static void usage(const char *);
142 static void output(const struct stat *, const char *, const char *, int);
143 static int format1(const struct stat *, /* stat info */
144 const char *, /* the file name */
145 const char *, int, /* the format string itself */
146 char *, size_t, /* a place to put the output */
147 int, int, int, int, /* the parsed format */
148 int, int);
149 static int hex2byte(const char [2]);
150 static char *xfflagstostr(unsigned long);
151 static int fdlistholes(int, const char *);
152 static int listholes(const char *);
153
154 static const char *timefmt;
155 static int linkfail;
156 static bool nonl;
157
158 #define addchar(s, c, nl) \
159 do { \
160 (void)fputc((c), (s)); \
161 (*nl) = ((c) == '\n'); \
162 } while (0/*CONSTCOND*/)
163
164 int
main(int argc,char * argv[])165 main(int argc, char *argv[])
166 {
167 struct stat st;
168 char dname[sizeof(_PATH_DEV) + SPECNAMELEN] = _PATH_DEV;
169 const char *statfmt, *options, *synopsis;
170 const char *file;
171 fhandle_t fhnd;
172 int ch, rc, errs, am_readlink, fn, fmtchar;
173 bool lsF, holes, usestat, nfs_handle, quiet;
174
175 am_readlink = 0;
176 errs = 0;
177 lsF = false;
178 fmtchar = '\0';
179 holes = false;
180 usestat = false;
181 nfs_handle = false;
182 nonl = false;
183 quiet = false;
184 linkfail = 0;
185 statfmt = NULL;
186 timefmt = NULL;
187
188 if (strcmp(getprogname(), "readlink") == 0) {
189 am_readlink = 1;
190 options = "fn";
191 synopsis = "[-fn] [file ...]";
192 statfmt = "%Y";
193 fmtchar = 'f';
194 quiet = 1;
195 } else {
196 options = "Ff:HhLlnqrst:x";
197 synopsis = "[-FHhLnq] [-f format | -l | -r | -s | -x] "
198 "[-t timefmt] [file|handle ...]";
199 }
200
201 while ((ch = getopt(argc, argv, options)) != -1)
202 switch (ch) {
203 case 'F':
204 lsF = true;
205 break;
206 case 'H':
207 nfs_handle = true;
208 break;
209 case 'h':
210 holes = true;
211 break;
212 case 'L':
213 usestat = true;
214 break;
215 case 'n':
216 nonl = true;
217 break;
218 case 't':
219 timefmt = optarg;
220 break;
221 case 'q':
222 quiet = true;
223 break;
224 /* remaining cases are purposefully out of order */
225 case 'f':
226 if (am_readlink) {
227 statfmt = "%R";
228 break;
229 }
230 statfmt = optarg;
231 /* FALLTHROUGH */
232 case 'l':
233 case 'r':
234 case 's':
235 case 'x':
236 if (fmtchar != 0)
237 errx(1, "can't use format '%c' with '%c'",
238 fmtchar, ch);
239 fmtchar = ch;
240 break;
241 default:
242 usage(synopsis);
243 }
244
245 argc -= optind;
246 argv += optind;
247 fn = 1;
248
249 if (holes) {
250 if (fmtchar || lsF || nfs_handle || usestat || timefmt)
251 usage(synopsis);
252 if (argc > 0) {
253 while (argc-- > 0) {
254 if (listholes(*argv) != 0) {
255 if (!quiet)
256 warn("%s", *argv);
257 errs++;
258 }
259 argv++;
260 }
261 } else {
262 if (fdlistholes(STDIN_FILENO, "stdin") != 0) {
263 if (!quiet)
264 warn("stdin");
265 errs++;
266 }
267 }
268 exit(errs ? 1 : 0);
269 }
270
271 if (fmtchar == '\0') {
272 if (lsF)
273 fmtchar = 'l';
274 else {
275 fmtchar = 'f';
276 statfmt = DEF_FORMAT;
277 }
278 }
279
280 if (lsF && fmtchar != 'l')
281 errx(1, "can't use format '%c' with -F", fmtchar);
282
283 switch (fmtchar) {
284 case 'f':
285 /* statfmt already set */
286 break;
287 case 'l':
288 statfmt = lsF ? LSF_FORMAT : LS_FORMAT;
289 break;
290 case 'r':
291 statfmt = RAW_FORMAT;
292 break;
293 case 's':
294 statfmt = SHELL_FORMAT;
295 break;
296 case 'x':
297 statfmt = LINUX_FORMAT;
298 if (timefmt == NULL)
299 timefmt = "%c";
300 break;
301 default:
302 usage(synopsis);
303 /*NOTREACHED*/
304 }
305
306 if (timefmt == NULL)
307 timefmt = TIME_FORMAT;
308
309 do {
310 if (argc == 0) {
311 if (fdevname_r(STDIN_FILENO, dname +
312 sizeof _PATH_DEV - 1, SPECNAMELEN) != NULL)
313 file = dname;
314 else
315 file = "(stdin)";
316 rc = fstat(STDIN_FILENO, &st);
317 } else {
318 int j;
319
320 file = argv[0];
321 if (nfs_handle) {
322 rc = 0;
323 bzero(&fhnd, sizeof(fhnd));
324 j = MIN(2 * sizeof(fhnd), strlen(file));
325 if ((j & 1) != 0) {
326 rc = -1;
327 } else {
328 while (j) {
329 rc = hex2byte(&file[j - 2]);
330 if (rc == -1)
331 break;
332 ((char*) &fhnd)[j / 2 - 1] = rc;
333 j -= 2;
334 }
335 }
336 if (rc == -1)
337 errno = EINVAL;
338 else
339 rc = fhstat(&fhnd, &st);
340
341 } else if (usestat) {
342 /*
343 * Try stat() and if it fails, fall back to
344 * lstat() just in case we're examining a
345 * broken symlink.
346 */
347 if ((rc = stat(file, &st)) == -1 &&
348 errno == ENOENT &&
349 (rc = lstat(file, &st)) == -1)
350 errno = ENOENT;
351 } else
352 rc = lstat(file, &st);
353 }
354
355 if (rc == -1) {
356 errs = 1;
357 linkfail = 1;
358 if (!quiet)
359 warn("%s", file);
360 } else
361 output(&st, file, statfmt, fn);
362
363 argv++;
364 argc--;
365 fn++;
366 } while (argc > 0);
367
368 return (am_readlink ? linkfail : errs);
369 }
370
371 /*
372 * fflagstostr() wrapper that leaks only once
373 */
374 static char *
xfflagstostr(unsigned long fflags)375 xfflagstostr(unsigned long fflags)
376 {
377 static char *str = NULL;
378
379 if (str != NULL)
380 free(str);
381
382 str = fflagstostr(fflags);
383 if (str == NULL)
384 err(1, "fflagstostr");
385 return (str);
386 }
387
388 static void
usage(const char * synopsis)389 usage(const char *synopsis)
390 {
391 (void)fprintf(stderr, "usage: %s %s\n", getprogname(), synopsis);
392 exit(1);
393 }
394
395 /*
396 * Parses a format string.
397 */
398 static void
output(const struct stat * st,const char * file,const char * statfmt,int fn)399 output(const struct stat *st, const char *file, const char *statfmt, int fn)
400 {
401 int flags, size, prec, ofmt, hilo, what;
402 char buf[PATH_MAX + 4 + 1];
403 const char *subfmt;
404 int nl, t, i;
405
406 nl = 1;
407 while (*statfmt != '\0') {
408
409 /*
410 * Non-format characters go straight out.
411 */
412 if (*statfmt != FMT_MAGIC) {
413 addchar(stdout, *statfmt, &nl);
414 statfmt++;
415 continue;
416 }
417
418 /*
419 * The current format "substring" starts here,
420 * and then we skip the magic.
421 */
422 subfmt = statfmt;
423 statfmt++;
424
425 /*
426 * Some simple one-character "formats".
427 */
428 switch (*statfmt) {
429 case SIMPLE_NEWLINE:
430 addchar(stdout, '\n', &nl);
431 statfmt++;
432 continue;
433 case SIMPLE_TAB:
434 addchar(stdout, '\t', &nl);
435 statfmt++;
436 continue;
437 case SIMPLE_PERCENT:
438 addchar(stdout, '%', &nl);
439 statfmt++;
440 continue;
441 case SIMPLE_NUMBER: {
442 char num[12], *p;
443
444 snprintf(num, sizeof(num), "%d", fn);
445 for (p = &num[0]; *p; p++)
446 addchar(stdout, *p, &nl);
447 statfmt++;
448 continue;
449 }
450 }
451
452 /*
453 * This must be an actual format string. Format strings are
454 * similar to printf(3) formats up to a point, and are of
455 * the form:
456 *
457 * % required start of format
458 * [-# +0] opt. format characters
459 * size opt. field width
460 * . opt. decimal separator, followed by
461 * prec opt. precision
462 * fmt opt. output specifier (string, numeric, etc.)
463 * sub opt. sub field specifier (high, middle, low)
464 * datum required field specifier (size, mode, etc)
465 *
466 * Only the % and the datum selector are required. All data
467 * have reasonable default output forms. The "sub" specifier
468 * only applies to certain data (mode, dev, rdev, filetype).
469 * The symlink output defaults to STRING, yet will only emit
470 * the leading " -> " if STRING is explicitly specified. The
471 * sizerdev datum will generate rdev output for character or
472 * block devices, and size output for all others.
473 */
474 flags = 0;
475 do {
476 if (*statfmt == FMT_POUND)
477 flags |= FLAG_POUND;
478 else if (*statfmt == FMT_SPACE)
479 flags |= FLAG_SPACE;
480 else if (*statfmt == FMT_PLUS)
481 flags |= FLAG_PLUS;
482 else if (*statfmt == FMT_ZERO)
483 flags |= FLAG_ZERO;
484 else if (*statfmt == FMT_MINUS)
485 flags |= FLAG_MINUS;
486 else
487 break;
488 statfmt++;
489 } while (1/*CONSTCOND*/);
490
491 size = -1;
492 if (isdigit((unsigned)*statfmt)) {
493 size = 0;
494 while (isdigit((unsigned)*statfmt)) {
495 size = (size * 10) + (*statfmt - '0');
496 statfmt++;
497 if (size < 0)
498 goto badfmt;
499 }
500 }
501
502 prec = -1;
503 if (*statfmt == FMT_DOT) {
504 statfmt++;
505
506 prec = 0;
507 while (isdigit((unsigned)*statfmt)) {
508 prec = (prec * 10) + (*statfmt - '0');
509 statfmt++;
510 if (prec < 0)
511 goto badfmt;
512 }
513 }
514
515 #define fmtcase(x, y) case (y): (x) = (y); statfmt++; break
516 #define fmtcasef(x, y, z) case (y): (x) = (z); statfmt++; break
517 switch (*statfmt) {
518 fmtcasef(ofmt, FMT_DECIMAL, FMTF_DECIMAL);
519 fmtcasef(ofmt, FMT_OCTAL, FMTF_OCTAL);
520 fmtcasef(ofmt, FMT_UNSIGNED, FMTF_UNSIGNED);
521 fmtcasef(ofmt, FMT_HEX, FMTF_HEX);
522 fmtcasef(ofmt, FMT_FLOAT, FMTF_FLOAT);
523 fmtcasef(ofmt, FMT_STRING, FMTF_STRING);
524 default:
525 ofmt = 0;
526 break;
527 }
528
529 switch (*statfmt) {
530 fmtcase(hilo, HIGH_PIECE);
531 fmtcase(hilo, MIDDLE_PIECE);
532 fmtcase(hilo, LOW_PIECE);
533 default:
534 hilo = 0;
535 break;
536 }
537
538 switch (*statfmt) {
539 fmtcase(what, SHOW_realpath);
540 fmtcase(what, SHOW_st_dev);
541 fmtcase(what, SHOW_st_ino);
542 fmtcase(what, SHOW_st_mode);
543 fmtcase(what, SHOW_st_nlink);
544 fmtcase(what, SHOW_st_uid);
545 fmtcase(what, SHOW_st_gid);
546 fmtcase(what, SHOW_st_rdev);
547 fmtcase(what, SHOW_st_atime);
548 fmtcase(what, SHOW_st_mtime);
549 fmtcase(what, SHOW_st_ctime);
550 fmtcase(what, SHOW_st_btime);
551 fmtcase(what, SHOW_st_size);
552 fmtcase(what, SHOW_st_blocks);
553 fmtcase(what, SHOW_st_blksize);
554 fmtcase(what, SHOW_st_flags);
555 fmtcase(what, SHOW_st_gen);
556 fmtcase(what, SHOW_symlink);
557 fmtcase(what, SHOW_filetype);
558 fmtcase(what, SHOW_filename);
559 fmtcase(what, SHOW_sizerdev);
560 default:
561 goto badfmt;
562 }
563 #undef fmtcasef
564 #undef fmtcase
565
566 t = format1(st,
567 file,
568 subfmt, statfmt - subfmt,
569 buf, sizeof(buf),
570 flags, size, prec, ofmt, hilo, what);
571
572 for (i = 0; i < t && i < (int)(sizeof(buf) - 1); i++)
573 addchar(stdout, buf[i], &nl);
574
575 continue;
576
577 badfmt:
578 errx(1, "%.*s: bad format",
579 (int)(statfmt - subfmt + 1), subfmt);
580 }
581
582 if (!nl && !nonl)
583 (void)fputc('\n', stdout);
584 (void)fflush(stdout);
585 }
586
587 /*
588 * Arranges output according to a single parsed format substring.
589 */
590 static int
format1(const struct stat * st,const char * file,const char * fmt,int flen,char * buf,size_t blen,int flags,int size,int prec,int ofmt,int hilo,int what)591 format1(const struct stat *st,
592 const char *file,
593 const char *fmt, int flen,
594 char *buf, size_t blen,
595 int flags, int size, int prec, int ofmt,
596 int hilo, int what)
597 {
598 u_int64_t data;
599 char *stmp, lfmt[24], tmp[20];
600 const char *sdata;
601 char smode[12], sid[12], path[PATH_MAX + 4];
602 const struct timespec *tsp;
603 struct timespec ts;
604 struct tm *tm;
605 int l, small, formats;
606 mode_t dtype;
607
608 tsp = NULL;
609 formats = 0;
610 small = 0;
611
612 /*
613 * First, pick out the data and tweak it based on hilo or
614 * specified output format (symlink output only).
615 */
616 switch (what) {
617 case SHOW_st_dev:
618 case SHOW_st_rdev:
619 small = (sizeof(st->st_dev) == 4);
620 switch (what) {
621 case SHOW_st_dev:
622 data = st->st_dev;
623 dtype = S_IFCHR;
624 break;
625 case SHOW_st_rdev:
626 data = st->st_rdev;
627 dtype = st->st_mode & (S_IFCHR | S_IFBLK);
628 break;
629 }
630 sdata = devname(data, dtype);
631 if (hilo == HIGH_PIECE) {
632 data = major(data);
633 hilo = 0;
634 }
635 else if (hilo == LOW_PIECE) {
636 data = minor((unsigned)data);
637 hilo = 0;
638 }
639 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX |
640 FMTF_STRING;
641 if (ofmt == 0)
642 ofmt = FMTF_UNSIGNED;
643 break;
644 case SHOW_st_ino:
645 small = (sizeof(st->st_ino) == 4);
646 data = st->st_ino;
647 sdata = NULL;
648 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX;
649 if (ofmt == 0)
650 ofmt = FMTF_UNSIGNED;
651 break;
652 case SHOW_st_mode:
653 small = (sizeof(st->st_mode) == 4);
654 data = st->st_mode;
655 strmode(st->st_mode, smode);
656 stmp = smode;
657 l = strlen(stmp);
658 if (stmp[l - 1] == ' ')
659 stmp[--l] = '\0';
660 if (hilo == HIGH_PIECE) {
661 data >>= 12;
662 stmp += 1;
663 stmp[3] = '\0';
664 hilo = 0;
665 }
666 else if (hilo == MIDDLE_PIECE) {
667 data = (data >> 9) & 07;
668 stmp += 4;
669 stmp[3] = '\0';
670 hilo = 0;
671 }
672 else if (hilo == LOW_PIECE) {
673 data &= 0777;
674 stmp += 7;
675 stmp[3] = '\0';
676 hilo = 0;
677 }
678 sdata = stmp;
679 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX |
680 FMTF_STRING;
681 if (ofmt == 0)
682 ofmt = FMTF_OCTAL;
683 break;
684 case SHOW_st_nlink:
685 small = (sizeof(st->st_dev) == 4);
686 data = st->st_nlink;
687 sdata = NULL;
688 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX;
689 if (ofmt == 0)
690 ofmt = FMTF_UNSIGNED;
691 break;
692 case SHOW_st_uid:
693 small = (sizeof(st->st_uid) == 4);
694 data = st->st_uid;
695 sdata = user_from_uid(st->st_uid, 1);
696 if (sdata == NULL) {
697 snprintf(sid, sizeof(sid), "(%ld)", (long)st->st_uid);
698 sdata = sid;
699 }
700 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX |
701 FMTF_STRING;
702 if (ofmt == 0)
703 ofmt = FMTF_UNSIGNED;
704 break;
705 case SHOW_st_gid:
706 small = (sizeof(st->st_gid) == 4);
707 data = st->st_gid;
708 sdata = group_from_gid(st->st_gid, 1);
709 if (sdata == NULL) {
710 snprintf(sid, sizeof(sid), "(%ld)", (long)st->st_gid);
711 sdata = sid;
712 }
713 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX |
714 FMTF_STRING;
715 if (ofmt == 0)
716 ofmt = FMTF_UNSIGNED;
717 break;
718 case SHOW_st_atime:
719 tsp = &st->st_atimespec;
720 /* FALLTHROUGH */
721 case SHOW_st_mtime:
722 if (tsp == NULL)
723 tsp = &st->st_mtimespec;
724 /* FALLTHROUGH */
725 case SHOW_st_ctime:
726 if (tsp == NULL)
727 tsp = &st->st_ctimespec;
728 /* FALLTHROUGH */
729 case SHOW_st_btime:
730 if (tsp == NULL)
731 tsp = &st->st_birthtimespec;
732 ts = *tsp; /* copy so we can muck with it */
733 small = (sizeof(ts.tv_sec) == 4);
734 data = ts.tv_sec;
735 tm = localtime(&ts.tv_sec);
736 if (tm == NULL) {
737 ts.tv_sec = 0;
738 tm = localtime(&ts.tv_sec);
739 }
740 (void)setlocale(LC_TIME, "");
741 (void)strftime(path, sizeof(path), timefmt, tm);
742 sdata = path;
743 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX |
744 FMTF_FLOAT | FMTF_STRING;
745 if (ofmt == 0)
746 ofmt = FMTF_DECIMAL;
747 break;
748 case SHOW_st_size:
749 small = (sizeof(st->st_size) == 4);
750 data = st->st_size;
751 sdata = NULL;
752 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX;
753 if (ofmt == 0)
754 ofmt = FMTF_UNSIGNED;
755 break;
756 case SHOW_st_blocks:
757 small = (sizeof(st->st_blocks) == 4);
758 data = st->st_blocks;
759 sdata = NULL;
760 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX;
761 if (ofmt == 0)
762 ofmt = FMTF_UNSIGNED;
763 break;
764 case SHOW_st_blksize:
765 small = (sizeof(st->st_blksize) == 4);
766 data = st->st_blksize;
767 sdata = NULL;
768 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX;
769 if (ofmt == 0)
770 ofmt = FMTF_UNSIGNED;
771 break;
772 case SHOW_st_flags:
773 small = (sizeof(st->st_flags) == 4);
774 data = st->st_flags;
775 sdata = xfflagstostr(st->st_flags);
776 if (*sdata == '\0')
777 sdata = "-";
778 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX |
779 FMTF_STRING;
780 if (ofmt == 0)
781 ofmt = FMTF_UNSIGNED;
782 break;
783 case SHOW_st_gen:
784 small = (sizeof(st->st_gen) == 4);
785 data = st->st_gen;
786 sdata = NULL;
787 formats = FMTF_DECIMAL | FMTF_OCTAL | FMTF_UNSIGNED | FMTF_HEX;
788 if (ofmt == 0)
789 ofmt = FMTF_UNSIGNED;
790 break;
791 case SHOW_realpath:
792 small = 0;
793 data = 0;
794 if (file == NULL) {
795 (void)strlcpy(path, "(stdin)", sizeof(path));
796 sdata = path;
797 } else {
798 snprintf(path, sizeof(path), " -> ");
799 if (realpath(file, path + 4) == NULL) {
800 linkfail = 1;
801 l = 0;
802 path[0] = '\0';
803 }
804 sdata = path + (ofmt == FMTF_STRING ? 0 : 4);
805 }
806
807 formats = FMTF_STRING;
808 if (ofmt == 0)
809 ofmt = FMTF_STRING;
810 break;
811 case SHOW_symlink:
812 small = 0;
813 data = 0;
814 if (S_ISLNK(st->st_mode)) {
815 snprintf(path, sizeof(path), " -> ");
816 l = readlink(file, path + 4, sizeof(path) - 4 - 1);
817 if (l == -1) {
818 linkfail = 1;
819 l = 0;
820 path[0] = '\0';
821 }
822 path[l + 4] = '\0';
823 sdata = path + (ofmt == FMTF_STRING ? 0 : 4);
824 }
825 else {
826 linkfail = 1;
827 sdata = "";
828 }
829 formats = FMTF_STRING;
830 if (ofmt == 0)
831 ofmt = FMTF_STRING;
832 break;
833 case SHOW_filetype:
834 small = 0;
835 data = 0;
836 sdata = "";
837 if (hilo == 0 || hilo == LOW_PIECE) {
838 switch (st->st_mode & S_IFMT) {
839 case S_IFIFO: sdata = "|"; break;
840 case S_IFDIR: sdata = "/"; break;
841 case S_IFREG:
842 if (st->st_mode &
843 (S_IXUSR | S_IXGRP | S_IXOTH))
844 sdata = "*";
845 break;
846 case S_IFLNK: sdata = "@"; break;
847 case S_IFSOCK: sdata = "="; break;
848 #ifdef S_IFWHT
849 case S_IFWHT: sdata = "%"; break;
850 #endif /* S_IFWHT */
851 #ifdef S_IFDOOR
852 case S_IFDOOR: sdata = ">"; break;
853 #endif /* S_IFDOOR */
854 }
855 hilo = 0;
856 }
857 else if (hilo == HIGH_PIECE) {
858 switch (st->st_mode & S_IFMT) {
859 case S_IFIFO: sdata = "Fifo File"; break;
860 case S_IFCHR: sdata = "Character Device"; break;
861 case S_IFDIR: sdata = "Directory"; break;
862 case S_IFBLK: sdata = "Block Device"; break;
863 case S_IFREG: sdata = "Regular File"; break;
864 case S_IFLNK: sdata = "Symbolic Link"; break;
865 case S_IFSOCK: sdata = "Socket"; break;
866 #ifdef S_IFWHT
867 case S_IFWHT: sdata = "Whiteout File"; break;
868 #endif /* S_IFWHT */
869 #ifdef S_IFDOOR
870 case S_IFDOOR: sdata = "Door"; break;
871 #endif /* S_IFDOOR */
872 default: sdata = "???"; break;
873 }
874 hilo = 0;
875 }
876 formats = FMTF_STRING;
877 if (ofmt == 0)
878 ofmt = FMTF_STRING;
879 break;
880 case SHOW_filename:
881 small = 0;
882 data = 0;
883 (void)strlcpy(path, file, sizeof(path));
884 sdata = path;
885 formats = FMTF_STRING;
886 if (ofmt == 0)
887 ofmt = FMTF_STRING;
888 break;
889 case SHOW_sizerdev:
890 if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) {
891 char majdev[20], mindev[20];
892 int l1, l2;
893
894 l1 = format1(st,
895 file,
896 fmt, flen,
897 majdev, sizeof(majdev),
898 flags, size, prec,
899 ofmt, HIGH_PIECE, SHOW_st_rdev);
900 l2 = format1(st,
901 file,
902 fmt, flen,
903 mindev, sizeof(mindev),
904 flags, size, prec,
905 ofmt, LOW_PIECE, SHOW_st_rdev);
906 return (snprintf(buf, blen, "%.*s,%.*s",
907 l1, majdev, l2, mindev));
908 }
909 else {
910 return (format1(st,
911 file,
912 fmt, flen,
913 buf, blen,
914 flags, size, prec,
915 ofmt, 0, SHOW_st_size));
916 }
917 /*NOTREACHED*/
918 default:
919 errx(1, "%.*s: bad format", (int)flen, fmt);
920 }
921
922 /*
923 * If a subdatum was specified but not supported, or an output
924 * format was selected that is not supported, that's an error.
925 */
926 if (hilo != 0 || (ofmt & formats) == 0)
927 errx(1, "%.*s: bad format", (int)flen, fmt);
928
929 /*
930 * Assemble the format string for passing to printf(3).
931 */
932 lfmt[0] = '\0';
933 (void)strcat(lfmt, "%");
934 if (flags & FLAG_POUND)
935 (void)strcat(lfmt, "#");
936 if (flags & FLAG_SPACE)
937 (void)strcat(lfmt, " ");
938 if (flags & FLAG_PLUS)
939 (void)strcat(lfmt, "+");
940 if (flags & FLAG_MINUS)
941 (void)strcat(lfmt, "-");
942 if (flags & FLAG_ZERO)
943 (void)strcat(lfmt, "0");
944
945 /*
946 * Only the timespecs support the FLOAT output format, and that
947 * requires work that differs from the other formats.
948 */
949 if (ofmt == FMTF_FLOAT) {
950 /*
951 * Nothing after the decimal point, so just print seconds.
952 */
953 if (prec == 0) {
954 if (size != -1) {
955 (void)snprintf(tmp, sizeof(tmp), "%d", size);
956 (void)strcat(lfmt, tmp);
957 }
958 (void)strcat(lfmt, "lld");
959 return (snprintf(buf, blen, lfmt,
960 (long long)ts.tv_sec));
961 }
962
963 /*
964 * Unspecified precision gets all the precision we have:
965 * 9 digits.
966 */
967 if (prec == -1)
968 prec = 9;
969
970 /*
971 * Adjust the size for the decimal point and the digits
972 * that will follow.
973 */
974 size -= prec + 1;
975
976 /*
977 * Any leftover size that's legitimate will be used.
978 */
979 if (size > 0) {
980 (void)snprintf(tmp, sizeof(tmp), "%d", size);
981 (void)strcat(lfmt, tmp);
982 }
983 /* Seconds: time_t cast to long long. */
984 (void)strcat(lfmt, "lld");
985
986 /*
987 * The stuff after the decimal point always needs zero
988 * filling.
989 */
990 (void)strcat(lfmt, ".%0");
991
992 /*
993 * We can "print" at most nine digits of precision. The
994 * rest we will pad on at the end.
995 *
996 * Nanoseconds: long.
997 */
998 (void)snprintf(tmp, sizeof(tmp), "%dld", MIN(prec, 9));
999 (void)strcat(lfmt, tmp);
1000
1001 /*
1002 * For precision of less than nine digits, trim off the
1003 * less significant figures.
1004 */
1005 for (; prec < 9; prec++)
1006 ts.tv_nsec /= 10;
1007
1008 /*
1009 * Use the format, and then tack on any zeroes that
1010 * might be required to make up the requested precision.
1011 */
1012 l = snprintf(buf, blen, lfmt, (long long)ts.tv_sec, ts.tv_nsec);
1013 for (; prec > 9 && l < (int)blen; prec--, l++)
1014 (void)strcat(buf, "0");
1015 return (l);
1016 }
1017
1018 /*
1019 * Add on size and precision, if specified, to the format.
1020 */
1021 if (size != -1) {
1022 (void)snprintf(tmp, sizeof(tmp), "%d", size);
1023 (void)strcat(lfmt, tmp);
1024 }
1025 if (prec != -1) {
1026 (void)snprintf(tmp, sizeof(tmp), ".%d", prec);
1027 (void)strcat(lfmt, tmp);
1028 }
1029
1030 /*
1031 * String output uses the temporary sdata.
1032 */
1033 if (ofmt == FMTF_STRING) {
1034 if (sdata == NULL)
1035 errx(1, "%.*s: bad format", (int)flen, fmt);
1036 (void)strcat(lfmt, "s");
1037 return (snprintf(buf, blen, lfmt, sdata));
1038 }
1039
1040 /*
1041 * Ensure that sign extension does not cause bad looking output
1042 * for some forms.
1043 */
1044 if (small && ofmt != FMTF_DECIMAL)
1045 data = (u_int32_t)data;
1046
1047 /*
1048 * The four "numeric" output forms.
1049 */
1050 (void)strcat(lfmt, "ll");
1051 switch (ofmt) {
1052 case FMTF_DECIMAL: (void)strcat(lfmt, "d"); break;
1053 case FMTF_OCTAL: (void)strcat(lfmt, "o"); break;
1054 case FMTF_UNSIGNED: (void)strcat(lfmt, "u"); break;
1055 case FMTF_HEX: (void)strcat(lfmt, "x"); break;
1056 }
1057
1058 return (snprintf(buf, blen, lfmt, data));
1059 }
1060
1061
1062 #define hex2nibble(c) (c <= '9' ? c - '0' : toupper(c) - 'A' + 10)
1063 static int
hex2byte(const char c[2])1064 hex2byte(const char c[2]) {
1065 if (!(ishexnumber(c[0]) && ishexnumber(c[1])))
1066 return -1;
1067 return (hex2nibble(c[0]) << 4) + hex2nibble(c[1]);
1068 }
1069
1070 static int
fdlistholes(int fd,const char * fn)1071 fdlistholes(int fd, const char *fn)
1072 {
1073 struct stat sb;
1074 off_t pos = 0, off;
1075 long l;
1076
1077 if (fstat(fd, &sb) < 0)
1078 return (-1);
1079 if (S_ISDIR(sb.st_mode)) {
1080 if ((l = fpathconf(fd, _PC_MIN_HOLE_SIZE)) < 0)
1081 return (-1);
1082 printf("%ld", l);
1083 } else if (!S_ISREG(sb.st_mode)) {
1084 errno = ESPIPE;
1085 return (-1);
1086 } else {
1087 for (;;) {
1088 if ((off = lseek(fd, pos, SEEK_HOLE)) < 0) {
1089 if (errno != ENXIO)
1090 return (-1);
1091 /*
1092 * This can only happen if the file was
1093 * truncated while we were scanning it, or
1094 * on the initial seek if the file is
1095 * empty. Report the virtual hole at the
1096 * end of the file at this position.
1097 */
1098 off = pos;
1099 }
1100 printf("%jd", (intmax_t)off);
1101 pos = off;
1102 if ((off = lseek(fd, pos, SEEK_DATA)) < 0) {
1103 if (errno != ENXIO)
1104 return (-1);
1105 /*
1106 * There are no more data regions in the
1107 * file, or it got truncated. However, we
1108 * may not be at the end yet.
1109 */
1110 if ((off = lseek(fd, 0, SEEK_END)) > pos)
1111 printf("-%jd", (intmax_t)off - 1);
1112 break;
1113 }
1114 printf("-%jd,", (intmax_t)off - 1);
1115 pos = off;
1116 }
1117 }
1118 printf(" %s", fn);
1119 if (!nonl)
1120 printf("\n");
1121 return (0);
1122 }
1123
1124 static int
listholes(const char * fn)1125 listholes(const char *fn)
1126 {
1127 int fd, ret;
1128
1129 if ((fd = open(fn, O_RDONLY)) < 0)
1130 return (-1);
1131 ret = fdlistholes(fd, fn);
1132 close(fd);
1133 return (ret);
1134 }
1135