GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/rcs/co.c Lines: 121 277 43.7 %
Date: 2017-11-07 Branches: 101 231 43.7 %

Line Branch Exec Source
1
/*	$OpenBSD: co.c,v 1.123 2017/08/29 16:47:33 otto Exp $	*/
2
/*
3
 * Copyright (c) 2005 Joris Vink <joris@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/stat.h>
28
#include <sys/time.h>
29
30
#include <err.h>
31
#include <fcntl.h>
32
#include <stdio.h>
33
#include <stdlib.h>
34
#include <string.h>
35
#include <unistd.h>
36
37
#include "rcsprog.h"
38
#include "diff.h"
39
40
#define CO_OPTSTRING	"d:f::I::k:l::M::p::q::r::s:Tu::Vw::x::z::"
41
42
static void	checkout_err_nobranch(RCSFILE *, const char *, const char *,
43
    const char *, int);
44
static int	checkout_file_has_diffs(RCSFILE *, RCSNUM *, const char *);
45
46
int
47
checkout_main(int argc, char **argv)
48
{
49
	int fd, i, ch, flags, kflag, ret;
50
	RCSNUM *rev;
51
	RCSFILE *file;
52
	const char *author, *date, *state;
53
98
	char fpath[PATH_MAX];
54
49
	char *rev_str, *username;
55
	time_t rcs_mtime = -1;
56
57
	flags = ret = 0;
58
	kflag = RCS_KWEXP_ERR;
59
49
	rev_str = NULL;
60
	author = date = state = NULL;
61
62
191
	while ((ch = rcs_getopt(argc, argv, CO_OPTSTRING)) != -1) {
63




93
		switch (ch) {
64
		case 'd':
65
			date = rcs_optarg;
66
			break;
67
		case 'f':
68
			rcs_setrevstr(&rev_str, rcs_optarg);
69
			flags |= FORCE;
70
			break;
71
		case 'I':
72
			rcs_setrevstr(&rev_str, rcs_optarg);
73
			flags |= INTERACTIVE;
74
			break;
75
76
		case 'k':
77
			kflag = rcs_kflag_get(rcs_optarg);
78
			if (RCS_KWEXP_INVAL(kflag)) {
79
				warnx("invalid RCS keyword substitution mode");
80
				(usage)();
81
			}
82
			break;
83
		case 'l':
84
33
			if (flags & CO_UNLOCK) {
85
				warnx("warning: -u overridden by -l");
86
				flags &= ~CO_UNLOCK;
87
			}
88
33
			rcs_setrevstr(&rev_str, rcs_optarg);
89
33
			flags |= CO_LOCK;
90
33
			break;
91
		case 'M':
92
			rcs_setrevstr(&rev_str, rcs_optarg);
93
			flags |= CO_REVDATE;
94
			break;
95
		case 'p':
96
			rcs_setrevstr(&rev_str, rcs_optarg);
97
			flags |= PIPEOUT;
98
			break;
99
		case 'q':
100
49
			rcs_setrevstr(&rev_str, rcs_optarg);
101
49
			flags |= QUIET;
102
49
			break;
103
		case 'r':
104
			rcs_setrevstr(&rev_str, rcs_optarg);
105
			break;
106
		case 's':
107
			state = rcs_optarg;
108
			flags |= CO_STATE;
109
			break;
110
		case 'T':
111
			flags |= PRESERVETIME;
112
			break;
113
		case 'u':
114
11
			rcs_setrevstr(&rev_str, rcs_optarg);
115
11
			if (flags & CO_LOCK) {
116
				warnx("warning: -l overridden by -u");
117
				flags &= ~CO_LOCK;
118
			}
119
11
			flags |= CO_UNLOCK;
120
11
			break;
121
		case 'V':
122
			printf("%s\n", rcs_version);
123
			exit(0);
124
		case 'w':
125
			/* if no argument, assume current user */
126
			if (rcs_optarg == NULL) {
127
				if ((author = getlogin()) == NULL)
128
					err(1, "getlogin");
129
			} else
130
				author = rcs_optarg;
131
			flags |= CO_AUTHOR;
132
			break;
133
		case 'x':
134
			/* Use blank extension if none given. */
135
			rcs_suffixes = rcs_optarg ? rcs_optarg : "";
136
			break;
137
		case 'z':
138
			timezone_flag = rcs_optarg;
139
			break;
140
		default:
141
			(usage)();
142
		}
143
	}
144
145
49
	argc -= rcs_optind;
146
49
	argv += rcs_optind;
147
148
49
	if (argc == 0) {
149
		warnx("no input file");
150
		(usage)();
151
	}
152
153
49
	if ((username = getlogin()) == NULL)
154
		err(1, "getlogin");
155
156
194
	for (i = 0; i < argc; i++) {
157
49
		fd = rcs_choosefile(argv[i], fpath, sizeof(fpath));
158
49
		if (fd < 0) {
159
			warn("%s", fpath);
160
			ret = 1;
161
			continue;
162
		}
163
49
		rcs_strip_suffix(argv[i]);
164
165
49
		if (!(flags & QUIET))
166
			(void)fprintf(stderr, "%s  -->  %s\n", fpath,
167
			    (flags & PIPEOUT) ? "standard output" : argv[i]);
168
169

82
		if ((flags & CO_LOCK) && (kflag & RCS_KWEXP_VAL)) {
170
			warnx("%s: cannot combine -kv and -l", fpath);
171
			(void)close(fd);
172
			continue;
173
		}
174
175
96
		if ((file = rcs_open(fpath, fd,
176
48
		    RCS_RDWR|RCS_PARSE_FULLY)) == NULL)
177
			continue;
178
179
48
		if (flags & PRESERVETIME)
180
			rcs_mtime = rcs_get_mtime(file);
181
182
48
		rcs_kwexp_set(file, kflag);
183
184
48
		if (rev_str != NULL) {
185
			if ((rev = rcs_getrevnum(rev_str, file)) == NULL)
186
				errx(1, "invalid revision: %s", rev_str);
187
		} else {
188
			/* no revisions in RCS file, generate empty 0.0 */
189
48
			if (file->rf_ndelta == 0) {
190
2
				rev = rcsnum_parse("0.0");
191
2
				if (rev == NULL)
192
					errx(1, "failed to generate rev 0.0");
193
			} else {
194
46
				rev = rcsnum_alloc();
195
46
				rcsnum_cpy(file->rf_head, rev, 0);
196
			}
197
		}
198
199
96
		if (checkout_rev(file, rev, argv[i], flags,
200
48
		    username, author, state, date) < 0) {
201
			rcs_close(file);
202
			rcsnum_free(rev);
203
			ret = 1;
204
			continue;
205
		}
206
207
48
		if (!(flags & QUIET))
208
			(void)fprintf(stderr, "done\n");
209
210
48
		rcsnum_free(rev);
211
212
48
		rcs_write(file);
213
48
		if (flags & PRESERVETIME)
214
			rcs_set_mtime(file, rcs_mtime);
215
48
		rcs_close(file);
216
48
	}
217
218
48
	return (ret);
219
48
}
220
221
__dead void
222
checkout_usage(void)
223
{
224
	fprintf(stderr,
225
	    "usage: co [-TV] [-ddate] [-f[rev]] [-I[rev]] [-kmode] [-l[rev]]\n"
226
	    "          [-M[rev]] [-p[rev]] [-q[rev]] [-r[rev]] [-sstate]\n"
227
	    "          [-u[rev]] [-w[user]] [-xsuffixes] [-ztz] file ...\n");
228
229
	exit(1);
230
}
231
232
/*
233
 * Checkout revision <rev> from RCSFILE <file>, writing it to the path <dst>
234
 * Currently recognised <flags> are CO_LOCK, CO_UNLOCK and CO_REVDATE.
235
 *
236
 * Looks up revision based upon <lockname>, <author>, <state> and <date>
237
 *
238
 * Returns 0 on success, -1 on failure.
239
 */
240
int
241
checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags,
242
    const char *lockname, const char *author, const char *state,
243
    const char *date)
244
{
245
	BUF *bp;
246
	u_int i;
247
	int fd, lcount;
248
154
	char buf[RCS_REV_BUFSZ];
249
	mode_t mode = DEFFILEMODE;
250
77
	struct stat st;
251
	struct rcs_delta *rdp;
252
	struct rcs_lock *lkp;
253
	char *fdate;
254
	const char *fstatus;
255
	time_t rcsdate, givendate;
256
	RCSNUM *rev;
257
258
	givendate = -1;
259

77
	if (date != NULL && (givendate = date_parse(date)) == -1) {
260
		warnx("invalid date: %s", date);
261
		return -1;
262
	}
263
264

79
	if (file->rf_ndelta == 0 && !(flags & QUIET))
265
		(void)fprintf(stderr,
266
		    "no revisions present; generating empty revision 0.0\n");
267
268
	/* XXX rcsnum_cmp()
269
	 * Check out the latest revision if <frev> is greater than HEAD
270
	 */
271
77
	if (file->rf_ndelta != 0) {
272
450
		for (i = 0; i < file->rf_head->rn_len; i++) {
273
150
			if (file->rf_head->rn_id[i] < frev->rn_id[i]) {
274
				frev = file->rf_head;
275
				break;
276
			}
277
		}
278
	}
279
280
	lcount = 0;
281
190
	TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
282
18
		if (!strcmp(lkp->rl_name, lockname))
283
18
			lcount++;
284
	}
285
286
	/*
287
	 * If the user didn't specify any revision, we cycle through
288
	 * revisions to lookup the first one that matches what he specified.
289
	 *
290
	 * If we cannot find one, we return an error.
291
	 */
292
	rdp = NULL;
293

152
	if (file->rf_ndelta != 0 && frev == file->rf_head) {
294
29
		if (lcount > 1) {
295
			warnx("multiple revisions locked by %s; "
296
			    "please specify one", lockname);
297
			return (-1);
298
		}
299
300
58
		TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) {
301
29
			if (date != NULL) {
302
				fdate = asctime(&rdp->rd_date);
303
				if ((rcsdate = date_parse(fdate)) == -1) {
304
					warnx("invalid date: %s", fdate);
305
					return -1;
306
				}
307
				if (givendate <= rcsdate)
308
					continue;
309
			}
310
311

30
			if (author != NULL &&
312
1
			    strcmp(rdp->rd_author, author))
313
				continue;
314
315

29
			if (state != NULL &&
316
			    strcmp(rdp->rd_state, state))
317
				continue;
318
319
29
			frev = rdp->rd_num;
320
29
			break;
321
		}
322
48
	} else if (file->rf_ndelta != 0) {
323
46
		rdp = rcs_findrev(file, frev);
324
46
	}
325
326
77
	if (file->rf_ndelta != 0 && rdp == NULL) {
327
		checkout_err_nobranch(file, author, date, state, flags);
328
		return (-1);
329
	}
330
331
77
	if (file->rf_ndelta == 0)
332
2
		rev = frev;
333
	else
334
75
		rev = rdp->rd_num;
335
336
77
	rcsnum_tostr(rev, buf, sizeof(buf));
337
338

152
	if (file->rf_ndelta != 0 && rdp->rd_locker != NULL) {
339
18
		if (strcmp(lockname, rdp->rd_locker)) {
340
			warnx("Revision %s is already locked by %s; %s",
341
			    buf, rdp->rd_locker,
342
			    (flags & CO_UNLOCK) ? "use co -r or rcs -u" : "");
343
			return (-1);
344
		}
345
	}
346
347

77
	if (!(flags & QUIET) && !(flags & NEWFILE) &&
348
	    !(flags & CO_REVERT) && file->rf_ndelta != 0)
349
		(void)fprintf(stderr, "revision %s", buf);
350
351
77
	if (file->rf_ndelta != 0) {
352
75
		if ((bp = rcs_getrev(file, rev)) == NULL) {
353
			warnx("cannot find revision `%s'", buf);
354
			return (-1);
355
		}
356
	} else {
357
2
		bp = buf_alloc(1);
358
	}
359
360
	/*
361
	 * Do keyword expansion if required.
362
	 */
363
77
	if (file->rf_ndelta != 0)
364
75
		bp = rcs_kwexp_buf(bp, file, rev);
365
	/*
366
	 * File inherits permissions from its ,v file
367
	 */
368
77
	if (file->rf_file != NULL) {
369

192
		if (fstat(fileno(file->rf_file), &st) == -1)
370
			err(1, "%s", file->rf_path);
371
64
		file->rf_mode = mode = st.st_mode;
372
64
	} else {
373
13
		mode = file->rf_mode;
374
	}
375
376
77
	if (flags & CO_LOCK) {
377
120
		if (file->rf_ndelta != 0) {
378
119
			if (lockname != NULL &&
379
59
			    rcs_lock_add(file, lockname, rev) < 0) {
380
59
				if (rcs_errno != RCS_ERR_DUPENT)
381
					return (-1);
382
			}
383
		}
384
385
		/* File should only be writable by owner. */
386
60
		mode &= ~(S_IWGRP|S_IWOTH);
387
60
		mode |= S_IWUSR;
388
389
60
		if (file->rf_ndelta != 0) {
390

59
			if (!(flags & QUIET) && !(flags & NEWFILE) &&
391
			    !(flags & CO_REVERT))
392
				(void)fprintf(stderr, " (locked)");
393
		}
394
17
	} else if (flags & CO_UNLOCK) {
395
13
		if (file->rf_ndelta != 0) {
396
26
			if (rcs_lock_remove(file, lockname, rev) < 0) {
397
13
				if (rcs_errno != RCS_ERR_NOENT)
398
					return (-1);
399
			}
400
		}
401
402
		/* Strip all write bits from mode */
403
13
		mode &= ~(S_IWUSR|S_IWGRP|S_IWOTH);
404
405
13
		if (file->rf_ndelta != 0) {
406

13
			if (!(flags & QUIET) && !(flags & NEWFILE) &&
407
			    !(flags & CO_REVERT))
408
				(void)fprintf(stderr, " (unlocked)");
409
		}
410
	}
411
412
	/* If strict locking is disabled, make file writable by owner. */
413
77
	if (rcs_lock_getmode(file) == RCS_LOCK_LOOSE)
414
		mode |= S_IWUSR;
415
416

79
	if (file->rf_ndelta == 0 && !(flags & QUIET) &&
417
	    ((flags & CO_LOCK) || (flags & CO_UNLOCK))) {
418
		(void)fprintf(stderr, "no revisions, so nothing can be %s\n",
419
		    (flags & CO_LOCK) ? "locked" : "unlocked");
420
77
	} else if (file->rf_ndelta != 0) {
421
		/* XXX - Not a good way to detect if a newline is needed. */
422

75
		if (!(flags & QUIET) && !(flags & NEWFILE) &&
423
		    !(flags & CO_REVERT))
424
			(void)fprintf(stderr, "\n");
425
	}
426
427
77
	if (flags & CO_LOCK) {
428
60
		if (rcs_errno != RCS_ERR_DUPENT)
429
41
			lcount++;
430

60
		if (!(flags & QUIET) && lcount > 1 && !(flags & CO_REVERT))
431
			warnx("%s: warning: You now have %d locks.",
432
			    file->rf_path, lcount);
433
	}
434
435

154
	if ((flags & (PIPEOUT|FORCE)) == 0 && stat(dst, &st) != -1) {
436
		/*
437
		 * Prompt the user if the file is writable or the file is
438
		 * not writable but is different from the RCS head version.
439
		 * This is different from GNU which will silently overwrite
440
		 * the file regardless of its contents so long as it is
441
		 * read-only.
442
		 */
443
		if (st.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH))
444
			fstatus = "writable";
445
		else if (checkout_file_has_diffs(file, frev, dst) != D_SAME)
446
			fstatus = "modified";
447
		else
448
			fstatus = NULL;
449
		if (fstatus) {
450
			(void)fprintf(stderr, "%s %s exists%s; ", fstatus, dst,
451
			    (getuid() == st.st_uid) ? "" :
452
			    ", and you do not own it");
453
			(void)fprintf(stderr, "remove it? [ny](n): ");
454
			if (rcs_yesno('n') == 'n') {
455
				if (!(flags & QUIET) && isatty(STDIN_FILENO))
456
					warnx("%s %s exists; checkout aborted",
457
					    fstatus, dst);
458
				else
459
					warnx("checkout aborted");
460
				return (-1);
461
			}
462
		}
463
	}
464
465
77
	if (flags & PIPEOUT)
466
		buf_write_fd(bp, STDOUT_FILENO);
467
	else {
468
77
		(void)unlink(dst);
469
470
77
		if ((fd = open(dst, O_WRONLY|O_CREAT|O_TRUNC, mode)) < 0)
471
			err(1, "%s", dst);
472
473
77
		if (buf_write_fd(bp, fd) < 0) {
474
			warnx("failed to write revision to file");
475
			buf_free(bp);
476
			(void)close(fd);
477
			return (-1);
478
		}
479
480
77
		if (fchmod(fd, mode) == -1)
481
			warn("%s", dst);
482
483
77
		if (flags & CO_REVDATE) {
484
			struct timeval tv[2];
485
			memset(&tv, 0, sizeof(tv));
486
			tv[0].tv_sec = rcs_rev_getdate(file, rev);
487
			tv[1].tv_sec = tv[0].tv_sec;
488
			if (futimes(fd, (const struct timeval *)&tv) < 0)
489
				warn("utimes");
490
		}
491
492
77
		(void)close(fd);
493
	}
494
495
77
	buf_free(bp);
496
497
77
	return (0);
498
77
}
499
500
/*
501
 * checkout_err_nobranch()
502
 *
503
 * XXX - should handle the dates too.
504
 */
505
static void
506
checkout_err_nobranch(RCSFILE *file, const char *author, const char *date,
507
    const char *state, int flags)
508
{
509
	if (!(flags & CO_AUTHOR))
510
		author = NULL;
511
	if (!(flags & CO_STATE))
512
		state = NULL;
513
514
	warnx("%s: No revision on branch has %s%s%s%s%s%s%s%s.",
515
	    file->rf_path,
516
	    date ? "a date before " : "",
517
	    date ? date : "",
518
	    (date && author) ? " and " : "",
519
	    author ? "author " : "",
520
	    author ? author : "",
521
	    ((date || author) && state) ? " and " : "",
522
	    state ? "state " : "",
523
	    state ? state : "");
524
525
}
526
527
/*
528
 * checkout_file_has_diffs()
529
 *
530
 * Check for diffs between the working file and its current revision.
531
 * Same return values as diffreg()
532
 */
533
static int
534
checkout_file_has_diffs(RCSFILE *rfp, RCSNUM *frev, const char *dst)
535
{
536
	char *tempfile;
537
	BUF *bp;
538
	int ret;
539
540
	tempfile = NULL;
541
542
	if ((bp = rcs_getrev(rfp, frev)) == NULL) {
543
		warnx("failed to load revision");
544
		return (D_ERROR);
545
	}
546
	if ((bp = rcs_kwexp_buf(bp, rfp, frev)) == NULL) {
547
		warnx("failed to expand tags");
548
		return (D_ERROR);
549
	}
550
551
	(void)xasprintf(&tempfile, "%s/diff.XXXXXXXXXX", rcs_tmpdir);
552
	buf_write_stmp(bp, tempfile);
553
	buf_empty(bp);
554
555
	diff_format = D_RCSDIFF;
556
	ret = diffreg(dst, tempfile, bp, D_FORCEASCII);
557
558
	buf_free(bp);
559
	unlink(tempfile);
560
	free(tempfile);
561
562
	return (ret);
563
}