GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/rcs/ci.c Lines: 0 444 0.0 %
Date: 2017-11-13 Branches: 0 314 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: ci.c,v 1.224 2016/07/04 01:39:12 millert Exp $	*/
2
/*
3
 * Copyright (c) 2005, 2006 Niall O'Higgins <niallo@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
29
#include <ctype.h>
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 CI_OPTSTRING	"d::f::I::i::j::k::l::M::m::N:n:qr::s:Tt::u::Vw:x::z::"
41
#define DATE_NOW	-1
42
#define DATE_MTIME	-2
43
44
#define KW_ID		"Id"
45
#define KW_OPENBSD	"OpenBSD"
46
#define KW_AUTHOR	"Author"
47
#define KW_DATE		"Date"
48
#define KW_STATE	"State"
49
#define KW_REVISION	"Revision"
50
51
#define KW_TYPE_ID		1
52
#define KW_TYPE_AUTHOR		2
53
#define KW_TYPE_DATE		3
54
#define KW_TYPE_STATE		4
55
#define KW_TYPE_REVISION	5
56
57
/* Maximum number of tokens in a keyword. */
58
#define KW_NUMTOKS_MAX		10
59
60
#define RCSNUM_ZERO_ENDING(x) (x->rn_id[x->rn_len - 1] == 0)
61
62
extern struct rcs_kw rcs_expkw[];
63
64
static int workfile_fd;
65
66
struct checkin_params {
67
	int flags, openflags;
68
	mode_t fmode;
69
	time_t date;
70
	RCSFILE *file;
71
	RCSNUM *frev, *newrev;
72
	const char *description, *symbol;
73
	char fpath[PATH_MAX], *rcs_msg, *username, *filename;
74
	char *author, *state;
75
	BUF *deltatext;
76
};
77
78
static int	 checkin_attach_symbol(struct checkin_params *);
79
static int	 checkin_checklock(struct checkin_params *);
80
static BUF	*checkin_diff_file(struct checkin_params *);
81
static char	*checkin_getlogmsg(RCSNUM *, RCSNUM *, int);
82
static int	 checkin_init(struct checkin_params *);
83
static int	 checkin_keywordscan(BUF *, RCSNUM **, time_t *, char **,
84
    char **);
85
static int	 checkin_keywordtype(char *);
86
static void	 checkin_mtimedate(struct checkin_params *);
87
static void	 checkin_parsekeyword(char *, RCSNUM **, time_t *, char **,
88
    char **);
89
static int	 checkin_update(struct checkin_params *);
90
static int	 checkin_revert(struct checkin_params *);
91
92
__dead void
93
checkin_usage(void)
94
{
95
	fprintf(stderr,
96
	    "usage: ci [-qV] [-d[date]] [-f[rev]] [-I[rev]] [-i[rev]]\n"
97
	    "          [-j[rev]] [-k[rev]] [-l[rev]] [-M[rev]] [-mmsg]\n"
98
	    "          [-Nsymbol] [-nsymbol] [-r[rev]] [-sstate] [-t[str]]\n"
99
	    "          [-u[rev]] [-wusername] [-xsuffixes] [-ztz] file ...\n");
100
101
	exit(1);
102
}
103
104
/*
105
 * checkin_main()
106
 *
107
 * Handler for the `ci' program.
108
 * Returns 0 on success, or >0 on error.
109
 */
110
int
111
checkin_main(int argc, char **argv)
112
{
113
	int fd;
114
	int i, ch, status;
115
	int base_flags, base_openflags;
116
	char *rev_str;
117
	struct checkin_params pb;
118
119
	pb.date = DATE_NOW;
120
	pb.file = NULL;
121
	pb.rcs_msg = pb.username = pb.author = pb.state = NULL;
122
	pb.description = pb.symbol = NULL;
123
	pb.deltatext = NULL;
124
	pb.newrev =  NULL;
125
	pb.fmode = S_IRUSR|S_IRGRP|S_IROTH;
126
	status = 0;
127
	base_flags = INTERACTIVE;
128
	base_openflags = RCS_RDWR|RCS_CREATE|RCS_PARSE_FULLY;
129
	rev_str = NULL;
130
131
	while ((ch = rcs_getopt(argc, argv, CI_OPTSTRING)) != -1) {
132
		switch (ch) {
133
		case 'd':
134
			if (rcs_optarg == NULL)
135
				pb.date = DATE_MTIME;
136
			else if ((pb.date = date_parse(rcs_optarg)) == -1)
137
				errx(1, "invalid date");
138
			break;
139
		case 'f':
140
			rcs_setrevstr(&rev_str, rcs_optarg);
141
			base_flags |= FORCE;
142
			break;
143
		case 'I':
144
			rcs_setrevstr(&rev_str, rcs_optarg);
145
			base_flags |= INTERACTIVE;
146
			break;
147
		case 'i':
148
			rcs_setrevstr(&rev_str, rcs_optarg);
149
			base_openflags |= RCS_CREATE;
150
			base_flags |= CI_INIT;
151
			break;
152
		case 'j':
153
			rcs_setrevstr(&rev_str, rcs_optarg);
154
			base_openflags &= ~RCS_CREATE;
155
			base_flags &= ~CI_INIT;
156
			break;
157
		case 'k':
158
			rcs_setrevstr(&rev_str, rcs_optarg);
159
			base_flags |= CI_KEYWORDSCAN;
160
			break;
161
		case 'l':
162
			rcs_setrevstr(&rev_str, rcs_optarg);
163
			base_flags |= CO_LOCK;
164
			break;
165
		case 'M':
166
			rcs_setrevstr(&rev_str, rcs_optarg);
167
			base_flags |= CO_REVDATE;
168
			break;
169
		case 'm':
170
			pb.rcs_msg = rcs_optarg;
171
			if (pb.rcs_msg == NULL)
172
				errx(1, "missing message for -m option");
173
			base_flags &= ~INTERACTIVE;
174
			break;
175
		case 'N':
176
			base_flags |= CI_SYMFORCE;
177
			/* FALLTHROUGH */
178
		case 'n':
179
			pb.symbol = rcs_optarg;
180
			if (rcs_sym_check(pb.symbol) != 1)
181
				errx(1, "invalid symbol `%s'", pb.symbol);
182
			break;
183
		case 'q':
184
			base_flags |= QUIET;
185
			break;
186
		case 'r':
187
			rcs_setrevstr(&rev_str, rcs_optarg);
188
			base_flags |= CI_DEFAULT;
189
			break;
190
		case 's':
191
			pb.state = rcs_optarg;
192
			if (rcs_state_check(pb.state) < 0)
193
				errx(1, "invalid state `%s'", pb.state);
194
			break;
195
		case 'T':
196
			base_flags |= PRESERVETIME;
197
			break;
198
		case 't':
199
			/* Ignore bare -t; kept for backwards compatibility. */
200
			if (rcs_optarg == NULL)
201
				break;
202
			pb.description = rcs_optarg;
203
			base_flags |= DESCRIPTION;
204
			break;
205
		case 'u':
206
			rcs_setrevstr(&rev_str, rcs_optarg);
207
			base_flags |= CO_UNLOCK;
208
			break;
209
		case 'V':
210
			printf("%s\n", rcs_version);
211
			exit(0);
212
		case 'w':
213
			free(pb.author);
214
			pb.author = xstrdup(rcs_optarg);
215
			break;
216
		case 'x':
217
			/* Use blank extension if none given. */
218
			rcs_suffixes = rcs_optarg ? rcs_optarg : "";
219
			break;
220
		case 'z':
221
			timezone_flag = rcs_optarg;
222
			break;
223
		default:
224
			(usage)();
225
		}
226
	}
227
228
	argc -= rcs_optind;
229
	argv += rcs_optind;
230
231
	if (argc == 0) {
232
		warnx("no input file");
233
		(usage)();
234
	}
235
236
	if ((pb.username = getlogin()) == NULL)
237
		err(1, "getlogin");
238
239
	for (i = 0; i < argc; i++) {
240
		/*
241
		 * The pb.flags and pb.openflags may change during
242
		 * loop iteration so restore them for each file.
243
		 */
244
		pb.flags = base_flags;
245
		pb.openflags = base_openflags;
246
247
		pb.filename = argv[i];
248
		rcs_strip_suffix(pb.filename);
249
250
		if ((workfile_fd = open(pb.filename, O_RDONLY)) == -1)
251
			err(1, "%s", pb.filename);
252
253
		/* Find RCS file path. */
254
		fd = rcs_choosefile(pb.filename, pb.fpath, sizeof(pb.fpath));
255
256
		if (fd < 0) {
257
			if (pb.openflags & RCS_CREATE)
258
				pb.flags |= NEWFILE;
259
			else {
260
				/* XXX - Check if errno == ENOENT. */
261
				warnx("No existing RCS file");
262
				status = 1;
263
				(void)close(workfile_fd);
264
				continue;
265
			}
266
		} else {
267
			if (pb.flags & CI_INIT) {
268
				warnx("%s already exists", pb.fpath);
269
				status = 1;
270
				(void)close(fd);
271
				(void)close(workfile_fd);
272
				continue;
273
			}
274
			pb.openflags &= ~RCS_CREATE;
275
		}
276
277
		pb.file = rcs_open(pb.fpath, fd, pb.openflags, pb.fmode);
278
		if (pb.file == NULL)
279
			errx(1, "failed to open rcsfile `%s'", pb.fpath);
280
281
		if ((pb.flags & DESCRIPTION) &&
282
		    rcs_set_description(pb.file, pb.description, pb.flags) == -1)
283
			err(1, "%s", pb.filename);
284
285
		if (!(pb.flags & QUIET))
286
			(void)fprintf(stderr,
287
			    "%s  <--  %s\n", pb.fpath, pb.filename);
288
289
		if (rev_str != NULL)
290
			if ((pb.newrev = rcs_getrevnum(rev_str, pb.file)) ==
291
			    NULL)
292
				errx(1, "invalid revision: %s", rev_str);
293
294
		if (!(pb.flags & NEWFILE))
295
			pb.flags |= CI_SKIPDESC;
296
297
		/* XXX - support for committing to a file without revisions */
298
		if (pb.file->rf_ndelta == 0) {
299
			pb.flags |= NEWFILE;
300
			pb.file->rf_flags |= RCS_CREATE;
301
		}
302
303
		/*
304
		 * workfile_fd will be closed in checkin_init or
305
		 * checkin_update
306
		 */
307
		if (pb.flags & NEWFILE) {
308
			if (checkin_init(&pb) == -1)
309
				status = 1;
310
		} else {
311
			if (checkin_update(&pb) == -1)
312
				status = 1;
313
		}
314
315
		rcs_close(pb.file);
316
		if (rev_str != NULL)
317
			rcsnum_free(pb.newrev);
318
		pb.newrev = NULL;
319
	}
320
321
	if (!(base_flags & QUIET) && status == 0)
322
		(void)fprintf(stderr, "done\n");
323
324
	return (status);
325
}
326
327
/*
328
 * checkin_diff_file()
329
 *
330
 * Generate the diff between the working file and a revision.
331
 * Returns pointer to a BUF on success, NULL on failure.
332
 */
333
static BUF *
334
checkin_diff_file(struct checkin_params *pb)
335
{
336
	char *path1, *path2;
337
	BUF *b1, *b2, *b3;
338
339
	b1 = b2 = b3 = NULL;
340
	path1 = path2 = NULL;
341
342
	if ((b1 = buf_load(pb->filename)) == NULL) {
343
		warnx("failed to load file: `%s'", pb->filename);
344
		goto out;
345
	}
346
347
	if ((b2 = rcs_getrev(pb->file, pb->frev)) == NULL) {
348
		warnx("failed to load revision");
349
		goto out;
350
	}
351
	b2 = rcs_kwexp_buf(b2, pb->file, pb->frev);
352
	b3 = buf_alloc(128);
353
354
	(void)xasprintf(&path1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir);
355
	buf_write_stmp(b1, path1);
356
357
	buf_free(b1);
358
	b1 = NULL;
359
360
	(void)xasprintf(&path2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir);
361
	buf_write_stmp(b2, path2);
362
363
	buf_free(b2);
364
	b2 = NULL;
365
366
	diff_format = D_RCSDIFF;
367
	if (diffreg(path1, path2, b3, D_FORCEASCII) == D_ERROR)
368
		goto out;
369
370
	return (b3);
371
out:
372
	buf_free(b1);
373
	buf_free(b2);
374
	buf_free(b3);
375
	free(path1);
376
	free(path2);
377
378
	return (NULL);
379
}
380
381
/*
382
 * checkin_getlogmsg()
383
 *
384
 * Get log message from user interactively.
385
 * Returns pointer to a char array on success, NULL on failure.
386
 */
387
static char *
388
checkin_getlogmsg(RCSNUM *rev, RCSNUM *rev2, int flags)
389
{
390
	char   *rcs_msg, nrev[RCS_REV_BUFSZ], prev[RCS_REV_BUFSZ];
391
	const char *prompt =
392
	    "enter log message, terminated with a single '.' or end of file:\n";
393
	RCSNUM *tmprev;
394
395
	rcs_msg = NULL;
396
	tmprev = rcsnum_alloc();
397
	rcsnum_cpy(rev, tmprev, 16);
398
	rcsnum_tostr(tmprev, prev, sizeof(prev));
399
	if (rev2 == NULL)
400
		rcsnum_tostr(rcsnum_inc(tmprev), nrev, sizeof(nrev));
401
	else
402
		rcsnum_tostr(rev2, nrev, sizeof(nrev));
403
	rcsnum_free(tmprev);
404
405
	if (!(flags & QUIET))
406
		(void)fprintf(stderr, "new revision: %s; "
407
		    "previous revision: %s\n", nrev, prev);
408
409
	rcs_msg = rcs_prompt(prompt, flags);
410
411
	return (rcs_msg);
412
}
413
414
/*
415
 * checkin_update()
416
 *
417
 * Do a checkin to an existing RCS file.
418
 *
419
 * On success, return 0. On error return -1.
420
 */
421
static int
422
checkin_update(struct checkin_params *pb)
423
{
424
	char numb1[RCS_REV_BUFSZ], numb2[RCS_REV_BUFSZ];
425
	struct stat st;
426
	BUF *bp;
427
428
	/*
429
	 * XXX this is wrong, we need to get the revision the user
430
	 * has the lock for. So we can decide if we want to create a
431
	 * branch or not. (if it's not current HEAD we need to branch).
432
	 */
433
	pb->frev = pb->file->rf_head;
434
435
	/* Load file contents */
436
	if ((bp = buf_load(pb->filename)) == NULL)
437
		return (-1);
438
439
	/* If this is a zero-ending RCSNUM eg 4.0, increment it (eg to 4.1) */
440
	if (pb->newrev != NULL && RCSNUM_ZERO_ENDING(pb->newrev))
441
		pb->newrev = rcsnum_inc(pb->newrev);
442
443
	if (checkin_checklock(pb) < 0)
444
		return (-1);
445
446
	/* If revision passed on command line is less than HEAD, bail.
447
	 * XXX only applies to ci -r1.2 foo for example if HEAD is > 1.2 and
448
	 * there is no lock set for the user.
449
	 */
450
	if (pb->newrev != NULL &&
451
	    rcsnum_cmp(pb->newrev, pb->frev, 0) != -1) {
452
		warnx("%s: revision %s too low; must be higher than %s",
453
		    pb->file->rf_path,
454
		    rcsnum_tostr(pb->newrev, numb1, sizeof(numb1)),
455
		    rcsnum_tostr(pb->frev, numb2, sizeof(numb2)));
456
		return (-1);
457
	}
458
459
	/*
460
	 * Set the date of the revision to be the last modification
461
	 * time of the working file if -d has no argument.
462
	 */
463
	if (pb->date == DATE_MTIME)
464
		checkin_mtimedate(pb);
465
466
	/* Date from argv/mtime must be more recent than HEAD */
467
	if (pb->date != DATE_NOW) {
468
		time_t head_date = rcs_rev_getdate(pb->file, pb->frev);
469
		if (pb->date <= head_date) {
470
			static const char fmt[] = "%Y/%m/%d %H:%M:%S";
471
			char dbuf1[256], dbuf2[256];
472
			struct tm *t, *t_head;
473
474
			t = gmtime(&pb->date);
475
			strftime(dbuf1, sizeof(dbuf1), fmt, t);
476
			t_head = gmtime(&head_date);
477
			strftime(dbuf2, sizeof(dbuf2), fmt, t_head);
478
479
			errx(1, "%s: Date %s precedes %s in revision %s.",
480
			    pb->file->rf_path, dbuf1, dbuf2,
481
			    rcsnum_tostr(pb->frev, numb2, sizeof(numb2)));
482
		}
483
	}
484
485
	/* Get RCS patch */
486
	if ((pb->deltatext = checkin_diff_file(pb)) == NULL) {
487
		warnx("failed to get diff");
488
		return (-1);
489
	}
490
491
	/*
492
	 * If -f is not specified and there are no differences, tell
493
	 * the user and revert to latest version.
494
	 */
495
	if (!(pb->flags & FORCE) && (buf_len(pb->deltatext) < 1)) {
496
		if (checkin_revert(pb) == -1)
497
			return (-1);
498
		else
499
			return (0);
500
	}
501
502
	/* If no log message specified, get it interactively. */
503
	if (pb->flags & INTERACTIVE) {
504
		if (pb->rcs_msg != NULL) {
505
			fprintf(stderr,
506
			    "reuse log message of previous file? [yn](y): ");
507
			if (rcs_yesno('y') != 'y') {
508
				free(pb->rcs_msg);
509
				pb->rcs_msg = NULL;
510
			}
511
		}
512
		if (pb->rcs_msg == NULL)
513
			pb->rcs_msg = checkin_getlogmsg(pb->frev, pb->newrev,
514
			    pb->flags);
515
	}
516
517
	if ((rcs_lock_remove(pb->file, pb->username, pb->frev) < 0) &&
518
	    (rcs_lock_getmode(pb->file) != RCS_LOCK_LOOSE)) {
519
		if (rcs_errno != RCS_ERR_NOENT)
520
			warnx("failed to remove lock");
521
		else if (!(pb->flags & CO_LOCK))
522
			warnx("previous revision was not locked; "
523
			    "ignoring -l option");
524
	}
525
526
	/* Current head revision gets the RCS patch as rd_text */
527
	if (rcs_deltatext_set(pb->file, pb->frev, pb->deltatext) == -1)
528
		errx(1, "failed to set new rd_text for head rev");
529
530
	/* Now add our new revision */
531
	if (rcs_rev_add(pb->file,
532
	    (pb->newrev == NULL ? RCS_HEAD_REV : pb->newrev),
533
	    pb->rcs_msg, pb->date, pb->author) != 0) {
534
		warnx("failed to add new revision");
535
		return (-1);
536
	}
537
538
	/*
539
	 * If we are checking in to a non-default (ie user-specified)
540
	 * revision, set head to this revision.
541
	 */
542
	if (pb->newrev != NULL) {
543
		if (rcs_head_set(pb->file, pb->newrev) < 0)
544
			errx(1, "rcs_head_set failed");
545
	} else
546
		pb->newrev = pb->file->rf_head;
547
548
	/* New head revision has to contain entire file; */
549
	if (rcs_deltatext_set(pb->file, pb->frev, bp) == -1)
550
		errx(1, "failed to set new head revision");
551
552
	/* Attach a symbolic name to this revision if specified. */
553
	if (pb->symbol != NULL &&
554
	    (checkin_attach_symbol(pb) < 0))
555
		return (-1);
556
557
	/* Set the state of this revision if specified. */
558
	if (pb->state != NULL)
559
		(void)rcs_state_set(pb->file, pb->newrev, pb->state);
560
561
	/* Maintain RCSFILE permissions */
562
	if (fstat(workfile_fd, &st) == -1)
563
		err(1, "%s", pb->filename);
564
565
	/* Strip all the write bits */
566
	pb->file->rf_mode = st.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH);
567
568
	(void)close(workfile_fd);
569
	(void)unlink(pb->filename);
570
571
	/* Write out RCSFILE before calling checkout_rev() */
572
	rcs_write(pb->file);
573
574
	/* Do checkout if -u or -l are specified. */
575
	if (((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK)) &&
576
	    !(pb->flags & CI_DEFAULT))
577
		checkout_rev(pb->file, pb->newrev, pb->filename, pb->flags,
578
		    pb->username, pb->author, NULL, NULL);
579
580
	if ((pb->flags & INTERACTIVE) && (pb->rcs_msg[0] == '\0')) {
581
		free(pb->rcs_msg);	/* free empty log message */
582
		pb->rcs_msg = NULL;
583
	}
584
585
	return (0);
586
}
587
588
/*
589
 * checkin_init()
590
 *
591
 * Does an initial check in, just enough to create the new ,v file
592
 * On success, return 0. On error return -1.
593
 */
594
static int
595
checkin_init(struct checkin_params *pb)
596
{
597
	BUF *bp;
598
	char numb[RCS_REV_BUFSZ];
599
	int fetchlog = 0;
600
	struct stat st;
601
602
	/* If this is a zero-ending RCSNUM eg 4.0, increment it (eg to 4.1) */
603
	if (pb->newrev != NULL && RCSNUM_ZERO_ENDING(pb->newrev)) {
604
		pb->frev = rcsnum_alloc();
605
		rcsnum_cpy(pb->newrev, pb->frev, 0);
606
		pb->newrev = rcsnum_inc(pb->newrev);
607
		fetchlog = 1;
608
	}
609
610
	/* Load file contents */
611
	if ((bp = buf_load(pb->filename)) == NULL)
612
		return (-1);
613
614
	/* Get default values from working copy if -k specified */
615
	if (pb->flags & CI_KEYWORDSCAN)
616
		checkin_keywordscan(bp, &pb->newrev,
617
		    &pb->date, &pb->state, &pb->author);
618
619
	if (pb->flags & CI_SKIPDESC)
620
		goto skipdesc;
621
622
	/* Get description from user */
623
	if (pb->description == NULL &&
624
	    rcs_set_description(pb->file, NULL, pb->flags) == -1) {
625
		warn("%s", pb->filename);
626
		return (-1);
627
	}
628
629
skipdesc:
630
631
	/*
632
	 * If the user had specified a zero-ending revision number e.g. 4.0
633
	 * emulate odd GNU behaviour and fetch log message.
634
	 */
635
	if (fetchlog == 1) {
636
		pb->rcs_msg = checkin_getlogmsg(pb->frev, pb->newrev,
637
		    pb->flags);
638
		rcsnum_free(pb->frev);
639
	}
640
641
	/*
642
	 * Set the date of the revision to be the last modification
643
	 * time of the working file if -d has no argument.
644
	 */
645
	if (pb->date == DATE_MTIME)
646
		checkin_mtimedate(pb);
647
648
	/* Now add our new revision */
649
	if (rcs_rev_add(pb->file,
650
	    (pb->newrev == NULL ? RCS_HEAD_REV : pb->newrev),
651
	    (pb->rcs_msg == NULL ? "Initial revision" : pb->rcs_msg),
652
	    pb->date, pb->author) != 0) {
653
		warnx("failed to add new revision");
654
		return (-1);
655
	}
656
657
	/*
658
	 * If we are checking in to a non-default (ie user-specified)
659
	 * revision, set head to this revision.
660
	 */
661
	if (pb->newrev != NULL) {
662
		if (rcs_head_set(pb->file, pb->newrev) < 0)
663
			errx(1, "rcs_head_set failed");
664
	} else
665
		pb->newrev = pb->file->rf_head;
666
667
	/* New head revision has to contain entire file; */
668
	if (rcs_deltatext_set(pb->file, pb->file->rf_head, bp) == -1) {
669
		warnx("failed to set new head revision");
670
		return (-1);
671
	}
672
673
	/* Attach a symbolic name to this revision if specified. */
674
	if (pb->symbol != NULL && checkin_attach_symbol(pb) < 0)
675
		return (-1);
676
677
	/* Set the state of this revision if specified. */
678
	if (pb->state != NULL)
679
		(void)rcs_state_set(pb->file, pb->newrev, pb->state);
680
681
	/* Inherit RCSFILE permissions from file being checked in */
682
	if (fstat(workfile_fd, &st) == -1)
683
		err(1, "%s", pb->filename);
684
685
	/* Strip all the write bits */
686
	pb->file->rf_mode = st.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH);
687
688
	(void)close(workfile_fd);
689
	(void)unlink(pb->filename);
690
691
	/* Write out RCSFILE before calling checkout_rev() */
692
	rcs_write(pb->file);
693
694
	/* Do checkout if -u or -l are specified. */
695
	if (((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK)) &&
696
	    !(pb->flags & CI_DEFAULT)) {
697
		checkout_rev(pb->file, pb->newrev, pb->filename, pb->flags,
698
		    pb->username, pb->author, NULL, NULL);
699
	}
700
701
	if (!(pb->flags & QUIET)) {
702
		fprintf(stderr, "initial revision: %s\n",
703
		    rcsnum_tostr(pb->newrev, numb, sizeof(numb)));
704
	}
705
706
	return (0);
707
}
708
709
/*
710
 * checkin_attach_symbol()
711
 *
712
 * Attempt to attach the specified symbol to the revision.
713
 * On success, return 0. On error return -1.
714
 */
715
static int
716
checkin_attach_symbol(struct checkin_params *pb)
717
{
718
	char rbuf[RCS_REV_BUFSZ];
719
	int ret;
720
	if (!(pb->flags & QUIET))
721
		printf("symbol: %s\n", pb->symbol);
722
	if (pb->flags & CI_SYMFORCE) {
723
		if (rcs_sym_remove(pb->file, pb->symbol) < 0) {
724
			if (rcs_errno != RCS_ERR_NOENT) {
725
				warnx("problem removing symbol: %s",
726
				    pb->symbol);
727
				return (-1);
728
			}
729
		}
730
	}
731
	if ((ret = rcs_sym_add(pb->file, pb->symbol, pb->newrev)) == -1 &&
732
	    (rcs_errno == RCS_ERR_DUPENT)) {
733
		rcsnum_tostr(rcs_sym_getrev(pb->file, pb->symbol),
734
		    rbuf, sizeof(rbuf));
735
		warnx("symbolic name %s already bound to %s", pb->symbol, rbuf);
736
		return (-1);
737
	} else if (ret == -1) {
738
		warnx("problem adding symbol: %s", pb->symbol);
739
		return (-1);
740
	}
741
	return (0);
742
}
743
744
/*
745
 * checkin_revert()
746
 *
747
 * If there are no differences between the working file and the latest revision
748
 * and the -f flag is not specified, simply revert to the latest version and
749
 * warn the user.
750
 *
751
 */
752
static int
753
checkin_revert(struct checkin_params *pb)
754
{
755
	char rbuf[RCS_REV_BUFSZ];
756
757
	rcsnum_tostr(pb->frev, rbuf, sizeof(rbuf));
758
759
	if (!(pb->flags & QUIET))
760
		(void)fprintf(stderr, "file is unchanged; reverting "
761
		    "to previous revision %s\n", rbuf);
762
763
	/* Attach a symbolic name to this revision if specified. */
764
	if (pb->symbol != NULL) {
765
		if (checkin_checklock(pb) == -1)
766
			return (-1);
767
768
		pb->newrev = pb->frev;
769
		if (checkin_attach_symbol(pb) == -1)
770
			return (-1);
771
	}
772
773
	pb->flags |= CO_REVERT;
774
	(void)close(workfile_fd);
775
	(void)unlink(pb->filename);
776
777
	/* If needed, write out RCSFILE before calling checkout_rev() */
778
	if (pb->symbol != NULL)
779
		rcs_write(pb->file);
780
781
	if ((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK))
782
		checkout_rev(pb->file, pb->frev, pb->filename,
783
		    pb->flags, pb->username, pb->author, NULL, NULL);
784
785
	return (0);
786
}
787
788
/*
789
 * checkin_checklock()
790
 *
791
 * Check for the existence of a lock on the file.  If there are no locks, or it
792
 * is not locked by the correct user, return -1.  Otherwise, return 0.
793
 */
794
static int
795
checkin_checklock(struct checkin_params *pb)
796
{
797
	struct rcs_lock *lkp;
798
799
	if (rcs_lock_getmode(pb->file) == RCS_LOCK_LOOSE)
800
		return (0);
801
802
	TAILQ_FOREACH(lkp, &(pb->file->rf_locks), rl_list) {
803
		if (!strcmp(lkp->rl_name, pb->username) &&
804
		    !rcsnum_cmp(lkp->rl_num, pb->frev, 0))
805
			return (0);
806
	}
807
808
	warnx("%s: no lock set by %s", pb->file->rf_path, pb->username);
809
	return (-1);
810
}
811
812
/*
813
 * checkin_mtimedate()
814
 *
815
 * Set the date of the revision to be the last modification
816
 * time of the working file.
817
 */
818
static void
819
checkin_mtimedate(struct checkin_params *pb)
820
{
821
	struct stat sb;
822
823
	if (fstat(workfile_fd, &sb) == -1)
824
		err(1, "%s", pb->filename);
825
826
	pb->date = sb.st_mtimespec.tv_sec;
827
}
828
829
/*
830
 * checkin_keywordscan()
831
 *
832
 * Searches working file for keyword values to determine its revision
833
 * number, creation date and author, and uses these values instead of
834
 * calculating them locally.
835
 *
836
 * Params: The data buffer to scan and pointers to pointers of variables in
837
 * which to store the outputs.
838
 *
839
 * On success, return 0. On error return -1.
840
 */
841
static int
842
checkin_keywordscan(BUF *data, RCSNUM **rev, time_t *date, char **author,
843
    char **state)
844
{
845
	BUF *buf;
846
	size_t left;
847
	u_int j;
848
	char *kwstr;
849
	unsigned char *c, *end, *start;
850
851
	end = buf_get(data) + buf_len(data) - 1;
852
	kwstr = NULL;
853
854
	left = buf_len(data);
855
	for (c = buf_get(data);
856
	    c <= end && (c = memchr(c, '$', left)) != NULL;
857
	    left = end - c + 1) {
858
		size_t len;
859
860
		start = c;
861
		c++;
862
		if (!isalpha(*c))
863
			continue;
864
865
		/* look for any matching keywords */
866
		for (j = 0; j < 10; j++) {
867
			len = strlen(rcs_expkw[j].kw_str);
868
			if (left < len)
869
				continue;
870
			if (memcmp(c, rcs_expkw[j].kw_str, len) != 0) {
871
				kwstr = rcs_expkw[j].kw_str;
872
				break;
873
			}
874
		}
875
876
		/* unknown keyword, continue looking */
877
		if (kwstr == NULL)
878
			continue;
879
880
		c += len;
881
		if (c > end) {
882
			kwstr = NULL;
883
			break;
884
		}
885
		if (*c != ':') {
886
			kwstr = NULL;
887
			continue;
888
		}
889
890
		/* Find end of line or end of keyword. */
891
		while (++c <= end) {
892
			if (*c == '\n') {
893
				/* Skip newline since it is definitely not `$'. */
894
				++c;
895
				goto loopend;
896
			}
897
			if (*c == '$')
898
				break;
899
		}
900
901
		len = c - start + 1;
902
		buf = buf_alloc(len + 1);
903
		buf_append(buf, start, len);
904
905
		/* XXX - Not binary safe. */
906
		buf_putc(buf, '\0');
907
		checkin_parsekeyword(buf_get(buf), rev, date, author, state);
908
		buf_free(buf);
909
loopend:;
910
	}
911
	if (kwstr == NULL)
912
		return (-1);
913
	else
914
		return (0);
915
}
916
917
/*
918
 * checkin_keywordtype()
919
 *
920
 * Given an RCS keyword string, determine what type of string it is.
921
 * This enables us to know what data should be in it.
922
 *
923
 * Returns type on success, or -1 on failure.
924
 */
925
static int
926
checkin_keywordtype(char *keystring)
927
{
928
	char *p;
929
930
	p = keystring;
931
	p++;
932
	if (strncmp(p, KW_ID, strlen(KW_ID)) == 0 ||
933
	    strncmp(p, KW_OPENBSD, strlen(KW_OPENBSD)) == 0)
934
		return (KW_TYPE_ID);
935
	else if (strncmp(p, KW_AUTHOR, strlen(KW_AUTHOR)) == 0)
936
		return (KW_TYPE_AUTHOR);
937
	else if (strncmp(p, KW_DATE, strlen(KW_DATE)) == 0)
938
		return (KW_TYPE_DATE);
939
	else if (strncmp(p, KW_STATE, strlen(KW_STATE)) == 0)
940
		return (KW_TYPE_STATE);
941
	else if (strncmp(p, KW_REVISION, strlen(KW_REVISION)) == 0)
942
		return (KW_TYPE_REVISION);
943
	else
944
		return (-1);
945
}
946
947
/*
948
 * checkin_parsekeyword()
949
 *
950
 * Do the actual parsing of an RCS keyword string, setting the values passed
951
 * to the function to whatever is found.
952
 *
953
 * XXX - Don't error out on malformed keywords.
954
 */
955
static void
956
checkin_parsekeyword(char *keystring, RCSNUM **rev, time_t *date,
957
    char **author, char **state)
958
{
959
	char *tokens[KW_NUMTOKS_MAX], *p, *datestring;
960
	int i = 0;
961
962
	for ((p = strtok(keystring, " ")); p; (p = strtok(NULL, " "))) {
963
		if (i < KW_NUMTOKS_MAX - 1)
964
			tokens[i++] = p;
965
		else
966
			break;
967
	}
968
969
	/* Parse data out of the expanded keyword */
970
	switch (checkin_keywordtype(keystring)) {
971
	case KW_TYPE_ID:
972
		if (i < 3)
973
			break;
974
		/* only parse revision if one is not already set */
975
		if (*rev == NULL) {
976
			if ((*rev = rcsnum_parse(tokens[2])) == NULL)
977
				errx(1, "could not parse rcsnum");
978
		}
979
980
		if (i < 5)
981
			break;
982
		(void)xasprintf(&datestring, "%s %s", tokens[3], tokens[4]);
983
		if ((*date = date_parse(datestring)) == -1)
984
			errx(1, "could not parse date");
985
		free(datestring);
986
987
		if (i < 6)
988
			break;
989
		free(*author);
990
		*author = xstrdup(tokens[5]);
991
992
		if (i < 7)
993
			break;
994
		free(*state);
995
		*state = xstrdup(tokens[6]);
996
		break;
997
	case KW_TYPE_AUTHOR:
998
		if (i < 2)
999
			break;
1000
		free(*author);
1001
		*author = xstrdup(tokens[1]);
1002
		break;
1003
	case KW_TYPE_DATE:
1004
		if (i < 3)
1005
			break;
1006
		(void)xasprintf(&datestring, "%s %s", tokens[1], tokens[2]);
1007
		if ((*date = date_parse(datestring)) == -1)
1008
			errx(1, "could not parse date");
1009
		free(datestring);
1010
		break;
1011
	case KW_TYPE_STATE:
1012
		if (i < 2)
1013
			break;
1014
		free(*state);
1015
		*state = xstrdup(tokens[1]);
1016
		break;
1017
	case KW_TYPE_REVISION:
1018
		if (i < 2)
1019
			break;
1020
		/* only parse revision if one is not already set */
1021
		if (*rev != NULL)
1022
			break;
1023
		if ((*rev = rcsnum_parse(tokens[1])) == NULL)
1024
			errx(1, "could not parse rcsnum");
1025
		break;
1026
	}
1027
}