1 /* $OpenBSD: logmsg.c,v 1.13 2005/04/18 21:02:50 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 (void)fclose(fp); 117 return (NULL); 118 } 119 120 /* lcont is used to tell if a buffer returned by fgets is a start 121 * of line or just line continuation because the buffer isn't 122 * large enough to hold the entire line. 123 */ 124 lcont = 0; 125 126 while (fgets(lbuf, sizeof(lbuf), fp) != NULL) { 127 len = strlen(lbuf); 128 if (len == 0) 129 continue; 130 else if ((lcont == 0) && (strncmp(lbuf, CVS_LOGMSG_PREFIX, 131 strlen(CVS_LOGMSG_PREFIX)) == 0)) 132 /* skip lines starting with the prefix */ 133 continue; 134 135 if (cvs_buf_append(bp, lbuf, strlen(lbuf)) < 0) { 136 cvs_buf_free(bp); 137 (void)fclose(fp); 138 return (NULL); 139 } 140 141 lcont = (lbuf[len - 1] == '\n') ? 0 : 1; 142 } 143 (void)fclose(fp); 144 145 if (cvs_buf_putc(bp, '\0') < 0) { 146 cvs_buf_free(bp); 147 return (NULL); 148 } 149 150 msg = (char *)cvs_buf_release(bp); 151 152 return (msg); 153 } 154 155 156 /* 157 * cvs_logmsg_get() 158 * 159 * Get a log message by forking and executing the user's editor. The <dir> 160 * argument is a relative path to the directory for which the log message 161 * applies, and the 3 tail queue arguments contains all the files for which the 162 * log message will apply. Any of these arguments can be set to NULL in the 163 * case where there is no information to display. 164 * Returns the message in a dynamically allocated string on success, NULL on 165 * failure. 166 */ 167 char* 168 cvs_logmsg_get(const char *dir, struct cvs_flist *added, 169 struct cvs_flist *modified, struct cvs_flist *removed) 170 { 171 int i, fd, argc, fds[3], nl; 172 size_t len, tlen; 173 char *argv[4], buf[16], path[MAXPATHLEN], fpath[MAXPATHLEN], *msg; 174 FILE *fp; 175 CVSFILE *cvsfp; 176 struct stat st1, st2; 177 struct cvs_flist *files[3]; 178 179 files[0] = added; 180 files[1] = modified; 181 files[2] = removed; 182 183 msg = NULL; 184 fds[0] = -1; 185 fds[1] = -1; 186 fds[2] = -1; 187 strlcpy(path, CVS_LOGMSG_FTMPL, sizeof(path)); 188 argc = 0; 189 argv[argc++] = cvs_editor; 190 argv[argc++] = path; 191 argv[argc] = NULL; 192 193 if ((fd = mkstemp(path)) == -1) { 194 cvs_log(LP_ERRNO, "failed to create temporary file"); 195 return (NULL); 196 } 197 198 fp = fdopen(fd, "w"); 199 if (fp == NULL) { 200 cvs_log(LP_ERRNO, "failed to fdopen"); 201 (void)close(fd); 202 if (unlink(path) == -1) 203 cvs_log(LP_ERRNO, "failed to unlink temporary file"); 204 return (NULL); 205 } 206 207 fprintf(fp, "\n%s %s\n%s Enter Log. Lines beginning with `%s' are " 208 "removed automatically\n%s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE, 209 CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX); 210 211 if (dir != NULL) 212 fprintf(fp, "%s Commiting in %s\n%s\n", CVS_LOGMSG_PREFIX, dir, 213 CVS_LOGMSG_PREFIX); 214 215 for (i = 0; i < 3; i++) { 216 if (files[i] == NULL) 217 continue; 218 219 fprintf(fp, "%s %s Files:", CVS_LOGMSG_PREFIX, 220 cvs_logmsg_ops[i]); 221 nl = 1; 222 SIMPLEQ_FOREACH(cvsfp, files[i], cf_list) { 223 /* take the space into account */ 224 cvs_file_getpath(cvsfp, fpath, sizeof(fpath)); 225 len = strlen(fpath) + 1; 226 if (tlen + len >= 72) 227 nl = 1; 228 229 if (nl) { 230 fprintf(fp, "\n%s\t", CVS_LOGMSG_PREFIX); 231 tlen = 8; 232 nl = 0; 233 } 234 235 fprintf(fp, " %s", fpath); 236 tlen += len; 237 } 238 fputc('\n', fp); 239 240 } 241 fprintf(fp, "%s %s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE); 242 (void)fflush(fp); 243 244 if (fstat(fd, &st1) == -1) { 245 cvs_log(LP_ERRNO, "failed to stat log message file"); 246 247 (void)fclose(fp); 248 if (unlink(path) == -1) 249 cvs_log(LP_ERRNO, "failed to unlink log file %s", path); 250 return (NULL); 251 } 252 253 for (;;) { 254 if (cvs_exec(argc, argv, fds) < 0) 255 break; 256 257 if (fstat(fd, &st2) == -1) { 258 cvs_log(LP_ERRNO, "failed to stat log message file"); 259 break; 260 } 261 262 if (st2.st_mtime != st1.st_mtime) { 263 msg = cvs_logmsg_open(path); 264 break; 265 } 266 267 /* nothing was entered */ 268 fprintf(stderr, 269 "Log message unchanged or not specified\na)bort, " 270 "c)ontinue, e)dit, !)reuse this message unchanged " 271 "for remaining dirs\nAction: (continue) "); 272 273 if (fgets(buf, sizeof(buf), stdin) == NULL) { 274 cvs_log(LP_ERRNO, "failed to read from standard input"); 275 break; 276 } 277 278 len = strlen(buf); 279 if ((len == 0) || (len > 2)) { 280 fprintf(stderr, "invalid input\n"); 281 continue; 282 } else if (buf[0] == 'a') { 283 cvs_log(LP_ERR, "aborted by user"); 284 break; 285 } else if ((buf[0] == '\n') || (buf[0] == 'c')) { 286 /* empty message */ 287 msg = strdup(""); 288 break; 289 } else if (buf[0] == 'e') 290 continue; 291 else if (buf[0] == '!') { 292 /* XXX do something */ 293 } 294 } 295 296 (void)fclose(fp); 297 (void)close(fd); 298 299 if (unlink(path) == -1) 300 cvs_log(LP_ERRNO, "failed to unlink log file %s", path); 301 302 return (msg); 303 } 304 305 306 /* 307 * cvs_logmsg_send() 308 * 309 */ 310 int 311 cvs_logmsg_send(struct cvsroot *root, const char *msg) 312 { 313 const char *mp; 314 char *np, buf[256]; 315 316 if (cvs_sendarg(root, "-m", 0) < 0) { 317 cvs_log(LP_ERR, "failed to send log message"); 318 return (-1); 319 } 320 321 for (mp = msg; mp != NULL; mp = strchr(mp, '\n')) { 322 if (*mp == '\n') 323 mp++; 324 325 /* XXX ghetto */ 326 strlcpy(buf, mp, sizeof(buf)); 327 np = strchr(buf, '\n'); 328 if (np != NULL) 329 *np = '\0'; 330 if (cvs_sendarg(root, buf, (mp == msg) ? 0 : 1) < 0) { 331 cvs_log(LP_ERR, "failed to send log message"); 332 return (-1); 333 } 334 } 335 336 return (0); 337 } 338