1 /* $OpenBSD: logmsg.c,v 1.18 2005/07/25 12:13:08 xsa 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 <string.h> 34 #include <unistd.h> 35 36 #include "buf.h" 37 #include "cvs.h" 38 #include "log.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 if (SIMPLEQ_EMPTY(files[i])) 220 continue; 221 222 fprintf(fp, "%s %s Files:", CVS_LOGMSG_PREFIX, 223 cvs_logmsg_ops[i]); 224 nl = 1; 225 SIMPLEQ_FOREACH(cvsfp, files[i], cf_list) { 226 /* take the space into account */ 227 cvs_file_getpath(cvsfp, fpath, sizeof(fpath)); 228 len = strlen(fpath) + 1; 229 if (tlen + len >= 72) 230 nl = 1; 231 232 if (nl) { 233 fprintf(fp, "\n%s\t", CVS_LOGMSG_PREFIX); 234 tlen = 8; 235 nl = 0; 236 } 237 238 fprintf(fp, " %s", fpath); 239 tlen += len; 240 } 241 fputc('\n', fp); 242 243 } 244 fprintf(fp, "%s %s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE); 245 (void)fflush(fp); 246 247 if (fstat(fd, &st1) == -1) { 248 cvs_log(LP_ERRNO, "failed to stat log message file"); 249 250 (void)fclose(fp); 251 if (unlink(path) == -1) 252 cvs_log(LP_ERRNO, "failed to unlink log file %s", path); 253 return (NULL); 254 } 255 256 for (;;) { 257 if (cvs_exec(argc, argv, fds) < 0) 258 break; 259 260 if (fstat(fd, &st2) == -1) { 261 cvs_log(LP_ERRNO, "failed to stat log message file"); 262 break; 263 } 264 265 if (st2.st_mtime != st1.st_mtime) { 266 msg = cvs_logmsg_open(path); 267 break; 268 } 269 270 /* nothing was entered */ 271 fprintf(stderr, 272 "\nLog message unchanged or not specified\na)bort, " 273 "c)ontinue, e)dit, !)reuse this message unchanged " 274 "for remaining dirs\nAction: (continue) "); 275 276 if (fgets(buf, sizeof(buf), stdin) == NULL) { 277 cvs_log(LP_ERRNO, "failed to read from standard input"); 278 break; 279 } 280 281 len = strlen(buf); 282 if ((len == 0) || (len > 2)) { 283 fprintf(stderr, "invalid input\n"); 284 continue; 285 } else if (buf[0] == 'a') { 286 cvs_log(LP_ABORT, "aborted by user"); 287 break; 288 } else if ((buf[0] == '\n') || (buf[0] == 'c')) { 289 /* empty message */ 290 msg = strdup(""); 291 break; 292 } else if (buf[0] == 'e') 293 continue; 294 else if (buf[0] == '!') { 295 /* XXX do something */ 296 } 297 } 298 299 (void)fclose(fp); 300 (void)close(fd); 301 302 if (unlink(path) == -1) 303 cvs_log(LP_ERRNO, "failed to unlink log file %s", path); 304 305 return (msg); 306 } 307 308 309 /* 310 * cvs_logmsg_send() 311 * 312 */ 313 int 314 cvs_logmsg_send(struct cvsroot *root, const char *msg) 315 { 316 const char *mp; 317 char *np, buf[256]; 318 319 if (cvs_sendarg(root, "-m", 0) < 0) { 320 cvs_log(LP_ERR, "failed to send log message"); 321 return (-1); 322 } 323 324 for (mp = msg; mp != NULL; mp = strchr(mp, '\n')) { 325 if (*mp == '\n') 326 mp++; 327 328 /* XXX ghetto */ 329 strlcpy(buf, mp, sizeof(buf)); 330 np = strchr(buf, '\n'); 331 if (np != NULL) 332 *np = '\0'; 333 if (cvs_sendarg(root, buf, (mp == msg) ? 0 : 1) < 0) { 334 cvs_log(LP_ERR, "failed to send log message"); 335 return (-1); 336 } 337 } 338 339 return (0); 340 } 341