Pseudo-terminals, and what tmux and htop needed around them

- /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
This commit is contained in:
Nexoniarz 2026-10-25 02:11:10 +01:00
commit fd9d392416
28 changed files with 1577 additions and 47 deletions

View file

@ -194,16 +194,36 @@ static void
gen_stat(struct sbuf *sb)
{
// USER_HZ, like times() -- everything that parses /proc/stat divides by 100.
uint t = (uint)((uint64)uptime() * USER_HZ / HZ);
//
// Busy is what processes were charged; idle is the rest of every online
// processor's time. Calling it all user time, as this did, is a machine at
// 100% forever in htop and top. The charge is not kept per processor, so
// each cpuN line is an equal share of the total -- right on average, which
// is all a meter can show anyway.
uint64 n = ncpu_online > 0 ? ncpu_online : 1;
uint64 all = (uint64)uptime() * USER_HZ / HZ * n;
uint64 user, sys;
proc_cputime(&user, &sys);
user = user * USER_HZ / HZ;
sys = sys * USER_HZ / HZ;
if (user + sys > all)
all = user + sys;
uint64 idle = all - user - sys;
// user nice system idle iowait irq softirq steal guest guest_nice
sb_puts(sb, "cpu ");
sb_putu(sb, t); sb_puts(sb, " 0 0 0 0 0 0 0 0 0\n");
sb_putu(sb, user); sb_puts(sb, " 0 ");
sb_putu(sb, sys); sb_putc(sb, ' ');
sb_putu(sb, idle); sb_puts(sb, " 0 0 0 0 0 0\n");
// Online, for the same reason as cpuinfo: top and friends count the cpuN
// lines here to decide how many cores to draw.
for (int i = 0; i < ncpu_online; i++) {
sb_puts(sb, "cpu");
sb_putu(sb, i);
sb_putc(sb, ' ');
sb_putu(sb, t); sb_puts(sb, " 0 0 0 0 0 0 0 0 0\n");
sb_putu(sb, user / n); sb_puts(sb, " 0 ");
sb_putu(sb, sys / n); sb_putc(sb, ' ');
sb_putu(sb, idle / n); sb_puts(sb, " 0 0 0 0 0 0\n");
}
sb_kv(sb, "processes ", (uint64)proc_num(), 0);
sb_kv(sb, "procs_running ", 1, 0);
@ -388,12 +408,42 @@ gen_filesystems(struct sbuf *sb)
#define PROC_BOOTDEBUG 30
#define PROC_SLAB 20
#define PROC_PARTITIONS 21
#define PROC_STRACE 31
// Render global file `id` (a PROC_* constant) into `buf`. Returns its length.
//
// Split out of procread() so the /proc *filesystem* (kernel/fs/procfs.c) can
// use the same generators: that file owns the namespace, this one owns the
// content, and they have no other business with each other.
// /proc/strace: the name of the program to trace, or empty. Read by exec, so
// a change takes effect at the next program started, not in running ones.
static char strace_name[16];
int
strace_wanted(const char *name)
{
return strace_name[0] && strncmp(name, strace_name, sizeof(strace_name)) == 0;
}
int
proc_write_strace(int user_src, uint64 src, uint n)
{
char tmp[16];
uint len = n < sizeof(tmp) - 1 ? n : sizeof(tmp) - 1;
if (user_src) {
if (copy_from_user(tmp, src, len) < 0)
return -EFAULT;
} else {
memmove(tmp, (char *)src, len);
}
while (len > 0 && (tmp[len - 1] == '\n' || tmp[len - 1] == ' '))
len--;
tmp[len] = 0;
memmove(strace_name, tmp, sizeof(strace_name));
return (int)n;
}
int
proc_gen_global(int id, char *buf, int cap)
{
@ -442,6 +492,10 @@ proc_gen_global(int id, char *buf, int cap)
sb_puts(&sb, boot_debug ? "on\n" : "off\n");
break;
case PROC_DEVICES: gen_devices(&sb); break;
case PROC_STRACE:
sb_puts(&sb, strace_name);
sb_putc(&sb, '\n');
break;
// The block layer renders its own table, like PCI and SchFS above.
case PROC_PARTITIONS: sb.len = blkdev_info(buf, cap); break;
default: return 0;