]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/nvi/ex/ex_script.c
Update nvi to 2.2.0-05ed8b9
[FreeBSD/FreeBSD.git] / contrib / nvi / ex / ex_script.c
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.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Brian Hirt.
9  *
10  * See the LICENSE file for redistribution information.
11  */
12
13 #include "config.h"
14
15 #include <sys/types.h>
16 #include <sys/ioctl.h>
17 #include <sys/queue.h>
18 #include <sys/select.h>
19 #include <sys/stat.h>
20 #include <sys/wait.h>
21
22 #include <bitstring.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <grp.h>
26 #include <limits.h>
27 #include <signal.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <termios.h>
32 #include <unistd.h>
33 #ifdef HAVE_LIBUTIL_H
34 #include <libutil.h>
35 #elif defined HAVE_PTY_H
36 #include <pty.h>
37 #else
38 #include <util.h>
39 #endif
40
41 #include "../common/common.h"
42 #include "../vi/vi.h"
43 #include "script.h"
44 #include "pathnames.h"
45
46 static void     sscr_check(SCR *);
47 static int      sscr_getprompt(SCR *);
48 static int      sscr_init(SCR *);
49 static int      sscr_insert(SCR *);
50 static int      sscr_matchprompt(SCR *, char *, size_t, size_t *);
51 static int      sscr_setprompt(SCR *, char *, size_t);
52
53 /*
54  * ex_script -- : sc[ript][!] [file]
55  *      Switch to script mode.
56  *
57  * PUBLIC: int ex_script(SCR *, EXCMD *);
58  */
59 int
60 ex_script(SCR *sp, EXCMD *cmdp)
61 {
62         /* Vi only command. */
63         if (!F_ISSET(sp, SC_VI)) {
64                 msgq(sp, M_ERR,
65                     "150|The script command is only available in vi mode");
66                 return (1);
67         }
68
69         /* Switch to the new file. */
70         if (cmdp->argc != 0 && ex_edit(sp, cmdp))
71                 return (1);
72
73         /* Create the shell, figure out the prompt. */
74         if (sscr_init(sp))
75                 return (1);
76
77         return (0);
78 }
79
80 /*
81  * sscr_init --
82  *      Create a pty setup for a shell.
83  */
84 static int
85 sscr_init(SCR *sp)
86 {
87         SCRIPT *sc;
88         char *sh, *sh_path;
89
90         /* We're going to need a shell. */
91         if (opts_empty(sp, O_SHELL, 0))
92                 return (1);
93
94         MALLOC_RET(sp, sc, sizeof(SCRIPT));
95         sp->script = sc;
96         sc->sh_prompt = NULL;
97         sc->sh_prompt_len = 0;
98
99         /*
100          * There are two different processes running through this code.
101          * They are the shell and the parent.
102          */
103         sc->sh_master = sc->sh_slave = -1;
104
105         if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) {
106                 msgq(sp, M_SYSERR, "tcgetattr");
107                 goto err;
108         }
109
110         /*
111          * Turn off output postprocessing and echo.
112          */
113         sc->sh_term.c_oflag &= ~OPOST;
114         sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK);
115
116         if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) {
117                 msgq(sp, M_SYSERR, "tcgetattr");
118                 goto err;
119         }
120
121         if (openpty(&sc->sh_master,
122             &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) {
123                 msgq(sp, M_SYSERR, "openpty");
124                 goto err;
125         }
126
127         /*
128          * __TK__ huh?
129          * Don't use vfork() here, because the signal semantics differ from
130          * implementation to implementation.
131          */
132         switch (sc->sh_pid = fork()) {
133         case -1:                        /* Error. */
134                 msgq(sp, M_SYSERR, "fork");
135 err:            if (sc->sh_master != -1)
136                         (void)close(sc->sh_master);
137                 if (sc->sh_slave != -1)
138                         (void)close(sc->sh_slave);
139                 return (1);
140         case 0:                         /* Utility. */
141                 /*
142                  * XXX
143                  * So that shells that do command line editing turn it off.
144                  */
145                 (void)setenv("TERM", "emacs", 1);
146                 (void)setenv("TERMCAP", "emacs:", 1);
147                 (void)setenv("EMACS", "t", 1);
148
149                 (void)setsid();
150 #ifdef TIOCSCTTY
151                 /*
152                  * 4.4BSD allocates a controlling terminal using the TIOCSCTTY
153                  * ioctl, not by opening a terminal device file.  POSIX 1003.1
154                  * doesn't define a portable way to do this.  If TIOCSCTTY is
155                  * not available, hope that the open does it.
156                  */
157                 (void)ioctl(sc->sh_slave, TIOCSCTTY, 0);
158 #endif
159                 (void)close(sc->sh_master);
160                 (void)dup2(sc->sh_slave, STDIN_FILENO);
161                 (void)dup2(sc->sh_slave, STDOUT_FILENO);
162                 (void)dup2(sc->sh_slave, STDERR_FILENO);
163                 (void)close(sc->sh_slave);
164
165                 /* Assumes that all shells have -i. */
166                 sh_path = O_STR(sp, O_SHELL);
167                 if ((sh = strrchr(sh_path, '/')) == NULL)
168                         sh = sh_path;
169                 else
170                         ++sh;
171                 execl(sh_path, sh, "-i", NULL);
172                 msgq_str(sp, M_SYSERR, sh_path, "execl: %s");
173                 _exit(127);
174         default:                        /* Parent. */
175                 break;
176         }
177
178         if (sscr_getprompt(sp))
179                 return (1);
180
181         F_SET(sp, SC_SCRIPT);
182         F_SET(sp->gp, G_SCRWIN);
183         return (0);
184 }
185
186 /*
187  * sscr_getprompt --
188  *      Eat lines printed by the shell until a line with no trailing
189  *      carriage return comes; set the prompt from that line.
190  */
191 static int
192 sscr_getprompt(SCR *sp)
193 {
194         EX_PRIVATE *exp;
195         struct timeval tv;
196         char *endp, *p, *t, buf[1024];
197         SCRIPT *sc;
198         fd_set fdset;
199         recno_t lline;
200         size_t llen, len;
201         int nr;
202         CHAR_T *wp;
203         size_t wlen;
204
205         exp = EXP(sp);
206
207         FD_ZERO(&fdset);
208         endp = buf;
209         len = sizeof(buf);
210
211         /* Wait up to a second for characters to read. */
212         tv.tv_sec = 5;
213         tv.tv_usec = 0;
214         sc = sp->script;
215         FD_SET(sc->sh_master, &fdset);
216         switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
217         case -1:                /* Error or interrupt. */
218                 msgq(sp, M_SYSERR, "select");
219                 goto prompterr;
220         case  0:                /* Timeout */
221                 msgq(sp, M_ERR, "Error: timed out");
222                 goto prompterr;
223         case  1:                /* Characters to read. */
224                 break;
225         }
226
227         /* Read the characters. */
228 more:   len = sizeof(buf) - (endp - buf);
229         switch (nr = read(sc->sh_master, endp, len)) {
230         case  0:                        /* EOF. */
231                 msgq(sp, M_ERR, "Error: shell: EOF");
232                 goto prompterr;
233         case -1:                        /* Error or interrupt. */
234                 msgq(sp, M_SYSERR, "shell");
235                 goto prompterr;
236         default:
237                 endp += nr;
238                 break;
239         }
240
241         /* If any complete lines, push them into the file. */
242         for (p = t = buf; p < endp; ++p) {
243                 if (*p == '\r' || *p == '\n') {
244                         if (CHAR2INT5(sp, exp->ibcw, t, p - t, wp, wlen))
245                                 goto conv_err;
246                         if (db_last(sp, &lline) ||
247                             db_append(sp, 0, lline, wp, wlen))
248                                 goto prompterr;
249                         t = p + 1;
250                 }
251         }
252         if (p > buf) {
253                 memmove(buf, t, endp - t);
254                 endp = buf + (endp - t);
255         }
256         if (endp == buf)
257                 goto more;
258
259         /* Wait up 1/10 of a second to make sure that we got it all. */
260         tv.tv_sec = 0;
261         tv.tv_usec = 100000;
262         switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
263         case -1:                /* Error or interrupt. */
264                 msgq(sp, M_SYSERR, "select");
265                 goto prompterr;
266         case  0:                /* Timeout */
267                 break;
268         case  1:                /* Characters to read. */
269                 goto more;
270         }
271
272         /* Timed out, so theoretically we have a prompt. */
273         llen = endp - buf;
274         endp = buf;
275
276         /* Append the line into the file. */
277         if (CHAR2INT5(sp, exp->ibcw, buf, llen, wp, wlen))
278                 goto conv_err;
279         if (db_last(sp, &lline) || db_append(sp, 0, lline, wp, wlen)) {
280                 if (0)
281 conv_err:               msgq(sp, M_ERR, "323|Invalid input. Truncated.");
282 prompterr:      sscr_end(sp);
283                 return (1);
284         }
285
286         return (sscr_setprompt(sp, buf, llen));
287 }
288
289 /*
290  * sscr_exec --
291  *      Take a line and hand it off to the shell.
292  *
293  * PUBLIC: int sscr_exec(SCR *, recno_t);
294  */
295 int
296 sscr_exec(SCR *sp, recno_t lno)
297 {
298         SCRIPT *sc;
299         recno_t last_lno;
300         size_t blen, len, last_len, tlen;
301         int isempty, matchprompt, nw, rval;
302         char *bp = NULL, *p;
303         CHAR_T *wp;
304         size_t wlen;
305
306         /* If there's a prompt on the last line, append the command. */
307         if (db_last(sp, &last_lno))
308                 return (1);
309         if (db_get(sp, last_lno, DBG_FATAL, &wp, &wlen))
310                 return (1);
311         INT2CHAR(sp, wp, wlen, p, last_len);
312         if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) {
313                 matchprompt = 1;
314                 GET_SPACE_RETC(sp, bp, blen, last_len + 128);
315                 memmove(bp, p, last_len);
316         } else
317                 matchprompt = 0;
318
319         /* Get something to execute. */
320         if (db_eget(sp, lno, &wp, &wlen, &isempty)) {
321                 if (isempty)
322                         goto empty;
323                 goto err1;
324         }
325
326         /* Empty lines aren't interesting. */
327         if (wlen == 0)
328                 goto empty;
329         INT2CHAR(sp, wp, wlen, p, len);
330
331         /* Delete any prompt. */
332         if (sscr_matchprompt(sp, p, len, &tlen)) {
333                 if (tlen == len) {
334 empty:                  msgq(sp, M_BERR, "151|No command to execute");
335                         goto err1;
336                 }
337                 p += (len - tlen);
338                 len = tlen;
339         }
340
341         /* Push the line to the shell. */
342         sc = sp->script;
343         if ((nw = write(sc->sh_master, p, len)) != len)
344                 goto err2;
345         rval = 0;
346         if (write(sc->sh_master, "\n", 1) != 1) {
347 err2:           if (nw == 0)
348                         errno = EIO;
349                 msgq(sp, M_SYSERR, "shell");
350                 goto err1;
351         }
352
353         if (matchprompt) {
354                 ADD_SPACE_RETC(sp, bp, blen, last_len + len);
355                 memmove(bp + last_len, p, len);
356                 CHAR2INT(sp, bp, last_len + len, wp, wlen);
357                 if (db_set(sp, last_lno, wp, wlen))
358 err1:                   rval = 1;
359         }
360         if (matchprompt)
361                 FREE_SPACE(sp, bp, blen);
362         return (rval);
363 }
364
365 /*
366  * sscr_input --
367  *      Read any waiting shell input.
368  *
369  * PUBLIC: int sscr_input(SCR *);
370  */
371 int
372 sscr_input(SCR *sp)
373 {
374         GS *gp;
375         struct timeval poll;
376         fd_set rdfd;
377         int maxfd;
378
379         gp = sp->gp;
380
381 loop:   maxfd = 0;
382         FD_ZERO(&rdfd);
383         poll.tv_sec = 0;
384         poll.tv_usec = 0;
385
386         /* Set up the input mask. */
387         TAILQ_FOREACH(sp, gp->dq, q)
388                 if (F_ISSET(sp, SC_SCRIPT)) {
389                         FD_SET(sp->script->sh_master, &rdfd);
390                         if (sp->script->sh_master > maxfd)
391                                 maxfd = sp->script->sh_master;
392                 }
393
394         /* Check for input. */
395         switch (select(maxfd + 1, &rdfd, NULL, NULL, &poll)) {
396         case -1:
397                 msgq(sp, M_SYSERR, "select");
398                 return (1);
399         case 0:
400                 return (0);
401         default:
402                 break;
403         }
404
405         /* Read the input. */
406         TAILQ_FOREACH(sp, gp->dq, q)
407                 if (F_ISSET(sp, SC_SCRIPT) &&
408                     FD_ISSET(sp->script->sh_master, &rdfd) &&
409                     sscr_insert(sp))
410                         return (1);
411         goto loop;
412 }
413
414 /*
415  * sscr_insert --
416  *      Take a line from the shell and insert it into the file.
417  */
418 static int
419 sscr_insert(SCR *sp)
420 {
421         EX_PRIVATE *exp;
422         struct timeval tv;
423         char *endp, *p, *t;
424         SCRIPT *sc;
425         fd_set rdfd;
426         recno_t lno;
427         size_t blen, len, tlen;
428         int nr, rval;
429         char *bp;
430         CHAR_T *wp;
431         size_t wlen = 0;
432
433         exp = EXP(sp);
434
435
436         /* Find out where the end of the file is. */
437         if (db_last(sp, &lno))
438                 return (1);
439
440 #define MINREAD 1024
441         GET_SPACE_RETC(sp, bp, blen, MINREAD);
442         endp = bp;
443
444         /* Read the characters. */
445         rval = 1;
446         sc = sp->script;
447 more:   switch (nr = read(sc->sh_master, endp, MINREAD)) {
448         case  0:                        /* EOF; shell just exited. */
449                 sscr_end(sp);
450                 rval = 0;
451                 goto ret;
452         case -1:                        /* Error or interrupt. */
453                 msgq(sp, M_SYSERR, "shell");
454                 goto ret;
455         default:
456                 endp += nr;
457                 break;
458         }
459
460         /* Append the lines into the file. */
461         for (p = t = bp; p < endp; ++p) {
462                 if (*p == '\r' || *p == '\n') {
463                         len = p - t;
464                         if (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen))
465                                 goto conv_err;
466                         if (db_append(sp, 1, lno++, wp, wlen))
467                                 goto ret;
468                         t = p + 1;
469                 }
470         }
471         if (p > t) {
472                 len = p - t;
473                 /*
474                  * If the last thing from the shell isn't another prompt, wait
475                  * up to 1/10 of a second for more stuff to show up, so that
476                  * we don't break the output into two separate lines.  Don't
477                  * want to hang indefinitely because some program is hanging,
478                  * confused the shell, or whatever.
479                  */
480                 if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) {
481                         tv.tv_sec = 0;
482                         tv.tv_usec = 100000;
483                         FD_ZERO(&rdfd);
484                         FD_SET(sc->sh_master, &rdfd);
485                         if (select(sc->sh_master + 1,
486                             &rdfd, NULL, NULL, &tv) == 1) {
487                                 memmove(bp, t, len);
488                                 endp = bp + len;
489                                 goto more;
490                         }
491                 }
492                 if (sscr_setprompt(sp, t, len))
493                         return (1);
494                 if (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen))
495                         goto conv_err;
496                 if (db_append(sp, 1, lno++, wp, wlen))
497                         goto ret;
498         }
499
500         /* The cursor moves to EOF. */
501         sp->lno = lno;
502         sp->cno = wlen ? wlen - 1 : 0;
503         rval = vs_refresh(sp, 1);
504
505         if (0)
506 conv_err:       msgq(sp, M_ERR, "323|Invalid input. Truncated.");
507
508 ret:    FREE_SPACE(sp, bp, blen);
509         return (rval);
510 }
511
512 /*
513  * sscr_setprompt --
514  *
515  * Set the prompt to the last line we got from the shell.
516  *
517  */
518 static int
519 sscr_setprompt(SCR *sp, char *buf, size_t len)
520 {
521         SCRIPT *sc;
522
523         sc = sp->script;
524         free(sc->sh_prompt);
525         MALLOC(sp, sc->sh_prompt, len + 1);
526         if (sc->sh_prompt == NULL) {
527                 sscr_end(sp);
528                 return (1);
529         }
530         memmove(sc->sh_prompt, buf, len);
531         sc->sh_prompt_len = len;
532         sc->sh_prompt[len] = '\0';
533         return (0);
534 }
535
536 /*
537  * sscr_matchprompt --
538  *      Check to see if a line matches the prompt.  Nul's indicate
539  *      parts that can change, in both content and size.
540  */
541 static int
542 sscr_matchprompt(SCR *sp, char *lp, size_t line_len, size_t *lenp)
543 {
544         SCRIPT *sc;
545         size_t prompt_len;
546         char *pp;
547
548         sc = sp->script;
549         if (line_len < (prompt_len = sc->sh_prompt_len))
550                 return (0);
551
552         for (pp = sc->sh_prompt;
553             prompt_len && line_len; --prompt_len, --line_len) {
554                 if (*pp == '\0') {
555                         for (; prompt_len && *pp == '\0'; --prompt_len, ++pp);
556                         if (!prompt_len)
557                                 return (0);
558                         for (; line_len && *lp != *pp; --line_len, ++lp);
559                         if (!line_len)
560                                 return (0);
561                 }
562                 if (*pp++ != *lp++)
563                         break;
564         }
565
566         if (prompt_len)
567                 return (0);
568         if (lenp != NULL)
569                 *lenp = line_len;
570         return (1);
571 }
572
573 /*
574  * sscr_end --
575  *      End the pipe to a shell.
576  *
577  * PUBLIC: int sscr_end(SCR *);
578  */
579 int
580 sscr_end(SCR *sp)
581 {
582         SCRIPT *sc;
583
584         if ((sc = sp->script) == NULL)
585                 return (0);
586
587         /* Turn off the script flags. */
588         F_CLR(sp, SC_SCRIPT);
589         sscr_check(sp);
590
591         /* Close down the parent's file descriptors. */
592         if (sc->sh_master != -1)
593             (void)close(sc->sh_master);
594         if (sc->sh_slave != -1)
595             (void)close(sc->sh_slave);
596
597         /* This should have killed the child. */
598         (void)proc_wait(sp, (long)sc->sh_pid, "script-shell", 0, 0);
599
600         /* Free memory. */
601         free(sc->sh_prompt);
602         free(sc);
603         sp->script = NULL;
604
605         return (0);
606 }
607
608 /*
609  * sscr_check --
610  *      Set/clear the global scripting bit.
611  */
612 static void
613 sscr_check(SCR *sp)
614 {
615         GS *gp;
616
617         gp = sp->gp;
618         TAILQ_FOREACH(sp, gp->dq, q)
619                 if (F_ISSET(sp, SC_SCRIPT)) {
620                         F_SET(gp, G_SCRWIN);
621                         return;
622                 }
623         F_CLR(gp, G_SCRWIN);
624 }