From: Tulio A M Mendes Date: Fri, 13 Feb 2026 05:48:55 +0000 (-0300) Subject: feat: industry-standard TTY output pipeline (Linux/BSD parity) X-Git-Url: https://projects.tadryanom.me/docs/static/gitweb.css?a=commitdiff_plain;h=2cdd7af48ecf42bf16f871459a9295778c310a85;p=AdrOS.git feat: industry-standard TTY output pipeline (Linux/BSD parity) 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 --- diff --git a/include/console.h b/include/console.h index 8c616b0..de201f2 100644 --- a/include/console.h +++ b/include/console.h @@ -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, ...); diff --git a/include/tty.h b/include/tty.h index 5068ff5..fc815d2 100644 --- a/include/tty.h +++ b/include/tty.h @@ -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); diff --git a/include/vga_console.h b/include/vga_console.h index 296bbcc..1277a86 100644 --- a/include/vga_console.h +++ b/include/vga_console.h @@ -4,6 +4,7 @@ #include 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); diff --git a/src/arch/x86/arch_platform.c b/src/arch/x86/arch_platform.c index 4532297..cccb242 100644 --- a/src/arch/x86/arch_platform.c +++ b/src/arch/x86/arch_platform.c @@ -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", diff --git a/src/kernel/console.c b/src/kernel/console.c index 08d5ee8..3bbf96d 100644 --- a/src/kernel/console.c +++ b/src/kernel/console.c @@ -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; diff --git a/src/kernel/syscall.c b/src/kernel/syscall.c index efe4f73..4ce9f30 100644 --- a/src/kernel/syscall.c +++ b/src/kernel/syscall.c @@ -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; diff --git a/src/kernel/tty.c b/src/kernel/tty.c index 4eff563..7fec5f8 100644 --- a/src/kernel/tty.c +++ b/src/kernel/tty.c @@ -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; diff --git a/user/ulibc/include/stdio.h b/user/ulibc/include/stdio.h index dd50fdb..740e110 100644 --- a/user/ulibc/include/stdio.h +++ b/user/ulibc/include/stdio.h @@ -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 diff --git a/user/ulibc/include/unistd.h b/user/ulibc/include/unistd.h index 14c275b..e73fef3 100644 --- a/user/ulibc/include/unistd.h +++ b/user/ulibc/include/unistd.h @@ -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)); diff --git a/user/ulibc/src/stdio.c b/user/ulibc/src/stdio.c index fd599e6..be589ba 100644 --- a/user/ulibc/src/stdio.c +++ b/user/ulibc/src/stdio.c @@ -4,8 +4,8 @@ #include 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, ...) { diff --git a/user/ulibc/src/unistd.c b/user/ulibc/src/unistd.c index ff56387..b0a820d 100644 --- a/user/ulibc/src/unistd.c +++ b/user/ulibc/src/unistd.c @@ -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.