xref: /openbsd-src/usr.bin/cvs/rcs.c (revision 94fd4554194a14f126fba33b837cc68a1df42468)
1 /*	$OpenBSD: rcs.c,v 1.210 2007/02/22 06:42:09 otto Exp $	*/
2 /*
3  * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. The name of the author may not be used to endorse or promote products
13  *    derived from this software without specific prior written permission.
14  *
15  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include <sys/stat.h>
28 
29 #include <ctype.h>
30 #include <errno.h>
31 #include <libgen.h>
32 #include <pwd.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 
37 #include "cvs.h"
38 #include "diff.h"
39 #include "rcs.h"
40 
41 #define RCS_BUFSIZE	16384
42 #define RCS_BUFEXTSIZE	8192
43 #define RCS_KWEXP_SIZE  1024
44 
45 /* RCS token types */
46 #define RCS_TOK_ERR	-1
47 #define RCS_TOK_EOF	0
48 #define RCS_TOK_NUM	1
49 #define RCS_TOK_ID	2
50 #define RCS_TOK_STRING	3
51 #define RCS_TOK_SCOLON	4
52 #define RCS_TOK_COLON	5
53 
54 #define RCS_TOK_HEAD		8
55 #define RCS_TOK_BRANCH		9
56 #define RCS_TOK_ACCESS		10
57 #define RCS_TOK_SYMBOLS		11
58 #define RCS_TOK_LOCKS		12
59 #define RCS_TOK_COMMENT		13
60 #define RCS_TOK_EXPAND		14
61 #define RCS_TOK_DATE		15
62 #define RCS_TOK_AUTHOR		16
63 #define RCS_TOK_STATE		17
64 #define RCS_TOK_NEXT		18
65 #define RCS_TOK_BRANCHES	19
66 #define RCS_TOK_DESC		20
67 #define RCS_TOK_LOG		21
68 #define RCS_TOK_TEXT		22
69 #define RCS_TOK_STRICT		23
70 
71 #define RCS_ISKEY(t)	(((t) >= RCS_TOK_HEAD) && ((t) <= RCS_TOK_BRANCHES))
72 
73 #define RCS_NOSCOL	0x01	/* no terminating semi-colon */
74 #define RCS_VOPT	0x02	/* value is optional */
75 
76 /* opaque parse data */
77 struct rcs_pdata {
78 	u_int	rp_lines;
79 
80 	char	*rp_buf;
81 	size_t	 rp_blen;
82 	char	*rp_bufend;
83 	size_t	 rp_tlen;
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 #define RCS_TOKSTR(rfp)	((struct rcs_pdata *)rfp->rf_pdata)->rp_buf
93 #define RCS_TOKLEN(rfp)	((struct rcs_pdata *)rfp->rf_pdata)->rp_tlen
94 
95 /* invalid characters in RCS symbol names */
96 static const char rcs_sym_invch[] = RCS_SYM_INVALCHAR;
97 
98 /* comment leaders, depending on the file's suffix */
99 static const struct rcs_comment {
100 	const char	*rc_suffix;
101 	const char	*rc_cstr;
102 } rcs_comments[] = {
103 	{ "1",    ".\\\" " },
104 	{ "2",    ".\\\" " },
105 	{ "3",    ".\\\" " },
106 	{ "4",    ".\\\" " },
107 	{ "5",    ".\\\" " },
108 	{ "6",    ".\\\" " },
109 	{ "7",    ".\\\" " },
110 	{ "8",    ".\\\" " },
111 	{ "9",    ".\\\" " },
112 	{ "a",    "-- "    },	/* Ada		 */
113 	{ "ada",  "-- "    },
114 	{ "adb",  "-- "    },
115 	{ "asm",  ";; "    },	/* assembler (MS-DOS) */
116 	{ "ads",  "-- "    },	/* Ada */
117 	{ "bat",  ":: "    },	/* batch (MS-DOS) */
118 	{ "body", "-- "    },	/* Ada */
119 	{ "c",    " * "    },	/* C */
120 	{ "c++",  "// "    },	/* C++ */
121 	{ "cc",   "// "    },
122 	{ "cpp",  "// "    },
123 	{ "cxx",  "// "    },
124 	{ "m",    "// "    },	/* Objective-C */
125 	{ "cl",   ";;; "   },	/* Common Lisp	 */
126 	{ "cmd",  ":: "    },	/* command (OS/2) */
127 	{ "cmf",  "c "     },	/* CM Fortran	 */
128 	{ "csh",  "# "     },	/* shell	 */
129 	{ "e",    "# "     },	/* efl		 */
130 	{ "epsf", "% "     },	/* encapsulated postscript */
131 	{ "epsi", "% "     },	/* encapsulated postscript */
132 	{ "el",   "; "     },	/* Emacs Lisp	 */
133 	{ "f",    "c "     },	/* Fortran	 */
134 	{ "for",  "c "     },
135 	{ "h",    " * "    },	/* C-header	 */
136 	{ "hh",   "// "    },	/* C++ header	 */
137 	{ "hpp",  "// "    },
138 	{ "hxx",  "// "    },
139 	{ "in",   "# "     },	/* for Makefile.in */
140 	{ "l",    " * "    },	/* lex */
141 	{ "mac",  ";; "    },	/* macro (DEC-10, MS-DOS, PDP-11, VMS, etc) */
142 	{ "mak",  "# "     },	/* makefile, e.g. Visual C++ */
143 	{ "me",   ".\\\" " },	/* me-macros	t/nroff	 */
144 	{ "ml",   "; "     },	/* mocklisp	 */
145 	{ "mm",   ".\\\" " },	/* mm-macros	t/nroff	 */
146 	{ "ms",   ".\\\" " },	/* ms-macros	t/nroff	 */
147 	{ "man",  ".\\\" " },	/* man-macros	t/nroff	 */
148 	{ "p",    " * "    },	/* pascal	 */
149 	{ "pas",  " * "    },
150 	{ "pl",   "# "     },	/* Perl	(conflict with Prolog) */
151 	{ "pm",   "# "     },	/* Perl	module */
152 	{ "ps",   "% "     },	/* postscript */
153 	{ "psw",  "% "     },	/* postscript wrap */
154 	{ "pswm", "% "     },	/* postscript wrap */
155 	{ "r",    "# "     },	/* ratfor	 */
156 	{ "rc",   " * "    },	/* Microsoft Windows resource file */
157 	{ "red",  "% "     },	/* psl/rlisp	 */
158 	{ "sh",   "# "     },	/* shell	 */
159 	{ "sl",   "% "     },	/* psl		 */
160 	{ "spec", "-- "    },	/* Ada		 */
161 	{ "tex",  "% "     },	/* tex		 */
162 	{ "y",    " * "    },	/* yacc		 */
163 	{ "ye",   " * "    },	/* yacc-efl	 */
164 	{ "yr",   " * "    },	/* yacc-ratfor	 */
165 };
166 
167 struct rcs_kw rcs_expkw[] =  {
168 	{ "Author",	RCS_KW_AUTHOR   },
169 	{ "Date",	RCS_KW_DATE     },
170 	{ "Header",	RCS_KW_HEADER   },
171 	{ "Id",		RCS_KW_ID       },
172 	{ "Log",	RCS_KW_LOG      },
173 	{ "Name",	RCS_KW_NAME     },
174 	{ "RCSfile",	RCS_KW_RCSFILE  },
175 	{ "Revision",	RCS_KW_REVISION },
176 	{ "Source",	RCS_KW_SOURCE   },
177 	{ "State",	RCS_KW_STATE    },
178 };
179 
180 #define NB_COMTYPES	(sizeof(rcs_comments)/sizeof(rcs_comments[0]))
181 
182 static struct rcs_key {
183 	char	rk_str[16];
184 	int	rk_id;
185 	int	rk_val;
186 	int	rk_flags;
187 } rcs_keys[] = {
188 	{ "access",   RCS_TOK_ACCESS,   RCS_TOK_ID,     RCS_VOPT     },
189 	{ "author",   RCS_TOK_AUTHOR,   RCS_TOK_ID,     0            },
190 	{ "branch",   RCS_TOK_BRANCH,   RCS_TOK_NUM,    RCS_VOPT     },
191 	{ "branches", RCS_TOK_BRANCHES, RCS_TOK_NUM,    RCS_VOPT     },
192 	{ "comment",  RCS_TOK_COMMENT,  RCS_TOK_STRING, RCS_VOPT     },
193 	{ "date",     RCS_TOK_DATE,     RCS_TOK_NUM,    0            },
194 	{ "desc",     RCS_TOK_DESC,     RCS_TOK_STRING, RCS_NOSCOL   },
195 	{ "expand",   RCS_TOK_EXPAND,   RCS_TOK_STRING, RCS_VOPT     },
196 	{ "head",     RCS_TOK_HEAD,     RCS_TOK_NUM,    RCS_VOPT     },
197 	{ "locks",    RCS_TOK_LOCKS,    RCS_TOK_ID,     0            },
198 	{ "log",      RCS_TOK_LOG,      RCS_TOK_STRING, RCS_NOSCOL   },
199 	{ "next",     RCS_TOK_NEXT,     RCS_TOK_NUM,    RCS_VOPT     },
200 	{ "state",    RCS_TOK_STATE,    RCS_TOK_ID,     RCS_VOPT     },
201 	{ "strict",   RCS_TOK_STRICT,   0,              0,           },
202 	{ "symbols",  RCS_TOK_SYMBOLS,  0,              0            },
203 	{ "text",     RCS_TOK_TEXT,     RCS_TOK_STRING, RCS_NOSCOL   },
204 };
205 
206 #define RCS_NKEYS	(sizeof(rcs_keys)/sizeof(rcs_keys[0]))
207 
208 static const char *rcs_errstrs[] = {
209 	"No error",
210 	"No such entry",
211 	"Duplicate entry found",
212 	"Bad RCS number",
213 	"Invalid RCS symbol",
214 	"Parse error",
215 };
216 
217 #define RCS_NERR   (sizeof(rcs_errstrs)/sizeof(rcs_errstrs[0]))
218 
219 int rcs_errno = RCS_ERR_NOERR;
220 
221 int		rcs_patch_lines(struct cvs_lines *, struct cvs_lines *);
222 static void	rcs_parse_init(RCSFILE *);
223 static int	rcs_parse_admin(RCSFILE *);
224 static int	rcs_parse_delta(RCSFILE *);
225 static void	rcs_parse_deltas(RCSFILE *, RCSNUM *);
226 static int	rcs_parse_deltatext(RCSFILE *);
227 static void	rcs_parse_deltatexts(RCSFILE *, RCSNUM *);
228 static void	rcs_parse_desc(RCSFILE *, RCSNUM *);
229 
230 static int	rcs_parse_access(RCSFILE *);
231 static int	rcs_parse_symbols(RCSFILE *);
232 static int	rcs_parse_locks(RCSFILE *);
233 static int	rcs_parse_branches(RCSFILE *, struct rcs_delta *);
234 static void	rcs_freedelta(struct rcs_delta *);
235 static void	rcs_freepdata(struct rcs_pdata *);
236 static int	rcs_gettok(RCSFILE *);
237 static int	rcs_pushtok(RCSFILE *, const char *, int);
238 static void	rcs_growbuf(RCSFILE *);
239 static void	rcs_strprint(const u_char *, size_t, FILE *);
240 
241 static void	rcs_kwexp_line(char *, struct rcs_delta *, struct cvs_line *,
242 		    int mode);
243 
244 RCSFILE *
245 rcs_open(const char *path, int fd, int flags, ...)
246 {
247 	int mode;
248 	mode_t fmode;
249 	RCSFILE *rfp;
250 	va_list vap;
251 	struct rcs_delta *rdp;
252 	struct rcs_lock *lkr;
253 
254 	fmode = S_IRUSR|S_IRGRP|S_IROTH;
255 	flags &= 0xffff;	/* ditch any internal flags */
256 
257 	if (flags & RCS_CREATE) {
258 		va_start(vap, flags);
259 		mode = va_arg(vap, int);
260 		va_end(vap);
261 		fmode = (mode_t)mode;
262 	}
263 
264 	rfp = xcalloc(1, sizeof(*rfp));
265 
266 	rfp->rf_path = xstrdup(path);
267 	rfp->rf_flags = flags | RCS_SLOCK | RCS_SYNCED;
268 	rfp->rf_mode = fmode;
269 	rfp->fd = fd;
270 	rfp->rf_dead = 0;
271 	rfp->rf_inattic = 0;
272 
273 	TAILQ_INIT(&(rfp->rf_delta));
274 	TAILQ_INIT(&(rfp->rf_access));
275 	TAILQ_INIT(&(rfp->rf_symbols));
276 	TAILQ_INIT(&(rfp->rf_locks));
277 
278 	if (!(rfp->rf_flags & RCS_CREATE))
279 		rcs_parse_init(rfp);
280 
281 	/* fill in rd_locker */
282 	TAILQ_FOREACH(lkr, &(rfp->rf_locks), rl_list) {
283 		if ((rdp = rcs_findrev(rfp, lkr->rl_num)) == NULL) {
284 			rcs_close(rfp);
285 			return (NULL);
286 		}
287 
288 		rdp->rd_locker = xstrdup(lkr->rl_name);
289 	}
290 
291 	return (rfp);
292 }
293 
294 /*
295  * rcs_close()
296  *
297  * Close an RCS file handle.
298  */
299 void
300 rcs_close(RCSFILE *rfp)
301 {
302 	struct rcs_delta *rdp;
303 	struct rcs_access *rap;
304 	struct rcs_lock *rlp;
305 	struct rcs_sym *rsp;
306 
307 	if ((rfp->rf_flags & RCS_WRITE) && !(rfp->rf_flags & RCS_SYNCED))
308 		rcs_write(rfp);
309 
310 	while (!TAILQ_EMPTY(&(rfp->rf_delta))) {
311 		rdp = TAILQ_FIRST(&(rfp->rf_delta));
312 		TAILQ_REMOVE(&(rfp->rf_delta), rdp, rd_list);
313 		rcs_freedelta(rdp);
314 	}
315 
316 	while (!TAILQ_EMPTY(&(rfp->rf_access))) {
317 		rap = TAILQ_FIRST(&(rfp->rf_access));
318 		TAILQ_REMOVE(&(rfp->rf_access), rap, ra_list);
319 		xfree(rap->ra_name);
320 		xfree(rap);
321 	}
322 
323 	while (!TAILQ_EMPTY(&(rfp->rf_symbols))) {
324 		rsp = TAILQ_FIRST(&(rfp->rf_symbols));
325 		TAILQ_REMOVE(&(rfp->rf_symbols), rsp, rs_list);
326 		rcsnum_free(rsp->rs_num);
327 		xfree(rsp->rs_name);
328 		xfree(rsp);
329 	}
330 
331 	while (!TAILQ_EMPTY(&(rfp->rf_locks))) {
332 		rlp = TAILQ_FIRST(&(rfp->rf_locks));
333 		TAILQ_REMOVE(&(rfp->rf_locks), rlp, rl_list);
334 		rcsnum_free(rlp->rl_num);
335 		xfree(rlp->rl_name);
336 		xfree(rlp);
337 	}
338 
339 	if (rfp->rf_head != NULL)
340 		rcsnum_free(rfp->rf_head);
341 	if (rfp->rf_branch != NULL)
342 		rcsnum_free(rfp->rf_branch);
343 
344 	if (rfp->rf_path != NULL)
345 		xfree(rfp->rf_path);
346 	if (rfp->rf_comment != NULL)
347 		xfree(rfp->rf_comment);
348 	if (rfp->rf_expand != NULL)
349 		xfree(rfp->rf_expand);
350 	if (rfp->rf_desc != NULL)
351 		xfree(rfp->rf_desc);
352 	if (rfp->rf_pdata != NULL)
353 		rcs_freepdata(rfp->rf_pdata);
354 	xfree(rfp);
355 }
356 
357 /*
358  * rcs_write()
359  *
360  * Write the contents of the RCS file handle <rfp> to disk in the file whose
361  * path is in <rf_path>.
362  */
363 void
364 rcs_write(RCSFILE *rfp)
365 {
366 	FILE *fp;
367 	char buf[1024], numbuf[64], *fn, tmpdir[MAXPATHLEN];
368 	struct rcs_access *ap;
369 	struct rcs_sym *symp;
370 	struct rcs_branch *brp;
371 	struct rcs_delta *rdp;
372 	struct rcs_lock *lkp;
373 	size_t len;
374 	int fd, saved_errno;
375 
376 	fd = -1;
377 
378 	if (rfp->rf_flags & RCS_SYNCED)
379 		return;
380 
381 	if (cvs_noexec == 1)
382 		return;
383 
384 	/* Write operations need the whole file parsed */
385 	rcs_parse_deltatexts(rfp, NULL);
386 
387 	if (strlcpy(tmpdir, rfp->rf_path, sizeof(tmpdir)) >= sizeof(tmpdir))
388 		fatal("rcs_write: truncation");
389 	(void)xasprintf(&fn, "%s/rcs.XXXXXXXXXX", dirname(tmpdir));
390 
391 	if ((fd = mkstemp(fn)) == -1)
392 		fatal("%s", fn);
393 
394 	if ((fp = fdopen(fd, "w")) == NULL) {
395 		saved_errno = errno;
396 		(void)unlink(fn);
397 		fatal("fdopen %s: %s", fn, strerror(saved_errno));
398 	}
399 
400 	if (rfp->rf_head != NULL)
401 		rcsnum_tostr(rfp->rf_head, numbuf, sizeof(numbuf));
402 	else
403 		numbuf[0] = '\0';
404 
405 	fprintf(fp, "head\t%s;\n", numbuf);
406 
407 	if (rfp->rf_branch != NULL) {
408 		rcsnum_tostr(rfp->rf_branch, numbuf, sizeof(numbuf));
409 		fprintf(fp, "branch\t%s;\n", numbuf);
410 	}
411 
412 	fputs("access", fp);
413 	TAILQ_FOREACH(ap, &(rfp->rf_access), ra_list) {
414 		fprintf(fp, "\n\t%s", ap->ra_name);
415 	}
416 	fputs(";\n", fp);
417 
418 	fprintf(fp, "symbols");
419 	TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
420 		rcsnum_tostr(symp->rs_num, numbuf, sizeof(numbuf));
421 		if (strlcpy(buf, symp->rs_name, sizeof(buf)) >= sizeof(buf) ||
422 		    strlcat(buf, ":", sizeof(buf)) >= sizeof(buf) ||
423 		    strlcat(buf, numbuf, sizeof(buf)) >= sizeof(buf))
424 			fatal("rcs_write: string overflow");
425 		fprintf(fp, "\n\t%s", buf);
426 	}
427 	fprintf(fp, ";\n");
428 
429 	fprintf(fp, "locks");
430 	TAILQ_FOREACH(lkp, &(rfp->rf_locks), rl_list) {
431 		rcsnum_tostr(lkp->rl_num, numbuf, sizeof(numbuf));
432 		fprintf(fp, "\n\t%s:%s", lkp->rl_name, numbuf);
433 	}
434 
435 	fprintf(fp, ";");
436 
437 	if (rfp->rf_flags & RCS_SLOCK)
438 		fprintf(fp, " strict;");
439 	fputc('\n', fp);
440 
441 	fputs("comment\t@", fp);
442 	if (rfp->rf_comment != NULL) {
443 		rcs_strprint((const u_char *)rfp->rf_comment,
444 		    strlen(rfp->rf_comment), fp);
445 		fputs("@;\n", fp);
446 	} else
447 		fputs("# @;\n", fp);
448 
449 	if (rfp->rf_expand != NULL) {
450 		fputs("expand @", fp);
451 		rcs_strprint((const u_char *)rfp->rf_expand,
452 		    strlen(rfp->rf_expand), fp);
453 		fputs("@;\n", fp);
454 	}
455 
456 	fputs("\n\n", fp);
457 
458 	TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
459 		fprintf(fp, "%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
460 		    sizeof(numbuf)));
461 		fprintf(fp, "date\t%d.%02d.%02d.%02d.%02d.%02d;",
462 		    rdp->rd_date.tm_year + 1900, rdp->rd_date.tm_mon + 1,
463 		    rdp->rd_date.tm_mday, rdp->rd_date.tm_hour,
464 		    rdp->rd_date.tm_min, rdp->rd_date.tm_sec);
465 		fprintf(fp, "\tauthor %s;\tstate %s;\n",
466 		    rdp->rd_author, rdp->rd_state);
467 		fputs("branches", fp);
468 		TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
469 			fprintf(fp, " %s", rcsnum_tostr(brp->rb_num, numbuf,
470 			    sizeof(numbuf)));
471 		}
472 		fputs(";\n", fp);
473 		fprintf(fp, "next\t%s;\n\n", rcsnum_tostr(rdp->rd_next,
474 		    numbuf, sizeof(numbuf)));
475 	}
476 
477 	fputs("\ndesc\n@", fp);
478 	if (rfp->rf_desc != NULL && (len = strlen(rfp->rf_desc)) > 0) {
479 		rcs_strprint((const u_char *)rfp->rf_desc, len, fp);
480 		if (rfp->rf_desc[len-1] != '\n')
481 			fputc('\n', fp);
482 	}
483 	fputs("@\n", fp);
484 
485 	/* deltatexts */
486 	TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
487 		fprintf(fp, "\n\n%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
488 		    sizeof(numbuf)));
489 		fputs("log\n@", fp);
490 		if (rdp->rd_log != NULL) {
491 			len = strlen(rdp->rd_log);
492 			rcs_strprint((const u_char *)rdp->rd_log, len, fp);
493 			if (rdp->rd_log[len-1] != '\n')
494 				fputc('\n', fp);
495 		}
496 		fputs("@\ntext\n@", fp);
497 		if (rdp->rd_text != NULL) {
498 			rcs_strprint(rdp->rd_text, rdp->rd_tlen, fp);
499 		}
500 		fputs("@\n", fp);
501 	}
502 	if (fchmod(fd, rfp->rf_mode) == -1) {
503 		saved_errno = errno;
504 		(void)unlink(fn);
505 		fatal("fchmod %s: %s", fn, strerror(saved_errno));
506 	}
507 
508 	(void)fclose(fp);
509 
510 	if (rename(fn, rfp->rf_path) == -1) {
511 		saved_errno = errno;
512 		(void)unlink(fn);
513 		fatal("rename(%s, %s): %s", fn, rfp->rf_path,
514 		    strerror(saved_errno));
515 	}
516 
517 	rfp->rf_flags |= RCS_SYNCED;
518 
519 	if (fn != NULL)
520 		xfree(fn);
521 }
522 
523 /*
524  * rcs_head_get()
525  *
526  * Retrieve the revision number of the head revision for the RCS file <file>.
527  */
528 RCSNUM *
529 rcs_head_get(RCSFILE *file)
530 {
531 	char br[16];
532 	RCSNUM *rev;
533 
534 	if (file->rf_branch != NULL) {
535 		rcsnum_tostr(file->rf_branch, br, sizeof(br));
536 		rev = rcs_translate_tag(br, file);
537 	} else {
538 		rev = rcsnum_alloc();
539 		rcsnum_cpy(file->rf_head, rev, 0);
540 	}
541 
542 	return (rev);
543 }
544 
545 /*
546  * rcs_head_set()
547  *
548  * Set the revision number of the head revision for the RCS file <file> to
549  * <rev>, which must reference a valid revision within the file.
550  */
551 int
552 rcs_head_set(RCSFILE *file, RCSNUM *rev)
553 {
554 	if (rcs_findrev(file, rev) == NULL)
555 		return (-1);
556 
557 	if (file->rf_head == NULL)
558 		file->rf_head = rcsnum_alloc();
559 
560 	rcsnum_cpy(rev, file->rf_head, 0);
561 	file->rf_flags &= ~RCS_SYNCED;
562 	return (0);
563 }
564 
565 
566 /*
567  * rcs_branch_get()
568  *
569  * Retrieve the default branch number for the RCS file <file>.
570  * Returns the number on success.  If NULL is returned, then there is no
571  * default branch for this file.
572  */
573 const RCSNUM *
574 rcs_branch_get(RCSFILE *file)
575 {
576 	return (file->rf_branch);
577 }
578 
579 /*
580  * rcs_branch_set()
581  *
582  * Set the default branch for the RCS file <file> to <bnum>.
583  * Returns 0 on success, -1 on failure.
584  */
585 int
586 rcs_branch_set(RCSFILE *file, const RCSNUM *bnum)
587 {
588 	if (file->rf_branch == NULL)
589 		file->rf_branch = rcsnum_alloc();
590 
591 	rcsnum_cpy(bnum, file->rf_branch, 0);
592 	file->rf_flags &= ~RCS_SYNCED;
593 	return (0);
594 }
595 
596 /*
597  * rcs_access_add()
598  *
599  * Add the login name <login> to the access list for the RCS file <file>.
600  * Returns 0 on success, or -1 on failure.
601  */
602 int
603 rcs_access_add(RCSFILE *file, const char *login)
604 {
605 	struct rcs_access *ap;
606 
607 	/* first look for duplication */
608 	TAILQ_FOREACH(ap, &(file->rf_access), ra_list) {
609 		if (strcmp(ap->ra_name, login) == 0) {
610 			rcs_errno = RCS_ERR_DUPENT;
611 			return (-1);
612 		}
613 	}
614 
615 	ap = xmalloc(sizeof(*ap));
616 	ap->ra_name = xstrdup(login);
617 	TAILQ_INSERT_TAIL(&(file->rf_access), ap, ra_list);
618 
619 	/* not synced anymore */
620 	file->rf_flags &= ~RCS_SYNCED;
621 	return (0);
622 }
623 
624 /*
625  * rcs_access_remove()
626  *
627  * Remove an entry with login name <login> from the access list of the RCS
628  * file <file>.
629  * Returns 0 on success, or -1 on failure.
630  */
631 int
632 rcs_access_remove(RCSFILE *file, const char *login)
633 {
634 	struct rcs_access *ap;
635 
636 	TAILQ_FOREACH(ap, &(file->rf_access), ra_list)
637 		if (strcmp(ap->ra_name, login) == 0)
638 			break;
639 
640 	if (ap == NULL) {
641 		rcs_errno = RCS_ERR_NOENT;
642 		return (-1);
643 	}
644 
645 	TAILQ_REMOVE(&(file->rf_access), ap, ra_list);
646 	xfree(ap->ra_name);
647 	xfree(ap);
648 
649 	/* not synced anymore */
650 	file->rf_flags &= ~RCS_SYNCED;
651 	return (0);
652 }
653 
654 /*
655  * rcs_sym_add()
656  *
657  * Add a symbol to the list of symbols for the RCS file <rfp>.  The new symbol
658  * is named <sym> and is bound to the RCS revision <snum>.
659  * Returns 0 on success, or -1 on failure.
660  */
661 int
662 rcs_sym_add(RCSFILE *rfp, const char *sym, RCSNUM *snum)
663 {
664 	struct rcs_sym *symp;
665 
666 	if (!rcs_sym_check(sym)) {
667 		rcs_errno = RCS_ERR_BADSYM;
668 		return (-1);
669 	}
670 
671 	/* first look for duplication */
672 	TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
673 		if (strcmp(symp->rs_name, sym) == 0) {
674 			rcs_errno = RCS_ERR_DUPENT;
675 			return (-1);
676 		}
677 	}
678 
679 	symp = xmalloc(sizeof(*symp));
680 	symp->rs_name = xstrdup(sym);
681 	symp->rs_num = rcsnum_alloc();
682 	rcsnum_cpy(snum, symp->rs_num, 0);
683 
684 	TAILQ_INSERT_HEAD(&(rfp->rf_symbols), symp, rs_list);
685 
686 	/* not synced anymore */
687 	rfp->rf_flags &= ~RCS_SYNCED;
688 	return (0);
689 }
690 
691 /*
692  * rcs_sym_remove()
693  *
694  * Remove the symbol with name <sym> from the symbol list for the RCS file
695  * <file>.  If no such symbol is found, the call fails and returns with an
696  * error.
697  * Returns 0 on success, or -1 on failure.
698  */
699 int
700 rcs_sym_remove(RCSFILE *file, const char *sym)
701 {
702 	struct rcs_sym *symp;
703 
704 	if (!rcs_sym_check(sym)) {
705 		rcs_errno = RCS_ERR_BADSYM;
706 		return (-1);
707 	}
708 
709 	TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
710 		if (strcmp(symp->rs_name, sym) == 0)
711 			break;
712 
713 	if (symp == NULL) {
714 		rcs_errno = RCS_ERR_NOENT;
715 		return (-1);
716 	}
717 
718 	TAILQ_REMOVE(&(file->rf_symbols), symp, rs_list);
719 	xfree(symp->rs_name);
720 	rcsnum_free(symp->rs_num);
721 	xfree(symp);
722 
723 	/* not synced anymore */
724 	file->rf_flags &= ~RCS_SYNCED;
725 	return (0);
726 }
727 
728 /*
729  * rcs_sym_get()
730  *
731  * Find a specific symbol <sym> entry in the tree of the RCS file <file>.
732  *
733  * Returns a pointer to the symbol on success, or NULL on failure.
734  */
735 struct rcs_sym *
736 rcs_sym_get(RCSFILE *file, const char *sym)
737 {
738 	struct rcs_sym *symp;
739 
740 	TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
741 		if (strcmp(symp->rs_name, sym) == 0)
742 			return (symp);
743 
744 	return (NULL);
745 }
746 
747 /*
748  * rcs_sym_getrev()
749  *
750  * Retrieve the RCS revision number associated with the symbol <sym> for the
751  * RCS file <file>.  The returned value is a dynamically-allocated copy and
752  * should be freed by the caller once they are done with it.
753  * Returns the RCSNUM on success, or NULL on failure.
754  */
755 RCSNUM *
756 rcs_sym_getrev(RCSFILE *file, const char *sym)
757 {
758 	RCSNUM *num;
759 	struct rcs_sym *symp;
760 
761 	if (!rcs_sym_check(sym)) {
762 		rcs_errno = RCS_ERR_BADSYM;
763 		return (NULL);
764 	}
765 
766 	if (!strcmp(sym, RCS_HEAD_BRANCH)) {
767 		num = rcsnum_alloc();
768 		rcsnum_cpy(file->rf_head, num, 0);
769 		return (num);
770 	}
771 
772 	num = NULL;
773 	TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
774 		if (strcmp(symp->rs_name, sym) == 0)
775 			break;
776 
777 	if (symp == NULL) {
778 		rcs_errno = RCS_ERR_NOENT;
779 	} else {
780 		num = rcsnum_alloc();
781 		rcsnum_cpy(symp->rs_num, num, 0);
782 	}
783 
784 	return (num);
785 }
786 
787 /*
788  * rcs_sym_check()
789  *
790  * Check the RCS symbol name <sym> for any unsupported characters.
791  * Returns 1 if the tag is correct, 0 if it isn't valid.
792  */
793 int
794 rcs_sym_check(const char *sym)
795 {
796 	int ret;
797 	const char *cp;
798 
799 	ret = 1;
800 	cp = sym;
801 	if (!isalpha(*cp++))
802 		return (0);
803 
804 	for (; *cp != '\0'; cp++)
805 		if (!isgraph(*cp) || (strchr(rcs_sym_invch, *cp) != NULL)) {
806 			ret = 0;
807 			break;
808 		}
809 
810 	return (ret);
811 }
812 
813 /*
814  * rcs_lock_getmode()
815  *
816  * Retrieve the locking mode of the RCS file <file>.
817  */
818 int
819 rcs_lock_getmode(RCSFILE *file)
820 {
821 	return (file->rf_flags & RCS_SLOCK) ? RCS_LOCK_STRICT : RCS_LOCK_LOOSE;
822 }
823 
824 /*
825  * rcs_lock_setmode()
826  *
827  * Set the locking mode of the RCS file <file> to <mode>, which must either
828  * be RCS_LOCK_LOOSE or RCS_LOCK_STRICT.
829  * Returns the previous mode on success, or -1 on failure.
830  */
831 int
832 rcs_lock_setmode(RCSFILE *file, int mode)
833 {
834 	int pmode;
835 	pmode = rcs_lock_getmode(file);
836 
837 	if (mode == RCS_LOCK_STRICT)
838 		file->rf_flags |= RCS_SLOCK;
839 	else if (mode == RCS_LOCK_LOOSE)
840 		file->rf_flags &= ~RCS_SLOCK;
841 	else
842 		fatal("rcs_lock_setmode: invalid mode `%d'", mode);
843 
844 	file->rf_flags &= ~RCS_SYNCED;
845 	return (pmode);
846 }
847 
848 /*
849  * rcs_lock_add()
850  *
851  * Add an RCS lock for the user <user> on revision <rev>.
852  * Returns 0 on success, or -1 on failure.
853  */
854 int
855 rcs_lock_add(RCSFILE *file, const char *user, RCSNUM *rev)
856 {
857 	struct rcs_lock *lkp;
858 
859 	/* first look for duplication */
860 	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
861 		if (strcmp(lkp->rl_name, user) == 0 &&
862 		    rcsnum_cmp(rev, lkp->rl_num, 0) == 0) {
863 			rcs_errno = RCS_ERR_DUPENT;
864 			return (-1);
865 		}
866 	}
867 
868 	lkp = xmalloc(sizeof(*lkp));
869 	lkp->rl_name = xstrdup(user);
870 	lkp->rl_num = rcsnum_alloc();
871 	rcsnum_cpy(rev, lkp->rl_num, 0);
872 
873 	TAILQ_INSERT_TAIL(&(file->rf_locks), lkp, rl_list);
874 
875 	/* not synced anymore */
876 	file->rf_flags &= ~RCS_SYNCED;
877 	return (0);
878 }
879 
880 
881 /*
882  * rcs_lock_remove()
883  *
884  * Remove the RCS lock on revision <rev>.
885  * Returns 0 on success, or -1 on failure.
886  */
887 int
888 rcs_lock_remove(RCSFILE *file, const char *user, RCSNUM *rev)
889 {
890 	struct rcs_lock *lkp;
891 
892 	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
893 		if (strcmp(lkp->rl_name, user) == 0 &&
894 		    rcsnum_cmp(lkp->rl_num, rev, 0) == 0)
895 			break;
896 	}
897 
898 	if (lkp == NULL) {
899 		rcs_errno = RCS_ERR_NOENT;
900 		return (-1);
901 	}
902 
903 	TAILQ_REMOVE(&(file->rf_locks), lkp, rl_list);
904 	rcsnum_free(lkp->rl_num);
905 	xfree(lkp->rl_name);
906 	xfree(lkp);
907 
908 	/* not synced anymore */
909 	file->rf_flags &= ~RCS_SYNCED;
910 	return (0);
911 }
912 
913 /*
914  * rcs_desc_get()
915  *
916  * Retrieve the description for the RCS file <file>.
917  */
918 const char *
919 rcs_desc_get(RCSFILE *file)
920 {
921 	return (file->rf_desc);
922 }
923 
924 /*
925  * rcs_desc_set()
926  *
927  * Set the description for the RCS file <file>.
928  */
929 void
930 rcs_desc_set(RCSFILE *file, const char *desc)
931 {
932 	char *tmp;
933 
934 	tmp = xstrdup(desc);
935 	if (file->rf_desc != NULL)
936 		xfree(file->rf_desc);
937 	file->rf_desc = tmp;
938 	file->rf_flags &= ~RCS_SYNCED;
939 }
940 
941 /*
942  * rcs_comment_lookup()
943  *
944  * Lookup the assumed comment leader based on a file's suffix.
945  * Returns a pointer to the string on success, or NULL on failure.
946  */
947 const char *
948 rcs_comment_lookup(const char *filename)
949 {
950 	int i;
951 	const char *sp;
952 
953 	if ((sp = strrchr(filename, '.')) == NULL) {
954 		rcs_errno = RCS_ERR_NOENT;
955 		return (NULL);
956 	}
957 	sp++;
958 
959 	for (i = 0; i < (int)NB_COMTYPES; i++)
960 		if (strcmp(rcs_comments[i].rc_suffix, sp) == 0)
961 			return (rcs_comments[i].rc_cstr);
962 	return (NULL);
963 }
964 
965 /*
966  * rcs_comment_get()
967  *
968  * Retrieve the comment leader for the RCS file <file>.
969  */
970 const char *
971 rcs_comment_get(RCSFILE *file)
972 {
973 	return (file->rf_comment);
974 }
975 
976 /*
977  * rcs_comment_set()
978  *
979  * Set the comment leader for the RCS file <file>.
980  */
981 void
982 rcs_comment_set(RCSFILE *file, const char *comment)
983 {
984 	char *tmp;
985 
986 	tmp = xstrdup(comment);
987 	if (file->rf_comment != NULL)
988 		xfree(file->rf_comment);
989 	file->rf_comment = tmp;
990 	file->rf_flags &= ~RCS_SYNCED;
991 }
992 
993 /*
994  * rcs_tag_resolve()
995  *
996  * Retrieve the revision number corresponding to the tag <tag> for the RCS
997  * file <file>.
998  */
999 RCSNUM *
1000 rcs_tag_resolve(RCSFILE *file, const char *tag)
1001 {
1002 	RCSNUM *num;
1003 
1004 	if ((num = rcsnum_parse(tag)) == NULL) {
1005 		num = rcs_sym_getrev(file, tag);
1006 	}
1007 
1008 	return (num);
1009 }
1010 
1011 int
1012 rcs_patch_lines(struct cvs_lines *dlines, struct cvs_lines *plines)
1013 {
1014 	u_char op;
1015 	char *ep;
1016 	struct cvs_line *lp, *dlp, *ndlp;
1017 	int i, lineno, nbln;
1018 	u_char tmp;
1019 
1020 	dlp = TAILQ_FIRST(&(dlines->l_lines));
1021 	lp = TAILQ_FIRST(&(plines->l_lines));
1022 
1023 	/* skip first bogus line */
1024 	for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
1025 	    lp = TAILQ_NEXT(lp, l_list)) {
1026 		if (lp->l_len < 2)
1027 			fatal("line too short, RCS patch seems broken");
1028 		op = *(lp->l_line);
1029 		/* NUL-terminate line buffer for strtol() safety. */
1030 		tmp = lp->l_line[lp->l_len - 1];
1031 		lp->l_line[lp->l_len - 1] = '\0';
1032 		lineno = (int)strtol((char*)(lp->l_line + 1), &ep, 10);
1033 		if (lineno - 1 > dlines->l_nblines || lineno < 0) {
1034 			fatal("invalid line specification in RCS patch");
1035 		}
1036 		ep++;
1037 		nbln = (int)strtol(ep, &ep, 10);
1038 		/* Restore the last byte of the buffer */
1039 		lp->l_line[lp->l_len - 1] = tmp;
1040 		if (nbln < 0)
1041 			fatal("invalid line number specification in RCS patch");
1042 
1043 		/* find the appropriate line */
1044 		for (;;) {
1045 			if (dlp == NULL)
1046 				break;
1047 			if (dlp->l_lineno == lineno)
1048 				break;
1049 			if (dlp->l_lineno > lineno) {
1050 				dlp = TAILQ_PREV(dlp, cvs_tqh, l_list);
1051 			} else if (dlp->l_lineno < lineno) {
1052 				if (((ndlp = TAILQ_NEXT(dlp, l_list)) == NULL) ||
1053 				    ndlp->l_lineno > lineno)
1054 					break;
1055 				dlp = ndlp;
1056 			}
1057 		}
1058 		if (dlp == NULL)
1059 			fatal("can't find referenced line in RCS patch");
1060 
1061 		if (op == 'd') {
1062 			for (i = 0; (i < nbln) && (dlp != NULL); i++) {
1063 				ndlp = TAILQ_NEXT(dlp, l_list);
1064 				TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list);
1065 				xfree(dlp);
1066 				dlp = ndlp;
1067 				/* last line is gone - reset dlp */
1068 				if (dlp == NULL) {
1069 					ndlp = TAILQ_LAST(&(dlines->l_lines),
1070 					    cvs_tqh);
1071 					dlp = ndlp;
1072 				}
1073 			}
1074 		} else if (op == 'a') {
1075 			for (i = 0; i < nbln; i++) {
1076 				ndlp = lp;
1077 				lp = TAILQ_NEXT(lp, l_list);
1078 				if (lp == NULL)
1079 					fatal("truncated RCS patch");
1080 				TAILQ_REMOVE(&(plines->l_lines), lp, l_list);
1081 				TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp,
1082 				    lp, l_list);
1083 				dlp = lp;
1084 
1085 				/* we don't want lookup to block on those */
1086 				lp->l_lineno = lineno;
1087 
1088 				lp = ndlp;
1089 			}
1090 		} else
1091 			fatal("unknown RCS patch operation `%c'", op);
1092 
1093 		/* last line of the patch, done */
1094 		if (lp->l_lineno == plines->l_nblines)
1095 			break;
1096 	}
1097 
1098 	/* once we're done patching, rebuild the line numbers */
1099 	lineno = 0;
1100 	TAILQ_FOREACH(lp, &(dlines->l_lines), l_list)
1101 		lp->l_lineno = lineno++;
1102 	dlines->l_nblines = lineno - 1;
1103 
1104 	return (0);
1105 }
1106 
1107 /*
1108  * rcs_rev_add()
1109  *
1110  * Add a revision to the RCS file <rf>.  The new revision's number can be
1111  * specified in <rev> (which can also be RCS_HEAD_REV, in which case the
1112  * new revision will have a number equal to the previous head revision plus
1113  * one).  The <msg> argument specifies the log message for that revision, and
1114  * <date> specifies the revision's date (a value of -1 is
1115  * equivalent to using the current time).
1116  * If <username> is NULL, set the author for this revision to the current user.
1117  * Otherwise, set it to <username>.
1118  * Returns 0 on success, or -1 on failure.
1119  */
1120 int
1121 rcs_rev_add(RCSFILE *rf, RCSNUM *rev, const char *msg, time_t date,
1122     const char *username)
1123 {
1124 	time_t now;
1125 	struct passwd *pw;
1126 	struct rcs_branch *brp;
1127 	struct rcs_delta *ordp, *rdp;
1128 
1129 	if (rev == RCS_HEAD_REV) {
1130 		if (rf->rf_flags & RCS_CREATE) {
1131 			if ((rev = rcsnum_parse(RCS_HEAD_INIT)) == NULL)
1132 				return (-1);
1133 			rf->rf_head = rcsnum_alloc();
1134 			rcsnum_cpy(rev, rf->rf_head, 0);
1135 		} else {
1136 			rev = rcsnum_inc(rf->rf_head);
1137 		}
1138 	} else {
1139 		if ((rdp = rcs_findrev(rf, rev)) != NULL) {
1140 			rcs_errno = RCS_ERR_DUPENT;
1141 			return (-1);
1142 		}
1143 	}
1144 
1145 	if ((pw = getpwuid(getuid())) == NULL)
1146 		fatal("getpwuid failed");
1147 
1148 	rdp = xcalloc(1, sizeof(*rdp));
1149 
1150 	TAILQ_INIT(&(rdp->rd_branches));
1151 
1152 	rdp->rd_num = rcsnum_alloc();
1153 	rcsnum_cpy(rev, rdp->rd_num, 0);
1154 
1155 	rdp->rd_next = rcsnum_alloc();
1156 
1157 	if (username == NULL)
1158 		username = pw->pw_name;
1159 
1160 	rdp->rd_author = xstrdup(username);
1161 	rdp->rd_state = xstrdup(RCS_STATE_EXP);
1162 	rdp->rd_log = xstrdup(msg);
1163 
1164 	if (date != (time_t)(-1))
1165 		now = date;
1166 	else
1167 		time(&now);
1168 	gmtime_r(&now, &(rdp->rd_date));
1169 
1170 	if (RCSNUM_ISBRANCHREV(rev))
1171 		TAILQ_INSERT_TAIL(&(rf->rf_delta), rdp, rd_list);
1172 	else
1173 		TAILQ_INSERT_HEAD(&(rf->rf_delta), rdp, rd_list);
1174 	rf->rf_ndelta++;
1175 
1176 	if (!(rf->rf_flags & RCS_CREATE)) {
1177 		if (RCSNUM_ISBRANCHREV(rev)) {
1178 			brp = xmalloc(sizeof(*brp));
1179 			brp->rb_num = rcsnum_alloc();
1180 			rcsnum_cpy(rdp->rd_num, brp->rb_num, 0);
1181 			TAILQ_INSERT_TAIL(&(rdp->rd_branches), brp, rb_list);
1182 			ordp = TAILQ_PREV(rdp, rcs_dlist, rd_list);
1183 			rcsnum_cpy(rdp->rd_num, ordp->rd_next, 0);
1184 		} else {
1185 			ordp = TAILQ_NEXT(rdp, rd_list);
1186 			rcsnum_cpy(ordp->rd_num, rdp->rd_next, 0);
1187 		}
1188 	}
1189 
1190 	/* not synced anymore */
1191 	rf->rf_flags &= ~RCS_SYNCED;
1192 
1193 	return (0);
1194 }
1195 
1196 /*
1197  * rcs_rev_remove()
1198  *
1199  * Remove the revision whose number is <rev> from the RCS file <rf>.
1200  */
1201 int
1202 rcs_rev_remove(RCSFILE *rf, RCSNUM *rev)
1203 {
1204 	char *path_tmp1, *path_tmp2;
1205 	struct rcs_delta *rdp, *prevrdp, *nextrdp;
1206 	BUF *prevbuf, *newdiff, *newdeltatext;
1207 
1208 	if (rev == RCS_HEAD_REV)
1209 		rev = rf->rf_head;
1210 
1211 	/* do we actually have that revision? */
1212 	if ((rdp = rcs_findrev(rf, rev)) == NULL) {
1213 		rcs_errno = RCS_ERR_NOENT;
1214 		return (-1);
1215 	}
1216 
1217 	/*
1218 	 * This is confusing, the previous delta is next in the TAILQ list.
1219 	 * the next delta is the previous one in the TAILQ list.
1220 	 *
1221 	 * When the HEAD revision got specified, nextrdp will be NULL.
1222 	 * When the first revision got specified, prevrdp will be NULL.
1223 	 */
1224 	prevrdp = (struct rcs_delta *)TAILQ_NEXT(rdp, rd_list);
1225 	nextrdp = (struct rcs_delta *)TAILQ_PREV(rdp, cvs_tqh, rd_list);
1226 
1227 	newdeltatext = NULL;
1228 	prevbuf = NULL;
1229 
1230 	if (prevrdp != NULL && nextrdp != NULL) {
1231 		newdiff = cvs_buf_alloc(64, BUF_AUTOEXT);
1232 
1233 		/* calculate new diff */
1234 		(void)xasprintf(&path_tmp1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
1235 		rcs_rev_write_stmp(rf, nextrdp->rd_num, path_tmp1, 0);
1236 
1237 		(void)xasprintf(&path_tmp2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
1238 		rcs_rev_write_stmp(rf, prevrdp->rd_num, path_tmp2, 0);
1239 
1240 		diff_format = D_RCSDIFF;
1241 		if (cvs_diffreg(path_tmp1, path_tmp2, newdiff) == D_ERROR)
1242 			fatal("rcs_diffreg failed");
1243 
1244 		newdeltatext = newdiff;
1245 	} else if (nextrdp == NULL && prevrdp != NULL) {
1246 		newdeltatext = prevbuf;
1247 	}
1248 
1249 	if (newdeltatext != NULL) {
1250 		if (rcs_deltatext_set(rf, prevrdp->rd_num, newdeltatext) < 0)
1251 			fatal("error setting new deltatext");
1252 	}
1253 
1254 	TAILQ_REMOVE(&(rf->rf_delta), rdp, rd_list);
1255 
1256 	/* update pointers */
1257 	if (prevrdp != NULL && nextrdp != NULL) {
1258 		rcsnum_cpy(prevrdp->rd_num, nextrdp->rd_next, 0);
1259 	} else if (prevrdp != NULL) {
1260 		if (rcs_head_set(rf, prevrdp->rd_num) < 0)
1261 			fatal("rcs_head_set failed");
1262 	} else if (nextrdp != NULL) {
1263 		rcsnum_free(nextrdp->rd_next);
1264 		nextrdp->rd_next = rcsnum_alloc();
1265 	} else {
1266 		rcsnum_free(rf->rf_head);
1267 		rf->rf_head = NULL;
1268 	}
1269 
1270 	rf->rf_ndelta--;
1271 	rf->rf_flags &= ~RCS_SYNCED;
1272 
1273 	rcs_freedelta(rdp);
1274 
1275 	if (newdeltatext != NULL)
1276 		xfree(newdeltatext);
1277 
1278 	if (path_tmp1 != NULL)
1279 		xfree(path_tmp1);
1280 	if (path_tmp2 != NULL)
1281 		xfree(path_tmp2);
1282 
1283 	return (0);
1284 }
1285 
1286 /*
1287  * rcs_findrev()
1288  *
1289  * Find a specific revision's delta entry in the tree of the RCS file <rfp>.
1290  * The revision number is given in <rev>.
1291  *
1292  * Returns a pointer to the delta on success, or NULL on failure.
1293  */
1294 struct rcs_delta *
1295 rcs_findrev(RCSFILE *rfp, RCSNUM *rev)
1296 {
1297 	int isbrev;
1298 	struct rcs_delta *rdp;
1299 
1300 	isbrev = RCSNUM_ISBRANCHREV(rev);
1301 
1302 	/*
1303 	 * We need to do more parsing if the last revision in the linked list
1304 	 * is greater than the requested revision.
1305 	 */
1306 	rdp = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
1307 	if (rdp == NULL ||
1308 	    (!isbrev && rcsnum_cmp(rdp->rd_num, rev, 0) == -1) ||
1309 	    ((isbrev && rdp->rd_num->rn_len < 4) ||
1310 	    (isbrev && rcsnum_differ(rev, rdp->rd_num)))) {
1311 		rcs_parse_deltas(rfp, rev);
1312 	}
1313 
1314 	TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
1315 		if (rcsnum_differ(rdp->rd_num, rev))
1316 			continue;
1317 		else
1318 			return (rdp);
1319 	}
1320 
1321 	return (NULL);
1322 }
1323 
1324 /*
1325  * rcs_kwexp_set()
1326  *
1327  * Set the keyword expansion mode to use on the RCS file <file> to <mode>.
1328  */
1329 void
1330 rcs_kwexp_set(RCSFILE *file, int mode)
1331 {
1332 	int i;
1333 	char *tmp, buf[8] = "";
1334 
1335 	if (RCS_KWEXP_INVAL(mode))
1336 		return;
1337 
1338 	i = 0;
1339 	if (mode == RCS_KWEXP_NONE)
1340 		buf[0] = 'b';
1341 	else if (mode == RCS_KWEXP_OLD)
1342 		buf[0] = 'o';
1343 	else {
1344 		if (mode & RCS_KWEXP_NAME)
1345 			buf[i++] = 'k';
1346 		if (mode & RCS_KWEXP_VAL)
1347 			buf[i++] = 'v';
1348 		if (mode & RCS_KWEXP_LKR)
1349 			buf[i++] = 'l';
1350 	}
1351 
1352 	tmp = xstrdup(buf);
1353 	if (file->rf_expand != NULL)
1354 		xfree(file->rf_expand);
1355 	file->rf_expand = tmp;
1356 	/* not synced anymore */
1357 	file->rf_flags &= ~RCS_SYNCED;
1358 }
1359 
1360 /*
1361  * rcs_kwexp_get()
1362  *
1363  * Retrieve the keyword expansion mode to be used for the RCS file <file>.
1364  */
1365 int
1366 rcs_kwexp_get(RCSFILE *file)
1367 {
1368 	return rcs_kflag_get(file->rf_expand);
1369 }
1370 
1371 /*
1372  * rcs_kflag_get()
1373  *
1374  * Get the keyword expansion mode from a set of character flags given in
1375  * <flags> and return the appropriate flag mask.  In case of an error, the
1376  * returned mask will have the RCS_KWEXP_ERR bit set to 1.
1377  */
1378 int
1379 rcs_kflag_get(const char *flags)
1380 {
1381 	int fl;
1382 	size_t len;
1383 	const char *fp;
1384 
1385 	fl = 0;
1386 	len = strlen(flags);
1387 
1388 	for (fp = flags; *fp != '\0'; fp++) {
1389 		if (*fp == 'k')
1390 			fl |= RCS_KWEXP_NAME;
1391 		else if (*fp == 'v')
1392 			fl |= RCS_KWEXP_VAL;
1393 		else if (*fp == 'l')
1394 			fl |= RCS_KWEXP_LKR;
1395 		else if (*fp == 'o') {
1396 			if (len != 1)
1397 				fl |= RCS_KWEXP_ERR;
1398 			fl |= RCS_KWEXP_OLD;
1399 		} else if (*fp == 'b') {
1400 			if (len != 1)
1401 				fl |= RCS_KWEXP_ERR;
1402 			fl |= RCS_KWEXP_NONE;
1403 		} else	/* unknown letter */
1404 			fl |= RCS_KWEXP_ERR;
1405 	}
1406 
1407 	return (fl);
1408 }
1409 
1410 /*
1411  * rcs_errstr()
1412  *
1413  * Get the error string matching the RCS error code <code>.
1414  */
1415 const char *
1416 rcs_errstr(int code)
1417 {
1418 	const char *esp;
1419 
1420 	if (code < 0 || (code >= (int)RCS_NERR && code != RCS_ERR_ERRNO))
1421 		esp = NULL;
1422 	else if (code == RCS_ERR_ERRNO)
1423 		esp = strerror(errno);
1424 	else
1425 		esp = rcs_errstrs[code];
1426 	return (esp);
1427 }
1428 
1429 /* rcs_parse_deltas()
1430  *
1431  * Parse deltas. If <rev> is not NULL, parse only as far as that
1432  * revision. If <rev> is NULL, parse all deltas.
1433  */
1434 static void
1435 rcs_parse_deltas(RCSFILE *rfp, RCSNUM *rev)
1436 {
1437 	int ret;
1438 	struct rcs_delta *enddelta;
1439 
1440 	if ((rfp->rf_flags & PARSED_DELTAS) || (rfp->rf_flags & RCS_CREATE))
1441 		return;
1442 
1443 	for (;;) {
1444 		ret = rcs_parse_delta(rfp);
1445 		if (rev != NULL) {
1446 			enddelta = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
1447 			if (rcsnum_cmp(enddelta->rd_num, rev, 0) == 0)
1448 				break;
1449 		}
1450 
1451 		if (ret == 0) {
1452 			rfp->rf_flags |= PARSED_DELTAS;
1453 			break;
1454 		}
1455 		else if (ret == -1)
1456 			fatal("error parsing deltas");
1457 	}
1458 }
1459 
1460 /* rcs_parse_deltatexts()
1461  *
1462  * Parse deltatexts. If <rev> is not NULL, parse only as far as that
1463  * revision. If <rev> is NULL, parse everything.
1464  */
1465 static void
1466 rcs_parse_deltatexts(RCSFILE *rfp, RCSNUM *rev)
1467 {
1468 	int ret;
1469 	struct rcs_delta *rdp;
1470 
1471 	if ((rfp->rf_flags & PARSED_DELTATEXTS) ||
1472 	    (rfp->rf_flags & RCS_CREATE))
1473 		return;
1474 
1475 	if (!(rfp->rf_flags & PARSED_DESC))
1476 		rcs_parse_desc(rfp, rev);
1477 	for (;;) {
1478 		if (rev != NULL) {
1479 			rdp = rcs_findrev(rfp, rev);
1480 			if (rdp->rd_text != NULL)
1481 				break;
1482 			else
1483 				ret = rcs_parse_deltatext(rfp);
1484 		} else
1485 			ret = rcs_parse_deltatext(rfp);
1486 		if (ret == 0) {
1487 			rfp->rf_flags |= PARSED_DELTATEXTS;
1488 			break;
1489 		}
1490 		else if (ret == -1)
1491 			fatal("problem parsing deltatexts");
1492 	}
1493 }
1494 
1495 /* rcs_parse_desc()
1496  *
1497  * Parse RCS description.
1498  */
1499 static void
1500 rcs_parse_desc(RCSFILE *rfp, RCSNUM *rev)
1501 {
1502 	int ret = 0;
1503 
1504 	if ((rfp->rf_flags & PARSED_DESC) || (rfp->rf_flags & RCS_CREATE))
1505 		return;
1506 	if (!(rfp->rf_flags & PARSED_DELTAS))
1507 		rcs_parse_deltas(rfp, rev);
1508 	/* do parsing */
1509 	ret = rcs_gettok(rfp);
1510 	if (ret != RCS_TOK_DESC)
1511 		fatal("token `%s' found where RCS desc expected",
1512 		    RCS_TOKSTR(rfp));
1513 
1514 	ret = rcs_gettok(rfp);
1515 	if (ret != RCS_TOK_STRING)
1516 		fatal("token `%s' found where RCS desc expected",
1517 		    RCS_TOKSTR(rfp));
1518 
1519 	rfp->rf_desc = xstrdup(RCS_TOKSTR(rfp));
1520 	rfp->rf_flags |= PARSED_DESC;
1521 }
1522 
1523 /*
1524  * rcs_parse_init()
1525  *
1526  * Initial parsing of file <path>, which are in the RCS format.
1527  * Just does admin section.
1528  */
1529 static void
1530 rcs_parse_init(RCSFILE *rfp)
1531 {
1532 	struct rcs_pdata *pdp;
1533 
1534 	if (rfp->rf_flags & RCS_PARSED)
1535 		return;
1536 
1537 	pdp = xcalloc(1, sizeof(*pdp));
1538 
1539 	pdp->rp_lines = 0;
1540 	pdp->rp_pttype = RCS_TOK_ERR;
1541 
1542 	if ((pdp->rp_file = fdopen(rfp->fd, "r")) == NULL)
1543 		fatal("fopen: `%s'", rfp->rf_path);
1544 
1545 	pdp->rp_buf = xmalloc((size_t)RCS_BUFSIZE);
1546 	pdp->rp_blen = RCS_BUFSIZE;
1547 	pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1;
1548 
1549 	/* ditch the strict lock */
1550 	rfp->rf_flags &= ~RCS_SLOCK;
1551 	rfp->rf_pdata = pdp;
1552 
1553 	if (rcs_parse_admin(rfp) < 0) {
1554 		rcs_freepdata(pdp);
1555 		fatal("could not parse admin data");
1556 	}
1557 
1558 	if (rfp->rf_flags & RCS_PARSE_FULLY)
1559 		rcs_parse_deltatexts(rfp, NULL);
1560 
1561 	rfp->rf_flags |= RCS_SYNCED;
1562 }
1563 
1564 /*
1565  * rcs_parse_admin()
1566  *
1567  * Parse the administrative portion of an RCS file.
1568  * Returns the type of the first token found after the admin section on
1569  * success, or -1 on failure.
1570  */
1571 static int
1572 rcs_parse_admin(RCSFILE *rfp)
1573 {
1574 	u_int i;
1575 	int tok, ntok, hmask;
1576 	struct rcs_key *rk;
1577 
1578 	rfp->rf_head = NULL;
1579 	rfp->rf_branch = NULL;
1580 
1581 	/* hmask is a mask of the headers already encountered */
1582 	hmask = 0;
1583 	for (;;) {
1584 		tok = rcs_gettok(rfp);
1585 		if (tok == RCS_TOK_ERR) {
1586 			rcs_errno = RCS_ERR_PARSE;
1587 			cvs_log(LP_ERR, "parse error in RCS admin section");
1588 			goto fail;
1589 		} else if (tok == RCS_TOK_NUM || tok == RCS_TOK_DESC) {
1590 			/*
1591 			 * Assume this is the start of the first delta or
1592 			 * that we are dealing with an empty RCS file and
1593 			 * we just found the description.
1594 			 */
1595 			rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok);
1596 			return (tok);
1597 		}
1598 
1599 		rk = NULL;
1600 		for (i = 0; i < RCS_NKEYS; i++)
1601 			if (rcs_keys[i].rk_id == tok)
1602 				rk = &(rcs_keys[i]);
1603 
1604 		if (hmask & (1 << tok)) {
1605 			rcs_errno = RCS_ERR_PARSE;
1606 			cvs_log(LP_ERR, "duplicate RCS key");
1607 			goto fail;
1608 		}
1609 		hmask |= (1 << tok);
1610 
1611 		switch (tok) {
1612 		case RCS_TOK_HEAD:
1613 		case RCS_TOK_BRANCH:
1614 		case RCS_TOK_COMMENT:
1615 		case RCS_TOK_EXPAND:
1616 			ntok = rcs_gettok(rfp);
1617 			if (ntok == RCS_TOK_SCOLON)
1618 				break;
1619 			if (ntok != rk->rk_val) {
1620 				rcs_errno = RCS_ERR_PARSE;
1621 				cvs_log(LP_ERR,
1622 				    "invalid value type for RCS key `%s'",
1623 				    rk->rk_str);
1624 			}
1625 
1626 			if (tok == RCS_TOK_HEAD) {
1627 				if (rfp->rf_head == NULL)
1628 					rfp->rf_head = rcsnum_alloc();
1629 				rcsnum_aton(RCS_TOKSTR(rfp), NULL,
1630 				    rfp->rf_head);
1631 			} else if (tok == RCS_TOK_BRANCH) {
1632 				if (rfp->rf_branch == NULL)
1633 					rfp->rf_branch = rcsnum_alloc();
1634 				if (rcsnum_aton(RCS_TOKSTR(rfp), NULL,
1635 				    rfp->rf_branch) < 0)
1636 					goto fail;
1637 			} else if (tok == RCS_TOK_COMMENT) {
1638 				rfp->rf_comment = xstrdup(RCS_TOKSTR(rfp));
1639 			} else if (tok == RCS_TOK_EXPAND) {
1640 				rfp->rf_expand = xstrdup(RCS_TOKSTR(rfp));
1641 			}
1642 
1643 			/* now get the expected semi-colon */
1644 			ntok = rcs_gettok(rfp);
1645 			if (ntok != RCS_TOK_SCOLON) {
1646 				rcs_errno = RCS_ERR_PARSE;
1647 				cvs_log(LP_ERR,
1648 				    "missing semi-colon after RCS `%s' key",
1649 				    rk->rk_str);
1650 				goto fail;
1651 			}
1652 			break;
1653 		case RCS_TOK_ACCESS:
1654 			if (rcs_parse_access(rfp) < 0)
1655 				goto fail;
1656 			break;
1657 		case RCS_TOK_SYMBOLS:
1658 			if (rcs_parse_symbols(rfp) < 0)
1659 				goto fail;
1660 			break;
1661 		case RCS_TOK_LOCKS:
1662 			if (rcs_parse_locks(rfp) < 0)
1663 				goto fail;
1664 			break;
1665 		default:
1666 			rcs_errno = RCS_ERR_PARSE;
1667 			cvs_log(LP_ERR,
1668 			    "unexpected token `%s' in RCS admin section",
1669 			    RCS_TOKSTR(rfp));
1670 			goto fail;
1671 		}
1672 	}
1673 
1674 fail:
1675 	return (-1);
1676 }
1677 
1678 /*
1679  * rcs_parse_delta()
1680  *
1681  * Parse an RCS delta section and allocate the structure to store that delta's
1682  * information in the <rfp> delta list.
1683  * Returns 1 if the section was parsed OK, 0 if it is the last delta, and
1684  * -1 on error.
1685  */
1686 static int
1687 rcs_parse_delta(RCSFILE *rfp)
1688 {
1689 	int ret, tok, ntok, hmask;
1690 	u_int i;
1691 	char *tokstr;
1692 	RCSNUM *datenum;
1693 	struct rcs_delta *rdp;
1694 	struct rcs_key *rk;
1695 
1696 	tok = rcs_gettok(rfp);
1697 	if (tok == RCS_TOK_DESC) {
1698 		rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok);
1699 		return (0);
1700 	} else if (tok != RCS_TOK_NUM) {
1701 		rcs_errno = RCS_ERR_PARSE;
1702 		cvs_log(LP_ERR, "unexpected token `%s' at start of delta",
1703 		    RCS_TOKSTR(rfp));
1704 		return (-1);
1705 	}
1706 
1707 	rdp = xcalloc(1, sizeof(*rdp));
1708 
1709 	rdp->rd_num = rcsnum_alloc();
1710 	rdp->rd_next = rcsnum_alloc();
1711 
1712 	TAILQ_INIT(&(rdp->rd_branches));
1713 
1714 	rcsnum_aton(RCS_TOKSTR(rfp), NULL, rdp->rd_num);
1715 
1716 	hmask = 0;
1717 	ret = 0;
1718 	tokstr = NULL;
1719 
1720 	for (;;) {
1721 		tok = rcs_gettok(rfp);
1722 		if (tok == RCS_TOK_ERR) {
1723 			rcs_errno = RCS_ERR_PARSE;
1724 			cvs_log(LP_ERR, "parse error in RCS delta section");
1725 			rcs_freedelta(rdp);
1726 			return (-1);
1727 		} else if (tok == RCS_TOK_NUM || tok == RCS_TOK_DESC) {
1728 			rcs_pushtok(rfp, RCS_TOKSTR(rfp), tok);
1729 			ret = (tok == RCS_TOK_NUM ? 1 : 0);
1730 			break;
1731 		}
1732 
1733 		rk = NULL;
1734 		for (i = 0; i < RCS_NKEYS; i++)
1735 			if (rcs_keys[i].rk_id == tok)
1736 				rk = &(rcs_keys[i]);
1737 
1738 		if (hmask & (1 << tok)) {
1739 			rcs_errno = RCS_ERR_PARSE;
1740 			cvs_log(LP_ERR, "duplicate RCS key");
1741 			rcs_freedelta(rdp);
1742 			return (-1);
1743 		}
1744 		hmask |= (1 << tok);
1745 
1746 		switch (tok) {
1747 		case RCS_TOK_DATE:
1748 		case RCS_TOK_AUTHOR:
1749 		case RCS_TOK_STATE:
1750 		case RCS_TOK_NEXT:
1751 			ntok = rcs_gettok(rfp);
1752 			if (ntok == RCS_TOK_SCOLON) {
1753 				if (rk->rk_flags & RCS_VOPT)
1754 					break;
1755 				else {
1756 					rcs_errno = RCS_ERR_PARSE;
1757 					cvs_log(LP_ERR, "missing mandatory "
1758 					    "value to RCS key `%s'",
1759 					    rk->rk_str);
1760 					rcs_freedelta(rdp);
1761 					return (-1);
1762 				}
1763 			}
1764 
1765 			if (ntok != rk->rk_val) {
1766 				rcs_errno = RCS_ERR_PARSE;
1767 				cvs_log(LP_ERR,
1768 				    "invalid value type for RCS key `%s'",
1769 				    rk->rk_str);
1770 				rcs_freedelta(rdp);
1771 				return (-1);
1772 			}
1773 
1774 			if (tokstr != NULL)
1775 				xfree(tokstr);
1776 			tokstr = xstrdup(RCS_TOKSTR(rfp));
1777 			/* now get the expected semi-colon */
1778 			ntok = rcs_gettok(rfp);
1779 			if (ntok != RCS_TOK_SCOLON) {
1780 				rcs_errno = RCS_ERR_PARSE;
1781 				cvs_log(LP_ERR,
1782 				    "missing semi-colon after RCS `%s' key",
1783 				    rk->rk_str);
1784 				xfree(tokstr);
1785 				rcs_freedelta(rdp);
1786 				return (-1);
1787 			}
1788 
1789 			if (tok == RCS_TOK_DATE) {
1790 				if ((datenum = rcsnum_parse(tokstr)) == NULL) {
1791 					xfree(tokstr);
1792 					rcs_freedelta(rdp);
1793 					return (-1);
1794 				}
1795 				if (datenum->rn_len != 6) {
1796 					rcs_errno = RCS_ERR_PARSE;
1797 					cvs_log(LP_ERR,
1798 					    "RCS date specification has %s "
1799 					    "fields",
1800 					    (datenum->rn_len > 6) ? "too many" :
1801 					    "missing");
1802 					xfree(tokstr);
1803 					rcs_freedelta(rdp);
1804 					rcsnum_free(datenum);
1805 					return (-1);
1806 				}
1807 				rdp->rd_date.tm_year = datenum->rn_id[0];
1808 				if (rdp->rd_date.tm_year >= 1900)
1809 					rdp->rd_date.tm_year -= 1900;
1810 				rdp->rd_date.tm_mon = datenum->rn_id[1] - 1;
1811 				rdp->rd_date.tm_mday = datenum->rn_id[2];
1812 				rdp->rd_date.tm_hour = datenum->rn_id[3];
1813 				rdp->rd_date.tm_min = datenum->rn_id[4];
1814 				rdp->rd_date.tm_sec = datenum->rn_id[5];
1815 				rcsnum_free(datenum);
1816 			} else if (tok == RCS_TOK_AUTHOR) {
1817 				rdp->rd_author = tokstr;
1818 				tokstr = NULL;
1819 			} else if (tok == RCS_TOK_STATE) {
1820 				rdp->rd_state = tokstr;
1821 				tokstr = NULL;
1822 			} else if (tok == RCS_TOK_NEXT) {
1823 				rcsnum_aton(tokstr, NULL, rdp->rd_next);
1824 			}
1825 			break;
1826 		case RCS_TOK_BRANCHES:
1827 			if (rcs_parse_branches(rfp, rdp) < 0) {
1828 				rcs_freedelta(rdp);
1829 				return (-1);
1830 			}
1831 			break;
1832 		default:
1833 			rcs_errno = RCS_ERR_PARSE;
1834 			cvs_log(LP_ERR, "unexpected token `%s' in RCS delta",
1835 			    RCS_TOKSTR(rfp));
1836 			rcs_freedelta(rdp);
1837 			return (-1);
1838 		}
1839 	}
1840 
1841 	if (tokstr != NULL)
1842 		xfree(tokstr);
1843 
1844 	TAILQ_INSERT_TAIL(&(rfp->rf_delta), rdp, rd_list);
1845 	rfp->rf_ndelta++;
1846 
1847 	return (ret);
1848 }
1849 
1850 /*
1851  * rcs_parse_deltatext()
1852  *
1853  * Parse an RCS delta text section and fill in the log and text field of the
1854  * appropriate delta section.
1855  * Returns 1 if the section was parsed OK, 0 if it is the last delta, and
1856  * -1 on error.
1857  */
1858 static int
1859 rcs_parse_deltatext(RCSFILE *rfp)
1860 {
1861 	int tok;
1862 	RCSNUM *tnum;
1863 	struct rcs_delta *rdp;
1864 
1865 	tok = rcs_gettok(rfp);
1866 	if (tok == RCS_TOK_EOF)
1867 		return (0);
1868 
1869 	if (tok != RCS_TOK_NUM) {
1870 		rcs_errno = RCS_ERR_PARSE;
1871 		cvs_log(LP_ERR,
1872 		    "unexpected token `%s' at start of RCS delta text",
1873 		    RCS_TOKSTR(rfp));
1874 		return (-1);
1875 	}
1876 
1877 	tnum = rcsnum_alloc();
1878 	rcsnum_aton(RCS_TOKSTR(rfp), NULL, tnum);
1879 
1880 	TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
1881 		if (rcsnum_cmp(tnum, rdp->rd_num, 0) == 0)
1882 			break;
1883 	}
1884 	rcsnum_free(tnum);
1885 
1886 	if (rdp == NULL) {
1887 		cvs_log(LP_ERR, "RCS delta text `%s' has no matching delta",
1888 		    RCS_TOKSTR(rfp));
1889 		return (-1);
1890 	}
1891 
1892 	tok = rcs_gettok(rfp);
1893 	if (tok != RCS_TOK_LOG) {
1894 		rcs_errno = RCS_ERR_PARSE;
1895 		cvs_log(LP_ERR, "unexpected token `%s' where RCS log expected",
1896 		    RCS_TOKSTR(rfp));
1897 		return (-1);
1898 	}
1899 
1900 	tok = rcs_gettok(rfp);
1901 	if (tok != RCS_TOK_STRING) {
1902 		rcs_errno = RCS_ERR_PARSE;
1903 		cvs_log(LP_ERR, "unexpected token `%s' where RCS log expected",
1904 		    RCS_TOKSTR(rfp));
1905 		return (-1);
1906 	}
1907 	rdp->rd_log = xstrdup(RCS_TOKSTR(rfp));
1908 	tok = rcs_gettok(rfp);
1909 	if (tok != RCS_TOK_TEXT) {
1910 		rcs_errno = RCS_ERR_PARSE;
1911 		cvs_log(LP_ERR, "unexpected token `%s' where RCS text expected",
1912 		    RCS_TOKSTR(rfp));
1913 		return (-1);
1914 	}
1915 
1916 	tok = rcs_gettok(rfp);
1917 	if (tok != RCS_TOK_STRING) {
1918 		rcs_errno = RCS_ERR_PARSE;
1919 		cvs_log(LP_ERR, "unexpected token `%s' where RCS text expected",
1920 		    RCS_TOKSTR(rfp));
1921 		return (-1);
1922 	}
1923 
1924 	if (RCS_TOKLEN(rfp) == 0) {
1925 		rdp->rd_text = xmalloc(1);
1926 		rdp->rd_text[0] = '\0';
1927 		rdp->rd_tlen = 0;
1928 	} else {
1929 		rdp->rd_text = xmalloc(RCS_TOKLEN(rfp));
1930 		memcpy(rdp->rd_text, RCS_TOKSTR(rfp), RCS_TOKLEN(rfp));
1931 		rdp->rd_tlen = RCS_TOKLEN(rfp);
1932 	}
1933 
1934 	return (1);
1935 }
1936 
1937 /*
1938  * rcs_parse_access()
1939  *
1940  * Parse the access list given as value to the `access' keyword.
1941  * Returns 0 on success, or -1 on failure.
1942  */
1943 static int
1944 rcs_parse_access(RCSFILE *rfp)
1945 {
1946 	int type;
1947 
1948 	while ((type = rcs_gettok(rfp)) != RCS_TOK_SCOLON) {
1949 		if (type != RCS_TOK_ID) {
1950 			rcs_errno = RCS_ERR_PARSE;
1951 			cvs_log(LP_ERR, "unexpected token `%s' in access list",
1952 			    RCS_TOKSTR(rfp));
1953 			return (-1);
1954 		}
1955 
1956 		if (rcs_access_add(rfp, RCS_TOKSTR(rfp)) < 0)
1957 			return (-1);
1958 	}
1959 
1960 	return (0);
1961 }
1962 
1963 /*
1964  * rcs_parse_symbols()
1965  *
1966  * Parse the symbol list given as value to the `symbols' keyword.
1967  * Returns 0 on success, or -1 on failure.
1968  */
1969 static int
1970 rcs_parse_symbols(RCSFILE *rfp)
1971 {
1972 	int type;
1973 	struct rcs_sym *symp;
1974 
1975 	for (;;) {
1976 		type = rcs_gettok(rfp);
1977 		if (type == RCS_TOK_SCOLON)
1978 			break;
1979 
1980 		if (type != RCS_TOK_ID) {
1981 			rcs_errno = RCS_ERR_PARSE;
1982 			cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
1983 			    RCS_TOKSTR(rfp));
1984 			return (-1);
1985 		}
1986 
1987 		symp = xmalloc(sizeof(*symp));
1988 		symp->rs_name = xstrdup(RCS_TOKSTR(rfp));
1989 		symp->rs_num = rcsnum_alloc();
1990 
1991 		type = rcs_gettok(rfp);
1992 		if (type != RCS_TOK_COLON) {
1993 			rcs_errno = RCS_ERR_PARSE;
1994 			cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
1995 			    RCS_TOKSTR(rfp));
1996 			rcsnum_free(symp->rs_num);
1997 			xfree(symp->rs_name);
1998 			xfree(symp);
1999 			return (-1);
2000 		}
2001 
2002 		type = rcs_gettok(rfp);
2003 		if (type != RCS_TOK_NUM) {
2004 			rcs_errno = RCS_ERR_PARSE;
2005 			cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
2006 			    RCS_TOKSTR(rfp));
2007 			rcsnum_free(symp->rs_num);
2008 			xfree(symp->rs_name);
2009 			xfree(symp);
2010 			return (-1);
2011 		}
2012 
2013 		if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, symp->rs_num) < 0) {
2014 			cvs_log(LP_ERR, "failed to parse RCS NUM `%s'",
2015 			    RCS_TOKSTR(rfp));
2016 			rcsnum_free(symp->rs_num);
2017 			xfree(symp->rs_name);
2018 			xfree(symp);
2019 			return (-1);
2020 		}
2021 
2022 		TAILQ_INSERT_TAIL(&(rfp->rf_symbols), symp, rs_list);
2023 	}
2024 
2025 	return (0);
2026 }
2027 
2028 /*
2029  * rcs_parse_locks()
2030  *
2031  * Parse the lock list given as value to the `locks' keyword.
2032  * Returns 0 on success, or -1 on failure.
2033  */
2034 static int
2035 rcs_parse_locks(RCSFILE *rfp)
2036 {
2037 	int type;
2038 	struct rcs_lock *lkp;
2039 
2040 	for (;;) {
2041 		type = rcs_gettok(rfp);
2042 		if (type == RCS_TOK_SCOLON)
2043 			break;
2044 
2045 		if (type != RCS_TOK_ID) {
2046 			rcs_errno = RCS_ERR_PARSE;
2047 			cvs_log(LP_ERR, "unexpected token `%s' in lock list",
2048 			    RCS_TOKSTR(rfp));
2049 			return (-1);
2050 		}
2051 
2052 		lkp = xmalloc(sizeof(*lkp));
2053 		lkp->rl_name = xstrdup(RCS_TOKSTR(rfp));
2054 		lkp->rl_num = rcsnum_alloc();
2055 
2056 		type = rcs_gettok(rfp);
2057 		if (type != RCS_TOK_COLON) {
2058 			rcs_errno = RCS_ERR_PARSE;
2059 			cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
2060 			    RCS_TOKSTR(rfp));
2061 			rcsnum_free(lkp->rl_num);
2062 			xfree(lkp->rl_name);
2063 			xfree(lkp);
2064 			return (-1);
2065 		}
2066 
2067 		type = rcs_gettok(rfp);
2068 		if (type != RCS_TOK_NUM) {
2069 			rcs_errno = RCS_ERR_PARSE;
2070 			cvs_log(LP_ERR, "unexpected token `%s' in symbol list",
2071 			    RCS_TOKSTR(rfp));
2072 			rcsnum_free(lkp->rl_num);
2073 			xfree(lkp->rl_name);
2074 			xfree(lkp);
2075 			return (-1);
2076 		}
2077 
2078 		if (rcsnum_aton(RCS_TOKSTR(rfp), NULL, lkp->rl_num) < 0) {
2079 			cvs_log(LP_ERR, "failed to parse RCS NUM `%s'",
2080 			    RCS_TOKSTR(rfp));
2081 			rcsnum_free(lkp->rl_num);
2082 			xfree(lkp->rl_name);
2083 			xfree(lkp);
2084 			return (-1);
2085 		}
2086 
2087 		TAILQ_INSERT_HEAD(&(rfp->rf_locks), lkp, rl_list);
2088 	}
2089 
2090 	/* check if we have a `strict' */
2091 	type = rcs_gettok(rfp);
2092 	if (type != RCS_TOK_STRICT) {
2093 		rcs_pushtok(rfp, RCS_TOKSTR(rfp), type);
2094 	} else {
2095 		rfp->rf_flags |= RCS_SLOCK;
2096 
2097 		type = rcs_gettok(rfp);
2098 		if (type != RCS_TOK_SCOLON) {
2099 			rcs_errno = RCS_ERR_PARSE;
2100 			cvs_log(LP_ERR,
2101 			    "missing semi-colon after `strict' keyword");
2102 			return (-1);
2103 		}
2104 	}
2105 
2106 	return (0);
2107 }
2108 
2109 /*
2110  * rcs_parse_branches()
2111  *
2112  * Parse the list of branches following a `branches' keyword in a delta.
2113  * Returns 0 on success, or -1 on failure.
2114  */
2115 static int
2116 rcs_parse_branches(RCSFILE *rfp, struct rcs_delta *rdp)
2117 {
2118 	int type;
2119 	struct rcs_branch *brp;
2120 
2121 	for (;;) {
2122 		type = rcs_gettok(rfp);
2123 		if (type == RCS_TOK_SCOLON)
2124 			break;
2125 
2126 		if (type != RCS_TOK_NUM) {
2127 			rcs_errno = RCS_ERR_PARSE;
2128 			cvs_log(LP_ERR,
2129 			    "unexpected token `%s' in list of branches",
2130 			    RCS_TOKSTR(rfp));
2131 			return (-1);
2132 		}
2133 
2134 		brp = xmalloc(sizeof(*brp));
2135 		brp->rb_num = rcsnum_parse(RCS_TOKSTR(rfp));
2136 		if (brp->rb_num == NULL) {
2137 			xfree(brp);
2138 			return (-1);
2139 		}
2140 
2141 		TAILQ_INSERT_TAIL(&(rdp->rd_branches), brp, rb_list);
2142 	}
2143 
2144 	return (0);
2145 }
2146 
2147 /*
2148  * rcs_freedelta()
2149  *
2150  * Free the contents of a delta structure.
2151  */
2152 static void
2153 rcs_freedelta(struct rcs_delta *rdp)
2154 {
2155 	struct rcs_branch *rb;
2156 
2157 	if (rdp->rd_num != NULL)
2158 		rcsnum_free(rdp->rd_num);
2159 	if (rdp->rd_next != NULL)
2160 		rcsnum_free(rdp->rd_next);
2161 
2162 	if (rdp->rd_author != NULL)
2163 		xfree(rdp->rd_author);
2164 	if (rdp->rd_locker != NULL)
2165 		xfree(rdp->rd_locker);
2166 	if (rdp->rd_state != NULL)
2167 		xfree(rdp->rd_state);
2168 	if (rdp->rd_log != NULL)
2169 		xfree(rdp->rd_log);
2170 	if (rdp->rd_text != NULL)
2171 		xfree(rdp->rd_text);
2172 
2173 	while ((rb = TAILQ_FIRST(&(rdp->rd_branches))) != NULL) {
2174 		TAILQ_REMOVE(&(rdp->rd_branches), rb, rb_list);
2175 		rcsnum_free(rb->rb_num);
2176 		xfree(rb);
2177 	}
2178 
2179 	xfree(rdp);
2180 }
2181 
2182 /*
2183  * rcs_freepdata()
2184  *
2185  * Free the contents of the parser data structure.
2186  */
2187 static void
2188 rcs_freepdata(struct rcs_pdata *pd)
2189 {
2190 	if (pd->rp_file != NULL)
2191 		(void)fclose(pd->rp_file);
2192 	if (pd->rp_buf != NULL)
2193 		xfree(pd->rp_buf);
2194 	xfree(pd);
2195 }
2196 
2197 /*
2198  * rcs_gettok()
2199  *
2200  * Get the next RCS token from the string <str>.
2201  */
2202 static int
2203 rcs_gettok(RCSFILE *rfp)
2204 {
2205 	u_int i;
2206 	int ch, last, type;
2207 	size_t len;
2208 	char *bp;
2209 	struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata;
2210 
2211 	type = RCS_TOK_ERR;
2212 	bp = pdp->rp_buf;
2213 	pdp->rp_tlen = 0;
2214 	*bp = '\0';
2215 
2216 	if (pdp->rp_pttype != RCS_TOK_ERR) {
2217 		type = pdp->rp_pttype;
2218 		if (strlcpy(pdp->rp_buf, pdp->rp_ptok, pdp->rp_blen) >=
2219 		    pdp->rp_blen)
2220 			fatal("rcs_gettok: strlcpy");
2221 		pdp->rp_pttype = RCS_TOK_ERR;
2222 		return (type);
2223 	}
2224 
2225 	/* skip leading whitespace */
2226 	/* XXX we must skip backspace too for compatibility, should we? */
2227 	do {
2228 		ch = getc(pdp->rp_file);
2229 		if (ch == '\n')
2230 			pdp->rp_lines++;
2231 	} while (isspace(ch));
2232 
2233 	if (ch == EOF) {
2234 		type = RCS_TOK_EOF;
2235 	} else if (ch == ';') {
2236 		type = RCS_TOK_SCOLON;
2237 	} else if (ch == ':') {
2238 		type = RCS_TOK_COLON;
2239 	} else if (isalpha(ch)) {
2240 		type = RCS_TOK_ID;
2241 		*(bp++) = ch;
2242 		for (;;) {
2243 			ch = getc(pdp->rp_file);
2244 			if (ch == EOF) {
2245 				type = RCS_TOK_EOF;
2246 				break;
2247 			} else if (!isalnum(ch) && ch != '_' && ch != '-' &&
2248 			    ch != '/' && ch != '+') {
2249 				ungetc(ch, pdp->rp_file);
2250 				break;
2251 			}
2252 			*(bp++) = ch;
2253 			pdp->rp_tlen++;
2254 			if (bp == pdp->rp_bufend - 1) {
2255 				len = bp - pdp->rp_buf;
2256 				rcs_growbuf(rfp);
2257 				bp = pdp->rp_buf + len;
2258 			}
2259 		}
2260 		*bp = '\0';
2261 
2262 		if (type != RCS_TOK_ERR) {
2263 			for (i = 0; i < RCS_NKEYS; i++) {
2264 				if (strcmp(rcs_keys[i].rk_str,
2265 				    pdp->rp_buf) == 0) {
2266 					type = rcs_keys[i].rk_id;
2267 					break;
2268 				}
2269 			}
2270 		}
2271 	} else if (ch == '@') {
2272 		/* we have a string */
2273 		type = RCS_TOK_STRING;
2274 		for (;;) {
2275 			ch = getc(pdp->rp_file);
2276 			if (ch == EOF) {
2277 				type = RCS_TOK_EOF;
2278 				break;
2279 			} else if (ch == '@') {
2280 				ch = getc(pdp->rp_file);
2281 				if (ch != '@') {
2282 					ungetc(ch, pdp->rp_file);
2283 					break;
2284 				}
2285 			} else if (ch == '\n')
2286 				pdp->rp_lines++;
2287 
2288 			*(bp++) = ch;
2289 			pdp->rp_tlen++;
2290 			if (bp == pdp->rp_bufend - 1) {
2291 				len = bp - pdp->rp_buf;
2292 				rcs_growbuf(rfp);
2293 				bp = pdp->rp_buf + len;
2294 			}
2295 		}
2296 
2297 		*bp = '\0';
2298 	} else if (isdigit(ch)) {
2299 		*(bp++) = ch;
2300 		last = ch;
2301 		type = RCS_TOK_NUM;
2302 
2303 		for (;;) {
2304 			ch = getc(pdp->rp_file);
2305 			if (ch == EOF) {
2306 				type = RCS_TOK_EOF;
2307 				break;
2308 			}
2309 			if (bp == pdp->rp_bufend)
2310 				break;
2311 			if (!isdigit(ch) && ch != '.') {
2312 				ungetc(ch, pdp->rp_file);
2313 				break;
2314 			}
2315 
2316 			if (last == '.' && ch == '.') {
2317 				type = RCS_TOK_ERR;
2318 				break;
2319 			}
2320 			last = ch;
2321 			*(bp++) = ch;
2322 			pdp->rp_tlen++;
2323 		}
2324 		*bp = '\0';
2325 	}
2326 
2327 	return (type);
2328 }
2329 
2330 /*
2331  * rcs_pushtok()
2332  *
2333  * Push a token back in the parser's token buffer.
2334  */
2335 static int
2336 rcs_pushtok(RCSFILE *rfp, const char *tok, int type)
2337 {
2338 	struct rcs_pdata *pdp = (struct rcs_pdata *)rfp->rf_pdata;
2339 
2340 	if (pdp->rp_pttype != RCS_TOK_ERR)
2341 		return (-1);
2342 
2343 	pdp->rp_pttype = type;
2344 	if (strlcpy(pdp->rp_ptok, tok, sizeof(pdp->rp_ptok)) >=
2345 	    sizeof(pdp->rp_ptok))
2346 		fatal("rcs_pushtok: strlcpy");
2347 	return (0);
2348 }
2349 
2350 
2351 /*
2352  * rcs_growbuf()
2353  *
2354  * Attempt to grow the internal parse buffer for the RCS file <rf> by
2355  * RCS_BUFEXTSIZE.
2356  * In case of failure, the original buffer is left unmodified.
2357  */
2358 static void
2359 rcs_growbuf(RCSFILE *rf)
2360 {
2361 	void *tmp;
2362 	struct rcs_pdata *pdp = (struct rcs_pdata *)rf->rf_pdata;
2363 
2364 	tmp = xrealloc(pdp->rp_buf, 1, pdp->rp_blen + RCS_BUFEXTSIZE);
2365 	pdp->rp_buf = tmp;
2366 	pdp->rp_blen += RCS_BUFEXTSIZE;
2367 	pdp->rp_bufend = pdp->rp_buf + pdp->rp_blen - 1;
2368 }
2369 
2370 /*
2371  * rcs_strprint()
2372  *
2373  * Output an RCS string <str> of size <slen> to the stream <stream>.  Any
2374  * '@' characters are escaped.  Otherwise, the string can contain arbitrary
2375  * binary data.
2376  */
2377 static void
2378 rcs_strprint(const u_char *str, size_t slen, FILE *stream)
2379 {
2380 	const u_char *ap, *ep, *sp;
2381 
2382 	if (slen == 0)
2383 		return;
2384 
2385 	ep = str + slen - 1;
2386 
2387 	for (sp = str; sp <= ep;)  {
2388 		ap = memchr(sp, '@', ep - sp);
2389 		if (ap == NULL)
2390 			ap = ep;
2391 		(void)fwrite(sp, sizeof(u_char), ap - sp + 1, stream);
2392 
2393 		if (*ap == '@')
2394 			putc('@', stream);
2395 		sp = ap + 1;
2396 	}
2397 }
2398 
2399 /*
2400  * rcs_deltatext_set()
2401  *
2402  * Set deltatext for <rev> in RCS file <rfp> to <dtext>
2403  * Returns -1 on error, 0 on success.
2404  */
2405 int
2406 rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, BUF *bp)
2407 {
2408 	size_t len;
2409 	u_char *dtext;
2410 	struct rcs_delta *rdp;
2411 
2412 	/* Write operations require full parsing */
2413 	rcs_parse_deltatexts(rfp, NULL);
2414 
2415 	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
2416 		return (-1);
2417 
2418 	if (rdp->rd_text != NULL)
2419 		xfree(rdp->rd_text);
2420 
2421 	len = cvs_buf_len(bp);
2422 	dtext = cvs_buf_release(bp);
2423 	bp = NULL;
2424 
2425 	if (len != 0) {
2426 		rdp->rd_text = xmalloc(len);
2427 		rdp->rd_tlen = len;
2428 		(void)memcpy(rdp->rd_text, dtext, len);
2429 	} else {
2430 		rdp->rd_text = NULL;
2431 		rdp->rd_tlen = 0;
2432 	}
2433 
2434 	if (dtext != NULL)
2435 		xfree(dtext);
2436 
2437 	return (0);
2438 }
2439 
2440 /*
2441  * rcs_rev_setlog()
2442  *
2443  * Sets the log message of revision <rev> to <logtext>
2444  */
2445 int
2446 rcs_rev_setlog(RCSFILE *rfp, RCSNUM *rev, const char *logtext)
2447 {
2448 	struct rcs_delta *rdp;
2449 	char buf[16];
2450 
2451 	rcsnum_tostr(rev, buf, sizeof(buf));
2452 
2453 	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
2454 		return (-1);
2455 
2456 	if (rdp->rd_log != NULL)
2457 		xfree(rdp->rd_log);
2458 
2459 	rdp->rd_log = xstrdup(logtext);
2460 	rfp->rf_flags &= ~RCS_SYNCED;
2461 	return (0);
2462 }
2463 /*
2464  * rcs_rev_getdate()
2465  *
2466  * Get the date corresponding to a given revision.
2467  * Returns the date on success, -1 on failure.
2468  */
2469 time_t
2470 rcs_rev_getdate(RCSFILE *rfp, RCSNUM *rev)
2471 {
2472 	struct rcs_delta *rdp;
2473 
2474 	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
2475 		return (-1);
2476 
2477 	return (timegm(&rdp->rd_date));
2478 }
2479 
2480 /*
2481  * rcs_state_set()
2482  *
2483  * Sets the state of revision <rev> to <state>
2484  * NOTE: default state is 'Exp'. States may not contain spaces.
2485  *
2486  * Returns -1 on failure, 0 on success.
2487  */
2488 int
2489 rcs_state_set(RCSFILE *rfp, RCSNUM *rev, const char *state)
2490 {
2491 	struct rcs_delta *rdp;
2492 
2493 	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
2494 		return (-1);
2495 
2496 	if (rdp->rd_state != NULL)
2497 		xfree(rdp->rd_state);
2498 
2499 	rdp->rd_state = xstrdup(state);
2500 
2501 	rfp->rf_flags &= ~RCS_SYNCED;
2502 
2503 	return (0);
2504 }
2505 
2506 /*
2507  * rcs_state_check()
2508  *
2509  * Check if string <state> is valid.
2510  *
2511  * Returns 0 if the string is valid, -1 otherwise.
2512  */
2513 int
2514 rcs_state_check(const char *state)
2515 {
2516 	if (strchr(state, ' ') != NULL)
2517 		return (-1);
2518 
2519 	return (0);
2520 }
2521 
2522 /*
2523  * rcs_state_get()
2524  *
2525  * Get the state for a given revision of a specified RCSFILE.
2526  *
2527  * Returns NULL on failure.
2528  */
2529 const char *
2530 rcs_state_get(RCSFILE *rfp, RCSNUM *rev)
2531 {
2532 	struct rcs_delta *rdp;
2533 
2534 	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
2535 		return (NULL);
2536 
2537 	return (rdp->rd_state);
2538 }
2539 
2540 RCSNUM *
2541 rcs_translate_tag(const char *revstr, RCSFILE *rfp)
2542 {
2543 	size_t i;
2544 	char *sdate;
2545 	RCSNUM *rev, *brev;
2546 	struct rcs_branch *brp;
2547 	struct rcs_delta *rdp;
2548 	time_t givendate, rcsdate;
2549 
2550 	rdp = NULL;
2551 
2552 	rev = rcs_sym_getrev(rfp, revstr);
2553 	if (rev == NULL) {
2554 		if ((rev = rcsnum_parse(revstr)) == NULL) {
2555 			if ((givendate = cvs_date_parse(revstr)) == -1)
2556 				fatal("tag %s does not exist (0)", revstr);
2557 
2558 			rcs_parse_deltas(rfp, NULL);
2559 
2560 			TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
2561 				sdate = asctime(&rdp->rd_date);
2562 				if (sdate == NULL)
2563 					fatal("failed to parse rcs date");
2564 				rcsdate = cvs_date_parse(sdate);
2565 				if (rcsdate == -1)
2566 					fatal("failed to parse %s", sdate);
2567 				if (givendate <= rcsdate)
2568 					continue;
2569 				break;
2570 			}
2571 
2572 			if (rdp == NULL)
2573 				fatal("no revision that matches date %s",
2574 				    revstr);
2575 
2576 			rev = rdp->rd_num;
2577 		}
2578 	}
2579 
2580 	if (RCSNUM_ISBRANCH(rev)) {
2581 		brev = rcsnum_alloc();
2582 		rcsnum_cpy(rev, brev, rev->rn_len - 1);
2583 	} else {
2584 		brev = rev;
2585 	}
2586 
2587 	if ((rdp = rcs_findrev(rfp, brev)) == NULL)
2588 		fatal("tag %s does not exist (1)", revstr);
2589 
2590 	if (RCSNUM_ISBRANCH(rev)) {
2591 		rcsnum_free(brev);
2592 		TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
2593 			for (i = 0; i < rev->rn_len; i++) {
2594 				if (brp->rb_num->rn_id[i] != rev->rn_id[i])
2595 					break;
2596 			}
2597 
2598 			if (i != rev->rn_len)
2599 				continue;
2600 
2601 			break;
2602 		}
2603 
2604 		if (brp == NULL)
2605 			return (NULL);
2606 
2607 		if ((rdp = rcs_findrev(rfp, brp->rb_num)) == NULL)
2608 			fatal("tag %s does not exist (3)", revstr);
2609 
2610 		while (rdp->rd_next->rn_len != 0) {
2611 			if ((rdp = rcs_findrev(rfp, rdp->rd_next)) == NULL)
2612 				fatal("tag %s does not exist (4)", revstr);
2613 		}
2614 
2615 		rcsnum_cpy(rdp->rd_num, rev, 0);
2616 	}
2617 
2618 	return (rev);
2619 }
2620 
2621 /*
2622  * rcs_rev_getlines()
2623  *
2624  * Get the entire contents of revision <rev> from the RCSFILE <rfp> and
2625  * return it as a pointer to a struct cvs_lines.
2626  */
2627 struct cvs_lines *
2628 rcs_rev_getlines(RCSFILE *rfp, RCSNUM *frev)
2629 {
2630 	size_t plen;
2631 	int i, done, nextroot, found;
2632 	RCSNUM *tnum, *bnum;
2633 	struct rcs_branch *brp;
2634 	struct rcs_delta *hrdp, *trdp, *rdp;
2635 	u_char *patch;
2636 	struct cvs_lines *dlines, *plines;
2637 
2638 	if ((hrdp = rcs_findrev(rfp, rfp->rf_head)) == NULL)
2639 		fatal("rcs_rev_getlines: no HEAD revision");
2640 
2641 	tnum = frev;
2642 	rcs_parse_deltatexts(rfp, hrdp->rd_num);
2643 
2644 	/* revision on branch, get the branch root */
2645 	nextroot = 2;
2646 	if (RCSNUM_ISBRANCHREV(tnum)) {
2647 		bnum = rcsnum_alloc();
2648 		rcsnum_cpy(tnum, bnum, nextroot);
2649 	} else {
2650 		bnum = tnum;
2651 	}
2652 
2653 	dlines = cvs_splitlines(hrdp->rd_text, hrdp->rd_tlen);
2654 
2655 	done = 0;
2656 
2657 	rdp = hrdp;
2658 	if (!rcsnum_differ(rdp->rd_num, bnum))
2659 		goto next;
2660 
2661 	if ((rdp = rcs_findrev(rfp, hrdp->rd_next)) == NULL)
2662 		goto done;
2663 
2664 again:
2665 	for (;;) {
2666 		if (rdp->rd_next->rn_len != 0) {
2667 			trdp = rcs_findrev(rfp, rdp->rd_next);
2668 			if (trdp == NULL)
2669 				fatal("failed to grab next revision");
2670 		}
2671 
2672 		if (rdp->rd_tlen == 0) {
2673 			rcs_parse_deltatexts(rfp, rdp->rd_num);
2674 			if (rdp->rd_tlen == 0) {
2675 				if (!rcsnum_differ(rdp->rd_num, bnum))
2676 					break;
2677 				rdp = trdp;
2678 				continue;
2679 			}
2680 		}
2681 
2682 		plen = rdp->rd_tlen;
2683 		patch = rdp->rd_text;
2684 		plines = cvs_splitlines(patch, plen);
2685 		rcs_patch_lines(dlines, plines);
2686 		cvs_freelines(plines);
2687 
2688 		if (!rcsnum_differ(rdp->rd_num, bnum))
2689 			break;
2690 
2691 		rdp = trdp;
2692 	}
2693 
2694 next:
2695 	if (!rcsnum_differ(rdp->rd_num, frev))
2696 		done = 1;
2697 
2698 	if (RCSNUM_ISBRANCHREV(frev) && done != 1) {
2699 		nextroot += 2;
2700 		rcsnum_cpy(frev, bnum, nextroot);
2701 
2702 		/* XXX strange loop and "found" set but not used */
2703 		TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
2704 			found = 1;
2705 			for (i = 0; i < nextroot - 1; i++) {
2706 				if (brp->rb_num->rn_id[i] != bnum->rn_id[i]) {
2707 					found = 0;
2708 					break;
2709 				}
2710 			}
2711 
2712 			break;
2713 		}
2714 
2715 		if (brp == NULL)
2716 			fatal("expected branch not found on branch list");
2717 
2718 		if ((rdp = rcs_findrev(rfp, brp->rb_num)) == NULL)
2719 			fatal("rcs_rev_getlines: failed to get delta for target rev");
2720 
2721 		goto again;
2722 	}
2723 done:
2724 	if (bnum != tnum)
2725 		rcsnum_free(bnum);
2726 
2727 	return (dlines);
2728 }
2729 
2730 /*
2731  * rcs_rev_getbuf()
2732  *
2733  * XXX: This is really really slow and should be avoided if at all possible!
2734  *
2735  * Get the entire contents of revision <rev> from the RCSFILE <rfp> and
2736  * return it as a BUF pointer.
2737  */
2738 BUF *
2739 rcs_rev_getbuf(RCSFILE *rfp, RCSNUM *rev, int mode)
2740 {
2741 	int expmode, expand;
2742 	struct rcs_delta *rdp;
2743 	struct cvs_lines *lines;
2744 	struct cvs_line *lp;
2745 	BUF *bp;
2746 
2747 	expand = 0;
2748 	lines = rcs_rev_getlines(rfp, rev);
2749 	bp = cvs_buf_alloc(1024, BUF_AUTOEXT);
2750 
2751 	if (!(mode & RCS_KWEXP_NONE)) {
2752 		if (rfp->rf_expand != NULL)
2753 			expmode = rcs_kwexp_get(rfp);
2754 		else
2755 			expmode = RCS_KWEXP_DEFAULT;
2756 
2757 		if (!(expmode & RCS_KWEXP_NONE)) {
2758 			if ((rdp = rcs_findrev(rfp, rev)) == NULL)
2759 				fatal("could not fetch revision");
2760 			expand = 1;
2761 		}
2762 	}
2763 
2764 	TAILQ_FOREACH(lp, &lines->l_lines, l_list) {
2765 		if (lp->l_line == NULL)
2766 			continue;
2767 
2768 		if (expand)
2769 			rcs_kwexp_line(rfp->rf_path, rdp, lp, expmode);
2770 
2771 		cvs_buf_append(bp, lp->l_line, lp->l_len);
2772 	}
2773 
2774 	cvs_freelines(lines);
2775 
2776 	return (bp);
2777 }
2778 
2779 /*
2780  * rcs_rev_write_fd()
2781  *
2782  * Write the entire contents of revision <frev> from the rcsfile <rfp> to
2783  * file descriptor <fd>.
2784  */
2785 void
2786 rcs_rev_write_fd(RCSFILE *rfp, RCSNUM *rev, int fd, int mode)
2787 {
2788 	int expmode, expand;
2789 	struct rcs_delta *rdp;
2790 	struct cvs_lines *lines;
2791 	struct cvs_line *lp;
2792 
2793 	expand = 0;
2794 	lines = rcs_rev_getlines(rfp, rev);
2795 
2796 	if (!(mode & RCS_KWEXP_NONE)) {
2797 		if (rfp->rf_expand != NULL)
2798 			expmode = rcs_kwexp_get(rfp);
2799 		else
2800 			expmode = RCS_KWEXP_DEFAULT;
2801 
2802 		if (!(expmode & RCS_KWEXP_NONE)) {
2803 			if ((rdp = rcs_findrev(rfp, rev)) == NULL)
2804 				fatal("could not fetch revision");
2805 			expand = 1;
2806 		}
2807 	}
2808 
2809 	TAILQ_FOREACH(lp, &lines->l_lines, l_list) {
2810 		if (lp->l_line == NULL)
2811 			continue;
2812 
2813 		if (expand)
2814 			rcs_kwexp_line(rfp->rf_path, rdp, lp, expmode);
2815 
2816 		if (write(fd, lp->l_line, lp->l_len) == -1)
2817 			fatal("rcs_rev_write_fd: %s", strerror(errno));
2818 	}
2819 
2820 	/* XXX: do we need to call futimes(2) on the output fd? */
2821 
2822 	cvs_freelines(lines);
2823 
2824 }
2825 
2826 /*
2827  * rcs_rev_write_stmp()
2828  *
2829  * Write the contents of the rev <rev> to a temporary file whose path is
2830  * specified using <template> (see mkstemp(3)). NB. This function will modify
2831  * <template>, as per mkstemp.
2832  */
2833 void
2834 rcs_rev_write_stmp(RCSFILE *rfp,  RCSNUM *rev, char *template, int mode)
2835 {
2836 	int fd;
2837 
2838 	if ((fd = mkstemp(template)) == -1)
2839 		fatal("mkstemp: `%s': %s", template, strerror(errno));
2840 
2841 	cvs_worklist_add(template, &temp_files);
2842 	rcs_rev_write_fd(rfp, rev, fd, mode);
2843 
2844 	(void)close(fd);
2845 }
2846 
2847 static void
2848 rcs_kwexp_line(char *rcsfile, struct rcs_delta *rdp, struct cvs_line *line,
2849     int mode)
2850 {
2851 	int kwtype;
2852 	u_int j, found;
2853 	const u_char *c, *start, *fin, *end;
2854 	char *kwstr;
2855 	char expbuf[256], buf[256];
2856 	char *fmt;
2857 	size_t len, kwlen;
2858 
2859 	kwtype = 0;
2860 	kwstr = NULL;
2861 
2862 	len = line->l_len;
2863 	if (len == 0)
2864 		return;
2865 
2866 	c = line->l_line;
2867 	found = 0;
2868 	/* Final character in buffer. */
2869 	fin = c + len - 1;
2870 
2871 	/*
2872 	 * Keyword formats:
2873 	 * $Keyword$
2874 	 * $Keyword: value$
2875 	 */
2876 	for (; c < fin; c++) {
2877 		if (*c == '$') {
2878 			BUF *tmpbuf;
2879 			size_t clen, tlen;
2880 
2881 			/* remember start of this possible keyword */
2882 			start = c;
2883 
2884 			/* first following character has to be alphanumeric */
2885 			c++;
2886 			if (!isalpha(*c)) {
2887 				c = start;
2888 				continue;
2889 			}
2890 
2891 			/* Number of characters between c and fin, inclusive. */
2892 			clen = fin - c + 1;
2893 
2894 			/* look for any matching keywords */
2895 			found = 0;
2896 			for (j = 0; j < RCS_NKWORDS; j++) {
2897 				kwlen = strlen(rcs_expkw[j].kw_str);
2898 				/*
2899 				 * kwlen must be less than clen since clen
2900 				 * includes either a terminating `$' or a `:'.
2901 				 */
2902 				if (kwlen < clen &&
2903 				    memcmp(c, rcs_expkw[j].kw_str, kwlen) == 0 &&
2904 				    (c[kwlen] == '$' || c[kwlen] == ':')) {
2905 					found = 1;
2906 					kwstr = rcs_expkw[j].kw_str;
2907 					kwtype = rcs_expkw[j].kw_type;
2908 					c += kwlen;
2909 					break;
2910 				}
2911 			}
2912 
2913 			if (found == 0 && cvs_tagname != NULL) {
2914 				kwlen = strlen(cvs_tagname);
2915 				if (kwlen < clen &&
2916 				    memcmp(c, cvs_tagname, kwlen) == 0 &&
2917 				    (c[kwlen] == '$' || c[kwlen] == ':')) {
2918 					found = 1;
2919 					kwstr = cvs_tagname;
2920 					kwtype = RCS_KW_ID;
2921 					c += kwlen;
2922 				}
2923 			}
2924 
2925 			/* unknown keyword, continue looking */
2926 			if (found == 0) {
2927 				c = start;
2928 				continue;
2929 			}
2930 
2931 			/*
2932 			 * if the next character was ':' we need to look for
2933 			 * an '$' before the end of the line to be sure it is
2934 			 * in fact a keyword.
2935 			 */
2936 			if (*c == ':') {
2937 				for (; c <= fin; ++c) {
2938 					if (*c == '$' || *c == '\n')
2939 						break;
2940 				}
2941 
2942 				if (*c != '$') {
2943 					c = start;
2944 					continue;
2945 				}
2946 			}
2947 			end = c + 1;
2948 
2949 			/* start constructing the expansion */
2950 			expbuf[0] = '\0';
2951 
2952 			if (mode & RCS_KWEXP_NAME) {
2953 				if (strlcat(expbuf, "$", sizeof(expbuf)) >= sizeof(expbuf) ||
2954 				    strlcat(expbuf, kwstr, sizeof(expbuf)) >= sizeof(expbuf))
2955 					fatal("rcs_kwexp_line: truncated");
2956 				if ((mode & RCS_KWEXP_VAL) &&
2957 				    strlcat(expbuf, ": ", sizeof(expbuf)) >= sizeof(expbuf))
2958 					fatal("rcs_kwexp_line: truncated");
2959 			}
2960 
2961 			/*
2962 			 * order matters because of RCS_KW_ID and
2963 			 * RCS_KW_HEADER here
2964 			 */
2965 			if (mode & RCS_KWEXP_VAL) {
2966 				if (kwtype & RCS_KW_RCSFILE) {
2967 					if (!(kwtype & RCS_KW_FULLPATH))
2968 						(void)strlcat(expbuf, basename(rcsfile), sizeof(expbuf));
2969 					else
2970 						(void)strlcat(expbuf, rcsfile, sizeof(expbuf));
2971 					if (strlcat(expbuf, " ", sizeof(expbuf)) >= sizeof(expbuf))
2972 						fatal("rcs_kwexp_line: truncated");
2973 				}
2974 
2975 				if (kwtype & RCS_KW_REVISION) {
2976 					rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
2977 					if (strlcat(buf, " ", sizeof(buf)) >= sizeof(buf) ||
2978 					    strlcat(expbuf, buf, sizeof(expbuf)) >= sizeof(buf))
2979 						fatal("rcs_kwexp_line: truncated");
2980 				}
2981 
2982 				if (kwtype & RCS_KW_DATE) {
2983 					fmt = "%Y/%m/%d %H:%M:%S ";
2984 
2985 					strftime(buf, sizeof(buf), fmt, &rdp->rd_date);
2986 					if (strlcat(expbuf, buf, sizeof(expbuf)) >= sizeof(expbuf))
2987 						fatal("rcs_kwexp_line: string truncated");
2988 				}
2989 
2990 				if (kwtype & RCS_KW_AUTHOR) {
2991 					if (strlcat(expbuf, rdp->rd_author, sizeof(expbuf)) >= sizeof(expbuf) ||
2992 					    strlcat(expbuf, " ", sizeof(expbuf)) >= sizeof(expbuf))
2993 						fatal("rcs_kwexp_line: string truncated");
2994 				}
2995 
2996 				if (kwtype & RCS_KW_STATE) {
2997 					if (strlcat(expbuf, rdp->rd_state, sizeof(expbuf)) >= sizeof(expbuf) ||
2998 					    strlcat(expbuf, " ", sizeof(expbuf)) >= sizeof(expbuf))
2999 						fatal("rcs_kwexp_line: string truncated");
3000 				}
3001 
3002 				/* order does not matter anymore below */
3003 				if (kwtype & RCS_KW_LOG)
3004 					if (strlcat(expbuf, " ", sizeof(expbuf)) >= sizeof(expbuf))
3005 						fatal("rcs_kwexp_line: string truncated");
3006 
3007 				if (kwtype & RCS_KW_SOURCE) {
3008 					if (strlcat(expbuf, rcsfile, sizeof(expbuf)) >= sizeof(expbuf) ||
3009 					    strlcat(expbuf, " ", sizeof(expbuf)) >= sizeof(expbuf))
3010 						fatal("rcs_kwexp_line: string truncated");
3011 				}
3012 
3013 				if (kwtype & RCS_KW_NAME)
3014 					if (strlcat(expbuf, " ", sizeof(expbuf)) >= sizeof(expbuf))
3015 						fatal("rcs_kwexp_line: string truncated");
3016 			}
3017 
3018 			/* end the expansion */
3019 			if (mode & RCS_KWEXP_NAME)
3020 				if (strlcat(expbuf, "$",
3021 				    sizeof(expbuf)) >= sizeof(expbuf))
3022 					fatal("rcs_kwexp_line: truncated");
3023 
3024 			/* Concatenate everything together. */
3025 			tmpbuf = cvs_buf_alloc(len + strlen(expbuf), BUF_AUTOEXT);
3026 			/* Append everything before keyword. */
3027 			cvs_buf_append(tmpbuf, line->l_line,
3028 			    start - line->l_line);
3029 			/* Append keyword. */
3030 			cvs_buf_append(tmpbuf, expbuf, strlen(expbuf));
3031 			/* Point c to end of keyword. */
3032 			tlen = cvs_buf_len(tmpbuf) - 1;
3033 			/* Append everything after keyword. */
3034 			cvs_buf_append(tmpbuf, end,
3035 			    line->l_line + line->l_len - end);
3036 			c = cvs_buf_get(tmpbuf) + tlen;
3037 			/* Point fin to end of data. */
3038 			fin = cvs_buf_get(tmpbuf) + cvs_buf_len(tmpbuf) - 1;
3039 			/* Recalculate new length. */
3040 			len = cvs_buf_len(tmpbuf);
3041 
3042 			/* tmpbuf is now ready, convert to string */
3043 			if (line->l_needsfree)
3044 				xfree(line->l_line);
3045 			line->l_len = len;
3046 			line->l_line = cvs_buf_release(tmpbuf);
3047 			line->l_needsfree = 1;
3048 		}
3049 	}
3050 }
3051