xref: /openbsd/usr.bin/cvs/logmsg.c (revision 3d8817e4)
1 /*	$OpenBSD: logmsg.c,v 1.54 2010/07/23 21:46:05 ray 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 <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #include "cvs.h"
32 
33 #define CVS_LOGMSG_PREFIX		"CVS:"
34 #define CVS_LOGMSG_LINE		\
35 "----------------------------------------------------------------------"
36 
37 int	cvs_logmsg_edit(const char *);
38 
39 char *
40 cvs_logmsg_read(const char *path)
41 {
42 	int fd;
43 	BUF *bp;
44 	FILE *fp;
45 	size_t len;
46 	struct stat st;
47 	char *buf, *lbuf;
48 
49 	if ((fd = open(path, O_RDONLY)) == -1)
50 		fatal("cvs_logmsg_read: open %s", strerror(errno));
51 
52 	if (fstat(fd, &st) == -1)
53 		fatal("cvs_logmsg_read: fstat %s", strerror(errno));
54 
55 	if (!S_ISREG(st.st_mode))
56 		fatal("cvs_logmsg_read: file is not a regular file");
57 
58 	if ((fp = fdopen(fd, "r")) == NULL)
59 		fatal("cvs_logmsg_read: fdopen %s", strerror(errno));
60 
61 	if (st.st_size > SIZE_MAX)
62 		fatal("cvs_logmsg_read: %s: file size too big", path);
63 
64 	lbuf = NULL;
65 	bp = buf_alloc(st.st_size);
66 	while ((buf = fgetln(fp, &len))) {
67 		if (buf[len - 1] == '\n') {
68 			buf[len - 1] = '\0';
69 			--len;
70 		} else {
71 			lbuf = xmalloc(len + 1);
72 			memcpy(lbuf, buf, len);
73 			lbuf[len] = '\0';
74 			buf = lbuf;
75 		}
76 
77 		if (!strncmp(buf, CVS_LOGMSG_PREFIX,
78 		    sizeof(CVS_LOGMSG_PREFIX) - 1))
79 			continue;
80 
81 		buf_append(bp, buf, len);
82 		buf_putc(bp, '\n');
83 	}
84 
85 	if (lbuf != NULL)
86 		xfree(lbuf);
87 
88 	(void)fclose(fp);
89 
90 	buf_putc(bp, '\0');
91 	return (buf_release(bp));
92 }
93 
94 char *
95 cvs_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[MAXPATHLEN];
103 	struct stat st;
104 	struct trigger_list *line_list;
105 	struct trigger_line *line;
106 	static int reuse = 0;
107 	static char *prevmsg = NULL;
108 
109 	if (reuse)
110 		return xstrdup(prevmsg);
111 
112 	(void)xasprintf(&fpath, "%s/cvsXXXXXXXXXX", cvs_tmpdir);
113 
114 	if ((fd = mkstemp(fpath)) == -1)
115 		fatal("cvs_logmsg_create: mkstemp %s", strerror(errno));
116 
117 	worklist_add(fpath, &temp_files);
118 
119 	if ((fp = fdopen(fd, "w")) == NULL) {
120 		saved_errno = errno;
121 		(void)unlink(fpath);
122 		fatal("cvs_logmsg_create: fdopen %s", strerror(saved_errno));
123 	}
124 
125 	if (prevmsg != NULL && prevmsg[0] != '\0')
126 		fprintf(fp, "%s", prevmsg);
127 	else
128 		fputc('\n', fp);
129 
130 	line_list = cvs_trigger_getlines(CVS_PATH_RCSINFO, repo);
131 	if (line_list != NULL) {
132 		TAILQ_FOREACH(line, line_list, flist) {
133 			if ((rd = open(line->line, O_RDONLY)) == -1)
134 				fatal("cvs_logmsg_create: open %s",
135 				    strerror(errno));
136 			if (fstat(rd, &st) == -1)
137 				fatal("cvs_logmsg_create: fstat %s",
138 				    strerror(errno));
139 			if (!S_ISREG(st.st_mode))
140 				fatal("cvs_logmsg_create: file is not a "
141 				    "regular file");
142 			if ((rp = fdopen(rd, "r")) == NULL)
143 				fatal("cvs_logmsg_create: fdopen %s",
144 				    strerror(errno));
145 			if (st.st_size > SIZE_MAX)
146 				fatal("cvs_logmsg_create: %s: file size "
147 				    "too big", line->line);
148 			logmsg = xmalloc(st.st_size);
149 			fread(logmsg, st.st_size, 1, rp);
150 			fwrite(logmsg, st.st_size, 1, fp);
151 			xfree(logmsg);
152 			(void)fclose(rp);
153 		}
154 		cvs_trigger_freelist(line_list);
155 	}
156 
157 	fprintf(fp, "%s %s\n%s Enter Log.  Lines beginning with `%s' are "
158 	    "removed automatically\n%s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE,
159 	    CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX);
160 
161 	if (cvs_cmdop == CVS_OP_COMMIT) {
162 		fprintf(fp, "%s Committing in %s\n%s\n", CVS_LOGMSG_PREFIX,
163 		    dir != NULL ? dir : ".", CVS_LOGMSG_PREFIX);
164 	}
165 
166 	if (added != NULL && !RB_EMPTY(added)) {
167 		fprintf(fp, "%s Added Files:", CVS_LOGMSG_PREFIX);
168 		RB_FOREACH(cf, cvs_flisthead, added)
169 			fprintf(fp, "\n%s\t%s", CVS_LOGMSG_PREFIX,
170 			    dir != NULL ? basename(cf->file_path) :
171 			    cf->file_path);
172 		fputs("\n", fp);
173 	}
174 
175 	if (removed != NULL && !RB_EMPTY(removed)) {
176 		fprintf(fp, "%s Removed Files:", CVS_LOGMSG_PREFIX);
177 		RB_FOREACH(cf, cvs_flisthead, removed)
178 			fprintf(fp, "\n%s\t%s", CVS_LOGMSG_PREFIX,
179 			    dir != NULL ? basename(cf->file_path) :
180 			    cf->file_path);
181 		fputs("\n", fp);
182 	}
183 
184 	if (modified != NULL && !RB_EMPTY(modified)) {
185 		fprintf(fp, "%s Modified Files:", CVS_LOGMSG_PREFIX);
186 		RB_FOREACH(cf, cvs_flisthead, modified)
187 			fprintf(fp, "\n%s\t%s", CVS_LOGMSG_PREFIX,
188 			    dir != NULL ? basename(cf->file_path) :
189 			    cf->file_path);
190 		fputs("\n", fp);
191 	}
192 
193 	fprintf(fp, "%s %s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE);
194 	(void)fflush(fp);
195 
196 	if (fstat(fd, &st1) == -1) {
197 		saved_errno = errno;
198 		(void)unlink(fpath);
199 		fatal("cvs_logmsg_create: fstat %s", strerror(saved_errno));
200 	}
201 
202 	logmsg = NULL;
203 
204 	for (;;) {
205 		if (cvs_logmsg_edit(fpath) == -1)
206 			break;
207 
208 		if (fstat(fd, &st2) == -1) {
209 			saved_errno = errno;
210 			(void)unlink(fpath);
211 			fatal("cvs_logmsg_create: fstat %s",
212 			    strerror(saved_errno));
213 		}
214 
215 		if (st1.st_mtime != st2.st_mtime) {
216 			logmsg = cvs_logmsg_read(fpath);
217 			if (prevmsg != NULL)
218 				xfree(prevmsg);
219 			prevmsg = xstrdup(logmsg);
220 			break;
221 		}
222 
223 		printf("\nLog message unchanged or not specified\n"
224 		    "a)bort, c)ontinue, e)dit, !)reuse this message "
225 		    "unchanged for remaining dirs\nAction: (continue) ");
226 		(void)fflush(stdout);
227 
228 		c = getc(stdin);
229 		if (c == EOF || c == 'a') {
230 			fatal("Aborted by user");
231 		} else if (c == '\n' || c == 'c') {
232 			if (prevmsg == NULL)
233 				prevmsg = xstrdup("");
234 			logmsg = xstrdup(prevmsg);
235 			break;
236 		} else if (c == 'e') {
237 			continue;
238 		} else if (c == '!') {
239 			reuse = 1;
240 			if (prevmsg == NULL)
241 				prevmsg = xstrdup("");
242 			logmsg = xstrdup(prevmsg);
243 			break;
244 		} else {
245 			cvs_log(LP_ERR, "invalid input");
246 			continue;
247 		}
248 	}
249 
250 	(void)fclose(fp);
251 	(void)unlink(fpath);
252 	xfree(fpath);
253 
254 	return (logmsg);
255 }
256 
257 /*
258  * Execute an editor on the specified pathname, which is interpreted
259  * from the shell.  This means flags may be included.
260  *
261  * Returns -1 on error, or the exit value on success.
262  */
263 int
264 cvs_logmsg_edit(const char *pathname)
265 {
266 	char *argp[] = {"sh", "-c", NULL, NULL}, *p;
267 	sig_t sighup, sigint, sigquit;
268 	pid_t pid;
269 	int saved_errno, st;
270 
271 	(void)xasprintf(&p, "%s %s", cvs_editor, pathname);
272 	argp[2] = p;
273 
274 	sighup = signal(SIGHUP, SIG_IGN);
275 	sigint = signal(SIGINT, SIG_IGN);
276 	sigquit = signal(SIGQUIT, SIG_IGN);
277 	if ((pid = fork()) == -1)
278 		goto fail;
279 	if (pid == 0) {
280 		execv(_PATH_BSHELL, argp);
281 		_exit(127);
282 	}
283 	while (waitpid(pid, &st, 0) == -1)
284 		if (errno != EINTR)
285 			goto fail;
286 	xfree(p);
287 	(void)signal(SIGHUP, sighup);
288 	(void)signal(SIGINT, sigint);
289 	(void)signal(SIGQUIT, sigquit);
290 	if (!WIFEXITED(st)) {
291 		errno = EINTR;
292 		return (-1);
293 	}
294 	return (WEXITSTATUS(st));
295 
296  fail:
297 	saved_errno = errno;
298 	(void)signal(SIGHUP, sighup);
299 	(void)signal(SIGINT, sigint);
300 	(void)signal(SIGQUIT, sigquit);
301 	xfree(p);
302 	errno = saved_errno;
303 	return (-1);
304 }
305 
306 int
307 cvs_logmsg_verify(char *logmsg)
308 {
309 	int fd, ret = 0;
310 	char *fpath;
311 	struct trigger_list *line_list;
312 	struct file_info_list files_info;
313 	struct file_info *fi;
314 
315 	line_list = cvs_trigger_getlines(CVS_PATH_VERIFYMSG, "DEFAULT");
316 	if (line_list != NULL) {
317 		TAILQ_INIT(&files_info);
318 
319 		(void)xasprintf(&fpath, "%s/cvsXXXXXXXXXX", cvs_tmpdir);
320 		if ((fd = mkstemp(fpath)) == -1)
321 			fatal("cvs_logmsg_verify: mkstemp %s", strerror(errno));
322 
323 		fi = xcalloc(1, sizeof(*fi));
324 		fi->file_path = xstrdup(fpath);
325 		TAILQ_INSERT_TAIL(&files_info, fi, flist);
326 
327 		if (cvs_trigger_handle(CVS_TRIGGER_VERIFYMSG, NULL, NULL,
328 		    line_list, &files_info)) {
329 			cvs_log(LP_ERR, "Log message check failed");
330 			ret = 1;
331 		}
332 
333 		cvs_trigger_freeinfo(&files_info);
334 		(void)close(fd);
335 		(void)unlink(fpath);
336 		xfree(fpath);
337 		cvs_trigger_freelist(line_list);
338 	}
339 
340 	return ret;
341 }
342 
343