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