1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26
27 /*
28 * dosys.cc
29 *
30 * Execute one commandline
31 */
32
33 /*
34 * Included files
35 */
36 #include <sys/wait.h> /* WIFEXITED(status) */
37 #include <alloca.h> /* alloca() */
38
39 #include <stdio.h> /* errno */
40 #include <errno.h> /* errno */
41 #include <fcntl.h> /* open() */
42 #include <mksh/dosys.h>
43 #include <mksh/macro.h> /* getvar() */
44 #include <mksh/misc.h> /* getmem(), fatal_mksh(), errmsg() */
45 #include <mksdmsi18n/mksdmsi18n.h> /* libmksdmsi18n_init() */
46 #include <sys/signal.h> /* SIG_DFL */
47 #include <sys/stat.h> /* open() */
48 #include <sys/wait.h> /* wait() */
49 #include <ulimit.h> /* ulimit() */
50 #include <unistd.h> /* close(), dup2() */
51
52
53
54 /*
55 * Defined macros
56 */
57 #define SEND_MTOOL_MSG(cmds)
58
59 /*
60 * typedefs & structs
61 */
62
63 /*
64 * Static variables
65 */
66
67 /*
68 * File table of contents
69 */
70 static Boolean exec_vp(register char *name, register char **argv, char **envp, register Boolean ignore_error, pathpt vroot_path);
71
72 /*
73 * Workaround for NFS bug. Sometimes, when running 'open' on a remote
74 * dmake server, it fails with "Stale NFS file handle" error.
75 * The second attempt seems to work.
76 */
77 int
78 my_open(const char *path, int oflag, mode_t mode) {
79 int res = open(path, oflag, mode);
80 if (res < 0 && (errno == ESTALE || errno == EAGAIN)) {
81 /* Stale NFS file handle. Try again */
82 res = open(path, oflag, mode);
83 }
84 return res;
85 }
86
87 /*
88 * void
89 * redirect_io(char *stdout_file, char *stderr_file)
90 *
91 * Redirects stdout and stderr for a child mksh process.
92 */
93 void
94 redirect_io(char *stdout_file, char *stderr_file)
95 {
96 long descriptor_limit;
97 int i;
98
99 if ((descriptor_limit = ulimit(UL_GDESLIM)) < 0) {
100 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 89, "ulimit() failed: %s"), errmsg(errno));
101 }
102 for (i = 3; i < descriptor_limit; i++) {
103 (void) close(i);
104 }
105 if ((i = my_open(stdout_file,
106 O_WRONLY | O_CREAT | O_TRUNC | O_DSYNC,
107 S_IREAD | S_IWRITE)) < 0) {
108 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 90, "Couldn't open standard out temp file `%s': %s"),
109 stdout_file,
110 errmsg(errno));
111 } else {
112 if (dup2(i, 1) == -1) {
113 fatal_mksh(NOCATGETS("*** Error: dup2(3, 1) failed: %s"),
114 errmsg(errno));
115 }
116 close(i);
117 }
118 if (stderr_file == NULL) {
119 if (dup2(1, 2) == -1) {
120 fatal_mksh(NOCATGETS("*** Error: dup2(1, 2) failed: %s"),
121 errmsg(errno));
122 }
123 } else if ((i = my_open(stderr_file,
124 O_WRONLY | O_CREAT | O_TRUNC | O_DSYNC,
125 S_IREAD | S_IWRITE)) < 0) {
126 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 91, "Couldn't open standard error temp file `%s': %s"),
127 stderr_file,
128 errmsg(errno));
129 } else {
130 if (dup2(i, 2) == -1) {
131 fatal_mksh(NOCATGETS("*** Error: dup2(3, 2) failed: %s"),
132 errmsg(errno));
133 }
134 close(i);
135 }
136 }
137
138 /*
139 * dosys_mksh(command, ignore_error, call_make, silent_error, target)
140 *
141 * Check if command string contains meta chars and dispatch to
142 * the proper routine for executing one command line.
143 *
144 * Return value:
145 * Indicates if the command execution failed
146 *
147 * Parameters:
148 * command The command to run
149 * ignore_error Should we abort when an error is seen?
150 * call_make Did command reference $(MAKE) ?
151 * silent_error Should error messages be suppressed for dmake?
152 * target Target we are building
153 *
154 * Global variables used:
155 * do_not_exec_rule Is -n on?
156 * working_on_targets We started processing real targets
157 */
158 Doname
159 dosys_mksh(register Name command, register Boolean ignore_error, register Boolean call_make, Boolean silent_error, Boolean always_exec, Name target, Boolean redirect_out_err, char *stdout_file, char *stderr_file, pathpt vroot_path, int nice_prio)
160 {
161 register int length = command->hash.length;
162 register wchar_t *p;
163 register wchar_t *q;
164 register wchar_t *cmd_string;
165 struct stat before;
166 Doname result;
167 Boolean working_on_targets_mksh = true;
168 Wstring wcb(command);
169 p = wcb.get_string();
170 cmd_string = p;
171
172 /* Strip spaces from head of command string */
173 while (iswspace(*p)) {
174 p++, length--;
175 }
176 if (*p == (int) nul_char) {
177 return build_failed;
178 }
179 /* If we are faking it we just return */
180 if (do_not_exec_rule &&
181 working_on_targets_mksh &&
182 !call_make &&
183 !always_exec) {
184 return build_ok;
185 }
186
187 /* Copy string to make it OK to write it. */
188 q = ALLOC_WC(length + 1);
189 (void) wscpy(q, p);
190 /* Write the state file iff this command uses make. */
191 /* XXX - currently does not support recursive make's, $(MAKE)'s
192 if (call_make && command_changed) {
193 write_state_file(0, false);
194 }
195 (void) stat(make_state->string_mb, &before);
196 */
197 /*
198 * Run command directly if it contains no shell meta chars,
199 * else run it using the shell.
200 */
201 /* XXX - command->meta *may* not be set correctly */
202 if (await(ignore_error,
203 silent_error,
204 target,
205 cmd_string,
206 command->meta ?
207 doshell(q, ignore_error, redirect_out_err, stdout_file, stderr_file, nice_prio) :
208 doexec(q, ignore_error, redirect_out_err, stdout_file, stderr_file, vroot_path, nice_prio),
209 false,
210 NULL,
211 -1)) {
212
213 #ifdef PRINT_EXIT_STATUS
214 warning_mksh(NOCATGETS("I'm in dosys_mksh(), and await() returned result of build_ok."));
215 #endif
216
217 result = build_ok;
218 } else {
219
220 #ifdef PRINT_EXIT_STATUS
221 warning_mksh(NOCATGETS("I'm in dosys_mksh(), and await() returned result of build_failed."));
222 #endif
223
224 result = build_failed;
225 }
226 retmem(q);
227
228 /* XXX - currently does not support recursive make's, $(MAKE)'s
229 if ((report_dependencies_level == 0) &&
230 call_make) {
231 make_state->stat.time = (time_t)file_no_time;
232 (void)exists(make_state);
233 if (before.st_mtime == make_state->stat.time) {
234 return result;
235 }
236 makefile_type = reading_statefile;
237 if (read_trace_level > 1) {
238 trace_reader = true;
239 }
240 (void) read_simple_file(make_state,
241 false,
242 false,
243 false,
244 false,
245 false,
246 true);
247 trace_reader = false;
248 }
249 */
250 return result;
251 }
252
253 /*
254 * doshell(command, ignore_error)
255 *
256 * Used to run command lines that include shell meta-characters.
257 * The make macro SHELL is supposed to contain a path to the shell.
258 *
259 * Return value:
260 * The pid of the process we started
261 *
262 * Parameters:
263 * command The command to run
264 * ignore_error Should we abort on error?
265 *
266 * Global variables used:
267 * filter_stderr If -X is on we redirect stderr
268 * shell_name The Name "SHELL", used to get the path to shell
269 */
270 int
271 doshell(wchar_t *command, register Boolean ignore_error, Boolean redirect_out_err, char *stdout_file, char *stderr_file, int nice_prio)
272 {
273 char *argv[6];
274 int argv_index = 0;
275 int cmd_argv_index;
276 int length;
277 char nice_prio_buf[MAXPATHLEN];
278 register Name shell = getvar(shell_name);
279 register char *shellname;
280 char *tmp_mbs_buffer;
281
282
283 if (IS_EQUAL(shell->string_mb, "")) {
284 shell = shell_name;
285 }
286 if ((shellname = strrchr(shell->string_mb, (int) slash_char)) == NULL) {
287 shellname = shell->string_mb;
288 } else {
289 shellname++;
290 }
291
292 /*
293 * Only prepend the /usr/bin/nice command to the original command
294 * if the nice priority, nice_prio, is NOT zero (0).
295 * Nice priorities can be a positive or a negative number.
296 */
297 if (nice_prio != 0) {
298 argv[argv_index++] = (char *)NOCATGETS("nice");
299 (void) sprintf(nice_prio_buf, NOCATGETS("-%d"), nice_prio);
300 argv[argv_index++] = strdup(nice_prio_buf);
301 }
302 argv[argv_index++] = shellname;
303 argv[argv_index++] = (char*)(ignore_error ? NOCATGETS("-c") : NOCATGETS("-ce"));
304 if ((length = wslen(command)) >= MAXPATHLEN) {
305 tmp_mbs_buffer = getmem((length * MB_LEN_MAX) + 1);
306 (void) wcstombs(tmp_mbs_buffer, command, (length * MB_LEN_MAX) + 1);
307 cmd_argv_index = argv_index;
308 argv[argv_index++] = strdup(tmp_mbs_buffer);
309 retmem_mb(tmp_mbs_buffer);
310 } else {
311 WCSTOMBS(mbs_buffer, command);
312 cmd_argv_index = argv_index;
313 argv[argv_index++] = strdup(mbs_buffer);
314 }
315 argv[argv_index] = NULL;
316 (void) fflush(stdout);
317 if ((childPid = fork()) == 0) {
318 enable_interrupt((void (*) (int)) SIG_DFL);
319 if (redirect_out_err) {
320 redirect_io(stdout_file, stderr_file);
321 }
322 #if 0
323 if (filter_stderr) {
324 redirect_stderr();
325 }
326 #endif
327 if (nice_prio != 0) {
328 (void) execve(NOCATGETS("/usr/bin/nice"), argv, environ);
329 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 92, "Could not load `/usr/bin/nice': %s"),
330 errmsg(errno));
331 } else {
332 (void) execve(shell->string_mb, argv, environ);
333 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 93, "Could not load Shell from `%s': %s"),
334 shell->string_mb,
335 errmsg(errno));
336 }
337 }
338 if (childPid == -1) {
339 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 94, "fork failed: %s"),
340 errmsg(errno));
341 }
342 retmem_mb(argv[cmd_argv_index]);
343 return childPid;
344 }
345
346 /*
347 * exec_vp(name, argv, envp, ignore_error)
348 *
349 * Like execve, but does path search.
350 * This starts command when make invokes it directly (without a shell).
351 *
352 * Return value:
353 * Returns false if the exec failed
354 *
355 * Parameters:
356 * name The name of the command to run
357 * argv Arguments for the command
358 * envp The environment for it
359 * ignore_error Should we abort on error?
360 *
361 * Global variables used:
362 * shell_name The Name "SHELL", used to get the path to shell
363 * vroot_path The path used by the vroot package
364 */
365 static Boolean
366 exec_vp(register char *name, register char **argv, char **envp, register Boolean ignore_error, pathpt vroot_path)
367 {
368 register Name shell = getvar(shell_name);
369 register char *shellname;
370 char *shargv[4];
371 Name tmp_shell;
372
373 if (IS_EQUAL(shell->string_mb, "")) {
374 shell = shell_name;
375 }
376
377 for (int i = 0; i < 5; i++) {
378 (void) execve_vroot(name,
379 argv + 1,
380 envp,
381 vroot_path,
382 VROOT_DEFAULT);
383 switch (errno) {
384 case ENOEXEC:
385 case ENOENT:
386 /* That failed. Let the shell handle it */
387 shellname = strrchr(shell->string_mb, (int) slash_char);
388 if (shellname == NULL) {
389 shellname = shell->string_mb;
390 } else {
391 shellname++;
392 }
393 shargv[0] = shellname;
394 shargv[1] = (char*)(ignore_error ? NOCATGETS("-c") : NOCATGETS("-ce"));
395 shargv[2] = argv[0];
396 shargv[3] = NULL;
397 tmp_shell = getvar(shell_name);
398 if (IS_EQUAL(tmp_shell->string_mb, "")) {
399 tmp_shell = shell_name;
400 }
401 (void) execve_vroot(tmp_shell->string_mb,
402 shargv,
403 envp,
404 vroot_path,
405 VROOT_DEFAULT);
406 return failed;
407 case ETXTBSY:
408 /*
409 * The program is busy (debugged?).
410 * Wait and then try again.
411 */
412 (void) sleep((unsigned) i);
413 case EAGAIN:
414 break;
415 default:
416 return failed;
417 }
418 }
419 return failed;
420 }
421
422 /*
423 * doexec(command, ignore_error)
424 *
425 * Will scan an argument string and split it into words
426 * thus building an argument list that can be passed to exec_ve()
427 *
428 * Return value:
429 * The pid of the process started here
430 *
431 * Parameters:
432 * command The command to run
433 * ignore_error Should we abort on error?
434 *
435 * Global variables used:
436 * filter_stderr If -X is on we redirect stderr
437 */
438 int
439 doexec(register wchar_t *command, register Boolean ignore_error, Boolean redirect_out_err, char *stdout_file, char *stderr_file, pathpt vroot_path, int nice_prio)
440 {
441 int arg_count = 5;
442 char **argv;
443 int length;
444 char nice_prio_buf[MAXPATHLEN];
445 register char **p;
446 wchar_t *q;
447 register wchar_t *t;
448 char *tmp_mbs_buffer;
449
450 /*
451 * Only prepend the /usr/bin/nice command to the original command
452 * if the nice priority, nice_prio, is NOT zero (0).
453 * Nice priorities can be a positive or a negative number.
454 */
455 if (nice_prio != 0) {
456 arg_count += 2;
457 }
458 for (t = command; *t != (int) nul_char; t++) {
459 if (iswspace(*t)) {
460 arg_count++;
461 }
462 }
463 argv = (char **)alloca(arg_count * (sizeof(char *)));
464 /*
465 * Reserve argv[0] for sh in case of exec_vp failure.
466 * Don't worry about prepending /usr/bin/nice command to argv[0].
467 * In fact, doing it may cause the sh command to fail!
468 */
469 p = &argv[1];
470 if ((length = wslen(command)) >= MAXPATHLEN) {
471 tmp_mbs_buffer = getmem((length * MB_LEN_MAX) + 1);
472 (void) wcstombs(tmp_mbs_buffer, command, (length * MB_LEN_MAX) + 1);
473 argv[0] = strdup(tmp_mbs_buffer);
474 retmem_mb(tmp_mbs_buffer);
475 } else {
476 WCSTOMBS(mbs_buffer, command);
477 argv[0] = strdup(mbs_buffer);
478 }
479
480 if (nice_prio != 0) {
481 *p++ = strdup(NOCATGETS("/usr/bin/nice"));
482 (void) sprintf(nice_prio_buf, NOCATGETS("-%d"), nice_prio);
483 *p++ = strdup(nice_prio_buf);
484 }
485 /* Build list of argument words. */
486 for (t = command; *t;) {
487 if (p >= &argv[arg_count]) {
488 /* This should never happen, right? */
489 WCSTOMBS(mbs_buffer, command);
490 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 95, "Command `%s' has more than %d arguments"),
491 mbs_buffer,
492 arg_count);
493 }
494 q = t;
495 while (!iswspace(*t) && (*t != (int) nul_char)) {
496 t++;
497 }
498 if (*t) {
499 for (*t++ = (int) nul_char; iswspace(*t); t++);
500 }
501 if ((length = wslen(q)) >= MAXPATHLEN) {
502 tmp_mbs_buffer = getmem((length * MB_LEN_MAX) + 1);
503 (void) wcstombs(tmp_mbs_buffer, q, (length * MB_LEN_MAX) + 1);
504 *p++ = strdup(tmp_mbs_buffer);
505 retmem_mb(tmp_mbs_buffer);
506 } else {
507 WCSTOMBS(mbs_buffer, q);
508 *p++ = strdup(mbs_buffer);
509 }
510 }
511 *p = NULL;
512
513 /* Then exec the command with that argument list. */
514 (void) fflush(stdout);
515 if ((childPid = fork()) == 0) {
516 enable_interrupt((void (*) (int)) SIG_DFL);
517 if (redirect_out_err) {
518 redirect_io(stdout_file, stderr_file);
519 }
520 #if 0
521 if (filter_stderr) {
522 redirect_stderr();
523 }
524 #endif
525 (void) exec_vp(argv[1], argv, environ, ignore_error, vroot_path);
526 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 96, "Cannot load command `%s': %s"), argv[1], errmsg(errno));
527 }
528 if (childPid == -1) {
529 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 97, "fork failed: %s"),
530 errmsg(errno));
531 }
532 for (int i = 0; argv[i] != NULL; i++) {
533 retmem_mb(argv[i]);
534 }
535 return childPid;
536 }
537
538 /*
539 * await(ignore_error, silent_error, target, command, running_pid)
540 *
541 * Wait for one child process and analyzes
542 * the returned status when the child process terminates.
543 *
544 * Return value:
545 * Returns true if commands ran OK
546 *
547 * Parameters:
548 * ignore_error Should we abort on error?
549 * silent_error Should error messages be suppressed for dmake?
550 * target The target we are building, for error msgs
551 * command The command we ran, for error msgs
552 * running_pid The pid of the process we are waiting for
553 *
554 * Static variables used:
555 * filter_file The fd for the filter file
556 * filter_file_name The name of the filter file
557 *
558 * Global variables used:
559 * filter_stderr Set if -X is on
560 */
561 Boolean
562 await(register Boolean ignore_error, register Boolean silent_error, Name target, wchar_t *command, pid_t running_pid, Boolean send_mtool_msgs, void *xdrs_p, int job_msg_id)
563 {
564 int status;
565 char *buffer;
566 int core_dumped;
567 int exit_status;
568 FILE *outfp;
569 register pid_t pid;
570 struct stat stat_buff;
571 int termination_signal;
572 char tmp_buf[MAXPATHLEN];
573
574 while ((pid = wait(&status)) != running_pid) {
575 if (pid == -1) {
576 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 98, "wait() failed: %s"), errmsg(errno));
577 }
578 }
579 (void) fflush(stdout);
580 (void) fflush(stderr);
581
582 if (status == 0) {
583
584 #ifdef PRINT_EXIT_STATUS
585 warning_mksh(NOCATGETS("I'm in await(), and status is 0."));
586 #endif
587
588 return succeeded;
589 }
590
591 #ifdef PRINT_EXIT_STATUS
592 warning_mksh(NOCATGETS("I'm in await(), and status is *NOT* 0."));
593 #endif
594
595
596 exit_status = WEXITSTATUS(status);
597
598 #ifdef PRINT_EXIT_STATUS
599 warning_mksh(NOCATGETS("I'm in await(), and exit_status is %d."), exit_status);
600 #endif
601
602 termination_signal = WTERMSIG(status);
603 core_dumped = WCOREDUMP(status);
604
605 /*
606 * If the child returned an error, we now try to print a
607 * nice message about it.
608 */
609 SEND_MTOOL_MSG(
610 make_output_msg = new Avo_CmdOutput();
611 (void) sprintf(tmp_buf, "%d", job_msg_id);
612 make_output_msg->appendOutput(strdup(tmp_buf));
613 );
614
615 tmp_buf[0] = (int) nul_char;
616 if (!silent_error) {
617 if (exit_status != 0) {
618 (void) fprintf(stdout,
619 catgets(libmksdmsi18n_catd, 1, 103, "*** Error code %d"),
620 exit_status);
621 SEND_MTOOL_MSG(
622 (void) sprintf(&tmp_buf[strlen(tmp_buf)],
623 catgets(libmksdmsi18n_catd, 1, 104, "*** Error code %d"),
624 exit_status);
625 );
626 } else {
627 (void) fprintf(stdout,
628 catgets(libmksdmsi18n_catd, 1, 105, "*** Signal %d"),
629 termination_signal);
630 SEND_MTOOL_MSG(
631 (void) sprintf(&tmp_buf[strlen(tmp_buf)],
632 catgets(libmksdmsi18n_catd, 1, 106, "*** Signal %d"),
633 termination_signal);
634 );
635 if (core_dumped) {
636 (void) fprintf(stdout,
637 catgets(libmksdmsi18n_catd, 1, 107, " - core dumped"));
638 SEND_MTOOL_MSG(
639 (void) sprintf(&tmp_buf[strlen(tmp_buf)],
640 catgets(libmksdmsi18n_catd, 1, 108, " - core dumped"));
641 );
642 }
643 }
644 if (ignore_error) {
645 (void) fprintf(stdout,
646 catgets(libmksdmsi18n_catd, 1, 109, " (ignored)"));
647 SEND_MTOOL_MSG(
648 (void) sprintf(&tmp_buf[strlen(tmp_buf)],
649 catgets(libmksdmsi18n_catd, 1, 110, " (ignored)"));
650 );
651 }
652 (void) fprintf(stdout, "\n");
653 (void) fflush(stdout);
654 SEND_MTOOL_MSG(
655 make_output_msg->appendOutput(strdup(tmp_buf));
656 );
657 }
658 SEND_MTOOL_MSG(
659 xdr_msg = (RWCollectable*) make_output_msg;
660 xdr(xdrs_p, xdr_msg);
661 delete make_output_msg;
662 );
663
664 #ifdef PRINT_EXIT_STATUS
665 warning_mksh(NOCATGETS("I'm in await(), returning failed."));
666 #endif
667
668 return failed;
669 }
670
671 /*
672 * sh_command2string(command, destination)
673 *
674 * Run one sh command and capture the output from it.
675 *
676 * Return value:
677 *
678 * Parameters:
679 * command The command to run
680 * destination Where to deposit the output from the command
681 *
682 * Static variables used:
683 *
684 * Global variables used:
685 */
686 void
687 sh_command2string(register String command, register String destination)
688 {
689 register FILE *fd;
690 register int chr;
691 int status;
692 Boolean command_generated_output = false;
693
694 command->text.p = (int) nul_char;
695 WCSTOMBS(mbs_buffer, command->buffer.start);
696 if ((fd = popen(mbs_buffer, "r")) == NULL) {
697 WCSTOMBS(mbs_buffer, command->buffer.start);
698 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 111, "Could not run command `%s' for :sh transformation"),
699 mbs_buffer);
700 }
701 while ((chr = getc(fd)) != EOF) {
702 if (chr == (int) newline_char) {
703 chr = (int) space_char;
704 }
705 command_generated_output = true;
706 append_char(chr, destination);
707 }
708
709 /*
710 * We don't want to keep the last LINE_FEED since usually
711 * the output of the 'sh:' command is used to evaluate
712 * some MACRO. ( /bin/sh and other shell add a line feed
713 * to the output so that the prompt appear in the right place.
714 * We don't need that
715 */
716 if (command_generated_output){
717 if ( *(destination->text.p-1) == (int) space_char) {
718 * (-- destination->text.p) = '\0';
719 }
720 } else {
721 /*
722 * If the command didn't generate any output,
723 * set the buffer to a null string.
724 */
725 *(destination->text.p) = '\0';
726 }
727
728 status = pclose(fd);
729 if (status != 0) {
730 WCSTOMBS(mbs_buffer, command->buffer.start);
731 fatal_mksh(catgets(libmksdmsi18n_catd, 1, 112, "The command `%s' returned status `%d'"),
732 mbs_buffer,
733 WEXITSTATUS(status));
734 }
735 }
736
737