1 #include <stdio.h>
2 #include <fcntl.h>
3 #include <unistd.h>
4 #include <pthread.h>
5 #include "internal.h"
6 #ifdef USING_PIDFD
7 #error "USING_PIDFD was already defined; it should not be."
8 #endif
9 #ifdef __MINGW64__
10 #include <winsock2.h>
11 #else
12 #include <spawn.h>
13 #endif
14 #if (defined(__linux__))
15 #include <linux/wait.h>
16 #include <asm/unistd.h>
17 #include <linux/sched.h>
18 #define NCPOLLEVENTS (POLLIN | POLLRDHUP)
19 #if (defined(__NR_clone3) && defined(P_PIDFD) && defined(CLONE_CLEAR_SIGHAND))
20 #define USING_PIDFD
21 #endif
22 #else
23 #define NCPOLLEVENTS (POLLIN)
24 #endif
25
26 // release the memory and fd, but don't join the thread (since we might be
27 // getting called within the thread's context, on a callback).
28 static int
ncfdplane_destroy_inner(ncfdplane * n)29 ncfdplane_destroy_inner(ncfdplane* n){
30 int ret = close(n->fd);
31 free(n);
32 return ret;
33 }
34
35 // if pidfd is < 0, it won't be used in the poll()
36 static void
fdthread(ncfdplane * ncfp,int pidfd)37 fdthread(ncfdplane* ncfp, int pidfd){
38 struct pollfd pfds[2];
39 memset(pfds, 0, sizeof(pfds));
40 char* buf = malloc(BUFSIZ + 1);
41 pfds[0].fd = ncfp->fd;
42 pfds[0].events = NCPOLLEVENTS;
43 const int fdcount = pidfd < 0 ? 1 : 2;
44 if(fdcount > 1){
45 pfds[1].fd = pidfd;
46 pfds[1].events = NCPOLLEVENTS;
47 }
48 ssize_t r = 0;
49 #ifndef __MINGW64__
50 while(poll(pfds, fdcount, -1) >= 0 || errno == EINTR){
51 #else
52 while(WSAPoll(pfds, fdcount, -1) >= 0){
53 #endif
54 if(pfds[0].revents){
55 while((r = read(ncfp->fd, buf, BUFSIZ)) >= 0){
56 if(r == 0){
57 break;
58 }
59 buf[r] = '\0';
60 if( (r = ncfp->cb(ncfp, buf, r, ncfp->curry)) ){
61 break;
62 }
63 if(ncfp->destroyed){
64 break;
65 }
66 }
67 // if we're not doing follow, break out on a zero-byte read
68 if(r == 0 && !ncfp->follow){
69 break;
70 }
71 }
72 if(fdcount > 1 && pfds[1].revents){
73 r = 0;
74 break;
75 }
76 }
77 if(r <= 0 && !ncfp->destroyed){
78 ncfp->donecb(ncfp, r == 0 ? 0 : errno, ncfp->curry);
79 }
80 if(ncfp->destroyed){
81 ncfdplane_destroy_inner(ncfp);
82 }
83 free(buf);
84 }
85
86 static void *
87 ncfdplane_thread(void* vncfp){
88 fdthread(vncfp, -1);
89 return NULL;
90 }
91
92 static ncfdplane*
93 ncfdplane_create_internal(ncplane* n, const ncfdplane_options* opts, int fd,
94 ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn,
95 bool thread){
96 if(opts->flags > 0){
97 logwarn("Provided unsupported flags %016" PRIx64 "\n", opts->flags);
98 }
99 ncfdplane* ret = malloc(sizeof(*ret));
100 if(ret == NULL){
101 return ret;
102 }
103 ret->cb = cbfxn;
104 ret->donecb = donecbfxn;
105 ret->follow = opts->follow;
106 ret->ncp = n;
107 ret->destroyed = false;
108 ncplane_set_scrolling(ret->ncp, true);
109 ret->fd = fd;
110 ret->curry = opts->curry;
111 if(thread){
112 if(pthread_create(&ret->tid, NULL, ncfdplane_thread, ret)){
113 free(ret);
114 return NULL;
115 }
116 }
117 return ret;
118 }
119
120 ncfdplane* ncfdplane_create(ncplane* n, const ncfdplane_options* opts, int fd,
121 ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn){
122 ncfdplane_options zeroed = {};
123 if(!opts){
124 opts = &zeroed;
125 }
126 if(fd < 0 || !cbfxn || !donecbfxn){
127 return NULL;
128 }
129 return ncfdplane_create_internal(n, opts, fd, cbfxn, donecbfxn, true);
130 }
131
132 ncplane* ncfdplane_plane(ncfdplane* n){
133 return n->ncp;
134 }
135
136 int ncfdplane_destroy(ncfdplane* n){
137 int ret = 0;
138 if(n){
139 if(pthread_equal(pthread_self(), n->tid)){
140 n->destroyed = true; // ncfdplane_destroy_inner() is called on thread exit
141 }else{
142 void* vret = NULL;
143 ret |= cancel_and_join("fdplane", n->tid, &vret);
144 ret |= ncfdplane_destroy_inner(n);
145 }
146 }
147 return ret;
148 }
149
150 #ifndef __MINGW64__
151 // get 2 pipes, and ensure they're both set to close-on-exec
152 static int
153 lay_pipes(int pipes[static 2]){
154 #ifdef __linux__
155 if(pipe2(pipes, O_CLOEXEC)){ // can't use O_NBLOCK here (affects client)
156 #else
157 if(pipe(pipes)){
158 #endif
159 return -1;
160 }
161 #ifndef __linux__
162 if(set_fd_cloexec(pipes[0], 1, NULL) || set_fd_cloexec(pipes[1], 1, NULL)){
163 close(pipes[0]);
164 close(pipes[1]);
165 return -1;
166 }
167 #endif
168 return 0;
169 }
170
171 // ncsubproc creates a pipe, retaining the read end. it clone()s a subprocess,
172 // getting a pidfd. the subprocess dup2()s the write end of the pipe onto file
173 // descriptors 1 and 2, exec()s, and begins running. the parent creates an
174 // ncfdplane around the read end, involving creation of a new thread. the
175 // parent then returns.
176 static pid_t
177 launch_pipe_process(int* pipefd, int* pidfd, unsigned usepath,
178 const char* bin, char* const arg[], char* const env[]){
179 *pidfd = -1;
180 int pipes[2];
181 if(lay_pipes(pipes)){
182 return -1;
183 }
184 pid_t p = -1;
185 #ifdef USING_PIDFD
186 // on linux, we try to use the brand-new pidfd capability via clone3(). if
187 // that fails, fall through to posix_spawn(), our only option on freebsd.
188 // FIXME clone3 is not yet supported on debian sparc64/alpha as of 2020-07
189 struct clone_args clargs;
190 memset(&clargs, 0, sizeof(clargs));
191 clargs.pidfd = (uintptr_t)pidfd;
192 clargs.flags = CLONE_CLEAR_SIGHAND | CLONE_FS | CLONE_PIDFD;
193 clargs.exit_signal = SIGCHLD;
194 p = syscall(__NR_clone3, &clargs, sizeof(clargs));
195 if(p == 0){ // child
196 if(dup2(pipes[1], STDOUT_FILENO) < 0 || dup2(pipes[1], STDERR_FILENO) < 0){
197 logerror("Couldn't dup() %d (%s)\n", pipes[1], strerror(errno));
198 exit(EXIT_FAILURE);
199 }
200 if(env){
201 execvpe(bin, arg, env);
202 }else if(usepath){
203 execvp(bin, arg);
204 }else{
205 execv(bin, arg);
206 }
207 exit(EXIT_FAILURE);
208 }else if(p < 0){
209 logwarn("clone3() failed (%s), using posix_spawn()\n", strerror(errno));
210 }
211 #endif
212 if(p < 0){
213 posix_spawn_file_actions_t factions;
214 if(posix_spawn_file_actions_init(&factions)){
215 logerror("couldn't initialize spawn file actions\n");
216 return -1;
217 }
218 posix_spawn_file_actions_adddup2(&factions, pipes[1], STDOUT_FILENO);
219 posix_spawn_file_actions_adddup2(&factions, pipes[1], STDERR_FILENO);
220 int r;
221 if(usepath){
222 r = posix_spawnp(&p, bin, &factions, NULL, arg, env);
223 }else{
224 r = posix_spawn(&p, bin, &factions, NULL, arg, env);
225 }
226 if(r){
227 logerror("posix_spawn %s failed (%s)\n", bin, strerror(errno));
228 }
229 posix_spawn_file_actions_destroy(&factions);
230 }
231 if(p > 0){ // parent
232 *pipefd = pipes[0];
233 set_fd_nonblocking(*pipefd, 1, NULL);
234 }
235 return p;
236 }
237 #endif
238
239 #ifndef __MINGW64__
240 // nuke the just-spawned process, and reap it. called before the subprocess
241 // reader thread is launched (which otherwise reaps the subprocess).
242 static int
243 kill_and_wait_subproc(pid_t pid, int pidfd, int* status){
244 int ret = -1;
245 // on linux, we try pidfd_send_signal, if the pidfd has been defined.
246 // otherwise, we fall back to regular old kill();
247 if(pidfd >= 0){
248 #ifdef USING_PIDFD
249 ret = syscall(__NR_pidfd_send_signal, pidfd, SIGKILL, NULL, 0);
250 siginfo_t info;
251 memset(&info, 0, sizeof(info));
252 waitid(P_PIDFD, pidfd, &info, 0);
253 #endif
254 }
255 if(ret < 0){
256 kill(pid, SIGKILL);
257 }
258 // process ought be available immediately following waitid(), so supply
259 // WNOHANG to avoid possible lockups due to weirdness
260 if(pid != waitpid(pid, status, WNOHANG)){
261 return -1;
262 }
263 return 0;
264 }
265
266 // need a poll on both main fd and pidfd
267 static void *
268 ncsubproc_thread(void* vncsp){
269 int* status = malloc(sizeof(*status));
270 if(status){
271 ncsubproc* ncsp = vncsp;
272 fdthread(ncsp->nfp, ncsp->pidfd);
273 if(kill_and_wait_subproc(ncsp->pid, ncsp->pidfd, status)){
274 *status = -1;
275 }
276 if(ncsp->nfp->destroyed){
277 ncfdplane_destroy_inner(ncsp->nfp);
278 free(ncsp);
279 }
280 }
281 return status;
282 }
283
284 // this is only used if we don't have a pidfd available for poll()ing. in that
285 // case, we want to perform a blocking waitpid() on the pid, calling the
286 // completion callback when it exits (since the process exit won't necessarily
287 // wake up our poll()ing thread).
288 static void *
289 ncsubproc_waiter(void* vncsp){
290 ncsubproc* ncsp = vncsp;
291 int* status = malloc(sizeof(*status));
292 pid_t pid;
293 while((pid = waitpid(ncsp->pid, status, 0)) < 0 && errno == EINTR){
294 ;
295 }
296 if(pid != ncsp->pid){
297 free(status);
298 return NULL;
299 }
300 pthread_mutex_lock(&ncsp->lock);
301 ncsp->waited = true;
302 pthread_mutex_unlock(&ncsp->lock);
303 if(!ncsp->nfp->destroyed){
304 ncsp->nfp->donecb(ncsp->nfp, *status, ncsp->nfp->curry);
305 }
306 return status;
307 }
308
309 static ncfdplane*
310 ncsubproc_launch(ncplane* n, ncsubproc* ret, const ncsubproc_options* opts, int fd,
311 ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn){
312 ncfdplane_options popts = {
313 .curry = opts->curry,
314 .follow = true,
315 };
316 ret->nfp = ncfdplane_create_internal(n, &popts, fd, cbfxn, donecbfxn, false);
317 if(ret->nfp == NULL){
318 return NULL;
319 }
320 if(pthread_create(&ret->nfp->tid, NULL, ncsubproc_thread, ret)){
321 ncfdplane_destroy_inner(ret->nfp);
322 ret->nfp = NULL;
323 }
324 if(ret->pidfd < 0){
325 // if we don't have a pidfd to throw into our poll(), we need spin up a
326 // thread to call waitpid() on our pid
327 if(pthread_create(&ret->waittid, NULL, ncsubproc_waiter, ret)){
328 // FIXME cancel and join thread
329 ncfdplane_destroy_inner(ret->nfp);
330 ret->nfp = NULL;
331 }
332 }
333 return ret->nfp;
334 }
335 #endif
336
337 // use of env implies usepath
338 static ncsubproc*
339 ncexecvpe(ncplane* n, const ncsubproc_options* opts, unsigned usepath,
340 const char* bin, char* const arg[], char* const env[],
341 ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn){
342 ncsubproc_options zeroed = {};
343 if(!opts){
344 opts = &zeroed;
345 }
346 if(!cbfxn || !donecbfxn){
347 return NULL;
348 }
349 if(opts->flags > 0){
350 logwarn("Provided unsupported flags %016" PRIx64 "\n", opts->flags);
351 }
352 #ifndef __MINGW64__
353 int fd = -1;
354 ncsubproc* ret = malloc(sizeof(*ret));
355 if(ret == NULL){
356 return NULL;
357 }
358 memset(ret, 0, sizeof(*ret));
359 ret->pid = launch_pipe_process(&fd, &ret->pidfd, usepath, bin, arg, env);
360 if(ret->pid < 0){
361 free(ret);
362 return NULL;
363 }
364 if((ret->nfp = ncsubproc_launch(n, ret, opts, fd, cbfxn, donecbfxn)) == NULL){
365 kill_and_wait_subproc(ret->pid, ret->pidfd, NULL);
366 free(ret);
367 return NULL;
368 }
369 return ret;
370 #else
371 // FIXME use CreateProcess()
372 (void)n;
373 (void)usepath;
374 (void)bin;
375 (void)arg;
376 (void)env;
377 (void)cbfxn;
378 (void)donecbfxn;
379 return NULL;
380 #endif
381 }
382
383 ncsubproc* ncsubproc_createv(ncplane* n, const ncsubproc_options* opts,
384 const char* bin, const char* const arg[],
385 ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn){
386 return ncexecvpe(n, opts, 0, bin, (char* const *)arg, NULL, cbfxn, donecbfxn);
387 }
388
389 ncsubproc* ncsubproc_createvp(ncplane* n, const ncsubproc_options* opts,
390 const char* bin, const char* const arg[],
391 ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn){
392 return ncexecvpe(n, opts, 1, bin, (char* const *)arg, NULL, cbfxn, donecbfxn);
393 }
394
395 ncsubproc* ncsubproc_createvpe(ncplane* n, const ncsubproc_options* opts,
396 const char* bin, const char* const arg[],
397 const char* const env[],
398 ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn){
399 return ncexecvpe(n, opts, 1, bin, (char* const *)arg, (char* const*)env, cbfxn, donecbfxn);
400 }
401
402 int ncsubproc_destroy(ncsubproc* n){
403 int ret = 0;
404 if(n){
405 void* vret = NULL;
406 //fprintf(stderr, "pid: %u pidfd: %d waittid: %u\n", n->pid, n->pidfd, n->waittid);
407 #ifndef __MINGW64__
408 #ifdef USING_PIDFD
409 if(n->pidfd >= 0){
410 loginfo("Sending SIGKILL to pidfd %d\n", n->pidfd);
411 if(syscall(__NR_pidfd_send_signal, n->pidfd, SIGKILL, NULL, 0)){
412 kill(n->pid, SIGKILL);
413 }
414 }
415 #else
416 pthread_mutex_lock(&n->lock);
417 if(!n->waited){
418 loginfo("Sending SIGKILL to PID %d\n", n->pid);
419 kill(n->pid, SIGKILL);
420 }
421 pthread_mutex_unlock(&n->lock);
422 #endif
423 #endif
424 // the thread waits on the subprocess via pidfd (iff pidfd >= 0), and
425 // then exits. don't try to cancel the thread in that case; rely instead on
426 // killing the subprocess.
427 if(n->pidfd < 0){
428 pthread_cancel(n->nfp->tid);
429 // shouldn't need a cancellation of waittid thanks to SIGKILL
430 pthread_join(n->waittid, &vret);
431 }
432 if(vret == NULL){
433 pthread_join(n->nfp->tid, &vret);
434 }else{
435 pthread_join(n->nfp->tid, NULL);
436 }
437 pthread_mutex_destroy(&n->lock);
438 free(n);
439 if(vret == NULL){
440 ret = -1;
441 }else if(vret != PTHREAD_CANCELED){
442 ret = *(int*)vret;
443 free(vret);
444 }
445 }
446 return ret;
447 }
448
449 ncplane* ncsubproc_plane(ncsubproc* n){
450 return n->nfp->ncp;
451 }
452
453 // if ttyfp is a tty, return a file descriptor extracted from it. otherwise,
454 // try to get the controlling terminal. otherwise, return -1.
455 int get_tty_fd(FILE* ttyfp){
456 int fd = -1;
457 if(ttyfp){
458 if((fd = fileno(ttyfp)) < 0){
459 logwarn("no file descriptor was available in outfp %p\n", ttyfp);
460 }else{
461 if(tty_check(fd)){
462 fd = dup(fd);
463 }else{
464 loginfo("fd %d is not a TTY\n", fd);
465 fd = -1;
466 }
467 }
468 }
469 if(fd < 0){
470 fd = open("/dev/tty", O_RDWR | O_CLOEXEC | O_NOCTTY);
471 if(fd < 0){
472 loginfo("couldn't open /dev/tty (%s)\n", strerror(errno));
473 }else{
474 if(!tty_check(fd)){
475 loginfo("file descriptor for /dev/tty (%d) is not actually a TTY\n", fd);
476 close(fd);
477 fd = -1;
478 }
479 }
480 }
481 if(fd >= 0){
482 loginfo("returning TTY fd %d\n", fd);
483 }
484 return fd;
485 }
486