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