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 * 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 * 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 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 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