SchOS/kernel/core/console.c
Nexoniarz 1754cbf1aa console: a bare ESC[m is a reset, and OSC sequences are consumed
Two ANSI bugs, both found by running fastfetch rather than by reading the
parser, and both invisible to anything that spells its escapes out in full.

ESC[m with no parameters means ESC[0m. ECMA-48 is explicit that an omitted
parameter takes its default and SGR's default is 0, and sgr() iterated its
parameter list -- which is empty in exactly that case, so the loop body never
ran and the reset was silently dropped.

Programs emit the bare form constantly. fastfetch writes "\x1b[32m1%\x1b[m", so
everything after the first coloured character stayed green forever. With a
background colour it is much worse: its palette swatch sets a white background
and resets, the reset did nothing, and the console went white-on-white and
stayed there -- filling the screen with white rectangles as it scrolled, which
looks like a corrupted framebuffer rather than one missing branch.

OSC sequences were not a state at all, so ESC ] fell through and the payload
printed as text: a hyperlinked path arrived as "Disk (]8;;file:///\]8;;\)".
Nothing here acts on an operating-system command -- there is no window title and
no hyperlink to follow -- but *discarding* one is not optional, because the
alternative is printing somebody's URL into the middle of their output. ESC \
ends it, and an ESC followed by anything else is handed back to the escape state
rather than eaten, so a malformed OSC does not swallow the sequence after it.

Verified by screendumping the framebuffer after two consecutive fastfetch runs:
identical output both times, near-white coverage down from most of the screen to
2% (the palette swatch's own white cell), and the disk line reading "Disk (/)".

Also installs docs/branding/Ascii/ascii_50.txt as /usr/share/schos/logo.txt --
Shift as characters, at the width that leaves room for an info block on an
80-column console. Installed from the branding directory rather than copied into
sysroot by hand, so there is one source.

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

816 lines
25 KiB
C

#include <stdarg.h>
#include "types.h"
#include "defs.h"
#include "gfx.h"
#include "theme.h"
#include "irq.h"
#include "param.h"
#include "traps.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "posix.h"
#include "x86.h"
void consputc(int);
// Set by panic() once the crash screen has been painted: after that, consputc
// spins instead of writing, so nothing else can scribble over the report.
int panicked = 0;
// When set, console output goes to the serial line only and the framebuffer
// is left alone.
//
// **Starts set, on purpose.** The display is the product; the serial line is
// the diagnostics. A machine that boots by printing "pci: 6 devices" and
// "vga: display controller 1234:1111, aperture fd000000" to the screen the
// user is looking at is showing them a developer's log, and that single fact
// is most of what makes an OS look like a hobby project.
//
// Nothing is lost: every one of those lines still goes out the UART, which is
// where tools/bootcheck.ps1 reads them from and where you want them when
// something goes wrong. console_takeover() clears this once userspace is up
// and the display genuinely belongs to the console. The F8 verbose boot never
// sets it, so "show me everything" still works.
int cons_serial_only = 1;
extern void gfx_screen_reset(void);
// Text colour used when drawing to the framebuffer. Changed by SGR escapes
// (see sgr() below); reset to the default by ESC[0m and ESC c.
//
// This was initialised to 7, a leftover from when it was a VGA text-mode
// attribute byte. gfx_putc takes a 32-bit RGB value, so 7 meant "very dark
// blue" -- it looked black against the cream background purely by accident.
static uint32 cons_fg = SCH_FG;
static struct {
struct spinlock lock;
int locking;
} cons;
static char digits[] = "0123456789abcdef";
static void
print_int(int64 xx, int base, int sign)
{
char buf[64];
int i;
uint64 x;
if(sign && (sign = (xx < 0)))
x = -xx;
else
x = xx;
i = 0;
do {
buf[i++] = digits[x % base];
} while((x /= base) != 0);
if(sign)
buf[i++] = '-';
while(--i >= 0)
consputc(buf[i]);
}
void
cprintf(char *fmt, ...)
{
va_list ap;
int i, c, locking;
char *s;
va_start(ap, fmt);
locking = cons.locking;
if (locking)
acquire(&cons.lock);
if (fmt == 0)
panic("null fmt");
for (i = 0; (c = fmt[i] & 0xff) != 0; i++) {
if (c != '%') {
consputc(c);
continue;
}
c = fmt[++i] & 0xff;
if (c == 0)
break;
switch(c) {
case 'd':
print_int(va_arg(ap, int), 10, 1);
break;
case 'u':
print_int(va_arg(ap, uint32), 10, 0);
break;
case 'l':
print_int(va_arg(ap, int64), 10, 1);
break;
case 'x':
print_int(va_arg(ap, uint32), 16, 0);
break;
case 'p':
print_int(va_arg(ap, uint64), 16, 0);
break;
case 'b':
print_int(va_arg(ap, uint32), 2, 0);
break;
case 's':
if ((s = va_arg(ap, char*)) == 0)
s = "(null)";
while (*s)
consputc(*(s++));
break;
case '%':
consputc('%');
break;
default:
consputc('%');
consputc(c);
break;
}
}
if (locking)
release(&cons.lock);
}
// Hand the console over to panic().
//
// Two things change. The lock stops being taken, so a panic can report itself
// from anywhere — including from inside a routine that already holds it. And
// output stops going to the framebuffer, because from here on the framebuffer
// belongs to the crash screen: without this the report would scroll straight
// over the panel that is trying to display it.
void
cons_panic_mode(void)
{
cons.locking = 0;
cons_serial_only = 1;
}
// Hand the display over to the text console.
//
// Called once, when userspace is far enough along that the screen should stop
// being a boot splash and start being a terminal. Until this runs the kernel
// has been talking to the serial line only (see cons_serial_only), so this is
// the moment the framebuffer changes owner -- which is why it clears first
// rather than scrolling the shell prompt in underneath the splash.
void
console_takeover(void)
{
if (!cons_serial_only)
return; // verbose boot: the console never left
cons_serial_only = 0;
gfx_screen_reset();
}
// Foreground process group of the console, i.e. the controlling terminal.
// tcgetpgrp()/tcsetpgrp() read and write this through TIOCGPGRP/TIOCSPGRP in
// sys_fs.c. Initialised to 1 -- init's process group -- so the first shell,
// which inherits that group, already sees itself as the foreground job and
// does not immediately stop itself with SIGTTIN.
int tty_fg_pgrp = 1;
#define BACKSPACE 0x100
// ---------------------------------------------------------------------------
// Output: an ANSI escape parser, and UTF-8
//
// The console used to print every byte it was given, which meant every escape
// sequence any program emitted appeared on screen as literal text -- `clear`
// drew "^[[2J^[[H" and left the screen exactly as it found it. Colour, cursor
// addressing, and anything full-screen were all equally impossible.
//
// The serial line does NOT go through any of this. On the other end of it is
// a real terminal emulator that already understands ANSI far better than this
// ever will, so escapes are passed through to it verbatim and interpreted
// only for the framebuffer. Two consumers, two right answers.
// ---------------------------------------------------------------------------
// The sixteen ANSI colours as RGB, for a linear framebuffer.
//
// consoleinit() used to program these into the VGA DAC with set_vga_palette(),
// which does nothing at all in a 32bpp linear mode -- there is no palette to
// index. The colours had to live somewhere real, and this is it.
//
// That DAC code is now gone rather than merely unused, along with the blink
// disable next to it: both were writes to VGA hardware registers made from the
// console, which is neither a driver nor allowed to know it was running on a
// VGA. Nothing observable changed, because nothing observable depended on it.
static const uint32 ansi_rgb[16] = {
0x000000, 0xAA0000, 0x00AA00, 0xAA5500, // black red green yellow(brown)
0x0000AA, 0xAA00AA, 0x00AAAA, 0xAAAAAA, // blue magenta cyan white
0x555555, 0xFF5555, 0x55FF55, 0xFFFF55, // bright versions
0x5555FF, 0xFF55FF, 0x55FFFF, 0xFFFFFF,
};
#define CONS_DEFAULT_FG SCH_FG // see include/theme.h
#define CONS_DEFAULT_BG SCH_BG
static uint32 cons_bg = CONS_DEFAULT_BG;
static int cons_bold = 0;
// Escape parser state.
// E_OSC and E_OSC_ESC exist for hyperlinks. A terminal that does not follow
// them must still *consume* them, or the URL is printed as text -- which is
// what "Disk (]8;;file:///\]8;;\): 4.6 MiB" was: fastfetch wrapping a path in
// OSC 8, and this console spelling it out.
enum { E_NORMAL, E_ESC, E_CSI, E_OSC, E_OSC_ESC };
static int esc_state = E_NORMAL;
static int esc_parm[8];
static int esc_nparm;
static int esc_private; // saw '?' after CSI
static int saved_col, saved_row;
// UTF-8 decoder state. A codepoint arrives as up to four bytes and has to be
// reassembled before it can be turned into a glyph.
static uint32 u8_cp;
static int u8_need;
// ---------------------------------------------------------------------------
// Unicode -> glyph
//
// The font's upper half is CP437, not Latin-1, so a codepoint cannot be used
// as an index. It looks like it can -- font.h's own comments say so, labelling
// each glyph with the Unicode character of the same number -- but the bitmaps
// disagree: index 0xE9 draws a capital theta, and rendering U+00E9 there turns
// "cafe" into "caf" followed by a theta. Every accented character came out as
// an unrelated symbol, consistently enough to look almost deliberate.
//
// So: a table. Entry i covers codepoint 0x80 + i, and 0 means "this font has
// no glyph for that". The C1 range (0x80-0x9F) has no printable characters at
// all, hence the leading run of zeros.
// ---------------------------------------------------------------------------
static const uchar latin1_to_cp437[128] = {
/* 0x80-0x9F: C1 controls, nothing to draw */
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
/* 0xA0 */ 0x20, 0xAD, 0x9B, 0x9C, 0, 0x9D, 0, 0x15,
/* 0xA8 */ 0, 0, 0xA6, 0xAE, 0xAA, 0x2D, 0, 0,
/* 0xB0 */ 0xF8, 0xF1, 0xFD, 0, 0, 0xE6, 0x14, 0xFA,
/* 0xB8 */ 0, 0, 0xA7, 0xAF, 0xAC, 0xAB, 0, 0xA8,
/* 0xC0 */ 0, 0, 0, 0, 0x8E, 0x8F, 0x92, 0x80,
/* 0xC8 */ 0, 0x90, 0, 0, 0, 0, 0, 0,
/* 0xD0 */ 0, 0xA5, 0, 0, 0, 0, 0x99, 0,
/* 0xD8 */ 0, 0, 0, 0, 0x9A, 0, 0, 0xE1,
/* 0xE0 */ 0x85, 0xA0, 0x83, 0, 0x84, 0x86, 0x91, 0x87,
/* 0xE8 */ 0x8A, 0x82, 0x88, 0x89, 0x8D, 0xA1, 0x8C, 0x8B,
/* 0xF0 */ 0, 0xA4, 0x95, 0xA2, 0x93, 0, 0x94, 0xF6,
/* 0xF8 */ 0, 0x97, 0xA3, 0x96, 0x81, 0, 0, 0x98,
};
// The handful of characters above U+00FF that CP437 does have. Box drawing
// and block elements matter because they are what every text-mode program
// draws frames and progress bars out of.
struct cp_pair { uint32 cp; uchar glyph; };
static const struct cp_pair cp437_extra[] = {
{ 0x2022, 0x07 }, /* bullet */
{ 0x2500, 0xC4 }, { 0x2502, 0xB3 }, /* light horizontal/vertical */
{ 0x250C, 0xDA }, { 0x2510, 0xBF },
{ 0x2514, 0xC0 }, { 0x2518, 0xD9 },
{ 0x251C, 0xC3 }, { 0x2524, 0xB4 },
{ 0x252C, 0xC2 }, { 0x2534, 0xC1 }, { 0x253C, 0xC5 },
{ 0x2550, 0xCD }, { 0x2551, 0xBA }, /* double */
{ 0x2554, 0xC9 }, { 0x2557, 0xBB },
{ 0x255A, 0xC8 }, { 0x255D, 0xBC },
{ 0x2580, 0xDF }, { 0x2584, 0xDC }, { 0x2588, 0xDB },
{ 0x258C, 0xDD }, { 0x2590, 0xDE },
{ 0x2591, 0xB0 }, { 0x2592, 0xB1 }, { 0x2593, 0xB2 },
{ 0x25A0, 0xFE }, /* black square */
{ 0x221A, 0xFB }, { 0x221E, 0xEC }, /* root, infinity */
{ 0x2260, 0, }, { 0x2264, 0xF3 }, { 0x2265, 0xF2 },
{ 0x2248, 0xF7 }, { 0x2261, 0xF0 },
};
// Returns the glyph to draw for a codepoint, or 0 if there is none.
static int
glyph_for(uint32 cp)
{
if (cp < 0x80)
return (int)cp; // ASCII is CP437's lower half
if (cp < 0x100)
return latin1_to_cp437[cp - 0x80];
for (unsigned i = 0; i < NELEM(cp437_extra); i++)
if (cp437_extra[i].cp == cp)
return cp437_extra[i].glyph;
return 0;
}
static void
sgr(void)
{
// "ESC[m" with no parameters means ESC[0m -- reset -- and ECMA-48 says so for
// every CSI: an omitted parameter takes its default, and SGR's default is 0.
//
// This loop used to start at a count of zero and therefore do nothing, so a
// bare reset was silently dropped. Programs emit it constantly: fastfetch
// writes "\x1b[32m1%\x1b[m", and with the reset ignored everything after the
// first coloured character stayed green forever. Worse with background
// colours -- its palette swatch sets a white background and resets, so the
// whole console went white-on-white and stayed there, which looks like a
// corrupted framebuffer rather than a missing branch.
int nparm = esc_nparm ? esc_nparm : 1; // esc_parm[0] is already 0
for (int i = 0; i < nparm; i++) {
int p = esc_parm[i];
if (p == 0) {
cons_fg = CONS_DEFAULT_FG;
cons_bg = CONS_DEFAULT_BG;
cons_bold = 0;
} else if (p == 1) {
cons_bold = 1;
// Re-apply brightness to a colour already chosen.
for (int j = 0; j < 8; j++)
if (cons_fg == ansi_rgb[j]) { cons_fg = ansi_rgb[j + 8]; break; }
} else if (p == 22) {
cons_bold = 0;
} else if (p >= 30 && p <= 37) {
cons_fg = ansi_rgb[(p - 30) + (cons_bold ? 8 : 0)];
} else if (p == 39) {
cons_fg = CONS_DEFAULT_FG;
} else if (p >= 40 && p <= 47) {
cons_bg = ansi_rgb[p - 40];
} else if (p == 49) {
cons_bg = CONS_DEFAULT_BG;
} else if (p >= 90 && p <= 97) {
cons_fg = ansi_rgb[(p - 90) + 8];
} else if (p >= 100 && p <= 107) {
cons_bg = ansi_rgb[(p - 100) + 8];
}
}
gfx_set_bg(cons_bg);
}
// One final byte of a CSI sequence, with esc_parm[] already filled in.
static void
csi_dispatch(int final)
{
int col, row;
int n = esc_nparm > 0 ? esc_parm[0] : 0;
int n1 = n ? n : 1; // most sequences default to 1
gfx_cursor_get(&col, &row);
switch (final) {
case 'A': gfx_cursor_set(col, row - n1); break;
case 'B': gfx_cursor_set(col, row + n1); break;
case 'C': gfx_cursor_set(col + n1, row); break;
case 'D': gfx_cursor_set(col - n1, row); break;
case 'E': gfx_cursor_set(0, row + n1); break;
case 'F': gfx_cursor_set(0, row - n1); break;
case 'G': gfx_cursor_set(n1 - 1, row); break;
case 'd': gfx_cursor_set(col, n1 - 1); break;
case 'H': case 'f': {
int r = esc_nparm > 0 && esc_parm[0] ? esc_parm[0] : 1;
int c = esc_nparm > 1 && esc_parm[1] ? esc_parm[1] : 1;
gfx_cursor_set(c - 1, r - 1); // ANSI counts from 1
break;
}
case 'J': gfx_erase_display(n); break;
case 'K': gfx_erase_line(n); break;
case 'm': sgr(); break;
case 's': saved_col = col; saved_row = row; break;
case 'u': gfx_cursor_set(saved_col, saved_row); break;
// Cursor visibility (DECTCEM) and the other private modes. There is no
// hardware cursor being drawn, so there is nothing to show or hide --
// swallowing them silently is the correct behaviour, and far better than
// printing "?25l" into the middle of someone's output.
case 'h': case 'l': break;
default: break; // unknown: drop it, do not print it
}
}
// Feed one output byte to the framebuffer side. Returns nothing; the serial
// side has already had the raw byte.
static void
gfx_feed(int c)
{
switch (esc_state) {
case E_ESC:
if (c == '[') {
esc_state = E_CSI;
esc_nparm = 0;
esc_private = 0;
for (int i = 0; i < 8; i++) esc_parm[i] = 0;
return;
}
if (c == ']') { // OSC: swallow it, see below
esc_state = E_OSC;
return;
}
esc_state = E_NORMAL;
if (c == 'c') { // RIS -- full reset
cons_fg = CONS_DEFAULT_FG;
cons_bg = CONS_DEFAULT_BG;
gfx_screen_reset();
}
return; // other two-byte escapes: ignored
// An operating-system command runs until BEL or until ST (ESC \). Nothing
// here acts on one -- there is no window title to set and no hyperlink to
// follow -- but discarding the payload is not optional: the alternative is
// printing somebody's URL into the middle of their output.
case E_OSC:
if (c == '\a') { esc_state = E_NORMAL; return; }
if (c == '\x1b') { esc_state = E_OSC_ESC; return; }
return;
case E_OSC_ESC:
// ESC \ ends it; ESC anything-else means the OSC was malformed and a new
// escape has started, so hand the byte to the ESC state rather than eating
// it. Dropping it would lose the sequence that follows.
if (c == '\\') { esc_state = E_NORMAL; return; }
esc_state = E_ESC;
gfx_feed(c);
return;
case E_CSI:
if (c == '?') { esc_private = 1; return; }
if (c >= '0' && c <= '9') {
if (esc_nparm == 0) esc_nparm = 1;
if (esc_nparm <= 8)
esc_parm[esc_nparm - 1] = esc_parm[esc_nparm - 1] * 10 + (c - '0');
return;
}
if (c == ';') {
if (esc_nparm < 8) esc_nparm++;
return;
}
esc_state = E_NORMAL;
if (!esc_private)
csi_dispatch(c);
return;
}
// E_NORMAL
if (c == '\x1b') { esc_state = E_ESC; return; }
// UTF-8 continuation. A codepoint the font cannot draw becomes '?' -- the
// one thing it must not become is a *different* character, which is exactly
// what indexing the font by codepoint did.
if (u8_need > 0) {
if ((c & 0xC0) != 0x80) { // malformed: abandon the sequence
u8_need = 0;
gfx_putc('?', cons_fg);
return;
}
u8_cp = (u8_cp << 6) | (c & 0x3F);
if (--u8_need > 0)
return;
int g = glyph_for(u8_cp);
gfx_putc(g ? g : '?', cons_fg);
return;
}
if (c >= 0xF0) { u8_cp = c & 0x07; u8_need = 3; return; }
else if (c >= 0xE0) { u8_cp = c & 0x0F; u8_need = 2; return; }
else if (c >= 0xC0) { u8_cp = c & 0x1F; u8_need = 1; return; }
else if (c >= 0x80) { gfx_putc('?', cons_fg); return; } // stray continuation
gfx_putc(c, cons_fg);
}
void
consputc(int c)
{
if (panicked) {
cli();
for(;;) hlt();
}
if (c == BACKSPACE) {
uartputc('\b'); uartputc(' '); uartputc('\b');
if (!cons_serial_only)
gfx_putc(c, cons_fg);
return;
}
uartputc(c);
if (!cons_serial_only)
gfx_feed(c & 0xff);
}
#define INPUT_BUF 256
struct {
struct spinlock lock;
char buf[INPUT_BUF];
uint r;
uint w;
uint e;
uint abort_read;
// Set by EOF (Ctrl-D) on an empty line. Canonical-mode end of input is not
// a character -- it is the *absence* of one -- so it cannot be signalled by
// putting something in the buffer. The old code pushed a literal ^D in and
// had consoleread() pick it back out, which meant a ^D typed mid-line was
// indistinguishable from one typed alone.
uint eof;
} input;
#define C(x) ((x)-'@')
// ---------------------------------------------------------------------------
// The line discipline
//
// TCGETS used to return a hardcoded set of flags and TCSETS threw its argument
// away, so the terminal was permanently in one mode: line-at-a-time, always
// echoing. Anything wanting a keypress at a time -- a pager, an editor, a
// shell doing its own line editing, which is most of what zsh is -- had no way
// to ask for one. This is the real thing: a struct programs can write, that
// changes what the kernel actually does.
// ---------------------------------------------------------------------------
static struct termios tio = {
.c_iflag = ICRNL,
.c_oflag = OPOST | ONLCR,
.c_cflag = CS8 | CREAD | CLOCAL,
.c_lflag = ISIG | ICANON | ECHO | ECHOE,
.c_cc = {
[VINTR] = C('C'), [VQUIT] = C('\\'), [VERASE] = '\x7f', [VKILL] = C('U'),
[VEOF] = C('D'), [VSUSP] = C('Z'), [VWERASE] = C('W'), [VMIN] = 1,
},
.c_ispeed = 15, .c_ospeed = 15,
};
void
cons_get_termios(struct termios *out)
{
acquire(&input.lock);
*out = tio;
release(&input.lock);
}
void
cons_set_termios(const struct termios *in)
{
acquire(&input.lock);
tio = *in;
// Switching out of canonical mode has to hand over whatever has been typed
// but not yet submitted, or a shell that turns off ICANON mid-line silently
// eats the characters already in the edit buffer.
if (!(tio.c_lflag & ICANON))
input.w = input.e;
release(&input.lock);
wakeup(&input.r);
}
static void
echo(int c)
{
if (c == '\n' || c == '\r') {
if ((tio.c_lflag & ECHO) || (tio.c_lflag & ECHONL))
consputc('\n');
return;
}
if (!(tio.c_lflag & ECHO))
return;
// Control characters echo as ^X, the way every other terminal shows them.
if (c < 32 && c != '\t') {
consputc('^');
consputc(c + '@');
} else {
consputc(c);
}
}
// Rub out the last character of the current line.
static void
erase_one(void)
{
if (input.e == input.w)
return;
input.e--;
if (tio.c_lflag & ECHOE)
consputc(BACKSPACE);
}
void
consoleintr(int (*getc)(void))
{
int c;
acquire(&input.lock);
while((c = getc()) >= 0){
if (c == 0)
continue;
// Input mapping, before anything looks at the character.
if (tio.c_iflag & ISTRIP) c &= 0x7f;
if (c == '\r') {
if (tio.c_iflag & IGNCR) continue;
if (tio.c_iflag & ICRNL) c = '\n';
} else if (c == '\n' && (tio.c_iflag & INLCR)) {
c = '\r';
}
// Signals. These are the characters that must not reach the reader.
//
// Ctrl-C used to do `myproc()->killed = 1` -- and `proc` in an interrupt
// handler is whatever happened to be running on this CPU when the key was
// pressed, which is almost never the program the user is looking at. It
// also set a kill flag rather than raising a signal, so nothing could
// catch it and the shell never learned the job had died. Both wrong;
// both fixed by sending a real signal to the foreground process group,
// which the kernel has been tracking in tty_fg_pgrp all along.
if (tio.c_lflag & ISIG) {
int sig = 0;
if (c == tio.c_cc[VINTR]) sig = SIGINT;
else if (c == tio.c_cc[VQUIT]) sig = SIGQUIT;
else if (c == tio.c_cc[VSUSP]) sig = SIGTSTP;
if (sig) {
echo(c);
input.e = input.w = input.r; // discard the partial line
input.abort_read = 1;
release(&input.lock);
sigsend_pgrp(tty_fg_pgrp, sig);
acquire(&input.lock);
wakeup(&input.r);
continue;
}
}
// Ctrl-P: dump the process table. Not POSIX, but it is the one debugging
// aid that works when everything else has stopped responding.
if (c == C('P')) {
procdump();
continue;
}
if (tio.c_lflag & ICANON) {
if (c == tio.c_cc[VERASE] || c == C('H')) {
erase_one();
continue;
}
if (c == tio.c_cc[VKILL]) {
while (input.e != input.w &&
input.buf[(input.e-1) % INPUT_BUF] != '\n')
erase_one();
continue;
}
if (c == tio.c_cc[VWERASE]) {
while (input.e != input.w &&
input.buf[(input.e-1) % INPUT_BUF] == ' ')
erase_one();
while (input.e != input.w &&
input.buf[(input.e-1) % INPUT_BUF] != ' ')
erase_one();
continue;
}
if (c == tio.c_cc[VEOF]) {
if (input.e == input.w)
input.eof = 1; // empty line: end of input
input.w = input.e; // otherwise just submit what is there
wakeup(&input.r);
continue;
}
}
if (input.e - input.r >= INPUT_BUF)
continue; // buffer full: drop it
// Everything else goes in, including bytes above 127 -- those are UTF-8
// continuation bytes, and the old `c >= 32 && c <= 126` test silently
// refused every non-ASCII character anyone tried to type.
input.buf[input.e++ % INPUT_BUF] = c;
echo(c);
// Canonical mode hands over a line at a time; raw mode hands over
// everything as it arrives.
if (!(tio.c_lflag & ICANON) ||
c == '\n' || input.e == input.r + INPUT_BUF) {
input.w = input.e;
wakeup(&input.r);
}
}
release(&input.lock);
}
int
consoleread(struct inode *ip, uint64 off, char *dst, int n)
{
uint target;
int c;
static uint32 prng_seed = 0x9A7B3C1D;
if (ip->minor == 2) {
return 0;
}
if (ip->minor == 4) {
for (int i = 0; i < n; i++) {
prng_seed ^= prng_seed << 13;
prng_seed ^= prng_seed >> 17;
prng_seed ^= prng_seed << 5;
dst[i] = prng_seed & 0xFF;
}
return n;
}
iunlock(ip);
target = n;
acquire(&input.lock);
while(n > 0){
while(input.r == input.w){
if (input.eof) {
input.eof = 0;
release(&input.lock);
ilock(ip);
return 0; // end of input
}
if (myproc()->killed || input.abort_read) {
input.abort_read = 0;
release(&input.lock);
ilock(ip);
return -1;
}
sleep(&input.r, &input.lock);
}
c = input.buf[input.r++ % INPUT_BUF];
*dst++ = c;
--n;
// Canonical mode returns a line at a time. Raw mode returns whatever has
// arrived: a program that asked for a keypress must not be made to wait
// for a newline that is never coming.
if (tio.c_lflag & ICANON) {
if (c == '\n')
break;
} else {
if (target - n >= (uint)(tio.c_cc[VMIN] ? tio.c_cc[VMIN] : 1))
break;
}
}
release(&input.lock);
ilock(ip);
return target - n;
}
// Nonzero if a completed input line is waiting — poll(2)'s POLLIN answer
// for the console tty. Mirrors the r/w cursors consoleread() blocks on.
int
console_input_ready(void)
{
int ready;
acquire(&input.lock);
ready = input.r != input.w;
release(&input.lock);
return ready;
}
int
consolewrite(struct inode *ip, uint64 off, char *buf, int n)
{
int i;
if (ip->minor == 2 || ip->minor == 3 || ip->minor == 4) {
return n;
}
iunlock(ip);
acquire(&cons.lock);
for(i = 0; i < n; i++)
consputc(buf[i] & 0xff);
release(&cons.lock);
ilock(ip);
return n;
}
// The keyboard handler lives in kbd.c, but the console is what owns the
// keyboard as an input source, so this is where the line gets claimed.
static void
kbd_irq(void *arg)
{
(void)arg;
kbdintr();
}
void
consoleinit(void)
{
initlock(&cons.lock, "console");
initlock(&input.lock, "input");
memset(&input, 0, sizeof(input));
devsw[CONSOLE].write = consolewrite;
devsw[CONSOLE].read = consoleread;
cons.locking = 1;
irq_register(IRQ_KBD, kbd_irq, 0, "ps2-kbd", 0);
}