1 /* $OpenBSD: rcs.c,v 1.80 2014/01/07 14:08:16 deraadt 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/stat.h> 28 29 #include <ctype.h> 30 #include <err.h> 31 #include <errno.h> 32 #include <libgen.h> 33 #include <pwd.h> 34 #include <stdarg.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <unistd.h> 39 40 #include "diff.h" 41 #include "rcs.h" 42 #include "rcsparse.h" 43 #include "rcsprog.h" 44 #include "rcsutil.h" 45 #include "xmalloc.h" 46 47 /* invalid characters in RCS states */ 48 static const char rcs_state_invch[] = RCS_STATE_INVALCHAR; 49 50 /* invalid characters in RCS symbol names */ 51 static const char rcs_sym_invch[] = RCS_SYM_INVALCHAR; 52 53 struct rcs_kw rcs_expkw[] = { 54 { "Author", RCS_KW_AUTHOR }, 55 { "Date", RCS_KW_DATE }, 56 { "Locker", RCS_KW_LOCKER }, 57 { "Header", RCS_KW_HEADER }, 58 { "Id", RCS_KW_ID }, 59 { "OpenBSD", RCS_KW_ID }, 60 { "Log", RCS_KW_LOG }, 61 { "Name", RCS_KW_NAME }, 62 { "RCSfile", RCS_KW_RCSFILE }, 63 { "Revision", RCS_KW_REVISION }, 64 { "Source", RCS_KW_SOURCE }, 65 { "State", RCS_KW_STATE }, 66 }; 67 68 int rcs_errno = RCS_ERR_NOERR; 69 char *timezone_flag = NULL; 70 71 int rcs_patch_lines(struct rcs_lines *, struct rcs_lines *); 72 static int rcs_movefile(char *, char *, mode_t, u_int); 73 74 static void rcs_freedelta(struct rcs_delta *); 75 static void rcs_strprint(const u_char *, size_t, FILE *); 76 77 static BUF *rcs_expand_keywords(char *, struct rcs_delta *, BUF *, int); 78 79 RCSFILE * 80 rcs_open(const char *path, int fd, int flags, ...) 81 { 82 int mode; 83 mode_t fmode; 84 RCSFILE *rfp; 85 va_list vap; 86 struct rcs_delta *rdp; 87 struct rcs_lock *lkr; 88 89 fmode = S_IRUSR|S_IRGRP|S_IROTH; 90 flags &= 0xffff; /* ditch any internal flags */ 91 92 if (flags & RCS_CREATE) { 93 va_start(vap, flags); 94 mode = va_arg(vap, int); 95 va_end(vap); 96 fmode = (mode_t)mode; 97 } 98 99 rfp = xcalloc(1, sizeof(*rfp)); 100 101 rfp->rf_path = xstrdup(path); 102 rfp->rf_flags = flags | RCS_SLOCK | RCS_SYNCED; 103 rfp->rf_mode = fmode; 104 if (fd == -1) 105 rfp->rf_file = NULL; 106 else if ((rfp->rf_file = fdopen(fd, "r")) == NULL) 107 err(1, "rcs_open: fdopen: `%s'", path); 108 109 TAILQ_INIT(&(rfp->rf_delta)); 110 TAILQ_INIT(&(rfp->rf_access)); 111 TAILQ_INIT(&(rfp->rf_symbols)); 112 TAILQ_INIT(&(rfp->rf_locks)); 113 114 if (!(rfp->rf_flags & RCS_CREATE)) { 115 if (rcsparse_init(rfp)) 116 errx(1, "could not parse admin data"); 117 118 /* fill in rd_locker */ 119 TAILQ_FOREACH(lkr, &(rfp->rf_locks), rl_list) { 120 if ((rdp = rcs_findrev(rfp, lkr->rl_num)) == NULL) { 121 rcs_close(rfp); 122 return (NULL); 123 } 124 125 rdp->rd_locker = xstrdup(lkr->rl_name); 126 } 127 } 128 129 return (rfp); 130 } 131 132 /* 133 * rcs_close() 134 * 135 * Close an RCS file handle. 136 */ 137 void 138 rcs_close(RCSFILE *rfp) 139 { 140 struct rcs_delta *rdp; 141 struct rcs_access *rap; 142 struct rcs_lock *rlp; 143 struct rcs_sym *rsp; 144 145 if ((rfp->rf_flags & RCS_WRITE) && !(rfp->rf_flags & RCS_SYNCED)) 146 rcs_write(rfp); 147 148 while (!TAILQ_EMPTY(&(rfp->rf_delta))) { 149 rdp = TAILQ_FIRST(&(rfp->rf_delta)); 150 TAILQ_REMOVE(&(rfp->rf_delta), rdp, rd_list); 151 rcs_freedelta(rdp); 152 } 153 154 while (!TAILQ_EMPTY(&(rfp->rf_access))) { 155 rap = TAILQ_FIRST(&(rfp->rf_access)); 156 TAILQ_REMOVE(&(rfp->rf_access), rap, ra_list); 157 xfree(rap->ra_name); 158 xfree(rap); 159 } 160 161 while (!TAILQ_EMPTY(&(rfp->rf_symbols))) { 162 rsp = TAILQ_FIRST(&(rfp->rf_symbols)); 163 TAILQ_REMOVE(&(rfp->rf_symbols), rsp, rs_list); 164 rcsnum_free(rsp->rs_num); 165 xfree(rsp->rs_name); 166 xfree(rsp); 167 } 168 169 while (!TAILQ_EMPTY(&(rfp->rf_locks))) { 170 rlp = TAILQ_FIRST(&(rfp->rf_locks)); 171 TAILQ_REMOVE(&(rfp->rf_locks), rlp, rl_list); 172 rcsnum_free(rlp->rl_num); 173 xfree(rlp->rl_name); 174 xfree(rlp); 175 } 176 177 if (rfp->rf_head != NULL) 178 rcsnum_free(rfp->rf_head); 179 if (rfp->rf_branch != NULL) 180 rcsnum_free(rfp->rf_branch); 181 182 if (rfp->rf_file != NULL) 183 fclose(rfp->rf_file); 184 if (rfp->rf_path != NULL) 185 xfree(rfp->rf_path); 186 if (rfp->rf_comment != NULL) 187 xfree(rfp->rf_comment); 188 if (rfp->rf_expand != NULL) 189 xfree(rfp->rf_expand); 190 if (rfp->rf_desc != NULL) 191 xfree(rfp->rf_desc); 192 if (rfp->rf_pdata != NULL) 193 rcsparse_free(rfp); 194 xfree(rfp); 195 } 196 197 /* 198 * rcs_write() 199 * 200 * Write the contents of the RCS file handle <rfp> to disk in the file whose 201 * path is in <rf_path>. 202 */ 203 void 204 rcs_write(RCSFILE *rfp) 205 { 206 FILE *fp; 207 char numbuf[RCS_REV_BUFSZ], *fn; 208 struct rcs_access *ap; 209 struct rcs_sym *symp; 210 struct rcs_branch *brp; 211 struct rcs_delta *rdp; 212 struct rcs_lock *lkp; 213 size_t len; 214 int fd; 215 216 fn = NULL; 217 fd = -1; 218 219 if (rfp->rf_flags & RCS_SYNCED) 220 return; 221 222 /* Write operations need the whole file parsed */ 223 if (rcsparse_deltatexts(rfp, NULL)) 224 errx(1, "problem parsing deltatexts"); 225 226 (void)xasprintf(&fn, "%s/rcs.XXXXXXXXXX", rcs_tmpdir); 227 228 if ((fd = mkstemp(fn)) == -1) 229 err(1, "%s", fn); 230 231 if ((fp = fdopen(fd, "w+")) == NULL) { 232 int saved_errno; 233 234 saved_errno = errno; 235 (void)unlink(fn); 236 errno = saved_errno; 237 err(1, "%s", fn); 238 } 239 240 worklist_add(fn, &temp_files); 241 242 if (rfp->rf_head != NULL) 243 rcsnum_tostr(rfp->rf_head, numbuf, sizeof(numbuf)); 244 else 245 numbuf[0] = '\0'; 246 247 fprintf(fp, "head\t%s;\n", numbuf); 248 249 if (rfp->rf_branch != NULL) { 250 rcsnum_tostr(rfp->rf_branch, numbuf, sizeof(numbuf)); 251 fprintf(fp, "branch\t%s;\n", numbuf); 252 } 253 254 fputs("access", fp); 255 TAILQ_FOREACH(ap, &(rfp->rf_access), ra_list) { 256 fprintf(fp, "\n\t%s", ap->ra_name); 257 } 258 fputs(";\n", fp); 259 260 fprintf(fp, "symbols"); 261 TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) { 262 if (RCSNUM_ISBRANCH(symp->rs_num)) 263 rcsnum_addmagic(symp->rs_num); 264 rcsnum_tostr(symp->rs_num, numbuf, sizeof(numbuf)); 265 fprintf(fp, "\n\t%s:%s", symp->rs_name, numbuf); 266 } 267 fprintf(fp, ";\n"); 268 269 fprintf(fp, "locks"); 270 TAILQ_FOREACH(lkp, &(rfp->rf_locks), rl_list) { 271 rcsnum_tostr(lkp->rl_num, numbuf, sizeof(numbuf)); 272 fprintf(fp, "\n\t%s:%s", lkp->rl_name, numbuf); 273 } 274 275 fprintf(fp, ";"); 276 277 if (rfp->rf_flags & RCS_SLOCK) 278 fprintf(fp, " strict;"); 279 fputc('\n', fp); 280 281 fputs("comment\t@", fp); 282 if (rfp->rf_comment != NULL) { 283 rcs_strprint((const u_char *)rfp->rf_comment, 284 strlen(rfp->rf_comment), fp); 285 fputs("@;\n", fp); 286 } else 287 fputs("# @;\n", fp); 288 289 if (rfp->rf_expand != NULL) { 290 fputs("expand @", fp); 291 rcs_strprint((const u_char *)rfp->rf_expand, 292 strlen(rfp->rf_expand), fp); 293 fputs("@;\n", fp); 294 } 295 296 fputs("\n\n", fp); 297 298 TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) { 299 fprintf(fp, "%s\n", rcsnum_tostr(rdp->rd_num, numbuf, 300 sizeof(numbuf))); 301 fprintf(fp, "date\t%d.%02d.%02d.%02d.%02d.%02d;", 302 rdp->rd_date.tm_year + 1900, rdp->rd_date.tm_mon + 1, 303 rdp->rd_date.tm_mday, rdp->rd_date.tm_hour, 304 rdp->rd_date.tm_min, rdp->rd_date.tm_sec); 305 fprintf(fp, "\tauthor %s;\tstate %s;\n", 306 rdp->rd_author, rdp->rd_state); 307 fputs("branches", fp); 308 TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) { 309 fprintf(fp, "\n\t%s", rcsnum_tostr(brp->rb_num, numbuf, 310 sizeof(numbuf))); 311 } 312 fputs(";\n", fp); 313 fprintf(fp, "next\t%s;\n\n", rcsnum_tostr(rdp->rd_next, 314 numbuf, sizeof(numbuf))); 315 } 316 317 fputs("\ndesc\n@", fp); 318 if (rfp->rf_desc != NULL && (len = strlen(rfp->rf_desc)) > 0) { 319 rcs_strprint((const u_char *)rfp->rf_desc, len, fp); 320 if (rfp->rf_desc[len-1] != '\n') 321 fputc('\n', fp); 322 } 323 fputs("@\n", fp); 324 325 /* deltatexts */ 326 TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) { 327 fprintf(fp, "\n\n%s\n", rcsnum_tostr(rdp->rd_num, numbuf, 328 sizeof(numbuf))); 329 fputs("log\n@", fp); 330 if (rdp->rd_log != NULL) { 331 len = strlen(rdp->rd_log); 332 rcs_strprint((const u_char *)rdp->rd_log, len, fp); 333 if (len == 0 || rdp->rd_log[len-1] != '\n') 334 fputc('\n', fp); 335 } 336 fputs("@\ntext\n@", fp); 337 if (rdp->rd_text != NULL) 338 rcs_strprint(rdp->rd_text, rdp->rd_tlen, fp); 339 fputs("@\n", fp); 340 } 341 (void)fclose(fp); 342 343 if (rcs_movefile(fn, rfp->rf_path, rfp->rf_mode, rfp->rf_flags) == -1) { 344 (void)unlink(fn); 345 errx(1, "rcs_movefile failed"); 346 } 347 348 rfp->rf_flags |= RCS_SYNCED; 349 350 if (fn != NULL) 351 xfree(fn); 352 } 353 354 /* 355 * rcs_movefile() 356 * 357 * Move a file using rename(2) if possible and copying if not. 358 * Returns 0 on success, -1 on failure. 359 */ 360 static int 361 rcs_movefile(char *from, char *to, mode_t perm, u_int to_flags) 362 { 363 FILE *src, *dst; 364 size_t nread, nwritten; 365 char *buf; 366 367 if (rename(from, to) == 0) { 368 if (chmod(to, perm) == -1) { 369 warn("%s", to); 370 return (-1); 371 } 372 return (0); 373 } else if (errno != EXDEV) { 374 warn("failed to access temp RCS output file"); 375 return (-1); 376 } 377 378 if ((chmod(to, S_IWUSR) == -1) && !(to_flags & RCS_CREATE)) { 379 warnx("chmod(%s, 0%o) failed", to, S_IWUSR); 380 return (-1); 381 } 382 383 /* different filesystem, have to copy the file */ 384 if ((src = fopen(from, "r")) == NULL) { 385 warn("%s", from); 386 return (-1); 387 } 388 if ((dst = fopen(to, "w")) == NULL) { 389 warn("%s", to); 390 (void)fclose(src); 391 return (-1); 392 } 393 if (fchmod(fileno(dst), perm)) { 394 warn("%s", to); 395 (void)unlink(to); 396 (void)fclose(src); 397 (void)fclose(dst); 398 return (-1); 399 } 400 401 buf = xmalloc(MAXBSIZE); 402 while ((nread = fread(buf, sizeof(char), MAXBSIZE, src)) != 0) { 403 if (ferror(src)) { 404 warnx("failed to read `%s'", from); 405 (void)unlink(to); 406 goto out; 407 } 408 nwritten = fwrite(buf, sizeof(char), nread, dst); 409 if (nwritten != nread) { 410 warnx("failed to write `%s'", to); 411 (void)unlink(to); 412 goto out; 413 } 414 } 415 416 (void)unlink(from); 417 418 out: 419 (void)fclose(src); 420 (void)fclose(dst); 421 xfree(buf); 422 423 return (0); 424 } 425 426 /* 427 * rcs_head_set() 428 * 429 * Set the revision number of the head revision for the RCS file <file> to 430 * <rev>, which must reference a valid revision within the file. 431 */ 432 int 433 rcs_head_set(RCSFILE *file, RCSNUM *rev) 434 { 435 if (rcs_findrev(file, rev) == NULL) 436 return (-1); 437 438 if (file->rf_head == NULL) 439 file->rf_head = rcsnum_alloc(); 440 441 rcsnum_cpy(rev, file->rf_head, 0); 442 file->rf_flags &= ~RCS_SYNCED; 443 return (0); 444 } 445 446 447 /* 448 * rcs_branch_get() 449 * 450 * Retrieve the default branch number for the RCS file <file>. 451 * Returns the number on success. If NULL is returned, then there is no 452 * default branch for this file. 453 */ 454 const RCSNUM * 455 rcs_branch_get(RCSFILE *file) 456 { 457 return (file->rf_branch); 458 } 459 460 /* 461 * rcs_access_add() 462 * 463 * Add the login name <login> to the access list for the RCS file <file>. 464 * Returns 0 on success, or -1 on failure. 465 */ 466 int 467 rcs_access_add(RCSFILE *file, const char *login) 468 { 469 struct rcs_access *ap; 470 471 /* first look for duplication */ 472 TAILQ_FOREACH(ap, &(file->rf_access), ra_list) { 473 if (strcmp(ap->ra_name, login) == 0) { 474 rcs_errno = RCS_ERR_DUPENT; 475 return (-1); 476 } 477 } 478 479 ap = xmalloc(sizeof(*ap)); 480 ap->ra_name = xstrdup(login); 481 TAILQ_INSERT_TAIL(&(file->rf_access), ap, ra_list); 482 483 /* not synced anymore */ 484 file->rf_flags &= ~RCS_SYNCED; 485 return (0); 486 } 487 488 /* 489 * rcs_access_remove() 490 * 491 * Remove an entry with login name <login> from the access list of the RCS 492 * file <file>. 493 * Returns 0 on success, or -1 on failure. 494 */ 495 int 496 rcs_access_remove(RCSFILE *file, const char *login) 497 { 498 struct rcs_access *ap; 499 500 TAILQ_FOREACH(ap, &(file->rf_access), ra_list) 501 if (strcmp(ap->ra_name, login) == 0) 502 break; 503 504 if (ap == NULL) { 505 rcs_errno = RCS_ERR_NOENT; 506 return (-1); 507 } 508 509 TAILQ_REMOVE(&(file->rf_access), ap, ra_list); 510 xfree(ap->ra_name); 511 xfree(ap); 512 513 /* not synced anymore */ 514 file->rf_flags &= ~RCS_SYNCED; 515 return (0); 516 } 517 518 /* 519 * rcs_sym_add() 520 * 521 * Add a symbol to the list of symbols for the RCS file <rfp>. The new symbol 522 * is named <sym> and is bound to the RCS revision <snum>. 523 * Returns 0 on success, or -1 on failure. 524 */ 525 int 526 rcs_sym_add(RCSFILE *rfp, const char *sym, RCSNUM *snum) 527 { 528 struct rcs_sym *symp; 529 530 if (!rcs_sym_check(sym)) { 531 rcs_errno = RCS_ERR_BADSYM; 532 return (-1); 533 } 534 535 /* first look for duplication */ 536 TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) { 537 if (strcmp(symp->rs_name, sym) == 0) { 538 rcs_errno = RCS_ERR_DUPENT; 539 return (-1); 540 } 541 } 542 543 symp = xmalloc(sizeof(*symp)); 544 symp->rs_name = xstrdup(sym); 545 symp->rs_num = rcsnum_alloc(); 546 rcsnum_cpy(snum, symp->rs_num, 0); 547 548 TAILQ_INSERT_HEAD(&(rfp->rf_symbols), symp, rs_list); 549 550 /* not synced anymore */ 551 rfp->rf_flags &= ~RCS_SYNCED; 552 return (0); 553 } 554 555 /* 556 * rcs_sym_remove() 557 * 558 * Remove the symbol with name <sym> from the symbol list for the RCS file 559 * <file>. If no such symbol is found, the call fails and returns with an 560 * error. 561 * Returns 0 on success, or -1 on failure. 562 */ 563 int 564 rcs_sym_remove(RCSFILE *file, const char *sym) 565 { 566 struct rcs_sym *symp; 567 568 if (!rcs_sym_check(sym)) { 569 rcs_errno = RCS_ERR_BADSYM; 570 return (-1); 571 } 572 573 TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list) 574 if (strcmp(symp->rs_name, sym) == 0) 575 break; 576 577 if (symp == NULL) { 578 rcs_errno = RCS_ERR_NOENT; 579 return (-1); 580 } 581 582 TAILQ_REMOVE(&(file->rf_symbols), symp, rs_list); 583 xfree(symp->rs_name); 584 rcsnum_free(symp->rs_num); 585 xfree(symp); 586 587 /* not synced anymore */ 588 file->rf_flags &= ~RCS_SYNCED; 589 return (0); 590 } 591 592 /* 593 * rcs_sym_getrev() 594 * 595 * Retrieve the RCS revision number associated with the symbol <sym> for the 596 * RCS file <file>. The returned value is a dynamically-allocated copy and 597 * should be freed by the caller once they are done with it. 598 * Returns the RCSNUM on success, or NULL on failure. 599 */ 600 RCSNUM * 601 rcs_sym_getrev(RCSFILE *file, const char *sym) 602 { 603 RCSNUM *num; 604 struct rcs_sym *symp; 605 606 if (!rcs_sym_check(sym)) { 607 rcs_errno = RCS_ERR_BADSYM; 608 return (NULL); 609 } 610 611 num = NULL; 612 TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list) 613 if (strcmp(symp->rs_name, sym) == 0) 614 break; 615 616 if (symp == NULL) { 617 rcs_errno = RCS_ERR_NOENT; 618 } else { 619 num = rcsnum_alloc(); 620 rcsnum_cpy(symp->rs_num, num, 0); 621 } 622 623 return (num); 624 } 625 626 /* 627 * rcs_sym_check() 628 * 629 * Check the RCS symbol name <sym> for any unsupported characters. 630 * Returns 1 if the tag is correct, 0 if it isn't valid. 631 */ 632 int 633 rcs_sym_check(const char *sym) 634 { 635 int ret; 636 const unsigned char *cp; 637 638 ret = 1; 639 cp = sym; 640 if (!isalpha(*cp++)) 641 return (0); 642 643 for (; *cp != '\0'; cp++) 644 if (!isgraph(*cp) || (strchr(rcs_sym_invch, *cp) != NULL)) { 645 ret = 0; 646 break; 647 } 648 649 return (ret); 650 } 651 652 /* 653 * rcs_lock_getmode() 654 * 655 * Retrieve the locking mode of the RCS file <file>. 656 */ 657 int 658 rcs_lock_getmode(RCSFILE *file) 659 { 660 return (file->rf_flags & RCS_SLOCK) ? RCS_LOCK_STRICT : RCS_LOCK_LOOSE; 661 } 662 663 /* 664 * rcs_lock_setmode() 665 * 666 * Set the locking mode of the RCS file <file> to <mode>, which must either 667 * be RCS_LOCK_LOOSE or RCS_LOCK_STRICT. 668 * Returns the previous mode on success, or -1 on failure. 669 */ 670 int 671 rcs_lock_setmode(RCSFILE *file, int mode) 672 { 673 int pmode; 674 pmode = rcs_lock_getmode(file); 675 676 if (mode == RCS_LOCK_STRICT) 677 file->rf_flags |= RCS_SLOCK; 678 else if (mode == RCS_LOCK_LOOSE) 679 file->rf_flags &= ~RCS_SLOCK; 680 else 681 errx(1, "rcs_lock_setmode: invalid mode `%d'", mode); 682 683 file->rf_flags &= ~RCS_SYNCED; 684 return (pmode); 685 } 686 687 /* 688 * rcs_lock_add() 689 * 690 * Add an RCS lock for the user <user> on revision <rev>. 691 * Returns 0 on success, or -1 on failure. 692 */ 693 int 694 rcs_lock_add(RCSFILE *file, const char *user, RCSNUM *rev) 695 { 696 struct rcs_lock *lkp; 697 698 /* first look for duplication */ 699 TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) { 700 if (strcmp(lkp->rl_name, user) == 0 && 701 rcsnum_cmp(rev, lkp->rl_num, 0) == 0) { 702 rcs_errno = RCS_ERR_DUPENT; 703 return (-1); 704 } 705 } 706 707 lkp = xmalloc(sizeof(*lkp)); 708 lkp->rl_name = xstrdup(user); 709 lkp->rl_num = rcsnum_alloc(); 710 rcsnum_cpy(rev, lkp->rl_num, 0); 711 712 TAILQ_INSERT_TAIL(&(file->rf_locks), lkp, rl_list); 713 714 /* not synced anymore */ 715 file->rf_flags &= ~RCS_SYNCED; 716 return (0); 717 } 718 719 720 /* 721 * rcs_lock_remove() 722 * 723 * Remove the RCS lock on revision <rev>. 724 * Returns 0 on success, or -1 on failure. 725 */ 726 int 727 rcs_lock_remove(RCSFILE *file, const char *user, RCSNUM *rev) 728 { 729 struct rcs_lock *lkp; 730 731 TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) { 732 if (strcmp(lkp->rl_name, user) == 0 && 733 rcsnum_cmp(lkp->rl_num, rev, 0) == 0) 734 break; 735 } 736 737 if (lkp == NULL) { 738 rcs_errno = RCS_ERR_NOENT; 739 return (-1); 740 } 741 742 TAILQ_REMOVE(&(file->rf_locks), lkp, rl_list); 743 rcsnum_free(lkp->rl_num); 744 xfree(lkp->rl_name); 745 xfree(lkp); 746 747 /* not synced anymore */ 748 file->rf_flags &= ~RCS_SYNCED; 749 return (0); 750 } 751 752 /* 753 * rcs_desc_set() 754 * 755 * Set the description for the RCS file <file>. 756 */ 757 void 758 rcs_desc_set(RCSFILE *file, const char *desc) 759 { 760 char *tmp; 761 762 tmp = xstrdup(desc); 763 if (file->rf_desc != NULL) 764 xfree(file->rf_desc); 765 file->rf_desc = tmp; 766 file->rf_flags &= ~RCS_SYNCED; 767 } 768 769 /* 770 * rcs_comment_set() 771 * 772 * Set the comment leader for the RCS file <file>. 773 */ 774 void 775 rcs_comment_set(RCSFILE *file, const char *comment) 776 { 777 char *tmp; 778 779 tmp = xstrdup(comment); 780 if (file->rf_comment != NULL) 781 xfree(file->rf_comment); 782 file->rf_comment = tmp; 783 file->rf_flags &= ~RCS_SYNCED; 784 } 785 786 int 787 rcs_patch_lines(struct rcs_lines *dlines, struct rcs_lines *plines) 788 { 789 char op, *ep; 790 struct rcs_line *lp, *dlp, *ndlp; 791 int i, lineno, nbln; 792 u_char tmp; 793 794 dlp = TAILQ_FIRST(&(dlines->l_lines)); 795 lp = TAILQ_FIRST(&(plines->l_lines)); 796 797 /* skip first bogus line */ 798 for (lp = TAILQ_NEXT(lp, l_list); lp != NULL; 799 lp = TAILQ_NEXT(lp, l_list)) { 800 if (lp->l_len < 2) 801 errx(1, "line too short, RCS patch seems broken"); 802 op = *(lp->l_line); 803 /* NUL-terminate line buffer for strtol() safety. */ 804 tmp = lp->l_line[lp->l_len - 1]; 805 lp->l_line[lp->l_len - 1] = '\0'; 806 lineno = (int)strtol((lp->l_line + 1), &ep, 10); 807 if (lineno > dlines->l_nblines || lineno < 0 || 808 *ep != ' ') 809 errx(1, "invalid line specification in RCS patch"); 810 ep++; 811 nbln = (int)strtol(ep, &ep, 10); 812 /* Restore the last byte of the buffer */ 813 lp->l_line[lp->l_len - 1] = tmp; 814 if (nbln < 0) 815 errx(1, 816 "invalid line number specification in RCS patch"); 817 818 /* find the appropriate line */ 819 for (;;) { 820 if (dlp == NULL) 821 break; 822 if (dlp->l_lineno == lineno) 823 break; 824 if (dlp->l_lineno > lineno) { 825 dlp = TAILQ_PREV(dlp, tqh, l_list); 826 } else if (dlp->l_lineno < lineno) { 827 if (((ndlp = TAILQ_NEXT(dlp, l_list)) == NULL) || 828 ndlp->l_lineno > lineno) 829 break; 830 dlp = ndlp; 831 } 832 } 833 if (dlp == NULL) 834 errx(1, "can't find referenced line in RCS patch"); 835 836 if (op == 'd') { 837 for (i = 0; (i < nbln) && (dlp != NULL); i++) { 838 ndlp = TAILQ_NEXT(dlp, l_list); 839 TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list); 840 xfree(dlp); 841 dlp = ndlp; 842 /* last line is gone - reset dlp */ 843 if (dlp == NULL) { 844 ndlp = TAILQ_LAST(&(dlines->l_lines), 845 tqh); 846 dlp = ndlp; 847 } 848 } 849 } else if (op == 'a') { 850 for (i = 0; i < nbln; i++) { 851 ndlp = lp; 852 lp = TAILQ_NEXT(lp, l_list); 853 if (lp == NULL) 854 errx(1, "truncated RCS patch"); 855 TAILQ_REMOVE(&(plines->l_lines), lp, l_list); 856 TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp, 857 lp, l_list); 858 dlp = lp; 859 860 /* we don't want lookup to block on those */ 861 lp->l_lineno = lineno; 862 863 lp = ndlp; 864 } 865 } else 866 errx(1, "unknown RCS patch operation `%c'", op); 867 868 /* last line of the patch, done */ 869 if (lp->l_lineno == plines->l_nblines) 870 break; 871 } 872 873 /* once we're done patching, rebuild the line numbers */ 874 lineno = 0; 875 TAILQ_FOREACH(lp, &(dlines->l_lines), l_list) 876 lp->l_lineno = lineno++; 877 dlines->l_nblines = lineno - 1; 878 879 return (0); 880 } 881 882 /* 883 * rcs_getrev() 884 * 885 * Get the whole contents of revision <rev> from the RCSFILE <rfp>. The 886 * returned buffer is dynamically allocated and should be released using 887 * buf_free() once the caller is done using it. 888 */ 889 BUF * 890 rcs_getrev(RCSFILE *rfp, RCSNUM *frev) 891 { 892 u_int i, numlen; 893 int isbranch, lookonbranch, found; 894 size_t dlen, plen, len; 895 RCSNUM *crev, *rev, *brev; 896 BUF *rbuf; 897 struct rcs_delta *rdp = NULL; 898 struct rcs_branch *rb; 899 u_char *data, *patch; 900 901 if (rfp->rf_head == NULL) 902 return (NULL); 903 904 if (frev == RCS_HEAD_REV) 905 rev = rfp->rf_head; 906 else 907 rev = frev; 908 909 /* XXX rcsnum_cmp() */ 910 for (i = 0; i < rfp->rf_head->rn_len; i++) { 911 if (rfp->rf_head->rn_id[i] < rev->rn_id[i]) { 912 rcs_errno = RCS_ERR_NOENT; 913 return (NULL); 914 } 915 } 916 917 /* No matter what, we'll need everything parsed up until the description 918 so go for it. */ 919 if (rcsparse_deltas(rfp, NULL)) 920 return (NULL); 921 922 rdp = rcs_findrev(rfp, rfp->rf_head); 923 if (rdp == NULL) { 924 warnx("failed to get RCS HEAD revision"); 925 return (NULL); 926 } 927 928 if (rdp->rd_tlen == 0) 929 if (rcsparse_deltatexts(rfp, rfp->rf_head)) 930 return (NULL); 931 932 len = rdp->rd_tlen; 933 if (len == 0) { 934 rbuf = buf_alloc(1); 935 buf_empty(rbuf); 936 return (rbuf); 937 } 938 939 rbuf = buf_alloc(len); 940 buf_append(rbuf, rdp->rd_text, len); 941 942 isbranch = 0; 943 brev = NULL; 944 945 /* 946 * If a branch was passed, get the latest revision on it. 947 */ 948 if (RCSNUM_ISBRANCH(rev)) { 949 brev = rev; 950 rdp = rcs_findrev(rfp, rev); 951 if (rdp == NULL) { 952 buf_free(rbuf); 953 return (NULL); 954 } 955 956 rev = rdp->rd_num; 957 } else { 958 if (RCSNUM_ISBRANCHREV(rev)) { 959 brev = rcsnum_revtobr(rev); 960 isbranch = 1; 961 } 962 } 963 964 lookonbranch = 0; 965 crev = NULL; 966 967 /* Apply patches backwards to get the right version. 968 */ 969 do { 970 found = 0; 971 972 if (rcsnum_cmp(rfp->rf_head, rev, 0) == 0) 973 break; 974 975 if (isbranch == 1 && rdp->rd_num->rn_len < rev->rn_len && 976 !TAILQ_EMPTY(&(rdp->rd_branches))) 977 lookonbranch = 1; 978 979 if (isbranch && lookonbranch == 1) { 980 lookonbranch = 0; 981 TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) { 982 /* XXX rcsnum_cmp() is totally broken for 983 * this purpose. 984 */ 985 numlen = MIN(brev->rn_len, 986 rb->rb_num->rn_len - 1); 987 for (i = 0; i < numlen; i++) { 988 if (rb->rb_num->rn_id[i] != 989 brev->rn_id[i]) 990 break; 991 } 992 993 if (i == numlen) { 994 crev = rb->rb_num; 995 found = 1; 996 break; 997 } 998 } 999 if (found == 0) 1000 crev = rdp->rd_next; 1001 } else { 1002 crev = rdp->rd_next; 1003 } 1004 1005 rdp = rcs_findrev(rfp, crev); 1006 if (rdp == NULL) { 1007 buf_free(rbuf); 1008 return (NULL); 1009 } 1010 1011 plen = rdp->rd_tlen; 1012 dlen = buf_len(rbuf); 1013 patch = rdp->rd_text; 1014 data = buf_release(rbuf); 1015 /* check if we have parsed this rev's deltatext */ 1016 if (rdp->rd_tlen == 0) 1017 if (rcsparse_deltatexts(rfp, rdp->rd_num)) 1018 return (NULL); 1019 1020 rbuf = rcs_patchfile(data, dlen, patch, plen, rcs_patch_lines); 1021 xfree(data); 1022 1023 if (rbuf == NULL) 1024 break; 1025 } while (rcsnum_cmp(crev, rev, 0) != 0); 1026 1027 return (rbuf); 1028 } 1029 1030 void 1031 rcs_delta_stats(struct rcs_delta *rdp, int *ladded, int *lremoved) 1032 { 1033 struct rcs_lines *plines; 1034 struct rcs_line *lp; 1035 int added, i, nbln, removed; 1036 char op, *ep; 1037 u_char tmp; 1038 1039 added = removed = 0; 1040 1041 plines = rcs_splitlines(rdp->rd_text, rdp->rd_tlen); 1042 lp = TAILQ_FIRST(&(plines->l_lines)); 1043 1044 /* skip first bogus line */ 1045 for (lp = TAILQ_NEXT(lp, l_list); lp != NULL; 1046 lp = TAILQ_NEXT(lp, l_list)) { 1047 if (lp->l_len < 2) 1048 errx(1, 1049 "line too short, RCS patch seems broken"); 1050 op = *(lp->l_line); 1051 /* NUL-terminate line buffer for strtol() safety. */ 1052 tmp = lp->l_line[lp->l_len - 1]; 1053 lp->l_line[lp->l_len - 1] = '\0'; 1054 (void)strtol((lp->l_line + 1), &ep, 10); 1055 ep++; 1056 nbln = (int)strtol(ep, &ep, 10); 1057 /* Restore the last byte of the buffer */ 1058 lp->l_line[lp->l_len - 1] = tmp; 1059 if (nbln < 0) 1060 errx(1, "invalid line number specification " 1061 "in RCS patch"); 1062 1063 if (op == 'a') { 1064 added += nbln; 1065 for (i = 0; i < nbln; i++) { 1066 lp = TAILQ_NEXT(lp, l_list); 1067 if (lp == NULL) 1068 errx(1, "truncated RCS patch"); 1069 } 1070 } else if (op == 'd') 1071 removed += nbln; 1072 else 1073 errx(1, "unknown RCS patch operation '%c'", op); 1074 } 1075 1076 rcs_freelines(plines); 1077 1078 *ladded = added; 1079 *lremoved = removed; 1080 } 1081 1082 /* 1083 * rcs_rev_add() 1084 * 1085 * Add a revision to the RCS file <rf>. The new revision's number can be 1086 * specified in <rev> (which can also be RCS_HEAD_REV, in which case the 1087 * new revision will have a number equal to the previous head revision plus 1088 * one). The <msg> argument specifies the log message for that revision, and 1089 * <date> specifies the revision's date (a value of -1 is 1090 * equivalent to using the current time). 1091 * If <author> is NULL, set the author for this revision to the current user. 1092 * Returns 0 on success, or -1 on failure. 1093 */ 1094 int 1095 rcs_rev_add(RCSFILE *rf, RCSNUM *rev, const char *msg, time_t date, 1096 const char *author) 1097 { 1098 time_t now; 1099 struct passwd *pw; 1100 struct rcs_delta *ordp, *rdp; 1101 1102 if (rev == RCS_HEAD_REV) { 1103 if (rf->rf_flags & RCS_CREATE) { 1104 if ((rev = rcsnum_parse(RCS_HEAD_INIT)) == NULL) 1105 return (-1); 1106 rf->rf_head = rev; 1107 } else { 1108 rev = rcsnum_inc(rf->rf_head); 1109 } 1110 } else { 1111 if ((rdp = rcs_findrev(rf, rev)) != NULL) { 1112 rcs_errno = RCS_ERR_DUPENT; 1113 return (-1); 1114 } 1115 } 1116 1117 rdp = xcalloc(1, sizeof(*rdp)); 1118 1119 TAILQ_INIT(&(rdp->rd_branches)); 1120 1121 rdp->rd_num = rcsnum_alloc(); 1122 rcsnum_cpy(rev, rdp->rd_num, 0); 1123 1124 rdp->rd_next = rcsnum_alloc(); 1125 1126 if (!(rf->rf_flags & RCS_CREATE)) { 1127 /* next should point to the previous HEAD */ 1128 ordp = TAILQ_FIRST(&(rf->rf_delta)); 1129 rcsnum_cpy(ordp->rd_num, rdp->rd_next, 0); 1130 } 1131 1132 if (!author && !(author = getlogin())) { 1133 if (!(pw = getpwuid(getuid()))) 1134 errx(1, "getpwuid failed"); 1135 author = pw->pw_name; 1136 } 1137 rdp->rd_author = xstrdup(author); 1138 rdp->rd_state = xstrdup(RCS_STATE_EXP); 1139 rdp->rd_log = xstrdup(msg); 1140 1141 if (date != (time_t)(-1)) 1142 now = date; 1143 else 1144 time(&now); 1145 gmtime_r(&now, &(rdp->rd_date)); 1146 1147 TAILQ_INSERT_HEAD(&(rf->rf_delta), rdp, rd_list); 1148 rf->rf_ndelta++; 1149 1150 /* not synced anymore */ 1151 rf->rf_flags &= ~RCS_SYNCED; 1152 1153 return (0); 1154 } 1155 1156 /* 1157 * rcs_rev_remove() 1158 * 1159 * Remove the revision whose number is <rev> from the RCS file <rf>. 1160 */ 1161 int 1162 rcs_rev_remove(RCSFILE *rf, RCSNUM *rev) 1163 { 1164 char *path_tmp1, *path_tmp2; 1165 struct rcs_delta *rdp, *prevrdp, *nextrdp; 1166 BUF *newdeltatext, *nextbuf, *prevbuf, *newdiff; 1167 1168 nextrdp = prevrdp = NULL; 1169 path_tmp1 = path_tmp2 = NULL; 1170 1171 if (rev == RCS_HEAD_REV) 1172 rev = rf->rf_head; 1173 1174 /* do we actually have that revision? */ 1175 if ((rdp = rcs_findrev(rf, rev)) == NULL) { 1176 rcs_errno = RCS_ERR_NOENT; 1177 return (-1); 1178 } 1179 1180 /* 1181 * This is confusing, the previous delta is next in the TAILQ list. 1182 * the next delta is the previous one in the TAILQ list. 1183 * 1184 * When the HEAD revision got specified, nextrdp will be NULL. 1185 * When the first revision got specified, prevrdp will be NULL. 1186 */ 1187 prevrdp = (struct rcs_delta *)TAILQ_NEXT(rdp, rd_list); 1188 nextrdp = (struct rcs_delta *)TAILQ_PREV(rdp, tqh, rd_list); 1189 1190 newdeltatext = prevbuf = nextbuf = NULL; 1191 1192 if (prevrdp != NULL) { 1193 if ((prevbuf = rcs_getrev(rf, prevrdp->rd_num)) == NULL) 1194 errx(1, "error getting revision"); 1195 } 1196 1197 if (prevrdp != NULL && nextrdp != NULL) { 1198 if ((nextbuf = rcs_getrev(rf, nextrdp->rd_num)) == NULL) 1199 errx(1, "error getting revision"); 1200 1201 newdiff = buf_alloc(64); 1202 1203 /* calculate new diff */ 1204 (void)xasprintf(&path_tmp1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir); 1205 buf_write_stmp(nextbuf, path_tmp1); 1206 buf_free(nextbuf); 1207 1208 (void)xasprintf(&path_tmp2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir); 1209 buf_write_stmp(prevbuf, path_tmp2); 1210 buf_free(prevbuf); 1211 1212 diff_format = D_RCSDIFF; 1213 if (diffreg(path_tmp1, path_tmp2, newdiff, D_FORCEASCII) == D_ERROR) 1214 errx(1, "diffreg failed"); 1215 1216 newdeltatext = newdiff; 1217 } else if (nextrdp == NULL && prevrdp != NULL) { 1218 newdeltatext = prevbuf; 1219 } 1220 1221 if (newdeltatext != NULL) { 1222 if (rcs_deltatext_set(rf, prevrdp->rd_num, newdeltatext) < 0) 1223 errx(1, "error setting new deltatext"); 1224 } 1225 1226 TAILQ_REMOVE(&(rf->rf_delta), rdp, rd_list); 1227 1228 /* update pointers */ 1229 if (prevrdp != NULL && nextrdp != NULL) { 1230 rcsnum_cpy(prevrdp->rd_num, nextrdp->rd_next, 0); 1231 } else if (prevrdp != NULL) { 1232 if (rcs_head_set(rf, prevrdp->rd_num) < 0) 1233 errx(1, "rcs_head_set failed"); 1234 } else if (nextrdp != NULL) { 1235 rcsnum_free(nextrdp->rd_next); 1236 nextrdp->rd_next = rcsnum_alloc(); 1237 } else { 1238 rcsnum_free(rf->rf_head); 1239 rf->rf_head = NULL; 1240 } 1241 1242 rf->rf_ndelta--; 1243 rf->rf_flags &= ~RCS_SYNCED; 1244 1245 rcs_freedelta(rdp); 1246 1247 if (path_tmp1 != NULL) 1248 xfree(path_tmp1); 1249 if (path_tmp2 != NULL) 1250 xfree(path_tmp2); 1251 1252 return (0); 1253 } 1254 1255 /* 1256 * rcs_findrev() 1257 * 1258 * Find a specific revision's delta entry in the tree of the RCS file <rfp>. 1259 * The revision number is given in <rev>. 1260 * 1261 * If the given revision is a branch number, we translate it into the latest 1262 * revision on the branch. 1263 * 1264 * Returns a pointer to the delta on success, or NULL on failure. 1265 */ 1266 struct rcs_delta * 1267 rcs_findrev(RCSFILE *rfp, RCSNUM *rev) 1268 { 1269 u_int cmplen; 1270 struct rcs_delta *rdp; 1271 RCSNUM *brev, *frev; 1272 1273 /* 1274 * We need to do more parsing if the last revision in the linked list 1275 * is greater than the requested revision. 1276 */ 1277 rdp = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist); 1278 if (rdp == NULL || 1279 rcsnum_cmp(rdp->rd_num, rev, 0) == -1) { 1280 if (rcsparse_deltas(rfp, rev)) 1281 return (NULL); 1282 } 1283 1284 /* 1285 * Translate a branch into the latest revision on the branch itself. 1286 */ 1287 if (RCSNUM_ISBRANCH(rev)) { 1288 brev = rcsnum_brtorev(rev); 1289 frev = brev; 1290 for (;;) { 1291 rdp = rcs_findrev(rfp, frev); 1292 if (rdp == NULL) 1293 return (NULL); 1294 1295 if (rdp->rd_next->rn_len == 0) 1296 break; 1297 1298 frev = rdp->rd_next; 1299 } 1300 1301 rcsnum_free(brev); 1302 return (rdp); 1303 } 1304 1305 cmplen = rev->rn_len; 1306 1307 TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) { 1308 if (rcsnum_cmp(rdp->rd_num, rev, cmplen) == 0) 1309 return (rdp); 1310 } 1311 1312 return (NULL); 1313 } 1314 1315 /* 1316 * rcs_kwexp_set() 1317 * 1318 * Set the keyword expansion mode to use on the RCS file <file> to <mode>. 1319 */ 1320 void 1321 rcs_kwexp_set(RCSFILE *file, int mode) 1322 { 1323 int i; 1324 char *tmp, buf[8] = ""; 1325 1326 if (RCS_KWEXP_INVAL(mode)) 1327 return; 1328 1329 i = 0; 1330 if (mode == RCS_KWEXP_NONE) 1331 buf[0] = 'b'; 1332 else if (mode == RCS_KWEXP_OLD) 1333 buf[0] = 'o'; 1334 else { 1335 if (mode & RCS_KWEXP_NAME) 1336 buf[i++] = 'k'; 1337 if (mode & RCS_KWEXP_VAL) 1338 buf[i++] = 'v'; 1339 if (mode & RCS_KWEXP_LKR) 1340 buf[i++] = 'l'; 1341 } 1342 1343 tmp = xstrdup(buf); 1344 if (file->rf_expand != NULL) 1345 xfree(file->rf_expand); 1346 file->rf_expand = tmp; 1347 /* not synced anymore */ 1348 file->rf_flags &= ~RCS_SYNCED; 1349 } 1350 1351 /* 1352 * rcs_kwexp_get() 1353 * 1354 * Retrieve the keyword expansion mode to be used for the RCS file <file>. 1355 */ 1356 int 1357 rcs_kwexp_get(RCSFILE *file) 1358 { 1359 if (file->rf_expand == NULL) 1360 return (RCS_KWEXP_DEFAULT); 1361 1362 return (rcs_kflag_get(file->rf_expand)); 1363 } 1364 1365 /* 1366 * rcs_kflag_get() 1367 * 1368 * Get the keyword expansion mode from a set of character flags given in 1369 * <flags> and return the appropriate flag mask. In case of an error, the 1370 * returned mask will have the RCS_KWEXP_ERR bit set to 1. 1371 */ 1372 int 1373 rcs_kflag_get(const char *flags) 1374 { 1375 int fl; 1376 size_t len; 1377 const char *fp; 1378 1379 if (flags == NULL || !(len = strlen(flags))) 1380 return (RCS_KWEXP_ERR); 1381 1382 fl = 0; 1383 for (fp = flags; *fp != '\0'; fp++) { 1384 if (*fp == 'k') 1385 fl |= RCS_KWEXP_NAME; 1386 else if (*fp == 'v') 1387 fl |= RCS_KWEXP_VAL; 1388 else if (*fp == 'l') 1389 fl |= RCS_KWEXP_LKR; 1390 else if (*fp == 'o') { 1391 if (len != 1) 1392 fl |= RCS_KWEXP_ERR; 1393 fl |= RCS_KWEXP_OLD; 1394 } else if (*fp == 'b') { 1395 if (len != 1) 1396 fl |= RCS_KWEXP_ERR; 1397 fl |= RCS_KWEXP_NONE; 1398 } else /* unknown letter */ 1399 fl |= RCS_KWEXP_ERR; 1400 } 1401 1402 return (fl); 1403 } 1404 1405 /* 1406 * rcs_freedelta() 1407 * 1408 * Free the contents of a delta structure. 1409 */ 1410 static void 1411 rcs_freedelta(struct rcs_delta *rdp) 1412 { 1413 struct rcs_branch *rb; 1414 1415 if (rdp->rd_num != NULL) 1416 rcsnum_free(rdp->rd_num); 1417 if (rdp->rd_next != NULL) 1418 rcsnum_free(rdp->rd_next); 1419 1420 if (rdp->rd_author != NULL) 1421 xfree(rdp->rd_author); 1422 if (rdp->rd_locker != NULL) 1423 xfree(rdp->rd_locker); 1424 if (rdp->rd_state != NULL) 1425 xfree(rdp->rd_state); 1426 if (rdp->rd_log != NULL) 1427 xfree(rdp->rd_log); 1428 if (rdp->rd_text != NULL) 1429 xfree(rdp->rd_text); 1430 1431 while ((rb = TAILQ_FIRST(&(rdp->rd_branches))) != NULL) { 1432 TAILQ_REMOVE(&(rdp->rd_branches), rb, rb_list); 1433 rcsnum_free(rb->rb_num); 1434 xfree(rb); 1435 } 1436 1437 xfree(rdp); 1438 } 1439 1440 /* 1441 * rcs_strprint() 1442 * 1443 * Output an RCS string <str> of size <slen> to the stream <stream>. Any 1444 * '@' characters are escaped. Otherwise, the string can contain arbitrary 1445 * binary data. 1446 */ 1447 static void 1448 rcs_strprint(const u_char *str, size_t slen, FILE *stream) 1449 { 1450 const u_char *ap, *ep, *sp; 1451 1452 if (slen == 0) 1453 return; 1454 1455 ep = str + slen - 1; 1456 1457 for (sp = str; sp <= ep;) { 1458 ap = memchr(sp, '@', ep - sp); 1459 if (ap == NULL) 1460 ap = ep; 1461 (void)fwrite(sp, sizeof(u_char), ap - sp + 1, stream); 1462 1463 if (*ap == '@') 1464 putc('@', stream); 1465 sp = ap + 1; 1466 } 1467 } 1468 1469 /* 1470 * rcs_expand_keywords() 1471 * 1472 * Return expansion any RCS keywords in <data> 1473 * 1474 * On error, return NULL. 1475 */ 1476 static BUF * 1477 rcs_expand_keywords(char *rcsfile_in, struct rcs_delta *rdp, BUF *bp, int mode) 1478 { 1479 BUF *newbuf; 1480 u_char *c, *kw, *fin; 1481 char buf[256], *tmpf, resolved[MAXPATHLEN], *rcsfile; 1482 u_char *line, *line2; 1483 u_int i, j; 1484 int kwtype; 1485 int found; 1486 struct tm tb; 1487 1488 tb = rdp->rd_date; 1489 if (timezone_flag != NULL) 1490 rcs_set_tz(timezone_flag, rdp, &tb); 1491 1492 if (realpath(rcsfile_in, resolved) == NULL) 1493 rcsfile = rcsfile_in; 1494 else 1495 rcsfile = resolved; 1496 1497 newbuf = buf_alloc(buf_len(bp)); 1498 1499 /* 1500 * Keyword formats: 1501 * $Keyword$ 1502 * $Keyword: value$ 1503 */ 1504 c = buf_get(bp); 1505 fin = c + buf_len(bp); 1506 /* Copying to newbuf is deferred until the first keyword. */ 1507 found = 0; 1508 1509 while (c < fin) { 1510 kw = memchr(c, '$', fin - c); 1511 if (kw == NULL) 1512 break; 1513 ++kw; 1514 if (found) { 1515 /* Copy everything up to and including the $. */ 1516 buf_append(newbuf, c, kw - c); 1517 } 1518 c = kw; 1519 /* c points after the $ now. */ 1520 if (c == fin) 1521 break; 1522 if (!isalpha(*c)) /* all valid keywords start with a letter */ 1523 continue; 1524 1525 for (i = 0; i < RCS_NKWORDS; ++i) { 1526 size_t kwlen; 1527 1528 kwlen = strlen(rcs_expkw[i].kw_str); 1529 /* 1530 * kwlen must be less than clen since clen includes 1531 * either a terminating `$' or a `:'. 1532 */ 1533 if (c + kwlen < fin && 1534 memcmp(c , rcs_expkw[i].kw_str, kwlen) == 0 && 1535 (c[kwlen] == '$' || c[kwlen] == ':')) { 1536 c += kwlen; 1537 break; 1538 } 1539 } 1540 if (i == RCS_NKWORDS) 1541 continue; 1542 kwtype = rcs_expkw[i].kw_type; 1543 1544 /* 1545 * If the next character is ':' we need to look for an '$' 1546 * before the end of the line to be sure it is in fact a 1547 * keyword. 1548 */ 1549 if (*c == ':') { 1550 for (; c < fin; ++c) { 1551 if (*c == '$' || *c == '\n') 1552 break; 1553 } 1554 1555 if (*c != '$') { 1556 if (found) 1557 buf_append(newbuf, kw, c - kw); 1558 continue; 1559 } 1560 } 1561 ++c; 1562 1563 if (!found) { 1564 found = 1; 1565 /* Copy everything up to and including the $. */ 1566 buf_append(newbuf, buf_get(bp), kw - buf_get(bp)); 1567 } 1568 1569 if (mode & RCS_KWEXP_NAME) { 1570 buf_puts(newbuf, rcs_expkw[i].kw_str); 1571 if (mode & RCS_KWEXP_VAL) 1572 buf_puts(newbuf, ": "); 1573 } 1574 1575 /* Order matters because of RCS_KW_ID and RCS_KW_HEADER. */ 1576 if (mode & RCS_KWEXP_VAL) { 1577 if (kwtype & (RCS_KW_RCSFILE|RCS_KW_LOG)) { 1578 if ((kwtype & RCS_KW_FULLPATH) || 1579 (tmpf = strrchr(rcsfile, '/')) == NULL) 1580 buf_puts(newbuf, rcsfile); 1581 else 1582 buf_puts(newbuf, tmpf + 1); 1583 buf_putc(newbuf, ' '); 1584 } 1585 1586 if (kwtype & RCS_KW_REVISION) { 1587 rcsnum_tostr(rdp->rd_num, buf, sizeof(buf)); 1588 buf_puts(newbuf, buf); 1589 buf_putc(newbuf, ' '); 1590 } 1591 1592 if (kwtype & RCS_KW_DATE) { 1593 strftime(buf, sizeof(buf), 1594 "%Y/%m/%d %H:%M:%S ", &tb); 1595 buf_puts(newbuf, buf); 1596 } 1597 1598 if (kwtype & RCS_KW_AUTHOR) { 1599 buf_puts(newbuf, rdp->rd_author); 1600 buf_putc(newbuf, ' '); 1601 } 1602 1603 if (kwtype & RCS_KW_STATE) { 1604 buf_puts(newbuf, rdp->rd_state); 1605 buf_putc(newbuf, ' '); 1606 } 1607 1608 /* Order does not matter anymore below. */ 1609 if (kwtype & RCS_KW_SOURCE) { 1610 buf_puts(newbuf, rcsfile); 1611 buf_putc(newbuf, ' '); 1612 } 1613 1614 if (kwtype & RCS_KW_NAME) 1615 buf_putc(newbuf, ' '); 1616 1617 if ((kwtype & RCS_KW_LOCKER)) { 1618 if (rdp->rd_locker) { 1619 buf_puts(newbuf, rdp->rd_locker); 1620 buf_putc(newbuf, ' '); 1621 } 1622 } 1623 } 1624 1625 /* End the expansion. */ 1626 if (mode & RCS_KWEXP_NAME) 1627 buf_putc(newbuf, '$'); 1628 1629 if (kwtype & RCS_KW_LOG) { 1630 line = memrchr(buf_get(bp), '\n', kw - buf_get(bp) - 1); 1631 if (line == NULL) 1632 line = buf_get(bp); 1633 else 1634 ++line; 1635 line2 = kw - 1; 1636 while (line2 > line && line2[-1] == ' ') 1637 --line2; 1638 1639 buf_putc(newbuf, '\n'); 1640 buf_append(newbuf, line, kw - 1 - line); 1641 buf_puts(newbuf, "Revision "); 1642 rcsnum_tostr(rdp->rd_num, buf, sizeof(buf)); 1643 buf_puts(newbuf, buf); 1644 buf_puts(newbuf, " "); 1645 strftime(buf, sizeof(buf), "%Y/%m/%d %H:%M:%S", &tb); 1646 buf_puts(newbuf, buf); 1647 1648 buf_puts(newbuf, " "); 1649 buf_puts(newbuf, rdp->rd_author); 1650 buf_putc(newbuf, '\n'); 1651 1652 for (i = 0; rdp->rd_log[i]; i += j) { 1653 j = strcspn(rdp->rd_log + i, "\n"); 1654 if (j == 0) 1655 buf_append(newbuf, line, line2 - line); 1656 else 1657 buf_append(newbuf, line, kw - 1 - line); 1658 if (rdp->rd_log[i + j]) 1659 ++j; 1660 buf_append(newbuf, rdp->rd_log + i, j); 1661 } 1662 buf_append(newbuf, line, line2 - line); 1663 for (j = 0; c + j < fin; ++j) { 1664 if (c[j] != ' ') 1665 break; 1666 } 1667 if (c + j == fin || c[j] == '\n') 1668 c += j; 1669 } 1670 } 1671 1672 if (found) { 1673 buf_append(newbuf, c, fin - c); 1674 buf_free(bp); 1675 return (newbuf); 1676 } else { 1677 buf_free(newbuf); 1678 return (bp); 1679 } 1680 } 1681 1682 /* 1683 * rcs_deltatext_set() 1684 * 1685 * Set deltatext for <rev> in RCS file <rfp> to <dtext> 1686 * Returns -1 on error, 0 on success. 1687 */ 1688 int 1689 rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, BUF *bp) 1690 { 1691 size_t len; 1692 u_char *dtext; 1693 struct rcs_delta *rdp; 1694 1695 /* Write operations require full parsing */ 1696 if (rcsparse_deltatexts(rfp, NULL)) 1697 return (-1); 1698 1699 if ((rdp = rcs_findrev(rfp, rev)) == NULL) 1700 return (-1); 1701 1702 if (rdp->rd_text != NULL) 1703 xfree(rdp->rd_text); 1704 1705 len = buf_len(bp); 1706 dtext = buf_release(bp); 1707 bp = NULL; 1708 1709 if (len != 0) { 1710 rdp->rd_text = xmalloc(len); 1711 rdp->rd_tlen = len; 1712 (void)memcpy(rdp->rd_text, dtext, len); 1713 } else { 1714 rdp->rd_text = NULL; 1715 rdp->rd_tlen = 0; 1716 } 1717 1718 if (dtext != NULL) 1719 xfree(dtext); 1720 1721 return (0); 1722 } 1723 1724 /* 1725 * rcs_rev_setlog() 1726 * 1727 * Sets the log message of revision <rev> to <logtext>. 1728 */ 1729 int 1730 rcs_rev_setlog(RCSFILE *rfp, RCSNUM *rev, const char *logtext) 1731 { 1732 struct rcs_delta *rdp; 1733 1734 if ((rdp = rcs_findrev(rfp, rev)) == NULL) 1735 return (-1); 1736 1737 if (rdp->rd_log != NULL) 1738 xfree(rdp->rd_log); 1739 1740 rdp->rd_log = xstrdup(logtext); 1741 rfp->rf_flags &= ~RCS_SYNCED; 1742 return (0); 1743 } 1744 /* 1745 * rcs_rev_getdate() 1746 * 1747 * Get the date corresponding to a given revision. 1748 * Returns the date on success, -1 on failure. 1749 */ 1750 time_t 1751 rcs_rev_getdate(RCSFILE *rfp, RCSNUM *rev) 1752 { 1753 struct rcs_delta *rdp; 1754 1755 if ((rdp = rcs_findrev(rfp, rev)) == NULL) 1756 return (-1); 1757 1758 return (mktime(&rdp->rd_date)); 1759 } 1760 1761 /* 1762 * rcs_state_set() 1763 * 1764 * Sets the state of revision <rev> to <state> 1765 * NOTE: default state is 'Exp'. States may not contain spaces. 1766 * 1767 * Returns -1 on failure, 0 on success. 1768 */ 1769 int 1770 rcs_state_set(RCSFILE *rfp, RCSNUM *rev, const char *state) 1771 { 1772 struct rcs_delta *rdp; 1773 1774 if ((rdp = rcs_findrev(rfp, rev)) == NULL) 1775 return (-1); 1776 1777 if (rdp->rd_state != NULL) 1778 xfree(rdp->rd_state); 1779 1780 rdp->rd_state = xstrdup(state); 1781 1782 rfp->rf_flags &= ~RCS_SYNCED; 1783 1784 return (0); 1785 } 1786 1787 /* 1788 * rcs_state_check() 1789 * 1790 * Check if string <state> is valid. 1791 * 1792 * Returns 0 if the string is valid, -1 otherwise. 1793 */ 1794 int 1795 rcs_state_check(const char *state) 1796 { 1797 int ret; 1798 const unsigned char *cp; 1799 1800 ret = 0; 1801 cp = state; 1802 if (!isalpha(*cp++)) 1803 return (-1); 1804 1805 for (; *cp != '\0'; cp++) 1806 if (!isgraph(*cp) || (strchr(rcs_state_invch, *cp) != NULL)) { 1807 ret = -1; 1808 break; 1809 } 1810 1811 return (ret); 1812 } 1813 1814 /* 1815 * rcs_kwexp_buf() 1816 * 1817 * Do keyword expansion on a buffer if necessary 1818 * 1819 */ 1820 BUF * 1821 rcs_kwexp_buf(BUF *bp, RCSFILE *rf, RCSNUM *rev) 1822 { 1823 struct rcs_delta *rdp; 1824 int expmode; 1825 1826 /* 1827 * Do keyword expansion if required. 1828 */ 1829 expmode = rcs_kwexp_get(rf); 1830 1831 if (!(expmode & RCS_KWEXP_NONE)) { 1832 if ((rdp = rcs_findrev(rf, rev)) == NULL) 1833 errx(1, "could not fetch revision"); 1834 return (rcs_expand_keywords(rf->rf_path, rdp, bp, expmode)); 1835 } 1836 return (bp); 1837 } 1838