diff options
Diffstat (limited to 'usr.bin')
-rw-r--r-- | usr.bin/last/last.c | 27 | ||||
-rw-r--r-- | usr.bin/lsvfs/lsvfs.c | 18 | ||||
-rw-r--r-- | usr.bin/netstat/inet.c | 12 | ||||
-rw-r--r-- | usr.bin/sockstat/Makefile | 4 | ||||
-rw-r--r-- | usr.bin/sockstat/main.c | 1883 | ||||
-rw-r--r-- | usr.bin/sockstat/sockstat.c | 1883 | ||||
-rw-r--r-- | usr.bin/sockstat/sockstat.h | 35 | ||||
-rw-r--r-- | usr.bin/sockstat/tests/Makefile | 8 | ||||
-rw-r--r-- | usr.bin/sockstat/tests/sockstat_test.c | 190 | ||||
-rw-r--r-- | usr.bin/systat/ip.c | 20 | ||||
-rw-r--r-- | usr.bin/tcopy/Makefile | 4 | ||||
-rw-r--r-- | usr.bin/tcopy/tcopy.1 | 195 | ||||
-rw-r--r-- | usr.bin/tcopy/tcopy.c | 338 | ||||
-rw-r--r-- | usr.bin/tcopy/tcopy.cc | 837 |
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); +} |