Add process group implementation and support set/getpgid, set/getpgrp

This commit is contained in:
Hui, Chunyang 2021-08-16 09:35:33 +00:00 committed by Zongmin.Gu
parent 81dba866f1
commit 88f04c8df9
20 changed files with 718 additions and 33 deletions

@ -8,7 +8,7 @@ use crate::exception::*;
use crate::fs::HostStdioFds; use crate::fs::HostStdioFds;
use crate::interrupt; use crate::interrupt;
use crate::process::idle_reap_zombie_children; use crate::process::idle_reap_zombie_children;
use crate::process::ProcessFilter; use crate::process::{ProcessFilter, SpawnAttr};
use crate::signal::SigNum; use crate::signal::SigNum;
use crate::time::up_time::init; use crate::time::up_time::init;
use crate::util::log::LevelFilter; use crate::util::log::LevelFilter;
@ -270,12 +270,21 @@ fn do_new_process(
let file_actions = Vec::new(); let file_actions = Vec::new();
let current = &process::IDLE; let current = &process::IDLE;
let program_path_str = program_path.to_str().unwrap(); 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( let new_tid = process::do_spawn_without_exec(
&program_path_str, &program_path_str,
argv, argv,
&env_concat, &env_concat,
&file_actions, &file_actions,
None, Some(spawn_attribute),
host_stdio_fds, host_stdio_fds,
current, current,
)?; )?;

@ -2,6 +2,7 @@ use crate::signal::constants::*;
use std::intrinsics::atomic_store; use std::intrinsics::atomic_store;
use super::do_futex::futex_wake; use super::do_futex::futex_wake;
use super::pgrp::clean_pgrp_when_exit;
use super::process::{Process, ProcessFilter}; use super::process::{Process, ProcessFilter};
use super::{table, ProcessRef, TermStatus, ThreadRef, ThreadStatus}; use super::{table, ProcessRef, TermStatus, ThreadRef, ThreadStatus};
use crate::prelude::*; use crate::prelude::*;
@ -102,6 +103,7 @@ fn exit_process(thread: &ThreadRef, term_status: TermStatus) {
let main_tid = pid; let main_tid = pid;
table::del_thread(main_tid).expect("tid must be in the table"); table::del_thread(main_tid).expect("tid must be in the table");
table::del_process(pid).expect("pid 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); process_inner.exit(term_status, &idle_ref, &mut idle_inner);
idle_inner.remove_zombie_child(pid); idle_inner.remove_zombie_child(pid);

@ -8,11 +8,6 @@ pub fn do_gettid() -> pid_t {
current!().tid() current!().tid()
} }
pub fn do_getpgid() -> pid_t {
// TODO: implement process groups
1
}
pub fn do_getppid() -> pid_t { pub fn do_getppid() -> pid_t {
current!().process().parent().pid() current!().process().parent().pid()
} }

@ -13,6 +13,7 @@ use crate::fs::{
CreationFlags, File, FileDesc, FileMode, FileTable, FsView, HostStdioFds, StdinFile, StdoutFile, CreationFlags, File, FileDesc, FileMode, FileTable, FsView, HostStdioFds, StdinFile, StdoutFile,
}; };
use crate::prelude::*; use crate::prelude::*;
use crate::process::pgrp::{get_spawn_attribute_pgrp, update_pgrp_for_new_process};
use crate::vm::ProcessVM; use crate::vm::ProcessVM;
mod aux_vec; mod aux_vec;
@ -269,6 +270,11 @@ fn new_process_common(
} }
trace!("new process sig_dispositions = {:?}", sig_dispositions); 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 // Make the default thread name to be the process's corresponding elf file name
let elf_name = elf_path.rsplit('/').collect::<Vec<&str>>()[0]; let elf_name = elf_path.rsplit('/').collect::<Vec<&str>>()[0];
let thread_name = ThreadName::new(elf_name); let thread_name = ThreadName::new(elf_name);
@ -282,7 +288,7 @@ fn new_process_common(
parent = process_ref; parent = process_ref;
} }
process_builder let new_process = process_builder
.vm(vm_ref) .vm(vm_ref)
.exec_path(&elf_path) .exec_path(&elf_path)
.umask(parent.umask()) .umask(parent.umask())
@ -291,11 +297,17 @@ fn new_process_common(
.sched(sched_ref) .sched(sched_ref)
.rlimits(rlimit_ref) .rlimits(rlimit_ref)
.fs(fs_ref) .fs(fs_ref)
.pgrp(pgrp_ref)
.files(files_ref) .files(files_ref)
.sig_mask(sig_mask) .sig_mask(sig_mask)
.name(thread_name) .name(thread_name)
.sig_dispositions(sig_dispositions) .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!( info!(

@ -1,3 +1,4 @@
use super::pgrp::clean_pgrp_when_exit;
use super::process::{ProcessFilter, ProcessInner}; use super::process::{ProcessFilter, ProcessInner};
use super::wait::Waiter; use super::wait::Waiter;
use super::{table, ProcessRef, ProcessStatus}; use super::{table, ProcessRef, ProcessStatus};
@ -102,6 +103,9 @@ fn free_zombie_child(mut parent_inner: SgxMutexGuard<ProcessInner>, zombie_pid:
let zombie = parent_inner.remove_zombie_child(zombie_pid); let zombie = parent_inner.remove_zombie_child(zombie_pid);
debug_assert!(zombie.status() == ProcessStatus::Zombie); 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(); let zombie_inner = zombie.inner();
zombie_inner.term_status().unwrap().as_u32() as i32 zombie_inner.term_status().unwrap().as_u32() as i32
} }

@ -16,6 +16,7 @@ use crate::sched::SchedAgent;
use crate::signal::{SigDispositions, SigQueues}; use crate::signal::{SigDispositions, SigQueues};
use crate::vm::ProcessVM; use crate::vm::ProcessVM;
use self::pgrp::ProcessGrp;
use self::process::{ProcessBuilder, ProcessInner}; use self::process::{ProcessBuilder, ProcessInner};
use self::thread::{ThreadBuilder, ThreadId, ThreadInner}; use self::thread::{ThreadBuilder, ThreadId, ThreadInner};
use self::wait::{WaitQueue, Waiter}; 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::do_wait4::idle_reap_zombie_children;
pub use self::process::{Process, ProcessFilter, ProcessStatus, IDLE}; pub use self::process::{Process, ProcessFilter, ProcessStatus, IDLE};
pub use self::spawn_attribute::posix_spawnattr_t; pub use self::spawn_attribute::posix_spawnattr_t;
pub use self::spawn_attribute::SpawnAttr;
pub use self::syscalls::*; pub use self::syscalls::*;
pub use self::task::Task; pub use self::task::Task;
pub use self::term_status::{ForcedExitStatus, TermStatus}; pub use self::term_status::{ForcedExitStatus, TermStatus};
@ -42,6 +44,7 @@ mod do_robust_list;
mod do_set_tid_address; mod do_set_tid_address;
mod do_spawn; mod do_spawn;
mod do_wait4; mod do_wait4;
mod pgrp;
mod prctl; mod prctl;
mod process; mod process;
mod spawn_attribute; mod spawn_attribute;
@ -71,3 +74,4 @@ pub type ProcessVMRef = Arc<ProcessVM>;
pub type FsViewRef = Arc<RwLock<FsView>>; pub type FsViewRef = Arc<RwLock<FsView>>;
pub type SchedAgentRef = Arc<SgxMutex<SchedAgent>>; pub type SchedAgentRef = Arc<SgxMutex<SchedAgent>>;
pub type ResourceLimitsRef = Arc<SgxMutex<ResourceLimits>>; pub type ResourceLimitsRef = Arc<SgxMutex<ResourceLimits>>;
pub type ProcessGrpRef = Arc<ProcessGrp>;

@ -0,0 +1,223 @@
use super::*;
use crate::process;
#[derive(Debug)]
struct PgrpInner {
pgid: pid_t,
process_group: HashMap<pid_t, ProcessRef>, // process id, process ref
leader_process: Option<ProcessRef>,
}
#[derive(Debug)]
pub struct ProcessGrp {
inner: RwLock<PgrpInner>,
}
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<ProcessRef> {
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<ProcessRef> {
self.inner
.read()
.unwrap()
.process_group
.values()
.cloned()
.collect()
}
// Create a new process group
pub fn new(process: ProcessRef) -> Result<Self> {
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<Self> {
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<isize> {
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<pid_t> {
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<isize> {
// 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<SpawnAttr>) -> Result<Option<pid_t>> {
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<pid_t>) -> 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();
}

@ -1,8 +1,9 @@
use super::super::table;
use super::super::task::Task; use super::super::task::Task;
use super::super::thread::{ThreadBuilder, ThreadId, ThreadName}; use super::super::thread::{ThreadBuilder, ThreadId, ThreadName};
use super::super::{ use super::super::{
FileTableRef, ForcedExitStatus, FsViewRef, ProcessRef, ProcessVMRef, ResourceLimitsRef, FileTableRef, ForcedExitStatus, FsViewRef, ProcessGrpRef, ProcessRef, ProcessVMRef,
SchedAgentRef, ResourceLimitsRef, SchedAgentRef,
}; };
use super::{Process, ProcessInner}; use super::{Process, ProcessInner};
use crate::fs::FileMode; use crate::fs::FileMode;
@ -15,6 +16,7 @@ pub struct ProcessBuilder {
thread_builder: Option<ThreadBuilder>, thread_builder: Option<ThreadBuilder>,
// Mandatory fields // Mandatory fields
vm: Option<ProcessVMRef>, vm: Option<ProcessVMRef>,
pgrp: Option<ProcessGrpRef>,
// Optional fields, which have reasonable default values // Optional fields, which have reasonable default values
exec_path: Option<String>, exec_path: Option<String>,
umask: Option<FileMode>, umask: Option<FileMode>,
@ -30,6 +32,7 @@ impl ProcessBuilder {
tid: None, tid: None,
thread_builder: Some(thread_builder), thread_builder: Some(thread_builder),
vm: None, vm: None,
pgrp: None,
exec_path: None, exec_path: None,
umask: None, umask: None,
parent: None, parent: None,
@ -68,6 +71,11 @@ impl ProcessBuilder {
self self
} }
pub fn pgrp(mut self, pgrp: ProcessGrpRef) -> Self {
self.pgrp = Some(pgrp);
self
}
pub fn task(mut self, task: Task) -> Self { pub fn task(mut self, task: Task) -> Self {
self.thread_builder(|tb| tb.task(task)) self.thread_builder(|tb| tb.task(task))
} }
@ -118,6 +126,7 @@ impl ProcessBuilder {
let exec_path = self.exec_path.take().unwrap_or_default(); let exec_path = self.exec_path.take().unwrap_or_default();
let umask = RwLock::new(self.umask.unwrap_or(FileMode::default_umask())); let umask = RwLock::new(self.umask.unwrap_or(FileMode::default_umask()));
let parent = self.parent.take().map(|parent| RwLock::new(parent)); let parent = self.parent.take().map(|parent| RwLock::new(parent));
let pgrp = RwLock::new(self.pgrp.clone());
let inner = SgxMutex::new(ProcessInner::new()); let inner = SgxMutex::new(ProcessInner::new());
let sig_dispositions = RwLock::new(self.sig_dispositions.unwrap_or_default()); let sig_dispositions = RwLock::new(self.sig_dispositions.unwrap_or_default());
let sig_queues = RwLock::new(SigQueues::new()); let sig_queues = RwLock::new(SigQueues::new());
@ -127,6 +136,7 @@ impl ProcessBuilder {
exec_path, exec_path,
umask, umask,
parent, parent,
pgrp,
inner, inner,
sig_dispositions, sig_dispositions,
sig_queues, sig_queues,
@ -148,6 +158,13 @@ impl ProcessBuilder {
.push(new_process.clone()); .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) Ok(new_process)
} }

@ -1,3 +1,5 @@
use super::super::pgrp::ProcessGrp;
use super::super::table;
use super::super::task::Task; use super::super::task::Task;
use super::super::thread::ThreadId; use super::super::thread::ThreadId;
use super::{ProcessBuilder, ThreadRef}; use super::{ProcessBuilder, ThreadRef};
@ -19,6 +21,7 @@ fn create_idle_thread() -> Result<ThreadRef> {
let dummy_tid = ThreadId::zero(); let dummy_tid = ThreadId::zero();
let dummy_vm = Arc::new(ProcessVM::default()); let dummy_vm = Arc::new(ProcessVM::default());
let dummy_task = Task::default(); let dummy_task = Task::default();
let dummy_pgrp = Arc::new(ProcessGrp::default());
// rlimit get from Occlum.json // rlimit get from Occlum.json
let rlimits = Arc::new(SgxMutex::new(ResourceLimits::default())); let rlimits = Arc::new(SgxMutex::new(ResourceLimits::default()));
@ -27,11 +30,13 @@ fn create_idle_thread() -> Result<ThreadRef> {
let idle_process = ProcessBuilder::new() let idle_process = ProcessBuilder::new()
.tid(dummy_tid) .tid(dummy_tid)
.vm(dummy_vm) .vm(dummy_vm)
.pgrp(dummy_pgrp)
.task(dummy_task) .task(dummy_task)
.rlimits(rlimits) .rlimits(rlimits)
.no_parent(true) .no_parent(true)
.build()?; .build()?;
debug_assert!(idle_process.pid() == 0); debug_assert!(idle_process.pid() == 0);
debug_assert!(idle_process.pgid() == 0);
let idle_thread = idle_process.main_thread().unwrap(); let idle_thread = idle_process.main_thread().unwrap();
debug_assert!(idle_thread.tid() == 0); debug_assert!(idle_thread.tid() == 0);
@ -39,5 +44,8 @@ fn create_idle_thread() -> Result<ThreadRef> {
// We do not add the idle process/thread to the process/thread table. // 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. // 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) Ok(idle_thread)
} }

@ -1,7 +1,7 @@
use std::fmt; use std::fmt;
use super::wait::WaitQueue; use super::wait::WaitQueue;
use super::{ForcedExitStatus, ProcessRef, TermStatus, ThreadRef}; use super::{ForcedExitStatus, ProcessGrpRef, ProcessRef, TermStatus, ThreadRef};
use crate::fs::FileMode; use crate::fs::FileMode;
use crate::prelude::*; use crate::prelude::*;
use crate::signal::{SigDispositions, SigNum, SigQueues}; use crate::signal::{SigDispositions, SigNum, SigQueues};
@ -18,6 +18,7 @@ pub struct Process {
exec_path: String, exec_path: String,
// Mutable info // Mutable info
parent: Option<RwLock<ProcessRef>>, parent: Option<RwLock<ProcessRef>>,
pgrp: RwLock<Option<ProcessGrpRef>>,
inner: SgxMutex<ProcessInner>, inner: SgxMutex<ProcessInner>,
umask: RwLock<FileMode>, umask: RwLock<FileMode>,
// Signal // Signal
@ -40,9 +41,8 @@ impl Process {
} }
/// Get process group ID /// Get process group ID
// TODO: implement process group
pub fn pgid(&self) -> pid_t { pub fn pgid(&self) -> pid_t {
self.pid self.pgrp().pgid()
} }
/// Get the parent process. /// Get the parent process.
@ -59,6 +59,29 @@ impl Process {
.clone() .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. /// Get the main thread.
/// ///
/// The main thread is a thread whose tid equals to the process's pid. /// 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) 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<ThreadRef>> { pub fn threads(&self) -> Option<&Vec<ThreadRef>> {
match self { match self {
Self::Live { threads, .. } => Some(threads), Self::Live { threads, .. } => Some(threads),
@ -309,6 +339,7 @@ impl fmt::Debug for Process {
.field("pid", &self.pid()) .field("pid", &self.pid())
.field("exec_path", &self.exec_path()) .field("exec_path", &self.exec_path())
.field("ppid", &ppid) .field("ppid", &ppid)
.field("pgid", &self.pgid())
.field("inner", &self.inner()) .field("inner", &self.inner())
.finish() .finish()
} }

@ -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. // 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. // 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 // 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)] #[repr(C)]
#[derive(Debug)] #[derive(Debug)]
pub struct posix_spawnattr_t { pub struct posix_spawnattr_t {
@ -32,6 +32,7 @@ bitflags! {
impl SpawnAttributeFlags { impl SpawnAttributeFlags {
fn supported(&self) -> bool { fn supported(&self) -> bool {
let unsupported_flags = SpawnAttributeFlags::all() let unsupported_flags = SpawnAttributeFlags::all()
- SpawnAttributeFlags::POSIX_SPAWN_SETPGROUP
- SpawnAttributeFlags::POSIX_SPAWN_SETSIGDEF - SpawnAttributeFlags::POSIX_SPAWN_SETSIGDEF
- SpawnAttributeFlags::POSIX_SPAWN_SETSIGMASK; - SpawnAttributeFlags::POSIX_SPAWN_SETSIGMASK;
if self.intersects(unsupported_flags) { if self.intersects(unsupported_flags) {
@ -44,6 +45,7 @@ impl SpawnAttributeFlags {
#[derive(Default, Debug, Copy, Clone)] #[derive(Default, Debug, Copy, Clone)]
pub struct SpawnAttr { pub struct SpawnAttr {
pub process_group: Option<pid_t>,
pub sig_mask: Option<SigSet>, pub sig_mask: Option<SigSet>,
pub sig_default: Option<SigSet>, pub sig_default: Option<SigSet>,
} }
@ -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 if spawn_attr
.flags .flags
.contains(SpawnAttributeFlags::POSIX_SPAWN_SETSIGDEF) .contains(SpawnAttributeFlags::POSIX_SPAWN_SETSIGDEF)

@ -5,6 +5,7 @@ use super::do_futex::{FutexFlags, FutexOp, FutexTimeout};
use super::do_robust_list::RobustListHead; use super::do_robust_list::RobustListHead;
use super::do_spawn::FileAction; use super::do_spawn::FileAction;
use super::do_wait4::WaitOptions; use super::do_wait4::WaitOptions;
use super::pgrp::*;
use super::prctl::PrctlCmd; use super::prctl::PrctlCmd;
use super::process::ProcessFilter; use super::process::ProcessFilter;
use super::spawn_attribute::{clone_spawn_atrributes_safely, posix_spawnattr_t, SpawnAttr}; use super::spawn_attribute::{clone_spawn_atrributes_safely, posix_spawnattr_t, SpawnAttr};
@ -393,11 +394,44 @@ pub fn do_getppid() -> Result<isize> {
Ok(ppid as isize) Ok(ppid as isize)
} }
pub fn do_getpgid() -> Result<isize> { pub fn do_getpgrp() -> Result<isize> {
let pgid = super::do_getpid::do_getpgid(); do_getpgid(0)
}
pub fn do_getpgid(pid: i32) -> Result<isize> {
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) Ok(pgid as isize)
} }
pub fn do_setpgid(pid: i32, pgid: i32) -> Result<isize> {
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 // TODO: implement uid, gid, euid, egid
pub fn do_getuid() -> Result<isize> { pub fn do_getuid() -> Result<isize> {

@ -1,6 +1,31 @@
use super::{ProcessRef, ThreadRef}; use super::{ProcessGrpRef, ProcessRef, ThreadRef};
use crate::prelude::*; use crate::prelude::*;
pub fn get_pgrp(pgid: pid_t) -> Result<ProcessGrpRef> {
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<ProcessGrpRef> {
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<ProcessGrpRef> {
PROCESSGRP_TABLE
.lock()
.unwrap()
.iter()
.map(|(_, pgrp_ref)| pgrp_ref.clone())
.collect()
}
pub fn get_process(pid: pid_t) -> Result<ProcessRef> { pub fn get_process(pid: pid_t) -> Result<ProcessRef> {
PROCESS_TABLE.lock().unwrap().get(pid) PROCESS_TABLE.lock().unwrap().get(pid)
} }
@ -64,6 +89,8 @@ lazy_static! {
{ SgxMutex::new(Table::<ProcessRef>::with_capacity(8)) }; { SgxMutex::new(Table::<ProcessRef>::with_capacity(8)) };
static ref THREAD_TABLE: SgxMutex<Table<ThreadRef>> = static ref THREAD_TABLE: SgxMutex<Table<ThreadRef>> =
{ SgxMutex::new(Table::<ThreadRef>::with_capacity(8)) }; { SgxMutex::new(Table::<ThreadRef>::with_capacity(8)) };
static ref PROCESSGRP_TABLE: SgxMutex<Table<ProcessGrpRef>> =
{ SgxMutex::new(Table::<ProcessGrpRef>::with_capacity(4)) };
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -78,6 +105,10 @@ impl<I: Debug + Clone + Send + Sync> Table<I> {
} }
} }
pub fn len(&self) -> usize {
self.map.len()
}
pub fn iter(&self) -> std::collections::hash_map::Iter<'_, pid_t, I> { pub fn iter(&self) -> std::collections::hash_map::Iter<'_, pid_t, I> {
self.map.iter() self.map.iter()
} }

@ -56,11 +56,8 @@ fn get_processes(filter: &ProcessFilter) -> Result<Vec<ProcessRef>> {
vec![process] vec![process]
} }
ProcessFilter::WithPgid(pgid) => { ProcessFilter::WithPgid(pgid) => {
// TODO: implement O(1) lookup for a process group let pgrp = table::get_pgrp(*pgid)?;
let processes: Vec<ProcessRef> = table::get_all_processes() let processes = pgrp.get_all_processes();
.into_iter()
.filter(|proc_ref| proc_ref.pgid() == *pgid)
.collect();
if processes.len() == 0 { if processes.len() == 0 {
return_errno!(EINVAL, "invalid pgid"); return_errno!(EINVAL, "invalid pgid");
} }

@ -43,10 +43,10 @@ use crate::net::{
}; };
use crate::process::{ use crate::process::{
do_arch_prctl, do_clone, do_execve, do_exit, do_exit_group, do_futex, do_get_robust_list, 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_getegid, do_geteuid, do_getgid, do_getgroups, do_getpgid, do_getpgrp, do_getpid, do_getppid,
do_getuid, do_prctl, do_set_robust_list, do_set_tid_address, do_spawn_for_glibc, do_gettid, do_getuid, do_prctl, do_set_robust_list, do_set_tid_address, do_setpgid,
do_spawn_for_musl, do_wait4, pid_t, posix_spawnattr_t, FdOp, RobustListHead, SpawnFileActions, do_spawn_for_glibc, do_spawn_for_musl, do_wait4, pid_t, posix_spawnattr_t, FdOp,
ThreadStatus, RobustListHead, SpawnFileActions, ThreadStatus,
}; };
use crate::sched::{do_getcpu, do_sched_getaffinity, do_sched_setaffinity, do_sched_yield}; use crate::sched::{do_getcpu, do_sched_getaffinity, do_sched_setaffinity, do_sched_yield};
use crate::signal::{ use crate::signal::{
@ -197,9 +197,9 @@ macro_rules! process_syscall_table_with_callback {
(Setgid = 106) => handle_unsupported(), (Setgid = 106) => handle_unsupported(),
(Geteuid = 107) => do_geteuid(), (Geteuid = 107) => do_geteuid(),
(Getegid = 108) => do_getegid(), (Getegid = 108) => do_getegid(),
(Setpgid = 109) => handle_unsupported(), (Setpgid = 109) => do_setpgid(pid: i32, pgid: i32),
(Getppid = 110) => do_getppid(), (Getppid = 110) => do_getppid(),
(Getpgrp = 111) => handle_unsupported(), (Getpgrp = 111) => do_getpgrp(),
(Setsid = 112) => handle_unsupported(), (Setsid = 112) => handle_unsupported(),
(Setreuid = 113) => handle_unsupported(), (Setreuid = 113) => handle_unsupported(),
(Setregid = 114) => handle_unsupported(), (Setregid = 114) => handle_unsupported(),
@ -209,7 +209,7 @@ macro_rules! process_syscall_table_with_callback {
(Getresuid = 118) => handle_unsupported(), (Getresuid = 118) => handle_unsupported(),
(Setresgid = 119) => handle_unsupported(), (Setresgid = 119) => handle_unsupported(),
(Getresgid = 120) => handle_unsupported(), (Getresgid = 120) => handle_unsupported(),
(Getpgid = 121) => do_getpgid(), (Getpgid = 121) => do_getpgid(pid: i32),
(Setfsuid = 122) => handle_unsupported(), (Setfsuid = 122) => handle_unsupported(),
(Setfsgid = 123) => handle_unsupported(), (Setfsgid = 123) => handle_unsupported(),
(Getsid = 124) => handle_unsupported(), (Getsid = 124) => handle_unsupported(),

@ -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 \ 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 \ 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 \ 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 # Benchmarks: need to be compiled and run by bench-% target
BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput

@ -2,7 +2,7 @@
"resource_limits": { "resource_limits": {
"kernel_space_heap_size": "40MB", "kernel_space_heap_size": "40MB",
"kernel_space_stack_size": "1MB", "kernel_space_stack_size": "1MB",
"user_space_size": "420MB", "user_space_size": "500MB",
"max_num_of_threads": 32 "max_num_of_threads": 32
}, },
"process": { "process": {

@ -5,7 +5,8 @@
#include <sys/syscall.h> #include <sys/syscall.h>
int main(int argc, const char *argv[]) { 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)); printf("tid = %ld\n", syscall(SYS_gettid));
return 0; return 0;
} }

5
test/pgrp/Makefile Normal file

@ -0,0 +1,5 @@
include ../test_common.mk
EXTRA_C_FLAGS := -g
EXTRA_LINK_FLAGS :=
BIN_ARGS :=

304
test/pgrp/main.c Normal file

@ -0,0 +1,304 @@
#include <unistd.h>
#include <spawn.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <assert.h>
#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;
}