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