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