diff options
Diffstat (limited to 'usr.sbin')
277 files changed, 16871 insertions, 8059 deletions
diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index a35c34ee23fc..b97c22ffeb08 100644 --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -14,6 +14,8 @@ SUBDIR= adduser \ clear_locks \ crashinfo \ cron \ + ctld \ + ctladm \ daemon \ dconschat \ devctl \ @@ -54,7 +56,6 @@ SUBDIR= adduser \ nfsuserd \ nmtree \ nologin \ - nvmfd \ pciconf \ periodic \ pnfsdscopymr \ @@ -83,6 +84,7 @@ SUBDIR= adduser \ setpmac \ smbmsg \ snapinfo \ + sndctl \ spi \ spray \ syslogd \ @@ -137,7 +139,7 @@ SUBDIR.${MK_FLOPPY}+= fdformat SUBDIR.${MK_FLOPPY}+= fdread SUBDIR.${MK_FLOPPY}+= fdwrite SUBDIR.${MK_FREEBSD_UPDATE}+= freebsd-update -SUBDIR.${MK_GSSAPI}+= gssd +SUBDIR.${MK_KERBEROS_SUPPORT}+= gssd SUBDIR.${MK_GPIO}+= gpioctl SUBDIR.${MK_HYPERV}+= hyperv SUBDIR.${MK_INET6}+= ip6addrctl @@ -152,7 +154,7 @@ SUBDIR.${MK_INET6}+= rtsold SUBDIR.${MK_INET6}+= traceroute6 SUBDIR.${MK_INETD}+= inetd SUBDIR.${MK_IPFW}+= ipfwpcap -SUBDIR.${MK_ISCSI}+= ctladm ctld iscsid +SUBDIR.${MK_ISCSI}+= iscsid SUBDIR.${MK_JAIL}+= jail SUBDIR.${MK_JAIL}+= jexec SUBDIR.${MK_JAIL}+= jls diff --git a/usr.sbin/Makefile.riscv b/usr.sbin/Makefile.riscv index a8897983059c..97a83e863f5d 100644 --- a/usr.sbin/Makefile.riscv +++ b/usr.sbin/Makefile.riscv @@ -1,2 +1,4 @@ +.if ${MK_BHYVE} != "no" SUBDIR+= bhyve SUBDIR+= bhyvectl +.endif diff --git a/usr.sbin/arp/arp.8 b/usr.sbin/arp/arp.8 index d31b2b482ba3..0a171c9e36be 100644 --- a/usr.sbin/arp/arp.8 +++ b/usr.sbin/arp/arp.8 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 13, 2020 +.Dd July 16, 2025 .Dt ARP 8 .Os .Sh NAME @@ -80,7 +80,7 @@ Generate output via .Xr libxo 3 in a selection of different human and machine readable formats. See -.Xr xo_parse_args 3 +.Xr xo_options 7 for details on command line arguments. .It Fl a The program displays or, if it is used with the @@ -183,7 +183,7 @@ character will mark the rest of the line as a comment. .Sh SEE ALSO .Xr inet 3 , .Xr libxo 3 , -.Xr xo_parse_args 3 , +.Xr xo_options 7 , .Xr arp 4 , .Xr ifconfig 8 , .Xr ndp 8 diff --git a/usr.sbin/autofs/Makefile b/usr.sbin/autofs/Makefile index 2d0c1bcb8806..b32c617cfd56 100644 --- a/usr.sbin/autofs/Makefile +++ b/usr.sbin/autofs/Makefile @@ -6,7 +6,6 @@ SRCS+= automountd.c SRCS+= autounmountd.c SRCS+= common.c SRCS+= defined.c -SRCS+= getmntopts.c SRCS+= log.c SRCS+= popen.c SRCS+= token.l @@ -18,15 +17,9 @@ MAN= automount.8 automountd.8 autounmountd.8 auto_master.5 LIBADD= util -# Needed for getmntopts.c -MOUNT= ${SRCTOP}/sbin/mount -CFLAGS+=-I${MOUNT} - LINKS= ${BINDIR}/automountd ${BINDIR}/automount LINKS+= ${BINDIR}/automountd ${BINDIR}/autounmountd -.PATH: ${MOUNT} - SUBDIR= autofs .include <bsd.prog.mk> diff --git a/usr.sbin/autofs/automount.c b/usr.sbin/autofs/automount.c index 32aa2300d094..5cd7106b1161 100644 --- a/usr.sbin/autofs/automount.c +++ b/usr.sbin/autofs/automount.c @@ -45,6 +45,7 @@ #include <fcntl.h> #include <libgen.h> #include <libutil.h> +#include <mntopts.h> #include <netdb.h> #include <signal.h> #include <stdbool.h> @@ -55,7 +56,6 @@ #include <unistd.h> #include "common.h" -#include "mntopts.h" static int unmount_by_statfs(const struct statfs *sb, bool force) diff --git a/usr.sbin/autofs/common.c b/usr.sbin/autofs/common.c index 18756752876c..2dd7c290cebc 100644 --- a/usr.sbin/autofs/common.c +++ b/usr.sbin/autofs/common.c @@ -149,10 +149,11 @@ create_directory(const char *path) error = mkdir(partial, 0755); if (error != 0 && errno != EEXIST) { log_warn("cannot create %s", partial); - return; + break; } } + free(partial); free(tofree); } diff --git a/usr.sbin/bhyve/aarch64/Makefile.inc b/usr.sbin/bhyve/aarch64/Makefile.inc index af458e719d44..7c1417ec6956 100644 --- a/usr.sbin/bhyve/aarch64/Makefile.inc +++ b/usr.sbin/bhyve/aarch64/Makefile.inc @@ -1,5 +1,6 @@ SRCS+= \ fdt.c \ + mem_aarch64.c \ rtc_pl031.c \ uart_pl011.c diff --git a/usr.sbin/bhyve/aarch64/bhyve_machdep.h b/usr.sbin/bhyve/aarch64/bhyve_machdep.h new file mode 100644 index 000000000000..2dde231091fc --- /dev/null +++ b/usr.sbin/bhyve/aarch64/bhyve_machdep.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2025 Arm Ltd + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _BHYVE_MACHDEP_H_ +#define _BHYVE_MACHDEP_H_ + +extern uint64_t *cpu_to_mpidr; +extern cpuset_t running_cpumask; + +#endif /* _BHYVE_MACHDEP_H_ */ diff --git a/usr.sbin/bhyve/aarch64/bhyverun_machdep.c b/usr.sbin/bhyve/aarch64/bhyverun_machdep.c index fbaba3128def..06fe85f96e0a 100644 --- a/usr.sbin/bhyve/aarch64/bhyverun_machdep.c +++ b/usr.sbin/bhyve/aarch64/bhyverun_machdep.c @@ -30,6 +30,8 @@ #include <sys/mman.h> #include <sys/stat.h> +#include <machine/armreg.h> + #include <assert.h> #include <err.h> #include <errno.h> @@ -41,6 +43,7 @@ #include <vmmapi.h> +#include "bhyve_machdep.h" #include "bhyverun.h" #include "config.h" #include "debug.h" @@ -73,6 +76,8 @@ #define PCIE_INTC 36 #define PCIE_INTD 37 +uint64_t *cpu_to_mpidr; + void bhyve_init_config(void) { @@ -363,6 +368,23 @@ bhyve_init_platform(struct vmctx *ctx, struct vcpu *bsp) int error; int pcie_intrs[4] = {PCIE_INTA, PCIE_INTB, PCIE_INTC, PCIE_INTD}; + cpu_to_mpidr = calloc(guest_ncpus, sizeof(*cpu_to_mpidr)); + if (cpu_to_mpidr == NULL) { + warnx("unable to allocate space for mpidr list"); + return (ENOMEM); + } + + for (uint64_t cpu = 0; cpu < (uint64_t)guest_ncpus; cpu++) { + uint64_t mpidr; + + error = vm_get_register(fbsdrun_vcpu(cpu), VM_REG_GUEST_MPIDR_EL1, + &mpidr); + assert(error == 0); +#define MPIDR_AFF_MASK (MPIDR_AFF0_MASK | MPIDR_AFF1_MASK | MPIDR_AFF2_MASK | MPIDR_AFF3_MASK) + cpu_to_mpidr[cpu] = mpidr & MPIDR_AFF_MASK; +#undef MPIDR_AFF_MASK + } + bootrom = get_config_value("bootrom"); if (bootrom == NULL) { warnx("no bootrom specified"); @@ -396,6 +418,9 @@ bhyve_init_platform(struct vmctx *ctx, struct vcpu *bsp) pci_irq_init(pcie_intrs); fdt_add_pcie(pcie_intrs); + /* Mark CPU0 as running */ + CPU_SET(0, &running_cpumask); + return (0); } diff --git a/usr.sbin/bhyve/aarch64/fdt.c b/usr.sbin/bhyve/aarch64/fdt.c index 3fb97a40c241..8832a99a6cf1 100644 --- a/usr.sbin/bhyve/aarch64/fdt.c +++ b/usr.sbin/bhyve/aarch64/fdt.c @@ -39,6 +39,7 @@ #include <vmmapi.h> #include "config.h" +#include "bhyve_machdep.h" #include "bhyverun.h" #include "fdt.h" @@ -92,7 +93,7 @@ add_cpu(void *fdt, int cpuid) fdt_begin_node(fdt, node_name); fdt_property_string(fdt, "device_type", "cpu"); fdt_property_string(fdt, "compatible", "arm,armv8"); - fdt_property_u64(fdt, "reg", cpuid); + fdt_property_u64(fdt, "reg", cpu_to_mpidr[cpuid]); fdt_property_string(fdt, "enable-method", "psci"); fdt_end_node(fdt); } diff --git a/usr.sbin/bhyve/aarch64/mem_aarch64.c b/usr.sbin/bhyve/aarch64/mem_aarch64.c new file mode 100644 index 000000000000..902d16fa7c51 --- /dev/null +++ b/usr.sbin/bhyve/aarch64/mem_aarch64.c @@ -0,0 +1,58 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * This software was developed by Konstantin Belousov <kib@FreeBSD.org> + * under sponsorship from the FreeBSD Foundation. + * + * 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> +#define _WANT_KERNEL_ERRNO 1 +#include <sys/errno.h> +#include <sys/tree.h> +#include <machine/armreg.h> +#include <machine/vmm.h> +#include <machine/vmm_instruction_emul.h> +#include <vmmapi.h> + +#include "mem.h" + +int +mmio_handle_non_backed_mem(struct vcpu *vcpu, uint64_t paddr, + struct mem_range **mr_paramp __unused) +{ + int err; + uint64_t spsr, esr; + + if (vm_get_register(vcpu, VM_REG_GUEST_CPSR, &spsr) == -1) + return (errno); + if ((spsr & PSR_M_MASK) == PSR_M_EL0t) + esr = EXCP_DATA_ABORT_L << ESR_ELx_EC_SHIFT; + else + esr = EXCP_DATA_ABORT << ESR_ELx_EC_SHIFT; + esr |= ESR_ELx_IL | ISS_DATA_DFSC_EXT; + err = vm_inject_exception(vcpu, esr, paddr); + return (err != 0 ? err : EJUSTRETURN); +} diff --git a/usr.sbin/bhyve/aarch64/vmexit.c b/usr.sbin/bhyve/aarch64/vmexit.c index 9ecf25c04e41..3acad4020a3c 100644 --- a/usr.sbin/bhyve/aarch64/vmexit.c +++ b/usr.sbin/bhyve/aarch64/vmexit.c @@ -47,6 +47,7 @@ #include <vmmapi.h> +#include "bhyve_machdep.h" #include "bhyverun.h" #include "config.h" #include "debug.h" @@ -54,7 +55,7 @@ #include "mem.h" #include "vmexit.h" -static cpuset_t running_cpumask; +cpuset_t running_cpumask; static int vmexit_inst_emul(struct vmctx *ctx __unused, struct vcpu *vcpu, @@ -151,7 +152,7 @@ vmexit_bogus(struct vmctx *ctx __unused, struct vcpu *vcpu __unused, static uint64_t smccc_affinity_info(uint64_t target_affinity, uint32_t lowest_affinity_level) { - uint64_t cpu_aff, mask = 0; + uint64_t mask = 0; switch (lowest_affinity_level) { case 0: @@ -171,13 +172,7 @@ smccc_affinity_info(uint64_t target_affinity, uint32_t lowest_affinity_level) } for (int vcpu = 0; vcpu < guest_ncpus; vcpu++) { - /* TODO: We should get this from the kernel */ - cpu_aff = (vcpu & 0xf) << MPIDR_AFF0_SHIFT | - ((vcpu >> 4) & 0xff) << MPIDR_AFF1_SHIFT | - ((vcpu >> 12) & 0xff) << MPIDR_AFF2_SHIFT | - (uint64_t)((vcpu >> 20) & 0xff) << MPIDR_AFF3_SHIFT; - - if ((cpu_aff & mask) == (target_affinity & mask) && + if ((cpu_to_mpidr[vcpu] & mask) == (target_affinity & mask) && CPU_ISSET(vcpu, &running_cpumask)) { /* Return ON if any CPUs are on */ return (PSCI_AFFINITY_INFO_ON); @@ -193,9 +188,9 @@ vmexit_smccc(struct vmctx *ctx, struct vcpu *vcpu, struct vm_run *vmrun) { struct vcpu *newvcpu; struct vm_exit *vme; - uint64_t newcpu, smccc_rv; + uint64_t mpidr, smccc_rv; enum vm_suspend_how how; - int error; + int error, newcpu; /* Return the Unknown Function Identifier by default */ smccc_rv = SMCCC_RET_NOT_SUPPORTED; @@ -207,16 +202,24 @@ vmexit_smccc(struct vmctx *ctx, struct vcpu *vcpu, struct vm_run *vmrun) smccc_rv = PSCI_VER(1, 0); break; case PSCI_FNID_CPU_SUSPEND: + break; case PSCI_FNID_CPU_OFF: + CPU_CLR_ATOMIC(vcpu_id(vcpu), &running_cpumask); + vm_suspend_cpu(vcpu); break; case PSCI_FNID_CPU_ON: - newcpu = vme->u.smccc_call.args[0]; - if (newcpu > (uint64_t)guest_ncpus) { + mpidr = vme->u.smccc_call.args[0]; + for (newcpu = 0; newcpu < guest_ncpus; newcpu++) { + if (cpu_to_mpidr[newcpu] == mpidr) + break; + } + + if (newcpu == guest_ncpus) { smccc_rv = PSCI_RETVAL_INVALID_PARAMS; break; } - if (CPU_ISSET(newcpu, &running_cpumask)) { + if (CPU_TEST_SET_ATOMIC(newcpu, &running_cpumask)) { smccc_rv = PSCI_RETVAL_ALREADY_ON; break; } @@ -235,7 +238,6 @@ vmexit_smccc(struct vmctx *ctx, struct vcpu *vcpu, struct vm_run *vmrun) assert(error == 0); vm_resume_cpu(newvcpu); - CPU_SET_ATOMIC(newcpu, &running_cpumask); smccc_rv = PSCI_RETVAL_SUCCESS; break; diff --git a/usr.sbin/bhyve/acpi.c b/usr.sbin/bhyve/acpi.c index 85864da57af2..6ff8dd8e273b 100644 --- a/usr.sbin/bhyve/acpi.c +++ b/usr.sbin/bhyve/acpi.c @@ -37,9 +37,12 @@ */ #include <sys/param.h> +#include <sys/cpuset.h> +#include <sys/domainset.h> #include <sys/endian.h> #include <sys/errno.h> #include <sys/stat.h> +#include <sys/tree.h> #include <err.h> #include <paths.h> @@ -50,7 +53,9 @@ #include <string.h> #include <unistd.h> +#include <dev/vmm/vmm_mem.h> #include <machine/vmm.h> +#include <machine/vmm_dev.h> #include <vmmapi.h> #include "bhyverun.h" @@ -79,6 +84,22 @@ static char basl_template[MAXPATHLEN]; static char basl_stemplate[MAXPATHLEN]; /* + * SRAT vCPU affinity info. + */ +struct acpi_vcpu_affinity_entry { + RB_ENTRY(acpi_vcpu_affinity_entry) entry; + int vcpuid; + int domain; +}; + +static int vcpu_affinity_cmp(struct acpi_vcpu_affinity_entry *const a1, + struct acpi_vcpu_affinity_entry *const a2); +static RB_HEAD(vcpu_affinities, + acpi_vcpu_affinity_entry) aff_head = RB_INITIALIZER(&aff_head); +RB_GENERATE_STATIC(vcpu_affinities, acpi_vcpu_affinity_entry, entry, + vcpu_affinity_cmp); + +/* * State for dsdt_line(), dsdt_indent(), and dsdt_unindent(). */ static FILE *dsdt_fp; @@ -121,6 +142,31 @@ acpi_tables_add_device(const struct acpi_device *const dev) return (0); } +static int +vcpu_affinity_cmp(struct acpi_vcpu_affinity_entry *a1, + struct acpi_vcpu_affinity_entry *a2) +{ + return (a1->vcpuid < a2->vcpuid ? -1 : a1->vcpuid > a2->vcpuid); +} + +int +acpi_add_vcpu_affinity(int vcpuid, int domain) +{ + struct acpi_vcpu_affinity_entry *entry = calloc(1, sizeof(*entry)); + if (entry == NULL) { + return (ENOMEM); + } + + entry->vcpuid = vcpuid; + entry->domain = domain; + if (RB_INSERT(vcpu_affinities, &aff_head, entry) != NULL) { + free(entry); + return (EEXIST); + } + + return (0); +} + /* * Helper routines for writing to the DSDT from other modules. */ @@ -726,6 +772,83 @@ build_spcr(struct vmctx *const ctx) return (0); } +static int +build_srat(struct vmctx *const ctx) +{ + ACPI_TABLE_SRAT srat; + ACPI_SRAT_MEM_AFFINITY srat_mem_affinity; + ACPI_SRAT_CPU_AFFINITY srat_cpu_affinity; + + struct acpi_vcpu_affinity_entry *ep; + struct basl_table *table; + int segid, domain; + int _flags, _prot; + vm_ooffset_t _off; + size_t maplen; + uint64_t gpa; + int ret; + + if (RB_EMPTY(&aff_head)) + return (0); + + memset(&srat, 0, sizeof(srat)); + BASL_EXEC(basl_table_create(&table, ctx, ACPI_SIG_SRAT, + BASL_TABLE_ALIGNMENT)); + BASL_EXEC(basl_table_append_header(table, ACPI_SIG_SRAT, 1, 1)); + srat.TableRevision = 1; + BASL_EXEC(basl_table_append_content(table, &srat, sizeof(srat))); + + /* + * Iterate over the VM's memory maps and add + * a 'Memory Affinity Structure' for each mapping. + */ + gpa = 0; + while (1) { + ret = vm_mmap_getnext(ctx, &gpa, &segid, &_off, &maplen, &_prot, + &_flags); + if (ret) { + break; + } + + if (segid >= VM_SYSMEM && segid < VM_BOOTROM) { + domain = segid - VM_SYSMEM; + } else { + /* Treat devmem segs as domain 0. */ + domain = 0; + } + memset(&srat_mem_affinity, 0, sizeof(srat_mem_affinity)); + srat_mem_affinity.Header.Type = ACPI_SRAT_TYPE_MEMORY_AFFINITY; + srat_mem_affinity.Header.Length = sizeof(srat_mem_affinity); + srat_mem_affinity.Flags |= ACPI_SRAT_MEM_ENABLED; + srat_mem_affinity.ProximityDomain = htole32(domain); + srat_mem_affinity.BaseAddress = htole64(gpa); + srat_mem_affinity.Length = htole64(maplen); + srat_mem_affinity.Flags = htole32(ACPI_SRAT_MEM_ENABLED); + BASL_EXEC(basl_table_append_bytes(table, &srat_mem_affinity, + sizeof(srat_mem_affinity))); + gpa += maplen; + } + + /* + * Iterate over each "vCPUid to domain id" mapping and emit a + * 'Processor Local APIC/SAPIC Affinity Structure' for each entry. + */ + RB_FOREACH(ep, vcpu_affinities, &aff_head) { + memset(&srat_cpu_affinity, 0, sizeof(srat_cpu_affinity)); + srat_cpu_affinity.Header.Type = ACPI_SRAT_TYPE_CPU_AFFINITY; + srat_cpu_affinity.Header.Length = sizeof(srat_cpu_affinity); + srat_cpu_affinity.ProximityDomainLo = (uint8_t)ep->domain; + srat_cpu_affinity.ApicId = (uint8_t)ep->vcpuid; + srat_cpu_affinity.Flags = htole32(ACPI_SRAT_CPU_USE_AFFINITY); + BASL_EXEC(basl_table_append_bytes(table, &srat_cpu_affinity, + sizeof(srat_cpu_affinity))); + } + + BASL_EXEC(basl_table_register_to_rsdt(table)); + + return (0); +} + int acpi_build(struct vmctx *ctx, int ncpu) { @@ -765,6 +888,7 @@ acpi_build(struct vmctx *ctx, int ncpu) BASL_EXEC(build_mcfg(ctx)); BASL_EXEC(build_facs(ctx)); BASL_EXEC(build_spcr(ctx)); + BASL_EXEC(build_srat(ctx)); /* Build ACPI device-specific tables such as a TPM2 table. */ const struct acpi_device_list_entry *entry; diff --git a/usr.sbin/bhyve/acpi.h b/usr.sbin/bhyve/acpi.h index 4b557993d67f..f4d24d63800e 100644 --- a/usr.sbin/bhyve/acpi.h +++ b/usr.sbin/bhyve/acpi.h @@ -56,7 +56,8 @@ struct vmctx; int acpi_build(struct vmctx *ctx, int ncpu); void acpi_raise_gpe(struct vmctx *ctx, unsigned bit); int acpi_tables_add_device(const struct acpi_device *const dev); -void dsdt_line(const char *fmt, ...); +int acpi_add_vcpu_affinity(int vcpuid, int domain); +void dsdt_line(const char *fmt, ...) __printflike(1, 2); void dsdt_fixed_ioport(uint16_t iobase, uint16_t length); void dsdt_fixed_irq(uint8_t irq); void dsdt_fixed_mem32(uint32_t base, uint32_t length); diff --git a/usr.sbin/bhyve/amd64/Makefile.inc b/usr.sbin/bhyve/amd64/Makefile.inc index 53320a70178b..92e53433ff01 100644 --- a/usr.sbin/bhyve/amd64/Makefile.inc +++ b/usr.sbin/bhyve/amd64/Makefile.inc @@ -7,6 +7,7 @@ SRCS+= \ inout.c \ ioapic.c \ kernemu_dev.c \ + mem_x86.c \ mptbl.c \ pci_fbuf.c \ pci_gvt-d.c \ diff --git a/usr.sbin/bhyve/amd64/bhyverun_machdep.c b/usr.sbin/bhyve/amd64/bhyverun_machdep.c index 85af124b5536..dad8f1e52e4e 100644 --- a/usr.sbin/bhyve/amd64/bhyverun_machdep.c +++ b/usr.sbin/bhyve/amd64/bhyverun_machdep.c @@ -91,6 +91,7 @@ bhyve_usage(int code) " -K: PS2 keyboard layout\n" " -l: LPC device configuration\n" " -m: memory size\n" + " -n: NUMA domain specification\n" " -o: set config 'var' to 'value'\n" " -P: vmexit from the guest on pause\n" " -p: pin 'vcpu' to 'hostcpu'\n" @@ -117,9 +118,9 @@ bhyve_optparse(int argc, char **argv) int c; #ifdef BHYVE_SNAPSHOT - optstr = "aehuwxACDHIPSWYk:f:o:p:G:c:s:m:l:K:U:r:"; + optstr = "aehuwxACDHIPSWYk:f:o:p:G:c:s:m:n:l:K:U:r:"; #else - optstr = "aehuwxACDHIPSWYk:f:o:p:G:c:s:m:l:K:U:"; + optstr = "aehuwxACDHIPSWYk:f:o:p:G:c:s:m:n:l:K:U:"; #endif while ((c = getopt(argc, argv, optstr)) != -1) { switch (c) { @@ -194,6 +195,15 @@ bhyve_optparse(int argc, char **argv) case 'm': set_config_value("memory.size", optarg); break; + case 'n': + if (bhyve_numa_parse(optarg) != 0) + errx(EX_USAGE, + "invalid NUMA configuration " + "'%s'", + optarg); + if (!get_config_bool("acpi_tables")) + errx(EX_USAGE, "NUMA emulation requires ACPI"); + break; case 'o': if (!bhyve_parse_config_option(optarg)) { errx(EX_USAGE, diff --git a/usr.sbin/bhyve/amd64/mem_x86.c b/usr.sbin/bhyve/amd64/mem_x86.c new file mode 100644 index 000000000000..742d827db47f --- /dev/null +++ b/usr.sbin/bhyve/amd64/mem_x86.c @@ -0,0 +1,82 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * This software was developed by Konstantin Belousov <kib@FreeBSD.org> + * under sponsorship from the FreeBSD Foundation. + * + * 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/errno.h> +#include <sys/tree.h> +#include <machine/vmm.h> + +#include <stdio.h> + +#include "debug.h" +#include "mem.h" + +static int +no_mem_handler(struct vcpu *vcpu __unused, int dir, uint64_t addr __unused, + int size, uint64_t *val, void *arg1 __unused, long arg2 __unused) +{ + if (dir == MEM_F_READ) { + switch (size) { + case 1: + *val = 0xff; + break; + case 2: + *val = 0xffff; + break; + case 4: + *val = 0xffffffff; + break; + case 8: + *val = 0xffffffffffffffff; + break; + } + } + return (0); +} + +static struct mem_range fb_entry = { + .handler = no_mem_handler, + .base = 0, + .size = 0xffffffffffffffff, +}; + +/* + * x86 hardware ignores writes without receiver, and returns all 1's + * from reads without response to transaction. + */ +int +mmio_handle_non_backed_mem(struct vcpu *vcpu __unused, uint64_t paddr, + struct mem_range **mr_paramp) +{ + *mr_paramp = &fb_entry; + EPRINTLN("Emulating access to non-existent address to %#lx\n", + paddr); + return (0); +} diff --git a/usr.sbin/bhyve/amd64/pci_gvt-d.c b/usr.sbin/bhyve/amd64/pci_gvt-d.c index 8cd5d21c8e6d..0ea53689f2b2 100644 --- a/usr.sbin/bhyve/amd64/pci_gvt-d.c +++ b/usr.sbin/bhyve/amd64/pci_gvt-d.c @@ -20,6 +20,7 @@ #include "amd64/e820.h" #include "pci_gvt-d-opregion.h" #include "pci_passthru.h" +#include "pciids_intel_gpus.h" #define KB (1024UL) #define MB (1024 * KB) @@ -32,13 +33,179 @@ #define PCI_VENDOR_INTEL 0x8086 #define PCIR_BDSM 0x5C /* Base of Data Stolen Memory register */ +#define PCIR_BDSM_GEN11 0xC0 #define PCIR_ASLS_CTL 0xFC /* Opregion start address register */ #define PCIM_BDSM_GSM_ALIGNMENT \ 0x00100000 /* Graphics Stolen Memory is 1 MB aligned */ +#define BDSM_GEN11_MMIO_ADDRESS 0x1080C0 + #define GVT_D_MAP_GSM 0 #define GVT_D_MAP_OPREGION 1 +#define GVT_D_MAP_VBT 2 + +static uint64_t +gvt_d_dsmbase_read(struct pci_devinst *pi, int baridx __unused, uint64_t offset, + int size) +{ + switch (size) { + case 1: + return (pci_get_cfgdata8(pi, PCIR_BDSM_GEN11 + offset)); + case 2: + return (pci_get_cfgdata16(pi, PCIR_BDSM_GEN11 + offset)); + case 4: + return (pci_get_cfgdata32(pi, PCIR_BDSM_GEN11 + offset)); + default: + return (UINT64_MAX); + } +} + +static void +gvt_d_dsmbase_write(struct pci_devinst *pi, int baridx __unused, + uint64_t offset, int size, uint64_t val) +{ + switch (size) { + case 1: + pci_set_cfgdata8(pi, PCIR_BDSM_GEN11 + offset, val); + break; + case 2: + pci_set_cfgdata16(pi, PCIR_BDSM_GEN11 + offset, val); + break; + case 4: + pci_set_cfgdata32(pi, PCIR_BDSM_GEN11 + offset, val); + break; + default: + break; + } +} + +static int +set_bdsm_gen3(struct pci_devinst *const pi, vm_paddr_t bdsm_gpa) +{ + struct passthru_softc *sc = pi->pi_arg; + uint32_t bdsm; + int error; + + bdsm = pci_host_read_config(passthru_get_sel(sc), PCIR_BDSM, 4); + + /* Protect the BDSM register in PCI space. */ + pci_set_cfgdata32(pi, PCIR_BDSM, + bdsm_gpa | (bdsm & (PCIM_BDSM_GSM_ALIGNMENT - 1))); + error = set_pcir_handler(sc, PCIR_BDSM, 4, passthru_cfgread_emulate, + passthru_cfgwrite_emulate); + if (error) { + warnx("%s: Failed to setup handler for BDSM register!", __func__); + return (error); + } + + return (0); +} + +static int +set_bdsm_gen11(struct pci_devinst *const pi, vm_paddr_t bdsm_gpa) +{ + struct passthru_softc *sc = pi->pi_arg; + uint64_t bdsm; + int error; + + bdsm = pci_host_read_config(passthru_get_sel(sc), PCIR_BDSM_GEN11, 8); + + /* Protect the BDSM register in PCI space. */ + pci_set_cfgdata32(pi, PCIR_BDSM_GEN11, + bdsm_gpa | (bdsm & (PCIM_BDSM_GSM_ALIGNMENT - 1))); + pci_set_cfgdata32(pi, PCIR_BDSM_GEN11 + 4, bdsm_gpa >> 32); + error = set_pcir_handler(sc, PCIR_BDSM_GEN11, 8, passthru_cfgread_emulate, + passthru_cfgwrite_emulate); + if (error) { + warnx("%s: Failed to setup handler for BDSM register!\n", __func__); + return (error); + } + + /* Protect the BDSM register in MMIO space. */ + error = passthru_set_bar_handler(sc, 0, BDSM_GEN11_MMIO_ADDRESS, sizeof(uint64_t), + gvt_d_dsmbase_read, gvt_d_dsmbase_write); + if (error) { + warnx("%s: Failed to setup handler for BDSM mirror!\n", __func__); + return (error); + } + + return (0); +} + +struct igd_ops { + int (*set_bdsm)(struct pci_devinst *const pi, vm_paddr_t bdsm_gpa); +}; + +static const struct igd_ops igd_ops_gen3 = { .set_bdsm = set_bdsm_gen3 }; + +static const struct igd_ops igd_ops_gen11 = { .set_bdsm = set_bdsm_gen11 }; + +struct igd_device { + uint32_t device_id; + const struct igd_ops *ops; +}; + +#define IGD_DEVICE(_device_id, _ops) \ + { \ + .device_id = (_device_id), \ + .ops = (_ops), \ + } + +static const struct igd_device igd_devices[] = { + INTEL_I915G_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_I915GM_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_I945G_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_I945GM_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_VLV_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_PNV_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_I965GM_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_GM45_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_G45_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_ILK_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_SNB_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_IVB_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_HSW_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_BDW_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_CHV_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_SKL_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_BXT_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_KBL_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_CFL_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_WHL_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_CML_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_GLK_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_CNL_IDS(IGD_DEVICE, &igd_ops_gen3), + INTEL_ICL_IDS(IGD_DEVICE, &igd_ops_gen11), + INTEL_EHL_IDS(IGD_DEVICE, &igd_ops_gen11), + INTEL_JSL_IDS(IGD_DEVICE, &igd_ops_gen11), + INTEL_TGL_IDS(IGD_DEVICE, &igd_ops_gen11), + INTEL_RKL_IDS(IGD_DEVICE, &igd_ops_gen11), + INTEL_ADLS_IDS(IGD_DEVICE, &igd_ops_gen11), + INTEL_ADLP_IDS(IGD_DEVICE, &igd_ops_gen11), + INTEL_ADLN_IDS(IGD_DEVICE, &igd_ops_gen11), + INTEL_RPLS_IDS(IGD_DEVICE, &igd_ops_gen11), + INTEL_RPLU_IDS(IGD_DEVICE, &igd_ops_gen11), + INTEL_RPLP_IDS(IGD_DEVICE, &igd_ops_gen11), +}; + +static const struct igd_ops * +get_igd_ops(struct pci_devinst *const pi) +{ + struct passthru_softc *sc = pi->pi_arg; + uint16_t device_id; + + device_id = pci_host_read_config(passthru_get_sel(sc), PCIR_DEVICE, + 0x02); + for (size_t i = 0; i < nitems(igd_devices); i++) { + if (igd_devices[i].device_id != device_id) + continue; + + return (igd_devices[i].ops); + } + + return (NULL); +} static int gvt_d_probe(struct pci_devinst *const pi) @@ -107,8 +274,8 @@ gvt_d_setup_gsm(struct pci_devinst *const pi) { struct passthru_softc *sc; struct passthru_mmio_mapping *gsm; + const struct igd_ops *igd_ops; size_t sysctl_len; - uint32_t bdsm; int error; sc = pi->pi_arg; @@ -169,12 +336,75 @@ gvt_d_setup_gsm(struct pci_devinst *const pi) "Warning: Unable to reuse host address of Graphics Stolen Memory. GPU passthrough might not work properly."); } - bdsm = pci_host_read_config(passthru_get_sel(sc), PCIR_BDSM, 4); - pci_set_cfgdata32(pi, PCIR_BDSM, - gsm->gpa | (bdsm & (PCIM_BDSM_GSM_ALIGNMENT - 1))); + igd_ops = get_igd_ops(pi); + if (igd_ops == NULL) { + warn("%s: Unknown IGD device. It's not supported yet!", + __func__); + return (-1); + } - return (set_pcir_handler(sc, PCIR_BDSM, 4, passthru_cfgread_emulate, - passthru_cfgwrite_emulate)); + return (igd_ops->set_bdsm(pi, gsm->gpa)); +} + +static int +gvt_d_setup_vbt(struct pci_devinst *const pi, int memfd, uint64_t vbt_hpa, + uint64_t vbt_len, vm_paddr_t *vbt_gpa) +{ + struct passthru_softc *sc; + struct passthru_mmio_mapping *vbt; + + sc = pi->pi_arg; + + vbt = passthru_get_mmio(sc, GVT_D_MAP_VBT); + if (vbt == NULL) { + warnx("%s: Unable to access VBT", __func__); + return (-1); + } + + vbt->hpa = vbt_hpa; + vbt->len = vbt_len; + + vbt->hva = mmap(NULL, vbt->len, PROT_READ, MAP_SHARED, memfd, vbt->hpa); + if (vbt->hva == MAP_FAILED) { + warn("%s: Unable to map VBT", __func__); + return (-1); + } + + vbt->gpa = gvt_d_alloc_mmio_memory(vbt->hpa, vbt->len, + E820_ALIGNMENT_NONE, E820_TYPE_NVS); + if (vbt->gpa == 0) { + warnx( + "%s: Unable to add VBT to E820 table (hpa 0x%lx len 0x%lx)", + __func__, vbt->hpa, vbt->len); + munmap(vbt->hva, vbt->len); + e820_dump_table(); + return (-1); + } + vbt->gva = vm_map_gpa(pi->pi_vmctx, vbt->gpa, vbt->len); + if (vbt->gva == NULL) { + warnx("%s: Unable to map guest VBT", __func__); + munmap(vbt->hva, vbt->len); + return (-1); + } + + if (vbt->gpa != vbt->hpa) { + /* + * A 1:1 host to guest mapping is not required but this could + * change in the future. + */ + warnx( + "Warning: Unable to reuse host address of VBT. GPU passthrough might not work properly."); + } + + memcpy(vbt->gva, vbt->hva, vbt->len); + + /* + * Return the guest physical address. It's used to patch the OpRegion + * properly. + */ + *vbt_gpa = vbt->gpa; + + return (0); } static int @@ -182,8 +412,12 @@ gvt_d_setup_opregion(struct pci_devinst *const pi) { struct passthru_softc *sc; struct passthru_mmio_mapping *opregion; + struct igd_opregion *opregion_ptr; struct igd_opregion_header *header; + vm_paddr_t vbt_gpa = 0; + vm_paddr_t vbt_hpa; uint64_t asls; + int error = 0; int memfd; sc = pi->pi_arg; @@ -236,6 +470,38 @@ gvt_d_setup_opregion(struct pci_devinst *const pi) close(memfd); return (-1); } + + opregion_ptr = (struct igd_opregion *)opregion->hva; + if (opregion_ptr->mbox3.rvda != 0) { + /* + * OpRegion v2.0 contains a physical address to the VBT. This + * address is useless in a guest environment. It's possible to + * patch that but we don't support that yet. So, the only thing + * we can do is give up. + */ + if (opregion_ptr->header.over == 0x02000000) { + warnx( + "%s: VBT lays outside OpRegion. That's not yet supported for a version 2.0 OpRegion", + __func__); + close(memfd); + return (-1); + } + vbt_hpa = opregion->hpa + opregion_ptr->mbox3.rvda; + if (vbt_hpa < opregion->hpa) { + warnx( + "%s: overflow when calculating VBT address (OpRegion @ 0x%lx, RVDA = 0x%lx)", + __func__, opregion->hpa, opregion_ptr->mbox3.rvda); + close(memfd); + return (-1); + } + + if ((error = gvt_d_setup_vbt(pi, memfd, vbt_hpa, + opregion_ptr->mbox3.rvds, &vbt_gpa)) != 0) { + close(memfd); + return (error); + } + } + close(memfd); opregion->gpa = gvt_d_alloc_mmio_memory(opregion->hpa, opregion->len, @@ -263,6 +529,20 @@ gvt_d_setup_opregion(struct pci_devinst *const pi) memcpy(opregion->gva, opregion->hva, opregion->len); + /* + * Patch the VBT address to match our guest physical address. + */ + if (vbt_gpa != 0) { + if (vbt_gpa < opregion->gpa) { + warnx( + "%s: invalid guest VBT address 0x%16lx (OpRegion @ 0x%16lx)", + __func__, vbt_gpa, opregion->gpa); + return (-1); + } + + ((struct igd_opregion *)opregion->gva)->mbox3.rvda = vbt_gpa - opregion->gpa; + } + pci_set_cfgdata32(pi, PCIR_ASLS_CTL, opregion->gpa); return (set_pcir_handler(sc, PCIR_ASLS_CTL, 4, passthru_cfgread_emulate, diff --git a/usr.sbin/bhyve/amd64/xmsr.c b/usr.sbin/bhyve/amd64/xmsr.c index cd80e4ef782e..7c174728f4fa 100644 --- a/usr.sbin/bhyve/amd64/xmsr.c +++ b/usr.sbin/bhyve/amd64/xmsr.c @@ -204,6 +204,15 @@ emulate_rdmsr(struct vcpu *vcpu __unused, uint32_t num, uint64_t *val) *val = 1; break; + case MSR_VM_CR: + /* + * We currently don't support nested virt. + * Windows seems to ignore the cpuid bits and reads this + * MSR anyways. + */ + *val = VM_CR_SVMDIS; + break; + default: error = -1; break; diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8 index 62e567fd359d..89c0b23961a8 100644 --- a/usr.sbin/bhyve/bhyve.8 +++ b/usr.sbin/bhyve/bhyve.8 @@ -269,8 +269,56 @@ or (either upper or lower case) to indicate a multiple of kilobytes, megabytes, gigabytes, or terabytes. If no suffix is given, the value is assumed to be in megabytes. -.Pp The default is 256M. +.Pp +.It Fl n Ar id Ns Cm \&, Ns Ar size Ns Cm \&, Ns Ar cpus Ns Op Cm \&, Ns Ar domain_policy +Configure guest NUMA domains. +This option applies only to the amd64 platform. +.Pp +The +.Fl n +option allows the guest physical address space to be partitioned into domains. +The layout of each domain is encoded in an ACPI table +visible to the guest operating system. +The +.Fl n +option also allows the specification of a +.Xr domainset 9 +memory allocation policy for the host memory backing a given NUMA domain. +A guest can have up to 8 NUMA domains. +This feature requires that the guest use a boot ROM, and in +particular cannot be used if the guest was initialized using +.Xr bhyveload 8 . +.Pp +Each domain is identified by a numerical +.Em id . +The domain memory +.Em size +is specified using the same format as the +.Fl m +flag. +The sum of all +.Em size +parameters overrides the total VM memory size specified by the +.Fl m +flag. +However, if at least one domain memory size parameter is +missing, the total VM memory size will be equally distributed across +all emulated domains. +The +.Em cpuset +parameter specifies the set of CPUs that are part of the domain. +The +.Em domain_policy +parameter may be optionally used to configure the +.Xr domainset 9 +host NUMA memory allocation policy for an emulated +domain. +See the +.Ar -n +flag in +.Xr cpuset 1 +for a list of valid NUMA memory allocation policies and their formats. .It Fl o Ar var Ns Cm = Ns Ar value Set the configuration variable .Ar var @@ -1202,6 +1250,33 @@ using this configuration file, use flag .Bd -literal -offset indent /usr/sbin/bhyve -k configfile vm0 .Ed +.Pp +Run a UEFI virtual machine with four CPUs and two emulated NUMA domains: +.Bd -literal -offset indent +bhyve -c 4 -w -H \\ + -s 0,hostbridge \\ + -s 4,ahci-hd,disk.img \\ + -s 31,lpc -l com1,stdio \\ + -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd \\ + -n id=0,size=4G,cpus=0-1 \\ + -n id=1,size=4G,cpus=2-3 \\ + numavm +.Ed +.Pp +Assuming a host machine with two NUMA domains, +run a UEFI virtual machine with four CPUs using a +.Ar prefer +.Xr domainset 9 +policy to allocate guest memory from the first host NUMA domain only. +.Bd -literal -offset indent +bhyve -c 2 -w -H \\ + -s 0,hostbridge \\ + -s 4,ahci-hd,disk.img \\ + -s 31,lpc -l com1,stdio \\ + -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd \\ + -n id=0,size=4G,cpus=0-1,domain_policy=prefer:0 \\ + numavm +.Ed .Sh SEE ALSO .Xr bhyve 4 , .Xr netgraph 4 , @@ -1211,7 +1286,8 @@ using this configuration file, use flag .Xr bhyve_config 5 , .Xr ethers 5 , .Xr bhyvectl 8 , -.Xr bhyveload 8 +.Xr bhyveload 8 , +.Xr domainset 9 .Pp .Rs .%A Intel diff --git a/usr.sbin/bhyve/bhyverun.c b/usr.sbin/bhyve/bhyverun.c index be9cd1611700..9ead49582a7d 100644 --- a/usr.sbin/bhyve/bhyverun.c +++ b/usr.sbin/bhyve/bhyverun.c @@ -30,6 +30,8 @@ #ifndef WITHOUT_CAPSICUM #include <sys/capsicum.h> #endif +#include <sys/cpuset.h> +#include <sys/domainset.h> #include <sys/mman.h> #ifdef BHYVE_SNAPSHOT #include <sys/socket.h> @@ -54,6 +56,7 @@ #include <fcntl.h> #endif #include <libgen.h> +#include <libutil.h> #include <unistd.h> #include <assert.h> #include <pthread.h> @@ -68,6 +71,7 @@ #include <libxo/xo.h> #endif +#include <dev/vmm/vmm_mem.h> #include <vmmapi.h> #include "acpi.h" @@ -108,6 +112,9 @@ static const int BSP = 0; static cpuset_t cpumask; +static struct vm_mem_domain guest_domains[VM_MAXMEMDOM]; +static int guest_ndomains = 0; + static void vm_loop(struct vmctx *ctx, struct vcpu *vcpu); static struct vcpu_info { @@ -179,6 +186,118 @@ parse_int_value(const char *key, const char *value, int minval, int maxval) return (lval); } +int +bhyve_numa_parse(const char *opt) +{ + int id = -1; + nvlist_t *nvl; + char *cp, *str, *tofree; + char pathbuf[64] = { 0 }; + char *size = NULL, *cpus = NULL, *domain_policy = NULL; + + if (*opt == '\0') { + return (-1); + } + + tofree = str = strdup(opt); + if (str == NULL) + errx(4, "Failed to allocate memory"); + + while ((cp = strsep(&str, ",")) != NULL) { + if (strncmp(cp, "id=", strlen("id=")) == 0) + id = parse_int_value("id", cp + strlen("id="), 0, + UINT8_MAX); + else if (strncmp(cp, "size=", strlen("size=")) == 0) + size = cp + strlen("size="); + else if (strncmp(cp, + "domain_policy=", strlen("domain_policy=")) == 0) + domain_policy = cp + strlen("domain_policy="); + else if (strncmp(cp, "cpus=", strlen("cpus=")) == 0) + cpus = cp + strlen("cpus="); + } + + if (id == -1) { + EPRINTLN("Missing NUMA domain ID in '%s'", opt); + goto out; + } + + snprintf(pathbuf, sizeof(pathbuf), "domains.%d", id); + nvl = find_config_node(pathbuf); + if (nvl == NULL) + nvl = create_config_node(pathbuf); + if (size != NULL) + set_config_value_node(nvl, "size", size); + if (domain_policy != NULL) + set_config_value_node(nvl, "domain_policy", domain_policy); + if (cpus != NULL) + set_config_value_node(nvl, "cpus", cpus); + + free(tofree); + return (0); + +out: + free(tofree); + return (-1); +} + +static void +calc_mem_affinity(size_t vm_memsize) +{ + int i; + nvlist_t *nvl; + bool need_recalc; + const char *value; + struct vm_mem_domain *dom; + char pathbuf[64] = { 0 }; + + need_recalc = false; + for (i = 0; i < VM_MAXMEMDOM; i++) { + dom = &guest_domains[i]; + snprintf(pathbuf, sizeof(pathbuf), "domains.%d", i); + nvl = find_config_node(pathbuf); + if (nvl == NULL) { + break; + } + + value = get_config_value_node(nvl, "size"); + need_recalc |= value == NULL; + if (value != NULL && vm_parse_memsize(value, &dom->size)) { + errx(EX_USAGE, "invalid memsize for domain %d: '%s'", i, + value); + } + + dom->ds_mask = calloc(1, sizeof(domainset_t)); + if (dom->ds_mask == NULL) { + errx(EX_OSERR, "Failed to allocate domainset mask"); + } + dom->ds_size = sizeof(domainset_t); + value = get_config_value_node(nvl, "domain_policy"); + if (value == NULL) { + dom->ds_policy = DOMAINSET_POLICY_INVALID; + DOMAINSET_ZERO(dom->ds_mask); + } else if (domainset_parselist(value, dom->ds_mask, &dom->ds_policy) != + CPUSET_PARSE_OK) { + errx(EX_USAGE, "failed to parse domain policy '%s'", value); + } + } + + guest_ndomains = i; + if (guest_ndomains == 0) { + /* + * No domains were specified - create domain + * 0 holding all CPUs and memory. + */ + guest_ndomains = 1; + guest_domains[0].size = vm_memsize; + } else if (need_recalc) { + warnx("At least one domain memory size was not specified, distributing" + " total VM memory size across all domains"); + for (i = 0; i < guest_ndomains; i++) { + guest_domains[i].size = vm_memsize / guest_ndomains; + } + } +} + /* * Set the sockets, cores, threads, and guest_cpus variables based on * the configured topology. @@ -340,6 +459,56 @@ build_vcpumaps(void) } } +static void +set_vcpu_affinities(void) +{ + int cpu, error; + nvlist_t *nvl = NULL; + cpuset_t cpus; + const char *value; + char pathbuf[64] = { 0 }; + + for (int dom = 0; dom < guest_ndomains; dom++) { + snprintf(pathbuf, sizeof(pathbuf), "domains.%d", dom); + nvl = find_config_node(pathbuf); + if (nvl == NULL) + break; + + value = get_config_value_node(nvl, "cpus"); + if (value == NULL) { + EPRINTLN("Missing CPU set for domain %d", dom); + exit(4); + } + + parse_cpuset(dom, value, &cpus); + CPU_FOREACH_ISSET(cpu, &cpus) { + error = acpi_add_vcpu_affinity(cpu, dom); + if (error) { + EPRINTLN( + "Unable to set vCPU %d affinity for domain %d: %s", + cpu, dom, strerror(errno)); + exit(4); + } + } + } + if (guest_ndomains > 1 || nvl != NULL) + return; + + /* + * If we're dealing with one domain and no cpuset was provided, create a + * default one holding all cpus. + */ + for (cpu = 0; cpu < guest_ncpus; cpu++) { + error = acpi_add_vcpu_affinity(cpu, 0); + if (error) { + EPRINTLN( + "Unable to set vCPU %d affinity for domain %d: %s", + cpu, 0, strerror(errno)); + exit(4); + } + } +} + void * paddr_guest2host(struct vmctx *ctx, uintptr_t gaddr, size_t len) { @@ -713,18 +882,21 @@ main(int argc, char *argv[]) vcpu_info[vcpuid].vcpu = vm_vcpu_open(ctx, vcpuid); } + calc_mem_affinity(memsize); memflags = 0; if (get_config_bool_default("memory.wired", false)) memflags |= VM_MEM_F_WIRED; if (get_config_bool_default("memory.guest_in_core", false)) memflags |= VM_MEM_F_INCORE; vm_set_memflags(ctx, memflags); - error = vm_setup_memory(ctx, memsize, VM_MMAP_ALL); + error = vm_setup_memory_domains(ctx, VM_MMAP_ALL, guest_domains, + guest_ndomains); if (error) { fprintf(stderr, "Unable to setup memory (%d)\n", errno); exit(4); } + set_vcpu_affinities(); init_mem(guest_ncpus); init_bootrom(ctx); if (bhyve_init_platform(ctx, bsp) != 0) diff --git a/usr.sbin/bhyve/bhyverun.h b/usr.sbin/bhyve/bhyverun.h index 005de6dc5410..0a7bbd72a19c 100644 --- a/usr.sbin/bhyve/bhyverun.h +++ b/usr.sbin/bhyve/bhyverun.h @@ -73,6 +73,7 @@ void bhyve_parse_gdb_options(const char *opt); #endif int bhyve_pincpu_parse(const char *opt); int bhyve_topology_parse(const char *opt); +int bhyve_numa_parse(const char *opt); void bhyve_init_vcpu(struct vcpu *vcpu); void bhyve_start_vcpu(struct vcpu *vcpu, bool bsp); diff --git a/usr.sbin/bhyve/bootrom.c b/usr.sbin/bhyve/bootrom.c index e4adaca55947..339974cb2017 100644 --- a/usr.sbin/bhyve/bootrom.c +++ b/usr.sbin/bhyve/bootrom.c @@ -31,6 +31,7 @@ #include <sys/mman.h> #include <sys/stat.h> +#include <dev/vmm/vmm_mem.h> #include <machine/vmm.h> #include <err.h> diff --git a/usr.sbin/bhyve/mem.c b/usr.sbin/bhyve/mem.c index b0c09dbf5ddf..2d7d012d084b 100644 --- a/usr.sbin/bhyve/mem.c +++ b/usr.sbin/bhyve/mem.c @@ -33,6 +33,7 @@ */ #include <sys/types.h> +#define _WANT_KERNEL_ERRNO 1 #include <sys/errno.h> #include <sys/tree.h> #include <machine/vmm.h> @@ -167,10 +168,13 @@ static int access_memory(struct vcpu *vcpu, uint64_t paddr, mem_cb_t *cb, void *arg) { struct mmio_rb_range *entry; + struct mem_range *mr; int err, perror, immutable, vcpuid; vcpuid = vcpu_id(vcpu); + mr = NULL; pthread_rwlock_rdlock(&mmio_rwlock); + /* * First check the per-vCPU cache */ @@ -185,14 +189,22 @@ access_memory(struct vcpu *vcpu, uint64_t paddr, mem_cb_t *cb, void *arg) if (mmio_rb_lookup(&mmio_rb_root, paddr, &entry) == 0) { /* Update the per-vCPU cache */ mmio_hint[vcpuid] = entry; - } else if (mmio_rb_lookup(&mmio_rb_fallback, paddr, &entry)) { - perror = pthread_rwlock_unlock(&mmio_rwlock); - assert(perror == 0); - return (ESRCH); + } else if (mmio_rb_lookup(&mmio_rb_fallback, paddr, + &entry) == 0) { + } else { + err = mmio_handle_non_backed_mem(vcpu, paddr, &mr); + if (err != 0) { + perror = pthread_rwlock_unlock(&mmio_rwlock); + assert(perror == 0); + return (err == EJUSTRETURN ? 0 : err); + } } } - assert(entry != NULL); + if (mr == NULL) { + assert(entry != NULL); + mr = &entry->mr_param; + } /* * An 'immutable' memory range is guaranteed to be never removed @@ -205,13 +217,13 @@ access_memory(struct vcpu *vcpu, uint64_t paddr, mem_cb_t *cb, void *arg) * deadlock on 'mmio_rwlock'. However by registering the extended * config space window as 'immutable' the deadlock can be avoided. */ - immutable = (entry->mr_param.flags & MEM_F_IMMUTABLE); + immutable = (mr->flags & MEM_F_IMMUTABLE) != 0; if (immutable) { perror = pthread_rwlock_unlock(&mmio_rwlock); assert(perror == 0); } - err = cb(vcpu, paddr, &entry->mr_param, arg); + err = cb(vcpu, paddr, mr, arg); if (!immutable) { perror = pthread_rwlock_unlock(&mmio_rwlock); diff --git a/usr.sbin/bhyve/mem.h b/usr.sbin/bhyve/mem.h index 90172d1c9124..d7544a6bce30 100644 --- a/usr.sbin/bhyve/mem.h +++ b/usr.sbin/bhyve/mem.h @@ -53,6 +53,8 @@ struct mem_range { void init_mem(int ncpu); int emulate_mem(struct vcpu *vcpu, uint64_t paddr, struct vie *vie, struct vm_guest_paging *paging); +int mmio_handle_non_backed_mem(struct vcpu *vcpu __unused, uint64_t paddr, + struct mem_range **mr_paramp); int read_mem(struct vcpu *vpu, uint64_t gpa, uint64_t *rval, int size); int register_mem(struct mem_range *memp); diff --git a/usr.sbin/bhyve/mem_md.c b/usr.sbin/bhyve/mem_md.c new file mode 100644 index 000000000000..0e29ec0a9de9 --- /dev/null +++ b/usr.sbin/bhyve/mem_md.c @@ -0,0 +1,44 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * This software was developed by Konstantin Belousov <kib@FreeBSD.org> + * under sponsorship from the FreeBSD Foundation. + * + * 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/errno.h> +#include <sys/tree.h> +#include <machine/vmm.h> +#include <machine/vmm_instruction_emul.h> + +#include "mem.h" + +int +mmio_handle_non_backed_mem(struct vcpu *vcpu __unused, uint64_t paddr __unused, + struct mem_range **mr_paramp __unused) +{ + return (ESRCH); +} diff --git a/usr.sbin/bhyve/pci_emul.c b/usr.sbin/bhyve/pci_emul.c index 2f04a488d9c1..9d6060e3e254 100644 --- a/usr.sbin/bhyve/pci_emul.c +++ b/usr.sbin/bhyve/pci_emul.c @@ -42,6 +42,7 @@ #include <stdbool.h> #include <sysexits.h> +#include <dev/vmm/vmm_mem.h> #include <machine/vmm.h> #include <machine/vmm_snapshot.h> #include <vmmapi.h> diff --git a/usr.sbin/bhyve/pci_fbuf.c b/usr.sbin/bhyve/pci_fbuf.c index 125428e0b772..1e3ec77c15b0 100644 --- a/usr.sbin/bhyve/pci_fbuf.c +++ b/usr.sbin/bhyve/pci_fbuf.c @@ -29,6 +29,7 @@ #include <sys/types.h> #include <sys/mman.h> +#include <dev/vmm/vmm_mem.h> #include <machine/vmm.h> #include <machine/vmm_snapshot.h> #include <vmmapi.h> diff --git a/usr.sbin/bhyve/pci_passthru.c b/usr.sbin/bhyve/pci_passthru.c index 61983010192a..8ddcd8bd56e8 100644 --- a/usr.sbin/bhyve/pci_passthru.c +++ b/usr.sbin/bhyve/pci_passthru.c @@ -38,6 +38,7 @@ #include <dev/io/iodev.h> #include <dev/pci/pcireg.h> +#include <dev/vmm/vmm_mem.h> #include <vm/vm.h> @@ -72,12 +73,20 @@ #define MSIX_TABLE_COUNT(ctrl) (((ctrl) & PCIM_MSIXCTRL_TABLE_SIZE) + 1) #define MSIX_CAPLEN 12 -#define PASSTHRU_MMIO_MAX 2 +#define PASSTHRU_MMIO_MAX 3 static int pcifd = -1; SET_DECLARE(passthru_dev_set, struct passthru_dev); +struct passthru_bar_handler { + TAILQ_ENTRY(passthru_bar_handler) chain; + uint64_t off; + uint64_t size; + passthru_read_handler read; + passthru_write_handler write; +}; + struct passthru_softc { struct pci_devinst *psc_pi; /* ROM is handled like a BAR */ @@ -95,6 +104,9 @@ struct passthru_softc { struct passthru_mmio_mapping psc_mmio_map[PASSTHRU_MMIO_MAX]; cfgread_handler psc_pcir_rhandler[PCI_REGMAX + 1]; cfgwrite_handler psc_pcir_whandler[PCI_REGMAX + 1]; + + TAILQ_HEAD(, + passthru_bar_handler) psc_bar_handler[PCI_BARMAX_WITH_ROM + 1]; }; static int @@ -740,6 +752,45 @@ set_pcir_handler(struct passthru_softc *sc, int reg, int len, return (0); } +int +passthru_set_bar_handler(struct passthru_softc *sc, int baridx, uint64_t off, + uint64_t size, passthru_read_handler rhandler, + passthru_write_handler whandler) +{ + struct passthru_bar_handler *handler_new; + struct passthru_bar_handler *handler; + + assert(sc->psc_bar[baridx].type == PCIBAR_IO || + sc->psc_bar[baridx].type == PCIBAR_MEM32 || + sc->psc_bar[baridx].type == PCIBAR_MEM64); + assert(sc->psc_bar[baridx].size >= off + size); + assert(off < off + size); + + handler_new = malloc(sizeof(struct passthru_bar_handler)); + if (handler_new == NULL) { + return (ENOMEM); + } + + handler_new->off = off; + handler_new->size = size; + handler_new->read = rhandler; + handler_new->write = whandler; + + TAILQ_FOREACH(handler, &sc->psc_bar_handler[baridx], chain) { + if (handler->off < handler_new->off) { + assert(handler->off + handler->size < handler_new->off); + continue; + } + assert(handler->off > handler_new->off + handler_new->size); + TAILQ_INSERT_BEFORE(handler, handler_new, chain); + return (0); + } + + TAILQ_INSERT_TAIL(&sc->psc_bar_handler[baridx], handler_new, chain); + + return (0); +} + static int passthru_legacy_config(nvlist_t *nvl, const char *opts) { @@ -946,6 +997,9 @@ passthru_init(struct pci_devinst *pi, nvlist_t *nvl) pi->pi_arg = sc; sc->psc_pi = pi; + for (uint8_t i = 0; i < PCI_BARMAX_WITH_ROM + 1; ++i) + TAILQ_INIT(&sc->psc_bar_handler[i]); + /* initialize config space */ if ((error = cfginit(pi, bus, slot, func)) != 0) goto done; @@ -1165,6 +1219,7 @@ passthru_write(struct pci_devinst *pi, int baridx, uint64_t offset, int size, uint64_t value) { struct passthru_softc *sc; + struct passthru_bar_handler *handler; struct pci_bar_ioreq pio; sc = pi->pi_arg; @@ -1172,9 +1227,27 @@ passthru_write(struct pci_devinst *pi, int baridx, uint64_t offset, int size, if (baridx == pci_msix_table_bar(pi)) { msix_table_write(sc, offset, size, value); } else { - assert(pi->pi_bar[baridx].type == PCIBAR_IO); assert(size == 1 || size == 2 || size == 4); - assert(offset <= UINT32_MAX && offset + size <= UINT32_MAX); + + TAILQ_FOREACH(handler, &sc->psc_bar_handler[baridx], chain) { + if (offset >= handler->off + handler->size) { + continue; + } else if (offset < handler->off) { + assert(offset + size < handler->off); + /* + * The list is sorted in ascending order, so all + * remaining handlers will have an even larger + * offset. + */ + break; + } + + assert(offset + size <= handler->off + handler->size); + + handler->write(pi, baridx, + offset - handler->off, size, value); + return; + } bzero(&pio, sizeof(pio)); pio.pbi_sel = sc->psc_sel; @@ -1192,6 +1265,7 @@ static uint64_t passthru_read(struct pci_devinst *pi, int baridx, uint64_t offset, int size) { struct passthru_softc *sc; + struct passthru_bar_handler *handler; struct pci_bar_ioreq pio; uint64_t val; @@ -1200,9 +1274,26 @@ passthru_read(struct pci_devinst *pi, int baridx, uint64_t offset, int size) if (baridx == pci_msix_table_bar(pi)) { val = msix_table_read(sc, offset, size); } else { - assert(pi->pi_bar[baridx].type == PCIBAR_IO); assert(size == 1 || size == 2 || size == 4); - assert(offset <= UINT32_MAX && offset + size <= UINT32_MAX); + + TAILQ_FOREACH(handler, &sc->psc_bar_handler[baridx], chain) { + if (offset >= handler->off + handler->size) { + continue; + } else if (offset < handler->off) { + assert(offset + size < handler->off); + /* + * The list is sorted in ascending order, so all + * remaining handlers will have an even larger + * offset. + */ + break; + } + + assert(offset + size <= handler->off + handler->size); + + return (handler->read(pi, baridx, + offset - handler->off, size)); + } bzero(&pio, sizeof(pio)); pio.pbi_sel = sc->psc_sel; @@ -1271,27 +1362,64 @@ passthru_msix_addr(struct pci_devinst *pi, int baridx, int enabled, } } -static void -passthru_mmio_addr(struct pci_devinst *pi, int baridx, int enabled, - uint64_t address) +static int +passthru_mmio_map(struct pci_devinst *pi, int baridx, int enabled, + uint64_t address, uint64_t off, uint64_t size) { struct passthru_softc *sc; sc = pi->pi_arg; if (!enabled) { if (vm_unmap_pptdev_mmio(pi->pi_vmctx, sc->psc_sel.pc_bus, - sc->psc_sel.pc_dev, - sc->psc_sel.pc_func, address, - sc->psc_bar[baridx].size) != 0) + sc->psc_sel.pc_dev, sc->psc_sel.pc_func, address + off, + size) != 0) { warnx("pci_passthru: unmap_pptdev_mmio failed"); + return (-1); + } } else { if (vm_map_pptdev_mmio(pi->pi_vmctx, sc->psc_sel.pc_bus, - sc->psc_sel.pc_dev, - sc->psc_sel.pc_func, address, - sc->psc_bar[baridx].size, - sc->psc_bar[baridx].addr) != 0) + sc->psc_sel.pc_dev, sc->psc_sel.pc_func, address + off, + size, sc->psc_bar[baridx].addr + off) != 0) { warnx("pci_passthru: map_pptdev_mmio failed"); + return (-1); + } } + + return (0); +} + +static void +passthru_mmio_addr(struct pci_devinst *pi, int baridx, int enabled, + uint64_t address) +{ + struct passthru_softc *sc; + struct passthru_bar_handler *handler; + uint64_t off; + + sc = pi->pi_arg; + + off = 0; + + /* The queue is sorted by offset in ascending order. */ + TAILQ_FOREACH(handler, &sc->psc_bar_handler[baridx], chain) { + uint64_t handler_off = trunc_page(handler->off); + uint64_t handler_end = round_page(handler->off + handler->size); + + /* + * When two handlers point to the same page, handler_off can be + * lower than off. That's fine because we have nothing to do in + * that case. + */ + if (handler_off > off) { + passthru_mmio_map(pi, baridx, enabled, address, off, + handler_off - off); + } + + off = handler_end; + } + + passthru_mmio_map(pi, baridx, enabled, address, off, + sc->psc_bar[baridx].size - off); } static void diff --git a/usr.sbin/bhyve/pci_passthru.h b/usr.sbin/bhyve/pci_passthru.h index a89ad287cbc5..9e7b27d95735 100644 --- a/usr.sbin/bhyve/pci_passthru.h +++ b/usr.sbin/bhyve/pci_passthru.h @@ -35,6 +35,10 @@ typedef int (*cfgread_handler)(struct passthru_softc *sc, struct pci_devinst *pi, int coff, int bytes, uint32_t *rv); typedef int (*cfgwrite_handler)(struct passthru_softc *sc, struct pci_devinst *pi, int coff, int bytes, uint32_t val); +typedef uint64_t (*passthru_read_handler)(struct pci_devinst *pi, int baridx, + uint64_t offset, int size); +typedef void (*passthru_write_handler)(struct pci_devinst *pi, int baridx, uint64_t offset, + int size, uint64_t val); uint32_t pci_host_read_config(const struct pcisel *sel, long reg, int width); void pci_host_write_config(const struct pcisel *sel, long reg, int width, @@ -49,3 +53,6 @@ struct passthru_mmio_mapping *passthru_get_mmio(struct passthru_softc *sc, struct pcisel *passthru_get_sel(struct passthru_softc *sc); int set_pcir_handler(struct passthru_softc *sc, int reg, int len, cfgread_handler rhandler, cfgwrite_handler whandler); +int passthru_set_bar_handler(struct passthru_softc *sc, int baridx, + uint64_t off, uint64_t size, passthru_read_handler rhandler, + passthru_write_handler whandler); diff --git a/usr.sbin/bhyve/pci_xhci.c b/usr.sbin/bhyve/pci_xhci.c index 5b21361f2823..ff12e40359e2 100644 --- a/usr.sbin/bhyve/pci_xhci.c +++ b/usr.sbin/bhyve/pci_xhci.c @@ -406,7 +406,7 @@ pci_xhci_usbcmd_write(struct pci_xhci_softc *sc, uint32_t cmd) * XHCI 4.19.3 USB2 RxDetect->Polling, * USB3 Polling->U0 */ - if (dev->dev_ue->ue_usbver == 2) + if (dev->hci.hci_usbver == 2) port->portsc |= XHCI_PS_PLS_SET(UPS_PORT_LS_POLL); else @@ -2588,9 +2588,9 @@ pci_xhci_reset_port(struct pci_xhci_softc *sc, int portn, int warm) if (dev) { port->portsc &= ~(XHCI_PS_PLS_MASK | XHCI_PS_PR | XHCI_PS_PRC); port->portsc |= XHCI_PS_PED | - XHCI_PS_SPEED_SET(dev->dev_ue->ue_usbspeed); + XHCI_PS_SPEED_SET(dev->hci.hci_speed); - if (warm && dev->dev_ue->ue_usbver == 3) { + if (warm && dev->hci.hci_usbver == 3) { port->portsc |= XHCI_PS_WRC; } @@ -2620,13 +2620,13 @@ pci_xhci_init_port(struct pci_xhci_softc *sc, int portn) port->portsc = XHCI_PS_CCS | /* connected */ XHCI_PS_PP; /* port power */ - if (dev->dev_ue->ue_usbver == 2) { + if (dev->hci.hci_usbver == 2) { port->portsc |= XHCI_PS_PLS_SET(UPS_PORT_LS_POLL) | - XHCI_PS_SPEED_SET(dev->dev_ue->ue_usbspeed); + XHCI_PS_SPEED_SET(dev->hci.hci_speed); } else { port->portsc |= XHCI_PS_PLS_SET(UPS_PORT_LS_U0) | - XHCI_PS_PED | /* enabled */ - XHCI_PS_SPEED_SET(dev->dev_ue->ue_usbspeed); + XHCI_PS_PED | /* enabled */ + XHCI_PS_SPEED_SET(dev->hci.hci_speed); } DPRINTF(("Init port %d 0x%x", portn, port->portsc)); @@ -2785,8 +2785,8 @@ pci_xhci_parse_devices(struct pci_xhci_softc *sc, nvlist_t *nvl) cookie = NULL; while ((name = nvlist_next(slots_nvl, &type, &cookie)) != NULL) { - if (usb2_port == ((sc->usb2_port_start) + XHCI_MAX_DEVS/2) || - usb3_port == ((sc->usb3_port_start) + XHCI_MAX_DEVS/2)) { + if (usb2_port == ((sc->usb2_port_start) + XHCI_MAX_DEVS / 2) || + usb3_port == ((sc->usb3_port_start) + XHCI_MAX_DEVS / 2)) { WPRINTF(("pci_xhci max number of USB 2 or 3 " "devices reached, max %d", XHCI_MAX_DEVS/2)); goto bad; @@ -2833,12 +2833,26 @@ pci_xhci_parse_devices(struct pci_xhci_softc *sc, nvlist_t *nvl) dev->hci.hci_sc = dev; dev->hci.hci_intr = pci_xhci_dev_intr; dev->hci.hci_event = pci_xhci_dev_event; + dev->hci.hci_speed = USB_SPEED_MAX; + dev->hci.hci_usbver = -1; - if (ue->ue_usbver == 2) { + devsc = ue->ue_probe(&dev->hci, nvl); + if (devsc == NULL) { + free(dev); + goto bad; + } + dev->dev_sc = devsc; + + if (dev->hci.hci_usbver == -1) + dev->hci.hci_usbver = ue->ue_usbver; + + if (dev->hci.hci_usbver == 2) { if (usb2_port == sc->usb2_port_start + XHCI_MAX_DEVS / 2) { WPRINTF(("pci_xhci max number of USB 2 devices " "reached, max %d", XHCI_MAX_DEVS / 2)); + free(dev->dev_sc); + free(dev); goto bad; } dev->hci.hci_port = usb2_port; @@ -2848,6 +2862,8 @@ pci_xhci_parse_devices(struct pci_xhci_softc *sc, nvlist_t *nvl) XHCI_MAX_DEVS / 2) { WPRINTF(("pci_xhci max number of USB 3 devices " "reached, max %d", XHCI_MAX_DEVS / 2)); + free(dev->dev_sc); + free(dev); goto bad; } dev->hci.hci_port = usb3_port; @@ -2856,13 +2872,12 @@ pci_xhci_parse_devices(struct pci_xhci_softc *sc, nvlist_t *nvl) XHCI_DEVINST_PTR(sc, dev->hci.hci_port) = dev; dev->hci.hci_address = 0; - devsc = ue->ue_init(&dev->hci, nvl); - if (devsc == NULL) { + if (ue->ue_init(dev->dev_sc)) goto bad; - } dev->dev_ue = ue; - dev->dev_sc = devsc; + if (dev->hci.hci_speed == USB_SPEED_MAX) + dev->hci.hci_speed = ue->ue_usbspeed; XHCI_SLOTDEV_PTR(sc, slot) = dev; ndevices++; @@ -2882,6 +2897,8 @@ portsfinal: bad: for (i = 1; i <= XHCI_MAX_DEVS; i++) { + if (XHCI_DEVINST_PTR(sc, i) != NULL) + free(XHCI_DEVINST_PTR(sc, i)->dev_sc); free(XHCI_DEVINST_PTR(sc, i)); } @@ -3228,6 +3245,8 @@ pci_xhci_snapshot(struct vm_snapshot_meta *meta) /* devices[i]->hci */ SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_address, meta, ret, done); SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_port, meta, ret, done); + SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_speed, meta, ret, done); + SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_usbver, meta, ret, done); } SNAPSHOT_VAR_OR_LEAVE(sc->usb2_port_start, meta, ret, done); diff --git a/usr.sbin/bhyve/pciids_intel_gpus.h b/usr.sbin/bhyve/pciids_intel_gpus.h new file mode 100644 index 000000000000..a7ce9523c50d --- /dev/null +++ b/usr.sbin/bhyve/pciids_intel_gpus.h @@ -0,0 +1,873 @@ +/* + * Copyright 2013 Intel Corporation + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef __PCIIDS_H__ +#define __PCIIDS_H__ + +#ifdef __KERNEL__ +#define INTEL_VGA_DEVICE(_id, _info) { \ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, (_id)), \ + .class = PCI_BASE_CLASS_DISPLAY << 16, .class_mask = 0xff << 16, \ + .driver_data = (kernel_ulong_t)(_info), \ +} + +#define INTEL_QUANTA_VGA_DEVICE(_info) { \ + .vendor = PCI_VENDOR_ID_INTEL, .device = 0x16a, \ + .subvendor = 0x152d, .subdevice = 0x8990, \ + .class = PCI_BASE_CLASS_DISPLAY << 16, .class_mask = 0xff << 16, \ + .driver_data = (kernel_ulong_t)(_info), \ +} +#endif + +#define INTEL_I810_IDS(MACRO__, ...) \ + MACRO__(0x7121, ## __VA_ARGS__), /* I810 */ \ + MACRO__(0x7123, ## __VA_ARGS__), /* I810_DC100 */ \ + MACRO__(0x7125, ## __VA_ARGS__) /* I810_E */ + +#define INTEL_I815_IDS(MACRO__, ...) \ + MACRO__(0x1132, ## __VA_ARGS__) /* I815*/ + +#define INTEL_I830_IDS(MACRO__, ...) \ + MACRO__(0x3577, ## __VA_ARGS__) + +#define INTEL_I845G_IDS(MACRO__, ...) \ + MACRO__(0x2562, ## __VA_ARGS__) + +#define INTEL_I85X_IDS(MACRO__, ...) \ + MACRO__(0x3582, ## __VA_ARGS__), /* I855_GM */ \ + MACRO__(0x358e, ## __VA_ARGS__) + +#define INTEL_I865G_IDS(MACRO__, ...) \ + MACRO__(0x2572, ## __VA_ARGS__) /* I865_G */ + +#define INTEL_I915G_IDS(MACRO__, ...) \ + MACRO__(0x2582, ## __VA_ARGS__), /* I915_G */ \ + MACRO__(0x258a, ## __VA_ARGS__) /* E7221_G */ + +#define INTEL_I915GM_IDS(MACRO__, ...) \ + MACRO__(0x2592, ## __VA_ARGS__) /* I915_GM */ + +#define INTEL_I945G_IDS(MACRO__, ...) \ + MACRO__(0x2772, ## __VA_ARGS__) /* I945_G */ + +#define INTEL_I945GM_IDS(MACRO__, ...) \ + MACRO__(0x27a2, ## __VA_ARGS__), /* I945_GM */ \ + MACRO__(0x27ae, ## __VA_ARGS__) /* I945_GME */ + +#define INTEL_I965G_IDS(MACRO__, ...) \ + MACRO__(0x2972, ## __VA_ARGS__), /* I946_GZ */ \ + MACRO__(0x2982, ## __VA_ARGS__), /* G35_G */ \ + MACRO__(0x2992, ## __VA_ARGS__), /* I965_Q */ \ + MACRO__(0x29a2, ## __VA_ARGS__) /* I965_G */ + +#define INTEL_G33_IDS(MACRO__, ...) \ + MACRO__(0x29b2, ## __VA_ARGS__), /* Q35_G */ \ + MACRO__(0x29c2, ## __VA_ARGS__), /* G33_G */ \ + MACRO__(0x29d2, ## __VA_ARGS__) /* Q33_G */ + +#define INTEL_I965GM_IDS(MACRO__, ...) \ + MACRO__(0x2a02, ## __VA_ARGS__), /* I965_GM */ \ + MACRO__(0x2a12, ## __VA_ARGS__) /* I965_GME */ + +#define INTEL_GM45_IDS(MACRO__, ...) \ + MACRO__(0x2a42, ## __VA_ARGS__) /* GM45_G */ + +#define INTEL_G45_IDS(MACRO__, ...) \ + MACRO__(0x2e02, ## __VA_ARGS__), /* IGD_E_G */ \ + MACRO__(0x2e12, ## __VA_ARGS__), /* Q45_G */ \ + MACRO__(0x2e22, ## __VA_ARGS__), /* G45_G */ \ + MACRO__(0x2e32, ## __VA_ARGS__), /* G41_G */ \ + MACRO__(0x2e42, ## __VA_ARGS__), /* B43_G */ \ + MACRO__(0x2e92, ## __VA_ARGS__) /* B43_G.1 */ + +#define INTEL_PNV_G_IDS(MACRO__, ...) \ + MACRO__(0xa001, ## __VA_ARGS__) + +#define INTEL_PNV_M_IDS(MACRO__, ...) \ + MACRO__(0xa011, ## __VA_ARGS__) + +#define INTEL_PNV_IDS(MACRO__, ...) \ + INTEL_PNV_G_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_PNV_M_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_ILK_D_IDS(MACRO__, ...) \ + MACRO__(0x0042, ## __VA_ARGS__) + +#define INTEL_ILK_M_IDS(MACRO__, ...) \ + MACRO__(0x0046, ## __VA_ARGS__) + +#define INTEL_ILK_IDS(MACRO__, ...) \ + INTEL_ILK_D_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_ILK_M_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_SNB_D_GT1_IDS(MACRO__, ...) \ + MACRO__(0x0102, ## __VA_ARGS__), \ + MACRO__(0x010A, ## __VA_ARGS__) + +#define INTEL_SNB_D_GT2_IDS(MACRO__, ...) \ + MACRO__(0x0112, ## __VA_ARGS__), \ + MACRO__(0x0122, ## __VA_ARGS__) + +#define INTEL_SNB_D_IDS(MACRO__, ...) \ + INTEL_SNB_D_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_SNB_D_GT2_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_SNB_M_GT1_IDS(MACRO__, ...) \ + MACRO__(0x0106, ## __VA_ARGS__) + +#define INTEL_SNB_M_GT2_IDS(MACRO__, ...) \ + MACRO__(0x0116, ## __VA_ARGS__), \ + MACRO__(0x0126, ## __VA_ARGS__) + +#define INTEL_SNB_M_IDS(MACRO__, ...) \ + INTEL_SNB_M_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_SNB_M_GT2_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_SNB_IDS(MACRO__, ...) \ + INTEL_SNB_D_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_SNB_M_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_IVB_M_GT1_IDS(MACRO__, ...) \ + MACRO__(0x0156, ## __VA_ARGS__) /* GT1 mobile */ + +#define INTEL_IVB_M_GT2_IDS(MACRO__, ...) \ + MACRO__(0x0166, ## __VA_ARGS__) /* GT2 mobile */ + +#define INTEL_IVB_M_IDS(MACRO__, ...) \ + INTEL_IVB_M_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_IVB_M_GT2_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_IVB_D_GT1_IDS(MACRO__, ...) \ + MACRO__(0x0152, ## __VA_ARGS__), /* GT1 desktop */ \ + MACRO__(0x015a, ## __VA_ARGS__) /* GT1 server */ + +#define INTEL_IVB_D_GT2_IDS(MACRO__, ...) \ + MACRO__(0x0162, ## __VA_ARGS__), /* GT2 desktop */ \ + MACRO__(0x016a, ## __VA_ARGS__) /* GT2 server */ + +#define INTEL_IVB_D_IDS(MACRO__, ...) \ + INTEL_IVB_D_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_IVB_D_GT2_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_IVB_IDS(MACRO__, ...) \ + INTEL_IVB_M_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_IVB_D_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_IVB_Q_IDS(MACRO__, ...) \ + INTEL_QUANTA_VGA_DEVICE(__VA_ARGS__) /* Quanta transcode */ + +#define INTEL_HSW_ULT_GT1_IDS(MACRO__, ...) \ + MACRO__(0x0A02, ## __VA_ARGS__), /* ULT GT1 desktop */ \ + MACRO__(0x0A06, ## __VA_ARGS__), /* ULT GT1 mobile */ \ + MACRO__(0x0A0A, ## __VA_ARGS__), /* ULT GT1 server */ \ + MACRO__(0x0A0B, ## __VA_ARGS__) /* ULT GT1 reserved */ + +#define INTEL_HSW_ULX_GT1_IDS(MACRO__, ...) \ + MACRO__(0x0A0E, ## __VA_ARGS__) /* ULX GT1 mobile */ + +#define INTEL_HSW_GT1_IDS(MACRO__, ...) \ + INTEL_HSW_ULT_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_HSW_ULX_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x0402, ## __VA_ARGS__), /* GT1 desktop */ \ + MACRO__(0x0406, ## __VA_ARGS__), /* GT1 mobile */ \ + MACRO__(0x040A, ## __VA_ARGS__), /* GT1 server */ \ + MACRO__(0x040B, ## __VA_ARGS__), /* GT1 reserved */ \ + MACRO__(0x040E, ## __VA_ARGS__), /* GT1 reserved */ \ + MACRO__(0x0C02, ## __VA_ARGS__), /* SDV GT1 desktop */ \ + MACRO__(0x0C06, ## __VA_ARGS__), /* SDV GT1 mobile */ \ + MACRO__(0x0C0A, ## __VA_ARGS__), /* SDV GT1 server */ \ + MACRO__(0x0C0B, ## __VA_ARGS__), /* SDV GT1 reserved */ \ + MACRO__(0x0C0E, ## __VA_ARGS__), /* SDV GT1 reserved */ \ + MACRO__(0x0D02, ## __VA_ARGS__), /* CRW GT1 desktop */ \ + MACRO__(0x0D06, ## __VA_ARGS__), /* CRW GT1 mobile */ \ + MACRO__(0x0D0A, ## __VA_ARGS__), /* CRW GT1 server */ \ + MACRO__(0x0D0B, ## __VA_ARGS__), /* CRW GT1 reserved */ \ + MACRO__(0x0D0E, ## __VA_ARGS__) /* CRW GT1 reserved */ + +#define INTEL_HSW_ULT_GT2_IDS(MACRO__, ...) \ + MACRO__(0x0A12, ## __VA_ARGS__), /* ULT GT2 desktop */ \ + MACRO__(0x0A16, ## __VA_ARGS__), /* ULT GT2 mobile */ \ + MACRO__(0x0A1A, ## __VA_ARGS__), /* ULT GT2 server */ \ + MACRO__(0x0A1B, ## __VA_ARGS__) /* ULT GT2 reserved */ \ + +#define INTEL_HSW_ULX_GT2_IDS(MACRO__, ...) \ + MACRO__(0x0A1E, ## __VA_ARGS__) /* ULX GT2 mobile */ \ + +#define INTEL_HSW_GT2_IDS(MACRO__, ...) \ + INTEL_HSW_ULT_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_HSW_ULX_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x0412, ## __VA_ARGS__), /* GT2 desktop */ \ + MACRO__(0x0416, ## __VA_ARGS__), /* GT2 mobile */ \ + MACRO__(0x041A, ## __VA_ARGS__), /* GT2 server */ \ + MACRO__(0x041B, ## __VA_ARGS__), /* GT2 reserved */ \ + MACRO__(0x041E, ## __VA_ARGS__), /* GT2 reserved */ \ + MACRO__(0x0C12, ## __VA_ARGS__), /* SDV GT2 desktop */ \ + MACRO__(0x0C16, ## __VA_ARGS__), /* SDV GT2 mobile */ \ + MACRO__(0x0C1A, ## __VA_ARGS__), /* SDV GT2 server */ \ + MACRO__(0x0C1B, ## __VA_ARGS__), /* SDV GT2 reserved */ \ + MACRO__(0x0C1E, ## __VA_ARGS__), /* SDV GT2 reserved */ \ + MACRO__(0x0D12, ## __VA_ARGS__), /* CRW GT2 desktop */ \ + MACRO__(0x0D16, ## __VA_ARGS__), /* CRW GT2 mobile */ \ + MACRO__(0x0D1A, ## __VA_ARGS__), /* CRW GT2 server */ \ + MACRO__(0x0D1B, ## __VA_ARGS__), /* CRW GT2 reserved */ \ + MACRO__(0x0D1E, ## __VA_ARGS__) /* CRW GT2 reserved */ + +#define INTEL_HSW_ULT_GT3_IDS(MACRO__, ...) \ + MACRO__(0x0A22, ## __VA_ARGS__), /* ULT GT3 desktop */ \ + MACRO__(0x0A26, ## __VA_ARGS__), /* ULT GT3 mobile */ \ + MACRO__(0x0A2A, ## __VA_ARGS__), /* ULT GT3 server */ \ + MACRO__(0x0A2B, ## __VA_ARGS__), /* ULT GT3 reserved */ \ + MACRO__(0x0A2E, ## __VA_ARGS__) /* ULT GT3 reserved */ + +#define INTEL_HSW_GT3_IDS(MACRO__, ...) \ + INTEL_HSW_ULT_GT3_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x0422, ## __VA_ARGS__), /* GT3 desktop */ \ + MACRO__(0x0426, ## __VA_ARGS__), /* GT3 mobile */ \ + MACRO__(0x042A, ## __VA_ARGS__), /* GT3 server */ \ + MACRO__(0x042B, ## __VA_ARGS__), /* GT3 reserved */ \ + MACRO__(0x042E, ## __VA_ARGS__), /* GT3 reserved */ \ + MACRO__(0x0C22, ## __VA_ARGS__), /* SDV GT3 desktop */ \ + MACRO__(0x0C26, ## __VA_ARGS__), /* SDV GT3 mobile */ \ + MACRO__(0x0C2A, ## __VA_ARGS__), /* SDV GT3 server */ \ + MACRO__(0x0C2B, ## __VA_ARGS__), /* SDV GT3 reserved */ \ + MACRO__(0x0C2E, ## __VA_ARGS__), /* SDV GT3 reserved */ \ + MACRO__(0x0D22, ## __VA_ARGS__), /* CRW GT3 desktop */ \ + MACRO__(0x0D26, ## __VA_ARGS__), /* CRW GT3 mobile */ \ + MACRO__(0x0D2A, ## __VA_ARGS__), /* CRW GT3 server */ \ + MACRO__(0x0D2B, ## __VA_ARGS__), /* CRW GT3 reserved */ \ + MACRO__(0x0D2E, ## __VA_ARGS__) /* CRW GT3 reserved */ + +#define INTEL_HSW_IDS(MACRO__, ...) \ + INTEL_HSW_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_HSW_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_HSW_GT3_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_VLV_IDS(MACRO__, ...) \ + MACRO__(0x0f30, ## __VA_ARGS__), \ + MACRO__(0x0f31, ## __VA_ARGS__), \ + MACRO__(0x0f32, ## __VA_ARGS__), \ + MACRO__(0x0f33, ## __VA_ARGS__) + +#define INTEL_BDW_ULT_GT1_IDS(MACRO__, ...) \ + MACRO__(0x1606, ## __VA_ARGS__), /* GT1 ULT */ \ + MACRO__(0x160B, ## __VA_ARGS__) /* GT1 Iris */ + +#define INTEL_BDW_ULX_GT1_IDS(MACRO__, ...) \ + MACRO__(0x160E, ## __VA_ARGS__) /* GT1 ULX */ + +#define INTEL_BDW_GT1_IDS(MACRO__, ...) \ + INTEL_BDW_ULT_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_BDW_ULX_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x1602, ## __VA_ARGS__), /* GT1 ULT */ \ + MACRO__(0x160A, ## __VA_ARGS__), /* GT1 Server */ \ + MACRO__(0x160D, ## __VA_ARGS__) /* GT1 Workstation */ + +#define INTEL_BDW_ULT_GT2_IDS(MACRO__, ...) \ + MACRO__(0x1616, ## __VA_ARGS__), /* GT2 ULT */ \ + MACRO__(0x161B, ## __VA_ARGS__) /* GT2 ULT */ + +#define INTEL_BDW_ULX_GT2_IDS(MACRO__, ...) \ + MACRO__(0x161E, ## __VA_ARGS__) /* GT2 ULX */ + +#define INTEL_BDW_GT2_IDS(MACRO__, ...) \ + INTEL_BDW_ULT_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_BDW_ULX_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x1612, ## __VA_ARGS__), /* GT2 Halo */ \ + MACRO__(0x161A, ## __VA_ARGS__), /* GT2 Server */ \ + MACRO__(0x161D, ## __VA_ARGS__) /* GT2 Workstation */ + +#define INTEL_BDW_ULT_GT3_IDS(MACRO__, ...) \ + MACRO__(0x1626, ## __VA_ARGS__), /* ULT */ \ + MACRO__(0x162B, ## __VA_ARGS__) /* Iris */ \ + +#define INTEL_BDW_ULX_GT3_IDS(MACRO__, ...) \ + MACRO__(0x162E, ## __VA_ARGS__) /* ULX */ + +#define INTEL_BDW_GT3_IDS(MACRO__, ...) \ + INTEL_BDW_ULT_GT3_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_BDW_ULX_GT3_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x1622, ## __VA_ARGS__), /* ULT */ \ + MACRO__(0x162A, ## __VA_ARGS__), /* Server */ \ + MACRO__(0x162D, ## __VA_ARGS__) /* Workstation */ + +#define INTEL_BDW_ULT_RSVD_IDS(MACRO__, ...) \ + MACRO__(0x1636, ## __VA_ARGS__), /* ULT */ \ + MACRO__(0x163B, ## __VA_ARGS__) /* Iris */ + +#define INTEL_BDW_ULX_RSVD_IDS(MACRO__, ...) \ + MACRO__(0x163E, ## __VA_ARGS__) /* ULX */ + +#define INTEL_BDW_RSVD_IDS(MACRO__, ...) \ + INTEL_BDW_ULT_RSVD_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_BDW_ULX_RSVD_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x1632, ## __VA_ARGS__), /* ULT */ \ + MACRO__(0x163A, ## __VA_ARGS__), /* Server */ \ + MACRO__(0x163D, ## __VA_ARGS__) /* Workstation */ + +#define INTEL_BDW_IDS(MACRO__, ...) \ + INTEL_BDW_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_BDW_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_BDW_GT3_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_BDW_RSVD_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_CHV_IDS(MACRO__, ...) \ + MACRO__(0x22b0, ## __VA_ARGS__), \ + MACRO__(0x22b1, ## __VA_ARGS__), \ + MACRO__(0x22b2, ## __VA_ARGS__), \ + MACRO__(0x22b3, ## __VA_ARGS__) + +#define INTEL_SKL_ULT_GT1_IDS(MACRO__, ...) \ + MACRO__(0x1906, ## __VA_ARGS__), /* ULT GT1 */ \ + MACRO__(0x1913, ## __VA_ARGS__) /* ULT GT1.5 */ + +#define INTEL_SKL_ULX_GT1_IDS(MACRO__, ...) \ + MACRO__(0x190E, ## __VA_ARGS__), /* ULX GT1 */ \ + MACRO__(0x1915, ## __VA_ARGS__) /* ULX GT1.5 */ + +#define INTEL_SKL_GT1_IDS(MACRO__, ...) \ + INTEL_SKL_ULT_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_SKL_ULX_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x1902, ## __VA_ARGS__), /* DT GT1 */ \ + MACRO__(0x190A, ## __VA_ARGS__), /* SRV GT1 */ \ + MACRO__(0x190B, ## __VA_ARGS__), /* Halo GT1 */ \ + MACRO__(0x1917, ## __VA_ARGS__) /* DT GT1.5 */ + +#define INTEL_SKL_ULT_GT2_IDS(MACRO__, ...) \ + MACRO__(0x1916, ## __VA_ARGS__), /* ULT GT2 */ \ + MACRO__(0x1921, ## __VA_ARGS__) /* ULT GT2F */ + +#define INTEL_SKL_ULX_GT2_IDS(MACRO__, ...) \ + MACRO__(0x191E, ## __VA_ARGS__) /* ULX GT2 */ + +#define INTEL_SKL_GT2_IDS(MACRO__, ...) \ + INTEL_SKL_ULT_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_SKL_ULX_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x1912, ## __VA_ARGS__), /* DT GT2 */ \ + MACRO__(0x191A, ## __VA_ARGS__), /* SRV GT2 */ \ + MACRO__(0x191B, ## __VA_ARGS__), /* Halo GT2 */ \ + MACRO__(0x191D, ## __VA_ARGS__) /* WKS GT2 */ + +#define INTEL_SKL_ULT_GT3_IDS(MACRO__, ...) \ + MACRO__(0x1923, ## __VA_ARGS__), /* ULT GT3 */ \ + MACRO__(0x1926, ## __VA_ARGS__), /* ULT GT3e */ \ + MACRO__(0x1927, ## __VA_ARGS__) /* ULT GT3e */ + +#define INTEL_SKL_GT3_IDS(MACRO__, ...) \ + INTEL_SKL_ULT_GT3_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x192A, ## __VA_ARGS__), /* SRV GT3 */ \ + MACRO__(0x192B, ## __VA_ARGS__), /* Halo GT3e */ \ + MACRO__(0x192D, ## __VA_ARGS__) /* SRV GT3e */ + +#define INTEL_SKL_GT4_IDS(MACRO__, ...) \ + MACRO__(0x1932, ## __VA_ARGS__), /* DT GT4 */ \ + MACRO__(0x193A, ## __VA_ARGS__), /* SRV GT4e */ \ + MACRO__(0x193B, ## __VA_ARGS__), /* Halo GT4e */ \ + MACRO__(0x193D, ## __VA_ARGS__) /* WKS GT4e */ + +#define INTEL_SKL_IDS(MACRO__, ...) \ + INTEL_SKL_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_SKL_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_SKL_GT3_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_SKL_GT4_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_BXT_IDS(MACRO__, ...) \ + MACRO__(0x0A84, ## __VA_ARGS__), \ + MACRO__(0x1A84, ## __VA_ARGS__), \ + MACRO__(0x1A85, ## __VA_ARGS__), \ + MACRO__(0x5A84, ## __VA_ARGS__), /* APL HD Graphics 505 */ \ + MACRO__(0x5A85, ## __VA_ARGS__) /* APL HD Graphics 500 */ + +#define INTEL_GLK_IDS(MACRO__, ...) \ + MACRO__(0x3184, ## __VA_ARGS__), \ + MACRO__(0x3185, ## __VA_ARGS__) + +#define INTEL_KBL_ULT_GT1_IDS(MACRO__, ...) \ + MACRO__(0x5906, ## __VA_ARGS__), /* ULT GT1 */ \ + MACRO__(0x5913, ## __VA_ARGS__) /* ULT GT1.5 */ + +#define INTEL_KBL_ULX_GT1_IDS(MACRO__, ...) \ + MACRO__(0x590E, ## __VA_ARGS__), /* ULX GT1 */ \ + MACRO__(0x5915, ## __VA_ARGS__) /* ULX GT1.5 */ + +#define INTEL_KBL_GT1_IDS(MACRO__, ...) \ + INTEL_KBL_ULT_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_KBL_ULX_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x5902, ## __VA_ARGS__), /* DT GT1 */ \ + MACRO__(0x5908, ## __VA_ARGS__), /* Halo GT1 */ \ + MACRO__(0x590A, ## __VA_ARGS__), /* SRV GT1 */ \ + MACRO__(0x590B, ## __VA_ARGS__) /* Halo GT1 */ + +#define INTEL_KBL_ULT_GT2_IDS(MACRO__, ...) \ + MACRO__(0x5916, ## __VA_ARGS__), /* ULT GT2 */ \ + MACRO__(0x5921, ## __VA_ARGS__) /* ULT GT2F */ + +#define INTEL_KBL_ULX_GT2_IDS(MACRO__, ...) \ + MACRO__(0x591E, ## __VA_ARGS__) /* ULX GT2 */ + +#define INTEL_KBL_GT2_IDS(MACRO__, ...) \ + INTEL_KBL_ULT_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_KBL_ULX_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x5912, ## __VA_ARGS__), /* DT GT2 */ \ + MACRO__(0x5917, ## __VA_ARGS__), /* Mobile GT2 */ \ + MACRO__(0x591A, ## __VA_ARGS__), /* SRV GT2 */ \ + MACRO__(0x591B, ## __VA_ARGS__), /* Halo GT2 */ \ + MACRO__(0x591D, ## __VA_ARGS__) /* WKS GT2 */ + +#define INTEL_KBL_ULT_GT3_IDS(MACRO__, ...) \ + MACRO__(0x5926, ## __VA_ARGS__) /* ULT GT3 */ + +#define INTEL_KBL_GT3_IDS(MACRO__, ...) \ + INTEL_KBL_ULT_GT3_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x5923, ## __VA_ARGS__), /* ULT GT3 */ \ + MACRO__(0x5927, ## __VA_ARGS__) /* ULT GT3 */ + +#define INTEL_KBL_GT4_IDS(MACRO__, ...) \ + MACRO__(0x593B, ## __VA_ARGS__) /* Halo GT4 */ + +/* AML/KBL Y GT2 */ +#define INTEL_AML_KBL_GT2_IDS(MACRO__, ...) \ + MACRO__(0x591C, ## __VA_ARGS__), /* ULX GT2 */ \ + MACRO__(0x87C0, ## __VA_ARGS__) /* ULX GT2 */ + +/* AML/CFL Y GT2 */ +#define INTEL_AML_CFL_GT2_IDS(MACRO__, ...) \ + MACRO__(0x87CA, ## __VA_ARGS__) + +/* CML GT1 */ +#define INTEL_CML_GT1_IDS(MACRO__, ...) \ + MACRO__(0x9BA2, ## __VA_ARGS__), \ + MACRO__(0x9BA4, ## __VA_ARGS__), \ + MACRO__(0x9BA5, ## __VA_ARGS__), \ + MACRO__(0x9BA8, ## __VA_ARGS__) + +#define INTEL_CML_U_GT1_IDS(MACRO__, ...) \ + MACRO__(0x9B21, ## __VA_ARGS__), \ + MACRO__(0x9BAA, ## __VA_ARGS__), \ + MACRO__(0x9BAC, ## __VA_ARGS__) + +/* CML GT2 */ +#define INTEL_CML_GT2_IDS(MACRO__, ...) \ + MACRO__(0x9BC2, ## __VA_ARGS__), \ + MACRO__(0x9BC4, ## __VA_ARGS__), \ + MACRO__(0x9BC5, ## __VA_ARGS__), \ + MACRO__(0x9BC6, ## __VA_ARGS__), \ + MACRO__(0x9BC8, ## __VA_ARGS__), \ + MACRO__(0x9BE6, ## __VA_ARGS__), \ + MACRO__(0x9BF6, ## __VA_ARGS__) + +#define INTEL_CML_U_GT2_IDS(MACRO__, ...) \ + MACRO__(0x9B41, ## __VA_ARGS__), \ + MACRO__(0x9BCA, ## __VA_ARGS__), \ + MACRO__(0x9BCC, ## __VA_ARGS__) + +#define INTEL_CML_IDS(MACRO__, ...) \ + INTEL_CML_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_CML_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_CML_U_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_CML_U_GT2_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_KBL_IDS(MACRO__, ...) \ + INTEL_KBL_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_KBL_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_KBL_GT3_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_KBL_GT4_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_AML_KBL_GT2_IDS(MACRO__, ## __VA_ARGS__) + +/* CFL S */ +#define INTEL_CFL_S_GT1_IDS(MACRO__, ...) \ + MACRO__(0x3E90, ## __VA_ARGS__), /* SRV GT1 */ \ + MACRO__(0x3E93, ## __VA_ARGS__), /* SRV GT1 */ \ + MACRO__(0x3E99, ## __VA_ARGS__) /* SRV GT1 */ + +#define INTEL_CFL_S_GT2_IDS(MACRO__, ...) \ + MACRO__(0x3E91, ## __VA_ARGS__), /* SRV GT2 */ \ + MACRO__(0x3E92, ## __VA_ARGS__), /* SRV GT2 */ \ + MACRO__(0x3E96, ## __VA_ARGS__), /* SRV GT2 */ \ + MACRO__(0x3E98, ## __VA_ARGS__), /* SRV GT2 */ \ + MACRO__(0x3E9A, ## __VA_ARGS__) /* SRV GT2 */ + +/* CFL H */ +#define INTEL_CFL_H_GT1_IDS(MACRO__, ...) \ + MACRO__(0x3E9C, ## __VA_ARGS__) + +#define INTEL_CFL_H_GT2_IDS(MACRO__, ...) \ + MACRO__(0x3E94, ## __VA_ARGS__), /* Halo GT2 */ \ + MACRO__(0x3E9B, ## __VA_ARGS__) /* Halo GT2 */ + +/* CFL U GT2 */ +#define INTEL_CFL_U_GT2_IDS(MACRO__, ...) \ + MACRO__(0x3EA9, ## __VA_ARGS__) + +/* CFL U GT3 */ +#define INTEL_CFL_U_GT3_IDS(MACRO__, ...) \ + MACRO__(0x3EA5, ## __VA_ARGS__), /* ULT GT3 */ \ + MACRO__(0x3EA6, ## __VA_ARGS__), /* ULT GT3 */ \ + MACRO__(0x3EA7, ## __VA_ARGS__), /* ULT GT3 */ \ + MACRO__(0x3EA8, ## __VA_ARGS__) /* ULT GT3 */ + +#define INTEL_CFL_IDS(MACRO__, ...) \ + INTEL_CFL_S_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_CFL_S_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_CFL_H_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_CFL_H_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_CFL_U_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_CFL_U_GT3_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_AML_CFL_GT2_IDS(MACRO__, ## __VA_ARGS__) + +/* WHL/CFL U GT1 */ +#define INTEL_WHL_U_GT1_IDS(MACRO__, ...) \ + MACRO__(0x3EA1, ## __VA_ARGS__), \ + MACRO__(0x3EA4, ## __VA_ARGS__) + +/* WHL/CFL U GT2 */ +#define INTEL_WHL_U_GT2_IDS(MACRO__, ...) \ + MACRO__(0x3EA0, ## __VA_ARGS__), \ + MACRO__(0x3EA3, ## __VA_ARGS__) + +/* WHL/CFL U GT3 */ +#define INTEL_WHL_U_GT3_IDS(MACRO__, ...) \ + MACRO__(0x3EA2, ## __VA_ARGS__) + +#define INTEL_WHL_IDS(MACRO__, ...) \ + INTEL_WHL_U_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_WHL_U_GT2_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_WHL_U_GT3_IDS(MACRO__, ## __VA_ARGS__) + +/* CNL */ +#define INTEL_CNL_PORT_F_IDS(MACRO__, ...) \ + MACRO__(0x5A44, ## __VA_ARGS__), \ + MACRO__(0x5A4C, ## __VA_ARGS__), \ + MACRO__(0x5A54, ## __VA_ARGS__), \ + MACRO__(0x5A5C, ## __VA_ARGS__) + +#define INTEL_CNL_IDS(MACRO__, ...) \ + INTEL_CNL_PORT_F_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x5A40, ## __VA_ARGS__), \ + MACRO__(0x5A41, ## __VA_ARGS__), \ + MACRO__(0x5A42, ## __VA_ARGS__), \ + MACRO__(0x5A49, ## __VA_ARGS__), \ + MACRO__(0x5A4A, ## __VA_ARGS__), \ + MACRO__(0x5A50, ## __VA_ARGS__), \ + MACRO__(0x5A51, ## __VA_ARGS__), \ + MACRO__(0x5A52, ## __VA_ARGS__), \ + MACRO__(0x5A59, ## __VA_ARGS__), \ + MACRO__(0x5A5A, ## __VA_ARGS__) + +/* ICL */ +#define INTEL_ICL_PORT_F_IDS(MACRO__, ...) \ + MACRO__(0x8A50, ## __VA_ARGS__), \ + MACRO__(0x8A52, ## __VA_ARGS__), \ + MACRO__(0x8A53, ## __VA_ARGS__), \ + MACRO__(0x8A54, ## __VA_ARGS__), \ + MACRO__(0x8A56, ## __VA_ARGS__), \ + MACRO__(0x8A57, ## __VA_ARGS__), \ + MACRO__(0x8A58, ## __VA_ARGS__), \ + MACRO__(0x8A59, ## __VA_ARGS__), \ + MACRO__(0x8A5A, ## __VA_ARGS__), \ + MACRO__(0x8A5B, ## __VA_ARGS__), \ + MACRO__(0x8A5C, ## __VA_ARGS__), \ + MACRO__(0x8A70, ## __VA_ARGS__), \ + MACRO__(0x8A71, ## __VA_ARGS__) + +#define INTEL_ICL_IDS(MACRO__, ...) \ + INTEL_ICL_PORT_F_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x8A51, ## __VA_ARGS__), \ + MACRO__(0x8A5D, ## __VA_ARGS__) + +/* EHL */ +#define INTEL_EHL_IDS(MACRO__, ...) \ + MACRO__(0x4541, ## __VA_ARGS__), \ + MACRO__(0x4551, ## __VA_ARGS__), \ + MACRO__(0x4555, ## __VA_ARGS__), \ + MACRO__(0x4557, ## __VA_ARGS__), \ + MACRO__(0x4570, ## __VA_ARGS__), \ + MACRO__(0x4571, ## __VA_ARGS__) + +/* JSL */ +#define INTEL_JSL_IDS(MACRO__, ...) \ + MACRO__(0x4E51, ## __VA_ARGS__), \ + MACRO__(0x4E55, ## __VA_ARGS__), \ + MACRO__(0x4E57, ## __VA_ARGS__), \ + MACRO__(0x4E61, ## __VA_ARGS__), \ + MACRO__(0x4E71, ## __VA_ARGS__) + +/* TGL */ +#define INTEL_TGL_GT1_IDS(MACRO__, ...) \ + MACRO__(0x9A60, ## __VA_ARGS__), \ + MACRO__(0x9A68, ## __VA_ARGS__), \ + MACRO__(0x9A70, ## __VA_ARGS__) + +#define INTEL_TGL_GT2_IDS(MACRO__, ...) \ + MACRO__(0x9A40, ## __VA_ARGS__), \ + MACRO__(0x9A49, ## __VA_ARGS__), \ + MACRO__(0x9A59, ## __VA_ARGS__), \ + MACRO__(0x9A78, ## __VA_ARGS__), \ + MACRO__(0x9AC0, ## __VA_ARGS__), \ + MACRO__(0x9AC9, ## __VA_ARGS__), \ + MACRO__(0x9AD9, ## __VA_ARGS__), \ + MACRO__(0x9AF8, ## __VA_ARGS__) + +#define INTEL_TGL_IDS(MACRO__, ...) \ + INTEL_TGL_GT1_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_TGL_GT2_IDS(MACRO__, ## __VA_ARGS__) + +/* RKL */ +#define INTEL_RKL_IDS(MACRO__, ...) \ + MACRO__(0x4C80, ## __VA_ARGS__), \ + MACRO__(0x4C8A, ## __VA_ARGS__), \ + MACRO__(0x4C8B, ## __VA_ARGS__), \ + MACRO__(0x4C8C, ## __VA_ARGS__), \ + MACRO__(0x4C90, ## __VA_ARGS__), \ + MACRO__(0x4C9A, ## __VA_ARGS__) + +/* DG1 */ +#define INTEL_DG1_IDS(MACRO__, ...) \ + MACRO__(0x4905, ## __VA_ARGS__), \ + MACRO__(0x4906, ## __VA_ARGS__), \ + MACRO__(0x4907, ## __VA_ARGS__), \ + MACRO__(0x4908, ## __VA_ARGS__), \ + MACRO__(0x4909, ## __VA_ARGS__) + +/* ADL-S */ +#define INTEL_ADLS_IDS(MACRO__, ...) \ + MACRO__(0x4680, ## __VA_ARGS__), \ + MACRO__(0x4682, ## __VA_ARGS__), \ + MACRO__(0x4688, ## __VA_ARGS__), \ + MACRO__(0x468A, ## __VA_ARGS__), \ + MACRO__(0x468B, ## __VA_ARGS__), \ + MACRO__(0x4690, ## __VA_ARGS__), \ + MACRO__(0x4692, ## __VA_ARGS__), \ + MACRO__(0x4693, ## __VA_ARGS__) + +/* ADL-P */ +#define INTEL_ADLP_IDS(MACRO__, ...) \ + MACRO__(0x46A0, ## __VA_ARGS__), \ + MACRO__(0x46A1, ## __VA_ARGS__), \ + MACRO__(0x46A2, ## __VA_ARGS__), \ + MACRO__(0x46A3, ## __VA_ARGS__), \ + MACRO__(0x46A6, ## __VA_ARGS__), \ + MACRO__(0x46A8, ## __VA_ARGS__), \ + MACRO__(0x46AA, ## __VA_ARGS__), \ + MACRO__(0x462A, ## __VA_ARGS__), \ + MACRO__(0x4626, ## __VA_ARGS__), \ + MACRO__(0x4628, ## __VA_ARGS__), \ + MACRO__(0x46B0, ## __VA_ARGS__), \ + MACRO__(0x46B1, ## __VA_ARGS__), \ + MACRO__(0x46B2, ## __VA_ARGS__), \ + MACRO__(0x46B3, ## __VA_ARGS__), \ + MACRO__(0x46C0, ## __VA_ARGS__), \ + MACRO__(0x46C1, ## __VA_ARGS__), \ + MACRO__(0x46C2, ## __VA_ARGS__), \ + MACRO__(0x46C3, ## __VA_ARGS__) + +/* ADL-N */ +#define INTEL_ADLN_IDS(MACRO__, ...) \ + MACRO__(0x46D0, ## __VA_ARGS__), \ + MACRO__(0x46D1, ## __VA_ARGS__), \ + MACRO__(0x46D2, ## __VA_ARGS__), \ + MACRO__(0x46D3, ## __VA_ARGS__), \ + MACRO__(0x46D4, ## __VA_ARGS__) + +/* RPL-S */ +#define INTEL_RPLS_IDS(MACRO__, ...) \ + MACRO__(0xA780, ## __VA_ARGS__), \ + MACRO__(0xA781, ## __VA_ARGS__), \ + MACRO__(0xA782, ## __VA_ARGS__), \ + MACRO__(0xA783, ## __VA_ARGS__), \ + MACRO__(0xA788, ## __VA_ARGS__), \ + MACRO__(0xA789, ## __VA_ARGS__), \ + MACRO__(0xA78A, ## __VA_ARGS__), \ + MACRO__(0xA78B, ## __VA_ARGS__) + +/* RPL-U */ +#define INTEL_RPLU_IDS(MACRO__, ...) \ + MACRO__(0xA721, ## __VA_ARGS__), \ + MACRO__(0xA7A1, ## __VA_ARGS__), \ + MACRO__(0xA7A9, ## __VA_ARGS__), \ + MACRO__(0xA7AC, ## __VA_ARGS__), \ + MACRO__(0xA7AD, ## __VA_ARGS__) + +/* RPL-P */ +#define INTEL_RPLP_IDS(MACRO__, ...) \ + MACRO__(0xA720, ## __VA_ARGS__), \ + MACRO__(0xA7A0, ## __VA_ARGS__), \ + MACRO__(0xA7A8, ## __VA_ARGS__), \ + MACRO__(0xA7AA, ## __VA_ARGS__), \ + MACRO__(0xA7AB, ## __VA_ARGS__) + +/* DG2 */ +#define INTEL_DG2_G10_D_IDS(MACRO__, ...) \ + MACRO__(0x56A0, ## __VA_ARGS__), \ + MACRO__(0x56A1, ## __VA_ARGS__), \ + MACRO__(0x56A2, ## __VA_ARGS__) + +#define INTEL_DG2_G10_E_IDS(MACRO__, ...) \ + MACRO__(0x56BE, ## __VA_ARGS__), \ + MACRO__(0x56BF, ## __VA_ARGS__) + +#define INTEL_DG2_G10_M_IDS(MACRO__, ...) \ + MACRO__(0x5690, ## __VA_ARGS__), \ + MACRO__(0x5691, ## __VA_ARGS__), \ + MACRO__(0x5692, ## __VA_ARGS__) + +#define INTEL_DG2_G10_IDS(MACRO__, ...) \ + INTEL_DG2_G10_D_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_DG2_G10_E_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_DG2_G10_M_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_DG2_G11_D_IDS(MACRO__, ...) \ + MACRO__(0x56A5, ## __VA_ARGS__), \ + MACRO__(0x56A6, ## __VA_ARGS__), \ + MACRO__(0x56B0, ## __VA_ARGS__), \ + MACRO__(0x56B1, ## __VA_ARGS__) + +#define INTEL_DG2_G11_E_IDS(MACRO__, ...) \ + MACRO__(0x56BA, ## __VA_ARGS__), \ + MACRO__(0x56BB, ## __VA_ARGS__), \ + MACRO__(0x56BC, ## __VA_ARGS__), \ + MACRO__(0x56BD, ## __VA_ARGS__) + +#define INTEL_DG2_G11_M_IDS(MACRO__, ...) \ + MACRO__(0x5693, ## __VA_ARGS__), \ + MACRO__(0x5694, ## __VA_ARGS__), \ + MACRO__(0x5695, ## __VA_ARGS__) + +#define INTEL_DG2_G11_IDS(MACRO__, ...) \ + INTEL_DG2_G11_D_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_DG2_G11_E_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_DG2_G11_M_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_DG2_G12_D_IDS(MACRO__, ...) \ + MACRO__(0x56A3, ## __VA_ARGS__), \ + MACRO__(0x56A4, ## __VA_ARGS__), \ + MACRO__(0x56B2, ## __VA_ARGS__), \ + MACRO__(0x56B3, ## __VA_ARGS__) + +#define INTEL_DG2_G12_M_IDS(MACRO__, ...) \ + MACRO__(0x5696, ## __VA_ARGS__), \ + MACRO__(0x5697, ## __VA_ARGS__) + +#define INTEL_DG2_G12_IDS(MACRO__, ...) \ + INTEL_DG2_G12_D_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_DG2_G12_M_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_DG2_D_IDS(MACRO__, ...) \ + INTEL_DG2_G10_D_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_DG2_G11_D_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_DG2_G12_D_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_DG2_IDS(MACRO__, ...) \ + INTEL_DG2_G10_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_DG2_G11_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_DG2_G12_IDS(MACRO__, ## __VA_ARGS__) + +#define INTEL_ATS_M150_IDS(MACRO__, ...) \ + MACRO__(0x56C0, ## __VA_ARGS__), \ + MACRO__(0x56C2, ## __VA_ARGS__) + +#define INTEL_ATS_M75_IDS(MACRO__, ...) \ + MACRO__(0x56C1, ## __VA_ARGS__) + +#define INTEL_ATS_M_IDS(MACRO__, ...) \ + INTEL_ATS_M150_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_ATS_M75_IDS(MACRO__, ## __VA_ARGS__) + +/* ARL */ +#define INTEL_ARL_H_IDS(MACRO__, ...) \ + MACRO__(0x7D51, ## __VA_ARGS__), \ + MACRO__(0x7DD1, ## __VA_ARGS__) + +#define INTEL_ARL_U_IDS(MACRO__, ...) \ + MACRO__(0x7D41, ## __VA_ARGS__) \ + +#define INTEL_ARL_S_IDS(MACRO__, ...) \ + MACRO__(0x7D67, ## __VA_ARGS__), \ + MACRO__(0xB640, ## __VA_ARGS__) + +#define INTEL_ARL_IDS(MACRO__, ...) \ + INTEL_ARL_H_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_ARL_U_IDS(MACRO__, ## __VA_ARGS__), \ + INTEL_ARL_S_IDS(MACRO__, ## __VA_ARGS__) + +/* MTL */ +#define INTEL_MTL_U_IDS(MACRO__, ...) \ + MACRO__(0x7D40, ## __VA_ARGS__), \ + MACRO__(0x7D45, ## __VA_ARGS__) + +#define INTEL_MTL_IDS(MACRO__, ...) \ + INTEL_MTL_U_IDS(MACRO__, ## __VA_ARGS__), \ + MACRO__(0x7D55, ## __VA_ARGS__), \ + MACRO__(0x7D60, ## __VA_ARGS__), \ + MACRO__(0x7DD5, ## __VA_ARGS__) + +/* PVC */ +#define INTEL_PVC_IDS(MACRO__, ...) \ + MACRO__(0x0B69, ## __VA_ARGS__), \ + MACRO__(0x0B6E, ## __VA_ARGS__), \ + MACRO__(0x0BD4, ## __VA_ARGS__), \ + MACRO__(0x0BD5, ## __VA_ARGS__), \ + MACRO__(0x0BD6, ## __VA_ARGS__), \ + MACRO__(0x0BD7, ## __VA_ARGS__), \ + MACRO__(0x0BD8, ## __VA_ARGS__), \ + MACRO__(0x0BD9, ## __VA_ARGS__), \ + MACRO__(0x0BDA, ## __VA_ARGS__), \ + MACRO__(0x0BDB, ## __VA_ARGS__), \ + MACRO__(0x0BE0, ## __VA_ARGS__), \ + MACRO__(0x0BE1, ## __VA_ARGS__), \ + MACRO__(0x0BE5, ## __VA_ARGS__) + +/* LNL */ +#define INTEL_LNL_IDS(MACRO__, ...) \ + MACRO__(0x6420, ## __VA_ARGS__), \ + MACRO__(0x64A0, ## __VA_ARGS__), \ + MACRO__(0x64B0, ## __VA_ARGS__) + +/* BMG */ +#define INTEL_BMG_IDS(MACRO__, ...) \ + MACRO__(0xE202, ## __VA_ARGS__), \ + MACRO__(0xE20B, ## __VA_ARGS__), \ + MACRO__(0xE20C, ## __VA_ARGS__), \ + MACRO__(0xE20D, ## __VA_ARGS__), \ + MACRO__(0xE210, ## __VA_ARGS__), \ + MACRO__(0xE211, ## __VA_ARGS__), \ + MACRO__(0xE212, ## __VA_ARGS__), \ + MACRO__(0xE215, ## __VA_ARGS__), \ + MACRO__(0xE216, ## __VA_ARGS__) + +/* PTL */ +#define INTEL_PTL_IDS(MACRO__, ...) \ + MACRO__(0xB080, ## __VA_ARGS__), \ + MACRO__(0xB081, ## __VA_ARGS__), \ + MACRO__(0xB082, ## __VA_ARGS__), \ + MACRO__(0xB083, ## __VA_ARGS__), \ + MACRO__(0xB084, ## __VA_ARGS__), \ + MACRO__(0xB085, ## __VA_ARGS__), \ + MACRO__(0xB086, ## __VA_ARGS__), \ + MACRO__(0xB087, ## __VA_ARGS__), \ + MACRO__(0xB08F, ## __VA_ARGS__), \ + MACRO__(0xB090, ## __VA_ARGS__), \ + MACRO__(0xB0A0, ## __VA_ARGS__), \ + MACRO__(0xB0B0, ## __VA_ARGS__) + +#endif /* __PCIIDS_H__ */ diff --git a/usr.sbin/bhyve/riscv/Makefile.inc b/usr.sbin/bhyve/riscv/Makefile.inc index 2b57201c6259..591bd119d5d1 100644 --- a/usr.sbin/bhyve/riscv/Makefile.inc +++ b/usr.sbin/bhyve/riscv/Makefile.inc @@ -1,5 +1,6 @@ SRCS+= \ - fdt.c + fdt.c \ + mem_md.c .PATH: ${BHYVE_SYSDIR}/sys/riscv/vmm SRCS+= vmm_instruction_emul.c diff --git a/usr.sbin/bhyve/tpm_ppi_qemu.c b/usr.sbin/bhyve/tpm_ppi_qemu.c index 01b8493e7273..6974b574b983 100644 --- a/usr.sbin/bhyve/tpm_ppi_qemu.c +++ b/usr.sbin/bhyve/tpm_ppi_qemu.c @@ -207,7 +207,7 @@ tpm_ppi_write_dsdt_regions(void *sc __unused) * Used for TCG Platform Reset Attack Mitigation */ dsdt_line("OperationRegion(TPP3, SystemMemory, 0x%8x, 1)", - TPM_PPI_ADDRESS + sizeof(struct tpm_ppi_qemu)); + TPM_PPI_ADDRESS + (uint32_t)sizeof(struct tpm_ppi_qemu)); dsdt_line("Field(TPP3, ByteAcc, NoLock, Preserve)"); dsdt_line("{"); dsdt_line(" MOVV, 8,"); diff --git a/usr.sbin/bhyve/usb_emul.h b/usr.sbin/bhyve/usb_emul.h index 8e0afcb2878b..43b6b53b5205 100644 --- a/usr.sbin/bhyve/usb_emul.h +++ b/usr.sbin/bhyve/usb_emul.h @@ -52,7 +52,8 @@ struct usb_devemu { int ue_usbspeed; /* usb device speed */ /* instance creation */ - void *(*ue_init)(struct usb_hci *hci, nvlist_t *nvl); + void *(*ue_probe)(struct usb_hci *hci, nvlist_t *nvl); + int (*ue_init)(void *sc); /* handlers */ int (*ue_request)(void *sc, struct usb_data_xfer *xfer); @@ -85,6 +86,8 @@ struct usb_hci { /* controller managed fields */ int hci_address; int hci_port; + int hci_speed; + int hci_usbver; }; /* diff --git a/usr.sbin/bhyve/usb_mouse.c b/usr.sbin/bhyve/usb_mouse.c index a37941c0cd9d..82b1159d5f61 100644 --- a/usr.sbin/bhyve/usb_mouse.c +++ b/usr.sbin/bhyve/usb_mouse.c @@ -295,20 +295,28 @@ umouse_event(uint8_t button, int x, int y, void *arg) } static void * -umouse_init(struct usb_hci *hci, nvlist_t *nvl __unused) +umouse_probe(struct usb_hci *hci, nvlist_t *nvl __unused) { struct umouse_softc *sc; sc = calloc(1, sizeof(struct umouse_softc)); sc->hci = hci; + return (sc); +} + +static int +umouse_init(void *scarg) +{ + struct umouse_softc *sc = (struct umouse_softc *)scarg; + sc->hid.protocol = 1; /* REPORT protocol */ pthread_mutex_init(&sc->mtx, NULL); pthread_mutex_init(&sc->ev_mtx, NULL); console_ptr_register(umouse_event, sc, 10); - return (sc); + return (0); } #define UREQ(x,y) ((x) | ((y) << 8)) @@ -811,6 +819,7 @@ static struct usb_devemu ue_mouse = { .ue_emu = "tablet", .ue_usbver = 3, .ue_usbspeed = USB_SPEED_HIGH, + .ue_probe = umouse_probe, .ue_init = umouse_init, .ue_request = umouse_request, .ue_data = umouse_data_handler, diff --git a/usr.sbin/bhyvectl/bhyvectl.8 b/usr.sbin/bhyvectl/bhyvectl.8 index c5282a98a767..550c4f10d8e6 100644 --- a/usr.sbin/bhyvectl/bhyvectl.8 +++ b/usr.sbin/bhyvectl/bhyvectl.8 @@ -1,3 +1,6 @@ +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" .\" Copyright (c) 2015 Christian Brueffer .\" All rights reserved. .\" @@ -22,12 +25,12 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 4, 2020 +.Dd May 8, 2025 .Dt BHYVECTL 8 .Os .Sh NAME .Nm bhyvectl -.Nd "control utility for bhyve instances" +.Nd control utility for bhyve instances .Sh SYNOPSIS .Nm .Fl -vm= Ns Ar <vmname> @@ -37,8 +40,8 @@ .Op Fl -inject-nmi .Op Fl -force-reset .Op Fl -force-poweroff -.Op Fl -checkpoint= Ns Ar <filename> -.Op Fl -suspend= Ns Ar <filename> +.Op Fl -checkpoint= Ns Ar <file> +.Op Fl -suspend= Ns Ar <file> .Sh DESCRIPTION The .Nm @@ -46,17 +49,8 @@ command is a control utility for active .Xr bhyve 8 virtual machine instances. .Pp -.Em Note : -Most -.Nm -flags are intended for querying and setting the state of an active instance. -These commands are intended for development purposes, and are not documented here. -A complete list can be obtained by executing -.Nm -without any arguments. -.Pp The user-facing options are as follows: -.Bl -tag -width ".Fl d Ar argument" +.Bl -tag -width "--checkpoint=<file>" .It Fl -vm= Ns Ar <vmname> Operate on the virtual machine .Ar <vmname> . @@ -72,24 +66,38 @@ Inject a non-maskable interrupt (NMI) into the VM. Force the VM to reset. .It Fl -force-poweroff Force the VM to power off. -.It Fl -checkpoint= Ns Ar <filename> +.It Fl -checkpoint= Ns Ar <file> Save a snapshot of a virtual machine. The guest memory contents are saved in the file given in -.Ar <filename> . +.Ar <file> . The guest device and vCPU state are saved in the file -.Ar <filename>.kern . -.It Fl -suspend= Ns Ar <filename> +.Ar <file>.kern . +.It Fl -suspend= Ns Ar <file> Save a snapshot of a virtual machine similar to .Fl -checkpoint . The virtual machine will terminate after the snapshot has been saved. .El +.Pp +.Em Note : +Most +.Nm +flags are intended for querying and setting +the state of an active instance. +These commands are intended for development purposes, +and are not documented here. +A complete list can be obtained by executing +.Nm +without any arguments. .Sh EXIT STATUS .Ex -std .Sh EXAMPLES Destroy the VM called fbsd10: .Pp -.Dl "bhyvectl --vm=fbsd10 --destroy" +.Dl bhyvectl --vm=fbsd10 --destroy +.Pp +Running VMs will be visible in +.Pa /dev/vmm/ . .Sh COMPATIBILITY The snapshot file format is not yet stable and is subject to future changes. Backwards compatibility support for the current snapshot file format is not diff --git a/usr.sbin/bluetooth/ath3kfw/ath3kfw.8 b/usr.sbin/bluetooth/ath3kfw/ath3kfw.8 index beb1b102b194..9e54445a012b 100644 --- a/usr.sbin/bluetooth/ath3kfw/ath3kfw.8 +++ b/usr.sbin/bluetooth/ath3kfw/ath3kfw.8 @@ -26,12 +26,12 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 31, 2024 +.Dd July 15, 2025 .Dt ATH3KFW 8 .Os .Sh NAME .Nm ath3kfw -.Nd download firmware for Atheros AR3011/AR3012 Bluetooth USB devices +.Nd load firmware for Atheros AR3011/AR3012 Bluetooth USB devices .Sh SYNOPSIS .Nm .Op Fl DI diff --git a/usr.sbin/bluetooth/bcmfw/bcmfw.8 b/usr.sbin/bluetooth/bcmfw/bcmfw.8 index 50e9739340ee..28f0cbbafe00 100644 --- a/usr.sbin/bluetooth/bcmfw/bcmfw.8 +++ b/usr.sbin/bluetooth/bcmfw/bcmfw.8 @@ -25,12 +25,12 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 31, 2024 +.Dd July 15, 2025 .Dt BCMFW 8 .Os .Sh NAME .Nm bcmfw -.Nd download firmware for Broadcom BCM2033 Bluetooth USB devices +.Nd load firmware for Broadcom BCM2033 Bluetooth USB devices .Sh SYNOPSIS .Nm .Op Fl h diff --git a/usr.sbin/bluetooth/bluetooth-config/bluetooth-config.sh b/usr.sbin/bluetooth/bluetooth-config/bluetooth-config.sh index 48a399a82fc7..148325fcecbc 100755 --- a/usr.sbin/bluetooth/bluetooth-config/bluetooth-config.sh +++ b/usr.sbin/bluetooth/bluetooth-config/bluetooth-config.sh @@ -17,7 +17,7 @@ main() { unset node device started bdaddresses retry # Only one command at the moment is scan (+ add) -[ "$#" -eq 1 -a "$1" = "scan" ] || print_syntax +[ "$1" = "scan" ] || print_syntax shift # Get command line options @@ -28,6 +28,12 @@ while getopts :d:n: arg; do ?) print_syntax;; esac done +shift "$((OPTIND-1))" + +# If there's leftover parameters, print usage +[ "$#" -eq 0 ] || print_syntax +shift + # No use running without super user rights if [ $( id -u ) -ne 0 ]; then diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 index 87f34435d3f4..ac32a675aa63 100644 --- a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 @@ -26,12 +26,12 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd September 15, 2024 +.Dd July 15, 2025 .Dt IWMBTFW 8 .Os .Sh NAME .Nm iwmbtfw -.Nd download firmware for Intel Wireless AC Bluetooth USB devices +.Nd load firmware for Intel Wireless AC Bluetooth USB devices .Sh SYNOPSIS .Nm .Op Fl DI diff --git a/usr.sbin/bluetooth/rtlbtfw/main.c b/usr.sbin/bluetooth/rtlbtfw/main.c index 029c04f98b26..e87a98036265 100644 --- a/usr.sbin/bluetooth/rtlbtfw/main.c +++ b/usr.sbin/bluetooth/rtlbtfw/main.c @@ -67,6 +67,9 @@ static struct rtlbt_devid rtlbt_list[] = { /* Realtek 8822CU Bluetooth devices */ { .vendor_id = 0x13d3, .product_id = 0x3549 }, + /* 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 }, @@ -76,7 +79,6 @@ static struct rtlbt_devid rtlbt_list[] = { { .vendor_id = 0x04ca, .product_id = 0x4006 }, { .vendor_id = 0x0cb8, .product_id = 0xc549 }, -#ifdef RTLBTFW_SUPPORTS_FW_V2 /* Realtek 8852CE Bluetooth devices */ { .vendor_id = 0x04ca, .product_id = 0x4007 }, { .vendor_id = 0x04c5, .product_id = 0x1675 }, @@ -84,12 +86,28 @@ static struct rtlbt_devid rtlbt_list[] = { { .vendor_id = 0x13d3, .product_id = 0x3587 }, { .vendor_id = 0x13d3, .product_id = 0x3586 }, { .vendor_id = 0x13d3, .product_id = 0x3592 }, -#endif + { .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 }, + { .vendor_id = 0x13d3, .product_id = 0x3591 }, + { .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 }, /* Realtek 8723AE Bluetooth devices */ { .vendor_id = 0x0930, .product_id = 0x021d }, @@ -112,6 +130,7 @@ static struct rtlbt_devid rtlbt_list[] = { { .vendor_id = 0x2ff8, .product_id = 0xb011 }, /* Realtek 8761BUV Bluetooth devices */ + { .vendor_id = 0x2c4e, .product_id = 0x0115 }, { .vendor_id = 0x2357, .product_id = 0x0604 }, { .vendor_id = 0x0b05, .product_id = 0x190e }, { .vendor_id = 0x2550, .product_id = 0x8761 }, @@ -311,6 +330,7 @@ main(int argc, char *argv[]) char *firmware_dir = NULL; char *firmware_path = NULL; char *config_path = NULL; + const char *fw_suffix; int retcode = 1; const struct rtlbt_id_table *ic; uint8_t rom_version; @@ -409,7 +429,8 @@ main(int argc, char *argv[]) if (firmware_dir == NULL) firmware_dir = strdup(_DEFAULT_RTLBT_FIRMWARE_PATH); - firmware_path = rtlbt_get_fwname(ic->fw_name, firmware_dir, "_fw.bin"); + fw_suffix = ic->fw_suffix == NULL ? "_fw.bin" : ic->fw_suffix; + firmware_path = rtlbt_get_fwname(ic->fw_name, firmware_dir, fw_suffix); if (firmware_path == NULL) goto shutdown; @@ -448,7 +469,18 @@ main(int argc, char *argv[]) rtlbt_debug("rom_version = %d", rom_version); /* Load in the firmware */ - r = rtlbt_parse_fwfile_v1(&fw, rom_version); + if (fw_type == RTLBT_FW_TYPE_V2) { + uint8_t key_id, reg_val[2]; + r = rtlbt_read_reg16(hdl, RTLBT_SEC_PROJ, reg_val); + if (r < 0) { + rtlbt_err("rtlbt_read_reg16() failed code %d", r); + goto shutdown; + } + key_id = reg_val[0]; + rtlbt_debug("key_id = %d", key_id); + r = rtlbt_parse_fwfile_v2(&fw, rom_version, key_id); + } else + r = rtlbt_parse_fwfile_v1(&fw, rom_version); if (r < 0) { rtlbt_err("Parseing firmware file failed"); goto shutdown; diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c index bb3d20d79527..d7e9f2f939c6 100644 --- a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c +++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c @@ -105,20 +105,31 @@ static const struct rtlbt_id_table rtlbt_ic_id_table[] = { .hci_version = 0xb, .flags = RTLBT_IC_FLAG_MSFT, .fw_name = "rtl8852bu", -#ifdef RTLBTFW_SUPPORTS_FW_V2 }, { /* 8852C */ .lmp_subversion = RTLBT_ROM_LMP_8852A, .hci_revision = 0xc, .hci_version = 0xc, .flags = RTLBT_IC_FLAG_MSFT, .fw_name = "rtl8852cu", + .fw_suffix = "_fw_v2.bin", }, { /* 8851B */ .lmp_subversion = RTLBT_ROM_LMP_8851B, .hci_revision = 0xb, .hci_version = 0xc, .flags = RTLBT_IC_FLAG_MSFT, .fw_name = "rtl8851bu", -#endif + }, { /* 8922A */ + .lmp_subversion = RTLBT_ROM_LMP_8922A, + .hci_revision = 0xa, + .hci_version = 0xc, + .flags = RTLBT_IC_FLAG_MSFT, + .fw_name = "rtl8922au", + }, { /* 8852BT/8852BE-VT */ + .lmp_subversion = RTLBT_ROM_LMP_8852A, + .hci_revision = 0x87, + .hci_version = 0xc, + .flags = RTLBT_IC_FLAG_MSFT, + .fw_name = "rtl8852btu", }, }; @@ -139,15 +150,15 @@ static const uint16_t project_ids[] = { [ 25 ] = RTLBT_ROM_LMP_8852A, /* 8852CU */ [ 33 ] = RTLBT_ROM_LMP_8822B, /* 8822EU */ [ 36 ] = RTLBT_ROM_LMP_8851B, /* 8851BU */ + [ 44 ] = RTLBT_ROM_LMP_8922A, /* 8922A */ + [ 47 ] = RTLBT_ROM_LMP_8852A, /* 8852BT */ }; /* Signatures */ static const uint8_t fw_header_sig_v1[8] = {0x52, 0x65, 0x61, 0x6C, 0x74, 0x65, 0x63, 0x68}; /* Realtech */ -#ifdef RTLBTFW_SUPPORTS_FW_V2 static const uint8_t fw_header_sig_v2[8] = {0x52, 0x54, 0x42, 0x54, 0x43, 0x6F, 0x72, 0x65}; /* RTBTCore */ -#endif static const uint8_t fw_ext_sig[4] = {0x51, 0x04, 0xFD, 0x77}; int @@ -260,12 +271,10 @@ rtlbt_get_fw_type(struct rtlbt_firmware *fw, uint16_t *fw_lmp_subversion) fw_type = RTLBT_FW_TYPE_V1; fw_header_len = sizeof(struct rtlbt_fw_header_v1); } else -#ifdef RTLBTFW_SUPPORTS_FW_V2 if (memcmp(fw->buf, fw_header_sig_v2, sizeof(fw_header_sig_v2)) == 0) { fw_type = RTLBT_FW_TYPE_V2; fw_header_len = sizeof(struct rtlbt_fw_header_v2); } else -#endif return (RTLBT_FW_TYPE_UNKNOWN); if (fw->len < fw_header_len + sizeof(fw_ext_sig) + 4) { @@ -367,6 +376,194 @@ rtlbt_parse_fwfile_v1(struct rtlbt_firmware *fw, uint8_t rom_version) return (0); } +static void * +rtlbt_iov_fetch(struct rtlbt_iov *iov, uint32_t len) +{ + void *data = NULL; + + if (iov->len >= len) { + data = iov->data; + iov->data += len; + iov->len -= len; + } + + return (data); +} + +static int +rtlbt_patch_entry_cmp(struct rtlbt_patch_entry *a, struct rtlbt_patch_entry *b, + void *thunk __unused) +{ + return ((a->prio > b->prio) - (a->prio < b->prio)); +} + +static int +rtlbt_parse_section(struct rtlbt_patch_list *patch_list, uint32_t opcode, + uint8_t *data, uint32_t len, uint8_t rom_version, uint8_t key_id) +{ + struct rtlbt_sec_hdr *hdr; + struct rtlbt_patch_entry *entry; + struct rtlbt_subsec_hdr *subsec_hdr; + struct rtlbt_subsec_security_hdr *subsec_security_hdr; + uint16_t num_subsecs; + uint8_t *subsec_data; + uint32_t subsec_len; + int i, sec_len = 0; + struct rtlbt_iov iov = { + .data = data, + .len = len, + }; + + hdr = rtlbt_iov_fetch(&iov, sizeof(*hdr)); + if (hdr == NULL) { + errno = EINVAL; + return (-1); + } + num_subsecs = le16toh(hdr->num); + + for (i = 0; i < num_subsecs; i++) { + subsec_hdr = rtlbt_iov_fetch(&iov, sizeof(*subsec_hdr)); + if (subsec_hdr == NULL) + break; + subsec_len = le32toh(subsec_hdr->len); + + rtlbt_debug("subsection, eco 0x%02x, prio 0x%02x, len 0x%x", + subsec_hdr->eco, subsec_hdr->prio, subsec_len); + + subsec_data = rtlbt_iov_fetch(&iov, subsec_len); + if (subsec_data == NULL) + break; + + if (subsec_hdr->eco == rom_version + 1) { + if (opcode == RTLBT_PATCH_SECURITY_HEADER) { + subsec_security_hdr = (void *)subsec_hdr; + if (subsec_security_hdr->key_id == key_id) + break; + continue; + } + + entry = calloc(1, sizeof(*entry)); + if (entry == NULL) { + errno = ENOMEM; + return (-1); + } + *entry = (struct rtlbt_patch_entry) { + .opcode = opcode, + .len = subsec_len, + .prio = subsec_hdr->prio, + .data = subsec_data, + }; + SLIST_INSERT_HEAD(patch_list, entry, next); + sec_len += subsec_len; + } + } + + return (sec_len); +} + +int +rtlbt_parse_fwfile_v2(struct rtlbt_firmware *fw, uint8_t rom_version, + uint8_t key_id) +{ + struct rtlbt_fw_header_v2 *hdr; + struct rtlbt_section *section; + struct rtlbt_patch_entry *entry; + uint32_t num_sections; + uint32_t section_len; + uint32_t opcode; + int seclen, len = 0, patch_len = 0; + uint32_t i; + uint8_t *section_data, *patch_buf; + struct rtlbt_patch_list patch_list = + SLIST_HEAD_INITIALIZER(patch_list); + struct rtlbt_iov iov = { + .data = fw->buf, + .len = fw->len - 7, + }; + + hdr = rtlbt_iov_fetch(&iov, sizeof(*hdr)); + if (hdr == NULL) { + errno = EINVAL; + return (-1); + } + num_sections = le32toh(hdr->num_sections); + + rtlbt_debug("FW version %02x%02x%02x%02x-%02x%02x%02x%02x", + hdr->fw_version[0], hdr->fw_version[1], + hdr->fw_version[2], hdr->fw_version[3], + hdr->fw_version[4], hdr->fw_version[5], + hdr->fw_version[6], hdr->fw_version[7]); + + for (i = 0; i < num_sections; i++) { + section = rtlbt_iov_fetch(&iov, sizeof(*section)); + if (section == NULL) + break; + section_len = le32toh(section->len); + opcode = le32toh(section->opcode); + + rtlbt_debug("section, opcode 0x%08x", section->opcode); + + section_data = rtlbt_iov_fetch(&iov, section_len); + if (section_data == NULL) + break; + + seclen = 0; + switch (opcode) { + case RTLBT_PATCH_SECURITY_HEADER: + if (key_id == 0) + break; + /* FALLTHROUGH */ + case RTLBT_PATCH_SNIPPETS: + case RTLBT_PATCH_DUMMY_HEADER: + seclen = rtlbt_parse_section(&patch_list, opcode, + section_data, section_len, rom_version, key_id); + break; + default: + break; + } + if (seclen < 0) { + rtlbt_err("Section parse (0x%08x) failed. err %d", + opcode, errno); + return (-1); + } + len += seclen; + } + + if (len == 0) { + errno = ENOMSG; + return (-1); + } + + patch_buf = calloc(1, len); + if (patch_buf == NULL) { + errno = ENOMEM; + return (-1); + } + + SLIST_MERGESORT(&patch_list, NULL, + rtlbt_patch_entry_cmp, rtlbt_patch_entry, next); + while (!SLIST_EMPTY(&patch_list)) { + entry = SLIST_FIRST(&patch_list); + rtlbt_debug("opcode 0x%08x, addr 0x%p, len 0x%x", + entry->opcode, entry->data, entry->len); + memcpy(patch_buf + patch_len, entry->data, entry->len); + patch_len += entry->len; + SLIST_REMOVE_HEAD(&patch_list, next); + free(entry); + } + + if (patch_len == 0) { + errno = EPERM; + return (-1); + } + + free(fw->buf); + fw->buf = patch_buf; + fw->len = patch_len; + + return (0); +} + int rtlbt_append_fwfile(struct rtlbt_firmware *fw, struct rtlbt_firmware *opt) { diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h index 340abacba759..e9af6c93950e 100644 --- a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h +++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h @@ -30,7 +30,8 @@ #ifndef __RTLBT_FW_H__ #define __RTLBT_FW_H__ -#include <stdbool.h> +#include <sys/queue.h> +#include <sys/queue_mergesort.h> #define RTLBT_ROM_LMP_8703B 0x8703 #define RTLBT_ROM_LMP_8723A 0x1200 @@ -40,13 +41,16 @@ #define RTLBT_ROM_LMP_8822B 0x8822 #define RTLBT_ROM_LMP_8852A 0x8852 #define RTLBT_ROM_LMP_8851B 0x8851 +#define RTLBT_ROM_LMP_8922A 0x8922 + +#define RTLBT_PATCH_SNIPPETS 0x01 +#define RTLBT_PATCH_DUMMY_HEADER 0x02 +#define RTLBT_PATCH_SECURITY_HEADER 0x03 enum rtlbt_fw_type { RTLBT_FW_TYPE_UNKNOWN, RTLBT_FW_TYPE_V1, -#ifdef RTLBTFW_SUPPORTS_FW_V2 RTLBT_FW_TYPE_V2, -#endif }; struct rtlbt_id_table { @@ -58,6 +62,7 @@ struct rtlbt_id_table { #define RTLBT_IC_FLAG_CONFIG (1 << 1) #define RTLBT_IC_FLAG_MSFT (2 << 1) const char *fw_name; + const char *fw_suffix; }; struct rtlbt_firmware { @@ -66,6 +71,21 @@ struct rtlbt_firmware { unsigned char *buf; }; +SLIST_HEAD(rtlbt_patch_list, rtlbt_patch_entry); + +struct rtlbt_patch_entry { + SLIST_ENTRY(rtlbt_patch_entry) next; + uint32_t opcode; + uint32_t len; + uint8_t prio; + uint8_t *data; +}; + +struct rtlbt_iov { + uint8_t *data; + uint32_t len; +}; + struct rtlbt_fw_header_v1 { uint8_t signature[8]; uint32_t fw_version; @@ -78,6 +98,32 @@ struct rtlbt_fw_header_v2 { uint32_t num_sections; } __attribute__ ((packed)); +struct rtlbt_section { + uint32_t opcode; + uint32_t len; + uint8_t data[]; +} __attribute__ ((packed)); + +struct rtlbt_sec_hdr { + uint16_t num; + uint16_t reserved; +} __attribute__ ((packed)); + +struct rtlbt_subsec_hdr { + uint8_t eco; + uint8_t prio; + uint8_t cb[2]; + uint32_t len; +} __attribute__ ((packed)); + +struct rtlbt_subsec_security_hdr { + uint8_t eco; + uint8_t prio; + uint8_t key_id; + uint8_t reserved; + uint32_t len; +} __attribute__ ((packed)); + int rtlbt_fw_read(struct rtlbt_firmware *fw, const char *fwname); void rtlbt_fw_free(struct rtlbt_firmware *fw); char *rtlbt_get_fwname(const char *fw_name, const char *prefix, @@ -87,6 +133,8 @@ const struct rtlbt_id_table *rtlbt_get_ic(uint16_t lmp_subversion, enum rtlbt_fw_type rtlbt_get_fw_type(struct rtlbt_firmware *fw, uint16_t *fw_lmp_subversion); int rtlbt_parse_fwfile_v1(struct rtlbt_firmware *fw, uint8_t rom_version); +int rtlbt_parse_fwfile_v2(struct rtlbt_firmware *fw, uint8_t rom_version, + uint8_t reg_id); int rtlbt_append_fwfile(struct rtlbt_firmware *fw, struct rtlbt_firmware *opt); #endif diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c index 493358294c07..82e22d406ea9 100644 --- a/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c +++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c @@ -179,6 +179,41 @@ rtlbt_read_rom_ver(struct libusb_device_handle *hdl, uint8_t *ver) } int +rtlbt_read_reg16(struct libusb_device_handle *hdl, + struct rtlbt_vendor_cmd *vcmd, uint8_t *resp) +{ + int ret, transferred; + struct rtlbt_hci_event_cmd_compl *event; + uint8_t cmd_buf[offsetof(struct rtlbt_hci_cmd, data) + sizeof(*vcmd)]; + struct rtlbt_hci_cmd *cmd = (struct rtlbt_hci_cmd *)cmd_buf; + cmd->opcode = htole16(0xfc61); + cmd->length = sizeof(struct rtlbt_vendor_cmd); + memcpy(cmd->data, vcmd, sizeof(struct rtlbt_vendor_cmd)); + uint8_t buf[RTLBT_HCI_EVT_COMPL_SIZE(struct rtlbt_vendor_rp)]; + + memset(buf, 0, sizeof(buf)); + + ret = rtlbt_hci_command(hdl, + cmd, + buf, + sizeof(buf), + &transferred, + RTLBT_HCI_CMD_TIMEOUT); + + if (ret < 0 || transferred != sizeof(buf)) { + rtlbt_debug("Can't read reg16: code=%d, size=%d", + ret, + transferred); + return (-1); + } + + event = (struct rtlbt_hci_event_cmd_compl *)buf; + memcpy(resp, &(((struct rtlbt_vendor_rp *)event->data)->data), 2); + + return (0); +} + +int rtlbt_load_fwfile(struct libusb_device_handle *hdl, const struct rtlbt_firmware *fw) { @@ -189,19 +224,18 @@ rtlbt_load_fwfile(struct libusb_device_handle *hdl, uint8_t *data = fw->buf; int frag_num = fw->len / RTLBT_MAX_CMD_DATA_LEN + 1; int frag_len = RTLBT_MAX_CMD_DATA_LEN; - int i; + int i, j; int ret, transferred; - for (i = 0; i < frag_num; i++) { + for (i = 0, j = 0; i < frag_num; i++, j++) { rtlbt_debug("download fw (%d/%d)", i + 1, frag_num); memset(cmd_buf, 0, sizeof(cmd_buf)); cmd->opcode = htole16(0xfc20); - if (i > 0x7f) - dl_cmd->index = (i & 0x7f) + 1; - else - dl_cmd->index = i; + if (j > 0x7f) + j = 1; + dl_cmd->index = j; if (i == (frag_num - 1)) { dl_cmd->index |= 0x80; /* data end */ diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h index bc7c9fee3f57..a7200a440272 100644 --- a/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h +++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h @@ -95,9 +95,22 @@ struct rtlbt_hci_dl_rp { uint8_t index; } __attribute__ ((packed)); +/* Vendor USB request payload */ +struct rtlbt_vendor_cmd { + uint8_t data[5]; +} __attribute__ ((packed)); +#define RTLBT_SEC_PROJ (&(struct rtlbt_vendor_cmd) {{0x10, 0xA4, 0x0D, 0x00, 0xb0}}) + +struct rtlbt_vendor_rp { + uint8_t status; + uint8_t data[2]; +}; + int rtlbt_read_local_ver(struct libusb_device_handle *hdl, ng_hci_read_local_ver_rp *ver); int rtlbt_read_rom_ver(struct libusb_device_handle *hdl, uint8_t *ver); +int rtlbt_read_reg16(struct libusb_device_handle *hdl, + struct rtlbt_vendor_cmd *cmd, uint8_t *resp); int rtlbt_load_fwfile(struct libusb_device_handle *hdl, const struct rtlbt_firmware *fw); diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.8 b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.8 index c3c0b83d97e5..5cae9c9d288d 100644 --- a/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.8 +++ b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.8 @@ -23,13 +23,12 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 19, 2023 +.Dd July 15, 2025 .Dt RTLBTFW 8 .Os .Sh NAME .Nm rtlbtfw -.Nd firmware download utility for Realtek 87XX/88XX chip based Bluetooth -USB devices +.Nd load firmware for Realtek 87XX/88XX Bluetooth USB devices .Sh SYNOPSIS .Nm .Fl d Ar device_name diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf index d544913aaa12..61ae53db8f39 100644 --- a/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf +++ b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf @@ -46,6 +46,16 @@ notify 100 { action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; }; +# Realtek 8851BE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x13d3"; + match "product" "0x3600"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + # Realtek 8852AE Bluetooth devices notify 100 { match "system" "USB"; @@ -113,6 +123,14 @@ notify 100 { match "product" "(0x3587|0x3586|0x3592)"; 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" "0x0489"; + match "product" "0xe122"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; # Realtek 8852BE Bluetooth devices notify 100 { @@ -128,7 +146,43 @@ notify 100 { match "subsystem" "DEVICE"; match "type" "ATTACH"; match "vendor" "0x0bda"; - match "product" "0x887b"; + 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"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0489"; + match "product" "(0xe123|0xe125)"; + 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 { @@ -136,7 +190,15 @@ notify 100 { match "subsystem" "DEVICE"; match "type" "ATTACH"; match "vendor" "0x13d3"; - match "product" "0x3571"; + match "product" "(0x3617|0x3616)"; + 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" "0x0489"; + match "product" "0xe130"; action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; }; @@ -217,6 +279,14 @@ notify 100 { match "system" "USB"; match "subsystem" "DEVICE"; match "type" "ATTACH"; + match "vendor" "0x2c4e"; + match "product" "0x0115"; + 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" "0x2357"; match "product" "0x0604"; action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; diff --git a/usr.sbin/bluetooth/sdpd/server.c b/usr.sbin/bluetooth/sdpd/server.c index ab398cd9339f..05a4cb5f0236 100644 --- a/usr.sbin/bluetooth/sdpd/server.c +++ b/usr.sbin/bluetooth/sdpd/server.c @@ -345,14 +345,12 @@ server_accept_client(server_p srv, int32_t fd) return; } } else { - struct xucred cr; + uid_t uid; + gid_t gid; struct passwd *pw; /* Get peer's credentials */ - memset(&cr, 0, sizeof(cr)); - size = sizeof(cr); - - if (getsockopt(cfd, 0, LOCAL_PEERCRED, &cr, &size) < 0) { + if (getpeereid(cfd, &uid, &gid) < 0) { log_err("Could not get peer's credentials. %s (%d)", strerror(errno), errno); close(cfd); @@ -360,12 +358,12 @@ server_accept_client(server_p srv, int32_t fd) } /* Check credentials */ - pw = getpwuid(cr.cr_uid); + pw = getpwuid(uid); if (pw != NULL) priv = (strcmp(pw->pw_name, "root") == 0); else log_warning("Could not verify credentials for uid %d", - cr.cr_uid); + uid); memcpy(&srv->req_sa.l2cap_bdaddr, NG_HCI_BDADDR_ANY, sizeof(srv->req_sa.l2cap_bdaddr)); diff --git a/usr.sbin/bsdconfig/Makefile b/usr.sbin/bsdconfig/Makefile index 5b03ebbe39f9..a3b72d248a14 100644 --- a/usr.sbin/bsdconfig/Makefile +++ b/usr.sbin/bsdconfig/Makefile @@ -1,5 +1,7 @@ .include <src.opts.mk> +PACKAGE= bsdconfig + SUBDIR= console \ diskmgmt \ docsinstall \ diff --git a/usr.sbin/bsdconfig/Makefile.inc b/usr.sbin/bsdconfig/Makefile.inc new file mode 100644 index 000000000000..156c588eb3de --- /dev/null +++ b/usr.sbin/bsdconfig/Makefile.inc @@ -0,0 +1 @@ +PACKAGE= bsdconfig diff --git a/usr.sbin/bsdconfig/console/Makefile.inc b/usr.sbin/bsdconfig/console/Makefile.inc new file mode 100644 index 000000000000..156c588eb3de --- /dev/null +++ b/usr.sbin/bsdconfig/console/Makefile.inc @@ -0,0 +1 @@ +PACKAGE= bsdconfig diff --git a/usr.sbin/bsdconfig/diskmgmt/Makefile.inc b/usr.sbin/bsdconfig/diskmgmt/Makefile.inc new file mode 100644 index 000000000000..156c588eb3de --- /dev/null +++ b/usr.sbin/bsdconfig/diskmgmt/Makefile.inc @@ -0,0 +1 @@ +PACKAGE= bsdconfig diff --git a/usr.sbin/bsdconfig/docsinstall/Makefile.inc b/usr.sbin/bsdconfig/docsinstall/Makefile.inc new file mode 100644 index 000000000000..156c588eb3de --- /dev/null +++ b/usr.sbin/bsdconfig/docsinstall/Makefile.inc @@ -0,0 +1 @@ +PACKAGE= bsdconfig diff --git a/usr.sbin/bsdconfig/dot/Makefile.inc b/usr.sbin/bsdconfig/dot/Makefile.inc new file mode 100644 index 000000000000..156c588eb3de --- /dev/null +++ b/usr.sbin/bsdconfig/dot/Makefile.inc @@ -0,0 +1 @@ +PACKAGE= bsdconfig diff --git a/usr.sbin/bsdconfig/includes/Makefile.inc b/usr.sbin/bsdconfig/includes/Makefile.inc new file mode 100644 index 000000000000..156c588eb3de --- /dev/null +++ b/usr.sbin/bsdconfig/includes/Makefile.inc @@ -0,0 +1 @@ +PACKAGE= bsdconfig diff --git a/usr.sbin/bsdconfig/mouse/Makefile.inc b/usr.sbin/bsdconfig/mouse/Makefile.inc new file mode 100644 index 000000000000..156c588eb3de --- /dev/null +++ b/usr.sbin/bsdconfig/mouse/Makefile.inc @@ -0,0 +1 @@ +PACKAGE= bsdconfig diff --git a/usr.sbin/bsdconfig/networking/Makefile.inc b/usr.sbin/bsdconfig/networking/Makefile.inc new file mode 100644 index 000000000000..01b5f23410c8 --- /dev/null +++ b/usr.sbin/bsdconfig/networking/Makefile.inc @@ -0,0 +1 @@ +.include "../Makefile.inc" diff --git a/usr.sbin/bsdconfig/packages/Makefile.inc b/usr.sbin/bsdconfig/packages/Makefile.inc new file mode 100644 index 000000000000..01b5f23410c8 --- /dev/null +++ b/usr.sbin/bsdconfig/packages/Makefile.inc @@ -0,0 +1 @@ +.include "../Makefile.inc" diff --git a/usr.sbin/bsdconfig/password/Makefile.inc b/usr.sbin/bsdconfig/password/Makefile.inc new file mode 100644 index 000000000000..01b5f23410c8 --- /dev/null +++ b/usr.sbin/bsdconfig/password/Makefile.inc @@ -0,0 +1 @@ +.include "../Makefile.inc" diff --git a/usr.sbin/bsdconfig/security/Makefile.inc b/usr.sbin/bsdconfig/security/Makefile.inc new file mode 100644 index 000000000000..156c588eb3de --- /dev/null +++ b/usr.sbin/bsdconfig/security/Makefile.inc @@ -0,0 +1 @@ +PACKAGE= bsdconfig diff --git a/usr.sbin/bsdconfig/share/Makefile.inc b/usr.sbin/bsdconfig/share/Makefile.inc new file mode 100644 index 000000000000..01b5f23410c8 --- /dev/null +++ b/usr.sbin/bsdconfig/share/Makefile.inc @@ -0,0 +1 @@ +.include "../Makefile.inc" diff --git a/usr.sbin/bsdconfig/startup/Makefile.inc b/usr.sbin/bsdconfig/startup/Makefile.inc new file mode 100644 index 000000000000..01b5f23410c8 --- /dev/null +++ b/usr.sbin/bsdconfig/startup/Makefile.inc @@ -0,0 +1 @@ +.include "../Makefile.inc" diff --git a/usr.sbin/bsdconfig/timezone/Makefile.inc b/usr.sbin/bsdconfig/timezone/Makefile.inc new file mode 100644 index 000000000000..01b5f23410c8 --- /dev/null +++ b/usr.sbin/bsdconfig/timezone/Makefile.inc @@ -0,0 +1 @@ +.include "../Makefile.inc" diff --git a/usr.sbin/bsdconfig/ttys/Makefile.inc b/usr.sbin/bsdconfig/ttys/Makefile.inc new file mode 100644 index 000000000000..156c588eb3de --- /dev/null +++ b/usr.sbin/bsdconfig/ttys/Makefile.inc @@ -0,0 +1 @@ +PACKAGE= bsdconfig diff --git a/usr.sbin/bsdconfig/usermgmt/Makefile.inc b/usr.sbin/bsdconfig/usermgmt/Makefile.inc new file mode 100644 index 000000000000..01b5f23410c8 --- /dev/null +++ b/usr.sbin/bsdconfig/usermgmt/Makefile.inc @@ -0,0 +1 @@ +.include "../Makefile.inc" diff --git a/usr.sbin/bsdinstall/FreeBSD-base.conf.in b/usr.sbin/bsdinstall/FreeBSD-base.conf.in new file mode 100644 index 000000000000..792c290facdf --- /dev/null +++ b/usr.sbin/bsdinstall/FreeBSD-base.conf.in @@ -0,0 +1,7 @@ +FreeBSD-base: { + url: "pkg+https://pkg.FreeBSD.org/${ABI}/%%SUBURL%%", + mirror_type: "srv", + signature_type: "fingerprints", + fingerprints: "/usr/share/keys/pkg", + enabled: yes +} diff --git a/usr.sbin/bsdinstall/Makefile b/usr.sbin/bsdinstall/Makefile index c9ba7cb52cd6..e5bb3197fa05 100644 --- a/usr.sbin/bsdinstall/Makefile +++ b/usr.sbin/bsdinstall/Makefile @@ -12,4 +12,24 @@ SCRIPTSDIR_startbsdinstall= ${LIBEXECDIR}/bsdinstall UPDATE_DEPENDFILE= no +FILESDIR= ${SHAREDIR}/bsdinstall +FILES= FreeBSD-base.conf + +_BRANCH!= ${MAKE} -C ${SRCTOP}/release -V BRANCH +BRANCH?= ${_BRANCH} +_REVISION!= ${MAKE} -C ${SRCTOP}/release -V REVISION +REVISION?= ${_REVISION} + +.if ${BRANCH} == CURRENT || ${BRANCH} == STABLE +SUBURL= base_latest +.elif ${BRANCH} == RELEASE +SUBURL= base_release_${REVISION:C/[0-9]+\.//} +.else +.warning Invalid branch "${BRANCH}" +SUBURL= base_latest +.endif + +FreeBSD-base.conf: FreeBSD-base.conf.in + sed "s|%%SUBURL%%|${SUBURL}|" < ${.ALLSRC} > ${.TARGET} + .include <bsd.prog.mk> diff --git a/usr.sbin/bsdinstall/bsdinstall.8 b/usr.sbin/bsdinstall/bsdinstall.8 index 50c8948a7989..181abdcf9d05 100644 --- a/usr.sbin/bsdinstall/bsdinstall.8 +++ b/usr.sbin/bsdinstall/bsdinstall.8 @@ -244,6 +244,17 @@ Extracts the distributions listed in .Ev DISTRIBUTIONS into .Ev BSDINSTALL_CHROOT . +.It Cm pkgbase Op Fl --no-kernel +Fetch and install base system packages to +.Ev BSDINSTALL_CHROOT . +Packages are fetched according to repository configuration in +.Ev BSDINSTALL_PKG_REPOS_DIR +if set, or +.Lk pkg.freebsd.org +otherwise. +If the +.Fl --no-kernel +option is passed, no kernel is installed. .It Cm firmware executes .Xr fwget 8 @@ -324,6 +335,17 @@ Example: .Pa https://download.freebsd.org/ftp/releases/powerpc/powerpc64/13.1-RELEASE/ or .Pa http://ftp-archive.freebsd.org/pub/FreeBSD-Archive/old-releases/amd64/12.2-RELEASE/ . +.It Ev BSDINSTALL_PKG_REPOS_DIR +Directory containing +.Xr pkg 8 +repository configuration files used by the +.Cm pkgbase +target. +See +.Sx REPOSITORY CONFIGURATION +in +.Xr pkg.conf 5 . +Default: unset .It Ev BSDINSTALL_CHROOT The directory into which the distribution files should be unpacked and the directory at which the root file system of the new system should be mounted. @@ -429,7 +451,7 @@ Each option must be preceded by the -O flag to be taken into consideration or the pool will not be created due to errors using the command .Cm zpool . Default: -.Dq Li "-O compress=lz4 -O atime=off" +.Dq Li "-O compression=on -O atime=off" .It Ev ZFSBOOT_BEROOT_NAME Name for the boot environment parent dataset. This is a non-mountable dataset meant to be a parent dataset where different diff --git a/usr.sbin/bsdinstall/partedit/part_wizard.c b/usr.sbin/bsdinstall/partedit/part_wizard.c index 44d9a08b0c78..90a8da1c3c9b 100644 --- a/usr.sbin/bsdinstall/partedit/part_wizard.c +++ b/usr.sbin/bsdinstall/partedit/part_wizard.c @@ -40,7 +40,7 @@ #include "partedit.h" -#define MIN_FREE_SPACE (1024*1024*1024) /* 1 GB */ +#define MIN_FREE_SPACE (1023*1024*1024) /* Just under 1 GB */ #define SWAP_SIZE(available) MIN(available/20, 4*1024*1024*1024LL) static char *wizard_partition(struct gmesh *mesh, const char *disk); diff --git a/usr.sbin/bsdinstall/scripts/Makefile b/usr.sbin/bsdinstall/scripts/Makefile index f3b9f07ed376..4fd59e49d506 100644 --- a/usr.sbin/bsdinstall/scripts/Makefile +++ b/usr.sbin/bsdinstall/scripts/Makefile @@ -1,3 +1,5 @@ +.include <bsd.compat.pre.mk> + SCRIPTS=auto \ adduser \ bootconfig \ @@ -17,6 +19,7 @@ SCRIPTS=auto \ netconfig \ netconfig_ipv4 \ netconfig_ipv6 \ + pkgbase \ rootpass \ script \ services \ @@ -29,4 +32,7 @@ BINDIR= ${LIBEXECDIR}/bsdinstall MAN= +pkgbase: pkgbase.in + sed "s|%%_ALL_libcompats%%|${_ALL_libcompats}|" < ${.ALLSRC} > ${.TARGET} + .include <bsd.prog.mk> diff --git a/usr.sbin/bsdinstall/scripts/auto b/usr.sbin/bsdinstall/scripts/auto index 7d041be015e7..0b47d496fdbd 100755 --- a/usr.sbin/bsdinstall/scripts/auto +++ b/usr.sbin/bsdinstall/scripts/auto @@ -35,6 +35,13 @@ f_include $BSDCFG_SHARE/dialog.subr ############################################################ GLOBALS # +# List of environment variables that may be defined by the user, but modified +# during the installation process. They are then restored when restarting this +# script. +# +user_env_vars="BSDINSTALL_DISTSITE DISTRIBUTIONS WORKAROUND_GPTACTIVE WORKAROUND_LENOVO ZFSBOOT_PARTITION_SCHEME" + +# # Strings that should be moved to an i18n file and loaded with f_include_lang() # hline_arrows_tab_enter="Press arrows, TAB or ENTER" @@ -90,6 +97,7 @@ error() --yes-label "$msg_restart" \ --yesno "$prompt" $height $width then + environment_restore exec $0 # NOTREACHED fi @@ -138,13 +146,46 @@ dialog_workaround() --yesno "$prompt" $height $width } +# environment_restore +# +# Restore a list of environment variables when this script is restarted. +# +environment_restore() +{ + for var in $user_env_vars; do + eval "if [ -n \"\${ORIG_$var}\" -o -z \"\${ORIG_$var-z}\" ]; then $var=\${ORIG_$var}; else unset $var; fi" + done +} + +# environment_save +# +# Save any user-defined environment variable that may be modified during the +# installation process. They are then restored when restarting this script. +# +environment_save() +{ + for var in $user_env_vars; do + eval "if [ -n \"\${$var}\" -o -z \"\${$var-z}\" ]; then ORIG_$var=\${$var}; else unset ORIG_$var; fi" + done +} + ############################################################ MAIN f_dprintf "Began Installation at %s" "$( date )" +environment_save + rm -rf $BSDINSTALL_TMPETC mkdir $BSDINSTALL_TMPETC +# With pkgbase, pkg OOM has been observed with QEMU-default 128 MiB memory size. +# Ensure we have at least about 256 MiB (with an allowance for rounding etc.). +physmem=$(($(sysctl -n hw.physmem) / 1048576)) +if [ $physmem -lt 200 ]; then + bsddialog --backtitle "$OSNAME Installer" --title "Warning" \ + --msgbox "Insufficient physical memory (${physmem} MiB) detected. At least 256 MiB is recommended. The installer or installed system may not function correctly." 0 0 +fi + [ -f /usr/libexec/bsdinstall/local.pre-everything ] && f_dprintf "Running local.pre-everything" && sh /usr/libexec/bsdinstall/local.pre-everything "$BSDINSTALL_CHROOT" trap true SIGINT # This section is optional @@ -153,36 +194,75 @@ trap true SIGINT # This section is optional trap error SIGINT # Catch cntrl-C here if [ -z "$BSDINSTALL_SKIP_HOSTNAME" ]; then bsdinstall hostname || error "Set hostname failed"; fi -export DISTRIBUTIONS="${DISTRIBUTIONS:-base.txz kernel.txz}" -if [ -f $BSDINSTALL_DISTDIR/MANIFEST ]; then - DISTMENU=`awk -F'\t' '!/^(kernel\.txz|base\.txz)/{print $1,$5,$6}' $BSDINSTALL_DISTDIR/MANIFEST` - DISTMENU="$(echo ${DISTMENU} | sed -E 's/\.txz//g')" +if [ -f /usr/freebsd-packages/repos/FreeBSD-base-offline.conf ]; then + HAVE_BASE_PACKAGES=yes + PKGBASE_DEFAULT_BUTTON=--default-no +else + unset HAVE_BASE_PACKAGES + unset PKGBASE_DEFAULT_BUTTON +fi - if [ -n "$DISTMENU" ]; then - exec 5>&1 - EXTRA_DISTS=$( eval bsddialog \ - --backtitle \"$OSNAME Installer\" \ - --title \"Distribution Select\" --nocancel --separate-output \ - --checklist \"Choose optional system components to install:\" \ - 0 0 0 $DISTMENU \ - 2>&1 1>&5 ) - for dist in $EXTRA_DISTS; do - export DISTRIBUTIONS="$DISTRIBUTIONS $dist.txz" - done +if [ ! -f $BSDINSTALL_DISTDIR/MANIFEST ]; then + PKGBASE=yes +else + bsddialog --backtitle "$OSNAME Installer" --title "Select Installation Type" \ + --yes-label "Traditional" --no-label "Packages (Experimental)" --yesno \ + $PKGBASE_DEFAULT_BUTTON \ + "Would you like to install the base system using traditional distribution sets or packages (experimental)?" 0 0 + if [ $? -eq 1 ]; then + PKGBASE=yes fi fi -FETCH_DISTRIBUTIONS="" -for dist in $DISTRIBUTIONS; do - if [ ! -f $BSDINSTALL_DISTDIR/$dist ]; then - FETCH_DISTRIBUTIONS="$FETCH_DISTRIBUTIONS $dist" +if [ "$PKGBASE" == yes ]; then + if [ "$HAVE_BASE_PACKAGES" == yes ]; then + bsddialog --backtitle "$OSNAME Installer" --title "Network or Offline Installation" \ + --yes-label "Network" --no-label "Offline (Limited Packages)" --yesno \ + "Would you like to fetch packages from the internet or use the limited set of packages included in this installation media?" 0 0 + if [ $? -eq 1 ]; then + export BSDINSTALL_PKG_REPOS_DIR=/usr/freebsd-packages/repos/ + else + bsdinstall netconfig || error + NETCONFIG_DONE=yes + fi + else + bsddialog --backtitle "$OSNAME Installer" --title "Network Installation" \ + --msgbox "No base system packages are included in this installation media. The next few screens will allow you to configure networking." 0 0 + bsdinstall netconfig || error + NETCONFIG_DONE=yes + fi +else + export DISTRIBUTIONS="${DISTRIBUTIONS:-base.txz kernel.txz}" + if [ -f $BSDINSTALL_DISTDIR/MANIFEST ]; then + DISTMENU=`awk -F'\t' '!/^(kernel\.txz|base\.txz)/{print $1,$5,$6}' $BSDINSTALL_DISTDIR/MANIFEST` + DISTMENU="$(echo ${DISTMENU} | sed -E 's/\.txz//g')" + + if [ -n "$DISTMENU" ]; then + exec 5>&1 + EXTRA_DISTS=$( eval bsddialog \ + --backtitle \"$OSNAME Installer\" \ + --title \"Distribution Select\" --nocancel --separate-output \ + --checklist \"Choose optional system components to install:\" \ + 0 0 0 $DISTMENU \ + 2>&1 1>&5 ) + for dist in $EXTRA_DISTS; do + export DISTRIBUTIONS="$DISTRIBUTIONS $dist.txz" + done + fi fi -done -if [ -n "$FETCH_DISTRIBUTIONS" -a -n "$BSDINSTALL_CONFIGCURRENT" ]; then - bsddialog --backtitle "$OSNAME Installer" --title "Network Installation" --msgbox "Some installation files were not found on the boot volume. The next few screens will allow you to configure networking so that they can be downloaded from the Internet." 0 0 - bsdinstall netconfig || error - NETCONFIG_DONE=yes + FETCH_DISTRIBUTIONS="" + for dist in $DISTRIBUTIONS; do + if [ ! -f $BSDINSTALL_DISTDIR/$dist ]; then + FETCH_DISTRIBUTIONS="$FETCH_DISTRIBUTIONS $dist" + fi + done + + if [ -n "$FETCH_DISTRIBUTIONS" -a -n "$BSDINSTALL_CONFIGCURRENT" ]; then + bsddialog --backtitle "$OSNAME Installer" --title "Network Installation" --msgbox "Some installation files were not found on the boot volume. The next few screens will allow you to configure networking so that they can be downloaded from the Internet." 0 0 + bsdinstall netconfig || error + NETCONFIG_DONE=yes + fi fi rm -f $PATH_FSTAB @@ -339,16 +419,20 @@ esac [ -f /usr/libexec/bsdinstall/local.pre-fetch ] && f_dprintf "Running local.pre-fetch" && sh /usr/libexec/bsdinstall/local.pre-fetch "$BSDINSTALL_CHROOT" -if [ -n "$FETCH_DISTRIBUTIONS" ]; then - exec 5>&1 - export BSDINSTALL_DISTDIR=$(`dirname $0`/fetchmissingdists 2>&1 1>&5) - FETCH_RESULT=$? - exec 5>&- +if [ "$PKGBASE" == yes ]; then + bsdinstall pkgbase || error "Installation of base system packages failed" +else + if [ -n "$FETCH_DISTRIBUTIONS" ]; then + exec 5>&1 + export BSDINSTALL_DISTDIR=$(`dirname $0`/fetchmissingdists 2>&1 1>&5) + FETCH_RESULT=$? + exec 5>&- - [ $FETCH_RESULT -ne 0 ] && error "Could not fetch remote distributions" + [ $FETCH_RESULT -ne 0 ] && error "Could not fetch remote distributions" + fi + bsdinstall checksum || error "Distribution checksum failed" + bsdinstall distextract || error "Distribution extract failed" fi -bsdinstall checksum || error "Distribution checksum failed" -bsdinstall distextract || error "Distribution extract failed" # Set up boot loader bsdinstall bootconfig || error "Failed to configure bootloader" @@ -391,7 +475,7 @@ if [ -z "$BSDINSTALL_SKIP_MANUAL" ]; then clear echo This shell is operating in a chroot in the new system. \ When finished making configuration changes, type \"exit\". - chroot "$BSDINSTALL_CHROOT" /bin/sh 2>&1 + chroot "$BSDINSTALL_CHROOT" /bin/sh -l 2>&1 fi fi diff --git a/usr.sbin/bsdinstall/scripts/bootconfig b/usr.sbin/bsdinstall/scripts/bootconfig index 618a1966095e..41243ad14b9b 100755 --- a/usr.sbin/bsdinstall/scripts/bootconfig +++ b/usr.sbin/bsdinstall/scripts/bootconfig @@ -29,6 +29,9 @@ BSDCFG_SHARE="/usr/share/bsdconfig" . $BSDCFG_SHARE/common.subr || exit 1 + +FREEBSD_BOOTLABEL=$OSNAME + f_dprintf "%s: loading_includes..." "$0" f_include $BSDCFG_SHARE/dialog.subr @@ -71,7 +74,7 @@ update_uefi_bootentry() fi $DIALOG --backtitle "$OSNAME Installer" --title 'Boot Configuration' \ - --yesno "There are multiple \"$OSNAME\" EFI boot entries. Would you like to remove them all and add a new one?" 0 0 + --yesno "One or more \"$OSNAME\" EFI boot manager entries already exist. Would you like to remove them all and add a new one?" 0 0 if [ $? -eq $DIALOG_OK ]; then for entry in $(efibootmgr | awk "\$NF == \"$EFI_LABEL_NAME\" { sub(/.*Boot/,\"\", \$1); sub(/\*/,\"\", \$1); print \$1 }"); do efibootmgr -B -b ${entry} diff --git a/usr.sbin/bsdinstall/scripts/firmware b/usr.sbin/bsdinstall/scripts/firmware index a563f0e578e4..0fc66f0a0261 100644 --- a/usr.sbin/bsdinstall/scripts/firmware +++ b/usr.sbin/bsdinstall/scripts/firmware @@ -115,11 +115,23 @@ f_quietly cp -f $BSDINSTALL_TMPETC/resolv.conf $BSDINSTALL_CHROOT/etc/ ${DIALOG} --title "$DIALOG_TITLE" --backtitle "$DIALOG_BACKTITLE" \ --infobox "Installing firmware. This may take a moment." 0 0 +pkg_install_fail= # Install each of the selected firmware packages for fw in ${selected}; do # We install one at a time in case one is not avail. # pkg-install.8 needs an option to skip unavail. ASSUME_ALWAYS_YES=YES chroot $BSDINSTALL_CHROOT pkg install -qy ${fw} + if [ $? -ne 0 ]; then + pkg_install_fail="$pkg_install_fail $fw" + fi done +if [ -n "$pkg_install_fail" ]; then + # Error(s) were likely spammed to the console; give the user a moment + # to read them. + sleep 5 + bsddialog --backtitle "$OSNAME Installer" --title "Error" \ + --msgbox "Error fetching firmware file(s)$pkg_install_fail" 0 0 + exit 1 +fi # end diff --git a/usr.sbin/bsdinstall/scripts/jail b/usr.sbin/bsdinstall/scripts/jail index 9acea20a34d8..0c3c7e125fdd 100755 --- a/usr.sbin/bsdinstall/scripts/jail +++ b/usr.sbin/bsdinstall/scripts/jail @@ -31,12 +31,23 @@ BSDCFG_SHARE="/usr/share/bsdconfig" . $BSDCFG_SHARE/common.subr || exit 1 -############################################################ MAIN +############################################################ GLOBALS -: ${BSDDIALOG_OK=0} +# +# List of environment variables that may be defined by the user, but modified +# during the installation process. They are then restored when restarting this +# script. +# +user_env_vars="BSDINSTALL_DISTSITE DISTRIBUTIONS" -f_dprintf "Began Installation at %s" "$( date )" +############################################################ FUNCTIONS +# error [$msg] +# +# Display generic error message when a script fails. An optional message +# argument can preceed the generic message. User is given the choice of +# restarting the installer or exiting. +# error() { local msg if [ -n "$1" ]; then @@ -48,16 +59,105 @@ error() { if [ $? -ne $BSDDIALOG_OK ]; then exit else - [ -z "$MIRROR_BUTTON" ] || unset BSDINSTALL_DISTSITE + environment_restore exec $0 $BSDINSTALL_CHROOT fi } +distbase() { + test ! -d $BSDINSTALL_DISTDIR && mkdir -p $BSDINSTALL_DISTDIR + + if [ ! -f $BSDINSTALL_DISTDIR/MANIFEST -a -z "$BSDINSTALL_DISTSITE" ]; then + exec 5>&1 + BSDINSTALL_DISTSITE=$(`dirname $0`/mirrorselect 2>&1 1>&5) + MIRROR_BUTTON=$? + exec 5>&- + test $MIRROR_BUTTON -eq 0 || error "No mirror selected" + export BSDINSTALL_DISTSITE + fetch -o $BSDINSTALL_DISTDIR/MANIFEST $BSDINSTALL_DISTSITE/MANIFEST || error "Could not download $BSDINSTALL_DISTSITE/MANIFEST" + fi + + : ${DISTRIBUTIONS="base.txz"}; export DISTRIBUTIONS + if [ -f $BSDINSTALL_DISTDIR/MANIFEST ]; then + DISTMENU=`cut -f 4,5,6 $BSDINSTALL_DISTDIR/MANIFEST | grep -v -e ^kernel -e ^base` + + if [ ! "$nonInteractive" == "YES" ] + then + exec 5>&1 + EXTRA_DISTS=$(echo $DISTMENU | xargs -o bsddialog \ + --backtitle "$OSNAME Installer" \ + --title "Distribution Select" --no-cancel \ + --separate-output \ + --checklist "Choose optional system components to install:" \ + 0 0 0 \ + 2>&1 1>&5) + for dist in $EXTRA_DISTS; do + export DISTRIBUTIONS="$DISTRIBUTIONS $dist.txz" + done + fi + fi + + FETCH_DISTRIBUTIONS="" + for dist in $DISTRIBUTIONS; do + if [ ! -f $BSDINSTALL_DISTDIR/$dist ]; then + FETCH_DISTRIBUTIONS="$FETCH_DISTRIBUTIONS $dist" + fi + done + FETCH_DISTRIBUTIONS=`echo $FETCH_DISTRIBUTIONS` # Trim white space + + if [ -n "$FETCH_DISTRIBUTIONS" -a -z "$BSDINSTALL_DISTSITE" ]; then + exec 5>&1 + BSDINSTALL_DISTSITE=$(`dirname $0`/mirrorselect 2>&1 1>&5) + MIRROR_BUTTON=$? + exec 5>&- + test $MIRROR_BUTTON -eq 0 || error "No mirror selected" + export BSDINSTALL_DISTSITE + fi + + if [ ! -z "$FETCH_DISTRIBUTIONS" ]; then + bsdinstall distfetch || error "Failed to fetch distribution" + fi + + bsdinstall checksum || error "Distribution checksum failed" + bsdinstall distextract || error "Distribution extract failed" +} + +# environment_restore +# +# Restore a list of environment variables when this script is restarted. +# +environment_restore() +{ + for var in $user_env_vars; do + eval "if [ -n \"\${ORIG_$var}\" -o -z \"\${ORIG_$var-z}\" ]; then $var=\${ORIG_$var}; else unset $var; fi" + done +} + +# environment_save +# +# Save any user-defined environment variable that may be modified during the +# installation process. They are then restored when restarting this script. +# +environment_save() +{ + for var in $user_env_vars; do + eval "if [ -n \"\${$var}\" -o -z \"\${$var-z}\" ]; then ORIG_$var=\${$var}; else unset ORIG_$var; fi" + done +} + +############################################################ MAIN + +: ${BSDDIALOG_OK=0} + +f_dprintf "Began Installation at %s" "$( date )" + if [ -z "$1" ]; then error "Directory can not be empty\n\nUsage:\nbsdinstall jail directory" fi export BSDINSTALL_CHROOT=$1 +environment_save + rm -rf $BSDINSTALL_TMPETC mkdir $BSDINSTALL_TMPETC mkdir -p $1 || error "mkdir failed for $1" @@ -73,61 +173,21 @@ if [ -n "$SCRIPT" ]; then . $TMPDIR/bsdinstall-installscript-preamble fi -test ! -d $BSDINSTALL_DISTDIR && mkdir -p $BSDINSTALL_DISTDIR - -if [ ! -f $BSDINSTALL_DISTDIR/MANIFEST -a -z "$BSDINSTALL_DISTSITE" ]; then - exec 5>&1 - BSDINSTALL_DISTSITE=$(`dirname $0`/mirrorselect 2>&1 1>&5) - MIRROR_BUTTON=$? - exec 5>&- - test $MIRROR_BUTTON -eq 0 || error "No mirror selected" - export BSDINSTALL_DISTSITE - fetch -o $BSDINSTALL_DISTDIR/MANIFEST $BSDINSTALL_DISTSITE/MANIFEST || error "Could not download $BSDINSTALL_DISTSITE/MANIFEST" -fi - -: ${DISTRIBUTIONS="base.txz"}; export DISTRIBUTIONS -if [ -f $BSDINSTALL_DISTDIR/MANIFEST ]; then - DISTMENU=`cut -f 4,5,6 $BSDINSTALL_DISTDIR/MANIFEST | grep -v -e ^kernel -e ^base` - - if [ ! "$nonInteractive" == "YES" ] - then - exec 5>&1 - EXTRA_DISTS=$(echo $DISTMENU | xargs -o bsddialog \ - --backtitle "$OSNAME Installer" \ - --title "Distribution Select" --no-cancel --separate-output \ - --checklist "Choose optional system components to install:" \ - 0 0 0 \ - 2>&1 1>&5) - for dist in $EXTRA_DISTS; do - export DISTRIBUTIONS="$DISTRIBUTIONS $dist.txz" - done - fi -fi - -FETCH_DISTRIBUTIONS="" -for dist in $DISTRIBUTIONS; do - if [ ! -f $BSDINSTALL_DISTDIR/$dist ]; then - FETCH_DISTRIBUTIONS="$FETCH_DISTRIBUTIONS $dist" +if [ ! "$nonInteractive" == "YES" ]; then + bsddialog --backtitle "$OSNAME Installer" --title "Select Installation Type" \ + --yes-label "Traditional" --no-label "Packages (Experimental)" --yesno \ + "Would you like to install the base system using traditional distribution sets or packages (experimental)?" 0 0 + if [ $? -eq 1 ]; then + PKGBASE=yes fi -done -FETCH_DISTRIBUTIONS=`echo $FETCH_DISTRIBUTIONS` # Trim white space - -if [ -n "$FETCH_DISTRIBUTIONS" -a -z "$BSDINSTALL_DISTSITE" ]; then - exec 5>&1 - BSDINSTALL_DISTSITE=$(`dirname $0`/mirrorselect 2>&1 1>&5) - MIRROR_BUTTON=$? - exec 5>&- - test $MIRROR_BUTTON -eq 0 || error "No mirror selected" - export BSDINSTALL_DISTSITE fi -if [ ! -z "$FETCH_DISTRIBUTIONS" ]; then - bsdinstall distfetch || error "Failed to fetch distribution" +if [ "$PKGBASE" == yes ]; then + bsdinstall pkgbase --no-kernel || error "Installation of base system packages failed" +else + distbase fi -bsdinstall checksum || error "Distribution checksum failed" -bsdinstall distextract || error "Distribution extract failed" - if [ ! "$nonInteractive" == "YES" ] then bsdinstall rootpass || error "Could not set root password" @@ -147,7 +207,7 @@ fi trap error SIGINT # SIGINT is bad again bsdinstall config || error "Failed to save config" cp /etc/resolv.conf $1/etc -cp /etc/localtime $1/etc +cp -P /etc/localtime $1/etc cp /var/db/zoneinfo $1/var/db # Run post-install script diff --git a/usr.sbin/bsdinstall/scripts/pkgbase.in b/usr.sbin/bsdinstall/scripts/pkgbase.in new file mode 100755 index 000000000000..d123394c170e --- /dev/null +++ b/usr.sbin/bsdinstall/scripts/pkgbase.in @@ -0,0 +1,281 @@ +#!/usr/libexec/flua + +-- SPDX-License-Identifier: BSD-2-Clause +-- +-- Copyright(c) 2025 The FreeBSD Foundation. +-- +-- This software was developed by Isaac Freund <ifreund@freebsdfoundation.org> +-- under sponsorship from the FreeBSD Foundation. + +local sys_wait = require("posix.sys.wait") +local unistd = require("posix.unistd") + +local all_libcompats <const> = "%%_ALL_libcompats%%" + +-- Run a command using the OS shell and capture the stdout +-- Strips exactly one trailing newline if present, does not strip any other whitespace. +-- Asserts that the command exits cleanly +local function capture(command) + local p = io.popen(command) + local output = p:read("*a") + assert(p:close()) + -- Strip exactly one trailing newline from the output, if there is one + return output:match("(.-)\n$") or output +end + +local function append_list(list, other) + for _, item in ipairs(other) do + table.insert(list, item) + end +end + +-- Read from the given fd until EOF +-- Returns all the data read as a single string +local function read_all(fd) + local ret = "" + repeat + local buffer = assert(unistd.read(fd, 1024)) + ret = ret .. buffer + until buffer == "" + return ret +end + +-- Run bsddialog with the given argument list +-- Returns the exit code and stderr output of bsddialog +local function bsddialog(args) + local r, w = assert(unistd.pipe()) + + local pid = assert(unistd.fork()) + if pid == 0 then + assert(unistd.close(r)) + assert(unistd.dup2(w, 2)) + assert(unistd.execp("bsddialog", args)) + unistd._exit() + end + assert(unistd.close(w)) + + local output = read_all(r) + assert(unistd.close(r)) + + local _, _, exit_code = assert(sys_wait.wait(pid)) + return exit_code, output +end + +-- Prompts the user for a yes/no answer to the given question using bsddialog +-- Returns true if the user answers yes and false if the user answers no. +local function prompt_yn(question) + local exit_code = bsddialog({ + "--yesno", + "--disable-esc", + question, + 0, 0, -- autosize + }) + return exit_code == 0 +end + +-- Creates a dialog for component selection mirroring the +-- traditional tarball component selection dialog. +local function select_components(components, options) + local descriptions = { + kernel_dbg = "Kernel debug info", + base_dbg = "Base system debug info", + src = "System source tree", + tests = "Test suite", + lib32 = "32-bit compatibility libraries", + lib32_dbg = "32-bit compatibility libraries debug info", + } + local defaults = { + kernel_dbg = "on", + base_dbg = "off", + src = "off", + tests = "off", + lib32 = "on", + lib32_dbg = "off", + } + + -- Sorting the components is necessary to ensure that the ordering is + -- consistent in the UI. + local sorted_components = {} + for component, _ in pairs(components) do + table.insert(sorted_components, component) + end + table.sort(sorted_components) + + local checklist_items = {} + for _, component in ipairs(sorted_components) do + if component ~= "base" and component ~= "kernel" and + not (component == "kernel_dbg" and options.no_kernel) and + #components[component] > 0 then + local description = descriptions[component] or "''" + local default = defaults[component] or "off" + table.insert(checklist_items, component) + table.insert(checklist_items, description) + table.insert(checklist_items, default) + end + end + + local bsddialog_args = { + "--backtitle", "FreeBSD Installer", + "--title", "Select System Components", + "--nocancel", + "--disable-esc", + "--separate-output", + "--checklist", "Choose optional system components to install:", + "0", "0", "0", -- autosize + } + append_list(bsddialog_args, checklist_items) + + local exit_code, output = bsddialog(bsddialog_args) + -- 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 + -- hopefully useful stack trace. + assert(exit_code == 0) + + local selected = {"base"} + if not options.no_kernel then + table.insert(selected, "kernel") + end + for component in output:gmatch("[^\n]+") do + table.insert(selected, component) + end + + return selected +end + +-- Returns a list of pkgbase packages selected by the user +local function select_packages(pkg, options) + local components = { + kernel = {}, + kernel_dbg = {}, + base = {}, + base_dbg = {}, + src = {}, + tests = {}, + } + + for compat in all_libcompats:gmatch("%S+") do + components["lib" .. compat] = {} + components["lib" .. compat .. "_dbg"] = {} + end + + local rquery = capture(pkg .. "rquery -U -r FreeBSD-base %n") + for package in rquery:gmatch("[^\n]+") do + if package == "FreeBSD-src" or package:match("^FreeBSD%-src%-.*") then + table.insert(components["src"], package) + elseif package == "FreeBSD-tests" or package:match("^FreeBSD%-tests%-.*") then + table.insert(components["tests"], package) + elseif package:match("^FreeBSD%-kernel%-.*") and + package ~= "FreeBSD-kernel-man" + then + -- Kernels other than FreeBSD-kernel-generic are ignored + if package == "FreeBSD-kernel-generic" then + table.insert(components["kernel"], package) + elseif package == "FreeBSD-kernel-generic-dbg" then + table.insert(components["kernel_dbg"], package) + end + elseif package:match(".*%-dbg$") then + table.insert(components["base_dbg"], package) + else + local found = false + for compat in all_libcompats:gmatch("%S+") do + if package:match(".*%-dbg%-lib" .. compat .. "$") then + table.insert(components["lib" .. compat .. "_dbg"], package) + found = true + break + elseif package:match(".*%-lib" .. compat .. "$") then + table.insert(components["lib" .. compat], package) + found = true + break + end + end + if not found then + table.insert(components["base"], package) + end + end + end + -- Don't assert the existence of dbg, tests, and src packages here. If using + -- a custom local repository with BSDINSTALL_PKG_REPOS_DIR we shouldn't + -- require it to have all packages. + assert(#components["kernel"] == 1) + assert(#components["base"] > 0) + + local selected = {} + for _, component in ipairs(select_components(components, options)) do + append_list(selected, components[component]) + end + + return selected +end + +local function parse_options() + local options = {} + for _, a in ipairs(arg) do + if a == "--no-kernel" then + options.no_kernel = true + else + io.stderr:write("Error: unknown option " .. a .. "\n") + os.exit(1) + end + end + return options +end + +-- Fetch and install pkgbase packages to BSDINSTALL_CHROOT. +-- Respect BSDINSTALL_PKG_REPOS_DIR if set, otherwise use pkg.freebsd.org. +local function pkgbase() + local options = parse_options() + + -- TODO Support fully offline pkgbase installation by taking a new enough + -- version of pkg.pkg as input. + if not os.execute("pkg -N > /dev/null 2>&1") then + print("Bootstrapping pkg on the host system") + assert(os.execute("pkg bootstrap -y")) + end + + local chroot = assert(os.getenv("BSDINSTALL_CHROOT")) + assert(os.execute("mkdir -p " .. chroot)) + + -- Always install the default FreeBSD-base.conf file to the chroot, even + -- if we don't actually fetch the packages from the repository specified + -- there (e.g. because we are performing an offline installation). + local chroot_repos_dir = chroot .. "/usr/local/etc/pkg/repos/" + assert(os.execute("mkdir -p " .. chroot_repos_dir)) + assert(os.execute("cp /usr/share/bsdinstall/FreeBSD-base.conf " .. + chroot_repos_dir)) + + local repos_dir = os.getenv("BSDINSTALL_PKG_REPOS_DIR") + if not repos_dir then + repos_dir = chroot_repos_dir + -- Since pkg always interprets fingerprints paths as relative to + -- the --rootdir we must copy the key from the host. + assert(os.execute("mkdir -p " .. chroot .. "/usr/share/keys")) + assert(os.execute("cp -R /usr/share/keys/pkg " .. chroot .. "/usr/share/keys/")) + end + + -- We must use --repo-conf-dir rather than -o REPOS_DIR here as the latter + -- is interpreted relative to the --rootdir. BSDINSTALL_PKG_REPOS_DIR must + -- be allowed to point to a path outside the chroot. + local pkg = "pkg --rootdir " .. chroot .. + " --repo-conf-dir " .. repos_dir .. " -o IGNORE_OSVERSION=yes " + + while not os.execute(pkg .. "update") do + if not prompt_yn("Updating repositories failed, try again?") then + os.exit(1) + end + end + + local packages = table.concat(select_packages(pkg, options), " ") + + while not os.execute(pkg .. "install -U -F -y -r FreeBSD-base " .. packages) do + if not prompt_yn("Fetching packages failed, try again?") then + os.exit(1) + end + end + + if not os.execute(pkg .. "install -U -y -r FreeBSD-base " .. packages) then + os.exit(1) + end +end + +pkgbase() diff --git a/usr.sbin/bsdinstall/scripts/rootpass b/usr.sbin/bsdinstall/scripts/rootpass index 93523a9880d3..9d25569ae946 100755 --- a/usr.sbin/bsdinstall/scripts/rootpass +++ b/usr.sbin/bsdinstall/scripts/rootpass @@ -1,6 +1,7 @@ #!/bin/sh #- # Copyright (c) 2011 Nathan Whitehorn +# Copyright (c) 2024 The FreeBSD Foundation # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -27,18 +28,89 @@ BSDCFG_SHARE="/usr/share/bsdconfig" . $BSDCFG_SHARE/common.subr || exit 1 -clear -echo "$OSNAME Installer" -echo "========================" -echo if [ -n "$ROOTPASS_ENC" ]; then printf '%s\n' "$ROOTPASS_ENC" | pw -R $BSDINSTALL_CHROOT usermod root -H 0 + exit $? elif [ -n "$ROOTPASS_PLAIN" ]; then printf '%s\n' "$ROOTPASS_PLAIN" | pw -R $BSDINSTALL_CHROOT usermod root -h 0 -else - echo "Please select a password for the system management account (root):" - echo "Typed characters will not be visible." - - chroot $BSDINSTALL_CHROOT passwd root 2>&1 + exit $? fi + +: ${BSDDIALOG_OK:=0} + +error_get_message() +{ + case $1 in + 62) + echo "The password cannot be empty" + ;; + 63) + echo "The passwords do not match" + ;; + 64) #EX_USAGE + echo "Command used incorrectly" + ;; + 65) #EX_DATAERR + echo "Incorrect input data" + ;; + 67) #EX_NOUSER + echo "User not found" + ;; + 70) #EX_SOFTWARE + echo "Internal software error" + ;; + 71) #EX_OSERR + echo "Operating System error detected" + ;; + 72) #EX_OSFILE + echo "Error in a system file" + ;; + 74) #EX_IOERR + echo "I/O error" + ;; + 77) #EX_NOPERM + echo "Insufficient permissions" + ;; + 78) #EX_CONFIG + echo "Configuration error" + ;; + 0) + ;; + *) + echo "An unknown error occurred (code $1)" + return 1 + ;; + esac + return $1 +} + +errormsg= +username="root" +while true; do + exec 5>&1 + output=$(bsddialog --backtitle "$OSNAME Installer" \ + --title "Set $username password" \ + --cancel-label "Skip" \ + --passwordform --insecure \ + "Please select a password for the system management account ($username) +$errormsg" \ + 0 0 2 \ + "Password" 0 0 '' 0 17 32 32 \ + "Repeat password" 1 0 '' 1 17 32 32 \ + 2>&1 1>&5) + res=$? + exec 5>&- + [ $res -eq $BSDDIALOG_OK ] || exit 0 + + echo -n "$output" | (read password1 + read password2 + [ -n "$password1" -o -n "$password2" ] || exit 62 + [ "$password1" = "$password2" ] || exit 63 + echo "$password1" | chroot $BSDINSTALL_CHROOT \ + /usr/sbin/pw usermod "$username" -h 0 + ) + err=$? + [ $err -eq 0 ] && exit 0 + errormsg=$(error_get_message $err) +done diff --git a/usr.sbin/bsdinstall/scripts/wlanconfig b/usr.sbin/bsdinstall/scripts/wlanconfig index 8ac64858eaba..33d94a933f45 100755 --- a/usr.sbin/bsdinstall/scripts/wlanconfig +++ b/usr.sbin/bsdinstall/scripts/wlanconfig @@ -92,7 +92,7 @@ dialog_country_select() sub(/.*domains:/, ""), /[^[:alnum:][[:space:]]/ { n = split($0, domains) for (i = 1; i <= n; i++) - printf "'\''%s'\'' '\'\''", domains[i] + printf "'\''%s'\'' '\'\''\n", domains[i] } ' | sort ) countries=$( echo "$input" | awk ' @@ -200,6 +200,12 @@ fi while :; do SCANSSID=0 + # While wpa_supplicant may IFF_UP the interface, we do not want to rely + # in this. In case the script is run manually (outside the installer, + # e.g., for testing) wpa_supplicant may be running and the wlanN + # interface may be down (especially if dialog_country_select is not + # run successfully either) and scanning will not work. + f_eval_catch -d wlanconfig ifconfig "ifconfig $WLAN_IFACE up" f_eval_catch -d wlanconfig wpa_cli "wpa_cli scan" f_dialog_title "Scanning" f_dialog_pause "Waiting 5 seconds to scan for wireless networks..." 5 || diff --git a/usr.sbin/bsdinstall/scripts/zfsboot b/usr.sbin/bsdinstall/scripts/zfsboot index f4670e0d272c..a3c1e2ddb89f 100755 --- a/usr.sbin/bsdinstall/scripts/zfsboot +++ b/usr.sbin/bsdinstall/scripts/zfsboot @@ -51,7 +51,7 @@ f_include $BSDCFG_SHARE/variable.subr # # Default options to use when creating zroot pool # -: ${ZFSBOOT_POOL_CREATE_OPTIONS:=-O compress=lz4 -O atime=off} +: ${ZFSBOOT_POOL_CREATE_OPTIONS:=-O compression=on -O atime=off} # # Default name for the boot environment parent dataset @@ -86,7 +86,7 @@ f_include $BSDCFG_SHARE/variable.subr # # Create a separate boot pool? -# NB: Automatically set when using geli(8) or MBR +# NB: Automatically set when using geli(8) # : ${ZFSBOOT_BOOT_POOL=} @@ -96,12 +96,12 @@ f_include $BSDCFG_SHARE/variable.subr : ${ZFSBOOT_BOOT_POOL_CREATE_OPTIONS:=} # -# Default name for boot pool when enabled (e.g., geli(8) or MBR) +# Default name for boot pool when enabled (e.g., geli(8)) # : ${ZFSBOOT_BOOT_POOL_NAME:=bootpool} # -# Default size for boot pool when enabled (e.g., geli(8) or MBR) +# Default size for boot pool when enabled (e.g., geli(8)) # : ${ZFSBOOT_BOOT_POOL_SIZE:=2g} @@ -121,7 +121,7 @@ f_include $BSDCFG_SHARE/variable.subr : ${ZFSBOOT_BOOT_TYPE:=} # -# How much swap to put on each block device in the boot zpool +# How much swap to put on each block device in the boot pool # NOTE: Value passed to gpart(8); which supports SI unit suffixes. # : ${ZFSBOOT_SWAP_SIZE:=2g} @@ -137,7 +137,7 @@ f_include $BSDCFG_SHARE/variable.subr : ${ZFSBOOT_SWAP_MIRROR=} # -# Default ZFS datasets for root zpool +# Default ZFS datasets for root pool # # NOTE: Requires /tmp, /var/tmp, /$ZFSBOOT_BEROOT_NAME/$ZFSBOOT_BOOTFS_NAME # NOTE: Anything after pound/hash character [#] is ignored as a comment. @@ -285,12 +285,12 @@ msg_odd_disk_selected="An even number of disks must be selected to create a RAID msg_ok="OK" msg_partition_scheme="Partition Scheme" msg_partition_scheme_help="Select partitioning scheme. GPT is recommended." -msg_please_enter_a_name_for_your_zpool="Please enter a name for your zpool:" +msg_please_enter_a_name_for_your_pool="Please enter a name for your pool:" msg_please_enter_amount_of_swap_space="Please enter amount of swap space (SI-Unit suffixes\nrecommended; e.g., \`2g' for 2 Gigabytes):" -msg_please_select_one_or_more_disks="Please select one or more disks to create a zpool:" +msg_please_select_one_or_more_disks="Please select one or more disks to create a pool:" msg_pool_name="Pool Name" msg_pool_name_cannot_be_empty="Pool name cannot be empty." -msg_pool_name_help="Customize the name of the zpool to be created (Required)" +msg_pool_name_help="Customize the name of the pool to be created (Required)" msg_pool_type_disks="Pool Type/Disks:" msg_pool_type_disks_help="Choose type of ZFS Virtual Device and disks to use (Required)" msg_processing_selection="Processing selection..." @@ -323,9 +323,9 @@ msg_unsupported_partition_scheme="%s is an unsupported partition scheme" msg_user_cancelled="User Cancelled." msg_yes="YES" msg_zfs_configuration="ZFS Configuration" -msg_please_enter_options_for_your_zpool="Please enter options for your zpool" +msg_please_enter_options_for_your_pool="Please enter options for your pool" msg_zfs_options_name="ZFS Pool Options" -msg_zfs_options_name_help="Customize ZFS options for the zpool to be created" +msg_zfs_options_name_help="Customize ZFS options for the pool to be created" ############################################################ FUNCTIONS @@ -748,8 +748,8 @@ dialog_menu_layout() # zfs_create_diskpart $disk $index # -# For each block device to be used in the zpool, rather than just create the -# zpool with the raw block devices (e.g., da0, da1, etc.) we create partitions +# For each block device to be used in the pool, rather than just create the +# pool with the raw block devices (e.g., da0, da1, etc.) we create partitions # so we can have some real swap. This also provides wiggle room incase your # replacement drivers do not have the exact same sector counts. # @@ -790,7 +790,7 @@ zfs_create_diskpart() # Check for unknown partition scheme before proceeding further case "$ZFSBOOT_PARTITION_SCHEME" in - ""|MBR|GPT*) : known good ;; + ""|GPT*) : known good ;; *) f_dprintf "$funcname: %s is an unsupported partition scheme" \ "$ZFSBOOT_PARTITION_SCHEME" @@ -825,14 +825,11 @@ zfs_create_diskpart() # # Lay down the desired type of partition scheme # - local setsize mbrindex align_small align_big + local setsize align_small align_big # # If user has requested 4 K alignment, add these params to the # gpart add calls. With GPT, we align large partitions to 1 M for - # improved performance on SSDs. MBR does not always play well with gaps - # between partitions, so all alignment is only 4k for that case. - # With MBR, we align the BSD partition that contains the MBR, otherwise - # the system fails to boot. + # improved performance on SSDs. # if [ "$ZFSBOOT_FORCE_4K_SECTORS" ]; then align_small="-a 4k" @@ -905,7 +902,7 @@ zfs_create_diskpart() fi fi - # NB: zpool will use the `zfs#' GPT labels + # NB: ZFS pools will use `zfs#' GPT labels if [ "$ZFSBOOT_BOOT_TYPE" = "BIOS+UEFI" ]; then if [ "$ZFSBOOT_BOOT_POOL" ]; then bootpart=p3 swappart=p4 targetpart=p4 @@ -974,90 +971,6 @@ zfs_create_diskpart() /dev/$disk$targetpart ;; - MBR) f_dprintf "$funcname: Creating MBR layout..." - # - # Enable boot pool if encryption is desired - # - [ "$ZFSBOOT_GELI_ENCRYPTION" ] && ZFSBOOT_BOOT_POOL=1 - # - # 1. Create MBR layout (no labels) - # - f_eval_catch $funcname gpart "$GPART_CREATE" mbr $disk || - return $FAILURE - f_eval_catch $funcname gpart "$GPART_BOOTCODE" /boot/mbr \ - $disk || return $FAILURE - - # - # 2. Add freebsd slice with all available space - # - f_eval_catch $funcname gpart "$GPART_ADD_ALIGN" \ - "$align_small" freebsd $disk || return $FAILURE - f_eval_catch $funcname gpart "$GPART_SET_ACTIVE" 1 $disk || - return $FAILURE - # Pedantically nuke any old labels - f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \ - /dev/${disk}s1 - # Pedantically nuke any old scheme - f_eval_catch -d $funcname gpart "$GPART_DESTROY_F" ${disk}s1 - - # - # 3. Write BSD scheme to the freebsd slice - # - f_eval_catch $funcname gpart "$GPART_CREATE" BSD ${disk}s1 || - return $FAILURE - - # NB: zpool will use s1a (no labels) - bootpart=s1a swappart=s1b targetpart=s1d mbrindex=4 - - # - # Always prepare a boot pool on MBR - # Do not align this partition, there must not be a gap - # - ZFSBOOT_BOOT_POOL=1 - f_eval_catch $funcname gpart \ - "$GPART_ADD_ALIGN_INDEX_WITH_SIZE" \ - "" 1 freebsd-zfs ${bootsize}b ${disk}s1 || - return $FAILURE - # Pedantically nuke any old labels - f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \ - /dev/$disk$bootpart - if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then - # Pedantically detach targetpart for later - f_eval_catch -d $funcname geli \ - "$GELI_DETACH_F" \ - /dev/$disk$targetpart - fi - - # - # 4. Add freebsd-swap partition - # - if [ ${swapsize:-0} -gt 0 ]; then - f_eval_catch $funcname gpart \ - "$GPART_ADD_ALIGN_INDEX_WITH_SIZE" \ - "$align_small" 2 freebsd-swap \ - ${swapsize}b ${disk}s1 || return $FAILURE - # Pedantically nuke any old labels on the swap - f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \ - /dev/${disk}s1b - fi - - # - # 5. Add freebsd-zfs partition for zroot - # - if [ "$ZFSBOOT_POOL_SIZE" ]; then - f_eval_catch $funcname gpart "$GPART_ADD_ALIGN_INDEX_WITH_SIZE" \ - "$align_small" $mbrindex freebsd-zfs $ZFSBOOT_POOL_SIZE ${disk}s1 || return $FAILURE - else - f_eval_catch $funcname gpart "$GPART_ADD_ALIGN_INDEX" \ - "$align_small" $mbrindex freebsd-zfs ${disk}s1 || return $FAILURE - fi - f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \ - /dev/$disk$targetpart # Pedantic - f_eval_catch $funcname dd "$DD_WITH_OPTIONS" \ - /boot/zfsboot /dev/${disk}s1 count=1 || - return $FAILURE - ;; - esac # $ZFSBOOT_PARTITION_SCHEME # Update fstab(5) @@ -1102,7 +1015,7 @@ zfs_create_boot() local zroot_vdevtype="$2" local zroot_vdevs= # Calculated below local swap_devs= # Calculated below - local boot_vdevs= # Used for geli(8) and/or MBR layouts + local boot_vdevs= # Used for geli(8) layouts shift 2 # poolname vdev_type local disks="$*" disk local isswapmirror @@ -1191,7 +1104,6 @@ zfs_create_boot() f_dprintf "$funcname: With 4K sectors..." f_eval_catch $funcname sysctl "$SYSCTL_ZFS_MIN_ASHIFT_12" \ || return $FAILURE - sysctl kern.geom.part.mbr.enforce_chs=0 fi local n=0 for disk in $disks; do @@ -1415,40 +1327,6 @@ zfs_create_boot() "bootfs=\"$zroot_name/$zroot_bootfs\"" "$zroot_name" || return $FAILURE - # MBR boot loader touch-up - if [ "$ZFSBOOT_PARTITION_SCHEME" = "MBR" ]; then - # Export the pool(s) - f_dprintf "$funcname: Temporarily exporting ZFS pool(s)..." - f_eval_catch $funcname zpool "$ZPOOL_EXPORT" "$zroot_name" || - return $FAILURE - if [ "$ZFSBOOT_BOOT_POOL" ]; then - f_eval_catch $funcname zpool "$ZPOOL_EXPORT" \ - "$bootpool_name" || return $FAILURE - fi - - f_dprintf "$funcname: Updating MBR boot loader on disks..." - # Stick the ZFS boot loader in the "convenient hole" after - # the ZFS internal metadata - for disk in $disks; do - f_eval_catch $funcname dd "$DD_WITH_OPTIONS" \ - /boot/zfsboot /dev/$disk$bootpart \ - "skip=1 seek=1024" || return $FAILURE - done - - # Re-import the ZFS pool(s) - f_dprintf "$funcname: Re-importing ZFS pool(s)..." - f_eval_catch $funcname zpool "$ZPOOL_IMPORT_WITH_OPTIONS" \ - "-o altroot=\"$BSDINSTALL_CHROOT\"" \ - "$zroot_name" || return $FAILURE - if [ "$ZFSBOOT_BOOT_POOL" ]; then - # Import the bootpool, but do not mount it yet - f_eval_catch $funcname zpool \ - "$ZPOOL_IMPORT_WITH_OPTIONS" \ - "-o altroot=\"$BSDINSTALL_CHROOT\" -N" \ - "$bootpool_name" || return $FAILURE - fi - fi - # Remount bootpool and create symlink(s) if [ "$ZFSBOOT_BOOT_POOL" ]; then f_eval_catch $funcname zfs "$ZFS_MOUNT" "$bootpool_name" || @@ -1594,7 +1472,7 @@ dialog_menu_diskinfo() return $SUCCESS } -dialog_zpool_name() +dialog_pool_name() { local prompt="$* is already taken, please enter a name for the ZFS pool \ (Or confirm using the same name by just pressing enter)" @@ -1639,7 +1517,7 @@ for pool in ${pools}; do f_dprintf "Checking ${pool} against ${ZFSBOOT_POOL_NAME}" if [ "${pool}" = "${ZFSBOOT_POOL_NAME}" ]; then f_dprintf "Pool ${pool} already taken" - ZFSBOOT_POOL_NAME=$(dialog_zpool_name "${ZFSBOOT_POOL_NAME}") + ZFSBOOT_POOL_NAME=$(dialog_pool_name "${ZFSBOOT_POOL_NAME}") break fi done @@ -1771,7 +1649,7 @@ while :; do ?" $msg_pool_name") # Prompt the user to input/change the name for the new pool f_dialog_input input \ - "$msg_please_enter_a_name_for_your_zpool" \ + "$msg_please_enter_a_name_for_your_pool" \ "$ZFSBOOT_POOL_NAME" && ZFSBOOT_POOL_NAME="$input" ;; @@ -1793,7 +1671,7 @@ while :; do fi ;; ?" $msg_partition_scheme") - # Toggle between GPT (BIOS), GPT (UEFI) and MBR + # Toggle between partition schemes if [ "$ZFSBOOT_PARTITION_SCHEME" = "GPT" -a \ "$ZFSBOOT_BOOT_TYPE" = "BIOS" ] then @@ -1805,9 +1683,6 @@ while :; do ZFSBOOT_PARTITION_SCHEME="GPT" ZFSBOOT_BOOT_TYPE="BIOS+UEFI" elif [ "$ZFSBOOT_PARTITION_SCHEME" = "GPT" ]; then - ZFSBOOT_PARTITION_SCHEME="MBR" - ZFSBOOT_BOOT_TYPE="BIOS" - elif [ "$ZFSBOOT_PARTITION_SCHEME" = "MBR" ]; then ZFSBOOT_PARTITION_SCHEME="GPT + Active" ZFSBOOT_BOOT_TYPE="BIOS" elif [ "$ZFSBOOT_PARTITION_SCHEME" = "GPT + Active" ]; then @@ -1860,7 +1735,7 @@ while :; do ?" $msg_zfs_options_name") # Prompt the user to input/change the pool options f_dialog_input input \ - "$msg_please_enter_options_for_your_zpool" \ + "$msg_please_enter_options_for_your_pool" \ "$ZFSBOOT_POOL_CREATE_OPTIONS" && ZFSBOOT_POOL_CREATE_OPTIONS="$input" ;; diff --git a/usr.sbin/bsnmpd/bsnmpd/Makefile b/usr.sbin/bsnmpd/bsnmpd/Makefile index e7c7a87eec7c..601fc31ec475 100644 --- a/usr.sbin/bsnmpd/bsnmpd/Makefile +++ b/usr.sbin/bsnmpd/bsnmpd/Makefile @@ -9,7 +9,7 @@ CONTRIB=${SRCTOP}/contrib/bsnmp CONFS= snmpd.config CONFSMODE= 600 PROG= bsnmpd -SRCS= main.c action.c config.c export.c trap.c trans_udp.c trans_lsock.c +SRCS= main.c action.c config.c export.c trap.c trans_lsock.c SRCS+= trans_inet.c oid.h tree.c tree.h XSYM= snmpMIB begemotSnmpdModuleTable begemotSnmpd begemotTrapSinkTable \ sysUpTime snmpTrapOID coldStart authenticationFailure \ diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swinstalled_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swinstalled_tbl.c index 8b7e4608d673..f7484e90189b 100644 --- a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swinstalled_tbl.c +++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swinstalled_tbl.c @@ -372,7 +372,7 @@ swins_get_packages(void) } if (errno != 0) { - syslog(LOG_ERR, "hrSWInstalledTable: readdir_r(\"%s\") failed:" + syslog(LOG_ERR, "hrSWInstalledTable: readdir(\"%s\") failed:" " %m", pkg_dir); ret = -1; } else { diff --git a/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_sys.c b/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_sys.c index b129e42b9d85..e80b53dcf44e 100644 --- a/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_sys.c +++ b/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_sys.c @@ -2167,7 +2167,7 @@ wlan_add_new_scan_result(struct wlan_iface *wif, return (-1); sr->opchannel = wlan_channel_flags_to_snmp_phy(isr->isr_flags); - sr->rssi = isr->isr_rssi; + sr->rssi = (isr->isr_rssi / 2) - isr->isr_noise; sr->frequency = isr->isr_freq; sr->noise = isr->isr_noise; sr->bintval = isr->isr_intval; diff --git a/usr.sbin/bsnmpd/tools/bsnmptools/bsnmpget.c b/usr.sbin/bsnmpd/tools/bsnmptools/bsnmpget.c index 9d5a693c7c68..9252e63749bb 100644 --- a/usr.sbin/bsnmpd/tools/bsnmptools/bsnmpget.c +++ b/usr.sbin/bsnmpd/tools/bsnmptools/bsnmpget.c @@ -1179,8 +1179,10 @@ main(int argc, char ** argv) /* On -h (help) exit without error. */ if (opt_num == -2) exit(0); - else + else { + fprintf(stderr, "Error: %s\n", snmp_client.error); exit(1); + } } oid_cnt = argc - opt_num - 1; @@ -1239,7 +1241,7 @@ main(int argc, char ** argv) } if (snmp_open(NULL, NULL, NULL, NULL)) { - warn("Failed to open snmp session"); + fprintf(stderr, "snmp_open(3): %s\n", snmp_client.error); snmp_tool_freeall(&snmptoolctx); exit(1); } diff --git a/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.c b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.c index fb09e1ac785e..d8fbb55290a8 100644 --- a/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.c +++ b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.c @@ -790,15 +790,6 @@ parse_server(char *opt_arg) if (snmp_parse_server(&snmp_client, opt_arg) < 0) return (-1); - if (snmp_client.trans > SNMP_TRANS_UDP && snmp_client.chost == NULL) { - if ((snmp_client.chost = malloc(strlen(SNMP_DEFAULT_LOCAL) + 1)) - == NULL) { - syslog(LOG_ERR, "malloc() failed: %s", strerror(errno)); - return (-1); - } - strcpy(snmp_client.chost, SNMP_DEFAULT_LOCAL); - } - return (2); } @@ -890,12 +881,11 @@ parse_local_path(char *opt_arg) { assert(opt_arg != NULL); - if (sizeof(opt_arg) > sizeof(SNMP_LOCAL_PATH)) { + if (strlcpy(snmp_client.local_path, opt_arg, + sizeof(snmp_client.local_path)) >= sizeof(snmp_client.local_path)) { warnx("Filename too long - %s", opt_arg); return (-1); } - - strlcpy(snmp_client.local_path, opt_arg, sizeof(SNMP_LOCAL_PATH)); return (2); } diff --git a/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.h b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.h index 2874f311fbd0..54a087491a4f 100644 --- a/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.h +++ b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.h @@ -43,7 +43,6 @@ #define MAX_BUFF_SIZE (ASN_MAXOCTETSTRING + 50) #define SNMP_DEFS_DIR "/usr/share/snmp/defs/" -#define SNMP_DEFAULT_LOCAL "/var/run/snmpd.sock" #define SNMP_MAX_REPETITIONS 10 diff --git a/usr.sbin/certctl/Makefile b/usr.sbin/certctl/Makefile index 88c024daf7e6..6900f0ce3b65 100644 --- a/usr.sbin/certctl/Makefile +++ b/usr.sbin/certctl/Makefile @@ -1,5 +1,14 @@ +.include <src.opts.mk> + PACKAGE= certctl -SCRIPTS=certctl.sh +PROG= certctl MAN= certctl.8 +LIBADD= crypto +HAS_TESTS= +SUBDIR.${MK_TESTS}= tests + +.ifdef BOOTSTRAPPING +CFLAGS+=-DBOOTSTRAPPING +.endif .include <bsd.prog.mk> diff --git a/usr.sbin/certctl/certctl.8 b/usr.sbin/certctl/certctl.8 index 286072c1b4d6..edf993e1361a 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 October 10, 2023 +.Dd August 18, 2025 .Dt CERTCTL 8 .Os .Sh NAME @@ -32,61 +32,85 @@ .Nd "tool for managing trusted and untrusted TLS certificates" .Sh SYNOPSIS .Nm -.Op Fl v +.Op Fl lv .Ic list .Nm -.Op Fl v +.Op Fl lv .Ic untrusted .Nm -.Op Fl nUv +.Op Fl BnUv .Op Fl D Ar destdir .Op Fl M Ar metalog .Ic rehash .Nm .Op Fl nv -.Ic untrust Ar file +.Ic untrust Ar .Nm .Op Fl nv -.Ic trust Ar file +.Ic trust Ar .Sh DESCRIPTION The .Nm utility manages the list of TLS Certificate Authorities that are trusted by applications that use OpenSSL. .Pp -Flags: +The following options are available: .Bl -tag -width 4n +.It Fl B +Do not generate a bundle. +This option is only valid in conjunction with the +.Ic rehash +command. .It Fl D Ar destdir Specify the DESTDIR (overriding values from the environment). .It Fl d Ar distbase Specify the DISTBASE (overriding values from the environment). +.It Fl l +When listing installed (trusted or untrusted) certificates, show the +full path and distinguished name for each certificate. .It Fl M Ar metalog -Specify the path of the METALOG file (default: $DESTDIR/METALOG). +Specify the path of the METALOG file +.Po +default: +.Pa ${DESTDIR}/METALOG +.Pc . +This option is only valid in conjunction with the +.Ic rehash +command. .It Fl n -No-Op mode, do not actually perform any actions. +Dry-run mode. +Do not actually perform any actions except write the metalog. .It Fl v -Be verbose, print details about actions before performing them. +Verbose mode. +Print detailed information about each action taken. .It Fl U -Unprivileged mode, do not change the ownership of created links. -Do record the ownership in the METALOG file. +Unprivileged mode. +Do not attempt to set the ownership of created files. +This option is only valid in conjunction with the +.Fl M +option and the +.Ic rehash +command. .El .Pp Primary command functions: .Bl -tag -width untrusted .It Ic list -List all currently trusted certificate authorities. +List all currently trusted certificates. .It Ic untrusted List all currently untrusted certificates. .It Ic rehash -Rebuild the list of trusted certificate authorities by scanning all directories +Rebuild the list of trusted certificates by scanning all directories in .Ev TRUSTPATH and all untrusted certificates in .Ev UNTRUSTPATH . -A symbolic link to each trusted certificate is placed in +A copy of each trusted certificate is placed in .Ev CERTDESTDIR and each untrusted certificate in .Ev UNTRUSTDESTDIR . +In addition, a bundle containing the trusted certificates is placed in +.Ev BUNDLEFILE . .It Ic untrust Add the specified file to the untrusted list. .It Ic trust @@ -95,9 +119,13 @@ Remove the specified file from the untrusted list. .Sh ENVIRONMENT .Bl -tag -width UNTRUSTDESTDIR .It Ev DESTDIR -Alternate destination directory to operate on. +Absolute path to an alternate destination directory to operate on +instead of the file system root, e.g. +.Dq Li /tmp/install . .It Ev DISTBASE Additional path component to include when operating on certificate directories. +This must start with a slash, e.g. +.Dq Li /base . .It Ev LOCALBASE Location for local programs. Defaults to the value of the user.localbase sysctl which is usually @@ -105,32 +133,34 @@ Defaults to the value of the user.localbase sysctl which is usually .It Ev TRUSTPATH List of paths to search for trusted certificates. Default: -.Pa <DESTDIR><DISTBASE>/usr/share/certs/trusted -.Pa <DESTDIR><DISTBASE>/usr/local/share/certs -.Pa <DESTDIR><DISTBASE><LOCALBASE>/etc/ssl/certs +.Pa ${DESTDIR}${DISTBASE}/usr/share/certs/trusted +.Pa ${DESTDIR}${LOCALBASE}/share/certs/trusted +.Pa ${DESTDIR}${LOCALBASE}/share/certs .It Ev UNTRUSTPATH List of paths to search for untrusted certificates. Default: -.Pa <DESTDIR><DISTBASE>/usr/share/certs/untrusted -.Pa <DESTDIR><DISTBASE><LOCALBASE>/etc/ssl/untrusted -.Pa <DESTDIR><DISTBASE><LOCALBASE>/etc/ssl/blacklisted -.It Ev CERTDESTDIR +.Pa ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted +.Pa ${DESTDIR}${LOCALBASE}/share/certs/untrusted +.It Ev TRUSTDESTDIR Destination directory for symbolic links to trusted certificates. Default: -.Pa <DESTDIR><DISTBASE>/etc/ssl/certs +.Pa ${DESTDIR}${DISTBASE}/etc/ssl/certs .It Ev UNTRUSTDESTDIR Destination directory for symbolic links to untrusted certificates. Default: -.Pa <DESTDIR><DISTBASE>/etc/ssl/untrusted -.It Ev EXTENSIONS -List of file extensions to read as certificate files. -Default: *.pem *.crt *.cer *.crl *.0 +.Pa ${DESTDIR}${DISTBASE}/etc/ssl/untrusted +.It Ev BUNDLE +File name of bundle to produce. .El .Sh SEE ALSO .Xr openssl 1 .Sh HISTORY .Nm first appeared in -.Fx 12.2 +.Fx 12.2 . .Sh AUTHORS -.An Allan Jude Aq Mt allanjude@freebsd.org +.An -nosplit +The original shell implementation was written by +.An Allan Jude Aq Mt allanjude@FreeBSD.org . +The current C implementation was written by +.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org . diff --git a/usr.sbin/certctl/certctl.c b/usr.sbin/certctl/certctl.c new file mode 100644 index 000000000000..3601f6929fc4 --- /dev/null +++ b/usr.sbin/certctl/certctl.c @@ -0,0 +1,1138 @@ +/*- + * Copyright (c) 2023-2025 Dag-Erling Smørgrav <des@FreeBSD.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/sysctl.h> +#include <sys/stat.h> +#include <sys/tree.h> + +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <fts.h> +#include <paths.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/ssl.h> + +#define info(fmt, ...) \ + do { \ + if (verbose) \ + fprintf(stderr, fmt "\n", ##__VA_ARGS__); \ + } while (0) + +static char * +xasprintf(const char *fmt, ...) +{ + va_list ap; + char *str; + int ret; + + va_start(ap, fmt); + ret = vasprintf(&str, fmt, ap); + va_end(ap); + if (ret < 0 || str == NULL) + err(1, NULL); + return (str); +} + +static char * +xstrdup(const char *str) +{ + char *dup; + + if ((dup = strdup(str)) == NULL) + err(1, NULL); + return (dup); +} + +static void usage(void); + +static bool dryrun; +static bool longnames; +static bool nobundle; +static bool unprivileged; +static bool verbose; + +static const char *localbase; +static const char *destdir; +static const char *distbase; +static const char *metalog; + +static const char *uname = "root"; +static const char *gname = "wheel"; + +static const char *const default_trusted_paths[] = { + "/usr/share/certs/trusted", + "%L/share/certs/trusted", + "%L/share/certs", + NULL +}; +static char **trusted_paths; + +static const char *const default_untrusted_paths[] = { + "/usr/share/certs/untrusted", + "%L/share/certs/untrusted", + NULL +}; +static char **untrusted_paths; + +static char *trusted_dest; +static char *untrusted_dest; +static char *bundle_dest; + +#define SSL_PATH "/etc/ssl" +#define TRUSTED_DIR "certs" +#define TRUSTED_PATH SSL_PATH "/" TRUSTED_DIR +#define UNTRUSTED_DIR "untrusted" +#define UNTRUSTED_PATH SSL_PATH "/" UNTRUSTED_DIR +#define LEGACY_DIR "blacklisted" +#define LEGACY_PATH SSL_PATH "/" LEGACY_DIR +#define BUNDLE_FILE "cert.pem" +#define BUNDLE_PATH SSL_PATH "/" BUNDLE_FILE + +static FILE *mlf; + +/* + * Create a directory and its parents as needed. + */ +static void +mkdirp(const char *dir) +{ + struct stat sb; + const char *sep; + char *parent; + + if (stat(dir, &sb) == 0) + return; + if ((sep = strrchr(dir, '/')) != NULL) { + parent = xasprintf("%.*s", (int)(sep - dir), dir); + mkdirp(parent); + free(parent); + } + info("creating %s", dir); + if (mkdir(dir, 0755) != 0) + err(1, "mkdir %s", dir); +} + +/* + * Remove duplicate and trailing slashes from a path. + */ +static char * +normalize_path(const char *str) +{ + char *buf, *dst; + + if ((buf = malloc(strlen(str) + 1)) == NULL) + err(1, NULL); + for (dst = buf; *str != '\0'; dst++) { + if ((*dst = *str++) == '/') { + while (*str == '/') + str++; + if (*str == '\0') + break; + } + } + *dst = '\0'; + return (buf); +} + +/* + * Split a colon-separated list into a NULL-terminated array. + */ +static char ** +split_paths(const char *str) +{ + char **paths; + const char *p, *q; + unsigned int i, n; + + for (p = str, n = 1; *p; p++) { + if (*p == ':') + n++; + } + if ((paths = calloc(n + 1, sizeof(*paths))) == NULL) + err(1, NULL); + for (p = q = str, i = 0; i < n; i++, p = q + 1) { + q = strchrnul(p, ':'); + if ((paths[i] = strndup(p, q - p)) == NULL) + err(1, NULL); + } + return (paths); +} + +/* + * Expand %L into LOCALBASE and prefix DESTDIR and DISTBASE as needed. + */ +static char * +expand_path(const char *template) +{ + if (template[0] == '%' && template[1] == 'L') + return (xasprintf("%s%s%s", destdir, localbase, template + 2)); + return (xasprintf("%s%s%s", destdir, distbase, template)); +} + +/* + * Expand an array of paths. + */ +static char ** +expand_paths(const char *const *templates) +{ + char **paths; + unsigned int i, n; + + for (n = 0; templates[n] != NULL; n++) + continue; + if ((paths = calloc(n + 1, sizeof(*paths))) == NULL) + err(1, NULL); + for (i = 0; i < n; i++) + paths[i] = expand_path(templates[i]); + return (paths); +} + +/* + * If destdir is a prefix of path, returns a pointer to the rest of path, + * otherwise returns path. + * + * Note that this intentionally does not strip distbase from the path! + * Unlike destdir, distbase is expected to be included in the metalog. + */ +static const char * +unexpand_path(const char *path) +{ + const char *p = path; + const char *q = destdir; + + while (*p && *p == *q) { + p++; + q++; + } + return (*q == '\0' && *p == '/' ? p : path); +} + +/* + * X509 certificate in a rank-balanced tree. + */ +struct cert { + RB_ENTRY(cert) entry; + unsigned long hash; + char *name; + X509 *x509; + char *path; +}; + +static void +free_cert(struct cert *cert) +{ + free(cert->name); + X509_free(cert->x509); + free(cert->path); + free(cert); +} + +static int +certcmp(const struct cert *a, const struct cert *b) +{ + return (X509_cmp(a->x509, b->x509)); +} + +RB_HEAD(cert_tree, cert); +static struct cert_tree trusted = RB_INITIALIZER(&trusted); +static struct cert_tree untrusted = RB_INITIALIZER(&untrusted); +RB_GENERATE_STATIC(cert_tree, cert, entry, certcmp); + +static void +free_certs(struct cert_tree *tree) +{ + struct cert *cert, *tmp; + + RB_FOREACH_SAFE(cert, cert_tree, tree, tmp) { + RB_REMOVE(cert_tree, tree, cert); + free_cert(cert); + } +} + +static struct cert * +find_cert(struct cert_tree *haystack, X509 *x509) +{ + struct cert needle = { .x509 = x509 }; + + return (RB_FIND(cert_tree, haystack, &needle)); +} + +/* + * File containing a certificate in a rank-balanced tree sorted by + * certificate hash and disambiguating counter. This is needed because + * the certificate hash function is prone to collisions, necessitating a + * counter to distinguish certificates that hash to the same value. + */ +struct file { + RB_ENTRY(file) entry; + const struct cert *cert; + unsigned int c; +}; + +static int +filecmp(const struct file *a, const struct file *b) +{ + if (a->cert->hash > b->cert->hash) + return (1); + if (a->cert->hash < b->cert->hash) + return (-1); + return (a->c - b->c); +} + +RB_HEAD(file_tree, file); +RB_GENERATE_STATIC(file_tree, file, entry, filecmp); + +/* + * Lexicographical sort for scandir(). + */ +static int +lexisort(const struct dirent **d1, const struct dirent **d2) +{ + return (strcmp((*d1)->d_name, (*d2)->d_name)); +} + +/* + * Read certificate(s) from a single file and insert them into a tree. + * Ignore certificates that already exist in the tree. If exclude is not + * null, also ignore certificates that exist in exclude. + * + * Returns the number certificates added to the tree, or -1 on failure. + */ +static int +read_cert(const char *path, struct cert_tree *tree, struct cert_tree *exclude) +{ + FILE *f; + X509 *x509; + X509_NAME *name; + struct cert *cert; + unsigned long hash; + int len, ni, no; + + if ((f = fopen(path, "r")) == NULL) { + warn("%s", path); + return (-1); + } + for (ni = no = 0; + (x509 = PEM_read_X509(f, NULL, NULL, NULL)) != NULL; + ni++) { + hash = X509_subject_name_hash(x509); + if (exclude && find_cert(exclude, x509)) { + info("%08lx: excluded", hash); + X509_free(x509); + continue; + } + if (find_cert(tree, x509)) { + info("%08lx: duplicate", hash); + X509_free(x509); + continue; + } + if ((cert = calloc(1, sizeof(*cert))) == NULL) + err(1, NULL); + cert->x509 = x509; + name = X509_get_subject_name(x509); + cert->hash = X509_NAME_hash_ex(name, NULL, NULL, NULL); + len = X509_NAME_get_text_by_NID(name, NID_commonName, + NULL, 0); + if (len > 0) { + if ((cert->name = malloc(len + 1)) == NULL) + err(1, NULL); + X509_NAME_get_text_by_NID(name, NID_commonName, + cert->name, len + 1); + } else { + /* fallback for certificates without CN */ + cert->name = X509_NAME_oneline(name, NULL, 0); + } + cert->path = xstrdup(unexpand_path(path)); + if (RB_INSERT(cert_tree, tree, cert) != NULL) + errx(1, "unexpected duplicate"); + info("%08lx: %s", cert->hash, cert->name); + no++; + } + /* + * ni is the number of certificates we found in the file. + * no is the number of certificates that weren't already in our + * tree or on the exclusion list. + */ + if (ni == 0) + warnx("%s: no valid certificates found", path); + fclose(f); + return (no); +} + +/* + * Load all certificates found in the specified path into a tree, + * optionally excluding those that already exist in a different tree. + * + * Returns the number of certificates added to the tree, or -1 on failure. + */ +static int +read_certs(const char *path, struct cert_tree *tree, struct cert_tree *exclude) +{ + struct stat sb; + char *paths[] = { (char *)(uintptr_t)path, NULL }; + FTS *fts; + FTSENT *ent; + int fts_options = FTS_LOGICAL | FTS_NOCHDIR; + int ret, total = 0; + + if (stat(path, &sb) != 0) { + return (-1); + } else if (!S_ISDIR(sb.st_mode)) { + errno = ENOTDIR; + return (-1); + } + if ((fts = fts_open(paths, fts_options, NULL)) == NULL) + err(1, "fts_open()"); + while ((ent = fts_read(fts)) != NULL) { + if (ent->fts_info != FTS_F) { + if (ent->fts_info == FTS_ERR) + warnc(ent->fts_errno, "fts_read()"); + continue; + } + info("found %s", ent->fts_path); + ret = read_cert(ent->fts_path, tree, exclude); + if (ret > 0) + total += ret; + } + fts_close(fts); + return (total); +} + +/* + * Save the contents of a cert tree to disk. + * + * Returns 0 on success and -1 on failure. + */ +static int +write_certs(const char *dir, struct cert_tree *tree) +{ + struct file_tree files = RB_INITIALIZER(&files); + struct cert *cert; + struct file *file, *tmp; + struct dirent **dents, **ent; + char *path, *tmppath = NULL; + FILE *f; + mode_t mode = 0444; + int cmp, d, fd, ndents, ret = 0; + + /* + * Start by generating unambiguous file names for each certificate + * and storing them in lexicographical order + */ + RB_FOREACH(cert, cert_tree, tree) { + if ((file = calloc(1, sizeof(*file))) == NULL) + err(1, NULL); + file->cert = cert; + for (file->c = 0; file->c < INT_MAX; file->c++) + if (RB_INSERT(file_tree, &files, file) == NULL) + break; + if (file->c == INT_MAX) + errx(1, "unable to disambiguate %08lx", cert->hash); + free(cert->path); + cert->path = xasprintf("%08lx.%d", cert->hash, file->c); + } + /* + * Open and scan the directory. + */ + if ((d = open(dir, O_DIRECTORY | O_RDONLY)) < 0 || +#ifdef BOOTSTRAPPING + (ndents = scandir(dir, &dents, NULL, lexisort)) +#else + (ndents = fdscandir(d, &dents, NULL, lexisort)) +#endif + < 0) + err(1, "%s", dir); + /* + * Iterate over the directory listing and the certificate listing + * in parallel. If the directory listing gets ahead of the + * certificate listing, we need to write the current certificate + * and advance the certificate listing. If the certificate + * listing is ahead of the directory listing, we need to delete + * the current file and advance the directory listing. If they + * are neck and neck, we have a match and could in theory compare + * the two, but in practice it's faster to just replace the + * current file with the current certificate (and advance both). + */ + ent = dents; + file = RB_MIN(file_tree, &files); + for (;;) { + if (ent < dents + ndents) { + /* skip directories */ + if ((*ent)->d_type == DT_DIR) { + free(*ent++); + continue; + } + if (file != NULL) { + /* compare current dirent to current cert */ + path = file->cert->path; + cmp = strcmp((*ent)->d_name, path); + } else { + /* trailing files in directory */ + path = NULL; + cmp = -1; + } + } else { + if (file != NULL) { + /* trailing certificates */ + path = file->cert->path; + cmp = 1; + } else { + /* end of both lists */ + path = NULL; + break; + } + } + if (cmp < 0) { + /* a file on disk with no matching certificate */ + info("removing %s/%s", dir, (*ent)->d_name); + if (!dryrun) + (void)unlinkat(d, (*ent)->d_name, 0); + free(*ent++); + continue; + } + if (cmp == 0) { + /* a file on disk with a matching certificate */ + info("replacing %s/%s", dir, (*ent)->d_name); + if (dryrun) { + fd = open(_PATH_DEVNULL, O_WRONLY); + } else { + tmppath = xasprintf(".%s", path); + fd = openat(d, tmppath, + O_CREAT | O_WRONLY | O_TRUNC, mode); + if (!unprivileged && fd >= 0) + (void)fchmod(fd, mode); + } + free(*ent++); + } else { + /* a certificate with no matching file */ + info("writing %s/%s", dir, path); + if (dryrun) { + fd = open(_PATH_DEVNULL, O_WRONLY); + } else { + tmppath = xasprintf(".%s", path); + fd = openat(d, tmppath, + O_CREAT | O_WRONLY | O_EXCL, mode); + } + } + /* write the certificate */ + if (fd < 0 || + (f = fdopen(fd, "w")) == NULL || + !PEM_write_X509(f, file->cert->x509)) { + if (tmppath != NULL && fd >= 0) { + int serrno = errno; + (void)unlinkat(d, tmppath, 0); + errno = serrno; + } + err(1, "%s/%s", dir, tmppath ? tmppath : path); + } + /* rename temp file if applicable */ + if (tmppath != NULL) { + if (ret == 0 && renameat(d, tmppath, d, path) != 0) { + warn("%s/%s", dir, path); + ret = -1; + } + if (ret != 0) + (void)unlinkat(d, tmppath, 0); + free(tmppath); + tmppath = NULL; + } + fflush(f); + /* emit metalog */ + if (mlf != NULL) { + fprintf(mlf, ".%s/%s type=file " + "uname=%s gname=%s mode=%#o size=%ld\n", + unexpand_path(dir), path, + uname, gname, mode, ftell(f)); + } + fclose(f); + /* advance certificate listing */ + tmp = RB_NEXT(file_tree, &files, file); + RB_REMOVE(file_tree, &files, file); + free(file); + file = tmp; + } + free(dents); + close(d); + return (ret); +} + +/* + * Save all certs in a tree to a single file (bundle). + * + * Returns 0 on success and -1 on failure. + */ +static int +write_bundle(const char *dir, const char *file, struct cert_tree *tree) +{ + struct cert *cert; + char *tmpfile = NULL; + FILE *f; + int d, fd, ret = 0; + mode_t mode = 0444; + + if (dir != NULL) { + if ((d = open(dir, O_DIRECTORY | O_RDONLY)) < 0) + err(1, "%s", dir); + } else { + dir = "."; + d = AT_FDCWD; + } + info("writing %s/%s", dir, file); + if (dryrun) { + fd = open(_PATH_DEVNULL, O_WRONLY); + } else { + tmpfile = xasprintf(".%s", file); + fd = openat(d, tmpfile, O_WRONLY | O_CREAT | O_EXCL, mode); + } + if (fd < 0 || (f = fdopen(fd, "w")) == NULL) { + if (tmpfile != NULL && fd >= 0) { + int serrno = errno; + (void)unlinkat(d, tmpfile, 0); + errno = serrno; + } + err(1, "%s/%s", dir, tmpfile ? tmpfile : file); + } + RB_FOREACH(cert, cert_tree, tree) { + if (!PEM_write_X509(f, cert->x509)) { + warn("%s/%s", dir, tmpfile ? tmpfile : file); + ret = -1; + break; + } + } + if (tmpfile != NULL) { + if (ret == 0 && renameat(d, tmpfile, d, file) != 0) { + warn("%s/%s", dir, file); + ret = -1; + } + if (ret != 0) + (void)unlinkat(d, tmpfile, 0); + free(tmpfile); + } + if (ret == 0 && mlf != NULL) { + fprintf(mlf, + ".%s/%s type=file uname=%s gname=%s mode=%#o size=%ld\n", + unexpand_path(dir), file, uname, gname, mode, ftell(f)); + } + fclose(f); + if (d != AT_FDCWD) + close(d); + return (ret); +} + +/* + * Load trusted certificates. + * + * Returns the number of certificates loaded. + */ +static unsigned int +load_trusted(bool all, struct cert_tree *exclude) +{ + 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); + 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); +} + +/* + * Load untrusted certificates. + * + * Returns the number of certificates loaded. + */ +static unsigned int +load_untrusted(bool all) +{ + char *path; + unsigned int i, n; + int ret; + + /* load external untrusted certs */ + for (i = n = 0; all && 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); + if (ret > 0) { + warnx("certificates found in legacy directory %s", + path); + n += ret; + } else if (ret == 0) { + warnx("legacy directory %s can safely be deleted", + path); + } + free(path); + + info("%d untrusted certificates found", n); + return (n); +} + +/* + * Save trusted certificates. + * + * Returns 0 on success and -1 on failure. + */ +static int +save_trusted(void) +{ + int ret; + + mkdirp(trusted_dest); + ret = write_certs(trusted_dest, &trusted); + return (ret); +} + +/* + * Save untrusted certificates. + * + * Returns 0 on success and -1 on failure. + */ +static int +save_untrusted(void) +{ + int ret; + + mkdirp(untrusted_dest); + ret = write_certs(untrusted_dest, &untrusted); + return (ret); +} + +/* + * Save certificate bundle. + * + * Returns 0 on success and -1 on failure. + */ +static int +save_bundle(void) +{ + char *dir, *file, *sep; + int ret; + + if ((sep = strrchr(bundle_dest, '/')) == NULL) { + dir = NULL; + file = bundle_dest; + } else { + dir = xasprintf("%.*s", (int)(sep - bundle_dest), bundle_dest); + file = sep + 1; + mkdirp(dir); + } + ret = write_bundle(dir, file, &trusted); + free(dir); + return (ret); +} + +/* + * Save everything. + * + * Returns 0 on success and -1 on failure. + */ +static int +save_all(void) +{ + int ret = 0; + + ret |= save_untrusted(); + ret |= save_trusted(); + if (!nobundle) + ret |= save_bundle(); + return (ret); +} + +/* + * List the contents of a certificate tree. + */ +static void +list_certs(struct cert_tree *tree) +{ + struct cert *cert; + char *path, *name; + + RB_FOREACH(cert, cert_tree, tree) { + path = longnames ? NULL : strrchr(cert->path, '/'); + name = longnames ? NULL : strrchr(cert->name, '='); + printf("%s\t%s\n", path ? path + 1 : cert->path, + name ? name + 1 : cert->name); + } +} + +/* + * Load installed trusted certificates, then list them. + * + * Returns 0 on success and -1 on failure. + */ +static int +certctl_list(int argc, char **argv __unused) +{ + if (argc > 1) + usage(); + /* load trusted certificates */ + load_trusted(false, NULL); + /* list them */ + list_certs(&trusted); + free_certs(&trusted); + return (0); +} + +/* + * Load installed untrusted certificates, then list them. + * + * Returns 0 on success and -1 on failure. + */ +static int +certctl_untrusted(int argc, char **argv __unused) +{ + if (argc > 1) + usage(); + /* load untrusted certificates */ + load_untrusted(false); + /* list them */ + list_certs(&untrusted); + free_certs(&untrusted); + return (0); +} + +/* + * Load trusted and untrusted certificates from all sources, then + * regenerate both the hashed directories and the bundle. + * + * Returns 0 on success and -1 on failure. + */ +static int +certctl_rehash(int argc, char **argv __unused) +{ + int ret; + + if (argc > 1) + usage(); + + if (unprivileged && (mlf = fopen(metalog, "a")) == NULL) { + warn("%s", metalog); + return (-1); + } + + /* load untrusted certs first */ + load_untrusted(true); + + /* load trusted certs, excluding any that are already untrusted */ + load_trusted(true, &untrusted); + + /* save everything */ + ret = save_all(); + + /* clean up */ + free_certs(&untrusted); + free_certs(&trusted); + if (mlf != NULL) + fclose(mlf); + return (ret); +} + +/* + * Manually add one or more certificates to the list of trusted certificates. + * + * Returns 0 on success and -1 on failure. + */ +static int +certctl_trust(int argc, char **argv) +{ + struct cert_tree extra = RB_INITIALIZER(&extra); + struct cert *cert, *other, *tmp; + unsigned int n; + int i, ret; + + if (argc < 2) + usage(); + + /* load untrusted certs first */ + load_untrusted(true); + + /* load trusted certs, excluding any that are already untrusted */ + load_trusted(true, &untrusted); + + /* now load the additional trusted certificates */ + n = 0; + for (i = 1; i < argc; i++) { + ret = read_cert(argv[i], &extra, &trusted); + if (ret > 0) + n += ret; + } + if (n == 0) { + warnx("no new trusted certificates found"); + free_certs(&untrusted); + free_certs(&trusted); + free_certs(&extra); + return (0); + } + + /* + * For each new trusted cert, move it from the extra list to the + * trusted list, then check if a matching certificate exists on + * the untrusted list. If that is the case, warn the user, then + * remove the matching certificate from the untrusted list. + */ + RB_FOREACH_SAFE(cert, cert_tree, &extra, tmp) { + RB_REMOVE(cert_tree, &extra, cert); + RB_INSERT(cert_tree, &trusted, cert); + if ((other = RB_FIND(cert_tree, &untrusted, cert)) != NULL) { + warnx("%s was previously untrusted", cert->name); + RB_REMOVE(cert_tree, &untrusted, other); + free_cert(other); + } + } + + /* save everything */ + ret = save_all(); + + /* clean up */ + free_certs(&untrusted); + free_certs(&trusted); + return (ret); +} + +/* + * Manually add one or more certificates to the list of untrusted + * certificates. + * + * Returns 0 on success and -1 on failure. + */ +static int +certctl_untrust(int argc, char **argv) +{ + unsigned int n; + int i, ret; + + if (argc < 2) + usage(); + + /* load untrusted certs first */ + load_untrusted(true); + + /* now load the additional untrusted certificates */ + n = 0; + for (i = 1; i < argc; i++) { + ret = read_cert(argv[i], &untrusted, NULL); + if (ret > 0) + n += ret; + } + if (n == 0) { + warnx("no new untrusted certificates found"); + free_certs(&untrusted); + return (0); + } + + /* load trusted certs, excluding any that are already untrusted */ + load_trusted(true, &untrusted); + + /* save everything */ + ret = save_all(); + + /* clean up */ + free_certs(&untrusted); + free_certs(&trusted); + return (ret); +} + +static void +set_defaults(void) +{ + const char *value; + char *str; + size_t len; + + if (localbase == NULL && + (localbase = getenv("LOCALBASE")) == NULL) { + if ((str = malloc((len = PATH_MAX) + 1)) == NULL) + err(1, NULL); + while (sysctlbyname("user.localbase", str, &len, NULL, 0) < 0) { + if (errno != ENOMEM) + err(1, "sysctl(user.localbase)"); + if ((str = realloc(str, len + 1)) == NULL) + err(1, NULL); + } + str[len] = '\0'; + localbase = str; + } + + if (destdir == NULL && + (destdir = getenv("DESTDIR")) == NULL) + destdir = ""; + destdir = normalize_path(destdir); + + if (distbase == NULL && + (distbase = getenv("DISTBASE")) == NULL) + distbase = ""; + if (*distbase != '\0' && *distbase != '/') + errx(1, "DISTBASE=%s does not begin with a slash", distbase); + distbase = normalize_path(distbase); + + if (unprivileged && metalog == NULL && + (metalog = getenv("METALOG")) == NULL) + metalog = xasprintf("%s/METALOG", destdir); + + if (!verbose) { + if ((value = getenv("CERTCTL_VERBOSE")) != NULL) { + if (value[0] != '\0') { + verbose = true; + } + } + } + + if ((value = getenv("TRUSTPATH")) != NULL) + trusted_paths = split_paths(value); + else + trusted_paths = expand_paths(default_trusted_paths); + + if ((value = getenv("UNTRUSTPATH")) != NULL) + untrusted_paths = split_paths(value); + else + untrusted_paths = expand_paths(default_untrusted_paths); + + if ((value = getenv("TRUSTDESTDIR")) != NULL || + (value = getenv("CERTDESTDIR")) != NULL) + trusted_dest = normalize_path(value); + else + trusted_dest = expand_path(TRUSTED_PATH); + + if ((value = getenv("UNTRUSTDESTDIR")) != NULL) + untrusted_dest = normalize_path(value); + else + untrusted_dest = expand_path(UNTRUSTED_PATH); + + if ((value = getenv("BUNDLE")) != NULL) + bundle_dest = normalize_path(value); + else + bundle_dest = expand_path(BUNDLE_PATH); + + info("localbase:\t%s", localbase); + info("destdir:\t%s", destdir); + info("distbase:\t%s", distbase); + info("unprivileged:\t%s", unprivileged ? "true" : "false"); + info("verbose:\t%s", verbose ? "true" : "false"); +} + +typedef int (*main_t)(int, char **); + +static struct { + const char *name; + main_t func; +} commands[] = { + { "list", certctl_list }, + { "untrusted", certctl_untrusted }, + { "rehash", certctl_rehash }, + { "untrust", certctl_untrust }, + { "trust", certctl_trust }, + { 0 }, +}; + +static void +usage(void) +{ + fprintf(stderr, "usage: certctl [-lv] [-D destdir] [-d distbase] list\n" + " certctl [-lv] [-D destdir] [-d distbase] untrusted\n" + " certctl [-BnUv] [-D destdir] [-d distbase] [-M metalog] rehash\n" + " certctl [-nv] [-D destdir] [-d distbase] untrust <file>\n" + " certctl [-nv] [-D destdir] [-d distbase] trust <file>\n"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + const char *command; + int opt; + + while ((opt = getopt(argc, argv, "BcD:d:g:lL:M:no:Uv")) != -1) + switch (opt) { + case 'B': + nobundle = true; + break; + case 'c': + /* ignored for compatibility */ + break; + case 'D': + destdir = optarg; + break; + case 'd': + distbase = optarg; + break; + case 'g': + gname = optarg; + break; + case 'l': + longnames = true; + break; + case 'L': + localbase = optarg; + break; + case 'M': + metalog = optarg; + break; + case 'n': + dryrun = true; + break; + case 'o': + uname = optarg; + break; + case 'U': + unprivileged = true; + break; + case 'v': + verbose = true; + break; + default: + usage(); + } + + argc -= optind; + argv += optind; + + if (argc < 1) + usage(); + + command = *argv; + + if ((nobundle || unprivileged || metalog != NULL) && + strcmp(command, "rehash") != 0) + usage(); + if (!unprivileged && metalog != NULL) { + warnx("-M may only be used in conjunction with -U"); + usage(); + } + + set_defaults(); + + for (unsigned i = 0; commands[i].name != NULL; i++) + if (strcmp(command, commands[i].name) == 0) + exit(!!commands[i].func(argc, argv)); + usage(); +} diff --git a/usr.sbin/certctl/certctl.sh b/usr.sbin/certctl/certctl.sh deleted file mode 100755 index 458f5c53682f..000000000000 --- a/usr.sbin/certctl/certctl.sh +++ /dev/null @@ -1,366 +0,0 @@ -#!/bin/sh -#- -# SPDX-License-Identifier: BSD-2-Clause -# -# Copyright 2018 Allan Jude <allanjude@freebsd.org> -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted providing 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 ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING -# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# - -set -u - -############################################################ CONFIGURATION - -: ${DESTDIR:=} -: ${DISTBASE:=} - -############################################################ GLOBALS - -SCRIPTNAME="${0##*/}" -ERRORS=0 -NOOP=false -UNPRIV=false -VERBOSE=false - -############################################################ FUNCTIONS - -info() -{ - echo "${0##*/}: $@" >&2 -} - -verbose() -{ - if "${VERBOSE}" ; then - info "$@" - fi -} - -perform() -{ - if ! "${NOOP}" ; then - "$@" - fi -} - -cert_files_in() -{ - find -L "$@" -type f \( \ - -name '*.pem' -or \ - -name '*.crt' -or \ - -name '*.cer' \ - \) 2>/dev/null -} - -eolcvt() -{ - cat "$@" | tr -s '\r' '\n' -} - -do_hash() -{ - local hash - - if hash=$(openssl x509 -noout -subject_hash -in "$1") ; then - echo "$hash" - return 0 - else - info "Error: $1" - ERRORS=$((ERRORS + 1)) - return 1 - fi -} - -get_decimal() -{ - local checkdir hash decimal - - checkdir=$1 - hash=$2 - decimal=0 - - while [ -e "$checkdir/$hash.$decimal" ] ; do - decimal=$((decimal + 1)) - done - - echo ${decimal} - return 0 -} - -create_trusted() -{ - local hash certhash otherfile otherhash - local suffix - local link=${2:+-lrs} - - hash=$(do_hash "$1") || return - certhash=$(openssl x509 -sha1 -in "$1" -noout -fingerprint) - for otherfile in $(find $UNTRUSTDESTDIR -name "$hash.*") ; do - otherhash=$(openssl x509 -sha1 -in "$otherfile" -noout -fingerprint) - if [ "$certhash" = "$otherhash" ] ; then - info "Skipping untrusted certificate $hash ($otherfile)" - return 0 - fi - done - for otherfile in $(find $CERTDESTDIR -name "$hash.*") ; do - otherhash=$(openssl x509 -sha1 -in "$otherfile" -noout -fingerprint) - if [ "$certhash" = "$otherhash" ] ; then - verbose "Skipping duplicate entry for certificate $hash" - return 0 - fi - done - suffix=$(get_decimal "$CERTDESTDIR" "$hash") - verbose "Adding $hash.$suffix to trust store" - perform install ${INSTALLFLAGS} -m 0444 ${link} \ - "$(realpath "$1")" "$CERTDESTDIR/$hash.$suffix" -} - -# Accepts either dot-hash form from `certctl list` or a path to a valid cert. -resolve_certname() -{ - local hash srcfile filename - local suffix - - # If it exists as a file, we'll try that; otherwise, we'll scan - if [ -e "$1" ] ; then - hash=$(do_hash "$1") || return - srcfile=$(realpath "$1") - suffix=$(get_decimal "$UNTRUSTDESTDIR" "$hash") - filename="$hash.$suffix" - echo "$srcfile" "$hash.$suffix" - elif [ -e "${CERTDESTDIR}/$1" ] ; then - srcfile=$(realpath "${CERTDESTDIR}/$1") - hash=$(echo "$1" | sed -Ee 's/\.([0-9])+$//') - suffix=$(get_decimal "$UNTRUSTDESTDIR" "$hash") - filename="$hash.$suffix" - echo "$srcfile" "$hash.$suffix" - fi -} - -create_untrusted() -{ - local srcfile filename - local link=${2:+-lrs} - - set -- $(resolve_certname "$1") - srcfile=$1 - filename=$2 - - if [ -z "$srcfile" -o -z "$filename" ] ; then - return - fi - - verbose "Adding $filename to untrusted list" - perform install ${INSTALLFLAGS} -m 0444 ${link} \ - "$srcfile" "$UNTRUSTDESTDIR/$filename" -} - -do_scan() -{ - local CFUNC CSEARCH CPATH CFILE CERT SPLITDIR - local oldIFS="$IFS" - CFUNC="$1" - CSEARCH="$2" - - IFS=: - set -- $CSEARCH - IFS="$oldIFS" - for CFILE in $(cert_files_in "$@") ; do - verbose "Reading $CFILE" - case $(eolcvt "$CFILE" | egrep -c '^-+BEGIN CERTIFICATE-+$') in - 0) - ;; - 1) - "$CFUNC" "$CFILE" link - ;; - *) - verbose "Multiple certificates found, splitting..." - SPLITDIR=$(mktemp -d) - eolcvt "$CFILE" | egrep '^(---|[0-9A-Za-z/+=]+$)' | \ - split -p '^-+BEGIN CERTIFICATE-+$' - "$SPLITDIR/x" - for CERT in $(find "$SPLITDIR" -type f) ; do - "$CFUNC" "$CERT" - done - rm -rf "$SPLITDIR" - ;; - esac - done -} - -do_list() -{ - local CFILE subject - - for CFILE in $(find "$@" \( -type f -or -type l \) -name '*.[0-9]') ; do - if [ ! -s "$CFILE" ] ; then - info "Unable to read $CFILE" - ERRORS=$((ERRORS + 1)) - continue - fi - subject= - if ! "$VERBOSE" ; then - subject=$(openssl x509 -noout -subject -nameopt multiline -in "$CFILE" | sed -n '/commonName/s/.*= //p') - fi - if [ -z "$subject" ] ; then - subject=$(openssl x509 -noout -subject -in "$CFILE") - fi - printf "%s\t%s\n" "${CFILE##*/}" "$subject" - done -} - -cmd_rehash() -{ - - if [ -e "$CERTDESTDIR" ] ; then - perform find "$CERTDESTDIR" \( -type f -or -type l \) -delete - else - perform install -d -m 0755 "$CERTDESTDIR" - fi - if [ -e "$UNTRUSTDESTDIR" ] ; then - perform find "$UNTRUSTDESTDIR" \( -type f -or -type l \) -delete - else - perform install -d -m 0755 "$UNTRUSTDESTDIR" - fi - - do_scan create_untrusted "$UNTRUSTPATH" - do_scan create_trusted "$TRUSTPATH" -} - -cmd_list() -{ - info "Listing Trusted Certificates:" - do_list "$CERTDESTDIR" -} - -cmd_untrust() -{ - local UTFILE - - shift # verb - perform install -d -m 0755 "$UNTRUSTDESTDIR" - for UTFILE in "$@"; do - info "Adding $UTFILE to untrusted list" - create_untrusted "$UTFILE" - done -} - -cmd_trust() -{ - local UTFILE untrustedhash certhash hash - - shift # verb - for UTFILE in "$@"; do - if [ -s "$UTFILE" ] ; then - hash=$(do_hash "$UTFILE") - certhash=$(openssl x509 -sha1 -in "$UTFILE" -noout -fingerprint) - for UNTRUSTEDFILE in $(find $UNTRUSTDESTDIR -name "$hash.*") ; do - untrustedhash=$(openssl x509 -sha1 -in "$UNTRUSTEDFILE" -noout -fingerprint) - if [ "$certhash" = "$untrustedhash" ] ; then - info "Removing $(basename "$UNTRUSTEDFILE") from untrusted list" - perform rm -f $UNTRUSTEDFILE - fi - done - elif [ -e "$UNTRUSTDESTDIR/$UTFILE" ] ; then - info "Removing $UTFILE from untrusted list" - perform rm -f "$UNTRUSTDESTDIR/$UTFILE" - else - info "Cannot find $UTFILE" - ERRORS=$((ERRORS + 1)) - fi - done -} - -cmd_untrusted() -{ - info "Listing Untrusted Certificates:" - do_list "$UNTRUSTDESTDIR" -} - -usage() -{ - exec >&2 - echo "Manage the TLS trusted certificates on the system" - echo " $SCRIPTNAME [-v] list" - echo " List trusted certificates" - echo " $SCRIPTNAME [-v] untrusted" - echo " List untrusted certificates" - echo " $SCRIPTNAME [-nUv] [-D <destdir>] [-d <distbase>] [-M <metalog>] rehash" - echo " Generate hash links for all certificates" - echo " $SCRIPTNAME [-nv] untrust <file>" - echo " Add <file> to the list of untrusted certificates" - echo " $SCRIPTNAME [-nv] trust <file>" - echo " Remove <file> from the list of untrusted certificates" - exit 64 -} - -############################################################ MAIN - -while getopts D:d:M:nUv flag; do - case "$flag" in - D) DESTDIR=${OPTARG} ;; - d) DISTBASE=${OPTARG} ;; - M) METALOG=${OPTARG} ;; - n) NOOP=true ;; - U) UNPRIV=true ;; - v) VERBOSE=true ;; - esac -done -shift $((OPTIND - 1)) - -DESTDIR=${DESTDIR%/} - -if ! [ -z "${CERTCTL_VERBOSE:-}" ] ; then - VERBOSE=true -fi -: ${METALOG:=${DESTDIR}/METALOG} -INSTALLFLAGS= -if "$UNPRIV" ; then - INSTALLFLAGS="-U -M ${METALOG} -D ${DESTDIR} -o root -g wheel" -fi -: ${LOCALBASE:=$(sysctl -n user.localbase)} -: ${TRUSTPATH:=${DESTDIR}${DISTBASE}/usr/share/certs/trusted:${DESTDIR}${LOCALBASE}/share/certs:${DESTDIR}${LOCALBASE}/etc/ssl/certs} -: ${UNTRUSTPATH:=${DESTDIR}${DISTBASE}/usr/share/certs/untrusted:${DESTDIR}${LOCALBASE}/etc/ssl/untrusted:${DESTDIR}${LOCALBASE}/etc/ssl/blacklisted} -: ${CERTDESTDIR:=${DESTDIR}${DISTBASE}/etc/ssl/certs} -: ${UNTRUSTDESTDIR:=${DESTDIR}${DISTBASE}/etc/ssl/untrusted} - -[ $# -gt 0 ] || usage -case "$1" in -list) cmd_list ;; -rehash) cmd_rehash ;; -blacklist) cmd_untrust "$@" ;; -untrust) cmd_untrust "$@" ;; -trust) cmd_trust "$@" ;; -unblacklist) cmd_trust "$@" ;; -untrusted) cmd_untrusted ;; -blacklisted) cmd_untrusted ;; -*) usage # NOTREACHED -esac - -retval=$? -if [ $ERRORS -gt 0 ] ; then - info "Encountered $ERRORS errors" -fi -exit $retval - -################################################################################ -# END -################################################################################ diff --git a/usr.sbin/certctl/tests/Makefile b/usr.sbin/certctl/tests/Makefile new file mode 100644 index 000000000000..da301c3ded03 --- /dev/null +++ b/usr.sbin/certctl/tests/Makefile @@ -0,0 +1,5 @@ +PACKAGE= tests +ATF_TESTS_SH= certctl_test +${PACKAGE}FILES+= certctl.subr + +.include <bsd.test.mk> diff --git a/usr.sbin/certctl/tests/certctl.subr b/usr.sbin/certctl/tests/certctl.subr new file mode 100644 index 000000000000..841cc1781e69 --- /dev/null +++ b/usr.sbin/certctl/tests/certctl.subr @@ -0,0 +1,44 @@ +# +# Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org> +# +# SPDX-License-Identifier: BSD-2-Clause +# + +# Generate a random name +rand_name() { + local length=${1:-32} + + jot -r -c -s '' ${length} A Z +} + +# Generate a subject for a given name +subject() { + local crtname=$1 + + echo "/CN=${crtname}/O=FreeBSD/OU=Test/" +} + +# Generate a key +gen_key() { + local keyname=$1 + + env -i PATH="${PATH}" OPENSSL_CONF=/dev/null \ + openssl genrsa -out ${keyname}.key +} + +# Generate a certificate for a given name, key, and serial number +gen_crt() { + local crtname=$1 + local keyname=${2:-${crtname}} + local serial=${3:-1} + + if ! [ -f "${keyname}".key ]; then + gen_key "${keyname}" + fi + env -i PATH="${PATH}" OPENSSL_CONF=/dev/null \ + openssl req -x509 -new \ + -subj="$(subject ${crtname})" \ + -set_serial ${serial} \ + -key ${keyname}.key \ + -out ${crtname}.crt +} diff --git a/usr.sbin/certctl/tests/certctl_test.sh b/usr.sbin/certctl/tests/certctl_test.sh new file mode 100644 index 000000000000..74749db0b3f5 --- /dev/null +++ b/usr.sbin/certctl/tests/certctl_test.sh @@ -0,0 +1,332 @@ +# +# Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org> +# +# SPDX-License-Identifier: BSD-2-Clause +# + +. $(atf_get_srcdir)/certctl.subr + +# Random sets of eight non-colliding names +set1() +{ + cat <<EOF +AVOYKJHSLFHWPVQMKBHENUAHJTEGMCCB 0ca83bbe +UYSYXKDNNJTYOQPBGIKQDHRJYZHTDPKK 0d9a6512 +LODHGFXMZYKGOKAYGWTMMYQJYHDATDDM 4e6219f5 +NBBTQHJLHKBFFFWJTHHSNKOQYMGLHLPW 5dd76abc +BJFAQZXZHYQLIDDPCAQFPDMNXICUXBXW ad68573d +IOKNTHVEVVIJMNMYAVILMEMQQWLVRESN b577803d +BHGMAJJGNJPIVMHMFCUTJLGFROJICEKN c98a6338 +HCRFQMGDQJALMLUQNXMPGLXFLLJRODJW f50c6379 +EOF +} + +set2() +{ + cat <<EOF +GOHKZTSKIPDSYNLMGYXGLROPTATELXIU 30789c88 +YOOTYHEGHZIYFXOBLNKENPSJUDGOPJJU 7fadbc13 +ETRINNYBGKIENAVGOKVJYFSSHFZIJZRH 8ed664af +DBFGMFFMRNLPQLQPOLXOEUVLCRXLRSWT 8f34355e +WFOPBQPLQFHDHZOUQFEIDGSYDUOTSNDQ ac0471df +HMNETZMGNIWRGXQCVZXVZGWSGFBRRDQC b32f1472 +SHFYBXDVAUACBFPPAIGDAQIAGYOYGMQE baca75fa +PCBGDNVPYCDGNRQSGRSLXFHYKXLAVLHW ddeeae01 +EOF +} + +set3() +{ + cat <<EOF +NJWIRLPWAIICVJBKXXHFHLCPAERZATRL 000aa2e5 +RJAENDPOCZQEVCPFUWOWDXPCSMYJPVYC 021b95a3 +PQUQDSWHBNVLBTNBGONYRLGZZVEFXVLO 071e8c50 +VZEXRKJUPZSFBDWBOLUZXOGLNTEAPCZM 3af7bb9b +ZXOWOXQTXNZMAMZIWVFDZDJEWOOAGAOH 48d5c7cc +KQSFQYVJMFTMADIHJIWGSQISWKSHRYQO 509f5ba1 +AIECYSLWZOIEPJWWUTWSQXCNCIHHZHYI 8cb0c503 +RFHWDJZEPOFLMPGXAHVEJFHCDODAPVEV 9ae4e049 +EOF +} + +# Random set of three colliding names +collhash=f2888ce3 +coll() +{ + cat <<EOF +EJFTZEOANQLOYPEHWWXBWEWEFVKHMSNA $collhash +LEMRWZAZLKZLPPSFLNLQZVGKKBEOFYWG $collhash +ZWUPHYWKKTVEFBJOLLPDAIKGRDFVXZID $collhash +EOF +} + +sortfile() { + for filename; do + sort "${filename}" >"${filename}"- + mv "${filename}"- "${filename}" + done +} + +certctl_setup() +{ + export DESTDIR="$PWD" + + # Create input directories + mkdir -p ${DESTDIR}${DISTBASE}/usr/share/certs/trusted + mkdir -p ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted + mkdir -p ${DESTDIR}/usr/local/share/certs + + # Do not create output directories; certctl will take care of it + #mkdir -p ${DESTDIR}${DISTBASE}/etc/ssl/certs + #mkdir -p ${DESTDIR}${DISTBASE}/etc/ssl/untrusted + + # Generate a random key + keyname="testkey" + gen_key ${keyname} + + # Generate certificates + :>metalog.expect + :>trusted.expect + :>untrusted.expect + metalog() { + echo ".${DISTBASE}$@ type=file" >>metalog.expect + } + trusted() { + local crtname=$1 + local filename=$2 + printf "%s\t%s\n" "${filename}" "${crtname}" >>trusted.expect + metalog "/etc/ssl/certs/${filename}" + } + untrusted() { + local crtname=$1 + local filename=$2 + printf "%s\t%s\n" "${filename}" "${crtname}" >>untrusted.expect + metalog "/etc/ssl/untrusted/${filename}" + } + set1 | while read crtname hash ; do + gen_crt ${crtname} ${keyname} + mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/trusted + trusted "${crtname}" "${hash}.0" + done + local c=0 + coll | while read crtname hash ; do + gen_crt ${crtname} ${keyname} + mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/trusted + trusted "${crtname}" "${hash}.${c}" + c=$((c+1)) + done + set2 | while read crtname hash ; do + gen_crt ${crtname} ${keyname} + openssl x509 -in ${crtname}.crt + rm ${crtname}.crt + trusted "${crtname}" "${hash}.0" + done >usr/local/share/certs/bundle.crt + set3 | while read crtname hash ; do + gen_crt ${crtname} ${keyname} + mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted + untrusted "${crtname}" "${hash}.0" + done + metalog "/etc/ssl/cert.pem" + unset -f untrusted + unset -f trusted + unset -f metalog + sortfile *.expect +} + +check_trusted() { + local crtname=$1 + local subject="$(subject ${crtname})" + local c=${2:-1} + + atf_check -e ignore -o match:"found: ${c}\$" \ + openssl storeutl -noout -subject "${subject}" \ + ${DESTDIR}${DISTBASE}/etc/ssl/certs + atf_check -e ignore -o not-match:"found: [1-9]" \ + openssl storeutl -noout -subject "${subject}" \ + ${DESTDIR}${DISTBASE}/etc/ssl/untrusted +} + +check_untrusted() { + local crtname=$1 + local subject="$(subject ${crtname})" + local c=${2:-1} + + atf_check -e ignore -o not-match:"found: [1-9]" \ + openssl storeutl -noout -subject "${subject}" \ + ${DESTDIR}/${DISTBASE}/etc/ssl/certs + atf_check -e ignore -o match:"found: ${c}\$" \ + openssl storeutl -noout -subject "${subject}" \ + ${DESTDIR}/${DISTBASE}/etc/ssl/untrusted +} + +check_in_bundle() { + local b=${DISTBASE}${DISTBASE+/} + local crtfile=$1 + local line + + line=$(tail +5 "${crtfile}" | head -1) + atf_check grep -q "${line}" ${DESTDIR}${DISTBASE}/etc/ssl/cert.pem +} + +check_not_in_bundle() { + local b=${DISTBASE}${DISTBASE+/} + local crtfile=$1 + local line + + line=$(tail +5 "${crtfile}" | head -1) + atf_check -s exit:1 grep -q "${line}" etc/ssl/cert.pem +} + +atf_test_case rehash +rehash_head() +{ + atf_set "descr" "Test the rehash command" +} +rehash_body() +{ + certctl_setup + atf_check certctl rehash + + # Verify non-colliding trusted certificates + (set1; set2) >trusted + while read crtname hash ; do + check_trusted "${crtname}" + done <trusted + + # Verify colliding trusted certificates + coll >coll + while read crtname hash ; do + check_trusted "${crtname}" $(wc -l <coll) + done <coll + + # Verify untrusted certificates + set3 >untrusted + while read crtname hash ; do + check_untrusted "${crtname}" + done <untrusted + + # Verify bundle + for f in etc/ssl/certs/*.? ; do + check_in_bundle "${f}" + done + for f in etc/ssl/untrusted/*.? ; do + check_not_in_bundle "${f}" + done +} + +atf_test_case list +list_head() +{ + atf_set "descr" "Test the list and untrusted commands" +} +list_body() +{ + certctl_setup + atf_check certctl rehash + + atf_check -o save:trusted.out certctl list + sortfile trusted.out + # the ordering of the colliding certificates is partly + # determined by fields that change every time we regenerate + # them, so ignore them in the diff + atf_check diff -u \ + --ignore-matching-lines $collhash \ + trusted.expect trusted.out + + atf_check -o save:untrusted.out certctl untrusted + sortfile untrusted.out + atf_check diff -u \ + untrusted.expect untrusted.out +} + +atf_test_case trust +trust_head() +{ + atf_set "descr" "Test the trust command" +} +trust_body() +{ + certctl_setup + atf_check certctl rehash + crtname=$(set3 | (read crtname hash ; echo ${crtname})) + crtfile=usr/share/certs/untrusted/${crtname}.crt + check_untrusted ${crtname} + check_not_in_bundle ${crtfile} + atf_check -e match:"was previously untrusted" \ + certctl trust ${crtfile} + check_trusted ${crtname} + check_in_bundle ${crtfile} +} + +atf_test_case untrust +untrust_head() +{ + atf_set "descr" "Test the untrust command" +} +untrust_body() +{ + certctl_setup + atf_check certctl rehash + crtname=$(set1 | (read crtname hash ; echo ${crtname})) + crtfile=usr/share/certs/trusted/${crtname}.crt + check_trusted "${crtname}" + check_in_bundle ${crtfile} + atf_check certctl untrust "${crtfile}" + check_untrusted "${crtname}" + check_not_in_bundle ${crtfile} +} + +atf_test_case metalog +metalog_head() +{ + atf_set "descr" "Verify the metalog" +} +metalog_body() +{ + export DISTBASE=/base + certctl_setup + + # certctl gets DESTDIR and DISTBASE from environment + rm -f metalog.orig + atf_check certctl -U -M metalog.orig rehash + sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short + atf_check diff -u metalog.expect metalog.short + + # certctl gets DESTDIR and DISTBASE from command line + rm -f metalog.orig + atf_check env -uDESTDIR -uDISTBASE \ + certctl -D ${DESTDIR} -d ${DISTBASE} -U -M metalog.orig rehash + sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short + atf_check diff -u metalog.expect metalog.short + + # as above, but intentionally add trailing slashes + rm -f metalog.orig + atf_check env -uDESTDIR -uDISTBASE \ + certctl -D ${DESTDIR}// -d ${DISTBASE}/ -U -M metalog.orig rehash + sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short + atf_check diff -u metalog.expect metalog.short +} + +atf_test_case misc +misc_head() +{ + atf_set "descr" "Test miscellaneous edge cases" +} +misc_body() +{ + # certctl rejects DISTBASE that does not begin with a slash + atf_check -s exit:1 -e match:"begin with a slash" \ + certctl -d base -n rehash + atf_check -s exit:1 -e match:"begin with a slash" \ + env DISTBASE=base certctl -n rehash +} + +atf_init_test_cases() +{ + atf_add_test_case rehash + atf_add_test_case list + atf_add_test_case trust + atf_add_test_case untrust + atf_add_test_case metalog + atf_add_test_case misc +} diff --git a/usr.sbin/chown/Makefile b/usr.sbin/chown/Makefile index 3e6ce8bfbd8c..4e26b9457fd5 100644 --- a/usr.sbin/chown/Makefile +++ b/usr.sbin/chown/Makefile @@ -1,5 +1,6 @@ .include <src.opts.mk> +PACKAGE= runtime PROG= chown LINKS= ${BINDIR}/chown /usr/bin/chgrp MAN= chgrp.1 chown.8 diff --git a/usr.sbin/chroot/chroot.8 b/usr.sbin/chroot/chroot.8 index f26b7e937da9..4a1a5a396631 100644 --- a/usr.sbin/chroot/chroot.8 +++ b/usr.sbin/chroot/chroot.8 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 20, 2021 +.Dd July 25, 2025 .Dt CHROOT 8 .Os .Sh NAME @@ -52,13 +52,15 @@ or an interactive copy of the user's login shell. The options are as follows: .Bl -tag -width "-G group[,group ...]" .It Fl G Ar group Ns Op Cm \&, Ns Ar group ... -Run the command with the permissions of the specified groups. +Run the command with the specified groups as supplementary groups. .It Fl g Ar group -Run the command with the permissions of the specified -.Ar group . +Run the command with the specified +.Ar group +as the real, effective and saved groups. .It Fl u Ar user -Run the command as the -.Ar user . +Run the command with the specified +.Ar user +as the real, effective and saved users. .It Fl n Use the .Dv PROC_NO_NEW_PRIVS_CTL diff --git a/usr.sbin/chroot/chroot.c b/usr.sbin/chroot/chroot.c index 32becaf12588..e1af0a4131d3 100644 --- a/usr.sbin/chroot/chroot.c +++ b/usr.sbin/chroot/chroot.c @@ -34,6 +34,7 @@ #include <ctype.h> #include <err.h> +#include <errno.h> #include <grp.h> #include <limits.h> #include <paths.h> @@ -46,22 +47,65 @@ static void usage(void) __dead2; +static gid_t +resolve_group(const char *group) +{ + char *endp; + struct group *gp; + unsigned long gid; + + gp = getgrnam(group); + if (gp != NULL) + return (gp->gr_gid); + + /* + * Numeric IDs don't need a trip through the database to check them, + * POSIX seems to think we should generally accept a numeric ID as long + * as it's within the valid range. + */ + errno = 0; + gid = strtoul(group, &endp, 0); + if (errno == 0 && *endp == '\0' && gid <= GID_MAX) + return (gid); + + errx(1, "no such group '%s'", group); +} + +static uid_t +resolve_user(const char *user) +{ + char *endp; + struct passwd *pw; + unsigned long uid; + + pw = getpwnam(user); + if (pw != NULL) + return (pw->pw_uid); + + errno = 0; + uid = strtoul(user, &endp, 0); + if (errno == 0 && *endp == '\0' && uid <= UID_MAX) + return (uid); + + errx(1, "no such user '%s'", user); +} + int main(int argc, char *argv[]) { - struct group *gp; - struct passwd *pw; - char *endp, *p, *user, *group, *grouplist; - const char *shell; + const char *group, *p, *shell, *user; + char *grouplist; + long ngroups_max; gid_t gid, *gidlist; uid_t uid; int arg, ch, error, gids; - long ngroups_max; bool nonprivileged; gid = 0; uid = 0; + gids = 0; user = group = grouplist = NULL; + gidlist = NULL; nonprivileged = false; while ((ch = getopt(argc, argv, "G:g:u:n")) != -1) { switch(ch) { @@ -77,6 +121,11 @@ main(int argc, char *argv[]) break; case 'G': grouplist = optarg; + + /* + * XXX Why not allow us to drop all of our supplementary + * groups? + */ if (*grouplist == '\0') usage(); break; @@ -94,58 +143,27 @@ main(int argc, char *argv[]) if (argc < 1) usage(); - if (group != NULL) { - if (isdigit((unsigned char)*group)) { - gid = (gid_t)strtoul(group, &endp, 0); - if (*endp != '\0') - goto getgroup; - } else { - getgroup: - if ((gp = getgrnam(group)) != NULL) - gid = gp->gr_gid; - else - errx(1, "no such group `%s'", group); - } - } + if (group != NULL) + gid = resolve_group(group); - ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1; - if ((gidlist = malloc(sizeof(gid_t) * ngroups_max)) == NULL) - err(1, "malloc"); - for (gids = 0; - (p = strsep(&grouplist, ",")) != NULL && gids < ngroups_max; ) { - if (*p == '\0') - continue; - - if (isdigit((unsigned char)*p)) { - gidlist[gids] = (gid_t)strtoul(p, &endp, 0); - if (*endp != '\0') - goto getglist; - } else { - getglist: - if ((gp = getgrnam(p)) != NULL) - gidlist[gids] = gp->gr_gid; - else - errx(1, "no such group `%s'", p); - } - gids++; - } - if (p != NULL && gids == ngroups_max) - errx(1, "too many supplementary groups provided"); - - if (user != NULL) { - if (isdigit((unsigned char)*user)) { - uid = (uid_t)strtoul(user, &endp, 0); - if (*endp != '\0') - goto getuser; - } else { - getuser: - if ((pw = getpwnam(user)) != NULL) - uid = pw->pw_uid; - else - errx(1, "no such user `%s'", user); + if (grouplist != NULL) { + ngroups_max = sysconf(_SC_NGROUPS_MAX); + if ((gidlist = malloc(sizeof(gid_t) * ngroups_max)) == NULL) + err(1, "malloc"); + for (gids = 0; (p = strsep(&grouplist, ",")) != NULL && + gids < ngroups_max; ) { + if (*p == '\0') + continue; + + gidlist[gids++] = resolve_group(p); } + if (p != NULL && gids == ngroups_max) + errx(1, "too many supplementary groups provided"); } + if (user != NULL) + uid = resolve_user(user); + if (nonprivileged) { arg = PROC_NO_NEW_PRIVS_ENABLE; error = procctl(P_PID, getpid(), PROC_NO_NEW_PRIVS_CTL, &arg); @@ -153,10 +171,15 @@ main(int argc, char *argv[]) err(1, "procctl"); } - if (chdir(argv[0]) == -1 || chroot(".") == -1) + if (chdir(argv[0]) == -1) err(1, "%s", argv[0]); + if (chroot(".") == -1) { + if (errno == EPERM && !nonprivileged && geteuid() != 0) + errx(1, "unprivileged use requires -n"); + err(1, "%s", argv[0]); + } - if (gids && setgroups(gids, gidlist) == -1) + if (gidlist != NULL && setgroups(gids, gidlist) == -1) err(1, "setgroups"); if (group && setgid(gid) == -1) err(1, "setgid"); diff --git a/usr.sbin/config/Makefile b/usr.sbin/config/Makefile index 93011f404585..6c0320a07cc1 100644 --- a/usr.sbin/config/Makefile +++ b/usr.sbin/config/Makefile @@ -1,5 +1,6 @@ SRCDIR:=${.PARSEDIR:tA} +PACKAGE= toolchain PROG_CXX= config MAN= config.5 config.8 SRCS= config.y main.cc lang.l mkmakefile.cc mkheaders.c \ diff --git a/usr.sbin/config/config.5 b/usr.sbin/config/config.5 index 93d65819d1d8..d7bf96cf6e7f 100644 --- a/usr.sbin/config/config.5 +++ b/usr.sbin/config/config.5 @@ -21,7 +21,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd February 10, 2025 +.Dd June 13, 2025 .Dt CONFIG 5 .Os .Sh NAME @@ -316,11 +316,28 @@ variable is specified, .Ar value is assumed to be the empty string. .Pp +Note that, as the common makefiles overwrite the +.Va CFLAGS +variable after having processed the configuration file, +customizing +.Va CFLAGS +directly via +.Ic makeoptions +is not possible. +Nonetheless, custom compiler flags can be specified using the +.Va CONF_CFLAGS +variable instead. +Its content is appended to +.Va CFLAGS +after the common makefiles have set the latter, allowing to override their +compilation flags. +.Pp Example: .Bd -literal -offset indent -compact makeoptions MYMAKEOPTION="foo" makeoptions MYMAKEOPTION+="bar" makeoptions MYNULLMAKEOPTION +makeoptions CONF_CFLAGS+="-DSOME_CONTROLLING_MACRO" .Ed .\" -------- MAXUSERS -------- .Pp diff --git a/usr.sbin/cron/cron/popen.c b/usr.sbin/cron/cron/popen.c index 72d4afc4d1f3..abd9de198e87 100644 --- a/usr.sbin/cron/cron/popen.c +++ b/usr.sbin/cron/cron/popen.c @@ -1,4 +1,6 @@ /* + * SPDX-License-Identifier: BSD-4.3TAHOE + * * Copyright (c) 1988 The Regents of the University of California. * All rights reserved. * diff --git a/usr.sbin/crunch/Makefile.inc b/usr.sbin/crunch/Makefile.inc index da4210505219..5d050019d637 100644 --- a/usr.sbin/crunch/Makefile.inc +++ b/usr.sbin/crunch/Makefile.inc @@ -1,2 +1,4 @@ +PACKAGE= toolchain + # modify to taste BINDIR?= /usr/bin diff --git a/usr.sbin/crunch/examples/really-big.conf b/usr.sbin/crunch/examples/really-big.conf index 12c08b73936f..9c227a7b8d29 100644 --- a/usr.sbin/crunch/examples/really-big.conf +++ b/usr.sbin/crunch/examples/really-big.conf @@ -52,7 +52,7 @@ 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 whois window write xargs xinstall xstr yacc yes ypcat ypmatch ypwhich -# shell scripts: lorder mkdep shar which +# shell scripts: lorder mkdep which # problems: rdist uses libcompat.a(regex.o), which conflicts with # libedit(readline.o) over regerror(). diff --git a/usr.sbin/ctladm/Makefile b/usr.sbin/ctladm/Makefile index 7d2ec477a4f3..9f9ade18934b 100644 --- a/usr.sbin/ctladm/Makefile +++ b/usr.sbin/ctladm/Makefile @@ -1,6 +1,6 @@ .include <src.opts.mk> -PACKAGE= iscsi +PACKAGE= ctl PROG= ctladm SRCS= ctladm.c util.c ctl_util.c ctl_nvme_all.c ctl_scsi_all.c .PATH: ${SRCTOP}/sys/cam/ctl diff --git a/usr.sbin/ctladm/tests/port.sh b/usr.sbin/ctladm/tests/port.sh index a9ff609d3f4c..d966529a85ae 100644 --- a/usr.sbin/ctladm/tests/port.sh +++ b/usr.sbin/ctladm/tests/port.sh @@ -38,12 +38,6 @@ # PGTAG,TARGET pair must be globally unique. PGTAG=30257 -load_cfiscsi() { - if ! kldstat -q -m cfiscsi; then - kldload cfiscsi || atf_skip "could not load cfscsi kernel mod" - fi -} - skip_if_ctld() { if service ctld onestatus > /dev/null; then # If ctld is running on this server, let's not interfere. @@ -73,6 +67,7 @@ create_ioctl_head() { atf_set "descr" "ctladm can create a new ioctl port" atf_set "require.user" "root" + atf_set "require.progs" ctladm } create_ioctl_body() { @@ -96,6 +91,7 @@ remove_ioctl_without_required_args_head() { atf_set "descr" "ctladm will gracefully fail to remove an ioctl target if required arguments are missing" atf_set "require.user" "root" + atf_set "require.progs" ctladm } remove_ioctl_without_required_args_body() { @@ -115,11 +111,12 @@ create_iscsi_head() { atf_set "descr" "ctladm can create a new iscsi port" atf_set "require.user" "root" + atf_set "require.progs" ctladm + atf_set "require.kmods" "cfiscsi" } create_iscsi_body() { skip_if_ctld - load_cfiscsi 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" @@ -142,11 +139,12 @@ create_iscsi_alias_head() { atf_set "descr" "ctladm can create a new iscsi port with a target alias" atf_set "require.user" "root" + atf_set "require.progs" ctladm + atf_set "require.kmods" "cfiscsi" } create_iscsi_alias_body() { skip_if_ctld - load_cfiscsi TARGET=iqn.2018-10.myhost.create_iscsi_alias ALIAS="foobar" @@ -168,11 +166,12 @@ create_iscsi_without_required_args_head() { atf_set "descr" "ctladm will gracefully fail to create an iSCSI target if required arguments are missing" atf_set "require.user" "root" + atf_set "require.progs" ctladm + atf_set "require.kmods" "cfiscsi" } create_iscsi_without_required_args_body() { skip_if_ctld - load_cfiscsi 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 @@ -184,6 +183,7 @@ create_ioctl_options_head() { atf_set "descr" "ctladm can set options when creating a new ioctl port" atf_set "require.user" "root" + atf_set "require.progs" ctladm } create_ioctl_options_body() { @@ -211,6 +211,7 @@ disable_ioctl_head() { atf_set "descr" "ctladm can disable an ioctl port" atf_set "require.user" "root" + atf_set "require.progs" ctladm } disable_ioctl_body() { @@ -232,6 +233,7 @@ enable_ioctl_head() { atf_set "descr" "ctladm can enable an ioctl port" atf_set "require.user" "root" + atf_set "require.progs" ctladm } enable_ioctl_body() { @@ -254,6 +256,7 @@ remove_ioctl_head() { atf_set "descr" "ctladm can remove an ioctl port" atf_set "require.user" "root" + atf_set "require.progs" ctladm } remove_ioctl_body() { @@ -278,11 +281,12 @@ remove_iscsi_head() { atf_set "descr" "ctladm can remove an iscsi port" atf_set "require.user" "root" + atf_set "require.progs" ctladm + atf_set "require.kmods" "cfiscsi" } remove_iscsi_body() { skip_if_ctld - load_cfiscsi 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" @@ -303,11 +307,12 @@ remove_iscsi_without_required_args_head() { atf_set "descr" "ctladm will gracefully fail to remove an iSCSI target if required arguments are missing" atf_set "require.user" "root" + atf_set "require.progs" ctladm + atf_set "require.kmods" "cfiscsi" } remove_iscsi_without_required_args_body() { skip_if_ctld - load_cfiscsi 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" diff --git a/usr.sbin/ctld/Makefile b/usr.sbin/ctld/Makefile index 79c69c95fbb1..61efe8a05cfb 100644 --- a/usr.sbin/ctld/Makefile +++ b/usr.sbin/ctld/Makefile @@ -3,29 +3,30 @@ CFLAGS+=-I${SRCTOP}/contrib/libucl/include .PATH: ${SRCTOP}/contrib/libucl/include -PACKAGE= iscsi +PACKAGE= ctl PROG_CXX= ctld -SRCS= ctld.cc conf.cc discovery.cc isns.cc kernel.cc -SRCS+= login.cc parse.y token.l y.tab.h uclparse.cc +SRCS= ctld.cc conf.cc discovery.cc iscsi.cc isns.cc kernel.cc +SRCS+= login.cc nvmf.cc nvmf_discovery.cc +SRCS+= parse.y token.l y.tab.h uclparse.cc CFLAGS+= -I${.CURDIR} CFLAGS+= -I${SRCTOP}/sys CFLAGS+= -I${SRCTOP}/sys/cam/ctl CFLAGS+= -I${SRCTOP}/sys/dev/iscsi CFLAGS+= -I${SRCTOP}/lib/libiscsiutil +CFLAGS+= -I${SRCTOP}/lib/libutil++ +CFLAGS+= -I${SRCTOP}/lib/libnvmf #CFLAGS+= -DICL_KERNEL_PROXY NO_WCAST_ALIGN= CXXWARNFLAGS.gcc= -Wno-shadow MAN= ctld.8 ctl.conf.5 -LIBADD= bsdxml iscsiutil md sbuf util ucl m nv +LIBADD= bsdxml iscsiutil nvmf md sbuf util ucl m nv util++ YFLAGS+= -v CLEANFILES= y.tab.c y.tab.h y.output NO_WMISSING_VARIABLE_DECLARATIONS= -.if ${MK_ISCSI} != "no" -CFLAGS+= -DWANT_ISCSI -.endif - .include <bsd.prog.mk> + +CXXWARNFLAGS.uclparse.cc= -Wno-shadow -Wno-cast-qual diff --git a/usr.sbin/ctld/conf.cc b/usr.sbin/ctld/conf.cc index e86b44ee5004..56a149a58a25 100644 --- a/usr.sbin/ctld/conf.cc +++ b/usr.sbin/ctld/conf.cc @@ -39,8 +39,10 @@ #include <string.h> #include <cam/scsi/scsi_all.h> +#include <libutil++.hh> + #include "conf.h" -#include "ctld.h" +#include "ctld.hh" static struct conf *conf = NULL; static struct auth_group *auth_group = NULL; @@ -68,130 +70,96 @@ conf_finish(void) bool isns_add_server(const char *addr) { - return (isns_new(conf, addr)); + return (conf->add_isns(addr)); } void conf_set_debug(int debug) { - conf->conf_debug = debug; + conf->set_debug(debug); } void conf_set_isns_period(int period) { - conf->conf_isns_period = period; + conf->set_isns_period(period); } void conf_set_isns_timeout(int timeout) { - conf->conf_isns_timeout = timeout; + conf->set_isns_timeout(timeout); } void conf_set_maxproc(int maxproc) { - conf->conf_maxproc = maxproc; + conf->set_maxproc(maxproc); } bool conf_set_pidfile_path(const char *path) { - if (conf->conf_pidfile_path != NULL) { - log_warnx("pidfile specified more than once"); - return (false); - } - conf->conf_pidfile_path = checked_strdup(path); - return (true); + return (conf->set_pidfile_path(path)); } void conf_set_timeout(int timeout) { - conf->conf_timeout = timeout; + conf->set_timeout(timeout); } -static bool -_auth_group_set_type(struct auth_group *ag, const char *str) +bool +auth_group_add_chap(const char *user, const char *secret) { - int type; - - if (strcmp(str, "none") == 0) { - type = AG_TYPE_NO_AUTHENTICATION; - } else if (strcmp(str, "deny") == 0) { - type = AG_TYPE_DENY; - } else if (strcmp(str, "chap") == 0) { - type = AG_TYPE_CHAP; - } else if (strcmp(str, "chap-mutual") == 0) { - type = AG_TYPE_CHAP_MUTUAL; - } else { - log_warnx("invalid auth-type \"%s\" for %s", str, ag->ag_label); - return (false); - } - - if (ag->ag_type != AG_TYPE_UNKNOWN && ag->ag_type != type) { - log_warnx("cannot set auth-type to \"%s\" for %s; " - "already has a different type", str, ag->ag_label); - return (false); - } - - ag->ag_type = type; + return (auth_group->add_chap(user, secret)); +} - return (true); +bool +auth_group_add_chap_mutual(const char *user, const char *secret, + const char *user2, const char *secret2) +{ + return (auth_group->add_chap_mutual(user, secret, user2, secret2)); } bool -auth_group_add_chap(const char *user, const char *secret) +auth_group_add_host_address(const char *portal) { - return (auth_new_chap(auth_group, user, secret)); + return (auth_group->add_host_address(portal)); } bool -auth_group_add_chap_mutual(const char *user, const char *secret, - const char *user2, const char *secret2) +auth_group_add_host_nqn(const char *name) { - return (auth_new_chap_mutual(auth_group, user, secret, user2, secret2)); + return (auth_group->add_host_nqn(name)); } bool auth_group_add_initiator_name(const char *name) { - return (auth_name_new(auth_group, name)); + return (auth_group->add_initiator_name(name)); } bool auth_group_add_initiator_portal(const char *portal) { - return (auth_portal_new(auth_group, portal)); + return (auth_group->add_initiator_portal(portal)); } bool auth_group_set_type(const char *type) { - return (_auth_group_set_type(auth_group, type)); + return (auth_group->set_type(type)); } bool auth_group_start(const char *name) { - /* - * Make it possible to redefine the default auth-group. but - * only once. - */ - if (strcmp(name, "default") == 0) { - if (conf->conf_default_ag_defined) { - log_warnx("duplicated auth-group \"default\""); - return (false); - } - - conf->conf_default_ag_defined = true; - auth_group = auth_group_find(conf, "default"); - return (true); - } - - auth_group = auth_group_new(conf, name); - return (auth_group != NULL); + if (strcmp(name, "default") == 0) + auth_group = conf->define_default_auth_group(); + else + auth_group = conf->add_auth_group(name); + return (auth_group != nullptr); } void @@ -203,22 +171,10 @@ auth_group_finish(void) bool portal_group_start(const char *name) { - /* - * Make it possible to redefine the default portal-group. but - * only once. - */ - if (strcmp(name, "default") == 0) { - if (conf->conf_default_pg_defined) { - log_warnx("duplicated portal-group \"default\""); - return (false); - } - - conf->conf_default_pg_defined = true; - portal_group = portal_group_find(conf, "default"); - return (true); - } - - portal_group = portal_group_new(conf, name); + if (strcmp(name, "default") == 0) + portal_group = conf->define_default_portal_group(); + else + portal_group = conf->add_portal_group(name); return (portal_group != NULL); } @@ -231,141 +187,91 @@ portal_group_finish(void) bool portal_group_add_listen(const char *listen, bool iser) { - return (portal_group_add_portal(portal_group, listen, iser)); + return (portal_group->add_portal(listen, iser ? portal_protocol::ISER : + portal_protocol::ISCSI)); } bool portal_group_add_option(const char *name, const char *value) { - return (option_new(portal_group->pg_options, name, value)); + return (portal_group->add_option(name, value)); } bool portal_group_set_discovery_auth_group(const char *name) { - if (portal_group->pg_discovery_auth_group != NULL) { - log_warnx("discovery-auth-group for portal-group " - "\"%s\" specified more than once", - portal_group->pg_name); - return (false); - } - portal_group->pg_discovery_auth_group = auth_group_find(conf, name); - if (portal_group->pg_discovery_auth_group == NULL) { - log_warnx("unknown discovery-auth-group \"%s\" " - "for portal-group \"%s\"", name, portal_group->pg_name); - return (false); - } - return (true); + return (portal_group->set_discovery_auth_group(name)); } bool portal_group_set_dscp(u_int dscp) { - if (dscp >= 0x40) { - log_warnx("invalid DSCP value %u for portal-group \"%s\"", - dscp, portal_group->pg_name); - return (false); - } - - portal_group->pg_dscp = dscp; - return (true); + return (portal_group->set_dscp(dscp)); } bool portal_group_set_filter(const char *str) { - int filter; - - if (strcmp(str, "none") == 0) { - filter = PG_FILTER_NONE; - } else if (strcmp(str, "portal") == 0) { - filter = PG_FILTER_PORTAL; - } else if (strcmp(str, "portal-name") == 0) { - filter = PG_FILTER_PORTAL_NAME; - } else if (strcmp(str, "portal-name-auth") == 0) { - filter = PG_FILTER_PORTAL_NAME_AUTH; - } else { - log_warnx("invalid discovery-filter \"%s\" for portal-group " - "\"%s\"; valid values are \"none\", \"portal\", " - "\"portal-name\", and \"portal-name-auth\"", - str, portal_group->pg_name); - return (false); - } - - if (portal_group->pg_discovery_filter != PG_FILTER_UNKNOWN && - portal_group->pg_discovery_filter != filter) { - log_warnx("cannot set discovery-filter to \"%s\" for " - "portal-group \"%s\"; already has a different " - "value", str, portal_group->pg_name); - return (false); - } - - portal_group->pg_discovery_filter = filter; - - return (true); + return (portal_group->set_filter(str)); } void portal_group_set_foreign(void) { - portal_group->pg_foreign = true; + portal_group->set_foreign(); } bool portal_group_set_offload(const char *offload) { - - if (portal_group->pg_offload != NULL) { - log_warnx("cannot set offload to \"%s\" for " - "portal-group \"%s\"; already defined", - offload, portal_group->pg_name); - return (false); - } - - portal_group->pg_offload = checked_strdup(offload); - - return (true); + return (portal_group->set_offload(offload)); } bool portal_group_set_pcp(u_int pcp) { - if (pcp > 7) { - log_warnx("invalid PCP value %u for portal-group \"%s\"", - pcp, portal_group->pg_name); - return (false); - } - - portal_group->pg_pcp = pcp; - return (true); + return (portal_group->set_pcp(pcp)); } bool portal_group_set_redirection(const char *addr) { + return (portal_group->set_redirection(addr)); +} - if (portal_group->pg_redirection != NULL) { - log_warnx("cannot set redirection to \"%s\" for " - "portal-group \"%s\"; already defined", - addr, portal_group->pg_name); - return (false); - } +void +portal_group_set_tag(uint16_t tag) +{ + portal_group->set_tag(tag); +} - portal_group->pg_redirection = checked_strdup(addr); +bool +transport_group_start(const char *name) +{ + if (strcmp(name, "default") == 0) + portal_group = conf->define_default_transport_group(); + else + portal_group = conf->add_transport_group(name); + return (portal_group != NULL); +} - return (true); +bool +transport_group_add_listen_discovery_tcp(const char *listen) +{ + return portal_group->add_portal(listen, + portal_protocol::NVME_DISCOVERY_TCP); } -void -portal_group_set_tag(uint16_t tag) +bool +transport_group_add_listen_tcp(const char *listen) { - portal_group->pg_tag = tag; + return portal_group->add_portal(listen, portal_protocol::NVME_TCP); } bool lun_start(const char *name) { - lun = lun_new(conf, name); + lun = conf->add_lun(name); return (lun != NULL); } @@ -378,128 +284,61 @@ lun_finish(void) bool lun_add_option(const char *name, const char *value) { - return (option_new(lun->l_options, name, value)); + return (lun->add_option(name, value)); } bool lun_set_backend(const char *value) { - if (lun->l_backend != NULL) { - log_warnx("backend for lun \"%s\" specified more than once", - lun->l_name); - return (false); - } - - lun->l_backend = checked_strdup(value); - return (true); + return (lun->set_backend(value)); } bool lun_set_blocksize(size_t value) { - if (lun->l_blocksize != 0) { - log_warnx("blocksize for lun \"%s\" specified more than once", - lun->l_name); - return (false); - } - lun->l_blocksize = value; - return (true); + return (lun->set_blocksize(value)); } bool lun_set_device_type(const char *value) { - uint64_t device_type; - - if (strcasecmp(value, "disk") == 0 || - strcasecmp(value, "direct") == 0) - device_type = T_DIRECT; - else if (strcasecmp(value, "processor") == 0) - device_type = T_PROCESSOR; - else if (strcasecmp(value, "cd") == 0 || - strcasecmp(value, "cdrom") == 0 || - strcasecmp(value, "dvd") == 0 || - strcasecmp(value, "dvdrom") == 0) - device_type = T_CDROM; - else if (expand_number(value, &device_type) != 0 || device_type > 15) { - log_warnx("invalid device-type \"%s\" for lun \"%s\"", value, - lun->l_name); - return (false); - } - - lun->l_device_type = device_type; - return (true); + return (lun->set_device_type(value)); } bool lun_set_device_id(const char *value) { - if (lun->l_device_id != NULL) { - log_warnx("device_id for lun \"%s\" specified more than once", - lun->l_name); - return (false); - } - - lun->l_device_id = checked_strdup(value); - return (true); + return (lun->set_device_id(value)); } bool lun_set_path(const char *value) { - if (lun->l_path != NULL) { - log_warnx("path for lun \"%s\" specified more than once", - lun->l_name); - return (false); - } - - lun->l_path = checked_strdup(value); - return (true); + return (lun->set_path(value)); } bool lun_set_serial(const char *value) { - if (lun->l_serial != NULL) { - log_warnx("serial for lun \"%s\" specified more than once", - lun->l_name); - return (false); - } - - lun->l_serial = checked_strdup(value); - return (true); + return (lun->set_serial(value)); } bool lun_set_size(uint64_t value) { - if (lun->l_size != 0) { - log_warnx("size for lun \"%s\" specified more than once", - lun->l_name); - return (false); - } - - lun->l_size = value; - return (true); + return (lun->set_size(value)); } bool lun_set_ctl_lun(uint32_t value) { - - if (lun->l_ctl_lun >= 0) { - log_warnx("ctl_lun for lun \"%s\" specified more than once", - lun->l_name); - return (false); - } - lun->l_ctl_lun = value; - return (true); + return (lun->set_ctl_lun(value)); } bool target_start(const char *name) { - target = target_new(conf, name); + target = conf->add_target(name); return (target != NULL); } @@ -512,244 +351,128 @@ target_finish(void) bool target_add_chap(const char *user, const char *secret) { - if (target->t_auth_group != NULL) { - if (target->t_auth_group->ag_name != NULL) { - log_warnx("cannot use both auth-group and " - "chap for target \"%s\"", target->t_name); - return (false); - } - } else { - target->t_auth_group = auth_group_new(conf, target); - if (target->t_auth_group == NULL) - return (false); - } - return (auth_new_chap(target->t_auth_group, user, secret)); + return (target->add_chap(user, secret)); } bool target_add_chap_mutual(const char *user, const char *secret, const char *user2, const char *secret2) { - if (target->t_auth_group != NULL) { - if (target->t_auth_group->ag_name != NULL) { - log_warnx("cannot use both auth-group and " - "chap-mutual for target \"%s\"", target->t_name); - return (false); - } - } else { - target->t_auth_group = auth_group_new(conf, target); - if (target->t_auth_group == NULL) - return (false); - } - return (auth_new_chap_mutual(target->t_auth_group, user, secret, user2, - secret2)); + return (target->add_chap_mutual(user, secret, user2, secret2)); } bool target_add_initiator_name(const char *name) { - if (target->t_auth_group != NULL) { - if (target->t_auth_group->ag_name != NULL) { - log_warnx("cannot use both auth-group and " - "initiator-name for target \"%s\"", target->t_name); - return (false); - } - } else { - target->t_auth_group = auth_group_new(conf, target); - if (target->t_auth_group == NULL) - return (false); - } - return (auth_name_new(target->t_auth_group, name)); + return (target->add_initiator_name(name)); } bool target_add_initiator_portal(const char *addr) { - if (target->t_auth_group != NULL) { - if (target->t_auth_group->ag_name != NULL) { - log_warnx("cannot use both auth-group and " - "initiator-portal for target \"%s\"", - target->t_name); - return (false); - } - } else { - target->t_auth_group = auth_group_new(conf, target); - if (target->t_auth_group == NULL) - return (false); - } - return (auth_portal_new(target->t_auth_group, addr)); + return (target->add_initiator_portal(addr)); } bool target_add_lun(u_int id, const char *name) { - struct lun *t_lun; - - if (id >= MAX_LUNS) { - log_warnx("LUN %u too big for target \"%s\"", id, - target->t_name); - return (false); - } - - if (target->t_luns[id] != NULL) { - log_warnx("duplicate LUN %u for target \"%s\"", id, - target->t_name); - return (false); - } - - t_lun = lun_find(conf, name); - if (t_lun == NULL) { - log_warnx("unknown LUN named %s used for target \"%s\"", - name, target->t_name); - return (false); - } - - target->t_luns[id] = t_lun; - return (true); + return (target->add_lun(id, name)); } bool target_add_portal_group(const char *pg_name, const char *ag_name) { - struct portal_group *pg; - struct auth_group *ag; - struct port *p; - - pg = portal_group_find(conf, pg_name); - if (pg == NULL) { - log_warnx("unknown portal-group \"%s\" for target \"%s\"", - pg_name, target->t_name); - return (false); - } - - if (ag_name != NULL) { - ag = auth_group_find(conf, ag_name); - if (ag == NULL) { - log_warnx("unknown auth-group \"%s\" for target \"%s\"", - ag_name, target->t_name); - return (false); - } - } else - ag = NULL; - - p = port_new(conf, target, pg); - if (p == NULL) { - log_warnx("can't link portal-group \"%s\" to target \"%s\"", - pg_name, target->t_name); - return (false); - } - p->p_auth_group = ag; - return (true); + return (target->add_portal_group(pg_name, ag_name)); } bool target_set_alias(const char *alias) { - if (target->t_alias != NULL) { - log_warnx("alias for target \"%s\" specified more than once", - target->t_name); - return (false); - } - target->t_alias = checked_strdup(alias); - return (true); + return (target->set_alias(alias)); } bool target_set_auth_group(const char *name) { - if (target->t_auth_group != NULL) { - if (target->t_auth_group->ag_name != NULL) - log_warnx("auth-group for target \"%s\" " - "specified more than once", target->t_name); - else - log_warnx("cannot use both auth-group and explicit " - "authorisations for target \"%s\"", target->t_name); - return (false); - } - target->t_auth_group = auth_group_find(conf, name); - if (target->t_auth_group == NULL) { - log_warnx("unknown auth-group \"%s\" for target \"%s\"", name, - target->t_name); - return (false); - } - return (true); + return (target->set_auth_group(name)); } bool target_set_auth_type(const char *type) { - if (target->t_auth_group != NULL) { - if (target->t_auth_group->ag_name != NULL) { - log_warnx("cannot use both auth-group and " - "auth-type for target \"%s\"", target->t_name); - return (false); - } - } else { - target->t_auth_group = auth_group_new(conf, target); - if (target->t_auth_group == NULL) - return (false); - } - return (_auth_group_set_type(target->t_auth_group, type)); + return (target->set_auth_type(type)); } bool target_set_physical_port(const char *pport) { - if (target->t_pport != NULL) { - log_warnx("cannot set multiple physical ports for target " - "\"%s\"", target->t_name); - return (false); - } - target->t_pport = checked_strdup(pport); - return (true); + return (target->set_physical_port(pport)); } bool target_set_redirection(const char *addr) { + return (target->set_redirection(addr)); +} - if (target->t_redirection != NULL) { - log_warnx("cannot set redirection to \"%s\" for " - "target \"%s\"; already defined", - addr, target->t_name); - return (false); - } +bool +target_start_lun(u_int id) +{ + lun = target->start_lun(id); + return (lun != nullptr); +} + +bool +controller_start(const char *name) +{ + target = conf->add_controller(name); + return (target != nullptr); +} + +bool +controller_add_host_address(const char *addr) +{ + return (target->add_host_address(addr)); +} - target->t_redirection = checked_strdup(addr); +bool +controller_add_host_nqn(const char *name) +{ + return (target->add_host_nqn(name)); +} - return (true); +bool +controller_add_namespace(u_int id, const char *name) +{ + return (target->add_namespace(id, name)); } bool -target_start_lun(u_int id) +controller_start_namespace(u_int id) { - struct lun *new_lun; - char *name; + lun = target->start_namespace(id); + return (lun != nullptr); +} - if (id >= MAX_LUNS) { - log_warnx("LUN %u too big for target \"%s\"", id, - target->t_name); +bool +parse_conf(const char *path) +{ + freebsd::FILE_up fp(fopen(path, "r")); + if (fp == nullptr) { + log_warn("unable to open configuration file %s", path); return (false); } - if (target->t_luns[id] != NULL) { - log_warnx("duplicate LUN %u for target \"%s\"", id, - target->t_name); + bool parsed; + try { + parsed = yyparse_conf(fp.get()); + } catch (std::bad_alloc &) { + log_warnx("failed to allocate memory parsing %s", path); return (false); - } - - if (asprintf(&name, "%s,lun,%u", target->t_name, id) <= 0) - log_err(1, "asprintf"); - - new_lun = lun_new(conf, name); - if (new_lun == NULL) + } catch (...) { + log_warnx("unknown exception parsing %s", path); return (false); + } - lun_set_scsiname(new_lun, name); - free(name); - - target->t_luns[id] = new_lun; - - lun = new_lun; - return (true); + return (parsed); } diff --git a/usr.sbin/ctld/conf.h b/usr.sbin/ctld/conf.h index 6e287ce70436..642c8f234d30 100644 --- a/usr.sbin/ctld/conf.h +++ b/usr.sbin/ctld/conf.h @@ -43,6 +43,8 @@ void auth_group_finish(void); bool auth_group_add_chap(const char *user, const char *secret); bool auth_group_add_chap_mutual(const char *user, const char *secret, const char *user2, const char *secret2); +bool auth_group_add_host_address(const char *portal); +bool auth_group_add_host_nqn(const char *name); bool auth_group_add_initiator_name(const char *name); bool auth_group_add_initiator_portal(const char *portal); bool auth_group_set_type(const char *type); @@ -69,6 +71,10 @@ bool portal_group_set_pcp(u_int pcp); bool portal_group_set_redirection(const char *addr); void portal_group_set_tag(uint16_t tag); +bool transport_group_start(const char *name); +bool transport_group_add_listen_discovery_tcp(const char *listen); +bool transport_group_add_listen_tcp(const char *listen); + bool target_start(const char *name); void target_finish(void); bool target_add_chap(const char *user, const char *secret); @@ -85,6 +91,12 @@ bool target_set_physical_port(const char *pport); bool target_set_redirection(const char *addr); bool target_start_lun(u_int id); +bool controller_start(const char *name); +bool controller_add_host_address(const char *addr); +bool controller_add_host_nqn(const char *name); +bool controller_add_namespace(u_int id, const char *name); +bool controller_start_namespace(u_int id); + bool lun_start(const char *name); void lun_finish(void); bool lun_add_option(const char *name, const char *value); @@ -97,7 +109,7 @@ bool lun_set_path(const char *value); bool lun_set_serial(const char *value); bool lun_set_size(uint64_t value); -bool parse_conf(const char *path); +bool yyparse_conf(FILE *fp); __END_DECLS diff --git a/usr.sbin/ctld/ctl.conf.5 b/usr.sbin/ctld/ctl.conf.5 index e42dd8067006..12f4186a6844 100644 --- a/usr.sbin/ctld/ctl.conf.5 +++ b/usr.sbin/ctld/ctl.conf.5 @@ -26,12 +26,12 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd February 26, 2025 +.Dd August 6, 2025 .Dt CTL.CONF 5 .Os .Sh NAME .Nm ctl.conf -.Nd CAM Target Layer / iSCSI target daemon configuration file +.Nd CAM Target Layer / iSCSI target / NVMeoF controller daemon configuration file .Sh DESCRIPTION The .Nm @@ -59,6 +59,11 @@ file is: .Dl ... } +.No transport-group Ar name No { +.Dl listen Ar transport Ar address +.Dl ... +} + .No target Ar name { .Dl auth-group Ar name .Dl portal-group Ar name @@ -67,6 +72,15 @@ file is: .Dl } .Dl ... } + +.No controller Ar name { +.Dl auth-group Ar name +.Dl transport-group Ar name +.Dl namespace Ar number No { +.Dl path Ar path +.Dl } +.Dl ... +} .Ed .Ss Global Context .Bl -tag -width indent @@ -94,16 +108,29 @@ Create a configuration context, defining a new portal-group, which can then be assigned to any number of targets. +.It Ic transport-group Ar name +Create a +.Sy transport-group +configuration context, +defining a new transport-group, +which can then be assigned to any number of NVMeoF controllers. .It Ic lun Ar name Create a .Sy lun -configuration context, defining a LUN to be exported by any number of targets. +configuration context, defining a LUN to be exported by any number of targets +or controllers. .It Ic target Ar name Create a .Sy target configuration context, which can optionally contain one or more .Sy lun contexts. +.It Ic controller Ar name +Create a +.Sy controller +configuration context, which can optionally contain one or more +.Sy namespace +contexts. .It Ic timeout Ar seconds The timeout for login sessions, after which the connection will be forcibly terminated. @@ -150,6 +177,19 @@ the configuration may only contain either or .Sy chap-mutual entries; it is an error to mix them. +.It Ic host-address Ar address Ns Op / Ns Ar prefixlen +An NVMeoF host address: an IPv4 or IPv6 address, optionally +followed by a literal slash and a prefix length. +Only NVMeoF hosts with an address matching one of the defined +addresses will be allowed to connect. +If not defined, there will be no restrictions based on host +address. +.It Ic host-nqn Ar name +An NVMeoF host name. +Only NVMeoF hosts with a name matching one of the defined +names will be allowed to connect. +If not defined, there will be no restrictions based on NVMe host +name. .It Ic initiator-name Ar initiator-name An iSCSI initiator name. Only initiators with a name matching one of the defined @@ -264,6 +304,75 @@ to .Qq Ar 7 . When omitted, the default for the outgoing interface is used. .El +.Ss transport-group Context +.Bl -tag -width indent +.It Ic discovery-auth-group Ar name +See the description for this option for +.Sy portal-group +contexts. +.It Ic discovery-filter Ar filter +Filter can be either +.Qq Ar none , +.Qq Ar address , +or +.Qq Ar address-name . +When set to +.Qq Ar none , +discovery will return all controllers assigned to that transport group. +When set to +.Qq Ar address , +discovery will not return controllers that cannot be accessed by the +host because of their +.Sy host-address . +When set to +.Qq Ar address-name , +the check will include both +.Sy host-address +and +.Sy host-nqn . +The default is +.Qq Ar none . +.It Ic listen Ar transport Ar address +An IPv4 or IPv6 address and port to listen on for incoming connections +using the specified NVMeoF transport. +Supported transports are +.Qq Ar tcp +.Pq for NVMe/TCP I/O controllers +and +.Qq Ar discovery-tcp +.Pq for NVMe/TCP discovery controllers . +.It Ic option Ar name Ar value +One of the following options: +.Bl -column "max_admin_qsize" "Default" "Transports" +.It Sy Name Ta Sy Default Ta Sy Transports Ta Sy Description +.It MAXH2CDATA Ta 256KiB Ta TCP Ta +Size in bytes of the maximum data payload size for data PDUs accepted from +remote hosts. +The value must be at least 4KiB and must be a multiple of 4. +.It SQFC Ta false Ta any Ta +Always enable SQ flow control. +.It HDGST Ta false Ta TCP Ta +Enable PDU header digests if requested by a remote host. +.It DDGST Ta false Ta TCP Ta +Enable PDU data digests if requested by a remote host. +.It max_admin_qsize Ta 4096 Ta any Ta +The maximum number of entries a remote host can request for an admin queue pair. +.It max_io_qsize Ta 65536 Ta any Ta +The maximum number of entries a remote host can request for an I/O queue pair. +.El +.It Ic tag Ar value +Unique 16-bit port ID for this +.Sy transport-group . +If not specified, the value is generated automatically. +.It Ic dscp Ar value +See the description for this option for +.Sy portal-group +contexts. +.It Ic pcp Ar value +See the description for this option for +.Sy portal-group +contexts. +.El .Ss target Context .Bl -tag -width indent .It Ic alias Ar text @@ -390,6 +499,101 @@ configuration context, defining a LUN exported by the parent target. This is an alternative to defining the LUN separately, useful in the common case of a LUN being exported by a single target. .El +.Ss controller Context +.Bl -tag -width indent +.It Ic auth-group Ar name +Assign a previously defined authentication group to the controller. +By default, controllers that do not specify their own auth settings, +using clauses such as +.Sy host-address +or +.Sy host-nqn , +are assigned to the +predefined +.Sy auth-group +.Qq Ar default , +which denies all access. +Another predefined +.Sy auth-group , +.Qq Ar no-authentication , +may be used to permit access +without authentication. +Note that this clause can be overridden using the second argument +to a +.Sy transport-group +clause. +.It Ic auth-type Ar type +Sets the authentication type. +Type can be either +.Qq Ar none +or +.Qq Ar deny . +In most cases it is not necessary to set the type using this clause; +it is usually used to disable authentication for a given +.Sy controller . +This clause is mutually exclusive with +.Sy auth-group ; +one cannot use +both in a single controller. +.It Ic host-address Ar address Ns Op / Ns Ar prefixlen +An NVMeoF host address: an IPv4 or IPv6 address, optionally +followed by a literal slash and a prefix length. +Only NVMeoF hosts with an address matching one of the defined +addresses will be allowed to connect. +If not defined, there will be no restrictions based on host +address. +This clause is mutually exclusive with +.Sy auth-group ; +one cannot use +both in a single controller. +.It Ic host-nqn Ar name +An NVMeoF host name. +Only NVMeoF hosts with a name matching one of the defined +names will be allowed to connect. +If not defined, there will be no restrictions based on NVMe host +name. +This clause is mutually exclusive with +.Sy auth-group ; +one cannot use +both in a single target. +.Pp +The +.Sy auth-type , +.Sy host-address , +and +.Sy host-nqn +clauses in the controller context provide an alternative to assigning an +.Sy auth-group +defined separately, useful in the common case of authentication settings +specific to a single controller. +.It Ic transport-group Ar name Op Ar ag-name +Assign a previously defined transport group to the controller. +The default transport group is +.Qq Ar default , +which makes the controller available +on TCP port 4420 on all configured IPv4 and IPv6 addresses. +The optional second argument specifies the +.Sy auth-group +for connections to this specific transport group group. +If the second argument is not specified, the controller +.Sy auth-group +is used. +.It Ic namespace Ar number Ar name +Export previously defined +.Sy lun +as an NVMe namespace from the parent controller. +.It Ic namespace Ar number +Create a +.Sy namespace +configuration context, defining an NVMe namespace exported by the parent target. +.Pp +This is an alternative to defining the namespace separately, +useful in the common case of a namespace being exported by a single controller. +.Sy namespace +configuration contexts accept the the same properties as +.Sy lun +contexts. +.El .Ss lun Context .Bl -tag -width indent .It Ic backend Ar block No | Ar ramdisk @@ -410,7 +614,7 @@ Global numeric identifier to use for a given LUN inside CTL. By default CTL allocates those IDs dynamically, but explicit specification may be needed for consistency in HA configurations. .It Ic device-id Ar string -The SCSI Device Identification string presented to the initiator. +The SCSI Device Identification string presented to iSCSI initiators. .It Ic device-type Ar type Specify the SCSI device type to use when creating the LUN. Currently CTL supports Direct Access (type 0), Processor (type 3) @@ -425,11 +629,11 @@ section of The path to the file, device node, or .Xr zfs 8 volume used to back the LUN. -For optimal performance, create the volume with the +For optimal performance, create ZFS volumes with the .Qq Ar volmode=dev property set. .It Ic serial Ar string -The SCSI serial number presented to the initiator. +The SCSI serial number presented to iSCSI initiators. .It Ic size Ar size The LUN size, in bytes or by number with a suffix of .Sy K , M , G , T @@ -498,6 +702,16 @@ target naa.50015178f369f092 { port isp1 lun 0 example_1 } + +controller nqn.2012-06.com.example:controller1 { + auth-group no-authentication; + namespace 1 example_1 + namespace 2 { + backend ramdisk + size 1G + option capacity 1G + } +} .Ed .Pp An equivalent configuration in UCL format, for use with @@ -585,6 +799,22 @@ target { } } } + +controller { + "nqn.2012-06.com.example:controller1" { + auth-group = no-authentication + namespace = { + 1 = example_1, + 2 { + backend = ramdisk + size = 1G + options { + capacity = 1G + } + } + } + } +} .Ed .Sh SEE ALSO .Xr ctl 4 , diff --git a/usr.sbin/ctld/ctld.cc b/usr.sbin/ctld/ctld.cc index ff23db6c5293..10c12f25068e 100644 --- a/usr.sbin/ctld/ctld.cc +++ b/usr.sbin/ctld/ctld.cc @@ -41,6 +41,7 @@ #include <assert.h> #include <ctype.h> #include <errno.h> +#include <libnvmf.h> #include <netdb.h> #include <signal.h> #include <stdbool.h> @@ -51,16 +52,12 @@ #include <unistd.h> #include <cam/scsi/scsi_all.h> -#include "conf.h" -#include "ctld.h" -#include "isns.h" +#include <algorithm> +#include <libutil++.hh> -static bool timed_out(void); -#ifdef ICL_KERNEL_PROXY -static void pdu_receive_proxy(struct pdu *pdu); -static void pdu_send_proxy(struct pdu *pdu); -#endif /* ICL_KERNEL_PROXY */ -static void pdu_fail(const struct connection *conn, const char *reason); +#include "conf.h" +#include "ctld.hh" +#include "isns.hh" bool proxy_mode = false; @@ -70,19 +67,8 @@ static volatile bool sigalrm_received = false; static int kqfd; static int nchildren = 0; -static uint16_t last_portal_group_tag = 0xff; -static struct connection_ops conn_ops = { - .timed_out = timed_out, -#ifdef ICL_KERNEL_PROXY - .pdu_receive_proxy = pdu_receive_proxy, - .pdu_send_proxy = pdu_send_proxy, -#else - .pdu_receive_proxy = nullptr, - .pdu_send_proxy = nullptr, -#endif - .fail = pdu_fail, -}; +uint32_t conf::global_genctr; static void usage(void) @@ -93,559 +79,563 @@ usage(void) exit(1); } -struct conf * -conf_new(void) +conf::conf() { - struct conf *conf; + conf_genctr = global_genctr++; +} - conf = reinterpret_cast<struct conf *>(calloc(1, sizeof(*conf))); - if (conf == NULL) - log_err(1, "calloc"); - TAILQ_INIT(&conf->conf_luns); - TAILQ_INIT(&conf->conf_targets); - TAILQ_INIT(&conf->conf_auth_groups); - TAILQ_INIT(&conf->conf_ports); - TAILQ_INIT(&conf->conf_portal_groups); - TAILQ_INIT(&conf->conf_isns); - - conf->conf_isns_period = 900; - conf->conf_isns_timeout = 5; - conf->conf_debug = 0; - conf->conf_timeout = 60; - conf->conf_maxproc = 30; +void +conf::set_debug(int debug) +{ + conf_debug = debug; +} - return (conf); +void +conf::set_isns_period(int period) +{ + conf_isns_period = period; +} + +void +conf::set_isns_timeout(int timeout) +{ + conf_isns_timeout = timeout; } void -conf_delete(struct conf *conf) +conf::set_maxproc(int maxproc) { - struct lun *lun, *ltmp; - struct target *targ, *tmp; - struct auth_group *ag, *cagtmp; - struct portal_group *pg, *cpgtmp; - struct isns *is, *istmp; + conf_maxproc = maxproc; +} - assert(conf->conf_pidfh == NULL); +void +conf::set_timeout(int timeout) +{ + conf_timeout = timeout; +} - TAILQ_FOREACH_SAFE(lun, &conf->conf_luns, l_next, ltmp) - lun_delete(lun); - TAILQ_FOREACH_SAFE(targ, &conf->conf_targets, t_next, tmp) - target_delete(targ); - TAILQ_FOREACH_SAFE(ag, &conf->conf_auth_groups, ag_next, cagtmp) - auth_group_delete(ag); - TAILQ_FOREACH_SAFE(pg, &conf->conf_portal_groups, pg_next, cpgtmp) - portal_group_delete(pg); - TAILQ_FOREACH_SAFE(is, &conf->conf_isns, i_next, istmp) - isns_delete(is); - assert(TAILQ_EMPTY(&conf->conf_ports)); - free(conf->conf_pidfile_path); - free(conf); +bool +conf::set_pidfile_path(std::string_view path) +{ + if (!conf_pidfile_path.empty()) { + log_warnx("pidfile specified more than once"); + return (false); + } + conf_pidfile_path = path; + return (true); } -static struct auth * -auth_new(struct auth_group *ag) +void +conf::open_pidfile() { - struct auth *auth; + const char *path; + pid_t otherpid; - auth = reinterpret_cast<struct auth *>(calloc(1, sizeof(*auth))); - if (auth == NULL) - log_err(1, "calloc"); - auth->a_auth_group = ag; - TAILQ_INSERT_TAIL(&ag->ag_auths, auth, a_next); - return (auth); + 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) { + if (errno == EEXIST) + log_errx(1, "daemon already running, pid: %jd.", + (intmax_t)otherpid); + log_err(1, "cannot open or create pidfile \"%s\"", path); + } } -static void -auth_delete(struct auth *auth) +void +conf::write_pidfile() +{ + conf_pidfile.write(); +} + +void +conf::close_pidfile() { - TAILQ_REMOVE(&auth->a_auth_group->ag_auths, auth, a_next); + conf_pidfile.close(); +} - free(auth->a_user); - free(auth->a_secret); - free(auth->a_mutual_user); - free(auth->a_mutual_secret); - free(auth); +#ifdef ICL_KERNEL_PROXY +int +conf::add_proxy_portal(portal *portal) +{ + conf_proxy_portals.push_back(portal); + return (conf_proxy_portals.size() - 1); } -const struct auth * -auth_find(const struct auth_group *ag, const char *user) +portal * +conf::proxy_portal(int id) { - const struct auth *auth; + if (id >= conf_proxy_portals.size()) + return (nullptr); + return (conf_proxy_portals[id]); +} +#endif - TAILQ_FOREACH(auth, &ag->ag_auths, a_next) { - if (strcmp(auth->a_user, user) == 0) - return (auth); +bool +auth_group::set_type(const char *str) +{ + auth_type type; + + if (strcmp(str, "none") == 0) { + type = auth_type::NO_AUTHENTICATION; + } else if (strcmp(str, "deny") == 0) { + type = auth_type::DENY; + } else if (strcmp(str, "chap") == 0) { + type = auth_type::CHAP; + } else if (strcmp(str, "chap-mutual") == 0) { + type = auth_type::CHAP_MUTUAL; + } else { + log_warnx("invalid auth-type \"%s\" for %s", str, label()); + return (false); } - return (NULL); + if (ag_type != auth_type::UNKNOWN && ag_type != type) { + log_warnx("cannot set auth-type to \"%s\" for %s; " + "already has a different type", str, label()); + return (false); + } + + ag_type = type; + + return (true); } -static void -auth_check_secret_length(const struct auth_group *ag, const char *user, - const char *secret, const char *secret_type) +void +auth_group::set_type(auth_type type) +{ + assert(ag_type == auth_type::UNKNOWN); + + ag_type = type; +} + +const struct auth * +auth_group::find_auth(std::string_view user) const +{ + auto it = ag_auths.find(std::string(user)); + if (it == ag_auths.end()) + return (nullptr); + + return (&it->second); +} + +void +auth_group::check_secret_length(const char *user, const char *secret, + const char *secret_type) { size_t len; len = strlen(secret); + assert(len != 0); if (len > 16) { log_warnx("%s for user \"%s\", %s, is too long; it should be " - "at most 16 characters long", secret_type, user, - ag->ag_label); + "at most 16 characters long", secret_type, user, label()); } if (len < 12) { log_warnx("%s for user \"%s\", %s, is too short; it should be " - "at least 12 characters long", secret_type, user, - ag->ag_label); + "at least 12 characters long", secret_type, user, label()); } } bool -auth_new_chap(struct auth_group *ag, const char *user, - const char *secret) +auth_group::add_chap(const char *user, const char *secret) { - struct auth *auth; - - if (ag->ag_type == AG_TYPE_UNKNOWN) - ag->ag_type = AG_TYPE_CHAP; - if (ag->ag_type != AG_TYPE_CHAP) { + if (ag_type == auth_type::UNKNOWN) + ag_type = auth_type::CHAP; + if (ag_type != auth_type::CHAP) { log_warnx("cannot mix \"chap\" authentication with " - "other types for %s", ag->ag_label); + "other types for %s", label()); return (false); } - auth_check_secret_length(ag, user, secret, "secret"); + check_secret_length(user, secret, "secret"); - auth = auth_new(ag); - auth->a_user = checked_strdup(user); - auth->a_secret = checked_strdup(secret); + const auto &pair = ag_auths.try_emplace(user, secret); + if (!pair.second) { + log_warnx("duplicate credentials for user \"%s\" for %s", + user, label()); + return (false); + } return (true); } bool -auth_new_chap_mutual(struct auth_group *ag, const char *user, - const char *secret, const char *user2, const char *secret2) +auth_group::add_chap_mutual(const char *user, const char *secret, + const char *user2, const char *secret2) { - struct auth *auth; - - if (ag->ag_type == AG_TYPE_UNKNOWN) - ag->ag_type = AG_TYPE_CHAP_MUTUAL; - if (ag->ag_type != AG_TYPE_CHAP_MUTUAL) { + if (ag_type == auth_type::UNKNOWN) + ag_type = auth_type::CHAP_MUTUAL; + if (ag_type != auth_type::CHAP_MUTUAL) { log_warnx("cannot mix \"chap-mutual\" authentication " - "with other types for %s", ag->ag_label); + "with other types for %s", label()); return (false); } - auth_check_secret_length(ag, user, secret, "secret"); - auth_check_secret_length(ag, user, secret2, "mutual secret"); + check_secret_length(user, secret, "secret"); + check_secret_length(user, secret2, "mutual secret"); - auth = auth_new(ag); - auth->a_user = checked_strdup(user); - auth->a_secret = checked_strdup(secret); - auth->a_mutual_user = checked_strdup(user2); - auth->a_mutual_secret = checked_strdup(secret2); + const auto &pair = ag_auths.try_emplace(user, secret, user2, secret2); + if (!pair.second) { + log_warnx("duplicate credentials for user \"%s\" for %s", + user, label()); + return (false); + } return (true); } bool -auth_name_new(struct auth_group *ag, const char *name) +auth_group::add_host_nqn(std::string_view nqn) { - struct auth_name *an; - - an = reinterpret_cast<struct auth_name *>(calloc(1, sizeof(*an))); - if (an == NULL) - log_err(1, "calloc"); - an->an_auth_group = ag; - an->an_initiator_name = checked_strdup(name); - TAILQ_INSERT_TAIL(&ag->ag_names, an, an_next); + /* Silently ignore duplicates. */ + ag_host_names.emplace(nqn); return (true); } -static void -auth_name_delete(struct auth_name *an) +bool +auth_group::host_permitted(std::string_view nqn) const { - TAILQ_REMOVE(&an->an_auth_group->ag_names, an, an_next); + if (ag_host_names.empty()) + return (true); - free(an->an_initiator_name); - free(an); + return (ag_host_names.count(std::string(nqn)) != 0); } bool -auth_name_defined(const struct auth_group *ag) +auth_group::add_initiator_name(std::string_view name) { - if (TAILQ_EMPTY(&ag->ag_names)) - return (false); + /* Silently ignore duplicates. */ + ag_initiator_names.emplace(name); return (true); } -const struct auth_name * -auth_name_find(const struct auth_group *ag, const char *name) +bool +auth_group::initiator_permitted(std::string_view initiator_name) const { - const struct auth_name *auth_name; - - TAILQ_FOREACH(auth_name, &ag->ag_names, an_next) { - if (strcmp(auth_name->an_initiator_name, name) == 0) - return (auth_name); - } + if (ag_initiator_names.empty()) + return (true); - return (NULL); + return (ag_initiator_names.count(std::string(initiator_name)) != 0); } bool -auth_name_check(const struct auth_group *ag, const char *initiator_name) +auth_portal::parse(const char *portal) { - if (!auth_name_defined(ag)) - return (true); + std::string net(portal); + std::string mask; - if (auth_name_find(ag, initiator_name) == NULL) - return (false); + /* Split into 'net' (address) and 'mask'. */ + size_t pos = net.find('/'); + if (pos != net.npos) { + mask = net.substr(pos + 1); + if (mask.empty()) + return false; + net.resize(pos); + } + if (net.empty()) + return false; - return (true); -} + /* + * If 'net' starts with a '[', ensure it ends with a ']' and + * force interpreting the address as IPv6. + */ + bool brackets = net[0] == '['; + if (brackets) { + net.erase(0, 1); -bool -auth_portal_new(struct auth_group *ag, const char *portal) -{ - struct auth_portal *ap; - char *net, *mask, *str, *tmp; - int len, dm, m; - - ap = reinterpret_cast<struct auth_portal *>(calloc(1, sizeof(*ap))); - if (ap == NULL) - log_err(1, "calloc"); - ap->ap_auth_group = ag; - ap->ap_initiator_portal = checked_strdup(portal); - mask = str = checked_strdup(portal); - net = strsep(&mask, "/"); - if (net[0] == '[') { - net++; - len = strlen(net); + size_t len = net.length(); if (len < 2) - goto error; + return false; if (net[len - 1] != ']') - goto error; - net[len - 1] = 0; - } else if (net[0] == '\0') - goto error; - if (str[0] == '[' || strchr(net, ':') != NULL) { + return false; + net.resize(len - 1); + } + + /* Parse address from 'net' and set default mask. */ + if (brackets || net.find(':') != net.npos) { struct sockaddr_in6 *sin6 = - (struct sockaddr_in6 *)&ap->ap_sa; + (struct sockaddr_in6 *)&ap_sa; sin6->sin6_len = sizeof(*sin6); sin6->sin6_family = AF_INET6; - if (inet_pton(AF_INET6, net, &sin6->sin6_addr) <= 0) - goto error; - dm = 128; + if (inet_pton(AF_INET6, net.c_str(), &sin6->sin6_addr) <= 0) + return false; + ap_mask = sizeof(sin6->sin6_addr) * 8; } else { struct sockaddr_in *sin = - (struct sockaddr_in *)&ap->ap_sa; + (struct sockaddr_in *)&ap_sa; sin->sin_len = sizeof(*sin); sin->sin_family = AF_INET; - if (inet_pton(AF_INET, net, &sin->sin_addr) <= 0) - goto error; - dm = 32; - } - if (mask != NULL) { - if (mask[0] == '\0') - goto error; - m = strtol(mask, &tmp, 0); - if (m < 0 || m > dm || tmp[0] != 0) - goto error; - } else - m = dm; - ap->ap_mask = m; - free(str); - TAILQ_INSERT_TAIL(&ag->ag_portals, ap, ap_next); - return (true); + if (inet_pton(AF_INET, net.c_str(), &sin->sin_addr) <= 0) + return false; + ap_mask = sizeof(sin->sin_addr) * 8; + } -error: - free(str); - free(ap); - log_warnx("incorrect initiator portal \"%s\"", portal); - return (false); + /* Parse explicit mask if present. */ + if (!mask.empty()) { + char *tmp; + long m = strtol(mask.c_str(), &tmp, 0); + if (m < 0 || m > ap_mask || tmp[0] != 0) + return false; + ap_mask = m; + } + + return true; } -static void -auth_portal_delete(struct auth_portal *ap) +bool +auth_group::add_host_address(const char *address) { - TAILQ_REMOVE(&ap->ap_auth_group->ag_portals, ap, ap_next); + auth_portal ap; + if (!ap.parse(address)) { + log_warnx("invalid controller address \"%s\" for %s", address, + label()); + return (false); + } - free(ap->ap_initiator_portal); - free(ap); + ag_host_addresses.emplace_back(ap); + return (true); } bool -auth_portal_defined(const struct auth_group *ag) +auth_group::add_initiator_portal(const char *portal) { - if (TAILQ_EMPTY(&ag->ag_portals)) + auth_portal ap; + if (!ap.parse(portal)) { + log_warnx("invalid initiator portal \"%s\" for %s", portal, + label()); return (false); + } + + ag_initiator_portals.emplace_back(ap); return (true); } -const struct auth_portal * -auth_portal_find(const struct auth_group *ag, const struct sockaddr_storage *ss) +bool +auth_portal::matches(const struct sockaddr *sa) const { - const struct auth_portal *ap; const uint8_t *a, *b; int i; - uint8_t bmask; - TAILQ_FOREACH(ap, &ag->ag_portals, ap_next) { - if (ap->ap_sa.ss_family != ss->ss_family) - continue; - if (ss->ss_family == AF_INET) { - a = (const uint8_t *) - &((const struct sockaddr_in *)ss)->sin_addr; - b = (const uint8_t *) - &((const struct sockaddr_in *)&ap->ap_sa)->sin_addr; - } else { - a = (const uint8_t *) - &((const struct sockaddr_in6 *)ss)->sin6_addr; - b = (const uint8_t *) - &((const struct sockaddr_in6 *)&ap->ap_sa)->sin6_addr; - } - for (i = 0; i < ap->ap_mask / 8; i++) { - if (a[i] != b[i]) - goto next; - } - if (ap->ap_mask % 8) { - bmask = 0xff << (8 - (ap->ap_mask % 8)); - if ((a[i] & bmask) != (b[i] & bmask)) - goto next; - } - return (ap); -next: - ; - } + if (ap_sa.ss_family != sa->sa_family) + return (false); - return (NULL); + if (sa->sa_family == AF_INET) { + a = (const uint8_t *) + &((const struct sockaddr_in *)sa)->sin_addr; + b = (const uint8_t *) + &((const struct sockaddr_in *)&ap_sa)->sin_addr; + } else { + a = (const uint8_t *) + &((const struct sockaddr_in6 *)sa)->sin6_addr; + b = (const uint8_t *) + &((const struct sockaddr_in6 *)&ap_sa)->sin6_addr; + } + for (i = 0; i < ap_mask / 8; i++) { + if (a[i] != b[i]) + return (false); + } + if ((ap_mask % 8) != 0) { + uint8_t bmask = 0xff << (8 - (ap_mask % 8)); + if ((a[i] & bmask) != (b[i] & bmask)) + return (false); + } + return (true); } bool -auth_portal_check(const struct auth_group *ag, const struct sockaddr_storage *sa) +auth_group::host_permitted(const struct sockaddr *sa) const { - - if (!auth_portal_defined(ag)) + if (ag_host_addresses.empty()) return (true); - if (auth_portal_find(ag, sa) == NULL) - return (false); - - return (true); + for (const auth_portal &ap : ag_host_addresses) + if (ap.matches(sa)) + return (true); + return (false); } -static struct auth_group * -auth_group_create(struct conf *conf, const char *name, char *label) +bool +auth_group::initiator_permitted(const struct sockaddr *sa) const { - struct auth_group *ag; - - ag = reinterpret_cast<struct auth_group *>(calloc(1, sizeof(*ag))); - if (ag == NULL) - log_err(1, "calloc"); - if (name != NULL) - ag->ag_name = checked_strdup(name); - ag->ag_label = label; - TAILQ_INIT(&ag->ag_auths); - TAILQ_INIT(&ag->ag_names); - TAILQ_INIT(&ag->ag_portals); - ag->ag_conf = conf; - TAILQ_INSERT_TAIL(&conf->conf_auth_groups, ag, ag_next); + if (ag_initiator_portals.empty()) + return (true); - return (ag); + for (const auth_portal &ap : ag_initiator_portals) + if (ap.matches(sa)) + return (true); + return (false); } struct auth_group * -auth_group_new(struct conf *conf, const char *name) +conf::add_auth_group(const char *name) { - struct auth_group *ag; - char *label; - - ag = auth_group_find(conf, name); - if (ag != NULL) { + const auto &pair = conf_auth_groups.try_emplace(name, + std::make_shared<auth_group>(freebsd::stringf("auth-group \"%s\"", + name))); + if (!pair.second) { log_warnx("duplicated auth-group \"%s\"", name); return (NULL); } - asprintf(&label, "auth-group \"%s\"", name); - return (auth_group_create(conf, name, label)); + return (pair.first->second.get()); } +/* + * Make it possible to redefine the default auth-group, but only once. + */ struct auth_group * -auth_group_new(struct conf *conf, struct target *target) +conf::define_default_auth_group() { - char *label; + if (conf_default_ag_defined) { + log_warnx("duplicated auth-group \"default\""); + return (nullptr); + } - asprintf(&label, "target \"%s\"", target->t_name); - return (auth_group_create(conf, NULL, label)); + conf_default_ag_defined = true; + return (find_auth_group("default").get()); } -void -auth_group_delete(struct auth_group *ag) +auth_group_sp +conf::find_auth_group(std::string_view name) { - struct auth *auth, *auth_tmp; - struct auth_name *auth_name, *auth_name_tmp; - struct auth_portal *auth_portal, *auth_portal_tmp; - - TAILQ_REMOVE(&ag->ag_conf->conf_auth_groups, ag, ag_next); + auto it = conf_auth_groups.find(std::string(name)); + if (it == conf_auth_groups.end()) + return {}; - TAILQ_FOREACH_SAFE(auth, &ag->ag_auths, a_next, auth_tmp) - auth_delete(auth); - TAILQ_FOREACH_SAFE(auth_name, &ag->ag_names, an_next, auth_name_tmp) - auth_name_delete(auth_name); - TAILQ_FOREACH_SAFE(auth_portal, &ag->ag_portals, ap_next, - auth_portal_tmp) - auth_portal_delete(auth_portal); - free(ag->ag_label); - free(ag->ag_name); - free(ag); + return (it->second); } -struct auth_group * -auth_group_find(const struct conf *conf, const char *name) +portal_group::portal_group(struct conf *conf, std::string_view name) : + pg_conf(conf), pg_options(nvlist_create(0)), pg_name(name) { - struct auth_group *ag; +} - assert(name != NULL); - TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) { - if (ag->ag_name != NULL && strcmp(ag->ag_name, name) == 0) - return (ag); +struct portal_group * +conf::add_portal_group(const char *name) +{ + auto pair = conf_portal_groups.try_emplace(name, + iscsi_make_portal_group(this, name)); + if (!pair.second) { + log_warnx("duplicated portal-group \"%s\"", name); + return (nullptr); } - return (NULL); + return (pair.first->second.get()); } -static struct portal * -portal_new(struct portal_group *pg) +/* + * Make it possible to redefine the default portal-group, but only + * once. + */ +struct portal_group * +conf::define_default_portal_group() { - struct portal *portal; + if (conf_default_pg_defined) { + log_warnx("duplicated portal-group \"default\""); + return (nullptr); + } - portal = reinterpret_cast<struct portal *>(calloc(1, sizeof(*portal))); - if (portal == NULL) - log_err(1, "calloc"); - TAILQ_INIT(&portal->p_targets); - portal->p_portal_group = pg; - TAILQ_INSERT_TAIL(&pg->pg_portals, portal, p_next); - return (portal); + conf_default_pg_defined = true; + return (find_portal_group("default")); } -static void -portal_delete(struct portal *portal) +struct portal_group * +conf::find_portal_group(std::string_view name) { + auto it = conf_portal_groups.find(std::string(name)); + if (it == conf_portal_groups.end()) + return (nullptr); - TAILQ_REMOVE(&portal->p_portal_group->pg_portals, portal, p_next); - if (portal->p_ai != NULL) - freeaddrinfo(portal->p_ai); - free(portal->p_listen); - free(portal); + return (it->second.get()); } struct portal_group * -portal_group_new(struct conf *conf, const char *name) +conf::add_transport_group(const char *name) { - struct portal_group *pg; - - pg = portal_group_find(conf, name); - if (pg != NULL) { - log_warnx("duplicated portal-group \"%s\"", name); - return (NULL); + auto pair = conf_transport_groups.try_emplace(name, + nvmf_make_transport_group(this, name)); + if (!pair.second) { + log_warnx("duplicated transport-group \"%s\"", name); + return (nullptr); } - pg = reinterpret_cast<struct portal_group *>(calloc(1, sizeof(*pg))); - if (pg == NULL) - log_err(1, "calloc"); - pg->pg_name = checked_strdup(name); - pg->pg_options = nvlist_create(0); - TAILQ_INIT(&pg->pg_portals); - TAILQ_INIT(&pg->pg_ports); - pg->pg_conf = conf; - pg->pg_tag = 0; /* Assigned later in conf_apply(). */ - pg->pg_dscp = -1; - pg->pg_pcp = -1; - TAILQ_INSERT_TAIL(&conf->conf_portal_groups, pg, pg_next); - - return (pg); + return (pair.first->second.get()); } -void -portal_group_delete(struct portal_group *pg) +/* + * Make it possible to redefine the default transport-group, but only + * once. + */ +struct portal_group * +conf::define_default_transport_group() { - struct portal *portal, *tmp; - struct port *port, *tport; - - TAILQ_FOREACH_SAFE(port, &pg->pg_ports, p_pgs, tport) - port_delete(port); - TAILQ_REMOVE(&pg->pg_conf->conf_portal_groups, pg, pg_next); + if (conf_default_tg_defined) { + log_warnx("duplicated transport-group \"default\""); + return (nullptr); + } - TAILQ_FOREACH_SAFE(portal, &pg->pg_portals, p_next, tmp) - portal_delete(portal); - nvlist_destroy(pg->pg_options); - free(pg->pg_name); - free(pg->pg_offload); - free(pg->pg_redirection); - free(pg); + conf_default_tg_defined = true; + return (find_transport_group("default")); } struct portal_group * -portal_group_find(const struct conf *conf, const char *name) +conf::find_transport_group(std::string_view name) { - struct portal_group *pg; + auto it = conf_transport_groups.find(std::string(name)); + if (it == conf_transport_groups.end()) + return (nullptr); - TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { - if (strcmp(pg->pg_name, name) == 0) - return (pg); - } + return (it->second.get()); +} - return (NULL); +bool +portal_group::is_dummy() const +{ + if (pg_foreign) + return (true); + if (pg_portals.empty()) + return (true); + return (false); } -static int -parse_addr_port(char *arg, const char *def_port, struct addrinfo **ai) +freebsd::addrinfo_up +parse_addr_port(const char *address, const char *def_port) { - struct addrinfo hints; - char *str, *addr, *ch; - const char *port; - int error, colons = 0; + struct addrinfo hints, *ai; + int error; - str = arg = strdup(arg); - if (arg[0] == '[') { + std::string addr(address); + std::string port(def_port); + if (addr[0] == '[') { /* * IPv6 address in square brackets, perhaps with port. */ - arg++; - addr = strsep(&arg, "]"); - if (arg == NULL) { - free(str); - return (1); - } - if (arg[0] == '\0') { - port = def_port; - } else if (arg[0] == ':') { - port = arg + 1; - } else { - free(str); - return (1); + addr.erase(0, 1); + size_t pos = addr.find(']'); + if (pos == 0 || pos == addr.npos) + return {}; + if (pos < addr.length() - 1) { + port = addr.substr(pos + 1); + if (port[0] != ':' || port.length() < 2) + return {}; + port.erase(0, 1); } + addr.resize(pos); } else { /* * Either IPv6 address without brackets - and without * a port - or IPv4 address. Just count the colons. */ - for (ch = arg; *ch != '\0'; ch++) { - if (*ch == ':') - colons++; - } - if (colons > 1) { - addr = arg; - port = def_port; - } else { - addr = strsep(&arg, ":"); - if (arg == NULL) - port = def_port; - else - port = arg; + size_t pos = addr.find(':'); + if (pos != addr.npos && addr.find(':', pos + 1) == addr.npos) { + /* Only a single colon at `pos`. */ + if (pos == addr.length() - 1) + return {}; + port = addr.substr(pos + 1); + addr.resize(pos); } } @@ -653,49 +643,217 @@ parse_addr_port(char *arg, const char *def_port, struct addrinfo **ai) hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; - error = getaddrinfo(addr, port, &hints, ai); - free(str); - return ((error != 0) ? 1 : 0); + error = getaddrinfo(addr.c_str(), port.c_str(), &hints, &ai); + if (error != 0) + return {}; + return freebsd::addrinfo_up(ai); +} + +void +portal_group::add_port(struct portal_group_port *port) +{ + pg_ports.emplace(port->target()->name(), port); +} + +void +portal_group::remove_port(struct portal_group_port *port) +{ + auto it = pg_ports.find(port->target()->name()); + pg_ports.erase(it); +} + +freebsd::nvlist_up +portal_group::options() const +{ + return (freebsd::nvlist_up(nvlist_clone(pg_options.get()))); } bool -portal_group_add_portal(struct portal_group *pg, const char *value, bool iser) +portal_group::add_option(const char *name, const char *value) { - struct portal *portal; + return (option_new(pg_options.get(), name, value)); +} - portal = portal_new(pg); - portal->p_listen = checked_strdup(value); - portal->p_iser = iser; +bool +portal_group::set_discovery_auth_group(const char *ag_name) +{ + if (pg_discovery_auth_group != nullptr) { + log_warnx("discovery-auth-group for %s " + "\"%s\" specified more than once", keyword(), name()); + return (false); + } + pg_discovery_auth_group = pg_conf->find_auth_group(ag_name); + if (pg_discovery_auth_group == nullptr) { + log_warnx("unknown discovery-auth-group \"%s\" " + "for %s \"%s\"", ag_name, keyword(), name()); + return (false); + } + return (true); +} - if (parse_addr_port(portal->p_listen, "3260", &portal->p_ai)) { - log_warnx("invalid listen address %s", portal->p_listen); - portal_delete(portal); +bool +portal_group::set_dscp(u_int dscp) +{ + if (dscp >= 0x40) { + log_warnx("invalid DSCP value %u for %s \"%s\"", + dscp, keyword(), name()); return (false); } - /* - * XXX: getaddrinfo(3) may return multiple addresses; we should turn - * those into multiple portals. - */ + pg_dscp = dscp; + return (true); +} + +void +portal_group::set_foreign() +{ + pg_foreign = true; +} +bool +portal_group::set_offload(const char *offload) +{ + if (!pg_offload.empty()) { + log_warnx("cannot set offload to \"%s\" for " + "%s \"%s\"; already defined", + offload, keyword(), name()); + return (false); + } + + pg_offload = offload; + return (true); +} + +bool +portal_group::set_pcp(u_int pcp) +{ + if (pcp > 7) { + log_warnx("invalid PCP value %u for %s \"%s\"", + pcp, keyword(), name()); + return (false); + } + + pg_pcp = pcp; return (true); } bool -isns_new(struct conf *conf, const char *addr) +portal_group::set_redirection(const char *addr) +{ + if (!pg_redirection.empty()) { + log_warnx("cannot set redirection to \"%s\" for " + "%s \"%s\"; already defined", + addr, keyword(), name()); + return (false); + } + + pg_redirection = addr; + return (true); +} + +void +portal_group::set_tag(uint16_t tag) +{ + pg_tag = tag; +} + +void +portal_group::verify(struct conf *conf) +{ + if (pg_discovery_auth_group == nullptr) { + pg_discovery_auth_group = conf->find_auth_group("default"); + assert(pg_discovery_auth_group != nullptr); + } + + if (pg_discovery_filter == discovery_filter::UNKNOWN) + pg_discovery_filter = discovery_filter::NONE; + + if (!pg_redirection.empty()) { + if (!pg_ports.empty()) { + log_debugx("%s \"%s\" assigned to target, " + "but configured for redirection", keyword(), + name()); + } + pg_assigned = true; + } else if (!pg_ports.empty()) { + pg_assigned = true; + } else { + if (pg_name != "default") + log_warnx("%s \"%s\" not assigned " + "to any target", keyword(), name()); + pg_assigned = false; + } +} + +/* + * Try to reuse a socket for 'newp' from an existing socket in one of + * our portals. + */ +bool +portal_group::reuse_socket(struct portal &newp) +{ + for (portal_up &portal : pg_portals) { + if (newp.reuse_socket(*portal)) + return (true); + } + return (false); +} + +int +portal_group::open_sockets(struct conf &oldconf) { - struct isns *isns; + int cumulated_error = 0; + + if (pg_foreign) + return (0); - isns = reinterpret_cast<struct isns *>(calloc(1, sizeof(*isns))); - if (isns == NULL) - log_err(1, "calloc"); - isns->i_conf = conf; - TAILQ_INSERT_TAIL(&conf->conf_isns, isns, i_next); - isns->i_addr = checked_strdup(addr); + if (!pg_assigned) { + log_debugx("not listening on %s \"%s\", " + "not assigned to any target", keyword(), name()); + return (0); + } - if (parse_addr_port(isns->i_addr, "3205", &isns->i_ai)) { - log_warnx("invalid iSNS address %s", isns->i_addr); - isns_delete(isns); + for (portal_up &portal : pg_portals) { + /* + * Try to find already open portal and reuse the + * listening socket. We don't care about what portal + * or portal group that was, what matters is the + * listening address. + */ + if (oldconf.reuse_portal_group_socket(*portal)) + continue; + + if (!portal->init_socket()) { + cumulated_error++; + continue; + } + } + return (cumulated_error); +} + +void +portal_group::close_sockets() +{ + for (portal_up &portal : pg_portals) { + if (portal->socket() < 0) + continue; + log_debugx("closing socket for %s, %s \"%s\"", + portal->listen(), keyword(), name()); + portal->close(); + } +} + +bool +conf::add_isns(const char *addr) +{ + if (conf_isns.count(addr) > 0) { + log_warnx("duplicate iSNS address %s", addr); + return (false); + } + + freebsd::addrinfo_up ai = parse_addr_port(addr, "3205"); + if (!ai) { + log_warnx("invalid iSNS address %s", addr); return (false); } @@ -704,998 +862,1168 @@ isns_new(struct conf *conf, const char *addr) * those into multiple servers. */ + conf_isns.emplace(addr, isns(addr, std::move(ai))); return (true); } -void -isns_delete(struct isns *isns) + +freebsd::fd_up +isns::connect() { + freebsd::fd_up s; - TAILQ_REMOVE(&isns->i_conf->conf_isns, isns, i_next); - free(isns->i_addr); - if (isns->i_ai != NULL) - freeaddrinfo(isns->i_ai); - free(isns); + s = socket(i_ai->ai_family, i_ai->ai_socktype, i_ai->ai_protocol); + if (!s) { + log_warn("socket(2) failed for %s", addr()); + return (s); + } + if (::connect(s, i_ai->ai_addr, i_ai->ai_addrlen)) { + log_warn("connect(2) failed for %s", addr()); + s.reset(); + } + return (s); } -static int -isns_do_connect(struct isns *isns) +bool +isns::send_request(int s, struct isns_req req) { - int s; - - s = socket(isns->i_ai->ai_family, isns->i_ai->ai_socktype, - isns->i_ai->ai_protocol); - if (s < 0) { - log_warn("socket(2) failed for %s", isns->i_addr); - return (-1); + if (!req.send(s)) { + log_warn("send(2) failed for %s", addr()); + return (false); + } + if (!req.receive(s)) { + log_warn("receive(2) failed for %s", addr()); + return (false); } - if (connect(s, isns->i_ai->ai_addr, isns->i_ai->ai_addrlen)) { - log_warn("connect(2) failed for %s", isns->i_addr); - close(s); - return (-1); + uint32_t error = req.get_status(); + if (error != 0) { + log_warnx("iSNS %s error %u for %s", req.descr(), error, + addr()); + return (false); } - return(s); + return (true); } -static int -isns_do_register(struct isns *isns, int s, const char *hostname) +struct isns_req +conf::isns_register_request(const char *hostname) { - struct conf *conf = isns->i_conf; - struct target *target; - struct portal *portal; - struct portal_group *pg; - struct port *port; - struct isns_req *req; - int res = 0; - uint32_t error; - - req = isns_req_create(ISNS_FUNC_DEVATTRREG, ISNS_FLAG_CLIENT); - isns_req_add_str(req, 32, TAILQ_FIRST(&conf->conf_targets)->t_name); - isns_req_add_delim(req); - isns_req_add_str(req, 1, hostname); - isns_req_add_32(req, 2, 2); /* 2 -- iSCSI */ - isns_req_add_32(req, 6, conf->conf_isns_period); - TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { - if (pg->pg_unassigned) + const struct portal_group *pg; + + isns_req req(ISNS_FUNC_DEVATTRREG, ISNS_FLAG_CLIENT, "register"); + req.add_str(32, conf_first_target->name()); + req.add_delim(); + req.add_str(1, hostname); + req.add_32(2, 2); /* 2 -- iSCSI */ + req.add_32(6, conf_isns_period); + for (const auto &kv : conf_portal_groups) { + pg = kv.second.get(); + + if (!pg->assigned()) continue; - TAILQ_FOREACH(portal, &pg->pg_portals, p_next) { - isns_req_add_addr(req, 16, portal->p_ai); - isns_req_add_port(req, 17, portal->p_ai); + for (const portal_up &portal : pg->portals()) { + req.add_addr(16, portal->ai()); + req.add_port(17, portal->ai()); } } - TAILQ_FOREACH(target, &conf->conf_targets, t_next) { - isns_req_add_str(req, 32, target->t_name); - isns_req_add_32(req, 33, 1); /* 1 -- Target*/ - if (target->t_alias != NULL) - isns_req_add_str(req, 34, target->t_alias); - TAILQ_FOREACH(port, &target->t_ports, p_ts) { - if ((pg = port->p_portal_group) == NULL) + for (const auto &kv : conf_targets) { + const struct target *target = kv.second.get(); + + req.add_str(32, target->name()); + req.add_32(33, 1); /* 1 -- Target*/ + if (target->has_alias()) + req.add_str(34, target->alias()); + for (const port *port : target->ports()) { + pg = port->portal_group(); + if (pg == nullptr) continue; - isns_req_add_32(req, 51, pg->pg_tag); - TAILQ_FOREACH(portal, &pg->pg_portals, p_next) { - isns_req_add_addr(req, 49, portal->p_ai); - isns_req_add_port(req, 50, portal->p_ai); + req.add_32(51, pg->tag()); + for (const portal_up &portal : pg->portals()) { + req.add_addr(49, portal->ai()); + req.add_port(50, portal->ai()); } } } - res = isns_req_send(s, req); - if (res < 0) { - log_warn("send(2) failed for %s", isns->i_addr); - goto quit; - } - res = isns_req_receive(s, req); - if (res < 0) { - log_warn("receive(2) failed for %s", isns->i_addr); - goto quit; - } - error = isns_req_get_status(req); - if (error != 0) { - log_warnx("iSNS register error %d for %s", error, isns->i_addr); - res = -1; - } -quit: - isns_req_free(req); - return (res); + return (req); } -static int -isns_do_check(struct isns *isns, int s, const char *hostname) -{ - struct conf *conf = isns->i_conf; - struct isns_req *req; - int res = 0; - uint32_t error; - - req = isns_req_create(ISNS_FUNC_DEVATTRQRY, ISNS_FLAG_CLIENT); - isns_req_add_str(req, 32, TAILQ_FIRST(&conf->conf_targets)->t_name); - isns_req_add_str(req, 1, hostname); - isns_req_add_delim(req); - isns_req_add(req, 2, 0, NULL); - res = isns_req_send(s, req); - if (res < 0) { - log_warn("send(2) failed for %s", isns->i_addr); - goto quit; - } - res = isns_req_receive(s, req); - if (res < 0) { - log_warn("receive(2) failed for %s", isns->i_addr); - goto quit; - } - error = isns_req_get_status(req); - if (error != 0) { - log_warnx("iSNS check error %d for %s", error, isns->i_addr); - res = -1; - } -quit: - isns_req_free(req); - return (res); +struct isns_req +conf::isns_check_request(const char *hostname) +{ + isns_req req(ISNS_FUNC_DEVATTRQRY, ISNS_FLAG_CLIENT, "check"); + req.add_str(32, conf_first_target->name()); + req.add_str(1, hostname); + req.add_delim(); + req.add(2, 0, NULL); + return (req); } -static int -isns_do_deregister(struct isns *isns, int s, const char *hostname) -{ - struct conf *conf = isns->i_conf; - struct isns_req *req; - int res = 0; - uint32_t error; - - req = isns_req_create(ISNS_FUNC_DEVDEREG, ISNS_FLAG_CLIENT); - isns_req_add_str(req, 32, TAILQ_FIRST(&conf->conf_targets)->t_name); - isns_req_add_delim(req); - isns_req_add_str(req, 1, hostname); - res = isns_req_send(s, req); - if (res < 0) { - log_warn("send(2) failed for %s", isns->i_addr); - goto quit; - } - res = isns_req_receive(s, req); - if (res < 0) { - log_warn("receive(2) failed for %s", isns->i_addr); - goto quit; - } - error = isns_req_get_status(req); - if (error != 0) { - log_warnx("iSNS deregister error %d for %s", error, isns->i_addr); - res = -1; - } -quit: - isns_req_free(req); - return (res); +struct isns_req +conf::isns_deregister_request(const char *hostname) +{ + isns_req req(ISNS_FUNC_DEVDEREG, ISNS_FLAG_CLIENT, "deregister"); + req.add_str(32, conf_first_target->name()); + req.add_delim(); + req.add_str(1, hostname); + return (req); } void -isns_register(struct isns *isns, struct isns *oldisns) +conf::isns_register_targets(struct isns *isns, struct conf *oldconf) { - struct conf *conf = isns->i_conf; - int error, s; + int error; char hostname[256]; - if (TAILQ_EMPTY(&conf->conf_targets) || - TAILQ_EMPTY(&conf->conf_portal_groups)) + if (conf_targets.empty() || conf_portal_groups.empty()) return; - set_timeout(conf->conf_isns_timeout, false); - s = isns_do_connect(isns); - if (s < 0) { - set_timeout(0, false); + start_timer(conf_isns_timeout); + freebsd::fd_up s = isns->connect(); + if (!s) { + stop_timer(); return; } error = gethostname(hostname, sizeof(hostname)); if (error != 0) log_err(1, "gethostname"); - if (oldisns == NULL || TAILQ_EMPTY(&oldisns->i_conf->conf_targets)) - oldisns = isns; - isns_do_deregister(oldisns, s, hostname); - isns_do_register(isns, s, hostname); - close(s); - set_timeout(0, false); + if (oldconf == nullptr || oldconf->conf_first_target == nullptr) + oldconf = this; + isns->send_request(s, oldconf->isns_deregister_request(hostname)); + isns->send_request(s, isns_register_request(hostname)); + s.reset(); + stop_timer(); } void -isns_check(struct isns *isns) +conf::isns_check(struct isns *isns) { - struct conf *conf = isns->i_conf; - int error, s, res; + int error; char hostname[256]; - if (TAILQ_EMPTY(&conf->conf_targets) || - TAILQ_EMPTY(&conf->conf_portal_groups)) + if (conf_targets.empty() || conf_portal_groups.empty()) return; - set_timeout(conf->conf_isns_timeout, false); - s = isns_do_connect(isns); - if (s < 0) { - set_timeout(0, false); + start_timer(conf_isns_timeout); + freebsd::fd_up s = isns->connect(); + if (!s) { + stop_timer(); return; } error = gethostname(hostname, sizeof(hostname)); if (error != 0) log_err(1, "gethostname"); - res = isns_do_check(isns, s, hostname); - if (res < 0) { - isns_do_deregister(isns, s, hostname); - isns_do_register(isns, s, hostname); + if (!isns->send_request(s, isns_check_request(hostname))) { + isns->send_request(s, isns_deregister_request(hostname)); + isns->send_request(s, isns_register_request(hostname)); } - close(s); - set_timeout(0, false); + s.reset(); + stop_timer(); } void -isns_deregister(struct isns *isns) +conf::isns_deregister_targets(struct isns *isns) { - struct conf *conf = isns->i_conf; - int error, s; + int error; char hostname[256]; - if (TAILQ_EMPTY(&conf->conf_targets) || - TAILQ_EMPTY(&conf->conf_portal_groups)) + if (conf_targets.empty() || conf_portal_groups.empty()) return; - set_timeout(conf->conf_isns_timeout, false); - s = isns_do_connect(isns); - if (s < 0) + start_timer(conf_isns_timeout); + freebsd::fd_up s = isns->connect(); + if (!s) return; error = gethostname(hostname, sizeof(hostname)); if (error != 0) log_err(1, "gethostname"); - isns_do_deregister(isns, s, hostname); - close(s); - set_timeout(0, false); + isns->send_request(s, isns_deregister_request(hostname)); + s.reset(); + stop_timer(); } -struct pport * -pport_new(struct kports *kports, const char *name, uint32_t ctl_port) +void +conf::isns_schedule_update() { - struct pport *pp; - - pp = reinterpret_cast<struct pport *>(calloc(1, sizeof(*pp))); - if (pp == NULL) - log_err(1, "calloc"); - pp->pp_kports = kports; - pp->pp_name = checked_strdup(name); - pp->pp_ctl_port = ctl_port; - TAILQ_INIT(&pp->pp_ports); - TAILQ_INSERT_TAIL(&kports->pports, pp, pp_next); - return (pp); + if (!conf_isns.empty()) + start_timer((conf_isns_period + 2) / 3); } -struct pport * -pport_find(const struct kports *kports, const char *name) +void +conf::isns_update() { - struct pport *pp; + stop_timer(); + for (auto &kv : conf_isns) + isns_check(&kv.second); + + isns_schedule_update(); +} - TAILQ_FOREACH(pp, &kports->pports, pp_next) { - if (strcasecmp(pp->pp_name, name) == 0) - return (pp); +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 (NULL); + + return (true); +} + +bool +kports::has_port(std::string_view name) +{ + return (pports.count(std::string(name)) > 0); } struct pport * -pport_copy(struct pport *pp, struct kports *kports) +kports::find_port(std::string_view name) { - struct pport *ppnew; + auto it = pports.find(std::string(name)); + if (it == pports.end()) + return (nullptr); + return (&it->second); +} - ppnew = pport_new(kports, pp->pp_name, pp->pp_ctl_port); - return (ppnew); +port::port(struct target *target) : + p_target(target) +{ + target->add_port(this); } void -pport_delete(struct pport *pp) +port::clear_references() +{ + p_target->remove_port(this); +} + +portal_group_port::portal_group_port(struct target *target, + struct portal_group *pg, auth_group_sp ag) : + port(target), p_auth_group(ag), p_portal_group(pg) { - struct port *port, *tport; + p_portal_group->add_port(this); +} - TAILQ_FOREACH_SAFE(port, &pp->pp_ports, p_ts, tport) - port_delete(port); - TAILQ_REMOVE(&pp->pp_kports->pports, pp, pp_next); - free(pp->pp_name); - free(pp); +portal_group_port::portal_group_port(struct target *target, + struct portal_group *pg, uint32_t ctl_port) : + port(target), p_portal_group(pg) +{ + p_ctl_port = ctl_port; + p_portal_group->add_port(this); } -struct port * -port_new(struct conf *conf, struct target *target, struct portal_group *pg) +bool +portal_group_port::is_dummy() const { - struct port *port; - char *name; - int ret; + return (p_portal_group->is_dummy()); +} - ret = asprintf(&name, "%s-%s", pg->pg_name, target->t_name); - if (ret <= 0) - log_err(1, "asprintf"); - if (port_find(conf, name) != NULL) { - log_warnx("duplicate port \"%s\"", name); - free(name); - return (NULL); +void +portal_group_port::clear_references() +{ + p_portal_group->remove_port(this); + port::clear_references(); +} + +bool +conf::add_port(struct target *target, struct portal_group *pg, auth_group_sp ag) +{ + std::string name = freebsd::stringf("%s-%s", pg->name(), + target->name()); + const auto &pair = conf_ports.try_emplace(name, pg->create_port(target, + ag)); + if (!pair.second) { + log_warnx("duplicate port \"%s\"", name.c_str()); + return (false); } - port = reinterpret_cast<struct port *>(calloc(1, sizeof(*port))); - if (port == NULL) - log_err(1, "calloc"); - port->p_conf = conf; - port->p_name = name; - TAILQ_INSERT_TAIL(&conf->conf_ports, port, p_next); - TAILQ_INSERT_TAIL(&target->t_ports, port, p_ts); - port->p_target = target; - TAILQ_INSERT_TAIL(&pg->pg_ports, port, p_pgs); - port->p_portal_group = pg; - return (port); + + return (true); } -struct port * -port_new_ioctl(struct conf *conf, struct kports *kports, struct target *target, - int pp, int vp) +bool +conf::add_port(struct target *target, struct portal_group *pg, + uint32_t ctl_port) +{ + std::string name = freebsd::stringf("%s-%s", pg->name(), + target->name()); + const auto &pair = conf_ports.try_emplace(name, pg->create_port(target, + ctl_port)); + if (!pair.second) { + log_warnx("duplicate port \"%s\"", name.c_str()); + return (false); + } + + return (true); +} + +bool +conf::add_port(struct target *target, struct pport *pp) +{ + std::string name = freebsd::stringf("%s-%s", pp->name(), + target->name()); + const auto &pair = conf_ports.try_emplace(name, + std::make_unique<kernel_port>(target, pp)); + if (!pair.second) { + log_warnx("duplicate port \"%s\"", name.c_str()); + return (false); + } + + pp->link(); + return (true); +} + +bool +conf::add_port(struct kports &kports, struct target *target, int pp, int vp) { struct pport *pport; - struct port *port; - char *pname; - char *name; - int ret; - - ret = asprintf(&pname, "ioctl/%d/%d", pp, vp); - if (ret <= 0) { - log_err(1, "asprintf"); - return (NULL); + + 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)); + if (!pair.second) { + log_warnx("duplicate port \"%s\"", name.c_str()); + return (false); } - pport = pport_find(kports, pname); - if (pport != NULL) { - free(pname); - return (port_new_pp(conf, target, pport)); + return (true); +} + +const struct port * +portal_group::find_port(std::string_view target) const +{ + auto it = pg_ports.find(std::string(target)); + if (it == pg_ports.end()) + return (nullptr); + return (it->second); +} + +struct target * +conf::add_controller(const char *name) +{ + if (!nvmf_nqn_valid_strict(name)) { + log_warnx("controller name \"%s\" is invalid for NVMe", name); + return nullptr; } - ret = asprintf(&name, "%s-%s", pname, target->t_name); - free(pname); + /* + * Normalize the name to lowercase to match iSCSI. + */ + std::string t_name(name); + for (char &c : t_name) + c = tolower(c); - if (ret <= 0) - log_err(1, "asprintf"); - if (port_find(conf, name) != NULL) { - log_warnx("duplicate port \"%s\"", name); - free(name); - return (NULL); + auto const &pair = conf_controllers.try_emplace(t_name, + nvmf_make_controller(this, t_name)); + if (!pair.second) { + log_warnx("duplicated controller \"%s\"", name); + return nullptr; } - port = reinterpret_cast<struct port *>(calloc(1, sizeof(*port))); - if (port == NULL) - log_err(1, "calloc"); - port->p_conf = conf; - port->p_name = name; - port->p_ioctl_port = true; - port->p_ioctl_pp = pp; - port->p_ioctl_vp = vp; - TAILQ_INSERT_TAIL(&conf->conf_ports, port, p_next); - TAILQ_INSERT_TAIL(&target->t_ports, port, p_ts); - port->p_target = target; - return (port); -} - -struct port * -port_new_pp(struct conf *conf, struct target *target, struct pport *pp) -{ - struct port *port; - char *name; - int ret; - - ret = asprintf(&name, "%s-%s", pp->pp_name, target->t_name); - if (ret <= 0) - log_err(1, "asprintf"); - if (port_find(conf, name) != NULL) { - log_warnx("duplicate port \"%s\"", name); - free(name); + + return pair.first->second.get(); +} + +struct target * +conf::find_controller(std::string_view name) +{ + auto it = conf_controllers.find(std::string(name)); + if (it == conf_controllers.end()) + return nullptr; + return it->second.get(); +} + +target::target(struct conf *conf, const char *keyword, std::string_view name) : + t_conf(conf), t_name(name) +{ + t_label = freebsd::stringf("%s \"%s\"", keyword, t_name.c_str()); +} + +struct target * +conf::add_target(const char *name) +{ + if (!valid_iscsi_name(name, log_warnx)) + return (nullptr); + + /* + * RFC 3722 requires us to normalize the name to lowercase. + */ + std::string t_name(name); + for (char &c : t_name) + c = tolower(c); + + auto const &pair = conf_targets.try_emplace(t_name, + iscsi_make_target(this, t_name)); + if (!pair.second) { + log_warnx("duplicated target \"%s\"", name); return (NULL); } - port = reinterpret_cast<struct port *>(calloc(1, sizeof(*port))); - if (port == NULL) - log_err(1, "calloc"); - port->p_conf = conf; - port->p_name = name; - TAILQ_INSERT_TAIL(&conf->conf_ports, port, p_next); - TAILQ_INSERT_TAIL(&target->t_ports, port, p_ts); - port->p_target = target; - TAILQ_INSERT_TAIL(&pp->pp_ports, port, p_pps); - port->p_pport = pp; - return (port); + + if (conf_first_target == nullptr) + conf_first_target = pair.first->second.get(); + return (pair.first->second.get()); +} + +struct target * +conf::find_target(std::string_view name) +{ + auto it = conf_targets.find(std::string(name)); + if (it == conf_targets.end()) + return (nullptr); + return (it->second.get()); } -struct port * -port_find(const struct conf *conf, const char *name) +bool +target::use_private_auth(const char *keyword) { - struct port *port; + if (t_private_auth) + return (true); - TAILQ_FOREACH(port, &conf->conf_ports, p_next) { - if (strcasecmp(port->p_name, name) == 0) - return (port); + if (t_auth_group != nullptr) { + log_warnx("cannot use both auth-group and %s for %s", + keyword, label()); + return (false); } - return (NULL); + t_auth_group = std::make_shared<struct auth_group>(t_label); + t_private_auth = true; + return (true); +} + +bool +target::add_chap(const char *user, const char *secret) +{ + if (!use_private_auth("chap")) + return (false); + return (t_auth_group->add_chap(user, secret)); +} + +bool +target::add_chap_mutual(const char *user, const char *secret, + const char *user2, const char *secret2) +{ + if (!use_private_auth("chap-mutual")) + return (false); + return (t_auth_group->add_chap_mutual(user, secret, user2, secret2)); } -struct port * -port_find_in_pg(const struct portal_group *pg, const char *target) +bool +target::add_lun(u_int id, const char *lun_label, const char *lun_name) { - struct port *port; + struct lun *t_lun; + + if (id >= MAX_LUNS) { + log_warnx("%s too big for %s", lun_label, label()); + return (false); + } + + if (t_luns[id] != NULL) { + log_warnx("duplicate %s for %s", lun_label, label()); + return (false); + } - TAILQ_FOREACH(port, &pg->pg_ports, p_pgs) { - if (strcasecmp(port->p_target->t_name, target) == 0) - return (port); + t_lun = t_conf->find_lun(lun_name); + if (t_lun == NULL) { + log_warnx("unknown LUN named %s used for %s", lun_name, + label()); + return (false); } - return (NULL); + t_luns[id] = t_lun; + return (true); } -void -port_delete(struct port *port) +bool +target::set_alias(std::string_view alias) { + if (has_alias()) { + log_warnx("alias for %s specified more than once", label()); + return (false); + } + t_alias = alias; + return (true); +} - if (port->p_portal_group) - TAILQ_REMOVE(&port->p_portal_group->pg_ports, port, p_pgs); - if (port->p_pport) - TAILQ_REMOVE(&port->p_pport->pp_ports, port, p_pps); - if (port->p_target) - TAILQ_REMOVE(&port->p_target->t_ports, port, p_ts); - TAILQ_REMOVE(&port->p_conf->conf_ports, port, p_next); - free(port->p_name); - free(port); +bool +target::set_auth_group(const char *ag_name) +{ + if (t_auth_group != nullptr) { + if (t_private_auth) + log_warnx("cannot use both auth-group and explicit " + "authorisations for %s", label()); + else + log_warnx("auth-group for %s " + "specified more than once", label()); + return (false); + } + t_auth_group = t_conf->find_auth_group(ag_name); + if (t_auth_group == nullptr) { + log_warnx("unknown auth-group \"%s\" for %s", + ag_name, label()); + return (false); + } + return (true); } bool -port_is_dummy(struct port *port) +target::set_auth_type(const char *type) { + if (!use_private_auth("auth-type")) + return (false); + return (t_auth_group->set_type(type)); +} - if (port->p_portal_group) { - if (port->p_portal_group->pg_foreign) - return (true); - if (TAILQ_EMPTY(&port->p_portal_group->pg_portals)) - return (true); +bool +target::set_physical_port(std::string_view pport) +{ + if (!t_pport.empty()) { + log_warnx("cannot set multiple physical ports for target " + "\"%s\"", name()); + return (false); } - return (false); + t_pport = pport; + return (true); } -struct target * -target_new(struct conf *conf, const char *name) +bool +target::set_redirection(const char *addr) { - struct target *targ; - int i, len; + if (!t_redirection.empty()) { + log_warnx("cannot set redirection to \"%s\" for " + "%s; already defined", + addr, label()); + return (false); + } - targ = target_find(conf, name); - if (targ != NULL) { - log_warnx("duplicated target \"%s\"", name); - return (NULL); + t_redirection = addr; + return (true); +} + +struct lun * +target::start_lun(u_int id, const char *lun_label, const char *lun_name) +{ + if (id >= MAX_LUNS) { + log_warnx("%s too big for %s", lun_label, label()); + return (nullptr); } - if (valid_iscsi_name(name, log_warnx) == false) { - return (NULL); + + if (t_luns[id] != NULL) { + log_warnx("duplicate %s for %s", lun_label, label()); + return (nullptr); } - targ = reinterpret_cast<struct target *>(calloc(1, sizeof(*targ))); - if (targ == NULL) - log_err(1, "calloc"); - targ->t_name = checked_strdup(name); - /* - * RFC 3722 requires us to normalize the name to lowercase. - */ - len = strlen(name); - for (i = 0; i < len; i++) - targ->t_name[i] = tolower(targ->t_name[i]); + struct lun *new_lun = t_conf->add_lun(lun_name); + if (new_lun == nullptr) + return (nullptr); + + new_lun->set_scsiname(lun_name); - targ->t_conf = conf; - TAILQ_INIT(&targ->t_ports); - TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next); + t_luns[id] = new_lun; - return (targ); + return (new_lun); } void -target_delete(struct target *targ) +target::add_port(struct port *port) { - struct port *port, *tport; - - TAILQ_FOREACH_SAFE(port, &targ->t_ports, p_ts, tport) - port_delete(port); - TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next); + t_ports.push_back(port); +} - free(targ->t_pport); - free(targ->t_name); - free(targ->t_redirection); - free(targ); +void +target::remove_port(struct port *port) +{ + t_ports.remove(port); } -struct target * -target_find(struct conf *conf, const char *name) +void +target::remove_lun(struct lun *lun) { - struct target *targ; + /* XXX: clang is not able to deduce the type without the cast. */ + std::replace(t_luns.begin(), t_luns.end(), lun, + static_cast<struct lun *>(nullptr)); +} - TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { - if (strcasecmp(targ->t_name, name) == 0) - return (targ); +void +target::verify() +{ + if (t_auth_group == nullptr) { + t_auth_group = t_conf->find_auth_group("default"); + assert(t_auth_group != nullptr); + } + if (t_ports.empty()) { + struct portal_group *pg = default_portal_group(); + assert(pg != NULL); + t_conf->add_port(this, pg, nullptr); } - return (NULL); + bool found = std::any_of(t_luns.begin(), t_luns.end(), + [](struct lun *lun) { return (lun != nullptr); }); + if (!found && t_redirection.empty()) + log_warnx("no LUNs defined for %s", label()); + if (found && !t_redirection.empty()) + log_debugx("%s contains LUNs, but configured " + "for redirection", label()); } -struct lun * -lun_new(struct conf *conf, const char *name) +lun::lun(struct conf *conf, std::string_view name) + : l_conf(conf), l_options(nvlist_create(0)), l_name(name) { - struct lun *lun; +} - lun = lun_find(conf, name); - if (lun != NULL) { +struct lun * +conf::add_lun(const char *name) +{ + const auto &pair = conf_luns.try_emplace(name, + std::make_unique<lun>(this, name)); + if (!pair.second) { log_warnx("duplicated lun \"%s\"", name); return (NULL); } + return (pair.first->second.get()); +} + +void +conf::delete_target_luns(struct lun *lun) +{ + for (const auto &kv : conf_targets) + kv.second->remove_lun(lun); + for (const auto &kv : conf_controllers) + kv.second->remove_lun(lun); +} - lun = reinterpret_cast<struct lun *>(calloc(1, sizeof(*lun))); - if (lun == NULL) - log_err(1, "calloc"); - lun->l_conf = conf; - lun->l_name = checked_strdup(name); - lun->l_options = nvlist_create(0); - TAILQ_INSERT_TAIL(&conf->conf_luns, lun, l_next); - lun->l_ctl_lun = -1; +struct lun * +conf::find_lun(std::string_view name) +{ + auto it = conf_luns.find(std::string(name)); + if (it == conf_luns.end()) + return (nullptr); + return (it->second.get()); +} - return (lun); +static void +nvlist_replace_string(nvlist_t *nvl, const char *name, const char *value) +{ + if (nvlist_exists_string(nvl, name)) + nvlist_free_string(nvl, name); + nvlist_add_string(nvl, name, value); } -void -lun_delete(struct lun *lun) +freebsd::nvlist_up +lun::options() const { - struct target *targ; - int i; + freebsd::nvlist_up nvl(nvlist_clone(l_options.get())); + if (!l_path.empty()) + nvlist_replace_string(nvl.get(), "file", l_path.c_str()); - TAILQ_FOREACH(targ, &lun->l_conf->conf_targets, t_next) { - for (i = 0; i < MAX_LUNS; i++) { - if (targ->t_luns[i] == lun) - targ->t_luns[i] = NULL; - } - } - TAILQ_REMOVE(&lun->l_conf->conf_luns, lun, l_next); + nvlist_replace_string(nvl.get(), "ctld_name", l_name.c_str()); - nvlist_destroy(lun->l_options); - free(lun->l_name); - free(lun->l_backend); - free(lun->l_device_id); - free(lun->l_path); - free(lun->l_scsiname); - free(lun->l_serial); - free(lun); + if (!nvlist_exists_string(nvl.get(), "scsiname") && + !l_scsiname.empty()) + nvlist_add_string(nvl.get(), "scsiname", l_scsiname.c_str()); + return (nvl); } -struct lun * -lun_find(const struct conf *conf, const char *name) +bool +lun::add_option(const char *name, const char *value) { - struct lun *lun; + return (option_new(l_options.get(), name, value)); +} - TAILQ_FOREACH(lun, &conf->conf_luns, l_next) { - if (strcmp(lun->l_name, name) == 0) - return (lun); +bool +lun::set_backend(std::string_view value) +{ + if (!l_backend.empty()) { + log_warnx("backend for lun \"%s\" specified more than once", + name()); + return (false); } - return (NULL); + l_backend = value; + return (true); } -void -lun_set_scsiname(struct lun *lun, const char *value) +bool +lun::set_blocksize(size_t value) { - free(lun->l_scsiname); - lun->l_scsiname = checked_strdup(value); + if (l_blocksize != 0) { + log_warnx("blocksize for lun \"%s\" specified more than once", + name()); + return (false); + } + l_blocksize = value; + return (true); } bool -option_new(nvlist_t *nvl, const char *name, const char *value) +lun::set_ctl_lun(uint32_t value) { - int error; - - if (nvlist_exists_string(nvl, name)) { - log_warnx("duplicated option \"%s\"", name); + if (l_ctl_lun >= 0) { + log_warnx("ctl_lun for lun \"%s\" specified more than once", + name()); return (false); } - nvlist_add_string(nvl, name, value); - error = nvlist_error(nvl); - if (error != 0) { - log_warnc(error, "failed to add option \"%s\"", name); + l_ctl_lun = value; + return (true); +} + +bool +lun::set_device_type(uint8_t device_type) +{ + if (device_type > 15) { + log_warnx("invalid device-type \"%u\" for lun \"%s\"", + device_type, name()); return (false); } + + l_device_type = device_type; return (true); } -#ifdef ICL_KERNEL_PROXY +bool +lun::set_device_type(const char *value) +{ + const char *errstr; + int device_type; + + if (strcasecmp(value, "disk") == 0 || + strcasecmp(value, "direct") == 0) + device_type = T_DIRECT; + else if (strcasecmp(value, "processor") == 0) + device_type = T_PROCESSOR; + else if (strcasecmp(value, "cd") == 0 || + strcasecmp(value, "cdrom") == 0 || + strcasecmp(value, "dvd") == 0 || + strcasecmp(value, "dvdrom") == 0) + device_type = T_CDROM; + else { + device_type = strtonum(value, 0, 15, &errstr); + if (errstr != NULL) { + log_warnx("invalid device-type \"%s\" for lun \"%s\"", + value, name()); + return (false); + } + } -static void -pdu_receive_proxy(struct pdu *pdu) + l_device_type = device_type; + return (true); +} + +bool +lun::set_device_id(std::string_view value) { - struct connection *conn; - size_t len; + if (!l_device_id.empty()) { + log_warnx("device_id for lun \"%s\" specified more than once", + name()); + return (false); + } - assert(proxy_mode); - conn = pdu->pdu_connection; + l_device_id = value; + return (true); +} - kernel_receive(pdu); +bool +lun::set_path(std::string_view value) +{ + if (!l_path.empty()) { + log_warnx("path for lun \"%s\" specified more than once", + name()); + return (false); + } - len = pdu_ahs_length(pdu); - if (len > 0) - log_errx(1, "protocol error: non-empty AHS"); + l_path = value; + return (true); +} - len = pdu_data_segment_length(pdu); - assert(len <= (size_t)conn->conn_max_recv_data_segment_length); - pdu->pdu_data_len = len; +void +lun::set_scsiname(std::string_view value) +{ + l_scsiname = value; } -static void -pdu_send_proxy(struct pdu *pdu) +bool +lun::set_serial(std::string_view value) { + if (!l_serial.empty()) { + log_warnx("serial for lun \"%s\" specified more than once", + name()); + return (false); + } - assert(proxy_mode); + l_serial = value; + return (true); +} - pdu_set_data_segment_length(pdu, pdu->pdu_data_len); - kernel_send(pdu); +bool +lun::set_size(uint64_t value) +{ + if (l_size != 0) { + log_warnx("size for lun \"%s\" specified more than once", + name()); + return (false); + } + + l_size = value; + return (true); } -#endif /* ICL_KERNEL_PROXY */ -static void -pdu_fail(const struct connection *conn __unused, const char *reason __unused) +bool +lun::changed(const struct lun &newlun) const { + if (l_backend != newlun.l_backend) { + log_debugx("backend for lun \"%s\", CTL lun %d changed; " + "removing", name(), l_ctl_lun); + return (true); + } + if (l_blocksize != newlun.l_blocksize) { + log_debugx("blocksize for lun \"%s\", CTL lun %d changed; " + "removing", name(), l_ctl_lun); + return (true); + } + if (l_device_id != newlun.l_device_id) { + log_debugx("device-id for lun \"%s\", CTL lun %d changed; " + "removing", name(), l_ctl_lun); + return (true); + } + if (l_path != newlun.l_path) { + log_debugx("path for lun \"%s\", CTL lun %d, changed; " + "removing", name(), l_ctl_lun); + return (true); + } + if (l_serial != newlun.l_serial) { + log_debugx("serial for lun \"%s\", CTL lun %d changed; " + "removing", name(), l_ctl_lun); + return (true); + } + return (false); } -static struct ctld_connection * -connection_new(struct portal *portal, int fd, const char *host, - const struct sockaddr *client_sa) +bool +option_new(nvlist_t *nvl, const char *name, const char *value) { - struct ctld_connection *conn; + int error; - conn = reinterpret_cast<struct ctld_connection *>(calloc(1, sizeof(*conn))); - if (conn == NULL) - log_err(1, "calloc"); - connection_init(&conn->conn, &conn_ops, proxy_mode); - conn->conn.conn_socket = fd; - conn->conn_portal = portal; - conn->conn_initiator_addr = checked_strdup(host); - memcpy(&conn->conn_initiator_sa, client_sa, client_sa->sa_len); + if (nvlist_exists_string(nvl, name)) { + log_warnx("duplicated option \"%s\"", name); + return (false); + } - return (conn); + nvlist_add_string(nvl, name, value); + error = nvlist_error(nvl); + if (error != 0) { + log_warnc(error, "failed to add option \"%s\"", name); + return (false); + } + return (true); } -static bool -conf_verify_lun(struct lun *lun) +bool +lun::verify() { - const struct lun *lun2; - - if (lun->l_backend == NULL) - lun->l_backend = checked_strdup("block"); - if (strcmp(lun->l_backend, "block") == 0) { - if (lun->l_path == NULL) { + if (l_backend.empty()) + l_backend = "block"; + if (l_backend == "block") { + if (l_path.empty()) { log_warnx("missing path for lun \"%s\"", - lun->l_name); + name()); return (false); } - } else if (strcmp(lun->l_backend, "ramdisk") == 0) { - if (lun->l_size == 0) { + } else if (l_backend == "ramdisk") { + if (l_size == 0) { log_warnx("missing size for ramdisk-backed lun \"%s\"", - lun->l_name); + name()); return (false); } - if (lun->l_path != NULL) { + if (!l_path.empty()) { log_warnx("path must not be specified " "for ramdisk-backed lun \"%s\"", - lun->l_name); + name()); return (false); } } - if (lun->l_blocksize == 0) { - if (lun->l_device_type == T_CDROM) - lun->l_blocksize = DEFAULT_CD_BLOCKSIZE; + if (l_blocksize == 0) { + if (l_device_type == T_CDROM) + l_blocksize = DEFAULT_CD_BLOCKSIZE; else - lun->l_blocksize = DEFAULT_BLOCKSIZE; - } else if (lun->l_blocksize < 0) { - log_warnx("invalid blocksize for lun \"%s\"; " - "must be larger than 0", lun->l_name); + l_blocksize = DEFAULT_BLOCKSIZE; + } else if (l_blocksize < 0) { + log_warnx("invalid blocksize %d for lun \"%s\"; " + "must be larger than 0", l_blocksize, name()); return (false); } - if (lun->l_size != 0 && lun->l_size % lun->l_blocksize != 0) { + if (l_size != 0 && (l_size % l_blocksize) != 0) { log_warnx("invalid size for lun \"%s\"; " - "must be multiple of blocksize", lun->l_name); + "must be multiple of blocksize", name()); return (false); } - TAILQ_FOREACH(lun2, &lun->l_conf->conf_luns, l_next) { - if (lun == lun2) - continue; - if (lun->l_path != NULL && lun2->l_path != NULL && - strcmp(lun->l_path, lun2->l_path) == 0) { - log_debugx("WARNING: path \"%s\" duplicated " - "between lun \"%s\", and " - "lun \"%s\"", lun->l_path, - lun->l_name, lun2->l_name); - } - } - return (true); } bool -conf_verify(struct conf *conf) +conf::verify() { - struct auth_group *ag; - struct portal_group *pg; - struct port *port; - struct target *targ; - struct lun *lun; - bool found; - int i; + if (conf_pidfile_path.empty()) + conf_pidfile_path = DEFAULT_PIDFILE; - if (conf->conf_pidfile_path == NULL) - conf->conf_pidfile_path = checked_strdup(DEFAULT_PIDFILE); - - TAILQ_FOREACH(lun, &conf->conf_luns, l_next) { - if (!conf_verify_lun(lun)) + std::unordered_map<std::string, struct lun *> path_map; + for (const auto &kv : conf_luns) { + struct lun *lun = kv.second.get(); + if (!lun->verify()) return (false); - } - TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { - if (targ->t_auth_group == NULL) { - targ->t_auth_group = auth_group_find(conf, - "default"); - assert(targ->t_auth_group != NULL); - } - if (TAILQ_EMPTY(&targ->t_ports)) { - pg = portal_group_find(conf, "default"); - assert(pg != NULL); - port_new(conf, targ, pg); - } - found = false; - for (i = 0; i < MAX_LUNS; i++) { - if (targ->t_luns[i] != NULL) - found = true; - } - if (!found && targ->t_redirection == NULL) { - log_warnx("no LUNs defined for target \"%s\"", - targ->t_name); - } - if (found && targ->t_redirection != NULL) { - log_debugx("target \"%s\" contains luns, " - " but configured for redirection", - targ->t_name); - } - } - TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { - assert(pg->pg_name != NULL); - if (pg->pg_discovery_auth_group == NULL) { - pg->pg_discovery_auth_group = - auth_group_find(conf, "default"); - assert(pg->pg_discovery_auth_group != NULL); - } - if (pg->pg_discovery_filter == PG_FILTER_UNKNOWN) - pg->pg_discovery_filter = PG_FILTER_NONE; + const std::string &path = lun->path(); + if (path.empty()) + continue; - if (pg->pg_redirection != NULL) { - if (!TAILQ_EMPTY(&pg->pg_ports)) { - log_debugx("portal-group \"%s\" assigned " - "to target, but configured " - "for redirection", - pg->pg_name); - } - pg->pg_unassigned = false; - } else if (!TAILQ_EMPTY(&pg->pg_ports)) { - pg->pg_unassigned = false; - } else { - if (strcmp(pg->pg_name, "default") != 0) - log_warnx("portal-group \"%s\" not assigned " - "to any target", pg->pg_name); - pg->pg_unassigned = true; + const auto &pair = path_map.try_emplace(path, lun); + if (!pair.second) { + struct lun *lun2 = pair.first->second; + log_debugx("WARNING: path \"%s\" duplicated " + "between lun \"%s\", and " + "lun \"%s\"", path.c_str(), + lun->name(), lun2->name()); } } - TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) { - found = false; - TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { - if (targ->t_auth_group == ag) { - found = true; - break; - } - } - TAILQ_FOREACH(port, &conf->conf_ports, p_next) { - if (port->p_auth_group == ag) { - found = true; - break; - } - } - TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { - if (pg->pg_discovery_auth_group == ag) { - found = true; - break; - } - } - if (!found && ag->ag_name != NULL && - strcmp(ag->ag_name, "default") != 0 && - strcmp(ag->ag_name, "no-authentication") != 0 && - strcmp(ag->ag_name, "no-access") != 0) { + + for (auto &kv : conf_targets) { + kv.second->verify(); + } + for (auto &kv : conf_controllers) { + kv.second->verify(); + } + for (auto &kv : conf_portal_groups) { + kv.second->verify(this); + } + for (auto &kv : conf_transport_groups) { + kv.second->verify(this); + } + for (const auto &kv : conf_auth_groups) { + const std::string &ag_name = kv.first; + if (ag_name == "default" || + ag_name == "no-authentication" || + ag_name == "no-access") + continue; + + if (kv.second.use_count() == 1) { log_warnx("auth-group \"%s\" not assigned " - "to any target", ag->ag_name); + "to any target", ag_name.c_str()); } } return (true); } -static bool -portal_reuse_socket(struct portal *oldp, struct portal *newp) +bool +portal::reuse_socket(struct portal &oldp) { struct kevent kev; - if (strcmp(newp->p_listen, oldp->p_listen) != 0) + if (p_listen != oldp.p_listen) return (false); - if (oldp->p_socket <= 0) + if (!oldp.p_socket) return (false); - EV_SET(&kev, oldp->p_socket, EVFILT_READ, EV_ADD, 0, 0, newp); + EV_SET(&kev, oldp.p_socket, EVFILT_READ, EV_ADD, 0, 0, this); if (kevent(kqfd, &kev, 1, NULL, 0, NULL) == -1) return (false); - newp->p_socket = oldp->p_socket; - oldp->p_socket = 0; + p_socket = std::move(oldp.p_socket); return (true); } -static bool -portal_init_socket(struct portal *p) +bool +portal::init_socket() { - struct portal_group *pg = p->p_portal_group; + struct portal_group *pg = portal_group(); struct kevent kev; - int error, sockbuf; + freebsd::fd_up s; + int error; int one = 1; - log_debugx("listening on %s, portal-group \"%s\"", - p->p_listen, pg->pg_name); - p->p_socket = socket(p->p_ai->ai_family, p->p_ai->ai_socktype, - p->p_ai->ai_protocol); - if (p->p_socket < 0) { - log_warn("socket(2) failed for %s", - p->p_listen); +#ifdef ICL_KERNEL_PROXY + if (proxy_mode) { + int id = pg->conf()->add_proxy_portal(this); + log_debugx("listening on %s, %s \"%s\", " + "portal id %d, using ICL proxy", listen(), pg->keyword(), + pg->name(), id); + kernel_listen(ai(), protocol() == ISER, id); + return (true); + } +#endif + assert(proxy_mode == false); + assert(protocol() != portal_protocol::ISER); + + log_debugx("listening on %s, %s \"%s\"", listen(), pg->keyword(), + pg->name()); + s = ::socket(p_ai->ai_family, p_ai->ai_socktype, p_ai->ai_protocol); + if (!s) { + log_warn("socket(2) failed for %s", listen()); return (false); } - sockbuf = SOCKBUF_SIZE; - if (setsockopt(p->p_socket, SOL_SOCKET, SO_RCVBUF, &sockbuf, - sizeof(sockbuf)) == -1) - log_warn("setsockopt(SO_RCVBUF) failed for %s", - p->p_listen); - sockbuf = SOCKBUF_SIZE; - if (setsockopt(p->p_socket, SOL_SOCKET, SO_SNDBUF, &sockbuf, - sizeof(sockbuf)) == -1) - log_warn("setsockopt(SO_SNDBUF) failed for %s", p->p_listen); - if (setsockopt(p->p_socket, SOL_SOCKET, SO_NO_DDP, &one, + if (setsockopt(s, SOL_SOCKET, SO_NO_DDP, &one, sizeof(one)) == -1) - log_warn("setsockopt(SO_NO_DDP) failed for %s", p->p_listen); - error = setsockopt(p->p_socket, SOL_SOCKET, SO_REUSEADDR, &one, + log_warn("setsockopt(SO_NO_DDP) failed for %s", listen()); + error = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); if (error != 0) { - log_warn("setsockopt(SO_REUSEADDR) failed for %s", p->p_listen); - close(p->p_socket); - p->p_socket = 0; + log_warn("setsockopt(SO_REUSEADDR) failed for %s", listen()); return (false); } - if (pg->pg_dscp != -1) { + if (pg->dscp() != -1) { /* Only allow the 6-bit DSCP field to be modified */ - int tos = pg->pg_dscp << 2; - switch (p->p_ai->ai_family) { + int tos = pg->dscp() << 2; + switch (p_ai->ai_family) { case AF_INET: - if (setsockopt(p->p_socket, IPPROTO_IP, IP_TOS, + if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1) log_warn("setsockopt(IP_TOS) failed for %s", - p->p_listen); + listen()); break; case AF_INET6: - if (setsockopt(p->p_socket, IPPROTO_IPV6, IPV6_TCLASS, + if (setsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)) == -1) log_warn("setsockopt(IPV6_TCLASS) failed for %s", - p->p_listen); + listen()); break; } } - if (pg->pg_pcp != -1) { - int pcp = pg->pg_pcp; - switch (p->p_ai->ai_family) { + if (pg->pcp() != -1) { + int pcp = pg->pcp(); + switch (p_ai->ai_family) { case AF_INET: - if (setsockopt(p->p_socket, IPPROTO_IP, IP_VLAN_PCP, + if (setsockopt(s, IPPROTO_IP, IP_VLAN_PCP, &pcp, sizeof(pcp)) == -1) log_warn("setsockopt(IP_VLAN_PCP) failed for %s", - p->p_listen); + listen()); break; case AF_INET6: - if (setsockopt(p->p_socket, IPPROTO_IPV6, IPV6_VLAN_PCP, + if (setsockopt(s, IPPROTO_IPV6, IPV6_VLAN_PCP, &pcp, sizeof(pcp)) == -1) log_warn("setsockopt(IPV6_VLAN_PCP) failed for %s", - p->p_listen); + listen()); break; } } - error = bind(p->p_socket, p->p_ai->ai_addr, - p->p_ai->ai_addrlen); + if (!init_socket_options(s)) + return (false); + + error = bind(s, p_ai->ai_addr, p_ai->ai_addrlen); if (error != 0) { - log_warn("bind(2) failed for %s", p->p_listen); - close(p->p_socket); - p->p_socket = 0; + log_warn("bind(2) failed for %s", listen()); return (false); } - error = listen(p->p_socket, -1); + error = ::listen(s, -1); if (error != 0) { - log_warn("listen(2) failed for %s", p->p_listen); - close(p->p_socket); - p->p_socket = 0; + log_warn("listen(2) failed for %s", listen()); return (false); } - EV_SET(&kev, p->p_socket, EVFILT_READ, EV_ADD, 0, 0, p); + EV_SET(&kev, s, EVFILT_READ, EV_ADD, 0, 0, this); error = kevent(kqfd, &kev, 1, NULL, 0, NULL); if (error == -1) { - log_warn("kevent(2) failed to register for %s", p->p_listen); - close(p->p_socket); - p->p_socket = 0; + log_warn("kevent(2) failed to register for %s", listen()); return (false); } + p_socket = std::move(s); return (true); } -static int -conf_apply(struct conf *oldconf, struct conf *newconf) -{ - struct lun *oldlun, *newlun, *tmplun; - struct portal_group *oldpg, *newpg; - struct portal *oldp, *newp; - struct port *oldport, *newport, *tmpport; - struct isns *oldns, *newns; - int changed, cumulated_error = 0, error; - - if (oldconf->conf_debug != newconf->conf_debug) { - log_debugx("changing debug level to %d", newconf->conf_debug); - log_init(newconf->conf_debug); - } - - if (oldconf->conf_pidfile_path != NULL && - newconf->conf_pidfile_path != NULL) - { - if (strcmp(oldconf->conf_pidfile_path, - newconf->conf_pidfile_path) != 0) - { +bool +conf::reuse_portal_group_socket(struct portal &newp) +{ + for (auto &kv : conf_portal_groups) { + struct portal_group &pg = *kv.second; + + if (pg.reuse_socket(newp)) + return (true); + } + for (auto &kv : conf_transport_groups) { + struct portal_group &pg = *kv.second; + + if (pg.reuse_socket(newp)) + return (true); + } + return (false); +} + +int +conf::apply(struct conf *oldconf) +{ + int cumulated_error = 0; + + if (oldconf->conf_debug != conf_debug) { + log_debugx("changing debug level to %d", conf_debug); + 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", - newconf->conf_pidfile_path); - if (rename(oldconf->conf_pidfile_path, - newconf->conf_pidfile_path)) - { + 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, - newconf->conf_pidfile_path); + oldconf->conf_pidfile_path.c_str(), + conf_pidfile_path.c_str()); } } - newconf->conf_pidfh = oldconf->conf_pidfh; - oldconf->conf_pidfh = NULL; + conf_pidfile = std::move(oldconf->conf_pidfile); } /* * Go through the new portal groups, assigning tags or preserving old. */ - TAILQ_FOREACH(newpg, &newconf->conf_portal_groups, pg_next) { - if (newpg->pg_tag != 0) + for (auto &kv : conf_portal_groups) { + struct portal_group &newpg = *kv.second; + + if (newpg.tag() != 0) continue; - oldpg = portal_group_find(oldconf, newpg->pg_name); - if (oldpg != NULL) - newpg->pg_tag = oldpg->pg_tag; + auto it = oldconf->conf_portal_groups.find(kv.first); + if (it != oldconf->conf_portal_groups.end()) + newpg.set_tag(it->second->tag()); else - newpg->pg_tag = ++last_portal_group_tag; + newpg.allocate_tag(); + } + for (auto &kv : conf_transport_groups) { + struct portal_group &newpg = *kv.second; + + if (newpg.tag() != 0) + continue; + auto it = oldconf->conf_transport_groups.find(kv.first); + if (it != oldconf->conf_transport_groups.end()) + newpg.set_tag(it->second->tag()); + else + newpg.allocate_tag(); } /* Deregister on removed iSNS servers. */ - TAILQ_FOREACH(oldns, &oldconf->conf_isns, i_next) { - TAILQ_FOREACH(newns, &newconf->conf_isns, i_next) { - if (strcmp(oldns->i_addr, newns->i_addr) == 0) - break; - } - if (newns == NULL) - isns_deregister(oldns); + for (auto &kv : oldconf->conf_isns) { + if (conf_isns.count(kv.first) == 0) + oldconf->isns_deregister_targets(&kv.second); } /* * XXX: If target or lun removal fails, we should somehow "move" - * the old lun or target into newconf, so that subsequent - * conf_apply() would try to remove them again. That would + * the old lun or target into this, so that subsequent + * conf::apply() would try to remove them again. That would * be somewhat hairy, though, and lun deletion failures don't * really happen, so leave it as it is for now. */ @@ -1703,17 +2031,18 @@ conf_apply(struct conf *oldconf, struct conf *newconf) * First, remove any ports present in the old configuration * and missing in the new one. */ - TAILQ_FOREACH_SAFE(oldport, &oldconf->conf_ports, p_next, tmpport) { - if (port_is_dummy(oldport)) + for (const auto &kv : oldconf->conf_ports) { + const std::string &name = kv.first; + port *oldport = kv.second.get(); + + if (oldport->is_dummy()) continue; - newport = port_find(newconf, oldport->p_name); - if (newport != NULL && !port_is_dummy(newport)) + const auto it = conf_ports.find(name); + if (it != conf_ports.end() && !it->second->is_dummy()) continue; - log_debugx("removing port \"%s\"", oldport->p_name); - error = kernel_port_remove(oldport); - if (error != 0) { - log_warnx("failed to remove port %s", - oldport->p_name); + log_debugx("removing port \"%s\"", name.c_str()); + if (!oldport->kernel_remove()) { + log_warnx("failed to remove port %s", name.c_str()); /* * XXX: Uncomment after fixing the root cause. * @@ -1726,221 +2055,156 @@ conf_apply(struct conf *oldconf, struct conf *newconf) * Second, remove any LUNs present in the old configuration * and missing in the new one. */ - TAILQ_FOREACH_SAFE(oldlun, &oldconf->conf_luns, l_next, tmplun) { - newlun = lun_find(newconf, oldlun->l_name); - if (newlun == NULL) { + for (auto it = oldconf->conf_luns.begin(); + it != oldconf->conf_luns.end(); ) { + struct lun *oldlun = it->second.get(); + + auto newit = conf_luns.find(it->first); + if (newit == conf_luns.end()) { log_debugx("lun \"%s\", CTL lun %d " "not found in new configuration; " - "removing", oldlun->l_name, oldlun->l_ctl_lun); - error = kernel_lun_remove(oldlun); - if (error != 0) { + "removing", oldlun->name(), oldlun->ctl_lun()); + if (!oldlun->kernel_remove()) { log_warnx("failed to remove lun \"%s\", " "CTL lun %d", - oldlun->l_name, oldlun->l_ctl_lun); + oldlun->name(), oldlun->ctl_lun()); cumulated_error++; } + it++; continue; } /* * Also remove the LUNs changed by more than size. */ - changed = 0; - assert(oldlun->l_backend != NULL); - assert(newlun->l_backend != NULL); - if (strcmp(newlun->l_backend, oldlun->l_backend) != 0) { - log_debugx("backend for lun \"%s\", " - "CTL lun %d changed; removing", - oldlun->l_name, oldlun->l_ctl_lun); - changed = 1; - } - if (oldlun->l_blocksize != newlun->l_blocksize) { - log_debugx("blocksize for lun \"%s\", " - "CTL lun %d changed; removing", - oldlun->l_name, oldlun->l_ctl_lun); - changed = 1; - } - if (newlun->l_device_id != NULL && - (oldlun->l_device_id == NULL || - strcmp(oldlun->l_device_id, newlun->l_device_id) != - 0)) { - log_debugx("device-id for lun \"%s\", " - "CTL lun %d changed; removing", - oldlun->l_name, oldlun->l_ctl_lun); - changed = 1; - } - if (newlun->l_path != NULL && - (oldlun->l_path == NULL || - strcmp(oldlun->l_path, newlun->l_path) != 0)) { - log_debugx("path for lun \"%s\", " - "CTL lun %d, changed; removing", - oldlun->l_name, oldlun->l_ctl_lun); - changed = 1; - } - if (newlun->l_serial != NULL && - (oldlun->l_serial == NULL || - strcmp(oldlun->l_serial, newlun->l_serial) != 0)) { - log_debugx("serial for lun \"%s\", " - "CTL lun %d changed; removing", - oldlun->l_name, oldlun->l_ctl_lun); - changed = 1; - } - if (changed) { - error = kernel_lun_remove(oldlun); - if (error != 0) { + struct lun *newlun = newit->second.get(); + if (oldlun->changed(*newlun)) { + if (!oldlun->kernel_remove()) { log_warnx("failed to remove lun \"%s\", " "CTL lun %d", - oldlun->l_name, oldlun->l_ctl_lun); + oldlun->name(), oldlun->ctl_lun()); cumulated_error++; } - lun_delete(oldlun); + + /* + * Delete the lun from the old configuration + * so it is added as a new LUN below. + */ + it = oldconf->conf_luns.erase(it); continue; } - newlun->l_ctl_lun = oldlun->l_ctl_lun; + newlun->set_ctl_lun(oldlun->ctl_lun()); + it++; } - TAILQ_FOREACH_SAFE(newlun, &newconf->conf_luns, l_next, tmplun) { - oldlun = lun_find(oldconf, newlun->l_name); - if (oldlun != NULL) { + for (auto it = conf_luns.begin(); it != conf_luns.end(); ) { + struct lun *newlun = it->second.get(); + + auto oldit = oldconf->conf_luns.find(it->first); + if (oldit != oldconf->conf_luns.end()) { log_debugx("modifying lun \"%s\", CTL lun %d", - newlun->l_name, newlun->l_ctl_lun); - error = kernel_lun_modify(newlun); - if (error != 0) { + newlun->name(), newlun->ctl_lun()); + if (!newlun->kernel_modify()) { log_warnx("failed to " "modify lun \"%s\", CTL lun %d", - newlun->l_name, newlun->l_ctl_lun); + newlun->name(), newlun->ctl_lun()); cumulated_error++; } + it++; continue; } - log_debugx("adding lun \"%s\"", newlun->l_name); - error = kernel_lun_add(newlun); - if (error != 0) { - log_warnx("failed to add lun \"%s\"", newlun->l_name); - lun_delete(newlun); + + log_debugx("adding lun \"%s\"", newlun->name()); + if (!newlun->kernel_add()) { + log_warnx("failed to add lun \"%s\"", newlun->name()); + delete_target_luns(newlun); + it = conf_luns.erase(it); cumulated_error++; - } + } else + it++; } /* * Now add new ports or modify existing ones. */ - TAILQ_FOREACH_SAFE(newport, &newconf->conf_ports, p_next, tmpport) { - if (port_is_dummy(newport)) + for (auto it = conf_ports.begin(); it != conf_ports.end(); ) { + const std::string &name = it->first; + port *newport = it->second.get(); + + if (newport->is_dummy()) { + it++; continue; - oldport = port_find(oldconf, newport->p_name); + } + const auto oldit = oldconf->conf_ports.find(name); + if (oldit == oldconf->conf_ports.end() || + oldit->second->is_dummy()) { + log_debugx("adding port \"%s\"", name.c_str()); + if (!newport->kernel_add()) { + log_warnx("failed to add port %s", + name.c_str()); + + /* + * XXX: Uncomment after fixing the + * root cause. + * + * cumulated_error++; + */ - if (oldport == NULL || port_is_dummy(oldport)) { - log_debugx("adding port \"%s\"", newport->p_name); - error = kernel_port_add(newport); + /* + * conf "owns" the port, but other + * objects contain pointers to this + * port that must be removed before + * deleting the port. + */ + newport->clear_references(); + it = conf_ports.erase(it); + } else + it++; } else { - log_debugx("updating port \"%s\"", newport->p_name); - newport->p_ctl_port = oldport->p_ctl_port; - error = kernel_port_update(newport, oldport); - } - if (error != 0) { - log_warnx("failed to %s port %s", - (oldport == NULL) ? "add" : "update", - newport->p_name); - if (oldport == NULL || port_is_dummy(oldport)) - port_delete(newport); - /* - * XXX: Uncomment after fixing the root cause. - * - * cumulated_error++; - */ + log_debugx("updating port \"%s\"", name.c_str()); + if (!newport->kernel_update(oldit->second.get())) + log_warnx("failed to update port %s", + name.c_str()); + it++; } } /* * Go through the new portals, opening the sockets as necessary. */ - TAILQ_FOREACH(newpg, &newconf->conf_portal_groups, pg_next) { - if (newpg->pg_foreign) - continue; - if (newpg->pg_unassigned) { - log_debugx("not listening on portal-group \"%s\", " - "not assigned to any target", - newpg->pg_name); - continue; - } - TAILQ_FOREACH(newp, &newpg->pg_portals, p_next) { - /* - * Try to find already open portal and reuse - * the listening socket. We don't care about - * what portal or portal group that was, what - * matters is the listening address. - */ - TAILQ_FOREACH(oldpg, &oldconf->conf_portal_groups, - pg_next) { - TAILQ_FOREACH(oldp, &oldpg->pg_portals, - p_next) { - if (portal_reuse_socket(oldp, newp)) - goto reused; - } - } - reused: - if (newp->p_socket > 0) { - /* - * We're done with this portal. - */ - continue; - } - -#ifdef ICL_KERNEL_PROXY - if (proxy_mode) { - newpg->pg_conf->conf_portal_id++; - newp->p_id = newpg->pg_conf->conf_portal_id; - log_debugx("listening on %s, portal-group " - "\"%s\", portal id %d, using ICL proxy", - newp->p_listen, newpg->pg_name, newp->p_id); - kernel_listen(newp->p_ai, newp->p_iser, - newp->p_id); - continue; - } -#endif - assert(proxy_mode == false); - assert(newp->p_iser == false); - - if (!portal_init_socket(newp)) { - cumulated_error++; - continue; - } - } + for (auto &kv : conf_portal_groups) { + cumulated_error += kv.second->open_sockets(*oldconf); + } + for (auto &kv : conf_transport_groups) { + cumulated_error += kv.second->open_sockets(*oldconf); } /* * Go through the no longer used sockets, closing them. */ - TAILQ_FOREACH(oldpg, &oldconf->conf_portal_groups, pg_next) { - TAILQ_FOREACH(oldp, &oldpg->pg_portals, p_next) { - if (oldp->p_socket <= 0) - continue; - log_debugx("closing socket for %s, portal-group \"%s\"", - oldp->p_listen, oldpg->pg_name); - close(oldp->p_socket); - oldp->p_socket = 0; - } + for (auto &kv : oldconf->conf_portal_groups) { + kv.second->close_sockets(); + } + for (auto &kv : oldconf->conf_transport_groups) { + kv.second->close_sockets(); } /* (Re-)Register on remaining/new iSNS servers. */ - TAILQ_FOREACH(newns, &newconf->conf_isns, i_next) { - TAILQ_FOREACH(oldns, &oldconf->conf_isns, i_next) { - if (strcmp(oldns->i_addr, newns->i_addr) == 0) - break; - } - isns_register(newns, oldns); + for (auto &kv : conf_isns) { + auto it = oldconf->conf_isns.find(kv.first); + if (it == oldconf->conf_isns.end()) + isns_register_targets(&kv.second, nullptr); + else + isns_register_targets(&kv.second, oldconf); } - /* Schedule iSNS update */ - if (!TAILQ_EMPTY(&newconf->conf_isns)) - set_timeout((newconf->conf_isns_period + 2) / 3, false); + isns_schedule_update(); return (cumulated_error); } -static bool +bool timed_out(void) { @@ -1975,19 +2239,28 @@ sigalrm_handler(int dummy __unused) } void -set_timeout(int timeout, int fatal) +stop_timer() +{ + struct itimerval itv; + int error; + + log_debugx("session timeout disabled"); + bzero(&itv, sizeof(itv)); + error = setitimer(ITIMER_REAL, &itv, NULL); + if (error != 0) + log_err(1, "setitimer"); + sigalrm_received = false; +} + +void +start_timer(int timeout, bool fatal) { struct sigaction sa; struct itimerval itv; int error; if (timeout <= 0) { - log_debugx("session timeout disabled"); - bzero(&itv, sizeof(itv)); - error = setitimer(ITIMER_REAL, &itv, NULL); - if (error != 0) - log_err(1, "setitimer"); - sigalrm_received = false; + stop_timer(); return; } @@ -2003,7 +2276,7 @@ set_timeout(int timeout, int fatal) log_err(1, "sigaction"); /* - * First SIGALRM will arive after conf_timeout seconds. + * First SIGALRM will arive after timeout seconds. * If we do nothing, another one will arrive a second later. */ log_debugx("setting session timeout to %d seconds", timeout); @@ -2048,16 +2321,17 @@ wait_for_children(bool block) } static void -handle_connection(struct portal *portal, int fd, +handle_connection(struct portal *portal, freebsd::fd_up fd, const struct sockaddr *client_sa, bool dont_fork) { - struct ctld_connection *conn; + struct portal_group *pg; int error; pid_t pid; char host[NI_MAXHOST + 1]; struct conf *conf; - conf = portal->p_portal_group->pg_conf; + pg = portal->portal_group(); + conf = pg->conf(); if (dont_fork) { log_debugx("incoming connection; not forking due to -d flag"); @@ -2065,9 +2339,10 @@ handle_connection(struct portal *portal, int fd, nchildren -= wait_for_children(false); assert(nchildren >= 0); - while (conf->conf_maxproc > 0 && nchildren >= conf->conf_maxproc) { + while (conf->maxproc() > 0 && nchildren >= conf->maxproc()) { log_debugx("maxproc limit of %d child processes hit; " - "waiting for child process to exit", conf->conf_maxproc); + "waiting for child process to exit", + conf->maxproc()); nchildren -= wait_for_children(true); assert(nchildren >= 0); } @@ -2077,11 +2352,9 @@ handle_connection(struct portal *portal, int fd, pid = fork(); if (pid < 0) log_err(1, "fork"); - if (pid > 0) { - close(fd); + if (pid > 0) return; - } - pidfile_close(conf->conf_pidfh); + conf->close_pidfile(); } error = getnameinfo(client_sa, client_sa->sa_len, @@ -2090,21 +2363,11 @@ handle_connection(struct portal *portal, int fd, log_errx(1, "getnameinfo: %s", gai_strerror(error)); log_debugx("accepted connection from %s; portal group \"%s\"", - host, portal->p_portal_group->pg_name); + host, pg->name()); log_set_peer_addr(host); setproctitle("%s", host); - conn = connection_new(portal, fd, host, client_sa); - set_timeout(conf->conf_timeout, true); - kernel_capsicate(); - login(conn); - if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { - kernel_handoff(conn); - log_debugx("connection handed off to the kernel"); - } else { - assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY); - discovery(conn); - } + portal->handle_connection(std::move(fd), host, client_sa); log_debugx("nothing more to do; exiting"); exit(0); } @@ -2135,18 +2398,12 @@ main_loop(bool dont_fork) log_debugx("incoming connection, id %d, portal id %d", connection_id, portal_id); - TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { - TAILQ_FOREACH(portal, &pg->pg_portals, p_next) { - if (portal->p_id == portal_id) { - goto found; - } - } - } - - log_errx(1, "kernel returned invalid portal_id %d", - portal_id); + portal = conf->proxy_portal(portal_id); + if (portal == nullptr) + log_errx(1, + "kernel returned invalid portal_id %d", + portal_id); -found: handle_connection(portal, connection_id, (struct sockaddr *)&client_sa, dont_fork); } else { @@ -2163,10 +2420,10 @@ found: switch (kev.filter) { case EVFILT_READ: portal = reinterpret_cast<struct portal *>(kev.udata); - assert(portal->p_socket == (int)kev.ident); + assert(portal->socket() == (int)kev.ident); client_salen = sizeof(client_sa); - client_fd = accept(portal->p_socket, + client_fd = accept(portal->socket(), (struct sockaddr *)&client_sa, &client_salen); if (client_fd < 0) { @@ -2270,33 +2527,35 @@ check_perms(const char *path) */ } -static struct conf * +static conf_up conf_new_from_file(const char *path, bool ucl) { - struct conf *conf; struct auth_group *ag; struct portal_group *pg; bool valid; log_debugx("obtaining configuration from %s", path); - conf = conf_new(); + conf_up conf = std::make_unique<struct conf>(); - ag = auth_group_new(conf, "default"); + ag = conf->add_auth_group("default"); assert(ag != NULL); - ag = auth_group_new(conf, "no-authentication"); + ag = conf->add_auth_group("no-authentication"); assert(ag != NULL); - ag->ag_type = AG_TYPE_NO_AUTHENTICATION; + ag->set_type(auth_type::NO_AUTHENTICATION); - ag = auth_group_new(conf, "no-access"); + ag = conf->add_auth_group("no-access"); assert(ag != NULL); - ag->ag_type = AG_TYPE_DENY; + ag->set_type(auth_type::DENY); - pg = portal_group_new(conf, "default"); + pg = conf->add_portal_group("default"); assert(pg != NULL); - conf_start(conf); + pg = conf->add_transport_group("default"); + assert(pg != NULL); + + conf_start(conf.get()); if (ucl) valid = uclparse_conf(path); else @@ -2304,34 +2563,39 @@ conf_new_from_file(const char *path, bool ucl) conf_finish(); if (!valid) { - conf_delete(conf); - return (NULL); + conf.reset(); + return {}; } check_perms(path); - if (conf->conf_default_ag_defined == false) { + if (!conf->default_auth_group_defined()) { log_debugx("auth-group \"default\" not defined; " "going with defaults"); - ag = auth_group_find(conf, "default"); + ag = conf->find_auth_group("default").get(); assert(ag != NULL); - ag->ag_type = AG_TYPE_DENY; + ag->set_type(auth_type::DENY); } - if (conf->conf_default_pg_defined == false) { + if (!conf->default_portal_group_defined()) { log_debugx("portal-group \"default\" not defined; " "going with defaults"); - pg = portal_group_find(conf, "default"); + pg = conf->find_portal_group("default"); assert(pg != NULL); - portal_group_add_portal(pg, "0.0.0.0", false); - portal_group_add_portal(pg, "[::]", false); + pg->add_default_portals(); } - conf->conf_kernel_port_on = true; + if (!conf->default_portal_group_defined()) { + log_debugx("transport-group \"default\" not defined; " + "going with defaults"); + pg = conf->find_transport_group("default"); + assert(pg != NULL); + pg->add_default_portals(); + } - if (!conf_verify(conf)) { - conf_delete(conf); - return (NULL); + if (!conf->verify()) { + conf.reset(); + return {}; } return (conf); @@ -2341,46 +2605,44 @@ conf_new_from_file(const char *path, bool ucl) * If the config file specifies physical ports for any target, associate them * with the config file. If necessary, create them. */ -static bool -new_pports_from_conf(struct conf *conf, struct kports *kports) +bool +conf::add_pports(struct kports &kports) { - struct target *targ; struct pport *pp; - struct port *tp; int ret, i_pp, i_vp; - TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { - if (!targ->t_pport) + for (auto &kv : conf_targets) { + struct target *targ = kv.second.get(); + + if (!targ->has_pport()) continue; - ret = sscanf(targ->t_pport, "ioctl/%d/%d", &i_pp, &i_vp); + ret = sscanf(targ->pport(), "ioctl/%d/%d", &i_pp, &i_vp); if (ret > 0) { - tp = port_new_ioctl(conf, kports, targ, i_pp, i_vp); - if (tp == NULL) { + if (!add_port(kports, targ, i_pp, i_vp)) { log_warnx("can't create new ioctl port " - "for target \"%s\"", targ->t_name); + "for %s", targ->label()); return (false); } continue; } - pp = pport_find(kports, targ->t_pport); + pp = kports.find_port(targ->pport()); if (pp == NULL) { - log_warnx("unknown port \"%s\" for target \"%s\"", - targ->t_pport, targ->t_name); + log_warnx("unknown port \"%s\" for %s", + targ->pport(), targ->label()); return (false); } - if (!TAILQ_EMPTY(&pp->pp_ports)) { - log_warnx("can't link port \"%s\" to target \"%s\", " + if (pp->linked()) { + log_warnx("can't link port \"%s\" to %s, " "port already linked to some target", - targ->t_pport, targ->t_name); + targ->pport(), targ->label()); return (false); } - tp = port_new_pp(conf, targ, pp); - if (tp == NULL) { - log_warnx("can't link port \"%s\" to target \"%s\"", - targ->t_pport, targ->t_name); + if (!add_port(targ, pp)) { + log_warnx("can't link port \"%s\" to %s", + targ->pport(), targ->label()); return (false); } } @@ -2391,11 +2653,8 @@ int main(int argc, char **argv) { struct kports kports; - struct conf *oldconf, *newconf, *tmpconf; - struct isns *newns; const char *config_path = DEFAULT_CONFIG_PATH; int debug = 0, ch, error; - pid_t otherpid; bool daemonize = true; bool test_config = false; bool use_ucl = false; @@ -2434,8 +2693,7 @@ main(int argc, char **argv) log_init(debug); kernel_init(); - TAILQ_INIT(&kports.pports); - newconf = conf_new_from_file(config_path, use_ucl); + conf_up newconf = conf_new_from_file(config_path, use_ucl); if (newconf == NULL) log_errx(1, "configuration error; exiting"); @@ -2443,85 +2701,69 @@ main(int argc, char **argv) if (test_config) return (0); - assert(newconf->conf_pidfile_path != NULL); - log_debugx("opening pidfile %s", newconf->conf_pidfile_path); - newconf->conf_pidfh = pidfile_open(newconf->conf_pidfile_path, 0600, - &otherpid); - if (newconf->conf_pidfh == NULL) { - if (errno == EEXIST) - log_errx(1, "daemon already running, pid: %jd.", - (intmax_t)otherpid); - log_err(1, "cannot open or create pidfile \"%s\"", - newconf->conf_pidfile_path); - } + newconf->open_pidfile(); register_signals(); - oldconf = conf_new_from_kernel(&kports); + conf_up oldconf = conf_new_from_kernel(kports); if (debug > 0) { - oldconf->conf_debug = debug; - newconf->conf_debug = debug; + oldconf->set_debug(debug); + newconf->set_debug(debug); } - if (!new_pports_from_conf(newconf, &kports)) + if (!newconf->add_pports(kports)) log_errx(1, "Error associating physical ports; exiting"); if (daemonize) { log_debugx("daemonizing"); if (daemon(0, 0) == -1) { log_warn("cannot daemonize"); - pidfile_remove(newconf->conf_pidfh); - exit(1); + return (1); } } kqfd = kqueue(); if (kqfd == -1) { log_warn("Cannot create kqueue"); - pidfile_remove(newconf->conf_pidfh); - exit(1); + return (1); } - error = conf_apply(oldconf, newconf); + error = newconf->apply(oldconf.get()); if (error != 0) log_errx(1, "failed to apply configuration; exiting"); - conf_delete(oldconf); - oldconf = NULL; + oldconf.reset(); - pidfile_write(newconf->conf_pidfh); + newconf->write_pidfile(); - /* Schedule iSNS update */ - if (!TAILQ_EMPTY(&newconf->conf_isns)) - set_timeout((newconf->conf_isns_period + 2) / 3, false); + newconf->isns_schedule_update(); for (;;) { main_loop(!daemonize); if (sighup_received) { sighup_received = false; log_debugx("received SIGHUP, reloading configuration"); - tmpconf = conf_new_from_file(config_path, use_ucl); + conf_up tmpconf = conf_new_from_file(config_path, + use_ucl); if (tmpconf == NULL) { log_warnx("configuration error, " "continuing with old configuration"); - } else if (!new_pports_from_conf(tmpconf, &kports)) { + } else if (!tmpconf->add_pports(kports)) { log_warnx("Error associating physical ports, " "continuing with old configuration"); - conf_delete(tmpconf); } else { if (debug > 0) - tmpconf->conf_debug = debug; - oldconf = newconf; - newconf = tmpconf; + tmpconf->set_debug(debug); + oldconf = std::move(newconf); + newconf = std::move(tmpconf); - error = conf_apply(oldconf, newconf); + error = newconf->apply(oldconf.get()); if (error != 0) log_warnx("failed to reload " "configuration"); - conf_delete(oldconf); - oldconf = NULL; + oldconf.reset(); } } else if (sigterm_received) { log_debugx("exiting on signal; " @@ -2530,36 +2772,22 @@ main(int argc, char **argv) log_debugx("removing CTL iSCSI ports " "and terminating all connections"); - oldconf = newconf; - newconf = conf_new(); + oldconf = std::move(newconf); + newconf = std::make_unique<conf>(); if (debug > 0) - newconf->conf_debug = debug; - error = conf_apply(oldconf, newconf); + newconf->set_debug(debug); + error = newconf->apply(oldconf.get()); if (error != 0) log_warnx("failed to apply configuration"); - if (oldconf->conf_pidfh) { - pidfile_remove(oldconf->conf_pidfh); - oldconf->conf_pidfh = NULL; - } - conf_delete(newconf); - conf_delete(oldconf); - oldconf = NULL; + oldconf.reset(); log_warnx("exiting on signal"); - exit(0); + return (0); } else { nchildren -= wait_for_children(false); assert(nchildren >= 0); if (timed_out()) { - set_timeout(0, false); - TAILQ_FOREACH(newns, &newconf->conf_isns, i_next) - isns_check(newns); - /* Schedule iSNS update */ - if (!TAILQ_EMPTY(&newconf->conf_isns)) { - set_timeout((newconf->conf_isns_period - + 2) / 3, - false); - } + newconf->isns_update(); } } } diff --git a/usr.sbin/ctld/ctld.h b/usr.sbin/ctld/ctld.h deleted file mode 100644 index 2cc9139fed1d..000000000000 --- a/usr.sbin/ctld/ctld.h +++ /dev/null @@ -1,362 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-2-Clause - * - * Copyright (c) 2012 The FreeBSD Foundation - * - * This software was developed by Edward Tomasz Napierala under sponsorship - * from the FreeBSD Foundation. - * - * 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 CTLD_H -#define CTLD_H - -#include <sys/_nv.h> -#include <sys/queue.h> -#ifdef ICL_KERNEL_PROXY -#include <sys/types.h> -#endif -#include <sys/socket.h> -#include <stdbool.h> -#include <libiscsiutil.h> -#include <libutil.h> - -#define DEFAULT_CONFIG_PATH "/etc/ctl.conf" -#define DEFAULT_PIDFILE "/var/run/ctld.pid" -#define DEFAULT_BLOCKSIZE 512 -#define DEFAULT_CD_BLOCKSIZE 2048 - -#define MAX_LUNS 1024 -#define SOCKBUF_SIZE 1048576 - -struct auth { - TAILQ_ENTRY(auth) a_next; - struct auth_group *a_auth_group; - char *a_user; - char *a_secret; - char *a_mutual_user; - char *a_mutual_secret; -}; - -struct auth_name { - TAILQ_ENTRY(auth_name) an_next; - struct auth_group *an_auth_group; - char *an_initiator_name; -}; - -struct auth_portal { - TAILQ_ENTRY(auth_portal) ap_next; - struct auth_group *ap_auth_group; - char *ap_initiator_portal; - struct sockaddr_storage ap_sa; - int ap_mask; -}; - -#define AG_TYPE_UNKNOWN 0 -#define AG_TYPE_DENY 1 -#define AG_TYPE_NO_AUTHENTICATION 2 -#define AG_TYPE_CHAP 3 -#define AG_TYPE_CHAP_MUTUAL 4 - -struct auth_group { - TAILQ_ENTRY(auth_group) ag_next; - struct conf *ag_conf; - char *ag_name; - char *ag_label; - int ag_type; - TAILQ_HEAD(, auth) ag_auths; - TAILQ_HEAD(, auth_name) ag_names; - TAILQ_HEAD(, auth_portal) ag_portals; -}; - -struct portal { - TAILQ_ENTRY(portal) p_next; - struct portal_group *p_portal_group; - bool p_iser; - char *p_listen; - struct addrinfo *p_ai; -#ifdef ICL_KERNEL_PROXY - int p_id; -#endif - - TAILQ_HEAD(, target) p_targets; - int p_socket; -}; - -#define PG_FILTER_UNKNOWN 0 -#define PG_FILTER_NONE 1 -#define PG_FILTER_PORTAL 2 -#define PG_FILTER_PORTAL_NAME 3 -#define PG_FILTER_PORTAL_NAME_AUTH 4 - -struct portal_group { - TAILQ_ENTRY(portal_group) pg_next; - struct conf *pg_conf; - nvlist_t *pg_options; - char *pg_name; - struct auth_group *pg_discovery_auth_group; - int pg_discovery_filter; - bool pg_foreign; - bool pg_unassigned; - TAILQ_HEAD(, portal) pg_portals; - TAILQ_HEAD(, port) pg_ports; - char *pg_offload; - char *pg_redirection; - int pg_dscp; - int pg_pcp; - - uint16_t pg_tag; -}; - -/* Ports created by the kernel. Perhaps the "p" means "physical" ? */ -struct pport { - TAILQ_ENTRY(pport) pp_next; - TAILQ_HEAD(, port) pp_ports; - struct kports *pp_kports; - char *pp_name; - - uint32_t pp_ctl_port; -}; - -struct port { - TAILQ_ENTRY(port) p_next; - TAILQ_ENTRY(port) p_pgs; - TAILQ_ENTRY(port) p_pps; - TAILQ_ENTRY(port) p_ts; - struct conf *p_conf; - char *p_name; - struct auth_group *p_auth_group; - struct portal_group *p_portal_group; - struct pport *p_pport; - struct target *p_target; - - bool p_ioctl_port; - int p_ioctl_pp; - int p_ioctl_vp; - uint32_t p_ctl_port; -}; - -struct lun { - TAILQ_ENTRY(lun) l_next; - struct conf *l_conf; - nvlist_t *l_options; - char *l_name; - char *l_backend; - uint8_t l_device_type; - int l_blocksize; - char *l_device_id; - char *l_path; - char *l_scsiname; - char *l_serial; - uint64_t l_size; - - int l_ctl_lun; -}; - -struct target { - TAILQ_ENTRY(target) t_next; - struct conf *t_conf; - struct lun *t_luns[MAX_LUNS]; - struct auth_group *t_auth_group; - TAILQ_HEAD(, port) t_ports; - char *t_name; - char *t_alias; - char *t_redirection; - /* Name of this target's physical port, if any, i.e. "isp0" */ - char *t_pport; -}; - -struct isns { - TAILQ_ENTRY(isns) i_next; - struct conf *i_conf; - char *i_addr; - struct addrinfo *i_ai; -}; - -struct conf { - char *conf_pidfile_path; - TAILQ_HEAD(, lun) conf_luns; - TAILQ_HEAD(, target) conf_targets; - TAILQ_HEAD(, auth_group) conf_auth_groups; - TAILQ_HEAD(, port) conf_ports; - TAILQ_HEAD(, portal_group) conf_portal_groups; - TAILQ_HEAD(, isns) conf_isns; - int conf_isns_period; - int conf_isns_timeout; - int conf_debug; - int conf_timeout; - int conf_maxproc; - -#ifdef ICL_KERNEL_PROXY - int conf_portal_id; -#endif - struct pidfh *conf_pidfh; - - bool conf_default_pg_defined; - bool conf_default_ag_defined; - bool conf_kernel_port_on; -}; - -/* Physical ports exposed by the kernel */ -struct kports { - TAILQ_HEAD(, pport) pports; -}; - -#define CONN_SESSION_TYPE_NONE 0 -#define CONN_SESSION_TYPE_DISCOVERY 1 -#define CONN_SESSION_TYPE_NORMAL 2 - -struct ctld_connection { - struct connection conn; - struct portal *conn_portal; - struct port *conn_port; - struct target *conn_target; - int conn_session_type; - char *conn_initiator_name; - char *conn_initiator_addr; - char *conn_initiator_alias; - uint8_t conn_initiator_isid[6]; - struct sockaddr_storage conn_initiator_sa; - int conn_max_recv_data_segment_limit; - int conn_max_send_data_segment_limit; - int conn_max_burst_limit; - int conn_first_burst_limit; - const char *conn_user; - struct chap *conn_chap; -}; - -extern int ctl_fd; - -bool uclparse_conf(const char *path); - -struct conf *conf_new(void); -struct conf *conf_new_from_kernel(struct kports *kports); -void conf_delete(struct conf *conf); -void conf_finish(void); -void conf_start(struct conf *new_conf); -bool conf_verify(struct conf *conf); - -struct auth_group *auth_group_new(struct conf *conf, const char *name); -struct auth_group *auth_group_new(struct conf *conf, - struct target *target); -void auth_group_delete(struct auth_group *ag); -struct auth_group *auth_group_find(const struct conf *conf, - const char *name); - -bool auth_new_chap(struct auth_group *ag, const char *user, - const char *secret); -bool auth_new_chap_mutual(struct auth_group *ag, - const char *user, const char *secret, - const char *user2, const char *secret2); -const struct auth *auth_find(const struct auth_group *ag, - const char *user); - -bool auth_name_new(struct auth_group *ag, - const char *initiator_name); -bool auth_name_defined(const struct auth_group *ag); -const struct auth_name *auth_name_find(const struct auth_group *ag, - const char *initiator_name); -bool auth_name_check(const struct auth_group *ag, - const char *initiator_name); - -bool auth_portal_new(struct auth_group *ag, - const char *initiator_portal); -bool auth_portal_defined(const struct auth_group *ag); -const struct auth_portal *auth_portal_find(const struct auth_group *ag, - const struct sockaddr_storage *sa); -bool auth_portal_check(const struct auth_group *ag, - const struct sockaddr_storage *sa); - -struct portal_group *portal_group_new(struct conf *conf, const char *name); -void portal_group_delete(struct portal_group *pg); -struct portal_group *portal_group_find(const struct conf *conf, - const char *name); -bool portal_group_add_portal(struct portal_group *pg, - const char *value, bool iser); - -bool isns_new(struct conf *conf, const char *addr); -void isns_delete(struct isns *is); -void isns_register(struct isns *isns, struct isns *oldisns); -void isns_check(struct isns *isns); -void isns_deregister(struct isns *isns); - -struct pport *pport_new(struct kports *kports, const char *name, - uint32_t ctl_port); -struct pport *pport_find(const struct kports *kports, - const char *name); -struct pport *pport_copy(struct pport *pp, struct kports *kports); -void pport_delete(struct pport *pport); - -struct port *port_new(struct conf *conf, struct target *target, - struct portal_group *pg); -struct port *port_new_ioctl(struct conf *conf, - struct kports *kports, struct target *target, - int pp, int vp); -struct port *port_new_pp(struct conf *conf, struct target *target, - struct pport *pp); -struct port *port_find(const struct conf *conf, const char *name); -struct port *port_find_in_pg(const struct portal_group *pg, - const char *target); -void port_delete(struct port *port); -bool port_is_dummy(struct port *port); - -struct target *target_new(struct conf *conf, const char *name); -void target_delete(struct target *target); -struct target *target_find(struct conf *conf, - const char *name); - -struct lun *lun_new(struct conf *conf, const char *name); -void lun_delete(struct lun *lun); -struct lun *lun_find(const struct conf *conf, const char *name); -void lun_set_scsiname(struct lun *lun, const char *value); - -bool option_new(nvlist_t *nvl, - const char *name, const char *value); - -void kernel_init(void); -int kernel_lun_add(struct lun *lun); -int kernel_lun_modify(struct lun *lun); -int kernel_lun_remove(struct lun *lun); -void kernel_handoff(struct ctld_connection *conn); -int kernel_port_add(struct port *port); -int kernel_port_update(struct port *port, struct port *old); -int kernel_port_remove(struct port *port); -void kernel_capsicate(void); - -#ifdef ICL_KERNEL_PROXY -void kernel_listen(struct addrinfo *ai, bool iser, - int portal_id); -void kernel_accept(int *connection_id, int *portal_id, - struct sockaddr *client_sa, - socklen_t *client_salen); -void kernel_send(struct pdu *pdu); -void kernel_receive(struct pdu *pdu); -#endif - -void login(struct ctld_connection *conn); - -void discovery(struct ctld_connection *conn); - -void set_timeout(int timeout, int fatal); - -#endif /* !CTLD_H */ diff --git a/usr.sbin/ctld/ctld.hh b/usr.sbin/ctld/ctld.hh new file mode 100644 index 000000000000..bfe4507bb3e6 --- /dev/null +++ b/usr.sbin/ctld/ctld.hh @@ -0,0 +1,637 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2012 The FreeBSD Foundation + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * 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 __CTLD_HH__ +#define __CTLD_HH__ + +#include <sys/_nv.h> +#include <sys/queue.h> +#ifdef ICL_KERNEL_PROXY +#include <sys/types.h> +#endif +#include <sys/socket.h> +#include <stdbool.h> +#include <libiscsiutil.h> +#include <libutil.h> + +#include <array> +#include <list> +#include <memory> +#include <string> +#include <string_view> +#include <unordered_map> +#include <unordered_set> +#include <libutil++.hh> + +#define DEFAULT_CONFIG_PATH "/etc/ctl.conf" +#define DEFAULT_PIDFILE "/var/run/ctld.pid" +#define DEFAULT_BLOCKSIZE 512 +#define DEFAULT_CD_BLOCKSIZE 2048 + +#define MAX_LUNS 1024 + +struct isns_req; +struct port; + +struct auth { + auth(std::string_view secret) : a_secret(secret) {} + auth(std::string_view secret, std::string_view mutual_user, + std::string_view mutual_secret) : + a_secret(secret), a_mutual_user(mutual_user), + a_mutual_secret(mutual_secret) {} + + bool mutual() const { return !a_mutual_user.empty(); } + + const char *secret() const { return a_secret.c_str(); } + const char *mutual_user() const { return a_mutual_user.c_str(); } + const char *mutual_secret() const { return a_mutual_secret.c_str(); } + +private: + std::string a_secret; + std::string a_mutual_user; + std::string a_mutual_secret; +}; + +struct auth_portal { + bool matches(const struct sockaddr *sa) const; + bool parse(const char *portal); + +private: + struct sockaddr_storage ap_sa; + int ap_mask = 0; +}; + +enum class auth_type { + UNKNOWN, + DENY, + NO_AUTHENTICATION, + CHAP, + CHAP_MUTUAL +}; + +struct auth_group { + auth_group(std::string label) : ag_label(label) {} + + auth_type type() const { return ag_type; } + bool set_type(const char *str); + void set_type(auth_type type); + + const char *label() const { return ag_label.c_str(); } + + bool add_chap(const char *user, const char *secret); + bool add_chap_mutual(const char *user, const char *secret, + const char *user2, const char *secret2); + const struct auth *find_auth(std::string_view user) const; + + bool add_host_nqn(std::string_view nqn); + bool host_permitted(std::string_view nqn) const; + + bool add_host_address(const char *address); + bool host_permitted(const struct sockaddr *sa) const; + + bool add_initiator_name(std::string_view initiator_name); + bool initiator_permitted(std::string_view initiator_name) const; + + bool add_initiator_portal(const char *initiator_portal); + bool initiator_permitted(const struct sockaddr *sa) const; + +private: + void check_secret_length(const char *user, const char *secret, + const char *secret_type); + + std::string ag_label; + auth_type ag_type = auth_type::UNKNOWN; + std::unordered_map<std::string, auth> ag_auths; + std::unordered_set<std::string> ag_host_names; + std::list<auth_portal> ag_host_addresses; + std::unordered_set<std::string> ag_initiator_names; + std::list<auth_portal> ag_initiator_portals; +}; + +using auth_group_sp = std::shared_ptr<auth_group>; + +enum class portal_protocol { + ISCSI, + ISER, + NVME_TCP, + NVME_DISCOVERY_TCP, +}; + +struct portal { + portal(struct portal_group *pg, std::string_view listen, + portal_protocol protocol, freebsd::addrinfo_up ai) : + p_portal_group(pg), p_listen(listen), p_ai(std::move(ai)), + p_protocol(protocol) {} + virtual ~portal() = default; + + bool reuse_socket(portal &oldp); + bool init_socket(); + virtual bool init_socket_options(int s __unused) { return true; } + virtual void handle_connection(freebsd::fd_up fd, const char *host, + const struct sockaddr *client_sa) = 0; + + struct portal_group *portal_group() const { return p_portal_group; } + const char *listen() const { return p_listen.c_str(); } + const addrinfo *ai() const { return p_ai.get(); } + portal_protocol protocol() const { return p_protocol; } + int socket() const { return p_socket; } + void close() { p_socket.reset(); } + +private: + struct portal_group *p_portal_group; + std::string p_listen; + freebsd::addrinfo_up p_ai; + portal_protocol p_protocol; + + freebsd::fd_up p_socket; +}; + +using portal_up = std::unique_ptr<portal>; +using port_up = std::unique_ptr<port>; + +enum class discovery_filter { + UNKNOWN, + NONE, + PORTAL, + PORTAL_NAME, + PORTAL_NAME_AUTH +}; + +struct portal_group { + portal_group(struct conf *conf, std::string_view name); + virtual ~portal_group() = default; + + struct conf *conf() const { return pg_conf; } + virtual const char *keyword() const = 0; + const char *name() const { return pg_name.c_str(); } + bool assigned() const { return pg_assigned; } + bool is_dummy() const; + bool is_redirecting() const { return !pg_redirection.empty(); } + struct auth_group *discovery_auth_group() const + { return pg_discovery_auth_group.get(); } + enum discovery_filter discovery_filter() const + { return pg_discovery_filter; } + int dscp() const { return pg_dscp; } + const char *offload() const { return pg_offload.c_str(); } + const char *redirection() const { return pg_redirection.c_str(); } + int pcp() const { return pg_pcp; } + uint16_t tag() const { return pg_tag; } + + freebsd::nvlist_up options() const; + + const std::list<portal_up> &portals() const { return pg_portals; } + const std::unordered_map<std::string, port *> &ports() const + { return pg_ports; } + + virtual void allocate_tag() = 0; + virtual bool add_portal(const char *value, + portal_protocol protocol) = 0; + virtual void add_default_portals() = 0; + bool add_option(const char *name, const char *value); + bool set_discovery_auth_group(const char *name); + bool set_dscp(u_int dscp); + virtual bool set_filter(const char *str) = 0; + void set_foreign(); + bool set_offload(const char *offload); + bool set_pcp(u_int pcp); + bool set_redirection(const char *addr); + void set_tag(uint16_t tag); + + virtual port_up create_port(struct target *target, auth_group_sp ag) = + 0; + virtual port_up create_port(struct target *target, uint32_t ctl_port) = + 0; + + void add_port(struct portal_group_port *port); + const struct port *find_port(std::string_view target) const; + void remove_port(struct portal_group_port *port); + void verify(struct conf *conf); + + bool reuse_socket(struct portal &newp); + int open_sockets(struct conf &oldconf); + void close_sockets(); + +protected: + struct conf *pg_conf; + freebsd::nvlist_up pg_options; + const char *pg_keyword; + std::string pg_name; + auth_group_sp pg_discovery_auth_group; + enum discovery_filter pg_discovery_filter = + discovery_filter::UNKNOWN; + bool pg_foreign = false; + bool pg_assigned = false; + std::list<portal_up> pg_portals; + std::unordered_map<std::string, port *> pg_ports; + std::string pg_offload; + std::string pg_redirection; + int pg_dscp = -1; + int pg_pcp = -1; + + uint16_t pg_tag = 0; +}; + +using portal_group_up = std::unique_ptr<portal_group>; + +struct port { + port(struct target *target); + virtual ~port() = default; + + struct target *target() const { return p_target; } + 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 void clear_references(); + + bool kernel_add(); + bool kernel_update(const port *oport); + bool kernel_remove(); + + virtual bool kernel_create_port() = 0; + virtual bool kernel_remove_port() = 0; + +protected: + struct target *p_target; + + uint32_t p_ctl_port = 0; +}; + +struct portal_group_port : public port { + portal_group_port(struct target *target, struct portal_group *pg, + auth_group_sp ag); + portal_group_port(struct target *target, struct portal_group *pg, + uint32_t ctl_port); + ~portal_group_port() override = default; + + struct auth_group *auth_group() const override + { return p_auth_group.get(); } + struct portal_group *portal_group() const override + { return p_portal_group; } + + bool is_dummy() const override; + + void clear_references() override; + +protected: + auth_group_sp p_auth_group; + struct portal_group *p_portal_group; +}; + +struct ioctl_port final : public port { + ioctl_port(struct target *target, int pp, int vp) : + port(target), p_ioctl_pp(pp), p_ioctl_vp(vp) {} + ~ioctl_port() override = default; + + bool kernel_create_port() override; + bool kernel_remove_port() override; + +private: + int p_ioctl_pp; + int p_ioctl_vp; +}; + +struct kernel_port final : public port { + kernel_port(struct target *target, struct pport *pp) : + port(target), p_pport(pp) {} + ~kernel_port() override = default; + + bool kernel_create_port() override; + bool kernel_remove_port() override; + +private: + struct pport *p_pport; +}; + +struct lun { + lun(struct conf *conf, std::string_view name); + + const char *name() const { return l_name.c_str(); } + const std::string &path() const { return l_path; } + int ctl_lun() const { return l_ctl_lun; } + + freebsd::nvlist_up options() const; + + bool add_option(const char *name, const char *value); + bool set_backend(std::string_view value); + bool set_blocksize(size_t value); + bool set_ctl_lun(uint32_t value); + bool set_device_type(uint8_t device_type); + bool set_device_type(const char *value); + bool set_device_id(std::string_view value); + bool set_path(std::string_view value); + void set_scsiname(std::string_view value); + bool set_serial(std::string_view value); + bool set_size(uint64_t value); + + bool changed(const struct lun &old) const; + bool verify(); + + bool kernel_add(); + bool kernel_modify() const; + bool kernel_remove() const; + +private: + struct conf *l_conf; + freebsd::nvlist_up l_options; + std::string l_name; + std::string l_backend; + uint8_t l_device_type = 0; + int l_blocksize = 0; + std::string l_device_id; + std::string l_path; + std::string l_scsiname; + std::string l_serial; + uint64_t l_size = 0; + + int l_ctl_lun = -1; +}; + +struct target { + target(struct conf *conf, const char *keyword, std::string_view name); + 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 struct lun *lun(int idx) const { return t_luns[idx]; } + + bool add_chap(const char *user, const char *secret); + bool add_chap_mutual(const char *user, const char *secret, + const char *user2, const char *secret2); + virtual bool add_host_address(const char *) { return false; } + virtual bool add_host_nqn(std::string_view) { return false; } + virtual bool add_initiator_name(std::string_view) { return false; } + 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; } + 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; } + + void add_port(struct port *port); + void remove_lun(struct lun *lun); + void remove_port(struct port *port); + void verify(); + +protected: + bool use_private_auth(const char *keyword); + bool add_lun(u_int id, const char *lun_label, const char *lun_name); + struct lun *start_lun(u_int id, const char *lun_label, + const char *lun_name); + virtual struct portal_group *default_portal_group() = 0; + + struct conf *t_conf; + std::array<struct lun *, MAX_LUNS> t_luns; + auth_group_sp t_auth_group; + std::list<port *> t_ports; + std::string t_name; + 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; + bool t_private_auth; +}; + +using target_up = std::unique_ptr<target>; + +struct isns { + isns(std::string_view addr, freebsd::addrinfo_up ai) : + i_addr(addr), i_ai(std::move(ai)) {} + + const char *addr() const { return i_addr.c_str(); } + + freebsd::fd_up connect(); + bool send_request(int s, struct isns_req req); + +private: + std::string i_addr; + freebsd::addrinfo_up i_ai; +}; + +struct conf { + conf(); + + int maxproc() const { return conf_maxproc; } + int timeout() const { return conf_timeout; } + uint32_t genctr() const { return conf_genctr; } + + bool default_auth_group_defined() const + { return conf_default_ag_defined; } + bool default_portal_group_defined() const + { return conf_default_pg_defined; } + bool default_transport_group_defined() const + { return conf_default_tg_defined; } + + struct auth_group *add_auth_group(const char *ag_name); + struct auth_group *define_default_auth_group(); + auth_group_sp find_auth_group(std::string_view ag_name); + + struct portal_group *add_portal_group(const char *name); + struct portal_group *define_default_portal_group(); + struct portal_group *find_portal_group(std::string_view name); + + struct portal_group *add_transport_group(const char *name); + struct portal_group *define_default_transport_group(); + struct portal_group *find_transport_group(std::string_view name); + + bool add_port(struct target *target, struct portal_group *pg, + auth_group_sp ag); + 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, + int vp); + bool add_pports(struct kports &kports); + + struct target *add_controller(const char *name); + struct target *find_controller(std::string_view name); + + struct target *add_target(const char *name); + struct target *find_target(std::string_view name); + + struct lun *add_lun(const char *name); + struct lun *find_lun(std::string_view name); + + void set_debug(int debug); + void set_isns_period(int period); + void set_isns_timeout(int timeout); + void set_maxproc(int maxproc); + 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); + bool verify(); + +private: + struct isns_req isns_register_request(const char *hostname); + struct isns_req isns_check_request(const char *hostname); + struct isns_req isns_deregister_request(const char *hostname); + void isns_check(struct isns *isns); + + std::string conf_pidfile_path; + std::unordered_map<std::string, std::unique_ptr<lun>> conf_luns; + std::unordered_map<std::string, target_up> conf_targets; + std::unordered_map<std::string, target_up> conf_controllers; + std::unordered_map<std::string, auth_group_sp> conf_auth_groups; + std::unordered_map<std::string, std::unique_ptr<port>> conf_ports; + std::unordered_map<std::string, portal_group_up> conf_portal_groups; + std::unordered_map<std::string, portal_group_up> conf_transport_groups; + std::unordered_map<std::string, isns> conf_isns; + struct target *conf_first_target = nullptr; + int conf_isns_period = 900; + int conf_isns_timeout = 5; + int conf_debug = 0; + int conf_timeout = 60; + 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; + + static uint32_t global_genctr; + +#ifdef ICL_KERNEL_PROXY +public: + int add_proxy_portal(portal *); + portal *proxy_portal(int); +private: + std::vector<portal *> conf_proxy_portals; +#endif +}; + +using conf_up = std::unique_ptr<conf>; + +/* Physical ports exposed by the kernel */ +struct pport { + pport(std::string_view name, uint32_t ctl_port) : pp_name(name), + pp_ctl_port(ctl_port) {} + + 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; +}; + +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); + +private: + std::unordered_map<std::string, struct pport> pports; +}; + +extern bool proxy_mode; +extern int ctl_fd; + +bool parse_conf(const char *path); +bool uclparse_conf(const char *path); + +conf_up conf_new_from_kernel(struct kports &kports); +void conf_finish(void); +void conf_start(struct conf *new_conf); + +bool option_new(nvlist_t *nvl, + const char *name, const char *value); + +freebsd::addrinfo_up parse_addr_port(const char *address, + const char *def_port); + +void kernel_init(void); +void kernel_capsicate(void); + +#ifdef ICL_KERNEL_PROXY +void kernel_listen(struct addrinfo *ai, bool iser, + int portal_id); +void kernel_accept(int *connection_id, int *portal_id, + struct sockaddr *client_sa, + socklen_t *client_salen); +void kernel_send(struct pdu *pdu); +void kernel_receive(struct pdu *pdu); +#endif + +bool ctl_create_port(const char *driver, + const nvlist_t *nvl, uint32_t *ctl_port); +bool ctl_remove_port(const char *driver, nvlist_t *nvl); + +portal_group_up iscsi_make_portal_group(struct conf *conf, + std::string_view name); +target_up iscsi_make_target(struct conf *conf, + std::string_view name); + +portal_group_up nvmf_make_transport_group(struct conf *conf, + std::string_view name); +target_up nvmf_make_controller(struct conf *conf, + std::string_view name); + +void start_timer(int timeout, bool fatal = false); +void stop_timer(); +bool timed_out(); + +#endif /* !__CTLD_HH__ */ diff --git a/usr.sbin/ctld/discovery.cc b/usr.sbin/ctld/discovery.cc index 3ae18786f1c2..8f6d371b696d 100644 --- a/usr.sbin/ctld/discovery.cc +++ b/usr.sbin/ctld/discovery.cc @@ -38,7 +38,8 @@ #include <netdb.h> #include <sys/socket.h> -#include "ctld.h" +#include "ctld.hh" +#include "iscsi.hh" #include "iscsi_proto.h" static struct pdu * @@ -101,19 +102,21 @@ logout_new_response(struct pdu *request) static void discovery_add_target(struct keys *response_keys, const struct target *targ) { - struct port *port; - struct portal *portal; char *buf; char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; - struct addrinfo *ai; + const struct addrinfo *ai; int ret; - keys_add(response_keys, "TargetName", targ->t_name); - TAILQ_FOREACH(port, &targ->t_ports, p_ts) { - if (port->p_portal_group == NULL) + keys_add(response_keys, "TargetName", targ->name()); + for (const port *port : targ->ports()) { + const struct portal_group *pg = port->portal_group(); + if (pg == nullptr) continue; - TAILQ_FOREACH(portal, &port->p_portal_group->pg_portals, p_next) { - ai = portal->p_ai; + for (const portal_up &portal : pg->portals()) { + if (portal->protocol() != portal_protocol::ISCSI && + portal->protocol() != portal_protocol::ISER) + continue; + ai = portal->ai(); ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV); @@ -126,13 +129,13 @@ discovery_add_target(struct keys *response_keys, const struct target *targ) if (strcmp(hbuf, "0.0.0.0") == 0) continue; ret = asprintf(&buf, "%s:%s,%d", hbuf, sbuf, - port->p_portal_group->pg_tag); + pg->tag()); break; case AF_INET6: if (strcmp(hbuf, "::") == 0) continue; ret = asprintf(&buf, "[%s]:%s,%d", hbuf, sbuf, - port->p_portal_group->pg_tag); + pg->tag()); break; default: continue; @@ -145,9 +148,8 @@ discovery_add_target(struct keys *response_keys, const struct target *targ) } } -static bool -discovery_target_filtered_out(const struct ctld_connection *conn, - const struct port *port) +bool +iscsi_connection::discovery_target_filtered_out(const struct port *port) const { const struct auth_group *ag; const struct portal_group *pg; @@ -155,52 +157,53 @@ discovery_target_filtered_out(const struct ctld_connection *conn, const struct auth *auth; int error; - targ = port->p_target; - ag = port->p_auth_group; - if (ag == NULL) - ag = targ->t_auth_group; - pg = conn->conn_portal->p_portal_group; + targ = port->target(); + ag = port->auth_group(); + if (ag == nullptr) + ag = targ->auth_group(); + pg = conn_portal->portal_group(); - assert(pg->pg_discovery_filter != PG_FILTER_UNKNOWN); + assert(pg->discovery_filter() != discovery_filter::UNKNOWN); - if (pg->pg_discovery_filter >= PG_FILTER_PORTAL && - !auth_portal_check(ag, &conn->conn_initiator_sa)) { + if (pg->discovery_filter() >= discovery_filter::PORTAL && + !ag->initiator_permitted(conn_initiator_sa)) { log_debugx("initiator does not match initiator portals " - "allowed for target \"%s\"; skipping", targ->t_name); + "allowed for target \"%s\"; skipping", targ->name()); return (true); } - if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME && - !auth_name_check(ag, conn->conn_initiator_name)) { + if (pg->discovery_filter() >= discovery_filter::PORTAL_NAME && + !ag->initiator_permitted(conn_initiator_name)) { log_debugx("initiator does not match initiator names " - "allowed for target \"%s\"; skipping", targ->t_name); + "allowed for target \"%s\"; skipping", targ->name()); return (true); } - if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME_AUTH && - ag->ag_type != AG_TYPE_NO_AUTHENTICATION) { - if (conn->conn_chap == NULL) { - assert(pg->pg_discovery_auth_group->ag_type == - AG_TYPE_NO_AUTHENTICATION); + if (pg->discovery_filter() >= discovery_filter::PORTAL_NAME_AUTH && + ag->type() != auth_type::NO_AUTHENTICATION) { + if (conn_chap == nullptr) { + assert(pg->discovery_auth_group()->type() == + auth_type::NO_AUTHENTICATION); log_debugx("initiator didn't authenticate, but target " - "\"%s\" requires CHAP; skipping", targ->t_name); + "\"%s\" requires CHAP; skipping", targ->name()); return (true); } - assert(conn->conn_user != NULL); - auth = auth_find(ag, conn->conn_user); + assert(!conn_user.empty()); + auth = ag->find_auth(conn_user); if (auth == NULL) { log_debugx("CHAP user \"%s\" doesn't match target " - "\"%s\"; skipping", conn->conn_user, targ->t_name); + "\"%s\"; skipping", conn_user.c_str(), + targ->name()); return (true); } - error = chap_authenticate(conn->conn_chap, auth->a_secret); + error = chap_authenticate(conn_chap, auth->secret()); if (error != 0) { log_debugx("password for CHAP user \"%s\" doesn't " "match target \"%s\"; skipping", - conn->conn_user, targ->t_name); + conn_user.c_str(), targ->name()); return (true); } } @@ -209,7 +212,7 @@ discovery_target_filtered_out(const struct ctld_connection *conn, } void -discovery(struct ctld_connection *conn) +iscsi_connection::discovery() { struct pdu *request, *response; struct keys *request_keys, *response_keys; @@ -217,10 +220,10 @@ discovery(struct ctld_connection *conn) const struct portal_group *pg; const char *send_targets; - pg = conn->conn_portal->p_portal_group; + pg = conn_portal->portal_group(); log_debugx("beginning discovery session; waiting for TextRequest PDU"); - request_keys = text_read_request(&conn->conn, &request); + request_keys = text_read_request(&conn, &request); send_targets = keys_find(request_keys, "SendTargets"); if (send_targets == NULL) @@ -229,23 +232,25 @@ discovery(struct ctld_connection *conn) response_keys = keys_new(); if (strcmp(send_targets, "All") == 0) { - TAILQ_FOREACH(port, &pg->pg_ports, p_pgs) { - if (discovery_target_filtered_out(conn, port)) { + for (const auto &kv : pg->ports()) { + port = kv.second; + if (discovery_target_filtered_out(port)) { /* Ignore this target. */ continue; } - discovery_add_target(response_keys, port->p_target); + discovery_add_target(response_keys, port->target()); } } else { - port = port_find_in_pg(pg, send_targets); + port = pg->find_port(send_targets); if (port == NULL) { log_debugx("initiator requested information on unknown " "target \"%s\"; returning nothing", send_targets); } else { - if (discovery_target_filtered_out(conn, port)) { + if (discovery_target_filtered_out(port)) { /* Ignore this target. */ } else { - discovery_add_target(response_keys, port->p_target); + discovery_add_target(response_keys, + port->target()); } } } @@ -256,7 +261,7 @@ discovery(struct ctld_connection *conn) keys_delete(request_keys); log_debugx("done sending targets; waiting for Logout PDU"); - request = logout_receive(&conn->conn); + request = logout_receive(&conn); response = logout_new_response(request); pdu_send(response); diff --git a/usr.sbin/ctld/iscsi.cc b/usr.sbin/ctld/iscsi.cc new file mode 100644 index 000000000000..bee036b95bf2 --- /dev/null +++ b/usr.sbin/ctld/iscsi.cc @@ -0,0 +1,508 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2003, 2004 Silicon Graphics International Corp. + * Copyright (c) 1997-2007 Kenneth D. Merry + * Copyright (c) 2012 The FreeBSD Foundation + * Copyright (c) 2017 Jakub Wojciech Klama <jceel@FreeBSD.org> + * All rights reserved. + * Copyright (c) 2025 Chelsio Communications, Inc. + * + * Portions of this software were developed by Edward Tomasz Napierala + * under sponsorship from the FreeBSD Foundation. + * + * 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, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. + * + */ + +#include <sys/param.h> +#include <sys/linker.h> +#include <sys/module.h> +#include <sys/time.h> +#include <assert.h> +#include <libiscsiutil.h> +#include <stdio.h> +#include <stdlib.h> +#include <cam/ctl/ctl.h> +#include <cam/ctl/ctl_io.h> +#include <cam/ctl/ctl_ioctl.h> + +#include "ctld.hh" +#include "iscsi.hh" + +#define SOCKBUF_SIZE 1048576 + +struct iscsi_portal final : public portal { + iscsi_portal(struct portal_group *pg, const char *listen, + portal_protocol protocol, freebsd::addrinfo_up ai) : + portal(pg, listen, protocol, std::move(ai)) {} + + bool init_socket_options(int s) override; + void handle_connection(freebsd::fd_up fd, const char *host, + const struct sockaddr *client_sa) override; +}; + +struct iscsi_portal_group final : public portal_group { + iscsi_portal_group(struct conf *conf, std::string_view name) : + portal_group(conf, name) {} + + const char *keyword() const override + { return "portal-group"; } + + void allocate_tag() override; + bool add_portal(const char *value, portal_protocol protocol) + override; + void add_default_portals() override; + bool set_filter(const char *str) override; + + virtual port_up create_port(struct target *target, auth_group_sp ag) + override; + virtual port_up create_port(struct target *target, uint32_t ctl_port) + override; +private: + static uint16_t last_portal_group_tag; +}; + +struct iscsi_port final : public portal_group_port { + iscsi_port(struct target *target, struct portal_group *pg, + auth_group_sp ag) : + portal_group_port(target, pg, ag) {} + iscsi_port(struct target *target, struct portal_group *pg, + uint32_t ctl_port) : + portal_group_port(target, pg, ctl_port) {} + + bool kernel_create_port() override; + bool kernel_remove_port() override; + +private: + static bool module_loaded; + static void load_kernel_module(); +}; + +struct iscsi_target final : public target { + iscsi_target(struct conf *conf, std::string_view name) : + target(conf, "target", name) {} + + bool add_initiator_name(std::string_view name) override; + bool add_initiator_portal(const char *addr) override; + bool add_lun(u_int id, const char *lun_name) override; + bool add_portal_group(const char *pg_name, const char *ag_name) + override; + struct lun *start_lun(u_int id) override; + +protected: + struct portal_group *default_portal_group() override; +}; + +#ifdef ICL_KERNEL_PROXY +static void pdu_receive_proxy(struct pdu *pdu); +static void pdu_send_proxy(struct pdu *pdu); +#endif /* ICL_KERNEL_PROXY */ +static void pdu_fail(const struct connection *conn, const char *reason); + +uint16_t iscsi_portal_group::last_portal_group_tag = 0xff; +bool iscsi_port::module_loaded = false; + +static struct connection_ops conn_ops = { + .timed_out = timed_out, +#ifdef ICL_KERNEL_PROXY + .pdu_receive_proxy = pdu_receive_proxy, + .pdu_send_proxy = pdu_send_proxy, +#else + .pdu_receive_proxy = nullptr, + .pdu_send_proxy = nullptr, +#endif + .fail = pdu_fail, +}; + +portal_group_up +iscsi_make_portal_group(struct conf *conf, std::string_view name) +{ + return std::make_unique<iscsi_portal_group>(conf, name); +} + +target_up +iscsi_make_target(struct conf *conf, std::string_view name) +{ + return std::make_unique<iscsi_target>(conf, name); +} + +void +iscsi_portal_group::allocate_tag() +{ + set_tag(++last_portal_group_tag); +} + +bool +iscsi_portal_group::add_portal(const char *value, portal_protocol protocol) +{ + switch (protocol) { + case portal_protocol::ISCSI: + case portal_protocol::ISER: + break; + default: + log_warnx("unsupported portal protocol for %s", value); + return (false); + } + + freebsd::addrinfo_up ai = parse_addr_port(value, "3260"); + if (!ai) { + log_warnx("invalid listen address %s", value); + return (false); + } + + /* + * XXX: getaddrinfo(3) may return multiple addresses; we should turn + * those into multiple portals. + */ + + pg_portals.emplace_back(std::make_unique<iscsi_portal>(this, value, + protocol, std::move(ai))); + return (true); +} + +void +iscsi_portal_group::add_default_portals() +{ + add_portal("0.0.0.0", portal_protocol::ISCSI); + add_portal("[::]", portal_protocol::ISCSI); +} + +bool +iscsi_portal_group::set_filter(const char *str) +{ + enum discovery_filter filter; + + if (strcmp(str, "none") == 0) { + filter = discovery_filter::NONE; + } else if (strcmp(str, "portal") == 0) { + filter = discovery_filter::PORTAL; + } else if (strcmp(str, "portal-name") == 0) { + filter = discovery_filter::PORTAL_NAME; + } else if (strcmp(str, "portal-name-auth") == 0) { + filter = discovery_filter::PORTAL_NAME_AUTH; + } else { + log_warnx("invalid discovery-filter \"%s\" for portal-group " + "\"%s\"; valid values are \"none\", \"portal\", " + "\"portal-name\", and \"portal-name-auth\"", + str, name()); + return (false); + } + + if (pg_discovery_filter != discovery_filter::UNKNOWN && + pg_discovery_filter != filter) { + log_warnx("cannot set discovery-filter to \"%s\" for " + "portal-group \"%s\"; already has a different " + "value", str, name()); + return (false); + } + + pg_discovery_filter = filter; + return (true); +} + +port_up +iscsi_portal_group::create_port(struct target *target, auth_group_sp ag) +{ + return std::make_unique<iscsi_port>(target, this, ag); +} + +port_up +iscsi_portal_group::create_port(struct target *target, uint32_t ctl_port) +{ + return std::make_unique<iscsi_port>(target, this, ctl_port); +} + +void +iscsi_port::load_kernel_module() +{ + int saved_errno; + + if (module_loaded) + return; + + saved_errno = errno; + if (modfind("cfiscsi") == -1 && kldload("cfiscsi") == -1) + log_warn("couldn't load cfiscsi"); + errno = saved_errno; + module_loaded = true; +} + +bool +iscsi_port::kernel_create_port() +{ + struct portal_group *pg = p_portal_group; + struct target *targ = p_target; + + load_kernel_module(); + + freebsd::nvlist_up nvl = pg->options(); + nvlist_add_string(nvl.get(), "cfiscsi_target", targ->name()); + nvlist_add_string(nvl.get(), "ctld_portal_group_name", pg->name()); + nvlist_add_stringf(nvl.get(), "cfiscsi_portal_group_tag", "%u", + pg->tag()); + + if (targ->has_alias()) { + nvlist_add_string(nvl.get(), "cfiscsi_target_alias", + targ->alias()); + } + + return (ctl_create_port("iscsi", nvl.get(), &p_ctl_port)); +} + +bool +iscsi_port::kernel_remove_port() +{ + freebsd::nvlist_up nvl(nvlist_create(0)); + nvlist_add_string(nvl.get(), "cfiscsi_target", p_target->name()); + nvlist_add_stringf(nvl.get(), "cfiscsi_portal_group_tag", "%u", + p_portal_group->tag()); + + return (ctl_remove_port("iscsi", nvl.get())); +} + +bool +iscsi_portal::init_socket_options(int s) +{ + int sockbuf; + + sockbuf = SOCKBUF_SIZE; + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sockbuf, + sizeof(sockbuf)) == -1) { + log_warn("setsockopt(SO_RCVBUF) failed for %s", listen()); + return (false); + } + sockbuf = SOCKBUF_SIZE; + if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sockbuf, + sizeof(sockbuf)) == -1) { + log_warn("setsockopt(SO_SNDBUF) failed for %s", listen()); + return (false); + } + return (true); +} + +bool +iscsi_target::add_initiator_name(std::string_view name) +{ + if (!use_private_auth("initiator-name")) + return (false); + return (t_auth_group->add_initiator_name(name)); +} + +bool +iscsi_target::add_initiator_portal(const char *addr) +{ + if (!use_private_auth("initiator-portal")) + return (false); + return (t_auth_group->add_initiator_portal(addr)); +} + +bool +iscsi_target::add_lun(u_int id, const char *lun_name) +{ + std::string lun_label = "LUN " + std::to_string(id); + return target::add_lun(id, lun_label.c_str(), lun_name); +} + +bool +iscsi_target::add_portal_group(const char *pg_name, const char *ag_name) +{ + struct portal_group *pg; + auth_group_sp ag; + + pg = t_conf->find_portal_group(pg_name); + if (pg == NULL) { + log_warnx("unknown portal-group \"%s\" for %s", pg_name, + label()); + return (false); + } + + if (ag_name != NULL) { + ag = t_conf->find_auth_group(ag_name); + if (ag == NULL) { + log_warnx("unknown auth-group \"%s\" for %s", ag_name, + label()); + return (false); + } + } + + if (!t_conf->add_port(this, pg, std::move(ag))) { + log_warnx("can't link portal-group \"%s\" to %s", pg_name, + label()); + return (false); + } + return (true); +} + +struct lun * +iscsi_target::start_lun(u_int id) +{ + std::string lun_label = "LUN " + std::to_string(id); + std::string lun_name = freebsd::stringf("%s,lun,%u", name(), id); + return target::start_lun(id, lun_label.c_str(), lun_name.c_str()); +} + +struct portal_group * +iscsi_target::default_portal_group() +{ + return t_conf->find_portal_group("default"); +} + +#ifdef ICL_KERNEL_PROXY + +static void +pdu_receive_proxy(struct pdu *pdu) +{ + struct connection *conn; + size_t len; + + assert(proxy_mode); + conn = pdu->pdu_connection; + + kernel_receive(pdu); + + len = pdu_ahs_length(pdu); + if (len > 0) + log_errx(1, "protocol error: non-empty AHS"); + + len = pdu_data_segment_length(pdu); + assert(len <= (size_t)conn->conn_max_recv_data_segment_length); + pdu->pdu_data_len = len; +} + +static void +pdu_send_proxy(struct pdu *pdu) +{ + + assert(proxy_mode); + + pdu_set_data_segment_length(pdu, pdu->pdu_data_len); + kernel_send(pdu); +} + +#endif /* ICL_KERNEL_PROXY */ + +static void +pdu_fail(const struct connection *conn __unused, const char *reason __unused) +{ +} + +iscsi_connection::iscsi_connection(struct portal *portal, freebsd::fd_up fd, + const char *host, const struct sockaddr *client_sa) : + conn_portal(portal), conn_fd(std::move(fd)), conn_initiator_addr(host), + conn_initiator_sa(client_sa) +{ + connection_init(&conn, &conn_ops, proxy_mode); + conn.conn_socket = conn_fd; +} + +iscsi_connection::~iscsi_connection() +{ + chap_delete(conn_chap); +} + +void +iscsi_connection::kernel_handoff() +{ + struct portal_group *pg = conn_portal->portal_group(); + struct ctl_iscsi req; + + bzero(&req, sizeof(req)); + + req.type = CTL_ISCSI_HANDOFF; + strlcpy(req.data.handoff.initiator_name, conn_initiator_name.c_str(), + sizeof(req.data.handoff.initiator_name)); + strlcpy(req.data.handoff.initiator_addr, conn_initiator_addr.c_str(), + sizeof(req.data.handoff.initiator_addr)); + if (!conn_initiator_alias.empty()) { + strlcpy(req.data.handoff.initiator_alias, + conn_initiator_alias.c_str(), + sizeof(req.data.handoff.initiator_alias)); + } + memcpy(req.data.handoff.initiator_isid, conn_initiator_isid, + sizeof(req.data.handoff.initiator_isid)); + strlcpy(req.data.handoff.target_name, conn_target->name(), + sizeof(req.data.handoff.target_name)); + strlcpy(req.data.handoff.offload, pg->offload(), + sizeof(req.data.handoff.offload)); +#ifdef ICL_KERNEL_PROXY + if (proxy_mode) + req.data.handoff.connection_id = conn.conn_socket; + else + req.data.handoff.socket = conn.conn_socket; +#else + req.data.handoff.socket = conn.conn_socket; +#endif + req.data.handoff.portal_group_tag = pg->tag(); + if (conn.conn_header_digest == CONN_DIGEST_CRC32C) + req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C; + if (conn.conn_data_digest == CONN_DIGEST_CRC32C) + req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C; + req.data.handoff.cmdsn = conn.conn_cmdsn; + req.data.handoff.statsn = conn.conn_statsn; + req.data.handoff.max_recv_data_segment_length = + conn.conn_max_recv_data_segment_length; + req.data.handoff.max_send_data_segment_length = + conn.conn_max_send_data_segment_length; + req.data.handoff.max_burst_length = conn.conn_max_burst_length; + req.data.handoff.first_burst_length = conn.conn_first_burst_length; + req.data.handoff.immediate_data = conn.conn_immediate_data; + + if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) { + log_err(1, "error issuing CTL_ISCSI ioctl; " + "dropping connection"); + } + + if (req.status != CTL_ISCSI_OK) { + log_errx(1, "error returned from CTL iSCSI handoff request: " + "%s; dropping connection", req.error_str); + } +} + +void +iscsi_connection::handle() +{ + login(); + if (conn_session_type == CONN_SESSION_TYPE_NORMAL) { + kernel_handoff(); + log_debugx("connection handed off to the kernel"); + } else { + assert(conn_session_type == CONN_SESSION_TYPE_DISCOVERY); + discovery(); + } +} + +void +iscsi_portal::handle_connection(freebsd::fd_up fd, const char *host, + const struct sockaddr *client_sa) +{ + struct conf *conf = portal_group()->conf(); + + iscsi_connection conn(this, std::move(fd), host, client_sa); + start_timer(conf->timeout(), true); + kernel_capsicate(); + conn.handle(); +} diff --git a/usr.sbin/ctld/iscsi.hh b/usr.sbin/ctld/iscsi.hh new file mode 100644 index 000000000000..d510e8c6731b --- /dev/null +++ b/usr.sbin/ctld/iscsi.hh @@ -0,0 +1,79 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2012 The FreeBSD Foundation + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * 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 __ISCSI_HH__ +#define __ISCSI_HH__ + +#define CONN_SESSION_TYPE_NONE 0 +#define CONN_SESSION_TYPE_DISCOVERY 1 +#define CONN_SESSION_TYPE_NORMAL 2 + +struct iscsi_connection { + iscsi_connection(struct portal *portal, freebsd::fd_up fd, + const char *host, const struct sockaddr *client_sa); + ~iscsi_connection(); + + void handle(); +private: + void login(); + void login_chap(struct auth_group *ag); + void login_negotiate_key(struct pdu *request, const char *name, + const char *value, bool skipped_security, + struct keys *response_keys); + bool login_portal_redirect(struct pdu *request); + bool login_target_redirect(struct pdu *request); + void login_negotiate(struct pdu *request); + void login_wait_transition(); + + void discovery(); + bool discovery_target_filtered_out(const struct port *port) const; + + void kernel_handoff(); + + struct connection conn; + struct portal *conn_portal = nullptr; + const struct port *conn_port = nullptr; + struct target *conn_target = nullptr; + freebsd::fd_up conn_fd; + int conn_session_type = CONN_SESSION_TYPE_NONE; + std::string conn_initiator_name; + std::string conn_initiator_addr; + std::string conn_initiator_alias; + uint8_t conn_initiator_isid[6]; + const struct sockaddr *conn_initiator_sa = nullptr; + int conn_max_recv_data_segment_limit = 0; + int conn_max_send_data_segment_limit = 0; + int conn_max_burst_limit = 0; + int conn_first_burst_limit = 0; + std::string conn_user; + struct chap *conn_chap = nullptr; +}; + +#endif /* !__ISCSI_HH__ */ diff --git a/usr.sbin/ctld/isns.cc b/usr.sbin/ctld/isns.cc index e4d3744b84dc..9877a4bc000d 100644 --- a/usr.sbin/ctld/isns.cc +++ b/usr.sbin/ctld/isns.cc @@ -27,7 +27,7 @@ * */ -#include <sys/types.h> +#include <sys/param.h> #include <sys/time.h> #include <sys/socket.h> #include <sys/wait.h> @@ -40,132 +40,90 @@ #include <string.h> #include <unistd.h> -#include "ctld.h" -#include "isns.h" +#include "ctld.hh" +#include "isns.hh" -struct isns_req * -isns_req_alloc(void) +isns_req::isns_req(uint16_t func, uint16_t flags, const char *descr) + : ir_descr(descr) { - struct isns_req *req; + struct isns_hdr hdr; - req = reinterpret_cast<struct isns_req *>(calloc(1, sizeof(struct isns_req))); - if (req == NULL) { - log_err(1, "calloc"); - return (NULL); - } - req->ir_buflen = sizeof(struct isns_hdr); - req->ir_usedlen = 0; - req->ir_buf = reinterpret_cast<uint8_t *>(calloc(1, req->ir_buflen)); - if (req->ir_buf == NULL) { - free(req); - log_err(1, "calloc"); - return (NULL); - } - return (req); -} - -struct isns_req * -isns_req_create(uint16_t func, uint16_t flags) -{ - struct isns_req *req; - struct isns_hdr *hdr; - - req = isns_req_alloc(); - req->ir_usedlen = sizeof(struct isns_hdr); - hdr = (struct isns_hdr *)req->ir_buf; - be16enc(hdr->ih_version, ISNS_VERSION); - be16enc(hdr->ih_function, func); - be16enc(hdr->ih_flags, flags); - return (req); + be16enc(&hdr.ih_version, ISNS_VERSION); + be16enc(&hdr.ih_function, func); + be16enc(&hdr.ih_flags, flags); + append(&hdr, sizeof(hdr)); } void -isns_req_free(struct isns_req *req) +isns_req::getspace(uint32_t len) { - - free(req->ir_buf); - free(req); + ir_buf.reserve(ir_buf.size() + len); } -static int -isns_req_getspace(struct isns_req *req, uint32_t len) +void +isns_req::append(const void *buf, size_t len) { - void *newbuf; - int newlen; - - if (req->ir_usedlen + len <= req->ir_buflen) - return (0); - newlen = 1 << flsl(req->ir_usedlen + len); - newbuf = realloc(req->ir_buf, newlen); - if (newbuf == NULL) { - log_err(1, "realloc"); - return (1); - } - req->ir_buf = reinterpret_cast<uint8_t *>(newbuf); - req->ir_buflen = newlen; - return (0); + const char *cp = reinterpret_cast<const char *>(buf); + ir_buf.insert(ir_buf.end(), cp, cp + len); } void -isns_req_add(struct isns_req *req, uint32_t tag, uint32_t len, - const void *value) +isns_req::add(uint32_t tag, uint32_t len, const void *value) { - struct isns_tlv *tlv; + struct isns_tlv tlv; uint32_t vlen; - vlen = len + ((len & 3) ? (4 - (len & 3)) : 0); - isns_req_getspace(req, sizeof(*tlv) + vlen); - tlv = (struct isns_tlv *)&req->ir_buf[req->ir_usedlen]; - be32enc(tlv->it_tag, tag); - be32enc(tlv->it_length, vlen); - memcpy(tlv->it_value, value, len); + vlen = roundup2(len, 4); + getspace(sizeof(tlv) + vlen); + be32enc(&tlv.it_tag, tag); + be32enc(&tlv.it_length, vlen); + append(&tlv, sizeof(tlv)); + append(value, len); if (vlen != len) - memset(&tlv->it_value[len], 0, vlen - len); - req->ir_usedlen += sizeof(*tlv) + vlen; + ir_buf.insert(ir_buf.end(), vlen - len, 0); } void -isns_req_add_delim(struct isns_req *req) +isns_req::add_delim() { - - isns_req_add(req, 0, 0, NULL); + add(0, 0, nullptr); } void -isns_req_add_str(struct isns_req *req, uint32_t tag, const char *value) +isns_req::add_str(uint32_t tag, const char *value) { - isns_req_add(req, tag, strlen(value) + 1, value); + add(tag, strlen(value) + 1, value); } void -isns_req_add_32(struct isns_req *req, uint32_t tag, uint32_t value) +isns_req::add_32(uint32_t tag, uint32_t value) { uint32_t beval; be32enc(&beval, value); - isns_req_add(req, tag, sizeof(value), &beval); + add(tag, sizeof(value), &beval); } void -isns_req_add_addr(struct isns_req *req, uint32_t tag, struct addrinfo *ai) +isns_req::add_addr(uint32_t tag, const struct addrinfo *ai) { - struct sockaddr_in *in4; - struct sockaddr_in6 *in6; + const struct sockaddr_in *in4; + const struct sockaddr_in6 *in6; uint8_t buf[16]; switch (ai->ai_addr->sa_family) { case AF_INET: - in4 = (struct sockaddr_in *)(void *)ai->ai_addr; + in4 = (const struct sockaddr_in *)ai->ai_addr; memset(buf, 0, 10); buf[10] = 0xff; buf[11] = 0xff; memcpy(&buf[12], &in4->sin_addr, sizeof(in4->sin_addr)); - isns_req_add(req, tag, sizeof(buf), buf); + add(tag, sizeof(buf), buf); break; case AF_INET6: - in6 = (struct sockaddr_in6 *)(void *)ai->ai_addr; - isns_req_add(req, tag, sizeof(in6->sin6_addr), &in6->sin6_addr); + in6 = (const struct sockaddr_in6 *)ai->ai_addr; + add(tag, sizeof(in6->sin6_addr), &in6->sin6_addr); break; default: log_errx(1, "Unsupported address family %d", @@ -174,22 +132,22 @@ isns_req_add_addr(struct isns_req *req, uint32_t tag, struct addrinfo *ai) } void -isns_req_add_port(struct isns_req *req, uint32_t tag, struct addrinfo *ai) +isns_req::add_port(uint32_t tag, const struct addrinfo *ai) { - struct sockaddr_in *in4; - struct sockaddr_in6 *in6; + const struct sockaddr_in *in4; + const struct sockaddr_in6 *in6; uint32_t buf; switch (ai->ai_addr->sa_family) { case AF_INET: - in4 = (struct sockaddr_in *)(void *)ai->ai_addr; + in4 = (const struct sockaddr_in *)ai->ai_addr; be32enc(&buf, ntohs(in4->sin_port)); - isns_req_add(req, tag, sizeof(buf), &buf); + add(tag, sizeof(buf), &buf); break; case AF_INET6: - in6 = (struct sockaddr_in6 *)(void *)ai->ai_addr; + in6 = (const struct sockaddr_in6 *)ai->ai_addr; be32enc(&buf, ntohs(in6->sin6_port)); - isns_req_add(req, tag, sizeof(buf), &buf); + add(tag, sizeof(buf), &buf); break; default: log_errx(1, "Unsupported address family %d", @@ -197,55 +155,54 @@ isns_req_add_port(struct isns_req *req, uint32_t tag, struct addrinfo *ai) } } -int -isns_req_send(int s, struct isns_req *req) +bool +isns_req::send(int s) { struct isns_hdr *hdr; int res; - hdr = (struct isns_hdr *)req->ir_buf; - be16enc(hdr->ih_length, req->ir_usedlen - sizeof(*hdr)); + hdr = (struct isns_hdr *)ir_buf.data(); + be16enc(hdr->ih_length, ir_buf.size() - sizeof(*hdr)); be16enc(hdr->ih_flags, be16dec(hdr->ih_flags) | ISNS_FLAG_LAST | ISNS_FLAG_FIRST); be16enc(hdr->ih_transaction, 0); be16enc(hdr->ih_sequence, 0); - res = write(s, req->ir_buf, req->ir_usedlen); - return ((res < 0) ? -1 : 0); + res = write(s, ir_buf.data(), ir_buf.size()); + return (res > 0 && (size_t)res == ir_buf.size()); } -int -isns_req_receive(int s, struct isns_req *req) +bool +isns_req::receive(int s) { struct isns_hdr *hdr; ssize_t res, len; - req->ir_usedlen = 0; - isns_req_getspace(req, sizeof(*hdr)); - res = read(s, req->ir_buf, sizeof(*hdr)); - if (res < (ssize_t)sizeof(*hdr)) - return (-1); - req->ir_usedlen = sizeof(*hdr); - hdr = (struct isns_hdr *)req->ir_buf; + ir_buf.resize(sizeof(*hdr)); + res = read(s, ir_buf.data(), sizeof(*hdr)); + if (res < (ssize_t)sizeof(*hdr)) { + ir_buf.clear(); + return (false); + } + hdr = (struct isns_hdr *)ir_buf.data(); if (be16dec(hdr->ih_version) != ISNS_VERSION) - return (-1); + return (false); if ((be16dec(hdr->ih_flags) & (ISNS_FLAG_LAST | ISNS_FLAG_FIRST)) != (ISNS_FLAG_LAST | ISNS_FLAG_FIRST)) - return (-1); + return (false); len = be16dec(hdr->ih_length); - isns_req_getspace(req, len); - res = read(s, &req->ir_buf[req->ir_usedlen], len); + ir_buf.resize(sizeof(*hdr) + len); + res = read(s, ir_buf.data() + sizeof(*hdr), len); if (res < len) - return (-1); - req->ir_usedlen += len; - return (0); + return (false); + return (res == len); } uint32_t -isns_req_get_status(struct isns_req *req) +isns_req::get_status() { - if (req->ir_usedlen < sizeof(struct isns_hdr) + 4) + if (ir_buf.size() < sizeof(struct isns_hdr) + 4) return (-1); - return (be32dec(&req->ir_buf[sizeof(struct isns_hdr)])); + return (be32dec(&ir_buf[sizeof(struct isns_hdr)])); } diff --git a/usr.sbin/ctld/isns.h b/usr.sbin/ctld/isns.hh index 70404b6f74e1..08a479626338 100644 --- a/usr.sbin/ctld/isns.h +++ b/usr.sbin/ctld/isns.hh @@ -24,8 +24,10 @@ * SUCH DAMAGE. */ -#ifndef _ISNS_H -#define _ISNS_H +#ifndef __ISNS_HH__ +#define __ISNS_HH__ + +#include <vector> #define ISNS_VERSION 0x0001 @@ -68,23 +70,26 @@ struct isns_tlv { }; struct isns_req { - u_int ir_buflen; - u_int ir_usedlen; - uint8_t *ir_buf; -}; + isns_req() {} + isns_req(uint16_t func, uint16_t flags, const char *descr); + + const char *descr() const { return ir_descr; } -struct isns_req * isns_req_alloc(void); -struct isns_req * isns_req_create(uint16_t func, uint16_t flags); -void isns_req_free(struct isns_req *req); -void isns_req_add(struct isns_req *req, uint32_t tag, uint32_t len, - const void *value); -void isns_req_add_delim(struct isns_req *req); -void isns_req_add_str(struct isns_req *req, uint32_t tag, const char *value); -void isns_req_add_32(struct isns_req *req, uint32_t tag, uint32_t value); -void isns_req_add_addr(struct isns_req *req, uint32_t tag, struct addrinfo *ai); -void isns_req_add_port(struct isns_req *req, uint32_t tag, struct addrinfo *ai); -int isns_req_send(int s, struct isns_req *req); -int isns_req_receive(int s, struct isns_req *req); -uint32_t isns_req_get_status(struct isns_req *req); + void add(uint32_t tag, uint32_t len, const void *value); + void add_delim(); + void add_str(uint32_t tag, const char *value); + void add_32(uint32_t tag, uint32_t value); + void add_addr(uint32_t tag, const struct addrinfo *ai); + void add_port(uint32_t tag, const struct addrinfo *ai); + bool send(int s); + bool receive(int s); + uint32_t get_status(); +private: + void getspace(uint32_t len); + void append(const void *buf, size_t len); + + std::vector<char> ir_buf; + const char *ir_descr; +}; -#endif /* _ISNS_H */ +#endif /* __ISNS_HH__ */ diff --git a/usr.sbin/ctld/kernel.cc b/usr.sbin/ctld/kernel.cc index 0cd0eaff6c6f..6b17ce60ac69 100644 --- a/usr.sbin/ctld/kernel.cc +++ b/usr.sbin/ctld/kernel.cc @@ -46,7 +46,6 @@ #include <sys/module.h> #include <sys/queue.h> #include <sys/sbuf.h> -#include <sys/nv.h> #include <sys/stat.h> #include <assert.h> #include <bsdxml.h> @@ -68,7 +67,7 @@ #include <cam/ctl/ctl_util.h> #include <cam/ctl/ctl_scsi_all.h> -#include "ctld.h" +#include "ctld.hh" #ifdef ICL_KERNEL_PROXY #include <netdb.h> @@ -76,8 +75,6 @@ #define NVLIST_BUFSIZE 1024 -extern bool proxy_mode; - int ctl_fd = 0; void @@ -109,42 +106,43 @@ kernel_init(void) /* * Backend LUN information. */ +using attr_list_t = std::list<std::pair<std::string, std::string>>; + struct cctl_lun { uint64_t lun_id; - char *backend_type; + std::string backend_type; uint8_t device_type; uint64_t size_blocks; uint32_t blocksize; - char *serial_number; - char *device_id; - char *ctld_name; - nvlist_t *attr_list; - STAILQ_ENTRY(cctl_lun) links; + std::string serial_number; + std::string device_id; + std::string ctld_name; + attr_list_t attr_list; }; struct cctl_port { uint32_t port_id; - char *port_frontend; - char *port_name; + std::string port_frontend; + std::string port_name; int pp; int vp; + uint16_t portid; int cfiscsi_state; - char *cfiscsi_target; + std::string cfiscsi_target; + std::string nqn; uint16_t cfiscsi_portal_group_tag; - char *ctld_portal_group_name; - nvlist_t *attr_list; - STAILQ_ENTRY(cctl_port) links; + std::string ctld_portal_group_name; + std::string ctld_transport_group_name; + attr_list_t attr_list; }; struct cctl_devlist_data { - int num_luns; - STAILQ_HEAD(,cctl_lun) lun_list; - struct cctl_lun *cur_lun; - int num_ports; - STAILQ_HEAD(,cctl_port) port_list; - struct cctl_port *cur_port; - int level; - struct sbuf *cur_sb[32]; + std::list<cctl_lun> lun_list; + struct cctl_lun *cur_lun = nullptr; + std::list<cctl_port> port_list; + struct cctl_port *cur_port = nullptr; + u_int level = 0; + struct sbuf *cur_sb[32] = {}; }; static void @@ -157,9 +155,8 @@ cctl_start_element(void *user_data, const char *name, const char **attr) devlist = (struct cctl_devlist_data *)user_data; cur_lun = devlist->cur_lun; devlist->level++; - if ((u_int)devlist->level >= (sizeof(devlist->cur_sb) / - sizeof(devlist->cur_sb[0]))) - log_errx(1, "%s: too many nesting levels, %zd max", __func__, + if (devlist->level >= nitems(devlist->cur_sb)) + log_errx(1, "%s: too many nesting levels, %zu max", __func__, nitems(devlist->cur_sb)); devlist->cur_sb[devlist->level] = sbuf_new_auto(); @@ -171,17 +168,11 @@ cctl_start_element(void *user_data, const char *name, const char **attr) log_errx(1, "%s: improper lun element nesting", __func__); - cur_lun = reinterpret_cast<struct cctl_lun *>(calloc(1, sizeof(*cur_lun))); - if (cur_lun == NULL) - log_err(1, "%s: cannot allocate %zd bytes", __func__, - sizeof(*cur_lun)); + devlist->lun_list.emplace_back(); + cur_lun = &devlist->lun_list.back(); - devlist->num_luns++; devlist->cur_lun = cur_lun; - cur_lun->attr_list = nvlist_create(0); - STAILQ_INSERT_TAIL(&devlist->lun_list, cur_lun, links); - for (i = 0; attr[i] != NULL; i += 2) { if (strcmp(attr[i], "id") == 0) { cur_lun->lun_id = strtoull(attr[i+1], NULL, 0); @@ -198,8 +189,7 @@ cctl_end_element(void *user_data, const char *name) { struct cctl_devlist_data *devlist; struct cctl_lun *cur_lun; - char *str; - int error; + std::string str; devlist = (struct cctl_devlist_data *)user_data; cur_lun = devlist->cur_lun; @@ -213,55 +203,39 @@ cctl_end_element(void *user_data, const char *name) devlist->level, name); sbuf_finish(devlist->cur_sb[devlist->level]); - str = checked_strdup(sbuf_data(devlist->cur_sb[devlist->level])); - - if (strlen(str) == 0) { - free(str); - str = NULL; - } + str = sbuf_data(devlist->cur_sb[devlist->level]); sbuf_delete(devlist->cur_sb[devlist->level]); devlist->cur_sb[devlist->level] = NULL; devlist->level--; if (strcmp(name, "backend_type") == 0) { - cur_lun->backend_type = str; - str = NULL; + cur_lun->backend_type = std::move(str); } else if (strcmp(name, "lun_type") == 0) { - if (str == NULL) + if (str.empty()) log_errx(1, "%s: %s missing its argument", __func__, name); - cur_lun->device_type = strtoull(str, NULL, 0); + cur_lun->device_type = strtoull(str.c_str(), NULL, 0); } else if (strcmp(name, "size") == 0) { - if (str == NULL) + if (str.empty()) log_errx(1, "%s: %s missing its argument", __func__, name); - cur_lun->size_blocks = strtoull(str, NULL, 0); + cur_lun->size_blocks = strtoull(str.c_str(), NULL, 0); } else if (strcmp(name, "blocksize") == 0) { - if (str == NULL) + if (str.empty()) log_errx(1, "%s: %s missing its argument", __func__, name); - cur_lun->blocksize = strtoul(str, NULL, 0); + cur_lun->blocksize = strtoul(str.c_str(), NULL, 0); } else if (strcmp(name, "serial_number") == 0) { - cur_lun->serial_number = str; - str = NULL; + cur_lun->serial_number = std::move(str); } else if (strcmp(name, "device_id") == 0) { - cur_lun->device_id = str; - str = NULL; + cur_lun->device_id = std::move(str); } else if (strcmp(name, "ctld_name") == 0) { - cur_lun->ctld_name = str; - str = NULL; + cur_lun->ctld_name = std::move(str); } else if (strcmp(name, "lun") == 0) { devlist->cur_lun = NULL; } else if (strcmp(name, "ctllunlist") == 0) { /* Nothing. */ } else { - nvlist_move_string(cur_lun->attr_list, name, str); - error = nvlist_error(cur_lun->attr_list); - if (error != 0) - log_errc(1, error, "%s: failed to add nv pair for %s", - __func__, name); - str = NULL; + cur_lun->attr_list.emplace_back(name, std::move(str)); } - - free(str); } static void @@ -274,9 +248,8 @@ cctl_start_pelement(void *user_data, const char *name, const char **attr) devlist = (struct cctl_devlist_data *)user_data; cur_port = devlist->cur_port; devlist->level++; - if ((u_int)devlist->level >= (sizeof(devlist->cur_sb) / - sizeof(devlist->cur_sb[0]))) - log_errx(1, "%s: too many nesting levels, %zd max", __func__, + if (devlist->level >= nitems(devlist->cur_sb)) + log_errx(1, "%s: too many nesting levels, %zu max", __func__, nitems(devlist->cur_sb)); devlist->cur_sb[devlist->level] = sbuf_new_auto(); @@ -288,17 +261,10 @@ cctl_start_pelement(void *user_data, const char *name, const char **attr) log_errx(1, "%s: improper port element nesting (%s)", __func__, name); - cur_port = reinterpret_cast<struct cctl_port *>(calloc(1, sizeof(*cur_port))); - if (cur_port == NULL) - log_err(1, "%s: cannot allocate %zd bytes", __func__, - sizeof(*cur_port)); - - devlist->num_ports++; + devlist->port_list.emplace_back(); + cur_port = &devlist->port_list.back(); devlist->cur_port = cur_port; - cur_port->attr_list = nvlist_create(0); - STAILQ_INSERT_TAIL(&devlist->port_list, cur_port, links); - for (i = 0; attr[i] != NULL; i += 2) { if (strcmp(attr[i], "id") == 0) { cur_port->port_id = strtoul(attr[i+1], NULL, 0); @@ -315,8 +281,7 @@ cctl_end_pelement(void *user_data, const char *name) { struct cctl_devlist_data *devlist; struct cctl_port *cur_port; - char *str; - int error; + std::string str; devlist = (struct cctl_devlist_data *)user_data; cur_port = devlist->cur_port; @@ -330,59 +295,51 @@ cctl_end_pelement(void *user_data, const char *name) devlist->level, name); sbuf_finish(devlist->cur_sb[devlist->level]); - str = checked_strdup(sbuf_data(devlist->cur_sb[devlist->level])); - - if (strlen(str) == 0) { - free(str); - str = NULL; - } + str = sbuf_data(devlist->cur_sb[devlist->level]); sbuf_delete(devlist->cur_sb[devlist->level]); devlist->cur_sb[devlist->level] = NULL; devlist->level--; if (strcmp(name, "frontend_type") == 0) { - cur_port->port_frontend = str; - str = NULL; + cur_port->port_frontend = std::move(str); } else if (strcmp(name, "port_name") == 0) { - cur_port->port_name = str; - str = NULL; + cur_port->port_name = std::move(str); } else if (strcmp(name, "physical_port") == 0) { - if (str == NULL) + if (str.empty()) log_errx(1, "%s: %s missing its argument", __func__, name); - cur_port->pp = strtoul(str, NULL, 0); + cur_port->pp = strtoul(str.c_str(), NULL, 0); } else if (strcmp(name, "virtual_port") == 0) { - if (str == NULL) + if (str.empty()) log_errx(1, "%s: %s missing its argument", __func__, name); - cur_port->vp = strtoul(str, NULL, 0); + cur_port->vp = strtoul(str.c_str(), NULL, 0); } else if (strcmp(name, "cfiscsi_target") == 0) { - cur_port->cfiscsi_target = str; - str = NULL; + cur_port->cfiscsi_target = std::move(str); } else if (strcmp(name, "cfiscsi_state") == 0) { - if (str == NULL) + if (str.empty()) log_errx(1, "%s: %s missing its argument", __func__, name); - cur_port->cfiscsi_state = strtoul(str, NULL, 0); + cur_port->cfiscsi_state = strtoul(str.c_str(), NULL, 0); } else if (strcmp(name, "cfiscsi_portal_group_tag") == 0) { - if (str == NULL) + if (str.empty()) log_errx(1, "%s: %s missing its argument", __func__, name); - cur_port->cfiscsi_portal_group_tag = strtoul(str, NULL, 0); + cur_port->cfiscsi_portal_group_tag = strtoul(str.c_str(), NULL, 0); } else if (strcmp(name, "ctld_portal_group_name") == 0) { - cur_port->ctld_portal_group_name = str; - str = NULL; + cur_port->ctld_portal_group_name = std::move(str); + } else if (strcmp(name, "ctld_transport_group_name") == 0) { + cur_port->ctld_transport_group_name = std::move(str); + } else if (strcmp(name, "nqn") == 0) { + cur_port->nqn = std::move(str); + } else if (strcmp(name, "portid") == 0) { + if (str.empty()) + log_errx(1, "%s: %s missing its argument", __func__, name); + cur_port->portid = strtoul(str.c_str(), NULL, 0); } else if (strcmp(name, "targ_port") == 0) { devlist->cur_port = NULL; } else if (strcmp(name, "ctlportlist") == 0) { /* Nothing. */ } else { - nvlist_move_string(cur_port->attr_list, name, str); - error = nvlist_error(cur_port->attr_list); - if (error != 0) - log_errc(1, error, "%s: failed to add nv pair for %s", - __func__, name); - str = NULL; + cur_port->attr_list.emplace_back(name, std::move(str)); } - - free(str); } static void @@ -395,329 +352,307 @@ cctl_char_handler(void *user_data, const XML_Char *str, int len) sbuf_bcat(devlist->cur_sb[devlist->level], str, len); } -struct conf * -conf_new_from_kernel(struct kports *kports) +static bool +parse_kernel_config(struct cctl_devlist_data &devlist) { - struct conf *conf = NULL; - struct target *targ; - struct portal_group *pg; - struct pport *pp; - struct port *cp; - struct lun *cl; struct ctl_lun_list list; - struct cctl_devlist_data devlist; - struct cctl_lun *lun; - struct cctl_port *port; XML_Parser parser; - const char *key; - char *str, *name; - void *cookie; - int error, len, retval; - - bzero(&devlist, sizeof(devlist)); - STAILQ_INIT(&devlist.lun_list); - STAILQ_INIT(&devlist.port_list); + int retval; - log_debugx("obtaining previously configured CTL luns from the kernel"); - - str = NULL; - len = 4096; + std::vector<char> buf(4096); retry: - str = reinterpret_cast<char *>(realloc(str, len)); - if (str == NULL) - log_err(1, "realloc"); - bzero(&list, sizeof(list)); - list.alloc_len = len; + list.alloc_len = buf.size(); list.status = CTL_LUN_LIST_NONE; - list.lun_xml = str; + list.lun_xml = buf.data(); if (ioctl(ctl_fd, CTL_LUN_LIST, &list) == -1) { log_warn("error issuing CTL_LUN_LIST ioctl"); - free(str); - return (NULL); + return (false); } if (list.status == CTL_LUN_LIST_ERROR) { log_warnx("error returned from CTL_LUN_LIST ioctl: %s", list.error_str); - free(str); - return (NULL); + return (false); } if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) { - len = len << 1; + buf.resize(buf.size() << 1); goto retry; } parser = XML_ParserCreate(NULL); if (parser == NULL) { log_warnx("unable to create XML parser"); - free(str); - return (NULL); + return (false); } XML_SetUserData(parser, &devlist); XML_SetElementHandler(parser, cctl_start_element, cctl_end_element); XML_SetCharacterDataHandler(parser, cctl_char_handler); - retval = XML_Parse(parser, str, strlen(str), 1); + retval = XML_Parse(parser, buf.data(), strlen(buf.data()), 1); XML_ParserFree(parser); - free(str); if (retval != 1) { log_warnx("XML_Parse failed"); - return (NULL); + return (false); } - str = NULL; - len = 4096; retry_port: - str = reinterpret_cast<char *>(realloc(str, len)); - if (str == NULL) - log_err(1, "realloc"); - bzero(&list, sizeof(list)); - list.alloc_len = len; + list.alloc_len = buf.size(); list.status = CTL_LUN_LIST_NONE; - list.lun_xml = str; + list.lun_xml = buf.data(); if (ioctl(ctl_fd, CTL_PORT_LIST, &list) == -1) { log_warn("error issuing CTL_PORT_LIST ioctl"); - free(str); - return (NULL); + return (false); } if (list.status == CTL_LUN_LIST_ERROR) { log_warnx("error returned from CTL_PORT_LIST ioctl: %s", list.error_str); - free(str); - return (NULL); + return (false); } if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) { - len = len << 1; + buf.resize(buf.size() << 1); goto retry_port; } parser = XML_ParserCreate(NULL); if (parser == NULL) { log_warnx("unable to create XML parser"); - free(str); - return (NULL); + return (false); } XML_SetUserData(parser, &devlist); XML_SetElementHandler(parser, cctl_start_pelement, cctl_end_pelement); XML_SetCharacterDataHandler(parser, cctl_char_handler); - retval = XML_Parse(parser, str, strlen(str), 1); + retval = XML_Parse(parser, buf.data(), strlen(buf.data()), 1); XML_ParserFree(parser); - free(str); if (retval != 1) { log_warnx("XML_Parse failed"); - return (NULL); + return (false); } - conf = conf_new(); + return (true); +} - name = NULL; - STAILQ_FOREACH(port, &devlist.port_list, links) { - if (strcmp(port->port_frontend, "ha") == 0) - continue; - free(name); - if (port->pp == 0 && port->vp == 0) { - name = checked_strdup(port->port_name); - } else if (port->vp == 0) { - retval = asprintf(&name, "%s/%d", - port->port_name, port->pp); - if (retval <= 0) - log_err(1, "asprintf"); - } else { - retval = asprintf(&name, "%s/%d/%d", - port->port_name, port->pp, port->vp); - if (retval <= 0) - log_err(1, "asprintf"); +void +add_iscsi_port(struct kports &kports, 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; + } } + return; + } + if (port.cfiscsi_state != 1) { + log_debugx("CTL port %ju is not active (%d); ignoring", + (uintmax_t)port.port_id, port.cfiscsi_state); + return; + } - if (port->cfiscsi_target == NULL) { - log_debugx("CTL port %u \"%s\" wasn't managed by ctld; ", - port->port_id, name); - pp = pport_find(kports, name); - if (pp == NULL) { - pp = pport_new(kports, name, port->port_id); - if (pp == NULL) { - log_warnx("pport_new failed"); - continue; - } - } - continue; + const char *t_name = port.cfiscsi_target.c_str(); + struct target *targ = conf->find_target(t_name); + if (targ == nullptr) { + targ = conf->add_target(t_name); + if (targ == nullptr) { + log_warnx("Failed to add target \"%s\"", t_name); + return; } - if (port->cfiscsi_state != 1) { - log_debugx("CTL port %ju is not active (%d); ignoring", - (uintmax_t)port->port_id, port->cfiscsi_state); - continue; + } + + if (port.ctld_portal_group_name.empty()) + return; + + const char *pg_name = port.ctld_portal_group_name.c_str(); + struct portal_group *pg = conf->find_portal_group(pg_name); + if (pg == nullptr) { + pg = conf->add_portal_group(pg_name); + if (pg == nullptr) { + log_warnx("Failed to add portal-group \"%s\"", pg_name); + return; } + } + pg->set_tag(port.cfiscsi_portal_group_tag); + if (!conf->add_port(targ, pg, port.port_id)) { + log_warnx("Failed to add port for target \"%s\" and portal-group \"%s\"", + t_name, pg_name); + } +} - targ = target_find(conf, port->cfiscsi_target); - if (targ == NULL) { - targ = target_new(conf, port->cfiscsi_target); - if (targ == NULL) { - log_warnx("target_new failed"); - continue; - } +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()); + return; + } + + const char *nqn = port.nqn.c_str(); + struct target *targ = conf->find_controller(nqn); + if (targ == nullptr) { + targ = conf->add_controller(nqn); + if (targ == nullptr) { + log_warnx("Failed to add controller \"%s\"", nqn); + return; } + } - if (port->ctld_portal_group_name == NULL) - continue; - pg = portal_group_find(conf, port->ctld_portal_group_name); - if (pg == NULL) { - pg = portal_group_new(conf, port->ctld_portal_group_name); - if (pg == NULL) { - log_warnx("portal_group_new failed"); - continue; - } + const char *tg_name = port.ctld_transport_group_name.c_str(); + struct portal_group *pg = conf->find_transport_group(tg_name); + if (pg == nullptr) { + pg = conf->add_transport_group(tg_name); + if (pg == nullptr) { + log_warnx("Failed to add transport-group \"%s\"", + tg_name); + return; } - pg->pg_tag = port->cfiscsi_portal_group_tag; - cp = port_new(conf, targ, pg); - if (cp == NULL) { - log_warnx("port_new failed"); + } + pg->set_tag(port.portid); + if (!conf->add_port(targ, pg, port.port_id)) { + log_warnx("Failed to add port for controller \"%s\" and transport-group \"%s\"", + nqn, tg_name); + } +} + +conf_up +conf_new_from_kernel(struct kports &kports) +{ + struct cctl_devlist_data devlist; + + log_debugx("obtaining previously configured CTL luns from the kernel"); + + if (!parse_kernel_config(devlist)) + return {}; + + conf_up conf = std::make_unique<struct conf>(); + + for (const auto &port : devlist.port_list) { + if (port.port_frontend == "ha") continue; + + std::string name = port.port_name; + if (port.pp != 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); + } 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); } - cp->p_ctl_port = port->port_id; - } - while ((port = STAILQ_FIRST(&devlist.port_list))) { - STAILQ_REMOVE_HEAD(&devlist.port_list, links); - free(port->port_frontend); - free(port->port_name); - free(port->cfiscsi_target); - free(port->ctld_portal_group_name); - nvlist_destroy(port->attr_list); - free(port); } - free(name); - STAILQ_FOREACH(lun, &devlist.lun_list, links) { - if (lun->ctld_name == NULL) { + for (const auto &lun : devlist.lun_list) { + if (lun.ctld_name.empty()) { log_debugx("CTL lun %ju wasn't managed by ctld; " - "ignoring", (uintmax_t)lun->lun_id); + "ignoring", (uintmax_t)lun.lun_id); continue; } - cl = lun_find(conf, lun->ctld_name); + const char *l_name = lun.ctld_name.c_str(); + struct lun *cl = conf->find_lun(l_name); if (cl != NULL) { log_warnx("found CTL lun %ju \"%s\", " "also backed by CTL lun %d; ignoring", - (uintmax_t)lun->lun_id, lun->ctld_name, - cl->l_ctl_lun); + (uintmax_t)lun.lun_id, l_name, + cl->ctl_lun()); continue; } log_debugx("found CTL lun %ju \"%s\"", - (uintmax_t)lun->lun_id, lun->ctld_name); + (uintmax_t)lun.lun_id, l_name); - cl = lun_new(conf, lun->ctld_name); + cl = conf->add_lun(l_name); if (cl == NULL) { log_warnx("lun_new failed"); continue; } - cl->l_backend = lun->backend_type; - lun->backend_type = NULL; - cl->l_device_type = lun->device_type; - cl->l_blocksize = lun->blocksize; - cl->l_device_id = lun->device_id; - lun->device_id = NULL; - cl->l_serial = lun->serial_number; - lun->serial_number = NULL; - cl->l_size = lun->size_blocks * cl->l_blocksize; - cl->l_ctl_lun = lun->lun_id; - - cookie = NULL; - while ((key = nvlist_next(lun->attr_list, NULL, &cookie)) != - NULL) { - if (strcmp(key, "file") == 0 || - strcmp(key, "dev") == 0) { - cl->l_path = checked_strdup( - cnvlist_get_string(cookie)); + cl->set_backend(lun.backend_type.c_str()); + cl->set_device_type(lun.device_type); + cl->set_blocksize(lun.blocksize); + cl->set_device_id(lun.device_id.c_str()); + cl->set_serial(lun.serial_number.c_str()); + cl->set_size(lun.size_blocks * lun.blocksize); + cl->set_ctl_lun(lun.lun_id); + + for (const auto &pair : lun.attr_list) { + const char *key = pair.first.c_str(); + const char *value = pair.second.c_str(); + if (pair.first == "file" || pair.first == "dev") { + cl->set_path(value); continue; } - nvlist_add_string(cl->l_options, key, - cnvlist_get_string(cookie)); - error = nvlist_error(cl->l_options); - if (error != 0) - log_warnc(error, "unable to add CTL lun option " + if (!cl->add_option(key, value)) + log_warnx("unable to add CTL lun option " "%s for CTL lun %ju \"%s\"", - key, (uintmax_t)lun->lun_id, - cl->l_name); + key, (uintmax_t)lun.lun_id, + cl->name()); } } - while ((lun = STAILQ_FIRST(&devlist.lun_list))) { - STAILQ_REMOVE_HEAD(&devlist.lun_list, links); - nvlist_destroy(lun->attr_list); - free(lun); - } return (conf); } -static void -nvlist_replace_string(nvlist_t *nvl, const char *name, const char *value) -{ - if (nvlist_exists_string(nvl, name)) - nvlist_free_string(nvl, name); - nvlist_add_string(nvl, name, value); -} - -int -kernel_lun_add(struct lun *lun) +bool +lun::kernel_add() { struct ctl_lun_req req; int error; bzero(&req, sizeof(req)); - strlcpy(req.backend, lun->l_backend, sizeof(req.backend)); + strlcpy(req.backend, l_backend.c_str(), sizeof(req.backend)); req.reqtype = CTL_LUNREQ_CREATE; - req.reqdata.create.blocksize_bytes = lun->l_blocksize; + req.reqdata.create.blocksize_bytes = l_blocksize; - if (lun->l_size != 0) - req.reqdata.create.lun_size_bytes = lun->l_size; + if (l_size != 0) + req.reqdata.create.lun_size_bytes = l_size; - if (lun->l_ctl_lun >= 0) { - req.reqdata.create.req_lun_id = lun->l_ctl_lun; + if (l_ctl_lun >= 0) { + req.reqdata.create.req_lun_id = l_ctl_lun; req.reqdata.create.flags |= CTL_LUN_FLAG_ID_REQ; } req.reqdata.create.flags |= CTL_LUN_FLAG_DEV_TYPE; - req.reqdata.create.device_type = lun->l_device_type; + req.reqdata.create.device_type = l_device_type; - if (lun->l_serial != NULL) { - strncpy((char *)req.reqdata.create.serial_num, lun->l_serial, + if (!l_serial.empty()) { + strncpy((char *)req.reqdata.create.serial_num, l_serial.c_str(), sizeof(req.reqdata.create.serial_num)); req.reqdata.create.flags |= CTL_LUN_FLAG_SERIAL_NUM; } - if (lun->l_device_id != NULL) { - strncpy((char *)req.reqdata.create.device_id, lun->l_device_id, - sizeof(req.reqdata.create.device_id)); + if (!l_device_id.empty()) { + strncpy((char *)req.reqdata.create.device_id, + l_device_id.c_str(), sizeof(req.reqdata.create.device_id)); req.reqdata.create.flags |= CTL_LUN_FLAG_DEVID; } - if (lun->l_path != NULL) - nvlist_replace_string(lun->l_options, "file", lun->l_path); - - nvlist_replace_string(lun->l_options, "ctld_name", lun->l_name); - - if (!nvlist_exists_string(lun->l_options, "scsiname") && - lun->l_scsiname != NULL) - nvlist_add_string(lun->l_options, "scsiname", lun->l_scsiname); - - if (!nvlist_empty(lun->l_options)) { - req.args = nvlist_pack(lun->l_options, &req.args_len); - if (req.args == NULL) { - log_warn("error packing nvlist"); - return (1); - } + freebsd::nvlist_up nvl = options(); + req.args = nvlist_pack(nvl.get(), &req.args_len); + if (req.args == NULL) { + log_warn("error packing nvlist"); + return (false); } error = ioctl(ctl_fd, CTL_LUN_REQ, &req); @@ -725,13 +660,13 @@ kernel_lun_add(struct lun *lun) if (error != 0) { log_warn("error issuing CTL_LUN_REQ ioctl"); - return (1); + return (false); } switch (req.status) { case CTL_LUN_ERROR: log_warnx("LUN creation error: %s", req.error_str); - return (1); + return (false); case CTL_LUN_WARNING: log_warnx("LUN creation warning: %s", req.error_str); break; @@ -740,42 +675,32 @@ kernel_lun_add(struct lun *lun) default: log_warnx("unknown LUN creation status: %d", req.status); - return (1); + return (false); } - lun->l_ctl_lun = req.reqdata.create.req_lun_id; - return (0); + l_ctl_lun = req.reqdata.create.req_lun_id; + return (true); } -int -kernel_lun_modify(struct lun *lun) +bool +lun::kernel_modify() const { struct ctl_lun_req req; int error; bzero(&req, sizeof(req)); - strlcpy(req.backend, lun->l_backend, sizeof(req.backend)); + strlcpy(req.backend, l_backend.c_str(), sizeof(req.backend)); req.reqtype = CTL_LUNREQ_MODIFY; - req.reqdata.modify.lun_id = lun->l_ctl_lun; - req.reqdata.modify.lun_size_bytes = lun->l_size; - - if (lun->l_path != NULL) - nvlist_replace_string(lun->l_options, "file", lun->l_path); - - nvlist_replace_string(lun->l_options, "ctld_name", lun->l_name); + req.reqdata.modify.lun_id = l_ctl_lun; + req.reqdata.modify.lun_size_bytes = l_size; - if (!nvlist_exists_string(lun->l_options, "scsiname") && - lun->l_scsiname != NULL) - nvlist_add_string(lun->l_options, "scsiname", lun->l_scsiname); - - if (!nvlist_empty(lun->l_options)) { - req.args = nvlist_pack(lun->l_options, &req.args_len); - if (req.args == NULL) { - log_warn("error packing nvlist"); - return (1); - } + freebsd::nvlist_up nvl = options(); + req.args = nvlist_pack(nvl.get(), &req.args_len); + if (req.args == NULL) { + log_warn("error packing nvlist"); + return (false); } error = ioctl(ctl_fd, CTL_LUN_REQ, &req); @@ -783,13 +708,13 @@ kernel_lun_modify(struct lun *lun) if (error != 0) { log_warn("error issuing CTL_LUN_REQ ioctl"); - return (1); + return (false); } switch (req.status) { case CTL_LUN_ERROR: log_warnx("LUN modification error: %s", req.error_str); - return (1); + return (false); case CTL_LUN_WARNING: log_warnx("LUN modification warning: %s", req.error_str); break; @@ -798,33 +723,33 @@ kernel_lun_modify(struct lun *lun) default: log_warnx("unknown LUN modification status: %d", req.status); - return (1); + return (false); } - return (0); + return (true); } -int -kernel_lun_remove(struct lun *lun) +bool +lun::kernel_remove() const { struct ctl_lun_req req; bzero(&req, sizeof(req)); - strlcpy(req.backend, lun->l_backend, sizeof(req.backend)); + strlcpy(req.backend, l_backend.c_str(), sizeof(req.backend)); req.reqtype = CTL_LUNREQ_RM; - req.reqdata.rm.lun_id = lun->l_ctl_lun; + req.reqdata.rm.lun_id = l_ctl_lun; if (ioctl(ctl_fd, CTL_LUN_REQ, &req) == -1) { log_warn("error issuing CTL_LUN_REQ ioctl"); - return (1); + return (false); } switch (req.status) { case CTL_LUN_ERROR: log_warnx("LUN removal error: %s", req.error_str); - return (1); + return (false); case CTL_LUN_WARNING: log_warnx("LUN removal warning: %s", req.error_str); break; @@ -832,167 +757,104 @@ kernel_lun_remove(struct lun *lun) break; default: log_warnx("unknown LUN removal status: %d", req.status); - return (1); + return (false); } - return (0); + return (true); } -void -kernel_handoff(struct ctld_connection *conn) +bool +ctl_create_port(const char *driver, const nvlist_t *nvl, uint32_t *ctl_port) { - struct ctl_iscsi req; + struct ctl_req req; + char result_buf[NVLIST_BUFSIZE]; + int error; bzero(&req, sizeof(req)); + req.reqtype = CTL_REQ_CREATE; - req.type = CTL_ISCSI_HANDOFF; - strlcpy(req.data.handoff.initiator_name, - conn->conn_initiator_name, sizeof(req.data.handoff.initiator_name)); - strlcpy(req.data.handoff.initiator_addr, - conn->conn_initiator_addr, sizeof(req.data.handoff.initiator_addr)); - if (conn->conn_initiator_alias != NULL) { - strlcpy(req.data.handoff.initiator_alias, - conn->conn_initiator_alias, sizeof(req.data.handoff.initiator_alias)); + strlcpy(req.driver, driver, sizeof(req.driver)); + req.args = nvlist_pack(nvl, &req.args_len); + if (req.args == NULL) { + log_warn("error packing nvlist"); + return (false); } - memcpy(req.data.handoff.initiator_isid, conn->conn_initiator_isid, - sizeof(req.data.handoff.initiator_isid)); - strlcpy(req.data.handoff.target_name, - conn->conn_target->t_name, sizeof(req.data.handoff.target_name)); - if (conn->conn_portal->p_portal_group->pg_offload != NULL) { - strlcpy(req.data.handoff.offload, - conn->conn_portal->p_portal_group->pg_offload, - sizeof(req.data.handoff.offload)); + + req.result = result_buf; + req.result_len = sizeof(result_buf); + error = ioctl(ctl_fd, CTL_PORT_REQ, &req); + free(req.args); + + if (error != 0) { + log_warn("error issuing CTL_PORT_REQ ioctl"); + return (false); + } + if (req.status == CTL_LUN_ERROR) { + log_warnx("error returned from port creation request: %s", + req.error_str); + return (false); + } + if (req.status != CTL_LUN_OK) { + log_warnx("unknown port creation request status %d", + req.status); + return (false); } -#ifdef ICL_KERNEL_PROXY - if (proxy_mode) - req.data.handoff.connection_id = conn->conn.conn_socket; - else - req.data.handoff.socket = conn->conn.conn_socket; -#else - req.data.handoff.socket = conn->conn.conn_socket; -#endif - req.data.handoff.portal_group_tag = - conn->conn_portal->p_portal_group->pg_tag; - if (conn->conn.conn_header_digest == CONN_DIGEST_CRC32C) - req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C; - if (conn->conn.conn_data_digest == CONN_DIGEST_CRC32C) - req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C; - req.data.handoff.cmdsn = conn->conn.conn_cmdsn; - req.data.handoff.statsn = conn->conn.conn_statsn; - req.data.handoff.max_recv_data_segment_length = - conn->conn.conn_max_recv_data_segment_length; - req.data.handoff.max_send_data_segment_length = - conn->conn.conn_max_send_data_segment_length; - req.data.handoff.max_burst_length = conn->conn.conn_max_burst_length; - req.data.handoff.first_burst_length = - conn->conn.conn_first_burst_length; - req.data.handoff.immediate_data = conn->conn.conn_immediate_data; - if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) { - log_err(1, "error issuing CTL_ISCSI ioctl; " - "dropping connection"); + freebsd::nvlist_up result_nvl(nvlist_unpack(result_buf, req.result_len, + 0)); + if (result_nvl == NULL) { + log_warnx("error unpacking result nvlist"); + return (false); } - if (req.status != CTL_ISCSI_OK) { - log_errx(1, "error returned from CTL iSCSI handoff request: " - "%s; dropping connection", req.error_str); + *ctl_port = nvlist_get_number(result_nvl.get(), "port_id"); + return (true); +} + +bool +ioctl_port::kernel_create_port() +{ + freebsd::nvlist_up nvl(nvlist_create(0)); + nvlist_add_stringf(nvl.get(), "pp", "%d", p_ioctl_pp); + nvlist_add_stringf(nvl.get(), "vp", "%d", p_ioctl_vp); + + return (ctl_create_port("ioctl", nvl.get(), &p_ctl_port)); +} + +bool +kernel_port::kernel_create_port() +{ + struct ctl_port_entry entry; + struct target *targ = p_target; + + p_ctl_port = p_pport->ctl_port(); + + if (strncmp(targ->name(), "naa.", 4) == 0 && + strlen(targ->name()) == 20) { + bzero(&entry, sizeof(entry)); + entry.port_type = CTL_PORT_NONE; + entry.targ_port = p_ctl_port; + entry.flags |= CTL_PORT_WWNN_VALID; + entry.wwnn = strtoull(targ->name() + 4, NULL, 16); + if (ioctl(ctl_fd, CTL_SET_PORT_WWNS, &entry) == -1) + log_warn("CTL_SET_PORT_WWNS ioctl failed"); } + return (true); } -int -kernel_port_add(struct port *port) +bool +port::kernel_add() { struct ctl_port_entry entry; - struct ctl_req req; struct ctl_lun_map lm; - struct target *targ = port->p_target; - struct portal_group *pg = port->p_portal_group; - char result_buf[NVLIST_BUFSIZE]; + struct target *targ = p_target; int error, i; - /* Create iSCSI port. */ - if (port->p_portal_group || port->p_ioctl_port) { - bzero(&req, sizeof(req)); - req.reqtype = CTL_REQ_CREATE; - - if (port->p_portal_group) { - strlcpy(req.driver, "iscsi", sizeof(req.driver)); - req.args_nvl = nvlist_clone(pg->pg_options); - nvlist_add_string(req.args_nvl, "cfiscsi_target", - targ->t_name); - nvlist_add_string(req.args_nvl, - "ctld_portal_group_name", pg->pg_name); - nvlist_add_stringf(req.args_nvl, - "cfiscsi_portal_group_tag", "%u", pg->pg_tag); - - if (targ->t_alias) { - nvlist_add_string(req.args_nvl, - "cfiscsi_target_alias", targ->t_alias); - } - } - - if (port->p_ioctl_port) { - strlcpy(req.driver, "ioctl", sizeof(req.driver)); - req.args_nvl = nvlist_create(0); - nvlist_add_stringf(req.args_nvl, "pp", "%d", - port->p_ioctl_pp); - nvlist_add_stringf(req.args_nvl, "vp", "%d", - port->p_ioctl_vp); - } - - req.args = nvlist_pack(req.args_nvl, &req.args_len); - if (req.args == NULL) { - nvlist_destroy(req.args_nvl); - log_warn("error packing nvlist"); - return (1); - } - - req.result = result_buf; - req.result_len = sizeof(result_buf); - error = ioctl(ctl_fd, CTL_PORT_REQ, &req); - free(req.args); - nvlist_destroy(req.args_nvl); - - if (error != 0) { - log_warn("error issuing CTL_PORT_REQ ioctl"); - return (1); - } - if (req.status == CTL_LUN_ERROR) { - log_warnx("error returned from port creation request: %s", - req.error_str); - return (1); - } - if (req.status != CTL_LUN_OK) { - log_warnx("unknown port creation request status %d", - req.status); - return (1); - } - - req.result_nvl = nvlist_unpack(result_buf, req.result_len, 0); - if (req.result_nvl == NULL) { - log_warnx("error unpacking result nvlist"); - return (1); - } - - port->p_ctl_port = nvlist_get_number(req.result_nvl, "port_id"); - nvlist_destroy(req.result_nvl); - } else if (port->p_pport) { - port->p_ctl_port = port->p_pport->pp_ctl_port; - - if (strncmp(targ->t_name, "naa.", 4) == 0 && - strlen(targ->t_name) == 20) { - bzero(&entry, sizeof(entry)); - entry.port_type = CTL_PORT_NONE; - entry.targ_port = port->p_ctl_port; - entry.flags |= CTL_PORT_WWNN_VALID; - entry.wwnn = strtoull(targ->t_name + 4, NULL, 16); - if (ioctl(ctl_fd, CTL_SET_PORT_WWNS, &entry) == -1) - log_warn("CTL_SET_PORT_WWNS ioctl failed"); - } - } + if (!kernel_create_port()) + return (false); /* Explicitly enable mapping to block any access except allowed. */ - lm.port = port->p_ctl_port; + lm.port = p_ctl_port; lm.plun = UINT32_MAX; lm.lun = 0; error = ioctl(ctl_fd, CTL_LUN_MAP, &lm); @@ -1001,11 +863,11 @@ kernel_port_add(struct port *port) /* Map configured LUNs */ for (i = 0; i < MAX_LUNS; i++) { - if (targ->t_luns[i] == NULL) + if (targ->lun(i) == nullptr) continue; - lm.port = port->p_ctl_port; + lm.port = p_ctl_port; lm.plun = i; - lm.lun = targ->t_luns[i]->l_ctl_lun; + lm.lun = targ->lun(i)->ctl_lun(); error = ioctl(ctl_fd, CTL_LUN_MAP, &lm); if (error != 0) log_warn("CTL_LUN_MAP ioctl failed"); @@ -1013,120 +875,122 @@ kernel_port_add(struct port *port) /* Enable port */ bzero(&entry, sizeof(entry)); - entry.targ_port = port->p_ctl_port; + entry.targ_port = p_ctl_port; error = ioctl(ctl_fd, CTL_ENABLE_PORT, &entry); if (error != 0) { log_warn("CTL_ENABLE_PORT ioctl failed"); - return (-1); + return (false); } - return (0); + return (true); } -int -kernel_port_update(struct port *port, struct port *oport) +bool +port::kernel_update(const struct port *oport) { struct ctl_lun_map lm; - struct target *targ = port->p_target; + struct target *targ = p_target; struct target *otarg = oport->p_target; int error, i; uint32_t olun; + p_ctl_port = oport->p_ctl_port; + /* Map configured LUNs and unmap others */ for (i = 0; i < MAX_LUNS; i++) { - lm.port = port->p_ctl_port; + lm.port = p_ctl_port; lm.plun = i; - if (targ->t_luns[i] == NULL) + if (targ->lun(i) == nullptr) lm.lun = UINT32_MAX; else - lm.lun = targ->t_luns[i]->l_ctl_lun; - if (otarg->t_luns[i] == NULL) + lm.lun = targ->lun(i)->ctl_lun(); + if (otarg->lun(i) == nullptr) olun = UINT32_MAX; else - olun = otarg->t_luns[i]->l_ctl_lun; + olun = otarg->lun(i)->ctl_lun(); if (lm.lun == olun) continue; error = ioctl(ctl_fd, CTL_LUN_MAP, &lm); if (error != 0) log_warn("CTL_LUN_MAP ioctl failed"); } - return (0); + return (true); } -int -kernel_port_remove(struct port *port) +bool +ctl_remove_port(const char *driver, nvlist_t *nvl) { - struct ctl_port_entry entry; - struct ctl_lun_map lm; struct ctl_req req; - struct target *targ = port->p_target; - struct portal_group *pg = port->p_portal_group; int error; - /* Disable port */ - bzero(&entry, sizeof(entry)); - entry.targ_port = port->p_ctl_port; - error = ioctl(ctl_fd, CTL_DISABLE_PORT, &entry); + strlcpy(req.driver, driver, sizeof(req.driver)); + req.reqtype = CTL_REQ_REMOVE; + req.args = nvlist_pack(nvl, &req.args_len); + if (req.args == NULL) { + log_warn("error packing nvlist"); + return (false); + } + + error = ioctl(ctl_fd, CTL_PORT_REQ, &req); + free(req.args); + if (error != 0) { - log_warn("CTL_DISABLE_PORT ioctl failed"); - return (-1); + log_warn("error issuing CTL_PORT_REQ ioctl"); + return (false); + } + if (req.status == CTL_LUN_ERROR) { + log_warnx("error returned from port removal request: %s", + req.error_str); + return (false); } + if (req.status != CTL_LUN_OK) { + log_warnx("unknown port removal request status %d", req.status); + return (false); + } + return (true); +} - /* Remove iSCSI or ioctl port. */ - if (port->p_portal_group || port->p_ioctl_port) { - bzero(&req, sizeof(req)); - strlcpy(req.driver, port->p_ioctl_port ? "ioctl" : "iscsi", - sizeof(req.driver)); - req.reqtype = CTL_REQ_REMOVE; - req.args_nvl = nvlist_create(0); - if (req.args_nvl == NULL) - log_err(1, "nvlist_create"); - - if (port->p_ioctl_port) - nvlist_add_stringf(req.args_nvl, "port_id", "%d", - port->p_ctl_port); - else { - nvlist_add_string(req.args_nvl, "cfiscsi_target", - targ->t_name); - nvlist_add_stringf(req.args_nvl, - "cfiscsi_portal_group_tag", "%u", pg->pg_tag); - } +bool +ioctl_port::kernel_remove_port() +{ + freebsd::nvlist_up nvl(nvlist_create(0)); + nvlist_add_stringf(nvl.get(), "port_id", "%d", p_ctl_port); - req.args = nvlist_pack(req.args_nvl, &req.args_len); - if (req.args == NULL) { - nvlist_destroy(req.args_nvl); - log_warn("error packing nvlist"); - return (1); - } + return (ctl_remove_port("ioctl", nvl.get())); +} - error = ioctl(ctl_fd, CTL_PORT_REQ, &req); - free(req.args); - nvlist_destroy(req.args_nvl); +bool +kernel_port::kernel_remove_port() +{ + struct ctl_lun_map lm; + int error; - if (error != 0) { - log_warn("error issuing CTL_PORT_REQ ioctl"); - return (1); - } - if (req.status == CTL_LUN_ERROR) { - log_warnx("error returned from port removal request: %s", - req.error_str); - return (1); - } - if (req.status != CTL_LUN_OK) { - log_warnx("unknown port removal request status %d", - req.status); - return (1); - } - } else { - /* Disable LUN mapping. */ - lm.port = port->p_ctl_port; - lm.plun = UINT32_MAX; - lm.lun = UINT32_MAX; - error = ioctl(ctl_fd, CTL_LUN_MAP, &lm); - if (error != 0) - log_warn("CTL_LUN_MAP ioctl failed"); + /* Disable LUN mapping. */ + lm.port = p_ctl_port; + lm.plun = UINT32_MAX; + lm.lun = UINT32_MAX; + error = ioctl(ctl_fd, CTL_LUN_MAP, &lm); + if (error != 0) + log_warn("CTL_LUN_MAP ioctl failed"); + return (true); +} + +bool +port::kernel_remove() +{ + struct ctl_port_entry entry; + int error; + + /* Disable port */ + bzero(&entry, sizeof(entry)); + entry.targ_port = p_ctl_port; + error = ioctl(ctl_fd, CTL_DISABLE_PORT, &entry); + if (error != 0) { + log_warn("CTL_DISABLE_PORT ioctl failed"); + return (false); } - return (0); + + return (kernel_remove_port()); } #ifdef ICL_KERNEL_PROXY @@ -1246,7 +1110,7 @@ void kernel_capsicate(void) { cap_rights_t rights; - const unsigned long cmds[] = { CTL_ISCSI }; + const unsigned long cmds[] = { CTL_ISCSI, CTL_NVMF }; cap_rights_init(&rights, CAP_IOCTL); if (caph_rights_limit(ctl_fd, &rights) < 0) diff --git a/usr.sbin/ctld/login.cc b/usr.sbin/ctld/login.cc index 961f57eb03fb..cda11cc1f21b 100644 --- a/usr.sbin/ctld/login.cc +++ b/usr.sbin/ctld/login.cc @@ -41,7 +41,8 @@ #include <cam/ctl/ctl_io.h> #include <cam/ctl/ctl_ioctl.h> -#include "ctld.h" +#include "ctld.hh" +#include "iscsi.hh" #include "iscsi_proto.h" #define MAX_DATA_SEGMENT_LENGTH (128 * 1024) @@ -60,9 +61,7 @@ kernel_limits(const char *offload, int s, int *max_recv_dsl, int *max_send_dsl, req.type = CTL_ISCSI_LIMITS; cilp = (struct ctl_iscsi_limits_params *)&(req.data.limits); - if (offload != NULL) { - strlcpy(cilp->offload, offload, sizeof(cilp->offload)); - } + strlcpy(cilp->offload, offload, sizeof(cilp->offload)); cilp->socket = s; if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) { @@ -88,7 +87,7 @@ kernel_limits(const char *offload, int s, int *max_recv_dsl, int *max_send_dsl, if (*max_burst_length < *first_burst_length) *first_burst_length = *max_burst_length; - if (offload != NULL) { + if (offload[0] != '\0') { log_debugx("Kernel limits for offload \"%s\" are " "MaxRecvDataSegment=%d, max_send_dsl=%d, " "MaxBurstLength=%d, FirstBurstLength=%d", @@ -336,7 +335,7 @@ login_send_chap_c(struct pdu *request, struct chap *chap) static struct pdu * login_receive_chap_r(struct connection *conn, struct auth_group *ag, - struct chap *chap, const struct auth **authp) + struct chap *chap, const struct auth **authp, std::string &user) { struct pdu *request; struct keys *request_keys; @@ -367,24 +366,22 @@ login_receive_chap_r(struct connection *conn, struct auth_group *ag, /* * Verify the response. */ - assert(ag->ag_type == AG_TYPE_CHAP || - ag->ag_type == AG_TYPE_CHAP_MUTUAL); - auth = auth_find(ag, chap_n); + assert(ag->type() == auth_type::CHAP || + ag->type() == auth_type::CHAP_MUTUAL); + auth = ag->find_auth(chap_n); if (auth == NULL) { login_send_error(request, 0x02, 0x01); log_errx(1, "received CHAP Login with invalid user \"%s\"", chap_n); } - assert(auth->a_secret != NULL); - assert(strlen(auth->a_secret) > 0); - - error = chap_authenticate(chap, auth->a_secret); + error = chap_authenticate(chap, auth->secret()); if (error != 0) { login_send_error(request, 0x02, 0x01); log_errx(1, "CHAP authentication failed for user \"%s\"", - auth->a_user); + chap_n); } + user = chap_n; keys_delete(request_keys); @@ -394,7 +391,7 @@ login_receive_chap_r(struct connection *conn, struct auth_group *ag, static void login_send_chap_success(struct pdu *request, - const struct auth *auth) + const struct auth *auth, const std::string &user) { struct pdu *response; struct keys *request_keys, *response_keys; @@ -424,17 +421,17 @@ login_send_chap_success(struct pdu *request, log_errx(1, "initiator requested target " "authentication, but didn't send CHAP_C"); } - if (auth->a_auth_group->ag_type != AG_TYPE_CHAP_MUTUAL) { + if (!auth->mutual()) { login_send_error(request, 0x02, 0x01); log_errx(1, "initiator requests target authentication " "for user \"%s\", but mutual user/secret " - "is not set", auth->a_user); + "is not set", user.c_str()); } log_debugx("performing mutual authentication as user \"%s\"", - auth->a_mutual_user); + auth->mutual_user()); - rchap = rchap_new(auth->a_mutual_secret); + rchap = rchap_new(auth->mutual_secret()); error = rchap_receive(rchap, chap_i, chap_c); if (error != 0) { login_send_error(request, 0x02, 0x07); @@ -444,7 +441,7 @@ login_send_chap_success(struct pdu *request, chap_r = rchap_get_response(rchap); rchap_delete(rchap); response_keys = keys_new(); - keys_add(response_keys, "CHAP_N", auth->a_mutual_user); + keys_add(response_keys, "CHAP_N", auth->mutual_user()); keys_add(response_keys, "CHAP_R", chap_r); free(chap_r); keys_save_pdu(response_keys, response); @@ -458,9 +455,10 @@ login_send_chap_success(struct pdu *request, pdu_delete(response); } -static void -login_chap(struct ctld_connection *conn, struct auth_group *ag) +void +iscsi_connection::login_chap(struct auth_group *ag) { + std::string user; const struct auth *auth; struct chap *chap; struct pdu *request; @@ -469,7 +467,7 @@ login_chap(struct ctld_connection *conn, struct auth_group *ag) * Receive CHAP_A PDU. */ log_debugx("beginning CHAP authentication; waiting for CHAP_A"); - request = login_receive_chap_a(&conn->conn); + request = login_receive_chap_a(&conn); /* * Generate the challenge. @@ -488,32 +486,31 @@ login_chap(struct ctld_connection *conn, struct auth_group *ag) * Receive CHAP_N/CHAP_R PDU and authenticate. */ log_debugx("waiting for CHAP_N/CHAP_R"); - request = login_receive_chap_r(&conn->conn, ag, chap, &auth); + request = login_receive_chap_r(&conn, ag, chap, &auth, user); /* * Yay, authentication succeeded! */ log_debugx("authentication succeeded for user \"%s\"; " - "transitioning to operational parameter negotiation", auth->a_user); - login_send_chap_success(request, auth); + "transitioning to operational parameter negotiation", user.c_str()); + login_send_chap_success(request, auth, user); pdu_delete(request); /* * Leave username and CHAP information for discovery(). */ - conn->conn_user = auth->a_user; - conn->conn_chap = chap; + conn_user = user; + conn_chap = chap; } -static void -login_negotiate_key(struct pdu *request, const char *name, +void +iscsi_connection::login_negotiate_key(struct pdu *request, const char *name, const char *value, bool skipped_security, struct keys *response_keys) { int which; size_t tmp; - struct ctld_connection *conn; - conn = (struct ctld_connection *)request->pdu_connection; + assert(request->pdu_connection == &conn); if (strcmp(name, "InitiatorName") == 0) { if (!skipped_security) @@ -525,16 +522,14 @@ login_negotiate_key(struct pdu *request, const char *name, if (!skipped_security) log_errx(1, "initiator resent TargetName"); } else if (strcmp(name, "InitiatorAlias") == 0) { - if (conn->conn_initiator_alias != NULL) - free(conn->conn_initiator_alias); - conn->conn_initiator_alias = checked_strdup(value); + conn_initiator_alias = value; } else if (strcmp(value, "Irrelevant") == 0) { /* Ignore. */ } else if (strcmp(name, "HeaderDigest") == 0) { /* * We don't handle digests for discovery sessions. */ - if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { + if (conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { log_debugx("discovery session; digests disabled"); keys_add(response_keys, name, "None"); return; @@ -545,7 +540,7 @@ login_negotiate_key(struct pdu *request, const char *name, case 1: log_debugx("initiator prefers CRC32C " "for header digest; we'll use it"); - conn->conn.conn_header_digest = CONN_DIGEST_CRC32C; + conn.conn_header_digest = CONN_DIGEST_CRC32C; keys_add(response_keys, name, "CRC32C"); break; case 2: @@ -560,7 +555,7 @@ login_negotiate_key(struct pdu *request, const char *name, break; } } else if (strcmp(name, "DataDigest") == 0) { - if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { + if (conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { log_debugx("discovery session; digests disabled"); keys_add(response_keys, name, "None"); return; @@ -571,7 +566,7 @@ login_negotiate_key(struct pdu *request, const char *name, case 1: log_debugx("initiator prefers CRC32C " "for data digest; we'll use it"); - conn->conn.conn_data_digest = CONN_DIGEST_CRC32C; + conn.conn_data_digest = CONN_DIGEST_CRC32C; keys_add(response_keys, name, "CRC32C"); break; case 2: @@ -590,15 +585,15 @@ login_negotiate_key(struct pdu *request, const char *name, } else if (strcmp(name, "InitialR2T") == 0) { keys_add(response_keys, name, "Yes"); } else if (strcmp(name, "ImmediateData") == 0) { - if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { + if (conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { log_debugx("discovery session; ImmediateData irrelevant"); keys_add(response_keys, name, "Irrelevant"); } else { if (strcmp(value, "Yes") == 0) { - conn->conn.conn_immediate_data = true; + conn.conn_immediate_data = true; keys_add(response_keys, name, "Yes"); } else { - conn->conn.conn_immediate_data = false; + conn.conn_immediate_data = false; keys_add(response_keys, name, "No"); } } @@ -616,25 +611,25 @@ login_negotiate_key(struct pdu *request, const char *name, * our MaxRecvDataSegmentLength is not influenced by the * initiator in any way. */ - if ((int)tmp > conn->conn_max_send_data_segment_limit) { + if ((int)tmp > conn_max_send_data_segment_limit) { log_debugx("capping MaxRecvDataSegmentLength " "from %zd to %d", tmp, - conn->conn_max_send_data_segment_limit); - tmp = conn->conn_max_send_data_segment_limit; + conn_max_send_data_segment_limit); + tmp = conn_max_send_data_segment_limit; } - conn->conn.conn_max_send_data_segment_length = tmp; + conn.conn_max_send_data_segment_length = tmp; } else if (strcmp(name, "MaxBurstLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) { login_send_error(request, 0x02, 0x00); log_errx(1, "received invalid MaxBurstLength"); } - if ((int)tmp > conn->conn_max_burst_limit) { + if ((int)tmp > conn_max_burst_limit) { log_debugx("capping MaxBurstLength from %zd to %d", - tmp, conn->conn_max_burst_limit); - tmp = conn->conn_max_burst_limit; + tmp, conn_max_burst_limit); + tmp = conn_max_burst_limit; } - conn->conn.conn_max_burst_length = tmp; + conn.conn_max_burst_length = tmp; keys_add_int(response_keys, name, tmp); } else if (strcmp(name, "FirstBurstLength") == 0) { tmp = strtoul(value, NULL, 10); @@ -642,12 +637,12 @@ login_negotiate_key(struct pdu *request, const char *name, login_send_error(request, 0x02, 0x00); log_errx(1, "received invalid FirstBurstLength"); } - if ((int)tmp > conn->conn_first_burst_limit) { + if ((int)tmp > conn_first_burst_limit) { log_debugx("capping FirstBurstLength from %zd to %d", - tmp, conn->conn_first_burst_limit); - tmp = conn->conn_first_burst_limit; + tmp, conn_first_burst_limit); + tmp = conn_first_burst_limit; } - conn->conn.conn_first_burst_length = tmp; + conn.conn_first_burst_length = tmp; keys_add_int(response_keys, name, tmp); } else if (strcmp(name, "DefaultTime2Wait") == 0) { keys_add(response_keys, name, value); @@ -699,102 +694,100 @@ login_redirect(struct pdu *request, const char *target_address) keys_delete(response_keys); } -static bool -login_portal_redirect(struct ctld_connection *conn, struct pdu *request) +bool +iscsi_connection::login_portal_redirect(struct pdu *request) { const struct portal_group *pg; - pg = conn->conn_portal->p_portal_group; - if (pg->pg_redirection == NULL) + pg = conn_portal->portal_group(); + if (!pg->is_redirecting()) return (false); log_debugx("portal-group \"%s\" configured to redirect to %s", - pg->pg_name, pg->pg_redirection); - login_redirect(request, pg->pg_redirection); + pg->name(), pg->redirection()); + login_redirect(request, pg->redirection()); return (true); } -static bool -login_target_redirect(struct ctld_connection *conn, struct pdu *request) +bool +iscsi_connection::login_target_redirect(struct pdu *request) { const char *target_address; - assert(conn->conn_portal->p_portal_group->pg_redirection == NULL); + assert(!conn_portal->portal_group()->is_redirecting()); - if (conn->conn_target == NULL) + if (conn_target == NULL) return (false); - target_address = conn->conn_target->t_redirection; - if (target_address == NULL) + if (!conn_target->has_redirection()) return (false); + target_address = conn_target->redirection(); log_debugx("target \"%s\" configured to redirect to %s", - conn->conn_target->t_name, target_address); + conn_target->name(), target_address); login_redirect(request, target_address); return (true); } -static void -login_negotiate(struct ctld_connection *conn, struct pdu *request) +void +iscsi_connection::login_negotiate(struct pdu *request) { + struct portal_group *pg = conn_portal->portal_group(); struct pdu *response; struct iscsi_bhs_login_response *bhslr2; struct keys *request_keys, *response_keys; int i; bool redirected, skipped_security; - if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { + if (conn_session_type == CONN_SESSION_TYPE_NORMAL) { /* * Query the kernel for various size limits. In case of * offload, it depends on hardware capabilities. */ - assert(conn->conn_target != NULL); - conn->conn_max_recv_data_segment_limit = (1 << 24) - 1; - conn->conn_max_send_data_segment_limit = (1 << 24) - 1; - conn->conn_max_burst_limit = (1 << 24) - 1; - conn->conn_first_burst_limit = (1 << 24) - 1; - kernel_limits(conn->conn_portal->p_portal_group->pg_offload, - conn->conn.conn_socket, - &conn->conn_max_recv_data_segment_limit, - &conn->conn_max_send_data_segment_limit, - &conn->conn_max_burst_limit, - &conn->conn_first_burst_limit); + assert(conn_target != NULL); + conn_max_recv_data_segment_limit = (1 << 24) - 1; + conn_max_send_data_segment_limit = (1 << 24) - 1; + conn_max_burst_limit = (1 << 24) - 1; + conn_first_burst_limit = (1 << 24) - 1; + kernel_limits(pg->offload(), + conn_fd, + &conn_max_recv_data_segment_limit, + &conn_max_send_data_segment_limit, + &conn_max_burst_limit, + &conn_first_burst_limit); /* We expect legal, usable values at this point. */ - assert(conn->conn_max_recv_data_segment_limit >= 512); - assert(conn->conn_max_recv_data_segment_limit < (1 << 24)); - assert(conn->conn_max_send_data_segment_limit >= 512); - assert(conn->conn_max_send_data_segment_limit < (1 << 24)); - assert(conn->conn_max_burst_limit >= 512); - assert(conn->conn_max_burst_limit < (1 << 24)); - assert(conn->conn_first_burst_limit >= 512); - assert(conn->conn_first_burst_limit < (1 << 24)); - assert(conn->conn_first_burst_limit <= - conn->conn_max_burst_limit); + assert(conn_max_recv_data_segment_limit >= 512); + assert(conn_max_recv_data_segment_limit < (1 << 24)); + assert(conn_max_send_data_segment_limit >= 512); + assert(conn_max_send_data_segment_limit < (1 << 24)); + assert(conn_max_burst_limit >= 512); + assert(conn_max_burst_limit < (1 << 24)); + assert(conn_first_burst_limit >= 512); + assert(conn_first_burst_limit < (1 << 24)); + assert(conn_first_burst_limit <= conn_max_burst_limit); /* * Limit default send length in case it won't be negotiated. * We can't do it for other limits, since they may affect both * sender and receiver operation, and we must obey defaults. */ - if (conn->conn_max_send_data_segment_limit < - conn->conn.conn_max_send_data_segment_length) { - conn->conn.conn_max_send_data_segment_length = - conn->conn_max_send_data_segment_limit; + if (conn_max_send_data_segment_limit < + conn.conn_max_send_data_segment_length) { + conn.conn_max_send_data_segment_length = + conn_max_send_data_segment_limit; } } else { - conn->conn_max_recv_data_segment_limit = - MAX_DATA_SEGMENT_LENGTH; - conn->conn_max_send_data_segment_limit = - MAX_DATA_SEGMENT_LENGTH; + conn_max_recv_data_segment_limit = MAX_DATA_SEGMENT_LENGTH; + conn_max_send_data_segment_limit = MAX_DATA_SEGMENT_LENGTH; } if (request == NULL) { log_debugx("beginning operational parameter negotiation; " "waiting for Login PDU"); - request = login_receive(&conn->conn, false); + request = login_receive(&conn, false); skipped_security = false; } else skipped_security = true; @@ -805,7 +798,7 @@ login_negotiate(struct ctld_connection *conn, struct pdu *request) * authentication, but MUST be accepted afterwards; that's * why we're doing it here and not earlier. */ - redirected = login_target_redirect(conn, request); + redirected = login_target_redirect(request); if (redirected) { log_debugx("initiator redirected; exiting"); exit(0); @@ -822,12 +815,12 @@ login_negotiate(struct ctld_connection *conn, struct pdu *request) response_keys = keys_new(); if (skipped_security && - conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { - if (conn->conn_target->t_alias != NULL) + conn_session_type == CONN_SESSION_TYPE_NORMAL) { + if (conn_target->has_alias()) keys_add(response_keys, - "TargetAlias", conn->conn_target->t_alias); + "TargetAlias", conn_target->alias()); keys_add_int(response_keys, "TargetPortalGroupTag", - conn->conn_portal->p_portal_group->pg_tag); + pg->tag()); } for (i = 0; i < KEYS_MAX; i++) { @@ -846,16 +839,15 @@ login_negotiate(struct ctld_connection *conn, struct pdu *request) * pairs in the order they are in the request we might have ended up * with illegal values here. */ - if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL && - conn->conn.conn_first_burst_length > - conn->conn.conn_max_burst_length) { + if (conn_session_type == CONN_SESSION_TYPE_NORMAL && + conn.conn_first_burst_length > conn.conn_max_burst_length) { log_errx(1, "initiator sent FirstBurstLength > MaxBurstLength"); } - conn->conn.conn_max_recv_data_segment_length = - conn->conn_max_recv_data_segment_limit; + conn.conn_max_recv_data_segment_length = + conn_max_recv_data_segment_limit; keys_add_int(response_keys, "MaxRecvDataSegmentLength", - conn->conn.conn_max_recv_data_segment_length); + conn.conn_max_recv_data_segment_length); log_debugx("operational parameter negotiation done; " "transitioning to Full Feature Phase"); @@ -868,14 +860,14 @@ login_negotiate(struct ctld_connection *conn, struct pdu *request) keys_delete(request_keys); } -static void -login_wait_transition(struct ctld_connection *conn) +void +iscsi_connection::login_wait_transition() { struct pdu *request, *response; struct iscsi_bhs_login_request *bhslr; log_debugx("waiting for state transition request"); - request = login_receive(&conn->conn, false); + request = login_receive(&conn, false); bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0) { login_send_error(request, 0x02, 0x00); @@ -889,11 +881,11 @@ login_wait_transition(struct ctld_connection *conn) pdu_send(response); pdu_delete(response); - login_negotiate(conn, NULL); + login_negotiate(nullptr); } void -login(struct ctld_connection *conn) +iscsi_connection::login() { struct pdu *request, *response; struct iscsi_bhs_login_request *bhslr; @@ -910,17 +902,17 @@ login(struct ctld_connection *conn) * is required, or call appropriate authentication code. */ log_debugx("beginning Login Phase; waiting for Login PDU"); - request = login_receive(&conn->conn, true); + request = login_receive(&conn, true); bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; if (bhslr->bhslr_tsih != 0) { login_send_error(request, 0x02, 0x0a); log_errx(1, "received Login PDU with non-zero TSIH"); } - pg = conn->conn_portal->p_portal_group; + pg = conn_portal->portal_group(); - memcpy(conn->conn_initiator_isid, bhslr->bhslr_isid, - sizeof(conn->conn_initiator_isid)); + memcpy(conn_initiator_isid, bhslr->bhslr_isid, + sizeof(conn_initiator_isid)); /* * XXX: Implement the C flag some day. @@ -928,7 +920,7 @@ login(struct ctld_connection *conn) request_keys = keys_new(); keys_load_pdu(request_keys, request); - assert(conn->conn_initiator_name == NULL); + assert(conn_initiator_name.empty()); initiator_name = keys_find(request_keys, "InitiatorName"); if (initiator_name == NULL) { login_send_error(request, 0x02, 0x07); @@ -938,11 +930,12 @@ login(struct ctld_connection *conn) login_send_error(request, 0x02, 0x00); log_errx(1, "received Login PDU with invalid InitiatorName"); } - conn->conn_initiator_name = checked_strdup(initiator_name); - log_set_peer_name(conn->conn_initiator_name); - setproctitle("%s (%s)", conn->conn_initiator_addr, conn->conn_initiator_name); + conn_initiator_name = initiator_name; + log_set_peer_name(conn_initiator_name.c_str()); + setproctitle("%s (%s)", conn_initiator_addr.c_str(), + conn_initiator_name.c_str()); - redirected = login_portal_redirect(conn, request); + redirected = login_portal_redirect(request); if (redirected) { log_debugx("initiator redirected; exiting"); exit(0); @@ -950,69 +943,69 @@ login(struct ctld_connection *conn) initiator_alias = keys_find(request_keys, "InitiatorAlias"); if (initiator_alias != NULL) - conn->conn_initiator_alias = checked_strdup(initiator_alias); + conn_initiator_alias = initiator_alias; - assert(conn->conn_session_type == CONN_SESSION_TYPE_NONE); + assert(conn_session_type == CONN_SESSION_TYPE_NONE); session_type = keys_find(request_keys, "SessionType"); if (session_type != NULL) { if (strcmp(session_type, "Normal") == 0) { - conn->conn_session_type = CONN_SESSION_TYPE_NORMAL; + conn_session_type = CONN_SESSION_TYPE_NORMAL; } else if (strcmp(session_type, "Discovery") == 0) { - conn->conn_session_type = CONN_SESSION_TYPE_DISCOVERY; + conn_session_type = CONN_SESSION_TYPE_DISCOVERY; } else { login_send_error(request, 0x02, 0x00); log_errx(1, "received Login PDU with invalid " "SessionType \"%s\"", session_type); } } else - conn->conn_session_type = CONN_SESSION_TYPE_NORMAL; + conn_session_type = CONN_SESSION_TYPE_NORMAL; - assert(conn->conn_target == NULL); - if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { + assert(conn_target == NULL); + if (conn_session_type == CONN_SESSION_TYPE_NORMAL) { target_name = keys_find(request_keys, "TargetName"); if (target_name == NULL) { login_send_error(request, 0x02, 0x07); log_errx(1, "received Login PDU without TargetName"); } - conn->conn_port = port_find_in_pg(pg, target_name); - if (conn->conn_port == NULL) { + conn_port = pg->find_port(target_name); + if (conn_port == NULL) { login_send_error(request, 0x02, 0x03); log_errx(1, "requested target \"%s\" not found", target_name); } - conn->conn_target = conn->conn_port->p_target; + conn_target = conn_port->target(); } /* * At this point we know what kind of authentication we need. */ - if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { - ag = conn->conn_port->p_auth_group; - if (ag == NULL) - ag = conn->conn_target->t_auth_group; - if (ag->ag_name != NULL) { + if (conn_session_type == CONN_SESSION_TYPE_NORMAL) { + ag = conn_port->auth_group(); + if (ag == nullptr) + ag = conn_target->auth_group(); + if (conn_port->auth_group() == nullptr && + conn_target->private_auth()) { log_debugx("initiator requests to connect " - "to target \"%s\"; auth-group \"%s\"", - conn->conn_target->t_name, - ag->ag_name); + "to target \"%s\"", conn_target->name()); } else { log_debugx("initiator requests to connect " - "to target \"%s\"", conn->conn_target->t_name); + "to target \"%s\"; %s", + conn_target->name(), ag->label()); } } else { - assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY); - ag = pg->pg_discovery_auth_group; + assert(conn_session_type == CONN_SESSION_TYPE_DISCOVERY); + ag = pg->discovery_auth_group(); log_debugx("initiator requests discovery session; %s", - ag->ag_label); + ag->label()); } - if (ag->ag_type == AG_TYPE_DENY) { + if (ag->type() == auth_type::DENY) { login_send_error(request, 0x02, 0x01); log_errx(1, "auth-type is \"deny\""); } - if (ag->ag_type == AG_TYPE_UNKNOWN) { + if (ag->type() == auth_type::UNKNOWN) { /* * This can happen with empty auth-group. */ @@ -1023,12 +1016,12 @@ login(struct ctld_connection *conn) /* * Enforce initiator-name and initiator-portal. */ - if (!auth_name_check(ag, initiator_name)) { + if (!ag->initiator_permitted(initiator_name)) { login_send_error(request, 0x02, 0x02); log_errx(1, "initiator does not match allowed initiator names"); } - if (!auth_portal_check(ag, &conn->conn_initiator_sa)) { + if (!ag->initiator_permitted(conn_initiator_sa)) { login_send_error(request, 0x02, 0x02); log_errx(1, "initiator does not match allowed " "initiator portals"); @@ -1039,7 +1032,7 @@ login(struct ctld_connection *conn) * at all. */ if (login_csg(request) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) { - if (ag->ag_type != AG_TYPE_NO_AUTHENTICATION) { + if (ag->type() != auth_type::NO_AUTHENTICATION) { login_send_error(request, 0x02, 0x01); log_errx(1, "initiator skipped the authentication, " "but authentication is required"); @@ -1049,7 +1042,7 @@ login(struct ctld_connection *conn) log_debugx("initiator skipped the authentication, " "and we don't need it; proceeding with negotiation"); - login_negotiate(conn, request); + login_negotiate(request); return; } @@ -1058,7 +1051,7 @@ login(struct ctld_connection *conn) response_keys = keys_new(); trans = (bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0; auth_method = keys_find(request_keys, "AuthMethod"); - if (ag->ag_type == AG_TYPE_NO_AUTHENTICATION) { + if (ag->type() == auth_type::NO_AUTHENTICATION) { log_debugx("authentication not required"); if (auth_method == NULL || login_list_contains(auth_method, "None")) { @@ -1084,12 +1077,12 @@ login(struct ctld_connection *conn) fail = true; } } - if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { - if (conn->conn_target->t_alias != NULL) + if (conn_session_type == CONN_SESSION_TYPE_NORMAL) { + if (conn_target->has_alias()) keys_add(response_keys, - "TargetAlias", conn->conn_target->t_alias); + "TargetAlias", conn_target->alias()); keys_add_int(response_keys, - "TargetPortalGroupTag", pg->pg_tag); + "TargetPortalGroupTag", pg->tag()); } keys_save_pdu(response_keys, response); @@ -1104,12 +1097,12 @@ login(struct ctld_connection *conn) exit(1); } - if (ag->ag_type != AG_TYPE_NO_AUTHENTICATION) { - login_chap(conn, ag); - login_negotiate(conn, NULL); + if (ag->type() != auth_type::NO_AUTHENTICATION) { + login_chap(ag); + login_negotiate(nullptr); } else if (trans) { - login_negotiate(conn, NULL); + login_negotiate(nullptr); } else { - login_wait_transition(conn); + login_wait_transition(); } } diff --git a/usr.sbin/ctld/nvmf.cc b/usr.sbin/ctld/nvmf.cc new file mode 100644 index 000000000000..d1240bfa4f6c --- /dev/null +++ b/usr.sbin/ctld/nvmf.cc @@ -0,0 +1,478 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Chelsio Communications, Inc. + * Written by: John Baldwin <jhb@FreeBSD.org> + */ + +#include <sys/param.h> +#include <sys/linker.h> +#include <sys/module.h> +#include <sys/time.h> +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <libiscsiutil.h> +#include <libnvmf.h> +#include <libutil.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <cam/ctl/ctl.h> +#include <cam/ctl/ctl_io.h> +#include <cam/ctl/ctl_ioctl.h> + +#include <memory> + +#include "ctld.hh" +#include "nvmf.hh" + +#define DEFAULT_MAXH2CDATA (256 * 1024) + +struct nvmf_io_portal final : public nvmf_portal { + nvmf_io_portal(struct portal_group *pg, const char *listen, + portal_protocol protocol, freebsd::addrinfo_up ai, + const struct nvmf_association_params &aparams, + nvmf_association_up na) : + nvmf_portal(pg, listen, protocol, std::move(ai), aparams, + std::move(na)) {} + + void handle_connection(freebsd::fd_up fd, const char *host, + const struct sockaddr *client_sa) override; +}; + +struct nvmf_transport_group final : public portal_group { + nvmf_transport_group(struct conf *conf, std::string_view name) : + portal_group(conf, name) {} + + const char *keyword() const override + { return "transport-group"; } + + void allocate_tag() override; + bool add_portal(const char *value, portal_protocol protocol) + override; + void add_default_portals() override; + bool set_filter(const char *str) override; + + virtual port_up create_port(struct target *target, auth_group_sp ag) + override; + virtual port_up create_port(struct target *target, uint32_t ctl_port) + override; + +private: + struct nvmf_association_params init_aparams(portal_protocol protocol); + + static uint16_t last_port_id; +}; + +struct nvmf_port final : public portal_group_port { + nvmf_port(struct target *target, struct portal_group *pg, + auth_group_sp ag) : + portal_group_port(target, pg, ag) {} + nvmf_port(struct target *target, struct portal_group *pg, + uint32_t ctl_port) : + portal_group_port(target, pg, ctl_port) {} + + bool kernel_create_port() override; + bool kernel_remove_port() override; + +private: + static bool modules_loaded; + static void load_kernel_modules(); +}; + +struct nvmf_controller final : public target { + nvmf_controller(struct conf *conf, std::string_view name) : + target(conf, "controller", name) {} + + bool add_host_nqn(std::string_view name) override; + bool add_host_address(const char *addr) override; + bool add_namespace(u_int id, const char *lun_name) override; + bool add_portal_group(const char *pg_name, const char *ag_name) + override; + struct lun *start_namespace(u_int id) override; + +protected: + struct portal_group *default_portal_group() override; +}; + +uint16_t nvmf_transport_group::last_port_id = 0; +bool nvmf_port::modules_loaded = false; + +static bool need_tcp_transport = false; + +static bool +parse_bool(const nvlist_t *nvl, const char *key, bool def) +{ + const char *value; + + if (!nvlist_exists_string(nvl, key)) + return def; + + value = nvlist_get_string(nvl, key); + if (strcasecmp(value, "true") == 0 || + strcasecmp(value, "1") == 0) + return true; + if (strcasecmp(value, "false") == 0 || + strcasecmp(value, "0") == 0) + return false; + + log_warnx("Invalid value \"%s\" for boolean option %s", value, key); + return def; +} + +static uint64_t +parse_number(const nvlist_t *nvl, const char *key, uint64_t def, uint64_t minv, + uint64_t maxv) +{ + const char *value; + int64_t val; + + if (!nvlist_exists_string(nvl, key)) + return def; + + value = nvlist_get_string(nvl, key); + if (expand_number(value, &val) == 0 && val >= 0 && + (uint64_t)val >= minv && (uint64_t)val <= maxv) + return (uint64_t)val; + + log_warnx("Invalid value \"%s\" for numeric option %s", value, key); + return def; +} + +struct nvmf_association_params +nvmf_transport_group::init_aparams(portal_protocol protocol) +{ + struct nvmf_association_params params; + memset(¶ms, 0, sizeof(params)); + + /* Options shared between discovery and I/O associations. */ + const nvlist_t *nvl = pg_options.get(); + params.tcp.header_digests = parse_bool(nvl, "HDGST", false); + params.tcp.data_digests = parse_bool(nvl, "DDGST", false); + uint64_t value = parse_number(nvl, "MAXH2CDATA", DEFAULT_MAXH2CDATA, + 4096, UINT32_MAX); + if (value % 4 != 0) { + log_warnx("Invalid value \"%ju\" for option MAXH2CDATA", + (uintmax_t)value); + value = DEFAULT_MAXH2CDATA; + } + params.tcp.maxh2cdata = value; + + switch (protocol) { + case portal_protocol::NVME_TCP: + params.sq_flow_control = parse_bool(nvl, "SQFC", false); + params.dynamic_controller_model = true; + params.max_admin_qsize = parse_number(nvl, "max_admin_qsize", + NVME_MAX_ADMIN_ENTRIES, NVME_MIN_ADMIN_ENTRIES, + NVME_MAX_ADMIN_ENTRIES); + params.max_io_qsize = parse_number(nvl, "max_io_qsize", + NVME_MAX_IO_ENTRIES, NVME_MIN_IO_ENTRIES, + NVME_MAX_IO_ENTRIES); + params.tcp.pda = 0; + break; + case portal_protocol::NVME_DISCOVERY_TCP: + params.sq_flow_control = false; + params.dynamic_controller_model = true; + params.max_admin_qsize = NVME_MAX_ADMIN_ENTRIES; + params.tcp.pda = 0; + break; + default: + __assert_unreachable(); + } + + return params; +} + +portal_group_up +nvmf_make_transport_group(struct conf *conf, std::string_view name) +{ + return std::make_unique<nvmf_transport_group>(conf, name); +} + +target_up +nvmf_make_controller(struct conf *conf, std::string_view name) +{ + return std::make_unique<nvmf_controller>(conf, name); +} + +void +nvmf_transport_group::allocate_tag() +{ + set_tag(++last_port_id); +} + +bool +nvmf_transport_group::add_portal(const char *value, portal_protocol protocol) +{ + freebsd::addrinfo_up ai; + enum nvmf_trtype trtype; + + switch (protocol) { + case portal_protocol::NVME_TCP: + trtype = NVMF_TRTYPE_TCP; + ai = parse_addr_port(value, "4420"); + break; + case portal_protocol::NVME_DISCOVERY_TCP: + trtype = NVMF_TRTYPE_TCP; + ai = parse_addr_port(value, "8009"); + break; + default: + log_warnx("unsupported transport protocol for %s", value); + return false; + } + + if (!ai) { + log_warnx("invalid listen address %s", value); + return false; + } + + struct nvmf_association_params aparams = init_aparams(protocol); + nvmf_association_up association(nvmf_allocate_association(trtype, true, + &aparams)); + if (!association) { + log_warn("Failed to create NVMe controller association"); + return false; + } + + /* + * XXX: getaddrinfo(3) may return multiple addresses; we should turn + * those into multiple portals. + */ + + portal_up portal; + if (protocol == portal_protocol::NVME_DISCOVERY_TCP) { + portal = std::make_unique<nvmf_discovery_portal>(this, value, + protocol, std::move(ai), aparams, std::move(association)); + } else { + portal = std::make_unique<nvmf_io_portal>(this, value, + protocol, std::move(ai), aparams, std::move(association)); + need_tcp_transport = true; + } + + pg_portals.emplace_back(std::move(portal)); + return true; +} + +void +nvmf_transport_group::add_default_portals() +{ + add_portal("0.0.0.0", portal_protocol::NVME_DISCOVERY_TCP); + add_portal("[::]", portal_protocol::NVME_DISCOVERY_TCP); + add_portal("0.0.0.0", portal_protocol::NVME_TCP); + add_portal("[::]", portal_protocol::NVME_TCP); +} + +bool +nvmf_transport_group::set_filter(const char *str) +{ + enum discovery_filter filter; + + if (strcmp(str, "none") == 0) { + filter = discovery_filter::NONE; + } else if (strcmp(str, "address") == 0) { + filter = discovery_filter::PORTAL; + } else if (strcmp(str, "address-name") == 0) { + filter = discovery_filter::PORTAL_NAME; + } else { + log_warnx("invalid discovery-filter \"%s\" for transport-group " + "\"%s\"; valid values are \"none\", \"address\", " + "and \"address-name\"", + str, name()); + return false; + } + + if (pg_discovery_filter != discovery_filter::UNKNOWN && + pg_discovery_filter != filter) { + log_warnx("cannot set discovery-filter to \"%s\" for " + "transport-group \"%s\"; already has a different " + "value", str, name()); + return false; + } + + pg_discovery_filter = filter; + return true; +} + +port_up +nvmf_transport_group::create_port(struct target *target, auth_group_sp ag) +{ + return std::make_unique<nvmf_port>(target, this, ag); +} + +port_up +nvmf_transport_group::create_port(struct target *target, uint32_t ctl_port) +{ + return std::make_unique<nvmf_port>(target, this, ctl_port); +} + +void +nvmf_port::load_kernel_modules() +{ + int saved_errno; + + if (modules_loaded) + return; + + saved_errno = errno; + if (modfind("nvmft") == -1 && kldload("nvmft") == -1) + log_warn("couldn't load nvmft"); + + if (need_tcp_transport) { + if (modfind("nvmf/tcp") == -1 && kldload("nvmf_tcp") == -1) + log_warn("couldn't load nvmf_tcp"); + } + + errno = saved_errno; + modules_loaded = true; +} + +bool +nvmf_port::kernel_create_port() +{ + struct portal_group *pg = p_portal_group; + struct target *targ = p_target; + + load_kernel_modules(); + + freebsd::nvlist_up nvl = pg->options(); + nvlist_add_string(nvl.get(), "subnqn", targ->name()); + nvlist_add_string(nvl.get(), "ctld_transport_group_name", + pg->name()); + nvlist_add_stringf(nvl.get(), "portid", "%u", pg->tag()); + if (!nvlist_exists_string(nvl.get(), "max_io_qsize")) + nvlist_add_stringf(nvl.get(), "max_io_qsize", "%u", + NVME_MAX_IO_ENTRIES); + + return ctl_create_port("nvmf", nvl.get(), &p_ctl_port); +} + +bool +nvmf_port::kernel_remove_port() +{ + freebsd::nvlist_up nvl(nvlist_create(0)); + nvlist_add_string(nvl.get(), "subnqn", p_target->name()); + + return ctl_remove_port("nvmf", nvl.get()); +} + +bool +nvmf_controller::add_host_nqn(std::string_view name) +{ + if (!use_private_auth("host-nqn")) + return false; + return t_auth_group->add_host_nqn(name); +} + +bool +nvmf_controller::add_host_address(const char *addr) +{ + if (!use_private_auth("host-address")) + return false; + return t_auth_group->add_host_address(addr); +} + +bool +nvmf_controller::add_namespace(u_int id, const char *lun_name) +{ + if (id == 0) { + log_warnx("namespace ID cannot be 0 for %s", label()); + return false; + } + + std::string lun_label = "namespace ID " + std::to_string(id - 1); + return target::add_lun(id, lun_label.c_str(), lun_name); +} + +bool +nvmf_controller::add_portal_group(const char *pg_name, const char *ag_name) +{ + struct portal_group *pg; + auth_group_sp ag; + + pg = t_conf->find_transport_group(pg_name); + if (pg == NULL) { + log_warnx("unknown transport-group \"%s\" for %s", pg_name, + label()); + return false; + } + + if (ag_name != NULL) { + ag = t_conf->find_auth_group(ag_name); + if (ag == NULL) { + log_warnx("unknown auth-group \"%s\" for %s", ag_name, + label()); + return false; + } + } + + if (!t_conf->add_port(this, pg, std::move(ag))) { + log_warnx("can't link transport-group \"%s\" to %s", pg_name, + label()); + return false; + } + return true; +} + +struct lun * +nvmf_controller::start_namespace(u_int id) +{ + if (id == 0) { + log_warnx("namespace ID cannot be 0 for %s", label()); + return nullptr; + } + + std::string lun_label = "namespace ID " + std::to_string(id - 1); + std::string lun_name = freebsd::stringf("%s,nsid,%u", name(), id); + return target::start_lun(id, lun_label.c_str(), lun_name.c_str()); +} + +struct portal_group * +nvmf_controller::default_portal_group() +{ + return t_conf->find_transport_group("default"); +} + +void +nvmf_io_portal::handle_connection(freebsd::fd_up fd, const char *host __unused, + const struct sockaddr *client_sa __unused) +{ + struct nvmf_qpair_params qparams; + memset(&qparams, 0, sizeof(qparams)); + qparams.tcp.fd = fd; + + struct nvmf_capsule *nc = NULL; + struct nvmf_fabric_connect_data data; + nvmf_qpair_up qp(nvmf_accept(association(), &qparams, &nc, &data)); + if (!qp) { + log_warnx("Failed to create NVMe I/O qpair: %s", + nvmf_association_error(association())); + return; + } + nvmf_capsule_up nc_guard(nc); + const struct nvmf_fabric_connect_cmd *cmd = + (const struct nvmf_fabric_connect_cmd *)nvmf_capsule_sqe(nc); + + struct ctl_nvmf req; + memset(&req, 0, sizeof(req)); + req.type = CTL_NVMF_HANDOFF; + int error = nvmf_handoff_controller_qpair(qp.get(), cmd, &data, + &req.data.handoff); + if (error != 0) { + log_warnc(error, + "Failed to prepare NVMe I/O qpair for handoff"); + return; + } + + if (ioctl(ctl_fd, CTL_NVMF, &req) != 0) + log_warn("ioctl(CTL_NVMF/CTL_NVMF_HANDOFF)"); + if (req.status == CTL_NVMF_ERROR) + log_warnx("Failed to handoff NVMF connection: %s", + req.error_str); + else if (req.status != CTL_NVMF_OK) + log_warnx("Failed to handoff NVMF connection with status %d", + req.status); +} diff --git a/usr.sbin/ctld/nvmf.hh b/usr.sbin/ctld/nvmf.hh new file mode 100644 index 000000000000..0b4f8d45adfd --- /dev/null +++ b/usr.sbin/ctld/nvmf.hh @@ -0,0 +1,71 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Chelsio Communications, Inc. + * Written by: John Baldwin <jhb@FreeBSD.org> + */ + +#ifndef __NVMF_HH__ +#define __NVMF_HH__ + +struct nvmf_association_deleter { + void operator()(struct nvmf_association *na) const + { + nvmf_free_association(na); + } +}; + +using nvmf_association_up = std::unique_ptr<nvmf_association, + nvmf_association_deleter>; + +struct nvmf_capsule_deleter { + void operator()(struct nvmf_capsule *nc) const + { + nvmf_free_capsule(nc); + } +}; + +using nvmf_capsule_up = std::unique_ptr<nvmf_capsule, nvmf_capsule_deleter>; + +struct nvmf_qpair_deleter { + void operator()(struct nvmf_qpair *qp) const + { + nvmf_free_qpair(qp); + } +}; + +using nvmf_qpair_up = std::unique_ptr<nvmf_qpair, nvmf_qpair_deleter>; + +struct nvmf_portal : public portal { + nvmf_portal(struct portal_group *pg, const char *listen, + portal_protocol protocol, freebsd::addrinfo_up ai, + const struct nvmf_association_params &aparams, + nvmf_association_up na) : + portal(pg, listen, protocol, std::move(ai)), + p_aparams(aparams), p_association(std::move(na)) {} + virtual ~nvmf_portal() override = default; + + const struct nvmf_association_params *aparams() const + { return &p_aparams; } + +protected: + struct nvmf_association *association() { return p_association.get(); } + +private: + struct nvmf_association_params p_aparams; + nvmf_association_up p_association; +}; + +struct nvmf_discovery_portal final : public nvmf_portal { + nvmf_discovery_portal(struct portal_group *pg, const char *listen, + portal_protocol protocol, freebsd::addrinfo_up ai, + const struct nvmf_association_params &aparams, + nvmf_association_up na) : + nvmf_portal(pg, listen, protocol, std::move(ai), aparams, + std::move(na)) {} + + void handle_connection(freebsd::fd_up fd, const char *host, + const struct sockaddr *client_sa) override; +}; + +#endif /* !__NVMF_HH__ */ diff --git a/usr.sbin/ctld/nvmf_discovery.cc b/usr.sbin/ctld/nvmf_discovery.cc new file mode 100644 index 000000000000..839b69fdac14 --- /dev/null +++ b/usr.sbin/ctld/nvmf_discovery.cc @@ -0,0 +1,518 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023-2025 Chelsio Communications, Inc. + * Written by: John Baldwin <jhb@FreeBSD.org> + */ + +#include <assert.h> +#include <errno.h> +#include <netdb.h> +#include <libiscsiutil.h> +#include <libnvmf.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <netinet/in.h> + +#include "ctld.hh" +#include "nvmf.hh" + +struct discovery_log { + discovery_log(const struct portal_group *pg); + + const char *data() const { return buf.data(); } + size_t length() const { return buf.size(); } + + void append(const struct nvme_discovery_log_entry *entry); + +private: + struct nvme_discovery_log *header() + { return reinterpret_cast<struct nvme_discovery_log *>(buf.data()); } + + std::vector<char> buf; +}; + +struct discovery_controller { + discovery_controller(freebsd::fd_up s, struct nvmf_qpair *qp, + const discovery_log &discovery_log); + + void handle_admin_commands(); +private: + bool update_cc(uint32_t new_cc); + void handle_property_get(const struct nvmf_capsule *nc, + const struct nvmf_fabric_prop_get_cmd *pget); + void handle_property_set(const struct nvmf_capsule *nc, + const struct nvmf_fabric_prop_set_cmd *pset); + void handle_fabrics_command(const struct nvmf_capsule *nc, + const struct nvmf_fabric_cmd *cmd); + void handle_identify_command(const struct nvmf_capsule *nc, + const struct nvme_command *cmd); + void handle_get_log_page_command(const struct nvmf_capsule *nc, + const struct nvme_command *cmd); + + struct nvmf_qpair *qp; + + uint64_t cap = 0; + uint32_t vs = 0; + uint32_t cc = 0; + uint32_t csts = 0; + + bool shutdown = false; + + struct nvme_controller_data cdata; + + const struct discovery_log &discovery_log; + freebsd::fd_up s; +}; + +discovery_log::discovery_log(const struct portal_group *pg) : + buf(sizeof(nvme_discovery_log)) +{ + struct nvme_discovery_log *log = header(); + + log->genctr = htole32(pg->conf()->genctr()); + log->recfmt = 0; +} + +void +discovery_log::append(const struct nvme_discovery_log_entry *entry) +{ + const char *cp = reinterpret_cast<const char *>(entry); + buf.insert(buf.end(), cp, cp + sizeof(*entry)); + + struct nvme_discovery_log *log = header(); + log->numrec = htole32(le32toh(log->numrec) + 1); +} + +static bool +discovery_controller_filtered(const struct portal_group *pg, + const struct sockaddr *client_sa, std::string_view hostnqn, + const struct port *port) +{ + const struct target *targ = port->target(); + const struct auth_group *ag = port->auth_group(); + if (ag == nullptr) + ag = targ->auth_group(); + + assert(pg->discovery_filter() != discovery_filter::UNKNOWN); + + if (pg->discovery_filter() >= discovery_filter::PORTAL && + !ag->host_permitted(client_sa)) { + log_debugx("host address does not match addresses " + "allowed for controller \"%s\"; skipping", targ->name()); + return true; + } + + if (pg->discovery_filter() >= discovery_filter::PORTAL_NAME && + !ag->host_permitted(hostnqn) != 0) { + log_debugx("HostNQN does not match NQNs " + "allowed for controller \"%s\"; skipping", targ->name()); + return true; + } + + /* XXX: auth not yet implemented for NVMe */ + + return false; +} + +static bool +portal_uses_wildcard_address(const struct portal *p) +{ + const struct addrinfo *ai = p->ai(); + + switch (ai->ai_family) { + case AF_INET: + { + const struct sockaddr_in *sin; + + sin = (const struct sockaddr_in *)ai->ai_addr; + return sin->sin_addr.s_addr == htonl(INADDR_ANY); + } + case AF_INET6: + { + const struct sockaddr_in6 *sin6; + + sin6 = (const struct sockaddr_in6 *)ai->ai_addr; + return memcmp(&sin6->sin6_addr, &in6addr_any, + sizeof(in6addr_any)) == 0; + } + default: + __assert_unreachable(); + } +} + +static bool +init_discovery_log_entry(struct nvme_discovery_log_entry *entry, + const struct target *target, const struct portal *portal, + const char *wildcard_host) +{ + /* + * The TCP port for I/O controllers might not be fixed, so + * fetch the sockaddr of the socket to determine which port + * the kernel chose. + */ + struct sockaddr_storage ss; + socklen_t len = sizeof(ss); + if (getsockname(portal->socket(), (struct sockaddr *)&ss, &len) == -1) { + log_warn("Failed getsockname building discovery log entry"); + return false; + } + + const struct nvmf_association_params *aparams = + static_cast<const nvmf_portal *>(portal)->aparams(); + + memset(entry, 0, sizeof(*entry)); + entry->trtype = NVMF_TRTYPE_TCP; + int error = getnameinfo((struct sockaddr *)&ss, len, + (char *)entry->traddr, sizeof(entry->traddr), + (char *)entry->trsvcid, sizeof(entry->trsvcid), + NI_NUMERICHOST | NI_NUMERICSERV); + if (error != 0) { + log_warnx("Failed getnameinfo building discovery log entry: %s", + gai_strerror(error)); + return false; + } + + if (portal_uses_wildcard_address(portal)) + strncpy((char *)entry->traddr, wildcard_host, + sizeof(entry->traddr)); + switch (portal->ai()->ai_family) { + case AF_INET: + entry->adrfam = NVMF_ADRFAM_IPV4; + break; + case AF_INET6: + entry->adrfam = NVMF_ADRFAM_IPV6; + break; + default: + __assert_unreachable(); + } + entry->subtype = NVMF_SUBTYPE_NVME; + if (!aparams->sq_flow_control) + entry->treq |= (1 << 2); + entry->portid = htole16(portal->portal_group()->tag()); + entry->cntlid = htole16(NVMF_CNTLID_DYNAMIC); + entry->aqsz = aparams->max_admin_qsize; + strncpy((char *)entry->subnqn, target->name(), sizeof(entry->subnqn)); + return true; +} + +static discovery_log +build_discovery_log_page(const struct portal_group *pg, int fd, + const struct sockaddr *client_sa, + const struct nvmf_fabric_connect_data &data) +{ + discovery_log discovery_log(pg); + + struct sockaddr_storage ss; + socklen_t len = sizeof(ss); + if (getsockname(fd, (struct sockaddr *)&ss, &len) == -1) { + log_warn("build_discovery_log_page: getsockname"); + return discovery_log; + } + + char wildcard_host[NI_MAXHOST]; + int error = getnameinfo((struct sockaddr *)&ss, len, wildcard_host, + sizeof(wildcard_host), NULL, 0, NI_NUMERICHOST); + if (error != 0) { + log_warnx("build_discovery_log_page: getnameinfo: %s", + gai_strerror(error)); + return discovery_log; + } + + const char *nqn = (const char *)data.hostnqn; + std::string hostnqn(nqn, strnlen(nqn, sizeof(data.hostnqn))); + for (const auto &kv : pg->ports()) { + const struct port *port = kv.second; + if (discovery_controller_filtered(pg, client_sa, hostnqn, port)) + continue; + + for (const portal_up &portal : pg->portals()) { + if (portal->protocol() != portal_protocol::NVME_TCP) + continue; + + if (portal_uses_wildcard_address(portal.get()) && + portal->ai()->ai_family != client_sa->sa_family) + continue; + + struct nvme_discovery_log_entry entry; + if (init_discovery_log_entry(&entry, port->target(), + portal.get(), wildcard_host)) + discovery_log.append(&entry); + } + } + + return discovery_log; +} + +bool +discovery_controller::update_cc(uint32_t new_cc) +{ + uint32_t changes; + + if (shutdown) + return false; + if (!nvmf_validate_cc(qp, cap, cc, new_cc)) + return false; + + changes = cc ^ new_cc; + cc = new_cc; + + /* Handle shutdown requests. */ + if (NVMEV(NVME_CC_REG_SHN, changes) != 0 && + NVMEV(NVME_CC_REG_SHN, new_cc) != 0) { + csts &= ~NVMEM(NVME_CSTS_REG_SHST); + csts |= NVMEF(NVME_CSTS_REG_SHST, NVME_SHST_COMPLETE); + shutdown = true; + } + + if (NVMEV(NVME_CC_REG_EN, changes) != 0) { + if (NVMEV(NVME_CC_REG_EN, new_cc) == 0) { + /* Controller reset. */ + csts = 0; + shutdown = true; + } else + csts |= NVMEF(NVME_CSTS_REG_RDY, 1); + } + return true; +} + +void +discovery_controller::handle_property_get(const struct nvmf_capsule *nc, + const struct nvmf_fabric_prop_get_cmd *pget) +{ + struct nvmf_fabric_prop_get_rsp rsp; + + nvmf_init_cqe(&rsp, nc, 0); + + switch (le32toh(pget->ofst)) { + case NVMF_PROP_CAP: + if (pget->attrib.size != NVMF_PROP_SIZE_8) + goto error; + rsp.value.u64 = htole64(cap); + break; + case NVMF_PROP_VS: + if (pget->attrib.size != NVMF_PROP_SIZE_4) + goto error; + rsp.value.u32.low = htole32(vs); + break; + case NVMF_PROP_CC: + if (pget->attrib.size != NVMF_PROP_SIZE_4) + goto error; + rsp.value.u32.low = htole32(cc); + break; + case NVMF_PROP_CSTS: + if (pget->attrib.size != NVMF_PROP_SIZE_4) + goto error; + rsp.value.u32.low = htole32(csts); + break; + default: + goto error; + } + + nvmf_send_response(nc, &rsp); + return; +error: + nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); +} + +void +discovery_controller::handle_property_set(const struct nvmf_capsule *nc, + const struct nvmf_fabric_prop_set_cmd *pset) +{ + switch (le32toh(pset->ofst)) { + case NVMF_PROP_CC: + if (pset->attrib.size != NVMF_PROP_SIZE_4) + goto error; + if (!update_cc(le32toh(pset->value.u32.low))) + goto error; + break; + default: + goto error; + } + + nvmf_send_success(nc); + return; +error: + nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); +} + +void +discovery_controller::handle_fabrics_command(const struct nvmf_capsule *nc, + const struct nvmf_fabric_cmd *fc) +{ + switch (fc->fctype) { + case NVMF_FABRIC_COMMAND_PROPERTY_GET: + handle_property_get(nc, + (const struct nvmf_fabric_prop_get_cmd *)fc); + break; + case NVMF_FABRIC_COMMAND_PROPERTY_SET: + handle_property_set(nc, + (const struct nvmf_fabric_prop_set_cmd *)fc); + break; + case NVMF_FABRIC_COMMAND_CONNECT: + log_warnx("CONNECT command on connected queue"); + nvmf_send_generic_error(nc, NVME_SC_COMMAND_SEQUENCE_ERROR); + break; + case NVMF_FABRIC_COMMAND_DISCONNECT: + log_warnx("DISCONNECT command on admin queue"); + nvmf_send_error(nc, NVME_SCT_COMMAND_SPECIFIC, + NVMF_FABRIC_SC_INVALID_QUEUE_TYPE); + break; + default: + log_warnx("Unsupported fabrics command %#x", fc->fctype); + nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE); + break; + } +} + +void +discovery_controller::handle_identify_command(const struct nvmf_capsule *nc, + const struct nvme_command *cmd) +{ + uint8_t cns; + + cns = le32toh(cmd->cdw10) & 0xFF; + switch (cns) { + case 1: + break; + default: + log_warnx("Unsupported CNS %#x for IDENTIFY", cns); + goto error; + } + + nvmf_send_controller_data(nc, &cdata, sizeof(cdata)); + return; +error: + nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); +} + +void +discovery_controller::handle_get_log_page_command(const struct nvmf_capsule *nc, + const struct nvme_command *cmd) +{ + uint64_t offset; + uint32_t length; + + switch (nvmf_get_log_page_id(cmd)) { + case NVME_LOG_DISCOVERY: + break; + default: + log_warnx("Unsupported log page %u for discovery controller", + nvmf_get_log_page_id(cmd)); + goto error; + } + + offset = nvmf_get_log_page_offset(cmd); + if (offset >= discovery_log.length()) + goto error; + + length = nvmf_get_log_page_length(cmd); + if (length > discovery_log.length() - offset) + length = discovery_log.length() - offset; + + nvmf_send_controller_data(nc, discovery_log.data() + offset, length); + return; +error: + nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); +} + +void +discovery_controller::handle_admin_commands() +{ + for (;;) { + struct nvmf_capsule *nc; + int error = nvmf_controller_receive_capsule(qp, &nc); + if (error != 0) { + if (error != ECONNRESET) + log_warnc(error, + "Failed to read command capsule"); + break; + } + nvmf_capsule_up nc_guard(nc); + + const struct nvme_command *cmd = + (const struct nvme_command *)nvmf_capsule_sqe(nc); + + /* + * Only permit Fabrics commands while a controller is + * disabled. + */ + if (NVMEV(NVME_CC_REG_EN, cc) == 0 && + cmd->opc != NVME_OPC_FABRICS_COMMANDS) { + log_warnx("Unsupported admin opcode %#x while disabled\n", + cmd->opc); + nvmf_send_generic_error(nc, + NVME_SC_COMMAND_SEQUENCE_ERROR); + continue; + } + + switch (cmd->opc) { + case NVME_OPC_FABRICS_COMMANDS: + handle_fabrics_command(nc, + (const struct nvmf_fabric_cmd *)cmd); + break; + case NVME_OPC_IDENTIFY: + handle_identify_command(nc, cmd); + break; + case NVME_OPC_GET_LOG_PAGE: + handle_get_log_page_command(nc, cmd); + break; + default: + log_warnx("Unsupported admin opcode %#x", cmd->opc); + nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE); + break; + } + } +} + +discovery_controller::discovery_controller(freebsd::fd_up fd, + struct nvmf_qpair *qp, const struct discovery_log &discovery_log) : + qp(qp), discovery_log(discovery_log), s(std::move(fd)) +{ + nvmf_init_discovery_controller_data(qp, &cdata); + cap = nvmf_controller_cap(qp); + vs = cdata.ver; +} + +void +nvmf_discovery_portal::handle_connection(freebsd::fd_up fd, + const char *host __unused, const struct sockaddr *client_sa) +{ + struct nvmf_qpair_params qparams; + memset(&qparams, 0, sizeof(qparams)); + qparams.tcp.fd = fd; + + struct nvmf_capsule *nc = NULL; + struct nvmf_fabric_connect_data data; + nvmf_qpair_up qp(nvmf_accept(association(), &qparams, &nc, &data)); + if (!qp) { + log_warnx("Failed to create NVMe discovery qpair: %s", + nvmf_association_error(association())); + return; + } + nvmf_capsule_up nc_guard(nc); + + if (strncmp((char *)data.subnqn, NVMF_DISCOVERY_NQN, + sizeof(data.subnqn)) != 0) { + log_warnx("Discovery NVMe qpair with invalid SubNQN: %.*s", + (int)sizeof(data.subnqn), data.subnqn); + nvmf_connect_invalid_parameters(nc, true, + offsetof(struct nvmf_fabric_connect_data, subnqn)); + return; + } + + /* Just use a controller ID of 1 for all discovery controllers. */ + int error = nvmf_finish_accept(nc, 1); + if (error != 0) { + log_warnc(error, "Failed to send NVMe CONNECT reponse"); + return; + } + nc_guard.reset(); + + discovery_log discovery_log = build_discovery_log_page(portal_group(), + fd, client_sa, data); + + discovery_controller controller(std::move(fd), qp.get(), discovery_log); + controller.handle_admin_commands(); +} diff --git a/usr.sbin/ctld/parse.y b/usr.sbin/ctld/parse.y index 56455d33bd27..5725c16b459a 100644 --- a/usr.sbin/ctld/parse.y +++ b/usr.sbin/ctld/parse.y @@ -54,12 +54,14 @@ extern void yyrestart(FILE *); %} %token ALIAS AUTH_GROUP AUTH_TYPE BACKEND BLOCKSIZE CHAP CHAP_MUTUAL -%token CLOSING_BRACKET CTL_LUN DEBUG DEVICE_ID DEVICE_TYPE -%token DISCOVERY_AUTH_GROUP DISCOVERY_FILTER DSCP FOREIGN +%token CLOSING_BRACKET CONTROLLER CTL_LUN DEBUG DEVICE_ID DEVICE_TYPE +%token DISCOVERY_AUTH_GROUP DISCOVERY_FILTER DISCOVERY_TCP DSCP FOREIGN +%token HOST_ADDRESS HOST_NQN %token INITIATOR_NAME INITIATOR_PORTAL ISNS_SERVER ISNS_PERIOD ISNS_TIMEOUT -%token LISTEN LISTEN_ISER LUN MAXPROC OFFLOAD OPENING_BRACKET OPTION +%token LISTEN LISTEN_ISER LUN MAXPROC NAMESPACE +%token OFFLOAD OPENING_BRACKET OPTION %token PATH PCP PIDFILE PORT PORTAL_GROUP REDIRECT SEMICOLON SERIAL -%token SIZE STR TAG TARGET TIMEOUT +%token SIZE STR TAG TARGET TCP TIMEOUT TRANSPORT_GROUP %token AF11 AF12 AF13 AF21 AF22 AF23 AF31 AF32 AF33 AF41 AF42 AF43 %token BE EF CS0 CS1 CS2 CS3 CS4 CS5 CS6 CS7 @@ -98,14 +100,18 @@ statement: | portal_group | + transport_group + | lun | target + | + controller ; debug: DEBUG STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -120,7 +126,7 @@ debug: DEBUG STR timeout: TIMEOUT STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -135,7 +141,7 @@ timeout: TIMEOUT STR maxproc: MAXPROC STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -172,7 +178,7 @@ isns_server: ISNS_SERVER STR isns_period: ISNS_PERIOD STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -187,7 +193,7 @@ isns_period: ISNS_PERIOD STR isns_timeout: ISNS_TIMEOUT STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -232,6 +238,10 @@ auth_group_entry: | auth_group_chap_mutual | + auth_group_host_address + | + auth_group_host_nqn + | auth_group_initiator_name | auth_group_initiator_portal @@ -274,6 +284,28 @@ auth_group_chap_mutual: CHAP_MUTUAL STR STR STR STR } ; +auth_group_host_address: HOST_ADDRESS STR + { + bool ok; + + ok = auth_group_add_host_address($2); + free($2); + if (!ok) + return (1); + } + ; + +auth_group_host_nqn: HOST_NQN STR + { + bool ok; + + ok = auth_group_add_host_nqn($2); + free($2); + if (!ok) + return (1); + } + ; + auth_group_initiator_name: INITIATOR_NAME STR { bool ok; @@ -432,7 +464,7 @@ portal_group_redirect: REDIRECT STR portal_group_tag: TAG STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -448,7 +480,7 @@ portal_group_tag: TAG STR portal_group_dscp : DSCP STR { - uint64_t tmp; + int64_t tmp; if (strcmp($2, "0x") == 0) { tmp = strtol($2 + 2, NULL, 16); @@ -488,7 +520,7 @@ portal_group_dscp portal_group_pcp: PCP STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -502,6 +534,71 @@ portal_group_pcp: PCP STR } ; +transport_group: TRANSPORT_GROUP transport_group_name + OPENING_BRACKET transport_group_entries CLOSING_BRACKET + { + portal_group_finish(); + } + ; + +transport_group_name: STR + { + bool ok; + + ok = transport_group_start($1); + free($1); + if (!ok) + return (1); + } + ; + +transport_group_entries: + | + transport_group_entries transport_group_entry + | + transport_group_entries transport_group_entry SEMICOLON + ; + +transport_group_entry: + portal_group_discovery_auth_group + | + portal_group_discovery_filter + | + transport_group_listen_discovery_tcp + | + transport_group_listen_tcp + | + portal_group_option + | + portal_group_tag + | + portal_group_dscp + | + portal_group_pcp + ; + +transport_group_listen_discovery_tcp: LISTEN DISCOVERY_TCP STR + { + bool ok; + + ok = transport_group_add_listen_discovery_tcp($3); + free($3); + if (!ok) + return (1); + } + ; + +transport_group_listen_tcp: LISTEN TCP STR + { + bool ok; + + ok = transport_group_add_listen_tcp($3); + free($3); + if (!ok) + return (1); + } + ; + lun: LUN lun_name OPENING_BRACKET lun_entries CLOSING_BRACKET { @@ -704,7 +801,7 @@ target_lun: LUN lun_number lun_number: STR { - uint64_t tmp; + int64_t tmp; if (expand_number($1, &tmp) != 0) { yyerror("invalid numeric value"); @@ -720,7 +817,7 @@ lun_number: STR target_lun_ref: LUN STR STR { - uint64_t tmp; + int64_t tmp; bool ok; if (expand_number($2, &tmp) != 0) { @@ -738,6 +835,133 @@ target_lun_ref: LUN STR STR } ; +controller: CONTROLLER controller_name + OPENING_BRACKET controller_entries CLOSING_BRACKET + { + target_finish(); + } + ; + +controller_name: STR + { + bool ok; + + ok = controller_start($1); + free($1); + if (!ok) + return (1); + } + ; + +controller_entries: + | + controller_entries controller_entry + | + controller_entries controller_entry SEMICOLON + ; + +controller_entry: + target_auth_group + | + target_auth_type + | + controller_host_address + | + controller_host_nqn + | + controller_transport_group + | + controller_namespace + | + controller_namespace_ref + ; + +controller_host_address: HOST_ADDRESS STR + { + bool ok; + + ok = controller_add_host_address($2); + free($2); + if (!ok) + return (1); + } + ; + +controller_host_nqn: HOST_NQN STR + { + bool ok; + + ok = controller_add_host_nqn($2); + free($2); + if (!ok) + return (1); + } + ; + +controller_transport_group: TRANSPORT_GROUP STR STR + { + bool ok; + + ok = target_add_portal_group($2, $3); + free($2); + free($3); + if (!ok) + return (1); + } + | TRANSPORT_GROUP STR + { + bool ok; + + ok = target_add_portal_group($2, NULL); + free($2); + if (!ok) + return (1); + } + ; + +controller_namespace: NAMESPACE ns_number + OPENING_BRACKET lun_entries CLOSING_BRACKET + { + lun_finish(); + } + ; + +ns_number: STR + { + uint64_t tmp; + + if (expand_number($1, &tmp) != 0) { + yyerror("invalid numeric value"); + free($1); + return (1); + } + free($1); + + if (!controller_start_namespace(tmp)) + return (1); + } + ; + +controller_namespace_ref: NAMESPACE STR STR + { + uint64_t tmp; + bool ok; + + if (expand_number($2, &tmp) != 0) { + yyerror("invalid numeric value"); + free($2); + free($3); + return (1); + } + free($2); + + ok = controller_add_namespace(tmp, $3); + free($3); + if (!ok) + return (1); + } + ; + lun_entries: | lun_entries lun_entry @@ -778,7 +1002,7 @@ lun_backend: BACKEND STR lun_blocksize: BLOCKSIZE STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -816,7 +1040,7 @@ lun_device_type: DEVICE_TYPE STR lun_ctl_lun: CTL_LUN STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -866,7 +1090,7 @@ lun_serial: SERIAL STR lun_size: SIZE STR { - uint64_t tmp; + int64_t tmp; if (expand_number($2, &tmp) != 0) { yyerror("invalid numeric value"); @@ -890,20 +1114,14 @@ yyerror(const char *str) } bool -parse_conf(const char *path) +yyparse_conf(FILE *fp) { int error; - yyin = fopen(path, "r"); - if (yyin == NULL) { - log_warn("unable to open configuration file %s", path); - return (false); - } - + yyin = fp; lineno = 1; yyrestart(yyin); error = yyparse(); - fclose(yyin); return (error == 0); } diff --git a/usr.sbin/ctld/token.l b/usr.sbin/ctld/token.l index c8f54103db55..5f959f648969 100644 --- a/usr.sbin/ctld/token.l +++ b/usr.sbin/ctld/token.l @@ -54,21 +54,26 @@ backend { return BACKEND; } blocksize { return BLOCKSIZE; } chap { return CHAP; } chap-mutual { return CHAP_MUTUAL; } +controller { return CONTROLLER; } ctl-lun { return CTL_LUN; } debug { return DEBUG; } device-id { return DEVICE_ID; } device-type { return DEVICE_TYPE; } discovery-auth-group { return DISCOVERY_AUTH_GROUP; } discovery-filter { return DISCOVERY_FILTER; } +discovery-tcp { return DISCOVERY_TCP; } dscp { return DSCP; } pcp { return PCP; } foreign { return FOREIGN; } +host-address { return HOST_ADDRESS; } +host-nqn { return HOST_NQN; } initiator-name { return INITIATOR_NAME; } initiator-portal { return INITIATOR_PORTAL; } listen { return LISTEN; } listen-iser { return LISTEN_ISER; } lun { return LUN; } maxproc { return MAXPROC; } +namespace { return NAMESPACE; } offload { return OFFLOAD; } option { return OPTION; } path { return PATH; } @@ -83,7 +88,9 @@ serial { return SERIAL; } size { return SIZE; } tag { return TAG; } target { return TARGET; } +tcp { return TCP; } timeout { return TIMEOUT; } +transport-group { return TRANSPORT_GROUP; } af11 { return AF11; } af12 { return AF12; } af13 { return AF13; } diff --git a/usr.sbin/ctld/uclparse.cc b/usr.sbin/ctld/uclparse.cc index 1eb9f7736e4b..8a62636ffec6 100644 --- a/usr.sbin/ctld/uclparse.cc +++ b/usr.sbin/ctld/uclparse.cc @@ -37,297 +37,392 @@ #include <stdint.h> #include <stdlib.h> #include <string.h> -#include <ucl.h> +#include <ucl++.h> #include <netinet/in.h> #include <netinet/ip.h> +#include <libutil++.hh> +#include <memory> + #include "conf.h" -#include "ctld.h" - -static bool uclparse_toplevel(const ucl_object_t *); -static bool uclparse_chap(const char *, const ucl_object_t *); -static bool uclparse_chap_mutual(const char *, const ucl_object_t *); -static bool uclparse_lun(const char *, const ucl_object_t *); -static bool uclparse_lun_entries(const char *, const ucl_object_t *); -static bool uclparse_auth_group(const char *, const ucl_object_t *); -static bool uclparse_portal_group(const char *, const ucl_object_t *); -static bool uclparse_target(const char *, const ucl_object_t *); -static bool uclparse_target_portal_group(const char *, const ucl_object_t *); -static bool uclparse_target_lun(const char *, const ucl_object_t *); +#include "ctld.hh" + +struct scope_exit { + using callback = void(); + scope_exit(callback *fn) : fn(fn) {} + + ~scope_exit() { fn(); } + +private: + callback *fn; +}; + +static bool uclparse_toplevel(const ucl::Ucl &); +static bool uclparse_chap(const char *, const ucl::Ucl &); +static bool uclparse_chap_mutual(const char *, const ucl::Ucl &); +static bool uclparse_lun(const char *, const ucl::Ucl &); +static bool uclparse_lun_entries(const char *, const ucl::Ucl &); +static bool uclparse_auth_group(const char *, const ucl::Ucl &); +static bool uclparse_portal_group(const char *, const ucl::Ucl &); +static bool uclparse_transport_group(const char *, const ucl::Ucl &); +static bool uclparse_controller(const char *, const ucl::Ucl &); +static bool uclparse_controller_transport_group(const char *, const ucl::Ucl &); +static bool uclparse_controller_namespace(const char *, const ucl::Ucl &); +static bool uclparse_target(const char *, const ucl::Ucl &); +static bool uclparse_target_portal_group(const char *, const ucl::Ucl &); +static bool uclparse_target_lun(const char *, const ucl::Ucl &); static bool -uclparse_chap(const char *ag_name, const ucl_object_t *obj) +uclparse_chap(const char *ag_name, const ucl::Ucl &obj) { - const ucl_object_t *user, *secret; - - user = ucl_object_find_key(obj, "user"); - if (!user || user->type != UCL_STRING) { + auto user = obj["user"]; + if (!user || user.type() != UCL_STRING) { log_warnx("chap section in auth-group \"%s\" is missing " "\"user\" string key", ag_name); return (false); } - secret = ucl_object_find_key(obj, "secret"); - if (!secret || secret->type != UCL_STRING) { + auto secret = obj["secret"]; + if (!secret || secret.type() != UCL_STRING) { log_warnx("chap section in auth-group \"%s\" is missing " "\"secret\" string key", ag_name); return (false); } return (auth_group_add_chap( - ucl_object_tostring(user), - ucl_object_tostring(secret))); + user.string_value().c_str(), + secret.string_value().c_str())); } static bool -uclparse_chap_mutual(const char *ag_name, const ucl_object_t *obj) +uclparse_chap_mutual(const char *ag_name, const ucl::Ucl &obj) { - const ucl_object_t *user, *secret, *mutual_user; - const ucl_object_t *mutual_secret; - - user = ucl_object_find_key(obj, "user"); - if (!user || user->type != UCL_STRING) { + auto user = obj["user"]; + if (!user || user.type() != UCL_STRING) { log_warnx("chap-mutual section in auth-group \"%s\" is missing " "\"user\" string key", ag_name); return (false); } - secret = ucl_object_find_key(obj, "secret"); - if (!secret || secret->type != UCL_STRING) { + auto secret = obj["secret"]; + if (!secret || secret.type() != UCL_STRING) { log_warnx("chap-mutual section in auth-group \"%s\" is missing " "\"secret\" string key", ag_name); return (false); } - mutual_user = ucl_object_find_key(obj, "mutual-user"); - if (!mutual_user || mutual_user->type != UCL_STRING) { + auto mutual_user = obj["mutual-user"]; + if (!mutual_user || mutual_user.type() != UCL_STRING) { log_warnx("chap-mutual section in auth-group \"%s\" is missing " "\"mutual-user\" string key", ag_name); return (false); } - mutual_secret = ucl_object_find_key(obj, "mutual-secret"); - if (!mutual_secret || mutual_secret->type != UCL_STRING) { + auto mutual_secret = obj["mutual-secret"]; + if (!mutual_secret || mutual_secret.type() != UCL_STRING) { log_warnx("chap-mutual section in auth-group \"%s\" is missing " "\"mutual-secret\" string key", ag_name); return (false); } return (auth_group_add_chap_mutual( - ucl_object_tostring(user), - ucl_object_tostring(secret), - ucl_object_tostring(mutual_user), - ucl_object_tostring(mutual_secret))); + user.string_value().c_str(), + secret.string_value().c_str(), + mutual_user.string_value().c_str(), + mutual_secret.string_value().c_str())); } static bool -uclparse_target_chap(const char *t_name, const ucl_object_t *obj) +uclparse_target_chap(const char *t_name, const ucl::Ucl &obj) { - const ucl_object_t *user, *secret; - - user = ucl_object_find_key(obj, "user"); - if (!user || user->type != UCL_STRING) { + auto user = obj["user"]; + if (!user || user.type() != UCL_STRING) { log_warnx("chap section in target \"%s\" is missing " "\"user\" string key", t_name); return (false); } - secret = ucl_object_find_key(obj, "secret"); - if (!secret || secret->type != UCL_STRING) { + auto secret = obj["secret"]; + if (!secret || secret.type() != UCL_STRING) { log_warnx("chap section in target \"%s\" is missing " "\"secret\" string key", t_name); return (false); } return (target_add_chap( - ucl_object_tostring(user), - ucl_object_tostring(secret))); + user.string_value().c_str(), + secret.string_value().c_str())); } static bool -uclparse_target_chap_mutual(const char *t_name, const ucl_object_t *obj) +uclparse_target_chap_mutual(const char *t_name, const ucl::Ucl &obj) { - const ucl_object_t *user, *secret, *mutual_user; - const ucl_object_t *mutual_secret; - - user = ucl_object_find_key(obj, "user"); - if (!user || user->type != UCL_STRING) { + auto user = obj["user"]; + if (!user || user.type() != UCL_STRING) { log_warnx("chap-mutual section in target \"%s\" is missing " "\"user\" string key", t_name); return (false); } - secret = ucl_object_find_key(obj, "secret"); - if (!secret || secret->type != UCL_STRING) { + auto secret = obj["secret"]; + if (!secret || secret.type() != UCL_STRING) { log_warnx("chap-mutual section in target \"%s\" is missing " "\"secret\" string key", t_name); return (false); } - mutual_user = ucl_object_find_key(obj, "mutual-user"); - if (!mutual_user || mutual_user->type != UCL_STRING) { + auto mutual_user = obj["mutual-user"]; + if (!mutual_user || mutual_user.type() != UCL_STRING) { log_warnx("chap-mutual section in target \"%s\" is missing " "\"mutual-user\" string key", t_name); return (false); } - mutual_secret = ucl_object_find_key(obj, "mutual-secret"); - if (!mutual_secret || mutual_secret->type != UCL_STRING) { + auto mutual_secret = obj["mutual-secret"]; + if (!mutual_secret || mutual_secret.type() != UCL_STRING) { log_warnx("chap-mutual section in target \"%s\" is missing " "\"mutual-secret\" string key", t_name); return (false); } return (target_add_chap_mutual( - ucl_object_tostring(user), - ucl_object_tostring(secret), - ucl_object_tostring(mutual_user), - ucl_object_tostring(mutual_secret))); + user.string_value().c_str(), + secret.string_value().c_str(), + mutual_user.string_value().c_str(), + mutual_secret.string_value().c_str())); } static bool -uclparse_target_portal_group(const char *t_name, const ucl_object_t *obj) +uclparse_target_portal_group(const char *t_name, const ucl::Ucl &obj) { - const ucl_object_t *portal_group, *auth_group; - const char *ag_name; - /* * If the value is a single string, assume it is a * portal-group name. */ - if (obj->type == UCL_STRING) - return (target_add_portal_group(ucl_object_tostring(obj), + if (obj.type() == UCL_STRING) + return (target_add_portal_group(obj.string_value().c_str(), NULL)); - if (obj->type != UCL_OBJECT) { + if (obj.type() != UCL_OBJECT) { log_warnx("portal-group section in target \"%s\" must be " "an object or string", t_name); return (false); } - portal_group = ucl_object_find_key(obj, "name"); - if (!portal_group || portal_group->type != UCL_STRING) { + auto portal_group = obj["name"]; + if (!portal_group || portal_group.type() != UCL_STRING) { log_warnx("portal-group section in target \"%s\" is missing " "\"name\" string key", t_name); return (false); } - auth_group = ucl_object_find_key(obj, "auth-group-name"); - if (auth_group != NULL) { - if (auth_group->type != UCL_STRING) { + auto auth_group = obj["auth-group-name"]; + if (auth_group) { + if (auth_group.type() != UCL_STRING) { log_warnx("\"auth-group-name\" property in " "portal-group section for target \"%s\" is not " "a string", t_name); return (false); } - ag_name = ucl_object_tostring(auth_group); - } else - ag_name = NULL; + return (target_add_portal_group( + portal_group.string_value().c_str(), + auth_group.string_value().c_str())); + } + + return (target_add_portal_group(portal_group.string_value().c_str(), + NULL)); +} + +static bool +uclparse_controller_transport_group(const char *t_name, const ucl::Ucl &obj) +{ + /* + * If the value is a single string, assume it is a + * transport-group name. + */ + if (obj.type() == UCL_STRING) + return target_add_portal_group(obj.string_value().c_str(), + nullptr); + + if (obj.type() != UCL_OBJECT) { + log_warnx("transport-group section in controller \"%s\" must " + "be an object or string", t_name); + return false; + } + + auto portal_group = obj["name"]; + if (!portal_group || portal_group.type() != UCL_STRING) { + log_warnx("transport-group section in controller \"%s\" is " + "missing \"name\" string key", t_name); + return false; + } + + auto auth_group = obj["auth-group-name"]; + if (auth_group) { + if (auth_group.type() != UCL_STRING) { + log_warnx("\"auth-group-name\" property in " + "transport-group section for controller \"%s\" is " + "not a string", t_name); + return false; + } + return target_add_portal_group( + portal_group.string_value().c_str(), + auth_group.string_value().c_str()); + } - return (target_add_portal_group(ucl_object_tostring(portal_group), - ag_name)); + return target_add_portal_group(portal_group.string_value().c_str(), + nullptr); } static bool -uclparse_target_lun(const char *t_name, const ucl_object_t *obj) +uclparse_target_lun(const char *t_name, const ucl::Ucl &obj) { - const ucl_object_t *num; - const ucl_object_t *name; - const char *key; - char *end, *lun_name; + char *end; u_int id; - bool ok; - key = ucl_object_key(obj); - if (key != NULL) { - id = strtoul(key, &end, 0); + std::string key = obj.key(); + if (!key.empty()) { + id = strtoul(key.c_str(), &end, 0); if (*end != '\0') { log_warnx("lun key \"%s\" in target \"%s\" is invalid", - key, t_name); + key.c_str(), t_name); return (false); } - if (obj->type == UCL_STRING) - return (target_add_lun(id, ucl_object_tostring(obj))); + if (obj.type() == UCL_STRING) + return (target_add_lun(id, obj.string_value().c_str())); } - if (obj->type != UCL_OBJECT) { + if (obj.type() != UCL_OBJECT) { log_warnx("lun section entries in target \"%s\" must be objects", t_name); return (false); } - if (key == NULL) { - num = ucl_object_find_key(obj, "number"); - if (num == NULL || num->type != UCL_INT) { + if (key.empty()) { + auto num = obj["number"]; + if (!num || num.type() != UCL_INT) { log_warnx("lun section in target \"%s\" is missing " "\"number\" integer property", t_name); return (false); } - id = ucl_object_toint(num); + id = num.int_value(); } - name = ucl_object_find_key(obj, "name"); - if (name == NULL) { + auto name = obj["name"]; + if (!name) { if (!target_start_lun(id)) return (false); - asprintf(&lun_name, "lun %u for target \"%s\"", id, t_name); - ok = uclparse_lun_entries(lun_name, obj); - free(lun_name); - return (ok); + scope_exit finisher(lun_finish); + std::string lun_name = + freebsd::stringf("lun %u for target \"%s\"", id, t_name); + return (uclparse_lun_entries(lun_name.c_str(), obj)); } - if (name->type != UCL_STRING) { + if (name.type() != UCL_STRING) { log_warnx("\"name\" property for lun %u for target " "\"%s\" is not a string", id, t_name); return (false); } - return (target_add_lun(id, ucl_object_tostring(name))); + return (target_add_lun(id, name.string_value().c_str())); } static bool -uclparse_toplevel(const ucl_object_t *top) +uclparse_controller_namespace(const char *t_name, const ucl::Ucl &obj) { - ucl_object_iter_t it = NULL, iter = NULL; - const ucl_object_t *obj = NULL, *child = NULL; + char *end; + u_int id; + + std::string key = obj.key(); + if (!key.empty()) { + id = strtoul(key.c_str(), &end, 0); + if (*end != '\0') { + log_warnx("namespace key \"%s\" in controller \"%s\"" + " is invalid", key.c_str(), t_name); + return false; + } + + if (obj.type() == UCL_STRING) + return controller_add_namespace(id, + obj.string_value().c_str()); + } + + if (obj.type() != UCL_OBJECT) { + log_warnx("namespace section entries in controller \"%s\"" + " must be objects", t_name); + return false; + } + + if (key.empty()) { + auto num = obj["number"]; + if (!num || num.type() != UCL_INT) { + log_warnx("namespace section in controller \"%s\" is " + "missing \"id\" integer property", t_name); + return (false); + } + id = num.int_value(); + } + + auto name = obj["name"]; + if (!name) { + if (!controller_start_namespace(id)) + return false; + + std::string lun_name = + freebsd::stringf("namespace %u for controller \"%s\"", id, + t_name); + return uclparse_lun_entries(lun_name.c_str(), obj); + } + + if (name.type() != UCL_STRING) { + log_warnx("\"name\" property for namespace %u for " + "controller \"%s\" is not a string", id, t_name); + return (false); + } + return controller_add_namespace(id, name.string_value().c_str()); +} + +static bool +uclparse_toplevel(const ucl::Ucl &top) +{ /* Pass 1 - everything except targets */ - while ((obj = ucl_iterate_object(top, &it, true))) { - const char *key = ucl_object_key(obj); + for (const auto &obj : top) { + std::string key = obj.key(); - if (strcmp(key, "debug") == 0) { - if (obj->type == UCL_INT) - conf_set_debug(ucl_object_toint(obj)); + if (key == "debug") { + if (obj.type() == UCL_INT) + conf_set_debug(obj.int_value()); else { log_warnx("\"debug\" property value is not integer"); return (false); } } - if (strcmp(key, "timeout") == 0) { - if (obj->type == UCL_INT) - conf_set_timeout(ucl_object_toint(obj)); + if (key == "timeout") { + if (obj.type() == UCL_INT) + conf_set_timeout(obj.int_value()); else { log_warnx("\"timeout\" property value is not integer"); return (false); } } - if (strcmp(key, "maxproc") == 0) { - if (obj->type == UCL_INT) - conf_set_maxproc(ucl_object_toint(obj)); + if (key == "maxproc") { + if (obj.type() == UCL_INT) + conf_set_maxproc(obj.int_value()); else { log_warnx("\"maxproc\" property value is not integer"); return (false); } } - if (strcmp(key, "pidfile") == 0) { - if (obj->type == UCL_STRING) { + if (key == "pidfile") { + if (obj.type() == UCL_STRING) { if (!conf_set_pidfile_path( - ucl_object_tostring(obj))) + obj.string_value().c_str())) return (false); } else { log_warnx("\"pidfile\" property value is not string"); @@ -335,16 +430,14 @@ uclparse_toplevel(const ucl_object_t *top) } } - if (strcmp(key, "isns-server") == 0) { - if (obj->type == UCL_ARRAY) { - iter = NULL; - while ((child = ucl_iterate_object(obj, &iter, - true))) { - if (child->type != UCL_STRING) + if (key == "isns-server") { + if (obj.type() == UCL_ARRAY) { + for (const auto &child : obj) { + if (child.type() != UCL_STRING) return (false); if (!isns_add_server( - ucl_object_tostring(child))) + child.string_value().c_str())) return (false); } } else { @@ -354,30 +447,29 @@ uclparse_toplevel(const ucl_object_t *top) } } - if (strcmp(key, "isns-period") == 0) { - if (obj->type == UCL_INT) - conf_set_isns_period(ucl_object_toint(obj)); + if (key == "isns-period") { + if (obj.type() == UCL_INT) + conf_set_isns_period(obj.int_value()); else { log_warnx("\"isns-period\" property value is not integer"); return (false); } } - if (strcmp(key, "isns-timeout") == 0) { - if (obj->type == UCL_INT) - conf_set_isns_timeout(ucl_object_toint(obj)); + if (key == "isns-timeout") { + if (obj.type() == UCL_INT) + conf_set_isns_timeout(obj.int_value()); else { log_warnx("\"isns-timeout\" property value is not integer"); return (false); } } - if (strcmp(key, "auth-group") == 0) { - if (obj->type == UCL_OBJECT) { - iter = NULL; - while ((child = ucl_iterate_object(obj, &iter, true))) { + if (key == "auth-group") { + if (obj.type() == UCL_OBJECT) { + for (const auto &child : obj) { if (!uclparse_auth_group( - ucl_object_key(child), child)) + child.key().c_str(), child)) return (false); } } else { @@ -386,12 +478,11 @@ uclparse_toplevel(const ucl_object_t *top) } } - if (strcmp(key, "portal-group") == 0) { - if (obj->type == UCL_OBJECT) { - iter = NULL; - while ((child = ucl_iterate_object(obj, &iter, true))) { + if (key == "portal-group") { + if (obj.type() == UCL_OBJECT) { + for (const auto &child : obj) { if (!uclparse_portal_group( - ucl_object_key(child), child)) + child.key().c_str(), child)) return (false); } } else { @@ -400,11 +491,23 @@ uclparse_toplevel(const ucl_object_t *top) } } - if (strcmp(key, "lun") == 0) { - if (obj->type == UCL_OBJECT) { - iter = NULL; - while ((child = ucl_iterate_object(obj, &iter, true))) { - if (!uclparse_lun(ucl_object_key(child), + if (key == "transport-group") { + if (obj.type() == UCL_OBJECT) { + for (const auto &child : obj) { + if (!uclparse_transport_group( + child.key().c_str(), child)) + return false; + } + } else { + log_warnx("\"transport-group\" section is not an object"); + return false; + } + } + + if (key == "lun") { + if (obj.type() == UCL_OBJECT) { + for (const auto &child : obj) { + if (!uclparse_lun(child.key().c_str(), child)) return (false); } @@ -416,17 +519,27 @@ uclparse_toplevel(const ucl_object_t *top) } /* Pass 2 - targets */ - it = NULL; - while ((obj = ucl_iterate_object(top, &it, true))) { - const char *key = ucl_object_key(obj); - - if (strcmp(key, "target") == 0) { - if (obj->type == UCL_OBJECT) { - iter = NULL; - while ((child = ucl_iterate_object(obj, &iter, - true))) { + for (const auto &obj : top) { + std::string key = obj.key(); + + if (key == "controller") { + if (obj.type() == UCL_OBJECT) { + for (const auto &child : obj) { + if (!uclparse_controller( + child.key().c_str(), child)) + return false; + } + } else { + log_warnx("\"controller\" section is not an object"); + return false; + } + } + + if (key == "target") { + if (obj.type() == UCL_OBJECT) { + for (const auto &child : obj) { if (!uclparse_target( - ucl_object_key(child), child)) + child.key().c_str(), child)) return (false); } } else { @@ -440,179 +553,190 @@ uclparse_toplevel(const ucl_object_t *top) } static bool -uclparse_auth_group(const char *name, const ucl_object_t *top) +uclparse_auth_group(const char *name, const ucl::Ucl &top) { - ucl_object_iter_t it = NULL, it2 = NULL; - const ucl_object_t *obj = NULL, *tmp = NULL; - const char *key; - if (!auth_group_start(name)) return (false); - while ((obj = ucl_iterate_object(top, &it, true))) { - key = ucl_object_key(obj); + scope_exit finisher(auth_group_finish); + for (const auto &obj : top) { + std::string key = obj.key(); - if (strcmp(key, "auth-type") == 0) { - const char *value = ucl_object_tostring(obj); - - if (!auth_group_set_type(value)) - goto fail; + if (key == "auth-type") { + if (!auth_group_set_type(obj.string_value().c_str())) + return false; } - if (strcmp(key, "chap") == 0) { - if (obj->type == UCL_OBJECT) { + if (key == "chap") { + if (obj.type() == UCL_OBJECT) { if (!uclparse_chap(name, obj)) - goto fail; - } else if (obj->type == UCL_ARRAY) { - it2 = NULL; - while ((tmp = ucl_iterate_object(obj, &it2, - true))) { + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { if (!uclparse_chap(name, tmp)) - goto fail; + return false; } } else { log_warnx("\"chap\" property of auth-group " "\"%s\" is not an array or object", name); - goto fail; + return false; } } - if (strcmp(key, "chap-mutual") == 0) { - if (obj->type == UCL_OBJECT) { + if (key == "chap-mutual") { + if (obj.type() == UCL_OBJECT) { if (!uclparse_chap_mutual(name, obj)) - goto fail; - } else if (obj->type == UCL_ARRAY) { - it2 = NULL; - while ((tmp = ucl_iterate_object(obj, &it2, - true))) { + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { if (!uclparse_chap_mutual(name, tmp)) - goto fail; + return false; } } else { log_warnx("\"chap-mutual\" property of " "auth-group \"%s\" is not an array or object", name); - goto fail; + return false; } } - if (strcmp(key, "initiator-name") == 0) { - if (obj->type == UCL_STRING) { - const char *value = ucl_object_tostring(obj); + if (key == "host-address") { + if (obj.type() == UCL_STRING) { + if (!auth_group_add_host_address( + obj.string_value().c_str())) + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { + if (!auth_group_add_host_address( + tmp.string_value().c_str())) + return false; + } + } else { + log_warnx("\"host-address\" property of " + "auth-group \"%s\" is not an array or string", + name); + return false; + } + } - if (!auth_group_add_initiator_name(value)) - goto fail; - } else if (obj->type == UCL_ARRAY) { - it2 = NULL; - while ((tmp = ucl_iterate_object(obj, &it2, - true))) { - const char *value = - ucl_object_tostring(tmp); + if (key == "host-nqn") { + if (obj.type() == UCL_STRING) { + if (!auth_group_add_host_nqn( + obj.string_value().c_str())) + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { + if (!auth_group_add_host_nqn( + tmp.string_value().c_str())) + return false; + } + } else { + log_warnx("\"host-nqn\" property of " + "auth-group \"%s\" is not an array or string", + name); + return false; + } + } + if (key == "initiator-name") { + if (obj.type() == UCL_STRING) { + if (!auth_group_add_initiator_name( + obj.string_value().c_str())) + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { if (!auth_group_add_initiator_name( - value)) - goto fail; + tmp.string_value().c_str())) + return false; } } else { log_warnx("\"initiator-name\" property of " "auth-group \"%s\" is not an array or string", name); - goto fail; + return false; } } - if (strcmp(key, "initiator-portal") == 0) { - if (obj->type == UCL_STRING) { - const char *value = ucl_object_tostring(obj); - - if (!auth_group_add_initiator_portal(value)) - goto fail; - } else if (obj->type == UCL_ARRAY) { - it2 = NULL; - while ((tmp = ucl_iterate_object(obj, &it2, - true))) { - const char *value = - ucl_object_tostring(tmp); - + if (key == "initiator-portal") { + if (obj.type() == UCL_STRING) { + if (!auth_group_add_initiator_portal( + obj.string_value().c_str())) + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { if (!auth_group_add_initiator_portal( - value)) - goto fail; + tmp.string_value().c_str())) + return false; } } else { log_warnx("\"initiator-portal\" property of " "auth-group \"%s\" is not an array or string", name); - goto fail; + return false; } } } - auth_group_finish(); return (true); -fail: - auth_group_finish(); - return (false); } static bool uclparse_dscp(const char *group_type, const char *pg_name, - const ucl_object_t *obj) + const ucl::Ucl &obj) { - const char *key; - - if ((obj->type != UCL_STRING) && (obj->type != UCL_INT)) { + if ((obj.type() != UCL_STRING) && (obj.type() != UCL_INT)) { log_warnx("\"dscp\" property of %s group \"%s\" is not a " "string or integer", group_type, pg_name); return (false); } - if (obj->type == UCL_INT) - return (portal_group_set_dscp(ucl_object_toint(obj))); + if (obj.type() == UCL_INT) + return (portal_group_set_dscp(obj.int_value())); - key = ucl_object_tostring(obj); - if (strcmp(key, "be") == 0 || strcmp(key, "cs0") == 0) + std::string key = obj.key(); + if (key == "be" || key == "cs0") portal_group_set_dscp(IPTOS_DSCP_CS0 >> 2); - else if (strcmp(key, "ef") == 0) + else if (key == "ef") portal_group_set_dscp(IPTOS_DSCP_EF >> 2); - else if (strcmp(key, "cs0") == 0) + else if (key == "cs0") portal_group_set_dscp(IPTOS_DSCP_CS0 >> 2); - else if (strcmp(key, "cs1") == 0) + else if (key == "cs1") portal_group_set_dscp(IPTOS_DSCP_CS1 >> 2); - else if (strcmp(key, "cs2") == 0) + else if (key == "cs2") portal_group_set_dscp(IPTOS_DSCP_CS2 >> 2); - else if (strcmp(key, "cs3") == 0) + else if (key == "cs3") portal_group_set_dscp(IPTOS_DSCP_CS3 >> 2); - else if (strcmp(key, "cs4") == 0) + else if (key == "cs4") portal_group_set_dscp(IPTOS_DSCP_CS4 >> 2); - else if (strcmp(key, "cs5") == 0) + else if (key == "cs5") portal_group_set_dscp(IPTOS_DSCP_CS5 >> 2); - else if (strcmp(key, "cs6") == 0) + else if (key == "cs6") portal_group_set_dscp(IPTOS_DSCP_CS6 >> 2); - else if (strcmp(key, "cs7") == 0) + else if (key == "cs7") portal_group_set_dscp(IPTOS_DSCP_CS7 >> 2); - else if (strcmp(key, "af11") == 0) + else if (key == "af11") portal_group_set_dscp(IPTOS_DSCP_AF11 >> 2); - else if (strcmp(key, "af12") == 0) + else if (key == "af12") portal_group_set_dscp(IPTOS_DSCP_AF12 >> 2); - else if (strcmp(key, "af13") == 0) + else if (key == "af13") portal_group_set_dscp(IPTOS_DSCP_AF13 >> 2); - else if (strcmp(key, "af21") == 0) + else if (key == "af21") portal_group_set_dscp(IPTOS_DSCP_AF21 >> 2); - else if (strcmp(key, "af22") == 0) + else if (key == "af22") portal_group_set_dscp(IPTOS_DSCP_AF22 >> 2); - else if (strcmp(key, "af23") == 0) + else if (key == "af23") portal_group_set_dscp(IPTOS_DSCP_AF23 >> 2); - else if (strcmp(key, "af31") == 0) + else if (key == "af31") portal_group_set_dscp(IPTOS_DSCP_AF31 >> 2); - else if (strcmp(key, "af32") == 0) + else if (key == "af32") portal_group_set_dscp(IPTOS_DSCP_AF32 >> 2); - else if (strcmp(key, "af33") == 0) + else if (key == "af33") portal_group_set_dscp(IPTOS_DSCP_AF33 >> 2); - else if (strcmp(key, "af41") == 0) + else if (key == "af41") portal_group_set_dscp(IPTOS_DSCP_AF41 >> 2); - else if (strcmp(key, "af42") == 0) + else if (key == "af42") portal_group_set_dscp(IPTOS_DSCP_AF42 >> 2); - else if (strcmp(key, "af43") == 0) + else if (key == "af43") portal_group_set_dscp(IPTOS_DSCP_AF43 >> 2); else { log_warnx("\"dscp\" property value is not a supported textual value"); @@ -623,499 +747,684 @@ uclparse_dscp(const char *group_type, const char *pg_name, static bool uclparse_pcp(const char *group_type, const char *pg_name, - const ucl_object_t *obj) + const ucl::Ucl &obj) { - if (obj->type != UCL_INT) { + if (obj.type() != UCL_INT) { log_warnx("\"pcp\" property of %s group \"%s\" is not an " "integer", group_type, pg_name); return (false); } - return (portal_group_set_pcp(ucl_object_toint(obj))); + return (portal_group_set_pcp(obj.int_value())); } static bool -uclparse_portal_group(const char *name, const ucl_object_t *top) +uclparse_portal_group(const char *name, const ucl::Ucl &top) { - ucl_object_iter_t it = NULL, it2 = NULL; - const ucl_object_t *obj = NULL, *tmp = NULL; - const char *key; - if (!portal_group_start(name)) return (false); - while ((obj = ucl_iterate_object(top, &it, true))) { - key = ucl_object_key(obj); + scope_exit finisher(portal_group_finish); + for (const auto &obj : top) { + std::string key = obj.key(); - if (strcmp(key, "discovery-auth-group") == 0) { - if (obj->type != UCL_STRING) { + if (key == "discovery-auth-group") { + if (obj.type() != UCL_STRING) { log_warnx("\"discovery-auth-group\" property " "of portal-group \"%s\" is not a string", name); - goto fail; + return false; } if (!portal_group_set_discovery_auth_group( - ucl_object_tostring(obj))) - goto fail; + obj.string_value().c_str())) + return false; } - if (strcmp(key, "discovery-filter") == 0) { - if (obj->type != UCL_STRING) { + if (key == "discovery-filter") { + if (obj.type() != UCL_STRING) { log_warnx("\"discovery-filter\" property of " "portal-group \"%s\" is not a string", name); - goto fail; + return false; } - if (!portal_group_set_filter(ucl_object_tostring(obj))) - goto fail; + if (!portal_group_set_filter( + obj.string_value().c_str())) + return false; } - if (strcmp(key, "foreign") == 0) { + if (key == "foreign") { portal_group_set_foreign(); } - if (strcmp(key, "listen") == 0) { - if (obj->type == UCL_STRING) { + if (key == "listen") { + if (obj.type() == UCL_STRING) { if (!portal_group_add_listen( - ucl_object_tostring(obj), false)) - goto fail; - } else if (obj->type == UCL_ARRAY) { - while ((tmp = ucl_iterate_object(obj, &it2, - true))) { + obj.string_value().c_str(), false)) + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { if (!portal_group_add_listen( - ucl_object_tostring(tmp), + tmp.string_value().c_str(), false)) - goto fail; + return false; } } else { log_warnx("\"listen\" property of " "portal-group \"%s\" is not a string", name); - goto fail; + return false; } } - if (strcmp(key, "listen-iser") == 0) { - if (obj->type == UCL_STRING) { + if (key == "listen-iser") { + if (obj.type() == UCL_STRING) { if (!portal_group_add_listen( - ucl_object_tostring(obj), true)) - goto fail; - } else if (obj->type == UCL_ARRAY) { - while ((tmp = ucl_iterate_object(obj, &it2, - true))) { + obj.string_value().c_str(), true)) + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { if (!portal_group_add_listen( - ucl_object_tostring(tmp), + tmp.string_value().c_str(), true)) - goto fail; + return false; } } else { log_warnx("\"listen\" property of " "portal-group \"%s\" is not a string", name); - goto fail; + return false; } } - if (strcmp(key, "offload") == 0) { - if (obj->type != UCL_STRING) { + if (key == "offload") { + if (obj.type() != UCL_STRING) { log_warnx("\"offload\" property of " "portal-group \"%s\" is not a string", name); - goto fail; + return false; } - if (!portal_group_set_offload(ucl_object_tostring(obj))) - goto fail; + if (!portal_group_set_offload( + obj.string_value().c_str())) + return false; } - if (strcmp(key, "redirect") == 0) { - if (obj->type != UCL_STRING) { + if (key == "redirect") { + if (obj.type() != UCL_STRING) { log_warnx("\"listen\" property of " "portal-group \"%s\" is not a string", name); - goto fail; + return false; } if (!portal_group_set_redirection( - ucl_object_tostring(obj))) - goto fail; + obj.string_value().c_str())) + return false; } - if (strcmp(key, "options") == 0) { - if (obj->type != UCL_OBJECT) { + if (key == "options") { + if (obj.type() != UCL_OBJECT) { log_warnx("\"options\" property of portal group " "\"%s\" is not an object", name); - goto fail; + return false; } - while ((tmp = ucl_iterate_object(obj, &it2, - true))) { + for (const auto &tmp : obj) { if (!portal_group_add_option( - ucl_object_key(tmp), - ucl_object_tostring_forced(tmp))) - goto fail; + tmp.key().c_str(), + tmp.forced_string_value().c_str())) + return false; } } - if (strcmp(key, "tag") == 0) { - if (obj->type != UCL_INT) { + if (key == "tag") { + if (obj.type() != UCL_INT) { log_warnx("\"tag\" property of portal group " "\"%s\" is not an integer", name); - goto fail; + return false; } - portal_group_set_tag(ucl_object_toint(obj)); + portal_group_set_tag(obj.int_value()); } - if (strcmp(key, "dscp") == 0) { + if (key == "dscp") { if (!uclparse_dscp("portal", name, obj)) - goto fail; + return false; } - if (strcmp(key, "pcp") == 0) { + if (key == "pcp") { if (!uclparse_pcp("portal", name, obj)) - goto fail; + return false; } } - portal_group_finish(); return (true); -fail: - portal_group_finish(); - return (false); } static bool -uclparse_target(const char *name, const ucl_object_t *top) +uclparse_transport_listen_obj(const char *pg_name, const ucl::Ucl &top) +{ + for (const auto &obj : top) { + std::string key = obj.key(); + + if (key.empty()) { + log_warnx("missing protocol for \"listen\" " + "property of transport-group \"%s\"", pg_name); + return false; + } + + if (key == "tcp") { + if (obj.type() == UCL_STRING) { + if (!transport_group_add_listen_tcp( + obj.string_value().c_str())) + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { + if (!transport_group_add_listen_tcp( + tmp.string_value().c_str())) + return false; + } + } + } else if (key == "discovery-tcp") { + if (obj.type() == UCL_STRING) { + if (!transport_group_add_listen_discovery_tcp( + obj.string_value().c_str())) + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { + if (!transport_group_add_listen_discovery_tcp( + tmp.string_value().c_str())) + return false; + } + } + } else { + log_warnx("invalid listen protocol \"%s\" for " + "transport-group \"%s\"", key.c_str(), pg_name); + return false; + } + } + return true; +} + +static bool +uclparse_transport_group(const char *name, const ucl::Ucl &top) +{ + if (!transport_group_start(name)) + return false; + + scope_exit finisher(portal_group_finish); + for (const auto &obj : top) { + std::string key = obj.key(); + + if (key == "discovery-auth-group") { + if (obj.type() != UCL_STRING) { + log_warnx("\"discovery-auth-group\" property " + "of transport-group \"%s\" is not a string", + name); + return false; + } + + if (!portal_group_set_discovery_auth_group( + obj.string_value().c_str())) + return false; + } + + if (key == "discovery-filter") { + if (obj.type() != UCL_STRING) { + log_warnx("\"discovery-filter\" property of " + "transport-group \"%s\" is not a string", + name); + return false; + } + + if (!portal_group_set_filter( + obj.string_value().c_str())) + return false; + } + + if (key == "listen") { + if (obj.type() != UCL_OBJECT) { + log_warnx("\"listen\" property of " + "transport-group \"%s\" is not an object", + name); + return false; + } + if (!uclparse_transport_listen_obj(name, obj)) + return false; + } + + if (key == "options") { + if (obj.type() != UCL_OBJECT) { + log_warnx("\"options\" property of transport group " + "\"%s\" is not an object", name); + return false; + } + + for (const auto &tmp : obj) { + if (!portal_group_add_option( + tmp.key().c_str(), + tmp.forced_string_value().c_str())) + return false; + } + } + + if (key == "dscp") { + if (!uclparse_dscp("transport", name, obj)) + return false; + } + + if (key == "pcp") { + if (!uclparse_pcp("transport", name, obj)) + return false; + } + } + + return true; +} + +static bool +uclparse_controller(const char *name, const ucl::Ucl &top) { - ucl_object_iter_t it = NULL, it2 = NULL; - const ucl_object_t *obj = NULL, *tmp = NULL; - const char *key; + if (!controller_start(name)) + return false; + + scope_exit finisher(target_finish); + for (const auto &obj : top) { + std::string key = obj.key(); + + if (key == "auth-group") { + if (obj.type() != UCL_STRING) { + log_warnx("\"auth-group\" property of " + "controller \"%s\" is not a string", name); + return false; + } + + if (!target_set_auth_group(obj.string_value().c_str())) + return false; + } + + if (key == "auth-type") { + if (obj.type() != UCL_STRING) { + log_warnx("\"auth-type\" property of " + "controller \"%s\" is not a string", name); + return false; + } + + if (!target_set_auth_type(obj.string_value().c_str())) + return false; + } + + if (key == "host-address") { + if (obj.type() == UCL_STRING) { + if (!controller_add_host_address( + obj.string_value().c_str())) + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { + if (!controller_add_host_address( + tmp.string_value().c_str())) + return false; + } + } else { + log_warnx("\"host-address\" property of " + "controller \"%s\" is not an array or " + "string", name); + return false; + } + } + + if (key == "host-nqn") { + if (obj.type() == UCL_STRING) { + if (!controller_add_host_nqn( + obj.string_value().c_str())) + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { + if (!controller_add_host_nqn( + tmp.string_value().c_str())) + return false; + } + } else { + log_warnx("\"host-nqn\" property of " + "controller \"%s\" is not an array or " + "string", name); + return false; + } + } + + if (key == "transport-group") { + if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { + if (!uclparse_controller_transport_group(name, + tmp)) + return false; + } + } else { + if (!uclparse_controller_transport_group(name, + obj)) + return false; + } + } + + if (key == "namespace") { + for (const auto &tmp : obj) { + if (!uclparse_controller_namespace(name, tmp)) + return false; + } + } + } + + return true; +} +static bool +uclparse_target(const char *name, const ucl::Ucl &top) +{ if (!target_start(name)) return (false); - while ((obj = ucl_iterate_object(top, &it, true))) { - key = ucl_object_key(obj); + scope_exit finisher(target_finish); + for (const auto &obj : top) { + std::string key = obj.key(); - if (strcmp(key, "alias") == 0) { - if (obj->type != UCL_STRING) { + if (key == "alias") { + if (obj.type() != UCL_STRING) { log_warnx("\"alias\" property of target " "\"%s\" is not a string", name); - goto fail; + return false; } - if (!target_set_alias(ucl_object_tostring(obj))) - goto fail; + if (!target_set_alias(obj.string_value().c_str())) + return false; } - if (strcmp(key, "auth-group") == 0) { - if (obj->type != UCL_STRING) { + if (key == "auth-group") { + if (obj.type() != UCL_STRING) { log_warnx("\"auth-group\" property of target " "\"%s\" is not a string", name); - goto fail; + return false; } - if (!target_set_auth_group(ucl_object_tostring(obj))) - goto fail; + if (!target_set_auth_group(obj.string_value().c_str())) + return false; } - if (strcmp(key, "auth-type") == 0) { - if (obj->type != UCL_STRING) { + if (key == "auth-type") { + if (obj.type() != UCL_STRING) { log_warnx("\"auth-type\" property of target " "\"%s\" is not a string", name); - goto fail; + return false; } - if (!target_set_auth_type(ucl_object_tostring(obj))) - goto fail; + if (!target_set_auth_type(obj.string_value().c_str())) + return false; } - if (strcmp(key, "chap") == 0) { - if (obj->type == UCL_OBJECT) { + if (key == "chap") { + if (obj.type() == UCL_OBJECT) { if (!uclparse_target_chap(name, obj)) - goto fail; - } else if (obj->type == UCL_ARRAY) { - while ((tmp = ucl_iterate_object(obj, &it2, - true))) { + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { if (!uclparse_target_chap(name, tmp)) - goto fail; + return false; } } else { log_warnx("\"chap\" property of target " "\"%s\" is not an array or object", name); - goto fail; + return false; } } - if (strcmp(key, "chap-mutual") == 0) { - if (obj->type == UCL_OBJECT) { + if (key == "chap-mutual") { + if (obj.type() == UCL_OBJECT) { if (!uclparse_target_chap_mutual(name, obj)) - goto fail; - } else if (obj->type == UCL_ARRAY) { - while ((tmp = ucl_iterate_object(obj, &it2, - true))) { + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { if (!uclparse_target_chap_mutual(name, tmp)) - goto fail; + return false; } } else { log_warnx("\"chap-mutual\" property of target " "\"%s\" is not an array or object", name); - goto fail; + return false; } } - if (strcmp(key, "initiator-name") == 0) { - if (obj->type == UCL_STRING) { + if (key == "initiator-name") { + if (obj.type() == UCL_STRING) { if (!target_add_initiator_name( - ucl_object_tostring(obj))) - goto fail; - } else if (obj->type == UCL_ARRAY) { - while ((tmp = ucl_iterate_object(obj, &it2, - true))) { + obj.string_value().c_str())) + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { if (!target_add_initiator_name( - ucl_object_tostring(tmp))) - goto fail; + tmp.string_value().c_str())) + return false; } } else { log_warnx("\"initiator-name\" property of " "target \"%s\" is not an array or string", name); - goto fail; + return false; } } - if (strcmp(key, "initiator-portal") == 0) { - if (obj->type == UCL_STRING) { + if (key == "initiator-portal") { + if (obj.type() == UCL_STRING) { if (!target_add_initiator_portal( - ucl_object_tostring(obj))) - goto fail; - } else if (obj->type == UCL_ARRAY) { - while ((tmp = ucl_iterate_object(obj, &it2, - true))) { + obj.string_value().c_str())) + return false; + } else if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { if (!target_add_initiator_portal( - ucl_object_tostring(tmp))) - goto fail; + tmp.string_value().c_str())) + return false; } } else { log_warnx("\"initiator-portal\" property of " "target \"%s\" is not an array or string", name); - goto fail; + return false; } } - if (strcmp(key, "portal-group") == 0) { - if (obj->type == UCL_ARRAY) { - while ((tmp = ucl_iterate_object(obj, &it2, - true))) { + if (key == "portal-group") { + if (obj.type() == UCL_ARRAY) { + for (const auto &tmp : obj) { if (!uclparse_target_portal_group(name, tmp)) - goto fail; + return false; } } else { if (!uclparse_target_portal_group(name, obj)) - goto fail; + return false; } } - if (strcmp(key, "port") == 0) { - if (obj->type != UCL_STRING) { + if (key == "port") { + if (obj.type() != UCL_STRING) { log_warnx("\"port\" property of target " "\"%s\" is not a string", name); - goto fail; + return false; } - if (!target_set_physical_port(ucl_object_tostring(obj))) - goto fail; + if (!target_set_physical_port(obj.string_value().c_str())) + return false; } - if (strcmp(key, "redirect") == 0) { - if (obj->type != UCL_STRING) { + if (key == "redirect") { + if (obj.type() != UCL_STRING) { log_warnx("\"redirect\" property of target " "\"%s\" is not a string", name); - goto fail; + return false; } - if (!target_set_redirection(ucl_object_tostring(obj))) - goto fail; + if (!target_set_redirection(obj.string_value().c_str())) + return false; } - if (strcmp(key, "lun") == 0) { - while ((tmp = ucl_iterate_object(obj, &it2, true))) { + if (key == "lun") { + for (const auto &tmp : obj) { if (!uclparse_target_lun(name, tmp)) - goto fail; + return false; } } } - target_finish(); return (true); -fail: - target_finish(); - return (false); } static bool -uclparse_lun(const char *name, const ucl_object_t *top) +uclparse_lun(const char *name, const ucl::Ucl &top) { - char *lun_name; - bool ok; - if (!lun_start(name)) return (false); - asprintf(&lun_name, "lun \"%s\"", name); - ok = uclparse_lun_entries(lun_name, top); - free(lun_name); - return (ok); + + scope_exit finisher(lun_finish); + std::string lun_name = freebsd::stringf("lun \"%s\"", name); + return (uclparse_lun_entries(lun_name.c_str(), top)); } static bool -uclparse_lun_entries(const char *name, const ucl_object_t *top) +uclparse_lun_entries(const char *name, const ucl::Ucl &top) { - ucl_object_iter_t it = NULL, child_it = NULL; - const ucl_object_t *obj = NULL, *child = NULL; - const char *key; - - while ((obj = ucl_iterate_object(top, &it, true))) { - key = ucl_object_key(obj); + for (const auto &obj : top) { + std::string key = obj.key(); - if (strcmp(key, "backend") == 0) { - if (obj->type != UCL_STRING) { + if (key == "backend") { + if (obj.type() != UCL_STRING) { log_warnx("\"backend\" property of %s " "is not a string", name); - goto fail; + return false; } - if (!lun_set_backend(ucl_object_tostring(obj))) - goto fail; + if (!lun_set_backend(obj.string_value().c_str())) + return false; } - if (strcmp(key, "blocksize") == 0) { - if (obj->type != UCL_INT) { + if (key == "blocksize") { + if (obj.type() != UCL_INT) { log_warnx("\"blocksize\" property of %s " "is not an integer", name); - goto fail; + return false; } - if (!lun_set_blocksize(ucl_object_toint(obj))) - goto fail; + if (!lun_set_blocksize(obj.int_value())) + return false; } - if (strcmp(key, "device-id") == 0) { - if (obj->type != UCL_STRING) { + if (key == "device-id") { + if (obj.type() != UCL_STRING) { log_warnx("\"device-id\" property of %s " "is not an integer", name); - goto fail; + return false; } - if (!lun_set_device_id(ucl_object_tostring(obj))) - goto fail; + if (!lun_set_device_id(obj.string_value().c_str())) + return false; } - if (strcmp(key, "device-type") == 0) { - if (obj->type != UCL_STRING) { + if (key == "device-type") { + if (obj.type() != UCL_STRING) { log_warnx("\"device-type\" property of %s " "is not an integer", name); - goto fail; + return false; } - if (!lun_set_device_type(ucl_object_tostring(obj))) - goto fail; + if (!lun_set_device_type(obj.string_value().c_str())) + return false; } - if (strcmp(key, "ctl-lun") == 0) { - if (obj->type != UCL_INT) { + if (key == "ctl-lun") { + if (obj.type() != UCL_INT) { log_warnx("\"ctl-lun\" property of %s " "is not an integer", name); - goto fail; + return false; } - if (!lun_set_ctl_lun(ucl_object_toint(obj))) - goto fail; + if (!lun_set_ctl_lun(obj.int_value())) + return false; } - if (strcmp(key, "options") == 0) { - if (obj->type != UCL_OBJECT) { + if (key == "options") { + if (obj.type() != UCL_OBJECT) { log_warnx("\"options\" property of %s " "is not an object", name); - goto fail; + return false; } - while ((child = ucl_iterate_object(obj, &child_it, - true))) { - if (!lun_add_option(ucl_object_key(child), - ucl_object_tostring_forced(child))) - goto fail; + for (const auto &child : obj) { + if (!lun_add_option(child.key().c_str(), + child.forced_string_value().c_str())) + return false; } } - if (strcmp(key, "path") == 0) { - if (obj->type != UCL_STRING) { + if (key == "path") { + if (obj.type() != UCL_STRING) { log_warnx("\"path\" property of %s " "is not a string", name); - goto fail; + return false; } - if (!lun_set_path(ucl_object_tostring(obj))) - goto fail; + if (!lun_set_path(obj.string_value().c_str())) + return false; } - if (strcmp(key, "serial") == 0) { - if (obj->type != UCL_STRING) { + if (key == "serial") { + if (obj.type() != UCL_STRING) { log_warnx("\"serial\" property of %s " "is not a string", name); - goto fail; + return false; } - if (!lun_set_serial(ucl_object_tostring(obj))) - goto fail; + if (!lun_set_serial(obj.string_value().c_str())) + return false; } - if (strcmp(key, "size") == 0) { - if (obj->type != UCL_INT) { + if (key == "size") { + if (obj.type() != UCL_INT) { log_warnx("\"size\" property of %s " "is not an integer", name); - goto fail; + return false; } - if (!lun_set_size(ucl_object_toint(obj))) - goto fail; + if (!lun_set_size(obj.int_value())) + return false; } } - lun_finish(); return (true); -fail: - lun_finish(); - return (false); } bool uclparse_conf(const char *path) { - struct ucl_parser *parser; - ucl_object_t *top; - bool parsed; - - parser = ucl_parser_new(0); - - if (!ucl_parser_add_file(parser, path)) { - log_warn("unable to parse configuration file %s: %s", path, - ucl_parser_get_error(parser)); - ucl_parser_free(parser); + std::string err; + ucl::Ucl top = ucl::Ucl::parse_from_file(path, err); + if (!top) { + log_warnx("unable to parse configuration file %s: %s", path, + err.c_str()); return (false); } - top = ucl_parser_get_object(parser); - parsed = uclparse_toplevel(top); - ucl_object_unref(top); - ucl_parser_free(parser); + bool parsed; + try { + parsed = uclparse_toplevel(top); + } catch (std::bad_alloc &) { + log_warnx("failed to allocate memory parsing %s", path); + parsed = false; + } catch (...) { + log_warnx("unknown exception parsing %s", path); + parsed = false; + } return (parsed); } diff --git a/usr.sbin/cxgbetool/cxgbetool.8 b/usr.sbin/cxgbetool/cxgbetool.8 index a5f345c6b781..f0dee1830a5a 100644 --- a/usr.sbin/cxgbetool/cxgbetool.8 +++ b/usr.sbin/cxgbetool/cxgbetool.8 @@ -1,3 +1,6 @@ +.\" +.\" SPDX-License-Identifier: BSD-3-Clause +.\" .\" Copyright (c) 2015, 2018 Chelsio Inc .\" All rights reserved. .\" @@ -29,68 +32,93 @@ .\" .\" * Other names and brands may be claimed as the property of others. .\" -.Dd December 10, 2024 +.Dd May 11, 2025 .Dt CXGBETOOL 8 .Os .Sh NAME .Nm cxgbetool -.Nd Userspace companion to -.Xr cxgbe 4 +.Nd userspace companion to Chelsio cxgbe Ethernet driver .Sh SYNOPSIS .Bl -item -compact .It -.Nm Ar nexus command Op Ar parameter ... +.Nm +.Ar nexus command Op Ar parameter ... .Pp .It -.Nm Ar nexus Cm clearstats Ar port_id +.Nm +.Ar nexus Cm clearstats Ar port_id .It -.Nm Ar nexus Cm clip Bro Cm hold | release Brc Ar ipv6-address +.Nm +.Ar nexus Cm clip Bro Cm hold | release Brc Ar ipv6-address .It -.Nm Ar nexus Cm clip Cm list +.Nm +.Ar nexus Cm clip Cm list .It -.Nm Ar nexus Cm context Bro Cm ingress | egress | fl | cong Brc Ar cntxt_id +.Nm +.Ar nexus Cm context Bro Cm ingress | egress | fl | cong Brc Ar cntxt_id .It -.Nm Ar nexus Cm hashfilter mode +.Nm +.Ar nexus Cm hashfilter mode .It -.Nm Ar nexus Cm hashfilter Ar filter-specification +.Nm +.Ar nexus Cm hashfilter Ar filter-specification .It -.Nm Ar nexus Cm hashfilter Ar idx Cm delete +.Nm +.Ar nexus Cm hashfilter Ar idx Cm delete .It -.Nm Ar nexus Cm hashfilter list +.Nm +.Ar nexus Cm hashfilter list .It -.Nm Ar nexus Cm filter mode +.Nm +.Ar nexus Cm filter mode .It -.Nm Ar nexus Cm filter Ar idx Ar filter-specification +.Nm +.Ar nexus Cm filter Ar idx Ar filter-specification .It -.Nm Ar nexus Cm filter Ar idx Cm delete Op Cm prio Bro Cm 0 | 1 Brc +.Nm +.Ar nexus Cm filter Ar idx Cm delete Op Cm prio Bro Cm 0 | 1 Brc .It -.Nm Ar nexus Cm filter list +.Nm +.Ar nexus Cm filter list .It -.Nm Ar nexus Cm i2c Ar port_id devaddr addr Op Ar len +.Nm +.Ar nexus Cm i2c Ar port_id devaddr addr Op Ar len .It -.Nm Ar nexus Cm loadcfg Ar fw-config.txt +.Nm +.Ar nexus Cm loadcfg Ar fw-config.txt .It -.Nm Ar nexus Cm loadcfg clear +.Nm +.Ar nexus Cm loadcfg clear .It -.Nm Ar nexus Cm loadfw Ar fw-image.bin +.Nm +.Ar nexus Cm loadfw Ar fw-image.bin .It -.Nm Ar nexus Cm memdump Ar addr len +.Nm +.Ar nexus Cm memdump Ar addr len .It -.Nm Ar nexus Cm policy Ar cop.txt +.Nm +.Ar nexus Cm policy Ar cop.txt .It -.Nm Ar nexus Cm policy clear +.Nm +.Ar nexus Cm policy clear .It -.Nm Ar nexus Bro Cm reg | reg64 Brc Ar addr Ns Op Ar =val +.Nm +.Ar nexus Bro Cm reg | reg64 Brc Ar addr Ns Op Ar =val .It -.Nm Ar nexus Cm regdump Op Ar register-block ... +.Nm +.Ar nexus Cm regdump Op Ar register-block ... .It -.Nm Ar nexus Cm sched-class Ar sub-command Op Ar param Ar value +.Nm +.Ar nexus Cm sched-class Ar sub-command Op Ar param Ar value .It -.Nm Ar nexus Cm sched-queue Ar port Ar queue Ar class +.Nm +.Ar nexus Cm sched-queue Ar port Ar queue Ar class .It -.Nm Ar nexus Cm stdio +.Nm +.Ar nexus Cm stdio .It -.Nm Ar nexus Cm tcb Ar tid +.Nm +.Ar nexus Cm tcb Ar tid .El .Sh DESCRIPTION .Nm @@ -155,22 +183,22 @@ A reference on the address must have been acquired previously. .El Display hardware context for an ingress queue, congestion manager, egress queue, or freelist manager. -.Bl -tag -width ingress_cntxt_id -compact +.Bl -tag -width "ingress_cntxt_id" -compact .It Ar ingress_cntxt_id context id of an ingress queue -- the value listed in one of -.Va dev.t4nex.%d.fwq.cntxt_id Ns , -.Va dev.cxgbe.%d.rxq.%d.cntxt_id Ns , +.Va dev.t4nex.%d.fwq.cntxt_id , +.Va dev.cxgbe.%d.rxq.%d.cntxt_id , or -.Va dev.cxgbe.%d.ofld_rxq.%d.cntxt_id Ns . +.Va dev.cxgbe.%d.ofld_rxq.%d.cntxt_id . .It Ar egress_cntxt_id context id of an egress queue -- the value listed in one of -.Va dev.t4nex.%d.mgmtq.cntxt_id Ns , -.Va dev.cxgbe.%d.txq.%d.cntxt_id Ns , -.Va dev.cxgbe.%d.ctrlq.%d.cntxt_id Ns , -.Va dev.cxgbe.%d.ofld_txq.%d.cntxt_id Ns , -.Va dev.cxgbe.%d.rxq.%d.fl.cntxt_id Ns , +.Va dev.t4nex.%d.mgmtq.cntxt_id , +.Va dev.cxgbe.%d.txq.%d.cntxt_id , +.Va dev.cxgbe.%d.ctrlq.%d.cntxt_id , +.Va dev.cxgbe.%d.ofld_txq.%d.cntxt_id , +.Va dev.cxgbe.%d.rxq.%d.fl.cntxt_id , or -.Va dev.cxgbe.%d.ofld_rxq.%d.fl.cntxt_id Ns . +.Va dev.cxgbe.%d.ofld_rxq.%d.fl.cntxt_id . Note that freelists are egress queues too. .It Ar flm_cntxt_id context id of a freelist manager. @@ -227,7 +255,7 @@ T} T{ bitwise and of the source address in an incoming IP datagram with .Ar mask equals -.Ar addr Ns . +.Ar addr . .Ar addr can be an IPv4 or IPv6 address. T} @@ -238,7 +266,7 @@ T} T{ bitwise and of the destination address in an incoming IP datagram with .Ar mask equals -.Ar addr Ns . +.Ar addr . .Ar addr can be an IPv4 or IPv6 address. T} @@ -249,7 +277,7 @@ T} T{ bitwise and of the source port in an incoming TCP or UDP datagram with .Ar mask equals -.Ar port Ns . +.Ar port . T} _ dport T{ @@ -258,7 +286,7 @@ T} T{ bitwise and of the destination port in an incoming TCP or UDP datagram with .Ar mask equals -.Ar port Ns . +.Ar port . T} _ fcoe T{ @@ -273,7 +301,7 @@ T} T{ bitwise and of the ingress port with .Ar mask equals -.Ar val Ns . +.Ar val . The ingress port is a 3 bit number that identifies the port on which a frame arrived. Physical ports are numbered 0-3 and 4-7 are internal loopback paths @@ -288,7 +316,7 @@ T} T{ bitwise and of the 16-bit outer VLAN tag of an incoming frame with .Ar mask equals -.Ar tag Ns . +.Ar tag . T} _ vlan T{ @@ -297,7 +325,7 @@ T} T{ bitwise and of the 16-bit VLAN tag of an incoming QinQ frame with .Ar mask equals -.Ar tag Ns . +.Ar tag . The inner VLAN tag is used if the incoming frame is QinQ. T} _ @@ -308,7 +336,7 @@ bitwise and of the 8-bit IP Type of Service/IPv6 Traffic Class in an incoming packet with .Ar mask equals -.Ar val Ns . +.Ar val . T} _ proto T{ @@ -317,7 +345,7 @@ T} T{ bitwise and of the 8-bit IP protocol in an incoming packet with .Ar mask equals -.Ar ipproto Ns . +.Ar ipproto . T} _ ethtype T{ @@ -326,7 +354,7 @@ T} T{ bitwise and of the 16-bit Ethernet type field of an incoming frame with .Ar mask equals -.Ar type Ns . +.Ar type . T} _ macidx T{ @@ -335,7 +363,7 @@ T} T{ bitwise and of the MAC Address Match Index of an incoming frame with .Ar mask equals -.Ar idx Ns . +.Ar idx . The MAC Address Match Index refers to an entry in the MPS TCAM or in the MPS hash. See .Cm matchtype @@ -348,7 +376,7 @@ T} T{ bitwise and of the Match Type of an incoming frame with .Ar mask equals -.Ar idx Ns . +.Ar idx . Match Type is one of the following: .Bl -tag -width "n" -compact .It 0 @@ -428,21 +456,21 @@ additional operational parameters. Hashfilters require an exact value for the 5-tuple (sip, dip, sport, dport, proto) and for any other match-criteria listed in "hashfilter mode". Possible filter actions are -.Cm drop Ns , -.Cm pass Ns , or -.Cm switch Ns . +.Cm drop , +.Cm pass , or +.Cm switch . .Pp -.Bl -tag -width nat_dport -offset indent -compact Operational parameters that can be used with all filters: +.Bl -tag -width "nat_dport" -offset indent -compact .It Cm hitcnts Count filter hits: 0 or 1 (default). .It Cm prio Filter has priority over active and server regions of TCAM: 0 (default) or 1. .El .Pp -.Bl -tag -width nat_dport -offset indent -compact Operational parameters that can be used with filters with -.Cm action pass Ns : +.Cm action pass : +.Bl -tag -width "nat_dport" -offset indent -compact .It Cm queue Context id of an ingress queue to which to deliver the packet. The context id is available in @@ -458,9 +486,9 @@ Select TCB hash information in rx descriptor. 0 (default) or 1 .El .Pp -.Bl -tag -width nat_dport -offset indent -compact Operational parameters that can be used with filters with -.Cm action switch Ns : +.Cm action switch : +.Bl -tag -width "nat_dport" -offset indent -compact .It Cm eport Egress port number on which to send the packet matching the filter. 0 to dev.<nexus>.<instance>.nports - 1. @@ -483,8 +511,9 @@ replaces the existing tag with the one provided, and .Cm + Ns Ar tag inserts the given tag into the frame. .It Cm nat -Specify the desired NAT mode. Valid NAT modes values are: -.Bl -tag -width dip-dp-sip -compact +Specify the desired NAT mode. +Valid NAT modes values are: +.Bl -tag -width "dip-dp-sip" -compact .It Cm dip Perform NAT on destination IP. .It Cm dip-dp @@ -546,12 +575,12 @@ to the card. Display .Ar len bytes of data of the card's memory starting at -.Ar addr Ns . +.Ar addr . The card's memory map is available in -.Va dev.t4nex.%d.misc.meminfo Ns . +.Va dev.t4nex.%d.misc.meminfo . .It Cm policy Ar cop.txt Install the Connection Offload Policy (COP) in -.Ar cop.txt Ns . +.Ar cop.txt . A COP offers fine-grained control over which connections get offloaded and with what parameters. Set @@ -645,7 +674,7 @@ Set ULP mode to ULP_MODE_TLS. Use the specified congestion control algorithm. .Ar algo must be one of -.Cm reno Ns , Cm tahoe Ns , Cm newreno Ns , or Cm highspeed Ns . +.Cm reno , Cm tahoe , Cm newreno , or Cm highspeed . .It Cm class Ar sc Bind the connection to the specified tx scheduling class. Valid range is 0 to 14 (for T4) and 0 to 15 (T5 onwards). @@ -653,17 +682,17 @@ Valid range is 0 to 14 (for T4) and 0 to 15 (T5 onwards). Use the specified offload rx queue. .Ar qnum should be -.Cm random Ns , Cm roundrobin Ns , +.Cm random , Cm roundrobin , or a number between 0 and nofldrxq for the ifnet. .It Cm txq Ar qnum Use the specified offload tx queue. .Ar qnum should be -.Cm random Ns , Cm roundrobin Ns , +.Cm random , Cm roundrobin , or a number between 0 and nofldtxq for the ifnet. .It Cm bind Ar qnum Shorthand for -.Cm rxq Ar qnum Cm txq Ar qnum Ns . +.Cm rxq Ar qnum Cm txq Ar qnum . Use when nofldrxq is the same as nofldtxq. .It Cm mss Ar val Set the advertised TCP MSS in the SYN for this connection to @@ -671,10 +700,10 @@ Set the advertised TCP MSS in the SYN for this connection to (in bytes). The hardware MTU table must already have an entry that is suitable for the MSS. .El -.Pp .It Example of a COP. Note that hardware listener for port 22 will be IPv4 only because the rule -before it will prevent any IPv6 servers other than the first two. Also note +before it will prevent any IPv6 servers other than the first two. +Also note that outgoing connections to 192.168/16 are the only outgoing connections that will get offloaded. .Bd -literal @@ -704,7 +733,7 @@ operation. .Ar register-block can be .Cm sge pci dbg mc ma edc0 edc1 cim tp ulp_rx ulp_tx pmrx pmtx mps cplsw -.Cm smb i2c mi uart pmu sf pl le ncsi xgmac Ns . +.Cm smb i2c mi uart pmu sf pl le ncsi xgmac . .It Cm sched-class config Op Ar param Ar value Configure optional feature capabilities for the TX scheduler. .Bl -ohang -offset indent @@ -794,14 +823,14 @@ Consult the adapter documentation for specific information on any limitations. Bind the indicated port's NIC TX .Ar queue to the specified TX Scheduler -.Ar class. +.Ar class . If the TX .Ar queue is .Cm all, * or any negative value, the binding will apply to all of the TX queues associated with the -.Ar interface. +.Ar interface . If the class is .Cm unbind, clear or any negative value, the TX queue(s) will be unbound from @@ -811,7 +840,7 @@ Switch to interactive mode. .It Cm tcb Ar tid Display contents of the hardware TCB (TCP Control Block) for the connection identfied by -.Ar tid Ns . +.Ar tid . .El .Sh FILES /sys/dev/cxgbe/t4_ioctl.h diff --git a/usr.sbin/cxgbetool/cxgbetool.c b/usr.sbin/cxgbetool/cxgbetool.c index 8f58fe8107dc..c3bd883b39fc 100644 --- a/usr.sbin/cxgbetool/cxgbetool.c +++ b/usr.sbin/cxgbetool/cxgbetool.c @@ -25,6 +25,8 @@ * SUCH DAMAGE. */ +#define _WANT_SFF_8472_ID + #include <sys/param.h> #include <sys/ioctl.h> #include <sys/mman.h> diff --git a/usr.sbin/devinfo/Makefile b/usr.sbin/devinfo/Makefile index f6506c176c9c..55b234f18363 100644 --- a/usr.sbin/devinfo/Makefile +++ b/usr.sbin/devinfo/Makefile @@ -2,6 +2,6 @@ PACKAGE= devmatch PROG= devinfo MAN= devinfo.8 -LIBADD= devinfo +LIBADD= xo devinfo .include <bsd.prog.mk> diff --git a/usr.sbin/devinfo/devinfo.8 b/usr.sbin/devinfo/devinfo.8 index f782b919056c..c34713d367ff 100644 --- a/usr.sbin/devinfo/devinfo.8 +++ b/usr.sbin/devinfo/devinfo.8 @@ -1,4 +1,5 @@ -.\" -*- nroff -*- +.\" +.\" SPDX-License-Identifer: BSD-2-Clause .\" .\" Copyright (c) 2002 Hiten Pandya .\" Copyright (c) 2002 Robert N. M. Watson @@ -25,7 +26,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 January 29, 2025 +.Dd August 28, 2025 .Dt DEVINFO 8 .Os .Sh NAME @@ -33,11 +34,14 @@ .Nd print information about system device configuration .Sh SYNOPSIS .Nm +.Op Fl -libxo .Op Fl rv .Nm -.Fl u Op Fl v -.Nm +.Op Fl -libxo .Fl p Ar dev Op Fl v +.Nm +.Op Fl -libxo +.Fl u Op Fl v .Sh DESCRIPTION The .Nm @@ -46,11 +50,23 @@ in the system, starting from the .Dq nexus device. .Pp -The following options are accepted. -.Bl -tag -width indent +The following options are accepted: +.Bl -tag -width "--libxo" +.It Fl -libxo +Generate output via +.Xr libxo 3 +in a selection of different human and machine readable formats. +See +.Xr xo_options 7 +for details on command line arguments. +.It Fl p Ar dev +Display the path of +.Ar dev +back to the root of the device tree. .It Fl r -Causes hardware resource information (such as IRQ, I/O ports, I/O memory -addresses) to be also listed, under each device that has reserved those resources. +Causes hardware resource information +.Pq such as IRQ, I/O ports, I/O memory addresses +to be also listed, under each device that has reserved those resources. .It Fl u Displays the same information as with .Fl r @@ -63,19 +79,22 @@ Display all devices in the driver tree, not just those that are attached or busy. Without this flag, only those devices that have attached are reported. This flag also displays verbose information about each device. -.It Fl p Ar dev -Display the path of -.Ar dev -back to the root of the device tree. .El .Sh SEE ALSO .Xr systat 1 , .Xr devinfo 3 , +.Xr libxo 3 , +.Xr xo_options 7 , .Xr devctl 8 , .Xr iostat 8 , .Xr pciconf 8 , .Xr vmstat 8 , .Xr devclass 9 , .Xr device 9 +.Sh HISTORY +The +.Nm +utility appeared in +.Fx 5.0 . .Sh AUTHORS .An Mike Smith Aq Mt msmith@FreeBSD.org diff --git a/usr.sbin/devinfo/devinfo.c b/usr.sbin/devinfo/devinfo.c index e8dd74b71144..4163151ec840 100644 --- a/usr.sbin/devinfo/devinfo.c +++ b/usr.sbin/devinfo/devinfo.c @@ -4,6 +4,7 @@ * Copyright (c) 2000, 2001 Michael Smith * Copyright (c) 2000 BSDi * All rights reserved. + * Copyright (c) 2024 KT Ullavik * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -31,24 +32,37 @@ * Print information about system device configuration. */ -#include <sys/types.h> +#include <sys/param.h> + #include <err.h> #include <errno.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> + +#include <libxo/xo.h> #include "devinfo.h" -static int rflag; -static int vflag; +static bool rflag; +static bool vflag; +static int open_tag_count; +static char *last_res; +static void print_indent(int); +static void print_kvlist(char *); +static char* xml_safe_string(char *); static void print_resource(struct devinfo_res *); static int print_device_matching_resource(struct devinfo_res *, void *); static int print_device_rman_resources(struct devinfo_rman *, void *); +static void print_device_props(struct devinfo_dev *); static int print_device(struct devinfo_dev *, void *); static int print_rman_resource(struct devinfo_res *, void *); static int print_rman(struct devinfo_rman *, void *); +static int print_device_path(struct devinfo_dev *, void *); +static void print_path(struct devinfo_dev *, char *); +static void usage(void); struct indent_arg { @@ -56,6 +70,59 @@ struct indent_arg void *arg; }; + +static void +print_indent(int n) +{ + static char buffer[1024]; + + if (n < 1) + return; + n = MIN((size_t)n, sizeof(buffer) - 1); + memset(buffer, ' ', n); + buffer[n] = '\0'; + xo_emit("{Pa:%s}", buffer); +} + +/* + * Takes a list of key-value pairs in the form + * "key1=val1 key2=val2 ..." and prints them according + * to xo formatting. + */ +static void +print_kvlist(char *s) +{ + char *kv; + char *copy; + + if ((copy = strdup(s)) == NULL) + xo_err(1, "No memory!"); + + while ((kv = strsep(©, " ")) != NULL) { + char* k = strsep(&kv, "="); + xo_emit("{ea:%s/%s} {d:key/%s}={d:value/%s}", k, kv, k, kv); + } + free(copy); +} + +static char +*xml_safe_string(char *desc) +{ + int i; + char *s; + + if ((s = strdup(desc)) == NULL) { + xo_err(1, "No memory!"); + } + + for (i=0; s[i] != '\0'; i++) { + if (s[i] == ' ' || s[i] == '/') { + s[i] = '-'; + } + } + return s; +} + /* * Print a resource. */ @@ -63,14 +130,30 @@ void print_resource(struct devinfo_res *res) { struct devinfo_rman *rman; - int hexmode; + bool hexmode; + rman_res_t end; + char *safe_desc; rman = devinfo_handle_to_rman(res->dr_rman); hexmode = (rman->dm_size > 1000) || (rman->dm_size == 0); - printf(hexmode ? "0x%jx" : "%ju", res->dr_start); - if (res->dr_size > 1) - printf(hexmode ? "-0x%jx" : "-%ju", - res->dr_start + res->dr_size - 1); + end = res->dr_start + res->dr_size - 1; + + safe_desc = xml_safe_string(rman->dm_desc); + xo_open_instance(safe_desc); + + if (hexmode) { + xo_emit("{:start/0x%jx}", res->dr_start); + if (res->dr_size > 1) + xo_emit("{D:-}{d:end/0x%jx}", end); + xo_emit("{e:end/0x%jx}", end); + } else { + xo_emit("{:start/%ju}", res->dr_start); + if (res->dr_size > 1) + xo_emit("{D:-}{d:end/%ju}", end); + xo_emit("{e:end/%ju}", end); + } + xo_close_instance(safe_desc); + free(safe_desc); } /* @@ -85,16 +168,14 @@ print_device_matching_resource(struct devinfo_res *res, void *arg) { struct indent_arg *ia = (struct indent_arg *)arg; struct devinfo_dev *dev = (struct devinfo_dev *)ia->arg; - int i; if (devinfo_handle_to_device(res->dr_device) == dev) { /* in 'detect' mode, found a match */ if (ia->indent == 0) return(1); - for (i = 0; i < ia->indent; i++) - printf(" "); + print_indent(ia->indent); print_resource(res); - printf("\n"); + xo_emit("\n"); } return(0); } @@ -106,7 +187,8 @@ int print_device_rman_resources(struct devinfo_rman *rman, void *arg) { struct indent_arg *ia = (struct indent_arg *)arg; - int indent, i; + int indent; + char *safe_desc; indent = ia->indent; @@ -116,52 +198,84 @@ print_device_rman_resources(struct devinfo_rman *rman, void *arg) print_device_matching_resource, ia) != 0) { /* there are, print header */ - for (i = 0; i < indent; i++) - printf(" "); - printf("%s:\n", rman->dm_desc); + safe_desc = xml_safe_string(rman->dm_desc); + print_indent(indent); + xo_emit("<{:description/%s}>\n", rman->dm_desc); + xo_open_list(safe_desc); /* print resources */ ia->indent = indent + 4; devinfo_foreach_rman_resource(rman, print_device_matching_resource, ia); + + xo_close_list(safe_desc); + free(safe_desc); } ia->indent = indent; return(0); } static void -print_dev(struct devinfo_dev *dev) +print_device_props(struct devinfo_dev *dev) { + if (vflag) { + if (*dev->dd_desc) { + xo_emit("<{:description/%s}>", dev->dd_desc); + } + if (*dev->dd_pnpinfo) { + xo_open_container("pnpinfo"); + xo_emit("{D: pnpinfo}"); + + if ((strcmp(dev->dd_pnpinfo, "unknown") == 0)) + xo_emit("{D: unknown}"); + else + print_kvlist(dev->dd_pnpinfo); + + xo_close_container("pnpinfo"); + } + if (*dev->dd_location) { + xo_open_container("location"); + xo_emit("{D: at}"); + print_kvlist(dev->dd_location); + xo_close_container("location"); + } + + // If verbose, then always print state for json/xml. + if (!(dev->dd_flags & DF_ENABLED)) + xo_emit("{e:state/disabled}"); + else if (dev->dd_flags & DF_SUSPENDED) + xo_emit("{e:state/suspended}"); + else + xo_emit("{e:state/enabled}"); + } - printf("%s", dev->dd_name[0] ? dev->dd_name : "unknown"); - if (vflag && *dev->dd_desc) - printf(" <%s>", dev->dd_desc); - if (vflag && *dev->dd_pnpinfo) - printf(" pnpinfo %s", dev->dd_pnpinfo); - if (vflag && *dev->dd_location) - printf(" at %s", dev->dd_location); if (!(dev->dd_flags & DF_ENABLED)) - printf(" (disabled)"); + xo_emit("{D: (disabled)}"); else if (dev->dd_flags & DF_SUSPENDED) - printf(" (suspended)"); + xo_emit("{D: (suspended)}"); } - /* * Print information about a device. */ -int +static int print_device(struct devinfo_dev *dev, void *arg) { struct indent_arg ia; - int i, indent; + int indent, ret; + const char* devname = dev->dd_name[0] ? dev->dd_name : "unknown"; + bool printit = vflag || (dev->dd_name[0] != 0 && + dev->dd_state >= DS_ATTACHED); - if (vflag || (dev->dd_name[0] != 0 && dev->dd_state >= DS_ATTACHED)) { + if (printit) { indent = (int)(intptr_t)arg; - for (i = 0; i < indent; i++) - printf(" "); - print_dev(dev); - printf("\n"); + print_indent(indent); + + xo_open_container(devname); + xo_emit("{d:devicename/%s}", devname); + + print_device_props(dev); + xo_emit("\n"); if (rflag) { ia.indent = indent + 4; ia.arg = dev; @@ -170,8 +284,13 @@ print_device(struct devinfo_dev *dev, void *arg) } } - return(devinfo_foreach_device_child(dev, print_device, + ret = (devinfo_foreach_device_child(dev, print_device, (void *)((char *)arg + 2))); + + if (printit) { + xo_close_container(devname); + } + return(ret); } /* @@ -181,13 +300,48 @@ int print_rman_resource(struct devinfo_res *res, void *arg __unused) { struct devinfo_dev *dev; - - printf(" "); - print_resource(res); + struct devinfo_rman *rman; + rman_res_t end; + char *res_str, *entry = NULL; + bool hexmode; + + dev = devinfo_handle_to_device(res->dr_device); + rman = devinfo_handle_to_rman(res->dr_rman); + hexmode = (rman->dm_size > 1000) || (rman->dm_size == 0); + end = res->dr_start + res->dr_size - 1; + + if (hexmode) { + if (res->dr_size > 1) + asprintf(&res_str, "0x%jx-0x%jx", res->dr_start, end); + else + asprintf(&res_str, "0x%jx", res->dr_start); + } else { + if (res->dr_size > 1) + asprintf(&res_str, "%ju-%ju", res->dr_start, end); + else + asprintf(&res_str, "%ju", res->dr_start); + } + + xo_emit("{P: }"); + + if (last_res == NULL) { + // First resource + xo_open_list(res_str); + } else if (strcmp(res_str, last_res) != 0) { + // We can't repeat json keys. So we keep an + // open list from the last iteration and only + // create a new list when see a new resource. + xo_close_list(last_res); + xo_open_list(res_str); + } + dev = devinfo_handle_to_device(res->dr_device); if (dev != NULL) { if (dev->dd_name[0] != 0) { printf(" (%s)", dev->dd_name); + asprintf(&entry, "{el:%s}{D:%s} {D:(%s)}\n", + res_str, res_str, dev->dd_name); + xo_emit(entry, dev->dd_name); } else { printf(" (unknown)"); if (vflag && *dev->dd_pnpinfo) @@ -196,9 +350,11 @@ print_rman_resource(struct devinfo_res *res, void *arg __unused) printf(" at %s", dev->dd_location); } } else { - printf(" ----"); + asprintf(&entry, "{el:%s}{D:%s} {D:----}\n", res_str, res_str); + xo_emit(entry, "----"); } - printf("\n"); + free(entry); + last_res = res_str; return(0); } @@ -208,41 +364,77 @@ print_rman_resource(struct devinfo_res *res, void *arg __unused) int print_rman(struct devinfo_rman *rman, void *arg __unused) { - printf("%s:\n", rman->dm_desc); + char* safe_desc = xml_safe_string(rman->dm_desc); + + xo_emit("<{:description/%s}\n>", rman->dm_desc); + xo_open_container(safe_desc); + devinfo_foreach_rman_resource(rman, print_rman_resource, 0); + + xo_close_list(last_res); + xo_close_container(safe_desc); + free(safe_desc); return(0); } +static void +print_device_path_entry(struct devinfo_dev *dev) +{ + const char *devname = dev->dd_name[0] ? dev->dd_name : "unknown"; + + xo_open_container(devname); + open_tag_count++; + xo_emit("{:devicename/%s} ", devname); + print_device_props(dev); + if (vflag) + xo_emit("\n"); +} + +/* + * Recurse until we find the right dev. On the way up we print path. + */ static int -print_path(struct devinfo_dev *dev, void *xname) +print_device_path(struct devinfo_dev *dev, void *xname) { const char *name = xname; int rv; if (strcmp(dev->dd_name, name) == 0) { - print_dev(dev); - if (vflag) - printf("\n"); + print_device_path_entry(dev); return (1); } - rv = devinfo_foreach_device_child(dev, print_path, xname); + rv = devinfo_foreach_device_child(dev, print_device_path, xname); if (rv == 1) { - printf(" "); - print_dev(dev); - if (vflag) - printf("\n"); + xo_emit("{P: }"); + print_device_path_entry(dev); } return (rv); } +static void +print_path(struct devinfo_dev *root, char *path) +{ + open_tag_count = 0; + if (devinfo_foreach_device_child(root, print_device_path, + (void *)path) == 0) + xo_errx(1, "%s: Not found", path); + if (!vflag) + xo_emit("\n"); + + while (open_tag_count > 0) { + xo_close_container_d(); + open_tag_count--; + } +} + static void __dead2 usage(void) { - fprintf(stderr, "%s\n%s\n%s\n", - "usage: devinfo [-rv]", - " devinfo -u [-v]", - " devinfo -p dev [-v]"); + xo_error( + "usage: devinfo [-rv]\n", + " devinfo -u [-v]\n", + " devinfo -p dev [-v]\n"); exit(1); } @@ -250,23 +442,29 @@ int main(int argc, char *argv[]) { struct devinfo_dev *root; - int c, uflag, rv; + int c, rv; + bool uflag; char *path = NULL; - uflag = 0; + argc = xo_parse_args(argc, argv); + if (argc < 0) { + exit(1); + } + + uflag = false; while ((c = getopt(argc, argv, "p:ruv")) != -1) { switch(c) { case 'p': path = optarg; break; case 'r': - rflag++; + rflag = true; break; case 'u': - uflag++; + uflag = true; break; case 'v': - vflag++; + vflag = true; break; default: usage(); @@ -278,23 +476,32 @@ main(int argc, char *argv[]) if ((rv = devinfo_init()) != 0) { errno = rv; - err(1, "devinfo_init"); + xo_err(1, "devinfo_init"); } if ((root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL) - errx(1, "can't find root device"); + xo_errx(1, "can't find root device"); if (path) { - if (devinfo_foreach_device_child(root, print_path, (void *)path) == 0) - errx(1, "%s: Not found", path); - if (!vflag) - printf("\n"); + xo_set_flags(NULL, XOF_DTRT); + xo_open_container("device-path"); + print_path(root, path); + xo_close_container("device-path"); } else if (uflag) { /* print resource usage? */ + xo_set_flags(NULL, XOF_DTRT); + xo_open_container("device-resources"); devinfo_foreach_rman(print_rman, NULL); + xo_close_container("device-resources"); } else { /* print device hierarchy */ + xo_open_container("device-information"); devinfo_foreach_device_child(root, print_device, (void *)0); + xo_close_container("device-information"); + } + + if (xo_finish() < 0) { + exit(1); } return(0); } diff --git a/usr.sbin/efitable/efitable.8 b/usr.sbin/efitable/efitable.8 index d1f4cedcdea8..52949abcb853 100644 --- a/usr.sbin/efitable/efitable.8 +++ b/usr.sbin/efitable/efitable.8 @@ -1,4 +1,6 @@ .\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" .\" Copyright (c) 2021 3mdeb Embedded Systems Consulting <contact@3mdeb.com> .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,12 +24,12 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd June 10, 2021 +.Dd July 16, 2025 .Dt EFITABLE 8 .Os .Sh NAME .Nm efitable -.Nd Dump UEFI tables +.Nd dump UEFI tables .Sh SYNOPSIS .Nm .Op Fl u Ar uuid | Fl t Ar name @@ -39,28 +41,29 @@ This program prints data from tables. .Pp The following options are available: -.Bl -tag -width 20m +.Bl -tag -width "-t name | --table name" .It Fl -libxo Generate output via .Xr libxo 3 in a selection of different human and machine readable formats. See -.Xr xo_parse_args 3 +.Xr xo_options 7 for details on command line arguments. -.It Fl t Ar name Fl -table Ar name +.It Fl t Ar name | Fl -table Ar name Specify the name of the table to print. Currently supported tables: .Pp .Bl -tag -width indent -compact .It Cm esrt EFI System Resource Table +.It Cm memory +EFI Memory Attributes Table .It Cm prop EFI Properties Table .El -.It Fl u Ar uuid Fl -uuid Ar uuid +.It Fl u Ar uuid | Fl -uuid Ar uuid Specify the UUID of the table to print. .El -.Pp .Sh HISTORY The .Nm diff --git a/usr.sbin/efitable/efitable.c b/usr.sbin/efitable/efitable.c index 81d8bb999c58..a506b4dea311 100644 --- a/usr.sbin/efitable/efitable.c +++ b/usr.sbin/efitable/efitable.c @@ -44,19 +44,22 @@ static void efi_table_print_esrt(const void *data); static void efi_table_print_prop(const void *data); +static void efi_table_print_memory(const void *data); static void usage(void) __dead2; struct efi_table_op { char name[TABLE_MAX_LEN]; void (*parse) (const void *); - struct uuid uuid; + efi_guid_t guid; }; static const struct efi_table_op efi_table_ops[] = { { .name = "esrt", .parse = efi_table_print_esrt, - .uuid = EFI_TABLE_ESRT }, + .guid = EFI_TABLE_ESRT }, { .name = "prop", .parse = efi_table_print_prop, - .uuid = EFI_PROPERTIES_TABLE } + .guid = EFI_PROPERTIES_TABLE }, + { .name = "memory", .parse = efi_table_print_memory, + .guid = EFI_MEMORY_ATTRIBUTES_TABLE } }; int @@ -81,14 +84,23 @@ main(int argc, char **argv) if (argc < 0) exit(EXIT_FAILURE); - while ((ch = getopt_long(argc, argv, "u:t:", longopts, NULL)) != -1) { + while ((ch = getopt_long(argc, argv, "g:t:u:", longopts, NULL)) != -1) { switch (ch) { + case 'g': case 'u': { char *uuid_str = optarg; struct uuid uuid; uint32_t status; + /* + * Note: we use the uuid parsing routine to parse the + * guid strings. However, EFI defines a slightly + * different structure to access them. We unify on + * using a structure that's compatible with EDK2 + * EFI_GUID structure. + */ + uuid_set = 1; uuid_from_string(uuid_str, &uuid, &status); @@ -96,7 +108,7 @@ main(int argc, char **argv) xo_errx(EX_DATAERR, "invalid UUID"); for (size_t n = 0; n < nitems(efi_table_ops); n++) { - if (!memcmp(&uuid, &efi_table_ops[n].uuid, + if (!memcmp(&uuid, &efi_table_ops[n].guid, sizeof(uuid))) { efi_idx = n; got_table = true; @@ -140,7 +152,7 @@ main(int argc, char **argv) if (efi_fd < 0) xo_err(EX_OSFILE, "/dev/efi"); - table.uuid = efi_table_ops[efi_idx].uuid; + memcpy(&table.uuid, &efi_table_ops[efi_idx].guid, sizeof(struct uuid)); if (ioctl(efi_fd, EFIIOC_GET_TABLE, &table) == -1) xo_err(EX_OSERR, "EFIIOC_GET_TABLE (len == 0)"); @@ -181,7 +193,7 @@ efi_table_print_esrt(const void *data) uint32_t status; char *uuid; - uuid_to_string(&e->fw_class, &uuid, &status); + uuid_to_string((const uuid_t *)&e->fw_class, &uuid, &status); if (status != uuid_s_ok) { xo_errx(EX_DATAERR, "uuid_to_string error"); } @@ -230,8 +242,53 @@ efi_table_print_prop(const void *data) xo_err(EX_IOERR, "stdout"); } +static void +efi_table_print_memory(const void *data) +{ + const struct efi_memory_attribute_table *attr = + (const struct efi_memory_attribute_table *)data; + const struct efi_memory_descriptor *desc; + int i, nentries; + + nentries = attr->num_ents; + desc = attr->tables; + + xo_set_version(EFITABLE_XO_VERSION); + + xo_open_container("memory"); + xo_emit("{Lwc:Version}{:version/%#x}\n", attr->version); + xo_emit("{Lwc:Length}{:length/%u}\n", attr->descriptor_size); + xo_emit("{Lwc:Entries}{:entries/%u}\n", attr->num_ents); + + xo_open_container("attributes"); + + /* + * According to https://forum.osdev.org/viewtopic.php?t=32953, the size + * of records into the attribute table never equals to + * sizeof(efi_memory_descriptor). The correct one for indexing the array + * resides in the attributet table. + */ + for (i = 0; i < nentries; i++) { + xo_emit("{Lwc:ID}{:id/%#x}\n", i); + xo_emit("{Lwc:Attributes}{:attributes/%#x}\n", desc->attrs); + xo_emit("{Lwc:Type}{:type/%#x}\n", desc->type); + xo_emit("{Lwc:Pages}{:pages/%#x}\n", desc->pages); + xo_emit("{Lwc:Phyaddr}{:phyaddr/%#p}\n", desc->phy_addr); + xo_emit("{Lwc:Virtaddr}{:virtaddr/%#p}\n", desc->virt_addr); + desc = (const struct efi_memory_descriptor *)(const void *) + ((const char *)desc + attr->descriptor_size); + } + + xo_close_container("attributes"); + + xo_close_container("memory"); + + if (xo_finish() < 0) + xo_err(EX_IOERR, "stdout"); +} + static void usage(void) { - xo_error("usage: efitable [-d uuid | -t name] [--libxo]\n"); + xo_error("usage: efitable [-g guid | -t name] [--libxo]\n"); exit(EX_USAGE); } diff --git a/usr.sbin/efivar/efivar.c b/usr.sbin/efivar/efivar.c index a87c73abef36..c40ff1ea010f 100644 --- a/usr.sbin/efivar/efivar.c +++ b/usr.sbin/efivar/efivar.c @@ -298,7 +298,7 @@ print_variables(void) static void print_known_guid(void) { - struct uuid_table *tbl; + struct guid_table *tbl; int i, n; n = efi_known_guid(&tbl); diff --git a/usr.sbin/fdcontrol/Makefile b/usr.sbin/fdcontrol/Makefile index daaae25815aa..65d8b6c3fcb7 100644 --- a/usr.sbin/fdcontrol/Makefile +++ b/usr.sbin/fdcontrol/Makefile @@ -1,5 +1,6 @@ .PATH: ${.CURDIR:H}/fdread +PACKAGE= fd PROG= fdcontrol SRCS= fdcontrol.c fdutil.c CFLAGS+= -I${.CURDIR:H}/fdread diff --git a/usr.sbin/fdformat/Makefile b/usr.sbin/fdformat/Makefile index 2b6891027b22..145cbde6798a 100644 --- a/usr.sbin/fdformat/Makefile +++ b/usr.sbin/fdformat/Makefile @@ -1,5 +1,6 @@ .PATH: ${.CURDIR:H}/fdread +PACKAGE= fd PROG= fdformat MAN= fdformat.8 SRCS= fdformat.c fdutil.c diff --git a/usr.sbin/fdread/Makefile b/usr.sbin/fdread/Makefile index eb205604cde0..c7cfce78cef5 100644 --- a/usr.sbin/fdread/Makefile +++ b/usr.sbin/fdread/Makefile @@ -1,3 +1,4 @@ +PACKAGE= fd PROG= fdread SRCS= fdread.c fdutil.c diff --git a/usr.sbin/fdwrite/Makefile b/usr.sbin/fdwrite/Makefile index 810ae058c7b0..681d3183bb61 100644 --- a/usr.sbin/fdwrite/Makefile +++ b/usr.sbin/fdwrite/Makefile @@ -6,6 +6,7 @@ # ---------------------------------------------------------------------------- # +PACKAGE= fd PROG= fdwrite .include <bsd.prog.mk> diff --git a/usr.sbin/freebsd-update/freebsd-update.sh b/usr.sbin/freebsd-update/freebsd-update.sh index ccd98a883dca..81040431ea79 100644 --- a/usr.sbin/freebsd-update/freebsd-update.sh +++ b/usr.sbin/freebsd-update/freebsd-update.sh @@ -1099,24 +1099,19 @@ IDS_check_params () { fetch_setup_verboselevel } -# Packaged base and freebsd-update are incompatible. Exit with an error if -# packaged base is in use. +# Return 0 if the system is managed using pkgbase, 1 otherwise. check_pkgbase() { # Packaged base requires that pkg is bootstrapped. - if ! pkg -c ${BASEDIR} -N >/dev/null 2>/dev/null; then - return + if ! pkg -r ${BASEDIR} -N >/dev/null 2>/dev/null; then + return 1 fi # uname(1) is used by pkg to determine ABI, so it should exist. # If it comes from a package then this system uses packaged base. - if ! pkg -c ${BASEDIR} which /usr/bin/uname >/dev/null; then - return + if ! pkg -r ${BASEDIR} which /usr/bin/uname >/dev/null; then + return 1 fi - cat <<EOF -freebsd-update is incompatible with the use of packaged base. Please see -https://wiki.freebsd.org/PkgBase for more information. -EOF - exit 1 + return 0 } #### Core functionality -- the actual work gets done here @@ -3005,7 +3000,7 @@ install_from_index () { if [ -z "${LINK}" ]; then # Create a file, without setting flags. gunzip < files/${HASH}.gz > ${HASH} - install -S -o ${OWNER} -g ${GROUP} \ + install -o ${OWNER} -g ${GROUP} \ -m ${PERM} ${HASH} ${BASEDIR}/${FPATH} rm ${HASH} else @@ -3615,10 +3610,18 @@ export LC_ALL=C # Clear environment variables that may affect operation of tools that we use. unset GREP_OPTIONS +# Parse command line options and the configuration file. +get_params "$@" + # Disallow use with packaged base. -check_pkgbase +if check_pkgbase; then + cat <<EOF +freebsd-update is incompatible with the use of packaged base. Please see +https://wiki.freebsd.org/PkgBase for more information. +EOF + exit 1 +fi -get_params $@ for COMMAND in ${COMMANDS}; do cmd_${COMMAND} done diff --git a/usr.sbin/fwget/Makefile b/usr.sbin/fwget/Makefile index 1cdf0f18230d..4c934aee3413 100644 --- a/usr.sbin/fwget/Makefile +++ b/usr.sbin/fwget/Makefile @@ -2,6 +2,6 @@ PACKAGE= fwget SCRIPTS= fwget MAN= fwget.8 -SUBDIR= pci +SUBDIR= pci usb .include <bsd.prog.mk> diff --git a/usr.sbin/fwget/fwget.8 b/usr.sbin/fwget/fwget.8 index 7b8b606cc591..86e304775e2d 100644 --- a/usr.sbin/fwget/fwget.8 +++ b/usr.sbin/fwget/fwget.8 @@ -23,7 +23,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd June 27, 2024 +.Dd July 7, 2025 .Dt FWGET 8 .Os .Sh NAME @@ -47,7 +47,11 @@ Dry run, only show needed packages .It Fl v Be more verbose .It Ar subsystem -Hardware subsystem, default pci +Hardware subsystem(s), default is all supported subsystems. +Space separated hardware subsystems, accepts +.Cm pci +and +.Cm usb .El .Sh SEE ALSO .Xr firmware 9 @@ -64,4 +68,8 @@ utility and this manual page were written by .An Emmanuel Vadot Aq Mt manu@FreeBSD.org for Beckhoff Automation GmbH & Co\. KG. .Sh CAVEATS -This utility currently only supports the pci subsystem. +This utility currently only supports the +.Xr pci 4 +and +.Xr usb 4 +subsystems. diff --git a/usr.sbin/fwget/fwget.sh b/usr.sbin/fwget/fwget.sh index 138a2a26bfb1..de1e6fa51f0f 100755 --- a/usr.sbin/fwget/fwget.sh +++ b/usr.sbin/fwget/fwget.sh @@ -35,7 +35,7 @@ usage() Usage: $(basename "$0") [options] [subsystem] Supported subsystems - pci + pci, usb Options: -n -- Do not install packages, only print the results @@ -100,9 +100,9 @@ done shift $(($OPTIND - 1)) subsystems="$@" -# Default searching PCI subsystem +# Default searching PCI and USB subsystem if [ -z "${subsystems}" ]; then - subsystems="pci" + subsystems="pci usb" fi # Fail early on unsupported subsystem diff --git a/usr.sbin/fwget/pci/pci_network_mediatek b/usr.sbin/fwget/pci/pci_network_mediatek index 3ed6c8b95b47..653c87c410eb 100644 --- a/usr.sbin/fwget/pci/pci_network_mediatek +++ b/usr.sbin/fwget/pci/pci_network_mediatek @@ -2,7 +2,7 @@ # SPDX-License-Identifier: BSD-2-Clause # # Copyright 2023 Bjoern A. Zeeb -# Copyright (c) 2024 The FreeBSD Foundation +# Copyright (c) 2024-2025 The FreeBSD Foundation # # Portions of this software were developed by Björn Zeeb # under sponsorship from the FreeBSD Foundation. @@ -41,10 +41,14 @@ pci_network_mediatek_mt76() 0x0608) addpkg "wifi-firmware-mediatek-kmod-mt792x"; return 1 ;; 0x0616) addpkg "wifi-firmware-mediatek-kmod-mt792x"; return 1 ;; 0x0717) addpkg "wifi-firmware-mediatek-kmod-mt792x"; return 1 ;; + 0x7611) addpkg "wifi-firmware-mediatek-kmod-mt7615"; return 1 ;; + 0x7615) addpkg "wifi-firmware-mediatek-kmod-mt7615"; return 1 ;; + 0x7663) addpkg "wifi-firmware-mediatek-kmod-mt7615"; return 1 ;; 0x7906) addpkg "wifi-firmware-mediatek-kmod-mt7915"; return 1 ;; 0x790a) addpkg "wifi-firmware-mediatek-kmod-mt7915"; return 1 ;; 0x7915) addpkg "wifi-firmware-mediatek-kmod-mt7915"; return 1 ;; 0x7916) addpkg "wifi-firmware-mediatek-kmod-mt7915"; return 1 ;; + 0x7920) addpkg "wifi-firmware-mediatek-kmod-mt792x"; return 1 ;; 0x7922) addpkg "wifi-firmware-mediatek-kmod-mt792x"; return 1 ;; 0x7925) addpkg "wifi-firmware-mediatek-kmod-mt792x"; return 1 ;; 0x7961) addpkg "wifi-firmware-mediatek-kmod-mt792x"; return 1 ;; diff --git a/usr.sbin/fwget/pci/pci_video_amd b/usr.sbin/fwget/pci/pci_video_amd index 98ecd5aaf236..5017789b9f28 100644 --- a/usr.sbin/fwget/pci/pci_video_amd +++ b/usr.sbin/fwget/pci/pci_video_amd @@ -96,6 +96,9 @@ pci_video_amd() 0x66a*) addpkg "gpu-firmware-amd-kmod-vega20" ;; + 0x15d8) + addpkg "gpu-firmware-amd-kmod-picasso" + ;; 0x15d*) addpkg "gpu-firmware-amd-kmod-raven" ;; diff --git a/usr.sbin/fwget/pci/pci_video_intel b/usr.sbin/fwget/pci/pci_video_intel index 3824c4a49ffb..2f9012ed1f52 100644 --- a/usr.sbin/fwget/pci/pci_video_intel +++ b/usr.sbin/fwget/pci/pci_video_intel @@ -72,7 +72,7 @@ pci_video_intel() addpkg "gpu-firmware-intel-kmod-alderlake gpu-firmware-intel-kmod-tigerlake" ;; *) - log "No package found for device $1" + log_verbose "No package found for device $1" ;; esac } diff --git a/usr.sbin/fwget/usb/Makefile b/usr.sbin/fwget/usb/Makefile new file mode 100644 index 000000000000..315e9c743cc8 --- /dev/null +++ b/usr.sbin/fwget/usb/Makefile @@ -0,0 +1,10 @@ +PACKAGE= fwget + +SCRIPTS=usb \ + usb_ralink + +BINDIR= ${LIBEXECDIR}/fwget + +MAN= + +.include <bsd.prog.mk> diff --git a/usr.sbin/fwget/usb/usb b/usr.sbin/fwget/usb/usb new file mode 100755 index 000000000000..fef6bc76ba89 --- /dev/null +++ b/usr.sbin/fwget/usb/usb @@ -0,0 +1,43 @@ +# +# Copyright 2023 Beckhoff Automation GmbH & Co. KG +# Copyright 2023 Bjoern A. Zeeb +# Copyright 2025 Jesper Schmitz Mouridsen + +# SPDX-License-Identifier: BSD-2-Clause + + +usb_get_vendor() +{ + local hexvendor=$(echo $1 | sed 's/.*idVendor=\(0x[0-9a-z]*\).*/\1/') + case "${hexvendor}" in + 0x148f) echo "ralink" ;; + esac +} + +usb_get_device() +{ + local hexdevice=$(echo $1 | sed 's/.*idProduct=\(0x[0-9a-z]*\).*/\1/') + echo "${hexdevice}" + +} + +usb_search_packages() +{ + local IFS + + oldifs=$IFS + IFS=$'\n' + for fulldevice in $(usbconfig -l dump_device_desc); do + vendor=$(usb_get_vendor "${fulldevice}") + if [ -z "${vendor}" ]; then + continue + fi + device=$(usb_get_device "${fulldevice}") + log_verbose "Trying to match device ${device} and vendor ${vendor} with usb_${vendor}" + if [ -f ${LIBEXEC_PATH}/usb_${vendor} ]; then + . ${LIBEXEC_PATH}/usb_${vendor} + usb_${vendor} ${device} + fi + done + IFS=${oldifs} +} diff --git a/usr.sbin/fwget/usb/usb_ralink b/usr.sbin/fwget/usb/usb_ralink new file mode 100755 index 000000000000..8d3135063011 --- /dev/null +++ b/usr.sbin/fwget/usb/usb_ralink @@ -0,0 +1,12 @@ +# +# Copyright (c) 2025 Jesper Schmitz Mouridsen +# +# SPDX-License-Identifier: BSD-2-Clause + +usb_ralink() +{ + + case "$1" in + 0x7601) addpkg "wifi-firmware-mt7601u-kmod"; return 1 ;; + esac +} diff --git a/usr.sbin/getfmac/getfmac.8 b/usr.sbin/getfmac/getfmac.8 index eb930e0044f9..6176bfa09271 100644 --- a/usr.sbin/getfmac/getfmac.8 +++ b/usr.sbin/getfmac/getfmac.8 @@ -51,5 +51,8 @@ specified files. .Xr mac 3 , .Xr mac_get_file 3 , .Xr mac 4 , +.Xr maclabel 7 , +.Xr getpmac 8 , .Xr setfmac 8 , +.Xr setpmac 8 , .Xr mac 9 diff --git a/usr.sbin/gssd/Makefile b/usr.sbin/gssd/Makefile index e30463ddf50b..a4ac035ae476 100644 --- a/usr.sbin/gssd/Makefile +++ b/usr.sbin/gssd/Makefile @@ -1,6 +1,6 @@ .include <src.opts.mk> -PACKAGE= kerberos +PACKAGE= gssd PROG= gssd MAN= gssd.8 @@ -9,11 +9,13 @@ SRCS= gssd.c gssd.h gssd_svc.c gssd_xdr.c gssd_prot.c CFLAGS+= -I. WARNS?= 1 -LIBADD= gssapi -.if ${MK_KERBEROS_SUPPORT} != "no" -LIBADD+= krb5 roken +.if ${MK_MITKRB5} != "no" +# MIT KRB5 +LIBADD+= gssapi_krb5 krb5 k5crypto krb5profile krb5support +CFLAGS+= -DMK_MITKRB5=yes .else -CFLAGS+= -DWITHOUT_KERBEROS +# Heimdal +LIBADD+= gssapi krb5 roken .endif CLEANFILES= gssd_svc.c gssd_xdr.c gssd.h diff --git a/usr.sbin/gssd/gssd.c b/usr.sbin/gssd/gssd.c index d1d5c2119ab5..54d2062dd29a 100644 --- a/usr.sbin/gssd/gssd.c +++ b/usr.sbin/gssd/gssd.c @@ -39,9 +39,7 @@ #include <dirent.h> #include <err.h> #include <errno.h> -#ifndef WITHOUT_KERBEROS #include <krb5.h> -#endif #include <netdb.h> #include <pwd.h> #include <signal.h> @@ -53,6 +51,9 @@ #include <arpa/inet.h> #include <netinet/in.h> #include <gssapi/gssapi.h> +#ifdef MK_MITKRB5 +#include <gssapi/gssapi_krb5.h> +#endif #include <rpc/rpc.h> #include <rpc/rpc_com.h> @@ -77,7 +78,6 @@ static char ccfile_dirlist[PATH_MAX + 1], ccfile_substring[NAME_MAX + 1]; static char pref_realm[1024]; static int verbose; static int hostbased_initiator_cred; -#ifndef WITHOUT_KERBEROS /* 1.2.752.43.13.14 */ static gss_OID_desc gss_krb5_set_allowable_enctypes_x_desc = {6, (void *) "\x2a\x85\x70\x2b\x0d\x0e"}; @@ -87,16 +87,13 @@ static gss_OID_desc gss_krb5_mech_oid_x_desc = {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; static gss_OID GSS_KRB5_MECH_OID_X = &gss_krb5_mech_oid_x_desc; -#endif static void gssd_load_mech(void); static int find_ccache_file(const char *, uid_t, char *); static int is_a_valid_tgt_cache(const char *, uid_t, int *, time_t *); static void gssd_verbose_out(const char *, ...); -#ifndef WITHOUT_KERBEROS static krb5_error_code gssd_get_cc_from_keytab(const char *); static OM_uint32 gssd_get_user_cred(OM_uint32 *, uid_t, gss_cred_id_t *); -#endif void gssd_terminate(int); extern void gssd_1(struct svc_req *rqstp, SVCXPRT *transp); @@ -128,32 +125,22 @@ main(int argc, char **argv) debug_level++; break; case 'h': -#ifndef WITHOUT_KERBEROS /* * Enable use of a host based initiator credential * in the default keytab file. */ hostbased_initiator_cred = 1; -#else - errx(1, "This option not available when built" - " without MK_KERBEROS\n"); -#endif break; case 'v': verbose = 1; break; case 's': -#ifndef WITHOUT_KERBEROS /* * Set the directory search list. This enables use of * find_ccache_file() to search the directories for a * suitable credentials cache file. */ strlcpy(ccfile_dirlist, optarg, sizeof(ccfile_dirlist)); -#else - errx(1, "This option not available when built" - " without MK_KERBEROS\n"); -#endif break; case 'c': /* @@ -335,6 +322,7 @@ gssd_null_1_svc(void *argp, void *result, struct svc_req *rqstp) return (TRUE); } +#ifndef MK_MITKRB5 bool_t gssd_init_sec_context_1_svc(init_sec_context_args *argp, init_sec_context_res *result, struct svc_req *rqstp) { @@ -344,12 +332,10 @@ gssd_init_sec_context_1_svc(init_sec_context_args *argp, init_sec_context_res *r char ccname[PATH_MAX + 5 + 1], *cp, *cp2; int gotone, gotcred; OM_uint32 min_stat; -#ifndef WITHOUT_KERBEROS gss_buffer_desc principal_desc; char enctype[sizeof(uint32_t)]; int key_enctype; OM_uint32 maj_stat; -#endif memset(result, 0, sizeof(*result)); if (hostbased_initiator_cred != 0 && argp->cred != 0 && @@ -459,6 +445,35 @@ gssd_init_sec_context_1_svc(init_sec_context_args *argp, init_sec_context_res *r } bool_t +gssd_supports_lucid_1_svc(void *argp, supports_lucid_res *result, struct svc_req *rqstp) +{ + + gssd_verbose_out("gssd_lucid: done\n"); + result->major_status = GSS_S_UNAVAILABLE; + return (TRUE); +} + +bool_t +gssd_init_sec_context_lucid_v1_1_svc(init_sec_context_lucid_v1_args *argp, + init_sec_context_lucid_v1_res *result, struct svc_req *rqstp) +{ + + gssd_verbose_out("gssd_init_sec_context_lucid_v1: Heimdal\n"); + result->major_status = GSS_S_UNAVAILABLE; + return (TRUE); +} + +bool_t +gssd_accept_sec_context_lucid_v1_1_svc(accept_sec_context_lucid_v1_args *argp, + accept_sec_context_lucid_v1_res *result, struct svc_req *rqstp) +{ + + gssd_verbose_out("gssd_accept_sec_context_lucid_v1: Heimdal\n"); + result->major_status = GSS_S_UNAVAILABLE; + return (TRUE); +} + +bool_t gssd_accept_sec_context_1_svc(accept_sec_context_args *argp, accept_sec_context_res *result, struct svc_req *rqstp) { gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; @@ -508,6 +523,446 @@ gssd_accept_sec_context_1_svc(accept_sec_context_args *argp, accept_sec_context_ return (TRUE); } +#else /* MK_MITKRB5 */ +bool_t +gssd_supports_lucid_1_svc(void *argp, supports_lucid_res *result, struct svc_req *rqstp) +{ + + gssd_verbose_out("gssd_lucid: done\n"); + result->vers = 1; + result->major_status = GSS_S_COMPLETE; + return (TRUE); +} + +bool_t +gssd_init_sec_context_1_svc(init_sec_context_args *argp, + init_sec_context_res *result, struct svc_req *rqstp) +{ + + gssd_verbose_out("gssd_init_sec_context: MIT\n"); + result->major_status = GSS_S_UNAVAILABLE; + return (TRUE); +} + +bool_t +gssd_accept_sec_context_1_svc(accept_sec_context_args *argp, + accept_sec_context_res *result, struct svc_req *rqstp) +{ + + gssd_verbose_out("gssd_accept_sec_context: MIT\n"); + result->major_status = GSS_S_UNAVAILABLE; + return (TRUE); +} + +bool_t +gssd_init_sec_context_lucid_v1_1_svc(init_sec_context_lucid_v1_args *argp, + init_sec_context_lucid_v1_res *result, struct svc_req *rqstp) +{ + gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; + gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; + gss_name_t name = GSS_C_NO_NAME; + char ccname[PATH_MAX + 5 + 1], *cp, *cp2; + int gotone, gotcred; + OM_uint32 min_stat; + gss_buffer_desc principal_desc; + char enctype[sizeof(uint32_t)]; + int key_enctype; + OM_uint32 maj_stat; + + memset(result, 0, sizeof(*result)); + if (hostbased_initiator_cred != 0 && argp->cred != 0 && + argp->uid == 0) { + /* + * These credentials are for a host based initiator name + * in a keytab file, which should now have credentials + * in /tmp/krb5cc_gssd, because gss_acquire_cred() did + * the equivalent of "kinit -k". + */ + snprintf(ccname, sizeof(ccname), "FILE:%s", + GSSD_CREDENTIAL_CACHE_FILE); + } else if (ccfile_dirlist[0] != '\0' && argp->cred == 0) { + /* + * For the "-s" case and no credentials provided as an + * argument, search the directory list for an appropriate + * credential cache file. If the search fails, return failure. + */ + gotone = 0; + cp = ccfile_dirlist; + do { + cp2 = strchr(cp, ':'); + if (cp2 != NULL) + *cp2 = '\0'; + gotone = find_ccache_file(cp, argp->uid, ccname); + if (gotone != 0) + break; + if (cp2 != NULL) + *cp2++ = ':'; + cp = cp2; + } while (cp != NULL && *cp != '\0'); + if (gotone == 0) { + result->major_status = GSS_S_CREDENTIALS_EXPIRED; + gssd_verbose_out("gssd_init_sec_context_plus: -s no" + " credential cache file found for uid=%d\n", + (int)argp->uid); + return (TRUE); + } + } else { + /* + * If there wasn't a "-s" option or the credentials have + * been provided as an argument, do it the old way. + * When credentials are provided, the uid should be root. + */ + if (argp->cred != 0 && argp->uid != 0) { + if (debug_level == 0) + syslog(LOG_ERR, "gss_init_sec_context_plus:" + " cred for non-root"); + else + fprintf(stderr, "gss_init_sec_context_plus:" + " cred for non-root\n"); + } + snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d", + (int) argp->uid); + } + setenv("KRB5CCNAME", ccname, TRUE); + + if (argp->cred) { + cred = gssd_find_resource(argp->cred); + if (!cred) { + result->major_status = GSS_S_CREDENTIALS_EXPIRED; + gssd_verbose_out("gssd_init_sec_context_plus: cred" + " resource not found\n"); + return (TRUE); + } + } + if (argp->ctx) { + ctx = gssd_find_resource(argp->ctx); + if (!ctx) { + result->major_status = GSS_S_CONTEXT_EXPIRED; + gssd_verbose_out("gssd_init_sec_context_plus: context" + " resource not found\n"); + return (TRUE); + } + } + if (argp->name) { + name = gssd_find_resource(argp->name); + if (!name) { + result->major_status = GSS_S_BAD_NAME; + gssd_verbose_out("gssd_init_sec_context_plus: name" + " resource not found\n"); + return (TRUE); + } + } + gotcred = 0; + + result->major_status = gss_init_sec_context(&result->minor_status, + cred, &ctx, name, argp->mech_type, + argp->req_flags, argp->time_req, argp->input_chan_bindings, + &argp->input_token, &result->actual_mech_type, + &result->output_token, &result->ret_flags, &result->time_rec); + gssd_verbose_out("gssd_init_sec_context_plus: done major=0x%x minor=%d" + " uid=%d\n", (unsigned int)result->major_status, + (int)result->minor_status, (int)argp->uid); + if (gotcred != 0) + gss_release_cred(&min_stat, &cred); + + if (result->actual_mech_type) { + /* + * Just to keep the bogus "elements" pointer + * from core dumping the daemon when linked to MIT + * libraries. For some reason, the "elements" pointer + * in actual_mech_type cannot be read. + */ + result->actual_mech_type = GSS_KRB5_MECH_OID_X; + } + + if (result->major_status == GSS_S_COMPLETE + || result->major_status == GSS_S_CONTINUE_NEEDED) { + if (argp->ctx) + result->ctx = argp->ctx; + else + result->ctx = gssd_make_resource(ctx); + } + + if (result->major_status == GSS_S_COMPLETE) { + gss_krb5_lucid_context_v1_t *lctx; + + result->major_status = gss_krb5_export_lucid_sec_context( + &result->minor_status, &ctx, 1, (void *)&lctx); + gssd_delete_resource(result->ctx); + if (result->major_status == GSS_S_COMPLETE && + lctx != NULL) { + result->lucid.initiate = lctx->initiate; + result->lucid.endtime = lctx->endtime; + result->lucid.send_seq = lctx->send_seq; + result->lucid.recv_seq = lctx->recv_seq; + result->lucid.protocol = lctx->protocol; + if (lctx->protocol == 0) { + result->lucid.rfc_sign = + lctx->rfc1964_kd.sign_alg; + result->lucid.rfc_seal = + lctx->rfc1964_kd.seal_alg; + result->lucid.ctx_type = + lctx->rfc1964_kd.ctx_key.type; + result->lucid.ctx_key.length = + lctx->rfc1964_kd.ctx_key.length; + result->lucid.ctx_key.value = + mem_alloc(result->lucid.ctx_key.length); + memcpy(result->lucid.ctx_key.value, + lctx->rfc1964_kd.ctx_key.data, + result->lucid.ctx_key.length); + } else if (lctx->protocol == 1) { + result->lucid.have_subkey = + lctx->cfx_kd.have_acceptor_subkey; + result->lucid.ctx_type = + lctx->cfx_kd.ctx_key.type; + result->lucid.ctx_key.length = + lctx->cfx_kd.ctx_key.length; + result->lucid.ctx_key.value = + mem_alloc(result->lucid.ctx_key.length); + memcpy(result->lucid.ctx_key.value, + lctx->cfx_kd.ctx_key.data, + result->lucid.ctx_key.length); + if (result->lucid.have_subkey != 0) { + result->lucid.subkey_type = + lctx->cfx_kd.acceptor_subkey.type; + result->lucid.subkey_key.length = + lctx->cfx_kd.acceptor_subkey.length; + result->lucid.subkey_key.value = + mem_alloc( + result->lucid.subkey_key.length); + memcpy(result->lucid.subkey_key.value, + lctx->cfx_kd.acceptor_subkey.data, + result->lucid.subkey_key.length); + } else { + result->lucid.subkey_type = 0; + result->lucid.subkey_key.length = 0; + result->lucid.subkey_key.value = NULL; + } + } + (void)gss_krb5_free_lucid_sec_context(&min_stat, + (void *)lctx); + } else { + gssd_verbose_out("gss_krb5_export_lucid_set_context" + " failed: major=0x%x minor=%d lctx=%p\n", + result->major_status, result->minor_status, lctx); + } + } + + return (TRUE); +} + +/* + * Internal function to acquire unix credentials. + */ +static OM_uint32 +_gss_get_unix_cred(OM_uint32 *minor_stat, gss_name_t name, gss_OID mech, + uid_t *uidp, gid_t *gidp, int *numgroups, gid_t *groups) +{ + OM_uint32 major_stat; + uid_t uid; + char buf[1024], *bufp; + struct passwd pwd, *pw; + size_t buflen; + int error; + static size_t buflen_hint = 1024; + + major_stat = gss_pname_to_uid(minor_stat, name, mech, &uid); + if (major_stat == GSS_S_COMPLETE) { + *uidp = uid; + buflen = buflen_hint; + for (;;) { + pw = NULL; + bufp = buf; + if (buflen > sizeof(buf)) + bufp = malloc(buflen); + if (bufp == NULL) + break; + error = getpwuid_r(uid, &pwd, bufp, buflen, + &pw); + if (error != ERANGE) + break; + if (buflen > sizeof(buf)) + free(bufp); + buflen += 1024; + if (buflen > buflen_hint) + buflen_hint = buflen; + } + if (pw) { + *gidp = pw->pw_gid; + getgrouplist(pw->pw_name, pw->pw_gid, + groups, numgroups); + } else { + major_stat = GSS_S_FAILURE; + gssd_verbose_out("get_unix_cred: cannot find" + " passwd entry\n"); + } + if (bufp != NULL && buflen > sizeof(buf)) + free(bufp); + } else if (major_stat != GSS_S_UNAVAILABLE) { + gssd_verbose_out("gssd_pname_to_uid: failed major=0x%x" + " minor=%d\n", major_stat, *minor_stat); + } + return (major_stat); +} + +bool_t +gssd_accept_sec_context_lucid_v1_1_svc(accept_sec_context_lucid_v1_args *argp, + accept_sec_context_lucid_v1_res *result, struct svc_req *rqstp) +{ + gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; + gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; + gss_name_t src_name; + gss_cred_id_t delegated_cred_handle; + OM_uint32 min_stat; + + memset(result, 0, sizeof(*result)); + if (argp->ctx) { + ctx = gssd_find_resource(argp->ctx); + if (!ctx) { + result->major_status = GSS_S_CONTEXT_EXPIRED; + gssd_verbose_out("gssd_accept_sec_context: ctx" + " resource not found\n"); + return (TRUE); + } + } + if (argp->cred) { + cred = gssd_find_resource(argp->cred); + if (!cred) { + result->major_status = GSS_S_CREDENTIALS_EXPIRED; + gssd_verbose_out("gssd_accept_sec_context: cred" + " resource not found\n"); + return (TRUE); + } + } + + memset(result, 0, sizeof(*result)); + result->major_status = gss_accept_sec_context(&result->minor_status, + &ctx, cred, &argp->input_token, argp->input_chan_bindings, + &src_name, &result->mech_type, &result->output_token, + &result->ret_flags, &result->time_rec, + &delegated_cred_handle); + gssd_verbose_out("gssd_accept_sec_context: done major=0x%x minor=%d\n", + (unsigned int)result->major_status, (int)result->minor_status); + + if (result->major_status == GSS_S_COMPLETE + || result->major_status == GSS_S_CONTINUE_NEEDED) { + if (argp->ctx) + result->ctx = argp->ctx; + else + result->ctx = gssd_make_resource(ctx); + result->src_name = gssd_make_resource(src_name); + result->delegated_cred_handle = + gssd_make_resource(delegated_cred_handle); + } + + if (result->major_status == GSS_S_COMPLETE) { + gss_krb5_lucid_context_v1_t *lctx; + + /* Get the lucid context stuff. */ + result->major_status = gss_krb5_export_lucid_sec_context( + &result->minor_status, &ctx, 1, (void *)&lctx); + gssd_delete_resource(result->ctx); + if (result->major_status == GSS_S_COMPLETE && + lctx != NULL) { + result->lucid.initiate = lctx->initiate; + result->lucid.endtime = lctx->endtime; + result->lucid.send_seq = lctx->send_seq; + result->lucid.recv_seq = lctx->recv_seq; + result->lucid.protocol = lctx->protocol; + if (lctx->protocol == 0) { + result->lucid.rfc_sign = + lctx->rfc1964_kd.sign_alg; + result->lucid.rfc_seal = + lctx->rfc1964_kd.seal_alg; + result->lucid.ctx_type = + lctx->rfc1964_kd.ctx_key.type; + result->lucid.ctx_key.length = + lctx->rfc1964_kd.ctx_key.length; + result->lucid.ctx_key.value = + mem_alloc(result->lucid.ctx_key.length); + memcpy(result->lucid.ctx_key.value, + lctx->rfc1964_kd.ctx_key.data, + result->lucid.ctx_key.length); + } else if (lctx->protocol == 1) { + result->lucid.have_subkey = + lctx->cfx_kd.have_acceptor_subkey; + result->lucid.ctx_type = + lctx->cfx_kd.ctx_key.type; + result->lucid.ctx_key.length = + lctx->cfx_kd.ctx_key.length; + result->lucid.ctx_key.value = + mem_alloc(result->lucid.ctx_key.length); + memcpy(result->lucid.ctx_key.value, + lctx->cfx_kd.ctx_key.data, + result->lucid.ctx_key.length); + if (result->lucid.have_subkey != 0) { + result->lucid.subkey_type = + lctx->cfx_kd.acceptor_subkey.type; + result->lucid.subkey_key.length = + lctx->cfx_kd.acceptor_subkey.length; + result->lucid.subkey_key.value = + mem_alloc( + result->lucid.subkey_key.length); + memcpy(result->lucid.subkey_key.value, + lctx->cfx_kd.acceptor_subkey.data, + result->lucid.subkey_key.length); + } else { + result->lucid.subkey_type = 0; + result->lucid.subkey_key.length = 0; + result->lucid.subkey_key.value = NULL; + } + } + (void)gss_krb5_free_lucid_sec_context(&min_stat, + (void *)lctx); + } else { + gssd_verbose_out("gss_krb5_export_lucid_set_context" + " failed: major=0x%x minor=%d lctx=%p\n", + result->major_status, result->minor_status, lctx); + } + + /* Now, get the exported name. */ + if (result->major_status == GSS_S_COMPLETE) { + result->major_status = gss_export_name( + &result->minor_status, src_name, + &result->exported_name); + gssd_verbose_out("gssd_accept_sec_context (name):" + " done major=0x%x minor=%d\n", + result->major_status, result->minor_status); + } + + /* Finally, get the unix credentials. */ + if (result->major_status == GSS_S_COMPLETE) { + gid_t groups[NGROUPS]; + int i, len = NGROUPS; + OM_uint32 major_stat, minor_stat; + + major_stat = _gss_get_unix_cred(&minor_stat, + src_name, result->mech_type, + &result->uid, &result->gid, &len, groups); + if (major_stat == GSS_S_COMPLETE) { + result->gidlist.gidlist_len = len; + result->gidlist.gidlist_val = + mem_alloc(len * sizeof(uint32_t)); + /* + * Just in case + * sizeof(gid_t) != sizeof(uint32_t). + */ + for (i = 0; i < len; i++) + result->gidlist.gidlist_val[i] = + groups[i]; + } else { + result->gid = 65534; + result->gidlist.gidlist_len = 0; + result->gidlist.gidlist_val = NULL; + gssd_verbose_out("gssd_pname_to_uid: mapped" + " to uid=%d, but no groups\n", + (int)result->uid); + } + } + } + return (TRUE); +} +#endif /* !MK_MITKRB5 */ bool_t gssd_delete_sec_context_1_svc(delete_sec_context_args *argp, delete_sec_context_res *result, struct svc_req *rqstp) @@ -758,11 +1213,9 @@ gssd_acquire_cred_1_svc(acquire_cred_args *argp, acquire_cred_res *result, struc gss_cred_id_t cred; char ccname[PATH_MAX + 5 + 1], *cp, *cp2; int gotone; -#ifndef WITHOUT_KERBEROS gss_buffer_desc namebuf; uint32_t minstat; krb5_error_code kret; -#endif memset(result, 0, sizeof(*result)); if (argp->desired_name) { @@ -775,7 +1228,6 @@ gssd_acquire_cred_1_svc(acquire_cred_args *argp, acquire_cred_res *result, struc } } -#ifndef WITHOUT_KERBEROS if (hostbased_initiator_cred != 0 && argp->desired_name != 0 && argp->uid == 0 && argp->cred_usage == GSS_C_INITIATE) { /* This is a host based initiator name in the keytab file. */ @@ -808,9 +1260,7 @@ gssd_acquire_cred_1_svc(acquire_cred_args *argp, acquire_cred_res *result, struc result->major_status = GSS_S_FAILURE; return (TRUE); } - } else -#endif /* !WITHOUT_KERBEROS */ - if (ccfile_dirlist[0] != '\0' && argp->desired_name == 0) { + } else if (ccfile_dirlist[0] != '\0' && argp->desired_name == 0) { /* * For the "-s" case and no name provided as an * argument, search the directory list for an appropriate @@ -1054,7 +1504,6 @@ static int is_a_valid_tgt_cache(const char *filepath, uid_t uid, int *retrating, time_t *retexptime) { -#ifndef WITHOUT_KERBEROS krb5_context context; krb5_principal princ; krb5_ccache ccache; @@ -1154,12 +1603,8 @@ is_a_valid_tgt_cache(const char *filepath, uid_t uid, int *retrating, *retexptime = exptime; } return (ret); -#else /* WITHOUT_KERBEROS */ - return (0); -#endif /* !WITHOUT_KERBEROS */ } -#ifndef WITHOUT_KERBEROS /* * This function attempts to do essentially a "kinit -k" for the principal * name provided as the argument, so that there will be a TGT in the @@ -1189,8 +1634,11 @@ gssd_get_cc_from_keytab(const char *name) if (ret == 0) ret = krb5_cc_initialize(context, ccache, principal); if (ret == 0) { +#ifndef MK_MITKRB5 + /* For Heimdal only */ krb5_get_init_creds_opt_set_default_flags(context, "gssd", krb5_principal_get_realm(context, principal), opt); +#endif kt_ret = ret = krb5_kt_default(context, &kt); } if (ret == 0) @@ -1259,15 +1707,12 @@ gssd_get_user_cred(OM_uint32 *min_statp, uid_t uid, gss_cred_id_t *credp) gss_release_oid_set(&min_stat, &mechlist); return (maj_stat); } -#endif /* !WITHOUT_KERBEROS */ void gssd_terminate(int sig __unused) { -#ifndef WITHOUT_KERBEROS if (hostbased_initiator_cred != 0) unlink(GSSD_CREDENTIAL_CACHE_FILE); -#endif exit(0); } diff --git a/usr.sbin/gstat/gstat.8 b/usr.sbin/gstat/gstat.8 index 02dbbbc54a3d..e882aa75b8d7 100644 --- a/usr.sbin/gstat/gstat.8 +++ b/usr.sbin/gstat/gstat.8 @@ -122,6 +122,9 @@ Quit .El .Sh EXIT STATUS .Ex -std +.Sh EXAMPLES +To filter the output to only physical disks named ada0 through ada4: +.Dl # gstat -f ada[0-4]$ .Sh SEE ALSO .Xr systat 1 , .Xr geom 4 , diff --git a/usr.sbin/inetd/builtins.c b/usr.sbin/inetd/builtins.c index 9609faf0b104..21ce44c77033 100644 --- a/usr.sbin/inetd/builtins.c +++ b/usr.sbin/inetd/builtins.c @@ -606,6 +606,8 @@ ident_stream(int s, struct servtab *sep) */ if (initgroups(pw->pw_name, pw->pw_gid) == -1) iderror(lport, fport, s, ID_UNKNOWN); + if (setegid(pw->pw_gid) == -1) + iderror(lport, fport, s, ID_UNKNOWN); if (seteuid(pw->pw_uid) == -1) iderror(lport, fport, s, ID_UNKNOWN); /* diff --git a/usr.sbin/inetd/inetd.conf b/usr.sbin/inetd/inetd.conf index 14e5fec5d1bf..a8359ea793f5 100644 --- a/usr.sbin/inetd/inetd.conf +++ b/usr.sbin/inetd/inetd.conf @@ -7,8 +7,8 @@ # #ftp stream tcp nowait root /usr/libexec/ftpd ftpd -l #ftp stream tcp6 nowait root /usr/libexec/ftpd ftpd -l -#ssh stream tcp nowait root /usr/sbin/sshd sshd -i -4 -#ssh stream tcp6 nowait root /usr/sbin/sshd sshd -i -6 +#ssh stream tcp nowait root /usr/sbin/sshd sshd -i +#ssh stream tcp6 nowait root /usr/sbin/sshd sshd -i #telnet stream tcp nowait root /usr/local/libexec/telnetd telnetd #telnet stream tcp6 nowait root /usr/local/libexec/telnetd telnetd #shell stream tcp nowait root /usr/local/sbin/rshd rshd @@ -24,8 +24,8 @@ # # ntalk is required for the 'talk' utility to work correctly #ntalk dgram udp wait tty:tty /usr/libexec/ntalkd ntalkd -#tftp dgram udp wait root /usr/libexec/tftpd tftpd -l -s /tftpboot -#tftp dgram udp6 wait root /usr/libexec/tftpd tftpd -l -s /tftpboot +#tftp dgram udp wait root /usr/libexec/tftpd tftpd -l -S -s /tftpboot +#tftp dgram udp6 wait root /usr/libexec/tftpd tftpd -l -S -s /tftpboot #bootps dgram udp wait root /usr/libexec/bootpd bootpd # # "Small servers" -- used to be standard on, but we're more conservative diff --git a/usr.sbin/jail/command.c b/usr.sbin/jail/command.c index fe6563230bde..9da4fe51673a 100644 --- a/usr.sbin/jail/command.c +++ b/usr.sbin/jail/command.c @@ -332,6 +332,25 @@ run_command(struct cfjail *j) printf("%d\n", j->jid); if (verbose >= 0 && (j->name || verbose > 0)) jail_note(j, "created\n"); + + /* + * Populate our jid and name parameters if they were not + * provided. This simplifies later logic that wants to + * use the jid or name to be able to do so reliably. + */ + if (j->intparams[KP_JID] == NULL) { + char ljidstr[16]; + + (void)snprintf(ljidstr, sizeof(ljidstr), "%d", + j->jid); + add_param(j, NULL, KP_JID, ljidstr); + } + + /* This matches the kernel behavior. */ + if (j->intparams[KP_NAME] == NULL) + add_param(j, j->intparams[KP_JID], KP_NAME, + NULL); + dep_done(j, DF_LIGHT); } return 0; @@ -456,8 +475,7 @@ run_command(struct cfjail *j) argv[0] = _PATH_IFCONFIG; argv[1] = comstring->s; argv[2] = down ? "-vnet" : "vnet"; - jidstr = string_param(j->intparams[KP_JID]); - argv[3] = jidstr ? jidstr : string_param(j->intparams[KP_NAME]); + argv[3] = string_param(j->intparams[KP_JID]); argv[4] = NULL; break; @@ -592,9 +610,7 @@ run_command(struct cfjail *j) case IP_ZFS_DATASET: argv = alloca(4 * sizeof(char *)); - jidstr = string_param(j->intparams[KP_JID]) ? - string_param(j->intparams[KP_JID]) : - string_param(j->intparams[KP_NAME]); + jidstr = string_param(j->intparams[KP_JID]); fmt = "if [ $(/sbin/zfs get -H -o value jailed %s) = on ]; then /sbin/zfs jail %s %s || echo error, attaching %s to jail %s failed; else echo error, you need to set jailed=on for dataset %s; fi"; comlen = strlen(fmt) + 2 * strlen(jidstr) @@ -795,6 +811,14 @@ run_command(struct cfjail *j) } endpwent(); } + 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); + + path = string_param(j->intparams[KP_PATH]); + setenv("JPATH", path ? path : "", 1); + } if (consfd != 0 && (dup2(consfd, 1) < 0 || dup2(consfd, 2) < 0)) { jail_warnx(j, "exec.consolelog: %s", strerror(errno)); diff --git a/usr.sbin/jail/config.c b/usr.sbin/jail/config.c index 3af0088626c9..1bad04ccde68 100644 --- a/usr.sbin/jail/config.c +++ b/usr.sbin/jail/config.c @@ -156,11 +156,14 @@ load_config(const char *cfname) TAILQ_CONCAT(&opp, &j->params, tq); /* * The jail name implies its "name" or "jid" parameter, - * though they may also be explicitly set later on. + * though they may also be explicitly set later on. After we + * collect other parameters, we'll go back and ensure they're + * both set if we need to do so here. */ add_param(j, NULL, strtol(j->name, &ep, 10) && !*ep ? KP_JID : KP_NAME, j->name); + /* * Collect parameters for the jail, global parameters/variables, * and any matching wildcard jails. @@ -180,6 +183,14 @@ load_config(const char *cfname) TAILQ_FOREACH(p, &opp, tq) add_param(j, p, 0, NULL); + /* + * We only backfill if it's the name that wasn't set; if it was + * the jid, we can assume that will be populated later when the + * jail is created or found. + */ + if (j->intparams[KP_NAME] == NULL) + add_param(j, NULL, KP_NAME, j->name); + /* Resolve any variable substitutions. */ pgen = 0; TAILQ_FOREACH(p, &j->params, tq) { diff --git a/usr.sbin/jail/jail.8 b/usr.sbin/jail/jail.8 index 3426f4f0d600..421aa9babb4c 100644 --- a/usr.sbin/jail/jail.8 +++ b/usr.sbin/jail/jail.8 @@ -23,7 +23,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd September 19, 2024 +.Dd August 7, 2025 .Dt JAIL 8 .Os .Sh NAME @@ -686,6 +686,12 @@ in the file outside of the jails. .It Va allow.reserved_ports The jail root may bind to ports lower than 1024. +.It Va allow.unprivileged_parent_tampering +Unprivileged processes in the jail's parent may tamper with processes of the +same UID in the jail. +This includes the ability to signal, debug, and +.Xr cpuset 1 +processes that belong to the jail. .It Va allow.unprivileged_proc_debug Unprivileged processes in the jail may use debugging facilities. .It Va allow.suser @@ -710,6 +716,9 @@ For example through utilities like .Xr date 1 . This permission includes also .Va allow.adjtime . +.It Va allow.routing +Allow privileged process in the non-VNET jail to modify the system routing +table. .El .El .Pp @@ -855,6 +864,22 @@ commands in sequence. All commands must succeed (return a zero exit status), or the jail will not be created or removed, as appropriate. .Pp +The following variables are added to the environment: +.Bl -tag -width indent -offset indent +.It Ev JID +The +.Va jid , +or jail identifier. +.It Ev JNAME +The +.Va name +of the jail. +.It Ev JPATH +The +.Va path +of the jail. +.El +.Pp The pseudo-parameters are: .Bl -tag -width indent .It Va exec.prepare @@ -919,6 +944,11 @@ is imported from the current environment. is set to "/bin:/usr/bin". The environment variables from the login class capability database for the target login are also set. +.Ev JID , +.Ev JNAME , +and +.Ev JPATH +are not set. If a user is specified (as with .Va exec.jail_user ) , commands are run from that (possibly jailed) user's directory. diff --git a/usr.sbin/jail/jail.c b/usr.sbin/jail/jail.c index 27769cc14958..46cabf76ae11 100644 --- a/usr.sbin/jail/jail.c +++ b/usr.sbin/jail/jail.c @@ -890,7 +890,14 @@ running_jid(struct cfjail *j) j->jid = -1; return; } + j->jid = jail_get(jiov, 2, 0); + if (j->jid > 0 && j->intparams[KP_JID] == NULL) { + char jidstr[16]; + + (void)snprintf(jidstr, sizeof(jidstr), "%d", j->jid); + add_param(j, NULL, KP_JID, jidstr); + } } static void diff --git a/usr.sbin/jail/tests/commands.jail.conf b/usr.sbin/jail/tests/commands.jail.conf index 4ea24ec6b058..ad152a28b7fe 100644 --- a/usr.sbin/jail/tests/commands.jail.conf +++ b/usr.sbin/jail/tests/commands.jail.conf @@ -1,6 +1,7 @@ exec.prestop = "echo STOP"; exec.prestart = "echo START"; +exec.poststart = "env"; persist; basejail {} diff --git a/usr.sbin/jail/tests/jail_basic_test.sh b/usr.sbin/jail/tests/jail_basic_test.sh index 5d67f42c2d56..6802da7b049a 100755 --- a/usr.sbin/jail/tests/jail_basic_test.sh +++ b/usr.sbin/jail/tests/jail_basic_test.sh @@ -133,31 +133,185 @@ commands_head() commands_body() { - # exec.prestart - atf_check -s exit:0 -o inline:"START\n" \ - jail -f $(atf_get_srcdir)/commands.jail.conf -qc basejail + cp "$(atf_get_srcdir)/commands.jail.conf" jail.conf + echo "path = \"$PWD\";" >> jail.conf + + # exec.prestart (START) and exec.poststart (env) + atf_check -o save:stdout -e empty \ + jail -f jail.conf -qc basejail + + # exec.prestart output is missing + atf_check grep -qE '^START$' stdout + # JID was not set in the exec.poststart env + atf_check grep -qE '^JID=[0-9]+' stdout + # JNAME was not set in the exec.poststart env + atf_check grep -qE '^JNAME=basejail$' stdout + # JPATH was not set in the exec.poststart env + atf_check grep -qE "^JPATH=$PWD$" stdout + # exec.prestop by jailname atf_check -s exit:0 -o inline:"STOP\n" \ - jail -f $(atf_get_srcdir)/commands.jail.conf -qr basejail + jail -f jail.conf -qr basejail # exec.prestop by jid - jail -f $(atf_get_srcdir)/commands.jail.conf -qc basejail + jail -f jail.conf -qc basejail atf_check -s exit:0 -o inline:"STOP\n" \ - jail -f $(atf_get_srcdir)/commands.jail.conf -qr `jls -j basejail jid` + jail -f jail.conf -qr `jls -j basejail jid` } -commands_cleanup() +commands_cleanup() { - jls -j basejail > /dev/null 2>&1 - if [ $? -e 0 ] - then + if jls -j basejail > /dev/null 2>&1; then jail -r basejail fi } +atf_test_case "jid_name_set" "cleanup" +jid_name_set_head() +{ + atf_set descr 'Test that one can set both the jid and name in a config file' + atf_set require.user root +} + +find_unused_jid() +{ + : ${JAIL_MAX=999999} + + # We'll start at a higher jid number and roll through the space until + # we find one that isn't taken. We start high to avoid racing parallel + # activity for the 'next available', though ideally we don't have a lot + # of parallel jail activity like that. + jid=5309 + while jls -cj "$jid"; do + if [ "$jid" -eq "$JAIL_MAX" ]; then + atf_skip "System has too many jail, cannot find free slot" + fi + + jid=$((jid + 1)) + done + + echo "$jid" | tee -a jails.lst +} +clean_jails() +{ + if [ ! -s jails.lst ]; then + return 0 + fi + + while read jail; do + if jls -c -j "$jail"; then + jail -r "$jail" + fi + done < jails.lst +} + +jid_name_set_body() +{ + local jid=$(find_unused_jid) + + echo "basejail" >> jails.lst + echo "$jid { name = basejail; persist; }" > jail.conf + atf_check -o match:"$jid: created" jail -f jail.conf -c "$jid" + # Confirm that we didn't override the explicitly-set name with the jid + # as the name. + atf_check -o match:"basejail" jls -j "$jid" name + atf_check -o match:"$jid: removed" jail -f jail.conf -r "$jid" + + echo "$jid { host.hostname = \"\${name}\"; persist; }" > jail.conf + atf_check -o match:"$jid: created" jail -f jail.conf -c "$jid" + # Confirm that ${name} expanded and expanded correctly to the + # jid-implied name. + atf_check -o match:"$jid" jls -j "$jid" host.hostname + atf_check -o match:"$jid: removed" jail -f jail.conf -r "$jid" + + echo "basejail { jid = $jid; persist; }" > jail.conf + atf_check -o match:"basejail: created" jail -f jail.conf -c basejail + # Confirm that our jid assigment in the definition worked out and we + # did in-fact create the jail there. + atf_check -o match:"$jid" jls -j "basejail" jid + atf_check -o match:"basejail: removed" jail -f jail.conf -r basejail +} + +jid_name_set_cleanup() +{ + clean_jails +} + +atf_test_case "param_consistency" "cleanup" +param_consistency_head() +{ + atf_set descr 'Test for consistency in jid/name params being set implicitly' + atf_set require.user root +} + +param_consistency_body() +{ + local iface jid + + echo "basejail" >> jails.lst + + # Most basic test: exec.poststart running a command without a jail + # config. This would previously crash as we only had the jid and name + # as populated at creation time. + atf_check jail -c path=/ exec.poststart="true" command=/usr/bin/true + + iface=$(ifconfig lo create) + atf_check test -n "$iface" + echo "$iface" >> interfaces.lst + + # Now do it again but exercising IP_VNET_INTERFACE, which is an + # implied command that wants to use the jid or name. This would crash + # as neither KP_JID or KP_NAME are populated when a jail is created, + # just as above- just at a different spot. + atf_check jail -c \ + path=/ vnet=new vnet.interface="$iface" command=/usr/bin/true + + # Test that a jail that we only know by name will have its jid resolved + # and added to its param set. + echo "basejail {path = /; exec.prestop = 'echo STOP'; persist; }" > jail.conf + + atf_check -o ignore jail -f jail.conf -c basejail + atf_check -o match:"STOP" jail -f jail.conf -r basejail + + # Do the same sequence as above, but use a jail with a jid-ish name. + jid=$(find_unused_jid) + echo "$jid {path = /; exec.prestop = 'echo STOP'; persist; }" > jail.conf + + atf_check -o ignore jail -f jail.conf -c "$jid" + atf_check -o match:"STOP" jail -f jail.conf -r "$jid" + + # Ditto, but now we set a name for that jid-jail. + echo "$jid {name = basejail; path = /; exec.prestop = 'echo STOP'; persist; }" > jail.conf + + atf_check -o ignore jail -f jail.conf -c "$jid" + atf_check -o match:"STOP" jail -f jail.conf -r "$jid" + + # Confirm that we have a valid jid available in exec.poststop. It's + # probably debatable whether we should or not. + echo "basejail {path = /; exec.poststop = 'echo JID=\$JID'; persist; }" > jail.conf + atf_check -o ignore jail -f jail.conf -c basejail + jid=$(jls -j basejail jid) + + atf_check -o match:"JID=$jid" jail -f jail.conf -r basejail + +} + +param_consistency_cleanup() +{ + clean_jails + + if [ -f "interfaces.lst" ]; then + while read iface; do + ifconfig "$iface" destroy + done < interfaces.lst + fi +} + atf_init_test_cases() { atf_add_test_case "basic" atf_add_test_case "list" atf_add_test_case "nested" atf_add_test_case "commands" + atf_add_test_case "jid_name_set" + atf_add_test_case "param_consistency" } diff --git a/usr.sbin/jls/jls.8 b/usr.sbin/jls/jls.8 index f7a5eeb321ef..715033082963 100644 --- a/usr.sbin/jls/jls.8 +++ b/usr.sbin/jls/jls.8 @@ -23,7 +23,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd February 13, 2025 +.Dd July 25, 2025 .Dt JLS 8 .Os .Sh NAME @@ -35,6 +35,10 @@ .Op Fl dhNnqsv .Op Fl j Ar jail .Op Ar parameter ... +.Nm +.Fl c +.Op Fl d +.Fl j Ar jail .Sh DESCRIPTION The .Nm @@ -54,11 +58,21 @@ for a description of some core parameters. If no .Ar parameters or any of the options -.Fl hns +.Fl chns are given, the following four columns will be printed: jail identifier (jid), IP address (ip4.addr), hostname (host.hostname), and path (path). .Pp +When the +.Fl c +option is used, +.Nm +will not emit any output except for usage errors. +This mode is intended solely to check for a single jail's existence, and it does +not accept any +.Ar parameter +or print-option flags. +.Pp The following options are available: .Bl -tag -width indent .It Fl -libxo @@ -68,6 +82,8 @@ in a selection of different human and machine readable formats. See .Xr xo_options 7 for details on command line arguments. +.It Fl c +Only check for the jail's existence. .It Fl d List .Va dying diff --git a/usr.sbin/jls/jls.c b/usr.sbin/jls/jls.c index bd193a69c458..4f697a5bb382 100644 --- a/usr.sbin/jls/jls.c +++ b/usr.sbin/jls/jls.c @@ -37,6 +37,7 @@ #include <arpa/inet.h> #include <netinet/in.h> +#include <assert.h> #include <ctype.h> #include <errno.h> #include <jail.h> @@ -59,6 +60,7 @@ #define PRINT_SKIP 0x10 #define PRINT_VERBOSE 0x20 #define PRINT_JAIL_NAME 0x40 +#define PRINT_EXISTS 0x80 static struct jailparam *params; static int *param_parent; @@ -81,6 +83,14 @@ static void quoted_print(int pflags, char *name, char *value); static void emit_ip_addr_list(int af_family, const char *list_name, struct jailparam *param); +static void +usage(void) +{ + xo_errx(1, + "usage: jls [-dhNnqv] [-j jail] [param ...]\n" + " jls -c [-d] -j jail"); +} + int main(int argc, char **argv) { @@ -94,12 +104,15 @@ main(int argc, char **argv) xo_set_version(JLS_XO_VERSION); jname = NULL; pflags = jflags = jid = 0; - while ((c = getopt(argc, argv, "adj:hNnqsv")) >= 0) + while ((c = getopt(argc, argv, "acdj:hNnqsv")) >= 0) switch (c) { case 'a': case 'd': jflags |= JAIL_DYING; break; + case 'c': + pflags |= PRINT_EXISTS; + break; case 'j': jid = strtoul(optarg, &ep, 10); if (!jid || *ep) { @@ -130,7 +143,7 @@ main(int argc, char **argv) PRINT_VERBOSE; break; default: - xo_errx(1, "usage: jls [-dhNnqv] [-j jail] [param ...]"); + usage(); } #ifdef INET6 @@ -140,8 +153,28 @@ main(int argc, char **argv) ip4_ok = feature_present("inet"); #endif + argc -= optind; + argv += optind; + /* Add the parameters to print. */ - if (optind == argc) { + if ((pflags & PRINT_EXISTS) != 0) { + if ((pflags & ~PRINT_EXISTS) != 0) { + xo_warnx("-c is incompatible with other print options"); + usage(); + } else if (argc != 0) { + xo_warnx("-c does not accept non-option arguments"); + usage(); + } else if (jid == 0 && jname == NULL) { + xo_warnx("-j jail to check must be provided for -c"); + usage(); + } + + /* + * Force libxo to be silent, as well -- we're only wanting our + * exit status. + */ + xo_set_style(NULL, XO_STYLE_TEXT); + } else if (argc == 0) { if (pflags & (PRINT_HEADER | PRINT_NAMEVAL)) add_param("all", NULL, (size_t)0, NULL, JP_USER); else if (pflags & PRINT_VERBOSE) { @@ -179,9 +212,8 @@ main(int argc, char **argv) } } else { pflags &= ~PRINT_VERBOSE; - while (optind < argc) - add_param(argv[optind++], NULL, (size_t)0, NULL, - JP_USER); + for (i = 0; i < argc; i++) + add_param(argv[i], NULL, (size_t)0, NULL, JP_USER); } if (pflags & PRINT_SKIP) { @@ -237,9 +269,17 @@ main(int argc, char **argv) xo_open_list("jail"); /* Fetch the jail(s) and print the parameters. */ if (jid != 0 || jname != NULL) { - if (print_jail(pflags, jflags) < 0) + if (print_jail(pflags, jflags) < 0) { + /* + * We omit errors from existential issues if we're just + * doing a -c check that the jail exists. + */ + if (pflags & PRINT_EXISTS) + exit(1); xo_errx(1, "%s", jail_errmsg); + } } else { + assert((pflags & PRINT_EXISTS) == 0); for (lastjid = 0; (lastjid = print_jail(pflags, jflags)) >= 0; ) ; @@ -390,6 +430,8 @@ print_jail(int pflags, int jflags) jid = jailparam_get(params, nparams, jflags); if (jid < 0) return jid; + else if (pflags & PRINT_EXISTS) + return 0; xo_open_instance("jail"); diff --git a/usr.sbin/lastlogin/lastlogin.8 b/usr.sbin/lastlogin/lastlogin.8 index 3b2d47fdaf76..9fd5c88d02cd 100644 --- a/usr.sbin/lastlogin/lastlogin.8 +++ b/usr.sbin/lastlogin/lastlogin.8 @@ -73,7 +73,7 @@ Generate output via .Xr libxo 3 in a selection of different human and machine readable formats. See -.Xr xo_parse_args 3 +.Xr xo_options 7 for details on command line arguments. .It Fl f Ar file Open last login database @@ -93,7 +93,7 @@ last login database .Xr last 1 , .Xr getutxent 3 , .Xr libxo 3 , -.Xr xo_parse_args 3 , +.Xr xo_options 7 , .Xr ac 8 .Sh AUTHORS .An -nosplit diff --git a/usr.sbin/lpr/lpc/lpc.c b/usr.sbin/lpr/lpc/lpc.c index a3da852de46e..b4db5bb2e29f 100644 --- a/usr.sbin/lpr/lpc/lpc.c +++ b/usr.sbin/lpr/lpc/lpc.c @@ -358,6 +358,8 @@ ingroup(const char *grname) err(1, "getgroups"); } gid = gptr->gr_gid; + if (gid == getegid()) + return(1); for (i = 0; i < ngroups; i++) if (gid == groups[i]) return(1); diff --git a/usr.sbin/makefs/ffs.c b/usr.sbin/makefs/ffs.c index aa5574c5201f..ed94abb7504f 100644 --- a/usr.sbin/makefs/ffs.c +++ b/usr.sbin/makefs/ffs.c @@ -591,6 +591,75 @@ ffs_create_image(const char *image, fsinfo_t *fsopts) return (fsopts->fd); } +static void +ffs_add_size(fsinfo_t *fsopts, size_t file_len) +{ + ffs_opt_t *ffs_opts = fsopts->fs_specific; + size_t blocks, fs_nindir, overhead; + int indir_level; + + blocks = howmany(file_len, ffs_opts->bsize); + + if (blocks <= UFS_NDADDR) { + /* Count full blocks. */ + fsopts->size += rounddown2(file_len, ffs_opts->bsize); + /* Calculate fragment size needed. */ + overhead = howmany(file_len - + rounddown2(file_len, ffs_opts->bsize), ffs_opts->fsize); + + /* + * A file could have just 1 fragment with size 1/8, 1/4 or 1/2 + * of bsize. + */ + switch (overhead) { + case 0: + break; + case 1: + fsopts->size += ffs_opts->fsize; + break; + case 2: + fsopts->size += 2 * ffs_opts->fsize; + break; + case 3: + case 4: + fsopts->size += 4 * ffs_opts->fsize; + break; + default: + fsopts->size += ffs_opts->bsize; + break; + } + return; + } + + /* File does not fit into direct blocks, count indirect blocks. */ + blocks = howmany(file_len - UFS_NDADDR * (size_t)ffs_opts->bsize, + ffs_opts->bsize); + fs_nindir = (size_t)ffs_opts->bsize / ((ffs_opts->version == 1) ? + sizeof(ufs1_daddr_t) : sizeof(ufs2_daddr_t)); + + indir_level = overhead = 0; + while (blocks > 0 && indir_level < 3) { + /* One indirect block is stored in di_ib[] */ + blocks = howmany(blocks, fs_nindir) - 1; + fsopts->size += ffs_opts->bsize * blocks; + overhead += blocks + 1; + indir_level++; + } + + assert(blocks == 0); + + if ((debug & DEBUG_FS_SIZE_DIR_NODE) != 0) { + printf("ffs_size_dir: size %jd, using %d levels of indirect " + "blocks, overhead %jd blocks\n", (uintmax_t)file_len, + indir_level, (uintmax_t)overhead); + } + + /* + * If the file is big enough to use indirect blocks, + * we allocate bsize block for trailing data. + */ + fsopts->size += roundup2(file_len, ffs_opts->bsize); +} static void ffs_size_dir(fsnode *root, fsinfo_t *fsopts) @@ -622,20 +691,6 @@ ffs_size_dir(fsnode *root, fsinfo_t *fsopts) e, tmpdir.d_namlen, this, curdirsize); \ } while (0); -#define ADDSIZE(x) do { \ - if ((size_t)(x) < UFS_NDADDR * (size_t)ffs_opts->bsize) { \ - fsopts->size += roundup((x), ffs_opts->fsize); \ - } else { \ - /* Count space consumed by indirecttion blocks. */ \ - fsopts->size += ffs_opts->bsize * \ - (howmany((x), UFS_NDADDR * ffs_opts->bsize) - 1); \ - /* \ - * If the file is big enough to use indirect blocks, \ - * we allocate bsize block for trailing data. \ - */ \ - fsopts->size += roundup((x), ffs_opts->bsize); \ - } \ -} while (0); curdirsize = 0; for (node = root; node != NULL; node = node->next) { @@ -646,13 +701,13 @@ ffs_size_dir(fsnode *root, fsinfo_t *fsopts) } else if ((node->inode->flags & FI_SIZED) == 0) { /* don't count duplicate names */ node->inode->flags |= FI_SIZED; - if (debug & DEBUG_FS_SIZE_DIR_NODE) + if ((debug & DEBUG_FS_SIZE_DIR_NODE) != 0) printf("ffs_size_dir: `%s' size %lld\n", node->name, (long long)node->inode->st.st_size); fsopts->inodes++; if (node->type == S_IFREG) - ADDSIZE(node->inode->st.st_size); + ffs_add_size(fsopts, node->inode->st.st_size); if (node->type == S_IFLNK) { size_t slen; @@ -660,13 +715,16 @@ ffs_size_dir(fsnode *root, fsinfo_t *fsopts) if (slen >= (ffs_opts->version == 1 ? UFS1_MAXSYMLINKLEN : UFS2_MAXSYMLINKLEN)) - ADDSIZE(slen); + ffs_add_size(fsopts, slen); } } if (node->type == S_IFDIR) ffs_size_dir(node->child, fsopts); } - ADDSIZE(curdirsize); + ffs_add_size(fsopts, curdirsize); + + /* Round up to full block to account fragment scattering. */ + fsopts->size = roundup2(fsopts->size, ffs_opts->bsize); if (debug & DEBUG_FS_SIZE_DIR) printf("ffs_size_dir: exit: size %lld inodes %lld\n", @@ -679,15 +737,14 @@ ffs_build_dinode1(struct ufs1_dinode *dinp, dirbuf_t *dbufp, fsnode *cur, { size_t slen; void *membuf; - struct stat *st = stampst.st_ino != 0 ? &stampst : &cur->inode->st; + struct stat *st; + st = &cur->inode->st; memset(dinp, 0, sizeof(*dinp)); dinp->di_mode = cur->inode->st.st_mode; dinp->di_nlink = cur->inode->nlink; dinp->di_size = cur->inode->st.st_size; -#if HAVE_STRUCT_STAT_ST_FLAGS - dinp->di_flags = cur->inode->st.st_flags; -#endif + dinp->di_flags = FSINODE_ST_FLAGS(*cur->inode); dinp->di_gen = random(); dinp->di_uid = cur->inode->st.st_uid; dinp->di_gid = cur->inode->st.st_gid; @@ -727,15 +784,14 @@ ffs_build_dinode2(struct ufs2_dinode *dinp, dirbuf_t *dbufp, fsnode *cur, { size_t slen; void *membuf; - struct stat *st = stampst.st_ino != 0 ? &stampst : &cur->inode->st; + struct stat *st; + st = &cur->inode->st; memset(dinp, 0, sizeof(*dinp)); dinp->di_mode = cur->inode->st.st_mode; dinp->di_nlink = cur->inode->nlink; dinp->di_size = cur->inode->st.st_size; -#if HAVE_STRUCT_STAT_ST_FLAGS - dinp->di_flags = cur->inode->st.st_flags; -#endif + dinp->di_flags = FSINODE_ST_FLAGS(*cur->inode); dinp->di_gen = random(); dinp->di_uid = cur->inode->st.st_uid; dinp->di_gid = cur->inode->st.st_gid; @@ -1058,7 +1114,7 @@ ffs_make_dirbuf(dirbuf_t *dbuf, const char *name, fsnode *node, int needswap) reclen = DIRSIZ_SWAP(0, &de, needswap); de.d_reclen = ufs_rw16(reclen, needswap); - dp = (struct direct *)(dbuf->buf + dbuf->cur); + dp = dbuf->buf == NULL ? NULL : (struct direct *)(dbuf->buf + dbuf->cur); llen = 0; if (dp != NULL) llen = DIRSIZ_SWAP(0, dp, needswap); diff --git a/usr.sbin/makefs/ffs/mkfs.c b/usr.sbin/makefs/ffs/mkfs.c index b2f752102a69..81e3da5725c8 100644 --- a/usr.sbin/makefs/ffs/mkfs.c +++ b/usr.sbin/makefs/ffs/mkfs.c @@ -579,13 +579,21 @@ ffs_write_superblock(struct fs *fs, const fsinfo_t *fsopts) { int size, blks, i, saveflag; uint32_t cylno; - void *space; + void *info, *space; char *wrbuf; saveflag = fs->fs_flags & FS_INTERNAL; fs->fs_flags &= ~FS_INTERNAL; - memcpy(writebuf, &sblock, sbsize); + /* + * Write out the superblock. Blank out the summary info field, as it's + * a random pointer that would make the resulting image unreproducible. + */ + info = fs->fs_si; + fs->fs_si = NULL; + memcpy(writebuf, fs, sbsize); + fs->fs_si = info; + if (fsopts->needswap) ffs_sb_swap(fs, (struct fs*)writebuf); ffs_wtfs(fs->fs_sblockloc / sectorsize, sbsize, writebuf, fsopts); diff --git a/usr.sbin/makefs/makefs.8 b/usr.sbin/makefs/makefs.8 index ae11309953e4..d20f69d87559 100644 --- a/usr.sbin/makefs/makefs.8 +++ b/usr.sbin/makefs/makefs.8 @@ -33,8 +33,7 @@ .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd January 19, 2024 - +.Dd July 19, 2025 .Dt MAKEFS 8 .Os .Sh NAME @@ -259,9 +258,13 @@ can be a .Pa pathname , where the timestamps are derived from that file, or an integer value interpreted as the number of seconds from the Epoch. -Note that timestamps specified in an +Timestamps in a .Xr mtree 5 -spec file, override the default timestamp. +specfile (specified with +.Fl F ) +are used even if a default timestamp is specified. +However, the timestamps in an mtree manifest are ignored +if a default timestamp is specified. .It Fl t Ar fs-type Create an .Ar fs-type @@ -547,6 +550,12 @@ This option allows the default heuristic to be overridden. .It verify-txgs Prompt OpenZFS to verify pool metadata during import. This is disabled by default as it may significantly increase import times. +.It poolguid +Use the specified 64-bit integer as the pool GUID. +If this option is not specified, the pool GUID will be random but fixed +across multiple identical invocations of +.Nm . +This option is useful for testing but not required for reproducibility. .It poolname The name of the ZFS pool. This option must be specified. @@ -592,10 +601,17 @@ The following properties may be set for a dataset: .Bl -tag -compact -offset indent .It atime .It canmount +.It compression .It exec .It mountpoint .It setuid .El +Note that +.Nm +does not implement compression of files included in the image, +regardless of the value of the +.Dv compression +property. .El .Sh SEE ALSO .Xr mtree 5 , diff --git a/usr.sbin/makefs/makefs.c b/usr.sbin/makefs/makefs.c index dbd8f3a4d39e..46e513e22b25 100644 --- a/usr.sbin/makefs/makefs.c +++ b/usr.sbin/makefs/makefs.c @@ -43,6 +43,7 @@ #include <ctype.h> #include <errno.h> #include <limits.h> +#include <locale.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -103,6 +104,13 @@ main(int argc, char *argv[]) setprogname(argv[0]); + /* + * Set the locale for collation, so that directory entry sorting is + * consistent. + */ + if (setlocale(LC_COLLATE, "C") == NULL) + err(1, "setlocale"); + debug = 0; if ((fstype = get_fstype(DEFAULT_FSTYPE)) == NULL) errx(1, "Unknown default fs type `%s'.", DEFAULT_FSTYPE); @@ -428,6 +436,22 @@ set_option_var(const option_t *options, const char *var, const char *val, return -1; } +void +set_tstamp(fsnode *cur) +{ + cur->inode->st.st_atime = stampst.st_atime; + cur->inode->st.st_mtime = stampst.st_mtime; + cur->inode->st.st_ctime = stampst.st_ctime; +#if HAVE_STRUCT_STAT_ST_MTIMENSEC + cur->inode->st.st_atimensec = stampst.st_atimensec; + cur->inode->st.st_mtimensec = stampst.st_mtimensec; + cur->inode->st.st_ctimensec = stampst.st_ctimensec; +#endif +#if HAVE_STRUCT_STAT_BIRTHTIME + cur->inode->st.st_birthtime = stampst.st_birthtime; + cur->inode->st.st_birthtimensec = stampst.st_birthtimensec; +#endif +} static fstype_t * get_fstype(const char *type) @@ -470,7 +494,7 @@ get_tstamp(const char *b, struct stat *st) } st->st_ino = 1; -#ifdef HAVE_STRUCT_STAT_BIRTHTIME +#if HAVE_STRUCT_STAT_BIRTHTIME st->st_birthtime = #endif st->st_mtime = st->st_ctime = st->st_atime = when; diff --git a/usr.sbin/makefs/makefs.h b/usr.sbin/makefs/makefs.h index db0addc4bc94..3cd56a036670 100644 --- a/usr.sbin/makefs/makefs.h +++ b/usr.sbin/makefs/makefs.h @@ -40,6 +40,12 @@ #ifndef _MAKEFS_H #define _MAKEFS_H +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#else +#define HAVE_STRUCT_STAT_ST_FLAGS 1 +#endif + #include <sys/stat.h> #include <err.h> @@ -85,8 +91,17 @@ typedef struct { enum fi_flags flags; /* flags used by fs specific code */ void *param; /* for use by individual fs impls */ struct stat st; /* stat entry */ +#if !HAVE_STRUCT_STAT_ST_FLAGS + uint32_t st_flags; /* stand-in for st.st_flags */ +#endif } fsinode; +#if HAVE_STRUCT_STAT_ST_FLAGS +#define FSINODE_ST_FLAGS(inode) (inode).st.st_flags +#else +#define FSINODE_ST_FLAGS(inode) (inode).st_flags +#endif + typedef struct _fsnode { struct _fsnode *parent; /* parent (NULL if root) */ struct _fsnode *child; /* child (if type == S_IFDIR) */ @@ -173,6 +188,7 @@ fsnode * read_mtree(const char *, fsnode *); int set_option(const option_t *, const char *, char *, size_t); int set_option_var(const option_t *, const char *, const char *, char *, size_t); +void set_tstamp(fsnode *); fsnode * walk_dir(const char *, const char *, fsnode *, fsnode *); void free_fsnodes(fsnode *); option_t * copy_opts(const option_t *); diff --git a/usr.sbin/makefs/msdos/Makefile.inc b/usr.sbin/makefs/msdos/Makefile.inc index 78ddc7804b31..cfa9e0e114c2 100644 --- a/usr.sbin/makefs/msdos/Makefile.inc +++ b/usr.sbin/makefs/msdos/Makefile.inc @@ -3,7 +3,7 @@ MSDOS_NEWFS= ${SRCTOP}/sbin/newfs_msdos .PATH: ${SRCDIR}/msdos ${MSDOS} ${MSDOS_NEWFS} -CFLAGS+= -DMAKEFS -I${MSDOS} -I${MSDOS_NEWFS} +CFLAGS+= -DMAKEFS -D_WANT_MSDOSFS_INTERNALS -I${MSDOS} -I${MSDOS_NEWFS} SRCS+= mkfs_msdos.c SRCS+= msdosfs_conv.c msdosfs_denode.c msdosfs_fat.c msdosfs_lookup.c diff --git a/usr.sbin/makefs/msdos/msdosfs_vnops.c b/usr.sbin/makefs/msdos/msdosfs_vnops.c index ae1fa3f7bf75..b104f419a86a 100644 --- a/usr.sbin/makefs/msdos/msdosfs_vnops.c +++ b/usr.sbin/makefs/msdos/msdosfs_vnops.c @@ -94,10 +94,8 @@ static void unix2fattime(const struct timespec *tsp, uint16_t *ddp, static void msdosfs_times(struct denode *dep, const struct stat *st) { - if (stampst.st_ino) - st = &stampst; -#ifdef HAVE_STRUCT_STAT_BIRTHTIME +#if HAVE_STRUCT_STAT_BIRTHTIME unix2fattime(&st->st_birthtim, &dep->de_CDate, &dep->de_CTime); #else unix2fattime(&st->st_ctim, &dep->de_CDate, &dep->de_CTime); @@ -113,7 +111,7 @@ unix2fattime(const struct timespec *tsp, uint16_t *ddp, uint16_t *dtp) struct tm lt = {0}; t1 = tsp->tv_sec; - localtime_r(&t1, <); + gmtime_r(&t1, <); unsigned long fat_time = ((lt.tm_year - 80) << 25) | ((lt.tm_mon + 1) << 21) | diff --git a/usr.sbin/makefs/mtree.c b/usr.sbin/makefs/mtree.c index fddc32b85322..4f3c3f85dcc3 100644 --- a/usr.sbin/makefs/mtree.c +++ b/usr.sbin/makefs/mtree.c @@ -533,13 +533,11 @@ read_mtree_keywords(FILE *fp, fsnode *node) break; } flset = flclr = 0; -#if HAVE_STRUCT_STAT_ST_FLAGS if (!strtofflags(&value, &flset, &flclr)) { - st->st_flags &= ~flclr; - st->st_flags |= flset; + FSINODE_ST_FLAGS(*node->inode) &= ~flclr; + FSINODE_ST_FLAGS(*node->inode) |= flset; } else error = errno; -#endif } else error = ENOSYS; break; @@ -633,6 +631,9 @@ read_mtree_keywords(FILE *fp, fsnode *node) } /* Ignore. */ } else if (strcmp(keyword, "time") == 0) { + /* Ignore if a default timestamp is present. */ + if (stampst.st_ino != 0) + break; if (value == NULL) { error = ENOATTR; break; @@ -722,7 +723,9 @@ read_mtree_keywords(FILE *fp, fsnode *node) return (error); st->st_mode = (st->st_mode & ~S_IFMT) | node->type; - + /* Store default timestamp, if present. */ + if (stampst.st_ino != 0) + set_tstamp(node); /* Nothing more to do for the global defaults. */ if (node->name == NULL) return (0); @@ -1053,8 +1056,16 @@ read_mtree(const char *fname, fsnode *node) mtree_global.inode = &mtree_global_inode; mtree_global_inode.nlink = 1; mtree_global_inode.st.st_nlink = 1; - mtree_global_inode.st.st_atime = mtree_global_inode.st.st_ctime = - mtree_global_inode.st.st_mtime = time(NULL); + if (stampst.st_ino != 0) { + set_tstamp(&mtree_global); + } else { +#if HAVE_STRUCT_STAT_BIRTHTIME + mtree_global_inode.st.st_birthtime = +#endif + mtree_global_inode.st.st_atime = + mtree_global_inode.st.st_ctime = + mtree_global_inode.st.st_mtime = time(NULL); + } errors = warnings = 0; setgroupent(1); diff --git a/usr.sbin/makefs/tests/Makefile b/usr.sbin/makefs/tests/Makefile index 39844827f999..748bafa06211 100644 --- a/usr.sbin/makefs/tests/Makefile +++ b/usr.sbin/makefs/tests/Makefile @@ -3,12 +3,10 @@ ATF_TESTS_SH+= makefs_cd9660_tests TEST_METADATA.makefs_cd9660_tests+= required_files="/sbin/mount_cd9660" ATF_TESTS_SH+= makefs_ffs_tests +ATF_TESTS_SH+= makefs_msdos_tests +TEST_METADATA.makefs_msdos_tests+= required_files="/sbin/mount_msdosfs" .if ${MK_ZFS} != "no" ATF_TESTS_SH+= makefs_zfs_tests -# ZFS pools created by makefs always have the same GUID, so OpenZFS -# refuses to import more than one at a time. Thus the ZFS tests cannot -# be run in parallel for now. -TEST_METADATA.makefs_zfs_tests+= is_exclusive="true" .endif BINDIR= ${TESTSDIR} diff --git a/usr.sbin/makefs/tests/makefs_cd9660_tests.sh b/usr.sbin/makefs/tests/makefs_cd9660_tests.sh index 066a9d6ec0e0..e058dfc57b7b 100644 --- a/usr.sbin/makefs/tests/makefs_cd9660_tests.sh +++ b/usr.sbin/makefs/tests/makefs_cd9660_tests.sh @@ -374,6 +374,81 @@ o_flag_rockridge_dev_nodes_cleanup() common_cleanup } +atf_test_case T_flag_dir cleanup +T_flag_dir_body() +{ + timestamp=1742574909 + check_cd9660_support + create_test_dirs + + mkdir -p $TEST_INPUTS_DIR/dir1 + atf_check -e empty -o empty -s exit:0 \ + $MAKEFS -T $timestamp -o rockridge $TEST_IMAGE $TEST_INPUTS_DIR + + mount_image + eval $(stat -s $TEST_MOUNT_DIR/dir1) + atf_check_equal $st_atime $timestamp + atf_check_equal $st_mtime $timestamp + atf_check_equal $st_ctime $timestamp +} + +T_flag_dir_cleanup() +{ + common_cleanup +} + +atf_test_case T_flag_F_flag cleanup +T_flag_F_flag_body() +{ + atf_expect_fail "-F doesn't take precedence over -T" + timestamp_F=1742574909 + timestamp_T=1742574910 + create_test_dirs + mkdir -p $TEST_INPUTS_DIR/dir1 + + atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \ + mtree -c -k "type,time" -p $TEST_INPUTS_DIR + change_mtree_timestamp $TEST_SPEC_FILE $timestamp_F + atf_check -e empty -o not-empty -s exit:0 \ + $MAKEFS -F $TEST_SPEC_FILE -T $timestamp_T -o rockridge $TEST_IMAGE $TEST_INPUTS_DIR + + mount_image + eval $(stat -s $TEST_MOUNT_DIR/dir1) + atf_check_equal $st_atime $timestamp_F + atf_check_equal $st_mtime $timestamp_F + atf_check_equal $st_ctime $timestamp_F +} + +T_flag_F_flag_cleanup() +{ + common_cleanup +} + +atf_test_case T_flag_mtree cleanup +T_flag_mtree_body() +{ + timestamp=1742574909 + create_test_dirs + mkdir -p $TEST_INPUTS_DIR/dir1 + + atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \ + mtree -c -k "type" -p $TEST_INPUTS_DIR + atf_check -e empty -o empty -s exit:0 \ + $MAKEFS -T $timestamp -o rockridge $TEST_IMAGE $TEST_SPEC_FILE + + check_cd9660_support + mount_image + eval $(stat -s $TEST_MOUNT_DIR/dir1) + atf_check_equal $st_atime $timestamp + atf_check_equal $st_mtime $timestamp + atf_check_equal $st_ctime $timestamp +} + +T_flag_mtree_cleanup() +{ + common_cleanup +} + atf_test_case duplicate_names cleanup duplicate_names_head() { @@ -425,6 +500,9 @@ atf_init_test_cases() atf_add_test_case o_flag_publisher atf_add_test_case o_flag_rockridge atf_add_test_case o_flag_rockridge_dev_nodes + atf_add_test_case T_flag_dir + atf_add_test_case T_flag_F_flag + atf_add_test_case T_flag_mtree atf_add_test_case duplicate_names } diff --git a/usr.sbin/makefs/tests/makefs_ffs_tests.sh b/usr.sbin/makefs/tests/makefs_ffs_tests.sh index 2505b2e24d72..f828f632b06e 100644 --- a/usr.sbin/makefs/tests/makefs_ffs_tests.sh +++ b/usr.sbin/makefs/tests/makefs_ffs_tests.sh @@ -241,6 +241,80 @@ o_flag_version_2_cleanup() common_cleanup } + +atf_test_case T_flag_dir cleanup +T_flag_dir_body() +{ + timestamp=1742574909 + create_test_dirs + + mkdir -p $TEST_INPUTS_DIR/dir1 + atf_check -e empty -o not-empty -s exit:0 \ + $MAKEFS -M 1m -T $timestamp $TEST_IMAGE $TEST_INPUTS_DIR + + mount_image + eval $(stat -s $TEST_MOUNT_DIR/dir1) + atf_check_equal $st_atime $timestamp + atf_check_equal $st_mtime $timestamp + atf_check_equal $st_ctime $timestamp +} + +T_flag_dir_cleanup() +{ + common_cleanup +} + +atf_test_case T_flag_F_flag cleanup +T_flag_F_flag_body() +{ + atf_expect_fail "-F doesn't take precedence over -T" + timestamp_F=1742574909 + timestamp_T=1742574910 + create_test_dirs + mkdir -p $TEST_INPUTS_DIR/dir1 + + atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \ + mtree -c -k "type,time" -p $TEST_INPUTS_DIR + change_mtree_timestamp $TEST_SPEC_FILE $timestamp_F + atf_check -e empty -o not-empty -s exit:0 \ + $MAKEFS -F $TEST_SPEC_FILE -T $timestamp_T -M 1m $TEST_IMAGE $TEST_INPUTS_DIR + + mount_image + eval $(stat -s $TEST_MOUNT_DIR/dir1) + atf_check_equal $st_atime $timestamp_F + atf_check_equal $st_mtime $timestamp_F + atf_check_equal $st_ctime $timestamp_F +} + +T_flag_F_flag_cleanup() +{ + common_cleanup +} + +atf_test_case T_flag_mtree cleanup +T_flag_mtree_body() +{ + timestamp=1742574909 + create_test_dirs + mkdir -p $TEST_INPUTS_DIR/dir1 + + atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \ + mtree -c -k "type" -p $TEST_INPUTS_DIR + atf_check -e empty -o not-empty -s exit:0 \ + $MAKEFS -M 1m -T $timestamp $TEST_IMAGE $TEST_SPEC_FILE + + mount_image + eval $(stat -s $TEST_MOUNT_DIR/dir1) + atf_check_equal $st_atime $timestamp + atf_check_equal $st_mtime $timestamp + atf_check_equal $st_ctime $timestamp +} + +T_flag_mtree_cleanup() +{ + common_cleanup +} + atf_init_test_cases() { @@ -255,4 +329,7 @@ atf_init_test_cases() atf_add_test_case o_flag_version_1 atf_add_test_case o_flag_version_2 + atf_add_test_case T_flag_dir + atf_add_test_case T_flag_F_flag + atf_add_test_case T_flag_mtree } diff --git a/usr.sbin/makefs/tests/makefs_msdos_tests.sh b/usr.sbin/makefs/tests/makefs_msdos_tests.sh new file mode 100644 index 000000000000..fb94429b477b --- /dev/null +++ b/usr.sbin/makefs/tests/makefs_msdos_tests.sh @@ -0,0 +1,136 @@ +#- +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2025 The FreeBSD Foundation +# +# This software was developed by Klara, Inc. +# under sponsorship from the FreeBSD Foundation. +# +# 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. +# + +MAKEFS="makefs -t msdos" +MOUNT="mount_msdosfs" +. "$(dirname "$0")/makefs_tests_common.sh" + +common_cleanup() +{ + if ! test_md_device=$(cat $TEST_MD_DEVICE_FILE); then + echo "$TEST_MD_DEVICE_FILE could not be opened; has an md(4) device been attached?" + return + fi + + umount -f /dev/$test_md_device || : + mdconfig -d -u $test_md_device || : +} + +check_msdosfs_support() +{ + kldstat -m msdosfs || \ + atf_skip "Requires msdosfs filesystem support to be present in the kernel" +} + +atf_test_case T_flag_dir cleanup +T_flag_dir_body() +{ + atf_expect_fail \ + "The msdos backend saves the wrong timestamp value" \ + "(possibly due to the 2s resolution for FAT timestamp)" + timestamp=1742574909 + check_msdosfs_support + + create_test_dirs + mkdir -p $TEST_INPUTS_DIR/dir1 + atf_check -e empty -o not-empty -s exit:0 \ + $MAKEFS -T $timestamp -s 1m $TEST_IMAGE $TEST_INPUTS_DIR + + mount_image + eval $(stat -s $TEST_MOUNT_DIR/dir1) + atf_check_equal $st_atime $timestamp + atf_check_equal $st_mtime $timestamp + atf_check_equal $st_ctime $timestamp +} + +T_flag_dir_cleanup() +{ + common_cleanup +} + +atf_test_case T_flag_F_flag cleanup +T_flag_F_flag_body() +{ + atf_expect_fail "-F doesn't take precedence over -T" + timestamp_F=1742574909 + timestamp_T=1742574910 + create_test_dirs + mkdir -p $TEST_INPUTS_DIR/dir1 + + atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \ + mtree -c -k "type,time" -p $TEST_INPUTS_DIR + change_mtree_timestamp $TEST_SPEC_FILE $timestamp_F + atf_check -e empty -o not-empty -s exit:0 \ + $MAKEFS -F $TEST_SPEC_FILE -T $timestamp_T -s 1m $TEST_IMAGE $TEST_INPUTS_DIR + + mount_image + eval $(stat -s $TEST_MOUNT_DIR/dir1) + atf_check_equal $st_atime $timestamp_F + atf_check_equal $st_mtime $timestamp_F + atf_check_equal $st_ctime $timestamp_F +} + +T_flag_F_flag_cleanup() +{ + common_cleanup +} + +atf_test_case T_flag_mtree cleanup +T_flag_mtree_body() +{ + timestamp=1742574908 # Even value, timestamp precision is 2s. + check_msdosfs_support + + create_test_dirs + mkdir -p $TEST_INPUTS_DIR/dir1 + atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \ + mtree -c -k "type" -p $TEST_INPUTS_DIR + atf_check -e empty -o not-empty -s exit:0 \ + $MAKEFS -T $timestamp -s 1m $TEST_IMAGE $TEST_SPEC_FILE + + mount_image + eval $(stat -s $TEST_MOUNT_DIR/dir1) + # FAT directory entries don't have an access time, just a date. + #atf_check_equal $st_atime $timestamp + atf_check_equal $st_mtime $timestamp + atf_check_equal $st_ctime $timestamp +} + +T_flag_mtree_cleanup() +{ + common_cleanup +} + +atf_init_test_cases() +{ + atf_add_test_case T_flag_dir + atf_add_test_case T_flag_F_flag + atf_add_test_case T_flag_mtree +} diff --git a/usr.sbin/makefs/tests/makefs_tests_common.sh b/usr.sbin/makefs/tests/makefs_tests_common.sh index b418cafc90a6..edb79bc811e1 100644 --- a/usr.sbin/makefs/tests/makefs_tests_common.sh +++ b/usr.sbin/makefs/tests/makefs_tests_common.sh @@ -141,3 +141,10 @@ mount_image() $MOUNT ${1} /dev/$(cat $TEST_MD_DEVICE_FILE) $TEST_MOUNT_DIR } +change_mtree_timestamp() +{ + filename="$1" + timestamp="$2" + + sed -i "" "s/time=.*$/time=${timestamp}.0/g" "$filename" +} diff --git a/usr.sbin/makefs/tests/makefs_zfs_tests.sh b/usr.sbin/makefs/tests/makefs_zfs_tests.sh index 3d5819439a73..2fafce85b347 100644 --- a/usr.sbin/makefs/tests/makefs_zfs_tests.sh +++ b/usr.sbin/makefs/tests/makefs_zfs_tests.sh @@ -28,7 +28,7 @@ # SUCH DAMAGE. # -MAKEFS="makefs -t zfs -o verify-txgs=true" +MAKEFS="makefs -t zfs -o verify-txgs=true -o poolguid=$$" ZFS_POOL_NAME="makefstest$$" TEST_ZFS_POOL_NAME="$TMPDIR/poolname" @@ -124,6 +124,95 @@ basic_cleanup() common_cleanup } +# +# Try configuring various compression algorithms. +# +atf_test_case compression cleanup +compression_body() +{ + create_test_inputs + + cd $TEST_INPUTS_DIR + mkdir dir + mkdir dir2 + cd - + + for alg in off on lzjb gzip gzip-1 gzip-2 gzip-3 gzip-4 \ + gzip-5 gzip-6 gzip-7 gzip-8 gzip-9 zle lz4 zstd; do + atf_check $MAKEFS -s 1g -o rootpath=/ \ + -o poolname=$ZFS_POOL_NAME \ + -o fs=${ZFS_POOL_NAME}\;compression=$alg \ + -o fs=${ZFS_POOL_NAME}/dir \ + -o fs=${ZFS_POOL_NAME}/dir2\;compression=off \ + $TEST_IMAGE $TEST_INPUTS_DIR + + import_image + + check_image_contents + + if [ $alg = gzip-6 ]; then + # ZFS reports gzip-6 as just gzip since it uses + # a default compression level of 6. + alg=gzip + fi + # The "dir" dataset's compression algorithm should be + # inherited from the root dataset. + atf_check -o inline:$alg\\n -e empty -s exit:0 \ + zfs get -H -o value compression ${ZFS_POOL_NAME} + atf_check -o inline:$alg\\n -e empty -s exit:0 \ + zfs get -H -o value compression ${ZFS_POOL_NAME}/dir + atf_check -o inline:off\\n -e empty -s exit:0 \ + zfs get -H -o value compression ${ZFS_POOL_NAME}/dir2 + + atf_check -e ignore dd if=/dev/random \ + of=${TEST_MOUNT_DIR}/dir/random bs=1M count=10 + atf_check -e ignore dd if=/dev/zero \ + of=${TEST_MOUNT_DIR}/dir/zero bs=1M count=10 + atf_check -e ignore dd if=/dev/zero \ + of=${TEST_MOUNT_DIR}/dir2/zero bs=1M count=10 + + # Export and reimport to ensure that everything is + # flushed to disk. + atf_check zpool export ${ZFS_POOL_NAME} + atf_check -o ignore -e empty -s exit:0 \ + zdb -e -p /dev/$(cat $TEST_MD_DEVICE_FILE) -mmm -ddddd \ + $ZFS_POOL_NAME + atf_check zpool import -R $TEST_MOUNT_DIR $ZFS_POOL_NAME + + if [ $alg = off ]; then + # If compression is off, the files should be the + # same size as the input. + atf_check -o match:"^11[[:space:]]+${TEST_MOUNT_DIR}/dir/random" \ + du -m ${TEST_MOUNT_DIR}/dir/random + atf_check -o match:"^11[[:space:]]+${TEST_MOUNT_DIR}/dir/zero" \ + du -m ${TEST_MOUNT_DIR}/dir/zero + atf_check -o match:"^11[[:space:]]+${TEST_MOUNT_DIR}/dir2/zero" \ + du -m ${TEST_MOUNT_DIR}/dir2/zero + else + # If compression is on, the dir/zero file ought + # to be smaller. + atf_check -o match:"^1[[:space:]]+${TEST_MOUNT_DIR}/dir/zero" \ + du -m ${TEST_MOUNT_DIR}/dir/zero + atf_check -o match:"^11[[:space:]]+${TEST_MOUNT_DIR}/dir/random" \ + du -m ${TEST_MOUNT_DIR}/dir/random + atf_check -o match:"^11[[:space:]]+${TEST_MOUNT_DIR}/dir2/zero" \ + du -m ${TEST_MOUNT_DIR}/dir2/zero + fi + + atf_check zpool destroy ${ZFS_POOL_NAME} + atf_check rm -f ${TEST_ZFS_POOL_NAME} + atf_check mdconfig -d -u $(cat ${TEST_MD_DEVICE_FILE}) + atf_check rm -f ${TEST_MD_DEVICE_FILE} + done +} +compression_cleanup() +{ + common_cleanup +} + +# +# Try destroying a dataset that was created by makefs. +# atf_test_case dataset_removal cleanup dataset_removal_body() { @@ -858,10 +947,88 @@ perms_cleanup() common_cleanup } +# +# Verify that -T timestamps are honored. +# +atf_test_case T_flag_dir cleanup +T_flag_dir_body() +{ + timestamp=1742574909 + create_test_dirs + mkdir -p $TEST_INPUTS_DIR/dir1 + + atf_check $MAKEFS -T $timestamp -s 10g -o rootpath=/ -o poolname=$ZFS_POOL_NAME \ + $TEST_IMAGE $TEST_INPUTS_DIR + + import_image + eval $(stat -s $TEST_MOUNT_DIR/dir1) + atf_check_equal $st_atime $timestamp + atf_check_equal $st_mtime $timestamp + atf_check_equal $st_ctime $timestamp +} + +T_flag_dir_cleanup() +{ + common_cleanup +} + +atf_test_case T_flag_F_flag cleanup +T_flag_F_flag_body() +{ + atf_expect_fail "-F doesn't take precedence over -T" + timestamp_F=1742574909 + timestamp_T=1742574910 + create_test_dirs + mkdir -p $TEST_INPUTS_DIR/dir1 + + atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \ + mtree -c -k "type,time" -p $TEST_INPUTS_DIR + change_mtree_timestamp $TEST_SPEC_FILE $timestamp_F + atf_check -e empty -o not-empty -s exit:0 \ + $MAKEFS -F $TEST_SPEC_FILE -T $timestamp_T -s 10g -o rootpath=/ \ + -o poolname=$ZFS_POOL_NAME $TEST_IMAGE $TEST_INPUTS_DIR + + mount_image + eval $(stat -s $TEST_MOUNT_DIR/dir1) + atf_check_equal $st_atime $timestamp_F + atf_check_equal $st_mtime $timestamp_F + atf_check_equal $st_ctime $timestamp_F +} + +T_flag_F_flag_cleanup() +{ + common_cleanup +} + +atf_test_case T_flag_mtree cleanup +T_flag_mtree_body() +{ + timestamp=1742574909 + create_test_dirs + mkdir -p $TEST_INPUTS_DIR/dir1 + + atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \ + mtree -c -k "type" -p $TEST_INPUTS_DIR + atf_check $MAKEFS -T $timestamp -s 10g -o rootpath=/ -o poolname=$ZFS_POOL_NAME \ + $TEST_IMAGE $TEST_SPEC_FILE + + import_image + eval $(stat -s $TEST_MOUNT_DIR/dir1) + atf_check_equal $st_atime $timestamp + atf_check_equal $st_mtime $timestamp + atf_check_equal $st_ctime $timestamp +} + +T_flag_mtree_cleanup() +{ + common_cleanup +} + atf_init_test_cases() { atf_add_test_case autoexpand atf_add_test_case basic + atf_add_test_case compression atf_add_test_case dataset_removal atf_add_test_case devfs atf_add_test_case empty_dir @@ -883,6 +1050,9 @@ atf_init_test_cases() atf_add_test_case root_props atf_add_test_case used_space_props atf_add_test_case perms + atf_add_test_case T_flag_dir + atf_add_test_case T_flag_F_flag + atf_add_test_case T_flag_mtree # XXXMJ tests: # - test with different ashifts (at least, 9 and 12), different image sizes diff --git a/usr.sbin/makefs/walk.c b/usr.sbin/makefs/walk.c index fe1fe8df80db..65ba3f41fe02 100644 --- a/usr.sbin/makefs/walk.c +++ b/usr.sbin/makefs/walk.c @@ -59,6 +59,50 @@ static void apply_specentry(const char *, NODE *, fsnode *); static fsnode *create_fsnode(const char *, const char *, const char *, struct stat *); +static int +cmp(const void *_a, const void *_b) +{ + const fsnode * const *a = _a; + const fsnode * const *b = _b; + + assert(strcmp((*a)->name, (*b)->name) != 0); + if (strcmp((*a)->name, ".") == 0) + return (-1); + if (strcmp((*b)->name, ".") == 0) + return (1); + return (strcoll((*a)->name, (*b)->name)); +} + +/* + * Sort the entries rather than relying on the order given by readdir(3), + * which might not be reproducible. + */ +static fsnode * +sort_dir(fsnode *list) +{ + fsnode **array; + fsnode *cur; + size_t nitems, i; + + nitems = 0; + for (cur = list; cur != NULL; cur = cur->next) + nitems++; + assert(nitems > 0); + + array = malloc(nitems * sizeof(fsnode *)); + if (array == NULL) + err(1, "malloc"); + for (i = 0, cur = list; cur != NULL; i++, cur = cur->next) + array[i] = cur; + qsort(array, nitems, sizeof(fsnode *), cmp); + for (i = 0; i < nitems; i++) { + array[i]->first = array[0]; + array[i]->next = i == nitems - 1 ? NULL : array[i + 1]; + } + cur = array[0]; + free(array); + return (cur); +} /* * walk_dir -- @@ -71,7 +115,7 @@ static fsnode *create_fsnode(const char *, const char *, const char *, fsnode * walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join) { - fsnode *first, *cur, *prev, *last; + fsnode *first, *cur; DIR *dirp; struct dirent *dent; char path[MAXPATHLEN + 1]; @@ -95,10 +139,8 @@ walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join) first = cur = join; while (cur->next != NULL) cur = cur->next; - prev = cur; } else - first = prev = NULL; - last = prev; + first = NULL; while ((dent = readdir(dirp)) != NULL) { name = dent->d_name; dot = 0; @@ -136,10 +178,6 @@ walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join) for (;;) { if (cur == NULL || strcmp(cur->name, name) == 0) break; - if (cur == last) { - cur = NULL; - break; - } cur = cur->next; } if (cur != NULL) { @@ -160,24 +198,11 @@ walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join) cur = create_fsnode(root, dir, name, &stbuf); cur->parent = parent; - if (dot) { - /* ensure "." is at the start of the list */ - cur->next = first; - first = cur; - if (! prev) - prev = cur; - cur->first = first; - } else { /* not "." */ - if (prev) - prev->next = cur; - prev = cur; - if (!first) - first = cur; - cur->first = first; - if (S_ISDIR(cur->type)) { - cur->child = walk_dir(root, rp, cur, NULL); - continue; - } + cur->next = first; + first = cur; + if (!dot && S_ISDIR(cur->type)) { + cur->child = walk_dir(root, rp, cur, NULL); + continue; } if (stbuf.st_nlink > 1) { fsinode *curino; @@ -204,13 +229,9 @@ walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join) cur->symlink = estrdup(slink); } } - assert(first != NULL); - if (join == NULL) - for (cur = first->next; cur != NULL; cur = cur->next) - cur->first = first; if (closedir(dirp) == -1) err(1, "Can't closedir `%s/%s'", root, dir); - return (first); + return (sort_dir(first)); } static fsnode * @@ -227,20 +248,8 @@ create_fsnode(const char *root, const char *path, const char *name, cur->type = stbuf->st_mode & S_IFMT; cur->inode->nlink = 1; cur->inode->st = *stbuf; - if (stampst.st_ino) { - cur->inode->st.st_atime = stampst.st_atime; - cur->inode->st.st_mtime = stampst.st_mtime; - cur->inode->st.st_ctime = stampst.st_ctime; -#if HAVE_STRUCT_STAT_ST_MTIMENSEC - cur->inode->st.st_atimensec = stampst.st_atimensec; - cur->inode->st.st_mtimensec = stampst.st_mtimensec; - cur->inode->st.st_ctimensec = stampst.st_ctimensec; -#endif -#if HAVE_STRUCT_STAT_BIRTHTIME - cur->inode->st.st_birthtime = stampst.st_birthtime; - cur->inode->st.st_birthtimensec = stampst.st_birthtimensec; -#endif - } + if (stampst.st_ino != 0) + set_tstamp(cur); return (cur); } @@ -530,14 +539,12 @@ apply_specentry(const char *dir, NODE *specnode, fsnode *dirnode) dirnode->inode->st.st_uid, specnode->st_uid); dirnode->inode->st.st_uid = specnode->st_uid; } -#if HAVE_STRUCT_STAT_ST_FLAGS if (specnode->flags & F_FLAGS) { ASEPRINT("flags", "%#lX", - (unsigned long)dirnode->inode->st.st_flags, + (unsigned long)FSINODE_ST_FLAGS(*dirnode->inode), (unsigned long)specnode->st_flags); - dirnode->inode->st.st_flags = specnode->st_flags; + FSINODE_ST_FLAGS(*dirnode->inode) = specnode->st_flags; } -#endif /* if (specnode->flags & F_DEV) { ASEPRINT("rdev", "%#llx", (unsigned long long)dirnode->inode->st.st_rdev, diff --git a/usr.sbin/makefs/zfs.c b/usr.sbin/makefs/zfs.c index 66e7f8dafc9c..e33a182e5c8f 100644 --- a/usr.sbin/makefs/zfs.c +++ b/usr.sbin/makefs/zfs.c @@ -38,6 +38,7 @@ #include <stdalign.h> #include <stdbool.h> #include <stddef.h> +#include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> @@ -85,6 +86,8 @@ zfs_prep_opts(fsinfo_t *fsopts) 0, 0, "Bootable dataset" }, { '\0', "mssize", &zfs->mssize, OPT_INT64, MINMSSIZE, MAXMSSIZE, "Metaslab size" }, + { '\0', "poolguid", &zfs->poolguid, OPT_INT64, + 0, INT64_MAX, "ZFS pool GUID" }, { '\0', "poolname", &zfs->poolname, OPT_STRPTR, 0, 0, "ZFS pool name" }, { '\0', "rootpath", &zfs->rootpath, OPT_STRPTR, @@ -547,7 +550,8 @@ pool_init(zfs_opt_t *zfs) { uint64_t dnid; - zfs->poolguid = randomguid(); + if (zfs->poolguid == 0) + zfs->poolguid = randomguid(); zfs->vdevguid = randomguid(); zfs->mos = objset_alloc(zfs, DMU_OST_META); @@ -592,7 +596,7 @@ pool_labels_write(zfs_opt_t *zfs) * checksum is calculated in vdev_label_write(). */ for (size_t uoff = 0; uoff < sizeof(label->vl_uberblock); - uoff += (1 << zfs->ashift)) { + uoff += ASHIFT_UBERBLOCK_SIZE(zfs->ashift)) { ub = (uberblock_t *)(&label->vl_uberblock[0] + uoff); ub->ub_magic = UBERBLOCK_MAGIC; ub->ub_version = SPA_VERSION; diff --git a/usr.sbin/makefs/zfs/dsl.c b/usr.sbin/makefs/zfs/dsl.c index f7264b9d2ca7..1977521d7f92 100644 --- a/usr.sbin/makefs/zfs/dsl.c +++ b/usr.sbin/makefs/zfs/dsl.c @@ -119,7 +119,7 @@ dsl_dir_get_mountpoint(zfs_opt_t *zfs, zfs_dsl_dir_t *dir) if (nvlist_find_string(pdir->propsnv, "mountpoint", &tmp) == 0) { - easprintf(&mountpoint, "%s%s%s", tmp, + (void)easprintf(&mountpoint, "%s%s%s", tmp, tmp[strlen(tmp) - 1] == '/' ? "" : "/", origmountpoint); free(tmp); @@ -127,7 +127,7 @@ dsl_dir_get_mountpoint(zfs_opt_t *zfs, zfs_dsl_dir_t *dir) break; } - easprintf(&mountpoint, "%s/%s", pdir->name, + (void)easprintf(&mountpoint, "%s/%s", pdir->name, origmountpoint); free(origmountpoint); } @@ -175,24 +175,57 @@ dsl_dir_set_prop(zfs_opt_t *zfs, zfs_dsl_dir_t *dir, const char *key, "the root path `%s'", val, zfs->rootpath); } } - nvlist_add_string(nvl, key, val); + (void)nvlist_add_string(nvl, key, val); } else if (strcmp(key, "atime") == 0 || strcmp(key, "exec") == 0 || strcmp(key, "setuid") == 0) { if (strcmp(val, "on") == 0) - nvlist_add_uint64(nvl, key, 1); + (void)nvlist_add_uint64(nvl, key, 1); else if (strcmp(val, "off") == 0) - nvlist_add_uint64(nvl, key, 0); + (void)nvlist_add_uint64(nvl, key, 0); else errx(1, "invalid value `%s' for %s", val, key); } else if (strcmp(key, "canmount") == 0) { if (strcmp(val, "noauto") == 0) - nvlist_add_uint64(nvl, key, 2); + (void)nvlist_add_uint64(nvl, key, 2); else if (strcmp(val, "on") == 0) - nvlist_add_uint64(nvl, key, 1); + (void)nvlist_add_uint64(nvl, key, 1); else if (strcmp(val, "off") == 0) - nvlist_add_uint64(nvl, key, 0); + (void)nvlist_add_uint64(nvl, key, 0); else errx(1, "invalid value `%s' for %s", val, key); + } else if (strcmp(key, "compression") == 0) { + size_t i; + + const struct zfs_compression_algorithm { + const char *name; + enum zio_compress alg; + } compression_algorithms[] = { + { "off", ZIO_COMPRESS_OFF }, + { "on", ZIO_COMPRESS_ON }, + { "lzjb", ZIO_COMPRESS_LZJB }, + { "gzip", ZIO_COMPRESS_GZIP_6 }, + { "gzip-1", ZIO_COMPRESS_GZIP_1 }, + { "gzip-2", ZIO_COMPRESS_GZIP_2 }, + { "gzip-3", ZIO_COMPRESS_GZIP_3 }, + { "gzip-4", ZIO_COMPRESS_GZIP_4 }, + { "gzip-5", ZIO_COMPRESS_GZIP_5 }, + { "gzip-6", ZIO_COMPRESS_GZIP_6 }, + { "gzip-7", ZIO_COMPRESS_GZIP_7 }, + { "gzip-8", ZIO_COMPRESS_GZIP_8 }, + { "gzip-9", ZIO_COMPRESS_GZIP_9 }, + { "zle", ZIO_COMPRESS_ZLE }, + { "lz4", ZIO_COMPRESS_LZ4 }, + { "zstd", ZIO_COMPRESS_ZSTD }, + }; + for (i = 0; i < nitems(compression_algorithms); i++) { + if (strcmp(val, compression_algorithms[i].name) == 0) { + nvlist_add_uint64(nvl, key, + compression_algorithms[i].alg); + break; + } + } + if (i == nitems(compression_algorithms)) + errx(1, "invalid compression algorithm `%s'", val); } else { errx(1, "unknown property `%s'", key); } @@ -204,7 +237,7 @@ dsl_metadir_alloc(zfs_opt_t *zfs, const char *name) zfs_dsl_dir_t *dir; char *path; - easprintf(&path, "%s/%s", zfs->poolname, name); + (void)easprintf(&path, "%s/%s", zfs->poolname, name); dir = dsl_dir_alloc(zfs, path); free(path); return (dir); @@ -236,9 +269,6 @@ dsl_init(zfs_opt_t *zfs) zfs->rootdsldir = dsl_dir_alloc(zfs, NULL); - nvlist_add_uint64(zfs->rootdsldir->propsnv, "compression", - ZIO_COMPRESS_OFF); - zfs->rootds = dsl_dataset_alloc(zfs, zfs->rootdsldir); zfs->rootdsldir->headds = zfs->rootds; @@ -288,11 +318,15 @@ dsl_init(zfs_opt_t *zfs) } /* - * Set the root dataset's mount point if the user didn't override the - * default. + * Set the root dataset's mount point and compression strategy if the + * user didn't override the defaults. */ + if (nvpair_find(zfs->rootdsldir->propsnv, "compression") == NULL) { + (void)nvlist_add_uint64(zfs->rootdsldir->propsnv, + "compression", ZIO_COMPRESS_OFF); + } if (nvpair_find(zfs->rootdsldir->propsnv, "mountpoint") == NULL) { - nvlist_add_string(zfs->rootdsldir->propsnv, "mountpoint", + (void)nvlist_add_string(zfs->rootdsldir->propsnv, "mountpoint", zfs->rootpath); } } @@ -397,6 +431,7 @@ dsl_dir_alloc(zfs_opt_t *zfs, const char *name) STAILQ_INIT(&l); STAILQ_INSERT_HEAD(&l, zfs->rootdsldir, next); origname = dirname = nextdir = estrdup(name); + parent = NULL; for (lp = &l;; lp = &parent->children) { dirname = strsep(&nextdir, "/"); if (nextdir == NULL) diff --git a/usr.sbin/makefs/zfs/fs.c b/usr.sbin/makefs/zfs/fs.c index 073dce3ce697..75f6e30e1500 100644 --- a/usr.sbin/makefs/zfs/fs.c +++ b/usr.sbin/makefs/zfs/fs.c @@ -28,6 +28,7 @@ * SUCH DAMAGE. */ +#include <sys/param.h> #include <sys/stat.h> #include <assert.h> @@ -383,22 +384,34 @@ fs_populate_sattrs(struct fs_populate_arg *arg, const fsnode *cur, links = 1; /* .. */ objsize = 1; /* .. */ - /* - * The size of a ZPL directory is the number of entries - * (including "." and ".."), and the link count is the number of - * entries which are directories (including "." and ".."). - */ - for (fsnode *c = fsnode_isroot(cur) ? cur->next : cur->child; - c != NULL; c = c->next) { - switch (c->type) { - case S_IFDIR: - links++; - /* FALLTHROUGH */ - case S_IFREG: - case S_IFLNK: - objsize++; - break; + if ((cur->inode->flags & FI_ROOT) == 0 ) { + /* + * The size of a ZPL directory is the number of entries + * (including "." and ".."), and the link count is the + * number of entries which are directories + * (including "." and ".."). + */ + for (fsnode *c = + fsnode_isroot(cur) ? cur->next : cur->child; + c != NULL; c = c->next) { + switch (c->type) { + case S_IFDIR: + links++; + /* FALLTHROUGH */ + case S_IFREG: + case S_IFLNK: + objsize++; + break; + } } + } else { + /* + * Root directory children do belong to + * different dataset and this directory is + * empty in the current objset. + */ + links++; /* . */ + objsize++; /* . */ } /* The root directory is its own parent. */ @@ -734,7 +747,7 @@ fs_add_zpl_attr_layout(zfs_zap_t *zap, unsigned int index, assert(sizeof(layout[0]) == 2); - snprintf(ti, sizeof(ti), "%u", index); + (void)snprintf(ti, sizeof(ti), "%u", index); zap_add(zap, ti, sizeof(sa_attr_type_t), sacnt, (const uint8_t *)layout); } diff --git a/usr.sbin/makefs/zfs/objset.c b/usr.sbin/makefs/zfs/objset.c index 6be732db477a..f47953ac4339 100644 --- a/usr.sbin/makefs/zfs/objset.c +++ b/usr.sbin/makefs/zfs/objset.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/makefs/zfs/vdev.c b/usr.sbin/makefs/zfs/vdev.c index ef9e681af2da..a2423e180ca3 100644 --- a/usr.sbin/makefs/zfs/vdev.c +++ b/usr.sbin/makefs/zfs/vdev.c @@ -28,6 +28,7 @@ * SUCH DAMAGE. */ +#include <sys/param.h> #include <assert.h> #include <fcntl.h> #include <stdlib.h> @@ -199,7 +200,7 @@ vdev_label_write(zfs_opt_t *zfs, int ind, const vdev_label_t *labelp) * per sector; for example, with an ashift of 12 we end up with * 128KB/4KB=32 copies of the uberblock in the ring. */ - blksz = 1 << zfs->ashift; + blksz = ASHIFT_UBERBLOCK_SIZE(zfs->ashift); assert(sizeof(label->vl_uberblock) % blksz == 0); for (size_t roff = 0; roff < sizeof(label->vl_uberblock); roff += blksz) { diff --git a/usr.sbin/makefs/zfs/zap.c b/usr.sbin/makefs/zfs/zap.c index d01f7527adf9..316d1446cecf 100644 --- a/usr.sbin/makefs/zfs/zap.c +++ b/usr.sbin/makefs/zfs/zap.c @@ -28,11 +28,12 @@ * SUCH DAMAGE. */ -#include <sys/types.h> +#include <sys/param.h> #include <sys/endian.h> #include <assert.h> #include <stddef.h> +#include <stdint.h> #include <stdlib.h> #include <string.h> @@ -171,14 +172,14 @@ zap_add_uint64_self(zfs_zap_t *zap, uint64_t val) { char name[32]; - snprintf(name, sizeof(name), "%jx", (uintmax_t)val); + (void)snprintf(name, sizeof(name), "%jx", (uintmax_t)val); zap_add(zap, name, sizeof(uint64_t), 1, (uint8_t *)&val); } void zap_add_string(zfs_zap_t *zap, const char *name, const char *val) { - zap_add(zap, name, 1, strlen(val) + 1, val); + zap_add(zap, name, 1, strlen(val) + 1, (const uint8_t *)val); } bool @@ -201,6 +202,10 @@ zap_micro_write(zfs_opt_t *zfs, zfs_zap_t *zap) mzap_phys_t *mzap; mzap_ent_phys_t *ment; off_t bytes, loc; + uint16_t cd; + + _Static_assert(MZAP_ENT_MAX <= UINT16_MAX, + "micro ZAP collision differentiator must fit in 16 bits"); memset(zfs->filebuf, 0, sizeof(zfs->filebuf)); mzap = (mzap_phys_t *)&zfs->filebuf[0]; @@ -211,11 +216,13 @@ zap_micro_write(zfs_opt_t *zfs, zfs_zap_t *zap) bytes = sizeof(*mzap) + (zap->kvpcnt - 1) * sizeof(*ment); assert(bytes <= (off_t)MZAP_MAX_BLKSZ); + cd = 0; ment = &mzap->mz_chunk[0]; STAILQ_FOREACH(ent, &zap->kvps, next) { memcpy(&ment->mze_value, ent->valp, ent->intsz * ent->intcnt); - ment->mze_cd = 0; /* XXX-MJ */ - strlcpy(ment->mze_name, ent->name, sizeof(ment->mze_name)); + ment->mze_cd = cd++; + (void)strlcpy(ment->mze_name, ent->name, + sizeof(ment->mze_name)); ment++; } @@ -241,6 +248,7 @@ zap_fat_write_array_chunk(zap_leaf_t *l, uint16_t li, size_t sz, struct zap_leaf_array *la; assert(sz <= ZAP_MAXVALUELEN); + assert(sz > 0); for (uint16_t n, resid = sz; resid > 0; resid -= n, val += n, li++) { n = MIN(resid, ZAP_LEAF_ARRAY_BYTES); @@ -497,7 +505,8 @@ zap_fat_write(zfs_opt_t *zfs, zfs_zap_t *zap) le->le_value_intlen = ent->intsz; le->le_value_numints = ent->intcnt; le->le_hash = ent->hash; - zap_fat_write_array_chunk(&l, *lptr + 1, namelen, ent->name); + zap_fat_write_array_chunk(&l, *lptr + 1, namelen, + (uint8_t *)ent->name); zap_fat_write_array_chunk(&l, *lptr + 1 + nnamechunks, ent->intcnt * ent->intsz, ent->valp); } diff --git a/usr.sbin/mfiutil/Makefile b/usr.sbin/mfiutil/Makefile index da1c9249f8cd..49c0e688e8e2 100644 --- a/usr.sbin/mfiutil/Makefile +++ b/usr.sbin/mfiutil/Makefile @@ -4,12 +4,12 @@ LINKS= ${BINDIR}/mfiutil ${BINDIR}/mrsasutil SRCS= mfiutil.c mfi_bbu.c mfi_cmd.c mfi_config.c mfi_drive.c mfi_evt.c \ mfi_flash.c mfi_patrol.c mfi_show.c mfi_volume.c mfi_foreign.c \ mfi_properties.c -MAN8= mfiutil.8 +MAN= mfiutil.8 MLINKS= mfiutil.8 mrsasutil.8 CFLAGS.gcc+= -fno-builtin-strftime -LIBADD= util +LIBADD= sbuf util # Here be dragons .ifdef DEBUG diff --git a/usr.sbin/mfiutil/mfi_bbu.c b/usr.sbin/mfiutil/mfi_bbu.c index 9075c4d0ddd0..3e78e791dfc2 100644 --- a/usr.sbin/mfiutil/mfi_bbu.c +++ b/usr.sbin/mfiutil/mfi_bbu.c @@ -40,28 +40,23 @@ /* The autolearn period is given in seconds. */ void -mfi_autolearn_period(uint32_t period, char *buf, size_t sz) +mfi_autolearn_period(FILE *fp, uint32_t period) { unsigned int d, h; - char *tmp; d = period / (24 * 3600); h = (period % (24 * 3600)) / 3600; - tmp = buf; if (d != 0) { - tmp += snprintf(buf, sz, "%u day%s", d, d == 1 ? "" : "s"); - sz -= tmp - buf; - if (h != 0) { - tmp += snprintf(tmp, sz, ", "); - sz -= 2; - } + fprintf(fp, "%u day%s", d, d == 1 ? "" : "s"); + if (h != 0) + fprintf(fp, ", "); } if (h != 0) - snprintf(tmp, sz, "%u hour%s", h, h == 1 ? "" : "s"); + fprintf(fp, "%u hour%s", h, h == 1 ? "" : "s"); if (d == 0 && h == 0) - snprintf(tmp, sz, "less than 1 hour"); + fprintf(fp, "less than 1 hour"); } /* The time to the next relearn is given in seconds since 1/1/2000. */ @@ -76,28 +71,28 @@ mfi_next_learn_time(uint32_t next_learn_time, char *buf, size_t sz) tm.tm_year = 100; basetime = timegm(&tm); basetime += (time_t)next_learn_time; - len = snprintf(buf, sz, "%s", ctime(&basetime)); - if (len > 0) + len = strlcpy(buf, ctime(&basetime), sz); + if (len < sz) /* Get rid of the newline added by ctime(3). */ buf[len - 1] = '\0'; } void -mfi_autolearn_mode(uint8_t mode, char *buf, size_t sz) +mfi_autolearn_mode(FILE *fp, uint8_t mode) { switch (mode) { case 0: - snprintf(buf, sz, "enabled"); + fprintf(fp, "enabled"); break; case 1: - snprintf(buf, sz, "disabled"); + fprintf(fp, "disabled"); break; case 2: - snprintf(buf, sz, "warn via event"); + fprintf(fp, "warn via event"); break; default: - snprintf(buf, sz, "mode 0x%02x", mode); + fprintf(fp, "mode 0x%02x", mode); break; } } diff --git a/usr.sbin/mfiutil/mfi_drive.c b/usr.sbin/mfiutil/mfi_drive.c index e8e945c566c4..c7c5aeb02f14 100644 --- a/usr.sbin/mfiutil/mfi_drive.c +++ b/usr.sbin/mfiutil/mfi_drive.c @@ -31,6 +31,7 @@ #include <sys/types.h> #include <sys/errno.h> +#include <sys/sbuf.h> #include <ctype.h> #include <err.h> #include <fcntl.h> @@ -56,9 +57,9 @@ const char * mfi_drive_name(struct mfi_pd_info *pinfo, uint16_t device_id, uint32_t def) { struct mfi_pd_info info; + struct sbuf sb; static char buf[16]; - char *p; - int error, fd, len; + int fd; if ((def & MFI_DNAME_HONOR_OPTS) != 0 && (mfi_opts & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) != 0) @@ -89,40 +90,29 @@ mfi_drive_name(struct mfi_pd_info *pinfo, uint16_t device_id, uint32_t def) pinfo = &info; } - p = buf; - len = sizeof(buf); + sbuf_new(&sb, buf, sizeof(buf), SBUF_FIXEDLEN); if (def & MFI_DNAME_DEVICE_ID) { if (device_id == 0xffff) - error = snprintf(p, len, "MISSING"); + sbuf_printf(&sb, "MISSING"); else - error = snprintf(p, len, "%2u", device_id); - if (error >= 0) { - p += error; - len -= error; - } + sbuf_printf(&sb, "%2u", device_id); } if ((def & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) == - (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID) && len >= 2) { - *p++ = ' '; - len--; - *p = '\0'; - len--; + (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) { + sbuf_cat(&sb, " "); } if (def & MFI_DNAME_ES) { if (pinfo->encl_device_id == 0xffff) - error = snprintf(p, len, "S%u", + sbuf_printf(&sb, "S%u", pinfo->slot_number); else if (pinfo->encl_device_id == pinfo->ref.v.device_id) - error = snprintf(p, len, "E%u", + sbuf_printf(&sb, "E%u", pinfo->encl_index); else - error = snprintf(p, len, "E%u:S%u", + sbuf_printf(&sb, "E%u:S%u", pinfo->encl_index, pinfo->slot_number); - if (error >= 0) { - p += error; - len -= error; - } } + sbuf_finish(&sb); return (buf); } diff --git a/usr.sbin/mfiutil/mfi_show.c b/usr.sbin/mfiutil/mfi_show.c index bf85c8b82d69..2d413f2a46b4 100644 --- a/usr.sbin/mfiutil/mfi_show.c +++ b/usr.sbin/mfiutil/mfi_show.c @@ -218,8 +218,9 @@ show_battery(int ac, char **av __unused) printf(" Current Voltage: %d mV\n", stat.voltage); printf(" Temperature: %d C\n", stat.temperature); if (show_props) { - mfi_autolearn_period(props.auto_learn_period, buf, sizeof(buf)); - printf(" Autolearn period: %s\n", buf); + printf(" Autolearn period: "); + mfi_autolearn_period(stdout, props.auto_learn_period); + printf("\n"); if (props.auto_learn_mode != 0) snprintf(buf, sizeof(buf), "never"); else @@ -229,8 +230,9 @@ show_battery(int ac, char **av __unused) printf(" Learn delay interval: %u hour%s\n", props.learn_delay_interval, props.learn_delay_interval != 1 ? "s" : ""); - mfi_autolearn_mode(props.auto_learn_mode, buf, sizeof(buf)); - printf(" Autolearn mode: %s\n", buf); + printf(" Autolearn mode: "); + mfi_autolearn_mode(stdout, props.auto_learn_mode); + printf("\n"); if (props.bbu_mode != 0) printf(" BBU Mode: %d\n", props.bbu_mode); } diff --git a/usr.sbin/mfiutil/mfiutil.h b/usr.sbin/mfiutil/mfiutil.h index 34b423098862..86b03998163c 100644 --- a/usr.sbin/mfiutil/mfiutil.h +++ b/usr.sbin/mfiutil/mfiutil.h @@ -175,9 +175,9 @@ int mfi_bbu_get_props(int fd, struct mfi_bbu_properties *props, uint8_t *statusp); int mfi_bbu_set_props(int fd, struct mfi_bbu_properties *props, uint8_t *statusp); -void mfi_autolearn_period(uint32_t, char *, size_t); +void mfi_autolearn_period(FILE *, uint32_t); void mfi_next_learn_time(uint32_t, char *, size_t); -void mfi_autolearn_mode(uint8_t, char *, size_t); +void mfi_autolearn_mode(FILE *, uint8_t); int get_mfi_unit(const char *dev); char *get_mfi_type(const char *dev); diff --git a/usr.sbin/mount_smbfs/Makefile b/usr.sbin/mount_smbfs/Makefile index 8bd1b0f34d99..857d475103d0 100644 --- a/usr.sbin/mount_smbfs/Makefile +++ b/usr.sbin/mount_smbfs/Makefile @@ -1,15 +1,12 @@ PROG= mount_smbfs PACKAGE= smbutils -SRCS= mount_smbfs.c getmntopts.c MAN= mount_smbfs.8 -MOUNTDIR= ${SRCTOP}/sbin/mount CONTRIBDIR= ${SRCTOP}/contrib/smbfs -CFLAGS+= -DSMBFS -I${MOUNTDIR} -I${CONTRIBDIR}/include +CFLAGS+= -DSMBFS -I${CONTRIBDIR}/include -LIBADD= smb +LIBADD= smb util .PATH: ${CONTRIBDIR}/mount_smbfs -.PATH: ${MOUNTDIR} .include <bsd.prog.mk> diff --git a/usr.sbin/mountd/Makefile b/usr.sbin/mountd/Makefile index e63b03bbfe45..3760f22bff6c 100644 --- a/usr.sbin/mountd/Makefile +++ b/usr.sbin/mountd/Makefile @@ -1,13 +1,9 @@ +PACKAGE= nfs PROG= mountd -SRCS= mountd.c getmntopts.c MAN= exports.5 netgroup.5 mountd.8 -MOUNT= ${SRCTOP}/sbin/mount -CFLAGS+= -I${MOUNT} WARNS?= 2 -.PATH: ${MOUNT} - LIBADD= util .include <bsd.prog.mk> diff --git a/usr.sbin/mountd/exports.5 b/usr.sbin/mountd/exports.5 index a90d81c4db99..786411fbf6d8 100644 --- a/usr.sbin/mountd/exports.5 +++ b/usr.sbin/mountd/exports.5 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd December 16, 2024 +.Dd August 24, 2025 .Dt EXPORTS 5 .Os .Sh NAME @@ -148,6 +148,19 @@ characters. Mount points for a file system may appear on multiple lines each with different sets of hosts and export options. .Pp +Note that, for NFSv4 exporting, there must be both one or more ``V4:'' line(s) +and one or more line(s) exporting the file systems that are to be +exported to NFSv4 clients. +If there are multiple ``V4:'' lines, these lines must all specify the +same root directory path, but with different options for different +clients. +These line(s) do not export any file system, but simply define the +location of the ``root'' of the NFSv4 export subtree. +The line(s) exporting the file systems should always +specify the pathname of the root of a server file system +and must include at least one line exporting the file system +which is specified as the ``root'' by the ``V4:'' line(s). +.Pp The second component of a line specifies how the file system is to be exported to the host set. The option flags specify whether the file system diff --git a/usr.sbin/mountd/mountd.c b/usr.sbin/mountd/mountd.c index 19d76b72da10..7e2f9b6e73bd 100644 --- a/usr.sbin/mountd/mountd.c +++ b/usr.sbin/mountd/mountd.c @@ -64,6 +64,7 @@ #include <grp.h> #include <libutil.h> #include <limits.h> +#include <mntopts.h> #include <netdb.h> #include <pwd.h> #include <signal.h> @@ -73,7 +74,6 @@ #include <unistd.h> #include <vis.h> #include "pathnames.h" -#include "mntopts.h" #ifdef DEBUG #include <stdarg.h> @@ -231,6 +231,7 @@ static void free_exports(struct exportlisthead *); static void read_exportfile(int); static int compare_nmount_exportlist(struct iovec *, int, char *); static int compare_export(struct exportlist *, struct exportlist *); +static int compare_addr(struct grouplist *, struct grouplist *); static int compare_cred(struct expcred *, struct expcred *); static int compare_secflavor(int *, int *, int); static void delete_export(struct iovec *, int, struct statfs *, char *); @@ -2099,19 +2100,7 @@ get_exportlist(int passno) syslog(LOG_ERR, "NFSv4 requires at least one V4: line"); } - if (iov != NULL) { - /* Free strings allocated by strdup() in getmntopts.c */ - free(iov[0].iov_base); /* fstype */ - free(iov[2].iov_base); /* fspath */ - free(iov[4].iov_base); /* from */ - free(iov[6].iov_base); /* update */ - free(iov[8].iov_base); /* export */ - free(iov[10].iov_base); /* errmsg */ - - /* free iov, allocated by realloc() */ - free(iov); - iovlen = 0; - } + free_iovec(&iov, &iovlen); /* * If there was no public fh, clear any previous one set. @@ -2348,7 +2337,8 @@ compare_export(struct exportlist *ep, struct exportlist *oep) grp->gr_exflags == ogrp->gr_exflags && compare_cred(&grp->gr_anon, &ogrp->gr_anon) == 0 && compare_secflavor(grp->gr_secflavors, - ogrp->gr_secflavors, grp->gr_numsecflavors) == 0) + ogrp->gr_secflavors, grp->gr_numsecflavors) == 0 && + compare_addr(grp, ogrp) == 0) break; if (ogrp != NULL) ogrp->gr_flag |= GR_FND; @@ -2362,6 +2352,46 @@ compare_export(struct exportlist *ep, struct exportlist *oep) } /* + * Compare the addresses in the group. It is safe to return they are not + * the same when the are, so only return they are the same when they are + * exactly the same. + */ +static int +compare_addr(struct grouplist *grp, struct grouplist *ogrp) +{ + struct addrinfo *ai, *oai; + + if (grp->gr_type != ogrp->gr_type) + return (1); + switch (grp->gr_type) { + case GT_HOST: + ai = grp->gr_ptr.gt_addrinfo; + oai = ogrp->gr_ptr.gt_addrinfo; + for (; ai != NULL && oai != NULL; ai = ai->ai_next, + oai = oai->ai_next) { + if (sacmp(ai->ai_addr, oai->ai_addr, NULL) != 0) + return (1); + } + if (ai != NULL || oai != NULL) + return (1); + break; + case GT_NET: + /* First compare the masks and then the nets. */ + if (sacmp((struct sockaddr *)&grp->gr_ptr.gt_net.nt_mask, + (struct sockaddr *)&ogrp->gr_ptr.gt_net.nt_mask, NULL) != 0) + return (1); + if (sacmp((struct sockaddr *)&grp->gr_ptr.gt_net.nt_net, + (struct sockaddr *)&ogrp->gr_ptr.gt_net.nt_net, + (struct sockaddr *)&grp->gr_ptr.gt_net.nt_mask) != 0) + return (1); + break; + default: + return (1); + } + return (0); +} + +/* * This algorithm compares two arrays of "n" items. It returns 0 if they are * the "same" and 1 otherwise. Although suboptimal, it is always safe to * return 1, which makes compare_nmount_export() reload the exports entry. @@ -3409,18 +3439,7 @@ skip: if (cp) *cp = savedc; error_exit: - /* free strings allocated by strdup() in getmntopts.c */ - if (iov != NULL) { - free(iov[0].iov_base); /* fstype */ - free(iov[2].iov_base); /* fspath */ - free(iov[4].iov_base); /* from */ - free(iov[6].iov_base); /* update */ - free(iov[8].iov_base); /* export */ - free(iov[10].iov_base); /* errmsg */ - - /* free iov, allocated by realloc() */ - free(iov); - } + free_iovec(&iov, &iovlen); return (ret); } diff --git a/usr.sbin/newsyslog/newsyslog.conf.d/opensm.conf b/usr.sbin/newsyslog/newsyslog.conf.d/opensm.conf index 5c7d1c74fdbf..1b3570c89619 100644 --- a/usr.sbin/newsyslog/newsyslog.conf.d/opensm.conf +++ b/usr.sbin/newsyslog/newsyslog.conf.d/opensm.conf @@ -1,2 +1 @@ - /var/log/opensm.log 600 7 1000 * J /var/run/opensm.pid 30 diff --git a/usr.sbin/nfsd/Makefile b/usr.sbin/nfsd/Makefile index d7ca8c380c48..b6bd9a28e651 100644 --- a/usr.sbin/nfsd/Makefile +++ b/usr.sbin/nfsd/Makefile @@ -3,4 +3,6 @@ PACKAGE= nfs PROG= nfsd MAN= nfsd.8 nfsv4.4 stablerestart.5 pnfs.4 pnfsserver.4 +LIBADD= util + .include <bsd.prog.mk> diff --git a/usr.sbin/nfsd/Makefile.depend b/usr.sbin/nfsd/Makefile.depend index 732a025c9552..7e5c47e39608 100644 --- a/usr.sbin/nfsd/Makefile.depend +++ b/usr.sbin/nfsd/Makefile.depend @@ -9,6 +9,7 @@ DIRDEPS = \ lib/${CSU_DIR} \ lib/libc \ lib/libcompiler_rt \ + lib/libutil \ .include <dirdeps.mk> diff --git a/usr.sbin/nfsd/nfsd.8 b/usr.sbin/nfsd/nfsd.8 index 992228fba752..2e5724dbce33 100644 --- a/usr.sbin/nfsd/nfsd.8 +++ b/usr.sbin/nfsd/nfsd.8 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd February 21, 2025 +.Dd May 30, 2025 .Dt NFSD 8 .Os .Sh NAME @@ -39,6 +39,7 @@ NFS server .Op Fl h Ar bindip .Op Fl p Ar pnfs_setup .Op Fl m Ar mirror_level +.Op Fl P Ar pidfile .Op Fl V Ar virtual_hostname .Op Fl Fl maxthreads Ar max_threads .Op Fl Fl minthreads Ar min_threads @@ -84,6 +85,10 @@ options to re-register NFS if the rpcbind server is restarted. Unregister the NFS service with .Xr rpcbind 8 without creating any servers. +.It Fl P Ar pidfile +Specify alternative location of a file where main process PID will be stored. +The default location is +.Pa /var/run/nfsd.pid . .It Fl V Ar virtual_hostname Specifies a hostname to be used as a principal name, instead of the default hostname. diff --git a/usr.sbin/nfsd/nfsd.c b/usr.sbin/nfsd/nfsd.c index f1f04af192da..94c30ae6dee1 100644 --- a/usr.sbin/nfsd/nfsd.c +++ b/usr.sbin/nfsd/nfsd.c @@ -58,6 +58,7 @@ #include <err.h> #include <errno.h> +#include <libutil.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> @@ -70,6 +71,7 @@ static int debug = 0; static int nofork = 0; +#define DEFAULT_PIDFILE "/var/run/nfsd.pid" #define NFSD_STABLERESTART "/var/db/nfs-stablerestart" #define NFSD_STABLEBACKUP "/var/db/nfs-stablerestart.bak" #define MAXNFSDCNT 256 @@ -79,6 +81,7 @@ static int nofork = 0; #define NFS_VER4 4 static pid_t children[MAXNFSDCNT]; /* PIDs of children */ static pid_t masterpid; /* PID of master/parent */ +static struct pidfh *masterpidfh = NULL; /* pidfh of master/parent */ static int nfsdcnt; /* number of children */ static int nfsdcnt_set; static int minthreads; @@ -161,7 +164,8 @@ main(int argc, char **argv) size_t jailed_size, nfs_minvers_size; const char *lopt; char **bindhost = NULL; - pid_t pid; + const char *pidfile_path = DEFAULT_PIDFILE; + pid_t pid, otherpid; struct nfsd_nfsd_args nfsdargs; const char *vhostname = NULL; @@ -171,14 +175,14 @@ main(int argc, char **argv) nfsdcnt = DEFNFSDCNT; unregister = reregister = tcpflag = maxsock = 0; bindanyflag = udpflag = connect_type_cnt = bindhostc = 0; - getopt_shortopts = "ah:n:rdtuep:m:V:N"; + getopt_shortopts = "ah:n:rdtuep:m:V:NP:"; getopt_usage = "usage:\n" " nfsd [-ardtueN] [-h bindip]\n" " [-n numservers] [--minthreads #] [--maxthreads #]\n" " [-p/--pnfs dsserver0:/dsserver0-mounted-on-dir,...," "dsserverN:/dsserverN-mounted-on-dir] [-m mirrorlevel]\n" - " [-V virtual_hostname]\n"; + " [-P pidfile ] [-V virtual_hostname]\n"; while ((ch = getopt_long(argc, argv, getopt_shortopts, longopts, &longindex)) != -1) switch (ch) { @@ -234,6 +238,9 @@ main(int argc, char **argv) case 'N': nofork = 1; break; + case 'P': + pidfile_path = optarg; + break; case 0: lopt = longopts[longindex].name; if (!strcmp(lopt, "minthreads")) { @@ -415,6 +422,16 @@ main(int argc, char **argv) } exit (0); } + + if (pidfile_path != NULL) { + masterpidfh = pidfile_open(pidfile_path, 0600, &otherpid); + if (masterpidfh == NULL) { + if (errno == EEXIST) + errx(1, "daemon already running, pid: %jd.", + (intmax_t)otherpid); + warn("cannot open pid file"); + } + } if (debug == 0 && nofork == 0) { daemon(0, 0); (void)signal(SIGHUP, SIG_IGN); @@ -434,6 +451,9 @@ main(int argc, char **argv) openlog("nfsd", LOG_PID | (debug ? LOG_PERROR : 0), LOG_DAEMON); + if (masterpidfh != NULL && pidfile_write(masterpidfh) != 0) + syslog(LOG_ERR, "pidfile_write(): %m"); + /* * For V4, we open the stablerestart file and call nfssvc() * to get it loaded. This is done before the daemons do the @@ -490,6 +510,7 @@ main(int argc, char **argv) if (pid) { children[0] = pid; } else { + pidfile_close(masterpidfh); (void)signal(SIGUSR1, child_cleanup); setproctitle("server"); start_server(0, &nfsdargs, vhostname); @@ -1008,6 +1029,8 @@ nfsd_exit(int status) { killchildren(); unregistration(); + if (masterpidfh != NULL) + pidfile_remove(masterpidfh); exit(status); } diff --git a/usr.sbin/ngctl/Makefile b/usr.sbin/ngctl/Makefile index 72a5ccaa96d7..997841272376 100644 --- a/usr.sbin/ngctl/Makefile +++ b/usr.sbin/ngctl/Makefile @@ -13,4 +13,9 @@ LIBADD= netgraph CFLAGS+= -DEDITLINE LIBADD+= edit pthread +.if ${MK_JAIL} != "no" +CFLAGS+= -DJAIL +LIBADD+= jail +.endif + .include <bsd.prog.mk> diff --git a/usr.sbin/ngctl/main.c b/usr.sbin/ngctl/main.c index 7c79e67d8275..b32e4f878b6e 100644 --- a/usr.sbin/ngctl/main.c +++ b/usr.sbin/ngctl/main.c @@ -55,6 +55,10 @@ #include <histedit.h> #include <pthread.h> #endif +#ifdef JAIL +#include <sys/jail.h> +#include <jail.h> +#endif #include <netgraph.h> @@ -137,16 +141,17 @@ int csock, dsock; int main(int ac, char *av[]) { - char name[NG_NODESIZ]; - int interactive = isatty(0) && isatty(1); - FILE *fp = NULL; - int ch, rtn = 0; + char name[NG_NODESIZ]; + int interactive = isatty(0) && isatty(1); + FILE *fp = NULL; + const char *jail_name = NULL; + int ch, rtn = 0; /* Set default node name */ snprintf(name, sizeof(name), "ngctl%d", getpid()); /* Parse command line */ - while ((ch = getopt(ac, av, "df:n:")) != -1) { + while ((ch = getopt(ac, av, "df:j:n:")) != -1) { switch (ch) { case 'd': NgSetDebug(NgSetDebug(-1) + 1); @@ -157,6 +162,13 @@ main(int ac, char *av[]) else if ((fp = fopen(optarg, "r")) == NULL) err(EX_NOINPUT, "%s", optarg); break; + case 'j': +#ifdef JAIL + jail_name = optarg; +#else + errx(EX_UNAVAILABLE, "not built with jail support"); +#endif + break; case 'n': snprintf(name, sizeof(name), "%s", optarg); break; @@ -169,6 +181,22 @@ main(int ac, char *av[]) ac -= optind; av += optind; + if (jail_name != NULL) { + int jid; + + if (jail_name[0] == '\0') + Usage("invalid jail name"); + + jid = jail_getid(jail_name); + + if (jid == -1) + errx((errno == EPERM) ? EX_NOPERM : EX_NOHOST, + "%s", jail_errmsg); + if (jail_attach(jid) != 0) + errx((errno == EPERM) ? EX_NOPERM : EX_OSERR, + "cannot attach to jail"); + } + /* Create a new socket node */ if (NgMkSockNode(name, &csock, &dsock) < 0) err(EX_OSERR, "can't create node"); @@ -657,6 +685,7 @@ Usage(const char *msg) if (msg) warnx("%s", msg); fprintf(stderr, - "usage: ngctl [-d] [-f file] [-n name] [command ...]\n"); + "usage: ngctl [-j jail] [-d] [-f filename] [-n nodename] " + "[command [argument ...]]\n"); exit(EX_USAGE); } diff --git a/usr.sbin/ngctl/ngctl.8 b/usr.sbin/ngctl/ngctl.8 index 2225c836674a..63b8f58ed3df 100644 --- a/usr.sbin/ngctl/ngctl.8 +++ b/usr.sbin/ngctl/ngctl.8 @@ -31,7 +31,7 @@ .\" OF SUCH DAMAGE. .\" $Whistle: ngctl.8,v 1.6 1999/01/20 03:19:44 archie Exp $ .\" -.Dd January 19, 1999 +.Dd August 29, 2025 .Dt NGCTL 8 .Os .Sh NAME @@ -39,9 +39,11 @@ .Nd netgraph control utility .Sh SYNOPSIS .Nm +.Op Fl j Ar jail .Op Fl d .Op Fl f Ar filename .Op Fl n Ar nodename +.Op Ar command Op Ns Ar argument ... .Op Ar command ... .Sh DESCRIPTION The @@ -73,12 +75,31 @@ form if the originating node supports conversion. .Pp The options are as follows: .Bl -tag -width indent -.It Fl f Ar nodeinfo +.It Fl f Ar filename Read commands from the named file. A single dash represents the standard input. Blank lines and lines starting with a .Dq # are ignored. +Note that when the +.Fl j Ar jail +option is specified, the file will be opened before attaching to the jail and +then be processed inside the jail. +.It Fl j Ar jail +Perform the actions inside the +.Ar jail . +.Pp +.Nm +will first attach to the +.Ar jail +(by jail id or jail name) before performing the effects. +.Pp +This allows netgraph nodes of +.Ar jail +to be created, modified, and destroyed even if the +.Nm +binary is not available in +.Ar jail . .It Fl n Ar nodename Assign .Em nodename diff --git a/usr.sbin/nvmfd/Makefile b/usr.sbin/nvmfd/Makefile deleted file mode 100644 index dc3dcc5e3a5c..000000000000 --- a/usr.sbin/nvmfd/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -.include <src.opts.mk> -.PATH: ${SRCTOP}/sys/libkern - -PACKAGE=nvme-tools -PROG= nvmfd -SRCS= nvmfd.c controller.c ctl.c devices.c discovery.c gsb_crc32.c io.c -CFLAGS+= -I${SRCTOP}/lib/libnvmf -MAN= nvmfd.8 -LIBADD+= nvmf pthread util nv - -.include <bsd.prog.mk> - -CFLAGS.ctl.c= -I${SRCTOP}/sys -CWARNFLAGS.gsb_crc32.c= -Wno-cast-align diff --git a/usr.sbin/nvmfd/Makefile.depend b/usr.sbin/nvmfd/Makefile.depend deleted file mode 100644 index c4c6125c7a7c..000000000000 --- a/usr.sbin/nvmfd/Makefile.depend +++ /dev/null @@ -1,20 +0,0 @@ -# Autogenerated - do NOT edit! - -DIRDEPS = \ - include \ - include/arpa \ - include/xlocale \ - lib/${CSU_DIR} \ - lib/libc \ - lib/libcompiler_rt \ - lib/libnv \ - lib/libnvmf \ - lib/libthr \ - lib/libutil \ - - -.include <dirdeps.mk> - -.if ${DEP_RELDIR} == ${_DEP_RELDIR} -# local dependencies - needed for -jN in clean tree -.endif diff --git a/usr.sbin/nvmfd/controller.c b/usr.sbin/nvmfd/controller.c deleted file mode 100644 index e9435bce69da..000000000000 --- a/usr.sbin/nvmfd/controller.c +++ /dev/null @@ -1,244 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-2-Clause - * - * Copyright (c) 2023-2024 Chelsio Communications, Inc. - * Written by: John Baldwin <jhb@FreeBSD.org> - */ - -#include <err.h> -#include <errno.h> -#include <libnvmf.h> -#include <stdlib.h> - -#include "internal.h" - -struct controller { - struct nvmf_qpair *qp; - - uint64_t cap; - uint32_t vs; - uint32_t cc; - uint32_t csts; - - bool shutdown; - - struct nvme_controller_data cdata; -}; - -static bool -update_cc(struct controller *c, uint32_t new_cc) -{ - uint32_t changes; - - if (c->shutdown) - return (false); - if (!nvmf_validate_cc(c->qp, c->cap, c->cc, new_cc)) - return (false); - - changes = c->cc ^ new_cc; - c->cc = new_cc; - - /* Handle shutdown requests. */ - if (NVMEV(NVME_CC_REG_SHN, changes) != 0 && - NVMEV(NVME_CC_REG_SHN, new_cc) != 0) { - c->csts &= ~NVMEM(NVME_CSTS_REG_SHST); - c->csts |= NVMEF(NVME_CSTS_REG_SHST, NVME_SHST_COMPLETE); - c->shutdown = true; - } - - if (NVMEV(NVME_CC_REG_EN, changes) != 0) { - if (NVMEV(NVME_CC_REG_EN, new_cc) == 0) { - /* Controller reset. */ - c->csts = 0; - c->shutdown = true; - } else - c->csts |= NVMEF(NVME_CSTS_REG_RDY, 1); - } - return (true); -} - -static void -handle_property_get(const struct controller *c, const struct nvmf_capsule *nc, - const struct nvmf_fabric_prop_get_cmd *pget) -{ - struct nvmf_fabric_prop_get_rsp rsp; - - nvmf_init_cqe(&rsp, nc, 0); - - switch (le32toh(pget->ofst)) { - case NVMF_PROP_CAP: - if (pget->attrib.size != NVMF_PROP_SIZE_8) - goto error; - rsp.value.u64 = htole64(c->cap); - break; - case NVMF_PROP_VS: - if (pget->attrib.size != NVMF_PROP_SIZE_4) - goto error; - rsp.value.u32.low = htole32(c->vs); - break; - case NVMF_PROP_CC: - if (pget->attrib.size != NVMF_PROP_SIZE_4) - goto error; - rsp.value.u32.low = htole32(c->cc); - break; - case NVMF_PROP_CSTS: - if (pget->attrib.size != NVMF_PROP_SIZE_4) - goto error; - rsp.value.u32.low = htole32(c->csts); - break; - default: - goto error; - } - - nvmf_send_response(nc, &rsp); - return; -error: - nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); -} - -static void -handle_property_set(struct controller *c, const struct nvmf_capsule *nc, - const struct nvmf_fabric_prop_set_cmd *pset) -{ - switch (le32toh(pset->ofst)) { - case NVMF_PROP_CC: - if (pset->attrib.size != NVMF_PROP_SIZE_4) - goto error; - if (!update_cc(c, le32toh(pset->value.u32.low))) - goto error; - break; - default: - goto error; - } - - nvmf_send_success(nc); - return; -error: - nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); -} - -static void -handle_fabrics_command(struct controller *c, - const struct nvmf_capsule *nc, const struct nvmf_fabric_cmd *fc) -{ - switch (fc->fctype) { - case NVMF_FABRIC_COMMAND_PROPERTY_GET: - handle_property_get(c, nc, - (const struct nvmf_fabric_prop_get_cmd *)fc); - break; - case NVMF_FABRIC_COMMAND_PROPERTY_SET: - handle_property_set(c, nc, - (const struct nvmf_fabric_prop_set_cmd *)fc); - break; - case NVMF_FABRIC_COMMAND_CONNECT: - warnx("CONNECT command on connected queue"); - nvmf_send_generic_error(nc, NVME_SC_COMMAND_SEQUENCE_ERROR); - break; - case NVMF_FABRIC_COMMAND_DISCONNECT: - warnx("DISCONNECT command on admin queue"); - nvmf_send_error(nc, NVME_SCT_COMMAND_SPECIFIC, - NVMF_FABRIC_SC_INVALID_QUEUE_TYPE); - break; - default: - warnx("Unsupported fabrics command %#x", fc->fctype); - nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE); - break; - } -} - -static void -handle_identify_command(const struct controller *c, - const struct nvmf_capsule *nc, const struct nvme_command *cmd) -{ - uint8_t cns; - - cns = le32toh(cmd->cdw10) & 0xFF; - switch (cns) { - case 1: - break; - default: - warnx("Unsupported CNS %#x for IDENTIFY", cns); - goto error; - } - - nvmf_send_controller_data(nc, &c->cdata, sizeof(c->cdata)); - return; -error: - nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); -} - -void -controller_handle_admin_commands(struct controller *c, handle_command *cb, - void *cb_arg) -{ - struct nvmf_qpair *qp = c->qp; - const struct nvme_command *cmd; - struct nvmf_capsule *nc; - int error; - - for (;;) { - error = nvmf_controller_receive_capsule(qp, &nc); - if (error != 0) { - if (error != ECONNRESET) - warnc(error, "Failed to read command capsule"); - break; - } - - cmd = nvmf_capsule_sqe(nc); - - /* - * Only permit Fabrics commands while a controller is - * disabled. - */ - if (NVMEV(NVME_CC_REG_EN, c->cc) == 0 && - cmd->opc != NVME_OPC_FABRICS_COMMANDS) { - warnx("Unsupported admin opcode %#x while disabled\n", - cmd->opc); - nvmf_send_generic_error(nc, - NVME_SC_COMMAND_SEQUENCE_ERROR); - nvmf_free_capsule(nc); - continue; - } - - if (cb(nc, cmd, cb_arg)) { - nvmf_free_capsule(nc); - continue; - } - - switch (cmd->opc) { - case NVME_OPC_FABRICS_COMMANDS: - handle_fabrics_command(c, nc, - (const struct nvmf_fabric_cmd *)cmd); - break; - case NVME_OPC_IDENTIFY: - handle_identify_command(c, nc, cmd); - break; - default: - warnx("Unsupported admin opcode %#x", cmd->opc); - nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE); - break; - } - nvmf_free_capsule(nc); - } -} - -struct controller * -init_controller(struct nvmf_qpair *qp, - const struct nvme_controller_data *cdata) -{ - struct controller *c; - - c = calloc(1, sizeof(*c)); - c->qp = qp; - c->cap = nvmf_controller_cap(c->qp); - c->vs = cdata->ver; - c->cdata = *cdata; - - return (c); -} - -void -free_controller(struct controller *c) -{ - free(c); -} diff --git a/usr.sbin/nvmfd/ctl.c b/usr.sbin/nvmfd/ctl.c deleted file mode 100644 index 73e90e1411bd..000000000000 --- a/usr.sbin/nvmfd/ctl.c +++ /dev/null @@ -1,137 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-2-Clause - * - * Copyright (c) 2023 Chelsio Communications, Inc. - * Written by: John Baldwin <jhb@FreeBSD.org> - */ - -#include <sys/param.h> -#include <sys/linker.h> -#include <sys/nv.h> -#include <sys/time.h> -#include <err.h> -#include <errno.h> -#include <fcntl.h> -#include <libnvmf.h> -#include <string.h> - -#include <cam/ctl/ctl.h> -#include <cam/ctl/ctl_io.h> -#include <cam/ctl/ctl_ioctl.h> - -#include "internal.h" - -static int ctl_fd = -1; -static int ctl_port; - -static void -open_ctl(void) -{ - if (ctl_fd > 0) - return; - - ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR); - if (ctl_fd == -1 && errno == ENOENT) { - if (kldload("ctl") == -1) - err(1, "Failed to load ctl.ko"); - ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR); - } - if (ctl_fd == -1) - err(1, "Failed to open %s", CTL_DEFAULT_DEV); -} - -void -init_ctl_port(const char *subnqn, const struct nvmf_association_params *params) -{ - char result_buf[256]; - struct ctl_port_entry entry; - struct ctl_req req; - nvlist_t *nvl; - - open_ctl(); - - nvl = nvlist_create(0); - - nvlist_add_string(nvl, "subnqn", subnqn); - - /* XXX: Hardcoded in discovery.c */ - nvlist_add_stringf(nvl, "portid", "%u", 1); - - nvlist_add_stringf(nvl, "max_io_qsize", "%u", params->max_io_qsize); - - memset(&req, 0, sizeof(req)); - strlcpy(req.driver, "nvmf", sizeof(req.driver)); - req.reqtype = CTL_REQ_CREATE; - req.args = nvlist_pack(nvl, &req.args_len); - if (req.args == NULL) - errx(1, "Failed to pack nvlist for CTL_PORT/CTL_REQ_CREATE"); - req.result = result_buf; - req.result_len = sizeof(result_buf); - if (ioctl(ctl_fd, CTL_PORT_REQ, &req) != 0) - err(1, "ioctl(CTL_PORT/CTL_REQ_CREATE)"); - if (req.status == CTL_LUN_ERROR) - errx(1, "Failed to create CTL port: %s", req.error_str); - if (req.status != CTL_LUN_OK) - errx(1, "Failed to create CTL port: %d", req.status); - - nvlist_destroy(nvl); - nvl = nvlist_unpack(result_buf, req.result_len, 0); - if (nvl == NULL) - errx(1, "Failed to unpack nvlist from CTL_PORT/CTL_REQ_CREATE"); - - ctl_port = nvlist_get_number(nvl, "port_id"); - nvlist_destroy(nvl); - - memset(&entry, 0, sizeof(entry)); - entry.targ_port = ctl_port; - if (ioctl(ctl_fd, CTL_ENABLE_PORT, &entry) != 0) - errx(1, "ioctl(CTL_ENABLE_PORT)"); -} - -void -shutdown_ctl_port(const char *subnqn) -{ - struct ctl_req req; - nvlist_t *nvl; - - open_ctl(); - - nvl = nvlist_create(0); - - nvlist_add_string(nvl, "subnqn", subnqn); - - memset(&req, 0, sizeof(req)); - strlcpy(req.driver, "nvmf", sizeof(req.driver)); - req.reqtype = CTL_REQ_REMOVE; - req.args = nvlist_pack(nvl, &req.args_len); - if (req.args == NULL) - errx(1, "Failed to pack nvlist for CTL_PORT/CTL_REQ_REMOVE"); - if (ioctl(ctl_fd, CTL_PORT_REQ, &req) != 0) - err(1, "ioctl(CTL_PORT/CTL_REQ_REMOVE)"); - if (req.status == CTL_LUN_ERROR) - errx(1, "Failed to remove CTL port: %s", req.error_str); - if (req.status != CTL_LUN_OK) - errx(1, "Failed to remove CTL port: %d", req.status); - - nvlist_destroy(nvl); -} - -void -ctl_handoff_qpair(struct nvmf_qpair *qp, - const struct nvmf_fabric_connect_cmd *cmd, - const struct nvmf_fabric_connect_data *data) -{ - struct ctl_nvmf req; - int error; - - memset(&req, 0, sizeof(req)); - req.type = CTL_NVMF_HANDOFF; - error = nvmf_handoff_controller_qpair(qp, cmd, data, &req.data.handoff); - if (error != 0) { - warnc(error, "Failed to prepare qpair for handoff"); - return; - } - - if (ioctl(ctl_fd, CTL_NVMF, &req) != 0) - warn("ioctl(CTL_NVMF/CTL_NVMF_HANDOFF)"); -} diff --git a/usr.sbin/nvmfd/devices.c b/usr.sbin/nvmfd/devices.c deleted file mode 100644 index fafc1077f207..000000000000 --- a/usr.sbin/nvmfd/devices.c +++ /dev/null @@ -1,386 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-2-Clause - * - * Copyright (c) 2023-2024 Chelsio Communications, Inc. - * Written by: John Baldwin <jhb@FreeBSD.org> - */ - -#include <sys/disk.h> -#include <sys/gsb_crc32.h> -#include <sys/ioctl.h> -#include <sys/stat.h> -#include <net/ieee_oui.h> -#include <err.h> -#include <errno.h> -#include <fcntl.h> -#include <libnvmf.h> -#include <libutil.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "internal.h" - -#define RAMDISK_PREFIX "ramdisk:" - -struct backing_device { - enum { RAMDISK, FILE, CDEV } type; - union { - int fd; /* FILE, CDEV */ - void *mem; /* RAMDISK */ - }; - u_int sector_size; - uint64_t nlbas; - uint64_t eui64; -}; - -static struct backing_device *devices; -static u_int ndevices; - -static uint64_t -generate_eui64(uint32_t low) -{ - return (OUI_FREEBSD_NVME_LOW << 16 | low); -} - -static uint32_t -crc32(const void *buf, size_t len) -{ - return (calculate_crc32c(0xffffffff, buf, len) ^ 0xffffffff); -} - -static void -init_ramdisk(const char *config, struct backing_device *dev) -{ - static uint32_t ramdisk_idx = 1; - uint64_t num; - - dev->type = RAMDISK; - dev->sector_size = 512; - if (expand_number(config, &num)) - errx(1, "Invalid ramdisk specification: %s", config); - if ((num % dev->sector_size) != 0) - errx(1, "Invalid ramdisk size %ju", (uintmax_t)num); - dev->mem = calloc(num, 1); - dev->nlbas = num / dev->sector_size; - dev->eui64 = generate_eui64('M' << 24 | ramdisk_idx++); -} - -static void -init_filedevice(const char *config, int fd, struct stat *sb, - struct backing_device *dev) -{ - dev->type = FILE; - dev->fd = fd; - dev->sector_size = 512; - if ((sb->st_size % dev->sector_size) != 0) - errx(1, "File size is not a multiple of 512: %s", config); - dev->nlbas = sb->st_size / dev->sector_size; - dev->eui64 = generate_eui64('F' << 24 | - (crc32(config, strlen(config)) & 0xffffff)); -} - -static void -init_chardevice(const char *config, int fd, struct backing_device *dev) -{ - off_t len; - - dev->type = CDEV; - dev->fd = fd; - if (ioctl(fd, DIOCGSECTORSIZE, &dev->sector_size) != 0) - err(1, "Failed to fetch sector size for %s", config); - if (ioctl(fd, DIOCGMEDIASIZE, &len) != 0) - err(1, "Failed to fetch sector size for %s", config); - dev->nlbas = len / dev->sector_size; - dev->eui64 = generate_eui64('C' << 24 | - (crc32(config, strlen(config)) & 0xffffff)); -} - -static void -init_device(const char *config, struct backing_device *dev) -{ - struct stat sb; - int fd; - - /* Check for a RAM disk. */ - if (strncmp(RAMDISK_PREFIX, config, strlen(RAMDISK_PREFIX)) == 0) { - init_ramdisk(config + strlen(RAMDISK_PREFIX), dev); - return; - } - - fd = open(config, O_RDWR); - if (fd == -1) - err(1, "Failed to open %s", config); - if (fstat(fd, &sb) == -1) - err(1, "fstat"); - switch (sb.st_mode & S_IFMT) { - case S_IFCHR: - init_chardevice(config, fd, dev); - break; - case S_IFREG: - init_filedevice(config, fd, &sb, dev); - break; - default: - errx(1, "Invalid file type for %s", config); - } -} - -void -register_devices(int ac, char **av) -{ - ndevices = ac; - devices = calloc(ndevices, sizeof(*devices)); - - for (int i = 0; i < ac; i++) - init_device(av[i], &devices[i]); -} - -u_int -device_count(void) -{ - return (ndevices); -} - -static struct backing_device * -lookup_device(uint32_t nsid) -{ - if (nsid == 0 || nsid > ndevices) - return (NULL); - return (&devices[nsid - 1]); -} - -void -device_active_nslist(uint32_t nsid, struct nvme_ns_list *nslist) -{ - u_int count; - - memset(nslist, 0, sizeof(*nslist)); - count = 0; - nsid++; - while (nsid <= ndevices) { - nslist->ns[count] = htole32(nsid); - count++; - if (count == nitems(nslist->ns)) - break; - nsid++; - } -} - -bool -device_identification_descriptor(uint32_t nsid, void *buf) -{ - struct backing_device *dev; - char *p; - - dev = lookup_device(nsid); - if (dev == NULL) - return (false); - - memset(buf, 0, 4096); - - p = buf; - - /* EUI64 */ - *p++ = 1; - *p++ = 8; - p += 2; - be64enc(p, dev->eui64); - return (true); -} - -bool -device_namespace_data(uint32_t nsid, struct nvme_namespace_data *nsdata) -{ - struct backing_device *dev; - - dev = lookup_device(nsid); - if (dev == NULL) - return (false); - - memset(nsdata, 0, sizeof(*nsdata)); - nsdata->nsze = htole64(dev->nlbas); - nsdata->ncap = nsdata->nsze; - nsdata->nuse = nsdata->ncap; - nsdata->nlbaf = 1 - 1; - nsdata->flbas = NVMEF(NVME_NS_DATA_FLBAS_FORMAT, 0); - nsdata->lbaf[0] = NVMEF(NVME_NS_DATA_LBAF_LBADS, - ffs(dev->sector_size) - 1); - - be64enc(nsdata->eui64, dev->eui64); - return (true); -} - -static bool -read_buffer(int fd, void *buf, size_t len, off_t offset) -{ - ssize_t nread; - char *dst; - - dst = buf; - while (len > 0) { - nread = pread(fd, dst, len, offset); - if (nread == -1 && errno == EINTR) - continue; - if (nread <= 0) - return (false); - dst += nread; - len -= nread; - offset += nread; - } - return (true); -} - -void -device_read(uint32_t nsid, uint64_t lba, u_int nlb, - const struct nvmf_capsule *nc) -{ - struct backing_device *dev; - char *p, *src; - off_t off; - size_t len; - - dev = lookup_device(nsid); - if (dev == NULL) { - nvmf_send_generic_error(nc, - NVME_SC_INVALID_NAMESPACE_OR_FORMAT); - return; - } - - if (lba + nlb < lba || lba + nlb > dev->nlbas) { - nvmf_send_generic_error(nc, NVME_SC_LBA_OUT_OF_RANGE); - return; - } - - off = lba * dev->sector_size; - len = nlb * dev->sector_size; - if (nvmf_capsule_data_len(nc) != len) { - nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); - return; - } - - if (dev->type == RAMDISK) { - p = NULL; - src = (char *)dev->mem + off; - } else { - p = malloc(len); - if (!read_buffer(dev->fd, p, len, off)) { - free(p); - nvmf_send_generic_error(nc, - NVME_SC_INTERNAL_DEVICE_ERROR); - return; - } - src = p; - } - - nvmf_send_controller_data(nc, src, len); - free(p); -} - -static bool -write_buffer(int fd, const void *buf, size_t len, off_t offset) -{ - ssize_t nwritten; - const char *src; - - src = buf; - while (len > 0) { - nwritten = pwrite(fd, src, len, offset); - if (nwritten == -1 && errno == EINTR) - continue; - if (nwritten <= 0) - return (false); - src += nwritten; - len -= nwritten; - offset += nwritten; - } - return (true); -} - -void -device_write(uint32_t nsid, uint64_t lba, u_int nlb, - const struct nvmf_capsule *nc) -{ - struct backing_device *dev; - char *p, *dst; - off_t off; - size_t len; - int error; - - dev = lookup_device(nsid); - if (dev == NULL) { - nvmf_send_generic_error(nc, - NVME_SC_INVALID_NAMESPACE_OR_FORMAT); - return; - } - - if (lba + nlb < lba || lba + nlb > dev->nlbas) { - nvmf_send_generic_error(nc, NVME_SC_LBA_OUT_OF_RANGE); - return; - } - - off = lba * dev->sector_size; - len = nlb * dev->sector_size; - if (nvmf_capsule_data_len(nc) != len) { - nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); - return; - } - - if (dev->type == RAMDISK) { - p = NULL; - dst = (char *)dev->mem + off; - } else { - p = malloc(len); - dst = p; - } - - error = nvmf_receive_controller_data(nc, 0, dst, len); - if (error != 0) { - nvmf_send_generic_error(nc, NVME_SC_TRANSIENT_TRANSPORT_ERROR); - free(p); - return; - } - - if (dev->type != RAMDISK) { - if (!write_buffer(dev->fd, p, len, off)) { - free(p); - nvmf_send_generic_error(nc, - NVME_SC_INTERNAL_DEVICE_ERROR); - return; - } - } - free(p); - nvmf_send_success(nc); -} - -void -device_flush(uint32_t nsid, const struct nvmf_capsule *nc) -{ - struct backing_device *dev; - - dev = lookup_device(nsid); - if (dev == NULL) { - nvmf_send_generic_error(nc, - NVME_SC_INVALID_NAMESPACE_OR_FORMAT); - return; - } - - switch (dev->type) { - case RAMDISK: - break; - case FILE: - if (fdatasync(dev->fd) == -1) { - nvmf_send_error(nc, NVME_SCT_MEDIA_ERROR, - NVME_SC_WRITE_FAULTS); - return; - } - break; - case CDEV: - if (ioctl(dev->fd, DIOCGFLUSH) == -1) { - nvmf_send_error(nc, NVME_SCT_MEDIA_ERROR, - NVME_SC_WRITE_FAULTS); - return; - } - } - - nvmf_send_success(nc); -} diff --git a/usr.sbin/nvmfd/discovery.c b/usr.sbin/nvmfd/discovery.c deleted file mode 100644 index 2cfe56731d7c..000000000000 --- a/usr.sbin/nvmfd/discovery.c +++ /dev/null @@ -1,342 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-2-Clause - * - * Copyright (c) 2023-2024 Chelsio Communications, Inc. - * Written by: John Baldwin <jhb@FreeBSD.org> - */ - -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <assert.h> -#include <err.h> -#include <libnvmf.h> -#include <pthread.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "internal.h" - -struct io_controller_data { - struct nvme_discovery_log_entry entry; - bool wildcard; -}; - -struct discovery_controller { - struct nvme_discovery_log *discovery_log; - size_t discovery_log_len; - int s; -}; - -struct discovery_thread_arg { - struct controller *c; - struct nvmf_qpair *qp; - int s; -}; - -static struct io_controller_data *io_controllers; -static struct nvmf_association *discovery_na; -static u_int num_io_controllers; - -static bool -init_discovery_log_entry(struct nvme_discovery_log_entry *entry, int s, - const char *subnqn) -{ - struct sockaddr_storage ss; - socklen_t len; - bool wildcard; - - len = sizeof(ss); - if (getsockname(s, (struct sockaddr *)&ss, &len) == -1) - err(1, "getsockname"); - - memset(entry, 0, sizeof(*entry)); - entry->trtype = NVMF_TRTYPE_TCP; - switch (ss.ss_family) { - case AF_INET: - { - struct sockaddr_in *sin; - - sin = (struct sockaddr_in *)&ss; - entry->adrfam = NVMF_ADRFAM_IPV4; - snprintf(entry->trsvcid, sizeof(entry->trsvcid), "%u", - htons(sin->sin_port)); - if (inet_ntop(AF_INET, &sin->sin_addr, entry->traddr, - sizeof(entry->traddr)) == NULL) - err(1, "inet_ntop"); - wildcard = (sin->sin_addr.s_addr == htonl(INADDR_ANY)); - break; - } - case AF_INET6: - { - struct sockaddr_in6 *sin6; - - sin6 = (struct sockaddr_in6 *)&ss; - entry->adrfam = NVMF_ADRFAM_IPV6; - snprintf(entry->trsvcid, sizeof(entry->trsvcid), "%u", - htons(sin6->sin6_port)); - if (inet_ntop(AF_INET6, &sin6->sin6_addr, entry->traddr, - sizeof(entry->traddr)) == NULL) - err(1, "inet_ntop"); - wildcard = (memcmp(&sin6->sin6_addr, &in6addr_any, - sizeof(in6addr_any)) == 0); - break; - } - default: - errx(1, "Unsupported address family %u", ss.ss_family); - } - entry->subtype = NVMF_SUBTYPE_NVME; - if (flow_control_disable) - entry->treq |= (1 << 2); - entry->portid = htole16(1); - entry->cntlid = htole16(NVMF_CNTLID_DYNAMIC); - entry->aqsz = NVME_MAX_ADMIN_ENTRIES; - strlcpy(entry->subnqn, subnqn, sizeof(entry->subnqn)); - return (wildcard); -} - -void -init_discovery(void) -{ - struct nvmf_association_params aparams; - - memset(&aparams, 0, sizeof(aparams)); - aparams.sq_flow_control = false; - aparams.dynamic_controller_model = true; - aparams.max_admin_qsize = NVME_MAX_ADMIN_ENTRIES; - aparams.tcp.pda = 0; - aparams.tcp.header_digests = header_digests; - aparams.tcp.data_digests = data_digests; - aparams.tcp.maxh2cdata = maxh2cdata; - discovery_na = nvmf_allocate_association(NVMF_TRTYPE_TCP, true, - &aparams); - if (discovery_na == NULL) - err(1, "Failed to create discovery association"); -} - -void -discovery_add_io_controller(int s, const char *subnqn) -{ - struct io_controller_data *icd; - - io_controllers = reallocf(io_controllers, (num_io_controllers + 1) * - sizeof(*io_controllers)); - - icd = &io_controllers[num_io_controllers]; - num_io_controllers++; - - icd->wildcard = init_discovery_log_entry(&icd->entry, s, subnqn); -} - -static void -build_discovery_log_page(struct discovery_controller *dc) -{ - struct sockaddr_storage ss; - socklen_t len; - char traddr[256]; - u_int i, nentries; - uint8_t adrfam; - - if (dc->discovery_log != NULL) - return; - - len = sizeof(ss); - if (getsockname(dc->s, (struct sockaddr *)&ss, &len) == -1) { - warn("build_discovery_log_page: getsockname"); - return; - } - - memset(traddr, 0, sizeof(traddr)); - switch (ss.ss_family) { - case AF_INET: - { - struct sockaddr_in *sin; - - sin = (struct sockaddr_in *)&ss; - adrfam = NVMF_ADRFAM_IPV4; - if (inet_ntop(AF_INET, &sin->sin_addr, traddr, - sizeof(traddr)) == NULL) { - warn("build_discovery_log_page: inet_ntop"); - return; - } - break; - } - case AF_INET6: - { - struct sockaddr_in6 *sin6; - - sin6 = (struct sockaddr_in6 *)&ss; - adrfam = NVMF_ADRFAM_IPV6; - if (inet_ntop(AF_INET6, &sin6->sin6_addr, traddr, - sizeof(traddr)) == NULL) { - warn("build_discovery_log_page: inet_ntop"); - return; - } - break; - } - default: - assert(false); - } - - nentries = 0; - for (i = 0; i < num_io_controllers; i++) { - if (io_controllers[i].wildcard && - io_controllers[i].entry.adrfam != adrfam) - continue; - nentries++; - } - - dc->discovery_log_len = sizeof(*dc->discovery_log) + - nentries * sizeof(struct nvme_discovery_log_entry); - dc->discovery_log = calloc(dc->discovery_log_len, 1); - dc->discovery_log->numrec = nentries; - dc->discovery_log->recfmt = 0; - nentries = 0; - for (i = 0; i < num_io_controllers; i++) { - if (io_controllers[i].wildcard && - io_controllers[i].entry.adrfam != adrfam) - continue; - - dc->discovery_log->entries[nentries] = io_controllers[i].entry; - if (io_controllers[i].wildcard) - memcpy(dc->discovery_log->entries[nentries].traddr, - traddr, sizeof(traddr)); - } -} - -static void -handle_get_log_page_command(const struct nvmf_capsule *nc, - const struct nvme_command *cmd, struct discovery_controller *dc) -{ - uint64_t offset; - uint32_t length; - - switch (nvmf_get_log_page_id(cmd)) { - case NVME_LOG_DISCOVERY: - break; - default: - warnx("Unsupported log page %u for discovery controller", - nvmf_get_log_page_id(cmd)); - goto error; - } - - build_discovery_log_page(dc); - - offset = nvmf_get_log_page_offset(cmd); - if (offset >= dc->discovery_log_len) - goto error; - - length = nvmf_get_log_page_length(cmd); - if (length > dc->discovery_log_len - offset) - length = dc->discovery_log_len - offset; - - nvmf_send_controller_data(nc, (char *)dc->discovery_log + offset, - length); - return; -error: - nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); -} - -static bool -discovery_command(const struct nvmf_capsule *nc, const struct nvme_command *cmd, - void *arg) -{ - struct discovery_controller *dc = arg; - - switch (cmd->opc) { - case NVME_OPC_GET_LOG_PAGE: - handle_get_log_page_command(nc, cmd, dc); - return (true); - default: - return (false); - } -} - -static void * -discovery_thread(void *arg) -{ - struct discovery_thread_arg *dta = arg; - struct discovery_controller dc; - - pthread_detach(pthread_self()); - - memset(&dc, 0, sizeof(dc)); - dc.s = dta->s; - - controller_handle_admin_commands(dta->c, discovery_command, &dc); - - free(dc.discovery_log); - free_controller(dta->c); - - nvmf_free_qpair(dta->qp); - - close(dta->s); - free(dta); - return (NULL); -} - -void -handle_discovery_socket(int s) -{ - struct nvmf_fabric_connect_data data; - struct nvme_controller_data cdata; - struct nvmf_qpair_params qparams; - struct discovery_thread_arg *dta; - struct nvmf_capsule *nc; - struct nvmf_qpair *qp; - pthread_t thr; - int error; - - memset(&qparams, 0, sizeof(qparams)); - qparams.tcp.fd = s; - - nc = NULL; - qp = nvmf_accept(discovery_na, &qparams, &nc, &data); - if (qp == NULL) { - warnx("Failed to create discovery qpair: %s", - nvmf_association_error(discovery_na)); - goto error; - } - - if (strcmp(data.subnqn, NVMF_DISCOVERY_NQN) != 0) { - warn("Discovery qpair with invalid SubNQN: %.*s", - (int)sizeof(data.subnqn), data.subnqn); - nvmf_connect_invalid_parameters(nc, true, - offsetof(struct nvmf_fabric_connect_data, subnqn)); - goto error; - } - - /* Just use a controller ID of 1 for all discovery controllers. */ - error = nvmf_finish_accept(nc, 1); - if (error != 0) { - warnc(error, "Failed to send CONNECT reponse"); - goto error; - } - - nvmf_init_discovery_controller_data(qp, &cdata); - - dta = malloc(sizeof(*dta)); - dta->qp = qp; - dta->s = s; - dta->c = init_controller(qp, &cdata); - - error = pthread_create(&thr, NULL, discovery_thread, dta); - if (error != 0) { - warnc(error, "Failed to create discovery thread"); - free_controller(dta->c); - free(dta); - goto error; - } - - nvmf_free_capsule(nc); - return; - -error: - if (nc != NULL) - nvmf_free_capsule(nc); - if (qp != NULL) - nvmf_free_qpair(qp); - close(s); -} diff --git a/usr.sbin/nvmfd/internal.h b/usr.sbin/nvmfd/internal.h deleted file mode 100644 index f70dc78881c6..000000000000 --- a/usr.sbin/nvmfd/internal.h +++ /dev/null @@ -1,66 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-2-Clause - * - * Copyright (c) 2023-2024 Chelsio Communications, Inc. - * Written by: John Baldwin <jhb@FreeBSD.org> - */ - -#ifndef __INTERNAL_H__ -#define __INTERNAL_H__ - -#include <stdbool.h> - -struct controller; -struct nvme_command; -struct nvme_controller_data; -struct nvme_ns_list; -struct nvmf_capsule; -struct nvmf_qpair; - -typedef bool handle_command(const struct nvmf_capsule *, - const struct nvme_command *, void *); - -extern bool data_digests; -extern bool header_digests; -extern bool flow_control_disable; -extern bool kernel_io; -extern uint32_t maxh2cdata; - -/* controller.c */ -void controller_handle_admin_commands(struct controller *c, - handle_command *cb, void *cb_arg); -struct controller *init_controller(struct nvmf_qpair *qp, - const struct nvme_controller_data *cdata); -void free_controller(struct controller *c); - -/* discovery.c */ -void init_discovery(void); -void handle_discovery_socket(int s); -void discovery_add_io_controller(int s, const char *subnqn); - -/* io.c */ -void init_io(const char *subnqn); -void handle_io_socket(int s); -void shutdown_io(void); - -/* devices.c */ -void register_devices(int ac, char **av); -u_int device_count(void); -void device_active_nslist(uint32_t nsid, struct nvme_ns_list *nslist); -bool device_identification_descriptor(uint32_t nsid, void *buf); -bool device_namespace_data(uint32_t nsid, struct nvme_namespace_data *nsdata); -void device_read(uint32_t nsid, uint64_t lba, u_int nlb, - const struct nvmf_capsule *nc); -void device_write(uint32_t nsid, uint64_t lba, u_int nlb, - const struct nvmf_capsule *nc); -void device_flush(uint32_t nsid, const struct nvmf_capsule *nc); - -/* ctl.c */ -void init_ctl_port(const char *subnqn, - const struct nvmf_association_params *params); -void ctl_handoff_qpair(struct nvmf_qpair *qp, - const struct nvmf_fabric_connect_cmd *cmd, - const struct nvmf_fabric_connect_data *data); -void shutdown_ctl_port(const char *subnqn); - -#endif /* !__INTERNAL_H__ */ diff --git a/usr.sbin/nvmfd/io.c b/usr.sbin/nvmfd/io.c deleted file mode 100644 index 4407360257a2..000000000000 --- a/usr.sbin/nvmfd/io.c +++ /dev/null @@ -1,676 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-2-Clause - * - * Copyright (c) 2023-2024 Chelsio Communications, Inc. - * Written by: John Baldwin <jhb@FreeBSD.org> - */ - -#include <sys/sysctl.h> -#include <err.h> -#include <errno.h> -#include <libnvmf.h> -#include <pthread.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "internal.h" - -struct io_controller { - struct controller *c; - - u_int num_io_queues; - u_int active_io_queues; - struct nvmf_qpair **io_qpairs; - int *io_sockets; - - struct nvme_firmware_page fp; - struct nvme_health_information_page hip; - uint16_t partial_dur; - uint16_t partial_duw; - - uint16_t cntlid; - char hostid[16]; - char hostnqn[NVME_NQN_FIELD_SIZE]; -}; - -static struct nvmf_association *io_na; -static pthread_cond_t io_cond; -static pthread_mutex_t io_na_mutex; -static struct io_controller *io_controller; -static const char *nqn; -static char serial[NVME_SERIAL_NUMBER_LENGTH]; - -void -init_io(const char *subnqn) -{ - struct nvmf_association_params aparams; - u_long hostid; - size_t len; - - memset(&aparams, 0, sizeof(aparams)); - aparams.sq_flow_control = !flow_control_disable; - aparams.dynamic_controller_model = true; - aparams.max_admin_qsize = NVME_MAX_ADMIN_ENTRIES; - aparams.max_io_qsize = NVMF_MAX_IO_ENTRIES; - aparams.tcp.pda = 0; - aparams.tcp.header_digests = header_digests; - aparams.tcp.data_digests = data_digests; - aparams.tcp.maxh2cdata = maxh2cdata; - io_na = nvmf_allocate_association(NVMF_TRTYPE_TCP, true, - &aparams); - if (io_na == NULL) - err(1, "Failed to create I/O controller association"); - - nqn = subnqn; - - /* Generate a serial number from the kern.hostid node. */ - len = sizeof(hostid); - if (sysctlbyname("kern.hostid", &hostid, &len, NULL, 0) == -1) - err(1, "sysctl: kern.hostid"); - - nvmf_controller_serial(serial, sizeof(serial), hostid); - - pthread_cond_init(&io_cond, NULL); - pthread_mutex_init(&io_na_mutex, NULL); - - if (kernel_io) - init_ctl_port(subnqn, &aparams); -} - -void -shutdown_io(void) -{ - if (kernel_io) - shutdown_ctl_port(nqn); -} - -static void -handle_get_log_page(struct io_controller *ioc, const struct nvmf_capsule *nc, - const struct nvme_command *cmd) -{ - uint64_t offset; - uint32_t numd; - size_t len; - uint8_t lid; - - lid = le32toh(cmd->cdw10) & 0xff; - numd = le32toh(cmd->cdw10) >> 16 | le32toh(cmd->cdw11) << 16; - offset = le32toh(cmd->cdw12) | (uint64_t)le32toh(cmd->cdw13) << 32; - - if (offset % 3 != 0) - goto error; - - len = (numd + 1) * 4; - - switch (lid) { - case NVME_LOG_ERROR: - { - void *buf; - - if (len % sizeof(struct nvme_error_information_entry) != 0) - goto error; - - buf = calloc(1, len); - nvmf_send_controller_data(nc, buf, len); - free(buf); - return; - } - case NVME_LOG_HEALTH_INFORMATION: - if (len != sizeof(ioc->hip)) - goto error; - - nvmf_send_controller_data(nc, &ioc->hip, sizeof(ioc->hip)); - return; - case NVME_LOG_FIRMWARE_SLOT: - if (len != sizeof(ioc->fp)) - goto error; - - nvmf_send_controller_data(nc, &ioc->fp, sizeof(ioc->fp)); - return; - default: - warnx("Unsupported page %#x for GET_LOG_PAGE\n", lid); - goto error; - } - -error: - nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); -} - -static bool -handle_io_identify_command(const struct nvmf_capsule *nc, - const struct nvme_command *cmd) -{ - struct nvme_namespace_data nsdata; - struct nvme_ns_list nslist; - uint32_t nsid; - uint8_t cns; - - cns = le32toh(cmd->cdw10) & 0xFF; - switch (cns) { - case 0: /* Namespace data. */ - if (!device_namespace_data(le32toh(cmd->nsid), &nsdata)) { - nvmf_send_generic_error(nc, - NVME_SC_INVALID_NAMESPACE_OR_FORMAT); - return (true); - } - - nvmf_send_controller_data(nc, &nsdata, sizeof(nsdata)); - return (true); - case 2: /* Active namespace list. */ - nsid = le32toh(cmd->nsid); - if (nsid >= 0xfffffffe) { - nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); - return (true); - } - - device_active_nslist(nsid, &nslist); - nvmf_send_controller_data(nc, &nslist, sizeof(nslist)); - return (true); - case 3: /* Namespace Identification Descriptor list. */ - if (!device_identification_descriptor(le32toh(cmd->nsid), - &nsdata)) { - nvmf_send_generic_error(nc, - NVME_SC_INVALID_NAMESPACE_OR_FORMAT); - return (true); - } - - nvmf_send_controller_data(nc, &nsdata, sizeof(nsdata)); - return (true); - default: - return (false); - } -} - -static void -handle_set_features(struct io_controller *ioc, const struct nvmf_capsule *nc, - const struct nvme_command *cmd) -{ - struct nvme_completion cqe; - uint8_t fid; - - fid = NVMEV(NVME_FEAT_SET_FID, le32toh(cmd->cdw10)); - switch (fid) { - case NVME_FEAT_NUMBER_OF_QUEUES: - { - uint32_t num_queues; - - if (ioc->num_io_queues != 0) { - nvmf_send_generic_error(nc, - NVME_SC_COMMAND_SEQUENCE_ERROR); - return; - } - - num_queues = le32toh(cmd->cdw11) & 0xffff; - - /* 5.12.1.7: 65535 is invalid. */ - if (num_queues == 65535) - goto error; - - /* Fabrics requires the same number of SQs and CQs. */ - if (le32toh(cmd->cdw11) >> 16 != num_queues) - goto error; - - /* Convert to 1's based */ - num_queues++; - - /* Lock to synchronize with handle_io_qpair. */ - pthread_mutex_lock(&io_na_mutex); - ioc->num_io_queues = num_queues; - ioc->io_qpairs = calloc(num_queues, sizeof(*ioc->io_qpairs)); - ioc->io_sockets = calloc(num_queues, sizeof(*ioc->io_sockets)); - pthread_mutex_unlock(&io_na_mutex); - - nvmf_init_cqe(&cqe, nc, 0); - cqe.cdw0 = cmd->cdw11; - nvmf_send_response(nc, &cqe); - return; - } - case NVME_FEAT_ASYNC_EVENT_CONFIGURATION: - { - uint32_t aer_mask; - - aer_mask = le32toh(cmd->cdw11); - - /* Check for any reserved or unimplemented feature bits. */ - if ((aer_mask & 0xffffc000) != 0) - goto error; - - /* No AERs are generated by this daemon. */ - nvmf_send_success(nc); - return; - } - default: - warnx("Unsupported feature ID %u for SET_FEATURES", fid); - goto error; - } - -error: - nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); -} - -static bool -admin_command(const struct nvmf_capsule *nc, const struct nvme_command *cmd, - void *arg) -{ - struct io_controller *ioc = arg; - - switch (cmd->opc) { - case NVME_OPC_GET_LOG_PAGE: - handle_get_log_page(ioc, nc, cmd); - return (true); - case NVME_OPC_IDENTIFY: - return (handle_io_identify_command(nc, cmd)); - case NVME_OPC_SET_FEATURES: - handle_set_features(ioc, nc, cmd); - return (true); - case NVME_OPC_ASYNC_EVENT_REQUEST: - /* Ignore and never complete. */ - return (true); - case NVME_OPC_KEEP_ALIVE: - nvmf_send_success(nc); - return (true); - default: - return (false); - } -} - -static void -handle_admin_qpair(struct io_controller *ioc) -{ - pthread_setname_np(pthread_self(), "admin queue"); - - controller_handle_admin_commands(ioc->c, admin_command, ioc); - - pthread_mutex_lock(&io_na_mutex); - for (u_int i = 0; i < ioc->num_io_queues; i++) { - if (ioc->io_qpairs[i] == NULL || ioc->io_sockets[i] == -1) - continue; - close(ioc->io_sockets[i]); - ioc->io_sockets[i] = -1; - } - - /* Wait for I/O threads to notice. */ - while (ioc->active_io_queues > 0) - pthread_cond_wait(&io_cond, &io_na_mutex); - - io_controller = NULL; - pthread_mutex_unlock(&io_na_mutex); - - free_controller(ioc->c); - - free(ioc); -} - -static bool -handle_io_fabrics_command(const struct nvmf_capsule *nc, - const struct nvmf_fabric_cmd *fc) -{ - switch (fc->fctype) { - case NVMF_FABRIC_COMMAND_CONNECT: - warnx("CONNECT command on connected queue"); - nvmf_send_generic_error(nc, NVME_SC_COMMAND_SEQUENCE_ERROR); - break; - case NVMF_FABRIC_COMMAND_DISCONNECT: - { - const struct nvmf_fabric_disconnect_cmd *dis = - (const struct nvmf_fabric_disconnect_cmd *)fc; - if (dis->recfmt != htole16(0)) { - nvmf_send_error(nc, NVME_SCT_COMMAND_SPECIFIC, - NVMF_FABRIC_SC_INCOMPATIBLE_FORMAT); - break; - } - nvmf_send_success(nc); - return (true); - } - default: - warnx("Unsupported fabrics command %#x", fc->fctype); - nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE); - break; - } - - return (false); -} - -static void -hip_add(uint64_t pair[2], uint64_t addend) -{ - uint64_t old, new; - - old = le64toh(pair[0]); - new = old + addend; - pair[0] = htole64(new); - if (new < old) - pair[1] += htole64(1); -} - -static uint64_t -cmd_lba(const struct nvme_command *cmd) -{ - return ((uint64_t)le32toh(cmd->cdw11) << 32 | le32toh(cmd->cdw10)); -} - -static u_int -cmd_nlb(const struct nvme_command *cmd) -{ - return ((le32toh(cmd->cdw12) & 0xffff) + 1); -} - -static void -handle_read(struct io_controller *ioc, const struct nvmf_capsule *nc, - const struct nvme_command *cmd) -{ - size_t len; - - len = nvmf_capsule_data_len(nc); - device_read(le32toh(cmd->nsid), cmd_lba(cmd), cmd_nlb(cmd), nc); - hip_add(ioc->hip.host_read_commands, 1); - - len /= 512; - len += ioc->partial_dur; - if (len > 1000) - hip_add(ioc->hip.data_units_read, len / 1000); - ioc->partial_dur = len % 1000; -} - -static void -handle_write(struct io_controller *ioc, const struct nvmf_capsule *nc, - const struct nvme_command *cmd) -{ - size_t len; - - len = nvmf_capsule_data_len(nc); - device_write(le32toh(cmd->nsid), cmd_lba(cmd), cmd_nlb(cmd), nc); - hip_add(ioc->hip.host_write_commands, 1); - - len /= 512; - len += ioc->partial_duw; - if (len > 1000) - hip_add(ioc->hip.data_units_written, len / 1000); - ioc->partial_duw = len % 1000; -} - -static void -handle_flush(const struct nvmf_capsule *nc, const struct nvme_command *cmd) -{ - device_flush(le32toh(cmd->nsid), nc); -} - -static bool -handle_io_commands(struct io_controller *ioc, struct nvmf_qpair *qp) -{ - const struct nvme_command *cmd; - struct nvmf_capsule *nc; - int error; - bool disconnect; - - disconnect = false; - - while (!disconnect) { - error = nvmf_controller_receive_capsule(qp, &nc); - if (error != 0) { - if (error != ECONNRESET) - warnc(error, "Failed to read command capsule"); - break; - } - - cmd = nvmf_capsule_sqe(nc); - - switch (cmd->opc) { - case NVME_OPC_FLUSH: - if (cmd->nsid == htole32(0xffffffff)) { - nvmf_send_generic_error(nc, - NVME_SC_INVALID_NAMESPACE_OR_FORMAT); - break; - } - handle_flush(nc, cmd); - break; - case NVME_OPC_WRITE: - handle_write(ioc, nc, cmd); - break; - case NVME_OPC_READ: - handle_read(ioc, nc, cmd); - break; - case NVME_OPC_FABRICS_COMMANDS: - disconnect = handle_io_fabrics_command(nc, - (const struct nvmf_fabric_cmd *)cmd); - break; - default: - warnx("Unsupported NVM opcode %#x", cmd->opc); - nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE); - break; - } - nvmf_free_capsule(nc); - } - - return (disconnect); -} - -static void -handle_io_qpair(struct io_controller *ioc, struct nvmf_qpair *qp, int qid) -{ - char name[64]; - bool disconnect; - - snprintf(name, sizeof(name), "I/O queue %d", qid); - pthread_setname_np(pthread_self(), name); - - disconnect = handle_io_commands(ioc, qp); - - pthread_mutex_lock(&io_na_mutex); - if (disconnect) - ioc->io_qpairs[qid - 1] = NULL; - if (ioc->io_sockets[qid - 1] != -1) { - close(ioc->io_sockets[qid - 1]); - ioc->io_sockets[qid - 1] = -1; - } - ioc->active_io_queues--; - if (ioc->active_io_queues == 0) - pthread_cond_broadcast(&io_cond); - pthread_mutex_unlock(&io_na_mutex); -} - -static void -connect_admin_qpair(int s, struct nvmf_qpair *qp, struct nvmf_capsule *nc, - const struct nvmf_fabric_connect_data *data) -{ - struct nvme_controller_data cdata; - struct io_controller *ioc; - int error; - - /* Can only have one active I/O controller at a time. */ - pthread_mutex_lock(&io_na_mutex); - if (io_controller != NULL) { - pthread_mutex_unlock(&io_na_mutex); - nvmf_send_error(nc, NVME_SCT_COMMAND_SPECIFIC, - NVMF_FABRIC_SC_CONTROLLER_BUSY); - goto error; - } - - error = nvmf_finish_accept(nc, 2); - if (error != 0) { - pthread_mutex_unlock(&io_na_mutex); - warnc(error, "Failed to send CONNECT response"); - goto error; - } - - ioc = calloc(1, sizeof(*ioc)); - ioc->cntlid = 2; - memcpy(ioc->hostid, data->hostid, sizeof(ioc->hostid)); - memcpy(ioc->hostnqn, data->hostnqn, sizeof(ioc->hostnqn)); - - nvmf_init_io_controller_data(qp, serial, nqn, device_count(), - NVMF_IOCCSZ, &cdata); - - ioc->fp.afi = NVMEF(NVME_FIRMWARE_PAGE_AFI_SLOT, 1); - memcpy(ioc->fp.revision[0], cdata.fr, sizeof(cdata.fr)); - - ioc->hip.power_cycles[0] = 1; - - ioc->c = init_controller(qp, &cdata); - - io_controller = ioc; - pthread_mutex_unlock(&io_na_mutex); - - nvmf_free_capsule(nc); - - handle_admin_qpair(ioc); - close(s); - return; - -error: - nvmf_free_capsule(nc); - close(s); -} - -static void -connect_io_qpair(int s, struct nvmf_qpair *qp, struct nvmf_capsule *nc, - const struct nvmf_fabric_connect_data *data, uint16_t qid) -{ - struct io_controller *ioc; - int error; - - pthread_mutex_lock(&io_na_mutex); - if (io_controller == NULL) { - pthread_mutex_unlock(&io_na_mutex); - warnx("Attempt to create I/O qpair without admin qpair"); - nvmf_send_generic_error(nc, NVME_SC_COMMAND_SEQUENCE_ERROR); - goto error; - } - - if (memcmp(io_controller->hostid, data->hostid, - sizeof(data->hostid)) != 0) { - pthread_mutex_unlock(&io_na_mutex); - warnx("hostid mismatch for I/O qpair CONNECT"); - nvmf_connect_invalid_parameters(nc, true, - offsetof(struct nvmf_fabric_connect_data, hostid)); - goto error; - } - if (le16toh(data->cntlid) != io_controller->cntlid) { - pthread_mutex_unlock(&io_na_mutex); - warnx("cntlid mismatch for I/O qpair CONNECT"); - nvmf_connect_invalid_parameters(nc, true, - offsetof(struct nvmf_fabric_connect_data, cntlid)); - goto error; - } - if (memcmp(io_controller->hostnqn, data->hostnqn, - sizeof(data->hostid)) != 0) { - pthread_mutex_unlock(&io_na_mutex); - warnx("host NQN mismatch for I/O qpair CONNECT"); - nvmf_connect_invalid_parameters(nc, true, - offsetof(struct nvmf_fabric_connect_data, hostnqn)); - goto error; - } - - if (io_controller->num_io_queues == 0) { - pthread_mutex_unlock(&io_na_mutex); - warnx("Attempt to create I/O qpair without enabled queues"); - nvmf_send_generic_error(nc, NVME_SC_COMMAND_SEQUENCE_ERROR); - goto error; - } - if (qid > io_controller->num_io_queues) { - pthread_mutex_unlock(&io_na_mutex); - warnx("Attempt to create invalid I/O qpair %u", qid); - nvmf_connect_invalid_parameters(nc, false, - offsetof(struct nvmf_fabric_connect_cmd, qid)); - goto error; - } - if (io_controller->io_qpairs[qid - 1] != NULL) { - pthread_mutex_unlock(&io_na_mutex); - warnx("Attempt to re-create I/O qpair %u", qid); - nvmf_send_generic_error(nc, NVME_SC_COMMAND_SEQUENCE_ERROR); - goto error; - } - - error = nvmf_finish_accept(nc, io_controller->cntlid); - if (error != 0) { - pthread_mutex_unlock(&io_na_mutex); - warnc(error, "Failed to send CONNECT response"); - goto error; - } - - ioc = io_controller; - ioc->active_io_queues++; - ioc->io_qpairs[qid - 1] = qp; - ioc->io_sockets[qid - 1] = s; - pthread_mutex_unlock(&io_na_mutex); - - nvmf_free_capsule(nc); - - handle_io_qpair(ioc, qp, qid); - return; - -error: - nvmf_free_capsule(nc); - close(s); -} - -static void * -io_socket_thread(void *arg) -{ - struct nvmf_fabric_connect_data data; - struct nvmf_qpair_params qparams; - const struct nvmf_fabric_connect_cmd *cmd; - struct nvmf_capsule *nc; - struct nvmf_qpair *qp; - int s; - - pthread_detach(pthread_self()); - - s = (intptr_t)arg; - memset(&qparams, 0, sizeof(qparams)); - qparams.tcp.fd = s; - - nc = NULL; - qp = nvmf_accept(io_na, &qparams, &nc, &data); - if (qp == NULL) { - warnx("Failed to create I/O qpair: %s", - nvmf_association_error(io_na)); - goto error; - } - - if (kernel_io) { - ctl_handoff_qpair(qp, nvmf_capsule_sqe(nc), &data); - goto error; - } - - if (strcmp(data.subnqn, nqn) != 0) { - warn("I/O qpair with invalid SubNQN: %.*s", - (int)sizeof(data.subnqn), data.subnqn); - nvmf_connect_invalid_parameters(nc, true, - offsetof(struct nvmf_fabric_connect_data, subnqn)); - goto error; - } - - /* Is this an admin or I/O queue pair? */ - cmd = nvmf_capsule_sqe(nc); - if (cmd->qid == 0) - connect_admin_qpair(s, qp, nc, &data); - else - connect_io_qpair(s, qp, nc, &data, le16toh(cmd->qid)); - nvmf_free_qpair(qp); - return (NULL); - -error: - if (nc != NULL) - nvmf_free_capsule(nc); - if (qp != NULL) - nvmf_free_qpair(qp); - close(s); - return (NULL); -} - -void -handle_io_socket(int s) -{ - pthread_t thr; - int error; - - error = pthread_create(&thr, NULL, io_socket_thread, - (void *)(uintptr_t)s); - if (error != 0) { - warnc(error, "Failed to create I/O qpair thread"); - close(s); - } -} diff --git a/usr.sbin/nvmfd/nvmfd.8 b/usr.sbin/nvmfd/nvmfd.8 deleted file mode 100644 index 1076583c417c..000000000000 --- a/usr.sbin/nvmfd/nvmfd.8 +++ /dev/null @@ -1,131 +0,0 @@ -.\" -.\" SPDX-License-Identifier: BSD-2-Clause -.\" -.\" Copyright (c) 2024 Chelsio Communications, Inc. -.\" -.Dd July 25, 2024 -.Dt NVMFD 8 -.Os -.Sh NAME -.Nm nvmfd -.Nd "NVMeoF controller daemon" -.Sh SYNOPSIS -.Nm -.Fl K -.Op Fl dFGg -.Op Fl H Ar MAXH2CDATA -.Op Fl P Ar port -.Op Fl p Ar port -.Op Fl t Ar transport -.Op Fl n Ar subnqn -.Nm -.Op Fl dFGg -.Op Fl H Ar MAXH2CDATA -.Op Fl P Ar port -.Op Fl p Ar port -.Op Fl t Ar transport -.Op Fl n Ar subnqn -.Ar device -.Op Ar device ... -.Sh DESCRIPTION -.Nm -accepts incoming NVMeoF connections for both I/O and discovery controllers. -.Nm -can either implement a single dynamic I/O controller in user mode or hand -off incoming I/O controller connections to -.Xr nvmft 4 . -A dynamic discovery controller service is always provided in user mode. -.Pp -The following options are available: -.Bl -tag -width "-t transport" -.It Fl F -Permit remote hosts to disable SQ flow control. -.It Fl G -Permit remote hosts to enable PDU data digests for the TCP transport. -.It Fl g -Permit remote hosts to enable PDU header digests for the TCP transport. -.It Fl H -Set the MAXH2CDATA value advertised to the remote host for the TCP transport. -This value is in bytes and determines the maximum data payload size for -data PDUs sent by the remote host. -The value must be at least 4096 and defaults to 256KiB. -.It Fl K -Enable kernel mode which hands off incoming I/O controller connections to -.Xr nvmft 4 . -.It Fl P Ar port -Use -.Ar port -as the listen TCP port for the discovery controller service. -The default value is 8009. -.It Fl d -Enable debug mode. -The daemon sends any errors to standard output and does not place -itself in the background. -.It Fl p Ar port -Use -.Ar port -as the listen TCP port for the I/O controller service. -By default an unused ephemeral port will be chosen. -.It Fl n Ar subnqn -The Subsystem NVMe Qualified Name for the I/O controller. -If an explicit NQN is not given, a default value is generated from the -current host's UUID obtained from the -.Vt kern.hostuuid -sysctl. -.It Fl t Ar transport -The transport type to use. -The default transport is -.Dq tcp . -.It Ar device -When implementing a user mode I/O controller, -one or more -.Ar device -arguments must be specified. -Each -.Ar device -describes the backing store for a namespace exported to remote hosts. -Devices can be specified using one of the following syntaxes: -.Bl -tag -width "ramdisk:size" -.It Pa pathname -File or disk device -.It ramdisk : Ns Ar size -Allocate a memory disk with the given -.Ar size . -.Ar size -may use any of the suffixes supported by -.Xr expand_number 3 . -.El -.El -.Sh FILES -.Bl -tag -width "/var/run/nvmfd.pid" -compact -.It Pa /var/run/nvmfd.pid -The default location of the -.Nm -PID file. -.El -.Sh EXIT STATUS -.Ex -std -.Sh SEE ALSO -.Xr ctl 4 , -.Xr nvmft 4 , -.Xr ctladm 8 , -.Xr ctld 8 -.Sh HISTORY -The -.Nm -module first appeared in -.Fx 15.0 . -.Sh AUTHORS -The -.Nm -subsystem was developed by -.An John Baldwin Aq Mt jhb@FreeBSD.org -under sponsorship from Chelsio Communications, Inc. -.Sh BUGS -The discovery controller and kernel mode functionality of -.Nm -should be merged into -.Xr ctld 8 . -.Pp -Additional parameters such as -queue sizes should be configurable. diff --git a/usr.sbin/nvmfd/nvmfd.c b/usr.sbin/nvmfd/nvmfd.c deleted file mode 100644 index df6f400b40e5..000000000000 --- a/usr.sbin/nvmfd/nvmfd.c +++ /dev/null @@ -1,271 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-2-Clause - * - * Copyright (c) 2023-2024 Chelsio Communications, Inc. - * Written by: John Baldwin <jhb@FreeBSD.org> - */ - -#include <sys/param.h> -#include <sys/event.h> -#include <sys/linker.h> -#include <sys/module.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <assert.h> -#include <err.h> -#include <errno.h> -#include <libnvmf.h> -#include <libutil.h> -#include <netdb.h> -#include <signal.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "internal.h" - -bool data_digests = false; -bool header_digests = false; -bool flow_control_disable = false; -bool kernel_io = false; -uint32_t maxh2cdata = 256 * 1024; - -static const char *subnqn; -static volatile bool quit = false; - -static void -usage(void) -{ - fprintf(stderr, "nvmfd -K [-dFGg] [-H MAXH2CDATA] [-P port] [-p port] [-t transport] [-n subnqn]\n" - "nvmfd [-dFGg] [-H MAXH2CDATA] [-P port] [-p port] [-t transport] [-n subnqn]\n" - "\tdevice [device [...]]\n" - "\n" - "Devices use one of the following syntaxes:\n" - "\tpathame - file or disk device\n" - "\tramdisk:size - memory disk of given size\n"); - exit(1); -} - -static void -handle_sig(int sig __unused) -{ - quit = true; -} - -static void -register_listen_socket(int kqfd, int s, void *udata) -{ - struct kevent kev; - - if (listen(s, -1) != 0) - err(1, "listen"); - - EV_SET(&kev, s, EVFILT_READ, EV_ADD, 0, 0, udata); - if (kevent(kqfd, &kev, 1, NULL, 0, NULL) == -1) - err(1, "kevent: failed to add listen socket"); -} - -static void -create_passive_sockets(int kqfd, const char *port, bool discovery) -{ - struct addrinfo hints, *ai, *list; - bool created; - int error, s; - - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_PASSIVE; - hints.ai_family = AF_UNSPEC; - hints.ai_protocol = IPPROTO_TCP; - error = getaddrinfo(NULL, port, &hints, &list); - if (error != 0) - errx(1, "%s", gai_strerror(error)); - created = false; - - for (ai = list; ai != NULL; ai = ai->ai_next) { - s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if (s == -1) - continue; - - if (bind(s, ai->ai_addr, ai->ai_addrlen) != 0) { - close(s); - continue; - } - - if (discovery) { - register_listen_socket(kqfd, s, (void *)1); - } else { - register_listen_socket(kqfd, s, (void *)2); - discovery_add_io_controller(s, subnqn); - } - created = true; - } - - freeaddrinfo(list); - if (!created) - err(1, "Failed to create any listen sockets"); -} - -static void -handle_connections(int kqfd) -{ - struct kevent ev; - int s; - - signal(SIGHUP, handle_sig); - signal(SIGINT, handle_sig); - signal(SIGQUIT, handle_sig); - signal(SIGTERM, handle_sig); - - while (!quit) { - if (kevent(kqfd, NULL, 0, &ev, 1, NULL) == -1) { - if (errno == EINTR) - continue; - err(1, "kevent"); - } - - assert(ev.filter == EVFILT_READ); - - s = accept(ev.ident, NULL, NULL); - if (s == -1) { - warn("accept"); - continue; - } - - switch ((uintptr_t)ev.udata) { - case 1: - handle_discovery_socket(s); - break; - case 2: - handle_io_socket(s); - break; - default: - __builtin_unreachable(); - } - } -} - -int -main(int ac, char **av) -{ - struct pidfh *pfh; - const char *dport, *ioport, *transport; - pid_t pid; - uint64_t value; - int ch, error, kqfd; - bool daemonize; - static char nqn[NVMF_NQN_MAX_LEN]; - - /* 7.4.9.3 Default port for discovery */ - dport = "8009"; - - pfh = NULL; - daemonize = true; - ioport = "0"; - subnqn = NULL; - transport = "tcp"; - while ((ch = getopt(ac, av, "dFgGH:Kn:P:p:t:")) != -1) { - switch (ch) { - case 'd': - daemonize = false; - break; - case 'F': - flow_control_disable = true; - break; - case 'G': - data_digests = true; - break; - case 'g': - header_digests = true; - break; - case 'H': - if (expand_number(optarg, &value) != 0) - errx(1, "Invalid MAXH2CDATA value %s", optarg); - if (value < 4096 || value > UINT32_MAX || - value % 4 != 0) - errx(1, "Invalid MAXH2CDATA value %s", optarg); - maxh2cdata = value; - break; - case 'K': - kernel_io = true; - break; - case 'n': - subnqn = optarg; - break; - case 'P': - dport = optarg; - break; - case 'p': - ioport = optarg; - break; - case 't': - transport = optarg; - break; - default: - usage(); - } - } - - av += optind; - ac -= optind; - - if (kernel_io) { - if (ac > 0) - usage(); - if (modfind("nvmft") == -1 && kldload("nvmft") == -1) - warn("couldn't load nvmft"); - } else { - if (ac < 1) - usage(); - } - - if (strcasecmp(transport, "tcp") == 0) { - } else - errx(1, "Invalid transport %s", transport); - - if (subnqn == NULL) { - error = nvmf_nqn_from_hostuuid(nqn); - if (error != 0) - errc(1, error, "Failed to generate NQN"); - subnqn = nqn; - } - - if (!kernel_io) - register_devices(ac, av); - - init_discovery(); - init_io(subnqn); - - if (daemonize) { - pfh = pidfile_open(NULL, 0600, &pid); - if (pfh == NULL) { - if (errno == EEXIST) - errx(1, "Daemon already running, pid: %jd", - (intmax_t)pid); - warn("Cannot open or create pidfile"); - } - - if (daemon(0, 0) != 0) { - pidfile_remove(pfh); - err(1, "Failed to fork into the background"); - } - - pidfile_write(pfh); - } - - kqfd = kqueue(); - if (kqfd == -1) { - pidfile_remove(pfh); - err(1, "kqueue"); - } - - create_passive_sockets(kqfd, dport, true); - create_passive_sockets(kqfd, ioport, false); - - handle_connections(kqfd); - shutdown_io(); - if (pfh != NULL) - pidfile_remove(pfh); - return (0); -} diff --git a/usr.sbin/pciconf/pciconf.8 b/usr.sbin/pciconf/pciconf.8 index 4e46d502887a..6c67e9e50df6 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 June 14, 2018 +.Dd May 19, 2025 .Dt PCICONF 8 .Os .Sh NAME @@ -39,7 +39,7 @@ .Nm .Fl w Oo Fl b | h Oc Ar device addr value .Nm -.Fl D Oo Fl b | h | x Oc Ar device addr Op start Ns Op : Ns Ar count +.Fl D Oo Fl b | h | x Oc Ar device bar Op Ar start Op Ns Ar count .Sh DESCRIPTION The .Nm diff --git a/usr.sbin/pciconf/pciconf.c b/usr.sbin/pciconf/pciconf.c index 83ea50efb183..4d3941131858 100644 --- a/usr.sbin/pciconf/pciconf.c +++ b/usr.sbin/pciconf/pciconf.c @@ -110,7 +110,7 @@ main(int argc, char **argv) bars = bridge = caps = errors = verbose = vpd= 0; width = 4; - while ((c = getopt(argc, argv, "aBbcDehlrwVv")) != -1) { + while ((c = getopt(argc, argv, "aBbcDehlrwVvx")) != -1) { switch(c) { case 'a': attachedmode = 1; @@ -1153,7 +1153,7 @@ dump_bar(const char *name, const char *reg, const char *bar_start, case 1: db = (uint8_t *)(uintptr_t)((uintptr_t)pbm.pbm_map_base + pbm.pbm_bar_off + start * width); - for (a = 0; a < count; a += width, db++) { + for (a = 0; a < count; a++, db++) { res = fwrite(db, width, 1, stdout); if (res != 1) { errx(1, "error writing to stdout"); @@ -1164,7 +1164,7 @@ dump_bar(const char *name, const char *reg, const char *bar_start, case 2: dh = (uint16_t *)(uintptr_t)((uintptr_t)pbm.pbm_map_base + pbm.pbm_bar_off + start * width); - for (a = 0; a < count; a += width, dh++) { + for (a = 0; a < count; a++, dh++) { res = fwrite(dh, width, 1, stdout); if (res != 1) { errx(1, "error writing to stdout"); @@ -1175,7 +1175,7 @@ dump_bar(const char *name, const char *reg, const char *bar_start, case 4: dd = (uint32_t *)(uintptr_t)((uintptr_t)pbm.pbm_map_base + pbm.pbm_bar_off + start * width); - for (a = 0; a < count; a += width, dd++) { + for (a = 0; a < count; a ++, dd++) { res = fwrite(dd, width, 1, stdout); if (res != 1) { errx(1, "error writing to stdout"); @@ -1186,7 +1186,7 @@ dump_bar(const char *name, const char *reg, const char *bar_start, case 8: dx = (uint64_t *)(uintptr_t)((uintptr_t)pbm.pbm_map_base + pbm.pbm_bar_off + start * width); - for (a = 0; a < count; a += width, dx++) { + for (a = 0; a < count; a++, dx++) { res = fwrite(dx, width, 1, stdout); if (res != 1) { errx(1, "error writing to stdout"); diff --git a/usr.sbin/periodic/etc/daily/Makefile b/usr.sbin/periodic/etc/daily/Makefile index d1b000df01e4..c92b4ed8a6ee 100644 --- a/usr.sbin/periodic/etc/daily/Makefile +++ b/usr.sbin/periodic/etc/daily/Makefile @@ -7,17 +7,9 @@ CONFGROUPS= CONFS CONFS= 100.clean-disks \ 110.clean-tmps \ 120.clean-preserve \ - 140.clean-rwho \ 200.backup-passwd \ 210.backup-aliases \ - 221.backup-gpart \ - 222.backup-gmirror \ 400.status-disks \ - 401.status-graid \ - 406.status-gmirror \ - 407.status-graid3 \ - 408.status-gstripe \ - 409.status-gconcat \ 410.status-mfi \ 420.status-network \ 430.status-uptime \ @@ -25,6 +17,20 @@ CONFS= 100.clean-disks \ 510.status-world-kernel \ 999.local +CONFGROUPS+= GEOM +GEOM+= 221.backup-gpart \ + 222.backup-gmirror \ + 401.status-graid \ + 406.status-gmirror \ + 407.status-graid3 \ + 408.status-gstripe \ + 409.status-gconcat +GEOMPACKAGE= geom + +CONFGROUPS+= RCMDS +RCMDS+= 140.clean-rwho +RCMDSPACKAGE= rcmds + # NB: keep these sorted by MK_* knobs .if ${MK_ACCT} != "no" @@ -60,10 +66,12 @@ SENDMAILPACKAGE= sendmail .endif .if ${MK_ZFS} != "no" -CONFS+= 223.backup-zfs \ - 404.status-zfs \ - 800.scrub-zfs \ - 801.trim-zfs +CONFGROUPS+= ZFS +ZFS+= 223.backup-zfs \ + 404.status-zfs \ + 800.scrub-zfs \ + 801.trim-zfs +ZFSPACKAGE= zfs .endif .include <bsd.prog.mk> diff --git a/usr.sbin/periodic/etc/security/Makefile b/usr.sbin/periodic/etc/security/Makefile index 4c6b39d9062d..c153523d7c39 100644 --- a/usr.sbin/periodic/etc/security/Makefile +++ b/usr.sbin/periodic/etc/security/Makefile @@ -40,7 +40,9 @@ PFPACKAGE= pf .endif .if ${MK_INETD} != "no" && ${MK_TCP_WRAPPERS} != "no" -CONFS+= 900.tcpwrap +CONFGROUPS+= TCPWRAP +TCPWRAP+= 900.tcpwrap +TCPWRAPPACKAGE= tcpd .endif .include <bsd.prog.mk> diff --git a/usr.sbin/pkg/FreeBSD.conf.latest b/usr.sbin/pkg/FreeBSD.conf.latest index a75b0703386f..ac1636386942 100644 --- a/usr.sbin/pkg/FreeBSD.conf.latest +++ b/usr.sbin/pkg/FreeBSD.conf.latest @@ -1,15 +1,23 @@ # -# To disable this repository, instead of modifying or removing this file, -# create a /usr/local/etc/pkg/repos/FreeBSD.conf file: +# To disable a repository, instead of modifying or removing this file, +# create a /usr/local/etc/pkg/repos/FreeBSD.conf file, e.g.: # # mkdir -p /usr/local/etc/pkg/repos -# echo "FreeBSD: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf +# echo "FreeBSD-ports: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf +# echo "FreeBSD-ports-kmods: { enabled: no }" >> /usr/local/etc/pkg/repos/FreeBSD.conf # -FreeBSD: { +FreeBSD-ports: { url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest", mirror_type: "srv", signature_type: "fingerprints", fingerprints: "/usr/share/keys/pkg", enabled: yes } +FreeBSD-ports-kmods: { + url: "pkg+https://pkg.FreeBSD.org/${ABI}/kmods_latest", + mirror_type: "srv", + signature_type: "fingerprints", + fingerprints: "/usr/share/keys/pkg", + enabled: yes +} diff --git a/usr.sbin/pkg/FreeBSD.conf.quarterly b/usr.sbin/pkg/FreeBSD.conf.quarterly index 645053820dda..4e26582c6981 100644 --- a/usr.sbin/pkg/FreeBSD.conf.quarterly +++ b/usr.sbin/pkg/FreeBSD.conf.quarterly @@ -1,15 +1,23 @@ # -# To disable this repository, instead of modifying or removing this file, -# create a /usr/local/etc/pkg/repos/FreeBSD.conf file: +# To disable a repository, instead of modifying or removing this file, +# create a /usr/local/etc/pkg/repos/FreeBSD.conf file, e.g.: # # mkdir -p /usr/local/etc/pkg/repos -# echo "FreeBSD: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf +# echo "FreeBSD-ports: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf +# echo "FreeBSD-ports-kmods: { enabled: no }" >> /usr/local/etc/pkg/repos/FreeBSD.conf # -FreeBSD: { +FreeBSD-ports: { url: "pkg+https://pkg.FreeBSD.org/${ABI}/quarterly", mirror_type: "srv", signature_type: "fingerprints", fingerprints: "/usr/share/keys/pkg", enabled: yes } +FreeBSD-ports-kmods: { + url: "pkg+https://pkg.FreeBSD.org/${ABI}/kmods_quarterly", + mirror_type: "srv", + signature_type: "fingerprints", + fingerprints: "/usr/share/keys/pkg", + enabled: yes +} diff --git a/usr.sbin/pkg/FreeBSD.conf.quarterly-release b/usr.sbin/pkg/FreeBSD.conf.quarterly-release new file mode 100644 index 000000000000..b4a78009f7d2 --- /dev/null +++ b/usr.sbin/pkg/FreeBSD.conf.quarterly-release @@ -0,0 +1,23 @@ +# +# To disable a repository, instead of modifying or removing this file, +# create a /usr/local/etc/pkg/repos/FreeBSD.conf file, e.g.: +# +# mkdir -p /usr/local/etc/pkg/repos +# echo "FreeBSD-ports: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf +# echo "FreeBSD-ports-kmods: { enabled: no }" >> /usr/local/etc/pkg/repos/FreeBSD.conf +# + +FreeBSD-ports: { + url: "pkg+https://pkg.FreeBSD.org/${ABI}/quarterly", + mirror_type: "srv", + signature_type: "fingerprints", + fingerprints: "/usr/share/keys/pkg", + enabled: yes +} +FreeBSD-ports-kmods: { + url: "pkg+https://pkg.FreeBSD.org/${ABI}/kmods_quarterly_${VERSION_MINOR}", + mirror_type: "srv", + signature_type: "fingerprints", + fingerprints: "/usr/share/keys/pkg", + enabled: yes +} diff --git a/usr.sbin/pkg/Makefile b/usr.sbin/pkg/Makefile index 2fa0dbb053ad..0420065bb7eb 100644 --- a/usr.sbin/pkg/Makefile +++ b/usr.sbin/pkg/Makefile @@ -6,7 +6,7 @@ BRANCH?= ${_BRANCH} PKGCONFBRANCH?= latest .else . if ${BRANCH:MBETA*} || ${BRANCH:MRC*} || ${BRANCH:MRELEASE*} -PKGCONFBRANCH?= quarterly +PKGCONFBRANCH?= quarterly-release . else . if ${MACHINE} != "amd64" && ${MACHINE} != "i386" && ${MACHINE} != "arm64" PKGCONFBRANCH?= quarterly diff --git a/usr.sbin/pkg/pkg.7 b/usr.sbin/pkg/pkg.7 index 982fd8ecaeb3..d2246f74a3fc 100644 --- a/usr.sbin/pkg/pkg.7 +++ b/usr.sbin/pkg/pkg.7 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd March 30, 2025 +.Dd April 29, 2025 .Dt PKG 7 .Os .Sh NAME @@ -33,10 +33,11 @@ .Op Fl d .Ar command ... .Nm +.Op Fl d .Cm add -.Op Fl dfy +.Op Fl fy .Op Fl r Ar reponame -.Ar pkg.txz +.Ar pkg.pkg .Nm .Fl N .Nm @@ -67,7 +68,7 @@ is not installed yet, it will be fetched, have its signature verified, installed, and then have the original command forwarded to it. If already installed, the command requested will be forwarded to the real .Xr pkg 8 . -.It Nm Cm add Oo Fl fy Oc Oo Fl r Ar reponame Oc Ar pkg.txz +.It Nm Cm add Oo Fl fy Oc Oo Fl r Ar reponame Oc Ar pkg.pkg Install .Xr pkg 8 from a local package instead of fetching from remote. diff --git a/usr.sbin/pkg/pkg.c b/usr.sbin/pkg/pkg.c index 92fdbf0ebff8..7899fbaeaf09 100644 --- a/usr.sbin/pkg/pkg.c +++ b/usr.sbin/pkg/pkg.c @@ -942,10 +942,6 @@ static const char args_bootstrap_message[] = "Too many arguments\n" "Usage: pkg [-4|-6] bootstrap [-f] [-y]\n"; -static const char args_add_message[] = -"Too many arguments\n" -"Usage: pkg add [-f] [-y] {pkg.pkg}\n"; - static int pkg_query_yes_no(void) { @@ -1072,137 +1068,138 @@ int main(int argc, char *argv[]) { char pkgpath[MAXPATHLEN]; + char **original_argv; const char *pkgarg, *repo_name; bool activation_test, add_pkg, bootstrap_only, force, yes; signed char ch; const char *fetchOpts; - char *command; struct repositories *repositories; activation_test = false; add_pkg = false; bootstrap_only = false; - command = NULL; fetchOpts = ""; force = false; + original_argv = argv; pkgarg = NULL; repo_name = NULL; yes = false; struct option longopts[] = { { "debug", no_argument, NULL, 'd' }, - { "force", no_argument, NULL, 'f' }, { "only-ipv4", no_argument, NULL, '4' }, { "only-ipv6", no_argument, NULL, '6' }, - { "yes", no_argument, NULL, 'y' }, { NULL, 0, NULL, 0 }, }; snprintf(pkgpath, MAXPATHLEN, "%s/sbin/pkg", getlocalbase()); - while ((ch = getopt_long(argc, argv, "-:dfr::yN46", longopts, NULL)) != -1) { + while ((ch = getopt_long(argc, argv, "+:dN46", longopts, NULL)) != -1) { switch (ch) { case 'd': debug++; break; - case 'f': - force = true; - break; case 'N': activation_test = true; break; - case 'y': - yes = true; - break; case '4': fetchOpts = "4"; break; case '6': fetchOpts = "6"; break; - case 'r': - /* - * The repository can only be specified for an explicit - * bootstrap request at this time, so that we don't - * confuse the user if they're trying to use a verb that - * has some other conflicting meaning but we need to - * bootstrap. - * - * For that reason, we specify that -r has an optional - * argument above and process the next index ourselves. - * This is mostly significant because getopt(3) will - * otherwise eat the next argument, which could be - * something we need to try and make sense of. - * - * At worst this gets us false positives that we ignore - * in other contexts, and we have to do a little fudging - * in order to support separating -r from the reponame - * with a space since it's not actually optional in - * the bootstrap/add sense. - */ - if (add_pkg || bootstrap_only) { - if (optarg != NULL) { - repo_name = optarg; - } else if (optind < argc) { - repo_name = argv[optind]; - } + default: + break; + } + } + if (debug > 1) + fetchDebug = 1; - if (repo_name == NULL || *repo_name == '\0') { - fprintf(stderr, - "Must specify a repository with -r!\n"); - exit(EXIT_FAILURE); - } + argc -= optind; + argv += optind; + + if (argc >= 1) { + if (strcmp(argv[0], "bootstrap") == 0) { + bootstrap_only = true; + } else if (strcmp(argv[0], "add") == 0) { + add_pkg = true; + } - if (optarg == NULL) { - /* Advance past repo name. */ - optreset = 1; - optind++; + optreset = 1; + optind = 1; + if (bootstrap_only || add_pkg) { + struct option sub_longopts[] = { + { "force", no_argument, NULL, 'f' }, + { "yes", no_argument, NULL, 'y' }, + { NULL, 0, NULL, 0 }, + }; + while ((ch = getopt_long(argc, argv, "+:fr:y", + sub_longopts, NULL)) != -1) { + switch (ch) { + case 'f': + force = true; + break; + case 'r': + repo_name = optarg; + break; + case 'y': + yes = true; + break; + case ':': + fprintf(stderr, "Option -%c requires an argument\n", optopt); + exit(EXIT_FAILURE); + break; + default: + break; } } - break; - case 1: - // Non-option arguments, first one is the command - if (command == NULL) { - command = argv[optind-1]; - if (strcmp(command, "add") == 0) { - add_pkg = true; - } - else if (strcmp(command, "bootstrap") == 0) { - bootstrap_only = true; + } else { + /* + * Parse -y and --yes regardless of the pkg subcommand + * specified. This is necessary to make, for example, + * `pkg install -y foobar` work as expected when pkg is + * not yet bootstrapped. + */ + struct option sub_longopts[] = { + { "yes", no_argument, NULL, 'y' }, + { NULL, 0, NULL, 0 }, + }; + while ((ch = getopt_long(argc, argv, "+:y", + sub_longopts, NULL)) != -1) { + switch (ch) { + case 'y': + yes = true; + break; + default: + break; } } - // bootstrap doesn't accept other arguments - else if (bootstrap_only) { - fprintf(stderr, args_bootstrap_message); + + } + argc -= optind; + argv += optind; + + if (bootstrap_only && argc > 0) { + fprintf(stderr, args_bootstrap_message); + exit(EXIT_FAILURE); + } + + if (add_pkg) { + if (argc < 1) { + fprintf(stderr, "Path to pkg.pkg required\n"); exit(EXIT_FAILURE); - } - else if (add_pkg && pkgarg != NULL) { + } else if (argc == 1 && pkg_is_pkg_pkg(argv[0])) { + pkgarg = argv[0]; + } else { /* - * Additional arguments also means it's not a - * local bootstrap request. + * If the target package is not pkg.pkg + * or there is more than one target package, + * this is not a local bootstrap request. */ add_pkg = false; } - else if (add_pkg) { - /* - * If it's not a request for pkg or pkg-devel, - * then we must assume they were trying to - * install some other local package and we - * should try to bootstrap from the repo. - */ - if (!pkg_is_pkg_pkg(argv[optind-1])) { - add_pkg = false; - } else { - pkgarg = argv[optind-1]; - } - } - break; - default: - break; } } - if (debug > 1) - fetchDebug = 1; if ((bootstrap_only && force) || access(pkgpath, X_OK) == -1) { struct repository *repo; @@ -1218,10 +1215,7 @@ main(int argc, char *argv[]) config_init(repo_name); if (add_pkg) { - if (pkgarg == NULL) { - fprintf(stderr, "Path to pkg.pkg required\n"); - exit(EXIT_FAILURE); - } + assert(pkgarg != NULL); if (access(pkgarg, R_OK) == -1) { fprintf(stderr, "No such file: %s\n", pkgarg); exit(EXIT_FAILURE); @@ -1263,7 +1257,7 @@ main(int argc, char *argv[]) exit(EXIT_SUCCESS); } - execv(pkgpath, argv); + execv(pkgpath, original_argv); /* NOT REACHED */ return (EXIT_FAILURE); diff --git a/usr.sbin/pmcstat/pmcstat.8 b/usr.sbin/pmcstat/pmcstat.8 index 02218a5cfa22..edb9ff106092 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 May 31, 2023 +.Dd April 19, 2025 .Dt PMCSTAT 8 .Os .Sh NAME @@ -485,16 +485,16 @@ The number of callchain records that had an value for a return address. .It "#exec handling errors" The number of -.Xr exec 2 +.Xr execve 2 events in the log file that named executables that could not be analyzed. .It "#exec/elf" The number of -.Xr exec 2 +.Xr execve 2 events that named ELF executables. .It "#exec/unknown" The number of -.Xr exec 2 +.Xr execve 2 events that named executables with unrecognized formats. .It "#samples/total" The total number of samples in the log file. diff --git a/usr.sbin/ppp/arp.c b/usr.sbin/ppp/arp.c index ad623525ad4c..5613b345c116 100644 --- a/usr.sbin/ppp/arp.c +++ b/usr.sbin/ppp/arp.c @@ -2,7 +2,7 @@ * sys-bsd.c - System-dependent procedures for setting up * PPP interfaces on bsd-4.4-ish systems (including 386BSD, NetBSD, etc.) * - * SPDX-License-Identifier: BSD-1-Clause + * SPDX-License-Identifier: BSD-4.3TAHOE * * Copyright (c) 1989 Carnegie Mellon University. * All rights reserved. diff --git a/usr.sbin/pw/pw.8 b/usr.sbin/pw/pw.8 index c72623ee05b3..5eae810b6732 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 July 29, 2024 +.Dd August 19, 2025 .Dt PW 8 .Os .Sh NAME @@ -191,7 +191,12 @@ utility handles updating the .Xr master.passwd 5 , .Xr group 5 and the secure and insecure -password database files, and must be run as root. +password database files, and must be run as root +.Po except when using +.Fl R +or +.Fl V +.Pc . .Pp The first one or two keywords provided to .Nm diff --git a/usr.sbin/pw/pw.c b/usr.sbin/pw/pw.c index fc17f6dba022..a4c95258f3bb 100644 --- a/usr.sbin/pw/pw.c +++ b/usr.sbin/pw/pw.c @@ -162,6 +162,7 @@ main(int argc, char *argv[]) snprintf(conf.etcpath, sizeof(conf.etcpath), "%s%s", optarg, arg == 'R' ? _PATH_PWD : ""); + conf.altroot = true; } else break; } diff --git a/usr.sbin/pw/pw_user.c b/usr.sbin/pw/pw_user.c index d9fd8c77c13e..8a9a4342f5ef 100644 --- a/usr.sbin/pw/pw_user.c +++ b/usr.sbin/pw/pw_user.c @@ -238,6 +238,13 @@ perform_chgpwent(const char *name, struct passwd *pwd, char *nispasswd) } } +static void +pw_check_root(void) +{ + if (!conf.altroot && geteuid() != 0) + errx(EX_NOPERM, "you must be root"); +} + /* * The M_LOCK and M_UNLOCK functions simply add or remove * a "*LOCKED*" prefix from in front of the password to @@ -256,8 +263,7 @@ pw_userlock(char *arg1, int mode) bool locked = false; uid_t id = (uid_t)-1; - if (geteuid() != 0) - errx(EX_NOPERM, "you must be root"); + pw_check_root(); if (arg1 == NULL) errx(EX_DATAERR, "username or id required"); @@ -1324,8 +1330,8 @@ pw_user_add(int argc, char **argv, char *arg1) if (argc > 0) usage(); - if (geteuid() != 0 && ! dryrun) - errx(EX_NOPERM, "you must be root"); + if (!dryrun) + pw_check_root(); if (quiet) freopen(_PATH_DEVNULL, "w", stderr); @@ -1641,8 +1647,8 @@ pw_user_mod(int argc, char **argv, char *arg1) if (argc > 0) usage(); - if (geteuid() != 0 && ! dryrun) - errx(EX_NOPERM, "you must be root"); + if (!dryrun) + pw_check_root(); if (quiet) freopen(_PATH_DEVNULL, "w", stderr); diff --git a/usr.sbin/pw/pwupd.h b/usr.sbin/pw/pwupd.h index 262b044e07fc..a39a022ca309 100644 --- a/usr.sbin/pw/pwupd.h +++ b/usr.sbin/pw/pwupd.h @@ -78,6 +78,7 @@ struct pwconf { char etcpath[MAXPATHLEN]; int fd; int rootfd; + bool altroot; bool checkduplicate; }; diff --git a/usr.sbin/pwd_mkdb/pwd_mkdb.8 b/usr.sbin/pwd_mkdb/pwd_mkdb.8 index 262a07f4ef3c..c142a25a9d6c 100644 --- a/usr.sbin/pwd_mkdb/pwd_mkdb.8 +++ b/usr.sbin/pwd_mkdb/pwd_mkdb.8 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd March 25, 2023 +.Dd April 19, 2025 .Dt PWD_MKDB 8 .Os .Sh NAME @@ -145,8 +145,7 @@ the password file: .Sh COMPATIBILITY Previous versions of the system had a program similar to .Nm , -.Xr mkpasswd 8 , -which built +mkpasswd, which built .Xr dbm 3 style databases for the password file but depended on the calling programs to install them. diff --git a/usr.sbin/rip6query/Makefile b/usr.sbin/rip6query/Makefile index e12637e1e1e4..7b268f6198d4 100644 --- a/usr.sbin/rip6query/Makefile +++ b/usr.sbin/rip6query/Makefile @@ -1,3 +1,4 @@ +PACKAGE=rip PROG= rip6query MAN= rip6query.8 diff --git a/usr.sbin/rip6query/rip6query.8 b/usr.sbin/rip6query/rip6query.8 index 856a59138bc1..92e49f5ade58 100644 --- a/usr.sbin/rip6query/rip6query.8 +++ b/usr.sbin/rip6query/rip6query.8 @@ -29,13 +29,19 @@ .\" .\" $Id: rip6query.8,v 1.2 2000/01/19 06:24:55 itojun Exp $ .\" -.Dd October 7, 1999 +.Dd May 20, 2025 .Dt RIP6QUERY 8 .Os .Sh NAME .Nm rip6query .Nd RIPng debugging tool .\" +.Sh DEPRECATION NOTICE +The +.Nm +utility is deprecated and will be removed in +.Fx 16.0 . +.\" .Sh SYNOPSIS .Nm .Op Fl I Ar interface diff --git a/usr.sbin/route6d/Makefile b/usr.sbin/route6d/Makefile index 059d329f5f09..d2a7151249f1 100644 --- a/usr.sbin/route6d/Makefile +++ b/usr.sbin/route6d/Makefile @@ -1,3 +1,4 @@ +PACKAGE=rip PROG= route6d MAN= route6d.8 diff --git a/usr.sbin/route6d/route6d.8 b/usr.sbin/route6d/route6d.8 index 3a7bc8721923..e9ad3266ba26 100644 --- a/usr.sbin/route6d/route6d.8 +++ b/usr.sbin/route6d/route6d.8 @@ -14,12 +14,17 @@ .\" LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR .\" A PARTICULAR PURPOSE. .\" -.Dd November 18, 2012 +.Dd May 20, 2025 .Dt ROUTE6D 8 .Os .Sh NAME .Nm route6d .Nd RIP6 Routing Daemon +.Sh DEPRECATION NOTICE +The +.Nm +utility is deprecated and will be removed in +.Fx 16.0 . .Sh SYNOPSIS .Nm .Op Fl adDhlnqsS diff --git a/usr.sbin/rpc.lockd/kern.c b/usr.sbin/rpc.lockd/kern.c index c24b81159ea5..1945bd68328a 100644 --- a/usr.sbin/rpc.lockd/kern.c +++ b/usr.sbin/rpc.lockd/kern.c @@ -39,6 +39,7 @@ #include <netinet/in.h> #include <arpa/inet.h> +#include <assert.h> #include <err.h> #include <errno.h> #include <fcntl.h> @@ -232,17 +233,29 @@ void set_auth(CLIENT *cl, struct xucred *xucred) { int ngroups; + gid_t *groups; - ngroups = xucred->cr_ngroups - 1; + /* + * Exclude the first element if it is actually the egid, but account for + * the possibility that we could eventually exclude the egid from the + * exported group list some day. + */ + ngroups = xucred->cr_ngroups; + groups = &xucred->cr_groups[0]; + if (groups == &xucred->cr_gid) { + assert(ngroups > 0); + ngroups--; + groups++; + } if (ngroups > NGRPS) ngroups = NGRPS; if (cl->cl_auth != NULL) cl->cl_auth->ah_ops->ah_destroy(cl->cl_auth); cl->cl_auth = authunix_create(hostname, xucred->cr_uid, - xucred->cr_groups[0], + xucred->cr_gid, ngroups, - &xucred->cr_groups[1]); + groups); } diff --git a/usr.sbin/rpc.statd/Makefile b/usr.sbin/rpc.statd/Makefile index 044ad3602a2a..e958b2ab5aeb 100644 --- a/usr.sbin/rpc.statd/Makefile +++ b/usr.sbin/rpc.statd/Makefile @@ -1,3 +1,4 @@ +PACKAGE= nfs PROG= rpc.statd MAN= rpc.statd.8 SRCS= file.c sm_inter_svc.c sm_inter.h statd.c procs.c diff --git a/usr.sbin/rpc.tlsservd/rpc.tlsservd.c b/usr.sbin/rpc.tlsservd/rpc.tlsservd.c index 438b745fb5de..f07385a2baa7 100644 --- a/usr.sbin/rpc.tlsservd/rpc.tlsservd.c +++ b/usr.sbin/rpc.tlsservd/rpc.tlsservd.c @@ -167,7 +167,8 @@ main(int argc, char **argv) } rpctls_verbose = false; - rpctls_maxthreads = (ncpu = (u_int)sysconf(_SC_NPROCESSORS_ONLN)) / 2; + ncpu = (u_int)sysconf(_SC_NPROCESSORS_ONLN); + rpctls_maxthreads = ncpu > 1 ? ncpu / 2 : 1; while ((ch = getopt_long(argc, argv, "2C:D:dhl:N:n:mp:r:uvWw", longopts, NULL)) != -1) { diff --git a/usr.sbin/rpcbind/Makefile b/usr.sbin/rpcbind/Makefile index 1bb2d7584ed2..eadadc71310e 100644 --- a/usr.sbin/rpcbind/Makefile +++ b/usr.sbin/rpcbind/Makefile @@ -9,6 +9,8 @@ SRCS= check_bound.c rpcb_stat.c rpcb_svc_4.c rpcbind.c pmap_svc.c \ CFLAGS+= -DPORTMAP +LIBADD= util + .if ${MK_INET6_SUPPORT} != "no" CFLAGS+= -DINET6 .endif diff --git a/usr.sbin/rpcbind/Makefile.depend b/usr.sbin/rpcbind/Makefile.depend index 732a025c9552..7e5c47e39608 100644 --- a/usr.sbin/rpcbind/Makefile.depend +++ b/usr.sbin/rpcbind/Makefile.depend @@ -9,6 +9,7 @@ DIRDEPS = \ lib/${CSU_DIR} \ lib/libc \ lib/libcompiler_rt \ + lib/libutil \ .include <dirdeps.mk> diff --git a/usr.sbin/rpcbind/check_bound.c b/usr.sbin/rpcbind/check_bound.c index 446dceb3502f..820f76d37346 100644 --- a/usr.sbin/rpcbind/check_bound.c +++ b/usr.sbin/rpcbind/check_bound.c @@ -42,13 +42,16 @@ #include <sys/types.h> #include <sys/socket.h> + #include <rpc/rpc.h> #include <rpc/svc_dg.h> + #include <netconfig.h> -#include <syslog.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include <syslog.h> #include <unistd.h> -#include <stdlib.h> #include "rpcbind.h" @@ -61,9 +64,9 @@ struct fdlist { static struct fdlist *fdhead; /* Link list of the check fd's */ static struct fdlist *fdtail; -static char *nullstring = ""; +static char nullstring[] = ""; -static bool_t check_bound(struct fdlist *, char *uaddr); +static bool_t check_bound(struct fdlist *, const char *uaddr); /* * Returns 1 if the given address is bound for the given addr & transport @@ -71,7 +74,7 @@ static bool_t check_bound(struct fdlist *, char *uaddr); * Returns 0 for success. */ static bool_t -check_bound(struct fdlist *fdl, char *uaddr) +check_bound(struct fdlist *fdl, const char *uaddr) { int fd; struct netbuf *na; @@ -101,7 +104,7 @@ check_bound(struct fdlist *fdl, char *uaddr) } int -add_bndlist(struct netconfig *nconf, struct netbuf *baddr __unused) +add_bndlist(const struct netconfig *nconf, struct netbuf *baddr __unused) { struct fdlist *fdl; struct netconfig *newnconf; @@ -109,7 +112,7 @@ add_bndlist(struct netconfig *nconf, struct netbuf *baddr __unused) newnconf = getnetconfigent(nconf->nc_netid); if (newnconf == NULL) return (-1); - fdl = malloc(sizeof (struct fdlist)); + fdl = malloc(sizeof(*fdl)); if (fdl == NULL) { freenetconfigent(newnconf); syslog(LOG_ERR, "no memory!"); @@ -131,7 +134,7 @@ add_bndlist(struct netconfig *nconf, struct netbuf *baddr __unused) } bool_t -is_bound(char *netid, char *uaddr) +is_bound(const char *netid, const char *uaddr) { struct fdlist *fdl; @@ -189,7 +192,7 @@ mergeaddr(SVCXPRT *xprt, char *netid, char *uaddr, char *saddr) return (NULL); } -#ifdef ND_DEBUG +#ifdef RPCBIND_DEBUG if (debugging) { if (saddr == NULL) { fprintf(stderr, "mergeaddr: client uaddr = %s\n", @@ -205,7 +208,7 @@ mergeaddr(SVCXPRT *xprt, char *netid, char *uaddr, char *saddr) * This is all we should need for IP 4 and 6 */ m_uaddr = addrmerge(svc_getrpccaller(xprt), s_uaddr, c_uaddr, netid); -#ifdef ND_DEBUG +#ifdef RPCBIND_DEBUG if (debugging) fprintf(stderr, "mergeaddr: uaddr = %s, merged uaddr = %s\n", uaddr, m_uaddr); diff --git a/usr.sbin/rpcbind/pmap_svc.c b/usr.sbin/rpcbind/pmap_svc.c index cea1606258ae..4dcbb0f6ddd2 100644 --- a/usr.sbin/rpcbind/pmap_svc.c +++ b/usr.sbin/rpcbind/pmap_svc.c @@ -35,7 +35,7 @@ /* * pmap_svc.c - * The server procedure for the version 2 portmaper. + * The server procedure for the version 2 portmapper. * All the portmapper related interface from the portmap side. */ @@ -47,12 +47,12 @@ #include <rpc/pmap_prot.h> #include <rpc/rpcb_prot.h> #ifdef RPCBIND_DEBUG +#include <stdio.h> #include <stdlib.h> #endif #include "rpcbind.h" -static struct pmaplist *find_service_pmap(rpcprog_t, rpcvers_t, - rpcprot_t); +static struct pmaplist *find_service_pmap(rpcprog_t, rpcvers_t, rpcprot_t); static bool_t pmapproc_change(struct svc_req *, SVCXPRT *, u_long); static bool_t pmapproc_getport(struct svc_req *, SVCXPRT *); static bool_t pmapproc_dump(struct svc_req *, SVCXPRT *); @@ -168,6 +168,11 @@ pmapproc_change(struct svc_req *rqstp __unused, SVCXPRT *xprt, unsigned long op) uid_t uid; char uidbuf[32]; + if (!svc_getargs(xprt, (xdrproc_t) xdr_pmap, (char *)®)) { + svcerr_decode(xprt); + return (FALSE); + } + #ifdef RPCBIND_DEBUG if (debugging) fprintf(stderr, "%s request for (%lu, %lu) : ", @@ -175,11 +180,6 @@ pmapproc_change(struct svc_req *rqstp __unused, SVCXPRT *xprt, unsigned long op) reg.pm_prog, reg.pm_vers); #endif - if (!svc_getargs(xprt, (xdrproc_t) xdr_pmap, (char *)®)) { - svcerr_decode(xprt); - return (FALSE); - } - if (!check_access(xprt, op, ®, PMAPVERS)) { svcerr_weakauth(xprt); return FALSE; @@ -192,12 +192,12 @@ pmapproc_change(struct svc_req *rqstp __unused, SVCXPRT *xprt, unsigned long op) * and looping. */ if (__rpc_get_local_uid(xprt, &uid) < 0) - rpcbreg.r_owner = "unknown"; + rpcbreg.r_owner = __UNCONST(rpcbind_unknown); else if (uid == 0) - rpcbreg.r_owner = "superuser"; + rpcbreg.r_owner = __UNCONST(rpcbind_superuser); else { /* r_owner will be strdup-ed later */ - snprintf(uidbuf, sizeof uidbuf, "%d", uid); + snprintf(uidbuf, sizeof(uidbuf), "%d", uid); rpcbreg.r_owner = uidbuf; } @@ -207,14 +207,14 @@ pmapproc_change(struct svc_req *rqstp __unused, SVCXPRT *xprt, unsigned long op) if (op == PMAPPROC_SET) { char buf[32]; - snprintf(buf, sizeof buf, "0.0.0.0.%d.%d", + snprintf(buf, sizeof(buf), "0.0.0.0.%d.%d", (int)((reg.pm_port >> 8) & 0xff), (int)(reg.pm_port & 0xff)); rpcbreg.r_addr = buf; if (reg.pm_prot == IPPROTO_UDP) { - rpcbreg.r_netid = udptrans; + rpcbreg.r_netid = __UNCONST(udptrans); } else if (reg.pm_prot == IPPROTO_TCP) { - rpcbreg.r_netid = tcptrans; + rpcbreg.r_netid = __UNCONST(tcptrans); } else { ans = FALSE; goto done_change; @@ -224,9 +224,9 @@ pmapproc_change(struct svc_req *rqstp __unused, SVCXPRT *xprt, unsigned long op) bool_t ans1, ans2; rpcbreg.r_addr = NULL; - rpcbreg.r_netid = tcptrans; + rpcbreg.r_netid = __UNCONST(tcptrans); ans1 = map_unset(&rpcbreg, rpcbreg.r_owner); - rpcbreg.r_netid = udptrans; + rpcbreg.r_netid = __UNCONST(udptrans); ans2 = map_unset(&rpcbreg, rpcbreg.r_owner); ans = ans1 || ans2; } else { @@ -285,9 +285,9 @@ pmapproc_getport(struct svc_req *rqstp __unused, SVCXPRT *xprt) #endif fnd = find_service_pmap(reg.pm_prog, reg.pm_vers, reg.pm_prot); if (fnd) { - char serveuaddr[32], *ua; + char serveuaddr[32]; int h1, h2, h3, h4, p1, p2; - char *netid; + const char *netid, *ua; if (reg.pm_prot == IPPROTO_UDP) { ua = udp_uaddr; @@ -303,7 +303,7 @@ pmapproc_getport(struct svc_req *rqstp __unused, SVCXPRT *xprt) &h4, &p1, &p2) == 6) { p1 = (fnd->pml_map.pm_port >> 8) & 0xff; p2 = (fnd->pml_map.pm_port) & 0xff; - snprintf(serveuaddr, sizeof serveuaddr, + snprintf(serveuaddr, sizeof(serveuaddr), "%d.%d.%d.%d.%d.%d", h1, h2, h3, h4, p1, p2); if (is_bound(netid, serveuaddr)) { port = fnd->pml_map.pm_port; diff --git a/usr.sbin/rpcbind/rpcb_stat.c b/usr.sbin/rpcbind/rpcb_stat.c index 9c03ce368293..9500bb7b7cbe 100644 --- a/usr.sbin/rpcbind/rpcb_stat.c +++ b/usr.sbin/rpcbind/rpcb_stat.c @@ -37,15 +37,18 @@ * Copyright (c) 1990 by Sun Microsystems, Inc. */ -#include <netconfig.h> +#include <sys/stat.h> + #include <rpc/rpc.h> #include <rpc/rpcb_prot.h> -#include <sys/stat.h> #ifdef PORTMAP #include <rpc/pmap_prot.h> #endif + +#include <netconfig.h> #include <stdlib.h> #include <string.h> + #include "rpcbind.h" static rpcb_stat_byvers inf; @@ -96,8 +99,8 @@ rpcbs_unset(rpcvers_t rtype, bool_t success) } void -rpcbs_getaddr(rpcvers_t rtype, rpcprog_t prog, rpcvers_t vers, char *netid, - char *uaddr) +rpcbs_getaddr(rpcvers_t rtype, rpcprog_t prog, rpcvers_t vers, + const char *netid, const char *uaddr) { rpcbs_addrlist *al; struct netconfig *nconf; @@ -121,7 +124,7 @@ rpcbs_getaddr(rpcvers_t rtype, rpcprog_t prog, rpcvers_t vers, char *netid, if (nconf == NULL) { return; } - al = (rpcbs_addrlist *) malloc(sizeof (rpcbs_addrlist)); + al = malloc(sizeof(*al)); if (al == NULL) { return; } @@ -170,7 +173,7 @@ rpcbs_rmtcall(rpcvers_t rtype, rpcproc_t rpcbproc, rpcprog_t prog, if (nconf == NULL) { return; } - rl = (rpcbs_rmtcalllist *) malloc(sizeof (rpcbs_rmtcalllist)); + rl = malloc(sizeof(*rl)); if (rl == NULL) { return; } diff --git a/usr.sbin/rpcbind/rpcb_svc.c b/usr.sbin/rpcbind/rpcb_svc.c index 94323ab992aa..5a23abe3dbc6 100644 --- a/usr.sbin/rpcbind/rpcb_svc.c +++ b/usr.sbin/rpcbind/rpcb_svc.c @@ -46,6 +46,7 @@ #include <netconfig.h> #include <stdio.h> #ifdef RPCBIND_DEBUG +#include <stdio.h> #include <stdlib.h> #endif #include <string.h> @@ -53,9 +54,9 @@ #include "rpcbind.h" static void *rpcbproc_getaddr_3_local(void *, struct svc_req *, SVCXPRT *, - rpcvers_t); + rpcvers_t); static void *rpcbproc_dump_3_local(void *, struct svc_req *, SVCXPRT *, - rpcvers_t); + rpcvers_t); /* * Called by svc_getreqset. There is a separate server handle for @@ -89,7 +90,7 @@ rpcb_service_3(struct svc_req *rqstp, SVCXPRT *transp) #endif /* This call just logs, no actual checks */ check_access(transp, rqstp->rq_proc, NULL, RPCBVERS); - (void) svc_sendreply(transp, (xdrproc_t)xdr_void, (char *)NULL); + (void) svc_sendreply(transp, (xdrproc_t)xdr_void, NULL); return; case RPCBPROC_SET: @@ -204,7 +205,7 @@ done: /* ARGSUSED */ static void * rpcbproc_getaddr_3_local(void *arg, struct svc_req *rqstp __unused, - SVCXPRT *transp __unused, rpcvers_t versnum __unused) + SVCXPRT *transp __unused, rpcvers_t versnum __unused) { RPCB *regp = (RPCB *)arg; #ifdef RPCBIND_DEBUG @@ -212,7 +213,7 @@ rpcbproc_getaddr_3_local(void *arg, struct svc_req *rqstp __unused, char *uaddr; uaddr = taddr2uaddr(rpcbind_get_conf(transp->xp_netid), - svc_getrpccaller(transp)); + svc_getrpccaller(transp)); fprintf(stderr, "RPCB_GETADDR req for (%lu, %lu, %s) from %s: ", (unsigned long)regp->r_prog, (unsigned long)regp->r_vers, regp->r_netid, uaddr); @@ -226,7 +227,7 @@ rpcbproc_getaddr_3_local(void *arg, struct svc_req *rqstp __unused, /* ARGSUSED */ static void * rpcbproc_dump_3_local(void *arg __unused, struct svc_req *rqstp __unused, - SVCXPRT *transp __unused, rpcvers_t versnum __unused) + SVCXPRT *transp __unused, rpcvers_t versnum __unused) { return ((void *)&list_rbl); } diff --git a/usr.sbin/rpcbind/rpcb_svc_4.c b/usr.sbin/rpcbind/rpcb_svc_4.c index 77240e4d3ef5..817312709cf0 100644 --- a/usr.sbin/rpcbind/rpcb_svc_4.c +++ b/usr.sbin/rpcbind/rpcb_svc_4.c @@ -51,12 +51,14 @@ #include "rpcbind.h" static void *rpcbproc_getaddr_4_local(void *, struct svc_req *, SVCXPRT *, - rpcvers_t); -static void *rpcbproc_getversaddr_4_local(void *, struct svc_req *, SVCXPRT *, rpcvers_t); -static void *rpcbproc_getaddrlist_4_local - (void *, struct svc_req *, SVCXPRT *, rpcvers_t); + rpcvers_t); +static void *rpcbproc_getversaddr_4_local(void *, struct svc_req *, SVCXPRT *, + rpcvers_t); +static void *rpcbproc_getaddrlist_4_local(void *, struct svc_req *, SVCXPRT *, + rpcvers_t); static void free_rpcb_entry_list(rpcb_entry_list_ptr *); -static void *rpcbproc_dump_4_local(void *, struct svc_req *, SVCXPRT *, rpcvers_t); +static void *rpcbproc_dump_4_local(void *, struct svc_req *, SVCXPRT *, + rpcvers_t); /* * Called by svc_getreqset. There is a separate server handle for @@ -88,8 +90,7 @@ rpcb_service_4(struct svc_req *rqstp, SVCXPRT *transp) fprintf(stderr, "RPCBPROC_NULL\n"); #endif check_access(transp, rqstp->rq_proc, NULL, RPCBVERS4); - (void) svc_sendreply(transp, (xdrproc_t) xdr_void, - (char *)NULL); + (void) svc_sendreply(transp, (xdrproc_t) xdr_void, NULL); return; case RPCBPROC_SET: @@ -257,7 +258,7 @@ done: /* ARGSUSED */ static void * rpcbproc_getaddr_4_local(void *arg, struct svc_req *rqstp, SVCXPRT *transp, - rpcvers_t rpcbversnum __unused) + rpcvers_t rpcbversnum __unused) { RPCB *regp = (RPCB *)arg; #ifdef RPCBIND_DEBUG @@ -315,7 +316,7 @@ rpcbproc_getversaddr_4_local(void *arg, struct svc_req *rqstp, SVCXPRT *transp, /* ARGSUSED */ static void * rpcbproc_getaddrlist_4_local(void *arg, struct svc_req *rqstp __unused, - SVCXPRT *transp, rpcvers_t versnum __unused) + SVCXPRT *transp, rpcvers_t versnum __unused) { RPCB *regp = (RPCB *)arg; static rpcb_entry_list_ptr rlist; @@ -384,7 +385,7 @@ rpcbproc_getaddrlist_4_local(void *arg, struct svc_req *rqstp __unused, /* * Add it to rlist. */ - rp = malloc(sizeof (rpcb_entry_list)); + rp = malloc(sizeof(*rp)); if (rp == NULL) goto fail; a = &rp->rpcb_entry_map; @@ -397,7 +398,7 @@ rpcbproc_getaddrlist_4_local(void *arg, struct svc_req *rqstp __unused, if (rlist == NULL) { rlist = rp; tail = rp; - } else { + } else if (tail != NULL) { tail->rpcb_entry_next = rp; tail = rp; } @@ -417,9 +418,10 @@ rpcbproc_getaddrlist_4_local(void *arg, struct svc_req *rqstp __unused, * Perhaps wrong, but better than it not getting counted at all. */ rpcbs_getaddr(RPCBVERS4 - 2, prog, vers, transp->xp_netid, maddr); - return (void *)&rlist; + return (&rlist); -fail: free_rpcb_entry_list(&rlist); +fail: + free_rpcb_entry_list(&rlist); return (NULL); } @@ -444,7 +446,7 @@ free_rpcb_entry_list(rpcb_entry_list_ptr *rlistp) /* ARGSUSED */ static void * rpcbproc_dump_4_local(void *arg __unused, struct svc_req *req __unused, - SVCXPRT *xprt __unused, rpcvers_t versnum __unused) + SVCXPRT *xprt __unused, rpcvers_t versnum __unused) { return ((void *)&list_rbl); } diff --git a/usr.sbin/rpcbind/rpcb_svc_com.c b/usr.sbin/rpcbind/rpcb_svc_com.c index 15ecda213abc..a82cf44bfa3c 100644 --- a/usr.sbin/rpcbind/rpcb_svc_com.c +++ b/usr.sbin/rpcbind/rpcb_svc_com.c @@ -110,7 +110,7 @@ static int check_rmtcalls(struct pollfd *, int); static void xprt_set_caller(SVCXPRT *, struct finfo *); static void send_svcsyserr(SVCXPRT *, struct finfo *); static void handle_reply(int, SVCXPRT *); -static void find_versions(rpcprog_t, char *, rpcvers_t *, rpcvers_t *); +static void find_versions(rpcprog_t, const char *, rpcvers_t *, rpcvers_t *); static rpcblist_ptr find_service(rpcprog_t, rpcvers_t, char *); static char *getowner(SVCXPRT *, char *, size_t); static int add_pmaplist(RPCB *); @@ -122,17 +122,17 @@ static int del_pmaplist(RPCB *); /* ARGSUSED */ void * rpcbproc_set_com(void *arg, struct svc_req *rqstp __unused, SVCXPRT *transp, - rpcvers_t rpcbversnum) + rpcvers_t rpcbversnum) { - RPCB *regp = (RPCB *)arg; + RPCB *regp = arg; static bool_t ans; char owner[64]; #ifdef RPCBIND_DEBUG if (debugging) - fprintf(stderr, "RPCB_SET request for (%lu, %lu, %s, %s) : ", - (unsigned long)regp->r_prog, (unsigned long)regp->r_vers, - regp->r_netid, regp->r_addr); + fprintf(stderr, "%s: RPCB_SET request for (%lu, %lu, %s, %s): ", + __func__, (unsigned long)regp->r_prog, + (unsigned long)regp->r_vers, regp->r_netid, regp->r_addr); #endif ans = map_set(regp, getowner(transp, owner, sizeof owner)); #ifdef RPCBIND_DEBUG @@ -145,7 +145,7 @@ rpcbproc_set_com(void *arg, struct svc_req *rqstp __unused, SVCXPRT *transp, } bool_t -map_set(RPCB *regp, char *owner) +map_set(RPCB *regp, const char *owner) { RPCB reg, *a; rpcblist_ptr rbl, fnd; @@ -170,7 +170,7 @@ map_set(RPCB *regp, char *owner) /* * add to the end of the list */ - rbl = malloc(sizeof (RPCBLIST)); + rbl = malloc(sizeof(*rbl)); if (rbl == NULL) return (FALSE); a = &(rbl->rpcb_map); @@ -186,7 +186,7 @@ map_set(RPCB *regp, char *owner) free(rbl); return (FALSE); } - rbl->rpcb_next = (rpcblist_ptr)NULL; + rbl->rpcb_next = NULL; if (list_rbl == NULL) { list_rbl = rbl; } else { @@ -196,7 +196,7 @@ map_set(RPCB *regp, char *owner) fnd->rpcb_next = rbl; } #ifdef PORTMAP - (void) add_pmaplist(regp); + (void)add_pmaplist(regp); #endif return (TRUE); } @@ -207,17 +207,17 @@ map_set(RPCB *regp, char *owner) /* ARGSUSED */ void * rpcbproc_unset_com(void *arg, struct svc_req *rqstp __unused, SVCXPRT *transp, - rpcvers_t rpcbversnum) + rpcvers_t rpcbversnum) { - RPCB *regp = (RPCB *)arg; + RPCB *regp = arg; static bool_t ans; char owner[64]; #ifdef RPCBIND_DEBUG if (debugging) - fprintf(stderr, "RPCB_UNSET request for (%lu, %lu, %s) : ", - (unsigned long)regp->r_prog, (unsigned long)regp->r_vers, - regp->r_netid); + fprintf(stderr, "%s: RPCB_UNSET request for (%lu, %lu, %s): ", + __func__, (unsigned long)regp->r_prog, + (unsigned long)regp->r_vers, regp->r_netid); #endif ans = map_unset(regp, getowner(transp, owner, sizeof owner)); #ifdef RPCBIND_DEBUG @@ -230,7 +230,7 @@ rpcbproc_unset_com(void *arg, struct svc_req *rqstp __unused, SVCXPRT *transp, } bool_t -map_unset(RPCB *regp, char *owner) +map_unset(RPCB *regp, const char *owner) { int ans = 0; rpcblist_ptr rbl, prev, tmp; @@ -252,7 +252,7 @@ map_unset(RPCB *regp, char *owner) * Check whether appropriate uid. Unset only * if superuser or the owner itself. */ - if (strcmp(owner, "superuser") && + if (strcmp(owner, rpcbind_superuser) && strcmp(rbl->rpcb_map.r_owner, owner)) return (0); /* found it; rbl moves forward, prev stays */ @@ -270,21 +270,21 @@ map_unset(RPCB *regp, char *owner) } #ifdef PORTMAP if (ans) - (void) del_pmaplist(regp); + (void)del_pmaplist(regp); #endif /* * We return 1 either when the entry was not there or it * was able to unset it. It can come to this point only if - * atleast one of the conditions is true. + * at least one of the conditions is true. */ return (1); } void -delete_prog(unsigned int prog) +delete_prog(rpcprog_t prog) { RPCB reg; - register rpcblist_ptr rbl; + rpcblist_ptr rbl; for (rbl = list_rbl; rbl != NULL; rbl = rbl->rpcb_next) { if ((rbl->rpcb_map.r_prog != prog)) @@ -294,14 +294,18 @@ delete_prog(unsigned int prog) reg.r_prog = rbl->rpcb_map.r_prog; reg.r_vers = rbl->rpcb_map.r_vers; reg.r_netid = strdup(rbl->rpcb_map.r_netid); - (void) map_unset(®, "superuser"); - free(reg.r_netid); + if (reg.r_netid == NULL) + syslog(LOG_ERR, "%s: %m", __func__); + else { + (void)map_unset(®, rpcbind_superuser); + free(reg.r_netid); + } } } void * rpcbproc_getaddr_com(RPCB *regp, struct svc_req *rqstp __unused, - SVCXPRT *transp, rpcvers_t rpcbversnum, rpcvers_t verstype) + SVCXPRT *transp, rpcvers_t rpcbversnum, rpcvers_t verstype) { static char *uaddr; char *saddr = NULL; @@ -333,23 +337,23 @@ rpcbproc_getaddr_com(RPCB *regp, struct svc_req *rqstp __unused, } #ifdef RPCBIND_DEBUG if (debugging) - fprintf(stderr, "getaddr: %s\n", uaddr); + fprintf(stderr, "%s: %s\n", __func__, uaddr); #endif /* XXX: should have used some defined constant here */ rpcbs_getaddr(rpcbversnum - 2, regp->r_prog, regp->r_vers, transp->xp_netid, uaddr); - return (void *)&uaddr; + return (&uaddr); } /* ARGSUSED */ void * rpcbproc_gettime_com(void *arg __unused, struct svc_req *rqstp __unused, - SVCXPRT *transp __unused, rpcvers_t rpcbversnum __unused) + SVCXPRT *transp __unused, rpcvers_t rpcbversnum __unused) { static time_t curtime; - (void) time(&curtime); - return (void *)&curtime; + (void)time(&curtime); + return (&curtime); } /* @@ -359,9 +363,9 @@ rpcbproc_gettime_com(void *arg __unused, struct svc_req *rqstp __unused, /* ARGSUSED */ void * rpcbproc_uaddr2taddr_com(void *arg, struct svc_req *rqstp __unused, - SVCXPRT *transp, rpcvers_t rpcbversnum __unused) + SVCXPRT *transp, rpcvers_t rpcbversnum __unused) { - char **uaddrp = (char **)arg; + char **uaddrp = arg; struct netconfig *nconf; static struct netbuf nbuf; static struct netbuf *taddr; @@ -370,10 +374,10 @@ rpcbproc_uaddr2taddr_com(void *arg, struct svc_req *rqstp __unused, taddr = NULL; if (((nconf = rpcbind_get_conf(transp->xp_netid)) == NULL) || ((taddr = uaddr2taddr(nconf, *uaddrp)) == NULL)) { - (void) memset((char *)&nbuf, 0, sizeof (struct netbuf)); - return (void *)&nbuf; + memset(&nbuf, 0, sizeof(nbuf)); + return (&nbuf); } - return (void *)taddr; + return (taddr); } /* @@ -383,9 +387,9 @@ rpcbproc_uaddr2taddr_com(void *arg, struct svc_req *rqstp __unused, /* ARGSUSED */ void * rpcbproc_taddr2uaddr_com(void *arg, struct svc_req *rqstp __unused, - SVCXPRT *transp, rpcvers_t rpcbversnum __unused) + SVCXPRT *transp, rpcvers_t rpcbversnum __unused) { - struct netbuf *taddr = (struct netbuf *)arg; + struct netbuf *taddr = arg; static char *uaddr; struct netconfig *nconf; @@ -393,7 +397,7 @@ rpcbproc_taddr2uaddr_com(void *arg, struct svc_req *rqstp __unused, int fd; if ((fd = open("/dev/null", O_RDONLY)) == -1) { - uaddr = (char *)strerror(errno); + uaddr = strerror(errno); return (&uaddr); } #endif /* CHEW_FDS */ @@ -480,7 +484,7 @@ static struct rmtcallfd_list *rmthead; static struct rmtcallfd_list *rmttail; int -create_rmtcall_fd(struct netconfig *nconf) +create_rmtcall_fd(const struct netconfig *nconf) { int fd; struct rmtcallfd_list *rmt; @@ -488,21 +492,20 @@ create_rmtcall_fd(struct netconfig *nconf) if ((fd = __rpc_nconf2fd(nconf)) == -1) { if (debugging) - fprintf(stderr, - "create_rmtcall_fd: couldn't open \"%s\" (errno %d)\n", - nconf->nc_device, errno); + fprintf(stderr, "%s: couldn't open \"%s\": %m\n", + __func__, nconf->nc_device); return (-1); } - xprt = svc_tli_create(fd, 0, (struct t_bind *) 0, 0, 0); + xprt = svc_tli_create(fd, 0, NULL, 0, 0); if (xprt == NULL) { if (debugging) - fprintf(stderr, - "create_rmtcall_fd: svc_tli_create failed\n"); + fprintf(stderr, "%s: svc_tli_create failed\n", + __func__); return (-1); } - rmt = malloc(sizeof (struct rmtcallfd_list)); + rmt = malloc(sizeof(*rmt)); if (rmt == NULL) { - syslog(LOG_ERR, "create_rmtcall_fd: no memory!"); + syslog(LOG_ERR, "%s: %m", __func__); return (-1); } rmt->xprt = xprt; @@ -591,16 +594,16 @@ find_rmtcallxprt_by_fd(int fd) void rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, - rpcproc_t reply_type, rpcvers_t versnum) + rpcproc_t reply_type, rpcvers_t versnum) { - register rpcblist_ptr rbl; + rpcblist_ptr rbl; struct netconfig *nconf; struct netbuf *caller; struct r_rmtcall_args a; char *buf_alloc = NULL, *outbufp; char *outbuf_alloc = NULL; char buf[RPC_BUF_MAX], outbuf[RPC_BUF_MAX]; - struct netbuf *na = (struct netbuf *) NULL; + struct netbuf *na = NULL; struct rpc_msg call_msg; int outlen; u_int sendsz; @@ -638,8 +641,7 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, #endif /* notyet */ if (buf_alloc == NULL) { if (debugging) - fprintf(stderr, - "rpcbproc_callit_com: No Memory!\n"); + fprintf(stderr, "%s: %m\n", __func__); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); return; @@ -650,12 +652,11 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, } call_msg.rm_xid = 0; /* For error checking purposes */ - if (!svc_getargs(transp, (xdrproc_t) xdr_rmtcall_args, (char *) &a)) { + if (!svc_getargs(transp, (xdrproc_t)xdr_rmtcall_args, (char *) &a)) { if (reply_type == RPCBPROC_INDIRECT) svcerr_decode(transp); if (debugging) - fprintf(stderr, - "rpcbproc_callit_com: svc_getargs failed\n"); + fprintf(stderr, "%s: svc_getargs failed\n", __func__); goto error; } @@ -668,14 +669,15 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, #ifdef RPCBIND_DEBUG if (debugging) { uaddr = taddr2uaddr(rpcbind_get_conf(transp->xp_netid), caller); - fprintf(stderr, "%s %s req for (%lu, %lu, %lu, %s) from %s : ", - versnum == PMAPVERS ? "pmap_rmtcall" : - versnum == RPCBVERS ? "rpcb_rmtcall" : - versnum == RPCBVERS4 ? "rpcb_indirect" : "unknown", - reply_type == RPCBPROC_INDIRECT ? "indirect" : "callit", - (unsigned long)a.rmt_prog, (unsigned long)a.rmt_vers, - (unsigned long)a.rmt_proc, transp->xp_netid, - uaddr ? uaddr : "unknown"); + fprintf(stderr, + "%s: %s %s req for (%lu, %lu, %lu, %s) from %s: ", + __func__, versnum == PMAPVERS ? "pmap_rmtcall" : + versnum == RPCBVERS ? "rpcb_rmtcall" : + versnum == RPCBVERS4 ? "rpcb_indirect" : rpcbind_unknown, + reply_type == RPCBPROC_INDIRECT ? "indirect" : "callit", + (unsigned long)a.rmt_prog, (unsigned long)a.rmt_vers, + (unsigned long)a.rmt_proc, transp->xp_netid, + uaddr ? uaddr : rpcbind_unknown); free(uaddr); } #endif @@ -685,7 +687,7 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, rpcbs_rmtcall(versnum - 2, reply_type, a.rmt_prog, a.rmt_vers, a.rmt_proc, transp->xp_netid, rbl); - if (rbl == (rpcblist_ptr)NULL) { + if (rbl == NULL) { #ifdef RPCBIND_DEBUG if (debugging) fprintf(stderr, "not found\n"); @@ -726,19 +728,18 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, free(uaddr); } nconf = rpcbind_get_conf(transp->xp_netid); - if (nconf == (struct netconfig *)NULL) { + if (nconf == NULL) { if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); if (debugging) - fprintf(stderr, - "rpcbproc_callit_com: rpcbind_get_conf failed\n"); + fprintf(stderr, "%s: rpcbind_get_conf failed\n", + __func__); goto error; } localsa = local_sa(((struct sockaddr *)caller->buf)->sa_family); if (localsa == NULL) { if (debugging) - fprintf(stderr, - "rpcbproc_callit_com: no local address\n"); + fprintf(stderr, "%s: no local address\n", __func__); goto error; } tbuf.len = tbuf.maxlen = localsa->sa_len; @@ -749,7 +750,7 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, nconf->nc_netid); #ifdef RPCBIND_DEBUG if (debugging) - fprintf(stderr, "merged uaddr %s\n", m_uaddr); + fprintf(stderr, "%s: merged uaddr %s\n", __func__, m_uaddr); #endif if ((fd = find_rmtcallfd_by_netid(nconf->nc_netid)) == -1) { if (reply_type == RPCBPROC_INDIRECT) @@ -769,22 +770,20 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, * beat on it any more. */ if (debugging) - fprintf(stderr, - "rpcbproc_callit_com: duplicate request\n"); + fprintf(stderr, "%s: duplicate request\n", __func__); goto error; case -1: /* forward_register failed. Perhaps no memory. */ if (debugging) - fprintf(stderr, - "rpcbproc_callit_com: forward_register failed\n"); + fprintf(stderr, "%s: forward_register failed\n", + __func__); goto error; } #ifdef DEBUG_RMTCALL if (debugging) - fprintf(stderr, - "rpcbproc_callit_com: original XID %x, new XID %x\n", - *xidp, call_msg.rm_xid); + fprintf(stderr, "%s: original XID %x, new XID %x\n", __func__, + *xidp, call_msg.rm_xid); #endif call_msg.rm_direction = CALL; call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION; @@ -797,11 +796,10 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, outbuf_alloc = malloc(sendsz); #endif /* notyet */ if (outbuf_alloc == NULL) { + if (debugging) + fprintf(stderr, "%s: %m\n", __func__); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); - if (debugging) - fprintf(stderr, - "rpcbproc_callit_com: No memory!\n"); goto error; } xdrmem_create(&outxdr, outbuf_alloc, sendsz, XDR_ENCODE); @@ -812,16 +810,14 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); if (debugging) - fprintf(stderr, - "rpcbproc_callit_com: xdr_callhdr failed\n"); + fprintf(stderr, "%s: xdr_callhdr failed\n", __func__); goto error; } if (!xdr_u_int32_t(&outxdr, &(a.rmt_proc))) { if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); if (debugging) - fprintf(stderr, - "rpcbproc_callit_com: xdr_u_long failed\n"); + fprintf(stderr, "%s: xdr_u_long failed\n", __func__); goto error; } @@ -839,8 +835,8 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, } else { /* we do not support any other authentication scheme */ if (debugging) - fprintf(stderr, -"rpcbproc_callit_com: oa_flavor != AUTH_NONE and oa_flavor != AUTH_SYS\n"); + fprintf(stderr, "%s: oa_flavor != AUTH_NONE and " + "oa_flavor != AUTH_SYS\n", __func__); if (reply_type == RPCBPROC_INDIRECT) svcerr_weakauth(transp); /* XXX too strong.. */ goto error; @@ -850,7 +846,8 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, svcerr_systemerr(transp); if (debugging) fprintf(stderr, - "rpcbproc_callit_com: authwhatever_create returned NULL\n"); + "%s: authwhatever_create returned NULL\n", + __func__); goto error; } if (!AUTH_MARSHALL(auth, &outxdr)) { @@ -858,8 +855,8 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, svcerr_systemerr(transp); AUTH_DESTROY(auth); if (debugging) - fprintf(stderr, - "rpcbproc_callit_com: AUTH_MARSHALL failed\n"); + fprintf(stderr, "%s: AUTH_MARSHALL failed\n", + __func__); goto error; } AUTH_DESTROY(auth); @@ -867,8 +864,8 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); if (debugging) - fprintf(stderr, - "rpcbproc_callit_com: xdr_opaque_parms failed\n"); + fprintf(stderr, "%s: xdr_opaque_parms failed\n", + __func__); goto error; } outlen = (int) XDR_GETPOS(&outxdr); @@ -887,8 +884,7 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, if (sendto(fd, outbufp, outlen, 0, (struct sockaddr *)na->buf, na->len) != outlen) { if (debugging) - fprintf(stderr, - "rpcbproc_callit_com: sendto failed: errno %d\n", errno); + fprintf(stderr, "%s: sendto failed: %m\n", __func__); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); goto error; @@ -897,7 +893,7 @@ rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, error: if (call_msg.rm_xid != 0) - (void) free_slot_by_xid(call_msg.rm_xid); + (void)free_slot_by_xid(call_msg.rm_xid); out: free(local_uaddr); free(buf_alloc); @@ -913,14 +909,12 @@ out: */ static int forward_register(u_int32_t caller_xid, struct netbuf *caller_addr, - int forward_fd, char *uaddr, rpcproc_t reply_type, - rpcvers_t versnum, u_int32_t *callxidp) + int forward_fd, char *uaddr, rpcproc_t reply_type, + rpcvers_t versnum, u_int32_t *callxidp) { - int i; - int j = 0; - time_t min_time, time_now; - static u_int32_t lastxid; - int entry = -1; + static u_int32_t lastxid; + time_t min_time, time_now; + int i, j = 0, entry = -1; min_time = FINFO[0].time; time_now = time((time_t *)0); @@ -945,7 +939,7 @@ forward_register(u_int32_t caller_xid, struct netbuf *caller_addr, } else { /* Should we wait any longer */ if ((time_now - FINFO[i].time) > MAXTIME_OFF) - (void) free_slot_by_index(i); + (void)free_slot_by_index(i); } } if (entry == -1) { @@ -961,7 +955,7 @@ forward_register(u_int32_t caller_xid, struct netbuf *caller_addr, /* use this empty slot */ j = entry; } else { - (void) free_slot_by_index(j); + (void)free_slot_by_index(j); } if ((FINFO[j].caller_addr = netbufdup(caller_addr)) == NULL) { return (-1); @@ -990,7 +984,7 @@ forward_register(u_int32_t caller_xid, struct netbuf *caller_addr, static struct finfo * forward_find(u_int32_t reply_xid) { - int i; + int i; i = reply_xid % (u_int32_t)NFORWARD; if ((FINFO[i].flag & FINFO_ACTIVE) && @@ -1012,7 +1006,7 @@ free_slot_by_xid(u_int32_t xid) static int free_slot_by_index(int index) { - struct finfo *fi; + struct finfo *fi; fi = &FINFO[index]; if (fi->flag & FINFO_ACTIVE) { @@ -1084,15 +1078,14 @@ extern bool_t __svc_clean_idle(fd_set *, int, bool_t); void my_svc_run(void) { - size_t nfds; struct pollfd pollfds[FD_SETSIZE + 1]; - int poll_ret, check_ret; - int n; + fd_set cleanfds; + struct pollfd *p; + size_t nfds; #ifdef SVC_RUN_DEBUG - int i; + size_t i; #endif - register struct pollfd *p; - fd_set cleanfds; + int n, poll_ret, check_ret; for (;;) { p = pollfds; @@ -1110,7 +1103,8 @@ my_svc_run(void) poll_ret = 0; #ifdef SVC_RUN_DEBUG if (debugging) { - fprintf(stderr, "polling for read on fd < "); + fprintf(stderr, "%s: polling for read on fd < ", + __func__); for (i = 0, p = pollfds; i < nfds; i++, p++) if (p->events) fprintf(stderr, "%d ", p->fd); @@ -1137,6 +1131,13 @@ my_svc_run(void) * that it was set by the signal handlers (or any * other outside event) and not caused by poll(). */ +#ifdef SVC_RUN_DEBUG + if (debugging) { + fprintf(stderr, "%s: poll returned %d: %m\n", + __func__, poll_ret); + } +#endif + /* FALLTHROUGH */ case 0: cleanfds = svc_fdset; __svc_clean_idle(&cleanfds, 30, FALSE); @@ -1144,10 +1145,12 @@ my_svc_run(void) default: #ifdef SVC_RUN_DEBUG if (debugging) { - fprintf(stderr, "poll returned read fds < "); + fprintf(stderr, "%s: poll returned read fds < ", + __func__); for (i = 0, p = pollfds; i < nfds; i++, p++) if (p->revents) - fprintf(stderr, "%d ", p->fd); + fprintf(stderr, "%d (%#x)", + p->fd, p->revents); fprintf(stderr, ">\n"); } #endif @@ -1165,7 +1168,8 @@ my_svc_run(void) } #ifdef SVC_RUN_DEBUG if (debugging) { - fprintf(stderr, "svc_maxfd now %u\n", svc_maxfd); + fprintf(stderr, "%s: svc_maxfd now %u\n", __func__, + svc_maxfd); } #endif } @@ -1187,9 +1191,9 @@ check_rmtcalls(struct pollfd *pfds, int nfds) ncallbacks_found++; #ifdef DEBUG_RMTCALL if (debugging) - fprintf(stderr, -"my_svc_run: polled on forwarding fd %d, netid %s - calling handle_reply\n", - pfds[j].fd, xprt->xp_netid); + fprintf(stderr, "%s: polled on forwarding " + "fd %d, netid %s - calling handle_reply\n", + __func__, pfds[j].fd, xprt->xp_netid); #endif handle_reply(pfds[j].fd, xprt); pfds[j].revents = 0; @@ -1251,27 +1255,26 @@ handle_reply(int fd, SVCXPRT *xprt) } while (inlen < 0 && errno == EINTR); if (inlen < 0) { if (debugging) - fprintf(stderr, - "handle_reply: recvfrom returned %d, errno %d\n", inlen, errno); + fprintf(stderr, "%s: recvfrom returned %d: %m\n", + __func__, inlen); goto done; } reply_msg.acpted_rply.ar_verf = _null_auth; reply_msg.acpted_rply.ar_results.where = 0; - reply_msg.acpted_rply.ar_results.proc = (xdrproc_t) xdr_void; + reply_msg.acpted_rply.ar_results.proc = (xdrproc_t)xdr_void; xdrmem_create(&reply_xdrs, buffer, (u_int)inlen, XDR_DECODE); if (!xdr_replymsg(&reply_xdrs, &reply_msg)) { if (debugging) - (void) fprintf(stderr, - "handle_reply: xdr_replymsg failed\n"); + fprintf(stderr, "%s: xdr_replymsg failed\n", __func__); goto done; } fi = forward_find(reply_msg.rm_xid); #ifdef SVC_RUN_DEBUG if (debugging) { - fprintf(stderr, "handle_reply: reply xid: %d fi addr: %p\n", - reply_msg.rm_xid, fi); + fprintf(stderr, "%s: reply xid: %d fi addr: %p\n", + __func__, reply_msg.rm_xid, fi); } #endif if (fi == NULL) { @@ -1280,8 +1283,8 @@ handle_reply(int fd, SVCXPRT *xprt) _seterr_reply(&reply_msg, &reply_error); if (reply_error.re_status != RPC_SUCCESS) { if (debugging) - (void) fprintf(stderr, "handle_reply: %s\n", - clnt_sperrno(reply_error.re_status)); + fprintf(stderr, "%s: %s\n", __func__, + clnt_sperrno(reply_error.re_status)); send_svcsyserr(xprt, fi); goto done; } @@ -1297,31 +1300,32 @@ handle_reply(int fd, SVCXPRT *xprt) uaddr = taddr2uaddr(rpcbind_get_conf("udp"), svc_getrpccaller(xprt)); if (debugging) { - fprintf(stderr, "handle_reply: forwarding address %s to %s\n", - a.rmt_uaddr, uaddr ? uaddr : "unknown"); + fprintf(stderr, "%s: forwarding address %s to %s\n", + __func__, a.rmt_uaddr, uaddr ? uaddr : rpcbind_unknown); } free(uaddr); #endif - svc_sendreply(xprt, (xdrproc_t) xdr_rmtcall_result, (char *) &a); + svc_sendreply(xprt, (xdrproc_t)xdr_rmtcall_result, &a); done: free(buffer); if (reply_msg.rm_xid == 0) { #ifdef SVC_RUN_DEBUG if (debugging) { - fprintf(stderr, "handle_reply: NULL xid on exit!\n"); + fprintf(stderr, "%s: NULL xid on exit!\n", __func__); } #endif } else - (void) free_slot_by_xid(reply_msg.rm_xid); + (void)free_slot_by_xid(reply_msg.rm_xid); } static void -find_versions(rpcprog_t prog, char *netid, rpcvers_t *lowvp, rpcvers_t *highvp) +find_versions(rpcprog_t prog, const char *netid, rpcvers_t *lowvp, + rpcvers_t *highvp) { - register rpcblist_ptr rbl; - unsigned int lowv = 0; - unsigned int highv = 0; + rpcblist_ptr rbl; + rpcvers_t lowv = 0; + rpcvers_t highv = 0; for (rbl = list_rbl; rbl != NULL; rbl = rbl->rpcb_next) { if ((rbl->rpcb_map.r_prog != prog) || @@ -1355,8 +1359,8 @@ find_versions(rpcprog_t prog, char *netid, rpcvers_t *lowvp, rpcvers_t *highvp) static rpcblist_ptr find_service(rpcprog_t prog, rpcvers_t vers, char *netid) { - register rpcblist_ptr hit = NULL; - register rpcblist_ptr rbl; + rpcblist_ptr hit = NULL; + rpcblist_ptr rbl; for (rbl = list_rbl; rbl != NULL; rbl = rbl->rpcb_next) { if ((rbl->rpcb_map.r_prog != prog) || @@ -1380,9 +1384,9 @@ getowner(SVCXPRT *transp, char *owner, size_t ownersize) uid_t uid; if (__rpc_get_local_uid(transp, &uid) < 0) - strlcpy(owner, "unknown", ownersize); + strlcpy(owner, rpcbind_unknown, ownersize); else if (uid == 0) - strlcpy(owner, "superuser", ownersize); + strlcpy(owner, rpcbind_superuser, ownersize); else snprintf(owner, ownersize, "%d", uid); @@ -1420,9 +1424,9 @@ add_pmaplist(RPCB *arg) /* * add to END of list */ - pml = malloc(sizeof (struct pmaplist)); + pml = malloc(sizeof(*pml)); if (pml == NULL) { - (void) syslog(LOG_ERR, "rpcbind: no memory!\n"); + syslog(LOG_ERR, "%s: %m", __func__); return (1); } pml->pml_map = pmap; @@ -1434,7 +1438,7 @@ add_pmaplist(RPCB *arg) /* Attach to the end of the list */ for (fnd = list_pml; fnd->pml_next; fnd = fnd->pml_next) - ; + continue; fnd->pml_next = pml; } return (0); diff --git a/usr.sbin/rpcbind/rpcbind.8 b/usr.sbin/rpcbind/rpcbind.8 index 36c7a8da9984..0132c8f6a5d0 100644 --- a/usr.sbin/rpcbind/rpcbind.8 +++ b/usr.sbin/rpcbind/rpcbind.8 @@ -1,6 +1,6 @@ .\" Copyright 1989 AT&T .\" Copyright 1991 Sun Microsystems, Inc. -.Dd July 11, 2024 +.Dd May 30, 2025 .Dt RPCBIND 8 .Os .Sh NAME @@ -8,7 +8,7 @@ .Nd universal addresses to RPC program number mapper .Sh SYNOPSIS .Nm -.Op Fl 6adIiLlNswW +.Op Fl 6adIiLlNPswW .Op Fl h Ar bindip .Sh DESCRIPTION The @@ -135,6 +135,10 @@ Run in foreground mode. In this mode, .Nm will not fork when it starts. +.It Fl P +Specify alternative location of a file where main process PID will be stored. +The default location is +.Pa /var/run/rpcbind.pid . .It Fl s Cause .Nm diff --git a/usr.sbin/rpcbind/rpcbind.c b/usr.sbin/rpcbind/rpcbind.c index 1397a0222396..faa24a4415e2 100644 --- a/usr.sbin/rpcbind/rpcbind.c +++ b/usr.sbin/rpcbind/rpcbind.c @@ -39,34 +39,39 @@ * */ -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/errno.h> -#include <sys/time.h> +#include <sys/param.h> +#include <sys/linker.h> +#include <sys/module.h> #include <sys/resource.h> -#include <sys/wait.h> #include <sys/signal.h> #include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> #include <sys/un.h> +#include <sys/wait.h> + #include <rpc/rpc.h> #include <rpc/rpc_com.h> #ifdef PORTMAP #include <netinet/in.h> #endif #include <arpa/inet.h> + #include <assert.h> +#include <err.h> +#include <errno.h> #include <fcntl.h> +#include <libutil.h> +#include <netconfig.h> #include <netdb.h> +#include <pwd.h> #include <stdbool.h> #include <stdio.h> -#include <netconfig.h> #include <stdlib.h> -#include <unistd.h> -#include <syslog.h> -#include <err.h> -#include <pwd.h> #include <string.h> -#include <errno.h> +#include <syslog.h> +#include <unistd.h> + #include "rpcbind.h" /* Global variables */ @@ -82,6 +87,11 @@ int rpcbindlockfd; #define RPCBINDDLOCK "/var/run/rpcbind.lock" +#define DEFAULT_PIDFILE "/var/run/rpcbind.pid" + +char *pidfile_path = DEFAULT_PIDFILE; +struct pidfh *pidfh = NULL; + static int runasdaemon = 0; int insecure = 0; int oldstyle_local = 0; @@ -101,36 +111,38 @@ static int terminate_wfd; #ifdef WARMSTART /* Local Variable */ -static int warmstart = 0; /* Grab an old copy of registrations. */ +static int warmstart = 0; /* Grab an old copy of registrations */ #endif #ifdef PORTMAP struct pmaplist *list_pml; /* A list of version 2 rpcbind services */ -char *udptrans; /* Name of UDP transport */ -char *tcptrans; /* Name of TCP transport */ -char *udp_uaddr; /* Universal UDP address */ -char *tcp_uaddr; /* Universal TCP address */ +const char *udptrans; /* Name of UDP transport */ +const char *tcptrans; /* Name of TCP transport */ +const char *udp_uaddr; /* Universal UDP address */ +const char *tcp_uaddr; /* Universal TCP address */ #endif -static char servname[] = "rpcbind"; -static char superuser[] = "superuser"; -static char nlname[] = "netlink"; +static const char servname[] = "rpcbind"; +const char rpcbind_superuser[] = "superuser"; +const char rpcbind_unknown[] = "unknown"; +static const char nlname[] = "netlink"; -static struct netconfig netlink_nconf = { - .nc_netid = nlname, +static const struct netconfig netlink_nconf = { + .nc_netid = __UNCONST(nlname), .nc_semantics = NC_TPI_CLTS, }; -static struct t_bind netlink_taddr = { +static const struct t_bind netlink_taddr = { .addr = { .maxlen = sizeof(nlname), .len = sizeof(nlname), - .buf = nlname, + .buf = __UNCONST(nlname), }, }; -static int init_transport(struct netconfig *); -static void rbllist_add(rpcprog_t, rpcvers_t, struct netconfig *, - struct netbuf *); +static int init_transport(const struct netconfig *); +static void rbllist_add(rpcprog_t, rpcvers_t, const struct netconfig *, + struct netbuf *); +static void cleanup_pidfile(void); static void terminate(int); static void parseargs(int, char *[]); static void update_bound_sa(void); @@ -148,14 +160,26 @@ main(int argc, char *argv[]) update_bound_sa(); + /* Ensure krpc is loaded */ + if (modfind("krpc") < 0 && kldload("krpc") < 0) { + warn("failed to load krpc module, " + "rpcbind services for kernel disabled"); + } + /* Check that another rpcbind isn't already running. */ - if ((rpcbindlockfd = (open(RPCBINDDLOCK, - O_RDONLY|O_CREAT, 0444))) == -1) + if ((rpcbindlockfd = open(RPCBINDDLOCK, O_RDONLY|O_CREAT, 0444)) < 0) err(1, "%s", RPCBINDDLOCK); - if(flock(rpcbindlockfd, LOCK_EX|LOCK_NB) == -1 && errno == EWOULDBLOCK) + if (flock(rpcbindlockfd, LOCK_EX|LOCK_NB) != 0 && errno == EWOULDBLOCK) errx(1, "another rpcbind is already running. Aborting"); + if (pidfile_path != NULL) { + pidfh = pidfile_open(pidfile_path, 0600, NULL); + if (pidfh == NULL) + warn("cannot open pid file"); + atexit(cleanup_pidfile); + } + getrlimit(RLIMIT_NOFILE, &rl); if (rl.rlim_cur < 128) { if (rl.rlim_max <= 128) @@ -169,7 +193,7 @@ main(int argc, char *argv[]) fprintf(stderr, "Sorry. You are not superuser\n"); exit(1); } - nc_handle = setnetconfig(); /* open netconfig file */ + nc_handle = setnetconfig(); /* open netconfig file */ if (nc_handle == NULL) { syslog(LOG_ERR, "could not read /etc/netconfig"); exit(1); @@ -193,7 +217,7 @@ main(int argc, char *argv[]) while ((nconf = getnetconfig(nc_handle))) { if (nconf->nc_flag & NC_VISIBLE) { - if (ipv6_only == 1 && strcmp(nconf->nc_protofmly, + if (ipv6_only == 1 && strcmp(nconf->nc_protofmly, "inet") == 0) { /* DO NOTHING */ } else @@ -241,6 +265,9 @@ main(int argc, char *argv[]) err(1, "fork failed"); } + if (pidfh != NULL && pidfile_write(pidfh) != 0) + syslog(LOG_ERR, "pidfile_write(): %m"); + if (runasdaemon) { struct passwd *p; @@ -270,7 +297,7 @@ main(int argc, char *argv[]) * Returns 0 if succeeds, else fails */ static int -init_transport(struct netconfig *nconf) +init_transport(const struct netconfig *nconf) { int fd = -1; struct t_bind taddr; @@ -282,8 +309,8 @@ init_transport(struct netconfig *nconf) int addrlen; int nhostsbak; int bound; - struct sockaddr *sa; u_int32_t host_addr[4]; /* IPv4 or IPv6 */ + struct sockaddr *sa; struct sockaddr_un sun; mode_t oldmask; bool local, netlink; @@ -295,17 +322,17 @@ init_transport(struct netconfig *nconf) if ((nconf->nc_semantics != NC_TPI_CLTS) && (nconf->nc_semantics != NC_TPI_COTS) && (nconf->nc_semantics != NC_TPI_COTS_ORD)) - return (1); /* not my type */ -#ifdef ND_DEBUG + return (1); /* not my type */ +#ifdef RPCBIND_DEBUG if (debugging) { - int i; - char **s; - - (void)fprintf(stderr, "%s: %ld lookup routines :\n", - nconf->nc_netid, nconf->nc_nlookups); - for (i = 0, s = nconf->nc_lookups; i < nconf->nc_nlookups; - i++, s++) - fprintf(stderr, "[%d] - %s\n", i, *s); + unsigned int i; + char **s; + + (void)fprintf(stderr, "%s: %ld lookup routines :\n", + nconf->nc_netid, nconf->nc_nlookups); + for (i = 0, s = nconf->nc_lookups; i < nconf->nc_nlookups; + i++, s++) + (void)fprintf(stderr, "[%d] - %s\n", i, *s); } #endif @@ -313,224 +340,223 @@ init_transport(struct netconfig *nconf) * XXX - using RPC library internal functions. */ if (local) { - /* - * For other transports we call this later, for each socket we - * like to bind. - */ - if ((fd = __rpc_nconf2fd(nconf)) < 0) { - int non_fatal = 0; - if (errno == EAFNOSUPPORT) - non_fatal = 1; - syslog(non_fatal?LOG_DEBUG:LOG_ERR, "cannot create socket for %s", - nconf->nc_netid); - return (1); - } + /* + * For other transports we call this later, for each socket we + * like to bind. + */ + if ((fd = __rpc_nconf2fd(nconf)) < 0) { + syslog(errno == EAFNOSUPPORT ? LOG_DEBUG : LOG_ERR, + "cannot create socket for %s", + nconf->nc_netid); + return (1); + } } if (!__rpc_nconf2sockinfo(nconf, &si)) { - syslog(LOG_ERR, "cannot get information for %s", - nconf->nc_netid); - return (1); + syslog(LOG_ERR, "cannot get information for %s", + nconf->nc_netid); + return (1); } if (local) { - memset(&sun, 0, sizeof sun); - sun.sun_family = AF_LOCAL; - unlink(_PATH_RPCBINDSOCK); - strcpy(sun.sun_path, _PATH_RPCBINDSOCK); - sun.sun_len = SUN_LEN(&sun); - addrlen = sizeof (struct sockaddr_un); - sa = (struct sockaddr *)&sun; + memset(&sun, 0, sizeof sun); + sun.sun_family = AF_LOCAL; + unlink(_PATH_RPCBINDSOCK); + strcpy(sun.sun_path, _PATH_RPCBINDSOCK); + sun.sun_len = SUN_LEN(&sun); + addrlen = sizeof (struct sockaddr_un); + sa = (struct sockaddr *)&sun; } else if (!netlink) { - /* Get rpcbind's address on this transport */ - - memset(&hints, 0, sizeof hints); - hints.ai_flags = AI_PASSIVE; - hints.ai_family = si.si_af; - hints.ai_socktype = si.si_socktype; - hints.ai_protocol = si.si_proto; + /* Get rpcbind's address on this transport */ + memset(&hints, 0, sizeof hints); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = si.si_af; + hints.ai_socktype = si.si_socktype; + hints.ai_protocol = si.si_proto; } if (!local && !netlink) { - /* - * If no hosts were specified, just bind to INADDR_ANY. - * Otherwise make sure 127.0.0.1 is added to the list. - */ - nhostsbak = nhosts + 1; - hosts = realloc(hosts, nhostsbak * sizeof(char *)); - if (nhostsbak == 1) - hosts[0] = "*"; - else { - if (hints.ai_family == AF_INET && nobind_localhost == 0) { - hosts[nhostsbak - 1] = "127.0.0.1"; - } else if (hints.ai_family == AF_INET6 && nobind_localhost == 0) { - hosts[nhostsbak - 1] = "::1"; - } else - return 1; - } - - /* - * Bind to specific IPs if asked to - */ - bound = 0; - while (nhostsbak > 0) { - --nhostsbak; /* - * XXX - using RPC library internal functions. + * If no hosts were specified, just bind to INADDR_ANY. + * Otherwise make sure 127.0.0.1 is added to the list. */ - if ((fd = __rpc_nconf2fd(nconf)) < 0) { - int non_fatal = 0; - if (errno == EAFNOSUPPORT && - nconf->nc_semantics != NC_TPI_CLTS) - non_fatal = 1; - syslog(non_fatal ? LOG_DEBUG : LOG_ERR, - "cannot create socket for %s", nconf->nc_netid); - return (1); + nhostsbak = nhosts + 1; + hosts = realloc(hosts, nhostsbak * sizeof(char *)); + if (nhostsbak == 1) + hosts[0] = "*"; + else { + if (hints.ai_family == AF_INET && + !nobind_localhost) { + hosts[nhostsbak - 1] = "127.0.0.1"; + } else if (hints.ai_family == AF_INET6 && + !nobind_localhost) { + hosts[nhostsbak - 1] = "::1"; + } else + return 1; } - switch (hints.ai_family) { - case AF_INET: - if (inet_pton(AF_INET, hosts[nhostsbak], - host_addr) == 1) { - hints.ai_flags &= AI_NUMERICHOST; - } else { + + /* + * Bind to specific IPs if asked to + */ + bound = 0; + while (nhostsbak > 0) { + --nhostsbak; /* - * Skip if we have an AF_INET6 address. + * XXX - using RPC library internal functions. */ - if (inet_pton(AF_INET6, - hosts[nhostsbak], host_addr) == 1) { - close(fd); - continue; + if ((fd = __rpc_nconf2fd(nconf)) < 0) { + int non_fatal = 0; + if (errno == EAFNOSUPPORT && + nconf->nc_semantics != NC_TPI_CLTS) + non_fatal = 1; + syslog(non_fatal ? LOG_DEBUG : LOG_ERR, + "cannot create socket for %s", nconf->nc_netid); + return (1); } - } - break; - case AF_INET6: - if (inet_pton(AF_INET6, hosts[nhostsbak], - host_addr) == 1) { - hints.ai_flags &= AI_NUMERICHOST; - } else { + switch (hints.ai_family) { + case AF_INET: + if (inet_pton(AF_INET, hosts[nhostsbak], + host_addr) == 1) { + hints.ai_flags &= AI_NUMERICHOST; + } else { + /* + * Skip if we have an AF_INET6 address. + */ + if (inet_pton(AF_INET6, + hosts[nhostsbak], host_addr) == 1) { + close(fd); + continue; + } + } + break; + case AF_INET6: + if (inet_pton(AF_INET6, hosts[nhostsbak], + host_addr) == 1) { + hints.ai_flags &= AI_NUMERICHOST; + } else { + /* + * Skip if we have an AF_INET address. + */ + if (inet_pton(AF_INET, hosts[nhostsbak], + host_addr) == 1) { + close(fd); + continue; + } + } + if (setsockopt(fd, IPPROTO_IPV6, + IPV6_V6ONLY, &on, sizeof on) < 0) { + syslog(LOG_ERR, + "can't set v6-only binding for " + "ipv6 socket: %m"); + continue; + } + break; + default: + break; + } + /* - * Skip if we have an AF_INET address. + * If no hosts were specified, just bind to INADDR_ANY */ - if (inet_pton(AF_INET, hosts[nhostsbak], - host_addr) == 1) { - close(fd); + if (strcmp("*", hosts[nhostsbak]) == 0) + hosts[nhostsbak] = NULL; + if ((aicode = getaddrinfo(hosts[nhostsbak], servname, &hints, + &res)) != 0) { + syslog(LOG_ERR, "cannot get local address for %s: %s", + nconf->nc_netid, gai_strerror(aicode)); continue; } - } - if (setsockopt(fd, IPPROTO_IPV6, - IPV6_V6ONLY, &on, sizeof on) < 0) { - syslog(LOG_ERR, - "can't set v6-only binding for " - "ipv6 socket: %m"); - continue; - } - break; - default: - break; - } + addrlen = res->ai_addrlen; + sa = (struct sockaddr *)res->ai_addr; + oldmask = umask(S_IXUSR|S_IXGRP|S_IXOTH); + if (bind(fd, sa, addrlen) != 0) { + syslog(LOG_ERR, "cannot bind %s on %s: %m", + (hosts[nhostsbak] == NULL) ? "*" : + hosts[nhostsbak], nconf->nc_netid); + if (res != NULL) + freeaddrinfo(res); + continue; + } else + bound = 1; + (void)umask(oldmask); + + /* Copy the address */ + taddr.addr.len = taddr.addr.maxlen = addrlen; + taddr.addr.buf = malloc(addrlen); + if (taddr.addr.buf == NULL) { + syslog(LOG_ERR, + "cannot allocate memory for %s address", + nconf->nc_netid); + if (res != NULL) + freeaddrinfo(res); + return 1; + } + memcpy(taddr.addr.buf, sa, addrlen); +#ifdef RPCBIND_DEBUG + if (debugging) { + /* + * for debugging print out our universal + * address + */ + char *uaddr; + struct netbuf nb; + + nb.buf = sa; + nb.len = nb.maxlen = sa->sa_len; + uaddr = taddr2uaddr(nconf, &nb); + (void)fprintf(stderr, + "rpcbind : my address is %s\n", uaddr); + (void)free(uaddr); + } +#endif - /* - * If no hosts were specified, just bind to INADDR_ANY - */ - if (strcmp("*", hosts[nhostsbak]) == 0) - hosts[nhostsbak] = NULL; - if ((aicode = getaddrinfo(hosts[nhostsbak], servname, &hints, - &res)) != 0) { - syslog(LOG_ERR, "cannot get local address for %s: %s", - nconf->nc_netid, gai_strerror(aicode)); - continue; + if (nconf->nc_semantics != NC_TPI_CLTS) + listen(fd, SOMAXCONN); + + my_xprt = (SVCXPRT *)svc_tli_create(fd, nconf, &taddr, + RPC_MAXDATASIZE, RPC_MAXDATASIZE); } - addrlen = res->ai_addrlen; - sa = (struct sockaddr *)res->ai_addr; + } else if (local) { oldmask = umask(S_IXUSR|S_IXGRP|S_IXOTH); - if (bind(fd, sa, addrlen) != 0) { - syslog(LOG_ERR, "cannot bind %s on %s: %m", - (hosts[nhostsbak] == NULL) ? "*" : - hosts[nhostsbak], nconf->nc_netid); - if (res != NULL) - freeaddrinfo(res); - continue; - } else - bound = 1; - (void)umask(oldmask); + if (bind(fd, sa, addrlen) < 0) { + syslog(LOG_ERR, "cannot bind %s: %m", nconf->nc_netid); + if (res != NULL) + freeaddrinfo(res); + return 1; + } + (void) umask(oldmask); /* Copy the address */ taddr.addr.len = taddr.addr.maxlen = addrlen; taddr.addr.buf = malloc(addrlen); if (taddr.addr.buf == NULL) { - syslog(LOG_ERR, - "cannot allocate memory for %s address", - nconf->nc_netid); - if (res != NULL) - freeaddrinfo(res); - return 1; + syslog(LOG_ERR, "cannot allocate memory for %s address", + nconf->nc_netid); + if (res != NULL) + freeaddrinfo(res); + return 1; } memcpy(taddr.addr.buf, sa, addrlen); -#ifdef ND_DEBUG +#ifdef RPCBIND_DEBUG if (debugging) { - /* - * for debugging print out our universal - * address - */ - char *uaddr; - struct netbuf nb; - - nb.buf = sa; - nb.len = nb.maxlen = sa->sa_len; - uaddr = taddr2uaddr(nconf, &nb); - (void)fprintf(stderr, - "rpcbind : my address is %s\n", uaddr); - (void)free(uaddr); - } + /* for debugging print out our universal address */ + char *uaddr; + struct netbuf nb; + + nb.buf = sa; + nb.len = nb.maxlen = sa->sa_len; + uaddr = taddr2uaddr(nconf, &nb); + (void)fprintf(stderr, "rpcbind : my address is %s\n", + uaddr); + (void)free(uaddr); + } #endif if (nconf->nc_semantics != NC_TPI_CLTS) - listen(fd, SOMAXCONN); + listen(fd, SOMAXCONN); my_xprt = (SVCXPRT *)svc_tli_create(fd, nconf, &taddr, RPC_MAXDATASIZE, RPC_MAXDATASIZE); - } - } else if (local) { - oldmask = umask(S_IXUSR|S_IXGRP|S_IXOTH); - if (bind(fd, sa, addrlen) < 0) { - syslog(LOG_ERR, "cannot bind %s: %m", nconf->nc_netid); - if (res != NULL) - freeaddrinfo(res); - return 1; - } - (void) umask(oldmask); - - /* Copy the address */ - taddr.addr.len = taddr.addr.maxlen = addrlen; - taddr.addr.buf = malloc(addrlen); - if (taddr.addr.buf == NULL) { - syslog(LOG_ERR, "cannot allocate memory for %s address", - nconf->nc_netid); - if (res != NULL) - freeaddrinfo(res); - return 1; - } - memcpy(taddr.addr.buf, sa, addrlen); -#ifdef ND_DEBUG - if (debugging) { - /* for debugging print out our universal address */ - char *uaddr; - struct netbuf nb; - - nb.buf = sa; - nb.len = nb.maxlen = sa->sa_len; - uaddr = taddr2uaddr(nconf, &nb); - (void) fprintf(stderr, "rpcbind : my address is %s\n", - uaddr); - (void) free(uaddr); - } -#endif - - if (nconf->nc_semantics != NC_TPI_CLTS) - listen(fd, SOMAXCONN); - - my_xprt = (SVCXPRT *)svc_tli_create(fd, nconf, &taddr, - RPC_MAXDATASIZE, RPC_MAXDATASIZE); } else { assert(netlink); taddr = netlink_taddr; @@ -555,7 +581,7 @@ init_transport(struct netconfig *nconf) if (!svc_register(my_xprt, PMAPPROG, PMAPVERS, pmap_service, 0)) { syslog(LOG_ERR, "could not register on %s", - nconf->nc_netid); + nconf->nc_netid); goto error; } pml = malloc(sizeof (struct pmaplist)); @@ -571,7 +597,7 @@ init_transport(struct netconfig *nconf) free(pml); pml = NULL; syslog(LOG_ERR, - "cannot have more than one TCP transport"); + "cannot have more than one TCP transport"); goto error; } tcptrans = strdup(nconf->nc_netid); @@ -583,7 +609,7 @@ init_transport(struct netconfig *nconf) } else if (strcmp(nconf->nc_proto, NC_UDP) == 0) { if (udptrans[0]) { syslog(LOG_ERR, - "cannot have more than one UDP transport"); + "cannot have more than one UDP transport"); goto error; } udptrans = strdup(nconf->nc_netid); @@ -600,9 +626,9 @@ init_transport(struct netconfig *nconf) list_pml = pml; /* Add version 3 information */ - pml = malloc(sizeof (struct pmaplist)); + pml = malloc(sizeof(*pml)); if (pml == NULL) { - syslog(LOG_ERR, "no memory!"); + syslog(LOG_ERR, "%m"); exit(1); } pml->pml_map = list_pml->pml_map; @@ -611,9 +637,9 @@ init_transport(struct netconfig *nconf) list_pml = pml; /* Add version 4 information */ - pml = malloc (sizeof (struct pmaplist)); + pml = malloc(sizeof(*pml)); if (pml == NULL) { - syslog(LOG_ERR, "no memory!"); + syslog(LOG_ERR, "%m"); exit(1); } pml->pml_map = list_pml->pml_map; @@ -629,7 +655,7 @@ init_transport(struct netconfig *nconf) /* version 3 registration */ if (!svc_reg(my_xprt, RPCBPROG, RPCBVERS, rpcb_service_3, NULL)) { syslog(LOG_ERR, "could not register %s version 3", - nconf->nc_netid); + nconf->nc_netid); goto error; } rbllist_add(RPCBPROG, RPCBVERS, nconf, &taddr.addr); @@ -637,24 +663,24 @@ init_transport(struct netconfig *nconf) /* version 4 registration */ if (!svc_reg(my_xprt, RPCBPROG, RPCBVERS4, rpcb_service_4, NULL)) { syslog(LOG_ERR, "could not register %s version 4", - nconf->nc_netid); + nconf->nc_netid); goto error; } rbllist_add(RPCBPROG, RPCBVERS4, nconf, &taddr.addr); /* decide if bound checking works for this transport */ status = add_bndlist(nconf, &taddr.addr); -#ifdef BIND_DEBUG +#ifdef RPCBIND_DEBUG if (debugging) { if (status < 0) { fprintf(stderr, "Error in finding bind status for %s\n", - nconf->nc_netid); + nconf->nc_netid); } else if (status == 0) { fprintf(stderr, "check binding for %s\n", - nconf->nc_netid); + nconf->nc_netid); } else if (status > 0) { fprintf(stderr, "No check binding for %s\n", - nconf->nc_netid); + nconf->nc_netid); } } #endif @@ -664,15 +690,15 @@ init_transport(struct netconfig *nconf) if (!netlink && nconf->nc_semantics == NC_TPI_CLTS) { status = create_rmtcall_fd(nconf); -#ifdef BIND_DEBUG +#ifdef RPCBIND_DEBUG if (debugging) { if (status < 0) { fprintf(stderr, "Could not create rmtcall fd for %s\n", - nconf->nc_netid); + nconf->nc_netid); } else { fprintf(stderr, "rmtcall fd for %s is %d\n", - nconf->nc_netid, status); + nconf->nc_netid, status); } } #endif @@ -735,13 +761,13 @@ listen_addr(const struct sockaddr *sa) continue; switch (sa->sa_family) { case AF_INET: - if (memcmp(&SA2SINADDR(sa), &SA2SINADDR(bound_sa[i]), + if (memcmp(&SA2SINADDR(sa), &SA2SINADDR(bound_sa[i]), sizeof(struct in_addr)) == 0) return (1); break; #ifdef INET6 case AF_INET6: - if (memcmp(&SA2SIN6ADDR(sa), &SA2SIN6ADDR(bound_sa[i]), + if (memcmp(&SA2SIN6ADDR(sa), &SA2SIN6ADDR(bound_sa[i]), sizeof(struct in6_addr)) == 0) return (1); break; @@ -754,8 +780,8 @@ listen_addr(const struct sockaddr *sa) } static void -rbllist_add(rpcprog_t prog, rpcvers_t vers, struct netconfig *nconf, - struct netbuf *addr) +rbllist_add(rpcprog_t prog, rpcvers_t vers, const struct netconfig *nconf, + struct netbuf *addr) { rpcblist_ptr rbl; @@ -769,12 +795,22 @@ rbllist_add(rpcprog_t prog, rpcvers_t vers, struct netconfig *nconf, rbl->rpcb_map.r_vers = vers; rbl->rpcb_map.r_netid = strdup(nconf->nc_netid); rbl->rpcb_map.r_addr = taddr2uaddr(nconf, addr); - rbl->rpcb_map.r_owner = strdup(superuser); + rbl->rpcb_map.r_owner = strdup(rpcbind_superuser); rbl->rpcb_next = list_rbl; /* Attach to global list */ list_rbl = rbl; } /* + * atexit callback for pidfh cleanup + */ +static void +cleanup_pidfile(void) +{ + if (pidfh != NULL) + pidfile_remove(pidfh); +} + +/* * Catch the signal and die */ static void @@ -785,8 +821,15 @@ terminate(int signum) doterminate = signum; wr = write(terminate_wfd, &c, 1); - if (wr < 1) + if (wr < 1) { + /* + * The call to cleanup_pidfile should be async-signal safe. + * pidfile_remove calls fstat and funlinkat system calls, and + * we are exiting immediately. + */ + cleanup_pidfile(); _exit(2); + } } void @@ -814,7 +857,7 @@ parseargs(int argc, char *argv[]) #else #define WRAPOP "" #endif - while ((c = getopt(argc, argv, "6adh:IiLlNs" WRAPOP WSOP)) != -1) { + while ((c = getopt(argc, argv, "6adh:IiLlNP:s" WRAPOP WSOP)) != -1) { switch (c) { case '6': ipv6_only = 1; @@ -853,6 +896,9 @@ parseargs(int argc, char *argv[]) case 's': runasdaemon = 1; break; + case 'P': + pidfile_path = strdup(optarg); + break; #ifdef LIBWRAP case 'W': libwrap = 1; @@ -865,7 +911,7 @@ parseargs(int argc, char *argv[]) #endif default: /* error */ fprintf(stderr, - "usage: rpcbind [-6adIiLls%s%s] [-h bindip]\n", + "usage: rpcbind [-6adIiLlNPs%s%s] [-h bindip]\n", WRAPOP, WSOP); exit (1); } @@ -882,9 +928,9 @@ void reap(int dummy __unused) { int save_errno = errno; - + while (wait3(NULL, WNOHANG, NULL) > 0) - ; + ; errno = save_errno; } diff --git a/usr.sbin/rpcbind/rpcbind.h b/usr.sbin/rpcbind/rpcbind.h index 28addac494ce..dd49b27efe0d 100644 --- a/usr.sbin/rpcbind/rpcbind.h +++ b/usr.sbin/rpcbind/rpcbind.h @@ -80,14 +80,17 @@ extern int rpcbindlockfd; #ifdef PORTMAP extern struct pmaplist *list_pml; /* A list of version 2 rpcbind services */ -extern char *udptrans; /* Name of UDP transport */ -extern char *tcptrans; /* Name of TCP transport */ -extern char *udp_uaddr; /* Universal UDP address */ -extern char *tcp_uaddr; /* Universal TCP address */ +extern const char *udptrans; /* Name of UDP transport */ +extern const char *tcptrans; /* Name of TCP transport */ +extern const char *udp_uaddr; /* Universal UDP address */ +extern const char *tcp_uaddr; /* Universal TCP address */ #endif -int add_bndlist(struct netconfig *, struct netbuf *); -bool_t is_bound(char *, char *); +extern const char rpcbind_superuser[]; +extern const char rpcbind_unknown[]; + +int add_bndlist(const struct netconfig *, struct netbuf *); +bool_t is_bound(const char *, const char *); char *mergeaddr(SVCXPRT *, char *, char *, char *); struct netconfig *rpcbind_get_conf(const char *); @@ -95,9 +98,10 @@ void rpcbs_init(void); void rpcbs_procinfo(rpcvers_t, rpcproc_t); void rpcbs_set(rpcvers_t, bool_t); void rpcbs_unset(rpcvers_t, bool_t); -void rpcbs_getaddr(rpcvers_t, rpcprog_t, rpcvers_t, char *, char *); +void rpcbs_getaddr(rpcvers_t, rpcprog_t, rpcvers_t, const char *, + const char *); void rpcbs_rmtcall(rpcvers_t, rpcproc_t, rpcprog_t, rpcvers_t, rpcproc_t, - char *, rpcblist_ptr); + char *, rpcblist_ptr); void *rpcbproc_getstat(void *, struct svc_req *, SVCXPRT *, rpcvers_t); void rpcb_service_3(struct svc_req *, SVCXPRT *); @@ -106,20 +110,16 @@ void rpcb_service_4(struct svc_req *, SVCXPRT *); /* Common functions shared between versions */ void *rpcbproc_set_com(void *, struct svc_req *, SVCXPRT *, rpcvers_t); void *rpcbproc_unset_com(void *, struct svc_req *, SVCXPRT *, rpcvers_t); -bool_t map_set(RPCB *, char *); -bool_t map_unset(RPCB *, char *); +bool_t map_set(RPCB *, const char *); +bool_t map_unset(RPCB *, const char *); void delete_prog(unsigned int); void *rpcbproc_getaddr_com(RPCB *, struct svc_req *, SVCXPRT *, rpcvers_t, - rpcvers_t); -void *rpcbproc_gettime_com(void *, struct svc_req *, SVCXPRT *, - rpcvers_t); -void *rpcbproc_uaddr2taddr_com(void *, struct svc_req *, - SVCXPRT *, rpcvers_t); -void *rpcbproc_taddr2uaddr_com(void *, struct svc_req *, SVCXPRT *, - rpcvers_t); -int create_rmtcall_fd(struct netconfig *); -void rpcbproc_callit_com(struct svc_req *, SVCXPRT *, rpcvers_t, - rpcvers_t); + rpcvers_t); +void *rpcbproc_gettime_com(void *, struct svc_req *, SVCXPRT *, rpcvers_t); +void *rpcbproc_uaddr2taddr_com(void *, struct svc_req *, SVCXPRT *, rpcvers_t); +void *rpcbproc_taddr2uaddr_com(void *, struct svc_req *, SVCXPRT *, rpcvers_t); +int create_rmtcall_fd(const struct netconfig *); +void rpcbproc_callit_com(struct svc_req *, SVCXPRT *, rpcvers_t, rpcvers_t); void my_svc_run(void); void rpcbind_abort(void); @@ -156,4 +156,6 @@ struct sockaddr *local_sa(int); #define SA2SIN6ADDR(sa) (SA2SIN6(sa)->sin6_addr) #endif +#define __UNCONST(a) __DECONST(void *, (a)) + #endif /* rpcbind_h */ diff --git a/usr.sbin/rpcbind/warmstart.c b/usr.sbin/rpcbind/warmstart.c index 075aebf421f1..e3c075f64a08 100644 --- a/usr.sbin/rpcbind/warmstart.c +++ b/usr.sbin/rpcbind/warmstart.c @@ -87,7 +87,7 @@ write_struct(char *filename, xdrproc_t structproc, void *list) return (FALSE); } } - (void) umask(omask); + (void)umask(omask); xdrstdio_create(&xdrs, fp, XDR_ENCODE); if (structproc(&xdrs, list) == FALSE) { @@ -143,9 +143,9 @@ error: fprintf(stderr, "rpcbind: will start from scratch\n"); void write_warmstart(void) { - (void) write_struct(RPCBFILE, (xdrproc_t)xdr_rpcblist_ptr, &list_rbl); + (void)write_struct(RPCBFILE, (xdrproc_t)xdr_rpcblist_ptr, &list_rbl); #ifdef PORTMAP - (void) write_struct(PMAPFILE, (xdrproc_t)xdr_pmaplist_ptr, &list_pml); + (void)write_struct(PMAPFILE, (xdrproc_t)xdr_pmaplist_ptr, &list_pml); #endif } @@ -166,13 +166,13 @@ read_warmstart(void) ok2 = read_struct(PMAPFILE, (xdrproc_t)xdr_pmaplist_ptr, &tmp_pmapl); #endif if (ok2 == FALSE) { - xdr_free((xdrproc_t) xdr_rpcblist_ptr, (char *)&tmp_rpcbl); + xdr_free((xdrproc_t)xdr_rpcblist_ptr, &tmp_rpcbl); return; } - xdr_free((xdrproc_t) xdr_rpcblist_ptr, (char *)&list_rbl); + xdr_free((xdrproc_t)xdr_rpcblist_ptr, &list_rbl); list_rbl = tmp_rpcbl; #ifdef PORTMAP - xdr_free((xdrproc_t) xdr_pmaplist_ptr, (char *)&list_pml); + xdr_free((xdrproc_t)xdr_pmaplist_ptr, &list_pml); list_pml = tmp_pmapl; #endif } diff --git a/usr.sbin/rwhod/rwhod.c b/usr.sbin/rwhod/rwhod.c index 237663eef74d..b99e4ea74b5a 100644 --- a/usr.sbin/rwhod/rwhod.c +++ b/usr.sbin/rwhod/rwhod.c @@ -246,12 +246,12 @@ main(int argc, char *argv[]) syslog(LOG_ERR, "bind: %m"); exit(1); } - if (setgid(unpriv_gid) != 0) { - syslog(LOG_ERR, "setgid: %m"); + if (setgroups(0, NULL) != 0) { + syslog(LOG_ERR, "setgroups: %m"); exit(1); } - if (setgroups(1, &unpriv_gid) != 0) { /* XXX BOGUS groups[0] = egid */ - syslog(LOG_ERR, "setgroups: %m"); + if (setgid(unpriv_gid) != 0) { + syslog(LOG_ERR, "setgid: %m"); exit(1); } if (setuid(unpriv_uid) != 0) { diff --git a/usr.sbin/service/Makefile b/usr.sbin/service/Makefile index 66bf0deb760b..4f7aea03ec38 100644 --- a/usr.sbin/service/Makefile +++ b/usr.sbin/service/Makefile @@ -1,4 +1,4 @@ -PACKAGE= runtime +PACKAGE= rc SCRIPTS=service.sh MAN= service.8 diff --git a/usr.sbin/service/service.8 b/usr.sbin/service/service.8 index 380fbc8c1269..3858e608b785 100644 --- a/usr.sbin/service/service.8 +++ b/usr.sbin/service/service.8 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd January 29, 2024 +.Dd August 2, 2025 .Dt SERVICE 8 .Os .Sh NAME @@ -34,6 +34,7 @@ .Fl e .Nm .Op Fl j Ar jail +.Op Fl q .Fl R .Nm .Op Fl j Ar jail @@ -45,10 +46,10 @@ .Fl r .Nm .Op Fl j Ar jail -.Op Fl v +.Op Fl dqv .Op Fl E Ar var=value .Ar script -.Ar command +.Op Ar command .Sh DESCRIPTION The .Nm @@ -73,6 +74,8 @@ scripts, see .Pp The options are as follows: .Bl -tag -width F1 +.It Fl d +Enable debugging. .It Fl E Ar var=value Set the environment variable .Ar var @@ -105,6 +108,9 @@ this is usually .Pa /usr/local/etc/rc.d . All files will be listed whether they are an actual rc.d script or not. +.It Fl q +Be quiet, redirecting output to +.Pa /dev/null . .It Fl R Restart all enabled local services. .It Fl r @@ -187,7 +193,7 @@ complete service 'c/-/(e l r v)/' 'p/1/`service -l`/' \e .Ed .Pp The following programmable completion entry can be used in -.Xr bash 1 +.Xr bash 1 Pq Pa ports/shells/bash for the names of the rc.d scripts: .Bd -literal -offset indent _service () { diff --git a/usr.sbin/service/service.sh b/usr.sbin/service/service.sh index 65aa18181101..9387a49051d6 100755 --- a/usr.sbin/service/service.sh +++ b/usr.sbin/service/service.sh @@ -27,34 +27,39 @@ # SUCH DAMAGE. . /etc/rc.subr -load_rc_config 'XXX' +load_rc_config usage () { echo '' echo 'Usage:' echo "${0##*/} [-j <jail name or id>] -e" - echo "${0##*/} [-j <jail name or id>] -R" - echo "${0##*/} [-j <jail name or id>] [-v] -l | -r" - echo "${0##*/} [-j <jail name or id>] [-v] [-E var=value] <rc.d script> start|stop|etc." + echo "${0##*/} [-j <jail name or id>] [-q] -R" + echo "${0##*/} [-j <jail name or id>] [-v] -l" + echo "${0##*/} [-j <jail name or id>] [-v] -r" + 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 "-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' echo "-R Stop and start enabled $local_startup services" echo "-l List all scripts in /etc/rc.d and $local_startup" echo '-r Show the results of boot time rcorder' + echo '-q quiet' echo '-v Verbose' echo '' } -while getopts 'j:E:ehlrRv' COMMAND_LINE_ARGUMENT ; do +while getopts 'dE:ehj:lqrRv' COMMAND_LINE_ARGUMENT ; do case "${COMMAND_LINE_ARGUMENT}" in - j) JAIL="${OPTARG}" ;; + d) DEBUG=dopt ;; E) VARS="${VARS} ${OPTARG}" ;; e) ENABLED=eopt ;; h) usage ; exit 0 ;; + j) JAIL="${OPTARG}" ;; l) LIST=lopt ;; + q) QUIET=qopt ;; r) RCORDER=ropt ;; R) RESTART=Ropt ;; v) VERBOSE=vopt ;; @@ -69,6 +74,7 @@ if [ -n "${JAIL}" ]; then args="" [ -n "${ENABLED}" ] && args="${args} -e" [ -n "${LIST}" ] && args="${args} -l" + [ -n "${QUIET}" ] && args="${args} -q" [ -n "${RCORDER}" ] && args="${args} -r" [ -n "${RESTART}" ] && args="${args} -R" [ -n "${VERBOSE}" ] && args="${args} -v" @@ -82,6 +88,10 @@ if [ -n "${JAIL}" ]; then exit $? fi +if [ -n "$DEBUG" ]; then + VARS="${VARS} rc_debug=yes" +fi + if [ -n "$RESTART" ]; then skip="-s nostart" if [ `/sbin/sysctl -n security.jail.jailed` -eq 1 ]; then @@ -100,14 +110,22 @@ if [ -n "$RESTART" ]; then if [ -n "$rcvar" ]; then load_rc_config_var ${name} ${rcvar} fi - checkyesno $rcvar 2>/dev/null && run_rc_script ${file} stop + if [ -n "$QUIET" ]; then + checkyesno $rcvar 2>/dev/null && run_rc_script ${file} stop >/dev/null 2>&1 + else + checkyesno $rcvar 2>/dev/null && run_rc_script ${file} stop + fi fi done for file in $files; do if grep -q ^rcvar $file; then eval `grep ^name= $file` eval `grep ^rcvar $file` - checkyesno $rcvar 2>/dev/null && run_rc_script ${file} start + if [ -n "$QUIET" ]; then + checkyesno $rcvar 2>/dev/null && run_rc_script ${file} start >/dev/null 2>&1 + else + checkyesno $rcvar 2>/dev/null && run_rc_script ${file} start + fi fi done @@ -162,7 +180,7 @@ if [ -n "$RCORDER" ]; then exit 0 fi -if [ $# -gt 1 ]; then +if [ $# -gt 0 ]; then script=$1 shift else @@ -174,7 +192,11 @@ cd / for dir in /etc/rc.d $local_startup; do if [ -x "$dir/$script" ]; then [ -n "$VERBOSE" ] && echo "$script is located in $dir" - exec env -i -L -/daemon HOME=/ PATH=/sbin:/bin:/usr/sbin:/usr/bin ${VARS} "$dir/$script" "$@" + if [ -n "$QUIET" ]; then + exec /usr/bin/env -i -L -/daemon HOME=/ PATH=/sbin:/bin:/usr/sbin:/usr/bin ${VARS} "$dir/$script" "$@" > /dev/null 2>&1 + else + exec /usr/bin/env -i -L -/daemon HOME=/ PATH=/sbin:/bin:/usr/sbin:/usr/bin ${VARS} "$dir/$script" "$@" + fi fi done diff --git a/usr.sbin/services_mkdb/services b/usr.sbin/services_mkdb/services index 4a5b6863d92d..c5f950831767 100644 --- a/usr.sbin/services_mkdb/services +++ b/usr.sbin/services_mkdb/services @@ -893,7 +893,7 @@ biff 512/udp comsat #used by mail system to notify users # processes on the same machine login 513/tcp #remote login a la telnet; # automatic authentication performed -# based on priviledged port numbers +# based on privileged port numbers # and distributed data bases which # identify "authentication domains" who 513/udp whod #maintains data bases showing who's diff --git a/usr.sbin/sesutil/sesutil.8 b/usr.sbin/sesutil/sesutil.8 index 664dcab593e9..d4960b3ec6bf 100644 --- a/usr.sbin/sesutil/sesutil.8 +++ b/usr.sbin/sesutil/sesutil.8 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 5, 2022 +.Dd July 16, 2025 .Dt SESUTIL 8 .Os .Sh NAME @@ -129,7 +129,7 @@ Generate output via .Xr libxo 3 in a selection of different human and machine readable formats. See -.Xr xo_parse_args 3 +.Xr xo_options 7 .El .Sh EXAMPLES Turn off all locate LEDs: @@ -146,7 +146,7 @@ Turn on the fault LED for a drive bay not associated with a device: .Dl Nm Cm fault -u /dev/ses2 7 on .Sh SEE ALSO .Xr libxo 3 , -.Xr xo_parse_args 3 , +.Xr xo_options 7 , .Xr ses 4 .Sh HISTORY The diff --git a/usr.sbin/sndctl/Makefile b/usr.sbin/sndctl/Makefile new file mode 100644 index 000000000000..c1830413f931 --- /dev/null +++ b/usr.sbin/sndctl/Makefile @@ -0,0 +1,8 @@ +.include <src.opts.mk> + +PROG= sndctl +SRCS= ${PROG}.c +MAN= ${PROG}.8 +LDFLAGS+= -lnv -lmixer + +.include <bsd.prog.mk> diff --git a/usr.sbin/sndctl/sndctl.8 b/usr.sbin/sndctl/sndctl.8 new file mode 100644 index 000000000000..4c3810f3c16b --- /dev/null +++ b/usr.sbin/sndctl/sndctl.8 @@ -0,0 +1,187 @@ +.\"- +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2024-2025 The FreeBSD Foundation +.\" +.\" Portions of this software were developed by Christos Margiolis +.\" <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation. +.\" +.\" 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. +.\" +.Dd May 5, 2025 +.Dt SNDCTL 8 +.Os +.Sh NAME +.Nm sndctl +.Nd list and modify soundcard properties +.Sh SYNOPSIS +.Nm +.Op Fl f Ar device +.Op Fl hov +.Op Ar control Ns Oo = Ns Ar value Oc Ar ... +.Sh DESCRIPTION +The +.Nm +utility is used to set and display sound card properties, using a +control-driven interface, in order to filter and/or set specific properties. +.Pp +The options are as follows: +.Bl -tag -width "-f device" +.It Fl f Ar device +Choose a specific audio device +.Pq see Sx FILES . +Userland devices (e.g those registered by +.Xr virtual_oss 8 +can also be selected. +.It Fl h +Print a help message. +.It Fl o +Print values in a format suitable for use inside scripts. +.It Fl v +Run in verbose mode. +This option will print all of the device's channel properties. +.El +.Pp +The device controls are as follows: +.Bl -column xxxxxxxxxxxxxxx xxxxx xxxxxxxx xxxxxxxxxxxxxxxxxxx -offset indent +.It Sy Name Ta Sy Type Ta Sy Read/Write Ta Sy Action +.It name Ta String Ta Read Ta Device name +.It desc Ta String Ta Read Ta Device description +.It status Ta String Ta Read Ta Device status +.It devnode Ta String Ta Read Ta Device node +.It from_user Ta Boolean Ta Read Ta Userland device +.It unit Ta Number Ta Read Ta Device unit +.It caps Ta String Ta Read Ta Device OSS capabitilies +.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 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 +.It play.vchans Ta Boolean Ta Read/Write Ta Playback VCHANs (virtual channels) enabled +.It play.min_rate Ta Number Ta Read Ta Minimum playback sample rate +.It play.max_rate Ta Number Ta Read Ta Maximum playback sample rate +.It play.min_chans Ta Number Ta Read Ta Natively supported minimum playback sample channels +.It play.max_chans Ta Number Ta Read Ta Natively supported maximum playback sample channels +.It play.formats Ta String Ta Read Ta Natively supported playback formats +.It rec Ta Group Ta Read Ta Recording properties +.It rec.format Ta String Ta Read/Write Ta Recording format +.It rec.rate Ta Number Ta Read/Write Ta Recording sample rate +.It rec.vchans Ta Boolean Ta Read/Write Ta Recording VCHANs (virtual channels) enabled +.It rec.min_rate Ta Number Ta Read Ta Minimum recording sample rate +.It rec.max_rate Ta Number Ta Read Ta Maximum recording sample rate +.It rec.min_chans Ta Number Ta Read Ta Natively supported minimum recording sample channels +.It rec.max_chans Ta Number Ta Read Ta Natively supported maximum recording sample channels +.It rec.formats Ta String Ta Read Ta Natively supported recording formats +.El +.Pp +The +.Pa play.format , +.Pa play.rate , +.Pa rec.format and +.Pa rec.rate +controls will be read-only if VCHANs are disabled. +.Pp +The device channel controls are as follows: +.Bl -column xxxxxxxxxxxxxxx xxxxx xxxxxxxx xxxxxxxxxxxxxxxxxxx -offset indent +.It Sy Name Ta Sy Type Ta Sy Read/Write Ta Sy Action +.It name Ta String Ta Read Ta Channel name +.It parentchan Ta String Ta Read Ta Parent (primary) channel name +.It unit Ta Number Ta Read Ta Channel unit +.It caps Ta String Ta Read Ta Channel OSS capabilities +.It latency Ta Number Ta Read Ta Channel latency +.It format Ta String Ta Read Ta Channel format +.It rate Ta Number Ta Read Ta Channel sample rate +.It pid Ta Number Ta Read Ta PID of process consuming channel +.It proc Ta String Ta Read Ta Name of process consuming channel +.It interrupts Ta Number Ta Read Ta Number of interrupts since channel was opened +.It xruns Ta Number Ta Read Ta Number of playback underruns/recoring overruns +.It feedcount Ta Number Ta Read Ta Number of bytes fed to channel +.It volume Ta Volume Ta Read Ta Channel left-right volume in normalized form (0.00 to 1.00). +.It hwbuf Ta Group Ta Read Ta Hardware buffer properties +.It hwbuf.format Ta String Ta Read Ta Hardware buffer format +.It hwbuf.rate Ta String Ta Read Ta Hardware buffer sample rate +.It hwbuf.size_bytes Ta Number Ta Read Ta Hardware buffer size in bytes +.It hwbuf.size_frames Ta Number Ta Read Ta Hardware buffer size in frames +.It hwbuf.blksz Ta Number Ta Read Ta Hardware buffer block size +.It hwbuf.blkcnt Ta Number Ta Read Ta Hardware buffer block count +.It hwbuf.free Ta Number Ta Read Ta Hardware buffer free space in bytes +.It hwbuf.ready Ta Number Ta Read Ta Hardware buffer ready space in bytes +.It swbuf Ta Group Ta Read Ta Software buffer properties +.It swbuf.format Ta String Ta Read Ta Software buffer format +.It swbuf.rate Ta String Ta Read Ta Software buffer sample rate +.It swbuf.size_bytes Ta Number Ta Read Ta Software buffer size in bytes +.It swbuf.size_frames Ta Number Ta Read Ta Software buffer size in frames +.It swbuf.blksz Ta Number Ta Read Ta Software buffer block size +.It swbuf.blkcnt Ta Number Ta Read Ta Software buffer block count +.It swbuf.free Ta Number Ta Read Ta Software buffer free space in bytes +.It swbuf.ready Ta Number Ta Read Ta Software buffer ready space in bytes +.It feederchain Ta String Ta Read Ta Channel feeder chain +.El +.Sh FILES +.Bl -tag -width /dev/dspX -compact +.It Pa /dev/dsp +The default audio device. +.It Pa /dev/dspX +The audio device file, where X is the unit of the device, for example +.Ar /dev/dsp0 . +.El +.Sh EXAMPLES +Disable auto-conversions and enable realtime mode to get as low latencies as +possible: +.Bd -literal -offset indent +$ sndctl autoconv=0 realtime=1 +.Ed +.Pp +Set the playback sample format to 2-channel signed 24-bit low endian, and sample +rate to 48000 Hz: +.Bd -literal -offset indent +$ sndctl play.format=s24le:2.0 play.rate=48000 +.Ed +.Pp +List the PIDs and process names of all channels for +.Pa /dev/dsp1 : +.Bd -literal -offset indent +$ sndctl -f /dev/dsp1 pid proc +.Ed +.Pp +Dump +.Pa /dev/dsp0 +information to a file and retrieve back later: +.Bd -literal -offset indent +$ sndctl -f /dev/dsp0 -o > info +\&... +$ sndctl -f /dev/dsp0 `cat info` +.Ed +.Sh SEE ALSO +.Xr sndstat 4 , +.Xr sound 4 , +.Xr mixer 8 , +.Xr sysctl 8 +.Sh AUTHORS +The +.Nm +utility was implemented by +.An Christos Margiolis Aq Mt christos@FreeBSD.org +under sponsorship from the +.Fx +Foundation. diff --git a/usr.sbin/sndctl/sndctl.c b/usr.sbin/sndctl/sndctl.c new file mode 100644 index 000000000000..156c845481c5 --- /dev/null +++ b/usr.sbin/sndctl/sndctl.c @@ -0,0 +1,1004 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2024-2025 The FreeBSD Foundation + * + * This software was developed by Christos Margiolis <christos@FreeBSD.org> + * under sponsorship from the FreeBSD Foundation. + * + * 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/nv.h> +#include <sys/queue.h> +#include <sys/sndstat.h> +#include <sys/soundcard.h> +#include <sys/sysctl.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <limits.h> +#include <mixer.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* Taken from sys/dev/sound/pcm/ */ +#define STATUS_LEN 64 +#define FMTSTR_LEN 16 + +struct snd_chan { + char name[NAME_MAX]; + char parentchan[NAME_MAX]; + int unit; +#define INPUT 0 +#define OUTPUT 1 + int direction; + char caps[BUFSIZ]; + int latency; + int rate; + char format[FMTSTR_LEN]; + int pid; + char proc[NAME_MAX]; + int interrupts; + int xruns; + int feedcount; + int volume; + struct { + char format[FMTSTR_LEN]; + int rate; + int size_bytes; + int size_frames; + int blksz; + int blkcnt; + int free; + int ready; + } hwbuf, swbuf; + char feederchain[BUFSIZ]; + struct snd_dev *dev; + TAILQ_ENTRY(snd_chan) next; +}; + +struct snd_dev { + char name[NAME_MAX]; + char desc[NAME_MAX]; + char status[BUFSIZ]; + char devnode[NAME_MAX]; + int from_user; + int unit; + char caps[BUFSIZ]; + int bitperfect; + int realtime; + int autoconv; + struct { + char format[FMTSTR_LEN]; + int rate; + int pchans; + int vchans; + int min_rate; + int max_rate; + int min_chans; + int max_chans; + char formats[BUFSIZ]; + } play, rec; + TAILQ_HEAD(, snd_chan) chans; +}; + +struct snd_ctl { + const char *name; + size_t off; +#define STR 0 +#define NUM 1 +#define VOL 2 +#define GRP 3 + int type; + int (*mod)(struct snd_dev *, void *); +}; + +struct map { + int val; + const char *str; +}; + +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_play_vchans(struct snd_dev *, void *); +static int mod_play_rate(struct snd_dev *, void *); +static int mod_play_format(struct snd_dev *, void *); +static int mod_rec_vchans(struct snd_dev *, void *); +static int mod_rec_rate(struct snd_dev *, void *); +static int mod_rec_format(struct snd_dev *, void *); + +static struct snd_ctl dev_ctls[] = { +#define F(member) offsetof(struct snd_dev, member) + { "name", F(name), STR, NULL }, + { "desc", F(desc), STR, NULL }, + { "status", F(status), STR, NULL }, + { "devnode", F(devnode), STR, NULL }, + { "from_user", F(from_user), NUM, NULL }, + { "unit", F(unit), NUM, NULL }, + { "caps", F(caps), STR, NULL }, + { "bitperfect", F(bitperfect), NUM, mod_bitperfect }, + { "autoconv", F(autoconv), NUM, mod_autoconv }, + { "realtime", F(realtime), NUM, mod_realtime }, + { "play", F(play), GRP, NULL }, + { "play.format", F(play.format), STR, mod_play_format }, + { "play.rate", F(play.rate), NUM, mod_play_rate }, + /*{ "play.pchans", F(play.pchans), NUM, NULL },*/ + { "play.vchans", F(play.vchans), NUM, mod_play_vchans }, + { "play.min_rate", F(play.min_rate), NUM, NULL }, + { "play.max_rate", F(play.max_rate), NUM, NULL }, + { "play.min_chans", F(play.min_chans), NUM, NULL }, + { "play.max_chans", F(play.max_chans), NUM, NULL }, + { "play.formats", F(play.formats), STR, NULL }, + { "rec", F(rec), GRP, NULL }, + { "rec.rate", F(rec.rate), NUM, mod_rec_rate }, + { "rec.format", F(rec.format), STR, mod_rec_format }, + /*{ "rec.pchans", F(rec.pchans), NUM, NULL },*/ + { "rec.vchans", F(rec.vchans), NUM, mod_rec_vchans }, + { "rec.min_rate", F(rec.min_rate), NUM, NULL }, + { "rec.max_rate", F(rec.max_rate), NUM, NULL }, + { "rec.min_chans", F(rec.min_chans), NUM, NULL }, + { "rec.max_chans", F(rec.max_chans), NUM, NULL }, + { "rec.formats", F(rec.formats), STR, NULL }, + { NULL, 0, 0, NULL } +#undef F +}; + +static struct snd_ctl chan_ctls[] = { +#define F(member) offsetof(struct snd_chan, member) + /*{ "name", F(name), STR, NULL },*/ + { "parentchan", F(parentchan), STR, NULL }, + { "unit", F(unit), NUM, NULL }, + { "caps", F(caps), STR, NULL }, + { "latency", F(latency), NUM, NULL }, + { "rate", F(rate), NUM, NULL }, + { "format", F(format), STR, NULL }, + { "pid", F(pid), NUM, NULL }, + { "proc", F(proc), STR, NULL }, + { "interrupts", F(interrupts), NUM, NULL }, + { "xruns", F(xruns), NUM, NULL }, + { "feedcount", F(feedcount), NUM, NULL }, + { "volume", F(volume), VOL, NULL }, + { "hwbuf", F(hwbuf), GRP, NULL }, + { "hwbuf.format", F(hwbuf.format), STR, NULL }, + { "hwbuf.rate", F(hwbuf.rate), NUM, NULL }, + { "hwbuf.size_bytes", F(hwbuf.size_bytes), NUM, NULL }, + { "hwbuf.size_frames", F(hwbuf.size_frames), NUM, NULL }, + { "hwbuf.blksz", F(hwbuf.blksz), NUM, NULL }, + { "hwbuf.blkcnt", F(hwbuf.blkcnt), NUM, NULL }, + { "hwbuf.free", F(hwbuf.free), NUM, NULL }, + { "hwbuf.ready", F(hwbuf.ready), NUM, NULL }, + { "swbuf", F(swbuf), GRP, NULL }, + { "swbuf.format", F(swbuf.format), STR, NULL }, + { "swbuf.rate", F(swbuf.rate), NUM, NULL }, + { "swbuf.size_bytes", F(swbuf.size_bytes), NUM, NULL }, + { "swbuf.size_frames", F(swbuf.size_frames), NUM, NULL }, + { "swbuf.blksz", F(swbuf.blksz), NUM, NULL }, + { "swbuf.blkcnt", F(swbuf.blkcnt), NUM, NULL }, + { "swbuf.free", F(swbuf.free), NUM, NULL }, + { "swbuf.ready", F(swbuf.ready), NUM, NULL }, + { "feederchain", F(feederchain), STR, NULL }, + { NULL, 0, 0, NULL } +#undef F +}; + +/* + * Taken from the OSSv4 manual. Not all of them are supported on FreeBSD + * however, and some of them are obsolete. + */ +static struct map capmap[] = { + { PCM_CAP_ANALOGIN, "ANALOGIN" }, + { PCM_CAP_ANALOGOUT, "ANALOGOUT" }, + { PCM_CAP_BATCH, "BATCH" }, + { PCM_CAP_BIND, "BIND" }, + { PCM_CAP_COPROC, "COPROC" }, + { PCM_CAP_DEFAULT, "DEFAULT" }, + { PCM_CAP_DIGITALIN, "DIGITALIN" }, + { PCM_CAP_DIGITALOUT, "DIGITALOUT" }, + { PCM_CAP_DUPLEX, "DUPLEX" }, + { PCM_CAP_FREERATE, "FREERATE" }, + { PCM_CAP_HIDDEN, "HIDDEN" }, + { PCM_CAP_INPUT, "INPUT" }, + { PCM_CAP_MMAP, "MMAP" }, + { PCM_CAP_MODEM, "MODEM" }, + { PCM_CAP_MULTI, "MULTI" }, + { PCM_CAP_OUTPUT, "OUTPUT" }, + { PCM_CAP_REALTIME, "REALTIME" }, + { PCM_CAP_REVISION, "REVISION" }, + { PCM_CAP_SHADOW, "SHADOW" }, + { PCM_CAP_SPECIAL, "SPECIAL" }, + { PCM_CAP_TRIGGER, "TRIGGER" }, + { PCM_CAP_VIRTUAL, "VIRTUAL" }, + { 0, NULL } +}; + +static struct map fmtmap[] = { + { AFMT_A_LAW, "alaw" }, + { AFMT_MU_LAW, "mulaw" }, + { AFMT_S8, "s8" }, + { AFMT_U8, "u8" }, + { AFMT_AC3, "ac3" }, + { AFMT_S16_LE, "s16le" }, + { AFMT_S16_BE, "s16be" }, + { AFMT_U16_LE, "u16le" }, + { AFMT_U16_BE, "u16be" }, + { AFMT_S24_LE, "s24le" }, + { AFMT_S24_BE, "s24be" }, + { AFMT_U24_LE, "u24le" }, + { AFMT_U24_BE, "u24be" }, + { AFMT_S32_LE, "s32le" }, + { AFMT_S32_BE, "s32be" }, + { AFMT_U32_LE, "u32le" }, + { AFMT_U32_BE, "u32be" }, + { AFMT_F32_LE, "f32le" }, + { AFMT_F32_BE, "f32be" }, + { 0, NULL } +}; + +static bool oflag = false; +static bool vflag = false; + +static void +cap2str(char *buf, size_t size, int caps) +{ + struct map *p; + + for (p = capmap; p->str != NULL; p++) { + if ((p->val & caps) == 0) + continue; + strlcat(buf, p->str, size); + strlcat(buf, ",", size); + } + if (*buf == '\0') + strlcpy(buf, "UNKNOWN", size); + else + buf[strlen(buf) - 1] = '\0'; +} + +static void +fmt2str(char *buf, size_t size, int fmt) +{ + struct map *p; + int enc, ch, ext; + + enc = fmt & 0xf00fffff; + ch = (fmt & 0x07f00000) >> 20; + ext = (fmt & 0x08000000) >> 27; + + for (p = fmtmap; p->str != NULL; p++) { + if ((p->val & enc) == 0) + continue; + strlcat(buf, p->str, size); + if (ch) { + snprintf(buf + strlen(buf), size, + ":%d.%d", ch - ext, ext); + } + strlcat(buf, ",", size); + } + if (*buf == '\0') + strlcpy(buf, "UNKNOWN", size); + else + buf[strlen(buf) - 1] = '\0'; +} + +static int +bytes2frames(int bytes, int fmt) +{ + int enc, ch, samplesz; + + enc = fmt & 0xf00fffff; + ch = (fmt & 0x07f00000) >> 20; + /* Add the channel extension if present (e.g 2.1). */ + ch += (fmt & 0x08000000) >> 27; + + if (enc & (AFMT_S8 | AFMT_U8 | AFMT_MU_LAW | AFMT_A_LAW)) + samplesz = 1; + else if (enc & (AFMT_S16_NE | AFMT_U16_NE)) + samplesz = 2; + else if (enc & (AFMT_S24_NE | AFMT_U24_NE)) + samplesz = 3; + else if (enc & (AFMT_S32_NE | AFMT_U32_NE | AFMT_F32_NE)) + samplesz = 4; + else + samplesz = 0; + + if (!samplesz || !ch) + return (-1); + + return (bytes / (samplesz * ch)); +} + +static int +sysctl_int(const char *buf, const char *arg, int *var) +{ + size_t size; + int n, prev; + + size = sizeof(int); + /* Read current value. */ + if (sysctlbyname(buf, &prev, &size, NULL, 0) < 0) { + warn("sysctlbyname(%s)", buf); + return (-1); + } + + /* Read-only. */ + if (arg != NULL) { + errno = 0; + n = strtol(arg, NULL, 10); + if (errno == EINVAL || errno == ERANGE) { + warn("strtol(%s)", arg); + return (-1); + } + + /* Apply new value. */ + if (sysctlbyname(buf, NULL, 0, &n, size) < 0) { + warn("sysctlbyname(%s, %d)", buf, n); + return (-1); + } + } + + /* Read back applied value for good measure. */ + if (sysctlbyname(buf, &n, &size, NULL, 0) < 0) { + warn("sysctlbyname(%s)", buf); + return (-1); + } + + if (arg != NULL) + printf("%s: %d -> %d\n", buf, prev, n); + if (var != NULL) + *var = n; + + return (0); +} + +static int +sysctl_str(const char *buf, const char *arg, char *var, size_t varsz) +{ + size_t size; + char prev[BUFSIZ]; + char *tmp; + + /* Read current value. */ + size = sizeof(prev); + if (sysctlbyname(buf, prev, &size, NULL, 0) < 0) { + warn("sysctlbyname(%s)", buf); + return (-1); + } + + /* Read-only. */ + if (arg != NULL) { + size = strlen(arg); + /* Apply new value. */ + if (sysctlbyname(buf, NULL, 0, arg, size) < 0) { + warn("sysctlbyname(%s, %s)", buf, arg); + return (-1); + } + /* Get size of new string. */ + if (sysctlbyname(buf, NULL, &size, NULL, 0) < 0) { + warn("sysctlbyname(%s)", buf); + return (-1); + } + } + + if ((tmp = calloc(1, size)) == NULL) + err(1, "calloc"); + /* Read back applied value for good measure. */ + if (sysctlbyname(buf, tmp, &size, NULL, 0) < 0) { + warn("sysctlbyname(%s)", buf); + free(tmp); + return (-1); + } + + if (arg != NULL) + printf("%s: %s -> %s\n", buf, prev, tmp); + if (var != NULL) + strlcpy(var, tmp, varsz); + free(tmp); + + return (0); +} + +static struct snd_dev * +read_dev(char *path) +{ + nvlist_t *nvl; + const nvlist_t * const *di; + const nvlist_t * const *cdi; + struct sndstioc_nv_arg arg; + struct snd_dev *dp = NULL; + struct snd_chan *ch; + size_t nitems, nchans, i, j; + int fd, caps, unit, t1, t2, t3; + + if ((fd = open("/dev/sndstat", O_RDONLY)) < 0) + err(1, "open(/dev/sndstat)"); + + if (ioctl(fd, SNDSTIOC_REFRESH_DEVS, NULL) < 0) + err(1, "ioctl(SNDSTIOC_REFRESH_DEVS)"); + + arg.nbytes = 0; + arg.buf = NULL; + if (ioctl(fd, SNDSTIOC_GET_DEVS, &arg) < 0) + err(1, "ioctl(SNDSTIOC_GET_DEVS#1)"); + + if ((arg.buf = malloc(arg.nbytes)) == NULL) + err(1, "malloc"); + + if (ioctl(fd, SNDSTIOC_GET_DEVS, &arg) < 0) + err(1, "ioctl(SNDSTIOC_GET_DEVS#2)"); + + if ((nvl = nvlist_unpack(arg.buf, arg.nbytes, 0)) == NULL) + err(1, "nvlist_unpack"); + + if (nvlist_empty(nvl) || !nvlist_exists(nvl, SNDST_DSPS)) + errx(1, "no soundcards attached"); + + if (path == NULL || (path != NULL && strcmp(basename(path), "dsp") == 0)) + unit = mixer_get_dunit(); + else + unit = -1; + + /* Find whether the requested device exists */ + di = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &nitems); + for (i = 0; i < nitems; i++) { + if (unit == -1 && strcmp(basename(path), + nvlist_get_string(di[i], SNDST_DSPS_DEVNODE)) == 0) + break; + else if (nvlist_exists(di[i], SNDST_DSPS_PROVIDER_INFO) && + (int)nvlist_get_number(nvlist_get_nvlist(di[i], + SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_UNIT) == unit) + break;; + } + if (i == nitems) + errx(1, "device not found"); + +#define NV(type, item) \ + nvlist_get_ ## type (di[i], SNDST_DSPS_ ## item) + if ((dp = calloc(1, sizeof(struct snd_dev))) == NULL) + err(1, "calloc"); + + dp->unit = -1; + strlcpy(dp->name, NV(string, NAMEUNIT), sizeof(dp->name)); + strlcpy(dp->desc, NV(string, DESC), sizeof(dp->desc)); + strlcpy(dp->devnode, NV(string, DEVNODE), sizeof(dp->devnode)); + dp->from_user = NV(bool, FROM_USER); + dp->play.pchans = NV(number, PCHAN); + dp->rec.pchans = NV(number, RCHAN); +#undef NV + + if (dp->play.pchans && !nvlist_exists(di[i], SNDST_DSPS_INFO_PLAY)) + errx(1, "%s: playback channel list empty", dp->name); + if (dp->rec.pchans && !nvlist_exists(di[i], SNDST_DSPS_INFO_REC)) + errx(1, "%s: recording channel list empty", dp->name); + +#define NV(type, mode, item) \ + nvlist_get_ ## type (nvlist_get_nvlist(di[i], \ + SNDST_DSPS_INFO_ ## mode), SNDST_DSPS_INFO_ ## item) + if (dp->play.pchans) { + dp->play.min_rate = NV(number, PLAY, MIN_RATE); + dp->play.max_rate = NV(number, PLAY, MAX_RATE); + dp->play.min_chans = NV(number, PLAY, MIN_CHN); + dp->play.max_chans = NV(number, PLAY, MAX_CHN); + fmt2str(dp->play.formats, sizeof(dp->play.formats), + NV(number, PLAY, FORMATS)); + } + if (dp->rec.pchans) { + dp->rec.min_rate = NV(number, REC, MIN_RATE); + dp->rec.max_rate = NV(number, REC, MAX_RATE); + dp->rec.min_chans = NV(number, REC, MIN_CHN); + dp->rec.max_chans = NV(number, REC, MAX_CHN); + fmt2str(dp->rec.formats, sizeof(dp->rec.formats), + NV(number, REC, FORMATS)); + } +#undef NV + + /* + * Skip further parsing if the provider is not sound(4), as the + * following code is sound(4)-specific. + */ + if (strcmp(nvlist_get_string(di[i], SNDST_DSPS_PROVIDER), + SNDST_DSPS_SOUND4_PROVIDER) != 0) + goto done; + + if (!nvlist_exists(di[i], SNDST_DSPS_PROVIDER_INFO)) + errx(1, "%s: provider_info list empty", dp->name); + +#define NV(type, item) \ + nvlist_get_ ## type (nvlist_get_nvlist(di[i], \ + SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_ ## item) + strlcpy(dp->status, NV(string, STATUS), sizeof(dp->status)); + dp->unit = NV(number, UNIT); + dp->bitperfect = NV(bool, BITPERFECT); + dp->play.vchans = NV(bool, PVCHAN); + dp->play.rate = NV(number, PVCHANRATE); + fmt2str(dp->play.format, sizeof(dp->play.format), + NV(number, PVCHANFORMAT)); + dp->rec.vchans = NV(bool, RVCHAN); + dp->rec.rate = NV(number, RVCHANRATE); + fmt2str(dp->rec.format, sizeof(dp->rec.format), + NV(number, RVCHANFORMAT)); +#undef NV + + dp->autoconv = (dp->play.vchans || dp->rec.vchans) && !dp->bitperfect; + + if (sysctl_int("hw.snd.latency", NULL, &t1) || + sysctl_int("hw.snd.latency_profile", NULL, &t2) || + sysctl_int("kern.timecounter.alloweddeviation", NULL, &t3)) + err(1, "%s: sysctl", dp->name); + if (t1 == 0 && t2 == 0 && t3 == 0) + dp->realtime = 1; + + if (!nvlist_exists(nvlist_get_nvlist(di[i], + SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_CHAN_INFO)) + errx(1, "%s: channel info list empty", dp->name); + + cdi = nvlist_get_nvlist_array( + nvlist_get_nvlist(di[i], SNDST_DSPS_PROVIDER_INFO), + SNDST_DSPS_SOUND4_CHAN_INFO, &nchans); + + TAILQ_INIT(&dp->chans); + caps = 0; + for (j = 0; j < nchans; j++) { +#define NV(type, item) \ + nvlist_get_ ## type (cdi[j], SNDST_DSPS_SOUND4_CHAN_ ## item) + if ((ch = calloc(1, sizeof(struct snd_chan))) == NULL) + err(1, "calloc"); + + strlcpy(ch->name, NV(string, NAME), sizeof(ch->name)); + strlcpy(ch->parentchan, NV(string, PARENTCHAN), + sizeof(ch->parentchan)); + ch->unit = NV(number, UNIT); + ch->direction = (NV(number, CAPS) & PCM_CAP_INPUT) ? + INPUT : OUTPUT; + cap2str(ch->caps, sizeof(ch->caps), NV(number, CAPS)); + ch->latency = NV(number, LATENCY); + ch->rate = NV(number, RATE); + fmt2str(ch->format, sizeof(ch->format), NV(number, FORMAT)); + ch->pid = NV(number, PID); + strlcpy(ch->proc, NV(string, COMM), sizeof(ch->proc)); + ch->interrupts = NV(number, INTR); + ch->xruns = NV(number, XRUNS); + ch->feedcount = NV(number, FEEDCNT); + ch->volume = NV(number, LEFTVOL) | + NV(number, RIGHTVOL) << 8; + fmt2str(ch->hwbuf.format, sizeof(ch->hwbuf.format), + NV(number, HWBUF_FORMAT)); + ch->hwbuf.rate = NV(number, HWBUF_RATE); + ch->hwbuf.size_bytes = NV(number, HWBUF_SIZE); + ch->hwbuf.size_frames = + bytes2frames(ch->hwbuf.size_bytes, NV(number, HWBUF_FORMAT)); + ch->hwbuf.blksz = NV(number, HWBUF_BLKSZ); + ch->hwbuf.blkcnt = NV(number, HWBUF_BLKCNT); + ch->hwbuf.free = NV(number, HWBUF_FREE); + ch->hwbuf.ready = NV(number, HWBUF_READY); + fmt2str(ch->swbuf.format, sizeof(ch->swbuf.format), + NV(number, SWBUF_FORMAT)); + ch->swbuf.rate = NV(number, SWBUF_RATE); + ch->swbuf.size_bytes = NV(number, SWBUF_SIZE); + ch->swbuf.size_frames = + bytes2frames(ch->swbuf.size_bytes, NV(number, SWBUF_FORMAT)); + ch->swbuf.blksz = NV(number, SWBUF_BLKSZ); + ch->swbuf.blkcnt = NV(number, SWBUF_BLKCNT); + ch->swbuf.free = NV(number, SWBUF_FREE); + ch->swbuf.ready = NV(number, SWBUF_READY); + strlcpy(ch->feederchain, NV(string, FEEDERCHAIN), + sizeof(ch->feederchain)); + ch->dev = dp; + + caps |= NV(number, CAPS); + TAILQ_INSERT_TAIL(&dp->chans, ch, next); + + if (!dp->rec.vchans && ch->direction == INPUT) { + strlcpy(dp->rec.format, ch->hwbuf.format, + sizeof(dp->rec.format)); + dp->rec.rate = ch->hwbuf.rate; + } else if (!dp->play.vchans && ch->direction == OUTPUT) { + strlcpy(dp->play.format, ch->hwbuf.format, + sizeof(dp->play.format)); + dp->play.rate = ch->hwbuf.rate; + } +#undef NV + } + cap2str(dp->caps, sizeof(dp->caps), caps); + +done: + free(arg.buf); + nvlist_destroy(nvl); + close(fd); + + return (dp); +} + +static void +free_dev(struct snd_dev *dp) +{ + struct snd_chan *ch; + + while (!TAILQ_EMPTY(&dp->chans)) { + ch = TAILQ_FIRST(&dp->chans); + TAILQ_REMOVE(&dp->chans, ch, next); + free(ch); + } + free(dp); +} + +static void +print_dev_ctl(struct snd_dev *dp, struct snd_ctl *ctl, bool simple, + bool showgrp) +{ + struct snd_ctl *cp; + size_t len; + + if (ctl->type != GRP) { + if (simple) + printf("%s=", ctl->name); + else + printf(" %-20s= ", ctl->name); + } + + switch (ctl->type) { + case STR: + printf("%s\n", (char *)dp + ctl->off); + break; + case NUM: + printf("%d\n", *(int *)((intptr_t)dp + ctl->off)); + break; + case VOL: + break; + case GRP: + if (!simple || !showgrp) + break; + for (cp = dev_ctls; cp->name != NULL; cp++) { + len = strlen(ctl->name); + if (strncmp(ctl->name, cp->name, len) == 0 && + cp->name[len] == '.' && cp->type != GRP) + print_dev_ctl(dp, cp, simple, showgrp); + } + break; + } +} + +static void +print_chan_ctl(struct snd_chan *ch, struct snd_ctl *ctl, bool simple, + bool showgrp) +{ + struct snd_ctl *cp; + size_t len; + int v; + + if (ctl->type != GRP) { + if (simple) + printf("%s.%s=", ch->name, ctl->name); + else + printf(" %-20s= ", ctl->name); + } + + switch (ctl->type) { + case STR: + printf("%s\n", (char *)ch + ctl->off); + break; + case NUM: + printf("%d\n", *(int *)((intptr_t)ch + ctl->off)); + break; + case VOL: + v = *(int *)((intptr_t)ch + ctl->off); + printf("%.2f:%.2f\n", + MIX_VOLNORM(v & 0x00ff), MIX_VOLNORM((v >> 8) & 0x00ff)); + break; + case GRP: + if (!simple || !showgrp) + break; + for (cp = chan_ctls; cp->name != NULL; cp++) { + len = strlen(ctl->name); + if (strncmp(ctl->name, cp->name, len) == 0 && + cp->name[len] == '.' && cp->type != GRP) + print_chan_ctl(ch, cp, simple, showgrp); + } + break; + } +} + +static void +print_dev(struct snd_dev *dp) +{ + struct snd_chan *ch; + struct snd_ctl *ctl; + + if (!oflag) { + printf("%s: <%s> %s", dp->name, dp->desc, dp->status); + + printf(" ("); + if (dp->play.pchans) + printf("play"); + if (dp->play.pchans && dp->rec.pchans) + printf("/"); + if (dp->rec.pchans) + printf("rec"); + printf(")\n"); + } + + for (ctl = dev_ctls; ctl->name != NULL; ctl++) + print_dev_ctl(dp, ctl, oflag, false); + + if (vflag) { + TAILQ_FOREACH(ch, &dp->chans, next) { + if (!oflag) + printf(" %s\n", ch->name); + for (ctl = chan_ctls; ctl->name != NULL; ctl++) + print_chan_ctl(ch, ctl, oflag, false); + } + } +} + +static int +mod_bitperfect(struct snd_dev *dp, void *arg) +{ + char buf[64]; + + if (dp->from_user) + return (-1); + + snprintf(buf, sizeof(buf), "dev.pcm.%d.bitperfect", dp->unit); + + return (sysctl_int(buf, arg, &dp->bitperfect)); +} + +static int +mod_autoconv(struct snd_dev *dp, void *arg) +{ + const char *val = arg; + const char *zero = "0"; + const char *one = "1"; + int rc = -1; + + if (dp->from_user) + return (rc); + + if (strcmp(val, zero) == 0) { + rc = mod_play_vchans(dp, __DECONST(char *, zero)) || + mod_rec_vchans(dp, __DECONST(char *, zero)) || + mod_bitperfect(dp, __DECONST(char *, one)); + if (rc == 0) + dp->autoconv = 0; + } else if (strcmp(val, one) == 0) { + rc = mod_play_vchans(dp, __DECONST(char *, one)) || + mod_rec_vchans(dp, __DECONST(char *, one)) || + mod_bitperfect(dp, __DECONST(char *, zero)); + if (rc == 0) + dp->autoconv = 1; + } + + return (rc); +} + +static int +mod_realtime(struct snd_dev *dp, void *arg) +{ + const char *val = arg; + int rc = -1; + + if (dp->from_user) + return (-1); + + if (strcmp(val, "0") == 0) { + /* TODO */ + rc = sysctl_int("hw.snd.latency", "2", NULL) || + sysctl_int("hw.snd.latency_profile", "1", NULL) || + sysctl_int("kern.timecounter.alloweddeviation", "5", NULL); + if (rc == 0) + dp->realtime = 0; + } else if (strcmp(val, "1") == 0) { + rc = sysctl_int("hw.snd.latency", "0", NULL) || + sysctl_int("hw.snd.latency_profile", "0", NULL) || + sysctl_int("kern.timecounter.alloweddeviation", "0", NULL); + if (rc == 0) + dp->realtime = 1; + } + + return (rc); +} + +static int +mod_play_vchans(struct snd_dev *dp, void *arg) +{ + char buf[64]; + + if (dp->from_user) + return (-1); + + snprintf(buf, sizeof(buf), "dev.pcm.%d.play.vchans", dp->unit); + + return (sysctl_int(buf, arg, &dp->play.vchans)); +} + +static int +mod_play_rate(struct snd_dev *dp, void *arg) +{ + char buf[64]; + + if (dp->from_user) + return (-1); + if (!dp->play.vchans) + return (0); + + snprintf(buf, sizeof(buf), "dev.pcm.%d.play.vchanrate", dp->unit); + + return (sysctl_int(buf, arg, &dp->play.rate)); +} + +static int +mod_play_format(struct snd_dev *dp, void *arg) +{ + char buf[64]; + + if (dp->from_user) + return (-1); + if (!dp->play.vchans) + return (0); + + snprintf(buf, sizeof(buf), "dev.pcm.%d.play.vchanformat", dp->unit); + + return (sysctl_str(buf, arg, dp->play.format, sizeof(dp->play.format))); +} + +static int +mod_rec_vchans(struct snd_dev *dp, void *arg) +{ + char buf[64]; + + if (dp->from_user) + return (-1); + + snprintf(buf, sizeof(buf), "dev.pcm.%d.rec.vchans", dp->unit); + + return (sysctl_int(buf, arg, &dp->rec.vchans)); +} + +static int +mod_rec_rate(struct snd_dev *dp, void *arg) +{ + char buf[64]; + + if (dp->from_user) + return (-1); + if (!dp->rec.vchans) + return (0); + + snprintf(buf, sizeof(buf), "dev.pcm.%d.rec.vchanrate", dp->unit); + + return (sysctl_int(buf, arg, &dp->rec.rate)); +} + +static int +mod_rec_format(struct snd_dev *dp, void *arg) +{ + char buf[64]; + + if (dp->from_user) + return (-1); + if (!dp->rec.vchans) + return (0); + + snprintf(buf, sizeof(buf), "dev.pcm.%d.rec.vchanformat", dp->unit); + + return (sysctl_str(buf, arg, dp->rec.format, sizeof(dp->rec.format))); +} + +static void __dead2 +usage(void) +{ + fprintf(stderr, "usage: %s [-f device] [-hov] [control[=value] ...]\n", + getprogname()); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct snd_dev *dp; + struct snd_chan *ch; + struct snd_ctl *ctl; + char *path = NULL; + char *s, *propstr; + bool show = true, found; + int c; + + while ((c = getopt(argc, argv, "f:hov")) != -1) { + switch (c) { + case 'f': + path = optarg; + break; + case 'o': + oflag = true; + break; + case 'v': + vflag = true; + break; + case 'h': /* FALLTHROUGH */ + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + dp = read_dev(path); + + while (argc > 0) { + if ((s = strdup(*argv)) == NULL) + err(1, "strdup(%s)", *argv); + + propstr = strsep(&s, "="); + if (propstr == NULL) + goto next; + + found = false; + for (ctl = dev_ctls; ctl->name != NULL; ctl++) { + if (strcmp(ctl->name, propstr) != 0) + continue; + if (s == NULL) { + print_dev_ctl(dp, ctl, true, true); + show = false; + } else if (ctl->mod != NULL && ctl->mod(dp, s) < 0) + warnx("%s(%s) failed", ctl->name, s); + found = true; + break; + } + TAILQ_FOREACH(ch, &dp->chans, next) { + for (ctl = chan_ctls; ctl->name != NULL; ctl++) { + if (strcmp(ctl->name, propstr) != 0) + continue; + print_chan_ctl(ch, ctl, true, true); + show = false; + found = true; + break; + } + } + if (!found) + warnx("%s: no such property", propstr); +next: + free(s); + argc--; + argv++; + } + + free_dev(dp); + + if (show) { + /* + * Re-read dev to reflect new state in case we changed some + * property. + */ + dp = read_dev(path); + print_dev(dp); + free_dev(dp); + } + + return (0); +} diff --git a/usr.sbin/spi/Makefile b/usr.sbin/spi/Makefile index 73f5af6fc3cc..fee967f6a234 100644 --- a/usr.sbin/spi/Makefile +++ b/usr.sbin/spi/Makefile @@ -2,6 +2,6 @@ PROG= spi -MAN8= spi.8 +MAN= spi.8 .include <bsd.prog.mk> diff --git a/usr.sbin/syslogd/syslogd.8 b/usr.sbin/syslogd/syslogd.8 index fa61e78eaf3e..d39d9fdc8f5a 100644 --- a/usr.sbin/syslogd/syslogd.8 +++ b/usr.sbin/syslogd/syslogd.8 @@ -403,7 +403,7 @@ The message can contain a priority code, which should be a preceding decimal number in angle braces, for example, -.Sq Aq 5 . +.Sq <5> . This priority code should map into the priorities defined in the include file .In sys/syslog.h . diff --git a/usr.sbin/syslogd/syslogd.c b/usr.sbin/syslogd/syslogd.c index 726cedc17b1d..81bbbbe66be8 100644 --- a/usr.sbin/syslogd/syslogd.c +++ b/usr.sbin/syslogd/syslogd.c @@ -1830,15 +1830,14 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags) case EHOSTUNREACH: case EHOSTDOWN: case EADDRNOTAVAIL: + case EAGAIN: + case ECONNREFUSED: break; /* case EBADF: */ /* case EACCES: */ /* case ENOTSOCK: */ /* case EFAULT: */ /* case EMSGSIZE: */ - /* case EAGAIN: */ - /* case ENOBUFS: */ - /* case ECONNREFUSED: */ default: dprintf("removing entry: errno=%d\n", e); f->f_type = F_UNUSED; @@ -2571,7 +2570,7 @@ syslogd_cap_enter(void) if (cap_syslogd == NULL) err(1, "Failed to open the syslogd.casper libcasper service"); cap_net = cap_service_open(cap_casper, "system.net"); - if (cap_syslogd == NULL) + if (cap_net == NULL) err(1, "Failed to open the system.net libcasper service"); cap_close(cap_casper); limit = cap_net_limit_init(cap_net, diff --git a/usr.sbin/sysrc/Makefile b/usr.sbin/sysrc/Makefile index 0f2f98e8b0e8..bad50caf2ffc 100644 --- a/usr.sbin/sysrc/Makefile +++ b/usr.sbin/sysrc/Makefile @@ -1,5 +1,11 @@ +.include <src.opts.mk> + +PACKAGE= bsdconfig SCRIPTS= sysrc MAN= sysrc.8 +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + .include <bsd.prog.mk> diff --git a/usr.sbin/sysrc/sysrc b/usr.sbin/sysrc/sysrc index 1766cf7ab835..625ff5ca4efd 100644 --- a/usr.sbin/sysrc/sysrc +++ b/usr.sbin/sysrc/sysrc @@ -765,33 +765,6 @@ while [ $# -gt 0 ]; do fi # - # If `-c' is passed, simply compare and move on - # - if [ "$CHECK_ONLY" ]; then - if ! IGNORED=$( f_sysrc_get "$NAME?" ); then - status=$FAILURE - [ "$VERBOSE" ] && - echo "$NAME: not currently set" - shift 1 - continue - fi - value=$( f_sysrc_get "$NAME" ) - if [ "$value" != "${1#*=}" ]; then - status=$FAILURE - if [ "$VERBOSE" ]; then - echo -n "$( f_sysrc_find "$NAME" ): " - echo -n "$NAME: would change from " - echo "\`$value' to \`${1#*=}'" - fi - elif [ "$VERBOSE" ]; then - echo -n "$( f_sysrc_find "$NAME" ): " - echo "$NAME: already set to \`$value'" - fi - shift 1 - continue - fi - - # # Determine both `before' value and appropriate `new' value # case "$mode" in @@ -849,6 +822,31 @@ while [ $# -gt 0 ]; do esac # + # If `-c' is passed, simply compare and move on + # + if [ "$CHECK_ONLY" ]; then + if ! IGNORED=$( f_sysrc_get "$NAME?" ); then + status=$FAILURE + [ "$VERBOSE" ] && + echo "$NAME: not currently set" + shift 1 + continue + elif [ "$new" != "$before" ]; then + status=$FAILURE + if [ "$VERBOSE" ]; then + echo -n "$( f_sysrc_find "$NAME" ): " + echo -n "$NAME: would change from " + echo "\`$before' to \`$new'" + fi + elif [ "$VERBOSE" ]; then + echo -n "$( f_sysrc_find "$NAME" ): " + echo "$NAME: already set to \`$before'" + fi + shift 1 + continue + fi + + # # If `-N' is passed, simplify the output # if [ ! "$SHOW_VALUE" ]; then diff --git a/usr.sbin/sysrc/tests/Makefile b/usr.sbin/sysrc/tests/Makefile new file mode 100644 index 000000000000..8b4bad96f4cc --- /dev/null +++ b/usr.sbin/sysrc/tests/Makefile @@ -0,0 +1,3 @@ +ATF_TESTS_SH+= sysrc_test + +.include <bsd.test.mk> diff --git a/usr.sbin/sysrc/tests/sysrc_test.sh b/usr.sbin/sysrc/tests/sysrc_test.sh new file mode 100644 index 000000000000..fa79e6262c21 --- /dev/null +++ b/usr.sbin/sysrc/tests/sysrc_test.sh @@ -0,0 +1,351 @@ +#- +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2025 Michal Scigocki <michal.os@hotmail.com> +# + +readonly SYSRC_CONF="${PWD}/sysrc_test.conf" + +atf_test_case check_variable +check_variable_head() +{ + atf_set "descr" "Check rc variable" +} +check_variable_body() +{ + cat <<- EOF > "${SYSRC_CONF}" + test_enable="NO" + test_list="item1 item2" + test2_list="" + EOF + + atf_check -o inline:"test_enable: NO\n" \ + sysrc -f "${SYSRC_CONF}" test_enable + atf_check -o inline:"test2_list: \n" \ + sysrc -f "${SYSRC_CONF}" test2_list + atf_check -o inline:"test_enable: NO\ntest_list: item1 item2\n" \ + sysrc -f "${SYSRC_CONF}" test_enable test_list + atf_check -e inline:"sysrc: unknown variable 'noexist'\n" \ + -s exit:1 sysrc -f "${SYSRC_CONF}" noexist + atf_check -e inline:"sysrc: unknown variable 'noexist'\n" \ + -s exit:1 -o inline:"test_enable: NO\n" \ + sysrc -f "${SYSRC_CONF}" test_enable noexist +} + +atf_test_case set_variable +set_variable_head() +{ + atf_set "descr" "Set rc variable" +} +set_variable_body() +{ + # Previously unset variable + atf_check -o inline:"test_enable: -> YES\n" \ + sysrc -f "${SYSRC_CONF}" test_enable=YES + atf_check -o inline:'test_enable="YES"\n' cat "${SYSRC_CONF}" + # Change set variable + atf_check -o inline:"test_enable: YES -> YES\n" \ + sysrc -f "${SYSRC_CONF}" test_enable=YES + atf_check -o inline:'test_enable="YES"\n' cat "${SYSRC_CONF}" + atf_check -o inline:"test_enable: YES -> NO\n" \ + sysrc -f "${SYSRC_CONF}" test_enable=NO + atf_check -o inline:'test_enable="NO"\n' cat "${SYSRC_CONF}" + # Set an empty variable + atf_check -o inline:"test2_enable: -> \n" \ + sysrc -f "${SYSRC_CONF}" test2_enable="" + atf_check -o inline:'test_enable="NO"\ntest2_enable=""\n' \ + cat "${SYSRC_CONF}" +} + +atf_test_case remove_variable_x_flag +remove_variable_x_flag_head() +{ + atf_set "descr" "Remove rc variable (-x flag)" +} +remove_variable_x_flag_body() +{ + cat <<- EOF > "${SYSRC_CONF}" + test1_enable="YES" + test2_enable="NO" + test3_enable="" + EOF + + atf_check sysrc -f "${SYSRC_CONF}" -x test1_enable + atf_check -o inline:'test2_enable="NO"\ntest3_enable=""\n' \ + cat "${SYSRC_CONF}" + atf_check sysrc -f "${SYSRC_CONF}" -x test2_enable test3_enable + atf_check -o empty cat "${SYSRC_CONF}" + atf_check -e inline:"sysrc: unknown variable 'noexist'\n"\ + -s exit:1 sysrc -f "${SYSRC_CONF}" -x noexist +} + +atf_test_case append_list +append_list_head() +{ + atf_set "descr" "Append rc variable to list" +} +append_list_body() +{ + atf_check -o inline:"test_list: -> item1\n" \ + sysrc -f "${SYSRC_CONF}" test_list+=item1 + atf_check -o inline:'test_list="item1"\n' cat "${SYSRC_CONF}" + atf_check -o inline:"test_list: item1 -> item1\n" \ + sysrc -f "${SYSRC_CONF}" test_list+=item1 + atf_check -o inline:'test_list="item1"\n' cat "${SYSRC_CONF}" + atf_check -o inline:"test_list: item1 -> item1 item2\n" \ + sysrc -f "${SYSRC_CONF}" test_list+=item2 + atf_check -o inline:'test_list="item1 item2"\n' \ + cat "${SYSRC_CONF}" +} + +atf_test_case subtract_list +subtract_list_head() +{ + atf_set "descr" "Subtract rc variable from list" +} +subtract_list_body() +{ + echo 'test_list="item1 item2"' > "${SYSRC_CONF}" + + atf_check -o inline:"test_list: item1 item2 -> item1\n" \ + sysrc -f "${SYSRC_CONF}" test_list-=item2 + atf_check -o inline:'test_list="item1"\n' cat "${SYSRC_CONF}" + atf_check -o inline:"test_list: item1 -> \n" \ + sysrc -f "${SYSRC_CONF}" test_list-=item1 + atf_check -o inline:'test_list=""\n' cat "${SYSRC_CONF}" + atf_check -o inline:"test_list: -> \n" \ + sysrc -f "${SYSRC_CONF}" test_list-=item3 + atf_check -o inline:'test_list=""\n' cat "${SYSRC_CONF}" + + # Subtracting a non-existant rc variable results in an empty variable + # being created. This is by design, see sysrc(8). + atf_check -s exit:1 grep test2_list "${SYSRC_CONF}" + atf_check -o inline:"test2_list: -> \n" \ + sysrc -f "${SYSRC_CONF}" test2_list-=item1 + atf_check -o inline:'test_list=""\ntest2_list=""\n' \ + cat "${SYSRC_CONF}" +} + +atf_test_case multiple_operations +multiple_operations_head() +{ + atf_set "descr" "Check performing multiple operations on rc variables" +} +multiple_operations_body() +{ + atf_check -o inline:"test1_enable: -> YES\ntest1_list: -> item1\n" \ + sysrc -f "${SYSRC_CONF}" test1_enable=YES test1_list+=item1 + atf_check -o inline:'test1_enable="YES"\ntest1_list="item1"\n' \ + cat "${SYSRC_CONF}" + + # The trailing space on line "^test1_list:" is necessary. + cat <<- EOF > expected + test1_enable: YES + test1_enable: YES -> NO + test2_enable: -> YES + test1_list: item1 -> + test2_list: -> item3 + EOF + atf_check -s exit:1 -e inline:"sysrc: unknown variable 'noexist'\n" \ + -o file:expected sysrc -f "${SYSRC_CONF}" test1_enable \ + test1_enable=NO noexist test2_enable=YES test1_list-=item1 \ + test2_list+=item3 + + cat <<- EOF > expected + test1_enable="NO" + test1_list="" + test2_enable="YES" + test2_list="item3" + EOF + atf_check -o file:expected cat "${SYSRC_CONF}" +} + +atf_test_case a_flag +a_flag_head() +{ + atf_set "descr" "Verify -a behavior" +} +a_flag_body() +{ + echo 'test_enable="YES"' > "${SYSRC_CONF}" + + atf_check -o inline:"test_enable: YES\n" sysrc -f "${SYSRC_CONF}" -a +} + +atf_test_case A_flag cleanup +A_flag_head() +{ + atf_set "descr" "Verify -A behavior" +} +A_flag_body() +{ + RC_DEFAULTS="${PWD}/rc_defaults.conf" + echo "rc_conf_files=\"${SYSRC_CONF}\"" > "${RC_DEFAULTS}" + echo 'test_enable="NO"' >> "${RC_DEFAULTS}" + echo 'test_enable="YES"' > "${SYSRC_CONF}" + + export RC_DEFAULTS + + # sysrc is coupled to the "source_rc_confs" sh(1) script function in + # /etc/defaults/rc.conf. For this test we use a custom default + # rc.conf file that is missing the "source_rc_confs" function, which + # causes sysrc to print an error message. While the coupling exists, + # we assume the error message is expected behaviour instead of just + # ignoring the stderr output to make sure we don't ignore other error + # messages in case of future regressions. + atf_check -e inline:"/usr/sbin/sysrc: source_rc_confs: not found\n" \ + -o inline:"rc_conf_files: ${SYSRC_CONF}\ntest_enable: NO\n" \ + sysrc -A + # Because "source_rc_confs" does not exist in our custom default + # rc.conf file, we cannot get sysrc to load the other SYSRC_CONF file + # to compare results. We can only verify that configuration variables + # get read from the RC_DEFAULTS file. +} +A_flag_cleanup() +{ + unset RC_DEFAULTS +} + +atf_test_case c_flag +c_flag_head() +{ + atf_set "descr" "Verify -c behavior" +} +c_flag_body() +{ + echo 'test_enable="NO"' > "${SYSRC_CONF}" + echo 'test_list="item1 item2"' >> "${SYSRC_CONF}" + + # Test if rc variable is set + atf_check sysrc -f "${SYSRC_CONF}" -c test_enable + atf_check sysrc -f "${SYSRC_CONF}" -c test_list + atf_check sysrc -f "${SYSRC_CONF}" -c test_enable test_list + atf_check -e inline:"sysrc: unknown variable 'noexist'\n"\ + -s exit:1 sysrc -f "${SYSRC_CONF}" -c noexist + atf_check -e inline:"sysrc: unknown variable 'noexist'\n"\ + -s exit:1 sysrc -f "${SYSRC_CONF}" -c test_enable noexist + + cat <<- EOF > expected_err + sysrc: unknown variable 'noexist1' + sysrc: unknown variable 'noexist2' + EOF + atf_check -e file:expected_err -s exit:1 \ + sysrc -f "${SYSRC_CONF}" -c noexist1 noexist2 + + # Test rc variable assignment + atf_check sysrc -f "${SYSRC_CONF}" -c test_enable=NO + atf_check -s exit:1 sysrc -f "${SYSRC_CONF}" -c test_enable=YES + atf_check -s exit:1 sysrc -f "${SYSRC_CONF}" -c noexist=YES + + # Test appending rc variables + atf_check sysrc -f "${SYSRC_CONF}" -c test_list+=item1 + atf_check -s exit:1 sysrc -f "${SYSRC_CONF}" -c test_list+=item3 + atf_check -s exit:1 sysrc -f "${SYSRC_CONF}" -c noexist+=item1 + atf_check sysrc -f "${SYSRC_CONF}" -c test_enable=NO test_list+=item1 + atf_check -s exit:1 \ + sysrc -f "${SYSRC_CONF}" -c test_enable=YES test_list+=item1 + atf_check -s exit:1 \ + sysrc -f "${SYSRC_CONF}" -c test_enable=NO test_list+=item3 + atf_check -s exit:1 \ + sysrc -f "${SYSRC_CONF}" -c test_enable=YES test_list+=item3 + + # Test subracting rc variables + atf_check sysrc -f "${SYSRC_CONF}" -c test_list-=item3 + atf_check -s exit:1 sysrc -f "${SYSRC_CONF}" -c test_list-=item1 + atf_check -s exit:1 sysrc -f "${SYSRC_CONF}" -c noexist-=item1 + atf_check sysrc -f "${SYSRC_CONF}" -c test_enable=NO test_list-=item3 + atf_check -s exit:1 \ + sysrc -f "${SYSRC_CONF}" -c test_enable=YES test_list-=item3 + atf_check -s exit:1 \ + sysrc -f "${SYSRC_CONF}" -c test_enable=NO test_list-=item1 + atf_check -s exit:1 \ + sysrc -f "${SYSRC_CONF}" -c test_enable=YES test_list-=item1 +} + +atf_test_case d_flag cleanup +d_flag_head() +{ + atf_set "descr" "Verify -f behavior" +} +d_flag_body() +{ + echo 'test_enable="NO" # Test Description' > "${SYSRC_CONF}" + + export RC_DEFAULTS="${SYSRC_CONF}" + + atf_check -o inline:'test_enable: Test Description\n' \ + sysrc -d test_enable +} +d_flag_cleanup() +{ + unset RC_DEFAULTS +} + +atf_test_case f_flag +f_flag_head() +{ + atf_set "descr" "Verify -f behavior" +} +f_flag_body() +{ + local sysrc2_conf="${PWD}/sysrc2.conf" + echo 'test_list="item1"' > "${sysrc2_conf}" + echo 'test_enable="NO"' >> "${sysrc2_conf}" + + atf_check test ! -f "${SYSRC_CONF}" + atf_check -e inline:"sysrc: unknown variable 'noexist'\n" \ + -s exit:1 sysrc -f "${SYSRC_CONF}" noexist + atf_check test ! -f "${SYSRC_CONF}" + atf_check -o inline:"test_enable: -> YES\n" \ + sysrc -f "${SYSRC_CONF}" test_enable=YES + atf_check test -f "${SYSRC_CONF}" + atf_check -o inline:"test_enable: YES\n" \ + sysrc -f "${SYSRC_CONF}" test_enable + # -f file order impacts final settings + atf_check -o inline:"test_enable: YES\n" \ + sysrc -f "${sysrc2_conf}" -f "${SYSRC_CONF}" test_enable + atf_check -o inline:"test_enable: NO\n" \ + sysrc -f "${SYSRC_CONF}" -f "${sysrc2_conf}" test_enable + atf_check -o inline:"test_enable: YES\ntest_list: item1\n" \ + sysrc -f "${sysrc2_conf}" -f "${SYSRC_CONF}" -a +} + +atf_test_case F_flag +F_flag_head() +{ + atf_set "descr" "Verify -F behavior" +} +F_flag_body() +{ + local sysrc2_conf="${PWD}/sysrc2.conf" + echo 'test_list="item1"' > "${sysrc2_conf}" + echo 'test_enable="NO"' > "${SYSRC_CONF}" + + atf_check -o inline:"test_enable: ${SYSRC_CONF}\n" \ + sysrc -f "${SYSRC_CONF}" -F test_enable + atf_check -o inline:"test_list: ${sysrc2_conf}\n" \ + sysrc -f "${sysrc2_conf}" -F test_list + + cat <<- EOF > expected + test_enable: ${SYSRC_CONF} + test_list: ${sysrc2_conf} + EOF + atf_check -o file:expected sysrc -f "${SYSRC_CONF}" \ + -f ${sysrc2_conf} -F test_enable test_list +} + +atf_init_test_cases() +{ + atf_add_test_case check_variable + atf_add_test_case set_variable + atf_add_test_case remove_variable_x_flag + atf_add_test_case append_list + atf_add_test_case subtract_list + atf_add_test_case multiple_operations + atf_add_test_case a_flag + atf_add_test_case A_flag + atf_add_test_case c_flag + atf_add_test_case d_flag + atf_add_test_case f_flag + atf_add_test_case F_flag +} diff --git a/usr.sbin/traceroute/Makefile b/usr.sbin/traceroute/Makefile index 62d82a47d953..c52bd52abb1d 100644 --- a/usr.sbin/traceroute/Makefile +++ b/usr.sbin/traceroute/Makefile @@ -7,6 +7,9 @@ SRCS= as.c traceroute.c ifaddrlist.c findsaddr-udp.c BINOWN= root BINMODE=4555 +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + .if !defined(TRACEROUTE_NO_IPSEC) CFLAGS+= -DIPSEC .endif diff --git a/usr.sbin/traceroute/tests/Makefile b/usr.sbin/traceroute/tests/Makefile new file mode 100644 index 000000000000..7c3d6f777582 --- /dev/null +++ b/usr.sbin/traceroute/tests/Makefile @@ -0,0 +1,7 @@ +ATF_TESTS_SH+= traceroute_test + +# Allow tests to run in parallel in their own jails +TEST_METADATA+= execenv="jail" +TEST_METADATA+= execenv_jail_params="vnet allow.raw_sockets" + +.include <bsd.test.mk> diff --git a/usr.sbin/traceroute/tests/traceroute_test.sh b/usr.sbin/traceroute/tests/traceroute_test.sh new file mode 100755 index 000000000000..268e0bd58b5b --- /dev/null +++ b/usr.sbin/traceroute/tests/traceroute_test.sh @@ -0,0 +1,874 @@ +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2025 Lexi Winter +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# We are missing tests for the following flags: +# +# -a (turn on ASN lookups) +# -A (specify ASN lookup server) +# -d (enable SO_DEBUG) +# -D (print the diff between our packet and the quote in the ICMP error) +# -E (detect ECN bleaching) +# -n (or rather, we enable -n by default and don't test without it) +# -S (print per-hop packet loss) +# -v (verbose output) +# -w (how long to wait for an error response) +# -x (toggle IP checksums) +# -z (how long to wait between each probe) + +. $(atf_get_srcdir)/../../sys/common/vnet.subr + +# These are the default flags we use for most test cases: +# - only send a single probe packet to reduce the risk of kernel ICMP +# rate-limiting breaking the test. +# - only trace up to 5 hops and only wait 1 second for a response so the test +# fails quicker if something goes wrong. +# - disable DNS resolution as we don't usually care about this. +TR_FLAGS="-w 1 -q 1 -m 5 -n" + +# The prefix our test networks are in. +TEST_PREFIX="192.0.2.0/24" + +# The IPv4 addresses of the first link net between trsrc and trrtr. +LINK_TRSRC_TRSRC="192.0.2.5" +LINK_TRSRC_TRRTR="192.0.2.6" +LINK_TRSRC_PREFIXLEN="30" + +# The IPv4 addresses of the second link net between trsrc and trrtr. +LINK_TRSRC2_TRSRC="192.0.2.13" +LINK_TRSRC2_TRRTR="192.0.2.14" +LINK_TRSRC2_PREFIXLEN="30" + +# The IPv4 addresses of the link net between trdst and trrtr. +LINK_TRDST_TRDST="192.0.2.9" +LINK_TRDST_TRRTR="192.0.2.10" +LINK_TRDST_PREFIXLEN="30" + +# This is an address inside $TEST_PREFIX which is not routed anywhere. +UNREACHABLE_ADDR="192.0.2.255" + +setup_network() +{ + # Create 3 jails: one to be the source host, one to be the router, + # and one to be the destination host. + + vnet_init + + # src jail + epsrc=$(vnet_mkepair) + epsrc2=$(vnet_mkepair) + vnet_mkjail trsrc ${epsrc}a ${epsrc2}a + + # dst jail + epdst=$(vnet_mkepair) + vnet_mkjail trdst ${epdst}a + + # router jail + vnet_mkjail trrtr ${epsrc}b ${epsrc2}b ${epdst}b + + # Configure IPv4 addresses and routes on each jail. + + # trsrc + jexec trsrc ifconfig ${epsrc}a inet \ + ${LINK_TRSRC_TRSRC}/${LINK_TRSRC_PREFIXLEN} + jexec trrtr ifconfig ${epsrc}b inet \ + ${LINK_TRSRC_TRRTR}/${LINK_TRSRC_PREFIXLEN} + jexec trsrc route add -inet ${TEST_PREFIX} ${LINK_TRSRC_TRRTR} + + # trsrc2 + jexec trsrc ifconfig ${epsrc2}a inet \ + ${LINK_TRSRC2_TRSRC}/${LINK_TRSRC2_PREFIXLEN} + jexec trrtr ifconfig ${epsrc2}b inet \ + ${LINK_TRSRC2_TRRTR}/${LINK_TRSRC2_PREFIXLEN} + + # trdst + jexec trdst ifconfig ${epdst}a inet \ + ${LINK_TRDST_TRDST}/${LINK_TRDST_PREFIXLEN} + jexec trrtr ifconfig ${epdst}b inet \ + ${LINK_TRDST_TRRTR}/${LINK_TRDST_PREFIXLEN} + jexec trdst route add -inet ${TEST_PREFIX} ${LINK_TRDST_TRRTR} + + # The router jail (only) needs IP forwarding enabled. + jexec trrtr sysctl net.inet.ip.forwarding=1 +} + +## +# +# start_tcpdump, stop_tcpdump: used to capture packets during the test so we +# can verify we actually sent the expected packets. + +start_tcpdump() +{ + # Run tcpdump on trrtr, either on the given interface or on + # ${epsrc}b, which is trsrc's default route interface. + + interface="$1" + if [ -z "$interface" ]; then + interface="${epsrc}b" + fi + + rm -f "${PWD}/traceroute.pcap" + + jexec trrtr daemon -p "${PWD}/tcpdump.pid" \ + tcpdump --immediate-mode -w "${PWD}/traceroute.pcap" -nv \ + -i $interface + + # Give tcpdump time to start + sleep 1 +} + +stop_tcpdump() +{ + # Sleep to give tcpdump a chance to finish flushing + jexec trrtr kill -USR2 $(cat "${PWD}/tcpdump.pid") + sleep 1 + jexec trrtr kill $(cat "${PWD}/tcpdump.pid") + + # Format the packet capture and merge continued lines (starting with + # whitespace) into a single line; this makes it easier to match in + # atf_check. Append a blank line since the N command exits on EOF. + (tcpdump -nv -r "${PWD}/traceroute.pcap"; echo) | \ + sed -E -e :a -e N -e 's/\n +/ /' -e ta -e P -e D \ + > tcpdump.output +} + +## +# test: ipv4_basic +# + +atf_test_case "ipv4_basic" "cleanup" +ipv4_basic_head() +{ + atf_set descr "Basic IPv4 traceroute across a router" + atf_set require.user root +} + +ipv4_basic_body() +{ + setup_network + + # Use a more detailed set of regexp here than the rest of the tests to + # make sure the basic output format is correct. + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST} \\(${LINK_TRDST_TRDST}\\), 5 hops max, 40 byte packets$" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR} [0-9.]+ ms$" \ + -o match:"^ 2 ${LINK_TRDST_TRDST} [0-9.]+ ms$" \ + -o not-match:"^ 3" \ + jexec trsrc traceroute $TR_FLAGS ${LINK_TRDST_TRDST} +} + +ipv4_basic_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_icmp +# + +atf_test_case "ipv4_icmp" "cleanup" +ipv4_icmp_head() +{ + atf_set descr "Basic IPv4 ICMP traceroute across a router" + atf_set require.user root +} + +ipv4_icmp_body() +{ + setup_network + + # -I and -Picmp should mean the same thing, so test both. + + for icmp_flag in -Picmp -I; do + start_tcpdump + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + -o match:"^ 2 ${LINK_TRDST_TRDST}" \ + -o not-match:"^ 3" \ + jexec trsrc traceroute $TR_FLAGS $icmp_flag \ + ${LINK_TRDST_TRDST} + + stop_tcpdump + + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto ICMP.*\\).* ${LINK_TRSRC_TRSRC} > ${LINK_TRDST_TRDST}: ICMP echo request" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto ICMP.*\\).* ${LINK_TRSRC_TRSRC} > ${LINK_TRDST_TRDST}: ICMP echo request" \ + cat tcpdump.output + done +} + +ipv4_icmp_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_udp +# + +atf_test_case "ipv4_udp" "cleanup" +ipv4_udp_head() +{ + atf_set descr "IPv4 UDP traceroute" + atf_set require.user root +} + +ipv4_udp_body() +{ + setup_network + + start_tcpdump + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + -o match:"^ 2 ${LINK_TRDST_TRDST}" \ + -o not-match:"^ 3" \ + jexec trsrc traceroute $TR_FLAGS -Pudp ${LINK_TRDST_TRDST} + + stop_tcpdump + + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto UDP .*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: UDP" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP .*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: UDP" \ + cat tcpdump.output + + # Test with -e, the destination port should not increment. + + start_tcpdump + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + -o match:"^ 2 ${LINK_TRDST_TRDST}" \ + -o not-match:"^ 3" \ + jexec trsrc traceroute $TR_FLAGS -Pudp -e -p 40000 ${LINK_TRDST_TRDST} + + stop_tcpdump + + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto UDP .*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40000: UDP" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP .*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40000: UDP" \ + cat tcpdump.output +} + +ipv4_udp_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_sctp +# + +atf_test_case "ipv4_sctp" "cleanup" +ipv4_sctp_head() +{ + atf_set descr "IPv4 SCTP traceroute" + atf_set require.user root +} + +ipv4_sctp_body() +{ + setup_network + + # For the default packet size, we should sent a SHUTDOWN ACK packet. + + start_tcpdump + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + jexec trsrc traceroute $TR_FLAGS -Psctp ${LINK_TRDST_TRDST} + + stop_tcpdump + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto SCTP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: sctp \(1\) \[SHUTDOWN ACK\]" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto SCTP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: sctp \(1\) \[SHUTDOWN ACK\]" \ + cat tcpdump.output + + # For a larger packet size we should send INIT packets. + + start_tcpdump + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + jexec trsrc traceroute $TR_FLAGS -Psctp ${LINK_TRDST_TRDST} 128 + + stop_tcpdump + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto SCTP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: sctp \(1\) \[INIT\]" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto SCTP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: sctp \(1\) \[INIT\]" \ + cat tcpdump.output + + # Test with -e, the destination port should not increment. + + start_tcpdump + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + jexec trsrc traceroute $TR_FLAGS -Psctp -e -p 40000 ${LINK_TRDST_TRDST} + + stop_tcpdump + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto SCTP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40000: sctp \(1\) \[SHUTDOWN ACK\]" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto SCTP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40000: sctp \(1\) \[SHUTDOWN ACK\]" \ + cat tcpdump.output +} + +ipv4_sctp_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_tcp +# + +atf_test_case "ipv4_tcp" "cleanup" +ipv4_tcp_head() +{ + atf_set descr "IPv4 TCP traceroute" + atf_set require.user root +} + +ipv4_tcp_body() +{ + setup_network + + start_tcpdump + + # We expect the second hop to be a failure since traceroute doesn't + # know how to capture the RST packet. + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + -o match:"^ 2 \\*" \ + jexec trsrc traceroute $TR_FLAGS -Ptcp ${LINK_TRDST_TRDST} + + stop_tcpdump + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto TCP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: Flags \[S\]" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto TCP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: Flags \[S\]" \ + cat tcpdump.output + + # Test with -e, the destination port should not increment. + start_tcpdump + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + -o match:"^ 2 \\*" \ + jexec trsrc traceroute $TR_FLAGS -Ptcp -e -p 40000 ${LINK_TRDST_TRDST} + + stop_tcpdump + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto TCP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40000: Flags \[S\]" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto TCP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40000: Flags \[S\]" \ + cat tcpdump.output +} + +ipv4_tcp_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_srcaddr +# + +atf_test_case "ipv4_srcaddr" "cleanup" +ipv4_srcaddr_head() +{ + atf_set descr "IPv4 traceroute with explicit source address" + atf_set require.user root +} + +ipv4_srcaddr_body() +{ + setup_network + + start_tcpdump + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST} \\($LINK_TRDST_TRDST\\) from ${LINK_TRSRC2_TRSRC}" \ + -o match:"^ 1 ${LINK_TRSRC2_TRRTR}" \ + -o match:"^ 2 ${LINK_TRDST_TRDST}" \ + -o not-match:"^ 3" \ + jexec trsrc traceroute $TR_FLAGS \ + -s ${LINK_TRSRC2_TRSRC} ${LINK_TRDST_TRDST} + + stop_tcpdump + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto UDP.*\\).* ${LINK_TRSRC2_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: UDP" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP.*\\).* ${LINK_TRSRC2_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: UDP" \ + cat tcpdump.output +} + +ipv4_srcaddr_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_srcinterface +# + +atf_test_case "ipv4_srcinterface" "cleanup" +ipv4_srcinterface_head() +{ + atf_set descr "IPv4 traceroute with explicit source interface" + atf_set require.user root +} + +ipv4_srcinterface_body() +{ + setup_network + + start_tcpdump + + # Unlike -s, traceroute doesn't print 'from ...' when using -i. + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC2_TRRTR}" \ + -o match:"^ 2 ${LINK_TRDST_TRDST}" \ + -o not-match:"^ 3" \ + jexec trsrc traceroute $TR_FLAGS \ + -i ${epsrc2}a ${LINK_TRDST_TRDST} + + stop_tcpdump + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto UDP.*\\).* ${LINK_TRSRC2_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: UDP" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP.*\\).* ${LINK_TRSRC2_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: UDP" \ + cat tcpdump.output +} + +ipv4_srcinterface_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_maxhops +# + +atf_test_case "ipv4_maxhops" "cleanup" +ipv4_maxhops_head() +{ + atf_set descr "IPv4 traceroute with -m" + atf_set require.user root +} + +ipv4_maxhops_body() +{ + setup_network + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + -o not-match:"^ 2" \ + jexec trsrc traceroute -w1 -q1 -m1 ${LINK_TRDST_TRDST} +} + +ipv4_maxhops_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_unreachable +# + +atf_test_case "ipv4_unreachable" "cleanup" +ipv4_unreachable_head() +{ + atf_set descr "IPv4 traceroute to an unreachable destination" + atf_set require.user root +} + +ipv4_unreachable_body() +{ + setup_network + + atf_check -s exit:0 \ + -e match:"^traceroute to ${UNREACHABLE_ADDR}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + -o match:"^ 2 ${LINK_TRSRC_TRRTR} [0-9.]+ ms !H" \ + -o not-match:"^ 3" \ + jexec trsrc traceroute $TR_FLAGS $UNREACHABLE_ADDR +} + +ipv4_unreachable_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_hugepacket +# + +atf_test_case "ipv4_hugepacket" "cleanup" +ipv4_hugepacket_head() +{ + atf_set descr "IPv4 traceroute with a huge packet" + atf_set require.user root +} + +ipv4_hugepacket_body() +{ + setup_network + + # We expect this to fail since we specified -F (don't fragment) and the + # 2000-byte packet is too large to fit through our tiny epair. Make + # sure traceroute reports the error. + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST} \\(${LINK_TRDST_TRDST}\\), 5 hops max, 2000 byte packets$" \ + -o match:"^ 1 traceroute: wrote ${LINK_TRDST_TRDST} 2000 chars, ret=-1" \ + -e match:"^traceroute: sendto: Message too long" \ + jexec trsrc traceroute -F $TR_FLAGS ${LINK_TRDST_TRDST} 2000 +} + +ipv4_hugepacket_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_firsthop +# + +atf_test_case "ipv4_firsthop" "cleanup" +ipv4_firsthop_head() +{ + atf_set descr "IPv4 traceroute with one hop skipped" + atf_set require.user root +} + +ipv4_firsthop_body() +{ + setup_network + + # -f 2 means we skip the first hop. For backward compatibility, -M is + # the same as -f, so test that too. + + for flag in -f2 -M2; do + start_tcpdump + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o not-match:"^ 1" \ + -o match:"^ 2 ${LINK_TRDST_TRDST}" \ + -o not-match:"^ 3" \ + jexec trsrc traceroute $flag $TR_FLAGS ${LINK_TRDST_TRDST} + + stop_tcpdump + atf_check -s exit:0 -e ignore \ + -o not-match:"^..:..:..\....... IP \\(tos 0x0, ttl 1, .*, proto UDP.*\\)" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: UDP" \ + cat tcpdump.output + done +} + +ipv4_firsthop_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_nprobes +# + +atf_test_case "ipv4_nprobes" "cleanup" +ipv4_nprobes_head() +{ + atf_set descr "IPv4 traceroute with varying number of probes" + atf_set require.user root +} + +ipv4_nprobes_body() +{ + setup_network + + # By default we should send 3 probes. + atf_check -s exit:0 -e ignore \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR} \(${LINK_TRSRC_TRRTR}\)( [0-9.]+ ms){3}$" \ + jexec trsrc traceroute -w1 -m1 ${LINK_TRDST_TRDST} + + # Also test 1 and 2 (below the default) and 5 (above the default) + for nprobes in 1 2 5; do + atf_check -s exit:0 -e ignore \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR} \(${LINK_TRSRC_TRRTR}\)( [0-9.]+ ms){$nprobes}$" \ + jexec trsrc traceroute -q$nprobes -w1 -m1 ${LINK_TRDST_TRDST} + done +} + +ipv4_nprobes_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_baseport +# + +atf_test_case "ipv4_baseport" "cleanup" +ipv4_baseport_head() +{ + atf_set descr "IPv4 traceroute with non-default base port" + atf_set require.user root +} + +ipv4_baseport_body() +{ + setup_network + + start_tcpdump + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + -o match:"^ 2 ${LINK_TRDST_TRDST}" \ + -o not-match:"^ 3" \ + jexec trsrc traceroute $TR_FLAGS -p 40000 \ + ${LINK_TRDST_TRDST} + + stop_tcpdump + + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto UDP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40001: UDP" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40002: UDP" \ + cat tcpdump.output +} + +ipv4_baseport_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_gre +# + +atf_test_case "ipv4_gre" "cleanup" +ipv4_gre_head() +{ + atf_set descr "IPv4 GRE traceroute" + atf_set require.user root +} + +ipv4_gre_body() +{ + setup_network + + start_tcpdump + + # We expect the second hop to be a failure since the remote host will + # ignore the GRE packet. + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + -o match:"^ 2 \\*" \ + jexec trsrc traceroute $TR_FLAGS -Pgre ${LINK_TRDST_TRDST} + + stop_tcpdump + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto GRE .*\\).* ${LINK_TRSRC_TRSRC} > ${LINK_TRDST_TRDST}: GREv1" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto GRE .*\\).* ${LINK_TRSRC_TRSRC} > ${LINK_TRDST_TRDST}: GREv1" \ + cat tcpdump.output +} + +ipv4_gre_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_udplite +# + +atf_test_case "ipv4_udplite" "cleanup" +ipv4_udplite_head() +{ + atf_set descr "IPv4 UDP-Lite traceroute" + atf_set require.user root +} + +ipv4_udplite_body() +{ + setup_network + + start_tcpdump + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + -o match:"^ 2 ${LINK_TRDST_TRDST}" \ + -o not-match:"^ 3" \ + jexec trsrc traceroute $TR_FLAGS -Pudplite ${LINK_TRDST_TRDST} + + stop_tcpdump + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto unknown \(136\), .*\\).* ${LINK_TRSRC_TRSRC} > ${LINK_TRDST_TRDST}: ip-proto-136" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto unknown \(136\), .*\\).* ${LINK_TRSRC_TRSRC} > ${LINK_TRDST_TRDST}: ip-proto-136" \ + cat tcpdump.output +} + +ipv4_udplite_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_iptos +# + +atf_test_case "ipv4_iptos" "cleanup" +ipv4_iptos_head() +{ + atf_set descr "IPv4 traceroute with explicit ToS" + atf_set require.user root +} + +ipv4_iptos_body() +{ + setup_network + + start_tcpdump + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + -o match:"^ 2 ${LINK_TRDST_TRDST}" \ + -o not-match:"^ 3" \ + jexec trsrc traceroute $TR_FLAGS -t 4 ${LINK_TRDST_TRDST} + + stop_tcpdump + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x4, ttl 1, .*, proto UDP .*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: UDP" \ + -o match:"IP \\(tos 0x4, ttl 2, .*, proto UDP .*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: UDP" \ + cat tcpdump.output +} + +ipv4_iptos_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_srcroute +# + +atf_test_case "ipv4_srcroute" "cleanup" +ipv4_srcroute_head() +{ + atf_set descr "IPv4 traceroute with explicit source routing" + atf_set require.user root +} + +ipv4_srcroute_body() +{ + setup_network + jexec trsrc sysctl net.inet.ip.sourceroute=1 + jexec trsrc sysctl net.inet.ip.accept_sourceroute=1 + jexec trrtr sysctl net.inet.ip.sourceroute=1 + + start_tcpdump + + # As we don't enable source routing on trdst, we should get an ICMP + # source routing failed error (!S). + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR}" \ + -o match:"^ 2 ${LINK_TRDST_TRDST} [0-9.]+ ms !S" \ + -o not-match:"^ 3" \ + jexec trsrc traceroute $TR_FLAGS \ + -g ${LINK_TRSRC_TRRTR} ${LINK_TRDST_TRDST} + + stop_tcpdump + atf_check -s exit:0 -e ignore \ + -o match:"IP \\(tos 0x0, ttl 1, .*, proto UDP .*, options \\(NOP,LSRR ${LINK_TRDST_TRDST}\\)\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRSRC_TRRTR}.33435: UDP" \ + -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP .*, options \\(NOP,LSRR ${LINK_TRDST_TRDST}\\)\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRSRC_TRRTR}.33436: UDP" \ + cat tcpdump.output +} + +ipv4_srcroute_cleanup() +{ + vnet_cleanup +} + +## +# test: ipv4_dontroute +# + +atf_test_case "ipv4_dontroute" "cleanup" +ipv4_dontroute_head() +{ + atf_set descr "IPv4 traceroute with -r" + atf_set require.user root +} + +ipv4_dontroute_body() +{ + setup_network + + # This one should work as trrtr is directly connected. + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRSRC_TRRTR}" \ + -o match:"^ 1 ${LINK_TRSRC_TRRTR} [0-9.]+ ms$" \ + -o not-match:"^ 2" \ + jexec trsrc traceroute -r $TR_FLAGS ${LINK_TRSRC_TRRTR} + + # This one should fail. + + atf_check -s exit:0 \ + -e match:"^traceroute to ${LINK_TRDST_TRDST}" \ + -o match:"^ 1 traceroute: wrote ${LINK_TRDST_TRDST} 40 chars, ret=-1" \ + jexec trsrc traceroute -r $TR_FLAGS ${LINK_TRDST_TRDST} +} + +ipv4_dontroute_cleanup() +{ + vnet_cleanup +} + +## +# test case declarations + +atf_init_test_cases() +{ + atf_add_test_case ipv4_basic + atf_add_test_case ipv4_udp + atf_add_test_case ipv4_icmp + atf_add_test_case ipv4_tcp + atf_add_test_case ipv4_sctp + atf_add_test_case ipv4_gre + atf_add_test_case ipv4_udplite + atf_add_test_case ipv4_srcaddr + atf_add_test_case ipv4_srcinterface + atf_add_test_case ipv4_maxhops + atf_add_test_case ipv4_unreachable + atf_add_test_case ipv4_hugepacket + atf_add_test_case ipv4_firsthop + atf_add_test_case ipv4_nprobes + atf_add_test_case ipv4_baseport + atf_add_test_case ipv4_iptos + atf_add_test_case ipv4_srcroute + atf_add_test_case ipv4_dontroute +} diff --git a/usr.sbin/traceroute/traceroute.8 b/usr.sbin/traceroute/traceroute.8 index 203b743fb408..f36d473f2727 100644 --- a/usr.sbin/traceroute/traceroute.8 +++ b/usr.sbin/traceroute/traceroute.8 @@ -1,3 +1,6 @@ +.\" +.\" SPDX-License-Identifier: BSD-4.3TAHOE +.\" .\" Copyright (c) 1989, 1995, 1996, 1997, 1999, 2000 .\" The Regents of the University of California. All rights reserved. .\" @@ -15,7 +18,7 @@ .\" .\" $Id: traceroute.8,v 1.19 2000/09/21 08:44:19 leres Exp $ .\" -.Dd November 17, 2023 +.Dd May 14, 2025 .Dt TRACEROUTE 8 .Os .Sh NAME @@ -136,10 +139,62 @@ to terminate the route tracing). If something is listening on a port in the default range, this option can be used to pick an unused port range. .It Fl P Ar proto -Send packets of specified IP protocol. -The currently supported protocols -are: UDP, UDP-Lite, TCP, SCTP, GRE and ICMP. -Other protocols may also be specified (either by name or by number), though +Use packets of specified IP protocol when sending probes. +The +.Ar proto +argument may be one of the following: +.Bl -tag -width Ar udplite +.It Ar udp +Use +.Xr udp 4 +packets. +This is the default. +.It Ar icmp +Use +.Xr icmp 4 +.Dq echo request +packets. +.It Ar udplite +Use +.Xr udplite 4 +packets. +.It Ar tcp +Use +.Xr tcp 4 +.Dq SYN +packets. +This will cause a successful traceroute to end with no response (i.e., a +.Dq * +response) since +.Nm +does not know how to detect the RST or SYN+ACK response from the +destination host. +.It Ar sctp +Use +.Xr sctp 4 +packets. +The +.Ar packetlen +argument must be a multiple of 4. +SCTP probes will be constructed as SCTP +.Dq INIT +chunks, unless the packet length is too small, in which case the probes +will be SCTP +.Dq SHUTDOWN-ACK +chunks followed by zero or one +.Dq PAD +chunks. +.It Ar gre +Use +.Xr gre 4 +packets. +The GRE packets will be constructed as if they contain a PPTP +(Point-to-Point Tunneling Protocol) payload. +.El +.Pp +Other protocols may also be specified, either by number or by name (see +.Xr protocols 5 ) , +though .Nm does not implement any special knowledge of their packet formats. This option is useful for determining which router along a path may be blocking diff --git a/usr.sbin/traceroute6/traceroute6.8 b/usr.sbin/traceroute6/traceroute6.8 index f185b8087411..406a96a04424 100644 --- a/usr.sbin/traceroute6/traceroute6.8 +++ b/usr.sbin/traceroute6/traceroute6.8 @@ -27,7 +27,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd February 2, 2024 +.Dd November 12, 2024 .Dt TRACEROUTE6 8 .Os .\" @@ -75,7 +75,7 @@ .Sh DESCRIPTION The .Nm -utility uses the IPv6 protocol hop limit field to elicit an ICMPv6 +utility uses the IPv6 protocol hop limit field to elicit an ICMP6 TIME_EXCEEDED response from each gateway along the path to some host. .Pp The only mandatory parameter is the destination host name or IPv6 address. @@ -191,6 +191,8 @@ Destination Unreachable - Not a Neighbour. Destination Unreachable - Address Unreachable. .It !H Parameter Problem - Unrecognized Next Header Type. +.It !<num> +ICMP6 unreachable code <num>. .It !\& This is printed if the hop limit is <= 1 on a port unreachable message. This means that the packet got to the destination, but that the reply had a hop diff --git a/usr.sbin/traceroute6/traceroute6.c b/usr.sbin/traceroute6/traceroute6.c index bfa840b3b1c2..173e97c13bb3 100644 --- a/usr.sbin/traceroute6/traceroute6.c +++ b/usr.sbin/traceroute6/traceroute6.c @@ -1009,6 +1009,10 @@ main(int argc, char *argv[]) printf(" !"); ++got_there; break; + default: + ++unreachable; + printf(" !<%d>", code & 0xff); + break; } } else if (type == ICMP6_PARAM_PROB && code == ICMP6_PARAMPROB_NEXTHEADER) { diff --git a/usr.sbin/trim/trim.8 b/usr.sbin/trim/trim.8 index 1ac10d7e3d46..a4874c54c183 100644 --- a/usr.sbin/trim/trim.8 +++ b/usr.sbin/trim/trim.8 @@ -23,7 +23,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd January 18, 2019 +.Dd July 20, 2025 .Dt TRIM 8 .Os .Sh NAME @@ -36,7 +36,7 @@ .Bk -words .Sm off .Ar offset -.Op Cm K | k | M | m | G | g | T | t ] +.Op Cm K | k | M | m | G | g | T | t | P | p | E | e ] .Sm on .Xc .Ek @@ -68,13 +68,13 @@ Overrides .It Fl l Xo .Sm off .Ar offset -.Op Cm K | k | M | m | G | g | T | t +.Op Cm K | k | M | m | G | g | T | t | P | p | E | e .Sm on .Xc .It Fl o Xo .Sm off .Ar offset -.Op Cm K | k | M | m | G | g | T | t +.Op Cm K | k | M | m | G | g | T | t | P | p | E | e .Sm on .Xc Specify the length @@ -88,12 +88,14 @@ unless one or both of these options are presented. The argument may be suffixed with one of .Cm K , .Cm M , -.Cm G +.Cm G , +.Cm T , +.Cm P or -.Cm T +.Cm E (either upper or lower case) to indicate a multiple of -Kilobytes, Megabytes, Gigabytes or Terabytes -respectively. +Kilobytes, Megabytes, Gigabytes, Terabytes, Petabytes or +Exabytes, respectively. .It Fl q Do not output anything except of possible error messages (quiet mode). Overrides diff --git a/usr.sbin/trim/trim.c b/usr.sbin/trim/trim.c index 3e187faa0fb3..27f57ac2fb72 100644 --- a/usr.sbin/trim/trim.c +++ b/usr.sbin/trim/trim.c @@ -114,7 +114,7 @@ main(int argc, char **argv) * * trim -f -- /dev/da0 -r rfile */ - + if (strcmp(argv[optind-1], "--") != 0) { for (ch = optind; ch < argc; ch++) if (argv[ch][0] == '-') @@ -127,6 +127,9 @@ main(int argc, char **argv) if (argc < 1) usage(name); + if (dryrun) + printf("dry run: add -f to actually perform the operation\n"); + while ((fname = *argv++) != NULL) if (trim(fname, offset, length, dryrun, verbose) < 0) error++; @@ -213,10 +216,8 @@ trim(const char *path, off_t offset, off_t length, bool dryrun, bool verbose) printf("trim %s offset %ju length %ju\n", path, (uintmax_t)offset, (uintmax_t)length); - if (dryrun) { - printf("dry run: add -f to actually perform the operation\n"); + if (dryrun) return (0); - } fd = opendev(path, O_RDWR | O_DIRECT); arg[0] = offset; @@ -237,7 +238,7 @@ static void usage(const char *name) { (void)fprintf(stderr, - "usage: %s [-[lo] offset[K|k|M|m|G|g|T|t]] [-r rfile] [-Nfqv] device ...\n", + "usage: %s [-[lo] offset[K|k|M|m|G|g|T|t|P|p|E|e]] [-r rfile] [-Nfqv] device ...\n", name); exit(EX_USAGE); } diff --git a/usr.sbin/uathload/uathload.8 b/usr.sbin/uathload/uathload.8 index 41cb2b4c1da7..7889245058e9 100644 --- a/usr.sbin/uathload/uathload.8 +++ b/usr.sbin/uathload/uathload.8 @@ -23,12 +23,12 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 11, 2010 +.Dd July 15, 2025 .Dt UATHLOAD 8 .Os .Sh NAME .Nm uathload -.Nd "firmware loader for Atheros USB wireless driver" +.Nd load firmware for Atheros USB wireless devices .Sh SYNOPSIS .Nm .Op Fl v diff --git a/usr.sbin/unbound/setup/local-unbound-setup.sh b/usr.sbin/unbound/setup/local-unbound-setup.sh index d52534b46fa3..d57d74952fc7 100755 --- a/usr.sbin/unbound/setup/local-unbound-setup.sh +++ b/usr.sbin/unbound/setup/local-unbound-setup.sh @@ -259,7 +259,7 @@ gen_unbound_conf() { echo " pidfile: ${pidfile}" echo " auto-trust-anchor-file: ${anchor}" if [ "${use_tls}" = "yes" ] ; then - echo " tls-system-cert: yes" + echo " tls-cert-bundle: /etc/ssl/cert.pem" fi echo "" if [ -f "${forward_conf}" ] ; then diff --git a/usr.sbin/usbconfig/dump.c b/usr.sbin/usbconfig/dump.c index 2a4a5300efeb..10ff2125853e 100644 --- a/usr.sbin/usbconfig/dump.c +++ b/usr.sbin/usbconfig/dump.c @@ -100,6 +100,8 @@ dump_speed(uint8_t value) return ("VARIABLE (52-480Mbps)"); case LIBUSB20_SPEED_SUPER: return ("SUPER (5.0Gbps)"); + case LIBUSB20_SPEED_SUPER_PLUS: + return ("SUPER+(10-20Gbps)"); default: break; } diff --git a/usr.sbin/watch/watch.8 b/usr.sbin/watch/watch.8 index 7acd79df8710..3cc72267f207 100644 --- a/usr.sbin/watch/watch.8 +++ b/usr.sbin/watch/watch.8 @@ -28,7 +28,7 @@ The utility writes to standard output. .Pp The options are as follows: -.Bl -tag -width indent +.Bl -tag -width "-f snpdev" .It Fl c Reconnect on close. If the tty observed by diff --git a/usr.sbin/watchdogd/watchdogd.c b/usr.sbin/watchdogd/watchdogd.c index 88b467486da1..27123f2143d0 100644 --- a/usr.sbin/watchdogd/watchdogd.c +++ b/usr.sbin/watchdogd/watchdogd.c @@ -63,25 +63,25 @@ static long fetchtimeout(int opt, const char *longopt, const char *myoptarg, int zero_ok); static void parseargs(int, char *[]); -static int seconds_to_pow2ns(int); static void sighandler(int); static void watchdog_loop(void); static int watchdog_init(void); static int watchdog_onoff(int onoff); -static int watchdog_patpat(u_int timeout); +static int watchdog_patpat(sbintime_t); static void usage(void); -static int tstotv(struct timeval *tv, struct timespec *ts); static int tvtohz(struct timeval *tv); static int debugging = 0; static int end_program = 0; static const char *pidfile = _PATH_VARRUN "watchdogd.pid"; -static u_int timeout = WD_TO_128SEC; +static sbintime_t timeout = 128 * SBT_1S; static u_int exit_timeout = WD_TO_NEVER; static u_int pretimeout = 0; static u_int timeout_sec; static u_int nap = 10; +#ifdef notyet static int passive = 0; +#endif static int is_daemon = 0; static int is_dry_run = 0; /* do not arm the watchdog, only report on timing of the watch @@ -174,38 +174,23 @@ main(int argc, char *argv[]) pidfile_remove(pfh); return (EX_OK); } else { - if (passive) - timeout |= WD_PASSIVE; - else - timeout |= WD_ACTIVE; if (watchdog_patpat(timeout) < 0) err(EX_OSERR, "patting the dog"); return (EX_OK); } } -static void -pow2ns_to_ts(int pow2ns, struct timespec *ts) -{ - uint64_t ns; - - ns = 1ULL << pow2ns; - ts->tv_sec = ns / 1000000000ULL; - ts->tv_nsec = ns % 1000000000ULL; -} - /* * Convert a timeout in seconds to N where 2^N nanoseconds is close to * "seconds". * * The kernel expects the timeouts for watchdogs in "2^N nanosecond format". */ -static u_int -parse_timeout_to_pow2ns(char opt, const char *longopt, const char *myoptarg) +static sbintime_t +parse_timeout_to_sbt(char opt, const char *longopt, const char *myoptarg) { - double a; - u_int rv; - struct timespec ts; + long a; + sbintime_t rv; struct timeval tv; int ticks; char shortopt[] = "- "; @@ -216,19 +201,17 @@ parse_timeout_to_pow2ns(char opt, const char *longopt, const char *myoptarg) a = fetchtimeout(opt, longopt, myoptarg, 1); if (a == 0) - rv = WD_TO_NEVER; + rv = 0; else - rv = seconds_to_pow2ns(a); - pow2ns_to_ts(rv, &ts); - tstotv(&tv, &ts); + rv = a * SBT_1S; + tv = sbttotv(rv); ticks = tvtohz(&tv); if (debugging) { printf("Timeout for %s%s " - "is 2^%d nanoseconds " - "(in: %s sec -> out: %jd sec %ld ns -> %d ticks)\n", + "is " + "(in: %s sec -> out: %jd sec %ld us -> %d ticks)\n", longopt ? "-" : "", longopt ? longopt : shortopt, - rv, - myoptarg, (intmax_t)ts.tv_sec, ts.tv_nsec, ticks); + myoptarg, (intmax_t)tv.tv_sec, tv.tv_usec, ticks); } if (ticks <= 0) { errx(1, "Timeout for %s%s is too small, please choose a higher timeout.", longopt ? "-" : "", longopt ? longopt : shortopt); @@ -364,7 +347,7 @@ watchdog_loop(void) } if (failed == 0) - watchdog_patpat(timeout|WD_ACTIVE); + watchdog_patpat(timeout); waited = watchdog_check_dogfunction_time(&ts_start, &ts_end); if (nap - waited > 0) @@ -387,13 +370,22 @@ try_end: * to keep the watchdog from firing. */ static int -watchdog_patpat(u_int t) +watchdog_patpat(sbintime_t sbt) { if (is_dry_run) return 0; - return ioctl(fd, WDIOCPATPAT, &t); + return ioctl(fd, WDIOC_SETTIMEOUT, &sbt); +} + +static int +watchdog_control(u_int control) +{ + if (is_dry_run) + return (0); + + return ioctl(fd, WDIOC_CONTROL, &control); } /* @@ -420,7 +412,7 @@ watchdog_onoff(int onoff) warn("setting WDIOC_SETSOFT %d", softtimeout_set); return (error); } - error = watchdog_patpat((timeout|WD_ACTIVE)); + error = watchdog_patpat(timeout); if (error) { warn("watchdog_patpat failed"); goto failsafe; @@ -452,12 +444,12 @@ watchdog_onoff(int onoff) } } /* pat one more time for good measure */ - return watchdog_patpat((timeout|WD_ACTIVE)); + return watchdog_patpat(timeout); } else { - return watchdog_patpat(exit_timeout); + return watchdog_control(WD_CTRL_DISABLE); } failsafe: - watchdog_patpat(exit_timeout); + watchdog_control(WD_CTRL_DISABLE); return (error); } @@ -567,15 +559,6 @@ timeout_act_str2int(const char *lopt, const char *acts) return rv; } -int -tstotv(struct timeval *tv, struct timespec *ts) -{ - - tv->tv_sec = ts->tv_sec; - tv->tv_usec = ts->tv_nsec / 1000; - return 0; -} - /* * Convert a timeval to a number of ticks. * Mostly copied from the kernel. @@ -647,30 +630,6 @@ tvtohz(struct timeval *tv) return ((int)ticks); } -static int -seconds_to_pow2ns(int seconds) -{ - uint64_t power; - uint64_t ns; - uint64_t shifted; - - if (seconds <= 0) - errx(1, "seconds %d < 0", seconds); - ns = ((uint64_t)seconds) * 1000000000ULL; - power = flsll(ns); - shifted = 1ULL << power; - if (shifted <= ns) { - power++; - } - if (debugging) { - printf("shifted %lld\n", (long long)shifted); - printf("seconds_to_pow2ns: seconds: %d, ns %lld, power %d\n", - seconds, (long long)ns, (int)power); - } - return (power); -} - - /* * Handle the few command line arguments supported. */ @@ -683,8 +642,7 @@ parseargs(int argc, char *argv[]) const char *lopt; /* Get the default value of timeout_sec from the default timeout. */ - pow2ns_to_ts(timeout, &ts); - timeout_sec = ts.tv_sec; + timeout_sec = sbintime_getsec(timeout); /* * if we end with a 'd' aka 'watchdogd' then we are the daemon program, @@ -727,10 +685,10 @@ parseargs(int argc, char *argv[]) break; case 't': timeout_sec = atoi(optarg); - timeout = parse_timeout_to_pow2ns(c, NULL, optarg); + timeout = parse_timeout_to_sbt(c, NULL, optarg); if (debugging) - printf("Timeout is 2^%d nanoseconds\n", - timeout); + printf("Timeout is %d\n", + (int)(timeout / SBT_1S)); break; case 'T': carp_thresh_seconds = @@ -740,7 +698,7 @@ parseargs(int argc, char *argv[]) do_timedog = 1; break; case 'x': - exit_timeout = parse_timeout_to_pow2ns(c, NULL, optarg); + exit_timeout = parse_timeout_to_sbt(c, NULL, optarg); if (exit_timeout != 0) exit_timeout |= WD_ACTIVE; break; diff --git a/usr.sbin/wlanstats/wlanstats.c b/usr.sbin/wlanstats/wlanstats.c index 588b67ddd417..83f5010341a9 100644 --- a/usr.sbin/wlanstats/wlanstats.c +++ b/usr.sbin/wlanstats/wlanstats.c @@ -394,6 +394,8 @@ static const struct fmt wlanstats[] = { { 9, "gcmp_nomem", "gcmpnomem", "No memory available (GCMP)" }, #define S_RX_GCMPNOSPC AFTER(S_RX_GCMPNOMEM) { 9, "gcmp_nospc", "gcmpnospc", "No mbuf space available (GCMP)" }, +#define S_CRYPTO_SWCIPHERFAIL AFTER(S_RX_GCMPNOSPC) + { 12, "crypto_swcipherfail", "swcipherfail", "No matching software cipher support" }, }; struct wlanstatfoo_p { @@ -848,6 +850,7 @@ wlan_get_curstat(struct bsdstat *sf, int s, char b[], size_t bs) case S_RX_GCMPMIC: STAT(rx_gcmpmic); case S_RX_GCMPNOMEM: STAT(crypto_gcmp_nomem); case S_RX_GCMPNOSPC: STAT(crypto_gcmp_nospc); + case S_CRYPTO_SWCIPHERFAIL: STAT(crypto_swcipherfail); } return wlan_getinfo(wf, s, b, bs); #undef NSTAT @@ -1019,6 +1022,7 @@ wlan_get_totstat(struct bsdstat *sf, int s, char b[], size_t bs) case S_RX_GCMPMIC: STAT(rx_gcmpmic); case S_RX_GCMPNOMEM: STAT(crypto_gcmp_nomem); case S_RX_GCMPNOSPC: STAT(crypto_gcmp_nospc); + case S_CRYPTO_SWCIPHERFAIL: STAT(crypto_swcipherfail); } return wlan_getinfo(wf, s, b, bs); #undef NSTAT diff --git a/usr.sbin/ypldap/ldapclient.c b/usr.sbin/ypldap/ldapclient.c index acd4410d939f..a246a25a9605 100644 --- a/usr.sbin/ypldap/ldapclient.c +++ b/usr.sbin/ypldap/ldapclient.c @@ -385,7 +385,7 @@ ldapclient(int pipe_main2client[2]) ypldap_process = PROC_CLIENT; #ifndef DEBUG - if (setgroups(1, &pw->pw_gid) || + if (setgroups(0, NULL) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("cannot drop privileges"); diff --git a/usr.sbin/ypldap/ypldap.c b/usr.sbin/ypldap/ypldap.c index 01b5955aa822..b9e938227831 100644 --- a/usr.sbin/ypldap/ypldap.c +++ b/usr.sbin/ypldap/ypldap.c @@ -602,7 +602,7 @@ main(int argc, char *argv[]) fatal("getpwnam"); #ifndef DEBUG - if (setgroups(1, &pw->pw_gid) || + if (setgroups(0, NULL) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("cannot drop privileges"); diff --git a/usr.sbin/ypldap/ypldap_dns.c b/usr.sbin/ypldap/ypldap_dns.c index 09ce636ebdc8..9dbbf26d237b 100644 --- a/usr.sbin/ypldap/ypldap_dns.c +++ b/usr.sbin/ypldap/ypldap_dns.c @@ -91,7 +91,7 @@ ypldap_dns(int pipe_ntp[2], struct passwd *pw) setproctitle("dns engine"); close(pipe_ntp[0]); - if (setgroups(1, &pw->pw_gid) || + if (setgroups(0, NULL) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("can't drop privileges"); |