Files
ghostty-rs/crates/ghostty-os/src/cgroup.rs
T
ddidderr 7868e56273 refactor: resolve cross-crate integration TODOs and resources discovery
Cleanup pass over items collected in docs/rewrite/INTEGRATION_TODO.md:
- OSC 133 printf-q / percent decoders live once in
  ghostty_vt::string_encoding; ghostty-os re-exports them.
- CursorStyle and clipboard codepoint map entries have one definition
  (ghostty-terminal); ghostty-config now depends on ghostty-terminal.
- ActionKey::docs() trailing blank lines fixed at the source; cli's
  workaround removed (+list-actions --docs still byte-identical).
- x11_color exposes names in original case; cli no longer re-parses
  rgb.txt.
- One cgroup scope wait loop (ghostty_os::cgroup::wait_for_scope).
- Flatpak passwd lookup implemented in ghostty-os via a local pty.
- +version shows the rustc version captured at build time.

Resources: dev builds find <workspace>/resources automatically (keeping
Ghostty's sentinel-file detection and GHOSTTY_RESOURCES_DIR);
scripts/gen-terminfo.sh (just terminfo) regenerates the compiled
terminfo; `just install [PREFIX]` installs binary, share/ghostty,
terminfo, desktop/D-Bus/systemd/metainfo files and icons.

Test Plan:
- cargo test for vt, os, config, input, cli
- diff `+list-keybinds`, `+list-actions --docs`, `+show-config --default`
  against /usr/bin/ghostty
2026-10-12 02:08:22 +02:00

65 lines
2.2 KiB
Rust

use std::io::Read;
/// Maximum number of bytes read from the cgroup file (the Zig version reads
/// into a caller-provided buffer; callers there use path-sized buffers).
const READ_LIMIT: u64 = 4096;
/// Returns the path to the cgroup for the given pid.
pub fn current(pid: u32) -> Option<String> {
// Read our cgroup by opening /proc/<pid>/cgroup and reading the first
// line. The first line will look something like this:
// 0::/user.slice/user-1000.slice/session-1.scope
// The cgroup path is the third field.
let file = std::fs::File::open(format!("/proc/{pid}/cgroup")).ok()?;
let mut contents = Vec::new();
file.take(READ_LIMIT).read_to_end(&mut contents).ok()?;
// Find the last ':'
let idx = contents.iter().rposition(|&b| b == b':')?;
let rest = &contents[idx + 1..];
let end = rest
.iter()
.rposition(|b| !matches!(b, b' ' | b'\r' | b'\n'))
.map_or(0, |i| i + 1);
Some(String::from_utf8_lossy(&rest[..end]).into_owned())
}
/// True if the final path component of `pid`'s cgroup equals `expected`.
pub fn scope_matches(pid: u32, expected: &str) -> bool {
current(pid)
.as_deref()
.and_then(|raw| raw.rsplit_once('/'))
.is_some_and(|(_, last)| last == expected)
}
/// Poll (every 25ms, at most 250ms) until `pid`'s cgroup ends in `expected`.
/// Returns true if seen, false on timeout. This is the single wait loop
/// shared by the termio `Command` hooks and apprt-gtk's pre-exec/post-fork.
pub fn wait_for_scope(pid: u32, expected: &str) -> bool {
let start = std::time::Instant::now();
loop {
if start.elapsed() > std::time::Duration::from_millis(250) {
return false;
}
if scope_matches(pid, expected) {
return true;
}
std::thread::sleep(std::time::Duration::from_millis(25));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn current_cgroup_of_self() {
// Not part of the Zig tests; sanity check that parsing works when
// /proc is available.
if let Some(p) = current(std::process::id()) {
assert!(!p.ends_with('\n'));
}
assert!(current(u32::MAX).is_none());
}
}