xref: /freebsd/usr.bin/sdiff/edit.c (revision d8a0fe102c0cfdfcd5b818f850eff09d8536c9bc)
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