1 /* $OpenBSD: ci.c,v 1.224 2016/07/04 01:39:12 millert 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 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 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 * 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 * 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 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 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 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 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 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 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_mtimespec.tv_sec; 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 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 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 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