diff options
Diffstat (limited to 'usr.bin')
53 files changed, 4687 insertions, 2447 deletions
diff --git a/usr.bin/Makefile b/usr.bin/Makefile index 512f75b5d093..da1a9b3a681f 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -219,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/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/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 1ca63b371f65..b6e18083e24b 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 @@ -479,6 +480,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/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/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/kyua/Makefile b/usr.bin/kyua/Makefile index a4f95f1106d9..178a1d083b79 100644 --- a/usr.bin/kyua/Makefile +++ b/usr.bin/kyua/Makefile @@ -10,7 +10,7 @@ KYUA_VERSION= 0.13 KYUA_SRCDIR= ${SRCTOP}/contrib/kyua .PATH: ${KYUA_SRCDIR} -PACKAGE= tests +PACKAGE= kyua PROG_CXX= kyua SRCS= main.cpp LIBADD= lutok sqlite3 util @@ -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 \ 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/lsvfs/lsvfs.c b/usr.bin/lsvfs/lsvfs.c index 5477d96434ac..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,41 +40,42 @@ static const char *fmt_flags(int); int main(int argc, char **argv) { - struct xvfsconf vfc, *xvfsp; + 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(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); - - for (size_t 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); } 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 18595042da5f..920223ce3c27 100755 --- a/usr.bin/man/man.sh +++ b/usr.bin/man/man.sh @@ -511,13 +511,21 @@ man_display_page_groff() { # Usage: man_find_and_display page # Search through the manpaths looking for the given page. man_find_and_display() { - local found_page locpath p path sect + local found_page has_slash locpath p path sect # Check to see if it's a file. But only if it has a '/' in - # the filename. + # the filename or if -l was specified. case "$1" in - */*) if [ -f "$1" -a -r "$1" ]; then + */*) has_slash=yes + ;; + esac + if [ -n "$has_slash" -o -n "$lflag" ]; then + if [ -f "$1" -a -r "$1" ]; then decho "Found a usable page, displaying that" + if [ -z "$lflag" ]; then + echo "Opening a file directly is deprecated," \ + "use -l instead." >&2 + fi unset use_cat manpage="$1" setup_cattool "$manpage" @@ -531,9 +539,12 @@ man_find_and_display() { man_display_page fi return + elif [ -n "$lflag" ]; then + echo "Cannot read $1" >&2 + ret=1 + return fi - ;; - esac + fi IFS=: for sect in $MANSECT; do @@ -601,7 +612,7 @@ man_parse_opts() { local cmd_arg OPTIND=1 - while getopts 'K:M:P:S:adfhkm:op:tw' cmd_arg; do + while getopts 'K:M:P:S:adfhklm:op:tw' cmd_arg; do case "${cmd_arg}" in K) Kflag=Kflag REGEXP=$OPTARG ;; @@ -613,6 +624,7 @@ man_parse_opts() { f) fflag=fflag ;; h) man_usage 0 ;; k) kflag=kflag ;; + l) lflag=lflag ;; m) mflag=$OPTARG ;; o) oflag=oflag ;; p) MANROFFSEQ=$OPTARG ;; @@ -625,16 +637,19 @@ man_parse_opts() { shift $(( $OPTIND - 1 )) # Check the args for incompatible options. - - case "${Kflag}${fflag}${kflag}${tflag}${wflag}" in + case "${Kflag}${fflag}${kflag}${lflag}${tflag}${wflag}" in Kflagfflag*) echo "Incompatible options: -K and -f"; man_usage ;; Kflag*kflag*) echo "Incompatible options: -K and -k"; man_usage ;; + Kflag*lflag*) echo "Incompatible options: -K and -l"; man_usage ;; Kflag*tflag) echo "Incompatible options: -K and -t"; man_usage ;; fflagkflag*) echo "Incompatible options: -f and -k"; man_usage ;; + fflag*lflag*) echo "Incompatible options: -f and -l"; man_usage ;; fflag*tflag*) echo "Incompatible options: -f and -t"; man_usage ;; fflag*wflag) echo "Incompatible options: -f and -w"; man_usage ;; - *kflagtflag*) echo "Incompatible options: -k and -t"; man_usage ;; + *kflaglflag*) echo "Incompatible options: -k and -l"; man_usage ;; + *kflag*tflag*) echo "Incompatible options: -k and -t"; man_usage ;; *kflag*wflag) echo "Incompatible options: -k and -w"; man_usage ;; + *lflag*wflag) echo "Incompatible options: -l and -w"; man_usage ;; *tflagwflag) echo "Incompatible options: -t and -w"; man_usage ;; esac @@ -751,7 +766,7 @@ man_setup_locale() { # Display usage for the man utility. man_usage() { echo 'Usage:' - echo ' man [-adho] [-t | -w] [-M manpath] [-P pager] [-S mansect]' + echo ' man [-adhlo] [-t | -w] [-M manpath] [-P pager] [-S mansect]' echo ' [-m arch[:machine]] [-p [eprtv]] [mansect] page [...]' echo ' man -K | -f | -k expression [...] -- Search manual pages' diff --git a/usr.bin/mandoc/Makefile b/usr.bin/mandoc/Makefile index 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..f320b6f547fd --- /dev/null +++ b/usr.bin/mandoc/mandoc.ucl @@ -0,0 +1,18 @@ +path_glob: "/usr/share/man/*" + +cleanup: { + type: lua + sandbox: false + script: <<EOD + os.remove("/usr/share/man/mandoc.db") +EOD +} + +trigger: { + type: lua + sandbox: false + script: <<EOD + print("Generating apropos(1) database...") + pkg.exec({"/usr/bin/makewhatis", "/usr/share/man"}) +EOD +} diff --git a/usr.bin/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/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/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..5f36b1599cad 100644 --- a/usr.bin/netstat/inet.c +++ b/usr.bin/netstat/inet.c @@ -767,15 +767,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 +909,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 +942,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 +956,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/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/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/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/sockstat/Makefile b/usr.bin/sockstat/Makefile index 7254511f21c6..c6e7a078162b 100644 --- a/usr.bin/sockstat/Makefile +++ b/usr.bin/sockstat/Makefile @@ -1,6 +1,7 @@ .include <src.opts.mk> PROG= sockstat +SRCS= main.c sockstat.c LIBADD= jail xo @@ -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..7fedfd5b8724 --- /dev/null +++ b/usr.bin/sockstat/main.c @@ -0,0 +1,1952 @@ +/*- + * 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", "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_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_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 | 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 && + 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 091911cd0879..d14eb967ad0f 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 July 17, 2025 +.Dd October 9, 2025 .Dt SOCKSTAT 1 .Os .Sh NAME @@ -34,7 +34,7 @@ .Sh SYNOPSIS .Nm .Op Fl -libxo -.Op Fl 46ACcfIiLlnqSsUuvw +.Op Fl 46AbCcfIiLlnqSsUuvw .Op Fl j Ar jail .Op Fl p Ar ports .Op Fl P Ar protocols @@ -65,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. @@ -202,10 +205,15 @@ is specified (only for SCTP or TCP). The path state if .Fl s is specified (only for SCTP). +This column is only shown when there is at least one path state shown. .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 diff --git a/usr.bin/sockstat/sockstat.c b/usr.bin/sockstat/sockstat.c index 6761faae5210..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,1889 +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 <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> - -#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_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 */ - -/* - * 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); - -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) { - 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; +int *ports; - 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 +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) xo_err(1, "calloc()"); p = portspec; while (*p != '\0') { - if (!isdigit(*p)) - xo_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) - xo_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) - xo_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) - 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); - } 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: - 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_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; - 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 = (xo_get_style(NULL) == XO_STYLE_TEXT) - ? "seqpac" - : "seqpacket"; - 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; - const bool is_text_style = (xo_get_style(NULL) == XO_STYLE_TEXT); - - 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_text_style) { - 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_text_style) { - 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 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; - const bool is_text_style = (xo_get_style(NULL) == XO_STYLE_TEXT); - if (faddr->conn != 0) { - /* Remote peer we connect(2) to, if any. */ - struct sock *p; - if (is_text_style) - pos += strlcpy(SAFEBUF, "-> ", SAFESIZE); - p = RB_FIND(pcbs_t, &pcbs, - &(struct sock){ .pcb = faddr->conn }); - if (__predict_false(p == NULL) && is_text_style) { - /* 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_text_style) { - 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_text_style) - 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_text_style) { - 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 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; - 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; - const bool is_text_style = (xo_get_style(NULL) == XO_STYLE_TEXT); - - 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_text_style) { - xo_emit(" {:/%-*.*s}", cw->local_addr, - cw->local_addr, buf); - } - xo_close_container("local"); - } else if (laddr->address.ss_len == 0 && - faddr->conn == 0 && is_text_style) { - xo_emit(" {:/%-*.*s}", cw->local_addr, - cw->local_addr, "(not connected)"); - } else if (is_text_style) { - xo_emit(" {:/%-*.*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_text_style) - xo_emit(" {:/%-*s}", - cw->foreign_addr, "??"); - else if (is_text_style) - xo_emit(" {:/%-*.*s}", cw->foreign_addr, - cw->foreign_addr, buf); - xo_close_container("foreign"); - } else if (is_text_style) - xo_emit(" {:/%-*s}", cw->foreign_addr, "??"); - } else { - if (laddr != NULL) { - xo_open_container("local"); - formataddr(&laddr->address, buf, bufsize); - if (is_text_style) { - xo_emit(" {:/%-*.*s}", cw->local_addr, - cw->local_addr, buf); - } - xo_close_container("local"); - } else if (is_text_style) - xo_emit(" {:/%-*.*s}", cw->local_addr, - cw->local_addr, "??"); - if (faddr != NULL) { - xo_open_container("foreign"); - formataddr(&faddr->address, buf, bufsize); - if (is_text_style) { - xo_emit(" {:/%-*.*s}", cw->foreign_addr, - cw->foreign_addr, buf); - } - xo_close_container("foreign"); - } else if (is_text_style) { - xo_emit(" {:/%-*.*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_text_style) - strlcpy(buf, "??", bufsize); - } else if (is_text_style) - strlcpy(buf, "??", bufsize); - if (is_text_style) - xo_emit(" {:/%-*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_text_style) - xo_emit(" {:/%*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_text_style) - xo_emit(" {:/%*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) { - xo_emit(" {:path-state/%-*s}", cw->path_state, - sctp_path_state(faddr->state)); - } else if (is_text_style) - xo_emit(" {:/%-*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_text_style) - xo_emit(" {:/%-*s}", - cw->conn_state, "??"); - break; - } - } else if (is_text_style) - xo_emit(" {:/%-*s}", - cw->conn_state, "??"); - } - if (opt_S) { - if (s->proto == IPPROTO_TCP) - xo_emit(" {:stack/%-*s}", - cw->stack, s->stack); - else if (is_text_style) - xo_emit(" {:/%-*s}", - cw->stack, "??"); - } - if (opt_C) { - if (s->proto == IPPROTO_TCP) - xo_emit(" {:cc/%-*s}", cw->cc, s->cc); - else if (is_text_style) - xo_emit(" {:/%-*s}", cw->cc, "??"); - } - } - if (laddr != NULL) - laddr = laddr->next; - if (faddr != NULL) - faddr = faddr->next; - if (is_text_style && (laddr != NULL || faddr != NULL)) - xo_emit("{:/%-*s} {:/%-*s} {:/%*s} {:/%*s}", - cw->user, "??", cw->command, "??", - cw->pid, "??", cw->fd, "??"); - first = false; - } - xo_emit("\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) { - xo_err(1, "malloc()"); - return; - } - - if (xo_get_style(NULL) == XO_STYLE_TEXT) { - 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"), - .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) { - xo_emit(" {T:/%-*s}", cw.path_state, "PATH STATE"); - xo_emit(" {T:/%-*s}", cw.conn_state, "CONN 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 (xo_get_style(NULL) == XO_STYLE_TEXT) - xo_emit(" {:/%-*.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) - return; - SLIST_FOREACH(s, &nosocks, socket_list) { - if (!check_ports(s)) - continue; - xo_open_instance("socket"); - if (xo_get_style(NULL) == XO_STYLE_TEXT) - xo_emit("{:/%-*s} {:/%-*s} {:/%*s} {:/%*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 (xo_get_style(NULL) == XO_STYLE_TEXT) - xo_emit("{:/%-*s} {:/%-*s} {:/%*s} {:/%*s}", - cw.user, "??", cw.command, "??", - cw.pid, "??", cw.fd, "??"); - display_sock(s, &cw, buf, bufsize); - xo_close_instance("socket"); - } - 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] [-46ACcfIiLlnqSsUuvw] [-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; - - argc = xo_parse_args(argc, argv); - if (argc < 0) - exit(1); - 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) - 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': - 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': - 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.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/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/tests/tail_test.sh b/usr.bin/tail/tests/tail_test.sh index 74d6908f7568..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 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/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/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 159eb3370c8c..2dbcffdeda1f 100644 --- a/usr.bin/w/w.1 +++ b/usr.bin/w/w.1 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 16, 2025 +.Dd September 11, 2025 .Dt W 1 .Os .Sh NAME @@ -54,7 +54,7 @@ user is on, the host from which the user is logged in, the time the user logged on, the time since the user last typed anything, and the name and arguments of the current process. .Pp -The options are as follows: +The following options are available: .Bl -tag -width indent .It Fl -libxo Generate output via 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/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 |