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