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