1/*	$OpenBSD: logmsg.c,v 1.61 2020/10/19 19:51:20 naddy Exp $	*/
2/*
3 * Copyright (c) 2007 Joris Vink <joris@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/stat.h>
19#include <sys/types.h>
20#include <sys/wait.h>
21
22#include <errno.h>
23#include <fcntl.h>
24#include <libgen.h>
25#include <paths.h>
26#include <signal.h>
27#include <stdint.h>
28#include <stdlib.h>
29#include <string.h>
30#include <unistd.h>
31
32#include "cvs.h"
33
34#define CVS_LOGMSG_PREFIX		"CVS:"
35#define CVS_LOGMSG_LINE		\
36"----------------------------------------------------------------------"
37
38int	cvs_logmsg_edit(const char *);
39
40char *
41cvs_logmsg_read(const char *path)
42{
43	int fd;
44	BUF *bp;
45	FILE *fp;
46	size_t len;
47	struct stat st;
48	char *buf, *lbuf;
49
50	if ((fd = open(path, O_RDONLY)) == -1)
51		fatal("cvs_logmsg_read: open %s", strerror(errno));
52
53	if (fstat(fd, &st) == -1)
54		fatal("cvs_logmsg_read: fstat %s", strerror(errno));
55
56	if (!S_ISREG(st.st_mode))
57		fatal("cvs_logmsg_read: file is not a regular file");
58
59	if ((fp = fdopen(fd, "r")) == NULL)
60		fatal("cvs_logmsg_read: fdopen %s", strerror(errno));
61
62	if ((uintmax_t)st.st_size > SIZE_MAX)
63		fatal("cvs_logmsg_read: %s: file size too big", path);
64
65	lbuf = NULL;
66	bp = buf_alloc(st.st_size);
67	while ((buf = fgetln(fp, &len))) {
68		if (buf[len - 1] == '\n') {
69			buf[len - 1] = '\0';
70			--len;
71		} else {
72			lbuf = xmalloc(len + 1);
73			memcpy(lbuf, buf, len);
74			lbuf[len] = '\0';
75			buf = lbuf;
76		}
77
78		if (!strncmp(buf, CVS_LOGMSG_PREFIX,
79		    sizeof(CVS_LOGMSG_PREFIX) - 1))
80			continue;
81
82		buf_append(bp, buf, len);
83		buf_putc(bp, '\n');
84	}
85
86	free(lbuf);
87
88	(void)fclose(fp);
89
90	buf_putc(bp, '\0');
91	return (buf_release(bp));
92}
93
94char *
95cvs_logmsg_create(char *dir, struct cvs_flisthead *added,
96    struct cvs_flisthead *removed, struct cvs_flisthead *modified)
97{
98	FILE *fp, *rp;
99	int c, fd, rd, saved_errno;
100	struct cvs_filelist *cf;
101	struct stat st1, st2;
102	char *fpath, *logmsg, repo[PATH_MAX];
103	char *f, path[PATH_MAX];
104	struct stat st;
105	struct trigger_list *line_list;
106	struct trigger_line *line;
107	static int reuse = 0;
108	static char *prevmsg = NULL;
109
110	if (reuse)
111		return xstrdup(prevmsg);
112
113	(void)xasprintf(&fpath, "%s/cvsXXXXXXXXXX", cvs_tmpdir);
114
115	if ((fd = mkstemp(fpath)) == -1)
116		fatal("cvs_logmsg_create: mkstemp %s", strerror(errno));
117
118	worklist_add(fpath, &temp_files);
119
120	if ((fp = fdopen(fd, "w")) == NULL) {
121		saved_errno = errno;
122		(void)unlink(fpath);
123		fatal("cvs_logmsg_create: fdopen %s", strerror(saved_errno));
124	}
125
126	if (prevmsg != NULL && prevmsg[0] != '\0')
127		fprintf(fp, "%s", prevmsg);
128	else
129		fputc('\n', fp);
130
131	line_list = cvs_trigger_getlines(CVS_PATH_RCSINFO, repo);
132	if (line_list != NULL) {
133		TAILQ_FOREACH(line, line_list, flist) {
134			if ((rd = open(line->line, O_RDONLY)) == -1)
135				fatal("cvs_logmsg_create: open %s",
136				    strerror(errno));
137			if (fstat(rd, &st) == -1)
138				fatal("cvs_logmsg_create: fstat %s",
139				    strerror(errno));
140			if (!S_ISREG(st.st_mode))
141				fatal("cvs_logmsg_create: file is not a "
142				    "regular file");
143			if ((rp = fdopen(rd, "r")) == NULL)
144				fatal("cvs_logmsg_create: fdopen %s",
145				    strerror(errno));
146			if ((uintmax_t)st.st_size > SIZE_MAX)
147				fatal("cvs_logmsg_create: %s: file size "
148				    "too big", line->line);
149			logmsg = xmalloc(st.st_size);
150			fread(logmsg, st.st_size, 1, rp);
151			fwrite(logmsg, st.st_size, 1, fp);
152			free(logmsg);
153			(void)fclose(rp);
154		}
155		cvs_trigger_freelist(line_list);
156	}
157
158	fprintf(fp, "%s %s\n%s Enter Log.  Lines beginning with `%s' are "
159	    "removed automatically\n%s \n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE,
160	    CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX);
161
162	if (cvs_cmdop == CVS_OP_COMMIT) {
163		fprintf(fp, "%s Committing in %s\n%s\n", CVS_LOGMSG_PREFIX,
164		    dir != NULL ? dir : ".", CVS_LOGMSG_PREFIX);
165	}
166
167	if (added != NULL && !RB_EMPTY(added)) {
168		fprintf(fp, "%s Added Files:", CVS_LOGMSG_PREFIX);
169		RB_FOREACH(cf, cvs_flisthead, added) {
170			f = cf->file_path;
171			if (dir != NULL) {
172				if (strlcpy(path, f, sizeof(path)) >=
173				    sizeof(path))
174					fatal("cvs_logmsg_create: truncation");
175				f = basename(path);
176			}
177			fprintf(fp, "\n%s \t%s ", CVS_LOGMSG_PREFIX, f);
178		}
179		fputs("\n", fp);
180	}
181
182	if (removed != NULL && !RB_EMPTY(removed)) {
183		fprintf(fp, "%s Removed Files:", CVS_LOGMSG_PREFIX);
184		RB_FOREACH(cf, cvs_flisthead, removed) {
185			f = cf->file_path;
186			if (dir != NULL) {
187				if (strlcpy(path, f, sizeof(path)) >=
188				    sizeof(path))
189					fatal("cvs_logmsg_create: truncation");
190				f = basename(path);
191			}
192			fprintf(fp, "\n%s \t%s ", CVS_LOGMSG_PREFIX, f);
193		}
194		fputs("\n", fp);
195	}
196
197	if (modified != NULL && !RB_EMPTY(modified)) {
198		fprintf(fp, "%s Modified Files:", CVS_LOGMSG_PREFIX);
199		RB_FOREACH(cf, cvs_flisthead, modified) {
200			f = cf->file_path;
201			if (dir != NULL) {
202				if (strlcpy(path, f, sizeof(path)) >=
203				    sizeof(path))
204					fatal("cvs_logmsg_create: truncation");
205				f = basename(path);
206			}
207			fprintf(fp, "\n%s \t%s ", CVS_LOGMSG_PREFIX, f);
208		}
209		fputs("\n", fp);
210	}
211
212	fprintf(fp, "%s %s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE);
213	(void)fflush(fp);
214
215	if (fstat(fd, &st1) == -1) {
216		saved_errno = errno;
217		(void)unlink(fpath);
218		fatal("cvs_logmsg_create: fstat %s", strerror(saved_errno));
219	}
220
221	logmsg = NULL;
222
223	for (;;) {
224		if (cvs_logmsg_edit(fpath) == -1)
225			break;
226
227		if (fstat(fd, &st2) == -1) {
228			saved_errno = errno;
229			(void)unlink(fpath);
230			fatal("cvs_logmsg_create: fstat %s",
231			    strerror(saved_errno));
232		}
233
234		if (st1.st_mtime != st2.st_mtime) {
235			logmsg = cvs_logmsg_read(fpath);
236			free(prevmsg);
237			prevmsg = xstrdup(logmsg);
238			break;
239		}
240
241		printf("\nLog message unchanged or not specified\n"
242		    "a)bort, c)ontinue, e)dit, !)reuse this message "
243		    "unchanged for remaining dirs\nAction: (abort) ");
244		(void)fflush(stdout);
245
246		c = getc(stdin);
247		if (c == EOF || c == '\n' || c == 'a' || c == 'A') {
248			fatal("Aborted by user");
249		} else if (c == 'c' || c == 'C') {
250			if (prevmsg == NULL)
251				prevmsg = xstrdup("");
252			logmsg = xstrdup(prevmsg);
253			break;
254		} else if (c == 'e' || c == 'E') {
255			continue;
256		} else if (c == '!') {
257			reuse = 1;
258			if (prevmsg == NULL)
259				prevmsg = xstrdup("");
260			logmsg = xstrdup(prevmsg);
261			break;
262		} else {
263			cvs_log(LP_ERR, "invalid input");
264			continue;
265		}
266	}
267
268	(void)fclose(fp);
269	(void)unlink(fpath);
270	free(fpath);
271
272	return (logmsg);
273}
274
275/*
276 * Execute an editor on the specified pathname, which is interpreted
277 * from the shell.  This means flags may be included.
278 *
279 * Returns -1 on error, or the exit value on success.
280 */
281int
282cvs_logmsg_edit(const char *pathname)
283{
284	char *argp[] = {"sh", "-c", NULL, NULL}, *p;
285	sig_t sighup, sigint, sigquit;
286	pid_t pid;
287	int saved_errno, st;
288
289	(void)xasprintf(&p, "%s %s", cvs_editor, pathname);
290	argp[2] = p;
291
292	sighup = signal(SIGHUP, SIG_IGN);
293	sigint = signal(SIGINT, SIG_IGN);
294	sigquit = signal(SIGQUIT, SIG_IGN);
295	if ((pid = fork()) == -1)
296		goto fail;
297	if (pid == 0) {
298		execv(_PATH_BSHELL, argp);
299		_exit(127);
300	}
301	while (waitpid(pid, &st, 0) == -1)
302		if (errno != EINTR)
303			goto fail;
304	free(p);
305	(void)signal(SIGHUP, sighup);
306	(void)signal(SIGINT, sigint);
307	(void)signal(SIGQUIT, sigquit);
308	if (!WIFEXITED(st)) {
309		errno = EINTR;
310		return (-1);
311	}
312	return (WEXITSTATUS(st));
313
314 fail:
315	saved_errno = errno;
316	(void)signal(SIGHUP, sighup);
317	(void)signal(SIGINT, sigint);
318	(void)signal(SIGQUIT, sigquit);
319	free(p);
320	errno = saved_errno;
321	return (-1);
322}
323
324int
325cvs_logmsg_verify(char *logmsg)
326{
327	int fd, ret = 0;
328	char *fpath;
329	struct trigger_list *line_list;
330	struct file_info_list files_info;
331	struct file_info *fi;
332
333	line_list = cvs_trigger_getlines(CVS_PATH_VERIFYMSG, "DEFAULT");
334	if (line_list != NULL) {
335		TAILQ_INIT(&files_info);
336
337		(void)xasprintf(&fpath, "%s/cvsXXXXXXXXXX", cvs_tmpdir);
338		if ((fd = mkstemp(fpath)) == -1)
339			fatal("cvs_logmsg_verify: mkstemp %s", strerror(errno));
340
341		fi = xcalloc(1, sizeof(*fi));
342		fi->file_path = xstrdup(fpath);
343		TAILQ_INSERT_TAIL(&files_info, fi, flist);
344
345		if (cvs_trigger_handle(CVS_TRIGGER_VERIFYMSG, NULL, NULL,
346		    line_list, &files_info)) {
347			cvs_log(LP_ERR, "Log message check failed");
348			ret = 1;
349		}
350
351		cvs_trigger_freeinfo(&files_info);
352		(void)close(fd);
353		(void)unlink(fpath);
354		free(fpath);
355		cvs_trigger_freelist(line_list);
356	}
357
358	return ret;
359}
360
361