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