aboutsummaryrefslogtreecommitdiff
path: root/usr.bin
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin')
-rw-r--r--usr.bin/last/last.c27
-rw-r--r--usr.bin/lsvfs/lsvfs.c18
-rw-r--r--usr.bin/netstat/inet.c12
-rw-r--r--usr.bin/sockstat/Makefile4
-rw-r--r--usr.bin/sockstat/main.c1883
-rw-r--r--usr.bin/sockstat/sockstat.c1883
-rw-r--r--usr.bin/sockstat/sockstat.h35
-rw-r--r--usr.bin/sockstat/tests/Makefile8
-rw-r--r--usr.bin/sockstat/tests/sockstat_test.c190
-rw-r--r--usr.bin/systat/ip.c20
-rw-r--r--usr.bin/tcopy/Makefile4
-rw-r--r--usr.bin/tcopy/tcopy.1195
-rw-r--r--usr.bin/tcopy/tcopy.c338
-rw-r--r--usr.bin/tcopy/tcopy.cc837
14 files changed, 3175 insertions, 2279 deletions
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/lsvfs/lsvfs.c b/usr.bin/lsvfs/lsvfs.c
index 04ed38e8d978..5477d96434ac 100644
--- a/usr.bin/lsvfs/lsvfs.c
+++ b/usr.bin/lsvfs/lsvfs.c
@@ -39,8 +39,8 @@ int
main(int argc, char **argv)
{
struct xvfsconf vfc, *xvfsp;
- size_t buflen;
- int cnt, i, rv = 0;
+ size_t cnt, buflen;
+ int rv = 0;
argc--, argv++;
@@ -59,15 +59,14 @@ main(int argc, char **argv)
}
} else {
if (sysctlbyname("vfs.conflist", NULL, &buflen, NULL, 0) < 0)
- err(1, "sysctl(vfs.conflist)");
- xvfsp = malloc(buflen);
- if (xvfsp == NULL)
- errx(1, "malloc failed");
+ 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(1, "sysctl(vfs.conflist)");
+ err(EXIT_FAILURE, "sysctl(vfs.conflist)");
cnt = buflen / sizeof(struct xvfsconf);
- for (i = 0; i < cnt; i++) {
+ 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));
@@ -82,10 +81,9 @@ static const char *
fmt_flags(int flags)
{
static char buf[sizeof(struct flaglist) * sizeof(fl)];
- int i;
buf[0] = '\0';
- for (i = 0; i < (int)nitems(fl); i++) {
+ for (size_t i = 0; i < (int)nitems(fl); i++) {
if ((flags & fl[i].flag) != 0) {
strlcat(buf, fl[i].str, sizeof(buf));
strlcat(buf, ", ", sizeof(buf));
diff --git a/usr.bin/netstat/inet.c b/usr.bin/netstat/inet.c
index 139ff9294fde..7014f02032c2 100644
--- a/usr.bin/netstat/inet.c
+++ b/usr.bin/netstat/inet.c
@@ -904,7 +904,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 +937,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 +951,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/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..b5e0248b743a
--- /dev/null
+++ b/usr.bin/sockstat/main.c
@@ -0,0 +1,1883 @@
+/*-
+ * 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 <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_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;
+
+/*
+ * 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;
+ 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);
+ } 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 = 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 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 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) {
+ 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_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, "??");
+ }
+ }
+ if (laddr != NULL)
+ laddr = laddr->next;
+ if (faddr != NULL)
+ faddr = faddr->next;
+ if (!is_xo_style_encoding && (laddr != NULL || faddr != NULL))
+ xo_emit("{:user/%-*s} {:command/%-*s} {:pid/%*s}"
+ " {:fd/%*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 (!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"),
+ .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 (!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)
+ return;
+ 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");
+ }
+ 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, 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, "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':
+ 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.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..9971bca2d474
--- /dev/null
+++ b/usr.bin/sockstat/tests/Makefile
@@ -0,0 +1,8 @@
+ATF_TESTS_C+= sockstat_test
+SRCS.sockstat_test= sockstat_test.c ../sockstat.c
+
+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/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/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..891c37f871e5
--- /dev/null
+++ b/usr.bin/tcopy/tcopy.cc
@@ -0,0 +1,837 @@
+/*-
+ * 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 [-cvx] [-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 (maxblk <= 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);
+}