Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions kernel/pty/pty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "mm/heap.h"
#include "mm/uaccess.h"
#include "dynpriv/dynpriv.h"
#include "signals/signal.h"
#include "sync/poll.h"
#include "sync/wait_queue.h"
#include "terminal/terminal.h"
Expand All @@ -18,6 +19,7 @@ constexpr uint32_t TCSETSF = 0x5404;
constexpr uint32_t TIOCGWINSZ = 0x5413;
constexpr uint32_t TIOCSWINSZ = 0x5414;

constexpr uint32_t LINUX_ISIG = 0x0001;
constexpr uint32_t LINUX_ECHO = 0x0008;
constexpr uint32_t LINUX_ICANON = 0x0002;

Expand Down Expand Up @@ -46,6 +48,14 @@ __PRIVILEGED_CODE static void pty_echo_fn(void* ctx, const uint8_t* buf, size_t
(void)ring_buffer_write(chan->m_output_rb, buf, len, true);
}

__PRIVILEGED_CODE static void pty_signal_fn(void* ctx, uint32_t sig) {
auto* chan = static_cast<pty_channel*>(ctx);
uint32_t fg = __atomic_load_n(&chan->m_fg_group, __ATOMIC_ACQUIRE);
if (fg) {
(void)signals::send_to_group_id(fg, sig);
}
}

// Master ops

static ssize_t pty_master_read(
Expand Down Expand Up @@ -80,6 +90,7 @@ static ssize_t pty_master_write(
result = resource::ERR_PIPE;
} else {
terminal::ld_input_buf(&chan->m_ld, chan->m_input_rb, &chan->m_echo,
&chan->m_sig,
static_cast<const char*>(ksrc), count);
result = static_cast<ssize_t>(count);
}
Expand Down Expand Up @@ -206,6 +217,9 @@ static int32_t do_tcgets(pty_channel* chan, uint64_t arg) {
if (chan->m_ld.mode != terminal::LD_MODE_RAW) {
t.c_lflag = LINUX_ICANON | LINUX_ECHO;
}
if (chan->m_ld.isig) {
t.c_lflag |= LINUX_ISIG;
}

int32_t rc = mm::uaccess::copy_to_user(
reinterpret_cast<void*>(arg), &t, sizeof(t));
Expand All @@ -226,7 +240,38 @@ static int32_t do_tcsets(pty_channel* chan, uint64_t arg) {
? terminal::STLX_TCSETS_COOKED
: terminal::STLX_TCSETS_RAW;

// The mode shortcut pairs ISIG with it, the termios bit then decides
terminal::ld_set_mode(&chan->m_ld, mode);
terminal::ld_set_isig(&chan->m_ld, (t.c_lflag & LINUX_ISIG) != 0);
return resource::OK;
}

static int32_t do_tiocgpgrp(pty_channel* chan, uint64_t arg) {
int32_t g = static_cast<int32_t>(
__atomic_load_n(&chan->m_fg_group, __ATOMIC_ACQUIRE));

int32_t rc = mm::uaccess::copy_to_user(
reinterpret_cast<void*>(arg), &g, sizeof(g));

return (rc == mm::uaccess::OK) ? resource::OK : resource::ERR_INVAL;
}

static int32_t do_tiocspgrp(pty_channel* chan, uint64_t arg) {
int32_t g = 0;
int32_t rc = mm::uaccess::copy_from_user(
&g, reinterpret_cast<const void*>(arg), sizeof(g));
if (rc != mm::uaccess::OK || g < 0) {
return resource::ERR_INVAL;
}

// POSIX requires an existing process group, 0 clears the foreground
if (g > 0 &&
signals::send_to_group_id(static_cast<uint32_t>(g), 0) != signals::OK) {
return resource::ERR_INVAL;
}

__atomic_store_n(&chan->m_fg_group, static_cast<uint32_t>(g),
__ATOMIC_RELEASE);
return resource::OK;
}

Expand Down Expand Up @@ -257,6 +302,8 @@ static int32_t pty_termios_ioctl(pty_channel* chan, uint32_t cmd, uint64_t arg)
case TCSETSF: return do_tcsets(chan, arg);
case TIOCGWINSZ: return do_tiocgwinsz(chan, arg);
case TIOCSWINSZ: return do_tiocswinsz(chan, arg);
case terminal::TIOCGPGRP: return do_tiocgpgrp(chan, arg);
case terminal::TIOCSPGRP: return do_tiocspgrp(chan, arg);
case terminal::STLX_TCSETS_RAW:
case terminal::STLX_TCSETS_COOKED: return terminal::ld_set_mode(&chan->m_ld, cmd);
default: return resource::ERR_INVAL;
Expand Down Expand Up @@ -367,8 +414,10 @@ __PRIVILEGED_CODE int32_t create_pair(

terminal::ld_init(&chan->m_ld);
chan->m_echo = { pty_echo_fn, chan.ptr() };
chan->m_sig = { pty_signal_fn, chan.ptr() };
chan->m_id = __atomic_fetch_add(&g_next_pty_id, 1, __ATOMIC_RELAXED);
chan->m_oflags = PTY_OFLAG_ONLCR;
chan->m_fg_group = 0;
chan->m_winsize = { 24, 80, 0, 0 };

auto* ep_master = heap::kalloc_new<pty_endpoint>();
Expand Down
2 changes: 2 additions & 0 deletions kernel/pty/pty.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ struct pty_channel : rc::ref_counted<pty_channel> {
ring_buffer* m_output_rb; // slave write -> master read
terminal::line_discipline m_ld;
terminal::echo_target m_echo;
terminal::signal_target m_sig;
uint32_t m_id;
uint32_t m_oflags; // output processing flags
uint32_t m_fg_group; // foreground process group, 0 = none
pty_winsize m_winsize; // set via TIOCSWINSZ from either end

/** @note Privilege: **required** */
Expand Down
36 changes: 36 additions & 0 deletions kernel/signals/signal.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "signals/signal.h"
#include "sched/sched.h"
#include "sched/task.h"
#include "sched/task_registry.h"
#include "timer/timer.h"
#include "common/logging.h"

Expand All @@ -13,6 +14,10 @@ enum class send_verdict : uint8_t {
HANDLED, // a user handler is installed, wake the target to deliver
};

// Distinct groups remembered during one group-id send. Signals coalesce,
// so duplicate sends past this window are harmless extra wakes.
constexpr uint32_t MAX_GROUP_SEND_GROUPS = 64;

/**
* Clear pending instances of sig from the shared set and every thread.
* @note Privilege: **required**
Expand Down Expand Up @@ -265,6 +270,37 @@ __PRIVILEGED_CODE int32_t send_to_group(sched::thread_group* tg, uint32_t sig) {
return OK;
}

__PRIVILEGED_CODE int32_t send_to_group_id(uint32_t group_id, uint32_t sig) {
sched::thread_group* seen[MAX_GROUP_SEND_GROUPS];
uint32_t seen_count = 0;
bool found = false;

sync::irq_state irq = sched::g_task_registry.lock();
sched::g_task_registry.for_each_locked([&](sched::task& t) {
sched::thread_group* tg = t.group;
if (!tg || __atomic_load_n(&tg->group_id, __ATOMIC_ACQUIRE) != group_id) {
return;
}

for (uint32_t i = 0; i < seen_count; i++) {
if (seen[i] == tg) {
return;
}
}
if (seen_count < MAX_GROUP_SEND_GROUPS) {
seen[seen_count++] = tg;
}

found = true;
if (sig != 0) {
send_to_group(tg, sig);
}
});
sched::g_task_registry.unlock(irq);

return found ? OK : ERR_INVAL;
}

__PRIVILEGED_CODE uint32_t fatal_pending(sched::task* t) {
sig_set_t pending = __atomic_load_n(&t->sig.pending, __ATOMIC_ACQUIRE);
sig_set_t shared = t->group
Expand Down
8 changes: 8 additions & 0 deletions kernel/signals/signal.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ __PRIVILEGED_CODE int32_t send_to_task(sched::task* t, uint32_t sig);
*/
__PRIVILEGED_CODE int32_t send_to_group(sched::thread_group* tg, uint32_t sig);

/**
* @brief Send sig to every process in the process group group_id, where
* sig 0 only probes for existence. Safe from ISR context.
* @return OK when at least one process matched, ERR_INVAL otherwise.
* @note Privilege: **required**
*/
__PRIVILEGED_CODE int32_t send_to_group_id(uint32_t group_id, uint32_t sig);

/**
* @brief Fatal signal the task must die from, or 0.
* A set kill flag reports the group's recorded exit signal (SIGKILL if
Expand Down
37 changes: 3 additions & 34 deletions kernel/syscall/handlers/sys_signal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ static constexpr uint64_t SIGSET_SIZE = 8;
// Highest value representable as a task or group id
static constexpr int64_t TASK_ID_LIMIT = 0xFFFFFFFF;

// Distinct groups remembered during one group kill. Signals coalesce, so
// duplicate sends past this window are harmless extra wakes, never misses.
static constexpr uint32_t MAX_KILL_GROUPS = 64;

static int64_t map_send_error(int32_t rc) {
switch (rc) {
case signals::OK: return 0;
Expand All @@ -24,37 +20,10 @@ static int64_t map_send_error(int32_t rc) {
}
}

// Send sig to every process in the group, where sig 0 only probes existence.
// Group pointers stay valid because registered tasks pin their groups.
// Send sig to every process in the group, where sig 0 only probes existence
static int64_t kill_process_group(uint32_t group_id, uint32_t sig) {
sched::thread_group* seen[MAX_KILL_GROUPS];
uint32_t seen_count = 0;
bool found = false;

sync::irq_state irq = sched::g_task_registry.lock();
sched::g_task_registry.for_each_locked([&](sched::task& t) {
sched::thread_group* tg = t.group;
if (!tg || __atomic_load_n(&tg->group_id, __ATOMIC_ACQUIRE) != group_id) {
return;
}

for (uint32_t i = 0; i < seen_count; i++) {
if (seen[i] == tg) {
return;
}
}
if (seen_count < MAX_KILL_GROUPS) {
seen[seen_count++] = tg;
}

found = true;
if (sig != 0) {
signals::send_to_group(tg, sig);
}
});
sched::g_task_registry.unlock(irq);

return found ? 0 : syscall::ESRCH;
return signals::send_to_group_id(group_id, sig) == signals::OK
? 0 : syscall::ESRCH;
}

// Thread-directed send shared by tkill and tgkill, tgid 0 skips the pair check
Expand Down
4 changes: 2 additions & 2 deletions kernel/terminal/console_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ ssize_t console_node::write(fs::file*, const void* buf, size_t count) {
return static_cast<ssize_t>(count);
}

int32_t console_node::ioctl(fs::file*, uint32_t cmd, uint64_t) {
return terminal::set_mode(cmd);
int32_t console_node::ioctl(fs::file*, uint32_t cmd, uint64_t arg) {
return terminal::console_ioctl(cmd, arg);
}

int32_t console_node::getattr(fs::vattr* attr) {
Expand Down
68 changes: 62 additions & 6 deletions kernel/terminal/line_discipline.cpp
Original file line number Diff line number Diff line change
@@ -1,20 +1,61 @@
#include "terminal/line_discipline.h"
#include "terminal/terminal.h"
#include "signals/signal_types.h"
#include "common/ring_buffer.h"
#include "dynpriv/dynpriv.h"

namespace terminal {

// Signal-generating control bytes (termios VINTR/VQUIT/VSUSP defaults)
constexpr char LD_CH_INTR = 0x03; // ^C
constexpr char LD_CH_QUIT = 0x1C; // ^backslash
constexpr char LD_CH_SUSP = 0x1A; // ^Z

void ld_init(line_discipline* ld) {
ld->mode = 0;
ld->isig = 1;
ld->line_len = 0;
ld->prev_char = 0;
ld->lock = sync::SPINLOCK_INIT;
}

static uint32_t signal_for_char(char c) {
switch (c) {
case LD_CH_INTR: return signals::SIGINT;
case LD_CH_QUIT: return signals::SIGQUIT;
case LD_CH_SUSP: return signals::SIGTSTP;
default: return 0;
}
}

// Echo "^X" plus CRLF so the next output starts on a fresh line
static void echo_signal_char(const echo_target* echo, char c) {
if (echo && echo->write) {
const uint8_t seq[] = {'^', static_cast<uint8_t>(c + 0x40), '\r', '\n'};
echo->write(echo->ctx, seq, sizeof(seq));
}
}

static void ld_process_byte(line_discipline* ld, ring_buffer* sink,
const echo_target* echo, char c,
bool hold_lock, sync::irq_state& irq) {
const echo_target* echo, const signal_target* sig,
char c, bool hold_lock, sync::irq_state& irq) {
uint32_t signum = ld->isig ? signal_for_char(c) : 0;
if (signum) {
// The byte becomes a signal instead of input, and the pending
// line is flushed (Linux ISIG semantics)
bool cooked = ld->mode != LD_MODE_RAW;
ld->line_len = 0;
ld->prev_char = c;
if (!hold_lock) sync::spin_unlock_irqrestore(ld->lock, irq);
if (cooked) {
echo_signal_char(echo, c);
}
if (sig && sig->send) {
sig->send(sig->ctx, signum);
}
return;
}

if (ld->mode == LD_MODE_RAW) {
ld->prev_char = c;
if (!hold_lock) sync::spin_unlock_irqrestore(ld->lock, irq);
Expand Down Expand Up @@ -70,24 +111,28 @@ static void ld_process_byte(line_discipline* ld, ring_buffer* sink,
}

__PRIVILEGED_CODE void ld_input(line_discipline* ld, ring_buffer* sink,
const echo_target* echo, char c) {
const echo_target* echo,
const signal_target* sig, char c) {
sync::irq_state irq = sync::spin_lock_irqsave(ld->lock);
ld_process_byte(ld, sink, echo, c, false, irq);
ld_process_byte(ld, sink, echo, sig, c, false, irq);
}

__PRIVILEGED_CODE void ld_input_buf(line_discipline* ld, ring_buffer* sink,
const echo_target* echo,
const signal_target* sig,
const char* buf, size_t len) {
sync::irq_state irq = sync::spin_lock_irqsave(ld->lock);

if (ld->mode == LD_MODE_RAW) {
// The raw bulk path only applies with ISIG off, interception needs
// the per-byte scan
if (ld->mode == LD_MODE_RAW && !ld->isig) {
sync::spin_unlock_irqrestore(ld->lock, irq);
(void)ring_buffer_write(sink, reinterpret_cast<const uint8_t*>(buf), len, true);
return;
}

for (size_t i = 0; i < len; i++) {
ld_process_byte(ld, sink, echo, buf[i], true, irq);
ld_process_byte(ld, sink, echo, sig, buf[i], true, irq);
}

sync::spin_unlock_irqrestore(ld->lock, irq);
Expand All @@ -109,9 +154,20 @@ int32_t ld_set_mode(line_discipline* ld, uint32_t cmd) {
ld->line_len = 0;
ld->mode = new_mode;
}
// The shortcuts pair ISIG with the mode, raw callers expect
// control bytes verbatim (cfmakeraw clears ISIG the same way)
ld->isig = (new_mode == LD_MODE_RAW) ? 0u : 1u;
sync::spin_unlock_irqrestore(ld->lock, irq);
});
return OK;
}

void ld_set_isig(line_discipline* ld, bool on) {
RUN_ELEVATED({
sync::irq_state irq = sync::spin_lock_irqsave(ld->lock);
ld->isig = on ? 1u : 0u;
sync::spin_unlock_irqrestore(ld->lock, irq);
});
}

} // namespace terminal
Loading
Loading