xref: /openbsd/usr.bin/rcs/ci.c (revision 03d5d688)
1 /*	$OpenBSD: ci.c,v 1.225 2023/08/11 04:44:28 guenther 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
checkin_usage(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
checkin_main(int argc,char ** argv)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 *
checkin_diff_file(struct checkin_params * pb)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 *
checkin_getlogmsg(RCSNUM * rev,RCSNUM * rev2,int flags)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
checkin_update(struct checkin_params * pb)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
checkin_init(struct checkin_params * pb)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
checkin_attach_symbol(struct checkin_params * pb)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
checkin_revert(struct checkin_params * pb)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
checkin_checklock(struct checkin_params * pb)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
checkin_mtimedate(struct checkin_params * pb)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_mtime;
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
checkin_keywordscan(BUF * data,RCSNUM ** rev,time_t * date,char ** author,char ** state)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
checkin_keywordtype(char * keystring)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
checkin_parsekeyword(char * keystring,RCSNUM ** rev,time_t * date,char ** author,char ** state)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 }
1028