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