xref: /openbsd-src/usr.bin/cvs/logmsg.c (revision 431378d1a7a36c77c1b34ffa43b6933d2ecc970a)
1*431378d1Snaddy /*	$OpenBSD: logmsg.c,v 1.61 2020/10/19 19:51:20 naddy Exp $	*/
2db758d52Sjoris /*
3db758d52Sjoris  * Copyright (c) 2007 Joris Vink <joris@openbsd.org>
4db758d52Sjoris  *
5db758d52Sjoris  * Permission to use, copy, modify, and distribute this software for any
6db758d52Sjoris  * purpose with or without fee is hereby granted, provided that the above
7db758d52Sjoris  * copyright notice and this permission notice appear in all copies.
8db758d52Sjoris  *
9db758d52Sjoris  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10db758d52Sjoris  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11db758d52Sjoris  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12db758d52Sjoris  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13db758d52Sjoris  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14db758d52Sjoris  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15db758d52Sjoris  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16db758d52Sjoris  */
17db758d52Sjoris 
181f8531bdSotto #include <sys/stat.h>
1966510bb6Sxsa #include <sys/types.h>
2066510bb6Sxsa #include <sys/wait.h>
211f8531bdSotto 
221f8531bdSotto #include <errno.h>
231f8531bdSotto #include <fcntl.h>
2484920933Stobias #include <libgen.h>
2566510bb6Sxsa #include <paths.h>
2666510bb6Sxsa #include <signal.h>
271357284aSmillert #include <stdint.h>
28f99068d5Schl #include <stdlib.h>
291f8531bdSotto #include <string.h>
301f8531bdSotto #include <unistd.h>
31db758d52Sjoris 
32db758d52Sjoris #include "cvs.h"
33db758d52Sjoris 
34db758d52Sjoris #define CVS_LOGMSG_PREFIX		"CVS:"
35db758d52Sjoris #define CVS_LOGMSG_LINE		\
36db758d52Sjoris "----------------------------------------------------------------------"
37db758d52Sjoris 
3866510bb6Sxsa int	cvs_logmsg_edit(const char *);
3966510bb6Sxsa 
40db758d52Sjoris char *
cvs_logmsg_read(const char * path)41db758d52Sjoris cvs_logmsg_read(const char *path)
42db758d52Sjoris {
43db758d52Sjoris 	int fd;
44db758d52Sjoris 	BUF *bp;
45db758d52Sjoris 	FILE *fp;
46db758d52Sjoris 	size_t len;
47db758d52Sjoris 	struct stat st;
48db758d52Sjoris 	char *buf, *lbuf;
49db758d52Sjoris 
50db758d52Sjoris 	if ((fd = open(path, O_RDONLY)) == -1)
51db758d52Sjoris 		fatal("cvs_logmsg_read: open %s", strerror(errno));
52db758d52Sjoris 
53db758d52Sjoris 	if (fstat(fd, &st) == -1)
54db758d52Sjoris 		fatal("cvs_logmsg_read: fstat %s", strerror(errno));
55db758d52Sjoris 
56db758d52Sjoris 	if (!S_ISREG(st.st_mode))
57db758d52Sjoris 		fatal("cvs_logmsg_read: file is not a regular file");
58db758d52Sjoris 
59db758d52Sjoris 	if ((fp = fdopen(fd, "r")) == NULL)
60db758d52Sjoris 		fatal("cvs_logmsg_read: fdopen %s", strerror(errno));
61db758d52Sjoris 
62ae886706Smillert 	if ((uintmax_t)st.st_size > SIZE_MAX)
63bd67a6ccStobias 		fatal("cvs_logmsg_read: %s: file size too big", path);
64fcb086e6Stobias 
65db758d52Sjoris 	lbuf = NULL;
667bb3ddb0Sray 	bp = buf_alloc(st.st_size);
67db758d52Sjoris 	while ((buf = fgetln(fp, &len))) {
68db758d52Sjoris 		if (buf[len - 1] == '\n') {
69db758d52Sjoris 			buf[len - 1] = '\0';
70c838fb7aSray 			--len;
71db758d52Sjoris 		} else {
72db758d52Sjoris 			lbuf = xmalloc(len + 1);
737347534fSotto 			memcpy(lbuf, buf, len);
747347534fSotto 			lbuf[len] = '\0';
75db758d52Sjoris 			buf = lbuf;
76db758d52Sjoris 		}
77db758d52Sjoris 
78db758d52Sjoris 		if (!strncmp(buf, CVS_LOGMSG_PREFIX,
79870158f9Stobias 		    sizeof(CVS_LOGMSG_PREFIX) - 1))
80db758d52Sjoris 			continue;
81db758d52Sjoris 
827bb3ddb0Sray 		buf_append(bp, buf, len);
837bb3ddb0Sray 		buf_putc(bp, '\n');
84db758d52Sjoris 	}
85db758d52Sjoris 
86397ddb8aSnicm 	free(lbuf);
87db758d52Sjoris 
88db758d52Sjoris 	(void)fclose(fp);
89db758d52Sjoris 
907bb3ddb0Sray 	buf_putc(bp, '\0');
917bb3ddb0Sray 	return (buf_release(bp));
92db758d52Sjoris }
93db758d52Sjoris 
94db758d52Sjoris char *
cvs_logmsg_create(char * dir,struct cvs_flisthead * added,struct cvs_flisthead * removed,struct cvs_flisthead * modified)9584920933Stobias cvs_logmsg_create(char *dir, struct cvs_flisthead *added,
9684920933Stobias     struct cvs_flisthead *removed, struct cvs_flisthead *modified)
97db758d52Sjoris {
98b034d592Sjoris 	FILE *fp, *rp;
99b034d592Sjoris 	int c, fd, rd, saved_errno;
100db758d52Sjoris 	struct cvs_filelist *cf;
101db758d52Sjoris 	struct stat st1, st2;
102b9fc9a72Sderaadt 	char *fpath, *logmsg, repo[PATH_MAX];
103*431378d1Snaddy 	char *f, path[PATH_MAX];
104b034d592Sjoris 	struct stat st;
105b034d592Sjoris 	struct trigger_list *line_list;
106b034d592Sjoris 	struct trigger_line *line;
10784920933Stobias 	static int reuse = 0;
10884920933Stobias 	static char *prevmsg = NULL;
10984920933Stobias 
11084920933Stobias 	if (reuse)
11184920933Stobias 		return xstrdup(prevmsg);
112db758d52Sjoris 
1138ad0f384Sxsa 	(void)xasprintf(&fpath, "%s/cvsXXXXXXXXXX", cvs_tmpdir);
114db758d52Sjoris 
115f99068d5Schl 	if ((fd = mkstemp(fpath)) == -1)
116db758d52Sjoris 		fatal("cvs_logmsg_create: mkstemp %s", strerror(errno));
117db758d52Sjoris 
1187a9e6d11Sray 	worklist_add(fpath, &temp_files);
11964f70df8Sjoris 
120db758d52Sjoris 	if ((fp = fdopen(fd, "w")) == NULL) {
121ba617dc1Sxsa 		saved_errno = errno;
122db758d52Sjoris 		(void)unlink(fpath);
123ba617dc1Sxsa 		fatal("cvs_logmsg_create: fdopen %s", strerror(saved_errno));
124db758d52Sjoris 	}
125db758d52Sjoris 
12684920933Stobias 	if (prevmsg != NULL && prevmsg[0] != '\0')
12784920933Stobias 		fprintf(fp, "%s", prevmsg);
12884920933Stobias 	else
12984920933Stobias 		fputc('\n', fp);
13084920933Stobias 
131b034d592Sjoris 	line_list = cvs_trigger_getlines(CVS_PATH_RCSINFO, repo);
132b034d592Sjoris 	if (line_list != NULL) {
133b034d592Sjoris 		TAILQ_FOREACH(line, line_list, flist) {
134b034d592Sjoris 			if ((rd = open(line->line, O_RDONLY)) == -1)
135b034d592Sjoris 				fatal("cvs_logmsg_create: open %s",
136b034d592Sjoris 				    strerror(errno));
137b034d592Sjoris 			if (fstat(rd, &st) == -1)
138b034d592Sjoris 				fatal("cvs_logmsg_create: fstat %s",
139b034d592Sjoris 				    strerror(errno));
140b034d592Sjoris 			if (!S_ISREG(st.st_mode))
141b034d592Sjoris 				fatal("cvs_logmsg_create: file is not a "
142b034d592Sjoris 				    "regular file");
143b034d592Sjoris 			if ((rp = fdopen(rd, "r")) == NULL)
144b034d592Sjoris 				fatal("cvs_logmsg_create: fdopen %s",
145b034d592Sjoris 				    strerror(errno));
146ae886706Smillert 			if ((uintmax_t)st.st_size > SIZE_MAX)
147b034d592Sjoris 				fatal("cvs_logmsg_create: %s: file size "
148b034d592Sjoris 				    "too big", line->line);
149b034d592Sjoris 			logmsg = xmalloc(st.st_size);
150b034d592Sjoris 			fread(logmsg, st.st_size, 1, rp);
151b034d592Sjoris 			fwrite(logmsg, st.st_size, 1, fp);
152397ddb8aSnicm 			free(logmsg);
153b034d592Sjoris 			(void)fclose(rp);
154b034d592Sjoris 		}
155b034d592Sjoris 		cvs_trigger_freelist(line_list);
156b034d592Sjoris 	}
157b034d592Sjoris 
15884920933Stobias 	fprintf(fp, "%s %s\n%s Enter Log.  Lines beginning with `%s' are "
159db758d52Sjoris 	    "removed automatically\n%s \n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE,
160db758d52Sjoris 	    CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX);
161db758d52Sjoris 
16284920933Stobias 	if (cvs_cmdop == CVS_OP_COMMIT) {
16384920933Stobias 		fprintf(fp, "%s Committing in %s\n%s\n", CVS_LOGMSG_PREFIX,
16484920933Stobias 		    dir != NULL ? dir : ".", CVS_LOGMSG_PREFIX);
16584920933Stobias 	}
16684920933Stobias 
167f106b389Sjoris 	if (added != NULL && !RB_EMPTY(added)) {
168db758d52Sjoris 		fprintf(fp, "%s Added Files:", CVS_LOGMSG_PREFIX);
169*431378d1Snaddy 		RB_FOREACH(cf, cvs_flisthead, added) {
170*431378d1Snaddy 			f = cf->file_path;
171*431378d1Snaddy 			if (dir != NULL) {
172*431378d1Snaddy 				if (strlcpy(path, f, sizeof(path)) >=
173*431378d1Snaddy 				    sizeof(path))
174*431378d1Snaddy 					fatal("cvs_logmsg_create: truncation");
175*431378d1Snaddy 				f = basename(path);
176*431378d1Snaddy 			}
177*431378d1Snaddy 			fprintf(fp, "\n%s \t%s ", CVS_LOGMSG_PREFIX, f);
178*431378d1Snaddy 		}
179db758d52Sjoris 		fputs("\n", fp);
180db758d52Sjoris 	}
181db758d52Sjoris 
182f106b389Sjoris 	if (removed != NULL && !RB_EMPTY(removed)) {
183db758d52Sjoris 		fprintf(fp, "%s Removed Files:", CVS_LOGMSG_PREFIX);
184*431378d1Snaddy 		RB_FOREACH(cf, cvs_flisthead, removed) {
185*431378d1Snaddy 			f = cf->file_path;
186*431378d1Snaddy 			if (dir != NULL) {
187*431378d1Snaddy 				if (strlcpy(path, f, sizeof(path)) >=
188*431378d1Snaddy 				    sizeof(path))
189*431378d1Snaddy 					fatal("cvs_logmsg_create: truncation");
190*431378d1Snaddy 				f = basename(path);
191*431378d1Snaddy 			}
192*431378d1Snaddy 			fprintf(fp, "\n%s \t%s ", CVS_LOGMSG_PREFIX, f);
193*431378d1Snaddy 		}
194db758d52Sjoris 		fputs("\n", fp);
195db758d52Sjoris 	}
196db758d52Sjoris 
197f106b389Sjoris 	if (modified != NULL && !RB_EMPTY(modified)) {
198db758d52Sjoris 		fprintf(fp, "%s Modified Files:", CVS_LOGMSG_PREFIX);
199*431378d1Snaddy 		RB_FOREACH(cf, cvs_flisthead, modified) {
200*431378d1Snaddy 			f = cf->file_path;
201*431378d1Snaddy 			if (dir != NULL) {
202*431378d1Snaddy 				if (strlcpy(path, f, sizeof(path)) >=
203*431378d1Snaddy 				    sizeof(path))
204*431378d1Snaddy 					fatal("cvs_logmsg_create: truncation");
205*431378d1Snaddy 				f = basename(path);
206*431378d1Snaddy 			}
207*431378d1Snaddy 			fprintf(fp, "\n%s \t%s ", CVS_LOGMSG_PREFIX, f);
208*431378d1Snaddy 		}
209db758d52Sjoris 		fputs("\n", fp);
210db758d52Sjoris 	}
211db758d52Sjoris 
212db758d52Sjoris 	fprintf(fp, "%s %s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE);
213db758d52Sjoris 	(void)fflush(fp);
214db758d52Sjoris 
215db758d52Sjoris 	if (fstat(fd, &st1) == -1) {
216ba617dc1Sxsa 		saved_errno = errno;
217db758d52Sjoris 		(void)unlink(fpath);
218ba617dc1Sxsa 		fatal("cvs_logmsg_create: fstat %s", strerror(saved_errno));
219db758d52Sjoris 	}
220db758d52Sjoris 
221db758d52Sjoris 	logmsg = NULL;
222db758d52Sjoris 
223db758d52Sjoris 	for (;;) {
224717c55ebSray 		if (cvs_logmsg_edit(fpath) == -1)
225db758d52Sjoris 			break;
226db758d52Sjoris 
227db758d52Sjoris 		if (fstat(fd, &st2) == -1) {
228ba617dc1Sxsa 			saved_errno = errno;
229db758d52Sjoris 			(void)unlink(fpath);
230ba617dc1Sxsa 			fatal("cvs_logmsg_create: fstat %s",
231ba617dc1Sxsa 			    strerror(saved_errno));
232db758d52Sjoris 		}
233db758d52Sjoris 
234db758d52Sjoris 		if (st1.st_mtime != st2.st_mtime) {
235db758d52Sjoris 			logmsg = cvs_logmsg_read(fpath);
236397ddb8aSnicm 			free(prevmsg);
23784920933Stobias 			prevmsg = xstrdup(logmsg);
238db758d52Sjoris 			break;
239db758d52Sjoris 		}
240db758d52Sjoris 
241db758d52Sjoris 		printf("\nLog message unchanged or not specified\n"
24284920933Stobias 		    "a)bort, c)ontinue, e)dit, !)reuse this message "
2433e379626Stb 		    "unchanged for remaining dirs\nAction: (abort) ");
244db758d52Sjoris 		(void)fflush(stdout);
245db758d52Sjoris 
246db758d52Sjoris 		c = getc(stdin);
2473e379626Stb 		if (c == EOF || c == '\n' || c == 'a' || c == 'A') {
248db758d52Sjoris 			fatal("Aborted by user");
2493e379626Stb 		} else if (c == 'c' || c == 'C') {
25084920933Stobias 			if (prevmsg == NULL)
25184920933Stobias 				prevmsg = xstrdup("");
25284920933Stobias 			logmsg = xstrdup(prevmsg);
253db758d52Sjoris 			break;
2543e379626Stb 		} else if (c == 'e' || c == 'E') {
255db758d52Sjoris 			continue;
25684920933Stobias 		} else if (c == '!') {
25784920933Stobias 			reuse = 1;
25884920933Stobias 			if (prevmsg == NULL)
25984920933Stobias 				prevmsg = xstrdup("");
26084920933Stobias 			logmsg = xstrdup(prevmsg);
26184920933Stobias 			break;
262db758d52Sjoris 		} else {
263db758d52Sjoris 			cvs_log(LP_ERR, "invalid input");
264db758d52Sjoris 			continue;
265db758d52Sjoris 		}
266db758d52Sjoris 	}
267db758d52Sjoris 
268db758d52Sjoris 	(void)fclose(fp);
269db758d52Sjoris 	(void)unlink(fpath);
270397ddb8aSnicm 	free(fpath);
271db758d52Sjoris 
272db758d52Sjoris 	return (logmsg);
273db758d52Sjoris }
27466510bb6Sxsa 
275717c55ebSray /*
276717c55ebSray  * Execute an editor on the specified pathname, which is interpreted
277717c55ebSray  * from the shell.  This means flags may be included.
278717c55ebSray  *
279717c55ebSray  * Returns -1 on error, or the exit value on success.
280717c55ebSray  */
28166510bb6Sxsa int
cvs_logmsg_edit(const char * pathname)28266510bb6Sxsa cvs_logmsg_edit(const char *pathname)
28366510bb6Sxsa {
28466510bb6Sxsa 	char *argp[] = {"sh", "-c", NULL, NULL}, *p;
28566510bb6Sxsa 	sig_t sighup, sigint, sigquit;
28666510bb6Sxsa 	pid_t pid;
28706381711Sray 	int saved_errno, st;
28866510bb6Sxsa 
28966510bb6Sxsa 	(void)xasprintf(&p, "%s %s", cvs_editor, pathname);
29066510bb6Sxsa 	argp[2] = p;
29166510bb6Sxsa 
29266510bb6Sxsa 	sighup = signal(SIGHUP, SIG_IGN);
29366510bb6Sxsa 	sigint = signal(SIGINT, SIG_IGN);
29466510bb6Sxsa 	sigquit = signal(SIGQUIT, SIG_IGN);
29506381711Sray 	if ((pid = fork()) == -1)
29606381711Sray 		goto fail;
29766510bb6Sxsa 	if (pid == 0) {
29866510bb6Sxsa 		execv(_PATH_BSHELL, argp);
29966510bb6Sxsa 		_exit(127);
30066510bb6Sxsa 	}
30106381711Sray 	while (waitpid(pid, &st, 0) == -1)
30266510bb6Sxsa 		if (errno != EINTR)
30306381711Sray 			goto fail;
304397ddb8aSnicm 	free(p);
30566510bb6Sxsa 	(void)signal(SIGHUP, sighup);
30666510bb6Sxsa 	(void)signal(SIGINT, sigint);
30766510bb6Sxsa 	(void)signal(SIGQUIT, sigquit);
30806381711Sray 	if (!WIFEXITED(st)) {
30906381711Sray 		errno = EINTR;
31066510bb6Sxsa 		return (-1);
31166510bb6Sxsa 	}
31206381711Sray 	return (WEXITSTATUS(st));
31306381711Sray 
31406381711Sray  fail:
31506381711Sray 	saved_errno = errno;
31606381711Sray 	(void)signal(SIGHUP, sighup);
31706381711Sray 	(void)signal(SIGINT, sigint);
31806381711Sray 	(void)signal(SIGQUIT, sigquit);
319397ddb8aSnicm 	free(p);
32006381711Sray 	errno = saved_errno;
32106381711Sray 	return (-1);
32266510bb6Sxsa }
323b034d592Sjoris 
324b034d592Sjoris int
cvs_logmsg_verify(char * logmsg)325b034d592Sjoris cvs_logmsg_verify(char *logmsg)
326b034d592Sjoris {
327b034d592Sjoris 	int fd, ret = 0;
328b034d592Sjoris 	char *fpath;
329b034d592Sjoris 	struct trigger_list *line_list;
330b034d592Sjoris 	struct file_info_list files_info;
331b034d592Sjoris 	struct file_info *fi;
332b034d592Sjoris 
333b034d592Sjoris 	line_list = cvs_trigger_getlines(CVS_PATH_VERIFYMSG, "DEFAULT");
334b034d592Sjoris 	if (line_list != NULL) {
335b034d592Sjoris 		TAILQ_INIT(&files_info);
336b034d592Sjoris 
337b034d592Sjoris 		(void)xasprintf(&fpath, "%s/cvsXXXXXXXXXX", cvs_tmpdir);
338b034d592Sjoris 		if ((fd = mkstemp(fpath)) == -1)
339b034d592Sjoris 			fatal("cvs_logmsg_verify: mkstemp %s", strerror(errno));
340b034d592Sjoris 
341b034d592Sjoris 		fi = xcalloc(1, sizeof(*fi));
342b034d592Sjoris 		fi->file_path = xstrdup(fpath);
343b034d592Sjoris 		TAILQ_INSERT_TAIL(&files_info, fi, flist);
344b034d592Sjoris 
345b034d592Sjoris 		if (cvs_trigger_handle(CVS_TRIGGER_VERIFYMSG, NULL, NULL,
346b034d592Sjoris 		    line_list, &files_info)) {
347b034d592Sjoris 			cvs_log(LP_ERR, "Log message check failed");
348b034d592Sjoris 			ret = 1;
349b034d592Sjoris 		}
350b034d592Sjoris 
351b034d592Sjoris 		cvs_trigger_freeinfo(&files_info);
352b034d592Sjoris 		(void)close(fd);
353b034d592Sjoris 		(void)unlink(fpath);
354397ddb8aSnicm 		free(fpath);
355b034d592Sjoris 		cvs_trigger_freelist(line_list);
356b034d592Sjoris 	}
357b034d592Sjoris 
358b034d592Sjoris 	return ret;
359b034d592Sjoris }
360b034d592Sjoris 
361