aboutsummaryrefslogtreecommitdiff
path: root/usr.bin
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin')
-rw-r--r--usr.bin/gh-bc/Makefile6
-rw-r--r--usr.bin/gh-bc/tests/Makefile9
-rw-r--r--usr.bin/kyua/Makefile2
-rw-r--r--usr.bin/man/Makefile2
-rw-r--r--usr.bin/man/man.124
-rwxr-xr-xusr.bin/man/man.sh35
-rw-r--r--usr.bin/mandoc/Makefile6
-rw-r--r--usr.bin/mandoc/mandoc.ucl18
-rw-r--r--usr.bin/sockstat/main.c3
-rw-r--r--usr.bin/stat/stat.145
-rw-r--r--usr.bin/stat/stat.c176
-rwxr-xr-xusr.bin/stat/tests/stat_test.sh72
-rw-r--r--usr.bin/w/uptime.118
-rw-r--r--usr.bin/w/w.14
14 files changed, 351 insertions, 69 deletions
diff --git a/usr.bin/gh-bc/Makefile b/usr.bin/gh-bc/Makefile
index 0fdc687c0e50..9aa5116f6faa 100644
--- a/usr.bin/gh-bc/Makefile
+++ b/usr.bin/gh-bc/Makefile
@@ -4,6 +4,7 @@ PROG= gh-bc
PROGNAME= bc
BCDIR= ${SRCTOP}/contrib/${PROGNAME}
+VERSION!= cat ${SRCTOP}/contrib/${PROGNAME}/VERSION.txt
SRCS= args.c bc.c bc_lex.c bc_parse.c data.c dc.c dc_lex.c dc_parse.c file.c history.c
SRCS+= lang.c lex.c main.c num.c opt.c parse.c program.c rand.c read.c vector.c vm.c
@@ -31,6 +32,8 @@ CATALOGS+= zh_CN.UTF-8 zh_CN.eucCN zh_CN.GB18030 zh_CN.GB2312 zh_CN.GBK
NLSNAME= bc
NLSSRCDIR= ${BCDIR}/locales
+CFLAGS+= -I${BCDIR}/include
+
CFLAGS+= -DBC_DEFAULT_BANNER=0
CFLAGS+= -DBC_DEFAULT_DIGIT_CLAMP=0
CFLAGS+= -DBC_DEFAULT_EXPR_EXIT=1
@@ -57,7 +60,8 @@ CFLAGS+= -DBUILD_TYPE=A
CFLAGS+= -DMAINEXEC=${PROGNAME}
CFLAGS+= -DNDEBUG
CFLAGS+= -DNLSPATH=/usr/share/nls/%L/%N.cat
-CFLAGS+= -I${BCDIR}/include
+
+CFLAGS+= -DVERSION=${VERSION}
# prevent floating point incompatibilities caused by -flto on some architectures
.if ${MACHINE_ARCH} != riscv64
diff --git a/usr.bin/gh-bc/tests/Makefile b/usr.bin/gh-bc/tests/Makefile
index 464ae4b5d3c3..f2c92aecb0a5 100644
--- a/usr.bin/gh-bc/tests/Makefile
+++ b/usr.bin/gh-bc/tests/Makefile
@@ -17,7 +17,8 @@ FILESfMODE= 0755
FILESGROUPS+= FILEStests
FILEStestsPACKAGE= ${PACKAGE}
FILEStestsDIR= ${TESTSDIR}/tests
-FILEStests!= echo ${TEST_DIR}/tests/*.py ${TEST_DIR}/tests/*.sh ${TEST_DIR}/tests/*.txt
+FILEStests!= echo ${TEST_DIR}/tests/*.py ${TEST_DIR}/tests/*.sed \
+ ${TEST_DIR}/tests/*.sh ${TEST_DIR}/tests/*.txt
FILEStestsMODE= 0755
FILESGROUPS+= FILESbc
@@ -56,10 +57,12 @@ PLAIN_TESTS_SH= bc_tests dc_tests
bc_tests.sh:
echo "#!/bin/sh" > ${.TARGET}
- echo "env LANG=C ${TESTSDIR}/tests/all.sh bc 1 1 0 0 0 bc" >> ${.TARGET}
+ echo 'env LANG=C BC_TEST_OUTPUT_DIR=$$(pwd) \
+ ${TESTSDIR}/tests/all.sh -n bc 1 1 0 0 bc' >> ${.TARGET}
dc_tests.sh:
echo "#!/bin/sh" > ${.TARGET}
- echo "env LANG=C ${TESTSDIR}/tests/all.sh dc 1 1 0 0 0 dc" >> ${.TARGET}
+ echo "env LANG=C BC_TEST_OUTPUT_DIR=\$pwd \
+ ${TESTSDIR}/tests/all.sh -n dc 1 1 0 0 dc" >> ${.TARGET}
.include <bsd.test.mk>
diff --git a/usr.bin/kyua/Makefile b/usr.bin/kyua/Makefile
index daefedbf8bca..178a1d083b79 100644
--- a/usr.bin/kyua/Makefile
+++ b/usr.bin/kyua/Makefile
@@ -10,7 +10,7 @@ KYUA_VERSION= 0.13
KYUA_SRCDIR= ${SRCTOP}/contrib/kyua
.PATH: ${KYUA_SRCDIR}
-PACKAGE= tests
+PACKAGE= kyua
PROG_CXX= kyua
SRCS= main.cpp
LIBADD= lutok sqlite3 util
diff --git a/usr.bin/man/Makefile b/usr.bin/man/Makefile
index 9c9098270735..9e47006cac0a 100644
--- a/usr.bin/man/Makefile
+++ b/usr.bin/man/Makefile
@@ -1,3 +1,5 @@
+PACKAGE= mandoc
+
SCRIPTS= man.sh
LINKS= ${BINDIR}/man ${BINDIR}/manpath
diff --git a/usr.bin/man/man.1 b/usr.bin/man/man.1
index 820d6a5b33a9..707677ccce06 100644
--- a/usr.bin/man/man.1
+++ b/usr.bin/man/man.1
@@ -33,7 +33,7 @@
.Nd display online manual documentation pages
.Sh SYNOPSIS
.Nm
-.Op Fl adho
+.Op Fl adhlo
.Op Fl t | w
.Op Fl M Ar manpath
.Op Fl P Ar pager
@@ -144,6 +144,15 @@ Search names and descriptions of all manual pages for an extended regular
.Ar expression ,
emulating basic functionality of
.Xr apropos 1 .
+.It Fl l
+Interpret all arguments as absolute or relative filename(s)
+of the manual page(s) to display.
+No search is done and the options
+.Fl M ,
+.Fl m ,
+and
+.Fl S
+are ignored.
.It Fl m Ar arch Ns Op : Ns Ar machine
Override the default architecture and machine settings allowing lookup of
other platform specific manual pages.
@@ -269,12 +278,15 @@ will search the following paths when considering section 4 manual pages in
.Pa /usr/share/man/man4
.El
.Ss Displaying Specific Manual Files
-The
+For compatibility reasons,
.Nm
-utility also supports displaying a specific manual page if passed a path
-to the file as long as it contains a
+will interpret any argument containing at least one
.Ql /
-character.
+character as an absolute or relative path to a manual page to be
+displayed.
+This heuristic, made redundant by the more widely supported
+.Fl l
+option, is now deprecated and may be removed in future releases.
.Sh ENVIRONMENT
The following environment variables affect the execution of
.Nm :
@@ -398,7 +410,7 @@ manual page:
.Pp
Show a manual page in the current working directory:
.Pp
-.Dl $ man ./man.1
+.Dl $ man -l man.1
.Pp
Show the location of manual pages in sections 1 and 8 which contain the word
.Ql arm :
diff --git a/usr.bin/man/man.sh b/usr.bin/man/man.sh
index 18595042da5f..920223ce3c27 100755
--- a/usr.bin/man/man.sh
+++ b/usr.bin/man/man.sh
@@ -511,13 +511,21 @@ man_display_page_groff() {
# Usage: man_find_and_display page
# Search through the manpaths looking for the given page.
man_find_and_display() {
- local found_page locpath p path sect
+ local found_page has_slash locpath p path sect
# Check to see if it's a file. But only if it has a '/' in
- # the filename.
+ # the filename or if -l was specified.
case "$1" in
- */*) if [ -f "$1" -a -r "$1" ]; then
+ */*) has_slash=yes
+ ;;
+ esac
+ if [ -n "$has_slash" -o -n "$lflag" ]; then
+ if [ -f "$1" -a -r "$1" ]; then
decho "Found a usable page, displaying that"
+ if [ -z "$lflag" ]; then
+ echo "Opening a file directly is deprecated," \
+ "use -l instead." >&2
+ fi
unset use_cat
manpage="$1"
setup_cattool "$manpage"
@@ -531,9 +539,12 @@ man_find_and_display() {
man_display_page
fi
return
+ elif [ -n "$lflag" ]; then
+ echo "Cannot read $1" >&2
+ ret=1
+ return
fi
- ;;
- esac
+ fi
IFS=:
for sect in $MANSECT; do
@@ -601,7 +612,7 @@ man_parse_opts() {
local cmd_arg
OPTIND=1
- while getopts 'K:M:P:S:adfhkm:op:tw' cmd_arg; do
+ while getopts 'K:M:P:S:adfhklm:op:tw' cmd_arg; do
case "${cmd_arg}" in
K) Kflag=Kflag
REGEXP=$OPTARG ;;
@@ -613,6 +624,7 @@ man_parse_opts() {
f) fflag=fflag ;;
h) man_usage 0 ;;
k) kflag=kflag ;;
+ l) lflag=lflag ;;
m) mflag=$OPTARG ;;
o) oflag=oflag ;;
p) MANROFFSEQ=$OPTARG ;;
@@ -625,16 +637,19 @@ man_parse_opts() {
shift $(( $OPTIND - 1 ))
# Check the args for incompatible options.
-
- case "${Kflag}${fflag}${kflag}${tflag}${wflag}" in
+ case "${Kflag}${fflag}${kflag}${lflag}${tflag}${wflag}" in
Kflagfflag*) echo "Incompatible options: -K and -f"; man_usage ;;
Kflag*kflag*) echo "Incompatible options: -K and -k"; man_usage ;;
+ Kflag*lflag*) echo "Incompatible options: -K and -l"; man_usage ;;
Kflag*tflag) echo "Incompatible options: -K and -t"; man_usage ;;
fflagkflag*) echo "Incompatible options: -f and -k"; man_usage ;;
+ fflag*lflag*) echo "Incompatible options: -f and -l"; man_usage ;;
fflag*tflag*) echo "Incompatible options: -f and -t"; man_usage ;;
fflag*wflag) echo "Incompatible options: -f and -w"; man_usage ;;
- *kflagtflag*) echo "Incompatible options: -k and -t"; man_usage ;;
+ *kflaglflag*) echo "Incompatible options: -k and -l"; man_usage ;;
+ *kflag*tflag*) echo "Incompatible options: -k and -t"; man_usage ;;
*kflag*wflag) echo "Incompatible options: -k and -w"; man_usage ;;
+ *lflag*wflag) echo "Incompatible options: -l and -w"; man_usage ;;
*tflagwflag) echo "Incompatible options: -t and -w"; man_usage ;;
esac
@@ -751,7 +766,7 @@ man_setup_locale() {
# Display usage for the man utility.
man_usage() {
echo 'Usage:'
- echo ' man [-adho] [-t | -w] [-M manpath] [-P pager] [-S mansect]'
+ echo ' man [-adhlo] [-t | -w] [-M manpath] [-P pager] [-S mansect]'
echo ' [-m arch[:machine]] [-p [eprtv]] [mansect] page [...]'
echo ' man -K | -f | -k expression [...] -- Search manual pages'
diff --git a/usr.bin/mandoc/Makefile b/usr.bin/mandoc/Makefile
index 0d04aad4e558..2c7c3ed85040 100644
--- a/usr.bin/mandoc/Makefile
+++ b/usr.bin/mandoc/Makefile
@@ -3,6 +3,8 @@
MANDOCDIR= ${SRCTOP}/contrib/mandoc
.PATH: ${MANDOCDIR}
+PACKAGE= mandoc
+
PROG= mandoc
MAN= mandoc.1 mandoc.db.5 eqn.7 mandoc_char.7 tbl.7 man.7 mdoc.7 roff.7
MLINKS= mandoc.1 mdocml.1
@@ -16,6 +18,10 @@ LINKS= ${BINDIR}/mandoc ${BINDIR}/whatis \
.error MK_MAN_UTILS should be set to yes when bootstrapping
.endif
+FILESGROUPS= TRIGGERS
+TRIGGERS= mandoc.ucl
+TRIGGERSDIR= /usr/share/pkg/triggers
+
LIBMAN_SRCS= man.c \
man_macro.c \
man_validate.c
diff --git a/usr.bin/mandoc/mandoc.ucl b/usr.bin/mandoc/mandoc.ucl
new file mode 100644
index 000000000000..f320b6f547fd
--- /dev/null
+++ b/usr.bin/mandoc/mandoc.ucl
@@ -0,0 +1,18 @@
+path_glob: "/usr/share/man/*"
+
+cleanup: {
+ type: lua
+ sandbox: false
+ script: <<EOD
+ os.remove("/usr/share/man/mandoc.db")
+EOD
+}
+
+trigger: {
+ type: lua
+ sandbox: false
+ script: <<EOD
+ print("Generating apropos(1) database...")
+ pkg.exec({"/usr/bin/makewhatis", "/usr/share/man"})
+EOD
+}
diff --git a/usr.bin/sockstat/main.c b/usr.bin/sockstat/main.c
index b5e0248b743a..2e75e4966d80 100644
--- a/usr.bin/sockstat/main.c
+++ b/usr.bin/sockstat/main.c
@@ -1612,7 +1612,7 @@ display(void)
}
}
if (opt_j >= 0)
- return;
+ goto out;
SLIST_FOREACH(s, &nosocks, socket_list) {
if (!check_ports(s))
continue;
@@ -1637,6 +1637,7 @@ display(void)
display_sock(s, &cw, buf, bufsize);
xo_close_instance("socket");
}
+out:
xo_close_list("socket");
xo_close_container("sockstat");
if (xo_finish() < 0)
diff --git a/usr.bin/stat/stat.1 b/usr.bin/stat/stat.1
index 2996781fafa6..55e64de0767e 100644
--- a/usr.bin/stat/stat.1
+++ b/usr.bin/stat/stat.1
@@ -6,6 +6,8 @@
.\" This code is derived from software contributed to The NetBSD Foundation
.\" by Andrew Brown and Jan Schaumann.
.\"
+.\" 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:
@@ -27,7 +29,7 @@
.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
-.Dd June 22, 2017
+.Dd September 9, 2025
.Dt STAT 1
.Os
.Sh NAME
@@ -36,7 +38,7 @@
.Nd display file status
.Sh SYNOPSIS
.Nm
-.Op Fl FHLnq
+.Op Fl FHhLnq
.Op Fl f Ar format | Fl l | r | s | x
.Op Fl t Ar timefmt
.Op Ar
@@ -129,6 +131,45 @@ and use
instead of
.Xr lstat 2 .
This requires root privileges.
+.It Fl h
+For each file argument, print a line consisting of a comma-separated
+list of holes, a space, and the file name.
+Each hole is reported as its starting offset as a decimal number
+followed by a hyphen and the ending offset (one less than the starting
+offset of the data region that follows the hole) as a decimal number.
+If the file ends in a hole, the ending offset of the final hole will
+be one less than the size of the file.
+Otherwise, the final entry in the list (indeed, the only entry in the
+list, if the file is not sparse), is a single decimal number
+corresponding to the size of the file, representing the virtual hole
+at the end of the file.
+.Pp
+If the argument is a directory, instead of a list of holes, a single
+number is printed, corresponding to the minimum hole size for that
+directory as reported by
+.Xr pathconf 2 ,
+followed by a space and the directory name.
+.Pp
+Please note that the only way to retrieve information about the holes
+in a file is to open it and walk the list of holes and data regions
+using
+.Xr lseek 2 .
+If the file is being modified by another process at the same time as
+.Nm
+is inspecting it, the result may be inconsistent.
+.Pp
+This option cannot be combined with the
+.Fl F ,
+.Fl f ,
+.Fl H ,
+.Fl L ,
+.Fl l ,
+.Fl r ,
+.Fl s ,
+.Fl t ,
+or
+.Fl x
+options.
.It Fl L
Use
.Xr stat 2
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);
+}
diff --git a/usr.bin/stat/tests/stat_test.sh b/usr.bin/stat/tests/stat_test.sh
index e75fd0c56490..afe698575034 100755
--- a/usr.bin/stat/tests/stat_test.sh
+++ b/usr.bin/stat/tests/stat_test.sh
@@ -1,6 +1,7 @@
#
# Copyright (c) 2017 Dell EMC
# All rights reserved.
+# Copyright (c) 2025 Klara, Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
@@ -45,6 +46,76 @@ F_flag_body()
atf_check -o match:'.* f\|' stat -Fn f
}
+atf_test_case h_flag cleanup
+h_flag_head()
+{
+ atf_set "descr" "Verify the output format for -h"
+ atf_set "require.user" "root"
+}
+h_flag_body()
+{
+ # POSIX defines a hole as “[a] contiguous region of bytes
+ # within a file, all having the value of zero” and requires
+ # that “all seekable files shall have a virtual hole starting
+ # at the current size of the file” but says “it is up to the
+ # implementation to define when sparse files can be created
+ # and with what granularity for the size of holes”. It also
+ # defines a sparse file as “[a] file that contains more holes
+ # than just the virtual hole at the end of the file”. That's
+ # pretty much the extent of its discussion of holes, apart
+ # from the description of SEEK_HOLE and SEEK_DATA in the lseek
+ # manual page. In other words, there is no portable way to
+ # reliably create a hole in a file on any given file system.
+ #
+ # On FreeBSD, this test is likely to run on either tmpfs, ufs
+ # (ffs2), or zfs. Of those three, only tmpfs has predictable
+ # semantics and supports all possible configurations (the
+ # minimum hole size on zfs is variable for small files, and
+ # ufs will not allow a file to end in a hole).
+ atf_check mkdir mnt
+ atf_check mount -t tmpfs tmpfs mnt
+ cd mnt
+
+ # For a directory, prints the minimum hole size, which on
+ # tmpfs is the system page size.
+ ps=$(sysctl -n hw.pagesize)
+ atf_check -o inline:"$((ps)) .\n" stat -h .
+ atf_check -o inline:"$((ps)) ." stat -hn .
+
+ # For a file, prints a list of holes.
+ atf_check truncate -s 0 foo
+ atf_check -o inline:"0 foo" \
+ stat -hn foo
+ atf_check truncate -s "$((ps))" foo
+ atf_check -o inline:"0-$((ps-1)) foo" \
+ stat -hn foo
+ atf_check dd status=none if=/COPYRIGHT of=foo \
+ oseek="$((ps))" bs=1 count=1
+ atf_check -o inline:"0-$((ps-1)),$((ps+1)) foo" \
+ stat -hn foo
+ atf_check truncate -s "$((ps*3))" foo
+ atf_check -o inline:"0-$((ps-1)),$((ps*2))-$((ps*3-1)) foo" \
+ stat -hn foo
+
+ # Test multiple files.
+ atf_check dd status=none if=/COPYRIGHT of=bar
+ sz=$(stat -f%z bar)
+ atf_check -o inline:"0-$((ps-1)),$((ps*2))-$((ps*3-1)) foo
+$((sz)) bar
+" \
+ stat -h foo bar
+
+ # For a device, fail.
+ atf_check -s exit:1 -e match:"/dev/null: Illegal seek" \
+ stat -h /dev/null
+}
+h_flag_cleanup()
+{
+ if [ -d mnt ]; then
+ umount mnt || true
+ fi
+}
+
atf_test_case l_flag
l_flag_head()
{
@@ -233,6 +304,7 @@ atf_init_test_cases()
{
atf_add_test_case F_flag
#atf_add_test_case H_flag
+ atf_add_test_case h_flag
#atf_add_test_case L_flag
#atf_add_test_case f_flag
atf_add_test_case l_flag
diff --git a/usr.bin/w/uptime.1 b/usr.bin/w/uptime.1
index b93972d3f932..37881793736f 100644
--- a/usr.bin/w/uptime.1
+++ b/usr.bin/w/uptime.1
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd August 18, 2020
+.Dd September 11, 2025
.Dt UPTIME 1
.Os
.Sh NAME
@@ -33,6 +33,7 @@
.Nd show how long system has been running
.Sh SYNOPSIS
.Nm
+.Op Fl -libxo
.Sh DESCRIPTION
The
.Nm
@@ -40,6 +41,17 @@ utility displays the current time,
the length of time the system has been up,
the number of users, and the load average of the system over the last
1, 5, and 15 minutes.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl -libxo
+Generate output via
+.Xr libxo 3
+in a selection of different human and machine readable formats.
+See
+.Xr xo_options 7
+for details on command line arguments.
+.El
.Sh FILES
.Bl -tag -width /boot/kernel/kernel
.It Pa /boot/kernel/kernel
@@ -51,7 +63,9 @@ $ uptime
11:23AM up 3:01, 8 users, load averages: 21.09, 15.43, 12.79
.Ed
.Sh SEE ALSO
-.Xr w 1
+.Xr w 1 ,
+.Xr libxo 3 ,
+.Xr xo_options 7
.Sh HISTORY
The
.Nm
diff --git a/usr.bin/w/w.1 b/usr.bin/w/w.1
index 159eb3370c8c..2dbcffdeda1f 100644
--- a/usr.bin/w/w.1
+++ b/usr.bin/w/w.1
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd July 16, 2025
+.Dd September 11, 2025
.Dt W 1
.Os
.Sh NAME
@@ -54,7 +54,7 @@ user is on, the host from which the user is logged in, the time the user
logged on, the time since the user last typed anything,
and the name and arguments of the current process.
.Pp
-The options are as follows:
+The following options are available:
.Bl -tag -width indent
.It Fl -libxo
Generate output via