1 /* $OpenBSD: edit.c,v 1.19 2009/06/07 13:29:50 ray Exp $ */ 2 3 /* 4 * Written by Raymond Lai <ray@cyth.net>. 5 * Public domain. 6 */ 7 8 #include <sys/cdefs.h> 9 #include <sys/types.h> 10 #include <sys/wait.h> 11 12 #include <ctype.h> 13 #include <err.h> 14 #include <errno.h> 15 #include <paths.h> 16 #include <signal.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <string.h> 20 #include <unistd.h> 21 22 #include "extern.h" 23 24 static void 25 cleanup(const char *filename) 26 { 27 28 if (unlink(filename)) 29 err(2, "could not delete: %s", filename); 30 exit(2); 31 } 32 33 /* 34 * Execute an editor on the specified pathname, which is interpreted 35 * from the shell. This means flags may be included. 36 * 37 * Returns -1 on error, or the exit value on success. 38 */ 39 static int 40 editit(const char *pathname) 41 { 42 sig_t sighup, sigint, sigquit, sigchld; 43 pid_t pid; 44 int saved_errno, st, ret = -1; 45 const char *ed; 46 47 ed = getenv("VISUAL"); 48 if (ed == NULL) 49 ed = getenv("EDITOR"); 50 if (ed == NULL) 51 ed = _PATH_VI; 52 53 sighup = signal(SIGHUP, SIG_IGN); 54 sigint = signal(SIGINT, SIG_IGN); 55 sigquit = signal(SIGQUIT, SIG_IGN); 56 sigchld = signal(SIGCHLD, SIG_DFL); 57 if ((pid = fork()) == -1) 58 goto fail; 59 if (pid == 0) { 60 execlp(ed, ed, pathname, (char *)NULL); 61 _exit(127); 62 } 63 while (waitpid(pid, &st, 0) == -1) 64 if (errno != EINTR) 65 goto fail; 66 if (!WIFEXITED(st)) 67 errno = EINTR; 68 else 69 ret = WEXITSTATUS(st); 70 71 fail: 72 saved_errno = errno; 73 (void)signal(SIGHUP, sighup); 74 (void)signal(SIGINT, sigint); 75 (void)signal(SIGQUIT, sigquit); 76 (void)signal(SIGCHLD, sigchld); 77 errno = saved_errno; 78 return (ret); 79 } 80 81 /* 82 * Parse edit command. Returns 0 on success, -1 on error. 83 */ 84 int 85 eparse(const char *cmd, const char *left, const char *right) 86 { 87 FILE *file; 88 size_t nread; 89 int fd; 90 char *filename; 91 char buf[BUFSIZ], *text; 92 93 /* Skip whitespace. */ 94 while (isspace(*cmd)) 95 ++cmd; 96 97 text = NULL; 98 switch (*cmd) { 99 case '\0': 100 /* Edit empty file. */ 101 break; 102 103 case 'b': 104 /* Both strings. */ 105 if (left == NULL) 106 goto RIGHT; 107 if (right == NULL) 108 goto LEFT; 109 110 /* Neither column is blank, so print both. */ 111 if (asprintf(&text, "%s\n%s\n", left, right) == -1) 112 err(2, "could not allocate memory"); 113 break; 114 115 case 'l': 116 LEFT: 117 /* Skip if there is no left column. */ 118 if (left == NULL) 119 break; 120 121 if (asprintf(&text, "%s\n", left) == -1) 122 err(2, "could not allocate memory"); 123 124 break; 125 126 case 'r': 127 RIGHT: 128 /* Skip if there is no right column. */ 129 if (right == NULL) 130 break; 131 132 if (asprintf(&text, "%s\n", right) == -1) 133 err(2, "could not allocate memory"); 134 135 break; 136 137 default: 138 return (-1); 139 } 140 141 /* Create temp file. */ 142 if (asprintf(&filename, "%s/sdiff.XXXXXXXXXX", tmpdir) == -1) 143 err(2, "asprintf"); 144 if ((fd = mkstemp(filename)) == -1) 145 err(2, "mkstemp"); 146 if (text != NULL) { 147 size_t len; 148 ssize_t nwritten; 149 150 len = strlen(text); 151 if ((nwritten = write(fd, text, len)) == -1 || 152 (size_t)nwritten != len) { 153 warn("error writing to temp file"); 154 cleanup(filename); 155 } 156 } 157 close(fd); 158 159 /* text is no longer used. */ 160 free(text); 161 162 /* Edit temp file. */ 163 if (editit(filename) == -1) { 164 warn("error editing %s", filename); 165 cleanup(filename); 166 } 167 168 /* Open temporary file. */ 169 if (!(file = fopen(filename, "r"))) { 170 warn("could not open edited file: %s", filename); 171 cleanup(filename); 172 } 173 174 /* Copy temporary file contents to output file. */ 175 for (nread = sizeof(buf); nread == sizeof(buf);) { 176 size_t nwritten; 177 178 nread = fread(buf, sizeof(*buf), sizeof(buf), file); 179 /* Test for error or end of file. */ 180 if (nread != sizeof(buf) && 181 (ferror(file) || !feof(file))) { 182 warnx("error reading edited file: %s", filename); 183 cleanup(filename); 184 } 185 186 /* 187 * If we have nothing to read, break out of loop 188 * instead of writing nothing. 189 */ 190 if (!nread) 191 break; 192 193 /* Write data we just read. */ 194 nwritten = fwrite(buf, sizeof(*buf), nread, outfp); 195 if (nwritten != nread) { 196 warnx("error writing to output file"); 197 cleanup(filename); 198 } 199 } 200 201 /* We've reached the end of the temporary file, so remove it. */ 202 if (unlink(filename)) 203 warn("could not delete: %s", filename); 204 fclose(file); 205 206 free(filename); 207 208 return (0); 209 } 210