xref: /openbsd-src/usr.bin/cvs/rcs.c (revision 6540267578fc4f09290439f592ae104eca637e2b)
1*65402675Sjsg /*	$OpenBSD: rcs.c,v 1.322 2024/05/30 10:25:58 jsg Exp $	*/
208f90673Sjfb /*
308f90673Sjfb  * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
408f90673Sjfb  * All rights reserved.
508f90673Sjfb  *
608f90673Sjfb  * Redistribution and use in source and binary forms, with or without
708f90673Sjfb  * modification, are permitted provided that the following conditions
808f90673Sjfb  * are met:
908f90673Sjfb  *
1008f90673Sjfb  * 1. Redistributions of source code must retain the above copyright
1108f90673Sjfb  *    notice, this list of conditions and the following disclaimer.
1208f90673Sjfb  * 2. The name of the author may not be used to endorse or promote products
1308f90673Sjfb  *    derived from this software without specific prior written permission.
1408f90673Sjfb  *
1508f90673Sjfb  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
1608f90673Sjfb  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
1708f90673Sjfb  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
1808f90673Sjfb  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
1908f90673Sjfb  * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
2008f90673Sjfb  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
2108f90673Sjfb  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
2208f90673Sjfb  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
2308f90673Sjfb  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
2408f90673Sjfb  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2508f90673Sjfb  */
2608f90673Sjfb 
271f8531bdSotto #include <sys/stat.h>
2808f90673Sjfb 
291f8531bdSotto #include <ctype.h>
301f8531bdSotto #include <errno.h>
311f8531bdSotto #include <libgen.h>
321f8531bdSotto #include <pwd.h>
331f8531bdSotto #include <stdlib.h>
341f8531bdSotto #include <string.h>
351f8531bdSotto #include <unistd.h>
361f8531bdSotto 
37bb59aeccStobias #include "atomicio.h"
3801af718aSjoris #include "cvs.h"
393ad3fb45Sjoris #include "diff.h"
409225b0caSxsa #include "rcs.h"
41fead43dfStobias #include "rcsparse.h"
4208f90673Sjfb 
43b9fc9a72Sderaadt #define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
44b9fc9a72Sderaadt 
45b71c7dfeSniallo #define RCS_KWEXP_SIZE  1024
4608f90673Sjfb 
475e4c4390Stobias #define ANNOTATE_NEVER	0
485e4c4390Stobias #define ANNOTATE_NOW	1
495e4c4390Stobias #define ANNOTATE_LATER	2
505e4c4390Stobias 
51a8b5986dSjfb /* invalid characters in RCS symbol names */
525be71afaSjfb static const char rcs_sym_invch[] = RCS_SYM_INVALCHAR;
53a8b5986dSjfb 
54ff88b297Sjfb /* comment leaders, depending on the file's suffix */
55ff88b297Sjfb static const struct rcs_comment {
56ff88b297Sjfb 	const char	*rc_suffix;
57ff88b297Sjfb 	const char	*rc_cstr;
58ff88b297Sjfb } rcs_comments[] = {
59ff88b297Sjfb 	{ "1",    ".\\\" " },
60ff88b297Sjfb 	{ "2",    ".\\\" " },
61ff88b297Sjfb 	{ "3",    ".\\\" " },
62ff88b297Sjfb 	{ "4",    ".\\\" " },
63ff88b297Sjfb 	{ "5",    ".\\\" " },
64ff88b297Sjfb 	{ "6",    ".\\\" " },
65ff88b297Sjfb 	{ "7",    ".\\\" " },
66ff88b297Sjfb 	{ "8",    ".\\\" " },
67ff88b297Sjfb 	{ "9",    ".\\\" " },
68ff88b297Sjfb 	{ "a",    "-- "    },	/* Ada		 */
69ff88b297Sjfb 	{ "ada",  "-- "    },
70ff88b297Sjfb 	{ "adb",  "-- "    },
71ff88b297Sjfb 	{ "asm",  ";; "    },	/* assembler (MS-DOS) */
72ff88b297Sjfb 	{ "ads",  "-- "    },	/* Ada */
73ff88b297Sjfb 	{ "bat",  ":: "    },	/* batch (MS-DOS) */
74ff88b297Sjfb 	{ "body", "-- "    },	/* Ada */
75ff88b297Sjfb 	{ "c",    " * "    },	/* C */
76ff88b297Sjfb 	{ "c++",  "// "    },	/* C++ */
77ff88b297Sjfb 	{ "cc",   "// "    },
78ff88b297Sjfb 	{ "cpp",  "// "    },
79ff88b297Sjfb 	{ "cxx",  "// "    },
80ff88b297Sjfb 	{ "m",    "// "    },	/* Objective-C */
81ff88b297Sjfb 	{ "cl",   ";;; "   },	/* Common Lisp	 */
82ff88b297Sjfb 	{ "cmd",  ":: "    },	/* command (OS/2) */
83ff88b297Sjfb 	{ "cmf",  "c "     },	/* CM Fortran	 */
84ff88b297Sjfb 	{ "csh",  "# "     },	/* shell	 */
85ff88b297Sjfb 	{ "e",    "# "     },	/* efl		 */
86ff88b297Sjfb 	{ "epsf", "% "     },	/* encapsulated postscript */
87ff88b297Sjfb 	{ "epsi", "% "     },	/* encapsulated postscript */
88ff88b297Sjfb 	{ "el",   "; "     },	/* Emacs Lisp	 */
89ff88b297Sjfb 	{ "f",    "c "     },	/* Fortran	 */
90ff88b297Sjfb 	{ "for",  "c "     },
91ff88b297Sjfb 	{ "h",    " * "    },	/* C-header	 */
92ff88b297Sjfb 	{ "hh",   "// "    },	/* C++ header	 */
93ff88b297Sjfb 	{ "hpp",  "// "    },
94ff88b297Sjfb 	{ "hxx",  "// "    },
95ff88b297Sjfb 	{ "in",   "# "     },	/* for Makefile.in */
96ff88b297Sjfb 	{ "l",    " * "    },	/* lex */
97ff88b297Sjfb 	{ "mac",  ";; "    },	/* macro (DEC-10, MS-DOS, PDP-11, VMS, etc) */
98ff88b297Sjfb 	{ "mak",  "# "     },	/* makefile, e.g. Visual C++ */
99ff88b297Sjfb 	{ "me",   ".\\\" " },	/* me-macros	t/nroff	 */
100ff88b297Sjfb 	{ "ml",   "; "     },	/* mocklisp	 */
101ff88b297Sjfb 	{ "mm",   ".\\\" " },	/* mm-macros	t/nroff	 */
102ff88b297Sjfb 	{ "ms",   ".\\\" " },	/* ms-macros	t/nroff	 */
103ff88b297Sjfb 	{ "man",  ".\\\" " },	/* man-macros	t/nroff	 */
104ff88b297Sjfb 	{ "p",    " * "    },	/* pascal	 */
105ff88b297Sjfb 	{ "pas",  " * "    },
10656bc38daSjfb 	{ "pl",   "# "     },	/* Perl	(conflict with Prolog) */
10756bc38daSjfb 	{ "pm",   "# "     },	/* Perl	module */
108ff88b297Sjfb 	{ "ps",   "% "     },	/* postscript */
109ff88b297Sjfb 	{ "psw",  "% "     },	/* postscript wrap */
110ff88b297Sjfb 	{ "pswm", "% "     },	/* postscript wrap */
111ff88b297Sjfb 	{ "r",    "# "     },	/* ratfor	 */
112ff88b297Sjfb 	{ "rc",   " * "    },	/* Microsoft Windows resource file */
113ff88b297Sjfb 	{ "red",  "% "     },	/* psl/rlisp	 */
114ff88b297Sjfb 	{ "sh",   "# "     },	/* shell	 */
115ff88b297Sjfb 	{ "sl",   "% "     },	/* psl		 */
116ff88b297Sjfb 	{ "spec", "-- "    },	/* Ada		 */
117ff88b297Sjfb 	{ "tex",  "% "     },	/* tex		 */
118ff88b297Sjfb 	{ "y",    " * "    },	/* yacc		 */
119ff88b297Sjfb 	{ "ye",   " * "    },	/* yacc-efl	 */
120ff88b297Sjfb 	{ "yr",   " * "    },	/* yacc-ratfor	 */
121ff88b297Sjfb };
122ff88b297Sjfb 
123a718728aSniallo struct rcs_kw rcs_expkw[] =  {
124a718728aSniallo 	{ "Author",	RCS_KW_AUTHOR   },
125a718728aSniallo 	{ "Date",	RCS_KW_DATE     },
126a718728aSniallo 	{ "Header",	RCS_KW_HEADER   },
127a718728aSniallo 	{ "Id",		RCS_KW_ID       },
1285c14cef8Stobias 	{ "Locker",	RCS_KW_LOCKER	},
129a718728aSniallo 	{ "Log",	RCS_KW_LOG      },
130a718728aSniallo 	{ "Name",	RCS_KW_NAME     },
131a718728aSniallo 	{ "RCSfile",	RCS_KW_RCSFILE  },
132a718728aSniallo 	{ "Revision",	RCS_KW_REVISION },
133a718728aSniallo 	{ "Source",	RCS_KW_SOURCE   },
134a718728aSniallo 	{ "State",	RCS_KW_STATE    },
135f4827e12Sniallo 	{ "Mdocdate",	RCS_KW_MDOCDATE },
136a718728aSniallo };
137a718728aSniallo 
138ff88b297Sjfb #define NB_COMTYPES	(sizeof(rcs_comments)/sizeof(rcs_comments[0]))
139ff88b297Sjfb 
140113cb29fStobias static RCSNUM	*rcs_get_revision(const char *, RCSFILE *);
1417bb3ddb0Sray int		rcs_patch_lines(struct rcs_lines *, struct rcs_lines *,
1427bb3ddb0Sray 		    struct rcs_line **, struct rcs_delta *);
1431b6534b8Sjfb static void	rcs_freedelta(struct rcs_delta *);
144f240ac99Sray static void	rcs_strprint(const u_char *, size_t, FILE *);
1451b6534b8Sjfb 
1467bb3ddb0Sray static void	rcs_kwexp_line(char *, struct rcs_delta *, struct rcs_lines *,
1477bb3ddb0Sray 		    struct rcs_line *, int mode);
1481b6534b8Sjfb 
149cc89a134Snicm /*
150cc89a134Snicm  * Prepare RCSFILE for parsing. The given file descriptor (if any) must be
151cc89a134Snicm  * read-only and is closed on rcs_close().
152cc89a134Snicm  */
15308f90673Sjfb RCSFILE *
rcs_open(const char * path,int fd,int flags,...)1543ad3fb45Sjoris rcs_open(const char *path, int fd, int flags, ...)
15508f90673Sjfb {
1563ad3fb45Sjoris 	int mode;
15788b7ad1cStobias 	mode_t fmode;
15808f90673Sjfb 	RCSFILE *rfp;
1591b6534b8Sjfb 	va_list vap;
1600d18a67fSjoris 	struct stat st;
161bbfcb536Sjoris 	struct rcs_delta *rdp;
162bbfcb536Sjoris 	struct rcs_lock *lkr;
16308f90673Sjfb 
164c51cb395Sniallo 	fmode = S_IRUSR|S_IRGRP|S_IROTH;
1651b6534b8Sjfb 	flags &= 0xffff;	/* ditch any internal flags */
1661b6534b8Sjfb 
1671b6534b8Sjfb 	if (flags & RCS_CREATE) {
1681b6534b8Sjfb 		va_start(vap, flags);
169ee96bdbeSjoris 		mode = va_arg(vap, int);
1701b6534b8Sjfb 		va_end(vap);
171ee96bdbeSjoris 		fmode = (mode_t)mode;
1720d18a67fSjoris 	} else {
1730d18a67fSjoris 		if (fstat(fd, &st) == -1)
1740d18a67fSjoris 			fatal("rcs_open: %s: fstat: %s", path, strerror(errno));
1750d18a67fSjoris 		fmode = st.st_mode;
17608f90673Sjfb 	}
17708f90673Sjfb 
17888b7ad1cStobias 	fmode &= ~cvs_umask;
17922ab9685Stobias 
1803b4c5c25Sray 	rfp = xcalloc(1, sizeof(*rfp));
18108f90673Sjfb 
1820450b43bSjoris 	rfp->rf_path = xstrdup(path);
1833c066d8cSniallo 	rfp->rf_flags = flags | RCS_SLOCK | RCS_SYNCED;
1841b6534b8Sjfb 	rfp->rf_mode = fmode;
185394437e6Stobias 	if (fd == -1)
186394437e6Stobias 		rfp->rf_file = NULL;
187394437e6Stobias 	else if ((rfp->rf_file = fdopen(fd, "r")) == NULL)
188394437e6Stobias 		fatal("rcs_open: %s: fdopen: %s", path, strerror(errno));
1893258e4a0Sjoris 	rfp->rf_dead = 0;
19008f90673Sjfb 
19108f90673Sjfb 	TAILQ_INIT(&(rfp->rf_delta));
192d510a4e8Sjfb 	TAILQ_INIT(&(rfp->rf_access));
19308f90673Sjfb 	TAILQ_INIT(&(rfp->rf_symbols));
19408f90673Sjfb 	TAILQ_INIT(&(rfp->rf_locks));
19508f90673Sjfb 
196fead43dfStobias 	if (!(rfp->rf_flags & RCS_CREATE)) {
197fead43dfStobias 		if (rcsparse_init(rfp))
198fead43dfStobias 			fatal("could not parse admin data");
199fead43dfStobias 	}
20008f90673Sjfb 
201bbfcb536Sjoris 	/* fill in rd_locker */
202bbfcb536Sjoris 	TAILQ_FOREACH(lkr, &(rfp->rf_locks), rl_list) {
203bbfcb536Sjoris 		if ((rdp = rcs_findrev(rfp, lkr->rl_num)) == NULL) {
204bbfcb536Sjoris 			rcs_close(rfp);
205bbfcb536Sjoris 			return (NULL);
206bbfcb536Sjoris 		}
207bbfcb536Sjoris 
2080450b43bSjoris 		rdp->rd_locker = xstrdup(lkr->rl_name);
209bbfcb536Sjoris 	}
210bbfcb536Sjoris 
21108f90673Sjfb 	return (rfp);
21208f90673Sjfb }
21308f90673Sjfb 
21408f90673Sjfb /*
21508f90673Sjfb  * rcs_close()
21608f90673Sjfb  *
21708f90673Sjfb  * Close an RCS file handle.
21808f90673Sjfb  */
21908f90673Sjfb void
rcs_close(RCSFILE * rfp)22008f90673Sjfb rcs_close(RCSFILE *rfp)
22108f90673Sjfb {
22208f90673Sjfb 	struct rcs_delta *rdp;
22313e4ff88Smoritz 	struct rcs_access *rap;
2244232e9ddSjfb 	struct rcs_lock *rlp;
2254232e9ddSjfb 	struct rcs_sym *rsp;
22608f90673Sjfb 
2271b6534b8Sjfb 	if ((rfp->rf_flags & RCS_WRITE) && !(rfp->rf_flags & RCS_SYNCED))
2281b6534b8Sjfb 		rcs_write(rfp);
2291b6534b8Sjfb 
23008f90673Sjfb 	while (!TAILQ_EMPTY(&(rfp->rf_delta))) {
23108f90673Sjfb 		rdp = TAILQ_FIRST(&(rfp->rf_delta));
23208f90673Sjfb 		TAILQ_REMOVE(&(rfp->rf_delta), rdp, rd_list);
23308f90673Sjfb 		rcs_freedelta(rdp);
23408f90673Sjfb 	}
23508f90673Sjfb 
23613e4ff88Smoritz 	while (!TAILQ_EMPTY(&(rfp->rf_access))) {
23713e4ff88Smoritz 		rap = TAILQ_FIRST(&(rfp->rf_access));
23813e4ff88Smoritz 		TAILQ_REMOVE(&(rfp->rf_access), rap, ra_list);
239397ddb8aSnicm 		free(rap->ra_name);
240397ddb8aSnicm 		free(rap);
24113e4ff88Smoritz 	}
24213e4ff88Smoritz 
2434232e9ddSjfb 	while (!TAILQ_EMPTY(&(rfp->rf_symbols))) {
2444232e9ddSjfb 		rsp = TAILQ_FIRST(&(rfp->rf_symbols));
2454232e9ddSjfb 		TAILQ_REMOVE(&(rfp->rf_symbols), rsp, rs_list);
24653ce2177Sfcambus 		free(rsp->rs_num);
247397ddb8aSnicm 		free(rsp->rs_name);
248397ddb8aSnicm 		free(rsp);
2494232e9ddSjfb 	}
2504232e9ddSjfb 
2514232e9ddSjfb 	while (!TAILQ_EMPTY(&(rfp->rf_locks))) {
2524232e9ddSjfb 		rlp = TAILQ_FIRST(&(rfp->rf_locks));
2534232e9ddSjfb 		TAILQ_REMOVE(&(rfp->rf_locks), rlp, rl_list);
25453ce2177Sfcambus 		free(rlp->rl_num);
255397ddb8aSnicm 		free(rlp->rl_name);
256397ddb8aSnicm 		free(rlp);
2574232e9ddSjfb 	}
2584232e9ddSjfb 
25953ce2177Sfcambus 	free(rfp->rf_head);
26053ce2177Sfcambus 	free(rfp->rf_branch);
26108f90673Sjfb 
262394437e6Stobias 	if (rfp->rf_file != NULL)
263394437e6Stobias 		fclose(rfp->rf_file);
264397ddb8aSnicm 	free(rfp->rf_path);
265397ddb8aSnicm 	free(rfp->rf_comment);
266397ddb8aSnicm 	free(rfp->rf_expand);
267397ddb8aSnicm 	free(rfp->rf_desc);
26824afc1baSjoris 	if (rfp->rf_pdata != NULL)
269fead43dfStobias 		rcsparse_free(rfp);
270397ddb8aSnicm 	free(rfp);
27108f90673Sjfb }
27208f90673Sjfb 
27308f90673Sjfb /*
27408f90673Sjfb  * rcs_write()
27508f90673Sjfb  *
27608f90673Sjfb  * Write the contents of the RCS file handle <rfp> to disk in the file whose
27708f90673Sjfb  * path is in <rf_path>.
27808f90673Sjfb  */
2793ad3fb45Sjoris void
rcs_write(RCSFILE * rfp)28008f90673Sjfb rcs_write(RCSFILE *rfp)
28108f90673Sjfb {
28208f90673Sjfb 	FILE *fp;
283b9fc9a72Sderaadt 	char   numbuf[CVS_REV_BUFSZ], *fn, tmpdir[PATH_MAX];
284d510a4e8Sjfb 	struct rcs_access *ap;
28508f90673Sjfb 	struct rcs_sym *symp;
28652fd60b9Sjfb 	struct rcs_branch *brp;
28708f90673Sjfb 	struct rcs_delta *rdp;
2886bf66b8aSjoris 	struct rcs_lock *lkp;
28963b70358Sxsa 	size_t len;
2903e028447Sotto 	int fd, saved_errno;
2916bf66b8aSjoris 
2923e028447Sotto 	fd = -1;
29308f90673Sjfb 
294343bce0eSjfb 	if (rfp->rf_flags & RCS_SYNCED)
2953ad3fb45Sjoris 		return;
29608f90673Sjfb 
2977938e528Sjoris 	if (cvs_noexec == 1)
2987938e528Sjoris 		return;
2997938e528Sjoris 
30012b92713Sjoris 	/* Write operations need the whole file parsed */
301fead43dfStobias 	if (rcsparse_deltatexts(rfp, NULL))
302fead43dfStobias 		fatal("rcs_write: rcsparse_deltatexts");
30312b92713Sjoris 
3043e028447Sotto 	if (strlcpy(tmpdir, rfp->rf_path, sizeof(tmpdir)) >= sizeof(tmpdir))
3053e028447Sotto 		fatal("rcs_write: truncation");
3063e028447Sotto 	(void)xasprintf(&fn, "%s/rcs.XXXXXXXXXX", dirname(tmpdir));
3073ad3fb45Sjoris 
308296ae42bSxsa 	if ((fd = mkstemp(fn)) == -1)
3093ad3fb45Sjoris 		fatal("%s", fn);
310296ae42bSxsa 
3113e028447Sotto 	if ((fp = fdopen(fd, "w")) == NULL) {
3123ad3fb45Sjoris 		saved_errno = errno;
3133ad3fb45Sjoris 		(void)unlink(fn);
3143e028447Sotto 		fatal("fdopen %s: %s", fn, strerror(saved_errno));
31508f90673Sjfb 	}
31608f90673Sjfb 
3177a9e6d11Sray 	worklist_add(fn, &temp_files);
3183627c1f6Sjoris 
319343bce0eSjfb 	if (rfp->rf_head != NULL)
32008f90673Sjfb 		rcsnum_tostr(rfp->rf_head, numbuf, sizeof(numbuf));
321343bce0eSjfb 	else
322343bce0eSjfb 		numbuf[0] = '\0';
323343bce0eSjfb 
32408f90673Sjfb 	fprintf(fp, "head\t%s;\n", numbuf);
32595553451Sjfb 
32695553451Sjfb 	if (rfp->rf_branch != NULL) {
32795553451Sjfb 		rcsnum_tostr(rfp->rf_branch, numbuf, sizeof(numbuf));
32895553451Sjfb 		fprintf(fp, "branch\t%s;\n", numbuf);
32995553451Sjfb 	}
33095553451Sjfb 
331d510a4e8Sjfb 	fputs("access", fp);
332d510a4e8Sjfb 	TAILQ_FOREACH(ap, &(rfp->rf_access), ra_list) {
333d510a4e8Sjfb 		fprintf(fp, "\n\t%s", ap->ra_name);
334d510a4e8Sjfb 	}
335d510a4e8Sjfb 	fputs(";\n", fp);
33608f90673Sjfb 
337ca193349Sjoris 	fprintf(fp, "symbols");
33808f90673Sjfb 	TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
3390f301d36Stobias 		if (RCSNUM_ISBRANCH(symp->rs_num))
3400f301d36Stobias 			rcsnum_addmagic(symp->rs_num);
34108f90673Sjfb 		rcsnum_tostr(symp->rs_num, numbuf, sizeof(numbuf));
342ad782d8bSzinovik 		fprintf(fp, "\n\t%s:%s", symp->rs_name, numbuf);
34308f90673Sjfb 	}
34408f90673Sjfb 	fprintf(fp, ";\n");
34508f90673Sjfb 
3466bf66b8aSjoris 	fprintf(fp, "locks");
3476bf66b8aSjoris 	TAILQ_FOREACH(lkp, &(rfp->rf_locks), rl_list) {
3486bf66b8aSjoris 		rcsnum_tostr(lkp->rl_num, numbuf, sizeof(numbuf));
3496bf66b8aSjoris 		fprintf(fp, "\n\t%s:%s", lkp->rl_name, numbuf);
3506bf66b8aSjoris 	}
3516bf66b8aSjoris 
3526bf66b8aSjoris 	fprintf(fp, ";");
35308f90673Sjfb 
3541b6534b8Sjfb 	if (rfp->rf_flags & RCS_SLOCK)
35508f90673Sjfb 		fprintf(fp, " strict;");
35608f90673Sjfb 	fputc('\n', fp);
35708f90673Sjfb 
358163f330fSjfb 	fputs("comment\t@", fp);
359dedf3eaaSxsa 	if (rfp->rf_comment != NULL) {
360bc37d6f8Sxsa 		rcs_strprint((const u_char *)rfp->rf_comment,
361bc37d6f8Sxsa 		    strlen(rfp->rf_comment), fp);
362163f330fSjfb 		fputs("@;\n", fp);
363dedf3eaaSxsa 	} else
364dedf3eaaSxsa 		fputs("# @;\n", fp);
36508f90673Sjfb 
366163f330fSjfb 	if (rfp->rf_expand != NULL) {
367163f330fSjfb 		fputs("expand @", fp);
368bc37d6f8Sxsa 		rcs_strprint((const u_char *)rfp->rf_expand,
369bc37d6f8Sxsa 		    strlen(rfp->rf_expand), fp);
370163f330fSjfb 		fputs("@;\n", fp);
371163f330fSjfb 	}
37208f90673Sjfb 
37352fd60b9Sjfb 	fputs("\n\n", fp);
37408f90673Sjfb 
37508f90673Sjfb 	TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
37608f90673Sjfb 		fprintf(fp, "%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
37708f90673Sjfb 		    sizeof(numbuf)));
37808f90673Sjfb 		fprintf(fp, "date\t%d.%02d.%02d.%02d.%02d.%02d;",
379b100d76eSjfb 		    rdp->rd_date.tm_year + 1900, rdp->rd_date.tm_mon + 1,
38008f90673Sjfb 		    rdp->rd_date.tm_mday, rdp->rd_date.tm_hour,
38108f90673Sjfb 		    rdp->rd_date.tm_min, rdp->rd_date.tm_sec);
38208f90673Sjfb 		fprintf(fp, "\tauthor %s;\tstate %s;\n",
38308f90673Sjfb 		    rdp->rd_author, rdp->rd_state);
38452fd60b9Sjfb 		fputs("branches", fp);
38552fd60b9Sjfb 		TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
3863081741dStobias 			fprintf(fp, "\n\t%s", rcsnum_tostr(brp->rb_num, numbuf,
38752fd60b9Sjfb 			    sizeof(numbuf)));
38852fd60b9Sjfb 		}
38952fd60b9Sjfb 		fputs(";\n", fp);
39008f90673Sjfb 		fprintf(fp, "next\t%s;\n\n", rcsnum_tostr(rdp->rd_next,
39108f90673Sjfb 		    numbuf, sizeof(numbuf)));
39208f90673Sjfb 	}
39308f90673Sjfb 
394163f330fSjfb 	fputs("\ndesc\n@", fp);
3957b7572f3Sray 	if (rfp->rf_desc != NULL && (len = strlen(rfp->rf_desc)) > 0) {
39663b70358Sxsa 		rcs_strprint((const u_char *)rfp->rf_desc, len, fp);
39763b70358Sxsa 		if (rfp->rf_desc[len-1] != '\n')
39863b70358Sxsa 			fputc('\n', fp);
39963b70358Sxsa 	}
40063b70358Sxsa 	fputs("@\n", fp);
40108f90673Sjfb 
40208f90673Sjfb 	/* deltatexts */
40308f90673Sjfb 	TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
40463b70358Sxsa 		fprintf(fp, "\n\n%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
40508f90673Sjfb 		    sizeof(numbuf)));
406163f330fSjfb 		fputs("log\n@", fp);
40763b70358Sxsa 		if (rdp->rd_log != NULL) {
40863b70358Sxsa 			len = strlen(rdp->rd_log);
40963b70358Sxsa 			rcs_strprint((const u_char *)rdp->rd_log, len, fp);
4101d911502Snicm 			if (len == 0 || rdp->rd_log[len-1] != '\n')
41163b70358Sxsa 				fputc('\n', fp);
41263b70358Sxsa 		}
413163f330fSjfb 		fputs("@\ntext\n@", fp);
4147a9e6d11Sray 		if (rdp->rd_text != NULL)
415163f330fSjfb 			rcs_strprint(rdp->rd_text, rdp->rd_tlen, fp);
41663b70358Sxsa 		fputs("@\n", fp);
41708f90673Sjfb 	}
4180d18a67fSjoris 
4193e028447Sotto 	if (fchmod(fd, rfp->rf_mode) == -1) {
4203e028447Sotto 		saved_errno = errno;
4213e028447Sotto 		(void)unlink(fn);
4223e028447Sotto 		fatal("fchmod %s: %s", fn, strerror(saved_errno));
4233e028447Sotto 	}
4243e028447Sotto 
4253ad3fb45Sjoris 	(void)fclose(fp);
4267bc8b3c9Sjoris 
4273e028447Sotto 	if (rename(fn, rfp->rf_path) == -1) {
4283e028447Sotto 		saved_errno = errno;
4293ad3fb45Sjoris 		(void)unlink(fn);
4303e028447Sotto 		fatal("rename(%s, %s): %s", fn, rfp->rf_path,
4313e028447Sotto 		    strerror(saved_errno));
432e8d7114cSniallo 	}
4337bc8b3c9Sjoris 
4341b6534b8Sjfb 	rfp->rf_flags |= RCS_SYNCED;
435397ddb8aSnicm 	free(fn);
4363ad3fb45Sjoris }
4373ad3fb45Sjoris 
4383ad3fb45Sjoris /*
43900f66f0dSjfb  * rcs_head_get()
44000f66f0dSjfb  *
44100f66f0dSjfb  * Retrieve the revision number of the head revision for the RCS file <file>.
44200f66f0dSjfb  */
44308458e59Sjoris RCSNUM *
rcs_head_get(RCSFILE * file)44400f66f0dSjfb rcs_head_get(RCSFILE *file)
44500f66f0dSjfb {
446ca2dc546Sniallo 	struct rcs_branch *brp;
447ca2dc546Sniallo 	struct rcs_delta *rdp;
448ca2dc546Sniallo 	RCSNUM *rev, *rootrev;
44908458e59Sjoris 
4509af0ab72Stobias 	if (file->rf_head == NULL)
4519af0ab72Stobias 		return NULL;
4529af0ab72Stobias 
453570941ffSjoris 	rev = rcsnum_alloc();
454ca2dc546Sniallo 	if (file->rf_branch != NULL) {
455ca2dc546Sniallo 		/* we have a default branch, use that to calculate the
456ca2dc546Sniallo 		 * real HEAD*/
457ca2dc546Sniallo 		rootrev = rcsnum_alloc();
45818258a85Stobias 		rcsnum_cpy(file->rf_branch, rootrev,
45918258a85Stobias 		    file->rf_branch->rn_len - 1);
460ca2dc546Sniallo 		if ((rdp = rcs_findrev(file, rootrev)) == NULL)
461ca2dc546Sniallo 			fatal("rcs_head_get: could not find root revision");
462ca2dc546Sniallo 
463ca2dc546Sniallo 		/* HEAD should be the last revision on the default branch */
464ca2dc546Sniallo 		TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
46518258a85Stobias 			if (rcsnum_cmp(brp->rb_num, file->rf_branch,
466e60fc6c5Schl 			    file->rf_branch->rn_len) == 0)
467ca2dc546Sniallo 				break;
468ca2dc546Sniallo 		}
46953ce2177Sfcambus 		free(rootrev);
4706daf1a4bStobias 
47118258a85Stobias 		if (brp == NULL)
47218258a85Stobias 			fatal("rcs_head_get: could not find first default "
47318258a85Stobias 			    "branch revision");
47418258a85Stobias 
4756daf1a4bStobias 		if ((rdp = rcs_findrev(file, brp->rb_num)) == NULL)
4766daf1a4bStobias 			fatal("rcs_head_get: could not find branch revision");
4776daf1a4bStobias 		while (rdp->rd_next->rn_len != 0)
4786daf1a4bStobias 			if ((rdp = rcs_findrev(file, rdp->rd_next)) == NULL)
4796daf1a4bStobias 				fatal("rcs_head_get: could not find "
4806daf1a4bStobias 				    "next branch revision");
4816daf1a4bStobias 
4826daf1a4bStobias 		rcsnum_cpy(rdp->rd_num, rev, 0);
483ca2dc546Sniallo 	} else {
484570941ffSjoris 		rcsnum_cpy(file->rf_head, rev, 0);
48508458e59Sjoris 	}
48608458e59Sjoris 
487570941ffSjoris 	return (rev);
48800f66f0dSjfb }
48900f66f0dSjfb 
49000f66f0dSjfb /*
49100f66f0dSjfb  * rcs_head_set()
49200f66f0dSjfb  *
49300f66f0dSjfb  * Set the revision number of the head revision for the RCS file <file> to
49400f66f0dSjfb  * <rev>, which must reference a valid revision within the file.
49500f66f0dSjfb  */
49600f66f0dSjfb int
rcs_head_set(RCSFILE * file,RCSNUM * rev)497b71c7dfeSniallo rcs_head_set(RCSFILE *file, RCSNUM *rev)
49800f66f0dSjfb {
499eb421b0cSray 	if (rcs_findrev(file, rev) == NULL)
50000f66f0dSjfb 		return (-1);
50100f66f0dSjfb 
5023422a189Sjoris 	if (file->rf_head == NULL)
5033422a189Sjoris 		file->rf_head = rcsnum_alloc();
50456bc38daSjfb 
5053422a189Sjoris 	rcsnum_cpy(rev, file->rf_head, 0);
50608946b33Smoritz 	file->rf_flags &= ~RCS_SYNCED;
50700f66f0dSjfb 	return (0);
50800f66f0dSjfb }
50900f66f0dSjfb 
51045b98c07Stobias /*
51145b98c07Stobias  * rcs_branch_new()
51245b98c07Stobias  *
51345b98c07Stobias  * Create a new branch out of supplied revision for the RCS file <file>.
51445b98c07Stobias  */
51545b98c07Stobias RCSNUM *
rcs_branch_new(RCSFILE * file,RCSNUM * rev)51645b98c07Stobias rcs_branch_new(RCSFILE *file, RCSNUM *rev)
51745b98c07Stobias {
51845b98c07Stobias 	RCSNUM *brev;
51945b98c07Stobias 	struct rcs_sym *sym;
52045b98c07Stobias 
52145b98c07Stobias 	if ((brev = rcsnum_new_branch(rev)) == NULL)
52245b98c07Stobias 		return (NULL);
52345b98c07Stobias 
52445b98c07Stobias 	for (;;) {
52545b98c07Stobias 		TAILQ_FOREACH(sym, &(file->rf_symbols), rs_list)
52645b98c07Stobias 			if (!rcsnum_cmp(sym->rs_num, brev, 0))
52745b98c07Stobias 				break;
52845b98c07Stobias 
5290e6e2e1bStobias 		if (sym == NULL)
53045b98c07Stobias 			break;
5310e6e2e1bStobias 
5320e6e2e1bStobias 		if (rcsnum_inc(brev) == NULL ||
5330e6e2e1bStobias 		    rcsnum_inc(brev) == NULL) {
53453ce2177Sfcambus 			free(brev);
5350e6e2e1bStobias 			return (NULL);
5360e6e2e1bStobias 		}
53745b98c07Stobias 	}
53845b98c07Stobias 
53945b98c07Stobias 	return (brev);
54045b98c07Stobias }
54100f66f0dSjfb 
54200f66f0dSjfb /*
54395553451Sjfb  * rcs_branch_get()
54495553451Sjfb  *
54595553451Sjfb  * Retrieve the default branch number for the RCS file <file>.
54695553451Sjfb  * Returns the number on success.  If NULL is returned, then there is no
54795553451Sjfb  * default branch for this file.
54895553451Sjfb  */
54995553451Sjfb const RCSNUM *
rcs_branch_get(RCSFILE * file)55095553451Sjfb rcs_branch_get(RCSFILE *file)
55195553451Sjfb {
55295553451Sjfb 	return (file->rf_branch);
55395553451Sjfb }
55495553451Sjfb 
55595553451Sjfb /*
55695553451Sjfb  * rcs_branch_set()
55795553451Sjfb  *
55895553451Sjfb  * Set the default branch for the RCS file <file> to <bnum>.
55995553451Sjfb  * Returns 0 on success, -1 on failure.
56095553451Sjfb  */
56195553451Sjfb int
rcs_branch_set(RCSFILE * file,const RCSNUM * bnum)56295553451Sjfb rcs_branch_set(RCSFILE *file, const RCSNUM *bnum)
56395553451Sjfb {
5643422a189Sjoris 	if (file->rf_branch == NULL)
5653422a189Sjoris 		file->rf_branch = rcsnum_alloc();
56695553451Sjfb 
5673422a189Sjoris 	rcsnum_cpy(bnum, file->rf_branch, 0);
56808946b33Smoritz 	file->rf_flags &= ~RCS_SYNCED;
56995553451Sjfb 	return (0);
57095553451Sjfb }
57195553451Sjfb 
57295553451Sjfb /*
573d510a4e8Sjfb  * rcs_access_add()
574d510a4e8Sjfb  *
575d510a4e8Sjfb  * Add the login name <login> to the access list for the RCS file <file>.
576d510a4e8Sjfb  * Returns 0 on success, or -1 on failure.
577d510a4e8Sjfb  */
578d510a4e8Sjfb int
rcs_access_add(RCSFILE * file,const char * login)579d510a4e8Sjfb rcs_access_add(RCSFILE *file, const char *login)
580d510a4e8Sjfb {
581d510a4e8Sjfb 	struct rcs_access *ap;
582d510a4e8Sjfb 
583d510a4e8Sjfb 	/* first look for duplication */
584d510a4e8Sjfb 	TAILQ_FOREACH(ap, &(file->rf_access), ra_list) {
5855b95d21fSjoris 		if (strcmp(ap->ra_name, login) == 0)
586d510a4e8Sjfb 			return (-1);
587d510a4e8Sjfb 	}
588d510a4e8Sjfb 
5893b4c5c25Sray 	ap = xmalloc(sizeof(*ap));
5900450b43bSjoris 	ap->ra_name = xstrdup(login);
591d510a4e8Sjfb 	TAILQ_INSERT_TAIL(&(file->rf_access), ap, ra_list);
592d510a4e8Sjfb 
593d510a4e8Sjfb 	/* not synced anymore */
594d510a4e8Sjfb 	file->rf_flags &= ~RCS_SYNCED;
595d510a4e8Sjfb 	return (0);
596d510a4e8Sjfb }
597d510a4e8Sjfb 
598d510a4e8Sjfb /*
599d510a4e8Sjfb  * rcs_access_remove()
600d510a4e8Sjfb  *
601d510a4e8Sjfb  * Remove an entry with login name <login> from the access list of the RCS
602d510a4e8Sjfb  * file <file>.
603d510a4e8Sjfb  * Returns 0 on success, or -1 on failure.
604d510a4e8Sjfb  */
605d510a4e8Sjfb int
rcs_access_remove(RCSFILE * file,const char * login)606d510a4e8Sjfb rcs_access_remove(RCSFILE *file, const char *login)
607d510a4e8Sjfb {
608d510a4e8Sjfb 	struct rcs_access *ap;
609d510a4e8Sjfb 
610d510a4e8Sjfb 	TAILQ_FOREACH(ap, &(file->rf_access), ra_list)
611d510a4e8Sjfb 		if (strcmp(ap->ra_name, login) == 0)
612d510a4e8Sjfb 			break;
613d510a4e8Sjfb 
6145b95d21fSjoris 	if (ap == NULL)
615d510a4e8Sjfb 		return (-1);
616d510a4e8Sjfb 
617d510a4e8Sjfb 	TAILQ_REMOVE(&(file->rf_access), ap, ra_list);
618397ddb8aSnicm 	free(ap->ra_name);
619397ddb8aSnicm 	free(ap);
620d510a4e8Sjfb 
621d510a4e8Sjfb 	/* not synced anymore */
622d510a4e8Sjfb 	file->rf_flags &= ~RCS_SYNCED;
623d510a4e8Sjfb 	return (0);
624d510a4e8Sjfb }
625d510a4e8Sjfb 
626d510a4e8Sjfb /*
6271b6534b8Sjfb  * rcs_sym_add()
62808f90673Sjfb  *
62908f90673Sjfb  * Add a symbol to the list of symbols for the RCS file <rfp>.  The new symbol
63008f90673Sjfb  * is named <sym> and is bound to the RCS revision <snum>.
63108f90673Sjfb  */
63208f90673Sjfb int
rcs_sym_add(RCSFILE * rfp,const char * sym,RCSNUM * snum)6331b6534b8Sjfb rcs_sym_add(RCSFILE *rfp, const char *sym, RCSNUM *snum)
63408f90673Sjfb {
63508f90673Sjfb 	struct rcs_sym *symp;
63608f90673Sjfb 
6375b95d21fSjoris 	if (!rcs_sym_check(sym))
63871ca9d3cSmoritz 		return (-1);
639a8b5986dSjfb 
64008f90673Sjfb 	/* first look for duplication */
64108f90673Sjfb 	TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
6425b95d21fSjoris 		if (strcmp(symp->rs_name, sym) == 0)
6435b95d21fSjoris 			return (1);
64408f90673Sjfb 	}
64508f90673Sjfb 
6463b4c5c25Sray 	symp = xmalloc(sizeof(*symp));
6470450b43bSjoris 	symp->rs_name = xstrdup(sym);
6483422a189Sjoris 	symp->rs_num = rcsnum_alloc();
64908f90673Sjfb 	rcsnum_cpy(snum, symp->rs_num, 0);
65008f90673Sjfb 
65108f90673Sjfb 	TAILQ_INSERT_HEAD(&(rfp->rf_symbols), symp, rs_list);
65208f90673Sjfb 
65308f90673Sjfb 	/* not synced anymore */
6541b6534b8Sjfb 	rfp->rf_flags &= ~RCS_SYNCED;
65508f90673Sjfb 	return (0);
65608f90673Sjfb }
65708f90673Sjfb 
65808f90673Sjfb /*
659101fc366Sjfb  * rcs_sym_remove()
660101fc366Sjfb  *
661101fc366Sjfb  * Remove the symbol with name <sym> from the symbol list for the RCS file
662101fc366Sjfb  * <file>.  If no such symbol is found, the call fails and returns with an
663101fc366Sjfb  * error.
664101fc366Sjfb  * Returns 0 on success, or -1 on failure.
665101fc366Sjfb  */
666101fc366Sjfb int
rcs_sym_remove(RCSFILE * file,const char * sym)667101fc366Sjfb rcs_sym_remove(RCSFILE *file, const char *sym)
668101fc366Sjfb {
669101fc366Sjfb 	struct rcs_sym *symp;
670101fc366Sjfb 
6715b95d21fSjoris 	if (!rcs_sym_check(sym))
67271ca9d3cSmoritz 		return (-1);
673a8b5986dSjfb 
674101fc366Sjfb 	TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
675101fc366Sjfb 		if (strcmp(symp->rs_name, sym) == 0)
676101fc366Sjfb 			break;
677101fc366Sjfb 
6785b95d21fSjoris 	if (symp == NULL)
679101fc366Sjfb 		return (-1);
680101fc366Sjfb 
681101fc366Sjfb 	TAILQ_REMOVE(&(file->rf_symbols), symp, rs_list);
682397ddb8aSnicm 	free(symp->rs_name);
68353ce2177Sfcambus 	free(symp->rs_num);
684397ddb8aSnicm 	free(symp);
685101fc366Sjfb 
686101fc366Sjfb 	/* not synced anymore */
687101fc366Sjfb 	file->rf_flags &= ~RCS_SYNCED;
688101fc366Sjfb 	return (0);
689101fc366Sjfb }
690101fc366Sjfb 
691101fc366Sjfb /*
692a0d919f1Sxsa  * rcs_sym_get()
693a0d919f1Sxsa  *
694a0d919f1Sxsa  * Find a specific symbol <sym> entry in the tree of the RCS file <file>.
695a0d919f1Sxsa  *
696a0d919f1Sxsa  * Returns a pointer to the symbol on success, or NULL on failure.
697a0d919f1Sxsa  */
698a0d919f1Sxsa struct rcs_sym *
rcs_sym_get(RCSFILE * file,const char * sym)699a0d919f1Sxsa rcs_sym_get(RCSFILE *file, const char *sym)
700a0d919f1Sxsa {
701a0d919f1Sxsa 	struct rcs_sym *symp;
702a0d919f1Sxsa 
703a0d919f1Sxsa 	TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
704a0d919f1Sxsa 		if (strcmp(symp->rs_name, sym) == 0)
705a0d919f1Sxsa 			return (symp);
706a0d919f1Sxsa 
707a0d919f1Sxsa 	return (NULL);
708a0d919f1Sxsa }
709a0d919f1Sxsa 
710a0d919f1Sxsa /*
711101fc366Sjfb  * rcs_sym_getrev()
712101fc366Sjfb  *
713101fc366Sjfb  * Retrieve the RCS revision number associated with the symbol <sym> for the
714101fc366Sjfb  * RCS file <file>.  The returned value is a dynamically-allocated copy and
715101fc366Sjfb  * should be freed by the caller once they are done with it.
716101fc366Sjfb  * Returns the RCSNUM on success, or NULL on failure.
717101fc366Sjfb  */
718101fc366Sjfb RCSNUM *
rcs_sym_getrev(RCSFILE * file,const char * sym)719101fc366Sjfb rcs_sym_getrev(RCSFILE *file, const char *sym)
720101fc366Sjfb {
721101fc366Sjfb 	RCSNUM *num;
722101fc366Sjfb 	struct rcs_sym *symp;
723101fc366Sjfb 
724e28eda4eStobias 	if (!rcs_sym_check(sym) || file->rf_head == NULL)
725a8b5986dSjfb 		return (NULL);
726a8b5986dSjfb 
72745d60d54Sjoris 	if (!strcmp(sym, RCS_HEAD_BRANCH)) {
72845d60d54Sjoris 		num = rcsnum_alloc();
72945d60d54Sjoris 		rcsnum_cpy(file->rf_head, num, 0);
73045d60d54Sjoris 		return (num);
73145d60d54Sjoris 	}
73245d60d54Sjoris 
733101fc366Sjfb 	num = NULL;
734101fc366Sjfb 	TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
735101fc366Sjfb 		if (strcmp(symp->rs_name, sym) == 0)
736101fc366Sjfb 			break;
737101fc366Sjfb 
7385b95d21fSjoris 	if (symp != NULL) {
7393422a189Sjoris 		num = rcsnum_alloc();
7403422a189Sjoris 		rcsnum_cpy(symp->rs_num, num, 0);
741101fc366Sjfb 	}
742101fc366Sjfb 
743101fc366Sjfb 	return (num);
744101fc366Sjfb }
745101fc366Sjfb 
746101fc366Sjfb /*
747a8b5986dSjfb  * rcs_sym_check()
748a8b5986dSjfb  *
749a8b5986dSjfb  * Check the RCS symbol name <sym> for any unsupported characters.
750a8b5986dSjfb  * Returns 1 if the tag is correct, 0 if it isn't valid.
751a8b5986dSjfb  */
752a8b5986dSjfb int
rcs_sym_check(const char * sym)753a8b5986dSjfb rcs_sym_check(const char *sym)
754a8b5986dSjfb {
755a8b5986dSjfb 	int ret;
756f6ac027fSokan 	const unsigned char *cp;
757a8b5986dSjfb 
758a8b5986dSjfb 	ret = 1;
759a8b5986dSjfb 	cp = sym;
760a8b5986dSjfb 	if (!isalpha(*cp++))
761a8b5986dSjfb 		return (0);
762a8b5986dSjfb 
763a8b5986dSjfb 	for (; *cp != '\0'; cp++)
764a8b5986dSjfb 		if (!isgraph(*cp) || (strchr(rcs_sym_invch, *cp) != NULL)) {
765a8b5986dSjfb 			ret = 0;
766a8b5986dSjfb 			break;
767a8b5986dSjfb 		}
768a8b5986dSjfb 
769a8b5986dSjfb 	return (ret);
770a8b5986dSjfb }
771a8b5986dSjfb 
772a8b5986dSjfb /*
773a021dec5Sjfb  * rcs_lock_getmode()
774a021dec5Sjfb  *
775a021dec5Sjfb  * Retrieve the locking mode of the RCS file <file>.
776a021dec5Sjfb  */
777a021dec5Sjfb int
rcs_lock_getmode(RCSFILE * file)778a021dec5Sjfb rcs_lock_getmode(RCSFILE *file)
779a021dec5Sjfb {
780a021dec5Sjfb 	return (file->rf_flags & RCS_SLOCK) ? RCS_LOCK_STRICT : RCS_LOCK_LOOSE;
781a021dec5Sjfb }
782a021dec5Sjfb 
783a021dec5Sjfb /*
784a021dec5Sjfb  * rcs_lock_setmode()
785a021dec5Sjfb  *
786a021dec5Sjfb  * Set the locking mode of the RCS file <file> to <mode>, which must either
787a021dec5Sjfb  * be RCS_LOCK_LOOSE or RCS_LOCK_STRICT.
788a021dec5Sjfb  * Returns the previous mode on success, or -1 on failure.
789a021dec5Sjfb  */
790a021dec5Sjfb int
rcs_lock_setmode(RCSFILE * file,int mode)791a021dec5Sjfb rcs_lock_setmode(RCSFILE *file, int mode)
792a021dec5Sjfb {
793a021dec5Sjfb 	int pmode;
794a021dec5Sjfb 	pmode = rcs_lock_getmode(file);
795a021dec5Sjfb 
796a021dec5Sjfb 	if (mode == RCS_LOCK_STRICT)
797a021dec5Sjfb 		file->rf_flags |= RCS_SLOCK;
798a021dec5Sjfb 	else if (mode == RCS_LOCK_LOOSE)
799a021dec5Sjfb 		file->rf_flags &= ~RCS_SLOCK;
800291869e5Sxsa 	else
801291869e5Sxsa 		fatal("rcs_lock_setmode: invalid mode `%d'", mode);
802a021dec5Sjfb 
80308946b33Smoritz 	file->rf_flags &= ~RCS_SYNCED;
804a021dec5Sjfb 	return (pmode);
805a021dec5Sjfb }
806a021dec5Sjfb 
807a021dec5Sjfb /*
808a33e30caSjfb  * rcs_lock_add()
809a33e30caSjfb  *
810a33e30caSjfb  * Add an RCS lock for the user <user> on revision <rev>.
811a33e30caSjfb  * Returns 0 on success, or -1 on failure.
812a33e30caSjfb  */
813a33e30caSjfb int
rcs_lock_add(RCSFILE * file,const char * user,RCSNUM * rev)814a33e30caSjfb rcs_lock_add(RCSFILE *file, const char *user, RCSNUM *rev)
815a33e30caSjfb {
816a33e30caSjfb 	struct rcs_lock *lkp;
817a33e30caSjfb 
818a33e30caSjfb 	/* first look for duplication */
819a33e30caSjfb 	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
820d593696fSderaadt 		if (strcmp(lkp->rl_name, user) == 0 &&
8215b95d21fSjoris 		    rcsnum_cmp(rev, lkp->rl_num, 0) == 0)
822a33e30caSjfb 			return (-1);
823a33e30caSjfb 	}
824a33e30caSjfb 
8253b4c5c25Sray 	lkp = xmalloc(sizeof(*lkp));
8260450b43bSjoris 	lkp->rl_name = xstrdup(user);
8273422a189Sjoris 	lkp->rl_num = rcsnum_alloc();
82846ced471Smoritz 	rcsnum_cpy(rev, lkp->rl_num, 0);
82946ced471Smoritz 
830a33e30caSjfb 	TAILQ_INSERT_TAIL(&(file->rf_locks), lkp, rl_list);
831a33e30caSjfb 
832a33e30caSjfb 	/* not synced anymore */
833a33e30caSjfb 	file->rf_flags &= ~RCS_SYNCED;
834a33e30caSjfb 	return (0);
835a33e30caSjfb }
836a33e30caSjfb 
837a33e30caSjfb 
838a33e30caSjfb /*
839a33e30caSjfb  * rcs_lock_remove()
840a33e30caSjfb  *
841a33e30caSjfb  * Remove the RCS lock on revision <rev>.
842a33e30caSjfb  * Returns 0 on success, or -1 on failure.
843a33e30caSjfb  */
844a33e30caSjfb int
rcs_lock_remove(RCSFILE * file,const char * user,RCSNUM * rev)845bbfcb536Sjoris rcs_lock_remove(RCSFILE *file, const char *user, RCSNUM *rev)
846a33e30caSjfb {
847a33e30caSjfb 	struct rcs_lock *lkp;
848a33e30caSjfb 
849bbfcb536Sjoris 	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
850d593696fSderaadt 		if (strcmp(lkp->rl_name, user) == 0 &&
851d593696fSderaadt 		    rcsnum_cmp(lkp->rl_num, rev, 0) == 0)
852a33e30caSjfb 			break;
853bbfcb536Sjoris 	}
854a33e30caSjfb 
8555b95d21fSjoris 	if (lkp == NULL)
856a33e30caSjfb 		return (-1);
857a33e30caSjfb 
858a33e30caSjfb 	TAILQ_REMOVE(&(file->rf_locks), lkp, rl_list);
85953ce2177Sfcambus 	free(lkp->rl_num);
860397ddb8aSnicm 	free(lkp->rl_name);
861397ddb8aSnicm 	free(lkp);
862a33e30caSjfb 
863a33e30caSjfb 	/* not synced anymore */
864a33e30caSjfb 	file->rf_flags &= ~RCS_SYNCED;
865a33e30caSjfb 	return (0);
866a33e30caSjfb }
867a33e30caSjfb 
868a33e30caSjfb /*
869101fc366Sjfb  * rcs_desc_get()
870101fc366Sjfb  *
871101fc366Sjfb  * Retrieve the description for the RCS file <file>.
872101fc366Sjfb  */
873101fc366Sjfb const char *
rcs_desc_get(RCSFILE * file)874101fc366Sjfb rcs_desc_get(RCSFILE *file)
875101fc366Sjfb {
876101fc366Sjfb 	return (file->rf_desc);
877101fc366Sjfb }
878101fc366Sjfb 
879101fc366Sjfb /*
880101fc366Sjfb  * rcs_desc_set()
881101fc366Sjfb  *
882101fc366Sjfb  * Set the description for the RCS file <file>.
883101fc366Sjfb  */
884f92ad66fSxsa void
rcs_desc_set(RCSFILE * file,const char * desc)885101fc366Sjfb rcs_desc_set(RCSFILE *file, const char *desc)
886101fc366Sjfb {
887101fc366Sjfb 	char *tmp;
888101fc366Sjfb 
8890450b43bSjoris 	tmp = xstrdup(desc);
890397ddb8aSnicm 	free(file->rf_desc);
891101fc366Sjfb 	file->rf_desc = tmp;
892101fc366Sjfb 	file->rf_flags &= ~RCS_SYNCED;
893101fc366Sjfb }
894101fc366Sjfb 
89570e8f52aSjfb /*
896ff88b297Sjfb  * rcs_comment_lookup()
897ff88b297Sjfb  *
898ff88b297Sjfb  * Lookup the assumed comment leader based on a file's suffix.
899ff88b297Sjfb  * Returns a pointer to the string on success, or NULL on failure.
900ff88b297Sjfb  */
901ff88b297Sjfb const char *
rcs_comment_lookup(const char * filename)902ff88b297Sjfb rcs_comment_lookup(const char *filename)
903ff88b297Sjfb {
904ff88b297Sjfb 	int i;
905ff88b297Sjfb 	const char *sp;
906ff88b297Sjfb 
9075b95d21fSjoris 	if ((sp = strrchr(filename, '.')) == NULL)
908ff88b297Sjfb 		return (NULL);
909ff88b297Sjfb 	sp++;
910ff88b297Sjfb 
911ff88b297Sjfb 	for (i = 0; i < (int)NB_COMTYPES; i++)
912ff88b297Sjfb 		if (strcmp(rcs_comments[i].rc_suffix, sp) == 0)
913ff88b297Sjfb 			return (rcs_comments[i].rc_cstr);
914ff88b297Sjfb 	return (NULL);
915ff88b297Sjfb }
916ff88b297Sjfb 
917ff88b297Sjfb /*
91870e8f52aSjfb  * rcs_comment_get()
91970e8f52aSjfb  *
92070e8f52aSjfb  * Retrieve the comment leader for the RCS file <file>.
92170e8f52aSjfb  */
92270e8f52aSjfb const char *
rcs_comment_get(RCSFILE * file)92370e8f52aSjfb rcs_comment_get(RCSFILE *file)
92470e8f52aSjfb {
92570e8f52aSjfb 	return (file->rf_comment);
92670e8f52aSjfb }
92770e8f52aSjfb 
92870e8f52aSjfb /*
92970e8f52aSjfb  * rcs_comment_set()
93070e8f52aSjfb  *
93170e8f52aSjfb  * Set the comment leader for the RCS file <file>.
93270e8f52aSjfb  */
9331d612bafSxsa void
rcs_comment_set(RCSFILE * file,const char * comment)93470e8f52aSjfb rcs_comment_set(RCSFILE *file, const char *comment)
93570e8f52aSjfb {
93670e8f52aSjfb 	char *tmp;
93770e8f52aSjfb 
9380450b43bSjoris 	tmp = xstrdup(comment);
939397ddb8aSnicm 	free(file->rf_comment);
94070e8f52aSjfb 	file->rf_comment = tmp;
94170e8f52aSjfb 	file->rf_flags &= ~RCS_SYNCED;
94270e8f52aSjfb }
943101fc366Sjfb 
94401af718aSjoris int
rcs_patch_lines(struct rcs_lines * dlines,struct rcs_lines * plines,struct rcs_line ** alines,struct rcs_delta * rdp)9457bb3ddb0Sray rcs_patch_lines(struct rcs_lines *dlines, struct rcs_lines *plines,
9467bb3ddb0Sray     struct rcs_line **alines, struct rcs_delta *rdp)
9478df73530Svincent {
94890fdc30cSotto 	u_char op;
94990fdc30cSotto 	char *ep;
9507bb3ddb0Sray 	struct rcs_line *lp, *dlp, *ndlp;
9518df73530Svincent 	int i, lineno, nbln;
95231cfd684Sniallo 	u_char tmp;
95308f90673Sjfb 
95401af718aSjoris 	dlp = TAILQ_FIRST(&(dlines->l_lines));
95501af718aSjoris 	lp = TAILQ_FIRST(&(plines->l_lines));
95608f90673Sjfb 
95708f90673Sjfb 	/* skip first bogus line */
95801af718aSjoris 	for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
95901af718aSjoris 	    lp = TAILQ_NEXT(lp, l_list)) {
96031cfd684Sniallo 		if (lp->l_len < 2)
96131cfd684Sniallo 			fatal("line too short, RCS patch seems broken");
96201af718aSjoris 		op = *(lp->l_line);
96331cfd684Sniallo 		/* NUL-terminate line buffer for strtol() safety. */
96431cfd684Sniallo 		tmp = lp->l_line[lp->l_len - 1];
96531cfd684Sniallo 		lp->l_line[lp->l_len - 1] = '\0';
96690fdc30cSotto 		lineno = (int)strtol((char*)(lp->l_line + 1), &ep, 10);
96731cfd684Sniallo 		if (lineno - 1 > dlines->l_nblines || lineno < 0) {
96831cfd684Sniallo 			fatal("invalid line specification in RCS patch");
96931cfd684Sniallo 		}
97008f90673Sjfb 		ep++;
97108f90673Sjfb 		nbln = (int)strtol(ep, &ep, 10);
97231cfd684Sniallo 		/* Restore the last byte of the buffer */
97331cfd684Sniallo 		lp->l_line[lp->l_len - 1] = tmp;
97431cfd684Sniallo 		if (nbln < 0)
975e643a8ebSniallo 			fatal("invalid line number specification in RCS patch");
97608f90673Sjfb 
97708f90673Sjfb 		/* find the appropriate line */
97808f90673Sjfb 		for (;;) {
97908f90673Sjfb 			if (dlp == NULL)
98008f90673Sjfb 				break;
98101af718aSjoris 			if (dlp->l_lineno == lineno)
98208f90673Sjfb 				break;
98301af718aSjoris 			if (dlp->l_lineno > lineno) {
9847a9e6d11Sray 				dlp = TAILQ_PREV(dlp, tqh, l_list);
98501af718aSjoris 			} else if (dlp->l_lineno < lineno) {
986228c451cSniallo 				if (((ndlp = TAILQ_NEXT(dlp, l_list)) == NULL) ||
987d593696fSderaadt 				    ndlp->l_lineno > lineno)
98808f90673Sjfb 					break;
98908f90673Sjfb 				dlp = ndlp;
99008f90673Sjfb 			}
99108f90673Sjfb 		}
992e643a8ebSniallo 		if (dlp == NULL)
993e643a8ebSniallo 			fatal("can't find referenced line in RCS patch");
99408f90673Sjfb 
99508f90673Sjfb 		if (op == 'd') {
99608f90673Sjfb 			for (i = 0; (i < nbln) && (dlp != NULL); i++) {
99701af718aSjoris 				ndlp = TAILQ_NEXT(dlp, l_list);
99801af718aSjoris 				TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list);
9995e4c4390Stobias 				if (alines != NULL && dlp->l_line != NULL) {
10005e4c4390Stobias 					dlp->l_delta = rdp;
10015e4c4390Stobias 					alines[dlp->l_lineno_orig - 1] =
10025e4c4390Stobias 						dlp;
10035e4c4390Stobias 				} else
1004397ddb8aSnicm 					free(dlp);
100508f90673Sjfb 				dlp = ndlp;
1006228c451cSniallo 				/* last line is gone - reset dlp */
1007228c451cSniallo 				if (dlp == NULL) {
1008228c451cSniallo 					ndlp = TAILQ_LAST(&(dlines->l_lines),
10097a9e6d11Sray 					    tqh);
1010228c451cSniallo 					dlp = ndlp;
1011228c451cSniallo 				}
101208f90673Sjfb 			}
10133917c9bfSderaadt 		} else if (op == 'a') {
101408f90673Sjfb 			for (i = 0; i < nbln; i++) {
101508f90673Sjfb 				ndlp = lp;
101601af718aSjoris 				lp = TAILQ_NEXT(lp, l_list);
1017e643a8ebSniallo 				if (lp == NULL)
1018e643a8ebSniallo 					fatal("truncated RCS patch");
101901af718aSjoris 				TAILQ_REMOVE(&(plines->l_lines), lp, l_list);
10205e4c4390Stobias 				if (alines != NULL) {
10215e4c4390Stobias 					if (lp->l_needsfree == 1)
1022397ddb8aSnicm 						free(lp->l_line);
10235e4c4390Stobias 					lp->l_line = NULL;
10245e4c4390Stobias 					lp->l_needsfree = 0;
10255e4c4390Stobias 				}
102637cbc181Stobias 				lp->l_delta = rdp;
102701af718aSjoris 				TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp,
102801af718aSjoris 				    lp, l_list);
102908f90673Sjfb 				dlp = lp;
103008f90673Sjfb 
103108f90673Sjfb 				/* we don't want lookup to block on those */
103201af718aSjoris 				lp->l_lineno = lineno;
103308f90673Sjfb 
103408f90673Sjfb 				lp = ndlp;
103508f90673Sjfb 			}
1036e643a8ebSniallo 		} else
1037e643a8ebSniallo 			fatal("unknown RCS patch operation `%c'", op);
103808f90673Sjfb 
103908f90673Sjfb 		/* last line of the patch, done */
104001af718aSjoris 		if (lp->l_lineno == plines->l_nblines)
104108f90673Sjfb 			break;
104208f90673Sjfb 	}
104308f90673Sjfb 
104408f90673Sjfb 	/* once we're done patching, rebuild the line numbers */
1045384f8873Svincent 	lineno = 0;
104601af718aSjoris 	TAILQ_FOREACH(lp, &(dlines->l_lines), l_list)
104701af718aSjoris 		lp->l_lineno = lineno++;
104801af718aSjoris 	dlines->l_nblines = lineno - 1;
104908f90673Sjfb 
10508df73530Svincent 	return (0);
105108f90673Sjfb }
105208f90673Sjfb 
10539f5450fbSjoris void
rcs_delta_stats(struct rcs_delta * rdp,int * ladded,int * lremoved)10549f5450fbSjoris rcs_delta_stats(struct rcs_delta *rdp, int *ladded, int *lremoved)
10559f5450fbSjoris {
10567bb3ddb0Sray 	struct rcs_lines *plines;
10577bb3ddb0Sray 	struct rcs_line *lp;
10584fc7a9b5Snicm 	int added, i, nbln, removed;
10599f5450fbSjoris 	char op, *ep;
10609f5450fbSjoris 	u_char tmp;
10619f5450fbSjoris 
10629f5450fbSjoris 	added = removed = 0;
10639f5450fbSjoris 
10649f5450fbSjoris 	plines = cvs_splitlines(rdp->rd_text, rdp->rd_tlen);
10659f5450fbSjoris 	lp = TAILQ_FIRST(&(plines->l_lines));
10669f5450fbSjoris 
10679f5450fbSjoris 	/* skip first bogus line */
10689f5450fbSjoris 	for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
10699f5450fbSjoris 	    lp = TAILQ_NEXT(lp, l_list)) {
10709f5450fbSjoris 		if (lp->l_len < 2)
10719f5450fbSjoris 			fatal("line too short, RCS patch seems broken");
10729f5450fbSjoris 		op = *(lp->l_line);
10739f5450fbSjoris 		/* NUL-terminate line buffer for strtol() safety. */
10749f5450fbSjoris 		tmp = lp->l_line[lp->l_len - 1];
10759f5450fbSjoris 		lp->l_line[lp->l_len - 1] = '\0';
10764fc7a9b5Snicm 		(void)strtol((lp->l_line + 1), &ep, 10);
10779f5450fbSjoris 		ep++;
10789f5450fbSjoris 		nbln = (int)strtol(ep, &ep, 10);
10799f5450fbSjoris 		/* Restore the last byte of the buffer */
10809f5450fbSjoris 		lp->l_line[lp->l_len - 1] = tmp;
10819f5450fbSjoris 		if (nbln < 0)
10829f5450fbSjoris 			fatal("invalid line number specification in RCS patch");
10839f5450fbSjoris 
10849f5450fbSjoris 		if (op == 'a') {
10859f5450fbSjoris 			added += nbln;
10869f5450fbSjoris 			for (i = 0; i < nbln; i++) {
10879f5450fbSjoris 				lp = TAILQ_NEXT(lp, l_list);
10889f5450fbSjoris 				if (lp == NULL)
10899f5450fbSjoris 					fatal("truncated RCS patch");
10909f5450fbSjoris 			}
10919f5450fbSjoris 		}
10929f5450fbSjoris 		else if (op == 'd')
10939f5450fbSjoris 			removed += nbln;
10949f5450fbSjoris 		else
10959f5450fbSjoris 			fatal("unknown RCS patch operation '%c'", op);
10969f5450fbSjoris 	}
10979f5450fbSjoris 
1098e0641964Stobias 	cvs_freelines(plines);
1099e0641964Stobias 
11009f5450fbSjoris 	*ladded = added;
11019f5450fbSjoris 	*lremoved = removed;
11029f5450fbSjoris }
11039f5450fbSjoris 
110408f90673Sjfb /*
110556bc38daSjfb  * rcs_rev_add()
110656bc38daSjfb  *
11077873cb13Sjfb  * Add a revision to the RCS file <rf>.  The new revision's number can be
11087873cb13Sjfb  * specified in <rev> (which can also be RCS_HEAD_REV, in which case the
11097873cb13Sjfb  * new revision will have a number equal to the previous head revision plus
11107873cb13Sjfb  * one).  The <msg> argument specifies the log message for that revision, and
11117873cb13Sjfb  * <date> specifies the revision's date (a value of -1 is
11127873cb13Sjfb  * equivalent to using the current time).
11139d97bd6aSray  * If <author> is NULL, set the author for this revision to the current user.
111456bc38daSjfb  * Returns 0 on success, or -1 on failure.
111556bc38daSjfb  */
111656bc38daSjfb int
rcs_rev_add(RCSFILE * rf,RCSNUM * rev,const char * msg,time_t date,const char * author)111744b1b892Sniallo rcs_rev_add(RCSFILE *rf, RCSNUM *rev, const char *msg, time_t date,
11189d97bd6aSray     const char *author)
111956bc38daSjfb {
112056bc38daSjfb 	time_t now;
1121e1936db1Stobias 	RCSNUM *root = NULL;
112256bc38daSjfb 	struct passwd *pw;
1123e1936db1Stobias 	struct rcs_branch *brp, *obrp;
1124181bf63dSjoris 	struct rcs_delta *ordp, *rdp;
1125181bf63dSjoris 
112656bc38daSjfb 	if (rev == RCS_HEAD_REV) {
112795498594Sniallo 		if (rf->rf_flags & RCS_CREATE) {
112895498594Sniallo 			if ((rev = rcsnum_parse(RCS_HEAD_INIT)) == NULL)
112995498594Sniallo 				return (-1);
1130397ddb8aSnicm 			free(rf->rf_head);
1131509a3673Stobias 			rf->rf_head = rev;
1132e28eda4eStobias 		} else if (rf->rf_head == NULL) {
1133e28eda4eStobias 			return (-1);
113495498594Sniallo 		} else {
1135f9b67873Sniallo 			rev = rcsnum_inc(rf->rf_head);
113695498594Sniallo 		}
1137181bf63dSjoris 	} else {
11385b95d21fSjoris 		if ((rdp = rcs_findrev(rf, rev)) != NULL)
113956bc38daSjfb 			return (-1);
114056bc38daSjfb 	}
1141181bf63dSjoris 
11423b4c5c25Sray 	rdp = xcalloc(1, sizeof(*rdp));
114356bc38daSjfb 
114456bc38daSjfb 	TAILQ_INIT(&(rdp->rd_branches));
114556bc38daSjfb 
11463422a189Sjoris 	rdp->rd_num = rcsnum_alloc();
114756bc38daSjfb 	rcsnum_cpy(rev, rdp->rd_num, 0);
114856bc38daSjfb 
11493422a189Sjoris 	rdp->rd_next = rcsnum_alloc();
115053b9345bSjoris 
11519d97bd6aSray 	if (!author && !(author = getlogin())) {
11529d97bd6aSray 		if (!(pw = getpwuid(getuid())))
11539d97bd6aSray 			fatal("getpwuid failed");
11549d97bd6aSray 		author = pw->pw_name;
11559d97bd6aSray 	}
11569d97bd6aSray 	rdp->rd_author = xstrdup(author);
11570450b43bSjoris 	rdp->rd_state = xstrdup(RCS_STATE_EXP);
11580450b43bSjoris 	rdp->rd_log = xstrdup(msg);
115956bc38daSjfb 
11607873cb13Sjfb 	if (date != (time_t)(-1))
11617873cb13Sjfb 		now = date;
11627873cb13Sjfb 	else
116356bc38daSjfb 		time(&now);
116456bc38daSjfb 	gmtime_r(&now, &(rdp->rd_date));
116556bc38daSjfb 
116608458e59Sjoris 	if (RCSNUM_ISBRANCHREV(rev))
116708458e59Sjoris 		TAILQ_INSERT_TAIL(&(rf->rf_delta), rdp, rd_list);
116808458e59Sjoris 	else
116956bc38daSjfb 		TAILQ_INSERT_HEAD(&(rf->rf_delta), rdp, rd_list);
117056bc38daSjfb 	rf->rf_ndelta++;
1171f9b67873Sniallo 
117208458e59Sjoris 	if (!(rf->rf_flags & RCS_CREATE)) {
117308458e59Sjoris 		if (RCSNUM_ISBRANCHREV(rev)) {
1174e1936db1Stobias 			if (rev->rn_id[rev->rn_len - 1] == 1) {
1175e1936db1Stobias 				/* a new branch */
117618cf7829Sjoris 				root = rcsnum_branch_root(rev);
117708458e59Sjoris 				brp = xmalloc(sizeof(*brp));
117808458e59Sjoris 				brp->rb_num = rcsnum_alloc();
117908458e59Sjoris 				rcsnum_cpy(rdp->rd_num, brp->rb_num, 0);
1180e1936db1Stobias 
118118cf7829Sjoris 				if ((ordp = rcs_findrev(rf, root)) == NULL)
118218cf7829Sjoris 					fatal("root node not found");
1183e1936db1Stobias 
1184e1936db1Stobias 				TAILQ_FOREACH(obrp, &(ordp->rd_branches),
1185e1936db1Stobias 				    rb_list) {
1186e1936db1Stobias 					if (!rcsnum_cmp(obrp->rb_num,
1187e1936db1Stobias 					    brp->rb_num,
1188e1936db1Stobias 					    brp->rb_num->rn_len - 1))
1189e1936db1Stobias 						break;
1190e1936db1Stobias 				}
1191e1936db1Stobias 
1192e1936db1Stobias 				if (obrp == NULL) {
119318cf7829Sjoris 					TAILQ_INSERT_TAIL(&(ordp->rd_branches),
119418cf7829Sjoris 					    brp, rb_list);
119518cf7829Sjoris 				}
1196e1936db1Stobias 			} else {
1197e1936db1Stobias 				root = rcsnum_alloc();
1198e1936db1Stobias 				rcsnum_cpy(rev, root, 0);
1199e1936db1Stobias 				rcsnum_dec(root);
1200e1936db1Stobias 				if ((ordp = rcs_findrev(rf, root)) == NULL)
1201e1936db1Stobias 					fatal("previous revision not found");
120208458e59Sjoris 				rcsnum_cpy(rdp->rd_num, ordp->rd_next, 0);
1203e1936db1Stobias 			}
120408458e59Sjoris 		} else {
120508458e59Sjoris 			ordp = TAILQ_NEXT(rdp, rd_list);
120608458e59Sjoris 			rcsnum_cpy(ordp->rd_num, rdp->rd_next, 0);
120708458e59Sjoris 		}
120808458e59Sjoris 	}
120908458e59Sjoris 
121053ce2177Sfcambus 	free(root);
1211e1936db1Stobias 
1212dddcab29Sniallo 	/* not synced anymore */
1213dddcab29Sniallo 	rf->rf_flags &= ~RCS_SYNCED;
121456bc38daSjfb 
121556bc38daSjfb 	return (0);
121656bc38daSjfb }
121756bc38daSjfb 
121856bc38daSjfb /*
121956bc38daSjfb  * rcs_rev_remove()
122056bc38daSjfb  *
122156bc38daSjfb  * Remove the revision whose number is <rev> from the RCS file <rf>.
122256bc38daSjfb  */
122356bc38daSjfb int
rcs_rev_remove(RCSFILE * rf,RCSNUM * rev)122456bc38daSjfb rcs_rev_remove(RCSFILE *rf, RCSNUM *rev)
122556bc38daSjfb {
12262e0d696aSjoris 	int fd1, fd2;
12274d33f394Sjoris 	char *path_tmp1, *path_tmp2;
1228ea1ee718Sjoris 	struct rcs_delta *rdp, *prevrdp, *nextrdp;
122990fdc30cSotto 	BUF *prevbuf, *newdiff, *newdeltatext;
123056bc38daSjfb 
123156bc38daSjfb 	if (rev == RCS_HEAD_REV)
123256bc38daSjfb 		rev = rf->rf_head;
123356bc38daSjfb 
1234e28eda4eStobias 	if (rev == NULL)
1235e28eda4eStobias 		return (-1);
1236e28eda4eStobias 
123756bc38daSjfb 	/* do we actually have that revision? */
12385b95d21fSjoris 	if ((rdp = rcs_findrev(rf, rev)) == NULL)
1239ea1ee718Sjoris 		return (-1);
124056bc38daSjfb 
1241ea1ee718Sjoris 	/*
1242ea1ee718Sjoris 	 * This is confusing, the previous delta is next in the TAILQ list.
1243ea1ee718Sjoris 	 * the next delta is the previous one in the TAILQ list.
1244ea1ee718Sjoris 	 *
1245ea1ee718Sjoris 	 * When the HEAD revision got specified, nextrdp will be NULL.
1246ea1ee718Sjoris 	 * When the first revision got specified, prevrdp will be NULL.
1247ea1ee718Sjoris 	 */
1248ea1ee718Sjoris 	prevrdp = (struct rcs_delta *)TAILQ_NEXT(rdp, rd_list);
12497a9e6d11Sray 	nextrdp = (struct rcs_delta *)TAILQ_PREV(rdp, tqh, rd_list);
125056bc38daSjfb 
1251ea1ee718Sjoris 	newdeltatext = NULL;
125290fdc30cSotto 	prevbuf = NULL;
125344b1b14fSjoris 	path_tmp1 = path_tmp2 = NULL;
1254ea1ee718Sjoris 
1255d593696fSderaadt 	if (prevrdp != NULL && nextrdp != NULL) {
12567bb3ddb0Sray 		newdiff = buf_alloc(64);
1257ea1ee718Sjoris 
1258ea1ee718Sjoris 		/* calculate new diff */
12593ad3fb45Sjoris 		(void)xasprintf(&path_tmp1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
12602e0d696aSjoris 		fd1 = rcs_rev_write_stmp(rf, nextrdp->rd_num, path_tmp1, 0);
1261ea1ee718Sjoris 
12623ad3fb45Sjoris 		(void)xasprintf(&path_tmp2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
12632e0d696aSjoris 		fd2 = rcs_rev_write_stmp(rf, prevrdp->rd_num, path_tmp2, 0);
1264ea1ee718Sjoris 
1265ea1ee718Sjoris 		diff_format = D_RCSDIFF;
126657003866Sray 		if (diffreg(path_tmp1, path_tmp2,
1267219c50abSray 		    fd1, fd2, newdiff, D_FORCEASCII) == D_ERROR)
12683ad3fb45Sjoris 			fatal("rcs_diffreg failed");
1269ea1ee718Sjoris 
12702e0d696aSjoris 		close(fd1);
12712e0d696aSjoris 		close(fd2);
12722e0d696aSjoris 
12734d33f394Sjoris 		newdeltatext = newdiff;
1274d593696fSderaadt 	} else if (nextrdp == NULL && prevrdp != NULL) {
12754d33f394Sjoris 		newdeltatext = prevbuf;
1276ea1ee718Sjoris 	}
1277ea1ee718Sjoris 
1278ea1ee718Sjoris 	if (newdeltatext != NULL) {
1279ea1ee718Sjoris 		if (rcs_deltatext_set(rf, prevrdp->rd_num, newdeltatext) < 0)
1280ea1ee718Sjoris 			fatal("error setting new deltatext");
1281ea1ee718Sjoris 	}
1282ea1ee718Sjoris 
1283ea1ee718Sjoris 	TAILQ_REMOVE(&(rf->rf_delta), rdp, rd_list);
1284ea1ee718Sjoris 
1285ea1ee718Sjoris 	/* update pointers */
1286d593696fSderaadt 	if (prevrdp != NULL && nextrdp != NULL) {
1287ea1ee718Sjoris 		rcsnum_cpy(prevrdp->rd_num, nextrdp->rd_next, 0);
1288ea1ee718Sjoris 	} else if (prevrdp != NULL) {
12891db03c24Sxsa 		if (rcs_head_set(rf, prevrdp->rd_num) < 0)
12901db03c24Sxsa 			fatal("rcs_head_set failed");
1291ea1ee718Sjoris 	} else if (nextrdp != NULL) {
129253ce2177Sfcambus 		free(nextrdp->rd_next);
1293ea1ee718Sjoris 		nextrdp->rd_next = rcsnum_alloc();
1294ea1ee718Sjoris 	} else {
129553ce2177Sfcambus 		free(rf->rf_head);
1296ea1ee718Sjoris 		rf->rf_head = NULL;
1297ea1ee718Sjoris 	}
1298ea1ee718Sjoris 
1299ea1ee718Sjoris 	rf->rf_ndelta--;
1300ea1ee718Sjoris 	rf->rf_flags &= ~RCS_SYNCED;
1301ea1ee718Sjoris 
1302ea1ee718Sjoris 	rcs_freedelta(rdp);
1303397ddb8aSnicm 	free(newdeltatext);
1304397ddb8aSnicm 	free(path_tmp1);
1305397ddb8aSnicm 	free(path_tmp2);
13063ad3fb45Sjoris 
1307ea1ee718Sjoris 	return (0);
130856bc38daSjfb }
130956bc38daSjfb 
131056bc38daSjfb /*
131108f90673Sjfb  * rcs_findrev()
131208f90673Sjfb  *
131308f90673Sjfb  * Find a specific revision's delta entry in the tree of the RCS file <rfp>.
131408f90673Sjfb  * The revision number is given in <rev>.
1315b1ce7b24Sjoris  *
131608f90673Sjfb  * Returns a pointer to the delta on success, or NULL on failure.
131708f90673Sjfb  */
1318fccdc061Sxsa struct rcs_delta *
rcs_findrev(RCSFILE * rfp,RCSNUM * rev)1319b71c7dfeSniallo rcs_findrev(RCSFILE *rfp, RCSNUM *rev)
132008f90673Sjfb {
132140e206ceSjoris 	int isbrev;
1322b1ce7b24Sjoris 	struct rcs_delta *rdp;
132340e206ceSjoris 
1324cd71f6bbStobias 	if (rev == NULL)
1325cd71f6bbStobias 		return NULL;
1326cd71f6bbStobias 
132740e206ceSjoris 	isbrev = RCSNUM_ISBRANCHREV(rev);
132808f90673Sjfb 
1329b71c7dfeSniallo 	/*
1330b71c7dfeSniallo 	 * We need to do more parsing if the last revision in the linked list
1331b71c7dfeSniallo 	 * is greater than the requested revision.
1332b71c7dfeSniallo 	 */
1333b1ce7b24Sjoris 	rdp = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
1334d593696fSderaadt 	if (rdp == NULL ||
133540e206ceSjoris 	    (!isbrev && rcsnum_cmp(rdp->rd_num, rev, 0) == -1) ||
133640e206ceSjoris 	    ((isbrev && rdp->rd_num->rn_len < 4) ||
133740e206ceSjoris 	    (isbrev && rcsnum_differ(rev, rdp->rd_num)))) {
1338fead43dfStobias 		if (rcsparse_deltas(rfp, rev))
1339fead43dfStobias 			fatal("error parsing deltas");
1340b71c7dfeSniallo 	}
1341b71c7dfeSniallo 
1342b1ce7b24Sjoris 	TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
134340e206ceSjoris 		if (rcsnum_differ(rdp->rd_num, rev))
134440e206ceSjoris 			continue;
134540e206ceSjoris 		else
1346b1ce7b24Sjoris 			return (rdp);
1347b1ce7b24Sjoris 	}
134808f90673Sjfb 
134908f90673Sjfb 	return (NULL);
135008f90673Sjfb }
135108f90673Sjfb 
135208f90673Sjfb /*
13531b6534b8Sjfb  * rcs_kwexp_set()
13541b6534b8Sjfb  *
13551b6534b8Sjfb  * Set the keyword expansion mode to use on the RCS file <file> to <mode>.
13561b6534b8Sjfb  */
135786ca4362Sxsa void
rcs_kwexp_set(RCSFILE * file,int mode)13581b6534b8Sjfb rcs_kwexp_set(RCSFILE *file, int mode)
13591b6534b8Sjfb {
13601b6534b8Sjfb 	int i;
13611b6534b8Sjfb 	char *tmp, buf[8] = "";
13621b6534b8Sjfb 
13631b6534b8Sjfb 	if (RCS_KWEXP_INVAL(mode))
136486ca4362Sxsa 		return;
13651b6534b8Sjfb 
13661b6534b8Sjfb 	i = 0;
13671b6534b8Sjfb 	if (mode == RCS_KWEXP_NONE)
13681b6534b8Sjfb 		buf[0] = 'b';
13691b6534b8Sjfb 	else if (mode == RCS_KWEXP_OLD)
13701b6534b8Sjfb 		buf[0] = 'o';
13711b6534b8Sjfb 	else {
13721b6534b8Sjfb 		if (mode & RCS_KWEXP_NAME)
13731b6534b8Sjfb 			buf[i++] = 'k';
13741b6534b8Sjfb 		if (mode & RCS_KWEXP_VAL)
13751b6534b8Sjfb 			buf[i++] = 'v';
13761b6534b8Sjfb 		if (mode & RCS_KWEXP_LKR)
13771b6534b8Sjfb 			buf[i++] = 'l';
13781b6534b8Sjfb 	}
13791b6534b8Sjfb 
13800450b43bSjoris 	tmp = xstrdup(buf);
1381397ddb8aSnicm 	free(file->rf_expand);
13821b6534b8Sjfb 	file->rf_expand = tmp;
1383dddcab29Sniallo 	/* not synced anymore */
1384dddcab29Sniallo 	file->rf_flags &= ~RCS_SYNCED;
13851b6534b8Sjfb }
13861b6534b8Sjfb 
13871b6534b8Sjfb /*
13881b6534b8Sjfb  * rcs_kwexp_get()
13891b6534b8Sjfb  *
13901b6534b8Sjfb  * Retrieve the keyword expansion mode to be used for the RCS file <file>.
13911b6534b8Sjfb  */
13921b6534b8Sjfb int
rcs_kwexp_get(RCSFILE * file)13931b6534b8Sjfb rcs_kwexp_get(RCSFILE *file)
13941b6534b8Sjfb {
1395f2a06cf4Stobias 	if (file->rf_expand == NULL)
1396f2a06cf4Stobias 		return (RCS_KWEXP_DEFAULT);
1397f2a06cf4Stobias 
1398f2a06cf4Stobias 	return (rcs_kflag_get(file->rf_expand));
13991b6534b8Sjfb }
14001b6534b8Sjfb 
14011b6534b8Sjfb /*
1402b856966bSjfb  * rcs_kflag_get()
1403b856966bSjfb  *
1404b856966bSjfb  * Get the keyword expansion mode from a set of character flags given in
1405b856966bSjfb  * <flags> and return the appropriate flag mask.  In case of an error, the
1406b856966bSjfb  * returned mask will have the RCS_KWEXP_ERR bit set to 1.
1407b856966bSjfb  */
1408b856966bSjfb int
rcs_kflag_get(const char * flags)1409b856966bSjfb rcs_kflag_get(const char *flags)
1410b856966bSjfb {
1411b856966bSjfb 	int fl;
1412b856966bSjfb 	size_t len;
1413b856966bSjfb 	const char *fp;
1414b856966bSjfb 
1415f2a06cf4Stobias 	if (flags == NULL || !(len = strlen(flags)))
1416f2a06cf4Stobias 		return (RCS_KWEXP_ERR);
14176a64c583Stobias 
1418b856966bSjfb 	fl = 0;
1419b856966bSjfb 	for (fp = flags; *fp != '\0'; fp++) {
1420b856966bSjfb 		if (*fp == 'k')
1421b856966bSjfb 			fl |= RCS_KWEXP_NAME;
1422b856966bSjfb 		else if (*fp == 'v')
1423b856966bSjfb 			fl |= RCS_KWEXP_VAL;
1424b856966bSjfb 		else if (*fp == 'l')
1425b856966bSjfb 			fl |= RCS_KWEXP_LKR;
1426b856966bSjfb 		else if (*fp == 'o') {
1427b856966bSjfb 			if (len != 1)
1428b856966bSjfb 				fl |= RCS_KWEXP_ERR;
1429b856966bSjfb 			fl |= RCS_KWEXP_OLD;
1430b856966bSjfb 		} else if (*fp == 'b') {
1431b856966bSjfb 			if (len != 1)
1432b856966bSjfb 				fl |= RCS_KWEXP_ERR;
143356f996a2Sxsa 			fl |= RCS_KWEXP_NONE;
1434b856966bSjfb 		} else	/* unknown letter */
1435b856966bSjfb 			fl |= RCS_KWEXP_ERR;
1436b856966bSjfb 	}
1437b856966bSjfb 
1438b856966bSjfb 	return (fl);
1439b856966bSjfb }
1440b856966bSjfb 
144108f90673Sjfb /*
144208f90673Sjfb  * rcs_freedelta()
144308f90673Sjfb  *
144408f90673Sjfb  * Free the contents of a delta structure.
144508f90673Sjfb  */
14467fe21e34Sjfb static void
rcs_freedelta(struct rcs_delta * rdp)144708f90673Sjfb rcs_freedelta(struct rcs_delta *rdp)
144808f90673Sjfb {
1449e0305c2eSjfb 	struct rcs_branch *rb;
145008f90673Sjfb 
145153ce2177Sfcambus 	free(rdp->rd_num);
145253ce2177Sfcambus 	free(rdp->rd_next);
1453397ddb8aSnicm 	free(rdp->rd_author);
1454397ddb8aSnicm 	free(rdp->rd_locker);
1455397ddb8aSnicm 	free(rdp->rd_state);
1456397ddb8aSnicm 	free(rdp->rd_log);
1457397ddb8aSnicm 	free(rdp->rd_text);
145808f90673Sjfb 
1459e0305c2eSjfb 	while ((rb = TAILQ_FIRST(&(rdp->rd_branches))) != NULL) {
1460e0305c2eSjfb 		TAILQ_REMOVE(&(rdp->rd_branches), rb, rb_list);
146153ce2177Sfcambus 		free(rb->rb_num);
1462397ddb8aSnicm 		free(rb);
1463e0305c2eSjfb 	}
1464e0305c2eSjfb 
1465397ddb8aSnicm 	free(rdp);
146608f90673Sjfb }
146708f90673Sjfb 
146808f90673Sjfb /*
1469163f330fSjfb  * rcs_strprint()
1470163f330fSjfb  *
1471163f330fSjfb  * Output an RCS string <str> of size <slen> to the stream <stream>.  Any
1472163f330fSjfb  * '@' characters are escaped.  Otherwise, the string can contain arbitrary
1473163f330fSjfb  * binary data.
1474163f330fSjfb  */
1475f240ac99Sray static void
rcs_strprint(const u_char * str,size_t slen,FILE * stream)1476163f330fSjfb rcs_strprint(const u_char *str, size_t slen, FILE *stream)
1477163f330fSjfb {
1478163f330fSjfb 	const u_char *ap, *ep, *sp;
1479163f330fSjfb 
148056bc38daSjfb 	if (slen == 0)
1481f240ac99Sray 		return;
148256bc38daSjfb 
1483163f330fSjfb 	ep = str + slen - 1;
1484163f330fSjfb 
1485163f330fSjfb 	for (sp = str; sp <= ep;)  {
1486163f330fSjfb 		ap = memchr(sp, '@', ep - sp);
1487163f330fSjfb 		if (ap == NULL)
1488163f330fSjfb 			ap = ep;
1489f240ac99Sray 		(void)fwrite(sp, sizeof(u_char), ap - sp + 1, stream);
1490163f330fSjfb 
1491163f330fSjfb 		if (*ap == '@')
1492163f330fSjfb 			putc('@', stream);
1493163f330fSjfb 		sp = ap + 1;
1494163f330fSjfb 	}
1495163f330fSjfb }
14963b63875eSjoris 
14973b63875eSjoris /*
1498f9b67873Sniallo  * rcs_deltatext_set()
1499f9b67873Sniallo  *
1500f9b67873Sniallo  * Set deltatext for <rev> in RCS file <rfp> to <dtext>
1501f9b67873Sniallo  * Returns -1 on error, 0 on success.
1502f9b67873Sniallo  */
1503f9b67873Sniallo int
rcs_deltatext_set(RCSFILE * rfp,RCSNUM * rev,BUF * bp)15044d33f394Sjoris rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, BUF *bp)
1505f9b67873Sniallo {
1506f9b67873Sniallo 	size_t len;
15074d33f394Sjoris 	u_char *dtext;
1508f9b67873Sniallo 	struct rcs_delta *rdp;
1509f9b67873Sniallo 
1510b71c7dfeSniallo 	/* Write operations require full parsing */
1511fead43dfStobias 	if (rcsparse_deltatexts(rfp, NULL))
1512fead43dfStobias 		return (-1);
1513b71c7dfeSniallo 
1514f9b67873Sniallo 	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1515f9b67873Sniallo 		return (-1);
1516f9b67873Sniallo 
1517397ddb8aSnicm 	free(rdp->rd_text);
1518f9b67873Sniallo 
15197bb3ddb0Sray 	len = buf_len(bp);
15207bb3ddb0Sray 	dtext = buf_release(bp);
15214d33f394Sjoris 	bp = NULL;
15224d33f394Sjoris 
152377cdcb05Sjoris 	if (len != 0) {
15244d33f394Sjoris 		rdp->rd_text = xmalloc(len);
1525f240ac99Sray 		rdp->rd_tlen = len;
15264d33f394Sjoris 		(void)memcpy(rdp->rd_text, dtext, len);
152777cdcb05Sjoris 	} else {
152877cdcb05Sjoris 		rdp->rd_text = NULL;
152977cdcb05Sjoris 		rdp->rd_tlen = 0;
153077cdcb05Sjoris 	}
1531f9b67873Sniallo 
1532397ddb8aSnicm 	free(dtext);
1533f9b67873Sniallo 	return (0);
1534f9b67873Sniallo }
1535e2cf1205Sjoris 
1536e2cf1205Sjoris /*
1537e2cf1205Sjoris  * rcs_rev_setlog()
1538e2cf1205Sjoris  *
15397a347c71Stobias  * Sets the log message of revision <rev> to <logtext>.
1540e2cf1205Sjoris  */
1541e2cf1205Sjoris int
rcs_rev_setlog(RCSFILE * rfp,RCSNUM * rev,const char * logtext)1542e2cf1205Sjoris rcs_rev_setlog(RCSFILE *rfp, RCSNUM *rev, const char *logtext)
1543e2cf1205Sjoris {
1544e2cf1205Sjoris 	struct rcs_delta *rdp;
1545e2cf1205Sjoris 
1546e2cf1205Sjoris 	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1547e2cf1205Sjoris 		return (-1);
1548e2cf1205Sjoris 
1549397ddb8aSnicm 	free(rdp->rd_log);
1550e2cf1205Sjoris 
15510450b43bSjoris 	rdp->rd_log = xstrdup(logtext);
1552e2cf1205Sjoris 	rfp->rf_flags &= ~RCS_SYNCED;
1553e2cf1205Sjoris 	return (0);
1554e2cf1205Sjoris }
1555c4521683Sniallo /*
1556c4521683Sniallo  * rcs_rev_getdate()
1557c4521683Sniallo  *
1558c4521683Sniallo  * Get the date corresponding to a given revision.
1559c4521683Sniallo  * Returns the date on success, -1 on failure.
1560c4521683Sniallo  */
1561c4521683Sniallo time_t
rcs_rev_getdate(RCSFILE * rfp,RCSNUM * rev)1562c4521683Sniallo rcs_rev_getdate(RCSFILE *rfp, RCSNUM *rev)
1563c4521683Sniallo {
1564c4521683Sniallo 	struct rcs_delta *rdp;
1565c4521683Sniallo 
1566c4521683Sniallo 	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1567c4521683Sniallo 		return (-1);
1568c4521683Sniallo 
156947e5fe63Sjoris 	return (timegm(&rdp->rd_date));
1570c4521683Sniallo }
15719aae8b12Sniallo 
15729aae8b12Sniallo /*
15739aae8b12Sniallo  * rcs_state_set()
15749aae8b12Sniallo  *
15759aae8b12Sniallo  * Sets the state of revision <rev> to <state>
15769aae8b12Sniallo  * NOTE: default state is 'Exp'. States may not contain spaces.
15779aae8b12Sniallo  *
15789aae8b12Sniallo  * Returns -1 on failure, 0 on success.
15799aae8b12Sniallo  */
15809aae8b12Sniallo int
rcs_state_set(RCSFILE * rfp,RCSNUM * rev,const char * state)15819aae8b12Sniallo rcs_state_set(RCSFILE *rfp, RCSNUM *rev, const char *state)
15829aae8b12Sniallo {
15839aae8b12Sniallo 	struct rcs_delta *rdp;
15849aae8b12Sniallo 
15859aae8b12Sniallo 	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
15869aae8b12Sniallo 		return (-1);
15879aae8b12Sniallo 
1588397ddb8aSnicm 	free(rdp->rd_state);
15899aae8b12Sniallo 
15900450b43bSjoris 	rdp->rd_state = xstrdup(state);
15919aae8b12Sniallo 
15929aae8b12Sniallo 	rfp->rf_flags &= ~RCS_SYNCED;
15939aae8b12Sniallo 
15949aae8b12Sniallo 	return (0);
15959aae8b12Sniallo }
15969aae8b12Sniallo 
15979aae8b12Sniallo /*
15989aae8b12Sniallo  * rcs_state_check()
15999aae8b12Sniallo  *
16009aae8b12Sniallo  * Check if string <state> is valid.
16019aae8b12Sniallo  *
16029aae8b12Sniallo  * Returns 0 if the string is valid, -1 otherwise.
16039aae8b12Sniallo  */
16049aae8b12Sniallo int
rcs_state_check(const char * state)16059aae8b12Sniallo rcs_state_check(const char *state)
16069aae8b12Sniallo {
1607feca7f66Stobias 	if (strcmp(state, RCS_STATE_DEAD) && strcmp(state, RCS_STATE_EXP))
16089aae8b12Sniallo 		return (-1);
16099aae8b12Sniallo 
16109aae8b12Sniallo 	return (0);
16119aae8b12Sniallo }
1612c4521683Sniallo 
1613c4521683Sniallo /*
1614c4521683Sniallo  * rcs_state_get()
1615c4521683Sniallo  *
1616c4521683Sniallo  * Get the state for a given revision of a specified RCSFILE.
1617c4521683Sniallo  *
1618c4521683Sniallo  * Returns NULL on failure.
1619c4521683Sniallo  */
1620c4521683Sniallo const char *
rcs_state_get(RCSFILE * rfp,RCSNUM * rev)1621c4521683Sniallo rcs_state_get(RCSFILE *rfp, RCSNUM *rev)
1622c4521683Sniallo {
1623c4521683Sniallo 	struct rcs_delta *rdp;
1624c4521683Sniallo 
1625c4521683Sniallo 	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1626c4521683Sniallo 		return (NULL);
1627c4521683Sniallo 
1628c4521683Sniallo 	return (rdp->rd_state);
1629c4521683Sniallo }
1630c4521683Sniallo 
1631113cb29fStobias /* rcs_get_revision() */
1632113cb29fStobias static RCSNUM *
rcs_get_revision(const char * revstr,RCSFILE * rfp)1633113cb29fStobias rcs_get_revision(const char *revstr, RCSFILE *rfp)
163445d60d54Sjoris {
1635ca2dc546Sniallo 	RCSNUM *rev, *brev, *frev;
163640e206ceSjoris 	struct rcs_branch *brp;
1637aa3964e7Sjoris 	struct rcs_delta *rdp;
1638ca2dc546Sniallo 	size_t i;
1639aa3964e7Sjoris 
1640aa3964e7Sjoris 	rdp = NULL;
164145d60d54Sjoris 
16425dd120b0Sjoris 	if (!strcmp(revstr, RCS_HEAD_BRANCH)) {
1643cd71f6bbStobias 		if (rfp->rf_head == NULL)
1644a37ec391Stobias 			return (NULL);
1645cd71f6bbStobias 
16465dd120b0Sjoris 		frev = rcsnum_alloc();
16475dd120b0Sjoris 		rcsnum_cpy(rfp->rf_head, frev, 0);
16485dd120b0Sjoris 		return (frev);
16495dd120b0Sjoris 	}
16505dd120b0Sjoris 
1651ca2dc546Sniallo 	/* Possibly we could be passed a version number */
1652b1989067Stobias 	if ((rev = rcsnum_parse(revstr)) != NULL) {
1653b1989067Stobias 		/* Do not return if it is not in RCS file */
1654a37ec391Stobias 		if ((rdp = rcs_findrev(rfp, rev)) != NULL)
1655a37ec391Stobias 			return (rev);
1656b1989067Stobias 	} else {
1657ca2dc546Sniallo 		/* More likely we will be passed a symbol */
165845d60d54Sjoris 		rev = rcs_sym_getrev(rfp, revstr);
1659b1989067Stobias 	}
1660aa3964e7Sjoris 
1661ca2dc546Sniallo 	if (rev == NULL)
1662b03d8731Stobias 		return (NULL);
166308458e59Sjoris 
1664f53e07caSjoris 	/*
1665f53e07caSjoris 	 * If it was not a branch, thats ok the symbolic
1666d9a51c35Sjmc 	 * name referred to a revision, so return the resolved
166791112048Sjoris 	 * revision for the given name. */
1668dd2f49b9Stobias 	if (!RCSNUM_ISBRANCH(rev)) {
166991112048Sjoris 		/* Sanity check: The first two elements of any
167091112048Sjoris 		 * revision (be it on a branch or on trunk) cannot
167191112048Sjoris 		 * be greater than HEAD.
167291112048Sjoris 		 *
167391112048Sjoris 		 * XXX: To avoid comparing to uninitialized memory,
167491112048Sjoris 		 * the minimum of both revision lengths is taken
167591112048Sjoris 		 * instead of just 2.
167691112048Sjoris 		 */
1677a37ec391Stobias 		if (rfp->rf_head == NULL || rcsnum_cmp(rev, rfp->rf_head,
1678b9fc9a72Sderaadt 		    MINIMUM(rfp->rf_head->rn_len, rev->rn_len)) < 0) {
167953ce2177Sfcambus 			free(rev);
1680a37ec391Stobias 			return (NULL);
1681dd2f49b9Stobias 		}
1682f53e07caSjoris 		return (rev);
1683dd2f49b9Stobias 	}
1684ca2dc546Sniallo 
1685ca2dc546Sniallo 	brev = rcsnum_alloc();
1686ca2dc546Sniallo 	rcsnum_cpy(rev, brev, rev->rn_len - 1);
1687ca2dc546Sniallo 
1688ca2dc546Sniallo 	if ((rdp = rcs_findrev(rfp, brev)) == NULL)
1689113cb29fStobias 		fatal("rcs_get_revision: tag `%s' does not exist", revstr);
169053ce2177Sfcambus 	free(brev);
1691ca2dc546Sniallo 
1692ca2dc546Sniallo 	TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
1693ca2dc546Sniallo 		for (i = 0; i < rev->rn_len; i++)
1694ca2dc546Sniallo 			if (brp->rb_num->rn_id[i] != rev->rn_id[i])
1695ca2dc546Sniallo 				break;
1696ca2dc546Sniallo 		if (i != rev->rn_len)
1697ca2dc546Sniallo 			continue;
1698ca2dc546Sniallo 		break;
1699ca2dc546Sniallo 	}
1700ca2dc546Sniallo 
170153ce2177Sfcambus 	free(rev);
1702ca2dc546Sniallo 	frev = rcsnum_alloc();
1703ca2dc546Sniallo 	if (brp == NULL) {
1704ca2dc546Sniallo 		rcsnum_cpy(rdp->rd_num, frev, 0);
1705ca2dc546Sniallo 		return (frev);
1706ca2dc546Sniallo 	} else {
1707ca2dc546Sniallo 		/* Fetch the delta with the correct branch num */
170808458e59Sjoris 		if ((rdp = rcs_findrev(rfp, brp->rb_num)) == NULL)
1709113cb29fStobias 			fatal("rcs_get_revision: could not fetch branch "
1710113cb29fStobias 			    "delta");
1711ca2dc546Sniallo 		rcsnum_cpy(rdp->rd_num, frev, 0);
1712ca2dc546Sniallo 		return (frev);
171340e206ceSjoris 	}
171445d60d54Sjoris }
17150dbb72f1Sniallo 
17160dbb72f1Sniallo /*
17170dbb72f1Sniallo  * rcs_rev_getlines()
17180dbb72f1Sniallo  *
171917b2872aStobias  * Get the entire contents of revision <frev> from the RCSFILE <rfp> and
17207bb3ddb0Sray  * return it as a pointer to a struct rcs_lines.
17210dbb72f1Sniallo  */
17227bb3ddb0Sray struct rcs_lines *
rcs_rev_getlines(RCSFILE * rfp,RCSNUM * frev,struct rcs_line *** alines)17237bb3ddb0Sray rcs_rev_getlines(RCSFILE *rfp, RCSNUM *frev, struct rcs_line ***alines)
17240dbb72f1Sniallo {
172590fdc30cSotto 	size_t plen;
17265e4c4390Stobias 	int annotate, done, i, nextroot;
17270dbb72f1Sniallo 	RCSNUM *tnum, *bnum;
17280dbb72f1Sniallo 	struct rcs_branch *brp;
17295e4c4390Stobias 	struct rcs_delta *hrdp, *prdp, *rdp, *trdp;
17300dbb72f1Sniallo 	u_char *patch;
17317bb3ddb0Sray 	struct rcs_line *line, *nline;
17327bb3ddb0Sray 	struct rcs_lines *dlines, *plines;
17330dbb72f1Sniallo 
1734bc415189Snicm 	hrdp = prdp = rdp = trdp = NULL;
1735bc415189Snicm 
1736e28eda4eStobias 	if (rfp->rf_head == NULL ||
1737e28eda4eStobias 	    (hrdp = rcs_findrev(rfp, rfp->rf_head)) == NULL)
17382700dfbcSjoris 		fatal("rcs_rev_getlines: no HEAD revision");
17390dbb72f1Sniallo 
17400dbb72f1Sniallo 	tnum = frev;
1741fead43dfStobias 	if (rcsparse_deltatexts(rfp, hrdp->rd_num))
1742fead43dfStobias 		fatal("rcs_rev_getlines: rcsparse_deltatexts");
17430dbb72f1Sniallo 
17440dbb72f1Sniallo 	/* revision on branch, get the branch root */
17450dbb72f1Sniallo 	nextroot = 2;
17460dbb72f1Sniallo 	bnum = rcsnum_alloc();
1747db6c4493Sjoris 	if (RCSNUM_ISBRANCHREV(tnum))
17480dbb72f1Sniallo 		rcsnum_cpy(tnum, bnum, nextroot);
1749db6c4493Sjoris 	else
1750db6c4493Sjoris 		rcsnum_cpy(tnum, bnum, tnum->rn_len);
17510dbb72f1Sniallo 
17525e4c4390Stobias 	if (alines != NULL) {
17535e4c4390Stobias 		/* start with annotate first at requested revision */
17545e4c4390Stobias 		annotate = ANNOTATE_LATER;
17555e4c4390Stobias 		*alines = NULL;
17565e4c4390Stobias 	} else
17575e4c4390Stobias 		annotate = ANNOTATE_NEVER;
17585e4c4390Stobias 
17590dbb72f1Sniallo 	dlines = cvs_splitlines(hrdp->rd_text, hrdp->rd_tlen);
17600dbb72f1Sniallo 
17610dbb72f1Sniallo 	done = 0;
17620dbb72f1Sniallo 
17630dbb72f1Sniallo 	rdp = hrdp;
17645e4c4390Stobias 	if (!rcsnum_differ(rdp->rd_num, bnum)) {
17655e4c4390Stobias 		if (annotate == ANNOTATE_LATER) {
17665e4c4390Stobias 			/* found requested revision for annotate */
17675e4c4390Stobias 			i = 0;
17685e4c4390Stobias 			TAILQ_FOREACH(line, &(dlines->l_lines), l_list) {
17695e4c4390Stobias 				line->l_lineno_orig = line->l_lineno;
17705e4c4390Stobias 				i++;
17715e4c4390Stobias 			}
17720dbb72f1Sniallo 
17737bb3ddb0Sray 			*alines = xcalloc(i + 1, sizeof(struct rcs_line *));
17745e4c4390Stobias 			(*alines)[i] = NULL;
17755e4c4390Stobias 			annotate = ANNOTATE_NOW;
17765e4c4390Stobias 
17775e4c4390Stobias 			/* annotate down to 1.1 from where we are */
177853ce2177Sfcambus 			free(bnum);
17795e4c4390Stobias 			bnum = rcsnum_parse("1.1");
17805e4c4390Stobias 			if (!rcsnum_differ(rdp->rd_num, bnum)) {
17815e4c4390Stobias 				goto next;
17825e4c4390Stobias 			}
17835e4c4390Stobias 		} else
17845e4c4390Stobias 			goto next;
17855e4c4390Stobias 	}
17865e4c4390Stobias 
17875e4c4390Stobias 	prdp = hrdp;
17880dbb72f1Sniallo 	if ((rdp = rcs_findrev(rfp, hrdp->rd_next)) == NULL)
17890dbb72f1Sniallo 		goto done;
17900dbb72f1Sniallo 
17910dbb72f1Sniallo again:
179247cd82aeSmillert 	while (rdp != NULL) {
17930dbb72f1Sniallo 		if (rdp->rd_next->rn_len != 0) {
17940dbb72f1Sniallo 			trdp = rcs_findrev(rfp, rdp->rd_next);
17950dbb72f1Sniallo 			if (trdp == NULL)
17960dbb72f1Sniallo 				fatal("failed to grab next revision");
17970dbb72f1Sniallo 		}
17980dbb72f1Sniallo 
17990dbb72f1Sniallo 		if (rdp->rd_tlen == 0) {
1800fead43dfStobias 			if (rcsparse_deltatexts(rfp, rdp->rd_num))
1801fead43dfStobias 				fatal("rcs_rev_getlines: rcsparse_deltatexts");
18020dbb72f1Sniallo 			if (rdp->rd_tlen == 0) {
18030dbb72f1Sniallo 				if (!rcsnum_differ(rdp->rd_num, bnum))
18040dbb72f1Sniallo 					break;
18050dbb72f1Sniallo 				rdp = trdp;
18060dbb72f1Sniallo 				continue;
18070dbb72f1Sniallo 			}
18080dbb72f1Sniallo 		}
18090dbb72f1Sniallo 
18100dbb72f1Sniallo 		plen = rdp->rd_tlen;
18110dbb72f1Sniallo 		patch = rdp->rd_text;
18120dbb72f1Sniallo 		plines = cvs_splitlines(patch, plen);
18135e4c4390Stobias 		if (annotate == ANNOTATE_NOW)
18145e4c4390Stobias 			rcs_patch_lines(dlines, plines, *alines, prdp);
18155e4c4390Stobias 		else
18165e4c4390Stobias 			rcs_patch_lines(dlines, plines, NULL, NULL);
18170dbb72f1Sniallo 		cvs_freelines(plines);
18180dbb72f1Sniallo 
18195e4c4390Stobias 		if (!rcsnum_differ(rdp->rd_num, bnum)) {
18205e4c4390Stobias 			if (annotate != ANNOTATE_LATER)
18215e4c4390Stobias 				break;
18225e4c4390Stobias 
18235e4c4390Stobias 			/* found requested revision for annotate */
18245e4c4390Stobias 			i = 0;
18255e4c4390Stobias 			TAILQ_FOREACH(line, &(dlines->l_lines), l_list) {
18265e4c4390Stobias 				line->l_lineno_orig = line->l_lineno;
18275e4c4390Stobias 				i++;
18285e4c4390Stobias 			}
18295e4c4390Stobias 
18307bb3ddb0Sray 			*alines = xcalloc(i + 1, sizeof(struct rcs_line *));
18315e4c4390Stobias 			(*alines)[i] = NULL;
18325e4c4390Stobias 			annotate = ANNOTATE_NOW;
18335e4c4390Stobias 
18345e4c4390Stobias 			/* annotate down to 1.1 from where we are */
183553ce2177Sfcambus 			free(bnum);
18365e4c4390Stobias 			bnum = rcsnum_parse("1.1");
18375e4c4390Stobias 
18380dbb72f1Sniallo 			if (!rcsnum_differ(rdp->rd_num, bnum))
18390dbb72f1Sniallo 				break;
18405e4c4390Stobias 		}
18410dbb72f1Sniallo 
18425e4c4390Stobias 		prdp = rdp;
18430dbb72f1Sniallo 		rdp = trdp;
18440dbb72f1Sniallo 	}
18450dbb72f1Sniallo 
18460dbb72f1Sniallo next:
184747cd82aeSmillert 	if (rdp == NULL || !rcsnum_differ(rdp->rd_num, frev))
18480dbb72f1Sniallo 		done = 1;
18490dbb72f1Sniallo 
18500dbb72f1Sniallo 	if (RCSNUM_ISBRANCHREV(frev) && done != 1) {
18510dbb72f1Sniallo 		nextroot += 2;
18520dbb72f1Sniallo 		rcsnum_cpy(frev, bnum, nextroot);
18530dbb72f1Sniallo 
18540dbb72f1Sniallo 		TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
1855ca2dc546Sniallo 			for (i = 0; i < nextroot - 1; i++)
1856ca2dc546Sniallo 				if (brp->rb_num->rn_id[i] != bnum->rn_id[i])
18570dbb72f1Sniallo 					break;
1858ca2dc546Sniallo 			if (i == nextroot - 1)
18590dbb72f1Sniallo 				break;
18600dbb72f1Sniallo 		}
18610dbb72f1Sniallo 
18625e4c4390Stobias 		if (brp == NULL) {
18635e4c4390Stobias 			if (annotate != ANNOTATE_NEVER) {
1864397ddb8aSnicm 				free(*alines);
18655e4c4390Stobias 				*alines = NULL;
18665e4c4390Stobias 				cvs_freelines(dlines);
186753ce2177Sfcambus 				free(bnum);
18685e4c4390Stobias 				return (NULL);
18695e4c4390Stobias 			}
18700dbb72f1Sniallo 			fatal("expected branch not found on branch list");
18715e4c4390Stobias 		}
18720dbb72f1Sniallo 
18730dbb72f1Sniallo 		if ((rdp = rcs_findrev(rfp, brp->rb_num)) == NULL)
18742700dfbcSjoris 			fatal("rcs_rev_getlines: failed to get delta for target rev");
18750dbb72f1Sniallo 
18760dbb72f1Sniallo 		goto again;
18770dbb72f1Sniallo 	}
18780dbb72f1Sniallo done:
187937cbc181Stobias 	/* put remaining lines into annotate buffer */
18805e4c4390Stobias 	if (annotate == ANNOTATE_NOW) {
18815e4c4390Stobias 		for (line = TAILQ_FIRST(&(dlines->l_lines));
18825e4c4390Stobias 		    line != NULL; line = nline) {
18835e4c4390Stobias 			nline = TAILQ_NEXT(line, l_list);
18845e4c4390Stobias 			TAILQ_REMOVE(&(dlines->l_lines), line, l_list);
18855e4c4390Stobias 			if (line->l_line == NULL) {
1886397ddb8aSnicm 				free(line);
18875e4c4390Stobias 				continue;
18885e4c4390Stobias 			}
18895e4c4390Stobias 
18905e4c4390Stobias 			line->l_delta = rdp;
18915e4c4390Stobias 			(*alines)[line->l_lineno_orig - 1] = line;
18925e4c4390Stobias 		}
18935e4c4390Stobias 
18945e4c4390Stobias 		cvs_freelines(dlines);
18955e4c4390Stobias 		dlines = NULL;
18965e4c4390Stobias 	}
18975e4c4390Stobias 
18980dbb72f1Sniallo 	if (bnum != tnum)
189953ce2177Sfcambus 		free(bnum);
19000dbb72f1Sniallo 
19010dbb72f1Sniallo 	return (dlines);
19020dbb72f1Sniallo }
19030dbb72f1Sniallo 
190437cbc181Stobias void
rcs_annotate_getlines(RCSFILE * rfp,RCSNUM * frev,struct rcs_line *** alines)19057bb3ddb0Sray rcs_annotate_getlines(RCSFILE *rfp, RCSNUM *frev, struct rcs_line ***alines)
190637cbc181Stobias {
190737cbc181Stobias 	size_t plen;
190837cbc181Stobias 	int i, nextroot;
190937cbc181Stobias 	RCSNUM *bnum;
191037cbc181Stobias 	struct rcs_branch *brp;
191137cbc181Stobias 	struct rcs_delta *rdp, *trdp;
191237cbc181Stobias 	u_char *patch;
19137bb3ddb0Sray 	struct rcs_line *line;
19147bb3ddb0Sray 	struct rcs_lines *dlines, *plines;
191537cbc181Stobias 
1916bc415189Snicm 	rdp = trdp = NULL;
1917bc415189Snicm 
191837cbc181Stobias 	if (!RCSNUM_ISBRANCHREV(frev))
191937cbc181Stobias 		fatal("rcs_annotate_getlines: branch revision expected");
192037cbc181Stobias 
192137cbc181Stobias 	/* revision on branch, get the branch root */
192237cbc181Stobias 	nextroot = 2;
192337cbc181Stobias 	bnum = rcsnum_alloc();
192437cbc181Stobias 	rcsnum_cpy(frev, bnum, nextroot);
192537cbc181Stobias 
192637cbc181Stobias 	/*
192737cbc181Stobias 	 * Going from HEAD to 1.1 enables the use of an array, which is
192837cbc181Stobias 	 * much faster. Unfortunately this is not possible with branch
192937cbc181Stobias 	 * revisions, so copy over our alines (array) into dlines (tailq).
193037cbc181Stobias 	 */
193137cbc181Stobias 	dlines = xcalloc(1, sizeof(*dlines));
193237cbc181Stobias 	TAILQ_INIT(&(dlines->l_lines));
193337cbc181Stobias 	line = xcalloc(1, sizeof(*line));
193437cbc181Stobias 	TAILQ_INSERT_TAIL(&(dlines->l_lines), line, l_list);
193537cbc181Stobias 
193637cbc181Stobias 	for (i = 0; (*alines)[i] != NULL; i++) {
193737cbc181Stobias 		line = (*alines)[i];
193837cbc181Stobias 		line->l_lineno = i + 1;
193937cbc181Stobias 		TAILQ_INSERT_TAIL(&(dlines->l_lines), line, l_list);
194037cbc181Stobias 	}
194137cbc181Stobias 
194237cbc181Stobias 	rdp = rcs_findrev(rfp, bnum);
194337cbc181Stobias 	if (rdp == NULL)
194437cbc181Stobias 		fatal("failed to grab branch root revision");
194537cbc181Stobias 
194637cbc181Stobias 	do {
194737cbc181Stobias 		nextroot += 2;
194837cbc181Stobias 		rcsnum_cpy(frev, bnum, nextroot);
194937cbc181Stobias 
195037cbc181Stobias 		TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
195137cbc181Stobias 			for (i = 0; i < nextroot - 1; i++)
195237cbc181Stobias 				if (brp->rb_num->rn_id[i] != bnum->rn_id[i])
195337cbc181Stobias 					break;
195437cbc181Stobias 			if (i == nextroot - 1)
195537cbc181Stobias 				break;
195637cbc181Stobias 		}
195737cbc181Stobias 
195837cbc181Stobias 		if (brp == NULL)
195937cbc181Stobias 			fatal("expected branch not found on branch list");
196037cbc181Stobias 
196137cbc181Stobias 		if ((rdp = rcs_findrev(rfp, brp->rb_num)) == NULL)
196237cbc181Stobias 			fatal("failed to get delta for target rev");
196337cbc181Stobias 
196437cbc181Stobias 		for (;;) {
196537cbc181Stobias 			if (rdp->rd_next->rn_len != 0) {
196637cbc181Stobias 				trdp = rcs_findrev(rfp, rdp->rd_next);
196737cbc181Stobias 				if (trdp == NULL)
196837cbc181Stobias 					fatal("failed to grab next revision");
196937cbc181Stobias 			}
197037cbc181Stobias 
197137cbc181Stobias 			if (rdp->rd_tlen == 0) {
1972fead43dfStobias 				if (rcsparse_deltatexts(rfp, rdp->rd_num))
1973fead43dfStobias 					fatal("rcs_annotate_getlines: "
1974fead43dfStobias 					    "rcsparse_deltatexts");
197537cbc181Stobias 				if (rdp->rd_tlen == 0) {
197637cbc181Stobias 					if (!rcsnum_differ(rdp->rd_num, bnum))
197737cbc181Stobias 						break;
197837cbc181Stobias 					rdp = trdp;
197937cbc181Stobias 					continue;
198037cbc181Stobias 				}
198137cbc181Stobias 			}
198237cbc181Stobias 
198337cbc181Stobias 			plen = rdp->rd_tlen;
198437cbc181Stobias 			patch = rdp->rd_text;
198537cbc181Stobias 			plines = cvs_splitlines(patch, plen);
198637cbc181Stobias 			rcs_patch_lines(dlines, plines, NULL, rdp);
198737cbc181Stobias 			cvs_freelines(plines);
198837cbc181Stobias 
198937cbc181Stobias 			if (!rcsnum_differ(rdp->rd_num, bnum))
199037cbc181Stobias 				break;
199137cbc181Stobias 
199237cbc181Stobias 			rdp = trdp;
199337cbc181Stobias 		}
199437cbc181Stobias 	} while (rcsnum_differ(rdp->rd_num, frev));
199537cbc181Stobias 
199637cbc181Stobias 	if (bnum != frev)
199753ce2177Sfcambus 		free(bnum);
199837cbc181Stobias 
199937cbc181Stobias 	/*
200037cbc181Stobias 	 * All lines have been parsed, now they must be copied over
200137cbc181Stobias 	 * into alines (array) again.
200237cbc181Stobias 	 */
2003397ddb8aSnicm 	free(*alines);
200437cbc181Stobias 
200537cbc181Stobias 	i = 0;
200637cbc181Stobias 	TAILQ_FOREACH(line, &(dlines->l_lines), l_list) {
200737cbc181Stobias 		if (line->l_line != NULL)
200837cbc181Stobias 			i++;
200937cbc181Stobias 	}
20107bb3ddb0Sray 	*alines = xcalloc(i + 1, sizeof(struct rcs_line *));
201137cbc181Stobias 	(*alines)[i] = NULL;
201237cbc181Stobias 
201337cbc181Stobias 	i = 0;
201437cbc181Stobias 	TAILQ_FOREACH(line, &(dlines->l_lines), l_list) {
201537cbc181Stobias 		if (line->l_line != NULL)
201637cbc181Stobias 			(*alines)[i++] = line;
201737cbc181Stobias 	}
201837cbc181Stobias }
201937cbc181Stobias 
20200dbb72f1Sniallo /*
20210dbb72f1Sniallo  * rcs_rev_getbuf()
20220dbb72f1Sniallo  *
20230dbb72f1Sniallo  * XXX: This is really really slow and should be avoided if at all possible!
20240dbb72f1Sniallo  *
20250dbb72f1Sniallo  * Get the entire contents of revision <rev> from the RCSFILE <rfp> and
20260dbb72f1Sniallo  * return it as a BUF pointer.
20270dbb72f1Sniallo  */
20280dbb72f1Sniallo BUF *
rcs_rev_getbuf(RCSFILE * rfp,RCSNUM * rev,int mode)2029ff7b57e3Sjoris rcs_rev_getbuf(RCSFILE *rfp, RCSNUM *rev, int mode)
20300dbb72f1Sniallo {
2031ff7b57e3Sjoris 	int expmode, expand;
2032ff7b57e3Sjoris 	struct rcs_delta *rdp;
20337bb3ddb0Sray 	struct rcs_lines *lines;
20347bb3ddb0Sray 	struct rcs_line *lp, *nlp;
20350dbb72f1Sniallo 	BUF *bp;
20360dbb72f1Sniallo 
2037bc415189Snicm 	rdp = NULL;
2038bc415189Snicm 	expmode = RCS_KWEXP_NONE;
2039ff7b57e3Sjoris 	expand = 0;
20405e4c4390Stobias 	lines = rcs_rev_getlines(rfp, rev, NULL);
20417bb3ddb0Sray 	bp = buf_alloc(1024 * 16);
2042ff7b57e3Sjoris 
2043ff7b57e3Sjoris 	if (!(mode & RCS_KWEXP_NONE)) {
2044ff7b57e3Sjoris 		expmode = rcs_kwexp_get(rfp);
2045ff7b57e3Sjoris 
2046ff7b57e3Sjoris 		if (!(expmode & RCS_KWEXP_NONE)) {
204747cd82aeSmillert 			if ((rdp = rcs_findrev(rfp, rev)) == NULL) {
204847cd82aeSmillert 				char version[RCSNUM_MAXSTR];
204947cd82aeSmillert 
205047cd82aeSmillert 				rcsnum_tostr(rev, version, sizeof(version));
205147cd82aeSmillert 				fatal("could not find desired version %s in %s",
205247cd82aeSmillert 				    version, rfp->rf_path);
205347cd82aeSmillert 			}
205447cd82aeSmillert 
2055ff7b57e3Sjoris 			expand = 1;
2056ff7b57e3Sjoris 		}
2057ff7b57e3Sjoris 	}
2058ff7b57e3Sjoris 
2059364cb4c9Stobias 	for (lp = TAILQ_FIRST(&lines->l_lines); lp != NULL;) {
2060364cb4c9Stobias 		nlp = TAILQ_NEXT(lp, l_list);
2061364cb4c9Stobias 
2062364cb4c9Stobias 		if (lp->l_line == NULL) {
2063364cb4c9Stobias 			lp = nlp;
20640dbb72f1Sniallo 			continue;
2065364cb4c9Stobias 		}
2066ff7b57e3Sjoris 
2067ff7b57e3Sjoris 		if (expand)
2068364cb4c9Stobias 			rcs_kwexp_line(rfp->rf_path, rdp, lines, lp, expmode);
2069ff7b57e3Sjoris 
2070364cb4c9Stobias 		do {
20717bb3ddb0Sray 			buf_append(bp, lp->l_line, lp->l_len);
2072364cb4c9Stobias 		} while ((lp = TAILQ_NEXT(lp, l_list)) != nlp);
20730dbb72f1Sniallo 	}
20740dbb72f1Sniallo 
20750dbb72f1Sniallo 	cvs_freelines(lines);
20760dbb72f1Sniallo 
20770dbb72f1Sniallo 	return (bp);
20780dbb72f1Sniallo }
20790dbb72f1Sniallo 
20800dbb72f1Sniallo /*
20810dbb72f1Sniallo  * rcs_rev_write_fd()
20820dbb72f1Sniallo  *
20830dbb72f1Sniallo  * Write the entire contents of revision <frev> from the rcsfile <rfp> to
20840dbb72f1Sniallo  * file descriptor <fd>.
20850dbb72f1Sniallo  */
20860dbb72f1Sniallo void
rcs_rev_write_fd(RCSFILE * rfp,RCSNUM * rev,int _fd,int mode)20877c1a09a6Sjoris rcs_rev_write_fd(RCSFILE *rfp, RCSNUM *rev, int _fd, int mode)
20880dbb72f1Sniallo {
20897c1a09a6Sjoris 	int fd;
20907c1a09a6Sjoris 	FILE *fp;
20917c1a09a6Sjoris 	size_t ret;
2092ff7b57e3Sjoris 	int expmode, expand;
20930dbb72f1Sniallo 	struct rcs_delta *rdp;
20947bb3ddb0Sray 	struct rcs_lines *lines;
20957bb3ddb0Sray 	struct rcs_line *lp, *nlp;
20967ac78d1dSjoris 	extern int print_stdout;
20970dbb72f1Sniallo 
2098bc415189Snicm 	rdp = NULL;
2099bc415189Snicm 	expmode = RCS_KWEXP_NONE;
2100ff7b57e3Sjoris 	expand = 0;
21015e4c4390Stobias 	lines = rcs_rev_getlines(rfp, rev, NULL);
2102ff7b57e3Sjoris 
21030dbb72f1Sniallo 	if (!(mode & RCS_KWEXP_NONE)) {
21040dbb72f1Sniallo 		expmode = rcs_kwexp_get(rfp);
21050dbb72f1Sniallo 
21060dbb72f1Sniallo 		if (!(expmode & RCS_KWEXP_NONE)) {
21070dbb72f1Sniallo 			if ((rdp = rcs_findrev(rfp, rev)) == NULL)
21080dbb72f1Sniallo 				fatal("could not fetch revision");
2109ff7b57e3Sjoris 			expand = 1;
21100dbb72f1Sniallo 		}
21110dbb72f1Sniallo 	}
2112ff7b57e3Sjoris 
21137c1a09a6Sjoris 	fd = dup(_fd);
21147c1a09a6Sjoris 	if (fd == -1)
21157c1a09a6Sjoris 		fatal("rcs_rev_write_fd: dup: %s", strerror(errno));
21167c1a09a6Sjoris 
21177c1a09a6Sjoris 	if ((fp = fdopen(fd, "w")) == NULL)
21187c1a09a6Sjoris 		fatal("rcs_rev_write_fd: fdopen: %s", strerror(errno));
21197c1a09a6Sjoris 
2120364cb4c9Stobias 	for (lp = TAILQ_FIRST(&lines->l_lines); lp != NULL;) {
2121364cb4c9Stobias 		nlp = TAILQ_NEXT(lp, l_list);
2122364cb4c9Stobias 
2123364cb4c9Stobias 		if (lp->l_line == NULL) {
2124364cb4c9Stobias 			lp = nlp;
21250dbb72f1Sniallo 			continue;
2126364cb4c9Stobias 		}
2127ff7b57e3Sjoris 
2128ff7b57e3Sjoris 		if (expand)
2129364cb4c9Stobias 			rcs_kwexp_line(rfp->rf_path, rdp, lines, lp, expmode);
2130ff7b57e3Sjoris 
2131364cb4c9Stobias 		do {
21327ac78d1dSjoris 			/*
21337ac78d1dSjoris 			 * Solely for the checkout and update -p options.
21347ac78d1dSjoris 			 */
21357ac78d1dSjoris 			if (cvs_server_active == 1 &&
21367ac78d1dSjoris 			    (cvs_cmdop == CVS_OP_CHECKOUT ||
21377ac78d1dSjoris 			    cvs_cmdop == CVS_OP_UPDATE) && print_stdout == 1) {
21387c1a09a6Sjoris 				ret = fwrite("M ", 1, 2, fp);
21397c1a09a6Sjoris 				if (ret != 2)
2140364cb4c9Stobias 					fatal("rcs_rev_write_fd: %s",
2141364cb4c9Stobias 					    strerror(errno));
21427ac78d1dSjoris 			}
21437ac78d1dSjoris 
21447c1a09a6Sjoris 			ret = fwrite(lp->l_line, 1, lp->l_len, fp);
21457c1a09a6Sjoris 			if (ret != lp->l_len)
21460dbb72f1Sniallo 				fatal("rcs_rev_write_fd: %s", strerror(errno));
2147364cb4c9Stobias 		} while ((lp = TAILQ_NEXT(lp, l_list)) != nlp);
21480dbb72f1Sniallo 	}
21490dbb72f1Sniallo 
21500dbb72f1Sniallo 	cvs_freelines(lines);
21517c1a09a6Sjoris 	(void)fclose(fp);
21520dbb72f1Sniallo }
21530dbb72f1Sniallo 
21540dbb72f1Sniallo /*
21550dbb72f1Sniallo  * rcs_rev_write_stmp()
21560dbb72f1Sniallo  *
21570dbb72f1Sniallo  * Write the contents of the rev <rev> to a temporary file whose path is
21580dbb72f1Sniallo  * specified using <template> (see mkstemp(3)). NB. This function will modify
21590dbb72f1Sniallo  * <template>, as per mkstemp.
21600dbb72f1Sniallo  */
21612e0d696aSjoris int
rcs_rev_write_stmp(RCSFILE * rfp,RCSNUM * rev,char * template,int mode)21620dbb72f1Sniallo rcs_rev_write_stmp(RCSFILE *rfp,  RCSNUM *rev, char *template, int mode)
21630dbb72f1Sniallo {
21640dbb72f1Sniallo 	int fd;
21650dbb72f1Sniallo 
21660dbb72f1Sniallo 	if ((fd = mkstemp(template)) == -1)
21670dbb72f1Sniallo 		fatal("mkstemp: `%s': %s", template, strerror(errno));
21680dbb72f1Sniallo 
21697a9e6d11Sray 	worklist_add(template, &temp_files);
21700dbb72f1Sniallo 	rcs_rev_write_fd(rfp, rev, fd, mode);
21710dbb72f1Sniallo 
21723aaa63ebSderaadt 	if (lseek(fd, 0, SEEK_SET) == -1)
21732e0d696aSjoris 		fatal("rcs_rev_write_stmp: lseek: %s", strerror(errno));
21742e0d696aSjoris 
21752e0d696aSjoris 	return (fd);
21760dbb72f1Sniallo }
21770dbb72f1Sniallo 
21780dbb72f1Sniallo static void
rcs_kwexp_line(char * rcsfile,struct rcs_delta * rdp,struct rcs_lines * lines,struct rcs_line * line,int mode)21797bb3ddb0Sray rcs_kwexp_line(char *rcsfile, struct rcs_delta *rdp, struct rcs_lines *lines,
21807bb3ddb0Sray     struct rcs_line *line, int mode)
21810dbb72f1Sniallo {
2182d0e85bc8Stobias 	BUF *tmpbuf;
21830dbb72f1Sniallo 	int kwtype;
21840dbb72f1Sniallo 	u_int j, found;
218590fdc30cSotto 	const u_char *c, *start, *fin, *end;
2186431378d1Snaddy 	char *kwstr, *rcsfile_basename;
2187431378d1Snaddy 	char expbuf[256], buf[256], path[PATH_MAX];
2188d0e85bc8Stobias 	size_t clen, kwlen, len, tlen;
21890dbb72f1Sniallo 
21900dbb72f1Sniallo 	kwtype = 0;
21910dbb72f1Sniallo 	kwstr = NULL;
21920dbb72f1Sniallo 
219384355cd7Stobias 	if (mode & RCS_KWEXP_OLD)
219484355cd7Stobias 		return;
219584355cd7Stobias 
21960dbb72f1Sniallo 	len = line->l_len;
21970dbb72f1Sniallo 	if (len == 0)
21980dbb72f1Sniallo 		return;
21990dbb72f1Sniallo 
22000dbb72f1Sniallo 	c = line->l_line;
22010dbb72f1Sniallo 	found = 0;
22020dbb72f1Sniallo 	/* Final character in buffer. */
22030dbb72f1Sniallo 	fin = c + len - 1;
22040dbb72f1Sniallo 
2205431378d1Snaddy 	if (strlcpy(path, rcsfile, sizeof(path)) >= sizeof(path))
2206431378d1Snaddy 		fatal("rcs_kwexp_line: truncation");
2207431378d1Snaddy 	rcsfile_basename = basename(path);
2208431378d1Snaddy 
22090dbb72f1Sniallo 	/*
22100dbb72f1Sniallo 	 * Keyword formats:
22110dbb72f1Sniallo 	 * $Keyword$
22120dbb72f1Sniallo 	 * $Keyword: value$
22130dbb72f1Sniallo 	 */
22140dbb72f1Sniallo 	for (; c < fin; c++) {
2215d0e85bc8Stobias 		if (*c != '$')
2216d0e85bc8Stobias 			continue;
22170dbb72f1Sniallo 
22180dbb72f1Sniallo 		/* remember start of this possible keyword */
22190dbb72f1Sniallo 		start = c;
22200dbb72f1Sniallo 
22210dbb72f1Sniallo 		/* first following character has to be alphanumeric */
22220dbb72f1Sniallo 		c++;
22230dbb72f1Sniallo 		if (!isalpha(*c)) {
22240dbb72f1Sniallo 			c = start;
22250dbb72f1Sniallo 			continue;
22260dbb72f1Sniallo 		}
22270dbb72f1Sniallo 
22280dbb72f1Sniallo 		/* Number of characters between c and fin, inclusive. */
22290dbb72f1Sniallo 		clen = fin - c + 1;
22300dbb72f1Sniallo 
22310dbb72f1Sniallo 		/* look for any matching keywords */
22320dbb72f1Sniallo 		found = 0;
22330dbb72f1Sniallo 		for (j = 0; j < RCS_NKWORDS; j++) {
22340dbb72f1Sniallo 			kwlen = strlen(rcs_expkw[j].kw_str);
22350dbb72f1Sniallo 			/*
22360dbb72f1Sniallo 			 * kwlen must be less than clen since clen
22370dbb72f1Sniallo 			 * includes either a terminating `$' or a `:'.
22380dbb72f1Sniallo 			 */
22390dbb72f1Sniallo 			if (kwlen < clen &&
22400dbb72f1Sniallo 			    memcmp(c, rcs_expkw[j].kw_str, kwlen) == 0 &&
22410dbb72f1Sniallo 			    (c[kwlen] == '$' || c[kwlen] == ':')) {
22420dbb72f1Sniallo 				found = 1;
22430dbb72f1Sniallo 				kwstr = rcs_expkw[j].kw_str;
22440dbb72f1Sniallo 				kwtype = rcs_expkw[j].kw_type;
22450dbb72f1Sniallo 				c += kwlen;
22460dbb72f1Sniallo 				break;
22470dbb72f1Sniallo 			}
22480dbb72f1Sniallo 		}
22490dbb72f1Sniallo 
22502700dfbcSjoris 		if (found == 0 && cvs_tagname != NULL) {
22512700dfbcSjoris 			kwlen = strlen(cvs_tagname);
22522700dfbcSjoris 			if (kwlen < clen &&
22532700dfbcSjoris 			    memcmp(c, cvs_tagname, kwlen) == 0 &&
22542700dfbcSjoris 			    (c[kwlen] == '$' || c[kwlen] == ':')) {
22552700dfbcSjoris 				found = 1;
22562700dfbcSjoris 				kwstr = cvs_tagname;
22572700dfbcSjoris 				kwtype = RCS_KW_ID;
22582700dfbcSjoris 				c += kwlen;
22592700dfbcSjoris 			}
22602700dfbcSjoris 		}
22612700dfbcSjoris 
22620dbb72f1Sniallo 		/* unknown keyword, continue looking */
22630dbb72f1Sniallo 		if (found == 0) {
22640dbb72f1Sniallo 			c = start;
22650dbb72f1Sniallo 			continue;
22660dbb72f1Sniallo 		}
22670dbb72f1Sniallo 
22680dbb72f1Sniallo 		/*
22690dbb72f1Sniallo 		 * if the next character was ':' we need to look for
22700dbb72f1Sniallo 		 * an '$' before the end of the line to be sure it is
22710dbb72f1Sniallo 		 * in fact a keyword.
22720dbb72f1Sniallo 		 */
22730dbb72f1Sniallo 		if (*c == ':') {
22740dbb72f1Sniallo 			for (; c <= fin; ++c) {
22750dbb72f1Sniallo 				if (*c == '$' || *c == '\n')
22760dbb72f1Sniallo 					break;
22770dbb72f1Sniallo 			}
22780dbb72f1Sniallo 
22790dbb72f1Sniallo 			if (*c != '$') {
22800dbb72f1Sniallo 				c = start;
22810dbb72f1Sniallo 				continue;
22820dbb72f1Sniallo 			}
22830dbb72f1Sniallo 		}
22840dbb72f1Sniallo 		end = c + 1;
22850dbb72f1Sniallo 
22860dbb72f1Sniallo 		/* start constructing the expansion */
22870dbb72f1Sniallo 		expbuf[0] = '\0';
22880dbb72f1Sniallo 
22890dbb72f1Sniallo 		if (mode & RCS_KWEXP_NAME) {
2290d0e85bc8Stobias 			if (strlcat(expbuf, "$", sizeof(expbuf)) >=
2291d0e85bc8Stobias 			    sizeof(expbuf) || strlcat(expbuf, kwstr,
2292d0e85bc8Stobias 			    sizeof(expbuf)) >= sizeof(expbuf))
2293ff7b57e3Sjoris 				fatal("rcs_kwexp_line: truncated");
22940dbb72f1Sniallo 			if ((mode & RCS_KWEXP_VAL) &&
2295d0e85bc8Stobias 			    strlcat(expbuf, ": ", sizeof(expbuf)) >=
2296d0e85bc8Stobias 			    sizeof(expbuf))
2297ff7b57e3Sjoris 				fatal("rcs_kwexp_line: truncated");
22980dbb72f1Sniallo 		}
22990dbb72f1Sniallo 
23000dbb72f1Sniallo 		/*
23010dbb72f1Sniallo 		 * order matters because of RCS_KW_ID and
23020dbb72f1Sniallo 		 * RCS_KW_HEADER here
23030dbb72f1Sniallo 		 */
23040dbb72f1Sniallo 		if (mode & RCS_KWEXP_VAL) {
23050dbb72f1Sniallo 			if (kwtype & RCS_KW_RCSFILE) {
23060dbb72f1Sniallo 				if (!(kwtype & RCS_KW_FULLPATH))
2307431378d1Snaddy 					(void)strlcat(expbuf, rcsfile_basename,
2308d0e85bc8Stobias 					    sizeof(expbuf));
23090dbb72f1Sniallo 				else
2310d0e85bc8Stobias 					(void)strlcat(expbuf, rcsfile,
2311d0e85bc8Stobias 					    sizeof(expbuf));
2312d0e85bc8Stobias 				if (strlcat(expbuf, " ", sizeof(expbuf)) >=
2313d0e85bc8Stobias 				    sizeof(expbuf))
2314ff7b57e3Sjoris 					fatal("rcs_kwexp_line: truncated");
23150dbb72f1Sniallo 			}
23160dbb72f1Sniallo 
23170dbb72f1Sniallo 			if (kwtype & RCS_KW_REVISION) {
23180dbb72f1Sniallo 				rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
2319d0e85bc8Stobias 				if (strlcat(buf, " ", sizeof(buf)) >=
2320d0e85bc8Stobias 				    sizeof(buf) || strlcat(expbuf, buf,
2321d0e85bc8Stobias 				    sizeof(expbuf)) >= sizeof(buf))
2322ff7b57e3Sjoris 					fatal("rcs_kwexp_line: truncated");
23230dbb72f1Sniallo 			}
23240dbb72f1Sniallo 
23250dbb72f1Sniallo 			if (kwtype & RCS_KW_DATE) {
232622872efdScanacar 				if (strftime(buf, sizeof(buf),
232722872efdScanacar 				    "%Y/%m/%d %H:%M:%S ",
2328d0e85bc8Stobias 				    &rdp->rd_date) == 0)
2329d0e85bc8Stobias 					fatal("rcs_kwexp_line: strftime "
2330d0e85bc8Stobias 					    "failure");
2331d0e85bc8Stobias 				if (strlcat(expbuf, buf, sizeof(expbuf)) >=
2332d0e85bc8Stobias 				    sizeof(expbuf))
2333d0e85bc8Stobias 					fatal("rcs_kwexp_line: string "
2334d0e85bc8Stobias 					    "truncated");
2335f4827e12Sniallo 			}
2336f4827e12Sniallo 
2337f4827e12Sniallo 			if (kwtype & RCS_KW_MDOCDATE) {
23382b9d4d98Stobias 				/*
23392b9d4d98Stobias 				 * Do not prepend ' ' for a single
23402b9d4d98Stobias 				 * digit, %e would do so and there is
23412b9d4d98Stobias 				 * no better format for strftime().
23422b9d4d98Stobias 				 */
234322872efdScanacar 				if (strftime(buf, sizeof(buf),
234422872efdScanacar 				    (rdp->rd_date.tm_mday < 10) ?
234522872efdScanacar 				        "%B%e %Y " : "%B %e %Y ",
2346d0e85bc8Stobias 				    &rdp->rd_date) == 0)
2347d0e85bc8Stobias 					fatal("rcs_kwexp_line: strftime "
2348d0e85bc8Stobias 					    "failure");
2349d0e85bc8Stobias 				if (strlcat(expbuf, buf, sizeof(expbuf)) >=
2350d0e85bc8Stobias 				    sizeof(expbuf))
2351d0e85bc8Stobias 					fatal("rcs_kwexp_line: string "
2352d0e85bc8Stobias 					    "truncated");
23530dbb72f1Sniallo 			}
23540dbb72f1Sniallo 
23550dbb72f1Sniallo 			if (kwtype & RCS_KW_AUTHOR) {
2356d0e85bc8Stobias 				if (strlcat(expbuf, rdp->rd_author,
2357d0e85bc8Stobias 				    sizeof(expbuf)) >= sizeof(expbuf) ||
2358d0e85bc8Stobias 				    strlcat(expbuf, " ", sizeof(expbuf)) >=
2359d0e85bc8Stobias 				    sizeof(expbuf))
2360d0e85bc8Stobias 					fatal("rcs_kwexp_line: string "
2361d0e85bc8Stobias 					    "truncated");
23620dbb72f1Sniallo 			}
23630dbb72f1Sniallo 
23640dbb72f1Sniallo 			if (kwtype & RCS_KW_STATE) {
2365d0e85bc8Stobias 				if (strlcat(expbuf, rdp->rd_state,
2366d0e85bc8Stobias 				    sizeof(expbuf)) >= sizeof(expbuf) ||
2367d0e85bc8Stobias 				    strlcat(expbuf, " ", sizeof(expbuf)) >=
2368d0e85bc8Stobias 				    sizeof(expbuf))
2369d0e85bc8Stobias 					fatal("rcs_kwexp_line: string "
2370d0e85bc8Stobias 					    "truncated");
23710dbb72f1Sniallo 			}
23720dbb72f1Sniallo 
23730dbb72f1Sniallo 			/* order does not matter anymore below */
2374364cb4c9Stobias 			if (kwtype & RCS_KW_LOG) {
2375364cb4c9Stobias 				char linebuf[256];
23767bb3ddb0Sray 				struct rcs_line *cur, *lp;
2377364cb4c9Stobias 				char *logp, *l_line, *prefix, *q, *sprefix;
2378364cb4c9Stobias 				size_t i;
2379364cb4c9Stobias 
2380a5f59dfcStobias 				/* Log line */
2381364cb4c9Stobias 				if (!(kwtype & RCS_KW_FULLPATH))
2382364cb4c9Stobias 					(void)strlcat(expbuf,
2383431378d1Snaddy 					    rcsfile_basename, sizeof(expbuf));
2384364cb4c9Stobias 				else
2385364cb4c9Stobias 					(void)strlcat(expbuf, rcsfile,
2386364cb4c9Stobias 					    sizeof(expbuf));
2387364cb4c9Stobias 
2388d0e85bc8Stobias 				if (strlcat(expbuf, " ", sizeof(expbuf)) >=
2389d0e85bc8Stobias 				    sizeof(expbuf))
2390d0e85bc8Stobias 					fatal("rcs_kwexp_line: string "
2391d0e85bc8Stobias 					    "truncated");
23920dbb72f1Sniallo 
2393364cb4c9Stobias 				cur = line;
2394364cb4c9Stobias 
2395364cb4c9Stobias 				/* copy rdp->rd_log for strsep */
2396364cb4c9Stobias 				logp = xstrdup(rdp->rd_log);
2397364cb4c9Stobias 
2398364cb4c9Stobias 				/* copy our prefix for later processing */
2399364cb4c9Stobias 				prefix = xmalloc(start - line->l_line + 1);
2400364cb4c9Stobias 				memcpy(prefix, line->l_line,
2401364cb4c9Stobias 				    start - line->l_line);
2402364cb4c9Stobias 				prefix[start - line->l_line] = '\0';
2403364cb4c9Stobias 
2404364cb4c9Stobias 				/* copy also prefix without trailing blanks. */
2405364cb4c9Stobias 				sprefix = xstrdup(prefix);
2406364cb4c9Stobias 				for (i = strlen(sprefix); i > 0 &&
2407364cb4c9Stobias 				    sprefix[i - 1] == ' '; i--)
2408364cb4c9Stobias 					sprefix[i - 1] = '\0';
2409364cb4c9Stobias 
2410364cb4c9Stobias 				/* new line: revision + date + author */
2411364cb4c9Stobias 				linebuf[0] = '\0';
2412364cb4c9Stobias 				if (strlcat(linebuf, "Revision ",
2413364cb4c9Stobias 				    sizeof(linebuf)) >= sizeof(linebuf))
2414364cb4c9Stobias 					fatal("rcs_kwexp_line: truncated");
2415364cb4c9Stobias 				rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
2416364cb4c9Stobias 				if (strlcat(linebuf, buf, sizeof(linebuf))
2417364cb4c9Stobias 				    >= sizeof(buf))
2418364cb4c9Stobias 					fatal("rcs_kwexp_line: truncated");
241922872efdScanacar 				if (strftime(buf, sizeof(buf),
242022872efdScanacar 				    "  %Y/%m/%d %H:%M:%S  ",
2421364cb4c9Stobias 				    &rdp->rd_date) == 0)
2422364cb4c9Stobias 					fatal("rcs_kwexp_line: strftime "
2423364cb4c9Stobias 					    "failure");
2424364cb4c9Stobias 				if (strlcat(linebuf, buf, sizeof(linebuf))
2425364cb4c9Stobias 				    >= sizeof(linebuf))
2426364cb4c9Stobias 					fatal("rcs_kwexp_line: string "
2427364cb4c9Stobias 					    "truncated");
2428364cb4c9Stobias 				if (strlcat(linebuf, rdp->rd_author,
2429364cb4c9Stobias 				    sizeof(linebuf)) >= sizeof(linebuf))
2430364cb4c9Stobias 					fatal("rcs_kwexp_line: string "
2431364cb4c9Stobias 					    "truncated");
2432364cb4c9Stobias 
2433364cb4c9Stobias 				lp = xcalloc(1, sizeof(*lp));
2434ae886706Smillert 				xasprintf((char **)&(lp->l_line), "%s%s\n",
2435364cb4c9Stobias 				    prefix, linebuf);
2436364cb4c9Stobias 				lp->l_len = strlen(lp->l_line);
2437364cb4c9Stobias 				TAILQ_INSERT_AFTER(&(lines->l_lines), cur, lp,
2438364cb4c9Stobias 				    l_list);
2439364cb4c9Stobias 				cur = lp;
2440364cb4c9Stobias 
2441364cb4c9Stobias 				/* Log message */
2442364cb4c9Stobias 				q = logp;
2443364cb4c9Stobias 				while ((l_line = strsep(&q, "\n")) != NULL &&
2444364cb4c9Stobias 				    q != NULL) {
2445364cb4c9Stobias 					lp = xcalloc(1, sizeof(*lp));
2446364cb4c9Stobias 
2447364cb4c9Stobias 					if (l_line[0] == '\0') {
2448ae886706Smillert 						xasprintf((char **)&(lp->l_line),
2449ae886706Smillert 						    "%s\n", sprefix);
2450364cb4c9Stobias 					} else {
2451ae886706Smillert 						xasprintf((char **)&(lp->l_line),
2452364cb4c9Stobias 						    "%s%s\n", prefix, l_line);
2453364cb4c9Stobias 					}
2454364cb4c9Stobias 
2455364cb4c9Stobias 					lp->l_len = strlen(lp->l_line);
2456364cb4c9Stobias 					TAILQ_INSERT_AFTER(&(lines->l_lines),
2457364cb4c9Stobias 					    cur, lp, l_list);
2458364cb4c9Stobias 					cur = lp;
2459364cb4c9Stobias 				}
2460397ddb8aSnicm 				free(logp);
2461364cb4c9Stobias 
2462364cb4c9Stobias 				/*
2463364cb4c9Stobias 				 * This is just another hairy mess, but it must
2464e2d7d3ceStobias 				 * be done: All characters behind Log will be
2465364cb4c9Stobias 				 * written in a new line next to log messages.
2466364cb4c9Stobias 				 * But that's not enough, we have to strip all
2467364cb4c9Stobias 				 * trailing whitespaces of our prefix.
2468364cb4c9Stobias 				 */
2469364cb4c9Stobias 				lp = xcalloc(1, sizeof(*lp));
2470c522126cSmillert 				xasprintf((char **)&lp->l_line, "%s%s",
2471c522126cSmillert 				    sprefix, end);
2472364cb4c9Stobias 				lp->l_len = strlen(lp->l_line);
2473364cb4c9Stobias 				TAILQ_INSERT_AFTER(&(lines->l_lines), cur, lp,
2474364cb4c9Stobias 				    l_list);
2475364cb4c9Stobias 				cur = lp;
2476364cb4c9Stobias 
2477364cb4c9Stobias 				end = line->l_line + line->l_len - 1;
2478364cb4c9Stobias 
2479397ddb8aSnicm 				free(prefix);
2480397ddb8aSnicm 				free(sprefix);
2481364cb4c9Stobias 
2482364cb4c9Stobias 			}
2483364cb4c9Stobias 
24840dbb72f1Sniallo 			if (kwtype & RCS_KW_SOURCE) {
2485d0e85bc8Stobias 				if (strlcat(expbuf, rcsfile, sizeof(expbuf)) >=
2486d0e85bc8Stobias 				    sizeof(expbuf) || strlcat(expbuf, " ",
2487d0e85bc8Stobias 				    sizeof(expbuf)) >= sizeof(expbuf))
2488d0e85bc8Stobias 					fatal("rcs_kwexp_line: string "
2489d0e85bc8Stobias 					    "truncated");
24900dbb72f1Sniallo 			}
24910dbb72f1Sniallo 
24920dbb72f1Sniallo 			if (kwtype & RCS_KW_NAME)
2493d0e85bc8Stobias 				if (strlcat(expbuf, " ", sizeof(expbuf)) >=
2494d0e85bc8Stobias 				    sizeof(expbuf))
2495d0e85bc8Stobias 					fatal("rcs_kwexp_line: string "
2496d0e85bc8Stobias 					    "truncated");
24975c14cef8Stobias 
24985c14cef8Stobias 			if (kwtype & RCS_KW_LOCKER)
2499d0e85bc8Stobias 				if (strlcat(expbuf, " ", sizeof(expbuf)) >=
2500d0e85bc8Stobias 				    sizeof(expbuf))
2501d0e85bc8Stobias 					fatal("rcs_kwexp_line: string "
2502d0e85bc8Stobias 					    "truncated");
25030dbb72f1Sniallo 		}
25040dbb72f1Sniallo 
25050dbb72f1Sniallo 		/* end the expansion */
25060dbb72f1Sniallo 		if (mode & RCS_KWEXP_NAME)
25070dbb72f1Sniallo 			if (strlcat(expbuf, "$",
25080dbb72f1Sniallo 			    sizeof(expbuf)) >= sizeof(expbuf))
2509ff7b57e3Sjoris 				fatal("rcs_kwexp_line: truncated");
25100dbb72f1Sniallo 
25110dbb72f1Sniallo 		/* Concatenate everything together. */
25127bb3ddb0Sray 		tmpbuf = buf_alloc(len + strlen(expbuf));
25130dbb72f1Sniallo 		/* Append everything before keyword. */
25147bb3ddb0Sray 		buf_append(tmpbuf, line->l_line,
251590fdc30cSotto 		    start - line->l_line);
25160dbb72f1Sniallo 		/* Append keyword. */
25177bb3ddb0Sray 		buf_puts(tmpbuf, expbuf);
25180dbb72f1Sniallo 		/* Point c to end of keyword. */
25197bb3ddb0Sray 		tlen = buf_len(tmpbuf) - 1;
25200dbb72f1Sniallo 		/* Append everything after keyword. */
25217bb3ddb0Sray 		buf_append(tmpbuf, end,
252290fdc30cSotto 		    line->l_line + line->l_len - end);
25237bb3ddb0Sray 		c = buf_get(tmpbuf) + tlen;
25240dbb72f1Sniallo 		/* Point fin to end of data. */
25257bb3ddb0Sray 		fin = buf_get(tmpbuf) + buf_len(tmpbuf) - 1;
25260dbb72f1Sniallo 		/* Recalculate new length. */
25277bb3ddb0Sray 		len = buf_len(tmpbuf);
25280dbb72f1Sniallo 
25290dbb72f1Sniallo 		/* tmpbuf is now ready, convert to string */
25307650a186Sotto 		if (line->l_needsfree)
2531397ddb8aSnicm 			free(line->l_line);
25320dbb72f1Sniallo 		line->l_len = len;
25337bb3ddb0Sray 		line->l_line = buf_release(tmpbuf);
25340951efdeSniallo 		line->l_needsfree = 1;
25350dbb72f1Sniallo 	}
25360dbb72f1Sniallo }
2537113cb29fStobias 
2538113cb29fStobias /* rcs_translate_tag() */
2539113cb29fStobias RCSNUM *
rcs_translate_tag(const char * revstr,RCSFILE * rfp)2540113cb29fStobias rcs_translate_tag(const char *revstr, RCSFILE *rfp)
2541113cb29fStobias {
2542113cb29fStobias 	int follow;
254325d391d7Sjoris 	time_t deltatime;
254467a02cd8Sjoris 	char branch[CVS_REV_BUFSZ];
2545272fac53Sjoris 	RCSNUM *brev, *frev, *rev;
2546113cb29fStobias 	struct rcs_delta *rdp, *trdp;
2547b87788a5Stobias 	time_t cdate;
2548113cb29fStobias 
2549272fac53Sjoris 	brev = frev = NULL;
2550113cb29fStobias 
255167a02cd8Sjoris 	if (revstr == NULL) {
255267a02cd8Sjoris 		if (rfp->rf_branch != NULL) {
255367a02cd8Sjoris 			rcsnum_tostr(rfp->rf_branch, branch, sizeof(branch));
255467a02cd8Sjoris 			revstr = branch;
255567a02cd8Sjoris 		} else {
2556113cb29fStobias 			revstr = RCS_HEAD_BRANCH;
255767a02cd8Sjoris 		}
255867a02cd8Sjoris 	}
2559113cb29fStobias 
2560113cb29fStobias 	if ((rev = rcs_get_revision(revstr, rfp)) == NULL)
256125d391d7Sjoris 		return (NULL);
256225d391d7Sjoris 
256325d391d7Sjoris 	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
25641db92473Stobias 		return (NULL);
256525d391d7Sjoris 
2566e1936db1Stobias 	/* let's see if we must follow a branch */
2567e1936db1Stobias 	if (!strcmp(revstr, RCS_HEAD_BRANCH))
2568e1936db1Stobias 		follow = 1;
2569e1936db1Stobias 	else {
2570e1936db1Stobias 		frev = rcs_sym_getrev(rfp, revstr);
2571e1936db1Stobias 		if (frev == NULL)
2572272fac53Sjoris 			frev = rcsnum_parse(revstr);
2573e1936db1Stobias 
2574e1936db1Stobias 		brev = rcsnum_alloc();
2575e1936db1Stobias 		rcsnum_cpy(rev, brev, rev->rn_len - 1);
2576e1936db1Stobias 
2577e1936db1Stobias 		if (frev != NULL && RCSNUM_ISBRANCH(frev) &&
2578e1936db1Stobias 		    !rcsnum_cmp(frev, brev, 0)) {
2579e1936db1Stobias 			follow = 1;
2580e1936db1Stobias 		} else
2581e1936db1Stobias 			follow = 0;
2582e1936db1Stobias 
258353ce2177Sfcambus 		free(brev);
2584*65402675Sjsg 		brev = NULL;
2585e1936db1Stobias 	}
2586e1936db1Stobias 
2587b87788a5Stobias 	if (cvs_specified_date != -1)
2588b87788a5Stobias 		cdate = cvs_specified_date;
2589b87788a5Stobias 	else
2590b87788a5Stobias 		cdate = cvs_directory_date;
2591b87788a5Stobias 
2592b87788a5Stobias 	if (cdate == -1) {
25934f5b80e5Sjoris 		free(frev);
25944f5b80e5Sjoris 
259525d391d7Sjoris 		/* XXX */
2596e1936db1Stobias 		if (rev->rn_len < 4 || !follow) {
259725d391d7Sjoris 			return (rev);
259825d391d7Sjoris 		}
259925d391d7Sjoris 
260025d391d7Sjoris 		/* Find the latest delta on that branch */
260153ce2177Sfcambus 		free(rev);
260225d391d7Sjoris 		for (;;) {
260325d391d7Sjoris 			if (rdp->rd_next->rn_len == 0)
260425d391d7Sjoris 				break;
260525d391d7Sjoris 			if ((rdp = rcs_findrev(rfp, rdp->rd_next)) == NULL)
26062c97fb1eStobias 				fatal("rcs_translate_tag: could not fetch "
260725d391d7Sjoris 				    "branch delta");
260825d391d7Sjoris 		}
260925d391d7Sjoris 
261025d391d7Sjoris 		rev = rcsnum_alloc();
261125d391d7Sjoris 		rcsnum_cpy(rdp->rd_num, rev, 0);
261225d391d7Sjoris 		return (rev);
261325d391d7Sjoris 	}
2614113cb29fStobias 
2615113cb29fStobias 	if (frev != NULL) {
2616113cb29fStobias 		brev = rcsnum_revtobr(frev);
26177cee39ecSjoris 		brev->rn_len = rev->rn_len - 1;
261853ce2177Sfcambus 		free(frev);
2619113cb29fStobias 	}
2620113cb29fStobias 
262153ce2177Sfcambus 	free(rev);
2622113cb29fStobias 
2623113cb29fStobias 	do {
2624761daeb8Stobias 		deltatime = timegm(&(rdp->rd_date));
262525d391d7Sjoris 
262625d391d7Sjoris 		if (RCSNUM_ISBRANCHREV(rdp->rd_num)) {
2627b87788a5Stobias 			if (deltatime > cdate) {
262825d391d7Sjoris 				trdp = TAILQ_PREV(rdp, rcs_dlist, rd_list);
262925d391d7Sjoris 				if (trdp == NULL)
263025d391d7Sjoris 					trdp = rdp;
26317cee39ecSjoris 
26327cee39ecSjoris 				if (trdp->rd_num->rn_len != rdp->rd_num->rn_len)
26337cee39ecSjoris 					return (NULL);
26347cee39ecSjoris 
263525d391d7Sjoris 				rev = rcsnum_alloc();
263625d391d7Sjoris 				rcsnum_cpy(trdp->rd_num, rev, 0);
263725d391d7Sjoris 				return (rev);
263825d391d7Sjoris 			}
26397cee39ecSjoris 
26407cee39ecSjoris 			if (rdp->rd_next->rn_len == 0) {
26417cee39ecSjoris 				rev = rcsnum_alloc();
26427cee39ecSjoris 				rcsnum_cpy(rdp->rd_num, rev, 0);
26437cee39ecSjoris 				return (rev);
26447cee39ecSjoris 			}
264525d391d7Sjoris 		} else {
2646b87788a5Stobias 			if (deltatime < cdate) {
2647113cb29fStobias 				rev = rcsnum_alloc();
2648113cb29fStobias 				rcsnum_cpy(rdp->rd_num, rev, 0);
264925d391d7Sjoris 				return (rev);
265025d391d7Sjoris 			}
2651113cb29fStobias 		}
2652113cb29fStobias 
2653113cb29fStobias 		if (follow && rdp->rd_next->rn_len != 0) {
2654113cb29fStobias 			if (brev != NULL && !rcsnum_cmp(brev, rdp->rd_num, 0))
2655113cb29fStobias 				break;
2656113cb29fStobias 
2657113cb29fStobias 			trdp = rcs_findrev(rfp, rdp->rd_next);
2658113cb29fStobias 			if (trdp == NULL)
2659113cb29fStobias 				fatal("failed to grab next revision");
2660113cb29fStobias 			rdp = trdp;
2661113cb29fStobias 		} else
2662113cb29fStobias 			follow = 0;
2663113cb29fStobias 	} while (follow);
2664113cb29fStobias 
266525d391d7Sjoris 	return (NULL);
2666113cb29fStobias }
2667