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