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