diff --git a/litebox_common_linux/src/lib.rs b/litebox_common_linux/src/lib.rs index 9108995d3..ebc977899 100644 --- a/litebox_common_linux/src/lib.rs +++ b/litebox_common_linux/src/lib.rs @@ -588,6 +588,8 @@ pub struct Winsize { pub const TCGETS: u32 = 0x5401; pub const TCSETS: u32 = 0x5402; +pub const TIOCGPGRP: u32 = 0x540F; +pub const TIOCSPGRP: u32 = 0x5410; pub const TIOCGWINSZ: u32 = 0x5413; pub const FIONBIO: u32 = 0x5421; pub const FIOCLEX: u32 = 0x5451; @@ -601,6 +603,10 @@ pub enum IoctlArg { TCGETS(Platform::RawMutPointer), /// Set the current serial port settings. TCSETS(Platform::RawConstPointer), + /// Get the process group ID of the foreground process group on the terminal. + TIOCGPGRP(Platform::RawMutPointer), + /// Set the foreground process group ID on the terminal. + TIOCSPGRP(Platform::RawConstPointer), /// Get window size. TIOCGWINSZ(Platform::RawMutPointer), /// Obtain device unit number, which can be used to generate @@ -2217,6 +2223,14 @@ pub enum SyscallRequest { }, Getpid, Getppid, + Getpgrp, + Setpgid { + pid: i32, + pgid: i32, + }, + Getpgid { + pid: i32, + }, Getuid, Geteuid, Getgid, @@ -2408,6 +2422,8 @@ impl SyscallRequest { match cmd { TCGETS => IoctlArg::TCGETS(ctx.sys_req_ptr(2)), TCSETS => IoctlArg::TCSETS(ctx.sys_req_ptr(2)), + TIOCGPGRP => IoctlArg::TIOCGPGRP(ctx.sys_req_ptr(2)), + TIOCSPGRP => IoctlArg::TIOCSPGRP(ctx.sys_req_ptr(2)), TIOCGWINSZ => IoctlArg::TIOCGWINSZ(ctx.sys_req_ptr(2)), TIOCGPTN => IoctlArg::TIOCGPTN(ctx.sys_req_ptr(2)), FIONBIO => IoctlArg::FIONBIO(ctx.sys_req_ptr(2)), @@ -2587,6 +2603,9 @@ impl SyscallRequest { Sysno::prlimit64 => sys_req!(Prlimit { pid, resource:?, new_limit:*, old_limit:* }), Sysno::getpid => SyscallRequest::Getpid, Sysno::getppid => SyscallRequest::Getppid, + Sysno::getpgrp => SyscallRequest::Getpgrp, + Sysno::setpgid => sys_req!(Setpgid { pid, pgid }), + Sysno::getpgid => sys_req!(Getpgid { pid }), Sysno::getuid => SyscallRequest::Getuid, Sysno::getgid => SyscallRequest::Getgid, Sysno::geteuid => SyscallRequest::Geteuid, diff --git a/litebox_shim_linux/src/lib.rs b/litebox_shim_linux/src/lib.rs index f4dccc729..af78e0e52 100644 --- a/litebox_shim_linux/src/lib.rs +++ b/litebox_shim_linux/src/lib.rs @@ -194,6 +194,7 @@ impl LinuxShimBuilder { boot_time: self.platform.now(), load_filter: self.load_filter, next_thread_id: 2.into(), // start from 2, as 1 is used by the main thread + fg_pgrp: core::sync::atomic::AtomicI32::new(1), litebox: self.litebox, unix_addr_table: litebox::sync::RwLock::new(syscalls::unix::UnixAddrTable::new()), }); @@ -234,6 +235,7 @@ impl LinuxShim { wait_state: wait::WaitState::new(self.0.platform), pid, ppid, + pgrp: Arc::new(core::sync::atomic::AtomicI32::new(pid)), tid: pid, credentials: syscalls::process::Credentials { uid, @@ -248,6 +250,10 @@ impl LinuxShim { signals: syscalls::signal::SignalState::new_process(), }, }; + // Default foreground process group to the initial process. + self.0 + .fg_pgrp + .store(pid, core::sync::atomic::Ordering::Relaxed); entrypoints.task.load_program( loader::elf::ElfLoader::new(&entrypoints.task, path)?, argv, @@ -1088,6 +1094,11 @@ impl Task { } SyscallRequest::Getpid => Ok(self.sys_getpid().reinterpret_as_unsigned() as usize), SyscallRequest::Getppid => Ok(self.sys_getppid().reinterpret_as_unsigned() as usize), + SyscallRequest::Getpgrp => Ok(self.sys_getpgrp().reinterpret_as_unsigned() as usize), + SyscallRequest::Setpgid { pid, pgid } => self.sys_setpgid(pid, pgid), + SyscallRequest::Getpgid { pid } => self + .sys_getpgid(pid) + .map(|v| v.reinterpret_as_unsigned() as usize), SyscallRequest::Getuid => Ok(self.sys_getuid() as usize), SyscallRequest::Getgid => Ok(self.sys_getgid() as usize), SyscallRequest::Geteuid => Ok(self.sys_geteuid() as usize), @@ -1161,6 +1172,8 @@ struct GlobalState { /// Next thread ID to assign. // TODO: better management of thread IDs next_thread_id: core::sync::atomic::AtomicI32, + /// Foreground process group ID for the terminal. + fg_pgrp: core::sync::atomic::AtomicI32, /// UNIX domain socket address table unix_addr_table: litebox::sync::RwLock, } @@ -1173,6 +1186,8 @@ struct Task { pid: i32, /// Parent Process ID ppid: i32, + /// Process group ID. Defaults to `pid`. + pgrp: Arc, /// Thread ID tid: i32, /// Task credentials. These are set per task but are Arc'd to save space @@ -1214,6 +1229,7 @@ mod test_utils { thread: syscalls::process::ThreadState::new_process(pid), pid, ppid: 0, + pgrp: Arc::new(core::sync::atomic::AtomicI32::new(pid)), tid: pid, credentials: Arc::new(syscalls::process::Credentials { uid: 0, @@ -1243,6 +1259,7 @@ mod test_utils { thread: self.thread.new_thread(tid)?, pid: self.pid, ppid: self.ppid, + pgrp: self.pgrp.clone(), tid, credentials: self.credentials.clone(), comm: self.comm.clone(), diff --git a/litebox_shim_linux/src/syscalls/file.rs b/litebox_shim_linux/src/syscalls/file.rs index 8c7283712..98a757942 100644 --- a/litebox_shim_linux/src/syscalls/file.rs +++ b/litebox_shim_linux/src/syscalls/file.rs @@ -1275,6 +1275,24 @@ impl Task { Ok(0) } IoctlArg::TCSETS(_) => Ok(0), // TODO: implement + IoctlArg::TIOCGPGRP(pgrp) => { + let fg = self + .global + .fg_pgrp + .load(core::sync::atomic::Ordering::Relaxed); + pgrp.write_at_offset(0, fg).ok_or(Errno::EFAULT)?; + Ok(0) + } + IoctlArg::TIOCSPGRP(pgrp) => { + let pgid: i32 = pgrp.read_at_offset(0).ok_or(Errno::EFAULT)?; + if pgid <= 0 { + return Err(Errno::EINVAL); + } + self.global + .fg_pgrp + .store(pgid, core::sync::atomic::Ordering::Relaxed); + Ok(0) + } IoctlArg::TIOCGWINSZ(ws) => { ws.write_at_offset( 0, @@ -1389,6 +1407,8 @@ impl Task { }, IoctlArg::TCGETS(..) | IoctlArg::TCSETS(..) + | IoctlArg::TIOCGPGRP(..) + | IoctlArg::TIOCSPGRP(..) | IoctlArg::TIOCGPTN(..) | IoctlArg::TIOCGWINSZ(..) => match desc { Descriptor::LiteBoxRawFd(raw_fd) => files.run_on_raw_fd( diff --git a/litebox_shim_linux/src/syscalls/process.rs b/litebox_shim_linux/src/syscalls/process.rs index 4f0abf1e9..e8ec0eec4 100644 --- a/litebox_shim_linux/src/syscalls/process.rs +++ b/litebox_shim_linux/src/syscalls/process.rs @@ -752,6 +752,7 @@ impl Task { pid: self.pid, tid: child_tid, ppid: self.ppid, + pgrp: self.pgrp.clone(), credentials: self.credentials.clone(), comm: self.comm.clone(), fs: fs.into(), @@ -1148,10 +1149,47 @@ impl Task { self.pid } + /// Handle syscall `getppid`. pub(crate) fn sys_getppid(&self) -> i32 { self.ppid } + /// Handle syscall `getpgrp`. + pub(crate) fn sys_getpgrp(&self) -> i32 { + self.pgrp.load(core::sync::atomic::Ordering::Relaxed) + } + + /// Handle syscall `getpgid`. + pub(crate) fn sys_getpgid(&self, pid: i32) -> Result { + if pid < 0 { + return Err(Errno::EINVAL); + } + if pid == 0 || pid == self.pid { + Ok(self.pgrp.load(core::sync::atomic::Ordering::Relaxed)) + } else { + Err(Errno::ESRCH) + } + } + + /// Handle syscall `setpgid`. + #[allow(clippy::similar_names)] + pub(crate) fn sys_setpgid(&self, pid: i32, pgid: i32) -> Result { + if pid < 0 { + return Err(Errno::EINVAL); + } + let target_pid = if pid == 0 { self.pid } else { pid }; + let target_pgid = if pgid == 0 { target_pid } else { pgid }; + if target_pgid < 0 { + return Err(Errno::EINVAL); + } + if target_pid != self.pid { + return Err(Errno::ESRCH); + } + self.pgrp + .store(target_pgid, core::sync::atomic::Ordering::Relaxed); + Ok(0) + } + /// Handle syscall `getuid`. pub(crate) fn sys_getuid(&self) -> u32 { self.credentials.uid @@ -1606,4 +1644,100 @@ mod tests { "prctl get_name returned unexpected comm for too long name" ); } + + #[test] + fn test_getpgrp() { + let task = crate::syscalls::tests::init_platform(None); + + // getpgrp should return the pid (default behavior for new processes) + let pgrp = task.sys_getpgrp(); + let pid = task.sys_getpid(); + assert_eq!( + pgrp, pid, + "getpgrp should return pid for a new process that hasn't joined another process group" + ); + + // After setpgid, getpgrp should reflect the new value + task.sys_setpgid(0, 42).expect("setpgid failed"); + assert_eq!(task.sys_getpgrp(), 42); + } + + #[test] + fn test_getpgid_self() { + let task = crate::syscalls::tests::init_platform(None); + let pid = task.sys_getpid(); + + // getpgid(0) should return own pgrp + assert_eq!(task.sys_getpgid(0).unwrap(), pid); + // getpgid(own_pid) should return own pgrp + assert_eq!(task.sys_getpgid(pid).unwrap(), pid); + } + + #[test] + fn test_getpgid_unknown() { + use crate::Errno; + let task = crate::syscalls::tests::init_platform(None); + + // getpgid for a negative pid should return EINVAL + assert_eq!(task.sys_getpgid(-1), Err(Errno::EINVAL)); + + // getpgid for an unknown pid should return ESRCH + assert_eq!(task.sys_getpgid(99999), Err(Errno::ESRCH)); + } + + #[test] + fn test_setpgid() { + let task = crate::syscalls::tests::init_platform(None); + let pid = task.sys_getpid(); + + // setpgid(0, 0) is equivalent to setpgrp: sets pgrp to own pid + task.sys_setpgid(0, 0).expect("setpgid(0, 0) failed"); + assert_eq!(task.sys_getpgrp(), pid); + assert_eq!(task.sys_getpgid(0).unwrap(), pid); + + // setpgid(0, 42) sets pgrp to 42 + task.sys_setpgid(0, 42).expect("setpgid(0, 42) failed"); + assert_eq!(task.sys_getpgrp(), 42); + assert_eq!(task.sys_getpgid(pid).unwrap(), 42); + } + + #[test] + fn test_setpgid_invalid() { + use crate::Errno; + let task = crate::syscalls::tests::init_platform(None); + + // Negative pid should return EINVAL + assert_eq!(task.sys_setpgid(-1, 1), Err(Errno::EINVAL)); + + // Negative pgid should return EINVAL + assert_eq!(task.sys_setpgid(0, -1), Err(Errno::EINVAL)); + + // Unknown pid should return ESRCH + assert_eq!(task.sys_setpgid(99999, 1), Err(Errno::ESRCH)); + } + + #[test] + fn test_pgrp_inherited() { + let task = crate::syscalls::tests::init_platform(None); + + // Set pgrp to a custom value + task.sys_setpgid(0, 42).expect("setpgid failed"); + assert_eq!(task.sys_getpgrp(), 42); + + // Clone and verify child inherits the pgrp + let child = task.clone_for_test().expect("clone_for_test failed"); + assert_eq!(child.sys_getpgrp(), 42); + } + + #[test] + fn test_pgrp_shared_between_threads() { + let task = crate::syscalls::tests::init_platform(None); + let child = task.clone_for_test().expect("clone_for_test failed"); + + task.sys_setpgid(0, 42).expect("setpgid failed"); + assert_eq!(child.sys_getpgrp(), 42); + + task.sys_setpgid(0, 7).expect("setpgid failed"); + assert_eq!(child.sys_getpgrp(), 7); + } }