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