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