aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/os/pty_proc_unix.c
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2024-11-19 22:57:13 +0000
committerJosh Rahm <joshuarahm@gmail.com>2024-11-19 22:57:13 +0000
commit9be89f131f87608f224f0ee06d199fcd09d32176 (patch)
tree11022dcfa9e08cb4ac5581b16734196128688d48 /src/nvim/os/pty_proc_unix.c
parentff7ed8f586589d620a806c3758fac4a47a8e7e15 (diff)
parent88085c2e80a7e3ac29aabb6b5420377eed99b8b6 (diff)
downloadrneovim-9be89f131f87608f224f0ee06d199fcd09d32176.tar.gz
rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.tar.bz2
rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.zip
Merge remote-tracking branch 'upstream/master' into mix_20240309
Diffstat (limited to 'src/nvim/os/pty_proc_unix.c')
-rw-r--r--src/nvim/os/pty_proc_unix.c416
1 files changed, 416 insertions, 0 deletions
diff --git a/src/nvim/os/pty_proc_unix.c b/src/nvim/os/pty_proc_unix.c
new file mode 100644
index 0000000000..3bca065d2d
--- /dev/null
+++ b/src/nvim/os/pty_proc_unix.c
@@ -0,0 +1,416 @@
+// Some of the code came from pangoterm and libuv
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <uv.h>
+
+// forkpty is not in POSIX, so headers are platform-specific
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+# include <libutil.h>
+#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
+# include <util.h>
+#elif defined(__sun)
+# include <fcntl.h>
+# include <signal.h>
+# include <sys/stream.h>
+# include <sys/syscall.h>
+# include <unistd.h>
+#else
+# include <pty.h>
+#endif
+
+#ifdef __APPLE__
+# include <crt_externs.h>
+#endif
+
+#include "auto/config.h"
+#include "nvim/eval/typval.h"
+#include "nvim/event/defs.h"
+#include "nvim/event/loop.h"
+#include "nvim/event/proc.h"
+#include "nvim/log.h"
+#include "nvim/os/fs.h"
+#include "nvim/os/os_defs.h"
+#include "nvim/os/pty_proc.h"
+#include "nvim/os/pty_proc_unix.h"
+#include "nvim/types_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/pty_proc_unix.c.generated.h"
+#endif
+
+#if defined(__sun) && !defined(HAVE_FORKPTY)
+
+// this header defines STR, just as nvim.h, but it is defined as ('S'<<8),
+// to avoid #undef STR, #undef STR, #define STR ('S'<<8) just delay the
+// inclusion of the header even though it gets include out of order.
+# include <sys/stropts.h>
+
+static int openpty(int *amaster, int *aslave, char *name, struct termios *termp,
+ struct winsize *winp)
+{
+ int slave = -1;
+ int master = open("/dev/ptmx", O_RDWR);
+ if (master == -1) {
+ goto error;
+ }
+
+ // grantpt will invoke a setuid program to change permissions
+ // and might fail if SIGCHLD handler is set, temporarily reset
+ // while running
+ void (*sig_saved)(int) = signal(SIGCHLD, SIG_DFL);
+ int res = grantpt(master);
+ signal(SIGCHLD, sig_saved);
+
+ if (res == -1 || unlockpt(master) == -1) {
+ goto error;
+ }
+
+ char *slave_name = ptsname(master);
+ if (slave_name == NULL) {
+ goto error;
+ }
+
+ slave = open(slave_name, O_RDWR|O_NOCTTY);
+ if (slave == -1) {
+ goto error;
+ }
+
+ // ptem emulates a terminal when used on a pseudo terminal driver,
+ // must be pushed before ldterm
+ ioctl(slave, I_PUSH, "ptem");
+ // ldterm provides most of the termio terminal interface
+ ioctl(slave, I_PUSH, "ldterm");
+ // ttcompat compatibility with older terminal ioctls
+ ioctl(slave, I_PUSH, "ttcompat");
+
+ if (termp) {
+ tcsetattr(slave, TCSAFLUSH, termp);
+ }
+ if (winp) {
+ ioctl(slave, TIOCSWINSZ, winp);
+ }
+
+ *amaster = master;
+ *aslave = slave;
+ // ignoring name, not passed and size is unknown in the API
+
+ return 0;
+
+error:
+ if (slave != -1) {
+ close(slave);
+ }
+ if (master != -1) {
+ close(master);
+ }
+ return -1;
+}
+
+static int login_tty(int fd)
+{
+ setsid();
+ if (ioctl(fd, TIOCSCTTY, NULL) == -1) {
+ return -1;
+ }
+
+ dup2(fd, STDIN_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ dup2(fd, STDERR_FILENO);
+ if (fd > STDERR_FILENO) {
+ close(fd);
+ }
+
+ return 0;
+}
+
+static pid_t forkpty(int *amaster, char *name, struct termios *termp, struct winsize *winp)
+{
+ int master, slave;
+ if (openpty(&master, &slave, name, termp, winp) == -1) {
+ return -1;
+ }
+
+ pid_t pid = fork();
+ switch (pid) {
+ case -1:
+ close(master);
+ close(slave);
+ return -1;
+ case 0:
+ close(master);
+ login_tty(slave);
+ return 0;
+ default:
+ close(slave);
+ *amaster = master;
+ return pid;
+ }
+}
+
+#endif
+
+/// @returns zero on success, or negative error code
+int pty_proc_spawn(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // termios initialized at first use
+ static struct termios termios_default;
+ if (!termios_default.c_cflag) {
+ init_termios(&termios_default);
+ }
+
+ int status = 0; // zero or negative error code (libuv convention)
+ Proc *proc = (Proc *)ptyproc;
+ assert(proc->err.s.closed);
+ uv_signal_start(&proc->loop->children_watcher, chld_handler, SIGCHLD);
+ ptyproc->winsize = (struct winsize){ ptyproc->height, ptyproc->width, 0, 0 };
+ uv_disable_stdio_inheritance();
+ int master;
+ int pid = forkpty(&master, NULL, &termios_default, &ptyproc->winsize);
+
+ if (pid < 0) {
+ status = -errno;
+ ELOG("forkpty failed: %s", strerror(errno));
+ return status;
+ } else if (pid == 0) {
+ init_child(ptyproc); // never returns
+ }
+
+ // make sure the master file descriptor is non blocking
+ int master_status_flags = fcntl(master, F_GETFL);
+ if (master_status_flags == -1) {
+ status = -errno;
+ ELOG("Failed to get master descriptor status flags: %s", strerror(errno));
+ goto error;
+ }
+ if (fcntl(master, F_SETFL, master_status_flags | O_NONBLOCK) == -1) {
+ status = -errno;
+ ELOG("Failed to make master descriptor non-blocking: %s", strerror(errno));
+ goto error;
+ }
+
+ // Other jobs and providers should not get a copy of this file descriptor.
+ if (os_set_cloexec(master) == -1) {
+ status = -errno;
+ ELOG("Failed to set CLOEXEC on ptmx file descriptor");
+ goto error;
+ }
+
+ if (!proc->in.closed
+ && (status = set_duplicating_descriptor(master, &proc->in.uv.pipe))) {
+ goto error;
+ }
+ if (!proc->out.s.closed
+ && (status = set_duplicating_descriptor(master, &proc->out.s.uv.pipe))) {
+ goto error;
+ }
+
+ ptyproc->tty_fd = master;
+ proc->pid = pid;
+ return 0;
+
+error:
+ close(master);
+ kill(pid, SIGKILL);
+ waitpid(pid, NULL, 0);
+ return status;
+}
+
+const char *pty_proc_tty_name(PtyProc *ptyproc)
+{
+ return ptsname(ptyproc->tty_fd);
+}
+
+void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height)
+ FUNC_ATTR_NONNULL_ALL
+{
+ ptyproc->winsize = (struct winsize){ height, width, 0, 0 };
+ ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize);
+}
+
+void pty_proc_close(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ pty_proc_close_master(ptyproc);
+ Proc *proc = (Proc *)ptyproc;
+ if (proc->internal_close_cb) {
+ proc->internal_close_cb(proc);
+ }
+}
+
+void pty_proc_close_master(PtyProc *ptyproc) FUNC_ATTR_NONNULL_ALL
+{
+ if (ptyproc->tty_fd >= 0) {
+ close(ptyproc->tty_fd);
+ ptyproc->tty_fd = -1;
+ }
+}
+
+void pty_proc_teardown(Loop *loop)
+{
+ uv_signal_stop(&loop->children_watcher);
+}
+
+static void init_child(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+#if defined(HAVE__NSGETENVIRON)
+# define environ (*_NSGetEnviron())
+#else
+ extern char **environ;
+#endif
+ // New session/process-group. #6530
+ setsid();
+
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGALRM, SIG_DFL);
+
+ Proc *proc = (Proc *)ptyproc;
+ if (proc->cwd && os_chdir(proc->cwd) != 0) {
+ ELOG("chdir(%s) failed: %s", proc->cwd, strerror(errno));
+ return;
+ }
+
+ const char *prog = proc_get_exepath(proc);
+
+ assert(proc->env);
+ environ = tv_dict_to_env(proc->env);
+ execvp(prog, proc->argv);
+ ELOG("execvp(%s) failed: %s", prog, strerror(errno));
+
+ _exit(122); // 122 is EXEC_FAILED in the Vim source.
+}
+
+static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL
+{
+ // Taken from pangoterm
+ termios->c_iflag = ICRNL|IXON;
+ termios->c_oflag = OPOST|ONLCR;
+#ifdef TAB0
+ termios->c_oflag |= TAB0;
+#endif
+ termios->c_cflag = CS8|CREAD;
+ termios->c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK;
+
+ // not using cfsetspeed, not available on all platforms
+ cfsetispeed(termios, 38400);
+ cfsetospeed(termios, 38400);
+
+#ifdef IUTF8
+ termios->c_iflag |= IUTF8;
+#endif
+#ifdef NL0
+ termios->c_oflag |= NL0;
+#endif
+#ifdef CR0
+ termios->c_oflag |= CR0;
+#endif
+#ifdef BS0
+ termios->c_oflag |= BS0;
+#endif
+#ifdef VT0
+ termios->c_oflag |= VT0;
+#endif
+#ifdef FF0
+ termios->c_oflag |= FF0;
+#endif
+#ifdef ECHOCTL
+ termios->c_lflag |= ECHOCTL;
+#endif
+#ifdef ECHOKE
+ termios->c_lflag |= ECHOKE;
+#endif
+
+ termios->c_cc[VINTR] = 0x1f & 'C';
+ termios->c_cc[VQUIT] = 0x1f & '\\';
+ termios->c_cc[VERASE] = 0x7f;
+ termios->c_cc[VKILL] = 0x1f & 'U';
+ termios->c_cc[VEOF] = 0x1f & 'D';
+ termios->c_cc[VEOL] = _POSIX_VDISABLE;
+ termios->c_cc[VEOL2] = _POSIX_VDISABLE;
+ termios->c_cc[VSTART] = 0x1f & 'Q';
+ termios->c_cc[VSTOP] = 0x1f & 'S';
+ termios->c_cc[VSUSP] = 0x1f & 'Z';
+ termios->c_cc[VREPRINT] = 0x1f & 'R';
+ termios->c_cc[VWERASE] = 0x1f & 'W';
+ termios->c_cc[VLNEXT] = 0x1f & 'V';
+ termios->c_cc[VMIN] = 1;
+ termios->c_cc[VTIME] = 0;
+}
+
+static int set_duplicating_descriptor(int fd, uv_pipe_t *pipe)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int status = 0; // zero or negative error code (libuv convention)
+ int fd_dup = dup(fd);
+ if (fd_dup < 0) {
+ status = -errno;
+ ELOG("Failed to dup descriptor %d: %s", fd, strerror(errno));
+ return status;
+ }
+
+ if (os_set_cloexec(fd_dup) == -1) {
+ status = -errno;
+ ELOG("Failed to set CLOEXEC on duplicate fd");
+ goto error;
+ }
+
+ status = uv_pipe_open(pipe, fd_dup);
+ if (status) {
+ ELOG("Failed to set pipe to descriptor %d: %s",
+ fd_dup, uv_strerror(status));
+ goto error;
+ }
+ return status;
+
+error:
+ close(fd_dup);
+ return status;
+}
+
+static void chld_handler(uv_signal_t *handle, int signum)
+{
+ int stat = 0;
+ int pid;
+
+ Loop *loop = handle->loop->data;
+
+ for (size_t i = 0; i < kv_size(loop->children); i++) {
+ Proc *proc = kv_A(loop->children, i);
+ do {
+ pid = waitpid(proc->pid, &stat, WNOHANG);
+ } while (pid < 0 && errno == EINTR);
+
+ if (pid <= 0) {
+ continue;
+ }
+
+ if (WIFEXITED(stat)) {
+ proc->status = WEXITSTATUS(stat);
+ } else if (WIFSIGNALED(stat)) {
+ proc->status = 128 + WTERMSIG(stat);
+ }
+ proc->internal_exit_cb(proc);
+ }
+}
+
+PtyProc pty_proc_init(Loop *loop, void *data)
+{
+ PtyProc rv = { 0 };
+ rv.proc = proc_init(loop, kProcTypePty, data);
+ rv.width = 80;
+ rv.height = 24;
+ rv.tty_fd = -1;
+ return rv;
+}