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