caac091f257a | atagen | 2026-10-04 11:41 UTC | 1 | #![cfg(target_os = "linux")] |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 2 | |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 3 | use std::{ |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 4 | fs, |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 5 | io::Write, |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 6 | os::fd::{AsRawFd, FromRawFd, OwnedFd}, |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 7 | os::unix::process::CommandExt, |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 8 | process::{Child, Command, Stdio}, |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 9 | time::Duration, |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 10 | }; |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 11 | |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 12 | fn gated_command(program: &str, arguments: &[&str]) -> (Child, OwnedFd) { |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 13 | let mut descriptors = [-1; 2]; |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 14 | assert_eq!( |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 15 | unsafe { libc::pipe2(descriptors.as_mut_ptr(), libc::O_CLOEXEC) }, |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 16 | 0 |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 17 | ); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 18 | let read = unsafe { OwnedFd::from_raw_fd(descriptors[0]) }; |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 19 | let write = unsafe { OwnedFd::from_raw_fd(descriptors[1]) }; |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 20 | let descriptor = read.as_raw_fd(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 21 | let mut command = Command::new(env!("CARGO_BIN_EXE_rabt")); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 22 | command |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 23 | .args(["__roundabout_exec", &descriptor.to_string(), program]) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 24 | .args(arguments) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 25 | .stdout(Stdio::piped()) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 26 | .stderr(Stdio::piped()); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 27 | unsafe { |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 28 | command.pre_exec(move || { |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 29 | if libc::fcntl(descriptor, libc::F_SETFD, 0) == -1 { |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 30 | return Err(std::io::Error::last_os_error()); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 31 | } |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 32 | Ok(()) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 33 | }); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 34 | } |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 35 | let child = command.spawn().unwrap(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 36 | drop(read); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 37 | (child, write) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 38 | } |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 39 | |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 40 | #[test] |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 41 | fn command_waits_for_scope_setup() { |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 42 | let (mut child, gate) = gated_command(env!("CARGO_BIN_EXE_rabt"), &["--version"]); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 43 | let mut output = libc::pollfd { |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 44 | fd: child.stdout.as_ref().unwrap().as_raw_fd(), |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 45 | events: libc::POLLIN, |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 46 | revents: 0, |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 47 | }; |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 48 | assert_eq!(unsafe { libc::poll(&raw mut output, 1, 100) }, 0); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 49 | assert!(child.try_wait().unwrap().is_none()); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 50 | fs::File::from(gate).write_all(b"1").unwrap(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 51 | let output = child.wait_with_output().unwrap(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 52 | assert!(output.status.success()); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 53 | assert!( |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 54 | String::from_utf8(output.stdout) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 55 | .unwrap() |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 56 | .starts_with("rabt ") |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 57 | ); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 58 | } |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 59 | |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 60 | #[test] |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 61 | fn failed_scope_setup_never_executes_the_command() { |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 62 | let (child, gate) = gated_command(env!("CARGO_BIN_EXE_rabt"), &["--version"]); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 63 | drop(gate); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 64 | let output = child.wait_with_output().unwrap(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 65 | assert_eq!(output.status.code(), Some(1)); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 66 | assert!(output.stdout.is_empty()); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 67 | } |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 68 | |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 69 | #[test] |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 70 | fn missing_commands_keep_exit_127() { |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 71 | let (child, gate) = gated_command("/roundabout-missing-command", &[]); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 72 | fs::File::from(gate).write_all(b"1").unwrap(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 73 | assert_eq!(child.wait_with_output().unwrap().status.code(), Some(127)); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 74 | } |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 75 | |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 76 | #[test] |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 77 | #[ignore = "requires a systemd user manager and cgroup v2"] |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 78 | fn transient_scope_enforces_the_grant() { |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 79 | if std::env::var_os("ROUNDABOUT_SCOPE_PROBE").is_some() { |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 80 | let membership = fs::read_to_string("/proc/self/cgroup").unwrap(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 81 | let path = membership |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 82 | .lines() |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 83 | .find_map(|line| line.strip_prefix("0::")) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 84 | .unwrap(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 85 | assert!(path.contains("roundabout-")); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 86 | let cgroup = std::path::Path::new("/sys/fs/cgroup").join(path.trim_start_matches('/')); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 87 | let cpu = fs::read_to_string(cgroup.join("cpu.max")).unwrap(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 88 | let limits: Vec<_> = cpu.split_whitespace().collect(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 89 | assert_eq!(limits[0], limits[1]); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 90 | assert_eq!( |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 91 | fs::read_to_string(cgroup.join("memory.max")) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 92 | .unwrap() |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 93 | .trim(), |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 94 | (80 * 1024 * 1024).to_string() |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 95 | ); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 96 | assert_eq!( |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 97 | fs::read_to_string(cgroup.join("memory.high")) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 98 | .unwrap() |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 99 | .trim(), |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 100 | (64 * 1024 * 1024).to_string() |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 101 | ); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 102 | assert_eq!( |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 103 | fs::read_to_string(cgroup.join("memory.oom.group")) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 104 | .unwrap() |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 105 | .trim(), |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 106 | "1" |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 107 | ); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 108 | assert_eq!(std::env::var("CARGO_BUILD_JOBS").unwrap(), "1"); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 109 | assert!(std::env::var("ROUNDABOUT_LEASE").is_ok()); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 110 | return; |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 111 | } |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 112 | let directory = |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 113 | std::env::temp_dir().join(format!("roundabout-scope-test-{}", std::process::id())); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 114 | fs::create_dir_all(&directory).unwrap(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 115 | let config = directory.join("config.toml"); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 116 | fs::write( |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 117 | &config, |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 118 | "[overrun]\nram = \"throttle\"\n[queue]\nlinger = \"0s\"\n", |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 119 | ) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 120 | .unwrap(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 121 | let output = Command::new(env!("CARGO_BIN_EXE_rabt")) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 122 | .args([ |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 123 | "run", "--ram", "64M", "--disk", "0", "--cores", "1", "--time", "5s", "--exact", "--", |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 124 | ]) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 125 | .arg(std::env::current_exe().unwrap()) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 126 | .args([ |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 127 | "--exact", |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 128 | "transient_scope_enforces_the_grant", |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 129 | "--ignored", |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 130 | "--nocapture", |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 131 | ]) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 132 | .current_dir(&directory) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 133 | .env("ROUNDABOUT_CONFIG", &config) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 134 | .env("ROUNDABOUT_RUNTIME_DIR", directory.join("runtime")) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 135 | .env("ROUNDABOUT_STATE_DIR", directory.join("state")) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 136 | .env("ROUNDABOUT_SCOPE_PROBE", "1") |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 137 | .output() |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 138 | .unwrap(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 139 | std::thread::sleep(Duration::from_secs(2)); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 140 | fs::remove_dir_all(directory).unwrap(); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 141 | assert!( |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 142 | output.status.success(), |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 143 | "{}\n{}", |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 144 | String::from_utf8_lossy(&output.stdout), |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 145 | String::from_utf8_lossy(&output.stderr) |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 146 | ); |
caac091f257a | atagen | 2026-10-04 11:41 UTC | 147 | } |