Viewing: pty.c
📄 pty.c (Read Only) ⬅ To go back
#include "pty.h"
#include "tty.h"

#include "devfs.h"
#include "errno.h"
#include "process.h"
#include "waitqueue.h"
#include "spinlock.h"
#include "uaccess.h"
#include "utils.h"

#include "hal/cpu.h"

#include <stddef.h>

#define PTY_BUF_CAP 1024

enum {
    SIGTTIN = 21,
    SIGTTOU = 22,
};

enum {
    TTY_TIOCGPGRP = 0x540F,
    TTY_TIOCSPGRP = 0x5410,
};

struct pty_pair {
    uint8_t  m2s_buf[PTY_BUF_CAP];
    uint32_t m2s_head;
    uint32_t m2s_tail;

    uint8_t  s2m_buf[PTY_BUF_CAP];
    uint32_t s2m_head;
    uint32_t s2m_tail;

    waitqueue_t m2s_wq;
    waitqueue_t s2m_wq;

    uint32_t session_id;
    uint32_t fg_pgrp;
    uint32_t oflag;      /* output flags (OPOST, ONLCR) */
    int      active;

    fs_node_t master_node;
    fs_node_t slave_node;
};

static spinlock_t pty_lock = {0};
static struct pty_pair g_ptys[PTY_MAX_PAIRS];
static int g_pty_count = 0;

static uint32_t rb_count(uint32_t head, uint32_t tail) {
    if (head >= tail) return head - tail;
    return (PTY_BUF_CAP - tail) + head;
}

static uint32_t rb_free(uint32_t head, uint32_t tail) {
    return (PTY_BUF_CAP - 1U) - rb_count(head, tail);
}

static void rb_push(uint8_t* buf, uint32_t* head, uint32_t* tail, uint8_t c) {
    uint32_t next = (*head + 1U) % PTY_BUF_CAP;
    if (next == *tail) {
        *tail = (*tail + 1U) % PTY_BUF_CAP;
    }
    buf[*head] = c;
    *head = next;
}

static int rb_pop(uint8_t* buf, uint32_t* head, uint32_t* tail, uint8_t* out) {
    if (*head == *tail) return 0;
    *out = buf[*tail];
    *tail = (*tail + 1U) % PTY_BUF_CAP;
    return 1;
}


static uint32_t pty_master_read_fn(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer);
static uint32_t pty_master_write_fn(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer);
static uint32_t pty_slave_read_fn(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer);
static uint32_t pty_slave_write_fn(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer);
static int pty_slave_ioctl_fn(fs_node_t* node, uint32_t cmd, void* arg);

static int pty_master_poll_fn(fs_node_t* node, int events) {
    int idx = pty_ino_to_idx(node->inode);
    if (idx < 0) return 0;
    int revents = 0;
    if ((events & VFS_POLL_IN) && pty_master_can_read_idx(idx)) revents |= VFS_POLL_IN;
    if ((events & VFS_POLL_OUT) && pty_master_can_write_idx(idx)) revents |= VFS_POLL_OUT;
    return revents;
}

static int pty_slave_poll_fn(fs_node_t* node, int events) {
    int idx = pty_ino_to_idx(node->inode);
    if (idx < 0) return 0;
    int revents = 0;
    if ((events & VFS_POLL_IN) && pty_slave_can_read_idx(idx)) revents |= VFS_POLL_IN;
    if ((events & VFS_POLL_OUT) && pty_slave_can_write_idx(idx)) revents |= VFS_POLL_OUT;
    return revents;
}

/* Forward declarations for file_operations tables */
static uint32_t pty_ptmx_read_fn(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer);
static uint32_t pty_ptmx_write_fn(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer);
static struct fs_node* pty_pts_finddir(struct fs_node* node, const char* name);
static int pty_pts_readdir(struct fs_node* node, uint32_t* inout_index, void* buf, uint32_t buf_len);

static const struct file_operations pty_master_fops = {
    .read  = pty_master_read_fn,
    .write = pty_master_write_fn,
    .poll  = pty_master_poll_fn,
};

static const struct file_operations pty_slave_fops = {
    .read  = pty_slave_read_fn,
    .write = pty_slave_write_fn,
    .ioctl = pty_slave_ioctl_fn,
    .poll  = pty_slave_poll_fn,
};

static const struct file_operations pty_ptmx_fops = {
    .read  = pty_ptmx_read_fn,
    .write = pty_ptmx_write_fn,
    .poll  = pty_master_poll_fn,
};

static const struct file_operations pty_pts_dir_fops = {0};

static const struct inode_operations pty_pts_dir_iops = {
    .lookup  = pty_pts_finddir,
    .readdir = pty_pts_readdir,
};

static void pty_init_pair(int idx) {
    struct pty_pair* p = &g_ptys[idx];
    memset(p, 0, sizeof(*p));
    p->active = 1;
    p->oflag = TTY_OPOST | TTY_ONLCR;

    char name[8];
    memset(&p->master_node, 0, sizeof(p->master_node));
    strcpy(p->master_node.name, "ptmx");
    p->master_node.flags = FS_CHARDEVICE;
    p->master_node.inode = PTY_MASTER_INO_BASE + (uint32_t)idx;
    p->master_node.f_ops = &pty_master_fops;

    memset(&p->slave_node, 0, sizeof(p->slave_node));
    name[0] = '0' + (char)idx;
    name[1] = '\0';
    strcpy(p->slave_node.name, name);
    p->slave_node.flags = FS_CHARDEVICE;
    p->slave_node.inode = PTY_SLAVE_INO_BASE + (uint32_t)idx;
    p->slave_node.f_ops = &pty_slave_fops;
}

/* --- DevFS pts directory callbacks --- */

static fs_node_t g_dev_ptmx_node;
static fs_node_t g_dev_pts_dir_node;

static uint32_t pty_ptmx_read_fn(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
    (void)offset;
    int idx = pty_ino_to_idx(node->inode);
    if (idx < 0) idx = 0;
    int rc = pty_master_read_idx(idx, buffer, size);
    if (rc < 0) return 0;
    return (uint32_t)rc;
}

static uint32_t pty_ptmx_write_fn(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer) {
    (void)offset;
    int idx = pty_ino_to_idx(node->inode);
    if (idx < 0) idx = 0;
    int rc = pty_master_write_idx(idx, buffer, size);
    if (rc < 0) return 0;
    return (uint32_t)rc;
}

static struct fs_node* pty_pts_finddir(struct fs_node* node, const char* name) {
    (void)node;
    if (!name || name[0] == 0) return 0;
    int count = pty_pair_count();
    for (int i = 0; i < count; i++) {
        char num[4];
        num[0] = '0' + (char)i;
        num[1] = '\0';
        if (strcmp(name, num) == 0) {
            return pty_get_slave_node(i);
        }
    }
    return 0;
}

static int pty_pts_readdir(struct fs_node* node, uint32_t* inout_index, void* buf, uint32_t buf_len) {
    (void)node;
    if (!inout_index || !buf) return -1;
    if (buf_len < sizeof(struct vfs_dirent)) return -1;

    int count = pty_pair_count();
    uint32_t idx = *inout_index;
    uint32_t cap = buf_len / (uint32_t)sizeof(struct vfs_dirent);
    struct vfs_dirent* ents = (struct vfs_dirent*)buf;
    uint32_t written = 0;

    while (written < cap) {
        struct vfs_dirent e;
        memset(&e, 0, sizeof(e));

        if (idx == 0) {
            e.d_ino = 5; e.d_type = FS_DIRECTORY; strcpy(e.d_name, ".");
        } else if (idx == 1) {
            e.d_ino = 1; e.d_type = FS_DIRECTORY; strcpy(e.d_name, "..");
        } else {
            int pi = (int)(idx - 2);
            if (pi >= count) break;
            e.d_ino = PTY_SLAVE_INO_BASE + (uint32_t)pi;
            e.d_type = FS_CHARDEVICE;
            e.d_name[0] = '0' + (char)pi;
            e.d_name[1] = '\0';
        }

        e.d_reclen = (uint16_t)sizeof(e);
        ents[written] = e;
        written++;
        idx++;
    }

    *inout_index = idx;
    return (int)(written * (uint32_t)sizeof(struct vfs_dirent));
}

void pty_init(void) {
    spinlock_init(&pty_lock);
    memset(g_ptys, 0, sizeof(g_ptys));
    g_pty_count = 0;
    pty_init_pair(0);
    g_pty_count = 1;

    /* Register /dev/ptmx */
    memset(&g_dev_ptmx_node, 0, sizeof(g_dev_ptmx_node));
    strcpy(g_dev_ptmx_node.name, "ptmx");
    g_dev_ptmx_node.flags = FS_CHARDEVICE;
    g_dev_ptmx_node.inode = PTY_MASTER_INO_BASE;
    g_dev_ptmx_node.f_ops = &pty_ptmx_fops;
    devfs_register_device(&g_dev_ptmx_node);

    /* Register /dev/pts directory */
    memset(&g_dev_pts_dir_node, 0, sizeof(g_dev_pts_dir_node));
    strcpy(g_dev_pts_dir_node.name, "pts");
    g_dev_pts_dir_node.flags = FS_DIRECTORY;
    g_dev_pts_dir_node.inode = 5;
    g_dev_pts_dir_node.f_ops = &pty_pts_dir_fops;
    g_dev_pts_dir_node.i_ops = &pty_pts_dir_iops;
    devfs_register_device(&g_dev_pts_dir_node);
}

int pty_alloc_pair(void) {
    uintptr_t flags = spin_lock_irqsave(&pty_lock);
    if (g_pty_count >= PTY_MAX_PAIRS) {
        spin_unlock_irqrestore(&pty_lock, flags);
        return -ENOMEM;
    }
    int idx = g_pty_count;
    g_pty_count++;
    spin_unlock_irqrestore(&pty_lock, flags);
    pty_init_pair(idx);
    return idx;
}

int pty_pair_count(void) {
    return g_pty_count;
}

int pty_pair_active(int idx) {
    if (idx < 0 || idx >= g_pty_count) return 0;
    return g_ptys[idx].active;
}

fs_node_t* pty_get_master_node(int idx) {
    if (idx < 0 || idx >= g_pty_count) return NULL;
    return &g_ptys[idx].master_node;
}

fs_node_t* pty_get_slave_node(int idx) {
    if (idx < 0 || idx >= g_pty_count) return NULL;
    return &g_ptys[idx].slave_node;
}

static int pty_jobctl_write_check(struct pty_pair* p) {
    if (current_process && p->session_id != 0 && current_process->session_id == p->session_id &&
        p->fg_pgrp != 0 && current_process->pgrp_id != p->fg_pgrp) {
        (void)process_kill(current_process->pid, SIGTTOU);
        return -EINTR;
    }
    return 0;
}

static int pty_jobctl_read_check(struct pty_pair* p) {
    if (!current_process) return -ECHILD;
    if (p->session_id != 0 && current_process->session_id == p->session_id &&
        p->fg_pgrp != 0 && current_process->pgrp_id != p->fg_pgrp) {
        (void)process_kill(current_process->pid, SIGTTIN);
        return -EINTR;
    }
    return 0;
}

int pty_master_can_read_idx(int idx) {
    if (idx < 0 || idx >= g_pty_count) return 0;
    struct pty_pair* p = &g_ptys[idx];
    uintptr_t flags = spin_lock_irqsave(&pty_lock);
    int ready = (rb_count(p->s2m_head, p->s2m_tail) != 0U) ? 1 : 0;
    spin_unlock_irqrestore(&pty_lock, flags);
    return ready;
}

int pty_master_can_write_idx(int idx) {
    if (idx < 0 || idx >= g_pty_count) return 0;
    struct pty_pair* p = &g_ptys[idx];
    uintptr_t flags = spin_lock_irqsave(&pty_lock);
    int ready = (rb_free(p->m2s_head, p->m2s_tail) != 0U) ? 1 : 0;
    spin_unlock_irqrestore(&pty_lock, flags);
    return ready;
}

int pty_slave_can_read_idx(int idx) {
    if (idx < 0 || idx >= g_pty_count) return 0;
    struct pty_pair* p = &g_ptys[idx];
    uintptr_t flags = spin_lock_irqsave(&pty_lock);
    int ready = (rb_count(p->m2s_head, p->m2s_tail) != 0U) ? 1 : 0;
    spin_unlock_irqrestore(&pty_lock, flags);
    return ready;
}

int pty_slave_can_write_idx(int idx) {
    if (idx < 0 || idx >= g_pty_count) return 0;
    struct pty_pair* p = &g_ptys[idx];
    uintptr_t flags = spin_lock_irqsave(&pty_lock);
    int ready = (rb_free(p->s2m_head, p->s2m_tail) != 0U) ? 1 : 0;
    spin_unlock_irqrestore(&pty_lock, flags);
    return ready;
}

int pty_master_read_idx(int idx, void* kbuf, uint32_t len) {
    if (idx < 0 || idx >= g_pty_count) return -ENODEV;
    struct pty_pair* p = &g_ptys[idx];
    if (!kbuf) return -EFAULT;
    if (len > 1024 * 1024) return -EINVAL;

    int jc = pty_jobctl_read_check(p);
    if (jc < 0) return jc;

    while (1) {
        uintptr_t flags = spin_lock_irqsave(&pty_lock);
        uint32_t avail = rb_count(p->s2m_head, p->s2m_tail);
        if (avail != 0U) {
            uint32_t to_read = len;
            if (to_read > avail) to_read = avail;
            for (uint32_t i = 0; i < to_read; i++) {
                uint8_t c = 0;
                (void)rb_pop(p->s2m_buf, &p->s2m_head, &p->s2m_tail, &c);
                ((uint8_t*)kbuf)[i] = c;
            }
            spin_unlock_irqrestore(&pty_lock, flags);
            return (int)to_read;
        }

        if (current_process) {
            if (wq_push(&p->s2m_wq, current_process) == 0) {
                current_process->state = PROCESS_BLOCKED;
            }
        }

        spin_unlock_irqrestore(&pty_lock, flags);
        hal_cpu_enable_interrupts();
        schedule();
    }
}

int pty_master_write_idx(int idx, const void* kbuf, uint32_t len) {
    if (idx < 0 || idx >= g_pty_count) return -ENODEV;
    struct pty_pair* p = &g_ptys[idx];
    if (!kbuf) return -EFAULT;
    if (len > 1024 * 1024) return -EINVAL;

    int jc = pty_jobctl_write_check(p);
    if (jc < 0) return jc;

    enum { SIGINT_NUM = 2, SIGQUIT_NUM = 3, SIGTSTP_NUM = 20 };

    const uint8_t* bytes = (const uint8_t*)kbuf;
    for (uint32_t i = 0; i < len; i++) {
        uint8_t ch = bytes[i];
        int sig = 0;
        if (ch == 0x03) sig = SIGINT_NUM;
        else if (ch == 0x1C) sig = SIGQUIT_NUM;
        else if (ch == 0x1A) sig = SIGTSTP_NUM;
        if (sig && p->fg_pgrp != 0) {
            process_kill_pgrp(p->fg_pgrp, sig);
        }
    }

    uintptr_t flags = spin_lock_irqsave(&pty_lock);
    uint32_t free_space = rb_free(p->m2s_head, p->m2s_tail);
    uint32_t to_write = len;
    if (to_write > free_space) to_write = free_space;

    for (uint32_t i = 0; i < to_write; i++) {
        rb_push(p->m2s_buf, &p->m2s_head, &p->m2s_tail, ((const uint8_t*)kbuf)[i]);
    }

    if (to_write) {
        wq_wake_one(&p->m2s_wq);
    }

    spin_unlock_irqrestore(&pty_lock, flags);
    return (int)to_write;
}

int pty_slave_read_idx(int idx, void* kbuf, uint32_t len) {
    if (idx < 0 || idx >= g_pty_count) return -ENODEV;
    struct pty_pair* p = &g_ptys[idx];
    if (!kbuf) return -EFAULT;
    if (len > 1024 * 1024) return -EINVAL;

    int jc = pty_jobctl_read_check(p);
    if (jc < 0) return jc;

    while (1) {
        uintptr_t flags = spin_lock_irqsave(&pty_lock);
        uint32_t avail = rb_count(p->m2s_head, p->m2s_tail);
        if (avail != 0U) {
            uint32_t to_read = len;
            if (to_read > avail) to_read = avail;
            for (uint32_t i = 0; i < to_read; i++) {
                uint8_t c = 0;
                (void)rb_pop(p->m2s_buf, &p->m2s_head, &p->m2s_tail, &c);
                ((uint8_t*)kbuf)[i] = c;
            }
            spin_unlock_irqrestore(&pty_lock, flags);
            return (int)to_read;
        }

        if (current_process) {
            if (wq_push(&p->m2s_wq, current_process) == 0) {
                current_process->state = PROCESS_BLOCKED;
            }
        }

        spin_unlock_irqrestore(&pty_lock, flags);
        hal_cpu_enable_interrupts();
        schedule();
    }
}

int pty_slave_write_idx(int idx, const void* kbuf, uint32_t len) {
    if (idx < 0 || idx >= g_pty_count) return -ENODEV;
    struct pty_pair* p = &g_ptys[idx];
    if (!kbuf) return -EFAULT;
    if (len > 1024 * 1024) return -EINVAL;

    int jc = pty_jobctl_write_check(p);
    if (jc < 0) return jc;

    int do_onlcr = (p->oflag & TTY_OPOST) && (p->oflag & TTY_ONLCR);

    uintptr_t flags = spin_lock_irqsave(&pty_lock);
    uint32_t written = 0;

    for (uint32_t i = 0; i < len; i++) {
        uint8_t ch = ((const uint8_t*)kbuf)[i];
        /* OPOST: convert \n to \r\n */
        if (do_onlcr && ch == '\n') {
            if (rb_free(p->s2m_head, p->s2m_tail) < 2U) break;
            rb_push(p->s2m_buf, &p->s2m_head, &p->s2m_tail, '\r');
            rb_push(p->s2m_buf, &p->s2m_head, &p->s2m_tail, '\n');
        } else {
            if (rb_free(p->s2m_head, p->s2m_tail) == 0U) break;
            rb_push(p->s2m_buf, &p->s2m_head, &p->s2m_tail, ch);
        }
        written++;
    }

    if (written) {
        wq_wake_one(&p->s2m_wq);
    }

    spin_unlock_irqrestore(&pty_lock, flags);
    return (int)written;
}

int pty_slave_ioctl_idx(int idx, uint32_t cmd, void* user_arg) {
    if (idx < 0 || idx >= g_pty_count) return -ENODEV;
    struct pty_pair* p = &g_ptys[idx];
    if (!user_arg) return -EFAULT;

    if (current_process && p->session_id == 0 && current_process->session_id != 0) {
        p->session_id = current_process->session_id;
        p->fg_pgrp = current_process->pgrp_id;
    }

    if (cmd == TTY_TIOCGPGRP) {
        if (user_range_ok(user_arg, sizeof(int)) == 0) return -EFAULT;
        int fg = (int)p->fg_pgrp;
        if (copy_to_user(user_arg, &fg, sizeof(fg)) < 0) return -EFAULT;
        return 0;
    }

    if (cmd == TTY_TIOCSPGRP) {
        if (user_range_ok(user_arg, sizeof(int)) == 0) return -EFAULT;
        int fg = 0;
        if (copy_from_user(&fg, user_arg, sizeof(fg)) < 0) return -EFAULT;
        if (!current_process) return -EINVAL;

        if (p->session_id == 0) {
            if (fg != 0) return -EPERM;
            p->fg_pgrp = 0;
            return 0;
        }

        if (current_process->session_id != p->session_id) return -EPERM;
        if (fg < 0) return -EINVAL;
        p->fg_pgrp = (uint32_t)fg;
        return 0;
    }

    enum { PTY_TCGETS = 0x5401, PTY_TCSETS = 0x5402 };

    if (cmd == PTY_TCGETS) {
        if (user_range_ok(user_arg, sizeof(struct termios)) == 0) return -EFAULT;
        struct termios t;
        memset(&t, 0, sizeof(t));
        uintptr_t flags = spin_lock_irqsave(&pty_lock);
        t.c_oflag = p->oflag;
        spin_unlock_irqrestore(&pty_lock, flags);
        if (copy_to_user(user_arg, &t, sizeof(t)) < 0) return -EFAULT;
        return 0;
    }

    if (cmd == PTY_TCSETS) {
        if (user_range_ok(user_arg, sizeof(struct termios)) == 0) return -EFAULT;
        struct termios t;
        if (copy_from_user(&t, user_arg, sizeof(t)) < 0) return -EFAULT;
        uintptr_t flags = spin_lock_irqsave(&pty_lock);
        p->oflag = t.c_oflag & (TTY_OPOST | TTY_ONLCR);
        spin_unlock_irqrestore(&pty_lock, flags);
        return 0;
    }

    return -EINVAL;
}

static uint32_t pty_master_read_fn(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
    (void)offset;
    int idx = pty_ino_to_idx(node->inode);
    int rc = pty_master_read_idx(idx, buffer, size);
    if (rc < 0) return 0;
    return (uint32_t)rc;
}

static uint32_t pty_master_write_fn(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer) {
    (void)offset;
    int idx = pty_ino_to_idx(node->inode);
    int rc = pty_master_write_idx(idx, buffer, size);
    if (rc < 0) return 0;
    return (uint32_t)rc;
}

static uint32_t pty_slave_read_fn(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
    (void)offset;
    int idx = pty_ino_to_idx(node->inode);
    int rc = pty_slave_read_idx(idx, buffer, size);
    if (rc < 0) return 0;
    return (uint32_t)rc;
}

static uint32_t pty_slave_write_fn(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer) {
    (void)offset;
    int idx = pty_ino_to_idx(node->inode);
    int rc = pty_slave_write_idx(idx, buffer, size);
    if (rc < 0) return 0;
    return (uint32_t)rc;
}

static int pty_slave_ioctl_fn(fs_node_t* node, uint32_t cmd, void* arg) {
    int idx = pty_ino_to_idx(node->inode);
    if (idx < 0) return -ENODEV;
    return pty_slave_ioctl_idx(idx, cmd, arg);
}

int pty_master_can_read(void)  { return pty_master_can_read_idx(0); }
int pty_master_can_write(void) { return pty_master_can_write_idx(0); }
int pty_slave_can_read(void)   { return pty_slave_can_read_idx(0); }
int pty_slave_can_write(void)  { return pty_slave_can_write_idx(0); }

int pty_master_read_kbuf(void* kbuf, uint32_t len)        { return pty_master_read_idx(0, kbuf, len); }
int pty_master_write_kbuf(const void* kbuf, uint32_t len)  { return pty_master_write_idx(0, kbuf, len); }
int pty_slave_read_kbuf(void* kbuf, uint32_t len)         { return pty_slave_read_idx(0, kbuf, len); }
int pty_slave_write_kbuf(const void* kbuf, uint32_t len)   { return pty_slave_write_idx(0, kbuf, len); }
int pty_slave_ioctl(uint32_t cmd, void* user_arg)          { return pty_slave_ioctl_idx(0, cmd, user_arg); }