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