aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin')
-rw-r--r--usr.sbin/Makefile2
-rw-r--r--usr.sbin/acpi/Makefile2
-rw-r--r--usr.sbin/acpi/einj/Makefile4
-rw-r--r--usr.sbin/acpi/einj/einj.873
-rw-r--r--usr.sbin/acpi/einj/einj.c256
-rw-r--r--usr.sbin/adduser/adduser.85
-rw-r--r--usr.sbin/adduser/adduser.sh4
-rw-r--r--usr.sbin/apm/Makefile1
-rw-r--r--usr.sbin/apmd/Makefile1
-rw-r--r--usr.sbin/autofs/common.h2
-rw-r--r--usr.sbin/autofs/log.c66
-rw-r--r--usr.sbin/bhyve/Makefile1
-rw-r--r--usr.sbin/bhyve/aarch64/bhyverun_machdep.c5
-rw-r--r--usr.sbin/bhyve/amd64/bhyverun_machdep.c11
-rw-r--r--usr.sbin/bhyve/bhyve.8118
-rw-r--r--usr.sbin/bhyve/bhyve_config.5101
-rw-r--r--usr.sbin/bhyve/bhyverun.c67
-rw-r--r--usr.sbin/bhyve/block_if.c48
-rw-r--r--usr.sbin/bhyve/iov.c12
-rw-r--r--usr.sbin/bhyve/iov.h4
-rw-r--r--usr.sbin/bhyve/pci_nvme.c16
-rw-r--r--usr.sbin/bhyve/pci_virtio_scsi.c1384
-rw-r--r--usr.sbin/bhyve/pci_virtio_scsi.h428
-rw-r--r--usr.sbin/bhyve/pci_virtio_scsi_ctl.c377
-rw-r--r--usr.sbin/bhyve/riscv/bhyverun_machdep.c5
-rw-r--r--usr.sbin/bhyve/rtc_pl031.c4
-rw-r--r--usr.sbin/bhyve/smbiostbl.c124
-rw-r--r--usr.sbin/bhyve/snapshot.c14
-rw-r--r--usr.sbin/bhyve/snapshot.h2
-rw-r--r--usr.sbin/bhyve/tpm_intf_crb.c17
-rw-r--r--usr.sbin/bhyvectl/bhyvectl.88
-rw-r--r--usr.sbin/bhyvectl/bhyvectl.c39
-rw-r--r--usr.sbin/bhyveload/bhyveload.c2
-rw-r--r--usr.sbin/blacklistd/blacklistd.conf2
-rw-r--r--usr.sbin/blocklistd/blocklistd.conf2
-rw-r--r--usr.sbin/bluetooth/hccontrol/le.c23
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c105
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h2
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c140
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h3
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbtfw.811
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf2
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/main.c82
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/main.c25
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c21
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf70
-rw-r--r--usr.sbin/boot0cfg/boot0cfg.832
-rw-r--r--usr.sbin/boot0cfg/boot0cfg.c40
-rw-r--r--usr.sbin/bsdconfig/bsdconfig.848
-rw-r--r--usr.sbin/bsdconfig/dot/USAGE31
-rw-r--r--usr.sbin/bsdconfig/share/common.subr2
-rw-r--r--usr.sbin/bsdconfig/share/media/wlan.subr3
-rw-r--r--usr.sbin/bsdinstall/FreeBSD-base.conf.in2
-rw-r--r--usr.sbin/bsdinstall/Makefile6
-rw-r--r--usr.sbin/bsdinstall/bsdinstall.861
-rw-r--r--usr.sbin/bsdinstall/distextract/distextract.c2
-rw-r--r--usr.sbin/bsdinstall/partedit/partedit_efi.c2
-rwxr-xr-xusr.sbin/bsdinstall/scripts/auto4
-rwxr-xr-xusr.sbin/bsdinstall/scripts/hardening4
-rwxr-xr-xusr.sbin/bsdinstall/scripts/hostname33
-rwxr-xr-xusr.sbin/bsdinstall/scripts/mirrorselect13
-rwxr-xr-xusr.sbin/bsdinstall/scripts/netconfig2
-rwxr-xr-xusr.sbin/bsdinstall/scripts/pkgbase.in15
-rwxr-xr-xusr.sbin/bsdinstall/scripts/rootpass4
-rwxr-xr-xusr.sbin/bsdinstall/scripts/script72
-rwxr-xr-xusr.sbin/bsdinstall/scripts/services5
-rwxr-xr-xusr.sbin/bsdinstall/scripts/wlanconfig47
-rwxr-xr-xusr.sbin/bsdinstall/scripts/zfsboot19
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_fs_tbl.c4
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c6
-rw-r--r--usr.sbin/certctl/certctl.815
-rw-r--r--usr.sbin/certctl/certctl.c90
-rw-r--r--usr.sbin/certctl/tests/certctl_test.sh3
-rw-r--r--usr.sbin/chkgrp/chkgrp.85
-rw-r--r--usr.sbin/chown/chgrp.13
-rw-r--r--usr.sbin/chown/chown.83
-rwxr-xr-xusr.sbin/crashinfo/crashinfo.sh3
-rw-r--r--usr.sbin/cron/cron/cron.811
-rw-r--r--usr.sbin/cron/cron/database.c6
-rw-r--r--usr.sbin/crunch/examples/really-big.conf2
-rw-r--r--usr.sbin/ctladm/tests/port.sh10
-rw-r--r--usr.sbin/ctld/conf.cc4
-rw-r--r--usr.sbin/ctld/conf.h2
-rw-r--r--usr.sbin/ctld/ctld.cc319
-rw-r--r--usr.sbin/ctld/ctld.hh36
-rw-r--r--usr.sbin/ctld/kernel.cc46
-rw-r--r--usr.sbin/ctld/login.cc10
-rw-r--r--usr.sbin/ctld/parse.y2
-rw-r--r--usr.sbin/ctld/uclparse.cc2
-rw-r--r--usr.sbin/daemon/daemon.c1
-rw-r--r--usr.sbin/diskinfo/diskinfo.812
-rw-r--r--usr.sbin/diskinfo/diskinfo.c10
-rw-r--r--usr.sbin/edquota/edquota.c1
-rw-r--r--usr.sbin/efibootmgr/Makefile7
-rw-r--r--usr.sbin/efibootmgr/efibootmgr.86
-rw-r--r--usr.sbin/efibootmgr/efibootmgr.c72
-rw-r--r--usr.sbin/efitable/efitable.86
-rw-r--r--usr.sbin/efitable/efitable.c3
-rw-r--r--usr.sbin/efivar/Makefile2
-rw-r--r--usr.sbin/efivar/efiutil.c190
-rw-r--r--usr.sbin/efivar/efivar.c9
-rw-r--r--usr.sbin/etcupdate/etcupdate.839
-rwxr-xr-xusr.sbin/etcupdate/etcupdate.sh44
-rw-r--r--usr.sbin/freebsd-update/freebsd-update.sh2
-rw-r--r--usr.sbin/fwcamctl/Makefile9
-rw-r--r--usr.sbin/fwcamctl/fwcamctl.8156
-rw-r--r--usr.sbin/fwcamctl/fwcamctl.c450
-rw-r--r--usr.sbin/fwget/pci/pci1
-rw-r--r--usr.sbin/fwget/pci/pci_network_intel246
-rw-r--r--usr.sbin/fwget/pci/pci_network_ittim24
-rw-r--r--usr.sbin/fwget/pci/pci_network_mediatek14
-rw-r--r--usr.sbin/fwget/pci/pci_network_realtek5
-rw-r--r--usr.sbin/fwget/pci/pci_video_amd4
-rw-r--r--usr.sbin/gssd/gssd.c22
-rw-r--r--usr.sbin/gstat/gstat.824
-rw-r--r--usr.sbin/gstat/gstat.c784
-rw-r--r--usr.sbin/ifmcstat/ifmcstat.848
-rw-r--r--usr.sbin/ifmcstat/ifmcstat.c502
-rw-r--r--usr.sbin/inetd/inetd.86
-rw-r--r--usr.sbin/inetd/inetd.c2
-rw-r--r--usr.sbin/iostat/iostat.87
-rw-r--r--usr.sbin/iovctl/iovctl.835
-rw-r--r--usr.sbin/iovctl/iovctl.c173
-rw-r--r--usr.sbin/iovctl/iovctl.conf.518
-rw-r--r--usr.sbin/ipfwpcap/ipfwpcap.c9
-rw-r--r--usr.sbin/jail/command.c4
-rw-r--r--usr.sbin/jail/config.c14
-rw-r--r--usr.sbin/jail/jail.84
-rwxr-xr-xusr.sbin/jail/tests/jail_basic_test.sh3
-rw-r--r--usr.sbin/kldxref/kldxref.87
-rw-r--r--usr.sbin/kldxref/kldxref.c17
-rw-r--r--usr.sbin/lpr/lpd/lpd.872
-rw-r--r--usr.sbin/lpr/lpd/lpd.c31
-rw-r--r--usr.sbin/lpr/lpd/printjob.c257
-rw-r--r--usr.sbin/makefs/cd9660/iso9660_rrip.c18
-rw-r--r--usr.sbin/makefs/zfs/dsl.c1
-rw-r--r--usr.sbin/mfiutil/Makefile2
-rw-r--r--usr.sbin/mfiutil/mfi_drive.c59
-rw-r--r--usr.sbin/mfiutil/mfiutil.89
-rw-r--r--usr.sbin/mixer/mixer.822
-rw-r--r--usr.sbin/mixer/mixer.c27
-rwxr-xr-xusr.sbin/mixer/tests/mixer_test.sh18
-rw-r--r--usr.sbin/mountd/mountd.c2
-rw-r--r--usr.sbin/mtest/mtest.c74
-rw-r--r--usr.sbin/ndp/Makefile5
-rw-r--r--usr.sbin/ndp/ndp.c7
-rw-r--r--usr.sbin/newsyslog/newsyslog.84
-rw-r--r--usr.sbin/newsyslog/newsyslog.c6
-rw-r--r--usr.sbin/newsyslog/newsyslog.conf.514
-rw-r--r--usr.sbin/nfsd/nfsv4.4178
-rw-r--r--usr.sbin/nfsd/pnfs.42
-rw-r--r--usr.sbin/nfsuserd/nfsuserd.c1
-rw-r--r--usr.sbin/nvram/Makefile1
-rw-r--r--usr.sbin/nvram/nvram.86
-rw-r--r--usr.sbin/pciconf/pciconf.831
-rw-r--r--usr.sbin/pciconf/pciconf.c209
-rwxr-xr-xusr.sbin/periodic/etc/daily/404.status-zfs4
-rwxr-xr-xusr.sbin/periodic/etc/security/520.pfdenied2
-rwxr-xr-xusr.sbin/periodic/etc/security/800.loginfail1
-rw-r--r--usr.sbin/pmc/Makefile6
-rw-r--r--usr.sbin/pmc/cmd_pmc.h7
-rw-r--r--usr.sbin/pmc/cmd_pmc_frontend.cc242
-rw-r--r--usr.sbin/pmc/cmd_pmc_info.cc186
-rw-r--r--usr.sbin/pmc/cmd_pmc_record.cc750
-rw-r--r--usr.sbin/pmc/display.cc571
-rw-r--r--usr.sbin/pmc/display.hh105
-rw-r--r--usr.sbin/pmc/headers.hh83
-rw-r--r--usr.sbin/pmc/pmc.c52
-rw-r--r--usr.sbin/pmc/util.cc82
-rw-r--r--usr.sbin/pmc/util.hh (renamed from usr.sbin/efivar/efiutil.h)22
-rw-r--r--usr.sbin/pmc/view.cc960
-rw-r--r--usr.sbin/pmc/view.hh411
-rw-r--r--usr.sbin/pmcstat/pmcstat.826
-rw-r--r--usr.sbin/pmcstat/pmcstat.c7
-rw-r--r--usr.sbin/pmcstat/pmcstat.h5
-rw-r--r--usr.sbin/pmcstat/pmcstat_log.c49
-rw-r--r--usr.sbin/ppp/chap.c4
-rw-r--r--usr.sbin/ppp/chap.h4
-rw-r--r--usr.sbin/ppp/defs.h2
-rw-r--r--usr.sbin/ppp/fsm.c6
-rw-r--r--usr.sbin/ppp/id.c5
-rw-r--r--usr.sbin/ppp/lcp.c6
-rw-r--r--usr.sbin/ppp/mp.c12
-rw-r--r--usr.sbin/prometheus_sysctl_exporter/prometheus_sysctl_exporter.87
-rw-r--r--usr.sbin/prometheus_sysctl_exporter/prometheus_sysctl_exporter.c54
-rw-r--r--usr.sbin/pw/pw.83
-rw-r--r--usr.sbin/pw/pw.h3
-rw-r--r--usr.sbin/pw/pw_conf.c26
-rw-r--r--usr.sbin/pw/pw_group.c2
-rw-r--r--usr.sbin/pw/pw_user.c4
-rwxr-xr-xusr.sbin/pw/tests/pw_config_test.sh12
-rwxr-xr-xusr.sbin/pw/tests/pw_groupdel_test.sh16
-rwxr-xr-xusr.sbin/pw/tests/pw_useradd_test.sh2
-rw-r--r--usr.sbin/rpc.tlsservd/rpc.tlsservd.c7
-rw-r--r--usr.sbin/rtadvd/Makefile5
-rw-r--r--usr.sbin/rtadvd/config.c38
-rw-r--r--usr.sbin/rtadvd/rtadvd.c38
-rw-r--r--usr.sbin/rtadvd/rtadvd.h3
-rwxr-xr-xusr.sbin/service/service.sh2
-rw-r--r--usr.sbin/smart/Makefile8
-rw-r--r--usr.sbin/sndctl/sndctl.84
-rw-r--r--usr.sbin/sndctl/sndctl.c41
-rw-r--r--usr.sbin/spi/spi.c35
-rw-r--r--usr.sbin/syslogd/syslog.conf.56
-rw-r--r--usr.sbin/syslogd/syslogd.c198
-rw-r--r--usr.sbin/syslogd/syslogd_cap.c2
-rw-r--r--usr.sbin/syslogd/syslogd_cap.h8
-rw-r--r--usr.sbin/syslogd/syslogd_cap_config.c7
-rw-r--r--usr.sbin/syslogd/syslogd_cap_log.c72
-rw-r--r--usr.sbin/syslogd/tests/Makefile3
-rw-r--r--usr.sbin/syslogd/tests/syslogd_test.sh91
-rw-r--r--usr.sbin/tcpdump/tcpdump/Makefile1
-rw-r--r--usr.sbin/tcpdump/tcpdump/config.h42
-rwxr-xr-xusr.sbin/unbound/setup/local-unbound-setup.sh50
-rw-r--r--usr.sbin/usbconfig/usbconfig.85
-rw-r--r--usr.sbin/usbconfig/usbconfig.c16
-rw-r--r--usr.sbin/usbdump/usbdump.89
-rw-r--r--usr.sbin/usbdump/usbdump.c33
-rw-r--r--usr.sbin/vidcontrol/vidcontrol.c7
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/int.h3
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/main.c72
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/virtual_oss.861
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/virtual_oss.c4
-rw-r--r--usr.sbin/virtual_oss/virtual_oss_cmd/command.c17
-rw-r--r--usr.sbin/wpa/Makefile.inc1
-rw-r--r--usr.sbin/wpa/src/utils/Makefile2
-rw-r--r--usr.sbin/zonectl/zonectl.c17
-rw-r--r--usr.sbin/zzz/Makefile4
-rw-r--r--usr.sbin/zzz/zzz.886
-rw-r--r--usr.sbin/zzz/zzz.c47
-rw-r--r--usr.sbin/zzz/zzz.sh41
231 files changed, 10078 insertions, 3515 deletions
diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile
index 3d3a8443a7cf..c55592c926a4 100644
--- a/usr.sbin/Makefile
+++ b/usr.sbin/Makefile
@@ -27,6 +27,7 @@ SUBDIR= adduser \
extattrctl \
fifolog \
fstyp \
+ fwcamctl \
fwcontrol \
fwget \
getfmac \
@@ -81,6 +82,7 @@ SUBDIR= adduser \
setfib \
setfmac \
setpmac \
+ smart \
smbmsg \
snapinfo \
spi \
diff --git a/usr.sbin/acpi/Makefile b/usr.sbin/acpi/Makefile
index c44a4146f44b..fb1216b43f60 100644
--- a/usr.sbin/acpi/Makefile
+++ b/usr.sbin/acpi/Makefile
@@ -1,6 +1,6 @@
# Makefile for acpi tools
# $Id: Makefile,v 1.1 2000/07/14 18:16:22 iwasaki Exp $
-SUBDIR= acpiconf acpidb acpidump iasl
+SUBDIR= acpiconf acpidb acpidump einj iasl
.include <bsd.subdir.mk>
diff --git a/usr.sbin/acpi/einj/Makefile b/usr.sbin/acpi/einj/Makefile
new file mode 100644
index 000000000000..4a0a9900f3b1
--- /dev/null
+++ b/usr.sbin/acpi/einj/Makefile
@@ -0,0 +1,4 @@
+PROG= einj
+MAN= einj.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/acpi/einj/einj.8 b/usr.sbin/acpi/einj/einj.8
new file mode 100644
index 000000000000..06ac0988158a
--- /dev/null
+++ b/usr.sbin/acpi/einj/einj.8
@@ -0,0 +1,73 @@
+.\"
+.\" Copyright (c) 2025 Netflix, Inc.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.Dd December 17, 2025
+.Dt EINJ 8
+.Os
+.Sh NAME
+.Nm einj
+.Nd ACPI Platform Error Interface error injection
+.Sh SYNOPSIS
+.Nm
+.Cm list
+.Nm
+.Cm inject
+.Op Fl a Ar address
+.Op Fl C Ar APIC ID
+.Oo Fl P Ar device
+.Sm off
+.Cm pci Oo Ar domain \&: Oc Ar bus \&: Ar slot \&: Ar function
+.Sm on
+.Oc
+.Ar type
+.Sh DESCRIPTION
+The
+.Nm
+utility provides a command line interface to the ACPI Platform Error Interface
+.Pq APEI
+error injection facility provided by
+.Xr acpi_einj 4 .
+.Pp
+The
+.Cm list
+command outputs the set of platform errors that can be injected on the
+current host.
+.Pp
+The
+.Cm inject
+command injects a single platform error.
+The
+.Ar type
+argument specifies the type of error to inject,
+for example
+.Dq mem.cor
+to inject a correctable memory error.
+Vendor-specific errors are not supported.
+.Pp
+The
+.Ar address
+argument sets a specific address to use with memory errors.
+.Pp
+The
+.Ar APIC ID
+argument sets a physical processor ID for processor errors.
+.Pp
+The
+.Ar device
+argument sets the target for PCI device errors.
+The argument must follow the format
+.Sm off
+.Cm pci Oo Ar domain \&: Oc Ar bus \&: Ar slot \&: Ar function
+.Sm on .
+.Sh EXAMPLES
+Inject a fatal uncorrectable memory error for the physical address 0x12345678:
+.Pp
+.Dl einj inject -a 0x12345678 mem.fatal
+.Sh SEE ALSO
+.Xr acpi_einj 4
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 16.0 .
diff --git a/usr.sbin/acpi/einj/einj.c b/usr.sbin/acpi/einj/einj.c
new file mode 100644
index 000000000000..38e0d129a2e5
--- /dev/null
+++ b/usr.sbin/acpi/einj/einj.c
@@ -0,0 +1,256 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Netflix, Inc.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <err.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/actbl1.h>
+#pragma GCC diagnostic pop
+
+#include <dev/acpica/acpiio.h>
+#include <dev/pci/pcireg.h>
+
+static int fd;
+
+static struct error_type_info {
+ const char *name;
+ uint32_t bit;
+ const char *description;
+} error_types[] = {
+ { "cpu.cor", ACPI_EINJ_PROCESSOR_CORRECTABLE, "Processor Correctable" },
+ { "cpu.uc", ACPI_EINJ_PROCESSOR_UNCORRECTABLE,
+ "Processor Uncorrectable Non-Fatal" },
+ { "cpu.fatal", ACPI_EINJ_PROCESSOR_FATAL,
+ "Processor Uncorrectable Fatal" },
+ { "mem.cor", ACPI_EINJ_MEMORY_CORRECTABLE, "Memory Correctable" },
+ { "mem.uc", ACPI_EINJ_MEMORY_UNCORRECTABLE,
+ "Memory Uncorrectable Non-Fatal" },
+ { "mem.fatal", ACPI_EINJ_MEMORY_FATAL, "Memory Uncorrectable Fatal" },
+ { "pcie.cor", ACPI_EINJ_PCIX_CORRECTABLE, "PCI Express Correctable" },
+ { "pcie.uc", ACPI_EINJ_PCIX_UNCORRECTABLE,
+ "PCI Express Uncorrectable Non-Fatal" },
+ { "pcie.fatal", ACPI_EINJ_PCIX_FATAL,
+ "PCI Express Uncorrectable Fatal" },
+ { "platform.cor", ACPI_EINJ_PLATFORM_CORRECTABLE,
+ "Platform Correctable" },
+ { "platform.uc", ACPI_EINJ_PLATFORM_UNCORRECTABLE,
+ "Platform Uncorrectable Non-Fatal" },
+ { "platform.fatal", ACPI_EINJ_PLATFORM_FATAL,
+ "Platform Uncorrectable Fatal" },
+ { "cxl.cache.cor", ACPI_EINJ_CXL_CACHE_CORRECTABLE,
+ "CXL.cache Correctable" },
+ { "cxl.cache.uc", ACPI_EINJ_CXL_CACHE_UNCORRECTABLE,
+ "CXL.cache Uncorrectable Non-Fatal" },
+ { "cxl.cache.fatal", ACPI_EINJ_CXL_CACHE_FATAL,
+ "CXL.cache Uncorrectable Fatal" },
+ { "cxl.mem.cor", ACPI_EINJ_CXL_MEM_CORRECTABLE, "CXL.mem Correctable" },
+ { "cxl.mem.uc", ACPI_EINJ_CXL_MEM_UNCORRECTABLE,
+ "CXL.mem Uncorrectable Non-Fatal" },
+ { "cxl.mem.fatal", ACPI_EINJ_CXL_MEM_FATAL,
+ "CXL.mem Uncorrectable Fatal" },
+ { "vendor", ACPI_EINJ_VENDOR_DEFINED, "Vendor Defined" },
+};
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage:\n"
+ "\teinj list\n"
+ "\teinj inject -a <address> -C <cpu> -P <dbsf> <type>\n");
+ exit(1);
+}
+
+static void
+print_vendor_struct(uint32_t vendor_length)
+{
+ struct acpi_einj_vendor_info vi;
+ ACPI_EINJ_VENDOR *vendor;
+ uint32_t dbsf;
+
+ vi.buf = malloc(vendor_length);
+ vi.len = vendor_length;
+ if (ioctl(fd, ACPIIO_EINJ_GET_VENDOR, &vi) == -1) {
+ warn("ACPIIO_EINJ_GET_VENDOR");
+ free(vi.buf);
+ return;
+ }
+
+ vendor = vi.buf;
+ dbsf = vendor->PcieId;
+
+ printf("Vendor Extension:\n");
+ printf("\tLength:\t%u\n", vendor->Length);
+ printf("\tPCIe Id:\t%u:%u:%u:%u\n", dbsf >> 24,
+ (dbsf >> 16) & 0xff, (dbsf >> 11) & 0x1f, (dbsf >> 8) & 7);
+ printf("\tVendor Id:\t%04x\n", vendor->VendorId);
+ printf("\tDevice Id:\t%04x\n", vendor->DeviceId);
+ printf("\tRevision:\t%02x\n", vendor->RevisionId);
+ free(vi.buf);
+}
+
+static void
+list_error_types(void)
+{
+ struct acpi_einj_info info;
+
+ if (ioctl(fd, ACPIIO_EINJ_GET_INFO, &info) == -1)
+ err(1, "ACPIIO_EINJ_GET_INFO");
+
+ printf("Supported errors <%#x>:\n", info.error_type);
+ for (u_int i = 0; i < nitems(error_types); i++) {
+ struct error_type_info *eti = &error_types[i];
+
+ if ((info.error_type & eti->bit) == 0)
+ continue;
+ printf("\t%s (%s)\n", eti->name, eti->description);
+ }
+
+ if ((info.error_type & ACPI_EINJ_VENDOR_DEFINED) != 0 &&
+ info.vendor_length != 0) {
+ printf("\n");
+ print_vendor_struct(info.vendor_length);
+ }
+}
+
+static uint32_t
+parse_dbsf(const char *arg)
+{
+ char *cp;
+ unsigned long val[4];
+ u_int i;
+
+ /*
+ * Permit the same format as pciconf. Eventually this should
+ * accept PCI device names, too.
+ */
+ if (strncmp(arg, "pci", 3) == 0)
+ arg += 3;
+
+ /* Read up to 4 colon-separated values. */
+ for (i = 0;; i++) {
+ val[i] = strtoul(arg, &cp, 10);
+ if (cp == arg || val[i] > 255)
+ goto error;
+ if (*cp == ':') {
+ if (i == nitems(val) - 1)
+ goto error;
+ arg = cp + 1;
+ continue;
+ }
+ if (*cp == '\0') {
+ if (i < 3)
+ goto error;
+ break;
+ }
+ goto error;
+ }
+
+ /* If no domain is given, assume a domain of 0. */
+ if (i == 3) {
+ val[3] = val[2];
+ val[2] = val[1];
+ val[1] = val[0];
+ val[0] = 0;
+ }
+
+ /*
+ * To handle ARI, only limit function numbers if the slot is
+ * non-zero.
+ */
+ if (val[2] != 0 && val[3] > PCI_FUNCMAX)
+ goto error;
+
+ return (val[0] << 24 | val[1] << 16 | val[2] << 3 | val[3]);
+
+error:
+ errx(1, "invalid PCIe ID");
+}
+
+static void
+inject_error(int argc, char *argv[])
+{
+ struct acpi_einj_error einj;
+ char *cp;
+ uintmax_t value;
+ int ch;
+
+ memset(&einj, 0, sizeof(einj));
+ while ((ch = getopt(argc, argv, "a:C:P:")) != -1) {
+ switch (ch) {
+ case 'a':
+ einj.memory_address = strtoumax(optarg, &cp, 0);
+ if (*cp != '\0')
+ errx(1, "invalid address");
+ einj.address_flags |= ACPI_EINJ_MEMADDRESS_VALID;
+ break;
+ case 'C':
+ value = strtoumax(optarg, &cp, 0);
+ if (*cp != '\0' || value > UINT32_MAX)
+ errx(1, "invalid APIC ID");
+ einj.apic_id = value;
+ einj.address_flags |= ACPI_EINJ_APICID_VALID;
+ break;
+ case 'P':
+ einj.pcie_id = parse_dbsf(optarg);
+ einj.address_flags |= ACPI_EINJ_PCIE_VALID;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ if (strcmp(argv[0], "vendor") == 0)
+ errx(1, "vendor errors are not supported");
+
+ for (u_int i = 0; i < nitems(error_types); i++) {
+ struct error_type_info *eti = &error_types[i];
+
+ if (strcmp(argv[0], eti->name) == 0) {
+ einj.error_type = eti->bit;
+ break;
+ }
+ }
+ if (einj.error_type == 0)
+ errx(1, "unknown error type");
+
+ if (ioctl(fd, ACPIIO_EINJ_SET_ERROR, &einj) == -1)
+ err(1, "ACPIIO_EINJ_SET_ERROR");
+}
+
+int
+main(int argc, char *argv[])
+{
+ if (argc < 2)
+ usage();
+
+ fd = open("/dev/acpi", O_RDWR);
+ if (fd == -1)
+ err(1, "/dev/acpi");
+
+ if (strcasecmp(argv[1], "list") == 0)
+ list_error_types();
+ else if (strcasecmp(argv[1], "inject") == 0)
+ inject_error(argc - 1, argv + 1);
+ else
+ usage();
+
+ close(fd);
+ return (0);
+}
diff --git a/usr.sbin/adduser/adduser.8 b/usr.sbin/adduser/adduser.8
index e21a7653f250..651e0ac91809 100644
--- a/usr.sbin/adduser/adduser.8
+++ b/usr.sbin/adduser/adduser.8
@@ -27,7 +27,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd December 1, 2024
+.Dd April 19, 2026
.Dt ADDUSER 8
.Os
.Sh NAME
@@ -445,6 +445,7 @@ logfile for
.Xr login.conf 5 ,
.Xr passwd 5 ,
.Xr shells 5 ,
+.Xr groups 7 ,
.Xr pw 8 ,
.Xr pwd_mkdb 8 ,
.Xr rmuser 8 ,
@@ -459,7 +460,7 @@ command appeared in
.An -nosplit
This manual page and the original script, in Perl, was written by
.An Wolfram Schneider Aq Mt wosch@FreeBSD.org .
-The replacement script, written as a Bourne
+The replacement script, written as a POSIX
shell script with some enhancements, and the man page modification that
came with it were done by
.An Mike Makonnen Aq Mt mtm@identd.net .
diff --git a/usr.sbin/adduser/adduser.sh b/usr.sbin/adduser/adduser.sh
index ecac7478befd..96d567d133e0 100644
--- a/usr.sbin/adduser/adduser.sh
+++ b/usr.sbin/adduser/adduser.sh
@@ -580,7 +580,7 @@ get_groups() {
fi
read _input
- [ -n "$_input" ] && ugroups="$_input"
+ [ -n "$_input" ] && ugroups="$(echo "$_input" | tr -s ',:;' ' ')"
}
# get_expire_dates
@@ -1018,7 +1018,7 @@ for _i in $* ; do
done
if [ -n "$readconfig" ] && [ -r "${ADDUSERCONF}" ]; then
. "${ADDUSERCONF}"
-fi
+fi
# Process command-line options
#
diff --git a/usr.sbin/apm/Makefile b/usr.sbin/apm/Makefile
index f9b3e11d22fa..81f47b8ff7b6 100644
--- a/usr.sbin/apm/Makefile
+++ b/usr.sbin/apm/Makefile
@@ -1,6 +1,5 @@
PROG= apm
MAN= apm.8
-MANSUBDIR= /${MACHINE_CPUARCH}
PACKAGE=apm
diff --git a/usr.sbin/apmd/Makefile b/usr.sbin/apmd/Makefile
index bfe59cc7cd7d..9e693c630308 100644
--- a/usr.sbin/apmd/Makefile
+++ b/usr.sbin/apmd/Makefile
@@ -1,7 +1,6 @@
CONFS= apmd.conf
PROG= apmd
MAN= apmd.8
-MANSUBDIR= /i386
SRCS= apmd.c apmdlex.l apmdparse.y y.tab.h
PACKAGE=apm
diff --git a/usr.sbin/autofs/common.h b/usr.sbin/autofs/common.h
index 5bd2abf5ef78..03ca4f8f577b 100644
--- a/usr.sbin/autofs/common.h
+++ b/usr.sbin/autofs/common.h
@@ -58,8 +58,6 @@ struct defined_value {
};
void log_init(int level);
-void log_set_peer_name(const char *name);
-void log_set_peer_addr(const char *addr);
void log_err(int, const char *, ...)
__dead2 __printf0like(2, 3);
void log_errx(int, const char *, ...)
diff --git a/usr.sbin/autofs/log.c b/usr.sbin/autofs/log.c
index 02a889bffbed..dcb9ae9a1fc2 100644
--- a/usr.sbin/autofs/log.c
+++ b/usr.sbin/autofs/log.c
@@ -41,8 +41,6 @@
#include "common.h"
static int log_level = 0;
-static char *peer_name = NULL;
-static char *peer_addr = NULL;
#define MSGBUF_LEN 1024
@@ -54,34 +52,6 @@ log_init(int level)
openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON);
}
-void
-log_set_peer_name(const char *name)
-{
-
- /*
- * XXX: Turn it into assertion?
- */
- if (peer_name != NULL)
- log_errx(1, "%s called twice", __func__);
- if (peer_addr == NULL)
- log_errx(1, "%s called before log_set_peer_addr", __func__);
-
- peer_name = checked_strdup(name);
-}
-
-void
-log_set_peer_addr(const char *addr)
-{
-
- /*
- * XXX: Turn it into assertion?
- */
- if (peer_addr != NULL)
- log_errx(1, "%s called twice", __func__);
-
- peer_addr = checked_strdup(addr);
-}
-
static void
log_common(int priority, int log_errno, const char *fmt, va_list ap)
{
@@ -105,40 +75,14 @@ log_common(int priority, int log_errno, const char *fmt, va_list ap)
}
if (log_errno == -1) {
- if (peer_name != NULL) {
- fprintf(stderr, "%s: %s (%s): %s\n", getprogname(),
- peer_addr, peer_name, msgbuf_strvised);
- syslog(priority, "%s (%s): %s",
- peer_addr, peer_name, msgbuf_strvised);
- } else if (peer_addr != NULL) {
- fprintf(stderr, "%s: %s: %s\n", getprogname(),
- peer_addr, msgbuf_strvised);
- syslog(priority, "%s: %s",
- peer_addr, msgbuf_strvised);
- } else {
- fprintf(stderr, "%s: %s\n", getprogname(), msgbuf_strvised);
- syslog(priority, "%s", msgbuf_strvised);
- }
-
+ fprintf(stderr, "%s: %s\n", getprogname(), msgbuf_strvised);
+ syslog(priority, "%s", msgbuf_strvised);
} else {
errstr = strerror(log_errno);
- if (peer_name != NULL) {
- fprintf(stderr, "%s: %s (%s): %s: %s\n", getprogname(),
- peer_addr, peer_name, msgbuf_strvised, errstr);
- syslog(priority, "%s (%s): %s: %s",
- peer_addr, peer_name, msgbuf_strvised, errstr);
- } else if (peer_addr != NULL) {
- fprintf(stderr, "%s: %s: %s: %s\n", getprogname(),
- peer_addr, msgbuf_strvised, errstr);
- syslog(priority, "%s: %s: %s",
- peer_addr, msgbuf_strvised, errstr);
- } else {
- fprintf(stderr, "%s: %s: %s\n", getprogname(),
- msgbuf_strvised, errstr);
- syslog(priority, "%s: %s",
- msgbuf_strvised, errstr);
- }
+ fprintf(stderr, "%s: %s: %s\n",
+ getprogname(), msgbuf_strvised, errstr);
+ syslog(priority, "%s: %s", msgbuf_strvised, errstr);
}
}
diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile
index 25fb71b5768f..b86c56991039 100644
--- a/usr.sbin/bhyve/Makefile
+++ b/usr.sbin/bhyve/Makefile
@@ -53,6 +53,7 @@ SRCS= \
pci_virtio_net.c \
pci_virtio_rnd.c \
pci_virtio_scsi.c \
+ pci_virtio_scsi_ctl.c \
pci_xhci.c \
qemu_fwcfg.c \
qemu_loader.c \
diff --git a/usr.sbin/bhyve/aarch64/bhyverun_machdep.c b/usr.sbin/bhyve/aarch64/bhyverun_machdep.c
index 10018d082f7e..7d82784131b4 100644
--- a/usr.sbin/bhyve/aarch64/bhyverun_machdep.c
+++ b/usr.sbin/bhyve/aarch64/bhyverun_machdep.c
@@ -100,7 +100,8 @@ bhyve_usage(int code)
"Usage: %s [-CDHhSW]\n"
" %*s [-c [[cpus=]numcpus][,sockets=n][,cores=n][,threads=n]]\n"
" %*s [-k config_file] [-m mem] [-o var=value]\n"
- " %*s [-p vcpu:hostcpu] [-r file] [-s pci] [-U uuid] vmname\n"
+ " %*s [-p vcpuN[-vcpuM]]:hostcpuX[-hostcpuY]\n"
+ " %*s [-r file] [-s pci] [-U uuid] vmname\n"
" -C: include guest memory in core file\n"
" -c: number of CPUs and/or topology specification\n"
" -D: destroy on power-off\n"
@@ -116,7 +117,7 @@ bhyve_usage(int code)
" -U: UUID\n"
" -W: force virtio to use single-vector MSI\n",
progname, (int)strlen(progname), "", (int)strlen(progname), "",
- (int)strlen(progname), "");
+ (int)strlen(progname), "", (int)strlen(progname), "");
exit(code);
}
diff --git a/usr.sbin/bhyve/amd64/bhyverun_machdep.c b/usr.sbin/bhyve/amd64/bhyverun_machdep.c
index 538d031fd1a4..2c8f825b4a7c 100644
--- a/usr.sbin/bhyve/amd64/bhyverun_machdep.c
+++ b/usr.sbin/bhyve/amd64/bhyverun_machdep.c
@@ -37,6 +37,9 @@
#include "acpi.h"
#include "atkbdc.h"
#include "bhyverun.h"
+#ifdef BHYVE_SNAPSHOT
+#include "snapshot.h"
+#endif
#include "bootrom.h"
#include "config.h"
#include "debug.h"
@@ -65,6 +68,9 @@ bhyve_init_config(void)
set_config_bool("x86.strictmsr", true);
set_config_bool("x86.verbosemsr", false);
set_config_value("lpc.fwcfg", "bhyve");
+#ifdef BHYVE_SNAPSHOT
+ set_config_value("rundir", BHYVE_RUN_DIR);
+#endif
}
void
@@ -78,7 +84,8 @@ bhyve_usage(int code)
"Usage: %s [-aCDeHhPSuWwxY]\n"
" %*s [-c [[cpus=]numcpus][,sockets=n][,cores=n][,threads=n]]\n"
" %*s [-G port] [-k config_file] [-l lpc] [-m mem] [-o var=value]\n"
- " %*s [-p vcpu:hostcpu] [-r file] [-s pci] [-U uuid] vmname\n"
+ " %*s [-p vcpuN[-vcpuM]]:hostcpuX[-hostcpuY]\n"
+ " %*s [-r file] [-s pci] [-U uuid] vmname\n"
" -a: local apic is in xAPIC mode (deprecated)\n"
" -C: include guest memory in core file\n"
" -c: number of CPUs and/or topology specification\n"
@@ -108,7 +115,7 @@ bhyve_usage(int code)
" -x: local APIC is in x2APIC mode\n"
" -Y: disable MPtable generation\n",
progname, (int)strlen(progname), "", (int)strlen(progname), "",
- (int)strlen(progname), "");
+ (int)strlen(progname), "", (int)strlen(progname), "");
exit(code);
}
diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8
index be906419d74e..73094f5dc23f 100644
--- a/usr.sbin/bhyve/bhyve.8
+++ b/usr.sbin/bhyve/bhyve.8
@@ -2,6 +2,7 @@
.\" SPDX-License-Identifier: BSD-2-Clause
.\"
.\" Copyright (c) 2013 Peter Grehan
+.\" Copyright (c) 2026 Hans Rosenfeld
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
@@ -25,7 +26,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd January 23, 2026
+.Dd June 5, 2026
.Dt BHYVE 8
.Os
.Sh NAME
@@ -46,8 +47,8 @@
.Op Cm ,threads= Ar n
.Oc
.Sm on
-.Oo Fl f
.Sm off
+.Oo Fl f\~
.Ar name Cm \&,
.Oo
.Cm string No | Cm file
@@ -58,7 +59,7 @@
.Oo
.Sm off
.Fl G\~
-.Oo Ar w Oc
+.Oo Cm w Oc
.Oo Ar bind_address Cm \&: Oc
.Ar port
.Sm on
@@ -79,7 +80,7 @@
.Sm on
.Oc
.Op Fl o Ar var Ns Cm = Ns Ar value
-.Op Fl p Ar vcpu Ns Cm \&: Ns Ar hostcpu
+.Oo Fl p Ar vcpuN Ns Oo - Ns Ar vcpuM Oc Ns Cm \&: Ns Ar hostcpuN Ns Oo - Ns Ar hostcpuM Oc Oc
.Op Fl r Ar file
.Sm off
.Oo Fl s\~
@@ -194,7 +195,7 @@ If a
is specified, bhyve reads the file and adds the file content as fw_cfg data.
.It Fl G Xo
.Sm off
-.Oo Ar w Oc
+.Oo Cm w Oc
.Oo Ar bind_address Cm \&: Oc
.Ar port
.Sm on
@@ -208,7 +209,7 @@ and
to listen for debugger connections.
Only a single debugger may be attached to the debug server at a time.
If the option begins with
-.Sq w ,
+.Cm w ,
.Nm
will pause execution at the first instruction waiting for a debugger to attach.
.It Fl H
@@ -355,19 +356,21 @@ for configuration options.
.It Fl P
Force the guest virtual CPU to exit when a PAUSE instruction is detected.
This option applies only to the amd64 platform.
-.It Fl p Ar vcpu Ns Cm \& : Ns Ar hostcpu
-Pin guest's virtual CPU
-.Em vcpu
+.It Fl p Ar vcpuN Ns Oo - Ns Ar vcpuM Oc Ns Cm \&: Ns Ar hostcpuN Ns Op - Ns Ar hostcpuM
+Pin the guest's virtual CPUs starting from
+.Em vcpuN
to
-.Em hostcpu .
+.Em vcpuM
+to the host CPUs starting from
+.Em hostcpuN
+to
+.Em hostcpuM .
Host CPUs and guest virtual CPUs are numbered starting from 0.
-A
-.Fl p
-option is required for every guest vCPU to be pinned.
-To map a 4 vCPU guest to host CPUs 12-15:
-.Bd -literal
--p 0:12 -p 1:13 -p 2:14 -p 3:15
-.Ed
+The
+.Em vcpuM
+and
+.Em hostcpuM
+arguments may be omitted.
.It Fl r Ar file
Resume a guest from a snapshot.
The guest memory contents are restored from
@@ -680,23 +683,86 @@ In that case, this feature doesn't work as expected.
.Bl -bullet
.Sm off
.It
-.Pa /dev/cam/ctl Oo Ar pp Cm \&. Ar vp Oc Oo Cm \&, Ar scsi-device-options Oc
+.Oo Cm target Ns = Ns Oo ID : Oc Ar path Ns Oc
+.Oo Cm \&, Ar scsi-device-options Oc
+.Oo Cm \&, Ar backend-specific-options Oc
.Sm on
.El
.Pp
+Multiple
+.Pa target
+parameters may be specified, each configuring a different
+.Ar path
+as a distinct SCSI target.
+If the
+.Pa target
+.Ar ID
+is not explicitly configured for a
+.Pa target ,
+the
+.Pa target
+will be assigned the next sequential
+.Ar ID
+following the highest
+.Pa target
+.Ar ID
+used at that point, or 0 if it is the first target configured.
+All
+.Pa target
+.Ar ID Ns s
+must be unique per instance.
+The meaning of the
+.Ar path
+argument is specific to each backend:
+.Bl -column "Backend" "/dev/cam/ctl[pp.vp]"
+.It Sy Backend Ta Sy Path Ta Sy Description
+.It ctl Ta Pa /dev/cam/ctl Ns Oo Ar pp . Ns Ar vp Oc Ta
+The path of a CAM target layer (CTL) device node.
+If no target is configured, a single target backed by
+.Qq /dev/cam/ctl
+will be configured by default.
+.El
+.Pp
The
.Ar scsi-device-options
are:
.Bl -tag -width 10n
-.It Cm iid= Ns Ar IID
-Initiator ID to use when sending requests to specified CTL port.
-The default value is 0.
+.It Cm backend= Ns Ar backend
+The virtio-scsi backend to use.
+The backend name is case-insensitive.
+There is currently only one backend
+.Qq ctl ,
+which is also the default backend.
.It Li bootindex= Ns Ar index
Add the device to the boot order at
.Ar index .
A fw_cfg file is used to specify the boot order.
The guest firmware may ignore or not support this fw_cfg file.
In that case, this feature doesn't work as expected.
+.It Li ctl_ringsz= Ns Ar ringsz
+The ring size to use for the control queue.
+.It Li evt_ringsz= Ns Ar ringsz
+The ring size to use for the event queue.
+.It Li req_ringsz= Ns Ar ringsz
+The ring size to use for each I/O request queue.
+.It Li num_queues= Ns Ar num
+The number of I/O request queues to use.
+.It Li seg_max= Ns Ar num
+The maximum number of segments allowed in a single command.
+.It Li thr_per_q= Ns Ar num
+The number of parallel request processing threads per I/O request queue.
+.El
+.Pp
+The
+.Ar backend-specific-options
+for the
+.Sy CTL
+backend are:
+.Bl -tag -width 10n
+.It Cm iid= Ns Ar IID
+Initiator ID to use when sending requests to
+.Sy CTL .
+The default value is 0.
.El
.Ss 9P device backends
.Bl -bullet
@@ -1339,6 +1405,16 @@ bhyve -c 2 -w -H \\
-n id=0,size=4G,cpus=0-1,domain_policy=prefer:0 \\
numavm
.Ed
+To run a virtual machine with a single vCPU pinned to host CPU 12:
+.Bd -literal -offset indent
+bhyve -c 1 -s 0,hostbridge -s 1,lpc -s 2,virtio-blk,/my/image \\
+ -l com1,stdio -H -P -m 1G -p 0:12 vm1
+.Ed
+To run a virtual machine with 4 vCPUs pinned to host CPUs 12-15:
+.Bd -literal -offset indent
+bhyve -c 4 -s 0,hostbridge -s 1,lpc -s 2,virtio-blk,/my/image \\
+ -l com1,stdio -H -P -m 1G -p 0-3:12-15 vm1
+.Ed
.Sh SEE ALSO
.Xr bhyve 4 ,
.Xr netgraph 4 ,
diff --git a/usr.sbin/bhyve/bhyve_config.5 b/usr.sbin/bhyve/bhyve_config.5
index 429ce3e38138..a12540d054c7 100644
--- a/usr.sbin/bhyve/bhyve_config.5
+++ b/usr.sbin/bhyve/bhyve_config.5
@@ -1,6 +1,7 @@
.\" SPDX-License-Identifier: BSD-2-Clause
.\"
.\" Copyright (c) 2021 John H. Baldwin <jhb@FreeBSD.org>
+.\" Copyright (c) 2026 Hans Rosenfeld
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
@@ -23,7 +24,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd December 26, 2025
+.Dd July 8, 2026
.Dt BHYVE_CONFIG 5
.Os
.Sh NAME
@@ -225,6 +226,16 @@ This value is used for the guest's System Management BIOS System Information str
Stock keeping unit of the chassis.
It's also called product ID or purchase order number.
This value is used for the guest's System Management BIOS System Information structure.
+.It Va oemstring.N Ta string Ta none Ta
+This value is returned for the guest's Nth System Management BIOS System Information OEM Strings structure.
+Configuration can specify up to 255 strings.
+.It Va rundir Ta String Ta Pa /var/run/bhyve/ Ta
+Path to the runtime directory for
+.Xr bhyve 8
+to store its service files, such as the checkpoint socket.
+Overriding this path is useful when running
+.Xr bhyve 8
+as an unprivileged user.
.El
.Ss x86-Specific Settings
.Bl -column "x86.vmexit_on_pause" "integer" "Default"
@@ -241,10 +252,10 @@ Inject a general protection fault if a guest accesses a Model Specific
Register (MSR) that is not emulated.
If this is false, writes are ignored and reads return zero.
.It Va x86.verbosemsr Ta bool Ta false Ta
-Enable verbose MSR print out. When this option is true,
-messages related to reading from (rdmsr) and writing to (wrmsr)
-MSRs on virtual CPUs will be printed out. This can be useful for
-debugging purposes.
+Enable verbose MSR print out.
+When this option is true, messages related to reading from (rdmsr)
+and writing to (wrmsr) MSRs on virtual CPUs will be printed out.
+This can be useful for debugging purposes.
.It Va x86.vmexit_on_hlt Ta bool Ta false Ta
Force a VM exit when a guest CPU executes the
.Dv HLT
@@ -408,7 +419,7 @@ bridge as the backend.
The value of
.Va backend
is passed to
-.Xr nm_open
+.Nm nm_open
to connect to a netmap port.
.It slirp
Use the slirp backend to provide a userspace network stack.
@@ -455,7 +466,12 @@ Either the pathname of a character device or
to use standard input and output of the
.Xr bhyve 8
process.
-.It Va tcp Ta Oo Ar IP Ns : Oc Ns Ar port Ta Ta
+.It Va tcp Ta Xo
+.Sm off
+.Op Ar IP Cm \&:
+.Ar port
+.Sm on
+.Xc Ta Ta
TCP address to listen on for remote connections.
The IP address must be given as a numeric address.
IPv6 addresses must be enclosed in square brackets and
@@ -469,8 +485,8 @@ supports scoped identifiers as described in
Values of PCI register.
.Bl -column "device" "Default"
.It Sy Name Ta Sy Default
-.It Va vendor Ta integer Ta 0x1275 Ta
-.It Va device Ta integer Ta 0x1275 Ta
+.It Va vendor Ta 0x1275
+.It Va device Ta 0x1275
.El
.El
.Ss AHCI Controller Settings
@@ -527,7 +543,12 @@ If specified, it must be a unicast MAC address.
.It Sy Name Ta Sy Format Ta Sy Default Ta Sy Description
.It Va wait Ta bool Ta false Ta
Wait for a remote connection before starting the VM.
-.It Va rfb Ta Oo Ar IP Ns : Oc Ns Ar port Ta 127.0.0.1:5900 Ta
+.It Va rfb Ta Xo
+.Sm off
+.Op Ar IP Cm \&:
+.Ar port
+.Sm on
+.Xc Ta 127.0.0.1:5900 Ta
TCP address to listen on for remote connections.
The IP address must be given as a numeric address.
IPv6 addresses must be enclosed in square brackets and
@@ -761,13 +782,61 @@ If specified, it must be a unicast MAC address.
The largest supported MTU advertised to the guest.
.El
.Ss VirtIO SCSI Settings
-.Bl -column "Name" "integer" "Default"
+.Bl -column "num_queues" "Format" "Default"
.It Sy Name Ta Sy Format Ta Sy Default Ta Sy Description
-.It Va dev Ta path Ta Ta
-The path of a CAM target layer (CTL) device to export:
-.Pa /dev/cam/ctl Ns Oo Ar pp . Ns Ar vp Oc .
-.It Va iid Ta integer Ta 0 Ta
-Initiator ID to use when sending requests to the CTL port.
+.It Va backend Ta string Ta ctl Ta
+The virtio-scsi backend to use (case-insensitive).
+.It Va ctl_ringsz Ta integer Ta 64 Ta
+The ring size of the control queue.
+.It Va evt_ringsz Ta integer Ta 64 Ta
+The ring size of the event queue.
+.It Va req_ringsz Ta integer Ta 64 Ta
+The ring size of each I/O request queue.
+.It Va num_queues Ta integer Ta 1 Ta
+The number of I/O request queues.
+.It Va seg_max Ta integer Ta 64 Ta
+The maximum number of segments allowed in a single command.
+.It Va thr_per_q Ta integer Ta 16 Ta
+The number of parallel request processing threads per I/O request queue.
+.It Va target Ta Oo Va ID : Oc Ns path Ta /dev/cam/ctl Ta
+The backend
+.Ar path
+of a target to configure.
+Optionally, a numeric target
+.Ar ID
+in the range from 0 to 255 may be specified before the
+.Ar path ,
+separated by a colon.
+.El
+.Pp
+The
+.Va target
+variable may be specified multiple times with different
+.Ar path
+arguments to configure multiple distinct SCSI targets.
+If not explicitly configured, the target
+.Ar ID Ns s
+will be assigned sequentially beginning with the highest target
+.Ar ID
+configured so far, or 0 for the first target configured.
+The target
+.Ar ID Ns s
+must be unique within each virtio-scsi instance.
+.Pp
+The meaning of the
+.Ar path
+argument is specific to each backend:
+.Bl -column "Backend" "/dev/cam/ctl[pp.vp]"
+.It Sy Backend Ta Sy Path Ta Sy Description
+.It ctl Ta Pa /dev/cam/ctl Ns Oo Ar pp . Ns Ar vp Oc Ta
+The path of a CAM target layer (CTL) device to configure as a target.
+.El
+.Pp
+The following backend-specific variables are supported for VirtIO SCSI:
+.Bl -column "Backend" "Name" "integer" "Default"
+.It Sy Backend Ta Sy Name Ta Sy Format Ta Sy Default Ta Sy Description
+.It ctl Ta Va iid Ta integer Ta 0 Ta
+Initiator ID to use when sending requests to CTL.
.El
.Sh SEE ALSO
.Xr expand_number 3 ,
diff --git a/usr.sbin/bhyve/bhyverun.c b/usr.sbin/bhyve/bhyverun.c
index 9db62972467c..ace631594544 100644
--- a/usr.sbin/bhyve/bhyverun.c
+++ b/usr.sbin/bhyve/bhyverun.c
@@ -2,6 +2,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2011 NetApp, Inc.
+ * Copyright (c) 2026 Hans Rosenfeld
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -356,18 +357,12 @@ calc_topology(void)
guest_ncpus = ncpus;
}
-int
-bhyve_pincpu_parse(const char *opt)
+static int
+bhyve_pincpu(int vcpu, int pcpu)
{
const char *value;
char *newval;
char key[16];
- int vcpu, pcpu;
-
- if (sscanf(opt, "%d:%d", &vcpu, &pcpu) != 2) {
- fprintf(stderr, "invalid format: %s\n", opt);
- return (-1);
- }
if (vcpu < 0) {
fprintf(stderr, "invalid vcpu '%d'\n", vcpu);
@@ -394,6 +389,34 @@ bhyve_pincpu_parse(const char *opt)
return (0);
}
+int
+bhyve_pincpu_parse(const char *opt)
+{
+ int vcpu_first, vcpu_last, pcpu_first, pcpu_last;
+ int vcpu, pcpu;
+
+ if (sscanf(opt, "%d-%d:%d-%d", &vcpu_first, &vcpu_last, &pcpu_first, &pcpu_last) == 4) {
+ if (vcpu_first > vcpu_last || pcpu_first > pcpu_last) {
+ fprintf(stderr, "invalid range (must be ascending): %s\n", opt);
+ return (-1);
+ }
+ if ((vcpu_last - vcpu_first) != (pcpu_last - pcpu_first)) {
+ fprintf(stderr, "range sizes do not match: %s\n", opt);
+ return (-1);
+ }
+ for (vcpu = vcpu_first, pcpu = pcpu_first; vcpu <= vcpu_last; vcpu++, pcpu++)
+ if (bhyve_pincpu(vcpu, pcpu) != 0)
+ return (-1);
+ return (0);
+ }
+
+ if (sscanf(opt, "%d:%d", &vcpu, &pcpu) == 2)
+ return (bhyve_pincpu(vcpu, pcpu));
+
+ fprintf(stderr, "invalid format: %s\n", opt);
+ return (-1);
+}
+
static void
parse_cpuset(int vcpu, const char *list, cpuset_t *set)
{
@@ -543,15 +566,21 @@ fbsdrun_start_thread(void *param)
{
char tname[MAXCOMLEN + 1];
struct vcpu_info *vi = param;
- int error;
snprintf(tname, sizeof(tname), "vcpu %d", vi->vcpuid);
pthread_set_name_np(pthread_self(), tname);
if (vcpumap[vi->vcpuid] != NULL) {
- error = pthread_setaffinity_np(pthread_self(),
- sizeof(cpuset_t), vcpumap[vi->vcpuid]);
- assert(error == 0);
+ if (pthread_setaffinity_np(pthread_self(),
+ sizeof(cpuset_t), vcpumap[vi->vcpuid]) != 0) {
+ int i;
+ warn("Error pinning vcpu %d to host cpuset@%p", vi->vcpuid,
+ vcpumap[vi->vcpuid]);
+ CPU_FOREACH_ISSET(i, vcpumap[vi->vcpuid]) {
+ warnx("cpu %d enabled\n", i);
+ }
+ exit(BHYVE_EXIT_ERROR);
+ }
}
#ifdef BHYVE_SNAPSHOT
@@ -917,6 +946,12 @@ main(int argc, char *argv[])
}
bsp = vm_vcpu_open(ctx, BSP);
+ if (bsp == NULL) {
+ fprintf(stderr, "Unable to open boot VCPU: %s",
+ strerror(errno));
+ exit(BHYVE_EXIT_ERROR);
+ }
+
max_vcpus = num_vcpus_allowed(ctx, bsp);
if (guest_ncpus > max_vcpus) {
fprintf(stderr, "%d vCPUs requested but only %d available\n",
@@ -935,6 +970,12 @@ main(int argc, char *argv[])
vcpu_info[vcpuid].vcpu = bsp;
else
vcpu_info[vcpuid].vcpu = vm_vcpu_open(ctx, vcpuid);
+
+ if (vcpu_info[vcpuid].vcpu == NULL) {
+ fprintf(stderr, "Unable to open VCPU %d: %s", vcpuid,
+ strerror(errno));
+ exit(BHYVE_EXIT_ERROR);
+ }
}
if (bhyve_init_platform(ctx, bsp) != 0)
@@ -1027,7 +1068,7 @@ main(int argc, char *argv[])
/*
* checkpointing thread for communication with bhyvectl
*/
- if (init_checkpoint_thread(ctx) != 0)
+ if (init_checkpoint_thread(ctx, get_config_value("rundir")) != 0)
errx(EX_OSERR, "Failed to start checkpoint thread");
#endif
diff --git a/usr.sbin/bhyve/block_if.c b/usr.sbin/bhyve/block_if.c
index 32543f9f3a18..0a4f76ae197d 100644
--- a/usr.sbin/bhyve/block_if.c
+++ b/usr.sbin/bhyve/block_if.c
@@ -495,6 +495,7 @@ blockif_open(nvlist_t *nvl, const char *ident)
pthread_once(&blockif_once, blockif_init);
+ bc = NULL;
fd = -1;
extra = 0;
ssopt = 0;
@@ -650,10 +651,13 @@ blockif_open(nvlist_t *nvl, const char *ident)
bc->bc_sectsz = sectsz;
bc->bc_psectsz = psectsz;
bc->bc_psectoff = psectoff;
- pthread_mutex_init(&bc->bc_mtx, NULL);
- pthread_cond_init(&bc->bc_cond, NULL);
+ if (pthread_mutex_init(&bc->bc_mtx, NULL) != 0)
+ goto err;
+ if (pthread_cond_init(&bc->bc_cond, NULL) != 0)
+ goto err;
bc->bc_paused = 0;
- pthread_cond_init(&bc->bc_work_done_cond, NULL);
+ if (pthread_cond_init(&bc->bc_work_done_cond, NULL) != 0)
+ goto err;
TAILQ_INIT(&bc->bc_freeq);
TAILQ_INIT(&bc->bc_pendq);
TAILQ_INIT(&bc->bc_busyq);
@@ -664,13 +668,34 @@ blockif_open(nvlist_t *nvl, const char *ident)
}
for (i = 0; i < BLOCKIF_NUMTHR; i++) {
- pthread_create(&bc->bc_btid[i], NULL, blockif_thr, bc);
+ if (pthread_create(&bc->bc_btid[i], NULL, blockif_thr, bc) != 0) {
+ bc->bc_btid[i] = NULL;
+ goto err;
+ }
snprintf(tname, sizeof(tname), "blk-%s-%d", ident, i);
pthread_set_name_np(bc->bc_btid[i], tname);
}
return (bc);
err:
+ if (bc != NULL) {
+ void *jval;
+ if (bc->bc_cond != NULL) {
+ pthread_mutex_lock(&bc->bc_mtx);
+ bc->bc_closing = 1;
+ pthread_mutex_unlock(&bc->bc_mtx);
+ pthread_cond_broadcast(&bc->bc_cond);
+ for (i = 0; i < BLOCKIF_NUMTHR; i++)
+ if (bc->bc_btid[i] != NULL)
+ pthread_join(bc->bc_btid[i], &jval);
+ pthread_cond_destroy(&bc->bc_cond);
+ }
+ if (bc->bc_mtx != NULL)
+ pthread_mutex_destroy(&bc->bc_mtx);
+ if (bc->bc_work_done_cond != NULL)
+ pthread_cond_destroy(&bc->bc_work_done_cond);
+ free(bc);
+ }
if (fd >= 0)
close(fd);
return (NULL);
@@ -806,6 +831,7 @@ blockif_delete(struct blockif_ctxt *bc, struct blockif_req *breq)
int
blockif_cancel(struct blockif_ctxt *bc, struct blockif_req *breq)
{
+ struct blockif_sig_elem bse;
struct blockif_elem *be;
assert(bc->bc_magic == BLOCKIF_SIG);
@@ -849,11 +875,10 @@ blockif_cancel(struct blockif_ctxt *bc, struct blockif_req *breq)
* Interrupt the processing thread to force it return
* prematurely via it's normal callback path.
*/
+ pthread_mutex_init(&bse.bse_mtx, NULL);
+ pthread_cond_init(&bse.bse_cond, NULL);
while (be->be_status == BST_BUSY) {
- struct blockif_sig_elem bse, *old_head;
-
- pthread_mutex_init(&bse.bse_mtx, NULL);
- pthread_cond_init(&bse.bse_cond, NULL);
+ struct blockif_sig_elem *old_head;
bse.bse_pending = 1;
@@ -872,6 +897,8 @@ blockif_cancel(struct blockif_ctxt *bc, struct blockif_req *breq)
pthread_mutex_unlock(&bse.bse_mtx);
}
+ pthread_mutex_destroy(&bse.bse_mtx);
+ pthread_cond_destroy(&bse.bse_cond);
pthread_mutex_unlock(&bc->bc_mtx);
/*
@@ -895,7 +922,7 @@ blockif_close(struct blockif_ctxt *bc)
pthread_mutex_lock(&bc->bc_mtx);
bc->bc_closing = 1;
if (bc->bc_resize_event != NULL)
- mevent_disable(bc->bc_resize_event);
+ mevent_delete(bc->bc_resize_event);
pthread_mutex_unlock(&bc->bc_mtx);
pthread_cond_broadcast(&bc->bc_cond);
for (i = 0; i < BLOCKIF_NUMTHR; i++)
@@ -908,6 +935,9 @@ blockif_close(struct blockif_ctxt *bc)
*/
bc->bc_magic = 0;
close(bc->bc_fd);
+ pthread_mutex_destroy(&bc->bc_mtx);
+ pthread_cond_destroy(&bc->bc_cond);
+ pthread_cond_destroy(&bc->bc_work_done_cond);
free(bc);
return (0);
diff --git a/usr.sbin/bhyve/iov.c b/usr.sbin/bhyve/iov.c
index 16d7765b437b..637b1f4be304 100644
--- a/usr.sbin/bhyve/iov.c
+++ b/usr.sbin/bhyve/iov.c
@@ -71,8 +71,8 @@ split_iov(struct iovec *iov, size_t *niov1, size_t offset, size_t *niov2)
}
/* The entry iov[count] needs to be split. */
- *niov1 = count + 1;
*niov2 = *niov1 - count;
+ *niov1 = count + 1;
memmove(&iov[count + 1], &iov[count], sizeof(struct iovec) * (*niov2));
iov[count].iov_len = resid;
iov[count + 1].iov_base = (char *)iov[count].iov_base + resid;
@@ -110,16 +110,16 @@ check_iov_len(const struct iovec *iov, size_t niov, size_t len)
return (false);
}
-ssize_t
+size_t
iov_to_buf(const struct iovec *iov, size_t niov, void **buf)
{
size_t ptr, total;
size_t i;
total = count_iov(iov, niov);
- *buf = realloc(*buf, total);
+ *buf = reallocf(*buf, total);
if (*buf == NULL)
- return (-1);
+ return (0);
for (i = 0, ptr = 0; i < niov; i++) {
memcpy((uint8_t *)*buf + ptr, iov[i].iov_base, iov[i].iov_len);
@@ -129,7 +129,7 @@ iov_to_buf(const struct iovec *iov, size_t niov, void **buf)
return (total);
}
-ssize_t
+size_t
buf_to_iov(const void *buf, size_t buflen, const struct iovec *iov, size_t niov)
{
size_t off = 0, len;
@@ -141,5 +141,5 @@ buf_to_iov(const void *buf, size_t buflen, const struct iovec *iov, size_t niov)
off += len;
}
- return ((ssize_t)off);
+ return (off);
}
diff --git a/usr.sbin/bhyve/iov.h b/usr.sbin/bhyve/iov.h
index e14a9bc7e019..788e8da84b8b 100644
--- a/usr.sbin/bhyve/iov.h
+++ b/usr.sbin/bhyve/iov.h
@@ -40,7 +40,7 @@
struct iovec *split_iov(struct iovec *, size_t *, size_t, size_t *);
size_t count_iov(const struct iovec *, size_t);
bool check_iov_len(const struct iovec *, size_t, size_t);
-ssize_t iov_to_buf(const struct iovec *, size_t, void **);
-ssize_t buf_to_iov(const void *, size_t, const struct iovec *, size_t);
+size_t iov_to_buf(const struct iovec *, size_t, void **);
+size_t buf_to_iov(const void *, size_t, const struct iovec *, size_t);
#endif /* _IOV_H_ */
diff --git a/usr.sbin/bhyve/pci_nvme.c b/usr.sbin/bhyve/pci_nvme.c
index 7e6c8bc9d719..b2b9f0ff88a8 100644
--- a/usr.sbin/bhyve/pci_nvme.c
+++ b/usr.sbin/bhyve/pci_nvme.c
@@ -3167,6 +3167,7 @@ pci_nvme_parse_config(struct pci_nvme_softc *sc, nvlist_t *nvl)
char bident[sizeof("XXX:XXX")];
const char *value;
uint32_t sectsz;
+ int val;
sc->max_queues = NVME_QUEUES;
sc->max_qentries = NVME_MAX_QENTRIES;
@@ -3183,20 +3184,21 @@ pci_nvme_parse_config(struct pci_nvme_softc *sc, nvlist_t *nvl)
sc->max_queues = atoi(value);
value = get_config_value_node(nvl, "qsz");
if (value != NULL) {
- sc->max_qentries = atoi(value);
- if (sc->max_qentries <= 0) {
- EPRINTLN("nvme: Invalid qsz option %d",
- sc->max_qentries);
+ val = atoi(value);
+ if (val <= 0 || val > UINT16_MAX) {
+ EPRINTLN("nvme: Invalid qsz option %d", val);
return (-1);
}
+ sc->max_qentries = val;
}
value = get_config_value_node(nvl, "ioslots");
if (value != NULL) {
- sc->ioslots = atoi(value);
- if (sc->ioslots <= 0) {
- EPRINTLN("Invalid ioslots option %d", sc->ioslots);
+ val = atoi(value);
+ if (val <= 0) {
+ EPRINTLN("Invalid ioslots option %d", val);
return (-1);
}
+ sc->ioslots = val;
}
value = get_config_value_node(nvl, "sectsz");
if (value != NULL)
diff --git a/usr.sbin/bhyve/pci_virtio_scsi.c b/usr.sbin/bhyve/pci_virtio_scsi.c
index dafff50fa531..97f1ff3c65d8 100644
--- a/usr.sbin/bhyve/pci_virtio_scsi.c
+++ b/usr.sbin/bhyve/pci_virtio_scsi.c
@@ -35,7 +35,6 @@
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/queue.h>
-#include <sys/sbuf.h>
#include <errno.h>
#include <fcntl.h>
@@ -48,16 +47,6 @@
#include <pthread.h>
#include <pthread_np.h>
-#include <cam/scsi/scsi_all.h>
-#include <cam/scsi/scsi_message.h>
-#include <cam/ctl/ctl.h>
-#include <cam/ctl/ctl_io.h>
-#include <cam/ctl/ctl_backend.h>
-#include <cam/ctl/ctl_ioctl.h>
-#include <cam/ctl/ctl_util.h>
-#include <cam/ctl/ctl_scsi_all.h>
-#include <camlib.h>
-
#include "bhyverun.h"
#include "config.h"
#include "debug.h"
@@ -65,206 +54,18 @@
#include "virtio.h"
#include "iov.h"
-#define VTSCSI_RINGSZ 64
-#define VTSCSI_REQUESTQ 1
-#define VTSCSI_THR_PER_Q 16
-#define VTSCSI_MAXQ (VTSCSI_REQUESTQ + 2)
-#define VTSCSI_MAXSEG 64
-
-#define VTSCSI_IN_HEADER_LEN(_sc) \
- (sizeof(struct pci_vtscsi_req_cmd_rd) + _sc->vss_config.cdb_size)
-
-#define VTSCSI_OUT_HEADER_LEN(_sc) \
- (sizeof(struct pci_vtscsi_req_cmd_wr) + _sc->vss_config.sense_size)
-
-#define VIRTIO_SCSI_MAX_CHANNEL 0
-#define VIRTIO_SCSI_MAX_TARGET 0
-#define VIRTIO_SCSI_MAX_LUN 16383
-
-#define VIRTIO_SCSI_F_INOUT (1 << 0)
-#define VIRTIO_SCSI_F_HOTPLUG (1 << 1)
-#define VIRTIO_SCSI_F_CHANGE (1 << 2)
-
-static int pci_vtscsi_debug = 0;
-#define WPRINTF(msg, params...) PRINTLN("virtio-scsi: " msg, ##params)
-#define DPRINTF(msg, params...) if (pci_vtscsi_debug) WPRINTF(msg, ##params)
+#include "pci_virtio_scsi.h"
-struct pci_vtscsi_config {
- uint32_t num_queues;
- uint32_t seg_max;
- uint32_t max_sectors;
- uint32_t cmd_per_lun;
- uint32_t event_info_size;
- uint32_t sense_size;
- uint32_t cdb_size;
- uint16_t max_channel;
- uint16_t max_target;
- uint32_t max_lun;
-} __attribute__((packed));
-
-/*
- * I/O request state and I/O request queues
- *
- * In addition to the control queue and notification queues, each virtio-scsi
- * device instance has at least one I/O request queue, the state of which is
- * is kept in an array of struct pci_vtscsi_queue in the device softc.
- *
- * Currently there is only one I/O request queue, but it's trivial to support
- * more than one.
- *
- * Each pci_vtscsi_queue has VTSCSI_RINGSZ pci_vtscsi_request structures pre-
- * allocated on vsq_free_requests. For each I/O request coming in on the I/O
- * virtqueue, the request queue handler will take a pci_vtscsi_request off
- * vsq_free_requests, fills in the data from the I/O virtqueue, puts it on
- * vsq_requests, and signals vsq_cv.
- *
- * There are VTSCSI_THR_PER_Q worker threads for each pci_vtscsi_queue which
- * wait on vsq_cv. When signalled, they repeatedly take one pci_vtscsi_request
- * off vsq_requests, construct a ctl_io for it, and hand it off to the CTL ioctl
- * Interface, which processes it synchronously. After completion of the request,
- * the pci_vtscsi_request is re-initialized and put back onto vsq_free_requests.
- *
- * The worker threads exit when vsq_cv is signalled after vsw_exiting was set.
- *
- * There are three mutexes to coordinate the accesses to an I/O request queue:
- * - vsq_rmtx protects vsq_requests and must be held when waiting on vsq_cv
- * - vsq_fmtx protects vsq_free_requests
- * - vsq_qmtx must be held when operating on the underlying virtqueue, vsq_vq
- */
-STAILQ_HEAD(pci_vtscsi_req_queue, pci_vtscsi_request);
-
-struct pci_vtscsi_queue {
- struct pci_vtscsi_softc * vsq_sc;
- struct vqueue_info * vsq_vq;
- pthread_mutex_t vsq_rmtx;
- pthread_mutex_t vsq_fmtx;
- pthread_mutex_t vsq_qmtx;
- pthread_cond_t vsq_cv;
- struct pci_vtscsi_req_queue vsq_requests;
- struct pci_vtscsi_req_queue vsq_free_requests;
- LIST_HEAD(, pci_vtscsi_worker) vsq_workers;
-};
-
-struct pci_vtscsi_worker {
- struct pci_vtscsi_queue * vsw_queue;
- pthread_t vsw_thread;
- bool vsw_exiting;
- LIST_ENTRY(pci_vtscsi_worker) vsw_link;
-};
-
-struct pci_vtscsi_request {
- struct pci_vtscsi_queue * vsr_queue;
- struct iovec vsr_iov[VTSCSI_MAXSEG + SPLIT_IOV_ADDL_IOV];
- struct iovec * vsr_iov_in;
- struct iovec * vsr_iov_out;
- struct iovec * vsr_data_iov_in;
- struct iovec * vsr_data_iov_out;
- struct pci_vtscsi_req_cmd_rd * vsr_cmd_rd;
- struct pci_vtscsi_req_cmd_wr * vsr_cmd_wr;
- union ctl_io * vsr_ctl_io;
- size_t vsr_niov_in;
- size_t vsr_niov_out;
- size_t vsr_data_niov_in;
- size_t vsr_data_niov_out;
- uint32_t vsr_idx;
- STAILQ_ENTRY(pci_vtscsi_request) vsr_link;
-};
-
-/*
- * Per-device softc
- */
-struct pci_vtscsi_softc {
- struct virtio_softc vss_vs;
- struct vqueue_info vss_vq[VTSCSI_MAXQ];
- struct pci_vtscsi_queue vss_queues[VTSCSI_REQUESTQ];
- pthread_mutex_t vss_mtx;
- int vss_iid;
- int vss_ctl_fd;
- uint32_t vss_features;
- struct pci_vtscsi_config vss_config;
+enum pci_vtscsi_walk {
+ PCI_VTSCSI_WALK_CONTINUE = 0,
+ PCI_VTSCSI_WALK_STOP,
};
-#define VIRTIO_SCSI_T_TMF 0
-#define VIRTIO_SCSI_T_TMF_ABORT_TASK 0
-#define VIRTIO_SCSI_T_TMF_ABORT_TASK_SET 1
-#define VIRTIO_SCSI_T_TMF_CLEAR_ACA 2
-#define VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET 3
-#define VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET 4
-#define VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET 5
-#define VIRTIO_SCSI_T_TMF_QUERY_TASK 6
-#define VIRTIO_SCSI_T_TMF_QUERY_TASK_SET 7
-
-/* command-specific response values */
-#define VIRTIO_SCSI_S_FUNCTION_COMPLETE 0
-#define VIRTIO_SCSI_S_FUNCTION_SUCCEEDED 10
-#define VIRTIO_SCSI_S_FUNCTION_REJECTED 11
-
-struct pci_vtscsi_ctrl_tmf {
- const uint32_t type;
- const uint32_t subtype;
- const uint8_t lun[8];
- const uint64_t id;
- uint8_t response;
-} __attribute__((packed));
-
-#define VIRTIO_SCSI_T_AN_QUERY 1
-#define VIRTIO_SCSI_EVT_ASYNC_OPERATIONAL_CHANGE 2
-#define VIRTIO_SCSI_EVT_ASYNC_POWER_MGMT 4
-#define VIRTIO_SCSI_EVT_ASYNC_EXTERNAL_REQUEST 8
-#define VIRTIO_SCSI_EVT_ASYNC_MEDIA_CHANGE 16
-#define VIRTIO_SCSI_EVT_ASYNC_MULTI_HOST 32
-#define VIRTIO_SCSI_EVT_ASYNC_DEVICE_BUSY 64
-
-struct pci_vtscsi_ctrl_an {
- const uint32_t type;
- const uint8_t lun[8];
- const uint32_t event_requested;
- uint32_t event_actual;
- uint8_t response;
-} __attribute__((packed));
-
-/* command-specific response values */
-#define VIRTIO_SCSI_S_OK 0
-#define VIRTIO_SCSI_S_OVERRUN 1
-#define VIRTIO_SCSI_S_ABORTED 2
-#define VIRTIO_SCSI_S_BAD_TARGET 3
-#define VIRTIO_SCSI_S_RESET 4
-#define VIRTIO_SCSI_S_BUSY 5
-#define VIRTIO_SCSI_S_TRANSPORT_FAILURE 6
-#define VIRTIO_SCSI_S_TARGET_FAILURE 7
-#define VIRTIO_SCSI_S_NEXUS_FAILURE 8
-#define VIRTIO_SCSI_S_FAILURE 9
-#define VIRTIO_SCSI_S_INCORRECT_LUN 12
+typedef enum pci_vtscsi_walk pci_vtscsi_walk_t;
+typedef pci_vtscsi_walk_t pci_vtscsi_walk_request_queue_cb_t(
+ struct pci_vtscsi_queue *, struct pci_vtscsi_request *, void *);
-/* task_attr */
-#define VIRTIO_SCSI_S_SIMPLE 0
-#define VIRTIO_SCSI_S_ORDERED 1
-#define VIRTIO_SCSI_S_HEAD 2
-#define VIRTIO_SCSI_S_ACA 3
-
-struct pci_vtscsi_event {
- uint32_t event;
- uint8_t lun[8];
- uint32_t reason;
-} __attribute__((packed));
-
-struct pci_vtscsi_req_cmd_rd {
- const uint8_t lun[8];
- const uint64_t id;
- const uint8_t task_attr;
- const uint8_t prio;
- const uint8_t crn;
- const uint8_t cdb[];
-} __attribute__((packed));
-
-struct pci_vtscsi_req_cmd_wr {
- uint32_t sense_len;
- uint32_t residual;
- uint16_t status_qualifier;
- uint8_t status;
- uint8_t response;
- uint8_t sense[];
-} __attribute__((packed));
+static void pci_vtscsi_print_supported_backends(void);
static void *pci_vtscsi_proc(void *);
static void pci_vtscsi_reset(void *);
@@ -272,18 +73,29 @@ static void pci_vtscsi_neg_features(void *, uint64_t);
static int pci_vtscsi_cfgread(void *, int, int, uint32_t *);
static int pci_vtscsi_cfgwrite(void *, int, int, uint32_t);
-static inline bool pci_vtscsi_check_lun(const uint8_t *);
-static inline int pci_vtscsi_get_lun(const uint8_t *);
+static pci_vtscsi_walk_request_queue_cb_t pci_vtscsi_tmf_handle_abort_task;
+static pci_vtscsi_walk_request_queue_cb_t pci_vtscsi_tmf_handle_abort_task_set;
+static pci_vtscsi_walk_request_queue_cb_t pci_vtscsi_tmf_handle_clear_aca;
+static pci_vtscsi_walk_request_queue_cb_t pci_vtscsi_tmf_handle_clear_task_set;
+static pci_vtscsi_walk_request_queue_cb_t pci_vtscsi_tmf_handle_i_t_nexus_reset;
+static pci_vtscsi_walk_request_queue_cb_t pci_vtscsi_tmf_handle_lun_reset;
+static pci_vtscsi_walk_request_queue_cb_t pci_vtscsi_tmf_handle_query_task;
+static pci_vtscsi_walk_request_queue_cb_t pci_vtscsi_tmf_handle_query_task_set;
+
+static pci_vtscsi_walk_t pci_vtscsi_walk_request_queue(
+ struct pci_vtscsi_queue *, pci_vtscsi_walk_request_queue_cb_t *, void *);
-static void pci_vtscsi_control_handle(struct pci_vtscsi_softc *, void *, size_t);
static void pci_vtscsi_tmf_handle(struct pci_vtscsi_softc *,
struct pci_vtscsi_ctrl_tmf *);
static void pci_vtscsi_an_handle(struct pci_vtscsi_softc *,
struct pci_vtscsi_ctrl_an *);
+static void pci_vtscsi_control_handle(struct pci_vtscsi_softc *, void *,
+ size_t);
static struct pci_vtscsi_request *pci_vtscsi_alloc_request(
struct pci_vtscsi_softc *);
-static void pci_vtscsi_free_request(struct pci_vtscsi_request *);
+static void pci_vtscsi_free_request(struct pci_vtscsi_softc *,
+ struct pci_vtscsi_request *);
static struct pci_vtscsi_request *pci_vtscsi_get_request(
struct pci_vtscsi_req_queue *);
static void pci_vtscsi_put_request(struct pci_vtscsi_req_queue *,
@@ -292,20 +104,25 @@ static void pci_vtscsi_queue_request(struct pci_vtscsi_softc *,
struct vqueue_info *);
static void pci_vtscsi_return_request(struct pci_vtscsi_queue *,
struct pci_vtscsi_request *, int);
-static int pci_vtscsi_request_handle(struct pci_vtscsi_softc *,
+static int pci_vtscsi_request_handle(struct pci_vtscsi_softc *, int,
struct pci_vtscsi_request *);
static void pci_vtscsi_controlq_notify(void *, struct vqueue_info *);
static void pci_vtscsi_eventq_notify(void *, struct vqueue_info *);
static void pci_vtscsi_requestq_notify(void *, struct vqueue_info *);
-static int pci_vtscsi_init_queue(struct pci_vtscsi_softc *,
+
+static int pci_vtscsi_add_target_config(nvlist_t *, const char *, int);
+static int pci_vtscsi_init_queue(struct pci_vtscsi_softc *,
struct pci_vtscsi_queue *, int);
-static void pci_vtscsi_destroy_queue(struct pci_vtscsi_queue *);
+static void pci_vtscsi_destroy_queue(struct pci_vtscsi_softc *,
+ struct pci_vtscsi_queue *);
static int pci_vtscsi_init(struct pci_devinst *, nvlist_t *);
+SET_DECLARE(pci_vtscsi_backend_set, struct pci_vtscsi_backend);
+
static struct virtio_consts vtscsi_vi_consts = {
.vc_name = "vtscsi",
- .vc_nvq = VTSCSI_MAXQ,
+ .vc_nvq = VTSCSI_DEF_REQUESTQ + VIRTIO_SCSI_ADDL_Q,
.vc_cfgsize = sizeof(struct pci_vtscsi_config),
.vc_reset = pci_vtscsi_reset,
.vc_cfgread = pci_vtscsi_cfgread,
@@ -314,16 +131,51 @@ static struct virtio_consts vtscsi_vi_consts = {
.vc_hv_caps = VIRTIO_RING_F_INDIRECT_DESC,
};
+static const struct pci_vtscsi_config vtscsi_config = {
+ .num_queues = VTSCSI_DEF_REQUESTQ,
+ /* Leave room for the request and the response. */
+ .seg_max = VTSCSI_DEF_MAXSEG - VIRTIO_SCSI_HDR_SEG,
+ .max_sectors = 0,
+ .cmd_per_lun = 1,
+ .event_info_size = sizeof(struct pci_vtscsi_event),
+ .sense_size = 96,
+ .cdb_size = 32,
+ .max_channel = VIRTIO_SCSI_MAX_CHANNEL,
+ .max_target = VIRTIO_SCSI_MAX_TARGET,
+ .max_lun = VIRTIO_SCSI_MAX_LUN
+};
+
+int pci_vtscsi_debug = 0;
+
+
+static void
+pci_vtscsi_print_supported_backends(void)
+{
+ struct pci_vtscsi_backend **vbpp;
+
+ if (SET_COUNT(pci_vtscsi_backend_set) == 0) {
+ printf("No virtio-scsi backends available");
+ return;
+ }
+
+ SET_FOREACH(vbpp, pci_vtscsi_backend_set) {
+ struct pci_vtscsi_backend *vbp = *vbpp;
+ printf("%s\n", vbp->vsb_name);
+ }
+}
+
static void *
pci_vtscsi_proc(void *arg)
{
struct pci_vtscsi_worker *worker = (struct pci_vtscsi_worker *)arg;
struct pci_vtscsi_queue *q = worker->vsw_queue;
struct pci_vtscsi_softc *sc = q->vsq_sc;
- int iolen;
for (;;) {
struct pci_vtscsi_request *req;
+ uint8_t target;
+ int iolen;
+ int fd;
pthread_mutex_lock(&q->vsq_rmtx);
@@ -338,11 +190,15 @@ pci_vtscsi_proc(void *arg)
req = pci_vtscsi_get_request(&q->vsq_requests);
pthread_mutex_unlock(&q->vsq_rmtx);
- DPRINTF("I/O request lun %d, data_niov_in %zu, data_niov_out "
- "%zu", pci_vtscsi_get_lun(req->vsr_cmd_rd->lun),
+ target = pci_vtscsi_get_target(sc, req->vsr_cmd_rd->lun);
+ fd = sc->vss_targets[target].vst_fd;
+
+ DPRINTF("I/O request tgt %u, lun %d, data_niov_in %zu, "
+ "data_niov_out %zu", target,
+ pci_vtscsi_get_lun(sc, req->vsr_cmd_rd->lun),
req->vsr_data_niov_in, req->vsr_data_niov_out);
- iolen = pci_vtscsi_request_handle(sc, req);
+ iolen = pci_vtscsi_request_handle(sc, fd, req);
pci_vtscsi_return_request(q, req, iolen);
}
@@ -359,20 +215,11 @@ pci_vtscsi_reset(void *vsc)
vi_reset_dev(&sc->vss_vs);
/* initialize config structure */
- sc->vss_config = (struct pci_vtscsi_config){
- .num_queues = VTSCSI_REQUESTQ,
- /* Leave room for the request and the response. */
- .seg_max = VTSCSI_MAXSEG - 2,
- /* CTL apparently doesn't have a limit here */
- .max_sectors = INT32_MAX,
- .cmd_per_lun = 1,
- .event_info_size = sizeof(struct pci_vtscsi_event),
- .sense_size = 96,
- .cdb_size = 32,
- .max_channel = VIRTIO_SCSI_MAX_CHANNEL,
- .max_target = VIRTIO_SCSI_MAX_TARGET,
- .max_lun = VIRTIO_SCSI_MAX_LUN
- };
+ sc->vss_config = sc->vss_default_config;
+
+ sc->vss_config.max_target = MAX(1, sc->vss_num_target) - 1;
+
+ sc->vss_backend->vsb_reset(sc);
}
static void
@@ -402,119 +249,284 @@ pci_vtscsi_cfgwrite(void *vsc __unused, int offset __unused, int size __unused,
}
/*
- * LUN address parsing
- *
- * The LUN address consists of 8 bytes. While the spec describes this as 0x01,
- * followed by the target byte, followed by a "single-level LUN structure",
- * this is actually the same as a hierarchical LUN address as defined by SAM-5,
- * consisting of four levels of addressing, where in each level the two MSB of
- * byte 0 select the address mode used in the remaining bits and bytes.
- *
- *
- * Only the first two levels are acutally used by virtio-scsi:
- *
- * Level 1: 0x01, 0xTT: Peripheral Device Addressing: Bus 1, Target 0-255
- * Level 2: 0xLL, 0xLL: Peripheral Device Addressing: Bus MBZ, LUN 0-255
- * or: Flat Space Addressing: LUN (0-16383)
- * Level 3 and 4: not used, MBZ
- *
- * Currently, we only support Target 0.
- *
- * Alternatively, the first level may contain an extended LUN address to select
- * the REPORT_LUNS well-known logical unit:
+ * ABORT TASK: Abort the specifed task queued for this LUN.
*
- * Level 1: 0xC1, 0x01: Extended LUN Adressing, Well-Known LUN 1 (REPORT_LUNS)
- * Level 2, 3, and 4: not used, MBZ
- *
- * The virtio spec says that we SHOULD implement the REPORT_LUNS well-known
- * logical unit but we currently don't.
+ * We can stop once we have found the specified task queued for this LUN.
+ */
+static pci_vtscsi_walk_t
+pci_vtscsi_tmf_handle_abort_task(struct pci_vtscsi_queue *q,
+ struct pci_vtscsi_request *req, void *arg)
+{
+ struct pci_vtscsi_ctrl_tmf *tmf = arg;
+
+ assert(tmf->subtype == VIRTIO_SCSI_T_TMF_ABORT_TASK);
+
+ if (pci_vtscsi_get_target(q->vsq_sc, tmf->lun) !=
+ pci_vtscsi_get_target(q->vsq_sc, req->vsr_cmd_rd->lun))
+ return (PCI_VTSCSI_WALK_CONTINUE);
+
+ if (pci_vtscsi_get_lun(q->vsq_sc, tmf->lun) !=
+ pci_vtscsi_get_lun(q->vsq_sc, req->vsr_cmd_rd->lun))
+ return (PCI_VTSCSI_WALK_CONTINUE);
+
+ if (tmf->id != req->vsr_cmd_rd->id)
+ return (PCI_VTSCSI_WALK_CONTINUE);
+
+ req->vsr_cmd_wr->response = VIRTIO_SCSI_S_ABORTED;
+ STAILQ_REMOVE(&q->vsq_requests, req, pci_vtscsi_request, vsr_link);
+ pci_vtscsi_return_request(q, req, 0);
+
+ return (PCI_VTSCSI_WALK_STOP);
+}
+
+/*
+ * ABORT TASK SET: Abort all tasks queued for this LUN.
+ */
+static pci_vtscsi_walk_t
+pci_vtscsi_tmf_handle_abort_task_set(struct pci_vtscsi_queue *q,
+ struct pci_vtscsi_request *req, void *arg)
+{
+ struct pci_vtscsi_ctrl_tmf *tmf = arg;
+
+ assert(tmf->subtype == VIRTIO_SCSI_T_TMF_ABORT_TASK_SET);
+
+ if (pci_vtscsi_get_target(q->vsq_sc, tmf->lun) !=
+ pci_vtscsi_get_target(q->vsq_sc, req->vsr_cmd_rd->lun))
+ return (PCI_VTSCSI_WALK_CONTINUE);
+
+ if (pci_vtscsi_get_lun(q->vsq_sc, tmf->lun) !=
+ pci_vtscsi_get_lun(q->vsq_sc, req->vsr_cmd_rd->lun))
+ return (PCI_VTSCSI_WALK_CONTINUE);
+
+ req->vsr_cmd_wr->response = VIRTIO_SCSI_S_ABORTED;
+ STAILQ_REMOVE(&q->vsq_requests, req, pci_vtscsi_request, vsr_link);
+ pci_vtscsi_return_request(q, req, 0);
+
+ return (PCI_VTSCSI_WALK_CONTINUE);
+}
+
+/*
+ * CLEAR ACA: Clear ACA (auto contingent allegiance) state.
+ */
+static pci_vtscsi_walk_t
+pci_vtscsi_tmf_handle_clear_aca(struct pci_vtscsi_queue *q __unused,
+ struct pci_vtscsi_request *req __unused, void *arg)
+{
+ struct pci_vtscsi_ctrl_tmf *tmf = arg;
+
+ assert(tmf->subtype == VIRTIO_SCSI_T_TMF_CLEAR_ACA);
+
+ /*
+ * We don't implement handling of NACA=1 in the CONTROL byte at all.
+ *
+ * Thus, we probably should start filtering NORMACA in INQUIRY and
+ * reject any command that sets NACA=1.
+ *
+ * In any case, there isn't anything we need to do with our queued
+ * requests, so stop right here.
+ */
+
+ return (PCI_VTSCSI_WALK_STOP);
+}
+
+/*
+ * CLEAR TASK SET: Clear all tasks queued for this LUN.
*
- * According to the virtio spec, these are the only LUNS address formats to be
- * used with virtio-scsi.
+ * All tasks in our queue were placed there by us, so there can be no other
+ * I_T nexus involved. Hence, this is handled the same as ABORT TASK SET.
*/
+static pci_vtscsi_walk_t
+pci_vtscsi_tmf_handle_clear_task_set(struct pci_vtscsi_queue *q,
+ struct pci_vtscsi_request *req, void *arg)
+{
+ struct pci_vtscsi_ctrl_tmf *tmf = arg;
+
+ assert(tmf->subtype == VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET);
+
+ if (pci_vtscsi_get_target(q->vsq_sc, tmf->lun) !=
+ pci_vtscsi_get_target(q->vsq_sc, req->vsr_cmd_rd->lun))
+ return (PCI_VTSCSI_WALK_CONTINUE);
+
+ if (pci_vtscsi_get_lun(q->vsq_sc, tmf->lun) !=
+ pci_vtscsi_get_lun(q->vsq_sc, req->vsr_cmd_rd->lun))
+ return (PCI_VTSCSI_WALK_CONTINUE);
+
+ req->vsr_cmd_wr->response = VIRTIO_SCSI_S_ABORTED;
+ STAILQ_REMOVE(&q->vsq_requests, req, pci_vtscsi_request, vsr_link);
+ pci_vtscsi_return_request(q, req, 0);
+
+ return (PCI_VTSCSI_WALK_CONTINUE);
+}
/*
- * Check that the given LUN address conforms to the virtio spec, does not
- * address an unknown target, and especially does not address the REPORT_LUNS
- * well-known logical unit.
+ * I_T NEXUS RESET: Abort all tasks queued for any LUN of this target.
*/
-static inline bool
-pci_vtscsi_check_lun(const uint8_t *lun)
+static pci_vtscsi_walk_t
+pci_vtscsi_tmf_handle_i_t_nexus_reset(struct pci_vtscsi_queue *q,
+ struct pci_vtscsi_request *req, void *arg)
{
- if (lun[0] == 0xC1)
- return (false);
+ struct pci_vtscsi_ctrl_tmf *tmf = arg;
- if (lun[0] != 0x01)
- return (false);
+ assert(tmf->subtype == VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET);
- if (lun[1] != 0x00)
- return (false);
+ if (pci_vtscsi_get_target(q->vsq_sc, tmf->lun) !=
+ pci_vtscsi_get_target(q->vsq_sc, req->vsr_cmd_rd->lun))
+ return (PCI_VTSCSI_WALK_CONTINUE);
- if (lun[2] != 0x00 && (lun[2] & 0xc0) != 0x40)
- return (false);
+ /*
+ * T10 "06-026r4 SAM-4 TASK ABORTED status clarifications" indicates
+ * that we should actually return ABORTED here, but other documents
+ * such as the VirtIO spec suggest RESET.
+ */
+ req->vsr_cmd_wr->response = VIRTIO_SCSI_S_RESET;
+ STAILQ_REMOVE(&q->vsq_requests, req, pci_vtscsi_request, vsr_link);
+ pci_vtscsi_return_request(q, req, 0);
- if (lun[4] != 0 || lun[5] != 0 || lun[6] != 0 || lun[7] != 0)
- return (false);
+ return (PCI_VTSCSI_WALK_CONTINUE);
+}
- return (true);
+/*
+ * LOGICAL UNIT RESET: Abort all tasks queued for this LUN.
+ */
+static pci_vtscsi_walk_t
+pci_vtscsi_tmf_handle_lun_reset(struct pci_vtscsi_queue *q,
+ struct pci_vtscsi_request *req, void *arg)
+{
+ struct pci_vtscsi_ctrl_tmf *tmf = arg;
+
+ assert(tmf->subtype == VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET);
+
+ if (pci_vtscsi_get_target(q->vsq_sc, tmf->lun) !=
+ pci_vtscsi_get_target(q->vsq_sc, req->vsr_cmd_rd->lun))
+ return (PCI_VTSCSI_WALK_CONTINUE);
+
+ if (pci_vtscsi_get_lun(q->vsq_sc, tmf->lun) !=
+ pci_vtscsi_get_lun(q->vsq_sc, req->vsr_cmd_rd->lun))
+ return (PCI_VTSCSI_WALK_CONTINUE);
+
+ /*
+ * T10 "06-026r4 SAM-4 TASK ABORTED status clarifications" indicates
+ * that we should actually return ABORTED here, but other documents
+ * such as the VirtIO spec suggest RESET.
+ */
+ req->vsr_cmd_wr->response = VIRTIO_SCSI_S_RESET;
+ STAILQ_REMOVE(&q->vsq_requests, req, pci_vtscsi_request, vsr_link);
+ pci_vtscsi_return_request(q, req, 0);
+
+ return (PCI_VTSCSI_WALK_CONTINUE);
}
/*
- * Get the LUN id from a LUN address.
+ * QUERY TASK: Is the specified task present in this LUN?
+ *
+ * We can stop once we have found the specified task queued for this LUN.
+ *
+ * Note that this function may cause false negatives under the following
+ * rare circumstances:
+ * (1) the specified task is still in the virtqueue, not yet having been
+ * processed by pci_vtscsi_requestq_notify()
+ * (2) the specified task was actively being processed by a worker thread
+ * but not yet processed by the backend by the time the QUERY TASK
+ * request was handled by the backend
*
- * Every code path using this function must have called pci_vtscsi_check_lun()
- * before to make sure the LUN address is valid.
+ * While a false negative may be confusing for a guest OS looking for the
+ * state of an I/O request it sent, it is not considered a fatal error of
+ * any kind and is easy to recover from. Also, in both of the above cases,
+ * the QUERY TASK TMF request would need to overtake the I/O request in
+ * question, which can only happen if the TMF request is sent immediately
+ * after the I/O request. While it is technically perfectly fine for a
+ * guest to do so, any normal use of QUERY TASK would involve a certain
+ * delay before the TMF request is sent, giving the I/O request time to
+ * be processed.
*/
-static inline int
-pci_vtscsi_get_lun(const uint8_t *lun)
+static pci_vtscsi_walk_t
+pci_vtscsi_tmf_handle_query_task(struct pci_vtscsi_queue *q,
+ struct pci_vtscsi_request *req, void *arg)
{
- assert(lun[0] == 0x01);
- assert(lun[1] == 0x00);
- assert(lun[2] == 0x00 || (lun[2] & 0xc0) == 0x40);
+ struct pci_vtscsi_ctrl_tmf *tmf = arg;
- return (((lun[2] << 8) | lun[3]) & 0x3fff);
+ assert(tmf->subtype == VIRTIO_SCSI_T_TMF_QUERY_TASK);
+
+ if (pci_vtscsi_get_target(q->vsq_sc, tmf->lun) !=
+ pci_vtscsi_get_target(q->vsq_sc, req->vsr_cmd_rd->lun))
+ return (PCI_VTSCSI_WALK_CONTINUE);
+
+ if (pci_vtscsi_get_lun(q->vsq_sc, tmf->lun) !=
+ pci_vtscsi_get_lun(q->vsq_sc, req->vsr_cmd_rd->lun))
+ return (PCI_VTSCSI_WALK_CONTINUE);
+
+ if (tmf->id != req->vsr_cmd_rd->id)
+ return (PCI_VTSCSI_WALK_CONTINUE);
+
+ tmf->response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED;
+ return (PCI_VTSCSI_WALK_STOP);
}
-static void
-pci_vtscsi_control_handle(struct pci_vtscsi_softc *sc, void *buf,
- size_t bufsize)
+/*
+ * QUERY TASK SET: Are there any tasks present in this LUN?
+ *
+ * We can stop as soon as we've found at least one task queued for this LUN.
+ */
+static pci_vtscsi_walk_t
+pci_vtscsi_tmf_handle_query_task_set(struct pci_vtscsi_queue *q,
+ struct pci_vtscsi_request *req, void *arg)
{
- struct pci_vtscsi_ctrl_tmf *tmf;
- struct pci_vtscsi_ctrl_an *an;
- uint32_t type;
+ struct pci_vtscsi_ctrl_tmf *tmf = arg;
- if (bufsize < sizeof(uint32_t)) {
- WPRINTF("ignoring truncated control request");
- return;
- }
+ assert(tmf->subtype == VIRTIO_SCSI_T_TMF_QUERY_TASK_SET);
- type = *(uint32_t *)buf;
+ if (pci_vtscsi_get_target(q->vsq_sc, tmf->lun) !=
+ pci_vtscsi_get_target(q->vsq_sc, req->vsr_cmd_rd->lun))
+ return (PCI_VTSCSI_WALK_CONTINUE);
- if (type == VIRTIO_SCSI_T_TMF) {
- if (bufsize != sizeof(*tmf)) {
- WPRINTF("ignoring tmf request with size %zu", bufsize);
- return;
- }
- tmf = (struct pci_vtscsi_ctrl_tmf *)buf;
- pci_vtscsi_tmf_handle(sc, tmf);
- } else if (type == VIRTIO_SCSI_T_AN_QUERY) {
- if (bufsize != sizeof(*an)) {
- WPRINTF("ignoring AN request with size %zu", bufsize);
- return;
- }
- an = (struct pci_vtscsi_ctrl_an *)buf;
- pci_vtscsi_an_handle(sc, an);
+ if (pci_vtscsi_get_lun(q->vsq_sc, tmf->lun) !=
+ pci_vtscsi_get_lun(q->vsq_sc, req->vsr_cmd_rd->lun))
+ return (PCI_VTSCSI_WALK_CONTINUE);
+
+ tmf->response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED;
+ return (PCI_VTSCSI_WALK_STOP);
+}
+
+static pci_vtscsi_walk_t
+pci_vtscsi_walk_request_queue(struct pci_vtscsi_queue *q,
+ pci_vtscsi_walk_request_queue_cb_t cb, void *arg)
+{
+ struct pci_vtscsi_request *req, *tmp;
+
+ STAILQ_FOREACH_SAFE(req, &q->vsq_requests, vsr_link, tmp) {
+ if (cb(q, req, arg) == PCI_VTSCSI_WALK_STOP)
+ return (PCI_VTSCSI_WALK_STOP);
}
+
+ return (PCI_VTSCSI_WALK_CONTINUE);
}
+static pci_vtscsi_walk_request_queue_cb_t *const pci_vtscsi_tmf_handler_cb[] = {
+ pci_vtscsi_tmf_handle_abort_task,
+ pci_vtscsi_tmf_handle_abort_task_set,
+ pci_vtscsi_tmf_handle_clear_aca,
+ pci_vtscsi_tmf_handle_clear_task_set,
+ pci_vtscsi_tmf_handle_i_t_nexus_reset,
+ pci_vtscsi_tmf_handle_lun_reset,
+ pci_vtscsi_tmf_handle_query_task,
+ pci_vtscsi_tmf_handle_query_task_set
+};
+
static void
pci_vtscsi_tmf_handle(struct pci_vtscsi_softc *sc,
struct pci_vtscsi_ctrl_tmf *tmf)
{
- union ctl_io *io;
- int err;
+ uint8_t target;
+ int fd;
+
+ if (tmf->subtype > VIRTIO_SCSI_T_TMF_MAX_FUNC) {
+ WPRINTF("pci_vtscsi_tmf_handle: invalid subtype %u",
+ tmf->subtype);
+ tmf->response = VIRTIO_SCSI_S_FUNCTION_REJECTED;
+ return;
+ }
- if (pci_vtscsi_check_lun(tmf->lun) == false) {
+ if (pci_vtscsi_check_lun(sc, tmf->lun) == false) {
DPRINTF("TMF request to invalid LUN %.2hhx%.2hhx-%.2hhx%.2hhx-"
"%.2hhx%.2hhx-%.2hhx%.2hhx", tmf->lun[0], tmf->lun[1],
tmf->lun[2], tmf->lun[3], tmf->lun[4], tmf->lun[5],
@@ -524,78 +536,145 @@ pci_vtscsi_tmf_handle(struct pci_vtscsi_softc *sc,
return;
}
- io = ctl_scsi_alloc_io(sc->vss_iid);
- if (io == NULL) {
- WPRINTF("failed to allocate ctl_io: err=%d (%s)",
- errno, strerror(errno));
+ target = pci_vtscsi_get_target(sc, tmf->lun);
- tmf->response = VIRTIO_SCSI_S_FAILURE;
- return;
- }
+ fd = sc->vss_targets[target].vst_fd;
- ctl_scsi_zero_io(io);
+ DPRINTF("TMF request tgt %d, lun %d, subtype %d, id %lu",
+ target, pci_vtscsi_get_lun(sc, tmf->lun), tmf->subtype, tmf->id);
- io->io_hdr.io_type = CTL_IO_TASK;
- io->io_hdr.nexus.initid = sc->vss_iid;
- io->io_hdr.nexus.targ_lun = pci_vtscsi_get_lun(tmf->lun);
- io->taskio.tag_type = CTL_TAG_SIMPLE;
- io->taskio.tag_num = tmf->id;
- io->io_hdr.flags |= CTL_FLAG_USER_TAG;
+ /*
+ * Lock out all the worker threads from processing any waiting requests
+ * while we're processing the TMF request. This also effectively blocks
+ * pci_vtscsi_requestq_notify() from adding any new requests to the
+ * request queue. This in turn means we will miss any I/O requests which
+ * may still be in the virtqueue.
+ *
+ * This does not prevent any requests currently being processed by the
+ * backend from being completed and returned, which we must guarantee to
+ * adhere to the ordering requirements for any TMF function which aborts
+ * tasks.
+ */
+ for (uint32_t i = 0; i < sc->vss_config.num_queues; i++) {
+ struct pci_vtscsi_queue *q = &sc->vss_queues[i];
- switch (tmf->subtype) {
- case VIRTIO_SCSI_T_TMF_ABORT_TASK:
- io->taskio.task_action = CTL_TASK_ABORT_TASK;
- break;
+ pthread_mutex_lock(&q->vsq_rmtx);
+ }
- case VIRTIO_SCSI_T_TMF_ABORT_TASK_SET:
- io->taskio.task_action = CTL_TASK_ABORT_TASK_SET;
- break;
+ /*
+ * The backend may set response to FAILURE for the TMF request.
+ *
+ * The default response of all TMF functions is FUNCTION COMPLETE if
+ * there was no error, regardless of whether it actually succeeded or
+ * not. The two notable exceptions are QUERY TASK and QUERY TASK SET,
+ * which will explicitly return FUNCTION SUCCEEDED if the specified
+ * task or any task was active in the target/LUN, respectively.
+ *
+ * Thus, we will call the backend first. Only if the response we get is
+ * FUNCTION COMPLETE we'll continue processing the TMF function on our
+ * queues. Note that there's a slim chance that we're racing against a
+ * worker thread that is actively processing an I/O request, which may
+ * lead to our TMF request being processed by the backend before the
+ * same I/O request, in which case it won't be on any queue either.
+ */
+ sc->vss_backend->vsb_tmf_hdl(sc, fd, tmf);
- case VIRTIO_SCSI_T_TMF_CLEAR_ACA:
- io->taskio.task_action = CTL_TASK_CLEAR_ACA;
- break;
+ if (tmf->response != VIRTIO_SCSI_S_FUNCTION_COMPLETE) {
+ /*
+ * If this is either a FAILURE or FUNCTION REJECTED, we must
+ * not continue to process the TMF function on our queued
+ * requests.
+ *
+ * If it is FUNCTION SUCCEEDED, we do not need to process the
+ * TMF function on our queued requests.
+ *
+ * If it is anything else, log a warning, but handle it the
+ * same as above.
+ */
+ if (tmf->response != VIRTIO_SCSI_S_FAILURE &&
+ tmf->response != VIRTIO_SCSI_S_FUNCTION_REJECTED &&
+ tmf->response != VIRTIO_SCSI_S_FUNCTION_SUCCEEDED) {
+ WPRINTF("pci_vtscsi_tmf_hdl: unexpected response from "
+ "backend: %d", tmf->response);
+ }
+ } else {
+ pci_vtscsi_walk_t ret = PCI_VTSCSI_WALK_CONTINUE;
+ uint32_t i;
- case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET:
- io->taskio.task_action = CTL_TASK_CLEAR_TASK_SET;
- break;
+ for (i = 0; i < sc->vss_config.num_queues; i++) {
+ struct pci_vtscsi_queue *q = &sc->vss_queues[i];
- case VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET:
- io->taskio.task_action = CTL_TASK_I_T_NEXUS_RESET;
- break;
+ ret = pci_vtscsi_walk_request_queue(q,
+ pci_vtscsi_tmf_handler_cb[tmf->subtype], tmf);
- case VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET:
- io->taskio.task_action = CTL_TASK_LUN_RESET;
- break;
+ if (ret == PCI_VTSCSI_WALK_STOP)
+ break;
+ }
+ }
- case VIRTIO_SCSI_T_TMF_QUERY_TASK:
- io->taskio.task_action = CTL_TASK_QUERY_TASK;
- break;
+ /* Unlock the request queues before we return. */
+ for (uint32_t i = 0; i < sc->vss_config.num_queues; i++) {
+ struct pci_vtscsi_queue *q = &sc->vss_queues[i];
- case VIRTIO_SCSI_T_TMF_QUERY_TASK_SET:
- io->taskio.task_action = CTL_TASK_QUERY_TASK_SET;
- break;
+ pthread_mutex_unlock(&q->vsq_rmtx);
}
+}
- if (pci_vtscsi_debug) {
- struct sbuf *sb = sbuf_new_auto();
- ctl_io_sbuf(io, sb);
- sbuf_finish(sb);
- DPRINTF("%s", sbuf_data(sb));
- sbuf_delete(sb);
+static void
+pci_vtscsi_an_handle(struct pci_vtscsi_softc *sc, struct pci_vtscsi_ctrl_an *an)
+{
+ int target;
+ int fd;
+
+ if (pci_vtscsi_check_lun(sc, an->lun) == false) {
+ DPRINTF("AN request to invalid LUN %.2hhx%.2hhx-%.2hhx%.2hhx-"
+ "%.2hhx%.2hhx-%.2hhx%.2hhx", an->lun[0], an->lun[1],
+ an->lun[2], an->lun[3], an->lun[4], an->lun[5], an->lun[6],
+ an->lun[7]);
+ an->response = VIRTIO_SCSI_S_BAD_TARGET;
+ return;
}
- err = ioctl(sc->vss_ctl_fd, CTL_IO, io);
- if (err != 0)
- WPRINTF("CTL_IO: err=%d (%s)", errno, strerror(errno));
+ target = pci_vtscsi_get_target(sc, an->lun);
+
+ fd = sc->vss_targets[target].vst_fd;
+
+ DPRINTF("AN request tgt %d, lun %d, event requested %x",
+ target, pci_vtscsi_get_lun(sc, an->lun), an->event_requested);
- tmf->response = io->taskio.task_status;
- ctl_scsi_free_io(io);
+ sc->vss_backend->vsb_an_hdl(sc, fd, an);
}
static void
-pci_vtscsi_an_handle(struct pci_vtscsi_softc *sc __unused,
- struct pci_vtscsi_ctrl_an *an __unused)
+pci_vtscsi_control_handle(struct pci_vtscsi_softc *sc, void *buf,
+ size_t bufsize)
{
+ uint32_t type;
+
+ if (bufsize < sizeof(uint32_t)) {
+ WPRINTF("ignoring truncated control request");
+ return;
+ }
+
+ type = *(uint32_t *)buf;
+
+ if (type == VIRTIO_SCSI_T_TMF) {
+ if (bufsize != sizeof(struct pci_vtscsi_ctrl_tmf)) {
+ WPRINTF("ignoring TMF request with size %zu", bufsize);
+ return;
+ }
+
+ pci_vtscsi_tmf_handle(sc, buf);
+ } else if (type == VIRTIO_SCSI_T_AN_QUERY) {
+ if (bufsize != sizeof(struct pci_vtscsi_ctrl_an)) {
+ WPRINTF("ignoring AN request with size %zu", bufsize);
+ return;
+ }
+
+ pci_vtscsi_an_handle(sc, buf);
+ } else {
+ WPRINTF("ignoring unknown control request type = %u", type);
+ }
}
static struct pci_vtscsi_request *
@@ -607,6 +686,11 @@ pci_vtscsi_alloc_request(struct pci_vtscsi_softc *sc)
if (req == NULL)
goto fail;
+ req->vsr_iov = calloc(sc->vss_config.seg_max + VIRTIO_SCSI_HDR_SEG +
+ SPLIT_IOV_ADDL_IOV, sizeof(struct iovec));
+ if (req->vsr_iov == NULL)
+ goto fail;
+
req->vsr_cmd_rd = calloc(1, VTSCSI_IN_HEADER_LEN(sc));
if (req->vsr_cmd_rd == NULL)
goto fail;
@@ -614,10 +698,9 @@ pci_vtscsi_alloc_request(struct pci_vtscsi_softc *sc)
if (req->vsr_cmd_wr == NULL)
goto fail;
- req->vsr_ctl_io = ctl_scsi_alloc_io(sc->vss_iid);
- if (req->vsr_ctl_io == NULL)
+ req->vsr_backend = sc->vss_backend->vsb_req_alloc(sc);
+ if (req->vsr_backend == NULL)
goto fail;
- ctl_scsi_zero_io(req->vsr_ctl_io);
return (req);
@@ -625,20 +708,23 @@ fail:
EPRINTLN("failed to allocate request: %s", strerror(errno));
if (req != NULL)
- pci_vtscsi_free_request(req);
+ pci_vtscsi_free_request(sc, req);
return (NULL);
}
static void
-pci_vtscsi_free_request(struct pci_vtscsi_request *req)
+pci_vtscsi_free_request(struct pci_vtscsi_softc *sc,
+ struct pci_vtscsi_request *req)
{
- if (req->vsr_ctl_io != NULL)
- ctl_scsi_free_io(req->vsr_ctl_io);
+ if (req->vsr_backend != NULL)
+ sc->vss_backend->vsb_req_free(req->vsr_backend);
if (req->vsr_cmd_rd != NULL)
free(req->vsr_cmd_rd);
if (req->vsr_cmd_wr != NULL)
free(req->vsr_cmd_wr);
+ if (req->vsr_iov != NULL)
+ free(req->vsr_iov);
free(req);
}
@@ -666,18 +752,23 @@ pci_vtscsi_put_request(struct pci_vtscsi_req_queue *req_queue,
static void
pci_vtscsi_queue_request(struct pci_vtscsi_softc *sc, struct vqueue_info *vq)
{
- struct pci_vtscsi_queue *q = &sc->vss_queues[vq->vq_num - 2];
+ struct pci_vtscsi_queue *q;
struct pci_vtscsi_request *req;
struct vi_req vireq;
- int n;
+ size_t res __maybe_unused;
+ int n, numseg;
+
+ q = &sc->vss_queues[vq->vq_num - VIRTIO_SCSI_ADDL_Q];
pthread_mutex_lock(&q->vsq_fmtx);
req = pci_vtscsi_get_request(&q->vsq_free_requests);
assert(req != NULL);
pthread_mutex_unlock(&q->vsq_fmtx);
- n = vq_getchain(vq, req->vsr_iov, VTSCSI_MAXSEG, &vireq);
- assert(n >= 1 && n <= VTSCSI_MAXSEG);
+ numseg = (int)(sc->vss_config.seg_max + VIRTIO_SCSI_HDR_SEG);
+
+ n = vq_getchain(vq, req->vsr_iov, numseg, &vireq);
+ assert(n >= 1 && n <= numseg);
req->vsr_idx = vireq.idx;
req->vsr_queue = q;
@@ -747,11 +838,12 @@ pci_vtscsi_queue_request(struct pci_vtscsi_softc *sc, struct vqueue_info *vq)
* This will have to change if we begin allowing config space writes
* to change sense size.
*/
- assert(iov_to_buf(req->vsr_iov_in, req->vsr_niov_in,
- (void **)&req->vsr_cmd_rd) == VTSCSI_IN_HEADER_LEN(q->vsq_sc));
+ res = iov_to_buf(req->vsr_iov_in, req->vsr_niov_in,
+ (void **)&req->vsr_cmd_rd);
+ assert(res == VTSCSI_IN_HEADER_LEN(q->vsq_sc));
/* Make sure this request addresses a valid LUN. */
- if (pci_vtscsi_check_lun(req->vsr_cmd_rd->lun) == false) {
+ if (pci_vtscsi_check_lun(sc, req->vsr_cmd_rd->lun) == false) {
DPRINTF("I/O request to invalid LUN "
"%.2hhx%.2hhx-%.2hhx%.2hhx-%.2hhx%.2hhx-%.2hhx%.2hhx",
req->vsr_cmd_rd->lun[0], req->vsr_cmd_rd->lun[1],
@@ -775,9 +867,11 @@ static void
pci_vtscsi_return_request(struct pci_vtscsi_queue *q,
struct pci_vtscsi_request *req, int iolen)
{
+ struct pci_vtscsi_softc *sc = q->vsq_sc;
+ void *iov = req->vsr_iov;
void *cmd_rd = req->vsr_cmd_rd;
void *cmd_wr = req->vsr_cmd_wr;
- void *ctl_io = req->vsr_ctl_io;
+ void *backend = req->vsr_backend;
int idx = req->vsr_idx;
DPRINTF("request <idx=%d> completed, response %d", idx,
@@ -786,15 +880,18 @@ pci_vtscsi_return_request(struct pci_vtscsi_queue *q,
iolen += buf_to_iov(cmd_wr, VTSCSI_OUT_HEADER_LEN(q->vsq_sc),
req->vsr_iov_out, req->vsr_niov_out);
- ctl_scsi_zero_io(req->vsr_ctl_io);
+ sc->vss_backend->vsb_req_clear(backend);
+ memset(iov, 0, sizeof(struct iovec) * (sc->vss_config.seg_max +
+ VIRTIO_SCSI_HDR_SEG + SPLIT_IOV_ADDL_IOV));
memset(cmd_rd, 0, VTSCSI_IN_HEADER_LEN(q->vsq_sc));
memset(cmd_wr, 0, VTSCSI_OUT_HEADER_LEN(q->vsq_sc));
memset(req, 0, sizeof(struct pci_vtscsi_request));
+ req->vsr_iov = iov;
req->vsr_cmd_rd = cmd_rd;
req->vsr_cmd_wr = cmd_wr;
- req->vsr_ctl_io = ctl_io;
+ req->vsr_backend = backend;
pthread_mutex_lock(&q->vsq_fmtx);
pci_vtscsi_put_request(&q->vsq_free_requests, req);
@@ -807,100 +904,26 @@ pci_vtscsi_return_request(struct pci_vtscsi_queue *q,
}
static int
-pci_vtscsi_request_handle(struct pci_vtscsi_softc *sc,
+pci_vtscsi_request_handle(struct pci_vtscsi_softc *sc, int fd,
struct pci_vtscsi_request *req)
{
- union ctl_io *io = req->vsr_ctl_io;
- void *ext_data_ptr = NULL;
- uint32_t ext_data_len = 0, ext_sg_entries = 0;
- int err, nxferred;
-
- io->io_hdr.nexus.initid = sc->vss_iid;
- io->io_hdr.nexus.targ_lun = pci_vtscsi_get_lun(req->vsr_cmd_rd->lun);
-
- io->io_hdr.io_type = CTL_IO_SCSI;
-
- if (req->vsr_data_niov_in > 0) {
- ext_data_ptr = (void *)req->vsr_data_iov_in;
- ext_sg_entries = req->vsr_data_niov_in;
- ext_data_len = count_iov(req->vsr_data_iov_in,
- req->vsr_data_niov_in);
- io->io_hdr.flags |= CTL_FLAG_DATA_OUT;
- } else if (req->vsr_data_niov_out > 0) {
- ext_data_ptr = (void *)req->vsr_data_iov_out;
- ext_sg_entries = req->vsr_data_niov_out;
- ext_data_len = count_iov(req->vsr_data_iov_out,
- req->vsr_data_niov_out);
- io->io_hdr.flags |= CTL_FLAG_DATA_IN;
- }
-
- io->scsiio.sense_len = sc->vss_config.sense_size;
- io->scsiio.tag_num = req->vsr_cmd_rd->id;
- io->io_hdr.flags |= CTL_FLAG_USER_TAG;
- switch (req->vsr_cmd_rd->task_attr) {
- case VIRTIO_SCSI_S_ORDERED:
- io->scsiio.tag_type = CTL_TAG_ORDERED;
- break;
- case VIRTIO_SCSI_S_HEAD:
- io->scsiio.tag_type = CTL_TAG_HEAD_OF_QUEUE;
- break;
- case VIRTIO_SCSI_S_ACA:
- io->scsiio.tag_type = CTL_TAG_ACA;
- break;
- case VIRTIO_SCSI_S_SIMPLE:
- default:
- io->scsiio.tag_type = CTL_TAG_SIMPLE;
- break;
- }
- io->scsiio.ext_sg_entries = ext_sg_entries;
- io->scsiio.ext_data_ptr = ext_data_ptr;
- io->scsiio.ext_data_len = ext_data_len;
- io->scsiio.ext_data_filled = 0;
- io->scsiio.cdb_len = sc->vss_config.cdb_size;
- memcpy(io->scsiio.cdb, req->vsr_cmd_rd->cdb, sc->vss_config.cdb_size);
-
- if (pci_vtscsi_debug) {
- struct sbuf *sb = sbuf_new_auto();
- ctl_io_sbuf(io, sb);
- sbuf_finish(sb);
- DPRINTF("%s", sbuf_data(sb));
- sbuf_delete(sb);
- }
-
- err = ioctl(sc->vss_ctl_fd, CTL_IO, io);
- if (err != 0) {
- WPRINTF("CTL_IO: err=%d (%s)", errno, strerror(errno));
- req->vsr_cmd_wr->response = VIRTIO_SCSI_S_FAILURE;
- } else {
- req->vsr_cmd_wr->sense_len =
- MIN(io->scsiio.sense_len, sc->vss_config.sense_size);
- req->vsr_cmd_wr->residual = ext_data_len -
- io->scsiio.ext_data_filled;
- req->vsr_cmd_wr->status = io->scsiio.scsi_status;
- req->vsr_cmd_wr->response = VIRTIO_SCSI_S_OK;
- memcpy(&req->vsr_cmd_wr->sense, &io->scsiio.sense_data,
- req->vsr_cmd_wr->sense_len);
- }
-
- nxferred = io->scsiio.ext_data_filled;
- return (nxferred);
+ return (sc->vss_backend->vsb_req_hdl(sc, fd, req));
}
static void
pci_vtscsi_controlq_notify(void *vsc, struct vqueue_info *vq)
{
- struct pci_vtscsi_softc *sc;
- struct iovec iov[VTSCSI_MAXSEG];
+ struct pci_vtscsi_softc *sc = vsc;
+ int numseg = (int)(sc->vss_config.seg_max + VIRTIO_SCSI_HDR_SEG);
+ struct iovec *iov = calloc(numseg, sizeof (struct iovec));
struct vi_req req;
void *buf = NULL;
size_t bufsize;
int n;
- sc = vsc;
-
while (vq_has_descs(vq)) {
- n = vq_getchain(vq, iov, VTSCSI_MAXSEG, &req);
- assert(n >= 1 && n <= VTSCSI_MAXSEG);
+ n = vq_getchain(vq, iov, numseg, &req);
+ assert(n >= 1 && n <= numseg);
bufsize = iov_to_buf(iov, n, &buf);
pci_vtscsi_control_handle(sc, buf, bufsize);
@@ -913,6 +936,7 @@ pci_vtscsi_controlq_notify(void *vsc, struct vqueue_info *vq)
}
vq_endchains(vq, 1); /* Generate interrupt if appropriate. */
free(buf);
+ free(iov);
}
static void
@@ -935,10 +959,10 @@ pci_vtscsi_init_queue(struct pci_vtscsi_softc *sc,
{
struct pci_vtscsi_worker *workers;
char tname[MAXCOMLEN + 1];
- int i;
+ uint32_t i;
queue->vsq_sc = sc;
- queue->vsq_vq = &sc->vss_vq[num + 2];
+ queue->vsq_vq = &sc->vss_vq[num];
pthread_mutex_init(&queue->vsq_rmtx, NULL);
pthread_mutex_init(&queue->vsq_fmtx, NULL);
@@ -948,7 +972,7 @@ pci_vtscsi_init_queue(struct pci_vtscsi_softc *sc,
STAILQ_INIT(&queue->vsq_free_requests);
LIST_INIT(&queue->vsq_workers);
- for (i = 0; i < VTSCSI_RINGSZ; i++) {
+ for (i = 0; i < sc->vss_req_ringsz; i++) {
struct pci_vtscsi_request *req;
req = pci_vtscsi_alloc_request(sc);
@@ -958,11 +982,11 @@ pci_vtscsi_init_queue(struct pci_vtscsi_softc *sc,
pci_vtscsi_put_request(&queue->vsq_free_requests, req);
}
- workers = calloc(VTSCSI_THR_PER_Q, sizeof(struct pci_vtscsi_worker));
+ workers = calloc(sc->vss_thr_per_q, sizeof(struct pci_vtscsi_worker));
if (workers == NULL)
goto fail;
- for (i = 0; i < VTSCSI_THR_PER_Q; i++) {
+ for (i = 0; i < sc->vss_thr_per_q; i++) {
workers[i].vsw_queue = queue;
pthread_create(&workers[i].vsw_thread, NULL, &pci_vtscsi_proc,
@@ -976,26 +1000,26 @@ pci_vtscsi_init_queue(struct pci_vtscsi_softc *sc,
return (0);
fail:
- pci_vtscsi_destroy_queue(queue);
+ pci_vtscsi_destroy_queue(sc, queue);
return (-1);
-
}
static void
-pci_vtscsi_destroy_queue(struct pci_vtscsi_queue *queue)
+pci_vtscsi_destroy_queue(struct pci_vtscsi_softc *sc,
+ struct pci_vtscsi_queue *queue)
{
if (queue->vsq_sc == NULL)
return;
- for (int i = VTSCSI_RINGSZ; i > 0; i--) {
+ for (int i = sc->vss_req_ringsz; i > 0; i--) {
struct pci_vtscsi_request *req;
if (STAILQ_EMPTY(&queue->vsq_free_requests))
break;
req = pci_vtscsi_get_request(&queue->vsq_free_requests);
- pci_vtscsi_free_request(req);
+ pci_vtscsi_free_request(queue->vsq_sc, req);
}
pthread_cond_destroy(&queue->vsq_cv);
@@ -1004,61 +1028,385 @@ pci_vtscsi_destroy_queue(struct pci_vtscsi_queue *queue)
pthread_mutex_destroy(&queue->vsq_rmtx);
}
+/*
+ * Create a target config node, return target id. If the target number isn't
+ * given as part of the path argument, use last_id + 1.
+ */
+static int
+pci_vtscsi_add_target_config(nvlist_t *nvl, const char *path, int last_id)
+{
+ uint64_t target;
+ char *id;
+ char tmp[4];
+
+ if (path == NULL) {
+ EPRINTLN("target path must be specified");
+ return (-1);
+ }
+
+ if (path[0] != '/' && (id = strchr(path, ':')) != NULL) {
+ const char *errstr;
+ int len = id - path;
+
+ id = strndup(path, len);
+ if (id == NULL) {
+ EPRINTLN("failed to get id string: %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ target = strtonumx(id, 0, VIRTIO_SCSI_MAX_TARGET, &errstr, 0);
+ if (errstr != NULL) {
+ EPRINTLN("invalid target %s: target ID is %s", id,
+ errstr);
+ free(id);
+ return (-1);
+ }
+
+ free(id);
+ path += len + 1;
+ } else {
+ target = last_id + 1;
+
+ if (target > VIRTIO_SCSI_MAX_TARGET) {
+ EPRINTLN("max target (%d) reached, can't add another",
+ VIRTIO_SCSI_MAX_TARGET);
+ return (-1);
+ }
+ }
+
+ snprintf(tmp, sizeof(tmp), "%lu", target);
+
+ if (get_config_value_node(nvl, tmp) != NULL) {
+ EPRINTLN("cannot add '%s' as target %s: already exits as '%s'",
+ path, tmp, get_config_value_node(nvl, tmp));
+ return (-1);
+ }
+
+ set_config_value_node(nvl, tmp, path);
+
+ return (target);
+}
+
+/*
+ * The following forms are accepted for legacy config options to configure a
+ * single target:
+ *
+ * (0) -s B:D:F,virtio-scsi
+ * (1) -s B:D:F,virtio-scsi,<dev>
+ * (2) -s B:D:F,virtio-scsi,<dev>,<name=value>,...
+ * (3) -s B:D:F,virtio-scsi,<name=value>,...
+ * (4) -s B:D:F,virtio-scsi,<name=value>
+ *
+ * To configure multiple targets, the following form is accepted:
+ * (5) -s B:D:F,virtio-scsi,[target=[id:]<dev>,...]
+ */
static int
pci_vtscsi_legacy_config(nvlist_t *nvl, const char *opts)
{
- char *cp, *devname;
+ int last_id = -1;
+ char *config, *tofree, *name, *value;
+ nvlist_t *targets;
+ size_t n;
+ /* Make sure no one accidentally sets "dev" anymore. */
+ (void) create_relative_config_node(nvl, "dev");
+
+ targets = create_relative_config_node(nvl, "target");
+
+ /* Legacy form (0) is handled in pci_vtscsi_init(). */
if (opts == NULL)
return (0);
- cp = strchr(opts, ',');
- if (cp == NULL) {
- set_config_value_node(nvl, "dev", opts);
+ if (strcmp("help", opts) == 0) {
+ pci_vtscsi_print_supported_backends();
+ exit(0);
+ }
+
+ n = strcspn(opts, ",=");
+
+ /* Handle legacy form (1) and (2). */
+ if (opts[n] == ',' || opts[n] == '\0') {
+ char *tmp = strndup(opts, n);
+
+ last_id = pci_vtscsi_add_target_config(targets, tmp, last_id);
+ free(tmp);
+
+ if (last_id < 0)
+ return (-1);
+
+ opts += n;
+ if (opts[0] == ',' && opts[1] != '\0')
+ opts++;
+ }
+
+ /* If this was form (1), we're done. */
+ if (opts[0] == '\0')
return (0);
+
+ /*
+ * For form (2), (3), (4), and (5), parse the remaining options.
+ *
+ * Contrary to other options, multiple target=<dev> options create a new
+ * target for each such option.
+ *
+ * For compatibility reasons we also accept dev=<dev> options for
+ * targets.
+ */
+ config = tofree = strdup(opts);
+ while ((name = strsep(&config, ",")) != NULL) {
+ value = strchr(name, '=');
+ if (value != NULL)
+ *value++ = '\0';
+
+ if (strcmp(name, "dev") == 0 || strcmp(name, "target") == 0) {
+ int new_id = pci_vtscsi_add_target_config(targets,
+ value, last_id);
+
+ if (new_id < 0) {
+ free(tofree);
+ return (-1);
+ }
+
+ if (new_id > last_id)
+ last_id = new_id;
+
+ } else if (value != NULL) {
+ set_config_value_node(nvl, name, value);
+ } else {
+ set_config_bool_node(nvl, name, true);
+ }
}
- devname = strndup(opts, cp - opts);
- set_config_value_node(nvl, "dev", devname);
- free(devname);
- return (pci_parse_legacy_config(nvl, cp + 1));
+
+ free(tofree);
+ return (0);
+}
+
+static int
+pci_vtscsi_count_targets(const char *prefix __unused,
+ const nvlist_t *parent __unused, const char *name, int type, void *arg)
+{
+ struct pci_vtscsi_softc *sc = arg;
+ const char *errstr;
+ uint64_t target;
+
+ if (type != NV_TYPE_STRING) {
+ EPRINTLN("invalid target \"%s\" type: not a string", name);
+ errno = EINVAL;
+ return (-1);
+ }
+
+ target = strtonumx(name, 0, VIRTIO_SCSI_MAX_TARGET, &errstr, 0);
+ if (errstr != NULL) {
+ EPRINTLN("invalid target %s: target ID is %s", name, errstr);
+ return (-1);
+ }
+
+ if (target >= sc->vss_num_target)
+ sc->vss_num_target = target + 1;
+
+ return (0);
+}
+
+static int
+pci_vtscsi_init_target(const char *prefix __unused, const nvlist_t *parent,
+ const char *name, int type, void *arg)
+{
+ struct pci_vtscsi_softc *sc = arg;
+ const char *value;
+ const char *errstr;
+ uint64_t target;
+ int ret;
+
+ assert(type == NV_TYPE_STRING);
+
+ /*
+ * Get the numeric value of the target id from 'name'.
+ */
+ target = strtonumx(name, 0, sc->vss_num_target - 1, &errstr, 0);
+ assert(errstr == NULL);
+ sc->vss_targets[target].vst_target = target;
+
+ /*
+ * 'value' contains the backend path. Call the backend to open it.
+ */
+ value = nvlist_get_string(parent, name);
+ ret = sc->vss_backend->vsb_open(sc, value, target);
+ if (ret != 0)
+ EPRINTLN("cannot open target %lu at %s: %s", target, value,
+ strerror(errno));
+ return (ret);
+}
+
+static int
+pci_vtscsi_get_config_num(nvlist_t *nvl, const char *name, uint32_t lim_lo,
+ uint32_t lim_hi, uint32_t *res)
+{
+ const char *value;
+ const char *errstr;
+ long long val;
+
+ value = get_config_value_node(nvl, name);
+ if (value == NULL)
+ return (0);
+
+ val = strtonumx(value, lim_lo, lim_hi, &errstr, 0);
+ if (errstr != NULL) {
+ EPRINTLN("Invalid value for %s: %s", name, value);
+ return (-1);
+ }
+
+ *res = (uint32_t)val;
+ return (0);
}
static int
pci_vtscsi_init(struct pci_devinst *pi, nvlist_t *nvl)
{
struct pci_vtscsi_softc *sc;
- const char *devname, *value;
- int err;
- int i;
+ struct pci_vtscsi_backend *backend, **vbpp;
+ const char *value;
+ uint32_t val;
+ size_t i;
+ int q, err;
sc = calloc(1, sizeof(struct pci_vtscsi_softc));
if (sc == NULL)
return (-1);
- value = get_config_value_node(nvl, "iid");
- if (value != NULL)
- sc->vss_iid = strtoul(value, NULL, 10);
+ sc->vss_vi_consts = vtscsi_vi_consts;
+ sc->vss_ctl_ringsz = VTSCSI_DEF_RINGSZ;
+ sc->vss_evt_ringsz = VTSCSI_DEF_RINGSZ;
+ sc->vss_req_ringsz = VTSCSI_DEF_RINGSZ;
+ sc->vss_thr_per_q = VTSCSI_DEF_THR_PER_Q;
+ sc->vss_default_config = vtscsi_config;
value = get_config_value_node(nvl, "bootindex");
if (value != NULL) {
if (pci_emul_add_boot_device(pi, atoi(value))) {
EPRINTLN("Invalid bootindex %d", atoi(value));
+ errno = EINVAL;
+ goto fail;
+ }
+ }
+
+ val = vtscsi_config.seg_max;
+ if (pci_vtscsi_get_config_num(nvl, "seg_max", VTSCSI_MIN_MAXSEG,
+ VTSCSI_MAX_MAXSEG, &val) != 0)
+ goto fail;
+ sc->vss_default_config.seg_max = val;
+
+ val = vtscsi_config.num_queues;
+ if (pci_vtscsi_get_config_num(nvl, "num_queues", VTSCSI_MIN_REQUESTQ,
+ VTSCSI_MAX_REQUESTQ, &val) != 0)
+ goto fail;
+ sc->vss_default_config.num_queues = val;
+
+ /*
+ * num_queues is only the number of request queues, but nvq must
+ * account for the control and event queues.
+ */
+ sc->vss_vi_consts.vc_nvq = val + VIRTIO_SCSI_ADDL_Q;
+
+ /*
+ * Allocate queues early, so that they're there for the call to
+ * vi_softc_linkup().
+ */
+ sc->vss_vq = calloc(sc->vss_vi_consts.vc_nvq,
+ sizeof(struct vqueue_info));
+ if (sc->vss_vq == NULL) {
+ EPRINTLN("can't allocate space for %d virtqueues",
+ sc->vss_vi_consts.vc_nvq);
+ goto fail;
+ }
+
+ sc->vss_queues = calloc(sc->vss_default_config.num_queues,
+ sizeof(struct pci_vtscsi_queue));
+ if (sc->vss_queues == NULL) {
+ EPRINTLN("can't allocate space for %d request queues",
+ sc->vss_config.num_queues);
+ goto fail;
+ }
+
+ if (pci_vtscsi_get_config_num(nvl, "ctl_ringsz", VTSCSI_MIN_RINGSZ,
+ VTSCSI_MAX_RINGSZ, &sc->vss_ctl_ringsz) != 0)
+ goto fail;
+
+ if (pci_vtscsi_get_config_num(nvl, "evt_ringsz", VTSCSI_MIN_RINGSZ,
+ VTSCSI_MAX_RINGSZ, &sc->vss_evt_ringsz) != 0)
+ goto fail;
+
+ if (pci_vtscsi_get_config_num(nvl, "req_ringsz", VTSCSI_MIN_RINGSZ,
+ VTSCSI_MAX_RINGSZ, &sc->vss_req_ringsz) != 0)
+ goto fail;
+
+ if (pci_vtscsi_get_config_num(nvl, "thr_per_q", VTSCSI_MIN_THR_PER_Q,
+ VTSCSI_MAX_THR_PER_Q, &sc->vss_thr_per_q) != 0)
+ goto fail;
+
+ value = get_config_value_node(nvl, "backend");
+ if (value == NULL) {
+ if (SET_COUNT(pci_vtscsi_backend_set) == 0) {
+ WPRINTF("No virtio-scsi backends available");
+ errno = EINVAL;
+ goto fail;
+ }
+ backend = SET_ITEM(pci_vtscsi_backend_set, 0);
+ } else {
+ backend = NULL;
+ SET_FOREACH(vbpp, pci_vtscsi_backend_set) {
+ if (strcasecmp(value, (*vbpp)->vsb_name) == 0) {
+ backend = *vbpp;
+ break;
+ }
+ }
+ if (backend == NULL) {
+ WPRINTF("No such virtio-scsi backend: %s", value);
+ errno = EINVAL;
goto fail;
}
}
- devname = get_config_value_node(nvl, "dev");
- if (devname == NULL)
- devname = "/dev/cam/ctl";
- sc->vss_ctl_fd = open(devname, O_RDWR);
- if (sc->vss_ctl_fd < 0) {
- WPRINTF("cannot open %s: %s", devname, strerror(errno));
+ err = backend->vsb_init(sc, backend, nvl);
+ if (err != 0) {
+ errno = EINVAL;
goto fail;
}
+ nvl = find_relative_config_node(nvl, "target");
+ if (nvl != NULL) {
+ err = walk_config_nodes("", nvl, sc, pci_vtscsi_count_targets);
+ if (err != 0)
+ goto fail;
+ }
+
+ if (sc->vss_num_target > 0) {
+ sc->vss_targets = malloc(sc->vss_num_target *
+ sizeof(struct pci_vtscsi_target));
+ if (sc->vss_targets == NULL) {
+ EPRINTLN("can't allocate space for %lu targets",
+ sc->vss_num_target);
+ goto fail;
+ }
+
+ memset(sc->vss_targets, -1, sc->vss_num_target *
+ sizeof(struct pci_vtscsi_target));
+
+ err = walk_config_nodes("", nvl, sc, pci_vtscsi_init_target);
+ if (err != 0)
+ goto fail;
+ }
+
+ /*
+ * All targets should be open now and have a valid fd.
+ */
+ for (i = 0; i < sc->vss_num_target; i++)
+ if (sc->vss_targets[i].vst_target == i)
+ assert(sc->vss_targets[i].vst_fd > 0);
+
pthread_mutex_init(&sc->vss_mtx, NULL);
- vi_softc_linkup(&sc->vss_vs, &vtscsi_vi_consts, sc, pi, sc->vss_vq);
+ vi_softc_linkup(&sc->vss_vs, &sc->vss_vi_consts, sc, pi, sc->vss_vq);
sc->vss_vs.vs_mtx = &sc->vss_mtx;
/*
@@ -1074,20 +1422,22 @@ pci_vtscsi_init(struct pci_devinst *pi, nvlist_t *nvl)
pci_vtscsi_reset(sc);
pthread_mutex_unlock(&sc->vss_mtx);
- /* controlq */
- sc->vss_vq[0].vq_qsize = VTSCSI_RINGSZ;
+ /* virtqueue 0: control queue */
+ sc->vss_vq[0].vq_qsize = sc->vss_ctl_ringsz;
sc->vss_vq[0].vq_notify = pci_vtscsi_controlq_notify;
- /* eventq */
- sc->vss_vq[1].vq_qsize = VTSCSI_RINGSZ;
+ /* virtqueue 1: event queue */
+ sc->vss_vq[1].vq_qsize = sc->vss_evt_ringsz;
sc->vss_vq[1].vq_notify = pci_vtscsi_eventq_notify;
- /* request queues */
- for (i = 2; i < VTSCSI_MAXQ; i++) {
- sc->vss_vq[i].vq_qsize = VTSCSI_RINGSZ;
- sc->vss_vq[i].vq_notify = pci_vtscsi_requestq_notify;
+ /* virtqueue 2-n: request queues */
+ for (q = VIRTIO_SCSI_ADDL_Q; q < sc->vss_vi_consts.vc_nvq; q++) {
+ int rq = q - VIRTIO_SCSI_ADDL_Q;
- err = pci_vtscsi_init_queue(sc, &sc->vss_queues[i - 2], i - 2);
+ sc->vss_vq[q].vq_qsize = sc->vss_req_ringsz;
+ sc->vss_vq[q].vq_notify = pci_vtscsi_requestq_notify;
+
+ err = pci_vtscsi_init_queue(sc, &sc->vss_queues[rq], q);
if (err != 0)
goto fail;
}
@@ -1099,7 +1449,8 @@ pci_vtscsi_init(struct pci_devinst *pi, nvlist_t *nvl)
pci_set_cfgdata16(pi, PCIR_SUBDEV_0, VIRTIO_ID_SCSI);
pci_set_cfgdata16(pi, PCIR_SUBVEND_0, VIRTIO_VENDOR);
- if (vi_intr_init(&sc->vss_vs, 1, fbsdrun_virtio_msix()))
+ err = vi_intr_init(&sc->vss_vs, 1, fbsdrun_virtio_msix());
+ if (err != 0)
goto fail;
vi_set_io_bar(&sc->vss_vs, 0);
@@ -1107,12 +1458,29 @@ pci_vtscsi_init(struct pci_devinst *pi, nvlist_t *nvl)
return (0);
fail:
- for (i = 2; i < VTSCSI_MAXQ; i++)
- pci_vtscsi_destroy_queue(&sc->vss_queues[i - 2]);
+ if (sc->vss_queues != NULL) {
+ for (q = VIRTIO_SCSI_ADDL_Q;
+ q < sc->vss_vi_consts.vc_nvq;
+ q++) {
+ int rq = q - VIRTIO_SCSI_ADDL_Q;
- if (sc->vss_ctl_fd > 0)
- close(sc->vss_ctl_fd);
+ pci_vtscsi_destroy_queue(sc, &sc->vss_queues[rq]);
+ }
+ }
+
+ pthread_mutex_destroy(&sc->vss_mtx);
+
+ for (i = 0; i < sc->vss_num_target; i++) {
+ if (sc->vss_targets[i].vst_target == i &&
+ sc->vss_targets[i].vst_fd >= 0) {
+ close(sc->vss_targets[i].vst_fd);
+ }
+ }
+ free(sc->vss_targets);
+ free(sc->vss_backend);
+ free(sc->vss_queues);
+ free(sc->vss_vq);
free(sc);
return (-1);
}
diff --git a/usr.sbin/bhyve/pci_virtio_scsi.h b/usr.sbin/bhyve/pci_virtio_scsi.h
new file mode 100644
index 000000000000..b0f41bbdd8c3
--- /dev/null
+++ b/usr.sbin/bhyve/pci_virtio_scsi.h
@@ -0,0 +1,428 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2016 Jakub Klama <jceel@FreeBSD.org>.
+ * Copyright (c) 2018 Marcelo Araujo <araujo@FreeBSD.org>.
+ * Copyright (c) 2026 Hans Rosenfeld
+ * 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 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 AUTHOR 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.
+ */
+
+#ifndef _PCI_VIRTIO_SCSI_H_
+#define _PCI_VIRTIO_SCSI_H_
+
+#include "iov.h"
+
+extern int pci_vtscsi_debug;
+
+#define WPRINTF(msg, params...) PRINTLN("virtio-scsi: " msg, ##params)
+#define DPRINTF(msg, params...) if (pci_vtscsi_debug) WPRINTF(msg, ##params)
+
+/* Absolute limits given by the VirtIO SCSI spec */
+#define VIRTIO_SCSI_MAX_CHANNEL 0
+#define VIRTIO_SCSI_MAX_TARGET 255
+#define VIRTIO_SCSI_MAX_LUN 16383
+#define VIRTIO_SCSI_HDR_SEG 2
+#define VIRTIO_SCSI_ADDL_Q 2
+
+/* Features specific to VirtIO SCSI, none of which we currently support */
+#define VIRTIO_SCSI_F_INOUT (1 << 0)
+#define VIRTIO_SCSI_F_HOTPLUG (1 << 1)
+#define VIRTIO_SCSI_F_CHANGE (1 << 2)
+
+/* Default limits which we set. All of these are configurable. */
+#define VTSCSI_DEF_RINGSZ 64
+#define VTSCSI_MIN_RINGSZ 4
+#define VTSCSI_MAX_RINGSZ 4096
+
+#define VTSCSI_DEF_THR_PER_Q 16
+#define VTSCSI_MIN_THR_PER_Q 1
+#define VTSCSI_MAX_THR_PER_Q 256
+
+#define VTSCSI_DEF_MAXSEG 64
+#define VTSCSI_MIN_MAXSEG (VIRTIO_SCSI_HDR_SEG + 1)
+#define VTSCSI_MAX_MAXSEG \
+ (4096 - VIRTIO_SCSI_HDR_SEG - SPLIT_IOV_ADDL_IOV)
+
+#define VTSCSI_DEF_REQUESTQ 1
+#define VTSCSI_MIN_REQUESTQ 1
+#define VTSCSI_MAX_REQUESTQ (32 - VIRTIO_SCSI_ADDL_Q)
+
+/*
+ * Device-specific config space registers
+ *
+ * The guest driver may try to modify cdb_size and sense_size by writing the
+ * respective config space registers. Since we currently ignore all writes to
+ * config space, these macros are essentially constant.
+ */
+#define VTSCSI_IN_HEADER_LEN(_sc) \
+ (sizeof(struct pci_vtscsi_req_cmd_rd) + _sc->vss_config.cdb_size)
+
+#define VTSCSI_OUT_HEADER_LEN(_sc) \
+ (sizeof(struct pci_vtscsi_req_cmd_wr) + _sc->vss_config.sense_size)
+
+struct pci_vtscsi_config {
+ uint32_t num_queues;
+ uint32_t seg_max;
+ uint32_t max_sectors;
+ uint32_t cmd_per_lun;
+ uint32_t event_info_size;
+ uint32_t sense_size;
+ uint32_t cdb_size;
+ uint16_t max_channel;
+ uint16_t max_target;
+ uint32_t max_lun;
+} __attribute__((packed));
+
+
+/*
+ * I/O request state and I/O request queues
+ *
+ * In addition to the control queue and notification queues, each virtio-scsi
+ * device instance has at least one I/O request queue, the state of which is
+ * is kept in an array of struct pci_vtscsi_queue in the device softc.
+ *
+ * Each pci_vtscsi_queue has configurable number of pci_vtscsi_request
+ * structures pre-allocated on vsq_free_requests. For each I/O request
+ * coming in on the I/O virtqueue, the request queue handler will take a
+ * pci_vtscsi_request off vsq_free_requests, fills in the data from the
+ * I/O virtqueue, puts it on vsq_requests, and signals vsq_cv.
+ *
+ * Each pci_vtscsi_queue will have a configurable number of worker threads,
+ * which wait on vsq_cv. When signalled, they repeatedly take a single
+ * pci_vtscsi_request off vsq_requests and hand it to the backend, which
+ * processes it synchronously. After completion, the pci_vtscsi_request
+ * is re-initialized and put back onto vsq_free_requests.
+ *
+ * The worker threads exit when vsq_cv is signalled after vsw_exiting was set.
+ *
+ * There are three mutexes to coordinate the accesses to an I/O request queue:
+ * - vsq_rmtx protects vsq_requests and must be held when waiting on vsq_cv
+ * - vsq_fmtx protects vsq_free_requests
+ * - vsq_qmtx must be held when operating on the underlying virtqueue, vsq_vq
+ *
+ * The I/O vectors for each request are kept in the preallocated iovec array
+ * vsr_iov, and pointers to the respective header/data in/out portions are set
+ * up to point into the array when the request is queued for processing.
+ *
+ * The number of iovecs preallocated for vsr_iov is derived from the configured
+ * 'seg_max' parameter defined by the virtio spec:
+ * - 'seg_max' parameter specifies the maximum number of I/O data vectors
+ * we support in any request
+ * - we need 2 additional iovecs for the I/O headers (VIRTIO_SCSI_HDR_SEG)
+ * - we need another 2 additional iovecs for split_iov() (SPLIT_IOV_ADDL_IOV)
+ *
+ * The only time we explicitly need the full size of vsr_iov after preallocation
+ * is during re-initialization after completing a request, and implicitly in the
+ * calls to split_iov() the set up the pointers. In all other cases, we use only
+ * 'seg_max' + VIRTIO_SCSI_HDR_SEG, and we advertise only 'seg_max' to the guest
+ * in accordance to the virtio spec.
+ */
+STAILQ_HEAD(pci_vtscsi_req_queue, pci_vtscsi_request);
+
+struct pci_vtscsi_queue {
+ struct pci_vtscsi_softc *vsq_sc;
+ struct vqueue_info *vsq_vq;
+ pthread_mutex_t vsq_rmtx;
+ pthread_mutex_t vsq_fmtx;
+ pthread_mutex_t vsq_qmtx;
+ pthread_cond_t vsq_cv;
+ struct pci_vtscsi_req_queue vsq_requests;
+ struct pci_vtscsi_req_queue vsq_free_requests;
+ LIST_HEAD(, pci_vtscsi_worker) vsq_workers;
+};
+
+struct pci_vtscsi_worker {
+ struct pci_vtscsi_queue *vsw_queue;
+ pthread_t vsw_thread;
+ bool vsw_exiting;
+ LIST_ENTRY(pci_vtscsi_worker) vsw_link;
+};
+
+struct pci_vtscsi_request {
+ struct pci_vtscsi_queue *vsr_queue;
+ struct iovec *vsr_iov;
+ struct iovec *vsr_iov_in;
+ struct iovec *vsr_iov_out;
+ struct iovec *vsr_data_iov_in;
+ struct iovec *vsr_data_iov_out;
+ struct pci_vtscsi_req_cmd_rd *vsr_cmd_rd;
+ struct pci_vtscsi_req_cmd_wr *vsr_cmd_wr;
+ void *vsr_backend;
+ size_t vsr_niov_in;
+ size_t vsr_niov_out;
+ size_t vsr_data_niov_in;
+ size_t vsr_data_niov_out;
+ uint32_t vsr_idx;
+ STAILQ_ENTRY(pci_vtscsi_request) vsr_link;
+};
+
+/*
+ * Per-target state.
+ */
+struct pci_vtscsi_target {
+ uint8_t vst_target;
+ int vst_fd;
+ int vst_max_sectors;
+};
+
+/*
+ * Per-device softc
+ */
+struct pci_vtscsi_softc {
+ struct virtio_softc vss_vs;
+ struct virtio_consts vss_vi_consts;
+ struct vqueue_info *vss_vq;
+ struct pci_vtscsi_queue *vss_queues;
+ pthread_mutex_t vss_mtx;
+ uint32_t vss_features;
+ size_t vss_num_target;
+ uint32_t vss_ctl_ringsz;
+ uint32_t vss_evt_ringsz;
+ uint32_t vss_req_ringsz;
+ uint32_t vss_thr_per_q;
+ struct pci_vtscsi_config vss_default_config;
+ struct pci_vtscsi_config vss_config;
+ struct pci_vtscsi_target *vss_targets;
+ struct pci_vtscsi_backend *vss_backend;
+};
+
+/*
+ * VirtIO-SCSI Task Management Function control requests
+ */
+#define VIRTIO_SCSI_T_TMF 0
+#define VIRTIO_SCSI_T_TMF_ABORT_TASK 0
+#define VIRTIO_SCSI_T_TMF_ABORT_TASK_SET 1
+#define VIRTIO_SCSI_T_TMF_CLEAR_ACA 2
+#define VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET 3
+#define VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET 4
+#define VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET 5
+#define VIRTIO_SCSI_T_TMF_QUERY_TASK 6
+#define VIRTIO_SCSI_T_TMF_QUERY_TASK_SET 7
+
+#define VIRTIO_SCSI_T_TMF_MAX_FUNC VIRTIO_SCSI_T_TMF_QUERY_TASK_SET
+
+/* command-specific response values */
+#define VIRTIO_SCSI_S_FUNCTION_COMPLETE 0
+#define VIRTIO_SCSI_S_FUNCTION_SUCCEEDED 10
+#define VIRTIO_SCSI_S_FUNCTION_REJECTED 11
+
+struct pci_vtscsi_ctrl_tmf {
+ const uint32_t type;
+ const uint32_t subtype;
+ const uint8_t lun[8];
+ const uint64_t id;
+ uint8_t response;
+} __attribute__((packed));
+
+
+/*
+ * VirtIO-SCSI Asynchronous Notification control requests
+ */
+#define VIRTIO_SCSI_T_AN_QUERY 1
+#define VIRTIO_SCSI_EVT_ASYNC_OPERATIONAL_CHANGE 2
+#define VIRTIO_SCSI_EVT_ASYNC_POWER_MGMT 4
+#define VIRTIO_SCSI_EVT_ASYNC_EXTERNAL_REQUEST 8
+#define VIRTIO_SCSI_EVT_ASYNC_MEDIA_CHANGE 16
+#define VIRTIO_SCSI_EVT_ASYNC_MULTI_HOST 32
+#define VIRTIO_SCSI_EVT_ASYNC_DEVICE_BUSY 64
+
+struct pci_vtscsi_ctrl_an {
+ const uint32_t type;
+ const uint8_t lun[8];
+ const uint32_t event_requested;
+ uint32_t event_actual;
+ uint8_t response;
+} __attribute__((packed));
+
+/* command-specific response values */
+#define VIRTIO_SCSI_S_OK 0
+#define VIRTIO_SCSI_S_OVERRUN 1
+#define VIRTIO_SCSI_S_ABORTED 2
+#define VIRTIO_SCSI_S_BAD_TARGET 3
+#define VIRTIO_SCSI_S_RESET 4
+#define VIRTIO_SCSI_S_BUSY 5
+#define VIRTIO_SCSI_S_TRANSPORT_FAILURE 6
+#define VIRTIO_SCSI_S_TARGET_FAILURE 7
+#define VIRTIO_SCSI_S_NEXUS_FAILURE 8
+#define VIRTIO_SCSI_S_FAILURE 9
+#define VIRTIO_SCSI_S_INCORRECT_LUN 12
+
+struct pci_vtscsi_event {
+ uint32_t event;
+ uint8_t lun[8];
+ uint32_t reason;
+} __attribute__((packed));
+
+/*
+ * VirtIO-SCSI I/O requests
+ */
+struct pci_vtscsi_req_cmd_rd {
+ const uint8_t lun[8];
+ const uint64_t id;
+ const uint8_t task_attr;
+ const uint8_t prio;
+ const uint8_t crn;
+ const uint8_t cdb[];
+} __attribute__((packed));
+
+/* task_attr */
+#define VIRTIO_SCSI_S_SIMPLE 0
+#define VIRTIO_SCSI_S_ORDERED 1
+#define VIRTIO_SCSI_S_HEAD 2
+#define VIRTIO_SCSI_S_ACA 3
+
+struct pci_vtscsi_req_cmd_wr {
+ uint32_t sense_len;
+ uint32_t residual;
+ uint16_t status_qualifier;
+ uint8_t status;
+ uint8_t response;
+ uint8_t sense[];
+} __attribute__((packed));
+
+/*
+ * Backend interface
+ */
+struct pci_vtscsi_backend {
+ const char *vsb_name;
+ int (*vsb_init)(struct pci_vtscsi_softc *,
+ struct pci_vtscsi_backend *, nvlist_t *);
+ int (*vsb_open)(struct pci_vtscsi_softc *, const char *,
+ long);
+ void (*vsb_reset)(struct pci_vtscsi_softc *);
+
+ void* (*vsb_req_alloc)(struct pci_vtscsi_softc *);
+ void (*vsb_req_clear)(void *);
+ void (*vsb_req_free)(void *);
+
+ void (*vsb_tmf_hdl)(struct pci_vtscsi_softc *, int,
+ struct pci_vtscsi_ctrl_tmf *);
+ void (*vsb_an_hdl)(struct pci_vtscsi_softc *, int,
+ struct pci_vtscsi_ctrl_an *);
+ int (*vsb_req_hdl)(struct pci_vtscsi_softc *, int,
+ struct pci_vtscsi_request *);
+};
+#define PCI_VTSCSI_BACKEND_SET(x) DATA_SET(pci_vtscsi_backend_set, x)
+
+/*
+ * LUN address parsing
+ *
+ * The LUN address consists of 8 bytes. While the spec describes this as 0x01,
+ * followed by the target byte, followed by a "single-level LUN structure",
+ * this is actually the same as a hierarchical LUN address as defined by SAM-5,
+ * consisting of four levels of addressing, where in each level the two MSB of
+ * byte 0 select the address mode used in the remaining bits and bytes.
+ *
+ *
+ * Only the first two levels are acutally used by virtio-scsi:
+ *
+ * Level 1: 0x01, 0xTT: Peripheral Device Addressing: Bus 1, Target 0-255
+ * Level 2: 0xLL, 0xLL: Peripheral Device Addressing: Bus MBZ, LUN 0-255
+ * or: Flat Space Addressing: LUN (0-16383)
+ * Level 3 and 4: not used, MBZ
+ *
+ *
+ * Alternatively, the first level may contain an extended LUN address to select
+ * the REPORT_LUNS well-known logical unit:
+ *
+ * Level 1: 0xC1, 0x01: Extended LUN Adressing, Well-Known LUN 1 (REPORT_LUNS)
+ * Level 2, 3, and 4: not used, MBZ
+ *
+ * The virtio spec says that we SHOULD implement the REPORT_LUNS well-known
+ * logical unit but we currently don't.
+ *
+ * According to the virtio spec, these are the only LUNS address formats to be
+ * used with virtio-scsi.
+ */
+
+/*
+ * Check that the given LUN address conforms to the virtio spec, does not
+ * address an unknown target, and especially does not address the REPORT_LUNS
+ * well-known logical unit.
+ */
+static inline bool
+pci_vtscsi_check_lun(struct pci_vtscsi_softc *sc, const uint8_t *lun)
+{
+ if (lun[0] == 0xC1)
+ return (false);
+
+ if (lun[0] != 0x01)
+ return (false);
+
+ if (lun[1] >= sc->vss_num_target)
+ return (false);
+
+ if (lun[1] != sc->vss_targets[lun[1]].vst_target)
+ return (false);
+
+ if (sc->vss_targets[lun[1]].vst_fd < 0)
+ return (false);
+
+ if (lun[2] != 0x00 && (lun[2] & 0xc0) != 0x40)
+ return (false);
+
+ if (lun[4] != 0 || lun[5] != 0 || lun[6] != 0 || lun[7] != 0)
+ return (false);
+
+ return (true);
+}
+
+/*
+ * Get the target id from a LUN address.
+ *
+ * Every code path using this function must have called pci_vtscsi_check_lun()
+ * before to make sure the LUN address is valid.
+ */
+static inline uint8_t
+pci_vtscsi_get_target(struct pci_vtscsi_softc *sc, const uint8_t *lun)
+{
+ assert(lun[0] == 0x01);
+ assert(lun[1] < sc->vss_num_target);
+ assert(lun[1] == sc->vss_targets[lun[1]].vst_target);
+ assert(sc->vss_targets[lun[1]].vst_fd >= 0);
+ assert(lun[2] == 0x00 || (lun[2] & 0xc0) == 0x40);
+
+ return (lun[1]);
+}
+
+/*
+ * Get the LUN id from a LUN address.
+ *
+ * Every code path using this function must have called pci_vtscsi_check_lun()
+ * before to make sure the LUN address is valid.
+ */
+static inline uint16_t
+pci_vtscsi_get_lun(struct pci_vtscsi_softc *sc, const uint8_t *lun)
+{
+ assert(lun[0] == 0x01);
+ assert(lun[1] < sc->vss_num_target);
+ assert(lun[1] == sc->vss_targets[lun[1]].vst_target);
+ assert(sc->vss_targets[lun[1]].vst_fd >= 0);
+ assert(lun[2] == 0x00 || (lun[2] & 0xc0) == 0x40);
+
+ return (((lun[2] << 8) | lun[3]) & 0x3fff);
+}
+
+#endif /* _PCI_VIRTIO_SCSI_H_ */
diff --git a/usr.sbin/bhyve/pci_virtio_scsi_ctl.c b/usr.sbin/bhyve/pci_virtio_scsi_ctl.c
new file mode 100644
index 000000000000..bfe619d8a11f
--- /dev/null
+++ b/usr.sbin/bhyve/pci_virtio_scsi_ctl.c
@@ -0,0 +1,377 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2016 Jakub Klama <jceel@FreeBSD.org>.
+ * Copyright (c) 2018 Marcelo Araujo <araujo@FreeBSD.org>.
+ * Copyright (c) 2026 Hans Rosenfeld
+ * 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 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 AUTHOR 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/param.h>
+#include <sys/linker_set.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+#include <sys/sbuf.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include <pthread.h>
+#include <pthread_np.h>
+
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_message.h>
+#include <cam/ctl/ctl.h>
+#include <cam/ctl/ctl_io.h>
+#include <cam/ctl/ctl_backend.h>
+#include <cam/ctl/ctl_ioctl.h>
+#include <cam/ctl/ctl_util.h>
+#include <cam/ctl/ctl_scsi_all.h>
+#include <camlib.h>
+
+#include "bhyverun.h"
+#include "config.h"
+#include "debug.h"
+#include "pci_emul.h"
+#include "virtio.h"
+#include "iov.h"
+#include "pci_virtio_scsi.h"
+
+struct vtscsi_ctl_backend {
+ struct pci_vtscsi_backend vcb_backend;
+ int vcb_iid;
+};
+
+static int vtscsi_ctl_init(struct pci_vtscsi_softc *,
+ struct pci_vtscsi_backend *, nvlist_t *);
+static int vtscsi_ctl_open(struct pci_vtscsi_softc *, const char *, long);
+static void vtscsi_ctl_reset(struct pci_vtscsi_softc *);
+
+static void *vtscsi_ctl_req_alloc(struct pci_vtscsi_softc *);
+static void vtscsi_ctl_req_clear(void *);
+static void vtscsi_ctl_req_free(void *);
+
+static void vtscsi_ctl_tmf_hdl(struct pci_vtscsi_softc *, int,
+ struct pci_vtscsi_ctrl_tmf *);
+static void vtscsi_ctl_an_hdl(struct pci_vtscsi_softc *, int,
+ struct pci_vtscsi_ctrl_an *);
+static int vtscsi_ctl_req_hdl(struct pci_vtscsi_softc *, int,
+ struct pci_vtscsi_request *);
+
+static int
+vtscsi_ctl_count_targets(const char *prefix __unused,
+ const nvlist_t *parent __unused, const char *name __unused, int type,
+ void *arg)
+{
+ int *count = arg;
+
+ if (type != NV_TYPE_STRING) {
+ EPRINTLN("invalid target \"%s\" type: not a string", name);
+ errno = EINVAL;
+ return (-1);
+ }
+
+ (*count)++;
+
+ return (0);
+}
+
+static int
+vtscsi_ctl_init(struct pci_vtscsi_softc *sc, struct pci_vtscsi_backend *backend,
+ nvlist_t *nvl)
+{
+ int count = 0;
+ int ret = 0;
+ struct vtscsi_ctl_backend *ctl_backend;
+ const char *value;
+
+ ctl_backend = calloc(1, sizeof(struct vtscsi_ctl_backend));
+ if (ctl_backend == NULL) {
+ EPRINTLN("failed to allocate backend data: %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ ctl_backend->vcb_backend = *backend;
+ sc->vss_backend = &ctl_backend->vcb_backend;
+
+ value = get_config_value_node(nvl, "iid");
+ if (value != NULL)
+ ctl_backend->vcb_iid = strtoul(value, NULL, 10);
+
+ /*
+ * Count configured targets. If no targets were configured, use
+ * /dev/cam/ctl to remain compatible with previous versions.
+ */
+ nvl = find_relative_config_node(nvl, "target");
+ assert(nvl != NULL);
+
+ ret = walk_config_nodes("", nvl, &count, vtscsi_ctl_count_targets);
+ if (ret != 0)
+ return (ret);
+
+ if (count == 0)
+ set_config_value_node(nvl, "0", "/dev/cam/ctl");
+
+ return (0);
+}
+
+static int
+vtscsi_ctl_open(struct pci_vtscsi_softc *sc __unused, const char *devname,
+ long target)
+{
+ struct pci_vtscsi_target *tgt = &sc->vss_targets[target];
+
+ tgt->vst_fd = open(devname, O_RDWR);
+ if (tgt->vst_fd < 0)
+ return (-1);
+
+ return (0);
+}
+
+static void
+vtscsi_ctl_reset(struct pci_vtscsi_softc *sc __unused)
+{
+ /*
+ * There doesn't seem to be a limit to the maximum number of
+ * sectors CTL can transfer in one request.
+ */
+ sc->vss_config.max_sectors = INT32_MAX;
+}
+
+static void *
+vtscsi_ctl_req_alloc(struct pci_vtscsi_softc *sc)
+{
+ struct vtscsi_ctl_backend *ctl =
+ (struct vtscsi_ctl_backend *)sc->vss_backend;
+ union ctl_io *io = ctl_scsi_alloc_io(ctl->vcb_iid);
+
+ if (io != NULL)
+ ctl_scsi_zero_io(io);
+
+ return (io);
+}
+
+static void
+vtscsi_ctl_req_clear(void *io)
+{
+ ctl_scsi_zero_io(io);
+}
+
+static void
+vtscsi_ctl_req_free(void *io)
+{
+ ctl_scsi_free_io(io);
+}
+
+static void
+vtscsi_ctl_tmf_hdl(struct pci_vtscsi_softc *sc, int fd,
+ struct pci_vtscsi_ctrl_tmf *tmf)
+{
+ struct vtscsi_ctl_backend *ctl;
+ union ctl_io *io;
+ int err;
+
+ ctl = (struct vtscsi_ctl_backend *)sc->vss_backend;
+
+ io = vtscsi_ctl_req_alloc(sc);
+ if (io == NULL) {
+ tmf->response = VIRTIO_SCSI_S_FAILURE;
+ return;
+ }
+ vtscsi_ctl_req_clear(io);
+
+ io->io_hdr.io_type = CTL_IO_TASK;
+ io->io_hdr.nexus.initid = ctl->vcb_iid;
+ io->io_hdr.nexus.targ_lun = pci_vtscsi_get_lun(sc, tmf->lun);
+ io->taskio.tag_type = CTL_TAG_SIMPLE;
+ io->taskio.tag_num = tmf->id;
+ io->io_hdr.flags |= CTL_FLAG_USER_TAG;
+
+ switch (tmf->subtype) {
+ case VIRTIO_SCSI_T_TMF_ABORT_TASK:
+ io->taskio.task_action = CTL_TASK_ABORT_TASK;
+ break;
+
+ case VIRTIO_SCSI_T_TMF_ABORT_TASK_SET:
+ io->taskio.task_action = CTL_TASK_ABORT_TASK_SET;
+ break;
+
+ case VIRTIO_SCSI_T_TMF_CLEAR_ACA:
+ io->taskio.task_action = CTL_TASK_CLEAR_ACA;
+ break;
+
+ case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET:
+ io->taskio.task_action = CTL_TASK_CLEAR_TASK_SET;
+ break;
+
+ case VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET:
+ io->taskio.task_action = CTL_TASK_I_T_NEXUS_RESET;
+ break;
+
+ case VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET:
+ io->taskio.task_action = CTL_TASK_LUN_RESET;
+ break;
+
+ case VIRTIO_SCSI_T_TMF_QUERY_TASK:
+ io->taskio.task_action = CTL_TASK_QUERY_TASK;
+ break;
+
+ case VIRTIO_SCSI_T_TMF_QUERY_TASK_SET:
+ io->taskio.task_action = CTL_TASK_QUERY_TASK_SET;
+ break;
+ }
+
+ if (pci_vtscsi_debug) {
+ struct sbuf *sb = sbuf_new_auto();
+ ctl_io_sbuf(io, sb);
+ sbuf_finish(sb);
+ DPRINTF("%s", sbuf_data(sb));
+ sbuf_delete(sb);
+ }
+
+ err = ioctl(fd, CTL_IO, io);
+ if (err != 0) {
+ WPRINTF("CTL_IO: err=%d (%s)", errno, strerror(errno));
+ tmf->response = VIRTIO_SCSI_S_FAILURE;
+ } else {
+ tmf->response = io->taskio.task_status;
+ }
+ vtscsi_ctl_req_free(io);
+}
+
+static void
+vtscsi_ctl_an_hdl(struct pci_vtscsi_softc *sc __unused, int fd __unused,
+ struct pci_vtscsi_ctrl_an *an)
+{
+ an->response = VIRTIO_SCSI_S_FAILURE;
+}
+
+static int
+vtscsi_ctl_req_hdl(struct pci_vtscsi_softc *sc, int fd,
+ struct pci_vtscsi_request *req)
+{
+ union ctl_io *io = req->vsr_backend;
+ void *ext_data_ptr = NULL;
+ uint32_t ext_data_len = 0, ext_sg_entries = 0;
+ struct vtscsi_ctl_backend *ctl;
+ int err, nxferred;
+
+ ctl = (struct vtscsi_ctl_backend *)sc->vss_backend;
+
+ io->io_hdr.nexus.initid = ctl->vcb_iid;
+ io->io_hdr.nexus.targ_lun = pci_vtscsi_get_lun(sc,
+ req->vsr_cmd_rd->lun);
+
+ io->io_hdr.io_type = CTL_IO_SCSI;
+
+ if (req->vsr_data_niov_in > 0) {
+ ext_data_ptr = (void *)req->vsr_data_iov_in;
+ ext_sg_entries = req->vsr_data_niov_in;
+ ext_data_len = count_iov(req->vsr_data_iov_in,
+ req->vsr_data_niov_in);
+ io->io_hdr.flags |= CTL_FLAG_DATA_OUT;
+ } else if (req->vsr_data_niov_out > 0) {
+ ext_data_ptr = (void *)req->vsr_data_iov_out;
+ ext_sg_entries = req->vsr_data_niov_out;
+ ext_data_len = count_iov(req->vsr_data_iov_out,
+ req->vsr_data_niov_out);
+ io->io_hdr.flags |= CTL_FLAG_DATA_IN;
+ }
+
+ io->scsiio.sense_len = sc->vss_config.sense_size;
+ io->scsiio.tag_num = req->vsr_cmd_rd->id;
+ io->io_hdr.flags |= CTL_FLAG_USER_TAG;
+ switch (req->vsr_cmd_rd->task_attr) {
+ case VIRTIO_SCSI_S_ORDERED:
+ io->scsiio.tag_type = CTL_TAG_ORDERED;
+ break;
+ case VIRTIO_SCSI_S_HEAD:
+ io->scsiio.tag_type = CTL_TAG_HEAD_OF_QUEUE;
+ break;
+ case VIRTIO_SCSI_S_ACA:
+ io->scsiio.tag_type = CTL_TAG_ACA;
+ break;
+ case VIRTIO_SCSI_S_SIMPLE:
+ default:
+ io->scsiio.tag_type = CTL_TAG_SIMPLE;
+ break;
+ }
+ io->scsiio.ext_sg_entries = ext_sg_entries;
+ io->scsiio.ext_data_ptr = ext_data_ptr;
+ io->scsiio.ext_data_len = ext_data_len;
+ io->scsiio.ext_data_filled = 0;
+ io->scsiio.cdb_len = sc->vss_config.cdb_size;
+ memcpy(io->scsiio.cdb, req->vsr_cmd_rd->cdb, sc->vss_config.cdb_size);
+
+ if (pci_vtscsi_debug) {
+ struct sbuf *sb = sbuf_new_auto();
+ ctl_io_sbuf(io, sb);
+ sbuf_finish(sb);
+ DPRINTF("%s", sbuf_data(sb));
+ sbuf_delete(sb);
+ }
+
+ err = ioctl(fd, CTL_IO, io);
+ if (err != 0) {
+ WPRINTF("CTL_IO: err=%d (%s)", errno, strerror(errno));
+ req->vsr_cmd_wr->response = VIRTIO_SCSI_S_FAILURE;
+ } else {
+ req->vsr_cmd_wr->sense_len =
+ MIN(io->scsiio.sense_len, sc->vss_config.sense_size);
+ req->vsr_cmd_wr->residual = ext_data_len -
+ io->scsiio.ext_data_filled;
+ req->vsr_cmd_wr->status = io->scsiio.scsi_status;
+ req->vsr_cmd_wr->response = VIRTIO_SCSI_S_OK;
+ memcpy(&req->vsr_cmd_wr->sense, &io->scsiio.sense_data,
+ req->vsr_cmd_wr->sense_len);
+ }
+
+ nxferred = io->scsiio.ext_data_filled;
+ return (nxferred);
+}
+
+static const struct pci_vtscsi_backend vtscsi_ctl_backend = {
+ .vsb_name = "ctl",
+ .vsb_init = vtscsi_ctl_init,
+ .vsb_open = vtscsi_ctl_open,
+ .vsb_reset = vtscsi_ctl_reset,
+
+ .vsb_req_alloc = vtscsi_ctl_req_alloc,
+ .vsb_req_clear = vtscsi_ctl_req_clear,
+ .vsb_req_free = vtscsi_ctl_req_free,
+
+ .vsb_tmf_hdl = vtscsi_ctl_tmf_hdl,
+ .vsb_an_hdl = vtscsi_ctl_an_hdl,
+ .vsb_req_hdl = vtscsi_ctl_req_hdl
+};
+PCI_VTSCSI_BACKEND_SET(vtscsi_ctl_backend);
diff --git a/usr.sbin/bhyve/riscv/bhyverun_machdep.c b/usr.sbin/bhyve/riscv/bhyverun_machdep.c
index a90a508901bc..adbba6bcce60 100644
--- a/usr.sbin/bhyve/riscv/bhyverun_machdep.c
+++ b/usr.sbin/bhyve/riscv/bhyverun_machdep.c
@@ -95,7 +95,8 @@ bhyve_usage(int code)
"Usage: %s [-CDHhSW]\n"
" %*s [-c [[cpus=]numcpus][,sockets=n][,cores=n][,threads=n]]\n"
" %*s [-k config_file] [-m mem] [-o var=value]\n"
- " %*s [-p vcpu:hostcpu] [-r file] [-s pci] [-U uuid] vmname\n"
+ " %*s [-p vcpuN[-vcpuM]]:hostcpuX[-hostcpuY]\n"
+ " %*s [-r file] [-s pci] [-U uuid] vmname\n"
" -C: include guest memory in core file\n"
" -c: number of CPUs and/or topology specification\n"
" -D: destroy on power-off\n"
@@ -110,7 +111,7 @@ bhyve_usage(int code)
" -U: UUID\n"
" -W: force virtio to use single-vector MSI\n",
progname, (int)strlen(progname), "", (int)strlen(progname), "",
- (int)strlen(progname), "");
+ (int)strlen(progname), "", (int)strlen(progname), "");
exit(code);
}
diff --git a/usr.sbin/bhyve/rtc_pl031.c b/usr.sbin/bhyve/rtc_pl031.c
index e334de6f92bb..74d392e5c023 100644
--- a/usr.sbin/bhyve/rtc_pl031.c
+++ b/usr.sbin/bhyve/rtc_pl031.c
@@ -236,13 +236,13 @@ rtc_pl031_read(struct rtc_pl031_softc *sc, int offset)
case RTCPeriphID1:
case RTCPeriphID2:
case RTCPeriphID3:
- reg = RTCPeriphID_VAL(offset - RTCPeriphID0);
+ reg = RTCPeriphID_VAL((offset - RTCPeriphID0) >> 2);
break;
case RTCCellID0:
case RTCCellID1:
case RTCCellID2:
case RTCCellID3:
- reg = RTCCellID_VAL(offset - RTCCellID0);
+ reg = RTCCellID_VAL((offset - RTCCellID0) >> 2);
break;
default:
/* Return 0 in reads from unasigned registers */
diff --git a/usr.sbin/bhyve/smbiostbl.c b/usr.sbin/bhyve/smbiostbl.c
index e09fbc95fc9e..8bb2415318bb 100644
--- a/usr.sbin/bhyve/smbiostbl.c
+++ b/usr.sbin/bhyve/smbiostbl.c
@@ -29,10 +29,13 @@
#include <sys/param.h>
#include <assert.h>
+#include <err.h>
#include <errno.h>
#include <md5.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
+#include <sysexits.h>
#include <unistd.h>
#include <uuid.h>
@@ -61,6 +64,7 @@
#define SMBIOS_TYPE_BOARD 2
#define SMBIOS_TYPE_CHASSIS 3
#define SMBIOS_TYPE_PROCESSOR 4
+#define SMBIOS_TYPE_OEM 11
#define SMBIOS_TYPE_MEMARRAY 16
#define SMBIOS_TYPE_MEMDEVICE 17
#define SMBIOS_TYPE_MEMARRAYMAP 19
@@ -250,6 +254,13 @@ struct smbios_table_type4 {
uint16_t family2; /* processor family 2 */
} __packed;
+#define SMBIOS_TYPE11_MAX_COUNT 255
+
+struct smbios_table_type11 {
+ struct smbios_structure header;
+ uint8_t nstrings; /* number of oem strings to follow */
+} __packed;
+
/*
* Physical Memory Array
*/
@@ -479,6 +490,15 @@ static int smbios_type4_initializer(
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n);
+static const struct smbios_table_type11 smbios_type11_template = {
+ { SMBIOS_TYPE_OEM, sizeof(struct smbios_table_type11), 0 },
+ 0 /* first string */
+};
+
+static int smbios_type11_initializer(const struct smbios_structure *template_entry,
+ const struct smbios_string *template_strings, char *curaddr, char **endaddr,
+ uint16_t *n);
+
static const struct smbios_table_type16 smbios_type16_template = {
{ SMBIOS_TYPE_MEMARRAY, sizeof (struct smbios_table_type16), 0 },
SMBIOS_MAL_SYSMB,
@@ -582,6 +602,9 @@ static struct smbios_template_entry smbios_template[] = {
{ (const struct smbios_structure *)&smbios_type4_template,
smbios_type4_strings,
smbios_type4_initializer },
+ { (const struct smbios_structure *)&smbios_type11_template,
+ NULL,
+ smbios_type11_initializer },
{ (const struct smbios_structure *)&smbios_type16_template,
NULL,
smbios_type16_initializer },
@@ -750,6 +773,107 @@ smbios_type4_initializer(const struct smbios_structure *template_entry,
return (0);
}
+/*
+ * Gather oemstring values from oemstring node of config kvlist
+ *
+ * Notes: The keys must be numeric and greater than zero.
+ *
+ * Any missing number keys will be added with a value
+ * of "N/A"
+ *
+ * The maximum number of strings allowed is 255
+ */
+
+static struct smbios_string *
+smbios_gather_type11_strings(void)
+{
+ const char *na = "N/A";
+ struct smbios_string *strings;
+ const char *name;
+ char *np;
+ void *cookie = NULL;
+ nvlist_t *node;
+ long maxnumber = 0;
+ long number;
+ char keystr[4]; /* SMBIOS_TYPE11_MAX_COUNT is a 3 digit number */
+ char canonical[4];
+ int key, type;
+
+ /* find any oemstrings */
+ node = find_config_node("oemstring");
+ /* nothing to do if none */
+ if (!node)
+ return NULL;
+
+ while ((name = nvlist_next(node, &type, &cookie)) != NULL) {
+ if (type != NV_TYPE_STRING)
+ errx(BHYVE_EXIT_ERROR, "Unexpected entry type for %s", name);
+
+ errno = 0;
+ number = strtol(name, &np, 10);
+ if ((*np != '\0') || (errno == EINVAL))
+ errx(BHYVE_EXIT_ERROR, "Invalid format: %s should be a decimal number", name);
+ if (number <= 0 || number > SMBIOS_TYPE11_MAX_COUNT)
+ errx(BHYVE_EXIT_ERROR, "Invalid range: %s should be in 1-%d", name,
+ SMBIOS_TYPE11_MAX_COUNT);
+ /* strtol accepts leading zeros and signed values which will break search */
+ snprintf(canonical, sizeof(canonical), "%ld", number);
+ if (strcmp(name, canonical) != 0)
+ errx(BHYVE_EXIT_ERROR, "Invalid format: %s should be %s", name, canonical);
+ if (number > maxnumber)
+ maxnumber = number;
+ }
+ strings = calloc(maxnumber + 1, sizeof(struct smbios_string));
+ if (strings == NULL)
+ err(BHYVE_EXIT_ERROR, "oemstring allocation failed");
+
+ /*
+ * The returned smbios_string should leave the node field as NULL to
+ * avoid creating a new node in the config tree.
+ */
+ for (key = 1; key <= maxnumber; key++) {
+ const char *vp;
+
+ snprintf(keystr, sizeof(keystr), "%d", key);
+ if (nvlist_exists(node, keystr))
+ vp = nvlist_get_string(node, keystr);
+ else
+ vp = na;
+ strings[key - 1].value = strdup(vp);
+ if (strings[key - 1].value == NULL)
+ err(BHYVE_EXIT_ERROR, "oemstring value allocation failed");
+ }
+
+ /* return data */
+ return (strings);
+}
+
+static int
+smbios_type11_initializer(const struct smbios_structure *template_entry,
+ const struct smbios_string *template_strings __unused, char *curaddr,
+ char **endaddr, uint16_t *n)
+{
+ struct smbios_table_type11 *type11;
+ struct smbios_string *type11_strings;
+ int i = 0;
+
+ type11_strings = smbios_gather_type11_strings();
+
+ smbios_generic_initializer(template_entry, type11_strings, curaddr,
+ endaddr, n);
+
+ if (type11_strings != NULL) {
+ /* count up strings */
+ type11 = (struct smbios_table_type11 *)curaddr;
+ for (i = 0; type11_strings[i].value != NULL; i++) {
+ free(__DECONST(char *, type11_strings[i].value));
+ }
+ type11->nstrings = i;
+ }
+ free(type11_strings);
+ return (0);
+}
+
static int
smbios_type16_initializer(const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
diff --git a/usr.sbin/bhyve/snapshot.c b/usr.sbin/bhyve/snapshot.c
index 5b941984c0c0..fdb5941bf1d0 100644
--- a/usr.sbin/bhyve/snapshot.c
+++ b/usr.sbin/bhyve/snapshot.c
@@ -1399,13 +1399,13 @@ IPC_COMMAND(ipc_cmd_set, checkpoint, vm_do_checkpoint);
* Create the listening socket for IPC with bhyvectl
*/
int
-init_checkpoint_thread(struct vmctx *ctx)
+init_checkpoint_thread(struct vmctx *ctx, const char *bhyve_run_dir)
{
struct checkpoint_thread_info *checkpoint_info = NULL;
struct sockaddr_un addr;
int socket_fd;
pthread_t checkpoint_pthread;
- int err;
+ int err, ret;
#ifndef WITHOUT_CAPSICUM
cap_rights_t rights;
#endif
@@ -1421,8 +1421,14 @@ init_checkpoint_thread(struct vmctx *ctx)
addr.sun_family = AF_UNIX;
- snprintf(addr.sun_path, sizeof(addr.sun_path), "%s%s",
- BHYVE_RUN_DIR, vm_get_name(ctx));
+ ret = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s",
+ bhyve_run_dir, vm_get_name(ctx));
+ if ((ret < 0) || ((size_t)ret >= sizeof(addr.sun_path))) {
+ EPRINTLN("%s: error setting socket path (%d)", __func__, ret);
+ err = -1;
+ goto fail;
+ }
+
addr.sun_len = SUN_LEN(&addr);
unlink(addr.sun_path);
diff --git a/usr.sbin/bhyve/snapshot.h b/usr.sbin/bhyve/snapshot.h
index 8bebdafd6117..5c71640f3f09 100644
--- a/usr.sbin/bhyve/snapshot.h
+++ b/usr.sbin/bhyve/snapshot.h
@@ -99,7 +99,7 @@ int vm_resume_devices(void);
int get_checkpoint_msg(int conn_fd, struct vmctx *ctx);
void *checkpoint_thread(void *param);
-int init_checkpoint_thread(struct vmctx *ctx);
+int init_checkpoint_thread(struct vmctx *ctx, const char *bhyve_run_dir);
int load_restore_file(const char *filename, struct restore_state *rstate);
diff --git a/usr.sbin/bhyve/tpm_intf_crb.c b/usr.sbin/bhyve/tpm_intf_crb.c
index 9b44d1d133b0..7058b47733f3 100644
--- a/usr.sbin/bhyve/tpm_intf_crb.c
+++ b/usr.sbin/bhyve/tpm_intf_crb.c
@@ -371,11 +371,18 @@ tpm_crb_mem_handler(struct vcpu *vcpu __unused, const int dir,
break;
}
case offsetof(struct tpm_crb_regs, ctrl_cancel): {
- /* TODO: cancel the tpm command */
- warnx(
- "%s: cancelling a TPM command is not implemented yet",
- __func__);
+ union tpm_crb_reg_ctrl_cancel cancel;
+
+ if ((size_t)size > sizeof(cancel))
+ goto err_out;
+ tpm_crb_mmiocpy(&cancel, val, size);
+ if (cancel.cancel != 0) {
+ /* TODO: cancel the tpm command */
+ warnx(
+ "%s: cancelling a TPM command is not implemented yet",
+ __func__);
+ }
break;
}
case offsetof(struct tpm_crb_regs, int_enable):
@@ -414,7 +421,7 @@ tpm_crb_mem_handler(struct vcpu *vcpu __unused, const int dir,
4:
case offsetof(struct tpm_crb_regs,
data_buffer) ... offsetof(struct tpm_crb_regs, data_buffer) +
- TPM_CRB_DATA_BUFFER_SIZE / 4:
+ sizeof(((struct tpm_crb_regs *)NULL)->data_buffer) - 1:
/*
* Those fields are used to execute a TPM command. The
* crb_thread will access them. For that reason, we have
diff --git a/usr.sbin/bhyvectl/bhyvectl.8 b/usr.sbin/bhyvectl/bhyvectl.8
index 550c4f10d8e6..b999230cfd73 100644
--- a/usr.sbin/bhyvectl/bhyvectl.8
+++ b/usr.sbin/bhyvectl/bhyvectl.8
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd May 8, 2025
+.Dd July 8, 2026
.Dt BHYVECTL 8
.Os
.Sh NAME
@@ -41,6 +41,7 @@
.Op Fl -force-reset
.Op Fl -force-poweroff
.Op Fl -checkpoint= Ns Ar <file>
+.Op Fl -rundir= Ns Ar <path>
.Op Fl -suspend= Ns Ar <file>
.Sh DESCRIPTION
The
@@ -77,6 +78,11 @@ Save a snapshot of a virtual machine similar to
.Fl -checkpoint .
The virtual machine will terminate after the snapshot has been
saved.
+.It Fl -rundir= Ns Ar <path>
+Specify path to the directory containing the checkpoint socket.
+If not specified,
+.Pa /var/run/bhyve
+is used.
.El
.Pp
.Em Note :
diff --git a/usr.sbin/bhyvectl/bhyvectl.c b/usr.sbin/bhyvectl/bhyvectl.c
index 8c37b670ab2e..b63cd5dc8c6e 100644
--- a/usr.sbin/bhyvectl/bhyvectl.c
+++ b/usr.sbin/bhyvectl/bhyvectl.c
@@ -2,6 +2,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2011 NetApp, Inc.
+ * Copyright (c) 2026 Hans Rosenfeld
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -85,6 +86,7 @@ enum {
#ifdef BHYVE_SNAPSHOT
SET_CHECKPOINT_FILE,
SET_SUSPEND_FILE,
+ SET_RUNDIR,
#endif
OPT_LAST,
};
@@ -137,6 +139,7 @@ setup_options(void)
#ifdef BHYVE_SNAPSHOT
{ "checkpoint", REQ_ARG, 0, SET_CHECKPOINT_FILE},
{ "suspend", REQ_ARG, 0, SET_SUSPEND_FILE},
+ { "rundir", REQ_ARG, 0, SET_RUNDIR},
#endif
};
@@ -154,6 +157,7 @@ usage(const struct option *opts)
#ifdef BHYVE_SNAPSHOT
[SET_CHECKPOINT_FILE] = "filename",
[SET_SUSPEND_FILE] = "filename",
+ [SET_RUNDIR] = "path",
#endif
};
(void)fprintf(stderr, "Usage: %s --vm=<vmname>\n", progname);
@@ -251,10 +255,10 @@ show_memseg(struct vmctx *ctx)
#ifdef BHYVE_SNAPSHOT
static int
-send_message(const char *vmname, nvlist_t *nvl)
+send_message(const char *vmname, nvlist_t *nvl, const char *rundir)
{
struct sockaddr_un addr;
- int err = 0, socket_fd;
+ int err = 0, ret, socket_fd;
socket_fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (socket_fd < 0) {
@@ -264,8 +268,15 @@ send_message(const char *vmname, nvlist_t *nvl)
}
memset(&addr, 0, sizeof(struct sockaddr_un));
- snprintf(addr.sun_path, sizeof(addr.sun_path), "%s%s",
- BHYVE_RUN_DIR, vmname);
+ ret = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s",
+ rundir, vmname);
+ if ((ret < 0) || ((size_t)ret >= sizeof(addr.sun_path))) {
+ fprintf(stderr, "%s: error setting socket path (%d)",
+ __func__, ret);
+ err = 1;
+ goto done;
+ }
+
addr.sun_family = AF_UNIX;
addr.sun_len = SUN_LEN(&addr);
@@ -304,7 +315,7 @@ open_directory(const char *file)
}
static int
-snapshot_request(const char *vmname, char *file, bool suspend)
+snapshot_request(const char *vmname, char *file, bool suspend, const char *rundir)
{
nvlist_t *nvl;
int fd;
@@ -318,7 +329,7 @@ snapshot_request(const char *vmname, char *file, bool suspend)
nvlist_add_bool(nvl, "suspend", suspend);
nvlist_move_descriptor(nvl, "fddir", fd);
- return (send_message(vmname, nvl));
+ return (send_message(vmname, nvl, rundir));
}
#endif
@@ -334,6 +345,7 @@ main(int argc, char *argv[])
struct option *opts;
#ifdef BHYVE_SNAPSHOT
char *checkpoint_file = NULL;
+ char *rundir = NULL;
#endif
opts = setup_options();
@@ -378,6 +390,11 @@ main(int argc, char *argv[])
checkpoint_file = optarg;
vm_suspend_opt = (ch == SET_SUSPEND_FILE);
break;
+
+ case SET_RUNDIR:
+ rundir = optarg;
+ break;
+
#endif
default:
usage(opts);
@@ -408,6 +425,12 @@ main(int argc, char *argv[])
exit(1);
}
vcpu = vm_vcpu_open(ctx, vcpuid);
+ if (vcpu == NULL) {
+ fprintf(stderr,
+ "vm_vcpu_open: %s vcpu %d could not be opened: %s\n",
+ vmname, vcpuid, strerror(errno));
+ exit(1);
+ }
error = 0;
if (!error && memsize)
@@ -521,7 +544,9 @@ main(int argc, char *argv[])
#ifdef BHYVE_SNAPSHOT
if (!error && checkpoint_file)
- error = snapshot_request(vmname, checkpoint_file, vm_suspend_opt);
+ error = snapshot_request(vmname, checkpoint_file,
+ vm_suspend_opt,
+ rundir ? rundir : BHYVE_RUN_DIR);
#endif
if (error)
diff --git a/usr.sbin/bhyveload/bhyveload.c b/usr.sbin/bhyveload/bhyveload.c
index 4cc566f334c3..3b416b7a5ad5 100644
--- a/usr.sbin/bhyveload/bhyveload.c
+++ b/usr.sbin/bhyveload/bhyveload.c
@@ -892,6 +892,8 @@ main(int argc, char** argv)
}
vcpu = vm_vcpu_open(ctx, BSP);
+ if (vcpu == NULL)
+ err(1, "vm_vcpu_open");
caph_cache_catpages();
if (caph_enter() < 0)
diff --git a/usr.sbin/blacklistd/blacklistd.conf b/usr.sbin/blacklistd/blacklistd.conf
index ec995e038574..04a09f8d3dea 100644
--- a/usr.sbin/blacklistd/blacklistd.conf
+++ b/usr.sbin/blacklistd/blacklistd.conf
@@ -14,7 +14,7 @@ submission stream * * * 3 24h
# adr/mask:port type proto owner name nfail duration
[remote]
-#129.168.0.0/16 * * * = * *
+#192.168.0.0/16 * * * = * *
#[2001:db8::]/32:ssh * * * = * *
#6161 = = = =/24 = =
#* stream tcp * = = =
diff --git a/usr.sbin/blocklistd/blocklistd.conf b/usr.sbin/blocklistd/blocklistd.conf
index c6c7f7c2fafc..db9a6fc5c720 100644
--- a/usr.sbin/blocklistd/blocklistd.conf
+++ b/usr.sbin/blocklistd/blocklistd.conf
@@ -10,7 +10,7 @@ submission stream * * * 3 24h
# adr/mask:port type proto owner name nfail duration
[remote]
-#129.168.0.0/16 * * * = * *
+#192.168.0.0/16 * * * = * *
#[2001:db8::]/32:ssh * * * = * *
#6161 = = = =/24 = =
#* stream tcp * = = =
diff --git a/usr.sbin/bluetooth/hccontrol/le.c b/usr.sbin/bluetooth/hccontrol/le.c
index 6d5440643b45..fbcee1451b0a 100644
--- a/usr.sbin/bluetooth/hccontrol/le.c
+++ b/usr.sbin/bluetooth/hccontrol/le.c
@@ -182,7 +182,7 @@ parse_param(int argc, char *argv[], char *buf, int *len)
uint16_t value;
optreset = 1;
optind = 0;
- while ((ch = getopt(argc, argv , "n:f:u:")) != -1) {
+ while ((ch = getopt(argc, argv , "n:f:u:b:")) != -1) {
switch(ch){
case 'n':
datalen = strlen(optarg);
@@ -218,7 +218,24 @@ parse_param(int argc, char *argv[], char *buf, int *len)
curbuf += 2;
*lenpos += 2;
}
-
+ break;
+ case 'b':
+ datalen = 1;
+ token = optarg;
+ while ((token = strchr(token, ',')) != NULL) {
+ datalen++;
+ token++;
+ }
+ if ((curbuf + datalen + 1) >= buflast)
+ goto done;
+ curbuf[0] = datalen;
+ curbuf++;
+ token = optarg;
+ while ((token = strsep(&optarg, ",")) != NULL) {
+ value = strtol(token, NULL, 16);
+ curbuf[0] = value & 0xff;
+ curbuf++;
+ }
}
}
done:
@@ -1297,7 +1314,7 @@ struct hci_command le_commands[] = {
},
{
"le_set_advertising_data",
- "le_set_advertising_data -n $name -f $flag -u $uuid16,$uuid16 \n"
+ "le_set_advertising_data -n $name -f $flag -u $uuid16,$uuid16 -b $byte,$byte,...,$byte\n"
"set LE device advertising packed data",
&le_set_advertising_data
},
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c
index 3a5cd9d42658..f71aacd6f2e9 100644
--- a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c
@@ -31,6 +31,7 @@
#include <sys/endian.h>
#include <sys/stat.h>
+#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
@@ -192,3 +193,107 @@ iwmbt_get_fwname_tlv(struct iwmbt_version_tlv *ver, const char *prefix,
return (fwname);
}
+
+int
+iwmbt_parse_tlv(uint8_t *data, uint8_t datalen,
+ struct iwmbt_version_tlv *version)
+{
+ uint8_t status, type, len;
+
+ status = *data++;
+ if (status != 0)
+ return (-1);
+ datalen--;
+
+ while (datalen >= 2) {
+ type = *data++;
+ len = *data++;
+ datalen -= 2;
+
+ if (datalen < len)
+ return (-1);
+
+ switch (type) {
+ case IWMBT_TLV_CNVI_TOP:
+ assert(len == 4);
+ version->cnvi_top = le32dec(data);
+ break;
+ case IWMBT_TLV_CNVR_TOP:
+ assert(len == 4);
+ version->cnvr_top = le32dec(data);
+ break;
+ case IWMBT_TLV_CNVI_BT:
+ assert(len == 4);
+ version->cnvi_bt = le32dec(data);
+ break;
+ case IWMBT_TLV_CNVR_BT:
+ assert(len == 4);
+ version->cnvr_bt = le32dec(data);
+ break;
+ case IWMBT_TLV_DEV_REV_ID:
+ assert(len == 2);
+ version->dev_rev_id = le16dec(data);
+ break;
+ case IWMBT_TLV_IMAGE_TYPE:
+ assert(len == 1);
+ version->img_type = *data;
+ break;
+ case IWMBT_TLV_TIME_STAMP:
+ assert(len == 2);
+ version->min_fw_build_cw = data[0];
+ version->min_fw_build_yy = data[1];
+ version->timestamp = le16dec(data);
+ break;
+ case IWMBT_TLV_BUILD_TYPE:
+ assert(len == 1);
+ version->build_type = *data;
+ break;
+ case IWMBT_TLV_BUILD_NUM:
+ assert(len == 4);
+ version->min_fw_build_nn = *data;
+ version->build_num = le32dec(data);
+ break;
+ case IWMBT_TLV_SECURE_BOOT:
+ assert(len == 1);
+ version->secure_boot = *data;
+ break;
+ case IWMBT_TLV_OTP_LOCK:
+ assert(len == 1);
+ version->otp_lock = *data;
+ break;
+ case IWMBT_TLV_API_LOCK:
+ assert(len == 1);
+ version->api_lock = *data;
+ break;
+ case IWMBT_TLV_DEBUG_LOCK:
+ assert(len == 1);
+ version->debug_lock = *data;
+ break;
+ case IWMBT_TLV_MIN_FW:
+ assert(len == 3);
+ version->min_fw_build_nn = data[0];
+ version->min_fw_build_cw = data[1];
+ version->min_fw_build_yy = data[2];
+ break;
+ case IWMBT_TLV_LIMITED_CCE:
+ assert(len == 1);
+ version->limited_cce = *data;
+ break;
+ case IWMBT_TLV_SBE_TYPE:
+ assert(len == 1);
+ version->sbe_type = *data;
+ break;
+ case IWMBT_TLV_OTP_BDADDR:
+ memcpy(&version->otp_bd_addr, data, sizeof(bdaddr_t));
+ break;
+ default:
+ /* Ignore other types */
+ break;
+ }
+
+ datalen -= len;
+ data += len;
+ }
+
+ return (0);
+}
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h
index eb6909a1f91d..1763f8688ed0 100644
--- a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h
@@ -152,5 +152,7 @@ extern char *iwmbt_get_fwname(struct iwmbt_version *ver,
const char *suffix);
extern char *iwmbt_get_fwname_tlv(struct iwmbt_version_tlv *ver,
const char *prefix, const char *suffix);
+extern int iwmbt_parse_tlv(uint8_t *data, uint8_t datalen,
+ struct iwmbt_version_tlv *ver);
#endif
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c
index 255181b8f4bc..81f1fbe7c5ce 100644
--- a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c
@@ -30,7 +30,6 @@
#include <sys/endian.h>
#include <sys/stat.h>
-#include <assert.h>
#include <err.h>
#include <errno.h>
#include <stddef.h>
@@ -409,6 +408,29 @@ iwmbt_load_fwfile(struct libusb_device_handle *hdl,
}
int
+iwmbt_bt_reset(struct libusb_device_handle *hdl)
+{
+ int ret, transferred;
+ static struct iwmbt_hci_cmd cmd = {
+ .opcode = htole16(0x0c03),
+ .length = 0,
+ };
+ uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE];
+
+ ret = iwmbt_hci_command(hdl,
+ &cmd,
+ buf,
+ sizeof(buf),
+ &transferred,
+ IWMBT_HCI_CMD_TIMEOUT);
+
+ if (ret < 0)
+ iwmbt_debug("HCI reset command failed: code=%d", ret);
+
+ return (ret);
+}
+
+int
iwmbt_enter_manufacturer(struct libusb_device_handle *hdl)
{
int ret, transferred;
@@ -502,8 +524,8 @@ iwmbt_get_version(struct libusb_device_handle *hdl,
}
int
-iwmbt_get_version_tlv(struct libusb_device_handle *hdl,
- struct iwmbt_version_tlv *version)
+iwmbt_read_version_tlv(struct libusb_device_handle *hdl,
+ uint8_t *data, uint8_t *datalen)
{
int ret, transferred;
struct iwmbt_hci_event_cmd_compl *event;
@@ -512,8 +534,6 @@ iwmbt_get_version_tlv(struct libusb_device_handle *hdl,
.length = 1,
.data = { 0xff },
};
- uint8_t status, datalen, type, len;
- uint8_t *data;
uint8_t buf[255];
memset(buf, 0, sizeof(buf));
@@ -533,106 +553,30 @@ iwmbt_get_version_tlv(struct libusb_device_handle *hdl,
}
event = (struct iwmbt_hci_event_cmd_compl *)buf;
- memcpy(version, event->data, sizeof(struct iwmbt_version));
+ *datalen = event->header.length - IWMBT_HCI_EVENT_COMPL_HEAD_SIZE;
+ memcpy(data, event->data, *datalen);
- datalen = event->header.length - IWMBT_HCI_EVENT_COMPL_HEAD_SIZE;
- data = event->data;
- status = *data++;
- if (status != 0)
- return (-1);
- datalen--;
+ return (0);
+}
- while (datalen >= 2) {
- type = *data++;
- len = *data++;
- datalen -= 2;
+int
+iwmbt_get_version_tlv(struct libusb_device_handle *hdl,
+ struct iwmbt_version_tlv *version)
+{
- if (datalen < len)
- return (-1);
+ uint8_t data[255];
+ uint8_t datalen;
+ int ret;
- switch (type) {
- case IWMBT_TLV_CNVI_TOP:
- assert(len == 4);
- version->cnvi_top = le32dec(data);
- break;
- case IWMBT_TLV_CNVR_TOP:
- assert(len == 4);
- version->cnvr_top = le32dec(data);
- break;
- case IWMBT_TLV_CNVI_BT:
- assert(len == 4);
- version->cnvi_bt = le32dec(data);
- break;
- case IWMBT_TLV_CNVR_BT:
- assert(len == 4);
- version->cnvr_bt = le32dec(data);
- break;
- case IWMBT_TLV_DEV_REV_ID:
- assert(len == 2);
- version->dev_rev_id = le16dec(data);
- break;
- case IWMBT_TLV_IMAGE_TYPE:
- assert(len == 1);
- version->img_type = *data;
- break;
- case IWMBT_TLV_TIME_STAMP:
- assert(len == 2);
- version->min_fw_build_cw = data[0];
- version->min_fw_build_yy = data[1];
- version->timestamp = le16dec(data);
- break;
- case IWMBT_TLV_BUILD_TYPE:
- assert(len == 1);
- version->build_type = *data;
- break;
- case IWMBT_TLV_BUILD_NUM:
- assert(len == 4);
- version->min_fw_build_nn = *data;
- version->build_num = le32dec(data);
- break;
- case IWMBT_TLV_SECURE_BOOT:
- assert(len == 1);
- version->secure_boot = *data;
- break;
- case IWMBT_TLV_OTP_LOCK:
- assert(len == 1);
- version->otp_lock = *data;
- break;
- case IWMBT_TLV_API_LOCK:
- assert(len == 1);
- version->api_lock = *data;
- break;
- case IWMBT_TLV_DEBUG_LOCK:
- assert(len == 1);
- version->debug_lock = *data;
- break;
- case IWMBT_TLV_MIN_FW:
- assert(len == 3);
- version->min_fw_build_nn = data[0];
- version->min_fw_build_cw = data[1];
- version->min_fw_build_yy = data[2];
- break;
- case IWMBT_TLV_LIMITED_CCE:
- assert(len == 1);
- version->limited_cce = *data;
- break;
- case IWMBT_TLV_SBE_TYPE:
- assert(len == 1);
- version->sbe_type = *data;
- break;
- case IWMBT_TLV_OTP_BDADDR:
- memcpy(&version->otp_bd_addr, data, sizeof(bdaddr_t));
- break;
- default:
- /* Ignore other types */
- break;
- }
+ memset(data, 0, sizeof(data));
- datalen -= len;
- data += len;
+ ret = iwmbt_read_version_tlv(hdl, data, &datalen);
+ if (ret < 0) {
+ iwmbt_debug("Can't get version tlv");
+ return (-1);
}
- return (0);
+ return (iwmbt_parse_tlv(data, datalen, version));
}
int
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h
index aac885dfd153..f1f1f56e0ca2 100644
--- a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h
@@ -101,11 +101,14 @@ extern int iwmbt_load_ecdsa_header(struct libusb_device_handle *hdl,
const struct iwmbt_firmware *fw);
extern int iwmbt_load_fwfile(struct libusb_device_handle *hdl,
const struct iwmbt_firmware *fw, uint32_t *boot_param, int offset);
+extern int iwmbt_bt_reset(struct libusb_device_handle *hdl);
extern int iwmbt_enter_manufacturer(struct libusb_device_handle *hdl);
extern int iwmbt_exit_manufacturer(struct libusb_device_handle *hdl,
enum iwmbt_mm_exit mode);
extern int iwmbt_get_version(struct libusb_device_handle *hdl,
struct iwmbt_version *version);
+extern int iwmbt_read_version_tlv(struct libusb_device_handle *hdl,
+ uint8_t *data, uint8_t *datalen);
extern int iwmbt_get_version_tlv(struct libusb_device_handle *hdl,
struct iwmbt_version_tlv *version);
extern int iwmbt_get_boot_params(struct libusb_device_handle *hdl,
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8
index ac32a675aa63..342656613421 100644
--- a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8
@@ -26,7 +26,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd July 15, 2025
+.Dd April 2, 2026
.Dt IWMBTFW 8
.Os
.Sh NAME
@@ -48,9 +48,10 @@ device.
.Pp
This utility will
.Em only
-work with Intel Wireless 7260/8260/9260 chip based Bluetooth USB devices
-and some of their successors.
-The identification is currently based on USB vendor ID/product ID pair.
+work with Intel Wireless 7260/8260/9260 and newer chip based Bluetooth
+USB devices, including AX and BE series wireless adapters.
+The identification is currently based on USB vendor ID/product ID pair
+and result of HCI queries.
The vendor ID should be 0x8087
.Pq Dv USB_VENDOR_INTEL2
and the product ID should be one of the supported devices.
@@ -78,6 +79,8 @@ device name.
Specify the directory containing the firmware files to search and upload.
.It Fl h
Display usage message and exit.
+.It Fl p
+Use only USB vendor ID/product ID pair for device model identification.
.El
.Sh EXIT STATUS
.Ex -std
diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf
index e30a3c15ccaa..d48206827f90 100644
--- a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf
+++ b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf
@@ -7,6 +7,6 @@ notify 100 {
match "subsystem" "DEVICE";
match "type" "ATTACH";
match "vendor" "0x8087";
- match "product" "(0x07dc|0x0a2a|0x0aa7|0x0a2b|0x0aaa|0x0025|0x0026|0x0029|0x0032|0x0033)";
+ match "product" "(0x07dc|0x0a2a|0x0aa7|0x0a2b|0x0aaa|0x0025|0x0026|0x0029|0x0032|0x0033|0x0035|0x0036)";
action "/usr/sbin/iwmbtfw -d $cdev -f /usr/local/share/iwmbt-firmware";
};
diff --git a/usr.sbin/bluetooth/iwmbtfw/main.c b/usr.sbin/bluetooth/iwmbtfw/main.c
index b27c5ad62239..560735fabd67 100644
--- a/usr.sbin/bluetooth/iwmbtfw/main.c
+++ b/usr.sbin/bluetooth/iwmbtfw/main.c
@@ -35,6 +35,7 @@
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -50,6 +51,7 @@
int iwmbt_do_debug = 0;
int iwmbt_do_info = 0;
+static bool iwmbt_do_pidvid = false;
enum iwmbt_device {
IWMBT_DEVICE_UNKNOWN,
@@ -81,6 +83,8 @@ static struct iwmbt_devid iwmbt_list[] = {
/* Intel Wireless 9260/9560 and successors */
{ .vendor_id = 0x8087, .product_id = 0x0032, .device = IWMBT_DEVICE_9260 },
{ .vendor_id = 0x8087, .product_id = 0x0033, .device = IWMBT_DEVICE_9260 },
+ { .vendor_id = 0x8087, .product_id = 0x0035, .device = IWMBT_DEVICE_9260 },
+ { .vendor_id = 0x8087, .product_id = 0x0036, .device = IWMBT_DEVICE_9260 },
};
static enum iwmbt_device
@@ -226,6 +230,65 @@ iwmbt_dump_version_tlv(struct iwmbt_version_tlv *ver)
ver->build_num);
}
+static enum iwmbt_device
+iwmbt_identify(libusb_device_handle *hdl, enum iwmbt_device device)
+{
+ uint8_t data[255];
+ uint8_t datalen, hw_platform, hw_variant;
+ struct iwmbt_version *ver = (struct iwmbt_version *)data;
+ struct iwmbt_version_tlv ver_tlv;
+ int r;
+
+ if (device == IWMBT_DEVICE_7260) {
+ r = iwmbt_bt_reset(hdl);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_bt_reset() failed!");
+ return (IWMBT_DEVICE_UNKNOWN);
+ }
+ }
+
+ memset(data, 0, sizeof(data));
+ r = iwmbt_read_version_tlv(hdl, data, &datalen);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_read_version_tlv() failed");
+ return (IWMBT_DEVICE_UNKNOWN);
+ }
+
+ if (datalen == sizeof(*ver) && ver->hw_platform == 0x37) {
+ switch (ver->hw_variant) {
+ case 0x07:
+ case 0x08:
+ return (IWMBT_DEVICE_7260);
+ case 0x0b:
+ case 0x0c:
+ case 0x11:
+ case 0x12:
+ case 0x13:
+ case 0x14:
+ return (IWMBT_DEVICE_8260);
+ default:
+ iwmbt_debug("Unsupported hw_variant (0x%2x)",
+ ver->hw_variant);
+ return (IWMBT_DEVICE_UNKNOWN);
+ }
+ }
+
+ r = iwmbt_parse_tlv(data, datalen, &ver_tlv);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_parse_tlv() failed");
+ return (IWMBT_DEVICE_UNKNOWN);
+ }
+
+ hw_platform = (ver_tlv.cnvi_bt >> 8) & 0xff;
+ hw_variant = (ver_tlv.cnvi_bt >> 16) & 0x3f;
+
+ if (hw_platform != 0x37) {
+ iwmbt_debug("Unsupported hw_platform (0x%2x)", hw_platform);
+ return (IWMBT_DEVICE_UNKNOWN);
+ }
+
+ return (hw_variant < 0x17 ? IWMBT_DEVICE_8260 : IWMBT_DEVICE_9260);
+}
static int
iwmbt_init_firmware(libusb_device_handle *hdl, const char *firmware_path,
@@ -375,11 +438,10 @@ usage(void)
fprintf(stderr, " -f: firmware path (defaults to %s)\n",
_DEFAULT_IWMBT_FIRMWARE_PATH);
fprintf(stderr, " -I: enable informational output\n");
+ fprintf(stderr, " -p: use PID/VID for model identification\n");
exit(127);
}
-
-
/*
* Returns 0 on success.
*/
@@ -556,7 +618,6 @@ handle_9260(libusb_device_handle *hdl, char *firmware_dir)
{
int r;
uint32_t boot_param;
- struct iwmbt_version vl;
struct iwmbt_version_tlv vt;
char *firmware_path = NULL;
@@ -616,9 +677,9 @@ handle_9260(libusb_device_handle *hdl, char *firmware_dir)
/* Once device is running in operational mode we can ignore failures */
- r = iwmbt_get_version(hdl, &vl);
+ r = iwmbt_get_version_tlv(hdl, &vt);
if (r == 0)
- iwmbt_dump_version(&vl);
+ iwmbt_dump_version_tlv(&vt);
/* Apply the device configuration (DDC) parameters */
firmware_path = iwmbt_get_fwname_tlv(&vt, firmware_dir, "ddc");
@@ -671,6 +732,9 @@ main(int argc, char *argv[])
case 'I':
iwmbt_do_info = 1;
break;
+ case 'p':
+ iwmbt_do_pidvid = true;
+ break;
case 'h':
default:
usage();
@@ -728,6 +792,14 @@ main(int argc, char *argv[])
goto shutdown;
}
+ if (!iwmbt_do_pidvid) {
+ iwmbt_device = iwmbt_identify(hdl, iwmbt_device);
+ if (iwmbt_device == IWMBT_DEVICE_UNKNOWN) {
+ iwmbt_err("Failed to identify device");
+ goto shutdown;
+ }
+ }
+
switch(iwmbt_device) {
case IWMBT_DEVICE_7260:
retcode = handle_7260(hdl, firmware_dir);
diff --git a/usr.sbin/bluetooth/rtlbtfw/main.c b/usr.sbin/bluetooth/rtlbtfw/main.c
index 58503b8087b5..dc9ccd6c5fcd 100644
--- a/usr.sbin/bluetooth/rtlbtfw/main.c
+++ b/usr.sbin/bluetooth/rtlbtfw/main.c
@@ -57,21 +57,19 @@ struct rtlbt_devid {
};
static struct rtlbt_devid rtlbt_list[] = {
+ /*
+ * Non-Realtek vendors using Realtek Bluetooth chipsets.
+ * Devices with vendor 0x0bda are already matched by the
+ * generic check in rtlbt_find_device().
+ */
+
/* Realtek 8821CE Bluetooth devices */
{ .vendor_id = 0x13d3, .product_id = 0x3529 },
- /* Realtek 8822CE Bluetooth devices */
- { .vendor_id = 0x0bda, .product_id = 0xb00c },
- { .vendor_id = 0x0bda, .product_id = 0xc822 },
-
/* Realtek 8851BE Bluetooth devices */
{ .vendor_id = 0x13d3, .product_id = 0x3600 },
/* Realtek 8852AE Bluetooth devices */
- { .vendor_id = 0x0bda, .product_id = 0x2852 },
- { .vendor_id = 0x0bda, .product_id = 0xc852 },
- { .vendor_id = 0x0bda, .product_id = 0x385a },
- { .vendor_id = 0x0bda, .product_id = 0x4852 },
{ .vendor_id = 0x04c5, .product_id = 0x165c },
{ .vendor_id = 0x04ca, .product_id = 0x4006 },
{ .vendor_id = 0x0cb8, .product_id = 0xc549 },
@@ -83,13 +81,11 @@ static struct rtlbt_devid rtlbt_list[] = {
{ .vendor_id = 0x13d3, .product_id = 0x3587 },
{ .vendor_id = 0x13d3, .product_id = 0x3586 },
{ .vendor_id = 0x13d3, .product_id = 0x3592 },
+ { .vendor_id = 0x13d3, .product_id = 0x3612 },
{ .vendor_id = 0x0489, .product_id = 0xe122 },
/* Realtek 8852BE Bluetooth devices */
{ .vendor_id = 0x0cb8, .product_id = 0xc559 },
- { .vendor_id = 0x0bda, .product_id = 0x4853 },
- { .vendor_id = 0x0bda, .product_id = 0x887b },
- { .vendor_id = 0x0bda, .product_id = 0xb85b },
{ .vendor_id = 0x13d3, .product_id = 0x3570 },
{ .vendor_id = 0x13d3, .product_id = 0x3571 },
{ .vendor_id = 0x13d3, .product_id = 0x3572 },
@@ -97,11 +93,7 @@ static struct rtlbt_devid rtlbt_list[] = {
{ .vendor_id = 0x0489, .product_id = 0xe123 },
{ .vendor_id = 0x0489, .product_id = 0xe125 },
- /* Realtek 8852BT/8852BE-VT Bluetooth devices */
- { .vendor_id = 0x0bda, .product_id = 0x8520 },
-
/* Realtek 8922AE Bluetooth devices */
- { .vendor_id = 0x0bda, .product_id = 0x8922 },
{ .vendor_id = 0x13d3, .product_id = 0x3617 },
{ .vendor_id = 0x13d3, .product_id = 0x3616 },
{ .vendor_id = 0x0489, .product_id = 0xe130 },
@@ -123,7 +115,6 @@ static struct rtlbt_devid rtlbt_list[] = {
{ .vendor_id = 0x7392, .product_id = 0xa611 },
/* Realtek 8723DE Bluetooth devices */
- { .vendor_id = 0x0bda, .product_id = 0xb009 },
{ .vendor_id = 0x2ff8, .product_id = 0xb011 },
/* Realtek 8761BUV Bluetooth devices */
@@ -131,7 +122,6 @@ static struct rtlbt_devid rtlbt_list[] = {
{ .vendor_id = 0x2357, .product_id = 0x0604 },
{ .vendor_id = 0x0b05, .product_id = 0x190e },
{ .vendor_id = 0x2550, .product_id = 0x8761 },
- { .vendor_id = 0x0bda, .product_id = 0x8771 },
{ .vendor_id = 0x6655, .product_id = 0x8771 },
{ .vendor_id = 0x7392, .product_id = 0xc611 },
{ .vendor_id = 0x2b89, .product_id = 0x8761 },
@@ -157,7 +147,6 @@ static struct rtlbt_devid rtlbt_list[] = {
{ .vendor_id = 0x13d3, .product_id = 0x3555 },
{ .vendor_id = 0x2ff8, .product_id = 0x3051 },
{ .vendor_id = 0x1358, .product_id = 0xc123 },
- { .vendor_id = 0x0bda, .product_id = 0xc123 },
{ .vendor_id = 0x0cb5, .product_id = 0xc547 },
};
diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c
index d7e9f2f939c6..aadd254571bb 100644
--- a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c
@@ -29,6 +29,7 @@
#include <sys/param.h>
#include <sys/endian.h>
+#include <sys/mman.h>
#include <sys/stat.h>
#include <err.h>
@@ -166,8 +167,7 @@ rtlbt_fw_read(struct rtlbt_firmware *fw, const char *fwname)
{
int fd;
struct stat sb;
- unsigned char *buf;
- ssize_t r;
+ unsigned char *buf, *mmap_addr;
fd = open(fwname, O_RDONLY);
if (fd < 0) {
@@ -188,23 +188,16 @@ rtlbt_fw_read(struct rtlbt_firmware *fw, const char *fwname)
return (0);
}
- /* XXX handle partial reads */
- r = read(fd, buf, sb.st_size);
- if (r < 0) {
- warn("%s: read", __func__);
+ mmap_addr = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (mmap_addr == MAP_FAILED) {
+ warn("%s: mmap: %s", __func__, fwname);
free(buf);
close(fd);
return (0);
}
- if (r != sb.st_size) {
- rtlbt_err("read len %d != file size %d",
- (int) r,
- (int) sb.st_size);
- free(buf);
- close(fd);
- return (0);
- }
+ memcpy(buf, mmap_addr, sb.st_size);
+ munmap(mmap_addr, sb.st_size);
/* We have everything, so! */
diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf
index 2ef56d2af93a..f27e0ee50ccc 100644
--- a/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf
+++ b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf
@@ -26,16 +26,6 @@ notify 100 {
action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
};
-# Realtek 8822CE Bluetooth devices
-notify 100 {
- match "system" "USB";
- match "subsystem" "DEVICE";
- match "type" "ATTACH";
- match "vendor" "0x0bda";
- match "product" "(0xb00c|0xc822)";
- action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
-};
-
# Realtek 8851BE Bluetooth devices
notify 100 {
match "system" "USB";
@@ -51,14 +41,6 @@ notify 100 {
match "system" "USB";
match "subsystem" "DEVICE";
match "type" "ATTACH";
- match "vendor" "0x0bda";
- match "product" "(0x2852|0xc852|0x385a|0x4852)";
- action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
-};
-notify 100 {
- match "system" "USB";
- match "subsystem" "DEVICE";
- match "type" "ATTACH";
match "vendor" "0x04c5";
match "product" "0x165c";
action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
@@ -110,7 +92,7 @@ notify 100 {
match "subsystem" "DEVICE";
match "type" "ATTACH";
match "vendor" "0x13d3";
- match "product" "(0x3587|0x3586|0x3592)";
+ match "product" "(0x3587|0x3586|0x3592|0x3612)";
action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
};
notify 100 {
@@ -135,14 +117,6 @@ notify 100 {
match "system" "USB";
match "subsystem" "DEVICE";
match "type" "ATTACH";
- match "vendor" "0x0bda";
- match "product" "(0x4853|0x887b|0xb85b)";
- action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
-};
-notify 100 {
- match "system" "USB";
- match "subsystem" "DEVICE";
- match "type" "ATTACH";
match "vendor" "0x13d3";
match "product" "(0x3570|0x3571|0x3572|0x3591)";
action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
@@ -156,29 +130,11 @@ notify 100 {
action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
};
-# Realtek 8852BT/8852BE-VT Bluetooth devices
-notify 100 {
- match "system" "USB";
- match "subsystem" "DEVICE";
- match "type" "ATTACH";
- match "vendor" "0x0bda";
- match "product" "0x8520";
- action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
-};
-
# Realtek 8922AE Bluetooth devices
notify 100 {
match "system" "USB";
match "subsystem" "DEVICE";
match "type" "ATTACH";
- match "vendor" "0x0bda";
- match "product" "0x8922";
- action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
-};
-notify 100 {
- match "system" "USB";
- match "subsystem" "DEVICE";
- match "type" "ATTACH";
match "vendor" "0x13d3";
match "product" "(0x3617|0x3616)";
action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
@@ -251,14 +207,6 @@ notify 100 {
match "system" "USB";
match "subsystem" "DEVICE";
match "type" "ATTACH";
- match "vendor" "0x0bda";
- match "product" "0xb009";
- action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
-};
-notify 100 {
- match "system" "USB";
- match "subsystem" "DEVICE";
- match "type" "ATTACH";
match "vendor" "0x2ff8";
match "product" "0xb011";
action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
@@ -301,14 +249,6 @@ notify 100 {
match "system" "USB";
match "subsystem" "DEVICE";
match "type" "ATTACH";
- match "vendor" "0x0bda";
- match "product" "0x8771";
- action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
-};
-notify 100 {
- match "system" "USB";
- match "subsystem" "DEVICE";
- match "type" "ATTACH";
match "vendor" "0x6655";
match "product" "0x8771";
action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
@@ -419,14 +359,6 @@ notify 100 {
match "system" "USB";
match "subsystem" "DEVICE";
match "type" "ATTACH";
- match "vendor" "0x0bda";
- match "product" "0xc123";
- action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
-};
-notify 100 {
- match "system" "USB";
- match "subsystem" "DEVICE";
- match "type" "ATTACH";
match "vendor" "0x0cb5";
match "product" "0xc547";
action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware";
diff --git a/usr.sbin/boot0cfg/boot0cfg.8 b/usr.sbin/boot0cfg/boot0cfg.8
index d2e01ada3552..6688d97c65fb 100644
--- a/usr.sbin/boot0cfg/boot0cfg.8
+++ b/usr.sbin/boot0cfg/boot0cfg.8
@@ -22,7 +22,7 @@
.\" OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
.\" EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
-.Dd October 1, 2013
+.Dd July 29, 2026
.Dt BOOT0CFG 8
.Os
.Sh NAME
@@ -40,7 +40,7 @@
.Op Fl o Ar options
.Op Fl s Ar slice
.Op Fl t Ar ticks
-.Ar disk
+.Ar disk | file
.Sh DESCRIPTION
The
.Fx
@@ -62,9 +62,15 @@ The
utility optionally installs the
.Sq boot0
boot manager on the specified
-.Ar disk ;
+.Ar disk
+or
+.Ar file ;
and allows various operational parameters to be configured.
.Pp
+If a file is specified as the target argument, all operations are
+performed entirely in-place.
+This allows for offline payload preparation.
+.Pp
On PCs, a boot manager typically occupies sector 0 of a disk, which is
known as the Master Boot Record (MBR).
The MBR contains both code (to which control is passed by the PC BIOS)
@@ -104,8 +110,10 @@ Specify that a backup copy of the preexisting MBR should be written to
This file is created if it does not exist, and replaced if it does.
.It Fl i Ar volume-id
Specifies a volume-id (in the form XXXX-XXXX) to be saved at location
-0x1b8 in the MBR. This information is sometimes used by NT, XP and Vista
-to identify the disk drive. The option is only compatible with version 2.00
+0x1b8 in the MBR.
+This information is sometimes used by NT, XP and Vista to identify the
+disk drive.
+The option is only compatible with version 2.00
of the 512-byte boot block.
.It Fl m Ar mask
Specify slices to be enabled/disabled, where
@@ -177,6 +185,17 @@ To enable just slices 1 and 3 in the menu:
.Pp
.Dl "boot0cfg -m 0x5 ada0"
.Pp
+Create a copy of
+.Sq boot0
+with only slices 1 and 3 enabled in the menu and install that onto a disk using
+.Xr gpart 8 :
+.Pp
+.Bd -literal -offset indent -compact
+cp /boot/boot0 /boot/boot0.custom
+boot0cfg -m 0x5 /boot/boot0.custom
+gpart bootcode -b /boot/boot0.custom ada0
+.Ed
+.Pp
To go back to non-interactive booting, use
.Xr gpart 8
to install the default MBR:
@@ -199,4 +218,5 @@ Use of the
.Sq setdrv
option with an incorrect -d operand may cause the boot0 code
to write the MBR to the wrong disk, thus trashing its previous
-content. Be careful.
+content.
+Be careful.
diff --git a/usr.sbin/boot0cfg/boot0cfg.c b/usr.sbin/boot0cfg/boot0cfg.c
index 2a919f0b2f2e..172281172908 100644
--- a/usr.sbin/boot0cfg/boot0cfg.c
+++ b/usr.sbin/boot0cfg/boot0cfg.c
@@ -115,6 +115,7 @@ static int v_flag;
int
main(int argc, char *argv[])
{
+ struct stat sb;
u_int8_t *mbr, *boot0;
int boot0_size, mbr_size;
const char *bpath, *fpath;
@@ -183,9 +184,15 @@ main(int argc, char *argv[])
argv += optind;
if (argc != 1)
usage();
- disk = g_device_path(*argv);
- if (disk == NULL)
- errx(1, "Unable to get providername for %s\n", *argv);
+ if (stat(*argv, &sb) == 0 && S_ISREG(sb.st_mode)) {
+ disk = strdup(*argv);
+ if (disk == NULL)
+ err(1, "strdup");
+ } else {
+ disk = g_device_path(*argv);
+ if (disk == NULL)
+ errx(1, "Unable to get providername for %s\n", *argv);
+ }
up = B_flag || d_arg != -1 || m_arg != -1 || o_flag || s_arg != -1
|| t_arg != -1;
@@ -373,6 +380,7 @@ write_mbr(const char *fname, int flags, u_int8_t *mbr, int mbr_size,
int disable_dsn)
{
struct gctl_req *grq;
+ struct stat sb;
const char *errmsg;
char *pname;
ssize_t n;
@@ -394,20 +402,40 @@ write_mbr(const char *fname, int flags, u_int8_t *mbr, int mbr_size,
if (flags != 0)
err(1, "can't open file %s to write backup", fname);
- /* Try open it read only. */
+ /*
+ * Try to open it read only to pass to GEOM,
+ * or inspect if it's a regular file.
+ */
fd = open(fname, O_RDONLY);
if (fd == -1) {
warn("error opening %s", fname);
return;
}
+ /*
+ * If name is a regular file, re-open it in write-only mode
+ * and write the MBR straight to it.
+ */
+ if (fstat(fd, &sb) == 0 && S_ISREG(sb.st_mode)) {
+ close(fd);
+ fd = open(fname, O_WRONLY);
+ if (fd == -1)
+ err(1, "can't open %s for writing", fname);
+ n = write(fd, mbr, mbr_size);
+ close(fd);
+ if (n != mbr_size)
+ errx(1, "%s: short write", fname);
+ return;
+ }
+
pname = g_providername(fd);
+ close(fd);
if (pname == NULL) {
warn("error getting providername for %s", fname);
return;
}
- /* First check that GEOM_PART is available */
+ /* Check that GEOM_PART is available */
if (geom_class_available("PART") != 0) {
grq = gctl_get_handle();
gctl_ro_param(grq, "class", -1, "PART");
@@ -594,6 +622,6 @@ usage(void)
{
fprintf(stderr, "%s\n%s\n",
"usage: boot0cfg [-Bv] [-b boot0] [-d drive] [-f file] [-m mask]",
- " [-o options] [-s slice] [-t ticks] disk");
+ " [-o options] [-s slice] [-t ticks] disk | file");
exit(1);
}
diff --git a/usr.sbin/bsdconfig/bsdconfig.8 b/usr.sbin/bsdconfig/bsdconfig.8
index c4fb1e612dbb..aa81785a3acc 100644
--- a/usr.sbin/bsdconfig/bsdconfig.8
+++ b/usr.sbin/bsdconfig/bsdconfig.8
@@ -23,7 +23,7 @@
.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
-.Dd April 12, 2020
+.Dd June 9, 2026
.Dt BSDCONFIG 8
.Os
.Sh NAME
@@ -170,29 +170,43 @@ Shortcut to the Delete menu under the View/Edit Startup Configuration menu
(startup_rcconf) of startup.
.It Cm startup_rcvar
Shortcut to the Toggle Startup Services menu under startup.
-.\" use neutral name, e.g. console_keymap instead of syscons_keymap?
-.\" font (encoding) selection not applicable to vt(4)!
.It Cm syscons_font
-Shortcut to the Font menu under console.
-.\" .It Cm console_keymap
-.\" Shortcut to the Keymap menu under console.
+Shortcut to the Font menu under console for
+.Xr syscons 4 .
.It Cm syscons_keymap
-Shortcut to the Keymap menu under console.
-.\" .It Cm vt_repeat
-.\" Shortcut to the Repeat menu under console.
+Shortcut to the Keymap menu under console for
+.Xr syscons 4 .
.It Cm syscons_repeat
-Shortcut to the Repeat menu under console.
+Shortcut to the Repeat menu under console for
+.Xr syscons 4 .
.\" .It Cm vt_saver
-.\" Shortcut to the Saver menu under console.
.It Cm syscons_saver
-Shortcut to the Saver menu under console.
-.\" screenmap (encoding) selection not applicable to vt(4)!
+Shortcut to the Saver menu under console for
+.Xr syscons 4 .
.It Cm syscons_screenmap
-Shortcut to the Screenmap menu under console.
-.\" .It Cm vt_syscons_ttys
-.\" Shortcut to the Ttys menu under console.
+Shortcut to the Screenmap menu under console for
+.Xr syscons 4 .
.It Cm syscons_ttys
-Shortcut to the Ttys menu under console.
+Shortcut to the Ttys menu under console for
+.Xr syscons 4 .
+.It Cm vt_font
+Shortcut to the Font menu under console for
+.Xr vt 4 .
+.It Cm vt_keymap
+Shortcut to the Keymap menu under console for
+.Xr vt 4 .
+.It Cm vt_repeat
+Shortcut to the Repeat menu under console for
+.Xr vt 4 .
+.It Cm vt_saver
+Shortcut to the Saver menu under console for
+.Xr vt 4 .
+.It Cm vt_screenmap
+Shortcut to the Screenmap menu under console for
+.Xr vt 4 .
+.It Cm vt_ttys
+Shortcut to the Ttys menu under console for
+.Xr vt 4 .
.It Cm timezone
Set the regional timezone of the local machine.
.It Cm ttys
diff --git a/usr.sbin/bsdconfig/dot/USAGE b/usr.sbin/bsdconfig/dot/USAGE
index 6de68c1dc3bb..53dd1c1a04f5 100644
--- a/usr.sbin/bsdconfig/dot/USAGE
+++ b/usr.sbin/bsdconfig/dot/USAGE
@@ -1,4 +1,4 @@
-# Copyright (c) 2012-2013 Devin Teske
+# Copyright (c) 2012-2026 Devin Teske
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -44,12 +44,6 @@ EXAMPLES:
View the above-rendered SVG file using your favorite X11-based viewer:
- gimmage bsdconfig.svg
-
- NOTE: Requires `graphics/gimmage' from ports/packages.
-
- or
-
gthumb bsdconfig.svg
NOTE: Image is scaled to fit window on launch.
@@ -57,19 +51,6 @@ EXAMPLES:
or
- gqview bsdconfig.svg
-
- NOTE: Requires `graphics/gqview' from ports/packages.
-
- or
-
- gx bsdconfig.svg
-
- NOTE: Image is scaled to fit window on launch.
- NOTE: Requires `graphics/gx' from ports/packages.
-
- or
-
eog bsdconfig.svg
NOTE: Requires `graphics/eog' from ports/packages.
@@ -98,16 +79,10 @@ EXAMPLES:
View the above-rendered PostScript poster using X11:
- gsview bsdconfig.ps
-
- NOTE: Requires `print/gsview' from ports/packages.
-
- or
-
- convert bsdconfig.ps bsdconfig.pdf
+ gm convert bsdconfig.ps bsdconfig.pdf
xpdf bsdconfig.pdf
- NOTE: Requires both `graphics/ImageMagick' and
+ NOTE: Requires both `graphics/GraphicsMagick' and
`graphics/xdpf' from ports/packages.
NOTE: The converted PDF file is not suitable for
diff --git a/usr.sbin/bsdconfig/share/common.subr b/usr.sbin/bsdconfig/share/common.subr
index 830279168ff2..f4fda1a22ac2 100644
--- a/usr.sbin/bsdconfig/share/common.subr
+++ b/usr.sbin/bsdconfig/share/common.subr
@@ -440,7 +440,7 @@ f_yesno()
# f_noyes $format [$arguments ...]
#
# Display a message in a dialog yes/no box using printf(1) syntax.
-# NOTE: THis is just like the f_yesno function except "No" is default.
+# NOTE: This is just like the f_yesno function except "No" is default.
#
f_noyes()
{
diff --git a/usr.sbin/bsdconfig/share/media/wlan.subr b/usr.sbin/bsdconfig/share/media/wlan.subr
index 5847356d1925..5d271c403b3c 100644
--- a/usr.sbin/bsdconfig/share/media/wlan.subr
+++ b/usr.sbin/bsdconfig/share/media/wlan.subr
@@ -813,6 +813,7 @@ f_dialog_wireless_edit()
[ $nmatches -le ${#DIALOG_MENU_TAGS} ] || break
f_substr -v tag "$DIALOG_MENU_TAGS" $nmatches 1
+ f_shell_escape "$wssid" wssid
f_wireless_describe WIRELESS_$n help
menu_list1="$menu_list1
'$tag $wssid' '$wbssid' '$help'
@@ -1076,6 +1077,7 @@ f_menu_wireless_configs()
while [ $n -lt $nunique ]; do
n=$(( $n + 1 ))
menuitem_$n get ssid ssid
+ f_shell_escape "$ssid" ssid
menuitem_$n get nconfigs nconfigs
desc="$nconfigs $msg_configured_lc"
@@ -1184,6 +1186,7 @@ f_menu_wpa_scan_results()
while [ $n -lt $nunique ]; do
n=$(( $n + 1 ))
menuitem_$n get ssid ssid
+ f_shell_escape "$ssid" ssid
desc=
if [ "$DIALOG_MENU_WLAN_SHOW_ALL" ]; then
diff --git a/usr.sbin/bsdinstall/FreeBSD-base.conf.in b/usr.sbin/bsdinstall/FreeBSD-base.conf.in
index 86ec8fabe253..13c94bbb8a64 100644
--- a/usr.sbin/bsdinstall/FreeBSD-base.conf.in
+++ b/usr.sbin/bsdinstall/FreeBSD-base.conf.in
@@ -1,5 +1,5 @@
FreeBSD-base: {
- url: "pkg+https://%%PKGHOST%%.FreeBSD.org/${ABI}/%%SUBURL%%",
+ url: "pkg+https://pkg.FreeBSD.org/${ABI}/%%SUBURL%%",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/%%KEYSDIR%%",
diff --git a/usr.sbin/bsdinstall/Makefile b/usr.sbin/bsdinstall/Makefile
index a576f2d0794e..f796a8710c5b 100644
--- a/usr.sbin/bsdinstall/Makefile
+++ b/usr.sbin/bsdinstall/Makefile
@@ -21,26 +21,22 @@ _REVISION!= ${MAKE} -C ${SRCTOP}/release -V REVISION
REVISION?= ${_REVISION}
.if ${BRANCH} == CURRENT || ${BRANCH} == STABLE
-PKGHOST= pkg
KEYSDIR= pkg
SUBURL= base_latest
.elif ${BRANCH} == RELEASE
-PKGHOST= pkgbase
KEYSDIR= pkgbase-${REVISION:C/\.[0-9]+//}
SUBURL= base_release_${REVISION:C/[0-9]+\.//}
.elif ${BRANCH:C/[0-9]+$//} == BETA || \
${BRANCH:C/[0-9]+$//} == RC
-PKGHOST= pkgbase
KEYSDIR= pkgbase-${REVISION:C/\.[0-9]+//}
SUBURL= base_release_${REVISION:C/[0-9]+\.//}_${BRANCH:tl}
.else
.warning Invalid branch "${BRANCH}"
-PKGHOST= pkg
KEYSDIR= pkg
SUBURL= base_latest
.endif
FreeBSD-base.conf: FreeBSD-base.conf.in
- sed "s|%%PKGHOST%%|${PKGHOST}|;s|%%SUBURL%%|${SUBURL}|;s|%%KEYSDIR%%|${KEYSDIR}|" < ${.ALLSRC} > ${.TARGET}
+ sed "s|%%SUBURL%%|${SUBURL}|;s|%%KEYSDIR%%|${KEYSDIR}|" < ${.ALLSRC} > ${.TARGET}
.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdinstall/bsdinstall.8 b/usr.sbin/bsdinstall/bsdinstall.8
index ee141e1d4296..a35c8f56ada7 100644
--- a/usr.sbin/bsdinstall/bsdinstall.8
+++ b/usr.sbin/bsdinstall/bsdinstall.8
@@ -29,7 +29,7 @@
.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
-.Dd October 3, 2025
+.Dd May 12, 2026
.Dt BSDINSTALL 8
.Os
.Sh NAME
@@ -79,6 +79,12 @@ targets.
.Bl -tag -width "-D file"
.It Cm auto
Run the standard interactive installation, including disk partitioning.
+.Pp
+The
+.Ev DISTRIBUTIONS
+environment variable is set to
+.Dq base.txz kernel.txz
+by default for this target.
.It Cm jail Ar destination
Sets up a new chroot system at
.Pa destination ,
@@ -88,12 +94,27 @@ Behavior is generally similar to
.Cm auto ,
except that disk partitioning and network setup are skipped and a kernel is
not installed into the new system.
+.Pp
+The
+.Ev DISTRIBUTIONS
+environment variable is set to
+.Dq base.txz
+by default for this target.
.It Cm script Ar script
Runs the installation script at
.Pa script .
See
.Sx SCRIPTING
for more information on this target.
+.Pp
+The
+.Ev DISTRIBUTIONS
+environment variable is unset by default for this target.
+Instead, the
+.Ev COMPONENTS
+environment variable is effectively set to
+.Dq base kernel kernel-dbg
+for this target.
.It Cm keymap
If the current controlling TTY is a
.Xr syscons 4
@@ -303,9 +324,20 @@ overridden when making scripted or customized installers.
The directory to use for temporary files.
Default:
.Dq Pa /tmp
+.It Ev COMPONENTS
+The set of components to install for scripted installations using
+base system packages, e.g.,
+.Dq base lib32 kernel-dbg tests .
+Default: "base kernel-dbg"
.It Ev DISTRIBUTIONS
-The set of distributions to install, e.g., "base.txz kernel.txz ports.txz".
-Default: unset
+The set of distributions to install for traditional installations, e.g.,
+.Dq base.txz kernel.txz ports.txz .
+If unset, then
+.Nm
+will install using base system packages.
+Default: unset unless noted otherwise in the
+.Sx TARGETS
+section.
.It Ev PARTITIONS
The partitioning of the disk onto which the system is being installed.
See
@@ -589,18 +621,18 @@ A typical bsdinstall script, using the default filesystem layout and the UFS
filesystem, looks like this:
.Bd -literal -offset indent
PARTITIONS=DEFAULT
-DISTRIBUTIONS="kernel.txz base.txz"
+COMPONENTS="base debug"
#!/bin/sh
sysrc ifconfig_DEFAULT=DHCP
sysrc sshd_enable=YES
-pkg install puppet
+pkg install -y puppet
.Ed
.Pp
For a scripted installation involving a ZFS pool spanning multiple disks,
the script instead looks like this:
.Bd -literal -offset indent
-DISTRIBUTIONS="kernel.txz base.txz"
+COMPONENTS="base debug"
export ZFSBOOT_VDEV_TYPE=stripe
export ZFSBOOT_DISKS="ada0 ada1"
export nonInteractive="YES"
@@ -608,7 +640,20 @@ export nonInteractive="YES"
#!/bin/sh
echo "ifconfig_DEFAULT=DHCP" >> /etc/rc.conf
echo "sshd_enable=YES" >> /etc/rc.conf
-pkg install puppet
+pkg install -y puppet
+.Ed
+.Pp
+To install using traditional distributions sets instead of packages, set
+.Ev DISTRIBUTIONS
+to the list of distribution sets to install, like this:
+.Bd -literal -offset indent
+PARTITIONS=DEFAULT
+DISTRIBUTIONS="kernel.txz base.txz"
+
+#!/bin/sh
+sysrc ifconfig_DEFAULT=DHCP
+sysrc sshd_enable=YES
+pkg install -y puppet
.Ed
.Pp
On
@@ -633,7 +678,7 @@ arbitrary commands can be run here to extend the installer.
In addition to the variables in
.Sx ENVIRONMENT VARIABLES ,
in particular
-.Ev DISTRIBUTIONS ,
+.Ev COMPONENTS ,
the preamble can contain a variable
.Ev PARTITIONS
which is passed to the
diff --git a/usr.sbin/bsdinstall/distextract/distextract.c b/usr.sbin/bsdinstall/distextract/distextract.c
index 32bd9453eb80..35cf19aae33f 100644
--- a/usr.sbin/bsdinstall/distextract/distextract.c
+++ b/usr.sbin/bsdinstall/distextract/distextract.c
@@ -291,6 +291,8 @@ extract_files(struct bsddialog_fileminibar *file)
/* If that went well, perform the extraction */
if (retval == ARCHIVE_OK)
retval = archive_read_extract(archive, entry,
+ ARCHIVE_EXTRACT_SECURE_NODOTDOT |
+ ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS |
ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER |
ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL |
ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS);
diff --git a/usr.sbin/bsdinstall/partedit/partedit_efi.c b/usr.sbin/bsdinstall/partedit/partedit_efi.c
index 7c4f85a69b8b..21d03c6668ba 100644
--- a/usr.sbin/bsdinstall/partedit/partedit_efi.c
+++ b/usr.sbin/bsdinstall/partedit/partedit_efi.c
@@ -1,4 +1,6 @@
/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
* Copyright (C) 2016 Cavium Inc.
* All rights reserved.
*
diff --git a/usr.sbin/bsdinstall/scripts/auto b/usr.sbin/bsdinstall/scripts/auto
index e9d6da149a85..ca0561daac1a 100755
--- a/usr.sbin/bsdinstall/scripts/auto
+++ b/usr.sbin/bsdinstall/scripts/auto
@@ -50,10 +50,10 @@ msg_abort="Abort"
msg_an_installation_step_has_been_aborted="An installation step has been aborted. Would you like\nto restart the installation or exit the installer?"
msg_auto_ufs="Auto (UFS)"
msg_auto_ufs_desc="Guided UFS Disk Setup"
-msg_auto_ufs_help="Menu options help choose which disk to setup using UFS and standard partitions"
+msg_auto_ufs_help="Choose which disk to setup using UFS and standard partition layout"
msg_auto_zfs="Auto (ZFS)"
msg_auto_zfs_desc="Guided Root-on-ZFS"
-msg_auto_zfs_help="To use ZFS with less than 8GB RAM, see https://wiki.freebsd.org/ZFSTuningGuide"
+msg_auto_zfs_help="Choose which disk to setup using ZFS and standard partition layout"
msg_exit="Exit"
msg_freebsd_installer="$OSNAME Installer"
msg_gpt_active_fix="Your hardware is known to have issues booting in CSM/Legacy/BIOS mode from GPT partitions that are not set active. Would you like the installer to apply this workaround for you?"
diff --git a/usr.sbin/bsdinstall/scripts/hardening b/usr.sbin/bsdinstall/scripts/hardening
index a77fcdeaf540..9f5639ee7c99 100755
--- a/usr.sbin/bsdinstall/scripts/hardening
+++ b/usr.sbin/bsdinstall/scripts/hardening
@@ -49,6 +49,7 @@ FEATURES=$( bsddialog --backtitle "$OSNAME Installer" \
"7 disable_syslogd" "Disable opening Syslogd network socket (disables remote logging)" ${disable_syslogd:-off} \
"8 secure_console" "Enable console password prompt" ${secure_console:-off} \
"9 disable_ddtrace" "Disallow DTrace destructive-mode" ${disable_ddtrace:-off} \
+ "10 disable_unpriv_kenv" "Disable unprivileged reading of kenv" ${disable_unpriv_kenv:-off} \
2>&1 1>&5 )
retval=$?
exec 5>&-
@@ -89,5 +90,8 @@ for feature in $FEATURES; do
disable_ddtrace)
echo 'security.bsd.allow_destructive_dtrace=0' >> $BSDINSTALL_TMPBOOT/loader.conf.hardening
;;
+ disable_unpriv_kenv)
+ echo 'security.bsd.unprivileged_kenv_read=0' >> $BSDINSTALL_TMPBOOT/loader.conf.hardening
+ ;;
esac
done
diff --git a/usr.sbin/bsdinstall/scripts/hostname b/usr.sbin/bsdinstall/scripts/hostname
index 19df8885893b..ec58ad5dfe66 100755
--- a/usr.sbin/bsdinstall/scripts/hostname
+++ b/usr.sbin/bsdinstall/scripts/hostname
@@ -54,7 +54,7 @@ msg_freebsd_installer="$OSNAME Installer"
msg_ok="OK"
msg_please_choose_a_hostname="Please choose a hostname for this machine.\n\nIf you are running on a managed network, please ask\nyour network administrator for an appropriate name."
msg_set_hostname="Set Hostname"
-
+msg_empty_hostname_warning="The hostname is currently empty. This is not recommended, as many network services rely on a valid hostname. Are you sure you want to continue?"
#
# Command strings for various tasks
#
@@ -97,10 +97,35 @@ f_dialog_title "$msg_set_hostname"
f_dialog_backtitle "$msg_freebsd_installer"
#
-# Get user input
+# Get user input and Warn if hostname is empty
#
-HOSTNAME=$( dialog_hostname "$HOSTNAME" )
-[ $? -eq $DIALOG_CANCEL ] && exit 1
+while :; do
+ HOSTNAME=$(dialog_hostname "$HOSTNAME")
+ [ $? -eq $DIALOG_CANCEL ] && exit 1
+
+ if [ -z "$HOSTNAME" ]; then
+ if [ "$USE_XDIALOG" ]; then
+ yes=yes no=no defaultno=defaultno
+ extra_args="--wrap --left"
+ else
+ yes=yes no=no defaultno=defaultno
+ extra_args="--colors --cr-wrap"
+ fi
+
+ $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --defaultno \
+ --yes-label "$yes" \
+ --no-label "$no" \
+ $extra_args \
+ --yesno "$msg_empty_hostname_warning" 0 0
+
+ [ $? -ne 0 ] && continue
+ fi
+
+ break
+done
#
# Store the user's choice
diff --git a/usr.sbin/bsdinstall/scripts/mirrorselect b/usr.sbin/bsdinstall/scripts/mirrorselect
index 65a662b2df62..835db62b220c 100755
--- a/usr.sbin/bsdinstall/scripts/mirrorselect
+++ b/usr.sbin/bsdinstall/scripts/mirrorselect
@@ -40,6 +40,7 @@ MIRROR=`bsddialog --backtitle "$OSNAME Installer" \
--title "Mirror Selection" --extra-button --extra-label "Other" \
--menu "Please select the best suitable site for you or \"other\" if you want to specify a different choice. The \"Main Site\" directs users to the nearest project managed mirror via GeoDNS (they carry the full range of possible distributions and support both IPv4 and IPv6). All other sites are known as \"Community Mirrors\"; not every site listed here carries more than the base distribution kits. Select a site!" \
0 0 16 \
+ http://download.freebsd.org "Main Site (GeoDNS, CDN, HTTP)"\
http://ftp.freebsd.org "Main Site (GeoDNS, HTTP)"\
ftp://ftp.freebsd.org "Main Site (GeoDNS, FTP)"\
http://ftp.au.freebsd.org "Australia - IPv6"\
@@ -102,7 +103,17 @@ case ${_UNAME_R} in
;;
esac
-BSDINSTALL_DISTSITE="$MIRROR/pub/FreeBSD/${RELDIR}/`uname -m`/`uname -p`/${_UNAME_R}"
+case ${MIRROR} in
+ *ftp*)
+ BSDINSTALL_DISTSITE="$MIRROR/pub/FreeBSD"
+ ;;
+ *)
+ BSDINSTALL_DISTSITE="$MIRROR"
+ ;;
+
+esac
+
+BSDINSTALL_DISTSITE="${BSDINSTALL_DISTSITE}/${RELDIR}/`uname -m`/`uname -p`/${_UNAME_R}"
case $MIRROR_BUTTON in
$BSDDIALOG_ERROR | $BSDDIALOG_CANCEL | $BSDDIALOG_ESC)
diff --git a/usr.sbin/bsdinstall/scripts/netconfig b/usr.sbin/bsdinstall/scripts/netconfig
index 0f23ae134cb6..ea005430798d 100755
--- a/usr.sbin/bsdinstall/scripts/netconfig
+++ b/usr.sbin/bsdinstall/scripts/netconfig
@@ -61,7 +61,7 @@ is_wireless_if() {
}
for IF in $INTERFACES; do
- DESC=`sysctl -n dev.$(echo $IF | sed -E 's/([[:alpha:]]*)([[:digit:]]*)/\1.\2/g').%desc`
+ DESC=`sysctl -n dev.$(echo $IF | sed -E 's/([[:alnum:]]*)([[:digit:]])/\1.\2/').%desc`
BSDDIALOG_ITEMS="$BSDDIALOG_ITEMS $IF \"$DESC\""
done
diff --git a/usr.sbin/bsdinstall/scripts/pkgbase.in b/usr.sbin/bsdinstall/scripts/pkgbase.in
index 89ddc244171e..2c7d6bcae904 100755
--- a/usr.sbin/bsdinstall/scripts/pkgbase.in
+++ b/usr.sbin/bsdinstall/scripts/pkgbase.in
@@ -180,7 +180,18 @@ local function select_components(components, options)
}
append_list(bsddialog_args, checklist_items)
- local exit_code, output = bsddialog(bsddialog_args)
+ local exit_code = 0
+ local output = ""
+ if options.non_interactive then
+ local env_components = os.getenv("COMPONENTS")
+ if env_components then
+ output = env_components:gsub(" ", "\n")
+ else
+ output = "base\nkernel-dbg"
+ end
+ else
+ exit_code, output = bsddialog(bsddialog_args)
+ end
-- This should only be possible if bsddialog is killed by a signal
-- or buggy, we disable the cancel option and esc key.
-- If this does happen, there's not much we can do except exit with a
@@ -300,6 +311,8 @@ local function parse_options()
for _, a in ipairs(arg) do
if a == "--jail" then
options.jail = true
+ elseif a == "--non-interactive" then
+ options.non_interactive = true
else
io.stderr:write("Error: unknown option " .. a .. "\n")
os.exit(1)
diff --git a/usr.sbin/bsdinstall/scripts/rootpass b/usr.sbin/bsdinstall/scripts/rootpass
index 9d25569ae946..5d3a44f4b179 100755
--- a/usr.sbin/bsdinstall/scripts/rootpass
+++ b/usr.sbin/bsdinstall/scripts/rootpass
@@ -103,8 +103,8 @@ $errormsg" \
exec 5>&-
[ $res -eq $BSDDIALOG_OK ] || exit 0
- echo -n "$output" | (read password1
- read password2
+ echo -n "$output" | (read -r password1
+ read -r password2
[ -n "$password1" -o -n "$password2" ] || exit 62
[ "$password1" = "$password2" ] || exit 63
echo "$password1" | chroot $BSDINSTALL_CHROOT \
diff --git a/usr.sbin/bsdinstall/scripts/script b/usr.sbin/bsdinstall/scripts/script
index 21da2ea7c366..4c792d80df6a 100755
--- a/usr.sbin/bsdinstall/scripts/script
+++ b/usr.sbin/bsdinstall/scripts/script
@@ -40,6 +40,7 @@ f_include $BSDCFG_SHARE/variable.subr
# PARTITIONS
# DISTRIBUTIONS
# BSDINSTALL_DISTDIR
+# COMPONENTS
#
# Default name of the ZFS boot-pool
@@ -49,6 +50,7 @@ f_include $BSDCFG_SHARE/variable.subr
############################################################ GLOBALS
: ${TMPDIR:="/tmp"}
+: ${DISTRIBUTIONS=""}; export DISTRIBUTIONS
#
# Strings that should be moved to an i18n file and loaded with f_include_lang()
@@ -97,7 +99,6 @@ awk 'BEGIN {pathb=ARGV[2]; ARGV[2]=""} /^#!/{b=1} {
>$TMPDIR/bsdinstall-installscript-preamble
. $TMPDIR/bsdinstall-installscript-preamble
-: ${DISTRIBUTIONS="kernel.txz base.txz"}; export DISTRIBUTIONS
export BSDINSTALL_DISTDIR
# Re-initialize a new log if preamble changed BSDINSTALL_LOG
@@ -118,37 +119,48 @@ else
fi
bsdinstall mount
-# Fetch missing distribution files, if any
-exec 5>&1
-export BSDINSTALL_DISTDIR=$(`dirname $0`/fetchmissingdists 2>&1 1>&5)
-FETCH_RESULT=$?
-exec 5>&-
+if [ -n "$COMPONENTS" -a -n "$DISTRIBUTIONS" ]; then
+ error "Cannot set both COMPONENTS and DISTRIBUTIONS"
+elif [ -z "$DISTRIBUTIONS" ]; then
+ # If COMPONENTS is set, or neither is, install with pkgbase
+ bsdinstall pkgbase --non-interactive
+else
+ # Otherwise, unpack distsets
-[ $FETCH_RESULT -ne 0 ] && error "Could not fetch remote distributions"
+ # Fetch missing distribution files, if any
+ exec 5>&1
+ export BSDINSTALL_DISTDIR=$(`dirname $0`/fetchmissingdists 2>&1 1>&5)
+ FETCH_RESULT=$?
+ exec 5>&-
-# Unpack distributions
-bsdinstall checksum
-if [ -t 0 ]; then
- # If install is a tty, use distextract as normal
- bsdinstall distextract
-else
- # Otherwise, we need to use tar (see https://reviews.freebsd.org/D10736)
- for set in $DISTRIBUTIONS; do
- f_dprintf "Extracting $BSDINSTALL_DISTDIR/$set"
- # XXX: The below fails if any mountpoints are FAT, due to
- # inability to set ctime/mtime on the root of FAT partitions,
- # which is needed to support e.g. EFI system partitions. tar has
- # no option to ignore this (distextract ignores them internally
- # through a hack), and returns 1 on any warning or error,
- # effectively turning all warnings into fatal errors.
- #
- # Work around this in an extremely lame way for the specific
- # case of EFI system partitions only. This *ONLY WORKS* if
- # /boot/efi is empty and does not handle analogous problems on
- # other systems (ARM, PPC64).
- tar -xf "$BSDINSTALL_DISTDIR/$set" -C $BSDINSTALL_CHROOT --exclude boot/efi
- mkdir -p $BSDINSTALL_CHROOT/boot/efi
- done
+ [ $FETCH_RESULT -ne 0 ] && error "Could not fetch remote distributions"
+
+ bsdinstall checksum
+ if [ -t 0 ]; then
+ # If install is a tty, use distextract as normal
+ bsdinstall distextract
+ else
+ # Otherwise, we need to use tar (see
+ # https://reviews.freebsd.org/D10736)
+ for set in $DISTRIBUTIONS; do
+ f_dprintf "Extracting $BSDINSTALL_DISTDIR/$set"
+ # XXX: The below fails if any mountpoints are FAT, due
+ # to inability to set ctime/mtime on the root of FAT
+ # partitions, which is needed to support e.g. EFI
+ # system partitions. tar has no option to ignore this
+ # (distextract ignores them internally through a hack),
+ # and returns 1 on any warning or error, effectively
+ # turning all warnings into fatal errors.
+ #
+ # Work around this in an extremely lame way for the
+ # specific case of EFI system partitions only. This
+ # *ONLY WORKS* if /boot/efi is empty and does not
+ # handle analogous problems on other systems (ARM,
+ # PPC64).
+ tar -xf "$BSDINSTALL_DISTDIR/$set" -C $BSDINSTALL_CHROOT --exclude boot/efi
+ mkdir -p $BSDINSTALL_CHROOT/boot/efi
+ done
+ fi
fi
# Configure bootloader if needed
diff --git a/usr.sbin/bsdinstall/scripts/services b/usr.sbin/bsdinstall/scripts/services
index 110b6f321ed1..814ce1f95753 100755
--- a/usr.sbin/bsdinstall/scripts/services
+++ b/usr.sbin/bsdinstall/scripts/services
@@ -71,6 +71,11 @@ if [ -x "${BSDINSTALL_CHROOT}/etc/rc.d/moused" ]; then
moused \"PS/2 mouse pointer on console\" ${moused_enable:-off}"
fi
+if [ -x "${BSDINSTALL_CHROOT}/etc/rc.d/virtual_oss" ]; then
+ DAEMON_OPTIONS="$DAEMON_OPTIONS \
+ virtual_oss \"Sound server\" ${virtual_oss_enable:-off}"
+fi
+
exec 5>&1
DAEMONS=$(eval bsddialog --backtitle \"$OSNAME Installer\" \
--title \"System Configuration\" --no-cancel --separate-output \
diff --git a/usr.sbin/bsdinstall/scripts/wlanconfig b/usr.sbin/bsdinstall/scripts/wlanconfig
index 33d94a933f45..aababf76c266 100755
--- a/usr.sbin/bsdinstall/scripts/wlanconfig
+++ b/usr.sbin/bsdinstall/scripts/wlanconfig
@@ -147,6 +147,34 @@ dialog_country_select()
country_set "$regdomain" "$country"
}
+dialog_network_select()
+{
+ local ssid flags height width rows prompt
+
+ # Avoid using eval on untrusted data.
+ set --
+ while IFS=$'\t' read -r ssid flags; do
+ [ -n "$ssid" ] || continue
+ set -- "$@" "$ssid" "$flags"
+ done <<EOF
+$NETWORKS
+EOF
+
+ f_dialog_title "Network Selection"
+ prompt="Select a wireless network to connect to."
+ f_dialog_menu_size height width rows \
+ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$prompt" "" "$@"
+ $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --extra-button \
+ --extra-label "Rescan" \
+ --menu "$prompt" \
+ $height $width $rows \
+ "$@" \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+}
+
############################################################ MAIN
: > "$BSDINSTALL_TMPETC/wpa_supplicant.conf"
@@ -213,27 +241,14 @@ while :; do
f_eval_catch -dk SCAN_RESULTS wlanconfig wpa_cli "wpa_cli scan_results"
NETWORKS=$( echo "$SCAN_RESULTS" | awk -F '\t' '
- /..:..:..:..:..:../ && $5 { printf "\"%s\"\t\"%s\"\n", $5, $4 }
+ /..:..:..:..:..:../ && $5 { print $5 "\t" $4 }
' | sort | uniq )
if [ ! "$NETWORKS" ]; then
f_dialog_title "$msg_error"
f_yesno "No wireless networks were found. Rescan?" && continue
else
- f_dialog_title "Network Selection"
- prompt="Select a wireless network to connect to."
- f_dialog_menu_size height width rows "$DIALOG_TITLE" \
- "$DIALOG_BACKTITLE" "$prompt" "" $NETWORKS
- NETWORK=$( eval $DIALOG \
- --title \"\$DIALOG_TITLE\" \
- --backtitle \"\$DIALOG_BACKTITLE\" \
- --extra-button \
- --extra-label \"Rescan\" \
- --menu \"\$prompt\" \
- $height $width $rows \
- $NETWORKS \
- 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
- )
+ NETWORK=$( dialog_network_select )
fi
retval=$?
f_dialog_data_sanitize NETWORK
@@ -270,7 +285,7 @@ while :; do
done
[ "$ENCRYPTION" ] || ENCRYPTION=$( echo "$NETWORKS" |
- awk -F '\t' "/^\"$NETWORK\"\t/ { print \$2 }" )
+ awk -F '\t' "/^$NETWORK\t/ { print \$2 }" )
if echo "$ENCRYPTION" | grep -q PSK; then
PASS=$( $DIALOG \
diff --git a/usr.sbin/bsdinstall/scripts/zfsboot b/usr.sbin/bsdinstall/scripts/zfsboot
index a973b03d17fd..dbf7e19c6f5b 100755
--- a/usr.sbin/bsdinstall/scripts/zfsboot
+++ b/usr.sbin/bsdinstall/scripts/zfsboot
@@ -242,6 +242,7 @@ ZPOOL_SET='zpool set %s "%s"'
hline_alnum_arrows_punc_tab_enter="Use alnum, arrows, punctuation, TAB or ENTER"
hline_arrows_space_tab_enter="Use arrows, SPACE, TAB or ENTER"
hline_arrows_tab_enter="Press arrows, TAB or ENTER"
+msg_all_possible_gpt_labels_already_taken="All possible GPT labels already taken"
msg_an_unknown_error_occurred="An unknown error occurred"
msg_back="Back"
msg_cancel="Cancel"
@@ -841,6 +842,24 @@ zfs_create_diskpart()
case "$ZFSBOOT_PARTITION_SCHEME" in
""|GPT*) f_dprintf "$funcname: Creating GPT layout..."
#
+ # 0. Check for potential GPT label conflicts with disks not
+ # managed by us and adjust $index accordingly
+ #
+ while [ -e /dev/gpt/efiboot$index ] ||
+ [ -e /dev/gpt/gptboot$index ] ||
+ [ -e /dev/gpt/boot$index ] ||
+ [ -e /dev/gpt/swap$index ] ||
+ [ -e /dev/gpt/zfs$index ]; do
+ index=$(( $index + 1 ))
+ if [ $index -ge 4096 ]; then
+ f_dprintf "$funcname: all possible GPT labels already taken"
+ msg_error="$msg_error: $funcname" f_show_err \
+ "$msg_all_possible_gpt_labels_already_taken"
+ return $FAILURE
+ fi
+ done
+
+ #
# 1. Create GPT layout using labels
#
f_eval_catch $funcname gpart "$GPART_CREATE" gpt $disk ||
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_fs_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_fs_tbl.c
index bdc20d19a372..147522036bf9 100644
--- a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_fs_tbl.c
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_fs_tbl.c
@@ -205,6 +205,7 @@ fs_entry_create(const char *name)
free(entry);
return (NULL);
}
+ entry->remoteMountPoint = NULL;
entry->index = map->hrIndex;
map->entry = entry;
@@ -352,6 +353,9 @@ fs_tbl_process_statfs_entry(const struct statfs *fs_p, int32_t storage_idx)
(entry = fs_entry_create(fs_p->f_mntonname)) != NULL) {
entry->flags |= HR_FS_FOUND;
+ free(entry->remoteMountPoint);
+ entry->remoteMountPoint = NULL;
+
if (!(fs_p->f_flags & MNT_LOCAL)) {
/* this is a remote mount */
entry->remoteMountPoint = strdup(fs_p->f_mntfromname);
diff --git a/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c b/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c
index f01218bee5f8..8221bbac812f 100644
--- a/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c
+++ b/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c
@@ -1355,7 +1355,7 @@ pfa_table_addrs(u_int sidx, struct pfr_table *pt)
struct pfr_table tbl = { 0 };
struct pfr_astats *t = NULL;
struct pfa_entry *e;
- int i, numaddrs = 1, outnum;
+ int error, i, numaddrs = 1, outnum;
if (pt == NULL)
return (-1);
@@ -1373,9 +1373,9 @@ pfa_table_addrs(u_int sidx, struct pfr_table *pt)
}
outnum = numaddrs;
- if (pfctl_get_astats(pfh, &tbl, t, &outnum, 0) != 0) {
+ if ((error = pfctl_get_astats(pfh, &tbl, t, &outnum, 0)) != 0) {
syslog(LOG_ERR, "pfa_table_addrs(): ioctl() on %s: %s",
- pt->pfrt_name, strerror(errno));
+ pt->pfrt_name, strerror(error));
numaddrs = -1;
break;
}
diff --git a/usr.sbin/certctl/certctl.8 b/usr.sbin/certctl/certctl.8
index 2d38ce020c04..7df89e4d35d0 100644
--- a/usr.sbin/certctl/certctl.8
+++ b/usr.sbin/certctl/certctl.8
@@ -24,7 +24,7 @@
.\" IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
-.Dd December 3, 2025
+.Dd April 24, 2026
.Dt CERTCTL 8
.Os
.Sh NAME
@@ -113,8 +113,19 @@ In addition, a bundle containing the trusted certificates is placed in
.Ev BUNDLE .
.It Ic untrust
Add the specified file to the untrusted list.
+Note that the next
+.Ic rehash
+will remove it unless a copy of it is also placed somewhere in a
+directory included in
+.Ev UNTRUSTPATH .
.It Ic trust
-Remove the specified file from the untrusted list.
+Add the specified file to the trusted list, unless it is already
+untrusted.
+Note that the next
+.Ic rehash
+will remove it unless a copy of it is also placed somewhere in a
+directory included in
+.Ev TRUSTPATH .
.El
.Sh ENVIRONMENT
.Bl -tag -width UNTRUSTDESTDIR
diff --git a/usr.sbin/certctl/certctl.c b/usr.sbin/certctl/certctl.c
index a53ed7b2b4b2..5c2b83b89d57 100644
--- a/usr.sbin/certctl/certctl.c
+++ b/usr.sbin/certctl/certctl.c
@@ -523,6 +523,8 @@ write_certs(const char *dir, struct cert_tree *tree)
tmppath = xasprintf(".%s", path);
fd = openat(d, tmppath,
O_CREAT | O_WRONLY | O_EXCL, mode);
+ if (!unprivileged && fd >= 0)
+ (void)fchmod(fd, mode);
}
}
/* write the certificate */
@@ -594,6 +596,8 @@ write_bundle(const char *dir, const char *file, struct cert_tree *tree)
} else {
tmpfile = xasprintf(".%s", file);
fd = openat(d, tmpfile, O_WRONLY | O_CREAT | O_EXCL, mode);
+ if (!unprivileged && fd >= 0)
+ (void)fchmod(fd, mode);
}
if (fd < 0 || (f = fdopen(fd, "w")) == NULL) {
if (tmpfile != NULL && fd >= 0) {
@@ -636,23 +640,18 @@ write_bundle(const char *dir, const char *file, struct cert_tree *tree)
* Returns the number of certificates loaded.
*/
static unsigned int
-load_trusted(bool all, struct cert_tree *exclude)
+load_trusted(void)
{
unsigned int i, n;
int ret;
/* load external trusted certs */
- for (i = n = 0; all && trusted_paths[i] != NULL; i++) {
- ret = read_certs(trusted_paths[i], &trusted, exclude);
+ for (i = n = 0; trusted_paths[i] != NULL; i++) {
+ ret = read_certs(trusted_paths[i], &trusted, &untrusted);
if (ret > 0)
n += ret;
}
- /* load installed trusted certs */
- ret = read_certs(trusted_dest, &trusted, exclude);
- if (ret > 0)
- n += ret;
-
info("%d trusted certificates found", n);
return (n);
}
@@ -663,24 +662,19 @@ load_trusted(bool all, struct cert_tree *exclude)
* Returns the number of certificates loaded.
*/
static unsigned int
-load_untrusted(bool all)
+load_untrusted(void)
{
char *path;
unsigned int i, n;
int ret;
/* load external untrusted certs */
- for (i = n = 0; all && untrusted_paths[i] != NULL; i++) {
+ for (i = n = 0; untrusted_paths[i] != NULL; i++) {
ret = read_certs(untrusted_paths[i], &untrusted, NULL);
if (ret > 0)
n += ret;
}
- /* load installed untrusted certs */
- ret = read_certs(untrusted_dest, &untrusted, NULL);
- if (ret > 0)
- n += ret;
-
/* load legacy untrusted certs */
path = expand_path(LEGACY_PATH);
ret = read_certs(path, &untrusted, NULL);
@@ -796,8 +790,8 @@ certctl_list(int argc, char **argv __unused)
{
if (argc > 1)
usage();
- /* load trusted certificates */
- load_trusted(false, NULL);
+ /* load installed trusted certificates */
+ read_certs(trusted_dest, &trusted, NULL);
/* list them */
list_certs(&trusted);
free_certs(&trusted);
@@ -814,8 +808,8 @@ certctl_untrusted(int argc, char **argv __unused)
{
if (argc > 1)
usage();
- /* load untrusted certificates */
- load_untrusted(false);
+ /* load installed untrusted certificates */
+ read_certs(untrusted_dest, &untrusted, NULL);
/* list them */
list_certs(&untrusted);
free_certs(&untrusted);
@@ -842,10 +836,10 @@ certctl_rehash(int argc, char **argv __unused)
}
/* load untrusted certs first */
- load_untrusted(true);
+ load_untrusted();
/* load trusted certs, excluding any that are already untrusted */
- load_trusted(true, &untrusted);
+ load_trusted();
/* save everything */
ret = save_all();
@@ -859,7 +853,8 @@ certctl_rehash(int argc, char **argv __unused)
}
/*
- * Manually add one or more certificates to the list of trusted certificates.
+ * Manually add one or more certificates to the list of trusted
+ * certificates.
*
* Returns 0 on success and -1 on failure.
*/
@@ -875,10 +870,10 @@ certctl_trust(int argc, char **argv)
usage();
/* load untrusted certs first */
- load_untrusted(true);
+ load_untrusted();
/* load trusted certs, excluding any that are already untrusted */
- load_trusted(true, &untrusted);
+ load_trusted();
/* now load the additional trusted certificates */
n = 0;
@@ -891,9 +886,9 @@ certctl_trust(int argc, char **argv)
warnx("no new trusted certificates found");
free_certs(&untrusted);
free_certs(&trusted);
- free_certs(&extra);
return (0);
}
+ warnx("%u new trusted certificate%s found", n, n > 1 ? "s" : "");
/*
* For each new trusted cert, move it from the extra list to the
@@ -906,10 +901,16 @@ certctl_trust(int argc, char **argv)
RB_INSERT(cert_tree, &trusted, cert);
if ((other = RB_FIND(cert_tree, &untrusted, cert)) != NULL) {
warnx("%s was previously untrusted", cert->name);
+ warnx("source of untrust: %s", other->path);
RB_REMOVE(cert_tree, &untrusted, other);
free_cert(other);
}
}
+ warnx("This operation is not persistent. To persistently add");
+ warnx("trusted certificates to the system store, copy them to");
+ warnx("one of these directories, then run `certctl rehash`:");
+ for (i = 0; trusted_paths[i] != NULL; i++)
+ warnx(" %s", trusted_paths[i]);
/* save everything */
ret = save_all();
@@ -929,6 +930,8 @@ certctl_trust(int argc, char **argv)
static int
certctl_untrust(int argc, char **argv)
{
+ struct cert_tree extra = RB_INITIALIZER(&extra);
+ struct cert *cert, *other, *tmp;
unsigned int n;
int i, ret;
@@ -936,23 +939,47 @@ certctl_untrust(int argc, char **argv)
usage();
/* load untrusted certs first */
- load_untrusted(true);
+ load_untrusted();
+
+ /* load trusted certs, excluding any that are already untrusted */
+ load_trusted();
/* now load the additional untrusted certificates */
n = 0;
for (i = 1; i < argc; i++) {
- ret = read_cert(argv[i], &untrusted, NULL);
+ ret = read_cert(argv[i], &extra, NULL);
if (ret > 0)
n += ret;
}
if (n == 0) {
warnx("no new untrusted certificates found");
free_certs(&untrusted);
+ free_certs(&trusted);
return (0);
}
+ warnx("%u new untrusted certificate%s found", n, n > 1 ? "s" : "");
- /* load trusted certs, excluding any that are already untrusted */
- load_trusted(true, &untrusted);
+ /*
+ * For each new untrusted cert, move it from the extra list to the
+ * untrusted list, then check if a matching certificate exists on
+ * the trusted list. If that is the case, warn the user, then
+ * remove the matching certificate from the trusted list.
+ */
+ RB_FOREACH_SAFE(cert, cert_tree, &extra, tmp) {
+ RB_REMOVE(cert_tree, &extra, cert);
+ RB_INSERT(cert_tree, &untrusted, cert);
+ if ((other = RB_FIND(cert_tree, &trusted, cert)) != NULL) {
+ warnx("%s was previously trusted", cert->name);
+ warnx("source of trust: %s", other->path);
+ RB_REMOVE(cert_tree, &trusted, other);
+ free_cert(other);
+ }
+ }
+ warnx("This operation is not persistent. To persistently add");
+ warnx("untrusted certificates to the system store, copy them to");
+ warnx("one of these directories, then run `certctl rehash`:");
+ for (i = 0; untrusted_paths[i] != NULL; i++)
+ warnx(" %s", untrusted_paths[i]);
/* save everything */
ret = save_all();
@@ -1070,6 +1097,7 @@ int
main(int argc, char *argv[])
{
const char *command;
+ unsigned int i;
int opt;
while ((opt = getopt(argc, argv, "BcD:d:g:lL:M:no:Uv")) != -1)
@@ -1132,8 +1160,8 @@ main(int argc, char *argv[])
set_defaults();
- for (unsigned i = 0; commands[i].name != NULL; i++)
+ for (i = 0; commands[i].name != NULL; i++)
if (strcmp(command, commands[i].name) == 0)
- exit(!!commands[i].func(argc, argv));
+ exit(commands[i].func(argc, argv) == 0 ? 0 : 1);
usage();
}
diff --git a/usr.sbin/certctl/tests/certctl_test.sh b/usr.sbin/certctl/tests/certctl_test.sh
index 74749db0b3f5..133d65854c42 100644
--- a/usr.sbin/certctl/tests/certctl_test.sh
+++ b/usr.sbin/certctl/tests/certctl_test.sh
@@ -271,7 +271,8 @@ untrust_body()
crtfile=usr/share/certs/trusted/${crtname}.crt
check_trusted "${crtname}"
check_in_bundle ${crtfile}
- atf_check certctl untrust "${crtfile}"
+ atf_check -e match:"1 new untrusted" \
+ certctl untrust "${crtfile}"
check_untrusted "${crtname}"
check_not_in_bundle ${crtfile}
}
diff --git a/usr.sbin/chkgrp/chkgrp.8 b/usr.sbin/chkgrp/chkgrp.8
index 3837e5fe6803..95dc272fb2db 100644
--- a/usr.sbin/chkgrp/chkgrp.8
+++ b/usr.sbin/chkgrp/chkgrp.8
@@ -24,7 +24,7 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
-.Dd May 26, 2005
+.Dd April 19, 2026
.Dt CHKGRP 8
.Os
.Sh NAME
@@ -73,7 +73,8 @@ will print an error message containing the name of the file being
scanned and the line number on which the error was found.
.Sh SEE ALSO
.Xr getgrent 3 ,
-.Xr group 5
+.Xr group 5 ,
+.Xr groups 7
.Sh HISTORY
The
.Nm
diff --git a/usr.sbin/chown/chgrp.1 b/usr.sbin/chown/chgrp.1
index f05c81e4bae7..95ca2b3bfd27 100644
--- a/usr.sbin/chown/chgrp.1
+++ b/usr.sbin/chown/chgrp.1
@@ -28,7 +28,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd January 7, 2017
+.Dd April 19, 2026
.Dt CHGRP 1
.Os
.Sh NAME
@@ -148,6 +148,7 @@ options are non-standard and their use in scripts is not recommended.
.Xr fts 3 ,
.Xr group 5 ,
.Xr passwd 5 ,
+.Xr groups 7 ,
.Xr symlink 7 ,
.Xr chown 8
.Sh STANDARDS
diff --git a/usr.sbin/chown/chown.8 b/usr.sbin/chown/chown.8
index 9f4cfafb6852..6c62f3267882 100644
--- a/usr.sbin/chown/chown.8
+++ b/usr.sbin/chown/chown.8
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd August 24, 2022
+.Dd April 19, 2026
.Dt CHOWN 8
.Os
.Sh NAME
@@ -166,6 +166,7 @@ options are non-standard and their use in scripts is not recommended.
.Xr find 1 ,
.Xr chown 2 ,
.Xr fts 3 ,
+.Xr groups 7 ,
.Xr symlink 7
.Sh STANDARDS
The
diff --git a/usr.sbin/crashinfo/crashinfo.sh b/usr.sbin/crashinfo/crashinfo.sh
index 68115f09f9d4..87e5565b98e3 100755
--- a/usr.sbin/crashinfo/crashinfo.sh
+++ b/usr.sbin/crashinfo/crashinfo.sh
@@ -149,11 +149,13 @@ fi
VMCORE=$CRASHDIR/vmcore.$DUMPNR
INFO=$CRASHDIR/info.$DUMPNR
FILE=$CRASHDIR/core.txt.$DUMPNR
+LINK=$CRASHDIR/core.txt.last
HOSTNAME=`hostname`
if $BATCH; then
echo "Writing crash summary to $FILE."
exec > $FILE 2>&1
+ ln -sf $FILE $LINK
fi
GDB=/usr/local/bin/gdb
@@ -204,6 +206,7 @@ machine=$(gdb_command $KERNEL 'printf "%s", machine')
if ! $BATCH; then
echo "Writing crash summary to $FILE."
exec > $FILE 2>&1
+ ln -sf $FILE $LINK
fi
echo "$HOSTNAME dumped core - see $VMCORE"
diff --git a/usr.sbin/cron/cron/cron.8 b/usr.sbin/cron/cron/cron.8
index 23a295393df5..f1a6a30d4cb5 100644
--- a/usr.sbin/cron/cron/cron.8
+++ b/usr.sbin/cron/cron/cron.8
@@ -19,7 +19,7 @@
.\"
.\" $Id: cron.8,v 1.2 1998/08/14 00:32:36 vixie Exp $
.\"
-.Dd January 20, 2026
+.Dd April 29, 2026
.Dt CRON 8
.Os
.Sh NAME
@@ -227,7 +227,14 @@ configuration file for
.It Pa /usr/local/etc/cron.d
Directory for third-party package provided crontab files.
.It Pa /var/cron/tabs
-Directory for personal crontab files
+Directory for personal crontab files.
+Internally the daemon constructs the relative path
+.Pa tabs/ Ns Ar filename ,
+which must fit within
+.Dv MAXNAMLEN
+bytes; in practice this allows filenames up to 250 bytes.
+Longer entries are skipped and a diagnostic is logged via
+.Xr syslog 3 .
.El
.Sh SEE ALSO
.Xr crontab 1 ,
diff --git a/usr.sbin/cron/cron/database.c b/usr.sbin/cron/cron/database.c
index 35e5fad3524d..234b5ef7fdd6 100644
--- a/usr.sbin/cron/cron/database.c
+++ b/usr.sbin/cron/cron/database.c
@@ -166,8 +166,10 @@ load_database(cron_db *old_db)
fname[sizeof(fname)-1] = '\0';
if (snprintf(tabname, sizeof tabname, CRON_TAB(fname))
- >= sizeof(tabname))
- continue; /* XXX log? */
+ >= (int)sizeof(tabname)) {
+ log_it("CRON", getpid(), "TABNAME TOO LONG", fname);
+ continue;
+ }
process_crontab(fname, fname, tabname,
&statbuf, &new_db, old_db);
diff --git a/usr.sbin/crunch/examples/really-big.conf b/usr.sbin/crunch/examples/really-big.conf
index 50f026e3ed6a..b299a97a2ac0 100644
--- a/usr.sbin/crunch/examples/really-big.conf
+++ b/usr.sbin/crunch/examples/really-big.conf
@@ -49,7 +49,7 @@ progs renice rev rlogin rpcgen rpcinfo rsh rup ruptime rusers rwall rwho
progs script sed showmount size soelim split strings strip su tail talk
progs tcopy tee telnet tftp time tip tn3270 touch tput tr true tset tsort
progs tty ul uname unexpand unifdef uniq units unvis users uudecode uuencode
-progs vacation vgrind vi vis vmstat w wall wc what whatis whereis who
+progs vacation vi vis vmstat w wall wc what whatis whereis who
progs whois window write xargs xinstall xstr yacc yes ypcat ypmatch ypwhich
# shell scripts: lorder mkdep which
diff --git a/usr.sbin/ctladm/tests/port.sh b/usr.sbin/ctladm/tests/port.sh
index d966529a85ae..1103a1d7bafb 100644
--- a/usr.sbin/ctladm/tests/port.sh
+++ b/usr.sbin/ctladm/tests/port.sh
@@ -118,7 +118,7 @@ create_iscsi_body()
{
skip_if_ctld
- TARGET=iqn.2018-10.myhost.create_iscsi
+ TARGET=iqn.2018-10.myhost.create-iscsi
atf_check -o save:port-create.txt ctladm port -c -d "iscsi" -O cfiscsi_portal_group_tag=$PGTAG -O cfiscsi_target="$TARGET"
echo "target: $TARGET" >> port-create.txt
atf_check egrep -q "Port created successfully" port-create.txt
@@ -146,7 +146,7 @@ create_iscsi_alias_body()
{
skip_if_ctld
- TARGET=iqn.2018-10.myhost.create_iscsi_alias
+ TARGET=iqn.2018-10.myhost.create-iscsi-alias
ALIAS="foobar"
atf_check -o save:port-create.txt ctladm port -c -d "iscsi" -O cfiscsi_portal_group_tag=$PGTAG -O cfiscsi_target="$TARGET" -O cfiscsi_target_alias="$ALIAS"
echo "target: $TARGET" >> port-create.txt
@@ -173,7 +173,7 @@ create_iscsi_without_required_args_body()
{
skip_if_ctld
- TARGET=iqn.2018-10.myhost.create_iscsi
+ TARGET=iqn.2018-10.myhost.create-iscsi
atf_check -s exit:1 -e match:"Missing required argument: cfiscsi_target" ctladm port -c -d "iscsi" -O cfiscsi_portal_group_tag=$PGTAG
atf_check -s exit:1 -e match:"Missing required argument: cfiscsi_portal_group_tag" ctladm port -c -d "iscsi" -O cfiscsi_target=$TARGET
}
@@ -288,7 +288,7 @@ remove_iscsi_body()
{
skip_if_ctld
- TARGET=iqn.2018-10.myhost.remove_iscsi
+ TARGET=iqn.2018-10.myhost.remove-iscsi
atf_check -o save:port-create.txt ctladm port -c -d "iscsi" -O cfiscsi_portal_group_tag=$PGTAG -O cfiscsi_target="$TARGET"
portnum=`awk '/port:/ {print $2}' port-create.txt`
atf_check -o save:portlist.txt ctladm portlist -qf iscsi
@@ -314,7 +314,7 @@ remove_iscsi_without_required_args_body()
{
skip_if_ctld
- TARGET=iqn.2018-10.myhost.remove_iscsi_without_required_args
+ TARGET=iqn.2018-10.myhost.remove-iscsi-without-required-args
atf_check -o save:port-create.txt ctladm port -c -d "iscsi" -O cfiscsi_portal_group_tag=$PGTAG -O cfiscsi_target="$TARGET"
echo "target: $TARGET" >> port-create.txt
atf_check -s exit:1 -e match:"Missing required argument: cfiscsi_portal_group_tag" ctladm port -r -d iscsi -O cfiscsi_target="$TARGET"
diff --git a/usr.sbin/ctld/conf.cc b/usr.sbin/ctld/conf.cc
index 56a149a58a25..a2c00525826b 100644
--- a/usr.sbin/ctld/conf.cc
+++ b/usr.sbin/ctld/conf.cc
@@ -404,9 +404,9 @@ target_set_auth_type(const char *type)
}
bool
-target_set_physical_port(const char *pport)
+target_add_physical_port(const char *pport)
{
- return (target->set_physical_port(pport));
+ return (target->add_physical_port(pport));
}
bool
diff --git a/usr.sbin/ctld/conf.h b/usr.sbin/ctld/conf.h
index 642c8f234d30..7eaee4f9871c 100644
--- a/usr.sbin/ctld/conf.h
+++ b/usr.sbin/ctld/conf.h
@@ -83,11 +83,11 @@ bool target_add_chap_mutual(const char *user, const char *secret,
bool target_add_initiator_name(const char *name);
bool target_add_initiator_portal(const char *addr);
bool target_add_lun(u_int id, const char *name);
+bool target_add_physical_port(const char *pport);
bool target_add_portal_group(const char *pg_name, const char *ag_name);
bool target_set_alias(const char *alias);
bool target_set_auth_group(const char *name);
bool target_set_auth_type(const char *type);
-bool target_set_physical_port(const char *pport);
bool target_set_redirection(const char *addr);
bool target_start_lun(u_int id);
diff --git a/usr.sbin/ctld/ctld.cc b/usr.sbin/ctld/ctld.cc
index 331c029e282e..6c027e513ee7 100644
--- a/usr.sbin/ctld/ctld.cc
+++ b/usr.sbin/ctld/ctld.cc
@@ -53,12 +53,15 @@
#include <cam/scsi/scsi_all.h>
#include <algorithm>
+#include <charconv>
#include <libutil++.hh>
#include "conf.h"
#include "ctld.hh"
#include "isns.hh"
+static freebsd::pidfile pidfile;
+
bool proxy_mode = false;
static volatile bool sighup_received = false;
@@ -125,17 +128,14 @@ conf::set_pidfile_path(std::string_view path)
return (true);
}
-void
-conf::open_pidfile()
+static void
+open_pidfile(const char *path)
{
- const char *path;
pid_t otherpid;
- assert(!conf_pidfile_path.empty());
- path = conf_pidfile_path.c_str();
log_debugx("opening pidfile %s", path);
- conf_pidfile = pidfile_open(path, 0600, &otherpid);
- if (!conf_pidfile) {
+ pidfile = pidfile_open(path, 0600, &otherpid);
+ if (!pidfile) {
if (errno == EEXIST)
log_errx(1, "daemon already running, pid: %jd.",
(intmax_t)otherpid);
@@ -143,18 +143,6 @@ conf::open_pidfile()
}
}
-void
-conf::write_pidfile()
-{
- conf_pidfile.write();
-}
-
-void
-conf::close_pidfile()
-{
- conf_pidfile.close();
-}
-
#ifdef ICL_KERNEL_PROXY
int
conf::add_proxy_portal(portal *portal)
@@ -591,9 +579,18 @@ conf::find_transport_group(std::string_view name)
return (it->second.get());
}
+/*
+ * Foreign portal groups (which only redirect to other targets), and portal
+ * groups without any active portals are considered dummies and ports belonging
+ * to such groups are ignored. However, portal groups that exist in the kernel
+ * prior to ctld starting will contain real ports but no portals, so these are
+ * never considered dummies.
+ */
bool
portal_group::is_dummy() const
{
+ if (pg_kernel)
+ return (false);
if (pg_foreign)
return (true);
if (pg_portals.empty())
@@ -710,6 +707,12 @@ portal_group::set_foreign()
pg_foreign = true;
}
+void
+portal_group::set_kernel()
+{
+ pg_kernel = true;
+}
+
bool
portal_group::set_offload(const char *offload)
{
@@ -1066,25 +1069,13 @@ bool
kports::add_port(std::string &name, uint32_t ctl_port)
{
const auto &pair = pports.try_emplace(name, name, ctl_port);
- if (!pair.second) {
- log_warnx("duplicate kernel port \"%s\" (%u)", name.c_str(),
- ctl_port);
- return (false);
- }
-
- return (true);
-}
-
-bool
-kports::has_port(std::string_view name)
-{
- return (pports.count(std::string(name)) > 0);
+ return (pair.second);
}
struct pport *
-kports::find_port(std::string_view name)
+kports::find_port(const std::string &name)
{
- auto it = pports.find(std::string(name));
+ auto it = pports.find(name);
if (it == pports.end())
return (nullptr);
return (&it->second);
@@ -1173,21 +1164,12 @@ conf::add_port(struct target *target, struct pport *pp)
return (false);
}
- pp->link();
return (true);
}
bool
-conf::add_port(struct kports &kports, struct target *target, int pp, int vp)
+conf::add_port(struct target *target, const std::string &pname, int pp, int vp)
{
- struct pport *pport;
-
- std::string pname = freebsd::stringf("ioctl/%d/%d", pp, vp);
-
- pport = kports.find_port(pname);
- if (pport != NULL)
- return (add_port(target, pport));
-
std::string name = pname + "-" + target->name();
const auto &pair = conf_ports.try_emplace(name,
std::make_unique<ioctl_port>(target, pp, vp));
@@ -1383,14 +1365,67 @@ target::set_auth_type(const char *type)
}
bool
-target::set_physical_port(std::string_view pport)
+target::add_physical_port(std::string_view pport)
{
- if (!t_pport.empty()) {
- log_warnx("cannot set multiple physical ports for target "
- "\"%s\"", name());
+ /* Normalize port names. */
+ std::string pname;
+ size_t pos = pport.find('/');
+ if (pos == 0) {
+ log_warnx("invalid physical port \"%s\" for target "
+ "\"%s\"", std::string(pport).c_str(), name());
return (false);
}
- t_pport = pport;
+
+ if (pos != pport.npos) {
+ const char *pport_end = pport.data() + pport.size();
+ int pp, vp;
+
+ auto parse_int = [](const char *start, const char *end) -> int {
+ int value;
+
+ if (start == end)
+ return -1;
+
+ auto [ptr, ec] = std::from_chars(start, end, value);
+ if (ec != std::errc() || ptr != end)
+ return -1;
+ return value;
+ };
+
+ const char *ppstart = pport.data() + pos + 1;
+ size_t ppend = pport.find('/', pos + 1);
+ if (ppend == pport.npos) {
+ pp = parse_int(ppstart, pport_end);
+ vp = 0;
+ } else {
+ const char *vpstart = pport.data() + ppend + 1;
+ pp = parse_int(ppstart, pport.data() + ppend);
+ vp = parse_int(vpstart, pport_end);
+ }
+
+ if (pp == -1 || vp == -1) {
+ log_warnx("invalid physical port \"%s\" for target "
+ "\"%s\"", std::string(pport).c_str(), name());
+ return (false);
+ }
+
+ pname = pport.substr(0, pos);
+ if (pp != 0 || vp != 0) {
+ pname += "/" + std::to_string(pp);
+ if (vp != 0)
+ pname += "/" + std::to_string(vp);
+ }
+ pport = pname;
+ }
+
+ for (const auto &s : t_pports) {
+ if (s == pport) {
+ log_warnx("duplicate physical port \"%s\" for target "
+ "\"%s\"", s.c_str(), name());
+ return (false);
+ }
+ }
+ t_pports.emplace_back(pport);
return (true);
}
@@ -1459,7 +1494,7 @@ target::verify()
t_auth_group = t_conf->find_auth_group("default");
assert(t_auth_group != nullptr);
}
- if (t_ports.empty()) {
+ if (t_ports.empty() && t_pports.empty()) {
struct portal_group *pg = default_portal_group();
assert(pg != NULL);
t_conf->add_port(this, pg, nullptr);
@@ -1969,28 +2004,15 @@ conf::apply(struct conf *oldconf)
log_init(conf_debug);
}
- /*
- * On startup, oldconf created via conf_new_from_kernel will
- * not contain a valid pidfile_path, and the current
- * conf_pidfile will already own the pidfile. On shutdown,
- * the temporary newconf will not contain a valid
- * pidfile_path, and the pidfile will be cleaned up when the
- * oldconf is deleted.
- */
- if (!oldconf->conf_pidfile_path.empty() &&
- !conf_pidfile_path.empty()) {
- if (oldconf->conf_pidfile_path != conf_pidfile_path) {
- /* pidfile has changed. rename it */
- log_debugx("moving pidfile to %s",
+ /* Rename the pidfile if the pathname changes. */
+ if (oldconf->conf_pidfile_path != conf_pidfile_path) {
+ log_debugx("moving pidfile to %s", conf_pidfile_path.c_str());
+ if (rename(oldconf->conf_pidfile_path.c_str(),
+ conf_pidfile_path.c_str()) != 0) {
+ log_err(1, "renaming pidfile %s -> %s",
+ oldconf->conf_pidfile_path.c_str(),
conf_pidfile_path.c_str());
- if (rename(oldconf->conf_pidfile_path.c_str(),
- conf_pidfile_path.c_str()) != 0) {
- log_err(1, "renaming pidfile %s -> %s",
- oldconf->conf_pidfile_path.c_str(),
- conf_pidfile_path.c_str());
- }
}
- conf_pidfile = std::move(oldconf->conf_pidfile);
}
/*
@@ -2209,6 +2231,41 @@ conf::apply(struct conf *oldconf)
return (cumulated_error);
}
+void
+conf::shutdown()
+{
+ /* Deregister from iSNS servers. */
+ for (auto &kv : conf_isns)
+ isns_deregister_targets(&kv.second);
+
+ /* Remove all ports. */
+ for (const auto &kv : conf_ports) {
+ const std::string &name = kv.first;
+ port *port = kv.second.get();
+
+ if (port->is_dummy())
+ continue;
+ log_debugx("removing port \"%s\"", name.c_str());
+ if (!port->kernel_remove())
+ log_warnx("failed to remove port %s", name.c_str());
+ }
+
+ /* Remove all LUNs. */
+ for (const auto &kv : conf_luns) {
+ struct lun *lun = kv.second.get();
+
+ if (!lun->kernel_remove())
+ log_warnx("failed to remove lun \"%s\", CTL lun %d",
+ lun->name(), lun->ctl_lun());
+ }
+
+ /* Close sockets on all portal groups. */
+ for (auto &kv : conf_portal_groups)
+ kv.second->close_sockets();
+ for (auto &kv : conf_transport_groups)
+ kv.second->close_sockets();
+}
+
bool
timed_out(void)
{
@@ -2293,18 +2350,17 @@ start_timer(int timeout, bool fatal)
log_err(1, "setitimer");
}
-static int
+static void
wait_for_children(bool block)
{
pid_t pid;
int status;
- int num = 0;
- for (;;) {
- /*
- * If "block" is true, wait for at least one process.
- */
- if (block && num == 0)
+ /*
+ * If "block" is true, wait for at least one process.
+ */
+ while (nchildren > 0) {
+ if (block)
pid = wait4(-1, &status, 0, NULL);
else
pid = wait4(-1, &status, WNOHANG, NULL);
@@ -2319,10 +2375,10 @@ wait_for_children(bool block)
} else {
log_debugx("child process %d terminated gracefully", pid);
}
- num++;
- }
+ nchildren--;
- return (num);
+ block = false;
+ }
}
static void
@@ -2341,15 +2397,13 @@ handle_connection(struct portal *portal, freebsd::fd_up fd,
if (dont_fork) {
log_debugx("incoming connection; not forking due to -d flag");
} else {
- nchildren -= wait_for_children(false);
- assert(nchildren >= 0);
+ wait_for_children(false);
while (conf->maxproc() > 0 && nchildren >= conf->maxproc()) {
log_debugx("maxproc limit of %d child processes hit; "
"waiting for child process to exit",
conf->maxproc());
- nchildren -= wait_for_children(true);
- assert(nchildren >= 0);
+ wait_for_children(true);
}
log_debugx("incoming connection; forking child process #%d",
nchildren);
@@ -2359,7 +2413,7 @@ handle_connection(struct portal *portal, freebsd::fd_up fd,
log_err(1, "fork");
if (pid > 0)
return;
- conf->close_pidfile();
+ pidfile.close();
}
error = getnameinfo(client_sa, client_sa->sa_len,
@@ -2418,7 +2472,7 @@ main_loop(bool dont_fork)
error = kevent(kqfd, NULL, 0, &kev, 1, NULL);
if (error == -1) {
if (errno == EINTR)
- continue;
+ return;
log_err(1, "kevent");
}
@@ -2613,41 +2667,59 @@ conf_new_from_file(const char *path, bool ucl)
bool
conf::add_pports(struct kports &kports)
{
+ std::unordered_map<struct pport *, struct target *> linked_ports;
struct pport *pp;
int ret, i_pp, i_vp;
for (auto &kv : conf_targets) {
struct target *targ = kv.second.get();
- if (!targ->has_pport())
- continue;
+ for (const auto &pport : targ->pports()) {
+ /*
+ * If this port is already present in the
+ * kernel, reuse the existing port.
+ */
+ pp = kports.find_port(pport);
+ if (pp != nullptr) {
+ const auto &pair = linked_ports.try_emplace(pp,
+ targ);
+ if (!pair.second) {
+ log_warnx("can't link port \"%s\" to "
+ "%s, port already linked to %s",
+ pport.c_str(), targ->label(),
+ pair.first->second->label());
+ return (false);
+ }
- ret = sscanf(targ->pport(), "ioctl/%d/%d", &i_pp, &i_vp);
- if (ret > 0) {
- if (!add_port(kports, targ, i_pp, i_vp)) {
- log_warnx("can't create new ioctl port "
- "for %s", targ->label());
- return (false);
+ if (!add_port(targ, pp)) {
+ log_warnx(
+ "can't link port \"%s\" to %s",
+ pport.c_str(), targ->label());
+ return (false);
+ }
+ continue;
}
- continue;
- }
+ /*
+ * If this port is an ioctl port, create a new
+ * port.
+ */
+ ret = sscanf(pport.c_str(), "ioctl/%d/%d", &i_pp,
+ &i_vp);
+ if (ret > 0) {
+ if (ret == 1)
+ i_vp = 0;
+ if (!add_port(targ, pport, i_pp, i_vp)) {
+ log_warnx("can't create new port %s "
+ "for %s", pport.c_str(),
+ targ->label());
+ return (false);
+ }
+ continue;
+ }
- pp = kports.find_port(targ->pport());
- if (pp == NULL) {
log_warnx("unknown port \"%s\" for %s",
- targ->pport(), targ->label());
- return (false);
- }
- if (pp->linked()) {
- log_warnx("can't link port \"%s\" to %s, "
- "port already linked to some target",
- targ->pport(), targ->label());
- return (false);
- }
- if (!add_port(targ, pp)) {
- log_warnx("can't link port \"%s\" to %s",
- targ->pport(), targ->label());
+ pport.c_str(), targ->label());
return (false);
}
}
@@ -2706,7 +2778,7 @@ main(int argc, char **argv)
if (test_config)
return (0);
- newconf->open_pidfile();
+ open_pidfile(newconf->pidfile_path());
register_signals();
@@ -2717,6 +2789,9 @@ main(int argc, char **argv)
newconf->set_debug(debug);
}
+ /* Reuse the pidfile path from the configuration file. */
+ oldconf->set_pidfile_path(newconf->pidfile_path());
+
if (!newconf->add_pports(kports))
log_errx(1, "Error associating physical ports; exiting");
@@ -2740,9 +2815,7 @@ main(int argc, char **argv)
oldconf.reset();
- newconf->write_pidfile();
-
- newconf->isns_schedule_update();
+ pidfile.write();
for (;;) {
main_loop(!daemonize);
@@ -2771,26 +2844,16 @@ main(int argc, char **argv)
oldconf.reset();
}
} else if (sigterm_received) {
- log_debugx("exiting on signal; "
- "reloading empty configuration");
+ log_debugx("exiting on signal");
- log_debugx("removing CTL iSCSI ports "
+ log_debugx("removing CTL iSCSI and NVMeoF ports "
"and terminating all connections");
- oldconf = std::move(newconf);
- newconf = std::make_unique<conf>();
- if (debug > 0)
- newconf->set_debug(debug);
- error = newconf->apply(oldconf.get());
- if (error != 0)
- log_warnx("failed to apply configuration");
- oldconf.reset();
-
+ newconf->shutdown();
log_warnx("exiting on signal");
return (0);
} else {
- nchildren -= wait_for_children(false);
- assert(nchildren >= 0);
+ wait_for_children(false);
if (timed_out()) {
newconf->isns_update();
}
diff --git a/usr.sbin/ctld/ctld.hh b/usr.sbin/ctld/ctld.hh
index 3bf18f6a32c0..870e1f230c8a 100644
--- a/usr.sbin/ctld/ctld.hh
+++ b/usr.sbin/ctld/ctld.hh
@@ -220,6 +220,7 @@ struct portal_group {
bool set_dscp(u_int dscp);
virtual bool set_filter(const char *str) = 0;
void set_foreign();
+ void set_kernel();
bool set_offload(const char *offload);
bool set_pcp(u_int pcp);
bool set_redirection(const char *addr);
@@ -248,6 +249,7 @@ protected:
enum discovery_filter pg_discovery_filter =
discovery_filter::UNKNOWN;
bool pg_foreign = false;
+ bool pg_kernel = false;
bool pg_assigned = false;
std::list<portal_up> pg_portals;
std::unordered_map<std::string, port *> pg_ports;
@@ -269,7 +271,7 @@ struct port {
virtual struct auth_group *auth_group() const { return nullptr; }
virtual struct portal_group *portal_group() const { return nullptr; }
- virtual bool is_dummy() const { return true; }
+ virtual bool is_dummy() const { return false; }
virtual void clear_references();
@@ -381,17 +383,16 @@ struct target {
virtual ~target() = default;
bool has_alias() const { return !t_alias.empty(); }
- bool has_pport() const { return !t_pport.empty(); }
bool has_redirection() const { return !t_redirection.empty(); }
const char *alias() const { return t_alias.c_str(); }
const char *name() const { return t_name.c_str(); }
const char *label() const { return t_label.c_str(); }
- const char *pport() const { return t_pport.c_str(); }
bool private_auth() const { return t_private_auth; }
const char *redirection() const { return t_redirection.c_str(); }
struct auth_group *auth_group() const { return t_auth_group.get(); }
const std::list<port *> &ports() const { return t_ports; }
+ const std::list<std::string> &pports() const { return t_pports; }
const struct lun *lun(int idx) const { return t_luns[idx]; }
bool add_chap(const char *user, const char *secret);
@@ -403,12 +404,12 @@ struct target {
virtual bool add_initiator_portal(const char *) { return false; }
virtual bool add_lun(u_int, const char *) { return false; }
virtual bool add_namespace(u_int, const char *) { return false; }
+ bool add_physical_port(std::string_view pport);
virtual bool add_portal_group(const char *pg_name,
const char *ag_name) = 0;
bool set_alias(std::string_view alias);
bool set_auth_group(const char *ag_name);
bool set_auth_type(const char *type);
- bool set_physical_port(std::string_view pport);
bool set_redirection(const char *addr);
virtual struct lun *start_lun(u_int) { return nullptr; }
virtual struct lun *start_namespace(u_int) { return nullptr; }
@@ -433,8 +434,8 @@ protected:
std::string t_label;
std::string t_alias;
std::string t_redirection;
- /* Name of this target's physical port, if any, i.e. "isp0" */
- std::string t_pport;
+ /* Names of this target's physical ports, e.g. "isp0" */
+ std::list<std::string> t_pports;
bool t_private_auth = false;
};
@@ -460,6 +461,7 @@ struct conf {
int maxproc() const { return conf_maxproc; }
int timeout() const { return conf_timeout; }
uint32_t genctr() const { return conf_genctr; }
+ const char *pidfile_path() const { return conf_pidfile_path.c_str(); }
bool default_auth_group_defined() const
{ return conf_default_ag_defined; }
@@ -485,7 +487,7 @@ struct conf {
bool add_port(struct target *target, struct portal_group *pg,
uint32_t ctl_port);
bool add_port(struct target *target, struct pport *pp);
- bool add_port(struct kports &kports, struct target *target, int pp,
+ bool add_port(struct target *target, const std::string &pname, int pp,
int vp);
bool add_pports(struct kports &kports);
@@ -505,19 +507,13 @@ struct conf {
bool set_pidfile_path(std::string_view path);
void set_timeout(int timeout);
- void open_pidfile();
- void write_pidfile();
- void close_pidfile();
-
bool add_isns(const char *addr);
- void isns_register_targets(struct isns *isns, struct conf *oldconf);
- void isns_deregister_targets(struct isns *isns);
- void isns_schedule_update();
void isns_update();
int apply(struct conf *oldconf);
void delete_target_luns(struct lun *lun);
bool reuse_portal_group_socket(struct portal &newp);
+ void shutdown();
bool verify();
private:
@@ -525,6 +521,9 @@ private:
struct isns_req isns_check_request(const char *hostname);
struct isns_req isns_deregister_request(const char *hostname);
void isns_check(struct isns *isns);
+ void isns_deregister_targets(struct isns *isns);
+ void isns_register_targets(struct isns *isns, struct conf *oldconf);
+ void isns_schedule_update();
std::string conf_pidfile_path;
std::unordered_map<std::string, std::unique_ptr<lun>> conf_luns;
@@ -543,8 +542,6 @@ private:
int conf_maxproc = 30;
uint32_t conf_genctr = 0;
- freebsd::pidfile conf_pidfile;
-
bool conf_default_pg_defined = false;
bool conf_default_tg_defined = false;
bool conf_default_ag_defined = false;
@@ -570,19 +567,14 @@ struct pport {
const char *name() const { return pp_name.c_str(); }
uint32_t ctl_port() const { return pp_ctl_port; }
- bool linked() const { return pp_linked; }
- void link() { pp_linked = true; }
-
private:
std::string pp_name;
uint32_t pp_ctl_port;
- bool pp_linked = false;
};
struct kports {
bool add_port(std::string &name, uint32_t ctl_port);
- bool has_port(std::string_view name);
- struct pport *find_port(std::string_view name);
+ struct pport *find_port(const std::string &name);
private:
std::unordered_map<std::string, struct pport> pports;
diff --git a/usr.sbin/ctld/kernel.cc b/usr.sbin/ctld/kernel.cc
index 6b17ce60ac69..952159ee8e59 100644
--- a/usr.sbin/ctld/kernel.cc
+++ b/usr.sbin/ctld/kernel.cc
@@ -441,19 +441,13 @@ retry_port:
return (true);
}
-void
-add_iscsi_port(struct kports &kports, struct conf *conf,
- const struct cctl_port &port, std::string &name)
+static void
+add_iscsi_port(struct conf *conf, const struct cctl_port &port,
+ std::string &name)
{
if (port.cfiscsi_target.empty()) {
- log_debugx("CTL port %u \"%s\" wasn't managed by ctld; ",
- port.port_id, name.c_str());
- if (!kports.has_port(name)) {
- if (!kports.add_port(name, port.port_id)) {
- log_warnx("kports::add_port failed");
- return;
- }
- }
+ log_debugx("CTL iSCSI port %u \"%s\" is not managed by ctld; "
+ "ignoring", port.port_id, name.c_str());
return;
}
if (port.cfiscsi_state != 1) {
@@ -483,6 +477,8 @@ add_iscsi_port(struct kports &kports, struct conf *conf,
log_warnx("Failed to add portal-group \"%s\"", pg_name);
return;
}
+
+ pg->set_kernel();
}
pg->set_tag(port.cfiscsi_portal_group_tag);
if (!conf->add_port(targ, pg, port.port_id)) {
@@ -491,13 +487,13 @@ add_iscsi_port(struct kports &kports, struct conf *conf,
}
}
-void
+static void
add_nvmf_port(struct conf *conf, const struct cctl_port &port,
std::string &name)
{
if (port.nqn.empty() || port.ctld_transport_group_name.empty()) {
- log_debugx("CTL port %u \"%s\" wasn't managed by ctld; ",
- port.port_id, name.c_str());
+ log_debugx("CTL NVMeoF port %u \"%s\" is not managed by ctld; "
+ "ignoring", port.port_id, name.c_str());
return;
}
@@ -520,6 +516,8 @@ add_nvmf_port(struct conf *conf, const struct cctl_port &port,
tg_name);
return;
}
+
+ pg->set_kernel();
}
pg->set_tag(port.portid);
if (!conf->add_port(targ, pg, port.port_id)) {
@@ -528,6 +526,17 @@ add_nvmf_port(struct conf *conf, const struct cctl_port &port,
}
}
+static void
+add_kernel_port(struct kports &kports, const struct cctl_port &port,
+ std::string &name)
+{
+ log_debugx("CTL kernel port %u \"%s\"", port.port_id, name.c_str());
+ if (!kports.add_port(name, port.port_id)) {
+ log_warnx("Ignoring duplicate kernel port \"%s\"",
+ name.c_str());
+ }
+}
+
conf_up
conf_new_from_kernel(struct kports &kports)
{
@@ -545,25 +554,24 @@ conf_new_from_kernel(struct kports &kports)
continue;
std::string name = port.port_name;
- if (port.pp != 0) {
+ if (port.pp != 0 || port.vp != 0) {
name += "/" + std::to_string(port.pp);
if (port.vp != 0)
name += "/" + std::to_string(port.vp);
}
if (port.port_frontend == "iscsi") {
- add_iscsi_port(kports, conf.get(), port, name);
+ add_iscsi_port(conf.get(), port, name);
} else if (port.port_frontend == "nvmf") {
add_nvmf_port(conf.get(), port, name);
} else {
- /* XXX: Treat all unknown ports as iSCSI? */
- add_iscsi_port(kports, conf.get(), port, name);
+ add_kernel_port(kports, port, name);
}
}
for (const auto &lun : devlist.lun_list) {
if (lun.ctld_name.empty()) {
- log_debugx("CTL lun %ju wasn't managed by ctld; "
+ log_debugx("CTL lun %ju is not managed by ctld; "
"ignoring", (uintmax_t)lun.lun_id);
continue;
}
diff --git a/usr.sbin/ctld/login.cc b/usr.sbin/ctld/login.cc
index cda11cc1f21b..fb4b60c37f54 100644
--- a/usr.sbin/ctld/login.cc
+++ b/usr.sbin/ctld/login.cc
@@ -967,12 +967,18 @@ iscsi_connection::login()
login_send_error(request, 0x02, 0x07);
log_errx(1, "received Login PDU without TargetName");
}
+ /*
+ * Normalize target_name according to RFC 3722
+ */
+ std::string t_name(target_name);
+ for (char &c : t_name)
+ c = tolower(c);
- conn_port = pg->find_port(target_name);
+ conn_port = pg->find_port(t_name);
if (conn_port == NULL) {
login_send_error(request, 0x02, 0x03);
log_errx(1, "requested target \"%s\" not found",
- target_name);
+ t_name.c_str());
}
conn_target = conn_port->target();
}
diff --git a/usr.sbin/ctld/parse.y b/usr.sbin/ctld/parse.y
index 5725c16b459a..05171d3aa159 100644
--- a/usr.sbin/ctld/parse.y
+++ b/usr.sbin/ctld/parse.y
@@ -774,7 +774,7 @@ target_port: PORT STR
{
bool ok;
- ok = target_set_physical_port($2);
+ ok = target_add_physical_port($2);
free($2);
if (!ok)
return (1);
diff --git a/usr.sbin/ctld/uclparse.cc b/usr.sbin/ctld/uclparse.cc
index 8a62636ffec6..0c6b2af8d741 100644
--- a/usr.sbin/ctld/uclparse.cc
+++ b/usr.sbin/ctld/uclparse.cc
@@ -1255,7 +1255,7 @@ uclparse_target(const char *name, const ucl::Ucl &top)
return false;
}
- if (!target_set_physical_port(obj.string_value().c_str()))
+ if (!target_add_physical_port(obj.string_value().c_str()))
return false;
}
diff --git a/usr.sbin/daemon/daemon.c b/usr.sbin/daemon/daemon.c
index 341438730a6d..9158d6404b29 100644
--- a/usr.sbin/daemon/daemon.c
+++ b/usr.sbin/daemon/daemon.c
@@ -194,6 +194,7 @@ main(int argc, char *argv[])
/*
* Supervision mode is enabled if one of the following options are used:
+ * --output-file -o
* --child-pidfile -p
* --supervisor-pidfile -P
* --restart -r / --restart-delay -R
diff --git a/usr.sbin/diskinfo/diskinfo.8 b/usr.sbin/diskinfo/diskinfo.8
index 970bafd4f8e5..aea4c123048a 100644
--- a/usr.sbin/diskinfo/diskinfo.8
+++ b/usr.sbin/diskinfo/diskinfo.8
@@ -35,7 +35,7 @@
.Nd get information about disk device
.Sh SYNOPSIS
.Nm
-.Op Fl citSvw
+.Op Fl ciStvw
.Ar disk ...
.Nm
.Op Fl l
@@ -52,9 +52,7 @@ utility prints out information about a disk device,
and optionally runs a naive performance test on the device.
.Pp
The following options are available:
-.Bl -tag -width ".Fl v"
-.It Fl v
-Print fields one per line with a descriptive comment.
+.Bl -tag -width "-c"
.It Fl c
Perform a simple measurement of the I/O read command overhead.
.It Fl i
@@ -70,16 +68,18 @@ character as a separator.
Return the physical path of the disk.
This is a string that identifies the physical path to the disk in the
storage enclosure.
-.It Fl s
-Return the disk ident, usually the serial number.
.It Fl S
Perform synchronous random write test (ZFS SLOG test),
measuring time required to write data blocks of different size and
flush disk cache.
Blocks of more then 128KB are written with multiple parallel operations.
+.It Fl s
+Return the disk ident, usually the serial number.
.It Fl t
Perform a simple and rather naive benchmark of the disks seek
and transfer performance.
+.It Fl v
+Print fields one per line with a descriptive comment.
.It Fl w
Allow disruptive write tests.
.El
diff --git a/usr.sbin/diskinfo/diskinfo.c b/usr.sbin/diskinfo/diskinfo.c
index f091d0ccfbea..d8a79d430edb 100644
--- a/usr.sbin/diskinfo/diskinfo.c
+++ b/usr.sbin/diskinfo/diskinfo.c
@@ -58,7 +58,7 @@
static void
usage(void)
{
- fprintf(stderr, "usage: diskinfo [-ciStvw] disk ...\n"
+ fprintf(stderr, "usage: diskinfo [-citSvw] disk ...\n"
" diskinfo [-l] -p disk ...\n"
" diskinfo [-l] -s disk ...\n"
);
@@ -91,7 +91,7 @@ main(int argc, char **argv)
u_int sectorsize, fwsectors, fwheads, zoned = 0, isreg;
uint32_t zone_mode;
- while ((ch = getopt(argc, argv, "cilpsStvw")) != -1) {
+ while ((ch = getopt(argc, argv, "cilpSstvw")) != -1) {
switch (ch) {
case 'c':
opt_c = 1;
@@ -107,13 +107,13 @@ main(int argc, char **argv)
case 'p':
opt_p = 1;
break;
- case 's':
- opt_s = 1;
- break;
case 'S':
opt_S = 1;
opt_v = 1;
break;
+ case 's':
+ opt_s = 1;
+ break;
case 't':
opt_t = 1;
opt_v = 1;
diff --git a/usr.sbin/edquota/edquota.c b/usr.sbin/edquota/edquota.c
index 6c209cd23716..f9780a7b0507 100644
--- a/usr.sbin/edquota/edquota.c
+++ b/usr.sbin/edquota/edquota.c
@@ -36,6 +36,7 @@
* Disk quota editor.
*/
+#include <sys/param.h>
#include <sys/file.h>
#include <sys/mount.h>
#include <sys/wait.h>
diff --git a/usr.sbin/efibootmgr/Makefile b/usr.sbin/efibootmgr/Makefile
index d976656a99c9..6f0a2a1dddbb 100644
--- a/usr.sbin/efibootmgr/Makefile
+++ b/usr.sbin/efibootmgr/Makefile
@@ -1,14 +1,13 @@
EFIBOOT=${SRCTOP}/stand/efi
EFIINCL=${SRCTOP}/stand/efi/include
-EFIVAR=${SRCTOP}/usr.sbin/efivar
-.PATH: ${EFIBOOT}/libefi ${EFIVAR}
-CFLAGS+= -I${EFIVAR} -I${EFIINCL}
+.PATH: ${EFIBOOT}/libefi
+CFLAGS+= -I${EFIINCL}
PACKAGE= efi-tools
PROG=efibootmgr
MAN= efibootmgr.8
-SRCS= efichar.c efiutil.c efibootmgr.c
+SRCS= efichar.c efibootmgr.c
LIBADD= efivar geom
diff --git a/usr.sbin/efibootmgr/efibootmgr.8 b/usr.sbin/efibootmgr/efibootmgr.8
index e5054023e40c..2d7aaf5bd4a4 100644
--- a/usr.sbin/efibootmgr/efibootmgr.8
+++ b/usr.sbin/efibootmgr/efibootmgr.8
@@ -24,7 +24,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd September 22, 2024
+.Dd April 11, 2026
.Dt EFIBOOTMGR 8
.Os
.Sh NAME
@@ -84,7 +84,7 @@ boot method to be tried once upon the next boot.
The UEFI standard defines how hosts may control what is used to
bootstrap the system.
Each method is encapsulated within a persistent UEFI variable,
-stored by the UEFI BIOS of the form
+stored by the UEFI firmware of the form
.Cm Boot Ns Em XXXX
(where XXXX are uppercase hexadecimal digits).
These variables are numbered, each describing where to load the bootstrap
@@ -200,7 +200,7 @@ variable.
Displays the UEFI device path of
.Ar unix-path .
.It Fl v -verbose
-Display the device path of boot entries in the output.
+Display extra information on boot entries: device path, attributes, category and optional data.
.El
.Sh EXAMPLES
To display the current
diff --git a/usr.sbin/efibootmgr/efibootmgr.c b/usr.sbin/efibootmgr/efibootmgr.c
index b919130d9c11..e1786d9fc1bc 100644
--- a/usr.sbin/efibootmgr/efibootmgr.c
+++ b/usr.sbin/efibootmgr/efibootmgr.c
@@ -50,16 +50,31 @@
#include <geom/geom_int.h>
#include <efivar.h>
-#include <efiutil.h>
#include <efichar.h>
#include <efivar-dp.h>
#ifndef LOAD_OPTION_ACTIVE
-#define LOAD_OPTION_ACTIVE 0x00000001
+#define LOAD_OPTION_ACTIVE 0x00000001
+#endif
+
+#ifndef LOAD_OPTION_FORCE_RECONNECT
+#define LOAD_OPTION_FORCE_RECONNECT 0x00000002
+#endif
+
+#ifndef LOAD_OPTION_HIDDEN
+#define LOAD_OPTION_HIDDEN 0x00000008
+#endif
+
+#ifndef LOAD_OPTION_CATEGORY
+#define LOAD_OPTION_CATEGORY 0x00001F00
#endif
#ifndef LOAD_OPTION_CATEGORY_BOOT
-#define LOAD_OPTION_CATEGORY_BOOT 0x00000000
+#define LOAD_OPTION_CATEGORY_BOOT 0x00000000
+#endif
+
+#ifndef LOAD_OPTION_CATEGORY_APP
+#define LOAD_OPTION_CATEGORY_APP 0x00000100
#endif
#define BAD_LENGTH ((size_t)-1)
@@ -745,14 +760,14 @@ static void
print_loadopt_str(uint8_t *data, size_t datalen)
{
char *dev, *relpath, *abspath;
- uint32_t attr;
+ uint32_t attr, categ;
uint16_t fplen;
efi_char *descr;
uint8_t *ep = data + datalen;
- uint8_t *walker = data;
+ uint8_t *walker = data, *opt;
efidp dp, edp;
char buf[1024];
- int len;
+ int len, optlen;
int rv;
int indent;
@@ -776,11 +791,13 @@ print_loadopt_str(uint8_t *data, size_t datalen)
if (walker > ep)
return;
edp = (efidp)walker;
- /*
- * Everything left is the binary option args
- * opt = walker;
- * optlen = ep - walker;
- */
+
+ /* Everything left is the binary option args */
+ opt = walker;
+ optlen = ep - walker;
+
+ printf("\n");
+ printf(" dp:");
indent = 1;
while (dp < edp) {
if (efidp_size(dp) == 0)
@@ -788,7 +805,7 @@ print_loadopt_str(uint8_t *data, size_t datalen)
efidp_format_device_path(buf, sizeof(buf), dp,
(intptr_t)(void *)edp - (intptr_t)(void *)dp);
printf("%*s%s\n", indent, "", buf);
- indent = 10 + len + 1;
+ indent = 11;
rv = efivar_device_path_to_unix_path(dp, &dev, &relpath, &abspath);
if (rv == 0) {
printf("%*s%s:%s %s\n", indent + 4, "", dev, relpath, abspath);
@@ -798,6 +815,37 @@ print_loadopt_str(uint8_t *data, size_t datalen)
}
dp = (efidp)((char *)dp + efidp_size(dp));
}
+
+ /* Optional Data */
+ if (optlen > 0) {
+ printf(" opt/x: ");
+ efi_hexdump(opt, optlen, 11);
+ printf(" opt/a: ");
+ efi_asciidump(opt, optlen, 11);
+ }
+
+ /* Attributes */
+ printf(" attr: %#x<", attr);
+ if (attr & LOAD_OPTION_ACTIVE)
+ printf("ACTIVE,");
+ if (attr & LOAD_OPTION_FORCE_RECONNECT)
+ printf("FORCE_RECONNECT,");
+ if (attr & LOAD_OPTION_HIDDEN)
+ printf("HIDDEN,");
+ printf(">\n");
+
+ /* Category */
+ categ = (attr & LOAD_OPTION_CATEGORY);
+ printf(" categ: %#x<", categ);
+ switch (categ) {
+ case LOAD_OPTION_CATEGORY_APP:
+ printf("APP");
+ break;
+ case LOAD_OPTION_CATEGORY_BOOT:
+ printf("BOOT");
+ break;
+ }
+ printf(">\n");
}
static char *
diff --git a/usr.sbin/efitable/efitable.8 b/usr.sbin/efitable/efitable.8
index 52949abcb853..f0785d3ba7cf 100644
--- a/usr.sbin/efitable/efitable.8
+++ b/usr.sbin/efitable/efitable.8
@@ -32,7 +32,7 @@
.Nd dump UEFI tables
.Sh SYNOPSIS
.Nm
-.Op Fl u Ar uuid | Fl t Ar name
+.Op Fl g Ar guid | Fl t Ar name
.Op Fl -libxo
.Sh DESCRIPTION
This program prints data from
@@ -49,6 +49,8 @@ in a selection of different human and machine readable formats.
See
.Xr xo_options 7
for details on command line arguments.
+.It Fl g Ar guid | Fl -guid Ar guid
+Specify the GUID of the table to print.
.It Fl t Ar name | Fl -table Ar name
Specify the name of the table to print.
Currently supported tables:
@@ -61,8 +63,6 @@ EFI Memory Attributes Table
.It Cm prop
EFI Properties Table
.El
-.It Fl u Ar uuid | Fl -uuid Ar uuid
-Specify the UUID of the table to print.
.El
.Sh HISTORY
The
diff --git a/usr.sbin/efitable/efitable.c b/usr.sbin/efitable/efitable.c
index a506b4dea311..b9e3d424398a 100644
--- a/usr.sbin/efitable/efitable.c
+++ b/usr.sbin/efitable/efitable.c
@@ -75,8 +75,9 @@ main(int argc, char **argv)
bool table_set = false;
bool uuid_set = false;
struct option longopts[] = {
- { "uuid", required_argument, NULL, 'u' },
+ { "guid", required_argument, NULL, 'g' },
{ "table", required_argument, NULL, 't' },
+ { "uuid", required_argument, NULL, 'u' },
{ NULL, 0, NULL, 0 }
};
diff --git a/usr.sbin/efivar/Makefile b/usr.sbin/efivar/Makefile
index dd655f6b953d..4d71ec7d4dc4 100644
--- a/usr.sbin/efivar/Makefile
+++ b/usr.sbin/efivar/Makefile
@@ -5,7 +5,7 @@ MAN= efivar.8
LIBADD= efivar geom
-SRCS= efivar.c efiutil.c
+SRCS= efivar.c
EFIBOOT=${SRCTOP}/stand/efi
CFLAGS+= -I${EFIBOOT}/include
diff --git a/usr.sbin/efivar/efiutil.c b/usr.sbin/efivar/efiutil.c
deleted file mode 100644
index fc2a309a81ef..000000000000
--- a/usr.sbin/efivar/efiutil.c
+++ /dev/null
@@ -1,190 +0,0 @@
-/*-
- * Copyright (c) 2017-2019 Netflix, Inc.
- *
- * 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 AUTHOR 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 AUTHOR 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/cdefs.h>
-#include <ctype.h>
-#include <efivar.h>
-#include <efivar-dp.h>
-#include <err.h>
-#include <errno.h>
-#include <getopt.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include "efiutil.h"
-#include "efichar.h"
-#include <efivar-dp.h>
-
-/*
- * Dump the data as ASCII data, which is a pretty
- * printed form
- */
-void
-asciidump(uint8_t *data, size_t datalen)
-{
- size_t i;
- int len;
-
- len = 0;
- for (i = 0; i < datalen; i++) {
- if (isprint(data[i])) {
- len++;
- if (len > 80) {
- len = 0;
- printf("\n");
- }
- printf("%c", data[i]);
- } else {
- len +=3;
- if (len > 80) {
- len = 0;
- printf("\n");
- }
- printf("%%%02x", data[i]);
- }
- }
- printf("\n");
-}
-
-void
-utf8dump(uint8_t *data, size_t datalen)
-{
- char *utf8 = NULL;
- efi_char *ucs2;
-
- /*
- * NUL terminate the string. Not all strings need it, but some
- * do and an extra NUL won't change what's printed.
- */
- ucs2 = malloc(datalen + sizeof(efi_char));
- memcpy(ucs2, data, datalen);
- ucs2[datalen / sizeof(efi_char)] = 0;
- ucs2_to_utf8(ucs2, &utf8);
- printf("%s\n", utf8);
- free(utf8);
- free(ucs2);
-}
-
-void
-hexdump(uint8_t *data, size_t datalen)
-{
- size_t i;
-
- for (i = 0; i < datalen; i++) {
- if (i % 16 == 0) {
- if (i != 0)
- printf("\n");
- printf("%04x: ", (int)i);
- }
- printf("%02x ", data[i]);
- }
- printf("\n");
-}
-
-void
-bindump(uint8_t *data, size_t datalen)
-{
- write(1, data, datalen);
-}
-
-#define LOAD_OPTION_ACTIVE 1
-
-#define SIZE(dp, edp) (size_t)((intptr_t)(void *)edp - (intptr_t)(void *)dp)
-
-void
-efi_print_load_option(uint8_t *data, size_t datalen, int Aflag, int bflag, int uflag)
-{
- char *dev, *relpath, *abspath;
- uint8_t *ep = data + datalen;
- uint8_t *walker = data;
- uint32_t attr;
- uint16_t fplen;
- efi_char *descr;
- efidp dp, edp;
- char *str = NULL;
- char buf[1024];
- int len;
- void *opt;
- int optlen;
- int rv;
-
- if (datalen < sizeof(attr) + sizeof(fplen) + sizeof(efi_char))
- return;
- // First 4 bytes are attribute flags
- attr = le32dec(walker);
- walker += sizeof(attr);
- // Next two bytes are length of the file paths
- fplen = le16dec(walker);
- walker += sizeof(fplen);
- // Next we have a 0 terminated UCS2 string that we know to be aligned
- descr = (efi_char *)(intptr_t)(void *)walker;
- len = ucs2len(descr); // XXX need to sanity check that len < (datalen - (ep - walker) / 2)
- walker += (len + 1) * sizeof(efi_char);
- if (walker > ep)
- return;
- // Now we have fplen bytes worth of file path stuff
- dp = (efidp)walker;
- walker += fplen;
- if (walker > ep)
- return;
- edp = (efidp)walker;
- // Everything left is the binary option args
- opt = walker;
- optlen = ep - walker;
- // We got to here, everything is good
- printf("%c ", attr & LOAD_OPTION_ACTIVE ? '*' : ' ');
- ucs2_to_utf8(descr, &str);
- printf("%s\n", str);
- free(str);
- if (fplen <= 4) {
- printf("Empty path\n");
- } else {
- while (dp < edp && SIZE(dp, edp) > sizeof(efidp_header)) {
- efidp_format_device_path(buf, sizeof(buf), dp, SIZE(dp, edp));
- rv = efivar_device_path_to_unix_path(dp, &dev, &relpath, &abspath);
- dp = (efidp)((char *)dp + efidp_size(dp));
- printf(" %s\n", buf);
- if (rv == 0) {
- printf(" %*s:%s\n", len + (int)strlen(dev), dev, relpath);
- free(dev);
- free(relpath);
- free(abspath);
- }
- }
- }
- if (optlen == 0)
- return;
- printf("Option:\n");
- if (Aflag)
- asciidump(opt, optlen);
- else if (bflag)
- bindump(opt, optlen);
- else if (uflag)
- utf8dump(opt, optlen);
- else
- hexdump(opt, optlen);
-}
diff --git a/usr.sbin/efivar/efivar.c b/usr.sbin/efivar/efivar.c
index c40ff1ea010f..6d252959e7de 100644
--- a/usr.sbin/efivar/efivar.c
+++ b/usr.sbin/efivar/efivar.c
@@ -38,7 +38,6 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#include "efiutil.h"
#include "efichar.h"
/* options descriptor */
@@ -254,15 +253,15 @@ print_var(efi_guid_t *guid, char *name)
if (load_opt_flag)
efi_print_load_option(data, datalen, Aflag, bflag, uflag);
else if (Aflag)
- asciidump(data, datalen);
+ efi_asciidump(data, datalen, 0);
else if (uflag)
- utf8dump(data, datalen);
+ efi_utf8dump(data, datalen, 0);
else if (bflag)
- bindump(data, datalen);
+ efi_bindump(data, datalen);
else if (dflag)
devpath_dump(data, datalen);
else
- hexdump(data, datalen);
+ efi_hexdump(data, datalen, 0);
} else {
printf("%s-%s", gname, name);
}
diff --git a/usr.sbin/etcupdate/etcupdate.8 b/usr.sbin/etcupdate/etcupdate.8
index bf7ddb792907..3a240ebfc1d8 100644
--- a/usr.sbin/etcupdate/etcupdate.8
+++ b/usr.sbin/etcupdate/etcupdate.8
@@ -23,7 +23,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd July 15, 2024
+.Dd June 18, 2026
.Dt ETCUPDATE 8
.Os
.Sh NAME
@@ -31,7 +31,8 @@
.Nd "manage updates to system files not updated by installworld"
.Sh SYNOPSIS
.Nm
-.Op Fl npBFN
+.Op Fl npFN
+.Op Fl B | Fl b
.Op Fl d Ar workdir
.Op Fl r | Fl s Ar source | Fl t Ar tarball
.Op Fl A Ar patterns
@@ -42,7 +43,8 @@
.Op Fl m Ar make
.Nm
.Cm build
-.Op Fl BN
+.Op Fl N
+.Op Fl B | Fl b
.Op Fl d Ar workdir
.Op Fl s Ar source
.Op Fl L Ar logfile
@@ -51,13 +53,15 @@
.Ar tarball
.Nm
.Cm diff
+.Op Fl l
.Op Fl d Ar workdir
.Op Fl D Ar destdir
.Op Fl I Ar patterns
.Op Fl L Ar logfile
.Nm
.Cm extract
-.Op Fl BN
+.Op Fl N
+.Op Fl B | Fl b
.Op Fl d Ar workdir
.Op Fl s Ar source | Fl t Ar tarball
.Op Fl D Ar destdir
@@ -387,15 +391,16 @@ variable or the
option will not be installed.
.It Fl B
Do not build generated files in a private object tree.
-Instead,
-reuse the generated files from a previously built object tree that matches
-the source tree.
-This can be useful to avoid gratuitous conflicts in
-.Xr sendmail 8
-configuration
-files when bootstrapping.
-It can also be useful for building a tarball that matches a specific
-world build.
+Instead, reuse the generated files from a previously built object tree
+that matches the source tree.
+This is the default.
+.It Fl b
+Build generated files in a private object tree before trying to
+compare anything.
+This is necessary in the uncommon case where
+.Nm
+is not run after a
+.Cm buildworld .
.It Fl D Ar destdir
Specify an alternate destination directory as the target of a merge.
This is analogous to the
@@ -468,6 +473,10 @@ shell pattern.
This option may be specified multiple times to specify multiple patterns.
Multiple space-separated patterns may also be specified in a single
option.
+.It Fl l
+In
+.Cm diff
+mode list files that are changed locally, omit printing content difference.
.It Fl L Ar logfile
Specify an alternate path for the log file.
The
@@ -690,7 +699,7 @@ before updating the source tree to start the next world upgrade cycle.
First,
generate a reference tree:
.Pp
-.Dl "etcupdate extract"
+.Dl "etcupdate extract -b"
.Pp
Second,
use the
@@ -716,7 +725,7 @@ The reference tree for
.Nm
can then be generated via:
.Pp
-.Dl "etcupdate extract -s /path/to/tree"
+.Dl "etcupdate extract -b -s /path/to/tree"
.Pp
The
.Cm diff
diff --git a/usr.sbin/etcupdate/etcupdate.sh b/usr.sbin/etcupdate/etcupdate.sh
index 8f4737136d72..4743e81a1562 100755
--- a/usr.sbin/etcupdate/etcupdate.sh
+++ b/usr.sbin/etcupdate/etcupdate.sh
@@ -200,6 +200,7 @@ build_tree()
log "Building tree at $1 with $make"
+ exec 4>&2
exec >&3 2>&1
mkdir -p $1/usr/obj
@@ -216,7 +217,16 @@ build_tree()
else
(
cd $SRCDIR || exit 1
- if ! [ -n "$nobuild" ]; then
+ if [ -n "$nobuild" ]; then
+ # Check for prior build
+ objdir=$($make -V .OBJDIR)
+ if ! [ -f "${objdir}/${BUILD_SENTINEL}" ]; then
+ cat <<EOF >&4
+Build sentinel not found in object directory, did you forget to make
+buildworld or pass -b to etcupdate?
+EOF
+ fi
+ else
export MAKEOBJDIRPREFIX=$destdir/usr/obj
if [ -n "$($make -V.ALLTARGETS:Mbuildetc)" ]; then
$make buildetc || exit 1
@@ -500,36 +510,32 @@ diffnode()
$COMPARE_EQUAL)
;;
$COMPARE_ONLYFIRST)
- echo
echo "Removed: $3"
- echo
;;
$COMPARE_ONLYSECOND)
- echo
echo "Added: $3"
- echo
;;
$COMPARE_DIFFTYPE)
first=`file_type $1/$3`
second=`file_type $2/$3`
- echo
echo "Node changed from a $first to a $second: $3"
- echo
;;
$COMPARE_DIFFLINKS)
first=`readlink $1/$file`
second=`readlink $2/$file`
- echo
echo "Link changed: $file"
rule "="
echo "-$first"
echo "+$second"
- echo
;;
$COMPARE_DIFFFILES)
- echo "Index: $3"
- rule "="
- diff -u $diffargs -L "$3 ($4)" $1/$3 -L "$3 ($5)" $2/$3
+ if [ -n "$difflistonly" ]; then
+ echo "Changed: $3"
+ else
+ echo "Index: $3"
+ rule "="
+ diff -u $diffargs -L "$3 ($4)" -L "$3 ($5)" $1/$3 $2/$3
+ fi
;;
esac
}
@@ -1784,14 +1790,21 @@ rerun=
always=
dryrun=
ignore=
-nobuild=
+nobuild=YES
preworld=
noroot=
-while getopts "d:m:nprs:t:A:BD:FI:L:M:N" option; do
+difflistonly=
+while getopts "bd:lm:nprs:t:A:BD:FI:L:M:N" option; do
case "$option" in
+ b)
+ nobuild=
+ ;;
d)
WORKDIR=$OPTARG
;;
+ l)
+ difflistonly=YES
+ ;;
m)
MAKE_CMD=$OPTARG
;;
@@ -1897,6 +1910,9 @@ EDITOR=${EDITOR:-/usr/bin/vi}
# Files that need to be updated before installworld.
PREWORLD_FILES="etc/master.passwd etc/group"
+# A file we know the build produces in the object directory.
+BUILD_SENTINEL=etc/termcap/termcap.small
+
# Handle command-specific argument processing such as complaining
# about unsupported options. Since the configuration file is always
# included, do not complain about extra command line arguments that
diff --git a/usr.sbin/freebsd-update/freebsd-update.sh b/usr.sbin/freebsd-update/freebsd-update.sh
index b23ada60e8aa..a932cdb26965 100644
--- a/usr.sbin/freebsd-update/freebsd-update.sh
+++ b/usr.sbin/freebsd-update/freebsd-update.sh
@@ -2581,7 +2581,7 @@ upgrade_merge () {
# Some files need special treatment.
case ${F} in
- /etc/spwd.db | /etc/pwd.db | /etc/login.conf.db)
+ /etc/spwd.db | /etc/pwd.db | /etc/login.conf.db | /etc/ssl/cert.pem)
# Don't merge these -- we're rebuild them
# after updates are installed.
cp merge/old/${F} merge/new/${F}
diff --git a/usr.sbin/fwcamctl/Makefile b/usr.sbin/fwcamctl/Makefile
new file mode 100644
index 000000000000..127639c7e91f
--- /dev/null
+++ b/usr.sbin/fwcamctl/Makefile
@@ -0,0 +1,9 @@
+PROG= fwcamctl
+SRCS= fwcamctl.c
+MAN= fwcamctl.8
+WARNS?= 3
+
+SDIR= ${SRCTOP}/sys
+CFLAGS+= -I${SDIR}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/fwcamctl/fwcamctl.8 b/usr.sbin/fwcamctl/fwcamctl.8
new file mode 100644
index 000000000000..389117bcc9d0
--- /dev/null
+++ b/usr.sbin/fwcamctl/fwcamctl.8
@@ -0,0 +1,156 @@
+.\" Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd May 27, 2026
+.Dt FWCAMCTL 8
+.Os
+.Sh NAME
+.Nm fwcamctl
+.Nd control utility for IIDC FireWire cameras
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar device
+.Cm info
+.Nm
+.Op Fl f Ar device
+.Cm snap
+.Op Fl o Ar output.ppm
+.Op Fl s Ar skip
+.Nm
+.Op Fl f Ar device
+.Cm mode
+.Ar format mode rate
+.Nm
+.Op Fl f Ar device
+.Cm feat get
+.Ar id
+.Nm
+.Op Fl f Ar device
+.Cm feat set
+.Ar id value Op Ar value2
+.Sh DESCRIPTION
+The
+.Nm
+utility controls IIDC 1394-based digital cameras via the
+.Xr fwcam 4
+driver.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl f Ar device
+Specify the camera device.
+Defaults to
+.Pa /dev/fwcam0 .
+.El
+.Pp
+The subcommands are:
+.Bl -tag -width indent
+.It Cm info
+Display camera state, current format/mode/framerate, supported formats,
+basic function capabilities, and present features.
+.It Cm snap Op Fl o Ar output.ppm Op Fl s Ar skip
+Capture a single video frame and write it as a PPM image.
+.Pp
+.Bl -tag -width indent -compact
+.It Fl o Ar output.ppm
+Output file path.
+Use
+.Sq -
+for stdout.
+Defaults to
+.Pa snap.ppm .
+.It Fl s Ar skip
+Number of frames to skip before capturing, to allow auto-exposure
+and auto-white-balance to settle.
+Defaults to 5.
+.El
+.Pp
+Supported pixel formats: YUV444, YUV422, YUV411, RGB8, Mono8.
+The frame is converted to RGB24 and written as a binary PPM (P6).
+.It Cm mode Ar format mode rate
+Set the camera video format, mode, and frame rate.
+All arguments are integer indices per the IIDC specification.
+Only Format_0 (VGA) is currently supported by the
+.Xr fwcam 4
+frame assembly path.
+.Pp
+Format_0 modes:
+.Bl -column "Mode" "Resolution" "Pixel format" -compact
+.It Em Mode Ta Em Resolution Ta Em Pixel format
+.It 0 Ta 160x120 Ta YUV444
+.It 1 Ta 320x240 Ta YUV422
+.It 2 Ta 640x480 Ta YUV411
+.It 3 Ta 640x480 Ta YUV422
+.It 4 Ta 640x480 Ta RGB8
+.It 5 Ta 640x480 Ta Mono8
+.El
+.Pp
+Frame rates: 0=1.875 1=3.75 2=7.5 3=15 4=30 5=60 fps.
+.It Cm feat get Ar id
+Query the camera feature with the given numeric ID and display its
+inquiry (presence, on/off, auto, manual, range) and current value.
+.It Cm feat set Ar id value Op Ar value2
+Set the camera feature with the given numeric ID to
+.Ar value .
+For the
+.Sq white_balance
+feature (id 3),
+.Ar value
+is the U/B component and
+.Ar value2
+is the V/R component.
+.El
+.Pp
+Feature IDs:
+.Bl -column "ID" "Name" -compact
+.It Em ID Ta Em Name
+.It 0 Ta brightness
+.It 1 Ta auto_exposure
+.It 2 Ta sharpness
+.It 3 Ta white_balance
+.It 4 Ta hue
+.It 5 Ta saturation
+.It 6 Ta gamma
+.It 7 Ta shutter
+.It 8 Ta gain
+.It 9 Ta iris
+.It 10 Ta focus
+.It 11 Ta temperature
+.It 12 Ta trigger
+.It 13 Ta zoom
+.It 14 Ta pan
+.It 15 Ta tilt
+.El
+.Sh EXAMPLES
+Show camera information:
+.Bd -literal -offset indent
+fwcamctl info
+.Ed
+.Pp
+Capture a photo to
+.Pa photo.ppm ,
+skipping 10 frames for AE to settle:
+.Bd -literal -offset indent
+fwcamctl snap -o photo.ppm -s 10
+.Ed
+.Pp
+Switch to 320x240 YUV422 at 30 fps:
+.Bd -literal -offset indent
+fwcamctl mode 0 1 4
+.Ed
+.Pp
+Read the current gain value:
+.Bd -literal -offset indent
+fwcamctl feat get 8
+.Ed
+.Pp
+Set brightness to 200:
+.Bd -literal -offset indent
+fwcamctl feat set 0 200
+.Ed
+.Sh SEE ALSO
+.Xr fwcam 4 ,
+.Xr fwcontrol 8
+.Sh AUTHORS
+.An Abdelkader Boudih Aq Mt freebsd@seuros.com
diff --git a/usr.sbin/fwcamctl/fwcamctl.c b/usr.sbin/fwcamctl/fwcamctl.c
new file mode 100644
index 000000000000..af484c39dee6
--- /dev/null
+++ b/usr.sbin/fwcamctl/fwcamctl.c
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * fwcamctl - control utility for fwcam(4) IIDC FireWire cameras
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <dev/firewire/fwcam.h>
+
+static const char *default_dev = "/dev/fwcam0";
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: fwcamctl [-f device] info\n"
+ " fwcamctl [-f device] snap [-o output.ppm] [-s skip]\n"
+ " fwcamctl [-f device] mode <format> <mode> <rate>\n"
+ " fwcamctl [-f device] feat get <id>\n"
+ " fwcamctl [-f device] feat set <id> <value> [value2]\n"
+ "\n"
+ " Feature IDs: 0=brightness 1=auto_exposure 2=sharpness\n"
+ " 3=white_balance 4=hue 5=saturation 6=gamma\n"
+ " 7=shutter 8=gain 9=iris 10=focus 11=temperature\n"
+ " 12=trigger 13=zoom 14=pan 15=tilt\n");
+ exit(1);
+}
+
+static inline int
+clamp_u8(int v)
+{
+ if (v < 0) return (0);
+ if (v > 255) return (255);
+ return (v);
+}
+
+/*
+ * IIDC YUV 4:2:2 → RGB24
+ * IIDC spec layout: U0 Y0 V0 Y1 (UYVY, 2 pixels per 4 bytes)
+ */
+static void
+yuv422_to_rgb(const uint8_t *src, uint8_t *dst, int w, int h)
+{
+ int i, n = w * h / 2;
+
+ for (i = 0; i < n; i++) {
+ int cb = src[0] - 128;
+ int y0 = src[1];
+ int cr = src[2] - 128;
+ int y1 = src[3];
+ src += 4;
+
+ /* pixel 0 */
+ dst[0] = clamp_u8(y0 + (int)(1.402f * cr));
+ dst[1] = clamp_u8(y0 - (int)(0.34414f * cb) - (int)(0.71414f * cr));
+ dst[2] = clamp_u8(y0 + (int)(1.772f * cb));
+ /* pixel 1 */
+ dst[3] = clamp_u8(y1 + (int)(1.402f * cr));
+ dst[4] = clamp_u8(y1 - (int)(0.34414f * cb) - (int)(0.71414f * cr));
+ dst[5] = clamp_u8(y1 + (int)(1.772f * cb));
+ dst += 6;
+ }
+}
+
+/*
+ * IIDC YUV 4:1:1 → RGB24
+ * Layout: U0 Y0 Y1 V0 Y2 Y3 (4 pixels per 6 bytes)
+ */
+static void
+yuv411_to_rgb(const uint8_t *src, uint8_t *dst, int w, int h)
+{
+ int i, j, n = w * h / 4;
+
+ for (i = 0; i < n; i++) {
+ int cb = src[0] - 128;
+ int y[4];
+ y[0] = src[1];
+ y[1] = src[2];
+ int cr = src[3] - 128;
+ y[2] = src[4];
+ y[3] = src[5];
+ src += 6;
+
+ for (j = 0; j < 4; j++) {
+ *dst++ = clamp_u8(y[j] + (int)(1.402f * cr));
+ *dst++ = clamp_u8(y[j] - (int)(0.34414f * cb) - (int)(0.71414f * cr));
+ *dst++ = clamp_u8(y[j] + (int)(1.772f * cb));
+ }
+ }
+}
+
+/*
+ * IIDC YUV 4:4:4 → RGB24
+ * Layout: U Y V (3 bytes/pixel)
+ */
+static void
+yuv444_to_rgb(const uint8_t *src, uint8_t *dst, int w, int h)
+{
+ int i, n = w * h;
+
+ for (i = 0; i < n; i++) {
+ int cb = src[0] - 128;
+ int y = src[1];
+ int cr = src[2] - 128;
+ src += 3;
+
+ *dst++ = clamp_u8(y + (int)(1.402f * cr));
+ *dst++ = clamp_u8(y - (int)(0.34414f * cb) - (int)(0.71414f * cr));
+ *dst++ = clamp_u8(y + (int)(1.772f * cb));
+ }
+}
+
+static int
+write_ppm(const char *path, const uint8_t *rgb, int w, int h)
+{
+ FILE *f;
+
+ if (strcmp(path, "-") == 0) {
+ f = stdout;
+ } else {
+ f = fopen(path, "wb");
+ if (f == NULL) {
+ warn("fopen %s", path);
+ return (-1);
+ }
+ }
+
+ fprintf(f, "P6\n%d %d\n255\n", w, h);
+ if (fwrite(rgb, 1, (size_t)(w * h * 3), f) != (size_t)(w * h * 3)) {
+ warn("fwrite");
+ if (f != stdout)
+ fclose(f);
+ return (-1);
+ }
+
+ if (f != stdout)
+ fclose(f);
+ return (0);
+}
+
+static int
+do_snap(int fd, const char *outpath, int skip)
+{
+ struct fwcam_info info;
+ uint8_t *frame_buf, *rgb_buf;
+ uint32_t rgb_size;
+ ssize_t n;
+ int w, h, i;
+ const char *pixfmt;
+
+ if (ioctl(fd, FWCAM_GINFO, &info) < 0)
+ err(1, "FWCAM_GINFO");
+
+ if (info.frame_size == 0)
+ errx(1, "frame_size is 0 — camera not yet probed?");
+
+ if (info.cur_format != IIDC_FMT_VGA ||
+ info.cur_mode >= nitems(fwcam_fmt0_modes))
+ errx(1, "unsupported format %d mode %d for snap "
+ "(only Format_0 VGA supported)",
+ info.cur_format, info.cur_mode);
+
+ w = fwcam_fmt0_modes[info.cur_mode].w;
+ h = fwcam_fmt0_modes[info.cur_mode].h;
+ pixfmt = fwcam_fmt0_modes[info.cur_mode].pixfmt;
+
+ if (strcmp(pixfmt, "Mono16") == 0)
+ errx(1, "Mono16 not supported for PPM output");
+
+ rgb_size = (uint32_t)(w * h * 3);
+ frame_buf = malloc(info.frame_size);
+ rgb_buf = malloc(rgb_size);
+ if (frame_buf == NULL || rgb_buf == NULL)
+ err(1, "malloc");
+
+ /* Read frames; skip the first N to let AE/AWB settle */
+ for (i = 0; i <= skip; i++) {
+ n = read(fd, frame_buf, info.frame_size);
+ if (n < 0)
+ err(1, "read");
+ if ((uint32_t)n < info.frame_size)
+ errx(1, "short read: got %zd of %u bytes",
+ n, info.frame_size);
+ if (i < skip)
+ fprintf(stderr, "skipping frame %d/%d...\n", i + 1, skip);
+ }
+
+ /* Convert to RGB24 */
+ if (strcmp(pixfmt, "YUV422") == 0) {
+ yuv422_to_rgb(frame_buf, rgb_buf, w, h);
+ } else if (strcmp(pixfmt, "YUV411") == 0) {
+ yuv411_to_rgb(frame_buf, rgb_buf, w, h);
+ } else if (strcmp(pixfmt, "YUV444") == 0) {
+ yuv444_to_rgb(frame_buf, rgb_buf, w, h);
+ } else if (strcmp(pixfmt, "RGB8") == 0) {
+ memcpy(rgb_buf, frame_buf, rgb_size);
+ } else if (strcmp(pixfmt, "Mono8") == 0) {
+ for (i = 0; i < w * h; i++) {
+ rgb_buf[i * 3 + 0] = frame_buf[i];
+ rgb_buf[i * 3 + 1] = frame_buf[i];
+ rgb_buf[i * 3 + 2] = frame_buf[i];
+ }
+ } else {
+ errx(1, "no converter for pixel format %s", pixfmt);
+ }
+
+ if (write_ppm(outpath, rgb_buf, w, h) < 0) {
+ free(frame_buf);
+ free(rgb_buf);
+ return (1);
+ }
+
+ fprintf(stderr, "saved %s (%dx%d %s, %u bytes raw)\n",
+ outpath, w, h, pixfmt, info.frame_size);
+
+ free(frame_buf);
+ free(rgb_buf);
+ return (0);
+}
+
+static void
+do_info(int fd)
+{
+ struct fwcam_info info;
+ int i;
+
+ if (ioctl(fd, FWCAM_GINFO, &info) < 0)
+ err(1, "FWCAM_GINFO");
+
+ printf("state: %s\n",
+ info.state < nitems(fwcam_state_names) ?
+ fwcam_state_names[info.state] : "unknown");
+ printf("cur format: %d (%s)\n", info.cur_format,
+ info.cur_format < nitems(fwcam_fmt_names) ?
+ fwcam_fmt_names[info.cur_format] : "?");
+
+ if (info.cur_format == IIDC_FMT_VGA &&
+ info.cur_mode < nitems(fwcam_fmt0_modes)) {
+ printf("cur mode: %d (%dx%d %s)\n", info.cur_mode,
+ fwcam_fmt0_modes[info.cur_mode].w,
+ fwcam_fmt0_modes[info.cur_mode].h,
+ fwcam_fmt0_modes[info.cur_mode].pixfmt);
+ } else {
+ printf("cur mode: %d\n", info.cur_mode);
+ }
+
+ printf("cur rate: %d (%s)\n", info.cur_framerate,
+ info.cur_framerate < nitems(fwcam_rate_names) ?
+ fwcam_rate_names[info.cur_framerate] : "?");
+ printf("iso_channel: %u\n", info.iso_channel);
+ printf("frame_size: %u bytes\n", info.frame_size);
+ printf("dropped: %u frames\n", info.frame_dropped);
+
+ printf("formats: 0x%08x (", info.formats);
+ for (i = 0; i < 8; i++) {
+ if (info.formats & (1 << (31 - i)))
+ printf("F%d ", i);
+ }
+ printf(")\n");
+
+ printf("basic_func: 0x%08x", info.basic_func);
+ if (info.basic_func & IIDC_CAM_POWER_CTRL) printf(" [power_ctrl]");
+ if (info.basic_func & IIDC_ONE_SHOT_INQ) printf(" [one_shot]");
+ if (info.basic_func & IIDC_MULTI_SHOT_INQ) printf(" [multi_shot]");
+ printf("\n");
+
+ printf("features: hi=0x%08x lo=0x%08x\n",
+ info.features_hi, info.features_lo);
+ if (info.features_hi) {
+ printf(" ");
+ if (info.features_hi & IIDC_HAS_BRIGHTNESS) printf("brightness ");
+ if (info.features_hi & IIDC_HAS_AUTO_EXPOSURE) printf("auto_exp ");
+ if (info.features_hi & IIDC_HAS_SHARPNESS) printf("sharpness ");
+ if (info.features_hi & IIDC_HAS_WHITE_BALANCE) printf("white_bal ");
+ if (info.features_hi & IIDC_HAS_HUE) printf("hue ");
+ if (info.features_hi & IIDC_HAS_SATURATION) printf("saturation ");
+ if (info.features_hi & IIDC_HAS_GAMMA) printf("gamma ");
+ if (info.features_hi & IIDC_HAS_SHUTTER) printf("shutter ");
+ if (info.features_hi & IIDC_HAS_GAIN) printf("gain ");
+ if (info.features_hi & IIDC_HAS_IRIS) printf("iris ");
+ if (info.features_hi & IIDC_HAS_FOCUS) printf("focus ");
+ if (info.features_hi & IIDC_HAS_TEMPERATURE) printf("temperature ");
+ if (info.features_hi & IIDC_HAS_TRIGGER) printf("trigger ");
+ printf("\n");
+ }
+}
+
+static void
+do_mode(int fd, int argc, char **argv)
+{
+ struct fwcam_mode mode;
+
+ if (argc < 3)
+ usage();
+
+ mode.format = (uint8_t)atoi(argv[0]);
+ mode.mode = (uint8_t)atoi(argv[1]);
+ mode.framerate = (uint8_t)atoi(argv[2]);
+ mode.frame_size = 0;
+ mode._pad = 0;
+
+ if (ioctl(fd, FWCAM_SMODE, &mode) < 0)
+ err(1, "FWCAM_SMODE");
+
+ printf("mode set: format=%d mode=%d framerate=%d frame_size=%u\n",
+ mode.format, mode.mode, mode.framerate, mode.frame_size);
+}
+
+static void
+do_feat_get(int fd, int feat_id)
+{
+ struct fwcam_feature feat;
+
+ if (feat_id < 0 || feat_id >= FWCAM_FEAT_MAX)
+ errx(1, "invalid feature id %d (0-%d)", feat_id, FWCAM_FEAT_MAX - 1);
+
+ memset(&feat, 0, sizeof(feat));
+ feat.id = (uint32_t)feat_id;
+
+ if (ioctl(fd, FWCAM_GFEAT, &feat) < 0)
+ err(1, "FWCAM_GFEAT");
+
+ printf("feature %d (%s):\n", feat_id,
+ fwcam_feat_names[feat_id] != NULL ? fwcam_feat_names[feat_id] : "?");
+ printf(" present: %s\n",
+ (feat.flags & FWCAM_FEATF_PRESENT) ? "yes" : "no");
+ printf(" flags: 0x%x", feat.flags);
+ if (feat.flags & FWCAM_FEATF_ONOFF) printf(" [on/off]");
+ if (feat.flags & FWCAM_FEATF_AUTO) printf(" [auto]");
+ if (feat.flags & FWCAM_FEATF_MANUAL) printf(" [manual]");
+ printf("\n");
+ printf(" range: %u - %u\n", feat.min, feat.max);
+
+ if (feat_id == FWCAM_FEAT_WHITE_BALANCE)
+ printf(" value: U/B=%u V/R=%u\n", feat.value, feat.value2);
+ else
+ printf(" value: %u\n", feat.value);
+}
+
+static void
+do_feat_set(int fd, int feat_id, uint32_t value, uint32_t value2)
+{
+ struct fwcam_feature feat;
+
+ if (feat_id < 0 || feat_id >= FWCAM_FEAT_MAX)
+ errx(1, "invalid feature id %d (0-%d)", feat_id, FWCAM_FEAT_MAX - 1);
+
+ memset(&feat, 0, sizeof(feat));
+ feat.id = (uint32_t)feat_id;
+ feat.value = value;
+ feat.value2 = value2;
+
+ if (ioctl(fd, FWCAM_SFEAT, &feat) < 0)
+ err(1, "FWCAM_SFEAT");
+
+ printf("feature %d (%s) set to %u\n", feat_id,
+ fwcam_feat_names[feat_id] != NULL ? fwcam_feat_names[feat_id] : "?", value);
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *devpath = default_dev;
+ int fd, ch;
+
+ while ((ch = getopt(argc, argv, "f:")) != -1) {
+ switch (ch) {
+ case 'f':
+ devpath = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ fd = open(devpath, O_RDWR);
+ if (fd < 0)
+ err(1, "open %s", devpath);
+
+ if (strcmp(argv[0], "info") == 0) {
+ do_info(fd);
+
+ } else if (strcmp(argv[0], "snap") == 0) {
+ const char *out = "snap.ppm";
+ int skip = 5;
+
+ /* re-parse the snap subcommand's own flags */
+ optreset = 1;
+ optind = 1;
+ while ((ch = getopt(argc, argv, "o:s:")) != -1) {
+ switch (ch) {
+ case 'o':
+ out = optarg;
+ break;
+ case 's':
+ skip = atoi(optarg);
+ break;
+ default:
+ usage();
+ }
+ }
+ do_snap(fd, out, skip);
+
+ } else if (strcmp(argv[0], "mode") == 0) {
+ do_mode(fd, argc - 1, argv + 1);
+
+ } else if (strcmp(argv[0], "feat") == 0) {
+ if (argc < 3)
+ usage();
+
+ int feat_id = atoi(argv[2]);
+
+ if (strcmp(argv[1], "get") == 0) {
+ do_feat_get(fd, feat_id);
+ } else if (strcmp(argv[1], "set") == 0) {
+ if (argc < 4)
+ usage();
+ uint32_t val = (uint32_t)atoi(argv[3]);
+ uint32_t val2 = (argc > 4) ? (uint32_t)atoi(argv[4]) : 0;
+ do_feat_set(fd, feat_id, val, val2);
+ } else {
+ usage();
+ }
+
+ } else {
+ warnx("unknown command: %s", argv[0]);
+ usage();
+ }
+
+ close(fd);
+ return (0);
+}
diff --git a/usr.sbin/fwget/pci/pci b/usr.sbin/fwget/pci/pci
index de8b7c8bb2b3..0c210cde1b8a 100644
--- a/usr.sbin/fwget/pci/pci
+++ b/usr.sbin/fwget/pci/pci
@@ -47,6 +47,7 @@ pci_get_vendor()
0x168c) echo "qca" ;; # Qualcomm Atheros
0x17cb) echo "qca" ;; # Qualcomm Technologies
0x8086) echo "intel" ;;
+ 0x0b48) echo "ittim" ;;
esac
}
diff --git a/usr.sbin/fwget/pci/pci_network_intel b/usr.sbin/fwget/pci/pci_network_intel
index 02d842a2d075..c088d3a57f83 100644
--- a/usr.sbin/fwget/pci/pci_network_intel
+++ b/usr.sbin/fwget/pci/pci_network_intel
@@ -1,7 +1,7 @@
#-
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (c) 2024 The FreeBSD Foundation
+# Copyright (c) 2024-2026 The FreeBSD Foundation
#
# This software was developed by Björn Zeeb
# under sponsorship from the FreeBSD Foundation.
@@ -26,18 +26,11 @@ pci_network_intel_iwlwifi()
### >>>
# 7000
- 0x08b1/*/0x4a6c) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x08b1/*/0x4a6e) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x08b1/*/0x4a70) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x08b1/*/0x4c60) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x08b1/*/0x4c70) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x08b1/*/0x402a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x08b1/*/0x406a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x08b1/*/0x446a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x08b1/*/0x486e) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x4020) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x08b1/*/0x402a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x4060) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x4062) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x08b1/*/0x406a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x4070) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x4072) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x4160) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
@@ -46,20 +39,27 @@ pci_network_intel_iwlwifi()
0x08b1/*/0x4420) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x4460) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x4462) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x08b1/*/0x446a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x4470) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x4472) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x4560) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x4570) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x08b1/*/0x486e) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x4870) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x08b1/*/0x4a6c) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x08b1/*/0x4a6e) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x08b1/*/0x4a70) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x08b1/*/0x4c60) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x08b1/*/0x4c70) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x5070) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x5072) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x5170) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0x5770) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x08b1/*/0xc02a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x08b1/*/0xc06a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0xc020) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x08b1/*/0xc02a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0xc060) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0xc062) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x08b1/*/0xc06a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0xc070) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0xc072) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0xc160) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
@@ -77,18 +77,18 @@ pci_network_intel_iwlwifi()
0x08b1/*/0xc770) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0xcc60) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b1/*/0xcc70) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x08b2/*/0x426a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b2/*/0x4220) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b2/*/0x4260) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b2/*/0x4262) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x08b2/*/0x426a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b2/*/0x4270) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b2/*/0x4272) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b2/*/0x4360) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b2/*/0x4370) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x08b2/*/0xc26a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b2/*/0xc220) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b2/*/0xc260) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b2/*/0xc262) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x08b2/*/0xc26a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b2/*/0xc270) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b2/*/0xc272) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b2/*/0xc370) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
@@ -116,23 +116,14 @@ pci_network_intel_iwlwifi()
0x08b4/*/0x8270) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b4/*/0x8272) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x08b4/*/0x8370) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x24fb/*/0x0000) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x24fb/*/0x2010) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x24fb/*/0x2050) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x24fb/*/0x2110) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x24fb/*/0x2150) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x095a/*/0x5c10) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x095a/*/0x5f10) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x095a/*/0x9e10) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x095a/*/0x500a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x095a/*/0x502a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x095a/*/0x900a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x1010) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x5000) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x5002) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x095a/*/0x500a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x5010) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x5012) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x5020) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x095a/*/0x502a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x5090) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x5100) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x5102) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
@@ -145,7 +136,10 @@ pci_network_intel_iwlwifi()
0x095a/*/0x5490) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x5510) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x5590) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x095a/*/0x5c10) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x095a/*/0x5f10) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x9000) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x095a/*/0x900a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x9010) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x9012) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x9110) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
@@ -153,9 +147,10 @@ pci_network_intel_iwlwifi()
0x095a/*/0x9400) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x9410) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095a/*/0x9510) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
- 0x095b/*/0x520a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x095a/*/0x9e10) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095b/*/0x5200) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095b/*/0x5202) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x095b/*/0x520a) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095b/*/0x5210) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095b/*/0x5212) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095b/*/0x5290) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
@@ -164,6 +159,11 @@ pci_network_intel_iwlwifi()
0x095b/*/0x9200) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095b/*/0x9210) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x095b/*/0x9310) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x24fb/*/0x0000) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x24fb/*/0x2010) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x24fb/*/0x2050) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x24fb/*/0x2110) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
+ 0x24fb/*/0x2150) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x3165/*/0x4010) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x3165/*/0x4012) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
0x3165/*/0x4110) addpkg "wifi-firmware-iwlwifi-kmod-7000"; return 1 ;;
@@ -177,10 +177,8 @@ pci_network_intel_iwlwifi()
# 8000
0x24f3/*/0x0000) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
- 0x24f3/*/0x01f0) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x0004) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x0010) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
- 0x24f3/*/0x10b0) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x0012) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x0044) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x0050) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
@@ -188,6 +186,7 @@ pci_network_intel_iwlwifi()
0x24f3/*/0x0130) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x0132) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x0150) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
+ 0x24f3/*/0x01f0) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x0250) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x0810) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x0850) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
@@ -197,6 +196,7 @@ pci_network_intel_iwlwifi()
0x24f3/*/0x1010) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x1012) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x1050) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
+ 0x24f3/*/0x10b0) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x1110) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x1130) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0x1132) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
@@ -216,11 +216,13 @@ pci_network_intel_iwlwifi()
0x24f3/*/0x9150) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0xb0b0) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0xc010) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
+ 0x24f3/*/0xc030) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0xc050) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0xc110) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
- 0x24f3/*/0xd0b0) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0xd010) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
+ 0x24f3/*/0xd030) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f3/*/0xd050) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
+ 0x24f3/*/0xd0b0) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f4/*/0x0030) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f4/*/0x1030) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f4/*/0x8030) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
@@ -229,10 +231,7 @@ pci_network_intel_iwlwifi()
0x24f4/*/0xd030) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f5/*/0x0010) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24f6/*/0x0030) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
- 0x24fd/*/0x3e01) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
- 0x24fd/*/0x3e02) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24fd/*/0x0010) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
- 0x24fd/*/0x10d0) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24fd/*/0x0012) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24fd/*/0x0014) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24fd/*/0x0050) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
@@ -247,8 +246,13 @@ pci_network_intel_iwlwifi()
0x24fd/*/0x1010) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24fd/*/0x1012) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24fd/*/0x1014) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
+ 0x24fd/*/0x10d0) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24fd/*/0x1110) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24fd/*/0x1130) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
+ 0x24fd/*/0x1431) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
+ 0x24fd/*/0x1432) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
+ 0x24fd/*/0x3e01) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
+ 0x24fd/*/0x3e02) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24fd/*/0x8010) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24fd/*/0x8050) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
0x24fd/*/0x8110) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
@@ -258,201 +262,93 @@ pci_network_intel_iwlwifi()
0x24fd/*/0x9110) addpkg "wifi-firmware-iwlwifi-kmod-8000"; return 1 ;;
# 9000
- 0x9df0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-9000"; return 1 ;;
- 0x30dc/*/*) addpkg "wifi-firmware-iwlwifi-kmod-9000"; return 1 ;;
- 0x31dc/*/*) addpkg "wifi-firmware-iwlwifi-kmod-9000"; return 1 ;;
+ 0x2526/*/*) addpkg "wifi-firmware-iwlwifi-kmod-9000"; return 1 ;;
0x271b/*/*) addpkg "wifi-firmware-iwlwifi-kmod-9000"; return 1 ;;
0x271c/*/*) addpkg "wifi-firmware-iwlwifi-kmod-9000"; return 1 ;;
- 0x2526/*/*) addpkg "wifi-firmware-iwlwifi-kmod-9000"; return 1 ;;
+ 0x30dc/*/*) addpkg "wifi-firmware-iwlwifi-kmod-9000"; return 1 ;;
+ 0x31dc/*/*) addpkg "wifi-firmware-iwlwifi-kmod-9000"; return 1 ;;
+ 0x9df0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-9000"; return 1 ;;
0xa370/*/*) addpkg "wifi-firmware-iwlwifi-kmod-9000"; return 1 ;;
# 22000
- 0x02f0/*/0x007c) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x02f0/*/0x0070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x02f0/*/0x0074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x02f0/*/0x0078) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x02f0/*/0x0310) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x02f0/*/0x1651) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x02f0/*/0x1652) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x02f0/*/0x2074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x02f0/*/0x4070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x02f0/*/0x6074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
0x02f0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x3df0/*/0x007c) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x3df0/*/0x0070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x3df0/*/0x0074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x3df0/*/0x0078) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x3df0/*/0x0310) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x3df0/*/0x1651) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x3df0/*/0x1652) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x3df0/*/0x2074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x3df0/*/0x4070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x3df0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x4df0/*/0x007c) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x4df0/*/0x0070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x4df0/*/0x0074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x4df0/*/0x0078) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x4df0/*/0x0310) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x4df0/*/0x1651) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x4df0/*/0x1652) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x4df0/*/0x2074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x4df0/*/0x4070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x4df0/*/0x6074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x4df0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x06f0/*/0x007c) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x06f0/*/0x0070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x06f0/*/0x0074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x06f0/*/0x0078) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x06f0/*/0x0310) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x06f0/*/0x1651) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x06f0/*/0x1652) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x06f0/*/0x2074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x06f0/*/0x4070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
0x06f0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x34f0/*/0x007c) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x34f0/*/0x0070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x34f0/*/0x0074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x34f0/*/0x0078) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x34f0/*/0x0310) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x34f0/*/0x1651) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x34f0/*/0x1652) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x34f0/*/0x2074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x34f0/*/0x4070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
+ 0x2723/*/*) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
0x34f0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x43f0/*/0x007c) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x43f0/*/0x0070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x43f0/*/0x0074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x43f0/*/0x0078) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x43f0/*/0x1651) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x43f0/*/0x1652) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x43f0/*/0x2074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x43f0/*/0x4070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
+ 0x3df0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
0x43f0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x2723/*/0x1653) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x2723/*/0x1654) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0x2723/*/*) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0xa0f0/*/0x0a10) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0xa0f0/*/0x007c) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0xa0f0/*/0x0070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0xa0f0/*/0x0074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0xa0f0/*/0x0078) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0xa0f0/*/0x1651) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0xa0f0/*/0x1652) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0xa0f0/*/0x2074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0xa0f0/*/0x4070) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
- 0xa0f0/*/0x6074) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
+ 0x4df0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
0xa0f0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-22000"; return 1 ;;
# ax210
- 0x7a70/*/0x0a10) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7a70/*/0x00b0) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7a70/*/0x0090) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7a70/*/0x0098) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7a70/*/0x0310) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7a70/*/0x0510) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7a70/*/0x1671) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7a70/*/0x1672) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7a70/*/0x1691) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7a70/*/0x1692) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
+ 0x2725/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
+ 0x2729/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
+ 0x51f0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
+ 0x51f1/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
+ 0x54f0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
0x7a70/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7af0/*/0x0a10) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7af0/*/0x00b0) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7af0/*/0x0090) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7af0/*/0x0098) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7af0/*/0x0310) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7af0/*/0x0510) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7af0/*/0x1671) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7af0/*/0x1672) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7af0/*/0x1691) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7af0/*/0x1692) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
0x7af0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7e40/*/0x1671) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7e40/*/0x1672) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7e40/*/0x1691) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7e40/*/0x1692) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
0x7e40/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7f70/*/0x1671) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x7f70/*/0x1672) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
0x7f70/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x51f0/*/0x1671) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x51f0/*/0x1672) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x51f0/*/0x1691) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x51f0/*/0x1692) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x51f0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x51f1/*/0x1671) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x51f1/*/0x1672) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x51f1/*/0x1692) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x51f1/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x54f0/*/0x1671) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x54f0/*/0x1672) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x54f0/*/0x1691) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x54f0/*/0x1692) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x54f0/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0x0a10) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0x0020) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0x0024) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0x0090) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0x0310) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0x0510) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0x1673) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0x1674) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0x2020) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0x4020) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0x6020) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0x6024) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0xe020) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/0xe024) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2725/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2726/*/0x1671) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2726/*/0x1672) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
- 0x2729/*/*) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
# bz
- 0x4d40/*/*) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0x272b/*/*) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
- 0x272d/*/*) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
- 0x2727/*/*) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
+ 0x4d40/*/*) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0x7740/*/*) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x0000) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
- 0xa840/*/0x0a10) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
+ 0xa840/*/0x0090) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
+ 0xa840/*/0x0094) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
+ 0xa840/*/0x0098) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
+ 0xa840/*/0x009c) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x00c0) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x00c4) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x00e0) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x00e4) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x00e8) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x00ec) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
- 0xa840/*/0x009c) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
- 0xa840/*/0x011c) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
- 0xa840/*/0x40c4) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
- 0xa840/*/0x40e0) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
- 0xa840/*/0x0090) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
- 0xa840/*/0x0094) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
- 0xa840/*/0x0098) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x0100) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x0110) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x0114) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x0118) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
+ 0xa840/*/0x011c) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x0310) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x0314) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x0510) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
+ 0xa840/*/0x0a10) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x1671) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x1672) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x1771) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x1772) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
+ 0xa840/*/0x1775) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
+ 0xa840/*/0x1776) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x1791) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x1792) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x4090) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
+ 0xa840/*/0x40c4) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
+ 0xa840/*/0x40e0) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x4110) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
0xa840/*/0x4314) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
# sc
0x6e70/*/*) addpkg "wifi-firmware-iwlwifi-kmod-sc"; return 1 ;;
+ 0xd240/*/*) addpkg "wifi-firmware-iwlwifi-kmod-sc"; return 1 ;;
0xd340/*/*) addpkg "wifi-firmware-iwlwifi-kmod-sc"; return 1 ;;
0xe340/*/*) addpkg "wifi-firmware-iwlwifi-kmod-sc"; return 1 ;;
0xe440/*/*) addpkg "wifi-firmware-iwlwifi-kmod-sc"; return 1 ;;
### <<<
+ # Historic values which are removed from a newer driver but are still
+ # present in at least stable/14.
+
+ # iwl_ax211_killer_1675*
+ 0x2726/*/0x1671) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
+ 0x2726/*/0x1672) addpkg "wifi-firmware-iwlwifi-kmod-ax210"; return 1 ;;
+
+ # bz
+ 0x2727/*/*) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
+ 0x272d/*/*) addpkg "wifi-firmware-iwlwifi-kmod-bz"; return 1 ;;
+
esac
}
diff --git a/usr.sbin/fwget/pci/pci_network_ittim b/usr.sbin/fwget/pci/pci_network_ittim
new file mode 100644
index 000000000000..632803bc1d35
--- /dev/null
+++ b/usr.sbin/fwget/pci/pci_network_ittim
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2026 The FreeBSD Foundation
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+pci_network_ittim_mt76()
+{
+
+ case "$1" in
+ 0x7922) addpkg "wifi-firmware-mt76-kmod-mt7921"; return 1 ;;
+ esac
+}
+
+pci_network_ittim()
+{
+
+ for _drv in mt76; do
+ pci_network_ittim_${_drv} "$1"
+ case $? in
+ 1) break ;;
+ esac
+ done
+}
diff --git a/usr.sbin/fwget/pci/pci_network_mediatek b/usr.sbin/fwget/pci/pci_network_mediatek
index e1e15dcfa2e5..6cc125d0da6a 100644
--- a/usr.sbin/fwget/pci/pci_network_mediatek
+++ b/usr.sbin/fwget/pci/pci_network_mediatek
@@ -38,9 +38,9 @@ pci_network_mediatek_mt76()
# { sys/contrib/dev/mediatek/mt76/zzz_fw_ports_fwget.sh }
### >>>
- 0x0608) addpkg "wifi-firmware-mt76-kmod-mt792x"; return 1 ;;
- 0x0616) addpkg "wifi-firmware-mt76-kmod-mt792x"; return 1 ;;
- 0x0717) addpkg "wifi-firmware-mt76-kmod-mt792x"; return 1 ;;
+ 0x0608) addpkg "wifi-firmware-mt76-kmod-mt7921"; return 1 ;;
+ 0x0616) addpkg "wifi-firmware-mt76-kmod-mt7921"; return 1 ;;
+ 0x0717) addpkg "wifi-firmware-mt76-kmod-mt7925"; return 1 ;;
0x7611) addpkg "wifi-firmware-mt76-kmod-mt7615"; return 1 ;;
0x7615) addpkg "wifi-firmware-mt76-kmod-mt7615"; return 1 ;;
0x7663) addpkg "wifi-firmware-mt76-kmod-mt7615"; return 1 ;;
@@ -48,10 +48,10 @@ pci_network_mediatek_mt76()
0x790a) addpkg "wifi-firmware-mt76-kmod-mt7915"; return 1 ;;
0x7915) addpkg "wifi-firmware-mt76-kmod-mt7915"; return 1 ;;
0x7916) addpkg "wifi-firmware-mt76-kmod-mt7915"; return 1 ;;
- 0x7920) addpkg "wifi-firmware-mt76-kmod-mt792x"; return 1 ;;
- 0x7922) addpkg "wifi-firmware-mt76-kmod-mt792x"; return 1 ;;
- 0x7925) addpkg "wifi-firmware-mt76-kmod-mt792x"; return 1 ;;
- 0x7961) addpkg "wifi-firmware-mt76-kmod-mt792x"; return 1 ;;
+ 0x7920) addpkg "wifi-firmware-mt76-kmod-mt7921"; return 1 ;;
+ 0x7922) addpkg "wifi-firmware-mt76-kmod-mt7921"; return 1 ;;
+ 0x7925) addpkg "wifi-firmware-mt76-kmod-mt7925"; return 1 ;;
+ 0x7961) addpkg "wifi-firmware-mt76-kmod-mt7921"; return 1 ;;
0x7990) addpkg "wifi-firmware-mt76-kmod-mt7996"; return 1 ;;
0x7991) addpkg "wifi-firmware-mt76-kmod-mt7996"; return 1 ;;
0x7992) addpkg "wifi-firmware-mt76-kmod-mt7996"; return 1 ;;
diff --git a/usr.sbin/fwget/pci/pci_network_realtek b/usr.sbin/fwget/pci/pci_network_realtek
index d70470b65244..24e24c498e67 100644
--- a/usr.sbin/fwget/pci/pci_network_realtek
+++ b/usr.sbin/fwget/pci/pci_network_realtek
@@ -2,7 +2,7 @@
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright 2023 Bjoern A. Zeeb
-# Copyright (c) 2024 The FreeBSD Foundation
+# Copyright (c) 2024-2026 The FreeBSD Foundation
#
# Portions of this software were developed by Björn Zeeb
# under sponsorship from the FreeBSD Foundation.
@@ -41,6 +41,7 @@ pci_network_realtek_rtw88()
### >>>
0xd723) addpkg "wifi-firmware-rtw88-kmod-rtw8723d"; return 1 ;;
+ 0x8813) addpkg "wifi-firmware-rtw88-kmod-rtw8814a"; return 1 ;;
0xb821) addpkg "wifi-firmware-rtw88-kmod-rtw8821c"; return 1 ;;
0xc821) addpkg "wifi-firmware-rtw88-kmod-rtw8821c"; return 1 ;;
0xb822) addpkg "wifi-firmware-rtw88-kmod-rtw8822b"; return 1 ;;
@@ -67,8 +68,10 @@ pci_network_realtek_rtw89()
0xa85a) addpkg "wifi-firmware-rtw89-kmod-rtw8852a"; return 1 ;;
0xb852) addpkg "wifi-firmware-rtw89-kmod-rtw8852b"; return 1 ;;
0xb85b) addpkg "wifi-firmware-rtw89-kmod-rtw8852b"; return 1 ;;
+ 0xb520) addpkg "wifi-firmware-rtw89-kmod-rtw8852bt"; return 1 ;;
0xc852) addpkg "wifi-firmware-rtw89-kmod-rtw8852c"; return 1 ;;
0x8922) addpkg "wifi-firmware-rtw89-kmod-rtw8922a"; return 1 ;;
+ 0x892b) addpkg "wifi-firmware-rtw89-kmod-rtw8922a"; return 1 ;;
### <<<
esac
diff --git a/usr.sbin/fwget/pci/pci_video_amd b/usr.sbin/fwget/pci/pci_video_amd
index 11f33eef78dd..4c350d92e643 100644
--- a/usr.sbin/fwget/pci/pci_video_amd
+++ b/usr.sbin/fwget/pci/pci_video_amd
@@ -174,10 +174,10 @@ pci_video_amd()
addpkg "gpu-firmware-amd-kmod-sdma-6-0-1"
addpkg "gpu-firmware-amd-kmod-vcn-4-0-2"
;;
- 0x164e)
+ 0x13c0|0x164e)
+ addpkg "gpu-firmware-amd-kmod-dcn-3-1-5"
addpkg "gpu-firmware-amd-kmod-gc-10-3-6"
addpkg "gpu-firmware-amd-kmod-psp-13-0-5"
- addpkg "gpu-firmware-amd-kmod-dcn-3-1-5"
addpkg "gpu-firmware-amd-kmod-sdma-5-2-6"
addpkg "gpu-firmware-amd-kmod-vcn-3-1-2"
;;
diff --git a/usr.sbin/gssd/gssd.c b/usr.sbin/gssd/gssd.c
index 54d2062dd29a..5a00dbdd7c97 100644
--- a/usr.sbin/gssd/gssd.c
+++ b/usr.sbin/gssd/gssd.c
@@ -167,6 +167,11 @@ main(int argc, char **argv)
gssd_load_mech();
+ if ((xprt = svc_nl_create("kgss")) == NULL)
+ errx(1, "Can't create transport for local gssd socket");
+ if (!svc_reg(xprt, GSSD, GSSDVERS, gssd_1, NULL))
+ errx(1, "Can't register service for local gssd socket");
+
if (!debug_level) {
if (daemon(0, 0) != 0)
err(1, "Can't daemonize");
@@ -177,23 +182,6 @@ main(int argc, char **argv)
signal(SIGTERM, gssd_terminate);
signal(SIGPIPE, gssd_terminate);
- if ((xprt = svc_nl_create("kgss")) == NULL) {
- if (debug_level == 0) {
- syslog(LOG_ERR,
- "Can't create transport for local gssd socket");
- exit(1);
- }
- err(1, "Can't create transport for local gssd socket");
- }
- if (!svc_reg(xprt, GSSD, GSSDVERS, gssd_1, NULL)) {
- if (debug_level == 0) {
- syslog(LOG_ERR,
- "Can't register service for local gssd socket");
- exit(1);
- }
- err(1, "Can't register service for local gssd socket");
- }
-
LIST_INIT(&gss_resources);
gss_next_id = 1;
gss_start_time = time(0);
diff --git a/usr.sbin/gstat/gstat.8 b/usr.sbin/gstat/gstat.8
index e882aa75b8d7..602376d5fd0d 100644
--- a/usr.sbin/gstat/gstat.8
+++ b/usr.sbin/gstat/gstat.8
@@ -78,6 +78,12 @@ Only devices with the names matching
will be displayed.
The format of the regular expression is described in
.Xr re_format 7 .
+.It Fl g
+Show Gigabytes
+.It Fl k
+Show kilobytes
+.It Fl m
+Show Megabytes
.It Fl I Ar interval
Refresh the
.Nm
@@ -96,7 +102,7 @@ Enable display of statistics for other operations
.It Fl p
Only display physical providers (those with rank of 1).
.It Fl s
-Enable blocks' size statistics.
+Enable average block size.
.El
.Sh INTERACTIVE COMMANDS
These commands are currently recognized if
@@ -107,17 +113,33 @@ is not running in batch mode:
Halve the update interval.
.It Ic >
Double the update interval.
+.It Ic 1
+Set update interval to one second
.It Ic c
Toggle display of
.Xr geom 4
consumers.
+.It Ic d
+Toggle display BIO_DELETE
.It Ic f
Display only devices with the names matching regular expression filter
(prompt for filter).
.It Ic F
Remove device filter.
+.It Ic g
+Show gigabytes.
+.It Ic k
+Show kilobytes.
+.It Ic m
+Show megabytes.
+.It Ic o
+Toggle display other (BIO_FLUSH) operations.
+.It Ic p
+Toggle display other only primary providers.
.It Ic q
Quit
+.It Ic s
+Toggle average block size.
.Nm .
.El
.Sh EXIT STATUS
diff --git a/usr.sbin/gstat/gstat.c b/usr.sbin/gstat/gstat.c
index c650f7120a40..2b991788cd9e 100644
--- a/usr.sbin/gstat/gstat.c
+++ b/usr.sbin/gstat/gstat.c
@@ -35,6 +35,7 @@
#include <sys/resource.h>
#include <sys/time.h>
+#include <assert.h>
#include <curses.h>
#include <devstat.h>
#include <err.h>
@@ -44,6 +45,7 @@
#include <libgeom.h>
#include <paths.h>
#include <regex.h>
+#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -51,20 +53,190 @@
#include <sysexits.h>
#include <unistd.h>
+#define HIGH_PCT_BUSY_THRESH 80
+#define MEDIUM_PCT_BUSY_THRESH 50
+
static int flag_a, flag_b, flag_B, flag_c, flag_C, flag_d, flag_o, flag_p,
flag_s;
static int flag_I = 1000000;
-#define HIGH_PCT_BUSY_THRESH 80
-#define MEDIUM_PCT_BUSY_THRESH 50
-#define PRINTMSG(...) do { \
- if ((flag_b && !loop) || (flag_B)) \
- printf(__VA_ARGS__); \
- else if (!flag_b) \
- printw(__VA_ARGS__); \
- } while(0)
+static int quit;
+static int max_flen;
+static History *hist;
+static EditLine *el;
+static HistEvent hist_ev;
+static regex_t f_re;
+static char pf_s[100];
+static char f_s[100];
+static uintmax_t loop;
+static double dt;
+static double byte_scale = 1024;
+static const char *byte_unit = "k";
+
+struct kstat {
+ long double transfers_per_second;
+ long double mb_per_second;
+ long double ms_per_transaction;
+ long double kb_per_transfer;
+};
+
+static void __dead2
+usage(void)
+{
+ fprintf(stderr,
+ "usage: gstat [-abBcCdgkmops] [-f filter] [-I interval]\n");
+ exit(EX_USAGE);
+ /* NOTREACHED */
+}
+
+static void
+use_kb(void)
+{
+ byte_scale = 1024;
+ byte_unit = "k";
+}
+
+static void
+use_mb(void)
+{
+ byte_scale = 1;
+ byte_unit = "M";
+}
+
+static void
+use_gb(void)
+{
+ byte_scale = 1.0/1024;
+ byte_unit = "G";
+}
+
+static void
+printer(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ if (flag_B || (flag_b && loop == 2))
+ vprintf(fmt, ap);
+ if (!flag_b)
+ vw_printw(stdscr, fmt, ap);
+ va_end(ap);
+}
+
+static void
+text_fld_header(const char *k)
+{
+ if (flag_s)
+ printer(" %s/s kB %sBps ms/%s ", k, byte_unit, k);
+ else
+ printer(" %s/s %sBps ms/%s ", k, byte_unit, k);
+}
+
+static void
+text_header(void)
+{
+ int curx = 0, cury = 0;
+ int maxx = 0, maxy = 0;
+
+ if (!flag_b)
+ move(0,0);
+ printer("dT: %5.3fs w: %.3fs", dt,
+ (float)flag_I / 1000000);
+ if (f_s[0] != '\0') {
+ printer(" filter: ");
+ if (!flag_b) {
+ getyx(stdscr, cury, curx);
+ (void)cury;
+ getmaxyx(stdscr, maxy, maxx);
+ (void)maxy;
+ }
+ strlcpy(pf_s, f_s, sizeof(pf_s));
+ max_flen = maxx - curx - 1;
+ if ((int)strlen(f_s) > max_flen && max_flen >= 0) {
+ if (max_flen > 3)
+ pf_s[max_flen - 3] = '.';
+ if (max_flen > 2)
+ pf_s[max_flen - 2] = '.';
+ if (max_flen > 1)
+ pf_s[max_flen - 1] = '.';
+ pf_s[max_flen] = '\0';
+ }
+ printer("%s", pf_s);
+ }
+
+ if (!flag_b)
+ clrtoeol();
+ printer("\n");
+
+ printer(" L(q) ops/s ");
+ text_fld_header("r");
+ text_fld_header("w");
+ if (flag_d)
+ text_fld_header("d");
+ if (flag_o)
+ printer(" o/s ms/o ");
+ printer("%%busy Name");
+ if (!flag_b)
+ clrtoeol();
+ printer("\n");
+}
+
+static void
+print_kstat_text(const struct kstat *ks, int other)
+{
+ printer(" %6.0f", (double)ks->transfers_per_second);
+ if (flag_s && !other)
+ printer(" %6.0f", (double)ks->kb_per_transfer);
+ if (!other)
+ printer(" %6.0f", (double)ks->mb_per_second * byte_scale);
+ if (ks->ms_per_transaction < 100)
+ printer(" %6.3f", (double)ks->ms_per_transaction);
+ else if (ks->ms_per_transaction < 1000)
+ printer(" %6.2f", (double)ks->ms_per_transaction);
+ else if (ks->ms_per_transaction < 10000)
+ printer(" %6.1f", (double)ks->ms_per_transaction);
+ else
+ printer(" %6.0f", (double)ks->ms_per_transaction);
+}
+
+static void
+print_head_csv(void)
+{
+
+ printf("timestamp,name,q-depth,total_ops/s");
+ if (flag_s) {
+ printf(",read/s,read_sz-KiB");
+ printf(",read-KiB/s,ms/read");
+ printf(",write/s,write_sz-KiB");
+ printf(",write-KiB/s,ms/write");
+ } else {
+ printf(",read/s,read-KiB/s,ms/read");
+ printf(",write/s,write-KiB/s,ms/write");
+ }
+ if (flag_d) {
+ if (flag_s) {
+ printf(",delete/s,delete-sz-KiB");
+ printf(",delete-KiB/s,ms/delete");
+ } else {
+ printf(",delete/s,delete-KiB/s");
+ printf(",ms/delete");
+ }
+ }
+ if (flag_o)
+ printf(",other/s,ms/other");
+ printf(",%%busy\n");
+}
+
+static void
+print_kstat_csv(const struct kstat *ks, int other)
+{
-static void usage(void) __dead2;
+ printf(",%.0f", (double)ks->transfers_per_second);
+ if (flag_s && !other)
+ printf(",%.0f", (double)ks->kb_per_transfer);
+ if (!other)
+ printf(",%.0f", (double)ks->mb_per_second * 1024);
+ printf(",%.3f", (double)ks->ms_per_transaction);
+}
static const char*
el_prompt(void)
@@ -73,42 +245,166 @@ el_prompt(void)
return ("Filter: ");
}
+static void
+setup_interactive(void)
+{
+ short cf, cb;
+
+ /* Setup libedit */
+ hist = history_init();
+ if (hist == NULL)
+ err(EX_SOFTWARE, "history_init()");
+ history(hist, &hist_ev, H_SETSIZE, 100);
+ el = el_init("gstat", stdin, stdout, stderr);
+ if (el == NULL)
+ err(EX_SOFTWARE, "el_init");
+ el_set(el, EL_EDITOR, "emacs");
+ el_set(el, EL_SIGNAL, 1);
+ el_set(el, EL_HIST, history, hist);
+ el_set(el, EL_PROMPT, el_prompt);
+ if (f_s[0] != '\0')
+ history(hist, &hist_ev, H_ENTER, f_s);
+ /* Setup curses */
+ initscr();
+ start_color();
+ use_default_colors();
+ pair_content(0, &cf, &cb);
+ init_pair(1, COLOR_GREEN, cb);
+ init_pair(2, COLOR_MAGENTA, cb);
+ init_pair(3, COLOR_RED, cb);
+ cbreak();
+ noecho();
+ nonl();
+ nodelay(stdscr, 1);
+ intrflush(stdscr, FALSE);
+ keypad(stdscr, TRUE);
+}
+
+static int
+poll_input(void)
+{
+ int i, line_len;
+ char *p;
+ const char *line;
+ char tmp_f_s[100];
+ regex_t tmp_f_re;
+
+ i = getch();
+ if (i == ERR)
+ return (0);
+ switch (i) {
+ case '>':
+ flag_I *= 2;
+ break;
+ case '<':
+ flag_I /= 2;
+ if (flag_I < 1000)
+ flag_I = 1000;
+ break;
+ case '1':
+ flag_I = 1000000;
+ break;
+ case 'c':
+ flag_c = !flag_c;
+ break;
+ case 'd':
+ flag_d = !flag_d;
+ break;
+ case 'o':
+ flag_o = !flag_o;
+ break;
+ case 'f':
+ move(0,0);
+ clrtoeol();
+ refresh();
+ line = el_gets(el, &line_len);
+ if (line == NULL)
+ err(1, "el_gets");
+ if (line_len > 1)
+ history(hist, &hist_ev, H_ENTER, line);
+ strlcpy(tmp_f_s, line, sizeof(f_s));
+ if ((p = strchr(tmp_f_s, '\n')) != NULL)
+ *p = '\0';
+ /*
+ * Fix the terminal. We messed up
+ * curses idea of the screen by using
+ * libedit.
+ */
+ clear();
+ refresh();
+ cbreak();
+ noecho();
+ nonl();
+ if (regcomp(&tmp_f_re, tmp_f_s, REG_EXTENDED)
+ != 0) {
+ move(0, 0);
+ printw("Invalid filter");
+ refresh();
+ sleep(1);
+ } else {
+ strlcpy(f_s, tmp_f_s, sizeof(f_s));
+ f_re = tmp_f_re;
+ }
+ break;
+ case 'F':
+ f_s[0] = '\0';
+ break;
+ case 'g':
+ case 'G':
+ use_gb();
+ break;
+ case 'k':
+ case 'K':
+ use_kb();
+ break;
+ case 'm':
+ case 'M':
+ use_mb();
+ break;
+ case 'q':
+ quit = 1;
+ break;
+ case 'p':
+ flag_p = !flag_p;
+ break;
+ case 's':
+ flag_s = !flag_s;
+ break;
+ default:
+ break;
+ }
+ return (1);
+}
+
int
main(int argc, char **argv)
{
- int error, i, quit;
- int curx, cury, maxx, maxy, line_len, loop, max_flen, head_printed;
+ int error, i;
+ int curx, cury, maxx, maxy;
struct devstat *gsp, *gsq;
void *sp, *sq;
- double dt;
struct timespec tp, tq;
struct gmesh gmp;
struct gprovider *pp;
struct gconsumer *cp;
struct gident *gid;
- regex_t f_re, tmp_f_re;
- short cf, cb;
char *p;
- char f_s[100], pf_s[100], tmp_f_s[100];
- char ts[100], g_name[4096];
- const char *line;
- long double ld[16];
- uint64_t u64;
- EditLine *el;
- History *hist;
- HistEvent hist_ev;
+ char ts[100], ts2[100], g_name[4096];
+ long double xfer_per_sec, busy_pct;
+ struct kstat krd, kwr, kfree, kother;
+ uint64_t queue_len;
+ memset(&kother, 0, sizeof kother);
hist = NULL;
el = NULL;
- maxx = -1;
curx = -1;
- loop = 1;
+ loop = 0;
/* Turn on batch mode if output is not tty. */
if (!isatty(fileno(stdout)))
flag_b = 1;
f_s[0] = '\0';
- while ((i = getopt(argc, argv, "abBdcCf:I:ops")) != -1) {
+ while ((i = getopt(argc, argv, "abBcdCf:gI:kmops")) != -1) {
switch (i) {
case 'a':
flag_a = 1;
@@ -128,7 +424,6 @@ main(int argc, char **argv)
/* csv out implies repeating batch mode */
flag_b = 1;
flag_B = 1;
- head_printed = 0;
break;
case 'd':
flag_d = 1;
@@ -141,6 +436,9 @@ main(int argc, char **argv)
"Invalid filter - see re_format(7)");
strlcpy(f_s, optarg, sizeof(f_s));
break;
+ case 'g':
+ use_gb();
+ break;
case 'I':
p = NULL;
i = strtoul(optarg, &p, 0);
@@ -155,6 +453,12 @@ main(int argc, char **argv)
i *= 1;
flag_I = i;
break;
+ case 'k':
+ use_kb();
+ break;
+ case 'm':
+ use_mb();
+ break;
case 'o':
flag_o = 1;
break;
@@ -180,42 +484,16 @@ main(int argc, char **argv)
error = geom_stats_open();
if (error)
err(1, "geom_stats_open()");
- sq = NULL;
sq = geom_stats_snapshot_get();
if (sq == NULL)
err(1, "geom_stats_snapshot()");
- if (!flag_b) {
- /* Setup libedit */
- hist = history_init();
- if (hist == NULL)
- err(EX_SOFTWARE, "history_init()");
- history(hist, &hist_ev, H_SETSIZE, 100);
- el = el_init("gstat", stdin, stdout, stderr);
- if (el == NULL)
- err(EX_SOFTWARE, "el_init");
- el_set(el, EL_EDITOR, "emacs");
- el_set(el, EL_SIGNAL, 1);
- el_set(el, EL_HIST, history, hist);
- el_set(el, EL_PROMPT, el_prompt);
- if (f_s[0] != '\0')
- history(hist, &hist_ev, H_ENTER, f_s);
- /* Setup curses */
- initscr();
- start_color();
- use_default_colors();
- pair_content(0, &cf, &cb);
- init_pair(1, COLOR_GREEN, cb);
- init_pair(2, COLOR_MAGENTA, cb);
- init_pair(3, COLOR_RED, cb);
- cbreak();
- noecho();
- nonl();
- nodelay(stdscr, 1);
- intrflush(stdscr, FALSE);
- keypad(stdscr, TRUE);
- }
+ if (!flag_b)
+ setup_interactive();
+ if (flag_C)
+ print_head_csv();
geom_stats_snapshot_timestamp(sq, &tq);
for (quit = 0; !quit;) {
+ loop += 1;
sp = geom_stats_snapshot_get();
if (sp == NULL)
err(1, "geom_stats_snapshot()");
@@ -224,84 +502,17 @@ main(int argc, char **argv)
dt += (tp.tv_nsec - tq.tv_nsec) * 1e-9;
tq = tp;
if (flag_C) { /* set timestamp string */
- (void)strftime(ts,sizeof(ts),
- "%F %T",localtime(&tq.tv_sec));
- (void)snprintf(ts,sizeof(ts),
- "%s.%.9ld",ts,tq.tv_nsec);
+ (void)strftime(ts2, sizeof(ts2),
+ "%F %T", localtime(&tq.tv_sec));
+ assert(strlen(ts2) < 50);
+ (void)snprintf(ts, sizeof(ts),
+ "%s.%.9ld" ,ts2,tq.tv_nsec);
}
-
+ if (loop > 1 && !flag_C)
+ text_header();
+
geom_stats_snapshot_reset(sp);
geom_stats_snapshot_reset(sq);
- if (!flag_b)
- move(0,0);
- if (!flag_C)
- PRINTMSG("dT: %5.3fs w: %.3fs", dt,
- (float)flag_I / 1000000);
- if (!flag_C && f_s[0] != '\0') {
- PRINTMSG(" filter: ");
- if (!flag_b) {
- getyx(stdscr, cury, curx);
- getmaxyx(stdscr, maxy, maxx);
- }
- strlcpy(pf_s, f_s, sizeof(pf_s));
- max_flen = maxx - curx - 1;
- if ((int)strlen(f_s) > max_flen && max_flen >= 0) {
- if (max_flen > 3)
- pf_s[max_flen - 3] = '.';
- if (max_flen > 2)
- pf_s[max_flen - 2] = '.';
- if (max_flen > 1)
- pf_s[max_flen - 1] = '.';
- pf_s[max_flen] = '\0';
- }
- PRINTMSG("%s", pf_s);
- }
- if (!flag_C) {
- PRINTMSG("\n");
- PRINTMSG(" L(q) ops/s ");
- if (flag_s) {
- PRINTMSG(" r/s kB kBps ms/r ");
- PRINTMSG(" w/s kB kBps ms/w ");
- }
- else {
- PRINTMSG(" r/s kBps ms/r ");
- PRINTMSG(" w/s kBps ms/w ");
- }
- if (flag_d) {
- if (flag_s) {
- PRINTMSG(" d/s kB kBps");
- PRINTMSG(" ms/d ");
- } else
- PRINTMSG(" d/s kBps ms/d ");
- }
- if (flag_o)
- PRINTMSG(" o/s ms/o ");
- PRINTMSG("%%busy Name\n");
- } else if (flag_C && !head_printed) {
- PRINTMSG("timestamp,name,q-depth,total_ops/s");
- if (flag_s) {
- PRINTMSG(",read/s,read_sz-KiB");
- PRINTMSG(",read-KiB/s,ms/read");
- PRINTMSG(",write/s,write_sz-KiB");
- PRINTMSG(",write-KiB/s,ms/write");
- } else {
- PRINTMSG(",read/s,read-KiB/s,ms/read");
- PRINTMSG(",write/s,write-KiB/s,ms/write");
- }
- if (flag_d) {
- if (flag_s) {
- PRINTMSG(",delete/s,delete-sz-KiB");
- PRINTMSG(",delete-KiB/s,ms/delete");
- } else {
- PRINTMSG(",delete/s,delete-KiB/s");
- PRINTMSG(",ms/delete");
- }
- }
- if (flag_o)
- PRINTMSG(",other/s,ms/other");
- PRINTMSG(",%%busy\n");
- head_printed = 1;
- }
for (;;) {
gsp = geom_stats_snapshot_next(sp);
gsq = geom_stats_snapshot_next(sq);
@@ -339,50 +550,48 @@ main(int argc, char **argv)
continue;
}
if (gsp->sequence0 != gsp->sequence1) {
- /*
+ /*
* it is ok to skip entire line silently
* for CSV output
*/
if (!flag_C)
- PRINTMSG("*\n");
+ printer("*\n");
continue;
}
- devstat_compute_statistics(gsp, gsq, dt,
- DSM_QUEUE_LENGTH, &u64,
- DSM_TRANSFERS_PER_SECOND, &ld[0],
-
- DSM_TRANSFERS_PER_SECOND_READ, &ld[1],
- DSM_MB_PER_SECOND_READ, &ld[2],
- DSM_MS_PER_TRANSACTION_READ, &ld[3],
- DSM_TRANSFERS_PER_SECOND_WRITE, &ld[4],
- DSM_MB_PER_SECOND_WRITE, &ld[5],
- DSM_MS_PER_TRANSACTION_WRITE, &ld[6],
+#define CONVENIENCE(oper, kst) \
+ DSM_TRANSFERS_PER_SECOND_##oper, &kst.transfers_per_second, \
+ DSM_MB_PER_SECOND_##oper, &kst.mb_per_second, \
+ DSM_MS_PER_TRANSACTION_##oper, &kst.ms_per_transaction, \
+ DSM_KB_PER_TRANSFER_##oper, &kst.kb_per_transfer,
- DSM_BUSY_PCT, &ld[7],
+ devstat_compute_statistics(gsp, gsq, dt,
+ DSM_QUEUE_LENGTH, &queue_len,
+ DSM_TRANSFERS_PER_SECOND, &xfer_per_sec,
- DSM_TRANSFERS_PER_SECOND_FREE, &ld[8],
- DSM_MB_PER_SECOND_FREE, &ld[9],
- DSM_MS_PER_TRANSACTION_FREE, &ld[10],
+ CONVENIENCE(READ, krd)
+ CONVENIENCE(WRITE, kwr)
+ CONVENIENCE(FREE, kfree)
- DSM_TRANSFERS_PER_SECOND_OTHER, &ld[11],
- DSM_MS_PER_TRANSACTION_OTHER, &ld[12],
+ DSM_BUSY_PCT, &busy_pct,
- DSM_KB_PER_TRANSFER_READ, &ld[13],
- DSM_KB_PER_TRANSFER_WRITE, &ld[14],
- DSM_KB_PER_TRANSFER_FREE, &ld[15],
+ DSM_TRANSFERS_PER_SECOND_OTHER,
+ &kother.transfers_per_second,
+ DSM_MS_PER_TRANSACTION_OTHER,
+ &kother.ms_per_transaction,
DSM_NONE);
- if (flag_a && ld[7] < 0.1) {
- *gsq = *gsp;
+ *gsq = *gsp;
+ if (loop == 1)
+ continue;
+
+ if (flag_a && busy_pct < 0.1) {
continue;
}
/* store name for geom device */
- if (gid == NULL) {
- (void)snprintf(g_name, sizeof(g_name), "??");
- } else if (gid->lg_what == ISPROVIDER) {
+ if (gid->lg_what == ISPROVIDER) {
pp = gid->lg_ptr;
(void)snprintf(g_name, sizeof(g_name), "%s",
pp->lg_name);
@@ -391,231 +600,82 @@ main(int argc, char **argv)
(void)snprintf(g_name, sizeof(g_name),
"%s/%s/%s",
cp->lg_geom->lg_class->lg_name,
- cp->lg_geom->lg_name,
- cp->lg_provider->lg_name);
+ cp->lg_geom->lg_name,
+ cp->lg_provider->lg_name);
}
-
- if (flag_C) {
- PRINTMSG("%s", ts); /* timestamp */
- PRINTMSG(",%s", g_name); /* print name */
- PRINTMSG(",%ju", (uintmax_t)u64);
- PRINTMSG(",%.0f", (double)ld[0]);
- PRINTMSG(",%.0f", (double)ld[1]);
- if (flag_s)
- PRINTMSG(",%.0f", (double)ld[13]);
- PRINTMSG(",%.0f", (double)ld[2] * 1024);
- if (ld[3] > 1e3)
- PRINTMSG(",%.0f", (double)ld[3]);
- else if (ld[3] > 1e0)
- PRINTMSG(",%.1f", (double)ld[3]);
- else
- PRINTMSG(",%.3f", (double)ld[3]);
- PRINTMSG(",%.0f", (double)ld[4]);
- if (flag_s)
- PRINTMSG(",%.0f", (double)ld[14]);
- PRINTMSG(",%.0f", (double)ld[5] * 1024);
- if (ld[6] > 1e3)
- PRINTMSG(",%.0f", (double)ld[6]);
- else if (ld[6] > 1e0)
- PRINTMSG(",%.1f", (double)ld[6]);
- else
- PRINTMSG(",%.3f", (double)ld[6]);
- if (flag_d) {
- PRINTMSG(",%.0f", (double)ld[8]);
- if (flag_s)
- PRINTMSG(",%.0f",
- (double)ld[15]);
- PRINTMSG(",%.0f", (double)ld[9] * 1024);
- if (ld[10] > 1e3)
- PRINTMSG(",%.0f",
- (double)ld[10]);
- else if (ld[10] > 1e0)
- PRINTMSG(",%.1f",
- (double)ld[10]);
- else
- PRINTMSG(",%.3f",
- (double)ld[10]);
- }
-
- if (flag_o) {
- PRINTMSG(",%.0f", (double)ld[11]);
- if (ld[12] > 1e3)
- PRINTMSG(",%.0f",
- (double)ld[12]);
- else if (ld[12] > 1e0)
- PRINTMSG(",%.1f",
- (double)ld[12]);
- else
- PRINTMSG(",%.3f",
- (double)ld[12]);
- }
- PRINTMSG(",%.1lf", (double)ld[7]);
+ if (flag_C) {
+ printf("%s", ts); /* timestamp */
+ printf(",%s", g_name); /* print name */
+ printf(",%ju", (uintmax_t)queue_len);
+ printf(",%.0f", (double)xfer_per_sec);
+ print_kstat_csv(&krd, 0);
+ print_kstat_csv(&kwr, 0);
+ if (flag_d)
+ print_kstat_csv(&kfree, 0);
+ if (flag_o)
+ print_kstat_csv(&kother, 1);
+ printf(",%.1lf\n", (double)busy_pct);
} else {
- PRINTMSG(" %4ju", (uintmax_t)u64);
- PRINTMSG(" %6.0f", (double)ld[0]);
- PRINTMSG(" %6.0f", (double)ld[1]);
- if (flag_s)
- PRINTMSG(" %6.0f", (double)ld[13]);
- PRINTMSG(" %6.0f", (double)ld[2] * 1024);
- if (ld[3] > 1e3)
- PRINTMSG(" %6.0f", (double)ld[3]);
- else if (ld[3] > 1e0)
- PRINTMSG(" %6.1f", (double)ld[3]);
- else
- PRINTMSG(" %6.3f", (double)ld[3]);
- PRINTMSG(" %6.0f", (double)ld[4]);
- if (flag_s)
- PRINTMSG(" %6.0f", (double)ld[14]);
- PRINTMSG(" %6.0f", (double)ld[5] * 1024);
- if (ld[6] > 1e3)
- PRINTMSG(" %6.0f", (double)ld[6]);
- else if (ld[6] > 1e0)
- PRINTMSG(" %6.1f", (double)ld[6]);
- else
- PRINTMSG(" %6.3f", (double)ld[6]);
+ printer(" %4ju", (uintmax_t)queue_len);
+ printer(" %6.0f", (double)xfer_per_sec);
- if (flag_d) {
- PRINTMSG(" %6.0f", (double)ld[8]);
- if (flag_s)
- PRINTMSG(" %6.0f",
- (double)ld[15]);
- PRINTMSG(" %6.0f",
- (double)ld[9] * 1024);
- if (ld[10] > 1e3)
- PRINTMSG(" %6.0f",
- (double)ld[10]);
- else if (ld[10] > 1e0)
- PRINTMSG(" %6.1f",
- (double)ld[10]);
- else
- PRINTMSG(" %6.3f",
- (double)ld[10]);
- }
-
- if (flag_o) {
- PRINTMSG(" %6.0f", (double)ld[11]);
- if (ld[12] > 1e3)
- PRINTMSG(" %6.0f",
- (double)ld[12]);
- else if (ld[12] > 1e0)
- PRINTMSG(" %6.1f",
- (double)ld[12]);
+ print_kstat_text(&krd, 0);
+ print_kstat_text(&kwr, 0);
+ if (flag_d)
+ print_kstat_text(&kfree, 0);
+ if (flag_o)
+ print_kstat_text(&kother, 1);
+ if (!flag_b) {
+ if (busy_pct > HIGH_PCT_BUSY_THRESH)
+ i = 3;
+ else if (busy_pct > MEDIUM_PCT_BUSY_THRESH)
+ i = 2;
else
- PRINTMSG(" %6.3f",
- (double)ld[12]);
+ i = 1;
+ attron(COLOR_PAIR(i));
}
-
- if (ld[7] > HIGH_PCT_BUSY_THRESH)
- i = 3;
- else if (ld[7] > MEDIUM_PCT_BUSY_THRESH)
- i = 2;
- else
- i = 1;
+ printer(" %6.1lf", (double)busy_pct);
if (!flag_b)
- attron(COLOR_PAIR(i));
- PRINTMSG(" %6.1lf", (double)ld[7]);
- if (!flag_b) {
attroff(COLOR_PAIR(i));
- PRINTMSG("|");
- } else
- PRINTMSG(" ");
- PRINTMSG(" %s", g_name);
+
+ printer(" %s", g_name);
if (!flag_b)
clrtoeol();
+ printer("\n");
}
- PRINTMSG("\n");
- *gsq = *gsp;
}
geom_stats_snapshot_free(sp);
- if (flag_b) {
- /* We loop extra to make sure we get the information. */
- if (!loop)
+ if (flag_b || flag_C) {
+ if (!flag_B && !flag_C && loop == 2)
break;
- if (!flag_B)
- loop = 0;
- else
- if (fflush(stdout) == EOF)
- goto out;
- usleep(flag_I);
- continue;
- }
- getyx(stdscr, cury, curx);
- getmaxyx(stdscr, maxy, maxx);
- clrtobot();
- if (maxy - 1 <= cury)
- move(maxy - 1, 0);
- refresh();
- usleep(flag_I);
- while((i = getch()) != ERR) {
- switch (i) {
- case '>':
- flag_I *= 2;
- break;
- case '<':
- flag_I /= 2;
- if (flag_I < 1000)
- flag_I = 1000;
- break;
- case 'c':
- flag_c = !flag_c;
- break;
- case 'f':
- move(0,0);
- clrtoeol();
- refresh();
- line = el_gets(el, &line_len);
- if (line == NULL)
- err(1, "el_gets");
- if (line_len > 1)
- history(hist, &hist_ev, H_ENTER, line);
- strlcpy(tmp_f_s, line, sizeof(f_s));
- if ((p = strchr(tmp_f_s, '\n')) != NULL)
- *p = '\0';
- /*
- * Fix the terminal. We messed up
- * curses idea of the screen by using
- * libedit.
- */
- clear();
- refresh();
- cbreak();
- noecho();
- nonl();
- if (regcomp(&tmp_f_re, tmp_f_s, REG_EXTENDED)
- != 0) {
- move(0, 0);
- printw("Invalid filter");
- refresh();
- sleep(1);
- } else {
- strlcpy(f_s, tmp_f_s, sizeof(f_s));
- f_re = tmp_f_re;
- }
- break;
- case 'F':
- f_s[0] = '\0';
- break;
- case 'q':
- quit = 1;
- break;
- default:
+ if (fflush(stdout) == EOF)
break;
+ usleep(flag_I);
+ } else {
+ getyx(stdscr, cury, curx);
+ getmaxyx(stdscr, maxy, maxx);
+ (void)maxx;
+ clrtobot();
+ if (maxy - 1 <= cury)
+ move(maxy - 1, 0);
+ refresh();
+
+ int di = flag_I;
+ while (di > 0) {
+ int dii = di;
+ if (dii > 100000)
+ dii = 100000;
+ usleep(dii);
+ di -= dii;
+ if (poll_input())
+ break;
}
}
}
-out:
if (!flag_b) {
el_end(el);
endwin();
}
exit(EX_OK);
}
-
-static void
-usage(void)
-{
- fprintf(stderr, "usage: gstat [-abBcCdops] [-f filter] [-I interval]\n");
- exit(EX_USAGE);
- /* NOTREACHED */
-}
diff --git a/usr.sbin/ifmcstat/ifmcstat.8 b/usr.sbin/ifmcstat/ifmcstat.8
index aa148e802ea7..6ddb991072da 100644
--- a/usr.sbin/ifmcstat/ifmcstat.8
+++ b/usr.sbin/ifmcstat/ifmcstat.8
@@ -28,7 +28,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd May 27, 2009
+.Dd March 18, 2026
.Dt IFMCSTAT 8
.Os
.Sh NAME
@@ -39,9 +39,6 @@
.Op Fl i Ar interface
.Op Fl f Ar address-family
.Op Fl v
-.Op Fl K
-.Op Fl M Ar core
-.Op Fl N Ar system
.\"
.Sh DESCRIPTION
The
@@ -65,30 +62,6 @@ they are suppressed by default.
It may not be specified for
.Fl f Ar link .
Source lists for each group will also be printed.
-.Pp
-If specified twice, and
-.Xr kvm 3
-is in use, the control plane timers for each interface
-and the source list counters for each group
-will also be printed.
-.El
-.Pp
-The following options are only available if
-.Nm
-has been built with support for
-.Xr kvm 3 :
-.Bl -tag -width Fl
-.It Fl K
-attempts to use
-.Xr kvm 3
-to retrieve the multicast group information.
-.It Fl M Ar core
-extracts values associated with the name list from the specified core,
-instead of the default
-.Pa /dev/kmem .
-.It Fl N Ar system
-extracts the name list from the specified kernel instead of the
-default, which is the kernel image the system has booted from.
.El
.Sh IMPLEMENTATION NOTES
.Nm
@@ -102,26 +75,7 @@ option,
.Nm
may print multicast MAC addresses twice if they are
referenced by a layer 3 protocol.
-.Pp
-When run with
-.Xr kvm 3
-support,
-the names of all interfaces configured in the system will be
-printed in the first column of output, even if no multicast
-group memberships are present on those interfaces.
-The output may also be slightly different, as the kernel
-data structures are being traversed with minimal post-processing
-of the output.
-.Pp
-When built without
-.Xr kvm 3
-support, the information displayed by
-.Nm
-is more limited.
-This support is recommended for debugging purposes.
-It requires super-user privilege if used to inspect a running kernel.
.Sh SEE ALSO
.Xr netstat 1 ,
.Xr getifaddrs 3 ,
.Xr getifmaddrs 3 ,
-.Xr kvm 3
diff --git a/usr.sbin/ifmcstat/ifmcstat.c b/usr.sbin/ifmcstat/ifmcstat.c
index 503c1000d82d..f9745bb1064c 100644
--- a/usr.sbin/ifmcstat/ifmcstat.c
+++ b/usr.sbin/ifmcstat/ifmcstat.c
@@ -76,17 +76,6 @@
#include <sysexits.h>
#include <unistd.h>
-#ifdef KVM
-/*
- * Currently the KVM build is broken. To be fixed it requires uncovering
- * large amount of _KERNEL code in include files, and it is also very
- * tentative to internal kernel ABI changes. If anyone wishes to restore
- * it, please move it out of src/usr.sbin to src/tools/tools.
- */
-#include <kvm.h>
-#include <nlist.h>
-#endif
-
/* XXX: This file currently assumes INET support in the base system. */
#ifndef INET
#define INET
@@ -109,9 +98,6 @@ typedef union sockunion sockunion_t;
uint32_t ifindex = 0;
int af = AF_UNSPEC;
-#ifdef WITH_KVM
-int Kflag = 0;
-#endif
int vflag = 0;
#define sa_dl_equal(a1, a2) \
@@ -121,41 +107,6 @@ int vflag = 0;
LLADDR((struct sockaddr_dl *)(a2)), \
((struct sockaddr_dl *)(a1))->sdl_alen) == 0))
-/*
- * Most of the code in this utility is to support the use of KVM for
- * post-mortem debugging of the multicast code.
- */
-#ifdef WITH_KVM
-
-#ifdef INET
-static void if_addrlist(struct ifaddr *);
-static struct in_multi *
- in_multientry(struct in_multi *);
-#endif /* INET */
-
-#ifdef INET6
-static void if6_addrlist(struct ifaddr *);
-static struct in6_multi *
- in6_multientry(struct in6_multi *);
-#endif /* INET6 */
-
-static void kread(u_long, void *, int);
-static void ll_addrlist(struct ifaddr *);
-
-static int ifmcstat_kvm(const char *kernel, const char *core);
-
-#define KREAD(addr, buf, type) \
- kread((u_long)addr, (void *)buf, sizeof(type))
-
-kvm_t *kvmd;
-struct nlist nl[] = {
- { "_ifnet", 0, 0, 0, 0, },
- { "", 0, 0, 0, 0, },
-};
-#define N_IFNET 0
-
-#endif /* WITH_KVM */
-
static int ifmcstat_getifmaddrs(void);
#ifdef INET
static void in_ifinfo(struct igmp_ifinfo *);
@@ -172,29 +123,16 @@ usage()
{
fprintf(stderr,
- "usage: ifmcstat [-i interface] [-f address family]"
- " [-v]"
-#ifdef WITH_KVM
- " [-K] [-M core] [-N system]"
-#endif
- "\n");
+ "usage: ifmcstat [-i interface] [-f address family] [-v]\n");
exit(EX_USAGE);
}
-static const char *options = "i:f:vM:N:"
-#ifdef WITH_KVM
- "K"
-#endif
- ;
+static const char *options = "i:f:v";
int
main(int argc, char **argv)
{
int c, error;
-#ifdef WITH_KVM
- const char *kernel = NULL;
- const char *core = NULL;
-#endif
while ((c = getopt(argc, argv, options)) != -1) {
switch (c) {
@@ -228,26 +166,10 @@ main(int argc, char **argv)
/*NOTREACHED*/
break;
-#ifdef WITH_KVM
- case 'K':
- ++Kflag;
- break;
-#endif
-
case 'v':
++vflag;
break;
-#ifdef WITH_KVM
- case 'M':
- core = strdup(optarg);
- break;
-
- case 'N':
- kernel = strdup(optarg);
- break;
-#endif
-
default:
usage();
break;
@@ -258,15 +180,6 @@ main(int argc, char **argv)
if (af == AF_LINK && vflag)
usage();
-#ifdef WITH_KVM
- if (Kflag)
- error = ifmcstat_kvm(kernel, core);
- /*
- * If KVM failed, and user did not explicitly specify a core file,
- * or force KVM backend to be disabled, try the sysctl backend.
- */
- if (!Kflag || (error != 0 && (core == NULL && kernel == NULL)))
-#endif
error = ifmcstat_getifmaddrs();
if (error != 0)
exit(EX_OSERR);
@@ -322,417 +235,6 @@ inm_mode(u_int mode)
#endif /* INET */
-#ifdef WITH_KVM
-
-static int
-ifmcstat_kvm(const char *kernel, const char *core)
-{
- char buf[_POSIX2_LINE_MAX], ifname[IFNAMSIZ];
- struct ifnet *ifp, *nifp, ifnet;
-
- if ((kvmd = kvm_openfiles(kernel, core, NULL, O_RDONLY, buf)) ==
- NULL) {
- perror("kvm_openfiles");
- return (-1);
- }
- if (kvm_nlist(kvmd, nl) < 0) {
- perror("kvm_nlist");
- return (-1);
- }
- if (nl[N_IFNET].n_value == 0) {
- printf("symbol %s not found\n", nl[N_IFNET].n_name);
- return (-1);
- }
- KREAD(nl[N_IFNET].n_value, &ifp, struct ifnet *);
- while (ifp) {
- KREAD(ifp, &ifnet, struct ifnet);
- nifp = ifnet.if_link.tqe_next;
- if (ifindex && ifindex != ifnet.if_index)
- goto next;
-
- printf("%s:\n", if_indextoname(ifnet.if_index, ifname));
-#ifdef INET
- if_addrlist(TAILQ_FIRST(&ifnet.if_addrhead));
-#endif
-#ifdef INET6
- if6_addrlist(TAILQ_FIRST(&ifnet.if_addrhead));
-#endif
- if (vflag)
- ll_addrlist(TAILQ_FIRST(&ifnet.if_addrhead));
- next:
- ifp = nifp;
- }
-
- return (0);
-}
-
-static void
-kread(u_long addr, void *buf, int len)
-{
-
- if (kvm_read(kvmd, addr, buf, len) != len) {
- perror("kvm_read");
- exit(EX_OSERR);
- }
-}
-
-static void
-ll_addrlist(struct ifaddr *ifap)
-{
- char addrbuf[NI_MAXHOST];
- struct ifaddr ifa;
- struct sockaddr sa;
- struct sockaddr_dl sdl;
- struct ifaddr *ifap0;
-
- if (af && af != AF_LINK)
- return;
-
- ifap0 = ifap;
- while (ifap) {
- KREAD(ifap, &ifa, struct ifaddr);
- if (ifa.ifa_addr == NULL)
- goto nextifap;
- KREAD(ifa.ifa_addr, &sa, struct sockaddr);
- if (sa.sa_family != PF_LINK)
- goto nextifap;
- KREAD(ifa.ifa_addr, &sdl, struct sockaddr_dl);
- if (sdl.sdl_alen == 0)
- goto nextifap;
- addrbuf[0] = '\0';
- getnameinfo((struct sockaddr *)&sdl, sdl.sdl_len,
- addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
- printf("\tlink %s\n", addrbuf);
- nextifap:
- ifap = ifa.ifa_link.tqe_next;
- }
- if (ifap0) {
- struct ifnet ifnet;
- struct ifmultiaddr ifm, *ifmp = 0;
-
- KREAD(ifap0, &ifa, struct ifaddr);
- KREAD(ifa.ifa_ifp, &ifnet, struct ifnet);
- if (TAILQ_FIRST(&ifnet.if_multiaddrs))
- ifmp = TAILQ_FIRST(&ifnet.if_multiaddrs);
- while (ifmp) {
- KREAD(ifmp, &ifm, struct ifmultiaddr);
- if (ifm.ifma_addr == NULL)
- goto nextmulti;
- KREAD(ifm.ifma_addr, &sa, struct sockaddr);
- if (sa.sa_family != AF_LINK)
- goto nextmulti;
- KREAD(ifm.ifma_addr, &sdl, struct sockaddr_dl);
- addrbuf[0] = '\0';
- getnameinfo((struct sockaddr *)&sdl,
- sdl.sdl_len, addrbuf, sizeof(addrbuf),
- NULL, 0, NI_NUMERICHOST);
- printf("\t\tgroup %s refcnt %d\n",
- addrbuf, ifm.ifma_refcount);
- nextmulti:
- ifmp = TAILQ_NEXT(&ifm, ifma_link);
- }
- }
-}
-
-#ifdef INET6
-
-static void
-if6_addrlist(struct ifaddr *ifap)
-{
- struct ifnet ifnet;
- struct ifaddr ifa;
- struct sockaddr sa;
- struct in6_ifaddr if6a;
- struct ifaddr *ifap0;
-
- if (af && af != AF_INET6)
- return;
- ifap0 = ifap;
- while (ifap) {
- KREAD(ifap, &ifa, struct ifaddr);
- if (ifa.ifa_addr == NULL)
- goto nextifap;
- KREAD(ifa.ifa_addr, &sa, struct sockaddr);
- if (sa.sa_family != PF_INET6)
- goto nextifap;
- KREAD(ifap, &if6a, struct in6_ifaddr);
- printf("\tinet6 %s\n", inet6_n2a(&if6a.ia_addr.sin6_addr,
- if6a.ia_addr.sin6_scope_id));
- /*
- * Print per-link MLD information, if available.
- */
- if (ifa.ifa_ifp != NULL) {
- struct in6_ifextra ie;
- struct mld_ifinfo mli;
-
- KREAD(ifa.ifa_ifp, &ifnet, struct ifnet);
- KREAD(ifnet.if_afdata[AF_INET6], &ie,
- struct in6_ifextra);
- if (ie.mld_ifinfo != NULL) {
- KREAD(ie.mld_ifinfo, &mli, struct mld_ifinfo);
- in6_ifinfo(&mli);
- }
- }
- nextifap:
- ifap = ifa.ifa_link.tqe_next;
- }
- if (ifap0) {
- struct ifnet ifnet;
- struct ifmultiaddr ifm, *ifmp = 0;
- struct sockaddr_dl sdl;
-
- KREAD(ifap0, &ifa, struct ifaddr);
- KREAD(ifa.ifa_ifp, &ifnet, struct ifnet);
- if (TAILQ_FIRST(&ifnet.if_multiaddrs))
- ifmp = TAILQ_FIRST(&ifnet.if_multiaddrs);
- while (ifmp) {
- KREAD(ifmp, &ifm, struct ifmultiaddr);
- if (ifm.ifma_addr == NULL)
- goto nextmulti;
- KREAD(ifm.ifma_addr, &sa, struct sockaddr);
- if (sa.sa_family != AF_INET6)
- goto nextmulti;
- (void)in6_multientry((struct in6_multi *)
- ifm.ifma_protospec);
- if (ifm.ifma_lladdr == 0)
- goto nextmulti;
- KREAD(ifm.ifma_lladdr, &sdl, struct sockaddr_dl);
- printf("\t\t\tmcast-macaddr %s refcnt %d\n",
- ether_ntoa((struct ether_addr *)LLADDR(&sdl)),
- ifm.ifma_refcount);
- nextmulti:
- ifmp = TAILQ_NEXT(&ifm, ifma_link);
- }
- }
-}
-
-static struct in6_multi *
-in6_multientry(struct in6_multi *mc)
-{
- struct in6_multi multi;
-
- KREAD(mc, &multi, struct in6_multi);
- printf("\t\tgroup %s", inet6_n2a(&multi.in6m_addr, 0));
- printf(" refcnt %u\n", multi.in6m_refcount);
-
- return (multi.in6m_entry.le_next);
-}
-
-#endif /* INET6 */
-
-#ifdef INET
-
-static void
-if_addrlist(struct ifaddr *ifap)
-{
- struct ifaddr ifa;
- struct ifnet ifnet;
- struct sockaddr sa;
- struct in_ifaddr ia;
- struct ifaddr *ifap0;
-
- if (af && af != AF_INET)
- return;
- ifap0 = ifap;
- while (ifap) {
- KREAD(ifap, &ifa, struct ifaddr);
- if (ifa.ifa_addr == NULL)
- goto nextifap;
- KREAD(ifa.ifa_addr, &sa, struct sockaddr);
- if (sa.sa_family != PF_INET)
- goto nextifap;
- KREAD(ifap, &ia, struct in_ifaddr);
- printf("\tinet %s\n", inet_ntoa(ia.ia_addr.sin_addr));
- /*
- * Print per-link IGMP information, if available.
- */
- if (ifa.ifa_ifp != NULL) {
- struct in_ifinfo ii;
- struct igmp_ifinfo igi;
-
- KREAD(ifa.ifa_ifp, &ifnet, struct ifnet);
- KREAD(ifnet.if_afdata[AF_INET], &ii, struct in_ifinfo);
- if (ii.ii_igmp != NULL) {
- KREAD(ii.ii_igmp, &igi, struct igmp_ifinfo);
- in_ifinfo(&igi);
- }
- }
- nextifap:
- ifap = ifa.ifa_link.tqe_next;
- }
- if (ifap0) {
- struct ifmultiaddr ifm, *ifmp = 0;
- struct sockaddr_dl sdl;
-
- KREAD(ifap0, &ifa, struct ifaddr);
- KREAD(ifa.ifa_ifp, &ifnet, struct ifnet);
- if (TAILQ_FIRST(&ifnet.if_multiaddrs))
- ifmp = TAILQ_FIRST(&ifnet.if_multiaddrs);
- while (ifmp) {
- KREAD(ifmp, &ifm, struct ifmultiaddr);
- if (ifm.ifma_addr == NULL)
- goto nextmulti;
- KREAD(ifm.ifma_addr, &sa, struct sockaddr);
- if (sa.sa_family != AF_INET)
- goto nextmulti;
- (void)in_multientry((struct in_multi *)
- ifm.ifma_protospec);
- if (ifm.ifma_lladdr == 0)
- goto nextmulti;
- KREAD(ifm.ifma_lladdr, &sdl, struct sockaddr_dl);
- printf("\t\t\tmcast-macaddr %s refcnt %d\n",
- ether_ntoa((struct ether_addr *)LLADDR(&sdl)),
- ifm.ifma_refcount);
- nextmulti:
- ifmp = TAILQ_NEXT(&ifm, ifma_link);
- }
- }
-}
-
-static const char *inm_states[] = {
- "not-member",
- "silent",
- "idle",
- "lazy",
- "sleeping",
- "awakening",
- "query-pending",
- "sg-query-pending",
- "leaving"
-};
-
-static const char *
-inm_state(u_int state)
-{
-
- if (state >= IGMP_NOT_MEMBER && state <= IGMP_LEAVING_MEMBER)
- return (inm_states[state]);
- return (NULL);
-}
-
-#if 0
-static struct ip_msource *
-ims_min_kvm(struct in_multi *pinm)
-{
- struct ip_msource ims0;
- struct ip_msource *tmp, *parent;
-
- parent = NULL;
- tmp = RB_ROOT(&pinm->inm_srcs);
- while (tmp) {
- parent = tmp;
- KREAD(tmp, &ims0, struct ip_msource);
- tmp = RB_LEFT(&ims0, ims_link);
- }
- return (parent); /* kva */
-}
-
-/* XXX This routine is buggy. See RB_NEXT in sys/tree.h. */
-static struct ip_msource *
-ims_next_kvm(struct ip_msource *ims)
-{
- struct ip_msource ims0, ims1;
- struct ip_msource *tmp;
-
- KREAD(ims, &ims0, struct ip_msource);
- if (RB_RIGHT(&ims0, ims_link)) {
- ims = RB_RIGHT(&ims0, ims_link);
- KREAD(ims, &ims1, struct ip_msource);
- while ((tmp = RB_LEFT(&ims1, ims_link))) {
- KREAD(tmp, &ims0, struct ip_msource);
- ims = RB_LEFT(&ims0, ims_link);
- }
- } else {
- tmp = RB_PARENT(&ims0, ims_link);
- if (tmp) {
- KREAD(tmp, &ims1, struct ip_msource);
- if (ims == RB_LEFT(&ims1, ims_link))
- ims = tmp;
- } else {
- while ((tmp = RB_PARENT(&ims0, ims_link))) {
- KREAD(tmp, &ims1, struct ip_msource);
- if (ims == RB_RIGHT(&ims1, ims_link)) {
- ims = tmp;
- KREAD(ims, &ims0, struct ip_msource);
- } else
- break;
- }
- ims = RB_PARENT(&ims0, ims_link);
- }
- }
- return (ims); /* kva */
-}
-
-static void
-inm_print_sources_kvm(struct in_multi *pinm)
-{
- struct ip_msource ims0;
- struct ip_msource *ims;
- struct in_addr src;
- int cnt;
- uint8_t fmode;
-
- cnt = 0;
- fmode = pinm->inm_st[1].iss_fmode;
- if (fmode == MCAST_UNDEFINED)
- return;
- for (ims = ims_min_kvm(pinm); ims != NULL; ims = ims_next_kvm(ims)) {
- if (cnt == 0)
- printf(" srcs ");
- KREAD(ims, &ims0, struct ip_msource);
- /* Only print sources in-mode at t1. */
- if (fmode != ims_get_mode(pinm, ims, 1))
- continue;
- src.s_addr = htonl(ims0.ims_haddr);
- printf("%s%s", (cnt++ == 0 ? "" : ","), inet_ntoa(src));
- }
-}
-#endif
-
-static struct in_multi *
-in_multientry(struct in_multi *pinm)
-{
- struct in_multi inm;
- const char *state, *mode;
-
- KREAD(pinm, &inm, struct in_multi);
- printf("\t\tgroup %s", inet_ntoa(inm.inm_addr));
- printf(" refcnt %u", inm.inm_refcount);
-
- state = inm_state(inm.inm_state);
- if (state)
- printf(" state %s", state);
- else
- printf(" state (%d)", inm.inm_state);
-
- mode = inm_mode(inm.inm_st[1].iss_fmode);
- if (mode)
- printf(" mode %s", mode);
- else
- printf(" mode (%d)", inm.inm_st[1].iss_fmode);
-
- if (vflag >= 2) {
- printf(" asm %u ex %u in %u rec %u",
- (u_int)inm.inm_st[1].iss_asm,
- (u_int)inm.inm_st[1].iss_ex,
- (u_int)inm.inm_st[1].iss_in,
- (u_int)inm.inm_st[1].iss_rec);
- }
-
-#if 0
- /* Buggy. */
- if (vflag)
- inm_print_sources_kvm(&inm);
-#endif
-
- printf("\n");
- return (NULL);
-}
-
-#endif /* INET */
-
-#endif /* WITH_KVM */
-
#ifdef INET6
static void
diff --git a/usr.sbin/inetd/inetd.8 b/usr.sbin/inetd/inetd.8
index 189415caa711..2fe116c076df 100644
--- a/usr.sbin/inetd/inetd.8
+++ b/usr.sbin/inetd/inetd.8
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd November 5, 2025
+.Dd June 10, 2026
.Dt INETD 8
.Os
.Sh NAME
@@ -38,7 +38,7 @@
.Op Fl a Ar address
.Op Fl C Ar rate
.Op Fl c Ar maximum
-.Op Fl p Ar filename
+.Op Fl p Ar pidfile
.Op Fl R Ar rate
.Op Fl s Ar maximum
.Op Ar configuration_file
@@ -115,7 +115,7 @@ parameter.
Turn on debugging.
.It Fl l
Turn on logging of successful connections.
-.It Fl p
+.It Fl p Ar pidfile
Specify an alternate file in which to store the process ID.
.It Fl R Ar rate
Specify the maximum number of times a service can be invoked
diff --git a/usr.sbin/inetd/inetd.c b/usr.sbin/inetd/inetd.c
index 2ebc9fe02a81..33316ce170a1 100644
--- a/usr.sbin/inetd/inetd.c
+++ b/usr.sbin/inetd/inetd.c
@@ -381,7 +381,7 @@ main(int argc, char **argv)
default:
syslog(LOG_ERR,
"usage: inetd [-dlWw] [-a address] [-C rate]"
- " [-c maximum] [-p filename] [-R rate]"
+ " [-c maximum] [-p pidfile] [-R rate]"
" [-s maximum] [configuration_file]");
exit(EX_USAGE);
}
diff --git a/usr.sbin/iostat/iostat.8 b/usr.sbin/iostat/iostat.8
index 11f0ef6fbeb6..09ca269f3b94 100644
--- a/usr.sbin/iostat/iostat.8
+++ b/usr.sbin/iostat/iostat.8
@@ -246,9 +246,14 @@ specified by the user.
Pause
.Ar wait
seconds between each display.
+If a repeat count
+.Pq Fl c
+is not specified and a wait interval is specified,
+the default repeat count is infinity.
If
.Fl w
-is not specified, the default interval is 1 second.
+is not specified and a repeat count is specified,
+the default interval is 1 second.
.Pp
The
.Nm
diff --git a/usr.sbin/iovctl/iovctl.8 b/usr.sbin/iovctl/iovctl.8
index 2574503e5ae7..5e1457d3fef5 100644
--- a/usr.sbin/iovctl/iovctl.8
+++ b/usr.sbin/iovctl/iovctl.8
@@ -23,7 +23,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd May 31, 2020
+.Dd August 8, 2026
.Dt IOVCTL 8
.Os
.Sh NAME
@@ -39,12 +39,15 @@
.Op Fl f Ar config-file | Fl d Ar device
.Op Fl n
.Nm
+.Fl L
+.Op Fl f Ar config-file | Fl d Ar device
+.Nm
.Fl S
.Op Fl f Ar config-file | Fl d Ar device
.Sh DESCRIPTION
The
.Nm
-utility creates or destroys PCI Single-Root I/O Virtualization
+utility configures and inspects PCI Single-Root I/O Virtualization
.Pq SR-IOV
Virtual Functions
.Pq VFs .
@@ -89,7 +92,8 @@ For the
.Fl C
option, this file will be used to specify all configuration values.
For the
-.Fl D
+.Fl D ,
+.Fl L ,
and
.Fl S
options, this file will only be used to specify the name of the PF device.
@@ -105,17 +109,40 @@ Perform a dry-run.
Perform all validation of the specified action and print what would be done,
but do not perform the actual creation or destruction of VFs.
This option may not be used with the
+.Fl L
+or
.Fl S
flag.
+.It Fl L
+Report the current PCI SR-IOV topology of the specified PF.
+The output identifies the PF and each configured VF by PCI location, reports
+whether each VF is attached to a host driver, and identifies bindings to
+.Va ppt .
+An unconfigured PF reports its enable state and VF capacity without any
+per-VF records.
+A passthrough binding does not imply that a running virtual machine currently
+owns the VF.
+See
+.Xr vmm 4
+for passthrough-device configuration.
.It Fl S
Read the configuration schema from the specified device and print its contents
to stdout.
This action may be used to discover the configuration parameters supported on
a given PF device.
.El
+.Pp
+For network devices, use
+.Xr ifconfig 8
+with the
+.Fl v
+option on the PF interface to display NIC-specific VF initialization,
+resource, and policy state.
.Sh SEE ALSO
+.Xr vmm 4 ,
.Xr iovctl.conf 5 ,
-.Xr rc.conf 5
+.Xr rc.conf 5 ,
+.Xr ifconfig 8
.Sh AUTHORS
This manual page was written by
.An Ryan Stone Aq Mt rstone@FreeBSD.org .
diff --git a/usr.sbin/iovctl/iovctl.c b/usr.sbin/iovctl/iovctl.c
index 28d2e0a93504..735b30340903 100644
--- a/usr.sbin/iovctl/iovctl.c
+++ b/usr.sbin/iovctl/iovctl.c
@@ -43,6 +43,7 @@
static void config_action(const char *filename, int dryrun);
static void delete_action(const char *device, int dryrun);
static void print_schema(const char *device);
+static void print_status(const char *device);
/*
* Fetch the config schema from the kernel via ioctl. This function has to
@@ -85,6 +86,48 @@ get_schema(int fd)
return (schema);
}
+/* Fetch and unpack the current PCI SR-IOV status. */
+static nvlist_t *
+get_status(int fd)
+{
+ struct pci_iov_status arg;
+ nvlist_t *status;
+ void *buf, *newbuf;
+ size_t buflen;
+ int error;
+
+ buf = NULL;
+ buflen = 0;
+ for (;;) {
+ memset(&arg, 0, sizeof(arg));
+ arg.status = buf;
+ arg.len = buflen;
+ error = ioctl(fd, IOV_GET_STATUS, &arg);
+ if (error != 0)
+ err(1, "Could not fetch SR-IOV status");
+ if (arg.error == 0)
+ break;
+ if (arg.error != EMSGSIZE || arg.len <= buflen) {
+ errno = arg.error;
+ err(1, "Could not fetch SR-IOV status");
+ }
+ newbuf = realloc(buf, arg.len);
+ if (newbuf == NULL)
+ err(1, "Could not allocate %zu bytes for SR-IOV status",
+ arg.len);
+ buf = newbuf;
+ buflen = arg.len;
+ }
+ if (arg.len == 0 || arg.len > buflen)
+ errx(1, "Kernel returned an invalid SR-IOV status length");
+
+ status = nvlist_unpack(buf, arg.len, 0);
+ if (status == NULL)
+ err(1, "Could not unpack SR-IOV status");
+ free(buf);
+ return (status);
+}
+
/*
* Call the ioctl that activates SR-IOV and creates the VFs.
*/
@@ -155,6 +198,7 @@ usage(void)
warnx("Usage: iovctl -C -f <config file> [-n]");
warnx(" iovctl -D [-d <PF device> | -f <config file>] [-n]");
+ warnx(" iovctl -L [-d <PF device> | -f <config file>]");
warnx(" iovctl -S [-d <PF device> | -f <config file>]");
exit(1);
@@ -164,6 +208,7 @@ enum main_action {
NONE,
CONFIG,
DELETE,
+ PRINT_STATUS,
PRINT_SCHEMA,
};
@@ -180,12 +225,11 @@ main(int argc, char **argv)
dryrun = 0;
action = NONE;
- while ((ch = getopt(argc, argv, "Cd:Df:nS")) != -1) {
+ while ((ch = getopt(argc, argv, "Cd:Df:LnS")) != -1) {
switch (ch) {
case 'C':
if (action != NONE) {
- warnx(
- "Only one of -C, -D or -S may be specified");
+ warnx("Only one action may be specified");
usage();
}
action = CONFIG;
@@ -195,8 +239,7 @@ main(int argc, char **argv)
break;
case 'D':
if (action != NONE) {
- warnx(
- "Only one of -C, -D or -S may be specified");
+ warnx("Only one action may be specified");
usage();
}
action = DELETE;
@@ -207,10 +250,16 @@ main(int argc, char **argv)
case 'n':
dryrun = 1;
break;
+ case 'L':
+ if (action != NONE) {
+ warnx("Only one action may be specified");
+ usage();
+ }
+ action = PRINT_STATUS;
+ break;
case 'S':
if (action != NONE) {
- warnx(
- "Only one of -C, -D or -S may be specified");
+ warnx("Only one action may be specified");
usage();
}
action = PRINT_SCHEMA;
@@ -260,6 +309,16 @@ main(int argc, char **argv)
print_schema(device);
free(device);
break;
+ case PRINT_STATUS:
+ if (dryrun) {
+ warnx("-n flag cannot be used with the -L flag");
+ usage();
+ }
+ if (device == NULL)
+ device = find_device(filename);
+ print_status(device);
+ free(device);
+ break;
default:
usage();
break;
@@ -313,6 +372,106 @@ delete_action(const char *dev_name, int dryrun)
}
static void
+validate_status(const nvlist_t *status)
+{
+ const nvlist_t *pf;
+ const nvlist_t * const *vfs;
+ size_t i, num_vfs;
+ uint64_t configured_vfs, index, total_vfs, version;
+
+ if (!nvlist_exists_number(status, IOV_STATUS_VERSION_NAME) ||
+ !nvlist_exists_nvlist(status, IOV_STATUS_PF_NAME))
+ errx(1, "Kernel returned an invalid SR-IOV status");
+ version = nvlist_get_number(status, IOV_STATUS_VERSION_NAME);
+ if (version != IOV_STATUS_VERSION)
+ errx(1, "Unsupported SR-IOV status version %ju",
+ (uintmax_t)version);
+
+ pf = nvlist_get_nvlist(status, IOV_STATUS_PF_NAME);
+ if (!nvlist_exists_string(pf, IOV_STATUS_DEVICE_NAME) ||
+ !nvlist_exists_string(pf, IOV_STATUS_PCI_LOCATION_NAME) ||
+ !nvlist_exists_bool(pf, IOV_STATUS_ENABLED_NAME) ||
+ !nvlist_exists_number(pf, IOV_STATUS_NUM_VFS_NAME) ||
+ !nvlist_exists_number(pf, IOV_STATUS_TOTAL_VFS_NAME))
+ errx(1, "Kernel returned an invalid SR-IOV PF status");
+ configured_vfs = nvlist_get_number(pf, IOV_STATUS_NUM_VFS_NAME);
+ total_vfs = nvlist_get_number(pf, IOV_STATUS_TOTAL_VFS_NAME);
+ if (configured_vfs > total_vfs)
+ errx(1, "Kernel returned inconsistent SR-IOV VF counts");
+ if (configured_vfs == 0) {
+ if (nvlist_exists(status, IOV_STATUS_VFS_NAME))
+ errx(1, "Kernel returned inconsistent SR-IOV VF counts");
+ return;
+ }
+ if (!nvlist_exists_nvlist_array(status, IOV_STATUS_VFS_NAME))
+ errx(1, "Kernel returned an invalid SR-IOV status");
+ vfs = nvlist_get_nvlist_array(status, IOV_STATUS_VFS_NAME, &num_vfs);
+ if (configured_vfs != num_vfs)
+ errx(1, "Kernel returned inconsistent SR-IOV VF counts");
+ for (i = 0; i < num_vfs; i++) {
+ if (!nvlist_exists_number(vfs[i], IOV_STATUS_VF_INDEX_NAME) ||
+ !nvlist_exists_string(vfs[i],
+ IOV_STATUS_PCI_LOCATION_NAME) ||
+ !nvlist_exists_bool(vfs[i], IOV_STATUS_ATTACHED_NAME) ||
+ !nvlist_exists_bool(vfs[i], IOV_STATUS_PASSTHROUGH_NAME) ||
+ (nvlist_exists(vfs[i], IOV_STATUS_BOUND_DRIVER_NAME) &&
+ !nvlist_exists_string(vfs[i],
+ IOV_STATUS_BOUND_DRIVER_NAME)))
+ errx(1, "Kernel returned an invalid SR-IOV VF status");
+ index = nvlist_get_number(vfs[i], IOV_STATUS_VF_INDEX_NAME);
+ if (index >= configured_vfs)
+ errx(1, "Kernel returned an invalid SR-IOV VF index");
+ }
+}
+
+static void
+print_status(const char *dev_name)
+{
+ const nvlist_t *pf, *vf;
+ const nvlist_t * const *vfs;
+ nvlist_t *status;
+ size_t i, num_vfs;
+ int fd;
+
+ fd = open_device(dev_name);
+ status = get_status(fd);
+ validate_status(status);
+ pf = nvlist_get_nvlist(status, IOV_STATUS_PF_NAME);
+ vfs = NULL;
+ num_vfs = 0;
+ if (nvlist_exists_nvlist_array(status, IOV_STATUS_VFS_NAME))
+ vfs = nvlist_get_nvlist_array(status, IOV_STATUS_VFS_NAME,
+ &num_vfs);
+
+ printf("%s:\n", nvlist_get_string(pf, IOV_STATUS_DEVICE_NAME));
+ printf("\tidentity: pci-location=%s\n",
+ nvlist_get_string(pf, IOV_STATUS_PCI_LOCATION_NAME));
+ printf("\tsriov: enabled=%s vfs=%ju/%ju\n",
+ nvlist_get_bool(pf, IOV_STATUS_ENABLED_NAME) ? "yes" : "no",
+ (uintmax_t)nvlist_get_number(pf, IOV_STATUS_NUM_VFS_NAME),
+ (uintmax_t)nvlist_get_number(pf, IOV_STATUS_TOTAL_VFS_NAME));
+ for (i = 0; i < num_vfs; i++) {
+ vf = vfs[i];
+ printf("\t\tvf %3ju:\n", (uintmax_t)nvlist_get_number(vf,
+ IOV_STATUS_VF_INDEX_NAME));
+ printf("\t\t\tidentity: pci-location=%s\n",
+ nvlist_get_string(vf, IOV_STATUS_PCI_LOCATION_NAME));
+ printf("\t\t\thost: attached=%s",
+ nvlist_get_bool(vf, IOV_STATUS_ATTACHED_NAME) ? "yes" :
+ "no");
+ if (nvlist_exists_string(vf, IOV_STATUS_BOUND_DRIVER_NAME))
+ printf(" driver=%s", nvlist_get_string(vf,
+ IOV_STATUS_BOUND_DRIVER_NAME));
+ printf(" passthrough=%s\n",
+ nvlist_get_bool(vf, IOV_STATUS_PASSTHROUGH_NAME) ? "yes" :
+ "no");
+ }
+
+ nvlist_destroy(status);
+ close(fd);
+}
+
+static void
print_default_value(const nvlist_t *parameter, const char *type)
{
const uint8_t *mac;
diff --git a/usr.sbin/iovctl/iovctl.conf.5 b/usr.sbin/iovctl/iovctl.conf.5
index 1bae96e150ba..e56d1345b945 100644
--- a/usr.sbin/iovctl/iovctl.conf.5
+++ b/usr.sbin/iovctl/iovctl.conf.5
@@ -23,7 +23,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd May 30, 2024
+.Dd August 8, 2026
.Dt IOVCTL.CONF 5
.Os
.Sh NAME
@@ -140,6 +140,21 @@ The default value of this parameter is false.
.Pp
See the PF driver manual page for configuration parameters specific to
particular hardware.
+.Pp
+After creating VFs, display their PCI attachment and passthrough state with:
+.Bd -literal -offset indent
+iovctl -L -d ix0
+.Ed
+For network devices, display NIC-specific initialization, resources, and
+policy state with:
+.Bd -literal -offset indent
+ifconfig -v ix0
+.Ed
+See
+.Xr ifconfig 8
+and
+.Xr iovctl 8
+for the meaning of the reported fields.
.Sh EXAMPLES
This sample file will create 3 VFs as children of the ix0 device.
VF-1 and VF-2 are set as
@@ -166,6 +181,7 @@ VF-0 {
.Ed
.Sh SEE ALSO
.Xr rc.conf 5 ,
+.Xr ifconfig 8 ,
.Xr iovctl 8
.Sh AUTHORS
This manual page was written by
diff --git a/usr.sbin/ipfwpcap/ipfwpcap.c b/usr.sbin/ipfwpcap/ipfwpcap.c
index a9cead99bd07..2032387aaa0b 100644
--- a/usr.sbin/ipfwpcap/ipfwpcap.c
+++ b/usr.sbin/ipfwpcap/ipfwpcap.c
@@ -41,11 +41,7 @@
#include <net/bpf.h>
-/* XXX normally defined in config.h */
-#define HAVE_STRLCPY 1
-#define HAVE_SNPRINTF 1
-#define HAVE_VSNPRINTF 1
-#include <pcap-int.h> /* see pcap(3) and /usr/src/contrib/libpcap/. */
+#include <pcap.h>
#ifdef IP_MAXPACKET
#define BUFMAX IP_MAXPACKET
@@ -295,8 +291,7 @@ if (debug) fprintf(stderr, " sendto(%d) = %d\n", sd, r);
(void) gettimeofday(&(phd.ts), NULL);
phd.caplen = phd.len = nr;
pcap_dump((u_char *)dp, &phd, buf);
- if (ferror((FILE *)dp)) { perror(dumpf); quit(14); }
- (void) fflush((FILE *)dp);
+ if (pcap_dump_flush(dp) == -1) { pcap_perror(p, dumpf); quit(14); }
}
quit(0);
diff --git a/usr.sbin/jail/command.c b/usr.sbin/jail/command.c
index 9da4fe51673a..8a1d281eff4f 100644
--- a/usr.sbin/jail/command.c
+++ b/usr.sbin/jail/command.c
@@ -814,8 +814,8 @@ run_command(struct cfjail *j)
if (!injail) {
if (string_param(j->intparams[KP_JID]))
setenv("JID", string_param(j->intparams[KP_JID]), 1);
- setenv("JNAME", string_param(j->intparams[KP_NAME]), 1);
-
+ if (string_param(j->intparams[KP_NAME]))
+ setenv("JNAME", string_param(j->intparams[KP_NAME]), 1);
path = string_param(j->intparams[KP_PATH]);
setenv("JPATH", path ? path : "", 1);
}
diff --git a/usr.sbin/jail/config.c b/usr.sbin/jail/config.c
index 1bad04ccde68..cc05c2916ca0 100644
--- a/usr.sbin/jail/config.c
+++ b/usr.sbin/jail/config.c
@@ -134,7 +134,7 @@ load_config(const char *cfname)
struct cfparams opp;
struct cfjail *j, *tj, *wj;
struct cfparam *p, *vp, *tp;
- struct cfstring *s, *vs, *ns;
+ struct cfstring *s, *vs, *ns, *cs;
struct cfvar *v, *vv;
char *ep;
int did_self, jseq, pgen;
@@ -246,13 +246,14 @@ load_config(const char *cfname)
while ((vv = STAILQ_NEXT(vv, tq)))
vv->pos += vs->len;
s->len += vs->len;
+ cs = s;
while ((vs = TAILQ_NEXT(vs, tq))) {
ns = emalloc(sizeof(struct cfstring));
ns->s = estrdup(vs->s);
ns->len = vs->len;
STAILQ_INIT(&ns->vars);
- TAILQ_INSERT_AFTER(&p->val, s, ns, tq);
- s = ns;
+ TAILQ_INSERT_AFTER(&p->val, cs, ns, tq);
+ cs = ns;
}
free_var:
free(v->name);
@@ -321,6 +322,7 @@ static void
parse_config(const char *cfname, int is_stdin)
{
struct cflex cflex = {.cfname = cfname, .error = 0};
+ FILE *yfp = NULL;
void *scanner;
yylex_init_extra(&cflex, &scanner);
@@ -328,7 +330,7 @@ parse_config(const char *cfname, int is_stdin)
cflex.cfname = "STDIN";
yyset_in(stdin, scanner);
} else {
- FILE *yfp = fopen(cfname, "r");
+ yfp = fopen(cfname, "re");
if (!yfp)
err(1, "%s", cfname);
yyset_in(yfp, scanner);
@@ -336,6 +338,8 @@ parse_config(const char *cfname, int is_stdin)
if (yyparse(scanner) || cflex.error)
exit(1);
yylex_destroy(scanner);
+ if (yfp != NULL)
+ fclose(yfp);
}
/*
@@ -723,7 +727,7 @@ check_intparams(struct cfjail *j)
TAILQ_FOREACH(s, &j->intparams[IP_MOUNT_FSTAB]->val, tq) {
if (s->len == 0)
continue;
- f = fopen(s->s, "r");
+ f = fopen(s->s, "re");
if (f == NULL) {
jail_warnx(j, "mount.fstab: %s: %s",
s->s, strerror(errno));
diff --git a/usr.sbin/jail/jail.8 b/usr.sbin/jail/jail.8
index adba3f34101b..3fc396824b58 100644
--- a/usr.sbin/jail/jail.8
+++ b/usr.sbin/jail/jail.8
@@ -683,7 +683,7 @@ to
in the
.Xr rc.conf 5
file outside of the jails.
-Similarily, if the
+Similarly, if the
.Xr gssd 8
is to be run in a jail, either the kernel
.Sy KGSSAPI
@@ -834,7 +834,7 @@ This flag is only available when the
.Xr vmm 4
kernel module is loaded.
.It Va allow.vmm_ppt
-The jail may configure PCI passtrough devices for use by
+The jail may configure PCI passthrough devices for use by
.Xr vmm 4
virtual machine guests.
This allows privileged users inside the jail to manipulate physical devices
diff --git a/usr.sbin/jail/tests/jail_basic_test.sh b/usr.sbin/jail/tests/jail_basic_test.sh
index c781eed78756..cb50a94a45e8 100755
--- a/usr.sbin/jail/tests/jail_basic_test.sh
+++ b/usr.sbin/jail/tests/jail_basic_test.sh
@@ -186,11 +186,12 @@ find_unused_jid()
atf_skip "System has too many jail, cannot find free slot"
fi
- jid=$((jid + 1))
+ : $(( jid += 1 ))
done
echo "$jid" | tee -a jails.lst
}
+
clean_jails()
{
if [ ! -s jails.lst ]; then
diff --git a/usr.sbin/kldxref/kldxref.8 b/usr.sbin/kldxref/kldxref.8
index 1704847592cd..c292a2338cb7 100644
--- a/usr.sbin/kldxref/kldxref.8
+++ b/usr.sbin/kldxref/kldxref.8
@@ -24,7 +24,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd February 25, 2023
+.Dd June 27, 2026
.Dt KLDXREF 8
.Os
.Sh NAME
@@ -35,6 +35,8 @@
.Op Fl Rdv
.Op Fl f Ar hintsfile
.Ar path ...
+.Nm
+.Fl m Ar module
.Sh DESCRIPTION
The
.Nm
@@ -67,6 +69,9 @@ output.
.It Fl f Ar hintsfile
Specify a different name for the hints files than
.Pa linker.hints .
+.It Fl m Ar module
+Print information about the modules in the file
+.Ar module .
.It Fl v
Operate in verbose mode.
.El
diff --git a/usr.sbin/kldxref/kldxref.c b/usr.sbin/kldxref/kldxref.c
index eed754e1e730..260a0baa6405 100644
--- a/usr.sbin/kldxref/kldxref.c
+++ b/usr.sbin/kldxref/kldxref.c
@@ -764,13 +764,13 @@ main(int argc, char *argv[])
{
FTS *ftsp;
FTSENT *p;
- char *dot = NULL;
+ char *dot = NULL, *module = NULL;
int opt, fts_options;
struct stat sb;
fts_options = FTS_PHYSICAL;
- while ((opt = getopt(argc, argv, "Rdf:v")) != -1) {
+ while ((opt = getopt(argc, argv, "Rdf:m:v")) != -1) {
switch (opt) {
case 'd': /* no hint file, only print on stdout */
dflag = true;
@@ -778,6 +778,10 @@ main(int argc, char *argv[])
case 'f': /* use this name instead of linker.hints */
xref_file = optarg;
break;
+ case 'm':
+ module = optarg;
+ dflag = true;
+ break;
case 'v':
verbose++;
break;
@@ -789,11 +793,18 @@ main(int argc, char *argv[])
/* NOTREACHED */
}
}
- if (argc - optind < 1)
+ if (argc - optind < (module ? 0 : 1))
usage();
argc -= optind;
argv += optind;
+ if (module) {
+ if (elf_version(EV_CURRENT) == EV_NONE)
+ errx(1, "unsupported libelf");
+ read_kld(module, module);
+ exit(0);
+ }
+
if (stat(argv[0], &sb) != 0)
err(1, "%s", argv[0]);
if ((sb.st_mode & S_IFDIR) == 0 && !dflag) {
diff --git a/usr.sbin/lpr/lpd/lpd.8 b/usr.sbin/lpr/lpd/lpd.8
index 9d24a450ba0e..57331df02089 100644
--- a/usr.sbin/lpr/lpd/lpd.8
+++ b/usr.sbin/lpr/lpd/lpd.8
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd February 19, 2026
+.Dd May 25, 2026
.Dt LPD 8
.Os
.Sh NAME
@@ -33,9 +33,9 @@
.Nd line printer spooler daemon
.Sh SYNOPSIS
.Nm
-.Op Fl cdlpsFW46
+.Op Fl cdlsFW46
.Op Fl t Ar timeout
-.Op Ar port#
+.Op Ar port
.Sh DEPRECATION NOTICE
This facility is scheduled for removal prior to the release of
.Fx 16.0 .
@@ -63,6 +63,12 @@ the request so the parent can continue to listen for more requests.
.Pp
Available options:
.Bl -tag -width Ds
+.It Fl 4
+Accept IPv4 connections.
+The default is to accept both IPv4 and IPv6.
+.It Fl 6
+Accept IPv6 connections.
+The default is to accept both IPv4 and IPv6.
.It Fl c
By default, if some remote host has a connection error while trying to
send a print request to
@@ -81,29 +87,25 @@ Turn on
.Dv SO_DEBUG
on the Internet listening socket (see
.Xr setsockopt 2 ) .
-.It Fl l
+.It Fl F
+By default,
+.Nm
+will daemonize into the background.
The
-.Fl l
+.Fl F
flag causes
.Nm
+to remain in the foreground.
+Logging is still performed with
+.Xr syslog 3 .
+.It Fl l
+Causes
+.Nm
to log valid requests received from the network.
This can be useful
for debugging purposes.
-.It Fl p
-The
-.Fl p
-flag is a synonym for the
-.Fl s
-flag.
-It is being deprecated, and may be removed in a
-future version of
-.Nm .
.It Fl s
-The
-.Fl s
-(secure) flag causes
-.Nm
-not to open an Internet listening socket.
+Do not open an Internet listening socket.
This means that
.Nm
will not accept any connections from any remote
@@ -114,17 +116,6 @@ Set the network receive timeout for client connections to
.Ar timeout
seconds.
The default is 120.
-.It Fl F
-By default,
-.Nm
-will daemonize into the background.
-The
-.Fl F
-flag causes
-.Nm
-to remain in the foreground.
-Logging is still performed with
-.Xr syslog 3 .
.It Fl W
By default, the
.Nm
@@ -137,19 +128,13 @@ flag causes
to accept connections coming from any port.
This is can be useful when you want to accept print jobs
from certain implementations of lpr written for Windows.
-.It Fl 4
-Inet only.
-.It Fl 6
-Inet6 only.
-.It Fl 46
-Inet and inet6 (default).
-.It Ar "port#"
-The Internet port number used to rendezvous
-with other processes is normally obtained with
-.Xr getservbyname 3
-but can be changed with the
-.Ar port#
-argument.
+.It Ar "port"
+The Internet port number or service name used to rendezvous with other
+processes.
+Can be any number from 1 to 65,535 or any service name defined in
+.Pa /etc/services .
+Defaults to
+.Li printer .
.El
.Pp
Access control is provided by two means.
@@ -346,6 +331,7 @@ but not under same administrative control.
.Xr syslog 3 ,
.Xr hosts.lpd 5 ,
.Xr printcap 5 ,
+.Xr services 5 ,
.Xr chkprintcap 8 ,
.Xr lpc 8 ,
.Xr pac 8
diff --git a/usr.sbin/lpr/lpd/lpd.c b/usr.sbin/lpr/lpd/lpd.c
index d1dcd0766a77..d01aac7a7a35 100644
--- a/usr.sbin/lpr/lpd/lpd.c
+++ b/usr.sbin/lpr/lpd/lpd.c
@@ -101,7 +101,7 @@ static void startup(void);
static void chkhost(struct sockaddr *_f, int _ch_opts);
static int ckqueue(struct printer *_pp);
static void fhosterr(int _ch_opts, char *_sysmsg, char *_usermsg);
-static int *socksetup(int _af, int _debuglvl);
+static int *socksetup(int _af, const char *portstr, int _debuglvl);
static void usage(void);
/* XXX from libc/net/rcmd.c */
@@ -124,7 +124,7 @@ main(int argc, char **argv)
struct sockaddr_storage frominet;
socklen_t fromlen;
sigset_t omask, nmask;
- struct servent *sp, serv;
+ const char *portstr = "printer";
int inet_flag = 0, inet6_flag = 0;
euid = geteuid(); /* these shouldn't be different */
@@ -153,9 +153,6 @@ main(int argc, char **argv)
lflag++;
break;
case 'p': /* letter initially used for -s */
- /*
- * This will probably be removed with 5.0-release.
- */
/* FALLTHROUGH */
case 's': /* secure (no inet) */
sflag++;
@@ -215,19 +212,9 @@ main(int argc, char **argv)
if (errs)
usage();
- if (argc == 1) {
- if ((i = atoi(argv[0])) == 0)
- usage();
- if (i < 0 || i > USHRT_MAX)
- errx(EX_USAGE, "port # %d is invalid", i);
-
- serv.s_port = htons(i);
- sp = &serv;
+ if (argc > 0) {
+ portstr = *argv++;
argc--;
- } else {
- sp = getservbyname("printer", "tcp");
- if (sp == NULL)
- errx(EX_OSFILE, "printer/tcp: unknown service");
}
if (argc != 0)
@@ -339,7 +326,7 @@ main(int argc, char **argv)
FD_SET(funix, &defreadfds);
listen(funix, 5);
if (sflag == 0) {
- finet = socksetup(family, socket_debug);
+ finet = socksetup(family, portstr, socket_debug);
} else
finet = NULL; /* pretend we couldn't open TCP socket. */
if (finet) {
@@ -855,7 +842,7 @@ fhosterr(int ch_opts, char *sysmsg, char *usermsg)
/* if af is PF_UNSPEC more than one socket may be returned */
/* the returned list is dynamically allocated, so caller needs to free it */
static int *
-socksetup(int af, int debuglvl)
+socksetup(int af, const char *portstr, int debuglvl)
{
struct addrinfo hints, *res, *r;
int error, maxs, *s, *socks;
@@ -865,7 +852,7 @@ socksetup(int af, int debuglvl)
hints.ai_flags = AI_PASSIVE;
hints.ai_family = af;
hints.ai_socktype = SOCK_STREAM;
- error = getaddrinfo(NULL, "printer", &hints, &res);
+ error = getaddrinfo(NULL, portstr, &hints, &res);
if (error) {
syslog(LOG_ERR, "%s", gai_strerror(error));
mcleanup(0);
@@ -934,9 +921,9 @@ static void
usage(void)
{
#ifdef INET6
- fprintf(stderr, "usage: lpd [-cdlsFW46] [port#]\n");
+ fprintf(stderr, "usage: lpd [-cdlsFW46] [port]\n");
#else
- fprintf(stderr, "usage: lpd [-cdlsFW] [port#]\n");
+ fprintf(stderr, "usage: lpd [-cdlsFW] [port]\n");
#endif
exit(EX_USAGE);
}
diff --git a/usr.sbin/lpr/lpd/printjob.c b/usr.sbin/lpr/lpd/printjob.c
index 1c6736097492..9173b69a36b9 100644
--- a/usr.sbin/lpr/lpd/printjob.c
+++ b/usr.sbin/lpr/lpd/printjob.c
@@ -68,8 +68,12 @@
/*
* The buffer size to use when reading/writing spool files.
+ * This is also used to compose commands for remote servers and therefore
+ * needs to be able to fit the longest command we could possibly send.
*/
-#define SPL_BUFSIZ BUFSIZ
+#define SPL_BUFSIZ (BUFSIZ * 2)
+_Static_assert(SPL_BUFSIZ > sizeof("x18446744073709551615 ") + PATH_MAX +
+ sizeof("_c2147483647 "), "SPL_BUFSIZ is too short");
/*
* Error tokens
@@ -132,10 +136,10 @@ static int printit(struct printer *_pp, char *_file);
static void pstatus(const struct printer *_pp, const char *_msg, ...)
__printflike(2, 3);
static char response(const struct printer *_pp);
-static void scan_out(struct printer *_pp, int _scfd, char *_scsp,
+static void scan_out(struct printer *_pp, int _scfd, char *_scsp,
int _dlm);
static char *scnline(int _key, char *_p, int _c);
-static int sendfile(struct printer *_pp, int _type, char *_file,
+static int sendfile(struct printer *_pp, int _type, char *_file,
char _format, int _copyreq);
static int sendit(struct printer *_pp, char *_file);
static void sendmail(struct printer *_pp, char *_userid, int _bombed);
@@ -146,18 +150,17 @@ void
printjob(struct printer *pp)
{
struct stat stb;
- register struct jobqueue *q, **qp;
+ struct jobqueue *q, **qp;
struct jobqueue **queue;
- register int i, nitems;
off_t pidoff;
pid_t printpid;
- int errcnt, jobcount, statok, tempfd;
+ int i, errcnt, jobcount, nitems, tempfd;
jobcount = 0;
init(pp); /* set up capabilities */
(void) write(STDOUT_FILENO, "", 1); /* ack that daemon is started */
(void) close(STDERR_FILENO); /* set up log file */
- if (open(pp->log_file, O_WRONLY|O_APPEND, LOG_FILE_MODE) < 0) {
+ if (open(pp->log_file, O_WRONLY | O_APPEND, LOG_FILE_MODE) < 0) {
syslog(LOG_ERR, "%s: open(%s): %m", pp->printer,
pp->log_file);
(void) open(_PATH_DEVNULL, O_WRONLY);
@@ -186,12 +189,9 @@ printjob(struct printer *pp)
pp->spool_dir);
exit(1);
}
- statok = stat(pp->lock_file, &stb);
- if (statok == 0 && (stb.st_mode & LFM_PRINT_DIS))
- exit(0); /* printing disabled */
umask(S_IWOTH);
- lfd = open(pp->lock_file, O_WRONLY|O_CREAT|O_EXLOCK|O_NONBLOCK,
- LOCK_FILE_MODE);
+ lfd = open(pp->lock_file, O_WRONLY | O_CREAT | O_EXLOCK | O_NONBLOCK,
+ LOCK_FILE_MODE);
if (lfd < 0) {
if (errno == EWOULDBLOCK) /* active daemon present */
exit(0);
@@ -199,12 +199,14 @@ printjob(struct printer *pp)
pp->lock_file);
exit(1);
}
- /*
- * If the initial call to stat() failed, then lock_file will have
- * been created by open(). Update &stb to match that new file.
- */
- if (statok != 0)
- statok = stat(pp->lock_file, &stb);
+ if (fstat(lfd, &stb) != 0) {
+ syslog(LOG_ERR, "%s: fstat(%s): %m", pp->printer,
+ pp->lock_file);
+ exit(1);
+ }
+ if ((stb.st_mode & LFM_PRINT_DIS) != 0) {
+ exit(0); /* printing disabled */
+ }
/* turn off non-blocking mode (was turned on for lock effects only) */
if (fcntl(lfd, F_SETFL, 0) < 0) {
syslog(LOG_ERR, "%s: fcntl(%s): %m", pp->printer,
@@ -215,8 +217,7 @@ printjob(struct printer *pp)
/*
* write process id for others to know
*/
- sprintf(line, "%u\n", printpid);
- pidoff = i = strlen(line);
+ pidoff = i = snprintf(line, sizeof(line), "%u\n", printpid);
if (write(lfd, line, i) != i) {
syslog(LOG_ERR, "%s: write(%s): %m", pp->printer,
pp->lock_file);
@@ -226,7 +227,7 @@ printjob(struct printer *pp)
* search the spool directory for work and sort by queue order.
*/
if ((nitems = getq(pp, &queue)) < 0) {
- syslog(LOG_ERR, "%s: can't scan %s", pp->printer,
+ syslog(LOG_ERR, "%s: can't scan %s", pp->printer,
pp->spool_dir);
exit(1);
}
@@ -244,9 +245,10 @@ printjob(struct printer *pp)
tempstderr);
exit(1);
}
- if ((i = fchmod(tempfd, 0664)) == -1) {
+ if (fchmod(tempfd, 0664) == -1) {
syslog(LOG_ERR, "%s: fchmod(%s): %m", pp->printer,
tempstderr);
+ (void) unlink(tempstderr);
exit(1);
}
/* lpd doesn't need it to be open, it just needs it to exist */
@@ -259,7 +261,7 @@ again:
* write the name of the current control file into the lock file
* so the spool queue program can tell what we're working on
*/
- for (qp = queue; nitems--; free((char *) q)) {
+ for (qp = queue; nitems--; free(q)) {
q = *qp++;
if (stat(q->job_cfname, &stb) < 0)
continue;
@@ -312,12 +314,12 @@ again:
}
(void) close(pfd); /* close printer */
if (ftruncate(lfd, pidoff) < 0)
- syslog(LOG_WARNING, "%s: ftruncate(%s): %m",
+ syslog(LOG_WARNING, "%s: ftruncate(%s): %m",
pp->printer, pp->lock_file);
openpr(pp); /* try to reopen printer */
goto restart;
} else {
- syslog(LOG_WARNING, "%s: job could not be %s (%s)",
+ syslog(LOG_WARNING, "%s: job could not be %s (%s)",
pp->printer,
pp->remote ? "sent to remote host" : "printed",
q->job_cfname);
@@ -358,15 +360,18 @@ again:
goto again;
}
-char fonts[4][50]; /* fonts for troff */
+char fonts[4][64]; /* fonts for troff */
-char ifonts[4][40] = {
+char ifonts[4][64] = {
_PATH_VFONTR,
_PATH_VFONTI,
_PATH_VFONTB,
_PATH_VFONTS,
};
+_Static_assert(sizeof(fonts) == sizeof(ifonts), "fonts != ifonts");
+_Static_assert(sizeof(fonts[0]) == sizeof(ifonts[0]), "fonts[0] != ifonts[0]");
+
/*
* The remaining part is the reading of the control file (cf)
* and performing the various actions.
@@ -374,9 +379,8 @@ char ifonts[4][40] = {
static int
printit(struct printer *pp, char *file)
{
- register int i;
char *cp;
- int bombed, didignorehdr;
+ int bombed, didignorehdr, i;
bombed = OK;
didignorehdr = 0;
@@ -390,14 +394,13 @@ printit(struct printer *pp, char *file)
/*
* Reset troff fonts.
*/
- for (i = 0; i < 4; i++)
- strcpy(fonts[i], ifonts[i]);
+ memcpy(fonts, ifonts, sizeof(fonts));
sprintf(&width[2], "%ld", pp->page_width);
- strcpy(indent+2, "0");
+ strcpy(indent, "-i0");
/* initialize job-specific count of datafiles processed */
job_dfcnt = 0;
-
+
/*
* read the control file for work to do
*
@@ -445,7 +448,7 @@ printit(struct printer *pp, char *file)
case 'H':
strlcpy(origin_host, line + 1, sizeof(origin_host));
if (class[0] == '\0') {
- strlcpy(class, line+1, sizeof(class));
+ strlcpy(class, line + 1, sizeof(class));
}
continue;
@@ -454,14 +457,14 @@ printit(struct printer *pp, char *file)
if (pp->restricted) { /* restricted */
if (getpwnam(logname) == NULL) {
bombed = NOACCT;
- sendmail(pp, line+1, bombed);
+ sendmail(pp, line + 1, bombed);
goto pass2;
}
}
continue;
case 'S':
- cp = line+1;
+ cp = line + 1;
i = 0;
while (*cp >= '0' && *cp <= '9')
i = i * 10 + (*cp++ - '0');
@@ -497,7 +500,7 @@ printit(struct printer *pp, char *file)
case 'L': /* identification line */
if (!pp->no_header && !pp->header_last)
- banner(pp, line+1, jobname);
+ banner(pp, line + 1, jobname);
continue;
case '1': /* troff fonts */
@@ -511,11 +514,11 @@ printit(struct printer *pp, char *file)
continue;
case 'W': /* page width */
- strlcpy(width+2, line + 1, sizeof(width) - 2);
+ strlcpy(width + 2, line + 1, sizeof(width) - 2);
continue;
case 'I': /* indent amount */
- strlcpy(indent+2, line + 1, sizeof(indent) - 2);
+ strlcpy(indent + 2, line + 1, sizeof(indent) - 2);
continue;
case 'Z': /* locale for pr */
@@ -537,7 +540,7 @@ printit(struct printer *pp, char *file)
pp->printer, line[0], &line[1]);
continue;
}
- i = print(pp, line[0], line+1);
+ i = print(pp, line[0], line + 1);
switch (i) {
case ERROR:
if (bombed == OK)
@@ -569,18 +572,18 @@ pass2:
switch (line[0]) {
case 'L': /* identification line */
if (!pp->no_header && pp->header_last)
- banner(pp, line+1, jobname);
+ banner(pp, line + 1, jobname);
continue;
case 'M':
if (bombed < NOACCT) /* already sent if >= NOACCT */
- sendmail(pp, line+1, bombed);
+ sendmail(pp, line + 1, bombed);
continue;
case 'U':
- if (strchr(line+1, '/'))
+ if (strchr(line + 1, '/'))
continue;
- (void) unlink(line+1);
+ (void) unlink(line + 1);
}
/*
* clean-up in case another control file exists
@@ -603,14 +606,13 @@ pass2:
static int
print(struct printer *pp, int format, char *file)
{
- register int n, i;
- register char *prog;
- int fi, fo;
+ char buf[SPL_BUFSIZ];
+ struct stat stb;
+ char *av[15];
+ char *prog;
FILE *fp;
- char *av[15], buf[SPL_BUFSIZ];
pid_t wpid;
- int p[2], retcode, stopped, wstatus, wstatus_set;
- struct stat stb;
+ int fi, fo, i, n, p[2], retcode, stopped, wstatus, wstatus_set;
/* Make sure the entire data file has arrived. */
wait4data(pp, file);
@@ -640,7 +642,7 @@ print(struct printer *pp, int format, char *file)
if (pp->filters[LPF_INPUT] == NULL
&& (format == 'f' || format == 'l' || format == 'o')) {
pp->tof = 0;
- while ((n = read(fi, buf, SPL_BUFSIZ)) > 0)
+ while ((n = read(fi, buf, sizeof(buf))) > 0)
if (write(ofd, buf, n) != n) {
(void) close(fi);
return (REPRINT);
@@ -674,7 +676,7 @@ print(struct printer *pp, int format, char *file)
execl(_PATH_PR, "pr", width, length,
"-h", *title ? title : " ",
"-L", *locale ? locale : "C",
- "-F", (char *)0);
+ "-F", NULL);
syslog(LOG_ERR, "cannot execl %s", _PATH_PR);
exit(2);
}
@@ -723,7 +725,7 @@ print(struct printer *pp, int format, char *file)
case 'd': /* print tex output */
(void) unlink(".railmag");
if ((fo = creat(".railmag", FILMOD)) < 0) {
- syslog(LOG_ERR, "%s: cannot create .railmag",
+ syslog(LOG_ERR, "%s: cannot create .railmag",
pp->printer);
(void) unlink(".railmag");
} else {
@@ -736,7 +738,7 @@ print(struct printer *pp, int format, char *file)
}
(void) close(fo);
}
- prog = (format == 't') ? pp->filters[LPF_TROFF]
+ prog = (format == 't') ? pp->filters[LPF_TROFF]
: ((format == 'n') ? pp->filters[LPF_DITROFF]
: pp->filters[LPF_DVI]);
av[1] = pxwidth;
@@ -810,7 +812,7 @@ start:
dup2(fo, STDOUT_FILENO);
/* setup stderr for the filter (child process)
* so it goes to our temporary errors file */
- n = open(tempstderr, O_WRONLY|O_TRUNC, 0664);
+ n = open(tempstderr, O_WRONLY | O_TRUNC, 0664);
if (n >= 0)
dup2(n, STDERR_FILENO);
closelog();
@@ -882,8 +884,9 @@ start:
static int
sendit(struct printer *pp, char *file)
{
+ char last[sizeof(line)];
+ char *cp;
int dfcopies, err, i;
- char *cp, last[sizeof(line)];
/*
* open control file
@@ -913,7 +916,7 @@ sendit(struct printer *pp, char *file)
while (get_line(cfp)) {
again:
if (line[0] == 'S') {
- cp = line+1;
+ cp = line + 1;
i = 0;
while (*cp >= '0' && *cp <= '9')
i = i * 10 + (*cp++ - '0');
@@ -932,22 +935,22 @@ sendit(struct printer *pp, char *file)
strlcpy(logname, line + 1, sizeof(logname));
if (pp->restricted) { /* restricted */
if (getpwnam(logname) == NULL) {
- sendmail(pp, line+1, NOACCT);
+ sendmail(pp, line + 1, NOACCT);
err = ERROR;
break;
}
}
} else if (line[0] == 'I') {
- strlcpy(indent+2, line + 1, sizeof(indent) - 2);
+ strlcpy(indent + 2, line + 1, sizeof(indent) - 2);
} else if (line[0] >= 'a' && line[0] <= 'z') {
dfcopies = 1;
- strcpy(last, line);
+ memcpy(last, line, sizeof(last));
while ((i = get_line(cfp)) != 0) {
if (strcmp(last, line) != 0)
break;
dfcopies++;
}
- switch (sendfile(pp, '\3', last+1, *last, dfcopies)) {
+ switch (sendfile(pp, '\3', last + 1, *last, dfcopies)) {
case OK:
if (i)
goto again;
@@ -972,8 +975,8 @@ sendit(struct printer *pp, char *file)
*/
fseek(cfp, 0L, 0);
while (get_line(cfp))
- if (line[0] == 'U' && !strchr(line+1, '/'))
- (void) unlink(line+1);
+ if (line[0] == 'U' && !strchr(line + 1, '/'))
+ (void) unlink(line + 1);
/*
* clean-up in case another control file exists
*/
@@ -989,11 +992,10 @@ sendit(struct printer *pp, char *file)
static int
sendfile(struct printer *pp, int type, char *file, char format, int copyreq)
{
- int i, amt;
+ char buf[SPL_BUFSIZ], opt_c[4], opt_h[4], opt_n[4];
struct stat stb;
char *av[15], *filtcmd;
- char buf[SPL_BUFSIZ], opt_c[4], opt_h[4], opt_n[4];
- int copycnt, filtstat, narg, resp, sfd, sfres, sizerr, statrc;
+ int amt, copycnt, filtstat, i, narg, resp, sfd, sfres, sizerr, statrc;
/* Make sure the entire data file has arrived. */
wait4data(pp, file);
@@ -1127,13 +1129,18 @@ sendfile(struct printer *pp, int type, char *file, char format, int copyreq)
sendagain:
copycnt++;
- if (copycnt < 2)
- (void) sprintf(buf, "%c%" PRId64 " %s\n", type, stb.st_size,
- file);
- else
- (void) sprintf(buf, "%c%" PRId64 " %s_c%d\n", type, stb.st_size,
- file, copycnt);
- amt = strlen(buf);
+ if (copycnt < 2) {
+ amt = snprintf(buf, sizeof(buf), "%c%" PRId64 " %s\n",
+ type, stb.st_size, file);
+ } else {
+ amt = snprintf(buf, sizeof(buf), "%c%" PRId64 " %s_c%d\n",
+ type, stb.st_size, file, copycnt);
+ }
+ if (amt >= sizeof(buf)) {
+ syslog(LOG_ERR, "%s: file name too long", file);
+ sfres = ERROR;
+ goto return_sfres;
+ }
for (i = 0; ; i++) {
if (write(pfd, buf, amt) != amt ||
(resp = response(pp)) < 0 || resp == '\1') {
@@ -1157,8 +1164,8 @@ sendagain:
*/
if (type == '\3')
trstat_init(pp, file, job_dfcnt);
- for (i = 0; i < stb.st_size; i += SPL_BUFSIZ) {
- amt = SPL_BUFSIZ;
+ for (i = 0; i < stb.st_size; i += sizeof(buf)) {
+ amt = sizeof(buf);
if (i + amt > stb.st_size)
amt = stb.st_size - i;
if (sizerr == 0 && read(sfd, buf, amt) != amt)
@@ -1248,12 +1255,12 @@ return_sfres:
static void
wait4data(struct printer *pp, const char *dfile)
{
+ struct stat statdf;
const char *cp;
- int statres;
- u_int sleepreq;
size_t dlen, hlen;
time_t amtslept, cur_time, prev_mtime;
- struct stat statdf;
+ unsigned int sleepreq;
+ int statres;
/* Skip these checks if the print job is from the local host. */
dlen = strlen(dfile);
@@ -1338,10 +1345,11 @@ wait4data(struct printer *pp, const char *dfile)
static int
execfilter(struct printer *pp, char *f_cmd, char *f_av[], int infd, int outfd)
{
+ char buf[BUFSIZ];
+ char *slash;
+ FILE *errfp;
pid_t fpid, wpid;
int errfd, retcode, wstatus;
- FILE *errfp;
- char buf[BUFSIZ], *slash;
fpid = dofork(pp, DORETURN);
if (fpid != 0) {
@@ -1403,7 +1411,7 @@ execfilter(struct printer *pp, char *f_cmd, char *f_av[], int infd, int outfd)
*/
dup2(infd, STDIN_FILENO);
dup2(outfd, STDOUT_FILENO);
- errfd = open(tempstderr, O_WRONLY|O_TRUNC, 0664);
+ errfd = open(tempstderr, O_WRONLY | O_TRUNC, 0664);
if (errfd >= 0)
dup2(errfd, STDERR_FILENO);
closelog();
@@ -1476,7 +1484,7 @@ banner(struct printer *pp, char *name1, char *name2)
static char *
scnline(int key, char *p, int c)
{
- register int scnwidth;
+ int scnwidth;
for (scnwidth = WIDTH; --scnwidth;) {
key <<= 1;
@@ -1490,12 +1498,12 @@ scnline(int key, char *p, int c)
static void
scan_out(struct printer *pp, int scfd, char *scsp, int dlm)
{
- register char *strp;
- register int nchrs, j;
- char outbuf[LINELEN+1], *sp, c, cc;
- int d, scnhgt;
+ char outbuf[LINELEN + 1];
+ char *sp, *strp;
+ int d, j, nchrs, scnhgt;
+ char c, cc;
- for (scnhgt = 0; scnhgt++ < HEIGHT+DROP; ) {
+ for (scnhgt = 0; scnhgt++ < HEIGHT + DROP; ) {
strp = &outbuf[0];
sp = scsp;
for (nchrs = 0; ; ) {
@@ -1504,9 +1512,10 @@ scan_out(struct printer *pp, int scfd, char *scsp, int dlm)
for (j = WIDTH; --j;)
*strp++ = BACKGND;
else
- strp = scnline(scnkey[(int)c][scnhgt-1-d], strp, cc);
- if (*sp == dlm || *sp == '\0' ||
- nchrs++ >= pp->page_width/(WIDTH+1)-1)
+ strp = scnline(scnkey[(int)c][scnhgt - 1 - d],
+ strp, cc);
+ if (*sp == dlm || *sp == '\0' ||
+ nchrs++ >= pp->page_width / (WIDTH + 1) - 1)
break;
*strp++ = BACKGND;
*strp++ = BACKGND;
@@ -1522,8 +1531,7 @@ scan_out(struct printer *pp, int scfd, char *scsp, int dlm)
static int
dropit(int c)
{
- switch(c) {
-
+ switch (c) {
case TRC('_'):
case TRC(';'):
case TRC(','):
@@ -1533,7 +1541,6 @@ dropit(int c)
case TRC('q'):
case TRC('y'):
return (DROP);
-
default:
return (0);
}
@@ -1546,11 +1553,10 @@ dropit(int c)
static void
sendmail(struct printer *pp, char *userid, int bombed)
{
- register int i;
- int p[2], s;
- register const char *cp;
struct stat stb;
FILE *fp;
+ const char *cp;
+ int i, p[2], s;
pipe(p);
if ((s = dofork(pp, DORETURN)) == 0) { /* child */
@@ -1561,7 +1567,7 @@ sendmail(struct printer *pp, char *userid, int bombed)
cp++;
else
cp = _PATH_SENDMAIL;
- execl(_PATH_SENDMAIL, cp, "-t", (char *)0);
+ execl(_PATH_SENDMAIL, cp, "-t", NULL);
_exit(0);
} else if (s > 0) { /* parent */
dup2(p[1], STDOUT_FILENO);
@@ -1590,8 +1596,8 @@ sendmail(struct printer *pp, char *userid, int bombed)
break;
case FILTERERR:
cp = "FILTERERR";
- if (stat(tempstderr, &stb) < 0 || stb.st_size == 0
- || (fp = fopen(tempstderr, "r")) == NULL) {
+ if (stat(tempstderr, &stb) < 0 || stb.st_size == 0 ||
+ (fp = fopen(tempstderr, "r")) == NULL) {
printf("\nhad some errors and may not have printed\n");
break;
}
@@ -1623,9 +1629,9 @@ sendmail(struct printer *pp, char *userid, int bombed)
static int
dofork(const struct printer *pp, int action)
{
+ struct passwd *pwd;
pid_t forkpid;
int i, fail;
- struct passwd *pwd;
forkpid = -1;
if (daemon_uname == NULL) {
@@ -1704,7 +1710,6 @@ error_ret:
static void
abortpr(int signo __unused)
{
-
(void) unlink(tempstderr);
kill(0, SIGINT);
if (of_pid > 0)
@@ -1734,12 +1739,13 @@ init(struct printer *pp)
void
startprinting(const char *printer)
{
- struct printer myprinter, *pp = &myprinter;
+ struct printer myprinter;
+ struct printer *pp = &myprinter;
int status;
init_printer(pp);
status = getprintcap(printer, pp);
- switch(status) {
+ switch (status) {
case PCAPERR_OSERR:
syslog(LOG_ERR, "can't open printer description file: %m");
exit(1);
@@ -1760,8 +1766,8 @@ startprinting(const char *printer)
static void
openpr(const struct printer *pp)
{
- int p[2];
char *cp;
+ int p[2];
if (pp->remote) {
openrem(pp);
@@ -1772,7 +1778,7 @@ openpr(const struct printer *pp)
* local print queues. For remote machines, all 'of='
* filter processing is handled in sendfile(), and that
* does not use these global "output filter" variables.
- */
+ */
ofd = -1;
of_pid = 0;
return;
@@ -1806,8 +1812,7 @@ openpr(const struct printer *pp)
cp = pp->filters[LPF_OUTPUT];
else
cp++;
- execl(pp->filters[LPF_OUTPUT], cp, width, length,
- (char *)0);
+ execl(pp->filters[LPF_OUTPUT], cp, width, length, NULL);
syslog(LOG_ERR, "%s: execl(%s): %m", pp->printer,
pp->filters[LPF_OUTPUT]);
exit(1);
@@ -1827,11 +1832,10 @@ openpr(const struct printer *pp)
static void
opennet(const struct printer *pp)
{
- register int i;
- int resp;
- u_long port;
- char *ep;
void (*savealrm)(int);
+ char *ep;
+ unsigned long port;
+ int i, resp;
port = strtoul(pp->lp, &ep, 0);
if (*ep != '@' || port > 65535) {
@@ -1864,7 +1868,7 @@ opennet(const struct printer *pp)
pstatus(pp, "waiting for %s to come up",
pp->lp);
else
- pstatus(pp,
+ pstatus(pp,
"waiting for access to printer on %s",
pp->lp);
}
@@ -1879,7 +1883,7 @@ opennet(const struct printer *pp)
static void
opentty(const struct printer *pp)
{
- register int i;
+ int i;
for (i = 1; ; i = i < 32 ? i << 1 : i) {
pfd = open(pp->lp, pp->rw ? O_RDWR : O_WRONLY);
@@ -1892,7 +1896,7 @@ opentty(const struct printer *pp)
exit(1);
}
if (i == 1)
- pstatus(pp,
+ pstatus(pp,
"waiting for %s to become ready (offline?)",
pp->printer);
sleep(i);
@@ -1908,9 +1912,8 @@ opentty(const struct printer *pp)
static void
openrem(const struct printer *pp)
{
- register int i;
- int resp;
void (*savealrm)(int);
+ int i, resp;
for (i = 1; ; i = i < 256 ? i << 1 : i) {
resp = -1;
@@ -1920,16 +1923,15 @@ openrem(const struct printer *pp)
alarm(0);
(void)signal(SIGALRM, savealrm);
if (pfd >= 0) {
- if ((writel(pfd, "\2", pp->remote_queue, "\n",
- (char *)0)
- == 2 + strlen(pp->remote_queue))
- && (resp = response(pp)) == 0)
+ if (writel(pfd, "\2", pp->remote_queue, "\n", NULL) ==
+ 2 + strlen(pp->remote_queue) &&
+ (resp = response(pp)) == 0)
break;
(void) close(pfd);
}
if (i == 1) {
if (resp < 0)
- pstatus(pp, "waiting for %s to come up",
+ pstatus(pp, "waiting for %s to come up",
pp->remote_host);
else {
pstatus(pp,
@@ -1951,7 +1953,7 @@ setty(const struct printer *pp)
{
struct termios ttybuf;
- if (ioctl(pfd, TIOCEXCL, (char *)0) < 0) {
+ if (ioctl(pfd, TIOCEXCL, NULL) < 0) {
syslog(LOG_ERR, "%s: ioctl(TIOCEXCL): %m", pp->printer);
exit(1);
}
@@ -1980,22 +1982,23 @@ setty(const struct printer *pp)
static void
pstatus(const struct printer *pp, const char *msg, ...)
{
- int fd;
- char *buf;
va_list ap;
- va_start(ap, msg);
+ char *buf;
+ int fd;
umask(S_IWOTH);
- fd = open(pp->status_file, O_WRONLY|O_CREAT|O_EXLOCK, STAT_FILE_MODE);
+ fd = open(pp->status_file, O_WRONLY | O_CREAT | O_EXLOCK,
+ STAT_FILE_MODE);
if (fd < 0) {
syslog(LOG_ERR, "%s: open(%s): %m", pp->printer,
pp->status_file);
exit(1);
}
ftruncate(fd, 0);
+ va_start(ap, msg);
vasprintf(&buf, msg, ap);
va_end(ap);
- writel(fd, buf, "\n", (char *)0);
+ writel(fd, buf, "\n", NULL);
close(fd);
free(buf);
}
diff --git a/usr.sbin/makefs/cd9660/iso9660_rrip.c b/usr.sbin/makefs/cd9660/iso9660_rrip.c
index 31c6e38a96fe..a4a8ea85c559 100644
--- a/usr.sbin/makefs/cd9660/iso9660_rrip.c
+++ b/usr.sbin/makefs/cd9660/iso9660_rrip.c
@@ -740,24 +740,34 @@ cd9660node_rrip_nm(struct ISO_SUSP_ATTRIBUTES *p, cd9660node *file_node)
int
cd9660node_rrip_tf(struct ISO_SUSP_ATTRIBUTES *p, fsnode *_node)
{
- p->attr.rr_entry.TF.flags[0] = TF_MODIFY | TF_ACCESS | TF_ATTRIBUTES;
+ p->attr.rr_entry.TF.flags[0] = TF_CREATION | TF_MODIFY | TF_ACCESS |
+ TF_ATTRIBUTES;
p->attr.rr_entry.TF.h.length[0] = 5;
p->attr.rr_entry.TF.h.version[0] = 1;
/*
- * Need to add creation time, backup time,
+ * Need to add backup time,
* expiration time, and effective time.
*/
+#if HAVE_STRUCT_STAT_BIRTHTIME
cd9660_time_915(p->attr.rr_entry.TF.timestamp,
- _node->inode->st.st_mtime);
+ _node->inode->st.st_birthtime);
+#else
+ cd9660_time_915(p->attr.rr_entry.TF.timestamp,
+ _node->inode->st.st_ctime);
+#endif
p->attr.rr_entry.TF.h.length[0] += 7;
cd9660_time_915(p->attr.rr_entry.TF.timestamp + 7,
- _node->inode->st.st_atime);
+ _node->inode->st.st_mtime);
p->attr.rr_entry.TF.h.length[0] += 7;
cd9660_time_915(p->attr.rr_entry.TF.timestamp + 14,
+ _node->inode->st.st_atime);
+ p->attr.rr_entry.TF.h.length[0] += 7;
+
+ cd9660_time_915(p->attr.rr_entry.TF.timestamp + 21,
_node->inode->st.st_ctime);
p->attr.rr_entry.TF.h.length[0] += 7;
return 1;
diff --git a/usr.sbin/makefs/zfs/dsl.c b/usr.sbin/makefs/zfs/dsl.c
index 1977521d7f92..7a634272b7d4 100644
--- a/usr.sbin/makefs/zfs/dsl.c
+++ b/usr.sbin/makefs/zfs/dsl.c
@@ -28,6 +28,7 @@
* SUCH DAMAGE.
*/
+#include <sys/param.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
diff --git a/usr.sbin/mfiutil/Makefile b/usr.sbin/mfiutil/Makefile
index 49c0e688e8e2..d7ccb1f2d9c1 100644
--- a/usr.sbin/mfiutil/Makefile
+++ b/usr.sbin/mfiutil/Makefile
@@ -9,7 +9,7 @@ MLINKS= mfiutil.8 mrsasutil.8
CFLAGS.gcc+= -fno-builtin-strftime
-LIBADD= sbuf util
+LIBADD= cam sbuf util
# Here be dragons
.ifdef DEBUG
diff --git a/usr.sbin/mfiutil/mfi_drive.c b/usr.sbin/mfiutil/mfi_drive.c
index c7c5aeb02f14..88d24623ae27 100644
--- a/usr.sbin/mfiutil/mfi_drive.c
+++ b/usr.sbin/mfiutil/mfi_drive.c
@@ -42,6 +42,7 @@
#include <string.h>
#include <strings.h>
#include <unistd.h>
+#include <cam/cam.h>
#include <cam/scsi/scsi_all.h>
#include "mfiutil.h"
@@ -279,50 +280,14 @@ mfi_pd_get_info(int fd, uint16_t device_id, struct mfi_pd_info *info,
sizeof(struct mfi_pd_info), mbox, 2, statusp));
}
-static void
-cam_strvis(char *dst, const char *src, int srclen, int dstlen)
-{
-
- /* Trim leading/trailing spaces, nulls. */
- while (srclen > 0 && src[0] == ' ')
- src++, srclen--;
- while (srclen > 0
- && (src[srclen-1] == ' ' || src[srclen-1] == '\0'))
- srclen--;
-
- while (srclen > 0 && dstlen > 1) {
- char *cur_pos = dst;
-
- if (*src < 0x20) {
- /* SCSI-II Specifies that these should never occur. */
- /* non-printable character */
- if (dstlen > 4) {
- *cur_pos++ = '\\';
- *cur_pos++ = ((*src & 0300) >> 6) + '0';
- *cur_pos++ = ((*src & 0070) >> 3) + '0';
- *cur_pos++ = ((*src & 0007) >> 0) + '0';
- } else {
- *cur_pos++ = '?';
- }
- } else {
- /* normal character */
- *cur_pos++ = *src;
- }
- src++;
- srclen--;
- dstlen -= cur_pos - dst;
- dst = cur_pos;
- }
- *dst = '\0';
-}
-
/* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */
const char *
mfi_pd_inq_string(struct mfi_pd_info *info)
{
struct scsi_inquiry_data iqd, *inq_data = &iqd;
- char vendor[16], product[48], revision[16], rstr[12], serial[SID_VENDOR_SPECIFIC_0_SIZE];
- static char inq_string[64];
+ char vendor[SID_VENDOR_SIZE+1], product[SID_PRODUCT_SIZE+1],
+ revision[SID_REVISION_SIZE+1], rstr[9], serial[SID_VENDOR_SPECIFIC_0_SIZE+1];
+ static char inq_string[80];
memcpy(inq_data, info->inquiry_data,
(sizeof (iqd) < sizeof (info->inquiry_data))?
@@ -334,14 +299,14 @@ mfi_pd_inq_string(struct mfi_pd_info *info)
if (SID_QUAL(inq_data) != SID_QUAL_LU_CONNECTED)
return (NULL);
- cam_strvis(vendor, inq_data->vendor, sizeof(inq_data->vendor),
- sizeof(vendor));
- cam_strvis(product, inq_data->product, sizeof(inq_data->product),
- sizeof(product));
- cam_strvis(revision, inq_data->revision, sizeof(inq_data->revision),
- sizeof(revision));
- cam_strvis(serial, (char *)inq_data->vendor_specific0, sizeof(inq_data->vendor_specific0),
- sizeof(serial));
+ cam_strvis_flag(vendor, inq_data->vendor, sizeof(inq_data->vendor),
+ sizeof(vendor), CAM_STRVIS_FLAG_NONASCII_TRIM);
+ cam_strvis_flag(product, inq_data->product, sizeof(inq_data->product),
+ sizeof(product), CAM_STRVIS_FLAG_NONASCII_TRIM);
+ cam_strvis_flag(revision, inq_data->revision, sizeof(inq_data->revision),
+ sizeof(revision), CAM_STRVIS_FLAG_NONASCII_TRIM);
+ cam_strvis_flag(serial, (char *)inq_data->vendor_specific0, sizeof(inq_data->vendor_specific0),
+ sizeof(serial), CAM_STRVIS_FLAG_NONASCII_SPC);
/* Hack for SATA disks, no idea how to tell speed. */
if (strcmp(vendor, "ATA") == 0) {
diff --git a/usr.sbin/mfiutil/mfiutil.8 b/usr.sbin/mfiutil/mfiutil.8
index 9f8cacf7f16d..25c2dfe4eb66 100644
--- a/usr.sbin/mfiutil/mfiutil.8
+++ b/usr.sbin/mfiutil/mfiutil.8
@@ -25,13 +25,13 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd September 2, 2011
+.Dd May 23, 2026
.Dt MFIUTIL 8
.Os
.Sh NAME
.Nm mfiutil ,
.Nm mrsasutil
-.Nd Utility for managing LSI MegaRAID SAS controllers
+.Nd manage Broadcom/LSI MegaRAID SAS controllers
.Sh SYNOPSIS
.Nm
.Cm version
@@ -258,7 +258,7 @@
.Sh DESCRIPTION
The
.Nm
-utility can be used to display or modify various parameters on LSI
+utility can be used to display or modify various parameters on Broadcom/LSI
MegaRAID SAS RAID controllers.
Each invocation of
.Nm
@@ -480,7 +480,8 @@ The firmware should kick off an array rebuild on its own if a failed drive
is marked as a rebuild drive.
.It Cm syspd Ar drive
Present the drive to the host operating system as a disk SYSPD block device in
-the format /dev/mfisyspdX.
+the format
+.Pa /dev/mfisyspdX .
Clear this flag with
.Cm good
.Ar drive
diff --git a/usr.sbin/mixer/mixer.8 b/usr.sbin/mixer/mixer.8
index bdff0dbedc11..2ba6cf031eb0 100644
--- a/usr.sbin/mixer/mixer.8
+++ b/usr.sbin/mixer/mixer.8
@@ -1,5 +1,5 @@
.\"-
-.\" Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
+.\" Copyright (c) 2021-2026 Christos Margiolis <christos@FreeBSD.org>
.\"
.\" Permission is hereby granted, free of charge, to any person obtaining a copy
.\" of this software and associated documentation files (the "Software"), to deal
@@ -19,7 +19,7 @@
.\" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
.\" THE SOFTWARE.
.\"
-.Dd February 26, 2026
+.Dd April 4, 2026
.Dt MIXER 8
.Os
.Sh NAME
@@ -113,9 +113,7 @@ with one of the available devices):
.Oo Cm \&+ | Cm \&- Oc Ar lvol Oo % Oc
.Oo Cm \&: Oo Cm \&+ | Cm \&- Oc Ar rvol Oo % Oc Oc
.Xc
-.It Ar dev Cm .mute Ta Cm 0 | 1 | ^
.It Ar dev Cm .mute Ta Cm off | on | toggle
-.It Ar dev Cm .recsrc Ta Cm ^ | + | - | =
.It Ar dev Cm .recsrc Ta Cm toggle | add | remove | set
.El
.Sm on
@@ -142,7 +140,7 @@ current settings by the amount specified.
Note that relative percentages are still relative to 1.0, not to the current
value.
If the volume is currently 0.40 and an adjustment of +20% is specified, then
-thet final volume will be set to 0.60.
+the final volume will be set to 0.60.
.Pp
Volume can also be set using the shorthand
.Ar dev Ns Cm =value .
@@ -153,13 +151,13 @@ The
control (un)mutes a device.
The following values are available:
.Bl -tag -width "xxxxxxxxxx" -offset indent
-.It Cm 0 | off
+.It Cm off
unmutes
.Ar dev
-.It Cm 1 | on
+.It Cm on
mutes
.Ar dev
-.It Cm ^ | toggle
+.It Cm toggle
toggles the mute of
.Ar dev
.El
@@ -177,19 +175,19 @@ on a
.Sy rec
device:
.Bl -tag -width "xxxxxxxxxx" -offset indent
-.It Cm ^ | toggle
+.It Cm toggle
toggles
.Ar dev
of possible recording devices
-.It Cm + | add
+.It Cm add
adds
.Ar dev
to possible recording devices
-.It Cm - | remove
+.It Cm remove
removes
.Ar dev
from possible recording devices
-.It Cm = | set
+.It Cm set
makes
.Ar dev
the only recording device.
diff --git a/usr.sbin/mixer/mixer.c b/usr.sbin/mixer/mixer.c
index a0fc9705a301..e8c36d0c8bf7 100644
--- a/usr.sbin/mixer/mixer.c
+++ b/usr.sbin/mixer/mixer.c
@@ -1,5 +1,5 @@
/*-
- * Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
+ * Copyright (c) 2021-2026 Christos Margiolis <christos@FreeBSD.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -428,16 +428,13 @@ mod_mute(struct mix_dev *d, void *p)
m = d->parent_mixer;
cp = mixer_get_ctl(m->dev, C_MUT);
val = p;
- if (strncmp(val, "off", strlen(val)) == 0 ||
- strncmp(val, "0", strlen(val)) == 0)
+ if (strncmp(val, "off", strlen(val)) == 0) {
opt = MIX_UNMUTE;
- else if (strncmp(val, "on", strlen(val)) == 0 ||
- strncmp(val, "1", strlen(val)) == 0)
+ } else if (strncmp(val, "on", strlen(val)) == 0) {
opt = MIX_MUTE;
- else if (strncmp(val, "toggle", strlen(val)) == 0 ||
- strncmp(val, "^", strlen(val)) == 0)
+ } else if (strncmp(val, "toggle", strlen(val)) == 0) {
opt = MIX_TOGGLEMUTE;
- else {
+ } else {
warnx("%s: no such modifier", val);
return (-1);
}
@@ -464,19 +461,15 @@ mod_recsrc(struct mix_dev *d, void *p)
m = d->parent_mixer;
cp = mixer_get_ctl(m->dev, C_SRC);
val = p;
- if (strncmp(val, "add", strlen(val)) == 0 ||
- strncmp(val, "+", strlen(val)) == 0)
+ if (strncmp(val, "add", strlen(val)) == 0) {
opt = MIX_ADDRECSRC;
- else if (strncmp(val, "remove", strlen(val)) == 0 ||
- strncmp(val, "-", strlen(val)) == 0)
+ } else if (strncmp(val, "remove", strlen(val)) == 0) {
opt = MIX_REMOVERECSRC;
- else if (strncmp(val, "set", strlen(val)) == 0 ||
- strncmp(val, "=", strlen(val)) == 0)
+ } else if (strncmp(val, "set", strlen(val)) == 0) {
opt = MIX_SETRECSRC;
- else if (strncmp(val, "toggle", strlen(val)) == 0 ||
- strncmp(val, "^", strlen(val)) == 0)
+ } else if (strncmp(val, "toggle", strlen(val)) == 0) {
opt = MIX_TOGGLERECSRC;
- else {
+ } else {
warnx("%s: no such modifier", val);
return (-1);
}
diff --git a/usr.sbin/mixer/tests/mixer_test.sh b/usr.sbin/mixer/tests/mixer_test.sh
index c48512db5ec7..da5f4d7566bc 100755
--- a/usr.sbin/mixer/tests/mixer_test.sh
+++ b/usr.sbin/mixer/tests/mixer_test.sh
@@ -1,7 +1,7 @@
#
# SPDX-License-Identifier: BSD-2-Clause
#
-# Copyright (c) 2024 The FreeBSD Foundation
+# Copyright (c) 2024-2026 The FreeBSD Foundation
#
# This software was developed by Christos Margiolis <christos@FreeBSD.org>
# under sponsorship from the FreeBSD Foundation.
@@ -210,16 +210,6 @@ mute_body()
atf_check -o ignore -e empty mixer vol.mute=toggle
atf_check -o match:"=off" mixer vol.mute
- # Test deprecated interface
- atf_check -o ignore -e empty mixer vol.mute=0
- atf_check -o match:"=off" mixer vol.mute
-
- atf_check -o ignore -e empty mixer vol.mute=1
- atf_check -o match:"=on" mixer vol.mute
-
- atf_check -o ignore -e empty mixer vol.mute=^
- atf_check -o match:"=off" mixer vol.mute
-
# Test wrong values
atf_check -o ignore -e not-empty mixer vol.mute=foobar
atf_check -o ignore -e not-empty mixer vol.mute=10
@@ -248,12 +238,6 @@ recsrc_body()
atf_check -o ignore -e empty mixer ${recsrc}.recsrc=set
atf_check -o ignore -e empty mixer ${recsrc}.recsrc=toggle
- # Test deprecated interface
- atf_check -o ignore -e empty mixer ${recsrc}.recsrc=+
- atf_check -o ignore -e empty mixer ${recsrc}.recsrc=-
- atf_check -o ignore -e empty mixer ${recsrc}.recsrc==
- atf_check -o ignore -e empty mixer ${recsrc}.recsrc=^
-
# Test wrong values
atf_check -o ignore -e not-empty mixer ${recsrc}.recsrc=foobar
atf_check -o ignore -e not-empty mixer ${recsrc}.recsrc=10
diff --git a/usr.sbin/mountd/mountd.c b/usr.sbin/mountd/mountd.c
index 7e2f9b6e73bd..e9c26b8c83c5 100644
--- a/usr.sbin/mountd/mountd.c
+++ b/usr.sbin/mountd/mountd.c
@@ -323,7 +323,7 @@ static gid_t *tmp_groups = NULL;
#define OP_MASKLEN 0x200
#define OP_SEC 0x400
#define OP_CLASSMASK 0x800 /* mask not specified, is Class A/B/C default */
-#define OP_NOTROOT 0x1000 /* Mark the the mount path is not a fs root */
+#define OP_NOTROOT 0x1000 /* Mark the mount path is not a fs root */
#ifdef DEBUG
static int debug = 1;
diff --git a/usr.sbin/mtest/mtest.c b/usr.sbin/mtest/mtest.c
index 941379b01bc9..1edeaec2d35b 100644
--- a/usr.sbin/mtest/mtest.c
+++ b/usr.sbin/mtest/mtest.c
@@ -1,7 +1,7 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
- * Copyright (c) 2007-2009 Bruce Simpson.
+ * Copyright (c) 2007-2026 Bruce Simpson.
* Copyright (c) 2000 Wilbert De Graaf.
* All rights reserved.
*
@@ -104,6 +104,10 @@ typedef union mrequnion mrequnion_t;
#ifdef INET
static int __ifindex_to_primary_ip(const uint32_t, struct in_addr *);
#endif
+#if defined(INET) && defined(INET6)
+static struct in6_addr *
+ __in6_v4_to_v4mapped(struct in6_addr *, struct in_addr *);
+#endif
static uint32_t parse_cmd_args(sockunion_t *, sockunion_t *,
const char *, const char *, const char *);
static void process_file(char *, int, int);
@@ -143,6 +147,23 @@ su_cmp(const void *a, const void *b)
return (memcmp(sua, sub, sua->sa.sa_len));
}
+#if defined(INET) && defined(INET6)
+/*
+ * Internal: Express an IPv4 address within IPv6 as IPv4-mapped.
+ * Converse of IN6_IS_ADDR_V4MAPPED(a).
+ */
+static struct in6_addr *
+__in6_v4_to_v4mapped(struct in6_addr *a6 , struct in_addr *a4)
+{
+
+ a6->s6_addr32[0] = 0;
+ a6->s6_addr32[1] = 0;
+ a6->s6_addr32[2] = ntohl(0x0000ffff);
+ a6->s6_addr32[3] = a4->s_addr;
+ return (a6);
+}
+#endif /* defined(INET) && defined(INET6) */
+
#ifdef INET
/*
* Internal: Map an interface index to primary IPv4 address.
@@ -519,6 +540,55 @@ process_cmd(char *cmd, int s, int s6, FILE *fp __unused)
break;
/*
+ * Exercise the non-IETF-ratified extension to RFC 3493 to
+ * join IPv4 groups as IPv4 mapped addresses on IPv6 sockets.
+ * Only ASM joins are supported; further use of the full-state API
+ * may result in undefined behaviour.
+ */
+ case 'u':
+ case 'v':
+ toptname = "";
+ sscanf(line, "%s %s", str1, str2);
+ ifindex = parse_cmd_args(&su, NULL, str1, str2, NULL);
+ if (ifindex == 0) {
+ printf("-1\n");
+ break;
+ }
+ af = su.sa.sa_family;
+#if defined(INET) && defined(INET6)
+ if (af == AF_INET) {
+ struct in_addr gaddr;
+
+ gaddr = su.sin.sin_addr;
+ __in6_v4_to_v4mapped(&su.sin6.sin6_addr, &gaddr);
+ mr.mr6.ipv6mr_multiaddr = su.sin6.sin6_addr;
+ mr.mr6.ipv6mr_interface = ifindex;
+ optname = (*cmd == 'u') ?
+ IPV6_JOIN_GROUP :
+ IPV6_LEAVE_GROUP;
+ toptname = (*cmd == 'u') ?
+ "IPV6_JOIN_GROUP" :
+ "IPV6_LEAVE_GROUP";
+ level = IPPROTO_IPV6;
+ optval = (void *)&mr.mr6;
+ optlen = sizeof(mr.mr6);
+ if (s6 < 0) {
+ warnc(EPROTONOSUPPORT, "setsockopt %s",
+ toptname);
+ } else if (setsockopt(s6, level, optname, optval,
+ optlen) == 0) {
+ printf("ok\n");
+ break;
+ } else {
+ warn("setsockopt %s", toptname);
+ }
+ }
+#endif /* defined(INET) && defined(INET6) */
+ /* FALLTHROUGH */
+ printf("-1\n");
+ break;
+
+ /*
* Set the socket to include or exclude filter mode, and
* add some sources to the filterlist, using the full-state API.
*/
@@ -833,6 +903,8 @@ usage(void)
printf("j mcast-addr ifname [src-addr] - join IP multicast group\n");
printf("l mcast-addr ifname [src-addr] - leave IP multicast group\n");
+ printf("u mcast-addr ifname - join IPv4-mapped group on IPv6 socket\n");
+ printf("v mcast-addr ifname - leave IPv4-mapped group on IPv6 socket\n");
printf(
"i mcast-addr ifname n - set n include mode src filter\n");
printf(
diff --git a/usr.sbin/ndp/Makefile b/usr.sbin/ndp/Makefile
index 998860d00a69..aad2380bc879 100644
--- a/usr.sbin/ndp/Makefile
+++ b/usr.sbin/ndp/Makefile
@@ -23,11 +23,6 @@ LIBADD= xo
CFLAGS+= -I. -I${.CURDIR}
CFLAGS+= -D_U_=""
-.if ${MK_EXPERIMENTAL} != "no"
-CFLAGS+= -DEXPERIMENTAL
-CFLAGS+= -DDRAFT_IETF_6MAN_IPV6ONLY_FLAG
-.endif
-
.if ${MK_NETLINK_SUPPORT} != "no"
SRCS+= ndp_netlink.c
.else
diff --git a/usr.sbin/ndp/ndp.c b/usr.sbin/ndp/ndp.c
index cbca8ec20941..7521be752c80 100644
--- a/usr.sbin/ndp/ndp.c
+++ b/usr.sbin/ndp/ndp.c
@@ -1225,12 +1225,7 @@ rtrlist(void)
*pflags++ = 'O';
xo_emit("{el:%s}", "other");
}
-#ifdef DRAFT_IETF_6MAN_IPV6ONLY_FLAG
- if (p->flags & ND_RA_FLAG_IPV6_ONLY) {
- *pflags++ = 'S';
- xo_emit("{el:%s}", "ipv6only");
- }
-#endif
+
xo_close_list("flags_pretty");
xo_emit(", flags={:flags/%s}", rflags);
diff --git a/usr.sbin/newsyslog/newsyslog.8 b/usr.sbin/newsyslog/newsyslog.8
index aa89ef4b779a..eb927f7e0a68 100644
--- a/usr.sbin/newsyslog/newsyslog.8
+++ b/usr.sbin/newsyslog/newsyslog.8
@@ -14,12 +14,12 @@
.\" the suitability of this software for any purpose. It is
.\" provided "as is" without express or implied warranty.
.\"
-.Dd September 22, 2025
+.Dd March 8, 2026
.Dt NEWSYSLOG 8
.Os
.Sh NAME
.Nm newsyslog
-.Nd maintain system log files to manageable sizes
+.Nd rotate system message log files to maintain manageable sizes
.Sh SYNOPSIS
.Nm
.Op Fl CFNPnrsv
diff --git a/usr.sbin/newsyslog/newsyslog.c b/usr.sbin/newsyslog/newsyslog.c
index 084aeb36b052..fc8ba7df79af 100644
--- a/usr.sbin/newsyslog/newsyslog.c
+++ b/usr.sbin/newsyslog/newsyslog.c
@@ -1955,9 +1955,9 @@ do_rotate(const struct conf_entry *ent)
if (noaction)
printf("\tmv %s %s\n", zfile1, zfile2);
- else {
- /* XXX - Ought to be checking for failure! */
- (void)rename(zfile1, zfile2);
+ else if (rename(zfile1, zfile2) != 0) {
+ warn("can't mv %s to %s", zfile1, zfile2);
+ continue;
}
change_attrs(zfile2, ent);
if (ent->compress && strlen(logfile_suffix) == 0) {
diff --git a/usr.sbin/newsyslog/newsyslog.conf.5 b/usr.sbin/newsyslog/newsyslog.conf.5
index b0c1e78eb085..b26df4bb1a61 100644
--- a/usr.sbin/newsyslog/newsyslog.conf.5
+++ b/usr.sbin/newsyslog/newsyslog.conf.5
@@ -18,14 +18,12 @@
.\" the suitability of this software for any purpose. It is
.\" provided "as is" without express or implied warranty.
.\"
-.Dd February 4, 2026
+.Dd April 15, 2026
.Dt NEWSYSLOG.CONF 5
.Os
.Sh NAME
.Nm newsyslog.conf
-.Nd
-.Xr newsyslog 8
-configuration file
+.Nd system message log rotator configuration file
.Sh DESCRIPTION
The
.Nm
@@ -157,10 +155,10 @@ When the size of the log file reaches
.Ar size ,
in kilobytes by default, or with suffixes like k, M, G, ... as supported by
.Xr expand_number 3 ,
-the log file will be trimmed as described above.
+the log file will be rotated.
If this field contains an asterisk
.Pq Ql * ,
-the log file will not be trimmed based on size.
+the log file will not be rotated based on size.
.It Ar when
The
.Ar when
@@ -185,10 +183,10 @@ Additionally, the format may also be constructed with a
sign along with a rotation time specification of once
a day, once a week, or once a month.
.Pp
-Time based trimming happens only if
+Time based rotation happens only if
.Xr newsyslog 8
is run within one hour of the specified time.
-If an interval is specified, the log file will be trimmed if that many
+If an interval is specified, the log file will be rotated if that many
hours have passed since the last rotation.
When both a time and an interval are
specified then both conditions must be satisfied for the rotation to
diff --git a/usr.sbin/nfsd/nfsv4.4 b/usr.sbin/nfsd/nfsv4.4
index e96e507e23ad..9ec775613b47 100644
--- a/usr.sbin/nfsd/nfsv4.4
+++ b/usr.sbin/nfsd/nfsv4.4
@@ -22,7 +22,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd January 8, 2024
+.Dd April, 8, 2026
.Dt NFSV4 4
.Os
.Sh NAME
@@ -106,16 +106,23 @@ The
allows a limited subset of operations to be performed on non-exported subtrees
of the local file system, so that traversal of the tree to the exported
subtrees is possible.
-As such, the ``<rootdir>'' can be in a non-exported file system.
+As such, the
+.Ql <rootdir>
+can be in a non-exported file system.
The exception is ZFS, which checks exports and, as such, all ZFS file systems
-below the ``<rootdir>'' must be exported.
+below the
+.Ql <rootdir>
+must be exported.
However,
the entire tree that is rooted at that point must be in local file systems
that are of types that can be NFS exported.
Since the
.Nm
-file system is rooted at ``<rootdir>'', setting this to anything other
-than ``/'' will result in clients being required to use different mount
+file system is rooted at
+.Ql <rootdir> ,
+setting this to anything other than
+.Ql /
+will result in clients being required to use different mount
paths for
.Nm
than for NFS Version 2 or 3.
@@ -132,9 +139,12 @@ take the form:
<user>@<dns.domain>
.Ed
.sp
-where ``<dns.domain>'' is not the same as the DNS domain used
+where
+.Ql <dns.domain>
+is not the same as the DNS domain used
for host name lookups, but is usually set to the same string.
-Most systems set this ``<dns.domain>''
+Most systems set this
+.Ql <dns.domain>
to the domain name part of the machine's
.Xr hostname 1
by default.
@@ -154,26 +164,32 @@ either client or server, this daemon must be running.
The form where the numbers are in the strings can only be used for AUTH_SYS.
To configure your systems this way, the
.Xr nfsuserd 8
-daemon does not need to be running on the server, but the following sysctls
-need to be set to 1 on the server.
+daemon should not be running on the NFS systems,
+but the following sysctls
+need to be set to 1 on the NFS server.
.sp
.Bd -literal -offset indent -compact
vfs.nfs.enable_uidtostring
vfs.nfsd.enable_stringtouid
.Ed
.sp
-On the client, the sysctl
+However, on the NFS client
+only the sysctl
.sp
.Bd -literal -offset indent -compact
vfs.nfs.enable_uidtostring
.Ed
.sp
-must be set to 1 and the
-.Xr nfsuserd 8
-daemon does not need to be running.
+needs to be set to 1.
.Pp
-If these strings are not configured correctly, ``ls -l'' will typically
-report a lot of ``nobody'' and ``nogroup'' ownerships.
+If these strings are not configured correctly,
+.Ql ls -l
+will typically
+report a lot of
+.Ql nobody
+and
+.Ql nogroup
+ownerships.
.Pp
Although uid/gid numbers are no longer used in the
.Nm
@@ -204,8 +220,12 @@ plus
nfsuserd_enable="YES"
.Ed
.sp
-if the server is using the ``<user>@<domain>'' form of user/group strings or
-is using the ``-manage-gids'' option for
+if the server is using the
+.Ql <user>@<domain>
+form of user/group strings or
+is using the
+.Ql -manage-gids
+option for
.Xr nfsuserd 8 .
.Pp
In addition, you can set:
@@ -216,7 +236,9 @@ nfsv4_server_only="YES"
.sp
to disable support for NFSv2 and NFSv3.
.Pp
-You will also need to add at least one ``V4:'' line to the
+You will also need to add at least one
+.Ql V4:
+line to the
.Xr exports 5
file for
.Nm
@@ -246,7 +268,9 @@ Disabling local locking can only be done if neither local accesses
to the exported file systems nor the NLM is operating on them.
.El
.sp
-Note that Samba server access would be considered ``local access'' for the above
+Note that Samba server access would be considered
+.Ql local access
+for the above
discussion.
.Pp
To build a kernel with the NFS server that supports
@@ -263,12 +287,16 @@ file.
.Sh CLIENT MOUNTS
To do an
.Nm
-mount, specify the ``nfsv4'' option on the
+mount, specify the
+.Ql nfsv4
+option on the
.Xr mount_nfs 8
command line.
This will force use of the client that supports
.Nm
-plus set ``tcp'' and
+plus set
+.Ql tcp
+and
.Nm .
.Pp
The
@@ -326,8 +354,8 @@ where the first 4 Ns are the host IP address and the last two are the
port# in network byte order (all decimal #s in the range 0-255).
.Pp
For NFSv4.1 and NFSv4.2, the callback path (called a backchannel) uses the
-same TCP connection as the mount, so none of the above applies and should
-work through gateways without any issues.
+same TCP connection as the mount and, as such, no additional
+configuration is needed for the callback path.
.Pp
To build a kernel with the client that supports
.Nm
@@ -345,7 +373,10 @@ Options can be specified for the
.Xr nfsuserd 8
and
.Xr nfscbd 8
-daemons at boot time via the ``nfsuserd_flags'' and ``nfscbd_flags''
+daemons at boot time via the
+.Ql nfsuserd_flags
+and
+.Ql nfscbd_flags
.Xr rc.conf 5
variables.
.Pp
@@ -356,6 +387,105 @@ It occurs when an nfsd thread tries to do an NFSv4
/ Close RPC as part of acquiring a new vnode.
If all other nfsd threads are blocked waiting for lock(s) held by this nfsd
thread, then there is no nfsd thread to service the Close RPC.
+.Sh NFSv4 ROOT FILE SYSTEM
+.Pp
+For an
+.Nm
+root file system, there are a few things that need
+to be done beyond what is needed for an NFSv3 root file system.
+The NFS server must be configured with the
+.Ql <rootdir>
+as
+.Pa /
+so that
+the mount path is the same for
+.Nm
+as NFSv3.
+This is required because the bootstrap code still uses NFSv3.
+.Pp
+The following additional changes are required in the exported
+root file system.
+.sp
+In
+.Xr fstab 5 ,
+the
+.Ql nfsv4
+mount option must be specified in
+the line for the root mount.
+.sp
+In
+.Pa /boot/loader.conf
+the line
+.sp
+.Bd -literal -offset indent -compact
+boot.nfsroot.options="nfsv4"
+.Ed
+.sp
+is needed to tell the kernel to use
+.Nm
+for the root mount.
+Additional mount options may be specified.
+.sp
+If your
+.Nm
+setup is using the form where the uid/gid numbers are in strings,
+the sysctl
+.Ql vfs.nfs.enable_uidtostring
+must be set to one before any NFSv4 read/write mounts are done.
+The recommended way to do this is to put
+.sp
+.Bd -literal -offset indent -compact
+vfs.nfs.enable_uidtostring=1
+.Ed
+.sp
+in
+.Pa /etc/sysctl.conf
+in the FS exported root file system.
+Alternately, if your
+.Nm
+setup is using
+.Ql <user>@<dns.domain>
+via
+.Xr nfsuserd 8
+for uids/gids, then you need the following additional line in
+.Pa /boot/loader.conf
+.sp
+.Bd -literal -offset indent -compact
+boot.nfsroot.user_domain="<dns.domain>"
+.Ed
+.sp
+which tells the booting kernel that you are going to be
+doing mapping via
+.Xr nfsuserd 8
+and what your
+.Ql <dns.domain>
+is.
+The booting kernel will set a few critical mappings to allow
+the boot to proceed until the
+.Xr nfsuserd 8
+daemon is started.
+Then in
+.Pa /etc/rc.conf
+you need the lines
+.sp
+.Bd -literal -offset indent -compact
+nfsuserd_enable="YES"
+nfsuserd_flags="-domain <dns.domain>"
+.Ed
+.sp
+to start the daemon.
+.Pp
+Beyond this, the setup should be the same as would be used
+for an NFSv3 root file system.
+See section
+.Ql Diskless Operation with PXE
+in the
+.Ql Advanced Networking
+chapter of the
+.Ql FreeBSD Handbook ,
+although configuring hosts as fixed addresses in your
+.Ql dhcpd.conf
+might be preferable.
.Sh FILES
.Bl -tag -width /var/db/nfs-stablerestart.bak -compact
.It Pa /var/db/nfs-stablerestart
diff --git a/usr.sbin/nfsd/pnfs.4 b/usr.sbin/nfsd/pnfs.4
index babd221a6d5a..d9818c051df4 100644
--- a/usr.sbin/nfsd/pnfs.4
+++ b/usr.sbin/nfsd/pnfs.4
@@ -215,7 +215,7 @@ set the sysctl
.Dq vfs.nfsd.flexlinuxhack
to one so that it works around
the Linux client driver's limitations.
-Wihout this sysctl being set, there will be access errors, since the Linux
+Without this sysctl being set, there will be access errors, since the Linux
client will use the authenticator in the layout (uid=999, gid=999) and not
the authenticator specified in the RPC header.
.Pp
diff --git a/usr.sbin/nfsuserd/nfsuserd.c b/usr.sbin/nfsuserd/nfsuserd.c
index 058253beaf95..9326dd9018e0 100644
--- a/usr.sbin/nfsuserd/nfsuserd.c
+++ b/usr.sbin/nfsuserd/nfsuserd.c
@@ -50,6 +50,7 @@
#include <fs/nfs/nfsproto.h>
#include <fs/nfs/nfskpiport.h>
#include <fs/nfs/nfs.h>
+#include <fs/nfs/nfsid.h>
#include <ctype.h>
#include <err.h>
diff --git a/usr.sbin/nvram/Makefile b/usr.sbin/nvram/Makefile
index 7d27790ed132..4988763c73b4 100644
--- a/usr.sbin/nvram/Makefile
+++ b/usr.sbin/nvram/Makefile
@@ -1,5 +1,4 @@
PROG= nvram
MAN= nvram.8
-MANSUBDIR= /powerpc
.include <bsd.prog.mk>
diff --git a/usr.sbin/nvram/nvram.8 b/usr.sbin/nvram/nvram.8
index f0b63cdbc731..c326551b72c9 100644
--- a/usr.sbin/nvram/nvram.8
+++ b/usr.sbin/nvram/nvram.8
@@ -1,4 +1,6 @@
-.\"-
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
.\" Copyright (c) 2006 Maxim Sobolev <sobomax@FreeBSD.org>
.\" All rights reserved.
.\"
@@ -51,7 +53,7 @@ following by
sign.
.Pp
The following options are available:
-.Bl -tag -width indent
+.Bl -tag -width "-d name"
.It Fl d Ar name
Delete the variable selected by
.Ar name
diff --git a/usr.sbin/pciconf/pciconf.8 b/usr.sbin/pciconf/pciconf.8
index 7958a538f3d9..acb5fc4d039d 100644
--- a/usr.sbin/pciconf/pciconf.8
+++ b/usr.sbin/pciconf/pciconf.8
@@ -23,7 +23,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd March 10, 2026
+.Dd April 24, 2026
.Dt PCICONF 8
.Os
.Sh NAME
@@ -42,6 +42,10 @@
.Fl w Oo Fl b | h Oc Ar device addr value
.Nm
.Fl D Oo Fl b | h | x Oc Ar device bar Op Ar start Op Ns Ar count
+.Nm
+.Fl R Oo Fl b | h | x Oc Ar device bar start Ns Op : Ns Ar end
+.Nm
+.Fl W Oo Fl b | h | x Oc Ar device bar offset value
.Sh DESCRIPTION
The
.Nm
@@ -341,7 +345,8 @@ of device
.Pp
The
.Fl D
-option request a dump of the specified BAR.
+option requests a dump of the BAR specified by its offset in config space
+.Ar bar .
Dump is performed to the standard output, raw register values
are written.
Use
@@ -353,9 +358,25 @@ Optionally, the
and
.Ar count
of the registers dumped can be specified, in multiple of the operation width,
-see next paragraph.
+see below.
+.Pp
+The
+.Fl R
+option reads from the BAR specified by its offset in config space
+.Ar bar .
+It starts reading at byte offset
+.Ar start .
+The optional last argument
+.Ar end
+specifies a range to read.
+The
+.Fl W
+option writes the
+.Ar value
+to the BAR at byte offset
+.Ar offset .
.Pp
-For read, write, and dump operations, the flags
+For read, write, BAR dump, BAR read, and BAR write operations, the flags
.Fl b ,
.Fl h ,
and
@@ -369,7 +390,7 @@ indicates a halfword (two-byte) operation.
indicates a quadword (four-byte) operation.
The default is to read or
write a longword (four bytes).
-The quadword mode is only valid for BAR dump.
+The quadword mode is only valid for BAR dump, BAR read, and BAR write.
.Sh ENVIRONMENT
PCI vendor and device information is read from
.Pa /usr/local/share/pciids/pci.ids .
diff --git a/usr.sbin/pciconf/pciconf.c b/usr.sbin/pciconf/pciconf.c
index 7da8aeadae93..f3c6a413826f 100644
--- a/usr.sbin/pciconf/pciconf.c
+++ b/usr.sbin/pciconf/pciconf.c
@@ -91,6 +91,10 @@ static void writeit(const char *, const char *, const char *, int);
static void chkattached(const char *);
static void dump_bar(const char *name, const char *reg, const char *bar_start,
const char *bar_count, int width, int verbose);
+static void read_bar(const char *name, const char *bar, const char *start_end,
+ int width);
+static void write_bar(const char *name, const char *bar, const char *offset_str,
+ const char *value_str, int width);
static int exitstatus = 0;
@@ -104,7 +108,9 @@ usage(void)
" pciconf -a device\n"
" pciconf -r [-b | -h] device addr[:addr2]\n"
" pciconf -w [-b | -h] device addr value\n"
- " pciconf -D [-b | -h | -x] device bar [start [count]]"
+ " pciconf -D [-b | -h | -x] device bar [start [count]]\n"
+ " pciconf -R [-b | -h | -x] device bar start[:end]\n"
+ " pciconf -W [-b | -h | -x] device bar offset value"
"\n");
exit(1);
}
@@ -113,14 +119,15 @@ int
main(int argc, char **argv)
{
int c, width;
- enum { NONE, LIST, TREE, READ, WRITE, ATTACHED, DUMPBAR } mode;
+ enum { NONE, LIST, TREE, READ, WRITE, ATTACHED, DUMPBAR, READBAR,
+ WRITEBAR } mode;
int compact, bars, bridge, caps, errors, verbose, vpd;
mode = NONE;
compact = bars = bridge = caps = errors = verbose = vpd = 0;
width = 4;
- while ((c = getopt(argc, argv, "aBbcDehlrtwVvx")) != -1) {
+ while ((c = getopt(argc, argv, "aBbcDehlRrtWwVvx")) != -1) {
switch(c) {
case 'a':
mode = ATTACHED;
@@ -157,6 +164,10 @@ main(int argc, char **argv)
mode = LIST;
break;
+ case 'R':
+ mode = READBAR;
+ break;
+
case 'r':
mode = READ;
break;
@@ -164,6 +175,11 @@ main(int argc, char **argv)
case 't':
mode = TREE;
break;
+
+ case 'W':
+ mode = WRITEBAR;
+ break;
+
case 'w':
mode = WRITE;
break;
@@ -221,6 +237,18 @@ main(int argc, char **argv)
optind + 3 < argc ? argv[optind + 3] : NULL,
width, verbose);
break;
+ case READBAR:
+ if (optind + 3 != argc)
+ usage();
+ read_bar(argv[optind], argv[optind + 1], argv[optind + 2],
+ width);
+ break;
+ case WRITEBAR:
+ if (optind + 4 != argc)
+ usage();
+ write_bar(argv[optind], argv[optind + 1], argv[optind + 2],
+ argv[optind + 3], width);
+ break;
default:
usage();
}
@@ -1290,20 +1318,13 @@ getsel(const char *str)
}
static void
-readone(int fd, struct pcisel *sel, long reg, int width)
-{
-
- printf("%0*x", width*2, read_config(fd, sel, reg, width));
-}
-
-static void
readit(const char *name, const char *reg, int width)
{
long rstart;
long rend;
long r;
char *end;
- int i;
+ int i, items_per_line;
int fd;
struct pcisel sel;
@@ -1312,18 +1333,22 @@ readit(const char *name, const char *reg, int width)
err(1, "%s", _PATH_DEVPCI);
rend = rstart = strtol(reg, &end, 0);
- if (end && *end == ':') {
+ if (*end == ':') {
end++;
- rend = strtol(end, (char **) 0, 0);
+ rend = strtol(end, NULL, 0);
}
sel = getsel(name);
+ items_per_line = 16 / width;
for (i = 1, r = rstart; r <= rend; i++, r += width) {
- readone(fd, &sel, r, width);
- if (i && !(i % 8))
+ printf("%0*x", width * 2, read_config(fd, &sel, r, width));
+
+ /* Use a double space in the middle when outputting bytes. */
+ if (width == 1 && i % 16 == 8)
putchar(' ');
- putchar(i % (16/width) ? ' ' : '\n');
+
+ putchar(i % items_per_line == 0 ? '\n' : ' ');
}
- if (i % (16/width) != 1)
+ if (i % items_per_line != 1)
putchar('\n');
close(fd);
}
@@ -1335,9 +1360,9 @@ writeit(const char *name, const char *reg, const char *data, int width)
struct pci_io pi;
pi.pi_sel = getsel(name);
- pi.pi_reg = strtoul(reg, (char **)0, 0); /* XXX error check */
+ pi.pi_reg = strtoul(reg, NULL, 0); /* XXX error check */
pi.pi_width = width;
- pi.pi_data = strtoul(data, (char **)0, 0); /* XXX error check */
+ pi.pi_data = strtoul(data, NULL, 0); /* XXX error check */
fd = open(_PATH_DEVPCI, O_RDWR, 0);
if (fd < 0)
@@ -1369,6 +1394,36 @@ chkattached(const char *name)
}
static void
+map_bar(const char *name, const char *reg, int flags, struct pci_bar_mmap *pbm)
+{
+ int fd;
+ char *el;
+
+ memset(pbm, 0, sizeof(struct pci_bar_mmap));
+ pbm->pbm_sel = getsel(name);
+ pbm->pbm_reg = strtoul(reg, &el, 0);
+ if (*reg == '\0' || *el != '\0')
+ errx(1, "Invalid bar specification %s", reg);
+ pbm->pbm_memattr = VM_MEMATTR_DEVICE;
+ pbm->pbm_flags = flags;
+
+ fd = open(_PATH_DEVPCI, O_RDWR, 0);
+ if (fd < 0)
+ err(1, "%s", _PATH_DEVPCI);
+
+ if (ioctl(fd, PCIOCBARMMAP, pbm) < 0)
+ err(1, "ioctl(PCIOCBARMMAP)");
+
+ close(fd);
+}
+
+static void
+unmap_bar(struct pci_bar_mmap *pbm)
+{
+ munmap(pbm->pbm_map_base, pbm->pbm_map_length);
+}
+
+static void
dump_bar(const char *name, const char *reg, const char *bar_start,
const char *bar_count, int width, int verbose)
{
@@ -1379,7 +1434,6 @@ dump_bar(const char *name, const char *reg, const char *bar_start,
uint64_t *dx, a, start, count;
char *el;
size_t res;
- int fd;
start = 0;
if (bar_start != NULL) {
@@ -1396,19 +1450,7 @@ dump_bar(const char *name, const char *reg, const char *bar_start,
bar_count);
}
- pbm.pbm_sel = getsel(name);
- pbm.pbm_reg = strtoul(reg, &el, 0);
- if (*reg == '\0' || *el != '\0')
- errx(1, "Invalid bar specification %s", reg);
- pbm.pbm_flags = 0;
- pbm.pbm_memattr = VM_MEMATTR_DEVICE;
-
- fd = open(_PATH_DEVPCI, O_RDWR, 0);
- if (fd < 0)
- err(1, "%s", _PATH_DEVPCI);
-
- if (ioctl(fd, PCIOCBARMMAP, &pbm) < 0)
- err(1, "ioctl(PCIOCBARMMAP)");
+ map_bar(name, reg, 0, &pbm);
if (count == 0)
count = pbm.pbm_bar_length / width;
@@ -1478,6 +1520,103 @@ dump_bar(const char *name, const char *reg, const char *bar_start,
errx(1, "invalid access width");
}
- munmap((void *)pbm.pbm_map_base, pbm.pbm_map_length);
- close(fd);
+ unmap_bar(&pbm);
+}
+
+union tunion {
+ uint8_t one;
+ uint16_t two;
+ uint32_t four;
+ uint64_t eight;
+ uint8_t bytes[8];
+};
+
+static void
+read_bar(const char *name, const char *bar, const char *start_end, int width)
+{
+ struct pci_bar_mmap pbm;
+ long rstart, rend, r;
+ char *end;
+ int items_per_line, i;
+
+ rend = rstart = strtol(start_end, &end, 0);
+ if (*end == ':') {
+ end++;
+ rend = strtol(end, NULL, 0);
+ }
+
+ map_bar(name, bar, 0, &pbm);
+
+ items_per_line = 16 / width;
+ for (i = 1, r = rstart; r <= rend; i++, r += width) {
+ uintptr_t addr;
+ union tunion t;
+ int j;
+
+ addr = (uintptr_t)pbm.pbm_map_base + pbm.pbm_bar_off + r;
+ switch (width) {
+ case 1:
+ t.one = *((uint8_t *)addr);
+ break;
+ case 2:
+ t.two = *((uint16_t *)addr);
+ break;
+ case 4:
+ t.four = *((uint32_t *)addr);
+ break;
+ case 8:
+ t.eight = *((uint64_t *)addr);
+ break;
+ default:
+ errx(1, "invalid read width");
+ }
+ for (j = 0; j < width; j++) {
+ printf("%02x", t.bytes[j]);
+ }
+ /* Use a double space in the middle when outputting bytes. */
+ if (width == 1 && (i % 16) == 8)
+ putchar(' ');
+ putchar(i % items_per_line == 0 ? '\n' : ' ');
+ }
+ if (i % items_per_line != 1)
+ putchar('\n');
+
+ unmap_bar(&pbm);
+}
+
+static void
+write_bar(const char *name, const char *bar, const char *offset_str,
+ const char *value_str, int width)
+{
+ struct pci_bar_mmap pbm;
+ uint64_t offset, value;
+ uintptr_t addr;
+ char *el;
+
+ offset = strtoul(offset_str, &el, 0);
+ if (*el != '\0')
+ errx(1, "Invalid bar offset specification %s", offset_str);
+ value = strtoul(value_str, (char **)0, 0); /* XXX error check */
+
+ map_bar(name, bar, PCIIO_BAR_MMAP_RW, &pbm);
+
+ addr = (uintptr_t)pbm.pbm_map_base + pbm.pbm_bar_off + offset;
+ switch (width) {
+ case 1:
+ *((uint8_t *)addr) = value;
+ break;
+ case 2:
+ *((uint16_t *)addr) = value;
+ break;
+ case 4:
+ *((uint32_t *)addr) = value;
+ break;
+ case 8:
+ *((uint64_t *)addr) = value;
+ break;
+ default:
+ errx(1, "invalid write width");
+ }
+
+ unmap_bar(&pbm);
}
diff --git a/usr.sbin/periodic/etc/daily/404.status-zfs b/usr.sbin/periodic/etc/daily/404.status-zfs
index 052f794c0bbc..64b67a2dc008 100755
--- a/usr.sbin/periodic/etc/daily/404.status-zfs
+++ b/usr.sbin/periodic/etc/daily/404.status-zfs
@@ -25,13 +25,15 @@ case "$daily_status_zfs_enable" in
;;
esac
sout=`zpool status -x`
- echo "$sout"
# zpool status -x always exits with 0, so we have to interpret its
# output to see what's going on.
if [ "$sout" = "all pools are healthy" \
-o "$sout" = "no pools available" ]; then
+ echo "$sout"
rc=0
else
+ # Show verbose status so we can see which files are affected
+ zpool status -v
rc=1
fi
;;
diff --git a/usr.sbin/periodic/etc/security/520.pfdenied b/usr.sbin/periodic/etc/security/520.pfdenied
index d87dfa0ae64c..a3cddf30d726 100755
--- a/usr.sbin/periodic/etc/security/520.pfdenied
+++ b/usr.sbin/periodic/etc/security/520.pfdenied
@@ -41,7 +41,7 @@ rc=0
if check_yesno_period security_status_pfdenied_enable
then
TMP=`mktemp -t security`
- for _a in "" $(pfctl -a "blacklistd" -sA 2>/dev/null) $(pfctl -a "blocklistd" -sA 2>/dev/null) ${security_status_pfdenied_additionalanchors}
+ for _a in "/" $(pfctl -a "blacklistd" -sA 2>/dev/null) $(pfctl -a "blocklistd" -sA 2>/dev/null) ${security_status_pfdenied_additionalanchors}
do
pfctl -a "${_a}" -sr -v -z 2>/dev/null | \
nawk '{if (/^block/) {buf=$0; getline; gsub(" +"," ",$0); if ($5 > 0) print buf$0;} }' >> ${TMP}
diff --git a/usr.sbin/periodic/etc/security/800.loginfail b/usr.sbin/periodic/etc/security/800.loginfail
index 91564185b653..562201439794 100755
--- a/usr.sbin/periodic/etc/security/800.loginfail
+++ b/usr.sbin/periodic/etc/security/800.loginfail
@@ -65,6 +65,7 @@ then
echo ""
echo "${host} login failures:"
n=$(catmsgs | egrep -ia "${prefix}.*\b(fail(ures?|ed)?|invalid|bad|illegal|auth.*error)\b" |
+ grep -Eva "${security_status_loginfail_ignore-^\$}" |
tee /dev/stderr | wc -l)
[ $n -gt 0 ] && rc=1 || rc=0
fi
diff --git a/usr.sbin/pmc/Makefile b/usr.sbin/pmc/Makefile
index d2f482b8fa5c..c27ad797e696 100644
--- a/usr.sbin/pmc/Makefile
+++ b/usr.sbin/pmc/Makefile
@@ -6,10 +6,12 @@ MAN=
CWARNFLAGS.gcc+= -Wno-redundant-decls
CFLAGS+= -I${SRCTOP}/lib/libpmcstat
-LIBADD= pmc m pmcstat elf
+LIBADD= pmc m util ncursesw pmcstat elf dwarf
SRCS= pmc.c pmc_util.c cmd_pmc_stat.c \
cmd_pmc_list.c cmd_pmc_filter.cc \
- cmd_pmc_summary.cc
+ cmd_pmc_summary.cc \
+ cmd_pmc_frontend.cc cmd_pmc_record.cc cmd_pmc_info.cc \
+ display.cc util.cc view.cc
.include <bsd.prog.mk>
diff --git a/usr.sbin/pmc/cmd_pmc.h b/usr.sbin/pmc/cmd_pmc.h
index 83c279c4e8b7..795c762cbed5 100644
--- a/usr.sbin/pmc/cmd_pmc.h
+++ b/usr.sbin/pmc/cmd_pmc.h
@@ -41,10 +41,13 @@ typedef int (*cmd_disp_t)(int, char **);
#if defined(__cplusplus)
extern "C" {
#endif
- int cmd_pmc_stat(int, char **);
+ int cmd_pmc_info(int, char **);
int cmd_pmc_filter(int, char **);
- int cmd_pmc_stat_system(int, char **);
+ int cmd_pmc_frontend(int, char **);
int cmd_pmc_list_events(int, char **);
+ int cmd_pmc_record(int, char **);
+ int cmd_pmc_stat(int, char **);
+ int cmd_pmc_stat_system(int, char **);
int cmd_pmc_summary(int, char **);
#if defined(__cplusplus)
};
diff --git a/usr.sbin/pmc/cmd_pmc_frontend.cc b/usr.sbin/pmc/cmd_pmc_frontend.cc
new file mode 100644
index 000000000000..18ffd607bf21
--- /dev/null
+++ b/usr.sbin/pmc/cmd_pmc_frontend.cc
@@ -0,0 +1,242 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026, Netflix, Inc.
+ *
+ * This software was developed by Ali Mashtizadeh under the sponsorship from
+ * Netflix, Inc.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/param.h>
+#include <sys/cpuset.h>
+#include <sys/event.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/ttycom.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <curses.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <kvm.h>
+#include <libgen.h>
+#include <limits.h>
+#include <locale.h>
+#include <math.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <libpmcstat.h>
+#include "cmd_pmc.h"
+
+#include <iostream>
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <dev/hwpmc/hwpmc_ibs.h>
+#include "display.hh"
+#include "view.hh"
+
+struct frontend {
+ syminfo func;
+ int64_t ocmiss;
+ int64_t l2miss;
+ int64_t l3miss;
+ int64_t l1tlbmiss;
+ int64_t l2tlbmiss;
+ int64_t latency;
+ int64_t samples;
+};
+
+static int sortcol = 2;
+
+class frontend_view : public pmcview
+{
+public:
+ frontend_view() : pmcview(), samples() { }
+ ~frontend_view() { }
+
+ virtual void
+ callchain(struct pmclog_ev_callchain &p,
+ ibsfetchinfo &f, __unused uintfptr_t *cc, __unused int len)
+ {
+ int usermode = PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(p.pl_cpuflags);
+
+ /* Ignore zeros */
+ if (IBS_FETCH_CTL_TO_LAT(f.ctl) == 0)
+ return;
+
+ auto inst = samples.find(f.linaddr);
+ if (inst == samples.end()) {
+ syminfo sym = addrtosymbol(usermode ? p.pl_pid : 0, f.linaddr);
+ if (sym.name == "")
+ return;
+
+ samples[f.linaddr] = frontend();
+ inst = samples.find(f.linaddr);
+ inst->second.func = sym;
+ }
+
+ /*
+ * Save fetch cache and TLB miss counts
+ */
+ if (f.ctl & IBS_FETCH_CTL_OPCACHEMISS)
+ inst->second.ocmiss += 1;
+ if (f.ctl & IBS_FETCH_CTL_L2MISS)
+ inst->second.l2miss += 1;
+ if (f.ctl & IBS_FETCH_CTL_L3MISS)
+ inst->second.l3miss += 1;
+
+ if (f.ctl & IBS_FETCH_CTL_L1TLBMISS)
+ inst->second.l1tlbmiss += 1;
+ if (f.ctl & IBS_FETCH_CTL_L2TLBMISS)
+ inst->second.l2tlbmiss += 1;
+
+ inst->second.latency += IBS_FETCH_CTL_TO_LAT(f.ctl);
+ inst->second.samples += 1;
+ }
+
+ virtual void
+ print()
+ {
+ title("IBS Frontend Analysis");
+
+ // Print header
+ table t = table();
+ t.addcolumn("Image", true);
+ t.addcolumn("Function", true);
+ t.addcolumn("Latency");
+ t.addcolumn("Samples");
+ t.addcolumn("OC Miss");
+ t.addcolumn("L2 Miss");
+ t.addcolumn("L3 Miss");
+ t.addcolumn("L1 TLB Miss");
+ t.addcolumn("L2 TLB Miss");
+
+ for (auto &kv : samples) {
+ std::vector<field> r;
+
+ r.emplace_back(kv.second.func.binary);
+ r.emplace_back(kv.second.func.to_string());
+ r.emplace_back(kv.second.latency);
+ r.emplace_back(kv.second.samples);
+ r.emplace_back(kv.second.ocmiss, kv.second.samples, true);
+ r.emplace_back(kv.second.l2miss, kv.second.samples, true);
+ r.emplace_back(kv.second.l3miss, kv.second.samples, true);
+ r.emplace_back(kv.second.l1tlbmiss, kv.second.samples, true);
+ r.emplace_back(kv.second.l2tlbmiss, kv.second.samples, true);
+
+ t.addrow(r);
+ }
+
+ t.sort(sortcol);
+ t.print();
+ }
+protected:
+ std::unordered_map<uint64_t, frontend> samples;
+};
+
+
+static struct option longopts[] = {
+ PMCFILTER_LOPTS,
+ { "sort", required_argument, NULL, 's' },
+ { NULL, 0, NULL, 0 }
+};
+
+static void
+usage(void)
+{
+ printf("Usage: pmc frontend [options] [pmclog]\n\n");
+ printf("Analyze frontend bottlenecks\n\n");
+ printf("Options:\n");
+ printf("\t-s,--sort Sort by event\n\n");
+ PMCFILTER_PRINTOPTS();
+}
+
+int
+cmd_pmc_frontend(int argc, char **argv)
+{
+ pmcfilter filter = pmcfilter();
+ const char *logfile = "default.log";
+ int option, logfd;
+
+ while ((option = getopt_long(argc, argv, PMCFILTER_SOPTS, longopts, NULL)) != -1) {
+ switch (option) {
+ PMCFILTER_CASE(filter)
+ case 's':
+ sortcol = atoi(optarg);
+ break;
+ case '?':
+ default:
+ usage();
+ exit(EX_USAGE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 0 && argc != 1) {
+ usage();
+ exit(EX_USAGE);
+ }
+ if (argc == 1)
+ logfile = argv[0];
+
+ setup_screen();
+
+ if ((logfd = open(logfile, O_RDONLY)) < 0) {
+ errx(EX_OSERR, "ERROR: Cannot open \"%s\" for reading: %s.", logfile,
+ strerror(errno));
+ }
+
+ frontend_view v = frontend_view();
+ v.setfilter(filter);
+ v.process(logfd);
+ v.print();
+
+ close(logfd);
+
+ return (0);
+}
diff --git a/usr.sbin/pmc/cmd_pmc_info.cc b/usr.sbin/pmc/cmd_pmc_info.cc
new file mode 100644
index 000000000000..4bff0240b774
--- /dev/null
+++ b/usr.sbin/pmc/cmd_pmc_info.cc
@@ -0,0 +1,186 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026, Netflix, Inc.
+ *
+ * This software was developed by Ali Mashtizadeh under the sponsorship from
+ * Netflix, Inc.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/param.h>
+#include <sys/cpuset.h>
+#include <sys/event.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/ttycom.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <curses.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <kvm.h>
+#include <libgen.h>
+#include <limits.h>
+#include <locale.h>
+#include <math.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "cmd_pmc.h"
+
+#include <iostream>
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "display.hh"
+#include "view.hh"
+
+class info_view : public pmcview
+{
+public:
+ virtual void
+ callchain(struct pmclog_ev_callchain &p)
+ {
+ uint64_t eventid = pmcidtoeventid(p.pl_pmcid);
+
+ pmcinfo[eventid].count++;
+ }
+
+ virtual void
+ print()
+ {
+ title("PMC Info");
+
+ header("System Info");
+ printf("CPU Model: %s\n", cpumodel.c_str());
+ printf("OS Release: %s\n", osrelease.c_str());
+ printf("Build ID: %s\n", buildid.c_str());
+ printf("\n");
+
+ if (arch == PMC_ARCH_AMD64) {
+ header("CPUID");
+ for (auto &c : cpuid)
+ printf("0x%08x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
+ c.first, c.second.eax, c.second.ebx,
+ c.second.ecx, c.second.edx);
+ printf("\n");
+ }
+
+ header("PMC Info");
+ for (auto &p : extpmcinfo)
+ printf("%s\n", p.event.c_str());
+ printf("\n");
+
+ table t = table();
+ t.addcolumn("Counter", true);
+ t.addcolumn("Counts", true);
+
+ for (auto &p : pmcinfo) {
+ std::vector<field> r;
+
+ r.emplace_back(p.second.name);
+ r.emplace_back((int64_t)p.second.count);
+
+ t.addrow(r);
+ }
+
+ t.print();
+ }
+};
+
+static struct option longopts[] = {
+ PMCFILTER_LOPTS,
+ { NULL, 0, NULL, 0 }
+};
+
+static void
+usage(void)
+{
+ printf("Usage: pmc info [options] [pmclog]\n\n");
+ printf("Display a log summary\n\n");
+ printf("Options:\n");
+ PMCFILTER_PRINTOPTS();
+}
+
+int
+cmd_pmc_info(int argc, char **argv)
+{
+ struct pmcfilter filter = pmcfilter();
+ const char *logfile = "default.log";
+ int option, logfd;
+
+ while ((option = getopt_long(argc, argv, PMCFILTER_SOPTS "s:", longopts, NULL)) != -1) {
+ switch (option) {
+ PMCFILTER_CASE(filter);
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 0 && argc != 1) {
+ usage();
+ exit(EX_USAGE);
+ }
+ if (argc == 1)
+ logfile = argv[0];
+
+ setup_screen();
+
+ if ((logfd = open(logfile, O_RDONLY)) < 0) {
+ errx(EX_OSERR, "ERROR: Cannot open \"%s\" for reading: %s.", logfile,
+ strerror(errno));
+ return (EX_NOINPUT);
+ }
+
+ info_view s = info_view();
+ s.setfilter(filter);
+ s.process(logfd);
+ s.print();
+
+ return (EX_OK);
+}
diff --git a/usr.sbin/pmc/cmd_pmc_record.cc b/usr.sbin/pmc/cmd_pmc_record.cc
new file mode 100644
index 000000000000..a8373b330705
--- /dev/null
+++ b/usr.sbin/pmc/cmd_pmc_record.cc
@@ -0,0 +1,750 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026, Netflix, Inc.
+ *
+ * This software was developed by Ali Mashtizadeh under the sponsorship from
+ * Netflix, Inc.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/param.h>
+#include <sys/cdefs.h>
+#include <sys/cpuset.h>
+#include <sys/event.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/ttycom.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <curses.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <kvm.h>
+#include <libgen.h>
+#include <limits.h>
+#include <locale.h>
+#include <math.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdalign.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <machine/cpufunc.h>
+
+#include <libpmcstat.h>
+#include "cmd_pmc.h"
+
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "display.hh"
+#include "headers.hh"
+
+#define DEFAULT_RATE 65536
+
+class pmc_config
+{
+public:
+ std::string event;
+ uint64_t count;
+ cpuset_t cpumask;
+ uint32_t caps;
+ pmc_config() : event(), count(0), ids()
+ {
+ CPU_ZERO(&cpumask);
+ }
+ pmc_config(const std::string &event, uint64_t count, cpuset_t cpumask)
+ : event(event), count(count), ids()
+ {
+ CPU_COPY(&cpumask, &this->cpumask);
+ }
+ /*
+ * Read the capabilities and fills in the cpumask for a given event
+ * counter. The way that the hwpmc module currently works we do not
+ * know which pmc class will back our counter until we look it up. The
+ * easiest way is to program the counter into the first CPU present in
+ * the mask and then retrieve the capabilities.
+ */
+ void getcaps() {
+ int status;
+ int testcpu;
+ pmc_id_t id;
+
+ testcpu = CPU_FFS(&cpumask) - 1;
+
+ status = pmc_allocate(event.c_str(), PMC_MODE_SS, PMC_F_CALLCHAIN,
+ testcpu, &id, count);
+ if (status < 0)
+ err(EX_OSERR, "ERROR: Cannot allocate event '%s'", event.c_str());
+
+ status = pmc_capabilities(id, &caps);
+ if (status < 0)
+ err(EX_OSERR, "ERROR: Cannot get pmc capabilities");
+
+ pmc_release(id);
+
+ if (caps & PMC_CAP_SYSWIDE) {
+ CPU_ZERO(&cpumask);
+ CPU_SET(0, &cpumask);
+ }
+ if (caps & PMC_CAP_DOMWIDE) {
+ int domains;
+ size_t len;
+
+ CPU_ZERO(&cpumask);
+
+ len = sizeof(domains);
+ if (sysctlbyname("vm.ndomains", &domains, &len, NULL, 0) == -1)
+ err(EX_OSERR, "ERROR: Cannot get number of domains");
+
+ for (int i = 1; i < domains; i++) {
+ cpuset_t dmask;
+
+ CPU_ZERO(&dmask);
+ status = cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_DOMAIN, i,
+ sizeof(dmask), &dmask);
+ if (status < 0)
+ err(EX_OSERR, "ERROR: Cannot get domain mask");
+ CPU_SET(CPU_FFS(&dmask) - 1, &cpumask);
+ }
+ }
+ }
+ /*
+ * Allocate the PMC across all CPUs.
+ */
+ void allocate() {
+ int cpu;
+ int status;
+ pmc_id_t id;
+
+ for (cpu = 0; cpu < CPU_SETSIZE; cpu++) {
+ if (!CPU_ISSET(cpu, &cpumask))
+ continue;
+
+ status = pmc_allocate(event.c_str(), PMC_MODE_SS, PMC_F_CALLCHAIN,
+ cpu, &id, count);
+ if (status < 0)
+ err(EX_OSERR, "ERROR: Cannot allocate event '%s'",
+ event.c_str());
+
+ status = pmc_set(id, count);
+ if (status < 0)
+ err(EX_OSERR, "ERROR: Cannot set sampling count for event '%s'",
+ event.c_str());
+
+ ids.push_back(id);
+ }
+ }
+ /*
+ * Release all PMC instances (one per CPU).
+ */
+ void release() {
+ for (auto i : ids) {
+ if (pmc_release(i) < 0) {
+ perror("pmc_start");
+ }
+ }
+ }
+ /*
+ * Start all PMC instances (one per CPU).
+ */
+ void start() {
+ for (auto i : ids) {
+ if (pmc_start(i) < 0) {
+ perror("pmc_start");
+ }
+ }
+ }
+ /*
+ * Stop all PMC instances (one per CPU).
+ */
+ void stop() {
+ for (auto i : ids) {
+ if (pmc_stop(i) < 0) {
+ perror("pmc_start");
+ }
+ }
+ }
+private:
+ std::vector<pmc_id_t> ids;
+};
+
+static std::vector<pmc_config> pmcs = std::vector<pmc_config>();
+static struct option longopts[] = {
+ { "rate", required_argument, NULL, 'n' },
+ { "counter", required_argument, NULL, 'c' },
+ { "time", required_argument, NULL, 't' },
+ { "study", required_argument, NULL, 's' },
+ { NULL, 0, NULL, 0 }
+};
+static struct timespec start;
+static int timelimit = 0;
+
+static void
+usage(void)
+{
+ printf("Usage: pmc record [options] [output.pmc]\n\n");
+ printf("Record a study\n\n");
+ printf("Options:\n");
+ printf("\t-c Sampling counter\n");
+ printf("\t-r Sample rate (default: %d)\n", DEFAULT_RATE);
+ printf("\t-s Specify a study\n");
+ printf("\t-t Run the study for specified amount of time\n");
+ printf("\nStudies:\n");
+#if defined(__i386__) || defined(__amd64__)
+ printf("\tbranches Study branch misprediction (Requires AMD IBS)\n");
+ printf("\tc2c Study cache to cache communications (Requires AMD IBS)\n");
+#endif
+ printf("\tdefault Instruction sampling for general analysis\n");
+ printf("\tflamegraph Instruction sampling for general analysis\n");
+#if defined(__i386__) || defined(__amd64__)
+ printf("\tfrontend Study frontend stalls (Requires AMD IBS)\n");
+ printf("\tmemory Study memory operations (Requires AMD IBS)\n");
+#endif
+}
+
+int
+writelog(int logfd, const void *buf, size_t len)
+{
+ int status;
+ const char *cur = (const char *)buf;
+
+ while (len != 0) {
+ status = write(logfd, cur, len);
+ if (status < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ else
+ return status;
+ }
+ if (status == 0)
+ return status;
+
+ cur += status;
+ len -= status;
+ }
+
+ return 0;
+}
+
+/*
+ * Write the PMC header. Each of the header payloads have their own header
+ * containing the payloads size. Thus the pmc tools can just skip over regions
+ * that they do not know how to handle.
+ */
+int
+write_header(int logfd)
+{
+ int status;
+ pmchdr_header hdr;
+
+ hdr.magic = PMC_HEADER_MAGIC;
+ hdr.version = PMC_HEADER_VERSION;
+#if defined(__amd64__)
+ hdr.arch = PMC_ARCH_AMD64;
+#elif defined(__aarch64__)
+ hdr.arch = PMC_ARCH_ARM64;
+#elif defined(__powerpc64__)
+ hdr.arch = PMC_ARCH_PPC64;
+#elif defined(__riscv)
+ hdr.arch = PMC_ARCH_RISCV64;
+#elif defined(__arm__)
+ hdr.arch = PMC_ARCH_ARM;
+#else
+ hdr.arch = 0;
+#endif
+
+ status = writelog(logfd, &hdr, sizeof(hdr));
+ if (status < 0) {
+ perror("writelog");
+ }
+
+ return status;
+}
+
+/*
+ * Write out the sysinfo header.
+ */
+int
+write_sysinfo(int logfd)
+{
+ int status;
+ pmchdr_infohdr hdr;
+ pmchdr_sysinfo sys;
+ char val[64];
+ size_t valsz;
+
+ valsz = sizeof(val);
+ status = sysctlbyname("hw.model", &val, &valsz, NULL, 0);
+ if (status < 0) {
+ perror("sysctlbyname");
+ return status;
+ }
+ strlcpy(sys.cpumodel, val, sizeof(sys.cpumodel));
+
+ valsz = sizeof(val);
+ status = sysctlbyname("kern.osrelease", &val, &valsz, NULL, 0);
+ if (status < 0) {
+ perror("sysctlbyname");
+ return status;
+ }
+ strlcpy(sys.osrelease, val, sizeof(sys.osrelease));
+
+ valsz = sizeof(val);
+ status = sysctlbyname("kern.build_id", &val, &valsz, NULL, 0);
+ if (status < 0) {
+ perror("sysctlbyname");
+ return status;
+ }
+ strlcpy(sys.buildid, val, sizeof(sys.buildid));
+
+ hdr.type = INFOHDR_TYPE_SYSINFO;
+ hdr.length = sizeof(sys);
+ status = writelog(logfd, &hdr, sizeof(hdr));
+ if (status < 0) {
+ err(EX_IOERR, "writelog");
+ }
+
+ status = writelog(logfd, &sys, sizeof(sys));
+ if (status < 0) {
+ err(EX_IOERR, "writelog");
+ }
+
+ return status;
+};
+
+#if defined(__i386__) || defined(__amd64__)
+#define CPUID_ROOT_BASE 0x00000000
+#define CPUID_ROOT_VM 0x40000000
+#define CPUID_ROOT_EXT 0x80000000
+
+/*
+ * Write out the CPU Info block. After the standard header that declares the
+ * size of the payload, we write out all the CPUID root and leafs for each of
+ * the three major roots: base, VM, and extended artribute space. On all
+ * modern processors the root contains the maximum leaf as the first value
+ * allowing us to decode how many leafs there are.
+ */
+int
+write_cpuinfo(int logfd)
+{
+ int status;
+ u_int tmp[4];
+ uint32_t base_max, vm_max, ext_max;
+ uint32_t len;
+ pmchdr_infohdr *hdr;
+ uint32_t *buf, *off;
+
+ /*
+ * Find the length of the main, VM, and extended CPUID leafs
+ */
+ do_cpuid(CPUID_ROOT_BASE, tmp);
+ base_max = tmp[0];
+ len = base_max + 1;
+
+ do_cpuid(CPUID_ROOT_VM, tmp);
+ vm_max = tmp[0];
+ if (vm_max != 0)
+ len += vm_max - CPUID_ROOT_VM + 1;
+
+ do_cpuid(CPUID_ROOT_EXT, tmp);
+ ext_max = tmp[0];
+ if (ext_max != 0)
+ len += ext_max - CPUID_ROOT_EXT + 1;
+
+ // 4 Registers per Leaf x 4 Bytes per Register
+ len *= 4;
+
+ buf = (uint32_t *)new uint32_t[len + sizeof(pmchdr_infohdr) / 4];
+ hdr = (pmchdr_infohdr *)buf;
+ hdr->type = INFOHDR_TYPE_CPUID;
+ hdr->length = 4 * len;
+ off = (uint32_t *)(hdr + 1);
+
+ for (uint32_t i = 0; i <= base_max; i++) {
+ do_cpuid(i, off);
+ off += 4;
+ }
+
+ if (vm_max) {
+ for (uint32_t i = CPUID_ROOT_VM; i <= vm_max; i++) {
+ do_cpuid(i, off);
+ off += 4;
+ }
+ }
+ if (ext_max) {
+ for (uint32_t i = CPUID_ROOT_EXT; i <= ext_max; i++) {
+ do_cpuid(i, off);
+ off += 4;
+ }
+ }
+
+ status = writelog(logfd, buf, 4 * len + sizeof(*hdr));
+ if (status < 0) {
+ delete[] buf;
+ err(EX_IOERR, "writelog");
+ }
+
+ delete[] buf;
+
+ return 0;
+}
+#elif defined(__aarch64__)
+int
+write_cpuinfo(__unused int logfd)
+{
+ return 0;
+}
+#elif defined(__powerpc64__)
+int
+write_cpuinfo(__unused int logfd)
+{
+ return 0;
+}
+#else
+int
+write_cpuinfo(__unused int logfd)
+{
+ return 0;
+}
+#endif
+
+int
+write_footer(int logfd)
+{
+ int status;
+ pmchdr_infohdr hdr;
+
+ hdr.type = INFOHDR_TYPE_DONE;
+ hdr.length = 0;
+
+ status = writelog(logfd, &hdr, sizeof(hdr));
+ if (status < 0) {
+ perror("writelog");
+ }
+
+ return status;
+}
+
+#if defined(__i386__) || defined(__amd64__)
+int
+setup_study(const std::string &study, uint64_t rate, cpuset_t mask)
+{
+ u_int tmp[4];
+ alignas(4) char vendor[16];
+ std::string event;
+ uint32_t ext_max;
+ uint32_t ibs_features;
+ bool is_amd, is_intel;
+
+ do_cpuid(CPUID_ROOT_BASE, tmp);
+
+ /* Find the brand */
+ is_intel = false;
+ is_amd = false;
+
+ /* i386 complains without explicit alignment */
+ ((u_int *)&vendor)[0] = tmp[1];
+ ((u_int *)&vendor)[1] = tmp[3];
+ ((u_int *)&vendor)[2] = tmp[2];
+ vendor[12] = 0;
+ if (strncmp(vendor, INTEL_VENDOR_ID, 12) == 0)
+ is_intel = true;
+ if (strncmp(vendor, AMD_VENDOR_ID, 12) == 0)
+ is_amd = true;
+ if (strncmp(vendor, HYGON_VENDOR_ID, 12) == 0)
+ is_amd = true;
+
+ ibs_features = 0;
+ do_cpuid(CPUID_ROOT_EXT, tmp);
+ ext_max = tmp[0];
+ if (is_amd && ext_max >= CPUID_IBSID) {
+ do_cpuid(CPUID_IBSID, tmp);
+ ibs_features = tmp[0];
+ }
+
+ if (study == "default" || study == "flamegraph") {
+ pmcs.push_back(pmc_config("unhalted-cycles", rate, mask));
+ return 0;
+ }
+
+ if (is_intel) {
+ err(EX_SOFTWARE, "ERROR: Study is unsupported on Intel");
+ }
+
+ if (study == "frontend") {
+ event = "ibs-fetch,randomize";
+ } else if (study == "branches" || study == "memory") {
+ event = "ibs-op";
+ if (ibs_features & CPUID_IBSID_OPCNT)
+ event += ",opcount";
+ } else if (study == "c2c") {
+ event = "ibs-op";
+ if (ibs_features & CPUID_IBSID_IBSLOADLATENCYFILT)
+ event += ",l3miss";
+ if (ibs_features & CPUID_IBSID_OPCNT)
+ event += ",opcount";
+ } else {
+ err(EX_USAGE, "ERROR: Study '%s' unknown", study.c_str());
+ }
+
+ pmcs.push_back(pmc_config(event, rate, mask));
+
+ return 0;
+}
+#else
+int
+setup_study(const std::string &study, uint64_t rate, cpuset_t mask)
+{
+ if (study == "default" || study == "flamegraph") {
+ pmcs.push_back(pmc_config("unhalted-cycles", rate, mask));
+ return 0;
+ }
+
+ err(EX_SOFTWARE, "ERROR: Study is unsupported on this architecture");
+}
+#endif
+
+/*
+ * Compute the current runtime and print it to the screen, if it exceeds
+ * timelimit return 1 otherwise 0.
+ */
+int
+update_status(int logfd)
+{
+ int status;
+ long sec, nsec;
+ struct timespec end;
+ struct stat sb;
+ std::string filesz;
+
+ status = clock_gettime(CLOCK_MONOTONIC, &end);
+ if (status < 0) {
+ err(EX_OSERR, "Could not read current time");
+ }
+
+ sec = end.tv_sec - start.tv_sec;
+ nsec = end.tv_nsec - start.tv_nsec;
+ if (nsec < 0) {
+ sec--;
+ nsec += 1000000000L;
+ }
+
+ // Do this another way for sockets
+ status = fstat(logfd, &sb);
+ if (status < 0) {
+ err(EX_OSERR, "Failed to fstat log file");
+ }
+
+ filesz = format_binprefix(sb.st_size);
+
+ /*
+ * Use %-10s to ensure we print spaces after in case the size string
+ * shrinks.
+ */
+ printf("Elapsed: %ld.%03ld seconds, Log file size: %-10s\r",
+ sec, nsec / 1000000,
+ filesz.c_str());
+ fflush(stdout);
+
+ if (timelimit != 0 && sec >= timelimit) {
+ printf("\nElapsed time completed\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+cmd_pmc_record(int argc, char **argv)
+{
+ struct kevent kev;
+ cpuset_t mask;
+ uint64_t rate = DEFAULT_RATE;
+ const char *logfile = "default.log";
+ int status;
+ int option, logfd, kq;
+
+ CPU_ZERO(&mask);
+ if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1,
+ sizeof(mask), &mask) == -1)
+ err(EX_OSERR, "ERROR: Cannot determine the available CPUs");
+
+ if (pmc_init() < 0)
+ err(EX_SOFTWARE, "ERROR: Failed to initialize libpmc");
+
+ while ((option = getopt_long(argc, argv, "c:r:s:t:", longopts, NULL)) != -1) {
+ switch (option) {
+ case 'c':
+ pmcs.push_back(pmc_config(optarg, rate, mask));
+ break;
+ case 'r':
+ rate = strtoull(optarg, NULL, 0);
+ break;
+ case 's':
+ setup_study(optarg, rate, mask);
+ break;
+ case 't':
+ timelimit = atoi(optarg);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 0 && argc != 1) {
+ usage();
+ exit(EX_USAGE);
+ }
+ if (argc == 1)
+ logfile = argv[0];
+
+ if (pmcs.size() == 0) {
+ errx(EX_NOINPUT, "ERROR: Please select one or more counters or performance studies.");
+ }
+
+ if ((logfd = open(logfile, O_CREAT|O_EXCL|O_WRONLY,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0) {
+ errx(EX_OSERR, "ERROR: Cannot open \"%s\" for writing: %s.", logfile,
+ strerror(errno));
+ }
+
+ setup_screen();
+
+ for (auto &p : pmcs) {
+ p.getcaps();
+ }
+
+ write_header(logfd);
+ write_sysinfo(logfd);
+ write_cpuinfo(logfd);
+ write_footer(logfd);
+
+ // Record pmclog
+ kq = kqueue();
+ if (kq < 0)
+ err(EX_OSERR, "ERROR: kqueue creation failed");
+
+ EV_SET(&kev, fileno(stdin), EVFILT_READ, EV_ADD, 0, 0, NULL);
+ if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: kevent failed to register stdin");
+
+ EV_SET(&kev, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
+ if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: kevent failed to register SIGINT");
+ EV_SET(&kev, SIGIO, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
+ if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: kevent failed to register SIGIO");
+
+ EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0, 100, NULL);
+ if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: Cannot register kevent for timer");
+
+ pmc_configure_logfile(logfd);
+
+ for (auto &p : pmcs) {
+ p.allocate();
+ }
+
+ for (auto &p : pmcs) {
+ p.start();
+ }
+
+ if (clock_gettime(CLOCK_MONOTONIC, &start) < 0)
+ err(EX_OSERR, "ERROR: Could not get the current time");
+
+ printf("Recording performance trace press Ctrl-C to stop\n");
+
+ /*
+ * loop till either the target process (if any) exits, or we
+ * are killed by a SIGINT or we reached the time duration.
+ */
+ while (1) {
+ status = kevent(kq, NULL, 0, &kev, 1, NULL);
+ if (status <= 0) {
+ if (errno != EINTR)
+ err(EX_OSERR, "ERROR: kevent failed");
+ else
+ continue;
+ }
+
+ if (kev.flags & EV_ERROR)
+ errc(EX_OSERR, kev.data, "ERROR: kevent failed");
+
+ switch (kev.filter) {
+ case EVFILT_READ: /* log file data is present */
+ if (kev.ident == (unsigned)fileno(stdin)) {
+ // Check for exit key
+ }
+ break;
+ case EVFILT_SIGNAL:
+ if (kev.ident == SIGIO) {
+ fprintf(stderr, "ERROR: IO error");
+ status = EX_OSERR;
+ goto done;
+ } else if (kev.ident == SIGINT) {
+ status = EX_OK;
+ goto done;
+ } else {
+ err(EX_OSERR, "Unknown signal recieved");
+ }
+ break;
+ case EVFILT_TIMER:
+ if (update_status(logfd) == 1)
+ goto done;
+ break;
+ }
+ }
+
+done:
+ for (auto &p : pmcs) {
+ p.stop();
+ p.release();
+ }
+ close(logfd);
+
+ return (status);
+}
+
diff --git a/usr.sbin/pmc/display.cc b/usr.sbin/pmc/display.cc
new file mode 100644
index 000000000000..454cb74e29bd
--- /dev/null
+++ b/usr.sbin/pmc/display.cc
@@ -0,0 +1,571 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026, Netflix, Inc.
+ *
+ * This software was developed by Ali Mashtizadeh under the sponsorship from
+ * Netflix, Inc.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/ioctl.h>
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <term.h>
+#include <math.h>
+
+#include <curses.h>
+#include <unistd.h>
+
+#include <cxxabi.h>
+#include <iomanip>
+#include <ios>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "display.hh"
+
+enum termmode {
+ TERMMODE_BASIC,
+ TERMMODE_VT,
+ TERMMODE_XTERM
+};
+
+static termmode tmode;
+static int disp_height;
+static int disp_width;
+
+#define TITLE_TITLE 0
+#define TITLE_SUBTITLE 1
+
+static const char **title_char;
+
+static const char *title_vt[] = {
+ "\xe2\x95\x90", "\xe2\x94\x80"
+};
+static const char *title_basic[] = { "=", "-" };
+
+#define ARROW_DOWN 0
+#define ARROW_UP 1
+
+static const char **arrow_char;
+
+static const char *arrow_vt[] = {
+ "\xe2\x96\xbc", "\xe2\x96\xb2"
+};
+static const char *arrow_basic[] = { "D", "U" };
+
+#define BORDER_HBAR 0
+#define BORDER_VBAR 1
+#define BORDER_TOPRIGHT 2
+#define BORDER_TOPLEFT 3
+#define BORDER_BOTTOMLEFT 4
+#define BORDER_BOTTOMRIGHT 5
+#define BORDER_TOPSPLIT 6
+#define BORDER_BOTTOMSPLIT 7
+#define BORDER_RIGHTSPLIT 8
+#define BORDER_LEFTSPLIT 9
+#define BORDER_CROSS 10
+
+static const char **border_char;
+
+static const char *border_vt[] = {
+ "\xe2\x94\x80", "\xe2\x94\x82",
+ "\xe2\x94\x8c", "\xe2\x94\x90", "\xe2\x94\x94", "\xe2\x94\x98",
+ "\xe2\x94\xac", "\xe2\x94\xb4", "\xe2\x94\xa4", "\xe2\x94\x9c",
+ "\xe2\x94\xbc"
+};
+static const char *border_basic[] = { "-", "|", "+", "+", "+", "+", "+", "+", "+", "+" };
+
+static char tc_boldbuf[16];
+static char tc_sgr0buf[16];
+static char tc_afbuf[16];
+static char tc_green[16];
+static char tc_yellow[16];
+static char tc_red[16];
+
+static const char *TC_BOLD;
+static const char *TC_SGR0;
+static const char *TC_SETAF;
+
+void
+tcemit(const char *c)
+{
+ if (c)
+ tputs(c, 1, putchar);
+}
+
+void
+tcfg(int n)
+{
+ if (TC_SETAF)
+ tputs(tgoto(TC_SETAF, 0, n), 1, putchar);
+}
+
+static void
+setup_termcap(const char *term)
+{
+ bool xcolor;
+ char *buf;
+ char *clicolor;
+ char termcapbuf[4096];
+
+ if (tgetent(termcapbuf, "xterm-256color") != 1) {
+ TC_BOLD = NULL;
+ TC_SGR0 = NULL;
+ TC_SETAF = NULL;
+ return;
+ }
+
+ buf = tc_boldbuf;
+ TC_BOLD = tgetstr("md", &buf);
+ buf = tc_sgr0buf;
+ TC_SGR0 = tgetstr("me", &buf);
+ buf = tc_afbuf;
+ TC_SETAF = tgetstr("AF", &buf);
+
+ clicolor = getenv("CLICOLOR");
+ if (clicolor == NULL || strcmp(clicolor, "1") != 0) {
+ return;
+ }
+
+ if (strcmp(term, "xterm-256color") == 0) {
+ xcolor = true;
+ } else {
+ xcolor = false;
+ }
+
+ buf = tgoto(TC_SETAF, 1, xcolor ? COLOR256_GREEN : COLOR_GREEN);
+ if (buf) {
+ strlcpy(tc_green, buf, sizeof(tc_green));
+ } else {
+ tc_green[0] = '\0';
+ }
+
+ buf = tgoto(TC_SETAF, 1, xcolor ? COLOR256_YELLOW : COLOR_YELLOW);
+ if (buf) {
+ strlcpy(tc_yellow, buf, sizeof(tc_yellow));
+ } else {
+ tc_yellow[0] = '\0';
+ }
+
+ buf = tgoto(TC_SETAF, 1, xcolor ? COLOR256_RED : COLOR_RED);
+ if (buf) {
+ strlcpy(tc_red, buf, sizeof(tc_red));
+ } else {
+ tc_red[0] = '\0';
+ }
+}
+
+void
+setup_screen()
+{
+ int status;
+ const char *term;
+ struct winsize wsz;
+
+ /* Sane defaults */
+ tmode = TERMMODE_BASIC;
+ title_char = title_basic;
+ arrow_char = arrow_basic;
+ border_char = border_basic;
+ disp_height = 25;
+ disp_width = 80;
+
+ if (isatty(STDOUT_FILENO) == 0) {
+ tmode = TERMMODE_XTERM;
+ title_char = title_vt;
+ arrow_char = arrow_vt;
+ border_char = border_vt;
+ return;
+ }
+
+ status = ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz);
+ if (status < 0) {
+ perror("ioctl(TIOCGWINSZ)");
+ return;
+ }
+
+ disp_height = wsz.ws_row;
+ disp_width = wsz.ws_col;
+
+ term = getenv("TERM");
+ if (term == NULL) {
+ tmode = TERMMODE_BASIC;
+ title_char = title_basic;
+ arrow_char = arrow_basic;
+ border_char = border_basic;
+ return;
+ }
+
+ if (strncmp(term, "xterm", 5) == 0) {
+ tmode = TERMMODE_XTERM;
+ title_char = title_vt;
+ arrow_char = arrow_vt;
+ border_char = border_vt;
+ } else if (strncmp(term, "vt", 2) == 0) {
+ tmode = TERMMODE_VT;
+ title_char = title_vt;
+ arrow_char = arrow_vt;
+ border_char = border_vt;
+ }
+
+ setup_termcap(term);
+}
+
+void
+title(const std::string &t)
+{
+ size_t i;
+
+ if (tmode != TERMMODE_BASIC)
+ tcemit(TC_BOLD);
+ printf("%s\n", t.c_str());
+ if (tmode != TERMMODE_BASIC)
+ tcemit(TC_SGR0);
+ for (i = 0; i < t.size(); i++)
+ printf("%s", title_char[TITLE_TITLE]);
+ printf("\n\n");
+}
+
+void
+header(const std::string &h)
+{
+ size_t i;
+
+ if (tmode != TERMMODE_BASIC)
+ tcemit(TC_BOLD);
+ printf("%s\n", h.c_str());
+ if (tmode != TERMMODE_BASIC)
+ tcemit(TC_SGR0);
+ for (i = 0; i < h.size(); i++)
+ printf("%s", title_char[TITLE_SUBTITLE]);
+ printf("\n");
+}
+
+void
+printval(const std::string &msg, uint64_t val, siunit ui)
+{
+ switch (ui) {
+ case siunit::seconds:
+ printf("%s: %" PRIu64 " s\n", msg.c_str(), val);
+ break;
+ case siunit::percent:
+ printf("%s: %" PRIu64 "%%\n", msg.c_str(), val);
+ break;
+ case siunit::cycles:
+ printf("%s: %" PRIu64 " cycles\n", msg.c_str(), val);
+ break;
+ }
+}
+
+void
+printval(const std::string &msg, float val, siunit ui)
+{
+ switch (ui) {
+ case siunit::seconds:
+ printf("%s: %.2f s\n", msg.c_str(), val);
+ break;
+ case siunit::percent:
+ printf("%s: %6.2f%%\n", msg.c_str(), val);
+ break;
+ case siunit::cycles:
+ printf("%s: %.2f cycles\n", msg.c_str(), val);
+ break;
+ }
+}
+
+std::string
+format_siprefix(uint64_t count)
+{
+ unsigned long index = 0;
+ char prefix[] = " kMGTP";
+ char buf[10];
+
+ while (count > 10000 && index < sizeof(prefix)) {
+ index++;
+ count /= 1000;
+ }
+
+ if (index == 0) {
+ snprintf(buf, sizeof(buf), "%" PRIu64, count);
+ } else {
+ snprintf(buf, sizeof(buf), "%" PRIu64 "%c", count, prefix[index]);
+ }
+
+ return buf;
+}
+
+std::string
+format_binprefix(uint64_t count)
+{
+ unsigned long index = 0;
+ char prefix[] = " KMGTP";
+ char buf[10];
+
+ while (count > 10000 && index < sizeof(prefix)) {
+ index++;
+ count /= 1000;
+ }
+
+ if (index == 0) {
+ snprintf(buf, sizeof(buf), "%" PRIu64 " B", count);
+ } else {
+ snprintf(buf, sizeof(buf), "%" PRIu64 " %ciB", count, prefix[index]);
+ }
+
+ return buf;
+}
+
+std::string
+format_sample(uint64_t count, uint64_t total)
+{
+ char buf[20];
+ std::string fmt = format_siprefix(count);
+
+ snprintf(buf, sizeof(buf), "%5s (%4.1f%%)", fmt.c_str(),
+ 100.0 * (float)count / (float)total);
+
+ return buf;
+}
+
+std::string
+format_percent(uint64_t count, uint64_t total)
+{
+ char buf[20];
+
+ if (total == 0) {
+ snprintf(buf, sizeof(buf), "%6s", "");
+ } else {
+ snprintf(buf, sizeof(buf), "%5.1f%%",
+ 100.0 * (float)count / (float)total);
+ }
+
+ return buf;
+}
+
+/* fields */
+
+field::field(int64_t v)
+{
+ type = FIELD::INTEGER;
+ value[0] = v;
+}
+
+field::field(float v)
+{
+ type = FIELD::FLOAT;
+ fvalue = v;
+}
+
+field::field(const std::string &s)
+{
+ type = FIELD::STRING;
+ svalue = s;
+}
+
+field::field(int64_t c, int64_t t, bool percent)
+{
+ if (percent)
+ type = FIELD::PERCENT;
+ else
+ type = FIELD::SAMPLE;
+ value[0] = c;
+ value[1] = t;
+}
+
+field::field(int64_t uc, int64_t ut, int64_t ku, int64_t kt)
+{
+ type = FIELD::DSAMPLE;
+ value[0] = uc;
+ value[1] = ut;
+ value[2] = ku;
+ value[3] = kt;
+}
+
+bool
+field::operator>(const field &b) const
+{
+ switch (type) {
+ case FIELD::STRING:
+ return (svalue > b.svalue);
+ case FIELD::FLOAT:
+ return (fvalue > b.fvalue);
+ case FIELD::INTEGER: [[fallthrough]];
+ case FIELD::SAMPLE:
+ return (value[0] > b.value[0]);
+ case FIELD::DSAMPLE:
+ return ((value[0] + value[2]) > (b.value[0] + b.value[2]));
+ case FIELD::PERCENT:
+ return (((float)value[0] / (float)value[1]) >
+ ((float)b.value[0] / (float)b.value[1]));
+ }
+}
+
+bool
+field::operator<(const field &b) const
+{
+ switch (type) {
+ case FIELD::STRING:
+ return (svalue < b.svalue);
+ case FIELD::FLOAT:
+ return (fvalue < b.fvalue);
+ case FIELD::INTEGER: [[fallthrough]];
+ case FIELD::SAMPLE:
+ return (value[0] < b.value[0]);
+ case FIELD::DSAMPLE:
+ return ((value[0] + value[2]) < (b.value[0] + b.value[2]));
+ case FIELD::PERCENT:
+ return (((float)value[0] / (float)value[1]) <
+ ((float)b.value[0] / (float)b.value[1]));
+ }
+}
+
+size_t
+field::length()
+{
+ return (to_string().length());
+}
+
+std::string
+field::to_string()
+{
+ switch (type) {
+ case FIELD::STRING:
+ return (svalue);
+ case FIELD::FLOAT:
+ return (std::to_string(fvalue));
+ case FIELD::INTEGER:
+ return (std::to_string(value[0]));
+ case FIELD::SAMPLE:
+ return (format_sample(value[0], value[1]));
+ case FIELD::DSAMPLE:
+ return (format_sample(value[0], value[1]) + " / " +
+ format_sample(value[2], value[3]));
+ case FIELD::PERCENT:
+ return (format_percent(value[0], value[1]));
+ }
+}
+
+/* table class */
+
+table::table() : sortcol(-1), sortdir(true), cols(), align(), rows()
+{
+}
+
+table::~table()
+{
+}
+
+void
+table::addcolumn(const std::string &c, bool alignleft)
+{
+ cols.push_back(c);
+ align.push_back(alignleft);
+}
+
+void
+table::addrow(std::vector<field> r)
+{
+ rows.push_back(std::move(r));
+}
+
+void
+table::sort(int col, bool descending)
+{
+ sortcol = col;
+ sortdir = descending;
+ if (descending)
+ std::sort(rows.begin(), rows.end(), [col](auto &a, auto &b)
+ { return (a[col] > b[col]); });
+ else
+ std::sort(rows.begin(), rows.end(), [col](auto &a, auto &b)
+ { return (a[col] < b[col]); });
+}
+
+void
+table::print()
+{
+ unsigned long c;
+ std::vector<unsigned long> width;
+
+ // Compute column width
+ for (auto h : cols)
+ width.push_back(h.length() + 1);
+ if (sortcol != -1)
+ width[sortcol] += 1;
+ for (auto &r : rows) {
+ for (c = 0; c < r.size(); c++) {
+ if (width[c] < r[c].length())
+ width[c] = r[c].length();
+ }
+ }
+
+ for (c = 0; c < width.size(); c++) {
+ if (align[c])
+ std::cout << std::left;
+ std::cout << std::setw(width[c]);
+ tcemit(TC_BOLD);
+ if (c == (unsigned long)sortcol)
+ std::cout << cols[c] + " " + arrow_char[sortdir ? ARROW_DOWN : ARROW_UP];
+ else
+ std::cout << cols[c];
+ tcemit(TC_SGR0);
+ if (c != (width.size() - 1))
+ std::cout << " " << border_char[BORDER_VBAR] << " ";
+ if (align[c])
+ std::cout << std::right;
+ }
+ std::cout << std::endl;
+
+ for (c = 0; c < width.size(); c++) {
+ for (unsigned long i = 0; i < width[c]; i++)
+ std::cout << border_char[BORDER_HBAR];
+ if (c != (width.size() - 1))
+ std::cout << border_char[BORDER_HBAR] <<
+ border_char[BORDER_CROSS] << border_char[BORDER_HBAR];
+ }
+ std::cout << std::setfill(' ') << std::endl;
+
+ for (auto &r : rows) {
+ for (c = 0; c < width.size(); c++) {
+ if (align[c])
+ std::cout << std::left;
+ std::cout << std::setw(width[c]) << r[c].to_string();
+ if (c != (width.size() - 1))
+ std::cout << " " << border_char[BORDER_VBAR] << " ";
+ if (align[c])
+ std::cout << std::right;
+ }
+ std::cout << std::endl;
+ }
+
+ std::cout << std::endl;
+}
+
diff --git a/usr.sbin/pmc/display.hh b/usr.sbin/pmc/display.hh
new file mode 100644
index 000000000000..4fa67a4beb30
--- /dev/null
+++ b/usr.sbin/pmc/display.hh
@@ -0,0 +1,105 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026, Netflix, Inc.
+ *
+ * This software was developed by Ali Mashtizadeh under the sponsorship from
+ * Netflix, Inc.
+ *
+ * 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 AUTHOR 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 AUTHOR 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.
+ *
+ */
+
+#define COLOR256_GREEN 46
+#define COLOR256_RED 196
+#define COLOR256_YELLOW 226
+
+void setup_screen();
+
+/* Formatting */
+std::string format_siprefix(uint64_t count);
+std::string format_binprefix(uint64_t count);
+std::string format_sample(uint64_t count, uint64_t total);
+std::string format_percent(uint64_t count, uint64_t total);
+
+/* Printing */
+enum class siunit {
+ seconds,
+ // Special
+ cycles,
+ percent
+};
+
+void title(const std::string &msg);
+void header(const std::string &msg);
+void printval(const std::string &msg, uint64_t val, siunit ui);
+void printval(const std::string &msg, float val, siunit ui);
+void printbar(float percent);
+
+/* Tables */
+class field
+{
+public:
+ field() { }
+ field(const std::string &s);
+ field(int64_t v);
+ field(float v);
+ field(int64_t count, int64_t total, bool percent = false);
+ field(int64_t user_count, int64_t user_total,
+ int64_t kernel_count, int64_t kernel_total);
+ virtual ~field() { }
+ virtual bool operator>(const field &b) const;
+ virtual bool operator<(const field &b) const;
+ virtual std::string to_string();
+ virtual size_t length();
+private:
+ enum class FIELD {
+ STRING,
+ INTEGER,
+ FLOAT,
+ SAMPLE,
+ DSAMPLE,
+ PERCENT
+ };
+ FIELD type;
+ int64_t value[4];
+ float fvalue;
+ std::string svalue;
+};
+
+class table
+{
+public:
+ table();
+ ~table();
+ void addcolumn(const std::string &c, bool alignleft = false);
+ void addrow(std::vector<field> r);
+ void sort(int col, bool descending = true);
+ void print();
+private:
+ int sortcol;
+ bool sortdir;
+ std::vector<std::string> cols;
+ std::vector<bool> align;
+ std::vector<std::vector<field>> rows;
+};
+
diff --git a/usr.sbin/pmc/headers.hh b/usr.sbin/pmc/headers.hh
new file mode 100644
index 000000000000..6bdabc4fbe6c
--- /dev/null
+++ b/usr.sbin/pmc/headers.hh
@@ -0,0 +1,83 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026, Netflix, Inc.
+ *
+ * This software was developed by Ali Mashtizadeh under the sponsorship from
+ * Netflix, Inc.
+ *
+ * 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 AUTHOR 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 AUTHOR 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.
+ *
+ */
+
+#ifndef __HEADERS_HH__
+#define __HEADERS_HH__
+
+#define PMC_HEADER_MAGIC 0x504D434C
+#define PMC_HEADER_VERSION 0x00
+
+#define PMC_ARCH_AMD64 0x01
+#define PMC_ARCH_ARM64 0x02
+#define PMC_ARCH_PPC64 0x03
+#define PMC_ARCH_RISCV64 0x04
+#define PMC_ARCH_ARM 0x05
+
+struct pmchdr_header
+{
+ uint32_t magic;
+ uint16_t version;
+ uint16_t arch;
+};
+
+#define INFOHDR_TYPE_DONE 0x00
+#define INFOHDR_TYPE_SYSINFO 0x01
+#define INFOHDR_TYPE_PMCINFO 0x02
+#define INFOHDR_TYPE_CPUID 0x03
+
+struct pmchdr_infohdr
+{
+ uint8_t type;
+ uint8_t reserved0;
+ uint16_t length;
+ uint32_t _reserved1;
+};
+
+struct pmchdr_sysinfo
+{
+ char cpumodel[64];
+ char osrelease[64];
+ char buildid[64];
+};
+
+struct pmchdr_pmcinfo
+{
+ uint64_t rate;
+ char pmc[];
+};
+
+struct pmchdr_cpuidinfo
+{
+ uint32_t cpuid[];
+};
+
+#endif
+
diff --git a/usr.sbin/pmc/pmc.c b/usr.sbin/pmc/pmc.c
index 4b4d113e2bd4..92f27db55026 100644
--- a/usr.sbin/pmc/pmc.c
+++ b/usr.sbin/pmc/pmc.c
@@ -36,11 +36,11 @@
#include <string.h>
#include <pmc.h>
#include <pmclog.h>
+#include <libelf.h>
#include <libpmcstat.h>
#include <sysexits.h>
#include <unistd.h>
-#include <libpmcstat.h>
#include "cmd_pmc.h"
int pmc_displayheight = DEFAULT_DISPLAY_HEIGHT;
@@ -57,27 +57,44 @@ struct pmcstat_process_hash_list pmcstat_process_hash[PMCSTAT_NHASH];
struct cmd_handler {
const char *ch_name;
cmd_disp_t ch_fn;
+ const char *ch_desc;
};
static struct cmd_handler disp_table[] = {
- {"stat", cmd_pmc_stat},
- {"stat-system", cmd_pmc_stat_system},
- {"list-events", cmd_pmc_list_events},
- {"filter", cmd_pmc_filter},
- {"summary", cmd_pmc_summary},
- {NULL, NULL}
+ { "filter", cmd_pmc_filter, NULL },
+ { "frontend", cmd_pmc_frontend, "Analyze front-end stalls" },
+ { "info", cmd_pmc_info, "Display high level information about a log" },
+ { "list-events", cmd_pmc_list_events, "List available PMC events" },
+ { "record", cmd_pmc_record, "Record a performance sample" },
+ { "stat", cmd_pmc_stat, NULL },
+ { "stat-system", cmd_pmc_stat_system, NULL },
+ { "summary", cmd_pmc_summary, NULL },
+ { NULL, NULL, NULL }
};
-static void __dead2
+static void
usage(void)
{
- errx(EX_USAGE,
- "\t pmc management utility\n"
- "\t stat <program> run program and print stats\n"
- "\t stat-system <program> run program and print system wide stats for duration of execution\n"
- "\t list-events list PMC events available on host\n"
- "\t filter filter records by lwp, pid, or event\n"
- );
+ int i;
+
+ fprintf(stderr, "Usage: pmc <COMMAND> [OPTIONS] ...\n\n");
+ fprintf(stderr, "PMC tool\n\n");
+
+ fprintf(stderr, "Commands:\n");
+ for (i = 0; disp_table[i].ch_name != NULL; i++) {
+ // Hide stuff we plan to deprecate
+ if (disp_table[i].ch_desc)
+ fprintf(stderr, " %-12s %s\n",
+ disp_table[i].ch_name, disp_table[i].ch_desc);
+ }
+
+ fprintf(stderr, "\nEnvironment Variables:\n");
+ fprintf(stderr, " CLICOLOR Enables color graphs\n");
+ fprintf(stderr, " SYSROOT Path to system root\n");
+ fprintf(stderr, " SYMROOT Path to debug symbols\n");
+ fprintf(stderr, " TERM Controls symbols and color palettes\n");
+
+ exit(EX_USAGE);
}
static cmd_disp_t
@@ -109,9 +126,14 @@ main(int argc, char **argv)
/* Allocate a kqueue */
if ((pmc_kq = kqueue()) < 0)
err(EX_OSERR, "ERROR: Cannot allocate kqueue");
+
if (pmc_init() < 0)
err(EX_UNAVAILABLE,
"ERROR: Initialization of the pmc(3) library failed"
);
+
+ if (elf_version(EV_CURRENT) == EV_NONE)
+ err(EX_UNAVAILABLE, "ERROR: ELF library version too old");
+
return (disp(argc, argv));
}
diff --git a/usr.sbin/pmc/util.cc b/usr.sbin/pmc/util.cc
new file mode 100644
index 000000000000..b6e43b9dd130
--- /dev/null
+++ b/usr.sbin/pmc/util.cc
@@ -0,0 +1,82 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026, Netflix, Inc.
+ *
+ * This software was developed by Ali Mashtizadeh under the sponsorship from
+ * Netflix, Inc.
+ *
+ * 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 AUTHOR 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 AUTHOR 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 <string>
+#include <unordered_set>
+
+#include "util.hh"
+
+std::string
+basename(const std::string &path)
+{
+ size_t s;
+
+ s = path.rfind("/");
+ if (s == std::string::npos)
+ return (path);
+ else
+ return (path.substr(s + 1));
+}
+
+void
+split_and_insert(std::unordered_set<int> *set, const std::string &str)
+{
+ size_t pos = 0;
+
+ while (pos < str.length()) {
+ size_t end = str.find(",", pos);
+ if (end == str.npos) {
+ set->insert(std::stoi(str.substr(pos)));
+ break;
+ }
+
+ set->insert(std::stoi(str.substr(pos, end - pos)));
+ pos = end + 1;
+ }
+}
+
+void
+split_and_insert(std::unordered_set<std::string> *set, const std::string &str)
+{
+ size_t pos = 0;
+
+ while (pos < str.length()) {
+ size_t end = str.find(",", pos);
+ if (end == str.npos) {
+ set->insert(str.substr(pos));
+ break;
+ }
+
+ set->insert(str.substr(pos, end - pos));
+ pos = end + 1;
+ }
+}
+
diff --git a/usr.sbin/efivar/efiutil.h b/usr.sbin/pmc/util.hh
index c9fcb99ec1eb..03c350721b2b 100644
--- a/usr.sbin/efivar/efiutil.h
+++ b/usr.sbin/pmc/util.hh
@@ -1,5 +1,10 @@
/*-
- * Copyright (c) 2017 Netflix, Inc.
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026, Netflix, Inc.
+ *
+ * This software was developed by Ali Mashtizadeh under the sponsorship from
+ * Netflix, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -21,15 +26,14 @@
* 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.
+ *
*/
+#ifndef __UTIL_H__
+#define __UTIL_H__
-/*
- * different routines to dump data.
- */
+std::string basename(const std::string &path);
+void split_and_insert(std::unordered_set<int> *set, const std::string &input);
+void split_and_insert(std::unordered_set<std::string> *set, const std::string &input);
-void asciidump(uint8_t *data, size_t datalen);
-void bindump(uint8_t *data, size_t datalen);
-void efi_print_load_option(uint8_t *, size_t, int, int, int);
-void hexdump(uint8_t *data, size_t datalen);
-void utf8dump(uint8_t *data, size_t datalen);
+#endif /* __UTIL_HH__ */
diff --git a/usr.sbin/pmc/view.cc b/usr.sbin/pmc/view.cc
new file mode 100644
index 000000000000..352856939a66
--- /dev/null
+++ b/usr.sbin/pmc/view.cc
@@ -0,0 +1,960 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026, Netflix, Inc.
+ *
+ * This software was developed by Ali Mashtizadeh under the sponsorship from
+ * Netflix, Inc.
+ *
+ * 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 AUTHOR 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 AUTHOR 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/param.h>
+
+#include <assert.h>
+#include <err.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <inttypes.h>
+#include <libelf.h>
+#include <pmclog.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <cxxabi.h>
+#include <iostream>
+#include <map>
+#include <set>
+#include <string>
+#include <sstream>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <dev/hwpmc/hwpmc_ibs.h>
+#include "util.hh"
+#include "view.hh"
+
+std::string
+syminfo::to_string(bool show_line)
+{
+ int status;
+ char *demangled;
+ std::stringstream ss;
+
+ if (name == "") {
+ ss << "0x" << std::hex << offset;
+ } else if (name.length() >= 2 && name[0] == '_' && name[1] == 'Z') {
+ demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);
+ if (status == 0) {
+ ss << demangled;
+ free(demangled);
+ } else {
+ ss << name;
+ }
+ } else {
+ ss << name;
+ }
+
+ if (!show_line)
+ return ss.str();
+
+ if (line) {
+ ss << ":" << std::dec << line;
+ } else if (funcoff) {
+ ss << "+0x" << std::hex << funcoff;
+ }
+
+ return ss.str();
+}
+
+pmcview::pmcview() : tscfreq(0), pmcid(), pmcinfo(), procs(), tidtopid(),
+ images(), sysroot(""), filter()
+{
+ char *root;
+
+ root = getenv("SYSROOT");
+ if (root) {
+ sysroot = root;
+ }
+}
+
+pmcview::~pmcview()
+{
+}
+
+void
+pmcview::setfilter(const pmcfilter &pmcfilter)
+{
+ filter = pmcfilter;
+}
+
+static int
+readlog(int logfd, void *buf, size_t len)
+{
+ int status;
+ char *cur = (char *)buf;
+ size_t left = len;
+
+ while (left != 0) {
+ status = read(logfd, cur, left);
+ if (status < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ else
+ return status;
+ }
+ if (status == 0)
+ return status;
+
+ cur += status;
+ left -= status;
+ }
+
+ return len;
+}
+
+int
+pmcview::process_sysinfo(int logfd, const pmchdr_infohdr &infohdr)
+{
+ int status;
+ pmchdr_sysinfo sysinfo;
+
+ if (infohdr.length != sizeof(sysinfo))
+ errx(EX_IOERR, "PMC log headers have an unexpected size");
+
+ status = readlog(logfd, &sysinfo, sizeof(sysinfo));
+ if (status < 0 || status != infohdr.length)
+ errx(EX_IOERR, "readlog");
+
+ cpumodel = sysinfo.cpumodel;
+ osrelease = sysinfo.osrelease;
+ buildid = sysinfo.buildid;
+
+ return 0;
+}
+
+int
+pmcview::process_pmcinfo(int logfd, const pmchdr_infohdr &infohdr)
+{
+ int status;
+ union {
+ pmchdr_pmcinfo pmcinfo;
+ __unused char reserve_max_size[256];
+ };
+
+ status = readlog(logfd, &pmcinfo, infohdr.length);
+ if (status < 0 || status != infohdr.length)
+ errx(EX_IOERR, "readlog");
+
+ extpmcinfo.emplace_back(pmcinfo.rate, std::string(pmcinfo.pmc));
+
+ return 0;
+}
+
+int
+pmcview::process_cpuidinfo(int logfd, const pmchdr_infohdr &infohdr)
+{
+ int status;
+ pmchdr_cpuidinfo *cpuidinfo;
+ int offset, len;
+ uint32_t root, maxleaf, count;
+
+ len = infohdr.length / 4;
+ cpuidinfo = (pmchdr_cpuidinfo *)new uint32_t[len];
+
+ status = readlog(logfd, cpuidinfo, infohdr.length);
+ if (status < 0 || status != infohdr.length)
+ errx(EX_IOERR, "readlog");
+
+ offset = 0;
+
+ while (offset < len) {
+ maxleaf = cpuidinfo->cpuid[offset];
+
+ /*
+ * In x86 the roots contains the maximum leaf number present.
+ */
+ root = maxleaf & 0xFFFF0000;
+ count = maxleaf & 0x0000FFFF;
+
+ for (uint32_t i = 0; i <= count; i++) {
+ cpuid[root + i] = { cpuidinfo->cpuid[4 * i + offset],
+ cpuidinfo->cpuid[4 * i + 1 + offset],
+ cpuidinfo->cpuid[4 * i + 2 + offset],
+ cpuidinfo->cpuid[4 * i + 3 + offset] };
+ }
+
+ offset += 4 * (count + 1);
+ }
+
+ delete[] cpuidinfo;
+
+ return 0;
+}
+
+void
+pmcview::process(int logfd)
+{
+ int status;
+ pmchdr_header hdr;
+ struct pmclog_parse_state *ps;
+
+ // Read header
+ status = readlog(logfd, &hdr, sizeof(hdr));
+ if (status < 0 || status != sizeof(hdr))
+ err(EX_IOERR, "readlog");
+ if (hdr.magic != PMC_HEADER_MAGIC)
+ errx(EX_DATAERR, "PMC log magic mismatch!");
+ if (hdr.version > PMC_HEADER_VERSION)
+ errx(EX_DATAERR, "PMC log version newer than supported!");
+
+ // Save architecture
+ arch = hdr.arch;
+
+ while (1) {
+ pmchdr_infohdr infohdr;
+
+ status = readlog(logfd, &infohdr, sizeof(infohdr));
+ if (status < 0 || status != sizeof(infohdr))
+ errx(EX_IOERR, "readlog");
+
+ if (infohdr.type == INFOHDR_TYPE_DONE) {
+ break;
+ } else if (infohdr.type == INFOHDR_TYPE_SYSINFO) {
+ process_sysinfo(logfd, infohdr);
+ } else if (infohdr.type == INFOHDR_TYPE_PMCINFO) {
+ process_pmcinfo(logfd, infohdr);
+ } else if (infohdr.type == INFOHDR_TYPE_CPUID) {
+ process_cpuidinfo(logfd, infohdr);
+ }
+ }
+
+ ps = static_cast<struct pmclog_parse_state*>(pmclog_open(logfd));
+ if (ps == NULL) {
+ errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n",
+ strerror(errno));
+ }
+
+ process(ps);
+
+ pmclog_close(ps);
+}
+
+void
+pmcview::process(struct pmclog_ev &p)
+{
+ switch (p.pl_type) {
+ case PMCLOG_TYPE_INITIALIZE:
+ process(p.pl_u.pl_i);
+ return;
+ case PMCLOG_TYPE_CLOSELOG:
+ process(p.pl_u.pl_cl);
+ return;
+ case PMCLOG_TYPE_PMCALLOCATE:
+ process(p.pl_u.pl_a);
+ return;
+ case PMCLOG_TYPE_PMCALLOCATEDYN:
+ process(p.pl_u.pl_ad);
+ return;
+ case PMCLOG_TYPE_PROC_CREATE:
+ process(p.pl_u.pl_pc);
+ return;
+ case PMCLOG_TYPE_PROCEXIT:
+ process(p.pl_u.pl_e);
+ return;
+ case PMCLOG_TYPE_PROCEXEC:
+ process(p.pl_u.pl_x);
+ return;
+ case PMCLOG_TYPE_PROCFORK:
+ process(p.pl_u.pl_f);
+ return;
+ case PMCLOG_TYPE_SYSEXIT:
+ process(p.pl_u.pl_se);
+ return;
+ case PMCLOG_TYPE_MAP_IN:
+ process(p.pl_u.pl_mi);
+ return;
+ case PMCLOG_TYPE_MAP_OUT:
+ process(p.pl_u.pl_mo);
+ return;
+ case PMCLOG_TYPE_THR_CREATE:
+ process(p.pl_u.pl_tc);
+ return;
+ case PMCLOG_TYPE_THR_EXIT:
+ process(p.pl_u.pl_te);
+ return;
+ case PMCLOG_TYPE_CALLCHAIN:
+ process(p.pl_u.pl_cc);
+ return;
+ case PMCLOG_TYPE_PMCATTACH: [[fallthrough]];
+ case PMCLOG_TYPE_PMCDETACH:
+ case PMCLOG_TYPE_USERDATA:
+ case PMCLOG_TYPE_PROCCSW:
+ case PMCLOG_TYPE_DROPNOTIFY:
+ return;
+ };
+}
+
+void
+pmcview::process(struct pmclog_parse_state *p)
+{
+ struct pmclog_ev ev;
+
+ while (pmclog_read(p, &ev) == 0) {
+ process(ev);
+ }
+}
+
+void
+pmcview::process(struct pmclog_ev_initialize &p)
+{
+ tscfreq = p.pl_tsc_freq;
+}
+
+void
+pmcview::process(__unused struct pmclog_ev_closelog &p)
+{
+}
+
+void
+pmcview::process(struct pmclog_ev_pmcallocate &p)
+{
+ pmcid[p.pl_pmcid] = p.pl_event;
+ if (pmcinfo.find(p.pl_event) == pmcinfo.end()) {
+ pmcinfo.emplace(p.pl_event, p);
+ }
+}
+
+void
+pmcview::process(struct pmclog_ev_pmcallocatedyn &p)
+{
+ pmcid[p.pl_pmcid] = p.pl_event;
+ if (pmcinfo.find(p.pl_event) == pmcinfo.end()) {
+ pmcinfo.emplace(p.pl_event, p);
+ }
+}
+
+void
+pmcview::process(struct pmclog_ev_proccreate &p)
+{
+ procs[p.pl_pid] = procinfo(p);
+
+ proccreate(p.pl_pid);
+}
+
+void
+pmcview::process(struct pmclog_ev_procexit &p)
+{
+ /*
+ * XXX: Seems we can recieve samples after the process exits we need
+ * some way to delay cleanup, then after the delayed cleanup discard
+ * delayed events.
+ */
+ procs.erase(p.pl_pid);
+
+ procexit(p.pl_pid);
+}
+
+void
+pmcview::process(struct pmclog_ev_procexec &p)
+{
+ procs[p.pl_pid].map.clear();
+ procs[p.pl_pid].name = basename(p.pl_pathname);
+ procs[p.pl_pid].fullpath = p.pl_pathname;
+ procs[p.pl_pid].baseaddr = p.pl_baseaddr;
+ procs[p.pl_pid].dynaddr = p.pl_dynaddr;
+
+ image im = loadimage(p.pl_pathname);
+
+ mapimage(p.pl_pid, im, im.vaddr + p.pl_dynaddr);
+
+ /*
+ * Map the dynamic runtime loader
+ */
+ if (im.isdynamic) {
+ image rtldim = loadimage(im.loader);
+
+ mapimage(p.pl_pid, rtldim, p.pl_baseaddr);
+ }
+
+ procexec(p.pl_pid);
+}
+
+void
+pmcview::process(struct pmclog_ev_procfork &p)
+{
+ procs[p.pl_newpid] = procs[p.pl_oldpid];
+
+ proccreate(p.pl_newpid);
+}
+
+void
+pmcview::process(struct pmclog_ev_sysexit &p)
+{
+ procs.erase(p.pl_pid);
+}
+
+void
+pmcview::process(struct pmclog_ev_threadcreate &p)
+{
+ procs[p.pl_pid].threads[p.pl_tid] = threadinfo(p.pl_tdname);
+ tidtopid[p.pl_tid] = p.pl_pid;
+}
+
+void
+pmcview::process(struct pmclog_ev_threadexit &p)
+{
+ pid_t pid = tidtopid[p.pl_tid];
+ procs[pid].threads.erase(p.pl_tid);
+ tidtopid.erase(p.pl_tid);
+}
+
+/*
+ * Load the ELF file to compute the values needed for the memory map and check
+ * if the dwarf symbols are available.
+ */
+image
+pmcview::loadimage(const std::string &path)
+{
+ std::string fullpath;
+ image im;
+ GElf_Ehdr eh;
+ Elf *e;
+ const char *elf;
+ uint64_t start;
+ uint64_t end;
+ size_t shstrndx;
+ int fd, i;
+ bool foundexec;
+
+ if (images.find(path) != images.end())
+ return images[path];
+
+ im = image();
+
+ if (path == "unknown") {
+ return (im);
+ }
+
+ fullpath = sysroot + path;
+
+ if (access(fullpath.c_str(), R_OK) != 0)
+ return (im);
+
+ fd = open(fullpath.c_str(), O_RDONLY, 0);
+ if (fd < 0) {
+ warnx("WARNING: Cannot open \"%s\".",
+ fullpath.c_str());
+ return (im);
+ }
+
+ e = elf_begin(fd, ELF_C_READ, NULL);
+ if (e == NULL) {
+ warnx("WARNING: Cannot read \"%s\".",
+ fullpath.c_str());
+ close(fd);
+ return (im);
+ }
+
+ if (gelf_getehdr(e, &eh) != &eh) {
+ warnx("WARNING: Cannot read the ELF header for \"%s\": %s.",
+ fullpath.c_str(), elf_errmsg(-1));
+ goto done;
+ }
+
+ if (eh.e_type != ET_EXEC && eh.e_type != ET_DYN && eh.e_type != ET_REL) {
+ warnx("WARNING: ELF file type is unsupported for \"%s\".",
+ fullpath.c_str());;
+ goto done;
+ }
+
+ elf = elf_rawfile(e, NULL);
+ if (elf == NULL) {
+ warnx("WARNING: Cannot read the ELF file for \"%s\": %s.",
+ fullpath.c_str(), elf_errmsg(-1));
+ goto done;
+ }
+
+ if (eh.e_type != ET_REL) {
+ foundexec = false;
+ for (i = 0; i < eh.e_phnum; i++) {
+ GElf_Phdr ph;
+
+ if (gelf_getphdr(e, i, &ph) != &ph) {
+ warnx("WARNING: Cannot read program header for \"%s\": %s.",
+ fullpath.c_str(), elf_errmsg(-1));
+ goto done;
+ }
+
+ if (ph.p_type == PT_DYNAMIC) {
+ im.isdynamic = 1;
+ continue;
+ }
+
+ if (ph.p_type == PT_INTERP) {
+ im.loader = elf + ph.p_offset;
+ }
+
+ if (ph.p_type == PT_LOAD) {
+ if ((ph.p_flags & PF_X) != 0 && !foundexec) {
+ im.vaddr = ph.p_vaddr & ~(ph.p_align - 1);
+ foundexec = true;
+ }
+ }
+ }
+ }
+
+ elf_getshdrstrndx(e, &shstrndx);
+
+ start = ~(0x0ULL);
+ end = 0;
+ for (i = 0; i < eh.e_shnum; i++) {
+ GElf_Shdr sh;
+ Elf_Scn *scn;
+ char *scname;
+
+ scn = elf_getscn(e, i);
+ if (scn == NULL) {
+ warnx("WARNING: Could not retrieve section descriptor for \"%s\".",
+ fullpath.c_str());
+ goto done;
+ }
+
+ if (gelf_getshdr(scn, &sh) != &sh) {
+ warnx("WARNING: Could not retrieve section header for \"%s\".",
+ fullpath.c_str());
+ goto done;
+ }
+
+ if (sh.sh_flags & SHF_EXECINSTR) {
+ start = std::min(start, sh.sh_addr);
+ end = std::max(end, sh.sh_addr + sh.sh_size);
+ }
+
+ // Check if dwarf is embedded
+ scname = elf_strptr(e, shstrndx, sh.sh_name);
+ if (scname != NULL && strcmp(scname, ".debug_info") == 0)
+ im.dwarf = fullpath;
+ }
+
+ im.start = start;
+ im.end = end;
+ im.binary = path;
+ im.path = fullpath;
+ im.name = basename(fullpath);
+
+ /*
+ * If the dwarf symbols aren't embedded check:
+ * 1. SYSROOT + /path + .debug
+ * 2. SYSROOT + /usr/lib/debug + /path + .debug
+ */
+ if (im.dwarf == "") {
+ std::string sympath = fullpath + ".debug";
+ if (access(sympath.c_str(), R_OK) == 0) {
+ im.dwarf = sympath;
+ }
+ }
+ if (im.dwarf == "") {
+ std::string sympath = sysroot + "/usr/lib/debug" + path + ".debug";
+ if (access(sympath.c_str(), R_OK) == 0) {
+ im.dwarf = sympath;
+ }
+ }
+
+ images[path] = im;
+
+done:
+ elf_end(e);
+ close(fd);
+
+ return (im);
+}
+
+void
+pmcview::loadsymboltable(image *im, Elf *e, Elf_Scn *scn, GElf_Shdr *sh)
+{
+ size_t n, nsyms;
+ char *fname;
+ GElf_Sym sym;
+ Elf_Data *data;
+ syminfo si;
+
+ si = syminfo();
+
+ if ((data = elf_getdata(scn, nullptr)) == nullptr)
+ return;
+
+ nsyms = sh->sh_size / sh->sh_entsize;
+
+ for (n = 0; n < nsyms; n++) {
+ if (gelf_getsym(data, (int) n, &sym) != &sym)
+ return;
+
+ // XXX: We should load globals as well
+ if (GELF_ST_TYPE(sym.st_info) != STT_FUNC)
+ continue;
+
+ if (sym.st_shndx == STN_UNDEF)
+ continue;
+
+ if ((fname = elf_strptr(e, sh->sh_link, sym.st_name)) == NULL)
+ continue;
+
+ // XXX: Extra checks to make sure we don't get corrupted
+ for (int i = 0; fname[i] != 0 && i < 32; i++) {
+ if (fname[i] < 0) {
+ printf("EEEK SYMBOL\n");
+ printf("%s\n", fname);
+ assert(false);
+ }
+ }
+
+ si.offset = sym.st_value;
+ si.length = sym.st_size;
+ si.binary = im->name;
+ si.name = fname;
+
+ im->symbols[si.offset] = si;
+ }
+}
+
+void
+pmcview::loadsymbols(image *im)
+{
+ int i;
+ int fd;
+ Elf *e;
+ Elf_Scn *scn;
+ GElf_Ehdr eh;
+ GElf_Shdr sh;
+
+ if (access(im->path.c_str(), R_OK) != 0)
+ return;
+
+ fd = open(im->path.c_str(), O_RDONLY, 0);
+ if (fd < 0) {
+ warnx("WARNING: Cannot open \"%s\".",
+ im->path.c_str());
+ return;
+ }
+
+ e = elf_begin(fd, ELF_C_READ, NULL);
+ if (e == NULL) {
+ warnx("WARNING: Cannot read \"%s\".",
+ im->path.c_str());
+ close(fd);
+ return;
+ }
+
+ if (gelf_getehdr(e, &eh) != &eh) {
+ warnx("WARNING: Cannot read the ELF header for \"%s\": %s.",
+ im->path.c_str(), elf_errmsg(-1));
+ goto done;
+ }
+
+ for (i = 0; i < eh.e_shnum; i++) {
+ scn = elf_getscn(e, i);
+ if (scn == NULL) {
+ warnx("WARNING: Could not retrieve section descriptor for \"%s\".",
+ im->path.c_str());
+ goto done;
+ }
+
+ if (gelf_getshdr(scn, &sh) != &sh) {
+ warnx("WARNING: Could not retrieve section header for \"%s\".",
+ im->path.c_str());
+ goto done;
+ }
+
+ if (sh.sh_type == SHT_SYMTAB || sh.sh_type == SHT_DYNSYM) {
+ loadsymboltable(im, e, scn, &sh);
+ }
+ }
+
+done:
+ elf_end(e);
+ close(fd);
+}
+
+void
+pmcview::mapimage(int pid, const image &im, uint64_t start)
+{
+ uint64_t offset;
+ vmmap map;
+
+ // Ignore empty images
+ if (im.start == 0 && im.end == 0)
+ return;
+
+ /*
+ * XXX: Need to adjust the address for the PowerPC kernel that is
+ * dynamic. Wonder if we can fix this elsewhere, because we should need
+ * a way to deal with this for KASLR?.
+ */
+
+ offset = start - im.vaddr;
+ map.lowpc = im.start + offset;
+ map.highpc = im.end + offset;
+ map.image = im.binary;
+
+ procs[pid].map[start] = map;
+}
+
+void
+pmcview::process(struct pmclog_ev_map_in &p)
+{
+ // Kernel map-in events should be mapped to pid 0
+ pid_t pid = (p.pl_pid == -1) ? 0 : p.pl_pid;
+
+ image im = loadimage(p.pl_pathname);
+
+ mapimage(pid, im, p.pl_start);
+}
+
+void
+pmcview::process(struct pmclog_ev_map_out &p)
+{
+ // Kernel map-in events should be mapped to pid 0
+ pid_t pid = (p.pl_pid == -1) ? 0 : p.pl_pid;
+ procinfo &proc = procs[pid];
+
+ /*
+ * XXX: We should handle all the mmap/munmap cases but only executable
+ * file mappings are included.
+ */
+ proc.map.erase(p.pl_start);
+}
+
+void
+pmcview::process(struct pmclog_ev_callchain &p)
+{
+ int i;
+ uint8_t *hdr = (uint8_t *)&p.pl_pc[0];
+ uint8_t type, len;
+ uintfptr_t *cc = &p.pl_pc[1];
+ ibsfetchinfo ibsf;
+ ibsopinfo ibso;
+
+ /*
+ * Callchain events are always attributed to one of the kernel
+ * processes. Make sure nobody breaks our assumption.
+ */
+ assert(p.pl_pid != ~(uint32_t)0);
+
+ ibsf.len = 0;
+ ibso.len = 0;
+
+ if (p.pl_cpuflags & PMC_CC_F_MULTIPART) {
+ for (i = 0; i < 4; i++) {
+ type = hdr[2 * i];
+ len = hdr[2 * i + 1];
+
+ switch (type) {
+ case PMC_CC_MULTIPART_IBS_FETCH:
+ ibsf.len = len;
+ ibsf.ctl = cc[PMC_MPIDX_FETCH_CTL];
+ ibsf.extctl = cc[PMC_MPIDX_FETCH_EXTCTL];
+ ibsf.linaddr = cc[PMC_MPIDX_FETCH_LINADDR];
+ ibsf.physaddr = cc[PMC_MPIDX_FETCH_PHYSADDR];
+ break;
+ case PMC_CC_MULTIPART_IBS_OP:
+ ibso.len = len;
+ ibso.ctl = cc[PMC_MPIDX_OP_CTL];
+ ibso.rip = cc[PMC_MPIDX_OP_RIP];
+ ibso.data = cc[PMC_MPIDX_OP_DATA];
+ ibso.data2 = cc[PMC_MPIDX_OP_DATA2];
+ ibso.data3 = cc[PMC_MPIDX_OP_DATA3];
+ ibso.linaddr = cc[PMC_MPIDX_OP_DC_LINADDR];
+ ibso.physaddr = cc[PMC_MPIDX_OP_DC_PHYSADDR];
+ ibso.tgtrip = cc[PMC_MPIDX_OP_TGT_RIP];
+ ibso.data4 = cc[PMC_MPIDX_OP_DATA4];
+ break;
+ }
+
+ cc += len;
+ }
+ }
+
+ /*
+ * Discard delayed events so we do not get unattributed samples.
+ */
+ auto pinfo = procs.find(p.pl_pid);
+ if (pinfo == procs.end())
+ return;
+
+ // Filter on pid, tid, program, thread and events
+ if (filter.filterpid && filter.pids.count(p.pl_pid) == 0)
+ return;
+ if (filter.filtertid && filter.tids.count(p.pl_tid) == 0)
+ return;
+ if (filter.filterprogram && filter.programs.count(pinfo->second.name) == 0)
+ return;
+ if (filter.filterthread) {
+ auto tinfo = pinfo->second.threads.find(p.pl_tid);
+ if (tinfo == pinfo->second.threads.end())
+ return;
+ if (filter.threads.count(tinfo->second.name) == 0)
+ return;
+ }
+ if (filter.filterevent) {
+ auto pmc = pmcid.find(p.pl_pmcid);
+ if (pmc == pmcid.end())
+ return;
+ if (filter.events.count(pmcinfo[pmc->second].name) == 0)
+ return;
+ }
+
+ // Filter on cpuset
+ int cpunum = PMC_CALLCHAIN_CPUFLAGS_TO_CPU(p.pl_cpuflags);
+ if (filter.filtercpu && !CPU_ISSET(cpunum, &filter.cpus)) {
+ return;
+ }
+
+ // Filter on user/kernel mode
+ int usermode = PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(p.pl_cpuflags);
+ if (filter.useronly && usermode == 0) {
+ return;
+ }
+ if (filter.kernelonly && usermode != 0) {
+ return;
+ }
+
+ // Advanced filters for AMD IBS
+ if (ibsf.len) {
+ if (filter.ibs_ldlat > IBS_FETCH_CTL_TO_LAT(ibsf.ctl))
+ return;
+ callchain(p, ibsf, cc, len);
+ } else if (ibso.len) {
+ if (filter.ibs_oplat > IBS_OP_DATA_TO_COMPTORET(ibso.data))
+ return;
+ if (filter.ibs_ldlat > IBS_OP_DATA3_TO_DCLAT(ibso.data3))
+ return;
+ if (filter.ibs_mmio) {
+ if (((ibso.data3 & IBS_OP_DATA3_STORE) == 0) &&
+ ((ibso.data3 & IBS_OP_DATA3_LOAD) == 0))
+ return;
+ if (((ibso.data3 & IBS_OP_DATA3_UCMEMACCESS) == 0) &&
+ ((ibso.data3 & IBS_OP_DATA3_WCMEMACCESS) == 0))
+ return;
+ }
+ callchain(p, ibso, cc, len);
+ } else {
+ callchain(p, cc, len);
+ }
+}
+
+uint32_t
+pmcview::pmcidtoeventid(uint32_t id)
+{
+ return pmcid[id];
+}
+
+std::string
+pmcview::pidtoname(pid_t pid)
+{
+ auto p = procs.find(pid);
+ if (p == procs.end())
+ return ("");
+ else
+ return (p->second.name);
+}
+
+syminfo
+pmcview::addrtosymbol(pid_t pid, uint64_t addr)
+{
+ syminfo si;
+ procinfo &proc = procs[pid];
+ vmmap *vm;
+ image *im;
+
+ im = nullptr;
+ auto v = proc.map.upper_bound(addr);
+ if (v != proc.map.begin()) {
+ v--;
+ if (v->second.lowpc <= addr && v->second.highpc >= addr) {
+ vm = &v->second;
+ im = &images[v->second.image];
+ }
+ }
+
+ if (im == nullptr) {
+#ifdef DEBUG_VIEW
+ printf("Symbol not found for 0x%lx in %s\n", addr, proc.name.c_str());
+ printvm(pid);
+#endif
+ return syminfo();
+ }
+
+ // Adjust address into the ELF's virtual address space
+ addr -= vm->lowpc - im->start;
+
+ // Load symbols if they haven't been loaded
+ if (im->symbols.size() == 0) {
+ loadsymbols(im);
+ }
+
+ // Look for the first symbol
+ auto s = im->symbols.upper_bound(addr);
+ if (s != im->symbols.begin())
+ s--;
+ if (s->second.offset <= addr &&
+ (s->second.offset + s->second.length + 1) >= addr) {
+ si = s->second;
+ si.funcoff = addr - si.offset;
+ return si;
+ }
+
+ // Special case for assembly functions in the kernel
+ if (s->second.offset <= addr && s->second.length == 0 && pid == 0) {
+ si = s->second;
+ si.funcoff = addr - si.offset;
+ return si;
+ }
+
+#ifdef DEBUG_VIEW
+ printf("Symbol not found in image %lx\n", addr);
+ printf("%lx %lx %s\n", s->second.offset, s->second.s_length, s->second.s_name.c_str());
+ printf("%lx\n", addr);
+#endif
+
+ si = syminfo();
+ si.binary = im->name;
+ std::stringstream str = std::stringstream();
+ str << "0x" << std::hex << addr;
+ si.name = str.str();
+ return si;
+}
+
+void
+pmcview::printvm(pid_t pid)
+{
+ procinfo &proc = procs[pid];
+
+ printf("%-18s %-18s %s\n", "Low PC", "High PC", "Image");
+ for (auto &i : proc.map) {
+ printf("0x%08" PRIx64 " 0x%08" PRIx64 " %s\n", i.second.lowpc,
+ i.second.highpc, i.second.image.c_str());
+ }
+}
+
diff --git a/usr.sbin/pmc/view.hh b/usr.sbin/pmc/view.hh
new file mode 100644
index 000000000000..062325b096dc
--- /dev/null
+++ b/usr.sbin/pmc/view.hh
@@ -0,0 +1,411 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026, Netflix, Inc.
+ *
+ * This software was developed by Ali Mashtizadeh under the sponsorship from
+ * Netflix, Inc.
+ *
+ * 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 AUTHOR 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 AUTHOR 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.
+ *
+ */
+#ifndef __VIEW_HH__
+#define __VIEW_HH__
+
+#include <libdwarf.h>
+#include <libelf.h>
+#include <libutil.h>
+#include <gelf.h>
+
+#include "headers.hh"
+#include "util.hh"
+
+/*
+ * PMC counter state.
+ */
+struct pmcinfo
+{
+ pmcinfo() : name(), rate(-1), event(0), count(0) { }
+ pmcinfo(struct pmclog_ev_pmcallocate &p) {
+ name = p.pl_evname;
+ rate = p.pl_rate;
+ event = p.pl_event;
+ count = 0;
+ }
+ pmcinfo(struct pmclog_ev_pmcallocatedyn &p) {
+ name = p.pl_evname;
+ rate = 0;
+ event = p.pl_event;
+ count = 0;
+ }
+ ~pmcinfo() { }
+ std::string name;
+ int rate;
+ uint32_t event;
+ uint64_t count; // Available for application use
+};
+
+/*
+ * Extended pmcinfo structure stores the complete event description passed to
+ * libpmc.
+ */
+struct pmcinfox
+{
+ pmcinfox() : rate(0), event() { }
+ pmcinfox(uint32_t rate, std::string event) : rate(rate), event(event) { }
+ ~pmcinfox() { }
+ uint32_t rate;
+ std::string event;
+};
+
+/*
+ * CPUID Leafs for X86 Logs
+ */
+struct cpuidleaf
+{
+ uint32_t eax;
+ uint32_t ebx;
+ uint32_t ecx;
+ uint32_t edx;
+};
+
+/*
+ * Stores the address range for an executable segment in the process.
+ */
+struct vmmap
+{
+ vmmap() : lowpc(0), highpc(0), image("") { }
+ ~vmmap() { }
+ uint64_t lowpc;
+ uint64_t highpc;
+ std::string image;
+};
+
+struct threadinfo
+{
+ threadinfo() : name("") { }
+ threadinfo(const std::string &name) : name(name) { }
+ ~threadinfo() { }
+ std::string name;
+};
+
+struct procinfo
+{
+ procinfo() : name(""), fullpath(""), map(), threads(),
+ baseaddr(0), dynaddr(0) { }
+ procinfo(struct pmclog_ev_proccreate &p) {
+ std::string pname = p.pl_pcomm;
+
+ if ((p.pl_flags & P_KPROC) != 0)
+ name = "[" + pname + "]";
+ else
+ name = pname;
+ map = std::map<uint64_t, vmmap>();
+ threads = std::map<pid_t, threadinfo>();
+ baseaddr = 0;
+ dynaddr = 0;
+ }
+ ~procinfo() { }
+ std::string name;
+ std::string fullpath;
+ std::map<uint64_t, vmmap> map;
+ std::map<pid_t, threadinfo> threads;
+ uint64_t baseaddr;
+ uint64_t dynaddr;
+};
+
+/*
+ * Symbol information
+ */
+struct syminfo
+{
+ syminfo() : binary(""), name(""), offset(0), length(0), funcoff(0),
+ line(0), column(0) { }
+ ~syminfo() { }
+ std::string to_string(bool show_line = true);
+ std::string binary;
+ std::string name;
+ uint64_t offset;
+ uint64_t length;
+ uint64_t funcoff;
+ uint32_t line;
+ uint32_t column;
+};
+
+struct image
+{
+ image() : isdynamic(false), vaddr(0), start(0), end(0),
+ offset(0), name(""), binary(""), path(""), loader(""),
+ dwarf(""), symbols(), dbg_fd(-1), dbg_elf(NULL) { }
+ ~image() { }
+ bool isdynamic;
+ uint64_t vaddr;
+ uint64_t start;
+ uint64_t end;
+ uint64_t offset;
+ std::string name;
+ std::string binary;
+ std::string path;
+ std::string loader;
+ std::string dwarf;
+ std::map<uint64_t, syminfo> symbols;
+ int dbg_fd;
+ Elf *dbg_elf;
+ Dwarf_Debug dbg_dwarf;
+};
+
+struct pmcfilter
+{
+ pmcfilter() : filterpid(false), filtertid(false),
+ filterprogram(false), filterthread(false),
+ filterevent(false), filtercpu(false),
+ kernelonly(false), useronly(false),
+ pids(), tids(), programs(), threads(),
+ ibs_ldlat(0), ibs_oplat(0) { CPU_ZERO(&cpus); }
+ ~pmcfilter() { }
+ bool filterpid;
+ bool filtertid;
+ bool filterprogram;
+ bool filterthread;
+ bool filterevent;
+ bool filtercpu;
+ bool kernelonly;
+ bool useronly;
+ std::unordered_set<int> pids;
+ std::unordered_set<int> tids;
+ std::unordered_set<std::string> programs;
+ std::unordered_set<std::string> threads;
+ std::unordered_set<std::string> events;
+ cpuset_t cpus;
+ /*
+ * Advanced filters for AMD IBS but should be generalized to support
+ * other processors.
+ */
+ uint64_t ibs_ldlat;
+ uint64_t ibs_oplat;
+ bool ibs_mmio;
+ void addpids(const char *ids) {
+ split_and_insert(&pids, ids);
+ filterpid = true;
+ }
+ void addtids(const char *ids) {
+ split_and_insert(&tids, ids);
+ filtertid = true;
+ }
+ void addthreads(const char *ids) {
+ split_and_insert(&threads, ids);
+ filterthread = true;
+ }
+ void addprograms(const char *ids) {
+ split_and_insert(&programs, ids);
+ filterprogram = true;
+ }
+ void addevents(const char *ids) {
+ split_and_insert(&events, ids);
+ filterevent = true;
+ }
+ void addcpus(const char *ids) {
+ if (cpuset_parselist(ids, &cpus) == CPUSET_PARSE_OK)
+ filtercpu = true;
+ else
+ fprintf(stderr, "Cannot parse cpulist");
+ }
+};
+
+/*
+ * These macros help provide a uniform filter facility across all passes.
+ * Filter options that are specific to a special feature, e.g., IBS, should
+ * only be accessible through long options, and we reserve option number 0-9
+ * for tool specific arguments.
+ */
+#define PMCFILTER_PRINTOPTS() \
+ do { \
+ printf("Filter Options:\n"); \
+ printf("\t-t,--lwps Filter by thread id\n"); \
+ printf("\t-p,--pids Filter by process id\n"); \
+ printf("\t-T,--threads Filter by thread name\n"); \
+ printf("\t-P,--processes Filter by process name\n"); \
+ printf("\t-e,--event Filter by event\n"); \
+ printf("\t-c,--cpus Filter by CPU id\n"); \
+ printf("\t-k,--kernel Show only kernel events\n"); \
+ printf("\t-u,--user Show only user events\n"); \
+ printf("\nIBS Fetch Options:\n"); \
+ printf("\t--ldlat Filter by load latency\n"); \
+ printf("\nIBS Op Options:\n"); \
+ printf("\t--ldlat Filter by load latency\n"); \
+ printf("\t--oplat Filter by operation latency\n"); \
+ printf("\t--mmio Show only mmio events\n"); \
+ } while (0)
+#define PMCFILTER_LOPTS \
+ { "lwps", required_argument, NULL, 't' }, \
+ { "pids", required_argument, NULL, 'p' }, \
+ { "threads", required_argument, NULL, 'T' }, \
+ { "processes", required_argument, NULL, 'P' }, \
+ { "events", required_argument, NULL, 'e' }, \
+ { "cpus", required_argument, NULL, 'c' }, \
+ { "kernel", no_argument, NULL, 'k' }, \
+ { "user", no_argument, NULL, 'u' }, \
+ { "ldlat", required_argument, NULL, 10 }, \
+ { "oplat", required_argument, NULL, 11 }, \
+ { "mmio", no_argument, NULL, 12 }
+#define PMCFILTER_SOPTS "t:p:T:P:e:c:ku"
+#define PMCFILTER_CASE(_filt) \
+ case 't': \
+ _filt.addtids(optarg); \
+ break; \
+ case 'p': \
+ _filt.addpids(optarg); \
+ break; \
+ case 'T': \
+ _filt.addthreads(optarg); \
+ break; \
+ case 'P': \
+ _filt.addprograms(optarg); \
+ break; \
+ case 'e': \
+ _filt.addevents(optarg); \
+ break; \
+ case 'c': \
+ _filt.addcpus(optarg); \
+ break; \
+ case 'k': \
+ _filt.kernelonly = true; \
+ break; \
+ case 'u': \
+ _filt.useronly = true; \
+ break; \
+ case 10: \
+ _filt.ibs_ldlat = atoi(optarg); \
+ break; \
+ case 11: \
+ _filt.ibs_oplat = atoi(optarg); \
+ break; \
+ case 12: \
+ _filt.ibs_mmio = true; \
+ break;
+
+struct ibsfetchinfo
+{
+ int len;
+ uint64_t ctl;
+ uint64_t extctl;
+ uint64_t linaddr;
+ uint64_t physaddr;
+};
+
+struct ibsopinfo
+{
+ int len;
+ uint64_t ctl;
+ uint64_t rip;
+ uint64_t data;
+ uint64_t data2;
+ uint64_t data3;
+ uint64_t linaddr;
+ uint64_t physaddr;
+ uint64_t tgtrip;
+ uint64_t data4;
+};
+
+class pmcview
+{
+protected:
+ pmcview();
+ virtual ~pmcview();
+public:
+ // Filter
+ void setfilter(const pmcfilter &pmcfilter);
+ // View Hooks
+ virtual void proccreate(__unused pid_t pid) { };
+ virtual void procexec(__unused pid_t pid) { };
+ virtual void procexit(__unused pid_t pid) { };
+ virtual void callchain(struct pmclog_ev_callchain &p,
+ __unused ibsfetchinfo &f, uintfptr_t *cc, int len) {
+ callchain(p, cc, len);
+ }
+ virtual void callchain(struct pmclog_ev_callchain &p,
+ __unused ibsopinfo &o, uintfptr_t *cc, int len) {
+ callchain(p, cc, len);
+ }
+ virtual void callchain(struct pmclog_ev_callchain &p,
+ __unused uintfptr_t *cc, __unused int len) {
+ callchain(p);
+ }
+ virtual void callchain(__unused struct pmclog_ev_callchain &p) { }
+ // Console/File output
+ virtual void print() { };
+ virtual int process_sysinfo(int logfd, const pmchdr_infohdr &infohdr);
+ virtual int process_pmcinfo(int logfd, const pmchdr_infohdr &infohdr);
+ virtual int process_cpuidinfo(int logfd, const pmchdr_infohdr &infohdr);
+ // Process log
+ virtual void process(int logfd);
+ virtual void process(struct pmclog_ev &p);
+ virtual void process(struct pmclog_parse_state *ps);
+ // Generic events
+ virtual void process(struct pmclog_ev_initialize &p);
+ virtual void process(struct pmclog_ev_closelog &p);
+ virtual void process(struct pmclog_ev_pmcallocate &p);
+ virtual void process(struct pmclog_ev_pmcallocatedyn &p);
+ // Process events
+ virtual void process(struct pmclog_ev_proccreate &p);
+ virtual void process(struct pmclog_ev_procexit &p);
+ virtual void process(struct pmclog_ev_procexec &p);
+ virtual void process(struct pmclog_ev_procfork &p);
+ virtual void process(struct pmclog_ev_sysexit &p);
+ // Thread events
+ virtual void process(struct pmclog_ev_threadcreate &p);
+ virtual void process(struct pmclog_ev_threadexit &p);
+ // Map events
+ virtual void process(struct pmclog_ev_map_in &p);
+ virtual void process(struct pmclog_ev_map_out &p);
+ // Callchain events
+ virtual void process(struct pmclog_ev_callchain &p);
+protected:
+ // Helper functions for views
+ uint32_t pmcidtoeventid(uint32_t pmcid);
+ std::string pidtoname(pid_t pid);
+ syminfo addrtosymbol(pid_t pid, uint64_t addr);
+ void printvm(pid_t pid);
+ // Fields available to views
+ uint64_t tscfreq;
+ std::unordered_map<uint32_t, uint32_t> pmcid;
+ std::unordered_map<uint32_t, pmcinfo> pmcinfo;
+ std::unordered_map<pid_t, procinfo> procs;
+ std::unordered_map<pid_t, pid_t> tidtopid;
+ std::unordered_map<std::string, image> images;
+ std::string sysroot;
+ pmcfilter filter;
+ // Extended info
+ int arch;
+ std::string cpumodel;
+ std::string osrelease;
+ std::string buildid;
+ std::vector<pmcinfox> extpmcinfo;
+ std::map<uint32_t, cpuidleaf> cpuid; // x86 Only
+private:
+ image loadimage(const std::string &path);
+ void mapimage(pid_t pid, const image &im, uint64_t linkaddr);
+ void loadsymboltable(image *im, Elf *e, Elf_Scn *scn, GElf_Shdr *sh);
+ void loadsymbols(image *im);
+};
+
+#endif /* __VIEW_HH__ */
diff --git a/usr.sbin/pmcstat/pmcstat.8 b/usr.sbin/pmcstat/pmcstat.8
index edb9ff106092..c78a13f910f9 100644
--- a/usr.sbin/pmcstat/pmcstat.8
+++ b/usr.sbin/pmcstat/pmcstat.8
@@ -23,7 +23,7 @@
.\" out of the use of this software, even if advised of the possibility of
.\" such damage.
.\"
-.Dd April 19, 2025
+.Dd April 15, 2026
.Dt PMCSTAT 8
.Os
.Sh NAME
@@ -220,6 +220,30 @@ specified in
.It Fl R Ar logfilename
Perform offline analysis using sampling data in file
.Ar logfilename .
+Each decoded record is printed as a single line with the following fields:
+a record type (e.g.,
+.Dq callchain ,
+.Dq initlog ) ,
+type-specific data, and a trailing 20-digit raw TSC value recording the
+CPU cycle at which the event occurred.
+The
+.Dq initlog
+record additionally prints
+.Dq Li tsc_freq=<hz> ,
+the TSC tick rate in Hz measured by the kernel at boot.
+To convert a TSC delta to nanoseconds:
+.Pp
+.Dl elapsed_ns = (tsc_end - tsc_start) * 1e9 / tsc_freq
+.Pp
+TSC-based timestamps and
+.Dq Li tsc_freq
+are only meaningful on x86 architectures
+.Pq amd64 and i386 .
+On all other architectures
+.Pq including arm64 and powerpc
+the
+.Dq Li tsc_freq
+field will be zero.
.It Fl S Ar event-spec
Allocate a system mode sampling PMC measuring hardware events
specified in
diff --git a/usr.sbin/pmcstat/pmcstat.c b/usr.sbin/pmcstat/pmcstat.c
index 1809dae7bc4c..a78062a5b6e0 100644
--- a/usr.sbin/pmcstat/pmcstat.c
+++ b/usr.sbin/pmcstat/pmcstat.c
@@ -739,8 +739,11 @@ main(int argc, char **argv)
STAILQ_INSERT_TAIL(&args.pa_events, ev, ev_next);
- if ((caps & PMC_CAP_SYSWIDE) == PMC_CAP_SYSWIDE)
- break;
+ if ((caps & PMC_CAP_SYSWIDE) == PMC_CAP_SYSWIDE) {
+ CPU_ZERO(&cpumask);
+ CPU_SET(0, &cpumask);
+ args.pa_flags |= FLAGS_HAS_CPUMASK;
+ }
if ((caps & PMC_CAP_DOMWIDE) == PMC_CAP_DOMWIDE) {
CPU_ZERO(&cpumask);
/*
diff --git a/usr.sbin/pmcstat/pmcstat.h b/usr.sbin/pmcstat/pmcstat.h
index 67571ce4b2ee..6131d7a50404 100644
--- a/usr.sbin/pmcstat/pmcstat.h
+++ b/usr.sbin/pmcstat/pmcstat.h
@@ -50,8 +50,9 @@
#define PMCSTAT_PRINT_ENTRY(T,...) do { \
(void) fprintf(args.pa_printfile, "%-9s", T); \
- (void) fprintf(args.pa_printfile, " " __VA_ARGS__); \
- (void) fprintf(args.pa_printfile, "\n"); \
+ (void) fprintf(args.pa_printfile, " " __VA_ARGS__); \
+ (void) fprintf(args.pa_printfile, " %20ju\n", \
+ (uintmax_t)_pmcstat_current_tsc); \
} while (0)
#define PMCSTAT_PL_NONE 0
diff --git a/usr.sbin/pmcstat/pmcstat_log.c b/usr.sbin/pmcstat/pmcstat_log.c
index db9cbfabd9da..77a6ea799594 100644
--- a/usr.sbin/pmcstat/pmcstat_log.c
+++ b/usr.sbin/pmcstat/pmcstat_log.c
@@ -194,6 +194,7 @@ static struct pmc_plugins plugins[] = {
};
static int pmcstat_mergepmc;
+static uint64_t _pmcstat_current_tsc; /* TSC for PMCSTAT_PRINT_ENTRY */
int pmcstat_pmcinfilter = 0; /* PMC filter for top mode. */
float pmcstat_threshold = 0.5; /* Cost filter for top mode. */
@@ -370,10 +371,10 @@ pmcstat_pmcindex_to_pmcr(int pmcin)
#if defined(__amd64__) || defined(__i386__)
static void
-pmcstat_print_ibs_fetch(struct pmclog_ev_callchain *cc, int offset)
+pmcstat_print_ibs_fetch(struct pmclog_ev_callchain *cc, int offset, int len64)
{
uint64_t *ibsbuf = (uint64_t *)&cc->pl_pc[offset];
- uint64_t ctl;
+ uint64_t ctl, ctl2;
ctl = ibsbuf[PMC_MPIDX_FETCH_CTL];
PMCSTAT_PRINT_ENTRY("ibs-fetch", "%s%s%s%s",
@@ -389,15 +390,28 @@ pmcstat_print_ibs_fetch(struct pmclog_ev_callchain *cc, int offset)
PMCSTAT_PRINT_ENTRY("IBS", "Physical Address %" PRIx64,
ibsbuf[PMC_MPIDX_FETCH_PHYSADDR]);
}
+ if (len64 > PMC_MPIDX_FETCH_CTL2) {
+ ctl2 = ibsbuf[PMC_MPIDX_FETCH_CTL2];
+ if ((ctl2 & IBS_FETCH_CTL2_EXCLADDR63EQ1) != 0)
+ PMCSTAT_PRINT_ENTRY("ibs-fetch", "addr63=0");
+ if ((ctl2 & IBS_FETCH_CTL2_EXCLADDR63EQ0) != 0)
+ PMCSTAT_PRINT_ENTRY("ibs-fetch", "addr63=1");
+ if ((ctl2 & IBS_FETCH_CTL2_LATFILTERMASK) != 0) {
+ PMCSTAT_PRINT_ENTRY("ibs-fetch",
+ "fetchlat>=%" PRIu64,
+ (uint64_t)IBS_FETCH_CTL2_CTL_TO_LAT(ctl2));
+ }
+ }
}
static void
-pmcstat_print_ibs_op(struct pmclog_ev_callchain *cc, int offset)
+pmcstat_print_ibs_op(struct pmclog_ev_callchain *cc, int offset, int len64)
{
uint64_t *ibsbuf = (uint64_t *)&cc->pl_pc[offset];
- uint64_t data, data3;
+ uint64_t data, data2, data3, ctl2;
data = ibsbuf[PMC_MPIDX_OP_DATA];
+ data2 = ibsbuf[PMC_MPIDX_OP_DATA2];
data3 = ibsbuf[PMC_MPIDX_OP_DATA3];
if ((data & IBS_OP_DATA_RIPINVALID) == 0) {
@@ -415,6 +429,11 @@ pmcstat_print_ibs_op(struct pmclog_ev_callchain *cc, int offset)
(data3 & IBS_OP_DATA3_LOCKEDOP) ? "lock " : "",
(data3 & IBS_OP_DATA3_DCL1TLBMISS) ? "l1tlbmiss " : "",
(data3 & IBS_OP_DATA3_DCMISS) ? "dcmiss " : "");
+ if ((data2 & (IBS_OP_DATA2_STRMST | IBS_OP_DATA2_RMTSOCKET)) != 0) {
+ PMCSTAT_PRINT_ENTRY("ibs-op", "%s%s",
+ (data2 & IBS_OP_DATA2_STRMST) ? "streamstore " : "",
+ (data2 & IBS_OP_DATA2_RMTSOCKET) ? "remotesocket" : "");
+ }
PMCSTAT_PRINT_ENTRY("ibs-op", "Latency %" PRIu64,
IBS_OP_DATA3_TO_DCLAT(data3));
if ((data3 & IBS_OP_DATA3_DCLINADDRVALID) != 0) {
@@ -425,6 +444,15 @@ pmcstat_print_ibs_op(struct pmclog_ev_callchain *cc, int offset)
PMCSTAT_PRINT_ENTRY("ibs-op", "Physical Address %" PRIx64,
ibsbuf[PMC_MPIDX_OP_DC_PHYSADDR]);
}
+ if (len64 > PMC_MPIDX_OP_CTL2) {
+ ctl2 = ibsbuf[PMC_MPIDX_OP_CTL2];
+ if ((ctl2 & IBS_OP_CTL2_EXCLRIP63EQ1) != 0)
+ PMCSTAT_PRINT_ENTRY("ibs-op", "addr63=0");
+ if ((ctl2 & IBS_OP_CTL2_EXCLRIP63EQ0) != 0)
+ PMCSTAT_PRINT_ENTRY("ibs-op", "addr63=1");
+ if ((ctl2 & IBS_OP_CTL2_STRMSTFILTER) != 0)
+ PMCSTAT_PRINT_ENTRY("ibs-op", "streamstore");
+ }
}
#endif
@@ -445,9 +473,11 @@ pmcstat_print_multipart(struct pmclog_ev_callchain *cc)
return (offset);
#if defined(__amd64__) || defined(__i386__)
} else if (type == PMC_CC_MULTIPART_IBS_FETCH) {
- pmcstat_print_ibs_fetch(cc, offset);
+ pmcstat_print_ibs_fetch(cc, offset,
+ len / (sizeof(uint64_t) / sizeof(uintptr_t)));
} else if (type == PMC_CC_MULTIPART_IBS_OP) {
- pmcstat_print_ibs_op(cc, offset);
+ pmcstat_print_ibs_op(cc, offset,
+ len / (sizeof(uint64_t) / sizeof(uintptr_t)));
#endif
} else {
PMCSTAT_PRINT_ENTRY("unsupported multipart type!");
@@ -471,6 +501,7 @@ pmcstat_print_log(void)
while (pmclog_read(args.pa_logparser, &ev) == 0) {
assert(ev.pl_state == PMCLOG_OK);
+ _pmcstat_current_tsc = ev.pl_tsc;
switch (ev.pl_type) {
case PMCLOG_TYPE_CALLCHAIN:
PMCSTAT_PRINT_ENTRY("callchain",
@@ -496,9 +527,11 @@ pmcstat_print_log(void)
PMCSTAT_PRINT_ENTRY("drop",);
break;
case PMCLOG_TYPE_INITIALIZE:
- PMCSTAT_PRINT_ENTRY("initlog","0x%x \"%s\"",
+ PMCSTAT_PRINT_ENTRY("initlog",
+ "0x%x \"%s\" tsc_freq=%" PRIu64,
ev.pl_u.pl_i.pl_version,
- pmc_name_of_cputype(ev.pl_u.pl_i.pl_arch));
+ pmc_name_of_cputype(ev.pl_u.pl_i.pl_arch),
+ ev.pl_u.pl_i.pl_tsc_freq);
if ((ev.pl_u.pl_i.pl_version & 0xFF000000) !=
PMC_VERSION_MAJOR << 24)
warnx(
diff --git a/usr.sbin/ppp/chap.c b/usr.sbin/ppp/chap.c
index 9cefa6db71ce..1129aa7fb2a3 100644
--- a/usr.sbin/ppp/chap.c
+++ b/usr.sbin/ppp/chap.c
@@ -238,7 +238,7 @@ chap_BuildAnswer(char *name, char *key, u_char id, char *challenge
MD5Init(&MD5context);
MD5Update(&MD5context, &id, 1);
MD5Update(&MD5context, key, klen);
- MD5Update(&MD5context, challenge + 1, *challenge);
+ MD5Update(&MD5context, challenge + 1, (u_char)*challenge);
MD5Final(digest, &MD5context);
memcpy(digest + 16, name, nlen);
@@ -913,7 +913,7 @@ chap_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
if (myans == NULL)
key = NULL;
else {
- if (!chap_Cmp(myans + 1, *myans, ans + 1, alen
+ if (!chap_Cmp(myans + 1, (u_char)*myans, ans + 1, alen
#ifndef NODES
, p->link.lcp.want_authtype, lanman
#endif
diff --git a/usr.sbin/ppp/chap.h b/usr.sbin/ppp/chap.h
index f697167ab165..993ed3f060f7 100644
--- a/usr.sbin/ppp/chap.h
+++ b/usr.sbin/ppp/chap.h
@@ -48,8 +48,8 @@ struct chap {
} child;
struct authinfo auth;
struct {
- u_char local[CHAPCHALLENGELEN + AUTHLEN]; /* I invented this one */
- u_char peer[CHAPCHALLENGELEN + AUTHLEN]; /* Peer gave us this one */
+ u_char local[CHAPCHALLENGELEN + 1 + AUTHLEN]; /* I invented this one */
+ u_char peer[CHAPCHALLENGELEN + 1 + AUTHLEN]; /* Peer gave us this one */
} challenge;
#ifndef NODES
unsigned NTRespSent : 1; /* Our last response */
diff --git a/usr.sbin/ppp/defs.h b/usr.sbin/ppp/defs.h
index c76cbd8ad9cb..31f2577a6c23 100644
--- a/usr.sbin/ppp/defs.h
+++ b/usr.sbin/ppp/defs.h
@@ -58,7 +58,7 @@
#define DEVICE_LEN SCRIPT_LEN /* Size of individual devices */
#define AUTHLEN 100 /* Size of authname/authkey */
#define CHAPDIGESTLEN 100 /* Maximum chap digest */
-#define CHAPCHALLENGELEN 48 /* Maximum chap challenge */
+#define CHAPCHALLENGELEN 255 /* Maximum chap challenge */
#define CHAPAUTHRESPONSELEN 48 /* Maximum chap authresponse (chap81) */
#define MAXARGS 40 /* How many args per config line */
#define NCP_IDLE_TIMEOUT 180 /* Drop all links */
diff --git a/usr.sbin/ppp/fsm.c b/usr.sbin/ppp/fsm.c
index 5c011bef7f4f..060a9149a6af 100644
--- a/usr.sbin/ppp/fsm.c
+++ b/usr.sbin/ppp/fsm.c
@@ -1060,6 +1060,12 @@ fsm_Input(struct fsm *fp, struct mbuf *bp)
m_freem(bp);
return;
}
+ if (ntohs(lh.length) < sizeof(lh)) {
+ log_Printf(LogWARN, "%s: Header length %d is too small - dropped\n",
+ fp->link->name, (int)ntohs(lh.length));
+ m_freem(bp);
+ return;
+ }
if (lh.code < fp->min_code || lh.code > fp->max_code ||
lh.code > sizeof FsmCodes / sizeof *FsmCodes) {
diff --git a/usr.sbin/ppp/id.c b/usr.sbin/ppp/id.c
index c7d512380afa..35bd3f08c261 100644
--- a/usr.sbin/ppp/id.c
+++ b/usr.sbin/ppp/id.c
@@ -145,7 +145,10 @@ ID0open(const char *path, int flags, ...)
va_start(ap, flags);
ID0set0();
- ret = open(path, flags, va_arg(ap, int));
+ if (flags & O_CREAT)
+ ret = open(path, flags, va_arg(ap, int));
+ else
+ ret = open(path, flags);
log_Printf(LogID0, "%d = open(\"%s\", %d)\n", ret, path, flags);
ID0setuser();
va_end(ap);
diff --git a/usr.sbin/ppp/lcp.c b/usr.sbin/ppp/lcp.c
index 462f3c4281bb..7029610a1591 100644
--- a/usr.sbin/ppp/lcp.c
+++ b/usr.sbin/ppp/lcp.c
@@ -1195,6 +1195,12 @@ LcpDecodeConfig(struct fsm *fp, u_char *cp, u_char *end, int mode_type,
break;
case TY_ENDDISC:
+ if (opt->hdr.len < 3) {
+ log_Printf(LogLCP, "%s - too short\n", request);
+ fsm_rej(dec, opt);
+ lcp->my_reject |= (1 << opt->hdr.id);
+ break;
+ }
mp = &lcp->fsm.bundle->ncp.mp;
log_Printf(LogLCP, "%s %s\n", request,
mp_Enddisc(opt->data[0], opt->data + 1, opt->hdr.len - 3));
diff --git a/usr.sbin/ppp/mp.c b/usr.sbin/ppp/mp.c
index 84ddb6e053e3..f5f9dd9ddece 100644
--- a/usr.sbin/ppp/mp.c
+++ b/usr.sbin/ppp/mp.c
@@ -931,10 +931,10 @@ mp_Enddisc(u_char c, const char *address, size_t len)
case ENDDISC_MAGIC:
sprintf(result, "Magic: 0x");
header = strlen(result);
- if (len + header + 1 > sizeof result)
- len = sizeof result - header - 1;
+ if (2 * len + header + 1 > sizeof result)
+ len = (sizeof result - header - 1) / 2;
for (f = 0; f < len; f++)
- sprintf(result + header + 2 * f, "%02x", address[f]);
+ sprintf(result + header + 2 * f, "%02x", (unsigned char)address[f]);
break;
case ENDDISC_PSN:
@@ -944,10 +944,10 @@ mp_Enddisc(u_char c, const char *address, size_t len)
default:
sprintf(result, "%d: ", (int)c);
header = strlen(result);
- if (len + header + 1 > sizeof result)
- len = sizeof result - header - 1;
+ if (2 * len + header + 1 > sizeof result)
+ len = (sizeof result - header - 1) / 2;
for (f = 0; f < len; f++)
- sprintf(result + header + 2 * f, "%02x", address[f]);
+ sprintf(result + header + 2 * f, "%02x", (unsigned char)address[f]);
break;
}
return result;
diff --git a/usr.sbin/prometheus_sysctl_exporter/prometheus_sysctl_exporter.8 b/usr.sbin/prometheus_sysctl_exporter/prometheus_sysctl_exporter.8
index da09f43c1cf9..57e29cdd5b86 100644
--- a/usr.sbin/prometheus_sysctl_exporter/prometheus_sysctl_exporter.8
+++ b/usr.sbin/prometheus_sysctl_exporter/prometheus_sysctl_exporter.8
@@ -20,7 +20,7 @@
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
-.Dd October 7, 2021
+.Dd August 6, 2026
.Dt PROMETHEUS_SYSCTL_EXPORTER 8
.Os
.Sh NAME
@@ -95,6 +95,11 @@ is to be a regular expression as described in
The provided regular expression is tested against the Prometheus
metric name.
.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 on success, and >0 if an error is encountered while processing
+metric labels.
.Sh SEE ALSO
.Xr cron 8 ,
.Xr inetd 8 ,
diff --git a/usr.sbin/prometheus_sysctl_exporter/prometheus_sysctl_exporter.c b/usr.sbin/prometheus_sysctl_exporter/prometheus_sysctl_exporter.c
index e3182467beab..b384df8da22e 100644
--- a/usr.sbin/prometheus_sysctl_exporter/prometheus_sysctl_exporter.c
+++ b/usr.sbin/prometheus_sysctl_exporter/prometheus_sysctl_exporter.c
@@ -371,8 +371,14 @@ oid_get_name(const struct oid *o, struct oidname *on)
on->oid = *o;
}
-/* Populates the name and labels of an OID to a buffer. */
-static void
+/*
+ * Populates the name and labels of an OID to a buffer. If an invalid label is
+ * encountered, we'll skip the metric entirely to avoid outputting ambiguous
+ * metrics and emit the relevant details to stderr for correction.
+ *
+ * Returns true if the metric is valid, false otherwise.
+ */
+static bool
oid_get_metric(const struct oidname *on, const struct oidformat *of,
char *metric, size_t mlen)
{
@@ -409,10 +415,15 @@ oid_get_metric(const struct oidname *on, const struct oidformat *of,
separator = '{';
for (i = 0; i < on->oid.len; ++i) {
if (*label != '\0') {
- assert(label[strspn(label,
+ if (label[strspn(label,
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "0123456789_")] == '\0');
+ "0123456789_")] != '\0') {
+ warnx("bad label for metric '%s': %s",
+ metric, label);
+ return (false);
+ }
+
snprintf(buf, sizeof(buf), "%c%s=\"", separator, label);
strlcat(metric, buf, mlen);
while (*name != '\0') {
@@ -430,6 +441,8 @@ oid_get_metric(const struct oidname *on, const struct oidformat *of,
}
if (separator != '{')
strlcat(metric, "}", mlen);
+
+ return (true);
}
/* Returns whether the OID name has any labels associated to it. */
@@ -486,7 +499,14 @@ oiddescription_print(const struct oiddescription *od, FILE *fp)
fprintf(fp, "%s", od->description);
}
-static void
+/*
+ * Print the given oid, subject to include/exclude rules. Returns true if the
+ * oid was skipped or printed, false if an error was encountered.
+ *
+ * For the purposes of the exporter, we don't consider it an error to be unable
+ * to fetch format/value information.
+ */
+static bool
oid_print(const struct oid *o, struct oidname *on, bool print_description,
bool exclude, bool include, FILE *fp)
{
@@ -497,16 +517,17 @@ oid_print(const struct oid *o, struct oidname *on, bool print_description,
bool has_desc;
if (!oid_get_format(o, &of) || !oid_get_value(o, &of, &ov))
- return;
+ return (true);
oid_get_name(o, on);
- oid_get_metric(on, &of, metric, sizeof(metric));
+ if (!oid_get_metric(on, &of, metric, sizeof(metric)))
+ return (false);
if (exclude && regexec(&exc_regex, metric, 0, NULL, 0) == 0)
- return;
+ return (true);
if (include && regexec(&inc_regex, metric, 0, NULL, 0) != 0)
- return;
+ return (true);
has_desc = oid_get_description(o, &od);
/*
@@ -514,7 +535,7 @@ oid_print(const struct oid *o, struct oidname *on, bool print_description,
* redundant ZFS sysctls whose names alias with the non-legacy versions.
*/
if (has_desc && strnstr(od.description, "(LEGACY)", BUFSIZ) != NULL)
- return;
+ return (true);
/*
* Print the line with the description. Prometheus expects a
* single unique description for every metric, which cannot be
@@ -534,6 +555,7 @@ oid_print(const struct oid *o, struct oidname *on, bool print_description,
fputc(' ', fp);
oidvalue_print(&ov, fp);
fputc('\n', fp);
+ return (true);
}
/* Gzip compresses a buffer of memory. */
@@ -573,7 +595,7 @@ main(int argc, char *argv[])
char *http_buf;
FILE *fp;
size_t http_buflen;
- int ch, error;
+ int ch, error, failed = 0;
bool exclude, include, gzip_mode, http_mode, print_descriptions;
char errbuf[BUFSIZ];
@@ -631,7 +653,9 @@ main(int argc, char *argv[])
/* Print all OIDs. */
oid_get_root(&o);
do {
- oid_print(&o, &on, print_descriptions, exclude, include, fp);
+ if (!oid_print(&o, &on, print_descriptions, exclude,
+ include, fp))
+ failed++;
} while (oid_get_next(&o, &o));
} else {
int i;
@@ -651,7 +675,9 @@ main(int argc, char *argv[])
}
o = root;
do {
- oid_print(&o, &on, print_descriptions, exclude, include, fp);
+ if (!oid_print(&o, &on, print_descriptions,
+ exclude, include, fp))
+ failed++;
} while (oid_get_next(&o, &o) &&
oid_is_beneath(&o, &root));
}
@@ -702,5 +728,5 @@ main(int argc, char *argv[])
}
}
}
- return (0);
+ return (failed != 0 ? 1 : 0);
}
diff --git a/usr.sbin/pw/pw.8 b/usr.sbin/pw/pw.8
index 91d746e90c02..1498f76fd9c5 100644
--- a/usr.sbin/pw/pw.8
+++ b/usr.sbin/pw/pw.8
@@ -22,7 +22,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd February 11, 2026
+.Dd April 19, 2026
.Dt PW 8
.Os
.Sh NAME
@@ -1104,6 +1104,7 @@ No base home directory configured.
.Xr login.conf 5 ,
.Xr passwd 5 ,
.Xr pw.conf 5 ,
+.Xr groups 7 ,
.Xr pwd_mkdb 8 ,
.Xr vipw 8 ,
.Xr zfs 8
diff --git a/usr.sbin/pw/pw.h b/usr.sbin/pw/pw.h
index ceb843d79503..6be667f166fb 100644
--- a/usr.sbin/pw/pw.h
+++ b/usr.sbin/pw/pw.h
@@ -81,9 +81,6 @@ int pw_group_mod(int argc, char **argv, char *name);
int pw_group_next(int argc, char **argv, char *name);
int pw_group_show(int argc, char **argv, char *name);
int pw_user_add(int argc, char **argv, char *name);
-int pw_user_add(int argc, char **argv, char *name);
-int pw_user_add(int argc, char **argv, char *name);
-int pw_user_add(int argc, char **argv, char *name);
int pw_user_del(int argc, char **argv, char *name);
int pw_user_lock(int argc, char **argv, char *name);
int pw_user_mod(int argc, char **argv, char *name);
diff --git a/usr.sbin/pw/pw_conf.c b/usr.sbin/pw/pw_conf.c
index e9042b15b321..7e8fdd375395 100644
--- a/usr.sbin/pw/pw_conf.c
+++ b/usr.sbin/pw/pw_conf.c
@@ -156,22 +156,22 @@ static char const *kwds[] =
};
static char *
-unquote(char const * str)
+unquote(char * str)
{
if (str && (*str == '"' || *str == '\'')) {
char *p = strchr(str + 1, *str);
if (p != NULL)
*p = '\0';
- return (char *) (*++str ? str : NULL);
+ return (*++str ? str : NULL);
}
- return (char *) str;
+ return (str);
}
int
boolean_val(char const * str, int dflt)
{
- if ((str = unquote(str)) != NULL) {
+ if ((str = unquote((char *)str)) != NULL) {
int i;
for (i = 0; booltrue[i]; i++)
@@ -187,7 +187,7 @@ boolean_val(char const * str, int dflt)
int
passwd_val(char const * str, int dflt)
{
- if ((str = unquote(str)) != NULL) {
+ if ((str = unquote((char *)str)) != NULL) {
int i;
for (i = 0; booltrue[i]; i++)
@@ -228,7 +228,7 @@ newstr(char const * p)
{
char *q;
- if ((p = unquote(p)) == NULL)
+ if ((p = unquote((char *)p)) == NULL)
return (NULL);
if ((q = strdup(p)) == NULL)
@@ -298,10 +298,16 @@ read_userconfig(char const * file)
? "/home" : newstr(q);
break;
case _UC_HOMEMODE:
- modeset = setmode(q);
- config.homemode = (q == NULL || !boolean_val(q, 1))
- ? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE);
- free(modeset);
+ if (q == NULL || !boolean_val(q, 1)) {
+ config.homemode = _DEF_DIRMODE;
+ } else {
+ modeset = setmode(q);
+ if (modeset == NULL)
+ errx(1, "Invalid mode: '%s'", q);
+ config.homemode = getmode(modeset,
+ _DEF_DIRMODE);
+ free(modeset);
+ }
break;
case _UC_SHELLPATH:
config.shelldir = (q == NULL || !boolean_val(q, 1))
diff --git a/usr.sbin/pw/pw_group.c b/usr.sbin/pw/pw_group.c
index 1941c03aa2c5..2840e972af59 100644
--- a/usr.sbin/pw/pw_group.c
+++ b/usr.sbin/pw/pw_group.c
@@ -367,7 +367,7 @@ pw_group_del(int argc, char **argv, char *arg1)
{
struct userconf *cnf = NULL;
struct group *grp = NULL;
- char *name;
+ char *name = NULL;
const char *cfg = NULL;
intmax_t id = -1;
int ch, rc;
diff --git a/usr.sbin/pw/pw_user.c b/usr.sbin/pw/pw_user.c
index 413eac4882cc..abb8a09ce468 100644
--- a/usr.sbin/pw/pw_user.c
+++ b/usr.sbin/pw/pw_user.c
@@ -123,7 +123,7 @@ mkdir_home_parents(int dfd, const char *dir)
dirs);
if (fchownat(dfd, dirs, 0, 0, 0) != 0)
warn("chown(%s)", dirs);
- metalog_emit(dir,
+ metalog_emit(dirs,
(_DEF_DIRMODE | S_IFDIR) & ~pumask, 0, 0,
0);
}
@@ -464,7 +464,7 @@ shell_path(char const * path, char *shells[], char *sh)
return shellpath;
}
}
- if (sh == NULL)
+ if (sh != NULL)
errx(EX_OSFILE, "can't find shell `%s' in shell paths", sh);
errx(EX_CONFIG, "no default shell available or defined");
return NULL;
diff --git a/usr.sbin/pw/tests/pw_config_test.sh b/usr.sbin/pw/tests/pw_config_test.sh
index e9209190dc5e..4087653fd4bb 100755
--- a/usr.sbin/pw/tests/pw_config_test.sh
+++ b/usr.sbin/pw/tests/pw_config_test.sh
@@ -19,7 +19,19 @@ modify_config_uid_gid_boundaries_body() {
cat ${HOME}/foo.conf
}
+atf_test_case homemode_boolean_no_crash
+homemode_boolean_no_crash_head() {
+ atf_set "descr" "Verify that homemode = yes in config gives a clean error"
+}
+homemode_boolean_no_crash_body() {
+ echo 'homemode = yes' > ${HOME}/bad.conf
+ populate_etc_skel
+ atf_check -s exit:1 -e inline:"pw: Invalid mode: 'yes'\n" \
+ ${PW} useradd -D -C ${HOME}/bad.conf
+}
+
atf_init_test_cases() {
atf_add_test_case generate_config
atf_add_test_case modify_config_uid_gid_boundaries
+ atf_add_test_case homemode_boolean_no_crash
}
diff --git a/usr.sbin/pw/tests/pw_groupdel_test.sh b/usr.sbin/pw/tests/pw_groupdel_test.sh
index fa51b3814205..0fb5651944f3 100755
--- a/usr.sbin/pw/tests/pw_groupdel_test.sh
+++ b/usr.sbin/pw/tests/pw_groupdel_test.sh
@@ -18,6 +18,20 @@ group_do_not_delete_wheel_if_group_unknown_body() {
}
+atf_test_case group_delete_by_gid cleanup
+group_delete_by_gid_head() {
+ atf_set "descr" "Test deleting a group by gid without providing a name"
+}
+group_delete_by_gid_body() {
+ populate_etc_skel
+ ${PW} groupadd testgroup -g 1000 || atf_fail "Creating test group"
+ atf_check -s exit:0 -o inline:"testgroup:*:1000:\n" \
+ -x ${PW} groupshow 1000
+ ${PW} groupdel -g 1000 || atf_fail "Deleting group by gid"
+}
+
+
atf_init_test_cases() {
- atf_add_test_case group_do_not_delete_wheel_if_group_unknown
+ atf_add_test_case group_do_not_delete_wheel_if_group_unknown
+ atf_add_test_case group_delete_by_gid
}
diff --git a/usr.sbin/pw/tests/pw_useradd_test.sh b/usr.sbin/pw/tests/pw_useradd_test.sh
index 75e96a64ba8e..4b0778759203 100755
--- a/usr.sbin/pw/tests/pw_useradd_test.sh
+++ b/usr.sbin/pw/tests/pw_useradd_test.sh
@@ -402,7 +402,7 @@ user_add_bad_shell_body() {
populate_etc_skel
atf_check -s exit:0 ${PW} useradd foo -s sh
- atf_check -s exit:78 -e ignore ${PW} useradd bar -s badshell
+ atf_check -s exit:72 -e inline:"pw: can't find shell \`badshell' in shell paths\n" ${PW} useradd bar -s badshell
}
atf_test_case user_add_already_exists
diff --git a/usr.sbin/rpc.tlsservd/rpc.tlsservd.c b/usr.sbin/rpc.tlsservd/rpc.tlsservd.c
index fb0501b2db4c..f07385a2baa7 100644
--- a/usr.sbin/rpc.tlsservd/rpc.tlsservd.c
+++ b/usr.sbin/rpc.tlsservd/rpc.tlsservd.c
@@ -168,12 +168,7 @@ main(int argc, char **argv)
rpctls_verbose = false;
ncpu = (u_int)sysconf(_SC_NPROCESSORS_ONLN);
-#ifdef notnow
rpctls_maxthreads = ncpu > 1 ? ncpu / 2 : 1;
-#else
- /* XXX For now, until fixed properly!! */
- rpctls_maxthreads = 1;
-#endif
while ((ch = getopt_long(argc, argv, "2C:D:dhl:N:n:mp:r:uvWw", longopts,
NULL)) != -1) {
@@ -204,8 +199,6 @@ main(int argc, char **argv)
if (rpctls_maxthreads < 1 || rpctls_maxthreads > ncpu)
errx(1, "maximum threads must be between 1 and "
"number of CPUs (%d)", ncpu);
- /* XXX For now, until fixed properly!! */
- rpctls_maxthreads = 1;
break;
case 'n':
hostname[0] = '@';
diff --git a/usr.sbin/rtadvd/Makefile b/usr.sbin/rtadvd/Makefile
index 583f9db88ceb..4ed2d7182a6b 100644
--- a/usr.sbin/rtadvd/Makefile
+++ b/usr.sbin/rtadvd/Makefile
@@ -20,11 +20,6 @@ MAN= rtadvd.conf.5 rtadvd.8
SRCS= rtadvd.c rrenum.c advcap.c if.c config.c timer.c timer_subr.c \
control.c control_server.c
-.if ${MK_EXPERIMENTAL} != "no"
-CFLAGS+= -DEXPERIMENTAL
-CFLAGS+= -DDRAFT_IETF_6MAN_IPV6ONLY_FLAG
-.endif
-
LIBADD= util
WARNS?= 1
diff --git a/usr.sbin/rtadvd/config.c b/usr.sbin/rtadvd/config.c
index 83b2efb68303..df4473841312 100644
--- a/usr.sbin/rtadvd/config.c
+++ b/usr.sbin/rtadvd/config.c
@@ -442,10 +442,6 @@ getconfig(struct ifinfo *ifi)
}
val |= ND_RA_FLAG_RTPREF_LOW;
}
-#ifdef DRAFT_IETF_6MAN_IPV6ONLY_FLAG
- if (strchr(flagstr, 'S'))
- val |= ND_RA_FLAG_IPV6_ONLY;
-#endif
} else
MAYHAVE(val, "raflags", 0);
@@ -461,9 +457,6 @@ getconfig(struct ifinfo *ifi)
__func__, rai->rai_rtpref, ifi->ifi_ifname);
goto getconfig_free_rai;
}
-#ifdef DRAFT_IETF_6MAN_IPV6ONLY_FLAG
- rai->rai_ipv6onlyflg = val & ND_RA_FLAG_IPV6_ONLY;
-#endif
MAYHAVE(val, "rltime", rai->rai_maxinterval * 3);
if ((uint16_t)val && ((uint16_t)val < rai->rai_maxinterval ||
@@ -619,7 +612,7 @@ getconfig_free_pfx:
get_prefix(rai);
MAYHAVE(val64, "mtu", 0);
- if (val < 0 || val64 > 0xffffffff) {
+ if (val64 < 0 || val64 > 0xffffffff) {
syslog(LOG_ERR,
"<%s> mtu (%" PRIu64 ") on %s out of range",
__func__, val64, ifi->ifi_ifname);
@@ -1028,12 +1021,15 @@ getconfig_free_prf64:
} else {
struct rdnss *rdn;
struct dnssl *dns;
+ struct rtinfo *rti;
rai_old->rai_lifetime = 0;
TAILQ_FOREACH(rdn, &rai_old->rai_rdnss, rd_next)
rdn->rd_ltime = 0;
TAILQ_FOREACH(dns, &rai_old->rai_dnssl, dn_next)
dns->dn_ltime = 0;
+ TAILQ_FOREACH(rti, &rai_old->rai_route, rti_next)
+ rti->rti_ltime = 0;
ifi->ifi_rainfo_trans = rai_old;
ifi->ifi_state = IFI_STATE_TRANSITIVE;
@@ -1111,6 +1107,7 @@ get_prefix(struct rainfo *rai)
ifi = rai->rai_ifinfo;
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ int64_t val64;
int plen;
if (strcmp(ifa->ifa_name, ifi->ifi_ifname) != 0)
@@ -1159,9 +1156,26 @@ get_prefix(struct rainfo *rai)
"<%s> add %s/%d to prefix list on %s",
__func__, ntopbuf, pfx->pfx_prefixlen, ifi->ifi_ifname);
+ MAYHAVE(val64, "vltime", DEF_ADVVALIDLIFETIME);
+ if (val64 < 0 || val64 > 0xffffffff) {
+ syslog(LOG_WARNING,
+ "<%s> vltime (%" PRIu64 ") for %s/%d on %s "
+ "is out of range, use default value instead.", __func__,
+ val64, ntopbuf, pfx->pfx_prefixlen, ifi->ifi_ifname);
+ pfx->pfx_validlifetime = DEF_ADVVALIDLIFETIME;
+ } else
+ pfx->pfx_validlifetime = val64;
+ MAYHAVE(val64, "pltime", DEF_ADVPREFERREDLIFETIME);
+ if (val64 < 0 || val64 > 0xffffffff) {
+ syslog(LOG_WARNING,
+ "<%s> pltime (%" PRIu64 ") for %s/%d on %s "
+ "is out of range, use default value instead.", __func__,
+ val64, ntopbuf, pfx->pfx_prefixlen, ifi->ifi_ifname);
+ pfx->pfx_preflifetime = DEF_ADVPREFERREDLIFETIME;
+ } else
+ pfx->pfx_preflifetime = val64;
+
/* set other fields with protocol defaults */
- pfx->pfx_validlifetime = DEF_ADVVALIDLIFETIME;
- pfx->pfx_preflifetime = DEF_ADVPREFERREDLIFETIME;
pfx->pfx_onlinkflg = 1;
pfx->pfx_autoconfflg = 1;
pfx->pfx_origin = PREFIX_FROM_KERNEL;
@@ -1485,10 +1499,6 @@ make_packet(struct rainfo *rai)
rai->rai_managedflg ? ND_RA_FLAG_MANAGED : 0;
ra->nd_ra_flags_reserved |=
rai->rai_otherflg ? ND_RA_FLAG_OTHER : 0;
-#ifdef DRAFT_IETF_6MAN_IPV6ONLY_FLAG
- ra->nd_ra_flags_reserved |=
- rai->rai_ipv6onlyflg ? ND_RA_FLAG_IPV6_ONLY : 0;
-#endif
ra->nd_ra_router_lifetime = htons(rai->rai_lifetime);
ra->nd_ra_reachable = htonl(rai->rai_reachabletime);
ra->nd_ra_retransmit = htonl(rai->rai_retranstimer);
diff --git a/usr.sbin/rtadvd/rtadvd.c b/usr.sbin/rtadvd/rtadvd.c
index 1eb8f12a7338..1525c992a65b 100644
--- a/usr.sbin/rtadvd/rtadvd.c
+++ b/usr.sbin/rtadvd/rtadvd.c
@@ -138,6 +138,7 @@ union nd_opt {
#define NDOPT_FLAG_RDNSS (1 << 5)
#define NDOPT_FLAG_DNSSL (1 << 6)
#define NDOPT_FLAG_PREF64 (1 << 7)
+#define NDOPT_FLAG_ROUTEINFO (1 << 8)
static uint32_t ndopt_flags[] = {
[ND_OPT_SOURCE_LINKADDR] = NDOPT_FLAG_SRCLINKADDR,
@@ -148,6 +149,7 @@ static uint32_t ndopt_flags[] = {
[ND_OPT_RDNSS] = NDOPT_FLAG_RDNSS,
[ND_OPT_DNSSL] = NDOPT_FLAG_DNSSL,
[ND_OPT_PREF64] = NDOPT_FLAG_PREF64,
+ [ND_OPT_ROUTE_INFO] = NDOPT_FLAG_ROUTEINFO,
};
static void rtadvd_shutdown(void);
@@ -372,6 +374,7 @@ rtadvd_shutdown(void)
struct rainfo *rai;
struct rdnss *rdn;
struct dnssl *dns;
+ struct rtinfo *rti;
if (wait_shutdown) {
syslog(LOG_INFO,
@@ -416,6 +419,8 @@ rtadvd_shutdown(void)
rdn->rd_ltime = 0;
TAILQ_FOREACH(dns, &rai->rai_dnssl, dn_next)
dns->dn_ltime = 0;
+ TAILQ_FOREACH(rti, &rai->rai_route, rti_next)
+ rti->rti_ltime = 0;
}
TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
if (!ifi->ifi_persist)
@@ -1085,7 +1090,8 @@ ra_input(int len, struct nd_router_advert *nra,
error = nd6_options((struct nd_opt_hdr *)(nra + 1),
len - sizeof(struct nd_router_advert), &ndopts,
NDOPT_FLAG_SRCLINKADDR | NDOPT_FLAG_PREFIXINFO | NDOPT_FLAG_MTU |
- NDOPT_FLAG_RDNSS | NDOPT_FLAG_DNSSL | NDOPT_FLAG_PREF64);
+ NDOPT_FLAG_RDNSS | NDOPT_FLAG_DNSSL | NDOPT_FLAG_PREF64 |
+ NDOPT_FLAG_ROUTEINFO);
if (error) {
syslog(LOG_INFO,
"<%s> ND option check failed for an RA from %s on %s",
@@ -1132,9 +1138,9 @@ ra_input(int len, struct nd_router_advert *nra,
syslog(LOG_NOTICE,
"M flag inconsistent on %s:"
" %s from %s, %s from us",
- ifi->ifi_ifname, on_off[!rai->rai_managedflg],
+ ifi->ifi_ifname, on_off[rai->rai_managedflg == 0],
inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
- sizeof(ntopbuf)), on_off[rai->rai_managedflg]);
+ sizeof(ntopbuf)), on_off[rai->rai_managedflg != 0]);
inconsistent++;
}
/* O flag */
@@ -1143,24 +1149,11 @@ ra_input(int len, struct nd_router_advert *nra,
syslog(LOG_NOTICE,
"O flag inconsistent on %s:"
" %s from %s, %s from us",
- ifi->ifi_ifname, on_off[!rai->rai_otherflg],
+ ifi->ifi_ifname, on_off[rai->rai_otherflg == 0],
inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
- sizeof(ntopbuf)), on_off[rai->rai_otherflg]);
+ sizeof(ntopbuf)), on_off[rai->rai_otherflg != 0]);
inconsistent++;
}
-#ifdef DRAFT_IETF_6MAN_IPV6ONLY_FLAG
- /* S "IPv6-Only" (Six, Silence-IPv4) flag */
- if ((nra->nd_ra_flags_reserved & ND_RA_FLAG_IPV6_ONLY) !=
- rai->rai_ipv6onlyflg) {
- syslog(LOG_NOTICE,
- "S flag inconsistent on %s:"
- " %s from %s, %s from us",
- ifi->ifi_ifname, on_off[!rai->rai_ipv6onlyflg],
- inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
- sizeof(ntopbuf)), on_off[rai->rai_ipv6onlyflg]);
- inconsistent++;
- }
-#endif
/* Reachable Time */
reachabletime = ntohl(nra->nd_ra_reachable);
if (reachabletime && rai->rai_reachabletime &&
@@ -1431,7 +1424,8 @@ nd6_options(struct nd_opt_hdr *hdr, int limit,
if (hdr->nd_opt_type > ND_OPT_MTU &&
hdr->nd_opt_type != ND_OPT_RDNSS &&
hdr->nd_opt_type != ND_OPT_DNSSL &&
- hdr->nd_opt_type != ND_OPT_PREF64) {
+ hdr->nd_opt_type != ND_OPT_PREF64 &&
+ hdr->nd_opt_type != ND_OPT_ROUTE_INFO) {
syslog(LOG_INFO, "<%s> unknown ND option(type %d)",
__func__, hdr->nd_opt_type);
continue;
@@ -1465,6 +1459,11 @@ nd6_options(struct nd_opt_hdr *hdr, int limit,
case ND_OPT_PREFIX_INFORMATION:
if (optlen == sizeof(struct nd_opt_prefix_info))
break;
+ goto skip;
+ case ND_OPT_ROUTE_INFO:
+ if (optlen >= 8 && optlen <= 24 &&
+ (optlen - sizeof(struct nd_opt_route_info)) % 8 == 0)
+ break;
skip:
syslog(LOG_INFO, "<%s> invalid option length",
__func__);
@@ -1477,6 +1476,7 @@ skip:
case ND_OPT_RDNSS:
case ND_OPT_DNSSL:
case ND_OPT_PREF64:
+ case ND_OPT_ROUTE_INFO:
break; /* we don't care about these options */
case ND_OPT_SOURCE_LINKADDR:
case ND_OPT_MTU:
diff --git a/usr.sbin/rtadvd/rtadvd.h b/usr.sbin/rtadvd/rtadvd.h
index 5ecfd1b56423..5b9037b2dcf3 100644
--- a/usr.sbin/rtadvd/rtadvd.h
+++ b/usr.sbin/rtadvd/rtadvd.h
@@ -204,9 +204,6 @@ struct rainfo {
uint16_t rai_mininterval; /* MinRtrAdvInterval */
int rai_managedflg; /* AdvManagedFlag */
int rai_otherflg; /* AdvOtherConfigFlag */
-#ifdef DRAFT_IETF_6MAN_IPV6ONLY_FLAG
- int rai_ipv6onlyflg; /* AdvIPv6OnlyFlag */
-#endif
int rai_rtpref; /* router preference */
uint32_t rai_linkmtu; /* AdvLinkMTU */
diff --git a/usr.sbin/service/service.sh b/usr.sbin/service/service.sh
index 9387a49051d6..5ae1b2bee933 100755
--- a/usr.sbin/service/service.sh
+++ b/usr.sbin/service/service.sh
@@ -39,7 +39,7 @@ usage () {
echo "${0##*/} [-j <jail name or id>] [-dqv] [-E var=value] <rc.d script> start|stop|etc."
echo "${0##*/} -h"
echo ''
- echo "-d Enable debugging of rc.d scripts"
+ echo "-d Enable debugging of rc.d scripts"
echo "-j Perform actions within the named jail"
echo "-E n=val Set variable n to val before executing the rc.d script"
echo '-e Show services that are enabled'
diff --git a/usr.sbin/smart/Makefile b/usr.sbin/smart/Makefile
new file mode 100644
index 000000000000..924b06d3ebcb
--- /dev/null
+++ b/usr.sbin/smart/Makefile
@@ -0,0 +1,8 @@
+.include <src.opts.mk>
+
+SMARTDIR=${SRCTOP}/contrib/smart
+.PATH: ${SMARTDIR}
+
+PACKAGE= smart
+
+.include "${SMARTDIR}/Makefile"
diff --git a/usr.sbin/sndctl/sndctl.8 b/usr.sbin/sndctl/sndctl.8
index 73414bd95325..44dcb791ca51 100644
--- a/usr.sbin/sndctl/sndctl.8
+++ b/usr.sbin/sndctl/sndctl.8
@@ -27,7 +27,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd December 2, 2025
+.Dd April 17, 2026
.Dt SNDCTL 8
.Os
.Sh NAME
@@ -82,6 +82,8 @@ The device controls are as follows:
.It bitperfect Ta Boolean Ta Read/Write Ta Bit-perfect mode enabled
.It autoconv Ta Boolean Ta Read/Write Ta Auto-conversions enabled
.It realtime Ta Boolean Ta Read/Write Ta Real-time mode enabled
+.It eq Ta Boolean Ta Read/Write Ta Equalization enabled
+.It eq_preamp Ta String Ta Read/Write Ta Equalization preamp value (in dB)
.It play Ta Group Ta Read Ta Playback properties
.It play.format Ta String Ta Read/Write Ta Playback format
.It play.rate Ta Number Ta Read/Write Ta Playback sample rate
diff --git a/usr.sbin/sndctl/sndctl.c b/usr.sbin/sndctl/sndctl.c
index bbc2da6a4ab9..778643a2a978 100644
--- a/usr.sbin/sndctl/sndctl.c
+++ b/usr.sbin/sndctl/sndctl.c
@@ -97,6 +97,8 @@ struct snd_dev {
int bitperfect;
int realtime;
int autoconv;
+ int eq;
+ char eq_preamp[BUFSIZ];
struct {
char format[FMTSTR_LEN];
int rate;
@@ -130,6 +132,8 @@ struct map {
static int mod_bitperfect(struct snd_dev *, void *);
static int mod_autoconv(struct snd_dev *, void *);
static int mod_realtime(struct snd_dev *, void *);
+static int mod_eq(struct snd_dev *, void *);
+static int mod_eq_preamp(struct snd_dev *, void *);
static int mod_play_vchans(struct snd_dev *, void *);
static int mod_play_rate(struct snd_dev *, void *);
static int mod_play_format(struct snd_dev *, void *);
@@ -149,6 +153,8 @@ static struct snd_ctl dev_ctls[] = {
{ "bitperfect", F(bitperfect), NUM, mod_bitperfect },
{ "autoconv", F(autoconv), NUM, mod_autoconv },
{ "realtime", F(realtime), NUM, mod_realtime },
+ { "eq", F(eq), NUM, mod_eq },
+ { "eq_preamp", F(eq_preamp), STR, mod_eq_preamp },
{ "play", F(play), GRP, NULL },
{ "play.format", F(play.format), STR, mod_play_format },
{ "play.rate", F(play.rate), NUM, mod_play_rate },
@@ -436,6 +442,7 @@ read_dev(char *path)
struct sndstioc_nv_arg arg;
struct snd_dev *dp = NULL;
struct snd_chan *ch;
+ char buf[64];
size_t nitems, nchans, i, j;
int fd, caps, unit, t1, t2, t3;
@@ -557,6 +564,14 @@ read_dev(char *path)
if (t1 == 0 && t2 == 0 && t3 == 0)
dp->realtime = 1;
+ snprintf(buf, sizeof(buf), "dev.pcm.%d.eq", dp->unit);
+ if (sysctl_int(buf, NULL, &dp->eq))
+ xo_err(1, "%s: sysctl", dp->name);
+
+ snprintf(buf, sizeof(buf), "dev.pcm.%d.eq_preamp", dp->unit);
+ if (sysctl_str(buf, NULL, dp->eq_preamp, sizeof(dp->eq_preamp)))
+ xo_err(1, "%s: sysctl", dp->name);
+
if (!nvlist_exists(nvlist_get_nvlist(di[i],
SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_CHAN_INFO))
xo_errx(1, "%s: channel info list empty", dp->name);
@@ -843,6 +858,32 @@ mod_realtime(struct snd_dev *dp, void *arg)
}
static int
+mod_eq(struct snd_dev *dp, void *arg)
+{
+ char buf[64];
+
+ if (dp->from_user)
+ return (-1);
+
+ snprintf(buf, sizeof(buf), "dev.pcm.%d.eq", dp->unit);
+
+ return (sysctl_int(buf, arg, &dp->eq));
+}
+
+static int
+mod_eq_preamp(struct snd_dev *dp, void *arg)
+{
+ char buf[64];
+
+ if (dp->from_user)
+ return (-1);
+
+ snprintf(buf, sizeof(buf), "dev.pcm.%d.eq_preamp", dp->unit);
+
+ return (sysctl_str(buf, arg, dp->eq_preamp, sizeof(dp->eq_preamp)));
+}
+
+static int
mod_play_vchans(struct snd_dev *dp, void *arg)
{
char buf[64];
diff --git a/usr.sbin/spi/spi.c b/usr.sbin/spi/spi.c
index 1ced2371e3d0..bbbc3d322b00 100644
--- a/usr.sbin/spi/spi.c
+++ b/usr.sbin/spi/spi.c
@@ -30,6 +30,7 @@
#include <sys/spigenio.h>
#include <sys/sysctl.h>
+#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
@@ -364,30 +365,24 @@ main(int argc, char *argv[], char *envp[] __unused)
/* do data transfer */
- if (stream) {
- while (!err && !feof(stdin)) {
- if (fdir == DIR_READ) {
- err = perform_read(hdev, &opt);
- }
- else if (fdir == DIR_WRITE) {
- err = perform_write(hdev, &opt);
- }
- else if (fdir == DIR_READWRITE) {
- err = perform_readwrite(hdev, &opt);
- }
- }
- }
- else {
- if (fdir == DIR_READ) {
+ assert(fdir != DIR_NONE);
+ do {
+ switch (fdir) {
+ case DIR_READ:
err = perform_read(hdev, &opt);
- }
- else if (fdir == DIR_WRITE) {
+ break;
+ case DIR_WRITE:
err = perform_write(hdev, &opt);
- }
- else if (fdir == DIR_READWRITE) {
+ break;
+ case DIR_READWRITE:
err = perform_readwrite(hdev, &opt);
+ break;
+ default:
+ fprintf(stderr, "Invalid state (%d)\n", fdir);
+ err = EINVAL;
+ break;
}
- }
+ } while (stream && !err && !feof(stdin));
the_end:
diff --git a/usr.sbin/syslogd/syslog.conf.5 b/usr.sbin/syslogd/syslog.conf.5
index 691f2cdd7062..f641aedee3e5 100644
--- a/usr.sbin/syslogd/syslog.conf.5
+++ b/usr.sbin/syslogd/syslog.conf.5
@@ -25,14 +25,12 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd December 10, 2020
+.Dd March 8, 2026
.Dt SYSLOG.CONF 5
.Os
.Sh NAME
.Nm syslog.conf
-.Nd
-.Xr syslogd 8
-configuration file
+.Nd syslogd system message log configuration file
.Sh DESCRIPTION
The
.Nm
diff --git a/usr.sbin/syslogd/syslogd.c b/usr.sbin/syslogd/syslogd.c
index 1b894ae54fc6..e3d144a532b5 100644
--- a/usr.sbin/syslogd/syslogd.c
+++ b/usr.sbin/syslogd/syslogd.c
@@ -360,7 +360,6 @@ static int evaluate_prop_filter(const struct prop_filter *filter,
static nvlist_t *prop_filter_compile(const char *);
static void parsemsg(const char *, char *);
static void printsys(char *);
-static const char *ttymsg_check(struct iovec *, int, char *, int);
static void usage(void);
static bool validate(struct sockaddr *, const char *);
static void unmapped(struct sockaddr *);
@@ -781,15 +780,21 @@ main(int argc, char *argv[])
case EVFILT_SIGNAL:
switch (ev.ident) {
case SIGHUP:
+ /* Reload */
init(true);
break;
case SIGINT:
case SIGQUIT:
+ /* Ignore these unless -F and / or -d */
+ if (!Foreground && !Debug)
+ break;
+ /* FALLTHROUGH */
case SIGTERM:
- if (ev.ident == SIGTERM || Debug)
- die(ev.ident);
+ /* Terminate */
+ die(ev.ident);
break;
case SIGALRM:
+ /* Mark and flush */
markit();
break;
}
@@ -1747,23 +1752,40 @@ iovlist_truncate(struct iovlist *il, size_t size)
}
#endif
+static int
+find_forw_fd(const struct sockaddr_storage *rss,
+ const struct sockaddr_storage *lss, struct filed *skip)
+{
+ struct filed *f;
+
+ STAILQ_FOREACH(f, &fhead, next) {
+ if (f->f_type != F_FORW || f == skip)
+ continue;
+ for (size_t i = 0; i < f->f_num_addr_fds; ++i) {
+ if (memcmp(&f->f_addrs[i].raddr, rss, rss->ss_len) == 0 &&
+ memcmp(&f->f_addrs[i].laddr, lss, lss->ss_len) == 0)
+ return (f->f_addr_fds[i]);
+ }
+ }
+ return (-1);
+}
+
static void
fprintlog_write(struct filed *f, struct iovlist *il, int flags)
{
- const char *msgret;
-
switch (f->f_type) {
case F_FORW: {
ssize_t lsent;
if (Debug) {
- int domain, sockfd = f->f_addr_fds[0];
+ int domain, port, sockfd = f->f_addr_fds[0];
socklen_t len = sizeof(domain);
if (getsockopt(sockfd, SOL_SOCKET, SO_DOMAIN,
&domain, &len) < 0)
err(1, "getsockopt");
+ port = -1;
printf(" %s", f->f_hname);
switch (domain) {
#ifdef INET
@@ -1773,8 +1795,10 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags)
len = sizeof(sin);
if (getpeername(sockfd,
(struct sockaddr *)&sin, &len) < 0)
- err(1, "getpeername");
- printf(":%d\n", ntohs(sin.sin_port));
+ warn("getpeername");
+ else
+ port = ntohs(sin.sin_port);
+ printf(":%d\n", port);
break;
}
#endif
@@ -1785,8 +1809,10 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags)
len = sizeof(sin6);
if (getpeername(sockfd,
(struct sockaddr *)&sin6, &len) < 0)
- err(1, "getpeername");
- printf(":%d\n", ntohs(sin6.sin6_port));
+ warn("getpeername");
+ else
+ port = ntohs(sin6.sin6_port);
+ printf(":%d\n", port);
break;
}
#endif
@@ -1800,23 +1826,76 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags)
iovlist_truncate(il, MaxForwardLen);
#endif
+ /*
+ * We have some constraints on message forwarding:
+ * - we want to send messages from an address to which syslogd
+ * is bound,
+ * - syslogd might start before the system's routes are
+ * configured, in which case connect() will fail,
+ * - there may be multiple logging rules which forward a message
+ * to a given address, i.e., sockets from different fileds
+ * may have the same <laddr, raddr> tuple,
+ * - we don't want to use casper to forward messages for us, as
+ * that's a lot of overhead to add to each message.
+ *
+ * These constraints plus Capsicum's restrictions make this
+ * rather complicated. We handle the first constraint by
+ * calling bind() when setting up forwarding sockets. The
+ * second constraint is handled by allowing connect() to fail
+ * during setup; if the sendmsg() call below fails for that
+ * reason, we then use cap_connect() to connect it lazily.
+ * Finally, that connect() call may fail due to the third
+ * constraint, in which case we look for another matching socket
+ * and use that one instead.
+ */
lsent = 0;
for (size_t i = 0; i < f->f_num_addr_fds; ++i) {
struct msghdr msg = {
.msg_iov = il->iov,
.msg_iovlen = il->iovcnt,
};
+ int fd;
+
+ fd = f->f_addr_fds[i];
+ lsent = sendmsg(fd, &msg, 0);
+ if (lsent == -1 &&
+ (errno == ENOTCONN || errno == EDESTADDRREQ)) {
+ int error;
- lsent = sendmsg(f->f_addr_fds[i], &msg, 0);
+ error = cap_connect(cap_net, fd,
+ (struct sockaddr *)&f->f_addrs[i].raddr,
+ f->f_addrs[i].raddr.ss_len);
+ if (error != 0 && errno == EADDRINUSE) {
+ fd = find_forw_fd(&f->f_addrs[i].raddr,
+ &f->f_addrs[i].laddr, f);
+ if (fd != -1) {
+ (void)dup2(fd,
+ f->f_addr_fds[i]);
+ fd = f->f_addr_fds[i];
+ } else {
+ /*
+ * Something is preventing us
+ * from connecting, we don't
+ * have much recourse. Keep
+ * fd==-1 to trigger an error
+ * from sendmsg() below.
+ */
+ dprintf(
+ "failed to connect to %s",
+ f->f_hname);
+ }
+ }
+ lsent = sendmsg(fd, &msg, 0);
+ }
if (lsent == (ssize_t)il->totalsize && !send_to_all)
break;
}
dprintf("lsent/totalsize: %zd/%zu\n", lsent, il->totalsize);
if (lsent != (ssize_t)il->totalsize) {
int e = errno;
+
logerror("sendto");
- errno = e;
- switch (errno) {
+ switch (e) {
case ENOBUFS:
case ENETDOWN:
case ENETUNREACH:
@@ -1825,6 +1904,9 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags)
case EADDRNOTAVAIL:
case EAGAIN:
case ECONNREFUSED:
+ case ENOTCONN:
+ case EDESTADDRREQ:
+ case EADDRINUSE:
break;
/* case EBADF: */
/* case EACCES: */
@@ -1833,7 +1915,7 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags)
/* case EMSGSIZE: */
default:
dprintf("removing entry: errno=%d\n", e);
- f->f_type = F_UNUSED;
+ close_filed(f);
break;
}
}
@@ -1904,10 +1986,10 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags)
dprintf(" %s%s\n", _PATH_DEV, f->f_fname);
iovlist_append(il, "\r\n");
errno = 0; /* ttymsg() only sometimes returns an errno */
- if ((msgret = cap_ttymsg(cap_syslogd, il->iov, il->iovcnt,
- f->f_fname, 10))) {
+ if (cap_ttymsg(cap_syslogd, il->iov, il->iovcnt, f->f_fname, 10,
+ true) != 0) {
f->f_type = F_UNUSED;
- logerror(msgret);
+ logerror(f->f_fname);
}
break;
@@ -2137,7 +2219,6 @@ wallmsg(const struct filed *f, struct iovec *iov, const int iovlen)
static int reenter; /* avoid calling ourselves */
struct utmpx *ut;
int i;
- const char *p;
if (reenter++)
return;
@@ -2147,19 +2228,19 @@ wallmsg(const struct filed *f, struct iovec *iov, const int iovlen)
if (ut->ut_type != USER_PROCESS)
continue;
if (f->f_type == F_WALL) {
- if ((p = ttymsg(iov, iovlen, ut->ut_line,
- TTYMSGTIME)) != NULL)
- dprintf("%s\n", p);
+ if (ttymsg(iov, iovlen, ut->ut_line, TTYMSGTIME,
+ false) != 0 && errno != ENOENT)
+ dprintf("%s: %m\n", ut->ut_line);
continue;
}
/* should we send the message to this user? */
for (i = 0; i < MAXUNAMES; i++) {
if (!f->f_uname[i][0])
break;
- if (!strcmp(f->f_uname[i], ut->ut_user)) {
- if ((p = ttymsg_check(iov, iovlen, ut->ut_line,
- TTYMSGTIME)) != NULL)
- dprintf("%s\n", p);
+ if (strcmp(f->f_uname[i], ut->ut_user) == 0) {
+ if (ttymsg(iov, iovlen, ut->ut_line, TTYMSGTIME,
+ true) != 0 && errno != ENOENT)
+ dprintf("%s: %m\n", ut->ut_line);
break;
}
}
@@ -2169,29 +2250,6 @@ wallmsg(const struct filed *f, struct iovec *iov, const int iovlen)
}
/*
- * Wrapper routine for ttymsg() that checks the terminal for messages enabled.
- */
-static const char *
-ttymsg_check(struct iovec *iov, int iovcnt, char *line, int tmout)
-{
- static char device[1024];
- static char errbuf[1024];
- struct stat sb;
-
- (void) snprintf(device, sizeof(device), "%s%s", _PATH_DEV, line);
-
- if (stat(device, &sb) < 0) {
- (void) snprintf(errbuf, sizeof(errbuf),
- "%s: %s", device, strerror(errno));
- return (errbuf);
- }
- if ((sb.st_mode & S_IWGRP) == 0)
- /* Messages disabled. */
- return (NULL);
- return (ttymsg(iov, iovcnt, line, tmout));
-}
-
-/*
* Return a printable representation of a host address.
*/
static const char *
@@ -2554,7 +2612,7 @@ syslogd_cap_enter(void)
err(1, "Failed to open the system.net libcasper service");
cap_close(cap_casper);
limit = cap_net_limit_init(cap_net,
- CAPNET_ADDR2NAME | CAPNET_NAME2ADDR);
+ CAPNET_ADDR2NAME | CAPNET_NAME2ADDR | CAPNET_CONNECT);
if (limit == NULL)
err(1, "Failed to create system.net limits");
if (cap_net_limit(limit) == -1)
@@ -2662,15 +2720,18 @@ init(bool reload)
&domain, &len) < 0)
err(1, "getsockopt");
+ port = -1;
switch (domain) {
#ifdef INET
case AF_INET: {
struct sockaddr_in sin;
len = sizeof(sin);
- if (getpeername(sockfd, (struct sockaddr *)&sin, &len) < 0)
- err(1, "getpeername");
- port = ntohs(sin.sin_port);
+ if (getpeername(sockfd,
+ (struct sockaddr *)&sin, &len) < 0)
+ warn("getpeername");
+ else
+ port = ntohs(sin.sin_port);
break;
}
#endif
@@ -2679,20 +2740,18 @@ init(bool reload)
struct sockaddr_in6 sin6;
len = sizeof(sin6);
- if (getpeername(sockfd, (struct sockaddr *)&sin6, &len) < 0)
- err(1, "getpeername");
- port = ntohs(sin6.sin6_port);
+ if (getpeername(sockfd,
+ (struct sockaddr *)&sin6, &len) < 0)
+ warn("getpeername");
+ else
+ port = ntohs(sin6.sin6_port);
break;
}
#endif
default:
port = 0;
}
- if (port != 514) {
- printf("%s:%d", f->f_hname, port);
- } else {
- printf("%s", f->f_hname);
- }
+ printf("%s:%d", f->f_hname, port);
break;
}
@@ -2814,7 +2873,7 @@ prop_filter_compile(const char *cfilter)
pfilter.cmp_type = FILT_CMP_REGEX;
else if (strcasecmp(argv[1], "ereregex") == 0) {
pfilter.cmp_type = FILT_CMP_REGEX;
- pfilter.cmp_flags |= REG_EXTENDED;
+ pfilter.cmp_flags |= FILT_FLAG_EXTENDED;
} else {
dprintf("unknown cmp function");
goto error;
@@ -3035,13 +3094,20 @@ make_forw_socket(const nvlist_t *nvl, struct addrinfo *ai, struct addrinfo *lai)
errc(1, EADDRINUSE, "connect");
(void)close(s);
s = s1;
+ } else if (errno == ENETUNREACH || errno == EHOSTUNREACH) {
+ /*
+ * We can't connect right now, the system is probably
+ * not fully configured. We will try again, using
+ * cap_net, once something actually tries to log.
+ */
+ ;
} else {
err(1, "connect");
}
}
/* Make it a write-only socket. */
if (shutdown(s, SHUT_RD) < 0)
- err(1, "shutdown");
+ warn("shutdown");
return (s);
}
@@ -3165,7 +3231,8 @@ parse_action(const nvlist_t *nvl, const char *p, struct filed *f)
};
error = getaddrinfo(f->f_hname, p ? p : "syslog", &hints, &res);
if (error) {
- dprintf("%s\n", gai_strerror(error));
+ dprintf("getaddrinfo(%s): %s\n", f->f_hname,
+ gai_strerror(error));
break;
}
make_forw_socket_array(nvl, f, res);
@@ -3705,6 +3772,7 @@ validate(struct sockaddr *sa, const char *hname)
int
p_open(const char *prog, int *rpd)
{
+ cap_rights_t rights;
struct sigaction act = { };
int pfd[2], pd;
pid_t pid;
@@ -3756,6 +3824,10 @@ p_open(const char *prog, int *rpd)
dprintf("Warning: cannot change pipe to PID %d to non-blocking"
"behaviour.", pid);
}
+
+ if (cap_rights_limit(pd,
+ cap_rights_init(&rights, CAP_PDKILL, CAP_EVENT)) == -1)
+ err(1, "cap_rights_limit");
*rpd = pd;
return (pfd[1]);
}
diff --git a/usr.sbin/syslogd/syslogd_cap.c b/usr.sbin/syslogd/syslogd_cap.c
index 7539e6b8661b..0149d09dab99 100644
--- a/usr.sbin/syslogd/syslogd_cap.c
+++ b/usr.sbin/syslogd/syslogd_cap.c
@@ -50,7 +50,7 @@ casper_command(const char *cmd, const nvlist_t *limits __unused,
else if (strcmp(cmd, "readconfigfile") == 0)
error = casper_readconfigfile(nvlin, nvlout);
else if (strcmp(cmd, "ttymsg") == 0)
- error = casper_ttymsg(nvlin, nvlout);
+ error = casper_ttymsg(nvlin);
else if (strcmp(cmd, "wallmsg") == 0)
error = casper_wallmsg(nvlin);
diff --git a/usr.sbin/syslogd/syslogd_cap.h b/usr.sbin/syslogd/syslogd_cap.h
index 2e52c57bcdf8..60106d7862bd 100644
--- a/usr.sbin/syslogd/syslogd_cap.h
+++ b/usr.sbin/syslogd/syslogd_cap.h
@@ -62,13 +62,13 @@ extern SLIST_HEAD(cfiled_list, cap_filed) cfiled_head;
int cap_p_open(cap_channel_t *, size_t, const char *, int *);
nvlist_t *cap_readconfigfile(cap_channel_t *, const char *);
-const char *cap_ttymsg(cap_channel_t *, struct iovec *, int, const char *, int);
+int cap_ttymsg(cap_channel_t *, struct iovec *, int, const char *, int, bool);
void cap_wallmsg(cap_channel_t *, const struct filed *, struct iovec *,
const int);
int casper_p_open(nvlist_t *, nvlist_t *);
int casper_readconfigfile(nvlist_t *, nvlist_t *);
-int casper_ttymsg(nvlist_t *, nvlist_t *);
+int casper_ttymsg(nvlist_t *);
int casper_wallmsg(nvlist_t *);
nvlist_t *filed_to_nvlist(const struct filed *);
@@ -83,8 +83,8 @@ struct prop_filter *nvlist_to_prop_filter(const nvlist_t *nvl_prop_filter);
p_open(prog, rpd)
#define cap_readconfigfile(chan, cf) \
readconfigfile(cf)
-#define cap_ttymsg(chan, iov, iovcnt, line, tmout) \
- ttymsg(iov, iovcnt, line, tmout)
+#define cap_ttymsg(chan, iov, iovcnt, line, timeout) \
+ ttymsg(iov, iovcnt, line, timeout)
#define cap_wallmsg(chan, f, iov, iovcnt) \
wallmsg(f, iov, iovcnt)
diff --git a/usr.sbin/syslogd/syslogd_cap_config.c b/usr.sbin/syslogd/syslogd_cap_config.c
index 0cf6fb67491d..5b333d5f6037 100644
--- a/usr.sbin/syslogd/syslogd_cap_config.c
+++ b/usr.sbin/syslogd/syslogd_cap_config.c
@@ -226,6 +226,13 @@ nvlist_to_filed(const nvlist_t *nvl_filed)
if (filed->f_addr_fds[i] < 0)
err(1, "dup");
}
+ filed->f_addrs = calloc(filed->f_num_addr_fds,
+ sizeof(*filed->f_addrs));
+ if (filed->f_addrs == NULL)
+ err(1, "calloc");
+ memcpy(filed->f_addrs,
+ nvlist_get_binary(nvl_filed, "f_addrs", NULL),
+ filed->f_num_addr_fds * sizeof(*filed->f_addrs));
} else if (filed->f_type == F_PIPE) {
(void)strlcpy(filed->f_pname, nvlist_get_string(nvl_filed,
"f_pname"), sizeof(filed->f_pname));
diff --git a/usr.sbin/syslogd/syslogd_cap_log.c b/usr.sbin/syslogd/syslogd_cap_log.c
index 0156cc6f6b6c..901349c4dc5a 100644
--- a/usr.sbin/syslogd/syslogd_cap_log.c
+++ b/usr.sbin/syslogd/syslogd_cap_log.c
@@ -52,14 +52,12 @@ cap_p_open(cap_channel_t *chan, size_t filed_idx, const char *prog,
exit(1);
}
error = nvlist_get_number(nvl, "error");
- if (error != 0) {
- errno = error;
- logerror("Failed to open piped command");
- }
pipedesc_w = dnvlist_take_descriptor(nvl, "pipedesc_w", -1);
*procdesc = dnvlist_take_descriptor(nvl, "procdesc", -1);
nvlist_destroy(nvl);
+ if (error != 0)
+ errno = error;
return (pipedesc_w);
}
@@ -81,29 +79,27 @@ casper_p_open(nvlist_t *nvlin, nvlist_t *nvlout)
pipedesc_w = p_open(prog, &procdesc);
if (pipedesc_w == -1)
- return (-1);
+ return (errno);
nvlist_move_descriptor(nvlout, "pipedesc_w", pipedesc_w);
nvlist_move_descriptor(nvlout, "procdesc", procdesc);
return (0);
}
-
- return (-1);
+ return (ECAPMODE);
}
-const char *
+int
cap_ttymsg(cap_channel_t *chan, struct iovec *iov, int iovcnt,
- const char *line, int tmout)
+ const char *line, int timeout, bool shout)
{
nvlist_t *nvl = nvlist_create(0);
- int error;
- static char errbuf[1024];
- char *ret = NULL;
+ int error = 0;
nvlist_add_string(nvl, "cmd", "ttymsg");
for (int i = 0; i < iovcnt; ++i)
nvlist_append_string_array(nvl, "iov_strs", iov[i].iov_base);
nvlist_add_string(nvl, "line", line);
- nvlist_add_number(nvl, "tmout", tmout);
+ nvlist_add_number(nvl, "timeout", timeout);
+ nvlist_add_bool(nvl, "shout", shout);
nvl = cap_xfer_nvlist(chan, nvl);
if (nvl == NULL) {
@@ -111,46 +107,40 @@ cap_ttymsg(cap_channel_t *chan, struct iovec *iov, int iovcnt,
exit(1);
}
error = nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
if (error != 0) {
errno = error;
- logerror("Failed to ttymsg");
- }
- if (nvlist_exists_string(nvl, "errstr")) {
- const char *errstr = nvlist_get_string(nvl, "errstr");
- (void)strlcpy(errbuf, errstr, sizeof(errbuf));
- ret = errbuf;
+ return (-1);
}
-
- nvlist_destroy(nvl);
- return (ret);
+ return (0);
}
int
-casper_ttymsg(nvlist_t *nvlin, nvlist_t *nvlout)
+casper_ttymsg(nvlist_t *nvlin)
{
- char **nvlstrs;
+ const char * const *nvlstrs;
struct iovec *iov;
- size_t iovcnt;
- int tmout;
const char *line;
+ size_t iovcnt;
+ int ret, timeout;
+ bool shout;
- nvlstrs = nvlist_take_string_array(nvlin, "iov_strs", &iovcnt);
+ nvlstrs = nvlist_get_string_array(nvlin, "iov_strs", &iovcnt);
assert(iovcnt <= TTYMSG_IOV_MAX);
iov = calloc(iovcnt, sizeof(*iov));
if (iov == NULL)
err(EXIT_FAILURE, "calloc");
for (size_t i = 0; i < iovcnt; ++i) {
- iov[i].iov_base = nvlstrs[i];
+ iov[i].iov_base = __DECONST(char *, nvlstrs[i]);
iov[i].iov_len = strlen(nvlstrs[i]);
}
line = nvlist_get_string(nvlin, "line");
- tmout = nvlist_get_number(nvlin, "tmout");
- line = ttymsg(iov, iovcnt, line, tmout);
- if (line != NULL)
- nvlist_add_string(nvlout, "errstr", line);
-
+ timeout = nvlist_get_number(nvlin, "timeout");
+ shout = nvlist_get_bool(nvlin, "shout");
+ if ((ret = ttymsg(iov, iovcnt, line, timeout, shout)) != 0)
+ ret = errno;
free(iov);
- return (0);
+ return (ret);
}
void
@@ -158,7 +148,6 @@ cap_wallmsg(cap_channel_t *chan, const struct filed *f, struct iovec *iov,
int iovcnt)
{
nvlist_t *nvl = nvlist_create(0);
- int error;
nvlist_add_string(nvl, "cmd", "wallmsg");
/*
@@ -175,11 +164,6 @@ cap_wallmsg(cap_channel_t *chan, const struct filed *f, struct iovec *iov,
logerror("Failed to xfer wallmsg nvlist");
exit(1);
}
- error = nvlist_get_number(nvl, "error");
- if (error != 0) {
- errno = error;
- logerror("Failed to wallmsg");
- }
nvlist_destroy(nvl);
}
@@ -187,25 +171,23 @@ int
casper_wallmsg(nvlist_t *nvlin)
{
const struct filed *f;
- char **nvlstrs;
+ const char * const *nvlstrs;
struct iovec *iov;
size_t sz;
f = nvlist_get_binary(nvlin, "filed", &sz);
assert(sz == sizeof(*f));
- nvlstrs = nvlist_take_string_array(nvlin, "iov_strs", &sz);
+ nvlstrs = nvlist_get_string_array(nvlin, "iov_strs", &sz);
assert(sz <= TTYMSG_IOV_MAX);
iov = calloc(sz, sizeof(*iov));
if (iov == NULL)
err(EXIT_FAILURE, "calloc");
for (size_t i = 0; i < sz; ++i) {
- iov[i].iov_base = nvlstrs[i];
+ iov[i].iov_base = __DECONST(char *, nvlstrs[i]);
iov[i].iov_len = strlen(nvlstrs[i]);
}
wallmsg(f, iov, sz);
- for (size_t i = 0; i < sz; ++i)
- free(iov[i].iov_base);
free(iov);
return (0);
}
diff --git a/usr.sbin/syslogd/tests/Makefile b/usr.sbin/syslogd/tests/Makefile
index fcc0679f3274..f362f40c6653 100644
--- a/usr.sbin/syslogd/tests/Makefile
+++ b/usr.sbin/syslogd/tests/Makefile
@@ -7,8 +7,7 @@ ATF_TESTS_SH= syslogd_basic_format_test \
# Run in jail with new vnet so we don't need to worry about address conflicts
TEST_METADATA= execenv="jail" \
- execenv_jail_params="vnet=new" \
- timeout=20
+ execenv_jail_params="vnet=new"
${PACKAGE}FILES=syslogd_format_test_common.sh \
syslogd_test_common.sh
diff --git a/usr.sbin/syslogd/tests/syslogd_test.sh b/usr.sbin/syslogd/tests/syslogd_test.sh
index 1f235c476c49..d17152f69bfc 100644
--- a/usr.sbin/syslogd/tests/syslogd_test.sh
+++ b/usr.sbin/syslogd/tests/syslogd_test.sh
@@ -238,6 +238,28 @@ prop_filter_body()
syslogd_check_log_nomatch "prop1: FreeBSD"
syslogd_check_log_nomatch "prop2: freebsd"
syslogd_check_log "prop3: Solaris"
+
+ printf ":msg,ereregex,\"substring1|substring2\"\nuser.debug\t${SYSLOGD_LOGFILE}\n" \
+ > "${SYSLOGD_CONFIG}"
+ syslogd_reload
+
+ syslogd_log -p user.debug -t "prop1" -h "${SYSLOGD_LOCAL_SOCKET}" "substring1"
+ syslogd_check_log "prop1: substring1"
+ syslogd_log -p user.debug -t "prop2" -h "${SYSLOGD_LOCAL_SOCKET}" "substring2"
+ syslogd_check_log "prop2: substring2"
+ syslogd_log -p user.debug -t "prop3" -h "${SYSLOGD_LOCAL_SOCKET}" "substring3"
+ syslogd_check_log_nomatch "prop3: substring3"
+
+ printf ":msg,!ereregex,\"substring1|substring2\"\nuser.debug\t${SYSLOGD_LOGFILE}\n" \
+ > "${SYSLOGD_CONFIG}"
+ syslogd_reload
+
+ syslogd_log -p user.debug -t "prop1" -h "${SYSLOGD_LOCAL_SOCKET}" "substring1"
+ syslogd_check_log_nomatch "prop1: substring1"
+ syslogd_log -p user.debug -t "prop2" -h "${SYSLOGD_LOCAL_SOCKET}" "substring2"
+ syslogd_check_log_nomatch "prop2: substring2"
+ syslogd_log -p user.debug -t "prop3" -h "${SYSLOGD_LOCAL_SOCKET}" "substring3"
+ syslogd_check_log "prop3: substring3"
}
prop_filter_cleanup()
{
@@ -566,6 +588,74 @@ forward_cleanup()
syslogd_cleanup
}
+atf_test_case "forward_reload" "cleanup"
+forward_reload_head()
+{
+ atf_set descr "syslogd might start before routes are configured"
+ atf_set require.user root
+}
+forward_reload_body()
+{
+ local epair server client
+
+ server=syslogd_server$$
+ client=syslogd_client$$
+
+ syslogd_check_req epair
+
+ atf_check -o save:epair ifconfig epair create
+ epair=$(cat epair)
+ epair=${epair%%a}
+
+ syslogd_mkjail $server vnet
+ atf_check ifconfig ${epair}a vnet $server
+ atf_check jexec $server ifconfig ${epair}a inet6 fd00::2/64
+
+ syslogd_mkjail $client vnet
+ atf_check ifconfig ${epair}b vnet $client
+
+ cat <<__EOF__ > ./server_config
+user.debug ${SYSLOGD_LOGFILE}
+ftp.debug ${SYSLOGD_LOGFILE}
+__EOF__
+
+ syslogd_start -j $server -f ${PWD}/server_config -b fd00::2
+
+ cat <<__EOF__ > ./client_config
+user.debug @[fd00::2]
+ftp.debug @[fd00::2]
+__EOF__
+
+ syslogd_start -j $client -f ${PWD}/client_config \
+ -p ${PWD}/client -P ${SYSLOGD_PIDFILE}.2
+
+ # Make sure the client can't reach the server with the current
+ # network configuration.
+ atf_check -s not-exit:0 -e match:"No route to host" \
+ jexec $client ping6 -c 1 fd00::2
+
+ syslogd_log_jail $client -p user.debug -t test1 "hello there"
+ syslogd_log_jail $client -p ftp.debug -t test2 "hi there"
+
+ atf_check jexec $client ifconfig ${epair}b inet6 fd00::1/64
+ atf_check -o ignore jexec $client ping6 -c 1 fd00::2
+
+ syslogd_check_log_nomatch "test1: hello there"
+ syslogd_check_log_nomatch "test2: hi there"
+
+ syslogd_log_jail $client \
+ -p user.debug -t test1 -h ${PWD}/client "how about now"
+ syslogd_check_log "test1: how about now"
+
+ syslogd_log_jail $client \
+ -p ftp.debug -t test2 -h ${PWD}/client "bing bong"
+ syslogd_check_log "test2: bing bong"
+}
+forward_reload_cleanup()
+{
+ syslogd_cleanup
+}
+
atf_init_test_cases()
{
atf_add_test_case "unix"
@@ -583,4 +673,5 @@ atf_init_test_cases()
atf_add_test_case "allowed_peer_forwarding"
atf_add_test_case "allowed_peer_wildcard"
atf_add_test_case "forward"
+ atf_add_test_case "forward_reload"
}
diff --git a/usr.sbin/tcpdump/tcpdump/Makefile b/usr.sbin/tcpdump/tcpdump/Makefile
index 21c5f9ac7fdf..27270cd34dfb 100644
--- a/usr.sbin/tcpdump/tcpdump/Makefile
+++ b/usr.sbin/tcpdump/tcpdump/Makefile
@@ -116,7 +116,6 @@ SRCS= addrtoname.c \
print-openflow-1.0.c \
print-ospf.c \
print-ospf6.c \
- print-otv.c \
print-pgm.c \
print-pim.c \
print-pktap.c \
diff --git a/usr.sbin/tcpdump/tcpdump/config.h b/usr.sbin/tcpdump/tcpdump/config.h
index 5e343d5ed0c3..1ccdbaf7647b 100644
--- a/usr.sbin/tcpdump/tcpdump/config.h
+++ b/usr.sbin/tcpdump/tcpdump/config.h
@@ -3,9 +3,17 @@
/* config.h. Generated from config.h.in by configure. */
/* config.h.in. Generated from configure.ac by autoheader. */
+
+#ifndef TCPDUMP_CONFIG_H_
+#define TCPDUMP_CONFIG_H_
+
+
/* Define to 1 if arpa/inet.h declares `ether_ntohost' */
/* #undef ARPA_INET_H_DECLARES_ETHER_NTOHOST */
+/* define if you want to build the instrument functions code */
+/* #undef ENABLE_INSTRUMENT_FUNCTIONS */
+
/* define if you want to build the possibly-buggy SMB printer */
#define ENABLE_SMB 1
@@ -43,9 +51,6 @@
/* Define to 1 if you have the `EVP_DecryptInit_ex' function. */
#define HAVE_EVP_DECRYPTINIT_EX 1
-/* Define to 1 if you have the <fcntl.h> header file. */
-#define HAVE_FCNTL_H 1
-
/* Define to 1 if you have the `fork' function. */
#define HAVE_FORK 1
@@ -64,32 +69,23 @@
/* Define to 1 if you have the `cap-ng' library (-lcap-ng). */
/* #undef HAVE_LIBCAP_NG */
-/* Define to 1 if you have the `crypto' library (-lcrypto). */
+/* Define to 1 if you have a usable `crypto' library (-lcrypto). */
/* See Makefile */
/* #undef HAVE_LIBCRYPTO */
/* Define to 1 if you have the `rpc' library (-lrpc). */
/* #undef HAVE_LIBRPC */
-/* Define to 1 if you have the <memory.h> header file. */
-#define HAVE_MEMORY_H 1
-
/* Define to 1 if you have the <net/if.h> header file. */
#define HAVE_NET_IF_H 1
-/* Define to 1 if printf(3) does not support the z length modifier. */
-/* #undef HAVE_NO_PRINTF_Z */
-
/* Define to 1 if you have the `openat' function. */
#define HAVE_OPENAT 1
-/* Define to 1 if you have the <openssl/evp.h> header file. */
-#define HAVE_OPENSSL_EVP_H 1
-
/* define if the OS provides AF_INET6 and struct in6_addr */
#define HAVE_OS_IPV6_SUPPORT 1
-/* if there's an os_proto.h for this platform, to use additional prototypes */
+/* if there's an os-proto.h for this platform, to use additional prototypes */
/* #undef HAVE_OS_PROTO_H */
/* Define to 1 if you have the `pcap_breakloop' function. */
@@ -179,6 +175,9 @@
/* Define to 1 if you have the <stdint.h> header file. */
#define HAVE_STDINT_H 1
+/* Define to 1 if you have the <stdio.h> header file. */
+#define HAVE_STDIO_H 1
+
/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1
@@ -237,7 +236,7 @@
#define PACKAGE_NAME "tcpdump"
/* Define to the full name and version of this package. */
-#define PACKAGE_STRING "tcpdump 4.99.5"
+#define PACKAGE_STRING "tcpdump 4.99.6"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "tcpdump"
@@ -246,12 +245,11 @@
#define PACKAGE_URL ""
/* Define to the version of this package. */
-#define PACKAGE_VERSION "4.99.5"
-
-/* The size of `void *', as computed by sizeof. */
-/* #undef SIZEOF_VOID_P */
+#define PACKAGE_VERSION "4.99.6"
-/* Define to 1 if you have the ANSI C header files. */
+/* Define to 1 if all of the C90 standard headers exist (not just the ones
+ required in a freestanding environment). This macro is provided for
+ backward compatibility; new code need not use it. */
#define STDC_HEADERS 1
/* Define to 1 if sys/ethernet.h declares `ether_ntohost' */
@@ -293,3 +291,7 @@
/* Define to the type of an unsigned integer type wide enough to hold a
pointer, if such a type exists, and if the system does not define it. */
/* #undef uintptr_t */
+
+
+#endif // TCPDUMP_CONFIG_H_
+
diff --git a/usr.sbin/unbound/setup/local-unbound-setup.sh b/usr.sbin/unbound/setup/local-unbound-setup.sh
index ec3aeb673ecc..6b217ec574cd 100755
--- a/usr.sbin/unbound/setup/local-unbound-setup.sh
+++ b/usr.sbin/unbound/setup/local-unbound-setup.sh
@@ -73,6 +73,19 @@ RE_forward_name="(${RE_dnsname}(@${RE_port})?)"
RE_forward_tls="(${RE_forward_addr}(#${RE_dnsname})?)"
#
+# Check if a kernel feature is available
+#
+has_feature() {
+ local name=$1 v
+ eval "v=\$kern_features_${name}"
+ if [ -z "$v" ] ; then
+ v="$(sysctl -qn "kern.features.${name}")"
+ eval "kern_features_${name}=$((v))"
+ fi
+ return $((!v))
+}
+
+#
# Set default values for unset configuration variables.
#
set_defaults() {
@@ -136,13 +149,12 @@ get_nameservers() {
#
# Scan through /etc/resolv.conf looking for uncommented nameserver
-# lines. Comment out any that don't point to localhost. Finally,
-# append a nameserver line that points to localhost, if there wasn't
-# one already, and enable the edns0 option.
+# lines and comment out any that don't point to localhost. Finally,
+# append the correct nameserver lines and enable the edns0 option.
#
gen_resolv_conf() {
- local localhost=no
- local edns0=no
+ local localhost4=false localhost6=false
+ local edns0=false
while read line ; do
local bareline=${line%%\#*}
local key=${bareline%%[[:space:]]*}
@@ -150,8 +162,17 @@ gen_resolv_conf() {
case ${key} in
nameserver)
case ${value} in
- 127.0.0.1|::1|localhost|localhost.*)
- localhost=yes
+ 127.0.0.1)
+ localhost4=true
+ if ! has_feature inet ; then
+ continue
+ fi
+ ;;
+ ::1)
+ localhost6=true
+ if ! has_feature inet6 ; then
+ continue
+ fi
;;
*)
echo -n "# "
@@ -161,17 +182,20 @@ gen_resolv_conf() {
options)
case ${value} in
*edns0*)
- edns0=yes
+ edns0=true
;;
esac
;;
esac
echo "${line}"
done
- if [ "${localhost}" = "no" ] ; then
+ if ! $localhost4 && has_feature inet ; then
echo "nameserver 127.0.0.1"
fi
- if [ "${edns0}" = "no" ] ; then
+ if ! $localhost6 && has_feature inet6 ; then
+ echo "nameserver ::1"
+ fi
+ if ! $edns0 ; then
echo "options edns0"
fi
}
@@ -261,6 +285,12 @@ gen_unbound_conf() {
if [ "${use_tls}" = "yes" ] ; then
echo " tls-cert-bundle: /etc/ssl/cert.pem"
fi
+ if ! has_feature inet ; then
+ echo " do-ip4: no"
+ fi
+ if ! has_feature inet6 ; then
+ echo " do-ip6: no"
+ fi
echo " so-sndbuf: 0"
echo ""
if [ -f "${forward_conf}" ] ; then
diff --git a/usr.sbin/usbconfig/usbconfig.8 b/usr.sbin/usbconfig/usbconfig.8
index 511fa9c5dddb..37066519a7cf 100644
--- a/usr.sbin/usbconfig/usbconfig.8
+++ b/usr.sbin/usbconfig/usbconfig.8
@@ -159,6 +159,11 @@ Detach kernel driver for the selected interface and USB device.
This command uses the
.Fl i Ar interface_index
option.
+.It Cm attach_kernel_driver
+Attach kernel driver for the selected interface and USB device.
+This command uses the
+.Fl i Ar interface_index
+option.
.It Cm suspend
Force the device to suspend.
.It Cm resume
diff --git a/usr.sbin/usbconfig/usbconfig.c b/usr.sbin/usbconfig/usbconfig.c
index 7cab4a1354b1..96235889c233 100644
--- a/usr.sbin/usbconfig/usbconfig.c
+++ b/usr.sbin/usbconfig/usbconfig.c
@@ -93,6 +93,7 @@ struct options {
uint8_t got_do_request:1;
uint8_t got_detach_kernel_driver:1;
uint8_t opt_dump_in_list_mode:1;
+ uint8_t got_attach_kernel_driver:1;
};
struct token {
@@ -129,6 +130,7 @@ enum {
T_RESET,
T_LIST,
T_DO_REQUEST,
+ T_ATTACH_KERNEL_DRIVER,
};
static struct options options;
@@ -143,6 +145,7 @@ static const struct token token[] = {
{"add_quirk", T_ADD_QUIRK, 1},
{"remove_quirk", T_REMOVE_QUIRK, 1},
{"detach_kernel_driver", T_DETACH_KERNEL_DRIVER, 0},
+ {"attach_kernel_driver", T_ATTACH_KERNEL_DRIVER, 0},
{"dump_quirk_names", T_DUMP_QUIRK_NAMES, 0},
{"dump_device_quirks", T_DUMP_DEVICE_QUIRKS, 0},
{"dump_all_desc", T_DUMP_ALL_DESC, 0},
@@ -283,6 +286,7 @@ usage(int exitcode)
" add_quirk <quirk>" "\n"
" remove_quirk <quirk>" "\n"
" detach_kernel_driver" "\n"
+ " attach_kernel_driver" "\n"
" dump_quirk_names" "\n"
" dump_device_quirks" "\n"
" dump_all_desc" "\n"
@@ -496,6 +500,11 @@ flush_command(struct libusb20_backend *pbe, struct options *opt)
err(1, "could not detach kernel driver");
}
}
+ if (opt->got_attach_kernel_driver) {
+ if (libusb20_dev_attach_kernel_driver(pdev, opt->iface)) {
+ err(1, "could not attach kernel driver");
+ }
+ }
dump_any =
(opt->got_dump_all_desc ||
opt->got_dump_device_desc ||
@@ -688,6 +697,13 @@ main(int argc, char **argv)
opt->got_any++;
break;
+ case T_ATTACH_KERNEL_DRIVER:
+ if (opt->got_attach_kernel_driver)
+ duplicate_option(argv[n]);
+ opt->got_attach_kernel_driver = 1;
+ opt->got_any++;
+ break;
+
case T_DUMP_QUIRK_NAMES:
if (opt->got_dump_quirk_names)
duplicate_option(argv[n]);
diff --git a/usr.sbin/usbdump/usbdump.8 b/usr.sbin/usbdump/usbdump.8
index 96be9f738e2d..3c50f54d081e 100644
--- a/usr.sbin/usbdump/usbdump.8
+++ b/usr.sbin/usbdump/usbdump.8
@@ -23,7 +23,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd May 14, 2021
+.Dd July 12, 2026
.Dt USBDUMP 8
.Os
.Sh NAME
@@ -37,6 +37,7 @@
.Op Fl i Ar ifname
.Op Fl r Ar file
.Op Fl s Ar snaplen
+.Op Fl t
.Op Fl u
.Op Fl v
.Op Fl w Ar file
@@ -90,6 +91,8 @@ This option also works with the -f option.
Snapshot
.Ar snaplen
bytes from each packet.
+.It Fl t
+Do not print packet timestamps.
.It Fl u
Unbuffered output.
.It Fl v
@@ -140,7 +143,9 @@ is as follows:
The meaning of the output format elements is as follows:
.Bl -tag -width "<xfertype>"
.It <time>
-A timestamp preceding all output lines.
+A timestamp preceding all output lines, unless the
+.Fl t
+option is used.
The timestamp has the format "hh:mm:ss.frac" and is as accurate as
the kernel's clock.
.It <bus>
diff --git a/usr.sbin/usbdump/usbdump.c b/usr.sbin/usbdump/usbdump.c
index d7b084657162..36b70d3160bd 100644
--- a/usr.sbin/usbdump/usbdump.c
+++ b/usr.sbin/usbdump/usbdump.c
@@ -45,6 +45,7 @@
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
@@ -108,6 +109,7 @@ struct header_32 {
static int doexit;
static int pkt_captured;
+static bool print_time = true;
static int verbose;
static int uf_minor;
static char *i_arg;
@@ -459,13 +461,9 @@ hexdump(const uint8_t *region, uint32_t len)
static void
print_apacket(const struct header_32 *hdr, const uint8_t *ptr, int ptr_len)
{
- struct tm *tm;
struct usbpf_pkthdr up_temp;
struct usbpf_pkthdr *up;
- struct timeval tv;
- size_t len;
uint32_t x;
- char buf[64];
ptr += USBPF_HDR_LEN;
ptr_len -= USBPF_HDR_LEN;
@@ -494,15 +492,22 @@ print_apacket(const struct header_32 *hdr, const uint8_t *ptr, int ptr_len)
if (!match_filter(up->up_address, up->up_endpoint))
return;
- tv.tv_sec = hdr->ts_sec;
- tv.tv_usec = hdr->ts_usec;
- tm = localtime(&tv.tv_sec);
+ if (verbose >= 0) {
+ if (print_time) {
+ struct timeval tv;
+ char buf[64];
+ struct tm *tm;
+ size_t len;
- len = strftime(buf, sizeof(buf), "%H:%M:%S", tm);
+ tv.tv_sec = hdr->ts_sec;
+ tv.tv_usec = hdr->ts_usec;
+ tm = localtime(&tv.tv_sec);
- if (verbose >= 0) {
- printf("%.*s.%06ld usbus%d.%d %s-%s-EP=%08x,SPD=%s,NFR=%d,SLEN=%d,IVAL=%d%s%s\n",
- (int)len, buf, tv.tv_usec,
+ len = strftime(buf, sizeof(buf), "%H:%M:%S", tm);
+ printf("%.*s.%06ld ", (int)len, buf, tv.tv_usec);
+ }
+
+ printf("usbus%d.%d %s-%s-EP=%08x,SPD=%s,NFR=%d,SLEN=%d,IVAL=%d%s%s\n",
(int)up->up_busunit, (int)up->up_address,
(up->up_type == USBPF_XFERTAP_SUBMIT) ? "SUBM" : "DONE",
usb_xferstr(up->up_xfertype),
@@ -784,6 +789,7 @@ usage(void)
fprintf(stderr, FMT, "-f <unit[.endpoint]>", "Specify a device and endpoint filter");
fprintf(stderr, FMT, "-r <file>", "Read the raw packets from file");
fprintf(stderr, FMT, "-s <snaplen>", "Snapshot bytes from each packet");
+ fprintf(stderr, FMT, "-t", "Do not print packet timestamps");
fprintf(stderr, FMT, "-v", "Increase the verbose level");
fprintf(stderr, FMT, "-b <file>", "Save raw version of all recorded data to file");
fprintf(stderr, FMT, "-w <file>", "Write the raw packets to file");
@@ -811,7 +817,7 @@ main(int argc, char *argv[])
const char *optstring;
char *pp;
- optstring = "b:d:hi:r:s:uvw:f:";
+ optstring = "b:d:hi:r:s:tuvw:f:";
while ((o = getopt(argc, argv, optstring)) != -1) {
switch (o) {
case 'b':
@@ -881,6 +887,9 @@ main(int argc, char *argv[])
if (snapshot == 0)
snapshot = -1;
break;
+ case 't':
+ print_time = false;
+ break;
case 'u':
setbuf(stdout, NULL);
setbuf(stderr, NULL);
diff --git a/usr.sbin/vidcontrol/vidcontrol.c b/usr.sbin/vidcontrol/vidcontrol.c
index e4cf1e8efc53..87e779eca9f2 100644
--- a/usr.sbin/vidcontrol/vidcontrol.c
+++ b/usr.sbin/vidcontrol/vidcontrol.c
@@ -182,7 +182,7 @@ usage(void)
if (vt4_mode)
fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n",
"usage: vidcontrol [-Cx] [-b color] [-c appearance] [-f [[size] file]]",
-" [-g geometry] [-h size] [-i active | adapter | mode]",
+" [-g geometry] [-h size] [-i active | adapter]",
" [-M char] [-m on | off]",
" [-r foreground background] [-S on | off] [-s number]",
" [-T xterm | cons25] [-t N | off] [mode]",
@@ -1206,7 +1206,10 @@ show_info(char *arg)
} else if (!strcmp(arg, "adapter")) {
show_adapter_info();
} else if (!strcmp(arg, "mode")) {
- show_mode_info();
+ if (vt4_mode)
+ warnx("-i mode is not supported by vt(4)");
+ else
+ show_mode_info();
} else {
revert();
errx(1, "argument to -i must be active, adapter, or mode");
diff --git a/usr.sbin/virtual_oss/virtual_oss/int.h b/usr.sbin/virtual_oss/virtual_oss/int.h
index 974f1a51f573..3cff923b2cfb 100644
--- a/usr.sbin/virtual_oss/virtual_oss/int.h
+++ b/usr.sbin/virtual_oss/virtual_oss/int.h
@@ -122,6 +122,8 @@ struct virtual_profile {
vclient_head_t head;
char oss_name[VMAX_STRING];
char wav_name[VMAX_STRING];
+ struct cuse_dev *oss_dev;
+ struct cuse_dev *wav_dev;
uint32_t rx_filter_size;
uint32_t tx_filter_size;
double *rx_filter_data[VMAX_CHAN];
@@ -142,7 +144,6 @@ struct virtual_profile {
double rx_compressor_gain[VMAX_CHAN];
uint8_t synchronized;
uint32_t rec_delay;
- int fd_sta;
struct {
const char * host;
const char * port;
diff --git a/usr.sbin/virtual_oss/virtual_oss/main.c b/usr.sbin/virtual_oss/virtual_oss/main.c
index 6a56adbc6075..1b03e2c8cf37 100644
--- a/usr.sbin/virtual_oss/virtual_oss/main.c
+++ b/usr.sbin/virtual_oss/virtual_oss/main.c
@@ -37,6 +37,7 @@
#include <dlfcn.h>
#include <errno.h>
+#include <grp.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
@@ -1620,7 +1621,6 @@ int voss_is_recording = 1;
int voss_has_synchronization;
volatile sig_atomic_t voss_exit = 0;
-static int voss_dsp_perm = 0666;
static int voss_do_background;
static int voss_baseclone = 0;
static const char *voss_pid_path;
@@ -1640,6 +1640,10 @@ struct voss_backend *voss_tx_backend;
static int voss_dups;
static int voss_ntds;
static pthread_t *voss_tds;
+static int voss_fd_sta = -1;
+
+static gid_t voss_gid;
+static int voss_perm = 0660;
/* XXX I do not like the prefix argument... */
static struct voss_backend *
@@ -1845,7 +1849,7 @@ init_sndstat(vprofile_t *ptr)
warn("Failed to pack nvlist");
goto done;
}
- err = ioctl(ptr->fd_sta, SNDSTIOC_ADD_USER_DEVS, &arg);
+ err = ioctl(voss_fd_sta, SNDSTIOC_ADD_USER_DEVS, &arg);
free(arg.buf);
if (err != 0) {
warn("Failed to issue ioctl(SNDSTIOC_ADD_USER_DEVS)");
@@ -1858,12 +1862,22 @@ done:
nvlist_destroy(nvl);
}
+static void
+cleanup_profile(vprofile_t *pvp)
+{
+ if (pvp->oss_dev != NULL)
+ cuse_dev_destroy(pvp->oss_dev);
+ if (pvp->wav_dev != NULL)
+ cuse_dev_destroy(pvp->wav_dev);
+}
+
static const char *
dup_profile(vprofile_t *pvp, int *pamp, int pol, int rx_mute,
int tx_mute, int synchronized, int is_client)
{
vprofile_t *ptr;
struct cuse_dev *pdev;
+ const char *errstr;
int x;
rx_mute = rx_mute ? 1 : 0;
@@ -1885,7 +1899,6 @@ dup_profile(vprofile_t *pvp, int *pamp, int pol, int rx_mute,
memcpy(ptr, pvp, sizeof(*ptr));
ptr->synchronized = synchronized;
- ptr->fd_sta = -1;
TAILQ_INIT(&ptr->head);
for (x = 0; x != ptr->channels; x++) {
@@ -1918,28 +1931,31 @@ dup_profile(vprofile_t *pvp, int *pamp, int pol, int rx_mute,
/* create DSP character device */
pdev = cuse_dev_create(&vclient_oss_methods, ptr, NULL,
- 0, 0, voss_dsp_perm, ptr->oss_name);
+ 0, voss_gid, voss_perm, ptr->oss_name);
if (pdev == NULL) {
- free(ptr);
- return ("Could not create CUSE DSP device");
+ errstr = "Could not create CUSE DSP device";
+ goto err;
}
+ ptr->oss_dev = pdev;
/* register to sndstat */
- ptr->fd_sta = open("/dev/sndstat", O_WRONLY);
- if (ptr->fd_sta < 0) {
- warn("Could not open /dev/sndstat");
- } else {
- init_sndstat(ptr);
+ if (voss_fd_sta < 0) {
+ if ((voss_fd_sta = open("/dev/sndstat", O_WRONLY)) < 0) {
+ errstr = "Could not open /dev/sndstat";
+ goto err;
+ }
}
+ init_sndstat(ptr);
}
/* create WAV device */
if (ptr->wav_name[0] != 0) {
pdev = cuse_dev_create(&vclient_wav_methods, ptr, NULL,
- 0, 0, voss_dsp_perm, ptr->wav_name);
+ 0, voss_gid, voss_perm, ptr->wav_name);
if (pdev == NULL) {
- free(ptr);
- return ("Could not create CUSE WAV device");
+ errstr = "Could not create CUSE WAV device";
+ goto err;
}
+ ptr->wav_dev = pdev;
}
atomic_lock();
@@ -1974,6 +1990,13 @@ dup_profile(vprofile_t *pvp, int *pamp, int pol, int rx_mute,
init_compressor(pvp);
return (voss_httpd_start(ptr));
+
+err:
+ cleanup_profile(ptr);
+ free(ptr);
+
+ return (errstr);
+
}
static void
@@ -2543,6 +2566,13 @@ main(int argc, char **argv)
const char *ptrerr;
struct sigaction sa;
struct cuse_dev *pdev = NULL;
+ struct virtual_profile *pvp;
+ struct group *gr;
+
+ /* Devices can be used only by users who part of the audio group. */
+ if ((gr = getgrnam("audio")) == NULL)
+ err(EX_USAGE, "getgrnam");
+ voss_gid = gr->gr_gid;
TAILQ_INIT(&virtual_profile_client_head);
TAILQ_INIT(&virtual_profile_loopback_head);
@@ -2612,7 +2642,7 @@ main(int argc, char **argv)
if (voss_ctl_device[0] != 0) {
pdev = cuse_dev_create(&vctl_methods, NULL, NULL,
- 0, 0, voss_dsp_perm, voss_ctl_device);
+ 0, voss_gid, voss_perm, voss_ctl_device);
if (pdev == NULL)
errx(EX_USAGE, "Could not create '/dev/%s'", voss_ctl_device);
@@ -2628,8 +2658,20 @@ main(int argc, char **argv)
destroy_threads();
+ /* Destroy CUSE devices */
+
if (voss_ctl_device[0] != 0)
cuse_dev_destroy(pdev);
+ TAILQ_FOREACH(pvp, &virtual_profile_client_head, entry) {
+ cleanup_profile(pvp);
+ }
+ TAILQ_FOREACH(pvp, &virtual_profile_loopback_head, entry) {
+ cleanup_profile(pvp);
+ }
+
+ cuse_uninit();
+ close(voss_fd_sta);
+
return (0);
}
diff --git a/usr.sbin/virtual_oss/virtual_oss/virtual_oss.8 b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.8
index 6aa9f1289b35..04343cb94689 100644
--- a/usr.sbin/virtual_oss/virtual_oss/virtual_oss.8
+++ b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.8
@@ -23,7 +23,7 @@
.\" SUCH DAMAGE.
.\"
.\"
-.Dd September 3, 2024
+.Dd June 24, 2026
.Dt VIRTUAL_OSS 8
.Os
.Sh NAME
@@ -142,19 +142,47 @@ The sixth argument gives the amplitude, default is zero or no gain.
Set control device name.
.It Fl P Ar devname
Set playback DSP device only.
-Specifying /dev/null is magic and means no playback device.
+Specifying
+.Pa /dev/null
+is magic and means no playback device.
Specifying a
.Xr sndio 7
-device descriptor prefixed by "/dev/sndio/" is also magic, and will use a sndio
-backend rather than an OSS device.
+device descriptor prefixed by
+.Pa /dev/sndio/
+is also magic, and will use a sndio backend rather than an OSS device.
+Specifying path prefixed by
+.Pa /dev/bluetooth/
+is also magic, and will use Bluetooth audio backend rather than an OSS device.
+Suffix after
+.Pa /dev/bluetooth/
+is Bluetooth device address or alias as configured in
+.Xr bluetooth.hosts 5 .
.It Fl O Ar devname
Set playback DSP device only which acts as a master device.
-This option is used in conjunction with -R /dev/null .
+This option is used in conjunction with
+.Fl R
+.Pa /dev/null .
.It Fl R Ar devname
Set recording DSP device only.
-Specifying /dev/null is magic and means no recording device.
+Specifying
+.Pa /dev/null
+is magic and means no recording device.
+Specifying path prefixed by
+.Pa /dev/bluetooth/
+is also magic, and will use Bluetooth audio backend rather than an OSS device.
+Suffix after
+.Pa /dev/bluetooth/
+is Bluetooth device address or alias as configured in
+.Xr bluetooth.hosts 5 .
.It Fl f Ar devname
-Set both playback and recording DSP device
+Set both playback and recording DSP device.
+Specifying path prefixed by
+.Pa /dev/bluetooth/
+is magic, and will use Bluetooth audio backend rather than an OSS device.
+Suffix after
+.Pa /dev/bluetooth/
+is Bluetooth device address or alias as configured in
+.Xr bluetooth.hosts 5 .
.It Fl w Ar name
Create a WAV file format compatible companion device by given name.
This option should be specified before the -d and -l options.
@@ -164,7 +192,9 @@ Create an OSS device by given name.
Create a loopback OSS device by given name.
.It Fl L Ar name
Create a loopback OSS device which acts as a master device.
-This option is used in conjunction with -f /dev/null .
+This option is used in conjunction with
+.Fl f
+.Pa /dev/null .
.It Fl F Ar size
Set receive filter size in number of samples or <milliseconds>ms for the next
device to be created.
@@ -325,20 +355,9 @@ server:
virtual_oss -S -b 16 -C 2 -c 2 -r 48000 -s 4ms \\
-f /dev/sndio/default -d dsp
.Ed
-.Pp
-How to set intel based CPUs in performance mode:
-.Bd -literal -offset indent
-sysctl -aN | fgrep dev.hwpstate | fgrep epp | \
-while read OID
-do
-sysctl ${OID}=0
-done
-
-sysctl kern.sched.preempt_thresh=224
-.Ed
.Sh NOTES
-All character devices are created using the 0666 mode which gives
-everyone in the system access.
+Users have to be part of the audio group to access any device node created by
+.Nm .
.Sh SEE ALSO
.Xr cuse 3 ,
.Xr sound 4 ,
diff --git a/usr.sbin/virtual_oss/virtual_oss/virtual_oss.c b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.c
index 1d95c87d57b1..42a748a5e3bd 100644
--- a/usr.sbin/virtual_oss/virtual_oss/virtual_oss.c
+++ b/usr.sbin/virtual_oss/virtual_oss/virtual_oss.c
@@ -783,8 +783,8 @@ virtual_oss_process(void *arg __unused)
/* check if compressor should be applied */
voss_compressor(buffer_temp, pvp->rx_compressor_gain,
- &pvp->rx_compressor_param, samples,
- samples * src_chans, (1ULL << (pvp->bits - 1)) - 1ULL);
+ &pvp->rx_compressor_param, samples * src_chans,
+ src_chans, (1ULL << (pvp->bits - 1)) - 1ULL);
TAILQ_FOREACH(pvc, &pvp->head, entry) {
diff --git a/usr.sbin/virtual_oss/virtual_oss_cmd/command.c b/usr.sbin/virtual_oss/virtual_oss_cmd/command.c
index 6f1491470e54..c647c9469630 100644
--- a/usr.sbin/virtual_oss/virtual_oss_cmd/command.c
+++ b/usr.sbin/virtual_oss/virtual_oss_cmd/command.c
@@ -37,20 +37,11 @@
#include "virtual_oss.h"
-static void
-message(const char *fmt, ...)
-{
- va_list list;
-
- va_start(list, fmt);
- vfprintf(stderr, fmt, list);
- va_end(list);
-}
-
-static void
+static void __dead2
usage(void)
{
- message("Usage: virtual_oss_cmd /dev/vdsp.ctl [command line arguments to pass to virtual_oss]\n");
+ fprintf(stderr, "usage: %s <control_device> [virtual_oss(8) command "
+ "line options]\n", getprogname());
exit(EX_USAGE);
}
@@ -68,7 +59,7 @@ main(int argc, char **argv)
fd = open(argv[1], O_RDWR);
if (fd < 0)
- errx(EX_SOFTWARE, "Could not open '%s'", argv[1]);
+ err(EX_SOFTWARE, "Could not open control device: %s", argv[1]);
for (int x = 2; x != argc; x++) {
size_t tmp = strlen(argv[x]) + 1;
diff --git a/usr.sbin/wpa/Makefile.inc b/usr.sbin/wpa/Makefile.inc
index 1d07f557b2d2..b332ead24556 100644
--- a/usr.sbin/wpa/Makefile.inc
+++ b/usr.sbin/wpa/Makefile.inc
@@ -40,6 +40,7 @@ CFLAGS+=-DCONFIG_IEEE80211R
CFLAGS+=-DCONFIG_IEEE80211W
CFLAGS+=-DTLS_DEFAULT_CIPHERS=\"DEFAULT:!EXP:!LOW\"
CFLAGS+=-DCONFIG_DEBUG_SYSLOG
+CFLAGS+=-DCONFIG_DEBUG_FILE
CFLAGS+=-DPKCS12_FUNCS
CFLAGS+=-DCONFIG_GAS
CFLAGS+=-DCONFIG_PEERKEY
diff --git a/usr.sbin/wpa/src/utils/Makefile b/usr.sbin/wpa/src/utils/Makefile
index 061ddeaad6f8..105b0dc042da 100644
--- a/usr.sbin/wpa/src/utils/Makefile
+++ b/usr.sbin/wpa/src/utils/Makefile
@@ -22,8 +22,6 @@ SRCS= base64.c \
wpa_debug.c \
wpabuf.c
-CFLAGS+=-DCONFIG_DEBUG_FILE
-
.if ${MK_INET6} != "no"
CFLAGS+= -DCONFIG_IPV6
.endif
diff --git a/usr.sbin/zonectl/zonectl.c b/usr.sbin/zonectl/zonectl.c
index 53e5e6599597..9dc6ddfd1aae 100644
--- a/usr.sbin/zonectl/zonectl.c
+++ b/usr.sbin/zonectl/zonectl.c
@@ -288,11 +288,22 @@ zonectl_print_rz(struct disk_zone_report *report, zone_output_flags out_flags,
for (i = 0; i < report->entries_filled; i++) {
entry = &report->entries[i];
- printf("%#*jx, %*ju, %#*jx, ", field_widths[ZONE_FW_START],
+ printf("%#*jx, %*ju, ", field_widths[ZONE_FW_START],
(uintmax_t)entry->zone_start_lba,
field_widths[ZONE_FW_LEN],
- (uintmax_t)entry->zone_length, field_widths[ZONE_FW_WP],
- (uintmax_t)entry->write_pointer_lba);
+ (uintmax_t)entry->zone_length);
+ if (entry->write_pointer_lba == 0xffffffffffffffff) {
+ /*
+ * This value is reported by HDDs for conventional
+ * zones. It really means "N/A". Reported it as -1,
+ * even though it's technically unsigned, to save
+ * space.
+ */
+ printf("%*d, ", field_widths[ZONE_FW_WP], -1);
+ } else {
+ printf("%#*jx, ", field_widths[ZONE_FW_WP],
+ (uintmax_t)entry->write_pointer_lba);
+ }
switch (entry->zone_type) {
case DISK_ZONE_TYPE_CONVENTIONAL:
diff --git a/usr.sbin/zzz/Makefile b/usr.sbin/zzz/Makefile
index 4d1a2dd9dc34..6ce3e03fa6cf 100644
--- a/usr.sbin/zzz/Makefile
+++ b/usr.sbin/zzz/Makefile
@@ -1,4 +1,6 @@
-SCRIPTS=zzz.sh
+.include <src.opts.mk>
+
+PROG= zzz
MAN= zzz.8
.include <bsd.prog.mk>
diff --git a/usr.sbin/zzz/zzz.8 b/usr.sbin/zzz/zzz.8
index c2799c28e177..e7df72c1e830 100644
--- a/usr.sbin/zzz/zzz.8
+++ b/usr.sbin/zzz/zzz.8
@@ -1,63 +1,59 @@
-.\" Copyright (c) 2003 Nate Lawson
-.\" All rights reserved.
+.\"-
+.\" SPDX-License-Identifier: BSD-2-Clause
.\"
-.\" 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.
+.\" Copyright (c) 2026 The FreeBSD Foundation
.\"
-.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+.\" This documentation was written by Aymeric Wibo <obiwac@freebsd.org> under
+.\" sponsorship from the FreeBSD Foundation.
.\"
-.Dd July 13, 2003
+.Dd May 26, 2026
.Dt ZZZ 8
.Os
.Sh NAME
.Nm zzz
-.Nd suspend an ACPI or APM system
+.Nd suspend the system
.Sh SYNOPSIS
.Nm
.Sh DESCRIPTION
The
.Nm
-utility
-checks for
-.Tn ACPI
-or
-.Tn APM
-support and then suspends the system appropriately.
-For
-.Tn APM ,
-.Pp
-.Dl apm -z
-.Pp
-will be issued.
-For
-.Tn ACPI,
-the configured suspend state will be looked up, checked to see
-if it is supported and,
+utility suspends the system by writing a suspend transition request to
+.Pa /dev/power .
.Pp
-.Dl acpiconf -s <state>
-.Pp
-will be issued.
+The specific sleep method used
+.Pq e.g.\& suspend-to-idle or firmware suspend
+is determined by the
+.Va kern.power.suspend
+sysctl.
.Sh SEE ALSO
.Xr acpi 4 ,
-.Xr apm 4 ,
-.Xr acpiconf 8 ,
+.Xr acpiconf 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared as a shell script in
+.Fx 5.1 .
+It suspended the system by invoking
+.Xr acpiconf 8
+with the suspend state configured via
+.Va hw.acpi.suspend_state ,
+or
.Xr apm 8
+on systems without ACPI support.
+.Pp
+It was rewritten in C in
+.Fx 16.0
+to use the generic power management interface provided by
+.Pa /dev/power .
.Sh AUTHORS
-This manual page was written by
+The original
+.Nm
+script was written by
+.An Mark Santcroos Aq Mt marks@ripe.net .
+This manual page was originally written by
.An Nate Lawson Aq Mt njl@FreeBSD.org .
+Both the current program and manual page were written by
+.An Aymeric Wibo Aq Mt obiwac@FreeBSD.org ,
+under sponsorship from the
+.Fx
+Foundation.
diff --git a/usr.sbin/zzz/zzz.c b/usr.sbin/zzz/zzz.c
new file mode 100644
index 000000000000..a014537720cf
--- /dev/null
+++ b/usr.sbin/zzz/zzz.c
@@ -0,0 +1,47 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 The FreeBSD Foundation
+ *
+ * This software was developed by Aymeric Wibo <obiwac@freebsd.org>
+ * under sponsorship from the FreeBSD Foundation.
+ */
+
+#include <sys/power.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: zzz\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int powerfd;
+ enum power_transition trans;
+
+ (void)argv;
+ if (argc > 1)
+ usage();
+
+ powerfd = open(_PATH_DEVPOWER, O_RDWR);
+ if (powerfd < 0)
+ err(EX_OSFILE, "could not open power device");
+
+ trans = POWER_TRANSITION_SUSPEND;
+ if (ioctl(powerfd, PIOTRANSITION, &trans) != 0)
+ err(EX_IOERR, "could not request suspend transition");
+
+ return (EXIT_SUCCESS);
+}
diff --git a/usr.sbin/zzz/zzz.sh b/usr.sbin/zzz/zzz.sh
deleted file mode 100644
index dde36e7984c6..000000000000
--- a/usr.sbin/zzz/zzz.sh
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/sh
-#
-# Suspend the system using either ACPI or APM.
-# For APM, "apm -z" will be issued.
-# For ACPI, the configured suspend state will be looked up, checked to see
-# if it is supported, and "acpiconf -s <state>" will be issued.
-#
-# Mark Santcroos <marks@ripe.net>
-#
-
-PATH=/sbin:/usr/sbin:/usr/bin:/bin
-
-ACPI_SUSPEND_STATE=hw.acpi.suspend_state
-ACPI_SUPPORTED_STATES=hw.acpi.supported_sleep_state
-APM_SUSPEND_DELAY=machdep.apm_suspend_delay
-
-# Check for ACPI support
-if sysctl $ACPI_SUSPEND_STATE >/dev/null 2>&1; then
- # Get configured suspend state
- SUSPEND_STATE=$(sysctl -n $ACPI_SUSPEND_STATE)
-
- # Get list of supported suspend states
- SUPPORTED_STATES=$(sysctl -n $ACPI_SUPPORTED_STATES)
-
- # Check if the configured suspend state is supported by the system
- if echo "$SUPPORTED_STATES" | grep "$SUSPEND_STATE" >/dev/null; then
- # execute ACPI style suspend command
- exec acpiconf -s "$SUSPEND_STATE"
- else
- echo "Requested suspend state $SUSPEND_STATE is not supported."
- echo "Supported states: $SUPPORTED_STATES"
- fi
-# Check for APM support
-elif sysctl $APM_SUSPEND_DELAY >/dev/null 2>&1; then
- # Execute APM style suspend command
- exec apm -z
-else
- echo "Error: no ACPI or APM suspend support found."
-fi
-
-exit 1