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
23static void
24cleanup(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 */
38static int
39editit(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 */
83int
84eparse(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':
115LEFT:
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':
126RIGHT:
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