Compare commits

..

5 Commits

Author SHA1 Message Date
81e1fdf81e
kernel+libc+login+sh+su: Implement foreground and background process groups in the default console
All checks were successful
continuous-integration/drone/push Build is passing
Also, the console sends SIGINT to the foreground process group when ^C is pressed!
2023-07-12 13:49:37 +02:00
9f45026cc2
kernel+sh: Implement interruptible syscalls 2023-07-12 13:48:43 +02:00
71ff763dd9
kernel+libc: Add the SIGTTIN and SIGTTOU signals 2023-07-12 13:45:36 +02:00
b64093dee5
kernel+libc: Implement getpgid() 2023-07-12 13:44:25 +02:00
d27ffce5db
kernel: Move the signal handling logic to after a syscall sets its return value
When a signal was caught after a syscall, it was doing so without preserving the return value of the syscall.
2023-07-12 13:34:30 +02:00
19 changed files with 244 additions and 18 deletions

View File

@ -1,7 +1,10 @@
#include <bits/termios.h>
#include <luna/String.h>
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <signal.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
Result<int> luna_main(int argc, char** argv)
@ -26,6 +29,14 @@ Result<int> luna_main(int argc, char** argv)
if (username.is_empty())
{
signal(SIGTTOU, SIG_IGN);
if (isatty(STDIN_FILENO))
{
pid_t pgid = getpgid(0);
ioctl(STDIN_FILENO, TIOCSPGRP, &pgid);
}
auto input = os::File::standard_input();
os::print("Username: ");

View File

@ -1,3 +1,4 @@
#include <bits/termios.h>
#include <errno.h>
#include <luna/String.h>
#include <luna/Vector.h>
@ -11,6 +12,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/utsname.h>
#include <unistd.h>
@ -31,6 +33,11 @@ static Result<void> execute_command(StringView command)
return os::Process::exec(args[0].view(), args.slice());
}
// Do nothing, but we need a handler so read() returns EINTR.
static void sigint_handler(int)
{
}
struct utsname g_sysinfo;
const char* hostname = "";
@ -41,7 +48,6 @@ Result<int> luna_main(int argc, char** argv)
{
StringView path;
StringView command;
bool interactive { false };
SharedPtr<File> input_file;
@ -54,17 +60,15 @@ Result<int> luna_main(int argc, char** argv)
if (!command.is_empty()) TRY(execute_command(command));
if (path == "-")
{
input_file = File::standard_input();
interactive = true;
}
if (path == "-") { input_file = File::standard_input(); }
else
{
input_file = TRY(File::open(path, File::ReadOnly));
input_file->set_close_on_exec();
}
bool interactive = isatty(input_file->fd());
if (interactive)
{
// Set up everything to form a prompt.
@ -77,6 +81,12 @@ Result<int> luna_main(int argc, char** argv)
if (pw) { username = pw->pw_name; }
else { username = getenv("USER"); }
endpwent();
signal(SIGTTOU, SIG_IGN);
signal(SIGINT, sigint_handler);
pid_t pgid = getpgid(0);
ioctl(STDIN_FILENO, TIOCSPGRP, &pgid);
}
while (1)
@ -87,7 +97,14 @@ Result<int> luna_main(int argc, char** argv)
os::print("%s@%s:%s%c ", username, hostname, cwd.chars(), prompt_end);
}
auto cmd = TRY(input_file->read_line());
auto maybe_cmd = input_file->read_line();
if (maybe_cmd.has_error())
{
if (maybe_cmd.error() == EINTR) continue;
return maybe_cmd.release_error();
}
auto cmd = maybe_cmd.release_value();
if (cmd.is_empty())
{
if (interactive) puts("exit");
@ -116,11 +133,26 @@ Result<int> luna_main(int argc, char** argv)
pid_t child = TRY(os::Process::fork());
if (child == 0) { TRY(execute_command(cmd.view())); }
if (child == 0)
{
if (interactive)
{
setpgid(0, 0);
pid_t pgid = getpgid(0);
ioctl(STDIN_FILENO, TIOCSPGRP, &pgid);
}
TRY(execute_command(cmd.view()));
}
int status;
TRY(os::Process::wait(child, &status));
if (interactive)
{
pid_t pgid = getpgid(0);
ioctl(STDIN_FILENO, TIOCSPGRP, &pgid);
}
if (WIFSIGNALED(status))
{
int sig = WTERMSIG(status);

View File

@ -1,6 +1,7 @@
#include <bits/termios.h>
#include <os/ArgumentParser.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
@ -13,8 +14,24 @@ void restore_terminal()
ioctl(fileno(stdin), TCSETS, &orig);
}
void sigint_handler(int)
{
restore_terminal();
raise(SIGINT);
}
char* getpass()
{
if (!isatty(STDIN_FILENO))
{
// FIXME: Just read from /dev/tty (the controlling terminal). Problem: that doesn't exist yet.
fprintf(stderr, "error: password must be read from a terminal!");
return nullptr;
}
pid_t pgid = getpgid(0);
ioctl(STDIN_FILENO, TIOCSPGRP, &pgid);
fputs("Password: ", stdout);
if (ioctl(fileno(stdin), TCGETS, &orig) < 0)
@ -23,6 +40,12 @@ char* getpass()
return nullptr;
}
struct sigaction sa;
sa.sa_handler = sigint_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESETHAND;
sigaction(SIGINT, &sa, NULL);
atexit(restore_terminal);
struct termios tc = orig;
@ -82,6 +105,8 @@ Result<int> luna_main(int argc, char** argv)
if ((prompt_password || getuid() != geteuid()) && *entry->pw_passwd)
{
signal(SIGTTOU, SIG_IGN);
char* pass = getpass();
if (!pass) return 1;
@ -102,6 +127,7 @@ Result<int> luna_main(int argc, char** argv)
chdir(entry->pw_dir);
clearenv();
setenv("PATH", "/bin:/sbin", 1);
setpgid(0, 0);
}
if (login || entry->pw_uid != 0) setenv("USER", entry->pw_name, 1);

View File

@ -186,6 +186,7 @@ extern "C" void arch_interrupt_entry(Registers* regs)
{
SyscallArgs args = { regs->rdi, regs->rsi, regs->rdx, regs->r10, regs->r8, regs->r9 };
regs->rax = (u64)invoke_syscall(regs, args, regs->rax);
Scheduler::current()->process_pending_signals(regs);
}
else
{

View File

@ -15,15 +15,42 @@
static Buffer g_console_input;
static bool g_eof { false };
static bool g_echo { true };
static bool g_stop_background_output { false };
static Option<pid_t> g_foreground_process_group;
Result<void> ConsoleDevice::create()
{
auto device = (SharedPtr<Device>)TRY(make_shared<ConsoleDevice>());
g_foreground_process_group = {};
return DeviceRegistry::register_special_device(DeviceRegistry::Console, 0, device);
}
static Result<void> handle_background_process_group(bool can_succeed, int signo)
{
if (!g_foreground_process_group.has_value()) return {};
auto foreground_pgrp = g_foreground_process_group.value();
auto* current = Scheduler::current();
if ((pid_t)current->pgid == foreground_pgrp) return {};
if ((current->signal_mask & (1 << (signo - 1))) || (current->signal_handlers[signo - 1].sa_handler == SIG_IGN))
{
if (can_succeed) return {};
return err(EIO);
}
current->send_signal(signo);
if (can_succeed) return err(EINTR);
return err(EIO);
}
Result<usize> ConsoleDevice::read(u8* buf, usize, usize length) const
{
TRY(handle_background_process_group(false, SIGTTIN));
if (length > g_console_input.size()) length = g_console_input.size();
memcpy(buf, g_console_input.data(), length);
@ -39,6 +66,8 @@ Result<usize> ConsoleDevice::read(u8* buf, usize, usize length) const
Result<usize> ConsoleDevice::write(const u8* buf, usize, usize length)
{
if (g_stop_background_output) TRY(handle_background_process_group(true, SIGTTOU));
TextConsole::write((const char*)buf, length);
return length;
}
@ -63,19 +92,36 @@ void ConsoleDevice::did_press_key(char key)
}
// Ctrl+D
if (key == 'd' && (Keyboard::modifiers() & Keyboard::LeftControl))
if (key == 'd' && (Keyboard::modifiers() == Keyboard::LeftControl))
{
if (g_temp_input.size() == 0) g_eof = true;
return;
}
if (key == 'e' && (Keyboard::modifiers() & (Keyboard::LeftAlt | Keyboard::LeftControl)))
if (key == 'c' && (Keyboard::modifiers() == Keyboard::LeftControl))
{
g_temp_input.clear();
if (g_echo) TextConsole::wprintln(L"^C");
if (g_foreground_process_group.has_value())
{
Scheduler::for_each_in_process_group(g_foreground_process_group.value(), [](Thread* thread) {
thread->send_signal(SIGINT);
return true;
});
}
return;
}
if (key == 'e' && (Keyboard::modifiers() == (Keyboard::LeftAlt | Keyboard::LeftControl)))
{
Scheduler::dump_state();
return;
}
if (key == 'm' && (Keyboard::modifiers() & (Keyboard::LeftAlt | Keyboard::LeftControl)))
if (key == 'm' && (Keyboard::modifiers() == (Keyboard::LeftAlt | Keyboard::LeftControl)))
{
kinfoln("Total memory: %s", to_dynamic_unit(MemoryManager::total()).release_value().chars());
kinfoln("Free memory: %s", to_dynamic_unit(MemoryManager::free()).release_value().chars());
@ -84,7 +130,7 @@ void ConsoleDevice::did_press_key(char key)
return;
}
if (key == 'h' && (Keyboard::modifiers() & (Keyboard::LeftAlt | Keyboard::LeftControl)))
if (key == 'h' && (Keyboard::modifiers() == (Keyboard::LeftAlt | Keyboard::LeftControl)))
{
dump_heap_usage();
return;
@ -113,15 +159,38 @@ Result<u64> ConsoleDevice::ioctl(int request, void* arg)
};
if (g_echo) tc.c_lflag |= ECHO;
if (g_stop_background_output) tc.c_lflag |= TOSTOP;
return MemoryManager::copy_to_user_typed((struct termios*)arg, &tc) ? 0 : err(EFAULT);
}
case TCSETS: {
TRY(handle_background_process_group(true, SIGTTOU));
struct termios tc;
if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &tc)) return err(EFAULT);
if (tc.c_lflag & ECHO) g_echo = true;
else
g_echo = false;
if (tc.c_lflag & TOSTOP) g_stop_background_output = true;
else
g_stop_background_output = false;
return 0;
}
case TIOCSPGRP: {
TRY(handle_background_process_group(true, SIGTTOU));
pid_t pgid;
if (!MemoryManager::copy_from_user_typed((const pid_t*)arg, &pgid)) return err(EFAULT);
bool pgid_exists = false;
Scheduler::for_each_in_process_group(pgid, [&pgid_exists](Thread*) {
pgid_exists = true;
return false;
});
if (!pgid_exists) return err(EPERM);
g_foreground_process_group = pgid;
return 0;
}
default: return err(EINVAL);

View File

@ -15,8 +15,6 @@ i64 invoke_syscall(Registers* regs, SyscallArgs args, u64 syscall)
auto rc = syscalls[syscall](regs, args);
Scheduler::current()->process_pending_signals(regs);
if (rc.has_error()) return -rc.error();
return (i64)rc.value();
}

View File

@ -10,7 +10,7 @@
#include <luna/SafeArithmetic.h>
#include <sys/types.h>
Result<u64> sys_read(Registers*, SyscallArgs args)
Result<u64> sys_read(Registers* regs, SyscallArgs args)
{
int fd = (int)args[0];
u8* buf = (u8*)args[1];
@ -31,6 +31,13 @@ Result<u64> sys_read(Registers*, SyscallArgs args)
if (descriptor.should_block()) kernel_sleep(10);
else
return err(EAGAIN);
if (current->interrupted)
{
kdbgln("signal: read interrupted by signal");
if (current->will_invoke_signal_handler()) return err(EINTR);
current->process_pending_signals(regs);
}
}
usize nread = TRY(descriptor.inode->read(buf, descriptor.offset, size));

View File

@ -128,6 +128,20 @@ Result<u64> sys_setpgid(Registers*, SyscallArgs args)
return 0;
}
Result<u64> sys_getpgid(Registers*, SyscallArgs args)
{
pid_t pid = (pid_t)args[0];
auto* current = Scheduler::current();
if (pid == 0) pid = (pid_t)current->id;
if (pid < 0) return err(EINVAL);
auto* thread = TRY(Result<Thread*>::from_option(Scheduler::find_by_pid(pid), ESRCH));
return (u64)thread->pgid;
}
Result<u64> sys_fchmodat(Registers*, SyscallArgs args)
{
int dirfd = (int)args[0];

View File

@ -11,5 +11,7 @@ Result<u64> sys_usleep(Registers*, SyscallArgs args)
kernel_sleep(us / 1000);
return 0;
auto* current = Scheduler::current();
return current->sleep_ticks_left;
}

View File

@ -1,9 +1,10 @@
#include "Log.h"
#include "memory/MemoryManager.h"
#include "sys/Syscall.h"
#include "thread/Scheduler.h"
#include <bits/waitpid.h>
Result<u64> sys_waitpid(Registers*, SyscallArgs args)
Result<u64> sys_waitpid(Registers* regs, SyscallArgs args)
{
pid_t pid = (pid_t)args[0];
int* status_ptr = (int*)args[1];
@ -21,7 +22,16 @@ Result<u64> sys_waitpid(Registers*, SyscallArgs args)
if (options & WNOHANG) return err(EAGAIN);
wait_for_child:
if (thread->state != ThreadState::Exited) kernel_wait(pid);
if (current->interrupted)
{
kdbgln("signal: waitpid interrupted by signal");
if (current->will_invoke_signal_handler()) return err(EINTR);
current->process_pending_signals(regs);
goto wait_for_child;
}
check(thread->state == ThreadState::Exited);
}
else if (pid == -1)
@ -33,7 +43,16 @@ Result<u64> sys_waitpid(Registers*, SyscallArgs args)
{
if (options & WNOHANG) return err(EAGAIN);
wait_for_any_child:
kernel_wait(pid);
if (current->interrupted)
{
kdbgln("signal: waitpid interrupted by signal");
if (current->will_invoke_signal_handler()) return err(EINTR);
current->process_pending_signals(regs);
goto wait_for_any_child;
}
check(current->child_being_waited_for.value_or(-1) != -1);
thread = TRY(Result<Thread*>::from_option(Scheduler::find_by_pid(*current->child_being_waited_for), ESRCH));

View File

@ -135,6 +135,7 @@ namespace Scheduler
thread->state = ThreadState::None;
thread->is_kernel = false;
thread->id = 1;
thread->pgid = 1;
thread->name = name;
thread->auth = Credentials { .uid = 0, .euid = 0, .suid = 0, .gid = 0, .egid = 0, .sgid = 0 };

View File

@ -110,6 +110,7 @@ enum class DefaultSignalAction
Terminate,
};
// FIXME: Implement coredumps for some signals.
static constexpr DefaultSignalAction default_actions[] = {
DefaultSignalAction::Terminate, // SIGHUP
DefaultSignalAction::Terminate, // SIGINT
@ -126,10 +127,13 @@ static constexpr DefaultSignalAction default_actions[] = {
DefaultSignalAction::Terminate, // SIGPIPE
DefaultSignalAction::Terminate, // SIGALRM
DefaultSignalAction::Terminate, // SIGTERM
DefaultSignalAction::Terminate, // SIGTTIN
DefaultSignalAction::Terminate, // SIGTTOU
};
void Thread::process_pending_signals(Registers* current_regs)
{
interrupted = false;
for (int i = 0; i < NSIG; i++)
{
int signo = i + 1;
@ -172,10 +176,33 @@ void Thread::process_pending_signals(Registers* current_regs)
}
}
bool Thread::will_invoke_signal_handler()
{
for (int i = 0; i < NSIG; i++)
{
if (pending_signals & (1 << i))
{
int signo = i + 1;
if (signo != SIGKILL && signo != SIGSTOP && signal_mask & (1 << i)) continue;
auto handler = signal_handlers[i];
if (handler.sa_handler == SIG_IGN || handler.sa_handler == SIG_DFL) return false;
if (signo == SIGKILL || signo == SIGSTOP) return false;
return true;
}
}
return false;
}
void Thread::send_signal(int signo)
{
check(signo > 0 && signo <= NSIG);
pending_signals |= 1 << (signo - 1);
if (state == ThreadState::Waiting || state == ThreadState::Sleeping)
{
interrupted = true;
wake_up();
}
}
bool FileDescriptor::should_append()

View File

@ -84,6 +84,7 @@ struct Thread : public LinkedListNode<Thread>
struct sigaction signal_handlers[NSIG];
sigset_t signal_mask { 0 };
sigset_t pending_signals { 0 };
bool interrupted { false };
FPData fp_data;
@ -139,6 +140,8 @@ struct Thread : public LinkedListNode<Thread>
void process_pending_signals(Registers* current_regs);
bool will_invoke_signal_handler();
bool deliver_signal(int signo, Registers* current_regs);
void sigreturn(Registers* current_regs);

View File

@ -49,6 +49,8 @@ enum __signals
SIGPIPE = 13,
SIGALRM = 14,
SIGTERM = 15,
SIGTTIN = 16,
SIGTTOU = 17,
// FIXME: Add the remaining signals.
__NSIG,
};

View File

@ -13,9 +13,11 @@ struct termios
// Values for c_lflag.
#define ECHO 1
#define TOSTOP 2
// termios ioctl() requests.
#define TCGETS 0
#define TCSETS 1
#define TIOCSPGRP 2
#endif

View File

@ -45,6 +45,9 @@ extern "C"
/* Return the current process' effective group ID. */
gid_t getegid(void);
/* Return a process' process group ID. */
pid_t getpgid(pid_t pid);
/* Set the current process' user IDs. */
int setuid(uid_t uid);

View File

@ -33,6 +33,8 @@ extern "C"
case SIGPIPE: return "Broken pipe";
case SIGALRM: return "Alarm signal";
case SIGTERM: return "Terminated";
case SIGTTIN: return "Background process stopped (terminal input)";
case SIGTTOU: return "Background process stopped (terminal output)";
default: return "Unknown signal";
}
}

View File

@ -120,6 +120,12 @@ extern "C"
return (gid_t)syscall(SYS_getegid);
}
pid_t getpgid(pid_t pid)
{
long rc = syscall(SYS_getpgid, pid);
__errno_return(rc, pid_t);
}
int setuid(uid_t uid)
{
long rc = syscall(SYS_setuid, uid);

View File

@ -6,7 +6,8 @@
_e(getgid) _e(getegid) _e(setuid) _e(setgid) _e(seteuid) _e(setegid) _e(fchmodat) _e(fchownat) _e(ioctl) \
_e(fstatat) _e(chdir) _e(getcwd) _e(unlinkat) _e(uname) _e(sethostname) _e(dup2) _e(pipe) _e(mount) \
_e(umount) _e(pstat) _e(getrusage) _e(symlinkat) _e(readlinkat) _e(umask) _e(linkat) _e(faccessat) \
_e(pivot_root) _e(sigreturn) _e(sigaction) _e(kill) _e(sigprocmask) _e(setpgid) _e(isatty)
_e(pivot_root) _e(sigreturn) _e(sigaction) _e(kill) _e(sigprocmask) _e(setpgid) _e(isatty) \
_e(getpgid)
enum Syscalls
{