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
|
|
@ -18,6 +18,7 @@
|
|||
#include "fs/schfs.h" /* SCHFS_BLKSIZE, SCHFS_NAME_MAX — statfs reports them */
|
||||
#include "gfx/gfx.h" /* gfx_cols/gfx_rows — TIOCGWINSZ reports them */
|
||||
#include "dev/vt.h" /* the terminals the TIOC and VT_ ioctls act on */
|
||||
#include "dev/pty.h"
|
||||
#include "dev/pciuser.h"
|
||||
#include "dev/keymap.h" /* KB_*LAYOUT — the keyboard layout catalogue */
|
||||
#include "net/netuser.h"
|
||||
|
|
@ -83,6 +84,10 @@ linux_tty_rdev(uint32 major, uint32 minor)
|
|||
if (major == 1 && minor >= 11 && minor <= 16) return linux_makedev(4, minor - 10);
|
||||
if (major == 1 && minor == 10) return linux_makedev(5, 0);
|
||||
if (major == 1 && minor == 1) return linux_makedev(5, 1);
|
||||
// Pseudo-terminals, as Linux numbers them: ptmx (5, 2), slaves 136.
|
||||
if (major == PTMX_MAJOR) return linux_makedev(5, 2);
|
||||
if (major == PTS_MAJOR) return linux_makedev(136, minor);
|
||||
if (major == PTM_MAJOR) return linux_makedev(128, minor);
|
||||
return linux_makedev(major, minor);
|
||||
}
|
||||
|
||||
|
|
@ -112,6 +117,10 @@ stati_linux(struct inode *ip, struct linux_stat *st)
|
|||
case T_SOCK: type_bits = LINUX_S_IFSOCK; perm_bits = 0755; break;
|
||||
default: type_bits = LINUX_S_IFREG; perm_bits = (ip->rwmode & 2) ? 0755 : 0555; break;
|
||||
}
|
||||
// A filesystem that keeps real permission bits -- SchFS does -- reports
|
||||
// those instead. tmux, ssh and gpg refuse a directory others can read.
|
||||
if (ip->mode)
|
||||
perm_bits = ip->mode & 07777;
|
||||
st->st_mode = type_bits | perm_bits;
|
||||
|
||||
// Which device this node actually is. Left at zero until now, so every
|
||||
|
|
@ -264,9 +273,21 @@ path_toolong(const char *path)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// The mode argument of a Linux open/mkdir, or -1 from a native program, whose
|
||||
// calls have no such argument and leave whatever was in the register.
|
||||
static int
|
||||
linux_arg_mode(int n)
|
||||
{
|
||||
int mode;
|
||||
if (!myproc()->syscall_map)
|
||||
return -1;
|
||||
argint(n, &mode);
|
||||
return mode & 07777;
|
||||
}
|
||||
|
||||
static struct inode*
|
||||
createat_x(int dirfd, char *path, short type, short major, short minor,
|
||||
int *err)
|
||||
int *err, int mode)
|
||||
{
|
||||
struct inode *ip, *dp;
|
||||
char name[VFS_NAME_MAX + 1];
|
||||
|
|
@ -320,6 +341,14 @@ createat_x(int dirfd, char *path, short type, short major, short minor,
|
|||
ilock(ip);
|
||||
ip->major = major; ip->minor = minor; ip->nlink = 1;
|
||||
ip->showmode = 1;
|
||||
// The permission bits asked for, less the umask -- or, from a caller that
|
||||
// has none to give, what open() and mkdir() would ask for. A symlink's
|
||||
// are 0777 on Linux whatever the umask, and nothing reads them.
|
||||
if (type == T_SYMLINK)
|
||||
ip->mode = 0777;
|
||||
else
|
||||
ip->mode = (uint)(mode >= 0 ? mode : type == T_DIR ? 0777 : 0666)
|
||||
& 07777 & ~myproc()->umask;
|
||||
iupdate(ip);
|
||||
if (type == T_DIR) {
|
||||
dp->nlink++;
|
||||
|
|
@ -343,7 +372,7 @@ createat_x(int dirfd, char *path, short type, short major, short minor,
|
|||
static struct inode*
|
||||
create(char *path, short type, short major, short minor)
|
||||
{
|
||||
return createat_x(AT_FDCWD, path, type, major, minor, 0);
|
||||
return createat_x(AT_FDCWD, path, type, major, minor, 0, -1);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
@ -571,12 +600,16 @@ addr_t sys_pipe(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// The flags were ignored, and they are the point of pipe2: libevent makes its
|
||||
// signal pipe with O_NONBLOCK and drains it until EAGAIN, so the drain blocked
|
||||
// on the read after the last byte and tmux's client stopped dead.
|
||||
addr_t sys_pipe2(void)
|
||||
{
|
||||
addr_t fdarray;
|
||||
struct file *rf, *wf;
|
||||
int fd0, fd1;
|
||||
int fd0, fd1, flags;
|
||||
if (argaddr(0, &fdarray) < 0) return -1;
|
||||
argint(1, &flags);
|
||||
int *fds = (int*)user_ptr(fdarray, 2 * sizeof(int));
|
||||
if (!fds || pipealloc(&rf, &wf) < 0) return -1;
|
||||
fd0 = fd1 = -1;
|
||||
|
|
@ -585,6 +618,11 @@ addr_t sys_pipe2(void)
|
|||
fileclose(rf); fileclose(wf);
|
||||
return -1;
|
||||
}
|
||||
rf->nonblock = wf->nonblock = (flags & O_NONBLOCK) != 0;
|
||||
if (flags & O_CLOEXEC) {
|
||||
myproc()->ofile_flags[fd0] |= FD_CLOEXEC;
|
||||
myproc()->ofile_flags[fd1] |= FD_CLOEXEC;
|
||||
}
|
||||
fds[0] = fd0; fds[1] = fd1;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -642,7 +680,8 @@ addr_t sys_open(void)
|
|||
if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -EISDIR; }
|
||||
} else
|
||||
ip = createat_x(AT_FDCWD, path,
|
||||
(omode & O_EXTENT) ? T_EXTENT : T_FILE, 0, 0, &cerr);
|
||||
(omode & O_EXTENT) ? T_EXTENT : T_FILE, 0, 0, &cerr,
|
||||
linux_arg_mode(2));
|
||||
// A refusal because the filesystem is read-only is not "no such file".
|
||||
// A shell prints the same sentence for every create failure, so
|
||||
// returning ENOENT here made writing to a snapshot look like a missing
|
||||
|
|
@ -660,6 +699,13 @@ addr_t sys_open(void)
|
|||
if (ip->type == T_DIR && (omode & O_ACCMODE) != O_RDONLY) { iunlockput(ip); end_op(); return -EISDIR; }
|
||||
if ((omode & O_TRUNC) && ip->type == T_FILE) itrunc(ip);
|
||||
}
|
||||
// /dev/ptmx: every open is a new pseudo-terminal, and the file holds its
|
||||
// master rather than the ptmx node. See kernel/dev/pty.c.
|
||||
if (ip->type == T_DEV && ip->major == PTMX_MAJOR) {
|
||||
struct inode *m = ptmx_open(ip);
|
||||
if (!m) { iunlockput(ip); end_op(); return -ENOSPC; }
|
||||
ip = m;
|
||||
}
|
||||
if (!(f = filealloc()) || (fd = fdalloc(f)) < 0) {
|
||||
if (f) fileclose(f);
|
||||
iunlockput(ip); end_op(); return -EMFILE;
|
||||
|
|
@ -699,7 +745,8 @@ addr_t sys_openat(void)
|
|||
if ((ip = open_existing(dirfd, path, omode)) != 0) {
|
||||
ilock(ip);
|
||||
if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -EISDIR; }
|
||||
} else if (!(ip = createat_x(dirfd, path, T_FILE, 0, 0, &cerr))) {
|
||||
} else if (!(ip = createat_x(dirfd, path, T_FILE, 0, 0, &cerr,
|
||||
linux_arg_mode(3)))) {
|
||||
end_op();
|
||||
return cerr ? -cerr : -ENOENT;
|
||||
}
|
||||
|
|
@ -717,6 +764,13 @@ addr_t sys_openat(void)
|
|||
// depending on which entry point libc happened to call.
|
||||
if ((omode & O_TRUNC) && (ip->type == T_FILE || ip->type == T_EXTENT))
|
||||
itrunc(ip);
|
||||
// /dev/ptmx: every open is a new pseudo-terminal, and the file holds its
|
||||
// master rather than the ptmx node. See kernel/dev/pty.c.
|
||||
if (ip->type == T_DEV && ip->major == PTMX_MAJOR) {
|
||||
struct inode *m = ptmx_open(ip);
|
||||
if (!m) { iunlockput(ip); end_op(); return -ENOSPC; }
|
||||
ip = m;
|
||||
}
|
||||
if (!(f = filealloc()) || (fd = fdalloc(f)) < 0) {
|
||||
if (f) fileclose(f);
|
||||
iunlockput(ip); end_op(); return -EMFILE;
|
||||
|
|
@ -905,7 +959,8 @@ addr_t sys_mkdir(void)
|
|||
return -EEXIST;
|
||||
}
|
||||
int cerr = 0;
|
||||
if (!(ip = createat_x(AT_FDCWD, path, T_DIR, 0, 0, &cerr))) {
|
||||
if (!(ip = createat_x(AT_FDCWD, path, T_DIR, 0, 0, &cerr,
|
||||
linux_arg_mode(1)))) {
|
||||
end_op();
|
||||
return cerr ? -cerr : -ENOENT;
|
||||
}
|
||||
|
|
@ -948,7 +1003,7 @@ do_mknod(int dirfd, char *path, uint32 mode, uint64 dev)
|
|||
if (ex) { iput(ex); end_op(); return -EEXIST; }
|
||||
int cerr = 0;
|
||||
struct inode *ip = createat_x(dirfd, path, type, (short)major,
|
||||
(short)minor, &cerr);
|
||||
(short)minor, &cerr, (int)(mode & 07777));
|
||||
if (!ip) { end_op(); return cerr ? -cerr : -EEXIST; }
|
||||
iunlockput(ip); end_op();
|
||||
return 0;
|
||||
|
|
@ -1275,7 +1330,7 @@ do_symlink(int newdirfd, char *target, char *linkpath)
|
|||
return -EEXIST;
|
||||
}
|
||||
int cerr = 0;
|
||||
if (!(ip = createat_x(newdirfd, linkpath, T_SYMLINK, 0, 0, &cerr))) {
|
||||
if (!(ip = createat_x(newdirfd, linkpath, T_SYMLINK, 0, 0, &cerr, -1))) {
|
||||
end_op();
|
||||
return cerr ? -cerr : -ENOENT;
|
||||
}
|
||||
|
|
@ -1503,6 +1558,9 @@ addr_t sys_ioctl(void)
|
|||
if (is_dev && f->ip->major == AUDIO_MAJOR)
|
||||
return dsp_ioctl((int)request, argp);
|
||||
|
||||
if (is_dev && (f->ip->major == PTM_MAJOR || f->ip->major == PTS_MAJOR))
|
||||
return pty_ioctl(f->ip, (uint)request, argp);
|
||||
|
||||
int is_tty = (is_dev && f->ip->major == 1);
|
||||
if (!is_tty) {
|
||||
// -ENOTTY, not 0.
|
||||
|
|
@ -1776,8 +1834,50 @@ addr_t sys_ioctl(void)
|
|||
// createat() — every *at() syscall below uses them for dirfd resolution.)
|
||||
|
||||
// chmod / fchmod / chown / fchown / lchown — stubs (no permission model yet).
|
||||
addr_t sys_chmod(void) { return 0; }
|
||||
addr_t sys_fchmod(void) { return 0; }
|
||||
// The new bits go in the inode and through iupdate to the filesystem. One
|
||||
// that keeps none (FAT) holds them only while the inode is cached, which is
|
||||
// as much as it can; there is no failure to report.
|
||||
static int
|
||||
chmod_inode(struct inode *ip, int mode)
|
||||
{
|
||||
if (fs_readonly(ip->dev))
|
||||
return -EROFS;
|
||||
ilock(ip);
|
||||
ip->mode = (uint)mode & 07777;
|
||||
if (ip->mode == 0)
|
||||
ip->mode = 01000000; // "no bits", as distinct from "never set"
|
||||
iupdate(ip);
|
||||
iunlock(ip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t sys_chmod(void)
|
||||
{
|
||||
char *path;
|
||||
int mode, err, r;
|
||||
struct inode *ip;
|
||||
if (argstr(0, &path) < 0) return -EFAULT;
|
||||
argint(1, &mode);
|
||||
begin_op();
|
||||
if (!(ip = at_nameif(AT_FDCWD, path, &err))) { end_op(); return -err; }
|
||||
r = chmod_inode(ip, mode);
|
||||
iput(ip);
|
||||
end_op();
|
||||
return r;
|
||||
}
|
||||
|
||||
addr_t sys_fchmod(void)
|
||||
{
|
||||
struct file *f;
|
||||
int mode, r;
|
||||
if (argfd(0, 0, &f) < 0) return -EBADF;
|
||||
argint(1, &mode);
|
||||
if (f->type != FD_INODE || !f->ip) return 0;
|
||||
begin_op();
|
||||
r = chmod_inode(f->ip, mode);
|
||||
end_op();
|
||||
return r;
|
||||
}
|
||||
addr_t sys_chown(void) { return 0; }
|
||||
addr_t sys_fchown(void) { return 0; }
|
||||
addr_t sys_lchown(void) { return 0; }
|
||||
|
|
@ -2054,7 +2154,8 @@ addr_t sys_mkdirat(void)
|
|||
return -EEXIST;
|
||||
}
|
||||
int cerr = 0;
|
||||
if (!(ip = createat_x(dirfd, path, T_DIR, 0, 0, &cerr))) {
|
||||
if (!(ip = createat_x(dirfd, path, T_DIR, 0, 0, &cerr,
|
||||
linux_arg_mode(2)))) {
|
||||
end_op();
|
||||
return cerr ? -cerr : -ENOENT;
|
||||
}
|
||||
|
|
@ -2183,7 +2284,21 @@ addr_t sys_readlinkat(void)
|
|||
return got;
|
||||
}
|
||||
|
||||
addr_t sys_fchmodat(void) { return 0; } // stub
|
||||
addr_t sys_fchmodat(void)
|
||||
{
|
||||
int dirfd, mode, err, r;
|
||||
char *path;
|
||||
struct inode *ip;
|
||||
argint(0, &dirfd);
|
||||
if (argstr(1, &path) < 0) return -EFAULT;
|
||||
argint(2, &mode);
|
||||
begin_op();
|
||||
if (!(ip = at_nameif(dirfd, path, &err))) { end_op(); return -err; }
|
||||
r = chmod_inode(ip, mode);
|
||||
iput(ip);
|
||||
end_op();
|
||||
return r;
|
||||
}
|
||||
addr_t sys_fchownat(void) { return 0; } // stub
|
||||
|
||||
addr_t sys_faccessat(void)
|
||||
|
|
|
|||
Loading…
Reference in a new issue