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