- /dev/ptmx and /dev/pts/N: Unix98 pairs with a full line discipline, window sizes, job control and hangup (kernel/dev/pty.c). - The console speaks scroll regions, insert/delete line, SU/SD, RI/IND/NEL, DECSC/DECRC; swallows charset designations and private CSI sequences; and LF only returns the carriage under ONLCR. - setsid() drops the controlling terminal, so a pane's pty becomes its ctty. - pipe2() honours O_NONBLOCK and O_CLOEXEC (libevent's signal pipe). - File modes are stored and enforced: chmod, fchmod(at), umask, creation modes; /tmp is 1777. - /proc/stat has real user/system/idle; /proc/<pid>/task; kthread flag. - SO_PEERCRED, IOV_MAX 1024. - /proc/strace: name a program and its syscalls go to the kernel log. - World gains htop, btop, tmux, mc, ncdu, fastfetch, games, X toys, NetSurf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
226 lines
7.7 KiB
C
226 lines
7.7 KiB
C
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "fs/fs.h"
|
|
#include "fs/file.h"
|
|
#include "arch/x86.h"
|
|
#include "uapi/errno.h"
|
|
#include "dev/pty.h"
|
|
|
|
extern int consoleread(struct inode*, uint64, char*, int);
|
|
extern int consolewrite(struct inode*, uint64, char*, int);
|
|
extern int mouseread(struct inode*, uint64, char*, int);
|
|
|
|
// --- /dev/random and /dev/urandom -------------------------------------------
|
|
//
|
|
// This was a linear congruential generator seeded with a constant, which means
|
|
// **every boot produced the same bytes in the same order**. Not weak: the same.
|
|
// A program asking for a key, a nonce, a temporary filename or a hash seed got
|
|
// one it shared with every other SchOS machine that had ever run.
|
|
//
|
|
// What is here now is still not a CSPRNG and does not pretend to be. It is an
|
|
// LCG that is *seeded* from something real and re-stirred from the cycle
|
|
// counter on every read, which fixes the one property that made the old one
|
|
// dangerous rather than merely poor. See "What it does not do" in
|
|
// docs/architecture/devices.md.
|
|
static uint64 rseed;
|
|
static int rseed_ready;
|
|
static void rseed_init(void);
|
|
|
|
// RDRAND, if this CPU has it. CPUID leaf 1, ECX bit 30.
|
|
//
|
|
// Checked rather than assumed: the instruction faults with #UD on a CPU that
|
|
// does not implement it, and "the random device is a kernel panic" is a worse
|
|
// failure than a weak seed.
|
|
static int
|
|
rdrand64(uint64 *out)
|
|
{
|
|
uint32 a, b, c, d;
|
|
unsigned char ok = 0;
|
|
uint64 v = 0;
|
|
|
|
cpuid_leaf(1, &a, &b, &c, &d);
|
|
if (!(c & (1u << 30)))
|
|
return 0;
|
|
|
|
// Ten tries, per Intel's own guidance: the instruction is allowed to fail
|
|
// when its entropy pool is momentarily empty, and it says so in CF.
|
|
for (int i = 0; i < 10; i++) {
|
|
asm volatile("rdrand %0; setc %1" : "=r"(v), "=qm"(ok) :: "cc");
|
|
if (ok) { *out = v; return 1; }
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Seeded at boot rather than on the first read.
|
|
//
|
|
// Lazy would work, but it puts the one line that says where the entropy came
|
|
// from in the middle of whatever the machine was doing hours later, and it
|
|
// makes the first reader pay for the CPUID. Called from main(); the lazy path
|
|
// stays as a belt-and-braces for anything that reads before init runs.
|
|
void
|
|
random_init(void)
|
|
{
|
|
if (!rseed_ready)
|
|
rseed_init();
|
|
}
|
|
|
|
static void
|
|
rseed_init(void)
|
|
{
|
|
uint64 v;
|
|
|
|
rseed_ready = 1;
|
|
if (rdrand64(&v)) {
|
|
rseed = v;
|
|
kinfo("random: seeded from RDRAND\n");
|
|
return;
|
|
}
|
|
|
|
// No RDRAND. The cycle counter is the only thing here that is different on
|
|
// every boot and not written down anywhere -- it is not entropy in any
|
|
// serious sense, but it is not a constant either, which was the whole
|
|
// problem. Mixed with the wall clock so that two machines starting at the
|
|
// same instant do not agree.
|
|
rseed = rdtsc();
|
|
rseed ^= boot_epoch * 0x9E3779B97F4A7C15ULL;
|
|
rseed ^= (uint64)(uintptr_t)&rseed;
|
|
if (rseed == 0)
|
|
rseed = 0x9E3779B97F4A7C15ULL;
|
|
kinfo("random: no RDRAND; seeded from the cycle counter and the clock\n");
|
|
}
|
|
|
|
int
|
|
nullwrite(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
return n;
|
|
}
|
|
|
|
int
|
|
nullread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
zeroread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
memset(dst, 0, n);
|
|
return n;
|
|
}
|
|
|
|
int
|
|
randomread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
if(!rseed_ready)
|
|
rseed_init();
|
|
|
|
for(int i = 0; i < n; i++){
|
|
// Re-stirred from the cycle counter every byte, so two reads a moment
|
|
// apart do not continue one sequence -- and so that a process cannot
|
|
// predict the next caller's bytes from its own.
|
|
rseed ^= rdtsc();
|
|
rseed = rseed * 6364136223846793005ULL + 1442695040888963407ULL;
|
|
dst[i] = (rseed >> 33) & 0xFF;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// /dev/full — reads as an endless run of zero bytes, every write fails with
|
|
// ENOSPC. Programs use it to exercise their out-of-disk-space paths.
|
|
int
|
|
fullwrite(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
return -ENOSPC;
|
|
}
|
|
|
|
int
|
|
mousewrite(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
return n;
|
|
}
|
|
|
|
int
|
|
memread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
switch(ip->minor) {
|
|
case 1: return consoleread(ip, off, dst, n);
|
|
case 2: return nullread(ip, off, dst, n);
|
|
case 3: return consoleread(ip, off, dst, n);
|
|
/* 10 = /dev/tty, 11..16 = /dev/tty1..tty6. consoleread resolves the
|
|
minor to a terminal; see vt_for_minor(). */
|
|
case 10: case 11: case 12: case 13: case 14: case 15: case 16:
|
|
return consoleread(ip, off, dst, n);
|
|
case 5: return zeroread(ip, off, dst, n);
|
|
case 7: return zeroread(ip, off, dst, n); // /dev/full reads as zeros
|
|
case 8:
|
|
case 9: return randomread(ip, off, dst, n);
|
|
// Not "operation not permitted", which is what -1 means once it reaches
|
|
// userspace. There is no device on this minor.
|
|
default: return -ENODEV;
|
|
}
|
|
}
|
|
|
|
int
|
|
memwrite(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
switch(ip->minor) {
|
|
case 1: return consolewrite(ip, off, dst, n);
|
|
case 10: case 11: case 12: case 13: case 14: case 15: case 16:
|
|
return consolewrite(ip, off, dst, n);
|
|
case 7: return fullwrite(ip, off, dst, n); // /dev/full: always ENOSPC
|
|
case 3: // /dev/keyboard: discarded
|
|
case 2:
|
|
case 5:
|
|
case 8:
|
|
case 9: return nullwrite(ip, off, dst, n);
|
|
default: return -ENODEV;
|
|
}
|
|
}
|
|
|
|
struct devsw devsw[] = {
|
|
[0] = { 0, 0 },
|
|
[1] = { memread, memwrite },
|
|
// /dev/fb0 (major 2) — the framebuffer, kernel/dev/fb.c. This used to be
|
|
// { nullread, nullwrite }, so writing pixels to it succeeded and did
|
|
// nothing.
|
|
// The close hook gives the text console its screen back when whatever was
|
|
// drawing on the framebuffer exits — see fbclose().
|
|
[2] = { fbread, fbwrite, 0, fbclose },
|
|
[3] = { mouseread, mousewrite },
|
|
[4] = { 0, 0 },
|
|
// /dev/tty (major 5) — alias for the console
|
|
[5] = { memread, memwrite },
|
|
// /proc status files (major 6) — see kernel/dev/procfs.c
|
|
[6] = { procread, procwrite },
|
|
// /dev/input/eventN (major 7) — raw evdev events, kernel/dev/input.c. The
|
|
// minor is the device index, so event0 is the keyboard and event1 the mouse.
|
|
[7] = { inputread, inputwrite, inputready, inputclose, inputopen },
|
|
// /dev/hdN (major 8) — raw disks, kernel/dev/disk.c. The minor is the IDE
|
|
// device number. No `ready` hook: a disk is always ready, which is what
|
|
// fileread assumes when one is absent.
|
|
[8] = { diskread, diskwrite },
|
|
// /dev/pci (major 9) -- PCI devices reachable from userspace, for drivers
|
|
// that do not live in the kernel. Registers arrive through mmap and
|
|
// everything else through ioctl; see include/dev/pciuser.h.
|
|
[9] = { pciuser_read, pciuser_write },
|
|
// /dev/dbg (major 10) -- ask the kernel a question. Write a line, read the
|
|
// answer; see include/core/dbg.h and docs/architecture/debugging.md.
|
|
[10] = { dbg_read, dbg_write },
|
|
// /dev/netdev (major 11) -- a process, being a network interface. The
|
|
// reverse of major 9: that hands a device to a process, this lets a process
|
|
// be one. See include/net/netuser.h.
|
|
[11] = { netuser_read, netuser_write, netuser_ready },
|
|
// /dev/dsp (major 12) -- UNIX audio, kernel/dev/audio.c. Write PCM to it.
|
|
// The close hook plays out what is still queued: a program that writes a
|
|
// sound and exits expects to hear the end of it.
|
|
[12] = { dspread, dspwrite, 0, dspclose },
|
|
// Pseudo-terminals, kernel/dev/pty.c. /dev/ptmx (13) is never read or
|
|
// written: open() swaps it for a new master (14) before it gets the chance.
|
|
// The slaves (15) are /dev/pts/N.
|
|
[PTMX_MAJOR] = { 0, 0 },
|
|
[PTM_MAJOR] = { ptm_read, ptm_write, ptm_ready, ptm_close, ptm_open },
|
|
[PTS_MAJOR] = { pts_read, pts_write, pts_ready, pts_close, pts_open },
|
|
};
|