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

@ -34,6 +34,7 @@
#include "uapi/stat.h"
#include "core/uaccess.h"
#include "uapi/errno.h"
#include "dev/pty.h"
// ---------------------------------------------------------------------------
// Inode numbering
@ -68,7 +69,8 @@
#define PF_EXE 5
#define PF_SCHEDSTAT 6
#define PF_FD 7 // the directory /proc/<pid>/fd
#define PF_MAX 7
#define PF_TASK 8 // the directory /proc/<pid>/task
#define PF_MAX 8
#define PF_FDENT 0x80 // | fd: one link in it
#define INO_PID(ino) ((int)((ino) >> 8))
@ -109,6 +111,9 @@ static const struct global_file globals[] = {
// Disks and partitions, in Linux's format. See blkdev_info().
{ "partitions", 21 },
{ "kmsg", 17 },
// A program's name: from its next exec on, it and everything it starts log
// every syscall. Empty to stop. See strace_call() in kernel/sys/syscall.c.
{ "strace", 31 },
};
#define NGLOBALS ((int)(sizeof(globals) / sizeof(globals[0])))
@ -132,6 +137,11 @@ static const struct global_file pidfiles[] = {
// One symlink per open descriptor, named by its number and pointing at the
// path it was opened by, or at "socket:[ino]" and the like.
{ "fd", PF_FD },
// The process's threads, one directory each, named by thread id. A process
// here is one thread, so task/ holds a single entry, the pid, and it *is*
// /proc/<pid> -- the same inode. htop reads every process through it and
// shows nothing at all without it.
{ "task", PF_TASK },
};
#define NPIDFILES ((int)(sizeof(pidfiles) / sizeof(pidfiles[0])))
@ -201,8 +211,16 @@ gen_pid_stat(struct sb *s, const struct procinfo *pi)
// header and nothing else: ps with no arguments shows only the processes on
// *this* terminal, and nothing could ever match. `ps -e` always worked.
sbputc(s, ' ');
sbputi(s, pi->ctty ? ((1 << 8) | (10 + pi->ctty)) : 0);
sbputs(s, " -1 0"); // 8 tpgid, 9 flags
// A pty is Linux's (136, N), the same number stat() gives /dev/pts/N.
if (pi->ctty >= PTY_CTTY)
sbputi(s, (136 << 8) | (pi->ctty - PTY_CTTY));
else
sbputi(s, pi->ctty ? ((1 << 8) | (10 + pi->ctty)) : 0);
// 8 tpgid, 9 flags. PF_KTHREAD (0x00200000) on a kernel thread is how htop
// and ps tell one from a process and hide it; without it acpi and dbgd
// listed as programs of four bytes each.
sbputs(s, " -1 ");
sbputi(s, pi->kthread ? 0x00200000 : 0);
// 10..13: minflt cminflt majflt cmajflt. There is no fault accounting, and
// a fabricated number is worse than none -- top would draw a plausible graph
@ -320,18 +338,20 @@ procfs_iload(struct inode *ip)
ip->valid = 1;
ip->nlink = 1;
ip->major = ip->minor = 0;
// Read-only, with two exceptions: /proc/sysrq is the deliberate write target
// (`echo c > /proc/sysrq` to crash on purpose), and /proc/schinit is where
// PID 1 publishes its job table. Marking everything 2 made filewrite()
// Read-only, with three exceptions: /proc/sysrq is the deliberate write
// target (`echo c > /proc/sysrq` to crash on purpose), /proc/schinit is where
// PID 1 publishes its job table, and /proc/strace takes a name to trace. Marking everything 2 made filewrite()
// refuse with "permission denied", which the USB suite caught immediately --
// three of its checks drive the disk through sysrq.
ip->rwmode = (INO_IS_GLOBAL(ino) &&
((ino & 0xFF) == 10 || (ino & 0xFF) == 16)) ? 3 : 2;
((ino & 0xFF) == 10 || (ino & 0xFF) == 16 ||
(ino & 0xFF) == 31)) ? 3 : 2;
ip->showmode = 1;
ip->atime = ip->mtime = ip->ctime = 0;
if (ino == PROCFS_ROOT ||
(!INO_IS_GLOBAL(ino) && (INO_FILE(ino) == PF_DIR || INO_FILE(ino) == PF_FD))) {
(!INO_IS_GLOBAL(ino) && (INO_FILE(ino) == PF_DIR || INO_FILE(ino) == PF_FD ||
INO_FILE(ino) == PF_TASK))) {
ip->type = T_DIR;
ip->size = 0;
ip->nlink = 2;
@ -460,6 +480,8 @@ procfs_write(struct inode *ip, int user_src, uint64 src, uint64 off, uint n)
// should be doing, and PID 1 already knows when the table changed.
if (INO_IS_GLOBAL(ip->inum) && (ip->inum & 0xFF) == 16)
return proc_write_schinit(user_src, src, n);
if (INO_IS_GLOBAL(ip->inum) && (ip->inum & 0xFF) == 31)
return proc_write_strace(user_src, src, n);
(void)off;
return -EROFS;
}
@ -495,8 +517,16 @@ procfs_dirlookup(struct inode *dp, char *name, uint *poff)
if (namecmp(name, ".") == 0)
return iget(dp->dev, dp->inum);
if (namecmp(name, "..") == 0) // /proc/<pid>/.. is /proc
return iget(dp->dev, INO_FILE(dp->inum) == PF_FD ? (dp->inum & ~0xFFULL)
: PROCFS_ROOT);
return iget(dp->dev, INO_FILE(dp->inum) == PF_FD ||
INO_FILE(dp->inum) == PF_TASK ? (dp->inum & ~0xFFULL)
: PROCFS_ROOT);
// Inside /proc/<pid>/task/: the one thread, which is the process.
if (dp->inum != PROCFS_ROOT && INO_FILE(dp->inum) == PF_TASK) {
if (name_to_pid(name) != INO_PID(dp->inum))
return 0;
return iget(dp->dev, (dp->inum & ~0xFFULL) | PF_DIR);
}
// Inside /proc/<pid>/fd/: a number that is an open descriptor.
if (dp->inum != PROCFS_ROOT && INO_FILE(dp->inum) == PF_FD) {
@ -631,12 +661,18 @@ procfs_getdents(struct inode *dp, char *out, int cap, uint64 *off)
utoa((uint)i, name);
EMIT((dp->inum & ~0xFFULL) | PF_FDENT | i, DT_LNK_, name);
}
} else if (INO_FILE(dp->inum) == PF_TASK) {
EMIT(dp->inum, DT_DIR_, ".");
EMIT(dp->inum & ~0xFFULL, DT_DIR_, "..");
utoa((uint)INO_PID(dp->inum), name);
EMIT((dp->inum & ~0xFFULL) | PF_DIR, DT_DIR_, name);
} else {
EMIT(dp->inum, DT_DIR_, ".");
EMIT(PROCFS_ROOT, DT_DIR_, "..");
for (i = 0; i < NPIDFILES; i++)
EMIT((dp->inum & ~0xFFULL) | pidfiles[i].id,
pidfiles[i].id == PF_FD ? DT_DIR_ : pidfiles[i].id == PF_EXE ? DT_LNK_ : DT_REG_,
pidfiles[i].id == PF_FD || pidfiles[i].id == PF_TASK ? DT_DIR_ :
pidfiles[i].id == PF_EXE ? DT_LNK_ : DT_REG_,
pidfiles[i].name);
}