1/*	$OpenBSD: rcs.c,v 1.89 2021/11/28 19:28:42 deraadt 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/types.h>
28#include <sys/stat.h>
29
30#include <ctype.h>
31#include <err.h>
32#include <errno.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 "rcsparse.h"
43#include "rcsprog.h"
44#include "rcsutil.h"
45#include "xmalloc.h"
46
47#define _MAXBSIZE (64 * 1024)
48
49#define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
50
51/* invalid characters in RCS states */
52static const char rcs_state_invch[] = RCS_STATE_INVALCHAR;
53
54/* invalid characters in RCS symbol names */
55static const char rcs_sym_invch[] = RCS_SYM_INVALCHAR;
56
57struct rcs_kw rcs_expkw[] =  {
58	{ "Author",	RCS_KW_AUTHOR   },
59	{ "Date",	RCS_KW_DATE     },
60	{ "Locker",	RCS_KW_LOCKER   },
61	{ "Header",	RCS_KW_HEADER   },
62	{ "Id",		RCS_KW_ID       },
63	{ "OpenBSD",	RCS_KW_ID       },
64	{ "Log",	RCS_KW_LOG      },
65	{ "Name",	RCS_KW_NAME     },
66	{ "RCSfile",	RCS_KW_RCSFILE  },
67	{ "Revision",	RCS_KW_REVISION },
68	{ "Source",	RCS_KW_SOURCE   },
69	{ "State",	RCS_KW_STATE    },
70	{ "Mdocdate",	RCS_KW_MDOCDATE },
71};
72
73int rcs_errno = RCS_ERR_NOERR;
74char *timezone_flag = NULL;
75
76int		rcs_patch_lines(struct rcs_lines *, struct rcs_lines *);
77static int	rcs_movefile(char *, char *, mode_t, u_int);
78
79static void	rcs_freedelta(struct rcs_delta *);
80static void	rcs_strprint(const u_char *, size_t, FILE *);
81
82static BUF	*rcs_expand_keywords(char *, struct rcs_delta *, BUF *, int);
83
84RCSFILE *
85rcs_open(const char *path, int fd, int flags, ...)
86{
87	int mode;
88	mode_t fmode;
89	RCSFILE *rfp;
90	va_list vap;
91	struct rcs_delta *rdp;
92	struct rcs_lock *lkr;
93
94	fmode = S_IRUSR|S_IRGRP|S_IROTH;
95	flags &= 0xffff;	/* ditch any internal flags */
96
97	if (flags & RCS_CREATE) {
98		va_start(vap, flags);
99		mode = va_arg(vap, int);
100		va_end(vap);
101		fmode = (mode_t)mode;
102	}
103
104	rfp = xcalloc(1, sizeof(*rfp));
105
106	rfp->rf_path = xstrdup(path);
107	rfp->rf_flags = flags | RCS_SLOCK | RCS_SYNCED;
108	rfp->rf_mode = fmode;
109	if (fd == -1)
110		rfp->rf_file = NULL;
111	else if ((rfp->rf_file = fdopen(fd, "r")) == NULL)
112		err(1, "rcs_open: fdopen: `%s'", path);
113
114	TAILQ_INIT(&(rfp->rf_delta));
115	TAILQ_INIT(&(rfp->rf_access));
116	TAILQ_INIT(&(rfp->rf_symbols));
117	TAILQ_INIT(&(rfp->rf_locks));
118
119	if (!(rfp->rf_flags & RCS_CREATE)) {
120		if (rcsparse_init(rfp))
121			errx(1, "could not parse admin data");
122
123		/* fill in rd_locker */
124		TAILQ_FOREACH(lkr, &(rfp->rf_locks), rl_list) {
125			if ((rdp = rcs_findrev(rfp, lkr->rl_num)) == NULL) {
126				rcs_close(rfp);
127				return (NULL);
128			}
129
130			rdp->rd_locker = xstrdup(lkr->rl_name);
131		}
132	}
133
134	return (rfp);
135}
136
137/*
138 * rcs_close()
139 *
140 * Close an RCS file handle.
141 */
142void
143rcs_close(RCSFILE *rfp)
144{
145	struct rcs_delta *rdp;
146	struct rcs_access *rap;
147	struct rcs_lock *rlp;
148	struct rcs_sym *rsp;
149
150	if ((rfp->rf_flags & RCS_WRITE) && !(rfp->rf_flags & RCS_SYNCED))
151		rcs_write(rfp);
152
153	while (!TAILQ_EMPTY(&(rfp->rf_delta))) {
154		rdp = TAILQ_FIRST(&(rfp->rf_delta));
155		TAILQ_REMOVE(&(rfp->rf_delta), rdp, rd_list);
156		rcs_freedelta(rdp);
157	}
158
159	while (!TAILQ_EMPTY(&(rfp->rf_access))) {
160		rap = TAILQ_FIRST(&(rfp->rf_access));
161		TAILQ_REMOVE(&(rfp->rf_access), rap, ra_list);
162		free(rap->ra_name);
163		free(rap);
164	}
165
166	while (!TAILQ_EMPTY(&(rfp->rf_symbols))) {
167		rsp = TAILQ_FIRST(&(rfp->rf_symbols));
168		TAILQ_REMOVE(&(rfp->rf_symbols), rsp, rs_list);
169		rcsnum_free(rsp->rs_num);
170		free(rsp->rs_name);
171		free(rsp);
172	}
173
174	while (!TAILQ_EMPTY(&(rfp->rf_locks))) {
175		rlp = TAILQ_FIRST(&(rfp->rf_locks));
176		TAILQ_REMOVE(&(rfp->rf_locks), rlp, rl_list);
177		rcsnum_free(rlp->rl_num);
178		free(rlp->rl_name);
179		free(rlp);
180	}
181
182	rcsnum_free(rfp->rf_head);
183	rcsnum_free(rfp->rf_branch);
184
185	if (rfp->rf_file != NULL)
186		fclose(rfp->rf_file);
187
188	free(rfp->rf_path);
189	free(rfp->rf_comment);
190	free(rfp->rf_expand);
191	free(rfp->rf_desc);
192	if (rfp->rf_pdata != NULL)
193		rcsparse_free(rfp);
194
195	free(rfp);
196}
197
198/*
199 * rcs_write()
200 *
201 * Write the contents of the RCS file handle <rfp> to disk in the file whose
202 * path is in <rf_path>.
203 */
204void
205rcs_write(RCSFILE *rfp)
206{
207	FILE *fp;
208	char numbuf[RCS_REV_BUFSZ], *fn;
209	struct rcs_access *ap;
210	struct rcs_sym *symp;
211	struct rcs_branch *brp;
212	struct rcs_delta *rdp;
213	struct rcs_lock *lkp;
214	size_t len;
215	int fd;
216
217	fn = NULL;
218
219	if (rfp->rf_flags & RCS_SYNCED)
220		return;
221
222	/* Write operations need the whole file parsed */
223	if (rcsparse_deltatexts(rfp, NULL))
224		errx(1, "problem parsing deltatexts");
225
226	(void)xasprintf(&fn, "%s/rcs.XXXXXXXXXX", rcs_tmpdir);
227
228	if ((fd = mkstemp(fn)) == -1)
229		err(1, "%s", fn);
230
231	if ((fp = fdopen(fd, "w+")) == NULL) {
232		int saved_errno;
233
234		saved_errno = errno;
235		(void)unlink(fn);
236		errno = saved_errno;
237		err(1, "%s", fn);
238	}
239
240	worklist_add(fn, &temp_files);
241
242	if (rfp->rf_head != NULL)
243		rcsnum_tostr(rfp->rf_head, numbuf, sizeof(numbuf));
244	else
245		numbuf[0] = '\0';
246
247	fprintf(fp, "head\t%s;\n", numbuf);
248
249	if (rfp->rf_branch != NULL) {
250		rcsnum_tostr(rfp->rf_branch, numbuf, sizeof(numbuf));
251		fprintf(fp, "branch\t%s;\n", numbuf);
252	}
253
254	fputs("access", fp);
255	TAILQ_FOREACH(ap, &(rfp->rf_access), ra_list) {
256		fprintf(fp, "\n\t%s", ap->ra_name);
257	}
258	fputs(";\n", fp);
259
260	fprintf(fp, "symbols");
261	TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
262		if (RCSNUM_ISBRANCH(symp->rs_num))
263			rcsnum_addmagic(symp->rs_num);
264		rcsnum_tostr(symp->rs_num, numbuf, sizeof(numbuf));
265		fprintf(fp, "\n\t%s:%s", symp->rs_name, numbuf);
266	}
267	fprintf(fp, ";\n");
268
269	fprintf(fp, "locks");
270	TAILQ_FOREACH(lkp, &(rfp->rf_locks), rl_list) {
271		rcsnum_tostr(lkp->rl_num, numbuf, sizeof(numbuf));
272		fprintf(fp, "\n\t%s:%s", lkp->rl_name, numbuf);
273	}
274
275	fprintf(fp, ";");
276
277	if (rfp->rf_flags & RCS_SLOCK)
278		fprintf(fp, " strict;");
279	fputc('\n', fp);
280
281	fputs("comment\t@", fp);
282	if (rfp->rf_comment != NULL) {
283		rcs_strprint((const u_char *)rfp->rf_comment,
284		    strlen(rfp->rf_comment), fp);
285		fputs("@;\n", fp);
286	} else
287		fputs("# @;\n", fp);
288
289	if (rfp->rf_expand != NULL) {
290		fputs("expand @", fp);
291		rcs_strprint((const u_char *)rfp->rf_expand,
292		    strlen(rfp->rf_expand), fp);
293		fputs("@;\n", fp);
294	}
295
296	fputs("\n\n", fp);
297
298	TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
299		fprintf(fp, "%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
300		    sizeof(numbuf)));
301		fprintf(fp, "date\t%d.%02d.%02d.%02d.%02d.%02d;",
302		    rdp->rd_date.tm_year + 1900, rdp->rd_date.tm_mon + 1,
303		    rdp->rd_date.tm_mday, rdp->rd_date.tm_hour,
304		    rdp->rd_date.tm_min, rdp->rd_date.tm_sec);
305		fprintf(fp, "\tauthor %s;\tstate %s;\n",
306		    rdp->rd_author, rdp->rd_state);
307		fputs("branches", fp);
308		TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
309			fprintf(fp, "\n\t%s", rcsnum_tostr(brp->rb_num, numbuf,
310			    sizeof(numbuf)));
311		}
312		fputs(";\n", fp);
313		fprintf(fp, "next\t%s;\n\n", rcsnum_tostr(rdp->rd_next,
314		    numbuf, sizeof(numbuf)));
315	}
316
317	fputs("\ndesc\n@", fp);
318	if (rfp->rf_desc != NULL && (len = strlen(rfp->rf_desc)) > 0) {
319		rcs_strprint((const u_char *)rfp->rf_desc, len, fp);
320		if (rfp->rf_desc[len-1] != '\n')
321			fputc('\n', fp);
322	}
323	fputs("@\n", fp);
324
325	/* deltatexts */
326	TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
327		fprintf(fp, "\n\n%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
328		    sizeof(numbuf)));
329		fputs("log\n@", fp);
330		if (rdp->rd_log != NULL) {
331			len = strlen(rdp->rd_log);
332			rcs_strprint((const u_char *)rdp->rd_log, len, fp);
333			if (len == 0 || rdp->rd_log[len-1] != '\n')
334				fputc('\n', fp);
335		}
336		fputs("@\ntext\n@", fp);
337		if (rdp->rd_text != NULL)
338			rcs_strprint(rdp->rd_text, rdp->rd_tlen, fp);
339		fputs("@\n", fp);
340	}
341	(void)fclose(fp);
342
343	if (rcs_movefile(fn, rfp->rf_path, rfp->rf_mode, rfp->rf_flags) == -1) {
344		(void)unlink(fn);
345		errx(1, "rcs_movefile failed");
346	}
347
348	rfp->rf_flags |= RCS_SYNCED;
349
350	free(fn);
351}
352
353/*
354 * rcs_movefile()
355 *
356 * Move a file using rename(2) if possible and copying if not.
357 * Returns 0 on success, -1 on failure.
358 */
359static int
360rcs_movefile(char *from, char *to, mode_t perm, u_int to_flags)
361{
362	FILE *src, *dst;
363	size_t nread, nwritten;
364	char *buf;
365
366	if (rename(from, to) == 0) {
367		if (chmod(to, perm) == -1) {
368			warn("%s", to);
369			return (-1);
370		}
371		return (0);
372	} else if (errno != EXDEV) {
373		warn("failed to access temp RCS output file");
374		return (-1);
375	}
376
377	if ((chmod(to, S_IWUSR) == -1) && !(to_flags & RCS_CREATE)) {
378		warnx("chmod(%s, 0%o) failed", to, S_IWUSR);
379		return (-1);
380	}
381
382	/* different filesystem, have to copy the file */
383	if ((src = fopen(from, "r")) == NULL) {
384		warn("%s", from);
385		return (-1);
386	}
387	if ((dst = fopen(to, "w")) == NULL) {
388		warn("%s", to);
389		(void)fclose(src);
390		return (-1);
391	}
392	if (fchmod(fileno(dst), perm)) {
393		warn("%s", to);
394		(void)unlink(to);
395		(void)fclose(src);
396		(void)fclose(dst);
397		return (-1);
398	}
399
400	buf = xmalloc(_MAXBSIZE);
401	while ((nread = fread(buf, sizeof(char), _MAXBSIZE, src)) != 0) {
402		if (ferror(src)) {
403			warnx("failed to read `%s'", from);
404			(void)unlink(to);
405			goto out;
406		}
407		nwritten = fwrite(buf, sizeof(char), nread, dst);
408		if (nwritten != nread) {
409			warnx("failed to write `%s'", to);
410			(void)unlink(to);
411			goto out;
412		}
413	}
414
415	(void)unlink(from);
416
417out:
418	(void)fclose(src);
419	(void)fclose(dst);
420	free(buf);
421
422	return (0);
423}
424
425/*
426 * rcs_head_set()
427 *
428 * Set the revision number of the head revision for the RCS file <file> to
429 * <rev>, which must reference a valid revision within the file.
430 */
431int
432rcs_head_set(RCSFILE *file, RCSNUM *rev)
433{
434	if (rcs_findrev(file, rev) == NULL)
435		return (-1);
436
437	if (file->rf_head == NULL)
438		file->rf_head = rcsnum_alloc();
439
440	rcsnum_cpy(rev, file->rf_head, 0);
441	file->rf_flags &= ~RCS_SYNCED;
442	return (0);
443}
444
445
446/*
447 * rcs_branch_get()
448 *
449 * Retrieve the default branch number for the RCS file <file>.
450 * Returns the number on success.  If NULL is returned, then there is no
451 * default branch for this file.
452 */
453const RCSNUM *
454rcs_branch_get(RCSFILE *file)
455{
456	return (file->rf_branch);
457}
458
459/*
460 * rcs_access_add()
461 *
462 * Add the login name <login> to the access list for the RCS file <file>.
463 * Returns 0 on success, or -1 on failure.
464 */
465int
466rcs_access_add(RCSFILE *file, const char *login)
467{
468	struct rcs_access *ap;
469
470	/* first look for duplication */
471	TAILQ_FOREACH(ap, &(file->rf_access), ra_list) {
472		if (strcmp(ap->ra_name, login) == 0) {
473			rcs_errno = RCS_ERR_DUPENT;
474			return (-1);
475		}
476	}
477
478	ap = xmalloc(sizeof(*ap));
479	ap->ra_name = xstrdup(login);
480	TAILQ_INSERT_TAIL(&(file->rf_access), ap, ra_list);
481
482	/* not synced anymore */
483	file->rf_flags &= ~RCS_SYNCED;
484	return (0);
485}
486
487/*
488 * rcs_access_remove()
489 *
490 * Remove an entry with login name <login> from the access list of the RCS
491 * file <file>.
492 * Returns 0 on success, or -1 on failure.
493 */
494int
495rcs_access_remove(RCSFILE *file, const char *login)
496{
497	struct rcs_access *ap;
498
499	TAILQ_FOREACH(ap, &(file->rf_access), ra_list)
500		if (strcmp(ap->ra_name, login) == 0)
501			break;
502
503	if (ap == NULL) {
504		rcs_errno = RCS_ERR_NOENT;
505		return (-1);
506	}
507
508	TAILQ_REMOVE(&(file->rf_access), ap, ra_list);
509	free(ap->ra_name);
510	free(ap);
511
512	/* not synced anymore */
513	file->rf_flags &= ~RCS_SYNCED;
514	return (0);
515}
516
517/*
518 * rcs_sym_add()
519 *
520 * Add a symbol to the list of symbols for the RCS file <rfp>.  The new symbol
521 * is named <sym> and is bound to the RCS revision <snum>.
522 * Returns 0 on success, or -1 on failure.
523 */
524int
525rcs_sym_add(RCSFILE *rfp, const char *sym, RCSNUM *snum)
526{
527	struct rcs_sym *symp;
528
529	if (!rcs_sym_check(sym)) {
530		rcs_errno = RCS_ERR_BADSYM;
531		return (-1);
532	}
533
534	/* first look for duplication */
535	TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
536		if (strcmp(symp->rs_name, sym) == 0) {
537			rcs_errno = RCS_ERR_DUPENT;
538			return (-1);
539		}
540	}
541
542	symp = xmalloc(sizeof(*symp));
543	symp->rs_name = xstrdup(sym);
544	symp->rs_num = rcsnum_alloc();
545	rcsnum_cpy(snum, symp->rs_num, 0);
546
547	TAILQ_INSERT_HEAD(&(rfp->rf_symbols), symp, rs_list);
548
549	/* not synced anymore */
550	rfp->rf_flags &= ~RCS_SYNCED;
551	return (0);
552}
553
554/*
555 * rcs_sym_remove()
556 *
557 * Remove the symbol with name <sym> from the symbol list for the RCS file
558 * <file>.  If no such symbol is found, the call fails and returns with an
559 * error.
560 * Returns 0 on success, or -1 on failure.
561 */
562int
563rcs_sym_remove(RCSFILE *file, const char *sym)
564{
565	struct rcs_sym *symp;
566
567	if (!rcs_sym_check(sym)) {
568		rcs_errno = RCS_ERR_BADSYM;
569		return (-1);
570	}
571
572	TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
573		if (strcmp(symp->rs_name, sym) == 0)
574			break;
575
576	if (symp == NULL) {
577		rcs_errno = RCS_ERR_NOENT;
578		return (-1);
579	}
580
581	TAILQ_REMOVE(&(file->rf_symbols), symp, rs_list);
582	free(symp->rs_name);
583	rcsnum_free(symp->rs_num);
584	free(symp);
585
586	/* not synced anymore */
587	file->rf_flags &= ~RCS_SYNCED;
588	return (0);
589}
590
591/*
592 * rcs_sym_getrev()
593 *
594 * Retrieve the RCS revision number associated with the symbol <sym> for the
595 * RCS file <file>.  The returned value is a dynamically-allocated copy and
596 * should be freed by the caller once they are done with it.
597 * Returns the RCSNUM on success, or NULL on failure.
598 */
599RCSNUM *
600rcs_sym_getrev(RCSFILE *file, const char *sym)
601{
602	RCSNUM *num;
603	struct rcs_sym *symp;
604
605	if (!rcs_sym_check(sym)) {
606		rcs_errno = RCS_ERR_BADSYM;
607		return (NULL);
608	}
609
610	num = NULL;
611	TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
612		if (strcmp(symp->rs_name, sym) == 0)
613			break;
614
615	if (symp == NULL) {
616		rcs_errno = RCS_ERR_NOENT;
617	} else {
618		num = rcsnum_alloc();
619		rcsnum_cpy(symp->rs_num, num, 0);
620	}
621
622	return (num);
623}
624
625/*
626 * rcs_sym_check()
627 *
628 * Check the RCS symbol name <sym> for any unsupported characters.
629 * Returns 1 if the tag is correct, 0 if it isn't valid.
630 */
631int
632rcs_sym_check(const char *sym)
633{
634	int ret;
635	const unsigned char *cp;
636
637	ret = 1;
638	cp = sym;
639	if (!isalpha(*cp++))
640		return (0);
641
642	for (; *cp != '\0'; cp++)
643		if (!isgraph(*cp) || (strchr(rcs_sym_invch, *cp) != NULL)) {
644			ret = 0;
645			break;
646		}
647
648	return (ret);
649}
650
651/*
652 * rcs_lock_getmode()
653 *
654 * Retrieve the locking mode of the RCS file <file>.
655 */
656int
657rcs_lock_getmode(RCSFILE *file)
658{
659	return (file->rf_flags & RCS_SLOCK) ? RCS_LOCK_STRICT : RCS_LOCK_LOOSE;
660}
661
662/*
663 * rcs_lock_setmode()
664 *
665 * Set the locking mode of the RCS file <file> to <mode>, which must either
666 * be RCS_LOCK_LOOSE or RCS_LOCK_STRICT.
667 * Returns the previous mode on success, or -1 on failure.
668 */
669int
670rcs_lock_setmode(RCSFILE *file, int mode)
671{
672	int pmode;
673	pmode = rcs_lock_getmode(file);
674
675	if (mode == RCS_LOCK_STRICT)
676		file->rf_flags |= RCS_SLOCK;
677	else if (mode == RCS_LOCK_LOOSE)
678		file->rf_flags &= ~RCS_SLOCK;
679	else
680		errx(1, "rcs_lock_setmode: invalid mode `%d'", mode);
681
682	file->rf_flags &= ~RCS_SYNCED;
683	return (pmode);
684}
685
686/*
687 * rcs_lock_add()
688 *
689 * Add an RCS lock for the user <user> on revision <rev>.
690 * Returns 0 on success, or -1 on failure.
691 */
692int
693rcs_lock_add(RCSFILE *file, const char *user, RCSNUM *rev)
694{
695	struct rcs_lock *lkp;
696	struct rcs_delta *rdp;
697
698	if ((rdp = rcs_findrev(file, rev)) == NULL) {
699		rcs_errno = RCS_ERR_NOENT;
700		return (-1);
701	}
702
703	/* first look for duplication */
704	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
705		if (strcmp(lkp->rl_name, user) == 0 &&
706		    rcsnum_cmp(rev, lkp->rl_num, 0) == 0) {
707			rcs_errno = RCS_ERR_DUPENT;
708			return (-1);
709		}
710	}
711
712	lkp = xmalloc(sizeof(*lkp));
713	lkp->rl_name = xstrdup(user);
714	lkp->rl_num = rcsnum_alloc();
715	rcsnum_cpy(rev, lkp->rl_num, 0);
716
717	free(rdp->rd_locker);
718	rdp->rd_locker = xstrdup(user);
719
720	TAILQ_INSERT_TAIL(&(file->rf_locks), lkp, rl_list);
721
722	/* not synced anymore */
723	file->rf_flags &= ~RCS_SYNCED;
724	return (0);
725}
726
727
728/*
729 * rcs_lock_remove()
730 *
731 * Remove the RCS lock on revision <rev>.
732 * Returns 0 on success, or -1 on failure.
733 */
734int
735rcs_lock_remove(RCSFILE *file, const char *user, RCSNUM *rev)
736{
737	struct rcs_lock *lkp;
738	struct rcs_delta *rdp;
739
740	if ((rdp = rcs_findrev(file, rev)) == NULL) {
741		rcs_errno = RCS_ERR_NOENT;
742		return (-1);
743	}
744
745	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
746		if (strcmp(lkp->rl_name, user) == 0 &&
747		    rcsnum_cmp(lkp->rl_num, rev, 0) == 0)
748			break;
749	}
750
751	if (lkp == NULL) {
752		rcs_errno = RCS_ERR_NOENT;
753		return (-1);
754	}
755
756	TAILQ_REMOVE(&(file->rf_locks), lkp, rl_list);
757	rcsnum_free(lkp->rl_num);
758	free(lkp->rl_name);
759	free(lkp);
760
761	free(rdp->rd_locker);
762	rdp->rd_locker = NULL;
763
764	/* not synced anymore */
765	file->rf_flags &= ~RCS_SYNCED;
766	return (0);
767}
768
769/*
770 * rcs_desc_set()
771 *
772 * Set the description for the RCS file <file>.
773 */
774void
775rcs_desc_set(RCSFILE *file, const char *desc)
776{
777	char *tmp;
778
779	tmp = xstrdup(desc);
780	free(file->rf_desc);
781	file->rf_desc = tmp;
782	file->rf_flags &= ~RCS_SYNCED;
783}
784
785/*
786 * rcs_comment_set()
787 *
788 * Set the comment leader for the RCS file <file>.
789 */
790void
791rcs_comment_set(RCSFILE *file, const char *comment)
792{
793	char *tmp;
794
795	tmp = xstrdup(comment);
796	free(file->rf_comment);
797	file->rf_comment = tmp;
798	file->rf_flags &= ~RCS_SYNCED;
799}
800
801int
802rcs_patch_lines(struct rcs_lines *dlines, struct rcs_lines *plines)
803{
804	char op, *ep;
805	struct rcs_line *lp, *dlp, *ndlp;
806	int i, lineno, nbln;
807	u_char tmp;
808
809	dlp = TAILQ_FIRST(&(dlines->l_lines));
810	lp = TAILQ_FIRST(&(plines->l_lines));
811
812	/* skip first bogus line */
813	for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
814	    lp = TAILQ_NEXT(lp, l_list)) {
815		if (lp->l_len < 2)
816			errx(1, "line too short, RCS patch seems broken");
817		op = *(lp->l_line);
818		/* NUL-terminate line buffer for strtol() safety. */
819		tmp = lp->l_line[lp->l_len - 1];
820		lp->l_line[lp->l_len - 1] = '\0';
821		lineno = (int)strtol((lp->l_line + 1), &ep, 10);
822		if (lineno > dlines->l_nblines || lineno < 0 ||
823		    *ep != ' ')
824			errx(1, "invalid line specification in RCS patch");
825		ep++;
826		nbln = (int)strtol(ep, &ep, 10);
827		/* Restore the last byte of the buffer */
828		lp->l_line[lp->l_len - 1] = tmp;
829		if (nbln < 0)
830			errx(1,
831			    "invalid line number specification in RCS patch");
832
833		/* find the appropriate line */
834		for (;;) {
835			if (dlp == NULL)
836				break;
837			if (dlp->l_lineno == lineno)
838				break;
839			if (dlp->l_lineno > lineno) {
840				dlp = TAILQ_PREV(dlp, tqh, l_list);
841			} else if (dlp->l_lineno < lineno) {
842				if (((ndlp = TAILQ_NEXT(dlp, l_list)) == NULL) ||
843				    ndlp->l_lineno > lineno)
844					break;
845				dlp = ndlp;
846			}
847		}
848		if (dlp == NULL)
849			errx(1, "can't find referenced line in RCS patch");
850
851		if (op == 'd') {
852			for (i = 0; (i < nbln) && (dlp != NULL); i++) {
853				ndlp = TAILQ_NEXT(dlp, l_list);
854				TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list);
855				free(dlp);
856				dlp = ndlp;
857				/* last line is gone - reset dlp */
858				if (dlp == NULL) {
859					ndlp = TAILQ_LAST(&(dlines->l_lines),
860					    tqh);
861					dlp = ndlp;
862				}
863			}
864		} else if (op == 'a') {
865			for (i = 0; i < nbln; i++) {
866				ndlp = lp;
867				lp = TAILQ_NEXT(lp, l_list);
868				if (lp == NULL)
869					errx(1, "truncated RCS patch");
870				TAILQ_REMOVE(&(plines->l_lines), lp, l_list);
871				TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp,
872				    lp, l_list);
873				dlp = lp;
874
875				/* we don't want lookup to block on those */
876				lp->l_lineno = lineno;
877
878				lp = ndlp;
879			}
880		} else
881			errx(1, "unknown RCS patch operation `%c'", op);
882
883		/* last line of the patch, done */
884		if (lp->l_lineno == plines->l_nblines)
885			break;
886	}
887
888	/* once we're done patching, rebuild the line numbers */
889	lineno = 0;
890	TAILQ_FOREACH(lp, &(dlines->l_lines), l_list)
891		lp->l_lineno = lineno++;
892	dlines->l_nblines = lineno - 1;
893
894	return (0);
895}
896
897/*
898 * rcs_getrev()
899 *
900 * Get the whole contents of revision <rev> from the RCSFILE <rfp>.  The
901 * returned buffer is dynamically allocated and should be released using
902 * buf_free() once the caller is done using it.
903 */
904BUF *
905rcs_getrev(RCSFILE *rfp, RCSNUM *frev)
906{
907	u_int i, numlen;
908	int isbranch, lookonbranch, found;
909	size_t dlen, plen, len;
910	RCSNUM *crev, *rev, *brev;
911	BUF *rbuf;
912	struct rcs_delta *rdp = NULL;
913	struct rcs_branch *rb;
914	u_char *data, *patch;
915
916	if (rfp->rf_head == NULL)
917		return (NULL);
918
919	if (frev == RCS_HEAD_REV)
920		rev = rfp->rf_head;
921	else
922		rev = frev;
923
924	/* XXX rcsnum_cmp() */
925	for (i = 0; i < rfp->rf_head->rn_len; i++) {
926		if (rfp->rf_head->rn_id[i] < rev->rn_id[i]) {
927			rcs_errno = RCS_ERR_NOENT;
928			return (NULL);
929		}
930	}
931
932	/* No matter what, we'll need everything parsed up until the description
933           so go for it. */
934	if (rcsparse_deltas(rfp, NULL))
935		return (NULL);
936
937	rdp = rcs_findrev(rfp, rfp->rf_head);
938	if (rdp == NULL) {
939		warnx("failed to get RCS HEAD revision");
940		return (NULL);
941	}
942
943	if (rdp->rd_tlen == 0)
944		if (rcsparse_deltatexts(rfp, rfp->rf_head))
945			return (NULL);
946
947	len = rdp->rd_tlen;
948	if (len == 0) {
949		rbuf = buf_alloc(1);
950		buf_empty(rbuf);
951		return (rbuf);
952	}
953
954	rbuf = buf_alloc(len);
955	buf_append(rbuf, rdp->rd_text, len);
956
957	isbranch = 0;
958	brev = NULL;
959
960	/*
961	 * If a branch was passed, get the latest revision on it.
962	 */
963	if (RCSNUM_ISBRANCH(rev)) {
964		brev = rev;
965		rdp = rcs_findrev(rfp, rev);
966		if (rdp == NULL) {
967			buf_free(rbuf);
968			return (NULL);
969		}
970
971		rev = rdp->rd_num;
972	} else {
973		if (RCSNUM_ISBRANCHREV(rev)) {
974			brev = rcsnum_revtobr(rev);
975			isbranch = 1;
976		}
977	}
978
979	lookonbranch = 0;
980	crev = NULL;
981
982	/* Apply patches backwards to get the right version.
983	 */
984	do {
985		found = 0;
986
987		if (rcsnum_cmp(rfp->rf_head, rev, 0) == 0)
988			break;
989
990		if (isbranch == 1 && rdp->rd_num->rn_len < rev->rn_len &&
991		    !TAILQ_EMPTY(&(rdp->rd_branches)))
992			lookonbranch = 1;
993
994		if (isbranch && lookonbranch == 1) {
995			lookonbranch = 0;
996			TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) {
997				/* XXX rcsnum_cmp() is totally broken for
998				 * this purpose.
999				 */
1000				numlen = MINIMUM(brev->rn_len,
1001				    rb->rb_num->rn_len - 1);
1002				for (i = 0; i < numlen; i++) {
1003					if (rb->rb_num->rn_id[i] !=
1004					    brev->rn_id[i])
1005						break;
1006				}
1007
1008				if (i == numlen) {
1009					crev = rb->rb_num;
1010					found = 1;
1011					break;
1012				}
1013			}
1014			if (found == 0)
1015				crev = rdp->rd_next;
1016		} else {
1017			crev = rdp->rd_next;
1018		}
1019
1020		rdp = rcs_findrev(rfp, crev);
1021		if (rdp == NULL) {
1022			buf_free(rbuf);
1023			return (NULL);
1024		}
1025
1026		plen = rdp->rd_tlen;
1027		dlen = buf_len(rbuf);
1028		patch = rdp->rd_text;
1029		data = buf_release(rbuf);
1030		/* check if we have parsed this rev's deltatext */
1031		if (rdp->rd_tlen == 0)
1032			if (rcsparse_deltatexts(rfp, rdp->rd_num))
1033				return (NULL);
1034
1035		rbuf = rcs_patchfile(data, dlen, patch, plen, rcs_patch_lines);
1036		free(data);
1037
1038		if (rbuf == NULL)
1039			break;
1040	} while (rcsnum_cmp(crev, rev, 0) != 0);
1041
1042	return (rbuf);
1043}
1044
1045void
1046rcs_delta_stats(struct rcs_delta *rdp, int *ladded, int *lremoved)
1047{
1048	struct rcs_lines *plines;
1049	struct rcs_line *lp;
1050	int added, i, nbln, removed;
1051	char op, *ep;
1052	u_char tmp;
1053
1054	added = removed = 0;
1055
1056	plines = rcs_splitlines(rdp->rd_text, rdp->rd_tlen);
1057	lp = TAILQ_FIRST(&(plines->l_lines));
1058
1059	/* skip first bogus line */
1060	for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
1061		lp = TAILQ_NEXT(lp, l_list)) {
1062			if (lp->l_len < 2)
1063				errx(1,
1064				    "line too short, RCS patch seems broken");
1065			op = *(lp->l_line);
1066			/* NUL-terminate line buffer for strtol() safety. */
1067			tmp = lp->l_line[lp->l_len - 1];
1068			lp->l_line[lp->l_len - 1] = '\0';
1069			(void)strtol((lp->l_line + 1), &ep, 10);
1070			ep++;
1071			nbln = (int)strtol(ep, &ep, 10);
1072			/* Restore the last byte of the buffer */
1073			lp->l_line[lp->l_len - 1] = tmp;
1074			if (nbln < 0)
1075				errx(1, "invalid line number specification "
1076				    "in RCS patch");
1077
1078			if (op == 'a') {
1079				added += nbln;
1080				for (i = 0; i < nbln; i++) {
1081					lp = TAILQ_NEXT(lp, l_list);
1082					if (lp == NULL)
1083						errx(1, "truncated RCS patch");
1084				}
1085			} else if (op == 'd')
1086				removed += nbln;
1087			else
1088				errx(1, "unknown RCS patch operation '%c'", op);
1089	}
1090
1091	rcs_freelines(plines);
1092
1093	*ladded = added;
1094	*lremoved = removed;
1095}
1096
1097/*
1098 * rcs_rev_add()
1099 *
1100 * Add a revision to the RCS file <rf>.  The new revision's number can be
1101 * specified in <rev> (which can also be RCS_HEAD_REV, in which case the
1102 * new revision will have a number equal to the previous head revision plus
1103 * one).  The <msg> argument specifies the log message for that revision, and
1104 * <date> specifies the revision's date (a value of -1 is
1105 * equivalent to using the current time).
1106 * If <author> is NULL, set the author for this revision to the current user.
1107 * Returns 0 on success, or -1 on failure.
1108 */
1109int
1110rcs_rev_add(RCSFILE *rf, RCSNUM *rev, const char *msg, time_t date,
1111    const char *author)
1112{
1113	time_t now;
1114	struct passwd *pw;
1115	struct rcs_delta *ordp, *rdp;
1116
1117	if (rev == RCS_HEAD_REV) {
1118		if (rf->rf_flags & RCS_CREATE) {
1119			if ((rev = rcsnum_parse(RCS_HEAD_INIT)) == NULL)
1120				return (-1);
1121			rf->rf_head = rev;
1122		} else {
1123			rev = rcsnum_inc(rf->rf_head);
1124		}
1125	} else {
1126		if ((rdp = rcs_findrev(rf, rev)) != NULL) {
1127			rcs_errno = RCS_ERR_DUPENT;
1128			return (-1);
1129		}
1130	}
1131
1132	rdp = xcalloc(1, sizeof(*rdp));
1133
1134	TAILQ_INIT(&(rdp->rd_branches));
1135
1136	rdp->rd_num = rcsnum_alloc();
1137	rcsnum_cpy(rev, rdp->rd_num, 0);
1138
1139	rdp->rd_next = rcsnum_alloc();
1140
1141	if (!(rf->rf_flags & RCS_CREATE)) {
1142		/* next should point to the previous HEAD */
1143		ordp = TAILQ_FIRST(&(rf->rf_delta));
1144		rcsnum_cpy(ordp->rd_num, rdp->rd_next, 0);
1145	}
1146
1147	if (!author && !(author = getlogin())) {
1148		if (!(pw = getpwuid(getuid())))
1149			errx(1, "getpwuid failed");
1150		author = pw->pw_name;
1151	}
1152	rdp->rd_author = xstrdup(author);
1153	rdp->rd_state = xstrdup(RCS_STATE_EXP);
1154	rdp->rd_log = xstrdup(msg);
1155
1156	if (date != (time_t)(-1))
1157		now = date;
1158	else
1159		time(&now);
1160	gmtime_r(&now, &(rdp->rd_date));
1161
1162	TAILQ_INSERT_HEAD(&(rf->rf_delta), rdp, rd_list);
1163	rf->rf_ndelta++;
1164
1165	/* not synced anymore */
1166	rf->rf_flags &= ~RCS_SYNCED;
1167
1168	return (0);
1169}
1170
1171/*
1172 * rcs_rev_remove()
1173 *
1174 * Remove the revision whose number is <rev> from the RCS file <rf>.
1175 */
1176int
1177rcs_rev_remove(RCSFILE *rf, RCSNUM *rev)
1178{
1179	char *path_tmp1, *path_tmp2;
1180	struct rcs_delta *rdp, *prevrdp, *nextrdp;
1181	BUF *newdeltatext, *nextbuf, *prevbuf, *newdiff;
1182
1183	nextrdp = prevrdp = NULL;
1184	path_tmp1 = path_tmp2 = NULL;
1185
1186	if (rev == RCS_HEAD_REV)
1187		rev = rf->rf_head;
1188
1189	/* do we actually have that revision? */
1190	if ((rdp = rcs_findrev(rf, rev)) == NULL) {
1191		rcs_errno = RCS_ERR_NOENT;
1192		return (-1);
1193	}
1194
1195	/*
1196	 * This is confusing, the previous delta is next in the TAILQ list.
1197	 * the next delta is the previous one in the TAILQ list.
1198	 *
1199	 * When the HEAD revision got specified, nextrdp will be NULL.
1200	 * When the first revision got specified, prevrdp will be NULL.
1201	 */
1202	prevrdp = (struct rcs_delta *)TAILQ_NEXT(rdp, rd_list);
1203	nextrdp = (struct rcs_delta *)TAILQ_PREV(rdp, tqh, rd_list);
1204
1205	newdeltatext = prevbuf = nextbuf = NULL;
1206
1207	if (prevrdp != NULL) {
1208		if ((prevbuf = rcs_getrev(rf, prevrdp->rd_num)) == NULL)
1209			errx(1, "error getting revision");
1210	}
1211
1212	if (prevrdp != NULL && nextrdp != NULL) {
1213		if ((nextbuf = rcs_getrev(rf, nextrdp->rd_num)) == NULL)
1214			errx(1, "error getting revision");
1215
1216		newdiff = buf_alloc(64);
1217
1218		/* calculate new diff */
1219		(void)xasprintf(&path_tmp1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir);
1220		buf_write_stmp(nextbuf, path_tmp1);
1221		buf_free(nextbuf);
1222
1223		(void)xasprintf(&path_tmp2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir);
1224		buf_write_stmp(prevbuf, path_tmp2);
1225		buf_free(prevbuf);
1226
1227		diff_format = D_RCSDIFF;
1228		if (diffreg(path_tmp1, path_tmp2, newdiff, D_FORCEASCII) == D_ERROR)
1229			errx(1, "diffreg failed");
1230
1231		newdeltatext = newdiff;
1232	} else if (nextrdp == NULL && prevrdp != NULL) {
1233		newdeltatext = prevbuf;
1234	}
1235
1236	if (newdeltatext != NULL) {
1237		if (rcs_deltatext_set(rf, prevrdp->rd_num, newdeltatext) < 0)
1238			errx(1, "error setting new deltatext");
1239	}
1240
1241	TAILQ_REMOVE(&(rf->rf_delta), rdp, rd_list);
1242
1243	/* update pointers */
1244	if (prevrdp != NULL && nextrdp != NULL) {
1245		rcsnum_cpy(prevrdp->rd_num, nextrdp->rd_next, 0);
1246	} else if (prevrdp != NULL) {
1247		if (rcs_head_set(rf, prevrdp->rd_num) < 0)
1248			errx(1, "rcs_head_set failed");
1249	} else if (nextrdp != NULL) {
1250		rcsnum_free(nextrdp->rd_next);
1251		nextrdp->rd_next = rcsnum_alloc();
1252	} else {
1253		rcsnum_free(rf->rf_head);
1254		rf->rf_head = NULL;
1255	}
1256
1257	rf->rf_ndelta--;
1258	rf->rf_flags &= ~RCS_SYNCED;
1259
1260	rcs_freedelta(rdp);
1261
1262	free(path_tmp1);
1263	free(path_tmp2);
1264
1265	return (0);
1266}
1267
1268/*
1269 * rcs_findrev()
1270 *
1271 * Find a specific revision's delta entry in the tree of the RCS file <rfp>.
1272 * The revision number is given in <rev>.
1273 *
1274 * If the given revision is a branch number, we translate it into the latest
1275 * revision on the branch.
1276 *
1277 * Returns a pointer to the delta on success, or NULL on failure.
1278 */
1279struct rcs_delta *
1280rcs_findrev(RCSFILE *rfp, RCSNUM *rev)
1281{
1282	u_int cmplen;
1283	struct rcs_delta *rdp;
1284	RCSNUM *brev, *frev;
1285
1286	/*
1287	 * We need to do more parsing if the last revision in the linked list
1288	 * is greater than the requested revision.
1289	 */
1290	rdp = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
1291	if (rdp == NULL ||
1292	    rcsnum_cmp(rdp->rd_num, rev, 0) == -1) {
1293		if (rcsparse_deltas(rfp, rev))
1294			return (NULL);
1295	}
1296
1297	/*
1298	 * Translate a branch into the latest revision on the branch itself.
1299	 */
1300	if (RCSNUM_ISBRANCH(rev)) {
1301		brev = rcsnum_brtorev(rev);
1302		frev = brev;
1303		for (;;) {
1304			rdp = rcs_findrev(rfp, frev);
1305			if (rdp == NULL)
1306				return (NULL);
1307
1308			if (rdp->rd_next->rn_len == 0)
1309				break;
1310
1311			frev = rdp->rd_next;
1312		}
1313
1314		rcsnum_free(brev);
1315		return (rdp);
1316	}
1317
1318	cmplen = rev->rn_len;
1319
1320	TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
1321		if (rcsnum_cmp(rdp->rd_num, rev, cmplen) == 0)
1322			return (rdp);
1323	}
1324
1325	return (NULL);
1326}
1327
1328/*
1329 * rcs_kwexp_set()
1330 *
1331 * Set the keyword expansion mode to use on the RCS file <file> to <mode>.
1332 */
1333void
1334rcs_kwexp_set(RCSFILE *file, int mode)
1335{
1336	int i;
1337	char *tmp, buf[8] = "";
1338
1339	if (RCS_KWEXP_INVAL(mode))
1340		return;
1341
1342	i = 0;
1343	if (mode == RCS_KWEXP_NONE)
1344		buf[0] = 'b';
1345	else if (mode == RCS_KWEXP_OLD)
1346		buf[0] = 'o';
1347	else {
1348		if (mode & RCS_KWEXP_NAME)
1349			buf[i++] = 'k';
1350		if (mode & RCS_KWEXP_VAL)
1351			buf[i++] = 'v';
1352		if (mode & RCS_KWEXP_LKR)
1353			buf[i++] = 'l';
1354	}
1355
1356	tmp = xstrdup(buf);
1357	free(file->rf_expand);
1358	file->rf_expand = tmp;
1359	/* not synced anymore */
1360	file->rf_flags &= ~RCS_SYNCED;
1361}
1362
1363/*
1364 * rcs_kwexp_get()
1365 *
1366 * Retrieve the keyword expansion mode to be used for the RCS file <file>.
1367 */
1368int
1369rcs_kwexp_get(RCSFILE *file)
1370{
1371	if (file->rf_expand == NULL)
1372		return (RCS_KWEXP_DEFAULT);
1373
1374	return (rcs_kflag_get(file->rf_expand));
1375}
1376
1377/*
1378 * rcs_kflag_get()
1379 *
1380 * Get the keyword expansion mode from a set of character flags given in
1381 * <flags> and return the appropriate flag mask.  In case of an error, the
1382 * returned mask will have the RCS_KWEXP_ERR bit set to 1.
1383 */
1384int
1385rcs_kflag_get(const char *flags)
1386{
1387	int fl;
1388	size_t len;
1389	const char *fp;
1390
1391	if (flags == NULL || !(len = strlen(flags)))
1392		return (RCS_KWEXP_ERR);
1393
1394	fl = 0;
1395	for (fp = flags; *fp != '\0'; fp++) {
1396		if (*fp == 'k')
1397			fl |= RCS_KWEXP_NAME;
1398		else if (*fp == 'v')
1399			fl |= RCS_KWEXP_VAL;
1400		else if (*fp == 'l')
1401			fl |= RCS_KWEXP_LKR;
1402		else if (*fp == 'o') {
1403			if (len != 1)
1404				fl |= RCS_KWEXP_ERR;
1405			fl |= RCS_KWEXP_OLD;
1406		} else if (*fp == 'b') {
1407			if (len != 1)
1408				fl |= RCS_KWEXP_ERR;
1409			fl |= RCS_KWEXP_NONE;
1410		} else	/* unknown letter */
1411			fl |= RCS_KWEXP_ERR;
1412	}
1413
1414	return (fl);
1415}
1416
1417/*
1418 * rcs_freedelta()
1419 *
1420 * Free the contents of a delta structure.
1421 */
1422static void
1423rcs_freedelta(struct rcs_delta *rdp)
1424{
1425	struct rcs_branch *rb;
1426
1427	rcsnum_free(rdp->rd_num);
1428	rcsnum_free(rdp->rd_next);
1429
1430	free(rdp->rd_author);
1431	free(rdp->rd_locker);
1432	free(rdp->rd_state);
1433	free(rdp->rd_log);
1434	free(rdp->rd_text);
1435
1436	while ((rb = TAILQ_FIRST(&(rdp->rd_branches))) != NULL) {
1437		TAILQ_REMOVE(&(rdp->rd_branches), rb, rb_list);
1438		rcsnum_free(rb->rb_num);
1439		free(rb);
1440	}
1441
1442	free(rdp);
1443}
1444
1445/*
1446 * rcs_strprint()
1447 *
1448 * Output an RCS string <str> of size <slen> to the stream <stream>.  Any
1449 * '@' characters are escaped.  Otherwise, the string can contain arbitrary
1450 * binary data.
1451 */
1452static void
1453rcs_strprint(const u_char *str, size_t slen, FILE *stream)
1454{
1455	const u_char *ap, *ep, *sp;
1456
1457	if (slen == 0)
1458		return;
1459
1460	ep = str + slen - 1;
1461
1462	for (sp = str; sp <= ep;)  {
1463		ap = memchr(sp, '@', ep - sp);
1464		if (ap == NULL)
1465			ap = ep;
1466		(void)fwrite(sp, sizeof(u_char), ap - sp + 1, stream);
1467
1468		if (*ap == '@')
1469			putc('@', stream);
1470		sp = ap + 1;
1471	}
1472}
1473
1474/*
1475 * rcs_expand_keywords()
1476 *
1477 * Return expansion any RCS keywords in <data>
1478 *
1479 * On error, return NULL.
1480 */
1481static BUF *
1482rcs_expand_keywords(char *rcsfile_in, struct rcs_delta *rdp, BUF *bp, int mode)
1483{
1484	BUF *newbuf;
1485	u_char *c, *kw, *fin;
1486	char buf[256], *tmpf, resolved[PATH_MAX], *rcsfile;
1487	u_char *line, *line2;
1488	u_int i, j;
1489	int kwtype;
1490	int found;
1491	struct tm tb;
1492
1493	tb = rdp->rd_date;
1494	if (timezone_flag != NULL)
1495		rcs_set_tz(timezone_flag, rdp, &tb);
1496
1497	if (realpath(rcsfile_in, resolved) == NULL)
1498		rcsfile = rcsfile_in;
1499	else
1500		rcsfile = resolved;
1501
1502	newbuf = buf_alloc(buf_len(bp));
1503
1504	/*
1505	 * Keyword formats:
1506	 * $Keyword$
1507	 * $Keyword: value$
1508	 */
1509	c = buf_get(bp);
1510	fin = c + buf_len(bp);
1511	/* Copying to newbuf is deferred until the first keyword. */
1512	found = 0;
1513
1514	while (c < fin) {
1515		kw = memchr(c, '$', fin - c);
1516		if (kw == NULL)
1517			break;
1518		++kw;
1519		if (found) {
1520			/* Copy everything up to and including the $. */
1521			buf_append(newbuf, c, kw - c);
1522		}
1523		c = kw;
1524		/* c points after the $ now. */
1525		if (c == fin)
1526			break;
1527		if (!isalpha(*c)) /* all valid keywords start with a letter */
1528			continue;
1529
1530		for (i = 0; i < RCS_NKWORDS; ++i) {
1531			size_t kwlen;
1532
1533			kwlen = strlen(rcs_expkw[i].kw_str);
1534			/*
1535			 * kwlen must be less than clen since clen includes
1536			 * either a terminating `$' or a `:'.
1537			 */
1538			if (c + kwlen < fin &&
1539			    memcmp(c , rcs_expkw[i].kw_str, kwlen) == 0 &&
1540			    (c[kwlen] == '$' || c[kwlen] == ':')) {
1541				c += kwlen;
1542				break;
1543			}
1544		}
1545		if (i == RCS_NKWORDS)
1546			continue;
1547		kwtype = rcs_expkw[i].kw_type;
1548
1549		/*
1550		 * If the next character is ':' we need to look for an '$'
1551		 * before the end of the line to be sure it is in fact a
1552		 * keyword.
1553		 */
1554		if (*c == ':') {
1555			for (; c < fin; ++c) {
1556				if (*c == '$' || *c == '\n')
1557					break;
1558			}
1559
1560			if (*c != '$') {
1561				if (found)
1562					buf_append(newbuf, kw, c - kw);
1563				continue;
1564			}
1565		}
1566		++c;
1567
1568		if (!found) {
1569			found = 1;
1570			/* Copy everything up to and including the $. */
1571			buf_append(newbuf, buf_get(bp), kw - buf_get(bp));
1572		}
1573
1574		if (mode & RCS_KWEXP_NAME) {
1575			buf_puts(newbuf, rcs_expkw[i].kw_str);
1576			if (mode & RCS_KWEXP_VAL)
1577				buf_puts(newbuf, ": ");
1578		}
1579
1580		/* Order matters because of RCS_KW_ID and RCS_KW_HEADER. */
1581		if (mode & RCS_KWEXP_VAL) {
1582			if (kwtype & (RCS_KW_RCSFILE|RCS_KW_LOG)) {
1583				if ((kwtype & RCS_KW_FULLPATH) ||
1584				    (tmpf = strrchr(rcsfile, '/')) == NULL)
1585					buf_puts(newbuf, rcsfile);
1586				else
1587					buf_puts(newbuf, tmpf + 1);
1588				buf_putc(newbuf, ' ');
1589			}
1590
1591			if (kwtype & RCS_KW_REVISION) {
1592				rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
1593				buf_puts(newbuf, buf);
1594				buf_putc(newbuf, ' ');
1595			}
1596
1597			if (kwtype & RCS_KW_DATE) {
1598				strftime(buf, sizeof(buf),
1599				    "%Y/%m/%d %H:%M:%S ", &tb);
1600				buf_puts(newbuf, buf);
1601			}
1602
1603			if (kwtype & RCS_KW_AUTHOR) {
1604				buf_puts(newbuf, rdp->rd_author);
1605				buf_putc(newbuf, ' ');
1606			}
1607
1608			if (kwtype & RCS_KW_STATE) {
1609				buf_puts(newbuf, rdp->rd_state);
1610				buf_putc(newbuf, ' ');
1611			}
1612
1613			/* Order does not matter anymore below. */
1614			if (kwtype & RCS_KW_SOURCE) {
1615				buf_puts(newbuf, rcsfile);
1616				buf_putc(newbuf, ' ');
1617			}
1618
1619			if (kwtype & RCS_KW_MDOCDATE) {
1620				strftime(buf, sizeof(buf), "%B", &tb);
1621				buf_puts(newbuf, buf);
1622				/* Only one blank before single-digit day. */
1623				snprintf(buf, sizeof(buf), " %d", tb.tm_mday);
1624				buf_puts(newbuf, buf);
1625				strftime(buf, sizeof(buf), " %Y ", &tb);
1626				buf_puts(newbuf, buf);
1627			}
1628
1629			if (kwtype & RCS_KW_NAME)
1630				buf_putc(newbuf, ' ');
1631
1632			if ((kwtype & RCS_KW_LOCKER)) {
1633				if (rdp->rd_locker) {
1634					buf_puts(newbuf, rdp->rd_locker);
1635					buf_putc(newbuf, ' ');
1636				}
1637			}
1638		}
1639
1640		/* End the expansion. */
1641		if (mode & RCS_KWEXP_NAME)
1642			buf_putc(newbuf, '$');
1643
1644		if (kwtype & RCS_KW_LOG) {
1645			line = memrchr(buf_get(bp), '\n', kw - buf_get(bp) - 1);
1646			if (line == NULL)
1647				line = buf_get(bp);
1648			else
1649				++line;
1650			line2 = kw - 1;
1651			while (line2 > line && line2[-1] == ' ')
1652				--line2;
1653
1654			buf_putc(newbuf, '\n');
1655			buf_append(newbuf, line, kw - 1 - line);
1656			buf_puts(newbuf, "Revision ");
1657			rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
1658			buf_puts(newbuf, buf);
1659			buf_puts(newbuf, "  ");
1660			strftime(buf, sizeof(buf), "%Y/%m/%d %H:%M:%S", &tb);
1661			buf_puts(newbuf, buf);
1662
1663			buf_puts(newbuf, "  ");
1664			buf_puts(newbuf, rdp->rd_author);
1665			buf_putc(newbuf, '\n');
1666
1667			for (i = 0; rdp->rd_log[i]; i += j) {
1668				j = strcspn(rdp->rd_log + i, "\n");
1669				if (j == 0)
1670					buf_append(newbuf, line, line2 - line);
1671				else
1672					buf_append(newbuf, line, kw - 1 - line);
1673				if (rdp->rd_log[i + j])
1674					++j;
1675				buf_append(newbuf, rdp->rd_log + i, j);
1676			}
1677
1678			if (i > 0 && rdp->rd_log[i - 1] != '\n')
1679				buf_putc(newbuf, '\n');
1680
1681			buf_append(newbuf, line, line2 - line);
1682			for (j = 0; c + j < fin; ++j) {
1683				if (c[j] != ' ')
1684					break;
1685			}
1686			if (c + j == fin || c[j] == '\n')
1687				c += j;
1688		}
1689	}
1690
1691	if (found) {
1692		buf_append(newbuf, c, fin - c);
1693		buf_free(bp);
1694		return (newbuf);
1695	} else {
1696		buf_free(newbuf);
1697		return (bp);
1698	}
1699}
1700
1701/*
1702 * rcs_deltatext_set()
1703 *
1704 * Set deltatext for <rev> in RCS file <rfp> to <dtext>
1705 * Returns -1 on error, 0 on success.
1706 */
1707int
1708rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, BUF *bp)
1709{
1710	size_t len;
1711	u_char *dtext;
1712	struct rcs_delta *rdp;
1713
1714	/* Write operations require full parsing */
1715	if (rcsparse_deltatexts(rfp, NULL))
1716		return (-1);
1717
1718	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1719		return (-1);
1720
1721	free(rdp->rd_text);
1722
1723	len = buf_len(bp);
1724	dtext = buf_release(bp);
1725	bp = NULL;
1726
1727	if (len != 0) {
1728		rdp->rd_text = xmalloc(len);
1729		rdp->rd_tlen = len;
1730		(void)memcpy(rdp->rd_text, dtext, len);
1731	} else {
1732		rdp->rd_text = NULL;
1733		rdp->rd_tlen = 0;
1734	}
1735
1736	free(dtext);
1737
1738	return (0);
1739}
1740
1741/*
1742 * rcs_rev_setlog()
1743 *
1744 * Sets the log message of revision <rev> to <logtext>.
1745 */
1746int
1747rcs_rev_setlog(RCSFILE *rfp, RCSNUM *rev, const char *logtext)
1748{
1749	struct rcs_delta *rdp;
1750
1751	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1752		return (-1);
1753
1754	free(rdp->rd_log);
1755
1756	rdp->rd_log = xstrdup(logtext);
1757	rfp->rf_flags &= ~RCS_SYNCED;
1758	return (0);
1759}
1760/*
1761 * rcs_rev_getdate()
1762 *
1763 * Get the date corresponding to a given revision.
1764 * Returns the date on success, -1 on failure.
1765 */
1766time_t
1767rcs_rev_getdate(RCSFILE *rfp, RCSNUM *rev)
1768{
1769	struct rcs_delta *rdp;
1770
1771	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1772		return (-1);
1773
1774	return (mktime(&rdp->rd_date));
1775}
1776
1777/*
1778 * rcs_state_set()
1779 *
1780 * Sets the state of revision <rev> to <state>
1781 * NOTE: default state is 'Exp'. States may not contain spaces.
1782 *
1783 * Returns -1 on failure, 0 on success.
1784 */
1785int
1786rcs_state_set(RCSFILE *rfp, RCSNUM *rev, const char *state)
1787{
1788	struct rcs_delta *rdp;
1789
1790	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1791		return (-1);
1792
1793	free(rdp->rd_state);
1794
1795	rdp->rd_state = xstrdup(state);
1796
1797	rfp->rf_flags &= ~RCS_SYNCED;
1798
1799	return (0);
1800}
1801
1802/*
1803 * rcs_state_check()
1804 *
1805 * Check if string <state> is valid.
1806 *
1807 * Returns 0 if the string is valid, -1 otherwise.
1808 */
1809int
1810rcs_state_check(const char *state)
1811{
1812	int ret;
1813	const unsigned char *cp;
1814
1815	ret = 0;
1816	cp = state;
1817	if (!isalpha(*cp++))
1818		return (-1);
1819
1820	for (; *cp != '\0'; cp++)
1821		if (!isgraph(*cp) || (strchr(rcs_state_invch, *cp) != NULL)) {
1822			ret = -1;
1823			break;
1824		}
1825
1826	return (ret);
1827}
1828
1829/*
1830 * rcs_kwexp_buf()
1831 *
1832 * Do keyword expansion on a buffer if necessary
1833 *
1834 */
1835BUF *
1836rcs_kwexp_buf(BUF *bp, RCSFILE *rf, RCSNUM *rev)
1837{
1838	struct rcs_delta *rdp;
1839	int expmode;
1840
1841	/*
1842	 * Do keyword expansion if required.
1843	 */
1844	expmode = rcs_kwexp_get(rf);
1845
1846	if (!(expmode & RCS_KWEXP_NONE)) {
1847		if ((rdp = rcs_findrev(rf, rev)) == NULL)
1848			errx(1, "could not fetch revision");
1849		return (rcs_expand_keywords(rf->rf_path, rdp, bp, expmode));
1850	}
1851	return (bp);
1852}
1853