SchOS/include/posix.h
Nexoniarz 1c1b0c9570 tty: six virtual terminals, and the plumbing that makes them terminals
/dev/tty1..tty6, Alt+F1..F6 or `chvt N`. Each is an independent terminal --
its own screen, cursor, line discipline, foreground process group and
session. Two shells on two terminals are unrelated processes that share a
keyboard and a screen.

What made it possible is a backing store. The console drew straight into the
framebuffer and scrolled by moving pixels, which works exactly as long as
nothing else wants the screen: there was nowhere to put terminal 2 while
terminal 1 was visible, and nothing to repaint terminal 1 from. Each terminal
owns a grid of cells now and the framebuffer shows whichever grid is active,
so vt_switch() is set-index, repaint, return -- nothing is saved on the way
out because the outgoing grid has been current all along.

The rule the layer is built on: a terminal does not know whether it is
visible. Writes land in the grid, the cursor moves, the grid scrolls, all
unconditionally; drawing is one question asked at the bottom of vt.c. Every
alternative spreads "am I on screen" through the escape parser, and then a
terminal switched away from mid-sequence comes back wrong.

Everything that was a single global is per-terminal now: tty_fg_pgrp (deleted),
termios, the input ring, the escape parser, the UTF-8 decoder, and the cursor
-- which also stops being pixels in gfx.c and becomes cells in the terminal.
TIOCSCTTY was `return 0` and now records proc->ctty, which is what /dev/tty
resolves through; /dev/tty was an alias for /dev/console with the same minor,
and they are different questions.

kbd.c names Alt+F1..F6 as KEY_ALT_F1+n, above 0xFF on purpose -- every byte
value is already a character or a UTF-8 continuation, and a chord colliding
with one would be typed into the terminal instead of switching away from it.
What the chord means is decided in console.c, where the terminals are.

VT_ACTIVATE / VT_WAITACTIVE / VT_GETSTATE / VT_OPENQRY, Linux's numbers.
The argument to ACTIVATE is by value, not a pointer -- same shape as TCFLSH.

Two bugs found by screenshotting the result rather than reading the log:

  - zsh emits 0x0F to leave alt-charset mode, because that is what the `linux`
    terminfo says rmacs is, and the console drew CP437 glyph 0x0F: a sun, in
    the prompt, on every line. A terminal never draws a C0 control character.
  - SGR 7 was unimplemented, so zsh's partial-line marker rendered as a bare
    '#' instead of a reversed block. Reverse video swaps the colours at draw
    time, not at store time, so turning it off restores what was underneath.

Verified by screendump: tty1 and tty2 show different screens, and switching
back repaints tty1's earlier content from its grid. schfs-check passes.

docs/architecture/terminals.md, including what it does not do -- no
scrollback, no reflow, no VT_PROCESS mode for a display server to veto a
switch, which Schcom will need before it owns Alt+F7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-13 00:08:00 +02:00

323 lines
8.4 KiB
C

// SPDX-License-Identifier: Apache-2.0
// SchOS — POSIX-compatibility types used by kernel syscall handlers.
#pragma once
#include "types.h"
#include "date.h"
struct iovec {
void *iov_base;
uint64 iov_len;
};
// Linux's IOV_MAX. readv/writev used to cap iovcnt at 16 -- which is legal,
// since POSIX only requires 16 -- and returned EINVAL above it. But programs
// are written against the platform they run on, and on Linux that limit is
// 1024: toybox's `yes` builds a 256-entry iovec and writev()s it in a loop,
// so `yes | head -2` failed with "Invalid argument" before producing a single
// byte. Nothing here costs per-entry memory; the count is just a loop bound.
#define UIO_MAXIOV 1024
// Simplified termios (matches Linux x86_64 layout enough for basic use).
struct termios {
uint32 c_iflag;
uint32 c_oflag;
uint32 c_cflag;
uint32 c_lflag;
uint8 c_line;
uint8 c_cc[19];
uint32 c_ispeed;
uint32 c_ospeed;
};
// Terminal flags, with Linux's values. The kernel honours the ones marked
// below; the rest are accepted and stored so a program that sets them and
// reads them back sees what it wrote, which is what cfmakeraw() and every
// line-editing library actually check.
//
// c_iflag
#define IGNBRK 0000001
#define BRKINT 0000002
#define IGNPAR 0000004
#define PARMRK 0000010
#define INPCK 0000020
#define ISTRIP 0000040 // honoured: strip the eighth bit
#define INLCR 0000100 // honoured: NL -> CR on input
#define IGNCR 0000200 // honoured: drop CR
#define ICRNL 0000400 // honoured: CR -> NL on input
#define IUCLC 0001000
#define IXON 0002000
#define IXANY 0004000
#define IXOFF 0010000
#define IMAXBEL 0020000
#define IUTF8 0040000
// c_oflag
#define OPOST 0000001 // honoured: enable output processing
#define OLCUC 0000002
#define ONLCR 0000004 // honoured: NL -> CR NL on output
#define OCRNL 0000010
#define ONOCR 0000020
#define ONLRET 0000040
// c_cflag
#define CS8 0000060
#define CREAD 0000200
#define HUPCL 0002000
#define CLOCAL 0004000
// c_lflag
#define ISIG 0000001 // honoured: INTR/QUIT/SUSP generate signals
#define ICANON 0000002 // honoured: line-at-a-time with editing
#define ECHO 0000010 // honoured
#define ECHOE 0000020 // honoured: ERASE rubs the character out
#define ECHOK 0000040
#define ECHONL 0000100 // honoured: echo NL even with ECHO off
#define NOFLSH 0000200
#define TOSTOP 0000400
#define IEXTEN 0100000
// c_cc indices (Linux order). VMIN/VTIME share slots with VEOF/VEOL, which is
// why they are only meaningful in the mode the other pair is not.
#define VINTR 0
#define VQUIT 1
#define VERASE 2
#define VKILL 3
#define VEOF 4
#define VTIME 5
#define VMIN 6
#define VSWTC 7
#define VSTART 8
#define VSTOP 9
#define VSUSP 10
#define VEOL 11
#define VREPRINT 12
#define VDISCARD 13
#define VWERASE 14
#define VLNEXT 15
#define VEOL2 16
struct winsize {
uint16 ws_row;
uint16 ws_col;
uint16 ws_xpixel;
uint16 ws_ypixel;
};
struct rlimit {
uint64 rlim_cur;
uint64 rlim_max;
};
// Linux ioctl codes we handle.
#define TCGETS 0x5401
#define TCSETS 0x5402
#define TCSETSW 0x5403
#define TCSETSF 0x5404
#define TCFLSH 0x540B
// TCFLSH queue selectors. Not a pointer -- these are the argument itself.
#define TCIFLUSH 0
#define TCOFLUSH 1
#define TCIOFLUSH 2
#define TIOCGPGRP 0x540F
#define TIOCSPGRP 0x5410
#define TIOCGWINSZ 0x5413
#define TIOCSWINSZ 0x5414
#define TIOCSCTTY 0x540E
// Virtual terminal control, Linux's numbers. The argument to VT_ACTIVATE and
// VT_WAITACTIVE is a 1-based terminal number passed by value, not a pointer.
#define VT_OPENQRY 0x5600 // first free terminal, out through a pointer
#define VT_GETMODE 0x5601
#define VT_SETMODE 0x5602
#define VT_GETSTATE 0x5603 // struct vt_stat
#define VT_ACTIVATE 0x5606
#define VT_WAITACTIVE 0x5607
struct vt_stat {
uint16 v_active; // active terminal, 1-based
uint16 v_signal; // unused; Linux keeps it for VT_SETMODE
uint16 v_state; // bitmask of terminals in use, bit 1 = tty1
};
#define TIOCGSERIAL 0x541E
#define FIONREAD 0x541B
// FICLONE: share another file's data blocks with this one. The argument is a
// file descriptor, not a pointer. Linux's value, so `cp --reflink` works.
#define FICLONE 0x40049409
#define TIOCEXCL 0x540C
#define TIOCNXCL 0x540D
// arch_prctl commands
#define ARCH_SET_GS 0x1001
#define ARCH_SET_FS 0x1002
#define ARCH_GET_FS 0x1003
#define ARCH_GET_GS 0x1004
// clone flags (subset)
#define CLONE_VM 0x00000100
#define CLONE_THREAD 0x00010000
#define CLONE_SIGHAND 0x00000800
// futex ops
#define FUTEX_WAIT 0
#define FUTEX_WAKE 1
#define FUTEX_WAIT_PRIVATE 128
#define FUTEX_WAKE_PRIVATE 129
// Resource limit codes (POSIX)
#define RLIMIT_AS 9
#define RLIMIT_STACK 3
#define RLIMIT_NOFILE 7
// clock IDs
#define CLOCK_REALTIME 0
#define CLOCK_MONOTONIC 1
struct timespec {
long tv_sec;
long tv_nsec;
};
struct itimerval {
struct timeval it_interval;
struct timeval it_value;
};
struct rusage {
struct timeval ru_utime;
struct timeval ru_stime;
long ru_maxrss;
long ru_ixrss;
long ru_idrss;
long ru_isrss;
long ru_minflt;
long ru_majflt;
long ru_nswap;
long ru_inblock;
long ru_oublock;
long ru_msgsnd;
long ru_msgrcv;
long ru_nsignals;
long ru_nvcsw;
long ru_nivcsw;
};
struct tms {
long tms_utime;
long tms_stime;
long tms_cutime;
long tms_cstime;
};
struct sysinfo {
long uptime;
uint64 loads[3];
uint64 totalram;
uint64 freeram;
uint64 sharedram;
uint64 bufferram;
uint64 totalswap;
uint64 freeswap;
uint16 procs;
uint16 pad;
uint32 pad2;
uint64 totalhigh;
uint64 freehigh;
uint32 mem_unit;
char _f[8];
};
// Must match Linux's x86-64 struct statfs field for field, because the programs
// reading it were compiled against Linux's.
//
// f_fsid is the trap. Linux's fsid_t is `struct { int val[2]; }` -- eight bytes
// in total, not two eight-byte words -- and declaring it as int64[2] made this
// struct one slot too long from f_fsid onwards. Everything after it landed in
// the wrong place: userland read f_frsize out of f_namelen and got 255, so df
// computed its block counts in units of 255 bytes and reported a 32 MB root
// filesystem as 1992 KB. It went unnoticed for as long as it did because every
// field here used to be an invented constant, so the numbers were meaningless
// whichever slot they came from.
struct statfs {
int64 f_type;
int64 f_bsize;
int64 f_blocks;
int64 f_bfree;
int64 f_bavail;
int64 f_files;
int64 f_ffree;
int32 f_fsid[2];
int64 f_namelen;
int64 f_frsize;
int64 f_flags;
int64 f_spare[4];
};
// at-syscall dirfd sentinel
#define AT_FDCWD (-100)
#define AT_REMOVEDIR 0x200
#define AT_SYMLINK_NOFOLLOW 0x100
#define AT_EMPTY_PATH 0x1000
#define AT_NO_AUTOMOUNT 0x800
// prctl options
#define PR_SET_NAME 15
#define PR_GET_NAME 16
#define PR_SET_DUMPABLE 4
#define PR_GET_DUMPABLE 3
// signal numbers (Linux x86_64 values), used by kill dispatch and
// default-action classification in sigsend()
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGABRT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
// Job-control and the remaining standard signals. A shell needs the TSTP/
// TTIN/TTOU trio to implement ^Z and background-job terminal access at all;
// they were simply absent, so any reference to them failed to compile.
#define SIGTRAP 5
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGPWR 30
#define SIGSYS 31
// getrusage who
#define RUSAGE_SELF 0
#define RUSAGE_CHILDREN (-1)
// fallocate mode
#define FALLOC_FL_KEEP_SIZE 1
// poll(2) event bits (Linux values)
#define POLLIN 0x001
#define POLLPRI 0x002
#define POLLOUT 0x004
#define POLLERR 0x008
#define POLLHUP 0x010
#define POLLNVAL 0x020
struct pollfd {
int fd;
short events;
short revents;
};