xref: /openbsd-src/usr.bin/cvs/logmsg.c (revision 11efff7f3ac2b3cfeff0c0cddc14294d9b3aca4f)
1 /*	$OpenBSD: logmsg.c,v 1.10 2004/12/08 21:49:02 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 		return (NULL);
117 
118 	/* lcont is used to tell if a buffer returned by fgets is a start
119 	 * of line or just line continuation because the buffer isn't
120 	 * large enough to hold the entire line.
121 	 */
122 	lcont = 0;
123 
124 	while (fgets(lbuf, sizeof(lbuf), fp) != NULL) {
125 		len = strlen(lbuf);
126 		if (len == 0)
127 			continue;
128 		else if ((lcont == 0) && (strncmp(lbuf, CVS_LOGMSG_PREFIX,
129 		    strlen(CVS_LOGMSG_PREFIX)) == 0))
130 			/* skip lines starting with the prefix */
131 			continue;
132 
133 		cvs_buf_append(bp, lbuf, strlen(lbuf));
134 
135 		lcont = (lbuf[len - 1] == '\n') ? 0 : 1;
136 	}
137 	cvs_buf_putc(bp, '\0');
138 
139 	msg = (char *)cvs_buf_release(bp);
140 
141 	return (msg);
142 }
143 
144 
145 /*
146  * cvs_logmsg_get()
147  *
148  * Get a log message by forking and executing the user's editor.  The <dir>
149  * argument is a relative path to the directory for which the log message
150  * applies, and the 3 tail queue arguemnts contains all the files for which the
151  * log message will apply.  Any of these arguments can be set to NULL in the
152  * case where there is no information to display.
153  * Returns the message in a dynamically allocated string on success, NULL on
154  * failure.
155  */
156 char*
157 cvs_logmsg_get(const char *dir, struct cvs_flist *added,
158     struct cvs_flist *modified, struct cvs_flist *removed)
159 {
160 	int i, fd, argc, fds[3], nl;
161 	size_t len, tlen;
162 	char *argv[4], buf[16], path[MAXPATHLEN], fpath[MAXPATHLEN], *msg;
163 	FILE *fp;
164 	CVSFILE *cvsfp;
165 	struct stat st1, st2;
166 	struct cvs_flist *files[3];
167 
168 	files[0] = added;
169 	files[1] = modified;
170 	files[2] = removed;
171 
172 	msg = NULL;
173 	fds[0] = -1;
174 	fds[1] = -1;
175 	fds[2] = -1;
176 	strlcpy(path, CVS_LOGMSG_FTMPL, sizeof(path));
177 	argc = 0;
178 	argv[argc++] = cvs_editor;
179 	argv[argc++] = path;
180 	argv[argc] = NULL;
181 
182 	if ((fd = mkstemp(path)) == -1) {
183 		cvs_log(LP_ERRNO, "failed to create temporary file");
184 		return (NULL);
185 	}
186 
187 	fp = fdopen(fd, "w");
188 	if (fp == NULL) {
189 		cvs_log(LP_ERRNO, "failed to fdopen");
190 		(void)close(fd);
191 		if (unlink(path) == -1)
192 			cvs_log(LP_ERRNO, "failed to unlink temporary file");
193 		return (NULL);
194 	}
195 
196 	fprintf(fp, "\n%s %s\n%s Enter Log.  Lines beginning with `%s' are "
197 	    "removed automatically\n%s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE,
198 	    CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX);
199 
200 	if (dir != NULL)
201 		fprintf(fp, "%s Commiting in %s\n%s\n", CVS_LOGMSG_PREFIX, dir,
202 		    CVS_LOGMSG_PREFIX);
203 
204 	for (i = 0; i < 3; i++) {
205 		if (files[i] == NULL)
206 			continue;
207 
208 		fprintf(fp, "%s %s Files:", CVS_LOGMSG_PREFIX,
209 		    cvs_logmsg_ops[i]);
210 		nl = 1;
211 		TAILQ_FOREACH(cvsfp, files[i], cf_list) {
212 			/* take the space into account */
213 			cvs_file_getpath(cvsfp, fpath, sizeof(fpath));
214 			len = strlen(fpath) + 1;
215 			if (tlen + len >= 72)
216 				nl = 1;
217 
218 			if (nl) {
219 				fprintf(fp, "\n%s\t", CVS_LOGMSG_PREFIX);
220 				tlen = 8;
221 				nl = 0;
222 			}
223 
224 			fprintf(fp, " %s", fpath);
225 			tlen += len;
226 		}
227 		fputc('\n', fp);
228 
229 	}
230 	fprintf(fp, "%s %s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE);
231 	(void)fflush(fp);
232 
233 	if (fstat(fd, &st1) == -1) {
234 		cvs_log(LP_ERRNO, "failed to stat log message file");
235 
236 		(void)fclose(fp);
237 		if (unlink(path) == -1)
238 			cvs_log(LP_ERRNO, "failed to unlink log file %s", path);
239 		return (NULL);
240 	}
241 
242 	for (;;) {
243 		if (cvs_exec(argc, argv, fds) < 0)
244 			break;
245 
246 		if (fstat(fd, &st2) == -1) {
247 			cvs_log(LP_ERRNO, "failed to stat log message file");
248 			break;
249 		}
250 
251 		if (st2.st_mtime != st1.st_mtime) {
252 			msg = cvs_logmsg_open(path);
253 			break;
254 		}
255 
256 		/* nothing was entered */
257 		fprintf(stderr,
258 		    "Log message unchanged or not specified\na)bort, "
259 		    "c)ontinue, e)dit, !)reuse this message unchanged "
260 		    "for remaining dirs\nAction: (continue) ");
261 
262 		if (fgets(buf, sizeof(buf), stdin) == NULL) {
263 			cvs_log(LP_ERRNO, "failed to read from standard input");
264 			break;
265 		}
266 
267 		len = strlen(buf);
268 		if ((len == 0) || (len > 2)) {
269 			fprintf(stderr, "invalid input\n");
270 			continue;
271 		} else if (buf[0] == 'a') {
272 			cvs_log(LP_ERR, "aborted by user");
273 			break;
274 		} else if ((buf[0] == '\n') || (buf[0] == 'c')) {
275 			/* empty message */
276 			msg = strdup("");
277 			break;
278 		} else if (buf[0] == 'e')
279 			continue;
280 		else if (buf[0] == '!') {
281 			/* XXX do something */
282 		}
283 	}
284 
285 	(void)fclose(fp);
286 	(void)close(fd);
287 
288 	if (unlink(path) == -1)
289 		cvs_log(LP_ERRNO, "failed to unlink log file %s", path);
290 
291 	return (msg);
292 }
293 
294 
295 /*
296  * cvs_logmsg_send()
297  *
298  */
299 int
300 cvs_logmsg_send(struct cvsroot *root, const char *msg)
301 {
302 	const char *mp;
303 	char *np, buf[256];
304 
305 	if (cvs_sendarg(root, "-m", 0) < 0) {
306 		cvs_log(LP_ERR, "failed to send log message");
307 		return (-1);
308 	}
309 
310 	for (mp = msg; mp != NULL; mp = strchr(mp, '\n')) {
311 		if (*mp == '\n')
312 			mp++;
313 
314 		/* XXX ghetto */
315 		strlcpy(buf, mp, sizeof(buf));
316 		np = strchr(buf, '\n');
317 		if (np != NULL)
318 			*np = '\0';
319 		if (cvs_sendarg(root, buf, (mp == msg) ? 0 : 1) < 0) {
320 			cvs_log(LP_ERR, "failed to send log message");
321 			return (-1);
322 		}
323 	}
324 
325 	return (0);
326 }
327