Annotation of src/usr.bin/rcs/rcs.c, Revision 1.1
1.1 ! joris 1: /* $OpenBSD: rcs.c,v 1.170 2006/04/25 10:31:39 xsa 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 "includes.h"
! 28:
! 29: #include "diff.h"
! 30: #include "util.h"
! 31: #include "rcs.h"
! 32: #include "rcsprog.h"
! 33: #include "xmalloc.h"
! 34:
! 35: #define RCS_BUFSIZE 16384
! 36: #define RCS_BUFEXTSIZE 8192
! 37: #define RCS_KWEXP_SIZE 1024
! 38:
! 39: /* RCS token types */
! 40: #define RCS_TOK_ERR -1
! 41: #define RCS_TOK_EOF 0
! 42: #define RCS_TOK_NUM 1
! 43: #define RCS_TOK_ID 2
! 44: #define RCS_TOK_STRING 3
! 45: #define RCS_TOK_SCOLON 4
! 46: #define RCS_TOK_COLON 5
! 47:
! 48: #define RCS_TOK_HEAD 8
! 49: #define RCS_TOK_BRANCH 9
! 50: #define RCS_TOK_ACCESS 10
! 51: #define RCS_TOK_SYMBOLS 11
! 52: #define RCS_TOK_LOCKS 12
! 53: #define RCS_TOK_COMMENT 13
! 54: #define RCS_TOK_EXPAND 14
! 55: #define RCS_TOK_DATE 15
! 56: #define RCS_TOK_AUTHOR 16
! 57: #define RCS_TOK_STATE 17
! 58: #define RCS_TOK_NEXT 18
! 59: #define RCS_TOK_BRANCHES 19
! 60: #define RCS_TOK_DESC 20
! 61: #define RCS_TOK_LOG 21
! 62: #define RCS_TOK_TEXT 22
! 63: #define RCS_TOK_STRICT 23
! 64:
! 65: #define RCS_ISKEY(t) (((t) >= RCS_TOK_HEAD) && ((t) <= RCS_TOK_BRANCHES))
! 66:
! 67: #define RCS_NOSCOL 0x01 /* no terminating semi-colon */
! 68: #define RCS_VOPT 0x02 /* value is optional */
! 69:
! 70: /* opaque parse data */
! 71: struct rcs_pdata {
! 72: u_int rp_lines;
! 73:
! 74: char *rp_buf;
! 75: size_t rp_blen;
! 76: char *rp_bufend;
! 77: size_t rp_tlen;
! 78:
! 79: /* pushback token buffer */
! 80: char rp_ptok[128];
! 81: int rp_pttype; /* token type, RCS_TOK_ERR if no token */
! 82:
! 83: FILE *rp_file;
! 84: };
! 85:
! 86: #define RCS_TOKSTR(rfp) ((struct rcs_pdata *)rfp->rf_pdata)->rp_buf
! 87: #define RCS_TOKLEN(rfp) ((struct rcs_pdata *)rfp->rf_pdata)->rp_tlen
! 88:
! 89: /* invalid characters in RCS symbol names */
! 90: static const char rcs_sym_invch[] = RCS_SYM_INVALCHAR;
! 91:
! 92: /* comment leaders, depending on the file's suffix */
! 93: static const struct rcs_comment {
! 94: const char *rc_suffix;
! 95: const char *rc_cstr;
! 96: } rcs_comments[] = {
! 97: { "1", ".\\\" " },
! 98: { "2", ".\\\" " },
! 99: { "3", ".\\\" " },
! 100: { "4", ".\\\" " },
! 101: { "5", ".\\\" " },
! 102: { "6", ".\\\" " },
! 103: { "7", ".\\\" " },
! 104: { "8", ".\\\" " },
! 105: { "9", ".\\\" " },
! 106: { "a", "-- " }, /* Ada */
! 107: { "ada", "-- " },
! 108: { "adb", "-- " },
! 109: { "asm", ";; " }, /* assembler (MS-DOS) */
! 110: { "ads", "-- " }, /* Ada */
! 111: { "bat", ":: " }, /* batch (MS-DOS) */
! 112: { "body", "-- " }, /* Ada */
! 113: { "c", " * " }, /* C */
! 114: { "c++", "// " }, /* C++ */
! 115: { "cc", "// " },
! 116: { "cpp", "// " },
! 117: { "cxx", "// " },
! 118: { "m", "// " }, /* Objective-C */
! 119: { "cl", ";;; " }, /* Common Lisp */
! 120: { "cmd", ":: " }, /* command (OS/2) */
! 121: { "cmf", "c " }, /* CM Fortran */
! 122: { "csh", "# " }, /* shell */
! 123: { "e", "# " }, /* efl */
! 124: { "epsf", "% " }, /* encapsulated postscript */
! 125: { "epsi", "% " }, /* encapsulated postscript */
! 126: { "el", "; " }, /* Emacs Lisp */
! 127: { "f", "c " }, /* Fortran */
! 128: { "for", "c " },
! 129: { "h", " * " }, /* C-header */
! 130: { "hh", "// " }, /* C++ header */
! 131: { "hpp", "// " },
! 132: { "hxx", "// " },
! 133: { "in", "# " }, /* for Makefile.in */
! 134: { "l", " * " }, /* lex */
! 135: { "mac", ";; " }, /* macro (DEC-10, MS-DOS, PDP-11, VMS, etc) */
! 136: { "mak", "# " }, /* makefile, e.g. Visual C++ */
! 137: { "me", ".\\\" " }, /* me-macros t/nroff */
! 138: { "ml", "; " }, /* mocklisp */
! 139: { "mm", ".\\\" " }, /* mm-macros t/nroff */
! 140: { "ms", ".\\\" " }, /* ms-macros t/nroff */
! 141: { "man", ".\\\" " }, /* man-macros t/nroff */
! 142: { "p", " * " }, /* pascal */
! 143: { "pas", " * " },
! 144: { "pl", "# " }, /* Perl (conflict with Prolog) */
! 145: { "pm", "# " }, /* Perl module */
! 146: { "ps", "% " }, /* postscript */
! 147: { "psw", "% " }, /* postscript wrap */
! 148: { "pswm", "% " }, /* postscript wrap */
! 149: { "r", "# " }, /* ratfor */
! 150: { "rc", " * " }, /* Microsoft Windows resource file */
! 151: { "red", "% " }, /* psl/rlisp */
! 152: { "sh", "# " }, /* shell */
! 153: { "sl", "% " }, /* psl */
! 154: { "spec", "-- " }, /* Ada */
! 155: { "tex", "% " }, /* tex */
! 156: { "y", " * " }, /* yacc */
! 157: { "ye", " * " }, /* yacc-efl */
! 158: { "yr", " * " }, /* yacc-ratfor */
! 159: };
! 160:
! 161: struct rcs_kw rcs_expkw[] = {
! 162: { "Author", RCS_KW_AUTHOR },
! 163: { "Date", RCS_KW_DATE },
! 164: { "Header", RCS_KW_HEADER },
! 165: { "Id", RCS_KW_ID },
! 166: { "Log", RCS_KW_LOG },
! 167: { "Name", RCS_KW_NAME },
! 168: { "RCSfile", RCS_KW_RCSFILE },
! 169: { "Revision", RCS_KW_REVISION },
! 170: { "Source", RCS_KW_SOURCE },
! 171: { "State", RCS_KW_STATE },
! 172: };
! 173:
! 174: #define NB_COMTYPES (sizeof(rcs_comments)/sizeof(rcs_comments[0]))
! 175:
! 176: static struct rcs_key {
! 177: char rk_str[16];
! 178: int rk_id;
! 179: int rk_val;
! 180: int rk_flags;
! 181: } rcs_keys[] = {
! 182: { "access", RCS_TOK_ACCESS, RCS_TOK_ID, RCS_VOPT },
! 183: { "author", RCS_TOK_AUTHOR, RCS_TOK_ID, 0 },
! 184: { "branch", RCS_TOK_BRANCH, RCS_TOK_NUM, RCS_VOPT },
! 185: { "branches", RCS_TOK_BRANCHES, RCS_TOK_NUM, RCS_VOPT },
! 186: { "comment", RCS_TOK_COMMENT, RCS_TOK_STRING, RCS_VOPT },
! 187: { "date", RCS_TOK_DATE, RCS_TOK_NUM, 0 },
! 188: { "desc", RCS_TOK_DESC, RCS_TOK_STRING, RCS_NOSCOL },
! 189: { "expand", RCS_TOK_EXPAND, RCS_TOK_STRING, RCS_VOPT },
! 190: { "head", RCS_TOK_HEAD, RCS_TOK_NUM, RCS_VOPT },
! 191: { "locks", RCS_TOK_LOCKS, RCS_TOK_ID, 0 },
! 192: { "log", RCS_TOK_LOG, RCS_TOK_STRING, RCS_NOSCOL },
! 193: { "next", RCS_TOK_NEXT, RCS_TOK_NUM, RCS_VOPT },
! 194: { "state", RCS_TOK_STATE, RCS_TOK_ID, RCS_VOPT },
! 195: { "strict", RCS_TOK_STRICT, 0, 0, },
! 196: { "symbols", RCS_TOK_SYMBOLS, 0, 0 },
! 197: { "text", RCS_TOK_TEXT, RCS_TOK_STRING, RCS_NOSCOL },
! 198: };
! 199:
! 200: #define RCS_NKEYS (sizeof(rcs_keys)/sizeof(rcs_keys[0]))
! 201:
! 202: static const char *rcs_errstrs[] = {
! 203: "No error",
! 204: "No such entry",
! 205: "Duplicate entry found",
! 206: "Bad RCS number",
! 207: "Invalid RCS symbol",
! 208: "Parse error",
! 209: };
! 210:
! 211: #define RCS_NERR (sizeof(rcs_errstrs)/sizeof(rcs_errstrs[0]))
! 212:
! 213: int rcs_errno = RCS_ERR_NOERR;
! 214: char *timezone_flag = NULL;
! 215:
! 216: int rcs_patch_lines(struct rcs_lines *, struct rcs_lines *);
! 217: static void rcs_parse_init(RCSFILE *);
! 218: static int rcs_parse_admin(RCSFILE *);
! 219: static int rcs_parse_delta(RCSFILE *);
! 220: static void rcs_parse_deltas(RCSFILE *, RCSNUM *);
! 221: static int rcs_parse_deltatext(RCSFILE *);
! 222: static void rcs_parse_deltatexts(RCSFILE *, RCSNUM *);
! 223: static void rcs_parse_desc(RCSFILE *, RCSNUM *);
! 224:
! 225: static int rcs_parse_access(RCSFILE *);
! 226: static int rcs_parse_symbols(RCSFILE *);
! 227: static int rcs_parse_locks(RCSFILE *);
! 228: static int rcs_parse_branches(RCSFILE *, struct rcs_delta *);
! 229: static void rcs_freedelta(struct rcs_delta *);
! 230: static void rcs_freepdata(struct rcs_pdata *);
! 231: static int rcs_gettok(RCSFILE *);
! 232: static int rcs_pushtok(RCSFILE *, const char *, int);
! 233: static void rcs_growbuf(RCSFILE *);
! 234: static void rcs_strprint(const u_char *, size_t, FILE *);
! 235:
! 236: static char* rcs_expand_keywords(char *, struct rcs_delta *, char *,
! 237: size_t, int);
! 238:
! 239: /*
! 240: * rcs_open()
! 241: *
! 242: * Open a file containing RCS-formatted information. The file's path is
! 243: * given in <path>, and the opening flags are given in <flags>, which is either
! 244: * RCS_READ, RCS_WRITE, or RCS_RDWR. If the open requests write access and
! 245: * the file does not exist, the RCS_CREATE flag must also be given, in which
! 246: * case it will be created with the mode specified in a third argument of
! 247: * type mode_t. If the file exists and RCS_CREATE is passed, the open will
! 248: * fail.
! 249: * Returns a handle to the opened file on success, or NULL on failure.
! 250: */
! 251: RCSFILE *
! 252: rcs_open(const char *path, int flags, ...)
! 253: {
! 254: int ret, mode;
! 255: mode_t fmode;
! 256: RCSFILE *rfp;
! 257: struct stat st;
! 258: va_list vap;
! 259: struct rcs_delta *rdp;
! 260: struct rcs_lock *lkr;
! 261:
! 262: fmode = S_IRUSR|S_IRGRP|S_IROTH;
! 263: flags &= 0xffff; /* ditch any internal flags */
! 264:
! 265: if (((ret = stat(path, &st)) == -1) && errno == ENOENT) {
! 266: if (flags & RCS_CREATE) {
! 267: va_start(vap, flags);
! 268: mode = va_arg(vap, int);
! 269: va_end(vap);
! 270: fmode = (mode_t)mode;
! 271: } else {
! 272: rcs_errno = RCS_ERR_NOENT;
! 273: return (NULL);
! 274: }
! 275: } else if (ret == 0 && (flags & RCS_CREATE)) {
! 276: warnx("RCS file `%s' exists", path);
! 277: return (NULL);
! 278: }
! 279:
! 280: rfp = xcalloc(1, sizeof(*rfp));
! 281:
! 282: rfp->rf_path = xstrdup(path);
! 283: rfp->rf_flags = flags | RCS_SLOCK | RCS_SYNCED;
! 284: rfp->rf_mode = fmode;
! 285:
! 286: TAILQ_INIT(&(rfp->rf_delta));
! 287: TAILQ_INIT(&(rfp->rf_access));
! 288: TAILQ_INIT(&(rfp->rf_symbols));
! 289: TAILQ_INIT(&(rfp->rf_locks));
! 290:
! 291: if (!(rfp->rf_flags & RCS_CREATE))
! 292: rcs_parse_init(rfp);
! 293:
! 294: /* fill in rd_locker */
! 295: TAILQ_FOREACH(lkr, &(rfp->rf_locks), rl_list) {
! 296: if ((rdp = rcs_findrev(rfp, lkr->rl_num)) == NULL) {
! 297: rcs_close(rfp);
! 298: return (NULL);
! 299: }
! 300:
! 301: rdp->rd_locker = xstrdup(lkr->rl_name);
! 302: }
! 303:
! 304: return (rfp);
! 305: }
! 306:
! 307: /*
! 308: * rcs_close()
! 309: *
! 310: * Close an RCS file handle.
! 311: */
! 312: void
! 313: rcs_close(RCSFILE *rfp)
! 314: {
! 315: struct rcs_delta *rdp;
! 316: struct rcs_access *rap;
! 317: struct rcs_lock *rlp;
! 318: struct rcs_sym *rsp;
! 319:
! 320: if ((rfp->rf_flags & RCS_WRITE) && !(rfp->rf_flags & RCS_SYNCED))
! 321: rcs_write(rfp);
! 322:
! 323: while (!TAILQ_EMPTY(&(rfp->rf_delta))) {
! 324: rdp = TAILQ_FIRST(&(rfp->rf_delta));
! 325: TAILQ_REMOVE(&(rfp->rf_delta), rdp, rd_list);
! 326: rcs_freedelta(rdp);
! 327: }
! 328:
! 329: while (!TAILQ_EMPTY(&(rfp->rf_access))) {
! 330: rap = TAILQ_FIRST(&(rfp->rf_access));
! 331: TAILQ_REMOVE(&(rfp->rf_access), rap, ra_list);
! 332: xfree(rap->ra_name);
! 333: xfree(rap);
! 334: }
! 335:
! 336: while (!TAILQ_EMPTY(&(rfp->rf_symbols))) {
! 337: rsp = TAILQ_FIRST(&(rfp->rf_symbols));
! 338: TAILQ_REMOVE(&(rfp->rf_symbols), rsp, rs_list);
! 339: rcsnum_free(rsp->rs_num);
! 340: xfree(rsp->rs_name);
! 341: xfree(rsp);
! 342: }
! 343:
! 344: while (!TAILQ_EMPTY(&(rfp->rf_locks))) {
! 345: rlp = TAILQ_FIRST(&(rfp->rf_locks));
! 346: TAILQ_REMOVE(&(rfp->rf_locks), rlp, rl_list);
! 347: rcsnum_free(rlp->rl_num);
! 348: xfree(rlp->rl_name);
! 349: xfree(rlp);
! 350: }
! 351:
! 352: if (rfp->rf_head != NULL)
! 353: rcsnum_free(rfp->rf_head);
! 354: if (rfp->rf_branch != NULL)
! 355: rcsnum_free(rfp->rf_branch);
! 356:
! 357: if (rfp->rf_path != NULL)
! 358: xfree(rfp->rf_path);
! 359: if (rfp->rf_comment != NULL)
! 360: xfree(rfp->rf_comment);
! 361: if (rfp->rf_expand != NULL)
! 362: xfree(rfp->rf_expand);
! 363: if (rfp->rf_desc != NULL)
! 364: xfree(rfp->rf_desc);
! 365: if (rfp->rf_pdata != NULL)
! 366: rcs_freepdata(rfp->rf_pdata);
! 367: xfree(rfp);
! 368: }
! 369:
! 370: /*
! 371: * rcs_write()
! 372: *
! 373: * Write the contents of the RCS file handle <rfp> to disk in the file whose
! 374: * path is in <rf_path>.
! 375: * Returns 0 on success, or -1 on failure.
! 376: */
! 377: int
! 378: rcs_write(RCSFILE *rfp)
! 379: {
! 380: FILE *fp;
! 381: char buf[1024], numbuf[64], fn[19] = "";
! 382: void *bp;
! 383: struct rcs_access *ap;
! 384: struct rcs_sym *symp;
! 385: struct rcs_branch *brp;
! 386: struct rcs_delta *rdp;
! 387: struct rcs_lock *lkp;
! 388: ssize_t nread, nwritten;
! 389: size_t len;
! 390: int fd, from_fd, to_fd;
! 391:
! 392: from_fd = to_fd = fd = -1;
! 393:
! 394: if (rfp->rf_flags & RCS_SYNCED)
! 395: return (0);
! 396:
! 397: /* Write operations need the whole file parsed */
! 398: rcs_parse_deltatexts(rfp, NULL);
! 399:
! 400: strlcpy(fn, "/tmp/rcs.XXXXXXXXXX", sizeof(fn));
! 401: if ((fd = mkstemp(fn)) == -1)
! 402: err(1, "mkstemp: `%s'", fn);
! 403:
! 404: if ((fp = fdopen(fd, "w+")) == NULL) {
! 405: fd = errno;
! 406: unlink(fn);
! 407: err(1, "fdopen: %s", fn);
! 408: }
! 409:
! 410: if (rfp->rf_head != NULL)
! 411: rcsnum_tostr(rfp->rf_head, numbuf, sizeof(numbuf));
! 412: else
! 413: numbuf[0] = '\0';
! 414:
! 415: fprintf(fp, "head\t%s;\n", numbuf);
! 416:
! 417: if (rfp->rf_branch != NULL) {
! 418: rcsnum_tostr(rfp->rf_branch, numbuf, sizeof(numbuf));
! 419: fprintf(fp, "branch\t%s;\n", numbuf);
! 420: }
! 421:
! 422: fputs("access", fp);
! 423: TAILQ_FOREACH(ap, &(rfp->rf_access), ra_list) {
! 424: fprintf(fp, "\n\t%s", ap->ra_name);
! 425: }
! 426: fputs(";\n", fp);
! 427:
! 428: fprintf(fp, "symbols");
! 429: TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
! 430: rcsnum_tostr(symp->rs_num, numbuf, sizeof(numbuf));
! 431: strlcpy(buf, symp->rs_name, sizeof(buf));
! 432: strlcat(buf, ":", sizeof(buf));
! 433: strlcat(buf, numbuf, sizeof(buf));
! 434: fprintf(fp, "\n\t%s", buf);
! 435: }
! 436: fprintf(fp, ";\n");
! 437:
! 438: fprintf(fp, "locks");
! 439: TAILQ_FOREACH(lkp, &(rfp->rf_locks), rl_list) {
! 440: rcsnum_tostr(lkp->rl_num, numbuf, sizeof(numbuf));
! 441: fprintf(fp, "\n\t%s:%s", lkp->rl_name, numbuf);
! 442: }
! 443:
! 444: fprintf(fp, ";");
! 445:
! 446: if (rfp->rf_flags & RCS_SLOCK)
! 447: fprintf(fp, " strict;");
! 448: fputc('\n', fp);
! 449:
! 450: fputs("comment\t@", fp);
! 451: if (rfp->rf_comment != NULL) {
! 452: rcs_strprint((const u_char *)rfp->rf_comment,
! 453: strlen(rfp->rf_comment), fp);
! 454: fputs("@;\n", fp);
! 455: } else
! 456: fputs("# @;\n", fp);
! 457:
! 458: if (rfp->rf_expand != NULL) {
! 459: fputs("expand @", fp);
! 460: rcs_strprint((const u_char *)rfp->rf_expand,
! 461: strlen(rfp->rf_expand), fp);
! 462: fputs("@;\n", fp);
! 463: }
! 464:
! 465: fputs("\n\n", fp);
! 466:
! 467: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
! 468: fprintf(fp, "%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
! 469: sizeof(numbuf)));
! 470: fprintf(fp, "date\t%d.%02d.%02d.%02d.%02d.%02d;",
! 471: rdp->rd_date.tm_year + 1900, rdp->rd_date.tm_mon + 1,
! 472: rdp->rd_date.tm_mday, rdp->rd_date.tm_hour,
! 473: rdp->rd_date.tm_min, rdp->rd_date.tm_sec);
! 474: fprintf(fp, "\tauthor %s;\tstate %s;\n",
! 475: rdp->rd_author, rdp->rd_state);
! 476: fputs("branches", fp);
! 477: TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
! 478: fprintf(fp, " %s", rcsnum_tostr(brp->rb_num, numbuf,
! 479: sizeof(numbuf)));
! 480: }
! 481: fputs(";\n", fp);
! 482: fprintf(fp, "next\t%s;\n\n", rcsnum_tostr(rdp->rd_next,
! 483: numbuf, sizeof(numbuf)));
! 484: }
! 485:
! 486: fputs("\ndesc\n@", fp);
! 487: if (rfp->rf_desc != NULL && (len = strlen(rfp->rf_desc)) > 0) {
! 488: rcs_strprint((const u_char *)rfp->rf_desc, len, fp);
! 489: if (rfp->rf_desc[len-1] != '\n')
! 490: fputc('\n', fp);
! 491: }
! 492: fputs("@\n", fp);
! 493:
! 494: /* deltatexts */
! 495: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
! 496: fprintf(fp, "\n\n%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
! 497: sizeof(numbuf)));
! 498: fputs("log\n@", fp);
! 499: if (rdp->rd_log != NULL) {
! 500: len = strlen(rdp->rd_log);
! 501: rcs_strprint((const u_char *)rdp->rd_log, len, fp);
! 502: if (rdp->rd_log[len-1] != '\n')
! 503: fputc('\n', fp);
! 504: }
! 505: fputs("@\ntext\n@", fp);
! 506: if (rdp->rd_text != NULL) {
! 507: rcs_strprint(rdp->rd_text, rdp->rd_tlen, fp);
! 508:
! 509: if (rdp->rd_tlen != 0) {
! 510: if (rdp->rd_text[rdp->rd_tlen-1] != '\n')
! 511: fputc('\n', fp);
! 512: }
! 513: }
! 514: fputs("@\n", fp);
! 515: }
! 516: fclose(fp);
! 517:
! 518: /*
! 519: * We try to use rename() to atomically put the new file in place.
! 520: * If that fails, we try a copy.
! 521: */
! 522: if (rename(fn, rfp->rf_path) == -1) {
! 523: if (errno == EXDEV) {
! 524: /* rename() not supported so we have to copy. */
! 525: if (chmod(rfp->rf_path, S_IWUSR) == -1 &&
! 526: !(rfp->rf_flags & RCS_CREATE)) {
! 527: errx(1, "chmod(%s, 0%o) failed",
! 528: rfp->rf_path, S_IWUSR);
! 529: }
! 530:
! 531: if ((from_fd = open(fn, O_RDONLY)) == -1) {
! 532: warn("failed to open `%s'",
! 533: rfp->rf_path);
! 534: return (-1);
! 535: }
! 536:
! 537: if ((to_fd = open(rfp->rf_path,
! 538: O_WRONLY|O_TRUNC|O_CREAT)) == -1) {
! 539: warn("failed to open `%s'", fn);
! 540: close(from_fd);
! 541: return (-1);
! 542: }
! 543:
! 544: bp = xmalloc(MAXBSIZE);
! 545: for (;;) {
! 546: if ((nread = read(from_fd, bp, MAXBSIZE)) == 0)
! 547: break;
! 548: if (nread == -1)
! 549: goto err;
! 550: nwritten = write(to_fd, bp, (size_t)nread);
! 551: if (nwritten == -1 || nwritten != nread)
! 552: goto err;
! 553: }
! 554:
! 555: if (nread < 0) {
! 556: err: if (unlink(rfp->rf_path) == -1)
! 557: warn("failed to unlink `%s'",
! 558: rfp->rf_path);
! 559: close(from_fd);
! 560: close(to_fd);
! 561: xfree(bp);
! 562: return (-1);
! 563: }
! 564:
! 565: close(from_fd);
! 566: close(to_fd);
! 567: xfree(bp);
! 568:
! 569: if (unlink(fn) == -1) {
! 570: warn("failed to unlink `%s'", fn);
! 571: return (-1);
! 572: }
! 573: } else {
! 574: warn("failed to access temp RCS output file");
! 575: return (-1);
! 576: }
! 577: }
! 578:
! 579: if (chmod(rfp->rf_path, rfp->rf_mode) == -1) {
! 580: warn("failed to chmod `%s'", rfp->rf_path);
! 581: return (-1);
! 582: }
! 583:
! 584: rfp->rf_flags |= RCS_SYNCED;
! 585:
! 586: return (0);
! 587: }
! 588:
! 589: /*
! 590: * rcs_head_get()
! 591: *
! 592: * Retrieve the revision number of the head revision for the RCS file <file>.
! 593: */
! 594: const RCSNUM *
! 595: rcs_head_get(RCSFILE *file)
! 596: {
! 597: return (file->rf_head);
! 598: }
! 599:
! 600: /*
! 601: * rcs_head_set()
! 602: *
! 603: * Set the revision number of the head revision for the RCS file <file> to
! 604: * <rev>, which must reference a valid revision within the file.
! 605: */
! 606: int
! 607: rcs_head_set(RCSFILE *file, RCSNUM *rev)
! 608: {
! 609: if (rcs_findrev(file, rev) == NULL)
! 610: return (-1);
! 611:
! 612: if (file->rf_head == NULL)
! 613: file->rf_head = rcsnum_alloc();
! 614:
! 615: rcsnum_cpy(rev, file->rf_head, 0);
! 616: file->rf_flags &= ~RCS_SYNCED;
! 617: return (0);
! 618: }
! 619:
! 620:
! 621: /*
! 622: * rcs_branch_get()
! 623: *
! 624: * Retrieve the default branch number for the RCS file <file>.
! 625: * Returns the number on success. If NULL is returned, then there is no
! 626: * default branch for this file.
! 627: */
! 628: const RCSNUM *
! 629: rcs_branch_get(RCSFILE *file)
! 630: {
! 631: return (file->rf_branch);
! 632: }
! 633:
! 634: /*
! 635: * rcs_branch_set()
! 636: *
! 637: * Set the default branch for the RCS file <file> to <bnum>.
! 638: * Returns 0 on success, -1 on failure.
! 639: */
! 640: int
! 641: rcs_branch_set(RCSFILE *file, const RCSNUM *bnum)
! 642: {
! 643: if (file->rf_branch == NULL)
! 644: file->rf_branch = rcsnum_alloc();
! 645:
! 646: rcsnum_cpy(bnum, file->rf_branch, 0);
! 647: file->rf_flags &= ~RCS_SYNCED;
! 648: return (0);
! 649: }
! 650:
! 651: /*
! 652: * rcs_access_add()
! 653: *
! 654: * Add the login name <login> to the access list for the RCS file <file>.
! 655: * Returns 0 on success, or -1 on failure.
! 656: */
! 657: int
! 658: rcs_access_add(RCSFILE *file, const char *login)
! 659: {
! 660: struct rcs_access *ap;
! 661:
! 662: /* first look for duplication */
! 663: TAILQ_FOREACH(ap, &(file->rf_access), ra_list) {
! 664: if (strcmp(ap->ra_name, login) == 0) {
! 665: rcs_errno = RCS_ERR_DUPENT;
! 666: return (-1);
! 667: }
! 668: }
! 669:
! 670: ap = xmalloc(sizeof(*ap));
! 671: ap->ra_name = xstrdup(login);
! 672: TAILQ_INSERT_TAIL(&(file->rf_access), ap, ra_list);
! 673:
! 674: /* not synced anymore */
! 675: file->rf_flags &= ~RCS_SYNCED;
! 676: return (0);
! 677: }
! 678:
! 679: /*
! 680: * rcs_access_remove()
! 681: *
! 682: * Remove an entry with login name <login> from the access list of the RCS
! 683: * file <file>.
! 684: * Returns 0 on success, or -1 on failure.
! 685: */
! 686: int
! 687: rcs_access_remove(RCSFILE *file, const char *login)
! 688: {
! 689: struct rcs_access *ap;
! 690:
! 691: TAILQ_FOREACH(ap, &(file->rf_access), ra_list)
! 692: if (strcmp(ap->ra_name, login) == 0)
! 693: break;
! 694:
! 695: if (ap == NULL) {
! 696: rcs_errno = RCS_ERR_NOENT;
! 697: return (-1);
! 698: }
! 699:
! 700: TAILQ_REMOVE(&(file->rf_access), ap, ra_list);
! 701: xfree(ap->ra_name);
! 702: xfree(ap);
! 703:
! 704: /* not synced anymore */
! 705: file->rf_flags &= ~RCS_SYNCED;
! 706: return (0);
! 707: }
! 708:
! 709: /*
! 710: * rcs_sym_add()
! 711: *
! 712: * Add a symbol to the list of symbols for the RCS file <rfp>. The new symbol
! 713: * is named <sym> and is bound to the RCS revision <snum>.
! 714: * Returns 0 on success, or -1 on failure.
! 715: */
! 716: int
! 717: rcs_sym_add(RCSFILE *rfp, const char *sym, RCSNUM *snum)
! 718: {
! 719: struct rcs_sym *symp;
! 720:
! 721: if (!rcs_sym_check(sym)) {
! 722: rcs_errno = RCS_ERR_BADSYM;
! 723: return (-1);
! 724: }
! 725:
! 726: /* first look for duplication */
! 727: TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
! 728: if (strcmp(symp->rs_name, sym) == 0) {
! 729: rcs_errno = RCS_ERR_DUPENT;
! 730: return (-1);
! 731: }
! 732: }
! 733:
! 734: symp = xmalloc(sizeof(*symp));
! 735: symp->rs_name = xstrdup(sym);
! 736: symp->rs_num = rcsnum_alloc();
! 737: rcsnum_cpy(snum, symp->rs_num, 0);
! 738:
! 739: TAILQ_INSERT_HEAD(&(rfp->rf_symbols), symp, rs_list);
! 740:
! 741: /* not synced anymore */
! 742: rfp->rf_flags &= ~RCS_SYNCED;
! 743: return (0);
! 744: }
! 745:
! 746: /*
! 747: * rcs_sym_remove()
! 748: *
! 749: * Remove the symbol with name <sym> from the symbol list for the RCS file
! 750: * <file>. If no such symbol is found, the call fails and returns with an
! 751: * error.
! 752: * Returns 0 on success, or -1 on failure.
! 753: */
! 754: int
! 755: rcs_sym_remove(RCSFILE *file, const char *sym)
! 756: {
! 757: struct rcs_sym *symp;
! 758:
! 759: if (!rcs_sym_check(sym)) {
! 760: rcs_errno = RCS_ERR_BADSYM;
! 761: return (-1);
! 762: }
! 763:
! 764: TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
! 765: if (strcmp(symp->rs_name, sym) == 0)
! 766: break;
! 767:
! 768: if (symp == NULL) {
! 769: rcs_errno = RCS_ERR_NOENT;
! 770: return (-1);
! 771: }
! 772:
! 773: TAILQ_REMOVE(&(file->rf_symbols), symp, rs_list);
! 774: xfree(symp->rs_name);
! 775: rcsnum_free(symp->rs_num);
! 776: xfree(symp);
! 777:
! 778: /* not synced anymore */
! 779: file->rf_flags &= ~RCS_SYNCED;
! 780: return (0);
! 781: }
! 782:
! 783: /*
! 784: * rcs_sym_getrev()
! 785: *
! 786: * Retrieve the RCS revision number associated with the symbol <sym> for the
! 787: * RCS file <file>. The returned value is a dynamically-allocated copy and
! 788: * should be freed by the caller once they are done with it.
! 789: * Returns the RCSNUM on success, or NULL on failure.
! 790: */
! 791: RCSNUM *
! 792: rcs_sym_getrev(RCSFILE *file, const char *sym)
! 793: {
! 794: RCSNUM *num;
! 795: struct rcs_sym *symp;
! 796:
! 797: if (!rcs_sym_check(sym)) {
! 798: rcs_errno = RCS_ERR_BADSYM;
! 799: return (NULL);
! 800: }
! 801:
! 802: num = NULL;
! 803: TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
! 804: if (strcmp(symp->rs_name, sym) == 0)
! 805: break;
! 806:
! 807: if (symp == NULL) {
! 808: rcs_errno = RCS_ERR_NOENT;
! 809: } else {
! 810: num = rcsnum_alloc();
! 811: rcsnum_cpy(symp->rs_num, num, 0);
! 812: }
! 813:
! 814: return (num);
! 815: }
! 816:
! 817: /*
! 818: * rcs_sym_check()
! 819: *
! 820: * Check the RCS symbol name <sym> for any unsupported characters.
! 821: * Returns 1 if the tag is correct, 0 if it isn't valid.
! 822: */
! 823: int
! 824: rcs_sym_check(const char *sym)
! 825: {
! 826: int ret;
! 827: const char *cp;
! 828:
! 829: ret = 1;
! 830: cp = sym;
! 831: if (!isalpha(*cp++))
! 832: return (0);
! 833:
! 834: for (; *cp != '\0'; cp++)
! 835: if (!isgraph(*cp) || (strchr(rcs_sym_invch, *cp) != NULL)) {
! 836: ret = 0;
! 837: break;
! 838: }
! 839:
! 840: return (ret);
! 841: }
! 842:
! 843: /*
! 844: * rcs_lock_getmode()
! 845: *
! 846: * Retrieve the locking mode of the RCS file <file>.
! 847: */
! 848: int
! 849: rcs_lock_getmode(RCSFILE *file)
! 850: {
! 851: return (file->rf_flags & RCS_SLOCK) ? RCS_LOCK_STRICT : RCS_LOCK_LOOSE;
! 852: }
! 853:
! 854: /*
! 855: * rcs_lock_setmode()
! 856: *
! 857: * Set the locking mode of the RCS file <file> to <mode>, which must either
! 858: * be RCS_LOCK_LOOSE or RCS_LOCK_STRICT.
! 859: * Returns the previous mode on success, or -1 on failure.
! 860: */
! 861: int
! 862: rcs_lock_setmode(RCSFILE *file, int mode)
! 863: {
! 864: int pmode;
! 865: pmode = rcs_lock_getmode(file);
! 866:
! 867: if (mode == RCS_LOCK_STRICT)
! 868: file->rf_flags |= RCS_SLOCK;
! 869: else if (mode == RCS_LOCK_LOOSE)
! 870: file->rf_flags &= ~RCS_SLOCK;
! 871: else
! 872: errx(1, "rcs_lock_setmode: invalid mode `%d'", mode);
! 873:
! 874: file->rf_flags &= ~RCS_SYNCED;
! 875: return (pmode);
! 876: }
! 877:
! 878: /*
! 879: * rcs_lock_add()
! 880: *
! 881: * Add an RCS lock for the user <user> on revision <rev>.
! 882: * Returns 0 on success, or -1 on failure.
! 883: */
! 884: int
! 885: rcs_lock_add(RCSFILE *file, const char *user, RCSNUM *rev)
! 886: {
! 887: struct rcs_lock *lkp;
! 888:
! 889: /* first look for duplication */
! 890: TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
! 891: if (strcmp(lkp->rl_name, user) == 0 &&
! 892: rcsnum_cmp(rev, lkp->rl_num, 0) == 0) {
! 893: rcs_errno = RCS_ERR_DUPENT;
! 894: return (-1);
! 895: }
! 896: }
! 897:
! 898: lkp = xmalloc(sizeof(*lkp));
! 899: lkp->rl_name = xstrdup(user);
! 900: lkp->rl_num = rcsnum_alloc();
! 901: rcsnum_cpy(rev, lkp->rl_num, 0);
! 902:
! 903: TAILQ_INSERT_TAIL(&(file->rf_locks), lkp, rl_list);
! 904:
! 905: /* not synced anymore */
! 906: file->rf_flags &= ~RCS_SYNCED;
! 907: return (0);
! 908: }
! 909:
! 910:
! 911: /*
! 912: * rcs_lock_remove()
! 913: *
! 914: * Remove the RCS lock on revision <rev>.
! 915: * Returns 0 on success, or -1 on failure.
! 916: */
! 917: int
! 918: rcs_lock_remove(RCSFILE *file, const char *user, RCSNUM *rev)
! 919: {
! 920: struct rcs_lock *lkp;
! 921:
! 922: TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
! 923: if (strcmp(lkp->rl_name, user) == 0 &&
! 924: rcsnum_cmp(lkp->rl_num, rev, 0) == 0)
! 925: break;
! 926: }
! 927:
! 928: if (lkp == NULL) {
! 929: rcs_errno = RCS_ERR_NOENT;
! 930: return (-1);
! 931: }
! 932:
! 933: TAILQ_REMOVE(&(file->rf_locks), lkp, rl_list);
! 934: rcsnum_free(lkp->rl_num);
! 935: xfree(lkp->rl_name);
! 936: xfree(lkp);
! 937:
! 938: /* not synced anymore */
! 939: file->rf_flags &= ~RCS_SYNCED;
! 940: return (0);
! 941: }
! 942:
! 943: /*
! 944: * rcs_desc_get()
! 945: *
! 946: * Retrieve the description for the RCS file <file>.
! 947: */
! 948: const char *
! 949: rcs_desc_get(RCSFILE *file)
! 950: {
! 951: return (file->rf_desc);
! 952: }
! 953:
! 954: /*
! 955: * rcs_desc_set()
! 956: *
! 957: * Set the description for the RCS file <file>.
! 958: */
! 959: void
! 960: rcs_desc_set(RCSFILE *file, const char *desc)
! 961: {
! 962: char *tmp;
! 963:
! 964: tmp = xstrdup(desc);
! 965: if (file->rf_desc != NULL)
! 966: xfree(file->rf_desc);
! 967: file->rf_desc = tmp;
! 968: file->rf_flags &= ~RCS_SYNCED;
! 969: }
! 970:
! 971: /*
! 972: * rcs_comment_lookup()
! 973: *
! 974: * Lookup the assumed comment leader based on a file's suffix.
! 975: * Returns a pointer to the string on success, or NULL on failure.
! 976: */
! 977: const char *
! 978: rcs_comment_lookup(const char *filename)
! 979: {
! 980: int i;
! 981: const char *sp;
! 982:
! 983: if ((sp = strrchr(filename, '.')) == NULL) {
! 984: rcs_errno = RCS_ERR_NOENT;
! 985: return (NULL);
! 986: }
! 987: sp++;
! 988:
! 989: for (i = 0; i < (int)NB_COMTYPES; i++)
! 990: if (strcmp(rcs_comments[i].rc_suffix, sp) == 0)
! 991: return (rcs_comments[i].rc_cstr);
! 992: return (NULL);
! 993: }
! 994:
! 995: /*
! 996: * rcs_comment_get()
! 997: *
! 998: * Retrieve the comment leader for the RCS file <file>.
! 999: */
! 1000: const char *
! 1001: rcs_comment_get(RCSFILE *file)
! 1002: {
! 1003: return (file->rf_comment);
! 1004: }
! 1005:
! 1006: /*
! 1007: * rcs_comment_set()
! 1008: *
! 1009: * Set the comment leader for the RCS file <file>.
! 1010: */
! 1011: void
! 1012: rcs_comment_set(RCSFILE *file, const char *comment)
! 1013: {
! 1014: char *tmp;
! 1015:
! 1016: tmp = xstrdup(comment);
! 1017: if (file->rf_comment != NULL)
! 1018: xfree(file->rf_comment);
! 1019: file->rf_comment = tmp;
! 1020: file->rf_flags &= ~RCS_SYNCED;
! 1021: }
! 1022:
! 1023: /*
! 1024: * rcs_tag_resolve()
! 1025: *
! 1026: * Retrieve the revision number corresponding to the tag <tag> for the RCS
! 1027: * file <file>.
! 1028: */
! 1029: RCSNUM *
! 1030: rcs_tag_resolve(RCSFILE *file, const char *tag)
! 1031: {
! 1032: RCSNUM *num;
! 1033:
! 1034: if ((num = rcsnum_parse(tag)) == NULL) {
! 1035: num = rcs_sym_getrev(file, tag);
! 1036: }
! 1037:
! 1038: return (num);
! 1039: }
! 1040:
! 1041: int
! 1042: rcs_patch_lines(struct rcs_lines *dlines, struct rcs_lines *plines)
! 1043: {
! 1044: char op, *ep;
! 1045: struct rcs_line *lp, *dlp, *ndlp;
! 1046: int i, lineno, nbln;
! 1047:
! 1048: dlp = TAILQ_FIRST(&(dlines->l_lines));
! 1049: lp = TAILQ_FIRST(&(plines->l_lines));
! 1050:
! 1051: /* skip first bogus line */
! 1052: for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
! 1053: lp = TAILQ_NEXT(lp, l_list)) {
! 1054: op = *(lp->l_line);
! 1055: lineno = (int)strtol((lp->l_line + 1), &ep, 10);
! 1056: if (lineno > dlines->l_nblines || lineno < 0 ||
! 1057: *ep != ' ')
! 1058: errx(1, "invalid line specification in RCS patch");
! 1059: ep++;
! 1060: nbln = (int)strtol(ep, &ep, 10);
! 1061: if (nbln < 0 || *ep != '\0')
! 1062: errx(1,
! 1063: "invalid line number specification in RCS patch");
! 1064:
! 1065: /* find the appropriate line */
! 1066: for (;;) {
! 1067: if (dlp == NULL)
! 1068: break;
! 1069: if (dlp->l_lineno == lineno)
! 1070: break;
! 1071: if (dlp->l_lineno > lineno) {
! 1072: dlp = TAILQ_PREV(dlp, rcs_tqh, l_list);
! 1073: } else if (dlp->l_lineno < lineno) {
! 1074: if (((ndlp = TAILQ_NEXT(dlp, l_list)) == NULL) ||
! 1075: ndlp->l_lineno > lineno)
! 1076: break;
! 1077: dlp = ndlp;
! 1078: }
! 1079: }
! 1080: if (dlp == NULL)
! 1081: errx(1, "can't find referenced line in RCS patch");
! 1082:
! 1083: if (op == 'd') {
! 1084: for (i = 0; (i < nbln) && (dlp != NULL); i++) {
! 1085: ndlp = TAILQ_NEXT(dlp, l_list);
! 1086: TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list);
! 1087: dlp = ndlp;
! 1088: /* last line is gone - reset dlp */
! 1089: if (dlp == NULL) {
! 1090: ndlp = TAILQ_LAST(&(dlines->l_lines),
! 1091: rcs_tqh);
! 1092: dlp = ndlp;
! 1093: }
! 1094: }
! 1095: } else if (op == 'a') {
! 1096: for (i = 0; i < nbln; i++) {
! 1097: ndlp = lp;
! 1098: lp = TAILQ_NEXT(lp, l_list);
! 1099: if (lp == NULL)
! 1100: errx(1, "truncated RCS patch");
! 1101: TAILQ_REMOVE(&(plines->l_lines), lp, l_list);
! 1102: TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp,
! 1103: lp, l_list);
! 1104: dlp = lp;
! 1105:
! 1106: /* we don't want lookup to block on those */
! 1107: lp->l_lineno = lineno;
! 1108:
! 1109: lp = ndlp;
! 1110: }
! 1111: } else
! 1112: errx(1, "unknown RCS patch operation `%c'", op);
! 1113:
! 1114: /* last line of the patch, done */
! 1115: if (lp->l_lineno == plines->l_nblines)
! 1116: break;
! 1117: }
! 1118:
! 1119: /* once we're done patching, rebuild the line numbers */
! 1120: lineno = 0;
! 1121: TAILQ_FOREACH(lp, &(dlines->l_lines), l_list)
! 1122: lp->l_lineno = lineno++;
! 1123: dlines->l_nblines = lineno - 1;
! 1124:
! 1125: return (0);
! 1126: }
! 1127:
! 1128: /*
! 1129: * rcs_getrev()
! 1130: *
! 1131: * Get the whole contents of revision <rev> from the RCSFILE <rfp>. The
! 1132: * returned buffer is dynamically allocated and should be released using
! 1133: * rcs_buf_free() once the caller is done using it.
! 1134: */
! 1135: BUF*
! 1136: rcs_getrev(RCSFILE *rfp, RCSNUM *frev)
! 1137: {
! 1138: u_int i, numlen;
! 1139: int isbranch, lookonbranch;
! 1140: size_t len;
! 1141: void *bp;
! 1142: RCSNUM *crev, *rev, *brev;
! 1143: BUF *rbuf;
! 1144: struct rcs_delta *rdp = NULL;
! 1145: struct rcs_branch *rb;
! 1146:
! 1147: if (rfp->rf_head == NULL)
! 1148: return (NULL);
! 1149:
! 1150: if (frev == RCS_HEAD_REV)
! 1151: rev = rfp->rf_head;
! 1152: else
! 1153: rev = frev;
! 1154:
! 1155: /* XXX rcsnum_cmp() */
! 1156: for (i = 0; i < rfp->rf_head->rn_len; i++) {
! 1157: if (rfp->rf_head->rn_id[i] < rev->rn_id[i]) {
! 1158: rcs_errno = RCS_ERR_NOENT;
! 1159: return (NULL);
! 1160: }
! 1161: }
! 1162:
! 1163: /* No matter what, we're going to need up the the description parsed */
! 1164: rcs_parse_desc(rfp, NULL);
! 1165:
! 1166: rdp = rcs_findrev(rfp, rfp->rf_head);
! 1167: if (rdp == NULL) {
! 1168: warnx("failed to get RCS HEAD revision");
! 1169: return (NULL);
! 1170: }
! 1171:
! 1172: if (rdp->rd_tlen == 0)
! 1173: rcs_parse_deltatexts(rfp, rfp->rf_head);
! 1174:
! 1175: len = rdp->rd_tlen;
! 1176: if (len == 0) {
! 1177: rbuf = rcs_buf_alloc(1, 0);
! 1178: rcs_buf_empty(rbuf);
! 1179: return (rbuf);
! 1180: }
! 1181:
! 1182: rbuf = rcs_buf_alloc(len, BUF_AUTOEXT);
! 1183: rcs_buf_append(rbuf, rdp->rd_text, len);
! 1184:
! 1185: isbranch = 0;
! 1186: brev = NULL;
! 1187:
! 1188: /*
! 1189: * If a branch was passed, get the latest revision on it.
! 1190: */
! 1191: if (RCSNUM_ISBRANCH(rev)) {
! 1192: brev = rev;
! 1193: rdp = rcs_findrev(rfp, rev);
! 1194: if (rdp == NULL)
! 1195: return (NULL);
! 1196:
! 1197: rev = rdp->rd_num;
! 1198: } else {
! 1199: if (RCSNUM_ISBRANCHREV(rev)) {
! 1200: brev = rcsnum_revtobr(rev);
! 1201: isbranch = 1;
! 1202: }
! 1203: }
! 1204:
! 1205: lookonbranch = 0;
! 1206: crev = NULL;
! 1207:
! 1208: /* Apply patches backwards to get the right version.
! 1209: */
! 1210: do {
! 1211: if (rcsnum_cmp(rfp->rf_head, rev, 0) == 0)
! 1212: break;
! 1213:
! 1214: if (isbranch == 1 && rdp->rd_num->rn_len < rev->rn_len &&
! 1215: !TAILQ_EMPTY(&(rdp->rd_branches)))
! 1216: lookonbranch = 1;
! 1217:
! 1218: if (isbranch && lookonbranch == 1) {
! 1219: lookonbranch = 0;
! 1220: TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) {
! 1221: /* XXX rcsnum_cmp() is totally broken for
! 1222: * this purpose.
! 1223: */
! 1224: numlen = MIN(brev->rn_len, rb->rb_num->rn_len);
! 1225: for (i = 0; i < numlen; i++) {
! 1226: if (rb->rb_num->rn_id[i] !=
! 1227: brev->rn_id[i])
! 1228: break;
! 1229: }
! 1230:
! 1231: if (i == numlen) {
! 1232: crev = rb->rb_num;
! 1233: break;
! 1234: }
! 1235: }
! 1236: } else {
! 1237: crev = rdp->rd_next;
! 1238: }
! 1239:
! 1240: rdp = rcs_findrev(rfp, crev);
! 1241: if (rdp == NULL) {
! 1242: rcs_buf_free(rbuf);
! 1243: return (NULL);
! 1244: }
! 1245:
! 1246: rcs_buf_putc(rbuf, '\0');
! 1247:
! 1248: /* check if we have parsed this rev's deltatext */
! 1249: if (rdp->rd_tlen == 0)
! 1250: rcs_parse_deltatexts(rfp, rdp->rd_num);
! 1251:
! 1252: bp = rcs_buf_release(rbuf);
! 1253: rbuf = rcs_patchfile((char *)bp, (char *)rdp->rd_text,
! 1254: rcs_patch_lines);
! 1255: xfree(bp);
! 1256:
! 1257: if (rbuf == NULL)
! 1258: break;
! 1259: } while (rcsnum_cmp(crev, rev, 0) != 0);
! 1260:
! 1261: if (rcs_buf_getc(rbuf, rcs_buf_len(rbuf)-1) != '\n' &&
! 1262: rbuf != NULL)
! 1263: rcs_buf_putc(rbuf, '\n');
! 1264:
! 1265: return (rbuf);
! 1266: }
! 1267:
! 1268: /*
! 1269: * rcs_rev_add()
! 1270: *
! 1271: * Add a revision to the RCS file <rf>. The new revision's number can be
! 1272: * specified in <rev> (which can also be RCS_HEAD_REV, in which case the
! 1273: * new revision will have a number equal to the previous head revision plus
! 1274: * one). The <msg> argument specifies the log message for that revision, and
! 1275: * <date> specifies the revision's date (a value of -1 is
! 1276: * equivalent to using the current time).
! 1277: * If <username> is NULL, set the author for this revision to the current user.
! 1278: * Otherwise, set it to <username>.
! 1279: * Returns 0 on success, or -1 on failure.
! 1280: */
! 1281: int
! 1282: rcs_rev_add(RCSFILE *rf, RCSNUM *rev, const char *msg, time_t date,
! 1283: const char *username)
! 1284: {
! 1285: time_t now;
! 1286: struct passwd *pw;
! 1287: struct rcs_delta *ordp, *rdp;
! 1288:
! 1289: if (rev == RCS_HEAD_REV) {
! 1290: if (rf->rf_flags & RCS_CREATE) {
! 1291: if ((rev = rcsnum_parse(RCS_HEAD_INIT)) == NULL)
! 1292: return (-1);
! 1293: rf->rf_head = rcsnum_alloc();
! 1294: rcsnum_cpy(rev, rf->rf_head, 0);
! 1295: } else {
! 1296: rev = rcsnum_inc(rf->rf_head);
! 1297: }
! 1298: } else {
! 1299: if ((rdp = rcs_findrev(rf, rev)) != NULL) {
! 1300: rcs_errno = RCS_ERR_DUPENT;
! 1301: return (-1);
! 1302: }
! 1303: }
! 1304:
! 1305: if ((pw = getpwuid(getuid())) == NULL)
! 1306: errx(1, "getpwuid failed");
! 1307:
! 1308: rdp = xcalloc(1, sizeof(*rdp));
! 1309:
! 1310: TAILQ_INIT(&(rdp->rd_branches));
! 1311:
! 1312: rdp->rd_num = rcsnum_alloc();
! 1313: rcsnum_cpy(rev, rdp->rd_num, 0);
! 1314:
! 1315: rdp->rd_next = rcsnum_alloc();
! 1316:
! 1317: if (!(rf->rf_flags & RCS_CREATE)) {
! 1318: /* next should point to the previous HEAD */
! 1319: ordp = TAILQ_FIRST(&(rf->rf_delta));
! 1320: rcsnum_cpy(ordp->rd_num, rdp->rd_next, 0);
! 1321: }
! 1322:
! 1323:
! 1324: if (username == NULL)
! 1325: username = pw->pw_name;
! 1326:
! 1327: rdp->rd_author = xstrdup(username);
! 1328: rdp->rd_state = xstrdup(RCS_STATE_EXP);
! 1329: rdp->rd_log = xstrdup(msg);
! 1330:
! 1331: if (date != (time_t)(-1))
! 1332: now = date;
! 1333: else
! 1334: time(&now);
! 1335: gmtime_r(&now, &(rdp->rd_date));
! 1336:
! 1337: TAILQ_INSERT_HEAD(&(rf->rf_delta), rdp, rd_list);
! 1338: rf->rf_ndelta++;
! 1339:
! 1340: /* not synced anymore */
! 1341: rf->rf_flags &= ~RCS_SYNCED;
! 1342:
! 1343: return (0);
! 1344: }
! 1345:
! 1346: /*
! 1347: * rcs_rev_remove()
! 1348: *
! 1349: * Remove the revision whose number is <rev> from the RCS file <rf>.
! 1350: */
! 1351: int
! 1352: rcs_rev_remove(RCSFILE *rf, RCSNUM *rev)
! 1353: {
! 1354: size_t len;
! 1355: char *tmpdir;
! 1356: char *newdeltatext, path_tmp1[MAXPATHLEN], path_tmp2[MAXPATHLEN];
! 1357: struct rcs_delta *rdp, *prevrdp, *nextrdp;
! 1358: BUF *nextbuf, *prevbuf, *newdiff;
! 1359:
! 1360: tmpdir = rcs_tmpdir;
! 1361:
! 1362: if (rev == RCS_HEAD_REV)
! 1363: rev = rf->rf_head;
! 1364:
! 1365: /* do we actually have that revision? */
! 1366: if ((rdp = rcs_findrev(rf, rev)) == NULL) {
! 1367: rcs_errno = RCS_ERR_NOENT;
! 1368: return (-1);
! 1369: }
! 1370:
! 1371: /*
! 1372: * This is confusing, the previous delta is next in the TAILQ list.
! 1373: * the next delta is the previous one in the TAILQ list.
! 1374: *
! 1375: * When the HEAD revision got specified, nextrdp will be NULL.
! 1376: * When the first revision got specified, prevrdp will be NULL.
! 1377: */
! 1378: prevrdp = (struct rcs_delta *)TAILQ_NEXT(rdp, rd_list);
! 1379: nextrdp = (struct rcs_delta *)TAILQ_PREV(rdp, rcs_tqh, rd_list);
! 1380:
! 1381: newdeltatext = NULL;
! 1382: prevbuf = nextbuf = NULL;
! 1383:
! 1384: if (prevrdp != NULL) {
! 1385: if ((prevbuf = rcs_getrev(rf, prevrdp->rd_num)) == NULL)
! 1386: errx(1, "error getting revision");
! 1387: }
! 1388:
! 1389: if (prevrdp != NULL && nextrdp != NULL) {
! 1390: if ((nextbuf = rcs_getrev(rf, nextrdp->rd_num)) == NULL)
! 1391: errx(1, "error getting revision");
! 1392:
! 1393: newdiff = rcs_buf_alloc(64, BUF_AUTOEXT);
! 1394:
! 1395: /* calculate new diff */
! 1396: len = strlcpy(path_tmp1, tmpdir, sizeof(path_tmp1));
! 1397: if (len >= sizeof(path_tmp1))
! 1398: errx(1, "path truncation in rcs_rev_remove");
! 1399:
! 1400: len = strlcat(path_tmp1, "/diff1.XXXXXXXXXX",
! 1401: sizeof(path_tmp1));
! 1402: if (len >= sizeof(path_tmp1))
! 1403: errx(1, "path truncation in rcs_rev_remove");
! 1404:
! 1405: rcs_buf_write_stmp(nextbuf, path_tmp1, 0600);
! 1406: rcs_buf_free(nextbuf);
! 1407:
! 1408: len = strlcpy(path_tmp2, tmpdir, sizeof(path_tmp2));
! 1409: if (len >= sizeof(path_tmp2))
! 1410: errx(1, "path truncation in rcs_rev_remove");
! 1411:
! 1412: len = strlcat(path_tmp2, "/diff2.XXXXXXXXXX",
! 1413: sizeof(path_tmp2));
! 1414: if (len >= sizeof(path_tmp2))
! 1415: errx(1, "path truncation in rcs_rev_remove");
! 1416:
! 1417: rcs_buf_write_stmp(prevbuf, path_tmp2, 0600);
! 1418: rcs_buf_free(prevbuf);
! 1419:
! 1420: diff_format = D_RCSDIFF;
! 1421: rcs_diffreg(path_tmp1, path_tmp2, newdiff);
! 1422:
! 1423: newdeltatext = rcs_buf_release(newdiff);
! 1424: } else if (nextrdp == NULL && prevrdp != NULL) {
! 1425: newdeltatext = rcs_buf_release(prevbuf);
! 1426: }
! 1427:
! 1428: if (newdeltatext != NULL) {
! 1429: if (rcs_deltatext_set(rf, prevrdp->rd_num, newdeltatext) < 0)
! 1430: errx(1, "error setting new deltatext");
! 1431: }
! 1432:
! 1433: TAILQ_REMOVE(&(rf->rf_delta), rdp, rd_list);
! 1434:
! 1435: /* update pointers */
! 1436: if (prevrdp != NULL && nextrdp != NULL) {
! 1437: rcsnum_cpy(prevrdp->rd_num, nextrdp->rd_next, 0);
! 1438: } else if (prevrdp != NULL) {
! 1439: if (rcs_head_set(rf, prevrdp->rd_num) < 0)
! 1440: errx(1, "rcs_head_set failed");
! 1441: } else if (nextrdp != NULL) {
! 1442: rcsnum_free(nextrdp->rd_next);
! 1443: nextrdp->rd_next = rcsnum_alloc();
! 1444: } else {
! 1445: rcsnum_free(rf->rf_head);
! 1446: rf->rf_head = NULL;
! 1447: }
! 1448:
! 1449: rf->rf_ndelta--;
! 1450: rf->rf_flags &= ~RCS_SYNCED;
! 1451:
! 1452: rcs_freedelta(rdp);
! 1453:
! 1454: if (newdeltatext != NULL)
! 1455: xfree(newdeltatext);
! 1456:
! 1457: return (0);
! 1458: }
! 1459:
! 1460: /*
! 1461: * rcs_findrev()
! 1462: *
! 1463: * Find a specific revision's delta entry in the tree of the RCS file <rfp>.
! 1464: * The revision number is given in <rev>.
! 1465: *
! 1466: * If the given revision is a branch number, we translate it into the latest
! 1467: * revision on the branch.
! 1468: *
! 1469: * Returns a pointer to the delta on success, or NULL on failure.
! 1470: */
! 1471: struct rcs_delta *
! 1472: rcs_findrev(RCSFILE *rfp, RCSNUM *rev)
! 1473: {
! 1474: u_int cmplen;
! 1475: struct rcs_delta *rdp;
! 1476: RCSNUM *brev, *frev;
! 1477:
! 1478: /*
! 1479: * We need to do more parsing if the last revision in the linked list
! 1480: * is greater than the requested revision.
! 1481: */
! 1482: rdp = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
! 1483: if (rdp == NULL ||
! 1484: rcsnum_cmp(rdp->rd_num, rev, 0) == -1) {
! 1485: rcs_parse_deltas(rfp, rev);
! 1486: }
! 1487:
! 1488: /*
! 1489: * Translate a branch into the latest revision on the branch itself.
! 1490: */
! 1491: if (RCSNUM_ISBRANCH(rev)) {
! 1492: brev = rcsnum_brtorev(rev);
! 1493: frev = brev;
! 1494: for (;;) {
! 1495: rdp = rcs_findrev(rfp, frev);
! 1496: if (rdp == NULL)
! 1497: return (NULL);
! 1498:
! 1499: if (rdp->rd_next->rn_len == 0)
! 1500: break;
! 1501:
! 1502: frev = rdp->rd_next;
! 1503: }
! 1504:
! 1505: rcsnum_free(brev);
! 1506: return (rdp);
! 1507: }
! 1508:
! 1509: cmplen = rev->rn_len;
! 1510:
! 1511: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
! 1512: if (rcsnum_cmp(rdp->rd_num, rev, cmplen) == 0)
! 1513: return (rdp);
! 1514: }
! 1515:
! 1516: return (NULL);
! 1517: }
! 1518:
! 1519: /*
! 1520: * rcs_kwexp_set()
! 1521: *
! 1522: * Set the keyword expansion mode to use on the RCS file <file> to <mode>.
! 1523: */
! 1524: void
! 1525: rcs_kwexp_set(RCSFILE *file, int mode)
! 1526: {
! 1527: int i;
! 1528: char *tmp, buf[8] = "";
! 1529:
! 1530: if (RCS_KWEXP_INVAL(mode))
! 1531: return;
! 1532:
! 1533: i = 0;
! 1534: if (mode == RCS_KWEXP_NONE)
! 1535: buf[0] = 'b';
! 1536: else if (mode == RCS_KWEXP_OLD)
! 1537: buf[0] = 'o';
! 1538: else {
! 1539: if (mode & RCS_KWEXP_NAME)
! 1540: buf[i++] = 'k';
! 1541: if (mode & RCS_KWEXP_VAL)
! 1542: buf[i++] = 'v';
! 1543: if (mode & RCS_KWEXP_LKR)
! 1544: buf[i++] = 'l';
! 1545: }
! 1546:
! 1547: tmp = xstrdup(buf);
! 1548: if (file->rf_expand != NULL)
! 1549: xfree(file->rf_expand);
! 1550: file->rf_expand = tmp;
! 1551: /* not synced anymore */
! 1552: file->rf_flags &= ~RCS_SYNCED;
! 1553: }
! 1554:
! 1555: /*
! 1556: * rcs_kwexp_get()
! 1557: *
! 1558: * Retrieve the keyword expansion mode to be used for the RCS file <file>.
! 1559: */
! 1560: int
! 1561: rcs_kwexp_get(RCSFILE *file)
! 1562: {
! 1563: return rcs_kflag_get(file->rf_expand);
! 1564: }
! 1565:
! 1566: /*
! 1567: * rcs_kflag_get()
! 1568: *
! 1569: * Get the keyword expansion mode from a set of character flags given in
! 1570: * <flags> and return the appropriate flag mask. In case of an error, the
! 1571: * returned mask will have the RCS_KWEXP_ERR bit set to 1.
! 1572: */
! 1573: int
! 1574: rcs_kflag_get(const char *flags)
! 1575: {
! 1576: int fl;
! 1577: size_t len;
! 1578: const char *fp;
! 1579:
! 1580: fl = 0;
! 1581: len = strlen(flags);
! 1582:
! 1583: for (fp = flags; *fp != '\0'; fp++) {
! 1584: if (*fp == 'k')
! 1585: fl |= RCS_KWEXP_NAME;
! 1586: else if (*fp == 'v')
! 1587: fl |= RCS_KWEXP_VAL;
! 1588: else if (*fp == 'l')
! 1589: fl |= RCS_KWEXP_LKR;
! 1590: else if (*fp == 'o') {
! 1591: if (len != 1)
! 1592: fl |= RCS_KWEXP_ERR;
! 1593: fl |= RCS_KWEXP_OLD;
! 1594: } else if (*fp == 'b') {
! 1595: if (len != 1)
! 1596: fl |= RCS_KWEXP_ERR;
! 1597: } else /* unknown letter */
! 1598: fl |= RCS_KWEXP_ERR;
! 1599: }
! 1600:
! 1601: return (fl);
! 1602: }
! 1603:
! 1604: /*
! 1605: * rcs_errstr()
! 1606: *
! 1607: * Get the error string matching the RCS error code <code>.
! 1608: */
! 1609: const char *
! 1610: rcs_errstr(int code)
! 1611: {
! 1612: const char *esp;
! 1613:
! 1614: if (code < 0 || (code >= (int)RCS_NERR && code != RCS_ERR_ERRNO))
! 1615: esp = NULL;
! 1616: else if (code == RCS_ERR_ERRNO)
! 1617: esp = strerror(errno);
! 1618: else
! 1619: esp = rcs_errstrs[code];
! 1620: return (esp);
! 1621: }
! 1622:
! 1623: /* rcs_parse_deltas()
! 1624: *
! 1625: * Parse deltas. If <rev> is not NULL, parse only as far as that
! 1626: * revision. If <rev> is NULL, parse all deltas.
! 1627: */
! 1628: static void
! 1629: rcs_parse_deltas(RCSFILE *rfp, RCSNUM *rev)
! 1630: {
! 1631: int ret;
! 1632: struct rcs_delta *enddelta;
! 1633:
! 1634: if ((rfp->rf_flags & PARSED_DELTAS) || (rfp->rf_flags & RCS_CREATE))
! 1635: return;
! 1636:
! 1637: for (;;) {
! 1638: ret = rcs_parse_delta(rfp);
! 1639: if (rev != NULL) {
! 1640: enddelta = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
! 1641: if (rcsnum_cmp(enddelta->rd_num, rev, 0) == 0)
! 1642: break;
! 1643: }
! 1644: if (ret == 0) {
! 1645: rfp->rf_flags |= PARSED_DELTAS;
! 1646: break;
! 1647: }
! 1648: else if (ret == -1)
! 1649: errx(1, "error parsing deltas");
! 1650: }
! 1651: }
! 1652:
! 1653: /* rcs_parse_deltatexts()
! 1654: *
! 1655: * Parse deltatexts. If <rev> is not NULL, parse only as far as that
! 1656: * revision. If <rev> is NULL, parse everything.
! 1657: */
! 1658: static void
! 1659: rcs_parse_deltatexts(RCSFILE *rfp, RCSNUM *rev)
! 1660: {
! 1661: int ret;
! 1662: struct rcs_delta *rdp;
! 1663:
! 1664: if ((rfp->rf_flags & PARSED_DELTATEXTS) ||
! 1665: (rfp->rf_flags & RCS_CREATE))
! 1666: return;
! 1667:
! 1668: if (!(rfp->rf_flags & PARSED_DESC))
! 1669: rcs_parse_desc(rfp, rev);
! 1670: for (;;) {
! 1671: if (rev != NULL) {
! 1672: rdp = rcs_findrev(rfp, rev);
! 1673: if (rdp->rd_text != NULL)
! 1674: break;
! 1675: else
! 1676: ret = rcs_parse_deltatext(rfp);
! 1677: } else
! 1678: ret = rcs_parse_deltatext(rfp);
! 1679: if (ret == 0) {
! 1680: rfp->rf_flags |= PARSED_DELTATEXTS;
! 1681: break;
! 1682: }
! 1683: else if (ret == -1)
! 1684: errx(1, "problem parsing deltatexts");
! 1685: }
! 1686: }
! 1687:
! 1688: /* rcs_parse_desc()
! 1689: *
! 1690: * Parse RCS description.
! 1691: */
! 1692: static void
! 1693: rcs_parse_desc(RCSFILE *rfp, RCSNUM *rev)
! 1694: {
! 1695: int ret = 0;
! 1696:
! 1697: if ((rfp->rf_flags & PARSED_DESC) || (rfp->rf_flags & RCS_CREATE))
! 1698: return;
! 1699: if (!(rfp->rf_flags & PARSED_DELTAS))
! 1700: rcs_parse_deltas(rfp, rev);
! 1701: /* do parsing */
! 1702: ret = rcs_gettok(rfp);
! 1703: if (ret != RCS_TOK_DESC)
! 1704: errx(1, "token `%s' found where RCS desc expected",
! 1705: RCS_TOKSTR(rfp));
! 1706:
! 1707: ret = rcs_gettok(rfp);
! 1708: if (ret != RCS_TOK_STRING)
! 1709: errx(1, "token `%s' found where RCS desc expected",
! 1710: RCS_TOKSTR(rfp));
! 1711:
! 1712: rfp->rf_desc = xstrdup(RCS_TOKSTR(rfp));
! 1713: rfp->rf_flags |= PARSED_DESC;
! 1714: }
! 1715:
! 1716: /*
! 1717: * rcs_parse_init()
! 1718: *
! 1719: * Initial parsing of file <path>, which are in the RCS format.
! 1720: * Just does admin section.
! 1721: */
! 1722: static void
! 1723: rcs_parse_init(RCSFILE *rfp)
! 1724: {
! 1725: struct rcs_pdata *pdp;
! 1726:
! 1727: if (rfp->rf_flags & RCS_PARSED)
! 1728: return;
! 1729:
! 1730: pdp = xcalloc(1, sizeof(*pdp));
! 1731:
! 1732: pdp->rp_lines = 0;
! 1733: pdp->rp_pttype = RCS_TOK_ERR;
! 1734:
! 1735: if ((pdp->rp_file = fopen(rfp->rf_path, "r")) == NULL)
! 1736: err(1, "fopen: `%s'", rfp->rf_path);
! 1737:
! 1738: pdp->rp_buf = xmalloc((size_t)RCS_BUFSIZE);
! 1739: pdp->rp_blen = RCS_BUFSIZE;
! 1740: pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1;
! 1741:
! 1742: /* ditch the strict lock */
! 1743: rfp->rf_flags &= ~RCS_SLOCK;
! 1744: rfp->rf_pdata = pdp;
! 1745:
! 1746: if (rcs_parse_admin(rfp) < 0) {
! 1747: rcs_freepdata(pdp);
! 1748: errx(1, "could not parse admin data");
! 1749: }
! 1750:
! 1751: if (rfp->rf_flags & RCS_PARSE_FULLY)
! 1752: rcs_parse_deltatexts(rfp, NULL);
! 1753:
! 1754: rfp->rf_flags |= RCS_SYNCED;
! 1755: }
! 1756:
! 1757: /*
! 1758: * rcs_parse_admin()
! 1759: *
! 1760: * Parse the administrative portion of an RCS file.
! 1761: * Returns the type of the first token found after the admin section on
! 1762: * success, or -1 on failure.
! 1763: */
! 1764: static int
! 1765: rcs_parse_admin(RCSFILE *rfp)
! 1766: {
! 1767: u_int i;
! 1768: int tok, ntok, hmask;
! 1769: struct rcs_key *rk;
! 1770:
! 1771: /* hmask is a mask of the headers already encountered */
! 1772: hmask = 0;
! 1773: for (;;) {
! 1774: tok = rcs_gettok(rfp);
! 1775: if (tok == RCS_TOK_ERR) {
! 1776: rcs_errno = RCS_ERR_PARSE;
! 1777: warnx("parse error in RCS admin section");
! 1778: goto fail;
! 1779: } else if (tok == RCS_TOK_NUM || tok == RCS_TOK_DESC) {
! 1780: /*
! 1781: * Assume this is the start of the first delta or
! 1782: * that we are dealing with an empty RCS file and
! 1783: * we just found the description.
! 1784: */
! 1785: rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok);
! 1786: return (tok);
! 1787: }
! 1788:
! 1789: rk = NULL;
! 1790: for (i = 0; i < RCS_NKEYS; i++)
! 1791: if (rcs_keys[i].rk_id == tok)
! 1792: rk = &(rcs_keys[i]);
! 1793:
! 1794: if (hmask & (1 << tok)) {
! 1795: rcs_errno = RCS_ERR_PARSE;
! 1796: warnx("duplicate RCS key");
! 1797: goto fail;
! 1798: }
! 1799: hmask |= (1 << tok);
! 1800:
! 1801: switch (tok) {
! 1802: case RCS_TOK_HEAD:
! 1803: case RCS_TOK_BRANCH:
! 1804: case RCS_TOK_COMMENT:
! 1805: case RCS_TOK_EXPAND:
! 1806: ntok = rcs_gettok(rfp);
! 1807: if (ntok == RCS_TOK_SCOLON)
! 1808: break;
! 1809: if (ntok != rk->rk_val) {
! 1810: rcs_errno = RCS_ERR_PARSE;
! 1811: warnx("invalid value type for RCS key `%s'",
! 1812: rk->rk_str);
! 1813: }
! 1814:
! 1815: if (tok == RCS_TOK_HEAD) {
! 1816: if (rfp->rf_head == NULL)
! 1817: rfp->rf_head = rcsnum_alloc();
! 1818: rcsnum_aton(RCS_TOKSTR(rfp), NULL,
! 1819: rfp->rf_head);
! 1820: } else if (tok == RCS_TOK_BRANCH) {
! 1821: if (rfp->rf_branch == NULL)
! 1822: rfp->rf_branch = rcsnum_alloc();
! 1823: if (rcsnum_aton(RCS_TOKSTR(rfp), NULL,
! 1824: rfp->rf_branch) < 0)
! 1825: goto fail;
! 1826: } else if (tok == RCS_TOK_COMMENT) {
! 1827: rfp->rf_comment = xstrdup(RCS_TOKSTR(rfp));
! 1828: } else if (tok == RCS_TOK_EXPAND) {
! 1829: rfp->rf_expand = xstrdup(RCS_TOKSTR(rfp));
! 1830: }
! 1831:
! 1832: /* now get the expected semi-colon */
! 1833: ntok = rcs_gettok(rfp);
! 1834: if (ntok != RCS_TOK_SCOLON) {
! 1835: rcs_errno = RCS_ERR_PARSE;
! 1836: warnx("missing semi-colon after RCS `%s' key",
! 1837: rk->rk_str);
! 1838: goto fail;
! 1839: }
! 1840: break;
! 1841: case RCS_TOK_ACCESS:
! 1842: if (rcs_parse_access(rfp) < 0)
! 1843: goto fail;
! 1844: break;
! 1845: case RCS_TOK_SYMBOLS:
! 1846: if (rcs_parse_symbols(rfp) < 0)
! 1847: goto fail;
! 1848: break;
! 1849: case RCS_TOK_LOCKS:
! 1850: if (rcs_parse_locks(rfp) < 0)
! 1851: goto fail;
! 1852: break;
! 1853: default:
! 1854: rcs_errno = RCS_ERR_PARSE;
! 1855: warnx("unexpected token `%s' in RCS admin section",
! 1856: RCS_TOKSTR(rfp));
! 1857: goto fail;
! 1858: }
! 1859: }
! 1860:
! 1861: fail:
! 1862: return (-1);
! 1863: }
! 1864:
! 1865: /*
! 1866: * rcs_parse_delta()
! 1867: *
! 1868: * Parse an RCS delta section and allocate the structure to store that delta's
! 1869: * information in the <rfp> delta list.
! 1870: * Returns 1 if the section was parsed OK, 0 if it is the last delta, and
! 1871: * -1 on error.
! 1872: */
! 1873: static int
! 1874: rcs_parse_delta(RCSFILE *rfp)
! 1875: {
! 1876: int ret, tok, ntok, hmask;
! 1877: u_int i;
! 1878: char *tokstr;
! 1879: RCSNUM *datenum;
! 1880: struct rcs_delta *rdp;
! 1881: struct rcs_key *rk;
! 1882:
! 1883: rdp = xcalloc(1, sizeof(*rdp));
! 1884:
! 1885: rdp->rd_num = rcsnum_alloc();
! 1886: rdp->rd_next = rcsnum_alloc();
! 1887:
! 1888: TAILQ_INIT(&(rdp->rd_branches));
! 1889:
! 1890: tok = rcs_gettok(rfp);
! 1891: if (tok == RCS_TOK_DESC) {
! 1892: rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok);
! 1893: return (0);
! 1894: } else if (tok != RCS_TOK_NUM) {
! 1895: rcs_errno = RCS_ERR_PARSE;
! 1896: warnx("unexpected token `%s' at start of delta",
! 1897: RCS_TOKSTR(rfp));
! 1898: rcs_freedelta(rdp);
! 1899: return (-1);
! 1900: }
! 1901: rcsnum_aton(RCS_TOKSTR(rfp), NULL, rdp->rd_num);
! 1902:
! 1903: hmask = 0;
! 1904: ret = 0;
! 1905: tokstr = NULL;
! 1906:
! 1907: for (;;) {
! 1908: tok = rcs_gettok(rfp);
! 1909: if (tok == RCS_TOK_ERR) {
! 1910: rcs_errno = RCS_ERR_PARSE;
! 1911: warnx("parse error in RCS delta section");
! 1912: rcs_freedelta(rdp);
! 1913: return (-1);
! 1914: } else if (tok == RCS_TOK_NUM || tok == RCS_TOK_DESC) {
! 1915: rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok);
! 1916: ret = (tok == RCS_TOK_NUM ? 1 : 0);
! 1917: break;
! 1918: }
! 1919:
! 1920: rk = NULL;
! 1921: for (i = 0; i < RCS_NKEYS; i++)
! 1922: if (rcs_keys[i].rk_id == tok)
! 1923: rk = &(rcs_keys[i]);
! 1924:
! 1925: if (hmask & (1 << tok)) {
! 1926: rcs_errno = RCS_ERR_PARSE;
! 1927: warnx("duplicate RCS key");
! 1928: rcs_freedelta(rdp);
! 1929: return (-1);
! 1930: }
! 1931: hmask |= (1 << tok);
! 1932:
! 1933: switch (tok) {
! 1934: case RCS_TOK_DATE:
! 1935: case RCS_TOK_AUTHOR:
! 1936: case RCS_TOK_STATE:
! 1937: case RCS_TOK_NEXT:
! 1938: ntok = rcs_gettok(rfp);
! 1939: if (ntok == RCS_TOK_SCOLON) {
! 1940: if (rk->rk_flags & RCS_VOPT)
! 1941: break;
! 1942: else {
! 1943: rcs_errno = RCS_ERR_PARSE;
! 1944: warnx("missing mandatory "
! 1945: "value to RCS key `%s'",
! 1946: rk->rk_str);
! 1947: rcs_freedelta(rdp);
! 1948: return (-1);
! 1949: }
! 1950: }
! 1951:
! 1952: if (ntok != rk->rk_val) {
! 1953: rcs_errno = RCS_ERR_PARSE;
! 1954: warnx("invalid value type for RCS key `%s'",
! 1955: rk->rk_str);
! 1956: rcs_freedelta(rdp);
! 1957: return (-1);
! 1958: }
! 1959:
! 1960: if (tokstr != NULL)
! 1961: xfree(tokstr);
! 1962: tokstr = xstrdup(RCS_TOKSTR(rfp));
! 1963: /* now get the expected semi-colon */
! 1964: ntok = rcs_gettok(rfp);
! 1965: if (ntok != RCS_TOK_SCOLON) {
! 1966: rcs_errno = RCS_ERR_PARSE;
! 1967: warnx("missing semi-colon after RCS `%s' key",
! 1968: rk->rk_str);
! 1969: xfree(tokstr);
! 1970: rcs_freedelta(rdp);
! 1971: return (-1);
! 1972: }
! 1973:
! 1974: if (tok == RCS_TOK_DATE) {
! 1975: if ((datenum = rcsnum_parse(tokstr)) == NULL) {
! 1976: xfree(tokstr);
! 1977: rcs_freedelta(rdp);
! 1978: return (-1);
! 1979: }
! 1980: if (datenum->rn_len != 6) {
! 1981: rcs_errno = RCS_ERR_PARSE;
! 1982: warnx("RCS date specification has %s "
! 1983: "fields",
! 1984: (datenum->rn_len > 6) ? "too many" :
! 1985: "missing");
! 1986: xfree(tokstr);
! 1987: rcs_freedelta(rdp);
! 1988: rcsnum_free(datenum);
! 1989: return (-1);
! 1990: }
! 1991: rdp->rd_date.tm_year = datenum->rn_id[0];
! 1992: if (rdp->rd_date.tm_year >= 1900)
! 1993: rdp->rd_date.tm_year -= 1900;
! 1994: rdp->rd_date.tm_mon = datenum->rn_id[1] - 1;
! 1995: rdp->rd_date.tm_mday = datenum->rn_id[2];
! 1996: rdp->rd_date.tm_hour = datenum->rn_id[3];
! 1997: rdp->rd_date.tm_min = datenum->rn_id[4];
! 1998: rdp->rd_date.tm_sec = datenum->rn_id[5];
! 1999: rcsnum_free(datenum);
! 2000: } else if (tok == RCS_TOK_AUTHOR) {
! 2001: rdp->rd_author = tokstr;
! 2002: tokstr = NULL;
! 2003: } else if (tok == RCS_TOK_STATE) {
! 2004: rdp->rd_state = tokstr;
! 2005: tokstr = NULL;
! 2006: } else if (tok == RCS_TOK_NEXT) {
! 2007: rcsnum_aton(tokstr, NULL, rdp->rd_next);
! 2008: }
! 2009: break;
! 2010: case RCS_TOK_BRANCHES:
! 2011: if (rcs_parse_branches(rfp, rdp) < 0) {
! 2012: rcs_freedelta(rdp);
! 2013: return (-1);
! 2014: }
! 2015: break;
! 2016: default:
! 2017: rcs_errno = RCS_ERR_PARSE;
! 2018: warnx("unexpected token `%s' in RCS delta",
! 2019: RCS_TOKSTR(rfp));
! 2020: rcs_freedelta(rdp);
! 2021: return (-1);
! 2022: }
! 2023: }
! 2024:
! 2025: if (tokstr != NULL)
! 2026: xfree(tokstr);
! 2027:
! 2028: TAILQ_INSERT_TAIL(&(rfp->rf_delta), rdp, rd_list);
! 2029: rfp->rf_ndelta++;
! 2030:
! 2031: return (ret);
! 2032: }
! 2033:
! 2034: /*
! 2035: * rcs_parse_deltatext()
! 2036: *
! 2037: * Parse an RCS delta text section and fill in the log and text field of the
! 2038: * appropriate delta section.
! 2039: * Returns 1 if the section was parsed OK, 0 if it is the last delta, and
! 2040: * -1 on error.
! 2041: */
! 2042: static int
! 2043: rcs_parse_deltatext(RCSFILE *rfp)
! 2044: {
! 2045: int tok;
! 2046: RCSNUM *tnum;
! 2047: struct rcs_delta *rdp;
! 2048:
! 2049: tok = rcs_gettok(rfp);
! 2050: if (tok == RCS_TOK_EOF)
! 2051: return (0);
! 2052:
! 2053: if (tok != RCS_TOK_NUM) {
! 2054: rcs_errno = RCS_ERR_PARSE;
! 2055: warnx("unexpected token `%s' at start of RCS delta text",
! 2056: RCS_TOKSTR(rfp));
! 2057: return (-1);
! 2058: }
! 2059:
! 2060: tnum = rcsnum_alloc();
! 2061: rcsnum_aton(RCS_TOKSTR(rfp), NULL, tnum);
! 2062:
! 2063: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
! 2064: if (rcsnum_cmp(tnum, rdp->rd_num, 0) == 0)
! 2065: break;
! 2066: }
! 2067: rcsnum_free(tnum);
! 2068:
! 2069: if (rdp == NULL) {
! 2070: warnx("RCS delta text `%s' has no matching delta",
! 2071: RCS_TOKSTR(rfp));
! 2072: return (-1);
! 2073: }
! 2074:
! 2075: tok = rcs_gettok(rfp);
! 2076: if (tok != RCS_TOK_LOG) {
! 2077: rcs_errno = RCS_ERR_PARSE;
! 2078: warnx("unexpected token `%s' where RCS log expected",
! 2079: RCS_TOKSTR(rfp));
! 2080: return (-1);
! 2081: }
! 2082:
! 2083: tok = rcs_gettok(rfp);
! 2084: if (tok != RCS_TOK_STRING) {
! 2085: rcs_errno = RCS_ERR_PARSE;
! 2086: warnx("unexpected token `%s' where RCS log expected",
! 2087: RCS_TOKSTR(rfp));
! 2088: return (-1);
! 2089: }
! 2090: rdp->rd_log = xstrdup(RCS_TOKSTR(rfp));
! 2091: tok = rcs_gettok(rfp);
! 2092: if (tok != RCS_TOK_TEXT) {
! 2093: rcs_errno = RCS_ERR_PARSE;
! 2094: warnx("unexpected token `%s' where RCS text expected",
! 2095: RCS_TOKSTR(rfp));
! 2096: return (-1);
! 2097: }
! 2098:
! 2099: tok = rcs_gettok(rfp);
! 2100: if (tok != RCS_TOK_STRING) {
! 2101: rcs_errno = RCS_ERR_PARSE;
! 2102: warnx("unexpected token `%s' where RCS text expected",
! 2103: RCS_TOKSTR(rfp));
! 2104: return (-1);
! 2105: }
! 2106:
! 2107: rdp->rd_text = xmalloc(RCS_TOKLEN(rfp) + 1);
! 2108: strlcpy(rdp->rd_text, RCS_TOKSTR(rfp), (RCS_TOKLEN(rfp) + 1));
! 2109: rdp->rd_tlen = RCS_TOKLEN(rfp);
! 2110:
! 2111: return (1);
! 2112: }
! 2113:
! 2114: /*
! 2115: * rcs_parse_access()
! 2116: *
! 2117: * Parse the access list given as value to the `access' keyword.
! 2118: * Returns 0 on success, or -1 on failure.
! 2119: */
! 2120: static int
! 2121: rcs_parse_access(RCSFILE *rfp)
! 2122: {
! 2123: int type;
! 2124:
! 2125: while ((type = rcs_gettok(rfp)) != RCS_TOK_SCOLON) {
! 2126: if (type != RCS_TOK_ID) {
! 2127: rcs_errno = RCS_ERR_PARSE;
! 2128: warnx("unexpected token `%s' in access list",
! 2129: RCS_TOKSTR(rfp));
! 2130: return (-1);
! 2131: }
! 2132:
! 2133: if (rcs_access_add(rfp, RCS_TOKSTR(rfp)) < 0)
! 2134: return (-1);
! 2135: }
! 2136:
! 2137: return (0);
! 2138: }
! 2139:
! 2140: /*
! 2141: * rcs_parse_symbols()
! 2142: *
! 2143: * Parse the symbol list given as value to the `symbols' keyword.
! 2144: * Returns 0 on success, or -1 on failure.
! 2145: */
! 2146: static int
! 2147: rcs_parse_symbols(RCSFILE *rfp)
! 2148: {
! 2149: int type;
! 2150: struct rcs_sym *symp;
! 2151:
! 2152: for (;;) {
! 2153: type = rcs_gettok(rfp);
! 2154: if (type == RCS_TOK_SCOLON)
! 2155: break;
! 2156:
! 2157: if (type != RCS_TOK_ID) {
! 2158: rcs_errno = RCS_ERR_PARSE;
! 2159: warnx("unexpected token `%s' in symbol list",
! 2160: RCS_TOKSTR(rfp));
! 2161: return (-1);
! 2162: }
! 2163:
! 2164: symp = xmalloc(sizeof(*symp));
! 2165: symp->rs_name = xstrdup(RCS_TOKSTR(rfp));
! 2166: symp->rs_num = rcsnum_alloc();
! 2167:
! 2168: type = rcs_gettok(rfp);
! 2169: if (type != RCS_TOK_COLON) {
! 2170: rcs_errno = RCS_ERR_PARSE;
! 2171: warnx("unexpected token `%s' in symbol list",
! 2172: RCS_TOKSTR(rfp));
! 2173: rcsnum_free(symp->rs_num);
! 2174: xfree(symp->rs_name);
! 2175: xfree(symp);
! 2176: return (-1);
! 2177: }
! 2178:
! 2179: type = rcs_gettok(rfp);
! 2180: if (type != RCS_TOK_NUM) {
! 2181: rcs_errno = RCS_ERR_PARSE;
! 2182: warnx("unexpected token `%s' in symbol list",
! 2183: RCS_TOKSTR(rfp));
! 2184: rcsnum_free(symp->rs_num);
! 2185: xfree(symp->rs_name);
! 2186: xfree(symp);
! 2187: return (-1);
! 2188: }
! 2189:
! 2190: if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, symp->rs_num) < 0) {
! 2191: warnx("failed to parse RCS NUM `%s'",
! 2192: RCS_TOKSTR(rfp));
! 2193: rcsnum_free(symp->rs_num);
! 2194: xfree(symp->rs_name);
! 2195: xfree(symp);
! 2196: return (-1);
! 2197: }
! 2198:
! 2199: TAILQ_INSERT_TAIL(&(rfp->rf_symbols), symp, rs_list);
! 2200: }
! 2201:
! 2202: return (0);
! 2203: }
! 2204:
! 2205: /*
! 2206: * rcs_parse_locks()
! 2207: *
! 2208: * Parse the lock list given as value to the `locks' keyword.
! 2209: * Returns 0 on success, or -1 on failure.
! 2210: */
! 2211: static int
! 2212: rcs_parse_locks(RCSFILE *rfp)
! 2213: {
! 2214: int type;
! 2215: struct rcs_lock *lkp;
! 2216:
! 2217: for (;;) {
! 2218: type = rcs_gettok(rfp);
! 2219: if (type == RCS_TOK_SCOLON)
! 2220: break;
! 2221:
! 2222: if (type != RCS_TOK_ID) {
! 2223: rcs_errno = RCS_ERR_PARSE;
! 2224: warnx("unexpected token `%s' in lock list",
! 2225: RCS_TOKSTR(rfp));
! 2226: return (-1);
! 2227: }
! 2228:
! 2229: lkp = xmalloc(sizeof(*lkp));
! 2230: lkp->rl_name = xstrdup(RCS_TOKSTR(rfp));
! 2231: lkp->rl_num = rcsnum_alloc();
! 2232:
! 2233: type = rcs_gettok(rfp);
! 2234: if (type != RCS_TOK_COLON) {
! 2235: rcs_errno = RCS_ERR_PARSE;
! 2236: warnx("unexpected token `%s' in symbol list",
! 2237: RCS_TOKSTR(rfp));
! 2238: rcsnum_free(lkp->rl_num);
! 2239: xfree(lkp->rl_name);
! 2240: xfree(lkp);
! 2241: return (-1);
! 2242: }
! 2243:
! 2244: type = rcs_gettok(rfp);
! 2245: if (type != RCS_TOK_NUM) {
! 2246: rcs_errno = RCS_ERR_PARSE;
! 2247: warnx("unexpected token `%s' in symbol list",
! 2248: RCS_TOKSTR(rfp));
! 2249: rcsnum_free(lkp->rl_num);
! 2250: xfree(lkp->rl_name);
! 2251: xfree(lkp);
! 2252: return (-1);
! 2253: }
! 2254:
! 2255: if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, lkp->rl_num) < 0) {
! 2256: warnx("failed to parse RCS NUM `%s'",
! 2257: RCS_TOKSTR(rfp));
! 2258: rcsnum_free(lkp->rl_num);
! 2259: xfree(lkp->rl_name);
! 2260: xfree(lkp);
! 2261: return (-1);
! 2262: }
! 2263:
! 2264: TAILQ_INSERT_HEAD(&(rfp->rf_locks), lkp, rl_list);
! 2265: }
! 2266:
! 2267: /* check if we have a `strict' */
! 2268: type = rcs_gettok(rfp);
! 2269: if (type != RCS_TOK_STRICT) {
! 2270: rcs_pushtok(rfp, RCS_TOKSTR(rfp), type);
! 2271: } else {
! 2272: rfp->rf_flags |= RCS_SLOCK;
! 2273:
! 2274: type = rcs_gettok(rfp);
! 2275: if (type != RCS_TOK_SCOLON) {
! 2276: rcs_errno = RCS_ERR_PARSE;
! 2277: warnx("missing semi-colon after `strict' keyword");
! 2278: return (-1);
! 2279: }
! 2280: }
! 2281:
! 2282: return (0);
! 2283: }
! 2284:
! 2285: /*
! 2286: * rcs_parse_branches()
! 2287: *
! 2288: * Parse the list of branches following a `branches' keyword in a delta.
! 2289: * Returns 0 on success, or -1 on failure.
! 2290: */
! 2291: static int
! 2292: rcs_parse_branches(RCSFILE *rfp, struct rcs_delta *rdp)
! 2293: {
! 2294: int type;
! 2295: struct rcs_branch *brp;
! 2296:
! 2297: for (;;) {
! 2298: type = rcs_gettok(rfp);
! 2299: if (type == RCS_TOK_SCOLON)
! 2300: break;
! 2301:
! 2302: if (type != RCS_TOK_NUM) {
! 2303: rcs_errno = RCS_ERR_PARSE;
! 2304: warnx("unexpected token `%s' in list of branches",
! 2305: RCS_TOKSTR(rfp));
! 2306: return (-1);
! 2307: }
! 2308:
! 2309: brp = xmalloc(sizeof(*brp));
! 2310: brp->rb_num = rcsnum_parse(RCS_TOKSTR(rfp));
! 2311: if (brp->rb_num == NULL) {
! 2312: xfree(brp);
! 2313: return (-1);
! 2314: }
! 2315:
! 2316: TAILQ_INSERT_TAIL(&(rdp->rd_branches), brp, rb_list);
! 2317: }
! 2318:
! 2319: return (0);
! 2320: }
! 2321:
! 2322: /*
! 2323: * rcs_freedelta()
! 2324: *
! 2325: * Free the contents of a delta structure.
! 2326: */
! 2327: static void
! 2328: rcs_freedelta(struct rcs_delta *rdp)
! 2329: {
! 2330: struct rcs_branch *rb;
! 2331:
! 2332: if (rdp->rd_num != NULL)
! 2333: rcsnum_free(rdp->rd_num);
! 2334: if (rdp->rd_next != NULL)
! 2335: rcsnum_free(rdp->rd_next);
! 2336:
! 2337: if (rdp->rd_author != NULL)
! 2338: xfree(rdp->rd_author);
! 2339: if (rdp->rd_locker != NULL)
! 2340: xfree(rdp->rd_locker);
! 2341: if (rdp->rd_state != NULL)
! 2342: xfree(rdp->rd_state);
! 2343: if (rdp->rd_log != NULL)
! 2344: xfree(rdp->rd_log);
! 2345: if (rdp->rd_text != NULL)
! 2346: xfree(rdp->rd_text);
! 2347:
! 2348: while ((rb = TAILQ_FIRST(&(rdp->rd_branches))) != NULL) {
! 2349: TAILQ_REMOVE(&(rdp->rd_branches), rb, rb_list);
! 2350: rcsnum_free(rb->rb_num);
! 2351: xfree(rb);
! 2352: }
! 2353:
! 2354: xfree(rdp);
! 2355: }
! 2356:
! 2357: /*
! 2358: * rcs_freepdata()
! 2359: *
! 2360: * Free the contents of the parser data structure.
! 2361: */
! 2362: static void
! 2363: rcs_freepdata(struct rcs_pdata *pd)
! 2364: {
! 2365: if (pd->rp_file != NULL)
! 2366: (void)fclose(pd->rp_file);
! 2367: if (pd->rp_buf != NULL)
! 2368: xfree(pd->rp_buf);
! 2369: xfree(pd);
! 2370: }
! 2371:
! 2372: /*
! 2373: * rcs_gettok()
! 2374: *
! 2375: * Get the next RCS token from the string <str>.
! 2376: */
! 2377: static int
! 2378: rcs_gettok(RCSFILE *rfp)
! 2379: {
! 2380: u_int i;
! 2381: int ch, last, type;
! 2382: size_t len;
! 2383: char *bp;
! 2384: struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata;
! 2385:
! 2386: type = RCS_TOK_ERR;
! 2387: bp = pdp->rp_buf;
! 2388: pdp->rp_tlen = 0;
! 2389: *bp = '\0';
! 2390:
! 2391: if (pdp->rp_pttype != RCS_TOK_ERR) {
! 2392: type = pdp->rp_pttype;
! 2393: strlcpy(pdp->rp_buf, pdp->rp_ptok, pdp->rp_blen);
! 2394: pdp->rp_pttype = RCS_TOK_ERR;
! 2395: return (type);
! 2396: }
! 2397:
! 2398: /* skip leading whitespace */
! 2399: /* XXX we must skip backspace too for compatibility, should we? */
! 2400: do {
! 2401: ch = getc(pdp->rp_file);
! 2402: if (ch == '\n')
! 2403: pdp->rp_lines++;
! 2404: } while (isspace(ch));
! 2405:
! 2406: if (ch == EOF) {
! 2407: type = RCS_TOK_EOF;
! 2408: } else if (ch == ';') {
! 2409: type = RCS_TOK_SCOLON;
! 2410: } else if (ch == ':') {
! 2411: type = RCS_TOK_COLON;
! 2412: } else if (isalpha(ch)) {
! 2413: type = RCS_TOK_ID;
! 2414: *(bp++) = ch;
! 2415: for (;;) {
! 2416: ch = getc(pdp->rp_file);
! 2417: if (!isalnum(ch) && ch != '_' && ch != '-' &&
! 2418: ch != '/') {
! 2419: ungetc(ch, pdp->rp_file);
! 2420: break;
! 2421: }
! 2422: *(bp++) = ch;
! 2423: pdp->rp_tlen++;
! 2424: if (bp == pdp->rp_bufend - 1) {
! 2425: len = bp - pdp->rp_buf;
! 2426: rcs_growbuf(rfp);
! 2427: bp = pdp->rp_buf + len;
! 2428: }
! 2429: }
! 2430: *bp = '\0';
! 2431:
! 2432: if (type != RCS_TOK_ERR) {
! 2433: for (i = 0; i < RCS_NKEYS; i++) {
! 2434: if (strcmp(rcs_keys[i].rk_str,
! 2435: pdp->rp_buf) == 0) {
! 2436: type = rcs_keys[i].rk_id;
! 2437: break;
! 2438: }
! 2439: }
! 2440: }
! 2441: } else if (ch == '@') {
! 2442: /* we have a string */
! 2443: type = RCS_TOK_STRING;
! 2444: for (;;) {
! 2445: ch = getc(pdp->rp_file);
! 2446: if (ch == '@') {
! 2447: ch = getc(pdp->rp_file);
! 2448: if (ch != '@') {
! 2449: ungetc(ch, pdp->rp_file);
! 2450: break;
! 2451: }
! 2452: } else if (ch == '\n')
! 2453: pdp->rp_lines++;
! 2454:
! 2455: *(bp++) = ch;
! 2456: pdp->rp_tlen++;
! 2457: if (bp == pdp->rp_bufend - 1) {
! 2458: len = bp - pdp->rp_buf;
! 2459: rcs_growbuf(rfp);
! 2460: bp = pdp->rp_buf + len;
! 2461: }
! 2462: }
! 2463:
! 2464: *bp = '\0';
! 2465: } else if (isdigit(ch)) {
! 2466: *(bp++) = ch;
! 2467: last = ch;
! 2468: type = RCS_TOK_NUM;
! 2469:
! 2470: for (;;) {
! 2471: ch = getc(pdp->rp_file);
! 2472: if (bp == pdp->rp_bufend)
! 2473: break;
! 2474: if (!isdigit(ch) && ch != '.') {
! 2475: ungetc(ch, pdp->rp_file);
! 2476: break;
! 2477: }
! 2478:
! 2479: if (last == '.' && ch == '.') {
! 2480: type = RCS_TOK_ERR;
! 2481: break;
! 2482: }
! 2483: last = ch;
! 2484: *(bp++) = ch;
! 2485: pdp->rp_tlen++;
! 2486: }
! 2487: *bp = '\0';
! 2488: }
! 2489:
! 2490: return (type);
! 2491: }
! 2492:
! 2493: /*
! 2494: * rcs_pushtok()
! 2495: *
! 2496: * Push a token back in the parser's token buffer.
! 2497: */
! 2498: static int
! 2499: rcs_pushtok(RCSFILE *rfp, const char *tok, int type)
! 2500: {
! 2501: struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata;
! 2502:
! 2503: if (pdp->rp_pttype != RCS_TOK_ERR)
! 2504: return (-1);
! 2505:
! 2506: pdp->rp_pttype = type;
! 2507: strlcpy(pdp->rp_ptok, tok, sizeof(pdp->rp_ptok));
! 2508: return (0);
! 2509: }
! 2510:
! 2511:
! 2512: /*
! 2513: * rcs_growbuf()
! 2514: *
! 2515: * Attempt to grow the internal parse buffer for the RCS file <rf> by
! 2516: * RCS_BUFEXTSIZE.
! 2517: * In case of failure, the original buffer is left unmodified.
! 2518: */
! 2519: static void
! 2520: rcs_growbuf(RCSFILE *rf)
! 2521: {
! 2522: void *tmp;
! 2523: struct rcs_pdata *pdp = (struct rcs_pdata *)rf->rf_pdata;
! 2524:
! 2525: tmp = xrealloc(pdp->rp_buf, 1, pdp->rp_blen + RCS_BUFEXTSIZE);
! 2526: pdp->rp_buf = tmp;
! 2527: pdp->rp_blen += RCS_BUFEXTSIZE;
! 2528: pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1;
! 2529: }
! 2530:
! 2531: /*
! 2532: * rcs_strprint()
! 2533: *
! 2534: * Output an RCS string <str> of size <slen> to the stream <stream>. Any
! 2535: * '@' characters are escaped. Otherwise, the string can contain arbitrary
! 2536: * binary data.
! 2537: */
! 2538: static void
! 2539: rcs_strprint(const u_char *str, size_t slen, FILE *stream)
! 2540: {
! 2541: const u_char *ap, *ep, *sp;
! 2542:
! 2543: if (slen == 0)
! 2544: return;
! 2545:
! 2546: ep = str + slen - 1;
! 2547:
! 2548: for (sp = str; sp <= ep;) {
! 2549: ap = memchr(sp, '@', ep - sp);
! 2550: if (ap == NULL)
! 2551: ap = ep;
! 2552: (void)fwrite(sp, sizeof(u_char), ap - sp + 1, stream);
! 2553:
! 2554: if (*ap == '@')
! 2555: putc('@', stream);
! 2556: sp = ap + 1;
! 2557: }
! 2558: }
! 2559:
! 2560: /*
! 2561: * rcs_expand_keywords()
! 2562: *
! 2563: * Return expansion any RCS keywords in <data>
! 2564: *
! 2565: * On error, return NULL.
! 2566: */
! 2567: static char *
! 2568: rcs_expand_keywords(char *rcsfile, struct rcs_delta *rdp, char *data,
! 2569: size_t len, int mode)
! 2570: {
! 2571: ptrdiff_t c_offset, sizdiff, start_offset;
! 2572: size_t i;
! 2573: int kwtype;
! 2574: u_int j, found;
! 2575: char *c, *kwstr, *start, *end, *tbuf;
! 2576: char expbuf[256], buf[256];
! 2577: struct tm tb;
! 2578: char *fmt;
! 2579:
! 2580: kwtype = 0;
! 2581: kwstr = NULL;
! 2582: i = 0;
! 2583:
! 2584: /*
! 2585: * -z support for RCS
! 2586: */
! 2587: tb = rdp->rd_date;
! 2588: if (timezone_flag != NULL)
! 2589: rcs_set_tz(timezone_flag, rdp, &tb);
! 2590:
! 2591: /*
! 2592: * Keyword formats:
! 2593: * $Keyword$
! 2594: * $Keyword: value$
! 2595: */
! 2596: for (c = data; *c != '\0' && i < len; c++) {
! 2597: if (*c == '$') {
! 2598: /* remember start of this possible keyword */
! 2599: start = c;
! 2600: start_offset = start - data;
! 2601:
! 2602: /* first following character has to be alphanumeric */
! 2603: c++;
! 2604: if (!isalpha(*c)) {
! 2605: c = start;
! 2606: continue;
! 2607: }
! 2608:
! 2609: /* look for any matching keywords */
! 2610: found = 0;
! 2611: for (j = 0; j < RCS_NKWORDS; j++) {
! 2612: if (!strncmp(c, rcs_expkw[j].kw_str,
! 2613: strlen(rcs_expkw[j].kw_str))) {
! 2614: found = 1;
! 2615: kwstr = rcs_expkw[j].kw_str;
! 2616: kwtype = rcs_expkw[j].kw_type;
! 2617: break;
! 2618: }
! 2619: }
! 2620:
! 2621: /* unknown keyword, continue looking */
! 2622: if (found == 0) {
! 2623: c = start;
! 2624: continue;
! 2625: }
! 2626:
! 2627: /* next character has to be ':' or '$' */
! 2628: c += strlen(kwstr);
! 2629: if (*c != ':' && *c != '$') {
! 2630: c = start;
! 2631: continue;
! 2632: }
! 2633:
! 2634: /*
! 2635: * if the next character was ':' we need to look for
! 2636: * an '$' before the end of the line to be sure it is
! 2637: * in fact a keyword.
! 2638: */
! 2639: if (*c == ':') {
! 2640: while (*c++) {
! 2641: if (*c == '$' || *c == '\n')
! 2642: break;
! 2643: }
! 2644:
! 2645: if (*c != '$') {
! 2646: c = start;
! 2647: continue;
! 2648: }
! 2649: }
! 2650: c_offset = c - data;
! 2651: end = c + 1;
! 2652:
! 2653: /* start constructing the expansion */
! 2654: expbuf[0] = '\0';
! 2655:
! 2656: if (mode & RCS_KWEXP_NAME) {
! 2657: strlcat(expbuf, "$", sizeof(expbuf));
! 2658: strlcat(expbuf, kwstr, sizeof(expbuf));
! 2659: if (mode & RCS_KWEXP_VAL)
! 2660: strlcat(expbuf, ": ", sizeof(expbuf));
! 2661: }
! 2662:
! 2663: /*
! 2664: * order matters because of RCS_KW_ID and
! 2665: * RCS_KW_HEADER here
! 2666: */
! 2667: if (mode & RCS_KWEXP_VAL) {
! 2668: if (kwtype & RCS_KW_RCSFILE) {
! 2669: if (!(kwtype & RCS_KW_FULLPATH))
! 2670: strlcat(expbuf,
! 2671: basename(rcsfile),
! 2672: sizeof(expbuf));
! 2673: else
! 2674: strlcat(expbuf, rcsfile,
! 2675: sizeof(expbuf));
! 2676: strlcat(expbuf, " ", sizeof(expbuf));
! 2677: }
! 2678:
! 2679: if (kwtype & RCS_KW_REVISION) {
! 2680: rcsnum_tostr(rdp->rd_num, buf,
! 2681: sizeof(buf));
! 2682: strlcat(buf, " ", sizeof(buf));
! 2683: strlcat(expbuf, buf, sizeof(expbuf));
! 2684: }
! 2685:
! 2686: if (kwtype & RCS_KW_DATE) {
! 2687: if (timezone_flag != NULL)
! 2688: fmt = "%Y/%m/%d %H:%M:%S%z ";
! 2689: else
! 2690: fmt = "%Y/%m/%d %H:%M:%S ";
! 2691:
! 2692: strftime(buf, sizeof(buf), fmt, &tb);
! 2693: strlcat(expbuf, buf, sizeof(expbuf));
! 2694: }
! 2695:
! 2696: if (kwtype & RCS_KW_AUTHOR) {
! 2697: strlcat(expbuf, rdp->rd_author,
! 2698: sizeof(expbuf));
! 2699: strlcat(expbuf, " ", sizeof(expbuf));
! 2700: }
! 2701:
! 2702: if (kwtype & RCS_KW_STATE) {
! 2703: strlcat(expbuf, rdp->rd_state,
! 2704: sizeof(expbuf));
! 2705: strlcat(expbuf, " ", sizeof(expbuf));
! 2706: }
! 2707:
! 2708: /* order does not matter anymore below */
! 2709: if (kwtype & RCS_KW_LOG)
! 2710: strlcat(expbuf, " ", sizeof(expbuf));
! 2711:
! 2712: if (kwtype & RCS_KW_SOURCE) {
! 2713: strlcat(expbuf, rcsfile,
! 2714: sizeof(expbuf));
! 2715: strlcat(expbuf, " ", sizeof(expbuf));
! 2716: }
! 2717:
! 2718: if (kwtype & RCS_KW_NAME)
! 2719: strlcat(expbuf, " ", sizeof(expbuf));
! 2720: }
! 2721:
! 2722: /* end the expansion */
! 2723: if (mode & RCS_KWEXP_NAME)
! 2724: strlcat(expbuf, "$", sizeof(expbuf));
! 2725:
! 2726: sizdiff = strlen(expbuf) - (end - start);
! 2727: tbuf = xstrdup(end);
! 2728: /* only realloc if we have to */
! 2729: if (sizdiff > 0) {
! 2730: char *newdata;
! 2731:
! 2732: len += sizdiff;
! 2733: newdata = xrealloc(data, 1, len);
! 2734: data = newdata;
! 2735: /*
! 2736: * ensure string pointers are not invalidated
! 2737: * after realloc()
! 2738: */
! 2739: start = data + start_offset;
! 2740: c = data + c_offset;
! 2741: }
! 2742: strlcpy(start, expbuf, len);
! 2743: strlcat(data, tbuf, len);
! 2744: xfree(tbuf);
! 2745: i += strlen(expbuf);
! 2746: }
! 2747: }
! 2748:
! 2749: return (data);
! 2750: }
! 2751:
! 2752: /*
! 2753: * rcs_deltatext_set()
! 2754: *
! 2755: * Set deltatext for <rev> in RCS file <rfp> to <dtext>
! 2756: * Returns -1 on error, 0 on success.
! 2757: */
! 2758: int
! 2759: rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, const char *dtext)
! 2760: {
! 2761: size_t len;
! 2762: struct rcs_delta *rdp;
! 2763:
! 2764: /* Write operations require full parsing */
! 2765: rcs_parse_deltatexts(rfp, NULL);
! 2766:
! 2767: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
! 2768: return (-1);
! 2769:
! 2770: if (rdp->rd_text != NULL)
! 2771: xfree(rdp->rd_text);
! 2772:
! 2773: len = strlen(dtext);
! 2774: if (len != 0) {
! 2775: /* XXX - use xstrdup() if rd_text changes to char *. */
! 2776: rdp->rd_text = xmalloc(len + 1);
! 2777: rdp->rd_tlen = len;
! 2778: (void)memcpy(rdp->rd_text, dtext, len + 1);
! 2779: } else {
! 2780: rdp->rd_text = NULL;
! 2781: rdp->rd_tlen = 0;
! 2782: }
! 2783:
! 2784: return (0);
! 2785: }
! 2786:
! 2787: /*
! 2788: * rcs_rev_setlog()
! 2789: *
! 2790: * Sets the log message of revision <rev> to <logtext>
! 2791: */
! 2792: int
! 2793: rcs_rev_setlog(RCSFILE *rfp, RCSNUM *rev, const char *logtext)
! 2794: {
! 2795: struct rcs_delta *rdp;
! 2796: char buf[16];
! 2797:
! 2798: rcsnum_tostr(rev, buf, sizeof(buf));
! 2799:
! 2800: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
! 2801: return (-1);
! 2802:
! 2803: if (rdp->rd_log != NULL)
! 2804: xfree(rdp->rd_log);
! 2805:
! 2806: rdp->rd_log = xstrdup(logtext);
! 2807: rfp->rf_flags &= ~RCS_SYNCED;
! 2808: return (0);
! 2809: }
! 2810: /*
! 2811: * rcs_rev_getdate()
! 2812: *
! 2813: * Get the date corresponding to a given revision.
! 2814: * Returns the date on success, -1 on failure.
! 2815: */
! 2816: time_t
! 2817: rcs_rev_getdate(RCSFILE *rfp, RCSNUM *rev)
! 2818: {
! 2819: struct rcs_delta *rdp;
! 2820:
! 2821: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
! 2822: return (-1);
! 2823:
! 2824: return (mktime(&rdp->rd_date));
! 2825: }
! 2826:
! 2827: /*
! 2828: * rcs_state_set()
! 2829: *
! 2830: * Sets the state of revision <rev> to <state>
! 2831: * NOTE: default state is 'Exp'. States may not contain spaces.
! 2832: *
! 2833: * Returns -1 on failure, 0 on success.
! 2834: */
! 2835: int
! 2836: rcs_state_set(RCSFILE *rfp, RCSNUM *rev, const char *state)
! 2837: {
! 2838: struct rcs_delta *rdp;
! 2839:
! 2840: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
! 2841: return (-1);
! 2842:
! 2843: if (rdp->rd_state != NULL)
! 2844: xfree(rdp->rd_state);
! 2845:
! 2846: rdp->rd_state = xstrdup(state);
! 2847:
! 2848: rfp->rf_flags &= ~RCS_SYNCED;
! 2849:
! 2850: return (0);
! 2851: }
! 2852:
! 2853: /*
! 2854: * rcs_state_check()
! 2855: *
! 2856: * Check if string <state> is valid.
! 2857: *
! 2858: * Returns 0 if the string is valid, -1 otherwise.
! 2859: */
! 2860: int
! 2861: rcs_state_check(const char *state)
! 2862: {
! 2863: if (strchr(state, ' ') != NULL)
! 2864: return (-1);
! 2865:
! 2866: return (0);
! 2867: }
! 2868:
! 2869: /*
! 2870: * rcs_state_get()
! 2871: *
! 2872: * Get the state for a given revision of a specified RCSFILE.
! 2873: *
! 2874: * Returns NULL on failure.
! 2875: */
! 2876: const char *
! 2877: rcs_state_get(RCSFILE *rfp, RCSNUM *rev)
! 2878: {
! 2879: struct rcs_delta *rdp;
! 2880:
! 2881: if ((rdp = rcs_findrev(rfp, rev)) == NULL)
! 2882: return (NULL);
! 2883:
! 2884: return (rdp->rd_state);
! 2885: }
! 2886:
! 2887: /*
! 2888: * rcs_kwexp_buf()
! 2889: *
! 2890: * Do keyword expansion on a buffer if necessary
! 2891: *
! 2892: */
! 2893: BUF *
! 2894: rcs_kwexp_buf(BUF *bp, RCSFILE *rf, RCSNUM *rev)
! 2895: {
! 2896: struct rcs_delta *rdp;
! 2897: char *expanded, *tbuf;
! 2898: int expmode;
! 2899: size_t len;
! 2900:
! 2901: /*
! 2902: * Do keyword expansion if required.
! 2903: */
! 2904: if (rf->rf_expand != NULL)
! 2905: expmode = rcs_kwexp_get(rf);
! 2906: else
! 2907: expmode = RCS_KWEXP_DEFAULT;
! 2908:
! 2909: if (!(expmode & RCS_KWEXP_NONE)) {
! 2910: if ((rdp = rcs_findrev(rf, rev)) == NULL)
! 2911: errx(1, "could not fetch revision");
! 2912: rcs_buf_putc(bp, '\0');
! 2913: len = rcs_buf_len(bp);
! 2914: tbuf = rcs_buf_release(bp);
! 2915: expanded = rcs_expand_keywords(rf->rf_path, rdp,
! 2916: tbuf, len, expmode);
! 2917: bp = rcs_buf_alloc(len, BUF_AUTOEXT);
! 2918: rcs_buf_set(bp, expanded, strlen(expanded), 0);
! 2919: xfree(expanded);
! 2920: }
! 2921: return (bp);
! 2922: }