xref: /openbsd-src/usr.bin/cvs/logmsg.c (revision 94fd4554194a14f126fba33b837cc68a1df42468)
1 /*	$OpenBSD: logmsg.c,v 1.41 2007/05/11 02:43:24 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 <paths.h>
25 #include <signal.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 #include "cvs.h"
30 
31 #define CVS_LOGMSG_PREFIX		"CVS:"
32 #define CVS_LOGMSG_LINE		\
33 "----------------------------------------------------------------------"
34 
35 int	cvs_logmsg_edit(const char *);
36 
37 char *
38 cvs_logmsg_read(const char *path)
39 {
40 	int fd;
41 	BUF *bp;
42 	FILE *fp;
43 	size_t len;
44 	struct stat st;
45 	char *buf, *lbuf;
46 
47 	if ((fd = open(path, O_RDONLY)) == -1)
48 		fatal("cvs_logmsg_read: open %s", strerror(errno));
49 
50 	if (fstat(fd, &st) == -1)
51 		fatal("cvs_logmsg_read: fstat %s", strerror(errno));
52 
53 	if (!S_ISREG(st.st_mode))
54 		fatal("cvs_logmsg_read: file is not a regular file");
55 
56 	if ((fp = fdopen(fd, "r")) == NULL)
57 		fatal("cvs_logmsg_read: fdopen %s", strerror(errno));
58 
59 	lbuf = NULL;
60 	bp = cvs_buf_alloc(st.st_size, BUF_AUTOEXT);
61 	while ((buf = fgetln(fp, &len))) {
62 		if (buf[len - 1] == '\n') {
63 			buf[len - 1] = '\0';
64 		} else {
65 			lbuf = xmalloc(len + 1);
66 			memcpy(lbuf, buf, len);
67 			lbuf[len] = '\0';
68 			buf = lbuf;
69 		}
70 
71 		len = strlen(buf);
72 		if (len == 0)
73 			continue;
74 
75 		if (!strncmp(buf, CVS_LOGMSG_PREFIX,
76 		    strlen(CVS_LOGMSG_PREFIX)))
77 			continue;
78 
79 		cvs_buf_append(bp, buf, len);
80 		cvs_buf_putc(bp, '\n');
81 	}
82 
83 	if (lbuf != NULL)
84 		xfree(lbuf);
85 
86 	(void)fclose(fp);
87 
88 	cvs_buf_putc(bp, '\0');
89 	return (cvs_buf_release(bp));
90 }
91 
92 char *
93 cvs_logmsg_create(struct cvs_flisthead *added, struct cvs_flisthead *removed,
94 	struct cvs_flisthead *modified)
95 {
96 	FILE *fp;
97 	int c, fd, saved_errno;
98 	struct cvs_filelist *cf;
99 	struct stat st1, st2;
100 	char *fpath, *logmsg;
101 
102 	(void)xasprintf(&fpath, "%s/cvsXXXXXXXXXX", cvs_tmpdir);
103 
104 	if ((fd = mkstemp(fpath)) == NULL)
105 		fatal("cvs_logmsg_create: mkstemp %s", strerror(errno));
106 
107 	cvs_worklist_add(fpath, &temp_files);
108 
109 	if ((fp = fdopen(fd, "w")) == NULL) {
110 		saved_errno = errno;
111 		(void)unlink(fpath);
112 		fatal("cvs_logmsg_create: fdopen %s", strerror(saved_errno));
113 	}
114 
115 	fprintf(fp, "\n%s %s\n%s Enter Log.  Lines beginning with `%s' are "
116 	    "removed automatically\n%s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE,
117 	    CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX);
118 
119 	if (added != NULL && !TAILQ_EMPTY(added)) {
120 		fprintf(fp, "%s Added Files:", CVS_LOGMSG_PREFIX);
121 		TAILQ_FOREACH(cf, added, flist)
122 			fprintf(fp, "\n%s\t%s",
123 			    CVS_LOGMSG_PREFIX, cf->file_path);
124 		fputs("\n", fp);
125 	}
126 
127 	if (removed != NULL && !TAILQ_EMPTY(removed)) {
128 		fprintf(fp, "%s Removed Files:", CVS_LOGMSG_PREFIX);
129 		TAILQ_FOREACH(cf, removed, flist)
130 			fprintf(fp, "\n%s\t%s",
131 			    CVS_LOGMSG_PREFIX, cf->file_path);
132 		fputs("\n", fp);
133 	}
134 
135 	if (modified != NULL && !TAILQ_EMPTY(modified)) {
136 		fprintf(fp, "%s Modified Files:", CVS_LOGMSG_PREFIX);
137 		TAILQ_FOREACH(cf, modified, flist)
138 			fprintf(fp, "\n%s\t%s",
139 			    CVS_LOGMSG_PREFIX, cf->file_path);
140 		fputs("\n", fp);
141 	}
142 
143 	fprintf(fp, "%s %s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE);
144 	(void)fflush(fp);
145 
146 	if (fstat(fd, &st1) == -1) {
147 		saved_errno = errno;
148 		(void)unlink(fpath);
149 		fatal("cvs_logmsg_create: fstat %s", strerror(saved_errno));
150 	}
151 
152 	logmsg = NULL;
153 
154 	for (;;) {
155 		if (cvs_logmsg_edit(fpath) == -1)
156 			break;
157 
158 		if (fstat(fd, &st2) == -1) {
159 			saved_errno = errno;
160 			(void)unlink(fpath);
161 			fatal("cvs_logmsg_create: fstat %s",
162 			    strerror(saved_errno));
163 		}
164 
165 		if (st1.st_mtime != st2.st_mtime) {
166 			logmsg = cvs_logmsg_read(fpath);
167 			break;
168 		}
169 
170 		printf("\nLog message unchanged or not specified\n"
171 		    "a)bort, c)ontinue, e)dit\nAction: (continue) ");
172 		(void)fflush(stdout);
173 
174 		c = getc(stdin);
175 		if (c == 'a') {
176 			fatal("Aborted by user");
177 		} else if (c == '\n' || c == 'c') {
178 			logmsg = xstrdup("");
179 			break;
180 		} else if (c == 'e') {
181 			continue;
182 		} else {
183 			cvs_log(LP_ERR, "invalid input");
184 			continue;
185 		}
186 	}
187 
188 	(void)fclose(fp);
189 	(void)unlink(fpath);
190 	xfree(fpath);
191 
192 	return (logmsg);
193 }
194 
195 /*
196  * Execute an editor on the specified pathname, which is interpreted
197  * from the shell.  This means flags may be included.
198  *
199  * Returns -1 on error, or the exit value on success.
200  */
201 int
202 cvs_logmsg_edit(const char *pathname)
203 {
204 	char *argp[] = {"sh", "-c", NULL, NULL}, *p;
205 	sig_t sighup, sigint, sigquit;
206 	pid_t pid;
207 	int saved_errno, st;
208 
209 	(void)xasprintf(&p, "%s %s", cvs_editor, pathname);
210 	argp[2] = p;
211 
212 	sighup = signal(SIGHUP, SIG_IGN);
213 	sigint = signal(SIGINT, SIG_IGN);
214 	sigquit = signal(SIGQUIT, SIG_IGN);
215 	if ((pid = fork()) == -1)
216 		goto fail;
217 	if (pid == 0) {
218 		execv(_PATH_BSHELL, argp);
219 		_exit(127);
220 	}
221 	while (waitpid(pid, &st, 0) == -1)
222 		if (errno != EINTR)
223 			goto fail;
224 	xfree(p);
225 	(void)signal(SIGHUP, sighup);
226 	(void)signal(SIGINT, sigint);
227 	(void)signal(SIGQUIT, sigquit);
228 	if (!WIFEXITED(st)) {
229 		errno = EINTR;
230 		return (-1);
231 	}
232 	return (WEXITSTATUS(st));
233 
234  fail:
235 	saved_errno = errno;
236 	(void)signal(SIGHUP, sighup);
237 	(void)signal(SIGINT, sigint);
238 	(void)signal(SIGQUIT, sigquit);
239 	xfree(p);
240 	errno = saved_errno;
241 	return (-1);
242 }
243