1 // This is an open source non-commercial project. Dear PVS-Studio, please check
2 // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3 
4 // Some of the code came from pangoterm and libuv
5 #include <stdbool.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/ioctl.h>
9 #include <sys/types.h>
10 #include <sys/wait.h>
11 #include <termios.h>
12 
13 // forkpty is not in POSIX, so headers are platform-specific
14 #if defined(__FreeBSD__) || defined(__DragonFly__)
15 # include <libutil.h>
16 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
17 # include <util.h>
18 #else
19 # include <pty.h>
20 #endif
21 
22 #ifdef __APPLE__
23 # include <crt_externs.h>
24 #endif
25 
26 #include <uv.h>
27 
28 #include "nvim/event/loop.h"
29 #include "nvim/event/process.h"
30 #include "nvim/event/rstream.h"
31 #include "nvim/event/wstream.h"
32 #include "nvim/lib/klist.h"
33 #include "nvim/log.h"
34 #include "nvim/os/os.h"
35 #include "nvim/os/pty_process_unix.h"
36 
37 #ifdef INCLUDE_GENERATED_DECLARATIONS
38 # include "os/pty_process_unix.c.generated.h"
39 #endif
40 
41 /// termios saved at startup (for TUI) or initialized by pty_process_spawn().
42 static struct termios termios_default;
43 
44 /// Saves the termios properties associated with `tty_fd`.
45 ///
46 /// @param tty_fd   TTY file descriptor, or -1 if not in a terminal.
pty_process_save_termios(int tty_fd)47 void pty_process_save_termios(int tty_fd)
48 {
49   DLOG("tty_fd=%d", tty_fd);
50   if (tty_fd == -1 || tcgetattr(tty_fd, &termios_default) != 0) {
51     return;
52   }
53 }
54 
55 /// @returns zero on success, or negative error code
pty_process_spawn(PtyProcess * ptyproc)56 int pty_process_spawn(PtyProcess *ptyproc)
57   FUNC_ATTR_NONNULL_ALL
58 {
59   if (!termios_default.c_cflag) {
60     // TODO(jkeyes): We could pass NULL to forkpty() instead ...
61     init_termios(&termios_default);
62   }
63 
64   int status = 0;  // zero or negative error code (libuv convention)
65   Process *proc = (Process *)ptyproc;
66   assert(proc->err.closed);
67   uv_signal_start(&proc->loop->children_watcher, chld_handler, SIGCHLD);
68   ptyproc->winsize = (struct winsize){ ptyproc->height, ptyproc->width, 0, 0 };
69   uv_disable_stdio_inheritance();
70   int master;
71   int pid = forkpty(&master, NULL, &termios_default, &ptyproc->winsize);
72 
73   if (pid < 0) {
74     status = -errno;
75     ELOG("forkpty failed: %s", strerror(errno));
76     return status;
77   } else if (pid == 0) {
78     init_child(ptyproc);  // never returns
79   }
80 
81   // make sure the master file descriptor is non blocking
82   int master_status_flags = fcntl(master, F_GETFL);
83   if (master_status_flags == -1) {
84     status = -errno;
85     ELOG("Failed to get master descriptor status flags: %s", strerror(errno));
86     goto error;
87   }
88   if (fcntl(master, F_SETFL, master_status_flags | O_NONBLOCK) == -1) {
89     status = -errno;
90     ELOG("Failed to make master descriptor non-blocking: %s", strerror(errno));
91     goto error;
92   }
93 
94   // Other jobs and providers should not get a copy of this file descriptor.
95   if (os_set_cloexec(master) == -1) {
96     status = -errno;
97     ELOG("Failed to set CLOEXEC on ptmx file descriptor");
98     goto error;
99   }
100 
101   if (!proc->in.closed
102       && (status = set_duplicating_descriptor(master, &proc->in.uv.pipe))) {
103     goto error;
104   }
105   if (!proc->out.closed
106       && (status = set_duplicating_descriptor(master, &proc->out.uv.pipe))) {
107     goto error;
108   }
109 
110   ptyproc->tty_fd = master;
111   proc->pid = pid;
112   return 0;
113 
114 error:
115   close(master);
116   kill(pid, SIGKILL);
117   waitpid(pid, NULL, 0);
118   return status;
119 }
120 
pty_process_tty_name(PtyProcess * ptyproc)121 const char *pty_process_tty_name(PtyProcess *ptyproc)
122 {
123   return ptsname(ptyproc->tty_fd);
124 }
125 
pty_process_resize(PtyProcess * ptyproc,uint16_t width,uint16_t height)126 void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height)
127   FUNC_ATTR_NONNULL_ALL
128 {
129   ptyproc->winsize = (struct winsize){ height, width, 0, 0 };
130   ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize);
131 }
132 
pty_process_close(PtyProcess * ptyproc)133 void pty_process_close(PtyProcess *ptyproc)
134   FUNC_ATTR_NONNULL_ALL
135 {
136   pty_process_close_master(ptyproc);
137   Process *proc = (Process *)ptyproc;
138   if (proc->internal_close_cb) {
139     proc->internal_close_cb(proc);
140   }
141 }
142 
pty_process_close_master(PtyProcess * ptyproc)143 void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
144 {
145   if (ptyproc->tty_fd >= 0) {
146     close(ptyproc->tty_fd);
147     ptyproc->tty_fd = -1;
148   }
149 }
150 
pty_process_teardown(Loop * loop)151 void pty_process_teardown(Loop *loop)
152 {
153   uv_signal_stop(&loop->children_watcher);
154 }
155 
init_child(PtyProcess * ptyproc)156 static void init_child(PtyProcess *ptyproc)
157   FUNC_ATTR_NONNULL_ALL
158 {
159 #if defined(HAVE__NSGETENVIRON)
160 # define environ (*_NSGetEnviron())
161 #else
162   extern char **environ;
163 #endif
164   // New session/process-group. #6530
165   setsid();
166 
167   signal(SIGCHLD, SIG_DFL);
168   signal(SIGHUP, SIG_DFL);
169   signal(SIGINT, SIG_DFL);
170   signal(SIGQUIT, SIG_DFL);
171   signal(SIGTERM, SIG_DFL);
172   signal(SIGALRM, SIG_DFL);
173 
174   Process *proc = (Process *)ptyproc;
175   if (proc->cwd && os_chdir(proc->cwd) != 0) {
176     ELOG("chdir(%s) failed: %s", proc->cwd, strerror(errno));
177     return;
178   }
179 
180   char *prog = ptyproc->process.argv[0];
181 
182   assert(proc->env);
183   environ = tv_dict_to_env(proc->env);
184   execvp(prog, proc->argv);
185   ELOG("execvp(%s) failed: %s", prog, strerror(errno));
186 
187   _exit(122);  // 122 is EXEC_FAILED in the Vim source.
188 }
189 
init_termios(struct termios * termios)190 static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL
191 {
192   // Taken from pangoterm
193   termios->c_iflag = ICRNL|IXON;
194   termios->c_oflag = OPOST|ONLCR;
195 #ifdef TAB0
196   termios->c_oflag |= TAB0;
197 #endif
198   termios->c_cflag = CS8|CREAD;
199   termios->c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK;
200 
201   cfsetspeed(termios, 38400);
202 
203 #ifdef IUTF8
204   termios->c_iflag |= IUTF8;
205 #endif
206 #ifdef NL0
207   termios->c_oflag |= NL0;
208 #endif
209 #ifdef CR0
210   termios->c_oflag |= CR0;
211 #endif
212 #ifdef BS0
213   termios->c_oflag |= BS0;
214 #endif
215 #ifdef VT0
216   termios->c_oflag |= VT0;
217 #endif
218 #ifdef FF0
219   termios->c_oflag |= FF0;
220 #endif
221 #ifdef ECHOCTL
222   termios->c_lflag |= ECHOCTL;
223 #endif
224 #ifdef ECHOKE
225   termios->c_lflag |= ECHOKE;
226 #endif
227 
228   termios->c_cc[VINTR]    = 0x1f & 'C';
229   termios->c_cc[VQUIT]    = 0x1f & '\\';
230   termios->c_cc[VERASE]   = 0x7f;
231   termios->c_cc[VKILL]    = 0x1f & 'U';
232   termios->c_cc[VEOF]     = 0x1f & 'D';
233   termios->c_cc[VEOL]     = _POSIX_VDISABLE;
234   termios->c_cc[VEOL2]    = _POSIX_VDISABLE;
235   termios->c_cc[VSTART]   = 0x1f & 'Q';
236   termios->c_cc[VSTOP]    = 0x1f & 'S';
237   termios->c_cc[VSUSP]    = 0x1f & 'Z';
238   termios->c_cc[VREPRINT] = 0x1f & 'R';
239   termios->c_cc[VWERASE]  = 0x1f & 'W';
240   termios->c_cc[VLNEXT]   = 0x1f & 'V';
241   termios->c_cc[VMIN]     = 1;
242   termios->c_cc[VTIME]    = 0;
243 }
244 
set_duplicating_descriptor(int fd,uv_pipe_t * pipe)245 static int set_duplicating_descriptor(int fd, uv_pipe_t *pipe)
246   FUNC_ATTR_NONNULL_ALL
247 {
248   int status = 0;  // zero or negative error code (libuv convention)
249   int fd_dup = dup(fd);
250   if (fd_dup < 0) {
251     status = -errno;
252     ELOG("Failed to dup descriptor %d: %s", fd, strerror(errno));
253     return status;
254   }
255 
256   if (os_set_cloexec(fd_dup) == -1) {
257     status = -errno;
258     ELOG("Failed to set CLOEXEC on duplicate fd");
259     goto error;
260   }
261 
262   status = uv_pipe_open(pipe, fd_dup);
263   if (status) {
264     ELOG("Failed to set pipe to descriptor %d: %s",
265          fd_dup, uv_strerror(status));
266     goto error;
267   }
268   return status;
269 
270 error:
271   close(fd_dup);
272   return status;
273 }
274 
chld_handler(uv_signal_t * handle,int signum)275 static void chld_handler(uv_signal_t *handle, int signum)
276 {
277   int stat = 0;
278   int pid;
279 
280   Loop *loop = handle->loop->data;
281 
282   kl_iter(WatcherPtr, loop->children, current) {
283     Process *proc = (*current)->data;
284     do {
285       pid = waitpid(proc->pid, &stat, WNOHANG);
286     } while (pid < 0 && errno == EINTR);
287 
288     if (pid <= 0) {
289       continue;
290     }
291 
292     if (WIFEXITED(stat)) {
293       proc->status = WEXITSTATUS(stat);
294     } else if (WIFSIGNALED(stat)) {
295       proc->status = 128 + WTERMSIG(stat);
296     }
297     proc->internal_exit_cb(proc);
298   }
299 }
300