Annotation of src/usr.bin/cvs/rcs.c, Revision 1.9
1.1 jfb 1: /* $OpenBSD$ */
2: /*
3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4: * All rights reserved.
5: *
6: * Redistribution and use in source and binary forms, with or without
7: * modification, are permitted provided that the following conditions
8: * are met:
9: *
10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
12: * 2. The name of the author may not be used to endorse or promote products
13: * derived from this software without specific prior written permission.
14: *
15: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25: */
26:
27: #include <sys/param.h>
28: #include <sys/queue.h>
29: #include <sys/stat.h>
30:
31: #include <errno.h>
32: #include <stdio.h>
33: #include <ctype.h>
34: #include <stdlib.h>
35: #include <string.h>
36:
37: #include "rcs.h"
38: #include "log.h"
39:
40: #define RCS_BUFSIZE 8192
41:
42:
43: /* RCS token types */
44: #define RCS_TOK_ERR -1
45: #define RCS_TOK_EOF 0
46: #define RCS_TOK_NUM 1
47: #define RCS_TOK_ID 2
48: #define RCS_TOK_STRING 3
49: #define RCS_TOK_SCOLON 4
50: #define RCS_TOK_COLON 5
51:
52:
53: #define RCS_TOK_HEAD 8
54: #define RCS_TOK_BRANCH 9
55: #define RCS_TOK_ACCESS 10
56: #define RCS_TOK_SYMBOLS 11
57: #define RCS_TOK_LOCKS 12
58: #define RCS_TOK_COMMENT 13
59: #define RCS_TOK_EXPAND 14
60: #define RCS_TOK_DATE 15
61: #define RCS_TOK_AUTHOR 16
62: #define RCS_TOK_STATE 17
63: #define RCS_TOK_NEXT 18
64: #define RCS_TOK_BRANCHES 19
65: #define RCS_TOK_DESC 20
66: #define RCS_TOK_LOG 21
67: #define RCS_TOK_TEXT 22
68: #define RCS_TOK_STRICT 23
69:
70: #define RCS_ISKEY(t) (((t) >= RCS_TOK_HEAD) && ((t) <= RCS_TOK_BRANCHES))
71:
72:
73: #define RCS_NOSCOL 0x01 /* no terminating semi-colon */
74: #define RCS_VOPT 0x02 /* value is optional */
75:
76:
77:
78: /* opaque parse data */
79: struct rcs_pdata {
80: u_int rp_line;
81:
82: char *rp_buf;
83: size_t rp_blen;
84:
85: /* pushback token buffer */
86: char rp_ptok[128];
87: int rp_pttype; /* token type, RCS_TOK_ERR if no token */
88:
89: FILE *rp_file;
90: };
91:
92:
93: struct rcs_line {
94: char *rl_line;
95: int rl_lineno;
96: TAILQ_ENTRY(rcs_line) rl_list;
97: };
1.5 vincent 98: TAILQ_HEAD(rcs_tqh, rcs_line);
1.1 jfb 99:
100: struct rcs_foo {
101: int rl_nblines;
102: char *rl_data;
1.5 vincent 103: struct rcs_tqh rl_lines;
1.1 jfb 104: };
105:
106: static int rcs_parse_admin (RCSFILE *);
107: static int rcs_parse_delta (RCSFILE *);
108: static int rcs_parse_deltatext (RCSFILE *);
109:
1.7 jfb 110: static int rcs_parse_access (RCSFILE *);
111: static int rcs_parse_symbols (RCSFILE *);
112: static int rcs_parse_locks (RCSFILE *);
113: static int rcs_parse_branches (RCSFILE *, struct rcs_delta *);
114: static void rcs_freedelta (struct rcs_delta *);
115: static void rcs_freepdata (struct rcs_pdata *);
116: static int rcs_gettok (RCSFILE *);
117: static int rcs_pushtok (RCSFILE *, const char *, int);
118: static int rcs_patch_lines (struct rcs_foo *, struct rcs_foo *);
119:
120: static struct rcs_delta* rcs_findrev (RCSFILE *, RCSNUM *);
121: static struct rcs_foo* rcs_splitlines (const char *);
122: static void rcs_freefoo (struct rcs_foo *);
1.1 jfb 123:
124: #define RCS_TOKSTR(rfp) ((struct rcs_pdata *)rfp->rf_pdata)->rp_buf
125: #define RCS_TOKLEN(rfp) ((struct rcs_pdata *)rfp->rf_pdata)->rp_blen
126:
127:
128: static struct rcs_key {
129: char rk_str[16];
130: int rk_id;
131: int rk_val;
132: int rk_flags;
133: } rcs_keys[] = {
134: { "access", RCS_TOK_ACCESS, RCS_TOK_ID, RCS_VOPT },
135: { "author", RCS_TOK_AUTHOR, RCS_TOK_STRING, 0 },
136: { "branch", RCS_TOK_BRANCH, RCS_TOK_NUM, RCS_VOPT },
137: { "branches", RCS_TOK_BRANCHES, RCS_TOK_NUM, RCS_VOPT },
138: { "comment", RCS_TOK_COMMENT, RCS_TOK_STRING, RCS_VOPT },
139: { "date", RCS_TOK_DATE, RCS_TOK_NUM, 0 },
140: { "desc", RCS_TOK_DESC, RCS_TOK_STRING, RCS_NOSCOL },
141: { "expand", RCS_TOK_EXPAND, RCS_TOK_STRING, RCS_VOPT },
142: { "head", RCS_TOK_HEAD, RCS_TOK_NUM, RCS_VOPT },
143: { "locks", RCS_TOK_LOCKS, RCS_TOK_ID, 0 },
144: { "log", RCS_TOK_LOG, RCS_TOK_STRING, RCS_NOSCOL },
145: { "next", RCS_TOK_NEXT, RCS_TOK_NUM, RCS_VOPT },
146: { "state", RCS_TOK_STATE, RCS_TOK_STRING, RCS_VOPT },
147: { "strict", RCS_TOK_STRICT, 0, 0, },
148: { "symbols", RCS_TOK_SYMBOLS, 0, 0 },
149: { "text", RCS_TOK_TEXT, RCS_TOK_STRING, RCS_NOSCOL },
150: };
151:
152:
153:
154: /*
155: * rcs_open()
156: *
157: * Open a file containing RCS-formatted information. The file's path is
158: * given in <path>, and the opening mode is given in <mode>, which is either
159: * RCS_MODE_READ, RCS_MODE_WRITE, or RCS_MODE_RDWR. If the mode requests write
160: * access and the file does not exist, it will be created.
161: * The file isn't actually parsed by rcs_open(); parsing is delayed until the
162: * first operation that requires information from the file.
163: * Returns a handle to the opened file on success, or NULL on failure.
164: */
165:
166: RCSFILE*
167: rcs_open(const char *path, u_int mode)
168: {
169: RCSFILE *rfp;
170: struct stat st;
171:
172: if ((stat(path, &st) == -1) && (errno == ENOENT) &&
173: !(mode & RCS_MODE_WRITE)) {
174: cvs_log(LP_ERRNO, "cannot open RCS file `%s'", path);
175: return (NULL);
176: }
177:
178: rfp = (RCSFILE *)malloc(sizeof(*rfp));
179: if (rfp == NULL) {
180: cvs_log(LP_ERRNO, "failed to allocate RCS file structure");
181: return (NULL);
182: }
183: memset(rfp, 0, sizeof(*rfp));
184:
185: rfp->rf_head = rcsnum_alloc();
186: if (rfp->rf_head == NULL) {
187: free(rfp);
188: return (NULL);
189: }
190:
191: rfp->rf_path = strdup(path);
192: if (rfp->rf_path == NULL) {
193: cvs_log(LP_ERRNO, "failed to duplicate RCS file path");
194: rcs_close(rfp);
195: return (NULL);
196: }
197:
198: rcsnum_aton(RCS_HEAD_INIT, NULL, rfp->rf_head);
199:
200: rfp->rf_ref = 1;
201: rfp->rf_flags |= RCS_RF_SLOCK;
202: rfp->rf_mode = mode;
203:
204: TAILQ_INIT(&(rfp->rf_delta));
205: TAILQ_INIT(&(rfp->rf_symbols));
206: TAILQ_INIT(&(rfp->rf_locks));
207:
208: if (rcs_parse(rfp) < 0) {
209: rcs_close(rfp);
210: return (NULL);
211: }
212:
213: return (rfp);
214: }
215:
216:
217: /*
218: * rcs_close()
219: *
220: * Close an RCS file handle.
221: */
222:
223: void
224: rcs_close(RCSFILE *rfp)
225: {
226: struct rcs_delta *rdp;
227:
228: if (rfp->rf_ref > 1) {
229: rfp->rf_ref--;
230: return;
231: }
232:
233: while (!TAILQ_EMPTY(&(rfp->rf_delta))) {
234: rdp = TAILQ_FIRST(&(rfp->rf_delta));
235: TAILQ_REMOVE(&(rfp->rf_delta), rdp, rd_list);
236: rcs_freedelta(rdp);
237: }
238:
239: if (rfp->rf_head != NULL)
240: rcsnum_free(rfp->rf_head);
241:
242: if (rfp->rf_path != NULL)
243: free(rfp->rf_path);
244: if (rfp->rf_comment != NULL)
245: free(rfp->rf_comment);
246: if (rfp->rf_expand != NULL)
247: free(rfp->rf_expand);
248: if (rfp->rf_desc != NULL)
249: free(rfp->rf_desc);
250: free(rfp);
251: }
252:
253:
254: /*
255: * rcs_write()
256: *
257: * Write the contents of the RCS file handle <rfp> to disk in the file whose
258: * path is in <rf_path>.
259: * Returns 0 on success, or -1 on failure.
260: */
261:
262: int
263: rcs_write(RCSFILE *rfp)
264: {
265: FILE *fp;
1.7 jfb 266: char buf[1024], numbuf[64], *cp;
267: size_t rlen, len;
1.1 jfb 268: struct rcs_sym *symp;
269: struct rcs_delta *rdp;
270:
271: if (rfp->rf_flags & RCS_RF_SYNCED)
272: return (0);
273:
274: fp = fopen(rfp->rf_path, "w");
275: if (fp == NULL) {
276: cvs_log(LP_ERRNO, "failed to open RCS output file `%s'",
277: rfp->rf_path);
278: return (-1);
279: }
280:
281: rcsnum_tostr(rfp->rf_head, numbuf, sizeof(numbuf));
282: fprintf(fp, "head\t%s;\n", numbuf);
283: fprintf(fp, "access;\n");
284:
285: fprintf(fp, "symbols\n");
286: TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
287: rcsnum_tostr(symp->rs_num, numbuf, sizeof(numbuf));
288: snprintf(buf, sizeof(buf), "%s:%s", symp->rs_name, numbuf);
289: fprintf(fp, "\t%s", buf);
290: if (symp != TAILQ_LAST(&(rfp->rf_symbols), rcs_slist))
291: fputc('\n', fp);
292: }
293: fprintf(fp, ";\n");
294:
295: fprintf(fp, "locks;");
296:
297: if (rfp->rf_flags & RCS_RF_SLOCK)
298: fprintf(fp, " strict;");
299: fputc('\n', fp);
300:
301: if (rfp->rf_comment != NULL)
302: fprintf(fp, "comment\t@%s@;\n", rfp->rf_comment);
303:
304: if (rfp->rf_expand != NULL)
305: fprintf(fp, "expand @ %s @;\n", rfp->rf_expand);
306:
307: fprintf(fp, "\n\n");
308:
309: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
310: fprintf(fp, "%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
311: sizeof(numbuf)));
312: fprintf(fp, "date\t%d.%02d.%02d.%02d.%02d.%02d;",
313: rdp->rd_date.tm_year, rdp->rd_date.tm_mon + 1,
314: rdp->rd_date.tm_mday, rdp->rd_date.tm_hour,
315: rdp->rd_date.tm_min, rdp->rd_date.tm_sec);
316: fprintf(fp, "\tauthor %s;\tstate %s;\n",
317: rdp->rd_author, rdp->rd_state);
318: fprintf(fp, "branches;\n");
319: fprintf(fp, "next\t%s;\n\n", rcsnum_tostr(rdp->rd_next,
320: numbuf, sizeof(numbuf)));
321: }
322:
323: fprintf(fp, "\ndesc\n@%s@\n\n", rfp->rf_desc);
324:
325: /* deltatexts */
326: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
327: fprintf(fp, "\n%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
328: sizeof(numbuf)));
1.7 jfb 329: fprintf(fp, "log\n@%s@\ntext\n@", rdp->rd_log);
330:
331: cp = rdp->rd_text;
332: do {
333: len = sizeof(buf);
334: rlen = rcs_stresc(1, cp, buf, &len);
335: fprintf(fp, "%s", buf);
336: cp += rlen;
337: } while (len != 0);
338: fprintf(fp, "@\n\n");
1.1 jfb 339: }
340: fclose(fp);
341:
342: rfp->rf_flags |= RCS_RF_SYNCED;
343:
344: return (0);
345: }
346:
347:
348: /*
349: * rcs_addsym()
350: *
351: * Add a symbol to the list of symbols for the RCS file <rfp>. The new symbol
352: * is named <sym> and is bound to the RCS revision <snum>.
353: * Returns 0 on success, or -1 on failure.
354: */
355:
356: int
357: rcs_addsym(RCSFILE *rfp, const char *sym, RCSNUM *snum)
358: {
359: struct rcs_sym *symp;
360:
361: /* first look for duplication */
362: TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
363: if (strcmp(symp->rs_name, sym) == 0) {
364: return (-1);
365: }
366: }
367:
368: symp = (struct rcs_sym *)malloc(sizeof(*symp));
369: if (symp == NULL) {
370: cvs_log(LP_ERRNO, "failed to allocate RCS symbol");
371: return (-1);
372: }
373:
374: symp->rs_name = strdup(sym);
375: symp->rs_num = rcsnum_alloc();
376: rcsnum_cpy(snum, symp->rs_num, 0);
377:
378: TAILQ_INSERT_HEAD(&(rfp->rf_symbols), symp, rs_list);
379:
380: /* not synced anymore */
381: rfp->rf_flags &= ~RCS_RF_SYNCED;
382:
383: return (0);
384: }
385:
386:
387: /*
388: * rcs_patch()
389: *
390: * Apply an RCS-format patch pointed to by <patch> to the file contents
391: * found in <data>.
392: * Returns 0 on success, or -1 on failure.
393: */
394:
395: BUF*
396: rcs_patch(const char *data, const char *patch)
397: {
1.5 vincent 398: struct rcs_foo *dlines, *plines;
399: struct rcs_line *lp;
1.1 jfb 400: size_t len;
1.5 vincent 401: int lineno;
1.1 jfb 402: BUF *res;
403:
404: len = strlen(data);
405: res = cvs_buf_alloc(len, BUF_AUTOEXT);
406: if (res == NULL)
407: return (NULL);
408:
409: dlines = rcs_splitlines(data);
410: if (dlines == NULL)
411: return (NULL);
1.5 vincent 412:
1.1 jfb 413: plines = rcs_splitlines(patch);
1.5 vincent 414: if (plines == NULL) {
415: rcs_freefoo(dlines);
1.1 jfb 416: return (NULL);
1.5 vincent 417: }
418:
419: if (rcs_patch_lines(dlines, plines) < 0) {
420: rcs_freefoo(plines);
421: rcs_freefoo(dlines);
422: return (NULL);
423: }
424:
425: lineno = 0;
426: TAILQ_FOREACH(lp, &dlines->rl_lines, rl_list) {
427: if (lineno != 0)
428: cvs_buf_fappend(res, "%s\n", lp->rl_line);
429: lineno++;
430: }
431:
432: rcs_freefoo(dlines);
433: rcs_freefoo(plines);
434: return (res);
435: }
436:
1.7 jfb 437: static int
1.5 vincent 438: rcs_patch_lines(struct rcs_foo *dlines, struct rcs_foo *plines)
439: {
440: char op, *ep;
441: struct rcs_line *lp, *dlp, *ndlp;
442: int i, lineno, nbln;
1.1 jfb 443:
444: dlp = TAILQ_FIRST(&(dlines->rl_lines));
445: lp = TAILQ_FIRST(&(plines->rl_lines));
446:
447: /* skip first bogus line */
448: for (lp = TAILQ_NEXT(lp, rl_list); lp != NULL;
449: lp = TAILQ_NEXT(lp, rl_list)) {
450: op = *(lp->rl_line);
451: lineno = (int)strtol((lp->rl_line + 1), &ep, 10);
452: if ((lineno > dlines->rl_nblines) || (lineno <= 0) ||
453: (*ep != ' ')) {
454: cvs_log(LP_ERR,
455: "invalid line specification in RCS patch");
456: return (NULL);
457: }
458: ep++;
459: nbln = (int)strtol(ep, &ep, 10);
460: if ((nbln <= 0) || (*ep != '\0')) {
461: cvs_log(LP_ERR,
462: "invalid line number specification in RCS patch");
463: return (NULL);
464: }
465:
466: /* find the appropriate line */
467: for (;;) {
468: if (dlp == NULL)
469: break;
470: if (dlp->rl_lineno == lineno)
471: break;
472: if (dlp->rl_lineno > lineno) {
473: dlp = TAILQ_PREV(dlp, rcs_tqh, rl_list);
474: }
475: else if (dlp->rl_lineno < lineno) {
476: ndlp = TAILQ_NEXT(dlp, rl_list);
477: if (ndlp->rl_lineno > lineno)
478: break;
479: dlp = ndlp;
480: }
481: }
482: if (dlp == NULL) {
483: cvs_log(LP_ERR,
484: "can't find referenced line in RCS patch");
485: return (NULL);
486: }
487:
488: if (op == 'd') {
489: for (i = 0; (i < nbln) && (dlp != NULL); i++) {
490: ndlp = TAILQ_NEXT(dlp, rl_list);
491: TAILQ_REMOVE(&(dlines->rl_lines), dlp, rl_list);
492: dlp = ndlp;
493: }
494: }
495: else if (op == 'a') {
496: for (i = 0; i < nbln; i++) {
497: ndlp = lp;
498: lp = TAILQ_NEXT(lp, rl_list);
499: if (lp == NULL) {
500: cvs_log(LP_ERR, "truncated RCS patch");
1.5 vincent 501: return (-1);
1.1 jfb 502: }
503: TAILQ_REMOVE(&(plines->rl_lines), lp, rl_list);
504: TAILQ_INSERT_AFTER(&(dlines->rl_lines), dlp,
505: lp, rl_list);
506: dlp = lp;
507:
508: /* we don't want lookup to block on those */
509: lp->rl_lineno = lineno;
510:
511: lp = ndlp;
512: }
513: }
514: else {
515: cvs_log(LP_ERR, "unknown RCS patch operation `%c'", op);
1.5 vincent 516: return (-1);
1.1 jfb 517: }
518:
519: /* last line of the patch, done */
520: if (lp->rl_lineno == plines->rl_nblines)
521: break;
522: }
523:
524: /* once we're done patching, rebuild the line numbers */
1.2 vincent 525: lineno = 0;
1.5 vincent 526: TAILQ_FOREACH(lp, &(dlines->rl_lines), rl_list)
1.1 jfb 527: lp->rl_lineno = lineno++;
528: dlines->rl_nblines = lineno - 1;
529:
1.5 vincent 530: return (0);
1.1 jfb 531: }
532:
533:
534: /*
535: * rcs_getrev()
536: *
537: * Get the whole contents of revision <rev> from the RCSFILE <rfp>. The
1.4 vincent 538: * returned buffer is dynamically allocated and should be released using
539: * cvs_buf_free() once the caller is done using it.
1.1 jfb 540: */
541:
542: BUF*
543: rcs_getrev(RCSFILE *rfp, RCSNUM *rev)
544: {
545: int res;
546: size_t len;
547: void *bp;
548: RCSNUM *crev;
549: BUF *rbuf;
550: struct rcs_delta *rdp = NULL;
551:
552: res = rcsnum_cmp(rfp->rf_head, rev, 0);
553: if (res == 1) {
554: cvs_log(LP_ERR, "sorry, can't travel in the future yet");
555: return (NULL);
556: }
557: else {
558: rdp = rcs_findrev(rfp, rfp->rf_head);
559: if (rdp == NULL) {
560: cvs_log(LP_ERR, "failed to get RCS HEAD revision");
561: return (NULL);
562: }
563:
564: len = strlen(rdp->rd_text);
565: rbuf = cvs_buf_alloc(len, BUF_AUTOEXT);
566: if (rbuf == NULL)
567: return (NULL);
568: cvs_buf_append(rbuf, rdp->rd_text, len);
569:
570: if (res != 0) {
571: /* Apply patches backwards to get the right version.
572: * This will need some rework to support sub branches.
573: */
574: crev = rcsnum_alloc();
575:
576: rcsnum_cpy(rfp->rf_head, crev, 0);
577: do {
578: crev->rn_id[crev->rn_len - 1]--;
579: rdp = rcs_findrev(rfp, crev);
580: if (rdp == NULL)
581: return (NULL);
582:
583: cvs_buf_putc(rbuf, '\0');
584: bp = cvs_buf_release(rbuf);
585: rbuf = rcs_patch((char *)bp, rdp->rd_text);
586: if (rbuf == NULL)
587: break;
588: } while (rcsnum_cmp(crev, rev, 0) != 0);
589:
590: rcsnum_free(crev);
591: }
592: }
593:
594:
595: return (rbuf);
596: }
597:
598:
599: /*
600: * rcs_getrevbydate()
601: *
602: * Get an RCS revision by a specific date.
603: */
604:
605: RCSNUM*
606: rcs_getrevbydate(RCSFILE *rfp, struct tm *date)
607: {
608: return (NULL);
609: }
610:
611:
612: /*
613: * rcs_findrev()
614: *
615: * Find a specific revision's delta entry in the tree of the RCS file <rfp>.
616: * The revision number is given in <rev>.
617: * Returns a pointer to the delta on success, or NULL on failure.
618: */
619:
620: static struct rcs_delta*
621: rcs_findrev(RCSFILE *rfp, RCSNUM *rev)
622: {
623: u_int cmplen;
624: struct rcs_delta *rdp;
625: struct rcs_dlist *hp;
1.6 vincent 626: int found;
627:
1.1 jfb 628: cmplen = 2;
629: hp = &(rfp->rf_delta);
630:
1.6 vincent 631: do {
632: found = 0;
633: TAILQ_FOREACH(rdp, hp, rd_list) {
634: if (rcsnum_cmp(rdp->rd_num, rev, cmplen) == 0) {
635: if (cmplen == rev->rn_len)
636: return (rdp);
1.1 jfb 637:
1.6 vincent 638: hp = &(rdp->rd_snodes);
639: cmplen += 2;
640: found = 1;
641: break;
642: }
1.1 jfb 643: }
1.6 vincent 644: } while (found && cmplen < rev->rn_len);
1.1 jfb 645:
646: return (NULL);
647: }
648:
649:
650: /*
651: * rcs_parse()
652: *
653: * Parse the contents of file <path>, which are in the RCS format.
654: * Returns 0 on success, or -1 on failure.
655: */
656:
657: int
658: rcs_parse(RCSFILE *rfp)
659: {
660: int ret;
661: struct rcs_pdata *pdp;
662:
663: if (rfp->rf_flags & RCS_RF_PARSED)
664: return (0);
665:
666: pdp = (struct rcs_pdata *)malloc(sizeof(*pdp));
667: if (pdp == NULL) {
668: cvs_log(LP_ERRNO, "failed to allocate RCS parser data");
669: return (-1);
670: }
671: memset(pdp, 0, sizeof(*pdp));
672:
673: pdp->rp_line = 1;
674: pdp->rp_pttype = RCS_TOK_ERR;
675:
676: pdp->rp_file = fopen(rfp->rf_path, "r");
677: if (pdp->rp_file == NULL) {
678: cvs_log(LP_ERRNO, "failed to open RCS file `%s'", rfp->rf_path);
679: rcs_freepdata(pdp);
680: return (-1);
681: }
682:
683: pdp->rp_buf = (char *)malloc(RCS_BUFSIZE);
684: if (pdp->rp_buf == NULL) {
685: cvs_log(LP_ERRNO, "failed to allocate RCS parser buffer");
686: rcs_freepdata(pdp);
687: return (-1);
688: }
689: pdp->rp_blen = RCS_BUFSIZE;
690:
691: /* ditch the strict lock */
692: rfp->rf_flags &= ~RCS_RF_SLOCK;
693: rfp->rf_pdata = pdp;
694:
695: if (rcs_parse_admin(rfp) < 0) {
696: rcs_freepdata(pdp);
697: return (-1);
698: }
699:
700: for (;;) {
701: ret = rcs_parse_delta(rfp);
702: if (ret == 0)
703: break;
704: else if (ret == -1) {
705: rcs_freepdata(pdp);
706: return (-1);
707: }
708: }
709:
710: ret = rcs_gettok(rfp);
711: if (ret != RCS_TOK_DESC) {
712: cvs_log(LP_ERR, "token `%s' found where RCS desc expected",
713: RCS_TOKSTR(rfp));
714: rcs_freepdata(pdp);
715: return (-1);
716: }
717:
718: ret = rcs_gettok(rfp);
719: if (ret != RCS_TOK_STRING) {
720: cvs_log(LP_ERR, "token `%s' found where RCS desc expected",
721: RCS_TOKSTR(rfp));
722: rcs_freepdata(pdp);
723: return (-1);
724: }
725:
726: rfp->rf_desc = strdup(RCS_TOKSTR(rfp));
727:
728: for (;;) {
729: ret = rcs_parse_deltatext(rfp);
730: if (ret == 0)
731: break;
732: else if (ret == -1) {
733: rcs_freepdata(pdp);
734: return (-1);
735: }
736: }
737:
738: cvs_log(LP_DEBUG, "RCS file `%s' parsed OK (%u lines)", rfp->rf_path,
739: pdp->rp_line);
740:
741: rcs_freepdata(pdp);
742:
743: rfp->rf_pdata = NULL;
744: rfp->rf_flags |= RCS_RF_PARSED|RCS_RF_SYNCED;
745:
746: return (0);
747: }
748:
749:
750: /*
751: * rcs_parse_admin()
752: *
753: * Parse the administrative portion of an RCS file.
754: * Returns 0 on success, or -1 on failure.
755: */
756:
757: static int
758: rcs_parse_admin(RCSFILE *rfp)
759: {
760: u_int i;
761: int tok, ntok, hmask;
762: struct rcs_key *rk;
763:
764: /* hmask is a mask of the headers already encountered */
765: hmask = 0;
766: for (;;) {
767: tok = rcs_gettok(rfp);
768: if (tok == RCS_TOK_ERR) {
769: cvs_log(LP_ERR, "parse error in RCS admin section");
770: return (-1);
771: }
772: else if (tok == RCS_TOK_NUM) {
773: /* assume this is the start of the first delta */
774: rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok);
775: return (0);
776: }
777:
778: rk = NULL;
779: for (i = 0; i < sizeof(rcs_keys)/sizeof(rcs_keys[0]); i++)
780: if (rcs_keys[i].rk_id == tok)
781: rk = &(rcs_keys[i]);
782:
783: if (hmask & (1 << tok)) {
784: cvs_log(LP_ERR, "duplicate RCS key");
785: return (-1);
786: }
787: hmask |= (1 << tok);
788:
789: switch (tok) {
790: case RCS_TOK_HEAD:
791: case RCS_TOK_BRANCH:
792: case RCS_TOK_COMMENT:
793: case RCS_TOK_EXPAND:
794: ntok = rcs_gettok(rfp);
795: if (ntok == RCS_TOK_SCOLON)
796: break;
797: if (ntok != rk->rk_val) {
798: cvs_log(LP_ERR,
799: "invalid value type for RCS key `%s'",
800: rk->rk_str);
801: }
802:
803: if (tok == RCS_TOK_HEAD) {
804: rcsnum_aton(RCS_TOKSTR(rfp), NULL,
805: rfp->rf_head);
806: }
807: else if (tok == RCS_TOK_BRANCH) {
808: rcsnum_aton(RCS_TOKSTR(rfp), NULL,
809: rfp->rf_branch);
810: }
811: else if (tok == RCS_TOK_COMMENT) {
812: rfp->rf_comment = strdup(RCS_TOKSTR(rfp));
813: }
814: else if (tok == RCS_TOK_EXPAND) {
815: rfp->rf_expand = strdup(RCS_TOKSTR(rfp));
816: }
817:
818: /* now get the expected semi-colon */
819: ntok = rcs_gettok(rfp);
820: if (ntok != RCS_TOK_SCOLON) {
821: cvs_log(LP_ERR,
822: "missing semi-colon after RCS `%s' key",
823: rk->rk_str);
824: return (-1);
825: }
826: break;
827: case RCS_TOK_ACCESS:
828: rcs_parse_access(rfp);
829: break;
830: case RCS_TOK_SYMBOLS:
831: rcs_parse_symbols(rfp);
832: break;
833: case RCS_TOK_LOCKS:
834: rcs_parse_locks(rfp);
835: break;
836: default:
837: cvs_log(LP_ERR,
838: "unexpected token `%s' in RCS admin section",
839: RCS_TOKSTR(rfp));
840: return (-1);
841: }
842: }
843:
844: return (0);
845: }
846:
847:
848: /*
849: * rcs_parse_delta()
850: *
851: * Parse an RCS delta section and allocate the structure to store that delta's
852: * information in the <rfp> delta list.
853: * Returns 1 if the section was parsed OK, 0 if it is the last delta, and
854: * -1 on error.
855: */
856:
857: static int
858: rcs_parse_delta(RCSFILE *rfp)
859: {
860: int ret, tok, ntok, hmask;
861: u_int i;
862: char *tokstr;
1.3 vincent 863: RCSNUM *datenum;
1.1 jfb 864: struct rcs_delta *rdp;
865: struct rcs_key *rk;
866:
867: rdp = (struct rcs_delta *)malloc(sizeof(*rdp));
868: if (rdp == NULL) {
869: cvs_log(LP_ERRNO, "failed to allocate RCS delta structure");
870: return (-1);
871: }
872: memset(rdp, 0, sizeof(*rdp));
873:
874: rdp->rd_num = rcsnum_alloc();
875: rdp->rd_next = rcsnum_alloc();
876:
877: TAILQ_INIT(&(rdp->rd_branches));
878:
879: tok = rcs_gettok(rfp);
880: if (tok != RCS_TOK_NUM) {
881: cvs_log(LP_ERR, "unexpected token `%s' at start of delta",
882: RCS_TOKSTR(rfp));
883: rcs_freedelta(rdp);
884: return (-1);
885: }
886: rcsnum_aton(RCS_TOKSTR(rfp), NULL, rdp->rd_num);
887:
888: hmask = 0;
889: ret = 0;
890: tokstr = NULL;
891:
892: for (;;) {
893: tok = rcs_gettok(rfp);
894: if (tok == RCS_TOK_ERR) {
895: cvs_log(LP_ERR, "parse error in RCS delta section");
896: rcs_freedelta(rdp);
897: return (-1);
898: }
899: else if (tok == RCS_TOK_NUM || tok == RCS_TOK_DESC) {
900: rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok);
901: ret = (tok == RCS_TOK_NUM ? 1 : 0);
902: break;
903: }
904:
905: rk = NULL;
906: for (i = 0; i < sizeof(rcs_keys)/sizeof(rcs_keys[0]); i++)
907: if (rcs_keys[i].rk_id == tok)
908: rk = &(rcs_keys[i]);
909:
910: if (hmask & (1 << tok)) {
911: cvs_log(LP_ERR, "duplicate RCS key");
912: rcs_freedelta(rdp);
913: return (-1);
914: }
915: hmask |= (1 << tok);
916:
917: switch (tok) {
918: case RCS_TOK_DATE:
919: case RCS_TOK_AUTHOR:
920: case RCS_TOK_STATE:
921: case RCS_TOK_NEXT:
922: ntok = rcs_gettok(rfp);
923: if (ntok == RCS_TOK_SCOLON) {
924: if (rk->rk_flags & RCS_VOPT)
925: break;
926: else {
927: cvs_log(LP_ERR, "missing mandatory "
928: "value to RCS key `%s'",
929: rk->rk_str);
930: rcs_freedelta(rdp);
931: return (-1);
932: }
933: }
934:
935: if (ntok != rk->rk_val) {
936: cvs_log(LP_ERR,
937: "invalid value type for RCS key `%s'",
938: rk->rk_str);
939: rcs_freedelta(rdp);
940: return (-1);
941: }
942:
943: if (tokstr != NULL)
944: free(tokstr);
945: tokstr = strdup(RCS_TOKSTR(rfp));
946:
947:
948: /* now get the expected semi-colon */
949: ntok = rcs_gettok(rfp);
950: if (ntok != RCS_TOK_SCOLON) {
951: cvs_log(LP_ERR,
952: "missing semi-colon after RCS `%s' key",
953: rk->rk_str);
954: rcs_freedelta(rdp);
955: return (-1);
956: }
957:
958: if (tok == RCS_TOK_DATE) {
1.3 vincent 959: datenum = rcsnum_alloc();
960: rcsnum_aton(tokstr, NULL, datenum);
961: if (datenum->rn_len != 6) {
1.1 jfb 962: cvs_log(LP_ERR,
963: "RCS date specification has %s "
964: "fields",
1.3 vincent 965: (datenum->rn_len > 6) ? "too many" :
1.1 jfb 966: "missing");
967: rcs_freedelta(rdp);
968: }
1.3 vincent 969: rdp->rd_date.tm_year = datenum->rn_id[0];
970: rdp->rd_date.tm_mon = datenum->rn_id[1] - 1;
971: rdp->rd_date.tm_mday = datenum->rn_id[2];
972: rdp->rd_date.tm_hour = datenum->rn_id[3];
973: rdp->rd_date.tm_min = datenum->rn_id[4];
974: rdp->rd_date.tm_sec = datenum->rn_id[5];
975: rcsnum_free(datenum);
1.1 jfb 976: }
977: else if (tok == RCS_TOK_AUTHOR) {
978: rdp->rd_author = tokstr;
979: tokstr = NULL;
980: }
981: else if (tok == RCS_TOK_STATE) {
982: rdp->rd_state = tokstr;
983: tokstr = NULL;
984: }
985: else if (tok == RCS_TOK_NEXT) {
986: rcsnum_aton(tokstr, NULL, rdp->rd_next);
987: }
988: break;
989: case RCS_TOK_BRANCHES:
990: rcs_parse_branches(rfp, rdp);
991: break;
992: default:
993: cvs_log(LP_ERR,
994: "unexpected token `%s' in RCS delta",
995: RCS_TOKSTR(rfp));
996: rcs_freedelta(rdp);
997: return (-1);
998: }
999: }
1000:
1001: TAILQ_INSERT_TAIL(&(rfp->rf_delta), rdp, rd_list);
1002:
1003: return (ret);
1004: }
1005:
1006:
1007: /*
1008: * rcs_parse_deltatext()
1009: *
1010: * Parse an RCS delta text section and fill in the log and text field of the
1011: * appropriate delta section.
1012: * Returns 1 if the section was parsed OK, 0 if it is the last delta, and
1013: * -1 on error.
1014: */
1015:
1016: static int
1017: rcs_parse_deltatext(RCSFILE *rfp)
1018: {
1019: int tok;
1020: RCSNUM *tnum;
1021: struct rcs_delta *rdp;
1022:
1023: tnum = rcsnum_alloc();
1024: if (tnum == NULL)
1025: return (-1);
1026:
1027: tok = rcs_gettok(rfp);
1028: if (tok == RCS_TOK_EOF)
1029: return (0);
1030:
1031: if (tok != RCS_TOK_NUM) {
1032: cvs_log(LP_ERR,
1033: "unexpected token `%s' at start of RCS delta text",
1034: RCS_TOKSTR(rfp));
1035: return (-1);
1036: }
1037: rcsnum_aton(RCS_TOKSTR(rfp), NULL, tnum);
1038:
1039: TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
1040: if (rcsnum_cmp(tnum, rdp->rd_num, 0) == 0)
1041: break;
1042: }
1043: if (rdp == NULL) {
1044: cvs_log(LP_ERR, "RCS delta text `%s' has no matching delta",
1045: RCS_TOKSTR(rfp));
1046: return (-1);
1047: }
1048:
1049: tok = rcs_gettok(rfp);
1050: if (tok != RCS_TOK_LOG) {
1051: cvs_log(LP_ERR, "unexpected token `%s' where RCS log expected",
1052: RCS_TOKSTR(rfp));
1053: return (-1);
1054: }
1055:
1056: tok = rcs_gettok(rfp);
1057: if (tok != RCS_TOK_STRING) {
1058: cvs_log(LP_ERR, "unexpected token `%s' where RCS log expected",
1059: RCS_TOKSTR(rfp));
1060: return (-1);
1061: }
1062: rdp->rd_log = strdup(RCS_TOKSTR(rfp));
1063: if (rdp->rd_log == NULL) {
1064: cvs_log(LP_ERRNO, "failed to copy RCS deltatext log");
1065: return (-1);
1066: }
1067:
1068: tok = rcs_gettok(rfp);
1069: if (tok != RCS_TOK_TEXT) {
1070: cvs_log(LP_ERR, "unexpected token `%s' where RCS text expected",
1071: RCS_TOKSTR(rfp));
1072: return (-1);
1073: }
1074:
1075: tok = rcs_gettok(rfp);
1076: if (tok != RCS_TOK_STRING) {
1077: cvs_log(LP_ERR, "unexpected token `%s' where RCS text expected",
1078: RCS_TOKSTR(rfp));
1079: return (-1);
1080: }
1081:
1082: rdp->rd_text = strdup(RCS_TOKSTR(rfp));
1083: if (rdp->rd_text == NULL) {
1084: cvs_log(LP_ERRNO, "failed to copy RCS delta text");
1085: return (-1);
1086: }
1087:
1088: return (1);
1089: }
1090:
1091:
1092: /*
1093: * rcs_parse_access()
1094: *
1095: * Parse the access list given as value to the `access' keyword.
1096: * Returns 0 on success, or -1 on failure.
1097: */
1098:
1099: static int
1100: rcs_parse_access(RCSFILE *rfp)
1101: {
1102: int type;
1103:
1104: while ((type = rcs_gettok(rfp)) != RCS_TOK_SCOLON) {
1105: if (type != RCS_TOK_ID) {
1106: cvs_log(LP_ERR, "unexpected token `%s' in access list",
1107: RCS_TOKSTR(rfp));
1108: return (-1);
1109: }
1110: }
1111:
1112: return (0);
1113: }
1114:
1115:
1116: /*
1117: * rcs_parse_symbols()
1118: *
1119: * Parse the symbol list given as value to the `symbols' keyword.
1120: * Returns 0 on success, or -1 on failure.
1121: */
1122:
1123: static int
1124: rcs_parse_symbols(RCSFILE *rfp)
1125: {
1126: int type;
1127: struct rcs_sym *symp;
1128:
1129: for (;;) {
1130: type = rcs_gettok(rfp);
1131: if (type == RCS_TOK_SCOLON)
1132: break;
1133:
1134: if (type != RCS_TOK_STRING) {
1135: cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
1136: RCS_TOKSTR(rfp));
1137: return (-1);
1138: }
1139:
1140: symp = (struct rcs_sym *)malloc(sizeof(*symp));
1141: if (symp == NULL) {
1142: cvs_log(LP_ERRNO, "failed to allocate RCS symbol");
1143: return (-1);
1144: }
1145: symp->rs_name = strdup(RCS_TOKSTR(rfp));
1146: symp->rs_num = rcsnum_alloc();
1147:
1148: type = rcs_gettok(rfp);
1149: if (type != RCS_TOK_COLON) {
1150: cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
1151: RCS_TOKSTR(rfp));
1152: free(symp->rs_name);
1153: free(symp);
1154: return (-1);
1155: }
1156:
1157: type = rcs_gettok(rfp);
1158: if (type != RCS_TOK_NUM) {
1159: cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
1160: RCS_TOKSTR(rfp));
1161: free(symp->rs_name);
1162: free(symp);
1163: return (-1);
1164: }
1165:
1166: if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, symp->rs_num) < 0) {
1167: cvs_log(LP_ERR, "failed to parse RCS NUM `%s'",
1168: RCS_TOKSTR(rfp));
1169: free(symp->rs_name);
1170: free(symp);
1171: return (-1);
1172: }
1173:
1174: TAILQ_INSERT_HEAD(&(rfp->rf_symbols), symp, rs_list);
1175: }
1176:
1177: return (0);
1178: }
1179:
1180:
1181: /*
1182: * rcs_parse_locks()
1183: *
1184: * Parse the lock list given as value to the `locks' keyword.
1185: * Returns 0 on success, or -1 on failure.
1186: */
1187:
1188: static int
1189: rcs_parse_locks(RCSFILE *rfp)
1190: {
1191: int type;
1192: struct rcs_lock *lkp;
1193:
1194: for (;;) {
1195: type = rcs_gettok(rfp);
1196: if (type == RCS_TOK_SCOLON)
1197: break;
1198:
1199: if (type != RCS_TOK_ID) {
1200: cvs_log(LP_ERR, "unexpected token `%s' in lock list",
1201: RCS_TOKSTR(rfp));
1202: return (-1);
1203: }
1204:
1205: lkp = (struct rcs_lock *)malloc(sizeof(*lkp));
1206: if (lkp == NULL) {
1207: cvs_log(LP_ERRNO, "failed to allocate RCS lock");
1208: return (-1);
1209: }
1210: lkp->rl_num = rcsnum_alloc();
1211:
1212: type = rcs_gettok(rfp);
1213: if (type != RCS_TOK_COLON) {
1214: cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
1215: RCS_TOKSTR(rfp));
1216: free(lkp);
1217: return (-1);
1218: }
1219:
1220: type = rcs_gettok(rfp);
1221: if (type != RCS_TOK_NUM) {
1222: cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
1223: RCS_TOKSTR(rfp));
1224: free(lkp);
1225: return (-1);
1226: }
1227:
1228: if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, lkp->rl_num) < 0) {
1229: cvs_log(LP_ERR, "failed to parse RCS NUM `%s'",
1230: RCS_TOKSTR(rfp));
1231: free(lkp);
1232: return (-1);
1233: }
1234:
1235: TAILQ_INSERT_HEAD(&(rfp->rf_locks), lkp, rl_list);
1236: }
1237:
1238: /* check if we have a `strict' */
1239: type = rcs_gettok(rfp);
1240: if (type != RCS_TOK_STRICT) {
1241: rcs_pushtok(rfp, RCS_TOKSTR(rfp), type);
1242: }
1243: else {
1244: rfp->rf_flags |= RCS_RF_SLOCK;
1245:
1246: type = rcs_gettok(rfp);
1247: if (type != RCS_TOK_SCOLON) {
1248: cvs_log(LP_ERR,
1249: "missing semi-colon after `strict' keyword");
1250: return (-1);
1251: }
1252: }
1253:
1254: return (0);
1255: }
1256:
1257: /*
1258: * rcs_parse_branches()
1259: *
1260: * Parse the list of branches following a `branches' keyword in a delta.
1261: * Returns 0 on success, or -1 on failure.
1262: */
1263:
1264: static int
1265: rcs_parse_branches(RCSFILE *rfp, struct rcs_delta *rdp)
1266: {
1267: int type;
1268: struct rcs_branch *brp;
1269:
1270: for (;;) {
1271: type = rcs_gettok(rfp);
1272: if (type == RCS_TOK_SCOLON)
1273: break;
1274:
1275: if (type != RCS_TOK_NUM) {
1276: cvs_log(LP_ERR,
1277: "unexpected token `%s' in list of branches",
1278: RCS_TOKSTR(rfp));
1279: return (-1);
1280: }
1281:
1282: brp = (struct rcs_branch *)malloc(sizeof(*brp));
1283: if (brp == NULL) {
1284: cvs_log(LP_ERRNO, "failed to allocate RCS branch");
1285: return (-1);
1286: }
1287: brp->rb_num = rcsnum_alloc();
1288: rcsnum_aton(RCS_TOKSTR(rfp), NULL, brp->rb_num);
1289:
1290: TAILQ_INSERT_TAIL(&(rdp->rd_branches), brp, rb_list);
1291: }
1292:
1293: return (0);
1294: }
1295:
1296:
1297: /*
1298: * rcs_freedelta()
1299: *
1300: * Free the contents of a delta structure.
1301: */
1302:
1303: void
1304: rcs_freedelta(struct rcs_delta *rdp)
1305: {
1306: struct rcs_delta *crdp;
1307:
1308: if (rdp->rd_author != NULL)
1309: free(rdp->rd_author);
1310: if (rdp->rd_state != NULL)
1311: free(rdp->rd_state);
1312: if (rdp->rd_log != NULL)
1313: free(rdp->rd_log);
1314: if (rdp->rd_text != NULL)
1315: free(rdp->rd_text);
1316:
1317: while ((crdp = TAILQ_FIRST(&(rdp->rd_snodes))) != NULL) {
1318: TAILQ_REMOVE(&(rdp->rd_snodes), crdp, rd_list);
1319: rcs_freedelta(crdp);
1320: }
1321:
1322: free(rdp);
1323: }
1324:
1325:
1326: /*
1327: * rcs_freepdata()
1328: *
1329: * Free the contents of the parser data structure.
1330: */
1331:
1332: static void
1333: rcs_freepdata(struct rcs_pdata *pd)
1334: {
1335: if (pd->rp_file != NULL)
1336: (void)fclose(pd->rp_file);
1337: if (pd->rp_buf != NULL)
1338: free(pd->rp_buf);
1339: free(pd);
1340: }
1341:
1342:
1343: /*
1344: * rcs_gettok()
1345: *
1346: * Get the next RCS token from the string <str>.
1347: */
1348:
1349: static int
1350: rcs_gettok(RCSFILE *rfp)
1351: {
1352: u_int i;
1353: int ch, last, type;
1354: char *bp, *bep;
1355: struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata;
1356:
1357: type = RCS_TOK_ERR;
1358: bp = pdp->rp_buf;
1359: bep = pdp->rp_buf + pdp->rp_blen - 1;
1360: *bp = '\0';
1361:
1362: if (pdp->rp_pttype != RCS_TOK_ERR) {
1363: type = pdp->rp_pttype;
1364: strlcpy(pdp->rp_buf, pdp->rp_ptok, pdp->rp_blen);
1365: pdp->rp_pttype = RCS_TOK_ERR;
1366: return (type);
1367: }
1368:
1369: /* skip leading whitespace */
1370: /* XXX we must skip backspace too for compatibility, should we? */
1371: do {
1372: ch = getc(pdp->rp_file);
1373: if (ch == '\n')
1374: pdp->rp_line++;
1375: } while (isspace(ch));
1376:
1377: if (ch == EOF) {
1378: type = RCS_TOK_EOF;
1379: }
1380: else if (ch == ';') {
1381: type = RCS_TOK_SCOLON;
1382: }
1383: else if (ch == ':') {
1384: type = RCS_TOK_COLON;
1385: }
1386: else if (isalpha(ch)) {
1387: *(bp++) = ch;
1388: while (bp <= bep - 1) {
1389: ch = getc(pdp->rp_file);
1390: if (!isalnum(ch)) {
1391: ungetc(ch, pdp->rp_file);
1392: break;
1393: }
1394: *(bp++) = ch;
1395: }
1396: *bp = '\0';
1397:
1398: for (i = 0; i < sizeof(rcs_keys)/sizeof(rcs_keys[0]); i++) {
1399: if (strcmp(rcs_keys[i].rk_str, pdp->rp_buf) == 0) {
1400: type = rcs_keys[i].rk_id;
1401: break;
1402: }
1403: }
1404:
1405: /* not a keyword, assume it's just a string */
1406: if (type == RCS_TOK_ERR)
1407: type = RCS_TOK_STRING;
1408: }
1409: else if (ch == '@') {
1410: /* we have a string */
1411: for (;;) {
1412: ch = getc(pdp->rp_file);
1413: if (ch == '@') {
1414: ch = getc(pdp->rp_file);
1415: if (ch != '@') {
1416: ungetc(ch, pdp->rp_file);
1417: break;
1418: }
1419: }
1420: else if (ch == '\n')
1421: pdp->rp_line++;
1422:
1423: *(bp++) = ch;
1424: if (bp == bep)
1425: break;
1426: }
1427:
1428: *bp = '\0';
1429: type = RCS_TOK_STRING;
1430: }
1431: else if (isdigit(ch)) {
1432: *(bp++) = ch;
1433: last = ch;
1434: type = RCS_TOK_NUM;
1435:
1436: for (;;) {
1437: ch = getc(pdp->rp_file);
1438: if (bp == bep)
1439: break;
1440: if (!isdigit(ch) && ch != '.') {
1441: ungetc(ch, pdp->rp_file);
1442: break;
1443: }
1444:
1445: if (last == '.' && ch == '.') {
1446: type = RCS_TOK_ERR;
1447: break;
1448: }
1449: last = ch;
1450: *(bp++) = ch;
1451: }
1452: *(bp) = '\0';
1453: }
1454:
1455: return (type);
1456: }
1457:
1458:
1459: /*
1460: * rcs_pushtok()
1461: *
1462: * Push a token back in the parser's token buffer.
1463: */
1464:
1465: static int
1466: rcs_pushtok(RCSFILE *rfp, const char *tok, int type)
1467: {
1468: struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata;
1469:
1470: if (pdp->rp_pttype != RCS_TOK_ERR)
1471: return (-1);
1472:
1473: pdp->rp_pttype = type;
1474: strlcpy(pdp->rp_ptok, tok, sizeof(pdp->rp_ptok));
1475: return (0);
1476: }
1477:
1478:
1479: /*
1480: * rcs_stresc()
1481: *
1482: * Performs either escaping or unescaping of the string stored in <str>.
1483: * The operation is to escape special RCS characters if the <esc> argument
1484: * is 1, or unescape otherwise. The result is stored in the <buf> destination
1485: * buffer, and <blen> must originally point to the size of <buf>.
1486: * Returns the number of bytes which have been read from the source <str> and
1487: * operated on. The <blen> parameter will contain the number of bytes
1488: * actually copied in <buf>.
1489: */
1490:
1491: size_t
1492: rcs_stresc(int esc, const char *str, char *buf, size_t *blen)
1493: {
1494: size_t rlen;
1495: const char *sp;
1496: char *bp, *bep;
1497:
1498: rlen = 0;
1499: bp = buf;
1500: bep = buf + *blen - 1;
1501:
1502: for (sp = str; (*sp != '\0') && (bp <= (bep - 1)); sp++) {
1503: if (*sp == '@') {
1504: if (esc) {
1505: if (bp > (bep - 2))
1506: break;
1507: *(bp++) = '@';
1508: }
1509: else {
1510: sp++;
1511: if (*sp != '@') {
1512: cvs_log(LP_WARN,
1513: "unknown escape character `%c' in "
1514: "RCS file", *sp);
1515: if (*sp == '\0')
1516: break;
1517: }
1518: }
1519: }
1520:
1521: *(bp++) = *sp;
1522: }
1523:
1524: *bp = '\0';
1525: *blen = (bp - buf);
1526: return (sp - str);
1527: }
1528:
1529:
1530: /*
1531: * rcs_splitlines()
1532: *
1533: * Split the contents of a file into a list of lines.
1534: */
1535:
1536: static struct rcs_foo*
1537: rcs_splitlines(const char *fcont)
1538: {
1539: char *dcp;
1540: struct rcs_foo *foo;
1541: struct rcs_line *lp;
1542:
1543: foo = (struct rcs_foo *)malloc(sizeof(*foo));
1544: if (foo == NULL) {
1545: cvs_log(LP_ERR, "failed to allocate line structure");
1546: return (NULL);
1547: }
1548: TAILQ_INIT(&(foo->rl_lines));
1549: foo->rl_nblines = 0;
1550: foo->rl_data = strdup(fcont);
1551: if (foo->rl_data == NULL) {
1552: cvs_log(LP_ERRNO, "failed to copy file contents");
1553: free(foo);
1554: return (NULL);
1555: }
1556:
1557: /*
1558: * Add a first bogus line with line number 0. This is used so we
1559: * can position the line pointer before 1 when changing the first line
1560: * in rcs_patch().
1561: */
1562: lp = (struct rcs_line *)malloc(sizeof(*lp));
1.5 vincent 1563: if (lp == NULL)
1.1 jfb 1564: return (NULL);
1.5 vincent 1565:
1.1 jfb 1566: lp->rl_line = NULL;
1567: lp->rl_lineno = 0;
1568: TAILQ_INSERT_TAIL(&(foo->rl_lines), lp, rl_list);
1569:
1570:
1571: for (dcp = foo->rl_data; *dcp != '\0';) {
1572: lp = (struct rcs_line *)malloc(sizeof(*lp));
1573: if (lp == NULL) {
1574: cvs_log(LP_ERR, "failed to allocate line entry");
1575: return (NULL);
1576: }
1577:
1578: lp->rl_line = dcp;
1579: lp->rl_lineno = ++(foo->rl_nblines);
1580: TAILQ_INSERT_TAIL(&(foo->rl_lines), lp, rl_list);
1581:
1582: dcp = strchr(dcp, '\n');
1583: if (dcp == NULL) {
1584: break;
1585: }
1586: *(dcp++) = '\0';
1587: }
1588:
1589: return (foo);
1.5 vincent 1590: }
1591:
1592: static void
1593: rcs_freefoo(struct rcs_foo *fp)
1594: {
1595: struct rcs_line *lp;
1596:
1597: while ((lp = TAILQ_FIRST(&fp->rl_lines)) != NULL) {
1598: TAILQ_REMOVE(&fp->rl_lines, lp, rl_list);
1599: free(lp);
1600: }
1601: free(fp->rl_data);
1602: free(fp);
1.1 jfb 1603: }