ex: convert line input buffer from file encoding to internal encoding
[nvi.git] / ex / ex_script.c
blob31f42c104568a0b8d0aab1d7042f432974c7ed00
1 /*-
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
8 * Brian Hirt.
10 * See the LICENSE file for redistribution information.
13 #include "config.h"
15 #ifndef lint
16 static const char sccsid[] = "$Id: ex_script.c,v 10.38 2001/06/25 15:19:19 skimo Exp $ (Berkeley) $Date: 2001/06/25 15:19:19 $";
17 #endif /* not lint */
19 #include <sys/types.h>
20 #include <sys/ioctl.h>
21 #include <sys/queue.h>
22 #ifdef HAVE_SYS_SELECT_H
23 #include <sys/select.h>
24 #endif
25 #include <sys/stat.h>
26 #ifdef HAVE_SYS5_PTY
27 #include <sys/stropts.h>
28 #endif
29 #include <sys/time.h>
30 #include <sys/wait.h>
32 #include <bitstring.h>
33 #include <errno.h>
34 #include <fcntl.h>
35 #include <stdio.h> /* XXX: OSF/1 bug: include before <grp.h> */
36 #include <grp.h>
37 #include <limits.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <termios.h>
41 #include <unistd.h>
43 #include "../common/common.h"
44 #include "../vi/vi.h"
45 #include "script.h"
46 #include "pathnames.h"
48 static void sscr_check __P((SCR *));
49 static int sscr_getprompt __P((SCR *));
50 static int sscr_init __P((SCR *));
51 static int sscr_insert __P((SCR *));
52 static int sscr_matchprompt __P((SCR *, CHAR_T *, size_t, size_t *));
53 static int sscr_pty __P((int *, int *, char *, struct termios *, void *));
54 static int sscr_setprompt __P((SCR *, CHAR_T *, size_t));
57 * ex_script -- : sc[ript][!] [file]
58 * Switch to script mode.
60 * PUBLIC: int ex_script __P((SCR *, EXCMD *));
62 int
63 ex_script(SCR *sp, EXCMD *cmdp)
65 /* Vi only command. */
66 if (!F_ISSET(sp, SC_VI)) {
67 msgq(sp, M_ERR,
68 "150|The script command is only available in vi mode");
69 return (1);
72 /* Switch to the new file. */
73 if (cmdp->argc != 0 && ex_edit(sp, cmdp))
74 return (1);
76 /* Create the shell, figure out the prompt. */
77 if (sscr_init(sp))
78 return (1);
80 return (0);
84 * sscr_init --
85 * Create a pty setup for a shell.
87 static int
88 sscr_init(SCR *sp)
90 SCRIPT *sc;
91 char *sh, *sh_path;
93 /* We're going to need a shell. */
94 if (opts_empty(sp, O_SHELL, 0))
95 return (1);
97 MALLOC_RET(sp, sc, SCRIPT *, sizeof(SCRIPT));
98 sp->script = sc;
99 sc->sh_prompt = NULL;
100 sc->sh_prompt_len = 0;
103 * There are two different processes running through this code.
104 * They are the shell and the parent.
106 sc->sh_master = sc->sh_slave = -1;
108 if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) {
109 msgq(sp, M_SYSERR, "tcgetattr");
110 goto err;
114 * Turn off output postprocessing and echo.
116 sc->sh_term.c_oflag &= ~OPOST;
117 sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK);
119 #ifdef TIOCGWINSZ
120 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) {
121 msgq(sp, M_SYSERR, "tcgetattr");
122 goto err;
125 if (sscr_pty(&sc->sh_master,
126 &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) {
127 msgq(sp, M_SYSERR, "pty");
128 goto err;
130 #else
131 if (sscr_pty(&sc->sh_master,
132 &sc->sh_slave, sc->sh_name, &sc->sh_term, NULL) == -1) {
133 msgq(sp, M_SYSERR, "pty");
134 goto err;
136 #endif
139 * __TK__ huh?
140 * Don't use vfork() here, because the signal semantics differ from
141 * implementation to implementation.
143 switch (sc->sh_pid = fork()) {
144 case -1: /* Error. */
145 msgq(sp, M_SYSERR, "fork");
146 err: if (sc->sh_master != -1)
147 (void)close(sc->sh_master);
148 if (sc->sh_slave != -1)
149 (void)close(sc->sh_slave);
150 return (1);
151 case 0: /* Utility. */
153 * XXX
154 * So that shells that do command line editing turn it off.
156 (void)setenv("TERM", "emacs", 1);
157 (void)setenv("TERMCAP", "emacs:", 1);
158 (void)setenv("EMACS", "t", 1);
160 (void)setsid();
161 #ifdef TIOCSCTTY
163 * 4.4BSD allocates a controlling terminal using the TIOCSCTTY
164 * ioctl, not by opening a terminal device file. POSIX 1003.1
165 * doesn't define a portable way to do this. If TIOCSCTTY is
166 * not available, hope that the open does it.
168 (void)ioctl(sc->sh_slave, TIOCSCTTY, 0);
169 #endif
170 (void)close(sc->sh_master);
171 (void)dup2(sc->sh_slave, STDIN_FILENO);
172 (void)dup2(sc->sh_slave, STDOUT_FILENO);
173 (void)dup2(sc->sh_slave, STDERR_FILENO);
174 (void)close(sc->sh_slave);
176 /* Assumes that all shells have -i. */
177 sh_path = O_STR(sp, O_SHELL);
178 if ((sh = strrchr(sh_path, '/')) == NULL)
179 sh = sh_path;
180 else
181 ++sh;
182 execl(sh_path, sh, "-i", NULL);
183 msgq_str(sp, M_SYSERR, sh_path, "execl: %s");
184 _exit(127);
185 default: /* Parent. */
186 break;
189 if (sscr_getprompt(sp))
190 return (1);
192 F_SET(sp, SC_SCRIPT);
193 F_SET(sp->gp, G_SCRWIN);
194 return (0);
198 * sscr_getprompt --
199 * Eat lines printed by the shell until a line with no trailing
200 * carriage return comes; set the prompt from that line.
202 static int
203 sscr_getprompt(SCR *sp)
205 struct timeval tv;
206 CHAR_T *endp, *p, *t, buf[1024];
207 SCRIPT *sc;
208 fd_set fdset;
209 db_recno_t lline;
210 size_t llen, len;
211 u_int value;
212 int nr;
214 FD_ZERO(&fdset);
215 endp = buf;
216 len = sizeof(buf);
218 /* Wait up to a second for characters to read. */
219 tv.tv_sec = 5;
220 tv.tv_usec = 0;
221 sc = sp->script;
222 FD_SET(sc->sh_master, &fdset);
223 switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
224 case -1: /* Error or interrupt. */
225 msgq(sp, M_SYSERR, "select");
226 goto prompterr;
227 case 0: /* Timeout */
228 msgq(sp, M_ERR, "Error: timed out");
229 goto prompterr;
230 case 1: /* Characters to read. */
231 break;
234 /* Read the characters. */
235 more: len = sizeof(buf) - (endp - buf);
236 switch (nr = read(sc->sh_master, endp, len)) {
237 case 0: /* EOF. */
238 msgq(sp, M_ERR, "Error: shell: EOF");
239 goto prompterr;
240 case -1: /* Error or interrupt. */
241 msgq(sp, M_SYSERR, "shell");
242 goto prompterr;
243 default:
244 endp += nr;
245 break;
248 /* If any complete lines, push them into the file. */
249 for (p = t = buf; p < endp; ++p) {
250 value = KEY_VAL(sp, *p);
251 if (value == K_CR || value == K_NL) {
252 if (db_last(sp, &lline) ||
253 db_append(sp, 0, lline, t, p - t))
254 goto prompterr;
255 t = p + 1;
258 if (p > buf) {
259 MEMMOVE(buf, t, endp - t);
260 endp = buf + (endp - t);
262 if (endp == buf)
263 goto more;
265 /* Wait up 1/10 of a second to make sure that we got it all. */
266 tv.tv_sec = 0;
267 tv.tv_usec = 100000;
268 switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
269 case -1: /* Error or interrupt. */
270 msgq(sp, M_SYSERR, "select");
271 goto prompterr;
272 case 0: /* Timeout */
273 break;
274 case 1: /* Characters to read. */
275 goto more;
278 /* Timed out, so theoretically we have a prompt. */
279 llen = endp - buf;
280 endp = buf;
282 /* Append the line into the file. */
283 if (db_last(sp, &lline) || db_append(sp, 0, lline, buf, llen)) {
284 prompterr: sscr_end(sp);
285 return (1);
288 return (sscr_setprompt(sp, buf, llen));
292 * sscr_exec --
293 * Take a line and hand it off to the shell.
295 * PUBLIC: int sscr_exec __P((SCR *, db_recno_t));
298 sscr_exec(SCR *sp, db_recno_t lno)
300 SCRIPT *sc;
301 db_recno_t last_lno;
302 size_t blen, len, last_len, tlen;
303 int isempty, matchprompt, nw, rval;
304 CHAR_T *bp;
305 CHAR_T *p;
307 /* If there's a prompt on the last line, append the command. */
308 if (db_last(sp, &last_lno))
309 return (1);
310 if (db_get(sp, last_lno, DBG_FATAL, &p, &last_len))
311 return (1);
312 if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) {
313 matchprompt = 1;
314 GET_SPACE_RETW(sp, bp, blen, last_len + 128);
315 MEMMOVEW(bp, p, last_len);
316 } else
317 matchprompt = 0;
319 /* Get something to execute. */
320 if (db_eget(sp, lno, &p, &len, &isempty)) {
321 if (isempty)
322 goto empty;
323 goto err1;
326 /* Empty lines aren't interesting. */
327 if (len == 0)
328 goto empty;
330 /* Delete any prompt. */
331 if (sscr_matchprompt(sp, p, len, &tlen)) {
332 if (tlen == len) {
333 empty: msgq(sp, M_BERR, "151|No command to execute");
334 goto err1;
336 p += (len - tlen);
337 len = tlen;
340 /* Push the line to the shell. */
341 sc = sp->script;
342 if ((nw = write(sc->sh_master, p, len)) != len)
343 goto err2;
344 rval = 0;
345 if (write(sc->sh_master, "\n", 1) != 1) {
346 err2: if (nw == 0)
347 errno = EIO;
348 msgq(sp, M_SYSERR, "shell");
349 goto err1;
352 if (matchprompt) {
353 ADD_SPACE_RETW(sp, bp, blen, last_len + len);
354 MEMMOVEW(bp + last_len, p, len);
355 if (db_set(sp, last_lno, bp, last_len + len))
356 err1: rval = 1;
358 if (matchprompt)
359 FREE_SPACEW(sp, bp, blen);
360 return (rval);
364 * sscr_check_input -
365 * Check whether any input from shell or passed set.
367 * PUBLIC: int sscr_check_input __P((SCR *sp, fd_set *rdfd, int maxfd));
370 sscr_check_input(SCR *sp, fd_set *fdset, int maxfd)
372 fd_set rdfd;
373 SCR *tsp;
374 WIN *wp;
376 wp = sp->wp;
378 loop: memcpy(&rdfd, fdset, sizeof(fd_set));
380 for (tsp = wp->scrq.cqh_first;
381 tsp != (void *)&wp->scrq; tsp = tsp->q.cqe_next)
382 if (F_ISSET(sp, SC_SCRIPT)) {
383 FD_SET(sp->script->sh_master, &rdfd);
384 if (sp->script->sh_master > maxfd)
385 maxfd = sp->script->sh_master;
387 switch (select(maxfd + 1, &rdfd, NULL, NULL, NULL)) {
388 case 0:
389 abort();
390 case -1:
391 return 1;
392 default:
393 break;
395 for (tsp = wp->scrq.cqh_first;
396 tsp != (void *)&wp->scrq; tsp = tsp->q.cqe_next)
397 if (F_ISSET(sp, SC_SCRIPT) &&
398 FD_ISSET(sp->script->sh_master, &rdfd)) {
399 if (sscr_input(sp))
400 return 1;
401 goto loop;
403 return 0;
407 * sscr_input --
408 * Read any waiting shell input.
410 * PUBLIC: int sscr_input __P((SCR *));
413 sscr_input(SCR *sp)
415 GS *gp;
416 WIN *wp;
417 struct timeval poll;
418 fd_set rdfd;
419 int maxfd;
421 gp = sp->gp;
422 wp = sp->wp;
424 loop: maxfd = 0;
425 FD_ZERO(&rdfd);
426 poll.tv_sec = 0;
427 poll.tv_usec = 0;
429 /* Set up the input mask. */
430 for (sp = wp->scrq.cqh_first; sp != (void *)&wp->scrq;
431 sp = sp->q.cqe_next)
432 if (F_ISSET(sp, SC_SCRIPT)) {
433 FD_SET(sp->script->sh_master, &rdfd);
434 if (sp->script->sh_master > maxfd)
435 maxfd = sp->script->sh_master;
438 /* Check for input. */
439 switch (select(maxfd + 1, &rdfd, NULL, NULL, &poll)) {
440 case -1:
441 msgq(sp, M_SYSERR, "select");
442 return (1);
443 case 0:
444 return (0);
445 default:
446 break;
449 /* Read the input. */
450 for (sp = wp->scrq.cqh_first; sp != (void *)&wp->scrq;
451 sp = sp->q.cqe_next)
452 if (F_ISSET(sp, SC_SCRIPT) &&
453 FD_ISSET(sp->script->sh_master, &rdfd) &&
454 sscr_insert(sp))
455 return (1);
456 goto loop;
460 * sscr_insert --
461 * Take a line from the shell and insert it into the file.
463 static int
464 sscr_insert(SCR *sp)
466 struct timeval tv;
467 CHAR_T *endp, *p, *t;
468 SCRIPT *sc;
469 fd_set rdfd;
470 db_recno_t lno;
471 size_t blen, len, tlen;
472 u_int value;
473 int nr, rval;
474 CHAR_T *bp;
476 /* Find out where the end of the file is. */
477 if (db_last(sp, &lno))
478 return (1);
480 #define MINREAD 1024
481 GET_SPACE_RETW(sp, bp, blen, MINREAD);
482 endp = bp;
484 /* Read the characters. */
485 rval = 1;
486 sc = sp->script;
487 more: switch (nr = read(sc->sh_master, endp, MINREAD)) {
488 case 0: /* EOF; shell just exited. */
489 sscr_end(sp);
490 rval = 0;
491 goto ret;
492 case -1: /* Error or interrupt. */
493 msgq(sp, M_SYSERR, "shell");
494 goto ret;
495 default:
496 endp += nr;
497 break;
500 /* Append the lines into the file. */
501 for (p = t = bp; p < endp; ++p) {
502 value = KEY_VAL(sp, *p);
503 if (value == K_CR || value == K_NL) {
504 len = p - t;
505 if (db_append(sp, 1, lno++, t, len))
506 goto ret;
507 t = p + 1;
510 if (p > t) {
511 len = p - t;
513 * If the last thing from the shell isn't another prompt, wait
514 * up to 1/10 of a second for more stuff to show up, so that
515 * we don't break the output into two separate lines. Don't
516 * want to hang indefinitely because some program is hanging,
517 * confused the shell, or whatever.
519 if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) {
520 tv.tv_sec = 0;
521 tv.tv_usec = 100000;
522 FD_ZERO(&rdfd);
523 FD_SET(sc->sh_master, &rdfd);
524 if (select(sc->sh_master + 1,
525 &rdfd, NULL, NULL, &tv) == 1) {
526 MEMMOVE(bp, t, len);
527 endp = bp + len;
528 goto more;
531 if (sscr_setprompt(sp, t, len))
532 return (1);
533 if (db_append(sp, 1, lno++, t, len))
534 goto ret;
537 /* The cursor moves to EOF. */
538 sp->lno = lno;
539 sp->cno = len ? len - 1 : 0;
540 rval = vs_refresh(sp, 1);
542 ret: FREE_SPACEW(sp, bp, blen);
543 return (rval);
547 * sscr_setprompt --
549 * Set the prompt to the last line we got from the shell.
552 static int
553 sscr_setprompt(SCR *sp, CHAR_T *buf, size_t len)
555 SCRIPT *sc;
556 char *np;
557 size_t nlen;
559 sc = sp->script;
560 if (sc->sh_prompt)
561 free(sc->sh_prompt);
562 MALLOC(sp, sc->sh_prompt, char *, len + 1);
563 if (sc->sh_prompt == NULL) {
564 sscr_end(sp);
565 return (1);
567 INT2CHAR(sp, buf, len, np, nlen);
568 memmove(sc->sh_prompt, np, nlen);
569 sc->sh_prompt_len = len;
570 sc->sh_prompt[len] = '\0';
571 return (0);
575 * sscr_matchprompt --
576 * Check to see if a line matches the prompt. Nul's indicate
577 * parts that can change, in both content and size.
579 static int
580 sscr_matchprompt(SCR *sp, CHAR_T *lp, size_t line_len, size_t *lenp)
582 SCRIPT *sc;
583 size_t prompt_len;
584 char *pp;
586 sc = sp->script;
587 if (line_len < (prompt_len = sc->sh_prompt_len))
588 return (0);
590 for (pp = sc->sh_prompt;
591 prompt_len && line_len; --prompt_len, --line_len) {
592 if (*pp == '\0') {
593 for (; prompt_len && *pp == '\0'; --prompt_len, ++pp);
594 if (!prompt_len)
595 return (0);
596 for (; line_len && *lp != *pp; --line_len, ++lp);
597 if (!line_len)
598 return (0);
600 if (*pp++ != *lp++)
601 break;
604 if (prompt_len)
605 return (0);
606 if (lenp != NULL)
607 *lenp = line_len;
608 return (1);
612 * sscr_end --
613 * End the pipe to a shell.
615 * PUBLIC: int sscr_end __P((SCR *));
618 sscr_end(SCR *sp)
620 SCRIPT *sc;
622 if ((sc = sp->script) == NULL)
623 return (0);
625 /* Turn off the script flags. */
626 F_CLR(sp, SC_SCRIPT);
627 sscr_check(sp);
629 /* Close down the parent's file descriptors. */
630 if (sc->sh_master != -1)
631 (void)close(sc->sh_master);
632 if (sc->sh_slave != -1)
633 (void)close(sc->sh_slave);
635 /* This should have killed the child. */
636 (void)proc_wait(sp, (long)sc->sh_pid, "script-shell", 0, 0);
638 /* Free memory. */
639 free(sc->sh_prompt);
640 free(sc);
641 sp->script = NULL;
643 return (0);
647 * sscr_check --
648 * Set/clear the global scripting bit.
650 static void
651 sscr_check(SCR *sp)
653 GS *gp;
654 WIN *wp;
656 gp = sp->gp;
657 wp = sp->wp;
658 for (sp = wp->scrq.cqh_first; sp != (void *)&wp->scrq;
659 sp = sp->q.cqe_next)
660 if (F_ISSET(sp, SC_SCRIPT)) {
661 F_SET(gp, G_SCRWIN);
662 return;
664 F_CLR(gp, G_SCRWIN);
667 #ifdef HAVE_SYS5_PTY
668 static int ptys_open __P((int, char *));
669 static int ptym_open __P((char *));
671 static int
672 sscr_pty(int *amaster, int *aslave, char *name, struct termios *termp, void *winp)
674 int master, slave, ttygid;
676 /* open master terminal */
677 if ((master = ptym_open(name)) < 0) {
678 errno = ENOENT; /* out of ptys */
679 return (-1);
682 /* open slave terminal */
683 if ((slave = ptys_open(master, name)) >= 0) {
684 *amaster = master;
685 *aslave = slave;
686 } else {
687 errno = ENOENT; /* out of ptys */
688 return (-1);
691 if (termp)
692 (void) tcsetattr(slave, TCSAFLUSH, termp);
693 #ifdef TIOCSWINSZ
694 if (winp != NULL)
695 (void) ioctl(slave, TIOCSWINSZ, (struct winsize *)winp);
696 #endif
697 return (0);
701 * ptym_open --
702 * This function opens a master pty and returns the file descriptor
703 * to it. pts_name is also returned which is the name of the slave.
705 static int
706 ptym_open(char *pts_name)
708 int fdm;
709 char *ptr, *ptsname();
711 strcpy(pts_name, _PATH_SYSV_PTY);
712 if ((fdm = open(pts_name, O_RDWR)) < 0 )
713 return (-1);
715 if (grantpt(fdm) < 0) {
716 close(fdm);
717 return (-2);
720 if (unlockpt(fdm) < 0) {
721 close(fdm);
722 return (-3);
725 if (unlockpt(fdm) < 0) {
726 close(fdm);
727 return (-3);
730 /* get slave's name */
731 if ((ptr = ptsname(fdm)) == NULL) {
732 close(fdm);
733 return (-3);
735 strcpy(pts_name, ptr);
736 return (fdm);
740 * ptys_open --
741 * This function opens the slave pty.
743 static int
744 ptys_open(int fdm, char *pts_name)
746 int fds;
748 if ((fds = open(pts_name, O_RDWR)) < 0) {
749 close(fdm);
750 return (-5);
753 if (ioctl(fds, I_PUSH, "ptem") < 0) {
754 close(fds);
755 close(fdm);
756 return (-6);
759 if (ioctl(fds, I_PUSH, "ldterm") < 0) {
760 close(fds);
761 close(fdm);
762 return (-7);
765 if (ioctl(fds, I_PUSH, "ttcompat") < 0) {
766 close(fds);
767 close(fdm);
768 return (-8);
771 return (fds);
774 #else /* !HAVE_SYS5_PTY */
776 static int
777 sscr_pty(amaster, aslave, name, termp, winp)
778 int *amaster, *aslave;
779 char *name;
780 struct termios *termp;
781 void *winp;
783 static char line[] = "/dev/ptyXX";
784 register char *cp1, *cp2;
785 register int master, slave, ttygid;
786 struct group *gr;
788 if ((gr = getgrnam("tty")) != NULL)
789 ttygid = gr->gr_gid;
790 else
791 ttygid = -1;
793 for (cp1 = "pqrs"; *cp1; cp1++) {
794 line[8] = *cp1;
795 for (cp2 = "0123456789abcdef"; *cp2; cp2++) {
796 line[5] = 'p';
797 line[9] = *cp2;
798 if ((master = open(line, O_RDWR, 0)) == -1) {
799 if (errno == ENOENT)
800 return (-1); /* out of ptys */
801 } else {
802 line[5] = 't';
803 (void) chown(line, getuid(), ttygid);
804 (void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP);
805 #ifdef HAVE_REVOKE
806 (void) revoke(line);
807 #endif
808 if ((slave = open(line, O_RDWR, 0)) != -1) {
809 *amaster = master;
810 *aslave = slave;
811 if (name)
812 strcpy(name, line);
813 if (termp)
814 (void) tcsetattr(slave,
815 TCSAFLUSH, termp);
816 #ifdef TIOCSWINSZ
817 if (winp)
818 (void) ioctl(slave, TIOCSWINSZ,
819 (char *)winp);
820 #endif
821 return (0);
823 (void) close(master);
827 errno = ENOENT; /* out of ptys */
828 return (-1);
830 #endif /* HAVE_SYS5_PTY */