1 /* $NetBSD: ex_script.c,v 1.9 2018/08/07 11:41:23 rin Exp $ */
2 /*-
3 * Copyright (c) 1992, 1993, 1994
4 * The Regents of the University of California. All rights reserved.
5 * Copyright (c) 1992, 1993, 1994, 1995, 1996
6 * Keith Bostic. All rights reserved.
7 *
8 * This code is derived from software contributed to Berkeley by
9 * Brian Hirt.
10 *
11 * See the LICENSE file for redistribution information.
12 */
13
14 #include "config.h"
15
16 #include <sys/cdefs.h>
17 #if 0
18 #ifndef lint
19 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 ";
20 #endif /* not lint */
21 #else
22 __RCSID("$NetBSD: ex_script.c,v 1.9 2018/08/07 11:41:23 rin Exp $");
23 #endif
24
25 #include <sys/types.h>
26 #include <sys/ioctl.h>
27 #include <sys/queue.h>
28 #ifdef HAVE_SYS_SELECT_H
29 #include <sys/select.h>
30 #endif
31 #include <sys/stat.h>
32 #if defined(HAVE_SYS5_PTY)
33 #include <sys/stropts.h>
34 #endif
35 #include <sys/time.h>
36 #include <sys/wait.h>
37
38 #include <bitstring.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <stdio.h> /* XXX: OSF/1 bug: include before <grp.h> */
42 #include <grp.h>
43 #include <limits.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <termios.h>
47 #include <unistd.h>
48 #ifdef HAVE_UTIL_H
49 #include <util.h>
50 #endif
51
52 #include "../common/common.h"
53 #include "../vi/vi.h"
54 #include "script.h"
55 #include "pathnames.h"
56
57 static void sscr_check __P((SCR *));
58 static int sscr_getprompt __P((SCR *));
59 static int sscr_init __P((SCR *));
60 static int sscr_insert __P((SCR *));
61 #ifdef HAVE_OPENPTY
62 #define sscr_pty openpty
63 #else
64 static int sscr_pty __P((int *, int *, char *, struct termios *, void *));
65 #endif
66 static int sscr_setprompt __P((SCR *, char *, size_t));
67
68 /*
69 * ex_script -- : sc[ript][!] [file]
70 * Switch to script mode.
71 *
72 * PUBLIC: int ex_script __P((SCR *, EXCMD *));
73 */
74 int
ex_script(SCR * sp,EXCMD * cmdp)75 ex_script(SCR *sp, EXCMD *cmdp)
76 {
77 /* Vi only command. */
78 if (!F_ISSET(sp, SC_VI)) {
79 msgq(sp, M_ERR,
80 "150|The script command is only available in vi mode");
81 return (1);
82 }
83
84 /* Avoid double run. */
85 if (F_ISSET(sp, SC_SCRIPT)) {
86 msgq(sp, M_ERR,
87 "The script command is already runninng");
88 return (1);
89 }
90
91 /* We're going to need a shell. */
92 if (opts_empty(sp, O_SHELL, 0))
93 return (1);
94
95 /* Switch to the new file. */
96 if (cmdp->argc != 0 && ex_edit(sp, cmdp))
97 return (1);
98
99 /* Create the shell, figure out the prompt. */
100 if (sscr_init(sp))
101 return (1);
102
103 return (0);
104 }
105
106 /*
107 * sscr_init --
108 * Create a pty setup for a shell.
109 */
110 static int
sscr_init(SCR * sp)111 sscr_init(SCR *sp)
112 {
113 SCRIPT *sc;
114 const char *sh, *sh_path;
115
116 MALLOC_RET(sp, sc, SCRIPT *, sizeof(SCRIPT));
117 sp->script = sc;
118 sc->sh_prompt = NULL;
119 sc->sh_prompt_len = 0;
120
121 /*
122 * There are two different processes running through this code.
123 * They are the shell and the parent.
124 */
125 sc->sh_master = sc->sh_slave = -1;
126
127 if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) {
128 msgq(sp, M_SYSERR, "tcgetattr");
129 goto err;
130 }
131
132 /*
133 * Turn off output postprocessing and echo.
134 */
135 sc->sh_term.c_oflag &= ~OPOST;
136 sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK);
137
138 #ifdef TIOCGWINSZ
139 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) {
140 msgq(sp, M_SYSERR, "tcgetattr");
141 goto err;
142 }
143
144 if (sscr_pty(&sc->sh_master,
145 &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) {
146 msgq(sp, M_SYSERR, "pty");
147 goto err;
148 }
149 #else
150 if (sscr_pty(&sc->sh_master,
151 &sc->sh_slave, sc->sh_name, &sc->sh_term, NULL) == -1) {
152 msgq(sp, M_SYSERR, "pty");
153 goto err;
154 }
155 #endif
156
157 /*
158 * __TK__ huh?
159 * Don't use vfork() here, because the signal semantics differ from
160 * implementation to implementation.
161 */
162 switch (sc->sh_pid = fork()) {
163 case -1: /* Error. */
164 msgq(sp, M_SYSERR, "fork");
165 err: if (sc->sh_master != -1)
166 (void)close(sc->sh_master);
167 if (sc->sh_slave != -1)
168 (void)close(sc->sh_slave);
169 return (1);
170 case 0: /* Utility. */
171 /*
172 * XXX
173 * So that shells that do command line editing turn it off.
174 */
175 (void)setenv("TERM", "emacs", 1);
176 (void)setenv("TERMCAP", "emacs:", 1);
177 (void)setenv("EMACS", "t", 1);
178
179 (void)setsid();
180 #ifdef TIOCSCTTY
181 /*
182 * 4.4BSD allocates a controlling terminal using the TIOCSCTTY
183 * ioctl, not by opening a terminal device file. POSIX 1003.1
184 * doesn't define a portable way to do this. If TIOCSCTTY is
185 * not available, hope that the open does it.
186 */
187 (void)ioctl(sc->sh_slave, TIOCSCTTY, 0);
188 #endif
189 (void)close(sc->sh_master);
190 (void)dup2(sc->sh_slave, STDIN_FILENO);
191 (void)dup2(sc->sh_slave, STDOUT_FILENO);
192 (void)dup2(sc->sh_slave, STDERR_FILENO);
193 (void)close(sc->sh_slave);
194
195 /* Assumes that all shells have -i. */
196 sh_path = O_STR(sp, O_SHELL);
197 if ((sh = strrchr(sh_path, '/')) == NULL)
198 sh = sh_path;
199 else
200 ++sh;
201 execl(sh_path, sh, "-i", NULL);
202 msgq_str(sp, M_SYSERR, sh_path, "execl: %s");
203 _exit(127);
204 default: /* Parent. */
205 break;
206 }
207
208 if (sscr_getprompt(sp))
209 return (1);
210
211 F_SET(sp, SC_SCRIPT);
212 F_SET(sp->gp, G_SCRWIN);
213 return (0);
214 }
215
216 /*
217 * sscr_getprompt --
218 * Eat lines printed by the shell until a line with no trailing
219 * carriage return comes; set the prompt from that line.
220 */
221 static int
sscr_getprompt(SCR * sp)222 sscr_getprompt(SCR *sp)
223 {
224 struct timeval tv;
225 fd_set fdset;
226 int master;
227
228 /* Wait up to a second for characters to read. */
229 tv.tv_sec = 5;
230 tv.tv_usec = 0;
231 master = sp->script->sh_master;
232 FD_ZERO(&fdset);
233 FD_SET(master, &fdset);
234 switch (select(master + 1, &fdset, NULL, NULL, &tv)) {
235 case -1: /* Error or interrupt. */
236 msgq(sp, M_SYSERR, "select");
237 break;
238 case 0: /* Timeout */
239 msgq(sp, M_ERR, "Error: timed out");
240 break;
241 case 1: /* Characters to read. */
242 return (sscr_insert(sp) || sp->script == NULL);
243 }
244
245 sscr_end(sp);
246 return (1);
247 }
248
249 /*
250 * sscr_exec --
251 * Take a line and hand it off to the shell.
252 *
253 * PUBLIC: int sscr_exec __P((SCR *, db_recno_t));
254 */
255 int
sscr_exec(SCR * sp,db_recno_t lno)256 sscr_exec(SCR *sp, db_recno_t lno)
257 {
258 SCRIPT *sc;
259 db_recno_t last_lno;
260 size_t blen, len, last_len;
261 int isempty, matchprompt, rval;
262 ssize_t nw;
263 char *bp = NULL;
264 const char *p;
265 const CHAR_T *wp;
266 size_t wlen;
267
268 sc = sp->script;
269
270 /* If there's a prompt on the last line, append the command. */
271 if (db_last(sp, &last_lno))
272 return (1);
273 if (db_get(sp, last_lno, DBG_FATAL, __UNCONST(&wp), &wlen))
274 return (1);
275 INT2CHAR(sp, wp, wlen, p, last_len);
276 if (last_len == sc->sh_prompt_len &&
277 memcmp(p, sc->sh_prompt, last_len) == 0) {
278 matchprompt = 1;
279 GET_SPACE_RETC(sp, bp, blen, last_len + 128);
280 memmove(bp, p, last_len);
281 } else
282 matchprompt = 0;
283
284 /* Get something to execute. */
285 if (db_eget(sp, lno, __UNCONST(&wp), &wlen, &isempty)) {
286 if (isempty)
287 goto empty;
288 goto err1;
289 }
290
291 /* Empty lines aren't interesting. */
292 if (wlen == 0)
293 goto empty;
294 INT2CHAR(sp, wp, wlen, p, len);
295
296 /* Delete any prompt. */
297 if (len >= sc->sh_prompt_len &&
298 memcmp(p, sc->sh_prompt, sc->sh_prompt_len) == 0) {
299 len -= sc->sh_prompt_len;
300 if (len == 0) {
301 empty: msgq(sp, M_BERR, "151|No command to execute");
302 goto err1;
303 }
304 p += sc->sh_prompt_len;
305 }
306
307 /* Push the line to the shell. */
308 if ((size_t)(nw = write(sc->sh_master, p, len)) != len)
309 goto err2;
310 rval = 0;
311 if (write(sc->sh_master, "\n", 1) != 1) {
312 err2: if (nw == 0)
313 errno = EIO;
314 msgq(sp, M_SYSERR, "shell");
315 goto err1;
316 }
317
318 if (matchprompt) {
319 ADD_SPACE_GOTO(sp, char, bp, blen, last_len + len);
320 memmove(bp + last_len, p, len);
321 CHAR2INT(sp, bp, last_len + len, wp, wlen);
322 if (db_set(sp, last_lno, wp, wlen))
323 err1: rval = 1;
324 }
325 if (matchprompt)
326 alloc_err: FREE_SPACE(sp, bp, blen);
327 return (rval);
328 }
329
330 /*
331 * sscr_check_input -
332 * Check whether any input from shell or passed set.
333 *
334 * PUBLIC: int sscr_check_input __P((SCR *sp, fd_set *rdfd, int maxfd));
335 */
336 int
sscr_check_input(SCR * sp,fd_set * fdset,int maxfd)337 sscr_check_input(SCR *sp, fd_set *fdset, int maxfd)
338 {
339 fd_set rdfd;
340 SCR *tsp;
341 WIN *wp;
342
343 wp = sp->wp;
344
345 loop: memcpy(&rdfd, fdset, sizeof(fd_set));
346
347 TAILQ_FOREACH(tsp, &wp->scrq, q)
348 if (F_ISSET(sp, SC_SCRIPT)) {
349 FD_SET(sp->script->sh_master, &rdfd);
350 if (sp->script->sh_master > maxfd)
351 maxfd = sp->script->sh_master;
352 }
353 switch (select(maxfd + 1, &rdfd, NULL, NULL, NULL)) {
354 case 0:
355 abort();
356 case -1:
357 return 1;
358 default:
359 break;
360 }
361 TAILQ_FOREACH(tsp, &wp->scrq, q)
362 if (F_ISSET(sp, SC_SCRIPT) &&
363 FD_ISSET(sp->script->sh_master, &rdfd)) {
364 if (sscr_input(sp))
365 return 1;
366 goto loop;
367 }
368 return 0;
369 }
370
371 /*
372 * sscr_input --
373 * Read any waiting shell input.
374 *
375 * PUBLIC: int sscr_input __P((SCR *));
376 */
377 int
sscr_input(SCR * sp)378 sscr_input(SCR *sp)
379 {
380 WIN *wp;
381 struct timeval poll;
382 fd_set rdfd;
383 int maxfd;
384
385 wp = sp->wp;
386
387 loop: maxfd = 0;
388 FD_ZERO(&rdfd);
389 poll.tv_sec = 0;
390 poll.tv_usec = 0;
391
392 /* Set up the input mask. */
393 TAILQ_FOREACH(sp, &wp->scrq, q)
394 if (F_ISSET(sp, SC_SCRIPT)) {
395 FD_SET(sp->script->sh_master, &rdfd);
396 if (sp->script->sh_master > maxfd)
397 maxfd = sp->script->sh_master;
398 }
399
400 /* Check for input. */
401 switch (select(maxfd + 1, &rdfd, NULL, NULL, &poll)) {
402 case -1:
403 msgq(sp, M_SYSERR, "select");
404 return (1);
405 case 0:
406 return (0);
407 default:
408 break;
409 }
410
411 /* Read the input. */
412 TAILQ_FOREACH(sp, &wp->scrq, q)
413 if (F_ISSET(sp, SC_SCRIPT) &&
414 FD_ISSET(sp->script->sh_master, &rdfd) &&
415 sscr_insert(sp))
416 return (1);
417 goto loop;
418 }
419
420 /*
421 * sscr_insert --
422 * Take a line from the shell and insert it into the file.
423 */
424 static int
sscr_insert(SCR * sp)425 sscr_insert(SCR *sp)
426 {
427 struct timeval tv;
428 char *endp, *p, *t;
429 SCRIPT *sc;
430 fd_set rdfd;
431 db_recno_t lno;
432 size_t len;
433 ssize_t nr;
434 char bp[1024];
435 const CHAR_T *wp;
436 size_t wlen = 0;
437
438 /* Find out where the end of the file is. */
439 if (db_last(sp, &lno))
440 return (1);
441
442 endp = bp;
443
444 /* Read the characters. */
445 sc = sp->script;
446 more: switch (nr = read(sc->sh_master, endp, bp + sizeof(bp) - endp)) {
447 case 0: /* EOF; shell just exited. */
448 sscr_end(sp);
449 return (0);
450 case -1: /* Error or interrupt. */
451 msgq(sp, M_SYSERR, "shell");
452 return (1);
453 default:
454 endp += nr;
455 break;
456 }
457
458 /* Append the lines into the file. */
459 for (p = t = bp; p < endp; ++p) {
460 if (*p == '\r' || *p == '\n') {
461 len = p - t;
462 if (CHAR2INT(sp, t, len, wp, wlen) ||
463 db_append(sp, 1, lno++, wp, wlen))
464 return (1);
465 t = p + 1;
466 }
467 }
468 /*
469 * If the last thing from the shell isn't another prompt, wait up to
470 * 1/10 of a second for more stuff to show up, so that we don't break
471 * the output into two separate lines. Don't want to hang indefinitely
472 * because some program is hanging, confused the shell, or whatever.
473 * Note that sc->sh_prompt can be NULL here.
474 */
475 len = p - t;
476 if (sc->sh_prompt == NULL || len != sc->sh_prompt_len ||
477 memcmp(t, sc->sh_prompt, len) != 0) {
478 tv.tv_sec = 0;
479 tv.tv_usec = 100000;
480 FD_ZERO(&rdfd);
481 FD_SET(sc->sh_master, &rdfd);
482 if (select(sc->sh_master + 1, &rdfd, NULL, NULL, &tv) == 1) {
483 if (len == sizeof(bp)) {
484 if (CHAR2INT(sp, t, len, wp, wlen) ||
485 db_append(sp, 1, lno++, wp, wlen))
486 return (1);
487 endp = bp;
488 } else {
489 memmove(bp, t, len);
490 endp = bp + len;
491 }
492 goto more;
493 }
494 if (sscr_setprompt(sp, t, len))
495 return (1);
496 }
497
498 /* Append the remains into the file, and the cursor moves to EOF. */
499 if (len > 0) {
500 if (CHAR2INT(sp, t, len, wp, wlen) ||
501 db_append(sp, 1, lno++, wp, wlen))
502 return (1);
503 sp->cno = wlen - 1;
504 } else
505 sp->cno = 0;
506 sp->lno = lno;
507 return (vs_refresh(sp, 1));
508 }
509
510 /*
511 * sscr_setprompt --
512 *
513 * Set the prompt in external ("char") encoding.
514 *
515 */
516 static int
sscr_setprompt(SCR * sp,char * buf,size_t len)517 sscr_setprompt(SCR *sp, char *buf, size_t len)
518 {
519 SCRIPT *sc;
520
521 sc = sp->script;
522 if (sc->sh_prompt)
523 free(sc->sh_prompt);
524 MALLOC(sp, sc->sh_prompt, char *, len + 1);
525 if (sc->sh_prompt == NULL) {
526 sscr_end(sp);
527 return (1);
528 }
529 memmove(sc->sh_prompt, buf, len);
530 sc->sh_prompt_len = len;
531 sc->sh_prompt[len] = '\0';
532 return (0);
533 }
534
535 /*
536 * sscr_end --
537 * End the pipe to a shell.
538 *
539 * PUBLIC: int sscr_end __P((SCR *));
540 */
541 int
sscr_end(SCR * sp)542 sscr_end(SCR *sp)
543 {
544 SCRIPT *sc;
545
546 if ((sc = sp->script) == NULL)
547 return (0);
548
549 /* Turn off the script flags. */
550 F_CLR(sp, SC_SCRIPT);
551 sscr_check(sp);
552
553 /* Close down the parent's file descriptors. */
554 if (sc->sh_master != -1)
555 (void)close(sc->sh_master);
556 if (sc->sh_slave != -1)
557 (void)close(sc->sh_slave);
558
559 /* This should have killed the child. */
560 (void)proc_wait(sp, (long)sc->sh_pid, "script-shell", 0, 0);
561
562 /* Free memory. */
563 free(sc->sh_prompt);
564 free(sc);
565 sp->script = NULL;
566
567 return (0);
568 }
569
570 /*
571 * sscr_check --
572 * Set/clear the global scripting bit.
573 */
574 static void
sscr_check(SCR * sp)575 sscr_check(SCR *sp)
576 {
577 GS *gp;
578 WIN *wp;
579
580 gp = sp->gp;
581 wp = sp->wp;
582 TAILQ_FOREACH(sp, &wp->scrq, q)
583 if (F_ISSET(sp, SC_SCRIPT)) {
584 F_SET(gp, G_SCRWIN);
585 return;
586 }
587 F_CLR(gp, G_SCRWIN);
588 }
589
590 #ifndef HAVE_OPENPTY
591 #ifdef HAVE_SYS5_PTY
592 static int ptys_open __P((int, char *));
593 static int ptym_open __P((char *));
594
595 static int
sscr_pty(int * amaster,int * aslave,char * name,struct termios * termp,void * winp)596 sscr_pty(int *amaster, int *aslave, char *name, struct termios *termp, void *winp)
597 {
598 int master, slave;
599
600 /* open master terminal */
601 if ((master = ptym_open(name)) < 0) {
602 errno = ENOENT; /* out of ptys */
603 return (-1);
604 }
605
606 /* open slave terminal */
607 if ((slave = ptys_open(master, name)) >= 0) {
608 *amaster = master;
609 *aslave = slave;
610 } else {
611 errno = ENOENT; /* out of ptys */
612 return (-1);
613 }
614
615 if (termp)
616 (void) tcsetattr(slave, TCSAFLUSH, termp);
617 #ifdef TIOCSWINSZ
618 if (winp != NULL)
619 (void) ioctl(slave, TIOCSWINSZ, (struct winsize *)winp);
620 #endif
621 return (0);
622 }
623
624 /*
625 * ptym_open --
626 * This function opens a master pty and returns the file descriptor
627 * to it. pts_name is also returned which is the name of the slave.
628 */
629 static int
ptym_open(char * pts_name)630 ptym_open(char *pts_name)
631 {
632 int fdm;
633 char *ptr;
634
635 strcpy(pts_name, _PATH_SYSV_PTY);
636 if ((fdm = open(pts_name, O_RDWR)) < 0 )
637 return (-1);
638
639 if (grantpt(fdm) < 0) {
640 close(fdm);
641 return (-2);
642 }
643
644 if (unlockpt(fdm) < 0) {
645 close(fdm);
646 return (-3);
647 }
648
649 if (unlockpt(fdm) < 0) {
650 close(fdm);
651 return (-3);
652 }
653
654 /* get slave's name */
655 if ((ptr = ptsname(fdm)) == NULL) {
656 close(fdm);
657 return (-3);
658 }
659 strcpy(pts_name, ptr);
660 return (fdm);
661 }
662
663 /*
664 * ptys_open --
665 * This function opens the slave pty.
666 */
667 static int
ptys_open(int fdm,char * pts_name)668 ptys_open(int fdm, char *pts_name)
669 {
670 int fds;
671
672 if ((fds = open(pts_name, O_RDWR)) < 0) {
673 close(fdm);
674 return (-5);
675 }
676
677 #ifdef I_PUSH
678 if (ioctl(fds, I_PUSH, "ptem") < 0) {
679 close(fds);
680 close(fdm);
681 return (-6);
682 }
683
684 if (ioctl(fds, I_PUSH, "ldterm") < 0) {
685 close(fds);
686 close(fdm);
687 return (-7);
688 }
689
690 if (ioctl(fds, I_PUSH, "ttcompat") < 0) {
691 close(fds);
692 close(fdm);
693 return (-8);
694 }
695 #endif /* I_PUSH */
696
697 return (fds);
698 }
699
700 #else /* !HAVE_SYS5_PTY */
701
702 static int
sscr_pty(amaster,aslave,name,termp,winp)703 sscr_pty(amaster, aslave, name, termp, winp)
704 int *amaster, *aslave;
705 char *name;
706 struct termios *termp;
707 void *winp;
708 {
709 static char line[] = "/dev/ptyXX";
710 const char *cp1, *cp2;
711 int master, slave, ttygid;
712 struct group *gr;
713
714 if ((gr = getgrnam("tty")) != NULL)
715 ttygid = gr->gr_gid;
716 else
717 ttygid = -1;
718
719 for (cp1 = "pqrs"; *cp1; cp1++) {
720 line[8] = *cp1;
721 for (cp2 = "0123456789abcdef"; *cp2; cp2++) {
722 line[5] = 'p';
723 line[9] = *cp2;
724 if ((master = open(line, O_RDWR, 0)) == -1) {
725 if (errno == ENOENT)
726 return (-1); /* out of ptys */
727 } else {
728 line[5] = 't';
729 (void) chown(line, getuid(), ttygid);
730 (void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP);
731 #ifdef HAVE_REVOKE
732 (void) revoke(line);
733 #endif
734 if ((slave = open(line, O_RDWR, 0)) != -1) {
735 *amaster = master;
736 *aslave = slave;
737 if (name)
738 strcpy(name, line);
739 if (termp)
740 (void) tcsetattr(slave,
741 TCSAFLUSH, termp);
742 #ifdef TIOCSWINSZ
743 if (winp)
744 (void) ioctl(slave, TIOCSWINSZ,
745 (char *)winp);
746 #endif
747 return (0);
748 }
749 (void) close(master);
750 }
751 }
752 }
753 errno = ENOENT; /* out of ptys */
754 return (-1);
755 }
756
757 #endif /* HAVE_SYS5_PTY */
758 #endif /* !HAVE_OPENPTY */
759