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