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:
parent
c65bfa3fa3
commit
fd9d392416
28 changed files with 1577 additions and 47 deletions
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue