From 88f04c8df91155ff57c4389e306723e8ea678e53 Mon Sep 17 00:00:00 2001 From: "Hui, Chunyang" Date: Mon, 16 Aug 2021 09:35:33 +0000 Subject: [PATCH] Add process group implementation and support set/getpgid, set/getpgrp --- src/libos/src/entry.rs | 13 +- src/libos/src/process/do_exit.rs | 2 + src/libos/src/process/do_getpid.rs | 5 - src/libos/src/process/do_spawn/mod.rs | 16 +- src/libos/src/process/do_wait4.rs | 4 + src/libos/src/process/mod.rs | 4 + src/libos/src/process/pgrp.rs | 223 +++++++++++++++++ src/libos/src/process/process/builder.rs | 21 +- src/libos/src/process/process/idle.rs | 8 + src/libos/src/process/process/mod.rs | 37 ++- src/libos/src/process/spawn_attribute.rs | 10 +- src/libos/src/process/syscalls.rs | 38 ++- src/libos/src/process/table.rs | 33 ++- src/libos/src/signal/do_kill.rs | 7 +- src/libos/src/syscall/mod.rs | 14 +- test/Makefile | 2 +- test/Occlum.json | 2 +- test/getpid/main.c | 3 +- test/pgrp/Makefile | 5 + test/pgrp/main.c | 304 +++++++++++++++++++++++ 20 files changed, 718 insertions(+), 33 deletions(-) create mode 100644 src/libos/src/process/pgrp.rs create mode 100644 test/pgrp/Makefile create mode 100644 test/pgrp/main.c diff --git a/src/libos/src/entry.rs b/src/libos/src/entry.rs index 1c1bee37..75eb6c0e 100644 --- a/src/libos/src/entry.rs +++ b/src/libos/src/entry.rs @@ -8,7 +8,7 @@ use crate::exception::*; use crate::fs::HostStdioFds; use crate::interrupt; use crate::process::idle_reap_zombie_children; -use crate::process::ProcessFilter; +use crate::process::{ProcessFilter, SpawnAttr}; use crate::signal::SigNum; use crate::time::up_time::init; use crate::util::log::LevelFilter; @@ -270,12 +270,21 @@ fn do_new_process( let file_actions = Vec::new(); let current = &process::IDLE; let program_path_str = program_path.to_str().unwrap(); + + // Called from occlum_ecall_new_process, give it an identical process group. + // So that "occlum run/exec" process will have its own process group. + let spawn_attribute = { + let mut attribute = SpawnAttr::default(); + attribute.process_group = Some(0); + attribute + }; + let new_tid = process::do_spawn_without_exec( &program_path_str, argv, &env_concat, &file_actions, - None, + Some(spawn_attribute), host_stdio_fds, current, )?; diff --git a/src/libos/src/process/do_exit.rs b/src/libos/src/process/do_exit.rs index e99431e9..2cfeb7f3 100644 --- a/src/libos/src/process/do_exit.rs +++ b/src/libos/src/process/do_exit.rs @@ -2,6 +2,7 @@ use crate::signal::constants::*; use std::intrinsics::atomic_store; use super::do_futex::futex_wake; +use super::pgrp::clean_pgrp_when_exit; use super::process::{Process, ProcessFilter}; use super::{table, ProcessRef, TermStatus, ThreadRef, ThreadStatus}; use crate::prelude::*; @@ -102,6 +103,7 @@ fn exit_process(thread: &ThreadRef, term_status: TermStatus) { let main_tid = pid; table::del_thread(main_tid).expect("tid must be in the table"); table::del_process(pid).expect("pid must be in the table"); + clean_pgrp_when_exit(process); process_inner.exit(term_status, &idle_ref, &mut idle_inner); idle_inner.remove_zombie_child(pid); diff --git a/src/libos/src/process/do_getpid.rs b/src/libos/src/process/do_getpid.rs index df613e78..ade2bbc7 100644 --- a/src/libos/src/process/do_getpid.rs +++ b/src/libos/src/process/do_getpid.rs @@ -8,11 +8,6 @@ pub fn do_gettid() -> pid_t { current!().tid() } -pub fn do_getpgid() -> pid_t { - // TODO: implement process groups - 1 -} - pub fn do_getppid() -> pid_t { current!().process().parent().pid() } diff --git a/src/libos/src/process/do_spawn/mod.rs b/src/libos/src/process/do_spawn/mod.rs index 5e286c42..77da1272 100644 --- a/src/libos/src/process/do_spawn/mod.rs +++ b/src/libos/src/process/do_spawn/mod.rs @@ -13,6 +13,7 @@ use crate::fs::{ CreationFlags, File, FileDesc, FileMode, FileTable, FsView, HostStdioFds, StdinFile, StdoutFile, }; use crate::prelude::*; +use crate::process::pgrp::{get_spawn_attribute_pgrp, update_pgrp_for_new_process}; use crate::vm::ProcessVM; mod aux_vec; @@ -269,6 +270,11 @@ fn new_process_common( } trace!("new process sig_dispositions = {:?}", sig_dispositions); + // Check for process group spawn attribute. This must be done before building the new process. + let new_pgid = get_spawn_attribute_pgrp(spawn_attributes)?; + // Use parent process's process group by default. + let pgrp_ref = process_ref.pgrp(); + // Make the default thread name to be the process's corresponding elf file name let elf_name = elf_path.rsplit('/').collect::>()[0]; let thread_name = ThreadName::new(elf_name); @@ -282,7 +288,7 @@ fn new_process_common( parent = process_ref; } - process_builder + let new_process = process_builder .vm(vm_ref) .exec_path(&elf_path) .umask(parent.umask()) @@ -291,11 +297,17 @@ fn new_process_common( .sched(sched_ref) .rlimits(rlimit_ref) .fs(fs_ref) + .pgrp(pgrp_ref) .files(files_ref) .sig_mask(sig_mask) .name(thread_name) .sig_dispositions(sig_dispositions) - .build()? + .build()?; + + // This is done here becuase if we want to create a new process group, we must have a new process first. + // So we can't set "pgrp" during the build above. + update_pgrp_for_new_process(new_process.clone(), new_pgid)?; + new_process }; info!( diff --git a/src/libos/src/process/do_wait4.rs b/src/libos/src/process/do_wait4.rs index 3f8f209c..fc90c067 100644 --- a/src/libos/src/process/do_wait4.rs +++ b/src/libos/src/process/do_wait4.rs @@ -1,3 +1,4 @@ +use super::pgrp::clean_pgrp_when_exit; use super::process::{ProcessFilter, ProcessInner}; use super::wait::Waiter; use super::{table, ProcessRef, ProcessStatus}; @@ -102,6 +103,9 @@ fn free_zombie_child(mut parent_inner: SgxMutexGuard, zombie_pid: let zombie = parent_inner.remove_zombie_child(zombie_pid); debug_assert!(zombie.status() == ProcessStatus::Zombie); + // This has to be done after removing from process table to make sure process.pgid() can work. + clean_pgrp_when_exit(&zombie); + let zombie_inner = zombie.inner(); zombie_inner.term_status().unwrap().as_u32() as i32 } diff --git a/src/libos/src/process/mod.rs b/src/libos/src/process/mod.rs index c10a897f..3ecbb38c 100644 --- a/src/libos/src/process/mod.rs +++ b/src/libos/src/process/mod.rs @@ -16,6 +16,7 @@ use crate::sched::SchedAgent; use crate::signal::{SigDispositions, SigQueues}; use crate::vm::ProcessVM; +use self::pgrp::ProcessGrp; use self::process::{ProcessBuilder, ProcessInner}; use self::thread::{ThreadBuilder, ThreadId, ThreadInner}; use self::wait::{WaitQueue, Waiter}; @@ -27,6 +28,7 @@ pub use self::do_spawn::do_spawn_without_exec; pub use self::do_wait4::idle_reap_zombie_children; pub use self::process::{Process, ProcessFilter, ProcessStatus, IDLE}; pub use self::spawn_attribute::posix_spawnattr_t; +pub use self::spawn_attribute::SpawnAttr; pub use self::syscalls::*; pub use self::task::Task; pub use self::term_status::{ForcedExitStatus, TermStatus}; @@ -42,6 +44,7 @@ mod do_robust_list; mod do_set_tid_address; mod do_spawn; mod do_wait4; +mod pgrp; mod prctl; mod process; mod spawn_attribute; @@ -71,3 +74,4 @@ pub type ProcessVMRef = Arc; pub type FsViewRef = Arc>; pub type SchedAgentRef = Arc>; pub type ResourceLimitsRef = Arc>; +pub type ProcessGrpRef = Arc; diff --git a/src/libos/src/process/pgrp.rs b/src/libos/src/process/pgrp.rs new file mode 100644 index 00000000..f24e12e6 --- /dev/null +++ b/src/libos/src/process/pgrp.rs @@ -0,0 +1,223 @@ +use super::*; +use crate::process; + +#[derive(Debug)] +struct PgrpInner { + pgid: pid_t, + process_group: HashMap, // process id, process ref + leader_process: Option, +} + +#[derive(Debug)] +pub struct ProcessGrp { + inner: RwLock, +} + +impl ProcessGrp { + pub fn default() -> Self { + ProcessGrp { + inner: RwLock::new(PgrpInner { + pgid: 0, + process_group: HashMap::new(), + leader_process: None, + }), + } + } + + pub fn pgid(&self) -> pid_t { + self.inner.read().unwrap().pgid + } + + pub fn get_process_number(&self) -> usize { + self.inner.read().unwrap().process_group.len() + } + + pub fn set_pgid(&self, pgid: pid_t) { + self.inner.write().unwrap().pgid = pgid; + } + + pub fn leader_process_is_set(&self) -> bool { + self.inner.read().unwrap().leader_process.is_some() + } + + pub fn get_leader_process(&self) -> Option { + self.inner.read().unwrap().leader_process.clone() + } + + pub fn set_leader_process(&self, new_leader: ProcessRef) { + self.inner.write().unwrap().leader_process = Some(new_leader); + } + + pub fn add_new_process(&self, process: ProcessRef) { + self.inner + .write() + .unwrap() + .process_group + .insert(process.pid(), process); + } + + pub fn get_all_processes(&self) -> Vec { + self.inner + .read() + .unwrap() + .process_group + .values() + .cloned() + .collect() + } + + // Create a new process group + pub fn new(process: ProcessRef) -> Result { + let pgrp = Self::default(); + let pid = process.pid(); + pgrp.set_pgid(pid); + pgrp.set_leader_process(process.clone()); + pgrp.add_new_process(process); + Ok(pgrp) + } + + // Create a new process group with given pid + pub fn new_with_pid(pid: pid_t) -> Result { + let leader_process = table::get_process(pid)?; + Self::new(leader_process) + } + + // Remove process from process group when process exit + pub fn remove_process(&self, process: &ProcessRef) -> Result { + let pgid = self.pgid(); + let leader_process_is_set = self.leader_process_is_set(); + let pgrp_process_num = self.inner.read().unwrap().process_group.len(); + let process_pid = process.pid(); + + if pgrp_process_num < 1 { + return_errno!(EINVAL, "This process group is empty"); + } + + let leader_process_pid = if leader_process_is_set { + Some(self.get_leader_process().unwrap().pid()) + } else { + None + }; + + if pgrp_process_num == 1 { + table::del_pgrp(pgid); + } + + { + // Release lock after removing to avoid deadlock + let mut process_group_inner = &mut self.inner.write().unwrap().process_group; + process_group_inner + .remove(&process_pid) + .ok_or_else(|| errno!(EINVAL, "This process doesn't belong to this pgrp"))?; + } + + if leader_process_pid.is_some() && leader_process_pid.unwrap() == process.pid() { + self.inner.write().unwrap().leader_process = None; + } + return Ok(0); + } +} + +pub fn do_getpgid(pid: pid_t) -> Result { + let process = + table::get_process(pid).map_err(|e| errno!(ESRCH, "pid does not match any process"))?; + Ok(process.pgid()) +} + +// do_setpgid can be called under two cases: +// 1. a running process is calling for itself +// 2. a parent process is calling for its children +// Thus, parent can't setpgid to child if it is executing and only can do that when creating +// it. +pub fn do_setpgid(pid: pid_t, pgid: pid_t, is_executing: bool) -> Result { + // If pid is zero, pid is the calling process's pid. + let pid = if pid == 0 { do_getpid()? as pid_t } else { pid }; + + // If pgid is zero, pgid is made the same as process ID specified by "pid" + let pgid = if pgid == 0 { pid } else { pgid }; + + debug!("setpgid: pid: {:?}, pgid: {:?}", pid, pgid); + + let process = table::get_process(pid)?; + let current_pid = current!().process().pid(); + + // if setpgid to a pgroup other than self, the pgroup must exist + if pgid != pid && table::get_pgrp(pgid).is_err() { + return_errno!(EPERM, "process group not exist"); + } + + // can't setpgid to a running process other than self + if current_pid != pid && is_executing { + return_errno!(EACCES, "can't setpgid to a running child process"); + } + + if let Ok(pgrp) = table::get_pgrp(pgid) { + // pgrp exists + let pgrp_ref = process.pgrp(); + pgrp_ref.remove_process(&process); + process.update_pgrp(pgrp.clone()); + pgrp.add_new_process(process); + } else { + // pgrp not exist + if is_executing { + // First remove process from previous process group. New process + // setpgid doesn't need this. This is done for self only. + debug_assert!(current_pid == pid); + let pgrp_ref = process.pgrp(); + pgrp_ref.remove_process(&process); + } + + let pgrp_ref = Arc::new(ProcessGrp::new_with_pid(pid)?); + process.update_pgrp(pgrp_ref.clone()); + table::add_pgrp(pgrp_ref); + } + Ok(0) +} + +pub fn get_spawn_attribute_pgrp(spawn_attributes: Option) -> Result> { + if spawn_attributes.is_some() && spawn_attributes.unwrap().process_group.is_some() { + let pgid = spawn_attributes.unwrap().process_group.unwrap(); + if pgid != 0 && table::get_pgrp(pgid).is_err() { + return_errno!(EPERM, "process group not exist"); + } else { + return Ok(Some(pgid)); + } + } else { + return Ok(None); + } +} + +// Check process group attribute here before exec. +// This must be done after process is ready. +pub fn update_pgrp_for_new_process(new_process_ref: ProcessRef, pgid: Option) -> Result<()> { + if let Some(pgid) = pgid { + if pgid == 0 { + // create a new process group and add self process + let pgrp_ref = Arc::new(ProcessGrp::new(new_process_ref.clone())?); + new_process_ref.update_pgrp(pgrp_ref.clone()); + table::add_pgrp(pgrp_ref); + } else { + // pgrp must exist when updating + let pgrp = table::get_pgrp(pgid).unwrap(); + new_process_ref.update_pgrp(pgrp.clone()); + pgrp.add_new_process(new_process_ref.clone()); + } + } else { + // By default, new process's process group is same as its parent. + let pgrp_ref = new_process_ref.pgrp(); + pgrp_ref.add_new_process(new_process_ref.clone()); + } + debug!("process group:{:?}", new_process_ref.pgrp()); + debug!("non idle process all pgrp: {:?}", table::get_all_pgrp()); + + Ok(()) +} + +pub fn clean_pgrp_when_exit(process: &ProcessRef) { + let pgrp_ref = process.pgrp(); + + // Remove process from pgrp + pgrp_ref.remove_process(process); + // Remove pgrp info from process + process.remove_pgrp(); +} diff --git a/src/libos/src/process/process/builder.rs b/src/libos/src/process/process/builder.rs index 26d5a0a2..ec4652dc 100644 --- a/src/libos/src/process/process/builder.rs +++ b/src/libos/src/process/process/builder.rs @@ -1,8 +1,9 @@ +use super::super::table; use super::super::task::Task; use super::super::thread::{ThreadBuilder, ThreadId, ThreadName}; use super::super::{ - FileTableRef, ForcedExitStatus, FsViewRef, ProcessRef, ProcessVMRef, ResourceLimitsRef, - SchedAgentRef, + FileTableRef, ForcedExitStatus, FsViewRef, ProcessGrpRef, ProcessRef, ProcessVMRef, + ResourceLimitsRef, SchedAgentRef, }; use super::{Process, ProcessInner}; use crate::fs::FileMode; @@ -15,6 +16,7 @@ pub struct ProcessBuilder { thread_builder: Option, // Mandatory fields vm: Option, + pgrp: Option, // Optional fields, which have reasonable default values exec_path: Option, umask: Option, @@ -30,6 +32,7 @@ impl ProcessBuilder { tid: None, thread_builder: Some(thread_builder), vm: None, + pgrp: None, exec_path: None, umask: None, parent: None, @@ -68,6 +71,11 @@ impl ProcessBuilder { self } + pub fn pgrp(mut self, pgrp: ProcessGrpRef) -> Self { + self.pgrp = Some(pgrp); + self + } + pub fn task(mut self, task: Task) -> Self { self.thread_builder(|tb| tb.task(task)) } @@ -118,6 +126,7 @@ impl ProcessBuilder { let exec_path = self.exec_path.take().unwrap_or_default(); let umask = RwLock::new(self.umask.unwrap_or(FileMode::default_umask())); let parent = self.parent.take().map(|parent| RwLock::new(parent)); + let pgrp = RwLock::new(self.pgrp.clone()); let inner = SgxMutex::new(ProcessInner::new()); let sig_dispositions = RwLock::new(self.sig_dispositions.unwrap_or_default()); let sig_queues = RwLock::new(SigQueues::new()); @@ -127,6 +136,7 @@ impl ProcessBuilder { exec_path, umask, parent, + pgrp, inner, sig_dispositions, sig_queues, @@ -148,6 +158,13 @@ impl ProcessBuilder { .push(new_process.clone()); } + // Only set leader process and process group id during process building when idle process first time init + let pgrp_ref = new_process.pgrp(); + if !pgrp_ref.leader_process_is_set() && pgrp_ref.pgid() == 0 { + pgrp_ref.set_leader_process(new_process.clone()); + pgrp_ref.set_pgid(pid); + } + Ok(new_process) } diff --git a/src/libos/src/process/process/idle.rs b/src/libos/src/process/process/idle.rs index 99e7e9ee..d45cb448 100644 --- a/src/libos/src/process/process/idle.rs +++ b/src/libos/src/process/process/idle.rs @@ -1,3 +1,5 @@ +use super::super::pgrp::ProcessGrp; +use super::super::table; use super::super::task::Task; use super::super::thread::ThreadId; use super::{ProcessBuilder, ThreadRef}; @@ -19,6 +21,7 @@ fn create_idle_thread() -> Result { let dummy_tid = ThreadId::zero(); let dummy_vm = Arc::new(ProcessVM::default()); let dummy_task = Task::default(); + let dummy_pgrp = Arc::new(ProcessGrp::default()); // rlimit get from Occlum.json let rlimits = Arc::new(SgxMutex::new(ResourceLimits::default())); @@ -27,11 +30,13 @@ fn create_idle_thread() -> Result { let idle_process = ProcessBuilder::new() .tid(dummy_tid) .vm(dummy_vm) + .pgrp(dummy_pgrp) .task(dummy_task) .rlimits(rlimits) .no_parent(true) .build()?; debug_assert!(idle_process.pid() == 0); + debug_assert!(idle_process.pgid() == 0); let idle_thread = idle_process.main_thread().unwrap(); debug_assert!(idle_thread.tid() == 0); @@ -39,5 +44,8 @@ fn create_idle_thread() -> Result { // We do not add the idle process/thread to the process/thread table. // This ensures that the idle process is not accessible from the user space. + // Keep process groud 0 in the table + table::add_pgrp(idle_process.pgrp()); + Ok(idle_thread) } diff --git a/src/libos/src/process/process/mod.rs b/src/libos/src/process/process/mod.rs index 6292b94c..578a2291 100644 --- a/src/libos/src/process/process/mod.rs +++ b/src/libos/src/process/process/mod.rs @@ -1,7 +1,7 @@ use std::fmt; use super::wait::WaitQueue; -use super::{ForcedExitStatus, ProcessRef, TermStatus, ThreadRef}; +use super::{ForcedExitStatus, ProcessGrpRef, ProcessRef, TermStatus, ThreadRef}; use crate::fs::FileMode; use crate::prelude::*; use crate::signal::{SigDispositions, SigNum, SigQueues}; @@ -18,6 +18,7 @@ pub struct Process { exec_path: String, // Mutable info parent: Option>, + pgrp: RwLock>, inner: SgxMutex, umask: RwLock, // Signal @@ -40,9 +41,8 @@ impl Process { } /// Get process group ID - // TODO: implement process group pub fn pgid(&self) -> pid_t { - self.pid + self.pgrp().pgid() } /// Get the parent process. @@ -59,6 +59,29 @@ impl Process { .clone() } + /// Get the process group. + pub fn pgrp(&self) -> ProcessGrpRef { + self.pgrp + .read() + .unwrap() + .as_ref() + // Process must be assigned a process group + .unwrap() + .clone() + } + + /// Update process group when setpgid is called + pub fn update_pgrp(&self, new_pgrp: ProcessGrpRef) { + let mut pgrp = self.pgrp.write().unwrap(); + *pgrp = Some(new_pgrp); + } + + /// Remove process group when process exit + pub fn remove_pgrp(&self) { + let mut pgrp = self.pgrp.write().unwrap(); + *pgrp = None; + } + /// Get the main thread. /// /// The main thread is a thread whose tid equals to the process's pid. @@ -199,6 +222,13 @@ impl ProcessInner { self.children().map(|children| children.len()).unwrap_or(0) } + pub fn is_child_of(&self, pid: pid_t) -> bool { + match self.children() { + Some(children) => children.iter().find(|&child| child.pid() == pid).is_some(), + None => false, + } + } + pub fn threads(&self) -> Option<&Vec> { match self { Self::Live { threads, .. } => Some(threads), @@ -309,6 +339,7 @@ impl fmt::Debug for Process { .field("pid", &self.pid()) .field("exec_path", &self.exec_path()) .field("ppid", &ppid) + .field("pgid", &self.pgid()) .field("inner", &self.inner()) .finish() } diff --git a/src/libos/src/process/spawn_attribute.rs b/src/libos/src/process/spawn_attribute.rs index bdbc0558..1c2abad8 100644 --- a/src/libos/src/process/spawn_attribute.rs +++ b/src/libos/src/process/spawn_attribute.rs @@ -5,7 +5,7 @@ use crate::util::mem_util::from_user::check_ptr; // Note: This is the Rust representation of `posix_spawnattr_t` defined in libc. // The name of the elements follow the glibc style. The comments show the name in musl. // Elements other than the listed ones are ignored because we don't care for now because -// Only POSIX_SPAWN_SETSIGDEF and POSIX_SPAWN_SETSIGMASK are supported now. +// Only POSIX_SPAWN_SETPGROUP, POSIX_SPAWN_SETSIGDEF and POSIX_SPAWN_SETSIGMASK are supported now. #[repr(C)] #[derive(Debug)] pub struct posix_spawnattr_t { @@ -32,6 +32,7 @@ bitflags! { impl SpawnAttributeFlags { fn supported(&self) -> bool { let unsupported_flags = SpawnAttributeFlags::all() + - SpawnAttributeFlags::POSIX_SPAWN_SETPGROUP - SpawnAttributeFlags::POSIX_SPAWN_SETSIGDEF - SpawnAttributeFlags::POSIX_SPAWN_SETSIGMASK; if self.intersects(unsupported_flags) { @@ -44,6 +45,7 @@ impl SpawnAttributeFlags { #[derive(Default, Debug, Copy, Clone)] pub struct SpawnAttr { + pub process_group: Option, pub sig_mask: Option, pub sig_default: Option, } @@ -70,6 +72,12 @@ pub fn clone_spawn_atrributes_safely( ); } + if spawn_attr + .flags + .contains(SpawnAttributeFlags::POSIX_SPAWN_SETPGROUP) + { + safe_attr.process_group = Some(spawn_attr.pgrp as pid_t); + } if spawn_attr .flags .contains(SpawnAttributeFlags::POSIX_SPAWN_SETSIGDEF) diff --git a/src/libos/src/process/syscalls.rs b/src/libos/src/process/syscalls.rs index 13740f50..ce3fc5df 100644 --- a/src/libos/src/process/syscalls.rs +++ b/src/libos/src/process/syscalls.rs @@ -5,6 +5,7 @@ use super::do_futex::{FutexFlags, FutexOp, FutexTimeout}; use super::do_robust_list::RobustListHead; use super::do_spawn::FileAction; use super::do_wait4::WaitOptions; +use super::pgrp::*; use super::prctl::PrctlCmd; use super::process::ProcessFilter; use super::spawn_attribute::{clone_spawn_atrributes_safely, posix_spawnattr_t, SpawnAttr}; @@ -393,11 +394,44 @@ pub fn do_getppid() -> Result { Ok(ppid as isize) } -pub fn do_getpgid() -> Result { - let pgid = super::do_getpid::do_getpgid(); +pub fn do_getpgrp() -> Result { + do_getpgid(0) +} + +pub fn do_getpgid(pid: i32) -> Result { + if pid < 0 { + return_errno!(ESRCH, "process with negative pid is not found"); + } + + let real_pid = if pid == 0 { + do_getpid()? as pid_t + } else { + pid as pid_t + }; + let pgid = super::pgrp::do_getpgid(real_pid)?; Ok(pgid as isize) } +pub fn do_setpgid(pid: i32, pgid: i32) -> Result { + if pgid < 0 { + return_errno!(EINVAL, "pgid can't be negative"); + } + + let pid = pid as pid_t; + let pgid = pgid as pid_t; + // Pid should be the calling process or a child of the calling process. + let current_pid = current!().process().pid(); + if pid != 0 && pid != current_pid && current!().process().inner().is_child_of(pid) == false { + return_errno!(ESRCH, "pid not calling process or child processes"); + } + + // When this function is calling, the process must be executing. + let is_executing = true; + let ret = super::pgrp::do_setpgid(pid, pgid, is_executing)?; + + Ok(ret) +} + // TODO: implement uid, gid, euid, egid pub fn do_getuid() -> Result { diff --git a/src/libos/src/process/table.rs b/src/libos/src/process/table.rs index 37a7f096..e42efe59 100644 --- a/src/libos/src/process/table.rs +++ b/src/libos/src/process/table.rs @@ -1,6 +1,31 @@ -use super::{ProcessRef, ThreadRef}; +use super::{ProcessGrpRef, ProcessRef, ThreadRef}; use crate::prelude::*; +pub fn get_pgrp(pgid: pid_t) -> Result { + PROCESSGRP_TABLE.lock().unwrap().get(pgid) +} + +pub(super) fn add_pgrp(pgrp: ProcessGrpRef) -> Result<()> { + PROCESSGRP_TABLE.lock().unwrap().add(pgrp.pgid(), pgrp) +} + +pub(super) fn del_pgrp(pgid: pid_t) -> Result { + PROCESSGRP_TABLE.lock().unwrap().del(pgid) +} + +pub fn get_pgrp_number(pgid: pid_t) -> usize { + PROCESSGRP_TABLE.lock().unwrap().len() +} + +pub fn get_all_pgrp() -> Vec { + PROCESSGRP_TABLE + .lock() + .unwrap() + .iter() + .map(|(_, pgrp_ref)| pgrp_ref.clone()) + .collect() +} + pub fn get_process(pid: pid_t) -> Result { PROCESS_TABLE.lock().unwrap().get(pid) } @@ -64,6 +89,8 @@ lazy_static! { { SgxMutex::new(Table::::with_capacity(8)) }; static ref THREAD_TABLE: SgxMutex> = { SgxMutex::new(Table::::with_capacity(8)) }; + static ref PROCESSGRP_TABLE: SgxMutex> = + { SgxMutex::new(Table::::with_capacity(4)) }; } #[derive(Debug, Clone)] @@ -78,6 +105,10 @@ impl Table { } } + pub fn len(&self) -> usize { + self.map.len() + } + pub fn iter(&self) -> std::collections::hash_map::Iter<'_, pid_t, I> { self.map.iter() } diff --git a/src/libos/src/signal/do_kill.rs b/src/libos/src/signal/do_kill.rs index 39cbdf5a..9f9bc803 100644 --- a/src/libos/src/signal/do_kill.rs +++ b/src/libos/src/signal/do_kill.rs @@ -56,11 +56,8 @@ fn get_processes(filter: &ProcessFilter) -> Result> { vec![process] } ProcessFilter::WithPgid(pgid) => { - // TODO: implement O(1) lookup for a process group - let processes: Vec = table::get_all_processes() - .into_iter() - .filter(|proc_ref| proc_ref.pgid() == *pgid) - .collect(); + let pgrp = table::get_pgrp(*pgid)?; + let processes = pgrp.get_all_processes(); if processes.len() == 0 { return_errno!(EINVAL, "invalid pgid"); } diff --git a/src/libos/src/syscall/mod.rs b/src/libos/src/syscall/mod.rs index 8e08388f..28a9c7ee 100644 --- a/src/libos/src/syscall/mod.rs +++ b/src/libos/src/syscall/mod.rs @@ -43,10 +43,10 @@ use crate::net::{ }; use crate::process::{ do_arch_prctl, do_clone, do_execve, do_exit, do_exit_group, do_futex, do_get_robust_list, - do_getegid, do_geteuid, do_getgid, do_getgroups, do_getpgid, do_getpid, do_getppid, do_gettid, - do_getuid, do_prctl, do_set_robust_list, do_set_tid_address, do_spawn_for_glibc, - do_spawn_for_musl, do_wait4, pid_t, posix_spawnattr_t, FdOp, RobustListHead, SpawnFileActions, - ThreadStatus, + do_getegid, do_geteuid, do_getgid, do_getgroups, do_getpgid, do_getpgrp, do_getpid, do_getppid, + do_gettid, do_getuid, do_prctl, do_set_robust_list, do_set_tid_address, do_setpgid, + do_spawn_for_glibc, do_spawn_for_musl, do_wait4, pid_t, posix_spawnattr_t, FdOp, + RobustListHead, SpawnFileActions, ThreadStatus, }; use crate::sched::{do_getcpu, do_sched_getaffinity, do_sched_setaffinity, do_sched_yield}; use crate::signal::{ @@ -197,9 +197,9 @@ macro_rules! process_syscall_table_with_callback { (Setgid = 106) => handle_unsupported(), (Geteuid = 107) => do_geteuid(), (Getegid = 108) => do_getegid(), - (Setpgid = 109) => handle_unsupported(), + (Setpgid = 109) => do_setpgid(pid: i32, pgid: i32), (Getppid = 110) => do_getppid(), - (Getpgrp = 111) => handle_unsupported(), + (Getpgrp = 111) => do_getpgrp(), (Setsid = 112) => handle_unsupported(), (Setreuid = 113) => handle_unsupported(), (Setregid = 114) => handle_unsupported(), @@ -209,7 +209,7 @@ macro_rules! process_syscall_table_with_callback { (Getresuid = 118) => handle_unsupported(), (Setresgid = 119) => handle_unsupported(), (Getresgid = 120) => handle_unsupported(), - (Getpgid = 121) => do_getpgid(), + (Getpgid = 121) => do_getpgid(pid: i32), (Setfsuid = 122) => handle_unsupported(), (Setfsgid = 123) => handle_unsupported(), (Getsid = 124) => handle_unsupported(), diff --git a/test/Makefile b/test/Makefile index 48c2dfb1..6e0429ff 100644 --- a/test/Makefile +++ b/test/Makefile @@ -19,7 +19,7 @@ TESTS ?= env empty hello_world malloc mmap file fs_perms getpid spawn sched pipe truncate readdir mkdir open stat link symlink chmod chown tls pthread system_info resolv_conf rlimit \ server server_epoll unix_socket cout hostfs cpuid rdtsc device sleep exit_group \ ioctl fcntl eventfd emulate_syscall access signal sysinfo prctl rename procfs wait \ - spawn_attribute exec statfs umask + spawn_attribute exec statfs umask pgrp # Benchmarks: need to be compiled and run by bench-% target BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput diff --git a/test/Occlum.json b/test/Occlum.json index d3c41039..7c298dcb 100644 --- a/test/Occlum.json +++ b/test/Occlum.json @@ -2,7 +2,7 @@ "resource_limits": { "kernel_space_heap_size": "40MB", "kernel_space_stack_size": "1MB", - "user_space_size": "420MB", + "user_space_size": "500MB", "max_num_of_threads": 32 }, "process": { diff --git a/test/getpid/main.c b/test/getpid/main.c index 4db3b2d9..d7ac6d1d 100644 --- a/test/getpid/main.c +++ b/test/getpid/main.c @@ -5,7 +5,8 @@ #include int main(int argc, const char *argv[]) { - printf("Run a new process with pid = %d and ppid = %d\n", getpid(), getppid()); + printf("Run a new process with pid = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), + getpgid(0)); printf("tid = %ld\n", syscall(SYS_gettid)); return 0; } diff --git a/test/pgrp/Makefile b/test/pgrp/Makefile new file mode 100644 index 00000000..02032dff --- /dev/null +++ b/test/pgrp/Makefile @@ -0,0 +1,5 @@ +include ../test_common.mk + +EXTRA_C_FLAGS := -g +EXTRA_LINK_FLAGS := +BIN_ARGS := diff --git a/test/pgrp/main.c b/test/pgrp/main.c new file mode 100644 index 00000000..8ba777b1 --- /dev/null +++ b/test/pgrp/main.c @@ -0,0 +1,304 @@ +#include +#include +#include +#include +#include +#include + +#include "test.h" + +// ============================================================================ +// Helper functions +// ============================================================================ + +static void handle_sigsegv(int num) { + printf("SIGSEGV Caught in child with pid = %d, pgid = %d\n", getpid(), getpgid(0)); + assert(num == SIGSEGV); + exit(0); +} + +// Create a child process with different args which will have the pgid specified by `pgid` +// and the child will sleep and then abort. +// This new process should be killed to prevent aborting. +static int create_process_with_pgid(int pgid) { + int ret = 0; + posix_spawnattr_t attr; + int child_pid = 0; + + // set child process spawn attribute + ret = posix_spawnattr_init(&attr); + if (ret != 0) { + THROW_ERROR("init spawnattr error"); + } + ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP); + if (ret != 0) { + THROW_ERROR("set attribute flag error"); + } + // child process will have its own process group + ret = posix_spawnattr_setpgroup(&attr, pgid); + if (ret != 0) { + THROW_ERROR("set process group attribute error"); + } + + int child_argc = 2; // /bin/pgrp again + char **child_argv = calloc(1, sizeof(char *) * (child_argc + 1)); + child_argv[0] = strdup("pgrp"); + child_argv[1] = strdup("again"); + ret = posix_spawn(&child_pid, "/bin/pgrp", NULL, &attr, child_argv, NULL); + if (ret < 0) { + THROW_ERROR("ERROR: failed to spawn a child process\n"); + } + printf("Spawn a new proces successfully pid = %d\n", child_pid); + posix_spawnattr_destroy(&attr); + free(child_argv); + return child_pid; +} + +// ============================================================================ +// Test cases for process group +// ============================================================================ +int test_child_getpgid() { + int ret, child_pid, status; + int pgid = getpgid(0); + int pgrp_id = getpgrp(); + if (pgid != pgrp_id) { + THROW_ERROR("getpgrp error"); + } + + printf("Run a parent process with pid = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), + pgid); + + ret = posix_spawn(&child_pid, "/bin/getpid", NULL, NULL, NULL, NULL); + if (ret < 0) { + THROW_ERROR("ERROR: failed to spawn a child process\n"); + } + printf("Spawn a child proces successfully with pid = %d\n", child_pid); + + // child process group should have same pgid with parent + int child_pgid = getpgid(child_pid); + if (child_pgid != pgid) { + THROW_ERROR("child process group error"); + } + + ret = wait4(-1, &status, 0, NULL); + if (ret < 0) { + THROW_ERROR("ERROR: failed to wait4 the child process\n"); + } + printf("Child process exited with status = %d\n", status); + + return 0; +} + +int test_child_setpgid() { + int ret, child_pid, status; + + printf("Parent process: pid = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), + getpgid(0)); + + child_pid = create_process_with_pgid(0); + if (child_pid < 0) { + THROW_ERROR("create child process error"); + } + + // child pgid should be same as its pid + int child_pgid = getpgid(child_pid); + if (child_pgid != child_pid) { + THROW_ERROR("child process group error"); + } + + kill(child_pid, SIGSEGV); + ret = wait4(-1, &status, 0, NULL); + if (ret < 0) { + printf("ERROR: failed to wait4 the child process\n"); + return -1; + } + printf("Child process exited with status = %d\n", status); + + return 0; +} + +int test_child_setpgid_to_other_child() { + int ret, first_child_pid, second_child_pid, status; + + first_child_pid = create_process_with_pgid(0); + if (first_child_pid < 0) { + THROW_ERROR("failed to create first child"); + } + + // child pgid should be same as its pid + int child_pgid = getpgid(first_child_pid); + printf("first_child_pgid = %d\n", child_pgid); + if (child_pgid != first_child_pid) { + THROW_ERROR("first child process group error"); + } + + // add the second child to the first child's process group + second_child_pid = create_process_with_pgid(child_pgid); + if (second_child_pid < 0) { + THROW_ERROR("failed to create first child"); + } + + // wait for child to run + sleep(1); + + // second child pgid should be same as the the first child pgid + int second_child_pgid = getpgid(second_child_pid); + if (second_child_pgid != child_pgid) { + THROW_ERROR("second child process group error"); + } + kill(0 - second_child_pid, SIGSEGV); + + ret = kill(0 - child_pgid, SIGSEGV); + if (ret < 0) { + THROW_ERROR("ERROR: failed to kill process group 1\n"); + } + + // wait for all child process to exit + while ((ret = wait(&status)) > 0); + + return 0; +} + +int test_setpgid_to_running_child() { + int ret, child_pid, status; + + ret = posix_spawn(&child_pid, "/bin/getpid", NULL, NULL, NULL, NULL); + if (ret != 0) { + THROW_ERROR("failed to spawn a child process"); + } + + // set child pgrp to itself + if (setpgid(child_pid, 0) == 0 || errno != EACCES) { + THROW_ERROR("set child process group error not catching"); + } + + ret = wait4(-1, &status, 0, NULL); + if (ret < 0) { + THROW_ERROR("ERROR: failed to wait4 the child process\n"); + } + + return 0; +} + +int test_setpgid_non_existent_pgrp() { + int ret, child_pid; + posix_spawnattr_t attr; + int non_existent_pgid = 10; + + // make self process to join a non-existent process group + if (setpgid(0, non_existent_pgid) == 0 || errno != EPERM ) { + THROW_ERROR("set self process group error not catching"); + } + + // set child process group to a non-existent pgroup + ret = posix_spawnattr_init(&attr); + if (ret != 0) { + THROW_ERROR("init spawnattr error"); + } + ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP); + if (ret != 0) { + THROW_ERROR("set attribute flag error"); + } + ret = posix_spawnattr_setpgroup(&attr, non_existent_pgid); + if (ret != 0) { + THROW_ERROR("set process group attribute error"); + } + ret = posix_spawn(&child_pid, "/bin/getpid", NULL, &attr, NULL, NULL); + if (ret == 0 || errno != EPERM ) { + THROW_ERROR("child process spawn error not catching\n"); + } + + //posix_spawn will fail. No need to wait for child. + posix_spawnattr_destroy(&attr); + return 0; +} + +int test_signal_a_group_of_process() { + printf("current(parent) pid = %d, pgid = %d\n", getpid(), getpgid(0)); + int process_group_1 = getpid(); + int ret, status; + + // spawn self with its own process group + int child = create_process_with_pgid(0); + if (child < 0) { + THROW_ERROR("failed to create child"); + } + int process_group_2 = child; + + // create 2 other children + int other_children[2] = {0}; + int child_argc = 2; // /bin/pgrp again + char **child_argv = calloc(1, sizeof(char *) * (child_argc + 1)); + child_argv[0] = strdup("pgrp"); + child_argv[1] = strdup("again"); + for (int i = 0; i < 2; i++) { + ret = posix_spawn(&other_children[i], "/bin/pgrp", NULL, NULL, child_argv, NULL); + if (ret < 0) { + THROW_ERROR("ERROR: failed to spawn a child process\n"); + } + printf("spawn other children pid = %d\n", other_children[i]); + } + free(child_argv); + sleep(1); + + // make self process to join child's process group + if (setpgid(0, process_group_2) < 0) { + THROW_ERROR("join child process group error"); + } + + if (getpgid(0) != process_group_2) { + THROW_ERROR("current pgid should be same as child's"); + } + + // other children should be in process group 1 + ret = kill(0 - process_group_1, SIGSEGV); + if (ret < 0) { + THROW_ERROR("ERROR: failed to kill process group 1\n"); + } + + // set a process group for self. + // setpgrp() == setpgid(0,0) + if (setpgrp() < 0) { + THROW_ERROR("join child process group error"); + } + + ret = kill(0 - process_group_2, SIGSEGV); + if (ret < 0) { + THROW_ERROR("ERROR: failed to kill process group 2\n"); + } + + // wait for all child process to exit + while ((ret = wait(&status)) > 0); + + return 0; +} + +// ============================================================================ +// Test suite main +// ============================================================================ + +static test_case_t test_cases[] = { + TEST_CASE(test_child_getpgid), + TEST_CASE(test_child_setpgid), + TEST_CASE(test_child_setpgid_to_other_child), + TEST_CASE(test_setpgid_to_running_child), + TEST_CASE(test_setpgid_non_existent_pgrp), + TEST_CASE(test_signal_a_group_of_process), +}; + + + +int main(int argc, char **argv) { + if (argc > 1) { + // Spawn self. Do some extra work here. + printf("pgrp run again as child with pid = %d, pgid = %d\n", getpid(), getpgid(0)); + signal(SIGSEGV, handle_sigsegv); + sleep(10); + // This shouldn't be reached. + abort(); + } + + int ret; + ret = test_suite_run(test_cases, ARRAY_SIZE(test_cases)); + return ret; +}