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