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
24int 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 */
32int
33editit(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 */
81int
82eparse(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':
113LEFT:
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':
124RIGHT:
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