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