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