1 /* $OpenBSD: edit.c,v 1.8 2021/07/10 07:10:31 anton Exp $ */ 2 /* 3 * Copyright (c) 2017 Anton Lindqvist <anton@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <sys/wait.h> 19 20 #include <err.h> 21 #include <errno.h> 22 #include <poll.h> 23 #include <signal.h> 24 #include <stdio.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <termios.h> 28 #include <unistd.h> 29 #include <util.h> 30 31 #define WRTIM 50 /* input write timeout */ 32 #define PRTIM 5000 /* prompt read timeout */ 33 34 static size_t findprompt(const char *, const char *); 35 static void sighandler(int); 36 static void __dead usage(void); 37 38 static volatile sig_atomic_t gotsig; 39 40 int 41 main(int argc, char *argv[]) 42 { 43 const char *linefeed = "\003\004\n\r"; 44 const char *prompt = ""; 45 char in[BUFSIZ], out[BUFSIZ]; 46 struct pollfd pfd; 47 struct winsize ws; 48 pid_t pid; 49 ssize_t n; 50 size_t nin, nprompt, nread, nwrite; 51 int c, nready, ptyfd, readprompt, ret, status, timeout; 52 53 while ((c = getopt(argc, argv, "p:")) != -1) { 54 switch (c) { 55 case 'p': 56 prompt = optarg; 57 break; 58 default: 59 usage(); 60 } 61 } 62 argc -= optind; 63 argv += optind; 64 if (argc == 0 || strlen(prompt) == 0) 65 usage(); 66 67 nin = 0; 68 for (;;) { 69 if (nin == sizeof(in)) 70 errx(1, "input buffer too small"); 71 72 n = read(0, in + nin, sizeof(in) - nin); 73 if (n == -1) 74 err(1, "read"); 75 if (n == 0) 76 break; 77 78 nin += n; 79 } 80 81 if (signal(SIGCHLD, sighandler) == SIG_ERR) 82 err(1, "signal: SIGCHLD"); 83 if (signal(SIGINT, sighandler) == SIG_ERR) 84 err(1, "signal: SIGINT"); 85 86 memset(&ws, 0, sizeof(ws)); 87 ws.ws_col = 80; 88 ws.ws_row = 24; 89 90 pid = forkpty(&ptyfd, NULL, NULL, &ws); 91 if (pid == -1) 92 err(1, "forkpty"); 93 if (pid == 0) { 94 execvp(*argv, argv); 95 err(1, "%s", *argv); 96 } 97 98 nprompt = nread = nwrite = ret = 0; 99 readprompt = 1; 100 while (!gotsig) { 101 pfd.fd = ptyfd; 102 if (!readprompt && nwrite < nin) 103 pfd.events = POLLOUT; 104 else 105 pfd.events = POLLIN; 106 timeout = readprompt ? PRTIM : WRTIM; 107 nready = poll(&pfd, 1, timeout); 108 if (nready == -1) { 109 if (errno == EINTR) 110 continue; 111 err(1, "poll"); 112 } 113 if (nready == 0) { 114 if (timeout == PRTIM) { 115 warnx("timeout waiting from prompt"); 116 ret = 1; 117 } 118 break; 119 } 120 if (pfd.revents & (POLLERR | POLLNVAL)) 121 errc(1, EBADF, NULL); 122 123 if (pfd.revents & (POLLIN | POLLHUP)) { 124 if (nread == sizeof(out)) 125 errx(1, "output buffer too small"); 126 127 n = read(ptyfd, out + nread, sizeof(out) - 1 - nread); 128 if (n == -1) 129 err(1, "read"); 130 nread += n; 131 out[nread] = '\0'; 132 133 if (readprompt && 134 (n = findprompt(out + nprompt, prompt)) > 0) { 135 nprompt += n; 136 readprompt = 0; 137 } 138 } else if (pfd.revents & POLLOUT) { 139 if (strchr(linefeed, in[nwrite]) != NULL) 140 readprompt = 1; 141 142 n = write(ptyfd, in + nwrite, 1); 143 if (n == -1) 144 err(1, "write"); 145 nwrite += n; 146 } 147 } 148 close(ptyfd); 149 while (waitpid(pid, &status, 0) == -1) 150 if (errno != EINTR) 151 err(1, "waitpid"); 152 if (WIFSIGNALED(status) && WTERMSIG(status) != SIGHUP) { 153 warnx("%s: terminated by signal %d", *argv, WTERMSIG(status)); 154 ret = 128 + WTERMSIG(status); 155 } 156 157 printf("%.*s", (int)nread, out); 158 159 return ret; 160 } 161 162 static size_t 163 findprompt(const char *str, const char *prompt) 164 { 165 char *cp; 166 size_t len; 167 168 if ((cp = strstr(str, prompt)) == NULL) 169 return 0; 170 len = strlen(prompt); 171 172 return (cp - str) + len; 173 } 174 175 static void 176 sighandler(int sig) 177 { 178 gotsig = sig; 179 } 180 181 static void __dead 182 usage(void) 183 { 184 fprintf(stderr, "usage: edit -p prompt command [args]\n"); 185 exit(1); 186 } 187