GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/rcs/rcsutil.c Lines: 193 264 73.1 %
Date: 2017-11-07 Branches: 129 220 58.6 %

Line Branch Exec Source
1
/*	$OpenBSD: rcsutil.c,v 1.46 2017/08/29 16:47:33 otto Exp $	*/
2
/*
3
 * Copyright (c) 2005, 2006 Joris Vink <joris@openbsd.org>
4
 * Copyright (c) 2006 Xavier Santolaria <xsa@openbsd.org>
5
 * Copyright (c) 2006 Niall O'Higgins <niallo@openbsd.org>
6
 * Copyright (c) 2006 Ray Lai <ray@openbsd.org>
7
 * All rights reserved.
8
 *
9
 * Redistribution and use in source and binary forms, with or without
10
 * modification, are permitted provided that the following conditions
11
 * are met:
12
 *
13
 * 1. Redistributions of source code must retain the above copyright
14
 *    notice, this list of conditions and the following disclaimer.
15
 * 2. The name of the author may not be used to endorse or promote products
16
 *    derived from this software without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
19
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
20
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
24
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
27
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29
30
#include <sys/stat.h>
31
#include <sys/time.h>
32
33
#include <ctype.h>
34
#include <err.h>
35
#include <fcntl.h>
36
#include <stdio.h>
37
#include <stdlib.h>
38
#include <string.h>
39
#include <unistd.h>
40
41
#include "rcsprog.h"
42
43
/*
44
 * rcs_get_mtime()
45
 *
46
 * Get <filename> last modified time.
47
 * Returns last modified time on success, or -1 on failure.
48
 */
49
time_t
50
rcs_get_mtime(RCSFILE *file)
51
{
52
	struct stat st;
53
	time_t mtime;
54
55
	if (file->rf_file == NULL)
56
		return (-1);
57
58
	if (fstat(fileno(file->rf_file), &st) == -1) {
59
		warn("%s", file->rf_path);
60
		return (-1);
61
	}
62
63
	mtime = st.st_mtimespec.tv_sec;
64
65
	return (mtime);
66
}
67
68
/*
69
 * rcs_set_mtime()
70
 *
71
 * Set <filename> last modified time to <mtime> if it's not set to -1.
72
 */
73
void
74
rcs_set_mtime(RCSFILE *file, time_t mtime)
75
{
76
	static struct timeval tv[2];
77
78
	if (file->rf_file == NULL || mtime == -1)
79
		return;
80
81
	tv[0].tv_sec = mtime;
82
	tv[1].tv_sec = tv[0].tv_sec;
83
84
	if (futimes(fileno(file->rf_file), tv) == -1)
85
		err(1, "utimes");
86
}
87
88
int
89
rcs_getopt(int argc, char **argv, const char *optstr)
90
{
91
	char *a;
92
	const char *c;
93
	static int i = 1;
94
	int opt, hasargument, ret;
95
96
	hasargument = 0;
97
1000
	rcs_optarg = NULL;
98
99
500
	if (i >= argc)
100
		return (-1);
101
102
500
	a = argv[i++];
103
500
	if (*a++ != '-')
104
167
		return (-1);
105
106
	ret = 0;
107
333
	opt = *a;
108
15098
	for (c = optstr; *c != '\0'; c++) {
109
7549
		if (*c == opt) {
110
333
			a++;
111
			ret = opt;
112
113
333
			if (*(c + 1) == ':') {
114
225
				if (*(c + 2) == ':') {
115
200
					if (*a != '\0')
116
52
						hasargument = 1;
117
				} else {
118
25
					if (*a != '\0') {
119
						hasargument = 1;
120
					} else {
121
						ret = 1;
122
						break;
123
					}
124
				}
125
			}
126
127
333
			if (hasargument == 1)
128
77
				rcs_optarg = a;
129
130
333
			if (ret == opt)
131
333
				rcs_optind++;
132
			break;
133
		}
134
	}
135
136
333
	if (ret == 0)
137
		warnx("unknown option -%c", opt);
138
333
	else if (ret == 1)
139
		warnx("missing argument for option -%c", opt);
140
141
333
	return (ret);
142
500
}
143
144
/*
145
 * rcs_choosefile()
146
 *
147
 * Given a relative filename, decide where the corresponding RCS file
148
 * should be.  Tries each extension until a file is found.  If no file
149
 * was found, returns a path with the first extension.
150
 *
151
 * Opens and returns file descriptor to RCS file.
152
 */
153
int
154
rcs_choosefile(const char *filename, char *out, size_t len)
155
{
156
	int fd;
157
336
	struct stat sb;
158
168
	char *p, *ext, name[PATH_MAX], *next, *ptr, rcsdir[PATH_MAX],
159
	    *suffixes, rcspath[PATH_MAX];
160
161
	/*
162
	 * If `filename' contains a directory, `rcspath' contains that
163
	 * directory, including a trailing slash.  Otherwise `rcspath'
164
	 * contains an empty string.
165
	 */
166
168
	if (strlcpy(rcspath, filename, sizeof(rcspath)) >= sizeof(rcspath))
167
		errx(1, "rcs_choosefile: truncation");
168
169
	/* If `/' is found, end string after `/'. */
170
168
	if ((ptr = strrchr(rcspath, '/')) != NULL)
171
1
		*(++ptr) = '\0';
172
	else
173
167
		rcspath[0] = '\0';
174
175
	/* Append RCS/ to `rcspath' if it exists. */
176

336
	if (strlcpy(rcsdir, rcspath, sizeof(rcsdir)) >= sizeof(rcsdir) ||
177
168
	    strlcat(rcsdir, RCSDIR, sizeof(rcsdir)) >= sizeof(rcsdir))
178
		errx(1, "rcs_choosefile: truncation");
179
180

246
	if (stat(rcsdir, &sb) == 0 && S_ISDIR(sb.st_mode))
181
156
		if (strlcpy(rcspath, rcsdir, sizeof(rcspath))
182
78
		    >= sizeof(rcspath) ||
183
78
		    strlcat(rcspath, "/", sizeof(rcspath)) >= sizeof(rcspath))
184
			errx(1, "rcs_choosefile: truncation");
185
186
	/* Name of file without path. */
187
168
	if ((ptr = strrchr(filename, '/')) == NULL) {
188
167
		if (strlcpy(name, filename, sizeof(name)) >= sizeof(name))
189
			errx(1, "rcs_choosefile: truncation");
190
	} else {
191
		/* Skip `/'. */
192
1
		if (strlcpy(name, ptr + 1, sizeof(name)) >= sizeof(name))
193
			errx(1, "rcs_choosefile: truncation");
194
	}
195
196
	/* Name of RCS file without an extension. */
197
168
	if (strlcat(rcspath, name, sizeof(rcspath)) >= sizeof(rcspath))
198
		errx(1, "rcs_choosefile: truncation");
199
200
	/*
201
	 * If only the empty suffix was given, use existing rcspath.
202
	 * This ensures that there is at least one suffix for strsep().
203
	 */
204
168
	if (strcmp(rcs_suffixes, "") == 0) {
205
1
		if (strlcpy(out, rcspath, len) >= len)
206
			errx(1, "rcs_choosefile: truncation");
207
1
		fd = open(rcspath, O_RDONLY);
208
1
		return (fd);
209
	}
210
211
	/*
212
	 * Cycle through slash-separated `rcs_suffixes', appending each
213
	 * extension to `rcspath' and testing if the file exists.  If it
214
	 * does, return that string.  Otherwise return path with first
215
	 * extension.
216
	 */
217
167
	suffixes = xstrdup(rcs_suffixes);
218
419
	for (next = suffixes; (ext = strsep(&next, "/")) != NULL;) {
219
210
		char fpath[PATH_MAX];
220
221
210
		if ((p = strrchr(rcspath, ',')) != NULL) {
222
2
			if (!strcmp(p, ext)) {
223
				if ((fd = open(rcspath, O_RDONLY)) == -1)
224
					continue;
225
226
				if (fstat(fd, &sb) == -1)
227
					err(1, "%s", rcspath);
228
229
				if (strlcpy(out, rcspath, len) >= len)
230
					errx(1, "rcs_choosefile; truncation");
231
232
				free(suffixes);
233
				return (fd);
234
			}
235
236
2
			continue;
237
		}
238
239
		/* Construct RCS file path. */
240

416
		if (strlcpy(fpath, rcspath, sizeof(fpath)) >= sizeof(fpath) ||
241
208
		    strlcat(fpath, ext, sizeof(fpath)) >= sizeof(fpath))
242
			errx(1, "rcs_choosefile: truncation");
243
244
		/* Don't use `filename' as RCS file. */
245
208
		if (strcmp(fpath, filename) == 0)
246
24
			continue;
247
248
184
		if ((fd = open(fpath, O_RDONLY)) == -1)
249
59
			continue;
250
251
125
		if (fstat(fd, &sb) == -1)
252
			err(1, "%s", fpath);
253
254
125
		if (strlcpy(out, fpath, len) >= len)
255
			errx(1, "rcs_choosefile: truncation");
256
257
125
		free(suffixes);
258
125
		return (fd);
259
210
	}
260
261
	/*
262
	 * `suffixes' should now be NUL separated, so the first
263
	 * extension can be read just by reading `suffixes'.
264
	 */
265
42
	if (strlcat(rcspath, suffixes, sizeof(rcspath)) >= sizeof(rcspath))
266
		errx(1, "rcs_choosefile: truncation");
267
268
42
	free(suffixes);
269
270
42
	if (strlcpy(out, rcspath, len) >= len)
271
		errx(1, "rcs_choosefile: truncation");
272
273
42
	fd = open(rcspath, O_RDONLY);
274
275
42
	return (fd);
276
168
}
277
278
/*
279
 * Set <str> to <new_str>.  Print warning if <str> is redefined.
280
 */
281
void
282
rcs_setrevstr(char **str, char *new_str)
283
{
284
276
	if (new_str == NULL)
285
		return;
286
5
	if (*str != NULL)
287
		warnx("redefinition of revision number");
288
5
	*str = new_str;
289
143
}
290
291
/*
292
 * Set <str1> or <str2> to <new_str>, depending on which is not set.
293
 * If both are set, error out.
294
 */
295
void
296
rcs_setrevstr2(char **str1, char **str2, char *new_str)
297
{
298
26
	if (new_str == NULL)
299
		return;
300
9
	if (*str1 == NULL)
301
5
		*str1 = new_str;
302
4
	else if (*str2 == NULL)
303
4
		*str2 = new_str;
304
	else
305
		errx(1, "too many revision numbers");
306
13
}
307
308
/*
309
 * Get revision from file.  The revision can be specified as a symbol or
310
 * a revision number.
311
 */
312
RCSNUM *
313
rcs_getrevnum(const char *rev_str, RCSFILE *file)
314
{
315
	RCSNUM *rev;
316
317
	/* Search for symbol. */
318
24
	rev = rcs_sym_getrev(file, rev_str);
319
320
	/* Search for revision number. */
321
12
	if (rev == NULL)
322
7
		rev = rcsnum_parse(rev_str);
323
324
12
	return (rev);
325
}
326
327
/*
328
 * Prompt for and store user's input in an allocated string.
329
 *
330
 * Returns the string's pointer.
331
 */
332
char *
333
rcs_prompt(const char *prompt, int flags)
334
{
335
	BUF *bp;
336
88
	size_t len;
337
	char *buf;
338
339

58
	if (!(flags & INTERACTIVE) && isatty(STDIN_FILENO))
340
		flags |= INTERACTIVE;
341
342
44
	bp = buf_alloc(0);
343
44
	if (flags & INTERACTIVE)
344
30
		(void)fprintf(stderr, "%s", prompt);
345
44
	if (flags & INTERACTIVE)
346
30
		(void)fprintf(stderr, ">> ");
347
88
	clearerr(stdin);
348
70
	while ((buf = fgetln(stdin, &len)) != NULL) {
349
		/* The last line may not be EOL terminated. */
350

85
		if (buf[0] == '.' && (len == 1 || buf[1] == '\n'))
351
			break;
352
		else
353
26
			buf_append(bp, buf, len);
354
355
26
		if (flags & INTERACTIVE)
356
18
			(void)fprintf(stderr, ">> ");
357
	}
358
44
	buf_putc(bp, '\0');
359
360
88
	return (buf_release(bp));
361
44
}
362
363
u_int
364
rcs_rev_select(RCSFILE *file, const char *range)
365
{
366
	int i;
367
	u_int nrev;
368
2
	const char *ep;
369
	char *lstr, *rstr;
370
	struct rcs_delta *rdp;
371
	struct rcs_argvector *revargv, *revrange;
372
1
	RCSNUM lnum, rnum;
373
374
	nrev = 0;
375
1
	(void)memset(&lnum, 0, sizeof(lnum));
376
1
	(void)memset(&rnum, 0, sizeof(rnum));
377
378
1
	if (range == NULL) {
379
		TAILQ_FOREACH(rdp, &file->rf_delta, rd_list)
380
			if (rcsnum_cmp(rdp->rd_num, file->rf_head, 0) == 0) {
381
				rdp->rd_flags |= RCS_RD_SELECT;
382
				return (1);
383
			}
384
		return (0);
385
	}
386
387
1
	revargv = rcs_strsplit(range, ",");
388
4
	for (i = 0; revargv->argv[i] != NULL; i++) {
389
1
		revrange = rcs_strsplit(revargv->argv[i], ":");
390
1
		if (revrange->argv[0] == NULL)
391
			/* should not happen */
392
			errx(1, "invalid revision range: %s", revargv->argv[i]);
393
1
		else if (revrange->argv[1] == NULL)
394
			lstr = rstr = revrange->argv[0];
395
		else {
396
1
			if (revrange->argv[2] != NULL)
397
				errx(1, "invalid revision range: %s",
398
				    revargv->argv[i]);
399
			lstr = revrange->argv[0];
400
			rstr = revrange->argv[1];
401
1
			if (strcmp(lstr, "") == 0)
402
				lstr = NULL;
403
1
			if (strcmp(rstr, "") == 0)
404
				rstr = NULL;
405
		}
406
407
1
		if (lstr == NULL)
408
			lstr = RCS_HEAD_INIT;
409

2
		if (rcsnum_aton(lstr, &ep, &lnum) == 0 || (*ep != '\0'))
410
			errx(1, "invalid revision: %s", lstr);
411
412
1
		if (rstr != NULL) {
413

2
			if (rcsnum_aton(rstr, &ep, &rnum) == 0 || (*ep != '\0'))
414
				errx(1, "invalid revision: %s", rstr);
415
		} else
416
			rcsnum_cpy(file->rf_head, &rnum, 0);
417
418
1
		rcs_argv_destroy(revrange);
419
420
14
		TAILQ_FOREACH(rdp, &file->rf_delta, rd_list)
421

9
			if (rcsnum_cmp(rdp->rd_num, &lnum, 0) <= 0 &&
422
4
			    rcsnum_cmp(rdp->rd_num, &rnum, 0) >= 0 &&
423
3
			    !(rdp->rd_flags & RCS_RD_SELECT)) {
424
3
				rdp->rd_flags |= RCS_RD_SELECT;
425
3
				nrev++;
426
3
			}
427
	}
428
1
	rcs_argv_destroy(revargv);
429
430
1
	free(lnum.rn_id);
431
1
	free(rnum.rn_id);
432
433
1
	return (nrev);
434
1
}
435
436
/*
437
 * Load description from <in> to <file>.
438
 * If <in> starts with a `-', <in> is taken as the description.
439
 * Otherwise <in> is the name of the file containing the description.
440
 * If <in> is NULL, the description is read from stdin.
441
 * Returns 0 on success, -1 on failure, setting errno.
442
 */
443
int
444
rcs_set_description(RCSFILE *file, const char *in, int flags)
445
{
446
	BUF *bp;
447
	char *content;
448
	const char *prompt =
449
	    "enter description, terminated with single '.' or end of file:\n"
450
	    "NOTE: This is NOT the log message!\n";
451
452
	/* Description is in file <in>. */
453

87
	if (in != NULL && *in != '-') {
454
1
		if ((bp = buf_load(in)) == NULL)
455
			return (-1);
456
1
		buf_putc(bp, '\0');
457
1
		content = buf_release(bp);
458
	/* Description is in <in>. */
459
41
	} else if (in != NULL)
460
		/* Skip leading `-'. */
461
4
		content = xstrdup(in + 1);
462
	/* Get description from stdin. */
463
	else
464
36
		content = rcs_prompt(prompt, flags);
465
466
41
	rcs_desc_set(file, content);
467
41
	free(content);
468
41
	return (0);
469
41
}
470
471
/*
472
 * Split the contents of a file into a list of lines.
473
 */
474
struct rcs_lines *
475
rcs_splitlines(u_char *data, size_t len)
476
{
477
	u_char *c, *p;
478
	struct rcs_lines *lines;
479
	struct rcs_line *lp;
480
	size_t i, tlen;
481
482
136
	lines = xcalloc(1, sizeof(*lines));
483
68
	TAILQ_INIT(&(lines->l_lines));
484
485
68
	lp = xcalloc(1, sizeof(*lp));
486
68
	TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
487
488
489
	p = c = data;
490
14596
	for (i = 0; i < len; i++) {
491

14073
		if (*p == '\n' || (i == len - 1)) {
492
387
			tlen = p - c + 1;
493
387
			lp = xmalloc(sizeof(*lp));
494
387
			lp->l_line = c;
495
387
			lp->l_len = tlen;
496
387
			lp->l_lineno = ++(lines->l_nblines);
497
387
			TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
498
387
			c = p + 1;
499
387
		}
500
7230
		p++;
501
	}
502
503
68
	return (lines);
504
}
505
506
void
507
rcs_freelines(struct rcs_lines *lines)
508
{
509
	struct rcs_line *lp;
510
511
1024
	while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) {
512
1230
		TAILQ_REMOVE(&(lines->l_lines), lp, l_list);
513
410
		free(lp);
514
	}
515
516
68
	free(lines);
517
68
}
518
519
BUF *
520
rcs_patchfile(u_char *data, size_t dlen, u_char *patch, size_t plen,
521
    int (*p)(struct rcs_lines *, struct rcs_lines *))
522
{
523
	struct rcs_lines *dlines, *plines;
524
	struct rcs_line *lp;
525
	BUF *res;
526
527
62
	dlines = rcs_splitlines(data, dlen);
528
31
	plines = rcs_splitlines(patch, plen);
529
530
31
	if (p(dlines, plines) < 0) {
531
		rcs_freelines(dlines);
532
		rcs_freelines(plines);
533
		return (NULL);
534
	}
535
536
31
	res = buf_alloc(1024);
537
742
	TAILQ_FOREACH(lp, &dlines->l_lines, l_list) {
538
340
		if (lp->l_line == NULL)
539
			continue;
540
309
		buf_append(res, lp->l_line, lp->l_len);
541
309
	}
542
543
31
	rcs_freelines(dlines);
544
31
	rcs_freelines(plines);
545
31
	return (res);
546
31
}
547
548
/*
549
 * rcs_yesno()
550
 *
551
 * Read a char from standard input, returns defc if the
552
 * user enters an equivalent to defc, else whatever char
553
 * was entered.  Converts input to lower case.
554
 */
555
int
556
rcs_yesno(int defc)
557
{
558
	int c, ret;
559
560
	fflush(stderr);
561
	fflush(stdout);
562
563
	clearerr(stdin);
564
	if (isalpha(c = getchar()))
565
		c = tolower(c);
566
	if (c == defc || c == '\n' || (c == EOF && feof(stdin)))
567
		ret = defc;
568
	else
569
		ret = c;
570
571
	while (c != EOF && c != '\n')
572
		c = getchar();
573
574
	return (ret);
575
}
576
577
/*
578
 * rcs_strsplit()
579
 *
580
 * Split a string <str> of <sep>-separated values and allocate
581
 * an argument vector for the values found.
582
 */
583
struct rcs_argvector *
584
rcs_strsplit(const char *str, const char *sep)
585
{
586
	struct rcs_argvector *av;
587
	size_t i = 0;
588
12
	char *cp, *p;
589
590
6
	cp = xstrdup(str);
591
6
	av = xmalloc(sizeof(*av));
592
6
	av->str = cp;
593
6
	av->argv = xmalloc(sizeof(*(av->argv)));
594
595
42
	while ((p = strsep(&cp, sep)) != NULL) {
596
15
		av->argv[i++] = p;
597
30
		av->argv = xreallocarray(av->argv,
598
15
		    i + 1, sizeof(*(av->argv)));
599
	}
600
6
	av->argv[i] = NULL;
601
602
6
	return (av);
603
6
}
604
605
/*
606
 * rcs_argv_destroy()
607
 *
608
 * Free an argument vector previously allocated by rcs_strsplit().
609
 */
610
void
611
rcs_argv_destroy(struct rcs_argvector *av)
612
{
613
12
	free(av->str);
614
6
	free(av->argv);
615
6
	free(av);
616
6
}
617
618
/*
619
 * Strip suffix from filename.
620
 */
621
void
622
rcs_strip_suffix(char *filename)
623
{
624
220
	char *p, *suffixes, *next, *ext;
625
626
110
	if ((p = strrchr(filename, ',')) != NULL) {
627
1
		suffixes = xstrdup(rcs_suffixes);
628
4
		for (next = suffixes; (ext = strsep(&next, "/")) != NULL;) {
629
2
			if (!strcmp(p, ext)) {
630
				*p = '\0';
631
				break;
632
			}
633
		}
634
1
		free(suffixes);
635
1
	}
636
110
}