1*5f46c183Santon /* $OpenBSD: edit.c,v 1.8 2021/07/10 07:10:31 anton Exp $ */
282270653Santon /*
382270653Santon * Copyright (c) 2017 Anton Lindqvist <anton@openbsd.org>
482270653Santon *
582270653Santon * Permission to use, copy, modify, and distribute this software for any
682270653Santon * purpose with or without fee is hereby granted, provided that the above
782270653Santon * copyright notice and this permission notice appear in all copies.
882270653Santon *
982270653Santon * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1082270653Santon * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1182270653Santon * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1282270653Santon * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1382270653Santon * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1482270653Santon * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1582270653Santon * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1682270653Santon */
1782270653Santon
18cf14a182Santon #include <sys/wait.h>
19cf14a182Santon
2082270653Santon #include <err.h>
2182270653Santon #include <errno.h>
2282270653Santon #include <poll.h>
2382270653Santon #include <signal.h>
2482270653Santon #include <stdio.h>
2578f99cd3Santon #include <stdlib.h>
2682270653Santon #include <string.h>
2782270653Santon #include <termios.h>
2882270653Santon #include <unistd.h>
2982270653Santon #include <util.h>
3082270653Santon
3190e71d7fSanton #define WRTIM 50 /* input write timeout */
3290e71d7fSanton #define PRTIM 5000 /* prompt read timeout */
3390e71d7fSanton
3490e71d7fSanton static size_t findprompt(const char *, const char *);
352a676cd6Santon static void sighandler(int);
3678f99cd3Santon static void __dead usage(void);
3782270653Santon
382a676cd6Santon static volatile sig_atomic_t gotsig;
3982270653Santon
4082270653Santon int
main(int argc,char * argv[])4178f99cd3Santon main(int argc, char *argv[])
4282270653Santon {
4390e71d7fSanton const char *linefeed = "\003\004\n\r";
4490e71d7fSanton const char *prompt = "";
4582270653Santon char in[BUFSIZ], out[BUFSIZ];
4682270653Santon struct pollfd pfd;
4782270653Santon struct winsize ws;
4882270653Santon pid_t pid;
4982270653Santon ssize_t n;
5090e71d7fSanton size_t nin, nprompt, nread, nwrite;
51cf14a182Santon int c, nready, ptyfd, readprompt, ret, status, timeout;
5282270653Santon
5390e71d7fSanton while ((c = getopt(argc, argv, "p:")) != -1) {
5490e71d7fSanton switch (c) {
5590e71d7fSanton case 'p':
5690e71d7fSanton prompt = optarg;
5790e71d7fSanton break;
5890e71d7fSanton default:
5990e71d7fSanton usage();
6090e71d7fSanton }
6190e71d7fSanton }
6290e71d7fSanton argc -= optind;
6390e71d7fSanton argv += optind;
6490e71d7fSanton if (argc == 0 || strlen(prompt) == 0)
6578f99cd3Santon usage();
6678f99cd3Santon
6782270653Santon nin = 0;
6882270653Santon for (;;) {
6982270653Santon if (nin == sizeof(in))
7082270653Santon errx(1, "input buffer too small");
7182270653Santon
7290e71d7fSanton n = read(0, in + nin, sizeof(in) - nin);
7382270653Santon if (n == -1)
7482270653Santon err(1, "read");
7582270653Santon if (n == 0)
7682270653Santon break;
7782270653Santon
7882270653Santon nin += n;
7982270653Santon }
8082270653Santon
812a676cd6Santon if (signal(SIGCHLD, sighandler) == SIG_ERR)
822a676cd6Santon err(1, "signal: SIGCHLD");
832a676cd6Santon if (signal(SIGINT, sighandler) == SIG_ERR)
842a676cd6Santon err(1, "signal: SIGINT");
8582270653Santon
8682270653Santon memset(&ws, 0, sizeof(ws));
87*5f46c183Santon ws.ws_col = 80;
8882270653Santon ws.ws_row = 24;
8982270653Santon
9082270653Santon pid = forkpty(&ptyfd, NULL, NULL, &ws);
9182270653Santon if (pid == -1)
9282270653Santon err(1, "forkpty");
9382270653Santon if (pid == 0) {
9490e71d7fSanton execvp(*argv, argv);
9590e71d7fSanton err(1, "%s", *argv);
9682270653Santon }
9782270653Santon
9890e71d7fSanton nprompt = nread = nwrite = ret = 0;
9990e71d7fSanton readprompt = 1;
1002a676cd6Santon while (!gotsig) {
10190e71d7fSanton pfd.fd = ptyfd;
10290e71d7fSanton if (!readprompt && nwrite < nin)
10390e71d7fSanton pfd.events = POLLOUT;
10490e71d7fSanton else
10590e71d7fSanton pfd.events = POLLIN;
10690e71d7fSanton timeout = readprompt ? PRTIM : WRTIM;
10790e71d7fSanton nready = poll(&pfd, 1, timeout);
10882270653Santon if (nready == -1) {
10982270653Santon if (errno == EINTR)
11082270653Santon continue;
11182270653Santon err(1, "poll");
11282270653Santon }
11390e71d7fSanton if (nready == 0) {
114*5f46c183Santon if (timeout == PRTIM) {
115*5f46c183Santon warnx("timeout waiting from prompt");
11690e71d7fSanton ret = 1;
117*5f46c183Santon }
11890e71d7fSanton break;
11990e71d7fSanton }
12082270653Santon if (pfd.revents & (POLLERR | POLLNVAL))
12182270653Santon errc(1, EBADF, NULL);
12282270653Santon
12382270653Santon if (pfd.revents & (POLLIN | POLLHUP)) {
12482270653Santon if (nread == sizeof(out))
12582270653Santon errx(1, "output buffer too small");
12682270653Santon
12790e71d7fSanton n = read(ptyfd, out + nread, sizeof(out) - 1 - nread);
12882270653Santon if (n == -1)
12982270653Santon err(1, "read");
13082270653Santon nread += n;
13190e71d7fSanton out[nread] = '\0';
13290e71d7fSanton
13390e71d7fSanton if (readprompt &&
13490e71d7fSanton (n = findprompt(out + nprompt, prompt)) > 0) {
13590e71d7fSanton nprompt += n;
13690e71d7fSanton readprompt = 0;
13790e71d7fSanton }
13882270653Santon } else if (pfd.revents & POLLOUT) {
13990e71d7fSanton if (strchr(linefeed, in[nwrite]) != NULL)
14090e71d7fSanton readprompt = 1;
14182270653Santon
14290e71d7fSanton n = write(ptyfd, in + nwrite, 1);
14382270653Santon if (n == -1)
14482270653Santon err(1, "write");
14582270653Santon nwrite += n;
14682270653Santon }
14782270653Santon }
14882270653Santon close(ptyfd);
149cf14a182Santon while (waitpid(pid, &status, 0) == -1)
150cf14a182Santon if (errno != EINTR)
151cf14a182Santon err(1, "waitpid");
152cf14a182Santon if (WIFSIGNALED(status) && WTERMSIG(status) != SIGHUP) {
153cf14a182Santon warnx("%s: terminated by signal %d", *argv, WTERMSIG(status));
154cf14a182Santon ret = 128 + WTERMSIG(status);
155cf14a182Santon }
15682270653Santon
15790e71d7fSanton printf("%.*s", (int)nread, out);
15882270653Santon
15990e71d7fSanton return ret;
16090e71d7fSanton }
16190e71d7fSanton
16290e71d7fSanton static size_t
findprompt(const char * str,const char * prompt)16390e71d7fSanton findprompt(const char *str, const char *prompt)
16490e71d7fSanton {
16590e71d7fSanton char *cp;
16690e71d7fSanton size_t len;
16790e71d7fSanton
16890e71d7fSanton if ((cp = strstr(str, prompt)) == NULL)
16982270653Santon return 0;
17090e71d7fSanton len = strlen(prompt);
17190e71d7fSanton
17290e71d7fSanton return (cp - str) + len;
17382270653Santon }
17482270653Santon
17582270653Santon static void
sighandler(int sig)1762a676cd6Santon sighandler(int sig)
17782270653Santon {
1782a676cd6Santon gotsig = sig;
17982270653Santon }
18078f99cd3Santon
18178f99cd3Santon static void __dead
usage(void)18278f99cd3Santon usage(void)
18378f99cd3Santon {
18490e71d7fSanton fprintf(stderr, "usage: edit -p prompt command [args]\n");
18578f99cd3Santon exit(1);
18678f99cd3Santon }
187