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