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
|
|
@ -38,6 +38,7 @@
|
|||
#include "fs/file.h"
|
||||
#include "uapi/errno.h"
|
||||
#include "fs/blkdev.h"
|
||||
#include "dev/pty.h"
|
||||
|
||||
// Inode numbering. Two directories and a flat table of nodes, so the number is
|
||||
// either a directory id or an index into devnodes[] offset past them.
|
||||
|
|
@ -49,6 +50,12 @@
|
|||
// device number, and inode DEVFS_BLK0 plus that number. Not in devnodes[],
|
||||
// because they come and go: a stick plugged in appears, pulled out vanishes.
|
||||
#define DEVFS_BLK0 4096ULL
|
||||
// Pseudo-terminals: the /dev/pts directory, its slaves by pair number, and the
|
||||
// masters, which have inodes so that an open file can hold one but appear in no
|
||||
// directory -- the only way to one is opening /dev/ptmx.
|
||||
#define DEVFS_PTS 3ULL
|
||||
#define DEVFS_PTS0 8192ULL
|
||||
#define DEVFS_PTM0 12288ULL
|
||||
|
||||
// Which directory a node lives in. Only two exist and a third would want a real
|
||||
// tree rather than a flag, which is the point at which this file should grow
|
||||
|
|
@ -110,6 +117,8 @@ static const struct devnode devnodes[] = {
|
|||
// Audio, spelled the way UNIX spells it. See include/dev/audio.h.
|
||||
{ "dsp", 12, 0, D_ROOT },
|
||||
{ "mouse", 3, 1, D_ROOT },
|
||||
// Opening it makes a new pseudo-terminal pair; see kernel/dev/pty.c.
|
||||
{ "ptmx", PTMX_MAJOR, 0, D_ROOT },
|
||||
|
||||
// Raw disks. Created whether or not a drive is present: opening one with no
|
||||
// disk behind it fails with ENXIO, which is a better answer than a missing
|
||||
|
|
@ -143,9 +152,22 @@ devfs_iload(struct inode *ip)
|
|||
ip->major = ip->minor = 0;
|
||||
ip->size = 0;
|
||||
|
||||
if (ip->inum == DEVFS_ROOT || ip->inum == DEVFS_INPUT) {
|
||||
if (ip->inum == DEVFS_ROOT || ip->inum == DEVFS_INPUT ||
|
||||
ip->inum == DEVFS_PTS) {
|
||||
ip->type = T_DIR;
|
||||
ip->nlink = 2;
|
||||
} else if (ip->inum >= DEVFS_PTS0 && ip->inum < DEVFS_PTS0 + NPTY &&
|
||||
pty_used((int)(ip->inum - DEVFS_PTS0))) {
|
||||
ip->type = T_DEV;
|
||||
ip->nlink = 1;
|
||||
ip->major = PTS_MAJOR;
|
||||
ip->minor = (short)(ip->inum - DEVFS_PTS0);
|
||||
ip->mode = 0620;
|
||||
} else if (ip->inum >= DEVFS_PTM0 && ip->inum < DEVFS_PTM0 + NPTY) {
|
||||
ip->type = T_DEV;
|
||||
ip->nlink = 1;
|
||||
ip->major = PTM_MAJOR;
|
||||
ip->minor = (short)(ip->inum - DEVFS_PTM0);
|
||||
} else if (i >= 0) {
|
||||
ip->type = T_DEV;
|
||||
ip->nlink = 1;
|
||||
|
|
@ -196,6 +218,32 @@ devfs_dirlookup(struct inode *dp, char *name, uint *poff)
|
|||
|
||||
if (dp->inum == DEVFS_ROOT && streq(name, "input"))
|
||||
return iget(DEVFS_DEV, DEVFS_INPUT);
|
||||
if (dp->inum == DEVFS_ROOT && streq(name, "pts"))
|
||||
return iget(DEVFS_DEV, DEVFS_PTS);
|
||||
|
||||
// /dev/pts/N: a pair that exists.
|
||||
if (dp->inum == DEVFS_PTS) {
|
||||
int n = 0;
|
||||
const char *s = name;
|
||||
if (!*s)
|
||||
return 0;
|
||||
for (; *s; s++) {
|
||||
if (*s < '0' || *s > '9')
|
||||
return 0;
|
||||
n = n * 10 + (*s - '0');
|
||||
if (n >= NPTY)
|
||||
return 0;
|
||||
}
|
||||
return pty_used(n) ? iget(DEVFS_DEV, DEVFS_PTS0 + (uint64)n) : 0;
|
||||
}
|
||||
|
||||
// /dev/tty is the caller's controlling terminal. When that is a pty, it is
|
||||
// the slave itself -- what a program inside xterm or tmux opens to talk to
|
||||
// its terminal must be that terminal, not the console.
|
||||
if (dp->inum == DEVFS_ROOT && streq(name, "tty") && myproc() &&
|
||||
myproc()->ctty >= PTY_CTTY && myproc()->ctty < PTY_CTTY + NPTY &&
|
||||
pty_used(myproc()->ctty - PTY_CTTY))
|
||||
return iget(DEVFS_DEV, DEVFS_PTS0 + (uint64)(myproc()->ctty - PTY_CTTY));
|
||||
|
||||
short want = dp->inum == DEVFS_INPUT ? D_INPUT : D_ROOT;
|
||||
for (int i = 0; i < NDEVNODES; i++)
|
||||
|
|
@ -258,7 +306,8 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
|
|||
int used = 0;
|
||||
uint64 n = *poff;
|
||||
|
||||
if (dp->inum != DEVFS_ROOT && dp->inum != DEVFS_INPUT)
|
||||
if (dp->inum != DEVFS_ROOT && dp->inum != DEVFS_INPUT &&
|
||||
dp->inum != DEVFS_PTS)
|
||||
return -1;
|
||||
|
||||
if (n == 0) {
|
||||
|
|
@ -271,15 +320,36 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
|
|||
n++;
|
||||
}
|
||||
|
||||
// /dev/input is a directory entry of the root, and it is emitted between the
|
||||
// dots and the nodes so that its ordinal is stable.
|
||||
// /dev/pts: the dots, then the pairs that exist. Ordinal 2 + N is pair N,
|
||||
// whether or not the ones before it exist, so a pair opened between two
|
||||
// calls does not shift the rest.
|
||||
if (dp->inum == DEVFS_PTS) {
|
||||
for (; n >= 2 && n < 2 + NPTY; n++) {
|
||||
int k = (int)(n - 2);
|
||||
char nm[4];
|
||||
if (!pty_used(k))
|
||||
continue;
|
||||
nm[0] = k >= 10 ? '0' + k / 10 : '0' + k;
|
||||
nm[1] = k >= 10 ? '0' + k % 10 : 0;
|
||||
nm[2] = 0;
|
||||
if (emit(out, cap, &used, DEVFS_PTS0 + (uint64)k, 2, nm) < 0) goto out;
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
// /dev/input and /dev/pts are directory entries of the root, emitted
|
||||
// between the dots and the nodes so that their ordinals are stable.
|
||||
if (dp->inum == DEVFS_ROOT && n == 2) {
|
||||
if (emit(out, cap, &used, DEVFS_INPUT, 4, "input") < 0) goto out;
|
||||
n++;
|
||||
}
|
||||
if (dp->inum == DEVFS_ROOT && n == 3) {
|
||||
if (emit(out, cap, &used, DEVFS_PTS, 4, "pts") < 0) goto out;
|
||||
n++;
|
||||
}
|
||||
|
||||
short want = dp->inum == DEVFS_INPUT ? D_INPUT : D_ROOT;
|
||||
uint64 base = dp->inum == DEVFS_ROOT ? 3 : 2;
|
||||
uint64 base = dp->inum == DEVFS_ROOT ? 4 : 2;
|
||||
|
||||
for (int i = 0; i < NDEVNODES; i++) {
|
||||
if (devnodes[i].dir != want)
|
||||
|
|
@ -339,3 +409,12 @@ devfs_mount(const char *target)
|
|||
iput(dp);
|
||||
return r < 0 ? -EBUSY : 0;
|
||||
}
|
||||
|
||||
// The inode of pty master N, for ptmx_open() to hand to the file it opens.
|
||||
struct inode *
|
||||
devfs_pty_master(int n)
|
||||
{
|
||||
if (n < 0 || n >= NPTY)
|
||||
return 0;
|
||||
return iget(DEVFS_DEV, DEVFS_PTM0 + (uint64)n);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -535,6 +535,8 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev)
|
|||
if(*s == '/')
|
||||
last = s+1;
|
||||
safestrcpy(myproc()->name, last, sizeof(myproc()->name));
|
||||
if (strace_wanted(myproc()->name))
|
||||
myproc()->strace = 1;
|
||||
safestrcpy(myproc()->exe, path, sizeof(myproc()->exe));
|
||||
|
||||
for(int i = 0; i < NOFILE; i++){
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "uapi/posix.h"
|
||||
#include "uapi/errno.h"
|
||||
#include "core/kmem.h"
|
||||
#include "dev/pty.h"
|
||||
|
||||
extern struct devsw devsw[];
|
||||
|
||||
|
|
@ -422,6 +423,10 @@ file_poll(struct file *f, int events)
|
|||
return rev;
|
||||
}
|
||||
|
||||
if(f->ip && f->ip->type == T_DEV &&
|
||||
(f->ip->major == PTM_MAJOR || f->ip->major == PTS_MAJOR))
|
||||
return pty_poll(f->ip, events, f->readable, f->writable);
|
||||
|
||||
if(f->ip && f->ip->type == T_DEV && f->ip->major == CONSOLE){
|
||||
int rev = 0;
|
||||
if((events & POLLIN) && f->readable && console_input_ready(f->ip ? f->ip->minor : 1))
|
||||
|
|
|
|||
|
|
@ -624,8 +624,10 @@ ilock(struct inode *ip)
|
|||
|
||||
acquiresleep(&ip->lock);
|
||||
|
||||
if (ip->valid == 0)
|
||||
if (ip->valid == 0) {
|
||||
ip->mode = 0;
|
||||
ip->ops->iload(ip);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ schfs_vfs_iload(struct inode *ip)
|
|||
ip->nlink = in.nlink > 0x7fffffffu ? 0x7fffffff : (int)in.nlink;
|
||||
ip->size = in.size;
|
||||
ip->rwmode = RW;
|
||||
ip->mode = in.mode & 07777; // 0 on disk: never recorded
|
||||
ip->supermode = 0;
|
||||
ip->showmode = 1;
|
||||
ip->atime = (uint)in.atime;
|
||||
|
|
@ -134,6 +135,8 @@ schfs_vfs_istore(struct inode *ip)
|
|||
in.mode = ip->type == T_DIR ? 0755 : 0644;
|
||||
}
|
||||
|
||||
if (ip->mode)
|
||||
in.mode = ip->mode & 07777;
|
||||
in.type = type_from_vfs(ip->type);
|
||||
in.rdev_major = (uint16_t)ip->major;
|
||||
in.rdev_minor = (uint16_t)ip->minor;
|
||||
|
|
|
|||
Loading…
Reference in a new issue