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