1 /* $OpenBSD: rcsutil.c,v 1.26 2007/01/11 18:13:33 niallo Exp $ */ 2 /* 3 * Copyright (c) 2005, 2006 Joris Vink <joris@openbsd.org> 4 * Copyright (c) 2006 Xavier Santolaria <xsa@openbsd.org> 5 * Copyright (c) 2006 Niall O'Higgins <niallo@openbsd.org> 6 * Copyright (c) 2006 Ray Lai <ray@openbsd.org> 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. The name of the author may not be used to endorse or promote products 16 * derived from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 19 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 20 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 24 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 26 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 27 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include "includes.h" 31 32 #include "rcsprog.h" 33 34 /* 35 * rcs_get_mtime() 36 * 37 * Get <filename> last modified time. 38 * Returns last modified time on success, or -1 on failure. 39 */ 40 time_t 41 rcs_get_mtime(RCSFILE *file) 42 { 43 struct stat st; 44 time_t mtime; 45 46 if (fstat(file->rf_fd, &st) == -1) { 47 warn("%s", file->rf_path); 48 return (-1); 49 } 50 51 mtime = (time_t)st.st_mtimespec.tv_sec; 52 53 return (mtime); 54 } 55 56 /* 57 * rcs_set_mtime() 58 * 59 * Set <filename> last modified time to <mtime> if it's not set to -1. 60 */ 61 void 62 rcs_set_mtime(RCSFILE *file, time_t mtime) 63 { 64 static struct timeval tv[2]; 65 66 if (mtime == -1) 67 return; 68 69 tv[0].tv_sec = mtime; 70 tv[1].tv_sec = tv[0].tv_sec; 71 72 if (futimes(file->rf_fd, tv) == -1) 73 err(1, "utimes"); 74 } 75 76 int 77 rcs_getopt(int argc, char **argv, const char *optstr) 78 { 79 char *a; 80 const char *c; 81 static int i = 1; 82 int opt, hasargument, ret; 83 84 hasargument = 0; 85 rcs_optarg = NULL; 86 87 if (i >= argc) 88 return (-1); 89 90 a = argv[i++]; 91 if (*a++ != '-') 92 return (-1); 93 94 ret = 0; 95 opt = *a; 96 for (c = optstr; *c != '\0'; c++) { 97 if (*c == opt) { 98 a++; 99 ret = opt; 100 101 if (*(c + 1) == ':') { 102 if (*(c + 2) == ':') { 103 if (*a != '\0') 104 hasargument = 1; 105 } else { 106 if (*a != '\0') { 107 hasargument = 1; 108 } else { 109 ret = 1; 110 break; 111 } 112 } 113 } 114 115 if (hasargument == 1) 116 rcs_optarg = a; 117 118 if (ret == opt) 119 rcs_optind++; 120 break; 121 } 122 } 123 124 if (ret == 0) 125 warnx("unknown option -%c", opt); 126 else if (ret == 1) 127 warnx("missing argument for option -%c", opt); 128 129 return (ret); 130 } 131 132 /* 133 * rcs_choosefile() 134 * 135 * Given a relative filename, decide where the corresponding RCS file 136 * should be. Tries each extension until a file is found. If no file 137 * was found, returns a path with the first extension. 138 * 139 * Opens and returns file descriptor to RCS file. 140 */ 141 int 142 rcs_choosefile(const char *filename, char *out, size_t len) 143 { 144 int fd; 145 struct stat sb; 146 char *p, *ext, name[MAXPATHLEN], *next, *ptr, rcsdir[MAXPATHLEN], 147 *suffixes, rcspath[MAXPATHLEN]; 148 149 /* If -x flag was not given, use default. */ 150 if (rcs_suffixes == NULL) 151 rcs_suffixes = RCS_DEFAULT_SUFFIX; 152 153 fd = -1; 154 155 /* 156 * If `filename' contains a directory, `rcspath' contains that 157 * directory, including a trailing slash. Otherwise `rcspath' 158 * contains an empty string. 159 */ 160 if (strlcpy(rcspath, filename, sizeof(rcspath)) >= sizeof(rcspath)) 161 errx(1, "rcs_choosefile: truncation"); 162 163 /* If `/' is found, end string after `/'. */ 164 if ((ptr = strrchr(rcspath, '/')) != NULL) 165 *(++ptr) = '\0'; 166 else 167 rcspath[0] = '\0'; 168 169 /* Append RCS/ to `rcspath' if it exists. */ 170 if (strlcpy(rcsdir, rcspath, sizeof(rcsdir)) >= sizeof(rcsdir) || 171 strlcat(rcsdir, RCSDIR, sizeof(rcsdir)) >= sizeof(rcsdir)) 172 errx(1, "rcs_choosefile: truncation"); 173 174 if (stat(rcsdir, &sb) == 0 && S_ISDIR(sb.st_mode)) 175 if (strlcpy(rcspath, rcsdir, sizeof(rcspath)) 176 >= sizeof(rcspath) || 177 strlcat(rcspath, "/", sizeof(rcspath)) >= sizeof(rcspath)) 178 errx(1, "rcs_choosefile: truncation"); 179 180 /* Name of file without path. */ 181 if ((ptr = strrchr(filename, '/')) == NULL) { 182 if (strlcpy(name, filename, sizeof(name)) >= sizeof(name)) 183 errx(1, "rcs_choosefile: truncation"); 184 } else { 185 /* Skip `/'. */ 186 if (strlcpy(name, ptr + 1, sizeof(name)) >= sizeof(name)) 187 errx(1, "rcs_choosefile: truncation"); 188 } 189 190 /* Name of RCS file without an extension. */ 191 if (strlcat(rcspath, name, sizeof(rcspath)) >= sizeof(rcspath)) 192 errx(1, "rcs_choosefile: truncation"); 193 194 /* 195 * If only the empty suffix was given, use existing rcspath. 196 * This ensures that there is at least one suffix for strsep(). 197 */ 198 if (strcmp(rcs_suffixes, "") == 0) { 199 if (strlcpy(out, rcspath, len) >= len) 200 errx(1, "rcs_choosefile: truncation"); 201 fd = open(rcspath, O_RDONLY); 202 return (fd); 203 } 204 205 /* 206 * Cycle through slash-separated `rcs_suffixes', appending each 207 * extension to `rcspath' and testing if the file exists. If it 208 * does, return that string. Otherwise return path with first 209 * extension. 210 */ 211 suffixes = xstrdup(rcs_suffixes); 212 for (next = suffixes; (ext = strsep(&next, "/")) != NULL;) { 213 char fpath[MAXPATHLEN]; 214 215 if ((p = strrchr(rcspath, ',')) != NULL) { 216 if (!strcmp(p, ext)) { 217 if ((fd = open(rcspath, O_RDONLY)) == -1) 218 continue; 219 220 if (fstat(fd, &sb) == -1) 221 err(1, "%s", rcspath); 222 223 if (strlcpy(out, rcspath, len) >= len) 224 errx(1, "rcs_choosefile; truncation"); 225 226 return (fd); 227 } 228 229 continue; 230 } 231 232 /* Construct RCS file path. */ 233 if (strlcpy(fpath, rcspath, sizeof(fpath)) >= sizeof(fpath) || 234 strlcat(fpath, ext, sizeof(fpath)) >= sizeof(fpath)) 235 errx(1, "rcs_choosefile: truncation"); 236 237 /* Don't use `filename' as RCS file. */ 238 if (strcmp(fpath, filename) == 0) 239 continue; 240 241 if ((fd = open(fpath, O_RDONLY)) == -1) 242 continue; 243 244 if (fstat(fd, &sb) == -1) 245 err(1, "%s", fpath); 246 247 if (strlcpy(out, fpath, len) >= len) 248 errx(1, "rcs_choosefile: truncation"); 249 250 return (fd); 251 } 252 253 /* 254 * `suffixes' should now be NUL separated, so the first 255 * extension can be read just by reading `suffixes'. 256 */ 257 if (strlcat(rcspath, suffixes, sizeof(rcspath)) >= sizeof(rcspath)) 258 errx(1, "rcs_choosefile: truncation"); 259 260 xfree(suffixes); 261 262 if (strlcpy(out, rcspath, len) >= len) 263 errx(1, "rcs_choosefile: truncation"); 264 265 fd = open(rcspath, O_RDONLY); 266 267 return (fd); 268 } 269 270 /* 271 * Allocate an RCSNUM and store in <rev>. 272 */ 273 void 274 rcs_set_rev(const char *str, RCSNUM **rev) 275 { 276 if (str == NULL || (*rev = rcsnum_parse(str)) == NULL) 277 errx(1, "bad revision number `%s'", str); 278 } 279 280 /* 281 * Set <str> to <new_str>. Print warning if <str> is redefined. 282 */ 283 void 284 rcs_setrevstr(char **str, char *new_str) 285 { 286 if (new_str == NULL) 287 return; 288 if (*str != NULL) 289 warnx("redefinition of revision number"); 290 *str = new_str; 291 } 292 293 /* 294 * Set <str1> or <str2> to <new_str>, depending on which is not set. 295 * If both are set, error out. 296 */ 297 void 298 rcs_setrevstr2(char **str1, char **str2, char *new_str) 299 { 300 if (new_str == NULL) 301 return; 302 if (*str1 == NULL) 303 *str1 = new_str; 304 else if (*str2 == NULL) 305 *str2 = new_str; 306 else 307 errx(1, "too many revision numbers"); 308 } 309 310 /* 311 * Get revision from file. The revision can be specified as a symbol or 312 * a revision number. 313 */ 314 RCSNUM * 315 rcs_getrevnum(const char *rev_str, RCSFILE *file) 316 { 317 RCSNUM *rev; 318 319 /* Search for symbol. */ 320 rev = rcs_sym_getrev(file, rev_str); 321 322 /* Search for revision number. */ 323 if (rev == NULL) 324 rev = rcsnum_parse(rev_str); 325 326 return (rev); 327 } 328 329 /* 330 * Prompt for and store user's input in an allocated string. 331 * 332 * Returns the string's pointer. 333 */ 334 char * 335 rcs_prompt(const char *prompt) 336 { 337 BUF *bp; 338 size_t len; 339 char *buf; 340 341 bp = rcs_buf_alloc(0, BUF_AUTOEXT); 342 if (isatty(STDIN_FILENO)) 343 (void)fprintf(stderr, "%s", prompt); 344 if (isatty(STDIN_FILENO)) 345 (void)fprintf(stderr, ">> "); 346 clearerr(stdin); 347 while ((buf = fgetln(stdin, &len)) != NULL) { 348 /* The last line may not be EOL terminated. */ 349 if (buf[0] == '.' && (len == 1 || buf[1] == '\n')) 350 break; 351 else 352 rcs_buf_append(bp, buf, len); 353 354 if (isatty(STDIN_FILENO)) 355 (void)fprintf(stderr, ">> "); 356 } 357 rcs_buf_putc(bp, '\0'); 358 359 return (rcs_buf_release(bp)); 360 } 361 362 u_int 363 rcs_rev_select(RCSFILE *file, const char *range) 364 { 365 int i; 366 u_int nrev; 367 char *ep; 368 char *lstr, *rstr; 369 struct rcs_delta *rdp; 370 struct rcs_argvector *revargv, *revrange; 371 RCSNUM lnum, rnum; 372 373 nrev = 0; 374 (void)memset(&lnum, 0, sizeof(lnum)); 375 (void)memset(&rnum, 0, sizeof(rnum)); 376 377 if (range == NULL) { 378 TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) 379 if (rcsnum_cmp(rdp->rd_num, file->rf_head, 0) == 0) { 380 rdp->rd_flags |= RCS_RD_SELECT; 381 return (1); 382 } 383 return (0); 384 } 385 386 revargv = rcs_strsplit(range, ","); 387 for (i = 0; revargv->argv[i] != NULL; i++) { 388 revrange = rcs_strsplit(revargv->argv[i], ":"); 389 if (revrange->argv[0] == NULL) 390 /* should not happen */ 391 errx(1, "invalid revision range: %s", revargv->argv[i]); 392 else if (revrange->argv[1] == NULL) 393 lstr = rstr = revrange->argv[0]; 394 else { 395 if (revrange->argv[2] != NULL) 396 errx(1, "invalid revision range: %s", 397 revargv->argv[i]); 398 lstr = revrange->argv[0]; 399 rstr = revrange->argv[1]; 400 if (strcmp(lstr, "") == 0) 401 lstr = NULL; 402 if (strcmp(rstr, "") == 0) 403 rstr = NULL; 404 } 405 406 if (lstr == NULL) 407 lstr = RCS_HEAD_INIT; 408 if (rcsnum_aton(lstr, &ep, &lnum) == 0 || (*ep != '\0')) 409 errx(1, "invalid revision: %s", lstr); 410 411 if (rstr != NULL) { 412 if (rcsnum_aton(rstr, &ep, &rnum) == 0 || (*ep != '\0')) 413 errx(1, "invalid revision: %s", rstr); 414 } else 415 rcsnum_cpy(file->rf_head, &rnum, 0); 416 417 rcs_argv_destroy(revrange); 418 419 TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) 420 if (rcsnum_cmp(rdp->rd_num, &lnum, 0) <= 0 && 421 rcsnum_cmp(rdp->rd_num, &rnum, 0) >= 0 && 422 !(rdp->rd_flags & RCS_RD_SELECT)) { 423 rdp->rd_flags |= RCS_RD_SELECT; 424 nrev++; 425 } 426 } 427 rcs_argv_destroy(revargv); 428 429 if (lnum.rn_id != NULL) 430 xfree(lnum.rn_id); 431 if (rnum.rn_id != NULL) 432 xfree(rnum.rn_id); 433 434 return (nrev); 435 } 436 437 /* 438 * Load description from <in> to <file>. 439 * If <in> starts with a `-', <in> is taken as the description. 440 * Otherwise <in> is the name of the file containing the description. 441 * If <in> is NULL, the description is read from stdin. 442 * Returns 0 on success, -1 on failure, setting errno. 443 */ 444 int 445 rcs_set_description(RCSFILE *file, const char *in) 446 { 447 BUF *bp; 448 char *content; 449 const char *prompt = 450 "enter description, terminated with single '.' or end of file:\n" 451 "NOTE: This is NOT the log message!\n"; 452 453 /* Description is in file <in>. */ 454 if (in != NULL && *in != '-') { 455 if ((bp = rcs_buf_load(in, BUF_AUTOEXT)) == NULL) 456 return (-1); 457 rcs_buf_putc(bp, '\0'); 458 content = rcs_buf_release(bp); 459 /* Description is in <in>. */ 460 } else if (in != NULL) 461 /* Skip leading `-'. */ 462 content = xstrdup(in + 1); 463 /* Get description from stdin. */ 464 else 465 content = rcs_prompt(prompt); 466 467 rcs_desc_set(file, content); 468 xfree(content); 469 return (0); 470 } 471 472 /* 473 * Split the contents of a file into a list of lines. 474 */ 475 struct rcs_lines * 476 rcs_splitlines(const u_char *data, size_t len) 477 { 478 u_char *c, *p; 479 struct rcs_lines *lines; 480 struct rcs_line *lp; 481 size_t i, tlen; 482 483 lines = xmalloc(sizeof(*lines)); 484 memset(lines, 0, sizeof(*lines)); 485 TAILQ_INIT(&(lines->l_lines)); 486 487 lp = xmalloc(sizeof(*lp)); 488 memset(lp, 0, sizeof(*lp)); 489 TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list); 490 491 492 p = c = data; 493 for (i = 0; i < len; i++) { 494 if (*p == '\n' || (i == len - 1)) { 495 tlen = p - c + 1; 496 lp = xmalloc(sizeof(*lp)); 497 lp->l_line = c; 498 lp->l_len = tlen; 499 lp->l_lineno = ++(lines->l_nblines); 500 TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list); 501 c = p + 1; 502 } 503 p++; 504 } 505 506 return (lines); 507 } 508 509 void 510 rcs_freelines(struct rcs_lines *lines) 511 { 512 struct rcs_line *lp; 513 514 while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) { 515 TAILQ_REMOVE(&(lines->l_lines), lp, l_list); 516 xfree(lp); 517 } 518 519 xfree(lines); 520 } 521 522 BUF * 523 rcs_patchfile(const u_char *data, size_t dlen, const u_char *patch, size_t plen, 524 int (*p)(struct rcs_lines *, struct rcs_lines *)) 525 { 526 struct rcs_lines *dlines, *plines; 527 struct rcs_line *lp; 528 BUF *res; 529 530 dlines = rcs_splitlines(data, dlen); 531 plines = rcs_splitlines(patch, plen); 532 533 if (p(dlines, plines) < 0) { 534 rcs_freelines(dlines); 535 rcs_freelines(plines); 536 return (NULL); 537 } 538 539 res = rcs_buf_alloc(1024, BUF_AUTOEXT); 540 TAILQ_FOREACH(lp, &dlines->l_lines, l_list) { 541 if (lp->l_line == NULL) 542 continue; 543 rcs_buf_append(res, lp->l_line, lp->l_len); 544 } 545 546 rcs_freelines(dlines); 547 rcs_freelines(plines); 548 return (res); 549 } 550 551 /* 552 * rcs_yesno() 553 * 554 * Read a char from standard input, returns defc if the 555 * user enters an equivalent to defc, else whatever char 556 * was entered. Converts input to lower case. 557 */ 558 int 559 rcs_yesno(int defc) 560 { 561 int c, ret; 562 563 fflush(stderr); 564 fflush(stdout); 565 566 clearerr(stdin); 567 if (isalpha(c = getchar())) 568 c = tolower(c); 569 if (c == defc || c == '\n' || (c == EOF && feof(stdin))) 570 ret = defc; 571 else 572 ret = c; 573 574 while (c != EOF && c != '\n') 575 c = getchar(); 576 577 return (ret); 578 } 579 580 /* 581 * rcs_strsplit() 582 * 583 * Split a string <str> of <sep>-separated values and allocate 584 * an argument vector for the values found. 585 */ 586 struct rcs_argvector * 587 rcs_strsplit(const char *str, const char *sep) 588 { 589 struct rcs_argvector *av; 590 size_t i = 0; 591 char **nargv; 592 char *cp, *p; 593 594 cp = xstrdup(str); 595 av = xmalloc(sizeof(*av)); 596 av->str = cp; 597 av->argv = xcalloc(i + 1, sizeof(*(av->argv))); 598 599 while ((p = strsep(&cp, sep)) != NULL) { 600 av->argv[i++] = p; 601 nargv = xrealloc(av->argv, 602 i + 1, sizeof(*(av->argv))); 603 av->argv = nargv; 604 } 605 av->argv[i] = NULL; 606 607 return (av); 608 } 609 610 /* 611 * rcs_argv_destroy() 612 * 613 * Free an argument vector previously allocated by rcs_strsplit(). 614 */ 615 void 616 rcs_argv_destroy(struct rcs_argvector *av) 617 { 618 xfree(av->str); 619 xfree(av->argv); 620 xfree(av); 621 } 622