aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/stat/stat.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/stat/stat.c')
-rw-r--r--usr.bin/stat/stat.c176
1 files changed, 135 insertions, 41 deletions
diff --git a/usr.bin/stat/stat.c b/usr.bin/stat/stat.c
index 1fd8288728c1..0ed5d3ae5b53 100644
--- a/usr.bin/stat/stat.c
+++ b/usr.bin/stat/stat.c
@@ -7,6 +7,8 @@
* This code is derived from software contributed to The NetBSD Foundation
* by Andrew Brown.
*
+ * Copyright (c) 2025 Klara, Inc.
+ *
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@@ -47,18 +49,19 @@ __RCSID("$NetBSD: stat.c,v 1.33 2011/01/15 22:54:10 njoly Exp $"
#endif /* HAVE_CONFIG_H */
#include <sys/param.h>
-#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
+#include <fcntl.h>
#include <grp.h>
#include <limits.h>
#include <locale.h>
#include <paths.h>
#include <pwd.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -178,22 +181,24 @@ __RCSID("$NetBSD: stat.c,v 1.33 2011/01/15 22:54:10 njoly Exp $"
#define SHOW_filename 'N'
#define SHOW_sizerdev 'Z'
-void usage(const char *);
-void output(const struct stat *, const char *,
- const char *, int, int);
-int format1(const struct stat *, /* stat info */
+static void usage(const char *);
+static void output(const struct stat *, const char *, const char *, int);
+static int format1(const struct stat *, /* stat info */
const char *, /* the file name */
const char *, int, /* the format string itself */
char *, size_t, /* a place to put the output */
int, int, int, int, /* the parsed format */
int, int);
-int hex2byte(const char [2]);
+static int hex2byte(const char [2]);
#if HAVE_STRUCT_STAT_ST_FLAGS
-char *xfflagstostr(unsigned long);
+static char *xfflagstostr(unsigned long);
#endif
+static int fdlistholes(int, const char *);
+static int listholes(const char *);
static const char *timefmt;
static int linkfail;
+static bool nonl;
#define addchar(s, c, nl) \
do { \
@@ -205,20 +210,22 @@ int
main(int argc, char *argv[])
{
struct stat st;
- int ch, rc, errs, am_readlink;
- int lsF, fmtchar, usestat, nfs_handle, fn, nonl, quiet;
- const char *statfmt, *options, *synopsis;
char dname[sizeof _PATH_DEV + SPECNAMELEN] = _PATH_DEV;
- fhandle_t fhnd;
+ const char *statfmt, *options, *synopsis;
const char *file;
+ fhandle_t fhnd;
+ int ch, rc, errs, am_readlink, fn, fmtchar;
+ bool lsF, holes, usestat, nfs_handle, quiet;
am_readlink = 0;
- lsF = 0;
+ errs = 0;
+ lsF = false;
fmtchar = '\0';
- usestat = 0;
- nfs_handle = 0;
- nonl = 0;
- quiet = 0;
+ holes = false;
+ usestat = false;
+ nfs_handle = false;
+ nonl = false;
+ quiet = false;
linkfail = 0;
statfmt = NULL;
timefmt = NULL;
@@ -231,28 +238,35 @@ main(int argc, char *argv[])
fmtchar = 'f';
quiet = 1;
} else {
- options = "f:FHlLnqrst:x";
- synopsis = "[-FLnq] [-f format | -l | -r | -s | -x] "
+ options = "Ff:HhLlnqrst:x";
+ synopsis = "[-FHhLnq] [-f format | -l | -r | -s | -x] "
"[-t timefmt] [file|handle ...]";
}
while ((ch = getopt(argc, argv, options)) != -1)
switch (ch) {
case 'F':
- lsF = 1;
+ lsF = true;
break;
case 'H':
- nfs_handle = 1;
+ nfs_handle = true;
+ break;
+ case 'h':
+ holes = true;
break;
case 'L':
- usestat = 1;
+ usestat = true;
break;
case 'n':
- nonl = 1;
+ nonl = true;
+ break;
+ case 't':
+ timefmt = optarg;
break;
case 'q':
- quiet = 1;
+ quiet = true;
break;
+ /* remaining cases are purposefully out of order */
case 'f':
if (am_readlink) {
statfmt = "%R";
@@ -269,9 +283,6 @@ main(int argc, char *argv[])
fmtchar, ch);
fmtchar = ch;
break;
- case 't':
- timefmt = optarg;
- break;
default:
usage(synopsis);
}
@@ -280,6 +291,28 @@ main(int argc, char *argv[])
argv += optind;
fn = 1;
+ if (holes) {
+ if (fmtchar || lsF || nfs_handle || usestat || timefmt)
+ usage(synopsis);
+ if (argc > 0) {
+ while (argc-- > 0) {
+ if (listholes(*argv) != 0) {
+ if (!quiet)
+ warn("%s", *argv);
+ errs++;
+ }
+ argv++;
+ }
+ } else {
+ if (fdlistholes(STDIN_FILENO, "stdin") != 0) {
+ if (!quiet)
+ warn("stdin");
+ errs++;
+ }
+ }
+ exit(errs ? 1 : 0);
+ }
+
if (fmtchar == '\0') {
if (lsF)
fmtchar = 'l';
@@ -318,7 +351,6 @@ main(int argc, char *argv[])
if (timefmt == NULL)
timefmt = TIME_FORMAT;
- errs = 0;
do {
if (argc == 0) {
if (fdevname_r(STDIN_FILENO, dname +
@@ -361,8 +393,7 @@ main(int argc, char *argv[])
errno == ENOENT &&
(rc = lstat(file, &st)) == -1)
errno = ENOENT;
- }
- else
+ } else
rc = lstat(file, &st);
}
@@ -371,9 +402,8 @@ main(int argc, char *argv[])
linkfail = 1;
if (!quiet)
warn("%s", file);
- }
- else
- output(&st, file, statfmt, fn, nonl);
+ } else
+ output(&st, file, statfmt, fn);
argv++;
argc--;
@@ -387,7 +417,7 @@ main(int argc, char *argv[])
/*
* fflagstostr() wrapper that leaks only once
*/
-char *
+static char *
xfflagstostr(unsigned long fflags)
{
static char *str = NULL;
@@ -402,10 +432,9 @@ xfflagstostr(unsigned long fflags)
}
#endif /* HAVE_STRUCT_STAT_ST_FLAGS */
-void
+static void
usage(const char *synopsis)
{
-
(void)fprintf(stderr, "usage: %s %s\n", getprogname(), synopsis);
exit(1);
}
@@ -413,9 +442,8 @@ usage(const char *synopsis)
/*
* Parses a format string.
*/
-void
-output(const struct stat *st, const char *file,
- const char *statfmt, int fn, int nonl)
+static void
+output(const struct stat *st, const char *file, const char *statfmt, int fn)
{
int flags, size, prec, ofmt, hilo, what;
char buf[PATH_MAX + 4 + 1];
@@ -606,7 +634,7 @@ output(const struct stat *st, const char *file,
/*
* Arranges output according to a single parsed format substring.
*/
-int
+static int
format1(const struct stat *st,
const char *file,
const char *fmt, int flen,
@@ -1073,7 +1101,7 @@ format1(const struct stat *st,
(void)strcat(lfmt, "ll");
switch (ofmt) {
case FMTF_DECIMAL: (void)strcat(lfmt, "d"); break;
- case FMTF_OCTAL: (void)strcat(lfmt, "o"); break;
+ case FMTF_OCTAL: (void)strcat(lfmt, "o"); break;
case FMTF_UNSIGNED: (void)strcat(lfmt, "u"); break;
case FMTF_HEX: (void)strcat(lfmt, "x"); break;
}
@@ -1083,9 +1111,75 @@ format1(const struct stat *st,
#define hex2nibble(c) (c <= '9' ? c - '0' : toupper(c) - 'A' + 10)
-int
+static int
hex2byte(const char c[2]) {
if (!(ishexnumber(c[0]) && ishexnumber(c[1])))
return -1;
return (hex2nibble(c[0]) << 4) + hex2nibble(c[1]);
}
+
+static int
+fdlistholes(int fd, const char *fn)
+{
+ struct stat sb;
+ off_t pos = 0, off;
+ long l;
+
+ if (fstat(fd, &sb) < 0)
+ return (-1);
+ if (S_ISDIR(sb.st_mode)) {
+ if ((l = fpathconf(fd, _PC_MIN_HOLE_SIZE)) < 0)
+ return (-1);
+ printf("%ld", l);
+ } else if (!S_ISREG(sb.st_mode)) {
+ errno = ESPIPE;
+ return (-1);
+ } else {
+ for (;;) {
+ if ((off = lseek(fd, pos, SEEK_HOLE)) < 0) {
+ if (errno != ENXIO)
+ return (-1);
+ /*
+ * This can only happen if the file was
+ * truncated while we were scanning it, or
+ * on the initial seek if the file is
+ * empty. Report the virtual hole at the
+ * end of the file at this position.
+ */
+ off = pos;
+ }
+ printf("%jd", (intmax_t)off);
+ pos = off;
+ if ((off = lseek(fd, pos, SEEK_DATA)) < 0) {
+ if (errno != ENXIO)
+ return (-1);
+ /*
+ * There are no more data regions in the
+ * file, or it got truncated. However, we
+ * may not be at the end yet.
+ */
+ if ((off = lseek(fd, 0, SEEK_END)) > pos)
+ printf("-%jd", (intmax_t)off - 1);
+ break;
+ }
+ printf("-%jd,", (intmax_t)off - 1);
+ pos = off;
+ }
+ }
+ printf(" %s", fn);
+ if (!nonl)
+ printf("\n");
+ return (0);
+}
+
+static int
+listholes(const char *fn)
+{
+ int fd, ret;
+
+ if ((fd = open(fn, O_RDONLY)) < 0)
+ return (-1);
+ ret = fdlistholes(fd, fn);
+ close(fd);
+ return (ret);
+}