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