xref: /openbsd-src/usr.bin/cvs/logmsg.c (revision cb39b41371628601fbe4c618205356d538b9d08a)
1 /*	$OpenBSD: logmsg.c,v 1.56 2015/02/05 12:59:57 millert 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 
38 int	cvs_logmsg_edit(const char *);
39 
40 char *
41 cvs_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 (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 	if (lbuf != NULL)
87 		xfree(lbuf);
88 
89 	(void)fclose(fp);
90 
91 	buf_putc(bp, '\0');
92 	return (buf_release(bp));
93 }
94 
95 char *
96 cvs_logmsg_create(char *dir, struct cvs_flisthead *added,
97     struct cvs_flisthead *removed, struct cvs_flisthead *modified)
98 {
99 	FILE *fp, *rp;
100 	int c, fd, rd, saved_errno;
101 	struct cvs_filelist *cf;
102 	struct stat st1, st2;
103 	char *fpath, *logmsg, repo[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 (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 			xfree(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 			fprintf(fp, "\n%s\t%s", CVS_LOGMSG_PREFIX,
171 			    dir != NULL ? basename(cf->file_path) :
172 			    cf->file_path);
173 		fputs("\n", fp);
174 	}
175 
176 	if (removed != NULL && !RB_EMPTY(removed)) {
177 		fprintf(fp, "%s Removed Files:", CVS_LOGMSG_PREFIX);
178 		RB_FOREACH(cf, cvs_flisthead, removed)
179 			fprintf(fp, "\n%s\t%s", CVS_LOGMSG_PREFIX,
180 			    dir != NULL ? basename(cf->file_path) :
181 			    cf->file_path);
182 		fputs("\n", fp);
183 	}
184 
185 	if (modified != NULL && !RB_EMPTY(modified)) {
186 		fprintf(fp, "%s Modified Files:", CVS_LOGMSG_PREFIX);
187 		RB_FOREACH(cf, cvs_flisthead, modified)
188 			fprintf(fp, "\n%s\t%s", CVS_LOGMSG_PREFIX,
189 			    dir != NULL ? basename(cf->file_path) :
190 			    cf->file_path);
191 		fputs("\n", fp);
192 	}
193 
194 	fprintf(fp, "%s %s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE);
195 	(void)fflush(fp);
196 
197 	if (fstat(fd, &st1) == -1) {
198 		saved_errno = errno;
199 		(void)unlink(fpath);
200 		fatal("cvs_logmsg_create: fstat %s", strerror(saved_errno));
201 	}
202 
203 	logmsg = NULL;
204 
205 	for (;;) {
206 		if (cvs_logmsg_edit(fpath) == -1)
207 			break;
208 
209 		if (fstat(fd, &st2) == -1) {
210 			saved_errno = errno;
211 			(void)unlink(fpath);
212 			fatal("cvs_logmsg_create: fstat %s",
213 			    strerror(saved_errno));
214 		}
215 
216 		if (st1.st_mtime != st2.st_mtime) {
217 			logmsg = cvs_logmsg_read(fpath);
218 			if (prevmsg != NULL)
219 				xfree(prevmsg);
220 			prevmsg = xstrdup(logmsg);
221 			break;
222 		}
223 
224 		printf("\nLog message unchanged or not specified\n"
225 		    "a)bort, c)ontinue, e)dit, !)reuse this message "
226 		    "unchanged for remaining dirs\nAction: (continue) ");
227 		(void)fflush(stdout);
228 
229 		c = getc(stdin);
230 		if (c == EOF || c == 'a') {
231 			fatal("Aborted by user");
232 		} else if (c == '\n' || c == 'c') {
233 			if (prevmsg == NULL)
234 				prevmsg = xstrdup("");
235 			logmsg = xstrdup(prevmsg);
236 			break;
237 		} else if (c == 'e') {
238 			continue;
239 		} else if (c == '!') {
240 			reuse = 1;
241 			if (prevmsg == NULL)
242 				prevmsg = xstrdup("");
243 			logmsg = xstrdup(prevmsg);
244 			break;
245 		} else {
246 			cvs_log(LP_ERR, "invalid input");
247 			continue;
248 		}
249 	}
250 
251 	(void)fclose(fp);
252 	(void)unlink(fpath);
253 	xfree(fpath);
254 
255 	return (logmsg);
256 }
257 
258 /*
259  * Execute an editor on the specified pathname, which is interpreted
260  * from the shell.  This means flags may be included.
261  *
262  * Returns -1 on error, or the exit value on success.
263  */
264 int
265 cvs_logmsg_edit(const char *pathname)
266 {
267 	char *argp[] = {"sh", "-c", NULL, NULL}, *p;
268 	sig_t sighup, sigint, sigquit;
269 	pid_t pid;
270 	int saved_errno, st;
271 
272 	(void)xasprintf(&p, "%s %s", cvs_editor, pathname);
273 	argp[2] = p;
274 
275 	sighup = signal(SIGHUP, SIG_IGN);
276 	sigint = signal(SIGINT, SIG_IGN);
277 	sigquit = signal(SIGQUIT, SIG_IGN);
278 	if ((pid = fork()) == -1)
279 		goto fail;
280 	if (pid == 0) {
281 		execv(_PATH_BSHELL, argp);
282 		_exit(127);
283 	}
284 	while (waitpid(pid, &st, 0) == -1)
285 		if (errno != EINTR)
286 			goto fail;
287 	xfree(p);
288 	(void)signal(SIGHUP, sighup);
289 	(void)signal(SIGINT, sigint);
290 	(void)signal(SIGQUIT, sigquit);
291 	if (!WIFEXITED(st)) {
292 		errno = EINTR;
293 		return (-1);
294 	}
295 	return (WEXITSTATUS(st));
296 
297  fail:
298 	saved_errno = errno;
299 	(void)signal(SIGHUP, sighup);
300 	(void)signal(SIGINT, sigint);
301 	(void)signal(SIGQUIT, sigquit);
302 	xfree(p);
303 	errno = saved_errno;
304 	return (-1);
305 }
306 
307 int
308 cvs_logmsg_verify(char *logmsg)
309 {
310 	int fd, ret = 0;
311 	char *fpath;
312 	struct trigger_list *line_list;
313 	struct file_info_list files_info;
314 	struct file_info *fi;
315 
316 	line_list = cvs_trigger_getlines(CVS_PATH_VERIFYMSG, "DEFAULT");
317 	if (line_list != NULL) {
318 		TAILQ_INIT(&files_info);
319 
320 		(void)xasprintf(&fpath, "%s/cvsXXXXXXXXXX", cvs_tmpdir);
321 		if ((fd = mkstemp(fpath)) == -1)
322 			fatal("cvs_logmsg_verify: mkstemp %s", strerror(errno));
323 
324 		fi = xcalloc(1, sizeof(*fi));
325 		fi->file_path = xstrdup(fpath);
326 		TAILQ_INSERT_TAIL(&files_info, fi, flist);
327 
328 		if (cvs_trigger_handle(CVS_TRIGGER_VERIFYMSG, NULL, NULL,
329 		    line_list, &files_info)) {
330 			cvs_log(LP_ERR, "Log message check failed");
331 			ret = 1;
332 		}
333 
334 		cvs_trigger_freeinfo(&files_info);
335 		(void)close(fd);
336 		(void)unlink(fpath);
337 		xfree(fpath);
338 		cvs_trigger_freelist(line_list);
339 	}
340 
341 	return ret;
342 }
343 
344