kernel - cleanup vfs_cache debugging
[dragonfly.git] / contrib / nvi2 / ex / ex_script.c
blob2dc44658a62d5fc2cd384e11ea2c7698b19e95f1
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.44 2012/10/05 10:17:47 zy Exp $";
17 #endif /* not lint */
19 #include <sys/types.h>
20 #include <sys/ioctl.h>
21 #include <sys/queue.h>
22 #include <sys/select.h>
23 #include <sys/stat.h>
24 #include <sys/wait.h>
26 #include <bitstring.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <grp.h>
30 #include <limits.h>
31 #include <signal.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <termios.h>
36 #include <unistd.h>
37 #ifdef HAVE_LIBUTIL_H
38 #include <libutil.h>
39 #else
40 #include <util.h>
41 #endif
43 #include "../common/common.h"
44 #include "../vi/vi.h"
45 #include "script.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 *);
61 int
62 ex_script(SCR *sp, EXCMD *cmdp)
64 /* Vi only command. */
65 if (!F_ISSET(sp, SC_VI)) {
66 msgq(sp, M_ERR,
67 "150|The script command is only available in vi mode");
68 return (1);
71 /* Switch to the new file. */
72 if (cmdp->argc != 0 && ex_edit(sp, cmdp))
73 return (1);
75 /* Create the shell, figure out the prompt. */
76 if (sscr_init(sp))
77 return (1);
79 return (0);
83 * sscr_init --
84 * Create a pty setup for a shell.
86 static int
87 sscr_init(SCR *sp)
89 SCRIPT *sc;
90 char *sh, *sh_path;
92 /* We're going to need a shell. */
93 if (opts_empty(sp, O_SHELL, 0))
94 return (1);
96 MALLOC_RET(sp, sc, SCRIPT *, sizeof(SCRIPT));
97 sp->script = sc;
98 sc->sh_prompt = NULL;
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");
109 goto err;
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");
120 goto err;
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");
126 goto err;
130 * __TK__ huh?
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);
141 return (1);
142 case 0: /* Utility. */
144 * XXX
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);
151 (void)setsid();
152 #ifdef TIOCSCTTY
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);
160 #endif
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)
170 sh = sh_path;
171 else
172 ++sh;
173 execl(sh_path, sh, "-i", NULL);
174 msgq_str(sp, M_SYSERR, sh_path, "execl: %s");
175 _exit(127);
176 default: /* Parent. */
177 break;
180 if (sscr_getprompt(sp))
181 return (1);
183 F_SET(sp, SC_SCRIPT);
184 F_SET(sp->gp, G_SCRWIN);
185 return (0);
189 * sscr_getprompt --
190 * Eat lines printed by the shell until a line with no trailing
191 * carriage return comes; set the prompt from that line.
193 static int
194 sscr_getprompt(SCR *sp)
196 EX_PRIVATE *exp;
197 struct timeval tv;
198 char *endp, *p, *t, buf[1024];
199 SCRIPT *sc;
200 fd_set fdset;
201 recno_t lline;
202 size_t llen, len;
203 int nr;
204 CHAR_T *wp;
205 size_t wlen;
207 exp = EXP(sp);
209 FD_ZERO(&fdset);
210 endp = buf;
211 len = sizeof(buf);
213 /* Wait up to a second for characters to read. */
214 tv.tv_sec = 5;
215 tv.tv_usec = 0;
216 sc = sp->script;
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");
221 goto prompterr;
222 case 0: /* Timeout */
223 msgq(sp, M_ERR, "Error: timed out");
224 goto prompterr;
225 case 1: /* Characters to read. */
226 break;
229 /* Read the characters. */
230 more: len = sizeof(buf) - (endp - buf);
231 switch (nr = read(sc->sh_master, endp, len)) {
232 case 0: /* EOF. */
233 msgq(sp, M_ERR, "Error: shell: EOF");
234 goto prompterr;
235 case -1: /* Error or interrupt. */
236 msgq(sp, M_SYSERR, "shell");
237 goto prompterr;
238 default:
239 endp += nr;
240 break;
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))
247 goto conv_err;
248 if (db_last(sp, &lline) ||
249 db_append(sp, 0, lline, wp, wlen))
250 goto prompterr;
251 t = p + 1;
254 if (p > buf) {
255 memmove(buf, t, endp - t);
256 endp = buf + (endp - t);
258 if (endp == buf)
259 goto more;
261 /* Wait up 1/10 of a second to make sure that we got it all. */
262 tv.tv_sec = 0;
263 tv.tv_usec = 100000;
264 switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
265 case -1: /* Error or interrupt. */
266 msgq(sp, M_SYSERR, "select");
267 goto prompterr;
268 case 0: /* Timeout */
269 break;
270 case 1: /* Characters to read. */
271 goto more;
274 /* Timed out, so theoretically we have a prompt. */
275 llen = endp - buf;
276 endp = buf;
278 /* Append the line into the file. */
279 if (CHAR2INT5(sp, exp->ibcw, buf, llen, wp, wlen))
280 goto conv_err;
281 if (db_last(sp, &lline) || db_append(sp, 0, lline, wp, wlen)) {
282 if (0)
283 conv_err: msgq(sp, M_ERR, "323|Invalid input. Truncated.");
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(SCR *, recno_t);
298 sscr_exec(SCR *sp, recno_t lno)
300 SCRIPT *sc;
301 recno_t last_lno;
302 size_t blen, len, last_len, tlen;
303 int isempty, matchprompt, nw, rval;
304 char *bp = NULL, *p;
305 CHAR_T *wp;
306 size_t wlen;
308 /* If there's a prompt on the last line, append the command. */
309 if (db_last(sp, &last_lno))
310 return (1);
311 if (db_get(sp, last_lno, DBG_FATAL, &wp, &wlen))
312 return (1);
313 INT2CHAR(sp, wp, wlen, p, last_len);
314 if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) {
315 matchprompt = 1;
316 GET_SPACE_RETC(sp, bp, blen, last_len + 128);
317 memmove(bp, p, last_len);
318 } else
319 matchprompt = 0;
321 /* Get something to execute. */
322 if (db_eget(sp, lno, &wp, &wlen, &isempty)) {
323 if (isempty)
324 goto empty;
325 goto err1;
328 /* Empty lines aren't interesting. */
329 if (wlen == 0)
330 goto empty;
331 INT2CHAR(sp, wp, wlen, p, len);
333 /* Delete any prompt. */
334 if (sscr_matchprompt(sp, p, len, &tlen)) {
335 if (tlen == len) {
336 empty: msgq(sp, M_BERR, "151|No command to execute");
337 goto err1;
339 p += (len - tlen);
340 len = tlen;
343 /* Push the line to the shell. */
344 sc = sp->script;
345 if ((nw = write(sc->sh_master, p, len)) != len)
346 goto err2;
347 rval = 0;
348 if (write(sc->sh_master, "\n", 1) != 1) {
349 err2: if (nw == 0)
350 errno = EIO;
351 msgq(sp, M_SYSERR, "shell");
352 goto err1;
355 if (matchprompt) {
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))
360 err1: rval = 1;
362 if (matchprompt)
363 FREE_SPACE(sp, bp, blen);
364 return (rval);
368 * sscr_input --
369 * Read any waiting shell input.
371 * PUBLIC: int sscr_input(SCR *);
374 sscr_input(SCR *sp)
376 GS *gp;
377 struct timeval poll;
378 fd_set rdfd;
379 int maxfd;
381 gp = sp->gp;
383 loop: maxfd = 0;
384 FD_ZERO(&rdfd);
385 poll.tv_sec = 0;
386 poll.tv_usec = 0;
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)) {
398 case -1:
399 msgq(sp, M_SYSERR, "select");
400 return (1);
401 case 0:
402 return (0);
403 default:
404 break;
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) &&
411 sscr_insert(sp))
412 return (1);
413 goto loop;
417 * sscr_insert --
418 * Take a line from the shell and insert it into the file.
420 static int
421 sscr_insert(SCR *sp)
423 EX_PRIVATE *exp;
424 struct timeval tv;
425 char *endp, *p, *t;
426 SCRIPT *sc;
427 fd_set rdfd;
428 recno_t lno;
429 size_t blen, len, tlen;
430 int nr, rval;
431 char *bp;
432 CHAR_T *wp;
433 size_t wlen = 0;
435 exp = EXP(sp);
438 /* Find out where the end of the file is. */
439 if (db_last(sp, &lno))
440 return (1);
442 #define MINREAD 1024
443 GET_SPACE_RETC(sp, bp, blen, MINREAD);
444 endp = bp;
446 /* Read the characters. */
447 rval = 1;
448 sc = sp->script;
449 more: switch (nr = read(sc->sh_master, endp, MINREAD)) {
450 case 0: /* EOF; shell just exited. */
451 sscr_end(sp);
452 rval = 0;
453 goto ret;
454 case -1: /* Error or interrupt. */
455 msgq(sp, M_SYSERR, "shell");
456 goto ret;
457 default:
458 endp += nr;
459 break;
462 /* Append the lines into the file. */
463 for (p = t = bp; p < endp; ++p) {
464 if (*p == '\r' || *p == '\n') {
465 len = p - t;
466 if (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen))
467 goto conv_err;
468 if (db_append(sp, 1, lno++, wp, wlen))
469 goto ret;
470 t = p + 1;
473 if (p > t) {
474 len = p - t;
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) {
483 tv.tv_sec = 0;
484 tv.tv_usec = 100000;
485 FD_ZERO(&rdfd);
486 FD_SET(sc->sh_master, &rdfd);
487 if (select(sc->sh_master + 1,
488 &rdfd, NULL, NULL, &tv) == 1) {
489 memmove(bp, t, len);
490 endp = bp + len;
491 goto more;
494 if (sscr_setprompt(sp, t, len))
495 return (1);
496 if (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen))
497 goto conv_err;
498 if (db_append(sp, 1, lno++, wp, wlen))
499 goto ret;
502 /* The cursor moves to EOF. */
503 sp->lno = lno;
504 sp->cno = wlen ? wlen - 1 : 0;
505 rval = vs_refresh(sp, 1);
507 if (0)
508 conv_err: msgq(sp, M_ERR, "323|Invalid input. Truncated.");
510 ret: FREE_SPACE(sp, bp, blen);
511 return (rval);
515 * sscr_setprompt --
517 * Set the prompt to the last line we got from the shell.
520 static int
521 sscr_setprompt(SCR *sp, char *buf, size_t len)
523 SCRIPT *sc;
525 sc = sp->script;
526 if (sc->sh_prompt)
527 free(sc->sh_prompt);
528 MALLOC(sp, sc->sh_prompt, char *, len + 1);
529 if (sc->sh_prompt == NULL) {
530 sscr_end(sp);
531 return (1);
533 memmove(sc->sh_prompt, buf, len);
534 sc->sh_prompt_len = len;
535 sc->sh_prompt[len] = '\0';
536 return (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.
544 static int
545 sscr_matchprompt(SCR *sp, char *lp, size_t line_len, size_t *lenp)
547 SCRIPT *sc;
548 size_t prompt_len;
549 char *pp;
551 sc = sp->script;
552 if (line_len < (prompt_len = sc->sh_prompt_len))
553 return (0);
555 for (pp = sc->sh_prompt;
556 prompt_len && line_len; --prompt_len, --line_len) {
557 if (*pp == '\0') {
558 for (; prompt_len && *pp == '\0'; --prompt_len, ++pp);
559 if (!prompt_len)
560 return (0);
561 for (; line_len && *lp != *pp; --line_len, ++lp);
562 if (!line_len)
563 return (0);
565 if (*pp++ != *lp++)
566 break;
569 if (prompt_len)
570 return (0);
571 if (lenp != NULL)
572 *lenp = line_len;
573 return (1);
577 * sscr_end --
578 * End the pipe to a shell.
580 * PUBLIC: int sscr_end(SCR *);
583 sscr_end(SCR *sp)
585 SCRIPT *sc;
587 if ((sc = sp->script) == NULL)
588 return (0);
590 /* Turn off the script flags. */
591 F_CLR(sp, SC_SCRIPT);
592 sscr_check(sp);
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);
603 /* Free memory. */
604 free(sc->sh_prompt);
605 free(sc);
606 sp->script = NULL;
608 return (0);
612 * sscr_check --
613 * Set/clear the global scripting bit.
615 static void
616 sscr_check(SCR *sp)
618 GS *gp;
620 gp = sp->gp;
621 TAILQ_FOREACH(sp, gp->dq, q)
622 if (F_ISSET(sp, SC_SCRIPT)) {
623 F_SET(gp, G_SCRWIN);
624 return;
626 F_CLR(gp, G_SCRWIN);