starry-kernel 0.6.0

A Linux-compatible OS kernel built on ArceOS unikernel
use alloc::sync::{Arc, Weak};
use core::task::Context;

use ax_errno::{AxResult, ax_bail};
use ax_kspin::SpinNoIrq;
use ax_task::current;
use axpoll::{IoEvents, PollSet, Pollable};
use starry_process::{ProcessGroup, Session};

use crate::task::AsThread;

pub struct JobControl {
    state: SpinNoIrq<JobControlState>,
    poll_fg: PollSet,
}

struct JobControlState {
    foreground: Weak<ProcessGroup>,
    session: Weak<Session>,
}

impl Default for JobControl {
    fn default() -> Self {
        Self::new()
    }
}

impl JobControl {
    pub fn new() -> Self {
        Self {
            state: SpinNoIrq::new(JobControlState {
                foreground: Weak::new(),
                session: Weak::new(),
            }),
            poll_fg: PollSet::new(),
        }
    }

    pub fn current_in_foreground(&self) -> bool {
        self.state
            .lock()
            .foreground
            .upgrade()
            .is_none_or(|pg| Arc::ptr_eq(&current().as_thread().proc_data.proc.group(), &pg))
    }

    pub fn foreground(&self) -> Option<Arc<ProcessGroup>> {
        self.state.lock().foreground.upgrade()
    }

    pub fn set_foreground(&self, pg: &Arc<ProcessGroup>) -> AxResult<()> {
        let mut state = self.state.lock();
        let weak = Arc::downgrade(pg);
        if Weak::ptr_eq(&weak, &state.foreground) {
            return Ok(());
        }

        let Some(session) = state.session.upgrade() else {
            ax_bail!(
                OperationNotPermitted,
                "No session associated with job control"
            );
        };
        if !Arc::ptr_eq(&pg.session(), &session) {
            ax_bail!(
                OperationNotPermitted,
                "Process group does not belong to the session"
            );
        }

        state.foreground = weak;
        drop(state);
        self.poll_fg.wake();
        Ok(())
    }

    pub fn set_session(&self, session: &Arc<Session>) -> AxResult<()> {
        let mut state = self.state.lock();
        if let Some(existing) = state.session.upgrade() {
            if Arc::ptr_eq(&existing, session) {
                return Ok(());
            }
            ax_bail!(
                ResourceBusy,
                "Terminal is already associated with another session"
            );
        }
        state.session = Arc::downgrade(session);
        Ok(())
    }

    pub fn clear_session(&self, session: &Arc<Session>) {
        let mut state = self.state.lock();
        if state
            .session
            .upgrade()
            .is_some_and(|existing| Arc::ptr_eq(&existing, session))
        {
            state.session = Weak::new();
        }

        let foreground_cleared = state
            .foreground
            .upgrade()
            .is_some_and(|pg| Arc::ptr_eq(&pg.session(), session));
        if foreground_cleared {
            state.foreground = Weak::new();
        }
        drop(state);

        if foreground_cleared {
            self.poll_fg.wake();
        }
    }
}

impl Pollable for JobControl {
    fn poll(&self) -> IoEvents {
        let mut events = IoEvents::empty();
        events.set(IoEvents::IN, self.current_in_foreground());
        events
    }

    fn register(&self, context: &mut Context<'_>, events: IoEvents) {
        if events.contains(IoEvents::IN) {
            self.poll_fg.register(context.waker());
        }
    }
}