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