xref: /openbsd-src/usr.bin/cvs/logmsg.c (revision c5f4fad510dd427c0c20c0f4d164f60ce24651b6)
1 /*	$OpenBSD: logmsg.c,v 1.13 2005/04/18 21:02:50 jfb Exp $	*/
2 /*
3  * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. The name of the author may not be used to endorse or promote products
13  *    derived from this software without specific prior written permission.
14  *
15  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 
30 #include <errno.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <string.h>
35 
36 #include "cvs.h"
37 #include "log.h"
38 #include "buf.h"
39 #include "proto.h"
40 
41 
42 #define CVS_LOGMSG_BIGMSG     32000
43 #define CVS_LOGMSG_FTMPL      "/tmp/cvsXXXXXXXXXX"
44 #define CVS_LOGMSG_PREFIX     "CVS:"
45 #define CVS_LOGMSG_LINE \
46 "----------------------------------------------------------------------"
47 
48 
49 static const char *cvs_logmsg_ops[3] = {
50 	"Added", "Modified", "Removed",
51 };
52 
53 
54 /*
55  * cvs_logmsg_open()
56  *
57  * Open the file specified by <path> and allocate a buffer large enough to
58  * hold all of the file's contents.  Lines starting with the log prefix
59  * are not included in the result.
60  * The returned value must later be free()d.
61  * Returns a pointer to the allocated buffer on success, or NULL on failure.
62  */
63 char*
64 cvs_logmsg_open(const char *path)
65 {
66 	int lcont;
67 	size_t len;
68 	char lbuf[256], *msg;
69 	struct stat st;
70 	FILE *fp;
71 	BUF *bp;
72 
73 	if (stat(path, &st) == -1) {
74 		cvs_log(LP_ERRNO, "failed to stat `%s'", path);
75 		return (NULL);
76 	}
77 
78 	if (!S_ISREG(st.st_mode)) {
79 		cvs_log(LP_ERR, "message file must be a regular file");
80 		return (NULL);
81 	}
82 
83 	if (st.st_size > CVS_LOGMSG_BIGMSG) {
84 		do {
85 			fprintf(stderr,
86 			    "The specified message file seems big.  "
87 			    "Proceed anyways? (y/n) ");
88 			if (fgets(lbuf, sizeof(lbuf), stdin) == NULL) {
89 				cvs_log(LP_ERRNO,
90 				    "failed to read from standard input");
91 				return (NULL);
92 			}
93 
94 			len = strlen(lbuf);
95 			if ((len == 0) || (len > 2) ||
96 			    ((lbuf[0] != 'y') && (lbuf[0] != 'n'))) {
97 				fprintf(stderr, "invalid input\n");
98 				continue;
99 			} else if (lbuf[0] == 'y')
100 				break;
101 			else if (lbuf[0] == 'n') {
102 				cvs_log(LP_ERR, "aborted by user");
103 				return (NULL);
104 			}
105 
106 		} while (1);
107 	}
108 
109 	if ((fp = fopen(path, "r")) == NULL) {
110 		cvs_log(LP_ERRNO, "failed to open message file `%s'", path);
111 		return (NULL);
112 	}
113 
114 	bp = cvs_buf_alloc(128, BUF_AUTOEXT);
115 	if (bp == NULL) {
116 		(void)fclose(fp);
117 		return (NULL);
118 	}
119 
120 	/* lcont is used to tell if a buffer returned by fgets is a start
121 	 * of line or just line continuation because the buffer isn't
122 	 * large enough to hold the entire line.
123 	 */
124 	lcont = 0;
125 
126 	while (fgets(lbuf, sizeof(lbuf), fp) != NULL) {
127 		len = strlen(lbuf);
128 		if (len == 0)
129 			continue;
130 		else if ((lcont == 0) && (strncmp(lbuf, CVS_LOGMSG_PREFIX,
131 		    strlen(CVS_LOGMSG_PREFIX)) == 0))
132 			/* skip lines starting with the prefix */
133 			continue;
134 
135 		if (cvs_buf_append(bp, lbuf, strlen(lbuf)) < 0) {
136 			cvs_buf_free(bp);
137 			(void)fclose(fp);
138 			return (NULL);
139 		}
140 
141 		lcont = (lbuf[len - 1] == '\n') ? 0 : 1;
142 	}
143 	(void)fclose(fp);
144 
145 	if (cvs_buf_putc(bp, '\0') < 0) {
146 		cvs_buf_free(bp);
147 		return (NULL);
148 	}
149 
150 	msg = (char *)cvs_buf_release(bp);
151 
152 	return (msg);
153 }
154 
155 
156 /*
157  * cvs_logmsg_get()
158  *
159  * Get a log message by forking and executing the user's editor.  The <dir>
160  * argument is a relative path to the directory for which the log message
161  * applies, and the 3 tail queue arguments contains all the files for which the
162  * log message will apply.  Any of these arguments can be set to NULL in the
163  * case where there is no information to display.
164  * Returns the message in a dynamically allocated string on success, NULL on
165  * failure.
166  */
167 char*
168 cvs_logmsg_get(const char *dir, struct cvs_flist *added,
169     struct cvs_flist *modified, struct cvs_flist *removed)
170 {
171 	int i, fd, argc, fds[3], nl;
172 	size_t len, tlen;
173 	char *argv[4], buf[16], path[MAXPATHLEN], fpath[MAXPATHLEN], *msg;
174 	FILE *fp;
175 	CVSFILE *cvsfp;
176 	struct stat st1, st2;
177 	struct cvs_flist *files[3];
178 
179 	files[0] = added;
180 	files[1] = modified;
181 	files[2] = removed;
182 
183 	msg = NULL;
184 	fds[0] = -1;
185 	fds[1] = -1;
186 	fds[2] = -1;
187 	strlcpy(path, CVS_LOGMSG_FTMPL, sizeof(path));
188 	argc = 0;
189 	argv[argc++] = cvs_editor;
190 	argv[argc++] = path;
191 	argv[argc] = NULL;
192 
193 	if ((fd = mkstemp(path)) == -1) {
194 		cvs_log(LP_ERRNO, "failed to create temporary file");
195 		return (NULL);
196 	}
197 
198 	fp = fdopen(fd, "w");
199 	if (fp == NULL) {
200 		cvs_log(LP_ERRNO, "failed to fdopen");
201 		(void)close(fd);
202 		if (unlink(path) == -1)
203 			cvs_log(LP_ERRNO, "failed to unlink temporary file");
204 		return (NULL);
205 	}
206 
207 	fprintf(fp, "\n%s %s\n%s Enter Log.  Lines beginning with `%s' are "
208 	    "removed automatically\n%s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE,
209 	    CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX);
210 
211 	if (dir != NULL)
212 		fprintf(fp, "%s Commiting in %s\n%s\n", CVS_LOGMSG_PREFIX, dir,
213 		    CVS_LOGMSG_PREFIX);
214 
215 	for (i = 0; i < 3; i++) {
216 		if (files[i] == NULL)
217 			continue;
218 
219 		fprintf(fp, "%s %s Files:", CVS_LOGMSG_PREFIX,
220 		    cvs_logmsg_ops[i]);
221 		nl = 1;
222 		SIMPLEQ_FOREACH(cvsfp, files[i], cf_list) {
223 			/* take the space into account */
224 			cvs_file_getpath(cvsfp, fpath, sizeof(fpath));
225 			len = strlen(fpath) + 1;
226 			if (tlen + len >= 72)
227 				nl = 1;
228 
229 			if (nl) {
230 				fprintf(fp, "\n%s\t", CVS_LOGMSG_PREFIX);
231 				tlen = 8;
232 				nl = 0;
233 			}
234 
235 			fprintf(fp, " %s", fpath);
236 			tlen += len;
237 		}
238 		fputc('\n', fp);
239 
240 	}
241 	fprintf(fp, "%s %s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE);
242 	(void)fflush(fp);
243 
244 	if (fstat(fd, &st1) == -1) {
245 		cvs_log(LP_ERRNO, "failed to stat log message file");
246 
247 		(void)fclose(fp);
248 		if (unlink(path) == -1)
249 			cvs_log(LP_ERRNO, "failed to unlink log file %s", path);
250 		return (NULL);
251 	}
252 
253 	for (;;) {
254 		if (cvs_exec(argc, argv, fds) < 0)
255 			break;
256 
257 		if (fstat(fd, &st2) == -1) {
258 			cvs_log(LP_ERRNO, "failed to stat log message file");
259 			break;
260 		}
261 
262 		if (st2.st_mtime != st1.st_mtime) {
263 			msg = cvs_logmsg_open(path);
264 			break;
265 		}
266 
267 		/* nothing was entered */
268 		fprintf(stderr,
269 		    "Log message unchanged or not specified\na)bort, "
270 		    "c)ontinue, e)dit, !)reuse this message unchanged "
271 		    "for remaining dirs\nAction: (continue) ");
272 
273 		if (fgets(buf, sizeof(buf), stdin) == NULL) {
274 			cvs_log(LP_ERRNO, "failed to read from standard input");
275 			break;
276 		}
277 
278 		len = strlen(buf);
279 		if ((len == 0) || (len > 2)) {
280 			fprintf(stderr, "invalid input\n");
281 			continue;
282 		} else if (buf[0] == 'a') {
283 			cvs_log(LP_ERR, "aborted by user");
284 			break;
285 		} else if ((buf[0] == '\n') || (buf[0] == 'c')) {
286 			/* empty message */
287 			msg = strdup("");
288 			break;
289 		} else if (buf[0] == 'e')
290 			continue;
291 		else if (buf[0] == '!') {
292 			/* XXX do something */
293 		}
294 	}
295 
296 	(void)fclose(fp);
297 	(void)close(fd);
298 
299 	if (unlink(path) == -1)
300 		cvs_log(LP_ERRNO, "failed to unlink log file %s", path);
301 
302 	return (msg);
303 }
304 
305 
306 /*
307  * cvs_logmsg_send()
308  *
309  */
310 int
311 cvs_logmsg_send(struct cvsroot *root, const char *msg)
312 {
313 	const char *mp;
314 	char *np, buf[256];
315 
316 	if (cvs_sendarg(root, "-m", 0) < 0) {
317 		cvs_log(LP_ERR, "failed to send log message");
318 		return (-1);
319 	}
320 
321 	for (mp = msg; mp != NULL; mp = strchr(mp, '\n')) {
322 		if (*mp == '\n')
323 			mp++;
324 
325 		/* XXX ghetto */
326 		strlcpy(buf, mp, sizeof(buf));
327 		np = strchr(buf, '\n');
328 		if (np != NULL)
329 			*np = '\0';
330 		if (cvs_sendarg(root, buf, (mp == msg) ? 0 : 1) < 0) {
331 			cvs_log(LP_ERR, "failed to send log message");
332 			return (-1);
333 		}
334 	}
335 
336 	return (0);
337 }
338