GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/rcs/rcs.c Lines: 562 804 69.9 %
Date: 2017-11-07 Branches: 308 544 56.6 %

Line Branch Exec Source
1
/*	$OpenBSD: rcs.c,v 1.85 2016/05/09 13:03:55 schwarze Exp $	*/
2
/*
3
 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions
8
 * are met:
9
 *
10
 * 1. Redistributions of source code must retain the above copyright
11
 *    notice, this list of conditions and the following disclaimer.
12
 * 2. The name of the author may not be used to endorse or promote products
13
 *    derived from this software without specific prior written permission.
14
 *
15
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
 */
26
27
#include <sys/param.h>	/* MAXBSIZE */
28
#include <sys/stat.h>
29
30
#include <ctype.h>
31
#include <err.h>
32
#include <errno.h>
33
#include <libgen.h>
34
#include <pwd.h>
35
#include <stdarg.h>
36
#include <stdio.h>
37
#include <stdlib.h>
38
#include <string.h>
39
#include <unistd.h>
40
41
#include "diff.h"
42
#include "rcs.h"
43
#include "rcsparse.h"
44
#include "rcsprog.h"
45
#include "rcsutil.h"
46
#include "xmalloc.h"
47
48
#define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
49
50
/* invalid characters in RCS states */
51
static const char rcs_state_invch[] = RCS_STATE_INVALCHAR;
52
53
/* invalid characters in RCS symbol names */
54
static const char rcs_sym_invch[] = RCS_SYM_INVALCHAR;
55
56
struct rcs_kw rcs_expkw[] =  {
57
	{ "Author",	RCS_KW_AUTHOR   },
58
	{ "Date",	RCS_KW_DATE     },
59
	{ "Locker",	RCS_KW_LOCKER   },
60
	{ "Header",	RCS_KW_HEADER   },
61
	{ "Id",		RCS_KW_ID       },
62
	{ "OpenBSD",	RCS_KW_ID       },
63
	{ "Log",	RCS_KW_LOG      },
64
	{ "Name",	RCS_KW_NAME     },
65
	{ "RCSfile",	RCS_KW_RCSFILE  },
66
	{ "Revision",	RCS_KW_REVISION },
67
	{ "Source",	RCS_KW_SOURCE   },
68
	{ "State",	RCS_KW_STATE    },
69
	{ "Mdocdate",	RCS_KW_MDOCDATE },
70
};
71
72
int rcs_errno = RCS_ERR_NOERR;
73
char *timezone_flag = NULL;
74
75
int		rcs_patch_lines(struct rcs_lines *, struct rcs_lines *);
76
static int	rcs_movefile(char *, char *, mode_t, u_int);
77
78
static void	rcs_freedelta(struct rcs_delta *);
79
static void	rcs_strprint(const u_char *, size_t, FILE *);
80
81
static BUF	*rcs_expand_keywords(char *, struct rcs_delta *, BUF *, int);
82
83
RCSFILE *
84
rcs_open(const char *path, int fd, int flags, ...)
85
{
86
	int mode;
87
	mode_t fmode;
88
	RCSFILE *rfp;
89
330
	va_list vap;
90
	struct rcs_delta *rdp;
91
	struct rcs_lock *lkr;
92
93
	fmode = S_IRUSR|S_IRGRP|S_IROTH;
94
165
	flags &= 0xffff;	/* ditch any internal flags */
95
96
165
	if (flags & RCS_CREATE) {
97
40
		va_start(vap, flags);
98
120
		mode = va_arg(vap, int);
99
40
		va_end(vap);
100
		fmode = (mode_t)mode;
101
40
	}
102
103
165
	rfp = xcalloc(1, sizeof(*rfp));
104
105
165
	rfp->rf_path = xstrdup(path);
106
165
	rfp->rf_flags = flags | RCS_SLOCK | RCS_SYNCED;
107
165
	rfp->rf_mode = fmode;
108
165
	if (fd == -1)
109
40
		rfp->rf_file = NULL;
110
125
	else if ((rfp->rf_file = fdopen(fd, "r")) == NULL)
111
		err(1, "rcs_open: fdopen: `%s'", path);
112
113
165
	TAILQ_INIT(&(rfp->rf_delta));
114
165
	TAILQ_INIT(&(rfp->rf_access));
115
165
	TAILQ_INIT(&(rfp->rf_symbols));
116
165
	TAILQ_INIT(&(rfp->rf_locks));
117
118
165
	if (!(rfp->rf_flags & RCS_CREATE)) {
119
125
		if (rcsparse_init(rfp))
120
			errx(1, "could not parse admin data");
121
122
		/* fill in rd_locker */
123
382
		TAILQ_FOREACH(lkr, &(rfp->rf_locks), rl_list) {
124
67
			if ((rdp = rcs_findrev(rfp, lkr->rl_num)) == NULL) {
125
				rcs_close(rfp);
126
				return (NULL);
127
			}
128
129
67
			rdp->rd_locker = xstrdup(lkr->rl_name);
130
		}
131
	}
132
133
164
	return (rfp);
134
164
}
135
136
/*
137
 * rcs_close()
138
 *
139
 * Close an RCS file handle.
140
 */
141
void
142
rcs_close(RCSFILE *rfp)
143
{
144
	struct rcs_delta *rdp;
145
	struct rcs_access *rap;
146
	struct rcs_lock *rlp;
147
	struct rcs_sym *rsp;
148
149

460
	if ((rfp->rf_flags & RCS_WRITE) && !(rfp->rf_flags & RCS_SYNCED))
150
25
		rcs_write(rfp);
151
152
760
	while (!TAILQ_EMPTY(&(rfp->rf_delta))) {
153
		rdp = TAILQ_FIRST(&(rfp->rf_delta));
154
900
		TAILQ_REMOVE(&(rfp->rf_delta), rdp, rd_list);
155
300
		rcs_freedelta(rdp);
156
	}
157
158
214
	while (!TAILQ_EMPTY(&(rfp->rf_access))) {
159
		rap = TAILQ_FIRST(&(rfp->rf_access));
160
81
		TAILQ_REMOVE(&(rfp->rf_access), rap, ra_list);
161
27
		free(rap->ra_name);
162
27
		free(rap);
163
	}
164
165
228
	while (!TAILQ_EMPTY(&(rfp->rf_symbols))) {
166
		rsp = TAILQ_FIRST(&(rfp->rf_symbols));
167
102
		TAILQ_REMOVE(&(rfp->rf_symbols), rsp, rs_list);
168
34
		rcsnum_free(rsp->rs_num);
169
34
		free(rsp->rs_name);
170
34
		free(rsp);
171
	}
172
173
320
	while (!TAILQ_EMPTY(&(rfp->rf_locks))) {
174
		rlp = TAILQ_FIRST(&(rfp->rf_locks));
175
240
		TAILQ_REMOVE(&(rfp->rf_locks), rlp, rl_list);
176
80
		rcsnum_free(rlp->rl_num);
177
80
		free(rlp->rl_name);
178
80
		free(rlp);
179
	}
180
181
160
	rcsnum_free(rfp->rf_head);
182
160
	rcsnum_free(rfp->rf_branch);
183
184
160
	if (rfp->rf_file != NULL)
185
120
		fclose(rfp->rf_file);
186
187
160
	free(rfp->rf_path);
188
160
	free(rfp->rf_comment);
189
160
	free(rfp->rf_expand);
190
160
	free(rfp->rf_desc);
191
160
	if (rfp->rf_pdata != NULL)
192
120
		rcsparse_free(rfp);
193
194
160
	free(rfp);
195
160
}
196
197
/*
198
 * rcs_write()
199
 *
200
 * Write the contents of the RCS file handle <rfp> to disk in the file whose
201
 * path is in <rf_path>.
202
 */
203
void
204
rcs_write(RCSFILE *rfp)
205
{
206
	FILE *fp;
207
324
	char numbuf[RCS_REV_BUFSZ], *fn;
208
	struct rcs_access *ap;
209
	struct rcs_sym *symp;
210
	struct rcs_branch *brp;
211
	struct rcs_delta *rdp;
212
	struct rcs_lock *lkp;
213
	size_t len;
214
	int fd;
215
216
162
	fn = NULL;
217
218
162
	if (rfp->rf_flags & RCS_SYNCED)
219
39
		return;
220
221
	/* Write operations need the whole file parsed */
222
123
	if (rcsparse_deltatexts(rfp, NULL))
223
		errx(1, "problem parsing deltatexts");
224
225
123
	(void)xasprintf(&fn, "%s/rcs.XXXXXXXXXX", rcs_tmpdir);
226
227
123
	if ((fd = mkstemp(fn)) == -1)
228
		err(1, "%s", fn);
229
230
123
	if ((fp = fdopen(fd, "w+")) == NULL) {
231
		int saved_errno;
232
233
		saved_errno = errno;
234
		(void)unlink(fn);
235
		errno = saved_errno;
236
		err(1, "%s", fn);
237
	}
238
239
123
	worklist_add(fn, &temp_files);
240
241
123
	if (rfp->rf_head != NULL)
242
108
		rcsnum_tostr(rfp->rf_head, numbuf, sizeof(numbuf));
243
	else
244
15
		numbuf[0] = '\0';
245
246
123
	fprintf(fp, "head\t%s;\n", numbuf);
247
248
123
	if (rfp->rf_branch != NULL) {
249
		rcsnum_tostr(rfp->rf_branch, numbuf, sizeof(numbuf));
250
		fprintf(fp, "branch\t%s;\n", numbuf);
251
	}
252
253
123
	fputs("access", fp);
254
270
	TAILQ_FOREACH(ap, &(rfp->rf_access), ra_list) {
255
12
		fprintf(fp, "\n\t%s", ap->ra_name);
256
	}
257
123
	fputs(";\n", fp);
258
259
123
	fprintf(fp, "symbols");
260
310
	TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
261
32
		if (RCSNUM_ISBRANCH(symp->rs_num))
262
			rcsnum_addmagic(symp->rs_num);
263
32
		rcsnum_tostr(symp->rs_num, numbuf, sizeof(numbuf));
264
32
		fprintf(fp, "\n\t%s:%s", symp->rs_name, numbuf);
265
	}
266
123
	fprintf(fp, ";\n");
267
268
123
	fprintf(fp, "locks");
269
344
	TAILQ_FOREACH(lkp, &(rfp->rf_locks), rl_list) {
270
49
		rcsnum_tostr(lkp->rl_num, numbuf, sizeof(numbuf));
271
49
		fprintf(fp, "\n\t%s:%s", lkp->rl_name, numbuf);
272
	}
273
274
123
	fprintf(fp, ";");
275
276
123
	if (rfp->rf_flags & RCS_SLOCK)
277
123
		fprintf(fp, " strict;");
278
123
	fputc('\n', fp);
279
280
123
	fputs("comment\t@", fp);
281
123
	if (rfp->rf_comment != NULL) {
282
71
		rcs_strprint((const u_char *)rfp->rf_comment,
283
71
		    strlen(rfp->rf_comment), fp);
284
71
		fputs("@;\n", fp);
285
71
	} else
286
52
		fputs("# @;\n", fp);
287
288
123
	if (rfp->rf_expand != NULL) {
289
		fputs("expand @", fp);
290
		rcs_strprint((const u_char *)rfp->rf_expand,
291
		    strlen(rfp->rf_expand), fp);
292
		fputs("@;\n", fp);
293
	}
294
295
123
	fputs("\n\n", fp);
296
297
762
	TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
298
258
		fprintf(fp, "%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
299
		    sizeof(numbuf)));
300
258
		fprintf(fp, "date\t%d.%02d.%02d.%02d.%02d.%02d;",
301
258
		    rdp->rd_date.tm_year + 1900, rdp->rd_date.tm_mon + 1,
302
258
		    rdp->rd_date.tm_mday, rdp->rd_date.tm_hour,
303
258
		    rdp->rd_date.tm_min, rdp->rd_date.tm_sec);
304
258
		fprintf(fp, "\tauthor %s;\tstate %s;\n",
305
258
		    rdp->rd_author, rdp->rd_state);
306
258
		fputs("branches", fp);
307
516
		TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
308
			fprintf(fp, "\n\t%s", rcsnum_tostr(brp->rb_num, numbuf,
309
			    sizeof(numbuf)));
310
		}
311
258
		fputs(";\n", fp);
312
258
		fprintf(fp, "next\t%s;\n\n", rcsnum_tostr(rdp->rd_next,
313
		    numbuf, sizeof(numbuf)));
314
	}
315
316
123
	fputs("\ndesc\n@", fp);
317

246
	if (rfp->rf_desc != NULL && (len = strlen(rfp->rf_desc)) > 0) {
318
84
		rcs_strprint((const u_char *)rfp->rf_desc, len, fp);
319
84
		if (rfp->rf_desc[len-1] != '\n')
320
6
			fputc('\n', fp);
321
	}
322
123
	fputs("@\n", fp);
323
324
	/* deltatexts */
325
762
	TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
326
258
		fprintf(fp, "\n\n%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
327
		    sizeof(numbuf)));
328
258
		fputs("log\n@", fp);
329
258
		if (rdp->rd_log != NULL) {
330
258
			len = strlen(rdp->rd_log);
331
258
			rcs_strprint((const u_char *)rdp->rd_log, len, fp);
332

514
			if (len == 0 || rdp->rd_log[len-1] != '\n')
333
75
				fputc('\n', fp);
334
		}
335
258
		fputs("@\ntext\n@", fp);
336
258
		if (rdp->rd_text != NULL)
337
245
			rcs_strprint(rdp->rd_text, rdp->rd_tlen, fp);
338
258
		fputs("@\n", fp);
339
	}
340
123
	(void)fclose(fp);
341
342
123
	if (rcs_movefile(fn, rfp->rf_path, rfp->rf_mode, rfp->rf_flags) == -1) {
343
		(void)unlink(fn);
344
		errx(1, "rcs_movefile failed");
345
	}
346
347
123
	rfp->rf_flags |= RCS_SYNCED;
348
349
123
	free(fn);
350
285
}
351
352
/*
353
 * rcs_movefile()
354
 *
355
 * Move a file using rename(2) if possible and copying if not.
356
 * Returns 0 on success, -1 on failure.
357
 */
358
static int
359
rcs_movefile(char *from, char *to, mode_t perm, u_int to_flags)
360
{
361
	FILE *src, *dst;
362
	size_t nread, nwritten;
363
	char *buf;
364
365
246
	if (rename(from, to) == 0) {
366
		if (chmod(to, perm) == -1) {
367
			warn("%s", to);
368
			return (-1);
369
		}
370
		return (0);
371
123
	} else if (errno != EXDEV) {
372
		warn("failed to access temp RCS output file");
373
		return (-1);
374
	}
375
376

163
	if ((chmod(to, S_IWUSR) == -1) && !(to_flags & RCS_CREATE)) {
377
		warnx("chmod(%s, 0%o) failed", to, S_IWUSR);
378
		return (-1);
379
	}
380
381
	/* different filesystem, have to copy the file */
382
123
	if ((src = fopen(from, "r")) == NULL) {
383
		warn("%s", from);
384
		return (-1);
385
	}
386
123
	if ((dst = fopen(to, "w")) == NULL) {
387
		warn("%s", to);
388
		(void)fclose(src);
389
		return (-1);
390
	}
391

369
	if (fchmod(fileno(dst), perm)) {
392
		warn("%s", to);
393
		(void)unlink(to);
394
		(void)fclose(src);
395
		(void)fclose(dst);
396
		return (-1);
397
	}
398
399
123
	buf = xmalloc(MAXBSIZE);
400
369
	while ((nread = fread(buf, sizeof(char), MAXBSIZE, src)) != 0) {
401

246
		if (ferror(src)) {
402
			warnx("failed to read `%s'", from);
403
			(void)unlink(to);
404
			goto out;
405
		}
406
123
		nwritten = fwrite(buf, sizeof(char), nread, dst);
407
123
		if (nwritten != nread) {
408
			warnx("failed to write `%s'", to);
409
			(void)unlink(to);
410
			goto out;
411
		}
412
	}
413
414
123
	(void)unlink(from);
415
416
out:
417
123
	(void)fclose(src);
418
123
	(void)fclose(dst);
419
123
	free(buf);
420
421
123
	return (0);
422
123
}
423
424
/*
425
 * rcs_head_set()
426
 *
427
 * Set the revision number of the head revision for the RCS file <file> to
428
 * <rev>, which must reference a valid revision within the file.
429
 */
430
int
431
rcs_head_set(RCSFILE *file, RCSNUM *rev)
432
{
433
2
	if (rcs_findrev(file, rev) == NULL)
434
		return (-1);
435
436
1
	if (file->rf_head == NULL)
437
		file->rf_head = rcsnum_alloc();
438
439
1
	rcsnum_cpy(rev, file->rf_head, 0);
440
1
	file->rf_flags &= ~RCS_SYNCED;
441
1
	return (0);
442
1
}
443
444
445
/*
446
 * rcs_branch_get()
447
 *
448
 * Retrieve the default branch number for the RCS file <file>.
449
 * Returns the number on success.  If NULL is returned, then there is no
450
 * default branch for this file.
451
 */
452
const RCSNUM *
453
rcs_branch_get(RCSFILE *file)
454
{
455
26
	return (file->rf_branch);
456
}
457
458
/*
459
 * rcs_access_add()
460
 *
461
 * Add the login name <login> to the access list for the RCS file <file>.
462
 * Returns 0 on success, or -1 on failure.
463
 */
464
int
465
rcs_access_add(RCSFILE *file, const char *login)
466
{
467
	struct rcs_access *ap;
468
469
	/* first look for duplication */
470
60
	TAILQ_FOREACH(ap, &(file->rf_access), ra_list) {
471
12
		if (strcmp(ap->ra_name, login) == 0) {
472
			rcs_errno = RCS_ERR_DUPENT;
473
			return (-1);
474
		}
475
	}
476
477
12
	ap = xmalloc(sizeof(*ap));
478
12
	ap->ra_name = xstrdup(login);
479
12
	TAILQ_INSERT_TAIL(&(file->rf_access), ap, ra_list);
480
481
	/* not synced anymore */
482
12
	file->rf_flags &= ~RCS_SYNCED;
483
12
	return (0);
484
12
}
485
486
/*
487
 * rcs_access_remove()
488
 *
489
 * Remove an entry with login name <login> from the access list of the RCS
490
 * file <file>.
491
 * Returns 0 on success, or -1 on failure.
492
 */
493
int
494
rcs_access_remove(RCSFILE *file, const char *login)
495
{
496
	struct rcs_access *ap;
497
498
9
	TAILQ_FOREACH(ap, &(file->rf_access), ra_list)
499
3
		if (strcmp(ap->ra_name, login) == 0)
500
			break;
501
502
3
	if (ap == NULL) {
503
		rcs_errno = RCS_ERR_NOENT;
504
		return (-1);
505
	}
506
507
9
	TAILQ_REMOVE(&(file->rf_access), ap, ra_list);
508
3
	free(ap->ra_name);
509
3
	free(ap);
510
511
	/* not synced anymore */
512
3
	file->rf_flags &= ~RCS_SYNCED;
513
3
	return (0);
514
3
}
515
516
/*
517
 * rcs_sym_add()
518
 *
519
 * Add a symbol to the list of symbols for the RCS file <rfp>.  The new symbol
520
 * is named <sym> and is bound to the RCS revision <snum>.
521
 * Returns 0 on success, or -1 on failure.
522
 */
523
int
524
rcs_sym_add(RCSFILE *rfp, const char *sym, RCSNUM *snum)
525
{
526
	struct rcs_sym *symp;
527
528
18
	if (!rcs_sym_check(sym)) {
529
		rcs_errno = RCS_ERR_BADSYM;
530
		return (-1);
531
	}
532
533
	/* first look for duplication */
534
30
	TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
535
6
		if (strcmp(symp->rs_name, sym) == 0) {
536
			rcs_errno = RCS_ERR_DUPENT;
537
			return (-1);
538
		}
539
	}
540
541
9
	symp = xmalloc(sizeof(*symp));
542
9
	symp->rs_name = xstrdup(sym);
543
9
	symp->rs_num = rcsnum_alloc();
544
9
	rcsnum_cpy(snum, symp->rs_num, 0);
545
546
27
	TAILQ_INSERT_HEAD(&(rfp->rf_symbols), symp, rs_list);
547
548
	/* not synced anymore */
549
9
	rfp->rf_flags &= ~RCS_SYNCED;
550
9
	return (0);
551
9
}
552
553
/*
554
 * rcs_sym_remove()
555
 *
556
 * Remove the symbol with name <sym> from the symbol list for the RCS file
557
 * <file>.  If no such symbol is found, the call fails and returns with an
558
 * error.
559
 * Returns 0 on success, or -1 on failure.
560
 */
561
int
562
rcs_sym_remove(RCSFILE *file, const char *sym)
563
{
564
	struct rcs_sym *symp;
565
566
2
	if (!rcs_sym_check(sym)) {
567
		rcs_errno = RCS_ERR_BADSYM;
568
		return (-1);
569
	}
570
571
2
	TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
572
1
		if (strcmp(symp->rs_name, sym) == 0)
573
			break;
574
575
1
	if (symp == NULL) {
576
		rcs_errno = RCS_ERR_NOENT;
577
		return (-1);
578
	}
579
580
3
	TAILQ_REMOVE(&(file->rf_symbols), symp, rs_list);
581
1
	free(symp->rs_name);
582
1
	rcsnum_free(symp->rs_num);
583
1
	free(symp);
584
585
	/* not synced anymore */
586
1
	file->rf_flags &= ~RCS_SYNCED;
587
1
	return (0);
588
1
}
589
590
/*
591
 * rcs_sym_getrev()
592
 *
593
 * Retrieve the RCS revision number associated with the symbol <sym> for the
594
 * RCS file <file>.  The returned value is a dynamically-allocated copy and
595
 * should be freed by the caller once they are done with it.
596
 * Returns the RCSNUM on success, or NULL on failure.
597
 */
598
RCSNUM *
599
rcs_sym_getrev(RCSFILE *file, const char *sym)
600
{
601
	RCSNUM *num;
602
	struct rcs_sym *symp;
603
604
24
	if (!rcs_sym_check(sym)) {
605
7
		rcs_errno = RCS_ERR_BADSYM;
606
7
		return (NULL);
607
	}
608
609
	num = NULL;
610
18
	TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
611
9
		if (strcmp(symp->rs_name, sym) == 0)
612
			break;
613
614
5
	if (symp == NULL) {
615
		rcs_errno = RCS_ERR_NOENT;
616
	} else {
617
5
		num = rcsnum_alloc();
618
5
		rcsnum_cpy(symp->rs_num, num, 0);
619
	}
620
621
5
	return (num);
622
12
}
623
624
/*
625
 * rcs_sym_check()
626
 *
627
 * Check the RCS symbol name <sym> for any unsupported characters.
628
 * Returns 1 if the tag is correct, 0 if it isn't valid.
629
 */
630
int
631
rcs_sym_check(const char *sym)
632
{
633
	int ret;
634
	const unsigned char *cp;
635
636
	ret = 1;
637
	cp = sym;
638
114
	if (!isalpha(*cp++))
639
7
		return (0);
640
641
404
	for (; *cp != '\0'; cp++)
642

354
		if (!isgraph(*cp) || (strchr(rcs_sym_invch, *cp) != NULL)) {
643
			ret = 0;
644
			break;
645
		}
646
647
50
	return (ret);
648
57
}
649
650
/*
651
 * rcs_lock_getmode()
652
 *
653
 * Retrieve the locking mode of the RCS file <file>.
654
 */
655
int
656
rcs_lock_getmode(RCSFILE *file)
657
{
658
212
	return (file->rf_flags & RCS_SLOCK) ? RCS_LOCK_STRICT : RCS_LOCK_LOOSE;
659
}
660
661
/*
662
 * rcs_lock_setmode()
663
 *
664
 * Set the locking mode of the RCS file <file> to <mode>, which must either
665
 * be RCS_LOCK_LOOSE or RCS_LOCK_STRICT.
666
 * Returns the previous mode on success, or -1 on failure.
667
 */
668
int
669
rcs_lock_setmode(RCSFILE *file, int mode)
670
{
671
	int pmode;
672
	pmode = rcs_lock_getmode(file);
673
674
	if (mode == RCS_LOCK_STRICT)
675
		file->rf_flags |= RCS_SLOCK;
676
	else if (mode == RCS_LOCK_LOOSE)
677
		file->rf_flags &= ~RCS_SLOCK;
678
	else
679
		errx(1, "rcs_lock_setmode: invalid mode `%d'", mode);
680
681
	file->rf_flags &= ~RCS_SYNCED;
682
	return (pmode);
683
}
684
685
/*
686
 * rcs_lock_add()
687
 *
688
 * Add an RCS lock for the user <user> on revision <rev>.
689
 * Returns 0 on success, or -1 on failure.
690
 */
691
int
692
rcs_lock_add(RCSFILE *file, const char *user, RCSNUM *rev)
693
{
694
	struct rcs_lock *lkp;
695
696
	/* first look for duplication */
697
192
	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
698

38
		if (strcmp(lkp->rl_name, user) == 0 &&
699
19
		    rcsnum_cmp(rev, lkp->rl_num, 0) == 0) {
700
19
			rcs_errno = RCS_ERR_DUPENT;
701
19
			return (-1);
702
		}
703
	}
704
705
45
	lkp = xmalloc(sizeof(*lkp));
706
45
	lkp->rl_name = xstrdup(user);
707
45
	lkp->rl_num = rcsnum_alloc();
708
45
	rcsnum_cpy(rev, lkp->rl_num, 0);
709
710
45
	TAILQ_INSERT_TAIL(&(file->rf_locks), lkp, rl_list);
711
712
	/* not synced anymore */
713
45
	file->rf_flags &= ~RCS_SYNCED;
714
45
	return (0);
715
64
}
716
717
718
/*
719
 * rcs_lock_remove()
720
 *
721
 * Remove the RCS lock on revision <rev>.
722
 * Returns 0 on success, or -1 on failure.
723
 */
724
int
725
rcs_lock_remove(RCSFILE *file, const char *user, RCSNUM *rev)
726
{
727
	struct rcs_lock *lkp;
728
729
126
	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
730

56
		if (strcmp(lkp->rl_name, user) == 0 &&
731
28
		    rcsnum_cmp(lkp->rl_num, rev, 0) == 0)
732
			break;
733
	}
734
735
42
	if (lkp == NULL) {
736
14
		rcs_errno = RCS_ERR_NOENT;
737
14
		return (-1);
738
	}
739
740
84
	TAILQ_REMOVE(&(file->rf_locks), lkp, rl_list);
741
28
	rcsnum_free(lkp->rl_num);
742
28
	free(lkp->rl_name);
743
28
	free(lkp);
744
745
	/* not synced anymore */
746
28
	file->rf_flags &= ~RCS_SYNCED;
747
28
	return (0);
748
42
}
749
750
/*
751
 * rcs_desc_set()
752
 *
753
 * Set the description for the RCS file <file>.
754
 */
755
void
756
rcs_desc_set(RCSFILE *file, const char *desc)
757
{
758
	char *tmp;
759
760
82
	tmp = xstrdup(desc);
761
41
	free(file->rf_desc);
762
41
	file->rf_desc = tmp;
763
41
	file->rf_flags &= ~RCS_SYNCED;
764
41
}
765
766
/*
767
 * rcs_comment_set()
768
 *
769
 * Set the comment leader for the RCS file <file>.
770
 */
771
void
772
rcs_comment_set(RCSFILE *file, const char *comment)
773
{
774
	char *tmp;
775
776
	tmp = xstrdup(comment);
777
	free(file->rf_comment);
778
	file->rf_comment = tmp;
779
	file->rf_flags &= ~RCS_SYNCED;
780
}
781
782
int
783
rcs_patch_lines(struct rcs_lines *dlines, struct rcs_lines *plines)
784
{
785
38
	char op, *ep;
786
	struct rcs_line *lp, *dlp, *ndlp;
787
	int i, lineno, nbln;
788
	u_char tmp;
789
790
19
	dlp = TAILQ_FIRST(&(dlines->l_lines));
791
19
	lp = TAILQ_FIRST(&(plines->l_lines));
792
793
	/* skip first bogus line */
794
62
	for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
795
12
	    lp = TAILQ_NEXT(lp, l_list)) {
796
25
		if (lp->l_len < 2)
797
			errx(1, "line too short, RCS patch seems broken");
798
25
		op = *(lp->l_line);
799
		/* NUL-terminate line buffer for strtol() safety. */
800
25
		tmp = lp->l_line[lp->l_len - 1];
801
25
		lp->l_line[lp->l_len - 1] = '\0';
802
25
		lineno = (int)strtol((lp->l_line + 1), &ep, 10);
803

50
		if (lineno > dlines->l_nblines || lineno < 0 ||
804
25
		    *ep != ' ')
805
			errx(1, "invalid line specification in RCS patch");
806
25
		ep++;
807
25
		nbln = (int)strtol(ep, &ep, 10);
808
		/* Restore the last byte of the buffer */
809
25
		lp->l_line[lp->l_len - 1] = tmp;
810
25
		if (nbln < 0)
811
			errx(1,
812
			    "invalid line number specification in RCS patch");
813
814
		/* find the appropriate line */
815
		for (;;) {
816
171
			if (dlp == NULL)
817
				break;
818
171
			if (dlp->l_lineno == lineno)
819
				break;
820
150
			if (dlp->l_lineno > lineno) {
821
2
				dlp = TAILQ_PREV(dlp, tqh, l_list);
822
150
			} else if (dlp->l_lineno < lineno) {
823

294
				if (((ndlp = TAILQ_NEXT(dlp, l_list)) == NULL) ||
824
146
				    ndlp->l_lineno > lineno)
825
					break;
826
				dlp = ndlp;
827
144
			}
828
		}
829
25
		if (dlp == NULL)
830
			errx(1, "can't find referenced line in RCS patch");
831
832
25
		if (op == 'd') {
833
132
			for (i = 0; (i < nbln) && (dlp != NULL); i++) {
834
45
				ndlp = TAILQ_NEXT(dlp, l_list);
835
135
				TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list);
836
45
				free(dlp);
837
				dlp = ndlp;
838
				/* last line is gone - reset dlp */
839
45
				if (dlp == NULL) {
840
15
					ndlp = TAILQ_LAST(&(dlines->l_lines),
841
					    tqh);
842
					dlp = ndlp;
843
15
				}
844
			}
845
4
		} else if (op == 'a') {
846
16
			for (i = 0; i < nbln; i++) {
847
				ndlp = lp;
848
4
				lp = TAILQ_NEXT(lp, l_list);
849
4
				if (lp == NULL)
850
					errx(1, "truncated RCS patch");
851
12
				TAILQ_REMOVE(&(plines->l_lines), lp, l_list);
852
12
				TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp,
853
				    lp, l_list);
854
				dlp = lp;
855
856
				/* we don't want lookup to block on those */
857
4
				lp->l_lineno = lineno;
858
859
				lp = ndlp;
860
			}
861
		} else
862
			errx(1, "unknown RCS patch operation `%c'", op);
863
864
		/* last line of the patch, done */
865
25
		if (lp->l_lineno == plines->l_nblines)
866
			break;
867
	}
868
869
	/* once we're done patching, rebuild the line numbers */
870
	lineno = 0;
871
450
	TAILQ_FOREACH(lp, &(dlines->l_lines), l_list)
872
206
		lp->l_lineno = lineno++;
873
19
	dlines->l_nblines = lineno - 1;
874
875
19
	return (0);
876
19
}
877
878
/*
879
 * rcs_getrev()
880
 *
881
 * Get the whole contents of revision <rev> from the RCSFILE <rfp>.  The
882
 * returned buffer is dynamically allocated and should be released using
883
 * buf_free() once the caller is done using it.
884
 */
885
BUF *
886
rcs_getrev(RCSFILE *rfp, RCSNUM *frev)
887
{
888
	u_int i, numlen;
889
	int isbranch, lookonbranch, found;
890
	size_t dlen, plen, len;
891
	RCSNUM *crev, *rev, *brev;
892
	BUF *rbuf;
893
	struct rcs_delta *rdp = NULL;
894
	struct rcs_branch *rb;
895
	u_char *data, *patch;
896
897
250
	if (rfp->rf_head == NULL)
898
		return (NULL);
899
900
125
	if (frev == RCS_HEAD_REV)
901
		rev = rfp->rf_head;
902
	else
903
		rev = frev;
904
905
	/* XXX rcsnum_cmp() */
906
750
	for (i = 0; i < rfp->rf_head->rn_len; i++) {
907
250
		if (rfp->rf_head->rn_id[i] < rev->rn_id[i]) {
908
			rcs_errno = RCS_ERR_NOENT;
909
			return (NULL);
910
		}
911
	}
912
913
	/* No matter what, we'll need everything parsed up until the description
914
           so go for it. */
915
125
	if (rcsparse_deltas(rfp, NULL))
916
		return (NULL);
917
918
125
	rdp = rcs_findrev(rfp, rfp->rf_head);
919
125
	if (rdp == NULL) {
920
		warnx("failed to get RCS HEAD revision");
921
		return (NULL);
922
	}
923
924
125
	if (rdp->rd_tlen == 0)
925
29
		if (rcsparse_deltatexts(rfp, rfp->rf_head))
926
			return (NULL);
927
928
125
	len = rdp->rd_tlen;
929
125
	if (len == 0) {
930
27
		rbuf = buf_alloc(1);
931
27
		buf_empty(rbuf);
932
27
		return (rbuf);
933
	}
934
935
98
	rbuf = buf_alloc(len);
936
98
	buf_append(rbuf, rdp->rd_text, len);
937
938
	isbranch = 0;
939
	brev = NULL;
940
941
	/*
942
	 * If a branch was passed, get the latest revision on it.
943
	 */
944
98
	if (RCSNUM_ISBRANCH(rev)) {
945
		brev = rev;
946
		rdp = rcs_findrev(rfp, rev);
947
		if (rdp == NULL) {
948
			buf_free(rbuf);
949
			return (NULL);
950
		}
951
952
		rev = rdp->rd_num;
953
	} else {
954

196
		if (RCSNUM_ISBRANCHREV(rev)) {
955
			brev = rcsnum_revtobr(rev);
956
			isbranch = 1;
957
		}
958
	}
959
960
	lookonbranch = 0;
961
	crev = NULL;
962
963
	/* Apply patches backwards to get the right version.
964
	 */
965
98
	do {
966
		found = 0;
967
968
109
		if (rcsnum_cmp(rfp->rf_head, rev, 0) == 0)
969
			break;
970
971

19
		if (isbranch == 1 && rdp->rd_num->rn_len < rev->rn_len &&
972
		    !TAILQ_EMPTY(&(rdp->rd_branches)))
973
			lookonbranch = 1;
974
975
19
		if (isbranch && lookonbranch == 1) {
976
			lookonbranch = 0;
977
			TAILQ_FOREACH(rb, &(rdp->rd_branches), rb_list) {
978
				/* XXX rcsnum_cmp() is totally broken for
979
				 * this purpose.
980
				 */
981
				numlen = MINIMUM(brev->rn_len,
982
				    rb->rb_num->rn_len - 1);
983
				for (i = 0; i < numlen; i++) {
984
					if (rb->rb_num->rn_id[i] !=
985
					    brev->rn_id[i])
986
						break;
987
				}
988
989
				if (i == numlen) {
990
					crev = rb->rb_num;
991
					found = 1;
992
					break;
993
				}
994
			}
995
			if (found == 0)
996
				crev = rdp->rd_next;
997
		} else {
998
19
			crev = rdp->rd_next;
999
		}
1000
1001
19
		rdp = rcs_findrev(rfp, crev);
1002
19
		if (rdp == NULL) {
1003
			buf_free(rbuf);
1004
			return (NULL);
1005
		}
1006
1007
19
		plen = rdp->rd_tlen;
1008
19
		dlen = buf_len(rbuf);
1009
19
		patch = rdp->rd_text;
1010
19
		data = buf_release(rbuf);
1011
		/* check if we have parsed this rev's deltatext */
1012
19
		if (rdp->rd_tlen == 0)
1013
4
			if (rcsparse_deltatexts(rfp, rdp->rd_num))
1014
				return (NULL);
1015
1016
19
		rbuf = rcs_patchfile(data, dlen, patch, plen, rcs_patch_lines);
1017
19
		free(data);
1018
1019
19
		if (rbuf == NULL)
1020
			break;
1021
19
	} while (rcsnum_cmp(crev, rev, 0) != 0);
1022
1023
98
	return (rbuf);
1024
125
}
1025
1026
void
1027
rcs_delta_stats(struct rcs_delta *rdp, int *ladded, int *lremoved)
1028
{
1029
	struct rcs_lines *plines;
1030
	struct rcs_line *lp;
1031
	int added, i, nbln, removed;
1032
12
	char op, *ep;
1033
	u_char tmp;
1034
1035
	added = removed = 0;
1036
1037
6
	plines = rcs_splitlines(rdp->rd_text, rdp->rd_tlen);
1038
6
	lp = TAILQ_FIRST(&(plines->l_lines));
1039
1040
	/* skip first bogus line */
1041
24
	for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
1042
6
		lp = TAILQ_NEXT(lp, l_list)) {
1043
6
			if (lp->l_len < 2)
1044
				errx(1,
1045
				    "line too short, RCS patch seems broken");
1046
6
			op = *(lp->l_line);
1047
			/* NUL-terminate line buffer for strtol() safety. */
1048
6
			tmp = lp->l_line[lp->l_len - 1];
1049
6
			lp->l_line[lp->l_len - 1] = '\0';
1050
6
			(void)strtol((lp->l_line + 1), &ep, 10);
1051
6
			ep++;
1052
6
			nbln = (int)strtol(ep, &ep, 10);
1053
			/* Restore the last byte of the buffer */
1054
6
			lp->l_line[lp->l_len - 1] = tmp;
1055
6
			if (nbln < 0)
1056
				errx(1, "invalid line number specification "
1057
				    "in RCS patch");
1058
1059
6
			if (op == 'a') {
1060
				added += nbln;
1061
				for (i = 0; i < nbln; i++) {
1062
					lp = TAILQ_NEXT(lp, l_list);
1063
					if (lp == NULL)
1064
						errx(1, "truncated RCS patch");
1065
				}
1066
6
			} else if (op == 'd')
1067
6
				removed += nbln;
1068
			else
1069
				errx(1, "unknown RCS patch operation '%c'", op);
1070
	}
1071
1072
6
	rcs_freelines(plines);
1073
1074
6
	*ladded = added;
1075
6
	*lremoved = removed;
1076
6
}
1077
1078
/*
1079
 * rcs_rev_add()
1080
 *
1081
 * Add a revision to the RCS file <rf>.  The new revision's number can be
1082
 * specified in <rev> (which can also be RCS_HEAD_REV, in which case the
1083
 * new revision will have a number equal to the previous head revision plus
1084
 * one).  The <msg> argument specifies the log message for that revision, and
1085
 * <date> specifies the revision's date (a value of -1 is
1086
 * equivalent to using the current time).
1087
 * If <author> is NULL, set the author for this revision to the current user.
1088
 * Returns 0 on success, or -1 on failure.
1089
 */
1090
int
1091
rcs_rev_add(RCSFILE *rf, RCSNUM *rev, const char *msg, time_t date,
1092
    const char *author)
1093
{
1094
110
	time_t now;
1095
	struct passwd *pw;
1096
	struct rcs_delta *ordp, *rdp;
1097
1098
55
	if (rev == RCS_HEAD_REV) {
1099
54
		if (rf->rf_flags & RCS_CREATE) {
1100
31
			if ((rev = rcsnum_parse(RCS_HEAD_INIT)) == NULL)
1101
				return (-1);
1102
31
			rf->rf_head = rev;
1103
31
		} else {
1104
23
			rev = rcsnum_inc(rf->rf_head);
1105
		}
1106
	} else {
1107
1
		if ((rdp = rcs_findrev(rf, rev)) != NULL) {
1108
			rcs_errno = RCS_ERR_DUPENT;
1109
			return (-1);
1110
		}
1111
	}
1112
1113
55
	rdp = xcalloc(1, sizeof(*rdp));
1114
1115
55
	TAILQ_INIT(&(rdp->rd_branches));
1116
1117
55
	rdp->rd_num = rcsnum_alloc();
1118
55
	rcsnum_cpy(rev, rdp->rd_num, 0);
1119
1120
55
	rdp->rd_next = rcsnum_alloc();
1121
1122
55
	if (!(rf->rf_flags & RCS_CREATE)) {
1123
		/* next should point to the previous HEAD */
1124
24
		ordp = TAILQ_FIRST(&(rf->rf_delta));
1125
24
		rcsnum_cpy(ordp->rd_num, rdp->rd_next, 0);
1126
24
	}
1127
1128

108
	if (!author && !(author = getlogin())) {
1129
		if (!(pw = getpwuid(getuid())))
1130
			errx(1, "getpwuid failed");
1131
		author = pw->pw_name;
1132
	}
1133
55
	rdp->rd_author = xstrdup(author);
1134
55
	rdp->rd_state = xstrdup(RCS_STATE_EXP);
1135
55
	rdp->rd_log = xstrdup(msg);
1136
1137
55
	if (date != (time_t)(-1))
1138
2
		now = date;
1139
	else
1140
53
		time(&now);
1141
55
	gmtime_r(&now, &(rdp->rd_date));
1142
1143
165
	TAILQ_INSERT_HEAD(&(rf->rf_delta), rdp, rd_list);
1144
55
	rf->rf_ndelta++;
1145
1146
	/* not synced anymore */
1147
55
	rf->rf_flags &= ~RCS_SYNCED;
1148
1149
55
	return (0);
1150
55
}
1151
1152
/*
1153
 * rcs_rev_remove()
1154
 *
1155
 * Remove the revision whose number is <rev> from the RCS file <rf>.
1156
 */
1157
int
1158
rcs_rev_remove(RCSFILE *rf, RCSNUM *rev)
1159
{
1160
6
	char *path_tmp1, *path_tmp2;
1161
	struct rcs_delta *rdp, *prevrdp, *nextrdp;
1162
	BUF *newdeltatext, *nextbuf, *prevbuf, *newdiff;
1163
1164
	nextrdp = prevrdp = NULL;
1165
3
	path_tmp1 = path_tmp2 = NULL;
1166
1167
3
	if (rev == RCS_HEAD_REV)
1168
		rev = rf->rf_head;
1169
1170
	/* do we actually have that revision? */
1171
3
	if ((rdp = rcs_findrev(rf, rev)) == NULL) {
1172
		rcs_errno = RCS_ERR_NOENT;
1173
		return (-1);
1174
	}
1175
1176
	/*
1177
	 * This is confusing, the previous delta is next in the TAILQ list.
1178
	 * the next delta is the previous one in the TAILQ list.
1179
	 *
1180
	 * When the HEAD revision got specified, nextrdp will be NULL.
1181
	 * When the first revision got specified, prevrdp will be NULL.
1182
	 */
1183
3
	prevrdp = (struct rcs_delta *)TAILQ_NEXT(rdp, rd_list);
1184
3
	nextrdp = (struct rcs_delta *)TAILQ_PREV(rdp, tqh, rd_list);
1185
1186
	newdeltatext = prevbuf = nextbuf = NULL;
1187
1188
3
	if (prevrdp != NULL) {
1189
3
		if ((prevbuf = rcs_getrev(rf, prevrdp->rd_num)) == NULL)
1190
			errx(1, "error getting revision");
1191
	}
1192
1193
3
	if (prevrdp != NULL && nextrdp != NULL) {
1194
3
		if ((nextbuf = rcs_getrev(rf, nextrdp->rd_num)) == NULL)
1195
			errx(1, "error getting revision");
1196
1197
3
		newdiff = buf_alloc(64);
1198
1199
		/* calculate new diff */
1200
3
		(void)xasprintf(&path_tmp1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir);
1201
3
		buf_write_stmp(nextbuf, path_tmp1);
1202
3
		buf_free(nextbuf);
1203
1204
3
		(void)xasprintf(&path_tmp2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir);
1205
3
		buf_write_stmp(prevbuf, path_tmp2);
1206
3
		buf_free(prevbuf);
1207
1208
3
		diff_format = D_RCSDIFF;
1209
3
		if (diffreg(path_tmp1, path_tmp2, newdiff, D_FORCEASCII) == D_ERROR)
1210
			errx(1, "diffreg failed");
1211
1212
		newdeltatext = newdiff;
1213
3
	} else if (nextrdp == NULL && prevrdp != NULL) {
1214
		newdeltatext = prevbuf;
1215
	}
1216
1217
3
	if (newdeltatext != NULL) {
1218
3
		if (rcs_deltatext_set(rf, prevrdp->rd_num, newdeltatext) < 0)
1219
			errx(1, "error setting new deltatext");
1220
	}
1221
1222
9
	TAILQ_REMOVE(&(rf->rf_delta), rdp, rd_list);
1223
1224
	/* update pointers */
1225
3
	if (prevrdp != NULL && nextrdp != NULL) {
1226
3
		rcsnum_cpy(prevrdp->rd_num, nextrdp->rd_next, 0);
1227
3
	} else if (prevrdp != NULL) {
1228
		if (rcs_head_set(rf, prevrdp->rd_num) < 0)
1229
			errx(1, "rcs_head_set failed");
1230
	} else if (nextrdp != NULL) {
1231
		rcsnum_free(nextrdp->rd_next);
1232
		nextrdp->rd_next = rcsnum_alloc();
1233
	} else {
1234
		rcsnum_free(rf->rf_head);
1235
		rf->rf_head = NULL;
1236
	}
1237
1238
3
	rf->rf_ndelta--;
1239
3
	rf->rf_flags &= ~RCS_SYNCED;
1240
1241
3
	rcs_freedelta(rdp);
1242
1243
3
	free(path_tmp1);
1244
3
	free(path_tmp2);
1245
1246
3
	return (0);
1247
3
}
1248
1249
/*
1250
 * rcs_findrev()
1251
 *
1252
 * Find a specific revision's delta entry in the tree of the RCS file <rfp>.
1253
 * The revision number is given in <rev>.
1254
 *
1255
 * If the given revision is a branch number, we translate it into the latest
1256
 * revision on the branch.
1257
 *
1258
 * Returns a pointer to the delta on success, or NULL on failure.
1259
 */
1260
struct rcs_delta *
1261
rcs_findrev(RCSFILE *rfp, RCSNUM *rev)
1262
{
1263
	u_int cmplen;
1264
	struct rcs_delta *rdp;
1265
	RCSNUM *brev, *frev;
1266
1267
	/*
1268
	 * We need to do more parsing if the last revision in the linked list
1269
	 * is greater than the requested revision.
1270
	 */
1271
972
	rdp = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
1272

966
	if (rdp == NULL ||
1273
480
	    rcsnum_cmp(rdp->rd_num, rev, 0) == -1) {
1274
6
		if (rcsparse_deltas(rfp, rev))
1275
			return (NULL);
1276
	}
1277
1278
	/*
1279
	 * Translate a branch into the latest revision on the branch itself.
1280
	 */
1281
486
	if (RCSNUM_ISBRANCH(rev)) {
1282
		brev = rcsnum_brtorev(rev);
1283
		frev = brev;
1284
		for (;;) {
1285
			rdp = rcs_findrev(rfp, frev);
1286
			if (rdp == NULL)
1287
				return (NULL);
1288
1289
			if (rdp->rd_next->rn_len == 0)
1290
				break;
1291
1292
			frev = rdp->rd_next;
1293
		}
1294
1295
		rcsnum_free(brev);
1296
		return (rdp);
1297
	}
1298
1299
	cmplen = rev->rn_len;
1300
1301
1152
	TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
1302
573
		if (rcsnum_cmp(rdp->rd_num, rev, cmplen) == 0)
1303
483
			return (rdp);
1304
	}
1305
1306
3
	return (NULL);
1307
486
}
1308
1309
/*
1310
 * rcs_kwexp_set()
1311
 *
1312
 * Set the keyword expansion mode to use on the RCS file <file> to <mode>.
1313
 */
1314
void
1315
rcs_kwexp_set(RCSFILE *file, int mode)
1316
{
1317
	int i;
1318
176
	char *tmp, buf[8] = "";
1319
1320

88
	if (RCS_KWEXP_INVAL(mode))
1321
88
		return;
1322
1323
	i = 0;
1324
	if (mode == RCS_KWEXP_NONE)
1325
		buf[0] = 'b';
1326
	else if (mode == RCS_KWEXP_OLD)
1327
		buf[0] = 'o';
1328
	else {
1329
		if (mode & RCS_KWEXP_NAME)
1330
			buf[i++] = 'k';
1331
		if (mode & RCS_KWEXP_VAL)
1332
			buf[i++] = 'v';
1333
		if (mode & RCS_KWEXP_LKR)
1334
			buf[i++] = 'l';
1335
	}
1336
1337
	tmp = xstrdup(buf);
1338
	free(file->rf_expand);
1339
	file->rf_expand = tmp;
1340
	/* not synced anymore */
1341
	file->rf_flags &= ~RCS_SYNCED;
1342
88
}
1343
1344
/*
1345
 * rcs_kwexp_get()
1346
 *
1347
 * Retrieve the keyword expansion mode to be used for the RCS file <file>.
1348
 */
1349
int
1350
rcs_kwexp_get(RCSFILE *file)
1351
{
1352
216
	if (file->rf_expand == NULL)
1353
108
		return (RCS_KWEXP_DEFAULT);
1354
1355
	return (rcs_kflag_get(file->rf_expand));
1356
108
}
1357
1358
/*
1359
 * rcs_kflag_get()
1360
 *
1361
 * Get the keyword expansion mode from a set of character flags given in
1362
 * <flags> and return the appropriate flag mask.  In case of an error, the
1363
 * returned mask will have the RCS_KWEXP_ERR bit set to 1.
1364
 */
1365
int
1366
rcs_kflag_get(const char *flags)
1367
{
1368
	int fl;
1369
	size_t len;
1370
	const char *fp;
1371
1372
	if (flags == NULL || !(len = strlen(flags)))
1373
		return (RCS_KWEXP_ERR);
1374
1375
	fl = 0;
1376
	for (fp = flags; *fp != '\0'; fp++) {
1377
		if (*fp == 'k')
1378
			fl |= RCS_KWEXP_NAME;
1379
		else if (*fp == 'v')
1380
			fl |= RCS_KWEXP_VAL;
1381
		else if (*fp == 'l')
1382
			fl |= RCS_KWEXP_LKR;
1383
		else if (*fp == 'o') {
1384
			if (len != 1)
1385
				fl |= RCS_KWEXP_ERR;
1386
			fl |= RCS_KWEXP_OLD;
1387
		} else if (*fp == 'b') {
1388
			if (len != 1)
1389
				fl |= RCS_KWEXP_ERR;
1390
			fl |= RCS_KWEXP_NONE;
1391
		} else	/* unknown letter */
1392
			fl |= RCS_KWEXP_ERR;
1393
	}
1394
1395
	return (fl);
1396
}
1397
1398
/*
1399
 * rcs_freedelta()
1400
 *
1401
 * Free the contents of a delta structure.
1402
 */
1403
static void
1404
rcs_freedelta(struct rcs_delta *rdp)
1405
{
1406
	struct rcs_branch *rb;
1407
1408
606
	rcsnum_free(rdp->rd_num);
1409
303
	rcsnum_free(rdp->rd_next);
1410
1411
303
	free(rdp->rd_author);
1412
303
	free(rdp->rd_locker);
1413
303
	free(rdp->rd_state);
1414
303
	free(rdp->rd_log);
1415
303
	free(rdp->rd_text);
1416
1417
606
	while ((rb = TAILQ_FIRST(&(rdp->rd_branches))) != NULL) {
1418
		TAILQ_REMOVE(&(rdp->rd_branches), rb, rb_list);
1419
		rcsnum_free(rb->rb_num);
1420
		free(rb);
1421
	}
1422
1423
303
	free(rdp);
1424
303
}
1425
1426
/*
1427
 * rcs_strprint()
1428
 *
1429
 * Output an RCS string <str> of size <slen> to the stream <stream>.  Any
1430
 * '@' characters are escaped.  Otherwise, the string can contain arbitrary
1431
 * binary data.
1432
 */
1433
static void
1434
rcs_strprint(const u_char *str, size_t slen, FILE *stream)
1435
{
1436
	const u_char *ap, *ep, *sp;
1437
1438
1316
	if (slen == 0)
1439
7
		return;
1440
1441
651
	ep = str + slen - 1;
1442
1443
2604
	for (sp = str; sp <= ep;)  {
1444
651
		ap = memchr(sp, '@', ep - sp);
1445
651
		if (ap == NULL)
1446
651
			ap = ep;
1447
651
		(void)fwrite(sp, sizeof(u_char), ap - sp + 1, stream);
1448
1449
651
		if (*ap == '@')
1450
			putc('@', stream);
1451
651
		sp = ap + 1;
1452
	}
1453
1309
}
1454
1455
/*
1456
 * rcs_expand_keywords()
1457
 *
1458
 * Return expansion any RCS keywords in <data>
1459
 *
1460
 * On error, return NULL.
1461
 */
1462
static BUF *
1463
rcs_expand_keywords(char *rcsfile_in, struct rcs_delta *rdp, BUF *bp, int mode)
1464
{
1465
	BUF *newbuf;
1466
	u_char *c, *kw, *fin;
1467
216
	char buf[256], *tmpf, resolved[PATH_MAX], *rcsfile;
1468
	u_char *line, *line2;
1469
	u_int i, j;
1470
	int kwtype;
1471
	int found;
1472
108
	struct tm tb;
1473
1474
108
	tb = rdp->rd_date;
1475
108
	if (timezone_flag != NULL)
1476
		rcs_set_tz(timezone_flag, rdp, &tb);
1477
1478
108
	if (realpath(rcsfile_in, resolved) == NULL)
1479
		rcsfile = rcsfile_in;
1480
	else
1481
		rcsfile = resolved;
1482
1483
108
	newbuf = buf_alloc(buf_len(bp));
1484
1485
	/*
1486
	 * Keyword formats:
1487
	 * $Keyword$
1488
	 * $Keyword: value$
1489
	 */
1490
108
	c = buf_get(bp);
1491
108
	fin = c + buf_len(bp);
1492
	/* Copying to newbuf is deferred until the first keyword. */
1493
	found = 0;
1494
1495
10225
	while (c < fin) {
1496
10097
		kw = memchr(c, '$', fin - c);
1497
10097
		if (kw == NULL)
1498
			break;
1499
10009
		++kw;
1500
10009
		if (found) {
1501
			/* Copy everything up to and including the $. */
1502
10007
			buf_append(newbuf, c, kw - c);
1503
10007
		}
1504
		c = kw;
1505
		/* c points after the $ now. */
1506
10009
		if (c == fin)
1507
			break;
1508
10009
		if (!isalpha(*c)) /* all valid keywords start with a letter */
1509
			continue;
1510
1511
100124
		for (i = 0; i < RCS_NKWORDS; ++i) {
1512
			size_t kwlen;
1513
1514
50062
			kwlen = strlen(rcs_expkw[i].kw_str);
1515
			/*
1516
			 * kwlen must be less than clen since clen includes
1517
			 * either a terminating `$' or a `:'.
1518
			 */
1519

50062
			if (c + kwlen < fin &&
1520
50055
			    memcmp(c , rcs_expkw[i].kw_str, kwlen) == 0 &&
1521
10009
			    (c[kwlen] == '$' || c[kwlen] == ':')) {
1522
				c += kwlen;
1523
10009
				break;
1524
			}
1525
40053
		}
1526
10009
		if (i == RCS_NKWORDS)
1527
			continue;
1528
10009
		kwtype = rcs_expkw[i].kw_type;
1529
1530
		/*
1531
		 * If the next character is ':' we need to look for an '$'
1532
		 * before the end of the line to be sure it is in fact a
1533
		 * keyword.
1534
		 */
1535
10009
		if (*c == ':') {
1536
			for (; c < fin; ++c) {
1537
				if (*c == '$' || *c == '\n')
1538
					break;
1539
			}
1540
1541
			if (*c != '$') {
1542
				if (found)
1543
					buf_append(newbuf, kw, c - kw);
1544
				continue;
1545
			}
1546
		}
1547
10009
		++c;
1548
1549
10009
		if (!found) {
1550
			found = 1;
1551
			/* Copy everything up to and including the $. */
1552
2
			buf_append(newbuf, buf_get(bp), kw - buf_get(bp));
1553
2
		}
1554
1555
10009
		if (mode & RCS_KWEXP_NAME) {
1556
10009
			buf_puts(newbuf, rcs_expkw[i].kw_str);
1557
10009
			if (mode & RCS_KWEXP_VAL)
1558
10009
				buf_puts(newbuf, ": ");
1559
		}
1560
1561
		/* Order matters because of RCS_KW_ID and RCS_KW_HEADER. */
1562
10009
		if (mode & RCS_KWEXP_VAL) {
1563
10009
			if (kwtype & (RCS_KW_RCSFILE|RCS_KW_LOG)) {
1564

20005
				if ((kwtype & RCS_KW_FULLPATH) ||
1565
10002
				    (tmpf = strrchr(rcsfile, '/')) == NULL)
1566
1
					buf_puts(newbuf, rcsfile);
1567
				else
1568
10002
					buf_puts(newbuf, tmpf + 1);
1569
10003
				buf_putc(newbuf, ' ');
1570
10003
			}
1571
1572
10009
			if (kwtype & RCS_KW_REVISION) {
1573
10003
				rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
1574
10003
				buf_puts(newbuf, buf);
1575
10003
				buf_putc(newbuf, ' ');
1576
10003
			}
1577
1578
10009
			if (kwtype & RCS_KW_DATE) {
1579
10003
				strftime(buf, sizeof(buf),
1580
				    "%Y/%m/%d %H:%M:%S ", &tb);
1581
10003
				buf_puts(newbuf, buf);
1582
10003
			}
1583
1584
10009
			if (kwtype & RCS_KW_AUTHOR) {
1585
10003
				buf_puts(newbuf, rdp->rd_author);
1586
10003
				buf_putc(newbuf, ' ');
1587
10003
			}
1588
1589
10009
			if (kwtype & RCS_KW_STATE) {
1590
10003
				buf_puts(newbuf, rdp->rd_state);
1591
10003
				buf_putc(newbuf, ' ');
1592
10003
			}
1593
1594
			/* Order does not matter anymore below. */
1595
10009
			if (kwtype & RCS_KW_SOURCE) {
1596
1
				buf_puts(newbuf, rcsfile);
1597
1
				buf_putc(newbuf, ' ');
1598
1
			}
1599
1600
10009
			if (kwtype & RCS_KW_MDOCDATE) {
1601
				strftime(buf, sizeof(buf), "%B", &tb);
1602
				buf_puts(newbuf, buf);
1603
				/* Only one blank before single-digit day. */
1604
				snprintf(buf, sizeof(buf), " %d", tb.tm_mday);
1605
				buf_puts(newbuf, buf);
1606
				strftime(buf, sizeof(buf), " %Y ", &tb);
1607
				buf_puts(newbuf, buf);
1608
			}
1609
1610
10009
			if (kwtype & RCS_KW_NAME)
1611
1
				buf_putc(newbuf, ' ');
1612
1613
10009
			if ((kwtype & RCS_KW_LOCKER)) {
1614
10002
				if (rdp->rd_locker) {
1615
					buf_puts(newbuf, rdp->rd_locker);
1616
					buf_putc(newbuf, ' ');
1617
				}
1618
			}
1619
		}
1620
1621
		/* End the expansion. */
1622
10009
		if (mode & RCS_KWEXP_NAME)
1623
10009
			buf_putc(newbuf, '$');
1624
1625
10009
		if (kwtype & RCS_KW_LOG) {
1626
			line = memrchr(buf_get(bp), '\n', kw - buf_get(bp) - 1);
1627
			if (line == NULL)
1628
				line = buf_get(bp);
1629
			else
1630
				++line;
1631
			line2 = kw - 1;
1632
			while (line2 > line && line2[-1] == ' ')
1633
				--line2;
1634
1635
			buf_putc(newbuf, '\n');
1636
			buf_append(newbuf, line, kw - 1 - line);
1637
			buf_puts(newbuf, "Revision ");
1638
			rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
1639
			buf_puts(newbuf, buf);
1640
			buf_puts(newbuf, "  ");
1641
			strftime(buf, sizeof(buf), "%Y/%m/%d %H:%M:%S", &tb);
1642
			buf_puts(newbuf, buf);
1643
1644
			buf_puts(newbuf, "  ");
1645
			buf_puts(newbuf, rdp->rd_author);
1646
			buf_putc(newbuf, '\n');
1647
1648
			for (i = 0; rdp->rd_log[i]; i += j) {
1649
				j = strcspn(rdp->rd_log + i, "\n");
1650
				if (j == 0)
1651
					buf_append(newbuf, line, line2 - line);
1652
				else
1653
					buf_append(newbuf, line, kw - 1 - line);
1654
				if (rdp->rd_log[i + j])
1655
					++j;
1656
				buf_append(newbuf, rdp->rd_log + i, j);
1657
			}
1658
			buf_append(newbuf, line, line2 - line);
1659
			for (j = 0; c + j < fin; ++j) {
1660
				if (c[j] != ' ')
1661
					break;
1662
			}
1663
			if (c + j == fin || c[j] == '\n')
1664
				c += j;
1665
		}
1666
	}
1667
1668
108
	if (found) {
1669
2
		buf_append(newbuf, c, fin - c);
1670
2
		buf_free(bp);
1671
2
		return (newbuf);
1672
	} else {
1673
106
		buf_free(newbuf);
1674
106
		return (bp);
1675
	}
1676
108
}
1677
1678
/*
1679
 * rcs_deltatext_set()
1680
 *
1681
 * Set deltatext for <rev> in RCS file <rfp> to <dtext>
1682
 * Returns -1 on error, 0 on success.
1683
 */
1684
int
1685
rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, BUF *bp)
1686
{
1687
	size_t len;
1688
	u_char *dtext;
1689
	struct rcs_delta *rdp;
1690
1691
	/* Write operations require full parsing */
1692
164
	if (rcsparse_deltatexts(rfp, NULL))
1693
		return (-1);
1694
1695
82
	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1696
		return (-1);
1697
1698
82
	free(rdp->rd_text);
1699
1700
82
	len = buf_len(bp);
1701
82
	dtext = buf_release(bp);
1702
	bp = NULL;
1703
1704
82
	if (len != 0) {
1705
74
		rdp->rd_text = xmalloc(len);
1706
74
		rdp->rd_tlen = len;
1707
74
		(void)memcpy(rdp->rd_text, dtext, len);
1708
74
	} else {
1709
8
		rdp->rd_text = NULL;
1710
8
		rdp->rd_tlen = 0;
1711
	}
1712
1713
82
	free(dtext);
1714
1715
82
	return (0);
1716
82
}
1717
1718
/*
1719
 * rcs_rev_setlog()
1720
 *
1721
 * Sets the log message of revision <rev> to <logtext>.
1722
 */
1723
int
1724
rcs_rev_setlog(RCSFILE *rfp, RCSNUM *rev, const char *logtext)
1725
{
1726
	struct rcs_delta *rdp;
1727
1728
4
	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1729
		return (-1);
1730
1731
2
	free(rdp->rd_log);
1732
1733
2
	rdp->rd_log = xstrdup(logtext);
1734
2
	rfp->rf_flags &= ~RCS_SYNCED;
1735
2
	return (0);
1736
2
}
1737
/*
1738
 * rcs_rev_getdate()
1739
 *
1740
 * Get the date corresponding to a given revision.
1741
 * Returns the date on success, -1 on failure.
1742
 */
1743
time_t
1744
rcs_rev_getdate(RCSFILE *rfp, RCSNUM *rev)
1745
{
1746
	struct rcs_delta *rdp;
1747
1748
16
	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1749
		return (-1);
1750
1751
8
	return (mktime(&rdp->rd_date));
1752
8
}
1753
1754
/*
1755
 * rcs_state_set()
1756
 *
1757
 * Sets the state of revision <rev> to <state>
1758
 * NOTE: default state is 'Exp'. States may not contain spaces.
1759
 *
1760
 * Returns -1 on failure, 0 on success.
1761
 */
1762
int
1763
rcs_state_set(RCSFILE *rfp, RCSNUM *rev, const char *state)
1764
{
1765
	struct rcs_delta *rdp;
1766
1767
	if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1768
		return (-1);
1769
1770
	free(rdp->rd_state);
1771
1772
	rdp->rd_state = xstrdup(state);
1773
1774
	rfp->rf_flags &= ~RCS_SYNCED;
1775
1776
	return (0);
1777
}
1778
1779
/*
1780
 * rcs_state_check()
1781
 *
1782
 * Check if string <state> is valid.
1783
 *
1784
 * Returns 0 if the string is valid, -1 otherwise.
1785
 */
1786
int
1787
rcs_state_check(const char *state)
1788
{
1789
	int ret;
1790
	const unsigned char *cp;
1791
1792
	ret = 0;
1793
	cp = state;
1794
522
	if (!isalpha(*cp++))
1795
		return (-1);
1796
1797
1309
	for (; *cp != '\0'; cp++)
1798

1049
		if (!isgraph(*cp) || (strchr(rcs_state_invch, *cp) != NULL)) {
1799
			ret = -1;
1800
1
			break;
1801
		}
1802
1803
261
	return (ret);
1804
261
}
1805
1806
/*
1807
 * rcs_kwexp_buf()
1808
 *
1809
 * Do keyword expansion on a buffer if necessary
1810
 *
1811
 */
1812
BUF *
1813
rcs_kwexp_buf(BUF *bp, RCSFILE *rf, RCSNUM *rev)
1814
{
1815
	struct rcs_delta *rdp;
1816
	int expmode;
1817
1818
	/*
1819
	 * Do keyword expansion if required.
1820
	 */
1821
216
	expmode = rcs_kwexp_get(rf);
1822
1823
108
	if (!(expmode & RCS_KWEXP_NONE)) {
1824
108
		if ((rdp = rcs_findrev(rf, rev)) == NULL)
1825
			errx(1, "could not fetch revision");
1826
108
		return (rcs_expand_keywords(rf->rf_path, rdp, bp, expmode));
1827
	}
1828
	return (bp);
1829
108
}