1# 2# Copyright (c) 2026 Devin Teske <dteske@FreeBSD.org> 3# 4# SPDX-License-Identifier: BSD-2-Clause 5# 6 7# 8# Resolve the sysconf binary: honor SYSCONF if set, else the sibling 9# build product (objdir layout: .../sysconf/tests next to .../sysconf/sysconf), 10# else PATH. Require a regular file: when installed under 11# /usr/tests/usr.sbin/sysconf, `../sysconf' is this directory itself and 12# `[ -x dir ]' is true for searchable directories. 13# 14find_sysconf() 15{ 16 if [ -n "$SYSCONF" ] && [ -f "$SYSCONF" ] && [ -x "$SYSCONF" ]; then 17 return 18 fi 19 if [ -f "$( atf_get_srcdir )/../sysconf" ] && 20 [ -x "$( atf_get_srcdir )/../sysconf" ] 21 then 22 SYSCONF="$( atf_get_srcdir )/../sysconf" 23 elif [ -f "$( pwd )/../sysconf" ] && [ -x "$( pwd )/../sysconf" ]; then 24 SYSCONF="$( pwd )/../sysconf" 25 else 26 SYSCONF=$( command -v sysconf ) || 27 atf_skip "sysconf binary not found" 28 fi 29} 30 31setup_root() 32{ 33 find_sysconf 34 ROOT="$( pwd )/root" 35 mkdir -p "$ROOT/etc" "$ROOT/boot/defaults" 36} 37 38cleanup_root() 39{ 40 rm -rf "$( pwd )/root" 41} 42 43atf_test_case make_append_accumulate cleanup 44make_append_accumulate_head() 45{ 46 atf_set "descr" \ 47 "make += accumulates on read and appends a line on write" 48} 49make_append_accumulate_body() 50{ 51 setup_root 52 printf 'foo=1\n' > "$ROOT/etc/make.conf" 53 atf_check -o inline:'foo: 1\n' "$SYSCONF" make -R "$ROOT" foo 54 printf 'foo+=2\n' >> "$ROOT/etc/make.conf" 55 atf_check -o inline:'foo: 1 2\n' "$SYSCONF" make -R "$ROOT" foo 56 atf_check -o inline:'foo: 1 2 -> 1 2 -O2\n' \ 57 "$SYSCONF" make -R "$ROOT" 'foo+=-O2' 58 atf_check -o inline:'foo: 1 2 -O2\n' "$SYSCONF" make -R "$ROOT" foo 59 atf_check -o match:'foo\+=-O2' cat "$ROOT/etc/make.conf" 60 # -V on a new += statement reports (added), not `+= -> +=value' 61 atf_check -o match:'make.conf:[0-9]*: foo\+=-O3 \(added\)' \ 62 "$SYSCONF" make -R "$ROOT" -V 'foo+=-O3' 63 atf_check -o inline:'foo: 1 2 -O2 -O3\n' "$SYSCONF" make -R "$ROOT" foo 64 # Identical += is a no-op (append_present) 65 before=$( stat -f '%m' "$ROOT/etc/make.conf" ) 66 atf_check "$SYSCONF" make -R "$ROOT" -c 'foo+=-O2' 67 after=$( stat -f '%m' "$ROOT/etc/make.conf" ) 68 atf_check_equal "$before" "$after" 69} 70make_append_accumulate_cleanup() 71{ 72 cleanup_root 73} 74 75atf_test_case src_triad_and_trail cleanup 76src_triad_and_trail_head() 77{ 78 atf_set "descr" "src triad order, -V trail, writes prefer src.conf" 79} 80src_triad_and_trail_body() 81{ 82 setup_root 83 printf 'CFLAGS=-O0\n' > "$ROOT/etc/src-env.conf" 84 printf 'CFLAGS+=-g\n' > "$ROOT/etc/make.conf" 85 printf 'CFLAGS+=-pipe\n' > "$ROOT/etc/src.conf" 86 atf_check -o inline:'CFLAGS: -O0 -g -pipe\n' \ 87 "$SYSCONF" src -R "$ROOT" CFLAGS 88 atf_check -o match:'src-env.conf:1: CFLAGS=-O0' \ 89 -o match:'make.conf:1: CFLAGS\+=-g -> -O0 -g' \ 90 -o match:'src.conf:1: CFLAGS\+=-pipe -> -O0 -g -pipe' \ 91 "$SYSCONF" src -R "$ROOT" -V CFLAGS 92 atf_check -o match:'src.conf' \ 93 "$SYSCONF" src -R "$ROOT" -v CFLAGS 94 # New directive lands in src.conf 95 atf_check "$SYSCONF" src -R "$ROOT" -q WITHOUT_FOO= 96 atf_check -o match:'WITHOUT_FOO=' cat "$ROOT/etc/src.conf" 97 atf_check -o not-match:'WITHOUT_FOO' cat "$ROOT/etc/make.conf" 98} 99src_triad_and_trail_cleanup() 100{ 101 cleanup_root 102} 103 104atf_test_case src_env_rejected 105src_env_rejected_head() 106{ 107 atf_set "descr" "src-env keyword is rejected with a hint toward src" 108} 109src_env_rejected_body() 110{ 111 find_sysconf 112 atf_check -s not-exit:0 -e match:'src-env' -e match:'src' \ 113 "$SYSCONF" src-env foo 114} 115 116atf_test_case env_overrides cleanup 117env_overrides_head() 118{ 119 atf_set "descr" "SRC_ENV_CONF / __MAKE_CONF / SRCCONF remap the triad" 120} 121env_overrides_body() 122{ 123 setup_root 124 mkdir -p "$ROOT/alt" 125 printf 'X=from-env\n' > "$ROOT/alt/se.conf" 126 printf 'X+=from-make\n' > "$ROOT/alt/mk.conf" 127 printf 'X+=from-src\n' > "$ROOT/alt/sc.conf" 128 # Empty stock files so -R still resolves the triad slots 129 :> "$ROOT/etc/src-env.conf" 130 :> "$ROOT/etc/make.conf" 131 :> "$ROOT/etc/src.conf" 132 export SRC_ENV_CONF="$ROOT/alt/se.conf" 133 export __MAKE_CONF="$ROOT/alt/mk.conf" 134 export SRCCONF="$ROOT/alt/sc.conf" 135 atf_check -o inline:'X: from-env from-make from-src\n' \ 136 "$SYSCONF" src -R "$ROOT" X 137 unset SRC_ENV_CONF __MAKE_CONF SRCCONF 138} 139env_overrides_cleanup() 140{ 141 cleanup_root 142} 143 144atf_test_case attached_f_passthrough cleanup 145attached_f_passthrough_head() 146{ 147 atf_set "descr" \ 148 "Attached -fPATH does not steal the next argv (rc as name)" 149} 150attached_f_passthrough_body() 151{ 152 setup_root 153 mkdir -p "$ROOT/boot" 154 printf 'zfs_load="YES"\n' > "$ROOT/boot/loader.conf" 155 # 156 # If find_target consumed 'loader' as -f's argument, 'rc' would become 157 # the target and exec sysrc(8). Correctly, target is loader and 'rc' 158 # is an unknown directive name. 159 # 160 atf_check -s not-exit:0 -e match:'unknown directive' \ 161 "$SYSCONF" -R "$ROOT" -f/boot/loader.conf loader rc 162} 163attached_f_passthrough_cleanup() 164{ 165 cleanup_root 166} 167 168atf_test_case verbatim_backslash cleanup 169verbatim_backslash_head() 170{ 171 atf_set "descr" \ 172 "verbatim_ok re-examines the byte after an even \\\\ run" 173} 174verbatim_backslash_body() 175{ 176 setup_root 177 printf 'foo=1\n' > "$ROOT/etc/make.conf" 178 # Even-length \\ before # must not skip #: put must reject (Faraz) 179 atf_check -s not-exit:0 -e match:'Invalid argument' \ 180 "$SYSCONF" make -R "$ROOT" 'foo=a\\#b' 181 # A value without a comment marker still writes. 182 atf_check -o match:'foo:' \ 183 "$SYSCONF" make -R "$ROOT" 'foo=a\\b' 184 atf_check -o match:'foo=a\\\\b' cat "$ROOT/etc/make.conf" 185} 186verbatim_backslash_cleanup() 187{ 188 cleanup_root 189} 190 191atf_test_case make_strike_last_assign cleanup 192make_strike_last_assign_head() 193{ 194 atf_set "descr" \ 195 "make -= strikes the last assignment containing the word" 196} 197make_strike_last_assign_body() 198{ 199 setup_root 200 printf 'bar+=1\nbar+=1 2\nbar+=1 2 3\n' > "$ROOT/etc/make.conf" 201 atf_check -o match:'->' "$SYSCONF" make -R "$ROOT" 'bar-=3' 202 atf_check -o inline:'bar+=1\nbar+=1 2\nbar+=1 2\n' \ 203 cat "$ROOT/etc/make.conf" 204 atf_check -o match:'->' "$SYSCONF" make -R "$ROOT" 'bar-=2' 205 atf_check -o inline:'bar+=1\nbar+=1 2\nbar+=1\n' \ 206 cat "$ROOT/etc/make.conf" 207 atf_check -o match:'->' "$SYSCONF" make -R "$ROOT" 'bar-=1' 208 atf_check -o inline:'bar+=1\nbar+=1 2\n' cat "$ROOT/etc/make.conf" 209 atf_check -o match:'->' "$SYSCONF" make -R "$ROOT" 'bar-=1' 210 atf_check -o inline:'bar+=1\nbar+=2\n' cat "$ROOT/etc/make.conf" 211 atf_check -o match:'->' "$SYSCONF" make -R "$ROOT" 'bar-=2' 212 atf_check -o inline:'bar+=1\n' cat "$ROOT/etc/make.conf" 213 atf_check -o inline:'bar: 1 (unchanged)\n' \ 214 "$SYSCONF" make -R "$ROOT" 'bar-=9' 215 # Emptying the last word deletes the statement: (removed), like (added) 216 printf 'z+=only\n' >>"$ROOT/etc/make.conf" 217 atf_check -o match:'make.conf:[0-9]*: z\+=only \(removed\)' \ 218 "$SYSCONF" make -R "$ROOT" -V 'z-=only' 219 atf_check -o not-match:'^z\+=' cat "$ROOT/etc/make.conf" 220} 221make_strike_last_assign_cleanup() 222{ 223 cleanup_root 224} 225 226atf_test_case equal_value_nocheck_mtime cleanup 227equal_value_nocheck_mtime_head() 228{ 229 atf_set "descr" "Equal-value write and -c leave mtime alone" 230} 231equal_value_nocheck_mtime_body() 232{ 233 setup_root 234 printf 'bar=1\n' > "$ROOT/etc/make.conf" 235 sleep 1 236 before=$( stat -f '%m' "$ROOT/etc/make.conf" ) 237 # Equal-value write: informational echo, file untouched 238 atf_check -o inline:'bar: 1 (unchanged)\n' \ 239 "$SYSCONF" make -R "$ROOT" bar=1 240 # -e uses `=' for write echoes the same as for reads 241 atf_check -o inline:'bar=1 (unchanged)\n' \ 242 "$SYSCONF" make -R "$ROOT" -e bar=1 243 after=$( stat -f '%m' "$ROOT/etc/make.conf" ) 244 atf_check_equal "$before" "$after" 245 # -q suppresses the echo; -c is silent and does not write 246 atf_check -o empty "$SYSCONF" make -R "$ROOT" -q bar=1 247 atf_check -o empty "$SYSCONF" make -R "$ROOT" -c bar=1 248 after2=$( stat -f '%m' "$ROOT/etc/make.conf" ) 249 atf_check_equal "$before" "$after2" 250 atf_check -o inline:'bar=1 # -> 2\n' \ 251 "$SYSCONF" make -R "$ROOT" -e bar=2 252 atf_check -o inline:'bar=2\n' \ 253 "$SYSCONF" make -R "$ROOT" -e bar 254} 255equal_value_nocheck_mtime_cleanup() 256{ 257 cleanup_root 258} 259 260atf_test_case sysctl_oid_range cleanup 261sysctl_oid_range_head() 262{ 263 atf_set "descr" \ 264 "sysctl target rejects values that overflow the OID CTLTYPE" 265} 266sysctl_oid_range_body() 267{ 268 find_sysconf 269 case "$( uname -s )" in 270 FreeBSD) ;; 271 *) atf_skip "requires FreeBSD sysctl OIDFMT" ;; 272 esac 273 # -f alone (no -R) still validates against the running kernel. 274 conf="$( pwd )/sysctl.conf" 275 :> "$conf" 276 atf_check -s exit:1 -e match:'out of range for integer' \ 277 "$SYSCONF" sysctl -f "$conf" \ 278 security.bsd.see_other_uids=99999999999 279 atf_check -o empty cat "$conf" 280 atf_check -s exit:1 -e match:'out of range for unsigned integer' \ 281 "$SYSCONF" sysctl -f "$conf" kern.ipc.somaxconn=-1 282 atf_check -s exit:1 -e match:'out of range for unsigned integer' \ 283 "$SYSCONF" sysctl -f "$conf" kern.ipc.somaxconn=abc 284 atf_check -o match:'see_other_uids' \ 285 "$SYSCONF" sysctl -f "$conf" security.bsd.see_other_uids=0 286 atf_check -o match:'see_other_uids=0' cat "$conf" 287} 288sysctl_oid_range_cleanup() 289{ 290 rm -f "$( pwd )/sysctl.conf" 291} 292 293atf_test_case make_src_knobs cleanup 294make_src_knobs_head() 295{ 296 atf_set "descr" \ 297 "-s WITH_/WITHOUT_ presence, query display, unchanged, -x" 298} 299make_src_knobs_body() 300{ 301 setup_root 302 :> "$ROOT/etc/make.conf" 303 :> "$ROOT/etc/sysctl.conf" 304 :> "$ROOT/etc/src-env.conf" 305 :> "$ROOT/etc/src.conf" 306 307 # -s only on make/src 308 atf_check -s exit:1 -e match:'only valid for make and src' \ 309 "$SYSCONF" sysctl -R "$ROOT" -s WITHOUT_FOO 310 # name must be WITH_/WITHOUT_ 311 atf_check -s exit:1 -e match:'WITH_ or WITHOUT_' \ 312 "$SYSCONF" make -R "$ROOT" -s CFLAGS 313 # no value with -s 314 atf_check -s exit:1 -e match:'does not take a value' \ 315 "$SYSCONF" make -R "$ROOT" -s 'WITHOUT_FOO=' 316 317 atf_check -o inline:'WITHOUT_FOO: (not present) -> (present)\n' \ 318 "$SYSCONF" make -R "$ROOT" -s WITHOUT_FOO 319 atf_check -o match:'^WITHOUT_FOO=$' cat "$ROOT/etc/make.conf" 320 atf_check -o inline:'WITHOUT_FOO (present)\n' \ 321 "$SYSCONF" make -R "$ROOT" WITHOUT_FOO 322 atf_check -o inline:'WITHOUT_FOO=\n' \ 323 "$SYSCONF" make -R "$ROOT" -e WITHOUT_FOO 324 atf_check -o inline:'\n' \ 325 "$SYSCONF" make -R "$ROOT" -n WITHOUT_FOO 326 327 before=$( stat -f '%m' "$ROOT/etc/make.conf" ) 328 atf_check -o inline:'WITHOUT_FOO: (present) (unchanged)\n' \ 329 "$SYSCONF" make -R "$ROOT" -s WITHOUT_FOO 330 after=$( stat -f '%m' "$ROOT/etc/make.conf" ) 331 atf_check_equal "$before" "$after" 332 333 # Non-empty placeholder (=t) still means present (defined()) 334 atf_check -o inline:'WITHOUT_BAR: (not present) -> (present)\n' \ 335 "$SYSCONF" make -R "$ROOT" 'WITHOUT_BAR=t' 336 atf_check -o match:'^WITHOUT_BAR=t$' cat "$ROOT/etc/make.conf" 337 atf_check -o inline:'WITHOUT_BAR (present)\n' \ 338 "$SYSCONF" make -R "$ROOT" WITHOUT_BAR 339 atf_check -o inline:'WITHOUT_BAR=t\n' \ 340 "$SYSCONF" make -R "$ROOT" -e WITHOUT_BAR 341 atf_check -o inline:'t\n' \ 342 "$SYSCONF" make -R "$ROOT" -n WITHOUT_BAR 343 before=$( stat -f '%m' "$ROOT/etc/make.conf" ) 344 atf_check -o inline:'WITHOUT_BAR: (present) (unchanged)\n' \ 345 "$SYSCONF" make -R "$ROOT" -s WITHOUT_BAR 346 after=$( stat -f '%m' "$ROOT/etc/make.conf" ) 347 atf_check_equal "$before" "$after" 348 atf_check -o match:'^WITHOUT_BAR=t$' cat "$ROOT/etc/make.conf" 349 350 # Explicit name= always writes (incl. empty); echo value changes 351 atf_check -o inline:'WITHOUT_BAR: t -> \n' \ 352 "$SYSCONF" make -R "$ROOT" 'WITHOUT_BAR=' 353 atf_check -o match:'^WITHOUT_BAR=$' cat "$ROOT/etc/make.conf" 354 atf_check -o inline:'WITHOUT_BAR: -> 123\n' \ 355 "$SYSCONF" make -R "$ROOT" 'WITHOUT_BAR=123' 356 atf_check -o match:'^WITHOUT_BAR=123$' cat "$ROOT/etc/make.conf" 357 atf_check -o inline:'WITHOUT_BAR: 123 -> 456\n' \ 358 "$SYSCONF" make -R "$ROOT" 'WITHOUT_BAR=456' 359 atf_check -o match:'^WITHOUT_BAR=456$' cat "$ROOT/etc/make.conf" 360 # -s still no-op while present with a value 361 before=$( stat -f '%m' "$ROOT/etc/make.conf" ) 362 atf_check -o inline:'WITHOUT_BAR: (present) (unchanged)\n' \ 363 "$SYSCONF" make -R "$ROOT" -s WITHOUT_BAR 364 after=$( stat -f '%m' "$ROOT/etc/make.conf" ) 365 atf_check_equal "$before" "$after" 366 atf_check -o match:'^WITHOUT_BAR=456$' cat "$ROOT/etc/make.conf" 367 368 # src prefers src.conf 369 atf_check -o inline:'WITHOUT_LLDB: (not present) -> (present)\n' \ 370 "$SYSCONF" src -R "$ROOT" -s WITHOUT_LLDB 371 atf_check -o match:'^WITHOUT_LLDB=$' cat "$ROOT/etc/src.conf" 372 # -x is quiet unless -v/-V (same as non-knob removals) 373 atf_check -o empty \ 374 "$SYSCONF" src -R "$ROOT" -x WITHOUT_LLDB 375 atf_check -o empty cat "$ROOT/etc/src.conf" 376 atf_check -o inline:'WITHOUT_LLDB: (not present) -> (present)\n' \ 377 "$SYSCONF" src -R "$ROOT" -s WITHOUT_LLDB 378 atf_check -o match:'WITHOUT_LLDB \(removed\)' \ 379 "$SYSCONF" src -R "$ROOT" -vx WITHOUT_LLDB 380 atf_check -o empty cat "$ROOT/etc/src.conf" 381 382 # WITH_*=no (exact lowercase) warns like bsd.mkopt.mk; WITHOUT_*=no 383 # and WITH_*=NO do not 384 atf_check -e match:'Use WITHOUT_FOO=1 instead of WITH_FOO=no' \ 385 -o match:'WITH_FOO' \ 386 "$SYSCONF" make -R "$ROOT" 'WITH_FOO=no' 387 atf_check -o match:'^WITH_FOO=no$' cat "$ROOT/etc/make.conf" 388 atf_check -e empty -o match:'WITH_BAR' \ 389 "$SYSCONF" make -R "$ROOT" 'WITH_BAR=NO' 390 atf_check -e empty -o match:'WITHOUT_BAZ' \ 391 "$SYSCONF" make -R "$ROOT" 'WITHOUT_BAZ=no' 392 393 # Reads remind with file:line (presence display hides =no) 394 atf_check -e match:'make.conf:[0-9]+: Use WITHOUT_FOO=1 instead of WITH_FOO=no' \ 395 -o inline:'WITH_FOO (present)\n' \ 396 "$SYSCONF" make -R "$ROOT" WITH_FOO 397 atf_check -e match:'make.conf:[0-9]+: Use WITHOUT_FOO=1 instead of WITH_FOO=no' \ 398 -o match:'WITH_FOO \(present\)' \ 399 "$SYSCONF" make -R "$ROOT" -a 400 atf_check -e match:'make.conf:[0-9]+: Use WITHOUT_FOO=1 instead of WITH_FOO=no' \ 401 -o inline:'WITH_FOO=no\n' \ 402 "$SYSCONF" make -R "$ROOT" -e WITH_FOO 403 atf_check -e empty -o inline:'WITH_FOO (present)\n' \ 404 "$SYSCONF" make -R "$ROOT" -q WITH_FOO 405 406 # WITHOUT_MODULES is not a presence knob 407 atf_check -s exit:1 -e match:'WITHOUT_MODULES takes a module list' \ 408 "$SYSCONF" make -R "$ROOT" -s WITHOUT_MODULES 409 atf_check -o match:'WITHOUT_MODULES: -> plip' \ 410 "$SYSCONF" make -R "$ROOT" 'WITHOUT_MODULES=plip' 411 atf_check -o inline:'WITHOUT_MODULES: plip\n' \ 412 "$SYSCONF" make -R "$ROOT" WITHOUT_MODULES 413} 414make_src_knobs_cleanup() 415{ 416 cleanup_root 417} 418 419atf_test_case max_bytes_cap cleanup 420max_bytes_cap_head() 421{ 422 atf_set "descr" "BSDCONF_MAX_BYTES bounds parse input" 423} 424max_bytes_cap_body() 425{ 426 setup_root 427 printf 'foo=ok\n' > "$ROOT/etc/make.conf" 428 atf_check -o inline:'foo: ok\n' "$SYSCONF" make -R "$ROOT" foo 429 # A tight cap rejects an otherwise valid file 430 atf_check -s not-exit:0 -e match:'File too large' \ 431 env BSDCONF_MAX_BYTES=4 "$SYSCONF" make -R "$ROOT" foo 432 got=$( printf 'bar=piped\n' | "$SYSCONF" generic -f - bar ) || 433 atf_fail "pipe parse failed" 434 atf_check_equal "bar: piped" "$got" 435} 436max_bytes_cap_cleanup() 437{ 438 cleanup_root 439} 440 441atf_init_test_cases() 442{ 443 atf_add_test_case make_src_knobs 444 atf_add_test_case make_append_accumulate 445 atf_add_test_case make_strike_last_assign 446 atf_add_test_case src_triad_and_trail 447 atf_add_test_case src_env_rejected 448 atf_add_test_case env_overrides 449 atf_add_test_case attached_f_passthrough 450 atf_add_test_case verbatim_backslash 451 atf_add_test_case equal_value_nocheck_mtime 452 atf_add_test_case sysctl_oid_range 453 atf_add_test_case max_bytes_cap 454} 455