summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-06 09:25:20 +0300
committerPaul Buetow <paul@buetow.org>2026-06-06 09:25:20 +0300
commit00ea2b26510c225977609620800c1989c4fefd8a (patch)
tree8c18a0a196aea49c801a40e6516e0a4bc1d6f0e2
parent178ca1f256b1e345cad2f506b6b244fb50d8d281 (diff)
test(priority): add end-to-end coverage for getpriority/setpriority
getpriority/setpriority (FamilyProcess, KindNull enter, UNCLASSIFIED ret) were untested end-to-end: no ioworkload scenario invoked them. Add a safe, unprivileged, non-disruptive priority-basic scenario that reads the current nice value via getpriority(PRIO_PROCESS, 0) and re-applies that exact value via setpriority(PRIO_PROCESS, 0, currentNice) — a byte-for-byte no-op, mirroring the existing schedRoundtripAffinity round-trip. Note getpriority returns 20-nice, so the value is converted back before re-applying. Register the scenario in scenarios.go and add TestPriorityBasic, which asserts enter_getpriority and enter_setpriority appear as null_event enters attributed to the ioworkload process. Enter-tracepoint presence is the right check given KindNull/UNCLASSIFIED (no fd/path/bytes to assert). Coverage only — classification verified correct in audits 6u and pz. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-rw-r--r--cmd/ioworkload/scenario_priority.go57
-rw-r--r--cmd/ioworkload/scenarios.go1
-rw-r--r--integrationtests/priority_test.go33
3 files changed, 91 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_priority.go b/cmd/ioworkload/scenario_priority.go
new file mode 100644
index 0000000..1383376
--- /dev/null
+++ b/cmd/ioworkload/scenario_priority.go
@@ -0,0 +1,57 @@
+package main
+
+import (
+ "fmt"
+
+ "golang.org/x/sys/unix"
+)
+
+// prioProcess is PRIO_PROCESS (value 0): the "which" selector telling
+// get/setpriority that "who" identifies a process (rather than a process group
+// or user). Paired with who == 0 it means "the calling process", so the calls
+// are entirely self-targeted and need no privilege.
+const prioProcess = 0
+
+// niceOffset is the bias the kernel applies to getpriority's return value. The
+// raw getpriority(2) syscall never returns a negative number (that range is
+// reserved for -errno), so instead of returning the nice value directly
+// (-20..19) it returns 20 - nice (i.e. 1..40). To recover the actual nice value
+// we must subtract the return value from this offset. setpriority(2), by
+// contrast, takes the real nice value (-20..19) directly, so we convert before
+// re-applying it.
+const niceOffset = 20
+
+// priorityBasic exercises the SAFE, NON-DISRUPTIVE members of the priority pair
+// (getpriority/setpriority), entirely self-targeted (PRIO_PROCESS, who 0 == the
+// calling process), so it changes no other process and leaves this process's
+// nice value byte-for-byte unchanged:
+//
+// - getpriority(PRIO_PROCESS, 0) reads the current nice value (a pure read).
+// - setpriority(PRIO_PROCESS, 0, currentNice) re-applies the EXACT nice value
+// just read back, so the priority is left unchanged.
+//
+// Re-applying the current nice value needs no privilege: lowering the priority
+// (a larger nice) is always allowed, and writing back the unchanged value is a
+// no-op the kernel permits regardless of RLIMIT_NICE. This mirrors the safe
+// sched_setaffinity round-trip in schedRoundtripAffinity (scenario_sched.go),
+// where we likewise read a value and write the identical value straight back.
+//
+// Both syscalls classify as FamilyProcess with a KindNull enter (PRIO_PROCESS is
+// an opcode, not an fd) and an UNCLASSIFIED return (getpriority returns a nice
+// value, NOT a byte count), so the scenario exists purely to fire the enter
+// tracepoints end-to-end.
+func priorityBasic() error {
+ // getpriority returns 20 - nice (see niceOffset); recover the real nice.
+ prio, err := unix.Getpriority(prioProcess, 0)
+ if err != nil {
+ return fmt.Errorf("getpriority(PRIO_PROCESS, 0): %w", err)
+ }
+ currentNice := niceOffset - prio
+
+ // Re-apply the IDENTICAL nice value we just read: a no-op change that needs
+ // no privilege.
+ if err := unix.Setpriority(prioProcess, 0, currentNice); err != nil {
+ return fmt.Errorf("setpriority(PRIO_PROCESS, 0, %d) (restore same nice): %w", currentNice, err)
+ }
+ return nil
+}
diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go
index 131e5f0..7e03b3d 100644
--- a/cmd/ioworkload/scenarios.go
+++ b/cmd/ioworkload/scenarios.go
@@ -148,6 +148,7 @@ var scenarios = map[string]func() error{
"signals-basic": signalsBasic,
"misc-basic": miscBasic,
"sched-basic": schedBasic,
+ "priority-basic": priorityBasic,
}
func makeTempDir(prefix string) (string, func(), error) {
diff --git a/integrationtests/priority_test.go b/integrationtests/priority_test.go
new file mode 100644
index 0000000..9e41185
--- /dev/null
+++ b/integrationtests/priority_test.go
@@ -0,0 +1,33 @@
+package integrationtests
+
+import "testing"
+
+// priorityTraceArgs restricts tracing to the two priority-family syscalls the
+// priority-basic workload issues. The tracer names each tracepoint after the
+// underlying kernel syscall, so the names below match the syscall names
+// verbatim.
+var priorityTraceArgs = []string{"-trace-syscalls", "getpriority,setpriority"}
+
+// TestPriorityBasic verifies the getpriority/setpriority pair is traced
+// end-to-end. The priority-basic workload self-targets both calls
+// (PRIO_PROCESS, who 0 == the calling process): it reads the current nice value
+// with getpriority and re-applies the IDENTICAL value with setpriority (a
+// byte-for-byte no-op), so the process's priority is never altered and no
+// privilege is required. Both syscalls classify as FamilyProcess with a KindNull
+// enter (PRIO_PROCESS is an opcode, not an fd) and an UNCLASSIFIED return (the
+// value is a nice value, not a byte count), so asserting the enter tracepoints
+// appear — attributed to the ioworkload process — is the right end-to-end check.
+func TestPriorityBasic(t *testing.T) {
+ h := newTestHarness(t)
+ result, pid, err := h.RunWithIorArgs("priority-basic", defaultDuration, priorityTraceArgs)
+ if err != nil {
+ t.Fatalf("run scenario priority-basic: %v", err)
+ }
+
+ AssertNoUnexpectedPID(t, result, pid)
+ AssertNoUnexpectedComm(t, result, "ioworkload")
+ AssertEventsPresent(t, result, []ExpectedEvent{
+ {Tracepoint: "enter_getpriority", Comm: "ioworkload", MinCount: 1},
+ {Tracepoint: "enter_setpriority", Comm: "ioworkload", MinCount: 1},
+ })
+}