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