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