diff --git a/compiler/src/types.ml b/compiler/src/types.ml index 869c727..a67f131 100644 --- a/compiler/src/types.ml +++ b/compiler/src/types.ml @@ -340,6 +340,10 @@ let stdlib_members : stdlib_member list = m "proc" "spawn" 2 97 (Some (TNullable (TScalar child_record_name))) (Some child_record_name); m "proc" "wait_dl" 2 98 (Some (TNullable (TScalar "Int"))) None; m "proc" "signal" 2 99 None None; + (* runtime-v2 2: the PTY child — stdin==stdout=master, stderr -1; + resize refuses by name on a pipe child *) + m "proc" "spawn_pty" 4 100 (Some (TNullable (TScalar child_record_name))) (Some child_record_name); + m "proc" "resize" 3 101 None None; (* json — both members are lowered specially (emit.ml): encode needs its argument's static kind, and decode has no type until an `as` names one, so neither goes through the generic builtin path. They are listed here diff --git a/runtime/src/sysio.c b/runtime/src/sysio.c index 5ddd4b3..6bff5e7 100644 --- a/runtime/src/sysio.c +++ b/runtime/src/sysio.c @@ -21,7 +21,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -1275,6 +1277,107 @@ int wo_builtin_sys(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg) { fb->park_done = 0; return WO_SYS_PARKED; } + /* ---- runtime-v2 2: the PTY child ---------------------------------- */ + case WO_B_PROC_SPAWN_PTY: { /* Child: stdin==stdout=master (raw side — + * the line discipline lives on the slave), + * stderr -1. The slot keeps a private dup + * of the master so resize survives the + * caller closing its copy. */ + char argbuf[62][512]; + char *argv[64]; + if (proc_argv(R[B], R[B + 1], path, sizeof path, argbuf, argv, msg)) + return WO_T_BOUNDS; + int cols = (int)(int64_t)R[B + 2], rows = (int)(int64_t)R[B + 3]; + if (cols <= 0) cols = 80; + if (rows <= 0) rows = 24; + wo_child *ch = proc_slot_claim(vm, msg); + if (!ch) return WO_T_IO; + int master = posix_openpt(O_RDWR | O_NOCTTY); + char sname[128]; + if (master < 0 || grantpt(master) != 0 || unlockpt(master) != 0 || + ptsname_r(master, sname, sizeof sname) != 0) { + if (master >= 0) close(master); + *msg = strerror(errno); + return WO_T_IO; + } + pid_t pid = fork(); + if (pid < 0) { + close(master); + *msg = strerror(errno); + return WO_T_IO; + } + if (pid == 0) { + setsid(); /* the slave becomes the CONTROLLING terminal */ + int slave = open(sname, O_RDWR); + if (slave < 0) _exit(127); + struct winsize ws; + memset(&ws, 0, sizeof ws); + ws.ws_col = (unsigned short)cols; + ws.ws_row = (unsigned short)rows; + ioctl(slave, TIOCSWINSZ, &ws); + dup2(slave, STDIN_FILENO); + dup2(slave, STDOUT_FILENO); + dup2(slave, STDERR_FILENO); + close(slave); + close(master); + execvp(path, argv); + _exit(127); + } + fcntl(master, F_SETFL, fcntl(master, F_GETFL, 0) | O_NONBLOCK); + int pidfd = (int)syscall(SYS_pidfd_open, pid, 0); + if (pidfd < 0) { + int e = errno; + kill(pid, SIGKILL); + int st; + while (waitpid(pid, &st, 0) < 0 && errno == EINTR) {} + close(master); + *msg = strerror(e); + return WO_T_IO; + } + memset(ch, 0, sizeof *ch); + ch->used = 1; + ch->streaming = 1; + ch->pid = (int)pid; + ch->pidfd = pidfd; + ch->epfd = -1; + ch->ofd = ch->efd = -1; + ch->master_dup = dup(master); + ch->gen = ++vm->proc_gen; + ch->owner_actor = vm->cur->actor; + vm->nchildren++; + wo_hdr *o = record_of(vm, R[B + 4], 4, msg); + if (!o) { + close(master); + proc_slot_kill(vm, ch); + return R[B + 4] >= vm->mod->class_cnt ? WO_T_BOUNDS : WO_T_OOM; + } + uint64_t *fp = wo_fields(o); + fp[0] = ((uint64_t)ch->gen << 6) | (uint64_t)(ch - vm->children); + fp[1] = (uint64_t)master; + fp[2] = (uint64_t)master; + fp[3] = (uint64_t)(int64_t)-1; + R[A] = (uint64_t)(uintptr_t)o; + return 0; + } + case WO_B_PROC_RESIZE: { /* (id, cols, rows) -> 0: TIOCSWINSZ through + * the slot's own master dup */ + wo_child *ch = proc_slot_by_id(vm, R[B], msg); + if (!ch) return WO_T_IO; + if (ch->master_dup < 0) { + *msg = "child has no terminal to resize"; + return WO_T_IO; + } + struct winsize ws; + memset(&ws, 0, sizeof ws); + ws.ws_col = (unsigned short)(int64_t)R[B + 1]; + ws.ws_row = (unsigned short)(int64_t)R[B + 2]; + if (ioctl(ch->master_dup, TIOCSWINSZ, &ws) != 0) { + *msg = strerror(errno); + return WO_T_IO; + } + R[A] = 0; + return 0; + } case WO_B_PROC_SIGNAL: { /* (id, sig) -> 0 through the pidfd */ wo_child *ch = proc_slot_by_id(vm, R[B], msg); if (!ch) return WO_T_IO; diff --git a/runtime/test/test_proc.c b/runtime/test/test_proc.c index 25e8fd2..2f0b1d4 100644 --- a/runtime/test/test_proc.c +++ b/runtime/test/test_proc.c @@ -881,6 +881,166 @@ static void test_stream_churn(void) { free(img); } +/* ---- runtime-v2 2: the PTY child ---------------------------------------- + * pty module: spawn_pty `sh -c