1# 2# SPDX-License-Identifier: BSD-2-Clause 3# 4# Copyright (c) 2026 Devin Teske <dteske@FreeBSD.org> 5# 6 7# The system calls a program makes vary with the machine and with the 8# run-time linker, so these tests assert which system calls -t may and 9# may not report rather than the exact sequence of them. 10 11require_truss() 12{ 13 truss -o /dev/null /usr/bin/true >/dev/null 2>&1 || 14 atf_skip "unable to trace a child process here" 15} 16 17# Write the sorted, unique names of the system calls reported in a truss 18# output file to another file. 19syscall_names() 20{ 21 sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_.]*\)(.*$/\1/p' "$1" | sort -u > "$2" 22} 23 24# Fail unless every name in a file matches an extended regular expression. 25only_names() 26{ 27 if grep -Ev "$1" "$2" > unexpected; then 28 atf_fail "reported system calls not selected by the filter:" \ 29 "$(tr '\n' ' ' < unexpected)" 30 fi 31} 32 33# Fail if any name in a file matches an extended regular expression. 34no_name() 35{ 36 if grep -E "$1" "$2" > unexpected; then 37 atf_fail "system calls excluded by the filter were reported:" \ 38 "$(tr '\n' ' ' < unexpected)" 39 fi 40} 41 42# Fail unless at least one name in a file matches. 43some_name() 44{ 45 grep -Eq "$1" "$2" || 46 atf_fail "no system call matching $1 was reported" 47} 48 49atf_test_case list 50list_head() 51{ 52 atf_set descr "-t with no expression prints the available groups" 53} 54list_body() 55{ 56 atf_check -s exit:2 -o match:'@all' -o match:'@none' \ 57 -o match:'@read' -o match:'@write' -o match:'@file' \ 58 -o match:'@net' \ 59 truss -t 60} 61 62atf_test_case unknown_group 63unknown_group_head() 64{ 65 atf_set descr "an unknown @group is rejected" 66} 67unknown_group_body() 68{ 69 atf_check -s exit:1 -e match:'unknown system call group @nosuch' \ 70 truss -t @nosuch /usr/bin/true 71} 72 73atf_test_case empty_term 74empty_term_head() 75{ 76 atf_set descr "an empty term adds nothing to the expression" 77} 78empty_term_body() 79{ 80 require_truss 81 printf 'hello\n' > input 82 83 # An empty expression filters nothing, as if -t were absent. 84 atf_check -s exit:0 -o inline:"hello\n" truss -o out -t '' cat input 85 syscall_names out names 86 some_name '^read$' names 87 some_name '^openat$' names 88 89 # A stray comma is ignored rather than being an error. 90 atf_check -s exit:0 -o inline:"hello\n" \ 91 truss -o out2 -t ',read,,write,' cat input 92 syscall_names out2 names2 93 only_names '^(read|write)$' names2 94 some_name '^read$' names2 95 96 # A negation with nothing to negate is still a mistake. 97 atf_check -s exit:1 -e match:"missing pattern after" \ 98 truss -t '!' /usr/bin/true 99} 100 101atf_test_case none 102none_head() 103{ 104 atf_set descr "@none selects no system call" 105} 106none_body() 107{ 108 require_truss 109 printf 'hello\n' > input 110 111 # @none's member list is the single negated member "!*", so this 112 # also covers a group whose members exclude rather than include. 113 atf_check -s exit:0 -o inline:"hello\n" truss -o out -t @none cat input 114 syscall_names out names 115 atf_check -o empty cat names 116 117 atf_check -s exit:0 -o inline:"hello\n" \ 118 truss -o out2 -t '!@none' cat input 119 syscall_names out2 names2 120 some_name '^read$' names2 121 some_name '^openat$' names2 122 123 # @none is the empty set rather than a switch: it selects nothing 124 # and leaves the terms before it alone. 125 atf_check -s exit:0 -o inline:"hello\n" \ 126 truss -o out3 -t '@file,@none' cat input 127 syscall_names out3 names3 128 some_name '^openat$' names3 129} 130 131atf_test_case by_number 132by_number_head() 133{ 134 atf_set descr "a term may name a system call by number" 135} 136by_number_body() 137{ 138 require_truss 139 printf 'hello\n' > input 140 141 # 3 and 4 have been read(2) and write(2) since 4.2BSD. 142 atf_check -s exit:0 -o inline:"hello\n" -e empty \ 143 truss -o out -t 3 cat input 144 syscall_names out names 145 only_names '^read$' names 146 some_name '^read$' names 147 148 atf_check -s exit:0 -o inline:"hello\n" -e empty \ 149 truss -o out2 -t 3,4 cat input 150 syscall_names out2 names2 151 only_names '^(read|write)$' names2 152 153 # Numeric terms negate like any other. 154 atf_check -s exit:0 -o inline:"hello\n" -e empty \ 155 truss -o out3 -t '!3' cat input 156 syscall_names out3 names3 157 no_name '^read$' names3 158 some_name '.' names3 159 160 # Leading zeroes are still just a number. 161 atf_check -s exit:0 -o inline:"hello\n" -e empty \ 162 truss -o out4 -t 003 cat input 163 syscall_names out4 names4 164 only_names '^read$' names4 165} 166 167atf_test_case bad_number 168bad_number_head() 169{ 170 atf_set descr "only an unrepresentable system call number is rejected" 171} 172bad_number_body() 173{ 174 require_truss 175 printf 'hello\n' > input 176 177 # A process may issue any number the kernel can hold, so a number 178 # beyond the tables truss knows is not second-guessed. It simply 179 # does not match anything this program happens to call. 180 atf_check -s exit:0 -o inline:"hello\n" -e empty \ 181 truss -o out -t 99999 cat input 182 syscall_names out names 183 atf_check -o empty cat names 184 185 # Too large to be a system call number at all: an error. 186 atf_check -s exit:1 -e match:'system call number is too large' \ 187 truss -t 4294967296 /usr/bin/true 188} 189 190atf_test_case unknown_syscall 191unknown_syscall_head() 192{ 193 atf_set descr "a term matching no system call warns but still runs" 194} 195unknown_syscall_body() 196{ 197 require_truss 198 printf 'hello\n' > input 199 200 # A typo is a warning, not an error: the command still runs. 201 atf_check -s exit:0 -o inline:"hello\n" \ 202 -e match:'opne: matches no known system call' \ 203 truss -o out -t opne cat input 204 syscall_names out names 205 atf_check -o empty cat names 206 207 # So is a pattern that can never match. 208 atf_check -s exit:0 -o inline:"hello\n" \ 209 -e match:'matches no known system call' \ 210 truss -o out2 -t 'raed*' cat input 211 212 # Names of every ABI truss knows are accepted without complaint, 213 # whether or not that ABI is the one being traced here. 214 for name in read openat linux_write linux_newstat compat11.stat; do 215 atf_check -s exit:0 -o inline:"hello\n" -e empty \ 216 truss -o out3 -t "$name" cat input 217 done 218 219 # A number is the way to name a system call by number; '#' is not 220 # a prefix truss accepts, so it is diagnosed like any other typo. 221 atf_check -s exit:0 -o inline:"hello\n" \ 222 -e match:'matches no known system call' \ 223 truss -o out4 -t '#237' cat input 224} 225 226atf_test_case by_name 227by_name_head() 228{ 229 atf_set descr "a term naming one system call selects only that one" 230} 231by_name_body() 232{ 233 require_truss 234 printf 'hello\n' > input 235 236 atf_check -s exit:0 -o inline:"hello\n" truss -o out -t read cat input 237 syscall_names out names 238 only_names '^read$' names 239 some_name '^read$' names 240} 241 242atf_test_case by_pattern 243by_pattern_head() 244{ 245 atf_set descr "a term may be an fnmatch(3) pattern" 246} 247by_pattern_body() 248{ 249 require_truss 250 atf_check ln -s target link 251 252 atf_check -s exit:0 -o ignore truss -o out -t 'readlink*' readlink link 253 syscall_names out names 254 only_names '^readlink' names 255 some_name '^readlink' names 256} 257 258atf_test_case group 259group_head() 260{ 261 atf_set descr "an @group selects the system calls it names" 262} 263group_body() 264{ 265 require_truss 266 printf 'hello\n' > input 267 268 atf_check -s exit:0 -o inline:"hello\n" truss -o out -t @read cat input 269 syscall_names out names 270 some_name '^read$' names 271 no_name '^(openat|close|mmap|munmap|mprotect)$' names 272} 273 274atf_test_case group_read_excludes_readlink 275group_read_excludes_readlink_head() 276{ 277 atf_set descr "@read selects I/O reads but not readlink(2)" 278} 279group_read_excludes_readlink_body() 280{ 281 require_truss 282 atf_check ln -s target link 283 284 # readlink(1) calls readlink(2), which @read must not select even 285 # though "read*" does. 286 atf_check -s exit:0 -o ignore truss -o out -t @read readlink link 287 syscall_names out names 288 no_name '^readlink' names 289 some_name '^read$' names 290 291 atf_check -s exit:0 -o ignore truss -o out2 -t 'read*' readlink link 292 syscall_names out2 names2 293 some_name '^readlink' names2 294} 295 296atf_test_case group_reference 297group_reference_head() 298{ 299 atf_set descr "@desc includes the members of @read and @write" 300} 301group_reference_body() 302{ 303 require_truss 304 printf 'hello\n' > input 305 306 atf_check -s exit:0 -o inline:"hello\n" truss -o out -t @desc cat input 307 syscall_names out names 308 some_name '^read$' names 309 some_name '^close$' names 310} 311 312atf_test_case negation 313negation_head() 314{ 315 atf_set descr "a term prefixed with ! excludes what it matches" 316} 317negation_body() 318{ 319 require_truss 320 321 atf_check -s exit:0 -o ignore truss -o out -t '!@all' /usr/bin/true 322 syscall_names out names 323 atf_check -o empty cat names 324 325 atf_check -s exit:0 -o ignore truss -o out2 -t '!@memory' /usr/bin/true 326 syscall_names out2 names2 327 no_name '^(mmap|munmap|mprotect)$' names2 328 some_name '.' names2 329} 330 331atf_test_case order 332order_head() 333{ 334 atf_set descr "the last term to match a system call wins" 335} 336order_body() 337{ 338 require_truss 339 printf 'hello\n' > input 340 341 atf_check -s exit:0 -o ignore truss -o out -t 'read,!read' cat input 342 syscall_names out names 343 atf_check -o empty cat names 344 345 atf_check -s exit:0 -o ignore truss -o out2 -t '!read,read' cat input 346 syscall_names out2 names2 347 only_names '^read$' names2 348 some_name '^read$' names2 349} 350 351atf_test_case accumulate 352accumulate_head() 353{ 354 atf_set descr "repeating -t appends to the expression" 355} 356accumulate_body() 357{ 358 require_truss 359 atf_check ln -s target link 360 361 atf_check -s exit:0 -o ignore \ 362 truss -o out -t read -t readlink readlink link 363 syscall_names out names 364 only_names '^(read|readlink)$' names 365 some_name '^read$' names 366 some_name '^readlink$' names 367} 368 369atf_test_case count 370count_head() 371{ 372 atf_set descr "-c counts only the selected system calls" 373} 374count_body() 375{ 376 require_truss 377 printf 'hello\n' > input 378 379 atf_check -s exit:0 -o inline:"hello\n" \ 380 truss -c -o out -t read cat input 381 atf_check -o match:'^read ' cat out 382 atf_check -s exit:1 -o empty grep -q '^openat' out 383} 384 385atf_init_test_cases() 386{ 387 atf_add_test_case list 388 atf_add_test_case unknown_group 389 atf_add_test_case empty_term 390 atf_add_test_case none 391 atf_add_test_case by_number 392 atf_add_test_case bad_number 393 atf_add_test_case unknown_syscall 394 atf_add_test_case by_name 395 atf_add_test_case by_pattern 396 atf_add_test_case group 397 atf_add_test_case group_read_excludes_readlink 398 atf_add_test_case group_reference 399 atf_add_test_case negation 400 atf_add_test_case order 401 atf_add_test_case accumulate 402 atf_add_test_case count 403} 404