]> 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 8c616b05552832229586bd2345487e480001d5b2..de201f27449e163d8161e1c85e6a9766a7edb063 100644 (file)
@@ -9,6 +9,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 5068ff5254446ea4f338d396e6b26f8b976df579..fc815d2d562f210826ac162f12cf57bbf273d250 100644 (file)
@@ -29,6 +29,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 296bbcce2e5db8a14ed792370d0f569f5f0acff9..1277a8645bc45c7e58aa91ed46e62b939a40fdff 100644 (file)
@@ -4,6 +4,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 4532297a916f28bf7d32f312579fafb7e83382af..cccb24202b53778a2d7643805efeeb69d6267ff7 100644 (file)
@@ -11,6 +11,7 @@
 #include "vmm.h"
 
 #include "process.h"
+#include "heap.h"
 
 #include "hal/cpu.h"
 #include "hal/usermode.h"
@@ -55,6 +56,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 08d5ee82627c31ba87e34a2fa723148aeabf070b..3bbf96dd181dc31513aa20a525ff9afc7c832097 100644 (file)
@@ -6,6 +6,7 @@
 
 #include "spinlock.h"
 #include "uart_console.h"
+#include "hal/uart.h"
 #include "vga_console.h"
 #include "keyboard.h"
 
@@ -64,6 +65,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 efe4f73b221f29e7d371e05aab8f0562547482f1..4ce9f30aafd599abafa529d4387266e7fce91b7b 100644 (file)
@@ -1320,7 +1320,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 4eff563eea0bfe45188ff7305d662dca7d35040f..7fec5f84ac924e8aa8798f1bc267e2469277fa1c 100644 (file)
@@ -28,6 +28,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 };
@@ -49,6 +50,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;
@@ -62,7 +71,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;
 }
@@ -236,6 +245,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;
@@ -247,6 +257,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;
@@ -326,7 +337,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;
@@ -347,7 +358,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++) {
@@ -365,7 +376,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);
             }
         }
     }
@@ -455,7 +466,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 dd50fdb5b4306d1b4a2e6b7746cabd2612236c79..740e1101e725b6c4b2a1ddbf2648ebbb20e422e0 100644 (file)
@@ -12,6 +12,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;
@@ -59,4 +61,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 14c275b162adc7bfada44207a5254a062d490b0b..e73fef345c0c474dffa9588f3c50cb03f0f7f663 100644 (file)
@@ -54,6 +54,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 fd599e67a7cad3a803feac4b96d1fe9e036cc139..be589baf989adc4239a01334c13259d375904b7b 100644 (file)
@@ -4,8 +4,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;
@@ -91,6 +91,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);
@@ -100,6 +111,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;
 }
 
@@ -170,6 +191,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);
@@ -190,15 +226,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;
 }
 
@@ -404,14 +440,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 ff56387e6661d6c1e1faa450c904cc1d3b97c127..b0a820d9c79920855c024fc4ddced23360bc2612 100644 (file)
@@ -158,6 +158,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.