1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2  *@ Implementation of child.h.
3  *@ TODO . argument and environment space constraints not tested.
4  *@ TODO . use a SU child, offer+use our own stuff for "wait status" checks.
5  *@ TODO   (requires event loop then, likely).
6  *@ TODO   Conditionally use waitid(2) instead of waitpid(2) (Joerg Schilling).
7  *@ TODO . STDERR is always "passed", yet not taken care of regarding termios!
8  *@ TODO . we would need full and true job control handling
9  *@ TODO   But at least notion of background and foreground, see termios.c!
10  *
11  * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
12  * SPDX-License-Identifier: ISC
13  *
14  * Permission to use, copy, modify, and/or distribute this software for any
15  * purpose with or without fee is hereby granted, provided that the above
16  * copyright notice and this permission notice appear in all copies.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
19  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
20  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
21  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
22  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
23  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
24  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25  */
26 #undef su_FILE
27 #define su_FILE child
28 #define mx_SOURCE
29 #define mx_SOURCE_CHILD
30 
31 #ifndef mx_HAVE_AMALGAMATION
32 # include "mx/nail.h"
33 #endif
34 
35 #include <sys/wait.h>
36 
37 #include <su/cs.h>
38 #include <su/mem.h>
39 
40 #include "mx/cmd.h"
41 #include "mx/file-streams.h"
42 #include "mx/sigs.h"
43 #include "mx/termcap.h"
44 #include "mx/termios.h"
45 
46 #include "mx/child.h"
47 #include "su/code-in.h"
48 
49 struct a_child_ent{
50    struct a_child_ent *ce_link;
51    s32 ce_pid; /* -1: struct can be gc'd */
52    s32 ce_status; /* wait status */
53    boole ce_done; /* has terminated */
54    boole ce_forget; /* will not be wait()ed upon */
55    boole ce_tios; /* Counts against child_termios_users */
56    boole ce_tios_suspended; /* Suspended via TERMIOS */
57    u8 ce__pad[4];
58 };
59 
60 static struct a_child_ent *a_child_head;
61 
62 /* Cleanup internal structures which have been rendered obsolete (children have
63  * terminated) in the meantime; returns list to be freed.
64  * Note: signals including SIGCHLD need to be blocked when calling this */
65 static struct a_child_ent *a_child_manager_cleanup(void);
66 
67 /* It or NIL is returned; if ceppp is set then it will point to the linked
68  * storage of the return value: signals need to be blocked in this case! */
69 SINLINE struct a_child_ent *a_child_find(s32 pid,
70       struct a_child_ent ***ceppp_or_nil);
71 
72 /* Handle SIGCHLD */
73 static void a_child__sigchld(int signo);
74 
75 /* Handle job control signals */
76 static boole a_child__on_termios_state_change(up cookie, u32 tiossc, s32 sig);
77 
78 static struct a_child_ent *
a_child_manager_cleanup(void)79 a_child_manager_cleanup(void){
80    struct a_child_ent *nlp, **nlpp, **cepp, *cep;
81    NYD_IN;
82 
83    nlp = NIL;
84    nlpp = &nlp;
85 
86    for(cepp = &a_child_head; *cepp != NIL;){
87       if((*cepp)->ce_pid == -1){
88          cep = *cepp;
89          *cepp = cep->ce_link;
90 
91          *nlpp = cep;
92          nlpp = &cep->ce_link;
93       }else
94          cepp = &(*cepp)->ce_link;
95    }
96    NYD_OU;
97    return nlp;
98 }
99 
100 SINLINE struct a_child_ent *
a_child_find(s32 pid,struct a_child_ent *** ceppp_or_nil)101 a_child_find(s32 pid, struct a_child_ent ***ceppp_or_nil){
102    struct a_child_ent **cepp, *cep;
103    NYD2_IN;
104 
105    for(cepp = &a_child_head; (cep = *cepp) != NIL; cepp = &(*cepp)->ce_link)
106       if(cep->ce_pid == pid)
107          break;
108 
109    if(ceppp_or_nil != NIL)
110       *ceppp_or_nil = cepp;
111    NYD2_OU;
112    return cep;
113 }
114 
115 static void
a_child__sigchld(int signo)116 a_child__sigchld(int signo){
117    struct a_child_ent *cep;
118    int status;
119    pid_t pid;
120    UNUSED(signo);
121 
122    for(;;){
123       pid = waitpid(-1, &status, WNOHANG);
124       if(pid <= 0){
125          if(pid == -1 && su_err_no() == su_ERR_INTR)
126             continue;
127          break;
128       }
129 
130       if((cep = a_child_find(S(s32,pid), NIL)) != NIL){
131          cep->ce_done = TRU1;
132          cep->ce_status = status;
133          if(cep->ce_forget)
134             cep->ce_pid = -1;
135       }
136    }
137 }
138 
139 static boole
a_child__on_termios_state_change(up cookie,u32 tiossc,s32 sig)140 a_child__on_termios_state_change(up cookie, u32 tiossc, s32 sig){/* TODO bad */
141    struct a_child_ent *cep;
142 
143    if((cep = a_child_find(S(s32,cookie), NIL)) != NIL){
144       if(cep->ce_done)
145          ;
146       else if(tiossc & mx_TERMIOS_STATE_POP){
147          /* TODO this is bad - we should have a reaper timer in the
148           * TODO (yet non-existing) event loop and shut this thing down
149           * TODO gracefully */
150          n_err("Reaping child process %d\n", cep->ce_pid);
151          cep->ce_tios = FAL0;
152          kill(cep->ce_pid,
153             (tiossc & mx_TERMIOS_STATE_SIGNAL ? sig : SIGTERM));
154          /* C99 */{
155             uz i;
156 
157             for(i = 0; i < 10; ++i){
158                n_msleep(100, FAL0);
159                if(cep->ce_done)
160                   break;
161             }
162             if(!cep->ce_done)
163                kill(cep->ce_pid, SIGKILL);
164          }
165       }else if(tiossc & mx_TERMIOS_STATE_SUSPEND){
166          if(!cep->ce_tios_suspended){
167             cep->ce_tios_suspended = TRU1;
168             if(!(tiossc & mx_TERMIOS_STATE_SIGNAL)){
169                int wstat;
170                pid_t wpid;
171 
172                kill(cep->ce_pid, SIGTSTP);
173                wpid = waitpid(cep->ce_pid, &wstat, WUNTRACED);
174                UNUSED(wpid);
175             }
176          }
177       }else if(tiossc & mx_TERMIOS_STATE_RESUME){
178          if(cep->ce_tios_suspended){
179             cep->ce_tios_suspended = FAL0;
180             /* TODO Sigh.  We do not handle process groups and have a bg/fg
181              * TODO notion, so we do handle the terminal even if kids have it.
182              * TODO Since job control sigs are sent to all processes in
183              * TODO a process group, we race with the child.
184              * TODO Synchronize that is impossible; for now we and termios
185              * TODO assume au */
186             if(!(tiossc & mx_TERMIOS_STATE_SIGNAL))
187                kill(cep->ce_pid, SIGCONT);
188          }
189       }
190    }
191 
192    return FAL0;
193 }
194 
195 void
mx_child_controller_setup(void)196 mx_child_controller_setup(void){
197    struct sigaction nact, oact;
198    NYD_IN;
199 
200    nact.sa_handler = &a_child__sigchld;
201    sigemptyset(&nact.sa_mask);
202    nact.sa_flags = SA_RESTART
203 #ifdef SA_NOCLDSTOP
204          | SA_NOCLDSTOP
205 #endif
206          ;
207 
208    if(sigaction(SIGCHLD, &nact, &oact) != 0)
209       n_panic(_("Cannot install signal handler for child process controller"));
210    NYD_OU;
211 }
212 
213 void
mx_child_ctx_setup(struct mx_child_ctx * ccp)214 mx_child_ctx_setup(struct mx_child_ctx *ccp){
215    NYD2_IN;
216    ASSERT(ccp);
217 
218    su_mem_set(ccp, 0, sizeof *ccp);
219    ccp->cc_fds[0] = ccp->cc_fds[1] = mx_CHILD_FD_PASS;
220    NYD2_OU;
221 }
222 
223 boole
mx_child_run(struct mx_child_ctx * ccp)224 mx_child_run(struct mx_child_ctx *ccp){
225    s32 e;
226    NYD_IN;
227 
228    ASSERT(ccp);
229    ASSERT(ccp->cc_pid == 0);
230    ASSERT(!(ccp->cc_flags & mx_CHILD_SPAWN_CONTROL_LINGER) ||
231       (ccp->cc_flags & mx_CHILD_SPAWN_CONTROL));
232 
233    e = su_ERR_NONE;
234 
235    if(!mx_child_fork(ccp))
236       e = ccp->cc_error;
237    else if(ccp->cc_pid == 0)
238       goto jchild;
239    else if(ccp->cc_flags & mx_CHILD_RUN_WAIT_LIFE){
240       if(!mx_child_wait(ccp))
241          e = ccp->cc_error;
242 
243       if((e != su_ERR_NONE || ccp->cc_exit_status < 0) &&
244             (ok_blook(bsdcompat) || ok_blook(bsdmsgs)))
245          n_err(_("Fatal error in process\n"));
246    }
247 
248    if(e != su_ERR_NONE){
249       n_perr(_("child_run()"), e);
250       su_err_set_no(ccp->cc_error = e);
251    }
252 
253    NYD_OU;
254    return (e == su_ERR_NONE);
255 
256 jchild:{
257    char *argv[128 + 4]; /* TODO magic constant, fixed size -> su_vector */
258    int i;
259 
260    if(ccp->cc_env_addon != NIL){
261       extern char **environ;
262       uz ei, ei_orig, ai, ai_orig;
263       char **env;
264       char const **env_addon;
265 
266       env_addon = ccp->cc_env_addon;
267 
268       /* TODO note we don't check the POSIX limit:
269        * the total space used to store the environment and the arguments to
270        * the process is limited to {ARG_MAX} bytes */
271       for(ei = 0; environ[ei] != NIL; ++ei)
272          ;
273       ei_orig = ei;
274       for(ai = 0; env_addon[ai] != NIL; ++ai)
275          ;
276       ai_orig = ai;
277       env = n_lofi_alloc(sizeof(*env) * (ei + ai +1));
278       su_mem_copy(env, environ, sizeof(*env) * ei);
279 
280       /* Replace all those keys that yet exist */
281       while(ai-- > 0){
282          char const *ee, *kvs;
283          uz kl;
284 
285          ee = env_addon[ai];
286          kvs = su_cs_find_c(ee, '=');
287          ASSERT(kvs != NIL);
288          kl = P2UZ(kvs - ee);
289          ASSERT(kl > 0);
290          for(ei = ei_orig; ei-- > 0;){
291             char const *ekvs;
292 
293             if((ekvs = su_cs_find_c(env[ei], '=')) != NIL &&
294                   kl == P2UZ(ekvs - env[ei]) && !su_mem_cmp(ee, env[ei], kl)){
295                env[ei] = UNCONST(char*,ee);
296                env_addon[ai] = NIL;
297                break;
298             }
299          }
300       }
301 
302       /* And append the rest */
303       for(ei = ei_orig, ai = ai_orig; ai-- > 0;)
304          if(env_addon[ai] != NIL)
305             env[ei++] = UNCONST(char*,env_addon[ai]);
306 
307       env[ei] = NIL;
308       environ = env;
309    }
310 
311    i = (int)getrawlist(TRU1, argv, NELEM(argv) - 4, ccp->cc_cmd,
312          su_cs_len(ccp->cc_cmd));
313    if(i >= 0){
314       if((argv[i++] = UNCONST(char*,ccp->cc_args[0])) != NIL &&
315             (argv[i++] = UNCONST(char*,ccp->cc_args[1])) != NIL &&
316             (argv[i++] = UNCONST(char*,ccp->cc_args[2])) != NIL)
317          argv[i] = NIL;
318 
319       mx_child_in_child_setup(ccp);
320 
321       execvp(argv[0], argv);
322       perror(argv[0]);
323    }
324    for(;;)
325       _exit(n_EXIT_ERR);
326    }
327 }
328 
329 boole
mx_child_fork(struct mx_child_ctx * ccp)330 mx_child_fork(struct mx_child_ctx *ccp){
331    struct a_child_ent *nlp, *cep;
332    NYD_IN;
333 
334    ASSERT(ccp);
335    ASSERT(ccp->cc_pid == 0);
336    ASSERT(!(ccp->cc_flags & mx_CHILD_SPAWN_CONTROL_LINGER) ||
337       (ccp->cc_flags & mx_CHILD_SPAWN_CONTROL));
338    ASSERT(ccp->cc_error == su_ERR_NONE);
339 
340    if(n_poption & n_PO_D_VV)
341       n_err(_("Forking child%s: %s %s %s %s\n"),
342          (ccp->cc_flags & mx_CHILD_SPAWN_CONTROL ? _(" with spawn control")
343             : su_empty),
344          n_shexp_quote_cp(((ccp->cc_cmd != R(char*,-1)
345             ? (ccp->cc_cmd != NIL ? ccp->cc_cmd : _("exec handled by caller"))
346             : _("forked concurrent \"in-image\" code"))), FAL0),
347          (ccp->cc_args[0] != NIL ? n_shexp_quote_cp(ccp->cc_args[0], FAL0)
348             : su_empty),
349          (ccp->cc_args[1] != NIL ? n_shexp_quote_cp(ccp->cc_args[1], FAL0)
350             : su_empty),
351          (ccp->cc_args[2] != NIL ? n_shexp_quote_cp(ccp->cc_args[2], FAL0)
352             : su_empty));
353 
354    if((ccp->cc_flags & mx_CHILD_SPAWN_CONTROL) &&
355          !mx_fs_pipe_cloexec(&ccp->cc__cpipe[0])){
356       ccp->cc_error = su_err_no();
357       goto jleave;
358    }
359 
360    cep = su_TCALLOC(struct a_child_ent, 1);
361 
362    /* Does this child take the terminal? */
363    if(ccp->cc_fds[0] == mx_CHILD_FD_PASS ||
364          ccp->cc_fds[1] == mx_CHILD_FD_PASS){
365       /* We strip that in started children.. */
366       if(n_psonce & n_PSO_TTYANY){
367          ccp->cc_flags |= mx__CHILD_JOBCTL;
368          cep->ce_tios = TRU1;
369       }
370    }
371 
372    /* TODO It is actually very bad to block all the signals for such a long
373     * TODO time, especially when taking into account on what is done in here */
374    mx_sigs_all_hold(SIGCHLD, 0);
375 
376    nlp = a_child_manager_cleanup();
377 
378    /* If this child takes the terminal, adjust termios now, but do not yet
379     * install our handler */
380    if(cep->ce_tios)
381       mx_termios_cmdx(mx_TERMIOS_CMD_PUSH | mx_TERMIOS_CMD_HANDS_OFF);
382 
383    switch((ccp->cc_pid = cep->ce_pid = fork())){
384    case 0:
385       goto jkid;
386    case -1:
387       ccp->cc_error = su_err_no();
388 
389       /* Link in cleanup list on failure */
390       cep->ce_link = nlp;
391       nlp = cep;
392 
393       /* And shutdown our termios environment */
394       if(cep->ce_tios)
395          mx_termios_cmdx(mx_TERMIOS_CMD_POP | mx_TERMIOS_CMD_HANDS_OFF);
396 
397       mx_sigs_all_rele();
398 
399       if(ccp->cc_flags & mx_CHILD_SPAWN_CONTROL){
400          close(S(int,ccp->cc__cpipe[0]));
401          close(S(int,ccp->cc__cpipe[1]));
402       }
403       n_perr(_("child_fork(): fork failure"), ccp->cc_error);
404       break;
405    default:
406       /* In the parent, conditionally wait on the control pipe */
407       if(ccp->cc_flags & mx_CHILD_SPAWN_CONTROL){
408          char ebuf[sizeof(ccp->cc_error)];
409          sz r;
410 
411          close(S(int,ccp->cc__cpipe[1]));
412          r = read(S(int,ccp->cc__cpipe[0]), ebuf, sizeof ebuf);
413          close(S(int,ccp->cc__cpipe[0]));
414 
415          switch(r){
416          case 0:
417             goto jlink_child;
418          case sizeof(ccp->cc_error):
419             su_mem_copy(&ccp->cc_error, ebuf, sizeof(ccp->cc_error));
420             break;
421          default:
422             ccp->cc_error = su_ERR_CHILD;
423             break;
424          }
425 
426          /* Link in cleanup list on failure */
427          cep->ce_link = nlp;
428          nlp = cep;
429 
430          /* And shutdown our termios environment */
431          if(cep->ce_tios)
432             mx_termios_cmdx(mx_TERMIOS_CMD_POP | mx_TERMIOS_CMD_HANDS_OFF);
433       }else{
434 jlink_child:
435          cep->ce_link = a_child_head;
436          a_child_head = cep;
437 
438          /* Time to install our termios handler */
439          if(cep->ce_tios)
440             mx_termios_on_state_change_set(&a_child__on_termios_state_change,
441                cep->ce_pid);
442       }
443 
444       /* in_child_setup() will procmask() for the child */
445       mx_sigs_all_rele();
446       break;
447    }
448 
449    /* Free all stale children */
450    while(nlp != NIL){
451       cep = nlp;
452       nlp = nlp->ce_link;
453       su_FREE(cep);
454    }
455 
456 jleave:
457    NYD_OU;
458    return (ccp->cc_error == su_ERR_NONE);
459 
460 jkid:
461    a_child_head = NIL;
462    /* Strip tty bits, our children will not care from our point of view */
463    n_psonce &= ~(n_PSO_TTYANY | n_PSO_INTERACTIVE);
464 
465    /* Close the unused end of the control pipe right now */
466    if(ccp->cc_flags & mx_CHILD_SPAWN_CONTROL)
467       close(S(int,ccp->cc__cpipe[0]));
468    goto jleave;
469 }
470 
471 void
mx_child_in_child_setup(struct mx_child_ctx * ccp)472 mx_child_in_child_setup(struct mx_child_ctx *ccp){
473    sigset_t fset;
474    s32 fd, i;
475    ASSERT(ccp);
476    ASSERT(ccp->cc_pid == 0);
477 
478    /* All file descriptors other than 0, 1, and 2 are supposed to be cloexec */
479    /* TODO WHAT IS WITH STDERR_FILENO DAMMIT? */
480    if((i = ((fd = S(s32,ccp->cc_fds[0])) == mx_CHILD_FD_NULL)))
481       ccp->cc_fds[0] = fd = open(n_path_devnull, O_RDONLY);
482    if(fd >= 0){
483       dup2(fd, STDIN_FILENO);
484       if(i)
485          close(fd);
486    }
487 
488    if((i = ((fd = S(s32,ccp->cc_fds[1])) == mx_CHILD_FD_NULL)))
489       ccp->cc_fds[1] = fd = open(n_path_devnull, O_WRONLY);
490    if(fd >= 0){
491       dup2(fd, STDOUT_FILENO);
492       if(i)
493          close(fd);
494    }
495 
496    /* Close our side of the control pipe, unless we should wait for cloexec to
497     * do that for us */
498    if((ccp->cc_flags & (mx_CHILD_SPAWN_CONTROL | mx_CHILD_SPAWN_CONTROL_LINGER)
499          ) == mx_CHILD_SPAWN_CONTROL)
500       close(S(int,ccp->cc__cpipe[1]));
501 
502    if(n_pstate & n_PS_SIGALARM)
503       alarm(0);
504 
505    if(ccp->cc_mask != NIL){
506       sigset_t *ssp;
507 
508       ssp = S(sigset_t*,ccp->cc_mask);
509       for(i = 1; i < NSIG; ++i)
510          if(sigismember(ssp, i))
511             safe_signal(i, SIG_IGN);
512       if(!sigismember(ssp, SIGINT))
513          safe_signal(SIGINT, SIG_DFL);
514    }
515 
516    sigemptyset(&fset);
517    sigprocmask(SIG_SETMASK, &fset, NIL);
518 }
519 
520 void
mx_child_in_child_exec_failed(struct mx_child_ctx * ccp,s32 err)521 mx_child_in_child_exec_failed(struct mx_child_ctx *ccp, s32 err){
522    ASSERT(ccp);
523    ASSERT(ccp->cc_pid == 0);
524    ASSERT(ccp->cc_flags & mx_CHILD_SPAWN_CONTROL_LINGER);
525 
526    ccp->cc_error = err;
527    (void)write(S(int,ccp->cc__cpipe[1]), &ccp->cc_error,
528       sizeof(ccp->cc_error));
529    close(S(int,ccp->cc__cpipe[1]));
530 }
531 
532 s32
mx_child_signal(struct mx_child_ctx * ccp,s32 sig)533 mx_child_signal(struct mx_child_ctx *ccp, s32 sig){
534    s32 rv;
535    struct a_child_ent *cep;
536    NYD_IN;
537 
538    ASSERT(ccp);
539    ASSERT(ccp->cc_pid > 0);
540 
541    if((cep = a_child_find(ccp->cc_pid, NIL)) != NIL){
542       ASSERT(!cep->ce_forget);
543       if(cep->ce_done)
544          rv = -1;
545       else if((rv = kill(S(pid_t,ccp->cc_pid), sig)) != 0)
546          rv = su_err_no();
547    }else
548       ccp->cc_pid = rv = -1;
549 
550    NYD_OU;
551    return rv;
552 }
553 
554 void
mx_child_forget(struct mx_child_ctx * ccp)555 mx_child_forget(struct mx_child_ctx *ccp){
556    struct a_child_ent *cep, **cepp;
557    NYD_IN;
558 
559    ASSERT(ccp);
560    ASSERT(ccp->cc_pid > 0);
561 
562    mx_sigs_all_hold(SIGCHLD, 0);
563 
564    if((cep = a_child_find(ccp->cc_pid, &cepp)) != NIL){
565       /* XXX ASSERT sigprocmask blocked, need debug wrapper */
566       ASSERT(!cep->ce_forget);
567       ASSERT(!(ccp->cc_flags & mx__CHILD_JOBCTL));  ASSERT(!cep->ce_tios);
568       cep->ce_forget = TRU1;
569       if(cep->ce_done)
570          *cepp = cep->ce_link;
571    }
572 
573    mx_sigs_all_rele();
574 
575    if(cep != NIL && cep->ce_done)
576       su_FREE(cep);
577 
578    ccp->cc_pid = -1;
579    NYD_OU;
580 }
581 
582 boole
mx_child_wait(struct mx_child_ctx * ccp)583 mx_child_wait(struct mx_child_ctx *ccp){
584    s32 ws;
585    boole ok;
586    struct a_child_ent *cep, **cepp;
587    NYD_IN;
588 
589    ASSERT(ccp);
590    ASSERT(ccp->cc_pid > 0);
591 
592    /* TODO Unless we place children which take the terminal in their own
593     * TODO thing (setsid();setlogin();ioctl(fd, TIOCSCTTY)), that is
594     * TODO CHLD:setpgid(0,0); PAREN:setpgid(CHILD,0),
595     * TODO tcsetpgrp(STDIN_FILENO,CHILD)
596     * TODO We need to ensure job control signals get through.
597     * TODO v15 also need to honour network keepalive alarms */
598    mx_sigs_all_hold(SIGCHLD, -SIGTSTP, -SIGTTIN, -SIGTTOU, -SIGALRM, 0);
599 
600    ok = TRU1;
601    if((cep = a_child_find(ccp->cc_pid, &cepp)) != NIL){
602       /* XXX It would actually be better to sigsuspend on SIGCHLD */
603       while(!cep->ce_done &&
604             waitpid(S(pid_t,ccp->cc_pid), &cep->ce_status, 0) == -1){
605          if((ws = su_err_no()) != su_ERR_INTR){
606             ok = FAL0;
607             break;
608          }
609       }
610 
611       ws = cep->ce_status;
612       *cepp = cep->ce_link;
613 
614       /* This must be the one which holds that level, no? */
615       if(cep->ce_tios)
616          mx_termios_cmdx(mx_TERMIOS_CMD_POP | mx_TERMIOS_CMD_HANDS_OFF);
617    }else{
618       cepp = R(struct a_child_ent**,-1);
619       ws = 0;
620    }
621 
622    mx_sigs_all_rele();
623 
624    if(cep != NIL)
625       su_FREE(cep);
626 
627    if(ok){
628       ccp->cc_exit_status = WEXITSTATUS(ws);
629       if(!WIFEXITED(ws) && ccp->cc_exit_status > 0)
630          ccp->cc_exit_status = -ccp->cc_exit_status;
631    }else{
632       cep = NIL;
633       ccp->cc_exit_status = -255;
634    }
635 
636    ccp->cc_pid = -1;
637    NYD_OU;
638    return (cep != NIL);
639 }
640 
641 #include "su/code-ou.h"
642 /* s-it-mode */
643