diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index e602a1c5..a7cff7e5 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -57,6 +57,8 @@ set(SOURCES src/net/UnixSocket.cpp src/fs/tmpfs/FileSystem.cpp src/fs/tmpfs/Inode.cpp + src/fs/devpts/FileSystem.cpp + src/fs/devpts/Inode.cpp src/fs/ext2/FileSystem.cpp src/fs/ext2/Inode.cpp src/fs/devices/DeviceRegistry.cpp @@ -69,6 +71,9 @@ set(SOURCES src/fs/devices/UARTDevice.cpp src/fs/devices/MouseDevice.cpp src/fs/devices/KeyboardDevice.cpp + src/fs/devices/PTYMultiplexer.cpp + src/fs/devices/MasterPTY.cpp + src/fs/devices/SlavePTY.cpp src/fs/InitRD.cpp src/binfmt/ELF.cpp src/binfmt/BinaryFormat.cpp diff --git a/kernel/src/fs/devices/DeviceRegistry.cpp b/kernel/src/fs/devices/DeviceRegistry.cpp index 33d6a946..a46b1d10 100644 --- a/kernel/src/fs/devices/DeviceRegistry.cpp +++ b/kernel/src/fs/devices/DeviceRegistry.cpp @@ -7,6 +7,7 @@ #include "fs/devices/KeyboardDevice.h" #include "fs/devices/MouseDevice.h" #include "fs/devices/NullDevice.h" +#include "fs/devices/PTYMultiplexer.h" #include "fs/devices/UARTDevice.h" #include "fs/devices/ZeroDevice.h" #include "fs/tmpfs/FileSystem.h" @@ -92,6 +93,12 @@ namespace DeviceRegistry for (const auto& descriptor : g_available_devices) TRY(create_special_device_inode(fs, descriptor)); + auto multiplexer = TRY(make_shared()); + multiplexer->set_fs(*fs); + multiplexer->set_inode_number(TRY(fs->allocate_inode_number())); + + TRY(fs->root_inode()->add_entry(multiplexer, "ptmx")); + return fs; } } diff --git a/kernel/src/fs/devices/DeviceRegistry.h b/kernel/src/fs/devices/DeviceRegistry.h index e4a8504d..7ee4a89d 100644 --- a/kernel/src/fs/devices/DeviceRegistry.h +++ b/kernel/src/fs/devices/DeviceRegistry.h @@ -17,6 +17,7 @@ namespace DeviceRegistry DiskPartition = 5, Serial = 6, Input = 7, + Terminal = 8, }; Result> fetch_special_device(u32 major, u32 minor); diff --git a/kernel/src/fs/devices/MasterPTY.cpp b/kernel/src/fs/devices/MasterPTY.cpp new file mode 100644 index 00000000..6cf7dc20 --- /dev/null +++ b/kernel/src/fs/devices/MasterPTY.cpp @@ -0,0 +1,125 @@ +#include "fs/devices/MasterPTY.h" +#include "Pledge.h" +#include "fs/devices/DeviceRegistry.h" +#include "fs/devices/PTYMultiplexer.h" +#include "fs/devpts/FileSystem.h" +#include "memory/MemoryManager.h" +#include "thread/Scheduler.h" + +Result> MasterPTY::create_pair(int index) +{ + auto master = TRY(make_shared()); + auto slave = TRY(make_shared()); + + auto name = TRY(String::format("%d"_sv, index)); + for (auto& fs : g_devpts_instances) { fs->root_inode()->add_entry(slave, name.chars()); } + slave->m_name = move(name); + + master->m_metadata.mode = 0666; + master->m_index = index; + master->m_slave = slave; + master->m_metadata.devid = luna_dev_makedev(DeviceRegistry::Terminal, 0); + master->m_settings.c_lflag = ECHO | ECHOE | ECHOCTL | ISIG | ICANON; + master->m_settings.c_cc[VEOF] = '\4'; + master->m_settings.c_cc[VERASE] = '\b'; + master->m_settings.c_cc[VINTR] = '\3'; + master->m_settings.c_cc[VQUIT] = '\x1c'; + master->m_window.ws_col = 80; + master->m_window.ws_row = 25; + + slave->m_master = master.ptr(); + slave->m_metadata.devid = luna_dev_makedev(DeviceRegistry::Terminal, index + 1); + slave->m_metadata.uid = Scheduler::current()->auth.euid; + slave->m_metadata.gid = Scheduler::current()->auth.egid; + slave->m_metadata.mode = 0620; + + return (SharedPtr)master; +} + +Result MasterPTY::handle_background_process_group(bool can_succeed, int signo) const +{ + if (!m_foreground_process_group.has_value()) return {}; + + auto foreground_pgrp = m_foreground_process_group.value(); + + auto* current = Scheduler::current(); + if (current->pgid == foreground_pgrp) return {}; + + if ((current->signal_mask.get(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 MasterPTY::read(u8* buf, usize, usize length) const +{ + length = m_buffer.dequeue_data(buf, length); + + return length; +} + +Result MasterPTY::write(const u8* buf, usize, usize length) +{ + TRY(m_slave->m_buffer.append_data(buf, length)); + + return length; +} + +Result MasterPTY::ioctl(int request, void* arg) +{ + auto* current = Scheduler::current(); + TRY(check_pledge(current, Promise::p_tty)); + + switch (request) + { + case TCGETS: { + return MemoryManager::copy_to_user_typed((struct termios*)arg, &m_settings) ? 0 : err(EFAULT); + } + case TCSETS: { + if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &m_settings)) return err(EFAULT); + + return 0; + } + case TIOCSPGRP: { + 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); + + m_foreground_process_group = pgid; + return 0; + } + case TIOCGPGRP: { + pid_t pgid = m_foreground_process_group.value_or((pid_t)next_thread_id()); + if (!MemoryManager::copy_to_user_typed((pid_t*)arg, &pgid)) return err(EFAULT); + return 0; + } + case TIOCGWINSZ: { + if (!MemoryManager::copy_to_user_typed((struct winsize*)arg, &m_window)) return err(EFAULT); + return 0; + } + case TIOCGPTN: { + if (!MemoryManager::copy_to_user_typed((int*)arg, &m_index)) return err(EFAULT); + return 0; + } + default: return err(EINVAL); + } +} + +MasterPTY::~MasterPTY() +{ + m_slave->m_master = nullptr; + for (auto& fs : g_devpts_instances) { fs->root_inode()->remove_entry(m_slave->m_name.chars()); } + PTYMultiplexer::did_remove_pty(m_index); +} diff --git a/kernel/src/fs/devices/MasterPTY.h b/kernel/src/fs/devices/MasterPTY.h new file mode 100644 index 00000000..a4131a09 --- /dev/null +++ b/kernel/src/fs/devices/MasterPTY.h @@ -0,0 +1,75 @@ +#pragma once +#include "fs/VFS.h" +#include "fs/devices/SlavePTY.h" +#include +#include + +class MasterPTY : public VFS::DeviceInode +{ + public: + MasterPTY() = default; + + static Result> create_pair(int index); + + VFS::InodeType type() const override + { + return VFS::InodeType::CharacterDevice; + } + + Result query_shared_memory(off_t, usize) override + { + return err(ENOTSUP); + } + + VFS::FileSystem* fs() const override + { + return nullptr; + } + + Result read(u8* buf, usize offset, usize length) const override; + + Result write(const u8* buf, usize offset, usize length) override; + + Result ioctl(int request, void* arg) override; + + Result isatty() const override + { + return 1; + } + + Result truncate(usize) override + { + // POSIX says truncate is for regular files, but doesn't tell us what error to return for non-regular files. + return err(EINVAL); + } + + bool will_block_if_read() const override + { + return m_buffer.is_empty(); + } + + void did_link() override + { + m_metadata.nlinks++; + } + + void did_unlink() override + { + m_metadata.nlinks--; + } + + virtual ~MasterPTY(); + + private: + struct termios m_settings; + mutable Buffer m_buffer; + SharedPtr m_slave; + mutable Option m_foreground_process_group; + struct winsize m_window; + + Result handle_background_process_group(bool can_succeed, int signo) const; + + int m_index; + + friend class SlavePTY; +}; diff --git a/kernel/src/fs/devices/PTYMultiplexer.cpp b/kernel/src/fs/devices/PTYMultiplexer.cpp new file mode 100644 index 00000000..74216560 --- /dev/null +++ b/kernel/src/fs/devices/PTYMultiplexer.cpp @@ -0,0 +1,36 @@ +#include "fs/devices/PTYMultiplexer.h" + +Bitset PTYMultiplexer::m_available_indexes = 0; + +PTYMultiplexer::PTYMultiplexer() +{ + m_metadata.devid = luna_dev_makedev(DeviceRegistry::Terminal, 0); + m_metadata.mode = 0666; +} + +Result> PTYMultiplexer::open() +{ + int index = -1; + for (int i = 0; i < 64; i++) + { + if (!m_available_indexes.get(i)) + { + index = i; + m_available_indexes.set(i, true); + break; + } + } + if (index == -1) return err(ENOSPC); + + return MasterPTY::create_pair(index); +} + +void PTYMultiplexer::init() +{ + m_available_indexes.clear(); +} + +void PTYMultiplexer::did_remove_pty(int index) +{ + m_available_indexes.set(index, false); +} diff --git a/kernel/src/fs/devices/PTYMultiplexer.h b/kernel/src/fs/devices/PTYMultiplexer.h new file mode 100644 index 00000000..1ec53dad --- /dev/null +++ b/kernel/src/fs/devices/PTYMultiplexer.h @@ -0,0 +1,80 @@ +#pragma once +#include "fs/VFS.h" +#include "fs/devices/DeviceRegistry.h" +#include "fs/devices/MasterPTY.h" +#include + +class PTYMultiplexer : public VFS::DeviceInode +{ + public: + PTYMultiplexer(); + + VFS::InodeType type() const override + { + return VFS::InodeType::CharacterDevice; + } + + void set_fs(VFS::FileSystem& fs) + { + m_fs = &fs; + } + + void set_inode_number(usize inum) + { + m_metadata.inum = inum; + } + + Result query_shared_memory(off_t, usize) override + { + unreachable(); + } + + Result> open() override; + + VFS::FileSystem* fs() const override + { + return m_fs; + } + + Result read(u8*, usize, usize) const override + { + unreachable(); + } + + Result write(const u8*, usize, usize) override + { + unreachable(); + } + + Result truncate(usize) override + { + // POSIX says truncate is for regular files, but doesn't tell us what error to return for non-regular files. + return err(EINVAL); + } + + bool will_block_if_read() const override + { + unreachable(); + } + + void did_link() override + { + m_metadata.nlinks++; + } + + void did_unlink() override + { + m_metadata.nlinks--; + } + + static void init(); + + static void did_remove_pty(int index); + + virtual ~PTYMultiplexer() = default; + + private: + VFS::FileSystem* m_fs; + + static Bitset m_available_indexes; +}; diff --git a/kernel/src/fs/devices/SlavePTY.cpp b/kernel/src/fs/devices/SlavePTY.cpp new file mode 100644 index 00000000..c5f7d4a7 --- /dev/null +++ b/kernel/src/fs/devices/SlavePTY.cpp @@ -0,0 +1,71 @@ +#include "fs/devices/SlavePTY.h" +#include "Pledge.h" +#include "fs/devices/MasterPTY.h" +#include "memory/MemoryManager.h" +#include "thread/Scheduler.h" + +Result SlavePTY::read(u8* buf, usize, usize length) const +{ + if (!m_master) return err(EIO); + + TRY(m_master->handle_background_process_group(false, SIGTTIN)); + + length = m_buffer.dequeue_data(buf, length); + + return length; +} + +Result SlavePTY::write(const u8* buf, usize, usize length) +{ + if (!m_master) return err(EIO); + + if (m_master->m_settings.c_lflag & TOSTOP) TRY(m_master->handle_background_process_group(true, SIGTTOU)); + + TRY(m_master->m_buffer.append_data(buf, length)); + + return length; +} + +Result SlavePTY::ioctl(int request, void* arg) +{ + auto* current = Scheduler::current(); + TRY(check_pledge(current, Promise::p_tty)); + + if (!m_master) return err(EIO); + + switch (request) + { + case TCGETS: { + return MemoryManager::copy_to_user_typed((struct termios*)arg, &m_master->m_settings) ? 0 : err(EFAULT); + } + case TCSETS: { + if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &m_master->m_settings)) return err(EFAULT); + + return 0; + } + case TIOCSPGRP: { + 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); + + m_master->m_foreground_process_group = pgid; + return 0; + } + case TIOCGPGRP: { + pid_t pgid = m_master->m_foreground_process_group.value_or((pid_t)next_thread_id()); + if (!MemoryManager::copy_to_user_typed((pid_t*)arg, &pgid)) return err(EFAULT); + return 0; + } + case TIOCGWINSZ: { + if (!MemoryManager::copy_to_user_typed((struct winsize*)arg, &m_master->m_window)) return err(EFAULT); + return 0; + } + default: return err(EINVAL); + } +} diff --git a/kernel/src/fs/devices/SlavePTY.h b/kernel/src/fs/devices/SlavePTY.h new file mode 100644 index 00000000..dafba4f9 --- /dev/null +++ b/kernel/src/fs/devices/SlavePTY.h @@ -0,0 +1,69 @@ +#pragma once +#include "fs/VFS.h" +#include +#include + +class MasterPTY; + +class SlavePTY : public VFS::DeviceInode +{ + public: + SlavePTY() = default; + + VFS::InodeType type() const override + { + return VFS::InodeType::CharacterDevice; + } + + Result query_shared_memory(off_t, usize) override + { + return err(ENOTSUP); + } + + VFS::FileSystem* fs() const override + { + return nullptr; + } + + Result read(u8* buf, usize offset, usize length) const override; + + Result write(const u8* buf, usize offset, usize length) override; + + Result ioctl(int request, void* arg) override; + + Result isatty() const override + { + return 1; + } + + Result truncate(usize) override + { + // POSIX says truncate is for regular files, but doesn't tell us what error to return for non-regular files. + return err(EINVAL); + } + + bool will_block_if_read() const override + { + return m_buffer.is_empty(); + } + + void did_link() override + { + m_metadata.nlinks++; + } + + void did_unlink() override + { + m_metadata.nlinks--; + } + + virtual ~SlavePTY() = default; + + private: + mutable Buffer m_buffer; + + MasterPTY* m_master; + String m_name; + + friend class MasterPTY; +}; diff --git a/kernel/src/fs/devpts/FileSystem.cpp b/kernel/src/fs/devpts/FileSystem.cpp new file mode 100644 index 00000000..b7d29cac --- /dev/null +++ b/kernel/src/fs/devpts/FileSystem.cpp @@ -0,0 +1,62 @@ +#include "fs/devpts/FileSystem.h" +#include "arch/Timer.h" +#include "fs/devices/DeviceRegistry.h" +#include "fs/devpts/Inode.h" +#include +#include +#include + +Vector g_devpts_instances; + +namespace DevPTS +{ + Result> FileSystem::create() + { + SharedPtr fs = TRY(adopt_shared_if_nonnull(new (std::nothrow) FileSystem())); + SharedPtr root = TRY(make_shared()); + + TRY(root->add_entry(root, ".")); + TRY(root->add_entry(root, "..")); + + root->set_self(root, {}); + root->set_fs(*fs, {}); + root->set_inode_number(); + root->m_metadata.mode = 0755; + root->m_metadata.atime = root->m_metadata.ctime = root->m_metadata.mtime = *Timer::realtime_clock(); + fs->set_root(root); + + TRY(g_devpts_instances.try_append(fs.ptr())); + + return (SharedPtr)fs; + } + + Result FileSystem::allocate_inode_number() + { + return err(ENOTSUP); + } + + FileSystem::FileSystem() + { + m_host_device_id = DeviceRegistry::next_null_device_id(); + } + + Result FileSystem::set_mount_dir(SharedPtr parent) + { + return m_root_inode->replace_entry(parent, ".."); + } + + Result FileSystem::reset_mount_dir() + { + return m_root_inode->replace_entry(m_root_inode, ".."); + } + + void FileSystem::set_root(SharedPtr root) + { + m_root_inode = root; + } + + FileSystem::~FileSystem() + { + g_devpts_instances.remove_first_matching([this](VFS::FileSystem* ptr) { return ptr == this; }); + } +} diff --git a/kernel/src/fs/devpts/FileSystem.h b/kernel/src/fs/devpts/FileSystem.h new file mode 100644 index 00000000..f9101938 --- /dev/null +++ b/kernel/src/fs/devpts/FileSystem.h @@ -0,0 +1,61 @@ +#pragma once +#include "fs/VFS.h" +#include "fs/devices/DeviceRegistry.h" + +namespace DevPTS +{ + class FileSystem : public VFS::FileSystem + { + public: + SharedPtr root_inode() const override + { + return m_root_inode; + } + + Result> create_file_inode(mode_t) override + { + return err(ENOTSUP); + } + + Result> create_dir_inode(SharedPtr, mode_t) override + { + return err(ENOTSUP); + } + + Result> create_device_inode(u32, u32, mode_t) override + { + return err(ENOTSUP); + } + + Result> create_symlink_inode(StringView) override + { + return err(ENOTSUP); + } + + Result allocate_inode_number() override; + + Result set_mount_dir(SharedPtr parent) override; + + Result reset_mount_dir() override; + + static Result> create(); + + dev_t host_device_id() const override + { + return m_host_device_id; + } + + virtual ~FileSystem(); + + private: + FileSystem(); + + void set_root(SharedPtr root); + + SharedPtr m_root_inode; + + dev_t m_host_device_id; + }; +} + +extern Vector g_devpts_instances; diff --git a/kernel/src/fs/devpts/Inode.cpp b/kernel/src/fs/devpts/Inode.cpp new file mode 100644 index 00000000..6efb94fe --- /dev/null +++ b/kernel/src/fs/devpts/Inode.cpp @@ -0,0 +1,78 @@ +#include "fs/devpts/Inode.h" + +namespace DevPTS +{ + Result> RootInode::find(const char* name) const + { + for (const auto& entry : m_entries) + { + if (!strcmp(name, entry.name.chars())) return entry.inode; + } + + return err(ENOENT); + } + + Result RootInode::replace_entry(SharedPtr inode, const char* name) + { + for (auto& entry : m_entries) + { + if (!strcmp(name, entry.name.chars())) + { + entry.inode = inode; + return {}; + } + } + + return err(ENOENT); + } + + Option RootInode::get(usize index) const + { + if (index >= m_entries.size()) return {}; + + return m_entries[index]; + } + + Result RootInode::add_entry(SharedPtr inode, const char* name) + { + if (find(name).has_value()) return err(EEXIST); + + VFS::DirectoryEntry entry { inode, name }; + + TRY(m_entries.try_append(move(entry))); + + inode->did_link(); + + m_metadata.mtime = *Timer::realtime_clock(); + + return {}; + } + + Result RootInode::remove_entry(const char* name) + { + SharedPtr inode = TRY(find(name)); + + if (inode->type() == VFS::InodeType::Directory && inode->entries() != 2) return err(ENOTEMPTY); + + if (inode->is_mountpoint()) return err(EBUSY); + + m_entries.remove_first_matching( + [&](const VFS::DirectoryEntry& entry) { return !strcmp(entry.name.chars(), name); }); + + inode->did_unlink(); + + m_metadata.mtime = *Timer::realtime_clock(); + + return {}; + } + + Result> RootInode::create_file(const char*, mode_t) + { + return err(ENOTSUP); + } + + Result> RootInode::create_subdirectory(const char*, mode_t) + { + return err(ENOTSUP); + } +} diff --git a/kernel/src/fs/devpts/Inode.h b/kernel/src/fs/devpts/Inode.h new file mode 100644 index 00000000..0575236a --- /dev/null +++ b/kernel/src/fs/devpts/Inode.h @@ -0,0 +1,93 @@ +#pragma once +#include "fs/VFS.h" +#include "fs/devices/DeviceRegistry.h" +#include "fs/devpts/FileSystem.h" + +namespace DevPTS +{ + class RootInode : public VFS::Inode + { + public: + RootInode() = default; + + void set_fs(FileSystem& fs, Badge) + { + m_fs = &fs; + } + + void set_inode_number() + { + m_metadata.inum = 2; + } + + void set_self(SharedPtr self, Badge) + { + m_self = self; + } + + Result> find(const char* name) const override; + Option get(usize index) const override; + + Result read(u8*, usize, usize) const override + { + return err(EISDIR); + } + + Result write(const u8*, usize, usize) override + { + return err(EISDIR); + } + + Result truncate(usize) override + { + return err(EISDIR); + } + + bool will_block_if_read() const override + { + return false; + } + + VFS::FileSystem* fs() const override + { + return m_fs; + } + + VFS::InodeType type() const override + { + return VFS::InodeType::Directory; + } + + void did_link() override + { + } + + void did_unlink() override + { + m_self = {}; + m_entries.clear(); + } + + usize entries() const override + { + return m_entries.size(); + } + + Result remove_entry(const char* name) override; + + Result> create_file(const char* name, mode_t mode) override; + Result> create_subdirectory(const char* name, mode_t mode) override; + + Result add_entry(SharedPtr inode, const char* name); + Result replace_entry(SharedPtr inode, const char* name); + + virtual ~RootInode() = default; + + private: + VFS::FileSystem* m_fs; + SharedPtr m_self; + Vector m_entries; + + friend class FileSystem; + }; +} diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp index 84703bfe..5e73a722 100644 --- a/kernel/src/main.cpp +++ b/kernel/src/main.cpp @@ -6,6 +6,7 @@ #include "config.h" #include "fs/InitRD.h" #include "fs/devices/DeviceRegistry.h" +#include "fs/devices/PTYMultiplexer.h" #include "fs/tmpfs/FileSystem.h" #include "memory/MemoryManager.h" #include "thread/Scheduler.h" @@ -94,6 +95,7 @@ extern "C" [[noreturn]] void _start() Thread::init(); Scheduler::init(); + PTYMultiplexer::init(); Scheduler::new_kernel_thread(init, "[kinit]"); diff --git a/kernel/src/sys/mount.cpp b/kernel/src/sys/mount.cpp index b01512e2..20b4a580 100644 --- a/kernel/src/sys/mount.cpp +++ b/kernel/src/sys/mount.cpp @@ -1,5 +1,6 @@ #include "Pledge.h" #include "fs/VFS.h" +#include "fs/devpts/FileSystem.h" #include "fs/ext2/FileSystem.h" #include "fs/tmpfs/FileSystem.h" #include "memory/MemoryManager.h" @@ -26,6 +27,8 @@ Result sys_mount(Registers*, SyscallArgs args) SharedPtr fs; if (fstype.view() == "tmpfs") fs = TRY(TmpFS::FileSystem::create()); + else if (fstype.view() == "devpts") + fs = TRY(DevPTS::FileSystem::create()); else if (fstype.view() == "devfs") fs = TRY(DeviceRegistry::create_devfs_instance()); else if (fstype.view() == "ext2") diff --git a/libc/include/bits/termios.h b/libc/include/bits/termios.h index d1506915..5273c236 100644 --- a/libc/include/bits/termios.h +++ b/libc/include/bits/termios.h @@ -45,5 +45,6 @@ struct winsize #define TIOCSPGRP 2 #define TIOCGPGRP 3 #define TIOCGWINSZ 4 +#define TIOCGPTN 5 #endif