diff options
author | Tobias Stoeckmann <tobias@cvs.openbsd.org> | 2010-10-15 08:44:13 +0000 |
---|---|---|
committer | Tobias Stoeckmann <tobias@cvs.openbsd.org> | 2010-10-15 08:44:13 +0000 |
commit | ce3620dabc66a20fd61e167be9dc1b1ed110f626 (patch) | |
tree | 44aff8e7c42d7644e22decbf6736a55ab997e751 /usr.bin | |
parent | 09cece3bceb9f3b13f794e4ab00c313e2f4ec2c8 (diff) |
Replaced RCS parser code with new rcsparse.{c,h}:
- be very strict about things we parse
- print more information about errors if they occur
- do not fatal() directly in parser, give caller a chance to react
- fix an rcs design issue when it comes to login names
tested by many on tech@
ok xsa
Diffstat (limited to 'usr.bin')
-rw-r--r-- | usr.bin/cvs/Makefile | 8 | ||||
-rw-r--r-- | usr.bin/cvs/rcs.c | 1013 | ||||
-rw-r--r-- | usr.bin/cvs/rcsparse.c | 1262 | ||||
-rw-r--r-- | usr.bin/cvs/rcsparse.h | 21 | ||||
-rw-r--r-- | usr.bin/rcs/Makefile | 6 | ||||
-rw-r--r-- | usr.bin/rcs/rcs.c | 1019 | ||||
-rw-r--r-- | usr.bin/rcs/rcsparse.c | 1265 | ||||
-rw-r--r-- | usr.bin/rcs/rcsparse.h | 21 |
8 files changed, 2614 insertions, 2001 deletions
diff --git a/usr.bin/cvs/Makefile b/usr.bin/cvs/Makefile index 79117100072..bc8b984b5e3 100644 --- a/usr.bin/cvs/Makefile +++ b/usr.bin/cvs/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.47 2008/06/21 15:39:15 joris Exp $ +# $OpenBSD: Makefile,v 1.48 2010/10/15 08:44:12 tobias Exp $ PROG= opencvs MAN= cvsintro.7 # cvs.1 cvs.5 @@ -8,9 +8,9 @@ SRCS= cvs.c add.c admin.c annotate.c atomicio.c commit.c config.c \ checkout.c client.c buf.c cmd.c date.y diff.c diff3.c \ diff_internals.c edit.c entries.c fatal.c file.c getlog.c hash.c \ hash_func.c history.c log.c logmsg.c modules.c import.c init.c \ - release.c remove.c repository.c rcs.c rcsnum.c remote.c root.c \ - server.c status.c tag.c trigger.c worklist.c util.c update.c version.c \ - watch.c xmalloc.c + release.c remove.c repository.c rcs.c rcsnum.c rcsparse.c remote.c \ + root.c server.c status.c tag.c trigger.c worklist.c util.c update.c \ + version.c watch.c xmalloc.c CFLAGS+=-Wall CFLAGS+=-Wstrict-prototypes -Wmissing-prototypes diff --git a/usr.bin/cvs/rcs.c b/usr.bin/cvs/rcs.c index e136a30e4a1..bd052f5303b 100644 --- a/usr.bin/cvs/rcs.c +++ b/usr.bin/cvs/rcs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: rcs.c,v 1.302 2010/09/29 09:23:54 tobias Exp $ */ +/* $OpenBSD: rcs.c,v 1.303 2010/10/15 08:44:12 tobias Exp $ */ /* * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> * All rights reserved. @@ -38,63 +38,14 @@ #include "cvs.h" #include "diff.h" #include "rcs.h" +#include "rcsparse.h" -#define RCS_BUFSIZE 16384 -#define RCS_BUFEXTSIZE 8192 #define RCS_KWEXP_SIZE 1024 -/* RCS token types */ -#define RCS_TOK_ERR -1 -#define RCS_TOK_EOF 0 -#define RCS_TOK_NUM 1 -#define RCS_TOK_ID 2 -#define RCS_TOK_STRING 3 -#define RCS_TOK_SCOLON 4 -#define RCS_TOK_COLON 5 - -#define RCS_TOK_HEAD 8 -#define RCS_TOK_BRANCH 9 -#define RCS_TOK_ACCESS 10 -#define RCS_TOK_SYMBOLS 11 -#define RCS_TOK_LOCKS 12 -#define RCS_TOK_COMMENT 13 -#define RCS_TOK_EXPAND 14 -#define RCS_TOK_DATE 15 -#define RCS_TOK_AUTHOR 16 -#define RCS_TOK_STATE 17 -#define RCS_TOK_NEXT 18 -#define RCS_TOK_BRANCHES 19 -#define RCS_TOK_DESC 20 -#define RCS_TOK_LOG 21 -#define RCS_TOK_TEXT 22 -#define RCS_TOK_STRICT 23 - -#define RCS_ISKEY(t) (((t) >= RCS_TOK_HEAD) && ((t) <= RCS_TOK_BRANCHES)) - -#define RCS_NOSCOL 0x01 /* no terminating semi-colon */ -#define RCS_VOPT 0x02 /* value is optional */ - #define ANNOTATE_NEVER 0 #define ANNOTATE_NOW 1 #define ANNOTATE_LATER 2 -/* opaque parse data */ -struct rcs_pdata { - char *rp_buf; - size_t rp_blen; - char *rp_bufend; - size_t rp_tlen; - - /* pushback token buffer */ - char rp_ptok[128]; - int rp_pttype; /* token type, RCS_TOK_ERR if no token */ - - FILE *rp_file; -}; - -#define RCS_TOKSTR(rfp) ((struct rcs_pdata *)rfp->rf_pdata)->rp_buf -#define RCS_TOKLEN(rfp) ((struct rcs_pdata *)rfp->rf_pdata)->rp_tlen - /* invalid characters in RCS symbol names */ static const char rcs_sym_invch[] = RCS_SYM_INVALCHAR; @@ -184,59 +135,15 @@ struct rcs_kw rcs_expkw[] = { #define NB_COMTYPES (sizeof(rcs_comments)/sizeof(rcs_comments[0])) -static struct rcs_key { - char rk_str[16]; - int rk_id; - int rk_val; - int rk_flags; -} rcs_keys[] = { - { "access", RCS_TOK_ACCESS, RCS_TOK_ID, RCS_VOPT }, - { "author", RCS_TOK_AUTHOR, RCS_TOK_ID, 0 }, - { "branch", RCS_TOK_BRANCH, RCS_TOK_NUM, RCS_VOPT }, - { "branches", RCS_TOK_BRANCHES, RCS_TOK_NUM, RCS_VOPT }, - { "comment", RCS_TOK_COMMENT, RCS_TOK_STRING, RCS_VOPT }, - { "date", RCS_TOK_DATE, RCS_TOK_NUM, 0 }, - { "desc", RCS_TOK_DESC, RCS_TOK_STRING, RCS_NOSCOL }, - { "expand", RCS_TOK_EXPAND, RCS_TOK_STRING, RCS_VOPT }, - { "head", RCS_TOK_HEAD, RCS_TOK_NUM, RCS_VOPT }, - { "locks", RCS_TOK_LOCKS, RCS_TOK_ID, 0 }, - { "log", RCS_TOK_LOG, RCS_TOK_STRING, RCS_NOSCOL }, - { "next", RCS_TOK_NEXT, RCS_TOK_NUM, RCS_VOPT }, - { "state", RCS_TOK_STATE, RCS_TOK_ID, RCS_VOPT }, - { "strict", RCS_TOK_STRICT, 0, 0, }, - { "symbols", RCS_TOK_SYMBOLS, 0, 0 }, - { "text", RCS_TOK_TEXT, RCS_TOK_STRING, RCS_NOSCOL }, -}; - -#define RCS_NKEYS (sizeof(rcs_keys)/sizeof(rcs_keys[0])) - static RCSNUM *rcs_get_revision(const char *, RCSFILE *); int rcs_patch_lines(struct rcs_lines *, struct rcs_lines *, struct rcs_line **, struct rcs_delta *); -static void rcs_parse_init(RCSFILE *); -static int rcs_parse_admin(RCSFILE *); -static int rcs_parse_delta(RCSFILE *); -static void rcs_parse_deltas(RCSFILE *, RCSNUM *); -static int rcs_parse_deltatext(RCSFILE *); -static void rcs_parse_deltatexts(RCSFILE *, RCSNUM *); -static void rcs_parse_desc(RCSFILE *); - -static int rcs_parse_access(RCSFILE *); -static int rcs_parse_symbols(RCSFILE *); -static int rcs_parse_locks(RCSFILE *); -static int rcs_parse_branches(RCSFILE *, struct rcs_delta *); static void rcs_freedelta(struct rcs_delta *); -static void rcs_freepdata(struct rcs_pdata *); -static int rcs_gettok(RCSFILE *); -static int rcs_pushtok(RCSFILE *, const char *, int); -static void rcs_growbuf(RCSFILE *); static void rcs_strprint(const u_char *, size_t, FILE *); static void rcs_kwexp_line(char *, struct rcs_delta *, struct rcs_lines *, struct rcs_line *, int mode); -static int rcs_ignore_keys = 0; - RCSFILE * rcs_open(const char *path, int fd, int flags, ...) { @@ -277,8 +184,10 @@ rcs_open(const char *path, int fd, int flags, ...) TAILQ_INIT(&(rfp->rf_symbols)); TAILQ_INIT(&(rfp->rf_locks)); - if (!(rfp->rf_flags & RCS_CREATE)) - rcs_parse_init(rfp); + if (!(rfp->rf_flags & RCS_CREATE)) { + if (rcsparse_init(rfp)) + fatal("could not parse admin data"); + } /* fill in rd_locker */ TAILQ_FOREACH(lkr, &(rfp->rf_locks), rl_list) { @@ -352,7 +261,7 @@ rcs_close(RCSFILE *rfp) if (rfp->rf_desc != NULL) xfree(rfp->rf_desc); if (rfp->rf_pdata != NULL) - rcs_freepdata(rfp->rf_pdata); + rcsparse_free(rfp); xfree(rfp); } @@ -384,7 +293,8 @@ rcs_write(RCSFILE *rfp) return; /* Write operations need the whole file parsed */ - rcs_parse_deltatexts(rfp, NULL); + if (rcsparse_deltatexts(rfp, NULL)) + fatal("rcs_write: rcsparse_deltatexts"); if (strlcpy(tmpdir, rfp->rf_path, sizeof(tmpdir)) >= sizeof(tmpdir)) fatal("rcs_write: truncation"); @@ -1425,7 +1335,8 @@ rcs_findrev(RCSFILE *rfp, RCSNUM *rev) (!isbrev && rcsnum_cmp(rdp->rd_num, rev, 0) == -1) || ((isbrev && rdp->rd_num->rn_len < 4) || (isbrev && rcsnum_differ(rev, rdp->rd_num)))) { - rcs_parse_deltas(rfp, rev); + if (rcsparse_deltas(rfp, rev)) + fatal("error parsing deltas"); } TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) { @@ -1528,711 +1439,6 @@ rcs_kflag_get(const char *flags) return (fl); } -/* rcs_parse_deltas() - * - * Parse deltas. If <rev> is not NULL, parse only as far as that - * revision. If <rev> is NULL, parse all deltas. - */ -static void -rcs_parse_deltas(RCSFILE *rfp, RCSNUM *rev) -{ - int ret; - struct rcs_delta *enddelta; - - if ((rfp->rf_flags & PARSED_DELTAS) || (rfp->rf_flags & RCS_CREATE)) - return; - - for (;;) { - ret = rcs_parse_delta(rfp); - if (rev != NULL) { - enddelta = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist); - if (rcsnum_cmp(enddelta->rd_num, rev, 0) == 0) - break; - } - - if (ret == 0) { - rfp->rf_flags |= PARSED_DELTAS; - break; - } - else if (ret == -1) - fatal("error parsing deltas"); - } -} - -/* rcs_parse_deltatexts() - * - * Parse deltatexts. If <rev> is not NULL, parse only as far as that - * revision. If <rev> is NULL, parse everything. - */ -static void -rcs_parse_deltatexts(RCSFILE *rfp, RCSNUM *rev) -{ - int ret; - struct rcs_delta *rdp; - - if ((rfp->rf_flags & PARSED_DELTATEXTS) || - (rfp->rf_flags & RCS_CREATE)) - return; - - if (!(rfp->rf_flags & PARSED_DESC)) - rcs_parse_desc(rfp); - for (;;) { - if (rev != NULL) { - rdp = rcs_findrev(rfp, rev); - if (rdp->rd_text != NULL) - break; - else - ret = rcs_parse_deltatext(rfp); - } else - ret = rcs_parse_deltatext(rfp); - if (ret == 0) { - rfp->rf_flags |= PARSED_DELTATEXTS; - break; - } - else if (ret == -1) - fatal("problem parsing deltatexts"); - } -} - -/* rcs_parse_desc() - * - * Parse RCS description. - */ -static void -rcs_parse_desc(RCSFILE *rfp) -{ - int ret = 0; - - if ((rfp->rf_flags & PARSED_DESC) || (rfp->rf_flags & RCS_CREATE)) - return; - if (!(rfp->rf_flags & PARSED_DELTAS)) - rcs_parse_deltas(rfp, NULL); - /* do parsing */ - ret = rcs_gettok(rfp); - if (ret != RCS_TOK_DESC) - fatal("token `%s' found where RCS desc expected", - RCS_TOKSTR(rfp)); - - ret = rcs_gettok(rfp); - if (ret != RCS_TOK_STRING) - fatal("token `%s' found where RCS desc expected", - RCS_TOKSTR(rfp)); - - rfp->rf_desc = xstrdup(RCS_TOKSTR(rfp)); - rfp->rf_flags |= PARSED_DESC; -} - -/* - * rcs_parse_init() - * - * Initial parsing of file <path>, which is in the RCS format. - * Just does admin section. - */ -static void -rcs_parse_init(RCSFILE *rfp) -{ - struct rcs_pdata *pdp; - - if (rfp->rf_flags & RCS_PARSED) - return; - - pdp = xcalloc(1, sizeof(*pdp)); - - pdp->rp_pttype = RCS_TOK_ERR; - - if ((pdp->rp_file = fdopen(rfp->rf_fd, "r")) == NULL) - fatal("fdopen: `%s'", rfp->rf_path); - - pdp->rp_buf = xmalloc((size_t)RCS_BUFSIZE); - pdp->rp_blen = RCS_BUFSIZE; - pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1; - - /* ditch the strict lock */ - rfp->rf_flags &= ~RCS_SLOCK; - rfp->rf_pdata = pdp; - - if (rcs_parse_admin(rfp) < 0) { - rcs_freepdata(pdp); - fatal("could not parse admin data"); - } - - if (rfp->rf_flags & RCS_PARSE_FULLY) { - rcs_parse_deltatexts(rfp, NULL); - (void)close(rfp->rf_fd); - rfp->rf_fd = -1; - } - - rfp->rf_flags |= RCS_SYNCED; -} - -/* - * rcs_parse_admin() - * - * Parse the administrative portion of an RCS file. - * Returns the type of the first token found after the admin section on - * success, or -1 on failure. - */ -static int -rcs_parse_admin(RCSFILE *rfp) -{ - u_int i; - int tok, ntok, hmask; - struct rcs_key *rk; - - rfp->rf_head = NULL; - rfp->rf_branch = NULL; - - /* hmask is a mask of the headers already encountered */ - hmask = 0; - for (;;) { - tok = rcs_gettok(rfp); - if (tok == RCS_TOK_ERR) { - cvs_log(LP_ERR, "parse error in RCS admin section"); - goto fail; - } else if (tok == RCS_TOK_NUM || tok == RCS_TOK_DESC) { - /* - * Assume this is the start of the first delta or - * that we are dealing with an empty RCS file and - * we just found the description. - */ - if (!(hmask & (1 << RCS_TOK_HEAD))) { - cvs_log(LP_ERR, "head missing"); - goto fail; - } - rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok); - return (tok); - } - - rk = NULL; - for (i = 0; i < RCS_NKEYS; i++) - if (rcs_keys[i].rk_id == tok) - rk = &(rcs_keys[i]); - - if (hmask & (1 << tok)) { - cvs_log(LP_ERR, "duplicate RCS key"); - goto fail; - } - hmask |= (1 << tok); - - switch (tok) { - case RCS_TOK_HEAD: - case RCS_TOK_BRANCH: - case RCS_TOK_COMMENT: - case RCS_TOK_EXPAND: - ntok = rcs_gettok(rfp); - if (ntok == RCS_TOK_SCOLON) - break; - if (ntok != rk->rk_val) { - cvs_log(LP_ERR, - "invalid value type for RCS key `%s'", - rk->rk_str); - } - - if (tok == RCS_TOK_HEAD) { - if (rfp->rf_head == NULL) - rfp->rf_head = rcsnum_alloc(); - if (RCS_TOKSTR(rfp)[0] == '\0' || - rcsnum_aton(RCS_TOKSTR(rfp), NULL, - rfp->rf_head) < 0) { - rcsnum_free(rfp->rf_head); - rfp->rf_head = NULL; - } - } else if (tok == RCS_TOK_BRANCH) { - if (rfp->rf_branch == NULL) - rfp->rf_branch = rcsnum_alloc(); - if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, - rfp->rf_branch) < 0) - goto fail; - } else if (tok == RCS_TOK_COMMENT) { - rfp->rf_comment = xstrdup(RCS_TOKSTR(rfp)); - } else if (tok == RCS_TOK_EXPAND) { - rfp->rf_expand = xstrdup(RCS_TOKSTR(rfp)); - } - - /* now get the expected semi-colon */ - ntok = rcs_gettok(rfp); - if (ntok != RCS_TOK_SCOLON) { - cvs_log(LP_ERR, - "missing semi-colon after RCS `%s' key", - rk->rk_str); - goto fail; - } - break; - case RCS_TOK_ACCESS: - if (rcs_parse_access(rfp) < 0) - goto fail; - break; - case RCS_TOK_SYMBOLS: - rcs_ignore_keys = 1; - if (rcs_parse_symbols(rfp) < 0) - goto fail; - break; - case RCS_TOK_LOCKS: - if (rcs_parse_locks(rfp) < 0) - goto fail; - break; - default: - cvs_log(LP_ERR, - "unexpected token `%s' in RCS admin section", - RCS_TOKSTR(rfp)); - goto fail; - } - - rcs_ignore_keys = 0; - - } - -fail: - return (-1); -} - -/* - * rcs_parse_delta() - * - * Parse an RCS delta section and allocate the structure to store that delta's - * information in the <rfp> delta list. - * Returns 1 if the section was parsed OK, 0 if it is the last delta, and - * -1 on error. - */ -static int -rcs_parse_delta(RCSFILE *rfp) -{ - int ret, tok, ntok, hmask; - u_int i; - char *tokstr; - RCSNUM *datenum; - struct rcs_delta *rdp; - struct rcs_key *rk; - - tok = rcs_gettok(rfp); - if (tok == RCS_TOK_DESC) { - rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok); - return (0); - } else if (tok != RCS_TOK_NUM) { - cvs_log(LP_ERR, "unexpected token `%s' at start of delta", - RCS_TOKSTR(rfp)); - return (-1); - } - - rdp = xcalloc(1, sizeof(*rdp)); - - rdp->rd_num = rcsnum_alloc(); - rdp->rd_next = rcsnum_alloc(); - - TAILQ_INIT(&(rdp->rd_branches)); - - rcsnum_aton(RCS_TOKSTR(rfp), NULL, rdp->rd_num); - - hmask = 0; - ret = 0; - tokstr = NULL; - - for (;;) { - tok = rcs_gettok(rfp); - if (tok == RCS_TOK_ERR) { - cvs_log(LP_ERR, "parse error in RCS delta section"); - rcs_freedelta(rdp); - return (-1); - } else if (tok == RCS_TOK_NUM || tok == RCS_TOK_DESC) { - rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok); - ret = (tok == RCS_TOK_NUM ? 1 : 0); - break; - } - - rk = NULL; - for (i = 0; i < RCS_NKEYS; i++) - if (rcs_keys[i].rk_id == tok) - rk = &(rcs_keys[i]); - - if (hmask & (1 << tok)) { - cvs_log(LP_ERR, "duplicate RCS key"); - rcs_freedelta(rdp); - return (-1); - } - hmask |= (1 << tok); - - switch (tok) { - case RCS_TOK_DATE: - case RCS_TOK_AUTHOR: - case RCS_TOK_STATE: - case RCS_TOK_NEXT: - ntok = rcs_gettok(rfp); - if (ntok == RCS_TOK_SCOLON) { - if (rk->rk_flags & RCS_VOPT) - break; - else { - cvs_log(LP_ERR, "missing mandatory " - "value to RCS key `%s'", - rk->rk_str); - rcs_freedelta(rdp); - return (-1); - } - } - - if (ntok != rk->rk_val) { - cvs_log(LP_ERR, - "invalid value type for RCS key `%s'", - rk->rk_str); - rcs_freedelta(rdp); - return (-1); - } - - if (tokstr != NULL) - xfree(tokstr); - tokstr = xstrdup(RCS_TOKSTR(rfp)); - /* now get the expected semi-colon */ - ntok = rcs_gettok(rfp); - if (ntok != RCS_TOK_SCOLON) { - cvs_log(LP_ERR, - "missing semi-colon after RCS `%s' key", - rk->rk_str); - xfree(tokstr); - rcs_freedelta(rdp); - return (-1); - } - - if (tok == RCS_TOK_DATE) { - if ((datenum = rcsnum_parse(tokstr)) == NULL) { - xfree(tokstr); - rcs_freedelta(rdp); - return (-1); - } - if (datenum->rn_len != 6) { - cvs_log(LP_ERR, - "RCS date specification has %s " - "fields", - (datenum->rn_len > 6) ? "too many" : - "missing"); - xfree(tokstr); - rcs_freedelta(rdp); - rcsnum_free(datenum); - return (-1); - } - rdp->rd_date.tm_year = datenum->rn_id[0]; - if (rdp->rd_date.tm_year >= 1900) - rdp->rd_date.tm_year -= 1900; - rdp->rd_date.tm_mon = datenum->rn_id[1] - 1; - rdp->rd_date.tm_mday = datenum->rn_id[2]; - rdp->rd_date.tm_hour = datenum->rn_id[3]; - rdp->rd_date.tm_min = datenum->rn_id[4]; - rdp->rd_date.tm_sec = datenum->rn_id[5]; - rcsnum_free(datenum); - } else if (tok == RCS_TOK_AUTHOR) { - rdp->rd_author = tokstr; - tokstr = NULL; - } else if (tok == RCS_TOK_STATE) { - rdp->rd_state = tokstr; - tokstr = NULL; - } else if (tok == RCS_TOK_NEXT) { - rcsnum_aton(tokstr, NULL, rdp->rd_next); - } - break; - case RCS_TOK_BRANCHES: - if (rcs_parse_branches(rfp, rdp) < 0) { - rcs_freedelta(rdp); - return (-1); - } - break; - default: - cvs_log(LP_ERR, "unexpected token `%s' in RCS delta", - RCS_TOKSTR(rfp)); - rcs_freedelta(rdp); - return (-1); - } - } - - if (tokstr != NULL) - xfree(tokstr); - - TAILQ_INSERT_TAIL(&(rfp->rf_delta), rdp, rd_list); - rfp->rf_ndelta++; - - return (ret); -} - -/* - * rcs_parse_deltatext() - * - * Parse an RCS delta text section and fill in the log and text field of the - * appropriate delta section. - * Returns 1 if the section was parsed OK, 0 if it is the last delta, and - * -1 on error. - */ -static int -rcs_parse_deltatext(RCSFILE *rfp) -{ - int tok; - RCSNUM *tnum; - struct rcs_delta *rdp; - - tok = rcs_gettok(rfp); - if (tok == RCS_TOK_EOF) - return (0); - - if (tok != RCS_TOK_NUM) { - cvs_log(LP_ERR, - "unexpected token `%s' at start of RCS delta text", - RCS_TOKSTR(rfp)); - return (-1); - } - - tnum = rcsnum_alloc(); - rcsnum_aton(RCS_TOKSTR(rfp), NULL, tnum); - - TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) { - if (rcsnum_cmp(tnum, rdp->rd_num, 0) == 0) - break; - } - rcsnum_free(tnum); - - if (rdp == NULL) { - cvs_log(LP_ERR, "RCS delta text `%s' has no matching delta", - RCS_TOKSTR(rfp)); - return (-1); - } - - tok = rcs_gettok(rfp); - if (tok != RCS_TOK_LOG) { - cvs_log(LP_ERR, "unexpected token `%s' where RCS log expected", - RCS_TOKSTR(rfp)); - return (-1); - } - - tok = rcs_gettok(rfp); - if (tok != RCS_TOK_STRING) { - cvs_log(LP_ERR, "unexpected token `%s' where RCS log expected", - RCS_TOKSTR(rfp)); - return (-1); - } - rdp->rd_log = xstrdup(RCS_TOKSTR(rfp)); - tok = rcs_gettok(rfp); - if (tok != RCS_TOK_TEXT) { - cvs_log(LP_ERR, "unexpected token `%s' where RCS text expected", - RCS_TOKSTR(rfp)); - return (-1); - } - - tok = rcs_gettok(rfp); - if (tok != RCS_TOK_STRING) { - cvs_log(LP_ERR, "unexpected token `%s' where RCS text expected", - RCS_TOKSTR(rfp)); - return (-1); - } - - if (RCS_TOKLEN(rfp) == 0) { - rdp->rd_text = xmalloc(1); - rdp->rd_text[0] = '\0'; - rdp->rd_tlen = 0; - } else { - rdp->rd_text = xmalloc(RCS_TOKLEN(rfp)); - memcpy(rdp->rd_text, RCS_TOKSTR(rfp), RCS_TOKLEN(rfp)); - rdp->rd_tlen = RCS_TOKLEN(rfp); - } - - return (1); -} - -/* - * rcs_parse_access() - * - * Parse the access list given as value to the `access' keyword. - * Returns 0 on success, or -1 on failure. - */ -static int -rcs_parse_access(RCSFILE *rfp) -{ - int type; - - while ((type = rcs_gettok(rfp)) != RCS_TOK_SCOLON) { - if (type != RCS_TOK_ID) { - cvs_log(LP_ERR, "unexpected token `%s' in access list", - RCS_TOKSTR(rfp)); - return (-1); - } - - if (rcs_access_add(rfp, RCS_TOKSTR(rfp)) < 0) - return (-1); - } - - return (0); -} - -/* - * rcs_parse_symbols() - * - * Parse the symbol list given as value to the `symbols' keyword. - * Returns 0 on success, or -1 on failure. - */ -static int -rcs_parse_symbols(RCSFILE *rfp) -{ - int type; - struct rcs_sym *symp; - - for (;;) { - type = rcs_gettok(rfp); - if (type == RCS_TOK_SCOLON) - break; - - if (type != RCS_TOK_ID) { - cvs_log(LP_ERR, "unexpected token `%s' in symbol list", - RCS_TOKSTR(rfp)); - return (-1); - } - - symp = xmalloc(sizeof(*symp)); - symp->rs_name = xstrdup(RCS_TOKSTR(rfp)); - symp->rs_num = rcsnum_alloc(); - - type = rcs_gettok(rfp); - if (type != RCS_TOK_COLON) { - cvs_log(LP_ERR, "unexpected token `%s' in symbol list", - RCS_TOKSTR(rfp)); - rcsnum_free(symp->rs_num); - xfree(symp->rs_name); - xfree(symp); - return (-1); - } - - type = rcs_gettok(rfp); - if (type != RCS_TOK_NUM) { - cvs_log(LP_ERR, "unexpected token `%s' in symbol list", - RCS_TOKSTR(rfp)); - rcsnum_free(symp->rs_num); - xfree(symp->rs_name); - xfree(symp); - return (-1); - } - - if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, symp->rs_num) < 0) { - cvs_log(LP_ERR, "failed to parse RCS NUM `%s'", - RCS_TOKSTR(rfp)); - rcsnum_free(symp->rs_num); - xfree(symp->rs_name); - xfree(symp); - return (-1); - } - - TAILQ_INSERT_TAIL(&(rfp->rf_symbols), symp, rs_list); - } - - return (0); -} - -/* - * rcs_parse_locks() - * - * Parse the lock list given as value to the `locks' keyword. - * Returns 0 on success, or -1 on failure. - */ -static int -rcs_parse_locks(RCSFILE *rfp) -{ - int type; - struct rcs_lock *lkp; - - for (;;) { - type = rcs_gettok(rfp); - if (type == RCS_TOK_SCOLON) - break; - - if (type != RCS_TOK_ID) { - cvs_log(LP_ERR, "unexpected token `%s' in lock list", - RCS_TOKSTR(rfp)); - return (-1); - } - - lkp = xmalloc(sizeof(*lkp)); - lkp->rl_name = xstrdup(RCS_TOKSTR(rfp)); - lkp->rl_num = rcsnum_alloc(); - - type = rcs_gettok(rfp); - if (type != RCS_TOK_COLON) { - cvs_log(LP_ERR, "unexpected token `%s' in symbol list", - RCS_TOKSTR(rfp)); - rcsnum_free(lkp->rl_num); - xfree(lkp->rl_name); - xfree(lkp); - return (-1); - } - - type = rcs_gettok(rfp); - if (type != RCS_TOK_NUM) { - cvs_log(LP_ERR, "unexpected token `%s' in symbol list", - RCS_TOKSTR(rfp)); - rcsnum_free(lkp->rl_num); - xfree(lkp->rl_name); - xfree(lkp); - return (-1); - } - - if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, lkp->rl_num) < 0) { - cvs_log(LP_ERR, "failed to parse RCS NUM `%s'", - RCS_TOKSTR(rfp)); - rcsnum_free(lkp->rl_num); - xfree(lkp->rl_name); - xfree(lkp); - return (-1); - } - - TAILQ_INSERT_HEAD(&(rfp->rf_locks), lkp, rl_list); - } - - /* check if we have a `strict' */ - type = rcs_gettok(rfp); - if (type != RCS_TOK_STRICT) { - rcs_pushtok(rfp, RCS_TOKSTR(rfp), type); - } else { - rfp->rf_flags |= RCS_SLOCK; - - type = rcs_gettok(rfp); - if (type != RCS_TOK_SCOLON) { - cvs_log(LP_ERR, - "missing semi-colon after `strict' keyword"); - return (-1); - } - } - - return (0); -} - -/* - * rcs_parse_branches() - * - * Parse the list of branches following a `branches' keyword in a delta. - * Returns 0 on success, or -1 on failure. - */ -static int -rcs_parse_branches(RCSFILE *rfp, struct rcs_delta *rdp) -{ - int type; - struct rcs_branch *brp; - - for (;;) { - type = rcs_gettok(rfp); - if (type == RCS_TOK_SCOLON) - break; - - if (type != RCS_TOK_NUM) { - cvs_log(LP_ERR, - "unexpected token `%s' in list of branches", - RCS_TOKSTR(rfp)); - return (-1); - } - - brp = xmalloc(sizeof(*brp)); - brp->rb_num = rcsnum_parse(RCS_TOKSTR(rfp)); - if (brp->rb_num == NULL) { - xfree(brp); - return (-1); - } - - TAILQ_INSERT_TAIL(&(rdp->rd_branches), brp, rb_list); - } - - return (0); -} - /* * rcs_freedelta() * @@ -2269,190 +1475,6 @@ rcs_freedelta(struct rcs_delta *rdp) } /* - * rcs_freepdata() - * - * Free the contents of the parser data structure. - */ -static void -rcs_freepdata(struct rcs_pdata *pd) -{ - if (pd->rp_file != NULL) - (void)fclose(pd->rp_file); - if (pd->rp_buf != NULL) - xfree(pd->rp_buf); - xfree(pd); -} - -/* - * rcs_gettok() - * - * Get the next RCS token from the string <str>. - */ -static int -rcs_gettok(RCSFILE *rfp) -{ - u_int i; - int ch, last, type; - size_t len; - char *bp; - struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata; - - type = RCS_TOK_ERR; - bp = pdp->rp_buf; - pdp->rp_tlen = 0; - *bp = '\0'; - - if (pdp->rp_pttype != RCS_TOK_ERR) { - type = pdp->rp_pttype; - if (strlcpy(pdp->rp_buf, pdp->rp_ptok, pdp->rp_blen) >= - pdp->rp_blen) - fatal("rcs_gettok: strlcpy"); - pdp->rp_pttype = RCS_TOK_ERR; - return (type); - } - - /* skip leading whitespace */ - /* XXX we must skip backspace too for compatibility, should we? */ - do { - ch = getc(pdp->rp_file); - } while (isspace(ch)); - - if (ch == EOF) { - type = RCS_TOK_EOF; - } else if (ch == ';') { - type = RCS_TOK_SCOLON; - } else if (ch == ':') { - type = RCS_TOK_COLON; - } else if (isalpha(ch)) { - type = RCS_TOK_ID; - *(bp++) = ch; - for (;;) { - ch = getc(pdp->rp_file); - if (ch == EOF) { - type = RCS_TOK_EOF; - break; - } else if (!isgraph(ch) || - strchr(rcs_sym_invch, ch) != NULL) { - ungetc(ch, pdp->rp_file); - break; - } - *(bp++) = ch; - pdp->rp_tlen++; - if (bp == pdp->rp_bufend - 1) { - len = bp - pdp->rp_buf; - rcs_growbuf(rfp); - bp = pdp->rp_buf + len; - } - } - *bp = '\0'; - - if (type != RCS_TOK_ERR && rcs_ignore_keys != 1) { - for (i = 0; i < RCS_NKEYS; i++) { - if (strcmp(rcs_keys[i].rk_str, - pdp->rp_buf) == 0) { - type = rcs_keys[i].rk_id; - break; - } - } - } - } else if (ch == '@') { - /* we have a string */ - type = RCS_TOK_STRING; - for (;;) { - ch = getc(pdp->rp_file); - if (ch == EOF) { - type = RCS_TOK_EOF; - break; - } else if (ch == '@') { - ch = getc(pdp->rp_file); - if (ch != '@') { - ungetc(ch, pdp->rp_file); - break; - } - } - - *(bp++) = ch; - pdp->rp_tlen++; - if (bp == pdp->rp_bufend - 1) { - len = bp - pdp->rp_buf; - rcs_growbuf(rfp); - bp = pdp->rp_buf + len; - } - } - - *bp = '\0'; - } else if (isdigit(ch)) { - *(bp++) = ch; - last = ch; - type = RCS_TOK_NUM; - - for (;;) { - ch = getc(pdp->rp_file); - if (ch == EOF) { - type = RCS_TOK_EOF; - break; - } - if (bp == pdp->rp_bufend) - break; - if (!isdigit(ch) && ch != '.') { - ungetc(ch, pdp->rp_file); - break; - } - - if (last == '.' && ch == '.') { - type = RCS_TOK_ERR; - break; - } - last = ch; - *(bp++) = ch; - pdp->rp_tlen++; - } - *bp = '\0'; - } - - return (type); -} - -/* - * rcs_pushtok() - * - * Push a token back in the parser's token buffer. - */ -static int -rcs_pushtok(RCSFILE *rfp, const char *tok, int type) -{ - struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata; - - if (pdp->rp_pttype != RCS_TOK_ERR) - return (-1); - - pdp->rp_pttype = type; - if (strlcpy(pdp->rp_ptok, tok, sizeof(pdp->rp_ptok)) >= - sizeof(pdp->rp_ptok)) - fatal("rcs_pushtok: strlcpy"); - return (0); -} - - -/* - * rcs_growbuf() - * - * Attempt to grow the internal parse buffer for the RCS file <rf> by - * RCS_BUFEXTSIZE. - * In case of failure, the original buffer is left unmodified. - */ -static void -rcs_growbuf(RCSFILE *rf) -{ - struct rcs_pdata *pdp = (struct rcs_pdata *)rf->rf_pdata; - - pdp->rp_buf = xrealloc(pdp->rp_buf, 1, - pdp->rp_blen + RCS_BUFEXTSIZE); - pdp->rp_blen += RCS_BUFEXTSIZE; - pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1; -} - -/* * rcs_strprint() * * Output an RCS string <str> of size <slen> to the stream <stream>. Any @@ -2495,7 +1517,8 @@ rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, BUF *bp) struct rcs_delta *rdp; /* Write operations require full parsing */ - rcs_parse_deltatexts(rfp, NULL); + if (rcsparse_deltatexts(rfp, NULL)) + return (-1); if ((rdp = rcs_findrev(rfp, rev)) == NULL) return (-1); @@ -2727,7 +1750,8 @@ rcs_rev_getlines(RCSFILE *rfp, RCSNUM *frev, struct rcs_line ***alines) fatal("rcs_rev_getlines: no HEAD revision"); tnum = frev; - rcs_parse_deltatexts(rfp, hrdp->rd_num); + if (rcsparse_deltatexts(rfp, hrdp->rd_num)) + fatal("rcs_rev_getlines: rcsparse_deltatexts"); /* revision on branch, get the branch root */ nextroot = 2; @@ -2792,7 +1816,8 @@ again: } if (rdp->rd_tlen == 0) { - rcs_parse_deltatexts(rfp, rdp->rd_num); + if (rcsparse_deltatexts(rfp, rdp->rd_num)) + fatal("rcs_rev_getlines: rcsparse_deltatexts"); if (rdp->rd_tlen == 0) { if (!rcsnum_differ(rdp->rd_num, bnum)) break; @@ -2962,7 +1987,9 @@ rcs_annotate_getlines(RCSFILE *rfp, RCSNUM *frev, struct rcs_line ***alines) } if (rdp->rd_tlen == 0) { - rcs_parse_deltatexts(rfp, rdp->rd_num); + if (rcsparse_deltatexts(rfp, rdp->rd_num)) + fatal("rcs_annotate_getlines: " + "rcsparse_deltatexts"); if (rdp->rd_tlen == 0) { if (!rcsnum_differ(rdp->rd_num, bnum)) break; diff --git a/usr.bin/cvs/rcsparse.c b/usr.bin/cvs/rcsparse.c new file mode 100644 index 00000000000..7b3de255e68 --- /dev/null +++ b/usr.bin/cvs/rcsparse.c @@ -0,0 +1,1262 @@ +/* $OpenBSD: rcsparse.c,v 1.1 2010/10/15 08:44:12 tobias Exp $ */ +/* + * Copyright (c) 2010 Tobias Stoeckmann <tobias@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/queue.h> + +#include <ctype.h> +#include <err.h> +#include <pwd.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "log.h" +#include "rcs.h" +#include "rcsparse.h" +#include "xmalloc.h" + +#define RCS_BUFSIZE 16384 +#define RCS_BUFEXTSIZE 8192 + +/* RCS token types */ +#define RCS_TOK_HEAD (1 << 0) +#define RCS_TOK_BRANCH (1 << 1) +#define RCS_TOK_ACCESS (1 << 2) +#define RCS_TOK_SYMBOLS (1 << 3) +#define RCS_TOK_LOCKS (1 << 4) +#define RCS_TOK_STRICT (1 << 5) +#define RCS_TOK_COMMENT (1 << 6) +#define RCS_TOK_COMMITID (1 << 7) +#define RCS_TOK_EXPAND (1 << 8) +#define RCS_TOK_DESC (1 << 9) +#define RCS_TOK_DATE (1 << 10) +#define RCS_TOK_AUTHOR (1 << 11) +#define RCS_TOK_STATE (1 << 12) +#define RCS_TOK_BRANCHES (1 << 13) +#define RCS_TOK_NEXT (1 << 14) +#define RCS_TOK_LOG (1 << 15) +#define RCS_TOK_TEXT (1 << 16) +#define RCS_TOK_COLON (1 << 17) +#define RCS_TOK_COMMA (1 << 18) +#define RCS_TOK_SCOLON (1 << 19) + +#define RCS_TYPE_STRING (1 << 20) +#define RCS_TYPE_NUMBER (1 << 21) +#define RCS_TYPE_BRANCH (1 << 22) +#define RCS_TYPE_REVISION (1 << 23) +#define RCS_TYPE_LOGIN (1 << 24) +#define RCS_TYPE_STATE (1 << 25) +#define RCS_TYPE_SYMBOL (1 << 26) +#define RCS_TYPE_DATE (1 << 27) +#define RCS_TYPE_KEYWORD (1 << 28) +#define RCS_TYPE_COMMITID (1 << 29) + +#define MANDATORY 0 +#define OPTIONAL 1 + +/* opaque parse data */ +struct rcs_pdata { + char *rp_buf; + size_t rp_blen; + char *rp_bufend; + size_t rp_tlen; + + struct rcs_delta *rp_delta; + FILE *rp_file; + int rp_lineno; + int rp_msglineno; + int rp_token; + + union { + RCSNUM *rev; + char *str; + struct tm date; + } rp_value; +}; + +struct rcs_keyword { + const char *k_name; + int k_val; +}; + +struct rcs_section { + int token; + int (*parse)(RCSFILE *, struct rcs_pdata *); + int opt; +}; + +/* this has to be sorted always */ +static const struct rcs_keyword keywords[] = { + { "access", RCS_TOK_ACCESS}, + { "author", RCS_TOK_AUTHOR}, + { "branch", RCS_TOK_BRANCH}, + { "branches", RCS_TOK_BRANCHES}, + { "comment", RCS_TOK_COMMENT}, + { "date", RCS_TOK_DATE}, + { "desc", RCS_TOK_DESC}, + { "expand", RCS_TOK_EXPAND}, + { "head", RCS_TOK_HEAD}, + { "locks", RCS_TOK_LOCKS}, + { "log", RCS_TOK_LOG}, + { "next", RCS_TOK_NEXT}, + { "state", RCS_TOK_STATE}, + { "strict", RCS_TOK_STRICT}, + { "symbols", RCS_TOK_SYMBOLS}, + { "text", RCS_TOK_TEXT} +}; + +/* parser functions specified in rcs_section structs */ +static int rcsparse_head(RCSFILE *, struct rcs_pdata *); +static int rcsparse_branch(RCSFILE *, struct rcs_pdata *); +static int rcsparse_access(RCSFILE *, struct rcs_pdata *); +static int rcsparse_symbols(RCSFILE *, struct rcs_pdata *); +static int rcsparse_locks(RCSFILE *, struct rcs_pdata *); +static int rcsparse_strict(RCSFILE *, struct rcs_pdata *); +static int rcsparse_comment(RCSFILE *, struct rcs_pdata *); +static int rcsparse_commitid(RCSFILE *, struct rcs_pdata *); +static int rcsparse_expand(RCSFILE *, struct rcs_pdata *); +static int rcsparse_deltarevision(RCSFILE *, struct rcs_pdata *); +static int rcsparse_date(RCSFILE *, struct rcs_pdata *); +static int rcsparse_author(RCSFILE *, struct rcs_pdata *); +static int rcsparse_state(RCSFILE *, struct rcs_pdata *); +static int rcsparse_branches(RCSFILE *, struct rcs_pdata *); +static int rcsparse_next(RCSFILE *, struct rcs_pdata *); +static int rcsparse_textrevision(RCSFILE *, struct rcs_pdata *); +static int rcsparse_log(RCSFILE *, struct rcs_pdata *); +static int rcsparse_text(RCSFILE *, struct rcs_pdata *); + +static int rcsparse_delta(RCSFILE *); +static int rcsparse_deltatext(RCSFILE *); +static int rcsparse_desc(RCSFILE *); + +static int kw_cmp(const void *, const void *); +static int rcsparse(RCSFILE *, struct rcs_section *); +static void rcsparse_growbuf(RCSFILE *); +static int rcsparse_string(RCSFILE *, int); +static int rcsparse_token(RCSFILE *, int); +static void rcsparse_warnx(RCSFILE *, char *, ...); +static int valid_login(char *); + +/* + * head [REVISION]; + * [branch BRANCH]; + * access [LOGIN ...]; + * symbols [SYMBOL:REVISION ...]; + * locks [LOGIN:REVISION ...]; + * [strict;] + * [comment [@[...]@];] + * [expand [@[...]@];] + */ +static struct rcs_section sec_admin[] = { + { RCS_TOK_HEAD, rcsparse_head, MANDATORY }, + { RCS_TOK_BRANCH, rcsparse_branch, OPTIONAL }, + { RCS_TOK_ACCESS, rcsparse_access, MANDATORY }, + { RCS_TOK_SYMBOLS, rcsparse_symbols, MANDATORY }, + { RCS_TOK_LOCKS, rcsparse_locks, MANDATORY }, + { RCS_TOK_STRICT, rcsparse_strict, OPTIONAL }, + { RCS_TOK_COMMENT, rcsparse_comment, OPTIONAL }, + { RCS_TOK_EXPAND, rcsparse_expand, OPTIONAL }, + { 0, NULL, 0 } +}; + +/* + * REVISION + * date [YY]YY.MM.DD.HH.MM.SS; + * author LOGIN; + * state STATE; + * branches [REVISION ...]; + * next [REVISION]; + * [commitid ID;] + */ +static struct rcs_section sec_delta[] = { + { RCS_TYPE_REVISION, rcsparse_deltarevision, MANDATORY }, + { RCS_TOK_DATE, rcsparse_date, MANDATORY }, + { RCS_TOK_AUTHOR, rcsparse_author, MANDATORY }, + { RCS_TOK_STATE, rcsparse_state, MANDATORY }, + { RCS_TOK_BRANCHES, rcsparse_branches, MANDATORY }, + { RCS_TOK_NEXT, rcsparse_next, MANDATORY }, + { RCS_TOK_COMMITID, rcsparse_commitid, OPTIONAL }, + { 0, NULL, 0 } +}; + +/* + * REVISION + * log @[...]@ + * text @[...]@ + */ +static struct rcs_section sec_deltatext[] = { + { RCS_TYPE_REVISION, rcsparse_textrevision, MANDATORY }, + { RCS_TOK_LOG, rcsparse_log, MANDATORY }, + { RCS_TOK_TEXT, rcsparse_text, MANDATORY }, + { 0, NULL, 0 } +}; + +/* + * rcsparse_init() + * + * Initializes the parsing data structure and parses the admin section of + * RCS file <rfp>. + * + * Returns 0 on success or 1 on failure. + */ +int +rcsparse_init(RCSFILE *rfp) +{ + struct rcs_pdata *pdp; + + if (rfp->rf_flags & RCS_PARSED) + return (0); + + pdp = xmalloc(sizeof(*pdp)); + pdp->rp_buf = xmalloc(RCS_BUFSIZE); + pdp->rp_blen = RCS_BUFSIZE; + pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1; + pdp->rp_token = -1; + pdp->rp_lineno = 1; + pdp->rp_msglineno = 1; + if ((pdp->rp_file = fdopen(rfp->rf_fd, "r")) == NULL) { + xfree(pdp); + return (1); + } + /* ditch the strict lock */ + rfp->rf_flags &= ~RCS_SLOCK; + rfp->rf_pdata = pdp; + + if (rcsparse(rfp, sec_admin)) { + rcsparse_free(rfp); + return (1); + } + + if ((rfp->rf_flags & RCS_PARSE_FULLY) && + rcsparse_deltatexts(rfp, NULL)) { + rcsparse_free(rfp); + return (1); + } + + rfp->rf_flags |= RCS_SYNCED; + return (0); +} + +/* + * rcsparse_deltas() + * + * Parse deltas. If <rev> is not NULL, parse only as far as that + * revision. If <rev> is NULL, parse all deltas. + * + * Returns 0 on success or 1 on error. + */ +int +rcsparse_deltas(RCSFILE *rfp, RCSNUM *rev) +{ + int ret; + struct rcs_delta *enddelta; + + if ((rfp->rf_flags & PARSED_DELTAS) || (rfp->rf_flags & RCS_CREATE)) + return (0); + + for (;;) { + ret = rcsparse_delta(rfp); + if (rev != NULL) { + enddelta = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist); + if (enddelta == NULL) + return (1); + + if (rcsnum_cmp(enddelta->rd_num, rev, 0) == 0) + break; + } + + if (ret == 0) { + rfp->rf_flags |= PARSED_DELTAS; + break; + } + else if (ret == -1) + return (1); + } + + return (0); +} + +/* + * rcsparse_deltatexts() + * + * Parse deltatexts. If <rev> is not NULL, parse only as far as that + * revision. If <rev> is NULL, parse everything. + * + * Returns 0 on success or 1 on error. + */ +int +rcsparse_deltatexts(RCSFILE *rfp, RCSNUM *rev) +{ + int ret; + struct rcs_delta *rdp; + + if ((rfp->rf_flags & PARSED_DELTATEXTS) || + (rfp->rf_flags & RCS_CREATE)) + return (0); + + if (!(rfp->rf_flags & PARSED_DESC)) + if (rcsparse_desc(rfp)) + return (1); + for (;;) { + if (rev != NULL) { + rdp = rcs_findrev(rfp, rev); + if (rdp->rd_text != NULL) + break; + else + ret = rcsparse_deltatext(rfp); + } else + ret = rcsparse_deltatext(rfp); + if (ret == 0) { + rfp->rf_flags |= PARSED_DELTATEXTS; + break; + } + else if (ret == -1) + return (1); + } + + return (0); +} + +/* + * rcsparse_free() + * + * Free the contents of the <rfp>'s parser data structure. + */ +void +rcsparse_free(RCSFILE *rfp) +{ + struct rcs_pdata *pdp; + + pdp = rfp->rf_pdata; + + if (pdp->rp_file != NULL) + (void)fclose(pdp->rp_file); + if (pdp->rp_buf != NULL) + xfree(pdp->rp_buf); + if (pdp->rp_token == RCS_TYPE_REVISION) + rcsnum_free(pdp->rp_value.rev); + xfree(pdp); +} + +/* + * rcsparse_desc() + * + * Parse desc of the RCS file <rfp>. By calling rcsparse_desc, all deltas + * will be parsed in order to proceed the reading cursor to the desc keyword. + * + * desc @[...]@; + * + * Returns 0 on success or 1 on error. + */ +static int +rcsparse_desc(RCSFILE *rfp) +{ + struct rcs_pdata *pdp; + + if (rfp->rf_flags & PARSED_DESC) + return (0); + + if (!(rfp->rf_flags & PARSED_DELTAS) && rcsparse_deltas(rfp, NULL)) + return (1); + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + + if (rcsparse_token(rfp, RCS_TOK_DESC) != RCS_TOK_DESC || + rcsparse_token(rfp, RCS_TYPE_STRING) != RCS_TYPE_STRING) + return (1); + + rfp->rf_desc = pdp->rp_value.str; + rfp->rf_flags |= PARSED_DESC; + + return (0); +} + +/* + * rcsparse_deltarevision() + * + * Called upon reaching a new REVISION entry in the delta section. + * A new rcs_delta structure will be prepared in pdp->rp_delta for further + * parsing. + * + * REVISION + * + * Always returns 0. + */ +static int +rcsparse_deltarevision(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + struct rcs_delta *rdp; + + rdp = xcalloc(1, sizeof(*rdp)); + TAILQ_INIT(&rdp->rd_branches); + rdp->rd_num = pdp->rp_value.rev; + pdp->rp_delta = rdp; + + return (0); +} + +/* + * rcsparse_date() + * + * Parses the specified date of current delta pdp->rp_delta. + * + * date YYYY.MM.DD.HH.MM.SS; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_date(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + if (rcsparse_token(rfp, RCS_TYPE_DATE) != RCS_TYPE_DATE) + return (1); + + pdp->rp_delta->rd_date = pdp->rp_value.date; + + return (rcsparse_token(rfp, RCS_TOK_SCOLON) != RCS_TOK_SCOLON); +} + +/* + * rcsparse_author() + * + * Parses the specified author of current delta pdp->rp_delta. + * + * author LOGIN; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_author(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + if (rcsparse_token(rfp, RCS_TYPE_LOGIN) != RCS_TYPE_LOGIN) + return (1); + + pdp->rp_delta->rd_author = pdp->rp_value.str; + + return (rcsparse_token(rfp, RCS_TOK_SCOLON) != RCS_TOK_SCOLON); +} + +/* + * rcsparse_state() + * + * Parses the specified state of current delta pdp->rp_delta. + * + * state STATE; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_state(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + if (rcsparse_token(rfp, RCS_TYPE_STATE) != RCS_TYPE_STATE) + return (1); + + pdp->rp_delta->rd_state = pdp->rp_value.str; + + return (rcsparse_token(rfp, RCS_TOK_SCOLON) != RCS_TOK_SCOLON); +} + +/* + * rcsparse_branches() + * + * Parses the specified branches of current delta pdp->rp_delta. + * + * branches [REVISION ...]; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_branches(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + struct rcs_branch *rb; + int type; + + while ((type = rcsparse_token(rfp, RCS_TOK_SCOLON|RCS_TYPE_REVISION)) + == RCS_TYPE_REVISION) { + rb = xmalloc(sizeof(*rb)); + rb->rb_num = pdp->rp_value.rev; + TAILQ_INSERT_TAIL(&(pdp->rp_delta->rd_branches), rb, rb_list); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_next() + * + * Parses the specified next revision of current delta pdp->rp_delta. + * + * next [REVISION]; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_next(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + int type; + + type = rcsparse_token(rfp, RCS_TYPE_REVISION|RCS_TOK_SCOLON); + if (type == RCS_TYPE_REVISION) { + pdp->rp_delta->rd_next = pdp->rp_value.rev; + type = rcsparse_token(rfp, RCS_TOK_SCOLON); + } else + pdp->rp_delta->rd_next = rcsnum_alloc(); + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_commitid() + * + * Parses the specified commit id of current delta pdp->rp_delta. The + * commitid keyword is optional and can be omitted. + * + * [commitid ID;] + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_commitid(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + if (rcsparse_token(rfp, RCS_TYPE_COMMITID) != RCS_TYPE_COMMITID) + return (1); + + /* XXX - do something with commitid */ + + return (rcsparse_token(rfp, RCS_TOK_SCOLON) != RCS_TOK_SCOLON); +} + +/* + * rcsparse_textrevision() + * + * Called upon reaching a new REVISION entry in the delta text section. + * pdp->rp_delta will be set to REVISION's delta (created in delta section) + * for further parsing. + * + * REVISION + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_textrevision(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + struct rcs_delta *rdp; + + TAILQ_FOREACH(rdp, &rfp->rf_delta, rd_list) { + if (rcsnum_cmp(rdp->rd_num, pdp->rp_value.rev, 0) == 0) + break; + } + if (rdp == NULL) { + rcsparse_warnx(rfp, "delta for revision \"%s\" not found", + pdp->rp_buf); + rcsnum_free(pdp->rp_value.rev); + return (1); + } + pdp->rp_delta = rdp; + + rcsnum_free(pdp->rp_value.rev); + return (0); +} + +/* + * rcsparse_log() + * + * Parses the specified log of current deltatext pdp->rp_delta. + * + * log @[...]@ + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_log(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + if (rcsparse_token(rfp, RCS_TYPE_STRING) != RCS_TYPE_STRING) + return (1); + + pdp->rp_delta->rd_log = pdp->rp_value.str; + + return (0); +} + +/* + * rcsparse_text() + * + * Parses the specified text of current deltatext pdp->rp_delta. + * + * text @[...]@ + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_text(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + if (rcsparse_token(rfp, RCS_TYPE_STRING) != RCS_TYPE_STRING) + return (1); + + pdp->rp_delta->rd_tlen = pdp->rp_tlen - 1; + if (pdp->rp_delta->rd_tlen == 0) { + pdp->rp_delta->rd_text = xstrdup(""); + } else { + pdp->rp_delta->rd_text = xmalloc(pdp->rp_delta->rd_tlen); + memcpy(pdp->rp_delta->rd_text, pdp->rp_buf, + pdp->rp_delta->rd_tlen); + } + xfree(pdp->rp_value.str); + + return (0); +} + +/* + * rcsparse_head() + * + * Parses the head revision of RCS file <rfp>. + * + * head [REVISION]; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_head(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + int type; + + type = rcsparse_token(rfp, RCS_TYPE_REVISION|RCS_TOK_SCOLON); + if (type == RCS_TYPE_REVISION) { + rfp->rf_head = pdp->rp_value.rev; + type = rcsparse_token(rfp, RCS_TOK_SCOLON); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_branch() + * + * Parses the default branch of RCS file <rfp>. The branch keyword is + * optional and can be omitted. + * + * [branch BRANCH;] + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_branch(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + int type; + + type = rcsparse_token(rfp, RCS_TYPE_BRANCH|RCS_TOK_SCOLON); + if (type == RCS_TYPE_BRANCH) { + rfp->rf_branch = pdp->rp_value.rev; + type = rcsparse_token(rfp, RCS_TOK_SCOLON); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_access() + * + * Parses the access list of RCS file <rfp>. + * + * access [LOGIN ...]; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_access(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + struct rcs_access *ap; + int type; + + while ((type = rcsparse_token(rfp, RCS_TOK_SCOLON|RCS_TYPE_LOGIN)) + == RCS_TYPE_LOGIN) { + ap = xmalloc(sizeof(*ap)); + ap->ra_name = pdp->rp_value.str; + TAILQ_INSERT_TAIL(&(rfp->rf_access), ap, ra_list); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_symbols() + * + * Parses the symbol list of RCS file <rfp>. + * + * symbols [SYMBOL:REVISION ...]; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_symbols(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + struct rcs_sym *symp; + char *name; + int type; + + while ((type = rcsparse_token(rfp, RCS_TOK_SCOLON|RCS_TYPE_SYMBOL)) == + RCS_TYPE_SYMBOL) { + name = pdp->rp_value.str; + if (rcsparse_token(rfp, RCS_TOK_COLON) != RCS_TOK_COLON || + rcsparse_token(rfp, RCS_TYPE_NUMBER) != RCS_TYPE_NUMBER) { + xfree(name); + return (1); + } + symp = xmalloc(sizeof(*symp)); + symp->rs_name = name; + symp->rs_num = pdp->rp_value.rev; + TAILQ_INSERT_TAIL(&(rfp->rf_symbols), symp, rs_list); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_locks() + * + * Parses the lock list of RCS file <rfp>. + * + * locks [SYMBOL:REVISION ...]; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_locks(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + struct rcs_lock *lkp; + char *name; + int type; + + while ((type = rcsparse_token(rfp, RCS_TOK_SCOLON|RCS_TYPE_LOGIN)) == + RCS_TYPE_LOGIN) { + name = pdp->rp_value.str; + if (rcsparse_token(rfp, RCS_TOK_COLON) != RCS_TOK_COLON || + rcsparse_token(rfp, RCS_TYPE_REVISION) != + RCS_TYPE_REVISION) { + xfree(name); + return (1); + } + lkp = xmalloc(sizeof(*lkp)); + lkp->rl_name = name; + lkp->rl_num = pdp->rp_value.rev; + TAILQ_INSERT_TAIL(&(rfp->rf_locks), lkp, rl_list); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_locks() + * + * Parses the strict keyword of RCS file <rfp>. The strict keyword is + * optional and can be omitted. + * + * [strict;] + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_strict(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + rfp->rf_flags |= RCS_SLOCK; + + return (rcsparse_token(rfp, RCS_TOK_SCOLON) != RCS_TOK_SCOLON); +} + +/* + * rcsparse_comment() + * + * Parses the comment of RCS file <rfp>. The comment keyword is optional + * and can be omitted. + * + * [comment [@[...]@];] + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_comment(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + int type; + + type = rcsparse_token(rfp, RCS_TYPE_STRING|RCS_TOK_SCOLON); + if (type == RCS_TYPE_STRING) { + rfp->rf_comment = pdp->rp_value.str; + type = rcsparse_token(rfp, RCS_TOK_SCOLON); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_expand() + * + * Parses expand of RCS file <rfp>. The expand keyword is optional and + * can be omitted. + * + * [expand [@[...]@];] + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_expand(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + int type; + + type = rcsparse_token(rfp, RCS_TYPE_STRING|RCS_TOK_SCOLON); + if (type == RCS_TYPE_STRING) { + rfp->rf_expand = pdp->rp_value.str; + type = rcsparse_token(rfp, RCS_TOK_SCOLON); + } + + return (type != RCS_TOK_SCOLON); +} + +#define RBUF_PUTC(ch) \ +do { \ + if (bp == pdp->rp_bufend - 1) { \ + len = bp - pdp->rp_buf; \ + rcsparse_growbuf(rfp); \ + bp = pdp->rp_buf + len; \ + } \ + *(bp++) = (ch); \ + pdp->rp_tlen++; \ +} while (0); + +static int +rcsparse_string(RCSFILE *rfp, int allowed) +{ + struct rcs_pdata *pdp; + int c; + size_t len; + char *bp; + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + + bp = pdp->rp_buf; + pdp->rp_tlen = 0; + *bp = '\0'; + + for (;;) { + c = getc(pdp->rp_file); + if (c == '@') { + c = getc(pdp->rp_file); + if (c == EOF) { + return (EOF); + } else if (c != '@') { + ungetc(c, pdp->rp_file); + break; + } + } + + if (c == EOF) { + return (EOF); + } else if (c == '\n') + pdp->rp_lineno++; + + RBUF_PUTC(c); + } + + bp = pdp->rp_buf + pdp->rp_tlen; + RBUF_PUTC('\0'); + + if (!(allowed & RCS_TYPE_STRING)) { + rcsparse_warnx(rfp, "unexpected RCS string"); + return (0); + } + + pdp->rp_value.str = xstrdup(pdp->rp_buf); + + return (RCS_TYPE_STRING); +} + +static int +rcsparse_token(RCSFILE *rfp, int allowed) +{ + const struct rcs_keyword *p; + struct rcs_pdata *pdp; + int c, pre, ret, type; + char *bp; + size_t len; + RCSNUM *datenum; + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + + if (pdp->rp_token != -1) { + /* no need to check for allowed here */ + type = pdp->rp_token; + pdp->rp_token = -1; + return (type); + } + + /* skip whitespaces */ + c = EOF; + do { + pre = c; + c = getc(pdp->rp_file); + if (c == EOF) { + if (ferror(pdp->rp_file)) { + rcsparse_warnx(rfp, "error during parsing"); + return (0); + } + if (pre != '\n') + rcsparse_warnx(rfp, + "no newline at end of file"); + return (EOF); + } else if (c == '\n') + pdp->rp_lineno++; + } while (isspace(c)); + + pdp->rp_msglineno = pdp->rp_lineno; + type = 0; + switch (c) { + case '@': + ret = rcsparse_string(rfp, allowed); + if (ret == EOF && ferror(pdp->rp_file)) { + rcsparse_warnx(rfp, "error during parsing"); + return (0); + } + return (ret); + /* NOTREACHED */ + case ':': + type = RCS_TOK_COLON; + if (type & allowed) + return (type); + rcsparse_warnx(rfp, "unexpected token \"%c\"", c); + return (0); + /* NOTREACHED */ + case ';': + type = RCS_TOK_SCOLON; + if (type & allowed) + return (type); + rcsparse_warnx(rfp, "unexpected token \"%c\"", c); + return (0); + /* NOTREACHED */ + case ',': + type = RCS_TOK_COMMA; + if (type & allowed) + return (type); + rcsparse_warnx(rfp, "unexpected token \"%c\"", c); + return (0); + /* NOTREACHED */ + default: + if (!isgraph(c)) { + rcsparse_warnx(rfp, "unexpected character 0x%.2X", c); + return (0); + } + break; + } + allowed &= ~(RCS_TOK_COLON|RCS_TOK_SCOLON|RCS_TOK_COMMA); + + bp = pdp->rp_buf; + pdp->rp_tlen = 0; + *bp = '\0'; + + for (;;) { + if (c == EOF) { + if (ferror(pdp->rp_file)) + rcsparse_warnx(rfp, "error during parsing"); + else + rcsparse_warnx(rfp, "unexpected end of file"); + return (0); + } else if (c == '\n') + pdp->rp_lineno++; + + RBUF_PUTC(c); + + c = getc(pdp->rp_file); + + if (isspace(c)) { + if (c == '\n') + pdp->rp_lineno++; + RBUF_PUTC('\0'); + break; + } else if (c == ';' || c == ':' || c == ',') { + ungetc(c, pdp->rp_file); + RBUF_PUTC('\0'); + break; + } else if (!isgraph(c)) { + rcsparse_warnx(rfp, "unexpected character 0x%.2X", c); + return (0); + } + } + + switch (allowed) { + case RCS_TYPE_COMMITID: + /* XXX validate commitd */ + break; + case RCS_TYPE_LOGIN: + if (!valid_login(pdp->rp_buf)) { + rcsparse_warnx(rfp, "invalid login \"%s\"", + pdp->rp_buf); + return (0); + } + pdp->rp_value.str = xstrdup(pdp->rp_buf); + break; + case RCS_TYPE_SYMBOL: + if (!rcs_sym_check(pdp->rp_buf)) { + rcsparse_warnx(rfp, "invalid symbol \"%s\"", + pdp->rp_buf); + return (0); + } + pdp->rp_value.str = xstrdup(pdp->rp_buf); + break; + /* FALLTHROUGH */ + case RCS_TYPE_STATE: + if (rcs_state_check(pdp->rp_buf)) { + rcsparse_warnx(rfp, "invalid state \"%s\"", + pdp->rp_buf); + return (0); + } + pdp->rp_value.str = xstrdup(pdp->rp_buf); + break; + case RCS_TYPE_DATE: + if ((datenum = rcsnum_parse(pdp->rp_buf)) == NULL) { + rcsparse_warnx(rfp, "invalid date \"%s\"", pdp->rp_buf); + return (0); + } + if (datenum->rn_len != 6) { + rcsnum_free(datenum); + rcsparse_warnx(rfp, "invalid date \"%s\"", pdp->rp_buf); + return (0); + } + pdp->rp_value.date.tm_year = datenum->rn_id[0]; + if (pdp->rp_value.date.tm_year >= 1900) + pdp->rp_value.date.tm_year -= 1900; + pdp->rp_value.date.tm_mon = datenum->rn_id[1] - 1; + pdp->rp_value.date.tm_mday = datenum->rn_id[2]; + pdp->rp_value.date.tm_hour = datenum->rn_id[3]; + pdp->rp_value.date.tm_min = datenum->rn_id[4]; + pdp->rp_value.date.tm_sec = datenum->rn_id[5]; + rcsnum_free(datenum); + break; + case RCS_TYPE_NUMBER: + pdp->rp_value.rev = rcsnum_parse(pdp->rp_buf); + if (pdp->rp_value.rev == NULL) { + rcsparse_warnx(rfp, "invalid number \"%s\"", + pdp->rp_buf); + return (0); + } + break; + case RCS_TYPE_BRANCH: + pdp->rp_value.rev = rcsnum_parse(pdp->rp_buf); + if (pdp->rp_value.rev == NULL) { + rcsparse_warnx(rfp, "invalid branch \"%s\"", + pdp->rp_buf); + return (0); + } + if (!RCSNUM_ISBRANCH(pdp->rp_value.rev)) { + rcsnum_free(pdp->rp_value.rev); + rcsparse_warnx(rfp, "expected branch, got \"%s\"", + pdp->rp_buf); + return (0); + } + break; + case RCS_TYPE_KEYWORD: + if (islower(*pdp->rp_buf)) { + p = bsearch(pdp->rp_buf, keywords, + sizeof(keywords) / sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + if (p != NULL) + return (p->k_val); + } + allowed = RCS_TYPE_REVISION; + /* FALLTHROUGH */ + case RCS_TYPE_REVISION: + pdp->rp_value.rev = rcsnum_parse(pdp->rp_buf); + if (pdp->rp_value.rev != NULL) { + if (RCSNUM_ISBRANCH(pdp->rp_value.rev)) { + rcsnum_free(pdp->rp_value.rev); + rcsparse_warnx(rfp, + "expected revision, got \"%s\"", + pdp->rp_buf); + return (0); + } + break; + } + /* FALLTHROUGH */ + default: + RBUF_PUTC('\0'); + rcsparse_warnx(rfp, "unexpected token \"%s\"", pdp->rp_buf); + return (0); + /* NOTREACHED */ + } + + return (allowed); +} + +static int +rcsparse(RCSFILE *rfp, struct rcs_section *sec) +{ + struct rcs_pdata *pdp; + int i, token; + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + i = 0; + + token = 0; + for (i = 0; sec[i].token != 0; i++) { + token = rcsparse_token(rfp, RCS_TYPE_KEYWORD); + if (token == 0) + return (1); + + while (token != sec[i].token) { + if (sec[i].parse == NULL) + goto end; + if (sec[i].opt) { + i++; + continue; + } + if (token == EOF || (!(rfp->rf_flags & PARSED_DELTAS) && + token == RCS_TOK_DESC)) + goto end; + rcsparse_warnx(rfp, "unexpected token \"%s\"", + pdp->rp_buf); + return (1); + } + + if (sec[i].parse(rfp, pdp)) + return (1); + } +end: + if (token == RCS_TYPE_REVISION) + pdp->rp_token = token; + else if (token == RCS_TOK_DESC) + pdp->rp_token = RCS_TOK_DESC; + else if (token == EOF) + rfp->rf_flags |= RCS_PARSED; + + return (0); +} + +static int +rcsparse_deltatext(RCSFILE *rfp) +{ + struct rcs_pdata *pdp; + int ret; + + if (rfp->rf_flags & PARSED_DELTATEXTS) + return (0); + + if (!(rfp->rf_flags & PARSED_DESC)) + if ((ret = rcsparse_desc(rfp))) + return (ret); + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + + if (rcsparse(rfp, sec_deltatext)) + return (-1); + + if (rfp->rf_flags & RCS_PARSED) + rfp->rf_flags |= PARSED_DELTATEXTS; + + return (1); +} + +static int +rcsparse_delta(RCSFILE *rfp) +{ + struct rcs_pdata *pdp; + + if (rfp->rf_flags & PARSED_DELTAS) + return (0); + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + if (pdp->rp_token == RCS_TOK_DESC) { + rfp->rf_flags |= PARSED_DELTAS; + return (0); + } + + if (rcsparse(rfp, sec_delta)) + return (-1); + + if (pdp->rp_delta != NULL) { + TAILQ_INSERT_TAIL(&rfp->rf_delta, pdp->rp_delta, rd_list); + pdp->rp_delta = NULL; + rfp->rf_ndelta++; + return (1); + } + + return (0); +} + +/* + * rcsparse_growbuf() + * + * Attempt to grow the internal parse buffer for the RCS file <rf> by + * RCS_BUFEXTSIZE. + * In case of failure, the original buffer is left unmodified. + */ +static void +rcsparse_growbuf(RCSFILE *rfp) +{ + struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata; + + pdp->rp_buf = xrealloc(pdp->rp_buf, 1, + pdp->rp_blen + RCS_BUFEXTSIZE); + pdp->rp_blen += RCS_BUFEXTSIZE; + pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1; +} + +/* + * Borrowed from src/usr.sbin/user/user.c: + * return 1 if `login' is a valid login name + */ +static int +valid_login(char *login_name) +{ + unsigned char *cp; + + /* The first character cannot be a hyphen */ + if (*login_name == '-') + return 0; + + for (cp = login_name ; *cp ; cp++) { + /* We allow '$' as the last character for samba */ + if (!isalnum(*cp) && *cp != '.' && *cp != '_' && *cp != '-' && + !(*cp == '$' && *(cp + 1) == '\0')) { + return 0; + } + } + if ((char *)cp - login_name > _PW_NAME_LEN) + return 0; + return 1; +} + +static int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct rcs_keyword *)e)->k_name)); +} + +static void +rcsparse_warnx(RCSFILE *rfp, char *fmt, ...) +{ + struct rcs_pdata *pdp; + va_list ap; + char *nfmt; + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + va_start(ap, fmt); + if (asprintf(&nfmt, "%s:%d: %s", rfp->rf_path, pdp->rp_msglineno, fmt) + == -1) + nfmt = fmt; + cvs_vlog(LP_ERR, nfmt, ap); + va_end(ap); + if (nfmt != fmt) + free(nfmt); +} diff --git a/usr.bin/cvs/rcsparse.h b/usr.bin/cvs/rcsparse.h new file mode 100644 index 00000000000..64c503a8304 --- /dev/null +++ b/usr.bin/cvs/rcsparse.h @@ -0,0 +1,21 @@ +/* $OpenBSD: rcsparse.h,v 1.1 2010/10/15 08:44:12 tobias Exp $ */ +/* + * Copyright (c) 2010 Tobias Stoeckmann <tobias@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +int rcsparse_deltas(RCSFILE *, RCSNUM *); +int rcsparse_deltatexts(RCSFILE *, RCSNUM *); +void rcsparse_free(RCSFILE *); +int rcsparse_init(RCSFILE *); diff --git a/usr.bin/rcs/Makefile b/usr.bin/rcs/Makefile index 1a57dd4df45..663ac1c06d0 100644 --- a/usr.bin/rcs/Makefile +++ b/usr.bin/rcs/Makefile @@ -1,10 +1,10 @@ -# $OpenBSD: Makefile,v 1.39 2010/04/15 15:51:40 sobrado Exp $ +# $OpenBSD: Makefile,v 1.40 2010/10/15 08:44:12 tobias Exp $ PROG= rcs MAN= ci.1 co.1 ident.1 merge.1 rcs.1 rcsclean.1 rcsdiff.1 rcsmerge.1 rlog.1 -SRCS= ci.c co.c ident.c merge.c rcsclean.c rcsdiff.c rcsmerge.c rcsprog.c \ - rlog.c rcsutil.c buf.c date.y diff.c diff3.c rcs.c rcsnum.c \ +SRCS= ci.c co.c ident.c merge.c rcsclean.c rcsdiff.c rcsmerge.c rcsparse.c \ + rcsprog.c rlog.c rcsutil.c buf.c date.y diff.c diff3.c rcs.c rcsnum.c \ rcstime.c worklist.c xmalloc.c LINKS= ${BINDIR}/rcs ${BINDIR}/ci ${BINDIR}/rcs ${BINDIR}/co \ diff --git a/usr.bin/rcs/rcs.c b/usr.bin/rcs/rcs.c index 1aa675aed98..0c27b338126 100644 --- a/usr.bin/rcs/rcs.c +++ b/usr.bin/rcs/rcs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: rcs.c,v 1.67 2010/10/05 15:16:48 tobias Exp $ */ +/* $OpenBSD: rcs.c,v 1.68 2010/10/15 08:44:12 tobias Exp $ */ /* * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> * All rights reserved. @@ -39,64 +39,13 @@ #include "diff.h" #include "rcs.h" +#include "rcsparse.h" #include "rcsprog.h" #include "rcsutil.h" #include "xmalloc.h" -#define RCS_BUFSIZE 16384 -#define RCS_BUFEXTSIZE 8192 #define RCS_KWEXP_SIZE 1024 -/* RCS token types */ -#define RCS_TOK_ERR -1 -#define RCS_TOK_EOF 0 -#define RCS_TOK_NUM 1 -#define RCS_TOK_ID 2 -#define RCS_TOK_STRING 3 -#define RCS_TOK_SCOLON 4 -#define RCS_TOK_COLON 5 - -#define RCS_TOK_HEAD 8 -#define RCS_TOK_BRANCH 9 -#define RCS_TOK_ACCESS 10 -#define RCS_TOK_SYMBOLS 11 -#define RCS_TOK_LOCKS 12 -#define RCS_TOK_COMMENT 13 -#define RCS_TOK_EXPAND 14 -#define RCS_TOK_DATE 15 -#define RCS_TOK_AUTHOR 16 -#define RCS_TOK_STATE 17 -#define RCS_TOK_NEXT 18 -#define RCS_TOK_BRANCHES 19 -#define RCS_TOK_DESC 20 -#define RCS_TOK_LOG 21 -#define RCS_TOK_TEXT 22 -#define RCS_TOK_STRICT 23 -#define RCS_TOK_COMMITID 24 - -#define RCS_ISKEY(t) (((t) >= RCS_TOK_HEAD) && ((t) <= RCS_TOK_BRANCHES)) - -#define RCS_NOSCOL 0x01 /* no terminating semi-colon */ -#define RCS_VOPT 0x02 /* value is optional */ - -/* opaque parse data */ -struct rcs_pdata { - u_int rp_lines; - - char *rp_buf; - size_t rp_blen; - char *rp_bufend; - size_t rp_tlen; - - /* pushback token buffer */ - char rp_ptok[128]; - int rp_pttype; /* token type, RCS_TOK_ERR if no token */ - - FILE *rp_file; -}; - -#define RCS_TOKSTR(rfp) ((struct rcs_pdata *)rfp->rf_pdata)->rp_buf -#define RCS_TOKLEN(rfp) ((struct rcs_pdata *)rfp->rf_pdata)->rp_tlen /* invalid characters in RCS states */ static const char rcs_state_invch[] = RCS_STATE_INVALCHAR; @@ -189,33 +138,6 @@ struct rcs_kw rcs_expkw[] = { #define NB_COMTYPES (sizeof(rcs_comments)/sizeof(rcs_comments[0])) -static struct rcs_key { - char rk_str[16]; - int rk_id; - int rk_val; - int rk_flags; -} rcs_keys[] = { - { "access", RCS_TOK_ACCESS, RCS_TOK_ID, RCS_VOPT }, - { "author", RCS_TOK_AUTHOR, RCS_TOK_ID, 0 }, - { "branch", RCS_TOK_BRANCH, RCS_TOK_NUM, RCS_VOPT }, - { "branches", RCS_TOK_BRANCHES, RCS_TOK_NUM, RCS_VOPT }, - { "comment", RCS_TOK_COMMENT, RCS_TOK_STRING, RCS_VOPT }, - { "commitid", RCS_TOK_COMMITID, RCS_TOK_ID, 0 }, - { "date", RCS_TOK_DATE, RCS_TOK_NUM, 0 }, - { "desc", RCS_TOK_DESC, RCS_TOK_STRING, RCS_NOSCOL }, - { "expand", RCS_TOK_EXPAND, RCS_TOK_STRING, RCS_VOPT }, - { "head", RCS_TOK_HEAD, RCS_TOK_NUM, RCS_VOPT }, - { "locks", RCS_TOK_LOCKS, RCS_TOK_ID, 0 }, - { "log", RCS_TOK_LOG, RCS_TOK_STRING, RCS_NOSCOL }, - { "next", RCS_TOK_NEXT, RCS_TOK_NUM, RCS_VOPT }, - { "state", RCS_TOK_STATE, RCS_TOK_ID, RCS_VOPT }, - { "strict", RCS_TOK_STRICT, 0, 0, }, - { "symbols", RCS_TOK_SYMBOLS, 0, 0 }, - { "text", RCS_TOK_TEXT, RCS_TOK_STRING, RCS_NOSCOL }, -}; - -#define RCS_NKEYS (sizeof(rcs_keys)/sizeof(rcs_keys[0])) - static const char *rcs_errstrs[] = { "No error", "No such entry", @@ -232,23 +154,8 @@ char *timezone_flag = NULL; int rcs_patch_lines(struct rcs_lines *, struct rcs_lines *); static int rcs_movefile(char *, char *, mode_t, u_int); -static void rcs_parse_init(RCSFILE *); -static int rcs_parse_admin(RCSFILE *); -static int rcs_parse_delta(RCSFILE *); -static void rcs_parse_deltas(RCSFILE *, RCSNUM *); -static int rcs_parse_deltatext(RCSFILE *); -static void rcs_parse_deltatexts(RCSFILE *, RCSNUM *); -static void rcs_parse_desc(RCSFILE *); - -static int rcs_parse_access(RCSFILE *); -static int rcs_parse_symbols(RCSFILE *); -static int rcs_parse_locks(RCSFILE *); -static int rcs_parse_branches(RCSFILE *, struct rcs_delta *); + static void rcs_freedelta(struct rcs_delta *); -static void rcs_freepdata(struct rcs_pdata *); -static int rcs_gettok(RCSFILE *); -static int rcs_pushtok(RCSFILE *, const char *, int); -static void rcs_growbuf(RCSFILE *); static void rcs_strprint(const u_char *, size_t, FILE *); static BUF *rcs_expand_keywords(char *, struct rcs_delta *, BUF *, int); @@ -286,7 +193,8 @@ rcs_open(const char *path, int fd, int flags, ...) TAILQ_INIT(&(rfp->rf_locks)); if (!(rfp->rf_flags & RCS_CREATE)) { - rcs_parse_init(rfp); + if (rcsparse_init(rfp)) + errx(1, "could not parse admin data"); /* fill in rd_locker */ TAILQ_FOREACH(lkr, &(rfp->rf_locks), rl_list) { @@ -361,7 +269,7 @@ rcs_close(RCSFILE *rfp) if (rfp->rf_desc != NULL) xfree(rfp->rf_desc); if (rfp->rf_pdata != NULL) - rcs_freepdata(rfp->rf_pdata); + rcsparse_free(rfp); xfree(rfp); } @@ -391,7 +299,8 @@ rcs_write(RCSFILE *rfp) return; /* Write operations need the whole file parsed */ - rcs_parse_deltatexts(rfp, NULL); + if (rcsparse_deltatexts(rfp, NULL)) + errx(1, "problem parsing deltatexts"); (void)xasprintf(&fn, "%s/rcs.XXXXXXXXXX", rcs_tmpdir); @@ -1162,7 +1071,8 @@ rcs_getrev(RCSFILE *rfp, RCSNUM *frev) /* No matter what, we'll need everything parsed up until the description so go for it. */ - rcs_parse_desc(rfp); + if (rcsparse_deltas(rfp, NULL)) + return (NULL); rdp = rcs_findrev(rfp, rfp->rf_head); if (rdp == NULL) { @@ -1171,7 +1081,8 @@ rcs_getrev(RCSFILE *rfp, RCSNUM *frev) } if (rdp->rd_tlen == 0) - rcs_parse_deltatexts(rfp, rfp->rf_head); + if (rcsparse_deltatexts(rfp, rfp->rf_head)) + return (NULL); len = rdp->rd_tlen; if (len == 0) { @@ -1258,7 +1169,8 @@ rcs_getrev(RCSFILE *rfp, RCSNUM *frev) data = buf_release(rbuf); /* check if we have parsed this rev's deltatext */ if (rdp->rd_tlen == 0) - rcs_parse_deltatexts(rfp, rdp->rd_num); + if (rcsparse_deltatexts(rfp, rdp->rd_num)) + return (NULL); rbuf = rcs_patchfile(data, dlen, patch, plen, rcs_patch_lines); xfree(data); @@ -1521,7 +1433,8 @@ rcs_findrev(RCSFILE *rfp, RCSNUM *rev) rdp = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist); if (rdp == NULL || rcsnum_cmp(rdp->rd_num, rev, 0) == -1) { - rcs_parse_deltas(rfp, rev); + if (rcsparse_deltas(rfp, rev)) + return (NULL); } /* @@ -1659,714 +1572,6 @@ rcs_errstr(int code) return (esp); } -/* rcs_parse_deltas() - * - * Parse deltas. If <rev> is not NULL, parse only as far as that - * revision. If <rev> is NULL, parse all deltas. - */ -static void -rcs_parse_deltas(RCSFILE *rfp, RCSNUM *rev) -{ - int ret; - struct rcs_delta *enddelta; - - if ((rfp->rf_flags & PARSED_DELTAS) || (rfp->rf_flags & RCS_CREATE)) - return; - - for (;;) { - ret = rcs_parse_delta(rfp); - if (rev != NULL) { - enddelta = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist); - if (rcsnum_cmp(enddelta->rd_num, rev, 0) == 0) - break; - } - if (ret == 0) { - rfp->rf_flags |= PARSED_DELTAS; - break; - } - else if (ret == -1) - errx(1, "error parsing deltas"); - } -} - -/* rcs_parse_deltatexts() - * - * Parse deltatexts. If <rev> is not NULL, parse only as far as that - * revision. If <rev> is NULL, parse everything. - */ -static void -rcs_parse_deltatexts(RCSFILE *rfp, RCSNUM *rev) -{ - int ret; - struct rcs_delta *rdp; - - if ((rfp->rf_flags & PARSED_DELTATEXTS) || - (rfp->rf_flags & RCS_CREATE)) - return; - - if (!(rfp->rf_flags & PARSED_DESC)) - rcs_parse_desc(rfp); - for (;;) { - if (rev != NULL) { - rdp = rcs_findrev(rfp, rev); - if (rdp->rd_text != NULL) - break; - else - ret = rcs_parse_deltatext(rfp); - } else - ret = rcs_parse_deltatext(rfp); - if (ret == 0) { - rfp->rf_flags |= PARSED_DELTATEXTS; - break; - } - else if (ret == -1) - errx(1, "problem parsing deltatexts"); - } -} - -/* rcs_parse_desc() - * - * Parse RCS description. - */ -static void -rcs_parse_desc(RCSFILE *rfp) -{ - int ret = 0; - - if ((rfp->rf_flags & PARSED_DESC) || (rfp->rf_flags & RCS_CREATE)) - return; - if (!(rfp->rf_flags & PARSED_DELTAS)) - rcs_parse_deltas(rfp, NULL); - /* do parsing */ - ret = rcs_gettok(rfp); - if (ret != RCS_TOK_DESC) - errx(1, "token `%s' found where RCS desc expected", - RCS_TOKSTR(rfp)); - - ret = rcs_gettok(rfp); - if (ret != RCS_TOK_STRING) - errx(1, "token `%s' found where RCS desc expected", - RCS_TOKSTR(rfp)); - - rfp->rf_desc = xstrdup(RCS_TOKSTR(rfp)); - rfp->rf_flags |= PARSED_DESC; -} - -/* - * rcs_parse_init() - * - * Initial parsing of file <path>, which is in the RCS format. - * Just does admin section. - */ -static void -rcs_parse_init(RCSFILE *rfp) -{ - struct rcs_pdata *pdp; - - if (rfp->rf_flags & RCS_PARSED) - return; - - pdp = xcalloc(1, sizeof(*pdp)); - - pdp->rp_lines = 0; - pdp->rp_pttype = RCS_TOK_ERR; - - if ((pdp->rp_file = fdopen(rfp->rf_fd, "r")) == NULL) - err(1, "fdopen: `%s'", rfp->rf_path); - - pdp->rp_buf = xmalloc((size_t)RCS_BUFSIZE); - pdp->rp_blen = RCS_BUFSIZE; - pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1; - - /* ditch the strict lock */ - rfp->rf_flags &= ~RCS_SLOCK; - rfp->rf_pdata = pdp; - - if (rcs_parse_admin(rfp) < 0) { - rcs_freepdata(pdp); - errx(1, "could not parse admin data"); - } - - if (rfp->rf_flags & RCS_PARSE_FULLY) - rcs_parse_deltatexts(rfp, NULL); - - rfp->rf_flags |= RCS_SYNCED; -} - -/* - * rcs_parse_admin() - * - * Parse the administrative portion of an RCS file. - * Returns the type of the first token found after the admin section on - * success, or -1 on failure. - */ -static int -rcs_parse_admin(RCSFILE *rfp) -{ - u_int i; - int tok, ntok, hmask; - struct rcs_key *rk; - - /* hmask is a mask of the headers already encountered */ - hmask = 0; - for (;;) { - tok = rcs_gettok(rfp); - if (tok == RCS_TOK_ERR) { - rcs_errno = RCS_ERR_PARSE; - warnx("parse error in RCS admin section"); - goto fail; - } else if (tok == RCS_TOK_NUM || tok == RCS_TOK_DESC) { - /* - * Assume this is the start of the first delta or - * that we are dealing with an empty RCS file and - * we just found the description. - */ - rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok); - return (tok); - } - - rk = NULL; - for (i = 0; i < RCS_NKEYS; i++) - if (rcs_keys[i].rk_id == tok) - rk = &(rcs_keys[i]); - - if (hmask & (1 << tok)) { - rcs_errno = RCS_ERR_PARSE; - warnx("duplicate RCS key"); - goto fail; - } - hmask |= (1 << tok); - - switch (tok) { - case RCS_TOK_HEAD: - case RCS_TOK_BRANCH: - case RCS_TOK_COMMENT: - case RCS_TOK_EXPAND: - ntok = rcs_gettok(rfp); - if (ntok == RCS_TOK_SCOLON) - break; - if (ntok != rk->rk_val) { - rcs_errno = RCS_ERR_PARSE; - warnx("invalid value type for RCS key `%s'", - rk->rk_str); - } - - if (tok == RCS_TOK_HEAD) { - if (rfp->rf_head == NULL) - rfp->rf_head = rcsnum_alloc(); - rcsnum_aton(RCS_TOKSTR(rfp), NULL, - rfp->rf_head); - } else if (tok == RCS_TOK_BRANCH) { - if (rfp->rf_branch == NULL) - rfp->rf_branch = rcsnum_alloc(); - if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, - rfp->rf_branch) < 0) - goto fail; - } else if (tok == RCS_TOK_COMMENT) { - rfp->rf_comment = xstrdup(RCS_TOKSTR(rfp)); - } else if (tok == RCS_TOK_EXPAND) { - rfp->rf_expand = xstrdup(RCS_TOKSTR(rfp)); - } - - /* now get the expected semi-colon */ - ntok = rcs_gettok(rfp); - if (ntok != RCS_TOK_SCOLON) { - rcs_errno = RCS_ERR_PARSE; - warnx("missing semi-colon after RCS `%s' key", - rk->rk_str); - goto fail; - } - break; - case RCS_TOK_ACCESS: - if (rcs_parse_access(rfp) < 0) - goto fail; - break; - case RCS_TOK_SYMBOLS: - if (rcs_parse_symbols(rfp) < 0) - goto fail; - break; - case RCS_TOK_LOCKS: - if (rcs_parse_locks(rfp) < 0) - goto fail; - break; - default: - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' in RCS admin section", - RCS_TOKSTR(rfp)); - goto fail; - } - } - -fail: - return (-1); -} - -/* - * rcs_parse_delta() - * - * Parse an RCS delta section and allocate the structure to store that delta's - * information in the <rfp> delta list. - * Returns 1 if the section was parsed OK, 0 if it is the last delta, and - * -1 on error. - */ -static int -rcs_parse_delta(RCSFILE *rfp) -{ - int ret, tok, ntok, hmask; - u_int i; - char *tokstr; - RCSNUM *datenum; - struct rcs_delta *rdp; - struct rcs_key *rk; - - tok = rcs_gettok(rfp); - if (tok == RCS_TOK_DESC) { - rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok); - return (0); - } else if (tok != RCS_TOK_NUM) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' at start of delta", - RCS_TOKSTR(rfp)); - return (-1); - } - - rdp = xcalloc(1, sizeof(*rdp)); - - rdp->rd_num = rcsnum_alloc(); - rdp->rd_next = rcsnum_alloc(); - - TAILQ_INIT(&(rdp->rd_branches)); - - rcsnum_aton(RCS_TOKSTR(rfp), NULL, rdp->rd_num); - - hmask = 0; - ret = 0; - tokstr = NULL; - - for (;;) { - tok = rcs_gettok(rfp); - if (tok == RCS_TOK_ERR) { - rcs_errno = RCS_ERR_PARSE; - warnx("parse error in RCS delta section"); - rcs_freedelta(rdp); - return (-1); - } else if (tok == RCS_TOK_NUM || tok == RCS_TOK_DESC) { - rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok); - ret = (tok == RCS_TOK_NUM ? 1 : 0); - break; - } - - rk = NULL; - for (i = 0; i < RCS_NKEYS; i++) - if (rcs_keys[i].rk_id == tok) - rk = &(rcs_keys[i]); - - if (hmask & (1 << tok)) { - rcs_errno = RCS_ERR_PARSE; - warnx("duplicate RCS key"); - rcs_freedelta(rdp); - return (-1); - } - hmask |= (1 << tok); - - switch (tok) { - case RCS_TOK_DATE: - case RCS_TOK_AUTHOR: - case RCS_TOK_STATE: - case RCS_TOK_NEXT: - case RCS_TOK_COMMITID: - ntok = rcs_gettok(rfp); - if (ntok == RCS_TOK_SCOLON) { - if (rk->rk_flags & RCS_VOPT) - break; - else { - rcs_errno = RCS_ERR_PARSE; - warnx("missing mandatory " - "value to RCS key `%s'", - rk->rk_str); - rcs_freedelta(rdp); - return (-1); - } - } - - if (ntok != rk->rk_val) { - rcs_errno = RCS_ERR_PARSE; - warnx("invalid value type for RCS key `%s'", - rk->rk_str); - rcs_freedelta(rdp); - return (-1); - } - - if (tokstr != NULL) - xfree(tokstr); - tokstr = xstrdup(RCS_TOKSTR(rfp)); - /* now get the expected semi-colon */ - ntok = rcs_gettok(rfp); - if (ntok != RCS_TOK_SCOLON) { - rcs_errno = RCS_ERR_PARSE; - warnx("missing semi-colon after RCS `%s' key", - rk->rk_str); - xfree(tokstr); - rcs_freedelta(rdp); - return (-1); - } - - if (tok == RCS_TOK_DATE) { - if ((datenum = rcsnum_parse(tokstr)) == NULL) { - xfree(tokstr); - rcs_freedelta(rdp); - return (-1); - } - if (datenum->rn_len != 6) { - rcs_errno = RCS_ERR_PARSE; - warnx("RCS date specification has %s " - "fields", - (datenum->rn_len > 6) ? "too many" : - "missing"); - xfree(tokstr); - rcs_freedelta(rdp); - rcsnum_free(datenum); - return (-1); - } - rdp->rd_date.tm_year = datenum->rn_id[0]; - if (rdp->rd_date.tm_year >= 1900) - rdp->rd_date.tm_year -= 1900; - rdp->rd_date.tm_mon = datenum->rn_id[1] - 1; - rdp->rd_date.tm_mday = datenum->rn_id[2]; - rdp->rd_date.tm_hour = datenum->rn_id[3]; - rdp->rd_date.tm_min = datenum->rn_id[4]; - rdp->rd_date.tm_sec = datenum->rn_id[5]; - rcsnum_free(datenum); - } else if (tok == RCS_TOK_AUTHOR) { - rdp->rd_author = tokstr; - tokstr = NULL; - } else if (tok == RCS_TOK_STATE) { - rdp->rd_state = tokstr; - tokstr = NULL; - } else if (tok == RCS_TOK_NEXT) { - rcsnum_aton(tokstr, NULL, rdp->rd_next); - } else if (tok == RCS_TOK_COMMITID) { - /* XXX just parse it, no action yet */ - } - break; - case RCS_TOK_BRANCHES: - if (rcs_parse_branches(rfp, rdp) < 0) { - rcs_freedelta(rdp); - return (-1); - } - break; - default: - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' in RCS delta", - RCS_TOKSTR(rfp)); - rcs_freedelta(rdp); - return (-1); - } - } - - if (tokstr != NULL) - xfree(tokstr); - - TAILQ_INSERT_TAIL(&(rfp->rf_delta), rdp, rd_list); - rfp->rf_ndelta++; - - return (ret); -} - -/* - * rcs_parse_deltatext() - * - * Parse an RCS delta text section and fill in the log and text field of the - * appropriate delta section. - * Returns 1 if the section was parsed OK, 0 if it is the last delta, and - * -1 on error. - */ -static int -rcs_parse_deltatext(RCSFILE *rfp) -{ - int tok; - RCSNUM *tnum; - struct rcs_delta *rdp; - - tok = rcs_gettok(rfp); - if (tok == RCS_TOK_EOF) - return (0); - - if (tok != RCS_TOK_NUM) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' at start of RCS delta text", - RCS_TOKSTR(rfp)); - return (-1); - } - - tnum = rcsnum_alloc(); - rcsnum_aton(RCS_TOKSTR(rfp), NULL, tnum); - - TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) { - if (rcsnum_cmp(tnum, rdp->rd_num, 0) == 0) - break; - } - rcsnum_free(tnum); - - if (rdp == NULL) { - warnx("RCS delta text `%s' has no matching delta", - RCS_TOKSTR(rfp)); - return (-1); - } - - tok = rcs_gettok(rfp); - if (tok != RCS_TOK_LOG) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' where RCS log expected", - RCS_TOKSTR(rfp)); - return (-1); - } - - tok = rcs_gettok(rfp); - if (tok != RCS_TOK_STRING) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' where RCS log expected", - RCS_TOKSTR(rfp)); - return (-1); - } - rdp->rd_log = xstrdup(RCS_TOKSTR(rfp)); - tok = rcs_gettok(rfp); - if (tok != RCS_TOK_TEXT) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' where RCS text expected", - RCS_TOKSTR(rfp)); - return (-1); - } - - tok = rcs_gettok(rfp); - if (tok != RCS_TOK_STRING) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' where RCS text expected", - RCS_TOKSTR(rfp)); - return (-1); - } - - if (RCS_TOKLEN(rfp) == 0) { - rdp->rd_text = xmalloc(1); - rdp->rd_text[0] = '\0'; - rdp->rd_tlen = 0; - } else { - rdp->rd_text = xmalloc(RCS_TOKLEN(rfp)); - memcpy(rdp->rd_text, RCS_TOKSTR(rfp), (RCS_TOKLEN(rfp))); - rdp->rd_tlen = RCS_TOKLEN(rfp); - } - - return (1); -} - -/* - * rcs_parse_access() - * - * Parse the access list given as value to the `access' keyword. - * Returns 0 on success, or -1 on failure. - */ -static int -rcs_parse_access(RCSFILE *rfp) -{ - int type; - - while ((type = rcs_gettok(rfp)) != RCS_TOK_SCOLON) { - if (type != RCS_TOK_ID) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' in access list", - RCS_TOKSTR(rfp)); - return (-1); - } - - if (rcs_access_add(rfp, RCS_TOKSTR(rfp)) < 0) - return (-1); - } - - return (0); -} - -/* - * rcs_parse_symbols() - * - * Parse the symbol list given as value to the `symbols' keyword. - * Returns 0 on success, or -1 on failure. - */ -static int -rcs_parse_symbols(RCSFILE *rfp) -{ - int type; - struct rcs_sym *symp; - - for (;;) { - type = rcs_gettok(rfp); - if (type == RCS_TOK_SCOLON) - break; - - if (type != RCS_TOK_ID) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' in symbol list", - RCS_TOKSTR(rfp)); - return (-1); - } - - symp = xmalloc(sizeof(*symp)); - symp->rs_name = xstrdup(RCS_TOKSTR(rfp)); - symp->rs_num = rcsnum_alloc(); - - type = rcs_gettok(rfp); - if (type != RCS_TOK_COLON) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' in symbol list", - RCS_TOKSTR(rfp)); - rcsnum_free(symp->rs_num); - xfree(symp->rs_name); - xfree(symp); - return (-1); - } - - type = rcs_gettok(rfp); - if (type != RCS_TOK_NUM) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' in symbol list", - RCS_TOKSTR(rfp)); - rcsnum_free(symp->rs_num); - xfree(symp->rs_name); - xfree(symp); - return (-1); - } - - if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, symp->rs_num) < 0) { - warnx("failed to parse RCS NUM `%s'", - RCS_TOKSTR(rfp)); - rcsnum_free(symp->rs_num); - xfree(symp->rs_name); - xfree(symp); - return (-1); - } - - TAILQ_INSERT_TAIL(&(rfp->rf_symbols), symp, rs_list); - } - - return (0); -} - -/* - * rcs_parse_locks() - * - * Parse the lock list given as value to the `locks' keyword. - * Returns 0 on success, or -1 on failure. - */ -static int -rcs_parse_locks(RCSFILE *rfp) -{ - int type; - struct rcs_lock *lkp; - - for (;;) { - type = rcs_gettok(rfp); - if (type == RCS_TOK_SCOLON) - break; - - if (type != RCS_TOK_ID) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' in lock list", - RCS_TOKSTR(rfp)); - return (-1); - } - - lkp = xmalloc(sizeof(*lkp)); - lkp->rl_name = xstrdup(RCS_TOKSTR(rfp)); - lkp->rl_num = rcsnum_alloc(); - - type = rcs_gettok(rfp); - if (type != RCS_TOK_COLON) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' in symbol list", - RCS_TOKSTR(rfp)); - rcsnum_free(lkp->rl_num); - xfree(lkp->rl_name); - xfree(lkp); - return (-1); - } - - type = rcs_gettok(rfp); - if (type != RCS_TOK_NUM) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' in symbol list", - RCS_TOKSTR(rfp)); - rcsnum_free(lkp->rl_num); - xfree(lkp->rl_name); - xfree(lkp); - return (-1); - } - - if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, lkp->rl_num) < 0) { - warnx("failed to parse RCS NUM `%s'", - RCS_TOKSTR(rfp)); - rcsnum_free(lkp->rl_num); - xfree(lkp->rl_name); - xfree(lkp); - return (-1); - } - - TAILQ_INSERT_HEAD(&(rfp->rf_locks), lkp, rl_list); - } - - /* check if we have a `strict' */ - type = rcs_gettok(rfp); - if (type != RCS_TOK_STRICT) { - rcs_pushtok(rfp, RCS_TOKSTR(rfp), type); - } else { - rfp->rf_flags |= RCS_SLOCK; - - type = rcs_gettok(rfp); - if (type != RCS_TOK_SCOLON) { - rcs_errno = RCS_ERR_PARSE; - warnx("missing semi-colon after `strict' keyword"); - return (-1); - } - } - - return (0); -} - -/* - * rcs_parse_branches() - * - * Parse the list of branches following a `branches' keyword in a delta. - * Returns 0 on success, or -1 on failure. - */ -static int -rcs_parse_branches(RCSFILE *rfp, struct rcs_delta *rdp) -{ - int type; - struct rcs_branch *brp; - - for (;;) { - type = rcs_gettok(rfp); - if (type == RCS_TOK_SCOLON) - break; - - if (type != RCS_TOK_NUM) { - rcs_errno = RCS_ERR_PARSE; - warnx("unexpected token `%s' in list of branches", - RCS_TOKSTR(rfp)); - return (-1); - } - - brp = xmalloc(sizeof(*brp)); - brp->rb_num = rcsnum_parse(RCS_TOKSTR(rfp)); - if (brp->rb_num == NULL) { - xfree(brp); - return (-1); - } - - TAILQ_INSERT_TAIL(&(rdp->rd_branches), brp, rb_list); - } - - return (0); -} - /* * rcs_freedelta() * @@ -2403,195 +1608,6 @@ rcs_freedelta(struct rcs_delta *rdp) } /* - * rcs_freepdata() - * - * Free the contents of the parser data structure. - */ -static void -rcs_freepdata(struct rcs_pdata *pd) -{ - if (pd->rp_file != NULL) - (void)fclose(pd->rp_file); - if (pd->rp_buf != NULL) - xfree(pd->rp_buf); - xfree(pd); -} - -/* - * rcs_gettok() - * - * Get the next RCS token from the string <str>. - */ -static int -rcs_gettok(RCSFILE *rfp) -{ - u_int i; - int ch, last, type; - size_t len; - char *bp; - struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata; - - type = RCS_TOK_ERR; - bp = pdp->rp_buf; - pdp->rp_tlen = 0; - *bp = '\0'; - - if (pdp->rp_pttype != RCS_TOK_ERR) { - type = pdp->rp_pttype; - if (strlcpy(pdp->rp_buf, pdp->rp_ptok, pdp->rp_blen) >= - pdp->rp_blen) - errx(1, "rcs_gettok: strlcpy"); - pdp->rp_pttype = RCS_TOK_ERR; - return (type); - } - - /* skip leading whitespace */ - /* XXX we must skip backspace too for compatibility, should we? */ - do { - ch = getc(pdp->rp_file); - if (ch == '\n') - pdp->rp_lines++; - } while (isspace(ch)); - - if (ch == EOF) { - type = RCS_TOK_EOF; - } else if (ch == ';') { - type = RCS_TOK_SCOLON; - } else if (ch == ':') { - type = RCS_TOK_COLON; - } else if (isalpha(ch)) { - type = RCS_TOK_ID; - *(bp++) = ch; - for (;;) { - ch = getc(pdp->rp_file); - if (ch == EOF) { - type = RCS_TOK_EOF; - break; - } else if (!isgraph(ch) || - strchr(rcs_sym_invch, ch) != NULL) { - ungetc(ch, pdp->rp_file); - break; - } - *(bp++) = ch; - pdp->rp_tlen++; - if (bp == pdp->rp_bufend - 1) { - len = bp - pdp->rp_buf; - rcs_growbuf(rfp); - bp = pdp->rp_buf + len; - } - } - *bp = '\0'; - - if (type != RCS_TOK_ERR) { - for (i = 0; i < RCS_NKEYS; i++) { - if (strcmp(rcs_keys[i].rk_str, - pdp->rp_buf) == 0) { - type = rcs_keys[i].rk_id; - break; - } - } - } - } else if (ch == '@') { - /* we have a string */ - type = RCS_TOK_STRING; - for (;;) { - ch = getc(pdp->rp_file); - if (ch == EOF) { - type = RCS_TOK_EOF; - break; - } else if (ch == '@') { - ch = getc(pdp->rp_file); - if (ch != '@') { - ungetc(ch, pdp->rp_file); - break; - } - } else if (ch == '\n') - pdp->rp_lines++; - - *(bp++) = ch; - pdp->rp_tlen++; - if (bp == pdp->rp_bufend - 1) { - len = bp - pdp->rp_buf; - rcs_growbuf(rfp); - bp = pdp->rp_buf + len; - } - } - - *bp = '\0'; - } else if (isdigit(ch)) { - *(bp++) = ch; - last = ch; - type = RCS_TOK_NUM; - - for (;;) { - ch = getc(pdp->rp_file); - if (ch == EOF) { - type = RCS_TOK_EOF; - break; - } - if (bp == pdp->rp_bufend) - break; - if (isalpha(ch) && ch != '.') { - type = RCS_TOK_ID; - } else if (!isdigit(ch) && ch != '.') { - ungetc(ch, pdp->rp_file); - break; - } - - if (last == '.' && ch == '.') { - type = RCS_TOK_ERR; - break; - } - last = ch; - *(bp++) = ch; - pdp->rp_tlen++; - } - *bp = '\0'; - } - - return (type); -} - -/* - * rcs_pushtok() - * - * Push a token back in the parser's token buffer. - */ -static int -rcs_pushtok(RCSFILE *rfp, const char *tok, int type) -{ - struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata; - - if (pdp->rp_pttype != RCS_TOK_ERR) - return (-1); - - pdp->rp_pttype = type; - if (strlcpy(pdp->rp_ptok, tok, sizeof(pdp->rp_ptok)) >= - sizeof(pdp->rp_ptok)) - errx(1, "rcs_pushtok: strlcpy"); - return (0); -} - - -/* - * rcs_growbuf() - * - * Attempt to grow the internal parse buffer for the RCS file <rf> by - * RCS_BUFEXTSIZE. - * In case of failure, the original buffer is left unmodified. - */ -static void -rcs_growbuf(RCSFILE *rf) -{ - struct rcs_pdata *pdp = (struct rcs_pdata *)rf->rf_pdata; - - pdp->rp_buf = xrealloc(pdp->rp_buf, 1, - pdp->rp_blen + RCS_BUFEXTSIZE); - pdp->rp_blen += RCS_BUFEXTSIZE; - pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1; -} - -/* * rcs_strprint() * * Output an RCS string <str> of size <slen> to the stream <stream>. Any @@ -2859,7 +1875,8 @@ rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, BUF *bp) struct rcs_delta *rdp; /* Write operations require full parsing */ - rcs_parse_deltatexts(rfp, NULL); + if (rcsparse_deltatexts(rfp, NULL)) + return (-1); if ((rdp = rcs_findrev(rfp, rev)) == NULL) return (-1); diff --git a/usr.bin/rcs/rcsparse.c b/usr.bin/rcs/rcsparse.c new file mode 100644 index 00000000000..e411146f228 --- /dev/null +++ b/usr.bin/rcs/rcsparse.c @@ -0,0 +1,1265 @@ +/* $OpenBSD: rcsparse.c,v 1.1 2010/10/15 08:44:12 tobias Exp $ */ +/* + * Copyright (c) 2010 Tobias Stoeckmann <tobias@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/queue.h> + +#include <ctype.h> +#include <err.h> +#include <pwd.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "rcs.h" +#include "rcsparse.h" +#include "xmalloc.h" + +#define RCS_BUFSIZE 16384 +#define RCS_BUFEXTSIZE 8192 + +/* RCS token types */ +#define RCS_TOK_HEAD (1 << 0) +#define RCS_TOK_BRANCH (1 << 1) +#define RCS_TOK_ACCESS (1 << 2) +#define RCS_TOK_SYMBOLS (1 << 3) +#define RCS_TOK_LOCKS (1 << 4) +#define RCS_TOK_STRICT (1 << 5) +#define RCS_TOK_COMMENT (1 << 6) +#define RCS_TOK_COMMITID (1 << 7) +#define RCS_TOK_EXPAND (1 << 8) +#define RCS_TOK_DESC (1 << 9) +#define RCS_TOK_DATE (1 << 10) +#define RCS_TOK_AUTHOR (1 << 11) +#define RCS_TOK_STATE (1 << 12) +#define RCS_TOK_BRANCHES (1 << 13) +#define RCS_TOK_NEXT (1 << 14) +#define RCS_TOK_LOG (1 << 15) +#define RCS_TOK_TEXT (1 << 16) +#define RCS_TOK_COLON (1 << 17) +#define RCS_TOK_COMMA (1 << 18) +#define RCS_TOK_SCOLON (1 << 19) + +#define RCS_TYPE_STRING (1 << 20) +#define RCS_TYPE_NUMBER (1 << 21) +#define RCS_TYPE_BRANCH (1 << 22) +#define RCS_TYPE_REVISION (1 << 23) +#define RCS_TYPE_LOGIN (1 << 24) +#define RCS_TYPE_STATE (1 << 25) +#define RCS_TYPE_SYMBOL (1 << 26) +#define RCS_TYPE_DATE (1 << 27) +#define RCS_TYPE_KEYWORD (1 << 28) +#define RCS_TYPE_COMMITID (1 << 29) + +#define MANDATORY 0 +#define OPTIONAL 1 + +/* opaque parse data */ +struct rcs_pdata { + char *rp_buf; + size_t rp_blen; + char *rp_bufend; + size_t rp_tlen; + + struct rcs_delta *rp_delta; + FILE *rp_file; + int rp_lineno; + int rp_msglineno; + int rp_token; + + union { + RCSNUM *rev; + char *str; + struct tm date; + } rp_value; +}; + +struct rcs_keyword { + const char *k_name; + int k_val; +}; + +struct rcs_section { + int token; + int (*parse)(RCSFILE *, struct rcs_pdata *); + int opt; +}; + +/* this has to be sorted always */ +static const struct rcs_keyword keywords[] = { + { "access", RCS_TOK_ACCESS}, + { "author", RCS_TOK_AUTHOR}, + { "branch", RCS_TOK_BRANCH}, + { "branches", RCS_TOK_BRANCHES}, + { "comment", RCS_TOK_COMMENT}, + { "date", RCS_TOK_DATE}, + { "desc", RCS_TOK_DESC}, + { "expand", RCS_TOK_EXPAND}, + { "head", RCS_TOK_HEAD}, + { "locks", RCS_TOK_LOCKS}, + { "log", RCS_TOK_LOG}, + { "next", RCS_TOK_NEXT}, + { "state", RCS_TOK_STATE}, + { "strict", RCS_TOK_STRICT}, + { "symbols", RCS_TOK_SYMBOLS}, + { "text", RCS_TOK_TEXT} +}; + +/* parser functions specified in rcs_section structs */ +static int rcsparse_head(RCSFILE *, struct rcs_pdata *); +static int rcsparse_branch(RCSFILE *, struct rcs_pdata *); +static int rcsparse_access(RCSFILE *, struct rcs_pdata *); +static int rcsparse_symbols(RCSFILE *, struct rcs_pdata *); +static int rcsparse_locks(RCSFILE *, struct rcs_pdata *); +static int rcsparse_strict(RCSFILE *, struct rcs_pdata *); +static int rcsparse_comment(RCSFILE *, struct rcs_pdata *); +static int rcsparse_commitid(RCSFILE *, struct rcs_pdata *); +static int rcsparse_expand(RCSFILE *, struct rcs_pdata *); +static int rcsparse_deltarevision(RCSFILE *, struct rcs_pdata *); +static int rcsparse_date(RCSFILE *, struct rcs_pdata *); +static int rcsparse_author(RCSFILE *, struct rcs_pdata *); +static int rcsparse_state(RCSFILE *, struct rcs_pdata *); +static int rcsparse_branches(RCSFILE *, struct rcs_pdata *); +static int rcsparse_next(RCSFILE *, struct rcs_pdata *); +static int rcsparse_textrevision(RCSFILE *, struct rcs_pdata *); +static int rcsparse_log(RCSFILE *, struct rcs_pdata *); +static int rcsparse_text(RCSFILE *, struct rcs_pdata *); + +static int rcsparse_delta(RCSFILE *); +static int rcsparse_deltatext(RCSFILE *); +static int rcsparse_desc(RCSFILE *); + +static int kw_cmp(const void *, const void *); +static int rcsparse(RCSFILE *, struct rcs_section *); +static void rcsparse_growbuf(RCSFILE *); +static int rcsparse_string(RCSFILE *, int); +static int rcsparse_token(RCSFILE *, int); +static void rcsparse_warnx(RCSFILE *, char *, ...); +static int valid_login(char *); + +/* + * head [REVISION]; + * [branch BRANCH]; + * access [LOGIN ...]; + * symbols [SYMBOL:REVISION ...]; + * locks [LOGIN:REVISION ...]; + * [strict;] + * [comment [@[...]@];] + * [expand [@[...]@];] + */ +static struct rcs_section sec_admin[] = { + { RCS_TOK_HEAD, rcsparse_head, MANDATORY }, + { RCS_TOK_BRANCH, rcsparse_branch, OPTIONAL }, + { RCS_TOK_ACCESS, rcsparse_access, MANDATORY }, + { RCS_TOK_SYMBOLS, rcsparse_symbols, MANDATORY }, + { RCS_TOK_LOCKS, rcsparse_locks, MANDATORY }, + { RCS_TOK_STRICT, rcsparse_strict, OPTIONAL }, + { RCS_TOK_COMMENT, rcsparse_comment, OPTIONAL }, + { RCS_TOK_EXPAND, rcsparse_expand, OPTIONAL }, + { 0, NULL, 0 } +}; + +/* + * REVISION + * date [YY]YY.MM.DD.HH.MM.SS; + * author LOGIN; + * state STATE; + * branches [REVISION ...]; + * next [REVISION]; + * [commitid ID;] + */ +static struct rcs_section sec_delta[] = { + { RCS_TYPE_REVISION, rcsparse_deltarevision, MANDATORY }, + { RCS_TOK_DATE, rcsparse_date, MANDATORY }, + { RCS_TOK_AUTHOR, rcsparse_author, MANDATORY }, + { RCS_TOK_STATE, rcsparse_state, MANDATORY }, + { RCS_TOK_BRANCHES, rcsparse_branches, MANDATORY }, + { RCS_TOK_NEXT, rcsparse_next, MANDATORY }, + { RCS_TOK_COMMITID, rcsparse_commitid, OPTIONAL }, + { 0, NULL, 0 } +}; + +/* + * REVISION + * log @[...]@ + * text @[...]@ + */ +static struct rcs_section sec_deltatext[] = { + { RCS_TYPE_REVISION, rcsparse_textrevision, MANDATORY }, + { RCS_TOK_LOG, rcsparse_log, MANDATORY }, + { RCS_TOK_TEXT, rcsparse_text, MANDATORY }, + { 0, NULL, 0 } +}; + +/* + * rcsparse_init() + * + * Initializes the parsing data structure and parses the admin section of + * RCS file <rfp>. + * + * Returns 0 on success or 1 on failure. + */ +int +rcsparse_init(RCSFILE *rfp) +{ + struct rcs_pdata *pdp; + + if (rfp->rf_flags & RCS_PARSED) { + rfp->rf_flags &= ~RCS_PARSED; + return (0); + } + + pdp = xmalloc(sizeof(*pdp)); + pdp->rp_buf = xmalloc(RCS_BUFSIZE); + pdp->rp_blen = RCS_BUFSIZE; + pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1; + pdp->rp_token = -1; + pdp->rp_lineno = 1; + pdp->rp_msglineno = 1; + if ((pdp->rp_file = fdopen(rfp->rf_fd, "r")) == NULL) { + xfree(pdp); + return (1); + } + /* ditch the strict lock */ + rfp->rf_flags &= ~RCS_SLOCK; + rfp->rf_pdata = pdp; + + if (rcsparse(rfp, sec_admin)) { + rcsparse_free(rfp); + return (1); + } + + if ((rfp->rf_flags & RCS_PARSE_FULLY) && + rcsparse_deltatexts(rfp, NULL)) { + rcsparse_free(rfp); + return (1); + } + + rfp->rf_flags |= RCS_SYNCED; + return (0); +} + +/* + * rcsparse_deltas() + * + * Parse deltas. If <rev> is not NULL, parse only as far as that + * revision. If <rev> is NULL, parse all deltas. + * + * Returns 0 on success or 1 on error. + */ +int +rcsparse_deltas(RCSFILE *rfp, RCSNUM *rev) +{ + int ret; + struct rcs_delta *enddelta; + + if ((rfp->rf_flags & PARSED_DELTAS) || (rfp->rf_flags & RCS_CREATE)) + return (0); + + for (;;) { + ret = rcsparse_delta(rfp); + if (rev != NULL) { + enddelta = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist); + if (enddelta == NULL) + return (1); + + if (rcsnum_cmp(enddelta->rd_num, rev, 0) == 0) + break; + } + + if (ret == 0) { + rfp->rf_flags |= PARSED_DELTAS; + break; + } + else if (ret == -1) + return (1); + } + + return (0); +} + +/* + * rcsparse_deltatexts() + * + * Parse deltatexts. If <rev> is not NULL, parse only as far as that + * revision. If <rev> is NULL, parse everything. + * + * Returns 0 on success or 1 on error. + */ +int +rcsparse_deltatexts(RCSFILE *rfp, RCSNUM *rev) +{ + int ret; + struct rcs_delta *rdp; + + if ((rfp->rf_flags & PARSED_DELTATEXTS) || + (rfp->rf_flags & RCS_CREATE)) + return (0); + + if (!(rfp->rf_flags & PARSED_DESC)) + if (rcsparse_desc(rfp)) + return (1); + for (;;) { + if (rev != NULL) { + rdp = rcs_findrev(rfp, rev); + if (rdp->rd_text != NULL) + break; + else + ret = rcsparse_deltatext(rfp); + } else + ret = rcsparse_deltatext(rfp); + if (ret == 0) { + rfp->rf_flags |= PARSED_DELTATEXTS; + break; + } + else if (ret == -1) + return (1); + } + + return (0); +} + +/* + * rcsparse_free() + * + * Free the contents of the <rfp>'s parser data structure. + */ +void +rcsparse_free(RCSFILE *rfp) +{ + struct rcs_pdata *pdp; + + pdp = rfp->rf_pdata; + + if (pdp->rp_file != NULL) + (void)fclose(pdp->rp_file); + if (pdp->rp_buf != NULL) + xfree(pdp->rp_buf); + if (pdp->rp_token == RCS_TYPE_REVISION) + rcsnum_free(pdp->rp_value.rev); + xfree(pdp); +} + +/* + * rcsparse_desc() + * + * Parse desc of the RCS file <rfp>. By calling rcsparse_desc, all deltas + * will be parsed in order to proceed the reading cursor to the desc keyword. + * + * desc @[...]@; + * + * Returns 0 on success or 1 on error. + */ +static int +rcsparse_desc(RCSFILE *rfp) +{ + struct rcs_pdata *pdp; + + if (rfp->rf_flags & PARSED_DESC) + return (0); + + if (!(rfp->rf_flags & PARSED_DELTAS) && rcsparse_deltas(rfp, NULL)) + return (1); + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + + if (rcsparse_token(rfp, RCS_TOK_DESC) != RCS_TOK_DESC || + rcsparse_token(rfp, RCS_TYPE_STRING) != RCS_TYPE_STRING) + return (1); + + rfp->rf_desc = pdp->rp_value.str; + rfp->rf_flags |= PARSED_DESC; + + return (0); +} + +/* + * rcsparse_deltarevision() + * + * Called upon reaching a new REVISION entry in the delta section. + * A new rcs_delta structure will be prepared in pdp->rp_delta for further + * parsing. + * + * REVISION + * + * Always returns 0. + */ +static int +rcsparse_deltarevision(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + struct rcs_delta *rdp; + + rdp = xcalloc(1, sizeof(*rdp)); + TAILQ_INIT(&rdp->rd_branches); + rdp->rd_num = pdp->rp_value.rev; + pdp->rp_delta = rdp; + + return (0); +} + +/* + * rcsparse_date() + * + * Parses the specified date of current delta pdp->rp_delta. + * + * date YYYY.MM.DD.HH.MM.SS; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_date(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + if (rcsparse_token(rfp, RCS_TYPE_DATE) != RCS_TYPE_DATE) + return (1); + + pdp->rp_delta->rd_date = pdp->rp_value.date; + + return (rcsparse_token(rfp, RCS_TOK_SCOLON) != RCS_TOK_SCOLON); +} + +/* + * rcsparse_author() + * + * Parses the specified author of current delta pdp->rp_delta. + * + * author LOGIN; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_author(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + if (rcsparse_token(rfp, RCS_TYPE_LOGIN) != RCS_TYPE_LOGIN) + return (1); + + pdp->rp_delta->rd_author = pdp->rp_value.str; + + return (rcsparse_token(rfp, RCS_TOK_SCOLON) != RCS_TOK_SCOLON); +} + +/* + * rcsparse_state() + * + * Parses the specified state of current delta pdp->rp_delta. + * + * state STATE; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_state(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + if (rcsparse_token(rfp, RCS_TYPE_STATE) != RCS_TYPE_STATE) + return (1); + + pdp->rp_delta->rd_state = pdp->rp_value.str; + + return (rcsparse_token(rfp, RCS_TOK_SCOLON) != RCS_TOK_SCOLON); +} + +/* + * rcsparse_branches() + * + * Parses the specified branches of current delta pdp->rp_delta. + * + * branches [REVISION ...]; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_branches(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + struct rcs_branch *rb; + int type; + + while ((type = rcsparse_token(rfp, RCS_TOK_SCOLON|RCS_TYPE_REVISION)) + == RCS_TYPE_REVISION) { + rb = xmalloc(sizeof(*rb)); + rb->rb_num = pdp->rp_value.rev; + TAILQ_INSERT_TAIL(&(pdp->rp_delta->rd_branches), rb, rb_list); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_next() + * + * Parses the specified next revision of current delta pdp->rp_delta. + * + * next [REVISION]; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_next(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + int type; + + type = rcsparse_token(rfp, RCS_TYPE_REVISION|RCS_TOK_SCOLON); + if (type == RCS_TYPE_REVISION) { + pdp->rp_delta->rd_next = pdp->rp_value.rev; + type = rcsparse_token(rfp, RCS_TOK_SCOLON); + } else + pdp->rp_delta->rd_next = rcsnum_alloc(); + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_commitid() + * + * Parses the specified commit id of current delta pdp->rp_delta. The + * commitid keyword is optional and can be omitted. + * + * [commitid ID;] + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_commitid(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + if (rcsparse_token(rfp, RCS_TYPE_COMMITID) != RCS_TYPE_COMMITID) + return (1); + + /* XXX - do something with commitid */ + + return (rcsparse_token(rfp, RCS_TOK_SCOLON) != RCS_TOK_SCOLON); +} + +/* + * rcsparse_textrevision() + * + * Called upon reaching a new REVISION entry in the delta text section. + * pdp->rp_delta will be set to REVISION's delta (created in delta section) + * for further parsing. + * + * REVISION + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_textrevision(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + struct rcs_delta *rdp; + + TAILQ_FOREACH(rdp, &rfp->rf_delta, rd_list) { + if (rcsnum_cmp(rdp->rd_num, pdp->rp_value.rev, 0) == 0) + break; + } + if (rdp == NULL) { + rcsparse_warnx(rfp, "delta for revision \"%s\" not found", + pdp->rp_buf); + rcsnum_free(pdp->rp_value.rev); + return (1); + } + pdp->rp_delta = rdp; + + rcsnum_free(pdp->rp_value.rev); + return (0); +} + +/* + * rcsparse_log() + * + * Parses the specified log of current deltatext pdp->rp_delta. + * + * log @[...]@ + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_log(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + if (rcsparse_token(rfp, RCS_TYPE_STRING) != RCS_TYPE_STRING) + return (1); + + pdp->rp_delta->rd_log = pdp->rp_value.str; + + return (0); +} + +/* + * rcsparse_text() + * + * Parses the specified text of current deltatext pdp->rp_delta. + * + * text @[...]@ + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_text(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + if (rcsparse_token(rfp, RCS_TYPE_STRING) != RCS_TYPE_STRING) + return (1); + + pdp->rp_delta->rd_tlen = pdp->rp_tlen - 1; + if (pdp->rp_delta->rd_tlen == 0) { + pdp->rp_delta->rd_text = xstrdup(""); + } else { + pdp->rp_delta->rd_text = xmalloc(pdp->rp_delta->rd_tlen); + memcpy(pdp->rp_delta->rd_text, pdp->rp_buf, + pdp->rp_delta->rd_tlen); + } + xfree(pdp->rp_value.str); + + return (0); +} + +/* + * rcsparse_head() + * + * Parses the head revision of RCS file <rfp>. + * + * head [REVISION]; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_head(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + int type; + + type = rcsparse_token(rfp, RCS_TYPE_REVISION|RCS_TOK_SCOLON); + if (type == RCS_TYPE_REVISION) { + rfp->rf_head = pdp->rp_value.rev; + type = rcsparse_token(rfp, RCS_TOK_SCOLON); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_branch() + * + * Parses the default branch of RCS file <rfp>. The branch keyword is + * optional and can be omitted. + * + * [branch BRANCH;] + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_branch(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + int type; + + type = rcsparse_token(rfp, RCS_TYPE_BRANCH|RCS_TOK_SCOLON); + if (type == RCS_TYPE_BRANCH) { + rfp->rf_branch = pdp->rp_value.rev; + type = rcsparse_token(rfp, RCS_TOK_SCOLON); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_access() + * + * Parses the access list of RCS file <rfp>. + * + * access [LOGIN ...]; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_access(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + struct rcs_access *ap; + int type; + + while ((type = rcsparse_token(rfp, RCS_TOK_SCOLON|RCS_TYPE_LOGIN)) + == RCS_TYPE_LOGIN) { + ap = xmalloc(sizeof(*ap)); + ap->ra_name = pdp->rp_value.str; + TAILQ_INSERT_TAIL(&(rfp->rf_access), ap, ra_list); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_symbols() + * + * Parses the symbol list of RCS file <rfp>. + * + * symbols [SYMBOL:REVISION ...]; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_symbols(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + struct rcs_sym *symp; + char *name; + int type; + + while ((type = rcsparse_token(rfp, RCS_TOK_SCOLON|RCS_TYPE_SYMBOL)) == + RCS_TYPE_SYMBOL) { + name = pdp->rp_value.str; + if (rcsparse_token(rfp, RCS_TOK_COLON) != RCS_TOK_COLON || + rcsparse_token(rfp, RCS_TYPE_NUMBER) != RCS_TYPE_NUMBER) { + xfree(name); + return (1); + } + symp = xmalloc(sizeof(*symp)); + symp->rs_name = name; + symp->rs_num = pdp->rp_value.rev; + TAILQ_INSERT_TAIL(&(rfp->rf_symbols), symp, rs_list); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_locks() + * + * Parses the lock list of RCS file <rfp>. + * + * locks [SYMBOL:REVISION ...]; + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_locks(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + struct rcs_lock *lkp; + char *name; + int type; + + while ((type = rcsparse_token(rfp, RCS_TOK_SCOLON|RCS_TYPE_LOGIN)) == + RCS_TYPE_LOGIN) { + name = pdp->rp_value.str; + if (rcsparse_token(rfp, RCS_TOK_COLON) != RCS_TOK_COLON || + rcsparse_token(rfp, RCS_TYPE_REVISION) != + RCS_TYPE_REVISION) { + xfree(name); + return (1); + } + lkp = xmalloc(sizeof(*lkp)); + lkp->rl_name = name; + lkp->rl_num = pdp->rp_value.rev; + TAILQ_INSERT_TAIL(&(rfp->rf_locks), lkp, rl_list); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_locks() + * + * Parses the strict keyword of RCS file <rfp>. The strict keyword is + * optional and can be omitted. + * + * [strict;] + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_strict(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + rfp->rf_flags |= RCS_SLOCK; + + return (rcsparse_token(rfp, RCS_TOK_SCOLON) != RCS_TOK_SCOLON); +} + +/* + * rcsparse_comment() + * + * Parses the comment of RCS file <rfp>. The comment keyword is optional + * and can be omitted. + * + * [comment [@[...]@];] + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_comment(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + int type; + + type = rcsparse_token(rfp, RCS_TYPE_STRING|RCS_TOK_SCOLON); + if (type == RCS_TYPE_STRING) { + rfp->rf_comment = pdp->rp_value.str; + type = rcsparse_token(rfp, RCS_TOK_SCOLON); + } + + return (type != RCS_TOK_SCOLON); +} + +/* + * rcsparse_expand() + * + * Parses expand of RCS file <rfp>. The expand keyword is optional and + * can be omitted. + * + * [expand [@[...]@];] + * + * Returns 0 on success or 1 on failure. + */ +static int +rcsparse_expand(RCSFILE *rfp, struct rcs_pdata *pdp) +{ + int type; + + type = rcsparse_token(rfp, RCS_TYPE_STRING|RCS_TOK_SCOLON); + if (type == RCS_TYPE_STRING) { + rfp->rf_expand = pdp->rp_value.str; + type = rcsparse_token(rfp, RCS_TOK_SCOLON); + } + + return (type != RCS_TOK_SCOLON); +} + +#define RBUF_PUTC(ch) \ +do { \ + if (bp == pdp->rp_bufend - 1) { \ + len = bp - pdp->rp_buf; \ + rcsparse_growbuf(rfp); \ + bp = pdp->rp_buf + len; \ + } \ + *(bp++) = (ch); \ + pdp->rp_tlen++; \ +} while (0); + +static int +rcsparse_string(RCSFILE *rfp, int allowed) +{ + struct rcs_pdata *pdp; + int c; + size_t len; + char *bp; + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + + bp = pdp->rp_buf; + pdp->rp_tlen = 0; + *bp = '\0'; + + for (;;) { + c = getc(pdp->rp_file); + if (c == '@') { + c = getc(pdp->rp_file); + if (c == EOF) { + return (EOF); + } else if (c != '@') { + ungetc(c, pdp->rp_file); + break; + } + } + + if (c == EOF) { + return (EOF); + } else if (c == '\n') + pdp->rp_lineno++; + + RBUF_PUTC(c); + } + + bp = pdp->rp_buf + pdp->rp_tlen; + RBUF_PUTC('\0'); + + if (!(allowed & RCS_TYPE_STRING)) { + rcsparse_warnx(rfp, "unexpected RCS string"); + return (0); + } + + pdp->rp_value.str = xstrdup(pdp->rp_buf); + + return (RCS_TYPE_STRING); +} + +static int +rcsparse_token(RCSFILE *rfp, int allowed) +{ + const struct rcs_keyword *p; + struct rcs_pdata *pdp; + int c, pre, ret, type; + char *bp; + size_t len; + RCSNUM *datenum; + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + + if (pdp->rp_token != -1) { + /* no need to check for allowed here */ + type = pdp->rp_token; + pdp->rp_token = -1; + return (type); + } + + /* skip whitespaces */ + c = EOF; + do { + pre = c; + c = getc(pdp->rp_file); + if (c == EOF) { + if (ferror(pdp->rp_file)) { + rcsparse_warnx(rfp, "error during parsing"); + return (0); + } + if (pre != '\n') + rcsparse_warnx(rfp, + "no newline at end of file"); + return (EOF); + } else if (c == '\n') + pdp->rp_lineno++; + } while (isspace(c)); + + pdp->rp_msglineno = pdp->rp_lineno; + type = 0; + switch (c) { + case '@': + ret = rcsparse_string(rfp, allowed); + if (ret == EOF && ferror(pdp->rp_file)) { + rcsparse_warnx(rfp, "error during parsing"); + return (0); + } + return (ret); + /* NOTREACHED */ + case ':': + type = RCS_TOK_COLON; + if (type & allowed) + return (type); + rcsparse_warnx(rfp, "unexpected token \"%c\"", c); + return (0); + /* NOTREACHED */ + case ';': + type = RCS_TOK_SCOLON; + if (type & allowed) + return (type); + rcsparse_warnx(rfp, "unexpected token \"%c\"", c); + return (0); + /* NOTREACHED */ + case ',': + type = RCS_TOK_COMMA; + if (type & allowed) + return (type); + rcsparse_warnx(rfp, "unexpected token \"%c\"", c); + return (0); + /* NOTREACHED */ + default: + if (!isgraph(c)) { + rcsparse_warnx(rfp, "unexpected character 0x%.2X", c); + return (0); + } + break; + } + allowed &= ~(RCS_TOK_COLON|RCS_TOK_SCOLON|RCS_TOK_COMMA); + + bp = pdp->rp_buf; + pdp->rp_tlen = 0; + *bp = '\0'; + + for (;;) { + if (c == EOF) { + if (ferror(pdp->rp_file)) + rcsparse_warnx(rfp, "error during parsing"); + else + rcsparse_warnx(rfp, "unexpected end of file"); + return (0); + } else if (c == '\n') + pdp->rp_lineno++; + + RBUF_PUTC(c); + + c = getc(pdp->rp_file); + + if (isspace(c)) { + if (c == '\n') + pdp->rp_lineno++; + RBUF_PUTC('\0'); + break; + } else if (c == ';' || c == ':' || c == ',') { + ungetc(c, pdp->rp_file); + RBUF_PUTC('\0'); + break; + } else if (!isgraph(c)) { + rcsparse_warnx(rfp, "unexpected character 0x%.2X", c); + return (0); + } + } + + switch (allowed) { + case RCS_TYPE_COMMITID: + /* XXX validate commitd */ + break; + case RCS_TYPE_LOGIN: + if (!valid_login(pdp->rp_buf)) { + rcsparse_warnx(rfp, "invalid login \"%s\"", + pdp->rp_buf); + return (0); + } + pdp->rp_value.str = xstrdup(pdp->rp_buf); + break; + case RCS_TYPE_SYMBOL: + if (!rcs_sym_check(pdp->rp_buf)) { + rcsparse_warnx(rfp, "invalid symbol \"%s\"", + pdp->rp_buf); + return (0); + } + pdp->rp_value.str = xstrdup(pdp->rp_buf); + break; + /* FALLTHROUGH */ + case RCS_TYPE_STATE: + if (rcs_state_check(pdp->rp_buf)) { + rcsparse_warnx(rfp, "invalid state \"%s\"", + pdp->rp_buf); + return (0); + } + pdp->rp_value.str = xstrdup(pdp->rp_buf); + break; + case RCS_TYPE_DATE: + if ((datenum = rcsnum_parse(pdp->rp_buf)) == NULL) { + rcsparse_warnx(rfp, "invalid date \"%s\"", pdp->rp_buf); + return (0); + } + if (datenum->rn_len != 6) { + rcsnum_free(datenum); + rcsparse_warnx(rfp, "invalid date \"%s\"", pdp->rp_buf); + return (0); + } + pdp->rp_value.date.tm_year = datenum->rn_id[0]; + if (pdp->rp_value.date.tm_year >= 1900) + pdp->rp_value.date.tm_year -= 1900; + pdp->rp_value.date.tm_mon = datenum->rn_id[1] - 1; + pdp->rp_value.date.tm_mday = datenum->rn_id[2]; + pdp->rp_value.date.tm_hour = datenum->rn_id[3]; + pdp->rp_value.date.tm_min = datenum->rn_id[4]; + pdp->rp_value.date.tm_sec = datenum->rn_id[5]; + rcsnum_free(datenum); + break; + case RCS_TYPE_NUMBER: + pdp->rp_value.rev = rcsnum_parse(pdp->rp_buf); + if (pdp->rp_value.rev == NULL) { + rcsparse_warnx(rfp, "invalid number \"%s\"", + pdp->rp_buf); + return (0); + } + break; + case RCS_TYPE_BRANCH: + pdp->rp_value.rev = rcsnum_parse(pdp->rp_buf); + if (pdp->rp_value.rev == NULL) { + rcsparse_warnx(rfp, "invalid branch \"%s\"", + pdp->rp_buf); + return (0); + } + if (!RCSNUM_ISBRANCH(pdp->rp_value.rev)) { + rcsnum_free(pdp->rp_value.rev); + rcsparse_warnx(rfp, "expected branch, got \"%s\"", + pdp->rp_buf); + return (0); + } + break; + case RCS_TYPE_KEYWORD: + if (islower(*pdp->rp_buf)) { + p = bsearch(pdp->rp_buf, keywords, + sizeof(keywords) / sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + if (p != NULL) + return (p->k_val); + } + allowed = RCS_TYPE_REVISION; + /* FALLTHROUGH */ + case RCS_TYPE_REVISION: + pdp->rp_value.rev = rcsnum_parse(pdp->rp_buf); + if (pdp->rp_value.rev != NULL) { + if (RCSNUM_ISBRANCH(pdp->rp_value.rev)) { + rcsnum_free(pdp->rp_value.rev); + rcsparse_warnx(rfp, + "expected revision, got \"%s\"", + pdp->rp_buf); + return (0); + } + break; + } + /* FALLTHROUGH */ + default: + RBUF_PUTC('\0'); + rcsparse_warnx(rfp, "unexpected token \"%s\"", pdp->rp_buf); + return (0); + /* NOTREACHED */ + } + + return (allowed); +} + +static int +rcsparse(RCSFILE *rfp, struct rcs_section *sec) +{ + struct rcs_pdata *pdp; + int i, token; + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + i = 0; + + token = 0; + for (i = 0; sec[i].token != 0; i++) { + token = rcsparse_token(rfp, RCS_TYPE_KEYWORD); + if (token == 0) + return (1); + + while (token != sec[i].token) { + if (sec[i].parse == NULL) + goto end; + if (sec[i].opt) { + i++; + continue; + } + if (token == EOF || (!(rfp->rf_flags & PARSED_DELTAS) && + token == RCS_TOK_DESC)) + goto end; + rcsparse_warnx(rfp, "unexpected token \"%s\"", + pdp->rp_buf); + return (1); + } + + if (sec[i].parse(rfp, pdp)) + return (1); + } +end: + if (token == RCS_TYPE_REVISION) + pdp->rp_token = token; + else if (token == RCS_TOK_DESC) + pdp->rp_token = RCS_TOK_DESC; + else if (token == EOF) + rfp->rf_flags |= RCS_PARSED; + + return (0); +} + +static int +rcsparse_deltatext(RCSFILE *rfp) +{ + struct rcs_pdata *pdp; + int ret; + + if (rfp->rf_flags & PARSED_DELTATEXTS) + return (0); + + if (!(rfp->rf_flags & PARSED_DESC)) + if ((ret = rcsparse_desc(rfp))) + return (ret); + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + + if (rcsparse(rfp, sec_deltatext)) + return (-1); + + if (rfp->rf_flags & RCS_PARSED) { + rfp->rf_flags &= ~RCS_PARSED; + rfp->rf_flags |= PARSED_DELTATEXTS; + } + + return (1); +} + +static int +rcsparse_delta(RCSFILE *rfp) +{ + struct rcs_pdata *pdp; + + if (rfp->rf_flags & PARSED_DELTAS) + return (0); + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + if (pdp->rp_token == RCS_TOK_DESC) { + rfp->rf_flags |= PARSED_DELTAS; + return (0); + } + + if (rcsparse(rfp, sec_delta)) + return (-1); + + if (pdp->rp_delta != NULL) { + TAILQ_INSERT_TAIL(&rfp->rf_delta, pdp->rp_delta, rd_list); + pdp->rp_delta = NULL; + rfp->rf_ndelta++; + return (1); + } + + return (0); +} + +/* + * rcsparse_growbuf() + * + * Attempt to grow the internal parse buffer for the RCS file <rf> by + * RCS_BUFEXTSIZE. + * In case of failure, the original buffer is left unmodified. + */ +static void +rcsparse_growbuf(RCSFILE *rfp) +{ + struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata; + + pdp->rp_buf = xrealloc(pdp->rp_buf, 1, + pdp->rp_blen + RCS_BUFEXTSIZE); + pdp->rp_blen += RCS_BUFEXTSIZE; + pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1; +} + +/* + * Borrowed from src/usr.sbin/user/user.c: + * return 1 if `login' is a valid login name + */ +static int +valid_login(char *login_name) +{ + unsigned char *cp; + + /* The first character cannot be a hyphen */ + if (*login_name == '-') + return 0; + + for (cp = login_name ; *cp ; cp++) { + /* We allow '$' as the last character for samba */ + if (!isalnum(*cp) && *cp != '.' && *cp != '_' && *cp != '-' && + !(*cp == '$' && *(cp + 1) == '\0')) { + return 0; + } + } + if ((char *)cp - login_name > _PW_NAME_LEN) + return 0; + return 1; +} + +static int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct rcs_keyword *)e)->k_name)); +} + +static void +rcsparse_warnx(RCSFILE *rfp, char *fmt, ...) +{ + struct rcs_pdata *pdp; + va_list ap; + char *nfmt; + + pdp = (struct rcs_pdata *)rfp->rf_pdata; + va_start(ap, fmt); + if (asprintf(&nfmt, "%s:%d: %s", rfp->rf_path, pdp->rp_msglineno, fmt) + == -1) + nfmt = fmt; + vwarnx(nfmt, ap); + va_end(ap); + if (nfmt != fmt) + free(nfmt); +} diff --git a/usr.bin/rcs/rcsparse.h b/usr.bin/rcs/rcsparse.h new file mode 100644 index 00000000000..64c503a8304 --- /dev/null +++ b/usr.bin/rcs/rcsparse.h @@ -0,0 +1,21 @@ +/* $OpenBSD: rcsparse.h,v 1.1 2010/10/15 08:44:12 tobias Exp $ */ +/* + * Copyright (c) 2010 Tobias Stoeckmann <tobias@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +int rcsparse_deltas(RCSFILE *, RCSNUM *); +int rcsparse_deltatexts(RCSFILE *, RCSNUM *); +void rcsparse_free(RCSFILE *); +int rcsparse_init(RCSFILE *); |