xref: /openbsd-src/usr.bin/rcs/ci.c (revision 03d5d688c73ff0f6d83c4610eaf1045d9c86d744)
1*03d5d688Sguenther /*	$OpenBSD: ci.c,v 1.225 2023/08/11 04:44:28 guenther Exp $	*/
238ad7a03Sniallo /*
3303c871eSniallo  * Copyright (c) 2005, 2006 Niall O'Higgins <niallo@openbsd.org>
438ad7a03Sniallo  * All rights reserved.
538ad7a03Sniallo  *
638ad7a03Sniallo  * Redistribution and use in source and binary forms, with or without
732b05e22Sniallo  * modification, are permitted provided that the following conditions
838ad7a03Sniallo  * are met:
938ad7a03Sniallo  *
1032b05e22Sniallo  * 1. Redistributions of source code must retain the above copyright
1132b05e22Sniallo  *    notice, this list of conditions and the following disclaimer.
1238ad7a03Sniallo  * 2. The name of the author may not be used to endorse or promote products
1338ad7a03Sniallo  *    derived from this software without specific prior written permission.
1438ad7a03Sniallo  *
1538ad7a03Sniallo  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
1638ad7a03Sniallo  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
1738ad7a03Sniallo  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
1838ad7a03Sniallo  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
1938ad7a03Sniallo  * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
2038ad7a03Sniallo  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
2138ad7a03Sniallo  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
2238ad7a03Sniallo  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
2338ad7a03Sniallo  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
2438ad7a03Sniallo  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2538ad7a03Sniallo  */
2638ad7a03Sniallo 
274781e2faSxsa #include <sys/stat.h>
284781e2faSxsa 
294781e2faSxsa #include <ctype.h>
304781e2faSxsa #include <err.h>
314781e2faSxsa #include <fcntl.h>
324781e2faSxsa #include <stdio.h>
334781e2faSxsa #include <stdlib.h>
344781e2faSxsa #include <string.h>
354781e2faSxsa #include <unistd.h>
3638ad7a03Sniallo 
3738ad7a03Sniallo #include "rcsprog.h"
38cd8dad74Sxsa #include "diff.h"
3938ad7a03Sniallo 
40d491b5edSray #define CI_OPTSTRING	"d::f::I::i::j::k::l::M::m::N:n:qr::s:Tt::u::Vw:x::z::"
418ea61e96Sniallo #define DATE_NOW	-1
428ea61e96Sniallo #define DATE_MTIME	-2
438ea61e96Sniallo 
44303c871eSniallo #define KW_ID		"Id"
453af23efeSguenther #define KW_OPENBSD	"OpenBSD"
46303c871eSniallo #define KW_AUTHOR	"Author"
47303c871eSniallo #define KW_DATE		"Date"
48303c871eSniallo #define KW_STATE	"State"
49303c871eSniallo #define KW_REVISION	"Revision"
50d5c46754Sderaadt 
51303c871eSniallo #define KW_TYPE_ID		1
52303c871eSniallo #define KW_TYPE_AUTHOR		2
53303c871eSniallo #define KW_TYPE_DATE		3
54303c871eSniallo #define KW_TYPE_STATE		4
55303c871eSniallo #define KW_TYPE_REVISION	5
56d5c46754Sderaadt 
5764d00fd2Sray /* Maximum number of tokens in a keyword. */
5864d00fd2Sray #define KW_NUMTOKS_MAX		10
5964d00fd2Sray 
60b12ab1d2Sniallo #define RCSNUM_ZERO_ENDING(x) (x->rn_id[x->rn_len - 1] == 0)
61b12ab1d2Sniallo 
62303c871eSniallo extern struct rcs_kw rcs_expkw[];
63303c871eSniallo 
64e13e08a4Sjoris static int workfile_fd;
65e13e08a4Sjoris 
66457dba6dSniallo struct checkin_params {
67457dba6dSniallo 	int flags, openflags;
68457dba6dSniallo 	mode_t fmode;
69457dba6dSniallo 	time_t date;
70457dba6dSniallo 	RCSFILE *file;
71457dba6dSniallo 	RCSNUM *frev, *newrev;
72f879e142Sray 	const char *description, *symbol;
73b9fc9a72Sderaadt 	char fpath[PATH_MAX], *rcs_msg, *username, *filename;
74f879e142Sray 	char *author, *state;
753de4517bSniallo 	BUF *deltatext;
76457dba6dSniallo };
77457dba6dSniallo 
781a1aa826Sniallo static int	 checkin_attach_symbol(struct checkin_params *);
791a1aa826Sniallo static int	 checkin_checklock(struct checkin_params *);
803de4517bSniallo static BUF	*checkin_diff_file(struct checkin_params *);
81913586deSxsa static char	*checkin_getlogmsg(RCSNUM *, RCSNUM *, int);
82e7f28f16Sniallo static int	 checkin_init(struct checkin_params *);
8364d00fd2Sray static int	 checkin_keywordscan(BUF *, RCSNUM **, time_t *, char **,
84303c871eSniallo     char **);
85303c871eSniallo static int	 checkin_keywordtype(char *);
86e13e08a4Sjoris static void	 checkin_mtimedate(struct checkin_params *);
87303c871eSniallo static void	 checkin_parsekeyword(char *, RCSNUM **, time_t *, char **,
88303c871eSniallo     char **);
891a1aa826Sniallo static int	 checkin_update(struct checkin_params *);
90fa741c14Sniallo static int	 checkin_revert(struct checkin_params *);
91f9b67873Sniallo 
92960e00beSotto __dead void
checkin_usage(void)9338ad7a03Sniallo checkin_usage(void)
9438ad7a03Sniallo {
9538ad7a03Sniallo 	fprintf(stderr,
966d1ef57bSniallo 	    "usage: ci [-qV] [-d[date]] [-f[rev]] [-I[rev]] [-i[rev]]\n"
97d8cd5333Sxsa 	    "          [-j[rev]] [-k[rev]] [-l[rev]] [-M[rev]] [-mmsg]\n"
984f1069e1Ssobrado 	    "          [-Nsymbol] [-nsymbol] [-r[rev]] [-sstate] [-t[str]]\n"
99d8cd5333Sxsa 	    "          [-u[rev]] [-wusername] [-xsuffixes] [-ztz] file ...\n");
100960e00beSotto 
101960e00beSotto 	exit(1);
10238ad7a03Sniallo }
10338ad7a03Sniallo 
10438ad7a03Sniallo /*
10538ad7a03Sniallo  * checkin_main()
10638ad7a03Sniallo  *
10738ad7a03Sniallo  * Handler for the `ci' program.
10838ad7a03Sniallo  * Returns 0 on success, or >0 on error.
10938ad7a03Sniallo  */
11038ad7a03Sniallo int
checkin_main(int argc,char ** argv)11138ad7a03Sniallo checkin_main(int argc, char **argv)
11238ad7a03Sniallo {
113bc8d37c3Sjoris 	int fd;
114457dba6dSniallo 	int i, ch, status;
11503ce18daSbluhm 	int base_flags, base_openflags;
11636076781Sray 	char *rev_str;
117457dba6dSniallo 	struct checkin_params pb;
11838ad7a03Sniallo 
119457dba6dSniallo 	pb.date = DATE_NOW;
120457dba6dSniallo 	pb.file = NULL;
121303c871eSniallo 	pb.rcs_msg = pb.username = pb.author = pb.state = NULL;
122f879e142Sray 	pb.description = pb.symbol = NULL;
123f879e142Sray 	pb.deltatext = NULL;
124457dba6dSniallo 	pb.newrev =  NULL;
125c51cb395Sniallo 	pb.fmode = S_IRUSR|S_IRGRP|S_IROTH;
12603ce18daSbluhm 	status = 0;
12703ce18daSbluhm 	base_flags = INTERACTIVE;
12803ce18daSbluhm 	base_openflags = RCS_RDWR|RCS_CREATE|RCS_PARSE_FULLY;
12936076781Sray 	rev_str = NULL;
130c825d341Sniallo 
131457dba6dSniallo 	while ((ch = rcs_getopt(argc, argv, CI_OPTSTRING)) != -1) {
13238ad7a03Sniallo 		switch (ch) {
13369f64830Sniallo 		case 'd':
1348ea61e96Sniallo 			if (rcs_optarg == NULL)
135457dba6dSniallo 				pb.date = DATE_MTIME;
136092db204Sray 			else if ((pb.date = date_parse(rcs_optarg)) == -1)
137a3660ae3Sxsa 				errx(1, "invalid date");
13869f64830Sniallo 			break;
13907d7e9e7Sniallo 		case 'f':
14036076781Sray 			rcs_setrevstr(&rev_str, rcs_optarg);
14103ce18daSbluhm 			base_flags |= FORCE;
14207d7e9e7Sniallo 			break;
143d8cd5333Sxsa 		case 'I':
14436076781Sray 			rcs_setrevstr(&rev_str, rcs_optarg);
14503ce18daSbluhm 			base_flags |= INTERACTIVE;
146d8cd5333Sxsa 			break;
147457dba6dSniallo 		case 'i':
14836076781Sray 			rcs_setrevstr(&rev_str, rcs_optarg);
14903ce18daSbluhm 			base_openflags |= RCS_CREATE;
15003ce18daSbluhm 			base_flags |= CI_INIT;
151457dba6dSniallo 			break;
152457dba6dSniallo 		case 'j':
15336076781Sray 			rcs_setrevstr(&rev_str, rcs_optarg);
15403ce18daSbluhm 			base_openflags &= ~RCS_CREATE;
15503ce18daSbluhm 			base_flags &= ~CI_INIT;
156457dba6dSniallo 			break;
157303c871eSniallo 		case 'k':
15836076781Sray 			rcs_setrevstr(&rev_str, rcs_optarg);
15903ce18daSbluhm 			base_flags |= CI_KEYWORDSCAN;
160303c871eSniallo 			break;
161c825d341Sniallo 		case 'l':
16236076781Sray 			rcs_setrevstr(&rev_str, rcs_optarg);
16303ce18daSbluhm 			base_flags |= CO_LOCK;
164c4521683Sniallo 			break;
165c4521683Sniallo 		case 'M':
16636076781Sray 			rcs_setrevstr(&rev_str, rcs_optarg);
16703ce18daSbluhm 			base_flags |= CO_REVDATE;
168c825d341Sniallo 			break;
16936f51f3dSniallo 		case 'm':
170457dba6dSniallo 			pb.rcs_msg = rcs_optarg;
1715b28f14aSxsa 			if (pb.rcs_msg == NULL)
172a3660ae3Sxsa 				errx(1, "missing message for -m option");
17303ce18daSbluhm 			base_flags &= ~INTERACTIVE;
17436f51f3dSniallo 			break;
17515bdf8c5Sniallo 		case 'N':
17603ce18daSbluhm 			base_flags |= CI_SYMFORCE;
17717d51281Sray 			/* FALLTHROUGH */
17840e38b8aSniallo 		case 'n':
179f879e142Sray 			pb.symbol = rcs_optarg;
1805b28f14aSxsa 			if (rcs_sym_check(pb.symbol) != 1)
181a3660ae3Sxsa 				errx(1, "invalid symbol `%s'", pb.symbol);
18240e38b8aSniallo 			break;
18336f51f3dSniallo 		case 'q':
18403ce18daSbluhm 			base_flags |= QUIET;
185c825d341Sniallo 			break;
18670de6859Sniallo 		case 'r':
18736076781Sray 			rcs_setrevstr(&rev_str, rcs_optarg);
18803ce18daSbluhm 			base_flags |= CI_DEFAULT;
18970de6859Sniallo 			break;
1909aae8b12Sniallo 		case 's':
191457dba6dSniallo 			pb.state = rcs_optarg;
1925b28f14aSxsa 			if (rcs_state_check(pb.state) < 0)
193a3660ae3Sxsa 				errx(1, "invalid state `%s'", pb.state);
1949aae8b12Sniallo 			break;
1954ecf1ebbSxsa 		case 'T':
19603ce18daSbluhm 			base_flags |= PRESERVETIME;
1974ecf1ebbSxsa 			break;
19830f91603Sniallo 		case 't':
199d491b5edSray 			/* Ignore bare -t; kept for backwards compatibility. */
200d491b5edSray 			if (rcs_optarg == NULL)
201d491b5edSray 				break;
202d491b5edSray 			pb.description = rcs_optarg;
20303ce18daSbluhm 			base_flags |= DESCRIPTION;
20430f91603Sniallo 			break;
20536f51f3dSniallo 		case 'u':
20636076781Sray 			rcs_setrevstr(&rev_str, rcs_optarg);
20703ce18daSbluhm 			base_flags |= CO_UNLOCK;
20836f51f3dSniallo 			break;
20936f51f3dSniallo 		case 'V':
21036f51f3dSniallo 			printf("%s\n", rcs_version);
21136f51f3dSniallo 			exit(0);
212d7a923b9Sniallo 		case 'w':
2138ac837e5Snicm 			free(pb.author);
2140450b43bSjoris 			pb.author = xstrdup(rcs_optarg);
215d7a923b9Sniallo 			break;
2167518c1e9Sxsa 		case 'x':
2174365263eSray 			/* Use blank extension if none given. */
2184365263eSray 			rcs_suffixes = rcs_optarg ? rcs_optarg : "";
2197518c1e9Sxsa 			break;
2206baf4b21Sjoris 		case 'z':
2216baf4b21Sjoris 			timezone_flag = rcs_optarg;
2226baf4b21Sjoris 			break;
22338ad7a03Sniallo 		default:
22438ad7a03Sniallo 			(usage)();
22538ad7a03Sniallo 		}
22638ad7a03Sniallo 	}
22738ad7a03Sniallo 
228a2b34663Sjoris 	argc -= rcs_optind;
229a2b34663Sjoris 	argv += rcs_optind;
230a2b34663Sjoris 
23138ad7a03Sniallo 	if (argc == 0) {
23282ca7eaaSxsa 		warnx("no input file");
23338ad7a03Sniallo 		(usage)();
23438ad7a03Sniallo 	}
23538ad7a03Sniallo 
236f5b81ab4Sxsa 	if ((pb.username = getlogin()) == NULL)
237a3660ae3Sxsa 		err(1, "getlogin");
238d7a923b9Sniallo 
23938ad7a03Sniallo 	for (i = 0; i < argc; i++) {
24003ce18daSbluhm 		/*
24103ce18daSbluhm 		 * The pb.flags and pb.openflags may change during
24203ce18daSbluhm 		 * loop iteration so restore them for each file.
24303ce18daSbluhm 		 */
24403ce18daSbluhm 		pb.flags = base_flags;
24503ce18daSbluhm 		pb.openflags = base_openflags;
24603ce18daSbluhm 
247457dba6dSniallo 		pb.filename = argv[i];
248168f4dcaSotto 		rcs_strip_suffix(pb.filename);
24938ad7a03Sniallo 
250e13e08a4Sjoris 		if ((workfile_fd = open(pb.filename, O_RDONLY)) == -1)
251a3660ae3Sxsa 			err(1, "%s", pb.filename);
252e13e08a4Sjoris 
253f3876e23Sray 		/* Find RCS file path. */
254f3876e23Sray 		fd = rcs_choosefile(pb.filename, pb.fpath, sizeof(pb.fpath));
255bc8d37c3Sjoris 
256bc8d37c3Sjoris 		if (fd < 0) {
25720c69716Sniallo 			if (pb.openflags & RCS_CREATE)
258457dba6dSniallo 				pb.flags |= NEWFILE;
25920c69716Sniallo 			else {
260d9622f60Sray 				/* XXX - Check if errno == ENOENT. */
26182ca7eaaSxsa 				warnx("No existing RCS file");
26295498594Sniallo 				status = 1;
263e13e08a4Sjoris 				(void)close(workfile_fd);
26495498594Sniallo 				continue;
26520c69716Sniallo 			}
26695498594Sniallo 		} else {
26795498594Sniallo 			if (pb.flags & CI_INIT) {
26882ca7eaaSxsa 				warnx("%s already exists", pb.fpath);
26995498594Sniallo 				status = 1;
270bc8d37c3Sjoris 				(void)close(fd);
271e13e08a4Sjoris 				(void)close(workfile_fd);
27295498594Sniallo 				continue;
27395498594Sniallo 			}
274457dba6dSniallo 			pb.openflags &= ~RCS_CREATE;
27595498594Sniallo 		}
276e13e08a4Sjoris 
277bc8d37c3Sjoris 		pb.file = rcs_open(pb.fpath, fd, pb.openflags, pb.fmode);
2785b28f14aSxsa 		if (pb.file == NULL)
279671d72e5Sxsa 			errx(1, "failed to open rcsfile `%s'", pb.fpath);
28007d7e9e7Sniallo 
2814b1a1f86Sray 		if ((pb.flags & DESCRIPTION) &&
28200f4e67cSmillert 		    rcs_set_description(pb.file, pb.description, pb.flags) == -1)
283d99706fcSjoris 			err(1, "%s", pb.filename);
284d491b5edSray 
285913586deSxsa 		if (!(pb.flags & QUIET))
286df0c9c73Sxsa 			(void)fprintf(stderr,
287df0c9c73Sxsa 			    "%s  <--  %s\n", pb.fpath, pb.filename);
288457dba6dSniallo 
28936076781Sray 		if (rev_str != NULL)
290e7816acdSray 			if ((pb.newrev = rcs_getrevnum(rev_str, pb.file)) ==
291e7816acdSray 			    NULL)
292a3660ae3Sxsa 				errx(1, "invalid revision: %s", rev_str);
29336076781Sray 
2944b1a048aSniallo 		if (!(pb.flags & NEWFILE))
2954b1a048aSniallo 			pb.flags |= CI_SKIPDESC;
296e13e08a4Sjoris 
2979a192d08Sdavid 		/* XXX - support for committing to a file without revisions */
298848faecaSjoris 		if (pb.file->rf_ndelta == 0) {
299848faecaSjoris 			pb.flags |= NEWFILE;
300848faecaSjoris 			pb.file->rf_flags |= RCS_CREATE;
301848faecaSjoris 		}
302848faecaSjoris 
303e13e08a4Sjoris 		/*
304e13e08a4Sjoris 		 * workfile_fd will be closed in checkin_init or
305e13e08a4Sjoris 		 * checkin_update
306e13e08a4Sjoris 		 */
3077ebe9266Sray 		if (pb.flags & NEWFILE) {
3087ebe9266Sray 			if (checkin_init(&pb) == -1)
3097ebe9266Sray 				status = 1;
3107ebe9266Sray 		} else {
3117ebe9266Sray 			if (checkin_update(&pb) == -1)
3127ebe9266Sray 				status = 1;
3137ebe9266Sray 		}
31408adf6b1Sniallo 
31587b13a24Sniallo 		rcs_close(pb.file);
316f3658d6bSjca 		if (rev_str != NULL)
317f3658d6bSjca 			rcsnum_free(pb.newrev);
31839bd7773Sniallo 		pb.newrev = NULL;
319f9b67873Sniallo 	}
320f9b67873Sniallo 
32103ce18daSbluhm 	if (!(base_flags & QUIET) && status == 0)
322df0c9c73Sxsa 		(void)fprintf(stderr, "done\n");
323cfbe36a0Sxsa 
324f018e672Sniallo 	return (status);
325f9b67873Sniallo }
326f9b67873Sniallo 
327446c1022Sniallo /*
328446c1022Sniallo  * checkin_diff_file()
329446c1022Sniallo  *
330446c1022Sniallo  * Generate the diff between the working file and a revision.
3319a30f545Sniallo  * Returns pointer to a BUF on success, NULL on failure.
332446c1022Sniallo  */
3333de4517bSniallo static BUF *
checkin_diff_file(struct checkin_params * pb)334457dba6dSniallo checkin_diff_file(struct checkin_params *pb)
335f9b67873Sniallo {
336cdc530d2Sxsa 	char *path1, *path2;
337f9b67873Sniallo 	BUF *b1, *b2, *b3;
338f9b67873Sniallo 
339f2af11b8Sjoris 	b1 = b2 = b3 = NULL;
340ac14fd1aSmillert 	path1 = path2 = NULL;
341f9b67873Sniallo 
3420ee14128Sray 	if ((b1 = buf_load(pb->filename)) == NULL) {
34382ca7eaaSxsa 		warnx("failed to load file: `%s'", pb->filename);
344f2af11b8Sjoris 		goto out;
345f9b67873Sniallo 	}
346f9b67873Sniallo 
347457dba6dSniallo 	if ((b2 = rcs_getrev(pb->file, pb->frev)) == NULL) {
34882ca7eaaSxsa 		warnx("failed to load revision");
349f2af11b8Sjoris 		goto out;
350f9b67873Sniallo 	}
351255c06afSmillert 	b2 = rcs_kwexp_buf(b2, pb->file, pb->frev);
3520ee14128Sray 	b3 = buf_alloc(128);
353f9b67873Sniallo 
354cdc530d2Sxsa 	(void)xasprintf(&path1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir);
3557bb3ddb0Sray 	buf_write_stmp(b1, path1);
3568933887bSjoris 
3577bb3ddb0Sray 	buf_free(b1);
358f2af11b8Sjoris 	b1 = NULL;
359f9b67873Sniallo 
360cdc530d2Sxsa 	(void)xasprintf(&path2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir);
3617bb3ddb0Sray 	buf_write_stmp(b2, path2);
3628933887bSjoris 
3637bb3ddb0Sray 	buf_free(b2);
364f2af11b8Sjoris 	b2 = NULL;
365f9b67873Sniallo 
366f9b67873Sniallo 	diff_format = D_RCSDIFF;
36768b523edSray 	if (diffreg(path1, path2, b3, D_FORCEASCII) == D_ERROR)
3682f865df6Sxsa 		goto out;
369f9b67873Sniallo 
3703de4517bSniallo 	return (b3);
371f2af11b8Sjoris out:
3727bb3ddb0Sray 	buf_free(b1);
3737bb3ddb0Sray 	buf_free(b2);
3747bb3ddb0Sray 	buf_free(b3);
3758ac837e5Snicm 	free(path1);
3768ac837e5Snicm 	free(path2);
377f9b67873Sniallo 
3783de4517bSniallo 	return (NULL);
37938ad7a03Sniallo }
3803082dc7fSniallo 
3813082dc7fSniallo /*
382446c1022Sniallo  * checkin_getlogmsg()
383446c1022Sniallo  *
3843082dc7fSniallo  * Get log message from user interactively.
385446c1022Sniallo  * Returns pointer to a char array on success, NULL on failure.
3863082dc7fSniallo  */
3873082dc7fSniallo static char *
checkin_getlogmsg(RCSNUM * rev,RCSNUM * rev2,int flags)388913586deSxsa checkin_getlogmsg(RCSNUM *rev, RCSNUM *rev2, int flags)
3893082dc7fSniallo {
390950e09dcSxsa 	char   *rcs_msg, nrev[RCS_REV_BUFSZ], prev[RCS_REV_BUFSZ];
39178f1771eSray 	const char *prompt =
39278f1771eSray 	    "enter log message, terminated with a single '.' or end of file:\n";
3933082dc7fSniallo 	RCSNUM *tmprev;
3943082dc7fSniallo 
3950d67b2a0Sniallo 	rcs_msg = NULL;
3963082dc7fSniallo 	tmprev = rcsnum_alloc();
3973082dc7fSniallo 	rcsnum_cpy(rev, tmprev, 16);
398c825d341Sniallo 	rcsnum_tostr(tmprev, prev, sizeof(prev));
39970de6859Sniallo 	if (rev2 == NULL)
4003082dc7fSniallo 		rcsnum_tostr(rcsnum_inc(tmprev), nrev, sizeof(nrev));
40170de6859Sniallo 	else
40270de6859Sniallo 		rcsnum_tostr(rev2, nrev, sizeof(nrev));
4033082dc7fSniallo 	rcsnum_free(tmprev);
4043082dc7fSniallo 
405913586deSxsa 	if (!(flags & QUIET))
406db3d0dd3Sxsa 		(void)fprintf(stderr, "new revision: %s; "
407db3d0dd3Sxsa 		    "previous revision: %s\n", nrev, prev);
4085a09c7eeSjoris 
40900f4e67cSmillert 	rcs_msg = rcs_prompt(prompt, flags);
41078f1771eSray 
411457dba6dSniallo 	return (rcs_msg);
412457dba6dSniallo }
413457dba6dSniallo 
414457dba6dSniallo /*
415e7f28f16Sniallo  * checkin_update()
416e7f28f16Sniallo  *
417e7f28f16Sniallo  * Do a checkin to an existing RCS file.
418e7f28f16Sniallo  *
419e7f28f16Sniallo  * On success, return 0. On error return -1.
420e7f28f16Sniallo  */
421e7f28f16Sniallo static int
checkin_update(struct checkin_params * pb)422e7f28f16Sniallo checkin_update(struct checkin_params *pb)
423e7f28f16Sniallo {
424950e09dcSxsa 	char numb1[RCS_REV_BUFSZ], numb2[RCS_REV_BUFSZ];
425c51cb395Sniallo 	struct stat st;
426e7f28f16Sniallo 	BUF *bp;
427e7f28f16Sniallo 
428bbfcb536Sjoris 	/*
429bbfcb536Sjoris 	 * XXX this is wrong, we need to get the revision the user
430bbfcb536Sjoris 	 * has the lock for. So we can decide if we want to create a
431bbfcb536Sjoris 	 * branch or not. (if it's not current HEAD we need to branch).
432bbfcb536Sjoris 	 */
433e7f28f16Sniallo 	pb->frev = pb->file->rf_head;
434e7f28f16Sniallo 
435a48b1fdbSniallo 	/* Load file contents */
4360ee14128Sray 	if ((bp = buf_load(pb->filename)) == NULL)
437fa741c14Sniallo 		return (-1);
438a48b1fdbSniallo 
439b12ab1d2Sniallo 	/* If this is a zero-ending RCSNUM eg 4.0, increment it (eg to 4.1) */
4407249ec9bSderaadt 	if (pb->newrev != NULL && RCSNUM_ZERO_ENDING(pb->newrev))
441b12ab1d2Sniallo 		pb->newrev = rcsnum_inc(pb->newrev);
442b12ab1d2Sniallo 
443bbfcb536Sjoris 	if (checkin_checklock(pb) < 0)
444fa741c14Sniallo 		return (-1);
445bbfcb536Sjoris 
446bbfcb536Sjoris 	/* If revision passed on command line is less than HEAD, bail.
447bbfcb536Sjoris 	 * XXX only applies to ci -r1.2 foo for example if HEAD is > 1.2 and
448bbfcb536Sjoris 	 * there is no lock set for the user.
449bbfcb536Sjoris 	 */
4507249ec9bSderaadt 	if (pb->newrev != NULL &&
451576cc718Sniallo 	    rcsnum_cmp(pb->newrev, pb->frev, 0) != -1) {
45282ca7eaaSxsa 		warnx("%s: revision %s too low; must be higher than %s",
4532e222e52Sxsa 		    pb->file->rf_path,
4542e222e52Sxsa 		    rcsnum_tostr(pb->newrev, numb1, sizeof(numb1)),
4552e222e52Sxsa 		    rcsnum_tostr(pb->frev, numb2, sizeof(numb2)));
456fa741c14Sniallo 		return (-1);
457e7f28f16Sniallo 	}
458e7f28f16Sniallo 
45917772c02Sniallo 	/*
46017772c02Sniallo 	 * Set the date of the revision to be the last modification
46117772c02Sniallo 	 * time of the working file if -d has no argument.
46217772c02Sniallo 	 */
463e13e08a4Sjoris 	if (pb->date == DATE_MTIME)
464e13e08a4Sjoris 		checkin_mtimedate(pb);
46517772c02Sniallo 
46617772c02Sniallo 	/* Date from argv/mtime must be more recent than HEAD */
46717772c02Sniallo 	if (pb->date != DATE_NOW) {
46817772c02Sniallo 		time_t head_date = rcs_rev_getdate(pb->file, pb->frev);
46917772c02Sniallo 		if (pb->date <= head_date) {
470c8b330d4Sguenther 			static const char fmt[] = "%Y/%m/%d %H:%M:%S";
471c8b330d4Sguenther 			char dbuf1[256], dbuf2[256];
472f8fe4695Sxsa 			struct tm *t, *t_head;
473f8fe4695Sxsa 
474114bf7b0Sray 			t = gmtime(&pb->date);
475f8fe4695Sxsa 			strftime(dbuf1, sizeof(dbuf1), fmt, t);
476114bf7b0Sray 			t_head = gmtime(&head_date);
477f8fe4695Sxsa 			strftime(dbuf2, sizeof(dbuf2), fmt, t_head);
478f8fe4695Sxsa 
479a1ce87d6Skrw 			errx(1, "%s: Date %s precedes %s in revision %s.",
480f8fe4695Sxsa 			    pb->file->rf_path, dbuf1, dbuf2,
48117772c02Sniallo 			    rcsnum_tostr(pb->frev, numb2, sizeof(numb2)));
48217772c02Sniallo 		}
48317772c02Sniallo 	}
48417772c02Sniallo 
4857da1ddabSxsa 	/* Get RCS patch */
486e7f28f16Sniallo 	if ((pb->deltatext = checkin_diff_file(pb)) == NULL) {
48782ca7eaaSxsa 		warnx("failed to get diff");
488fa741c14Sniallo 		return (-1);
489e7f28f16Sniallo 	}
490e7f28f16Sniallo 
491e7f28f16Sniallo 	/*
492e7f28f16Sniallo 	 * If -f is not specified and there are no differences, tell
493e7f28f16Sniallo 	 * the user and revert to latest version.
494e7f28f16Sniallo 	 */
4957bb3ddb0Sray 	if (!(pb->flags & FORCE) && (buf_len(pb->deltatext) < 1)) {
496fa741c14Sniallo 		if (checkin_revert(pb) == -1)
497fa741c14Sniallo 			return (-1);
498fa741c14Sniallo 		else
499fa741c14Sniallo 			return (0);
500e7f28f16Sniallo 	}
501e7f28f16Sniallo 
5027da1ddabSxsa 	/* If no log message specified, get it interactively. */
5039642f3ecSmillert 	if (pb->flags & INTERACTIVE) {
5049642f3ecSmillert 		if (pb->rcs_msg != NULL) {
5059642f3ecSmillert 			fprintf(stderr,
5069642f3ecSmillert 			    "reuse log message of previous file? [yn](y): ");
5079642f3ecSmillert 			if (rcs_yesno('y') != 'y') {
5088ac837e5Snicm 				free(pb->rcs_msg);
5099642f3ecSmillert 				pb->rcs_msg = NULL;
5109642f3ecSmillert 			}
5119642f3ecSmillert 		}
5129642f3ecSmillert 		if (pb->rcs_msg == NULL)
513913586deSxsa 			pb->rcs_msg = checkin_getlogmsg(pb->frev, pb->newrev,
514913586deSxsa 			    pb->flags);
5159642f3ecSmillert 	}
516e7f28f16Sniallo 
5178f60c266Sxsa 	if ((rcs_lock_remove(pb->file, pb->username, pb->frev) < 0) &&
5188f60c266Sxsa 	    (rcs_lock_getmode(pb->file) != RCS_LOCK_LOOSE)) {
519e7f28f16Sniallo 		if (rcs_errno != RCS_ERR_NOENT)
52082ca7eaaSxsa 			warnx("failed to remove lock");
521bbfcb536Sjoris 		else if (!(pb->flags & CO_LOCK))
52282ca7eaaSxsa 			warnx("previous revision was not locked; "
523bbfcb536Sjoris 			    "ignoring -l option");
524e7f28f16Sniallo 	}
525e7f28f16Sniallo 
5267da1ddabSxsa 	/* Current head revision gets the RCS patch as rd_text */
5275b28f14aSxsa 	if (rcs_deltatext_set(pb->file, pb->frev, pb->deltatext) == -1)
528a3660ae3Sxsa 		errx(1, "failed to set new rd_text for head rev");
529e7f28f16Sniallo 
5307da1ddabSxsa 	/* Now add our new revision */
531e7f28f16Sniallo 	if (rcs_rev_add(pb->file,
532e7f28f16Sniallo 	    (pb->newrev == NULL ? RCS_HEAD_REV : pb->newrev),
533bbfcb536Sjoris 	    pb->rcs_msg, pb->date, pb->author) != 0) {
53482ca7eaaSxsa 		warnx("failed to add new revision");
535fa741c14Sniallo 		return (-1);
536e7f28f16Sniallo 	}
537e7f28f16Sniallo 
538e7f28f16Sniallo 	/*
539e7f28f16Sniallo 	 * If we are checking in to a non-default (ie user-specified)
540e7f28f16Sniallo 	 * revision, set head to this revision.
541e7f28f16Sniallo 	 */
542223d3d94Sxsa 	if (pb->newrev != NULL) {
543223d3d94Sxsa 		if (rcs_head_set(pb->file, pb->newrev) < 0)
544a3660ae3Sxsa 			errx(1, "rcs_head_set failed");
545223d3d94Sxsa 	} else
546e7f28f16Sniallo 		pb->newrev = pb->file->rf_head;
547e7f28f16Sniallo 
5487da1ddabSxsa 	/* New head revision has to contain entire file; */
5493de4517bSniallo 	if (rcs_deltatext_set(pb->file, pb->frev, bp) == -1)
550a3660ae3Sxsa 		errx(1, "failed to set new head revision");
551e7f28f16Sniallo 
5527da1ddabSxsa 	/* Attach a symbolic name to this revision if specified. */
5537249ec9bSderaadt 	if (pb->symbol != NULL &&
5547249ec9bSderaadt 	    (checkin_attach_symbol(pb) < 0))
555fa741c14Sniallo 		return (-1);
556e7f28f16Sniallo 
5577da1ddabSxsa 	/* Set the state of this revision if specified. */
558e7f28f16Sniallo 	if (pb->state != NULL)
559e7f28f16Sniallo 		(void)rcs_state_set(pb->file, pb->newrev, pb->state);
560e7f28f16Sniallo 
561c51cb395Sniallo 	/* Maintain RCSFILE permissions */
56290df8749Sxsa 	if (fstat(workfile_fd, &st) == -1)
563a3660ae3Sxsa 		err(1, "%s", pb->filename);
564c51cb395Sniallo 
565c51cb395Sniallo 	/* Strip all the write bits */
5669642f3ecSmillert 	pb->file->rf_mode = st.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH);
567c51cb395Sniallo 
568e13e08a4Sjoris 	(void)close(workfile_fd);
569e7f28f16Sniallo 	(void)unlink(pb->filename);
570e7f28f16Sniallo 
571c51cb395Sniallo 	/* Write out RCSFILE before calling checkout_rev() */
572c51cb395Sniallo 	rcs_write(pb->file);
573c51cb395Sniallo 
5747da1ddabSxsa 	/* Do checkout if -u or -l are specified. */
5757249ec9bSderaadt 	if (((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK)) &&
5767249ec9bSderaadt 	    !(pb->flags & CI_DEFAULT))
577e7f28f16Sniallo 		checkout_rev(pb->file, pb->newrev, pb->filename, pb->flags,
57843f1a30dSjoris 		    pb->username, pb->author, NULL, NULL);
5791989ff02Smillert 
5801989ff02Smillert 	if ((pb->flags & INTERACTIVE) && (pb->rcs_msg[0] == '\0')) {
5818ac837e5Snicm 		free(pb->rcs_msg);	/* free empty log message */
5821989ff02Smillert 		pb->rcs_msg = NULL;
5831989ff02Smillert 	}
584a48b1fdbSniallo 
585fa741c14Sniallo 	return (0);
586e7f28f16Sniallo }
587e7f28f16Sniallo 
588e7f28f16Sniallo /*
589457dba6dSniallo  * checkin_init()
590457dba6dSniallo  *
591457dba6dSniallo  * Does an initial check in, just enough to create the new ,v file
592e7f28f16Sniallo  * On success, return 0. On error return -1.
593457dba6dSniallo  */
594e7f28f16Sniallo static int
checkin_init(struct checkin_params * pb)595457dba6dSniallo checkin_init(struct checkin_params *pb)
596457dba6dSniallo {
597d491b5edSray 	BUF *bp;
598950e09dcSxsa 	char numb[RCS_REV_BUFSZ];
599b12ab1d2Sniallo 	int fetchlog = 0;
600c51cb395Sniallo 	struct stat st;
601457dba6dSniallo 
602b12ab1d2Sniallo 	/* If this is a zero-ending RCSNUM eg 4.0, increment it (eg to 4.1) */
6037249ec9bSderaadt 	if (pb->newrev != NULL && RCSNUM_ZERO_ENDING(pb->newrev)) {
604b12ab1d2Sniallo 		pb->frev = rcsnum_alloc();
605b12ab1d2Sniallo 		rcsnum_cpy(pb->newrev, pb->frev, 0);
606b12ab1d2Sniallo 		pb->newrev = rcsnum_inc(pb->newrev);
607b12ab1d2Sniallo 		fetchlog = 1;
608b12ab1d2Sniallo 	}
609b12ab1d2Sniallo 
6107da1ddabSxsa 	/* Load file contents */
6110ee14128Sray 	if ((bp = buf_load(pb->filename)) == NULL)
612fa741c14Sniallo 		return (-1);
613457dba6dSniallo 
614303c871eSniallo 	/* Get default values from working copy if -k specified */
615303c871eSniallo 	if (pb->flags & CI_KEYWORDSCAN)
61664d00fd2Sray 		checkin_keywordscan(bp, &pb->newrev,
6173de4517bSniallo 		    &pb->date, &pb->state, &pb->author);
618303c871eSniallo 
6194b1a048aSniallo 	if (pb->flags & CI_SKIPDESC)
620848faecaSjoris 		goto skipdesc;
621848faecaSjoris 
6227da1ddabSxsa 	/* Get description from user */
6234b1a1f86Sray 	if (pb->description == NULL &&
62400f4e67cSmillert 	    rcs_set_description(pb->file, NULL, pb->flags) == -1) {
625d99706fcSjoris 		warn("%s", pb->filename);
626fa741c14Sniallo 		return (-1);
6274b1a1f86Sray 	}
628457dba6dSniallo 
629848faecaSjoris skipdesc:
630848faecaSjoris 
6313aa0b88bSniallo 	/*
632d9622f60Sray 	 * If the user had specified a zero-ending revision number e.g. 4.0
633b12ab1d2Sniallo 	 * emulate odd GNU behaviour and fetch log message.
634b12ab1d2Sniallo 	 */
635b12ab1d2Sniallo 	if (fetchlog == 1) {
636913586deSxsa 		pb->rcs_msg = checkin_getlogmsg(pb->frev, pb->newrev,
637913586deSxsa 		    pb->flags);
638b12ab1d2Sniallo 		rcsnum_free(pb->frev);
639b12ab1d2Sniallo 	}
640d771ffd9Sjoris 
641b12ab1d2Sniallo 	/*
6423aa0b88bSniallo 	 * Set the date of the revision to be the last modification
6433aa0b88bSniallo 	 * time of the working file if -d has no argument.
6443aa0b88bSniallo 	 */
645e13e08a4Sjoris 	if (pb->date == DATE_MTIME)
646e13e08a4Sjoris 		checkin_mtimedate(pb);
6473aa0b88bSniallo 
6487da1ddabSxsa 	/* Now add our new revision */
64968b0b6d6Sniallo 	if (rcs_rev_add(pb->file,
65068b0b6d6Sniallo 	    (pb->newrev == NULL ? RCS_HEAD_REV : pb->newrev),
651d5c46754Sderaadt 	    (pb->rcs_msg == NULL ? "Initial revision" : pb->rcs_msg),
652cecfa317Sniallo 	    pb->date, pb->author) != 0) {
65382ca7eaaSxsa 		warnx("failed to add new revision");
654fa741c14Sniallo 		return (-1);
655457dba6dSniallo 	}
656d771ffd9Sjoris 
657e7f28f16Sniallo 	/*
658e7f28f16Sniallo 	 * If we are checking in to a non-default (ie user-specified)
659e7f28f16Sniallo 	 * revision, set head to this revision.
660e7f28f16Sniallo 	 */
661223d3d94Sxsa 	if (pb->newrev != NULL) {
662223d3d94Sxsa 		if (rcs_head_set(pb->file, pb->newrev) < 0)
663a3660ae3Sxsa 			errx(1, "rcs_head_set failed");
664223d3d94Sxsa 	} else
665e7f28f16Sniallo 		pb->newrev = pb->file->rf_head;
666e7f28f16Sniallo 
6677da1ddabSxsa 	/* New head revision has to contain entire file; */
6683de4517bSniallo 	if (rcs_deltatext_set(pb->file, pb->file->rf_head, bp) == -1) {
66982ca7eaaSxsa 		warnx("failed to set new head revision");
670fa741c14Sniallo 		return (-1);
671e7f28f16Sniallo 	}
672d771ffd9Sjoris 
6737da1ddabSxsa 	/* Attach a symbolic name to this revision if specified. */
674d9622f60Sray 	if (pb->symbol != NULL && checkin_attach_symbol(pb) < 0)
675fa741c14Sniallo 		return (-1);
676e7f28f16Sniallo 
6777da1ddabSxsa 	/* Set the state of this revision if specified. */
67895498594Sniallo 	if (pb->state != NULL)
67995498594Sniallo 		(void)rcs_state_set(pb->file, pb->newrev, pb->state);
68095498594Sniallo 
681c51cb395Sniallo 	/* Inherit RCSFILE permissions from file being checked in */
68290df8749Sxsa 	if (fstat(workfile_fd, &st) == -1)
683a3660ae3Sxsa 		err(1, "%s", pb->filename);
684e13e08a4Sjoris 
685c51cb395Sniallo 	/* Strip all the write bits */
6869642f3ecSmillert 	pb->file->rf_mode = st.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH);
687c51cb395Sniallo 
688e13e08a4Sjoris 	(void)close(workfile_fd);
68995498594Sniallo 	(void)unlink(pb->filename);
69095498594Sniallo 
691c51cb395Sniallo 	/* Write out RCSFILE before calling checkout_rev() */
692c51cb395Sniallo 	rcs_write(pb->file);
693c51cb395Sniallo 
6947da1ddabSxsa 	/* Do checkout if -u or -l are specified. */
6957249ec9bSderaadt 	if (((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK)) &&
6967249ec9bSderaadt 	    !(pb->flags & CI_DEFAULT)) {
69795498594Sniallo 		checkout_rev(pb->file, pb->newrev, pb->filename, pb->flags,
69843f1a30dSjoris 		    pb->username, pb->author, NULL, NULL);
699c51cb395Sniallo 	}
700c51cb395Sniallo 
701913586deSxsa 	if (!(pb->flags & QUIET)) {
702d8cd5333Sxsa 		fprintf(stderr, "initial revision: %s\n",
703cfbe36a0Sxsa 		    rcsnum_tostr(pb->newrev, numb, sizeof(numb)));
704d8cd5333Sxsa 	}
705cfbe36a0Sxsa 
706e7f28f16Sniallo 	return (0);
707457dba6dSniallo }
708457dba6dSniallo 
709446c1022Sniallo /*
710446c1022Sniallo  * checkin_attach_symbol()
711446c1022Sniallo  *
712446c1022Sniallo  * Attempt to attach the specified symbol to the revision.
713446c1022Sniallo  * On success, return 0. On error return -1.
714446c1022Sniallo  */
715457dba6dSniallo static int
checkin_attach_symbol(struct checkin_params * pb)716457dba6dSniallo checkin_attach_symbol(struct checkin_params *pb)
717457dba6dSniallo {
718950e09dcSxsa 	char rbuf[RCS_REV_BUFSZ];
719457dba6dSniallo 	int ret;
720913586deSxsa 	if (!(pb->flags & QUIET))
721457dba6dSniallo 		printf("symbol: %s\n", pb->symbol);
722fe0541e0Sxsa 	if (pb->flags & CI_SYMFORCE) {
723fe0541e0Sxsa 		if (rcs_sym_remove(pb->file, pb->symbol) < 0) {
724fe0541e0Sxsa 			if (rcs_errno != RCS_ERR_NOENT) {
72582ca7eaaSxsa 				warnx("problem removing symbol: %s",
72682ca7eaaSxsa 				    pb->symbol);
727fe0541e0Sxsa 				return (-1);
728fe0541e0Sxsa 			}
729fe0541e0Sxsa 		}
730fe0541e0Sxsa 	}
73154baf1aaSjsg 	if ((ret = rcs_sym_add(pb->file, pb->symbol, pb->newrev)) == -1 &&
7327249ec9bSderaadt 	    (rcs_errno == RCS_ERR_DUPENT)) {
733457dba6dSniallo 		rcsnum_tostr(rcs_sym_getrev(pb->file, pb->symbol),
734457dba6dSniallo 		    rbuf, sizeof(rbuf));
73582ca7eaaSxsa 		warnx("symbolic name %s already bound to %s", pb->symbol, rbuf);
736457dba6dSniallo 		return (-1);
737457dba6dSniallo 	} else if (ret == -1) {
73882ca7eaaSxsa 		warnx("problem adding symbol: %s", pb->symbol);
739457dba6dSniallo 		return (-1);
740457dba6dSniallo 	}
741457dba6dSniallo 	return (0);
742457dba6dSniallo }
743457dba6dSniallo 
744446c1022Sniallo /*
745446c1022Sniallo  * checkin_revert()
746446c1022Sniallo  *
747446c1022Sniallo  * If there are no differences between the working file and the latest revision
748446c1022Sniallo  * and the -f flag is not specified, simply revert to the latest version and
749446c1022Sniallo  * warn the user.
750446c1022Sniallo  *
751446c1022Sniallo  */
752fa741c14Sniallo static int
checkin_revert(struct checkin_params * pb)753457dba6dSniallo checkin_revert(struct checkin_params *pb)
754457dba6dSniallo {
755950e09dcSxsa 	char rbuf[RCS_REV_BUFSZ];
756457dba6dSniallo 
757457dba6dSniallo 	rcsnum_tostr(pb->frev, rbuf, sizeof(rbuf));
758df0c9c73Sxsa 
759df0c9c73Sxsa 	if (!(pb->flags & QUIET))
760df0c9c73Sxsa 		(void)fprintf(stderr, "file is unchanged; reverting "
761df0c9c73Sxsa 		    "to previous revision %s\n", rbuf);
762df0c9c73Sxsa 
763fa741c14Sniallo 	/* Attach a symbolic name to this revision if specified. */
764fa741c14Sniallo 	if (pb->symbol != NULL) {
765fa741c14Sniallo 		if (checkin_checklock(pb) == -1)
766fa741c14Sniallo 			return (-1);
767fa741c14Sniallo 
768fa741c14Sniallo 		pb->newrev = pb->frev;
769fa741c14Sniallo 		if (checkin_attach_symbol(pb) == -1)
770fa741c14Sniallo 			return (-1);
771fa741c14Sniallo 	}
772fa741c14Sniallo 
7733491417cSniallo 	pb->flags |= CO_REVERT;
774e13e08a4Sjoris 	(void)close(workfile_fd);
775457dba6dSniallo 	(void)unlink(pb->filename);
776fa741c14Sniallo 
777fa741c14Sniallo 	/* If needed, write out RCSFILE before calling checkout_rev() */
778fa741c14Sniallo 	if (pb->symbol != NULL)
779fa741c14Sniallo 		rcs_write(pb->file);
780fa741c14Sniallo 
781457dba6dSniallo 	if ((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK))
782457dba6dSniallo 		checkout_rev(pb->file, pb->frev, pb->filename,
78343f1a30dSjoris 		    pb->flags, pb->username, pb->author, NULL, NULL);
784fa741c14Sniallo 
785fa741c14Sniallo 	return (0);
786457dba6dSniallo }
787457dba6dSniallo 
788446c1022Sniallo /*
789446c1022Sniallo  * checkin_checklock()
790446c1022Sniallo  *
791446c1022Sniallo  * Check for the existence of a lock on the file.  If there are no locks, or it
792446c1022Sniallo  * is not locked by the correct user, return -1.  Otherwise, return 0.
793446c1022Sniallo  */
794457dba6dSniallo static int
checkin_checklock(struct checkin_params * pb)795457dba6dSniallo checkin_checklock(struct checkin_params *pb)
796457dba6dSniallo {
797457dba6dSniallo 	struct rcs_lock *lkp;
798457dba6dSniallo 
7998f60c266Sxsa 	if (rcs_lock_getmode(pb->file) == RCS_LOCK_LOOSE)
8008f60c266Sxsa 		return (0);
8018f60c266Sxsa 
802457dba6dSniallo 	TAILQ_FOREACH(lkp, &(pb->file->rf_locks), rl_list) {
8037249ec9bSderaadt 		if (!strcmp(lkp->rl_name, pb->username) &&
8047249ec9bSderaadt 		    !rcsnum_cmp(lkp->rl_num, pb->frev, 0))
805457dba6dSniallo 			return (0);
806457dba6dSniallo 	}
807457dba6dSniallo 
80882ca7eaaSxsa 	warnx("%s: no lock set by %s", pb->file->rf_path, pb->username);
809457dba6dSniallo 	return (-1);
810457dba6dSniallo }
81144a81e63Sniallo 
81244a81e63Sniallo /*
8135da2f631Sniallo  * checkin_mtimedate()
81444a81e63Sniallo  *
81544a81e63Sniallo  * Set the date of the revision to be the last modification
8165da2f631Sniallo  * time of the working file.
81744a81e63Sniallo  */
818e13e08a4Sjoris static void
checkin_mtimedate(struct checkin_params * pb)8195da2f631Sniallo checkin_mtimedate(struct checkin_params *pb)
82044a81e63Sniallo {
82144a81e63Sniallo 	struct stat sb;
822e13e08a4Sjoris 
82390df8749Sxsa 	if (fstat(workfile_fd, &sb) == -1)
824a3660ae3Sxsa 		err(1, "%s", pb->filename);
825e13e08a4Sjoris 
826*03d5d688Sguenther 	pb->date = sb.st_mtime;
82744a81e63Sniallo }
828e7f28f16Sniallo 
829e7f28f16Sniallo /*
830303c871eSniallo  * checkin_keywordscan()
831303c871eSniallo  *
832303c871eSniallo  * Searches working file for keyword values to determine its revision
833303c871eSniallo  * number, creation date and author, and uses these values instead of
834303c871eSniallo  * calculating them locally.
835303c871eSniallo  *
836303c871eSniallo  * Params: The data buffer to scan and pointers to pointers of variables in
837303c871eSniallo  * which to store the outputs.
838303c871eSniallo  *
839303c871eSniallo  * On success, return 0. On error return -1.
840303c871eSniallo  */
841303c871eSniallo static int
checkin_keywordscan(BUF * data,RCSNUM ** rev,time_t * date,char ** author,char ** state)84264d00fd2Sray checkin_keywordscan(BUF *data, RCSNUM **rev, time_t *date, char **author,
843303c871eSniallo     char **state)
844303c871eSniallo {
84564d00fd2Sray 	BUF *buf;
84664d00fd2Sray 	size_t left;
847af4d9f25Sray 	u_int j;
84864d00fd2Sray 	char *kwstr;
84964d00fd2Sray 	unsigned char *c, *end, *start;
850303c871eSniallo 
8517bb3ddb0Sray 	end = buf_get(data) + buf_len(data) - 1;
852af4d9f25Sray 	kwstr = NULL;
853303c871eSniallo 
8547bb3ddb0Sray 	left = buf_len(data);
8557bb3ddb0Sray 	for (c = buf_get(data);
85664d00fd2Sray 	    c <= end && (c = memchr(c, '$', left)) != NULL;
85764d00fd2Sray 	    left = end - c + 1) {
858af4d9f25Sray 		size_t len;
859303c871eSniallo 
860303c871eSniallo 		start = c;
8615920b252Sderaadt 		c++;
862af4d9f25Sray 		if (!isalpha(*c))
863303c871eSniallo 			continue;
864af4d9f25Sray 
865303c871eSniallo 		/* look for any matching keywords */
866303c871eSniallo 		for (j = 0; j < 10; j++) {
867af4d9f25Sray 			len = strlen(rcs_expkw[j].kw_str);
86864d00fd2Sray 			if (left < len)
86964d00fd2Sray 				continue;
87064d00fd2Sray 			if (memcmp(c, rcs_expkw[j].kw_str, len) != 0) {
871303c871eSniallo 				kwstr = rcs_expkw[j].kw_str;
872303c871eSniallo 				break;
873303c871eSniallo 			}
874303c871eSniallo 		}
875303c871eSniallo 
876303c871eSniallo 		/* unknown keyword, continue looking */
877af4d9f25Sray 		if (kwstr == NULL)
878303c871eSniallo 			continue;
879303c871eSniallo 
880af4d9f25Sray 		c += len;
88164d00fd2Sray 		if (c > end) {
88264d00fd2Sray 			kwstr = NULL;
88364d00fd2Sray 			break;
88464d00fd2Sray 		}
88564d00fd2Sray 		if (*c != ':') {
88664d00fd2Sray 			kwstr = NULL;
887303c871eSniallo 			continue;
88864d00fd2Sray 		}
889303c871eSniallo 
890af4d9f25Sray 		/* Find end of line or end of keyword. */
89164d00fd2Sray 		while (++c <= end) {
89264d00fd2Sray 			if (*c == '\n') {
89364d00fd2Sray 				/* Skip newline since it is definitely not `$'. */
89464d00fd2Sray 				++c;
89564d00fd2Sray 				goto loopend;
89664d00fd2Sray 			}
89764d00fd2Sray 			if (*c == '$')
89864d00fd2Sray 				break;
89964d00fd2Sray 		}
900af4d9f25Sray 
90164d00fd2Sray 		len = c - start + 1;
9020ee14128Sray 		buf = buf_alloc(len + 1);
9037bb3ddb0Sray 		buf_append(buf, start, len);
90464d00fd2Sray 
90564d00fd2Sray 		/* XXX - Not binary safe. */
9067bb3ddb0Sray 		buf_putc(buf, '\0');
9077bb3ddb0Sray 		checkin_parsekeyword(buf_get(buf), rev, date, author, state);
9085bf94677Sray 		buf_free(buf);
909e2ef0f67Sderaadt loopend:;
910303c871eSniallo 	}
911af4d9f25Sray 	if (kwstr == NULL)
912303c871eSniallo 		return (-1);
913303c871eSniallo 	else
914303c871eSniallo 		return (0);
915303c871eSniallo }
916303c871eSniallo 
917303c871eSniallo /*
918303c871eSniallo  * checkin_keywordtype()
919303c871eSniallo  *
920303c871eSniallo  * Given an RCS keyword string, determine what type of string it is.
921303c871eSniallo  * This enables us to know what data should be in it.
922303c871eSniallo  *
923303c871eSniallo  * Returns type on success, or -1 on failure.
924303c871eSniallo  */
925303c871eSniallo static int
checkin_keywordtype(char * keystring)926303c871eSniallo checkin_keywordtype(char *keystring)
927303c871eSniallo {
928303c871eSniallo 	char *p;
929303c871eSniallo 
930303c871eSniallo 	p = keystring;
9315920b252Sderaadt 	p++;
9323af23efeSguenther 	if (strncmp(p, KW_ID, strlen(KW_ID)) == 0 ||
9333af23efeSguenther 	    strncmp(p, KW_OPENBSD, strlen(KW_OPENBSD)) == 0)
934303c871eSniallo 		return (KW_TYPE_ID);
935303c871eSniallo 	else if (strncmp(p, KW_AUTHOR, strlen(KW_AUTHOR)) == 0)
936303c871eSniallo 		return (KW_TYPE_AUTHOR);
937303c871eSniallo 	else if (strncmp(p, KW_DATE, strlen(KW_DATE)) == 0)
938303c871eSniallo 		return (KW_TYPE_DATE);
939303c871eSniallo 	else if (strncmp(p, KW_STATE, strlen(KW_STATE)) == 0)
940303c871eSniallo 		return (KW_TYPE_STATE);
941303c871eSniallo 	else if (strncmp(p, KW_REVISION, strlen(KW_REVISION)) == 0)
942303c871eSniallo 		return (KW_TYPE_REVISION);
943303c871eSniallo 	else
944303c871eSniallo 		return (-1);
945303c871eSniallo }
946303c871eSniallo 
947303c871eSniallo /*
948303c871eSniallo  * checkin_parsekeyword()
949303c871eSniallo  *
950303c871eSniallo  * Do the actual parsing of an RCS keyword string, setting the values passed
951303c871eSniallo  * to the function to whatever is found.
952303c871eSniallo  *
95364d00fd2Sray  * XXX - Don't error out on malformed keywords.
954303c871eSniallo  */
955303c871eSniallo static void
checkin_parsekeyword(char * keystring,RCSNUM ** rev,time_t * date,char ** author,char ** state)956303c871eSniallo checkin_parsekeyword(char *keystring, RCSNUM **rev, time_t *date,
957303c871eSniallo     char **author, char **state)
958303c871eSniallo {
95964d00fd2Sray 	char *tokens[KW_NUMTOKS_MAX], *p, *datestring;
960ba7cd5ffSniallo 	int i = 0;
961d5c46754Sderaadt 
96264d00fd2Sray 	for ((p = strtok(keystring, " ")); p; (p = strtok(NULL, " "))) {
96364d00fd2Sray 		if (i < KW_NUMTOKS_MAX - 1)
96464d00fd2Sray 			tokens[i++] = p;
96564d00fd2Sray 		else
96664d00fd2Sray 			break;
96764d00fd2Sray 	}
96864d00fd2Sray 
969303c871eSniallo 	/* Parse data out of the expanded keyword */
970303c871eSniallo 	switch (checkin_keywordtype(keystring)) {
971303c871eSniallo 	case KW_TYPE_ID:
97264d00fd2Sray 		if (i < 3)
97364d00fd2Sray 			break;
974303c871eSniallo 		/* only parse revision if one is not already set */
975303c871eSniallo 		if (*rev == NULL) {
976303c871eSniallo 			if ((*rev = rcsnum_parse(tokens[2])) == NULL)
977a3660ae3Sxsa 				errx(1, "could not parse rcsnum");
978303c871eSniallo 		}
97964d00fd2Sray 
98064d00fd2Sray 		if (i < 5)
98164d00fd2Sray 			break;
982679c1b1aSray 		(void)xasprintf(&datestring, "%s %s", tokens[3], tokens[4]);
983092db204Sray 		if ((*date = date_parse(datestring)) == -1)
984a3660ae3Sxsa 			errx(1, "could not parse date");
9858ac837e5Snicm 		free(datestring);
98664d00fd2Sray 
98764d00fd2Sray 		if (i < 6)
98864d00fd2Sray 			break;
9898ac837e5Snicm 		free(*author);
99064d00fd2Sray 		*author = xstrdup(tokens[5]);
99164d00fd2Sray 
99264d00fd2Sray 		if (i < 7)
99364d00fd2Sray 			break;
9948ac837e5Snicm 		free(*state);
99564d00fd2Sray 		*state = xstrdup(tokens[6]);
996303c871eSniallo 		break;
997303c871eSniallo 	case KW_TYPE_AUTHOR:
99864d00fd2Sray 		if (i < 2)
99964d00fd2Sray 			break;
10008ac837e5Snicm 		free(*author);
10013b4c5c25Sray 		*author = xstrdup(tokens[1]);
1002303c871eSniallo 		break;
1003303c871eSniallo 	case KW_TYPE_DATE:
100464d00fd2Sray 		if (i < 3)
100564d00fd2Sray 			break;
1006679c1b1aSray 		(void)xasprintf(&datestring, "%s %s", tokens[1], tokens[2]);
1007092db204Sray 		if ((*date = date_parse(datestring)) == -1)
1008a3660ae3Sxsa 			errx(1, "could not parse date");
10098ac837e5Snicm 		free(datestring);
1010303c871eSniallo 		break;
1011303c871eSniallo 	case KW_TYPE_STATE:
101264d00fd2Sray 		if (i < 2)
101364d00fd2Sray 			break;
10148ac837e5Snicm 		free(*state);
10153b4c5c25Sray 		*state = xstrdup(tokens[1]);
1016303c871eSniallo 		break;
1017303c871eSniallo 	case KW_TYPE_REVISION:
101864d00fd2Sray 		if (i < 2)
101964d00fd2Sray 			break;
1020303c871eSniallo 		/* only parse revision if one is not already set */
1021303c871eSniallo 		if (*rev != NULL)
1022303c871eSniallo 			break;
1023303c871eSniallo 		if ((*rev = rcsnum_parse(tokens[1])) == NULL)
1024a3660ae3Sxsa 			errx(1, "could not parse rcsnum");
1025303c871eSniallo 		break;
1026303c871eSniallo 	}
1027303c871eSniallo }
1028