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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue