neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

pty_proc_unix.c (11220B)


      1 // Some of the code came from pangoterm and libuv
      2 
      3 #include <assert.h>
      4 #include <errno.h>
      5 #include <fcntl.h>
      6 #include <signal.h>
      7 #include <stdlib.h>
      8 #include <string.h>
      9 #include <sys/ioctl.h>
     10 #include <sys/wait.h>
     11 #include <uv.h>
     12 
     13 // forkpty is not in POSIX, so headers are platform-specific
     14 #if defined(__FreeBSD__) || defined(__DragonFly__)
     15 # include <libutil.h>
     16 // TODO(bfredl): this is available on darwin, but there is an issue with cross-compile headers
     17 #elif defined(__APPLE__) && !defined(HAVE_FORKPTY)
     18 int forkpty(int *, char *, const struct termios *, const struct winsize *);
     19 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
     20 # include <util.h>
     21 #elif defined(__sun)
     22 # include <fcntl.h>
     23 # include <signal.h>
     24 # include <sys/stream.h>
     25 # include <sys/syscall.h>
     26 # include <unistd.h>
     27 #else
     28 # include <pty.h>
     29 #endif
     30 
     31 #ifdef __APPLE__
     32 # include <crt_externs.h>
     33 #endif
     34 #ifdef __linux__
     35 # include <poll.h>
     36 #endif
     37 
     38 #include "auto/config.h"
     39 #include "klib/kvec.h"
     40 #include "nvim/eval/typval.h"
     41 #include "nvim/event/defs.h"
     42 #include "nvim/event/loop.h"
     43 #include "nvim/event/proc.h"
     44 #include "nvim/log.h"
     45 #include "nvim/os/fs.h"
     46 #include "nvim/os/os_defs.h"
     47 #include "nvim/os/pty_proc.h"
     48 #include "nvim/os/pty_proc_unix.h"
     49 #include "nvim/types_defs.h"
     50 
     51 #include "os/pty_proc_unix.c.generated.h"
     52 
     53 #if !defined(HAVE_FORKPTY) && !defined(__APPLE__)
     54 
     55 // this header defines STR, just as nvim.h, but it is defined as ('S'<<8),
     56 // to avoid #undef STR, #undef STR, #define STR ('S'<<8) just delay the
     57 // inclusion of the header even though it gets include out of order.
     58 
     59 # if !defined(__HAIKU__)
     60 #  include <sys/stropts.h>
     61 # else
     62 #  define I_PUSH 0  // XXX: find the actual value
     63 # endif
     64 
     65 static int vim_openpty(int *amaster, int *aslave, char *name, struct termios *termp,
     66                       struct winsize *winp)
     67 {
     68  int slave = -1;
     69  int master = open("/dev/ptmx", O_RDWR);
     70  if (master == -1) {
     71    goto error;
     72  }
     73 
     74  // grantpt will invoke a setuid program to change permissions
     75  // and might fail if SIGCHLD handler is set, temporarily reset
     76  // while running
     77  void (*sig_saved)(int) = signal(SIGCHLD, SIG_DFL);
     78  int res = grantpt(master);
     79  signal(SIGCHLD, sig_saved);
     80 
     81  if (res == -1 || unlockpt(master) == -1) {
     82    goto error;
     83  }
     84 
     85  char *slave_name = ptsname(master);
     86  if (slave_name == NULL) {
     87    goto error;
     88  }
     89 
     90  slave = open(slave_name, O_RDWR|O_NOCTTY);
     91  if (slave == -1) {
     92    goto error;
     93  }
     94 
     95  // ptem emulates a terminal when used on a pseudo terminal driver,
     96  // must be pushed before ldterm
     97  ioctl(slave, I_PUSH, "ptem");
     98  // ldterm provides most of the termio terminal interface
     99  ioctl(slave, I_PUSH, "ldterm");
    100  // ttcompat compatibility with older terminal ioctls
    101  ioctl(slave, I_PUSH, "ttcompat");
    102 
    103  if (termp) {
    104    tcsetattr(slave, TCSAFLUSH, termp);
    105  }
    106  if (winp) {
    107    ioctl(slave, TIOCSWINSZ, winp);
    108  }
    109 
    110  *amaster = master;
    111  *aslave = slave;
    112  // ignoring name, not passed and size is unknown in the API
    113 
    114  return 0;
    115 
    116 error:
    117  if (slave != -1) {
    118    close(slave);
    119  }
    120  if (master != -1) {
    121    close(master);
    122  }
    123  return -1;
    124 }
    125 
    126 static int vim_login_tty(int fd)
    127 {
    128  setsid();
    129  if (ioctl(fd, TIOCSCTTY, NULL) == -1) {
    130    return -1;
    131  }
    132 
    133  dup2(fd, STDIN_FILENO);
    134  dup2(fd, STDOUT_FILENO);
    135  dup2(fd, STDERR_FILENO);
    136  if (fd > STDERR_FILENO) {
    137    close(fd);
    138  }
    139 
    140  return 0;
    141 }
    142 
    143 pid_t vim_forkpty(int *amaster, char *name, struct termios *termp, struct winsize *winp)
    144 {
    145  int master, slave;
    146  if (vim_openpty(&master, &slave, name, termp, winp) == -1) {
    147    return -1;
    148  }
    149 
    150  pid_t pid = fork();
    151  switch (pid) {
    152  case -1:
    153    close(master);
    154    close(slave);
    155    return -1;
    156  case 0:
    157    close(master);
    158    vim_login_tty(slave);
    159    return 0;
    160  default:
    161    close(slave);
    162    *amaster = master;
    163    return pid;
    164  }
    165 }
    166 # define forkpty vim_forkpty
    167 #endif
    168 
    169 /// @returns zero on success, or negative error code
    170 int pty_proc_spawn(PtyProc *ptyproc)
    171  FUNC_ATTR_NONNULL_ALL
    172 {
    173  // termios initialized at first use
    174  static struct termios termios_default;
    175  if (!termios_default.c_cflag) {
    176    init_termios(&termios_default);
    177  }
    178 
    179  int status = 0;  // zero or negative error code (libuv convention)
    180  Proc *proc = (Proc *)ptyproc;
    181  assert(proc->err.s.closed);
    182  uv_signal_start(&proc->loop->children_watcher, chld_handler, SIGCHLD);
    183  ptyproc->winsize = (struct winsize){ ptyproc->height, ptyproc->width, 0, 0 };
    184  uv_disable_stdio_inheritance();
    185  int master;
    186  int pid = forkpty(&master, NULL, &termios_default, &ptyproc->winsize);
    187 
    188  if (pid < 0) {
    189    status = -errno;
    190    ELOG("forkpty failed: %s", strerror(errno));
    191    return status;
    192  } else if (pid == 0) {
    193    init_child(ptyproc);  // never returns
    194  }
    195 
    196  // make sure the master file descriptor is non blocking
    197  int master_status_flags = fcntl(master, F_GETFL);
    198  if (master_status_flags == -1) {
    199    status = -errno;
    200    ELOG("Failed to get master descriptor status flags: %s", strerror(errno));
    201    goto error;
    202  }
    203  if (fcntl(master, F_SETFL, master_status_flags | O_NONBLOCK) == -1) {
    204    status = -errno;
    205    ELOG("Failed to make master descriptor non-blocking: %s", strerror(errno));
    206    goto error;
    207  }
    208 
    209  // Other jobs and providers should not get a copy of this file descriptor.
    210  if (os_set_cloexec(master) == -1) {
    211    status = -errno;
    212    ELOG("Failed to set CLOEXEC on ptmx file descriptor");
    213    goto error;
    214  }
    215 
    216  if (!proc->in.closed
    217      && (status = set_duplicating_descriptor(master, &proc->in.uv.pipe))) {
    218    goto error;
    219  }
    220  // The stream_init() call in proc_spawn() will initialize proc->out.s.uv.poll.
    221 
    222  ptyproc->tty_fd = master;
    223  proc->pid = pid;
    224  return 0;
    225 
    226 error:
    227  close(master);
    228  kill(pid, SIGKILL);
    229  waitpid(pid, NULL, 0);
    230  return status;
    231 }
    232 
    233 const char *pty_proc_tty_name(PtyProc *ptyproc)
    234 {
    235  return ptsname(ptyproc->tty_fd);
    236 }
    237 
    238 void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height)
    239  FUNC_ATTR_NONNULL_ALL
    240 {
    241  ptyproc->winsize = (struct winsize){ height, width, 0, 0 };
    242  ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize);
    243 }
    244 
    245 void pty_proc_resume(PtyProc *ptyproc)
    246  FUNC_ATTR_NONNULL_ALL
    247 {
    248  // Send SIGCONT to the entire process group, as some shells (e.g. fish) don't
    249  // propagate SIGCONT to suspended child processes.
    250  killpg(((Proc *)ptyproc)->pid, SIGCONT);
    251 }
    252 
    253 /// On Linux, libuv's polling (which uses epoll) doesn't flush PTY master's pending
    254 /// work on kernel workqueue, so use an explcit poll() before that. #37982
    255 /// Note that poll() only flushes pending work if no data is immediately available,
    256 /// so this function is needed before every libuv poll in flush_stream().
    257 void pty_proc_flush_master(PtyProc *ptyproc)
    258  FUNC_ATTR_NONNULL_ALL
    259 {
    260 #ifdef __linux__
    261  struct pollfd pollfd = { .fd = ptyproc->tty_fd, .events = POLLIN };
    262  int n = 0;
    263  do {
    264    n = poll(&pollfd, 1, 0);
    265  } while (n < 0 && errno == EINTR);
    266 #endif
    267 }
    268 
    269 void pty_proc_close(PtyProc *ptyproc)
    270  FUNC_ATTR_NONNULL_ALL
    271 {
    272  pty_proc_close_master(ptyproc);
    273  Proc *proc = (Proc *)ptyproc;
    274  if (proc->internal_close_cb) {
    275    proc->internal_close_cb(proc);
    276  }
    277 }
    278 
    279 void pty_proc_close_master(PtyProc *ptyproc)
    280  FUNC_ATTR_NONNULL_ALL
    281 {
    282  if (ptyproc->tty_fd >= 0) {
    283    close(ptyproc->tty_fd);
    284    ptyproc->tty_fd = -1;
    285  }
    286 }
    287 
    288 void pty_proc_teardown(Loop *loop)
    289 {
    290  uv_signal_stop(&loop->children_watcher);
    291 }
    292 
    293 static void init_child(PtyProc *ptyproc)
    294  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NORETURN
    295 {
    296 #if defined(HAVE__NSGETENVIRON)
    297 # define environ (*_NSGetEnviron())
    298 #else
    299  extern char **environ;
    300 #endif
    301  // New session/process-group. #6530
    302  setsid();
    303 
    304  signal(SIGCHLD, SIG_DFL);
    305  signal(SIGHUP, SIG_DFL);
    306  signal(SIGINT, SIG_DFL);
    307  signal(SIGQUIT, SIG_DFL);
    308  signal(SIGTERM, SIG_DFL);
    309  signal(SIGALRM, SIG_DFL);
    310 
    311  Proc *proc = (Proc *)ptyproc;
    312  int err = 0;
    313  // Don't use os_chdir() as that may buffer UI events unnecessarily.
    314  if (proc->cwd && (err = uv_chdir(proc->cwd)) != 0) {
    315    ELOG("chdir(%s) failed: %s", proc->cwd, uv_strerror(err));
    316    _exit(122);
    317  }
    318 
    319  const char *prog = proc_get_exepath(proc);
    320 
    321  assert(proc->env);
    322  environ = tv_dict_to_env(proc->env);
    323  execvp(prog, proc->argv);
    324  ELOG("execvp(%s) failed: %s", prog, strerror(errno));
    325 
    326  _exit(122);  // 122 is EXEC_FAILED in the Vim source.
    327 }
    328 
    329 static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL
    330 {
    331  // Taken from pangoterm
    332  termios->c_iflag = ICRNL|IXON;
    333  termios->c_oflag = OPOST|ONLCR;
    334 #ifdef TAB0
    335  termios->c_oflag |= TAB0;
    336 #endif
    337  termios->c_cflag = CS8|CREAD;
    338  termios->c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK;
    339 
    340  // not using cfsetspeed, not available on all platforms
    341  cfsetispeed(termios, 38400);
    342  cfsetospeed(termios, 38400);
    343 
    344 #ifdef IUTF8
    345  termios->c_iflag |= IUTF8;
    346 #endif
    347 #ifdef NL0
    348  termios->c_oflag |= NL0;
    349 #endif
    350 #ifdef CR0
    351  termios->c_oflag |= CR0;
    352 #endif
    353 #ifdef BS0
    354  termios->c_oflag |= BS0;
    355 #endif
    356 #ifdef VT0
    357  termios->c_oflag |= VT0;
    358 #endif
    359 #ifdef FF0
    360  termios->c_oflag |= FF0;
    361 #endif
    362 #ifdef ECHOCTL
    363  termios->c_lflag |= ECHOCTL;
    364 #endif
    365 #ifdef ECHOKE
    366  termios->c_lflag |= ECHOKE;
    367 #endif
    368 
    369  termios->c_cc[VINTR] = 0x1f & 'C';
    370  termios->c_cc[VQUIT] = 0x1f & '\\';
    371  termios->c_cc[VERASE] = 0x7f;
    372  termios->c_cc[VKILL] = 0x1f & 'U';
    373  termios->c_cc[VEOF] = 0x1f & 'D';
    374  termios->c_cc[VEOL] = _POSIX_VDISABLE;
    375  termios->c_cc[VEOL2] = _POSIX_VDISABLE;
    376  termios->c_cc[VSTART] = 0x1f & 'Q';
    377  termios->c_cc[VSTOP] = 0x1f & 'S';
    378  termios->c_cc[VSUSP] = 0x1f & 'Z';
    379 #if !defined(__HAIKU__)
    380  termios->c_cc[VREPRINT] = 0x1f & 'R';
    381  termios->c_cc[VWERASE] = 0x1f & 'W';
    382  termios->c_cc[VLNEXT] = 0x1f & 'V';
    383 #endif
    384  termios->c_cc[VMIN] = 1;
    385  termios->c_cc[VTIME] = 0;
    386 }
    387 
    388 static int set_duplicating_descriptor(int fd, uv_pipe_t *pipe)
    389  FUNC_ATTR_NONNULL_ALL
    390 {
    391  int status = 0;  // zero or negative error code (libuv convention)
    392  int fd_dup = dup(fd);
    393  if (fd_dup < 0) {
    394    status = -errno;
    395    ELOG("Failed to dup descriptor %d: %s", fd, strerror(errno));
    396    return status;
    397  }
    398 
    399  if (os_set_cloexec(fd_dup) == -1) {
    400    status = -errno;
    401    ELOG("Failed to set CLOEXEC on duplicate fd");
    402    goto error;
    403  }
    404 
    405  status = uv_pipe_open(pipe, fd_dup);
    406  if (status) {
    407    ELOG("Failed to set pipe to descriptor %d: %s",
    408         fd_dup, uv_strerror(status));
    409    goto error;
    410  }
    411  return status;
    412 
    413 error:
    414  close(fd_dup);
    415  return status;
    416 }
    417 
    418 static void chld_handler(uv_signal_t *handle, int signum)
    419 {
    420  int stat = 0;
    421  int pid;
    422 
    423  Loop *loop = handle->loop->data;
    424 
    425  for (size_t i = 0; i < kv_size(loop->children); i++) {
    426    Proc *proc = kv_A(loop->children, i);
    427    do {
    428      pid = waitpid(proc->pid, &stat, WNOHANG|WUNTRACED|WCONTINUED);
    429    } while (pid < 0 && errno == EINTR);
    430 
    431    if (pid <= 0) {
    432      continue;
    433    }
    434 
    435    if (WIFSTOPPED(stat)) {
    436      proc->state_cb(proc, true, proc->data);
    437      continue;
    438    }
    439    if (WIFCONTINUED(stat)) {
    440      proc->state_cb(proc, false, proc->data);
    441      continue;
    442    }
    443 
    444    if (WIFEXITED(stat)) {
    445      proc->status = WEXITSTATUS(stat);
    446    } else if (WIFSIGNALED(stat)) {
    447      proc->status = 128 + WTERMSIG(stat);
    448    }
    449    proc->internal_exit_cb(proc);
    450  }
    451 }
    452 
    453 PtyProc pty_proc_init(Loop *loop, void *data)
    454 {
    455  PtyProc rv = { 0 };
    456  rv.proc = proc_init(loop, kProcTypePty, data);
    457  rv.width = 80;
    458  rv.height = 24;
    459  rv.tty_fd = -1;
    460  return rv;
    461 }