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/cvs/rcsparse.c | |
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/cvs/rcsparse.c')
-rw-r--r-- | usr.bin/cvs/rcsparse.c | 1262 |
1 files changed, 1262 insertions, 0 deletions
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); +} |