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