]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: industry-standard TTY output pipeline (Linux/BSD parity)
authorTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 05:48:55 +0000 (02:48 -0300)
committerTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 05:48:55 +0000 (02:48 -0300)
Kernel:
- Open /dev/console as fd 0/1/2 for init process (mirrors Linux
  kernel_init: open + dup + dup pattern)
- Add console_put_char() that outputs to both UART and VGA
- TTY write path now routes through console_put_char() instead of
  uart_put_char() only — userspace output now appears on VGA too
- Implement OPOST/ONLCR output processing: \n → \r\n conversion
  (POSIX termios c_oflag, enabled by default)
- TCGETS/TCSETS ioctl now reads/writes c_oflag
- All TTY echo paths (canonical, raw, line editing) use tty_output_char()
  for consistent UART+VGA output with OPOST processing
- Increase syscall write copy buffer from 256 to 1024 bytes
- Declare vga_put_char() in vga_console.h

Userspace (ulibc):
- stdout is now line-buffered (_STDIO_LBUF): flushes on \n
- stderr is now unbuffered (_STDIO_UNBUF): writes immediately
- printf()/vprintf() now go through fwrite(stdout) instead of raw
  write(), unifying all stdio output through the FILE buffer
- putchar()/puts() also route through fwrite(stdout)
- fwrite() respects buffering modes: unbuffered bypasses buffer,
  line-buffered flushes on newline, full-buffered flushes when full
- Add setvbuf()/setbuf() with _IOFBF/_IOLBF/_IONBF modes
- Add isatty() implemented via TCGETS ioctl probe (POSIX standard)

Build: clean, cppcheck: clean, smoke: 19/19 pass

include/console.h
include/tty.h
include/vga_console.h
src/arch/x86/arch_platform.c
src/kernel/console.c
src/kernel/syscall.c
src/kernel/tty.c
user/ulibc/include/stdio.h
user/ulibc/include/unistd.h
user/ulibc/src/stdio.c
user/ulibc/src/unistd.c

index 4b87ae2258106eba8635801af59c512614b68c4c..c016a590cff13dbec921769afc181f42bbe5a48b 100644 (file)
@@ -18,6 +18,7 @@ void console_enable_uart(int enabled);
 void console_enable_vga(int enabled);
 
 void console_write(const char* s);
+void console_put_char(char c);
 
 int kvsnprintf(char* out, size_t out_size, const char* fmt, va_list ap);
 int ksnprintf(char* out, size_t out_size, const char* fmt, ...);
index 9af8f1c40fe0ac44ad353c234efb6732e9acde60..1c1e5288daaabc602cd8688d73f0df724d7c74f6 100644 (file)
@@ -38,6 +38,12 @@ enum {
     TTY_ISIG   = 0x0001,
 };
 
+/* c_oflag bits (POSIX) */
+enum {
+    TTY_OPOST = 0x0001,
+    TTY_ONLCR = 0x0004,
+};
+
 void tty_init(void);
 
 int tty_read(void* user_buf, uint32_t len);
index 4fb43e4c32b132eb687dadaaafcf24209d6b1e4a..7ea6c52d6cdb5b2988bed1ad25cee11c2333536f 100644 (file)
@@ -13,6 +13,7 @@
 #include <stdint.h>
 
 void vga_init(void);
+void vga_put_char(char c);
 void vga_print(const char* str);
 void vga_set_color(uint8_t fg, uint8_t bg);
 
index 0f459c43d0dcd4ec29dd758d6aa5fc679736646c..7c89c8c33eb5613365d0062e8d30d4c9316d92a0 100644 (file)
@@ -20,6 +20,7 @@
 #include "vmm.h"
 
 #include "process.h"
+#include "heap.h"
 
 #include "hal/cpu.h"
 #include "hal/usermode.h"
@@ -64,6 +65,28 @@ static void userspace_init_thread(void) {
     current_process->heap_break = heap_brk;
     vmm_as_activate(user_as);
 
+    /* Open /dev/console as fd 0, 1, 2 — mirrors Linux kernel_init:
+     *   sys_open("/dev/console", O_RDWR, 0);
+     *   sys_dup(0); sys_dup(0);                                     */
+    {
+        fs_node_t* con = vfs_lookup("/dev/console");
+        if (con) {
+            struct file* f = (struct file*)kmalloc(sizeof(*f));
+            if (f) {
+                f->node = con;
+                f->offset = 0;
+                f->flags = 2; /* O_RDWR */
+                f->refcount = 3;
+                current_process->files[0] = f;
+                current_process->files[1] = f;
+                current_process->files[2] = f;
+                kprintf("[INIT] opened /dev/console as fd 0/1/2\n");
+            }
+        } else {
+            kprintf("[INIT] WARNING: /dev/console not found\n");
+        }
+    }
+
     kprintf("[ELF] starting /bin/init.elf\n");
 
     kprintf("[ELF] user_range_ok(entry)=%c user_range_ok(stack)=%c\n",
index ee55be7e076884d2874950393f043c5fe045bf21..3511fe35f0288e5b68639edcf71d4abe8c0e87a4 100644 (file)
@@ -15,6 +15,7 @@
 
 #include "spinlock.h"
 #include "uart_console.h"
+#include "hal/uart.h"
 #include "vga_console.h"
 #include "keyboard.h"
 
@@ -73,6 +74,19 @@ void console_write(const char* s) {
     spin_unlock_irqrestore(&g_console_lock, flags);
 }
 
+void console_put_char(char c) {
+    uintptr_t flags = spin_lock_irqsave(&g_console_lock);
+
+    if (g_console_uart_enabled) {
+        hal_uart_putc(c);
+    }
+    if (g_console_vga_enabled) {
+        vga_put_char(c);
+    }
+
+    spin_unlock_irqrestore(&g_console_lock, flags);
+}
+
 static void out_char(char** dst, size_t* rem, int* total, char c) {
     (*total)++;
     if (*rem == 0) return;
index b1e25bd5c70f37f231bca656786e96a8cdfa42e5..740c5d970dd8ce6d824a15a851c7b39e46132975 100644 (file)
@@ -1329,7 +1329,7 @@ static int syscall_write_impl(int fd, const void* user_buf, uint32_t len) {
         f->offset = f->node->length;
     }
 
-    uint8_t kbuf[256];
+    uint8_t kbuf[1024];
     uint32_t total = 0;
     while (total < len) {
         uint32_t chunk = len - total;
index 2fdf424c1f5c05e481dd1dcaae573d86e245c4a2..c34891ff7d4db2cc11c0943b716a64d24dc80ebd 100644 (file)
@@ -37,6 +37,7 @@ static uint32_t canon_tail = 0;
 static waitqueue_t tty_wq;
 
 static uint32_t tty_lflag = TTY_ICANON | TTY_ECHO | TTY_ISIG;
+static uint32_t tty_oflag = TTY_OPOST | TTY_ONLCR;
 static uint8_t tty_cc[NCCS] = {0, 0, 0, 0, 1, 0, 0, 0};
 
 static struct winsize tty_winsize = { 24, 80, 0, 0 };
@@ -58,6 +59,14 @@ static int canon_empty(void) {
 
 static uint32_t canon_count(void);
 
+/* Output a single character with OPOST processing to all console backends. */
+static void tty_output_char(char c) {
+    if ((tty_oflag & TTY_OPOST) && (tty_oflag & TTY_ONLCR) && c == '\n') {
+        console_put_char('\r');
+    }
+    console_put_char(c);
+}
+
 int tty_write_kbuf(const void* kbuf, uint32_t len) {
     if (!kbuf) return -EFAULT;
     if (len > 1024 * 1024) return -EINVAL;
@@ -71,7 +80,7 @@ int tty_write_kbuf(const void* kbuf, uint32_t len) {
 
     const char* p = (const char*)kbuf;
     for (uint32_t i = 0; i < len; i++) {
-        uart_put_char(p[i]);
+        tty_output_char(p[i]);
     }
     return (int)len;
 }
@@ -245,6 +254,7 @@ int tty_ioctl(uint32_t cmd, void* user_arg) {
         memset(&t, 0, sizeof(t));
         uintptr_t flags = spin_lock_irqsave(&tty_lock);
         t.c_lflag = tty_lflag;
+        t.c_oflag = tty_oflag;
         for (int i = 0; i < NCCS; i++) t.c_cc[i] = tty_cc[i];
         spin_unlock_irqrestore(&tty_lock, flags);
         if (copy_to_user(user_arg, &t, sizeof(t)) < 0) return -EFAULT;
@@ -256,6 +266,7 @@ int tty_ioctl(uint32_t cmd, void* user_arg) {
         if (copy_from_user(&t, user_arg, sizeof(t)) < 0) return -EFAULT;
         uintptr_t flags = spin_lock_irqsave(&tty_lock);
         tty_lflag = t.c_lflag & (TTY_ICANON | TTY_ECHO | TTY_ISIG);
+        tty_oflag = t.c_oflag & (TTY_OPOST | TTY_ONLCR);
         for (int i = 0; i < NCCS; i++) tty_cc[i] = t.c_cc[i];
         spin_unlock_irqrestore(&tty_lock, flags);
         return 0;
@@ -335,7 +346,7 @@ void tty_input_char(char c) {
         canon_push(c);
         wq_wake_one(&tty_wq);
         if (lflag & TTY_ECHO) {
-            uart_put_char(c);
+            tty_output_char(c);
         }
         spin_unlock_irqrestore(&tty_lock, flags);
         return;
@@ -356,7 +367,7 @@ void tty_input_char(char c) {
 
     if (c == '\n') {
         if (lflag & TTY_ECHO) {
-            uart_put_char('\n');
+            tty_output_char('\n');
         }
 
         for (uint32_t i = 0; i < line_len; i++) {
@@ -374,7 +385,7 @@ void tty_input_char(char c) {
         if (line_len + 1 < sizeof(line_buf)) {
             line_buf[line_len++] = c;
             if (lflag & TTY_ECHO) {
-                uart_put_char(c);
+                tty_output_char(c);
             }
         }
     }
@@ -464,7 +475,7 @@ int tty_write(const void* user_buf, uint32_t len) {
         if (copy_from_user(kbuf, (const void*)up, (size_t)chunk) < 0) return -EFAULT;
 
         for (uint32_t i = 0; i < chunk; i++) {
-            uart_put_char(kbuf[i]);
+            tty_output_char(kbuf[i]);
         }
 
         up += chunk;
index 050b529e49c706256ee13a499c576e8010f91b1e..19746f86789d5c75ba37c77ef3f9f1da12d0485c 100644 (file)
@@ -21,6 +21,8 @@
 #define _STDIO_WRITE 0x02
 #define _STDIO_EOF   0x04
 #define _STDIO_ERR   0x08
+#define _STDIO_LBUF  0x10  /* line-buffered (flush on \n) */
+#define _STDIO_UNBUF 0x20  /* unbuffered (flush every write) */
 
 typedef struct _FILE {
     int     fd;
@@ -68,4 +70,11 @@ int     sscanf(const char* str, const char* fmt, ...);
 int     remove(const char* path);
 int     rename(const char* oldpath, const char* newpath);
 
+/* Buffering modes for setvbuf (POSIX values) */
+#define _IOFBF 0  /* fully buffered */
+#define _IOLBF 1  /* line buffered */
+#define _IONBF 2  /* unbuffered */
+int     setvbuf(FILE* fp, char* buf, int mode, size_t size);
+void    setbuf(FILE* fp, char* buf);
+
 #endif
index 522d11563cf83ff5d477447061d3f03806422ec7..cdc35f227080f0ae6e0557a9fd2eb8e0c5f6a582 100644 (file)
@@ -63,6 +63,7 @@ unsigned int alarm(unsigned int seconds);
 #define LOCK_UN 8
 #define LOCK_NB 4
 int     flock(int fd, int operation);
+int     isatty(int fd);
 void*   brk(void* addr);
 
 void    _exit(int status) __attribute__((noreturn));
index de941ef40b8258df596fbffad85bbef3350521aa..63fa182a459602b2d86080bd6bf8fe419caed873 100644 (file)
@@ -13,8 +13,8 @@
 #include <stdarg.h>
 
 static FILE _stdin_file  = { .fd = 0, .flags = _STDIO_READ };
-static FILE _stdout_file = { .fd = 1, .flags = _STDIO_WRITE };
-static FILE _stderr_file = { .fd = 2, .flags = _STDIO_WRITE };
+static FILE _stdout_file = { .fd = 1, .flags = _STDIO_WRITE | _STDIO_LBUF };
+static FILE _stderr_file = { .fd = 2, .flags = _STDIO_WRITE | _STDIO_UNBUF };
 
 FILE* stdin  = &_stdin_file;
 FILE* stdout = &_stdout_file;
@@ -100,6 +100,17 @@ size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* fp) {
     size_t total = size * nmemb;
     const char* src = (const char*)ptr;
     size_t done = 0;
+
+    /* Unbuffered: write directly, bypass buffer */
+    if (fp->flags & _STDIO_UNBUF) {
+        while (done < total) {
+            int w = write(fp->fd, src + done, total - done);
+            if (w <= 0) break;
+            done += (size_t)w;
+        }
+        return done / size;
+    }
+
     while (done < total) {
         int space = BUFSIZ - fp->buf_pos;
         int want = (int)(total - done);
@@ -109,6 +120,16 @@ size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* fp) {
         done += (size_t)chunk;
         if (fp->buf_pos >= BUFSIZ) fflush(fp);
     }
+
+    /* Line-buffered: flush if the written data contains a newline */
+    if ((fp->flags & _STDIO_LBUF) && fp->buf_pos > 0) {
+        for (size_t i = 0; i < total; i++) {
+            if (src[i] == '\n') {
+                fflush(fp);
+                break;
+            }
+        }
+    }
     return done / size;
 }
 
@@ -179,6 +200,21 @@ int rename(const char* oldpath, const char* newpath) {
     return -1;
 }
 
+int setvbuf(FILE* fp, char* buf, int mode, size_t size) {
+    (void)buf; (void)size; /* we use internal buffer only */
+    if (!fp) return -1;
+    fp->flags &= ~(_STDIO_LBUF | _STDIO_UNBUF);
+    if (mode == 1 /* _IOLBF */) fp->flags |= _STDIO_LBUF;
+    else if (mode == 2 /* _IONBF */) fp->flags |= _STDIO_UNBUF;
+    /* _IOFBF (0): fully buffered — no extra flag needed */
+    return 0;
+}
+
+void setbuf(FILE* fp, char* buf) {
+    if (!buf) setvbuf(fp, (char*)0, 2 /* _IONBF */, 0);
+    else setvbuf(fp, buf, 0 /* _IOFBF */, BUFSIZ);
+}
+
 int vfprintf(FILE* fp, const char* fmt, va_list ap) {
     char buf[1024];
     int n = vsnprintf(buf, sizeof(buf), fmt, ap);
@@ -199,15 +235,15 @@ int fprintf(FILE* fp, const char* fmt, ...) {
 }
 
 int putchar(int c) {
-    char ch = (char)c;
-    write(STDOUT_FILENO, &ch, 1);
+    unsigned char ch = (unsigned char)c;
+    fwrite(&ch, 1, 1, stdout);
     return c;
 }
 
 int puts(const char* s) {
     int len = (int)strlen(s);
-    write(STDOUT_FILENO, s, (size_t)len);
-    write(STDOUT_FILENO, "\n", 1);
+    fwrite(s, 1, (size_t)len, stdout);
+    fwrite("\n", 1, 1, stdout);
     return len + 1;
 }
 
@@ -413,14 +449,7 @@ int sscanf(const char* str, const char* fmt, ...) {
 }
 
 int vprintf(const char* fmt, va_list ap) {
-    char buf[1024];
-    int n = vsnprintf(buf, sizeof(buf), fmt, ap);
-    if (n > 0) {
-        int w = n;
-        if (w > (int)(sizeof(buf) - 1)) w = (int)(sizeof(buf) - 1);
-        write(STDOUT_FILENO, buf, (size_t)w);
-    }
-    return n;
+    return vfprintf(stdout, fmt, ap);
 }
 
 int printf(const char* fmt, ...) {
index dc7651dc7e4d2c98bf8be4f53f59af47b50cf681..8681ad59eee462f9c9a8aba9c369e61b15fad46e 100644 (file)
@@ -167,6 +167,14 @@ void* brk(void* addr) {
     return (void*)_syscall1(SYS_BRK, (int)addr);
 }
 
+int isatty(int fd) {
+    /* POSIX: isatty() returns 1 if fd refers to a terminal, 0 otherwise.
+     * Implementation: try TCGETS ioctl; if it succeeds, fd is a tty. */
+    struct { uint32_t a,b,c,d; uint8_t e[8]; } t;
+    int rc = _syscall3(SYS_IOCTL, fd, 0x5401 /* TCGETS */, (int)&t);
+    return (rc == 0) ? 1 : 0;
+}
+
 void _exit(int status) {
     _syscall1(SYS_EXIT, status);
     /* If exit syscall somehow returns, loop forever.