1/*	$NetBSD: edit.c,v 1.5 2021/08/27 17:36:37 rillig Exp $	*/
2/*	$OpenBSD: edit.c,v 1.14 2006/05/25 03:20:32 ray Exp $ */
3
4/*
5 * Written by Raymond Lai <ray@cyth.net>.
6 * Public domain.
7 */
8
9#include <sys/types.h>
10#include <sys/wait.h>
11
12#include <ctype.h>
13#include <err.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <unistd.h>
18
19#include "common.h"
20#include "extern.h"
21
22static void edit(const char *);
23
24/*
25 * Takes the name of a file and opens it with an editor.
26 */
27static void
28edit(const char *filename)
29{
30	int status;
31	pid_t pid;
32	const char *editor;
33
34	editor = getenv("VISUAL");
35	if (editor == NULL)
36		editor = getenv("EDITOR");
37	if (editor == NULL)
38		editor = "vi";
39
40	/* Start editor on temporary file. */
41	switch (pid = fork()) {
42	case 0:
43		/* child */
44		execlp(editor, editor, filename, (void *)NULL);
45		warn("could not execute editor: %s", editor);
46		cleanup(filename);
47	case -1:
48		warn("could not fork");
49		cleanup(filename);
50	}
51
52	/* parent */
53	/* Wait for editor to exit. */
54	if (waitpid(pid, &status, 0) == -1) {
55		warn("waitpid");
56		cleanup(filename);
57	}
58
59	/* Check that editor terminated normally. */
60	if (!WIFEXITED(status)) {
61		warn("%s terminated abnormally", editor);
62		cleanup(filename);
63	}
64}
65
66/*
67 * Parse edit command.  Returns 0 on success, -1 on error.
68 */
69int
70eparse(const char *cmd, const char *left, const char *right)
71{
72	FILE *file;
73	size_t nread, nwritten;
74	int fd;
75	char *filename;
76	char buf[BUFSIZ], *text;
77
78	/* Skip whitespace. */
79	while (isspace((unsigned char)(*cmd)))
80		++cmd;
81
82	text = NULL;
83	switch (*cmd) {
84	case '\0':
85		/* Edit empty file. */
86		break;
87
88	case 'b':
89		/* Both strings. */
90		if (left == NULL)
91			goto RIGHT;
92		if (right == NULL)
93			goto LEFT;
94
95		/* Neither column is blank, so print both. */
96		if (asprintf(&text, "%s\n%s\n", left, right) == -1)
97			err(2, "could not allocate memory");
98		break;
99
100	case 'l':
101LEFT:
102		/* Skip if there is no left column. */
103		if (left == NULL)
104			break;
105
106		if (asprintf(&text, "%s\n", left) == -1)
107			err(2, "could not allocate memory");
108
109		break;
110
111	case 'r':
112RIGHT:
113		/* Skip if there is no right column. */
114		if (right == NULL)
115			break;
116
117		if (asprintf(&text, "%s\n", right) == -1)
118			err(2, "could not allocate memory");
119
120		break;
121
122	default:
123		return (-1);
124	}
125
126	/* Create temp file. */
127	if (asprintf(&filename, "%s/sdiff.XXXXXXXXXX", tmpdir) == -1)
128		err(2, "asprintf");
129	if ((fd = mkstemp(filename)) == -1)
130		err(2, "mkstemp");
131	if (text != NULL) {
132		size_t len;
133
134		len = strlen(text);
135		if ((size_t)write(fd, text, len) != len) {
136			warn("error writing to temp file");
137			cleanup(filename);
138		}
139	}
140	close(fd);
141
142	/* text is no longer used. */
143	free(text);
144
145	/* Edit temp file. */
146	edit(filename);
147
148	/* Open temporary file. */
149	if (!(file = fopen(filename, "r"))) {
150		warn("could not open edited file: %s", filename);
151		cleanup(filename);
152	}
153
154	/* Copy temporary file contents to output file. */
155	for (nread = sizeof(buf); nread == sizeof(buf);) {
156		nread = fread(buf, sizeof(*buf), sizeof(buf), file);
157		/* Test for error or end of file. */
158		if (nread != sizeof(buf) &&
159		    (ferror(file) || !feof(file))) {
160			warnx("error reading edited file: %s", filename);
161			cleanup(filename);
162		}
163
164		/*
165		 * If we have nothing to read, break out of loop
166		 * instead of writing nothing.
167		 */
168		if (!nread)
169			break;
170
171		/* Write data we just read. */
172		nwritten = fwrite(buf, sizeof(*buf), nread, outfile);
173		if (nwritten != nread) {
174			warnx("error writing to output file");
175			cleanup(filename);
176		}
177	}
178
179	/* We've reached the end of the temporary file, so remove it. */
180	if (unlink(filename))
181		warn("could not delete: %s", filename);
182	fclose(file);
183
184	free(filename);
185
186	return (0);
187}
188