├── tools ├── helios-build │ ├── .gitignore │ ├── rustfmt.toml │ ├── Cargo.toml │ └── src │ │ ├── zfs.rs │ │ ├── common.rs │ │ ├── expand.rs │ │ ├── archive.rs │ │ ├── ensure.rs │ │ └── illumos.rs └── packages │ ├── os-conflicts.mogrify │ └── os-deps.mogrify ├── image ├── templates │ ├── files │ │ ├── auto_master │ │ ├── max-power.stress-ng │ │ ├── bash_profile │ │ ├── hosts │ │ ├── compliance-beacon.sh │ │ ├── nvme-generic.fio │ │ ├── dumpadm.conf │ │ ├── chrony.conf │ │ ├── nvme-seqread.fio │ │ ├── nvme-randread.fio │ │ ├── nvme-seqwrite.fio │ │ ├── nvme-randwrite.fio │ │ ├── rootisramdisk │ │ ├── default_init │ │ ├── compliance-hostname.sh │ │ ├── recovery-hostname.sh │ │ ├── compliance-cpu-loadgen.sh │ │ ├── bootparams.sh │ │ ├── compliance-pinger.sh │ │ ├── compliance-mem-loadgen.sh │ │ ├── mfg.sh │ │ ├── motd │ │ ├── sled-postboot.sh │ │ ├── nvme-fio.sh │ │ ├── site.xml │ │ ├── compliance-tofino.xml │ │ ├── compliance-postboot.xml │ │ ├── compliance-cpu-loadgen.xml │ │ ├── site-mfg.xml │ │ ├── mfg.xml │ │ ├── site-compliance.xml │ │ ├── compliance-pinger.xml │ │ ├── compliance-dump.xml │ │ ├── sled-system-zfs:dbuf │ │ ├── compliance-hostname.xml │ │ ├── compliance-mem-loadgen.xml │ │ ├── compliance-time.xml │ │ ├── recovery-hostname.xml │ │ ├── net-setup.sh │ │ ├── compliance-nvme-loadgen.sh │ │ ├── bashrc │ │ ├── compliance-beacon.xml │ │ ├── ttydefs.3000000 │ │ ├── compliance-nvme-loadgen.xml │ │ ├── net-setup.xml │ │ ├── sled-postboot.xml │ │ ├── mfg_default_login │ │ ├── sshd_config │ │ └── dhcpagent │ ├── include │ │ ├── root-noauth.json │ │ ├── t6-firmware.json │ │ ├── stress.json │ │ ├── devfs.json │ │ ├── common.json │ │ ├── smf-reduce.json │ │ └── compliance-common.json │ └── sled │ │ ├── ramdisk-03-recovery-trim.json │ │ ├── targets.toml │ │ ├── zfs-recovery.json │ │ ├── zfs.json │ │ ├── ramdisk-01-os.json │ │ └── ramdisk-02-trim.json ├── amd │ ├── turin-cosmo-1.0.0.7-mbist.toml │ ├── turin-cosmo-1.0.0.7-mbist-noaggr.toml │ ├── milan-gimlet-b-1.0.0.h.toml │ ├── turin-cosmo-a-mbist.patch │ ├── turin-cosmo-a-mbist-noaggr.patch │ └── turin-cosmo-1.0.0.7.toml └── mkcpio.sh ├── .gitignore ├── Makefile ├── config └── projects.toml └── LICENSE /tools/helios-build/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /image/templates/files/auto_master: -------------------------------------------------------------------------------- 1 | +auto_master 2 | /net -hosts -nosuid,nobrowse 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /projects 2 | /packages 3 | /helios-build 4 | /cache 5 | /tmp 6 | /image/output 7 | -------------------------------------------------------------------------------- /tools/helios-build/rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | edition = "2021" 3 | use_small_heuristics = "Max" 4 | -------------------------------------------------------------------------------- /image/templates/files/max-power.stress-ng: -------------------------------------------------------------------------------- 1 | verbose 2 | verify 3 | 4 | matrix 114 5 | 6 | stream 14 7 | stream-l3-size 256m 8 | -------------------------------------------------------------------------------- /image/templates/files/bash_profile: -------------------------------------------------------------------------------- 1 | if [[ $TERM == sun-color ]]; then 2 | export TERM=xterm 3 | fi 4 | 5 | ENV="$HOME/.bashrc" 6 | . "$ENV" 7 | -------------------------------------------------------------------------------- /image/templates/files/hosts: -------------------------------------------------------------------------------- 1 | # 2 | # Internet host table 3 | # 4 | ::1 unknown unknown.local localhost loghost 5 | 127.0.0.1 unknown unknown.local localhost loghost 6 | -------------------------------------------------------------------------------- /image/amd/turin-cosmo-1.0.0.7-mbist.toml: -------------------------------------------------------------------------------- 1 | base = 'turin-cosmo-1.0.0.7.toml' 2 | 3 | [patch] 4 | base = 'turin-cosmo-a.efs.json5' 5 | diff = 'turin-cosmo-a-mbist.patch' 6 | -------------------------------------------------------------------------------- /image/amd/turin-cosmo-1.0.0.7-mbist-noaggr.toml: -------------------------------------------------------------------------------- 1 | base = 'turin-cosmo-1.0.0.7.toml' 2 | 3 | [patch] 4 | base = 'turin-cosmo-a.efs.json5' 5 | diff = 'turin-cosmo-a-mbist-noaggr.patch' 6 | -------------------------------------------------------------------------------- /tools/packages/os-conflicts.mogrify: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 Oxide Computer Company 3 | # 4 | 5 | drop> 6 | drop> 7 | -------------------------------------------------------------------------------- /image/templates/files/compliance-beacon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2024 Oxide Computer Company 4 | # 5 | 6 | set -o errexit 7 | set -o pipefail 8 | set -o xtrace 9 | 10 | exec pilot host announce 11 | -------------------------------------------------------------------------------- /image/templates/files/nvme-generic.fio: -------------------------------------------------------------------------------- 1 | [nvme-generic] 2 | ioengine=${IOENGINE} 3 | rw=${RW} 4 | bs=${BLOCK_SIZE} 5 | iodepth=${IODEPTH} 6 | filename=${FILENAME} 7 | numjobs=${NUMJOBS} 8 | runtime=2d 9 | time_based 10 | -------------------------------------------------------------------------------- /image/templates/files/dumpadm.conf: -------------------------------------------------------------------------------- 1 | # 2 | # dumpadm.conf 3 | # 4 | # Configuration parameters for system crash dump. 5 | # Do NOT edit this file by hand -- use dumpadm(8) instead. 6 | # 7 | DUMPADM_CONTENT=curproc 8 | DUMPADM_ENABLE=no 9 | DUMPADM_CSAVE=on 10 | -------------------------------------------------------------------------------- /image/templates/files/chrony.conf: -------------------------------------------------------------------------------- 1 | pool 0.omnios.pool.ntp.org iburst 2 | 3 | driftfile /var/lib/chrony/drift 4 | ntsdumpdir /var/lib/chrony 5 | dumpdir /var/lib/chrony 6 | pidfile /var/run/chrony/chronyd.pid 7 | 8 | makestep 1.0 3 9 | 10 | allow fe80::/10 11 | local stratum 10 12 | -------------------------------------------------------------------------------- /image/templates/files/nvme-seqread.fio: -------------------------------------------------------------------------------- 1 | [nvme-seqread] 2 | ioengine=${IOENGINE} 3 | rw=read 4 | bs=${BLOCK_SIZE} 5 | direct=${DIRECT} 6 | buffered=${BUFFERED} 7 | iodepth=${IODEPTH} 8 | size=${SIZE} 9 | filename=${FILENAME} 10 | numjobs=${NUMJOBS} 11 | runtime=2d 12 | time_based 13 | -------------------------------------------------------------------------------- /image/templates/files/nvme-randread.fio: -------------------------------------------------------------------------------- 1 | [nvme-randread] 2 | ioengine=${IOENGINE} 3 | rw=randread 4 | bs=${BLOCK_SIZE} 5 | direct=${DIRECT} 6 | buffered=${BUFFERED} 7 | iodepth=${IODEPTH} 8 | size=${SIZE} 9 | filename=${FILENAME} 10 | numjobs=${NUMJOBS} 11 | runtime=2d 12 | time_based 13 | -------------------------------------------------------------------------------- /image/templates/files/nvme-seqwrite.fio: -------------------------------------------------------------------------------- 1 | [nvme-seqwrite] 2 | ioengine=${IOENGINE} 3 | rw=write 4 | bs=${BLOCK_SIZE} 5 | direct=${DIRECT} 6 | buffered=${BUFFERED} 7 | iodepth=${IODEPTH} 8 | size=${SIZE} 9 | filename=${FILENAME} 10 | numjobs=${NUMJOBS} 11 | runtime=2d 12 | time_based 13 | -------------------------------------------------------------------------------- /image/templates/files/nvme-randwrite.fio: -------------------------------------------------------------------------------- 1 | [nvme-randwrite] 2 | ioengine=${IOENGINE} 3 | rw=randwrite 4 | bs=${BLOCK_SIZE} 5 | direct=${DIRECT} 6 | buffered=${BUFFERED} 7 | iodepth=${IODEPTH} 8 | size=${SIZE} 9 | filename=${FILENAME} 10 | numjobs=${NUMJOBS} 11 | runtime=2d 12 | time_based 13 | -------------------------------------------------------------------------------- /image/templates/files/rootisramdisk: -------------------------------------------------------------------------------- 1 | #!/sbin/sh 2 | 3 | # 4 | # Copyright 2024 Oxide Computer Company 5 | # 6 | 7 | # 8 | # The real rootisramdisk needs to be able to look through ZFS, which presently 9 | # obfuscates the ramdisk reality somewhat. For now, we shall fake it until we 10 | # make it. 11 | # 12 | exit 0 13 | -------------------------------------------------------------------------------- /image/templates/include/root-noauth.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [ 3 | { "t": "shadow", "username": "root", "password": "" }, 4 | { "t": "ensure_file", 5 | "file": "/etc/default/login", 6 | "src": "mfg_default_login", 7 | "owner": "root", "group": "sys", "mode": "0644" } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /image/templates/sled/ramdisk-03-recovery-trim.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataset": { 3 | "name": "ramdisk", 4 | "input_snapshot": "trim", 5 | "output_snapshot": "recovery-trim" 6 | }, 7 | 8 | "steps": [ 9 | { "t": "include", "name": "recovery-elide" }, 10 | { "t": "pack_tar", "name": "sled-recovery-ramdisk.tar" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /image/templates/files/default_init: -------------------------------------------------------------------------------- 1 | # 2 | # This file is /etc/default/init. 3 | # This file looks like a shell script, but it is not. 4 | # 5 | # Lines of this file should be of the form VAR=value, where VAR is one of 6 | # TZ, LANG, CMASK, or any of the LC_* environment variables. value may 7 | # be enclosed in double quotes (") or single quotes ('). 8 | # 9 | TZ=UTC 10 | CMASK=022 11 | -------------------------------------------------------------------------------- /image/templates/files/compliance-hostname.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2024 Oxide Computer Company 4 | # 5 | 6 | set -o errexit 7 | set -o pipefail 8 | set -o xtrace 9 | 10 | # 11 | # Use the system serial number for the hostname: 12 | # 13 | if sn=$(pilot local info -i); then 14 | echo "$sn" > /etc/nodename 15 | sed -i -e "s/unknown/$sn/g" /etc/inet/hosts 16 | fi 17 | 18 | exit 0 19 | -------------------------------------------------------------------------------- /image/templates/sled/targets.toml: -------------------------------------------------------------------------------- 1 | [gimlet] 2 | efs = "milan-gimlet-b.efs.json5" 3 | app = "milan-gimlet-b-1.0.0.h.toml" 4 | 5 | [cosmo] 6 | efs = "turin-cosmo-a.efs.json5" 7 | app = "turin-cosmo-1.0.0.7.toml" 8 | 9 | [cosmo-mbist] 10 | app = "turin-cosmo-1.0.0.7-mbist.toml" 11 | feature = "mbist" 12 | 13 | [cosmo-mbist-noaggr] 14 | app = "turin-cosmo-1.0.0.7-mbist-noaggr.toml" 15 | feature = "mbist" 16 | -------------------------------------------------------------------------------- /image/templates/files/recovery-hostname.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ksh 2 | # 3 | # Copyright 2024 Oxide Computer Company 4 | # 5 | 6 | export PATH=/usr/bin:/usr/sbin:/sbin 7 | 8 | set -o errexit 9 | set -o pipefail 10 | 11 | sn=$(prtconf -v /devices | 12 | awk -F"'" 'f { print $2; exit } /baseboard-identifier/ { f=1 }') 13 | 14 | if [[ ! -n "$sn" ]]; then 15 | exit 1 16 | fi 17 | 18 | echo "$sn" > /etc/nodename 19 | sed -i -e "s/unknown/$sn/g" /etc/inet/hosts 20 | 21 | exit 0 22 | -------------------------------------------------------------------------------- /image/templates/files/compliance-cpu-loadgen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ksh93 2 | # 3 | # Copyright 2025 Oxide Computer Company 4 | # 5 | 6 | . /lib/svc/share/smf_include.sh 7 | 8 | if (( $# != 1 )); then 9 | echo "usage: compliance-cpu-loadgen.sh " >&2 10 | exit $SMF_EXIT_ERR_FATAL 11 | fi 12 | 13 | integer nthreads=$1 14 | 15 | if (( nthreads == 0 )); then 16 | nthreads=$(psrinfo -t) 17 | fi 18 | 19 | for i in {1..$nthreads}; do 20 | while true; do :; done & 21 | done 22 | 23 | exit $SMF_EXIT_OK 24 | -------------------------------------------------------------------------------- /image/templates/files/bootparams.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2024 Oxide Computer Company 4 | # 5 | 6 | prtconf -v /devices | awk -v want="$1" " 7 | /name='/ && /type=string/ && /items=1/ { 8 | n = \$1; 9 | sub(\".*name='\", \"\", n); 10 | sub(\"'.*\", \"\", n); 11 | next; 12 | } 13 | 14 | n && /value='/ { 15 | v = \$1; 16 | sub(\".*value='\", \"\", v); 17 | sub(\"'.*\", \"\", v); 18 | if (want == \"\") { 19 | printf(\"%s=%s\\n\", n, v); 20 | } else if (want == n) { 21 | printf(\"%s\\n\", v); 22 | } 23 | n = 0; 24 | next; 25 | } 26 | " 27 | -------------------------------------------------------------------------------- /image/templates/files/compliance-pinger.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ksh93 2 | # 3 | # Copyright 2025 Oxide Computer Company 4 | # 5 | 6 | . /lib/svc/share/smf_include.sh 7 | 8 | function fatal { 9 | echo "$@" >&2 10 | exit $SMF_EXIT_ERR_FATAL 11 | } 12 | 13 | # The only required argument is the name of the interface (the service instance 14 | # name). 15 | typeset -r intf="$1" 16 | [[ -z "$intf" ]] && fatal "Usage: compliance-pinger " 17 | 18 | smf_present || fatal "Service Management framework not initialized." 19 | 20 | smf_clear_env 21 | /usr/sbin/ping -I 0.01 -sn ff02::1%$intf >/dev/null & 22 | 23 | exit $SMF_EXIT_OK 24 | -------------------------------------------------------------------------------- /image/templates/files/compliance-mem-loadgen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ksh93 2 | # 3 | # Copyright 2025 Oxide Computer Company 4 | # 5 | 6 | . /lib/svc/share/smf_include.sh 7 | 8 | function fatal { 9 | echo "$@" >&2 10 | exit $SMF_EXIT_ERR_FATAL 11 | } 12 | 13 | if (( $# != 1 )); then 14 | fatal "usage: compliance-mem-loadgen " 15 | fi 16 | 17 | JOB_FILE=$1 18 | STRESS_NG=/opt/ooce/bin/stress-ng 19 | 20 | if [[ ! -x $STRESS_NG ]]; then 21 | fatal "Cannot find executable file $STRESS_NG" 22 | fi 23 | 24 | if [[ ! -f $JOB_FILE ]]; then 25 | echo "compliance-mem-loadgen: Cannot find job file $JOB_FILE" >&2 26 | exit $SMF_EXIT_ERR_CONFIG 27 | fi 28 | 29 | $STRESS_NG --job $JOB_FILE & 30 | 31 | exit $SMF_EXIT_OK 32 | -------------------------------------------------------------------------------- /tools/helios-build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "helios-build" 3 | version = "0.0.0" 4 | edition = "2021" 5 | license = "MPL-2.0" 6 | # 7 | # Report a specific error in the case that the toolchain is too old for 8 | # let-else: 9 | # 10 | rust-version = "1.65.0" 11 | 12 | [dependencies] 13 | anyhow = "1" 14 | flate2 = { version = "1", features = [ "zlib" ] } 15 | getopts = "0.2" 16 | helios-build-utils = { git = "https://github.com/oxidecomputer/helios-omicron-brand.git" } 17 | json5 = "0.4.1" 18 | libc = "0.2" 19 | serde = { version = "1", features = [ "derive" ] } 20 | serde_json = "1" 21 | slog = "2.5" 22 | slog-term = "2.5" 23 | tar = "0.4" 24 | time = { version = "0.3" } 25 | toml = "0.8" 26 | walkdir = "2.3" 27 | -------------------------------------------------------------------------------- /image/templates/files/mfg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2025 Oxide Computer Company 4 | # 5 | 6 | set -o pipefail 7 | 8 | # 9 | # Find the PCI NIC we want; it will use one of the igb, e1000g, or ixgbe drivers. 10 | # 11 | nic= 12 | for try in $(dladm show-ether -po link); do 13 | if [[ $try != igb* ]] && [[ $try != e1000g* ]] && 14 | [[ $try != ixgbe* ]]; then 15 | continue 16 | fi 17 | 18 | nic=$try 19 | break 20 | done 21 | 22 | if [[ -z $nic ]]; then 23 | printf 'ERROR: no PCI NIC?\n' >&2 24 | exit 1 25 | fi 26 | 27 | # 28 | # Ping the all-hosts multicast address through that interface so that we can be 29 | # detected by the manufacturing control station. 30 | # 31 | while :; do 32 | ping -s -A inet6 -i "$nic" -n ff02::1 >/dev/null 2>&1 33 | sleep 1 34 | done 35 | -------------------------------------------------------------------------------- /image/templates/include/t6-firmware.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [ 3 | { "t": "ensure_dir", 4 | "dir": "/platform/Oxide,Gimlet/firmware", 5 | "owner": "root", "group": "sys", "mode": "0755" }, 6 | 7 | { "t": "ensure_file", 8 | "file": "/platform/Oxide,Gimlet/firmware/Oxide_t6_2x100Gbase_kr_nomemory_v8C_6_26_10.bin", 9 | "extsrc": "chelsio-t6-roms/srom/bins/Oxide_t6_2x100Gbase_kr_nomemory_v8C_6_26_10.bin", 10 | "owner": "root", "group": "sys", "mode": "0555" }, 11 | 12 | { "t": "ensure_symlink", 13 | "link": "/platform/Oxide,Gimlet/firmware/t6srom.bin", 14 | "target": "Oxide_t6_2x100Gbase_kr_nomemory_v8C_6_26_10.bin", 15 | "owner": "root", "group": "root" }, 16 | 17 | { "t": "ensure_symlink", 18 | "link": "/platform/Oxide,Cosmo", 19 | "target": "Oxide,Gimlet", 20 | "owner": "root", "group": "root" } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /image/templates/files/motd: -------------------------------------------------------------------------------- 1 | 2 | ##### 3 | ## ## 4 | ## # ## ## ## 5 | ## # ## ## ## Oxide Computer Company 6 | ## # ## ### Engineering 7 | ## ## ## ## 8 | ##### ## ## Compute Sled 9 | 10 | -------------------------------------------------------------------------------- /image/templates/files/sled-postboot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2024 Oxide Computer Company 4 | # 5 | 6 | set -o errexit 7 | set -o pipefail 8 | set -o xtrace 9 | 10 | cat >/dev/msglog </dev/msglog 30 | fi 31 | else 32 | printf 'WARNING: no data pool could be imported\n' \ 33 | >/dev/msglog 34 | fi 35 | 36 | exit 0 37 | -------------------------------------------------------------------------------- /image/amd/milan-gimlet-b-1.0.0.h.toml: -------------------------------------------------------------------------------- 1 | cpu = 'milan' 2 | board = 'gimlet-b' 3 | firmware_version = '1.0.0.h' 4 | size = 32 5 | blobs = [ 6 | 'AmdPubKey_gn.tkn', 7 | 'PspBootLoader_gn.sbin', 8 | 'PspRecoveryBootLoader_gn.sbin', 9 | 'SmuFirmwareGn.csbin', 10 | 'SecureDebugToken_gn.stkn', 11 | 'PspABLFw_gn.stkn', 12 | 'SmuFirmware2Gn.csbin', 13 | 'SecureDebugUnlock_gn.sbin', 14 | 'PspIkek_gn.bin', 15 | 'SecureEmptyToken.bin', 16 | 'RsmuSecPolicy_gn.sbin', 17 | 'Mp5Gn.csbin', 18 | 'AgesaBootloader_U_prod_GN.csbin', 19 | 'GnPhyFw.sbin', 20 | 'PSP-Key-DB_gn.sbin', 21 | 'Appb_GN_1D_Ddr4_Udimm_Imem.csbin', 22 | 'Appb_GN_1D_Ddr4_Udimm_Dmem.csbin', 23 | 'Appb_GN_1D_Ddr4_Rdimm_Imem.csbin', 24 | 'Appb_GN_1D_Ddr4_Rdimm_Dmem.csbin', 25 | 'Appb_GN_2D_Ddr4_Udimm_Imem.csbin', 26 | 'Appb_GN_2D_Ddr4_Udimm_Dmem.csbin', 27 | 'Appb_GN_2D_Ddr4_Rdimm_Imem.csbin', 28 | 'Appb_GN_2D_Ddr4_Rdimm_Dmem.csbin', 29 | 'Appb_GN_BIST_Ddr4_Udimm_Imem.csbin', 30 | 'Appb_GN_BIST_Ddr4_Udimm_Dmem.csbin', 31 | 'Appb_GN_BIST_Ddr4_Rdimm_Imem.csbin', 32 | 'Appb_GN_BIST_Ddr4_Rdimm_Dmem.csbin', 33 | 'Appb_GN_BIST_Ddr4_Lrdimm_Imem.csbin', 34 | 'Appb_GN_BIST_Ddr4_Lrdimm_Dmem.csbin', 35 | ] 36 | -------------------------------------------------------------------------------- /image/templates/files/nvme-fio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ksh93 2 | # 3 | # Runs fio (the "Flexible I/O tester") with the given profile. 4 | # By default, the output filenames are derived from NVMe devices 5 | # in the system, but this (among other things) can be overridden 6 | # via environment variables. 7 | # 8 | # Because this can be very destructive if not used carefully 9 | # (fio will happily open a device file and clobber anything on 10 | # the associated device), this prints the command it would run, 11 | # in a manner that can be inspected and then copied or piped 12 | # into a shell. 13 | # 14 | 15 | # 16 | # Copyright 2024 Oxide Computer Company 17 | # 18 | 19 | # 20 | # The only required argument is the name of the config. 21 | # 22 | if [[ "$#" != 1 ]]; then 23 | echo "Usage: nvme-fio.sh config" >&2 24 | exit 1 25 | fi 26 | CONFIG=$1 27 | OUTDIR=${OUTDIR:-/fio} 28 | typeset -a NVMES=($(diskinfo -Hp | awk '/^NVME/ {print "'"${OUTDIR}"'/"$2}')) 29 | 30 | echo env \ 31 | IOENGINE="${IOENGINE:-pvsync}" \ 32 | BLOCK_SIZE="${BLOCK_SIZE:-4k}" \ 33 | DIRECT="${DIRECT:-1}" \ 34 | BUFFERED="${BUFFERED:-0}" \ 35 | IODEPTH="${IODEPTH:-64}" \ 36 | SIZE="${SIZE:-64g}" \ 37 | RW="${RW}" \ 38 | FILENAME="${FILENAME:-$(IFS=':'; echo "${NVMES[*]}")}" \ 39 | NUMJOBS="${NUMJOBS:-${#NVMES[@]}}" \ 40 | fio nvme-${CONFIG}.fio 41 | -------------------------------------------------------------------------------- /image/templates/files/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tools/helios-build/src/zfs.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | /* 3 | * Copyright 2024 Oxide Computer Company 4 | */ 5 | 6 | use anyhow::{bail, Result}; 7 | 8 | pub fn dataset_exists(dataset: &str) -> Result { 9 | if dataset.contains('@') { 10 | bail!("no @ allowed here"); 11 | } 12 | 13 | let zfs = Command::new("/sbin/zfs") 14 | .env_clear() 15 | .arg("list") 16 | .arg("-Ho") 17 | .arg("name") 18 | .arg(dataset) 19 | .output()?; 20 | 21 | if !zfs.status.success() { 22 | let errmsg = String::from_utf8_lossy(&zfs.stderr); 23 | if errmsg.trim().ends_with("dataset does not exist") { 24 | return Ok(false); 25 | } 26 | bail!("zfs list failed: {}", errmsg); 27 | } 28 | 29 | Ok(true) 30 | } 31 | 32 | pub fn zfs_get(dataset: &str, n: &str) -> Result { 33 | let zfs = Command::new("/sbin/zfs") 34 | .env_clear() 35 | .arg("get") 36 | .arg("-H") 37 | .arg("-o") 38 | .arg("value") 39 | .arg(n) 40 | .arg(dataset) 41 | .output()?; 42 | 43 | if !zfs.status.success() { 44 | let errmsg = String::from_utf8_lossy(&zfs.stderr); 45 | bail!("zfs get failed: {}", errmsg); 46 | } 47 | 48 | let out = String::from_utf8(zfs.stdout)?; 49 | Ok(out.trim().to_string()) 50 | } 51 | -------------------------------------------------------------------------------- /image/templates/include/stress.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [ 3 | { "t": "ensure_file", 4 | "file": "/root/max-power.stress-ng", 5 | "src": "max-power.stress-ng", 6 | "owner": "root", "group": "root", "mode": "644" }, 7 | { "t": "ensure_file", 8 | "file": "/root/nvme-fio.sh", 9 | "src": "nvme-fio.sh", 10 | "owner": "root", "group": "root", "mode": "755" }, 11 | { "t": "ensure_file", 12 | "file": "/root/nvme-generic.fio", 13 | "src": "nvme-generic.fio", 14 | "owner": "root", "group": "root", "mode": "644" }, 15 | { "t": "ensure_file", 16 | "file": "/root/nvme-randread.fio", 17 | "src": "nvme-randread.fio", 18 | "owner": "root", "group": "root", "mode": "644" }, 19 | { "t": "ensure_file", 20 | "file": "/root/nvme-randwrite.fio", 21 | "src": "nvme-randwrite.fio", 22 | "owner": "root", "group": "root", "mode": "644" }, 23 | { "t": "ensure_file", 24 | "file": "/root/nvme-seqread.fio", 25 | "src": "nvme-seqread.fio", 26 | "owner": "root", "group": "root", "mode": "644" }, 27 | { "t": "ensure_file", 28 | "file": "/root/nvme-seqwrite.fio", 29 | "src": "nvme-seqwrite.fio", 30 | "owner": "root", "group": "root", "mode": "644" } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /image/templates/files/compliance-tofino.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /image/templates/files/compliance-postboot.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /image/templates/files/compliance-cpu-loadgen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /image/templates/files/site-mfg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /image/templates/files/mfg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /image/templates/files/site-compliance.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /image/templates/files/compliance-pinger.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /image/templates/files/compliance-dump.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /image/templates/files/sled-system-zfs:dbuf: -------------------------------------------------------------------------------- 1 | * 2 | * Normally, the dbuf cache is 1/32nd of RAM and the dbuf metadata cache is 3 | * 1/64th of RAM. On a 1TiB system, these are way, way too big for us -- 4 | * especially with 800GiB already spoken for. Moreover, the primary advantage 5 | * of the dbuf cache -- namely, eliminate the cost of uncompression on a dbuf 6 | * cache hit -- is negated by the non-compressability for Crucible data (which 7 | * is encrypted). We therefore tune these numbers down quite a bit, knowing 8 | * that any eviction from the dbuf cache can still be in the ARC. 9 | * 10 | set zfs:dbuf_cache_max_bytes = 0x40000000 11 | set zfs:dbuf_metadata_cache_max_bytes = 0x40000000 12 | 13 | * 14 | * By default, ZFS tries to internally aggregate multiple I/O operations, so it 15 | * can dispatch them as a single operation to the disk. This logic comes from 16 | * the era of spinning drives, where putting in the work to issue fewer, larger 17 | * commands would increase throughput. For SSDs, and especially the compute 18 | * sled SSDs, it instead generates extra work within ZFS that reduces 19 | * throughput. 20 | * 21 | * We set the limit to 0 disable aggregation entirely. 22 | * 23 | set zfs:zfs_vdev_aggregation_limit = 0 24 | 25 | * 26 | * These task queues will allocate threads equal to 75% of the number of CPU 27 | * threads on the system. On Gimlet, for example, that means that it allocates 28 | * 96 threads. These queues are per-zpool, so actually that means it allocates 29 | * 960 threads by default. This is a bit excessive. These parameters bring it 30 | * down to allocate 5% per pool instead, significantly reducing mutex contention 31 | * between the worker threads. 32 | * 33 | set zfs:zfs_sync_taskq_batch_pct = 5 34 | set zfs:zio_taskq_batch_pct = 5 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 Oxide Computer Company 3 | # 4 | 5 | TOP = $(PWD) 6 | 7 | HELIOS_BUILD = $(TOP)/tools/helios-build/target/debug/helios-build 8 | 9 | .PHONY: welcome 10 | welcome: gmakecheck 11 | @printf '\n' 12 | @printf 'Welcome to the Helios build system\n' 13 | @printf '\n' 14 | @printf '\n' 15 | @if ! cargo --version >/dev/null 2>&1; then \ 16 | printf ' You must install Rust before continuing.\n'; \ 17 | else \ 18 | printf ' Try "gmake setup" to get started!\n'; \ 19 | fi 20 | @printf '\n' 21 | 22 | .PHONY: gmakecheck 23 | gmakecheck: 24 | @if [[ -z "$(.FEATURES)" ]]; then \ 25 | printf 'ERROR: This Makefile requires GNU Make (gmake)\n' >&2; \ 26 | exit 1; \ 27 | fi 28 | 29 | .PHONY: cargocheck 30 | cargocheck: 31 | @if ! cargo --version >/dev/null 2>&1; then \ 32 | printf ' You must install Rust before continuing.\n' >&2; \ 33 | exit 1; \ 34 | fi 35 | 36 | # 37 | # Run a "quick" build of illumos for development: 38 | # 39 | .PHONY: illumos 40 | illumos: gmakecheck $(HELIOS_BUILD) 41 | $(HELIOS_BUILD) build-illumos -q 42 | 43 | # 44 | # Enter the "quick" build environment so that you can run dmake, etc: 45 | # 46 | .PHONY: bldenv 47 | bldenv: gmakecheck $(HELIOS_BUILD) 48 | $(HELIOS_BUILD) bldenv -q 49 | 50 | .PHONY: setup 51 | setup: gmakecheck $(HELIOS_BUILD) 52 | @$(HELIOS_BUILD) setup 53 | rm -f helios-build 54 | ln -s tools/helios-build/target/debug/helios-build 55 | @printf '\n' 56 | @printf 'Setup complete! ./helios-build is now available.\n' 57 | @printf '\n' 58 | 59 | .PHONY: $(HELIOS_BUILD) 60 | $(HELIOS_BUILD): cargocheck 61 | @if [[ $$(/usr/bin/uname -o) != illumos ]]; then \ 62 | printf 'ERROR: must be built on illumos\n' >&2; \ 63 | exit 1; \ 64 | fi 65 | cd tools/helios-build && cargo build --quiet 66 | 67 | .PHONY: clean 68 | clean: 69 | cd tools/helios-build && cargo clean --quiet 70 | -------------------------------------------------------------------------------- /image/templates/files/compliance-hostname.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /image/templates/files/compliance-mem-loadgen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 26 | 28 | 29 | 30 | 35 | 36 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /image/templates/include/devfs.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [ 3 | { "t": "devfsadm" }, 4 | 5 | { "t": "ensure_dir", "dir": "/dev/cfg", 6 | "owner": "root", "group": "root", "mode": "755" }, 7 | { "t": "ensure_dir", "dir": "/dev/dsk", 8 | "owner": "root", "group": "sys", "mode": "755" }, 9 | { "t": "ensure_dir", "dir": "/dev/rdsk", 10 | "owner": "root", "group": "sys", "mode": "755" }, 11 | { "t": "ensure_dir", "dir": "/dev/sensors", 12 | "owner": "root", "group": "root", "mode": "755" }, 13 | { "t": "ensure_dir", "dir": "/dev/usb", 14 | "owner": "root", "group": "root", "mode": "755" }, 15 | 16 | { "t": "remove_files", "dir": "/dev/cfg" }, 17 | { "t": "remove_files", "dir": "/dev/dsk" }, 18 | { "t": "remove_files", "dir": "/dev/rdsk" }, 19 | { "t": "remove_files", "dir": "/dev/sensors" }, 20 | { "t": "remove_files", "dir": "/dev/usb" }, 21 | 22 | { "t": "ensure_dir", "dir": "/dev/cfg", 23 | "owner": "root", "group": "root", "mode": "755" }, 24 | { "t": "ensure_dir", "dir": "/dev/dsk", 25 | "owner": "root", "group": "sys", "mode": "755" }, 26 | { "t": "ensure_dir", "dir": "/dev/rdsk", 27 | "owner": "root", "group": "sys", "mode": "755" }, 28 | { "t": "ensure_dir", "dir": "/dev/sensors", 29 | "owner": "root", "group": "root", "mode": "755" }, 30 | { "t": "ensure_dir", "dir": "/dev/usb", 31 | "owner": "root", "group": "root", "mode": "755" }, 32 | 33 | { "t": "ensure_symlink", "link": "/dev/msglog", 34 | "target": "../devices/pseudo/sysmsg@0:msglog", 35 | "owner": "root", "group": "root" }, 36 | { "t": "ensure_file", "file": "/reconfigure", 37 | "contents": "", 38 | "owner": "root", "group": "root", "mode": "644" } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /image/templates/files/compliance-time.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /image/templates/files/recovery-hostname.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /image/templates/files/net-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2025 Oxide Computer Company 4 | # 5 | 6 | set -o errexit 7 | set -o pipefail 8 | set -o xtrace 9 | 10 | . /lib/svc/share/smf_include.sh 11 | 12 | if (( $# != 1 )); then 13 | printf 'usage: $0 LINK\n' >&2 14 | exit $SMF_EXIT_ERR_FATAL 15 | fi 16 | nicdev=$1 17 | 18 | if [[ -z $SMF_FMRI ]]; then 19 | printf 'ERROR: must run under SMF\n' >&2 20 | exit $SMF_EXIT_ERR_FATAL 21 | fi 22 | fail_not_found=$(svcprop -p 'config/fail_not_found' "$SMF_FMRI") 23 | 24 | # 25 | # Find the NICs we want to bring up for IPv6: 26 | # 27 | nics=() 28 | for try in $(dladm show-ether -po link); do 29 | if [[ $try == $nicdev* ]]; then 30 | nics+=( $try ) 31 | fi 32 | done 33 | 34 | if (( ${#nics[@]} == 0 )); then 35 | if [[ $fail_not_found == true ]]; then 36 | exit $SMF_EXIT_ERR_FATAL 37 | else 38 | exit $SMF_EXIT_OK 39 | fi 40 | fi 41 | 42 | # 43 | # Ensure any Chelsio NICs are configured to allow for jumbo frames. 44 | # 45 | for (( i = 0; i < ${#nics[@]}; i++ )); do 46 | nic=${nics[$i]} 47 | 48 | if [[ $nic != cxgbe* ]]; then 49 | continue 50 | fi 51 | 52 | if ! mtu=$(dladm show-linkprop -o value -c -p mtu "$nic"); then 53 | printf 'WARNING: could not get MTU for %s?\n' "$nic" >&2 54 | continue 55 | fi 56 | 57 | want=9000 58 | if [[ $mtu == $want ]]; then 59 | continue 60 | fi 61 | 62 | if ! dladm set-linkprop -p "mtu=$want" "$nic"; then 63 | printf 'WARNING: could not set MTU for %s?\n' "$nic" >&2 64 | fi 65 | done 66 | 67 | fail=no 68 | for (( i = 0; i < ${#nics[@]}; i++ )); do 69 | nic=${nics[$i]} 70 | 71 | if ! ipadm show-if "$nic" >/dev/null 2>&1; then 72 | if ! ipadm create-if -t "$nic" >&2; then 73 | fail=yes 74 | continue 75 | fi 76 | fi 77 | if ! ipadm show-addr "$nic/ll" >/dev/null 2>&1; then 78 | if ! ipadm create-addr -T addrconf -t "$nic/ll" >&2; then 79 | fail=yes 80 | continue 81 | fi 82 | fi 83 | done 84 | 85 | if [[ $fail == yes ]]; then 86 | exit $SMF_EXIT_ERR_FATAL 87 | fi 88 | 89 | exit $SMF_EXIT_OK 90 | -------------------------------------------------------------------------------- /image/templates/files/compliance-nvme-loadgen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ksh93 2 | # 3 | # Copyright 2025 Oxide Computer Company 4 | # 5 | 6 | # 7 | # Runs fio (the "Flexible I/O tester") with the given profile. 8 | # By default, the output filenames are derived from NVMe devices 9 | # in the system, but this (among other things) can be overridden 10 | # via environment variables. 11 | # 12 | 13 | . /lib/svc/share/smf_include.sh 14 | 15 | set -e -o pipefail 16 | 17 | function fatal { 18 | echo "$@" >&2 19 | exit $SMF_EXIT_ERR_FATAL 20 | } 21 | 22 | function config { 23 | svcprop -c -p config/$1 $SMF_FMRI 24 | } 25 | 26 | function booltoint { 27 | case "$1" in 28 | false) echo 0;; 29 | *) echo 1;; 30 | esac 31 | } 32 | 33 | if ! smf_present; then 34 | fatal "Service Management framework not initialized." 35 | fi 36 | 37 | 38 | # The only required arguments are the name of the workload, and 39 | # at least one instance. 40 | if (( $# < 2 )); then 41 | fatal "Usage: compliance-nvme-loadgen workload instance[s]" 42 | fi 43 | workload=$1; shift 44 | 45 | ioengine=$(config ioengine) 46 | block_size=$(config block_size) 47 | direct=$(booltoint $(config direct)) 48 | buffered=$(booltoint $(config buffered)) 49 | iodepth=$(config iodepth) 50 | size=$(config size) 51 | devdir=$(config devdir) 52 | 53 | typeset -A nvme_dev_map 54 | pilot local disk ls -H -p -o label,disk | 55 | sed 's/BSU0/East/;s/BSU1/West/' | 56 | while read instance device; do 57 | nvme_dev_map[$instance]=$device 58 | done 59 | 60 | typeset -a out_files 61 | for nvme do 62 | if [[ ! -v nvme_dev_map[$nvme] ]]; then 63 | fatal "Unknown device \"$nvme\"" 64 | fi 65 | dev="${nvme_dev_map[$nvme]}p0" 66 | out_files+=("$devdir/$dev") 67 | done 68 | 69 | env \ 70 | IOENGINE="${ioengine}" \ 71 | BLOCK_SIZE="${block_size}" \ 72 | DIRECT="${direct}" \ 73 | BUFFERED="${buffered}" \ 74 | IODEPTH="${iodepth}" \ 75 | SIZE="${size}" \ 76 | RW="${RW}" \ 77 | FILENAME="${FILENAME:-$(IFS=':'; echo "${out_files[*]}")}" \ 78 | NUMJOBS="${NUMJOBS:-${#out_files[@]}}" \ 79 | fio /root/nvme-${workload}.fio & 80 | 81 | exit $SMF_EXIT_OK 82 | -------------------------------------------------------------------------------- /image/templates/files/bashrc: -------------------------------------------------------------------------------- 1 | # 2 | # Accept a preference value for the editing mode of the shell. The name of 3 | # this variable is effectively a contract between several things, including: 4 | # 5 | # - the end user who must specify it in their environment somehow 6 | # - the SSH server configuration that must allow it to be passed through the 7 | # connection (see AcceptEnv in sshd_config) 8 | # - the SSH client configuration that provides the value (see SetEnv and 9 | # SendEnv in ssh_config) 10 | # - pilot, which will attempt to inject and propagate the variable through 11 | # the various shell sessions it can establish 12 | # - the omicron1 zone brand, which needs to arrange for propagation of 13 | # the value through interactive zlogin, and which needs to include a 14 | # similar usage in bashrc as we have here 15 | # 16 | if [[ -n "$OXIDE_PREF_SHELL_MODE" ]]; then 17 | case "$OXIDE_PREF_SHELL_MODE" in 18 | vi|emacs) 19 | set -o "$OXIDE_PREF_SHELL_MODE" 20 | ;; 21 | *) 22 | printf 'WARNING: OXIDE_PREF_SHELL_MODE="%s" not understood' \ 23 | "$OXIDE_PREF_SHELL_MODE" >&2 24 | ;; 25 | esac 26 | fi 27 | 28 | C_RED='\[\033[01;31m\]' 29 | C_BLD='\[\033[1m\]' 30 | C_NUL='\[\033[00m\]' 31 | 32 | if [[ -n $SSH_CLIENT ]]; then 33 | export PROMPT_COMMAND='echo -ne "\033]0;${HOSTNAME} \007" && history -a' 34 | fi 35 | 36 | case "$TERM" in 37 | xterm*|rxvt*|screen*|sun-color) 38 | PS1="$C_RED\\h $C_NUL$C_BLD#$C_NUL " 39 | ;; 40 | esac 41 | 42 | pathdirs=( 43 | "$HOME/bin" 44 | '/opt/ooce/sbin' 45 | '/opt/ooce/bin' 46 | '/opt/oxide/opte/bin' 47 | '/opt/oxide/oxlog' 48 | '/opt/oxide/mg-ddm' 49 | '/usr/sbin' 50 | '/usr/bin' 51 | '/bin' 52 | '/sbin' 53 | ) 54 | export PATH=$(IFS=':'; printf '%s' "${pathdirs[*]}") 55 | 56 | # 57 | # Bracketed paste in bash is a deeply questionable facility, and on a serial 58 | # console where one may reset the system at any time it leaves the terminal in 59 | # a state where one cannot then paste correctly into one of the development 60 | # loaders, such as bldb or nanobl-rs. 61 | # 62 | bind 'set enable-bracketed-paste off' 63 | -------------------------------------------------------------------------------- /image/templates/files/compliance-beacon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /image/amd/turin-cosmo-a-mbist.patch: -------------------------------------------------------------------------------- 1 | --- turin-cosmo-1.0.0.7.efs.json5 Sat Oct 4 20:42:22 2025 2 | +++ turin-cosmo-1.0.0.7.mbist-mfg-v0-dext-len3.efs.json5 Wed Oct 15 20:06:13 2025 3 | @@ -10684,7 +10684,7 @@ 4 | }, 5 | { 6 | Byte: { 7 | - MemMbistAggressorsChannelDdrMode: "All" 8 | + MemMbistAggressorsChannelDdrMode: "SubChannel" 9 | } 10 | }, 11 | { 12 | @@ -10749,7 +10749,7 @@ 13 | }, 14 | { 15 | Byte: { 16 | - MemMbistPatternLengthDdr: 6 17 | + MemMbistPatternLengthDdr: 3 18 | } 19 | }, 20 | { 21 | @@ -10966,7 +10966,7 @@ 22 | }, 23 | { 24 | Byte: { 25 | - MemMbistPatternSelectDdr: "Auto" 26 | + MemMbistPatternSelectDdr: "Prbs" 27 | } 28 | }, 29 | { 30 | @@ -11046,7 +11046,7 @@ 31 | }, 32 | { 33 | Byte: { 34 | - MemMbistDataEyeSilentExecutionDdr: "Disabled" 35 | + MemMbistDataEyeSilentExecutionDdr: "Enabled" 36 | } 37 | }, 38 | { 39 | @@ -11136,7 +11136,7 @@ 40 | }, 41 | { 42 | Byte: { 43 | - MemMbistTestModeDdr: "Both" 44 | + MemMbistTestModeDdr: "DataEye" 45 | } 46 | }, 47 | { 48 | @@ -11265,7 +11265,7 @@ 49 | }, 50 | { 51 | Byte: { 52 | - MemMbistAggressorsDdr: "Auto" 53 | + MemMbistAggressorsDdr: "Enabled" 54 | } 55 | }, 56 | { 57 | -------------------------------------------------------------------------------- /image/amd/turin-cosmo-a-mbist-noaggr.patch: -------------------------------------------------------------------------------- 1 | --- turin-cosmo-1.0.0.7.efs.json5 Sat Oct 4 20:42:22 2025 2 | +++ turin-cosmo-1.0.0.7.mbist-mfg-v0-dext-len3.efs.json5 Wed Oct 15 20:06:13 2025 3 | @@ -10684,7 +10684,7 @@ 4 | }, 5 | { 6 | Byte: { 7 | - MemMbistAggressorsChannelDdrMode: "All" 8 | + MemMbistAggressorsChannelDdrMode: "SubChannel" 9 | } 10 | }, 11 | { 12 | @@ -10749,7 +10749,7 @@ 13 | }, 14 | { 15 | Byte: { 16 | - MemMbistPatternLengthDdr: 6 17 | + MemMbistPatternLengthDdr: 3 18 | } 19 | }, 20 | { 21 | @@ -10966,7 +10966,7 @@ 22 | }, 23 | { 24 | Byte: { 25 | - MemMbistPatternSelectDdr: "Auto" 26 | + MemMbistPatternSelectDdr: "Prbs" 27 | } 28 | }, 29 | { 30 | @@ -11046,7 +11046,7 @@ 31 | }, 32 | { 33 | Byte: { 34 | - MemMbistDataEyeSilentExecutionDdr: "Disabled" 35 | + MemMbistDataEyeSilentExecutionDdr: "Enabled" 36 | } 37 | }, 38 | { 39 | @@ -11136,7 +11136,7 @@ 40 | }, 41 | { 42 | Byte: { 43 | - MemMbistTestModeDdr: "Both" 44 | + MemMbistTestModeDdr: "DataEye" 45 | } 46 | }, 47 | { 48 | @@ -11265,7 +11265,7 @@ 49 | }, 50 | { 51 | Byte: { 52 | - MemMbistAggressorsDdr: "Auto" 53 | + MemMbistAggressorsDdr: "Disabled" 54 | } 55 | }, 56 | { 57 | -------------------------------------------------------------------------------- /tools/packages/os-deps.mogrify: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 Oxide Computer Company 3 | # 4 | 5 | # 6 | # Each "transform" rule acts on a specific pkg(5) action, such as a "depend" 7 | # or a "file" entry. In this file, we wish to trim out a few specific 8 | # dependencies from a few specific packages. Unfortunately, pkgmogrify 9 | # only allows for matching action-level attributes (e.g., "type" and "fmri" for 10 | # a "depend" action) in predicates, but we want to be able to match on 11 | # the FMRI of the package which contains the "depend" entries we wish to drop. 12 | # 13 | # To work around this limitation, we add a temporary action-level attribute 14 | # to _every_ depend entry, using the value expansion pattern match %{...}, 15 | # which _does_ have access to pacakage-level attributes. 16 | # 17 | default tmp.fmri %{pkg.fmri}> 18 | 19 | # 20 | # We can then match on specific FMRI values and assign them a removal bank 21 | # number. 22 | # 23 | 24 | # 25 | # Bank 1: system/file-system/zfs depends on a Python runtime directly because 26 | # it contains a vestigial script, "/usr/lib/zfs/pyzfs.py". Drop those 27 | # dependencies for now until we have a chance to clean up the gate: 28 | # 29 | default X 1> 31 | drop> 32 | drop> 33 | drop> 34 | 35 | # 36 | # Bank 2: mailwrapper depends on sendmail directly, but we do not wish sendmail 37 | # installed. Drop the dependency, so that mail will at least fail to be 38 | # delivered rather than litter the system. 39 | # 40 | default X 2> 42 | drop> 43 | 44 | # 45 | # Bank 3: In fact, let's not require mailwrapper at all. 46 | # 47 | default X 3> 49 | drop> 50 | 51 | # 52 | # Unconditionally drop our temporary attributes so that they do not then appear 53 | # in the manifest output: 54 | # 55 | delete X .*> 56 | delete tmp.fmri .*> 57 | -------------------------------------------------------------------------------- /image/templates/include/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [ 3 | { "t": "ensure_symlink", "link": "/etc/svc/profile/generic.xml", 4 | "target": "generic_limited_net.xml", 5 | "owner": "root", "group": "root" }, 6 | { "t": "ensure_symlink", "link": "/etc/svc/profile/inetd_services.xml", 7 | "target": "inetd_generic.xml", 8 | "owner": "root", "group": "root" }, 9 | { "t": "ensure_symlink", "link": "/etc/svc/profile/platform.xml", 10 | "target": "platform_oxide.xml", 11 | "owner": "root", "group": "root" }, 12 | 13 | { "t": "ensure_symlink", "link": "/etc/svc/profile/name_service.xml", 14 | "target": "ns_dns.xml", 15 | "owner": "root", "group": "root" }, 16 | { "t": "ensure_file", "file": "/etc/nsswitch.conf", 17 | "imagesrc": "/etc/nsswitch.dns", 18 | "owner": "root", "group": "root", "mode": "644" }, 19 | 20 | { "t": "shadow", "username": "root", "password": 21 | "$5$kr1VgdIt$OUiUAyZCDogH/uaxH71rMeQxvpDEY2yX.x0ZQRnmeb9" }, 22 | 23 | { "t": "ensure_file", "file": "/etc/inet/hosts", 24 | "src": "hosts", 25 | "owner": "root", "group": "root", "mode": "644" }, 26 | { "t": "ensure_file", "file": "/etc/nodename", 27 | "contents": "unknown\n", 28 | "owner": "root", "group": "root", "mode": "644" }, 29 | 30 | { "t": "ensure_file", "file": "/etc/default/dhcpagent", 31 | "src": "dhcpagent", 32 | "owner": "root", "group": "sys", "mode": "644" }, 33 | 34 | { "t": "ensure_file", "file": "/etc/resolv.conf", 35 | "contents": "", 36 | "owner": "root", "group": "root", "mode": "644" }, 37 | 38 | { "t": "ensure_dir", "dir": "/usr/lib/oxide", 39 | "owner": "root", "group": "sys", "mode": "0755" }, 40 | { "t": "ensure_file", "file": "/usr/lib/oxide/net-setup", 41 | "src": "net-setup.sh", 42 | "owner": "root", "group": "bin", "mode": "0755" }, 43 | { "t": "ensure_dir", "dir": "/lib/svc/manifest/oxide", 44 | "owner": "root", "group": "sys", "mode": "0755" }, 45 | { "t": "ensure_file", "file": "/lib/svc/manifest/oxide/net-setup.xml", 46 | "src": "net-setup.xml", 47 | "owner": "root", "group": "sys", "mode": "0644" } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /image/templates/files/ttydefs.3000000: -------------------------------------------------------------------------------- 1 | # VERSION=1 2 | 460800:460800 hupcl:460800 hupcl::307200 3 | 307200:307200 hupcl:307200 hupcl::230400 4 | 230400:230400 hupcl:230400 hupcl::153600 5 | 153600:153600 hupcl:153600 hupcl::115200 6 | 115200:115200 hupcl:115200 hupcl::76800 7 | 76800:76800 hupcl:76800 hupcl::57600 8 | 57600:57600 hupcl:57600 hupcl::38400 9 | 38400:38400 hupcl:38400 hupcl::19200 10 | 19200:19200 hupcl:19200 hupcl::9600 11 | 9600:9600 hupcl:9600 hupcl::4800 12 | 4800:4800 hupcl:4800 hupcl::2400 13 | 2400:2400 hupcl:2400 hupcl::1200 14 | 1200:1200 hupcl:1200 hupcl::300 15 | 300:300 hupcl:300 hupcl::460800 16 | 17 | 460800E:460800 hupcl evenp:460800 evenp::307200 18 | 307200E:307200 hupcl evenp:307200 evenp::230400 19 | 230400E:230400 hupcl evenp:230400 evenp::153600 20 | 153600E:153600 hupcl evenp:153600 evenp::115200 21 | 115200E:115200 hupcl evenp:115200 evenp::76800 22 | 76800E:76800 hupcl evenp:76800 evenp::57600 23 | 57600E:57600 hupcl evenp:57600 evenp::38400 24 | 38400E:38400 hupcl evenp:38400 evenp::19200 25 | 19200E:19200 hupcl evenp:19200 evenp::9600 26 | 9600E:9600 hupcl evenp:9600 evenp::4800 27 | 4800E:4800 hupcl evenp:4800 evenp::2400 28 | 2400E:2400 hupcl evenp:2400 evenp::1200 29 | 1200E:1200 hupcl evenp:1200 evenp::300 30 | 300E:300 hupcl evenp:300 evenp::19200 31 | 32 | auto:hupcl:sane hupcl:A:9600 33 | 34 | console:3000000 hupcl opost onlcr:3000000::console 35 | console1:1200 hupcl opost onlcr:1200::console2 36 | console2:300 hupcl opost onlcr:300::console3 37 | console3:2400 hupcl opost onlcr:2400::console4 38 | console4:4800 hupcl opost onlcr:4800::console5 39 | console5:19200 hupcl opost onlcr:19200::console 40 | 41 | contty:9600 hupcl opost onlcr:9600 sane::contty1 42 | contty1:1200 hupcl opost onlcr:1200 sane::contty2 43 | contty2:300 hupcl opost onlcr:300 sane::contty3 44 | contty3:2400 hupcl opost onlcr:2400 sane::contty4 45 | contty4:4800 hupcl opost onlcr:4800 sane::contty5 46 | contty5:19200 hupcl opost onlcr:19200 sane::contty 47 | 48 | 49 | 4800H:4800:4800 sane hupcl::9600H 50 | 9600H:9600:9600 sane hupcl::19200H 51 | 19200H:19200:19200 sane hupcl::38400H 52 | 38400H:38400:38400 sane hupcl::2400H 53 | 2400H:2400:2400 sane hupcl::1200H 54 | 1200H:1200:1200 sane hupcl::300H 55 | 300H:300:300 sane hupcl::4800H 56 | 57 | conttyH:9600 opost onlcr:9600 hupcl sane::contty1H 58 | contty1H:1200 opost onlcr:1200 hupcl sane::contty2H 59 | contty2H:300 opost onlcr:300 hupcl sane::contty3H 60 | contty3H:2400 opost onlcr:2400 hupcl sane::contty4H 61 | contty4H:4800 opost onlcr:4800 hupcl sane::contty5H 62 | contty5H:19200 opost onlcr:19200 hupcl sane::conttyH 63 | 64 | -------------------------------------------------------------------------------- /image/templates/files/compliance-nvme-loadgen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /image/templates/sled/zfs-recovery.json: -------------------------------------------------------------------------------- 1 | { 2 | "pool": { 3 | "name": "rpool", 4 | "bename": "ramdisk", 5 | "ashift": 9, 6 | "uefi": false, 7 | "size": 2000, 8 | "label": false, 9 | "no_features": false, 10 | "compression": "off", 11 | "autoexpand": true, 12 | "trim": true, 13 | "options": [ "failmode=panic" ], 14 | "fsoptions": [] 15 | }, 16 | 17 | "steps": [ 18 | { "t": "create_be" }, 19 | 20 | { "t": "unpack_tar", "name": "sled-recovery-ramdisk.tar" }, 21 | 22 | { "t": "include", "name": "devfs" }, 23 | 24 | { "t": "include", "name": "common" }, 25 | 26 | { "t": "ensure_file", "file": "/etc/auto_master", 27 | "src": "auto_master", 28 | "owner": "root", "group": "root", "mode": "644" }, 29 | 30 | { "t": "ensure_file", "file": "/etc/ttydefs", 31 | "src": "ttydefs.${baud}", 32 | "owner": "root", "group": "sys", "mode": "644" }, 33 | 34 | { "t": "ensure_file", "file": "/etc/default/init", 35 | "src": "default_init", 36 | "owner": "root", "group": "root", "mode": "644" }, 37 | 38 | { "t": "ensure_file", "file": "/etc/ssh/sshd_config", 39 | "src": "sshd_config", 40 | "owner": "root", "group": "root", "mode": "644" }, 41 | 42 | { "t": "ensure_file", "file": "/usr/lib/bootparams", 43 | "src": "bootparams.sh", 44 | "owner": "root", "group": "bin", "mode": "0755" }, 45 | 46 | { "t": "include", "name": "t6-firmware" }, 47 | 48 | { "t": "ensure_file", 49 | "file": "/usr/lib/recovery-hostname", 50 | "src": "recovery-hostname.sh", 51 | "owner": "root", "group": "bin", "mode": "0755" }, 52 | { "t": "ensure_file", 53 | "file": "/lib/svc/manifest/site/recovery-hostname.xml", 54 | "src": "recovery-hostname.xml", 55 | "owner": "root", "group": "bin", "mode": "0644" }, 56 | 57 | { "t": "ensure_file", 58 | "file": "/var/svc/profile/site.xml", 59 | "src": "site.xml", 60 | "owner": "root", "group": "root", "mode": "644" }, 61 | 62 | { "t": "include", "name": "root-noauth" }, 63 | 64 | { "t": "include", "with": "genproto", 65 | "name": "genproto", "file": "${genproto}" }, 66 | 67 | { "t": "include", "name": "smf-reduce" }, 68 | { "t": "seed_smf", "skip_seed": true, 69 | "apply_profiles": [ 70 | "generic", 71 | "platform", 72 | "site" 73 | ] 74 | } 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /config/projects.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2025 Oxide Computer Company 3 | # 4 | 5 | [project.illumos] 6 | github = "oxidecomputer/illumos-gate" 7 | rev = "stlouis" 8 | unless_env = "BUILD_OS" 9 | 10 | [project.omnios-build] 11 | github = "oxidecomputer/helios-omnios-build" 12 | rev = "helios2" 13 | unless_env = "BUILD_OS" 14 | use_ssh = false 15 | site_sh = true 16 | 17 | [project.omnios-extra] 18 | github = "oxidecomputer/helios-omnios-extra" 19 | rev = "helios2" 20 | unless_env = "BUILD_OS" 21 | use_ssh = false 22 | site_sh = true 23 | 24 | [project.pinprick] 25 | github = "oxidecomputer/pinprick" 26 | use_ssh = false 27 | cargo_build = true 28 | 29 | [project.phbl] 30 | github = "oxidecomputer/phbl" 31 | use_ssh = false 32 | auto_update = true 33 | 34 | [project.image-builder] 35 | github = "illumos/image-builder" 36 | use_ssh = false 37 | cargo_build = true 38 | use_debug = true 39 | auto_update = true 40 | 41 | [project.bootserver] 42 | github = "oxidecomputer/boot-image-tools" 43 | use_ssh = false 44 | cargo_build = true 45 | auto_update = true 46 | 47 | [project.amd-host-image-builder] 48 | github = "oxidecomputer/amd-host-image-builder" 49 | use_ssh = false 50 | cargo_build = true 51 | use_debug = true 52 | auto_update = true 53 | 54 | [[project.amd-host-image-builder.fixup]] 55 | # 56 | # We used to pin this commit. To avoid a manual flag day, switch a user with 57 | # this specific commit checked out as a detached HEAD back to main: 58 | # 59 | from_commit = "4eae23e8a86a6b5ae16e26283e9c0bee87cc2167" 60 | to_branch = "main" 61 | 62 | # 63 | # ----------------------------------------------------------------------------- 64 | # NOTE: Repositories below this comment are all still private. 65 | # 66 | # If you do not have access to private repositories in the "oxidecomputer" 67 | # GitHub organisation, set "OXIDE_STAFF=no" in your environment to skip 68 | # cloning them. 69 | # ----------------------------------------------------------------------------- 70 | # 71 | # BEFORE you add a private repository here, you MUST add it to the 72 | # `access_repos` list of: 73 | # https://github.com/oxidecomputer/omicron/blob/main/.github/buildomat/jobs/tuf-repo.sh 74 | # 75 | # Don't break CI! 76 | # 77 | 78 | [project.amd-firmware] 79 | github = "oxidecomputer/amd-firmware" 80 | use_ssh = true 81 | auto_update = true 82 | unless_env = "OXIDE_STAFF" 83 | 84 | [project.chelsio-t6-roms] 85 | github = "oxidecomputer/chelsio-t6-roms" 86 | use_ssh = true 87 | cargo_build = false 88 | auto_update = true 89 | unless_env = "OXIDE_STAFF" 90 | 91 | [project.pilot] 92 | github = "oxidecomputer/pilot" 93 | use_ssh = true 94 | cargo_build = true 95 | auto_update = true 96 | unless_env = "OXIDE_STAFF" 97 | 98 | [project.dmar-report] 99 | github = "oxidecomputer/dmar-report" 100 | use_ssh = true 101 | cargo_build = true 102 | auto_update = true 103 | unless_env = "OXIDE_STAFF" 104 | -------------------------------------------------------------------------------- /image/templates/include/smf-reduce.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [ 3 | { "t": "remove_files", "dir": "/lib/svc/manifest/network/ipsec" }, 4 | { "t": "remove_files", "dir": "/lib/svc/manifest/network/ldap" }, 5 | { "t": "remove_files", "without": "nfs", 6 | "dir": "/lib/svc/manifest/network/rpc" }, 7 | { "t": "remove_files", "dir": "/lib/svc/manifest/network/security" }, 8 | { "t": "remove_files", "without": "nfs", 9 | "dir": "/lib/svc/manifest/network/shares" }, 10 | { "t": "remove_files", "dir": "/lib/svc/manifest/network/smb" }, 11 | { "t": "remove_files", 12 | "file": "/lib/svc/manifest/application/management/net-snmp.xml" }, 13 | { "t": "remove_files", 14 | "file": "/lib/svc/manifest/application/security/tcsd.xml" }, 15 | { "t": "remove_files", 16 | "file": "/lib/svc/manifest/network/dns/install.xml" }, 17 | { "t": "remove_files", 18 | "file": "/lib/svc/manifest/network/inetd-upgrade.xml" }, 19 | { "t": "remove_files", "without": "nfs", 20 | "file": "/lib/svc/manifest/network/inetd.xml" }, 21 | { "t": "remove_files", 22 | "file": "/lib/svc/manifest/network/network-install.xml" }, 23 | { "t": "remove_files", 24 | "file": "/lib/svc/manifest/network/network-ipmp.xml" }, 25 | { "t": "remove_files", 26 | "file": "/lib/svc/manifest/network/network-iptun.xml" }, 27 | { "t": "remove_files", 28 | "file": "/lib/svc/manifest/network/network-location.xml" }, 29 | { "t": "remove_files", 30 | "file": "/lib/svc/manifest/network/network-netcfg.xml" }, 31 | { "t": "remove_files", 32 | "file": "/lib/svc/manifest/network/routing/rdisc.xml" }, 33 | { "t": "remove_files", 34 | "file": "/lib/svc/manifest/network/varpd.xml" }, 35 | { "t": "remove_files", 36 | "file": "/lib/svc/manifest/system/device/allocate.xml" }, 37 | { "t": "remove_files", 38 | "file": "/lib/svc/manifest/system/device/devices-audio.xml" }, 39 | { "t": "remove_files", 40 | "file": "/usr/lib/devfsadm/linkmod/SUNW_audio_link.so" }, 41 | { "t": "remove_files", 42 | "file": "/dev/sndstat" }, 43 | { "t": "remove_files", 44 | "file": "/lib/svc/manifest/system/device/mpxio-upgrade.xml" }, 45 | { "t": "remove_files", 46 | "file": "/lib/svc/manifest/system/hostid.xml" }, 47 | { "t": "remove_files", "without": "nfs", 48 | "file": "/lib/svc/manifest/system/idmap.xml" }, 49 | { "t": "remove_files", 50 | "file": "/lib/svc/manifest/system/pkgserv.xml" }, 51 | { "t": "remove_files", 52 | "file": "/lib/svc/manifest/system/zones.xml" }, 53 | { "t": "remove_files", 54 | "file": "/lib/svc/manifest/system/intrd.xml" }, 55 | { "t": "remove_files", "without": "ntp", 56 | "file": "/lib/svc/manifest/network/chrony.xml" } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /image/templates/files/net-setup.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 23 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /image/templates/include/compliance-common.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [ 3 | { "t": "ensure_file", "with": "compliance", 4 | "file": "/lib/svc/manifest/site/postboot.xml", 5 | "src": "compliance-postboot.xml", 6 | "owner": "root", "group": "bin", "mode": "0644" }, 7 | 8 | { "t": "ensure_file", 9 | "file": "/usr/lib/compliance-hostname", 10 | "src": "compliance-hostname.sh", 11 | "owner": "root", "group": "bin", "mode": "0755" }, 12 | { "t": "ensure_file", 13 | "file": "/lib/svc/manifest/site/compliance-hostname.xml", 14 | "src": "compliance-hostname.xml", 15 | "owner": "root", "group": "bin", "mode": "0644" }, 16 | 17 | { "t": "ensure_file", 18 | "file": "/usr/lib/compliance-cpu-loadgen", 19 | "src": "compliance-cpu-loadgen.sh", 20 | "owner": "root", "group": "bin", "mode": "0755" }, 21 | { "t": "ensure_file", 22 | "file": "/lib/svc/manifest/site/compliance-cpu-loadgen.xml", 23 | "src": "compliance-cpu-loadgen.xml", 24 | "owner": "root", "group": "bin", "mode": "0644" }, 25 | 26 | { "t": "ensure_file", 27 | "file": "/usr/lib/compliance-mem-loadgen", 28 | "src": "compliance-mem-loadgen.sh", 29 | "owner": "root", "group": "bin", "mode": "0755" }, 30 | { "t": "ensure_file", 31 | "file": "/lib/svc/manifest/site/compliance-mem-loadgen.xml", 32 | "src": "compliance-mem-loadgen.xml", 33 | "owner": "root", "group": "bin", "mode": "0644" }, 34 | 35 | { "t": "ensure_file", 36 | "file": "/usr/lib/compliance-nvme-loadgen", 37 | "src": "compliance-nvme-loadgen.sh", 38 | "owner": "root", "group": "bin", "mode": "0755" }, 39 | { "t": "ensure_file", 40 | "file": "/lib/svc/manifest/site/compliance-nvme-loadgen.xml", 41 | "src": "compliance-nvme-loadgen.xml", 42 | "owner": "root", "group": "bin", "mode": "0644" }, 43 | 44 | { "t": "ensure_file", 45 | "file": "/usr/lib/compliance-pinger", 46 | "src": "compliance-pinger.sh", 47 | "owner": "root", "group": "bin", "mode": "0755" }, 48 | { "t": "ensure_file", 49 | "file": "/lib/svc/manifest/site/compliance-pinger.xml", 50 | "src": "compliance-pinger.xml", 51 | "owner": "root", "group": "bin", "mode": "0644" }, 52 | 53 | { "t": "ensure_file", 54 | "file": "/usr/lib/compliance-beacon", 55 | "src": "compliance-beacon.sh", 56 | "owner": "root", "group": "bin", "mode": "0755" }, 57 | { "t": "ensure_file", 58 | "file": "/lib/svc/manifest/site/compliance-beacon.xml", 59 | "src": "compliance-beacon.xml", 60 | "owner": "root", "group": "bin", "mode": "0644" }, 61 | 62 | { "t": "ensure_file", 63 | "file": "/lib/svc/manifest/site/compliance-time.xml", 64 | "src": "compliance-time.xml", 65 | "owner": "root", "group": "bin", "mode": "0644" }, 66 | 67 | { "t": "ensure_file", 68 | "file": "/lib/svc/manifest/site/compliance-dump.xml", 69 | "src": "compliance-dump.xml", 70 | "owner": "root", "group": "bin", "mode": "0644" } 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /image/amd/turin-cosmo-1.0.0.7.toml: -------------------------------------------------------------------------------- 1 | cpu = 'turin' 2 | board = 'cosmo' 3 | firmware_version = '1.0.0.7' 4 | size = 32 5 | blobs = [ 6 | 'TypeId0x00_AmdPubKey_BRH.tkn', 7 | 'TypeId0x01_PspBl_BRH.esbin', 8 | 'TypeId0x02_PspOS_BRH.ecsbin', 9 | 'TypeId0x03_PspRecBl_BRH.esbin', 10 | 'TypeId0x08_SmuFirmware_breithorn.ecsbin', 11 | 'TypeId0x208_SmuFirmware_BRHDense.ecsbin', 12 | 'TypeId0x09_PspDebugUnlockToken_BRH.stkn', 13 | 'TypeId0x0A_PspAblPubKey_BRH.stkn', 14 | 'TypeId0x13_SduFw_BRH.ecsbin', 15 | 'TypeId0x21_PspAmdIkek_BRH.bin', 16 | 'TypeId0x22_SecureEmptyToken.bin', 17 | 'TypeId0x24_RegisterAccessPolicy_BRH.cesbin', 18 | 'TypeId0x224_RegisterAccessPolicy_BRHDense.cesbin', 19 | 'TypeId0x28_PspSystemDriver_BRH.ecsbin', 20 | 'TypeId0x2A_SmuFirmware_breithorn.ecsbin', 21 | 'TypeId0x22A_SmuFirmware_BRHDense.ecsbin', 22 | 'TypeId0x2D_AblRt.ecsbin', 23 | 'TypeId0x30_AgesaBootLoaderU_BRH.ecsbin', 24 | 'TypeId0x42_PhyFw_BRH.ecsbin', 25 | 'TypeId0x44_USB_PHY_BRH.esbin', 26 | 'TypeId0x45_RegisterAccessPolicy_BRH.cesbin', 27 | 'TypeId0x245_RegisterAccessPolicy_BRHDense.cesbin', 28 | 'TypeId0x50_PspKeyDataBase_BRH.sbin', 29 | 'TypeId0x51_PspTosKeyDataBase_BRH.ecsbin', 30 | 'TypeId0x55_SPLTable_BRH.sbin', 31 | 'TypeId0x5DMpioFw_BRH.cesbin', 32 | 'TypeId0x64_RasDriver_BRH.ecsbin', 33 | 'TypeId0x65_ta_ras_prod_amdTEE.ecsbin', 34 | 'TypeId0x1A_SevDriver_BRH.ecsbin', 35 | 'TypeId0x15_IpKeyManagerDriver_BRH.ecsbin', 36 | 'TypeId0x1B_BootDriver_BRH.ecsbin', 37 | 'TypeId0x1C_SocDriver_BRH.ecsbin', 38 | 'TypeId0x1D_HadDriver_BRH.ecsbin', 39 | 'TypeId0x1F_InterfaceDriver_BRH.ecsbin', 40 | 'TypeId0x38_PspSevEmptyData.bin', 41 | 'TypeId0x47_DRTMDriver_BRH.ecsbin', 42 | 'TypeId0x67_FHPDriver_BRH.ecsbin', 43 | 'TypeId0x68_SPDMDriver_BRH.ecsbin', 44 | 'TypeId0x69_DPEDriver_BRH.ecsbin', 45 | 'TypeId0x73_PspBl_BRH.ecsbin', 46 | 'TypeId0x76_DfRib_BRH.csbin', 47 | 'TypeId0x8C_MPDMATF_BRH.sbin', 48 | 'TypeId0x91_GmiPhyFw_BRH.esbin', 49 | 'TypeId0x92_Page_BRH.sbin', 50 | 'TypeId0x9D_AspSramFwExt_BRH.sbin', 51 | 'TypeId0x9F_psp_tos_wl_bin_brh.sbin', 52 | 'TypeId0xA0_S3Image_BRH_A0.sbin', 53 | 'TypeId0xA0_S3Image_BRHD_A0.sbin', 54 | 'TypeId0xA0_S3Image_BRH_B0.sbin', 55 | 'TypeId0xA0_S3Image_BRH_C0.sbin', 56 | 'TypeId0xA0_S3Image_BRHD_B0.sbin', 57 | 'TypeId0xA0_S3Image_BRH_B1.sbin', 58 | 'TypeId0xA0_S3Image_BRH_C1.sbin', 59 | 'Type0x64_AppbDdr5RdimmImem3_BRH.ecsbin', 60 | 'Type0x64_AppbDdr5RdimmImem3_BRH_C0.ecsbin', 61 | 'Type0x64_AppbDdr5RdimmImem4_BRH.ecsbin', 62 | 'Type0x64_AppbDdr5RdimmImem4_BRH_C0.ecsbin', 63 | 'Type0x64_AppbDdr5RdimmPosttrainImem9_BRH.ecsbin', 64 | 'Type0x64_AppbDdr5RdimmPosttrainImem9_BRH_C0.ecsbin', 65 | 'Type0x64_AppbDdr5RdimmPosttrainImem10_BRH.ecsbin', 66 | 'Type0x64_AppbDdr5RdimmPosttrainImem10_BRH_C0.ecsbin', 67 | 'Type0x64_AppbDdr5RdimmQuickbootImem11_BRH_C0.ecsbin', 68 | 'Type0x65_AppbDdr5RdimmDmem3_BRH.ecsbin', 69 | 'Type0x65_AppbDdr5RdimmDmem3_BRH_C0.ecsbin', 70 | 'Type0x65_AppbDdr5RdimmDmem4_BRH.ecsbin', 71 | 'Type0x65_AppbDdr5RdimmDmem4_BRH_C0.ecsbin', 72 | 'Type0x65_AppbDdr5RdimmPosttrainDmem9_BRH.ecsbin', 73 | 'Type0x65_AppbDdr5RdimmPosttrainDmem9_BRH_C0.ecsbin', 74 | 'Type0x65_AppbDdr5RdimmPosttrainDmem10_BRH.ecsbin', 75 | 'Type0x65_AppbDdr5RdimmPosttrainDmem10_BRH_C0.ecsbin', 76 | 'Type0x65_AppbDdr5RdimmQuickbootDmem11_BRH_C0.ecsbin', 77 | 'Type0x65_AppbDdr5RdimmQuickbootDmem12_BRH_C0.ecsbin', 78 | 'APOB_NV_BRH.bin', 79 | ] 80 | -------------------------------------------------------------------------------- /image/templates/files/sled-postboot.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 25 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 61 | 62 | 63 | 64 | 65 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /image/templates/sled/zfs.json: -------------------------------------------------------------------------------- 1 | { 2 | "pool": { 3 | "name": "rpool", 4 | "bename": "ramdisk", 5 | "ashift": 9, 6 | "uefi": false, 7 | "size": 1050, 8 | "label": false, 9 | "no_features": false, 10 | "compression": "gzip-9", 11 | "autoexpand": true, 12 | "options": [ "failmode=panic" ], 13 | "fsoptions": [] 14 | }, 15 | 16 | "steps": [ 17 | { "t": "create_be" }, 18 | 19 | { "t": "unpack_tar", "name": "sled-ramdisk.tar" }, 20 | 21 | { "t": "include", "name": "devfs" }, 22 | 23 | { "t": "include", "name": "common" }, 24 | 25 | { "t": "ensure_file", "file": "/etc/auto_master", 26 | "src": "auto_master", 27 | "owner": "root", "group": "root", "mode": "644" }, 28 | 29 | { "t": "ensure_file", "file": "/etc/ttydefs", 30 | "src": "ttydefs.${baud}", 31 | "owner": "root", "group": "sys", "mode": "644" }, 32 | 33 | { "t": "ensure_file", "file": "/etc/default/init", 34 | "src": "default_init", 35 | "owner": "root", "group": "root", "mode": "644" }, 36 | 37 | { "t": "ensure_file", "file": "/etc/ssh/sshd_config", 38 | "src": "sshd_config", 39 | "owner": "root", "group": "root", "mode": "644" }, 40 | 41 | { "t": "ensure_file", "file": "/usr/lib/bootparams", 42 | "src": "bootparams.sh", 43 | "owner": "root", "group": "bin", "mode": "0755" }, 44 | 45 | { "t": "include", "name": "t6-firmware" }, 46 | 47 | { "t": "ensure_file", "without": "mfg", 48 | "file": "/usr/lib/postboot", 49 | "src": "sled-postboot.sh", 50 | "owner": "root", "group": "bin", "mode": "0755" }, 51 | { "t": "ensure_file", "without": "mfg", 52 | "file": "/lib/svc/manifest/site/postboot.xml", 53 | "src": "sled-postboot.xml", 54 | "owner": "root", "group": "bin", "mode": "0644" }, 55 | 56 | { "t": "ensure_file", 57 | "file": "/var/svc/profile/site.xml", 58 | "src": "site${mfg?-mfg}${compliance?-compliance}.xml", 59 | "owner": "root", "group": "root", "mode": "644" }, 60 | 61 | { "t": "include", "with": "stress", "name": "stress" }, 62 | { "t": "include", "with": "compliance", "name": "stress" }, 63 | 64 | { "t": "include", "name": "root-noauth" }, 65 | 66 | { "t": "ensure_file", "with": "mfg", 67 | "file": "/lib/svc/method/mfg", 68 | "src": "mfg.sh", 69 | "owner": "root", "group": "sys", "mode": "0755" }, 70 | { "t": "ensure_file", "with": "mfg", 71 | "file": "/lib/svc/manifest/site/mfg.xml", 72 | "src": "mfg.xml", 73 | "owner": "root", "group": "sys", "mode": "0644" }, 74 | 75 | { "t": "ensure_file", "without": "no-pilot", 76 | "file": "/usr/bin/pilot", 77 | "extsrc": "pilot/target/release/pilot", 78 | "owner": "root", "group": "bin", "mode": "0755" }, 79 | { "t": "include", "without": "no-pilot", "name": "compliance-common" }, 80 | 81 | { "t": "ensure_file", "with": "mbist", 82 | "file": "/usr/bin/dmar-report", 83 | "extsrc": "dmar-report/target/release/dmar-report", 84 | "owner": "root", "group": "bin", "mode": "0755" }, 85 | 86 | { "t": "include", "with": "genproto", 87 | "name": "genproto", "file": "${genproto}" }, 88 | 89 | { "t": "include", "name": "smf-reduce" }, 90 | { "t": "seed_smf", "skip_seed": true, 91 | "apply_profiles": [ 92 | "generic", 93 | "platform", 94 | "site" 95 | ] 96 | } 97 | ] 98 | } 99 | -------------------------------------------------------------------------------- /image/templates/files/mfg_default_login: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2008 Sun Microsystems, Inc. All rights reserved. 3 | # Use is subject to license terms. 4 | # 5 | # CDDL HEADER START 6 | # 7 | # The contents of this file are subject to the terms of the 8 | # Common Development and Distribution License (the "License"). 9 | # You may not use this file except in compliance with the License. 10 | # 11 | # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 12 | # or http://www.opensolaris.org/os/licensing. 13 | # See the License for the specific language governing permissions 14 | # and limitations under the License. 15 | # 16 | # When distributing Covered Code, include this CDDL HEADER in each 17 | # file and include the License file at usr/src/OPENSOLARIS.LICENSE. 18 | # If applicable, add the following below this CDDL HEADER, with the 19 | # fields enclosed by brackets "[]" replaced with your own identifying 20 | # information: Portions Copyright [yyyy] [name of copyright owner] 21 | # 22 | # CDDL HEADER END 23 | # 24 | 25 | # Set the TZ environment variable of the shell. 26 | # 27 | #TIMEZONE=EST5EDT 28 | 29 | # ULIMIT sets the file size limit for the login. Units are disk blocks. 30 | # The default of zero means no limit. 31 | # 32 | #ULIMIT=0 33 | 34 | # If CONSOLE is set, root can only login on that device. 35 | # If the specified device is /dev/console, then root can also log into 36 | # any of the currently enabled /dev/vt/# virtual terminal devices. 37 | # Comment this line out to allow remote login by root. 38 | # 39 | #CONSOLE=/dev/console 40 | 41 | # PASSREQ determines if login requires a password. 42 | # 43 | PASSREQ=NO 44 | 45 | # ALTSHELL determines if the SHELL environment variable should be set 46 | # 47 | ALTSHELL=YES 48 | 49 | # PATH sets the initial shell PATH variable 50 | # sample with GNU tools in front of the path 51 | # PATH=/usr/gnu/bin:/usr/bin:/usr/sbin:/sbin 52 | # sample with XPG4 tools in front of the path 53 | # PATH=/usr/xpg4/bin:/usr/bin:/usr/sbin:/sbin 54 | PATH=/usr/bin:/usr/sbin:/sbin:/usr/gnu/bin 55 | 56 | # SUPATH sets the initial shell PATH variable for root 57 | # 58 | SUPATH=/usr/sbin:/sbin:/usr/bin 59 | 60 | # TIMEOUT sets the number of seconds (between 0 and 900) to wait before 61 | # abandoning a login session. 62 | # 63 | #TIMEOUT=300 64 | 65 | # UMASK sets the initial shell file creation mode mask. See umask(1). 66 | # 67 | #UMASK=022 68 | 69 | # SYSLOG determines whether the syslog(3) LOG_AUTH facility should be used 70 | # to log all root logins at level LOG_NOTICE and multiple failed login 71 | # attempts at LOG_CRIT. 72 | # 73 | SYSLOG=YES 74 | 75 | # SLEEPTIME controls the number of seconds that the command should 76 | # wait before printing the "login incorrect" message when a 77 | # bad password is provided. The range is limited from 78 | # 0 to 5 seconds. 79 | # 80 | #SLEEPTIME=4 81 | 82 | # DISABLETIME If present, and greater than zero, the number of seconds 83 | # login will wait after RETRIES failed attempts or the PAM framework returns 84 | # PAM_ABORT. Default is 20. Minimum is 0. No maximum is imposed. 85 | # 86 | #DISABLETIME=20 87 | 88 | # RETRIES determines the number of failed logins that will be 89 | # allowed before login exits. Default is 5 and maximum is 15. 90 | # If account locking is configured (user_attr(5)/policy.conf(5)) 91 | # for a local user's account (passwd(5)/shadow(5)), that account 92 | # will be locked if failed logins equals or exceeds RETRIES. 93 | # 94 | #RETRIES=5 95 | # 96 | # The SYSLOG_FAILED_LOGINS variable is used to determine how many failed 97 | # login attempts will be allowed by the system before a failed login 98 | # message is logged, using the syslog(3) LOG_NOTICE facility. For example, 99 | # if the variable is set to 0, login will log -all- failed login attempts. 100 | # 101 | #SYSLOG_FAILED_LOGINS=5 102 | -------------------------------------------------------------------------------- /image/templates/files/sshd_config: -------------------------------------------------------------------------------- 1 | # $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $ 2 | 3 | # This is the sshd server system-wide configuration file. See 4 | # sshd_config(5) for more information. 5 | 6 | # This sshd was compiled with PATH=/usr/ccs/bin:/usr/bin:/bin:/usr/sbin:/sbin 7 | 8 | # The strategy used for options in the default sshd_config shipped with 9 | # OpenSSH is to specify options with their default value where 10 | # possible, but leave them commented. Uncommented options override the 11 | # default value. 12 | 13 | #Port 22 14 | #AddressFamily any 15 | #ListenAddress 0.0.0.0 16 | #ListenAddress :: 17 | 18 | #HostKey /etc/ssh/ssh_host_rsa_key 19 | #HostKey /etc/ssh/ssh_host_ecdsa_key 20 | #HostKey /etc/ssh/ssh_host_ed25519_key 21 | 22 | # Ciphers and keying 23 | #RekeyLimit default none 24 | 25 | # Logging 26 | #SyslogFacility AUTH 27 | #LogLevel INFO 28 | 29 | # Use the client's locale/language settings 30 | #AcceptEnv LANG LC_ALL LC_CTYPE LC_COLLATE LC_TIME LC_NUMERIC 31 | #AcceptEnv LC_MONETARY LC_MESSAGES 32 | AcceptEnv LANG LC_* OXIDE_PREF_* 33 | 34 | # Authentication: 35 | 36 | #LoginGraceTime 2m 37 | PermitRootLogin yes 38 | #StrictModes yes 39 | #MaxAuthTries 6 40 | #MaxSessions 10 41 | 42 | #PubkeyAuthentication yes 43 | 44 | # The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2 45 | # but this is overridden so installations will only check .ssh/authorized_keys 46 | AuthorizedKeysFile .ssh/authorized_keys 47 | 48 | #AuthorizedPrincipalsFile none 49 | 50 | #AuthorizedKeysCommand none 51 | #AuthorizedKeysCommandUser nobody 52 | 53 | # For this to work you will also need host keys in /etc/ssh/ssh_known_hosts 54 | #HostbasedAuthentication no 55 | # Change to yes if you don't trust ~/.ssh/known_hosts for 56 | # HostbasedAuthentication 57 | #IgnoreUserKnownHosts no 58 | # Don't read the user's ~/.rhosts and ~/.shosts files 59 | #IgnoreRhosts yes 60 | 61 | # To disable tunneled clear text passwords, change to no here! 62 | #PasswordAuthentication yes 63 | #PermitEmptyPasswords no 64 | 65 | # Change to no to disable s/key passwords 66 | #ChallengeResponseAuthentication yes 67 | 68 | # Kerberos options 69 | #KerberosAuthentication no 70 | #KerberosOrLocalPasswd yes 71 | #KerberosTicketCleanup yes 72 | #KerberosGetAFSToken no 73 | 74 | # GSSAPI options 75 | #GSSAPIAuthentication no 76 | #GSSAPICleanupCredentials yes 77 | 78 | # Set this to 'yes' to enable PAM authentication, account processing, 79 | # and session processing. If this is enabled, PAM authentication will 80 | # be allowed through the ChallengeResponseAuthentication and 81 | # PasswordAuthentication. Depending on your PAM configuration, 82 | # PAM authentication via ChallengeResponseAuthentication may bypass 83 | # the setting of "PermitRootLogin without-password". 84 | # If you just want the PAM account and session checks to run without 85 | # PAM authentication, then enable this but set PasswordAuthentication 86 | # and ChallengeResponseAuthentication to 'no'. 87 | #UsePAM no 88 | 89 | #AllowAgentForwarding yes 90 | #AllowTcpForwarding yes 91 | #GatewayPorts no 92 | #X11Forwarding no 93 | #X11DisplayOffset 10 94 | #X11UseLocalhost yes 95 | #PermitTTY yes 96 | PrintMotd no 97 | #PrintLastLog yes 98 | #TCPKeepAlive yes 99 | #PermitUserEnvironment no 100 | #Compression delayed 101 | #ClientAliveInterval 0 102 | #ClientAliveCountMax 3 103 | #UseDNS no 104 | #PidFile /var/run/sshd.pid 105 | #MaxStartups 10:30:100 106 | #PermitTunnel no 107 | #ChrootDirectory none 108 | #VersionAddendum none 109 | 110 | # no default banner path 111 | #Banner none 112 | 113 | # override default of no subsystems 114 | Subsystem sftp /usr/libexec/amd64/sftp-server 115 | 116 | # Example of overriding settings on a per-user basis 117 | #Match User anoncvs 118 | # X11Forwarding no 119 | # AllowTcpForwarding no 120 | # PermitTTY no 121 | # ForceCommand cvs server 122 | -------------------------------------------------------------------------------- /tools/helios-build/src/common.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Oxide Computer Company 3 | */ 4 | 5 | use anyhow::{bail, Result}; 6 | use serde::Deserialize; 7 | use slog::{Drain, Logger}; 8 | use std::io::IsTerminal; 9 | use std::path::Path; 10 | use std::sync::Mutex; 11 | 12 | pub use slog::{info, o}; 13 | 14 | /** 15 | * Initialise a logger which writes to stdout, and which does the right thing on 16 | * both an interactive terminal and when stdout is not a tty. 17 | */ 18 | pub fn init_log() -> Logger { 19 | let dec = slog_term::TermDecorator::new().stdout().build(); 20 | if std::io::stdout().is_terminal() { 21 | let dr = Mutex::new(slog_term::CompactFormat::new(dec).build()).fuse(); 22 | slog::Logger::root(dr, o!()) 23 | } else { 24 | let dr = Mutex::new( 25 | slog_term::FullFormat::new(dec).use_original_order().build(), 26 | ) 27 | .fuse(); 28 | slog::Logger::root(dr, o!()) 29 | } 30 | } 31 | 32 | pub fn sleep(s: u64) { 33 | std::thread::sleep(std::time::Duration::from_secs(s)); 34 | } 35 | 36 | pub trait OutputExt { 37 | fn info(&self) -> String; 38 | } 39 | 40 | impl OutputExt for std::process::Output { 41 | fn info(&self) -> String { 42 | let mut out = String::new(); 43 | 44 | if let Some(code) = self.status.code() { 45 | out.push_str(&format!("exit code {}", code)); 46 | } 47 | 48 | /* 49 | * Attempt to render stderr from the command: 50 | */ 51 | let stderr = String::from_utf8_lossy(&self.stderr).trim().to_string(); 52 | let extra = if stderr.is_empty() { 53 | /* 54 | * If there is no stderr output, this command might emit its 55 | * failure message on stdout: 56 | */ 57 | String::from_utf8_lossy(&self.stdout).trim().to_string() 58 | } else { 59 | stderr 60 | }; 61 | 62 | if !extra.is_empty() { 63 | if !out.is_empty() { 64 | out.push_str(": "); 65 | } 66 | out.push_str(&extra); 67 | } 68 | 69 | out 70 | } 71 | } 72 | 73 | pub fn read_toml(path: P) -> Result 74 | where 75 | P: AsRef, 76 | for<'de> O: Deserialize<'de>, 77 | { 78 | Ok(toml::from_str(&std::fs::read_to_string(path.as_ref())?)?) 79 | } 80 | 81 | fn exists>(path: P) -> Result> { 82 | let p = path.as_ref(); 83 | match std::fs::metadata(p) { 84 | Ok(m) => Ok(Some(m)), 85 | Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), 86 | Err(e) => bail!("checking for path {}: {}", p.display(), e), 87 | } 88 | } 89 | 90 | #[allow(unused)] 91 | pub fn exists_file>(path: P) -> Result { 92 | let p = path.as_ref(); 93 | 94 | if let Some(m) = exists(p)? { 95 | if m.is_file() { 96 | Ok(true) 97 | } else { 98 | bail!("path {} exists but is not a file", p.display()); 99 | } 100 | } else { 101 | Ok(false) 102 | } 103 | } 104 | 105 | pub fn exists_dir>(path: P) -> Result { 106 | let p = path.as_ref(); 107 | 108 | if let Some(m) = exists(p)? { 109 | if m.is_dir() { 110 | Ok(true) 111 | } else { 112 | bail!("path {} exists but is not a directory", p.display()); 113 | } 114 | } else { 115 | Ok(false) 116 | } 117 | } 118 | 119 | /** 120 | * Try to unlink a file. If it did not exist, treat that as a success; report 121 | * any other error. 122 | */ 123 | pub fn maybe_unlink(f: &Path) -> Result<()> { 124 | match std::fs::remove_file(f) { 125 | Ok(_) => Ok(()), 126 | Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()), 127 | Err(e) => bail!("could not remove {f:?}: {e:?}"), 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /tools/helios-build/src/expand.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | /* 3 | * Copyright 2024 Oxide Computer Company 4 | */ 5 | 6 | use anyhow::{bail, Result}; 7 | 8 | pub struct Expansion { 9 | chunks: Vec, 10 | } 11 | 12 | enum Chunk { 13 | Char(char), 14 | Simple(String), 15 | IfLiteral(String, String), 16 | } 17 | 18 | fn is_variable_char(c: char) -> bool { 19 | c.is_ascii_alphanumeric() || c == '-' || c == '_' 20 | } 21 | 22 | /* 23 | * Current expansion forms: 24 | * 25 | * ${variable?literal} expand to "literal" if variable is defined, 26 | * otherwise the empty string 27 | * ${variable} expand to "variable" if set, or error if not 28 | */ 29 | fn expand(expand: &str) -> Result { 30 | enum State { 31 | Variable, 32 | Literal, 33 | } 34 | 35 | let mut s = State::Variable; 36 | let mut chars = expand.chars(); 37 | let mut variable = String::new(); 38 | let mut literal = String::new(); 39 | 40 | loop { 41 | match s { 42 | State::Variable => match chars.next() { 43 | Some('?') => { 44 | if variable.is_empty() { 45 | bail!("empty variable unexpected"); 46 | } 47 | s = State::Literal; 48 | } 49 | Some(c) if is_variable_char(c) => variable.push(c), 50 | Some(c) => bail!("unexpected char in variable name: {:?}", c), 51 | None => { 52 | if variable.is_empty() { 53 | bail!("empty variable unexpected"); 54 | } 55 | return Ok(Chunk::Simple(variable)); 56 | } 57 | }, 58 | State::Literal => match chars.next() { 59 | Some(c) => literal.push(c), 60 | None => return Ok(Chunk::IfLiteral(variable, literal)), 61 | }, 62 | } 63 | } 64 | } 65 | 66 | impl Expansion { 67 | pub fn parse(template: &str) -> Result { 68 | enum State { 69 | Rest, 70 | Dollar, 71 | Expansion, 72 | } 73 | 74 | let mut s = State::Rest; 75 | let mut chars = template.chars(); 76 | let mut chunks = Vec::new(); 77 | let mut exp = String::new(); 78 | 79 | loop { 80 | match s { 81 | State::Rest => match chars.next() { 82 | Some('$') => { 83 | s = State::Dollar; 84 | } 85 | Some(c) => { 86 | chunks.push(Chunk::Char(c)); 87 | } 88 | None => { 89 | return Ok(Expansion { chunks }); 90 | } 91 | }, 92 | State::Dollar => match chars.next() { 93 | Some('$') => { 94 | chunks.push(Chunk::Char('$')); 95 | s = State::Rest; 96 | } 97 | Some('{') => { 98 | s = State::Expansion; 99 | } 100 | Some(c) => { 101 | bail!("expected $ or {{ after $, not {:?}", c); 102 | } 103 | None => { 104 | bail!("unexpected end of string after $"); 105 | } 106 | }, 107 | State::Expansion => match chars.next() { 108 | Some('}') => { 109 | chunks.push(expand(&exp)?); 110 | exp.clear(); 111 | s = State::Rest; 112 | } 113 | Some('$') => { 114 | bail!("no nesting in expansions for now"); 115 | } 116 | Some(c) => { 117 | exp.push(c); 118 | } 119 | None => { 120 | bail!("unexpected end of string after ${{"); 121 | } 122 | }, 123 | } 124 | } 125 | } 126 | 127 | pub fn evaluate( 128 | &self, 129 | variables: &HashMap, 130 | ) -> Result { 131 | let mut out = String::new(); 132 | 133 | for ch in self.chunks.iter() { 134 | match ch { 135 | Chunk::Char(c) => { 136 | out.push(*c); 137 | } 138 | Chunk::Simple(f) => { 139 | if let Some(v) = variables.get(f) { 140 | out.push_str(v); 141 | } else { 142 | bail!("variable {:?} not defined", f); 143 | } 144 | } 145 | Chunk::IfLiteral(f, l) => { 146 | if variables.contains_key(f) { 147 | out.push_str(l); 148 | } 149 | } 150 | } 151 | } 152 | 153 | Ok(out) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /image/templates/sled/ramdisk-01-os.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataset": { 3 | "name": "ramdisk", 4 | "output_snapshot": "os" 5 | }, 6 | 7 | "steps": [ 8 | { "t": "pkg_image_create" }, 9 | 10 | { "t": "pkg_set_publisher", "with": "publisher_0_name", 11 | "publisher": "${publisher_0_name}", 12 | "uris": [ "${@@publisher_0_url}" ] }, 13 | { "t": "pkg_set_publisher", "with": "publisher_1_name", 14 | "publisher": "${publisher_1_name}", 15 | "uris": [ "${@@publisher_1_url}" ] }, 16 | { "t": "pkg_set_publisher", "with": "publisher_2_name", 17 | "publisher": "${publisher_2_name}", 18 | "uris": [ "${@@publisher_2_url}" ] }, 19 | { "t": "pkg_set_publisher", "with": "publisher_3_name", 20 | "publisher": "${publisher_3_name}", 21 | "uris": [ "${@@publisher_3_url}" ] }, 22 | 23 | { "t": "pkg_change_variant", "variant": "opensolaris.zone", 24 | "value": "global" }, 25 | { "t": "pkg_change_variant", "variant": "opensolaris.imagetype", 26 | "value": "partial" }, 27 | { "t": "pkg_change_variant", "with": "debug_variant", 28 | "variant": "debug.illumos", "value": "true" }, 29 | 30 | { "t": "pkg_change_facet", "facet": "openssl.10", 31 | "value": "false" }, 32 | { "t": "pkg_change_facet", "facet": "doc", 33 | "value": "false", "with": "recovery" }, 34 | { "t": "pkg_change_facet", "facet": "doc.man", 35 | "value": "false", "with": "recovery" }, 36 | 37 | { "t": "pkg_install", "pkgs": [ 38 | "/developer/debug/mdb", 39 | "/system/kernel/dtrace/providers", 40 | "/system/network", 41 | "/system/microcode/oxide", 42 | 43 | "/driver/network/cxgbe", 44 | "/driver/network/e1000g", 45 | "/driver/network/igb", 46 | "/driver/network/ixgbe", 47 | "/driver/network/rge", 48 | 49 | "/driver/storage/nvme", 50 | "/developer/macro/cpp", 51 | 52 | "/system/library/gcc-runtime", 53 | "/system/library/g++-runtime", 54 | "/developer/linker", 55 | "/diagnostic/diskinfo", 56 | "/diagnostic/pci", 57 | "/diagnostic/cpu-counters", 58 | "/system/data/hardware-registry", 59 | "/system/extended-system-utilities", 60 | "/web/curl", 61 | "/text/column", 62 | "/text/less", 63 | "/text/looker", 64 | "/ooce/util/jq", 65 | "/system/watch", 66 | "/editor/vim", 67 | "/terminal/resize", 68 | "/system/data/urxvt-terminfo", 69 | "/network/netcat", 70 | "/network/rsync", 71 | "/network/snoop", 72 | "/network/overwatch", 73 | "/compress/gzip", 74 | "/locale/en", 75 | "/system/network/routing", 76 | "/driver/cpu/amd/zen", 77 | "/driver/gpio/amd/zen", 78 | "/system/gpio", 79 | "/system/bhyve", 80 | "/system/library/bhyve", 81 | "/service/network/chrony", 82 | 83 | "/driver/misc/tofino", 84 | "/library/expat", 85 | "/system/library/pcap", 86 | 87 | "/network/dns/bind", 88 | "/network/openssh-server", 89 | 90 | "oxide/platform-identity-cacerts", 91 | 92 | "${@extra_packages}" 93 | ] }, 94 | 95 | { "t": "pkg_install", "without": "recovery", "pkgs": [ 96 | "/system/man", 97 | "/developer/object-file", 98 | "/system/ksensor", 99 | "/driver/cpu/sensor", 100 | "/driver/ktest" 101 | ] }, 102 | 103 | { "t": "pkg_install", "with": "omicron1", "pkgs": [ 104 | "/library/libxmlsec1", 105 | "/ooce/library/postgresql-13", 106 | "/system/zones/brand/omicron1" 107 | ] }, 108 | 109 | { "t": "pkg_install", "with": "optever", "pkgs": [ 110 | "/driver/network/opte@${optever}" 111 | ] }, 112 | 113 | { "t": "pkg_install", "with": "mbist", "pkgs": [ 114 | "/driver/developer/amd/zen" 115 | ] }, 116 | 117 | { "t": "pkg_install", "with": "mfg", "pkgs": [ 118 | "/driver/developer/amd/zen", 119 | "/driver/cpu/amd/psp", 120 | "/driver/developer/amd/psp/einj" 121 | ] }, 122 | 123 | { "t": "pkg_install", "with": "compliance", "pkgs": [ 124 | "/driver/developer/amd/zen", 125 | "/driver/cpu/amd/psp", 126 | "/driver/developer/amd/psp/einj", 127 | "/developer/debug/humility" 128 | ] }, 129 | 130 | { "t": "pkg_install", "with": "stress", "pkgs": [ 131 | "/ooce/util/stress-ng", 132 | "/system/test/fio" 133 | ] }, 134 | 135 | { "t": "pkg_install", "with": "compliance", "pkgs": [ 136 | "/ooce/util/stress-ng", 137 | "/system/test/fio" 138 | ] }, 139 | 140 | { "t": "pkg_install", "with": "nfs", "pkgs": [ 141 | "/system/file-system/autofs", 142 | "/system/file-system/nfs" 143 | ] } 144 | ] 145 | } 146 | -------------------------------------------------------------------------------- /tools/helios-build/src/archive.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | /* 3 | * Copyright 2024 Oxide Computer Company 4 | */ 5 | 6 | use std::path::{Path, PathBuf}; 7 | use std::sync::mpsc; 8 | use std::thread::JoinHandle; 9 | use std::time::{SystemTime, UNIX_EPOCH}; 10 | 11 | use crate::maybe_unlink; 12 | use anyhow::{bail, Result}; 13 | use helios_build_utils::metadata::Metadata; 14 | 15 | enum Act { 16 | File(String, PathBuf), 17 | FileWithData(String, Vec), 18 | Complete, 19 | } 20 | 21 | /** 22 | * Create a tar file with the gzip compressor running in another thread. Files 23 | * are pushed from the main thread into a channel, where the worker thread adds 24 | * files to the archive as directed. The result, success or error, is made 25 | * available to the user when they join the worker thread. 26 | */ 27 | pub struct Archive { 28 | tx: mpsc::Sender, 29 | hdl: JoinHandle>, 30 | } 31 | 32 | impl Archive { 33 | pub fn new(p: &Path, m: Metadata) -> Result { 34 | let path = p.to_path_buf(); 35 | 36 | maybe_unlink(&path)?; 37 | let f = std::fs::OpenOptions::new() 38 | .create(true) 39 | .truncate(true) 40 | .write(true) 41 | .open(&path)?; 42 | let gzw = flate2::write::GzEncoder::new(f, flate2::Compression::best()); 43 | let mut tar = tar::Builder::new(gzw); 44 | let mtime = 45 | SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); 46 | 47 | /* 48 | * Append the metadata file first in the archive. 49 | */ 50 | m.append_to_tar(&mut tar)?; 51 | 52 | /* 53 | * Append the image/ directory under which all of our data files will be 54 | * included: 55 | */ 56 | { 57 | let mut h = tar::Header::new_ustar(); 58 | 59 | h.set_entry_type(tar::EntryType::Directory); 60 | h.set_username("root")?; 61 | h.set_uid(0); 62 | h.set_groupname("root")?; 63 | h.set_gid(0); 64 | h.set_path("image")?; 65 | h.set_mtime(mtime); 66 | h.set_mode(0o755); 67 | h.set_size(0); 68 | h.set_cksum(); 69 | 70 | tar.append(&h, std::io::empty())?; 71 | } 72 | 73 | let (tx, rx) = mpsc::channel(); 74 | 75 | let hdl = std::thread::spawn(move || -> Result<()> { 76 | loop { 77 | match rx.recv().unwrap() { 78 | Act::File(name, path) => { 79 | let f = std::fs::OpenOptions::new() 80 | .create(false) 81 | .read(true) 82 | .open(&path)?; 83 | 84 | let mut h = tar::Header::new_ustar(); 85 | 86 | h.set_entry_type(tar::EntryType::Regular); 87 | h.set_metadata(&f.metadata()?); 88 | h.set_mode(0o444); 89 | h.set_username("root")?; 90 | h.set_uid(0); 91 | h.set_groupname("root")?; 92 | h.set_uid(0); 93 | h.set_path(&name)?; 94 | h.set_mtime(mtime); 95 | h.set_cksum(); 96 | 97 | tar.append(&h, f)?; 98 | } 99 | Act::FileWithData(name, data) => { 100 | let mut h = tar::Header::new_ustar(); 101 | 102 | let mtime = SystemTime::now() 103 | .duration_since(UNIX_EPOCH) 104 | .unwrap() 105 | .as_secs(); 106 | 107 | h.set_entry_type(tar::EntryType::Regular); 108 | h.set_mode(0o444); 109 | h.set_username("root")?; 110 | h.set_uid(0); 111 | h.set_groupname("root")?; 112 | h.set_uid(0); 113 | h.set_path(&name)?; 114 | h.set_mtime(mtime); 115 | h.set_size(data.len().try_into().unwrap()); 116 | h.set_cksum(); 117 | 118 | tar.append(&h, data.as_slice())?; 119 | } 120 | Act::Complete => break, 121 | } 122 | } 123 | 124 | let gzw = tar.into_inner()?; 125 | let mut f = gzw.finish()?; 126 | f.flush()?; 127 | Ok(()) 128 | }); 129 | 130 | Ok(Archive { tx, hdl }) 131 | } 132 | 133 | pub fn add_file(&self, p: &Path, n: &str) -> Result<()> { 134 | if !p.is_file() { 135 | bail!("{p:?} is not a file"); 136 | } 137 | 138 | if n.contains("/") { 139 | bail!("{n:?} must be a bare file name, not directory components"); 140 | } 141 | 142 | self.tx.send(Act::File(format!("image/{n}"), p.to_path_buf()))?; 143 | Ok(()) 144 | } 145 | 146 | pub fn add_file_with_data(&self, data: Vec, n: &str) -> Result<()> { 147 | if n.contains("/") { 148 | bail!("{n:?} must be a bare file name, not directory components"); 149 | } 150 | 151 | self.tx.send(Act::FileWithData(format!("image/{n}"), data))?; 152 | Ok(()) 153 | } 154 | 155 | pub fn finish(self) -> Result<()> { 156 | self.tx.send(Act::Complete)?; 157 | self.hdl.join().unwrap() 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /image/templates/sled/ramdisk-02-trim.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataset": { 3 | "name": "ramdisk", 4 | "input_snapshot": "os", 5 | "output_snapshot": "trim" 6 | }, 7 | 8 | "steps": [ 9 | { "t": "remove_files", "dir": "/var/pkg" }, 10 | 11 | { "t": "remove_files", "dir": "/usr/xpg4" }, 12 | { "t": "remove_files", "dir": "/usr/include" }, 13 | { "t": "remove_files", "dir": "/usr/demo" }, 14 | { "t": "remove_files", "dir": "/usr/lib/help" }, 15 | { "t": "remove_files", "dir": "/usr/share/doc" }, 16 | { "t": "remove_files", "dir": "/usr/share/bash-completion" }, 17 | 18 | { "t": "remove_files", 19 | "dir": "/usr/perl5/5.36/man", "without": "recovery" }, 20 | { "t": "remove_files", 21 | "dir": "/usr/share/man/man3ssl", "without": "recovery" }, 22 | { "t": "remove_files", 23 | "dir": "/usr/ssl-3/man", "without": "recovery" }, 24 | 25 | { "t": "remove_files", "dir": "/etc/net-snmp" }, 26 | 27 | { "t": "remove_files", "dir": "/usr/bin/i386" }, 28 | { "t": "remove_files", "dir": "/platform/i86xpv" }, 29 | 30 | { "t": "remove_files", "pattern": "*.a" }, 31 | 32 | { "t": "remove_files", "dir": "/boot" }, 33 | { "t": "remove_files", "dir": "/platform/i86pc" }, 34 | { "t": "remove_files", "dir": "/usr/platform/i86xpv" }, 35 | { "t": "remove_files", "dir": "/usr/platform/i86pc" }, 36 | 37 | { "t": "remove_files", "file": "/kernel/misc/amd64/acpica" }, 38 | { "t": "remove_files", "file": "/kernel/misc/amd64/tem" }, 39 | { "t": "remove_files", "file": "/kernel/misc/amd64/kbtrans" }, 40 | 41 | { "t": "remove_files", "file": "/kernel/drv/amd64/wc" }, 42 | { "t": "remove_files", "file": "/kernel/drv/amd64/acpi_drv" }, 43 | { "t": "remove_files", "file": "/kernel/drv/amd64/conskbd" }, 44 | { "t": "remove_files", "file": "/kernel/drv/amd64/tzmon" }, 45 | { "t": "remove_files", "file": "/kernel/drv/amd64/power" }, 46 | 47 | { "t": "remove_files", "file": "/kernel/drv/amd64/audio" }, 48 | { "t": "remove_files", "file": "/kernel/drv/amd64/audio1575" }, 49 | { "t": "remove_files", "file": "/kernel/drv/amd64/audioens" }, 50 | { "t": "remove_files", "file": "/kernel/drv/amd64/audiopci" }, 51 | { "t": "remove_files", "file": "/kernel/drv/amd64/audiots" }, 52 | 53 | { "t": "remove_files", "file": "/kernel/drv/amd64/ehci" }, 54 | { "t": "remove_files", "file": "/kernel/drv/amd64/hid" }, 55 | { "t": "remove_files", "file": "/kernel/drv/amd64/hubd" }, 56 | { "t": "remove_files", "file": "/kernel/drv/amd64/ohci" }, 57 | { "t": "remove_files", "file": "/kernel/drv/amd64/scsa2usb" }, 58 | { "t": "remove_files", "file": "/kernel/drv/amd64/uhci" }, 59 | { "t": "remove_files", "file": "/kernel/drv/amd64/usb_ac" }, 60 | { "t": "remove_files", "file": "/kernel/drv/amd64/usb_as" }, 61 | { "t": "remove_files", "file": "/kernel/drv/amd64/usb_ia" }, 62 | { "t": "remove_files", "file": "/kernel/drv/amd64/usb_mid" }, 63 | { "t": "remove_files", "file": "/kernel/drv/amd64/usbprn" }, 64 | { "t": "remove_files", "file": "/kernel/drv/amd64/xhci" }, 65 | 66 | { "t": "remove_files", "file": "/kernel/drv/amd64/intel_nhm" }, 67 | { "t": "remove_files", "file": "/kernel/drv/amd64/intel_nb5000" }, 68 | { "t": "remove_files", "file": "/kernel/drv/amd64/mc-amd" }, 69 | 70 | { "t": "remove_files", "file": "/kernel/drv/amd64/acpi_toshiba" }, 71 | { "t": "remove_files", "file": "/kernel/drv/amd64/intel_nhmex" }, 72 | { "t": "remove_files", "file": "/kernel/drv/intel_nhmex.conf" }, 73 | { "t": "remove_files", "file": "/kernel/drv/amd64/mpt" }, 74 | { "t": "remove_files", "file": "/kernel/drv/mpt.conf" }, 75 | 76 | { "t": "remove_files", 77 | "file": "/usr/lib/fm/fmd/plugins/ses-log-transport.so" }, 78 | { "t": "remove_files", 79 | "file": "/usr/lib/fm/fmd/plugins/ses-log-transport.conf" }, 80 | 81 | { "t": "remove_files", "file": "/sbin/bootadm" }, 82 | { "t": "remove_files", "file": "/usr/sbin/bootadm" }, 83 | { "t": "remove_files", "file": "/sbin/beadm" }, 84 | { "t": "remove_files", "file": "/usr/sbin/beadm" }, 85 | 86 | { "t": "remove_files", "file": "/etc/default/tar" }, 87 | 88 | { "t": "ensure_file", 89 | "file": "/lib/svc/method/keymap", 90 | "contents": "", 91 | "owner": "root", "group": "bin", "mode": "755" }, 92 | { "t": "ensure_file", 93 | "file": "/lib/svc/bin/rootisramdisk", 94 | "src": "rootisramdisk", 95 | "owner": "root", "group": "sys", "mode": "755" }, 96 | 97 | { "t": "ensure_file", 98 | "file": "/root/.bash_profile", 99 | "src": "bash_profile", 100 | "owner": "root", "group": "root", "mode": "0644" }, 101 | { "t": "ensure_file", 102 | "file": "/root/.bashrc", 103 | "src": "bashrc", 104 | "owner": "root", "group": "root", "mode": "0644" }, 105 | { "t": "ensure_file", 106 | "file": "/etc/motd", 107 | "src": "motd", 108 | "owner": "root", "group": "sys", "mode": "0644" }, 109 | 110 | { "t": "ensure_file", 111 | "file": "/etc/system.d/zfs:dbuf", 112 | "src": "sled-system-zfs:dbuf", 113 | "owner": "root", "group": "sys", "mode": "0644" }, 114 | 115 | { "t": "assemble_files", 116 | "dir": "/etc/system.d", 117 | "output": "/etc/system.d/.self-assembly" }, 118 | 119 | { "t": "ensure_file", 120 | "file": "/etc/dumpadm.conf", 121 | "src": "dumpadm.conf", 122 | "owner": "root", "group": "other", "mode": "0644" }, 123 | 124 | { "t": "ensure_dir", "with": "omicron1", 125 | "dir": "/usr/lib/brand/omicron1/baseline", 126 | "owner": "root", "group": "sys", "mode": "0755" }, 127 | { "t": "ensure_file", "with": "omicron1", 128 | "file": "/usr/lib/brand/omicron1/baseline/files.tar.gz", 129 | "extsrc": "files.tar.gz", 130 | "owner": "root", "group": "sys", "mode": "0644" }, 131 | { "t": "ensure_file", "with": "omicron1", 132 | "file": "/usr/lib/brand/omicron1/baseline/gzonly.txt", 133 | "extsrc": "gzonly.txt", 134 | "owner": "root", "group": "sys", "mode": "0644" }, 135 | 136 | { "t": "pack_tar", "name": "sled-ramdisk.tar" } 137 | ] 138 | } 139 | -------------------------------------------------------------------------------- /image/mkcpio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2024 Oxide Computer Company 4 | # 5 | 6 | set -o errexit 7 | set -o pipefail 8 | set -o xtrace 9 | 10 | # 11 | # XXX This should be done by image-builder like everything else... 12 | # 13 | root=$1 14 | outfile=$2 15 | tmpdir=$3 16 | 17 | # 18 | # Files that we get from the image: 19 | # 20 | (cd "$root" && cpio -qo -H odc -O "$outfile") <<'EOF' 21 | etc/dacf.conf 22 | etc/driver_aliases 23 | etc/driver_classes 24 | etc/mach 25 | etc/name_to_major 26 | etc/name_to_sysnum 27 | etc/path_to_inst 28 | etc/system 29 | etc/system.d/.self-assembly 30 | etc/versions/build 31 | kernel/amd64/genunix 32 | kernel/crypto/amd64/aes 33 | kernel/crypto/amd64/arcfour 34 | kernel/crypto/amd64/blowfish 35 | kernel/crypto/amd64/des 36 | kernel/crypto/amd64/ecc 37 | kernel/crypto/amd64/edonr 38 | kernel/crypto/amd64/md4 39 | kernel/crypto/amd64/md5 40 | kernel/crypto/amd64/rsa 41 | kernel/crypto/amd64/sha1 42 | kernel/crypto/amd64/sha2 43 | kernel/crypto/amd64/skein 44 | kernel/crypto/amd64/swrand 45 | kernel/dacf/amd64/net_dacf 46 | kernel/drv/amd64/bl 47 | kernel/drv/amd64/blkdev 48 | kernel/drv/amd64/clone 49 | kernel/drv/amd64/cn 50 | kernel/drv/amd64/cpuid 51 | kernel/drv/amd64/cpunex 52 | kernel/drv/amd64/crypto 53 | kernel/drv/amd64/cryptoadm 54 | kernel/drv/amd64/cxgbe 55 | kernel/drv/amd64/devinfo 56 | kernel/drv/amd64/dld 57 | kernel/drv/amd64/dlpistub 58 | kernel/drv/amd64/e1000g 59 | kernel/drv/amd64/fm 60 | kernel/drv/amd64/igb 61 | kernel/drv/amd64/iwscn 62 | kernel/drv/amd64/ixgbe 63 | kernel/drv/amd64/kmdb 64 | kernel/drv/amd64/lofi 65 | kernel/drv/amd64/log 66 | kernel/drv/amd64/mm 67 | kernel/drv/amd64/nulldriver 68 | kernel/drv/amd64/nvme 69 | kernel/drv/amd64/openeepr 70 | kernel/drv/amd64/options 71 | kernel/drv/amd64/pci_pci 72 | kernel/drv/amd64/pcieb 73 | kernel/drv/amd64/physmem 74 | kernel/drv/amd64/poll 75 | kernel/drv/amd64/pseudo 76 | kernel/drv/amd64/ptc 77 | kernel/drv/amd64/ptsl 78 | kernel/drv/amd64/ramdisk 79 | kernel/drv/amd64/random 80 | kernel/drv/amd64/rge 81 | kernel/drv/amd64/sad 82 | kernel/drv/amd64/softmac 83 | kernel/drv/amd64/sy 84 | kernel/drv/amd64/sysevent 85 | kernel/drv/amd64/sysmsg 86 | kernel/drv/amd64/t4nex 87 | kernel/drv/amd64/ucode 88 | kernel/drv/amd64/ufm 89 | kernel/drv/amd64/vnic 90 | kernel/drv/amd64/zfs 91 | kernel/drv/bl.conf 92 | kernel/drv/bridge.conf 93 | kernel/drv/clone.conf 94 | kernel/drv/cn.conf 95 | kernel/drv/consms.conf 96 | kernel/drv/cpuid.conf 97 | kernel/drv/crypto.conf 98 | kernel/drv/cryptoadm.conf 99 | kernel/drv/devinfo.conf 100 | kernel/drv/dld.conf 101 | kernel/drv/dlpistub.conf 102 | kernel/drv/e1000g.conf 103 | kernel/drv/fm.conf 104 | kernel/drv/igb.conf 105 | kernel/drv/iwscn.conf 106 | kernel/drv/ixgbe.conf 107 | kernel/drv/kmdb.conf 108 | kernel/drv/llc1.conf 109 | kernel/drv/lofi.conf 110 | kernel/drv/log.conf 111 | kernel/drv/mm.conf 112 | kernel/drv/nvme.conf 113 | kernel/drv/openeepr.conf 114 | kernel/drv/options.conf 115 | kernel/drv/pcieb.conf 116 | kernel/drv/physmem.conf 117 | kernel/drv/poll.conf 118 | kernel/drv/pseudo.conf 119 | kernel/drv/ptc.conf 120 | kernel/drv/ptsl.conf 121 | kernel/drv/ramdisk.conf 122 | kernel/drv/random.conf 123 | kernel/drv/sad.conf 124 | kernel/drv/sgen.conf 125 | kernel/drv/simnet.conf 126 | kernel/drv/smbios.conf 127 | kernel/drv/softmac.conf 128 | kernel/drv/st.conf 129 | kernel/drv/sy.conf 130 | kernel/drv/sysevent.conf 131 | kernel/drv/sysmsg.conf 132 | kernel/drv/ucode.conf 133 | kernel/drv/ufm.conf 134 | kernel/drv/vnic.conf 135 | kernel/drv/zfs.conf 136 | kernel/exec/amd64/elfexec 137 | kernel/exec/amd64/intpexec 138 | kernel/fs/amd64/bootfs 139 | kernel/fs/amd64/ctfs 140 | kernel/fs/amd64/dev 141 | kernel/fs/amd64/devfs 142 | kernel/fs/amd64/fifofs 143 | kernel/fs/amd64/lofs 144 | kernel/fs/amd64/mntfs 145 | kernel/fs/amd64/namefs 146 | kernel/fs/amd64/objfs 147 | kernel/fs/amd64/procfs 148 | kernel/fs/amd64/sharefs 149 | kernel/fs/amd64/specfs 150 | kernel/fs/amd64/tmpfs 151 | kernel/fs/amd64/zfs 152 | kernel/kmdb/amd64/cpc 153 | kernel/kmdb/amd64/cpu_ms.AuthenticAMD.15 154 | kernel/kmdb/amd64/cpu.generic 155 | kernel/kmdb/amd64/crypto 156 | kernel/kmdb/amd64/genunix 157 | kernel/kmdb/amd64/hook 158 | kernel/kmdb/amd64/ipc 159 | kernel/kmdb/amd64/krtld 160 | kernel/kmdb/amd64/lofs 161 | kernel/kmdb/amd64/logindmux 162 | kernel/kmdb/amd64/mac 163 | kernel/kmdb/amd64/mdb_ds 164 | kernel/kmdb/amd64/mm 165 | kernel/kmdb/amd64/neti 166 | kernel/kmdb/amd64/nfs 167 | kernel/kmdb/amd64/ptm 168 | kernel/kmdb/amd64/random 169 | kernel/kmdb/amd64/sata 170 | kernel/kmdb/amd64/sctp 171 | kernel/kmdb/amd64/specfs 172 | kernel/kmdb/amd64/zfs 173 | kernel/mac/amd64/mac_ether 174 | kernel/mac/amd64/mac_ipv4 175 | kernel/mac/amd64/mac_ipv6 176 | kernel/misc/amd64/bignum 177 | kernel/misc/amd64/bootdev 178 | kernel/misc/amd64/busra 179 | kernel/misc/amd64/cmlb 180 | kernel/misc/amd64/consconfig 181 | kernel/misc/amd64/ctf 182 | kernel/misc/amd64/dls 183 | kernel/misc/amd64/edonr 184 | kernel/misc/amd64/fssnap_if 185 | kernel/misc/amd64/gld 186 | kernel/misc/amd64/hidparser 187 | kernel/misc/amd64/hook 188 | kernel/misc/amd64/hpcsvc 189 | kernel/misc/amd64/idmap 190 | kernel/misc/amd64/iommulib 191 | kernel/misc/amd64/ipc 192 | kernel/misc/amd64/kcf 193 | kernel/misc/amd64/kmdbmod 194 | kernel/misc/amd64/mac 195 | kernel/misc/amd64/md5 196 | kernel/misc/amd64/mii 197 | kernel/misc/amd64/neti 198 | kernel/misc/amd64/pci_autoconfig 199 | kernel/misc/amd64/pcicfg 200 | kernel/misc/amd64/pcihp 201 | kernel/misc/amd64/rpcsec 202 | kernel/misc/amd64/sata 203 | kernel/misc/amd64/scsi 204 | kernel/misc/amd64/sha1 205 | kernel/misc/amd64/sha2 206 | kernel/misc/amd64/skein 207 | kernel/misc/amd64/strplumb 208 | kernel/misc/amd64/tlimod 209 | kernel/sched/amd64/SDC 210 | kernel/sched/amd64/TS 211 | kernel/sched/amd64/TS_DPTBL 212 | kernel/strmod/amd64/rpcmod 213 | kernel/sys/amd64/autofs 214 | kernel/sys/amd64/c2audit 215 | kernel/sys/amd64/doorfs 216 | kernel/sys/amd64/inst_sync 217 | kernel/sys/amd64/kaio 218 | kernel/sys/amd64/msgsys 219 | kernel/sys/amd64/pipe 220 | kernel/sys/amd64/portfs 221 | kernel/sys/amd64/pset 222 | kernel/sys/amd64/rpcmod 223 | kernel/sys/amd64/semsys 224 | kernel/sys/amd64/shmsys 225 | platform/oxide/kernel/amd64/unix 226 | platform/oxide/kernel/cpu/amd64/cpu.generic 227 | platform/oxide/kernel/dacf/amd64/consconfig_dacf 228 | platform/oxide/kernel/drv/amd64/cpc 229 | platform/oxide/kernel/drv/amd64/dwu 230 | platform/oxide/kernel/drv/amd64/fch 231 | platform/oxide/kernel/drv/amd64/npe 232 | platform/oxide/kernel/drv/amd64/rootnex 233 | platform/oxide/kernel/drv/cpc.conf 234 | platform/oxide/kernel/drv/dwu.conf 235 | platform/oxide/kernel/kmdb/amd64/apix 236 | platform/oxide/kernel/kmdb/amd64/unix 237 | platform/oxide/kernel/mach/amd64/apix 238 | platform/oxide/kernel/misc/amd64/boot_image 239 | platform/oxide/kernel/misc/amd64/pci_prd 240 | platform/oxide/kernel/misc/amd64/pcie 241 | platform/oxide/kernel/sys/amd64/cpc 242 | platform/oxide/ucode/AuthenticAMD/A011-00 243 | platform/oxide/ucode/AuthenticAMD/fallback/A011-00 244 | platform/oxide/ucode/AuthenticAMD/B021-00 245 | platform/oxide/ucode/AuthenticAMD/B110-00 246 | platform/oxide/ucode/AuthenticAMD/equivalence-table 247 | usr/kernel/pcbe/amd64/pcbe.AuthenticAMD 248 | EOF 249 | 250 | # 251 | # The checksum appears in the boot archive, but nowhere else, because we don't 252 | # know the checksum until after the image itself is sealed up: 253 | # 254 | (cd "$tmpdir" && cpio -qo -H odc -AO "$outfile") <<. 26 | # 27 | 28 | # 29 | # This file contains tunable parameters for dhcpagent(1M). 30 | # 31 | 32 | # All parameters can be tuned for a specific interface by prepending 33 | # the interface name to the parameter name. For example, to make 34 | # VERIFIED_LEASE_ONLY happen on all interfaces except hme0, specify: 35 | # 36 | # hme0.VERIFIED_LEASE_ONLY=no 37 | # VERIFIED_LEASE_ONLY=yes 38 | # 39 | # An interface name alone specifies IPv4 DHCP. For DHCPv6, append ".v6". 40 | # Some examples: 41 | # 42 | # hme0.VERIFIED_LEASE_ONLY=no specify hme0 v4 behavior 43 | # hme0.v6.VERIFIED_LEASE_ONLY=no specify hme0 v6 behavior 44 | # VERIFIED_LEASE_ONLY=no match all v4 interfaces 45 | # .v6.VERIFIED_LEASE_ONLY=no match all v6 interfaces 46 | 47 | # By default, when the DHCP agent is sent a SIGTERM (typically when 48 | # the system is shut down), all managed addresses are dropped rather 49 | # than released. Dropping an address does not notify the DHCP server 50 | # that the address is no longer in use, leaving it possibly available 51 | # for subsequent use by the same client. If DHCP is later restarted 52 | # on the interface, the client will ask the server if it can continue 53 | # to use the address. If the server either grants the request, or 54 | # does not answer (and the lease has not yet expired), then the client 55 | # will use the original address. 56 | # 57 | # Similarly, when the system is suspended and then woken up or when 58 | # the link status transitions from down to up, DHCP will ask the server 59 | # to continue to use the managed address, in case the lease has changed. 60 | # 61 | # By uncommenting the following parameter-value pairs, all managed 62 | # addresses are released on SIGTERM instead, and any that may have been 63 | # saved but cannot be verified will not be used. When SIGTERM is 64 | # received, the DHCP server is notified that the address is available 65 | # for use, and the address will not be saved for a later restart. If 66 | # DHCP receives SIGTHAW or a link-up event, DHCP will attempt to verify 67 | # the previous lease, but if unable to do so, it will not attempt to 68 | # use that lease. This behavior is often preferred for roaming systems. 69 | # 70 | # VERIFIED_LEASE_ONLY=yes 71 | # .v6.VERIFIED_LEASE_ONLY=yes 72 | 73 | # By default, the DHCP agent waits 3 seconds to collect OFFER 74 | # responses to a DISCOVER. If it receives no OFFERs in this time, it 75 | # then waits for another 3 seconds, and so forth. To change this 76 | # behavior, set and uncomment the following parameter-value pair. 77 | # Note: this does not control the retransmission strategy for 78 | # DISCOVERs, which is formally specified in RFC 2131. This parameter 79 | # is specified in seconds. 80 | # 81 | # OFFER_WAIT= 82 | 83 | # By default, the DHCP agent does not send out a client identifier 84 | # (and hence, the chaddr field is used by the DHCP server as the 85 | # client identifier.) To make the DHCP agent send a client 86 | # identifier, set and uncomment the following parameter-value pair. 87 | # Note that by default this is treated as an NVT ASCII string. To 88 | # specify a binary value, prepend "0x" to a sequence of hexadecimal 89 | # digits (for example, the value 0xAABBCC11 would set the client 90 | # identifier to the 4-byte binary sequence 0xAA 0xBB 0xCC 0x11). 91 | # 92 | # CLIENT_ID= 93 | 94 | # By default, for an IPv4 interface that is not in an IP network 95 | # multipathing (IPMP) group, that is not IP over InfiniBand (IPoIB), and 96 | # that is not a logical interface, the DHCP agent will forgo sending a 97 | # client identifier unless CLIENT_ID is defined. 98 | # 99 | # To use a system-managed, RFC 3315-style (i.e., DHCPv6-style) binding 100 | # identifier as documented in RFC 4361, "Node-specific Client Identifiers 101 | # for DHCPv4," for all IPv4 interfaces (unless CLIENT_ID is defined), 102 | # uncomment the following line. 103 | # 104 | # V4_DEFAULT_IAID_DUID=yes 105 | 106 | # By default, the DHCP agent will try to request the Fully Qualified Domain 107 | # Name (FQDN) currently associated with the interface performing DHCP. The 108 | # hostname is defined by using the -h,--reqhost option of ipadm(1M) or the 109 | # ncu ip-reqhost property of nwamcfg(1M) or by flagging the interface as 110 | # primary so that nodename(4) is used as the hostname. 111 | # 112 | # A defined hostname will be used as the FQDN if it is "rooted" (i.e., if 113 | # it ends with a '.') or if it consists of at least three DNS labels (e.g., 114 | # srv.example.com). If the hostname is not an FQDN, then DNS_DOMAINNAME 115 | # will be appended if defined or ADOPT_DOMAINNAME discernment will be used 116 | # if active. If no FQDN can be determined, the option will not be used. 117 | # 118 | # If this REQUEST_FQDN option is enabled, an FQDN will be sent in messages 119 | # to the DHCP server along with RFC 4702 options to request that a 120 | # collaborating DNS server perform DNS updates for A and PTR resource 121 | # records. To prevent sending FQDN and DNS options, uncomment the line 122 | # below. 123 | # 124 | # If an FQDN is sent, REQUEST_HOSTNAME processing will not be done, per RFC 125 | # 4702 (3.1): "clients that send the Client FQDN option in their messages 126 | # MUST NOT also send the Host Name." 127 | # 128 | # REQUEST_FQDN=no 129 | 130 | # By default, the DHCP agent will not attempt to construct an FQDN from a 131 | # PQDN specified by the -h,--reqhost option of ipadm(1M), by the ncu 132 | # ip-reqhost property of nwamcfg(1M), or by nodename(4). Set and 133 | # uncomment the following parameter to indicate a domain name to be used by 134 | # the DHCP agent to construct if necessary an FQDN. 135 | # 136 | # DNS_DOMAINNAME= 137 | 138 | # By default, the DHCP agent will not attempt to use a domain name returned 139 | # by the DHCP server or the domain in resolv.conf(4) to construct an FQDN 140 | # from a PQDN specified by the -h,--reqhost option of ipadm(1M), by the ncu 141 | # ip-reqhost property of nwamcfg(1M), or by nodename(4). Set and uncomment 142 | # the following parameter to indicate that a returned DHCPv4 DNSdmain or the 143 | # domain from resolv.conf(4) should be adopted by the DHCP agent to 144 | # construct if necessary an FQDN. 145 | # 146 | # ADOPT_DOMAINNAME=yes 147 | 148 | # By default, the DHCP agent will try to request the hostname currently 149 | # associated with the interface performing DHCP. If this option is 150 | # enabled, the agent will attempt to use an -h,--reqhost option saved with 151 | # ipadm(1M) or an ncu ip-reqhost property set with nwamcfg(1M); or else 152 | # attempt to find a host name in /etc/hostname., which must contain a 153 | # line of the form 154 | # 155 | # inet name 156 | # 157 | # where "name" is a single RFC 1101-compliant token; or else use 158 | # nodename(4) for a DHCP interface flagged as primary. If found in any of 159 | # these configurations, the token will be used to request that host name 160 | # from the DHCP server. To prevent this, uncomment the following line. 161 | # 162 | # REQUEST_HOSTNAME=no 163 | 164 | # By default, a parameter request list requesting a subnet mask (1), 165 | # router (3), DNS server (6), hostname (12), DNS domain (15), broadcast 166 | # address (28), and encapsulated vendor options (43), is sent to the DHCP 167 | # server when the DHCP agent sends requests. However, if desired, this 168 | # can be changed by altering the following parameter-value pair. The 169 | # numbers correspond to the values defined in the IANA bootp-dhcp-parameters 170 | # registry at the time of this writing. Site and standard option names from 171 | # /etc/dhcp/inittab are also accepted. 172 | # 173 | PARAM_REQUEST_LIST=1,3,6,12,15,28,43 174 | 175 | # The default DHCPv6 parameter request list has preference (7), unicast (12), 176 | # DNS addresses (23), DNS search list (24), NIS addresses (27), and 177 | # NIS domain (29). This may be changed by altering the following parameter- 178 | # value pair. The numbers correspond to the values defined in the IANA 179 | # dhcpv6-parameters registry at the time of this writing. Site and standard 180 | # option names from /etc/dhcp/inittab6 are also accepted. 181 | .v6.PARAM_REQUEST_LIST=7,12,23,24,27,29 182 | 183 | # The parameter ignore list allows you to instruct the DHCP client to discard 184 | # optional parameters received from the DHCP server. The format is the same 185 | # as the request list above. When discarded, a parameter will not be acted 186 | # on by the DHCP client or returned to users via the dhcpinfo(1) command. 187 | PARAM_IGNORE_LIST=12 188 | .v6.PARAM_IGNORE_LIST= 189 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /tools/helios-build/src/ensure.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Oxide Computer Company 3 | */ 4 | 5 | use anyhow::{anyhow, bail, Result}; 6 | use slog::{error, info, warn, Logger}; 7 | use std::ffi::CString; 8 | use std::ffi::OsStr; 9 | use std::fs::{DirBuilder, File}; 10 | use std::io::{BufRead, BufReader, Read, Write}; 11 | use std::os::unix::fs::DirBuilderExt; 12 | use std::path::{Path, PathBuf}; 13 | use std::process::{Command, Stdio}; 14 | 15 | #[derive(Debug, PartialEq)] 16 | pub enum FileType { 17 | Directory, 18 | File, 19 | Link, 20 | } 21 | 22 | #[derive(Debug, PartialEq)] 23 | pub enum Id { 24 | Name(String), 25 | Id(u32), 26 | } 27 | 28 | #[derive(Debug, PartialEq)] 29 | pub struct FileInfo { 30 | pub filetype: FileType, 31 | pub perms: u32, 32 | pub target: Option, /* for symbolic links */ 33 | } 34 | 35 | impl FileInfo { 36 | pub fn is_user_executable(&self) -> bool { 37 | (self.perms & libc::S_IXUSR) != 0 38 | } 39 | } 40 | 41 | pub fn check>(p: P) -> Result> { 42 | let name: &str = p.as_ref().to_str().unwrap(); 43 | let cname = CString::new(name.to_string())?; 44 | let st = 45 | Box::into_raw(Box::new(unsafe { std::mem::zeroed::() })); 46 | let (r, e, st) = unsafe { 47 | let r = libc::lstat(cname.as_ptr(), st); 48 | let e = *libc::___errno(); 49 | (r, e, Box::from_raw(st)) 50 | }; 51 | if r != 0 { 52 | if e == libc::ENOENT { 53 | return Ok(None); 54 | } 55 | 56 | bail!("lstat({}): errno {}", name, e); 57 | } 58 | 59 | let fmt = st.st_mode & libc::S_IFMT; 60 | 61 | let filetype = if fmt == libc::S_IFDIR { 62 | FileType::Directory 63 | } else if fmt == libc::S_IFREG { 64 | FileType::File 65 | } else if fmt == libc::S_IFLNK { 66 | FileType::Link 67 | } else { 68 | bail!("lstat({}): unexpected file type: {:x}", name, fmt); 69 | }; 70 | 71 | let target = if filetype == FileType::Link { 72 | Some(std::fs::read_link(p)?) 73 | } else { 74 | None 75 | }; 76 | 77 | let perms = st.st_mode & 0o7777; /* as per mknod(2) */ 78 | 79 | Ok(Some(FileInfo { filetype, perms, target })) 80 | } 81 | 82 | pub fn perms>(log: &Logger, p: P, perms: u32) -> Result { 83 | let p = p.as_ref(); 84 | let log = log.new(slog::o!("path" => p.display().to_string())); 85 | let mut did_work = false; 86 | 87 | let fi = if let Some(fi) = check(p)? { 88 | fi 89 | } else { 90 | bail!("{} does not exist", p.display()); 91 | }; 92 | 93 | /* 94 | * Check permissions on the path. Note that symbolic links do 95 | * not actually have permissions, so we skip those completely. 96 | */ 97 | if fi.filetype != FileType::Link && fi.perms != perms { 98 | did_work = true; 99 | info!(log, "perms are {:o}, should be {:o}", fi.perms, perms); 100 | 101 | let cname = CString::new(p.to_str().unwrap().to_string())?; 102 | let (r, e) = unsafe { 103 | let r = libc::chmod(cname.as_ptr(), perms); 104 | let e = *libc::___errno(); 105 | (r, e) 106 | }; 107 | if r != 0 { 108 | bail!("lchmod({}, {:o}): errno {}", p.display(), perms, e); 109 | } 110 | 111 | info!(log, "chmod ok"); 112 | } 113 | 114 | Ok(did_work) 115 | } 116 | 117 | pub fn directory>( 118 | log: &Logger, 119 | dir: P, 120 | mode: u32, 121 | ) -> Result { 122 | let dir = dir.as_ref(); 123 | let mut did_work = false; 124 | 125 | if let Some(fi) = check(dir)? { 126 | /* 127 | * The path exists already. Make sure it is a directory. 128 | */ 129 | if fi.filetype != FileType::Directory { 130 | bail!("{} is {:?}, not a directory", dir.display(), fi.filetype); 131 | } 132 | } else { 133 | /* 134 | * Create the directory, and all missing parents: 135 | */ 136 | did_work = true; 137 | info!(log, "creating directory: {}", dir.display()); 138 | DirBuilder::new().recursive(true).mode(mode).create(dir)?; 139 | 140 | /* 141 | * Check the path again, to make sure we have up-to-date information: 142 | */ 143 | check(dir)?.expect("directory should now exist"); 144 | } 145 | 146 | if perms(log, dir, mode)? { 147 | did_work = true; 148 | } 149 | 150 | Ok(did_work) 151 | } 152 | 153 | #[derive(Debug, PartialEq)] 154 | pub enum Create { 155 | IfMissing, 156 | Always, 157 | } 158 | 159 | fn open>(p: P) -> Result { 160 | let p = p.as_ref(); 161 | 162 | match File::open(p) { 163 | Ok(f) => Ok(f), 164 | Err(e) => Err(anyhow!("opening \"{}\": {}", p.display(), e)), 165 | } 166 | } 167 | 168 | fn comparestr>(src: &str, dst: P) -> Result { 169 | let dstf = open(dst)?; 170 | let mut dstr = BufReader::new(dstf); 171 | 172 | /* 173 | * Assume that if the file can be passed in as a string slice, it can also 174 | * be loaded into memory fully for comparison. 175 | */ 176 | let mut dstbuf = Vec::::new(); 177 | dstr.read_to_end(&mut dstbuf)?; 178 | 179 | Ok(dstbuf == src.as_bytes()) 180 | } 181 | 182 | fn compare, P2: AsRef>(src: P1, dst: P2) -> Result { 183 | let srcf = open(src)?; 184 | let dstf = open(dst)?; 185 | let mut srcr = BufReader::new(srcf); 186 | let mut dstr = BufReader::new(dstf); 187 | 188 | loop { 189 | let mut srcbuf = [0u8; 1]; 190 | let mut dstbuf = [0u8; 1]; 191 | let srcsz = srcr.read(&mut srcbuf)?; 192 | let dstsz = dstr.read(&mut dstbuf)?; 193 | 194 | if srcsz != dstsz { 195 | /* 196 | * Files are not the same size... 197 | */ 198 | return Ok(false); 199 | } 200 | 201 | if srcsz == 0 { 202 | /* 203 | * End-of-file reached, without a mismatched comparison. These 204 | * files are equal in contents. 205 | */ 206 | return Ok(true); 207 | } 208 | 209 | if srcbuf != dstbuf { 210 | /* 211 | * This portion of the read files are not the same. 212 | */ 213 | return Ok(false); 214 | } 215 | } 216 | } 217 | 218 | pub fn removed>(log: &Logger, dst: P) -> Result<()> { 219 | let dst = dst.as_ref(); 220 | 221 | if let Some(fi) = check(dst)? { 222 | match fi.filetype { 223 | FileType::File | FileType::Link => { 224 | info!( 225 | log, 226 | "file {} exists (as {:?}), removing", 227 | dst.display(), 228 | fi.filetype 229 | ); 230 | 231 | std::fs::remove_file(dst)?; 232 | } 233 | t => { 234 | bail!( 235 | "file {} exists as {:?}, unexpected type", 236 | dst.display(), 237 | t 238 | ); 239 | } 240 | } 241 | } else { 242 | info!( 243 | log, 244 | "file {} does not already exist, skipping removal", 245 | dst.display() 246 | ); 247 | } 248 | 249 | Ok(()) 250 | } 251 | 252 | pub fn file_str>( 253 | log: &Logger, 254 | contents: &str, 255 | dst: P, 256 | mode: u32, 257 | create: Create, 258 | ) -> Result { 259 | let dst = dst.as_ref(); 260 | let mut did_work = false; 261 | 262 | let do_copy = if let Some(fi) = check(dst)? { 263 | /* 264 | * The path exists already. 265 | */ 266 | match create { 267 | Create::IfMissing if fi.filetype == FileType::File => { 268 | info!( 269 | log, 270 | "file {} exists, skipping population", 271 | dst.display() 272 | ); 273 | false 274 | } 275 | Create::IfMissing if fi.filetype == FileType::Link => { 276 | warn!( 277 | log, 278 | "symlink {} exists, skipping population", 279 | dst.display() 280 | ); 281 | false 282 | } 283 | Create::IfMissing => { 284 | /* 285 | * Avoid clobbering an unexpected entry when we have been asked 286 | * to preserve in the face of modifications. 287 | */ 288 | bail!( 289 | "{} should be a file, but is a {:?}", 290 | dst.display(), 291 | fi.filetype 292 | ); 293 | } 294 | Create::Always if fi.filetype == FileType::File => { 295 | /* 296 | * Check the contents of the file to make sure it matches 297 | * what we expect. 298 | */ 299 | if comparestr(contents, dst)? { 300 | info!( 301 | log, 302 | "file {} exists, with correct contents", 303 | dst.display() 304 | ); 305 | false 306 | } else { 307 | warn!( 308 | log, 309 | "file {} exists, with wrong contents, unlinking", 310 | dst.display() 311 | ); 312 | std::fs::remove_file(dst)?; 313 | true 314 | } 315 | } 316 | Create::Always => { 317 | /* 318 | * We found a file type we don't expect. Try to unlink it 319 | * anyway. 320 | */ 321 | warn!( 322 | log, 323 | "file {} exists, of type {:?}, unlinking", 324 | dst.display(), 325 | fi.filetype 326 | ); 327 | std::fs::remove_file(dst)?; 328 | true 329 | } 330 | } 331 | } else { 332 | info!(log, "file {} does not exist", dst.display()); 333 | true 334 | }; 335 | 336 | if do_copy { 337 | did_work = true; 338 | info!(log, "writing {} ...", dst.display()); 339 | 340 | let mut f = std::fs::OpenOptions::new() 341 | .create_new(true) 342 | .write(true) 343 | .open(&dst)?; 344 | f.write_all(contents.as_bytes())?; 345 | f.flush()?; 346 | } 347 | 348 | if perms(log, dst, mode)? { 349 | did_work = true; 350 | } 351 | 352 | info!(log, "ok!"); 353 | Ok(did_work) 354 | } 355 | 356 | pub fn file, P2: AsRef>( 357 | log: &Logger, 358 | src: P1, 359 | dst: P2, 360 | mode: u32, 361 | create: Create, 362 | ) -> Result { 363 | let src = src.as_ref(); 364 | let dst = dst.as_ref(); 365 | let mut did_work = false; 366 | 367 | let do_copy = if let Some(fi) = check(dst)? { 368 | /* 369 | * The path exists already. 370 | */ 371 | match create { 372 | Create::IfMissing if fi.filetype == FileType::File => { 373 | info!( 374 | log, 375 | "file {} exists, skipping population", 376 | dst.display() 377 | ); 378 | false 379 | } 380 | Create::IfMissing if fi.filetype == FileType::Link => { 381 | warn!( 382 | log, 383 | "symlink {} exists, skipping population", 384 | dst.display() 385 | ); 386 | false 387 | } 388 | Create::IfMissing => { 389 | /* 390 | * Avoid clobbering an unexpected entry when we have been asked 391 | * to preserve in the face of modifications. 392 | */ 393 | bail!( 394 | "{} should be a file, but is a {:?}", 395 | dst.display(), 396 | fi.filetype 397 | ); 398 | } 399 | Create::Always if fi.filetype == FileType::File => { 400 | /* 401 | * Check the contents of the file to make sure it matches 402 | * what we expect. 403 | */ 404 | if compare(src, dst)? { 405 | info!( 406 | log, 407 | "file {} exists, with correct contents", 408 | dst.display() 409 | ); 410 | false 411 | } else { 412 | warn!( 413 | log, 414 | "file {} exists, with wrong contents, unlinking", 415 | dst.display() 416 | ); 417 | std::fs::remove_file(dst)?; 418 | true 419 | } 420 | } 421 | Create::Always => { 422 | /* 423 | * We found a file type we don't expect. Try to unlink it 424 | * anyway. 425 | */ 426 | warn!( 427 | log, 428 | "file {} exists, of type {:?}, unlinking", 429 | dst.display(), 430 | fi.filetype 431 | ); 432 | std::fs::remove_file(dst)?; 433 | true 434 | } 435 | } 436 | } else { 437 | info!(log, "file {} does not exist", dst.display()); 438 | true 439 | }; 440 | 441 | if do_copy { 442 | did_work = true; 443 | info!(log, "copying {} -> {} ...", src.display(), dst.display()); 444 | std::fs::copy(src, dst)?; 445 | } 446 | 447 | if perms(log, dst, mode)? { 448 | did_work = true; 449 | } 450 | 451 | info!(log, "ok!"); 452 | Ok(did_work) 453 | } 454 | 455 | pub fn symlink, P2: AsRef>( 456 | log: &Logger, 457 | dst: P1, 458 | target: P2, 459 | ) -> Result { 460 | let dst = dst.as_ref(); 461 | let target = target.as_ref(); 462 | let mut did_work = false; 463 | 464 | let do_link = if let Some(fi) = check(dst)? { 465 | if fi.filetype == FileType::Link { 466 | let fitarget = fi.target.unwrap(); 467 | if fitarget == target { 468 | info!(log, "link target ok ({})", target.display()); 469 | false 470 | } else { 471 | warn!( 472 | log, 473 | "link target wrong: want {}, got {}; unlinking", 474 | target.display(), 475 | fitarget.display() 476 | ); 477 | std::fs::remove_file(dst)?; 478 | true 479 | } 480 | } else { 481 | /* 482 | * File type not correct. Unlink. 483 | */ 484 | warn!( 485 | log, 486 | "file {} exists, of type {:?}, unlinking", 487 | dst.display(), 488 | fi.filetype 489 | ); 490 | std::fs::remove_file(dst)?; 491 | true 492 | } 493 | } else { 494 | info!(log, "link {} does not exist", dst.display()); 495 | true 496 | }; 497 | 498 | if do_link { 499 | did_work = true; 500 | info!(log, "linking {} -> {} ...", dst.display(), target.display()); 501 | std::os::unix::fs::symlink(target, dst)?; 502 | } 503 | 504 | if perms(log, dst, 0)? { 505 | did_work = true; 506 | } 507 | 508 | info!(log, "ok!"); 509 | Ok(did_work) 510 | } 511 | 512 | fn spawn_reader( 513 | log: &Logger, 514 | name: &str, 515 | stream: Option, 516 | ) -> Option> 517 | where 518 | T: Read + Send + 'static, 519 | { 520 | let name = name.to_string(); 521 | let stream = match stream { 522 | Some(stream) => stream, 523 | None => return None, 524 | }; 525 | 526 | let log = log.clone(); 527 | 528 | Some(std::thread::spawn(move || { 529 | let mut r = BufReader::new(stream); 530 | 531 | loop { 532 | let mut buf: Vec = Vec::new(); 533 | 534 | /* 535 | * We have no particular control over the output from the child 536 | * processes we run, so we read until a newline character without 537 | * relying on totally valid UTF-8 output. 538 | */ 539 | match r.read_until(b'\n', &mut buf) { 540 | Ok(0) => { 541 | /* 542 | * EOF. 543 | */ 544 | return; 545 | } 546 | Ok(_) => { 547 | let s = String::from_utf8_lossy(&buf); 548 | let s = s.trim(); 549 | 550 | if !s.is_empty() { 551 | info!(log, "{}| {}", name, s); 552 | } 553 | } 554 | Err(e) => { 555 | error!(log, "failed to read {}: {}", name, e); 556 | std::process::exit(100); 557 | } 558 | } 559 | } 560 | })) 561 | } 562 | 563 | pub fn run2(log: &Logger, cmd: &mut Command) -> Result<()> { 564 | let mut logargs = vec![cmd.get_program().to_owned()]; 565 | for arg in cmd.get_args() { 566 | logargs.push(arg.to_owned()); 567 | } 568 | info!(log, "exec: {:?}", &logargs; "pwd" => ?cmd.get_current_dir()); 569 | 570 | cmd.stdin(Stdio::null()); 571 | cmd.stdout(Stdio::piped()); 572 | cmd.stderr(Stdio::piped()); 573 | 574 | let mut child = cmd.spawn()?; 575 | 576 | let readout = spawn_reader(log, "O", child.stdout.take()); 577 | let readerr = spawn_reader(log, "E", child.stderr.take()); 578 | 579 | if let Some(t) = readout { 580 | t.join().expect("join stdout thread"); 581 | } 582 | if let Some(t) = readerr { 583 | t.join().expect("join stderr thread"); 584 | } 585 | 586 | match child.wait() { 587 | Err(e) => Err(e.into()), 588 | Ok(es) => { 589 | if !es.success() { 590 | Err(anyhow!("exec {:?}: failed {:?}", &logargs, &es)) 591 | } else { 592 | Ok(()) 593 | } 594 | } 595 | } 596 | } 597 | 598 | fn run_common(log: &Logger, cmd: &mut Command, args: &[&OsStr]) -> Result<()> { 599 | info!(log, "exec: {:?}", &args; "pwd" => ?cmd.get_current_dir()); 600 | 601 | cmd.stdin(Stdio::null()); 602 | cmd.stdout(Stdio::piped()); 603 | cmd.stderr(Stdio::piped()); 604 | 605 | let mut child = cmd.spawn()?; 606 | 607 | let readout = spawn_reader(log, "O", child.stdout.take()); 608 | let readerr = spawn_reader(log, "E", child.stderr.take()); 609 | 610 | if let Some(t) = readout { 611 | t.join().expect("join stdout thread"); 612 | } 613 | if let Some(t) = readerr { 614 | t.join().expect("join stderr thread"); 615 | } 616 | 617 | match child.wait() { 618 | Err(e) => Err(e.into()), 619 | Ok(es) => { 620 | if !es.success() { 621 | Err(anyhow!("exec {:?}: failed {:?}", &args, &es)) 622 | } else { 623 | Ok(()) 624 | } 625 | } 626 | } 627 | } 628 | 629 | pub fn scrub_env(cmd: &mut Command, utf8: bool) { 630 | if utf8 { 631 | cmd.env("LANG", "en_US.UTF-8"); 632 | } else { 633 | cmd.env_remove("LANG"); 634 | } 635 | cmd.env_remove("LC_CTYPE"); 636 | cmd.env_remove("LC_NUMERIC"); 637 | cmd.env_remove("LC_TIME"); 638 | cmd.env_remove("LC_COLLATE"); 639 | cmd.env_remove("LC_MONETARY"); 640 | cmd.env_remove("LC_MESSAGES"); 641 | cmd.env_remove("LC_ALL"); 642 | } 643 | 644 | pub fn run_in, P: AsRef>( 645 | log: &Logger, 646 | pwd: P, 647 | args: &[S], 648 | ) -> Result<()> { 649 | let args: Vec<&OsStr> = args.iter().map(|s| s.as_ref()).collect(); 650 | 651 | let mut cmd = Command::new(&args[0]); 652 | cmd.current_dir(pwd.as_ref()); 653 | 654 | scrub_env(&mut cmd, false); 655 | 656 | if args.len() > 1 { 657 | cmd.args(&args[1..]); 658 | } 659 | 660 | run_common(log, &mut cmd, args.as_slice()) 661 | } 662 | 663 | pub fn run>(log: &Logger, args: &[S]) -> Result<()> { 664 | let args: Vec<&OsStr> = args.iter().map(|s| s.as_ref()).collect(); 665 | 666 | let mut cmd = Command::new(&args[0]); 667 | 668 | scrub_env(&mut cmd, false); 669 | 670 | if args.len() > 1 { 671 | cmd.args(&args[1..]); 672 | } 673 | 674 | run_common(log, &mut cmd, args.as_slice()) 675 | } 676 | 677 | pub fn run_utf8>(log: &Logger, args: &[S]) -> Result<()> { 678 | let args: Vec<&OsStr> = args.iter().map(|s| s.as_ref()).collect(); 679 | 680 | let mut cmd = Command::new(&args[0]); 681 | 682 | scrub_env(&mut cmd, true); 683 | 684 | if args.len() > 1 { 685 | cmd.args(&args[1..]); 686 | } 687 | 688 | run_common(log, &mut cmd, args.as_slice()) 689 | } 690 | 691 | pub fn run_env(log: &Logger, args: &[S], env: I) -> Result<()> 692 | where 693 | S: AsRef, 694 | I: IntoIterator, 695 | K: AsRef, 696 | V: AsRef, 697 | { 698 | let args: Vec<&OsStr> = args.iter().map(|s| s.as_ref()).collect(); 699 | 700 | let mut cmd = Command::new(&args[0]); 701 | 702 | cmd.env_clear(); 703 | cmd.envs(env); 704 | 705 | if args.len() > 1 { 706 | cmd.args(&args[1..]); 707 | } 708 | 709 | run_common(log, &mut cmd, args.as_slice()) 710 | } 711 | -------------------------------------------------------------------------------- /tools/helios-build/src/illumos.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Oxide Computer Company 3 | */ 4 | 5 | use super::common::{sleep, OutputExt}; 6 | use anyhow::{bail, Result}; 7 | use slog::Logger; 8 | use std::collections::HashMap; 9 | use std::ffi::{CStr, CString}; 10 | use std::io::Write; 11 | use std::os::raw::{c_char, c_int}; 12 | use std::path::{Path, PathBuf}; 13 | use std::process::{exit, Command}; 14 | 15 | const PFEXEC: &str = "/bin/pfexec"; 16 | const ZONEADM: &str = "/usr/sbin/zoneadm"; 17 | const ZONECFG: &str = "/usr/sbin/zonecfg"; 18 | const ZLOGIN: &str = "/usr/sbin/zlogin"; 19 | const SVCS: &str = "/bin/svcs"; 20 | 21 | #[derive(Debug, PartialEq)] 22 | pub struct UserAttr { 23 | pub name: String, 24 | pub attr: HashMap, 25 | } 26 | 27 | impl UserAttr { 28 | pub fn profiles(&self) -> Vec { 29 | if let Some(p) = self.attr.get("profiles") { 30 | p.split(',').map(|s| s.trim().to_string()).collect::>() 31 | } else { 32 | Vec::new() 33 | } 34 | } 35 | } 36 | 37 | #[repr(C)] 38 | struct Kv { 39 | key: *const c_char, 40 | value: *const c_char, 41 | } 42 | 43 | impl Kv { 44 | fn name(&self) -> &CStr { 45 | unsafe { CStr::from_ptr(self.key) } 46 | } 47 | 48 | fn value(&self) -> &CStr { 49 | unsafe { CStr::from_ptr(self.value) } 50 | } 51 | } 52 | 53 | #[repr(C)] 54 | struct Kva { 55 | length: c_int, 56 | data: *const Kv, 57 | } 58 | 59 | impl Kva { 60 | fn values(&self) -> &[Kv] { 61 | unsafe { std::slice::from_raw_parts(self.data, self.length as usize) } 62 | } 63 | } 64 | 65 | #[repr(C)] 66 | struct UserAttrRaw { 67 | name: *mut c_char, 68 | qualifier: *mut c_char, 69 | res1: *mut c_char, 70 | res2: *mut c_char, 71 | attr: *mut Kva, 72 | } 73 | 74 | #[link(name = "secdb")] 75 | extern "C" { 76 | fn getusernam(buf: *const c_char) -> *mut UserAttrRaw; 77 | fn free_userattr(userattr: *mut UserAttrRaw); 78 | } 79 | 80 | pub fn get_user_attr_by_name(name: &str) -> Result> { 81 | let mut out = UserAttr { name: name.to_string(), attr: HashMap::new() }; 82 | 83 | let name = CString::new(name.to_owned())?; 84 | let ua = unsafe { getusernam(name.as_ptr()) }; 85 | if ua.is_null() { 86 | return Ok(None); 87 | } 88 | 89 | for kv in unsafe { (*(*ua).attr).values() } { 90 | if let (Ok(k), Ok(v)) = (kv.name().to_str(), kv.value().to_str()) { 91 | out.attr.insert(k.to_string(), v.to_string()); 92 | } else { 93 | continue; 94 | } 95 | } 96 | 97 | unsafe { free_userattr(ua) }; 98 | 99 | Ok(Some(out)) 100 | } 101 | 102 | pub fn nodename() -> String { 103 | unsafe { 104 | let mut un: libc::utsname = std::mem::zeroed(); 105 | if libc::uname(&mut un) < 0 { 106 | eprintln!("uname failure"); 107 | exit(100); 108 | } 109 | std::ffi::CStr::from_ptr(un.nodename.as_mut_ptr()) 110 | } 111 | .to_str() 112 | .unwrap() 113 | .to_string() 114 | } 115 | 116 | #[link(name = "c")] 117 | extern "C" { 118 | fn getzoneid() -> i32; 119 | fn getzonenamebyid(id: i32, buf: *mut u8, buflen: usize) -> isize; 120 | } 121 | 122 | pub fn zoneid() -> i32 { 123 | unsafe { getzoneid() } 124 | } 125 | 126 | pub fn zonename() -> String { 127 | let buf = unsafe { 128 | let mut buf: [u8; 64] = std::mem::zeroed(); /* ZONENAME_MAX */ 129 | 130 | let sz = getzonenamebyid(getzoneid(), buf.as_mut_ptr(), 64); 131 | if sz > 64 || sz < 0 { 132 | eprintln!("getzonenamebyid failure"); 133 | exit(100); 134 | } 135 | 136 | Vec::from(&buf[0..sz as usize]) 137 | }; 138 | std::ffi::CStr::from_bytes_with_nul(&buf) 139 | .unwrap() 140 | .to_str() 141 | .unwrap() 142 | .to_string() 143 | } 144 | 145 | fn errno() -> i32 { 146 | unsafe { 147 | let enp = libc::___errno(); 148 | *enp 149 | } 150 | } 151 | 152 | fn clear_errno() { 153 | unsafe { 154 | let enp = libc::___errno(); 155 | *enp = 0; 156 | } 157 | } 158 | 159 | #[derive(Debug, Clone, PartialEq)] 160 | pub struct Passwd { 161 | pub name: Option, 162 | pub passwd: Option, 163 | pub uid: u32, 164 | pub gid: u32, 165 | pub age: Option, 166 | pub comment: Option, 167 | pub gecos: Option, 168 | pub dir: Option, 169 | pub shell: Option, 170 | } 171 | 172 | impl Passwd { 173 | fn from(p: *const libc::passwd) -> Result { 174 | fn cs(lpsz: *const c_char) -> Result> { 175 | if lpsz.is_null() { 176 | Ok(None) 177 | } else { 178 | let cstr = unsafe { CStr::from_ptr(lpsz) }; 179 | Ok(Some(cstr.to_str()?.to_string())) 180 | } 181 | } 182 | 183 | Ok(Passwd { 184 | name: cs(unsafe { (*p).pw_name })?, 185 | passwd: cs(unsafe { (*p).pw_passwd })?, 186 | uid: unsafe { (*p).pw_uid }, 187 | gid: unsafe { (*p).pw_gid }, 188 | age: cs(unsafe { (*p).pw_age })?, 189 | comment: cs(unsafe { (*p).pw_comment })?, 190 | gecos: cs(unsafe { (*p).pw_gecos })?, 191 | dir: cs(unsafe { (*p).pw_dir })?, 192 | shell: cs(unsafe { (*p).pw_shell })?, 193 | }) 194 | } 195 | } 196 | 197 | #[derive(Debug, Clone, PartialEq)] 198 | pub struct Group { 199 | pub name: Option, 200 | pub passwd: Option, 201 | pub gid: u32, 202 | pub members: Option>, 203 | } 204 | 205 | impl Group { 206 | fn from(g: *mut libc::group) -> Result { 207 | fn cs(lpsz: *const c_char) -> Result> { 208 | if lpsz.is_null() { 209 | Ok(None) 210 | } else { 211 | let cstr = unsafe { CStr::from_ptr(lpsz) }; 212 | Ok(Some(cstr.to_str()?.to_string())) 213 | } 214 | } 215 | 216 | let mut mems = unsafe { (*g).gr_mem }; 217 | let members: Option> = if !mems.is_null() { 218 | let mut members = Vec::new(); 219 | loop { 220 | if unsafe { *mems }.is_null() { 221 | break; 222 | } 223 | 224 | members.push(cs(unsafe { *mems })?.unwrap()); 225 | 226 | mems = unsafe { mems.offset(1) }; 227 | } 228 | Some(members) 229 | } else { 230 | None 231 | }; 232 | 233 | Ok(Group { 234 | name: cs(unsafe { (*g).gr_name })?, 235 | passwd: cs(unsafe { (*g).gr_passwd })?, 236 | gid: unsafe { (*g).gr_gid }, 237 | members, 238 | }) 239 | } 240 | } 241 | 242 | pub fn get_username() -> Result> { 243 | let uid = unsafe { libc::getuid() }; 244 | if let Some(pw) = get_passwd_by_id(uid)? { 245 | Ok(pw.name) 246 | } else { 247 | Ok(None) 248 | } 249 | } 250 | 251 | pub fn get_passwd_by_id(uid: u32) -> Result> { 252 | clear_errno(); 253 | let p = unsafe { libc::getpwuid(uid) }; 254 | let e = errno(); 255 | if p.is_null() { 256 | if e == 0 { 257 | Ok(None) 258 | } else { 259 | bail!("getpwuid: errno {}", e); 260 | } 261 | } else { 262 | Ok(Some(Passwd::from(p)?)) 263 | } 264 | } 265 | 266 | pub fn get_passwd_by_name(name: &str) -> Result> { 267 | clear_errno(); 268 | let name = CString::new(name.to_owned())?; 269 | let p = unsafe { libc::getpwnam(name.as_ptr()) }; 270 | let e = errno(); 271 | if p.is_null() { 272 | if e == 0 { 273 | Ok(None) 274 | } else { 275 | bail!("getpwnam: errno {}", e); 276 | } 277 | } else { 278 | Ok(Some(Passwd::from(p)?)) 279 | } 280 | } 281 | 282 | pub fn get_group_by_name(name: &str) -> Result> { 283 | clear_errno(); 284 | let name = CString::new(name.to_owned())?; 285 | let g = unsafe { libc::getgrnam(name.as_ptr()) }; 286 | let e = errno(); 287 | if g.is_null() { 288 | if e == 0 { 289 | Ok(None) 290 | } else { 291 | bail!("getgrnam: errno {}", e); 292 | } 293 | } else { 294 | Ok(Some(Group::from(g)?)) 295 | } 296 | } 297 | 298 | pub fn get_group_by_id(gid: u32) -> Result> { 299 | clear_errno(); 300 | let g = unsafe { libc::getgrgid(gid) }; 301 | let e = errno(); 302 | if g.is_null() { 303 | if e == 0 { 304 | Ok(None) 305 | } else { 306 | bail!("getgrgid: errno {}", e); 307 | } 308 | } else { 309 | Ok(Some(Group::from(g)?)) 310 | } 311 | } 312 | 313 | pub struct Terms { 314 | terms: Vec, 315 | buf: Option, 316 | } 317 | 318 | impl Terms { 319 | fn append(&mut self, c: char) { 320 | if self.buf.is_none() { 321 | self.buf = Some(String::new()); 322 | } 323 | self.buf.as_mut().unwrap().push(c); 324 | } 325 | 326 | fn commit(&mut self) { 327 | if let Some(val) = &self.buf { 328 | self.terms.push(val.to_string()); 329 | } 330 | self.buf = Some(String::new()); 331 | } 332 | 333 | fn result(&self) -> Vec { 334 | self.terms.to_owned() 335 | } 336 | 337 | fn new() -> Terms { 338 | Terms { terms: Vec::new(), buf: Some(String::new()) } 339 | } 340 | } 341 | 342 | pub fn parse_net_adm(stdout: Vec) -> Result>> { 343 | let stdout = String::from_utf8(stdout)?; 344 | let mut out = Vec::new(); 345 | 346 | for l in stdout.lines() { 347 | let mut terms = Terms::new(); 348 | let mut escape = false; 349 | 350 | for c in l.chars() { 351 | if escape { 352 | terms.append(c); 353 | escape = false; 354 | } else if c == '\\' { 355 | escape = true; 356 | } else if c == ':' { 357 | terms.commit(); 358 | } else { 359 | terms.append(c); 360 | } 361 | } 362 | terms.commit(); 363 | 364 | out.push(terms.result()); 365 | } 366 | 367 | Ok(out) 368 | } 369 | 370 | #[derive(Debug, Clone)] 371 | pub struct Zone { 372 | pub id: Option, 373 | pub name: String, 374 | pub state: String, 375 | pub path: PathBuf, 376 | pub uuid: Option, 377 | pub brand: String, 378 | pub ip_type: String, 379 | } 380 | 381 | pub trait ZonesExt { 382 | fn by_name(&self, n: &str) -> Result; 383 | fn exists(&self, n: &str) -> bool; 384 | } 385 | 386 | impl ZonesExt for Vec { 387 | fn exists(&self, n: &str) -> bool { 388 | for z in self { 389 | if n == z.name { 390 | return true; 391 | } 392 | } 393 | 394 | false 395 | } 396 | 397 | fn by_name(&self, n: &str) -> Result { 398 | for z in self { 399 | if n == z.name { 400 | return Ok(z.clone()); 401 | } 402 | } 403 | 404 | bail!("could not find zone by name \"{}\"", n); 405 | } 406 | } 407 | 408 | pub fn zone_list() -> Result> { 409 | let out = Command::new("/usr/sbin/zoneadm") 410 | .env_clear() 411 | .arg("list") 412 | .arg("-cip") 413 | .output()?; 414 | 415 | if !out.status.success() { 416 | bail!("zoneadm list failure: {}", out.info()); 417 | } 418 | 419 | let mut zones = Vec::new(); 420 | 421 | for line in parse_net_adm(out.stdout)?.iter() { 422 | if line.len() < 7 { 423 | bail!("invalid zoneadm list line: {:?}", line); 424 | } 425 | 426 | let id = if line[0] == "-" { None } else { Some(line[0].parse()?) }; 427 | 428 | let uuid = if line[4] == "" { None } else { Some(line[4].to_string()) }; 429 | 430 | zones.push(Zone { 431 | id, 432 | name: line[1].to_string(), 433 | state: line[2].to_string(), 434 | path: PathBuf::from(&line[3]), 435 | uuid, 436 | brand: line[5].to_string(), 437 | ip_type: line[6].to_string(), 438 | }); 439 | } 440 | 441 | Ok(zones) 442 | } 443 | 444 | pub fn zone_create(name: S1, path: P, brand: S2) -> Result<()> 445 | where 446 | P: AsRef, 447 | S1: AsRef, 448 | S2: AsRef, 449 | { 450 | let n = name.as_ref(); 451 | let p = path.as_ref(); 452 | let b = brand.as_ref(); 453 | 454 | let mut script = String::new(); 455 | script += "create -b; "; 456 | script += &format!("set zonepath={}; ", p.to_str().unwrap()); 457 | script += &format!("set zonename={}; ", n); 458 | script += &format!("set brand={}; ", b); 459 | script += "commit; "; 460 | 461 | println!("args: {}", script); 462 | 463 | let out = Command::new(PFEXEC) 464 | .env_clear() 465 | .arg(ZONECFG) 466 | .arg("-z") 467 | .arg(n) 468 | .arg(script) 469 | .output()?; 470 | 471 | if !out.status.success() { 472 | bail!("zonecfg create failure: {}", out.info()); 473 | } 474 | 475 | Ok(()) 476 | } 477 | 478 | pub fn zone_add_lofs(name: S1, gz: P1, ngz: P2) -> Result<()> 479 | where 480 | P1: AsRef, 481 | P2: AsRef, 482 | S1: AsRef, 483 | { 484 | let n = name.as_ref(); 485 | let gz = gz.as_ref(); 486 | let ngz = ngz.as_ref(); 487 | 488 | let mut script = String::new(); 489 | script += "add fs; "; 490 | script += &format!("set dir = {}; ", ngz.to_str().unwrap()); 491 | script += &format!("set special = {}; ", gz.to_str().unwrap()); 492 | script += &format!("set type = lofs; "); 493 | script += &format!("set options = [rw,nodevices]; "); 494 | script += "end; "; 495 | script += "commit; "; 496 | 497 | println!("args: {}", script); 498 | 499 | let out = Command::new(PFEXEC) 500 | .env_clear() 501 | .arg(ZONECFG) 502 | .arg("-z") 503 | .arg(n) 504 | .arg(script) 505 | .output()?; 506 | 507 | if !out.status.success() { 508 | bail!("zonecfg failure: {}", out.info()); 509 | } 510 | 511 | Ok(()) 512 | } 513 | 514 | pub fn zone_install(name: S1, packages: &[&str]) -> Result<()> 515 | where 516 | S1: AsRef, 517 | { 518 | let n = name.as_ref(); 519 | 520 | let mut cmd = Command::new(PFEXEC); 521 | cmd.env_clear(); 522 | cmd.arg(ZONEADM); 523 | cmd.arg("-z"); 524 | cmd.arg(n); 525 | cmd.arg("install"); 526 | 527 | if !packages.is_empty() { 528 | cmd.arg("-e"); 529 | for p in packages.iter() { 530 | cmd.arg(p); 531 | } 532 | } 533 | 534 | let mut child = cmd.spawn()?; 535 | 536 | let status = child.wait()?; 537 | 538 | if !status.success() { 539 | bail!("zoneadm install failure"); 540 | } 541 | 542 | Ok(()) 543 | } 544 | 545 | pub fn zone_clone(name: S1, src: S2) -> Result<()> 546 | where 547 | S1: AsRef, 548 | S2: AsRef, 549 | { 550 | let n = name.as_ref(); 551 | let src = src.as_ref(); 552 | 553 | let mut cmd = Command::new(PFEXEC); 554 | cmd.env_clear(); 555 | cmd.arg(ZONEADM); 556 | cmd.arg("-z"); 557 | cmd.arg(n); 558 | cmd.arg("clone"); 559 | cmd.arg(src); 560 | 561 | let mut child = cmd.spawn()?; 562 | 563 | let status = child.wait()?; 564 | 565 | if !status.success() { 566 | bail!("zoneadm clone failure"); 567 | } 568 | 569 | Ok(()) 570 | } 571 | 572 | pub fn zone_halt(name: S1) -> Result<()> 573 | where 574 | S1: AsRef, 575 | { 576 | let n = name.as_ref(); 577 | 578 | let out = Command::new(PFEXEC) 579 | .env_clear() 580 | .arg(ZONEADM) 581 | .arg("-z") 582 | .arg(n) 583 | .arg("halt") 584 | .output()?; 585 | 586 | if !out.status.success() { 587 | bail!("zoneadm halt {} failure: {}", n, out.info()); 588 | } 589 | 590 | Ok(()) 591 | } 592 | 593 | pub fn zone_boot(name: S1) -> Result<()> 594 | where 595 | S1: AsRef, 596 | { 597 | let n = name.as_ref(); 598 | 599 | let out = Command::new(PFEXEC) 600 | .env_clear() 601 | .arg(ZONEADM) 602 | .arg("-z") 603 | .arg(n) 604 | .arg("boot") 605 | .output()?; 606 | 607 | if !out.status.success() { 608 | bail!("zoneadm boot {} failure: {}", n, out.info()); 609 | } 610 | 611 | Ok(()) 612 | } 613 | 614 | pub fn zone_milestone_wait( 615 | _log: &Logger, 616 | name: S1, 617 | fmri: S2, 618 | ) -> Result<()> 619 | where 620 | S1: AsRef, 621 | S2: AsRef, 622 | { 623 | let name = name.as_ref(); 624 | let fmri = fmri.as_ref(); 625 | 626 | loop { 627 | let out = Command::new(PFEXEC) 628 | .env_clear() 629 | .arg(SVCS) 630 | .arg("-z") 631 | .arg(name) 632 | .arg("-Ho") 633 | .arg("sta,nsta") 634 | .arg(fmri) 635 | .output(); 636 | 637 | if let Ok(out) = out { 638 | let stdout = String::from_utf8(out.stdout)?; 639 | let lines: Vec<_> = stdout.lines().collect(); 640 | if lines.len() == 1 { 641 | let t: Vec<&str> = lines[0].split_whitespace().collect(); 642 | 643 | if t[0] == "ON" && t[1] == "-" { 644 | break; 645 | } 646 | 647 | println!("... {} -> {:?} ...", fmri, t); 648 | } else if lines.len() > 1 { 649 | bail!("unexpected output for {}: {:?}", fmri, lines); 650 | } 651 | } 652 | 653 | sleep(1); 654 | } 655 | 656 | Ok(()) 657 | } 658 | 659 | pub fn zone_mount(name: S1) -> Result<()> 660 | where 661 | S1: AsRef, 662 | { 663 | let n = name.as_ref(); 664 | 665 | let out = Command::new(PFEXEC) 666 | .env_clear() 667 | .arg(ZONEADM) 668 | .arg("-z") 669 | .arg(n) 670 | .arg("mount") 671 | .output()?; 672 | 673 | if !out.status.success() { 674 | bail!("zoneadm mount {} failure: {}", n, out.info()); 675 | } 676 | 677 | Ok(()) 678 | } 679 | 680 | pub fn zone_unmount(name: S1) -> Result<()> 681 | where 682 | S1: AsRef, 683 | { 684 | let n = name.as_ref(); 685 | 686 | let out = Command::new(PFEXEC) 687 | .env_clear() 688 | .arg(ZONEADM) 689 | .arg("-z") 690 | .arg(n) 691 | .arg("unmount") 692 | .output()?; 693 | 694 | if !out.status.success() { 695 | bail!("zoneadm unmount {} failure: {}", n, out.info()); 696 | } 697 | 698 | Ok(()) 699 | } 700 | 701 | pub fn zone_uninstall(name: S1) -> Result<()> 702 | where 703 | S1: AsRef, 704 | { 705 | let n = name.as_ref(); 706 | 707 | let out = Command::new(PFEXEC) 708 | .env_clear() 709 | .arg(ZONEADM) 710 | .arg("-z") 711 | .arg(n) 712 | .arg("uninstall") 713 | .arg("-F") 714 | .output()?; 715 | 716 | if !out.status.success() { 717 | bail!("zoneadm uninstall {} failure: {}", n, out.info()); 718 | } 719 | 720 | Ok(()) 721 | } 722 | 723 | pub fn zone_delete(name: S1) -> Result<()> 724 | where 725 | S1: AsRef, 726 | { 727 | let n = name.as_ref(); 728 | 729 | let out = Command::new(PFEXEC) 730 | .env_clear() 731 | .arg(ZONECFG) 732 | .arg("-z") 733 | .arg(n) 734 | .arg("delete") 735 | .arg("-F") 736 | .output()?; 737 | 738 | if !out.status.success() { 739 | bail!("zonecfg delete {} failure: {}", n, out.info()); 740 | } 741 | 742 | Ok(()) 743 | } 744 | 745 | pub fn zone_deposit_script(name: S1, contents: S2) -> Result 746 | where 747 | S1: AsRef, 748 | S2: AsRef, 749 | { 750 | let n = name.as_ref(); 751 | let c = contents.as_ref(); 752 | let sp = format!("/tmp/helios.build.{}.sh", std::process::id()); 753 | 754 | let mut child = Command::new(PFEXEC) 755 | .env_clear() 756 | .arg(ZLOGIN) 757 | .arg("-S") 758 | .arg(n) 759 | .arg("tee") 760 | .arg(&sp) 761 | .stdin(std::process::Stdio::piped()) 762 | .spawn()?; 763 | 764 | { 765 | let mut stdin = child.stdin.take().unwrap(); 766 | stdin.write_all(c.as_bytes())?; 767 | stdin.flush()?; 768 | } 769 | 770 | let status = child.wait()?; 771 | 772 | if !status.success() { 773 | bail!("zlogin {} tee {} failure", n, sp); 774 | } 775 | 776 | let out = Command::new(PFEXEC) 777 | .env_clear() 778 | .arg(ZLOGIN) 779 | .arg("-S") 780 | .arg(n) 781 | .arg("/bin/chmod") 782 | .arg("0755") 783 | .arg(&sp) 784 | .output()?; 785 | 786 | if !out.status.success() { 787 | bail!("zlogin {} chmod {} failure: {}", n, sp, out.info()); 788 | } 789 | 790 | Ok(sp) 791 | } 792 | 793 | pub fn zoneinstall_append(name: S1, path: P, line: S2) -> Result<()> 794 | where 795 | P: AsRef, 796 | S1: AsRef, 797 | S2: AsRef, 798 | { 799 | let n = name.as_ref(); 800 | let p = path.as_ref(); 801 | let l = line.as_ref(); 802 | 803 | let mut child = Command::new(PFEXEC) 804 | .env_clear() 805 | .arg(ZLOGIN) 806 | .arg("-S") 807 | .arg(n) 808 | .arg("tee") 809 | .arg("-a") 810 | .arg(&p) 811 | .stdin(std::process::Stdio::piped()) 812 | .spawn()?; 813 | 814 | { 815 | let mut stdin = child.stdin.take().unwrap(); 816 | stdin.write_all(l.as_bytes())?; 817 | stdin.flush()?; 818 | } 819 | 820 | let status = child.wait()?; 821 | 822 | if !status.success() { 823 | bail!("zlogin {} tee {} failure", n, p.display()); 824 | } 825 | 826 | Ok(()) 827 | } 828 | 829 | pub fn zoneinstall_mkdir( 830 | name: S1, 831 | path: P, 832 | uid: u32, 833 | gid: u32, 834 | ) -> Result<()> 835 | where 836 | P: AsRef, 837 | S1: AsRef, 838 | { 839 | let n = name.as_ref(); 840 | let p = path.as_ref(); 841 | 842 | let out = Command::new(PFEXEC) 843 | .env_clear() 844 | .arg(ZLOGIN) 845 | .arg("-S") 846 | .arg(n) 847 | .arg("mkdir") 848 | .arg("-p") 849 | .arg(&p) 850 | .output()?; 851 | 852 | if !out.status.success() { 853 | bail!("zlogin {} mkdir {} failure: {}", n, p.display(), out.info()); 854 | } 855 | 856 | let out = Command::new(PFEXEC) 857 | .env_clear() 858 | .arg(ZLOGIN) 859 | .arg("-S") 860 | .arg(n) 861 | .arg("chown") 862 | .arg(&format!("{}:{}", uid, gid)) 863 | .arg(&p) 864 | .output()?; 865 | 866 | if !out.status.success() { 867 | bail!("zlogin {} chown {} failure: {}", n, p.display(), out.info()); 868 | } 869 | 870 | Ok(()) 871 | } 872 | --------------------------------------------------------------------------------