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