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