1 /* $OpenBSD: logmsg.c,v 1.54 2010/07/23 21:46:05 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 <libgen.h> 25 #include <paths.h> 26 #include <signal.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <unistd.h> 30 31 #include "cvs.h" 32 33 #define CVS_LOGMSG_PREFIX "CVS:" 34 #define CVS_LOGMSG_LINE \ 35 "----------------------------------------------------------------------" 36 37 int cvs_logmsg_edit(const char *); 38 39 char * 40 cvs_logmsg_read(const char *path) 41 { 42 int fd; 43 BUF *bp; 44 FILE *fp; 45 size_t len; 46 struct stat st; 47 char *buf, *lbuf; 48 49 if ((fd = open(path, O_RDONLY)) == -1) 50 fatal("cvs_logmsg_read: open %s", strerror(errno)); 51 52 if (fstat(fd, &st) == -1) 53 fatal("cvs_logmsg_read: fstat %s", strerror(errno)); 54 55 if (!S_ISREG(st.st_mode)) 56 fatal("cvs_logmsg_read: file is not a regular file"); 57 58 if ((fp = fdopen(fd, "r")) == NULL) 59 fatal("cvs_logmsg_read: fdopen %s", strerror(errno)); 60 61 if (st.st_size > SIZE_MAX) 62 fatal("cvs_logmsg_read: %s: file size too big", path); 63 64 lbuf = NULL; 65 bp = buf_alloc(st.st_size); 66 while ((buf = fgetln(fp, &len))) { 67 if (buf[len - 1] == '\n') { 68 buf[len - 1] = '\0'; 69 --len; 70 } else { 71 lbuf = xmalloc(len + 1); 72 memcpy(lbuf, buf, len); 73 lbuf[len] = '\0'; 74 buf = lbuf; 75 } 76 77 if (!strncmp(buf, CVS_LOGMSG_PREFIX, 78 sizeof(CVS_LOGMSG_PREFIX) - 1)) 79 continue; 80 81 buf_append(bp, buf, len); 82 buf_putc(bp, '\n'); 83 } 84 85 if (lbuf != NULL) 86 xfree(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[MAXPATHLEN]; 103 struct stat st; 104 struct trigger_list *line_list; 105 struct trigger_line *line; 106 static int reuse = 0; 107 static char *prevmsg = NULL; 108 109 if (reuse) 110 return xstrdup(prevmsg); 111 112 (void)xasprintf(&fpath, "%s/cvsXXXXXXXXXX", cvs_tmpdir); 113 114 if ((fd = mkstemp(fpath)) == -1) 115 fatal("cvs_logmsg_create: mkstemp %s", strerror(errno)); 116 117 worklist_add(fpath, &temp_files); 118 119 if ((fp = fdopen(fd, "w")) == NULL) { 120 saved_errno = errno; 121 (void)unlink(fpath); 122 fatal("cvs_logmsg_create: fdopen %s", strerror(saved_errno)); 123 } 124 125 if (prevmsg != NULL && prevmsg[0] != '\0') 126 fprintf(fp, "%s", prevmsg); 127 else 128 fputc('\n', fp); 129 130 line_list = cvs_trigger_getlines(CVS_PATH_RCSINFO, repo); 131 if (line_list != NULL) { 132 TAILQ_FOREACH(line, line_list, flist) { 133 if ((rd = open(line->line, O_RDONLY)) == -1) 134 fatal("cvs_logmsg_create: open %s", 135 strerror(errno)); 136 if (fstat(rd, &st) == -1) 137 fatal("cvs_logmsg_create: fstat %s", 138 strerror(errno)); 139 if (!S_ISREG(st.st_mode)) 140 fatal("cvs_logmsg_create: file is not a " 141 "regular file"); 142 if ((rp = fdopen(rd, "r")) == NULL) 143 fatal("cvs_logmsg_create: fdopen %s", 144 strerror(errno)); 145 if (st.st_size > SIZE_MAX) 146 fatal("cvs_logmsg_create: %s: file size " 147 "too big", line->line); 148 logmsg = xmalloc(st.st_size); 149 fread(logmsg, st.st_size, 1, rp); 150 fwrite(logmsg, st.st_size, 1, fp); 151 xfree(logmsg); 152 (void)fclose(rp); 153 } 154 cvs_trigger_freelist(line_list); 155 } 156 157 fprintf(fp, "%s %s\n%s Enter Log. Lines beginning with `%s' are " 158 "removed automatically\n%s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE, 159 CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX); 160 161 if (cvs_cmdop == CVS_OP_COMMIT) { 162 fprintf(fp, "%s Committing in %s\n%s\n", CVS_LOGMSG_PREFIX, 163 dir != NULL ? dir : ".", CVS_LOGMSG_PREFIX); 164 } 165 166 if (added != NULL && !RB_EMPTY(added)) { 167 fprintf(fp, "%s Added Files:", CVS_LOGMSG_PREFIX); 168 RB_FOREACH(cf, cvs_flisthead, added) 169 fprintf(fp, "\n%s\t%s", CVS_LOGMSG_PREFIX, 170 dir != NULL ? basename(cf->file_path) : 171 cf->file_path); 172 fputs("\n", fp); 173 } 174 175 if (removed != NULL && !RB_EMPTY(removed)) { 176 fprintf(fp, "%s Removed Files:", CVS_LOGMSG_PREFIX); 177 RB_FOREACH(cf, cvs_flisthead, removed) 178 fprintf(fp, "\n%s\t%s", CVS_LOGMSG_PREFIX, 179 dir != NULL ? basename(cf->file_path) : 180 cf->file_path); 181 fputs("\n", fp); 182 } 183 184 if (modified != NULL && !RB_EMPTY(modified)) { 185 fprintf(fp, "%s Modified Files:", CVS_LOGMSG_PREFIX); 186 RB_FOREACH(cf, cvs_flisthead, modified) 187 fprintf(fp, "\n%s\t%s", CVS_LOGMSG_PREFIX, 188 dir != NULL ? basename(cf->file_path) : 189 cf->file_path); 190 fputs("\n", fp); 191 } 192 193 fprintf(fp, "%s %s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE); 194 (void)fflush(fp); 195 196 if (fstat(fd, &st1) == -1) { 197 saved_errno = errno; 198 (void)unlink(fpath); 199 fatal("cvs_logmsg_create: fstat %s", strerror(saved_errno)); 200 } 201 202 logmsg = NULL; 203 204 for (;;) { 205 if (cvs_logmsg_edit(fpath) == -1) 206 break; 207 208 if (fstat(fd, &st2) == -1) { 209 saved_errno = errno; 210 (void)unlink(fpath); 211 fatal("cvs_logmsg_create: fstat %s", 212 strerror(saved_errno)); 213 } 214 215 if (st1.st_mtime != st2.st_mtime) { 216 logmsg = cvs_logmsg_read(fpath); 217 if (prevmsg != NULL) 218 xfree(prevmsg); 219 prevmsg = xstrdup(logmsg); 220 break; 221 } 222 223 printf("\nLog message unchanged or not specified\n" 224 "a)bort, c)ontinue, e)dit, !)reuse this message " 225 "unchanged for remaining dirs\nAction: (continue) "); 226 (void)fflush(stdout); 227 228 c = getc(stdin); 229 if (c == EOF || c == 'a') { 230 fatal("Aborted by user"); 231 } else if (c == '\n' || c == 'c') { 232 if (prevmsg == NULL) 233 prevmsg = xstrdup(""); 234 logmsg = xstrdup(prevmsg); 235 break; 236 } else if (c == 'e') { 237 continue; 238 } else if (c == '!') { 239 reuse = 1; 240 if (prevmsg == NULL) 241 prevmsg = xstrdup(""); 242 logmsg = xstrdup(prevmsg); 243 break; 244 } else { 245 cvs_log(LP_ERR, "invalid input"); 246 continue; 247 } 248 } 249 250 (void)fclose(fp); 251 (void)unlink(fpath); 252 xfree(fpath); 253 254 return (logmsg); 255 } 256 257 /* 258 * Execute an editor on the specified pathname, which is interpreted 259 * from the shell. This means flags may be included. 260 * 261 * Returns -1 on error, or the exit value on success. 262 */ 263 int 264 cvs_logmsg_edit(const char *pathname) 265 { 266 char *argp[] = {"sh", "-c", NULL, NULL}, *p; 267 sig_t sighup, sigint, sigquit; 268 pid_t pid; 269 int saved_errno, st; 270 271 (void)xasprintf(&p, "%s %s", cvs_editor, pathname); 272 argp[2] = p; 273 274 sighup = signal(SIGHUP, SIG_IGN); 275 sigint = signal(SIGINT, SIG_IGN); 276 sigquit = signal(SIGQUIT, SIG_IGN); 277 if ((pid = fork()) == -1) 278 goto fail; 279 if (pid == 0) { 280 execv(_PATH_BSHELL, argp); 281 _exit(127); 282 } 283 while (waitpid(pid, &st, 0) == -1) 284 if (errno != EINTR) 285 goto fail; 286 xfree(p); 287 (void)signal(SIGHUP, sighup); 288 (void)signal(SIGINT, sigint); 289 (void)signal(SIGQUIT, sigquit); 290 if (!WIFEXITED(st)) { 291 errno = EINTR; 292 return (-1); 293 } 294 return (WEXITSTATUS(st)); 295 296 fail: 297 saved_errno = errno; 298 (void)signal(SIGHUP, sighup); 299 (void)signal(SIGINT, sigint); 300 (void)signal(SIGQUIT, sigquit); 301 xfree(p); 302 errno = saved_errno; 303 return (-1); 304 } 305 306 int 307 cvs_logmsg_verify(char *logmsg) 308 { 309 int fd, ret = 0; 310 char *fpath; 311 struct trigger_list *line_list; 312 struct file_info_list files_info; 313 struct file_info *fi; 314 315 line_list = cvs_trigger_getlines(CVS_PATH_VERIFYMSG, "DEFAULT"); 316 if (line_list != NULL) { 317 TAILQ_INIT(&files_info); 318 319 (void)xasprintf(&fpath, "%s/cvsXXXXXXXXXX", cvs_tmpdir); 320 if ((fd = mkstemp(fpath)) == -1) 321 fatal("cvs_logmsg_verify: mkstemp %s", strerror(errno)); 322 323 fi = xcalloc(1, sizeof(*fi)); 324 fi->file_path = xstrdup(fpath); 325 TAILQ_INSERT_TAIL(&files_info, fi, flist); 326 327 if (cvs_trigger_handle(CVS_TRIGGER_VERIFYMSG, NULL, NULL, 328 line_list, &files_info)) { 329 cvs_log(LP_ERR, "Log message check failed"); 330 ret = 1; 331 } 332 333 cvs_trigger_freeinfo(&files_info); 334 (void)close(fd); 335 (void)unlink(fpath); 336 xfree(fpath); 337 cvs_trigger_freelist(line_list); 338 } 339 340 return ret; 341 } 342 343