* Copyright (c) 1992, 1993, 1994
* The Regents of the University of California. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
static char sccsid
[] = "@(#)ex_script.c 9.4 (Berkeley) 12/2/94";
int openpty
__P((int *, int *, char *, struct termios
*, struct winsize
*));
int openpty
__P((int *, int *, char *, struct termios
*, NULL
));
static int sscr_getprompt
__P((SCR
*));
static int sscr_init
__P((SCR
*));
static int sscr_matchprompt
__P((SCR
*, char *, size_t, size_t *));
static int sscr_setprompt
__P((SCR
*, char *, size_t));
* ex_script -- : sc[ript][!] [file]
"153|The script command is only available in vi mode");
/* Switch to the new file. */
if (cmdp
->argc
!= 0 && ex_edit(sp
, cmdp
))
/* Create the shell, figure out the prompt. */
* Create a pty setup for a shell.
MALLOC_RET(sp
, sc
, SCRIPT
*, sizeof(SCRIPT
));
* There are two different processes running through this code.
* They are the shell and the parent.
sc
->sh_master
= sc
->sh_slave
= -1;
if (tcgetattr(STDIN_FILENO
, &sc
->sh_term
) == -1) {
msgq(sp
, M_SYSERR
, "tcgetattr");
* Turn off output postprocessing and echo.
sc
->sh_term
.c_oflag
&= ~OPOST
;
sc
->sh_term
.c_cflag
&= ~(ECHO
|ECHOE
|ECHONL
|ECHOK
);
if (ioctl(STDIN_FILENO
, TIOCGWINSZ
, &sc
->sh_win
) == -1) {
msgq(sp
, M_SYSERR
, "tcgetattr");
if (openpty(&sc
->sh_master
,
&sc
->sh_slave
, sc
->sh_name
, &sc
->sh_term
, &sc
->sh_win
) == -1) {
msgq(sp
, M_SYSERR
, "openpty");
if (openpty(&sc
->sh_master
,
&sc
->sh_slave
, sc
->sh_name
, &sc
->sh_term
, NULL
) == -1) {
msgq(sp
, M_SYSERR
, "openpty");
* Don't use vfork() here, because the signal semantics differ from
* implementation to implementation.
switch (sc
->sh_pid
= fork()) {
msgq(sp
, M_SYSERR
, "fork");
err
: if (sc
->sh_master
!= -1)
(void)close(sc
->sh_master
);
(void)close(sc
->sh_slave
);
/* The utility has default signal behavior. */
* So that shells that do command line editing turn it off.
(void)putenv("TERM=emacs");
(void)putenv("TERMCAP=emacs:");
* 4.4BSD allocates a controlling terminal using the TIOCSCTTY
* ioctl, not by opening a terminal device file. POSIX 1003.1
* doesn't define a portable way to do this. If TIOCSCTTY is
* not available, hope that the open does it.
(void)ioctl(sc
->sh_slave
, TIOCSCTTY
, 0);
(void)close(sc
->sh_master
);
(void)dup2(sc
->sh_slave
, STDIN_FILENO
);
(void)dup2(sc
->sh_slave
, STDOUT_FILENO
);
(void)dup2(sc
->sh_slave
, STDERR_FILENO
);
(void)close(sc
->sh_slave
);
/* Assumes that all shells have -i. */
sh_path
= O_STR(sp
, O_SHELL
);
if ((sh
= strrchr(sh_path
, '/')) == NULL
)
execl(sh_path
, sh
, "-i", NULL
);
p
= msg_print(sp
, sh_path
, &nf
);
msgq(sp
, M_SYSERR
, "execl: %s", p
);
* Eat lines printed by the shell until a line with no trailing
* carriage return comes; set the prompt from that line.
CHAR_T
*endp
, *p
, *t
, buf
[1024];
/* Wait up to a second for characters to read. */
FD_SET(sc
->sh_master
, &fdset
);
switch (select(sc
->sh_master
+ 1, &fdset
, NULL
, NULL
, &tv
)) {
case -1: /* Error or interrupt. */
msgq(sp
, M_SYSERR
, "select");
msgq(sp
, M_ERR
, "Error: timed out");
case 1: /* Characters to read. */
/* Read the characters. */
more
: len
= sizeof(buf
) - (endp
- buf
);
switch (nr
= read(sc
->sh_master
, endp
, len
)) {
msgq(sp
, M_ERR
, "Error: shell: EOF");
case -1: /* Error or interrupt. */
msgq(sp
, M_SYSERR
, "shell");
/* If any complete lines, push them into the file. */
for (p
= t
= buf
; p
< endp
; ++p
) {
if (value
== K_CR
|| value
== K_NL
) {
if (file_lline(sp
, &lline
) ||
file_aline(sp
, 0, lline
, t
, p
- t
))
memmove(buf
, t
, endp
- t
);
/* Wait up 1/10 of a second to make sure that we got it all. */
switch (select(sc
->sh_master
+ 1, &fdset
, NULL
, NULL
, &tv
)) {
case -1: /* Error or interrupt. */
msgq(sp
, M_SYSERR
, "select");
case 1: /* Characters to read. */
/* Timed out, so theoretically we have a prompt. */
/* Append the line into the file. */
if (file_lline(sp
, &lline
) ||
file_aline(sp
, 0, lline
, buf
, llen
)) {
return (sscr_setprompt(sp
, buf
, llen
));
* Take a line and hand it off to the shell.
size_t blen
, len
, last_len
, tlen
;
int matchprompt
, nw
, rval
;
/* If there's a prompt on the last line, append the command. */
if (file_lline(sp
, &last_lno
))
if ((p
= file_gline(sp
, last_lno
, &last_len
)) == NULL
) {
GETLINE_ERR(sp
, last_lno
);
if (sscr_matchprompt(sp
, p
, last_len
, &tlen
) && tlen
== 0) {
GET_SPACE_RET(sp
, bp
, blen
, last_len
+ 128);
memmove(bp
, p
, last_len
);
/* Get something to execute. */
if ((p
= file_gline(sp
, lno
, &len
)) == NULL
) {
if (file_lline(sp
, &lno
))
/* Empty lines aren't interesting. */
if (sscr_matchprompt(sp
, p
, len
, &tlen
)) {
empty
: msgq(sp
, M_BERR
, "154|No command to execute");
/* Push the line to the shell. */
if ((nw
= write(sc
->sh_master
, p
, len
)) != len
)
if (write(sc
->sh_master
, "\n", 1) != 1) {
msgq(sp
, M_SYSERR
, "shell");
ADD_SPACE_RET(sp
, bp
, blen
, last_len
+ len
);
memmove(bp
+ last_len
, p
, len
);
if (file_sline(sp
, last_lno
, bp
, last_len
+ len
))
FREE_SPACE(sp
, bp
, blen
);
* Take a line from the shell and insert it into the file.
/* Find out where the end of the file is. */
if (file_lline(sp
, &lno
))
GET_SPACE_RET(sp
, bp
, blen
, MINREAD
);
/* Read the characters. */
more
: switch (nr
= read(sc
->sh_master
, endp
, MINREAD
)) {
case 0: /* EOF; shell just exited. */
case -1: /* Error or interrupt. */
msgq(sp
, M_SYSERR
, "shell");
/* Append the lines into the file. */
for (p
= t
= bp
; p
< endp
; ++p
) {
if (value
== K_CR
|| value
== K_NL
) {
if (file_aline(sp
, 1, lno
++, t
, len
))
* If the last thing from the shell isn't another prompt, wait
* up to 1/10 of a second for more stuff to show up, so that
* we don't break the output into two separate lines. Don't
* want to hang indefinitely because some program is hanging,
* confused the shell, or whatever.
if (!sscr_matchprompt(sp
, t
, len
, &tlen
) || tlen
!= 0) {
FD_SET(sc
->sh_master
, &sp
->rdfd
);
FD_CLR(STDIN_FILENO
, &sp
->rdfd
);
if (select(sc
->sh_master
+ 1,
&sp
->rdfd
, NULL
, NULL
, &tv
) == 1) {
if (sscr_setprompt(sp
, t
, len
))
if (file_aline(sp
, 1, lno
++, t
, len
))
/* The cursor moves to EOF. */
sp
->cno
= len
? len
- 1 : 0;
rval
= sp
->s_refresh(sp
);
ret
: FREE_SPACE(sp
, bp
, blen
);
* Set the prompt to the last line we got from the shell.
sscr_setprompt(sp
, buf
, len
)
FREE(sc
->sh_prompt
, sc
->sh_prompt_len
);
MALLOC(sp
, sc
->sh_prompt
, char *, len
+ 1);
if (sc
->sh_prompt
== NULL
) {
memmove(sc
->sh_prompt
, buf
, len
);
sc
->sh_prompt
[len
] = '\0';
* Check to see if a line matches the prompt. Nul's indicate
* parts that can change, in both content and size.
sscr_matchprompt(sp
, lp
, line_len
, lenp
)
if (line_len
< (prompt_len
= sc
->sh_prompt_len
))
prompt_len
&& line_len
; --prompt_len
, --line_len
) {
for (; prompt_len
&& *pp
== '\0'; --prompt_len
, ++pp
);
for (; line_len
&& *lp
!= *pp
; --line_len
, ++lp
);
* End the pipe to a shell.
if ((sc
= sp
->script
) == NULL
)
/* Turn off the script flag. */
/* Close down the parent's file descriptors. */
(void)close(sc
->sh_master
);
(void)close(sc
->sh_slave
);
/* This should have killed the child. */
rval
= proc_wait(sp
, (long)sc
->sh_pid
, "script-shell", 0);
FREE(sc
->sh_prompt
, sc
->sh_prompt_len
);
FREE(sc
, sizeof(SCRIPT
));