2 * Copyright (c) 1992, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 * Copyright (c) 1992, 1993, 1994, 1995, 1996
5 * Keith Bostic. All rights reserved.
7 * This code is derived from software contributed to Berkeley by
10 * See the LICENSE file for redistribution information.
16 static const char sccsid
[] = "$Id: ex_script.c,v 10.44 2012/10/05 10:17:47 zy Exp $";
19 #include <sys/types.h>
20 #include <sys/ioctl.h>
21 #include <sys/queue.h>
22 #include <sys/select.h>
26 #include <bitstring.h>
43 #include "../common/common.h"
46 #include "pathnames.h"
48 static void sscr_check(SCR
*);
49 static int sscr_getprompt(SCR
*);
50 static int sscr_init(SCR
*);
51 static int sscr_insert(SCR
*);
52 static int sscr_matchprompt(SCR
*, char *, size_t, size_t *);
53 static int sscr_setprompt(SCR
*, char *, size_t);
56 * ex_script -- : sc[ript][!] [file]
57 * Switch to script mode.
59 * PUBLIC: int ex_script(SCR *, EXCMD *);
62 ex_script(SCR
*sp
, EXCMD
*cmdp
)
64 /* Vi only command. */
65 if (!F_ISSET(sp
, SC_VI
)) {
67 "150|The script command is only available in vi mode");
71 /* Switch to the new file. */
72 if (cmdp
->argc
!= 0 && ex_edit(sp
, cmdp
))
75 /* Create the shell, figure out the prompt. */
84 * Create a pty setup for a shell.
92 /* We're going to need a shell. */
93 if (opts_empty(sp
, O_SHELL
, 0))
96 MALLOC_RET(sp
, sc
, SCRIPT
*, sizeof(SCRIPT
));
99 sc
->sh_prompt_len
= 0;
102 * There are two different processes running through this code.
103 * They are the shell and the parent.
105 sc
->sh_master
= sc
->sh_slave
= -1;
107 if (tcgetattr(STDIN_FILENO
, &sc
->sh_term
) == -1) {
108 msgq(sp
, M_SYSERR
, "tcgetattr");
113 * Turn off output postprocessing and echo.
115 sc
->sh_term
.c_oflag
&= ~OPOST
;
116 sc
->sh_term
.c_cflag
&= ~(ECHO
|ECHOE
|ECHONL
|ECHOK
);
118 if (ioctl(STDIN_FILENO
, TIOCGWINSZ
, &sc
->sh_win
) == -1) {
119 msgq(sp
, M_SYSERR
, "tcgetattr");
123 if (openpty(&sc
->sh_master
,
124 &sc
->sh_slave
, sc
->sh_name
, &sc
->sh_term
, &sc
->sh_win
) == -1) {
125 msgq(sp
, M_SYSERR
, "openpty");
131 * Don't use vfork() here, because the signal semantics differ from
132 * implementation to implementation.
134 switch (sc
->sh_pid
= fork()) {
135 case -1: /* Error. */
136 msgq(sp
, M_SYSERR
, "fork");
137 err
: if (sc
->sh_master
!= -1)
138 (void)close(sc
->sh_master
);
139 if (sc
->sh_slave
!= -1)
140 (void)close(sc
->sh_slave
);
142 case 0: /* Utility. */
145 * So that shells that do command line editing turn it off.
147 (void)setenv("TERM", "emacs", 1);
148 (void)setenv("TERMCAP", "emacs:", 1);
149 (void)setenv("EMACS", "t", 1);
154 * 4.4BSD allocates a controlling terminal using the TIOCSCTTY
155 * ioctl, not by opening a terminal device file. POSIX 1003.1
156 * doesn't define a portable way to do this. If TIOCSCTTY is
157 * not available, hope that the open does it.
159 (void)ioctl(sc
->sh_slave
, TIOCSCTTY
, 0);
161 (void)close(sc
->sh_master
);
162 (void)dup2(sc
->sh_slave
, STDIN_FILENO
);
163 (void)dup2(sc
->sh_slave
, STDOUT_FILENO
);
164 (void)dup2(sc
->sh_slave
, STDERR_FILENO
);
165 (void)close(sc
->sh_slave
);
167 /* Assumes that all shells have -i. */
168 sh_path
= O_STR(sp
, O_SHELL
);
169 if ((sh
= strrchr(sh_path
, '/')) == NULL
)
173 execl(sh_path
, sh
, "-i", NULL
);
174 msgq_str(sp
, M_SYSERR
, sh_path
, "execl: %s");
176 default: /* Parent. */
180 if (sscr_getprompt(sp
))
183 F_SET(sp
, SC_SCRIPT
);
184 F_SET(sp
->gp
, G_SCRWIN
);
190 * Eat lines printed by the shell until a line with no trailing
191 * carriage return comes; set the prompt from that line.
194 sscr_getprompt(SCR
*sp
)
198 char *endp
, *p
, *t
, buf
[1024];
213 /* Wait up to a second for characters to read. */
217 FD_SET(sc
->sh_master
, &fdset
);
218 switch (select(sc
->sh_master
+ 1, &fdset
, NULL
, NULL
, &tv
)) {
219 case -1: /* Error or interrupt. */
220 msgq(sp
, M_SYSERR
, "select");
222 case 0: /* Timeout */
223 msgq(sp
, M_ERR
, "Error: timed out");
225 case 1: /* Characters to read. */
229 /* Read the characters. */
230 more
: len
= sizeof(buf
) - (endp
- buf
);
231 switch (nr
= read(sc
->sh_master
, endp
, len
)) {
233 msgq(sp
, M_ERR
, "Error: shell: EOF");
235 case -1: /* Error or interrupt. */
236 msgq(sp
, M_SYSERR
, "shell");
243 /* If any complete lines, push them into the file. */
244 for (p
= t
= buf
; p
< endp
; ++p
) {
245 if (*p
== '\r' || *p
== '\n') {
246 if (CHAR2INT5(sp
, exp
->ibcw
, t
, p
- t
, wp
, wlen
))
248 if (db_last(sp
, &lline
) ||
249 db_append(sp
, 0, lline
, wp
, wlen
))
255 memmove(buf
, t
, endp
- t
);
256 endp
= buf
+ (endp
- t
);
261 /* Wait up 1/10 of a second to make sure that we got it all. */
264 switch (select(sc
->sh_master
+ 1, &fdset
, NULL
, NULL
, &tv
)) {
265 case -1: /* Error or interrupt. */
266 msgq(sp
, M_SYSERR
, "select");
268 case 0: /* Timeout */
270 case 1: /* Characters to read. */
274 /* Timed out, so theoretically we have a prompt. */
278 /* Append the line into the file. */
279 if (CHAR2INT5(sp
, exp
->ibcw
, buf
, llen
, wp
, wlen
))
281 if (db_last(sp
, &lline
) || db_append(sp
, 0, lline
, wp
, wlen
)) {
283 conv_err
: msgq(sp
, M_ERR
, "323|Invalid input. Truncated.");
284 prompterr
: sscr_end(sp
);
288 return (sscr_setprompt(sp
, buf
, llen
));
293 * Take a line and hand it off to the shell.
295 * PUBLIC: int sscr_exec(SCR *, recno_t);
298 sscr_exec(SCR
*sp
, recno_t lno
)
302 size_t blen
, len
, last_len
, tlen
;
303 int isempty
, matchprompt
, nw
, rval
;
308 /* If there's a prompt on the last line, append the command. */
309 if (db_last(sp
, &last_lno
))
311 if (db_get(sp
, last_lno
, DBG_FATAL
, &wp
, &wlen
))
313 INT2CHAR(sp
, wp
, wlen
, p
, last_len
);
314 if (sscr_matchprompt(sp
, p
, last_len
, &tlen
) && tlen
== 0) {
316 GET_SPACE_RETC(sp
, bp
, blen
, last_len
+ 128);
317 memmove(bp
, p
, last_len
);
321 /* Get something to execute. */
322 if (db_eget(sp
, lno
, &wp
, &wlen
, &isempty
)) {
328 /* Empty lines aren't interesting. */
331 INT2CHAR(sp
, wp
, wlen
, p
, len
);
333 /* Delete any prompt. */
334 if (sscr_matchprompt(sp
, p
, len
, &tlen
)) {
336 empty
: msgq(sp
, M_BERR
, "151|No command to execute");
343 /* Push the line to the shell. */
345 if ((nw
= write(sc
->sh_master
, p
, len
)) != len
)
348 if (write(sc
->sh_master
, "\n", 1) != 1) {
351 msgq(sp
, M_SYSERR
, "shell");
356 ADD_SPACE_RETC(sp
, bp
, blen
, last_len
+ len
);
357 memmove(bp
+ last_len
, p
, len
);
358 CHAR2INT(sp
, bp
, last_len
+ len
, wp
, wlen
);
359 if (db_set(sp
, last_lno
, wp
, wlen
))
363 FREE_SPACE(sp
, bp
, blen
);
369 * Read any waiting shell input.
371 * PUBLIC: int sscr_input(SCR *);
388 /* Set up the input mask. */
389 TAILQ_FOREACH(sp
, gp
->dq
, q
)
390 if (F_ISSET(sp
, SC_SCRIPT
)) {
391 FD_SET(sp
->script
->sh_master
, &rdfd
);
392 if (sp
->script
->sh_master
> maxfd
)
393 maxfd
= sp
->script
->sh_master
;
396 /* Check for input. */
397 switch (select(maxfd
+ 1, &rdfd
, NULL
, NULL
, &poll
)) {
399 msgq(sp
, M_SYSERR
, "select");
407 /* Read the input. */
408 TAILQ_FOREACH(sp
, gp
->dq
, q
)
409 if (F_ISSET(sp
, SC_SCRIPT
) &&
410 FD_ISSET(sp
->script
->sh_master
, &rdfd
) &&
418 * Take a line from the shell and insert it into the file.
429 size_t blen
, len
, tlen
;
438 /* Find out where the end of the file is. */
439 if (db_last(sp
, &lno
))
443 GET_SPACE_RETC(sp
, bp
, blen
, MINREAD
);
446 /* Read the characters. */
449 more
: switch (nr
= read(sc
->sh_master
, endp
, MINREAD
)) {
450 case 0: /* EOF; shell just exited. */
454 case -1: /* Error or interrupt. */
455 msgq(sp
, M_SYSERR
, "shell");
462 /* Append the lines into the file. */
463 for (p
= t
= bp
; p
< endp
; ++p
) {
464 if (*p
== '\r' || *p
== '\n') {
466 if (CHAR2INT5(sp
, exp
->ibcw
, t
, len
, wp
, wlen
))
468 if (db_append(sp
, 1, lno
++, wp
, wlen
))
476 * If the last thing from the shell isn't another prompt, wait
477 * up to 1/10 of a second for more stuff to show up, so that
478 * we don't break the output into two separate lines. Don't
479 * want to hang indefinitely because some program is hanging,
480 * confused the shell, or whatever.
482 if (!sscr_matchprompt(sp
, t
, len
, &tlen
) || tlen
!= 0) {
486 FD_SET(sc
->sh_master
, &rdfd
);
487 if (select(sc
->sh_master
+ 1,
488 &rdfd
, NULL
, NULL
, &tv
) == 1) {
494 if (sscr_setprompt(sp
, t
, len
))
496 if (CHAR2INT5(sp
, exp
->ibcw
, t
, len
, wp
, wlen
))
498 if (db_append(sp
, 1, lno
++, wp
, wlen
))
502 /* The cursor moves to EOF. */
504 sp
->cno
= wlen
? wlen
- 1 : 0;
505 rval
= vs_refresh(sp
, 1);
508 conv_err
: msgq(sp
, M_ERR
, "323|Invalid input. Truncated.");
510 ret
: FREE_SPACE(sp
, bp
, blen
);
517 * Set the prompt to the last line we got from the shell.
521 sscr_setprompt(SCR
*sp
, char *buf
, size_t len
)
528 MALLOC(sp
, sc
->sh_prompt
, char *, len
+ 1);
529 if (sc
->sh_prompt
== NULL
) {
533 memmove(sc
->sh_prompt
, buf
, len
);
534 sc
->sh_prompt_len
= len
;
535 sc
->sh_prompt
[len
] = '\0';
540 * sscr_matchprompt --
541 * Check to see if a line matches the prompt. Nul's indicate
542 * parts that can change, in both content and size.
545 sscr_matchprompt(SCR
*sp
, char *lp
, size_t line_len
, size_t *lenp
)
552 if (line_len
< (prompt_len
= sc
->sh_prompt_len
))
555 for (pp
= sc
->sh_prompt
;
556 prompt_len
&& line_len
; --prompt_len
, --line_len
) {
558 for (; prompt_len
&& *pp
== '\0'; --prompt_len
, ++pp
);
561 for (; line_len
&& *lp
!= *pp
; --line_len
, ++lp
);
578 * End the pipe to a shell.
580 * PUBLIC: int sscr_end(SCR *);
587 if ((sc
= sp
->script
) == NULL
)
590 /* Turn off the script flags. */
591 F_CLR(sp
, SC_SCRIPT
);
594 /* Close down the parent's file descriptors. */
595 if (sc
->sh_master
!= -1)
596 (void)close(sc
->sh_master
);
597 if (sc
->sh_slave
!= -1)
598 (void)close(sc
->sh_slave
);
600 /* This should have killed the child. */
601 (void)proc_wait(sp
, (long)sc
->sh_pid
, "script-shell", 0, 0);
613 * Set/clear the global scripting bit.
621 TAILQ_FOREACH(sp
, gp
->dq
, q
)
622 if (F_ISSET(sp
, SC_SCRIPT
)) {