[BACK]Return to rcs.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / rcs

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: }