1 /* $OpenBSD: ci.c,v 1.205 2009/02/25 23:16:20 ray 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_AUTHOR "Author" 46 #define KW_DATE "Date" 47 #define KW_STATE "State" 48 #define KW_REVISION "Revision" 49 50 #define KW_TYPE_ID 1 51 #define KW_TYPE_AUTHOR 2 52 #define KW_TYPE_DATE 3 53 #define KW_TYPE_STATE 4 54 #define KW_TYPE_REVISION 5 55 56 #define KW_NUMTOKS_ID 10 57 #define KW_NUMTOKS_AUTHOR 3 58 #define KW_NUMTOKS_DATE 4 59 #define KW_NUMTOKS_STATE 3 60 #define KW_NUMTOKS_REVISION 3 61 62 /* Maximum number of tokens in a keyword. */ 63 #define KW_NUMTOKS_MAX 10 64 65 #define RCSNUM_ZERO_ENDING(x) (x->rn_id[x->rn_len - 1] == 0) 66 67 extern struct rcs_kw rcs_expkw[]; 68 69 static int workfile_fd; 70 71 struct checkin_params { 72 int flags, openflags; 73 mode_t fmode; 74 time_t date; 75 RCSFILE *file; 76 RCSNUM *frev, *newrev; 77 const char *description, *symbol; 78 char fpath[MAXPATHLEN], *rcs_msg, *username, *filename; 79 char *author, *state; 80 BUF *deltatext; 81 }; 82 83 static int checkin_attach_symbol(struct checkin_params *); 84 static int checkin_checklock(struct checkin_params *); 85 static BUF *checkin_diff_file(struct checkin_params *); 86 static char *checkin_getlogmsg(RCSNUM *, RCSNUM *, int); 87 static int checkin_init(struct checkin_params *); 88 static int checkin_keywordscan(BUF *, RCSNUM **, time_t *, char **, 89 char **); 90 static int checkin_keywordtype(char *); 91 static void checkin_mtimedate(struct checkin_params *); 92 static void checkin_parsekeyword(char *, RCSNUM **, time_t *, char **, 93 char **); 94 static int checkin_update(struct checkin_params *); 95 static int checkin_revert(struct checkin_params *); 96 97 void 98 checkin_usage(void) 99 { 100 fprintf(stderr, 101 "usage: ci [-qV] [-d[date]] [-f[rev]] [-I[rev]] [-i[rev]]\n" 102 " [-j[rev]] [-k[rev]] [-l[rev]] [-M[rev]] [-mmsg]\n" 103 " [-Nsymbol] [-nsymbol] [-r[rev]] [-sstate] [-tstr]\n" 104 " [-u[rev]] [-wusername] [-xsuffixes] [-ztz] file ...\n"); 105 } 106 107 /* 108 * checkin_main() 109 * 110 * Handler for the `ci' program. 111 * Returns 0 on success, or >0 on error. 112 */ 113 int 114 checkin_main(int argc, char **argv) 115 { 116 int fd; 117 int i, ch, status; 118 int base_flags, base_openflags; 119 char *rev_str; 120 struct checkin_params pb; 121 122 pb.date = DATE_NOW; 123 pb.file = NULL; 124 pb.rcs_msg = pb.username = pb.author = pb.state = NULL; 125 pb.description = pb.symbol = NULL; 126 pb.deltatext = NULL; 127 pb.newrev = NULL; 128 pb.fmode = S_IRUSR|S_IRGRP|S_IROTH; 129 status = 0; 130 base_flags = INTERACTIVE; 131 base_openflags = RCS_RDWR|RCS_CREATE|RCS_PARSE_FULLY; 132 rev_str = NULL; 133 134 while ((ch = rcs_getopt(argc, argv, CI_OPTSTRING)) != -1) { 135 switch (ch) { 136 case 'd': 137 if (rcs_optarg == NULL) 138 pb.date = DATE_MTIME; 139 else if ((pb.date = rcs_date_parse(rcs_optarg)) <= 0) 140 errx(1, "invalid date"); 141 break; 142 case 'f': 143 rcs_setrevstr(&rev_str, rcs_optarg); 144 base_flags |= FORCE; 145 break; 146 case 'I': 147 rcs_setrevstr(&rev_str, rcs_optarg); 148 base_flags |= INTERACTIVE; 149 break; 150 case 'i': 151 rcs_setrevstr(&rev_str, rcs_optarg); 152 base_openflags |= RCS_CREATE; 153 base_flags |= CI_INIT; 154 break; 155 case 'j': 156 rcs_setrevstr(&rev_str, rcs_optarg); 157 base_openflags &= ~RCS_CREATE; 158 base_flags &= ~CI_INIT; 159 break; 160 case 'k': 161 rcs_setrevstr(&rev_str, rcs_optarg); 162 base_flags |= CI_KEYWORDSCAN; 163 break; 164 case 'l': 165 rcs_setrevstr(&rev_str, rcs_optarg); 166 base_flags |= CO_LOCK; 167 break; 168 case 'M': 169 rcs_setrevstr(&rev_str, rcs_optarg); 170 base_flags |= CO_REVDATE; 171 break; 172 case 'm': 173 pb.rcs_msg = rcs_optarg; 174 if (pb.rcs_msg == NULL) 175 errx(1, "missing message for -m option"); 176 base_flags &= ~INTERACTIVE; 177 break; 178 case 'N': 179 base_flags |= CI_SYMFORCE; 180 /* FALLTHROUGH */ 181 case 'n': 182 pb.symbol = rcs_optarg; 183 if (rcs_sym_check(pb.symbol) != 1) 184 errx(1, "invalid symbol `%s'", pb.symbol); 185 break; 186 case 'q': 187 base_flags |= QUIET; 188 break; 189 case 'r': 190 rcs_setrevstr(&rev_str, rcs_optarg); 191 base_flags |= CI_DEFAULT; 192 break; 193 case 's': 194 pb.state = rcs_optarg; 195 if (rcs_state_check(pb.state) < 0) 196 errx(1, "invalid state `%s'", pb.state); 197 break; 198 case 'T': 199 base_flags |= PRESERVETIME; 200 break; 201 case 't': 202 /* Ignore bare -t; kept for backwards compatibility. */ 203 if (rcs_optarg == NULL) 204 break; 205 pb.description = rcs_optarg; 206 base_flags |= DESCRIPTION; 207 break; 208 case 'u': 209 rcs_setrevstr(&rev_str, rcs_optarg); 210 base_flags |= CO_UNLOCK; 211 break; 212 case 'V': 213 printf("%s\n", rcs_version); 214 exit(0); 215 case 'w': 216 if (pb.author != NULL) 217 xfree(pb.author); 218 pb.author = xstrdup(rcs_optarg); 219 break; 220 case 'x': 221 /* Use blank extension if none given. */ 222 rcs_suffixes = rcs_optarg ? rcs_optarg : ""; 223 break; 224 case 'z': 225 timezone_flag = rcs_optarg; 226 break; 227 default: 228 (usage)(); 229 exit(1); 230 } 231 } 232 233 argc -= rcs_optind; 234 argv += rcs_optind; 235 236 if (argc == 0) { 237 warnx("no input file"); 238 (usage)(); 239 exit(1); 240 } 241 242 if ((pb.username = getlogin()) == NULL) 243 err(1, "getlogin"); 244 245 /* If -x flag was not given, use default. */ 246 if (rcs_suffixes == NULL) 247 rcs_suffixes = RCS_DEFAULT_SUFFIX; 248 249 for (i = 0; i < argc; i++) { 250 /* 251 * The pb.flags and pb.openflags may change during 252 * loop iteration so restore them for each file. 253 */ 254 pb.flags = base_flags; 255 pb.openflags = base_openflags; 256 257 pb.filename = argv[i]; 258 rcs_strip_suffix(pb.filename); 259 260 if ((workfile_fd = open(pb.filename, O_RDONLY)) == -1) 261 err(1, "%s", pb.filename); 262 263 /* Find RCS file path. */ 264 fd = rcs_choosefile(pb.filename, pb.fpath, sizeof(pb.fpath)); 265 266 if (fd < 0) { 267 if (pb.openflags & RCS_CREATE) 268 pb.flags |= NEWFILE; 269 else { 270 /* XXX - Check if errno == ENOENT. */ 271 warnx("No existing RCS file"); 272 status = 1; 273 (void)close(workfile_fd); 274 continue; 275 } 276 } else { 277 if (pb.flags & CI_INIT) { 278 warnx("%s already exists", pb.fpath); 279 status = 1; 280 (void)close(fd); 281 (void)close(workfile_fd); 282 continue; 283 } 284 pb.openflags &= ~RCS_CREATE; 285 } 286 287 pb.file = rcs_open(pb.fpath, fd, pb.openflags, pb.fmode); 288 if (pb.file == NULL) 289 errx(1, "failed to open rcsfile `%s'", pb.fpath); 290 291 if ((pb.flags & DESCRIPTION) && 292 rcs_set_description(pb.file, pb.description) == -1) 293 err(1, "%s", pb.filename); 294 295 if (!(pb.flags & QUIET)) 296 (void)fprintf(stderr, 297 "%s <-- %s\n", pb.fpath, pb.filename); 298 299 /* XXX - Should we rcsnum_free(pb.newrev)? */ 300 if (rev_str != NULL) 301 if ((pb.newrev = rcs_getrevnum(rev_str, pb.file)) == 302 NULL) 303 errx(1, "invalid revision: %s", rev_str); 304 305 if (!(pb.flags & NEWFILE)) 306 pb.flags |= CI_SKIPDESC; 307 308 /* XXX - support for committing to a file without revisions */ 309 if (pb.file->rf_ndelta == 0) { 310 pb.flags |= NEWFILE; 311 pb.file->rf_flags |= RCS_CREATE; 312 } 313 314 /* 315 * workfile_fd will be closed in checkin_init or 316 * checkin_update 317 */ 318 if (pb.flags & NEWFILE) { 319 if (checkin_init(&pb) == -1) 320 status = 1; 321 } else { 322 if (checkin_update(&pb) == -1) 323 status = 1; 324 } 325 326 rcs_close(pb.file); 327 pb.newrev = NULL; 328 } 329 330 if (!(base_flags & QUIET) && status == 0) 331 (void)fprintf(stderr, "done\n"); 332 333 return (status); 334 } 335 336 /* 337 * checkin_diff_file() 338 * 339 * Generate the diff between the working file and a revision. 340 * Returns pointer to a BUF on success, NULL on failure. 341 */ 342 static BUF * 343 checkin_diff_file(struct checkin_params *pb) 344 { 345 char *path1, *path2; 346 BUF *b1, *b2, *b3; 347 char rbuf[RCS_REV_BUFSZ]; 348 349 b1 = b2 = b3 = NULL; 350 path1 = path2 = NULL; 351 rcsnum_tostr(pb->frev, rbuf, sizeof(rbuf)); 352 353 if ((b1 = rcs_buf_load(pb->filename, BUF_AUTOEXT)) == NULL) { 354 warnx("failed to load file: `%s'", pb->filename); 355 goto out; 356 } 357 358 if ((b2 = rcs_getrev(pb->file, pb->frev)) == NULL) { 359 warnx("failed to load revision"); 360 goto out; 361 } 362 363 if ((b3 = rcs_buf_alloc(128, BUF_AUTOEXT)) == NULL) { 364 warnx("failed to allocated buffer for diff"); 365 goto out; 366 } 367 368 (void)xasprintf(&path1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir); 369 rcs_buf_write_stmp(b1, path1); 370 371 rcs_buf_free(b1); 372 b1 = NULL; 373 374 (void)xasprintf(&path2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir); 375 rcs_buf_write_stmp(b2, path2); 376 377 rcs_buf_free(b2); 378 b2 = NULL; 379 380 diff_format = D_RCSDIFF; 381 if (diffreg(path1, path2, b3, D_FORCEASCII) == D_ERROR) 382 goto out; 383 384 return (b3); 385 out: 386 if (b1 != NULL) 387 rcs_buf_free(b1); 388 if (b2 != NULL) 389 rcs_buf_free(b2); 390 if (b3 != NULL) 391 rcs_buf_free(b3); 392 if (path1 != NULL) 393 xfree(path1); 394 if (path2 != NULL) 395 xfree(path2); 396 397 return (NULL); 398 } 399 400 /* 401 * checkin_getlogmsg() 402 * 403 * Get log message from user interactively. 404 * Returns pointer to a char array on success, NULL on failure. 405 */ 406 static char * 407 checkin_getlogmsg(RCSNUM *rev, RCSNUM *rev2, int flags) 408 { 409 char *rcs_msg, nrev[RCS_REV_BUFSZ], prev[RCS_REV_BUFSZ]; 410 const char *prompt = 411 "enter log message, terminated with a single '.' or end of file:\n"; 412 RCSNUM *tmprev; 413 414 rcs_msg = NULL; 415 tmprev = rcsnum_alloc(); 416 rcsnum_cpy(rev, tmprev, 16); 417 rcsnum_tostr(tmprev, prev, sizeof(prev)); 418 if (rev2 == NULL) 419 rcsnum_tostr(rcsnum_inc(tmprev), nrev, sizeof(nrev)); 420 else 421 rcsnum_tostr(rev2, nrev, sizeof(nrev)); 422 rcsnum_free(tmprev); 423 424 if (!(flags & QUIET)) 425 (void)fprintf(stderr, "new revision: %s; " 426 "previous revision: %s\n", nrev, prev); 427 428 rcs_msg = rcs_prompt(prompt); 429 430 return (rcs_msg); 431 } 432 433 /* 434 * checkin_update() 435 * 436 * Do a checkin to an existing RCS file. 437 * 438 * On success, return 0. On error return -1. 439 */ 440 static int 441 checkin_update(struct checkin_params *pb) 442 { 443 char numb1[RCS_REV_BUFSZ], numb2[RCS_REV_BUFSZ]; 444 struct stat st; 445 BUF *bp; 446 447 /* 448 * XXX this is wrong, we need to get the revision the user 449 * has the lock for. So we can decide if we want to create a 450 * branch or not. (if it's not current HEAD we need to branch). 451 */ 452 pb->frev = pb->file->rf_head; 453 454 /* Load file contents */ 455 if ((bp = rcs_buf_load(pb->filename, BUF_AUTOEXT)) == NULL) 456 return (-1); 457 458 /* If this is a zero-ending RCSNUM eg 4.0, increment it (eg to 4.1) */ 459 if (pb->newrev != NULL && RCSNUM_ZERO_ENDING(pb->newrev)) 460 pb->newrev = rcsnum_inc(pb->newrev); 461 462 if (checkin_checklock(pb) < 0) 463 return (-1); 464 465 /* If revision passed on command line is less than HEAD, bail. 466 * XXX only applies to ci -r1.2 foo for example if HEAD is > 1.2 and 467 * there is no lock set for the user. 468 */ 469 if (pb->newrev != NULL && 470 rcsnum_cmp(pb->newrev, pb->frev, 0) != -1) { 471 warnx("%s: revision %s too low; must be higher than %s", 472 pb->file->rf_path, 473 rcsnum_tostr(pb->newrev, numb1, sizeof(numb1)), 474 rcsnum_tostr(pb->frev, numb2, sizeof(numb2))); 475 return (-1); 476 } 477 478 /* 479 * Set the date of the revision to be the last modification 480 * time of the working file if -d has no argument. 481 */ 482 if (pb->date == DATE_MTIME) 483 checkin_mtimedate(pb); 484 485 /* Date from argv/mtime must be more recent than HEAD */ 486 if (pb->date != DATE_NOW) { 487 time_t head_date = rcs_rev_getdate(pb->file, pb->frev); 488 if (pb->date <= head_date) { 489 char dbuf1[256], dbuf2[256], *fmt; 490 struct tm *t, *t_head; 491 492 fmt = "%Y/%m/%d %H:%M:%S"; 493 494 t = gmtime(&pb->date); 495 strftime(dbuf1, sizeof(dbuf1), fmt, t); 496 t_head = gmtime(&head_date); 497 strftime(dbuf2, sizeof(dbuf2), fmt, t_head); 498 499 errx(1, "%s: Date %s precedes %s in revision %s.", 500 pb->file->rf_path, dbuf1, dbuf2, 501 rcsnum_tostr(pb->frev, numb2, sizeof(numb2))); 502 } 503 } 504 505 /* Get RCS patch */ 506 if ((pb->deltatext = checkin_diff_file(pb)) == NULL) { 507 warnx("failed to get diff"); 508 return (-1); 509 } 510 511 /* 512 * If -f is not specified and there are no differences, tell 513 * the user and revert to latest version. 514 */ 515 if (!(pb->flags & FORCE) && (rcs_buf_len(pb->deltatext) < 1)) { 516 if (checkin_revert(pb) == -1) 517 return (-1); 518 else 519 return (0); 520 } 521 522 /* If no log message specified, get it interactively. */ 523 if (pb->flags & INTERACTIVE) { 524 if (pb->rcs_msg != NULL) { 525 fprintf(stderr, 526 "reuse log message of previous file? [yn](y): "); 527 if (rcs_yesno('y') != 'y') { 528 xfree(pb->rcs_msg); 529 pb->rcs_msg = NULL; 530 } 531 } 532 if (pb->rcs_msg == NULL) 533 pb->rcs_msg = checkin_getlogmsg(pb->frev, pb->newrev, 534 pb->flags); 535 } 536 537 if ((rcs_lock_remove(pb->file, pb->username, pb->frev) < 0) && 538 (rcs_lock_getmode(pb->file) != RCS_LOCK_LOOSE)) { 539 if (rcs_errno != RCS_ERR_NOENT) 540 warnx("failed to remove lock"); 541 else if (!(pb->flags & CO_LOCK)) 542 warnx("previous revision was not locked; " 543 "ignoring -l option"); 544 } 545 546 /* Current head revision gets the RCS patch as rd_text */ 547 if (rcs_deltatext_set(pb->file, pb->frev, pb->deltatext) == -1) 548 errx(1, "failed to set new rd_text for head rev"); 549 550 /* Now add our new revision */ 551 if (rcs_rev_add(pb->file, 552 (pb->newrev == NULL ? RCS_HEAD_REV : pb->newrev), 553 pb->rcs_msg, pb->date, pb->author) != 0) { 554 warnx("failed to add new revision"); 555 return (-1); 556 } 557 558 /* 559 * If we are checking in to a non-default (ie user-specified) 560 * revision, set head to this revision. 561 */ 562 if (pb->newrev != NULL) { 563 if (rcs_head_set(pb->file, pb->newrev) < 0) 564 errx(1, "rcs_head_set failed"); 565 } else 566 pb->newrev = pb->file->rf_head; 567 568 /* New head revision has to contain entire file; */ 569 if (rcs_deltatext_set(pb->file, pb->frev, bp) == -1) 570 errx(1, "failed to set new head revision"); 571 572 /* Attach a symbolic name to this revision if specified. */ 573 if (pb->symbol != NULL && 574 (checkin_attach_symbol(pb) < 0)) 575 return (-1); 576 577 /* Set the state of this revision if specified. */ 578 if (pb->state != NULL) 579 (void)rcs_state_set(pb->file, pb->newrev, pb->state); 580 581 /* Maintain RCSFILE permissions */ 582 if (fstat(workfile_fd, &st) == -1) 583 err(1, "%s", pb->filename); 584 585 /* Strip all the write bits */ 586 pb->file->rf_mode = st.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH); 587 588 (void)close(workfile_fd); 589 (void)unlink(pb->filename); 590 591 /* Write out RCSFILE before calling checkout_rev() */ 592 rcs_write(pb->file); 593 594 /* Do checkout if -u or -l are specified. */ 595 if (((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK)) && 596 !(pb->flags & CI_DEFAULT)) 597 checkout_rev(pb->file, pb->newrev, pb->filename, pb->flags, 598 pb->username, pb->author, NULL, NULL); 599 600 if ((pb->flags & INTERACTIVE) && (pb->rcs_msg[0] == '\0')) { 601 xfree(pb->rcs_msg); /* free empty log message */ 602 pb->rcs_msg = NULL; 603 } 604 605 return (0); 606 } 607 608 /* 609 * checkin_init() 610 * 611 * Does an initial check in, just enough to create the new ,v file 612 * On success, return 0. On error return -1. 613 */ 614 static int 615 checkin_init(struct checkin_params *pb) 616 { 617 BUF *bp; 618 char numb[RCS_REV_BUFSZ]; 619 int fetchlog = 0; 620 struct stat st; 621 622 /* If this is a zero-ending RCSNUM eg 4.0, increment it (eg to 4.1) */ 623 if (pb->newrev != NULL && RCSNUM_ZERO_ENDING(pb->newrev)) { 624 pb->frev = rcsnum_alloc(); 625 rcsnum_cpy(pb->newrev, pb->frev, 0); 626 pb->newrev = rcsnum_inc(pb->newrev); 627 fetchlog = 1; 628 } 629 630 /* Load file contents */ 631 if ((bp = rcs_buf_load(pb->filename, BUF_AUTOEXT)) == NULL) 632 return (-1); 633 634 /* Get default values from working copy if -k specified */ 635 if (pb->flags & CI_KEYWORDSCAN) 636 checkin_keywordscan(bp, &pb->newrev, 637 &pb->date, &pb->state, &pb->author); 638 639 if (pb->flags & CI_SKIPDESC) 640 goto skipdesc; 641 642 /* Get description from user */ 643 if (pb->description == NULL && 644 rcs_set_description(pb->file, NULL) == -1) { 645 warn("%s", pb->filename); 646 return (-1); 647 } 648 649 skipdesc: 650 651 /* 652 * If the user had specified a zero-ending revision number e.g. 4.0 653 * emulate odd GNU behaviour and fetch log message. 654 */ 655 if (fetchlog == 1) { 656 pb->rcs_msg = checkin_getlogmsg(pb->frev, pb->newrev, 657 pb->flags); 658 rcsnum_free(pb->frev); 659 } 660 661 /* 662 * Set the date of the revision to be the last modification 663 * time of the working file if -d has no argument. 664 */ 665 if (pb->date == DATE_MTIME) 666 checkin_mtimedate(pb); 667 668 /* Now add our new revision */ 669 if (rcs_rev_add(pb->file, 670 (pb->newrev == NULL ? RCS_HEAD_REV : pb->newrev), 671 (pb->rcs_msg == NULL ? "Initial revision" : pb->rcs_msg), 672 pb->date, pb->author) != 0) { 673 warnx("failed to add new revision"); 674 return (-1); 675 } 676 677 /* 678 * If we are checking in to a non-default (ie user-specified) 679 * revision, set head to this revision. 680 */ 681 if (pb->newrev != NULL) { 682 if (rcs_head_set(pb->file, pb->newrev) < 0) 683 errx(1, "rcs_head_set failed"); 684 } else 685 pb->newrev = pb->file->rf_head; 686 687 /* New head revision has to contain entire file; */ 688 if (rcs_deltatext_set(pb->file, pb->file->rf_head, bp) == -1) { 689 warnx("failed to set new head revision"); 690 return (-1); 691 } 692 693 /* Attach a symbolic name to this revision if specified. */ 694 if (pb->symbol != NULL && checkin_attach_symbol(pb) < 0) 695 return (-1); 696 697 /* Set the state of this revision if specified. */ 698 if (pb->state != NULL) 699 (void)rcs_state_set(pb->file, pb->newrev, pb->state); 700 701 /* Inherit RCSFILE permissions from file being checked in */ 702 if (fstat(workfile_fd, &st) == -1) 703 err(1, "%s", pb->filename); 704 705 /* Strip all the write bits */ 706 pb->file->rf_mode = st.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH); 707 708 (void)close(workfile_fd); 709 (void)unlink(pb->filename); 710 711 /* Write out RCSFILE before calling checkout_rev() */ 712 rcs_write(pb->file); 713 714 /* Do checkout if -u or -l are specified. */ 715 if (((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK)) && 716 !(pb->flags & CI_DEFAULT)) { 717 checkout_rev(pb->file, pb->newrev, pb->filename, pb->flags, 718 pb->username, pb->author, NULL, NULL); 719 } 720 721 if (!(pb->flags & QUIET)) { 722 fprintf(stderr, "initial revision: %s\n", 723 rcsnum_tostr(pb->newrev, numb, sizeof(numb))); 724 } 725 726 return (0); 727 } 728 729 /* 730 * checkin_attach_symbol() 731 * 732 * Attempt to attach the specified symbol to the revision. 733 * On success, return 0. On error return -1. 734 */ 735 static int 736 checkin_attach_symbol(struct checkin_params *pb) 737 { 738 char rbuf[RCS_REV_BUFSZ]; 739 int ret; 740 if (!(pb->flags & QUIET)) 741 printf("symbol: %s\n", pb->symbol); 742 if (pb->flags & CI_SYMFORCE) { 743 if (rcs_sym_remove(pb->file, pb->symbol) < 0) { 744 if (rcs_errno != RCS_ERR_NOENT) { 745 warnx("problem removing symbol: %s", 746 pb->symbol); 747 return (-1); 748 } 749 } 750 } 751 if ((ret = rcs_sym_add(pb->file, pb->symbol, pb->newrev) == -1) && 752 (rcs_errno == RCS_ERR_DUPENT)) { 753 rcsnum_tostr(rcs_sym_getrev(pb->file, pb->symbol), 754 rbuf, sizeof(rbuf)); 755 warnx("symbolic name %s already bound to %s", pb->symbol, rbuf); 756 return (-1); 757 } else if (ret == -1) { 758 warnx("problem adding symbol: %s", pb->symbol); 759 return (-1); 760 } 761 return (0); 762 } 763 764 /* 765 * checkin_revert() 766 * 767 * If there are no differences between the working file and the latest revision 768 * and the -f flag is not specified, simply revert to the latest version and 769 * warn the user. 770 * 771 */ 772 static int 773 checkin_revert(struct checkin_params *pb) 774 { 775 char rbuf[RCS_REV_BUFSZ]; 776 777 rcsnum_tostr(pb->frev, rbuf, sizeof(rbuf)); 778 779 if (!(pb->flags & QUIET)) 780 (void)fprintf(stderr, "file is unchanged; reverting " 781 "to previous revision %s\n", rbuf); 782 783 /* Attach a symbolic name to this revision if specified. */ 784 if (pb->symbol != NULL) { 785 if (checkin_checklock(pb) == -1) 786 return (-1); 787 788 pb->newrev = pb->frev; 789 if (checkin_attach_symbol(pb) == -1) 790 return (-1); 791 } 792 793 pb->flags |= CO_REVERT; 794 (void)close(workfile_fd); 795 (void)unlink(pb->filename); 796 797 /* If needed, write out RCSFILE before calling checkout_rev() */ 798 if (pb->symbol != NULL) 799 rcs_write(pb->file); 800 801 if ((pb->flags & CO_LOCK) || (pb->flags & CO_UNLOCK)) 802 checkout_rev(pb->file, pb->frev, pb->filename, 803 pb->flags, pb->username, pb->author, NULL, NULL); 804 805 return (0); 806 } 807 808 /* 809 * checkin_checklock() 810 * 811 * Check for the existence of a lock on the file. If there are no locks, or it 812 * is not locked by the correct user, return -1. Otherwise, return 0. 813 */ 814 static int 815 checkin_checklock(struct checkin_params *pb) 816 { 817 struct rcs_lock *lkp; 818 819 if (rcs_lock_getmode(pb->file) == RCS_LOCK_LOOSE) 820 return (0); 821 822 TAILQ_FOREACH(lkp, &(pb->file->rf_locks), rl_list) { 823 if (!strcmp(lkp->rl_name, pb->username) && 824 !rcsnum_cmp(lkp->rl_num, pb->frev, 0)) 825 return (0); 826 } 827 828 warnx("%s: no lock set by %s", pb->file->rf_path, pb->username); 829 return (-1); 830 } 831 832 /* 833 * checkin_mtimedate() 834 * 835 * Set the date of the revision to be the last modification 836 * time of the working file. 837 */ 838 static void 839 checkin_mtimedate(struct checkin_params *pb) 840 { 841 struct stat sb; 842 843 if (fstat(workfile_fd, &sb) == -1) 844 err(1, "%s", pb->filename); 845 846 pb->date = (time_t)sb.st_mtimespec.tv_sec; 847 } 848 849 /* 850 * checkin_keywordscan() 851 * 852 * Searches working file for keyword values to determine its revision 853 * number, creation date and author, and uses these values instead of 854 * calculating them locally. 855 * 856 * Params: The data buffer to scan and pointers to pointers of variables in 857 * which to store the outputs. 858 * 859 * On success, return 0. On error return -1. 860 */ 861 static int 862 checkin_keywordscan(BUF *data, RCSNUM **rev, time_t *date, char **author, 863 char **state) 864 { 865 BUF *buf; 866 size_t left; 867 u_int j; 868 char *kwstr; 869 unsigned char *c, *end, *start; 870 871 end = rcs_buf_get(data) + rcs_buf_len(data) - 1; 872 kwstr = NULL; 873 874 left = rcs_buf_len(data); 875 for (c = rcs_buf_get(data); 876 c <= end && (c = memchr(c, '$', left)) != NULL; 877 left = end - c + 1) { 878 size_t len; 879 880 start = c; 881 c++; 882 if (!isalpha(*c)) 883 continue; 884 885 /* look for any matching keywords */ 886 for (j = 0; j < 10; j++) { 887 len = strlen(rcs_expkw[j].kw_str); 888 if (left < len) 889 continue; 890 if (memcmp(c, rcs_expkw[j].kw_str, len) != 0) { 891 kwstr = rcs_expkw[j].kw_str; 892 break; 893 } 894 } 895 896 /* unknown keyword, continue looking */ 897 if (kwstr == NULL) 898 continue; 899 900 c += len; 901 if (c > end) { 902 kwstr = NULL; 903 break; 904 } 905 if (*c != ':') { 906 kwstr = NULL; 907 continue; 908 } 909 910 /* Find end of line or end of keyword. */ 911 while (++c <= end) { 912 if (*c == '\n') { 913 /* Skip newline since it is definitely not `$'. */ 914 ++c; 915 goto loopend; 916 } 917 if (*c == '$') 918 break; 919 } 920 921 len = c - start + 1; 922 buf = rcs_buf_alloc(len + 1, 0); 923 rcs_buf_append(buf, start, len); 924 925 /* XXX - Not binary safe. */ 926 rcs_buf_putc(buf, '\0'); 927 checkin_parsekeyword(rcs_buf_get(buf), rev, date, author, state); 928 loopend:; 929 } 930 if (kwstr == NULL) 931 return (-1); 932 else 933 return (0); 934 } 935 936 /* 937 * checkin_keywordtype() 938 * 939 * Given an RCS keyword string, determine what type of string it is. 940 * This enables us to know what data should be in it. 941 * 942 * Returns type on success, or -1 on failure. 943 */ 944 static int 945 checkin_keywordtype(char *keystring) 946 { 947 char *p; 948 949 p = keystring; 950 p++; 951 if (strncmp(p, KW_ID, strlen(KW_ID)) == 0) 952 return (KW_TYPE_ID); 953 else if (strncmp(p, KW_AUTHOR, strlen(KW_AUTHOR)) == 0) 954 return (KW_TYPE_AUTHOR); 955 else if (strncmp(p, KW_DATE, strlen(KW_DATE)) == 0) 956 return (KW_TYPE_DATE); 957 else if (strncmp(p, KW_STATE, strlen(KW_STATE)) == 0) 958 return (KW_TYPE_STATE); 959 else if (strncmp(p, KW_REVISION, strlen(KW_REVISION)) == 0) 960 return (KW_TYPE_REVISION); 961 else 962 return (-1); 963 } 964 965 /* 966 * checkin_parsekeyword() 967 * 968 * Do the actual parsing of an RCS keyword string, setting the values passed 969 * to the function to whatever is found. 970 * 971 * XXX - Don't error out on malformed keywords. 972 */ 973 static void 974 checkin_parsekeyword(char *keystring, RCSNUM **rev, time_t *date, 975 char **author, char **state) 976 { 977 char *tokens[KW_NUMTOKS_MAX], *p, *datestring; 978 int i = 0; 979 980 for ((p = strtok(keystring, " ")); p; (p = strtok(NULL, " "))) { 981 if (i < KW_NUMTOKS_MAX - 1) 982 tokens[i++] = p; 983 else 984 break; 985 } 986 987 /* Parse data out of the expanded keyword */ 988 switch (checkin_keywordtype(keystring)) { 989 case KW_TYPE_ID: 990 if (i < 3) 991 break; 992 /* only parse revision if one is not already set */ 993 if (*rev == NULL) { 994 if ((*rev = rcsnum_parse(tokens[2])) == NULL) 995 errx(1, "could not parse rcsnum"); 996 } 997 998 if (i < 5) 999 break; 1000 (void)xasprintf(&datestring, "%s %s", tokens[3], tokens[4]); 1001 if ((*date = rcs_date_parse(datestring)) <= 0) 1002 errx(1, "could not parse date"); 1003 xfree(datestring); 1004 1005 if (i < 6) 1006 break; 1007 if (*author != NULL) 1008 xfree(*author); 1009 *author = xstrdup(tokens[5]); 1010 1011 if (i < 7) 1012 break; 1013 if (*state != NULL) 1014 xfree(*state); 1015 *state = xstrdup(tokens[6]); 1016 break; 1017 case KW_TYPE_AUTHOR: 1018 if (i < 2) 1019 break; 1020 if (*author != NULL) 1021 xfree(*author); 1022 *author = xstrdup(tokens[1]); 1023 break; 1024 case KW_TYPE_DATE: 1025 if (i < 3) 1026 break; 1027 (void)xasprintf(&datestring, "%s %s", tokens[1], tokens[2]); 1028 if ((*date = rcs_date_parse(datestring)) <= 0) 1029 errx(1, "could not parse date"); 1030 xfree(datestring); 1031 break; 1032 case KW_TYPE_STATE: 1033 if (i < 2) 1034 break; 1035 if (*state != NULL) 1036 xfree(*state); 1037 *state = xstrdup(tokens[1]); 1038 break; 1039 case KW_TYPE_REVISION: 1040 if (i < 2) 1041 break; 1042 /* only parse revision if one is not already set */ 1043 if (*rev != NULL) 1044 break; 1045 if ((*rev = rcsnum_parse(tokens[1])) == NULL) 1046 errx(1, "could not parse rcsnum"); 1047 break; 1048 } 1049 } 1050