1 /* $OpenBSD: logmsg.c,v 1.10 2004/12/08 21:49:02 jfb Exp $ */ 2 /* 3 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. The name of the author may not be used to endorse or promote products 13 * derived from this software without specific prior written permission. 14 * 15 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 16 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 17 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 18 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 21 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 24 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include <sys/types.h> 28 #include <sys/stat.h> 29 30 #include <errno.h> 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <unistd.h> 34 #include <string.h> 35 36 #include "cvs.h" 37 #include "log.h" 38 #include "buf.h" 39 #include "proto.h" 40 41 42 #define CVS_LOGMSG_BIGMSG 32000 43 #define CVS_LOGMSG_FTMPL "/tmp/cvsXXXXXXXXXX" 44 #define CVS_LOGMSG_PREFIX "CVS:" 45 #define CVS_LOGMSG_LINE \ 46 "----------------------------------------------------------------------" 47 48 49 static const char *cvs_logmsg_ops[3] = { 50 "Added", "Modified", "Removed", 51 }; 52 53 54 /* 55 * cvs_logmsg_open() 56 * 57 * Open the file specified by <path> and allocate a buffer large enough to 58 * hold all of the file's contents. Lines starting with the log prefix 59 * are not included in the result. 60 * The returned value must later be free()d. 61 * Returns a pointer to the allocated buffer on success, or NULL on failure. 62 */ 63 char* 64 cvs_logmsg_open(const char *path) 65 { 66 int lcont; 67 size_t len; 68 char lbuf[256], *msg; 69 struct stat st; 70 FILE *fp; 71 BUF *bp; 72 73 if (stat(path, &st) == -1) { 74 cvs_log(LP_ERRNO, "failed to stat `%s'", path); 75 return (NULL); 76 } 77 78 if (!S_ISREG(st.st_mode)) { 79 cvs_log(LP_ERR, "message file must be a regular file"); 80 return (NULL); 81 } 82 83 if (st.st_size > CVS_LOGMSG_BIGMSG) { 84 do { 85 fprintf(stderr, 86 "The specified message file seems big. " 87 "Proceed anyways? (y/n) "); 88 if (fgets(lbuf, sizeof(lbuf), stdin) == NULL) { 89 cvs_log(LP_ERRNO, 90 "failed to read from standard input"); 91 return (NULL); 92 } 93 94 len = strlen(lbuf); 95 if ((len == 0) || (len > 2) || 96 ((lbuf[0] != 'y') && (lbuf[0] != 'n'))) { 97 fprintf(stderr, "invalid input\n"); 98 continue; 99 } else if (lbuf[0] == 'y') 100 break; 101 else if (lbuf[0] == 'n') { 102 cvs_log(LP_ERR, "aborted by user"); 103 return (NULL); 104 } 105 106 } while (1); 107 } 108 109 if ((fp = fopen(path, "r")) == NULL) { 110 cvs_log(LP_ERRNO, "failed to open message file `%s'", path); 111 return (NULL); 112 } 113 114 bp = cvs_buf_alloc(128, BUF_AUTOEXT); 115 if (bp == NULL) 116 return (NULL); 117 118 /* lcont is used to tell if a buffer returned by fgets is a start 119 * of line or just line continuation because the buffer isn't 120 * large enough to hold the entire line. 121 */ 122 lcont = 0; 123 124 while (fgets(lbuf, sizeof(lbuf), fp) != NULL) { 125 len = strlen(lbuf); 126 if (len == 0) 127 continue; 128 else if ((lcont == 0) && (strncmp(lbuf, CVS_LOGMSG_PREFIX, 129 strlen(CVS_LOGMSG_PREFIX)) == 0)) 130 /* skip lines starting with the prefix */ 131 continue; 132 133 cvs_buf_append(bp, lbuf, strlen(lbuf)); 134 135 lcont = (lbuf[len - 1] == '\n') ? 0 : 1; 136 } 137 cvs_buf_putc(bp, '\0'); 138 139 msg = (char *)cvs_buf_release(bp); 140 141 return (msg); 142 } 143 144 145 /* 146 * cvs_logmsg_get() 147 * 148 * Get a log message by forking and executing the user's editor. The <dir> 149 * argument is a relative path to the directory for which the log message 150 * applies, and the 3 tail queue arguemnts contains all the files for which the 151 * log message will apply. Any of these arguments can be set to NULL in the 152 * case where there is no information to display. 153 * Returns the message in a dynamically allocated string on success, NULL on 154 * failure. 155 */ 156 char* 157 cvs_logmsg_get(const char *dir, struct cvs_flist *added, 158 struct cvs_flist *modified, struct cvs_flist *removed) 159 { 160 int i, fd, argc, fds[3], nl; 161 size_t len, tlen; 162 char *argv[4], buf[16], path[MAXPATHLEN], fpath[MAXPATHLEN], *msg; 163 FILE *fp; 164 CVSFILE *cvsfp; 165 struct stat st1, st2; 166 struct cvs_flist *files[3]; 167 168 files[0] = added; 169 files[1] = modified; 170 files[2] = removed; 171 172 msg = NULL; 173 fds[0] = -1; 174 fds[1] = -1; 175 fds[2] = -1; 176 strlcpy(path, CVS_LOGMSG_FTMPL, sizeof(path)); 177 argc = 0; 178 argv[argc++] = cvs_editor; 179 argv[argc++] = path; 180 argv[argc] = NULL; 181 182 if ((fd = mkstemp(path)) == -1) { 183 cvs_log(LP_ERRNO, "failed to create temporary file"); 184 return (NULL); 185 } 186 187 fp = fdopen(fd, "w"); 188 if (fp == NULL) { 189 cvs_log(LP_ERRNO, "failed to fdopen"); 190 (void)close(fd); 191 if (unlink(path) == -1) 192 cvs_log(LP_ERRNO, "failed to unlink temporary file"); 193 return (NULL); 194 } 195 196 fprintf(fp, "\n%s %s\n%s Enter Log. Lines beginning with `%s' are " 197 "removed automatically\n%s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE, 198 CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX); 199 200 if (dir != NULL) 201 fprintf(fp, "%s Commiting in %s\n%s\n", CVS_LOGMSG_PREFIX, dir, 202 CVS_LOGMSG_PREFIX); 203 204 for (i = 0; i < 3; i++) { 205 if (files[i] == NULL) 206 continue; 207 208 fprintf(fp, "%s %s Files:", CVS_LOGMSG_PREFIX, 209 cvs_logmsg_ops[i]); 210 nl = 1; 211 TAILQ_FOREACH(cvsfp, files[i], cf_list) { 212 /* take the space into account */ 213 cvs_file_getpath(cvsfp, fpath, sizeof(fpath)); 214 len = strlen(fpath) + 1; 215 if (tlen + len >= 72) 216 nl = 1; 217 218 if (nl) { 219 fprintf(fp, "\n%s\t", CVS_LOGMSG_PREFIX); 220 tlen = 8; 221 nl = 0; 222 } 223 224 fprintf(fp, " %s", fpath); 225 tlen += len; 226 } 227 fputc('\n', fp); 228 229 } 230 fprintf(fp, "%s %s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE); 231 (void)fflush(fp); 232 233 if (fstat(fd, &st1) == -1) { 234 cvs_log(LP_ERRNO, "failed to stat log message file"); 235 236 (void)fclose(fp); 237 if (unlink(path) == -1) 238 cvs_log(LP_ERRNO, "failed to unlink log file %s", path); 239 return (NULL); 240 } 241 242 for (;;) { 243 if (cvs_exec(argc, argv, fds) < 0) 244 break; 245 246 if (fstat(fd, &st2) == -1) { 247 cvs_log(LP_ERRNO, "failed to stat log message file"); 248 break; 249 } 250 251 if (st2.st_mtime != st1.st_mtime) { 252 msg = cvs_logmsg_open(path); 253 break; 254 } 255 256 /* nothing was entered */ 257 fprintf(stderr, 258 "Log message unchanged or not specified\na)bort, " 259 "c)ontinue, e)dit, !)reuse this message unchanged " 260 "for remaining dirs\nAction: (continue) "); 261 262 if (fgets(buf, sizeof(buf), stdin) == NULL) { 263 cvs_log(LP_ERRNO, "failed to read from standard input"); 264 break; 265 } 266 267 len = strlen(buf); 268 if ((len == 0) || (len > 2)) { 269 fprintf(stderr, "invalid input\n"); 270 continue; 271 } else if (buf[0] == 'a') { 272 cvs_log(LP_ERR, "aborted by user"); 273 break; 274 } else if ((buf[0] == '\n') || (buf[0] == 'c')) { 275 /* empty message */ 276 msg = strdup(""); 277 break; 278 } else if (buf[0] == 'e') 279 continue; 280 else if (buf[0] == '!') { 281 /* XXX do something */ 282 } 283 } 284 285 (void)fclose(fp); 286 (void)close(fd); 287 288 if (unlink(path) == -1) 289 cvs_log(LP_ERRNO, "failed to unlink log file %s", path); 290 291 return (msg); 292 } 293 294 295 /* 296 * cvs_logmsg_send() 297 * 298 */ 299 int 300 cvs_logmsg_send(struct cvsroot *root, const char *msg) 301 { 302 const char *mp; 303 char *np, buf[256]; 304 305 if (cvs_sendarg(root, "-m", 0) < 0) { 306 cvs_log(LP_ERR, "failed to send log message"); 307 return (-1); 308 } 309 310 for (mp = msg; mp != NULL; mp = strchr(mp, '\n')) { 311 if (*mp == '\n') 312 mp++; 313 314 /* XXX ghetto */ 315 strlcpy(buf, mp, sizeof(buf)); 316 np = strchr(buf, '\n'); 317 if (np != NULL) 318 *np = '\0'; 319 if (cvs_sendarg(root, buf, (mp == msg) ? 0 : 1) < 0) { 320 cvs_log(LP_ERR, "failed to send log message"); 321 return (-1); 322 } 323 } 324 325 return (0); 326 } 327