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