Forráskód Böngészése

proc: rewrite process list organization

- Use intrusive lists to store and organize the process hierarchy.
- Remove `FileArray::open_console()`. Do it in the init script instead.
- Fix open logic: acquire controlling terminals only if O_NOCTTY is not
  set. Put this into TerminalFile::open().
- Send SIGHUP and then SIGCONT to foreground pgroup procs when the
  controlling terminal is dropped.
- Set the controlling terminal of sessions in Terminal.
- Limit max line width to 80. Format some codes.

Signed-off-by: greatbridf <greatbridf@icloud.com>
greatbridf 2 hete
szülő
commit
997edb05a2

+ 2 - 2
.rustfmt.toml

@@ -1,4 +1,4 @@
-max_width = 100
+max_width = 80
 hard_tabs = false
 tab_spaces = 4
 newline_style = "Auto"
@@ -14,7 +14,7 @@ single_line_if_else_max_width = 60
 single_line_let_else_max_width = 60
 wrap_comments = false
 format_code_in_doc_comments = false
-doc_comment_code_block_width = 100
+doc_comment_code_block_width = 80
 comment_width = 80
 normalize_comments = false
 normalize_doc_attributes = false

+ 3 - 1
Cargo.toml

@@ -27,8 +27,10 @@ pointers = { path = "./crates/pointers" }
 posix_types = { path = "./crates/posix_types" }
 slab_allocator = { path = "./crates/slab_allocator" }
 
+intrusive-collections = { version = "0.9.8", features = [
+    "nightly",
+], git = "https://github.com/greatbridf/intrusive-rs" }
 bitflags = "2.6.0"
-intrusive-collections = { version = "0.9.8", git = "https://github.com/greatbridf/intrusive-rs" }
 itertools = { version = "0.13.0", default-features = false }
 acpi = "5.2.0"
 align_ext = "0.1.0"

+ 5 - 1
crates/posix_types/src/open.rs

@@ -11,6 +11,8 @@ bitflags! {
         const O_CREAT = 0x40;
         /// Exclusive access, fail if file exists
         const O_EXCL = 0x80;
+        /// Don't set controlling terminal.
+        const O_NOCTTY = 0x100;
         /// Truncate file to zero length if it exists
         const O_TRUNC = 0x200;
         /// Open file in append mode
@@ -116,6 +118,8 @@ impl AtFlags {
     }
 
     pub fn statx_default_sync(&self) -> bool {
-        !self.intersects(AtFlags::AT_STATX_FORCE_SYNC | AtFlags::AT_STATX_DONT_SYNC)
+        !self.intersects(
+            AtFlags::AT_STATX_FORCE_SYNC | AtFlags::AT_STATX_DONT_SYNC,
+        )
     }
 }

+ 42 - 42
src/kernel/chardev.rs

@@ -1,22 +1,18 @@
-use super::{
-    console::get_console,
-    constants::{EEXIST, EIO},
-    task::{block_on, ProcessList, Thread},
-    terminal::Terminal,
-    vfs::{types::DeviceId, File, FileType, TerminalFile},
-};
-use crate::{
-    io::{Buffer, Stream, StreamRead},
-    prelude::*,
-};
-use alloc::{
-    boxed::Box,
-    collections::btree_map::{BTreeMap, Entry},
-    sync::Arc,
-};
-use eonix_sync::AsProof as _;
+use alloc::boxed::Box;
+use alloc::collections::btree_map::{BTreeMap, Entry};
+use alloc::sync::Arc;
+
 use posix_types::open::OpenFlags;
 
+use super::console::get_console;
+use super::constants::{EEXIST, EIO};
+use super::task::{block_on, Thread};
+use super::terminal::Terminal;
+use super::vfs::types::DeviceId;
+use super::vfs::{File, FileType, TerminalFile};
+use crate::io::{Buffer, Stream, StreamRead};
+use crate::prelude::*;
+
 pub trait VirtualCharDevice: Send + Sync {
     fn read(&self, buffer: &mut dyn Buffer) -> KResult<usize>;
     fn write(&self, stream: &mut dyn Stream) -> KResult<usize>;
@@ -33,12 +29,15 @@ pub struct CharDevice {
     device: CharDeviceType,
 }
 
-static CHAR_DEVICES: Spin<BTreeMap<DeviceId, Arc<CharDevice>>> = Spin::new(BTreeMap::new());
+static CHAR_DEVICES: Spin<BTreeMap<DeviceId, Arc<CharDevice>>> =
+    Spin::new(BTreeMap::new());
 
 impl CharDevice {
     pub fn read(&self, buffer: &mut dyn Buffer) -> KResult<usize> {
         match &self.device {
-            CharDeviceType::Terminal(terminal) => block_on(terminal.read(buffer)),
+            CharDeviceType::Terminal(terminal) => {
+                block_on(terminal.read(buffer))
+            }
             CharDeviceType::Virtual(device) => device.read(buffer),
         }
     }
@@ -46,10 +45,12 @@ impl CharDevice {
     pub fn write(&self, stream: &mut dyn Stream) -> KResult<usize> {
         match &self.device {
             CharDeviceType::Virtual(device) => device.write(stream),
-            CharDeviceType::Terminal(terminal) => stream.read_till_end(&mut [0; 128], |data| {
-                terminal.write(data);
-                Ok(())
-            }),
+            CharDeviceType::Terminal(terminal) => {
+                stream.read_till_end(&mut [0; 128], |data| {
+                    terminal.write(data);
+                    Ok(())
+                })
+            }
         }
     }
 
@@ -57,7 +58,11 @@ impl CharDevice {
         CHAR_DEVICES.lock().get(&devid).cloned()
     }
 
-    pub fn register(devid: DeviceId, name: Arc<str>, device: CharDeviceType) -> KResult<()> {
+    pub fn register(
+        devid: DeviceId,
+        name: Arc<str>,
+        device: CharDeviceType,
+    ) -> KResult<()> {
         match CHAR_DEVICES.lock().entry(devid) {
             Entry::Vacant(entry) => {
                 entry.insert(Arc::new(CharDevice { name, device }));
@@ -67,26 +72,21 @@ impl CharDevice {
         }
     }
 
-    pub fn open(self: &Arc<Self>, flags: OpenFlags) -> KResult<File> {
-        Ok(match &self.device {
+    pub async fn open(
+        self: &Arc<Self>,
+        thread: &Thread,
+        flags: OpenFlags,
+    ) -> KResult<File> {
+        let file = match &self.device {
+            CharDeviceType::Virtual(_) => {
+                File::new(flags, FileType::CharDev(self.clone()))
+            }
             CharDeviceType::Terminal(terminal) => {
-                let procs = block_on(ProcessList::get().read());
-                let current = Thread::current();
-                let session = current.process.session(procs.prove());
-                // We only set the control terminal if the process is the session leader.
-                if session.sid == Thread::current().process.pid {
-                    // Silently fail if we can't set the control terminal.
-                    dont_check!(block_on(session.set_control_terminal(
-                        &terminal,
-                        false,
-                        procs.prove()
-                    )));
-                }
-
-                TerminalFile::new(terminal.clone(), flags)
+                TerminalFile::open(thread, terminal, flags).await
             }
-            CharDeviceType::Virtual(_) => File::new(flags, FileType::CharDev(self.clone())),
-        })
+        };
+
+        Ok(file)
     }
 }
 

+ 114 - 30
src/kernel/syscall/file_rw.rs

@@ -12,7 +12,8 @@ use posix_types::syscall_no::*;
 use super::{FromSyscallArg, User};
 use crate::io::{Buffer, BufferFill, IntoStream};
 use crate::kernel::constants::{
-    EBADF, EFAULT, EINVAL, ENOENT, ENOSYS, ENOTDIR, SEEK_CUR, SEEK_END, SEEK_SET,
+    EBADF, EFAULT, EINVAL, ENOENT, ENOSYS, ENOTDIR, SEEK_CUR, SEEK_END,
+    SEEK_SET,
 };
 use crate::kernel::syscall::UserMut;
 use crate::kernel::task::Thread;
@@ -61,7 +62,13 @@ async fn dentry_from(
             let dir_file = thread.files.get(dirfd).ok_or(EBADF)?;
             let dir_dentry = dir_file.as_path().ok_or(ENOTDIR)?;
 
-            Dentry::open_at(&thread.fs_context, dir_dentry, path, follow_symlink).await
+            Dentry::open_at(
+                &thread.fs_context,
+                dir_dentry,
+                path,
+                follow_symlink,
+            )
+            .await
         }
     }
 }
@@ -79,7 +86,12 @@ async fn read(fd: FD, buffer: UserMut<u8>, bufsize: usize) -> KResult<usize> {
 }
 
 #[eonix_macros::define_syscall(SYS_PREAD64)]
-async fn pread64(fd: FD, buffer: UserMut<u8>, bufsize: usize, offset: usize) -> KResult<usize> {
+async fn pread64(
+    fd: FD,
+    buffer: UserMut<u8>,
+    bufsize: usize,
+    offset: usize,
+) -> KResult<usize> {
     let mut buffer = UserBuffer::new(buffer, bufsize)?;
 
     thread
@@ -104,7 +116,12 @@ async fn write(fd: FD, buffer: User<u8>, count: usize) -> KResult<usize> {
 }
 
 #[eonix_macros::define_syscall(SYS_PWRITE64)]
-async fn pwrite64(fd: FD, buffer: User<u8>, count: usize, offset: usize) -> KResult<usize> {
+async fn pwrite64(
+    fd: FD,
+    buffer: User<u8>,
+    count: usize,
+    offset: usize,
+) -> KResult<usize> {
     let buffer = CheckedUserPointer::new(buffer, count)?;
     let mut stream = buffer.into_stream();
 
@@ -117,11 +134,17 @@ async fn pwrite64(fd: FD, buffer: User<u8>, count: usize, offset: usize) -> KRes
 }
 
 #[eonix_macros::define_syscall(SYS_OPENAT)]
-async fn openat(dirfd: FD, pathname: User<u8>, flags: OpenFlags, mode: Mode) -> KResult<FD> {
-    let dentry = dentry_from(thread, dirfd, pathname, flags.follow_symlink()).await?;
+async fn openat(
+    dirfd: FD,
+    pathname: User<u8>,
+    flags: OpenFlags,
+    mode: Mode,
+) -> KResult<FD> {
+    let dentry =
+        dentry_from(thread, dirfd, pathname, flags.follow_symlink()).await?;
     let perm = mode.perm().mask_with(*thread.fs_context.umask.lock());
 
-    thread.files.open(&dentry, flags, perm).await
+    thread.files.open(thread, &dentry, flags, perm).await
 }
 
 #[cfg(target_arch = "x86_64")]
@@ -156,7 +179,8 @@ async fn dup3(old_fd: FD, new_fd: FD, flags: OpenFlags) -> KResult<FD> {
 
 #[eonix_macros::define_syscall(SYS_PIPE2)]
 async fn pipe2(pipe_fd: UserMut<[FD; 2]>, flags: OpenFlags) -> KResult<()> {
-    let mut buffer = UserBuffer::new(pipe_fd.cast(), core::mem::size_of::<[FD; 2]>())?;
+    let mut buffer =
+        UserBuffer::new(pipe_fd.cast(), core::mem::size_of::<[FD; 2]>())?;
     let (read_fd, write_fd) = thread.files.pipe(flags)?;
 
     buffer.copy(&[read_fd, write_fd])?.ok_or(EFAULT)
@@ -170,7 +194,11 @@ async fn pipe(pipe_fd: UserMut<[FD; 2]>) -> KResult<()> {
 
 #[cfg(target_arch = "x86_64")]
 #[eonix_macros::define_syscall(SYS_GETDENTS)]
-async fn getdents(fd: FD, buffer: UserMut<u8>, bufsize: usize) -> KResult<usize> {
+async fn getdents(
+    fd: FD,
+    buffer: UserMut<u8>,
+    bufsize: usize,
+) -> KResult<usize> {
     let mut buffer = UserBuffer::new(buffer, bufsize)?;
 
     thread
@@ -184,7 +212,11 @@ async fn getdents(fd: FD, buffer: UserMut<u8>, bufsize: usize) -> KResult<usize>
 }
 
 #[eonix_macros::define_syscall(SYS_GETDENTS64)]
-async fn getdents64(fd: FD, buffer: UserMut<u8>, bufsize: usize) -> KResult<usize> {
+async fn getdents64(
+    fd: FD,
+    buffer: UserMut<u8>,
+    bufsize: usize,
+) -> KResult<usize> {
     let mut buffer = UserBuffer::new(buffer, bufsize)?;
 
     thread
@@ -230,7 +262,8 @@ async fn newfstatat(
 )]
 #[cfg_attr(target_arch = "x86_64", eonix_macros::define_syscall(SYS_FSTAT64))]
 async fn newfstat(fd: FD, statbuf: UserMut<Stat>) -> KResult<()> {
-    sys_newfstatat(thread, fd, User::null(), statbuf, AtFlags::AT_EMPTY_PATH).await
+    sys_newfstatat(thread, fd, User::null(), statbuf, AtFlags::AT_EMPTY_PATH)
+        .await
 }
 
 #[eonix_macros::define_syscall(SYS_STATX)]
@@ -307,7 +340,11 @@ async fn unlink(pathname: User<u8>) -> KResult<()> {
 }
 
 #[eonix_macros::define_syscall(SYS_SYMLINKAT)]
-async fn symlinkat(target: User<u8>, dirfd: FD, linkpath: User<u8>) -> KResult<()> {
+async fn symlinkat(
+    target: User<u8>,
+    dirfd: FD,
+    linkpath: User<u8>,
+) -> KResult<()> {
     let target = UserString::new(target)?;
     let dentry = dentry_from(thread, dirfd, linkpath, false).await?;
 
@@ -341,7 +378,12 @@ impl UserDeviceId {
 }
 
 #[eonix_macros::define_syscall(SYS_MKNODAT)]
-async fn mknodat(dirfd: FD, pathname: User<u8>, mut mode: Mode, dev: UserDeviceId) -> KResult<()> {
+async fn mknodat(
+    dirfd: FD,
+    pathname: User<u8>,
+    mut mode: Mode,
+    dev: UserDeviceId,
+) -> KResult<()> {
     if !mode.is_blk() && !mode.is_chr() {
         return Err(EINVAL);
     }
@@ -354,7 +396,11 @@ async fn mknodat(dirfd: FD, pathname: User<u8>, mut mode: Mode, dev: UserDeviceI
 
 #[cfg(target_arch = "x86_64")]
 #[eonix_macros::define_syscall(SYS_MKNOD)]
-async fn mknod(pathname: User<u8>, mode: Mode, dev: UserDeviceId) -> KResult<()> {
+async fn mknod(
+    pathname: User<u8>,
+    mode: Mode,
+    dev: UserDeviceId,
+) -> KResult<()> {
     sys_mknodat(thread, FD::AT_FDCWD, pathname, mode, dev).await
 }
 
@@ -373,11 +419,20 @@ async fn readlinkat(
 
 #[cfg(target_arch = "x86_64")]
 #[eonix_macros::define_syscall(SYS_READLINK)]
-async fn readlink(pathname: User<u8>, buffer: UserMut<u8>, bufsize: usize) -> KResult<usize> {
+async fn readlink(
+    pathname: User<u8>,
+    buffer: UserMut<u8>,
+    bufsize: usize,
+) -> KResult<usize> {
     sys_readlinkat(thread, FD::AT_FDCWD, pathname, buffer, bufsize).await
 }
 
-async fn do_lseek(thread: &Thread, fd: FD, offset: u64, whence: u32) -> KResult<u64> {
+async fn do_lseek(
+    thread: &Thread,
+    fd: FD,
+    offset: u64,
+    whence: u32,
+) -> KResult<u64> {
     let file = thread.files.get(fd).ok_or(EBADF)?;
 
     Ok(match whence {
@@ -403,7 +458,8 @@ async fn llseek(
     result: UserMut<u64>,
     whence: u32,
 ) -> KResult<()> {
-    let mut result = UserBuffer::new(result.cast(), core::mem::size_of::<u64>())?;
+    let mut result =
+        UserBuffer::new(result.cast(), core::mem::size_of::<u64>())?;
     let offset = ((offset_high as u64) << 32) | (offset_low as u64);
 
     let new_offset = do_lseek(thread, fd, offset, whence).await?;
@@ -434,9 +490,10 @@ async fn readv(fd: FD, iov_user: User<IoVec>, iovcnt: u32) -> KResult<usize> {
             Ok(IoVec {
                 len: Long::ZERO, ..
             }) => None,
-            Ok(IoVec { base, len }) => {
-                Some(UserBuffer::new(UserMut::with_addr(base.addr()), len.get()))
-            }
+            Ok(IoVec { base, len }) => Some(UserBuffer::new(
+                UserMut::with_addr(base.addr()),
+                len.get(),
+            )),
         })
         .collect::<KResult<Vec<_>>>()?;
 
@@ -471,8 +528,11 @@ async fn writev(fd: FD, iov_user: User<IoVec>, iovcnt: u32) -> KResult<usize> {
                 len: Long::ZERO, ..
             }) => None,
             Ok(IoVec { base, len }) => Some(
-                CheckedUserPointer::new(User::with_addr(base.addr()), len.get())
-                    .map(|ptr| ptr.into_stream()),
+                CheckedUserPointer::new(
+                    User::with_addr(base.addr()),
+                    len.get(),
+                )
+                .map(|ptr| ptr.into_stream()),
             ),
         })
         .collect::<KResult<Vec<_>>>()?;
@@ -491,7 +551,12 @@ async fn writev(fd: FD, iov_user: User<IoVec>, iovcnt: u32) -> KResult<usize> {
 }
 
 #[eonix_macros::define_syscall(SYS_FACCESSAT)]
-async fn faccessat(dirfd: FD, pathname: User<u8>, _mode: u32, flags: AtFlags) -> KResult<()> {
+async fn faccessat(
+    dirfd: FD,
+    pathname: User<u8>,
+    _mode: u32,
+    flags: AtFlags,
+) -> KResult<()> {
     let dentry = if flags.at_empty_path() {
         let file = thread.files.get(dirfd).ok_or(EBADF)?;
         file.as_path().ok_or(EBADF)?.clone()
@@ -522,7 +587,12 @@ async fn access(pathname: User<u8>, mode: u32) -> KResult<()> {
 }
 
 #[eonix_macros::define_syscall(SYS_SENDFILE64)]
-async fn sendfile64(out_fd: FD, in_fd: FD, offset: UserMut<u8>, count: usize) -> KResult<usize> {
+async fn sendfile64(
+    out_fd: FD,
+    in_fd: FD,
+    offset: UserMut<u8>,
+    count: usize,
+) -> KResult<usize> {
     let in_file = thread.files.get(in_fd).ok_or(EBADF)?;
     let out_file = thread.files.get(out_fd).ok_or(EBADF)?;
 
@@ -627,7 +697,11 @@ async fn pselect6(
 
 #[cfg(target_arch = "x86_64")]
 #[eonix_macros::define_syscall(SYS_POLL)]
-async fn poll(fds: UserMut<UserPollFd>, nfds: u32, timeout: u32) -> KResult<u32> {
+async fn poll(
+    fds: UserMut<UserPollFd>,
+    nfds: u32,
+    timeout: u32,
+) -> KResult<u32> {
     do_poll(thread, fds, nfds, timeout).await
 }
 
@@ -639,7 +713,8 @@ async fn fchownat(
     gid: u32,
     flags: AtFlags,
 ) -> KResult<()> {
-    let dentry = dentry_from(thread, dirfd, pathname, !flags.no_follow()).await?;
+    let dentry =
+        dentry_from(thread, dirfd, pathname, !flags.no_follow()).await?;
     if !dentry.is_valid() {
         return Err(ENOENT);
     }
@@ -648,7 +723,12 @@ async fn fchownat(
 }
 
 #[eonix_macros::define_syscall(SYS_FCHMODAT)]
-async fn fchmodat(dirfd: FD, pathname: User<u8>, mode: Mode, flags: AtFlags) -> KResult<()> {
+async fn fchmodat(
+    dirfd: FD,
+    pathname: User<u8>,
+    mode: Mode,
+    flags: AtFlags,
+) -> KResult<()> {
     let dentry = if flags.at_empty_path() {
         let file = thread.files.get(dirfd).ok_or(EBADF)?;
         file.as_path().ok_or(EBADF)?.clone()
@@ -709,12 +789,16 @@ async fn renameat2(
     let flags = RenameFlags::from_bits(flags).ok_or(EINVAL)?;
 
     // The two flags RENAME_NOREPLACE and RENAME_EXCHANGE are mutually exclusive.
-    if flags.contains(RenameFlags::RENAME_NOREPLACE | RenameFlags::RENAME_EXCHANGE) {
+    if flags
+        .contains(RenameFlags::RENAME_NOREPLACE | RenameFlags::RENAME_EXCHANGE)
+    {
         Err(EINVAL)?;
     }
 
-    let old_dentry = dentry_from(thread, old_dirfd, old_pathname, false).await?;
-    let new_dentry = dentry_from(thread, new_dirfd, new_pathname, false).await?;
+    let old_dentry =
+        dentry_from(thread, old_dirfd, old_pathname, false).await?;
+    let new_dentry =
+        dentry_from(thread, new_dirfd, new_pathname, false).await?;
 
     old_dentry.rename(&new_dentry, flags).await
 }

+ 177 - 93
src/kernel/task/process.rs

@@ -1,22 +1,24 @@
-use alloc::collections::btree_map::BTreeMap;
 use alloc::collections::vec_deque::VecDeque;
-use alloc::sync::{Arc, Weak};
+use alloc::sync::Arc;
 use core::sync::atomic::{AtomicU32, Ordering};
 
 use eonix_sync::{
-    AsProof as _, AsProofMut as _, Locked, Proof, ProofMut, RwLockReadGuard, SpinGuard,
-    UnlockableGuard as _, UnlockedGuard as _,
+    AsProof as _, AsProofMut as _, Locked, Proof, ProofMut, RwLockReadGuard,
+    SpinGuard, UnlockableGuard as _, UnlockedGuard as _,
+};
+use intrusive_collections::{
+    intrusive_adapter, KeyAdapter, RBTree, RBTreeAtomicLink,
 };
 use pointers::BorrowedArc;
 use posix_types::constants::{
-    CLD_CONTINUED, CLD_DUMPED, CLD_EXITED, CLD_KILLED, CLD_STOPPED, P_ALL, P_PGID, P_PID, P_PIDFD,
+    CLD_CONTINUED, CLD_DUMPED, CLD_EXITED, CLD_KILLED, CLD_STOPPED, P_ALL,
+    P_PGID, P_PID, P_PIDFD,
 };
 use posix_types::signal::Signal;
 use posix_types::SIGNAL_COREDUMP;
 
-use super::process_group::ProcessGroupBuilder;
 use super::signal::RaiseResult;
-use super::thread::ThreadBuilder;
+use super::thread::{ProcessThreads, ThreadBuilder};
 use super::{ProcessGroup, ProcessList, Session, Thread};
 use crate::kernel::constants::{ECHILD, EINTR, EINVAL, EPERM, ESRCH};
 use crate::kernel::mem::MMList;
@@ -35,7 +37,6 @@ pub struct ProcessBuilder {
     pid: Option<u32>,
 }
 
-#[derive(Debug)]
 pub struct Process {
     /// Process id
     ///
@@ -66,14 +67,55 @@ pub struct Process {
     /// The only case where it may be `None` is when the process is kernel thread.
     pub(super) session: RCUPointer<Session>,
 
-    /// All things related to the process list.
-    pub(super) inner: Locked<ProcessInner, ProcessList>,
+    pub children: Locked<RBTree<ProcessChildren>, ProcessList>,
+    pub threads: Locked<RBTree<ProcessThreads>, ProcessList>,
+
+    all_procs_link: RBTreeAtomicLink,
+    group_procs_link: RBTreeAtomicLink,
+    siblings_link: RBTreeAtomicLink,
 }
 
-#[derive(Debug)]
-pub(super) struct ProcessInner {
-    pub(super) children: BTreeMap<u32, Weak<Process>>,
-    pub(super) threads: BTreeMap<u32, Weak<Thread>>,
+intrusive_adapter!(pub AllProcs = Arc<Process>: Process {
+    all_procs_link: RBTreeAtomicLink
+});
+intrusive_adapter!(pub GroupProcs = Arc<Process>: Process {
+    group_procs_link: RBTreeAtomicLink
+});
+intrusive_adapter!(pub ProcessChildren = Arc<Process>: Process {
+    siblings_link: RBTreeAtomicLink
+});
+
+impl KeyAdapter<'_> for AllProcs {
+    type Key = u32;
+
+    fn get_key(
+        &self,
+        value: &'_ <Self::PointerOps as intrusive_collections::PointerOps>::Value,
+    ) -> Self::Key {
+        value.pid
+    }
+}
+
+impl KeyAdapter<'_> for GroupProcs {
+    type Key = u32;
+
+    fn get_key(
+        &self,
+        value: &'_ <Self::PointerOps as intrusive_collections::PointerOps>::Value,
+    ) -> Self::Key {
+        value.pid
+    }
+}
+
+impl KeyAdapter<'_> for ProcessChildren {
+    type Key = u32;
+
+    fn get_key(
+        &self,
+        value: &'_ <Self::PointerOps as intrusive_collections::PointerOps>::Value,
+    ) -> Self::Key {
+        value.pid
+    }
 }
 
 #[derive(Debug)]
@@ -148,7 +190,9 @@ impl WaitType {
     pub fn to_wstatus(self) -> u32 {
         match self {
             WaitType::Exited(status) => (status & 0xff) << 8,
-            WaitType::Signaled(signal @ SIGNAL_COREDUMP!()) => signal.into_raw() | 0x80,
+            WaitType::Signaled(signal @ SIGNAL_COREDUMP!()) => {
+                signal.into_raw() | 0x80
+            }
             WaitType::Signaled(signal) => signal.into_raw(),
             WaitType::Stopped(signal) => 0x7f | (signal.into_raw() << 8),
             WaitType::Continued => 0xffff,
@@ -159,7 +203,9 @@ impl WaitType {
         // TODO: CLD_TRAPPED
         match self {
             WaitType::Exited(status) => (status, CLD_EXITED),
-            WaitType::Signaled(signal @ SIGNAL_COREDUMP!()) => (signal.into_raw(), CLD_DUMPED),
+            WaitType::Signaled(signal @ SIGNAL_COREDUMP!()) => {
+                (signal.into_raw(), CLD_DUMPED)
+            }
             WaitType::Signaled(signal) => (signal.into_raw(), CLD_KILLED),
             WaitType::Stopped(signal) => (signal.into_raw(), CLD_STOPPED),
             WaitType::Continued => (Signal::SIGCONT.into_raw(), CLD_CONTINUED),
@@ -194,7 +240,11 @@ impl ProcessBuilder {
         }
     }
 
-    pub async fn clone_from(mut self, process: Arc<Process>, clone_args: &CloneArgs) -> Self {
+    pub async fn clone_from(
+        mut self,
+        process: Arc<Process>,
+        clone_args: &CloneArgs,
+    ) -> Self {
         let mm_list = if clone_args.flags.contains(CloneFlags::CLONE_VM) {
             process.mm_list.new_shared().await
         } else {
@@ -243,7 +293,10 @@ impl ProcessBuilder {
         self
     }
 
-    pub fn build(self, process_list: &mut ProcessList) -> (Arc<Thread>, Arc<Process>) {
+    pub fn build(
+        self,
+        process_list: &mut ProcessList,
+    ) -> (Arc<Thread>, Arc<Process>) {
         let mm_list = self.mm_list.unwrap_or_else(|| MMList::new());
 
         let process = Arc::new(Process {
@@ -254,18 +307,23 @@ impl ProcessBuilder {
             parent: RCUPointer::empty(),
             pgroup: RCUPointer::empty(),
             session: RCUPointer::empty(),
-            inner: Locked::new(
-                ProcessInner {
-                    children: BTreeMap::new(),
-                    threads: BTreeMap::new(),
-                },
+            children: Locked::new(
+                RBTree::new(ProcessChildren::NEW),
+                process_list,
+            ),
+            threads: Locked::new(
+                RBTree::new(ProcessThreads::NEW),
                 process_list,
             ),
+            all_procs_link: RBTreeAtomicLink::new(),
+            group_procs_link: RBTreeAtomicLink::new(),
+            siblings_link: RBTreeAtomicLink::new(),
         });
 
         process_list.add_process(&process);
 
-        let thread_builder = self.thread_builder.expect("Thread builder is not set");
+        let thread_builder =
+            self.thread_builder.expect("Thread builder is not set");
         let thread = thread_builder
             .process(process.clone())
             .tid(process.pid)
@@ -281,10 +339,7 @@ impl ProcessBuilder {
                 pgroup.add_member(&process, process_list.prove_mut());
                 pgroup
             }
-            None => ProcessGroupBuilder::new()
-                .leader(&process)
-                .session(session.clone())
-                .build(process_list),
+            None => ProcessGroup::new(&process, &session, process_list),
         };
 
         if let Some(parent) = &self.parent {
@@ -304,30 +359,30 @@ impl ProcessBuilder {
 
 impl Process {
     pub fn raise(&self, signal: Signal, procs: Proof<'_, ProcessList>) {
-        let inner = self.inner.access(procs);
-        for thread in inner.threads.values().map(|t| t.upgrade().unwrap()) {
+        let threads = self.threads.access(procs);
+        for thread in threads.iter() {
             if let RaiseResult::Finished = thread.raise(signal) {
                 break;
             }
         }
     }
 
-    pub(super) fn add_child(&self, child: &Arc<Process>, procs: ProofMut<'_, ProcessList>) {
-        assert!(self
-            .inner
-            .access_mut(procs)
-            .children
-            .insert(child.pid, Arc::downgrade(child))
-            .is_none());
+    pub fn add_child(
+        &self,
+        child: &Arc<Process>,
+        procs: ProofMut<'_, ProcessList>,
+    ) {
+        assert!(self.all_procs_link.is_linked(), "Dead process");
+        self.children.access_mut(procs).insert(child.clone());
     }
 
-    pub(super) fn add_thread(&self, thread: &Arc<Thread>, procs: ProofMut<'_, ProcessList>) {
-        assert!(self
-            .inner
-            .access_mut(procs)
-            .threads
-            .insert(thread.tid, Arc::downgrade(thread))
-            .is_none());
+    pub fn add_thread(
+        &self,
+        thread: &Arc<Thread>,
+        procs: ProofMut<'_, ProcessList>,
+    ) {
+        assert!(self.all_procs_link.is_linked(), "Dead process");
+        self.threads.access_mut(procs).insert(thread.clone());
     }
 
     pub async fn wait(
@@ -354,12 +409,7 @@ impl Process {
                     break object;
                 }
 
-                if self
-                    .inner
-                    .access(waits.process_list.prove())
-                    .children
-                    .is_empty()
-                {
+                if self.children.access(waits.process_list.prove()).is_empty() {
                     return Err(ECHILD);
                 }
 
@@ -375,12 +425,12 @@ impl Process {
             Ok(Some(wait_object))
         } else {
             let mut procs = ProcessList::get().write().await;
-            procs.remove_process(wait_object.pid).await;
+            procs.remove_process(wait_object.pid);
             assert!(self
-                .inner
-                .access_mut(procs.prove_mut())
                 .children
-                .remove(&wait_object.pid)
+                .access_mut(procs.prove_mut())
+                .find_mut(&wait_object.pid)
+                .remove()
                 .is_some());
 
             Ok(Some(wait_object))
@@ -396,15 +446,17 @@ impl Process {
         if process_list.try_find_session(self.pid).is_some() {
             return Err(EPERM);
         }
+
+        self.pgroup(process_list.prove())
+            .remove_member(self, &mut process_list);
+
         let session = Session::new(self, &mut process_list);
-        let pgroup = ProcessGroupBuilder::new()
-            .leader(self)
-            .session(session.clone())
-            .build(&mut process_list);
+        let pgroup = ProcessGroup::new(self, &session, &mut process_list);
 
-        let old_session = unsafe { self.session.swap(Some(session.clone())) }.unwrap();
-        let old_pgroup = unsafe { self.pgroup.swap(Some(pgroup.clone())) }.unwrap();
-        old_pgroup.remove_member(self.pid, process_list.prove_mut());
+        let old_session =
+            unsafe { self.session.swap(Some(session.clone())) }.unwrap();
+        let old_pgroup =
+            unsafe { self.pgroup.swap(Some(pgroup.clone())) }.unwrap();
 
         call_rcu(move || {
             drop(old_session);
@@ -417,47 +469,56 @@ impl Process {
     /// Set the process group id of the process to `pgid`.
     ///
     /// This function does the actual work.
-    fn do_setpgid(self: &Arc<Self>, pgid: u32, procs: &mut ProcessList) -> KResult<()> {
+    fn do_setpgid(
+        self: &Arc<Self>,
+        pgid: u32,
+        procs: &mut ProcessList,
+    ) -> KResult<()> {
         // SAFETY: We are holding the process list lock.
         let session = unsafe { self.session.load_locked().unwrap() };
-        let pgroup = unsafe { self.pgroup.load_locked().unwrap() };
 
         // Changing the process group of a session leader is not allowed.
         if session.sid == self.pid {
             return Err(EPERM);
         }
 
-        let new_pgroup = if let Some(new_pgroup) = procs.try_find_pgroup(pgid) {
+        let cur_pgroup = self.pgroup(procs.prove()).clone();
+        let existing_pgroup = procs.try_find_pgroup(pgid);
+
+        if let Some(new_pgroup) = &existing_pgroup {
             // Move us to an existing process group.
             // Check that the two groups are in the same session.
-            if new_pgroup.session.upgrade().unwrap().sid != session.sid {
+            if new_pgroup.session.sid != session.sid {
                 return Err(EPERM);
             }
 
             // If we are already in the process group, we are done.
-            if new_pgroup.pgid == pgroup.pgid {
+            if new_pgroup.pgid == cur_pgroup.pgid {
                 return Ok(());
             }
-
-            new_pgroup.add_member(self, procs.prove_mut());
-
-            new_pgroup
         } else {
             // Create a new process group only if `pgid` matches our `pid`.
             if pgid != self.pid {
                 return Err(EPERM);
             }
+        }
 
-            ProcessGroupBuilder::new()
-                .leader(self)
-                .session(session.clone())
-                .build(procs)
-        };
+        // Permission checks done. Let's do the actual work.
+        cur_pgroup.remove_member(self, procs);
 
-        pgroup.remove_member(self.pid, procs.prove_mut());
+        let new_pgroup;
+        if let Some(pgroup) = existing_pgroup {
+            pgroup.add_member(self, procs.prove_mut());
+            new_pgroup = pgroup;
+        } else {
+            new_pgroup = ProcessGroup::new(self, &session, procs);
+        }
 
-        let old_pgroup = unsafe { self.pgroup.swap(Some(new_pgroup)) }.unwrap();
-        call_rcu(move || drop(old_pgroup));
+        unsafe {
+            // SAFETY: `cur_pgroup` held above.
+            self.pgroup.swap(Some(new_pgroup));
+        }
+        call_rcu(move || drop(cur_pgroup));
 
         Ok(())
     }
@@ -475,15 +536,14 @@ impl Process {
             let child = {
                 // If `pid` refers to one of our children, the thread leaders must be
                 // in out children list.
-                let children = &self.inner.access(procs.prove()).children;
-                let child = {
-                    let child = children.get(&pid);
-                    child.and_then(Weak::upgrade).ok_or(ESRCH)?
-                };
+                let children = self.children.access(procs.prove());
+                let child = children.find(&pid).clone_pointer().ok_or(ESRCH)?;
 
                 // Changing the process group of a child is only allowed
                 // if we are in the same session.
-                if child.session(procs.prove()).sid != self.session(procs.prove()).sid {
+                if child.session(procs.prove()).sid
+                    != self.session(procs.prove()).sid
+                {
                     return Err(EPERM);
                 }
 
@@ -497,19 +557,28 @@ impl Process {
     }
 
     /// Provide locked (consistent) access to the session.
-    pub fn session<'r>(&'r self, _procs: Proof<'r, ProcessList>) -> BorrowedArc<'r, Session> {
+    pub fn session<'r>(
+        &'r self,
+        _procs: Proof<'r, ProcessList>,
+    ) -> BorrowedArc<'r, Session> {
         // SAFETY: We are holding the process list lock.
         unsafe { self.session.load_locked() }.unwrap()
     }
 
     /// Provide locked (consistent) access to the process group.
-    pub fn pgroup<'r>(&'r self, _procs: Proof<'r, ProcessList>) -> BorrowedArc<'r, ProcessGroup> {
+    pub fn pgroup<'r>(
+        &'r self,
+        _procs: Proof<'r, ProcessList>,
+    ) -> BorrowedArc<'r, ProcessGroup> {
         // SAFETY: We are holding the process list lock.
         unsafe { self.pgroup.load_locked() }.unwrap()
     }
 
     /// Provide locked (consistent) access to the parent process.
-    pub fn parent<'r>(&'r self, _procs: Proof<'r, ProcessList>) -> BorrowedArc<'r, Process> {
+    pub fn parent<'r>(
+        &'r self,
+        _procs: Proof<'r, ProcessList>,
+    ) -> BorrowedArc<'r, Process> {
         // SAFETY: We are holding the process list lock.
         unsafe { self.parent.load_locked() }.unwrap()
     }
@@ -520,16 +589,25 @@ impl Process {
     }
 
     /// Provide RCU locked (maybe inconsistent) access to the process group.
-    pub fn pgroup_rcu(&self) -> RCUReadGuard<'_, BorrowedArc<'_, ProcessGroup>> {
+    pub fn pgroup_rcu(
+        &self,
+    ) -> RCUReadGuard<'_, BorrowedArc<'_, ProcessGroup>> {
         self.pgroup.load().unwrap()
     }
 
     /// Provide RCU locked (maybe inconsistent) access to the parent process.
-    pub fn parent_rcu(&self) -> Option<RCUReadGuard<'_, BorrowedArc<'_, Process>>> {
+    pub fn parent_rcu(
+        &self,
+    ) -> Option<RCUReadGuard<'_, BorrowedArc<'_, Process>>> {
         self.parent.load()
     }
 
-    pub fn notify(&self, signal: Option<Signal>, wait: WaitObject, procs: Proof<'_, ProcessList>) {
+    pub fn notify(
+        &self,
+        signal: Option<Signal>,
+        wait: WaitObject,
+        procs: Proof<'_, ProcessList>,
+    ) {
         self.wait_list.notify(wait);
 
         if let Some(signal) = signal {
@@ -607,8 +685,11 @@ impl Entry<'_, '_, '_> {
                 WaitId::Any => true,
                 WaitId::Pid(pid) => item.pid == pid,
                 WaitId::Pgid(pgid) => {
-                    if let Some(process) = self.process_list.try_find_process(item.pid) {
-                        return process.pgroup(self.process_list.prove()).pgid == pgid;
+                    if let Some(process) =
+                        self.process_list.try_find_process(item.pid)
+                    {
+                        return process.pgroup(self.process_list.prove()).pgid
+                            == pgid;
                     }
                     false
                 }
@@ -622,7 +703,10 @@ impl Entry<'_, '_, '_> {
         }
     }
 
-    pub fn wait(self, no_block: bool) -> impl core::future::Future<Output = KResult<Self>> + Send {
+    pub fn wait(
+        self,
+        no_block: bool,
+    ) -> impl core::future::Future<Output = KResult<Self>> + Send {
         let wait_procs = self.wait_procs.unlock();
 
         async move {

+ 90 - 56
src/kernel/task/process_group.rs

@@ -1,87 +1,121 @@
-use super::{Process, ProcessList, Session};
-use alloc::{
-    collections::btree_map::BTreeMap,
-    sync::{Arc, Weak},
+use alloc::sync::{Arc, Weak};
+
+use eonix_sync::{AsProofMut, Locked, Proof, ProofMut};
+use intrusive_collections::{
+    intrusive_adapter, KeyAdapter, RBTree, RBTreeAtomicLink,
 };
-use eonix_sync::{Locked, Proof, ProofMut};
 use posix_types::signal::Signal;
 
-pub struct ProcessGroupBuilder {
-    pgid: Option<u32>,
-    leader: Option<Weak<Process>>,
-    session: Option<Arc<Session>>,
-}
+use super::process::GroupProcs;
+use super::{Process, ProcessList, Session};
 
-#[derive(Debug)]
 pub struct ProcessGroup {
     pub pgid: u32,
-    pub _leader: Weak<Process>,
-    pub session: Weak<Session>,
+    pub leader: Weak<Process>,
+    pub session: Arc<Session>,
 
-    pub processes: Locked<BTreeMap<u32, Weak<Process>>, ProcessList>,
+    pub procs: Locked<RBTree<GroupProcs>, ProcessList>,
+
+    all_groups_link: RBTreeAtomicLink,
+    session_groups_link: RBTreeAtomicLink,
 }
 
-impl ProcessGroupBuilder {
-    pub const fn new() -> Self {
-        Self {
-            pgid: None,
-            leader: None,
-            session: None,
-        }
-    }
+intrusive_adapter!(pub AllGroups = Arc<ProcessGroup>: ProcessGroup {
+    all_groups_link: RBTreeAtomicLink
+});
+intrusive_adapter!(pub SessionGroups = Arc<ProcessGroup>: ProcessGroup {
+    session_groups_link: RBTreeAtomicLink
+});
+
+impl KeyAdapter<'_> for AllGroups {
+    type Key = u32;
 
-    pub fn leader(mut self, leader: &Arc<Process>) -> Self {
-        self.pgid = Some(leader.pid);
-        self.leader = Some(Arc::downgrade(leader));
-        self
+    fn get_key(
+        &self,
+        value: &'_ <Self::PointerOps as intrusive_collections::PointerOps>::Value,
+    ) -> Self::Key {
+        value.pgid
     }
+}
+
+impl KeyAdapter<'_> for SessionGroups {
+    type Key = u32;
 
-    pub fn session(mut self, session: Arc<Session>) -> Self {
-        self.session = Some(session);
-        self
+    fn get_key(
+        &self,
+        value: &'_ <Self::PointerOps as intrusive_collections::PointerOps>::Value,
+    ) -> Self::Key {
+        value.pgid
     }
+}
 
-    pub fn build(self, process_list: &mut ProcessList) -> Arc<ProcessGroup> {
-        let pgid = self.pgid.expect("PGID is not set");
-        let leader = self.leader.expect("Leader is not set");
-        let session = self.session.expect("Session is not set");
+impl ProcessGroup {
+    /// Create a pgroup and add it to the global pgroup list.
+    /// Add the pgroup to the session.
+    ///
+    /// # Panics
+    /// Panics if `leader` is already in some pgroup.
+    pub fn new(
+        leader: &Arc<Process>,
+        session: &Arc<Session>,
+        procs: &mut ProcessList,
+    ) -> Arc<Self> {
+        let pgid = leader.pid;
+        let pgroup_procs = {
+            let mut list = RBTree::new(GroupProcs::new());
+            list.insert(leader.clone());
+            list
+        };
 
         let pgroup = Arc::new(ProcessGroup {
             pgid,
-            session: Arc::downgrade(&session),
-            processes: Locked::new(BTreeMap::from([(pgid, leader.clone())]), process_list),
-            _leader: leader,
+            session: session.clone(),
+            procs: Locked::new(pgroup_procs, procs),
+            leader: Arc::downgrade(leader),
+            all_groups_link: RBTreeAtomicLink::new(),
+            session_groups_link: RBTreeAtomicLink::new(),
         });
 
-        process_list.add_pgroup(&pgroup);
-        session.add_member(process_list, &pgroup);
+        procs.add_pgroup(&pgroup);
+        session.add_member(&pgroup, procs.prove_mut());
         pgroup
     }
-}
 
-impl ProcessGroup {
-    pub(super) fn add_member(&self, process: &Arc<Process>, procs: ProofMut<'_, ProcessList>) {
-        assert!(self
-            .processes
-            .access_mut(procs)
-            .insert(process.pid, Arc::downgrade(process))
-            .is_none());
+    /// Add `process` to the pgroup.
+    ///
+    /// # Panics
+    /// Panics if `process` is already in some pgroup or the pgroup is dead.
+    pub fn add_member(
+        &self,
+        process: &Arc<Process>,
+        procs: ProofMut<'_, ProcessList>,
+    ) {
+        assert!(self.all_groups_link.is_linked(), "Dead pgroup");
+        self.procs.access_mut(procs).insert(process.clone());
     }
 
-    pub(super) fn remove_member(&self, pid: u32, procs: ProofMut<'_, ProcessList>) {
-        let processes = self.processes.access_mut(procs);
-        assert!(processes.remove(&pid).is_some());
-        if processes.is_empty() {
-            self.session
-                .upgrade()
-                .unwrap()
-                .remove_member(self.pgid, procs);
+    pub fn remove_member(
+        self: &Arc<Self>,
+        process: &Arc<Process>,
+        procs: &mut ProcessList,
+    ) {
+        let members = self.procs.access_mut(procs.prove_mut());
+        assert!(
+            members.find_mut(&process.pid).remove().is_some(),
+            "Not a member"
+        );
+
+        if !members.is_empty() {
+            return;
         }
+
+        self.session.remove_member(self, procs);
+        procs.remove_pgroup(self);
     }
 
     pub fn raise(&self, signal: Signal, procs: Proof<'_, ProcessList>) {
-        let processes = self.processes.access(procs);
-        for process in processes.values().map(|p| p.upgrade().unwrap()) {
+        let members = self.procs.access(procs);
+        for process in members.iter() {
             process.raise(signal, procs);
         }
     }

+ 86 - 65
src/kernel/task/process_list.rs

@@ -1,37 +1,41 @@
-use alloc::collections::btree_map::BTreeMap;
 use alloc::collections::vec_deque::VecDeque;
-use alloc::sync::{Arc, Weak};
+use alloc::sync::Arc;
 use core::pin::pin;
-use core::sync::atomic::Ordering;
 
 use eonix_runtime::scheduler::RUNTIME;
 use eonix_sync::{AsProof as _, AsProofMut as _, RwLock, Spin, WaitList};
+use intrusive_collections::RBTree;
 
 use super::loader::LoadInfo;
+use super::process::AllProcs;
+use super::process_group::AllGroups;
+use super::session::AllSessions;
+use super::thread::AllThreads;
 use super::{
-    alloc_pid, Process, ProcessBuilder, ProcessGroup, Session, Thread, ThreadBuilder, WaitObject,
+    alloc_pid, Process, ProcessBuilder, ProcessGroup, Session, Thread,
+    ThreadBuilder, WaitObject,
 };
-use crate::rcu::rcu_sync;
+use crate::rcu::call_rcu;
 
 pub struct ProcessList {
     /// The init process.
     init: Option<Arc<Process>>,
     /// All threads.
-    threads: BTreeMap<u32, Arc<Thread>>,
+    threads: RBTree<AllThreads>,
     /// All processes.
-    processes: BTreeMap<u32, Weak<Process>>,
+    procs: RBTree<AllProcs>,
     /// All process groups.
-    pgroups: BTreeMap<u32, Weak<ProcessGroup>>,
+    pgroups: RBTree<AllGroups>,
     /// All sessions.
-    sessions: BTreeMap<u32, Weak<Session>>,
+    sessions: RBTree<AllSessions>,
 }
 
 static GLOBAL_PROC_LIST: RwLock<ProcessList> = RwLock::new(ProcessList {
     init: None,
-    threads: BTreeMap::new(),
-    processes: BTreeMap::new(),
-    pgroups: BTreeMap::new(),
-    sessions: BTreeMap::new(),
+    threads: RBTree::new(AllThreads::NEW),
+    procs: RBTree::new(AllProcs::NEW),
+    pgroups: RBTree::new(AllGroups::NEW),
+    sessions: RBTree::new(AllSessions::NEW),
 });
 
 impl ProcessList {
@@ -40,43 +44,64 @@ impl ProcessList {
     }
 
     pub fn add_session(&mut self, session: &Arc<Session>) {
-        self.sessions.insert(session.sid, Arc::downgrade(session));
+        self.sessions.insert(session.clone());
     }
 
     pub fn add_pgroup(&mut self, pgroup: &Arc<ProcessGroup>) {
-        self.pgroups.insert(pgroup.pgid, Arc::downgrade(pgroup));
+        self.pgroups.insert(pgroup.clone());
     }
 
     pub fn add_process(&mut self, process: &Arc<Process>) {
-        self.processes.insert(process.pid, Arc::downgrade(process));
+        self.procs.insert(process.clone());
     }
 
     pub fn add_thread(&mut self, thread: &Arc<Thread>) {
-        self.threads.insert(thread.tid, thread.clone());
+        self.threads.insert(thread.clone());
     }
 
-    pub async fn remove_process(&mut self, pid: u32) {
+    pub fn remove_process(&mut self, pid: u32) {
         // Thread group leader has the same tid as the pid.
-        if let Some(thread) = self.threads.remove(&pid) {
-            self.processes.remove(&pid);
-
-            // SAFETY: We wait until all references are dropped below with `rcu_sync()`.
-            let session = unsafe { thread.process.session.swap(None) }.unwrap();
-            let pgroup = unsafe { thread.process.pgroup.swap(None) }.unwrap();
-            let _parent = unsafe { thread.process.parent.swap(None) }.unwrap();
-            pgroup.remove_member(pid, self.prove_mut());
-            rcu_sync().await;
-
-            if Arc::strong_count(&pgroup) == 1 {
-                self.pgroups.remove(&pgroup.pgid);
-            }
+        let Some(_) = self.threads.find_mut(&pid).remove() else {
+            panic!("Thread {} not found", pid);
+        };
 
-            if Arc::strong_count(&session) == 1 {
-                self.sessions.remove(&session.sid);
-            }
-        } else {
+        let Some(proc) = self.procs.find_mut(&pid).remove() else {
             panic!("Process {} not found", pid);
-        }
+        };
+
+        // SAFETY: `call_rcu` below.
+        let session = unsafe { proc.session.swap(None) }.unwrap();
+        let pgroup = unsafe { proc.pgroup.swap(None) }.unwrap();
+        let parent = unsafe { proc.parent.swap(None) }.unwrap();
+
+        pgroup.remove_member(&proc, self);
+
+        call_rcu(move || {
+            drop(session);
+            drop(pgroup);
+            drop(parent);
+        });
+    }
+
+    pub fn remove_thread(&mut self, thread: &Arc<Thread>) {
+        assert!(
+            self.threads.find_mut(&thread.tid).remove().is_some(),
+            "Double remove"
+        );
+    }
+
+    pub fn remove_session(&mut self, session: &Arc<Session>) {
+        assert!(
+            self.sessions.find_mut(&session.sid).remove().is_some(),
+            "Double remove"
+        );
+    }
+
+    pub fn remove_pgroup(&mut self, pgroup: &Arc<ProcessGroup>) {
+        assert!(
+            self.pgroups.find_mut(&pgroup.pgid).remove().is_some(),
+            "Double remove"
+        );
     }
 
     fn set_init_process(&mut self, init: Arc<Process>) {
@@ -88,20 +113,20 @@ impl ProcessList {
         self.init.as_ref().unwrap()
     }
 
-    pub fn try_find_thread(&self, tid: u32) -> Option<&Arc<Thread>> {
-        self.threads.get(&tid)
+    pub fn try_find_thread(&self, tid: u32) -> Option<Arc<Thread>> {
+        self.threads.find(&tid).clone_pointer()
     }
 
     pub fn try_find_process(&self, pid: u32) -> Option<Arc<Process>> {
-        self.processes.get(&pid).and_then(Weak::upgrade)
+        self.procs.find(&pid).clone_pointer()
     }
 
     pub fn try_find_pgroup(&self, pgid: u32) -> Option<Arc<ProcessGroup>> {
-        self.pgroups.get(&pgid).and_then(Weak::upgrade)
+        self.pgroups.find(&pgid).clone_pointer()
     }
 
     pub fn try_find_session(&self, sid: u32) -> Option<Arc<Session>> {
-        self.sessions.get(&sid).and_then(Weak::upgrade)
+        self.sessions.find(&sid).clone_pointer()
     }
 
     pub async fn sys_init(load_info: LoadInfo) {
@@ -118,9 +143,6 @@ impl ProcessList {
 
         process_list.set_init_process(process);
 
-        // TODO!!!: Remove this.
-        thread.files.open_console();
-
         RUNTIME.spawn(Reaper::daemon());
         RUNTIME.spawn(thread.run());
     }
@@ -152,18 +174,19 @@ impl Reaper {
         let process = &thread.process;
 
         if process.pid == 1 && thread.tid == process.pid {
-            panic!("init exited");
+            panic!("init exited: {}", alloc_pid());
         }
 
         let mut procs = ProcessList::get().write().await;
 
-        let inner = process.inner.access_mut(procs.prove_mut());
-
-        thread.dead.store(true, Ordering::SeqCst);
-
         if thread.tid != process.pid {
-            procs.threads.remove(&thread.tid);
-            inner.threads.remove(&thread.tid).unwrap();
+            let threads = process.threads.access_mut(procs.prove_mut());
+            assert!(
+                threads.find_mut(&thread.tid).remove().is_some(),
+                "Thread gone?"
+            );
+
+            procs.remove_thread(&thread);
         }
 
         // main thread exit
@@ -172,11 +195,11 @@ impl Reaper {
 
             thread.files.close_all().await;
 
+            let session = process.session(procs.prove()).clone();
             // If we are the session leader, we should drop the control terminal.
-            if process.session(procs.prove()).sid == process.pid {
-                if let Some(terminal) = process.session(procs.prove()).drop_control_terminal().await
-                {
-                    terminal.drop_session().await;
+            if session.sid == process.pid {
+                if let Some(terminal) = session.control_terminal() {
+                    terminal.drop_session(procs.prove());
                 }
             }
 
@@ -184,16 +207,14 @@ impl Reaper {
             process.mm_list.release();
 
             // Make children orphans (adopted by init)
-            {
-                let init = procs.init_process();
-                inner.children.retain(|_, child| {
-                    let child = child.upgrade().unwrap();
-                    // SAFETY: `child.parent` must be ourself. So we don't need to free it.
-                    unsafe { child.parent.swap(Some(init.clone())) };
-                    init.add_child(&child, procs.prove_mut());
-
-                    false
-                });
+            let init = procs.init_process();
+            let children = process.children.access_mut(procs.prove_mut());
+            for child in children.take() {
+                // XXX: May buggy. Check here again.
+                // SAFETY: `child.parent` must be ourself.
+                //         So we don't need to free it.
+                unsafe { child.parent.swap(Some(init.clone())) };
+                init.add_child(&child, procs.prove_mut());
             }
 
             let mut init_notify = procs.init_process().notify_batch();

+ 120 - 67
src/kernel/task/session.rs

@@ -1,117 +1,170 @@
-use super::{Process, ProcessGroup, ProcessList, Thread};
-use crate::kernel::constants::EPERM;
-use crate::{kernel::Terminal, prelude::*};
-use alloc::{
-    collections::btree_map::BTreeMap,
-    sync::{Arc, Weak},
+use alloc::sync::{Arc, Weak};
+
+use eonix_sync::{AsProof as _, AsProofMut, Locked, Proof, ProofMut};
+use intrusive_collections::{
+    intrusive_adapter, KeyAdapter, RBTree, RBTreeAtomicLink,
 };
-use eonix_sync::{AsProof as _, AsProofMut as _, Locked, Proof, ProofMut, RwLock};
 use posix_types::signal::Signal;
 
-#[derive(Debug)]
+use super::process_group::SessionGroups;
+use super::{Process, ProcessGroup, ProcessList};
+use crate::kernel::constants::EPERM;
+use crate::kernel::Terminal;
+use crate::prelude::*;
+
 struct SessionJobControl {
-    /// Foreground process group
-    foreground: Weak<ProcessGroup>,
+    foreground: Option<Arc<ProcessGroup>>,
     control_terminal: Option<Arc<Terminal>>,
 }
 
-#[allow(dead_code)]
-#[derive(Debug)]
 pub struct Session {
     pub sid: u32,
     pub leader: Weak<Process>,
-    job_control: RwLock<SessionJobControl>,
+    job_control: Spin<SessionJobControl>,
+    groups: Locked<RBTree<SessionGroups>, ProcessList>,
+    all_sessions_link: RBTreeAtomicLink,
+}
 
-    groups: Locked<BTreeMap<u32, Weak<ProcessGroup>>, ProcessList>,
+intrusive_adapter!(pub AllSessions = Arc<Session>: Session {
+    all_sessions_link: RBTreeAtomicLink
+});
+
+impl KeyAdapter<'_> for AllSessions {
+    type Key = u32;
+
+    fn get_key(
+        &self,
+        value: &'_ <Self::PointerOps as intrusive_collections::PointerOps>::Value,
+    ) -> Self::Key {
+        value.sid
+    }
 }
 
 impl Session {
     /// Create a session and add it to the global session list.
-    pub fn new(leader: &Arc<Process>, process_list: &mut ProcessList) -> Arc<Self> {
+    pub fn new(leader: &Arc<Process>, proclist: &mut ProcessList) -> Arc<Self> {
         let session = Arc::new(Self {
             sid: leader.pid,
             leader: Arc::downgrade(leader),
-            job_control: RwLock::new(SessionJobControl {
-                foreground: Weak::new(),
+            job_control: Spin::new(SessionJobControl {
+                foreground: None,
                 control_terminal: None,
             }),
-            groups: Locked::new(
-                BTreeMap::new(),
-                // SAFETY: `procs` must be the global process list, which won't be moved.
-                process_list,
-            ),
+            groups: Locked::new(RBTree::new(SessionGroups::NEW), proclist),
+            all_sessions_link: RBTreeAtomicLink::new(),
         });
 
-        process_list.add_session(&session);
+        proclist.add_session(&session);
         session
     }
 
-    pub(super) fn add_member(&self, procs: &mut ProcessList, pgroup: &Arc<ProcessGroup>) {
-        let groups = self.groups.access_mut(procs.prove_mut());
-        let old = groups.insert(pgroup.pgid, Arc::downgrade(pgroup));
-        assert!(old.is_none(), "Process group already exists");
+    pub fn add_member(
+        &self,
+        pgroup: &Arc<ProcessGroup>,
+        procs: ProofMut<'_, ProcessList>,
+    ) {
+        assert!(self.all_sessions_link.is_linked(), "Dead session");
+        self.groups.access_mut(procs).insert(pgroup.clone());
     }
 
-    pub(super) fn remove_member(&self, pgid: u32, procs: ProofMut<'_, ProcessList>) {
-        assert!(self.groups.access_mut(procs).remove(&pgid).is_some());
+    pub fn remove_member(
+        self: &Arc<Self>,
+        pgroup: &Arc<ProcessGroup>,
+        procs: &mut ProcessList,
+    ) {
+        let members = self.groups.access_mut(procs.prove_mut());
+        assert!(
+            members.find_mut(&pgroup.pgid).remove().is_some(),
+            "Not a member"
+        );
+
+        if let Some(fg_pgroup) = self.foreground_pgroup() {
+            if fg_pgroup.pgid == pgroup.pgid {
+                let _ = self.set_foreground_pgroup(None);
+            }
+        }
+
+        if !members.is_empty() {
+            return;
+        }
+
+        // Recycle dead session.
+        procs.remove_session(self);
     }
 
-    pub async fn foreground(&self) -> Option<Arc<ProcessGroup>> {
-        self.job_control.read().await.foreground.upgrade()
+    pub fn leader(&self) -> Option<Arc<Process>> {
+        self.leader.upgrade()
+    }
+
+    pub fn foreground_pgroup(&self) -> Option<Arc<ProcessGroup>> {
+        self.job_control.lock().foreground.clone()
+    }
+
+    pub fn control_terminal(&self) -> Option<Arc<Terminal>> {
+        self.job_control.lock().control_terminal.clone()
     }
 
     /// Set the foreground process group identified by `pgid`.
     /// The process group must belong to the session.
-    pub async fn set_foreground_pgid(
+    pub fn set_foreground_pgroup(
         &self,
-        pgid: u32,
-        procs: Proof<'_, ProcessList>,
+        pgroup: Option<&Arc<ProcessGroup>>,
     ) -> KResult<()> {
-        if let Some(group) = self.groups.access(procs).get(&pgid) {
-            self.job_control.write().await.foreground = group.clone();
-            Ok(())
-        } else {
-            // TODO: Check if the process group refers to an existing process group.
-            //       That's not a problem though, the operation will fail anyway.
-            Err(EPERM)
+        if let Some(pgroup) = pgroup {
+            if pgroup.session.sid != self.sid {
+                return Err(EPERM);
+            }
         }
+
+        self.job_control.lock().foreground = pgroup.cloned();
+        Ok(())
     }
 
-    /// Only session leaders can set the control terminal.
-    /// Make sure we've checked that before calling this function.
-    pub async fn set_control_terminal(
+    /// Set our controlling terminal to `terminal`. Only meant to be called by
+    /// the session leader. The pgroup that the session leader is in becomes the
+    /// new foreground pgroup.
+    ///
+    /// # Panics
+    /// Panics if we have a controlling terminal already
+    /// or the session leader is gone.
+    pub fn _set_control_terminal(
         self: &Arc<Self>,
         terminal: &Arc<Terminal>,
-        forced: bool,
         procs: Proof<'_, ProcessList>,
-    ) -> KResult<()> {
-        let mut job_control = self.job_control.write().await;
-        if let Some(_) = job_control.control_terminal.as_ref() {
-            if let Some(session) = terminal.session().await.as_ref() {
-                if session.sid == self.sid {
-                    return Ok(());
-                }
-            }
-            return Err(EPERM);
-        }
-        terminal.set_session(self, forced).await?;
+    ) {
+        let mut job_control = self.job_control.lock();
+        let leader = self.leader().expect("Leader is gone?");
+
+        assert!(
+            job_control.control_terminal.is_none(),
+            "We have a controlling terminal already"
+        );
+
         job_control.control_terminal = Some(terminal.clone());
-        job_control.foreground = Arc::downgrade(&Thread::current().process.pgroup(procs));
-        Ok(())
+        job_control.foreground = Some(leader.pgroup(procs).clone());
     }
 
     /// Drop the control terminal reference inside the session.
-    /// DO NOT TOUCH THE TERMINAL'S SESSION FIELD.
-    pub async fn drop_control_terminal(&self) -> Option<Arc<Terminal>> {
-        let mut inner = self.job_control.write().await;
-        inner.foreground = Weak::new();
-        inner.control_terminal.take()
+    /// Send SIGHUP and then SIGCONT to our foreground pgroup.
+    pub fn _drop_control_terminal(&self, procs: Proof<'_, ProcessList>) {
+        let foreground = {
+            let mut inner = self.job_control.lock();
+            inner.control_terminal = None;
+            inner.foreground.take()
+        };
+
+        if let Some(foreground) = foreground {
+            foreground.raise(Signal::SIGHUP, procs);
+            foreground.raise(Signal::SIGCHLD, procs);
+        }
     }
 
     pub async fn raise_foreground(&self, signal: Signal) {
-        if let Some(fg) = self.foreground().await {
-            let procs = ProcessList::get().read().await;
-            fg.raise(signal, procs.prove());
-        }
+        let Some(fg) = self.foreground_pgroup() else {
+            return;
+        };
+
+        let procs = ProcessList::get().read().await;
+        fg.raise(signal, procs.prove());
     }
 }

+ 89 - 17
src/kernel/task/thread.rs

@@ -14,6 +14,7 @@ use eonix_hal::traits::trap::{RawTrapContext, TrapReturn, TrapType};
 use eonix_hal::trap::TrapContext;
 use eonix_mm::address::{Addr as _, VAddr};
 use eonix_sync::AsProofMut as _;
+use intrusive_collections::{intrusive_adapter, KeyAdapter, RBTreeAtomicLink};
 use pointers::BorrowedArc;
 use posix_types::signal::Signal;
 use stalloc::UnsafeStalloc;
@@ -84,9 +85,44 @@ pub struct Thread {
     pub dead: AtomicBool,
     pub exit_status: Spin<Option<WaitType>>,
 
+    /// Link in the global thread list.
+    all_threads_link: RBTreeAtomicLink,
+
+    /// Link in the process's thread list.
+    process_threads_link: RBTreeAtomicLink,
+
     inner: Spin<ThreadInner>,
 }
 
+intrusive_adapter!(pub AllThreads = Arc<Thread>: Thread {
+    all_threads_link: RBTreeAtomicLink
+});
+intrusive_adapter!(pub ProcessThreads = Arc<Thread>: Thread {
+    process_threads_link: RBTreeAtomicLink
+});
+
+impl KeyAdapter<'_> for AllThreads {
+    type Key = u32;
+
+    fn get_key(
+        &self,
+        value: &'_ <Self::PointerOps as intrusive_collections::PointerOps>::Value,
+    ) -> Self::Key {
+        value.tid
+    }
+}
+
+impl KeyAdapter<'_> for ProcessThreads {
+    type Key = u32;
+
+    fn get_key(
+        &self,
+        value: &'_ <Self::PointerOps as intrusive_collections::PointerOps>::Value,
+    ) -> Self::Key {
+        value.tid
+    }
+}
+
 impl ThreadBuilder {
     pub fn new() -> Self {
         Self {
@@ -139,12 +175,18 @@ impl ThreadBuilder {
         self
     }
 
-    pub fn set_child_tid(mut self, set_child_tid: Option<UserMut<u32>>) -> Self {
+    pub fn set_child_tid(
+        mut self,
+        set_child_tid: Option<UserMut<u32>>,
+    ) -> Self {
         self.set_child_tid = set_child_tid;
         self
     }
 
-    pub fn clear_child_tid(mut self, clear_child_tid: Option<UserMut<u32>>) -> Self {
+    pub fn clear_child_tid(
+        mut self,
+        clear_child_tid: Option<UserMut<u32>>,
+    ) -> Self {
         self.clear_child_tid = clear_child_tid;
         self
     }
@@ -171,7 +213,11 @@ impl ThreadBuilder {
     }
 
     /// Clone the thread from another thread.
-    pub fn clone_from(self, thread: &Thread, clone_args: &CloneArgs) -> KResult<Self> {
+    pub fn clone_from(
+        self,
+        thread: &Thread,
+        clone_args: &CloneArgs,
+    ) -> KResult<Self> {
         let inner = thread.inner.lock();
 
         let mut trap_ctx = thread.trap_ctx.borrow().clone();
@@ -199,11 +245,12 @@ impl ThreadBuilder {
             FileArray::new_cloned(&thread.files)
         };
 
-        let signal_list = if clone_args.flags.contains(CloneFlags::CLONE_SIGHAND) {
-            SignalList::new_shared(&thread.signal_list)
-        } else {
-            SignalList::new_cloned(&thread.signal_list)
-        };
+        let signal_list =
+            if clone_args.flags.contains(CloneFlags::CLONE_SIGHAND) {
+                SignalList::new_shared(&thread.signal_list)
+            } else {
+                SignalList::new_cloned(&thread.signal_list)
+            };
 
         Ok(self
             .files(files)
@@ -241,6 +288,8 @@ impl ThreadBuilder {
             fpu_state: AtomicUniqueRefCell::new(fpu_state),
             dead: AtomicBool::new(false),
             exit_status: Spin::new(None),
+            all_threads_link: RBTreeAtomicLink::new(),
+            process_threads_link: RBTreeAtomicLink::new(),
             inner: Spin::new(ThreadInner {
                 name,
                 tls: self.tls,
@@ -281,7 +330,10 @@ impl Thread {
         Ok(())
     }
 
-    pub fn set_robust_list(&self, robust_list_address: Option<User<RobustListHead>>) {
+    pub fn set_robust_list(
+        &self,
+        robust_list_address: Option<User<RobustListHead>>,
+    ) {
         self.inner.lock().robust_list_address = robust_list_address;
     }
 
@@ -371,7 +423,10 @@ impl Thread {
         while !self.is_dead() {
             if self.signal_list.has_pending_signal() {
                 self.signal_list
-                    .handle(&mut self.trap_ctx.borrow(), &mut self.fpu_state.borrow())
+                    .handle(
+                        &mut self.trap_ctx.borrow(),
+                        &mut self.fpu_state.borrow(),
+                    )
                     .await;
             }
 
@@ -399,7 +454,9 @@ impl Thread {
                     }
 
                     let mms = &self.process.mm_list;
-                    if let Err(signal) = mms.handle_user_page_fault(addr, error_code).await {
+                    if let Err(signal) =
+                        mms.handle_user_page_fault(addr, error_code).await
+                    {
                         self.signal_list.raise(signal);
                     }
                 }
@@ -409,8 +466,12 @@ impl Thread {
                 TrapType::Fault(Fault::InvalidOp) => {
                     self.signal_list.raise(Signal::SIGILL);
                 }
-                TrapType::Fault(Fault::Unknown(_)) => unimplemented!("Unhandled fault"),
-                TrapType::Breakpoint => unimplemented!("Breakpoint in user space"),
+                TrapType::Fault(Fault::Unknown(_)) => {
+                    unimplemented!("Unhandled fault")
+                }
+                TrapType::Breakpoint => {
+                    unimplemented!("Breakpoint in user space")
+                }
                 TrapType::Irq { callback } => callback(default_irq_handler),
                 TrapType::Timer { callback } => {
                     callback(timer_interrupt);
@@ -424,11 +485,16 @@ impl Thread {
                         return;
                     }
 
-                    if let Some(retval) = self.handle_syscall(thd_alloc, no, args).await {
+                    if let Some(retval) =
+                        self.handle_syscall(thd_alloc, no, args).await
+                    {
                         let mut trap_ctx = self.trap_ctx.borrow();
                         trap_ctx.set_user_return_value(retval);
 
-                        #[cfg(any(target_arch = "riscv64", target_arch = "loongarch64"))]
+                        #[cfg(any(
+                            target_arch = "riscv64",
+                            target_arch = "loongarch64"
+                        ))]
                         {
                             let pc = trap_ctx.get_program_counter();
                             trap_ctx.set_program_counter(pc + 4);
@@ -472,7 +538,10 @@ impl Thread {
             })
             .await;
 
-            assert!(self.is_dead(), "`real_run` returned before the thread die?");
+            assert!(
+                self.is_dead(),
+                "`real_run` returned before the thread die?"
+            );
             ProcessList::send_to_reaper(self);
         }
     }
@@ -499,7 +568,10 @@ pub async fn yield_now() {
     impl Future for Yield {
         type Output = ();
 
-        fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+        fn poll(
+            mut self: Pin<&mut Self>,
+            cx: &mut Context<'_>,
+        ) -> Poll<Self::Output> {
             if self.as_mut().yielded {
                 Poll::Ready(())
             } else {

+ 101 - 55
src/kernel/terminal.rs

@@ -1,18 +1,19 @@
-use super::{
-    task::{ProcessList, Session, Thread},
-    user::{UserPointer, UserPointerMut},
-};
-use crate::kernel::constants::{EINTR, ENOTTY, EPERM};
-use crate::{io::Buffer, prelude::*, sync::CondVar};
-use alloc::{
-    collections::vec_deque::VecDeque,
-    sync::{Arc, Weak},
-};
+use alloc::collections::vec_deque::VecDeque;
+use alloc::sync::{Arc, Weak};
+
 use bitflags::bitflags;
 use eonix_log::ConsoleWrite;
-use eonix_sync::{AsProof as _, Mutex};
+use eonix_sync::{Mutex, Proof};
 use posix_types::signal::Signal;
 
+use super::constants::ESRCH;
+use super::task::{ProcessList, Session, Thread};
+use super::user::{UserPointer, UserPointerMut};
+use crate::io::Buffer;
+use crate::kernel::constants::{EINTR, ENOTTY, EPERM};
+use crate::prelude::*;
+use crate::sync::CondVar;
+
 const BUFFER_SIZE: usize = 4096;
 
 const NCCS: usize = 19;
@@ -351,12 +352,12 @@ pub trait TerminalDevice: Send + Sync {
 
 struct TerminalInner {
     termio: Termios,
-    session: Weak<Session>,
     buffer: VecDeque<u8>,
 }
 
 pub struct Terminal {
     inner: Mutex<TerminalInner>,
+    session: Spin<Weak<Session>>,
     device: Arc<dyn TerminalDevice>,
     cv: CondVar,
 }
@@ -400,9 +401,9 @@ impl Terminal {
         Arc::new(Self {
             inner: Mutex::new(TerminalInner {
                 termio: Termios::new_standard(),
-                session: Weak::new(),
                 buffer: VecDeque::with_capacity(BUFFER_SIZE),
             }),
+            session: Spin::new(Weak::new()),
             cv: CondVar::new(),
             device,
         })
@@ -447,15 +448,21 @@ impl Terminal {
     }
 
     async fn signal(&self, inner: &mut TerminalInner, signal: Signal) {
-        if let Some(session) = inner.session.upgrade() {
+        if let Some(session) = self.session() {
             session.raise_foreground(signal).await;
         }
+
         if !inner.termio.noflsh() {
             self.clear_read_buffer(inner);
         }
     }
 
-    async fn echo_and_signal(&self, inner: &mut TerminalInner, ch: u8, signal: Signal) {
+    async fn echo_and_signal(
+        &self,
+        inner: &mut TerminalInner,
+        ch: u8,
+        signal: Signal,
+    ) {
         self.echo_char(inner, ch);
         self.signal(inner, signal).await;
     }
@@ -481,13 +488,19 @@ impl Terminal {
             match ch {
                 0xff => {}
                 ch if ch == inner.termio.vintr() => {
-                    return self.echo_and_signal(&mut inner, ch, Signal::SIGINT).await
+                    return self
+                        .echo_and_signal(&mut inner, ch, Signal::SIGINT)
+                        .await
                 }
                 ch if ch == inner.termio.vquit() => {
-                    return self.echo_and_signal(&mut inner, ch, Signal::SIGQUIT).await
+                    return self
+                        .echo_and_signal(&mut inner, ch, Signal::SIGQUIT)
+                        .await
                 }
                 ch if ch == inner.termio.vsusp() => {
-                    return self.echo_and_signal(&mut inner, ch, Signal::SIGTSTP).await
+                    return self
+                        .echo_and_signal(&mut inner, ch, Signal::SIGTSTP)
+                        .await
                 }
                 _ => {}
             }
@@ -517,8 +530,12 @@ impl Terminal {
 
         match ch {
             b'\r' if inner.termio.igncr() => {}
-            b'\r' if inner.termio.icrnl() => return self.do_commit_char(&mut inner, b'\n'),
-            b'\n' if inner.termio.inlcr() => return self.do_commit_char(&mut inner, b'\r'),
+            b'\r' if inner.termio.icrnl() => {
+                return self.do_commit_char(&mut inner, b'\n')
+            }
+            b'\n' if inner.termio.inlcr() => {
+                return self.do_commit_char(&mut inner, b'\r')
+            }
             _ => self.do_commit_char(&mut inner, ch),
         }
     }
@@ -589,26 +606,30 @@ impl Terminal {
     pub async fn ioctl(&self, request: TerminalIORequest<'_>) -> KResult<()> {
         match request {
             TerminalIORequest::GetProcessGroup(pgid_pointer) => {
-                if let Some(session) = self.inner.lock().await.session.upgrade() {
-                    if let Some(pgroup) = session.foreground().await {
-                        return pgid_pointer.write(pgroup.pgid);
-                    }
-                }
+                let Some(session) = self.session() else {
+                    return Err(ENOTTY);
+                };
+
+                let Some(pgroup) = session.foreground_pgroup() else {
+                    return Err(ENOTTY);
+                };
 
-                Err(ENOTTY)
+                pgid_pointer.write(pgroup.pgid)
             }
             TerminalIORequest::SetProcessGroup(pgid) => {
                 let pgid = pgid.read()?;
 
                 let procs = ProcessList::get().read().await;
-                let inner = self.inner.lock().await;
-                let session = inner.session.upgrade();
+                let Some(session) = self.session() else {
+                    return Err(ENOTTY);
+                };
 
-                if let Some(session) = session {
-                    session.set_foreground_pgid(pgid, procs.prove()).await
-                } else {
-                    Err(ENOTTY)
-                }
+                let Some(pgroup) = procs.try_find_pgroup(pgid) else {
+                    return Err(ESRCH);
+                };
+
+                session.set_foreground_pgroup(Some(&pgroup))?;
+                Ok(())
             }
             TerminalIORequest::GetWindowSize(ptr) => {
                 // TODO: Get the actual window size
@@ -630,9 +651,12 @@ impl Terminal {
                 let mut inner = self.inner.lock().await;
 
                 // TODO: We ignore unknown bits for now.
-                inner.termio.iflag = TermioIFlags::from_bits_truncate(user_termios.iflag as u16);
-                inner.termio.oflag = TermioOFlags::from_bits_truncate(user_termios.oflag as u16);
-                inner.termio.lflag = TermioLFlags::from_bits_truncate(user_termios.lflag as u16);
+                inner.termio.iflag =
+                    TermioIFlags::from_bits_truncate(user_termios.iflag as u16);
+                inner.termio.oflag =
+                    TermioOFlags::from_bits_truncate(user_termios.oflag as u16);
+                inner.termio.lflag =
+                    TermioLFlags::from_bits_truncate(user_termios.lflag as u16);
                 inner.termio.cflag = user_termios.cflag;
                 inner.termio.line = user_termios.line;
                 inner.termio.cc = user_termios.cc;
@@ -642,30 +666,52 @@ impl Terminal {
         }
     }
 
-    /// Assign the `session` to this terminal. Drop the previous session if `forced` is true.
-    pub async fn set_session(&self, session: &Arc<Session>, forced: bool) -> KResult<()> {
-        let mut inner = self.inner.lock().await;
-        if let Some(session) = inner.session.upgrade() {
+    pub fn session(&self) -> Option<Arc<Session>> {
+        self.session.lock().upgrade()
+    }
+
+    /// Drop our current controlled session. The old session lose its controlling
+    /// terminal and all processes in it will receive a SIGHUP and then SIGCONT.
+    pub fn drop_session(&self, procs: Proof<'_, ProcessList>) {
+        let session =
+            core::mem::replace(&mut *self.session.lock(), Weak::new());
+        let Some(old_session) = session.upgrade() else {
+            return;
+        };
+
+        old_session._drop_control_terminal(procs);
+    }
+
+    /// Assign the `session` to this terminal.
+    /// Drop the previous session if `forced` is true.
+    pub async fn set_session(
+        self: &Arc<Self>,
+        session: &Arc<Session>,
+        forced: bool,
+        procs: Proof<'_, ProcessList>,
+    ) -> KResult<()> {
+        let mut cur_session = self.session.lock();
+
+        // XXX: Holding spinlock for too long?
+        if let Some(old_session) = cur_session.upgrade() {
+            if old_session.sid == session.sid {
+                return Ok(());
+            }
+
             if !forced {
-                Err(EPERM)
-            } else {
-                session.drop_control_terminal().await;
-                inner.session = Arc::downgrade(&session);
-                Ok(())
+                return Err(EPERM);
             }
-        } else {
-            // Sessions should set their `control_terminal` field.
-            inner.session = Arc::downgrade(&session);
-            Ok(())
+
+            // TODO: Check whether the caller has the CAP_SYS_ADMIN capability.
+
+            // We've stolen the terminal from the old session.
+            old_session._drop_control_terminal(procs);
         }
-    }
 
-    pub async fn drop_session(&self) {
-        self.inner.lock().await.session = Weak::new();
-    }
+        *cur_session = Arc::downgrade(session);
+        session._set_control_terminal(self, procs);
 
-    pub async fn session(&self) -> Option<Arc<Session>> {
-        self.inner.lock().await.session.upgrade()
+        Ok(())
     }
 }
 

+ 50 - 18
src/kernel/vfs/file/terminal_file.rs

@@ -1,24 +1,46 @@
-use super::{File, FileType, PollEvent};
-use crate::{
-    io::{Buffer, Stream, StreamRead},
-    kernel::{
-        constants::{EINVAL, TCGETS, TCSETS, TIOCGPGRP, TIOCGWINSZ, TIOCSPGRP},
-        terminal::TerminalIORequest,
-        user::{UserPointer, UserPointerMut},
-        Terminal,
-    },
-    prelude::KResult,
-};
 use alloc::sync::Arc;
+
+use eonix_sync::AsProof;
 use posix_types::open::OpenFlags;
 
+use super::{File, FileType, PollEvent};
+use crate::io::{Buffer, Stream, StreamRead};
+use crate::kernel::constants::{
+    EINVAL, TCGETS, TCSETS, TIOCGPGRP, TIOCGWINSZ, TIOCSPGRP,
+};
+use crate::kernel::task::{ProcessList, Thread};
+use crate::kernel::terminal::TerminalIORequest;
+use crate::kernel::user::{UserPointer, UserPointerMut};
+use crate::kernel::Terminal;
+use crate::prelude::KResult;
+
 pub struct TerminalFile {
     terminal: Arc<Terminal>,
 }
 
 impl TerminalFile {
-    pub fn new(tty: Arc<Terminal>, flags: OpenFlags) -> File {
-        File::new(flags, FileType::Terminal(TerminalFile { terminal: tty }))
+    pub async fn open(
+        thread: &Thread,
+        terminal: &Arc<Terminal>,
+        flags: OpenFlags,
+    ) -> File {
+        let set_control_tty = !flags.contains(OpenFlags::O_NOCTTY);
+
+        let procs = ProcessList::get().read().await;
+        let session = thread.process.session(procs.prove());
+
+        // We only set the control terminal if the process is the session leader.
+        if set_control_tty && session.sid == thread.process.pid {
+            // Silently fail if we can't set the control terminal.
+            let _ = terminal.set_session(&session, false, procs.prove()).await;
+        }
+
+        File::new(
+            flags,
+            FileType::Terminal(TerminalFile {
+                terminal: terminal.clone(),
+            }),
+        )
     }
 
     pub async fn read(&self, buffer: &mut dyn Buffer) -> KResult<usize> {
@@ -43,11 +65,21 @@ impl TerminalFile {
     pub async fn ioctl(&self, request: usize, arg3: usize) -> KResult<()> {
         self.terminal
             .ioctl(match request as u32 {
-                TCGETS => TerminalIORequest::GetTermios(UserPointerMut::with_addr(arg3)?),
-                TCSETS => TerminalIORequest::SetTermios(UserPointer::with_addr(arg3)?),
-                TIOCGPGRP => TerminalIORequest::GetProcessGroup(UserPointerMut::with_addr(arg3)?),
-                TIOCSPGRP => TerminalIORequest::SetProcessGroup(UserPointer::with_addr(arg3)?),
-                TIOCGWINSZ => TerminalIORequest::GetWindowSize(UserPointerMut::with_addr(arg3)?),
+                TCGETS => TerminalIORequest::GetTermios(
+                    UserPointerMut::with_addr(arg3)?,
+                ),
+                TCSETS => {
+                    TerminalIORequest::SetTermios(UserPointer::with_addr(arg3)?)
+                }
+                TIOCGPGRP => TerminalIORequest::GetProcessGroup(
+                    UserPointerMut::with_addr(arg3)?,
+                ),
+                TIOCSPGRP => TerminalIORequest::SetProcessGroup(
+                    UserPointer::with_addr(arg3)?,
+                ),
+                TIOCGWINSZ => TerminalIORequest::GetWindowSize(
+                    UserPointerMut::with_addr(arg3)?,
+                ),
                 _ => return Err(EINVAL),
             })
             .await

+ 44 - 44
src/kernel/vfs/filearray.rs

@@ -1,19 +1,22 @@
 use alloc::sync::Arc;
 
 use intrusive_collections::rbtree::Entry;
-use intrusive_collections::{intrusive_adapter, Bound, KeyAdapter, RBTree, RBTreeAtomicLink};
+use intrusive_collections::{
+    intrusive_adapter, Bound, KeyAdapter, RBTree, RBTreeAtomicLink,
+};
 use itertools::FoldWhile::{Continue, Done};
 use itertools::Itertools;
 use posix_types::open::{FDFlags, OpenFlags};
 
 use super::file::{File, InodeFile, Pipe};
 use super::types::{Format, Permission};
-use super::{Spin, TerminalFile};
-use crate::kernel::console::get_console;
+use super::Spin;
 use crate::kernel::constants::{
-    EBADF, EISDIR, ENOTDIR, ENXIO, F_DUPFD, F_DUPFD_CLOEXEC, F_GETFD, F_GETFL, F_SETFD, F_SETFL,
+    EBADF, EISDIR, ENOTDIR, ENXIO, F_DUPFD, F_DUPFD_CLOEXEC, F_GETFD, F_GETFL,
+    F_SETFD, F_SETFL,
 };
 use crate::kernel::syscall::{FromSyscallArg, SyscallRetVal};
+use crate::kernel::task::Thread;
 use crate::kernel::vfs::dentry::Dentry;
 use crate::kernel::CharDevice;
 use crate::prelude::*;
@@ -80,7 +83,11 @@ impl FDAllocator {
         self.min_avail = FD(0);
     }
 
-    fn find_available(&mut self, from: FD, files: &RBTree<OpenFileAdapter>) -> FD {
+    fn find_available(
+        &mut self,
+        from: FD,
+        files: &RBTree<OpenFileAdapter>,
+    ) -> FD {
         files
             .range(Bound::Included(&from), Bound::Unbounded)
             .fold_while(from, |current, OpenFile { fd, .. }| {
@@ -143,7 +150,8 @@ impl FileArray {
                     let other_inner = other.inner.lock();
 
                     for file in other_inner.files.iter() {
-                        let new_file = OpenFile::new(file.fd, file.flags, file.file.dup());
+                        let new_file =
+                            OpenFile::new(file.fd, file.flags, file.file.dup());
                         new_files.insert(new_file);
                     }
                     (new_files, other_inner.fd_alloc.clone())
@@ -223,7 +231,12 @@ impl FileArray {
 
     /// Duplicates the file to a new file descriptor, returning the old file
     /// description to be dropped.
-    fn dup_to_no_close(&self, old_fd: FD, new_fd: FD, fd_flags: FDFlags) -> KResult<Option<File>> {
+    fn dup_to_no_close(
+        &self,
+        old_fd: FD,
+        new_fd: FD,
+        fd_flags: FDFlags,
+    ) -> KResult<Option<File>> {
         let mut inner = self.inner.lock();
         let (files, fd_alloc) = inner.split_borrow();
 
@@ -240,7 +253,8 @@ impl FileArray {
             Entry::Occupied(mut entry) => {
                 let mut file = entry.remove().unwrap();
                 file.flags = fd_flags;
-                let old_file = core::mem::replace(&mut file.file, new_file_data);
+                let old_file =
+                    core::mem::replace(&mut file.file, new_file_data);
 
                 entry.insert(file);
 
@@ -249,8 +263,15 @@ impl FileArray {
         }
     }
 
-    pub async fn dup_to(&self, old_fd: FD, new_fd: FD, flags: OpenFlags) -> KResult<FD> {
-        if let Some(old_file) = self.dup_to_no_close(old_fd, new_fd, flags.as_fd_flags())? {
+    pub async fn dup_to(
+        &self,
+        old_fd: FD,
+        new_fd: FD,
+        flags: OpenFlags,
+    ) -> KResult<FD> {
+        if let Some(old_file) =
+            self.dup_to_no_close(old_fd, new_fd, flags.as_fd_flags())?
+        {
             old_file.close().await;
         }
 
@@ -277,6 +298,7 @@ impl FileArray {
 
     pub async fn open(
         &self,
+        thread: &Thread,
         dentry: &Arc<Dentry>,
         flags: OpenFlags,
         perm: Permission,
@@ -300,7 +322,7 @@ impl FileArray {
 
         let file = if inode.format == Format::CHR {
             let device = CharDevice::get(inode.devid()?).ok_or(ENXIO)?;
-            device.open(flags)?
+            device.open(thread, flags).await?
         } else {
             InodeFile::new(dentry.clone(), flags)
         };
@@ -323,7 +345,8 @@ impl FileArray {
             F_DUPFD | F_DUPFD_CLOEXEC => {
                 let ofile = cursor.get().ok_or(EBADF)?;
 
-                let cloexec = cmd == F_DUPFD_CLOEXEC || ofile.flags.close_on_exec();
+                let cloexec =
+                    cmd == F_DUPFD_CLOEXEC || ofile.flags.close_on_exec();
                 let flags = cloexec
                     .then_some(FDFlags::FD_CLOEXEC)
                     .unwrap_or(FDFlags::empty());
@@ -342,7 +365,9 @@ impl FileArray {
                 cursor.insert(ofile);
                 0
             }
-            F_GETFL => cursor.get().ok_or(EBADF)?.file.get_flags().bits() as usize,
+            F_GETFL => {
+                cursor.get().ok_or(EBADF)?.file.get_flags().bits() as usize
+            }
             F_SETFL => {
                 cursor
                     .get()
@@ -357,35 +382,6 @@ impl FileArray {
 
         Ok(ret)
     }
-
-    /// Only used for init process.
-    pub fn open_console(&self) {
-        let mut inner = self.inner.lock();
-        let (files, fd_alloc) = inner.split_borrow();
-
-        let (stdin, stdout, stderr) = (
-            fd_alloc.next_fd(files),
-            fd_alloc.next_fd(files),
-            fd_alloc.next_fd(files),
-        );
-        let console_terminal = get_console().expect("No console terminal");
-
-        inner.do_insert(
-            stdin,
-            FDFlags::FD_CLOEXEC,
-            TerminalFile::new(console_terminal.clone(), OpenFlags::empty()),
-        );
-        inner.do_insert(
-            stdout,
-            FDFlags::FD_CLOEXEC,
-            TerminalFile::new(console_terminal.clone(), OpenFlags::empty()),
-        );
-        inner.do_insert(
-            stderr,
-            FDFlags::FD_CLOEXEC,
-            TerminalFile::new(console_terminal.clone(), OpenFlags::empty()),
-        );
-    }
 }
 
 impl FileArrayInner {
@@ -397,7 +393,9 @@ impl FileArrayInner {
     fn do_insert(&mut self, fd: FD, flags: FDFlags, file: File) {
         match self.files.entry(&fd) {
             Entry::Occupied(_) => {
-                panic!("File descriptor {fd:?} already exists in the file array.");
+                panic!(
+                    "File descriptor {fd:?} already exists in the file array."
+                );
             }
             Entry::Vacant(insert_cursor) => {
                 insert_cursor.insert(OpenFile::new(fd, flags, file));
@@ -405,7 +403,9 @@ impl FileArrayInner {
         }
     }
 
-    fn split_borrow(&mut self) -> (&mut RBTree<OpenFileAdapter>, &mut FDAllocator) {
+    fn split_borrow(
+        &mut self,
+    ) -> (&mut RBTree<OpenFileAdapter>, &mut FDAllocator) {
         let Self { files, fd_alloc } = self;
         (files, fd_alloc)
     }

+ 4 - 2
user-programs/init_script_riscv64.sh

@@ -56,6 +56,9 @@ busybox mknod -m 666 /dev/vdb b 8 16
 busybox mknod -m 666 /dev/ttyS0 c 4 64
 busybox mknod -m 666 /dev/ttyS1 c 4 65
 
+exec < "$TERMINAL"
+exec > "$TERMINAL" 2>&1
+
 info "deploying busybox..."
 
 busybox mkdir -p /bin /lib
@@ -106,8 +109,7 @@ int main() {
 }
 EOF
 
-# shellcheck disable=SC2094
-exec sh -l < "$TERMINAL" > "$TERMINAL" 2> "$TERMINAL"
+exec sh -l
 
 # We don't have a working init yet, so we use busybox sh directly for now.
 # exec /mnt/init /bin/sh -c 'exec sh -l < /dev/ttyS0 > /dev/ttyS0 2> /dev/ttyS0'