diff options
Diffstat (limited to 'usr.bin')
125 files changed, 6260 insertions, 2950 deletions
| diff --git a/usr.bin/Makefile b/usr.bin/Makefile index b69d25480479..da1a9b3a681f 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -133,7 +133,6 @@ SUBDIR=	alias \  	sdiff \  	sed \  	seq \ -	shar \  	showmount \  	sockstat \  	soelim \ @@ -220,7 +219,7 @@ SUBDIR.${MK_ISCSI}+=	iscsictl  SUBDIR.${MK_KDUMP}+=	kdump  SUBDIR.${MK_KDUMP}+=	truss  .if ${MK_MITKRB5} == "no" -SUBDIR.${MK_KERBEROS_SUPPORT}+=	compile_et +SUBDIR.${MK_KERBEROS}+=	compile_et  .endif  SUBDIR.${MK_LDNS_UTILS}+=	drill  SUBDIR.${MK_LDNS_UTILS}+=	host diff --git a/usr.bin/asa/asa.1 b/usr.bin/asa/asa.1 index da1af0e8ce84..68d0735774a6 100644 --- a/usr.bin/asa/asa.1 +++ b/usr.bin/asa/asa.1 @@ -84,8 +84,6 @@ To format the output of a  program and redirect it to a line-printer:  .Pp  .Dl "a.out | asa | lpr" -.Sh SEE ALSO -.Xr f77 1  .Sh STANDARDS  The  .Nm diff --git a/usr.bin/awk/awk.1 b/usr.bin/awk/awk.1 index 65c91738966b..612669629a02 100644 --- a/usr.bin/awk/awk.1 +++ b/usr.bin/awk/awk.1 @@ -21,7 +21,7 @@  .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,  .\" ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF  .\" THIS SOFTWARE. -.Dd July 30, 2021 +.Dd September 3, 2025  .Dt AWK 1  .Os  .Sh NAME @@ -32,7 +32,7 @@  .Op Fl safe  .Op Fl version  .Op Fl d Ns Op Ar n -.Op Fl F Ar fs +.Op Fl F Ar fs | Fl -csv  .Op Fl v Ar var Ns = Ns Ar value  .Op Ar prog | Fl f Ar progfile  .Ar @@ -42,9 +42,11 @@ scans each input  .Ar file  for lines that match any of a set of patterns specified literally in  .Ar prog -or in one or more files specified as +or in one or more files +specified as  .Fl f Ar progfile . -With each pattern there can be an associated action that will be performed +With each pattern +there can be an associated action that will be performed  when a line of a  .Ar file  matches the pattern. @@ -76,6 +78,11 @@ to dump core on fatal errors.  .It Fl F Ar fs  Define the input field separator to be the regular expression  .Ar fs . +.It Fl -csv +causes +.Nm +to process records using (more or less) standard comma-separated values +(CSV) format.  .It Fl f Ar progfile  Read program code from the specified file  .Ar progfile @@ -178,7 +185,7 @@ as the field separator, use the  option with a value of  .Sq [t] .  .Pp -A pattern-action statement has the form +A pattern-action statement has the form:  .Pp  .D1 Ar pattern Ic \&{ Ar action Ic \&}  .Pp @@ -347,7 +354,7 @@ in a pattern.  A pattern may consist of two patterns separated by a comma;  in this case, the action is performed for all lines  from an occurrence of the first pattern -through an occurrence of the second. +through an occurrence of the second, inclusive.  .Pp  A relational expression is one of the following:  .Pp @@ -363,7 +370,8 @@ A relational expression is one of the following:  .Pp  where a  .Ar relop -is any of the six relational operators in C, and a +is any of the six relational operators in C, +and a  .Ar matchop  is either  .Ic ~ @@ -386,6 +394,9 @@ and after the last.  and  .Ic END  do not combine with other patterns. +They may appear multiple times in a program and execute +in the order they are read by +.Nm  .Pp  Variable names with special meanings:  .Pp @@ -428,6 +439,11 @@ The length of the string matched by the  function.  .It Va RS  Input record separator (default newline). +If empty, blank lines separate records. +If more than one character long, +.Va RS +is treated as a regular expression, and records are +separated by text matching the expression.  .It Va RSTART  The starting position of the string matched by the  .Fn match @@ -515,7 +531,8 @@ occurs, or 0 if it does not.  The length of  .Fa s  taken as a string, -or of +number of elements in an array for an array argument, +or length of  .Va $0  if no argument is given.  .It Fn match s r @@ -696,10 +713,44 @@ records from  .Ar file  remains open until explicitly closed with a call to  .Fn close . +.It Fn systime +returns the current date and time as a standard +.Dq seconds since the epoch +value. +.It Fn strftime fmt timestamp +formats +.Fa timestamp +(a value in seconds since the epoch) +according to +Fa fmt , +which is a format string as supported by +.Xr strftime 3 . +Both +.Fa timestamp +and +.Fa fmt +may be omitted; if no +.Fa timestamp , +the current time of day is used, and if no +.Fa fmt , +a default format of +.Dq %a %b %e %H:%M:%S %Z %Y +is used.  .It Fn system cmd  Executes  .Fa cmd  and returns its exit status. +This will be -1 upon error, +.Fa cmd 's +exit status upon a normal exit, +256 + +.Va sig +upon death-by-signal, where +.Va sig +is the number of the murdering signal, +or 512 + +.Va sig +if there was a core dump.  .El  .Ss Bit-Operation Functions  .Bl -tag -width "lshift(a, b)" @@ -725,6 +776,16 @@ Returns integer argument x shifted by n bits to the right.  But note that the  .Ic exit  expression can modify the exit status. +.Sh ENVIRONMENT VARIABLES +If +.Va POSIXLY_CORRECT +is set in the environment, then +.Nm +follows the POSIX rules for +.Fn sub +and +.Fn gsub +with respect to consecutive backslashes and ampersands.  .Sh EXAMPLES  Print lines longer than 72 characters:  .Pp @@ -734,7 +795,7 @@ Print first two fields in opposite order:  .Pp  .Dl { print $2, $1 }  .Pp -Same, with input fields separated by comma and/or blanks and tabs: +Same, with input fields separated by comma and/or spaces and tabs:  .Bd -literal -offset indent  BEGIN { FS = ",[ \et]*|[ \et]+" }        { print $2, $1 } @@ -810,6 +871,63 @@ to it.  .Pp  The scope rules for variables in functions are a botch;  the syntax is worse. +.Pp +Input is expected to be UTF-8 encoded. +Other multibyte character sets are not handled. +However, in eight-bit locales, +.Nm +treats each input byte as a separate character. +.Sh UNUSUAL FLOATING-POINT VALUES +.Nm +was designed before IEEE 754 arithmetic defined Not-A-Number (NaN) +and Infinity values, which are supported by all modern floating-point +hardware. +.Pp +Because +.Nm +uses +.Xr strtod 3 +and +.Xr atof 3 +to convert string values to double-precision floating-point values, +modern C libraries also convert strings starting with +.Va inf +and +.Va nan +into infinity and NaN values respectively. +This led to strange results, +with something like this: +.Bd -literal -offset indent +echo nancy | awk '{ print $1 + 0 }' +.Ed +.Pp +printing +.Dq nan +instead of zero. +.Pp +.Nm +now follows GNU AWK, and prefilters string values before attempting +to convert them to numbers, as follows: +.Bl -tag -width "Hexadecimal values" +.It Hexadecimal values +Hexadecimal values (allowed since C99) convert to zero, as they did +prior to C99. +.It NaN values +The two strings +.Dq +nan +and +.Dq -nan +(case independent) convert to NaN. +No others do. +(NaNs can have signs.) +.It Infinity values +The two strings +.Dq +inf +and +.Dq -inf +(case independent) convert to positive and negative infinity, respectively. +No others do. +.El  .Sh DEPRECATED BEHAVIOR  One True Awk has accepted  .Fl F Ar t diff --git a/usr.bin/beep/Makefile b/usr.bin/beep/Makefile index 1f2548c24819..79735e6f354a 100644 --- a/usr.bin/beep/Makefile +++ b/usr.bin/beep/Makefile @@ -1,3 +1,5 @@ +PACKAGE=sound +  PROG=	beep  MAN=	beep.1  LIBADD=	m diff --git a/usr.bin/bmake/Makefile.config b/usr.bin/bmake/Makefile.config index 78babc2f1382..2415f9d3882c 100644 --- a/usr.bin/bmake/Makefile.config +++ b/usr.bin/bmake/Makefile.config @@ -6,7 +6,7 @@ SRCTOP?= ${.CURDIR:H:H}  # things set by configure -_MAKE_VERSION?=20250618 +_MAKE_VERSION?=20250804  prefix?= /usr  srcdir= ${SRCTOP}/contrib/bmake diff --git a/usr.bin/bmake/Makefile.inc b/usr.bin/bmake/Makefile.inc index 5140bd18bb37..a064563a2283 100644 --- a/usr.bin/bmake/Makefile.inc +++ b/usr.bin/bmake/Makefile.inc @@ -3,6 +3,8 @@ MK_host_egacy= no  .sinclude <src.opts.mk> +PACKAGE?= bmake +  .if defined(.PARSEDIR)  # make sure this is available to unit-tests/Makefile  .export SRCTOP diff --git a/usr.bin/bmake/unit-tests/Makefile b/usr.bin/bmake/unit-tests/Makefile index 1b9a47febe11..66bdc04321e8 100644 --- a/usr.bin/bmake/unit-tests/Makefile +++ b/usr.bin/bmake/unit-tests/Makefile @@ -1,9 +1,9 @@  # This is a generated file, do NOT edit!  # See contrib/bmake/bsd.after-import.mk  # -# $Id: Makefile,v 1.239 2025/06/15 21:32:16 sjg Exp $ +# $Id: Makefile,v 1.245 2025/08/05 16:18:07 sjg Exp $  # -# $NetBSD: Makefile,v 1.367 2025/06/13 20:23:16 rillig Exp $ +# $NetBSD: Makefile,v 1.372 2025/08/04 22:44:49 sjg Exp $  #  # Unit tests for make(1)  # @@ -61,6 +61,7 @@ rm-tmpdir:	.NOMETA  # src/tests/usr.bin/make/t_make.sh as well.  #TESTS+=		archive  #TESTS+=		archive-suffix +TESTS+=		char-005c-reverse-solidus  TESTS+=		cmd-errors  TESTS+=		cmd-errors-jobs  TESTS+=		cmd-errors-lint @@ -236,6 +237,7 @@ TESTS+=		jobs-error-nested-make  TESTS+=		lint  TESTS+=		make-exported  TESTS+=		meta-cmd-cmp +TESTS+=		meta-output  TESTS+=		moderrs  TESTS+=		modmisc  .if ${.MAKE.UID} > 0 @@ -416,6 +418,7 @@ TESTS+=		varmod-to-upper  TESTS+=		varmod-undefined  TESTS+=		varmod-unique  TESTS+=		varname +TESTS+=		varname-circumflex  TESTS+=		varname-dollar  TESTS+=		varname-dot-alltargets  TESTS+=		varname-dot-curdir @@ -544,7 +547,6 @@ TESTS:= ${TESTS:${BROKEN_TESTS:S,^,N,:ts:}}  # Ideas for more tests:  #	char-0020-space.mk -#	char-005C-backslash.mk  #	escape-cond-str.mk  #	escape-cond-func-arg.mk  #	escape-varmod.mk @@ -594,6 +596,7 @@ SED_CMDS.directive-include-guard= \  	-e '/^ParseDependency/d' \  	-e '/^ParseEOF:/d'  SED_CMDS.export=	-e '/^[^=_A-Za-z0-9]*=/d' +SED_CMDS.export+=	-e '/^DIFF/d'  .if ${.MAKE.OS:NCygwin} == ""  SED_CMDS.export+=	-e '/^WINDIR=/d' -e '/^SYSTEMROOT=/d'  .endif @@ -804,6 +807,7 @@ EGREP= grep -E  EGREP?= egrep  MAKE_TEST_ENV=  EGREP="${EGREP}" +MAKE_TEST_ENV+= DIFF="${TOOL_DIFF}" DIFF_FLAGS="${DIFF_FLAGS}"  MAKE_TEST_ENV+=	MALLOC_OPTIONS="JA"	# for jemalloc 100  MAKE_TEST_ENV+=	MALLOC_CONF="junk:true"	# for jemalloc 510  MAKE_TEST_ENV+= TMPDIR=${TMPDIR} diff --git a/usr.bin/bsdcat/Makefile b/usr.bin/bsdcat/Makefile index 032207217be6..06081fc2b2f8 100644 --- a/usr.bin/bsdcat/Makefile +++ b/usr.bin/bsdcat/Makefile @@ -11,7 +11,7 @@ BSDCAT_VERSION_STRING!=	sed -n '/define.*ARCHIVE_VERSION_ONLY_STRING/{s,[^0-9.],  SRCS=	bsdcat.c cmdline.c  .PATH:	${_LIBARCHIVEDIR}/libarchive_fe -SRCS+=	err.c +SRCS+=	lafe_err.c  CFLAGS+= -DBSDCAT_VERSION_STRING=\"${BSDCAT_VERSION_STRING}\"  CFLAGS+= -DPLATFORM_CONFIG_H=\"${_LIBARCHIVECONFDIR}/config_freebsd.h\" diff --git a/usr.bin/bzip2/Makefile b/usr.bin/bzip2/Makefile index 99679cc00c44..b89f8bbb85a2 100644 --- a/usr.bin/bzip2/Makefile +++ b/usr.bin/bzip2/Makefile @@ -1,3 +1,5 @@ +PACKAGE=	bzip2 +  BZ2DIR=	${SRCTOP}/contrib/bzip2  .PATH: ${BZ2DIR} diff --git a/usr.bin/bzip2recover/Makefile b/usr.bin/bzip2recover/Makefile index 2b11d3a45694..b62cba373296 100644 --- a/usr.bin/bzip2recover/Makefile +++ b/usr.bin/bzip2recover/Makefile @@ -1,3 +1,5 @@ +PACKAGE=	bzip2 +  BZ2DIR=	${SRCTOP}/contrib/bzip2  .PATH: ${BZ2DIR} diff --git a/usr.bin/calendar/calendars/calendar.freebsd b/usr.bin/calendar/calendars/calendar.freebsd index 0b1a37f43723..b85f2f1dee35 100644 --- a/usr.bin/calendar/calendars/calendar.freebsd +++ b/usr.bin/calendar/calendars/calendar.freebsd @@ -146,6 +146,7 @@  03/29	Dave Cottlehuber <dch@FreeBSD.org> born in Christchurch, New Zealand, 1973  03/29	Thierry Thomas <thierry@FreeBSD.org> born in Luxeuil les Bains, France, 1961  03/30	Po-Chuan Hsieh <sunpoet@FreeBSD.org> born in Taipei, Taiwan, Republic of China, 1978 +03/31   Vladlen Popolitov <vladlen@FreeBSD.org> born in Lipetsk region, USSR, 1969  04/01	Matthew Jacob <mjacob@FreeBSD.org> born in San Francisco, California, United States, 1958  04/01	Alexander V. Chernikov <melifaro@FreeBSD.org> born in Moscow, Russian Federation, 1984  04/01	Bill Fenner <fenner@FreeBSD.org> born in Bellefonte, Pennsylvania, United States, 1971 @@ -174,6 +175,7 @@  04/22	Jakub Klama <jceel@FreeBSD.org> born in Blachownia, Silesia, Poland, 1989  04/25	Richard Gallamore <ultima@FreeBSD.org> born in Kissimmee, Florida, United States, 1987  04/26	Rene Ladan <rene@FreeBSD.org> born in Geldrop, the Netherlands, 1980 +04/27	Jose Luis Duran <jlduran@FreeBSD.org> born in Panama City, Panama, 1978  04/28	Oleg Bulyzhin <oleg@FreeBSD.org> born in Kharkov, USSR, 1976  04/28	Andriy Voskoboinyk <avos@FreeBSD.org> born in Bila Tserkva, Ukraine, 1992  04/28	Nuno Teixeira <eduardo@FreeBSD.org> born in Aveiro, Portugal, 1974 @@ -259,6 +261,7 @@  06/11	Alonso Cardenas Marquez <acm@FreeBSD.org> born in Arequipa, Peru, 1979  06/14	Josh Paetzel <jpaetzel@FreeBSD.org> born in Minneapolis, Minnesota, United States, 1973  06/15	Second quarterly status reports are due on 06/30 +06/15	Aymeric Wibo <obiwac@FreeBSD.org> born in Plaistow, London, United Kingdom, 2004  06/17	Tilman Linneweh <arved@FreeBSD.org> born in Weinheim, Baden-Wuerttemberg, Germany, 1978  06/18	Li-Wen Hsu <lwhsu@FreeBSD.org> born in Taipei, Taiwan, Republic of China, 1984  06/18	Roman Bogorodskiy <novel@FreeBSD.org> born in Saratov, Russian Federation, 1986 @@ -478,6 +481,7 @@  12/05	Ivan Voras <ivoras@FreeBSD.org> born in Slavonski Brod, Croatia, 1981  12/06	Stefan Farfeleder <stefanf@FreeBSD.org> born in Wien, Austria, 1980  12/08	Michael Tuexen <tuexen@FreeBSD.org> born in Oldenburg, Germany, 1966 +12/09	Tiago Gasiba <tiga@FreeBSD.org> born in Porto, Portugal, 1978  12/10	Hiroki Tagato <tagattie@FreeBSD.org> born in Shiga, Japan, 1971  12/11	Ganael Laplanche <martymac@FreeBSD.org> born in Reims, France, 1980  12/11	Koichiro Iwao <meta@FreeBSD.org> born in Oita, Japan, 1987 diff --git a/usr.bin/chat/Makefile b/usr.bin/chat/Makefile index dfe76f71aba7..1b53d2abb1f7 100644 --- a/usr.bin/chat/Makefile +++ b/usr.bin/chat/Makefile @@ -1,6 +1,7 @@  # I once used this extensively, but no longer have a modem.  Feel free  # to ask me questions about it, but I disclaim ownership now.  -Peter +PACKAGE=ppp  PROG=	chat  MAN=	chat.8 diff --git a/usr.bin/clang/clang-scan-deps/Makefile b/usr.bin/clang/clang-scan-deps/Makefile index 16fecdb88867..8da12faccc45 100644 --- a/usr.bin/clang/clang-scan-deps/Makefile +++ b/usr.bin/clang/clang-scan-deps/Makefile @@ -10,13 +10,14 @@ SRCS+=		ClangScanDeps.cpp \  .include "${SRCTOP}/lib/clang/clang.pre.mk"  CFLAGS+=	-I${.OBJDIR} -TDFILE=		Opts.td -INCFILE=	${TDFILE:.td=.inc} + +INCFILE=	Opts.inc +TDFILE=		${LLVM_BASE}/${SRCDIR}/Opts.td  GENOPT=		-gen-opt-parser-defs  ${INCFILE}: ${TDFILE}  	${LLVM_TBLGEN} ${GENOPT} -I ${LLVM_SRCS}/include -d ${.TARGET:C/$/.d/} \ -	    -o ${.TARGET} ${.ALLSRC} +	    -o ${.TARGET} ${TDFILE}  TGHDRS+=	${INCFILE}  DEPENDFILES+=	${TGHDRS:C/$/.d/} diff --git a/usr.bin/clang/clang.prog.mk b/usr.bin/clang/clang.prog.mk index 36c601bcbe36..3baf3d0baf0f 100644 --- a/usr.bin/clang/clang.prog.mk +++ b/usr.bin/clang/clang.prog.mk @@ -31,7 +31,7 @@ DPADD+=		${OBJTOP}/lib/clang/lib${lib}/lib${LIBPRIV}${lib}.${LIBEXT}  LDADD+=		${OBJTOP}/lib/clang/lib${lib}/lib${LIBPRIV}${lib}.${LIBEXT}  .endfor -PACKAGE=	clang +PACKAGE?=	clang  .if ${.MAKE.OS} == "FreeBSD" || !defined(BOOTSTRAPPING)  LIBADD+=	execinfo diff --git a/usr.bin/clang/llvm-ar/Makefile b/usr.bin/clang/llvm-ar/Makefile index fd12b1ddef57..e019c89b3581 100644 --- a/usr.bin/clang/llvm-ar/Makefile +++ b/usr.bin/clang/llvm-ar/Makefile @@ -1,5 +1,6 @@  .include <src.opts.mk> +PACKAGE=	toolchain  PROG_CXX=	llvm-ar  MAN=		llvm-ar.1 llvm-ranlib.1 diff --git a/usr.bin/clang/llvm-nm/Makefile b/usr.bin/clang/llvm-nm/Makefile index 825faf74719b..7e089d1b408d 100644 --- a/usr.bin/clang/llvm-nm/Makefile +++ b/usr.bin/clang/llvm-nm/Makefile @@ -1,5 +1,6 @@  .include <src.opts.mk> +PACKAGE=	toolchain  PROG_CXX=	llvm-nm  SRCDIR=		llvm/tools/llvm-nm diff --git a/usr.bin/clang/llvm-size/Makefile b/usr.bin/clang/llvm-size/Makefile index 2860a0069538..9d3505cdd319 100644 --- a/usr.bin/clang/llvm-size/Makefile +++ b/usr.bin/clang/llvm-size/Makefile @@ -1,5 +1,6 @@  .include <src.opts.mk> +PACKAGE=	toolchain  PROG_CXX=	llvm-size  SRCDIR=		llvm/tools/llvm-size diff --git a/usr.bin/clang/llvm.prog.mk b/usr.bin/clang/llvm.prog.mk index f702082e31bd..c369fe8d5944 100644 --- a/usr.bin/clang/llvm.prog.mk +++ b/usr.bin/clang/llvm.prog.mk @@ -25,7 +25,7 @@ DPADD+=		${OBJTOP}/lib/clang/lib${lib}/lib${LIBPRIV}${lib}.${LIBEXT}  LDADD+=		${OBJTOP}/lib/clang/lib${lib}/lib${LIBPRIV}${lib}.${LIBEXT}  .endfor -PACKAGE=	clang +PACKAGE?=	clang  .if ${.MAKE.OS} == "FreeBSD" || !defined(BOOTSTRAPPING)  LIBADD+=	execinfo diff --git a/usr.bin/cpio/Makefile b/usr.bin/cpio/Makefile index 46fe36d8c18e..31b25e4199da 100644 --- a/usr.bin/cpio/Makefile +++ b/usr.bin/cpio/Makefile @@ -11,7 +11,7 @@ BSDCPIO_VERSION_STRING!=	sed -n '/define.*ARCHIVE_VERSION_ONLY_STRING/{s,[^0-9.]  SRCS=	cpio.c cmdline.c  .PATH:	${_LIBARCHIVEDIR}/libarchive_fe -SRCS+=	err.c line_reader.c passphrase.c +SRCS+=	lafe_err.c line_reader.c passphrase.c  CFLAGS+= -DBSDCPIO_VERSION_STRING=\"${BSDCPIO_VERSION_STRING}\"  CFLAGS+= -DPLATFORM_CONFIG_H=\"${_LIBARCHIVECONFDIR}/config_freebsd.h\" diff --git a/usr.bin/cpio/tests/Makefile b/usr.bin/cpio/tests/Makefile index 9e1028c7eb58..ee4da15bc7e4 100644 --- a/usr.bin/cpio/tests/Makefile +++ b/usr.bin/cpio/tests/Makefile @@ -26,7 +26,7 @@ CFLAGS.test_utils.c+=	-Wno-cast-align  CPIO_SRCS= cmdline.c  .PATH:	${_LIBARCHIVEDIR}/libarchive_fe -CPIO_SRCS+= err.c +CPIO_SRCS+= lafe_err.c  .PATH:	${_LIBARCHIVEDIR}/cpio/test  TESTS_SRCS=	\ diff --git a/usr.bin/cut/cut.c b/usr.bin/cut/cut.c index 60ff5a31062a..e4e322b4e5c9 100644 --- a/usr.bin/cut/cut.c +++ b/usr.bin/cut/cut.c @@ -448,8 +448,8 @@ f_cut(FILE *fp, const char *fname)  					break;  				}  				if (*pos) -					for (i = 0; i < (int)clen; i++) -						putchar(p[i - clen]); +					(void)fwrite(p - clen, 1, clen, +					    stdout);  			}  			if (ch == '\n')  				break; diff --git a/usr.bin/du/du.1 b/usr.bin/du/du.1 index 37f7d7837b11..1b6d800b0285 100644 --- a/usr.bin/du/du.1 +++ b/usr.bin/du/du.1 @@ -25,7 +25,7 @@  .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF  .\" SUCH DAMAGE.  .\" -.Dd April 29, 2024 +.Dd July 16, 2025  .Dt DU 1  .Os  .Sh NAME @@ -58,7 +58,7 @@ Generate output via  .Xr libxo 3  in a selection of different human and machine readable formats.  See -.Xr xo_parse_args 3 +.Xr xo_options 7  for details on command line arguments.  .It Fl A  Display the apparent size instead of the disk usage. @@ -225,7 +225,7 @@ Also display a grand total at the end:  .Xr chflags 2 ,  .Xr fts 3 ,  .Xr libxo 3 , -.Xr xo_parse_args 3 , +.Xr xo_options 7 ,  .Xr symlink 7 ,  .Xr quot 8  .Sh STANDARDS diff --git a/usr.bin/elfdump/Makefile b/usr.bin/elfdump/Makefile index 5b48a8b6174d..40dc04ab688e 100644 --- a/usr.bin/elfdump/Makefile +++ b/usr.bin/elfdump/Makefile @@ -1,3 +1,4 @@ +PACKAGE=toolchain  PROG=	elfdump  .include <bsd.prog.mk> diff --git a/usr.bin/find/Makefile b/usr.bin/find/Makefile index 904c08620833..48b164133bb0 100644 --- a/usr.bin/find/Makefile +++ b/usr.bin/find/Makefile @@ -3,7 +3,7 @@  PACKAGE=	runtime  PROG=	find -SRCS=	find.c function.c ls.c main.c misc.c operator.c option.c \ +SRCS=	find.c function.c ls.c main.c misc.c operator.c option.c printf.c \  	getdate.y  YFLAGS=  CFLAGS.clang+=	-Werror=undef diff --git a/usr.bin/find/extern.h b/usr.bin/find/extern.h index feb2e0202056..02c85d06a34c 100644 --- a/usr.bin/find/extern.h +++ b/usr.bin/find/extern.h @@ -44,6 +44,8 @@ void	 printlong(char *, char *, struct stat *);  int	 queryuser(char **);  OPTION	*lookup_option(const char *);  void	 finish_execplus(void); +void	 do_printf(PLAN *plan, FTSENT *entry, FILE *fout); +  creat_f	c_Xmin;  creat_f	c_Xtime; @@ -55,6 +57,7 @@ creat_f	c_empty;  creat_f	c_exec;  creat_f	c_flags;  creat_f	c_follow; +creat_f	c_fprint;  creat_f	c_fstype;  creat_f	c_group;  creat_f	c_ignore_readdir_race; @@ -68,6 +71,7 @@ creat_f	c_nogroup;  creat_f	c_nouser;  creat_f	c_perm;  creat_f	c_print; +creat_f	c_printf;  creat_f	c_regex;  creat_f	c_samefile;  creat_f	c_simple; @@ -90,6 +94,8 @@ exec_f	f_executable;  exec_f	f_expr;  exec_f	f_false;  exec_f	f_flags; +exec_f	f_fprint; +exec_f	f_fprint0;  exec_f	f_fstype;  exec_f	f_group;  exec_f	f_inum; @@ -106,6 +112,7 @@ exec_f	f_path;  exec_f	f_perm;  exec_f	f_print;  exec_f	f_print0; +exec_f	f_printf;  exec_f	f_prune;  exec_f	f_quit;  exec_f	f_readable; diff --git a/usr.bin/find/find.1 b/usr.bin/find/find.1 index eb3fd4d0dbde..98521a98762d 100644 --- a/usr.bin/find/find.1 +++ b/usr.bin/find/find.1 @@ -28,7 +28,7 @@  .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF  .\" SUCH DAMAGE.  .\" -.Dd November 23, 2024 +.Dd July 26, 2025  .Dt FIND 1  .Os  .Sh NAME @@ -515,6 +515,28 @@ and none of the  .Ar flags  bits match those of  .Ar notflags . +.It Ic -fprint Ar filename +This primary always evaluates to true. +This creates +.Ar filename +or truncates the file if it already exists. +The file is created at startup. +It writes the pathname of the current file to this file, followed +by a newline character. +The file will be empty if no files are matched. +.Pp +.It Ic -fprint0 Ar filename +This primary always evaluates to true. +This creates +.Ar filename +or truncates the file if it already exists. +The file is created at startup. +It writes the pathname of the current file to this file, followed +by an ASCII +.Dv NUL +character (character code 0). +The file will be empty if no files are matched. +.Pp  .It Ic -fstype Ar type  True if the file is contained in a file system of type  .Ar type . @@ -821,6 +843,17 @@ It prints the pathname of the current file to standard output, followed by an  ASCII  .Dv NUL  character (character code 0). +.It Ic -printf Ar fmt +This primary always evaluates to true. +It prints information about the file, interpreting +.Sq \ +and +.Sq % +escape sequences as described in the PRINTF FORMATS section. +Unlike +.Ic -print , +.Ic -printf +does not add a newline automatically.  .It Ic -prune  This primary always evaluates to true.  It causes @@ -993,6 +1026,158 @@ All operands and primaries must be separate arguments to  Primaries which themselves take arguments expect each argument  to be a separate argument to  .Nm . +.Sh PRINTF FORMATS +The following +.Sq \e +escapes are recognized: +.Bl -tag -width Ds -offset indent -compact +.It Cm \ea +Write a <bell> character. +.It Cm \eb +Write a <backspace> character. +.It Cm \ec +Writes no characters, but terminates the string and flushes the output so far +after each match. +.It Cm \ef +Write a <form-feed> character. +.It Cm \en +Write a <new-line> character. +.It Cm \er +Write a <carriage return> character. +.It Cm \et +Write a <tab> character. +.It Cm \ev +Write a <vertical tab> character. +.It Cm \e\' +Write a <single quote> character. +.It Cm \e\e +Write a backslash character. +.It Cm \e Ns Ar num +Write a byte whose +value is the 1-, 2-, or 3-digit +octal number +.Ar num . +Multibyte characters can be constructed using multiple +.Cm \e Ns Ar num +sequences. +.El +.Pp +Each format specification is introduced by the percent character +(``%''). +The remainder of the format specification includes, +in the following order: +.Bl -tag -width Ds +.It "Zero or more of the following flags:" +.Bl -tag -width Ds +.It Cm # +A `#' character, has no effect on almost all formats. +It is not yet implemented. +.It Cm \&\- +A minus sign `\-' which specifies +.Em left adjustment +of the output in the indicated field; +It is not yet implemented. +.It "Field Width:" +An optional digit string specifying a +.Em field width ; +if the output string has fewer bytes than the field width it will +be blank-padded on the left (or right, if the left-adjustment indicator +has been given) to make up the field width (note that a leading zero +is a flag, but an embedded zero is part of a field width); +It is not yet implemented. +.It Precision: +An optional period, +.Sq Cm \&.\& , +followed by an optional digit string giving a +.Em precision +which specifies the maximum number of bytes to be printed +from a string; if the digit string is missing, the precision is treated +as zero; +It is not yet implemented. +.It Format: +One or two characters, described below, which indicates the information to display. +.Bl -tag -width Ds +.It p +Path to file +.It f +Filename without directories. +.It h +Path relative to the starting point, or '.' if that's empty for some reason. +.It P +Unimplemented -- File with command line arg. +.It H +Unimplemented -- Command line arg. +.It g +gid in human readable form. +.It G +gid as a number. +.It h +uid in human readable form. +.It U +uid as a number. +.It m +File permission mode in octal. +.It M +File mode in +.Xr ls 1 +standard form. +.It k +File size in KiB (units of 1024 bytes). +.It b +File size in blocks (Always 512 byte units, even if underlying storage +size differs). +.It s +Size in bytes of the file. +.It S +Sparseness of the file. +The blocks the file occupies times 512 divided by the file size. +.It d +Depth in the tree +.It D +Device number for the file. +.It F +Unimplemented -- Filesystem type where the file resides. +.It l +Object of the symbolic link. +.It i +Inode of the file. +.It n +Number of hard links. +.It y +A single character representing the type of the file. +.It Y +A single character representing the type of the file. +If the file is a symbolic link, show information for the target of the +link instead, or +.Sq L +if the link loops, +.Sq N +if the target does not exist, or +.Sq ? +if any other error occurs while attempting to determine the type of +the target. +.It a +Access time of the file. +.It A +Access time of the file in strftime format. +Takes an additional argument. +.It B +Birth time of the file in strftime format. +Takes an additional argument. +.It c +Creation time of the file. +.It C +Creation time of the file in strftime format. +Takes an additional argument. +.It t +Modification time of the file. +.It T +Modification time of the file in strftime format. +Takes an additional argument. +.El +Any format not listed is not supported, though the error changes. +.El +.El  .Sh ENVIRONMENT  The  .Ev LANG , LC_ALL , LC_COLLATE , LC_CTYPE , LC_MESSAGES @@ -1156,7 +1341,7 @@ and was removed in  .At v3 .  It was rewritten for  .At v5 -and later be enhanced for the Programmer's Workbench (PWB). +and was later enhanced for the Programmer's Workbench (PWB).  These changes were later incorporated in  .At v7 .  .Sh BUGS diff --git a/usr.bin/find/find.c b/usr.bin/find/find.c index 8b24ecd6a306..2247ae86a94b 100644 --- a/usr.bin/find/find.c +++ b/usr.bin/find/find.c @@ -211,7 +211,7 @@ find_execute(PLAN *plan, char *paths[])  		}  		if (showinfo) { -			fprintf(stderr, "Scanning: %s/%s\n", entry->fts_path, entry->fts_name); +			fprintf(stderr, "Scanning: %s\n", entry->fts_path);  			fprintf(stderr, "Scanned: %zu\n\n", counter);  			showinfo = 0;  		} diff --git a/usr.bin/find/find.h b/usr.bin/find/find.h index 1664eeb9a93f..e8bb0ca8c649 100644 --- a/usr.bin/find/find.h +++ b/usr.bin/find/find.h @@ -97,6 +97,8 @@ typedef	struct _plandata *creat_f(struct _option *, char ***);  #define	F_TIME2_B	0x00080000	/* one of -newer?B */  #endif  #define F_LINK		0x00100000	/* lname or ilname */ +/* Notes about execution */ +#define F_HAS_WARNED	0x10000000	/* Has issued a warning for maybe bad input */  /* node definition */  typedef struct _plandata { @@ -133,6 +135,7 @@ typedef struct _plandata {  		char *_a_data[2];		/* array of char pointers */  		char *_c_data;			/* char pointer */  		regex_t *_re_data;		/* regex */ +		FILE *_fprint_file;		/* file stream for -fprint */  	} p_un;  } PLAN;  #define	a_data	p_un._a_data @@ -160,6 +163,7 @@ typedef struct _plandata {  #define e_pbsize p_un.ex._e_pbsize  #define e_psizemax p_un.ex._e_psizemax  #define e_next p_un.ex._e_next +#define	fprint_file	p_un._fprint_file  typedef struct _option {  	const char *name;		/* option name */ diff --git a/usr.bin/find/function.c b/usr.bin/find/function.c index ef610903cc00..b260a71ef4a9 100644 --- a/usr.bin/find/function.c +++ b/usr.bin/find/function.c @@ -866,6 +866,49 @@ c_follow(OPTION *option, char ***argvp __unused)  	return palloc(option);  } +/* + * -fprint functions -- + * + *	Always true, causes the current pathname to be written to + *	specified file followed by a newline + */ +int +f_fprint(PLAN *plan, FTSENT *entry) +{ +	fprintf(plan->fprint_file, "%s\n", entry->fts_path); +	return 1; +} + +PLAN * +c_fprint(OPTION *option, char ***argvp) +{ +	PLAN *new; +	char *fn; + +	isoutput = 1; + +	new = palloc(option); +	fn = nextarg(option, argvp); +	new->fprint_file = fopen(fn, "w"); +	if (new->fprint_file == NULL) +		err(1, "fprint: cannot create %s", fn); + +	return (new); +} + +/* + * -fprint0 functions -- + * + *	Always true, causes the current pathname to be written to + *	specified file followed by a NUL + */ +int +f_fprint0(PLAN *plan, FTSENT *entry) +{ +	fprintf(plan->fprint_file, "%s%c", entry->fts_path, '\0'); +	return 1; +} +  #if HAVE_STRUCT_STATFS_F_FSTYPENAME  /*   * -fstype functions -- @@ -1389,6 +1432,37 @@ f_print0(PLAN *plan __unused, FTSENT *entry)  /* c_print0 is the same as c_print */  /* + * -printf functions -- + * + *	Always true. Causes information as specified in the + *	argument to be written to standard output. + */ +int +f_printf(PLAN *plan, FTSENT *entry) +{ +	do_printf(plan, entry, stdout); +	return 1; +} + +PLAN * +c_printf(OPTION *option, char ***argvp) +{ +	PLAN *new; + +	/* +	 * XXX We could scan the format looking for stat-dependent formats, and +	 * turn off the nostat bit for trival cases: `%p`/`%f`/`%h`. +	 */ +	isoutput = 1; +	ftsoptions &= ~FTS_NOSTAT; + +	new = palloc(option); +	new->c_data = nextarg(option, argvp); + +	return (new); +} + +/*   * -prune functions --   *   *	Prune a portion of the hierarchy. diff --git a/usr.bin/find/option.c b/usr.bin/find/option.c index 268803343a8d..fa09231a3152 100644 --- a/usr.bin/find/option.c +++ b/usr.bin/find/option.c @@ -83,8 +83,8 @@ static OPTION const options[] = {  #endif  // -fls  	{ "-follow",	c_follow,	f_always_true,	0 }, -// -fprint -// -fprint0 +	{ "-fprint",	c_fprint,	f_fprint,	0 }, +	{ "-fprint0",	c_fprint,	f_fprint0,	0 },  // -fprintf  #if HAVE_STRUCT_STATFS_F_FSTYPENAME  	{ "-fstype",	c_fstype,	f_fstype,	0 }, @@ -148,7 +148,7 @@ static OPTION const options[] = {  	{ "-perm",	c_perm,		f_perm,		0 },  	{ "-print",	c_print,	f_print,	0 },  	{ "-print0",	c_print,	f_print0,	0 }, -// -printf +	{ "-printf",	c_printf,	f_printf,	0 },  	{ "-prune",	c_simple,	f_prune,	0 },  	{ "-quit",	c_simple,	f_quit,		0 },  	{ "-readable",	c_simple,	f_readable,	0 }, diff --git a/usr.bin/find/printf.c b/usr.bin/find/printf.c new file mode 100644 index 000000000000..c1be04376156 --- /dev/null +++ b/usr.bin/find/printf.c @@ -0,0 +1,348 @@ +/*- + * Copyright (c) 2023, Netflix, Inc + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> + +#include <err.h> +#include <errno.h> +#include <fts.h> +#include <grp.h> +#include <pwd.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "find.h" + +/* translate \X to proper escape, or to itself if no special meaning */ +static const char *esc = "\a\bcde\fghijklm\nopq\rs\tu\v"; + +static inline bool +isoct(char c) +{ +	return (c >= '0' && c <= '7'); +} + +static inline bool +isesc(char c) +{ +	return (c >= 'a' && c <= 'v' && esc[c - 'a'] != c); +} + +static char * +escape(const char *str, bool *flush, bool *warned) +{ +	char c; +	int value; +	char *tmpstr; +	size_t tmplen; +	FILE *fp; + +	fp = open_memstream(&tmpstr, &tmplen); + +	/* +	 * Copy the str string into a new struct sbuf and return that expanding +	 * the different ANSI escape sequences. +	 */ +	*flush = false; +	for (c = *str++; c; c = *str++) { +		if (c != '\\') { +			putc(c, fp); +			continue; +		} +		c = *str++; + +		/* +		 * User error \ at end of string +		 */ +		if (c == '\0') { +			putc('\\', fp); +			break; +		} + +		/* +		 * \c terminates output now and is supposed to flush the output +		 * too... +		 */ +		if (c == 'c') { +			*flush = true; +			break; +		} + +		/* +		 * Is it octal? If so, decode up to 3 octal characters. +		 */ +		if (isoct(c)) { +			value = 0; +			for (int i = 3; i-- > 0 && isoct(c); +			     c = *str++) { +				value <<= 3; +				value += c - '0'; +			} +			str--; +			putc((char)value, fp); +			continue; +		} + +		/* +		 * It's an ANSI X3.159-1989 escape, use the mini-escape lookup +		 * table to translate. +		 */ +		if (isesc(c)) { +			putc(esc[c - 'a'], fp); +			continue; +		} + +		/* +		 * Otherwise, it's self inserting. gnu find specifically says +		 * not to rely on this behavior though. gnu find will issue +		 * a warning here, while printf(1) won't. +		 */ +		if (!*warned) { +			warn("Unknown character %c after \\.", c); +			*warned = true; +		} +		putc(c, fp); +	} +	fclose(fp); + +	return (tmpstr); +} + +static void +fp_ctime(FILE *fp, time_t t) +{ +	char s[26]; + +	ctime_r(&t, s); +	s[24] = '\0';	/* kill newline, though gnu find info silent on issue */ +	fputs(s, fp); +} + +/* + * Assumes all times are displayed in UTC rather than local time, gnu find info + * page silent on the issue. + * + * Also assumes that gnu find doesn't support multiple character escape sequences, + * which it's info page is also silent on. + */ +static void +fp_strftime(FILE *fp, time_t t, char mod) +{ +	struct tm tm; +	char buffer[128]; +	char fmt[3] = "% "; + +	/* +	 * Gnu libc extension we don't yet support -- seconds since epoch +	 * Used in Linux kernel build, so we kinda have to support it here +	 */ +	if (mod == '@')	{ +		fprintf(fp, "%ju", (uintmax_t)t); +		return; +	} + +	gmtime_r(&t, &tm); +	fmt[1] = mod; +	if (strftime(buffer, sizeof(buffer), fmt, &tm) == 0) +		errx(1, "Format bad or data too long for buffer"); /* Can't really happen ??? */ +	fputs(buffer, fp); +} + +void +do_printf(PLAN *plan, FTSENT *entry, FILE *fout) +{ +	char buf[4096]; +	struct stat sb; +	struct stat *sp; +	const char *path, *pend; +	char *all, *fmt; +	ssize_t ret; +	int c; +	bool flush, warned; + +	warned = (plan->flags & F_HAS_WARNED) != 0; +	all = fmt = escape(plan->c_data, &flush, &warned); +	if (warned) +		plan->flags |= F_HAS_WARNED; +	for (c = *fmt++; c; c = *fmt++) { +		sp = entry->fts_statp; +		if (c != '%') { +			putc(c, fout); +			continue; +		} +		c = *fmt++; +		/* Style(9) deviation: case order same as gnu find info doc */ +		switch (c) { +		case '%': +			putc(c, fout); +			break; +		case 'p': /* Path to file */ +			fputs(entry->fts_path, fout); +			break; +		case 'f': /* filename w/o dirs */ +			fputs(entry->fts_name, fout); +			break; +		case 'h': +			/* +			 * path, relative to the starting point, of the file, or +			 * '.' if that's empty for some reason. +			 */ +			path = entry->fts_path; +			pend = strrchr(path, '/'); +			if (pend == NULL) +				putc('.', fout); +			else +				fwrite(path, pend - path, 1, fout); +			break; +		case 'P': /* file with command line arg rm'd -- HOW? fts_parent? */ +			errx(1, "%%%c is unimplemented", c); +		case 'H': /* Command line arg -- HOW? */ +			errx(1, "%%%c is unimplemented", c); +		case 'g': /* gid human readable */ +			fputs(group_from_gid(sp->st_gid, 0), fout); +			break; +		case 'G': /* gid numeric */ +			fprintf(fout, "%d", sp->st_gid); +			break; +		case 'u': /* uid human readable */ +			fputs(user_from_uid(sp->st_uid, 0), fout); +			break; +		case 'U': /* uid numeric */ +			fprintf(fout, "%d", sp->st_uid); +			break; +		case 'm': /* mode in octal */ +			fprintf(fout, "%o", sp->st_mode & 07777); +			break; +		case 'M': /* Mode in ls-standard form */ +			strmode(sp->st_mode, buf); +			fwrite(buf, 10, 1, fout); +			break; +		case 'k': /* kbytes used by file */ +			fprintf(fout, "%jd", (intmax_t)sp->st_blocks / 2); +			break; +		case 'b': /* blocks used by file */ +			fprintf(fout, "%jd", (intmax_t)sp->st_blocks); +			break; +		case 's': /* size in bytes of file */ +			fprintf(fout, "%ju", (uintmax_t)sp->st_size); +			break; +		case 'S': /* sparseness of file */ +			fprintf(fout, "%3.1f", +			    (float)sp->st_blocks * 512 / (float)sp->st_size); +			break; +		case 'd': /* Depth in tree */ +			fprintf(fout, "%ld", entry->fts_level); +			break; +		case 'D': /* device number */ +			fprintf(fout, "%ju", (uintmax_t)sp->st_dev); +			break; +		case 'F': /* Filesystem type */ +			errx(1, "%%%c is unimplemented", c); +		case 'l': /* object of symbolic link */ +			ret = readlink(entry->fts_accpath, buf, sizeof(buf)); +			if (ret > 0) +				fwrite(buf, ret, 1, fout); +			break; +		case 'i': /* inode # */ +			fprintf(fout, "%ju", (uintmax_t)sp->st_ino); +			break; +		case 'n': /* number of hard links */ +			fprintf(fout, "%ju", (uintmax_t)sp->st_nlink); +			break; +		case 'Y': /* -type of file, following 'l' types L loop ? error */ +			if (S_ISLNK(sp->st_mode)) { +				if (stat(entry->fts_accpath, &sb) != 0) { +					switch (errno) { +					case ELOOP: +						putc('L', fout); +						break; +					case ENOENT: +						putc('N', fout); +						break; +					default: +						putc('?', fout); +						break; +					} +					break; +				} +				sp = &sb; +			} +			/* FALLTHROUGH */ +		case 'y': /* -type of file, incl 'l' */ +			switch (sp->st_mode & S_IFMT) { +			case S_IFIFO: +				putc('p', fout); +				break; +			case S_IFCHR: +				putc('c', fout); +				break; +			case S_IFDIR: +				putc('d', fout); +				break; +			case S_IFBLK: +				putc('b', fout); +				break; +			case S_IFREG: +				putc('f', fout); +				break; +			case S_IFLNK: +				putc('l', fout); +				break; +			case S_IFSOCK: +				putc('s', fout); +				break; +			case S_IFWHT: +				putc('w', fout); +				break; +			default: +				putc('U', fout); +				break; +			} +			break; +		case 'a': /* access time ctime */ +			fp_ctime(fout, sp->st_atime); +			break; +		case 'A': /* access time with next char strftime format */ +			fp_strftime(fout, sp->st_atime, *fmt++); +			break; +		case 'B': /* birth time with next char strftime format */ +#ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME +			if (sp->st_birthtime != 0) +				fp_strftime(fout, sp->st_birthtime, *fmt); +#endif +			fmt++; +			break;	/* blank on systems that don't support it */ +		case 'c': /* status change time ctime */ +			fp_ctime(fout, sp->st_ctime); +			break; +		case 'C': /* status change time with next char strftime format */ +			fp_strftime(fout, sp->st_ctime, *fmt++); +			break; +		case 't': /* modification change time ctime */ +			fp_ctime(fout, sp->st_mtime); +			break; +		case 'T': /* modification time with next char strftime format */ +			fp_strftime(fout, sp->st_mtime, *fmt++); +			break; +		case 'Z': /* empty string for compat SELinux context string */ +			break; +		/* Modifier parsing here, but also need to modify above somehow */ +		case '#': case '-': case '0': case '1': case '2': case '3': case '4': +		case '5': case '6': case '7': case '8': case '9': case '.': +			errx(1, "Format modifier %c not yet supported: '%s'", c, all); +		/* Any FeeeBSD-specific modifications here -- none yet */ +		default: +			errx(1, "Unknown format %c '%s'", c, all); +		} +	} +	if (flush) +		fflush(fout); +	free(all); +} diff --git a/usr.bin/find/tests/find_test.sh b/usr.bin/find/tests/find_test.sh index 8b8c23688018..99d2f6af4d45 100755 --- a/usr.bin/find/tests/find_test.sh +++ b/usr.bin/find/tests/find_test.sh @@ -1,5 +1,8 @@ +#- +# SPDX-License-Identifier: BSD-2-Clause  #  # Copyright 2017, Conrad Meyer <cem@FreeBSD.org>. +# Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org>  #  # Redistribution and use in source and binary forms, with or without  # modification, are permitted provided that the following conditions are @@ -64,8 +67,116 @@ find_samefile_link_body()  	atf_check -s exit:0 -o "inline:test/link2\n" find test -samefile test/link2  } +atf_test_case find_printf +find_printf_head() +{ +	atf_set "descr" "Test the -printf primary" +} +find_printf_body() +{ +	mkdir dir +	chmod 0755 dir +	jot -b hello 1024 >dir/file +	chmod 0644 dir/file +	ln -s file dir/link +	chmod -h 0444 dir/link +	local db=$(stat -f %b dir) +	local fb=$(stat -f %b dir/file) +	local lb=$(stat -f %b dir/link) + +	# paths +	atf_check -o inline:"dir\ndir/file\ndir/link\n" \ +	    find -s dir -printf '%p\n' +	atf_check -o inline:"dir\nfile\nlink\n" \ +	    find -s dir -printf '%f\n' +	atf_check -o inline:".\ndir\ndir\n" \ +	    find -s dir -printf '%h\n' +	atf_check -s exit:1 -e match:"unimplemented" -o ignore \ +	    find -s dir -printf '%P\n' +	atf_check -s exit:1 -e match:"unimplemented" -o ignore \ +	    find -s dir -printf '%H\n' + +	# group +	atf_check -o inline:"$(stat -f %Sg dir dir/file dir/link)\n" \ +	    find -s dir -printf '%g\n' +	atf_check -o inline:"$(stat -f %g dir dir/file dir/link)\n" \ +	    find -s dir -printf '%G\n' + +	# owner +	atf_check -o inline:"$(stat -f %Su dir dir/file dir/link)\n" \ +	    find -s dir -printf '%u\n' +	atf_check -o inline:"$(stat -f %u dir dir/file dir/link)\n" \ +	    find -s dir -printf '%U\n' + +	# mode +	atf_check -o inline:"$(stat -f %Lp dir dir/file dir/link)\n" \ +	    find -s dir -printf '%m\n' +	atf_check -o inline:"$(stat -f %Sp dir dir/file dir/link)\n" \ +	    find -s dir -printf '%M\n' + +	# size +	atf_check -o inline:"$((db/2))\n$((fb/2))\n$((lb/2))\n" \ +	    find -s dir -printf '%k\n' +	atf_check -o inline:"$db\n$fb\n$lb\n" \ +	    find -s dir -printf '%b\n' +	atf_check -o inline:"$(stat -f %z dir dir/file dir/link)\n" \ +	    find -s dir -printf '%s\n' +	# XXX test %S properly +	atf_check -o ignore \ +	    find -s dir -printf '%S\n' +	atf_check -o inline:"0\n1\n1\n" \ +	    find -s dir -printf '%d\n' + +	# device +	atf_check -o inline:"$(stat -f %d dir dir/file dir/link)\n" \ +	    find -s dir -printf '%D\n' +	atf_check -s exit:1 -e match:"unimplemented" -o ignore \ +	    find -s dir -printf '%F\n' + +	# link target +	atf_check -o inline:"\n\nfile\n" \ +	    find -s dir -printf '%l\n' + +	# inode +	atf_check -o inline:"$(stat -f %i dir dir/file dir/link)\n" \ +	    find -s dir -printf '%i\n' + +	# nlinks +	atf_check -o inline:"$(stat -f %l dir dir/file dir/link)\n" \ +	    find -s dir -printf '%n\n' + +	# type +	atf_check -o inline:"d\nf\nl\n" \ +	    find -s dir -printf '%y\n' +	atf_check -o inline:"d\nf\nf\n" \ +	    find -s dir -printf '%Y\n' + +	# access time +	atf_check -o inline:"$(stat -f %Sa -t '%a %b %e %T %Y' dir dir/file dir/link)\n" \ +	    find -s dir -printf '%a\n' +	atf_check -o inline:"$(stat -f %Sa -t '%e' dir dir/file dir/link)\n" \ +	    find -s dir -printf '%Ae\n' + +	# birth time +	atf_check -o inline:"$(stat -f %SB -t '%e' dir dir/file dir/link)\n" \ +	    find -s dir -printf '%Be\n' + +	# inode change time +	atf_check -o inline:"$(stat -f %Sc -t '%a %b %e %T %Y' dir dir/file dir/link)\n" \ +	    find -s dir -printf '%c\n' +	atf_check -o inline:"$(stat -f %Sc -t '%e' dir dir/file dir/link)\n" \ +	    find -s dir -printf '%Ce\n' + +	# modification time +	atf_check -o inline:"$(stat -f %Sm -t '%a %b %e %T %Y' dir dir/file dir/link)\n" \ +	    find -s dir -printf '%t\n' +	atf_check -o inline:"$(stat -f %Sm -t '%e' dir dir/file dir/link)\n" \ +	    find -s dir -printf '%Te\n' +} +  atf_init_test_cases()  {  	atf_add_test_case find_newer_link  	atf_add_test_case find_samefile_link +	atf_add_test_case find_printf  } diff --git a/usr.bin/fortune/datfiles/freebsd-tips b/usr.bin/fortune/datfiles/freebsd-tips index 1e9501e3a6fb..6a2b59ff5fa7 100644 --- a/usr.bin/fortune/datfiles/freebsd-tips +++ b/usr.bin/fortune/datfiles/freebsd-tips @@ -555,7 +555,7 @@ Use "sysrc name=value" to add an entry and "sysrc -x name" to delete an entry.  You can upload the dmesg of your system to help developers get an overview of commonly  used hardware and peripherals for FreeBSD. Use the curl package to upload it like this:  curl -v -d "nickname=$USER" -d "description=FreeBSD/$(uname -m) on \ -$(kenv smbios.system.maker) $(kenv smbios.system.product)" -d "do=addd" \ +$(kenv smbios.system.maker) $(kenv smbios.system.product)" -d "do=add" \  --data-urlencode 'dmesg@/var/run/dmesg.boot' http://dmesgd.nycbug.org/index.cgi  %  Want to know how much memory (in bytes) your machine has installed? Let diff --git a/usr.bin/fstat/Makefile b/usr.bin/fstat/Makefile index fa51a92eb52f..f8617fd0c6a4 100644 --- a/usr.bin/fstat/Makefile +++ b/usr.bin/fstat/Makefile @@ -3,6 +3,6 @@ SRCS=	fstat.c fuser.c main.c  LINKS=	${BINDIR}/fstat ${BINDIR}/fuser  LIBADD=	procstat -MAN1=	fuser.1 fstat.1 +MAN=	fuser.1 fstat.1  .include <bsd.prog.mk> diff --git a/usr.bin/getconf/sysconf.gperf b/usr.bin/getconf/sysconf.gperf index baf341c8962b..2bd75dd47851 100644 --- a/usr.bin/getconf/sysconf.gperf +++ b/usr.bin/getconf/sysconf.gperf @@ -47,6 +47,7 @@ OPEN_MAX, _SC_OPEN_MAX  PAGESIZE, _SC_PAGESIZE  PAGE_SIZE, _SC_PAGESIZE  PASS_MAX, _SC_PASS_MAX +PHYS_PAGES, _SC_PHYS_PAGES  PTHREAD_DESTRUCTOR_ITERATIONS, _SC_THREAD_DESTRUCTOR_ITERATIONS   PTHREAD_KEYS_MAX, _SC_THREAD_KEYS_MAX   PTHREAD_STACK_MIN, _SC_THREAD_STACK_MIN  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/grep/Makefile b/usr.bin/grep/Makefile index 2204758ece5a..c72b86656148 100644 --- a/usr.bin/grep/Makefile +++ b/usr.bin/grep/Makefile @@ -6,7 +6,7 @@  PACKAGE=	runtime  PROG=	grep -MAN1=	grep.1 zgrep.1 +MAN=	grep.1 zgrep.1  SRCS=	file.c grep.c queue.c util.c diff --git a/usr.bin/grep/util.c b/usr.bin/grep/util.c index ed87e56956f6..5b40405852b3 100644 --- a/usr.bin/grep/util.c +++ b/usr.bin/grep/util.c @@ -728,6 +728,8 @@ void grep_printline(struct str *line, int sep) {  	printline_metadata(line, sep);  	fwrite(line->dat, line->len, 1, stdout);  	putchar(fileeol); + +	fflush(stdout);  }  static void @@ -834,6 +836,7 @@ printline(struct parsec *pc, int sep, size_t *last_out)  				*last_out = pc->ln.len;  			}  			putchar('\n'); +			fflush(stdout);  		} else if (!oflag) {  			/*  			 * -o is terminated on every match output, so this @@ -843,6 +846,8 @@ printline(struct parsec *pc, int sep, size_t *last_out)  			 * to terminate if it needs to.  			 */  			terminated = false; +		} else { +			fflush(stdout);  		}  	} else  		grep_printline(&pc->ln, sep); diff --git a/usr.bin/id/id.1 b/usr.bin/id/id.1 index b8dafb6650b0..62c941f84798 100644 --- a/usr.bin/id/id.1 +++ b/usr.bin/id/id.1 @@ -28,7 +28,7 @@  .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF  .\" SUCH DAMAGE.  .\" -.Dd March 5, 2011 +.Dd October 23, 2025  .Dt ID 1  .Os  .Sh NAME @@ -50,12 +50,18 @@  .Nm  .Fl c  .Nm +.Fl d +.Op Ar user +.Nm  .Fl g Op Fl nr  .Op Ar user  .Nm  .Fl p  .Op Ar user  .Nm +.Fl s +.Op Ar user +.Nm  .Fl u Op Fl nr  .Op Ar user  .Sh DESCRIPTION @@ -90,6 +96,8 @@ Ignored for compatibility with other  implementations.  .It Fl c  Display current login class. +.It Fl d +Display the home directory of the current or specified user.  .It Fl g  Display the effective group ID as a number.  .It Fl n @@ -128,6 +136,8 @@ Display the real ID for the  and  .Fl u  options instead of the effective ID. +.It Fl s +Display the shell of the current or specified user.  .It Fl u  Display the effective user ID as a number.  .El @@ -174,8 +184,20 @@ bob          pts/5        Dec  4 19:51  .Sh STANDARDS  The  .Nm -function is expected to conform to -.St -p1003.2 . +utility is expected to conform to +.St -p1003.1-2024 . +The +.Fl A , +.Fl M , +.Fl P , +.Fl c , +.Fl d , +.Fl p , +and +.Fl s +options are +.Fx +extensions.  .Sh HISTORY  The  historic diff --git a/usr.bin/id/id.c b/usr.bin/id/id.c index dfd2e89a7e78..5f9d2670caa3 100644 --- a/usr.bin/id/id.c +++ b/usr.bin/id/id.c @@ -40,91 +40,107 @@  #include <errno.h>  #include <grp.h>  #include <pwd.h> +#include <stdbool.h>  #include <stdint.h>  #include <stdio.h>  #include <stdlib.h>  #include <string.h>  #include <unistd.h> -static void	id_print(struct passwd *, int, int, int); +static void	id_print(struct passwd *);  static void	pline(struct passwd *);  static void	pretty(struct passwd *);  #ifdef USE_BSM_AUDIT  static void	auditid(void);  #endif -static void	group(struct passwd *, int); +static void	group(struct passwd *, bool);  static void	maclabel(void); +static void	dir(struct passwd *); +static void	shell(struct passwd *);  static void	usage(void);  static struct passwd *who(char *); -static int isgroups, iswhoami; +static bool isgroups, iswhoami;  int  main(int argc, char *argv[])  {  	struct group *gr;  	struct passwd *pw; -	int Gflag, Mflag, Pflag, ch, gflag, id, nflag, pflag, rflag, uflag; -	int Aflag, cflag; -	int error; -	const char *myname; +#ifdef USE_BSM_AUDIT +	bool Aflag; +#endif +	bool Gflag, Mflag, Pflag; +	bool cflag, dflag, gflag, nflag, pflag, rflag, sflag, uflag; +	int ch, combo, error, id; +	const char *myname, *optstr;  	char loginclass[MAXLOGNAME]; -	Gflag = Mflag = Pflag = gflag = nflag = pflag = rflag = uflag = 0; -	Aflag = cflag = 0; +#ifdef USE_BSM_AUDIT +	Aflag = false; +#endif +	Gflag = Mflag = Pflag = false; +	cflag = dflag = gflag = nflag = pflag = rflag = sflag = uflag = false; -	myname = strrchr(argv[0], '/'); -	myname = (myname != NULL) ? myname + 1 : argv[0]; +	myname = getprogname(); +	optstr = "AGMPacdgnprsu";  	if (strcmp(myname, "groups") == 0) { -		isgroups = 1; -		Gflag = nflag = 1; +		isgroups = true; +		optstr = ""; +		Gflag = nflag = true;  	}  	else if (strcmp(myname, "whoami") == 0) { -		iswhoami = 1; -		uflag = nflag = 1; +		iswhoami = true; +		optstr = ""; +		uflag = nflag = true;  	} -	while ((ch = getopt(argc, argv, -	    (isgroups || iswhoami) ? "" : "APGMacgnpru")) != -1) +	while ((ch = getopt(argc, argv, optstr)) != -1) {  		switch(ch) {  #ifdef USE_BSM_AUDIT  		case 'A': -			Aflag = 1; +			Aflag = true;  			break;  #endif  		case 'G': -			Gflag = 1; +			Gflag = true;  			break;  		case 'M': -			Mflag = 1; +			Mflag = true;  			break;  		case 'P': -			Pflag = 1; +			Pflag = true;  			break;  		case 'a':  			break;  		case 'c': -			cflag = 1; +			cflag = true; +			break; +		case 'd': +			dflag = true;  			break;  		case 'g': -			gflag = 1; +			gflag = true;  			break;  		case 'n': -			nflag = 1; +			nflag = true;  			break;  		case 'p': -			pflag = 1; +			pflag = true;  			break;  		case 'r': -			rflag = 1; +			rflag = true; +			break; +		case 's': +			sflag = true;  			break;  		case 'u': -			uflag = 1; +			uflag = true;  			break; -		case '?':  		default:  			usage();  		} +	}  	argc -= optind;  	argv += optind; @@ -133,16 +149,13 @@ main(int argc, char *argv[])  	if ((cflag || Aflag || Mflag) && argc > 0)  		usage(); -	switch(Aflag + Gflag + Mflag + Pflag + gflag + pflag + uflag) { -	case 1: -		break; -	case 0: -		if (!nflag && !rflag) -			break; -		/* FALLTHROUGH */ -	default: +	combo = Aflag + Gflag + Mflag + Pflag + gflag + pflag + uflag; +	if (combo + dflag + sflag > 1) +		usage(); +	if (combo > 1) +		usage(); +	if (combo == 0 && (nflag || rflag))  		usage(); -	}  	pw = *argv ? who(*argv) : NULL; @@ -182,6 +195,11 @@ main(int argc, char *argv[])  		exit(0);  	} +	if (dflag) { +		dir(pw); +		exit(0); +	} +  	if (Gflag) {  		group(pw, nflag);  		exit(0); @@ -202,14 +220,12 @@ main(int argc, char *argv[])  		exit(0);  	} -	if (pw) { -		id_print(pw, 1, 0, 0); -	} -	else { -		id = getuid(); -		pw = getpwuid(id); -		id_print(pw, 0, 1, 1); +	if (sflag) { +		shell(pw); +		exit(0);  	} + +	id_print(pw);  	exit(0);  } @@ -223,7 +239,7 @@ pretty(struct passwd *pw)  	if (pw) {  		(void)printf("uid\t%s\n", pw->pw_name);  		(void)printf("groups\t"); -		group(pw, 1); +		group(pw, true);  	} else {  		if ((login = getlogin()) == NULL)  			err(1, "getlogin"); @@ -249,12 +265,12 @@ pretty(struct passwd *pw)  				(void)printf("rgid\t%u\n", rid);  		}  		(void)printf("groups\t"); -		group(NULL, 1); +		group(NULL, true);  	}  }  static void -id_print(struct passwd *pw, int use_ggl, int p_euid, int p_egid) +id_print(struct passwd *pw)  {  	struct group *gr;  	gid_t gid, egid, lastgid; @@ -263,21 +279,24 @@ id_print(struct passwd *pw, int use_ggl, int p_euid, int p_egid)  	long ngroups_max;  	gid_t *groups;  	const char *fmt; +	bool print_dbinfo; -	if (pw != NULL) { +	print_dbinfo = pw != NULL; +	if (print_dbinfo) {  		uid = pw->pw_uid;  		gid = pw->pw_gid;  	}  	else {  		uid = getuid();  		gid = getgid(); +		pw = getpwuid(uid);  	}  	ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1;  	if ((groups = malloc(sizeof(gid_t) * ngroups_max)) == NULL)  		err(1, "malloc"); -	if (use_ggl && pw != NULL) { +	if (print_dbinfo) {  		ngroups = ngroups_max;  		getgrouplist(pw->pw_name, gid, groups, &ngroups);  	} @@ -285,19 +304,23 @@ id_print(struct passwd *pw, int use_ggl, int p_euid, int p_egid)  		ngroups = getgroups(ngroups_max, groups);  	} +	/* +	 * We always resolve uids and gids where we can to a name, even if we +	 * are printing the running process credentials, to be nice. +	 */  	if (pw != NULL)  		printf("uid=%u(%s)", uid, pw->pw_name); -	else  -		printf("uid=%u", getuid()); +	else +		printf("uid=%u", uid);  	printf(" gid=%u", gid);  	if ((gr = getgrgid(gid)))  		(void)printf("(%s)", gr->gr_name); -	if (p_euid && (euid = geteuid()) != uid) { +	if (!print_dbinfo && (euid = geteuid()) != uid) {  		(void)printf(" euid=%u", euid);  		if ((pw = getpwuid(euid)))  			(void)printf("(%s)", pw->pw_name);  	} -	if (p_egid && (egid = getegid()) != gid) { +	if (!print_dbinfo && (egid = getegid()) != gid) {  		(void)printf(" egid=%u", egid);  		if ((gr = getgrgid(egid)))  			(void)printf("(%s)", gr->gr_name); @@ -365,7 +388,7 @@ auditid(void)  #endif  static void -group(struct passwd *pw, int nflag) +group(struct passwd *pw, bool nflag)  {  	struct group *gr;  	int cnt, id, lastid, ngroups; @@ -451,41 +474,57 @@ who(char *u)  static void  pline(struct passwd *pw)  { - -	if (!pw) { +	if (pw == NULL) {  		if ((pw = getpwuid(getuid())) == NULL)  			err(1, "getpwuid");  	} -  	(void)printf("%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s\n", pw->pw_name, -			pw->pw_passwd, pw->pw_uid, pw->pw_gid, pw->pw_class, -			(long)pw->pw_change, (long)pw->pw_expire, pw->pw_gecos, -			pw->pw_dir, pw->pw_shell); +	    pw->pw_passwd, pw->pw_uid, pw->pw_gid, pw->pw_class, +	    (long)pw->pw_change, (long)pw->pw_expire, pw->pw_gecos, +	    pw->pw_dir, pw->pw_shell);  } +static void +dir(struct passwd *pw) +{ +	if (pw == NULL) { +		if ((pw = getpwuid(getuid())) == NULL) +			err(1, "getpwuid"); +	} +	printf("%s\n", pw->pw_dir); +}  static void -usage(void) +shell(struct passwd *pw)  { +	if (pw == NULL) { +		if ((pw = getpwuid(getuid())) == NULL) +			err(1, "getpwuid"); +	} +	printf("%s\n", pw->pw_shell); +} +static void +usage(void) +{  	if (isgroups)  		(void)fprintf(stderr, "usage: groups [user]\n");  	else if (iswhoami)  		(void)fprintf(stderr, "usage: whoami\n");  	else -		(void)fprintf(stderr, "%s\n%s%s\n%s\n%s\n%s\n%s\n%s\n%s\n", -		    "usage: id [user]", +		(void)fprintf(stderr, +		    "usage: id [user]\n"  #ifdef USE_BSM_AUDIT -		    "       id -A\n", -#else -		    "", +		    "       id -A\n"  #endif -		    "       id -G [-n] [user]", -		    "       id -M", -		    "       id -P [user]", -		    "       id -c", -		    "       id -g [-nr] [user]", -		    "       id -p [user]", -		    "       id -u [-nr] [user]"); +		    "       id -G [-n] [user]\n" +		    "       id -M\n" +		    "       id -P [user]\n" +		    "       id -c\n" +		    "       id -d [user]\n" +		    "       id -g [-nr] [user]\n" +		    "       id -p [user]\n" +		    "       id -s [user]\n" +		    "       id -u [-nr] [user]\n");  	exit(1);  } diff --git a/usr.bin/indent/indent.c b/usr.bin/indent/indent.c index 4739e861fef9..3ea78e1f153e 100644 --- a/usr.bin/indent/indent.c +++ b/usr.bin/indent/indent.c @@ -83,7 +83,6 @@ const char *out_name = "Standard Output";	/* will always point to name  						 * of output file */  const char *simple_backup_suffix = ".BAK";	/* Suffix to use for backup  						 * files */ -char        bakfile[MAXPATHLEN] = "";  int  main(int argc, char **argv) @@ -1231,41 +1230,35 @@ check_type:  }  /* - * copy input file to backup file if in_name is /blah/blah/blah/file, then - * backup file will be ".Bfile" then make the backup file the input and + * copy input file to backup file then make the backup file the input and   * original input file the output   */  static void  bakcopy(void)  { -    int         n, -                bakchn; -    char        buff[8 * 1024]; -    const char *p; - -    /* construct file name .Bfile */ -    for (p = in_name; *p; p++);	/* skip to end of string */ -    while (p > in_name && *p != '/')	/* find last '/' */ -	p--; -    if (*p == '/') -	p++; -    sprintf(bakfile, "%s%s", p, simple_backup_suffix); +    static char buff[8 * 1024]; +    char *bakfile; +    ssize_t len; +    int bakfd; + +    /* generate the backup file name */ +    if (asprintf(&bakfile, "%s%s", in_name, simple_backup_suffix) < 0) +	err(1, "%s%s", in_name, simple_backup_suffix);      /* copy in_name to backup file */ -    bakchn = creat(bakfile, 0600); -    if (bakchn < 0) +    bakfd = open(bakfile, O_RDWR | O_CREAT | O_TRUNC, 0600); +    if (bakfd < 0)  	err(1, "%s", bakfile); -    while ((n = read(fileno(input), buff, sizeof(buff))) > 0) -	if (write(bakchn, buff, n) != n) +    while ((len = read(fileno(input), buff, sizeof(buff))) > 0) +	if (write(bakfd, buff, len) != len)  	    err(1, "%s", bakfile); -    if (n < 0) +    if (len < 0)  	err(1, "%s", in_name); -    close(bakchn);      fclose(input);      /* re-open backup file as the input file */ -    input = fopen(bakfile, "r"); -    if (input == NULL) +    input = fdopen(bakfd, "r"); +    if (input == NULL || fseek(input, 0, SEEK_SET) != 0)  	err(1, "%s", bakfile);      /* now the original input file will be the output */      output = fopen(in_name, "w"); @@ -1273,6 +1266,7 @@ bakcopy(void)  	unlink(bakfile);  	err(1, "%s", in_name);      } +    free(bakfile);  }  static void diff --git a/usr.bin/indent/tests/functional_test.sh b/usr.bin/indent/tests/functional_test.sh index 44538e5b6e12..9cfe5878f69d 100755 --- a/usr.bin/indent/tests/functional_test.sh +++ b/usr.bin/indent/tests/functional_test.sh @@ -1,6 +1,9 @@ +#- +# SPDX-License-Identifier: BSD-2-Clause  #  # Copyright 2016 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 are @@ -29,61 +32,51 @@ SRCDIR=$(atf_get_srcdir)  check()  { -	local tc=${1}; shift - -	local indent=$(atf_config_get usr.bin.indent.test_indent /usr/bin/indent) - -	# All of the files need to be in the ATF sandbox in order for the tests -	# to pass. -	atf_check cp ${SRCDIR}/${tc}* . +	local tc=${1} +	local profile_flag -	# Remove $FreeBSD$ RCS expansions because they get re-indented, which -	# changes the output -	local out_file="${tc}.stdout" -	if [ -f "${out_file}" ]; then -		parsed_file=output_file.parsed +	cp "${SRCDIR}/${tc%.[0-9]}".* . -		atf_check -o save:$parsed_file sed -e '/\$FreeBSD.*\$/d' \ -		    ${tc}.stdout -		out_flag="-o file:$parsed_file" -	fi -	local profile_file="${tc}.pro" -	if [ -f "${profile_file}" ]; then -		profile_flag="-P${profile_file}" +	if [ -f "${tc}.pro" ]; then +		profile_flag="-P${tc}.pro"  	else  		# Make sure we don't implicitly use ~/.indent.pro from the test  		# host, for determinism purposes.  		profile_flag="-npro"  	fi -	sed -e '/\$FreeBSD.*\$/d' ${tc} > input_file.parsed -	atf_check -s exit:${tc##*.} ${out_flag} ${indent} ${profile_flag} < input_file.parsed +	atf_check -s exit:${tc##*.} -o file:"${tc}.stdout" \ +	    indent ${profile_flag} < "${tc}"  } -add_testcase() +add_legacy_testcase()  {  	local tc=${1} -	local tc_escaped word -	case "${tc%.*}" in -	*-*) -		local IFS="-" -		for word in ${tc%.*}; do -			tc_escaped="${tc_escaped:+${tc_escaped}_}${word}" -		done -		;; -	*) -		tc_escaped=${tc%.*} -		;; -	esac +	atf_test_case ${tc%.[0-9]} +	eval "${tc%.[0-9]}_body() { check ${tc}; }" +	atf_add_test_case ${tc%.[0-9]} +} + +atf_test_case backup_suffix +backup_suffix_body() +{ +	local argmax=$(sysctl -n kern.argmax) +	local suffix=$(jot -b .bak -s '' $((argmax/5))) +	local code=$'int main() {}\n' + +	printf "${code}" >input.c + +	atf_check indent input.c +	atf_check -o inline:"${code}" cat input.c.BAK -	atf_test_case ${tc_escaped} -	eval "${tc_escaped}_body() { check ${tc}; }" -	atf_add_test_case ${tc_escaped} +	atf_check -s exit:1 -e match:"name too long"\ +	    env SIMPLE_BACKUP_SUFFIX=${suffix} indent input.c  }  atf_init_test_cases()  { -	for path in $(find -Es "${SRCDIR}" -regex '.*\.[0-9]+$'); do -		add_testcase ${path##*/} +	for tc in $(find -s "${SRCDIR}" -name '*.[0-9]'); do +		add_legacy_testcase "${tc##*/}"  	done +	atf_add_test_case backup_suffix  } diff --git a/usr.bin/iscsictl/iscsictl.8 b/usr.bin/iscsictl/iscsictl.8 index 74394063a007..88c79c297848 100644 --- a/usr.bin/iscsictl/iscsictl.8 +++ b/usr.bin/iscsictl/iscsictl.8 @@ -24,7 +24,7 @@  .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF  .\" SUCH DAMAGE.  .\" -.Dd December 27, 2018 +.Dd July 16, 2025  .Dt ISCSICTL 8  .Os  .Sh NAME @@ -88,7 +88,7 @@ Generate output via  .Xr libxo 3  in a selection of different human and machine readable formats.  See -.Xr xo_parse_args 3 +.Xr xo_options 7  for details on command line arguments.  .It Fl A  Add session. @@ -190,7 +190,7 @@ Disconnect all iSCSI sessions:  .Dl Nm Fl Ra  .Sh SEE ALSO  .Xr libxo 3 , -.Xr xo_parse_args 3 , +.Xr xo_options 7 ,  .Xr iscsi 4 ,  .Xr iscsi.conf 5 ,  .Xr iscsid 8 diff --git a/usr.bin/kyua/Makefile b/usr.bin/kyua/Makefile index d3a7b9b61f64..d6131651afbf 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 @@ -32,7 +32,7 @@ MAN=		kyua-about.1 \  CFLAGS+=	-I${KYUA_SRCDIR} -I${.CURDIR}  CFLAGS+=	-I${SRCTOP}/contrib/lutok/include -CFLAGS+=	-I${SRCTOP}/contrib/sqlite3 +CFLAGS+=	-I${SYSROOT:U${DESTDIR}}/${INCLUDEDIR}/private/sqlite3  CFLAGS+=	-DHAVE_CONFIG_H  # We compile the kyua libraries as part of the main executable as this saves @@ -129,7 +129,8 @@ SRCS+=	engine/atf.cpp			\  	engine/execenv/execenv_host.cpp  SRCS+=	os/freebsd/execenv_jail_manager.cpp	\ -	os/freebsd/main.cpp +	os/freebsd/main.cpp			\ +	os/freebsd/reqs_checker_kmods.cpp  SRCS+=	store/dbtypes.cpp		\  	store/exceptions.cpp		\ @@ -181,25 +182,25 @@ FILESGROUPS+=	EXAMPLES  CONFS=		kyua.conf-default  CONFSDIR=	${KYUA_CONFDIR}  CONFSNAME=	kyua.conf -CONFSDIRTAGS=	package=tests +CONFSDIRTAGS=	package=kyua  DOCS=		AUTHORS CONTRIBUTORS LICENSE  DOCSDIR=	${KYUA_DOCDIR} -DOCSTAGS=	package=tests +DOCSTAGS=	package=kyua  EXAMPLES=	Kyuafile.top kyua.conf  EXAMPLESDIR=	${KYUA_EGDIR} -EXAMPLESTAGS=	package=tests +EXAMPLESTAGS=	package=kyua  .PATH:		${KYUA_SRCDIR}/examples  MISC=		context.html index.html report.css test_result.html  MISCDIR=	${KYUA_MISCDIR} -MISCTAGS=	package=tests +MISCTAGS=	package=kyua  .PATH:		${KYUA_SRCDIR}/misc  STORE=		migrate_v1_v2.sql migrate_v2_v3.sql schema_v3.sql  STOREDIR=	${KYUA_STOREDIR} -STORETAGS=	package=tests +STORETAGS=	package=kyua  .PATH:		${KYUA_SRCDIR}/store  CLEANFILES+=	${MAN} diff --git a/usr.bin/last/last.1 b/usr.bin/last/last.1 index f3ccc6e772af..b026ed6a7921 100644 --- a/usr.bin/last/last.1 +++ b/usr.bin/last/last.1 @@ -25,7 +25,7 @@  .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF  .\" SUCH DAMAGE.  .\" -.Dd January 9, 2021 +.Dd July 16, 2025  .Dt LAST 1  .Os  .Sh NAME @@ -75,7 +75,7 @@ Generate output via  .Xr libxo 3  in a selection of different human and machine readable formats.  See -.Xr xo_parse_args 3 +.Xr xo_options 7  for details on command line arguments.  .It Fl d Ar date  Specify the snapshot date and time. @@ -223,7 +223,7 @@ alice  ttyv0    Mon Dec  7 19:18 - 22:27  (03:09)  .Xr lastcomm 1 ,  .Xr getutxent 3 ,  .Xr libxo 3 , -.Xr xo_parse_args 3 , +.Xr xo_options 7 ,  .Xr ac 8 ,  .Xr lastlogin 8  .Sh HISTORY diff --git a/usr.bin/last/last.c b/usr.bin/last/last.c index 69848f359d79..2e6754abab8e 100644 --- a/usr.bin/last/last.c +++ b/usr.bin/last/last.c @@ -433,15 +433,15 @@ want(struct utmpx *bp)  				return (YES);  			break;  		case HOST_TYPE: -			if (!strcasecmp(step->name, bp->ut_host)) +			if (strcasecmp(step->name, bp->ut_host) == 0)  				return (YES);  			break;  		case TTY_TYPE: -			if (!strcmp(step->name, bp->ut_line)) +			if (strcmp(step->name, bp->ut_line) == 0)  				return (YES);  			break;  		case USER_TYPE: -			if (!strcmp(step->name, bp->ut_user)) +			if (strcmp(step->name, bp->ut_user) == 0)  				return (YES);  			break;  		} @@ -478,7 +478,7 @@ hostconv(char *arg)  	static char *hostdot, name[MAXHOSTNAMELEN];  	char *argdot; -	if (!(argdot = strchr(arg, '.'))) +	if ((argdot = strchr(arg, '.')) == NULL)  		return;  	if (first) {  		first = 0; @@ -486,7 +486,7 @@ hostconv(char *arg)  			xo_err(1, "gethostname");  		hostdot = strchr(name, '.');  	} -	if (hostdot && !strcasecmp(hostdot, argdot)) +	if (hostdot != NULL && strcasecmp(hostdot, argdot) == 0)  		*argdot = '\0';  } @@ -504,19 +504,16 @@ ttyconv(char *arg)  	 * a two character suffix.  	 */  	if (strlen(arg) == 2) { -		/* either 6 for "ttyxx" or 8 for "console" */ -		if ((mval = malloc(8)) == NULL) +		if (strcmp(arg, "co") == 0) +			mval = strdup("console"); +		else +			asprintf(&mval, "tty%s", arg); +		if (mval == NULL)  			xo_errx(1, "malloc failure"); -		if (!strcmp(arg, "co")) -			(void)strcpy(mval, "console"); -		else { -			(void)strcpy(mval, "tty"); -			(void)strcpy(mval + 3, arg); -		}  		return (mval);  	} -	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1)) -		return (arg + 5); +	if (strncmp(arg, _PATH_DEV, strlen(_PATH_DEV)) == 0) +		return (arg + strlen(_PATH_DEV));  	return (arg);  } diff --git a/usr.bin/localedef/localedef.1 b/usr.bin/localedef/localedef.1 index be37715f490d..918b57961c6c 100644 --- a/usr.bin/localedef/localedef.1 +++ b/usr.bin/localedef/localedef.1 @@ -164,7 +164,7 @@ unless instructed otherwise by the  .Fl D (  BSD  output) option. -The contants of this directory should generally be copied into the +The content of this directory should generally be copied into the  appropriate subdirectory of  .Pa /usr/share/locale  in order the definitions to be visible to programs linked with libc. diff --git a/usr.bin/lockf/lockf.c b/usr.bin/lockf/lockf.c index b0e16285998a..16bae36a21e0 100644 --- a/usr.bin/lockf/lockf.c +++ b/usr.bin/lockf/lockf.c @@ -62,35 +62,35 @@ _Static_assert(sizeof(sig_atomic_t) >= sizeof(pid_t),      "PIDs cannot be managed safely from a signal handler on this platform.");  static sig_atomic_t child = -1;  static int lockfd = -1; -static int keep; -static int fdlock; +static bool keep; +static bool fdlock;  static int status;  static bool termchild; -static volatile sig_atomic_t timed_out; +static sig_atomic_t timed_out;  /*   * Check if fdlock is implied by the given `lockname`.  We'll write the fd that   * is represented by it out to ofd, and the caller is expected to do any   * necessary validation on it.   */ -static int +static bool  fdlock_implied(const char *name, long *ofd)  {  	char *endp;  	long fd;  	if (strncmp(name, FDLOCK_PREFIX, sizeof(FDLOCK_PREFIX) - 1) != 0) -		return (0); +		return (false);  	/* Skip past the prefix. */  	name += sizeof(FDLOCK_PREFIX) - 1;  	errno = 0;  	fd = strtol(name, &endp, 10);  	if (errno != 0 || *endp != '\0') -		return (0); +		return (false);  	*ofd = fd; -	return (1); +	return (true);  }  /* @@ -105,38 +105,36 @@ main(int argc, char **argv)  	}, sa_prev;  	sigset_t mask, omask;  	long long waitsec; +	const char *errstr;  	union lock_subject subj; -	int ch, flags, silent, writepid; +	int ch, flags; +	bool silent, writepid; -	silent = keep = writepid = 0; +	silent = writepid = false;  	flags = O_CREAT | O_RDONLY;  	waitsec = -1;	/* Infinite. */  	while ((ch = getopt(argc, argv, "knpsTt:w")) != -1) {  		switch (ch) {  		case 'k': -			keep = 1; +			keep = true;  			break;  		case 'n':  			flags &= ~O_CREAT;  			break;  		case 's': -			silent = 1; +			silent = true;  			break;  		case 'T':  			termchild = true;  			break;  		case 't': -		{ -			const char *errstr; -  			waitsec = strtonum(optarg, 0, UINT_MAX, &errstr);  			if (errstr != NULL)  				errx(EX_USAGE,  				    "invalid timeout \"%s\"", optarg); -		}  			break;  		case 'p': -			writepid = 1; +			writepid = true;  			flags |= O_TRUNC;  			/* FALLTHROUGH */  		case 'w': @@ -162,7 +160,7 @@ main(int argc, char **argv)  	 * If there aren't any arguments left, then we must be in fdlock mode.  	 */  	if (argc == 0 && *lockname != '/') { -		fdlock = 1; +		fdlock = true;  		subj.subj_fd = -1;  	} else {  		fdlock = fdlock_implied(lockname, &subj.subj_fd); @@ -227,13 +225,16 @@ main(int argc, char **argv)  	 */  	lockfd = acquire_lock(&subj, flags | O_NONBLOCK, silent);  	while (lockfd == -1 && !timed_out && waitsec != 0) { -		if (keep || fdlock) +		if (keep || fdlock) {  			lockfd = acquire_lock(&subj, flags, silent); -		else { +		} else {  			wait_for_lock(lockname);  			lockfd = acquire_lock(&subj, flags | O_NONBLOCK,  			    silent);  		} + +		/* timed_out */ +		atomic_signal_fence(memory_order_acquire);  	}  	if (waitsec > 0)  		alarm(0); @@ -365,7 +366,7 @@ killed(int sig)  		kill(child, sig);  	cleanup();  	signal(sig, SIG_DFL); -	if (kill(getpid(), sig) == -1) +	if (raise(sig) == -1)  		_Exit(EX_OSERR);  } @@ -397,6 +398,7 @@ timeout(int sig __unused)  {  	timed_out = 1; +	atomic_signal_fence(memory_order_release);  }  static void diff --git a/usr.bin/login/login.conf b/usr.bin/login/login.conf index 1069da17b4db..c65a83caa565 100644 --- a/usr.bin/login/login.conf +++ b/usr.bin/login/login.conf @@ -46,7 +46,6 @@ default:\  	:umtxp=unlimited:\  	:pipebuf=unlimited:\  	:priority=0:\ -	:ignoretime@:\  	:umask=022:\  	:charset=UTF-8:\  	:lang=C.UTF-8: @@ -149,7 +148,6 @@ russian|Russian Users Accounts:\  #	:requirehome:\  #	:passwordtime=90d:\  #	:umask=002:\ -#	:ignoretime@:\  #	:tc=default:  #  # @@ -174,7 +172,6 @@ russian|Russian Users Accounts:\  ##  #staff:\  #	:ignorenologin:\ -#	:ignoretime:\  #	:requirehome@:\  #	:accounted@:\  #	:path=~/bin /bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin:\ @@ -265,7 +262,6 @@ russian|Russian Users Accounts:\  ## - no time accounting, restricted to access via dialin lines  ##  #site:\ -#	:ignoretime:\  #	:passwordtime@:\  #	:refreshtime@:\  #	:refreshperiod@:\ diff --git a/usr.bin/lsvfs/lsvfs.c b/usr.bin/lsvfs/lsvfs.c index 04ed38e8d978..8925b8988cd3 100644 --- a/usr.bin/lsvfs/lsvfs.c +++ b/usr.bin/lsvfs/lsvfs.c @@ -5,10 +5,12 @@   *   */ +#include <sys/capsicum.h>  #include <sys/param.h>  #include <sys/mount.h>  #include <sys/sysctl.h> +#include <capsicum_helpers.h>  #include <err.h>  #include <stdio.h>  #include <stdlib.h> @@ -38,42 +40,42 @@ static const char *fmt_flags(int);  int  main(int argc, char **argv)  { -	struct xvfsconf vfc, *xvfsp; -	size_t buflen; -	int cnt, i, rv = 0; +	struct xvfsconf *xvfsp; +	size_t cnt, buflen; +	int rv = 0;  	argc--, argv++; +	if (sysctlbyname("vfs.conflist", NULL, &buflen, NULL, 0) < 0) +		err(EXIT_FAILURE, "sysctl(vfs.conflist)"); +	if ((xvfsp = malloc(buflen)) == NULL) +		errx(EXIT_FAILURE, "malloc failed"); +	if (sysctlbyname("vfs.conflist", xvfsp, &buflen, NULL, 0) < 0) +		err(EXIT_FAILURE, "sysctl(vfs.conflist)"); +	cnt = buflen / sizeof(struct xvfsconf); + +	caph_cache_catpages(); +	if (caph_enter() != 0) +		err(EXIT_FAILURE, "failed to enter capability mode"); +  	printf(HDRFMT, "Filesystem", "Num", "Refs", "Flags");  	fputs(DASHES, stdout); -	if (argc > 0) { -		for (; argc > 0; argc--, argv++) { -			if (getvfsbyname(*argv, &vfc) == 0) { -				printf(FMT, vfc.vfc_name, vfc.vfc_typenum, -				    vfc.vfc_refcount, fmt_flags(vfc.vfc_flags)); -			} else { -				warnx("VFS %s unknown or not loaded", *argv); -				rv++; +	for (size_t i = 0; i < cnt; i++) { +		if (argc > 0) { +			int j; +			for (j = 0; j < argc; j++) { +				if (strcmp(argv[j], xvfsp[i].vfc_name) == 0) +					break;  			} +			if (j == argc) +				continue;  		} -	} else { -		if (sysctlbyname("vfs.conflist", NULL, &buflen, NULL, 0) < 0) -			err(1, "sysctl(vfs.conflist)"); -		xvfsp = malloc(buflen); -		if (xvfsp == NULL) -			errx(1, "malloc failed"); -		if (sysctlbyname("vfs.conflist", xvfsp, &buflen, NULL, 0) < 0) -			err(1, "sysctl(vfs.conflist)"); -		cnt = buflen / sizeof(struct xvfsconf); - -		for (i = 0; i < cnt; i++) { -			printf(FMT, xvfsp[i].vfc_name, xvfsp[i].vfc_typenum, -			    xvfsp[i].vfc_refcount, -			    fmt_flags(xvfsp[i].vfc_flags)); -		} -		free(xvfsp); + +		printf(FMT, xvfsp[i].vfc_name, xvfsp[i].vfc_typenum, +		    xvfsp[i].vfc_refcount, fmt_flags(xvfsp[i].vfc_flags));  	} +	free(xvfsp);  	return (rv);  } @@ -82,10 +84,9 @@ static const char *  fmt_flags(int flags)  {  	static char buf[sizeof(struct flaglist) * sizeof(fl)]; -	int i;  	buf[0] = '\0'; -	for (i = 0; i < (int)nitems(fl); i++) { +	for (size_t i = 0; i < (int)nitems(fl); i++) {  		if ((flags & fl[i].flag) != 0) {  			strlcat(buf, fl[i].str, sizeof(buf));  			strlcat(buf, ", ", sizeof(buf)); diff --git a/usr.bin/lzmainfo/Makefile b/usr.bin/lzmainfo/Makefile index afde07163a01..e537e4c86083 100644 --- a/usr.bin/lzmainfo/Makefile +++ b/usr.bin/lzmainfo/Makefile @@ -1,3 +1,5 @@ +PACKAGE=xz +  PROG=	lzmainfo  XZDIR=	${SRCTOP}/contrib/xz/src 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 ec20fc813bf4..920223ce3c27 100755 --- a/usr.bin/man/man.sh +++ b/usr.bin/man/man.sh @@ -313,6 +313,8 @@ manpath_warnings() {  # redirected to another source file.  man_check_for_so() {  	local IFS line tstr +	local counter_so=0 +	local counter_so_limit=32  	unset IFS  	if [ -n "$catpage" ]; then @@ -320,13 +322,16 @@ man_check_for_so() {  	fi  	# We need to loop to accommodate multiple .so directives. -	while true +	while [ $counter_so -lt $counter_so_limit ]  	do +		counter_so=$((counter_so + 1)) +  		line=$($cattool "$manpage" 2>/dev/null | grep -E -m1 -v '^\.\\"[ ]*|^[ ]*$')  		case "$line" in                 '.so /'*) break ;; # ignore absolute path                 '.so '*) trim "${line#.so}" -			decho "$manpage includes $tstr" +			decho "$manpage includes $tstri level=$counter_so" +  			# Glob and check for the file.  			if ! check_man "$1/$tstr" ""; then  				decho "  Unable to find $tstr" @@ -337,6 +342,10 @@ man_check_for_so() {  		esac  	done +	if [ $counter_so -ge $counter_so_limit ]; then +		decho ".so include limit of $counter_so_limit reached, stop" +	fi +  	return 0  } @@ -502,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" @@ -522,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 @@ -592,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 ;; @@ -604,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 ;; @@ -616,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 @@ -742,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 c5255b1468fd..181d4e16c8ee 100644 --- a/usr.bin/mandoc/Makefile +++ b/usr.bin/mandoc/Makefile @@ -3,8 +3,10 @@  MANDOCDIR=	${SRCTOP}/contrib/mandoc  .PATH: ${MANDOCDIR} +PACKAGE=	mandoc +  PROG=	mandoc -MAN=	mandoc.1 eqn.7 mandoc_char.7 tbl.7 man.7 mdoc.7 roff.7 +MAN=	mandoc.1 mandoc.db.5 eqn.7 mandoc_char.7 tbl.7 man.7 mdoc.7 roff.7  MLINKS=	mandoc.1 mdocml.1  .if ${MK_MAN_UTILS} != no  MAN+=	apropos.1 makewhatis.8 @@ -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 @@ -54,8 +60,7 @@ LIB_SRCS=	${LIBMAN_SRCS} \  		mandoc_xr.c \  		msec.c \  		preconv.c \ -		read.c \ -		compat_recallocarray.c \ +		read.c  HTML_SRCS=	eqn_html.c \  		html.c \ diff --git a/usr.bin/mandoc/mandoc.ucl b/usr.bin/mandoc/mandoc.ucl new file mode 100644 index 000000000000..75b8123d55cc --- /dev/null +++ b/usr.bin/mandoc/mandoc.ucl @@ -0,0 +1,32 @@ +path_glob: [ +	"/usr/share/man/*", +	"/usr/share/openssl/man/*", +] + +cleanup: { +	type: lua +	sandbox: false +	script: <<EOD +	os.remove("/usr/share/man/mandoc.db") +	os.remove("/usr/share/openssl/man/mandoc.db") +EOD +} + +trigger: { +	type: lua +	sandbox: false +	script: <<EOD + +	local dirs = { +		"/usr/share/man", +		"/usr/share/openssl/man", +	} + +	for _,dir in ipairs(dirs) do +		if pkg.stat(dir) then +			print("Generating apropos(1) database for "..dir.."...") +			pkg.exec({"/usr/bin/makewhatis", dir}) +		end +	end +EOD +} diff --git a/usr.bin/mdo/mdo.c b/usr.bin/mdo/mdo.c index 8435fc17f26f..3eb5d4e5c23f 100644 --- a/usr.bin/mdo/mdo.c +++ b/usr.bin/mdo/mdo.c @@ -1,13 +1,24 @@  /*- + * SPDX-License-Identifier: BSD-2-Clause + *   * Copyright(c) 2024 Baptiste Daroussin <bapt@FreeBSD.org> + * Copyright (c) 2025 Kushagra Srivastava <kushagra1403@gmail.com> + * Copyright (c) 2025 The FreeBSD Foundation   * - * SPDX-License-Identifier: BSD-2-Clause + * Portions of this software were developed by Olivier Certner + * <olce@FreeBSD.org> at Kumacom SARL under sponsorship from the FreeBSD + * Foundation.   */ +#include <sys/errno.h>  #include <sys/limits.h> +#include <sys/types.h>  #include <sys/ucred.h> +#include <assert.h>  #include <err.h> +#include <getopt.h> +#include <grp.h>  #include <paths.h>  #include <pwd.h>  #include <stdbool.h> @@ -16,84 +27,848 @@  #include <string.h>  #include <unistd.h> +  static void  usage(void)  { -	fprintf(stderr, "usage: mdo [-u username] [-i] [--] [command [args]]\n"); -	exit(EXIT_FAILURE); +	fprintf(stderr, +	    "Usage: mdo [options] [--] [command [args...]]\n" +	    "\n" +	    "Options:\n" +	    "  -u <user>         Target user (name or UID; name sets groups)\n" +	    "  -k                Keep current user, allows selective overrides " +	    "(implies -i)\n" +	    "  -i                Keep current groups, unless explicitly overridden\n" +	    "  -g <group>        Override primary group (name or GID)\n" +	    "  -G <g1,g2,...>    Set supplementary groups (name or GID list)\n" +	    "  -s <mods>         Modify supplementary groups using:\n" +	    "                      @ (first) to reset, +group to add, -group to remove\n" +	    "\n" +	    "Advanced UID/GID overrides:\n" +	    "  --euid <uid>      Set effective UID\n" +	    "  --ruid <uid>      Set real UID\n" +	    "  --svuid <uid>     Set saved UID\n" +	    "  --egid <gid>      Set effective GID\n" +	    "  --rgid <gid>      Set real GID\n" +	    "  --svgid <gid>     Set saved GID\n" +	    "\n" +	    "  -h                Show this help message\n" +	    "\n" +	    "Examples:\n" +	    "  mdo -u alice id\n" +	    "  mdo -u 1001 -g wheel -G staff,operator sh\n" +	    "  mdo -u bob -s +wheel,+operator id\n" +	    "  mdo -k --ruid 1002 --egid 1004 id\n" +	); +	exit(1); +} + +struct alloc { +	void	*start; +	size_t	 size; +}; + +static const struct alloc ALLOC_INITIALIZER = { +	.start = NULL, +	.size = 0, +}; + +/* + * The default value should cover almost all cases. + * + * For getpwnam_r(), we assume: + * - 88 bytes for 'struct passwd' + * - Less than 16 bytes for the user name + * - A typical shadow hash of 106 bytes + * - Less than 16 bytes for the login class name + * - Less than 64 bytes for GECOS info + * - Less than 128 bytes for the home directory + * - Less than 32 bytes for the shell path + * Total: 256 + 88 + 106 = 450. + * + * For getgrnam_r(), we assume: + * - 32 bytes for 'struct group' + * - Less than 16 bytes for the group name + * - Some hash of 106 bytes + * - No more than 16 members, each of less than 16 bytes (=> 256 bytes) + * Total: 256 + 32 + 16 + 106 = 410. + * + * We thus choose 512 (leeway, power of 2). + */ +static const size_t ALLOC_FIRST_SIZE = 512; + +static bool +alloc_is_empty(const struct alloc *const alloc) +{ +	if (alloc->size == 0) { +		assert(alloc->start == NULL); +		return (true); +	} else { +		assert(alloc->start != NULL); +		return (false); +	} +} + +static void +alloc_realloc(struct alloc *const alloc) +{ +	const size_t old_size = alloc->size; +	size_t new_size; + +	if (old_size == 0) { +		assert(alloc->start == NULL); +		new_size = ALLOC_FIRST_SIZE; +	} else if (old_size < PAGE_SIZE) +		new_size = 2 * old_size; +	else +		/* +		 * We never allocate more than a page at a time when reaching +		 * a page (except perhaps for the first increment, up to two). +		 * Use roundup2() to be immune to previous cases' changes. */ +		new_size = roundup2(old_size, PAGE_SIZE) + PAGE_SIZE; + +	alloc->start = realloc(alloc->start, new_size); +	if (alloc->start == NULL) +		errx(EXIT_FAILURE, +		    "cannot realloc allocation (old size: %zu, new: %zu)", +		    old_size, new_size); +	alloc->size = new_size; +} + +static void +alloc_free(struct alloc *const alloc) +{ +	if (!alloc_is_empty(alloc)) { +		free(alloc->start); +		*alloc = ALLOC_INITIALIZER; +	} +} + +struct alloc_wrap_data { +	int (*func)(void *data, const struct alloc *alloc); +}; + +/* + * Wraps functions needing a backing allocation. + * + * Uses 'alloc' as the starting allocation, and may extend it as necessary. + * 'alloc' is never freed, even on failure of the wrapped function. + * + * The function is expected to return ERANGE if and only if the provided + * allocation is not big enough.  All other values are passed through. + */ +static int +alloc_wrap(struct alloc_wrap_data *const data, struct alloc *alloc) +{ +	int error; + +	/* Avoid a systematic ERANGE on first iteration. */ +	if (alloc_is_empty(alloc)) +		alloc_realloc(alloc); + +	for (;;) { +		error = data->func(data, alloc); +		if (error != ERANGE) +			break; +		alloc_realloc(alloc); +	} + +	return (error); +} + +struct getpwnam_wrapper_data { +	struct alloc_wrap_data	  wrapped; +	const char		 *name; +	struct passwd		**pwdp; +}; + +static int +wrapped_getpwnam_r(void *data, const struct alloc *alloc) +{ +	struct passwd *const pwd = alloc->start; +	struct passwd *result; +	struct getpwnam_wrapper_data *d = data; +	int error; + +	assert(alloc->size >= sizeof(*pwd)); + +	error = getpwnam_r(d->name, pwd, (char *)(pwd + 1), +	    alloc->size - sizeof(*pwd), &result); + +	if (error == 0) { +		if (result == NULL) +			error = ENOENT; +	} else +		assert(result == NULL); +	*d->pwdp = result; +	return (error); +} + +/* + * Wraps getpwnam_r(), automatically dealing with memory allocation. + * + * 'alloc' may be any allocation (even empty), and will be extended as + * necessary.  It is not freed on error. + * + * On success, '*pwdp' is filled with a pointer to the returned 'struct passwd', + * and on failure, is set to NULL. + */ +static int +alloc_getpwnam(const char *name, struct passwd **pwdp, +    struct alloc *const alloc) +{ +	struct getpwnam_wrapper_data data; + +	data.wrapped.func = wrapped_getpwnam_r; +	data.name = name; +	data.pwdp = pwdp; +	return (alloc_wrap((struct alloc_wrap_data *)&data, alloc)); +} + +struct getgrnam_wrapper_data { +	struct alloc_wrap_data	  wrapped; +	const char		 *name; +	struct group		**grpp; +}; + +static int +wrapped_getgrnam_r(void *data, const struct alloc *alloc) +{ +	struct group *grp = alloc->start; +	struct group *result; +	struct getgrnam_wrapper_data *d = data; +	int error; + +	assert(alloc->size >= sizeof(*grp)); + +	error = getgrnam_r(d->name, grp, (char *)(grp + 1), +	    alloc->size - sizeof(*grp), &result); + +	if (error == 0) { +		if (result == NULL) +			error = ENOENT; +	} else +		assert(result == NULL); +	*d->grpp = result; +	return (error); +} + +/* + * Wraps getgrnam_r(), automatically dealing with memory allocation. + * + * 'alloc' may be any allocation (even empty), and will be extended as + * necessary.  It is not freed on error. + * + * On success, '*grpp' is filled with a pointer to the returned 'struct group', + * and on failure, is set to NULL. + */ +static int +alloc_getgrnam(const char *const name, struct group **const grpp, +    struct alloc *const alloc) +{ +	struct getgrnam_wrapper_data data; + +	data.wrapped.func = wrapped_getgrnam_r; +	data.name = name; +	data.grpp = grpp; +	return (alloc_wrap((struct alloc_wrap_data *)&data, alloc)); +} + +/* + * Retrieve the UID from a user string. + * + * Tries first to interpret the string as a user name, then as a numeric ID + * (this order is prescribed by POSIX for a number of utilities). + * + * 'pwdp' and 'allocp' must be NULL or non-NULL together.  If non-NULL, then + * 'allocp' can be any allocation (possibly empty) and will be extended to + * contain the result if necessary.  It will not be freed (even on failure). + */ +static uid_t +parse_user_pwd(const char *s, struct passwd **pwdp, struct alloc *allocp) +{ +	struct passwd *pwd; +	struct alloc alloc = ALLOC_INITIALIZER; +	const char *errp; +	uid_t uid; +	int error; + +	assert((pwdp == NULL && allocp == NULL) || +	    (pwdp != NULL && allocp != NULL)); + +	if (pwdp == NULL) { +		pwdp = &pwd; +		allocp = &alloc; +	} + +	error = alloc_getpwnam(s, pwdp, allocp); +	if (error == 0) { +		uid = (*pwdp)->pw_uid; +		goto finish; +	} else if (error != ENOENT) +		errc(EXIT_FAILURE, error, +		    "cannot access the password database"); + +	uid = strtonum(s, 0, UID_MAX, &errp); +	if (errp != NULL) +		errx(EXIT_FAILURE, "invalid UID '%s': %s", s, errp); + +finish: +	if (allocp == &alloc) +		alloc_free(allocp); +	return (uid); +} + +/* See parse_user_pwd() for the doc. */ +static uid_t +parse_user(const char *s) +{ +	return (parse_user_pwd(s, NULL, NULL)); +} + +/* + * Retrieve the GID from a group string. + * + * Tries first to interpret the string as a group name, then as a numeric ID + * (this order is prescribed by POSIX for a number of utilities). + */ +static gid_t +parse_group(const char *s) +{ +	struct group *grp; +	struct alloc alloc = ALLOC_INITIALIZER; +	const char *errp; +	gid_t gid; +	int error; + +	error = alloc_getgrnam(s, &grp, &alloc); +	if (error == 0) { +		gid = grp->gr_gid; +		goto finish; +	} else if (error != ENOENT) +		errc(EXIT_FAILURE, error, "cannot access the group database"); + +	gid = strtonum(s, 0, GID_MAX, &errp); +	if (errp != NULL) +		errx(EXIT_FAILURE, "invalid GID '%s': %s", s, errp); + +finish: +	alloc_free(&alloc); +	return (gid); +} + +struct group_array { +	u_int	 nb; +	gid_t	*groups; +}; + +static const struct group_array GROUP_ARRAY_INITIALIZER = { +	.nb = 0, +	.groups = NULL, +}; + +static bool +group_array_is_empty(const struct group_array *const ga) +{ +	return (ga->nb == 0); +} + +static void +realloc_groups(struct group_array *const ga, const u_int diff) +{ +	const u_int new_nb = ga->nb + diff; +	const size_t new_size = new_nb * sizeof(*ga->groups); + +	assert(new_nb >= diff && new_size >= new_nb); +	ga->groups = realloc(ga->groups, new_size); +	if (ga->groups == NULL) +		err(EXIT_FAILURE, "realloc of groups failed"); +	ga->nb = new_nb; +} + +static int +gidp_cmp(const void *p1, const void *p2) +{ +	const gid_t g1 = *(const gid_t *)p1; +	const gid_t g2 = *(const gid_t *)p2; + +	return ((g1 > g2) - (g1 < g2)); +} + +static void +sort_uniq_groups(struct group_array *const ga) +{ +	size_t j = 0; + +	if (ga->nb <= 1) +		return; + +	qsort(ga->groups, ga->nb, sizeof(gid_t), gidp_cmp); + +	for (size_t i = 1; i < ga->nb; ++i) +		if (ga->groups[i] != ga->groups[j]) +			ga->groups[++j] = ga->groups[i]; +} + +/* + * Remove elements in 'set' that are in 'remove'. + * + * Expects both arrays to have been treated with sort_uniq_groups(). Works in + * O(n + m), modifying 'set' in place. + */ +static void +remove_groups(struct group_array *const set, +    const struct group_array *const remove) +{ +	u_int from = 0, to = 0, rem = 0; +	gid_t cand, to_rem; + +	if (set->nb == 0 || remove->nb == 0) +		/* Nothing to remove. */ +		return; + +	cand = set->groups[0]; +	to_rem = remove->groups[0]; + +	for (;;) { +		if (cand < to_rem) { +			/* Keep. */ +			if (to != from) +				set->groups[to] = cand; +			++to; +			cand = set->groups[++from]; +			if (from == set->nb) +				break; +		} else if (cand == to_rem) { +			cand = set->groups[++from]; +			if (from == set->nb) +				break; +			to_rem = remove->groups[++rem]; /* No duplicates. */ +			if (rem == remove->nb) +				break; +		} else { +			to_rem = remove->groups[++rem]; +			if (rem == remove->nb) +				break; +		} +	} + +	/* All remaining groups in 'set' must be kept. */ +	if (from == to) +		/* Nothing was removed.  'set' will stay the same. */ +		return; +	memmove(set->groups + to, set->groups + from, +	    (set->nb - from) * sizeof(gid_t)); +	set->nb = to + (set->nb - from);  }  int  main(int argc, char **argv)  { -	struct passwd *pw; -	const char *username = "root"; +	const char *const default_user = "root"; + +	const char *user_name = NULL; +	const char *primary_group = NULL; +	char *supp_groups_str = NULL; +	char *supp_mod_str = NULL; +	bool start_from_current_groups = false; +	bool start_from_current_users = false; +	const char *euid_str = NULL; +	const char *ruid_str = NULL; +	const char *svuid_str = NULL; +	const char *egid_str = NULL; +	const char *rgid_str = NULL; +	const char *svgid_str = NULL; +	bool need_user = false; /* '-u' or '-k' needed. */ + +	const int go_euid = 1000; +	const int go_ruid = 1001; +	const int go_svuid = 1002; +	const int go_egid = 1003; +	const int go_rgid = 1004; +	const int go_svgid = 1005; +	const struct option longopts[] = { +		{"euid", required_argument, NULL, go_euid}, +		{"ruid", required_argument, NULL, go_ruid}, +		{"svuid", required_argument, NULL, go_svuid}, +		{"egid", required_argument, NULL, go_egid}, +		{"rgid", required_argument, NULL, go_rgid}, +		{"svgid", required_argument, NULL, go_svgid}, +		{NULL, 0, NULL, 0} +	}; +	int ch; +  	struct setcred wcred = SETCRED_INITIALIZER;  	u_int setcred_flags = 0; -	bool uidonly = false; -	int ch; -	while ((ch = getopt(argc, argv, "u:i")) != -1) { +	struct passwd *pw = NULL; +	struct alloc pw_alloc = ALLOC_INITIALIZER; +	struct group_array supp_groups = GROUP_ARRAY_INITIALIZER; +	struct group_array supp_rem = GROUP_ARRAY_INITIALIZER; + + +	/* +	 * Process options. +	 */ +	while (ch = getopt_long(argc, argv, "+G:g:hiks:u:", longopts, NULL), +	    ch != -1) {  		switch (ch) { -		case 'u': -			username = optarg; +		case 'G': +			supp_groups_str = optarg; +			need_user = true; +			break; +		case 'g': +			primary_group = optarg; +			need_user = true;  			break; +		case 'h': +			usage();  		case 'i': -			uidonly = true; +			start_from_current_groups = true; +			break; +		case 'k': +			start_from_current_users = true; +			break; +		case 's': +			supp_mod_str = optarg; +			need_user = true; +			break; +		case 'u': +			user_name = optarg; +			break; +		case go_euid: +			euid_str = optarg; +			need_user = true; +			break; +		case go_ruid: +			ruid_str = optarg; +			need_user = true; +			break; +		case go_svuid: +			svuid_str = optarg; +			need_user = true; +			break; +		case go_egid: +			egid_str = optarg; +			need_user = true; +			break; +		case go_rgid: +			rgid_str = optarg; +			need_user = true; +			break; +		case go_svgid: +			svgid_str = optarg; +			need_user = true;  			break;  		default:  			usage();  		}  	} +  	argc -= optind;  	argv += optind; -	if ((pw = getpwnam(username)) == NULL) { -		if (strspn(username, "0123456789") == strlen(username)) { -			const char *errp = NULL; -			uid_t uid = strtonum(username, 0, UID_MAX, &errp); -			if (errp != NULL) -				err(EXIT_FAILURE, "invalid user ID '%s'", -				    username); -			pw = getpwuid(uid); +	/* +	 * Determine users. +	 * +	 * We do that first as in some cases we need to retrieve the +	 * corresponding password database entry to be able to set the primary +	 * groups. +	 */ + +	if (start_from_current_users) { +		if (user_name != NULL) +			errx(EXIT_FAILURE, "-k incompatible with -u"); + +		/* +		 * If starting from the current user(s) as a base, finding one +		 * of them in the password database and using its groups would +		 * be quite surprising, so we instead let '-k' imply '-i'. +		 */ +		start_from_current_groups = true; +	} else { +		uid_t uid; + +		/* +		 * In the case of any overrides, we impose an explicit base user +		 * via '-u' or '-k' instead of implicitly taking 'root' as the +		 * base. +		 */ +		if (user_name == NULL) { +			if (need_user) +				errx(EXIT_FAILURE, +				    "Some overrides specified, " +				    "'-u' or '-k' needed."); +			user_name = default_user;  		} + +		/* +		 * Even if all user overrides are present as well as primary and +		 * supplementary groups ones, in which case the final result +		 * doesn't depend on '-u', we still call parse_user_pwd() to +		 * check that the passed username is correct. +		 */ +		uid = parse_user_pwd(user_name, &pw, &pw_alloc); +		wcred.sc_uid = wcred.sc_ruid = wcred.sc_svuid = uid; +		setcred_flags |= SETCREDF_UID | SETCREDF_RUID | +		    SETCREDF_SVUID; +	} + +	if (euid_str != NULL) { +		wcred.sc_uid = parse_user(euid_str); +		setcred_flags |= SETCREDF_UID; +	} + +	if (ruid_str != NULL) { +		wcred.sc_ruid = parse_user(ruid_str); +		setcred_flags |= SETCREDF_RUID; +	} + +	if (svuid_str != NULL) { +		wcred.sc_svuid = parse_user(svuid_str); +		setcred_flags |= SETCREDF_SVUID; +	} + +	/* +	 * Determine primary groups. +	 */ + +	/* +	 * When not starting from the current groups, we need to set all +	 * primary groups.  If '-g' was not passed, we use the primary +	 * group from the password database as the "base" to which +	 * overrides '--egid', '--rgid' and '--svgid' apply.  But if all +	 * overrides were specified, we in fact just don't need the +	 * password database at all. +	 * +	 * '-g' is treated outside of this 'if' as it can also be used +	 * as an override. +	 */ +	if (!start_from_current_groups && primary_group == NULL && +	    (egid_str == NULL || rgid_str == NULL || svgid_str == NULL)) {  		if (pw == NULL) -			err(EXIT_FAILURE, "invalid username '%s'", username); +			errx(EXIT_FAILURE, +			    "must specify primary groups or a user name " +			    "with an entry in the password database"); + +		wcred.sc_gid = wcred.sc_rgid = wcred.sc_svgid = +		    pw->pw_gid; +		setcred_flags |= SETCREDF_GID | SETCREDF_RGID | +		    SETCREDF_SVGID; +	} + +	if (primary_group != NULL) { +		/* +		 * We always call parse_group() even in case all overrides are +		 * present to check that the passed group is valid. +		 */ +		wcred.sc_gid = wcred.sc_rgid = wcred.sc_svgid = +		    parse_group(primary_group); +		setcred_flags |= SETCREDF_GID | SETCREDF_RGID | SETCREDF_SVGID; +	} + +	if (egid_str != NULL) { +		wcred.sc_gid = parse_group(egid_str); +		setcred_flags |= SETCREDF_GID; +	} + +	if (rgid_str != NULL) { +		wcred.sc_rgid = parse_group(rgid_str); +		setcred_flags |= SETCREDF_RGID; +	} + +	if (svgid_str != NULL) { +		wcred.sc_svgid = parse_group(svgid_str); +		setcred_flags |= SETCREDF_SVGID; +	} + +	/* +	 * Determine supplementary groups. +	 */ + +	/* +	 * This makes sense to catch user's mistakes.  It is not a strong +	 * limitation of the code below (allowing this case is just a matter of, +	 * in the block treating '-s' with '@' below, replacing an assert() by +	 * a reset of 'supp_groups'). +	 */ +	if (supp_groups_str != NULL && supp_mod_str != NULL && +	    supp_mod_str[0] == '@') +		errx(EXIT_FAILURE, "'-G' and '-s' with '@' are incompatible"); + +	/* +	 * Determine the supplementary groups to start with, but only if we +	 * really need to operate on them later (and set them back). +	 */ +	if (!start_from_current_groups) { +		assert(!start_from_current_users); + +		if (supp_groups_str == NULL && (supp_mod_str == NULL || +		    supp_mod_str[0] != '@')) { +			/* +			 * If we are to replace supplementary groups (i.e., +			 * neither '-i' nor '-k' was specified) and they are not +			 * completely specified (with '-g' or '-s' with '@'), we +			 * start from those in the groups database if we were +			 * passed a user name that is in the password database +			 * (this is a protection against erroneous ID/name +			 * conflation in the groups database), else we simply +			 * error. +			 */ + +			if (pw == NULL) +				errx(EXIT_FAILURE, +				    "must specify the full supplementary " +				    "groups set or a user name with an entry " +				    "in the password database"); + +			const long ngroups_alloc = sysconf(_SC_NGROUPS_MAX) + 1; +			gid_t *groups; +			int ngroups; + +			groups = malloc(sizeof(*groups) * ngroups_alloc); +			if (groups == NULL) +				errx(EXIT_FAILURE, +				    "cannot allocate memory to retrieve " +				    "user groups from the groups database"); + +			ngroups = ngroups_alloc; +			getgrouplist(user_name, pw->pw_gid, groups, &ngroups); + +			if (ngroups > ngroups_alloc) +				err(EXIT_FAILURE, +				    "too many groups for user '%s'", +				    user_name); + +			realloc_groups(&supp_groups, ngroups); +			memcpy(supp_groups.groups + supp_groups.nb - ngroups, +			    groups, ngroups * sizeof(*groups)); +			free(groups); + +			/* +			 * Have to set SETCREDF_SUPP_GROUPS here since we may be +			 * in the case where neither '-G' nor '-s' was passed, +			 * but we still have to set the supplementary groups to +			 * those of the groups database. +			 */ +			setcred_flags |= SETCREDF_SUPP_GROUPS; +		} +	} else if (supp_groups_str == NULL && (supp_mod_str == NULL || +	    supp_mod_str[0] != '@')) { +		const int ngroups = getgroups(0, NULL); + +		if (ngroups > 0) { +			realloc_groups(&supp_groups, ngroups); + +			if (getgroups(ngroups, supp_groups.groups + +			    supp_groups.nb - ngroups) < 0) +				err(EXIT_FAILURE, "getgroups() failed"); +		} + +		/* +		 * Setting SETCREDF_SUPP_GROUPS here is not necessary, we will +		 * do it below since 'supp_mod_str' != NULL. +		 */  	} -	wcred.sc_uid = wcred.sc_ruid = wcred.sc_svuid = pw->pw_uid; -	setcred_flags |= SETCREDF_UID | SETCREDF_RUID | SETCREDF_SVUID; +	if (supp_groups_str != NULL) { +		char *p = supp_groups_str; +		char *tok; -	if (!uidonly) {  		/* -		 * If there are too many groups specified for some UID, setting -		 * the groups will fail.  We preserve this condition by -		 * allocating one more group slot than allowed, as -		 * getgrouplist() itself is just some getter function and thus -		 * doesn't (and shouldn't) check the limit, and to allow -		 * setcred() to actually check for overflow. +		 * We will set the supplementary groups to exactly the set +		 * passed with '-G', and we took care above not to retrieve +		 * "base" groups (current ones or those from the groups +		 * database) in this case.  		 */ -		const long ngroups_alloc = sysconf(_SC_NGROUPS_MAX) + 2; -		gid_t *const groups = malloc(sizeof(*groups) * ngroups_alloc); -		int ngroups = ngroups_alloc; +		assert(group_array_is_empty(&supp_groups)); + +		/* WARNING: 'supp_groups_str' going to be modified. */ +		while ((tok = strsep(&p, ",")) != NULL) { +			gid_t g; + +			if (*tok == '\0') +				continue; + +			g = parse_group(tok); +			realloc_groups(&supp_groups, 1); +			supp_groups.groups[supp_groups.nb - 1] = g; +		} + +		setcred_flags |= SETCREDF_SUPP_GROUPS; +	} + +	if (supp_mod_str != NULL) { +		char *p = supp_mod_str; +		char *tok; +		gid_t gid; -		if (groups == NULL) -			err(EXIT_FAILURE, "cannot allocate memory for groups"); +		/* WARNING: 'supp_mod_str' going to be modified. */ +		while ((tok = strsep(&p, ",")) != NULL) { +			switch (tok[0]) { +			case '\0': +			        break; -		getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups); +			case '@': +				if (tok != supp_mod_str) +					errx(EXIT_FAILURE, "'@' must be " +					    "the first token in '-s' option"); +				/* See same assert() above. */ +				assert(group_array_is_empty(&supp_groups)); +				break; + +			case '+': +			case '-': +				gid = parse_group(tok + 1); +				if (tok[0] == '+') { +					realloc_groups(&supp_groups, 1); +					supp_groups.groups[supp_groups.nb - 1] = gid; +				} else { +					realloc_groups(&supp_rem, 1); +					supp_rem.groups[supp_rem.nb - 1] = gid; +				} +				break; + +			default: +				errx(EXIT_FAILURE, +				    "invalid '-s' token '%s' at index %zu", +				    tok, tok - supp_mod_str); +			} +		} -		wcred.sc_gid = wcred.sc_rgid = wcred.sc_svgid = pw->pw_gid; -		wcred.sc_supp_groups = groups + 1; -		wcred.sc_supp_groups_nb = ngroups - 1; -		setcred_flags |= SETCREDF_GID | SETCREDF_RGID | SETCREDF_SVGID | -		    SETCREDF_SUPP_GROUPS; +		setcred_flags |= SETCREDF_SUPP_GROUPS; +	} + +	/* +	 * We don't need to pass the kernel a normalized representation of the +	 * new supplementary groups set (array sorted and without duplicates), +	 * so we don't do it here unless we need to remove some groups, where it +	 * enables more efficient algorithms (if the number of groups for some +	 * reason grows out of control). +	 */ +	if (!group_array_is_empty(&supp_groups) && +	    !group_array_is_empty(&supp_rem)) { +		sort_uniq_groups(&supp_groups); +		sort_uniq_groups(&supp_rem); +		remove_groups(&supp_groups, &supp_rem); +	} + +	if ((setcred_flags & SETCREDF_SUPP_GROUPS) != 0) { +		wcred.sc_supp_groups = supp_groups.groups; +		wcred.sc_supp_groups_nb = supp_groups.nb;  	}  	if (setcred(setcred_flags, &wcred, sizeof(wcred)) != 0) -		err(EXIT_FAILURE, "calling setcred() failed"); +		err(EXIT_FAILURE, "setcred()"); + +	/* +	 * We don't bother freeing memory still allocated at this point as we +	 * are about to exec() or exit. +	 */  	if (*argv == NULL) {  		const char *sh = getenv("SHELL"); +  		if (sh == NULL)  			sh = _PATH_BSHELL;  		execlp(sh, sh, "-i", NULL); diff --git a/usr.bin/mididump/Makefile b/usr.bin/mididump/Makefile index 758bbb3a1189..5b22376b7bb8 100644 --- a/usr.bin/mididump/Makefile +++ b/usr.bin/mididump/Makefile @@ -1,5 +1,7 @@  .include <src.opts.mk> +PACKAGE=	sound +  PROG=		mididump  SRCS=		${PROG}.c  MAN=		${PROG}.1 diff --git a/usr.bin/mkimg/mkimg.c b/usr.bin/mkimg/mkimg.c index a7409b686560..27b79b82ca02 100644 --- a/usr.bin/mkimg/mkimg.c +++ b/usr.bin/mkimg/mkimg.c @@ -142,8 +142,10 @@ static void  usage(const char *why)  { -	warnx("error: %s", why); -	fputc('\n', stderr); +	if (why != NULL) { +		warnx("error: %s", why); +		fputc('\n', stderr); +	}  	fprintf(stderr, "usage: %s <options>\n", getprogname());  	fprintf(stderr, "    options:\n"); @@ -171,19 +173,19 @@ usage(const char *why)  	print_schemes(1);  	fputc('\n', stderr);  	fprintf(stderr, "    partition specification:\n"); -	fprintf(stderr, "\t<t>[/<l>]::<size>[:[+]<offset>]\t-  " +	fprintf(stderr, "\t<type>[/<label>]::<size>[:[+]<offset>]\t-  "  	    "empty partition of given size and\n\t\t\t\t\t"  	    "   optional relative or absolute offset\n"); -	fprintf(stderr, "\t<t>[/<l>]:=<file>[:[+]offset]\t-  partition " +	fprintf(stderr, "\t<type>[/<label>]:=<file>[:[+]offset]\t-  partition "  	    "content and size are\n\t\t\t\t\t"  	    "   determined by the named file and\n"  	    "\t\t\t\t\t   optional relative or absolute offset\n"); -	fprintf(stderr, "\t<t>[/<l>]:-<cmd>\t\t-  partition content and size " +	fprintf(stderr, "\t<type>[/<label>]:-<cmd>\t\t-  partition content and size "  	    "are taken\n\t\t\t\t\t   from the output of the command to run\n");  	fprintf(stderr, "\t-\t\t\t\t-  unused partition entry\n");  	fprintf(stderr, "\t    where:\n"); -	fprintf(stderr, "\t\t<t>\t-  scheme neutral partition type\n"); -	fprintf(stderr, "\t\t<l>\t-  optional scheme-dependent partition " +	fprintf(stderr, "\t\t<type>\t-  scheme neutral partition type\n"); +	fprintf(stderr, "\t\t<label>\t-  optional scheme-dependent partition "  	    "label\n");  	exit(EX_USAGE); @@ -564,7 +566,7 @@ main(int argc, char *argv[])  	bcfd = -1;  	outfd = 1;	/* Write to stdout by default */ -	while ((c = getopt_long(argc, argv, "a:b:c:C:f:o:p:s:t:vyH:P:S:T:", +	while ((c = getopt_long(argc, argv, "a:b:c:C:f:ho:p:s:t:vyH:P:S:T:",  	    longopts, NULL)) != -1) {  		switch (c) {  		case 'a':	/* ACTIVE PARTITION, if supported */ @@ -596,6 +598,9 @@ main(int argc, char *argv[])  			if (error)  				errc(EX_DATAERR, error, "format");  			break; +		case 'h':	/* HELP */ +			usage(NULL); +			break;  		case 'o':	/* OUTPUT FILE */  			if (outfd != 1)  				usage("multiple output files given"); diff --git a/usr.bin/mktemp/mktemp.1 b/usr.bin/mktemp/mktemp.1 index 063f25f216dc..3b8381c0586c 100644 --- a/usr.bin/mktemp/mktemp.1 +++ b/usr.bin/mktemp/mktemp.1 @@ -27,7 +27,7 @@  .\"  .\" From: $OpenBSD: mktemp.1,v 1.8 1998/03/19 06:13:37 millert Exp $  .\" -.Dd August 4, 2022 +.Dd September 27, 2025  .Dt MKTEMP 1  .Os  .Sh NAME @@ -185,6 +185,13 @@ but still introduces a race condition.  Use of this  option is not encouraged.  .El +.Sh ENVIRONMENT +.Bl -tag -width TMPDIR +.It Ev TMPDIR +The directory in which to store temporary files. +Refer to +.Xr environ 7 . +.El  .Sh EXIT STATUS  .Ex -std  .Sh EXAMPLES @@ -200,7 +207,8 @@ TMPFILE=`mktemp /tmp/${tempfoo}.XXXXXXXXXX` || exit 1  echo "program output" >> $TMPFILE  .Ed  .Pp -To allow the use of $TMPDIR: +To allow the use of +.Ev TMPDIR :  .Bd -literal -offset indent  tempfoo=`basename $0`  TMPFILE=`mktemp -t ${tempfoo}` || exit 1 diff --git a/usr.bin/ncurses/Makefile b/usr.bin/ncurses/Makefile index 33001e6ab568..1ed8a2d9a915 100644 --- a/usr.bin/ncurses/Makefile +++ b/usr.bin/ncurses/Makefile @@ -1,4 +1,4 @@ -PACKAGE=	runtime +PACKAGE=	ncurses  .include <bsd.own.mk>  .include "${SRCTOP}/lib/ncurses/config.mk" diff --git a/usr.bin/netstat/if.c b/usr.bin/netstat/if.c index 1603c7662bbd..7ee03eb3689b 100644 --- a/usr.bin/netstat/if.c +++ b/usr.bin/netstat/if.c @@ -84,8 +84,10 @@ static const char* pfsyncacts[] = {  	/* PFSYNC_ACT_BUS */		"bulk update mark",  	/* PFSYNC_ACT_TDB */		"TDB replay counter update",  	/* PFSYNC_ACT_EOF */		"end of frame mark", -	/* PFSYNC_ACT_INS_1400 */	"state insert", -	/* PFSYNC_ACT_UPD_1400 */	"state update", +	/* PFSYNC_ACT_INS_1400 */	"14.0 state insert", +	/* PFSYNC_ACT_UPD_1400 */	"14.0 state update", +	/* PFSYNC_ACT_INS_1500 */	"state insert", +	/* PFSYNC_ACT_UPD_1500 */	"state update",  };  static const char* pfsyncacts_name[] = { @@ -102,8 +104,10 @@ static const char* pfsyncacts_name[] = {  	/* PFSYNC_ACT_BUS */		"bulk-update-mark",  	/* PFSYNC_ACT_TDB */		"TDB-replay-counter-update",  	/* PFSYNC_ACT_EOF */		"end-of-frame-mark", -	/* PFSYNC_ACT_INS_1400 */	"state-insert", -	/* PFSYNC_ACT_UPD_1400 */	"state-update", +	/* PFSYNC_ACT_INS_1400 */	"state-insert-1400", +	/* PFSYNC_ACT_UPD_1400 */	"state-update-1400", +	/* PFSYNC_ACT_INS_1500 */	"state-insert", +	/* PFSYNC_ACT_UPD_1500 */	"state-update",  };  static void @@ -278,7 +282,8 @@ next_ifma(struct ifmaddrs *ifma, const char *name, const sa_family_t family)  		sdl = (struct sockaddr_dl *)ifma->ifma_name;  		if (ifma->ifma_addr->sa_family == family && -		    strcmp(sdl->sdl_data, name) == 0) +		    sdl->sdl_nlen == strlen(name) && +		    strncmp(sdl->sdl_data, name, sdl->sdl_nlen) == 0)  			break;  	} diff --git a/usr.bin/netstat/inet.c b/usr.bin/netstat/inet.c index 139ff9294fde..dee245b63a87 100644 --- a/usr.bin/netstat/inet.c +++ b/usr.bin/netstat/inet.c @@ -83,7 +83,7 @@ static void inetprint(const char *, struct in_addr *, int, const char *, int,      const int);  #endif  #ifdef INET6 -static int udp_done, tcp_done, sdp_done; +static int udp_done, udplite_done, tcp_done, sdp_done;  #endif /* INET6 */  static int @@ -100,6 +100,9 @@ pcblist_sysctl(int proto, const char *name, char **bufp)  	case IPPROTO_UDP:  		mibvar = "net.inet.udp.pcblist";  		break; +	case IPPROTO_UDPLITE: +		mibvar = "net.inet.udplite.pcblist"; +		break;  	default:  		mibvar = "net.inet.raw.pcblist";  		break; @@ -222,11 +225,18 @@ protopr(u_long off, const char *name, int af1, int proto)  			udp_done = 1;  #endif  		break; +	case IPPROTO_UDPLITE: +#ifdef INET6 +		if (udplite_done != 0) +			return; +		else +			udplite_done = 1; +#endif +		break;  	}  	if (!pcblist_sysctl(proto, name, &buf))  		return; -  	if (istcp && (cflag || Cflag)) {  		fnamelen = strlen("Stack");  		cnamelen = strlen("CC"); @@ -318,18 +328,18 @@ protopr(u_long off, const char *name, int af1, int proto)  				    "Proto", "Listen", "Local Address");  			else if (Tflag)  				xo_emit((Aflag && !Wflag) ? -    "{T:/%-5.5s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-18.18s} {T:/%s}" : +    "{T:/%-9.9s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-18.18s} {T:/%s}" :  				    ((!Wflag || af1 == AF_INET) ? -    "{T:/%-5.5s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-22.22s} {T:/%s}" : -    "{T:/%-5.5s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-45.45s} {T:/%s}"), +    "{T:/%-9.9s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-22.22s} {T:/%s}" : +    "{T:/%-9.9s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-45.45s} {T:/%s}"),  				    "Proto", "Rexmit", "OOORcv", "0-win",  				    "Local Address", "Foreign Address");  			else {  				xo_emit((Aflag && !Wflag) ? -    "{T:/%-5.5s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-18.18s} {T:/%-18.18s}" : +    "{T:/%-9.9s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-18.18s} {T:/%-18.18s}" :  				    ((!Wflag || af1 == AF_INET) ? -    "{T:/%-5.5s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-22.22s} {T:/%-22.22s}" : -    "{T:/%-5.5s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-45.45s} {T:/%-45.45s}"), +    "{T:/%-9.9s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-22.22s} {T:/%-22.22s}" : +    "{T:/%-9.9s} {T:/%-6.6s} {T:/%-6.6s} {T:/%-45.45s} {T:/%-45.45s}"),  				    "Proto", "Recv-Q", "Send-Q",  				    "Local Address", "Foreign Address");  				if (!xflag && !Rflag) @@ -382,9 +392,14 @@ protopr(u_long off, const char *name, int af1, int proto)  		vchar = ((inp->inp_vflag & INP_IPV4) != 0) ?  		    "4" : "";  		if (istcp && (tp->t_flags & TF_TOE) != 0) -			xo_emit("{:protocol/%-3.3s%-2.2s/%s%s} ", "toe", vchar); -		else -			xo_emit("{:protocol/%-3.3s%-2.2s/%s%s} ", name, vchar); +			xo_emit("{:protocol/%-3.3s%-6.6s/%s%s} ", "toe", vchar); +		else { +			int len; + +			len = max (2, 9 - strlen(name)); +			xo_emit("{:protocol/%.7s%-*.*s/%s%s} ", name, len, len, +			    vchar); +		}  		if (Lflag) {  			char buf1[33]; @@ -767,15 +782,20 @@ tcp_stats(u_long off, const char *name, int af1 __unused, int proto __unused)  	p1a(tcps_sc_badack, "\t\t{:bad-ack/%ju} {N:/badack}\n");  	p1a(tcps_sc_unreach, "\t\t{:unreachable/%ju} {N:/unreach}\n");  	p(tcps_sc_zonefail, "\t\t{:zone-failures/%ju} {N:/zone failure%s}\n"); + +	xo_close_container("syncache"); + +	xo_open_container("syncookies"); +  	p(tcps_sc_sendcookie, "\t{:sent-cookies/%ju} {N:/cookie%s sent}\n"); -	p(tcps_sc_recvcookie, "\t{:received-cookies/%ju} " +	p(tcps_sc_recvcookie, "\t\t{:received-cookies/%ju} "  	    "{N:/cookie%s received}\n"); -	p(tcps_sc_spurcookie, "\t{:spurious-cookies/%ju} " +	p(tcps_sc_spurcookie, "\t\t{:spurious-cookies/%ju} "  	    "{N:/spurious cookie%s rejected}\n"); -	p(tcps_sc_failcookie, "\t{:failed-cookies/%ju} " +	p(tcps_sc_failcookie, "\t\t{:failed-cookies/%ju} "  	    "{N:/failed cookie%s rejected}\n"); -	xo_close_container("syncache"); +	xo_close_container("syncookies");  	xo_open_container("hostcache"); @@ -904,7 +924,7 @@ void  udp_stats(u_long off, const char *name, int af1 __unused, int proto __unused)  {  	struct udpstat udpstat; -	uint64_t delivered; +	uint64_t delivered, noportbmcast;  #ifdef INET6  	if (udp_done != 0) @@ -937,8 +957,11 @@ udp_stats(u_long off, const char *name, int af1 __unused, int proto __unused)  	    "{N:/with no checksum}\n");  	p1a(udps_noport, "{:dropped-no-socket/%ju} "  	    "{N:/dropped due to no socket}\n"); -	p(udps_noportbcast, "{:dropped-broadcast-multicast/%ju} " -	    "{N:/broadcast\\/multicast datagram%s undelivered}\n"); +	noportbmcast = udpstat.udps_noportmcast + udpstat.udps_noportbcast; +	if (noportbmcast || sflag <= 1) +		xo_emit("\t{:dropped-broadcast-multicast/%ju} " +		    "{N:/broadcast\\/multicast datagram%s undelivered}\n", +		    (uintmax_t)noportbmcast, plural(noportbmcast));  	p1a(udps_fullsock, "{:dropped-full-socket-buffer/%ju} "  	    "{N:/dropped due to full socket buffers}\n");  	p1a(udpps_pcbhashmiss, "{:not-for-hashed-pcb/%ju} " @@ -948,11 +971,10 @@ udp_stats(u_long off, const char *name, int af1 __unused, int proto __unused)  		    udpstat.udps_badlen -  		    udpstat.udps_badsum -  		    udpstat.udps_noport - -		    udpstat.udps_noportbcast -  		    udpstat.udps_fullsock;  	if (delivered || sflag <= 1)  		xo_emit("\t{:delivered-packets/%ju} {N:/delivered}\n", -		    (uint64_t)delivered); +		    (uintmax_t)delivered);  	p(udps_opackets, "{:output-packets/%ju} {N:/datagram%s output}\n");  	/* the next statistic is cumulative in udps_noportbcast */  	p(udps_filtermcast, "{:multicast-source-filter-matches/%ju} " diff --git a/usr.bin/netstat/main.c b/usr.bin/netstat/main.c index e8f657006982..79830049948a 100644 --- a/usr.bin/netstat/main.c +++ b/usr.bin/netstat/main.c @@ -83,6 +83,8 @@ static struct protox {  	  tcp_stats,	NULL,		"tcp",	1,	IPPROTO_TCP },  	{ -1	,	N_UDPSTAT,	1,	protopr,  	  udp_stats,	NULL,		"udp",	1,	IPPROTO_UDP }, +	{ -1,		-1,		1,	protopr, +	  NULL,		NULL,		"udplite", 1,	IPPROTO_UDPLITE },  #ifdef SCTP  	{ -1,		N_SCTPSTAT,	1,	sctp_protopr,  	  sctp_stats,	NULL,		"sctp",	1,	IPPROTO_SCTP }, @@ -131,6 +133,8 @@ static struct protox ip6protox[] = {  	  tcp_stats,	NULL,		"tcp",	1,	IPPROTO_TCP },  	{ -1	,	N_UDPSTAT,	1,	protopr,  	  udp_stats,	NULL,		"udp",	1,	IPPROTO_UDP }, +	{ -1,		-1,		1,	protopr, +	  NULL,		NULL,		"udplite", 1,	IPPROTO_UDPLITE },  	{ -1	,	N_IP6STAT,	1,	protopr,  	  ip6_stats,	ip6_ifstats,	"ip6",	1,	IPPROTO_RAW },  	{ -1	,	N_ICMP6STAT,	1,	protopr, diff --git a/usr.bin/netstat/netstat.1 b/usr.bin/netstat/netstat.1 index 1a2c786e90aa..1931c38a1fad 100644 --- a/usr.bin/netstat/netstat.1 +++ b/usr.bin/netstat/netstat.1 @@ -25,7 +25,7 @@  .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF  .\" SUCH DAMAGE.  .\" -.Dd April 30, 2025 +.Dd July 16, 2025  .Dt NETSTAT 1  .Os  .Sh NAME @@ -166,7 +166,7 @@ Generate output via  .Xr libxo 3  in a selection of different human and machine readable formats.  See -.Xr xo_parse_args 3 +.Xr xo_options 7  for details on command line arguments.  .It Fl 4  Show IPv4 only. @@ -416,7 +416,8 @@ When used with  or  .Fl 6 ,  limit the output to IPv4 or IPv6 routes respectively. -This option provides details about individual nexthop addresses used in routing decisions. +This option provides details about individual nexthop addresses +used in routing decisions.  .It Xo  .Bk -words  .Nm netstat @@ -430,7 +431,8 @@ When used with  or  .Fl 6 ,  restrict the output to IPv4 or IPv6 nexthop groups respectively. -This option shows grouped nexthop entries for multipath or load-balanced routing setups. +This option shows grouped nexthop entries for multipath or +load-balanced routing setups.  .It Xo  .Bk -words  .Nm @@ -926,25 +928,25 @@ binary is not available in the  .Sh EXAMPLES  Show packet traffic information (packets, bytes, errors, packet drops, etc) for  interface re0 updated every 2 seconds and exit after 5 outputs: -.Bd -literal -offset indent -$ netstat -w 2 -q 5 -I re0 -.Ed +.Pp +.Dl netstat -w 2 -q 5 -I re0  .Pp  Show statistics for ICMP on any interface: -.Bd -literal -offset indent -$ netstat -s -p icmp -.Ed +.Pp +.Dl netstat -s -p icmp  .Pp  Show routing tables: -.Bd -literal -offset indent -$ netstat -r -.Ed +.Pp +.Dl netstat -r  .Pp  Same as above, but without resolving numeric addresses and port numbers to  names: -.Bd -literal -offset indent -$ netstat -rn -.Ed +.Pp +.Dl netstat -rn +.Pp +Show IPv4 listening sockets: +.Pp +.Dl netstat -4l  .Sh SEE ALSO  .Xr fstat 1 ,  .Xr nfsstat 1 , @@ -952,7 +954,7 @@ $ netstat -rn  .Xr ps 1 ,  .Xr sockstat 1 ,  .Xr libxo 3 , -.Xr xo_parse_args 3 , +.Xr xo_options 7 ,  .Xr bpf 4 ,  .Xr inet 4 ,  .Xr route 4 , diff --git a/usr.bin/netstat/sctp.c b/usr.bin/netstat/sctp.c index c3abac407327..08cfc31c12c9 100644 --- a/usr.bin/netstat/sctp.c +++ b/usr.bin/netstat/sctp.c @@ -622,6 +622,10 @@ sctp_stats(u_long off, const char *name, int af1 __unused, int proto __unused)  	    "{N:/fast path receives all one chunk}\n");  	p1a(sctps_recvexpressm, "\t\t{:receives-fast-path-multipart/%ju} "  	    "{N:/fast path multi-part data}\n"); +	p1a(sctps_recvswcrc, "\t\t{:performed-receive-crc32c-computation/%ju} " +	    "{N:/performed receive crc32c computation}\n"); +	p1a(sctps_recvhwcrc, "\t\t{:performed-receive-crc32c-offloading/%ju} " +	    "{N:/performed receive crc32c offloading}\n");  	/*  	 * output statistics @@ -648,6 +652,10 @@ sctp_stats(u_long off, const char *name, int af1 __unused, int proto __unused)  	    "{N:/output AUTH chunk%s}\n");  	p1a(sctps_senderrors, "\t\t{:send-errors/%ju} "  	    "{N:/ip_output error counter}\n"); +	p1a(sctps_sendswcrc, "\t\t{:performed-receive-crc32c-computation/%ju} " +	    "{N:/performed transmit crc32c computation}\n"); +	p1a(sctps_sendhwcrc, "\t\t{:performed-transmit-crc32c-offloading/%ju} " +	    "{N:/performed transmit crc32c offloading}\n");  	/*  	 * PCKDROPREP statistics diff --git a/usr.bin/newgrp/newgrp.c b/usr.bin/newgrp/newgrp.c index f1da1c8cb1f5..0971f4d13b49 100644 --- a/usr.bin/newgrp/newgrp.c +++ b/usr.bin/newgrp/newgrp.c @@ -186,7 +186,7 @@ addgroup(const char *grpname)  		}  	} -	ngrps_max = sysconf(_SC_NGROUPS_MAX) + 1; +	ngrps_max = sysconf(_SC_NGROUPS_MAX);  	if ((grps = malloc(sizeof(gid_t) * ngrps_max)) == NULL)  		err(1, "malloc");  	if ((ngrps = getgroups(ngrps_max, (gid_t *)grps)) < 0) { @@ -194,7 +194,12 @@ addgroup(const char *grpname)  		goto end;  	} -	/* Remove requested gid from supp. list if it exists. */ +	/* +	 * Remove requested gid from supp. list if it exists and doesn't match +	 * our prior egid -- this exception is to avoid providing the user a +	 * means to get rid of a group that could be used for, e.g., negative +	 * permissions. +	 */  	if (grp->gr_gid != egid && inarray(grp->gr_gid, grps, ngrps)) {  		for (i = 0; i < ngrps; i++)  			if (grps[i] == grp->gr_gid) @@ -217,10 +222,9 @@ addgroup(const char *grpname)  		goto end;  	}  	PRIV_END; -	grps[0] = grp->gr_gid;  	/* Add old effective gid to supp. list if it does not exist. */ -	if (egid != grp->gr_gid && !inarray(egid, grps, ngrps)) { +	if (!inarray(egid, grps, ngrps)) {  		if (ngrps == ngrps_max)  			warnx("too many groups");  		else { diff --git a/usr.bin/nfsstat/nfsstat.1 b/usr.bin/nfsstat/nfsstat.1 index 7d641b50f1ac..a4a00586f21b 100644 --- a/usr.bin/nfsstat/nfsstat.1 +++ b/usr.bin/nfsstat/nfsstat.1 @@ -25,7 +25,7 @@  .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF  .\" SUCH DAMAGE.  .\" -.Dd December 28, 2023 +.Dd July 16, 2025  .Dt NFSSTAT 1  .Os  .Sh NAME @@ -123,7 +123,7 @@ Generate output via  .Xr libxo 3  in a selection of different human and machine readable formats.  See -.Xr xo_parse_args 3 +.Xr xo_options 7  for details on command line arguments.  .El  .Sh SEE ALSO diff --git a/usr.bin/patch/tests/unified_patch_test.sh b/usr.bin/patch/tests/unified_patch_test.sh index 7d4b74182c41..a91332908773 100755 --- a/usr.bin/patch/tests/unified_patch_test.sh +++ b/usr.bin/patch/tests/unified_patch_test.sh @@ -141,6 +141,23 @@ file_removal_body()  	atf_check -o inline:"y\n" cat foo  } +atf_test_case namespace +namespace_head() +{ +	atf_set "descr" "Test that patch(1) handles files with spaces in the name" +} +namespace_body() +{ +	echo "ABC" > "with spaces.orig" +	echo "ZYX" > "with spaces" + +	atf_check -s not-exit:0 -o save:spaces.diff \ +	    diff -u "with spaces.orig" "with spaces" + +	atf_check mv "with spaces.orig" "with spaces" +	atf_check -o not-empty patch < spaces.diff +} +  atf_test_case plinelen  plinelen_body()  { @@ -166,5 +183,6 @@ atf_init_test_cases()  	atf_add_test_case file_creation  	atf_add_test_case file_nodupe  	atf_add_test_case file_removal +	atf_add_test_case namespace  	atf_add_test_case plinelen  } diff --git a/usr.bin/pom/pom.6 b/usr.bin/pom/pom.6 index a3dc68b0a46b..a4dbdde2d4f5 100644 --- a/usr.bin/pom/pom.6 +++ b/usr.bin/pom/pom.6 @@ -25,7 +25,7 @@  .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF  .\" SUCH DAMAGE.  .\" -.Dd July 14, 2010 +.Dd July 24, 2025  .Dt POM 6  .Os  .Sh NAME @@ -60,4 +60,10 @@ but not  has been specified, it will calculate the phase of the moon on that  day at midnight.  .Sh SEE ALSO -`Practical Astronomy with Your Calculator' by Duffett-Smith. +.Rs +.%A Peter Duffett-Smith +.%B Practical Astronomy with Your Calculator +.%I Cambridge University Press +.%C Cambridge, UK +.%D 1979 +.Re diff --git a/usr.bin/pom/pom.c b/usr.bin/pom/pom.c index db0033373b47..bcfbcadc8238 100644 --- a/usr.bin/pom/pom.c +++ b/usr.bin/pom/pom.c @@ -83,6 +83,7 @@ main(int argc, char **argv)  		err(1, "unable to limit capabitilities for stdio");  	caph_cache_catpages(); +	caph_cache_tzdata();  	if (caph_enter() < 0)  		err(1, "unable to enter capability mode"); diff --git a/usr.bin/procstat/procstat.1 b/usr.bin/procstat/procstat.1 index 1e05e235e619..b810abf66da7 100644 --- a/usr.bin/procstat/procstat.1 +++ b/usr.bin/procstat/procstat.1 @@ -23,7 +23,7 @@  .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF  .\" SUCH DAMAGE.  .\" -.Dd April 7, 2022 +.Dd July 16, 2025  .Dt PROCSTAT 1  .Os  .Sh NAME @@ -136,7 +136,7 @@ flag is specified the output is generated via  .Xr libxo 3  in a selection of different human and machine readable formats.  See -.Xr xo_parse_args 3 +.Xr xo_options 7  for details on command line arguments.  .Pp  The following commands are available for @@ -870,7 +870,7 @@ procstat: procstat_getprocs()  .Xr libprocstat 3 ,  .Xr libxo 3 ,  .Xr signal 3 , -.Xr xo_parse_args 3 , +.Xr xo_options 7 ,  .Xr ddb 4 ,  .Xr divert 4 ,  .Xr icmp 4 , diff --git a/usr.bin/quota/quota.c b/usr.bin/quota/quota.c index b5d28fd7c184..9ad4076cec40 100644 --- a/usr.bin/quota/quota.c +++ b/usr.bin/quota/quota.c @@ -100,8 +100,7 @@ static char	*filename = NULL;  int  main(int argc, char *argv[])  { -	int ngroups;  -	gid_t mygid, gidset[NGROUPS]; +	int ngroups;  	int i, ch, gflag = 0, uflag = 0, errflag = 0;  	while ((ch = getopt(argc, argv, "f:ghlrquv")) != -1) { @@ -142,11 +141,15 @@ main(int argc, char *argv[])  		if (uflag)  			errflag += showuid(getuid());  		if (gflag) { +			gid_t mygid, myegid, gidset[NGROUPS_MAX]; +  			mygid = getgid(); -			ngroups = getgroups(NGROUPS, gidset); +			errflag += showgid(mygid); +			myegid = getegid(); +			errflag += showgid(myegid); +			ngroups = getgroups(NGROUPS_MAX, gidset);  			if (ngroups < 0)  				err(1, "getgroups"); -			errflag += showgid(mygid);  			for (i = 0; i < ngroups; i++)  				if (gidset[i] != mygid)  					errflag += showgid(gidset[i]); diff --git a/usr.bin/sdiff/Makefile b/usr.bin/sdiff/Makefile index 03587f373098..af9a037e9a58 100644 --- a/usr.bin/sdiff/Makefile +++ b/usr.bin/sdiff/Makefile @@ -3,7 +3,7 @@  PROG=	sdiff  SRCS=	edit.c sdiff.c -MAN1=	sdiff.1 +MAN=	sdiff.1  HAS_TESTS=  SUBDIR.${MK_TESTS}+= tests diff --git a/usr.bin/sed/sed.1 b/usr.bin/sed/sed.1 index 345f673310d8..5fd894eaf78b 100644 --- a/usr.bin/sed/sed.1 +++ b/usr.bin/sed/sed.1 @@ -1,3 +1,6 @@ +.\" +.\" SPDX-License-Identifier: BSD-3-Clause +.\"  .\" Copyright (c) 1992, 1993  .\"	The Regents of the University of California.  All rights reserved.  .\" @@ -28,7 +31,7 @@  .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF  .\" SUCH DAMAGE.  .\" -.Dd December 17, 2024 +.Dd June 14, 2025  .Dt SED 1  .Os  .Sh NAME @@ -597,17 +600,17 @@ with  .Ql baz  when piped from another command:  .Bd -literal -offset indent -echo "An alternate word, like bar, is sometimes used in examples." | sed 's/bar/baz/' +echo "use bar in examples" | sed 's/bar/baz/'  .Ed  .Pp  Using backlashes can sometimes be hard to read and follow:  .Bd -literal -offset indent -echo "/home/example" | sed  's/\\/home\\/example/\\/usr\\/local\\/example/' +echo "/bin/bash" | sed 's/\\/bin\\/bash/\\/bin\\/sh/'  .Ed  .Pp  Using a different separator can be handy when working with paths:  .Bd -literal -offset indent -echo "/home/example" | sed 's#/home/example#/usr/local/example#' +echo "/bin/bash" | sed 's#/bin/bash#/bin/sh#'  .Ed  .Pp  Replace all occurrences of diff --git a/usr.bin/shar/Makefile b/usr.bin/shar/Makefile deleted file mode 100644 index fc940c06d463..000000000000 --- a/usr.bin/shar/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -SCRIPTS=shar.sh -MAN=	shar.1 - -.include <bsd.prog.mk> diff --git a/usr.bin/shar/Makefile.depend b/usr.bin/shar/Makefile.depend deleted file mode 100644 index 11aba52f82cf..000000000000 --- a/usr.bin/shar/Makefile.depend +++ /dev/null @@ -1,10 +0,0 @@ -# Autogenerated - do NOT edit! - -DIRDEPS = \ - - -.include <dirdeps.mk> - -.if ${DEP_RELDIR} == ${_DEP_RELDIR} -# local dependencies - needed for -jN in clean tree -.endif diff --git a/usr.bin/shar/shar.1 b/usr.bin/shar/shar.1 deleted file mode 100644 index 6beb1e84ceab..000000000000 --- a/usr.bin/shar/shar.1 +++ /dev/null @@ -1,121 +0,0 @@ -.\" Copyright (c) 1990, 1993 -.\"	The Regents of the University of California.  All rights reserved. -.\" -.\" Redistribution and use in source and binary forms, with or without -.\" modification, are permitted provided that the following conditions -.\" are met: -.\" 1. Redistributions of source code must retain the above copyright -.\"    notice, this list of conditions and the following disclaimer. -.\" 2. Redistributions in binary form must reproduce the above copyright -.\"    notice, this list of conditions and the following disclaimer in the -.\"    documentation and/or other materials provided with the distribution. -.\" 3. Neither the name of the University nor the names of its contributors -.\"    may be used to endorse or promote products derived from this software -.\"    without specific prior written permission. -.\" -.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND -.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -.\" SUCH DAMAGE. -.\" -.Dd January 1, 2025 -.Dt SHAR 1 -.Os -.Sh NAME -.Nm shar -.Nd create a shell archive of files -.Sh DEPRECATION NOTICE -.Nm -is obsolete and may not be present in -.Fx 15 -and later. -Because shell archives are simultaneously data and code and are typically -interpreted by -.Xr sh 1 , -they can easily be trojan-horsed and pose a significant security risk to users. -The -.Xr tar 1 -utility can still produce shar encodings of files if needed. -The -.Pa sysutils/freebsd-shar -port has been created to maintain this version of -.Nm -past its deprecation in base. -.Sh SYNOPSIS -.Nm -.Ar -.Sh DESCRIPTION -The -.Nm -command writes a -.Xr sh 1 -shell script to the standard output which will recreate the file -hierarchy specified by the command line operands. -Directories will be recreated and must be specified before the -files they contain (the -.Xr find 1 -utility does this correctly). -.Pp -The -.Nm -command is normally used for distributing files by -.Xr ftp 1 -or -.Xr mail 1 . -.Sh EXAMPLES -To create a shell archive of the program -.Xr ls 1 -and mail it to Rick: -.Bd -literal -offset indent -cd ls -shar `find . -print` \&| mail -s "ls source" rick -.Ed -.Pp -To recreate the program directory: -.Bd -literal -offset indent -mkdir ls -cd ls -\&... -<delete header lines and examine mailed archive> -\&... -sh archive -.Ed -.Sh SEE ALSO -.Xr compress 1 , -.Xr mail 1 , -.Xr tar 1 , -.Xr uuencode 1 -.Sh HISTORY -The -.Nm -command appeared in -.Bx 4.4 . -.Sh BUGS -The -.Nm -command makes no provisions for special types of files or files containing -magic characters. -The -.Nm -command cannot handle files without a newline ('\\n') -as the last character. -.Pp -It is easy to insert trojan horses into -.Nm -files. -It is strongly recommended that all shell archive files be examined -before running them through -.Xr sh 1 . -Archives produced using this implementation of -.Nm -may be easily examined with the command: -.Bd -literal -offset indent -egrep -av '^[X#]' shar.file -.Ed diff --git a/usr.bin/shar/shar.sh b/usr.bin/shar/shar.sh deleted file mode 100644 index 52c31b419fc4..000000000000 --- a/usr.bin/shar/shar.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/sh - -# -# SPDX-License-Identifier: BSD-3-Clause -# -# Copyright (c) 1990, 1993 -#	The Regents of the University of California.  All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -#    notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -#    notice, this list of conditions and the following disclaimer in the -#    documentation and/or other materials provided with the distribution. -# 3. Neither the name of the University nor the names of its contributors -#    may be used to endorse or promote products derived from this software -#    without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -# SUCH DAMAGE. - -if [ $# -eq 0 ]; then -	echo 'usage: shar file ...' 1>&2 -	exit 64			# EX_USAGE -fi - -for i -do -	if [ ! \( -d $i -o -r $i \) ]; then -		echo "$i inaccessible or not exist" 1>&2 -		exit 66		# EX_NOINPUT -	fi -done - -cat << EOF -# This is a shell archive.  Save it in a file, remove anything before -# this line, and then unpack it by entering "sh file".  Note, it may -# create directories; files and directories will be owned by you and -# have default permissions. -# -# This archive contains: -# -EOF - -for i -do -	echo "#	$i" -done - -echo "#" - -for i -do -	if [ -d "$i" ]; then -		echo "echo c - '$i'" -		echo "mkdir -p '$i' > /dev/null 2>&1" -	else -		md5sum=`echo -n "$i" | md5` -		echo "echo x - '$i'" -		echo "sed 's/^X//' >'$i' << '$md5sum'" -		sed 's/^/X/' "$i" || exit 1 -		echo "$md5sum" -	fi -done -echo exit -echo "" - -exit 0 diff --git a/usr.bin/sockstat/Makefile b/usr.bin/sockstat/Makefile index 188432dfc27e..c6e7a078162b 100644 --- a/usr.bin/sockstat/Makefile +++ b/usr.bin/sockstat/Makefile @@ -1,8 +1,9 @@  .include <src.opts.mk>  PROG=		sockstat +SRCS=		main.c sockstat.c -LIBADD=		jail +LIBADD=		jail xo  .if ${MK_CASPER} != "no"  LIBADD+=	casper @@ -13,4 +14,7 @@ LIBADD+=	cap_sysctl  CFLAGS+=	-DWITH_CASPER  .endif +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests +  .include <bsd.prog.mk> diff --git a/usr.bin/sockstat/main.c b/usr.bin/sockstat/main.c new file mode 100644 index 000000000000..07663e54534d --- /dev/null +++ b/usr.bin/sockstat/main.c @@ -0,0 +1,1962 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2002 Dag-Erling Smørgrav + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer + *    in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + *    derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/file.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/sysctl.h> +#include <sys/jail.h> +#include <sys/user.h> +#include <sys/queue.h> +#include <sys/tree.h> + +#include <sys/un.h> +#include <sys/unpcb.h> + +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_pcb.h> +#include <netinet/sctp.h> +#include <netinet/tcp.h> +#define TCPSTATES /* load state names */ +#include <netinet/tcp_fsm.h> +#include <netinet/tcp_seq.h> +#include <netinet/tcp_var.h> +#include <netinet/tcp_log_buf.h> +#include <arpa/inet.h> + +#include <capsicum_helpers.h> +#include <errno.h> +#include <inttypes.h> +#include <jail.h> +#include <netdb.h> +#include <pwd.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <libxo/xo.h> + +#include <libcasper.h> +#include <casper/cap_net.h> +#include <casper/cap_netdb.h> +#include <casper/cap_pwd.h> +#include <casper/cap_sysctl.h> + +#include "sockstat.h" + +#define SOCKSTAT_XO_VERSION "1" +#define	sstosin(ss)	((struct sockaddr_in *)(ss)) +#define	sstosin6(ss)	((struct sockaddr_in6 *)(ss)) +#define	sstosun(ss)	((struct sockaddr_un *)(ss)) +#define	sstosa(ss)	((struct sockaddr *)(ss)) + +static bool	 opt_4;		/* Show IPv4 sockets */ +static bool	 opt_6;		/* Show IPv6 sockets */ +static bool	 opt_A;		/* Show kernel address of pcb */ +static bool	 opt_b;		/* Show BBLog state */ +static bool	 opt_C;		/* Show congestion control */ +static bool	 opt_c;		/* Show connected sockets */ +static bool	 opt_f;		/* Show FIB numbers */ +static bool	 opt_I;		/* Show spliced socket addresses */ +static bool	 opt_i;		/* Show inp_gencnt */ +static int	 opt_j;		/* Show specified jail */ +static bool	 opt_L;		/* Don't show IPv4 or IPv6 loopback sockets */ +static bool	 opt_l;		/* Show listening sockets */ +static bool	 opt_n;		/* Don't resolve UIDs to user names */ +static bool	 opt_q;		/* Don't show header */ +static bool	 opt_S;		/* Show protocol stack if applicable */ +static bool	 opt_s;		/* Show protocol state if applicable */ +static bool	 opt_U;		/* Show remote UDP encapsulation port number */ +static bool	 opt_u;		/* Show Unix domain sockets */ +static u_int	 opt_v;		/* Verbose mode */ +static bool	 opt_w;		/* Automatically size the columns */ +static bool	 is_xo_style_encoding; +static bool	 show_path_state = false; + +/* + * Default protocols to use if no -P was defined. + */ +static const char *default_protos[] = {"sctp", "tcp", "udp", "udplite", +    "divert" }; +static size_t	   default_numprotos = nitems(default_protos); + +static int	*protos;	/* protocols to use */ +static size_t	 numprotos;	/* allocated size of protos[] */ + +struct addr { +	union { +		struct sockaddr_storage address; +		struct {	/* unix(4) faddr */ +			kvaddr_t conn; +			kvaddr_t firstref; +			kvaddr_t nextref; +		}; +	}; +	unsigned int encaps_port; +	int state; +	struct addr *next; +}; + +struct sock { +	union { +		RB_ENTRY(sock) socket_tree;	/* tree of pcbs with socket */ +		SLIST_ENTRY(sock) socket_list;	/* list of pcbs w/o socket */ +	}; +	RB_ENTRY(sock) pcb_tree; +	kvaddr_t socket; +	kvaddr_t pcb; +	kvaddr_t splice_socket; +	uint64_t inp_gencnt; +	int shown; +	int vflag; +	int family; +	int proto; +	int state; +	int fibnum; +	int bblog_state; +	const char *protoname; +	char stack[TCP_FUNCTION_NAME_LEN_MAX]; +	char cc[TCP_CA_NAME_MAX]; +	struct addr *laddr; +	struct addr *faddr; +}; + +static RB_HEAD(socks_t, sock) socks = RB_INITIALIZER(&socks); +static int64_t +socket_compare(const struct sock *a, const struct sock *b) +{ +	return ((int64_t)(a->socket/2 - b->socket/2)); +} +RB_GENERATE_STATIC(socks_t, sock, socket_tree, socket_compare); + +static RB_HEAD(pcbs_t, sock) pcbs = RB_INITIALIZER(&pcbs); +static int64_t +pcb_compare(const struct sock *a, const struct sock *b) +{ +        return ((int64_t)(a->pcb/2 - b->pcb/2)); +} +RB_GENERATE_STATIC(pcbs_t, sock, pcb_tree, pcb_compare); + +static SLIST_HEAD(, sock) nosocks = SLIST_HEAD_INITIALIZER(&nosocks); + +struct file { +	RB_ENTRY(file)	file_tree; +	kvaddr_t	xf_data; +	pid_t	xf_pid; +	uid_t	xf_uid; +	int	xf_fd; +}; + +static RB_HEAD(files_t, file) ftree = RB_INITIALIZER(&ftree); +static int64_t +file_compare(const struct file *a, const struct file *b) +{ +	return ((int64_t)(a->xf_data/2 - b->xf_data/2)); +} +RB_GENERATE_STATIC(files_t, file, file_tree, file_compare); + +static struct file *files; +static int nfiles; + +static cap_channel_t *capnet; +static cap_channel_t *capnetdb; +static cap_channel_t *capsysctl; +static cap_channel_t *cappwd; + +static bool +_check_ksize(size_t received_size, size_t expected_size, const char *struct_name) +{ +	if (received_size != expected_size) { +		xo_warnx("%s size mismatch: expected %zd, received %zd", +		    struct_name, expected_size, received_size); +		return false; +	} +	return true; +} +#define check_ksize(_sz, _struct)	(_check_ksize(_sz, sizeof(_struct), #_struct)) + +static void +_enforce_ksize(size_t received_size, size_t expected_size, const char *struct_name) +{ +	if (received_size != expected_size) { +		xo_errx(1, "fatal: struct %s size mismatch: expected %zd, received %zd", +		    struct_name, expected_size, received_size); +	} +} +#define enforce_ksize(_sz, _struct)	(_enforce_ksize(_sz, sizeof(_struct), #_struct)) + +static int +get_proto_type(const char *proto) +{ +	struct protoent *pent; + +	if (strlen(proto) == 0) +		return (0); +	if (capnetdb != NULL) +		pent = cap_getprotobyname(capnetdb, proto); +	else +		pent = getprotobyname(proto); +	if (pent == NULL) { +		xo_warn("cap_getprotobyname"); +		return (-1); +	} +	return (pent->p_proto); +} + +static void +init_protos(int num) +{ +	int proto_count = 0; + +	if (num > 0) { +		proto_count = num; +	} else { +		/* Find the maximum number of possible protocols. */ +		while (getprotoent() != NULL) +			proto_count++; +		endprotoent(); +	} + +	if ((protos = malloc(sizeof(int) * proto_count)) == NULL) +		xo_err(1, "malloc"); +	numprotos = proto_count; +} + +static int +parse_protos(char *protospec) +{ +	char *prot; +	int proto_type, proto_index; + +	if (protospec == NULL) +		return (-1); + +	init_protos(0); +	proto_index = 0; +	while ((prot = strsep(&protospec, ",")) != NULL) { +		if (strlen(prot) == 0) +			continue; +		proto_type = get_proto_type(prot); +		if (proto_type != -1) +			protos[proto_index++] = proto_type; +	} +	numprotos = proto_index; +	return (proto_index); +} + +static void +sockaddr(struct sockaddr_storage *ss, int af, void *addr, int port) +{ +	struct sockaddr_in *sin4; +	struct sockaddr_in6 *sin6; + +	bzero(ss, sizeof(*ss)); +	switch (af) { +	case AF_INET: +		sin4 = sstosin(ss); +		sin4->sin_len = sizeof(*sin4); +		sin4->sin_family = af; +		sin4->sin_port = port; +		sin4->sin_addr = *(struct in_addr *)addr; +		break; +	case AF_INET6: +		sin6 = sstosin6(ss); +		sin6->sin6_len = sizeof(*sin6); +		sin6->sin6_family = af; +		sin6->sin6_port = port; +		sin6->sin6_addr = *(struct in6_addr *)addr; +#define	s6_addr16	__u6_addr.__u6_addr16 +		if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { +			sin6->sin6_scope_id = +			    ntohs(sin6->sin6_addr.s6_addr16[1]); +			sin6->sin6_addr.s6_addr16[1] = 0; +		} +		break; +	default: +		abort(); +	} +} + +static void +free_socket(struct sock *sock) +{ +	struct addr *cur, *next; + +	cur = sock->laddr; +	while (cur != NULL) { +		next = cur->next; +		free(cur); +		cur = next; +	} +	cur = sock->faddr; +	while (cur != NULL) { +		next = cur->next; +		free(cur); +		cur = next; +	} +	free(sock); +} + +static void +gather_sctp(void) +{ +	struct sock *sock; +	struct addr *laddr, *prev_laddr, *faddr, *prev_faddr; +	struct xsctp_inpcb *xinpcb; +	struct xsctp_tcb *xstcb; +	struct xsctp_raddr *xraddr; +	struct xsctp_laddr *xladdr; +	const char *varname; +	size_t len, offset; +	char *buf; +	int vflag; +	int no_stcb, local_all_loopback, foreign_all_loopback; + +	vflag = 0; +	if (opt_4) +		vflag |= INP_IPV4; +	if (opt_6) +		vflag |= INP_IPV6; + +	varname = "net.inet.sctp.assoclist"; +	if (cap_sysctlbyname(capsysctl, varname, 0, &len, 0, 0) < 0) { +		if (errno != ENOENT) +			xo_err(1, "cap_sysctlbyname()"); +		return; +	} +	if ((buf = (char *)malloc(len)) == NULL) { +		xo_err(1, "malloc()"); +		return; +	} +	if (cap_sysctlbyname(capsysctl, varname, buf, &len, 0, 0) < 0) { +		xo_err(1, "cap_sysctlbyname()"); +		free(buf); +		return; +	} +	xinpcb = (struct xsctp_inpcb *)(void *)buf; +	offset = sizeof(struct xsctp_inpcb); +	while ((offset < len) && (xinpcb->last == 0)) { +		if ((sock = calloc(1, sizeof *sock)) == NULL) +			xo_err(1, "malloc()"); +		sock->socket = xinpcb->socket; +		sock->proto = IPPROTO_SCTP; +		sock->protoname = "sctp"; +		if (xinpcb->maxqlen == 0) +			sock->state = SCTP_CLOSED; +		else +			sock->state = SCTP_LISTEN; +		if (xinpcb->flags & SCTP_PCB_FLAGS_BOUND_V6) { +			sock->family = AF_INET6; +			/* +			 * Currently there is no way to distinguish between +			 * IPv6 only sockets or dual family sockets. +			 * So mark it as dual socket. +			 */ +			sock->vflag = INP_IPV6 | INP_IPV4; +		} else { +			sock->family = AF_INET; +			sock->vflag = INP_IPV4; +		} +		prev_laddr = NULL; +		local_all_loopback = 1; +		while (offset < len) { +			xladdr = (struct xsctp_laddr *)(void *)(buf + offset); +			offset += sizeof(struct xsctp_laddr); +			if (xladdr->last == 1) +				break; +			if ((laddr = calloc(1, sizeof(struct addr))) == NULL) +				xo_err(1, "malloc()"); +			switch (xladdr->address.sa.sa_family) { +			case AF_INET: +#define	__IN_IS_ADDR_LOOPBACK(pina) \ +	((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) +				if (!__IN_IS_ADDR_LOOPBACK( +				    &xladdr->address.sin.sin_addr)) +					local_all_loopback = 0; +#undef	__IN_IS_ADDR_LOOPBACK +				sockaddr(&laddr->address, AF_INET, +				    &xladdr->address.sin.sin_addr, +				    htons(xinpcb->local_port)); +				break; +			case AF_INET6: +				if (!IN6_IS_ADDR_LOOPBACK( +				    &xladdr->address.sin6.sin6_addr)) +					local_all_loopback = 0; +				sockaddr(&laddr->address, AF_INET6, +				    &xladdr->address.sin6.sin6_addr, +				    htons(xinpcb->local_port)); +				break; +			default: +				xo_errx(1, "address family %d not supported", +				    xladdr->address.sa.sa_family); +			} +			laddr->next = NULL; +			if (prev_laddr == NULL) +				sock->laddr = laddr; +			else +				prev_laddr->next = laddr; +			prev_laddr = laddr; +		} +		if (sock->laddr == NULL) { +			if ((sock->laddr = +			    calloc(1, sizeof(struct addr))) == NULL) +				xo_err(1, "malloc()"); +			sock->laddr->address.ss_family = sock->family; +			if (sock->family == AF_INET) +				sock->laddr->address.ss_len = +				    sizeof(struct sockaddr_in); +			else +				sock->laddr->address.ss_len = +				    sizeof(struct sockaddr_in6); +			local_all_loopback = 0; +		} +		if ((sock->faddr = calloc(1, sizeof(struct addr))) == NULL) +			xo_err(1, "malloc()"); +		sock->faddr->address.ss_family = sock->family; +		if (sock->family == AF_INET) +			sock->faddr->address.ss_len = +			    sizeof(struct sockaddr_in); +		else +			sock->faddr->address.ss_len = +			    sizeof(struct sockaddr_in6); +		no_stcb = 1; +		while (offset < len) { +			xstcb = (struct xsctp_tcb *)(void *)(buf + offset); +			offset += sizeof(struct xsctp_tcb); +			if (no_stcb) { +				if (opt_l && (sock->vflag & vflag) && +				    (!opt_L || !local_all_loopback) && +				    ((xinpcb->flags & SCTP_PCB_FLAGS_UDPTYPE) || +				     (xstcb->last == 1))) { +					RB_INSERT(socks_t, &socks, sock); +				} else { +					free_socket(sock); +				} +			} +			if (xstcb->last == 1) +				break; +			no_stcb = 0; +			if (opt_c) { +				if ((sock = calloc(1, sizeof *sock)) == NULL) +					xo_err(1, "malloc()"); +				sock->socket = xinpcb->socket; +				sock->proto = IPPROTO_SCTP; +				sock->protoname = "sctp"; +				sock->state = (int)xstcb->state; +				if (xinpcb->flags & SCTP_PCB_FLAGS_BOUND_V6) { +					sock->family = AF_INET6; +				/* +				 * Currently there is no way to distinguish +				 * between IPv6 only sockets or dual family +				 *  sockets. So mark it as dual socket. +				 */ +					sock->vflag = INP_IPV6 | INP_IPV4; +				} else { +					sock->family = AF_INET; +					sock->vflag = INP_IPV4; +				} +			} +			prev_laddr = NULL; +			local_all_loopback = 1; +			while (offset < len) { +				xladdr = (struct xsctp_laddr *)(void *)(buf + +				    offset); +				offset += sizeof(struct xsctp_laddr); +				if (xladdr->last == 1) +					break; +				if (!opt_c) +					continue; +				laddr = calloc(1, sizeof(struct addr)); +				if (laddr == NULL) +					xo_err(1, "malloc()"); +				switch (xladdr->address.sa.sa_family) { +				case AF_INET: +#define	__IN_IS_ADDR_LOOPBACK(pina) \ +	((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) +					if (!__IN_IS_ADDR_LOOPBACK( +					    &xladdr->address.sin.sin_addr)) +						local_all_loopback = 0; +#undef	__IN_IS_ADDR_LOOPBACK +					sockaddr(&laddr->address, AF_INET, +					    &xladdr->address.sin.sin_addr, +					    htons(xstcb->local_port)); +					break; +				case AF_INET6: +					if (!IN6_IS_ADDR_LOOPBACK( +					    &xladdr->address.sin6.sin6_addr)) +						local_all_loopback = 0; +					sockaddr(&laddr->address, AF_INET6, +					    &xladdr->address.sin6.sin6_addr, +					    htons(xstcb->local_port)); +					break; +				default: +					xo_errx(1, +					    "address family %d not supported", +					    xladdr->address.sa.sa_family); +				} +				laddr->next = NULL; +				if (prev_laddr == NULL) +					sock->laddr = laddr; +				else +					prev_laddr->next = laddr; +				prev_laddr = laddr; +			} +			prev_faddr = NULL; +			foreign_all_loopback = 1; +			while (offset < len) { +				xraddr = (struct xsctp_raddr *)(void *)(buf + +				    offset); +				offset += sizeof(struct xsctp_raddr); +				if (xraddr->last == 1) +					break; +				if (!opt_c) +					continue; +				faddr = calloc(1, sizeof(struct addr)); +				if (faddr == NULL) +					xo_err(1, "malloc()"); +				switch (xraddr->address.sa.sa_family) { +				case AF_INET: +#define	__IN_IS_ADDR_LOOPBACK(pina) \ +	((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) +					if (!__IN_IS_ADDR_LOOPBACK( +					    &xraddr->address.sin.sin_addr)) +						foreign_all_loopback = 0; +#undef	__IN_IS_ADDR_LOOPBACK +					sockaddr(&faddr->address, AF_INET, +					    &xraddr->address.sin.sin_addr, +					    htons(xstcb->remote_port)); +					break; +				case AF_INET6: +					if (!IN6_IS_ADDR_LOOPBACK( +					    &xraddr->address.sin6.sin6_addr)) +						foreign_all_loopback = 0; +					sockaddr(&faddr->address, AF_INET6, +					    &xraddr->address.sin6.sin6_addr, +					    htons(xstcb->remote_port)); +					break; +				default: +					xo_errx(1, +					    "address family %d not supported", +					    xraddr->address.sa.sa_family); +				} +				faddr->encaps_port = xraddr->encaps_port; +				faddr->state = xraddr->state; +				faddr->next = NULL; +				if (prev_faddr == NULL) +					sock->faddr = faddr; +				else +					prev_faddr->next = faddr; +				prev_faddr = faddr; +			} +			if (opt_c) { +				if ((sock->vflag & vflag) && +				    (!opt_L || +				     !(local_all_loopback || +				     foreign_all_loopback))) { +					RB_INSERT(socks_t, &socks, sock); +					show_path_state = true; +				} else { +					free_socket(sock); +				} +			} +		} +		xinpcb = (struct xsctp_inpcb *)(void *)(buf + offset); +		offset += sizeof(struct xsctp_inpcb); +	} +	free(buf); +} + +static void +gather_inet(int proto) +{ +	struct xinpgen *xig, *exig; +	struct xinpcb *xip; +	struct xtcpcb *xtp = NULL; +	struct xsocket *so; +	struct sock *sock; +	struct addr *laddr, *faddr; +	const char *varname, *protoname; +	size_t len, bufsize; +	void *buf; +	int retry, vflag; + +	vflag = 0; +	if (opt_4) +		vflag |= INP_IPV4; +	if (opt_6) +		vflag |= INP_IPV6; + +	switch (proto) { +	case IPPROTO_TCP: +		varname = "net.inet.tcp.pcblist"; +		protoname = "tcp"; +		break; +	case IPPROTO_UDP: +		varname = "net.inet.udp.pcblist"; +		protoname = "udp"; +		break; +	case IPPROTO_UDPLITE: +		varname = "net.inet.udplite.pcblist"; +		protoname = "udplite"; +		break; +	case IPPROTO_DIVERT: +		varname = "net.inet.divert.pcblist"; +		protoname = "div"; +		break; +	default: +		xo_errx(1, "protocol %d not supported", proto); +	} + +	buf = NULL; +	bufsize = 8192; +	retry = 5; +	do { +		for (;;) { +			if ((buf = realloc(buf, bufsize)) == NULL) +				xo_err(1, "realloc()"); +			len = bufsize; +			if (cap_sysctlbyname(capsysctl, varname, buf, &len, +			    NULL, 0) == 0) +				break; +			if (errno == ENOENT) +				goto out; +			if (errno != ENOMEM || len != bufsize) +				xo_err(1, "cap_sysctlbyname()"); +			bufsize *= 2; +		} +		xig = (struct xinpgen *)buf; +		exig = (struct xinpgen *)(void *) +		    ((char *)buf + len - sizeof *exig); +		enforce_ksize(xig->xig_len, struct xinpgen); +		enforce_ksize(exig->xig_len, struct xinpgen); +	} while (xig->xig_gen != exig->xig_gen && retry--); + +	if (xig->xig_gen != exig->xig_gen && opt_v) +		xo_warnx("warning: data may be inconsistent"); + +	for (;;) { +		xig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len); +		if (xig >= exig) +			break; +		switch (proto) { +		case IPPROTO_TCP: +			xtp = (struct xtcpcb *)xig; +			xip = &xtp->xt_inp; +			if (!check_ksize(xtp->xt_len, struct xtcpcb)) +				goto out; +			protoname = xtp->t_flags & TF_TOE ? "toe" : "tcp"; +			break; +		case IPPROTO_UDP: +		case IPPROTO_UDPLITE: +		case IPPROTO_DIVERT: +			xip = (struct xinpcb *)xig; +			if (!check_ksize(xip->xi_len, struct xinpcb)) +				goto out; +			break; +		default: +			xo_errx(1, "protocol %d not supported", proto); +		} +		so = &xip->xi_socket; +		if ((xip->inp_vflag & vflag) == 0) +			continue; +		if (xip->inp_vflag & INP_IPV4) { +			if ((xip->inp_fport == 0 && !opt_l) || +			    (xip->inp_fport != 0 && !opt_c)) +				continue; +#define	__IN_IS_ADDR_LOOPBACK(pina) \ +	((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) +			if (opt_L && +			    (__IN_IS_ADDR_LOOPBACK(&xip->inp_faddr) || +			     __IN_IS_ADDR_LOOPBACK(&xip->inp_laddr))) +				continue; +#undef	__IN_IS_ADDR_LOOPBACK +		} else if (xip->inp_vflag & INP_IPV6) { +			if ((xip->inp_fport == 0 && !opt_l) || +			    (xip->inp_fport != 0 && !opt_c)) +				continue; +			if (opt_L && +			    (IN6_IS_ADDR_LOOPBACK(&xip->in6p_faddr) || +			     IN6_IS_ADDR_LOOPBACK(&xip->in6p_laddr))) +				continue; +		} else { +			if (opt_v) +				xo_warnx("invalid vflag 0x%x", xip->inp_vflag); +			continue; +		} +		if ((sock = calloc(1, sizeof(*sock))) == NULL) +			xo_err(1, "malloc()"); +		if ((laddr = calloc(1, sizeof *laddr)) == NULL) +			xo_err(1, "malloc()"); +		if ((faddr = calloc(1, sizeof *faddr)) == NULL) +			xo_err(1, "malloc()"); +		sock->socket = so->xso_so; +		sock->pcb = so->so_pcb; +		sock->splice_socket = so->so_splice_so; +		sock->proto = proto; +		sock->inp_gencnt = xip->inp_gencnt; +		sock->fibnum = so->so_fibnum; +		if (xip->inp_vflag & INP_IPV4) { +			sock->family = AF_INET; +			sockaddr(&laddr->address, sock->family, +			    &xip->inp_laddr, xip->inp_lport); +			sockaddr(&faddr->address, sock->family, +			    &xip->inp_faddr, xip->inp_fport); +		} else if (xip->inp_vflag & INP_IPV6) { +			sock->family = AF_INET6; +			sockaddr(&laddr->address, sock->family, +			    &xip->in6p_laddr, xip->inp_lport); +			sockaddr(&faddr->address, sock->family, +			    &xip->in6p_faddr, xip->inp_fport); +		} +		if (proto == IPPROTO_TCP) +			faddr->encaps_port = xtp->xt_encaps_port; +		laddr->next = NULL; +		faddr->next = NULL; +		sock->laddr = laddr; +		sock->faddr = faddr; +		sock->vflag = xip->inp_vflag; +		if (proto == IPPROTO_TCP) { +			sock->state = xtp->t_state; +			sock->bblog_state = xtp->t_logstate; +			memcpy(sock->stack, xtp->xt_stack, +			    TCP_FUNCTION_NAME_LEN_MAX); +			memcpy(sock->cc, xtp->xt_cc, TCP_CA_NAME_MAX); +		} +		sock->protoname = protoname; +		if (sock->socket != 0) +			RB_INSERT(socks_t, &socks, sock); +		else +			SLIST_INSERT_HEAD(&nosocks, sock, socket_list); +	} +out: +	free(buf); +} + +static void +gather_unix(int proto) +{ +	struct xunpgen *xug, *exug; +	struct xunpcb *xup; +	struct sock *sock; +	struct addr *laddr, *faddr; +	const char *varname, *protoname; +	size_t len, bufsize; +	void *buf; +	int retry; + +	switch (proto) { +	case SOCK_STREAM: +		varname = "net.local.stream.pcblist"; +		protoname = "stream"; +		break; +	case SOCK_DGRAM: +		varname = "net.local.dgram.pcblist"; +		protoname = "dgram"; +		break; +	case SOCK_SEQPACKET: +		varname = "net.local.seqpacket.pcblist"; +		protoname = is_xo_style_encoding ? "seqpacket" : "seqpack"; +		break; +	default: +		abort(); +	} +	buf = NULL; +	bufsize = 8192; +	retry = 5; +	do { +		for (;;) { +			if ((buf = realloc(buf, bufsize)) == NULL) +				xo_err(1, "realloc()"); +			len = bufsize; +			if (cap_sysctlbyname(capsysctl, varname, buf, &len, +			    NULL, 0) == 0) +				break; +			if (errno != ENOMEM || len != bufsize) +				xo_err(1, "cap_sysctlbyname()"); +			bufsize *= 2; +		} +		xug = (struct xunpgen *)buf; +		exug = (struct xunpgen *)(void *) +		    ((char *)buf + len - sizeof(*exug)); +		if (!check_ksize(xug->xug_len, struct xunpgen) || +		    !check_ksize(exug->xug_len, struct xunpgen)) +			goto out; +	} while (xug->xug_gen != exug->xug_gen && retry--); + +	if (xug->xug_gen != exug->xug_gen && opt_v) +		xo_warnx("warning: data may be inconsistent"); + +	for (;;) { +		xug = (struct xunpgen *)(void *)((char *)xug + xug->xug_len); +		if (xug >= exug) +			break; +		xup = (struct xunpcb *)xug; +		if (!check_ksize(xup->xu_len, struct xunpcb)) +			goto out; +		if ((xup->unp_conn == 0 && !opt_l) || +		    (xup->unp_conn != 0 && !opt_c)) +			continue; +		if ((sock = calloc(1, sizeof(*sock))) == NULL) +			xo_err(1, "malloc()"); +		if ((laddr = calloc(1, sizeof *laddr)) == NULL) +			xo_err(1, "malloc()"); +		if ((faddr = calloc(1, sizeof *faddr)) == NULL) +			xo_err(1, "malloc()"); +		sock->socket = xup->xu_socket.xso_so; +		sock->pcb = xup->xu_unpp; +		sock->proto = proto; +		sock->family = AF_UNIX; +		sock->protoname = protoname; +		if (xup->xu_addr.sun_family == AF_UNIX) +			laddr->address = +			    *(struct sockaddr_storage *)(void *)&xup->xu_addr; +		faddr->conn = xup->unp_conn; +		faddr->firstref = xup->xu_firstref; +		faddr->nextref = xup->xu_nextref; +		laddr->next = NULL; +		faddr->next = NULL; +		sock->laddr = laddr; +		sock->faddr = faddr; +		RB_INSERT(socks_t, &socks, sock); +		RB_INSERT(pcbs_t, &pcbs, sock); +	} +out: +	free(buf); +} + +static void +getfiles(void) +{ +	struct xfile *xfiles; +	size_t len, olen; + +	olen = len = sizeof(*xfiles); +	if ((xfiles = malloc(len)) == NULL) +		xo_err(1, "malloc()"); +	while (cap_sysctlbyname(capsysctl, "kern.file", xfiles, &len, 0, 0) +	    == -1) { +		if (errno != ENOMEM || len != olen) +			xo_err(1, "cap_sysctlbyname()"); +		olen = len *= 2; +		if ((xfiles = realloc(xfiles, len)) == NULL) +			xo_err(1, "realloc()"); +	} +	if (len > 0) +		enforce_ksize(xfiles->xf_size, struct xfile); +	nfiles = len / sizeof(*xfiles); + +	if ((files = malloc(nfiles * sizeof(struct file))) == NULL) +		xo_err(1, "malloc()"); + +	for (int i = 0; i < nfiles; i++) { +		files[i].xf_data = xfiles[i].xf_data; +		files[i].xf_pid = xfiles[i].xf_pid; +		files[i].xf_uid = xfiles[i].xf_uid; +		files[i].xf_fd = xfiles[i].xf_fd; +		RB_INSERT(files_t, &ftree, &files[i]); +	} + +	free(xfiles); +} + +static int +formataddr(struct sockaddr_storage *ss, char *buf, size_t bufsize) +{ +	struct sockaddr_un *sun; +	char addrstr[NI_MAXHOST] = { '\0', '\0' }; +	int error, off, port = 0; + +	switch (ss->ss_family) { +	case AF_INET: +		if (sstosin(ss)->sin_addr.s_addr == INADDR_ANY) +			addrstr[0] = '*'; +		port = ntohs(sstosin(ss)->sin_port); +		break; +	case AF_INET6: +		if (IN6_IS_ADDR_UNSPECIFIED(&sstosin6(ss)->sin6_addr)) +			addrstr[0] = '*'; +		port = ntohs(sstosin6(ss)->sin6_port); +		break; +	case AF_UNIX: +		sun = sstosun(ss); +		off = (int)((char *)&sun->sun_path - (char *)sun); +		if (is_xo_style_encoding) { +			xo_emit("{:path/%.*s}", sun->sun_len - off, +				sun->sun_path); +			return 0; +		} +		return snprintf(buf, bufsize, "%.*s", +				sun->sun_len - off, sun->sun_path); +	} +	if (addrstr[0] == '\0') { +		error = cap_getnameinfo(capnet, sstosa(ss), ss->ss_len, +		    addrstr, sizeof(addrstr), NULL, 0, NI_NUMERICHOST); +		if (error) +			xo_errx(1, "cap_getnameinfo()"); +	} +	if (is_xo_style_encoding) { +		xo_emit("{:address/%s}", addrstr); +		xo_emit("{:port/%d}", port); +		return 0; +	} +	if (port == 0) +		return snprintf(buf, bufsize, "%s:*", addrstr); +	return snprintf(buf, bufsize, "%s:%d", addrstr, port); +} + +static const char * +getprocname(pid_t pid) +{ +	static struct kinfo_proc proc; +	size_t len; +	int mib[4]; + +	mib[0] = CTL_KERN; +	mib[1] = KERN_PROC; +	mib[2] = KERN_PROC_PID; +	mib[3] = (int)pid; +	len = sizeof(proc); +	if (cap_sysctl(capsysctl, mib, nitems(mib), &proc, &len, NULL, 0) +	    == -1) { +		/* Do not warn if the process exits before we get its name. */ +		if (errno != ESRCH) +			xo_warn("cap_sysctl()"); +		return ("??"); +	} +	return (proc.ki_comm); +} + +static int +getprocjid(pid_t pid) +{ +	static struct kinfo_proc proc; +	size_t len; +	int mib[4]; + +	mib[0] = CTL_KERN; +	mib[1] = KERN_PROC; +	mib[2] = KERN_PROC_PID; +	mib[3] = (int)pid; +	len = sizeof(proc); +	if (cap_sysctl(capsysctl, mib, nitems(mib), &proc, &len, NULL, 0) +	    == -1) { +		/* Do not warn if the process exits before we get its jid. */ +		if (errno != ESRCH) +			xo_warn("cap_sysctl()"); +		return (-1); +	} +	return (proc.ki_jid); +} + +static int +check_ports(struct sock *s) +{ +	int port; +	struct addr *addr; + +	if (ports == NULL) +		return (1); +	if ((s->family != AF_INET) && (s->family != AF_INET6)) +		return (1); +	for (addr = s->laddr; addr != NULL; addr = addr->next) { +		if (s->family == AF_INET) +			port = ntohs(sstosin(&addr->address)->sin_port); +		else +			port = ntohs(sstosin6(&addr->address)->sin6_port); +		if (CHK_PORT(port)) +			return (1); +	} +	for (addr = s->faddr; addr != NULL; addr = addr->next) { +		if (s->family == AF_INET) +			port = ntohs(sstosin(&addr->address)->sin_port); +		else +			port = ntohs(sstosin6(&addr->address)->sin6_port); +		if (CHK_PORT(port)) +			return (1); +	} +	return (0); +} + +static const char * +sctp_conn_state(int state) +{ +	switch (state) { +	case SCTP_CLOSED: +		return "CLOSED"; +		break; +	case SCTP_BOUND: +		return "BOUND"; +		break; +	case SCTP_LISTEN: +		return "LISTEN"; +		break; +	case SCTP_COOKIE_WAIT: +		return "COOKIE_WAIT"; +		break; +	case SCTP_COOKIE_ECHOED: +		return "COOKIE_ECHOED"; +		break; +	case SCTP_ESTABLISHED: +		return "ESTABLISHED"; +		break; +	case SCTP_SHUTDOWN_SENT: +		return "SHUTDOWN_SENT"; +		break; +	case SCTP_SHUTDOWN_RECEIVED: +		return "SHUTDOWN_RECEIVED"; +		break; +	case SCTP_SHUTDOWN_ACK_SENT: +		return "SHUTDOWN_ACK_SENT"; +		break; +	case SCTP_SHUTDOWN_PENDING: +		return "SHUTDOWN_PENDING"; +		break; +	default: +		return "UNKNOWN"; +		break; +	} +} + +static const char * +sctp_path_state(int state) +{ +	switch (state) { +	case SCTP_UNCONFIRMED: +		return "UNCONFIRMED"; +		break; +	case SCTP_ACTIVE: +		return "ACTIVE"; +		break; +	case SCTP_INACTIVE: +		return "INACTIVE"; +		break; +	default: +		return "UNKNOWN"; +		break; +	} +} + +static const char * +bblog_state(int state) +{ +	switch (state) { +	case TCP_LOG_STATE_OFF: +		return "OFF"; +		break; +	case TCP_LOG_STATE_TAIL: +		return "TAIL"; +		break; +	case TCP_LOG_STATE_HEAD: +		return "HEAD"; +		break; +	case TCP_LOG_STATE_HEAD_AUTO: +		return "HEAD_AUTO"; +		break; +	case TCP_LOG_STATE_CONTINUAL: +		return "CONTINUAL"; +		break; +	case TCP_LOG_STATE_TAIL_AUTO: +		return "TAIL_AUTO"; +		break; +	case TCP_LOG_VIA_BBPOINTS: +		return "BBPOINTS"; +		break; +	default: +		return "UNKNOWN"; +		break; +	} +} + +static int +format_unix_faddr(struct addr *faddr, char *buf, size_t bufsize) { +	#define SAFEBUF  (buf == NULL ? NULL : buf + pos) +	#define SAFESIZE (buf == NULL ? 0 : bufsize - pos) + +	size_t pos = 0; +	if (faddr->conn != 0) { +		/* Remote peer we connect(2) to, if any. */ +		struct sock *p; +		if (!is_xo_style_encoding) +			pos += strlcpy(SAFEBUF, "-> ", SAFESIZE); +		p = RB_FIND(pcbs_t, &pcbs, +			&(struct sock){ .pcb = faddr->conn }); +		if (__predict_false(p == NULL) && !is_xo_style_encoding) { +			/* XXGL: can this happen at all? */ +			pos += snprintf(SAFEBUF, SAFESIZE, "??"); +		} else if (p->laddr->address.ss_len == 0) { +			struct file *f; +			f = RB_FIND(files_t, &ftree, +				&(struct file){ .xf_data = +				p->socket }); +			if (f != NULL) { +				if (!is_xo_style_encoding) { +					pos += snprintf(SAFEBUF, SAFESIZE, +						"[%lu %d]", (u_long)f->xf_pid, +						f->xf_fd); +				} else { +					xo_open_list("connections"); +					xo_open_instance("connections"); +					xo_emit("{:pid/%lu}", (u_long)f->xf_pid); +					xo_emit("{:fd/%d}", f->xf_fd); +					xo_close_instance("connections"); +					xo_close_list("connections"); +				} +			} +		} else +			pos += formataddr(&p->laddr->address, +				SAFEBUF, SAFESIZE); +	} else if (faddr->firstref != 0) { +		/* Remote peer(s) connect(2)ed to us, if any. */ +		struct sock *p; +		struct file *f; +		kvaddr_t ref = faddr->firstref; +		bool fref = true; + +		if (!is_xo_style_encoding) +			pos += snprintf(SAFEBUF, SAFESIZE, " <- "); +		xo_open_list("connections"); +		while ((p = RB_FIND(pcbs_t, &pcbs, +			&(struct sock){ .pcb = ref })) != 0) { +			f = RB_FIND(files_t, &ftree, +				&(struct file){ .xf_data = p->socket }); +			if (f != NULL) { +				if (!is_xo_style_encoding) { +					pos += snprintf(SAFEBUF, SAFESIZE, +						"%s[%lu %d]", fref ? "" : ",", +						(u_long)f->xf_pid, f->xf_fd); +				} else { +					xo_open_instance("connections"); +					xo_emit("{:pid/%lu}", (u_long)f->xf_pid); +					xo_emit("{:fd/%d}", f->xf_fd); +					xo_close_instance("connections"); +				} +			} +			ref = p->faddr->nextref; +			fref = false; +		} +		xo_close_list("connections"); +	} +	return pos; +} + +struct col_widths { +	int user; +	int command; +	int pid; +	int fd; +	int proto; +	int local_addr; +	int foreign_addr; +	int pcb_kva; +	int fib; +	int splice_address; +	int inp_gencnt; +	int encaps; +	int path_state; +	int conn_state; +	int bblog_state; +	int stack; +	int cc; +}; + +static void +calculate_sock_column_widths(struct col_widths *cw, struct sock *s) +{ +	struct addr *laddr, *faddr; +	bool first = true; +	int len = 0; +	laddr = s->laddr; +	faddr = s->faddr; +	first = true; + +	len = strlen(s->protoname); +	if (s->vflag & INP_IPV4) +		len += 1; +	if (s->vflag & INP_IPV6) +		len += 1; +	cw->proto = MAX(cw->proto, len); + +	while (laddr != NULL || faddr != NULL) { +		if (opt_w && s->family == AF_UNIX) { +			if ((laddr == NULL) || (faddr == NULL)) +				xo_errx(1, "laddr = %p or faddr = %p is NULL", +					(void *)laddr, (void *)faddr); +			if (laddr->address.ss_len > 0) +				len = formataddr(&laddr->address, NULL, 0); +			cw->local_addr = MAX(cw->local_addr, len); +			len = format_unix_faddr(faddr, NULL, 0); +			cw->foreign_addr = MAX(cw->foreign_addr, len); +		} else if (opt_w) { +			if (laddr != NULL) { +				len = formataddr(&laddr->address, NULL, 0); +				cw->local_addr = MAX(cw->local_addr, len); +			} +			if (faddr != NULL) { +				len = formataddr(&faddr->address, NULL, 0); +				cw->foreign_addr = MAX(cw->foreign_addr, len); +			} +		} +		if (opt_f) { +			len = snprintf(NULL, 0, "%d", s->fibnum); +			cw->fib = MAX(cw->fib, len); +		} +		if (opt_I) { +			if (s->splice_socket != 0) { +				struct sock *sp; + +				sp = RB_FIND(socks_t, &socks, &(struct sock) +					{ .socket = s->splice_socket }); +				if (sp != NULL) { +					len = formataddr(&sp->laddr->address, +					    NULL, 0); +					cw->splice_address = MAX( +					    cw->splice_address, len); +				} +			} +		} +		if (opt_i) { +			if (s->proto == IPPROTO_TCP || +			    s->proto == IPPROTO_UDP) { +				len = snprintf(NULL, 0, +				    "%" PRIu64, s->inp_gencnt); +				cw->inp_gencnt = MAX(cw->inp_gencnt, len); +			} +		} +		if (opt_U) { +			if (faddr != NULL && +			    ((s->proto == IPPROTO_SCTP && +			      s->state != SCTP_CLOSED && +			      s->state != SCTP_BOUND && +			      s->state != SCTP_LISTEN) || +			    (s->proto == IPPROTO_TCP && +			     s->state != TCPS_CLOSED && +			     s->state != TCPS_LISTEN))) { +				len = snprintf(NULL, 0, "%u", +				    ntohs(faddr->encaps_port)); +				cw->encaps = MAX(cw->encaps, len); +			} +		} +		if (opt_s) { +			if (faddr != NULL && +			    s->proto == IPPROTO_SCTP && +			    s->state != SCTP_CLOSED && +			    s->state != SCTP_BOUND && +			    s->state != SCTP_LISTEN) { +				len = strlen(sctp_path_state(faddr->state)); +				cw->path_state = MAX(cw->path_state, len); +			} +		} +		if (first) { +			if (opt_s) { +				if (s->proto == IPPROTO_SCTP || +				    s->proto == IPPROTO_TCP) { +					switch (s->proto) { +					case IPPROTO_SCTP: +						len = strlen( +						    sctp_conn_state(s->state)); +						cw->conn_state = MAX( +						    cw->conn_state, len); +						break; +					case IPPROTO_TCP: +						if (s->state >= 0 && +						    s->state < TCP_NSTATES) { +							len = strlen( +							    tcpstates[s->state]); +							cw->conn_state = MAX( +							    cw->conn_state, +							    len); +						} +						break; +					} +				} +			} +			if (opt_S && s->proto == IPPROTO_TCP) { +				len = strlen(s->stack); +				cw->stack = MAX(cw->stack, len); +			} +			if (opt_C && s->proto == IPPROTO_TCP) { +				len = strlen(s->cc); +				cw->cc = MAX(cw->cc, len); +			} +		} +		if (laddr != NULL) +			laddr = laddr->next; +		if (faddr != NULL) +			faddr = faddr->next; +		first = false; +	} +} + +static void +calculate_column_widths(struct col_widths *cw) +{ +	int n, len; +	struct file *xf; +	struct sock *s; +	struct passwd *pwd; + +	cap_setpassent(cappwd, 1); +	for (xf = files, n = 0; n < nfiles; ++n, ++xf) { +		if (xf->xf_data == 0) +			continue; +		if (opt_j >= 0 && opt_j != getprocjid(xf->xf_pid)) +			continue; +		s = RB_FIND(socks_t, &socks, +			&(struct sock){ .socket = xf->xf_data}); +		if (s == NULL || (!check_ports(s))) +			continue; +		s->shown = 1; +		if (opt_n || +			(pwd = cap_getpwuid(cappwd, xf->xf_uid)) == NULL) +			len = snprintf(NULL, 0, "%lu", (u_long)xf->xf_uid); +		else +			len = snprintf(NULL, 0, "%s", pwd->pw_name); +		cw->user = MAX(cw->user, len); +		len = snprintf(NULL, 0, "%lu", (u_long)xf->xf_pid); +		cw->pid = MAX(cw->pid, len); +		len = snprintf(NULL, 0, "%d", xf->xf_fd); +		cw->fd = MAX(cw->fd, len); + +		calculate_sock_column_widths(cw, s); +	} +	if (opt_j >= 0) +		return; +	SLIST_FOREACH(s, &nosocks, socket_list) { +		if (!check_ports(s)) +			continue; +		calculate_sock_column_widths(cw, s); +	} +	RB_FOREACH(s, socks_t, &socks) { +		if (s->shown) +			continue; +		if (!check_ports(s)) +			continue; +		calculate_sock_column_widths(cw, s); +	} +} + +static void +display_sock(struct sock *s, struct col_widths *cw, char *buf, size_t bufsize) +{ +	struct addr *laddr, *faddr; +	bool first; +	laddr = s->laddr; +	faddr = s->faddr; +	first = true; + +	snprintf(buf, bufsize, "%s%s%s", +		s->protoname, +		s->vflag & INP_IPV4 ? "4" : "", +		s->vflag & INP_IPV6 ? "6" : ""); +	xo_emit(" {:proto/%-*s}", cw->proto, buf); +	while (laddr != NULL || faddr != NULL) { +		if (s->family == AF_UNIX) { +			if ((laddr == NULL) || (faddr == NULL)) +				xo_errx(1, "laddr = %p or faddr = %p is NULL", +					(void *)laddr, (void *)faddr); +			if (laddr->address.ss_len > 0) { +				xo_open_container("local"); +				formataddr(&laddr->address, buf, bufsize); +				if (!is_xo_style_encoding) { +					xo_emit(" {:local-address/%-*.*s}", +						cw->local_addr, cw->local_addr, +						buf); +				} +				xo_close_container("local"); +			} else if (laddr->address.ss_len == 0 && +				faddr->conn == 0 && !is_xo_style_encoding) { +				xo_emit(" {:local-address/%-*.*s}", +					cw->local_addr,	cw->local_addr, +					"(not connected)"); +			} else if (!is_xo_style_encoding) { +				xo_emit(" {:local-address/%-*.*s}", +					cw->local_addr, cw->local_addr, "??"); +			} +			if (faddr->conn != 0 || faddr->firstref != 0) { +				xo_open_container("foreign"); +				int len = format_unix_faddr(faddr, buf, +						bufsize); +				if (len == 0 && !is_xo_style_encoding) +					xo_emit(" {:foreign-address/%-*s}", +						cw->foreign_addr, "??"); +				else if (!is_xo_style_encoding) +					xo_emit(" {:foreign-address/%-*.*s}", +						cw->foreign_addr, +						cw->foreign_addr, buf); +				xo_close_container("foreign"); +			} else if (!is_xo_style_encoding) +				xo_emit(" {:foreign-address/%-*s}", +					cw->foreign_addr, "??"); +		} else { +			if (laddr != NULL) { +				xo_open_container("local"); +				formataddr(&laddr->address, buf, bufsize); +				if (!is_xo_style_encoding) { +					xo_emit(" {:local-address/%-*.*s}", +						cw->local_addr, cw->local_addr, +						buf); +				} +				xo_close_container("local"); +			} else if (!is_xo_style_encoding) +				xo_emit(" {:local-address/%-*.*s}", +					cw->local_addr, cw->local_addr, "??"); +			if (faddr != NULL) { +				xo_open_container("foreign"); +				formataddr(&faddr->address, buf, bufsize); +				if (!is_xo_style_encoding) { +					xo_emit(" {:foreign-address/%-*.*s}", +						cw->foreign_addr, +						cw->foreign_addr, buf); +				} +				xo_close_container("foreign"); +			} else if (!is_xo_style_encoding) { +				xo_emit(" {:foreign-address/%-*.*s}", +					cw->foreign_addr, cw->foreign_addr, +					"??"); +			} +		} +		if (opt_A) { +			snprintf(buf, bufsize, "%#*" PRIx64, +				cw->pcb_kva, s->pcb); +			xo_emit(" {:pcb-kva/%s}", buf); +		} +		if (opt_f) +			xo_emit(" {:fib/%*d}", cw->fib, s->fibnum); +		if (opt_I) { +			if (s->splice_socket != 0) { +				struct sock *sp; +				sp = RB_FIND(socks_t, &socks, &(struct sock) +					{ .socket = s->splice_socket }); +				if (sp != NULL) { +					xo_open_container("splice"); +					formataddr(&sp->laddr->address, +								buf, bufsize); +					xo_close_container("splice"); +				} else if (!is_xo_style_encoding) +					strlcpy(buf, "??", bufsize); +			} else if (!is_xo_style_encoding) +				strlcpy(buf, "??", bufsize); +			if (!is_xo_style_encoding) +				xo_emit(" {:splice-address/%-*s}", +					cw->splice_address, buf); +		} +		if (opt_i) { +			if (s->proto == IPPROTO_TCP || +			    s->proto == IPPROTO_UDP) { +				snprintf(buf, bufsize, "%" PRIu64, +					s->inp_gencnt); +				xo_emit(" {:id/%*s}", cw->inp_gencnt, buf); +			} else if (!is_xo_style_encoding) +				xo_emit(" {:id/%*s}", cw->inp_gencnt, "??"); +		} +		if (opt_U) { +			if (faddr != NULL && +			    ((s->proto == IPPROTO_SCTP && +			      s->state != SCTP_CLOSED && +			      s->state != SCTP_BOUND && +			      s->state != SCTP_LISTEN) || +			     (s->proto == IPPROTO_TCP && +			      s->state != TCPS_CLOSED && +			      s->state != TCPS_LISTEN))) { +				xo_emit(" {:encaps/%*u}", cw->encaps, +				    ntohs(faddr->encaps_port)); +			} else if (!is_xo_style_encoding) +				xo_emit(" {:encaps/%*s}", cw->encaps, "??"); +		} +		if (opt_s && show_path_state) { +			if (faddr != NULL && +			    s->proto == IPPROTO_SCTP && +			    s->state != SCTP_CLOSED && +			    s->state != SCTP_BOUND && +			    s->state != SCTP_LISTEN) { +				xo_emit(" {:path-state/%-*s}", cw->path_state, +				    sctp_path_state(faddr->state)); +			} else if (!is_xo_style_encoding) +				xo_emit(" {:path-state/%-*s}", cw->path_state, +				    "??"); +		} +		if (first) { +			if (opt_s) { +				if (s->proto == IPPROTO_SCTP || +				    s->proto == IPPROTO_TCP) { +					switch (s->proto) { +					case IPPROTO_SCTP: +						xo_emit(" {:conn-state/%-*s}", +						    cw->conn_state, +						    sctp_conn_state(s->state)); +						break; +					case IPPROTO_TCP: +						if (s->state >= 0 && +						    s->state < TCP_NSTATES) +							xo_emit(" {:conn-state/%-*s}", +							    cw->conn_state, +							    tcpstates[s->state]); +						else if (!is_xo_style_encoding) +							xo_emit(" {:conn-state/%-*s}", +							    cw->conn_state, "??"); +						break; +					} +				} else if (!is_xo_style_encoding) +					xo_emit(" {:conn-state/%-*s}", +					    cw->conn_state, "??"); +			} +			if (opt_b) { +				if (s->proto == IPPROTO_TCP) +					xo_emit(" {:bblog-state/%-*s}", +					    cw->bblog_state, +					    bblog_state(s->bblog_state)); +				else if (!is_xo_style_encoding) +					xo_emit(" {:bblog-state/%-*s}", +					    cw->bblog_state, "??"); +			} +			if (opt_S) { +				if (s->proto == IPPROTO_TCP) +					xo_emit(" {:stack/%-*s}", +					    cw->stack, s->stack); +				else if (!is_xo_style_encoding) +					xo_emit(" {:stack/%-*s}", +					    cw->stack, "??"); +			} +			if (opt_C) { +				if (s->proto == IPPROTO_TCP) +					xo_emit(" {:cc/%-*s}", cw->cc, s->cc); +				else if (!is_xo_style_encoding) +					xo_emit(" {:cc/%-*s}", cw->cc, "??"); +			} +		} else if (!is_xo_style_encoding) { +			if (opt_s) +				xo_emit(" {:conn-state/%-*s}", cw->conn_state, +				    "??"); +			if (opt_b) +				xo_emit(" {:bblog-state/%-*s}", cw->bblog_state, +				    "??"); +			if (opt_S) +				xo_emit(" {:stack/%-*s}", cw->stack, "??"); +			if (opt_C) +				xo_emit(" {:cc/%-*s}", cw->cc, "??"); +		} +		if (laddr != NULL) +			laddr = laddr->next; +		if (faddr != NULL) +			faddr = faddr->next; +		xo_emit("\n"); +		if (!is_xo_style_encoding && (laddr != NULL || faddr != NULL)) +			xo_emit("{:user/%-*s} {:command/%-*s} {:pid/%*s}" +			    " {:fd/%*s} {:proto/%-*s}", cw->user, "??", +			    cw->command, "??", cw->pid, "??", cw->fd, "??", +			    cw->proto, "??"); +		first = false; +	} +} + +static void +display(void) +{ +	struct passwd *pwd; +	struct file *xf; +	struct sock *s; +	int n; +	struct col_widths cw; +	const size_t bufsize = 512; +	void *buf; +	if ((buf = (char *)malloc(bufsize)) == NULL) { +		xo_err(1, "malloc()"); +		return; +	} + +	if (!is_xo_style_encoding) { +		cw = (struct col_widths) { +			.user = strlen("USER"), +			.command = 10, +			.pid = strlen("PID"), +			.fd = strlen("FD"), +			.proto = strlen("PROTO"), +			.local_addr = opt_w ? strlen("LOCAL ADDRESS") : 21, +			.foreign_addr = opt_w ? strlen("FOREIGN ADDRESS") : 21, +			.pcb_kva = 18, +			.fib = strlen("FIB"), +			.splice_address = strlen("SPLICE ADDRESS"), +			.inp_gencnt = strlen("ID"), +			.encaps = strlen("ENCAPS"), +			.path_state = strlen("PATH STATE"), +			.conn_state = strlen("CONN STATE"), +			.bblog_state = strlen("BBLOG STATE"), +			.stack = strlen("STACK"), +			.cc = strlen("CC"), +		}; +		calculate_column_widths(&cw); +	} else +		memset(&cw, 0, sizeof(cw)); + +	xo_set_version(SOCKSTAT_XO_VERSION); +	xo_open_container("sockstat"); +	xo_open_list("socket"); +	if (!opt_q) { +		xo_emit("{T:/%-*s} {T:/%-*s} {T:/%*s} {T:/%*s} {T:/%-*s} " +			"{T:/%-*s} {T:/%-*s}", cw.user, "USER", cw.command, +			"COMMAND", cw.pid, "PID", cw.fd, "FD", cw.proto, +			"PROTO", cw.local_addr, "LOCAL ADDRESS", +			cw.foreign_addr, "FOREIGN ADDRESS"); +		if (opt_A) +			xo_emit(" {T:/%-*s}", cw.pcb_kva, "PCB KVA"); +		if (opt_f) +			/* RT_MAXFIBS is 65535. */ +			xo_emit(" {T:/%*s}", cw.fib, "FIB"); +		if (opt_I) +			xo_emit(" {T:/%-*s}", cw.splice_address, +			    "SPLICE ADDRESS"); +		if (opt_i) +			xo_emit(" {T:/%*s}", cw.inp_gencnt, "ID"); +		if (opt_U) +			xo_emit(" {T:/%*s}", cw.encaps, "ENCAPS"); +		if (opt_s) { +			if (show_path_state) +				xo_emit(" {T:/%-*s}", cw.path_state, +				    "PATH STATE"); +			xo_emit(" {T:/%-*s}", cw.conn_state, "CONN STATE"); +		} +		if (opt_b) +			xo_emit(" {T:/%-*s}", cw.bblog_state, "BBLOG STATE"); +		if (opt_S) +			xo_emit(" {T:/%-*s}", cw.stack, "STACK"); +		if (opt_C) +			xo_emit(" {T:/%-*s}", cw.cc, "CC"); +		xo_emit("\n"); +	} +	cap_setpassent(cappwd, 1); +	for (xf = files, n = 0; n < nfiles; ++n, ++xf) { +		if (xf->xf_data == 0) +			continue; +		if (opt_j >= 0 && opt_j != getprocjid(xf->xf_pid)) +			continue; +		s = RB_FIND(socks_t, &socks, +			&(struct sock){ .socket = xf->xf_data}); +		if (s != NULL && check_ports(s)) { +			xo_open_instance("socket"); +			s->shown = 1; +			if (opt_n || +			    (pwd = cap_getpwuid(cappwd, xf->xf_uid)) == NULL) +				xo_emit("{:user/%-*lu}", cw.user, +				    (u_long)xf->xf_uid); +			else +				xo_emit("{:user/%-*s}", cw.user, pwd->pw_name); +			if (!is_xo_style_encoding) +				xo_emit(" {:command/%-*.10s}", cw.command, +				    getprocname(xf->xf_pid)); +			else +				xo_emit(" {:command/%-*s}", cw.command, +				    getprocname(xf->xf_pid)); +			xo_emit(" {:pid/%*lu}", cw.pid, (u_long)xf->xf_pid); +			xo_emit(" {:fd/%*d}", cw.fd, xf->xf_fd); +			display_sock(s, &cw, buf, bufsize); +			xo_close_instance("socket"); +		} +	} +	if (opt_j >= 0) +		goto out; +	SLIST_FOREACH(s, &nosocks, socket_list) { +		if (!check_ports(s)) +			continue; +		xo_open_instance("socket"); +		if (!is_xo_style_encoding) +			xo_emit("{:user/%-*s} {:command/%-*s} {:pid/%*s}" +			    " {:fd/%*s}", cw.user, "??", cw.command, "??", +			    cw.pid, "??", cw.fd, "??"); +		display_sock(s, &cw, buf, bufsize); +		xo_close_instance("socket"); +	} +	RB_FOREACH(s, socks_t, &socks) { +		if (s->shown) +			continue; +		if (!check_ports(s)) +			continue; +		xo_open_instance("socket"); +		if (!is_xo_style_encoding) +			xo_emit("{:user/%-*s} {:command/%-*s} {:pid/%*s}" +			    " {:fd/%*s}", cw.user, "??", cw.command, "??", +			    cw.pid, "??", cw.fd, "??"); +		display_sock(s, &cw, buf, bufsize); +		xo_close_instance("socket"); +	} +out: +	xo_close_list("socket"); +	xo_close_container("sockstat"); +	if (xo_finish() < 0) +		xo_err(1, "stdout"); +	free(buf); +	cap_endpwent(cappwd); +} + +static int +set_default_protos(void) +{ +	struct protoent *prot; +	const char *pname; +	size_t pindex; + +	init_protos(default_numprotos); + +	for (pindex = 0; pindex < default_numprotos; pindex++) { +		pname = default_protos[pindex]; +		prot = cap_getprotobyname(capnetdb, pname); +		if (prot == NULL) +			xo_err(1, "cap_getprotobyname: %s", pname); +		protos[pindex] = prot->p_proto; +	} +	numprotos = pindex; +	return (pindex); +} + +/* + * Return the vnet property of the jail, or -1 on error. + */ +static int +jail_getvnet(int jid) +{ +	struct iovec jiov[6]; +	int vnet; +	size_t len = sizeof(vnet); + +	if (sysctlbyname("kern.features.vimage", &vnet, &len, NULL, 0) != 0) +		return (0); + +	vnet = -1; +	jiov[0].iov_base = __DECONST(char *, "jid"); +	jiov[0].iov_len = sizeof("jid"); +	jiov[1].iov_base = &jid; +	jiov[1].iov_len = sizeof(jid); +	jiov[2].iov_base = __DECONST(char *, "vnet"); +	jiov[2].iov_len = sizeof("vnet"); +	jiov[3].iov_base = &vnet; +	jiov[3].iov_len = sizeof(vnet); +	jiov[4].iov_base = __DECONST(char *, "errmsg"); +	jiov[4].iov_len = sizeof("errmsg"); +	jiov[5].iov_base = jail_errmsg; +	jiov[5].iov_len = JAIL_ERRMSGLEN; +	jail_errmsg[0] = '\0'; +	if (jail_get(jiov, nitems(jiov), 0) < 0) { +		if (!jail_errmsg[0]) +			snprintf(jail_errmsg, JAIL_ERRMSGLEN, +			    "jail_get: %s", strerror(errno)); +		return (-1); +	} +	return (vnet); +} + +static void +usage(void) +{ +	xo_error( +"usage: sockstat [--libxo ...] [-46AbCcfIiLlnqSsUuvw] [-j jid] [-p ports]\n" +"                [-P protocols]\n"); +	exit(1); +} + +int +main(int argc, char *argv[]) +{ +	cap_channel_t *capcas; +	cap_net_limit_t *limit; +	const char *pwdcmds[] = { "setpassent", "getpwuid" }; +	const char *pwdfields[] = { "pw_name" }; +	int protos_defined = -1; +	int o, i, err; + +	argc = xo_parse_args(argc, argv); +	if (argc < 0) +		exit(1); +	if (xo_get_style(NULL) != XO_STYLE_TEXT) { +		show_path_state = true; +		if (xo_get_style(NULL) != XO_STYLE_HTML) +			is_xo_style_encoding = true; +	} +	opt_j = -1; +	while ((o = getopt(argc, argv, "46AbCcfIij:Llnp:P:qSsUuvw")) != -1) +		switch (o) { +		case '4': +			opt_4 = true; +			break; +		case '6': +			opt_6 = true; +			break; +		case 'A': +			opt_A = true; +			break; +		case 'b': +			opt_b = true; +			break; +		case 'C': +			opt_C = true; +			break; +		case 'c': +			opt_c = true; +			break; +		case 'f': +			opt_f = true; +			break; +		case 'I': +			opt_I = true; +			break; +		case 'i': +			opt_i = true; +			break; +		case 'j': +			opt_j = jail_getid(optarg); +			if (opt_j < 0) +				xo_errx(1, "jail_getid: %s", jail_errmsg); +			break; +		case 'L': +			opt_L = true; +			break; +		case 'l': +			opt_l = true; +			break; +		case 'n': +			opt_n = true; +			break; +		case 'p': +			err = parse_ports(optarg); +			switch (err) { +			case EINVAL: +				xo_errx(1, "syntax error in port range"); +				break; +			case ERANGE: +				xo_errx(1, "invalid port number"); +				break; +			} +			break; +		case 'P': +			protos_defined = parse_protos(optarg); +			break; +		case 'q': +			opt_q = true; +			break; +		case 'S': +			opt_S = true; +			break; +		case 's': +			opt_s = true; +			break; +		case 'U': +			opt_U = true; +			break; +		case 'u': +			opt_u = true; +			break; +		case 'v': +			++opt_v; +			break; +		case 'w': +			opt_w = true; +			break; +		default: +			usage(); +		} + +	argc -= optind; +	argv += optind; + +	if (argc > 0) +		usage(); + +	if (opt_j > 0) { +		switch (jail_getvnet(opt_j)) { +		case -1: +			xo_errx(2, "jail_getvnet: %s", jail_errmsg); +		case JAIL_SYS_NEW: +			if (jail_attach(opt_j) < 0) +				xo_err(3, "jail_attach()"); +			/* Set back to -1 for normal output in vnet jail. */ +			opt_j = -1; +			break; +		default: +			break; +		} +	} + +	capcas = cap_init(); +	if (capcas == NULL) +		xo_err(1, "Unable to contact Casper"); +	if (caph_enter_casper() < 0) +		xo_err(1, "Unable to enter capability mode"); +	capnet = cap_service_open(capcas, "system.net"); +	if (capnet == NULL) +		xo_err(1, "Unable to open system.net service"); +	capnetdb = cap_service_open(capcas, "system.netdb"); +	if (capnetdb == NULL) +		xo_err(1, "Unable to open system.netdb service"); +	capsysctl = cap_service_open(capcas, "system.sysctl"); +	if (capsysctl == NULL) +		xo_err(1, "Unable to open system.sysctl service"); +	cappwd = cap_service_open(capcas, "system.pwd"); +	if (cappwd == NULL) +		xo_err(1, "Unable to open system.pwd service"); +	cap_close(capcas); +	limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME); +	if (limit == NULL) +		xo_err(1, "Unable to init cap_net limits"); +	if (cap_net_limit(limit) < 0) +		xo_err(1, "Unable to apply limits"); +	if (cap_pwd_limit_cmds(cappwd, pwdcmds, nitems(pwdcmds)) < 0) +		xo_err(1, "Unable to apply pwd commands limits"); +	if (cap_pwd_limit_fields(cappwd, pwdfields, nitems(pwdfields)) < 0) +		xo_err(1, "Unable to apply pwd commands limits"); + +	if ((!opt_4 && !opt_6) && protos_defined != -1) +		opt_4 = opt_6 = true; +	if (!opt_4 && !opt_6 && !opt_u) +		opt_4 = opt_6 = opt_u = true; +	if ((opt_4 || opt_6) && protos_defined == -1) +		protos_defined = set_default_protos(); +	if (!opt_c && !opt_l) +		opt_c = opt_l = true; + +	if (opt_4 || opt_6) { +		for (i = 0; i < protos_defined; i++) +			if (protos[i] == IPPROTO_SCTP) +				gather_sctp(); +			else +				gather_inet(protos[i]); +	} + +	if (opt_u || (protos_defined == -1 && !opt_4 && !opt_6)) { +		gather_unix(SOCK_STREAM); +		gather_unix(SOCK_DGRAM); +		gather_unix(SOCK_SEQPACKET); +	} +	getfiles(); +	display(); +	exit(0); +} diff --git a/usr.bin/sockstat/sockstat.1 b/usr.bin/sockstat/sockstat.1 index da658e33e542..1498fb1d88f7 100644 --- a/usr.bin/sockstat/sockstat.1 +++ b/usr.bin/sockstat/sockstat.1 @@ -25,7 +25,7 @@  .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  .\" -.Dd June 27, 2025 +.Dd October 14, 2025  .Dt SOCKSTAT 1  .Os  .Sh NAME @@ -33,7 +33,8 @@  .Nd list open sockets  .Sh SYNOPSIS  .Nm -.Op Fl 46ACcfIiLlnqSsUuv +.Op Fl -libxo +.Op Fl 46AbCcfIiLlnqSsUuvw  .Op Fl j Ar jail  .Op Fl p Ar ports  .Op Fl P Ar protocols @@ -46,6 +47,13 @@ domain sockets.  .Pp  The following options are available:  .Bl -tag -width Fl +.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.  .It Fl 4  Show  .Dv AF_INET @@ -57,6 +65,9 @@ Show  .It Fl A  Show the address of a protocol control block (PCB) associated with a socket;  used for debugging. +.It Fl b +Show the BBLog state of the socket. +This is currently only implemented for TCP.  .It Fl C  Display the congestion control module, if applicable.  This is currently only implemented for TCP. @@ -119,6 +130,8 @@ Show  sockets.  .It Fl v  Verbose mode. +.It Fl w +Automatically size the columns.  .El  .Pp  If neither @@ -192,10 +205,16 @@ is specified (only for SCTP or TCP).  The path state if  .Fl s  is specified (only for SCTP). +When using traditional text output, this column is only shown when there is at +least one path state to show.  .It Li CONN STATE  The connection state if  .Fl s  is specified (only for SCTP or TCP). +.It Li BBLOG STATE +The BBLog state if +.Fl b +is specified (only for TCP).  .It Li STACK  The protocol stack if  .Fl S @@ -227,6 +246,11 @@ Show TCP IPv6 sockets which are listening and connected (default):  .Bd -literal -offset indent  $ sockstat -6 -P tcp  .Ed +.Pp +Show all sockets in JSON format with neat alignment: +.Bd -literal -offset indent +$ sockstat --libxo json,pretty +.Ed  .Sh SEE ALSO  .Xr fstat 1 ,  .Xr netstat 1 , @@ -235,6 +259,8 @@ $ sockstat -6 -P tcp  .Xr inet 4 ,  .Xr inet6 4 ,  .Xr protocols 5 +.Xr libxo 3 , +.Xr xo_options 7  .Sh HISTORY  The  .Nm diff --git a/usr.bin/sockstat/sockstat.c b/usr.bin/sockstat/sockstat.c index 1a24ff67c321..7bb7f6a66e3f 100644 --- a/usr.bin/sockstat/sockstat.c +++ b/usr.bin/sockstat/sockstat.c @@ -1,7 +1,7 @@  /*-   * SPDX-License-Identifier: BSD-2-Clause   * - * Copyright (c) 2002 Dag-Erling Smørgrav + * Copyright (c) 2025 ConnectWise   * All rights reserved.   *   * Redistribution and use in source and binary forms, with or without @@ -13,8 +13,6 @@   * 2. Redistributions in binary form must reproduce the above copyright   *    notice, this list of conditions and the following disclaimer in the   *    documentation and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - *    derived from this software without specific prior written permission.   *   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES @@ -28,1777 +26,52 @@   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.   */ -#include <sys/param.h> -#include <sys/file.h> -#include <sys/socket.h> -#include <sys/socketvar.h> -#include <sys/sysctl.h> -#include <sys/jail.h> -#include <sys/user.h> -#include <sys/queue.h> -#include <sys/tree.h> - -#include <sys/un.h> -#include <sys/unpcb.h> - -#include <net/route.h> - -#include <netinet/in.h> -#include <netinet/in_pcb.h> -#include <netinet/sctp.h> -#include <netinet/tcp.h> -#define TCPSTATES /* load state names */ -#include <netinet/tcp_fsm.h> -#include <netinet/tcp_seq.h> -#include <netinet/tcp_var.h> -#include <arpa/inet.h> - -#include <capsicum_helpers.h>  #include <ctype.h> -#include <err.h> -#include <errno.h> -#include <inttypes.h> -#include <jail.h> -#include <netdb.h> -#include <pwd.h> -#include <stdarg.h> -#include <stdbool.h> -#include <stdio.h>  #include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include <libcasper.h> -#include <casper/cap_net.h> -#include <casper/cap_netdb.h> -#include <casper/cap_pwd.h> -#include <casper/cap_sysctl.h> - -#define	sstosin(ss)	((struct sockaddr_in *)(ss)) -#define	sstosin6(ss)	((struct sockaddr_in6 *)(ss)) -#define	sstosun(ss)	((struct sockaddr_un *)(ss)) -#define	sstosa(ss)	((struct sockaddr *)(ss)) - -static bool	 opt_4;		/* Show IPv4 sockets */ -static bool	 opt_6;		/* Show IPv6 sockets */ -static bool	 opt_A;		/* Show kernel address of pcb */ -static bool	 opt_C;		/* Show congestion control */ -static bool	 opt_c;		/* Show connected sockets */ -static bool	 opt_f;		/* Show FIB numbers */ -static bool	 opt_I;		/* Show spliced socket addresses */ -static bool	 opt_i;		/* Show inp_gencnt */ -static int	 opt_j;		/* Show specified jail */ -static bool	 opt_L;		/* Don't show IPv4 or IPv6 loopback sockets */ -static bool	 opt_l;		/* Show listening sockets */ -static bool	 opt_n;		/* Don't resolve UIDs to user names */ -static bool	 opt_q;		/* Don't show header */ -static bool	 opt_S;		/* Show protocol stack if applicable */ -static bool	 opt_s;		/* Show protocol state if applicable */ -static bool	 opt_U;		/* Show remote UDP encapsulation port number */ -static bool	 opt_u;		/* Show Unix domain sockets */ -static u_int	 opt_v;		/* Verbose mode */ - -/* - * Default protocols to use if no -P was defined. - */ -static const char *default_protos[] = {"sctp", "tcp", "udp", "divert" }; -static size_t	   default_numprotos = nitems(default_protos); +#include <libxo/xo.h> -static int	*protos;	/* protocols to use */ -static size_t	 numprotos;	/* allocated size of protos[] */ +#include "sockstat.h" -static int	*ports; - -#define	INT_BIT (sizeof(int)*CHAR_BIT) -#define	SET_PORT(p) do { ports[p / INT_BIT] |= 1 << (p % INT_BIT); } while (0) -#define	CHK_PORT(p) (ports[p / INT_BIT] & (1 << (p % INT_BIT))) - -struct addr { -	union { -		struct sockaddr_storage address; -		struct {	/* unix(4) faddr */ -			kvaddr_t conn; -			kvaddr_t firstref; -			kvaddr_t nextref; -		}; -	}; -	unsigned int encaps_port; -	int state; -	struct addr *next; -}; - -struct sock { -	union { -		RB_ENTRY(sock) socket_tree;	/* tree of pcbs with socket */ -		SLIST_ENTRY(sock) socket_list;	/* list of pcbs w/o socket */ -	}; -	RB_ENTRY(sock) pcb_tree; -	kvaddr_t socket; -	kvaddr_t pcb; -	kvaddr_t splice_socket; -	uint64_t inp_gencnt; -	int shown; -	int vflag; -	int family; -	int proto; -	int state; -	int fibnum; -	const char *protoname; -	char stack[TCP_FUNCTION_NAME_LEN_MAX]; -	char cc[TCP_CA_NAME_MAX]; -	struct addr *laddr; -	struct addr *faddr; -}; - -static RB_HEAD(socks_t, sock) socks = RB_INITIALIZER(&socks); -static int64_t -socket_compare(const struct sock *a, const struct sock *b) -{ -	return ((int64_t)(a->socket/2 - b->socket/2)); -} -RB_GENERATE_STATIC(socks_t, sock, socket_tree, socket_compare); - -static RB_HEAD(pcbs_t, sock) pcbs = RB_INITIALIZER(&pcbs); -static int64_t -pcb_compare(const struct sock *a, const struct sock *b) -{ -        return ((int64_t)(a->pcb/2 - b->pcb/2)); -} -RB_GENERATE_STATIC(pcbs_t, sock, pcb_tree, pcb_compare); - -static SLIST_HEAD(, sock) nosocks = SLIST_HEAD_INITIALIZER(&nosocks); - -struct file { -	RB_ENTRY(file)	file_tree; -	kvaddr_t	xf_data; -	pid_t	xf_pid; -	uid_t	xf_uid; -	int	xf_fd; -}; - -static RB_HEAD(files_t, file) ftree = RB_INITIALIZER(&ftree); -static int64_t -file_compare(const struct file *a, const struct file *b) -{ -	return ((int64_t)(a->xf_data/2 - b->xf_data/2)); -} -RB_GENERATE_STATIC(files_t, file, file_tree, file_compare); - -static struct file *files; -static int nfiles; - -static cap_channel_t *capnet; -static cap_channel_t *capnetdb; -static cap_channel_t *capsysctl; -static cap_channel_t *cappwd; - -static bool -_check_ksize(size_t received_size, size_t expected_size, const char *struct_name) -{ -	if (received_size != expected_size) { -		warnx("%s size mismatch: expected %zd, received %zd", -		    struct_name, expected_size, received_size); -		return false; -	} -	return true; -} -#define check_ksize(_sz, _struct)	(_check_ksize(_sz, sizeof(_struct), #_struct)) - -static void -_enforce_ksize(size_t received_size, size_t expected_size, const char *struct_name) -{ -	if (received_size != expected_size) { -		errx(1, "fatal: struct %s size mismatch: expected %zd, received %zd", -		    struct_name, expected_size, received_size); -	} -} -#define enforce_ksize(_sz, _struct)	(_enforce_ksize(_sz, sizeof(_struct), #_struct)) - -static int -get_proto_type(const char *proto) -{ -	struct protoent *pent; +int	*ports; -	if (strlen(proto) == 0) -		return (0); -	if (capnetdb != NULL) -		pent = cap_getprotobyname(capnetdb, proto); -	else -		pent = getprotobyname(proto); -	if (pent == NULL) { -		warn("cap_getprotobyname"); -		return (-1); -	} -	return (pent->p_proto); -} - -static void -init_protos(int num) -{ -	int proto_count = 0; - -	if (num > 0) { -		proto_count = num; -	} else { -		/* Find the maximum number of possible protocols. */ -		while (getprotoent() != NULL) -			proto_count++; -		endprotoent(); -	} - -	if ((protos = malloc(sizeof(int) * proto_count)) == NULL) -		err(1, "malloc"); -	numprotos = proto_count; -} - -static int -parse_protos(char *protospec) -{ -	char *prot; -	int proto_type, proto_index; - -	if (protospec == NULL) -		return (-1); - -	init_protos(0); -	proto_index = 0; -	while ((prot = strsep(&protospec, ",")) != NULL) { -		if (strlen(prot) == 0) -			continue; -		proto_type = get_proto_type(prot); -		if (proto_type != -1) -			protos[proto_index++] = proto_type; -	} -	numprotos = proto_index; -	return (proto_index); -} - -static void +int  parse_ports(const char *portspec)  { -	const char *p, *q; -	int port, end; +	const char *p;  	if (ports == NULL)  		if ((ports = calloc(65536 / INT_BIT, sizeof(int))) == NULL) -			err(1, "calloc()"); +			xo_err(1, "calloc()");  	p = portspec;  	while (*p != '\0') { -		if (!isdigit(*p)) -			errx(1, "syntax error in port range"); -		for (q = p; *q != '\0' && isdigit(*q); ++q) -			/* nothing */ ; -		for (port = 0; p < q; ++p) -			port = port * 10 + digittoint(*p); +		long port, end; +		char *endptr = NULL; + +		errno = 0; +		port = strtol(p, &endptr, 10); +		if (errno) +			return (errno);  		if (port < 0 || port > 65535) -			errx(1, "invalid port number"); +			return (ERANGE);  		SET_PORT(port); -		switch (*p) { +		switch (*endptr) {  		case '-': -			++p; +			p = endptr + 1; +			end = strtol(p, &endptr, 10);  			break;  		case ',': -			++p; -			/* fall through */ -		case '\0': +			p = endptr + 1; +			continue;  		default: +			p = endptr;  			continue;  		} -		for (q = p; *q != '\0' && isdigit(*q); ++q) -			/* nothing */ ; -		for (end = 0; p < q; ++p) -			end = end * 10 + digittoint(*p); +		if (errno) +			return (errno);  		if (end < port || end > 65535) -			errx(1, "invalid port number"); +			return (ERANGE);  		while (port++ < end)  			SET_PORT(port); -		if (*p == ',') -			++p; -	} -} - -static void -sockaddr(struct sockaddr_storage *ss, int af, void *addr, int port) -{ -	struct sockaddr_in *sin4; -	struct sockaddr_in6 *sin6; - -	bzero(ss, sizeof(*ss)); -	switch (af) { -	case AF_INET: -		sin4 = sstosin(ss); -		sin4->sin_len = sizeof(*sin4); -		sin4->sin_family = af; -		sin4->sin_port = port; -		sin4->sin_addr = *(struct in_addr *)addr; -		break; -	case AF_INET6: -		sin6 = sstosin6(ss); -		sin6->sin6_len = sizeof(*sin6); -		sin6->sin6_family = af; -		sin6->sin6_port = port; -		sin6->sin6_addr = *(struct in6_addr *)addr; -#define	s6_addr16	__u6_addr.__u6_addr16 -		if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { -			sin6->sin6_scope_id = -			    ntohs(sin6->sin6_addr.s6_addr16[1]); -			sin6->sin6_addr.s6_addr16[1] = 0; -		} -		break; -	default: -		abort(); -	} -} - -static void -free_socket(struct sock *sock) -{ -	struct addr *cur, *next; - -	cur = sock->laddr; -	while (cur != NULL) { -		next = cur->next; -		free(cur); -		cur = next; -	} -	cur = sock->faddr; -	while (cur != NULL) { -		next = cur->next; -		free(cur); -		cur = next; -	} -	free(sock); -} - -static void -gather_sctp(void) -{ -	struct sock *sock; -	struct addr *laddr, *prev_laddr, *faddr, *prev_faddr; -	struct xsctp_inpcb *xinpcb; -	struct xsctp_tcb *xstcb; -	struct xsctp_raddr *xraddr; -	struct xsctp_laddr *xladdr; -	const char *varname; -	size_t len, offset; -	char *buf; -	int vflag; -	int no_stcb, local_all_loopback, foreign_all_loopback; - -	vflag = 0; -	if (opt_4) -		vflag |= INP_IPV4; -	if (opt_6) -		vflag |= INP_IPV6; - -	varname = "net.inet.sctp.assoclist"; -	if (cap_sysctlbyname(capsysctl, varname, 0, &len, 0, 0) < 0) { -		if (errno != ENOENT) -			err(1, "cap_sysctlbyname()"); -		return; -	} -	if ((buf = (char *)malloc(len)) == NULL) { -		err(1, "malloc()"); -		return; -	} -	if (cap_sysctlbyname(capsysctl, varname, buf, &len, 0, 0) < 0) { -		err(1, "cap_sysctlbyname()"); -		free(buf); -		return; -	} -	xinpcb = (struct xsctp_inpcb *)(void *)buf; -	offset = sizeof(struct xsctp_inpcb); -	while ((offset < len) && (xinpcb->last == 0)) { -		if ((sock = calloc(1, sizeof *sock)) == NULL) -			err(1, "malloc()"); -		sock->socket = xinpcb->socket; -		sock->proto = IPPROTO_SCTP; -		sock->protoname = "sctp"; -		if (xinpcb->maxqlen == 0) -			sock->state = SCTP_CLOSED; -		else -			sock->state = SCTP_LISTEN; -		if (xinpcb->flags & SCTP_PCB_FLAGS_BOUND_V6) { -			sock->family = AF_INET6; -			/* -			 * Currently there is no way to distinguish between -			 * IPv6 only sockets or dual family sockets. -			 * So mark it as dual socket. -			 */ -			sock->vflag = INP_IPV6 | INP_IPV4; -		} else { -			sock->family = AF_INET; -			sock->vflag = INP_IPV4; -		} -		prev_laddr = NULL; -		local_all_loopback = 1; -		while (offset < len) { -			xladdr = (struct xsctp_laddr *)(void *)(buf + offset); -			offset += sizeof(struct xsctp_laddr); -			if (xladdr->last == 1) -				break; -			if ((laddr = calloc(1, sizeof(struct addr))) == NULL) -				err(1, "malloc()"); -			switch (xladdr->address.sa.sa_family) { -			case AF_INET: -#define	__IN_IS_ADDR_LOOPBACK(pina) \ -	((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) -				if (!__IN_IS_ADDR_LOOPBACK( -				    &xladdr->address.sin.sin_addr)) -					local_all_loopback = 0; -#undef	__IN_IS_ADDR_LOOPBACK -				sockaddr(&laddr->address, AF_INET, -				    &xladdr->address.sin.sin_addr, -				    htons(xinpcb->local_port)); -				break; -			case AF_INET6: -				if (!IN6_IS_ADDR_LOOPBACK( -				    &xladdr->address.sin6.sin6_addr)) -					local_all_loopback = 0; -				sockaddr(&laddr->address, AF_INET6, -				    &xladdr->address.sin6.sin6_addr, -				    htons(xinpcb->local_port)); -				break; -			default: -				errx(1, "address family %d not supported", -				    xladdr->address.sa.sa_family); -			} -			laddr->next = NULL; -			if (prev_laddr == NULL) -				sock->laddr = laddr; -			else -				prev_laddr->next = laddr; -			prev_laddr = laddr; -		} -		if (sock->laddr == NULL) { -			if ((sock->laddr = -			    calloc(1, sizeof(struct addr))) == NULL) -				err(1, "malloc()"); -			sock->laddr->address.ss_family = sock->family; -			if (sock->family == AF_INET) -				sock->laddr->address.ss_len = -				    sizeof(struct sockaddr_in); -			else -				sock->laddr->address.ss_len = -				    sizeof(struct sockaddr_in6); -			local_all_loopback = 0; -		} -		if ((sock->faddr = calloc(1, sizeof(struct addr))) == NULL) -			err(1, "malloc()"); -		sock->faddr->address.ss_family = sock->family; -		if (sock->family == AF_INET) -			sock->faddr->address.ss_len = -			    sizeof(struct sockaddr_in); -		else -			sock->faddr->address.ss_len = -			    sizeof(struct sockaddr_in6); -		no_stcb = 1; -		while (offset < len) { -			xstcb = (struct xsctp_tcb *)(void *)(buf + offset); -			offset += sizeof(struct xsctp_tcb); -			if (no_stcb) { -				if (opt_l && (sock->vflag & vflag) && -				    (!opt_L || !local_all_loopback) && -				    ((xinpcb->flags & SCTP_PCB_FLAGS_UDPTYPE) || -				     (xstcb->last == 1))) { -					RB_INSERT(socks_t, &socks, sock); -				} else { -					free_socket(sock); -				} -			} -			if (xstcb->last == 1) -				break; -			no_stcb = 0; -			if (opt_c) { -				if ((sock = calloc(1, sizeof *sock)) == NULL) -					err(1, "malloc()"); -				sock->socket = xinpcb->socket; -				sock->proto = IPPROTO_SCTP; -				sock->protoname = "sctp"; -				sock->state = (int)xstcb->state; -				if (xinpcb->flags & SCTP_PCB_FLAGS_BOUND_V6) { -					sock->family = AF_INET6; -				/* -				 * Currently there is no way to distinguish -				 * between IPv6 only sockets or dual family -				 *  sockets. So mark it as dual socket. -				 */ -					sock->vflag = INP_IPV6 | INP_IPV4; -				} else { -					sock->family = AF_INET; -					sock->vflag = INP_IPV4; -				} -			} -			prev_laddr = NULL; -			local_all_loopback = 1; -			while (offset < len) { -				xladdr = (struct xsctp_laddr *)(void *)(buf + -				    offset); -				offset += sizeof(struct xsctp_laddr); -				if (xladdr->last == 1) -					break; -				if (!opt_c) -					continue; -				laddr = calloc(1, sizeof(struct addr)); -				if (laddr == NULL) -					err(1, "malloc()"); -				switch (xladdr->address.sa.sa_family) { -				case AF_INET: -#define	__IN_IS_ADDR_LOOPBACK(pina) \ -	((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) -					if (!__IN_IS_ADDR_LOOPBACK( -					    &xladdr->address.sin.sin_addr)) -						local_all_loopback = 0; -#undef	__IN_IS_ADDR_LOOPBACK -					sockaddr(&laddr->address, AF_INET, -					    &xladdr->address.sin.sin_addr, -					    htons(xstcb->local_port)); -					break; -				case AF_INET6: -					if (!IN6_IS_ADDR_LOOPBACK( -					    &xladdr->address.sin6.sin6_addr)) -						local_all_loopback = 0; -					sockaddr(&laddr->address, AF_INET6, -					    &xladdr->address.sin6.sin6_addr, -					    htons(xstcb->local_port)); -					break; -				default: -					errx(1, -					    "address family %d not supported", -					    xladdr->address.sa.sa_family); -				} -				laddr->next = NULL; -				if (prev_laddr == NULL) -					sock->laddr = laddr; -				else -					prev_laddr->next = laddr; -				prev_laddr = laddr; -			} -			prev_faddr = NULL; -			foreign_all_loopback = 1; -			while (offset < len) { -				xraddr = (struct xsctp_raddr *)(void *)(buf + -				    offset); -				offset += sizeof(struct xsctp_raddr); -				if (xraddr->last == 1) -					break; -				if (!opt_c) -					continue; -				faddr = calloc(1, sizeof(struct addr)); -				if (faddr == NULL) -					err(1, "malloc()"); -				switch (xraddr->address.sa.sa_family) { -				case AF_INET: -#define	__IN_IS_ADDR_LOOPBACK(pina) \ -	((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) -					if (!__IN_IS_ADDR_LOOPBACK( -					    &xraddr->address.sin.sin_addr)) -						foreign_all_loopback = 0; -#undef	__IN_IS_ADDR_LOOPBACK -					sockaddr(&faddr->address, AF_INET, -					    &xraddr->address.sin.sin_addr, -					    htons(xstcb->remote_port)); -					break; -				case AF_INET6: -					if (!IN6_IS_ADDR_LOOPBACK( -					    &xraddr->address.sin6.sin6_addr)) -						foreign_all_loopback = 0; -					sockaddr(&faddr->address, AF_INET6, -					    &xraddr->address.sin6.sin6_addr, -					    htons(xstcb->remote_port)); -					break; -				default: -					errx(1, -					    "address family %d not supported", -					    xraddr->address.sa.sa_family); -				} -				faddr->encaps_port = xraddr->encaps_port; -				faddr->state = xraddr->state; -				faddr->next = NULL; -				if (prev_faddr == NULL) -					sock->faddr = faddr; -				else -					prev_faddr->next = faddr; -				prev_faddr = faddr; -			} -			if (opt_c) { -				if ((sock->vflag & vflag) && -				    (!opt_L || -				     !(local_all_loopback || -				     foreign_all_loopback))) { -					RB_INSERT(socks_t, &socks, sock); -				} else { -					free_socket(sock); -				} -			} -		} -		xinpcb = (struct xsctp_inpcb *)(void *)(buf + offset); -		offset += sizeof(struct xsctp_inpcb); -	} -	free(buf); -} - -static void -gather_inet(int proto) -{ -	struct xinpgen *xig, *exig; -	struct xinpcb *xip; -	struct xtcpcb *xtp = NULL; -	struct xsocket *so; -	struct sock *sock; -	struct addr *laddr, *faddr; -	const char *varname, *protoname; -	size_t len, bufsize; -	void *buf; -	int retry, vflag; - -	vflag = 0; -	if (opt_4) -		vflag |= INP_IPV4; -	if (opt_6) -		vflag |= INP_IPV6; - -	switch (proto) { -	case IPPROTO_TCP: -		varname = "net.inet.tcp.pcblist"; -		protoname = "tcp"; -		break; -	case IPPROTO_UDP: -		varname = "net.inet.udp.pcblist"; -		protoname = "udp"; -		break; -	case IPPROTO_DIVERT: -		varname = "net.inet.divert.pcblist"; -		protoname = "div"; -		break; -	default: -		errx(1, "protocol %d not supported", proto); -	} - -	buf = NULL; -	bufsize = 8192; -	retry = 5; -	do { -		for (;;) { -			if ((buf = realloc(buf, bufsize)) == NULL) -				err(1, "realloc()"); -			len = bufsize; -			if (cap_sysctlbyname(capsysctl, varname, buf, &len, -			    NULL, 0) == 0) -				break; -			if (errno == ENOENT) -				goto out; -			if (errno != ENOMEM || len != bufsize) -				err(1, "cap_sysctlbyname()"); -			bufsize *= 2; -		} -		xig = (struct xinpgen *)buf; -		exig = (struct xinpgen *)(void *) -		    ((char *)buf + len - sizeof *exig); -		enforce_ksize(xig->xig_len, struct xinpgen); -		enforce_ksize(exig->xig_len, struct xinpgen); -	} while (xig->xig_gen != exig->xig_gen && retry--); - -	if (xig->xig_gen != exig->xig_gen && opt_v) -		warnx("warning: data may be inconsistent"); - -	for (;;) { -		xig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len); -		if (xig >= exig) -			break; -		switch (proto) { -		case IPPROTO_TCP: -			xtp = (struct xtcpcb *)xig; -			xip = &xtp->xt_inp; -			if (!check_ksize(xtp->xt_len, struct xtcpcb)) -				goto out; -			protoname = xtp->t_flags & TF_TOE ? "toe" : "tcp"; -			break; -		case IPPROTO_UDP: -		case IPPROTO_DIVERT: -			xip = (struct xinpcb *)xig; -			if (!check_ksize(xip->xi_len, struct xinpcb)) -				goto out; -			break; -		default: -			errx(1, "protocol %d not supported", proto); -		} -		so = &xip->xi_socket; -		if ((xip->inp_vflag & vflag) == 0) -			continue; -		if (xip->inp_vflag & INP_IPV4) { -			if ((xip->inp_fport == 0 && !opt_l) || -			    (xip->inp_fport != 0 && !opt_c)) -				continue; -#define	__IN_IS_ADDR_LOOPBACK(pina) \ -	((ntohl((pina)->s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) -			if (opt_L && -			    (__IN_IS_ADDR_LOOPBACK(&xip->inp_faddr) || -			     __IN_IS_ADDR_LOOPBACK(&xip->inp_laddr))) -				continue; -#undef	__IN_IS_ADDR_LOOPBACK -		} else if (xip->inp_vflag & INP_IPV6) { -			if ((xip->inp_fport == 0 && !opt_l) || -			    (xip->inp_fport != 0 && !opt_c)) -				continue; -			if (opt_L && -			    (IN6_IS_ADDR_LOOPBACK(&xip->in6p_faddr) || -			     IN6_IS_ADDR_LOOPBACK(&xip->in6p_laddr))) -				continue; -		} else { -			if (opt_v) -				warnx("invalid vflag 0x%x", xip->inp_vflag); -			continue; -		} -		if ((sock = calloc(1, sizeof(*sock))) == NULL) -			err(1, "malloc()"); -		if ((laddr = calloc(1, sizeof *laddr)) == NULL) -			err(1, "malloc()"); -		if ((faddr = calloc(1, sizeof *faddr)) == NULL) -			err(1, "malloc()"); -		sock->socket = so->xso_so; -		sock->pcb = so->so_pcb; -		sock->splice_socket = so->so_splice_so; -		sock->proto = proto; -		sock->inp_gencnt = xip->inp_gencnt; -		sock->fibnum = so->so_fibnum; -		if (xip->inp_vflag & INP_IPV4) { -			sock->family = AF_INET; -			sockaddr(&laddr->address, sock->family, -			    &xip->inp_laddr, xip->inp_lport); -			sockaddr(&faddr->address, sock->family, -			    &xip->inp_faddr, xip->inp_fport); -		} else if (xip->inp_vflag & INP_IPV6) { -			sock->family = AF_INET6; -			sockaddr(&laddr->address, sock->family, -			    &xip->in6p_laddr, xip->inp_lport); -			sockaddr(&faddr->address, sock->family, -			    &xip->in6p_faddr, xip->inp_fport); -		} -		if (proto == IPPROTO_TCP) -			faddr->encaps_port = xtp->xt_encaps_port; -		laddr->next = NULL; -		faddr->next = NULL; -		sock->laddr = laddr; -		sock->faddr = faddr; -		sock->vflag = xip->inp_vflag; -		if (proto == IPPROTO_TCP) { -			sock->state = xtp->t_state; -			memcpy(sock->stack, xtp->xt_stack, -			    TCP_FUNCTION_NAME_LEN_MAX); -			memcpy(sock->cc, xtp->xt_cc, TCP_CA_NAME_MAX); -		} -		sock->protoname = protoname; -		if (sock->socket != 0) -			RB_INSERT(socks_t, &socks, sock); -		else -			SLIST_INSERT_HEAD(&nosocks, sock, socket_list); -	} -out: -	free(buf); -} - -static void -gather_unix(int proto) -{ -	struct xunpgen *xug, *exug; -	struct xunpcb *xup; -	struct sock *sock; -	struct addr *laddr, *faddr; -	const char *varname, *protoname; -	size_t len, bufsize; -	void *buf; -	int retry; - -	switch (proto) { -	case SOCK_STREAM: -		varname = "net.local.stream.pcblist"; -		protoname = "stream"; -		break; -	case SOCK_DGRAM: -		varname = "net.local.dgram.pcblist"; -		protoname = "dgram"; -		break; -	case SOCK_SEQPACKET: -		varname = "net.local.seqpacket.pcblist"; -		protoname = "seqpac"; -		break; -	default: -		abort(); -	} -	buf = NULL; -	bufsize = 8192; -	retry = 5; -	do { -		for (;;) { -			if ((buf = realloc(buf, bufsize)) == NULL) -				err(1, "realloc()"); -			len = bufsize; -			if (cap_sysctlbyname(capsysctl, varname, buf, &len, -			    NULL, 0) == 0) -				break; -			if (errno != ENOMEM || len != bufsize) -				err(1, "cap_sysctlbyname()"); -			bufsize *= 2; -		} -		xug = (struct xunpgen *)buf; -		exug = (struct xunpgen *)(void *) -		    ((char *)buf + len - sizeof(*exug)); -		if (!check_ksize(xug->xug_len, struct xunpgen) || -		    !check_ksize(exug->xug_len, struct xunpgen)) -			goto out; -	} while (xug->xug_gen != exug->xug_gen && retry--); - -	if (xug->xug_gen != exug->xug_gen && opt_v) -		warnx("warning: data may be inconsistent"); - -	for (;;) { -		xug = (struct xunpgen *)(void *)((char *)xug + xug->xug_len); -		if (xug >= exug) -			break; -		xup = (struct xunpcb *)xug; -		if (!check_ksize(xup->xu_len, struct xunpcb)) -			goto out; -		if ((xup->unp_conn == 0 && !opt_l) || -		    (xup->unp_conn != 0 && !opt_c)) -			continue; -		if ((sock = calloc(1, sizeof(*sock))) == NULL) -			err(1, "malloc()"); -		if ((laddr = calloc(1, sizeof *laddr)) == NULL) -			err(1, "malloc()"); -		if ((faddr = calloc(1, sizeof *faddr)) == NULL) -			err(1, "malloc()"); -		sock->socket = xup->xu_socket.xso_so; -		sock->pcb = xup->xu_unpp; -		sock->proto = proto; -		sock->family = AF_UNIX; -		sock->protoname = protoname; -		if (xup->xu_addr.sun_family == AF_UNIX) -			laddr->address = -			    *(struct sockaddr_storage *)(void *)&xup->xu_addr; -		faddr->conn = xup->unp_conn; -		faddr->firstref = xup->xu_firstref; -		faddr->nextref = xup->xu_nextref; -		laddr->next = NULL; -		faddr->next = NULL; -		sock->laddr = laddr; -		sock->faddr = faddr; -		RB_INSERT(socks_t, &socks, sock); -		RB_INSERT(pcbs_t, &pcbs, sock); -	} -out: -	free(buf); -} - -static void -getfiles(void) -{ -	struct xfile *xfiles; -	size_t len, olen; - -	olen = len = sizeof(*xfiles); -	if ((xfiles = malloc(len)) == NULL) -		err(1, "malloc()"); -	while (cap_sysctlbyname(capsysctl, "kern.file", xfiles, &len, 0, 0) -	    == -1) { -		if (errno != ENOMEM || len != olen) -			err(1, "cap_sysctlbyname()"); -		olen = len *= 2; -		if ((xfiles = realloc(xfiles, len)) == NULL) -			err(1, "realloc()"); -	} -	if (len > 0) -		enforce_ksize(xfiles->xf_size, struct xfile); -	nfiles = len / sizeof(*xfiles); - -	if ((files = malloc(nfiles * sizeof(struct file))) == NULL) -		err(1, "malloc()"); - -	for (int i = 0; i < nfiles; i++) { -		files[i].xf_data = xfiles[i].xf_data; -		files[i].xf_pid = xfiles[i].xf_pid; -		files[i].xf_uid = xfiles[i].xf_uid; -		files[i].xf_fd = xfiles[i].xf_fd; -		RB_INSERT(files_t, &ftree, &files[i]); -	} - -	free(xfiles); -} - -static int -formataddr(struct sockaddr_storage *ss, char *buf, size_t bufsize) -{ -	struct sockaddr_un *sun; -	char addrstr[NI_MAXHOST] = { '\0', '\0' }; -	int error, off, port = 0; - -	switch (ss->ss_family) { -	case AF_INET: -		if (sstosin(ss)->sin_addr.s_addr == INADDR_ANY) -			addrstr[0] = '*'; -		port = ntohs(sstosin(ss)->sin_port); -		break; -	case AF_INET6: -		if (IN6_IS_ADDR_UNSPECIFIED(&sstosin6(ss)->sin6_addr)) -			addrstr[0] = '*'; -		port = ntohs(sstosin6(ss)->sin6_port); -		break; -	case AF_UNIX: -		sun = sstosun(ss); -		off = (int)((char *)&sun->sun_path - (char *)sun); -		return snprintf(buf, bufsize, "%.*s", -				sun->sun_len - off, sun->sun_path); -	} -	if (addrstr[0] == '\0') { -		error = cap_getnameinfo(capnet, sstosa(ss), ss->ss_len, -		    addrstr, sizeof(addrstr), NULL, 0, NI_NUMERICHOST); -		if (error) -			errx(1, "cap_getnameinfo()"); -	} -	if (port == 0) -		return snprintf(buf, bufsize, "%s:*", addrstr); -	return snprintf(buf, bufsize, "%s:%d", addrstr, port); -} - -static const char * -getprocname(pid_t pid) -{ -	static struct kinfo_proc proc; -	size_t len; -	int mib[4]; - -	mib[0] = CTL_KERN; -	mib[1] = KERN_PROC; -	mib[2] = KERN_PROC_PID; -	mib[3] = (int)pid; -	len = sizeof(proc); -	if (cap_sysctl(capsysctl, mib, nitems(mib), &proc, &len, NULL, 0) -	    == -1) { -		/* Do not warn if the process exits before we get its name. */ -		if (errno != ESRCH) -			warn("cap_sysctl()"); -		return ("??"); -	} -	return (proc.ki_comm); -} - -static int -getprocjid(pid_t pid) -{ -	static struct kinfo_proc proc; -	size_t len; -	int mib[4]; - -	mib[0] = CTL_KERN; -	mib[1] = KERN_PROC; -	mib[2] = KERN_PROC_PID; -	mib[3] = (int)pid; -	len = sizeof(proc); -	if (cap_sysctl(capsysctl, mib, nitems(mib), &proc, &len, NULL, 0) -	    == -1) { -		/* Do not warn if the process exits before we get its jid. */ -		if (errno != ESRCH) -			warn("cap_sysctl()"); -		return (-1); -	} -	return (proc.ki_jid); -} - -static int -check_ports(struct sock *s) -{ -	int port; -	struct addr *addr; - -	if (ports == NULL) -		return (1); -	if ((s->family != AF_INET) && (s->family != AF_INET6)) -		return (1); -	for (addr = s->laddr; addr != NULL; addr = addr->next) { -		if (s->family == AF_INET) -			port = ntohs(sstosin(&addr->address)->sin_port); -		else -			port = ntohs(sstosin6(&addr->address)->sin6_port); -		if (CHK_PORT(port)) -			return (1); -	} -	for (addr = s->faddr; addr != NULL; addr = addr->next) { -		if (s->family == AF_INET) -			port = ntohs(sstosin(&addr->address)->sin_port); -		else -			port = ntohs(sstosin6(&addr->address)->sin6_port); -		if (CHK_PORT(port)) -			return (1);  	}  	return (0);  } - -static const char * -sctp_conn_state(int state) -{ -	switch (state) { -	case SCTP_CLOSED: -		return "CLOSED"; -		break; -	case SCTP_BOUND: -		return "BOUND"; -		break; -	case SCTP_LISTEN: -		return "LISTEN"; -		break; -	case SCTP_COOKIE_WAIT: -		return "COOKIE_WAIT"; -		break; -	case SCTP_COOKIE_ECHOED: -		return "COOKIE_ECHOED"; -		break; -	case SCTP_ESTABLISHED: -		return "ESTABLISHED"; -		break; -	case SCTP_SHUTDOWN_SENT: -		return "SHUTDOWN_SENT"; -		break; -	case SCTP_SHUTDOWN_RECEIVED: -		return "SHUTDOWN_RECEIVED"; -		break; -	case SCTP_SHUTDOWN_ACK_SENT: -		return "SHUTDOWN_ACK_SENT"; -		break; -	case SCTP_SHUTDOWN_PENDING: -		return "SHUTDOWN_PENDING"; -		break; -	default: -		return "UNKNOWN"; -		break; -	} -} - -static const char * -sctp_path_state(int state) -{ -	switch (state) { -	case SCTP_UNCONFIRMED: -		return "UNCONFIRMED"; -		break; -	case SCTP_ACTIVE: -		return "ACTIVE"; -		break; -	case SCTP_INACTIVE: -		return "INACTIVE"; -		break; -	default: -		return "UNKNOWN"; -		break; -	} -} - -static int -format_unix_faddr(struct addr *faddr, char *buf, size_t bufsize) { -	#define SAFEBUF  (buf == NULL ? NULL : buf + pos) -	#define SAFESIZE (buf == NULL ? 0 : bufsize - pos) - -	size_t pos = 0; -	/* Remote peer we connect(2) to, if any. */ -	if (faddr->conn != 0) { -		struct sock *p; -		pos += strlcpy(buf, "-> ", bufsize); -		p = RB_FIND(pcbs_t, &pcbs, -			&(struct sock){ .pcb = faddr->conn }); -		if (__predict_false(p == NULL)) { -			/* XXGL: can this happen at all? */ -			pos += snprintf(SAFEBUF, SAFESIZE, "??"); -		} else if (p->laddr->address.ss_len == 0) { -			struct file *f; -			f = RB_FIND(files_t, &ftree, -				&(struct file){ .xf_data = -				p->socket }); -			if (f != NULL) { -				pos += snprintf(SAFEBUF, SAFESIZE, "[%lu %d]", -					(u_long)f->xf_pid, f->xf_fd); -			} -		} else -			pos += formataddr(&p->laddr->address, -				SAFEBUF, SAFESIZE); -	} -	/* Remote peer(s) connect(2)ed to us, if any. */ -	if (faddr->firstref != 0) { -		struct sock *p; -		struct file *f; -		kvaddr_t ref = faddr->firstref; -		bool fref = true; - -		pos += snprintf(SAFEBUF, SAFESIZE, " <- "); - -		while ((p = RB_FIND(pcbs_t, &pcbs, -			&(struct sock){ .pcb = ref })) != 0) { -			f = RB_FIND(files_t, &ftree, -				&(struct file){ .xf_data = -				p->socket }); -			if (f != NULL) { -				pos += snprintf(SAFEBUF, SAFESIZE, -					"%s[%lu %d]", fref ? "" : ",", -					(u_long)f->xf_pid, f->xf_fd); -			} -			ref = p->faddr->nextref; -			fref = false; -		} -	} -	return pos; -} - -struct col_widths { -	int user; -	int command; -	int pid; -	int fd; -	int proto; -	int local_addr; -	int foreign_addr; -	int pcb_kva; -	int fib; -	int splice_address; -	int inp_gencnt; -	int encaps; -	int path_state; -	int conn_state; -	int stack; -	int cc; -}; - -static void -calculate_sock_column_widths(struct col_widths *cw, struct sock *s) -{ -	struct addr *laddr, *faddr; -	bool first = true; -	int len = 0; -	laddr = s->laddr; -	faddr = s->faddr; -	first = true; - -	len = strlen(s->protoname); -	if (s->vflag & (INP_IPV4 | INP_IPV6)) -		len += 1; -	if (laddr != NULL && faddr != NULL && s->family == AF_UNIX && -		laddr->address.ss_len == 0 && faddr->conn == 0) -		len += strlen(" (not connected)"); -	cw->proto = MAX(cw->proto, len); - -	while (laddr != NULL || faddr != NULL) { -		if (s->family == AF_UNIX) { -			if ((laddr == NULL) || (faddr == NULL)) -				errx(1, "laddr = %p or faddr = %p is NULL", -					(void *)laddr, (void *)faddr); -			if (laddr->address.ss_len > 0) -				len = formataddr(&laddr->address, NULL, 0); -			cw->local_addr = MAX(cw->local_addr, len); -			len = format_unix_faddr(faddr, NULL, 0); -			cw->foreign_addr = MAX(cw->foreign_addr, len); -		} else { -			if (laddr != NULL) { -				len = formataddr(&laddr->address, NULL, 0); -				cw->local_addr = MAX(cw->local_addr, len); -			} -			if (faddr != NULL) { -				len = formataddr(&faddr->address, NULL, 0); -				cw->foreign_addr = MAX(cw->foreign_addr, len); -			} -		} -		if (opt_f) { -			len = snprintf(NULL, 0, "%d", s->fibnum); -			cw->fib = MAX(cw->fib, len); -		} -		if (opt_I) { -			if (s->splice_socket != 0) { -				struct sock *sp; - -				sp = RB_FIND(socks_t, &socks, &(struct sock) -					{ .socket = s->splice_socket }); -				if (sp != NULL) { -					len = formataddr(&sp->laddr->address, -						 NULL, 0); -					cw->splice_address = MAX( -						cw->splice_address, len); -				} -			} -		} -		if (opt_i) { -			if (s->proto == IPPROTO_TCP || s->proto == IPPROTO_UDP) -			{ -				len = snprintf(NULL, 0, -					"%" PRIu64, s->inp_gencnt); -				cw->inp_gencnt = MAX(cw->inp_gencnt, len); -			} -		} -		if (opt_U) { -			if (faddr != NULL && -				((s->proto == IPPROTO_SCTP && -					s->state != SCTP_CLOSED && -					s->state != SCTP_BOUND && -					s->state != SCTP_LISTEN) || -					(s->proto == IPPROTO_TCP && -					s->state != TCPS_CLOSED && -					s->state != TCPS_LISTEN))) { -				len = snprintf(NULL, 0, "%u", -					ntohs(faddr->encaps_port)); -				cw->encaps = MAX(cw->encaps, len); -			} -		} -		if (opt_s) { -			if (faddr != NULL && -				s->proto == IPPROTO_SCTP && -				s->state != SCTP_CLOSED && -				s->state != SCTP_BOUND && -				s->state != SCTP_LISTEN) { -				len = strlen(sctp_path_state(faddr->state)); -				cw->path_state = MAX(cw->path_state, len); -			} -		} -		if (first) { -			if (opt_s) { -				if (s->proto == IPPROTO_SCTP || -					s->proto == IPPROTO_TCP) { -					switch (s->proto) { -					case IPPROTO_SCTP: -						len = strlen( -						    sctp_conn_state(s->state)); -						cw->conn_state = MAX( -							cw->conn_state, len); -						break; -					case IPPROTO_TCP: -						if (s->state >= 0 && -						    s->state < TCP_NSTATES) { -						    len = strlen( -							tcpstates[s->state]); -						    cw->conn_state = MAX( -							cw->conn_state, len); -						} -						break; -					} -				} -			} -			if (opt_S && s->proto == IPPROTO_TCP) { -				len = strlen(s->stack); -				cw->stack = MAX(cw->stack, len); -			} -			if (opt_C && s->proto == IPPROTO_TCP) { -				len = strlen(s->cc); -				cw->cc = MAX(cw->cc, len); -			} -		} -		if (laddr != NULL) -			laddr = laddr->next; -		if (faddr != NULL) -			faddr = faddr->next; -		first = false; -	} -} - -static void -calculate_column_widths(struct col_widths *cw) -{ -	cw->user = 4; -	cw->command = 10; -	cw->pid = 3; -	cw->fd = 2; -	cw->proto = 5; -	cw->local_addr = 13; -	cw->foreign_addr = 15; -	cw->pcb_kva = 18; -	cw->fib = 3; -	cw->splice_address = 14; -	cw->inp_gencnt = 2; -	cw->encaps = 6; -	cw->path_state = 10; -	cw->conn_state = 10; -	cw->stack = 5; -	cw->cc = 2; - -	int n, len; -	struct file *xf; -	struct sock *s; -	struct passwd *pwd; - -	for (xf = files, n = 0; n < nfiles; ++n, ++xf) { -		if (xf->xf_data == 0) -			continue; -		if (opt_j >= 0 && opt_j != getprocjid(xf->xf_pid)) -			continue; -		s = RB_FIND(socks_t, &socks, -			&(struct sock){ .socket = xf->xf_data}); -		if (s == NULL || (!check_ports(s))) -			continue; -		s->shown = 1; -		if (opt_n || -			(pwd = cap_getpwuid(cappwd, xf->xf_uid)) == NULL) -			len = snprintf(NULL, 0, "%lu", (u_long)xf->xf_uid); -		else -			len = snprintf(NULL, 0, "%s", pwd->pw_name); -		cw->user = MAX(cw->user, len); -		len = snprintf(NULL, 0, "%lu", (u_long)xf->xf_pid); -		cw->pid = MAX(cw->pid, len); -		len = snprintf(NULL, 0, "%d", xf->xf_fd); -		cw->fd = MAX(cw->fd, len); - -		calculate_sock_column_widths(cw, s); -	} -	if (opt_j >= 0) -		return; -	SLIST_FOREACH(s, &nosocks, socket_list) { -		if (!check_ports(s)) -			continue; -		calculate_sock_column_widths(cw, s); -	} -	RB_FOREACH(s, socks_t, &socks) { -		if (s->shown) -			continue; -		if (!check_ports(s)) -			continue; -		calculate_sock_column_widths(cw, s); -	} -} - -static void -display_sock(struct sock *s, struct col_widths *cw, char *buf, size_t bufsize) -{ -	struct addr *laddr, *faddr; -	bool first; -	laddr = s->laddr; -	faddr = s->faddr; -	first = true; - -	snprintf(buf, bufsize, "%s%s%s%s", -		s->protoname, -		s->vflag & INP_IPV4 ? "4" : "", -		s->vflag & INP_IPV6 ? "6" : "", -		(laddr != NULL && faddr != NULL && -		s->family == AF_UNIX && laddr->address.ss_len == 0 && -		faddr->conn == 0) ? " (not connected)" : ""); -	printf(" %-*s", cw->proto, buf); -	while (laddr != NULL || faddr != NULL) { -		if (s->family == AF_UNIX) { -			if ((laddr == NULL) || (faddr == NULL)) -				errx(1, "laddr = %p or faddr = %p is NULL", -					(void *)laddr, (void *)faddr); -			if (laddr->address.ss_len > 0) -				formataddr(&laddr->address, buf, bufsize); -			else -				strlcpy(buf, "??", bufsize); -			printf(" %-*s", cw->local_addr, buf); -			if (format_unix_faddr(faddr, buf, bufsize) == 0) -				strlcpy(buf, "??", bufsize); -			printf(" %-*s", cw->foreign_addr, buf); -		} else { -			if (laddr != NULL) -				formataddr(&laddr->address, buf, bufsize); -			else -				strlcpy(buf, "??", bufsize); -			printf(" %-*s", cw->local_addr, buf); -			if (faddr != NULL) -				formataddr(&faddr->address, buf, bufsize); -			else -				strlcpy(buf, "??", bufsize); -			printf(" %-*s", cw->foreign_addr, buf); -		} -		if (opt_A) -			printf(" %#*" PRIx64, cw->pcb_kva, s->pcb); -		if (opt_f) -			printf(" %*d", cw->fib, s->fibnum); -		if (opt_I) { -			if (s->splice_socket != 0) { -				struct sock *sp; -				sp = RB_FIND(socks_t, &socks, &(struct sock) -					{ .socket = s->splice_socket }); -				if (sp != NULL) -					formataddr(&sp->laddr->address, -								buf, bufsize); -			} else -				strlcpy(buf, "??", bufsize); -			printf(" %-*s", cw->splice_address, buf); -		} -		if (opt_i) { -			if (s->proto == IPPROTO_TCP || s->proto == IPPROTO_UDP) -				printf(" %*" PRIu64, cw->inp_gencnt, -					s->inp_gencnt); -			else -				printf(" %*s", cw->inp_gencnt, "??"); -		} -		if (opt_U) { -			if (faddr != NULL && -				((s->proto == IPPROTO_SCTP && -					s->state != SCTP_CLOSED && -					s->state != SCTP_BOUND && -					s->state != SCTP_LISTEN) || -					(s->proto == IPPROTO_TCP && -					s->state != TCPS_CLOSED && -					s->state != TCPS_LISTEN))) { -				printf(" %*u", cw->encaps, -					ntohs(faddr->encaps_port)); -			} else -				printf(" %*s", cw->encaps, "??"); -		} -		if (opt_s) { -			if (faddr != NULL && -				s->proto == IPPROTO_SCTP && -				s->state != SCTP_CLOSED && -				s->state != SCTP_BOUND && -				s->state != SCTP_LISTEN) { -				printf(" %-*s", cw->path_state, -					sctp_path_state(faddr->state)); -			} else -				printf(" %-*s", cw->path_state, "??"); -		} -		if (first) { -			if (opt_s) { -				if (s->proto == IPPROTO_SCTP || -				    s->proto == IPPROTO_TCP) { -					switch (s->proto) { -					case IPPROTO_SCTP: -						printf(" %-*s", cw->conn_state, -						    sctp_conn_state(s->state)); -						break; -					case IPPROTO_TCP: -						if (s->state >= 0 && -							s->state < TCP_NSTATES) -							printf(" %-*s", -							cw->conn_state, -							tcpstates[s->state]); -						else -							printf(" %-*s", -							cw->conn_state, "??"); -						break; -					} -				} else -					printf(" %-*s", cw->conn_state, "??"); -			} -			if (opt_S) { -				if (s->proto == IPPROTO_TCP) -					printf(" %-*s", cw->stack, s->stack); -				else -					printf(" %-*s", cw->stack, "??"); -			} -			if (opt_C) { -				if (s->proto == IPPROTO_TCP) -					printf(" %-*s", cw->cc, s->cc); -				else -					printf(" %-*s", cw->cc, "??"); -			} -		} -		if (laddr != NULL) -			laddr = laddr->next; -		if (faddr != NULL) -			faddr = faddr->next; -		if (laddr != NULL || faddr != NULL) -			printf("%-*s %-*s %-*s %-*s %-*s", cw->user, "", -				cw->command, "", cw->pid, "", cw->fd, "", -				cw->proto, ""); -		first = false; -	} -	printf("\n"); -} - -static void -display(void) -{ -	struct passwd *pwd; -	struct file *xf; -	struct sock *s; -	int n; -	struct col_widths cw; -	const size_t bufsize = 512; -	void *buf; -	if ((buf = (char *)malloc(bufsize)) == NULL) { -		err(1, "malloc()"); -		return; -	} -	calculate_column_widths(&cw); - -	if (!opt_q) { -		printf("%-*s %-*s %*s %*s %-*s %-*s %-*s", -				cw.user, "USER", cw.command, "COMMAND", -				cw.pid, "PID", cw.fd, "FD", cw.proto, "PROTO", -				cw.local_addr, "LOCAL ADDRESS", -				cw.foreign_addr,"FOREIGN ADDRESS"); -		if (opt_A) -			printf(" %-*s", cw.pcb_kva, "PCB KVA"); -		if (opt_f) -			/* RT_MAXFIBS is 65535. */ -			printf(" %*s", cw.fib, "FIB"); -		if (opt_I) -			printf(" %-*s", cw.splice_address, "SPLICE ADDRESS"); -		if (opt_i) -			printf(" %*s", cw.inp_gencnt, "ID"); -		if (opt_U) -			printf(" %*s", cw.encaps, "ENCAPS"); -		if (opt_s) { -			printf(" %-*s", cw.path_state, "PATH STATE"); -			printf(" %-*s", cw.conn_state, "CONN STATE"); -		} -		if (opt_S) -			printf(" %-*s", cw.stack, "STACK"); -		if (opt_C) -			printf(" %-*s", cw.cc, "CC"); -		printf("\n"); -	} -	cap_setpassent(cappwd, 1); -	for (xf = files, n = 0; n < nfiles; ++n, ++xf) { -		if (xf->xf_data == 0) -			continue; -		if (opt_j >= 0 && opt_j != getprocjid(xf->xf_pid)) -			continue; -		s = RB_FIND(socks_t, &socks, -			&(struct sock){ .socket = xf->xf_data}); -		if (s != NULL && check_ports(s)) { -			s->shown = 1; -			if (opt_n || -			    (pwd = cap_getpwuid(cappwd, xf->xf_uid)) == NULL) -				printf("%-*lu", cw.user, (u_long)xf->xf_uid); -			else -				printf("%-*s", cw.user, pwd->pw_name); -			printf(" %-*.*s", cw.command, cw.command, -				getprocname(xf->xf_pid)); -			printf(" %*lu", cw.pid, (u_long)xf->xf_pid); -			printf(" %*d", cw.fd, xf->xf_fd); -			display_sock(s, &cw, buf, bufsize); -		} -	} -	if (opt_j >= 0) -		return; -	SLIST_FOREACH(s, &nosocks, socket_list) { -		if (!check_ports(s)) -			continue; -		printf("%-*s %-*s %*s %*s", cw.user, "??", cw.command, "??", -			cw.pid, "??", cw.fd, "??"); -		display_sock(s, &cw, buf, bufsize); -	} -	RB_FOREACH(s, socks_t, &socks) { -		if (s->shown) -			continue; -		if (!check_ports(s)) -			continue; -		printf("%-*s %-*s %*s %*s", cw.user, "??", cw.command, "??", -			cw.pid, "??", cw.fd, "??"); -		display_sock(s, &cw, buf, bufsize); -	} -	free(buf); -} - -static int -set_default_protos(void) -{ -	struct protoent *prot; -	const char *pname; -	size_t pindex; - -	init_protos(default_numprotos); - -	for (pindex = 0; pindex < default_numprotos; pindex++) { -		pname = default_protos[pindex]; -		prot = cap_getprotobyname(capnetdb, pname); -		if (prot == NULL) -			err(1, "cap_getprotobyname: %s", pname); -		protos[pindex] = prot->p_proto; -	} -	numprotos = pindex; -	return (pindex); -} - -/* - * Return the vnet property of the jail, or -1 on error. - */ -static int -jail_getvnet(int jid) -{ -	struct iovec jiov[6]; -	int vnet; -	size_t len = sizeof(vnet); - -	if (sysctlbyname("kern.features.vimage", &vnet, &len, NULL, 0) != 0) -		return (0); - -	vnet = -1; -	jiov[0].iov_base = __DECONST(char *, "jid"); -	jiov[0].iov_len = sizeof("jid"); -	jiov[1].iov_base = &jid; -	jiov[1].iov_len = sizeof(jid); -	jiov[2].iov_base = __DECONST(char *, "vnet"); -	jiov[2].iov_len = sizeof("vnet"); -	jiov[3].iov_base = &vnet; -	jiov[3].iov_len = sizeof(vnet); -	jiov[4].iov_base = __DECONST(char *, "errmsg"); -	jiov[4].iov_len = sizeof("errmsg"); -	jiov[5].iov_base = jail_errmsg; -	jiov[5].iov_len = JAIL_ERRMSGLEN; -	jail_errmsg[0] = '\0'; -	if (jail_get(jiov, nitems(jiov), 0) < 0) { -		if (!jail_errmsg[0]) -			snprintf(jail_errmsg, JAIL_ERRMSGLEN, -			    "jail_get: %s", strerror(errno)); -		return (-1); -	} -	return (vnet); -} - -static void -usage(void) -{ -	errx(1, -    "usage: sockstat [-46ACcfIiLlnqSsUuv] [-j jid] [-p ports] [-P protocols]"); -} - -int -main(int argc, char *argv[]) -{ -	cap_channel_t *capcas; -	cap_net_limit_t *limit; -	const char *pwdcmds[] = { "setpassent", "getpwuid" }; -	const char *pwdfields[] = { "pw_name" }; -	int protos_defined = -1; -	int o, i; - -	opt_j = -1; -	while ((o = getopt(argc, argv, "46ACcfIij:Llnp:P:qSsUuvw")) != -1) -		switch (o) { -		case '4': -			opt_4 = true; -			break; -		case '6': -			opt_6 = true; -			break; -		case 'A': -			opt_A = true; -			break; -		case 'C': -			opt_C = true; -			break; -		case 'c': -			opt_c = true; -			break; -		case 'f': -			opt_f = true; -			break; -		case 'I': -			opt_I = true; -			break; -		case 'i': -			opt_i = true; -			break; -		case 'j': -			opt_j = jail_getid(optarg); -			if (opt_j < 0) -				errx(1, "jail_getid: %s", jail_errmsg); -			break; -		case 'L': -			opt_L = true; -			break; -		case 'l': -			opt_l = true; -			break; -		case 'n': -			opt_n = true; -			break; -		case 'p': -			parse_ports(optarg); -			break; -		case 'P': -			protos_defined = parse_protos(optarg); -			break; -		case 'q': -			opt_q = true; -			break; -		case 'S': -			opt_S = true; -			break; -		case 's': -			opt_s = true; -			break; -		case 'U': -			opt_U = true; -			break; -		case 'u': -			opt_u = true; -			break; -		case 'v': -			++opt_v; -			break; -		case 'w': -			/* left for backward compatibility. */ -			break; -		default: -			usage(); -		} - -	argc -= optind; -	argv += optind; - -	if (argc > 0) -		usage(); - -	if (opt_j > 0) { -		switch (jail_getvnet(opt_j)) { -		case -1: -			errx(2, "jail_getvnet: %s", jail_errmsg); -		case JAIL_SYS_NEW: -			if (jail_attach(opt_j) < 0) -				err(3, "jail_attach()"); -			/* Set back to -1 for normal output in vnet jail. */ -			opt_j = -1; -			break; -		default: -			break; -		} -	} - -	capcas = cap_init(); -	if (capcas == NULL) -		err(1, "Unable to contact Casper"); -	if (caph_enter_casper() < 0) -		err(1, "Unable to enter capability mode"); -	capnet = cap_service_open(capcas, "system.net"); -	if (capnet == NULL) -		err(1, "Unable to open system.net service"); -	capnetdb = cap_service_open(capcas, "system.netdb"); -	if (capnetdb == NULL) -		err(1, "Unable to open system.netdb service"); -	capsysctl = cap_service_open(capcas, "system.sysctl"); -	if (capsysctl == NULL) -		err(1, "Unable to open system.sysctl service"); -	cappwd = cap_service_open(capcas, "system.pwd"); -	if (cappwd == NULL) -		err(1, "Unable to open system.pwd service"); -	cap_close(capcas); -	limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME); -	if (limit == NULL) -		err(1, "Unable to init cap_net limits"); -	if (cap_net_limit(limit) < 0) -		err(1, "Unable to apply limits"); -	if (cap_pwd_limit_cmds(cappwd, pwdcmds, nitems(pwdcmds)) < 0) -		err(1, "Unable to apply pwd commands limits"); -	if (cap_pwd_limit_fields(cappwd, pwdfields, nitems(pwdfields)) < 0) -		err(1, "Unable to apply pwd commands limits"); - -	if ((!opt_4 && !opt_6) && protos_defined != -1) -		opt_4 = opt_6 = true; -	if (!opt_4 && !opt_6 && !opt_u) -		opt_4 = opt_6 = opt_u = true; -	if ((opt_4 || opt_6) && protos_defined == -1) -		protos_defined = set_default_protos(); -	if (!opt_c && !opt_l) -		opt_c = opt_l = true; - -	if (opt_4 || opt_6) { -		for (i = 0; i < protos_defined; i++) -			if (protos[i] == IPPROTO_SCTP) -				gather_sctp(); -			else -				gather_inet(protos[i]); -	} - -	if (opt_u || (protos_defined == -1 && !opt_4 && !opt_6)) { -		gather_unix(SOCK_STREAM); -		gather_unix(SOCK_DGRAM); -		gather_unix(SOCK_SEQPACKET); -	} -	getfiles(); -	display(); -	exit(0); -} diff --git a/usr.bin/sockstat/sockstat.h b/usr.bin/sockstat/sockstat.h new file mode 100644 index 000000000000..80d91ebbaddc --- /dev/null +++ b/usr.bin/sockstat/sockstat.h @@ -0,0 +1,35 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 ConnectWise + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer + *    in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define	INT_BIT (sizeof(int)*CHAR_BIT) +#define	SET_PORT(p) do { ports[p / INT_BIT] |= 1 << (p % INT_BIT); } while (0) +#define	CHK_PORT(p) (ports[p / INT_BIT] & (1 << (p % INT_BIT))) + +extern int	*ports; + +int parse_ports(const char *portspec); diff --git a/usr.bin/sockstat/tests/Makefile b/usr.bin/sockstat/tests/Makefile new file mode 100644 index 000000000000..5412e9d842aa --- /dev/null +++ b/usr.bin/sockstat/tests/Makefile @@ -0,0 +1,9 @@ +ATF_TESTS_C+=	sockstat_test +SRCS.sockstat_test= sockstat_test.c sockstat.c +.PATH:	${.CURDIR:H} + +LIBADD=		xo + +PACKAGE= tests + +.include <bsd.test.mk> diff --git a/usr.bin/sockstat/tests/sockstat_test.c b/usr.bin/sockstat/tests/sockstat_test.c new file mode 100644 index 000000000000..2d2a23c7a166 --- /dev/null +++ b/usr.bin/sockstat/tests/sockstat_test.c @@ -0,0 +1,190 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 ConnectWise + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer + *    in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/errno.h> + +#include <atf-c.h> + +#include "../sockstat.h" + +ATF_TC_WITHOUT_HEAD(backwards_range); +ATF_TC_BODY(backwards_range, tc) +{ +	const char portspec[] = "22-21"; + +	ATF_CHECK_EQ(ERANGE, parse_ports(portspec)); +} + +ATF_TC_WITHOUT_HEAD(erange_low); +ATF_TC_BODY(erange_low, tc) +{ +	const char portspec[] = "-1"; + +	ATF_CHECK_EQ(ERANGE, parse_ports(portspec)); +} + +ATF_TC_WITHOUT_HEAD(erange_high); +ATF_TC_BODY(erange_high, tc) +{ +	const char portspec[] = "65536"; + +	ATF_CHECK_EQ(ERANGE, parse_ports(portspec)); +} + +ATF_TC_WITHOUT_HEAD(erange_on_range_end); +ATF_TC_BODY(erange_on_range_end, tc) +{ +	const char portspec[] = "22-65536"; + +	ATF_CHECK_EQ(ERANGE, parse_ports(portspec)); +} + +ATF_TC_WITHOUT_HEAD(multiple); +ATF_TC_BODY(multiple, tc) +{ +	const char portspec[] = "80,443"; + +	ATF_REQUIRE_EQ(0, parse_ports(portspec)); + +	ATF_CHECK(!CHK_PORT(0)); + +	ATF_CHECK(!CHK_PORT(79)); +	ATF_CHECK(CHK_PORT(80)); +	ATF_CHECK(!CHK_PORT(81)); + +	ATF_CHECK(!CHK_PORT(442)); +	ATF_CHECK(CHK_PORT(443)); +	ATF_CHECK(!CHK_PORT(444)); +} + +ATF_TC_WITHOUT_HEAD(multiple_plus_ranges); +ATF_TC_BODY(multiple_plus_ranges, tc) +{ +	const char portspec[] = "80,443,500-501,510,520,40000-40002"; + +	ATF_REQUIRE_EQ(0, parse_ports(portspec)); + +	ATF_CHECK(!CHK_PORT(0)); + +	ATF_CHECK(!CHK_PORT(79)); +	ATF_CHECK(CHK_PORT(80)); +	ATF_CHECK(!CHK_PORT(81)); + +	ATF_CHECK(!CHK_PORT(442)); +	ATF_CHECK(CHK_PORT(443)); +	ATF_CHECK(!CHK_PORT(444)); + +	ATF_CHECK(!CHK_PORT(499)); +	ATF_CHECK(CHK_PORT(500)); +	ATF_CHECK(CHK_PORT(501)); +	ATF_CHECK(!CHK_PORT(502)); + +	ATF_CHECK(!CHK_PORT(519)); +	ATF_CHECK(CHK_PORT(520)); +	ATF_CHECK(!CHK_PORT(521)); + +	ATF_CHECK(!CHK_PORT(39999)); +	ATF_CHECK(CHK_PORT(40000)); +	ATF_CHECK(CHK_PORT(40001)); +	ATF_CHECK(CHK_PORT(40002)); +	ATF_CHECK(!CHK_PORT(40003)); +} + +ATF_TC_WITHOUT_HEAD(nonnumeric); +ATF_TC_BODY(nonnumeric, tc) +{ +	const char portspec[] = "foo"; + +	ATF_CHECK_EQ(EINVAL, parse_ports(portspec)); +} + +ATF_TC_WITHOUT_HEAD(null_range); +ATF_TC_BODY(null_range, tc) +{ +	const char portspec[] = "22-22"; + +	ATF_REQUIRE_EQ(0, parse_ports(portspec)); + +	ATF_CHECK(!CHK_PORT(0)); +	ATF_CHECK(CHK_PORT(22)); +	ATF_CHECK(!CHK_PORT(23)); +} + +ATF_TC_WITHOUT_HEAD(range); +ATF_TC_BODY(range, tc) +{ +	const char portspec[] = "22-25"; + +	ATF_REQUIRE_EQ(0, parse_ports(portspec)); + +	ATF_CHECK(!CHK_PORT(0)); +	ATF_CHECK(CHK_PORT(22)); +	ATF_CHECK(CHK_PORT(23)); +	ATF_CHECK(CHK_PORT(24)); +	ATF_CHECK(CHK_PORT(25)); +	ATF_CHECK(!CHK_PORT(26)); +} + +ATF_TC_WITHOUT_HEAD(single); +ATF_TC_BODY(single, tc) +{ +	const char portspec[] = "22"; + +	ATF_REQUIRE_EQ(0, parse_ports(portspec)); + +	ATF_CHECK(!CHK_PORT(0)); +	ATF_CHECK(CHK_PORT(22)); +} + +ATF_TC_WITHOUT_HEAD(zero); +ATF_TC_BODY(zero, tc) +{ +	const char portspec[] = "0"; + +	ATF_REQUIRE_EQ(0, parse_ports(portspec)); + +	ATF_CHECK(CHK_PORT(0)); +} + +ATF_TP_ADD_TCS(tp) +{ +	ATF_TP_ADD_TC(tp, backwards_range); +	ATF_TP_ADD_TC(tp, erange_low); +	ATF_TP_ADD_TC(tp, erange_high); +	ATF_TP_ADD_TC(tp, erange_on_range_end); +	ATF_TP_ADD_TC(tp, multiple); +	ATF_TP_ADD_TC(tp, multiple_plus_ranges); +	ATF_TP_ADD_TC(tp, nonnumeric); +	ATF_TP_ADD_TC(tp, null_range); +	ATF_TP_ADD_TC(tp, range); +	ATF_TP_ADD_TC(tp, single); +	ATF_TP_ADD_TC(tp, zero); + +	return (atf_no_error()); +} 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/strings/Makefile b/usr.bin/strings/Makefile index 8e2572810947..c01e775b0b89 100644 --- a/usr.bin/strings/Makefile +++ b/usr.bin/strings/Makefile @@ -1,5 +1,7 @@  .include <src.opts.mk> +PACKAGE=	toolchain +  ELFTCDIR=	${SRCTOP}/contrib/elftoolchain  .PATH: ${ELFTCDIR}/strings diff --git a/usr.bin/systat/ip.c b/usr.bin/systat/ip.c index 6cb3787b3f91..344b74011e99 100644 --- a/usr.bin/systat/ip.c +++ b/usr.bin/systat/ip.c @@ -82,9 +82,10 @@ static struct stat curstat, initstat, oldstat;  13999999999 packets forwarded        999999999 - no checksum  14999999999 - unreachable dests      999999999 - invalid length  15999999999 - redirects generated    999999999 - no socket for dest port -16999999999 option errors            999999999 - no socket for broadcast -17999999999 unwanted multicasts      999999999 - socket buffer full -18999999999 delivered to upper layer 999999999 total output packets +16999999999 option errors            999999999   - no socket for broadcast +17999999999 unwanted multicasts      999999999   - no socket for multicast +18999999999 delivered to upper layer 999999999 - socket buffer full +19999999999                          999999999 total output packets  --0123456789012345678901234567890123456789012345678901234567890123456789012345  --0         1         2         3         4         5         6         7  */ @@ -127,9 +128,10 @@ labelip(void)  	L(13, "packets forwarded");	R(13, "- no checksum");  	L(14, "- unreachable dests");	R(14, "- invalid length");  	L(15, "- redirects generated");	R(15, "- no socket for dest port"); -	L(16, "option errors");		R(16, "- no socket for broadcast"); -	L(17, "unwanted multicasts");	R(17, "- socket buffer full"); -	L(18, "delivered to upper layer");	R(18, "total output packets"); +	L(16, "option errors");		R(16, "  - no socket for broadcast"); +	L(17, "unwanted multicasts");	R(17, "  - no socket for multicast"); +	L(18, "delivered to upper layer");	R(18, "- socket buffer full"); +					R(19, "total output packets");  #undef L  #undef R  } @@ -189,6 +191,7 @@ domode(struct stat *ret)  	DO(u.udps_badlen);  	DO(u.udps_noport);  	DO(u.udps_noportbcast); +	DO(u.udps_noportmcast);  	DO(u.udps_fullsock);  	DO(u.udps_opackets);  #undef DO @@ -237,9 +240,10 @@ showip(void)  	DO(i.ips_badoptions, 16, 0);  	DO(u.udps_noportbcast, 16, 35);  	DO(i.ips_notmember, 17, 0); -	DO(u.udps_fullsock, 17, 35); +	DO(u.udps_noportmcast, 17, 35);  	DO(i.ips_delivered, 18, 0); -	DO(u.udps_opackets, 18, 35); +	DO(u.udps_fullsock, 18, 35); +	DO(u.udps_opackets, 19, 35);  #undef DO  } diff --git a/usr.bin/tail/tail.c b/usr.bin/tail/tail.c index fc60a82287df..a92eee3881b4 100644 --- a/usr.bin/tail/tail.c +++ b/usr.bin/tail/tail.c @@ -95,15 +95,17 @@ main(int argc, char *argv[])  	 * -r is the entire file, not 10 lines.  	 */  #define	ARG(units, forward, backward) {					\ +	int64_t num;							\  	if (style)							\  		usage();						\ -	if (expand_number(optarg, &off))				\ +	if (expand_number(optarg, &num))				\  		err(1, "illegal offset -- %s", optarg);			\ -	if (off > INT64_MAX / units || off < INT64_MIN / units )	\ +	if (num > INT64_MAX / units || num < INT64_MIN / units)		\  		errx(1, "illegal offset -- %s", optarg);		\ -	switch(optarg[0]) {						\ +	off = num * units;						\ +	switch (optarg[0]) {						\  	case '+':							\ -		if (off)						\ +		if (off != 0)						\  			off -= (units);					\  		style = (forward);					\  		break;							\ @@ -121,7 +123,7 @@ main(int argc, char *argv[])  	off = 0;  	while ((ch = getopt_long(argc, argv, "+Fb:c:fn:qrv", long_opts, NULL)) !=  	    -1) -		switch(ch) { +		switch (ch) {  		case 'F':	/* -F is superset of (and implies) -f */  			Fflag = fflag = 1;  			break; diff --git a/usr.bin/tail/tests/tail_test.sh b/usr.bin/tail/tests/tail_test.sh index 9c941f8a2c2f..82c74a0d4da4 100755 --- a/usr.bin/tail/tests/tail_test.sh +++ b/usr.bin/tail/tests/tail_test.sh @@ -341,6 +341,7 @@ follow_create_body()  	rm -f infile  	tail -F infile > outfile &  	pid=$! +	sleep 0.1  	seq 1 5 >infile  	sleep 2  	atf_check cmp infile outfile @@ -360,6 +361,7 @@ follow_rename_body()  	seq 1 3 > infile  	tail -F infile > outfile &  	pid=$! +	sleep 0.1  	seq 4 5 > infile_new  	atf_check mv infile infile_old  	atf_check mv infile_new infile @@ -423,6 +425,51 @@ no_lf_at_eof_body()  	atf_check -o inline:"a\nb\nc" tail -4 infile  } +atf_test_case tail_b +tail_b_head() +{ +	atf_set "descr" "Test -b option" +} +tail_b_body() +{ +	(jot -b a 256 ; jot -b b 256 ; jot -b c 256) >infile +	(jot -b b 256 ; jot -b c 256) >outfile +	# infile is 3 blocks long, outfile contains the last two +	atf_check -o file:outfile tail -b +2 infile # start at the 2nd block +	atf_check -o file:outfile tail -b -2 infile # 2 blocks from the end +	atf_check -o file:outfile tail -b  2 infile # 2 blocks from the end +} + +atf_test_case tail_c +tail_c_head() +{ +	atf_set "descr" "Test -c option" +} +tail_c_body() +{ +	(jot -b a 256 ; jot -b b 256 ; jot -b c 256) >infile +	(jot -b b 256 ; jot -b c 256) >outfile +	# infile is 1536 bytes long, outfile contains the last 1024 +	atf_check -o file:outfile tail -c  +513 infile # start at the 513th byte +	atf_check -o file:outfile tail -c -1024 infile # 1024 bytes from the end +	atf_check -o file:outfile tail -c  1024 infile # 1024 bytes from the end +} + +atf_test_case tail_n +tail_n_head() +{ +	atf_set "descr" "Test -n option" +} +tail_n_body() +{ +	(jot -b a 256 ; jot -b b 256 ; jot -b c 256) >infile +	(jot -b b 256 ; jot -b c 256) >outfile +	# infile is 768 lines long, outfile contains the last 512 +	atf_check -o file:outfile tail -n +257 infile # start at the 257th line +	atf_check -o file:outfile tail -n -512 infile # 512 lines from the end +	atf_check -o file:outfile tail -n  512 infile # 512 lines from the end +} +  atf_init_test_cases()  {  	atf_add_test_case empty_r @@ -448,4 +495,7 @@ atf_init_test_cases()  	atf_add_test_case verbose_header  	atf_add_test_case si_number  	atf_add_test_case no_lf_at_eof +	atf_add_test_case tail_b +	atf_add_test_case tail_c +	atf_add_test_case tail_n  } diff --git a/usr.bin/tar/Makefile b/usr.bin/tar/Makefile index 9260315fb30b..8b0d3e4a6cf0 100644 --- a/usr.bin/tar/Makefile +++ b/usr.bin/tar/Makefile @@ -17,7 +17,7 @@ SRCS=	bsdtar.c	\  	write.c  .PATH: ${_LIBARCHIVEDIR}/libarchive_fe -SRCS+=	err.c		\ +SRCS+=	lafe_err.c	\  	line_reader.c	\  	passphrase.c diff --git a/usr.bin/tar/tests/Makefile b/usr.bin/tar/tests/Makefile index 929f8127f9b3..116425b0621f 100644 --- a/usr.bin/tar/tests/Makefile +++ b/usr.bin/tar/tests/Makefile @@ -27,6 +27,7 @@ TESTS_SRCS=	\  	test_0.c				\  	test_basic.c				\  	test_copy.c				\ +	test_crlf_mtree.c			\  	test_empty_mtree.c			\  	test_extract_tar_bz2.c			\  	test_extract_tar_grz.c			\ diff --git a/usr.bin/tcopy/Makefile b/usr.bin/tcopy/Makefile index 73dcd45b2e0f..831de7625db8 100644 --- a/usr.bin/tcopy/Makefile +++ b/usr.bin/tcopy/Makefile @@ -1,4 +1,4 @@ -PROG=	tcopy -LIBADD=      util +PROG_CXX=	tcopy +LIBADD=		util  .include <bsd.prog.mk> diff --git a/usr.bin/tcopy/tcopy.1 b/usr.bin/tcopy/tcopy.1 index 3f12a807e41e..f74a29ba1173 100644 --- a/usr.bin/tcopy/tcopy.1 +++ b/usr.bin/tcopy/tcopy.1 @@ -30,65 +30,189 @@  .Os  .Sh NAME  .Nm tcopy -.Nd copy and/or verify mag tapes +.Nd read, write, copy and verify tapes  .Sh SYNOPSIS  .Nm -.Op Fl cvx +.Op Fl crvx +.Op Fl l Ar logfile  .Op Fl s Ar maxblk  .Oo Ar src Op Ar dest  .Oc  .Sh DESCRIPTION  The  .Nm -utility is designed to copy magnetic tapes. +utility is designed to read, write and copy tapes. +.Pp  The only assumption made  about the tape layout is that there are two sequential EOF marks  at the end. -By default, the -.Nm -utility will print -information about the sizes of records and files found -on the +.Pp +The +.Ar src +argument can be a tape device and defaults to  .Pa /dev/sa0 -tape, or on the tape specified by the +or it can be data in SIMH-TAP format. +If +.Ar src +is +.Dq Cm - +the standard input is read. +.Pp +If the +.Ar dest +argument is also specified, a copy of the  .Ar src -argument. -If a destination tape is also specified by the +will be made onto the +.Ar dest . +If +.Ar dest +is +.Dq Cm - +standard output will be written to. +.Pp +If  .Ar dest -argument, a copy of the source tape will be made. -The blocking on the -destination tape will be identical to that used on the source tape. -Copying -a tape will yield the same program output as if just printing the sizes. +is a tape device, the file and record structure will be the same. +.Pp +If +.Ar dest +is a filename ending in +.Dq Cm .000 +the contents each file on +.Ar src +will be written to sequentially numbered files +.Dq Cm .000 , +.Dq Cm .001 , +.Dq Cm .002 +etc. +Information about record sizes will be lost. +.Pp +If the +.Fl r +flag is specified, only the data will be written, information about +file and record layout is lost. +.Pp +Otherwise the data, file and record structure of +.Ar src +will be written in SIMH-TAP format. +.Pp +The +.Nm +utility will report information about the layout of +.Ar src +like this on standard output: +.Bd -literal -offset indent +file 0: block size 80: 6 records +file 0: eof after 6 records: 480 bytes +file 1: block size 3072: records 0 to 262 +file 1: block size 612: record 262 +file 1: eof after 263 records: 805476 bytes +[…] +eot +total length: 972851280 bytes time: 41 s rate: 22934.8 kB/s +.Ed +.Pp +If +.Ar dest +is +.Dq Cm - +or if +.Fl x +is specified this goes to standard error instead, +and can also be redirected with +.Fl l Ar logfile , +in which case the final total line will also be reported on standard error. +.Pp +If +.Nm +receives a +.Dv SIGINFO +signal, current counts are reported on standard error.  .Pp  The following options are available:  .Bl -tag -width ".Fl s Ar maxblk"  .It Fl c -Copy +Rewind both tapes, copy  .Ar src  to -.Ar dest -and then verify that the two tapes are identical. +.Ar dest , +rewind again and verify that the two tapes are now identical. +.It Fl l Ar logfile +Output all informational messages to +.Ar logfile . +.It Fl r +Write only the contents of all data blocks to the output. +The file and record structure of the input will be lost.  .It Fl s Ar maxblk  Specify a maximum block size,  .Ar maxblk . +The default is +.Va kern.maxphys .  .It Fl v -Given the two tapes +Verify that  .Ar src  and -.Ar dest , -verify that they are identical. +.Ar dest +are identical. +Note that the tapes are not rewound prior to the comparison.  .It Fl x  Output all informational messages to the standard error  instead of the standard output. -This option is useful when +This option is automatic if  .Ar dest  is given as -.Pa /dev/stdout . +.Dq Cm - .  .El +.Sh EXIT STATUS +Unfortunately all over the place, but zero always means succeess. +.Sh EXAMPLES +Verify that the tape in /dev/sa0 can be read and see the layout +of its content: +.Bd -literal -offset indent +tcopy +.Ed +.Pp +Copy a tape using two tape drives: +.Bd -literal -offset indent +tcopy  /dev/sa0 /dev/sa1 +.Ed +.Pp +Copy a tape using only a single tape drive, and verify the result: +.Bd -literal -offset indent +tcopy /dev/sa0 /tmp/temp.tapfile +# change tape +tcopy -c /tmp/temp.tapfile /dev/sa0 +.Ed +.Pp +Make a cryptographic hash of both the contents and the layout of the tape in +/dev/sa1: +.Pp +.Bd -literal -offset indent +tcopy /dev/sa1 - | sha256 +.Ed +.Pp +Copy a tape to a tape drive on another machine: +.Bd -literal -offset indent +tcopy /dev/sa0 - | ssh otherhost tcopy - /dev/sa0 +.Ed +.Pp +Extract the tape files into individual files: +.Bd -literal -offset indent +tcopy /dev/sa0 /tmp/_.tape.000 +.Ed +.Pp +Ignore all structure on the tape and feed all data to +.Xr tar 1 : +.Bd -literal -offset indent +tcopy -l /dev/null -r /dev/sa0 - | tar tvf - +.Ed  .Sh SEE ALSO  .Xr mt 1 , +.Xr sa 4 ,  .Xr mtio 4 +.Sh STANDARDS +The SIMH-TAP format is documented in the open-simh github repos: +.Pa https://github.com/open-simh/simh/blob/master/doc/simh_magtape.doc  .Sh HISTORY  The  .Nm @@ -99,19 +223,16 @@ command appeared in  .It  Modern tape drives may return a SCSI "Incorrect Length Indicator (ILI)"  for each read with a different block size that what is on the -tape, and that slows things down a lot. +tape, and that slows +.Nm +down a lot.  This can be disabled with the  .Xr mt 1  command:  .Bd -literal -offset indent -$ mt param sili -s 1 +mt param sili -s 1  .Ed  .It -Writing an image of a tape to a file does not preserve much more than -the raw data. -Block size(s) and tape EOF marks are lost which would -otherwise be preserved in a tape-to-tape copy. -.It  End of data (EOD) is determined by two sequential EOF marks  with no data between them.  There used to be old systems which typically wrote three EOF's between tape @@ -120,13 +241,7 @@ The  .Nm  utility will erroneously stop copying early in this case.  .It -When using the copy/verify option -.Fl c , -.Nm -does not rewind the tapes prior to start. -A rewind is performed -after writing, prior to the verification stage. -If one does not start -at the beginning-of-tape (BOT) then the comparison -may not be of the intended data. +With +.Fl c +the tape drives are not rewound at the same time, but one after the other.  .El diff --git a/usr.bin/tcopy/tcopy.c b/usr.bin/tcopy/tcopy.c deleted file mode 100644 index 39eae4126324..000000000000 --- a/usr.bin/tcopy/tcopy.c +++ /dev/null @@ -1,338 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-3-Clause - * - * Copyright (c) 1985, 1987, 1993 - *	The Regents of the University of California.  All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - *    notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - *    notice, this list of conditions and the following disclaimer in the - *    documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - *    may be used to endorse or promote products derived from this software - *    without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/ioctl.h> -#include <sys/mtio.h> - -#include <err.h> -#include <errno.h> -#include <fcntl.h> -#include <libutil.h> -#include <paths.h> -#include <sys/sysctl.h> -#include <signal.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#define	MAXREC	(64 * 1024) -#define	NOCOUNT	(-2) - -static int	filen, guesslen, maxblk = MAXREC; -static uint64_t	lastrec, record, size, tsize; -static FILE	*msg; - -static void	*getspace(int); -static void	 intr(int); -static void	 usage(void) __dead2; -static void	 verify(int, int, char *); -static void	 writeop(int, int); -static void	 rewind_tape(int); - -int -main(int argc, char *argv[]) -{ -	int lastnread, nread, nw, inp, outp; -	enum {READ, VERIFY, COPY, COPYVERIFY} op = READ; -	sig_t oldsig; -	int ch, needeof; -	char *buff; -	const char *inf; -	unsigned long maxphys = 0; -	size_t l_maxphys = sizeof maxphys; -	uint64_t tmp; - -	if (!sysctlbyname("kern.maxphys", &maxphys, &l_maxphys, NULL, 0)) -		maxblk = maxphys; - -	msg = stdout; -	guesslen = 1; -	outp = -1; -	while ((ch = getopt(argc, argv, "cs:vx")) != -1) -		switch((char)ch) { -		case 'c': -			op = COPYVERIFY; -			break; -		case 's': -			if (expand_number(optarg, &tmp)) { -				warnx("illegal block size"); -				usage(); -			} -			maxblk = tmp; -			if (maxblk == 0) { -				warnx("illegal block size"); -				usage(); -			} -			guesslen = 0; -			break; -		case 'v': -			op = VERIFY; -			break; -		case 'x': -			msg = stderr; -			break; -		case '?': -		default: -			usage(); -		} -	argc -= optind; -	argv += optind; - -	switch(argc) { -	case 0: -		if (op != READ) -			usage(); -		inf = _PATH_DEFTAPE; -		break; -	case 1: -		if (op != READ) -			usage(); -		inf = argv[0]; -		break; -	case 2: -		if (op == READ) -			op = COPY; -		inf = argv[0]; -		if ((outp = open(argv[1], op == VERIFY ? O_RDONLY : -		    op == COPY ? O_WRONLY : O_RDWR, DEFFILEMODE)) < 0) -			err(3, "%s", argv[1]); -		break; -	default: -		usage(); -	} - -	if ((inp = open(inf, O_RDONLY, 0)) < 0) -		err(1, "%s", inf); - -	buff = getspace(maxblk); - -	if (op == VERIFY) { -		verify(inp, outp, buff); -		exit(0); -	} - -	if ((oldsig = signal(SIGINT, SIG_IGN)) != SIG_IGN) -		(void) signal(SIGINT, intr); - -	needeof = 0; -	for (lastnread = NOCOUNT;;) { -		if ((nread = read(inp, buff, maxblk)) == -1) { -			while (errno == EINVAL && (maxblk -= 1024)) { -				nread = read(inp, buff, maxblk); -				if (nread >= 0) -					goto r1; -			} -			err(1, "read error, file %d, record %ju", filen, (intmax_t)record); -		} else if (nread != lastnread) { -			if (lastnread != 0 && lastnread != NOCOUNT) { -				if (lastrec == 0 && nread == 0) -					fprintf(msg, "%ju records\n", (intmax_t)record); -				else if (record - lastrec > 1) -					fprintf(msg, "records %ju to %ju\n", -					    (intmax_t)lastrec, (intmax_t)record); -				else -					fprintf(msg, "record %ju\n", (intmax_t)lastrec); -			} -			if (nread != 0) -				fprintf(msg, "file %d: block size %d: ", -				    filen, nread); -			(void) fflush(stdout); -			lastrec = record; -		} -r1:		guesslen = 0; -		if (nread > 0) { -			if (op == COPY || op == COPYVERIFY) { -				if (needeof) { -					writeop(outp, MTWEOF); -					needeof = 0; -				} -				nw = write(outp, buff, nread); -				if (nw != nread) { -					if (nw == -1) { -						warn("write error, file %d, record %ju", filen, -						    (intmax_t)record); -					} else { -						warnx("write error, file %d, record %ju", filen, -						    (intmax_t)record); -						warnx("write (%d) != read (%d)", nw, nread); -					} -					errx(5, "copy aborted"); -				} -			} -			size += nread; -			record++; -		} else { -			if (lastnread <= 0 && lastnread != NOCOUNT) { -				fprintf(msg, "eot\n"); -				break; -			} -			fprintf(msg, -			    "file %d: eof after %ju records: %ju bytes\n", -			    filen, (intmax_t)record, (intmax_t)size); -			needeof = 1; -			filen++; -			tsize += size; -			size = record = lastrec = 0; -			lastnread = 0; -		} -		lastnread = nread; -	} -	fprintf(msg, "total length: %ju bytes\n", (intmax_t)tsize); -	(void)signal(SIGINT, oldsig); -	if (op == COPY || op == COPYVERIFY) { -		writeop(outp, MTWEOF); -		writeop(outp, MTWEOF); -		if (op == COPYVERIFY) { -			rewind_tape(outp); -			rewind_tape(inp); -			verify(inp, outp, buff); -		} -	} -	exit(0); -} - -static void -verify(int inp, int outp, char *outb) -{ -	int eot, inmaxblk, inn, outmaxblk, outn; -	char *inb; - -	inb = getspace(maxblk); -	inmaxblk = outmaxblk = maxblk; -	for (eot = 0;; guesslen = 0) { -		if ((inn = read(inp, inb, inmaxblk)) == -1) { -			if (guesslen) -				while (errno == EINVAL && (inmaxblk -= 1024)) { -					inn = read(inp, inb, inmaxblk); -					if (inn >= 0) -						goto r1; -				} -			warn("read error"); -			break; -		} -r1:		if ((outn = read(outp, outb, outmaxblk)) == -1) { -			if (guesslen) -				while (errno == EINVAL && (outmaxblk -= 1024)) { -					outn = read(outp, outb, outmaxblk); -					if (outn >= 0) -						goto r2; -				} -			warn("read error"); -			break; -		} -r2:		if (inn != outn) { -			fprintf(msg, -			    "%s: tapes have different block sizes; %d != %d.\n", -			    "tcopy", inn, outn); -			break; -		} -		if (!inn) { -			if (eot++) { -				fprintf(msg, "tcopy: tapes are identical.\n"); -				free(inb); -				return; -			} -		} else { -			if (bcmp(inb, outb, inn)) { -				fprintf(msg, -				    "tcopy: tapes have different data.\n"); -				break; -			} -			eot = 0; -		} -	} -	exit(1); -} - -static void -intr(int signo __unused) -{ -	if (record) { -		if (record - lastrec > 1) -			fprintf(msg, "records %ju to %ju\n", (intmax_t)lastrec, (intmax_t)record); -		else -			fprintf(msg, "record %ju\n", (intmax_t)lastrec); -	} -	fprintf(msg, "interrupt at file %d: record %ju\n", filen, (intmax_t)record); -	fprintf(msg, "total length: %ju bytes\n", (uintmax_t)(tsize + size)); -	exit(1); -} - -static void * -getspace(int blk) -{ -	void *bp; - -	if ((bp = malloc((size_t)blk)) == NULL) -		errx(11, "no memory"); -	return (bp); -} - -static void -writeop(int fd, int type) -{ -	struct mtop op; - -	op.mt_op = type; -	op.mt_count = (daddr_t)1; -	if (ioctl(fd, MTIOCTOP, (char *)&op) < 0) -		err(6, "tape op"); -} - -static void -usage(void) -{ -	fprintf(stderr, "usage: tcopy [-cvx] [-s maxblk] [src [dest]]\n"); -	exit(1); -} - -static void -rewind_tape(int fd) -{ -	struct stat sp; - -	if(fstat(fd, &sp)) -		errx(12, "fstat in rewind"); - -	/* -	 * don't want to do tape ioctl on regular files: -	 */ -	if( S_ISREG(sp.st_mode) ) { -		if( lseek(fd, 0, SEEK_SET) == -1 ) -			errx(13, "lseek"); -	} else -		/*  assume its a tape	*/ -		writeop(fd, MTREW); -} diff --git a/usr.bin/tcopy/tcopy.cc b/usr.bin/tcopy/tcopy.cc new file mode 100644 index 000000000000..a1dd35682aac --- /dev/null +++ b/usr.bin/tcopy/tcopy.cc @@ -0,0 +1,839 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Poul-Henning Kamp, <phk@FreeBSD.org> + * Copyright (c) 1985, 1987, 1993 + *	The Regents of the University of California.  All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sysexits.h> +#include <unistd.h> + +#include <sys/endian.h> +#include <sys/mtio.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/uio.h> + +#include <libutil.h> + +#define	MAXREC	(1024 * 1024) +#define	NOCOUNT	(-2) + +enum operation {READ, VERIFY, COPY, COPYVERIFY}; + +// Stuff the tape_devs need to know about +static int filen; +static uint64_t	record; + +//--------------------------------------------------------------------- + +class tape_dev { +	size_t	max_read_size; +public: +	int fd; +	char *name; +	enum direction {SRC, DST} direction; + +	tape_dev(int file_handle, const char *spec, bool destination); + +	virtual ssize_t read_blk(void *dst, size_t len); +	virtual ssize_t verify_blk(void *dst, size_t len, size_t expected); +	virtual void write_blk(const void *src, size_t len); +	virtual void file_mark(void); +	virtual void rewind(void); +}; + +tape_dev::tape_dev(int file_handle, const char *spec, bool destination) +{ +	assert(file_handle >= 0); +	fd = file_handle; +	name = strdup(spec); +	assert(name != NULL); +	direction = destination ? DST : SRC; +	max_read_size = 0; +} + +ssize_t +tape_dev::read_blk(void *dst, size_t len) +{ +	ssize_t retval = -1; + +	if (max_read_size == 0) { +		max_read_size = len; +		while (max_read_size > 0) { +			retval = read(fd, dst, max_read_size); +			if (retval >= 0 || (errno != EINVAL && errno != EFBIG)) +				break; +			if (max_read_size < 512) +				errx(1, "Cannot find a sane max blocksize"); + +			// Reduce to next lower power of two +			int i = flsl((long)max_read_size - 1L); +			max_read_size = 1UL << (i - 1); +		} +	} else { +		retval = read(fd, dst, (size_t)max_read_size); +	} +	if (retval < 0) { +		err(1, "read error, %s, file %d, record %ju", +		    name, filen, (uintmax_t)record); +	} +	return (retval); +} + +ssize_t +tape_dev::verify_blk(void *dst, size_t len, size_t expected) +{ +	(void)expected; +	return read_blk(dst, len); +} + +void +tape_dev::write_blk(const void *src, size_t len) +{ +	assert(len > 0); +	ssize_t nwrite = write(fd, src, len); +	if (nwrite < 0 || (size_t) nwrite != len) { +		if (nwrite == -1) { +			warn("write error, file %d, record %ju", +			    filen, (intmax_t)record); +		} else { +			warnx("write error, file %d, record %ju", +			    filen, (intmax_t)record); +			warnx("write (%zd) != read (%zd)", nwrite, len); +		} +		errx(5, "copy aborted"); +	} +	return; +} + +void +tape_dev::file_mark(void) +{ +	struct mtop op; + +	op.mt_op = MTWEOF; +	op.mt_count = (daddr_t)1; +	if (ioctl(fd, MTIOCTOP, (char *)&op) < 0) +		err(6, "tape op (write file mark)"); +} + +void +tape_dev::rewind(void) +{ +	struct mtop op; + +	op.mt_op = MTREW; +	op.mt_count = (daddr_t)1; +	if (ioctl(fd, MTIOCTOP, (char *)&op) < 0) +		err(6, "tape op (rewind)"); +} + +//--------------------------------------------------------------------- + +class tap_file: public tape_dev { +public: +	tap_file(int file_handle, const char *spec, bool dst) : +		tape_dev(file_handle, spec, dst) {}; +	ssize_t read_blk(void *dst, size_t len); +	void write_blk(const void *src, size_t len); +	void file_mark(void); +	virtual void rewind(void); +}; + +static +ssize_t full_read(int fd, void *dst, size_t len) +{ +	// Input may be a socket which returns partial reads + +	ssize_t retval = read(fd, dst, len); +	if (retval <= 0 || (size_t)retval == len) +		return (retval); + +	char *ptr = (char *)dst + retval; +	size_t left = len - (size_t)retval; +	while (left > 0) { +		retval = read(fd, ptr, left); +		if (retval <= 0) +			return (retval); +		left -= (size_t)retval; +		ptr += retval; +	} +	return ((ssize_t)len); +} + +ssize_t +tap_file::read_blk(void *dst, size_t len) +{ +	char lbuf[4]; + +	ssize_t nread = full_read(fd, lbuf, sizeof lbuf); +	if (nread == 0) +		return (0); + +	if ((size_t)nread != sizeof lbuf) +		err(EX_DATAERR, "Corrupt tap-file, read hdr1=%zd", nread); + +	uint32_t u = le32dec(lbuf); +	if (u == 0 || (u >> 24) == 0xff) +		return(0); + +	if (u > len) +		err(17, "tapfile blocksize too big, 0x%08x", u); + +	size_t alen = (u + 1) & ~1; +	assert (alen <= len); + +	ssize_t retval = full_read(fd, dst, alen); +	if (retval < 0 || (size_t)retval != alen) +		err(EX_DATAERR, "Corrupt tap-file, read data=%zd", retval); + +	nread = full_read(fd, lbuf, sizeof lbuf); +	if ((size_t)nread != sizeof lbuf) +		err(EX_DATAERR, "Corrupt tap-file, read hdr2=%zd", nread); + +	uint32_t v = le32dec(lbuf); +	if (u == v) +		return (u); +	err(EX_DATAERR, +	    "Corrupt tap-file, headers differ (0x%08x != 0x%08x)", u, v); +} + +void +tap_file::write_blk(const void *src, size_t len) +{ +	struct iovec iov[4]; +	uint8_t zero = 0; +	int niov = 0; +	size_t expect = 0; +	char tbuf[4]; + +	assert((len & ~0xffffffffULL) == 0); +	le32enc(tbuf, (uint32_t)len); + +	iov[niov].iov_base = tbuf; +	iov[niov].iov_len = sizeof tbuf; +	expect += iov[niov].iov_len; +	niov += 1; + +	iov[niov].iov_base = (void*)(uintptr_t)src; +	iov[niov].iov_len = len; +	expect += iov[niov].iov_len; +	niov += 1; + +	if (len & 1) { +		iov[niov].iov_base = &zero; +		iov[niov].iov_len = 1; +		expect += iov[niov].iov_len; +		niov += 1; +	} + +	iov[niov].iov_base = tbuf; +	iov[niov].iov_len = sizeof tbuf; +	expect += iov[niov].iov_len; +	niov += 1; + +	ssize_t nwrite = writev(fd, iov, niov); +	if (nwrite < 0 || (size_t)nwrite != expect) +		errx(17, "write error (%zd != %zd)", nwrite, expect); +} + +void +tap_file::file_mark(void) +{ +	char tbuf[4]; +	le32enc(tbuf, 0); +	ssize_t nwrite = write(fd, tbuf, sizeof tbuf); +	if ((size_t)nwrite != sizeof tbuf) +		errx(17, "write error (%zd != %zd)", nwrite, sizeof tbuf); +} + +void +tap_file::rewind(void) +{ +	off_t where; +	if (direction == DST) { +		char tbuf[4]; +		le32enc(tbuf, 0xffffffff); +		ssize_t nwrite = write(fd, tbuf, sizeof tbuf); +		if ((size_t)nwrite != sizeof tbuf) +			errx(17, +			    "write error (%zd != %zd)", nwrite, sizeof tbuf); +	} +	where = lseek(fd, 0L, SEEK_SET); +	if (where != 0 && errno == ESPIPE) +		err(EX_USAGE, "Cannot rewind sockets and pipes"); +	if (where != 0) +		err(17, "lseek(0) failed"); +} + +//--------------------------------------------------------------------- + +class file_set: public tape_dev { +public: +	file_set(int file_handle, const char *spec, bool dst) : +		tape_dev(file_handle, spec, dst) {}; +	ssize_t read_blk(void *dst, size_t len); +	ssize_t verify_blk(void *dst, size_t len, size_t expected); +	void write_blk(const void *src, size_t len); +	void file_mark(void); +	void rewind(void); +	void open_next(bool increment); +}; + +void +file_set::open_next(bool increment) +{ +	if (fd >= 0) { +		assert(close(fd) >= 0); +		fd = -1; +	} +	if (increment) { +		char *p = strchr(name, '\0') - 3; +		if (++p[2] == '9') { +			p[2] = '0'; +			if (++p[1] == '9') { +				p[1] = '0'; +				if (++p[0] == '9') { +					errx(EX_USAGE, +					    "file-set sequence overflow"); +				} +			} +		} +	} +	if (direction == DST) { +		fd = open(name, O_RDWR|O_CREAT, DEFFILEMODE); +		if (fd < 0) +			err(1, "Could not open %s", name); +	} else { +		fd = open(name, O_RDONLY, 0); +	} +} + +ssize_t +file_set::read_blk(void *dst, size_t len) +{ +	(void)dst; +	(void)len; +	errx(EX_SOFTWARE, "That was not supposed to happen"); +} + +ssize_t +file_set::verify_blk(void *dst, size_t len, size_t expected) +{ +	(void)len; +	if (fd < 0) +		open_next(true); +	if (fd < 0) +		return (0); +	ssize_t retval = read(fd, dst, expected); +	if (retval == 0) { +		assert(close(fd) >= 0); +		fd = -1; +	} +	return (retval); +} + +void +file_set::write_blk(const void *src, size_t len) +{ +	if (fd < 0) +		open_next(true); +	ssize_t nwrite = write(fd, src, len); +	if (nwrite < 0 || (size_t)nwrite != len) +		errx(17, "write error (%zd != %zd)", nwrite, len); +} + +void +file_set::file_mark(void) +{ +	if (fd < 0) +		return; + +	off_t where = lseek(fd, 0UL, SEEK_CUR); + +	int i = ftruncate(fd, where); +	if (i < 0) +		errx(17, "truncate error, %s to %jd", name, (intmax_t)where); +	assert(close(fd) >= 0); +	fd = -1; +} + +void +file_set::rewind(void) +{ +	char *p = strchr(name, '\0') - 3; +	p[0] = '0'; +	p[1] = '0'; +	p[2] = '0'; +	open_next(false); +} + +//--------------------------------------------------------------------- + +class flat_file: public tape_dev { +public: +	flat_file(int file_handle, const char *spec, bool dst) : +		tape_dev(file_handle, spec, dst) {}; +	ssize_t read_blk(void *dst, size_t len); +	ssize_t verify_blk(void *dst, size_t len, size_t expected); +	void write_blk(const void *src, size_t len); +	void file_mark(void); +	virtual void rewind(void); +}; + +ssize_t +flat_file::read_blk(void *dst, size_t len) +{ +	(void)dst; +	(void)len; +	errx(EX_SOFTWARE, "That was not supposed to happen"); +} + +ssize_t +flat_file::verify_blk(void *dst, size_t len, size_t expected) +{ +	(void)len; +	return (read(fd, dst, expected)); +} + +void +flat_file::write_blk(const void *src, size_t len) +{ +	ssize_t nwrite = write(fd, src, len); +	if (nwrite < 0 || (size_t)nwrite != len) +		errx(17, "write error (%zd != %zd)", nwrite, len); +} + +void +flat_file::file_mark(void) +{ +	return; +} + +void +flat_file::rewind(void) +{ +	errx(EX_SOFTWARE, "That was not supposed to happen"); +} + +//--------------------------------------------------------------------- + +enum e_how {H_INPUT, H_OUTPUT, H_VERIFY}; + +static tape_dev * +open_arg(const char *arg, enum e_how how, int rawfile) +{ +	int fd; + +	if (!strcmp(arg, "-") && how == H_OUTPUT) +		fd = STDOUT_FILENO; +	else if (!strcmp(arg, "-")) +		fd = STDIN_FILENO; +	else if (how == H_OUTPUT) +		fd = open(arg, O_RDWR|O_CREAT, DEFFILEMODE); +	else +		fd = open(arg, O_RDONLY); + +	if (fd < 0) +		err(EX_NOINPUT, "Cannot open %s:", arg); + +	struct mtop mt; +	mt.mt_op = MTNOP; +	mt.mt_count = 1; +	int i = ioctl(fd, MTIOCTOP, &mt); + +	if (i >= 0) +		return (new tape_dev(fd, arg, how == H_OUTPUT)); + +	size_t alen = strlen(arg); +	if (alen >= 5 && !strcmp(arg + (alen - 4), ".000")) { +		if (how == H_INPUT) +			errx(EX_USAGE, +			    "File-sets files cannot be used as source"); +		return (new file_set(fd, arg, how == H_OUTPUT)); +	} + +	if (how != H_INPUT && rawfile) +		return (new flat_file(fd, arg, how == H_OUTPUT)); + +	return (new tap_file(fd, arg, how == H_OUTPUT)); +} + +//--------------------------------------------------------------------- + +static tape_dev *input; +static tape_dev *output; + +static size_t maxblk = MAXREC; +static uint64_t	lastrec, fsize, tsize; +static FILE *msg; +static ssize_t lastnread; +static struct timespec t_start, t_end; + +static void +report_total(FILE *file) +{ +	double dur = (t_end.tv_nsec - t_start.tv_nsec) * 1e-9; +	dur += t_end.tv_sec - t_start.tv_sec; +	uintmax_t tot = tsize + fsize; +	fprintf(file, "total length: %ju bytes", tot); +	fprintf(file, " time: %.0f s", dur); +	tot /= 1024; +	fprintf(file, " rate: %.1f kB/s", (double)tot/dur); +	fprintf(file, "\n"); +} + +static void +sigintr(int signo __unused) +{ +	(void)signo; +	(void)clock_gettime(CLOCK_MONOTONIC, &t_end); +	if (record) { +		if (record - lastrec > 1) +			fprintf(msg, "records %ju to %ju\n", +			    (intmax_t)lastrec, (intmax_t)record); +		else +			fprintf(msg, "record %ju\n", (intmax_t)lastrec); +	} +	fprintf(msg, "interrupt at file %d: record %ju\n", +	    filen, (uintmax_t)record); +	report_total(msg); +	exit(1); +} + +#ifdef SIGINFO +static volatile sig_atomic_t want_info; + +static void +siginfo(int signo) +{ +	(void)signo; +	want_info = 1; +} + +static void +check_want_info(void) +{ +	if (want_info) { +		(void)clock_gettime(CLOCK_MONOTONIC, &t_end); +		fprintf(stderr, "tcopy: file %d record %ju ", +		    filen, (uintmax_t)record); +		report_total(stderr); +		want_info = 0; +	} +} + +#else /* !SIGINFO */ + +static void +check_want_info(void) +{ +} + +#endif + +static char * +getspace(size_t blk) +{ +	void *bp; + +	assert(blk > 0); +	if ((bp = malloc(blk)) == NULL) +		errx(11, "no memory"); +	return ((char *)bp); +} + +static void +usage(void) +{ +	fprintf(stderr, +            "usage: tcopy [-crvx] [-l logfile] [-s maxblk] [src [dest]]\n" +        ); +	exit(1); +} + +static void +progress(ssize_t nread) +{ +	if (nread != lastnread) { +		if (lastnread != 0 && lastnread != NOCOUNT) { +			if (lastrec == 0 && nread == 0) +				fprintf(msg, "%ju records\n", +				    (uintmax_t)record); +			else if (record - lastrec > 1) +				fprintf(msg, "records %ju to %ju\n", +				    (uintmax_t)lastrec, +				    (uintmax_t)record); +			else +				fprintf(msg, "record %ju\n", +				    (uintmax_t)lastrec); +		} +		if (nread != 0) +			fprintf(msg, +			    "file %d: block size %zd: ", filen, nread); +		(void) fflush(msg); +		lastrec = record; +	} +	if (nread > 0) { +		fsize += (size_t)nread; +		record++; +	} else { +		if (lastnread <= 0 && lastnread != NOCOUNT) { +			fprintf(msg, "eot\n"); +			return; +		} +		fprintf(msg, +		    "file %d: eof after %ju records: %ju bytes\n", +		    filen, (uintmax_t)record, (uintmax_t)fsize); +		filen++; +		tsize += fsize; +		fsize = record = lastrec = 0; +		lastnread = 0; +	} +	lastnread = nread; +} + +static void +read_or_copy(void) +{ +	int needeof; +	ssize_t nread, prev_read; +	char *buff = getspace(maxblk); + +	(void)clock_gettime(CLOCK_MONOTONIC, &t_start); +	needeof = 0; +	for (prev_read = NOCOUNT;;) { +		check_want_info(); +		nread = input->read_blk(buff, maxblk); +		progress(nread); +		if (nread > 0) { +			if (output != NULL) { +				if (needeof) { +					output->file_mark(); +					needeof = 0; +				} +				output->write_blk(buff, (size_t)nread); +			} +		} else { +			if (prev_read <= 0 && prev_read != NOCOUNT) { +				break; +			} +			needeof = 1; +		} +		prev_read = nread; +	} +	(void)clock_gettime(CLOCK_MONOTONIC, &t_end); +	report_total(msg); +	free(buff); +} + +static void +verify(void) +{ +	char *buf1 = getspace(maxblk); +	char *buf2 = getspace(maxblk); +	int eot = 0; +	ssize_t nread1, nread2; +	filen = 0; +	tsize = 0; + +	assert(output != NULL); +	(void)clock_gettime(CLOCK_MONOTONIC, &t_start); + +	while (1) { +		check_want_info(); +		nread1 = input->read_blk(buf1, (size_t)maxblk); +		nread2 = output->verify_blk(buf2, maxblk, (size_t)nread1); +		progress(nread1); +		if (nread1 != nread2) { +			fprintf(msg, +			    "tcopy: tapes have different block sizes; " +			    "%zd != %zd.\n", nread1, nread2); +			exit(1); +		} +		if (nread1 > 0 && memcmp(buf1, buf2, (size_t)nread1)) { +			fprintf(msg, "tcopy: tapes have different data.\n"); +			exit(1); +		} else if (nread1 > 0) { +			eot = 0; +		} else if (eot++) { +			break; +		} +	} +	(void)clock_gettime(CLOCK_MONOTONIC, &t_end); +	report_total(msg); +	fprintf(msg, "tcopy: tapes are identical.\n"); +	fprintf(msg, "rewinding\n"); +	input->rewind(); +	output->rewind(); + +	free(buf1); +	free(buf2); +} + +int +main(int argc, char *argv[]) +{ +	enum operation op = READ; +	int ch; +	unsigned long maxphys = 0; +	size_t l_maxphys = sizeof maxphys; +	int64_t tmp; +	int rawfile = 0; + +	setbuf(stderr, NULL); + +	if (!sysctlbyname("kern.maxphys", &maxphys, &l_maxphys, NULL, 0UL)) +		maxblk = maxphys; + +	msg = stdout; +	while ((ch = getopt(argc, argv, "cl:rs:vx")) != -1) +		switch((char)ch) { +		case 'c': +			op = COPYVERIFY; +			break; +		case 'l': +			msg = fopen(optarg, "w"); +			if (msg == NULL) +				errx(EX_CANTCREAT, "Cannot open %s", optarg); +			setbuf(msg, NULL); +			break; +		case 'r': +			rawfile = 1; +			break; +		case 's': +			if (expand_number(optarg, &tmp)) { +				warnx("illegal block size"); +				usage(); +			} +			if (tmp <= 0) { +				warnx("illegal block size"); +				usage(); +			} +			maxblk = tmp; +			break; +		case 'v': +			op = VERIFY; +			break; +		case 'x': +			if (msg == stdout) +				msg = stderr; +			break; +		case '?': +		default: +			usage(); +		} +	argc -= optind; +	argv += optind; + +	switch(argc) { +	case 0: +		if (op != READ) +			usage(); +		break; +	case 1: +		if (op != READ) +			usage(); +		break; +	case 2: +		if (op == READ) +			op = COPY; +		if (!strcmp(argv[1], "-")) { +			if (op == COPYVERIFY) +				errx(EX_USAGE, +				    "Cannot copy+verify with '-' destination"); +			if (msg == stdout) +				msg = stderr; +		} +		if (op == VERIFY) +			output = open_arg(argv[1], H_VERIFY, 0); +		else +			output = open_arg(argv[1], H_OUTPUT, rawfile); +		break; +	default: +		usage(); +	} + +	if (argc == 0) { +		input = open_arg(_PATH_DEFTAPE, H_INPUT, 0); +	} else { +		input = open_arg(argv[0], H_INPUT, 0); +	} + +	if ((signal(SIGINT, SIG_IGN)) != SIG_IGN) +		(void) signal(SIGINT, sigintr); + +#ifdef SIGINFO +	(void)signal(SIGINFO, siginfo); +#endif + +	if (op != VERIFY) { +		if (op == COPYVERIFY) { +			assert(output != NULL); +			fprintf(msg, "rewinding\n"); +			input->rewind(); +			output->rewind(); +		} + +		read_or_copy(); + +		if (op == COPY || op == COPYVERIFY) { +			assert(output != NULL); +			output->file_mark(); +			output->file_mark(); +		} +	} + +	if (op == VERIFY || op == COPYVERIFY) { + +		if (op == COPYVERIFY) { +			assert(output != NULL); +			fprintf(msg, "rewinding\n"); +			input->rewind(); +			output->rewind(); +			input->direction = tape_dev::SRC; +			output->direction = tape_dev::SRC; +		} + +		verify(); +	} + +	if (msg != stderr && msg != stdout) +		report_total(stderr); + +	return(0); +} diff --git a/usr.bin/top/machine.c b/usr.bin/top/machine.c index 8c035b5df383..1f70ee9281e8 100644 --- a/usr.bin/top/machine.c +++ b/usr.bin/top/machine.c @@ -190,15 +190,6 @@ static int pageshift;		/* log base 2 of the pagesize */  #define ki_swap(kip) \      ((kip)->ki_swrss > (kip)->ki_rssize ? (kip)->ki_swrss - (kip)->ki_rssize : 0) -/* - * Sorting orders.  The first element is the default. - */ -static const char *ordernames[] = { -	"cpu", "size", "res", "time", "pri", "threads", -	"total", "read", "write", "fault", "vcsw", "ivcsw", -	"jid", "swap", "pid", NULL -}; -  /* Per-cpu time states */  static int maxcpu;  static int maxid; @@ -214,6 +205,18 @@ static int *pcpu_cpu_states;  static int battery_units;  static int battery_life; +static int compare_cpu(const void *a, const void *b); +static int compare_size(const void *a, const void *b); +static int compare_res(const void *a, const void *b); +static int compare_time(const void *a, const void *b); +static int compare_prio(const void *a, const void *b); +static int compare_threads(const void *a, const void *b); +static int compare_iototal(const void *a, const void *b); +static int compare_ioread(const void *a, const void *b); +static int compare_iowrite(const void *a, const void *b); +static int compare_iofault(const void *a, const void *b); +static int compare_vcsw(const void *a, const void *b); +static int compare_ivcsw(const void *a, const void *b);  static int compare_swap(const void *a, const void *b);  static int compare_jid(const void *a, const void *b);  static int compare_pid(const void *a, const void *b); @@ -225,6 +228,77 @@ static void update_layout(void);  static int find_uid(uid_t needle, int *haystack);  static int cmd_matches(struct kinfo_proc *, const char *); +/* + * Sorting orders.  The first element is the default. + */ + +typedef int (compare_fn)(const void *arg1, const void *arg2); +static const struct sort_info { +	const char	*si_name; +	compare_fn	*si_compare; +} sortdata[] = { +	{ +		.si_name = "cpu", +		.si_compare = &compare_cpu, +	}, +	{ +		.si_name = "size", +		.si_compare = &compare_size, +	}, +	{ +		.si_name = "res", +		.si_compare = &compare_res, +	}, +	{ +		.si_name = "time", +		.si_compare = &compare_time, +	}, +	{ +		.si_name = "pri", +		.si_compare = &compare_prio, +	}, +	{ +		.si_name = "threads", +		.si_compare = &compare_threads, +	}, +	{ +		.si_name = "total", +		.si_compare = &compare_iototal, +	}, +	{ +		.si_name = "read", +		.si_compare = &compare_ioread, +	}, +	{ +		.si_name = "write", +		.si_compare = &compare_iowrite, +	}, +	{ +		.si_name = "fault", +		.si_compare = &compare_iofault, +	}, +	{ +		.si_name = "vcsw", +		.si_compare = &compare_vcsw, +	}, +	{ +		.si_name = "ivcsw", +		.si_compare = &compare_ivcsw, +	}, +	{ +		.si_name = "jid", +		.si_compare = &compare_jid, +	}, +	{ +		.si_name = "swap", +		.si_compare = &compare_swap, +	}, +	{ +		.si_name = "pid", +		.si_compare = &compare_pid, +	}, +}; +  static int  find_uid(uid_t needle, int *haystack)  { @@ -353,7 +427,6 @@ machine_init(struct statics *statics)  		statics->swap_names = swapnames;  	else  		statics->swap_names = NULL; -	statics->order_names = ordernames;  	/* Allocate state for per-CPU stats. */  	GETSYSCTL("kern.smp.maxcpus", maxcpu); @@ -742,7 +815,7 @@ static struct handle handle;  void *  get_process_info(struct system_info *si, struct process_select *sel, -    int (*compare)(const void *, const void *)) +    const struct sort_info *sort_info)  {  	int i;  	int total_procs; @@ -753,6 +826,9 @@ get_process_info(struct system_info *si, struct process_select *sel,  	struct kinfo_proc **prefp;  	struct kinfo_proc *pp;  	struct timespec previous_proc_uptime; +	compare_fn *compare; + +	compare = sort_info->si_compare;  	/*  	 * If thread state was toggled, don't cache the previous processes. @@ -899,6 +975,43 @@ get_process_info(struct system_info *si, struct process_select *sel,  	return (&handle);  } +/* + * Returns the sort info associated with the specified order.  Currently, that's + * really only the comparator that we'll later use.  Specifying a NULL ordername + * will return the default comparator. + */ +const struct sort_info * +get_sort_info(const char *ordername) +{ +	const struct sort_info *info; +	size_t idx; + +	if (ordername == NULL) +		return (&sortdata[0]); + +	for (idx = 0; idx < nitems(sortdata); idx++) { +		info = &sortdata[idx]; + +		if (strcmp(info->si_name, ordername) == 0) +			return (info); +	} + +	return (NULL); +} + +void +dump_sort_names(FILE *fp) +{ +	const struct sort_info *info; +	size_t idx; + +	for (idx = 0; idx < nitems(sortdata); idx++) { +		info = &sortdata[idx]; + +		fprintf(fp, " %s", info->si_name); +	} +} +  static int  cmd_matches(struct kinfo_proc *proc, const char *term)  { @@ -1559,26 +1672,6 @@ compare_ivcsw(const void *arg1, const void *arg2)  	return (flp2 - flp1);  } -int (*compares[])(const void *arg1, const void *arg2) = { -	compare_cpu, -	compare_size, -	compare_res, -	compare_time, -	compare_prio, -	compare_threads, -	compare_iototal, -	compare_ioread, -	compare_iowrite, -	compare_iofault, -	compare_vcsw, -	compare_ivcsw, -	compare_jid, -	compare_swap, -	compare_pid, -	NULL -}; - -  static int  swapmode(int *retavail, int *retfree)  { diff --git a/usr.bin/top/machine.h b/usr.bin/top/machine.h index 57f2846cdba5..b4f2f1a8bd50 100644 --- a/usr.bin/top/machine.h +++ b/usr.bin/top/machine.h @@ -89,10 +89,15 @@ void	 get_system_info(struct system_info *si);  int	 machine_init(struct statics *statics);  /* non-int routines typically used by the machine dependent module */ +struct sort_info; +  extern struct process_select ps;  void *  get_process_info(struct system_info *si, struct process_select *sel, -    int (*compare)(const void *, const void *)); +    const struct sort_info *); + +const struct sort_info *get_sort_info(const char *name); +void dump_sort_names(FILE *fp);  #endif /* MACHINE_H */ diff --git a/usr.bin/top/top.1 b/usr.bin/top/top.1 index d8ef763e7a34..9b1860246de9 100644 --- a/usr.bin/top/top.1 +++ b/usr.bin/top/top.1 @@ -1,4 +1,4 @@ -.Dd April 1, 2025 +.Dd June 9, 2025  .Dt TOP 1  .Os  .Sh NAME @@ -189,7 +189,7 @@ This option makes them visible.  Set the delay between screen updates to  .Ar time  seconds, which may be fractional. -The default delay between updates is 1 second. +The default delay between updates is 2 seconds.  .It Fl T  Toggle displaying thread ID (tid) instead of process id (pid).  .It Fl t @@ -398,6 +398,7 @@ ID corresponding to the process,  USERNAME is the name of the process's owner (if  .Fl u  is specified, a UID column will be substituted for USERNAME), +THR is the thread count, showing the number of threads a process has,  PRI is the current priority of the process,  NICE is the  .Xr nice 1 diff --git a/usr.bin/top/top.c b/usr.bin/top/top.c index 856ad838dc1c..f0458a4037af 100644 --- a/usr.bin/top/top.c +++ b/usr.bin/top/top.c @@ -252,7 +252,7 @@ main(int argc, const char *argv[])      char no_command = 1;      struct timeval timeout;      char *order_name = NULL; -    int order_index = 0; +    const struct sort_info *sort_info = NULL;      fd_set readfds;  	char *nptr; @@ -505,21 +505,18 @@ main(int argc, const char *argv[])      /* determine sorting order index, if necessary */      if (order_name != NULL)      { -	if ((order_index = string_index(order_name, statics.order_names)) == -1) -	{ -	    const char * const *pp; - +	if ((sort_info = get_sort_info(order_name)) == NULL) {  	    warnx("'%s' is not a recognized sorting order.", order_name);  	    fprintf(stderr, "\tTry one of these:"); -	    pp = statics.order_names; -	    while (*pp != NULL) -	    { -		fprintf(stderr, " %s", *pp++); -	    } +	    dump_sort_names(stderr);  	    fputc('\n', stderr);  	    exit(1);  	}      } +    else +    { +	sort_info = get_sort_info(NULL); +    }      /* initialize termcap */      init_termcap(interactive); @@ -602,17 +599,13 @@ restart:      while ((displays == -1) || (displays-- > 0))      { -	int (*compare)(const void * const, const void * const); -  	/* get the current stats */  	get_system_info(&system_info); -	compare = compares[order_index]; -  	/* get the current set of processes */  	processes = -		get_process_info(&system_info, &ps, compare); +		get_process_info(&system_info, &ps, sort_info);  	/* display the load averages */  	(*d_loadave)(system_info.last_pid, @@ -1047,7 +1040,9 @@ restart:  				    "Order to sort: ");  				if (readline(tempbuf2, sizeof(tempbuf2), false) > 0)  				{ -				  if ((i = string_index(tempbuf2, statics.order_names)) == -1) +				  const struct sort_info *new_sort_info; + +				  if ((new_sort_info = get_sort_info(tempbuf2)) == NULL)  					{  					  new_message(MT_standout,  					      " %s: unrecognized sorting order", tempbuf2); @@ -1055,7 +1050,7 @@ restart:  				    }  				    else  				    { -					order_index = i; +					sort_info = new_sort_info;  				    }  				    putchar('\r');  				} diff --git a/usr.bin/top/utils.c b/usr.bin/top/utils.c index cde89a211b53..a8ddb3eb63a0 100644 --- a/usr.bin/top/utils.c +++ b/usr.bin/top/utils.c @@ -117,27 +117,6 @@ digits(int val)  }  /* - * string_index(string, array) - find string in array and return index - */ - -int -string_index(const char *string, const char * const *array) -{ -    size_t i = 0; - -    while (*array != NULL) -    { -	if (strcmp(string, *array) == 0) -	{ -	    return(i); -	} -	array++; -	i++; -    } -    return(-1); -} - -/*   * argparse(line, cntp) - parse arguments in string "line", separating them   *	out into an argv-like array, and setting *cntp to the number of   *	arguments encountered.  This is a simple parser that doesn't understand diff --git a/usr.bin/top/utils.h b/usr.bin/top/utils.h index a730e339d200..bbc63803b6e1 100644 --- a/usr.bin/top/utils.h +++ b/usr.bin/top/utils.h @@ -19,6 +19,5 @@ const char **argparse(char *, int *);  long percentages(int, int *, long *, long *, long *);  const char *format_time(long);  char *format_k(int64_t); -int string_index(const char *string, const char * const *array);  int find_pid(pid_t pid); diff --git a/usr.bin/unzip/Makefile b/usr.bin/unzip/Makefile index 2db5e9ac4c99..63f49a203685 100644 --- a/usr.bin/unzip/Makefile +++ b/usr.bin/unzip/Makefile @@ -12,7 +12,7 @@ BSDUNZIP_VERSION_STRING!= sed -n '/define.*ARCHIVE_VERSION_ONLY_STRING/{s,[^0-9.  SRCS=	bsdunzip.c  .PATH:	${_LIBARCHIVEDIR}/libarchive_fe -SRCS+=	cmdline.c err.c passphrase.c +SRCS+=	cmdline.c lafe_err.c passphrase.c  CFLAGS+= -DBSDUNZIP_VERSION_STRING=\"${BSDUNZIP_VERSION_STRING}\"  CFLAGS+= -DPLATFORM_CONFIG_H=\"${_LIBARCHIVECONFDIR}/config_freebsd.h\" diff --git a/usr.bin/unzip/tests/Makefile b/usr.bin/unzip/tests/Makefile index 404a546410e4..fada172b1bd7 100644 --- a/usr.bin/unzip/tests/Makefile +++ b/usr.bin/unzip/tests/Makefile @@ -23,7 +23,7 @@ CFLAGS+=	-I${_LIBARCHIVEDIR}/libarchive_fe -I${_LIBARCHIVEDIR}/test_utils  CFLAGS.test_utils.c+=	-Wno-cast-align  .PATH:	${_LIBARCHIVEDIR}/libarchive_fe -UNZIP_SRCS+= err.c +UNZIP_SRCS+= lafe_err.c  .PATH:	${_LIBARCHIVEDIR}/unzip/test  TESTS_SRCS=	\ diff --git a/usr.bin/vmstat/vmstat.8 b/usr.bin/vmstat/vmstat.8 index de4176c9361c..80facb05cc35 100644 --- a/usr.bin/vmstat/vmstat.8 +++ b/usr.bin/vmstat/vmstat.8 @@ -25,7 +25,7 @@  .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF  .\" SUCH DAMAGE.  .\" -.Dd June 21, 2021 +.Dd July 16, 2025  .Dt VMSTAT 8  .Os  .Sh NAME @@ -71,7 +71,7 @@ Generate output via  .Xr libxo 3  in a selection of different human and machine readable formats.  See -.Xr xo_parse_args 3 +.Xr xo_options 7  for details on command line arguments.  .It Fl a  When used with @@ -371,7 +371,7 @@ statistics every second.  .Xr systat 1 ,  .Xr libmemstat 3 ,  .Xr libxo 3 , -.Xr xo_parse_args 3 , +.Xr xo_options 7 ,  .Xr gstat 8 ,  .Xr iostat 8 ,  .Xr pstat 8 , diff --git a/usr.bin/vmstat/vmstat.c b/usr.bin/vmstat/vmstat.c index 7a7c83fe1ac8..9b4d3a25ee07 100644 --- a/usr.bin/vmstat/vmstat.c +++ b/usr.bin/vmstat/vmstat.c @@ -1465,6 +1465,7 @@ display_object(struct kinfo_vmobject *kvo)  	xo_emit("{:active/%5ju} ", (uintmax_t)kvo->kvo_active);  	xo_emit("{:inactive/%5ju} ", (uintmax_t)kvo->kvo_inactive);  	xo_emit("{:laundry/%5ju} ", (uintmax_t)kvo->kvo_laundry); +	xo_emit("{:wired/%5ju} ", (uintmax_t)kvo->kvo_wired);  	xo_emit("{:refcount/%3d} ", kvo->kvo_ref_count);  	xo_emit("{:shadowcount/%3d} ", kvo->kvo_shadow_count); @@ -1568,7 +1569,8 @@ doobjstat(void)  		return;  	}  	xo_emit("{T:RES/%5s} {T:ACT/%5s} {T:INACT/%5s} {T:LAUND/%5s} " -	    "{T:REF/%3s} {T:SHD/%3s} {T:CM/%2s} {T:TP/%3s} {T:PATH/%s}\n"); +	    "{T:WIRED/%5s} {T:REF/%3s} {T:SHD/%3s} {T:CM/%2s} {T:TP/%3s} " +	    "{T:PATH/%s}\n");  	xo_open_list("object");  	for (i = 0; i < cnt; i++)  		display_object(&kvo[i]); diff --git a/usr.bin/vtfontcvt/Makefile b/usr.bin/vtfontcvt/Makefile index de011660ca28..13e60c406b26 100644 --- a/usr.bin/vtfontcvt/Makefile +++ b/usr.bin/vtfontcvt/Makefile @@ -1,6 +1,6 @@  PROG=	vtfontcvt  SRCS=	vtfontcvt.c lz4.c -MAN8=	vtfontcvt.8 +MAN=	vtfontcvt.8  # lz4 compression functionality  .PATH: ${SRCTOP}/sys/cddl/contrib/opensolaris/common/lz4 diff --git a/usr.bin/w/pr_time.c b/usr.bin/w/pr_time.c index aef8b5dfaa87..445431fe3ec5 100644 --- a/usr.bin/w/pr_time.c +++ b/usr.bin/w/pr_time.c @@ -79,8 +79,13 @@ pr_attime(time_t *started, time_t *now)  	(void)wcsftime(buf, sizeof(buf), fmt, &tp);  	len = wcslen(buf);  	width = wcswidth(buf, len); -	xo_attr("since", "%lu", (unsigned long) *started); -	xo_attr("delta", "%lu", (unsigned long) diff); +	if (xo_get_style(NULL) == XO_STYLE_XML) { +		xo_attr("since", "%lu", (unsigned long)*started); +		xo_attr("delta", "%lu", (unsigned long)diff); +	} else { +		xo_emit("{e:login-time-since/%lu}{e:login-time-delta/%lu}", +		    (unsigned long)*started, (unsigned long)diff); +	}  	if (len == width)  		xo_emit("{:login-time/%-7.7ls/%ls}", buf);  	else if (width < 7) @@ -100,10 +105,16 @@ pr_attime(time_t *started, time_t *now)  int  pr_idle(time_t idle)  { +	/* In encoded formats, emit the raw data as well */ +	if (xo_get_style(NULL) == XO_STYLE_XML) +		xo_attr("seconds", "%lu", (unsigned long) idle); +	else +		xo_emit("{e:idle-seconds/%lu}", (unsigned long) idle); +  	/* If idle more than 36 hours, print as a number of days. */  	if (idle >= 36 * 3600) {  		int days = idle / 86400; -		xo_emit(" {:idle/%dday%s} ", days, days > 1 ? "s" : " " ); +		xo_emit(" {q:idle/%dday%s} ", days, days > 1 ? "s" : " " );  		if (days >= 100)  			return (2);  		if (days >= 10) @@ -111,16 +122,17 @@ pr_idle(time_t idle)  	}  	/* If idle more than an hour, print as HH:MM. */ -	else if (idle >= 3600) -		xo_emit(" {:idle/%2d:%02d/} ", +	else if (idle >= 3600) { +		xo_emit(" {q:idle/%2d:%02d} ",  		    (int)(idle / 3600), (int)((idle % 3600) / 60)); +	}  	else if (idle / 60 == 0) -		xo_emit("     - "); +		xo_emit("     - {q:idle//0}");  	/* Else print the minutes idle. */  	else -		xo_emit("    {:idle/%2d} ", (int)(idle / 60)); +		xo_emit("    {q:idle/%2d} ", (int)(idle / 60));  	return (0); /* not idle longer than 9 days */  } 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 a92edc6f0059..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 August 24, 2020 +.Dd September 11, 2025  .Dt W 1  .Os  .Sh NAME @@ -54,14 +54,14 @@ 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  .Xr libxo 3  in a selection of different human and machine readable formats.  See -.Xr xo_parse_args 3 +.Xr xo_options 7  for details on command line arguments.  .It Fl d  dumps out the entire process list on a per controlling @@ -145,7 +145,7 @@ flags are no longer supported.  .Xr uptime 1 ,  .Xr who 1 ,  .Xr libxo 3 , -.Xr xo_parse_args 3 +.Xr xo_options 7  .Sh HISTORY  The  .Nm diff --git a/usr.bin/w/w.c b/usr.bin/w/w.c index ac1df96077d3..502bf5a412b9 100644 --- a/usr.bin/w/w.c +++ b/usr.bin/w/w.c @@ -473,7 +473,7 @@ main(int argc, char *argv[])  static void  pr_header(time_t *nowp, int nusers)  { -	char buf[64]; +	char buf[64], *s, *e;  	struct sbuf upbuf;  	double avenrun[3];  	struct timespec tp; @@ -484,8 +484,15 @@ pr_header(time_t *nowp, int nusers)  	 * Print time of day.  	 */  	if (strftime(buf, sizeof(buf), -	    use_ampm ? "%l:%M%p" : "%k:%M", localtime(nowp)) != 0) -		xo_emit("{:time-of-day/%s} ", buf); +	    use_ampm ? "%l:%M%p" : "%k:%M", localtime(nowp)) != 0) { +		s = buf; +		if (xo_get_style(NULL) != XO_STYLE_TEXT) { +			/* trim leading whitespace */ +			while (isspace((unsigned char)*s)) +				s++; +		} +		xo_emit("{:time-of-day/%s} ", s); +	}  	/*  	 * Print how long system has been up.  	 */ @@ -516,21 +523,31 @@ pr_header(time_t *nowp, int nusers)  		if (days > 0)  			sbuf_printf(&upbuf, " %ld day%s,", -				days, days > 1 ? "s" : ""); +			    days, days > 1 ? "s" : "");  		if (hrs > 0 && mins > 0)  			sbuf_printf(&upbuf, " %2ld:%02ld,", hrs, mins);  		else if (hrs > 0)  			sbuf_printf(&upbuf, " %ld hr%s,", -				hrs, hrs > 1 ? "s" : ""); +			    hrs, hrs > 1 ? "s" : "");  		else if (mins > 0)  			sbuf_printf(&upbuf, " %ld min%s,", -				mins, mins > 1 ? "s" : ""); +			    mins, mins > 1 ? "s" : "");  		else  			sbuf_printf(&upbuf, " %ld sec%s,", -				secs, secs > 1 ? "s" : ""); +			    secs, secs > 1 ? "s" : "");  		if (sbuf_finish(&upbuf) != 0)  			xo_err(1, "Could not generate output"); -		xo_emit("{:uptime-human/%s}", sbuf_data(&upbuf)); +		s = sbuf_data(&upbuf); +		if (xo_get_style(NULL) != XO_STYLE_TEXT) { +			e = s + sbuf_len(&upbuf) - 1; +			/* trim leading whitespace */ +			while (isspace((unsigned char)*s)) +				s++; +			/* trim trailing comma */ +			if (e > s && *e == ',') +				*e = '\0'; +		} +		xo_emit("{:uptime-human/%s}", s);  		sbuf_delete(&upbuf);  	} diff --git a/usr.bin/wc/wc.1 b/usr.bin/wc/wc.1 index 482145c8f01f..656408794950 100644 --- a/usr.bin/wc/wc.1 +++ b/usr.bin/wc/wc.1 @@ -28,7 +28,7 @@  .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF  .\" SUCH DAMAGE.  .\" -.Dd April 11, 2020 +.Dd July 16, 2025  .Dt WC 1  .Os  .Sh NAME @@ -70,7 +70,7 @@ Generate output via  .Xr libxo 3  in a selection of different human and machine readable formats.  See -.Xr xo_parse_args 3 +.Xr xo_options 7  for details on command line arguments.  .It Fl L  Write the length of the line containing the most bytes (default) or characters @@ -196,7 +196,7 @@ utility.  .Sh SEE ALSO  .Xr iswspace 3 ,  .Xr libxo 3 , -.Xr xo_parse_args 3 +.Xr xo_options 7  .Sh STANDARDS  The  .Nm diff --git a/usr.bin/who/Makefile b/usr.bin/who/Makefile index 77626f51824a..c7c455d5261c 100644 --- a/usr.bin/who/Makefile +++ b/usr.bin/who/Makefile @@ -1,4 +1,3 @@  PROG=	who -PACKAGE=	acct  .include <bsd.prog.mk> diff --git a/usr.bin/xargs/tests/Makefile b/usr.bin/xargs/tests/Makefile index b1e6782069de..9fa8ff11fac2 100644 --- a/usr.bin/xargs/tests/Makefile +++ b/usr.bin/xargs/tests/Makefile @@ -1,6 +1,6 @@  PACKAGE=	tests -TAP_TESTS_SH=	legacy_test +ATF_TESTS_SH=	xargs_test  ${PACKAGE}FILES+=		regress.0.in  ${PACKAGE}FILES+=		regress.0.out @@ -17,12 +17,11 @@ ${PACKAGE}FILES+=		regress.R-1.out  ${PACKAGE}FILES+=		regress.in  ${PACKAGE}FILES+=		regress.n1.out  ${PACKAGE}FILES+=		regress.n2.out -${PACKAGE}FILES+=		regress.n2147483647.out +${PACKAGE}FILES+=		regress.nargmax.out  ${PACKAGE}FILES+=		regress.n2P0.out  ${PACKAGE}FILES+=		regress.n3.out  ${PACKAGE}FILES+=		regress.normal.out  ${PACKAGE}FILES+=		regress.quotes.in  ${PACKAGE}FILES+=		regress.quotes.out -${PACKAGE}FILES+=		regress.sh  .include <bsd.test.mk> diff --git a/usr.bin/xargs/tests/legacy_test.sh b/usr.bin/xargs/tests/legacy_test.sh deleted file mode 100644 index 3c7842d07bf0..000000000000 --- a/usr.bin/xargs/tests/legacy_test.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -SRCDIR="$(dirname "${0}")"; export SRCDIR - -m4 "${SRCDIR}/../regress.m4" "${SRCDIR}/regress.sh" | sh diff --git a/usr.bin/xargs/tests/regress.n2147483647.out b/usr.bin/xargs/tests/regress.nargmax.out index cc32a92a2199..cc32a92a2199 100644 --- a/usr.bin/xargs/tests/regress.n2147483647.out +++ b/usr.bin/xargs/tests/regress.nargmax.out diff --git a/usr.bin/xargs/tests/regress.sh b/usr.bin/xargs/tests/regress.sh deleted file mode 100644 index 9b4839d2a8ec..000000000000 --- a/usr.bin/xargs/tests/regress.sh +++ /dev/null @@ -1,32 +0,0 @@ - -echo 1..21 - -REGRESSION_START($1) - -REGRESSION_TEST(`normal', `xargs echo The <${SRCDIR}/regress.in') -REGRESSION_TEST(`I', `xargs -I% echo The % % % %% % % <${SRCDIR}/regress.in') -REGRESSION_TEST(`J', `xargs -J% echo The % again. <${SRCDIR}/regress.in') -REGRESSION_TEST(`L', `xargs -L3 echo <${SRCDIR}/regress.in') -REGRESSION_TEST(`P1', `xargs -P1 echo <${SRCDIR}/regress.in') -REGRESSION_TEST(`R', `xargs -I% -R1 echo The % % % %% % % <${SRCDIR}/regress.in') -REGRESSION_TEST(`R-1', `xargs -I% -R-1 echo The % % % %% % % <${SRCDIR}/regress.in') -REGRESSION_TEST(`n1', `xargs -n1 echo <${SRCDIR}/regress.in') -REGRESSION_TEST(`n2', `xargs -n2 echo <${SRCDIR}/regress.in') -# This test may consume a large amount of memory, making it unsuited to CI -# environments.  Disable it for now. -#REGRESSION_TEST(`n2147483647', `xargs -n2147483647 <${SRCDIR}/regress.in') -REGRESSION_TEST(`n2P0',`xargs -n2 -P0 echo <${SRCDIR}/regress.in | sort') -REGRESSION_TEST(`n3', `xargs -n3 echo <${SRCDIR}/regress.in') -REGRESSION_TEST(`0', `xargs -0 -n1 echo <${SRCDIR}/regress.0.in') -REGRESSION_TEST(`0I', `xargs -0 -I% echo The % %% % <${SRCDIR}/regress.0.in') -REGRESSION_TEST(`0J', `xargs -0 -J% echo The % again. <${SRCDIR}/regress.0.in') -REGRESSION_TEST(`0L', `xargs -0 -L2 echo <${SRCDIR}/regress.0.in') -REGRESSION_TEST(`0P1', `xargs -0 -P1 echo <${SRCDIR}/regress.0.in') -REGRESSION_TEST(`quotes', `xargs -n1 echo <${SRCDIR}/regress.quotes.in') - -REGRESSION_TEST_FREEFORM(`parallel1', `echo /var/empty       /var/empty       | xargs -n1 -P2 test -d; [ $? = 0 ]') -REGRESSION_TEST_FREEFORM(`parallel2', `echo /var/empty       /var/empty/nodir | xargs -n1 -P2 test -d; [ $? = 1 ]') -REGRESSION_TEST_FREEFORM(`parallel3', `echo /var/empty/nodir /var/empty       | xargs -n1 -P2 test -d; [ $? = 1 ]') -REGRESSION_TEST_FREEFORM(`parallel4', `echo /var/empty/nodir /var/empty/nodir | xargs -n1 -P2 test -d; [ $? = 1 ]') - -REGRESSION_END() diff --git a/usr.bin/xargs/tests/xargs_test.sh b/usr.bin/xargs/tests/xargs_test.sh new file mode 100755 index 000000000000..12c9407a7e45 --- /dev/null +++ b/usr.bin/xargs/tests/xargs_test.sh @@ -0,0 +1,193 @@ +# +# Copyright (c) 2002 Juli Mallett <jmallett@FreeBSD.org> +# Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org> +# +# SPDX-License-Identifier: BSD-2-Clause +# + +SRCDIR=$(atf_get_srcdir) + +atf_test_case xargs_normal +xargs_normal_body() +{ +	atf_check -o file:${SRCDIR}/regress.normal.out \ +	    xargs echo The <${SRCDIR}/regress.in +} + +atf_test_case xargs_I +xargs_I_body() +{ +	atf_check -o file:${SRCDIR}/regress.I.out \ +	    xargs -I% echo The % % % %% % % <${SRCDIR}/regress.in +} + +atf_test_case xargs_J +xargs_J_body() +{ +	atf_check -o file:${SRCDIR}/regress.J.out \ +	    xargs -J% echo The % again. <${SRCDIR}/regress.in +} + +atf_test_case xargs_L +xargs_L_body() +{ +	atf_check -o file:${SRCDIR}/regress.L.out \ +	    xargs -L3 echo <${SRCDIR}/regress.in +} + +atf_test_case xargs_P1 +xargs_P1_body() +{ +	atf_check -o file:${SRCDIR}/regress.P1.out \ +	    xargs -P1 echo <${SRCDIR}/regress.in +} + +atf_test_case xargs_R +xargs_R_body() +{ +	atf_check -o file:${SRCDIR}/regress.R.out \ +	    xargs -I% -R1 echo The % % % %% % % <${SRCDIR}/regress.in +} + +atf_test_case xargs_R_1 +xargs_R_1_body() +{ +	atf_check -o file:${SRCDIR}/regress.R-1.out \ +	    xargs -I% -R-1 echo The % % % %% % % <${SRCDIR}/regress.in +} + +atf_test_case xargs_n1 +xargs_n1_body() +{ +	atf_check -o file:${SRCDIR}/regress.n1.out \ +	    xargs -n1 echo <${SRCDIR}/regress.in +} + +atf_test_case xargs_n2 +xargs_n2_body() +{ +	atf_check -o file:${SRCDIR}/regress.n2.out \ +	    xargs -n2 echo <${SRCDIR}/regress.in +} + +atf_test_case xargs_nargmax +xargs_nargmax_body() +{ +	argmax=$(sysctl -n kern.argmax) +	atf_check -o file:${SRCDIR}/regress.nargmax.out \ +	    xargs -n$((argmax)) <${SRCDIR}/regress.in +	atf_check -s exit:1 -e match:"too large" \ +	    xargs -n$((argmax+1)) <${SRCDIR}/regress.in +} + +atf_test_case xargs_n2P0 +xargs_n2P0_body() +{ +	atf_check -o save:regress.out \ +	    xargs -n2 -P0 echo <${SRCDIR}/regress.in +	atf_check -o file:${SRCDIR}/regress.n2P0.out \ +	    sort regress.out +} + +atf_test_case xargs_n3 +xargs_n3_body() +{ +	atf_check -o file:${SRCDIR}/regress.n3.out \ +	    xargs -n3 echo <${SRCDIR}/regress.in +} + +atf_test_case xargs_0 +xargs_0_body() +{ +	atf_check -o file:${SRCDIR}/regress.0.out \ +	    xargs -0 -n1 echo <${SRCDIR}/regress.0.in +} + +atf_test_case xargs_0I +xargs_0I_body() +{ +	atf_check -o file:${SRCDIR}/regress.0I.out \ +	    xargs -0 -I% echo The % %% % <${SRCDIR}/regress.0.in +} + +atf_test_case xargs_0J +xargs_0J_body() +{ +	atf_check -o file:${SRCDIR}/regress.0J.out \ +	    xargs -0 -J% echo The % again. <${SRCDIR}/regress.0.in +} + +atf_test_case xargs_0L +xargs_0L_body() +{ +	atf_check -o file:${SRCDIR}/regress.0L.out \ +	    xargs -0 -L2 echo <${SRCDIR}/regress.0.in +} + +atf_test_case xargs_0P1 +xargs_0P1_body() +{ +	atf_check -o file:${SRCDIR}/regress.0P1.out \ +	    xargs -0 -P1 echo <${SRCDIR}/regress.0.in +} + +atf_test_case xargs_quotes +xargs_quotes_body() +{ +	atf_check -o file:${SRCDIR}/regress.quotes.out \ +	    xargs -n1 echo <${SRCDIR}/regress.quotes.in +} + +atf_test_case xargs_parallel1 +xargs_parallel1_body() +{ +	echo /var/empty /var/empty >input +	atf_check xargs -n1 -P2 test -d <input +} + +atf_test_case xargs_parallel2 +xargs_parallel2_body() +{ +	echo /var/empty /var/empty/nodir >input +	atf_check -s exit:1 xargs -n1 -P2 test -d <input +} + +atf_test_case xargs_parallel3 +xargs_parallel3_body() +{ +	echo /var/empty/nodir /var/empty >input +	atf_check -s exit:1 xargs -n1 -P2 test -d <input +} + +atf_test_case xargs_parallel4 +xargs_parallel4_body() +{ +	echo /var/empty/nodir /var/empty/nodir >input +	atf_check -s exit:1 xargs -n1 -P2 test -d <input +} + +atf_init_test_cases() +{ +	atf_add_test_case xargs_normal +	atf_add_test_case xargs_I +	atf_add_test_case xargs_J +	atf_add_test_case xargs_L +	atf_add_test_case xargs_P1 +	atf_add_test_case xargs_R +	atf_add_test_case xargs_R_1 +	atf_add_test_case xargs_n1 +	atf_add_test_case xargs_n2 +	atf_add_test_case xargs_nargmax +	atf_add_test_case xargs_n2P0 +	atf_add_test_case xargs_n3 +	atf_add_test_case xargs_0 +	atf_add_test_case xargs_0I +	atf_add_test_case xargs_0J +	atf_add_test_case xargs_0L +	atf_add_test_case xargs_0P1 +	atf_add_test_case xargs_quotes +	atf_add_test_case xargs_parallel1 +	atf_add_test_case xargs_parallel2 +	atf_add_test_case xargs_parallel3 +	atf_add_test_case xargs_parallel4 +} diff --git a/usr.bin/xargs/xargs.c b/usr.bin/xargs/xargs.c index 237beff26504..2a7f026e5066 100644 --- a/usr.bin/xargs/xargs.c +++ b/usr.bin/xargs/xargs.c @@ -166,7 +166,7 @@ main(int argc, char *argv[])  			break;  		case 'n':  			nflag = 1; -			nargs = (int)strtonum(optarg, 1, INT_MAX, &errstr); +			nargs = (int)strtonum(optarg, 1, arg_max, &errstr);  			if (errstr)  				errx(1, "-%c %s: %s", ch, optarg, errstr);  			break; diff --git a/usr.bin/xz/Makefile b/usr.bin/xz/Makefile index 0d5bce4c16f0..0a9103d60a13 100644 --- a/usr.bin/xz/Makefile +++ b/usr.bin/xz/Makefile @@ -1,5 +1,7 @@  .include <src.opts.mk> +PACKAGE=xz +  PROG=	xz  LINKS=	${BINDIR}/xz ${BINDIR}/unxz diff --git a/usr.bin/xzdec/Makefile b/usr.bin/xzdec/Makefile index 7c43b2e03d78..6bf3dc07a408 100644 --- a/usr.bin/xzdec/Makefile +++ b/usr.bin/xzdec/Makefile @@ -1,3 +1,5 @@ +PACKAGE=xz +  PROG=	xzdec  LINKS=	${BINDIR}/xzdec ${BINDIR}/lzdec | 
