From 420547d7f45eafa2793c8029d83f8a214315176f Mon Sep 17 00:00:00 2001 From: Tulio A M Mendes Date: Fri, 13 Feb 2026 02:53:18 -0300 Subject: [PATCH] feat: PTY line discipline with OPOST/ONLCR processing MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit - Add per-PTY oflag field (default: OPOST | ONLCR) - PTY slave write now applies ONLCR: \n → \r\n conversion - PTY slave ioctl now supports TCGETS/TCSETS for c_oflag - isatty() now returns 1 for PTY slaves (TCGETS succeeds) - Matches Linux n_tty line discipline behavior on PTY output Build: clean, cppcheck: clean, smoke: 19/19 pass --- src/kernel/pty.c | 50 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/kernel/pty.c b/src/kernel/pty.c index b8e661c..9e6626c 100644 --- a/src/kernel/pty.c +++ b/src/kernel/pty.c @@ -1,4 +1,5 @@ #include "pty.h" +#include "tty.h" #include "devfs.h" #include "errno.h" @@ -38,6 +39,7 @@ struct pty_pair { uint32_t session_id; uint32_t fg_pgrp; + uint32_t oflag; /* output flags (OPOST, ONLCR) */ int active; fs_node_t master_node; @@ -84,6 +86,7 @@ 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)); @@ -414,21 +417,31 @@ int pty_slave_write_idx(int idx, const void* kbuf, uint32_t len) { 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 free_space = rb_free(p->s2m_head, p->s2m_tail); - uint32_t to_write = len; - if (to_write > free_space) to_write = free_space; + uint32_t written = 0; - for (uint32_t i = 0; i < to_write; i++) { - rb_push(p->s2m_buf, &p->s2m_head, &p->s2m_tail, ((const uint8_t*)kbuf)[i]); + 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 (to_write) { + if (written) { wq_wake_one(&p->s2m_wq); } spin_unlock_irqrestore(&pty_lock, flags); - return (int)to_write; + return (int)written; } int pty_slave_ioctl_idx(int idx, uint32_t cmd, void* user_arg) { @@ -466,6 +479,29 @@ int pty_slave_ioctl_idx(int idx, uint32_t cmd, void* user_arg) { 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; } -- 2.43.0