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