1 /* $OpenBSD: co.c,v 1.117 2013/04/16 20:24:45 deraadt Exp $ */ 2 /* 3 * Copyright (c) 2005 Joris Vink <joris@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 #include <sys/time.h> 29 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 CO_OPTSTRING "d:f::I::k:l::M::p::q::r::s:Tu::Vw::x::z::" 41 42 static void checkout_err_nobranch(RCSFILE *, const char *, const char *, 43 const char *, int); 44 static int checkout_file_has_diffs(RCSFILE *, RCSNUM *, const char *); 45 46 int 47 checkout_main(int argc, char **argv) 48 { 49 int fd, i, ch, flags, kflag, ret; 50 RCSNUM *rev; 51 RCSFILE *file; 52 const char *author, *date, *state; 53 char fpath[MAXPATHLEN]; 54 char *rev_str, *username; 55 time_t rcs_mtime = -1; 56 57 flags = ret = 0; 58 kflag = RCS_KWEXP_ERR; 59 rev = RCS_HEAD_REV; 60 rev_str = NULL; 61 author = date = state = NULL; 62 63 while ((ch = rcs_getopt(argc, argv, CO_OPTSTRING)) != -1) { 64 switch (ch) { 65 case 'd': 66 date = rcs_optarg; 67 break; 68 case 'f': 69 rcs_setrevstr(&rev_str, rcs_optarg); 70 flags |= FORCE; 71 break; 72 case 'I': 73 rcs_setrevstr(&rev_str, rcs_optarg); 74 flags |= INTERACTIVE; 75 break; 76 77 case 'k': 78 kflag = rcs_kflag_get(rcs_optarg); 79 if (RCS_KWEXP_INVAL(kflag)) { 80 warnx("invalid RCS keyword substitution mode"); 81 (usage)(); 82 exit(1); 83 } 84 break; 85 case 'l': 86 if (flags & CO_UNLOCK) { 87 warnx("warning: -u overridden by -l"); 88 flags &= ~CO_UNLOCK; 89 } 90 rcs_setrevstr(&rev_str, rcs_optarg); 91 flags |= CO_LOCK; 92 break; 93 case 'M': 94 rcs_setrevstr(&rev_str, rcs_optarg); 95 flags |= CO_REVDATE; 96 break; 97 case 'p': 98 rcs_setrevstr(&rev_str, rcs_optarg); 99 flags |= PIPEOUT; 100 break; 101 case 'q': 102 rcs_setrevstr(&rev_str, rcs_optarg); 103 flags |= QUIET; 104 break; 105 case 'r': 106 rcs_setrevstr(&rev_str, rcs_optarg); 107 break; 108 case 's': 109 state = rcs_optarg; 110 flags |= CO_STATE; 111 break; 112 case 'T': 113 flags |= PRESERVETIME; 114 break; 115 case 'u': 116 rcs_setrevstr(&rev_str, rcs_optarg); 117 if (flags & CO_LOCK) { 118 warnx("warning: -l overridden by -u"); 119 flags &= ~CO_LOCK; 120 } 121 flags |= CO_UNLOCK; 122 break; 123 case 'V': 124 printf("%s\n", rcs_version); 125 exit(0); 126 case 'w': 127 /* if no argument, assume current user */ 128 if (rcs_optarg == NULL) { 129 if ((author = getlogin()) == NULL) 130 err(1, "getlogin"); 131 } else 132 author = rcs_optarg; 133 flags |= CO_AUTHOR; 134 break; 135 case 'x': 136 /* Use blank extension if none given. */ 137 rcs_suffixes = rcs_optarg ? rcs_optarg : ""; 138 break; 139 case 'z': 140 timezone_flag = rcs_optarg; 141 break; 142 default: 143 (usage)(); 144 exit(1); 145 } 146 } 147 148 argc -= rcs_optind; 149 argv += rcs_optind; 150 151 if (argc == 0) { 152 warnx("no input file"); 153 (usage)(); 154 exit (1); 155 } 156 157 if ((username = getlogin()) == NULL) 158 err(1, "getlogin"); 159 160 for (i = 0; i < argc; i++) { 161 fd = rcs_choosefile(argv[i], fpath, sizeof(fpath)); 162 if (fd < 0) { 163 warn("%s", fpath); 164 ret = 1; 165 continue; 166 } 167 rcs_strip_suffix(argv[i]); 168 169 if (!(flags & QUIET)) 170 (void)fprintf(stderr, "%s --> %s\n", fpath, 171 (flags & PIPEOUT) ? "standard output" : argv[i]); 172 173 if ((flags & CO_LOCK) && (kflag & RCS_KWEXP_VAL)) { 174 warnx("%s: cannot combine -kv and -l", fpath); 175 (void)close(fd); 176 continue; 177 } 178 179 if ((file = rcs_open(fpath, fd, 180 RCS_RDWR|RCS_PARSE_FULLY)) == NULL) 181 continue; 182 183 if (flags & PRESERVETIME) 184 rcs_mtime = rcs_get_mtime(file); 185 186 rcs_kwexp_set(file, kflag); 187 188 if (rev_str != NULL) { 189 if ((rev = rcs_getrevnum(rev_str, file)) == NULL) 190 errx(1, "invalid revision: %s", rev_str); 191 } else { 192 /* no revisions in RCS file, generate empty 0.0 */ 193 if (file->rf_ndelta == 0) { 194 rev = rcsnum_parse("0.0"); 195 if (rev == NULL) 196 errx(1, "failed to generate rev 0.0"); 197 } else { 198 rev = rcsnum_alloc(); 199 rcsnum_cpy(file->rf_head, rev, 0); 200 } 201 } 202 203 if (checkout_rev(file, rev, argv[i], flags, 204 username, author, state, date) < 0) { 205 rcs_close(file); 206 rcsnum_free(rev); 207 ret = 1; 208 continue; 209 } 210 211 if (!(flags & QUIET)) 212 (void)fprintf(stderr, "done\n"); 213 214 rcsnum_free(rev); 215 216 rcs_write(file); 217 if (flags & PRESERVETIME) 218 rcs_set_mtime(file, rcs_mtime); 219 rcs_close(file); 220 } 221 222 return (ret); 223 } 224 225 void 226 checkout_usage(void) 227 { 228 fprintf(stderr, 229 "usage: co [-TV] [-ddate] [-f[rev]] [-I[rev]] [-kmode] [-l[rev]]\n" 230 " [-M[rev]] [-p[rev]] [-q[rev]] [-r[rev]] [-sstate]\n" 231 " [-u[rev]] [-w[user]] [-xsuffixes] [-ztz] file ...\n"); 232 } 233 234 /* 235 * Checkout revision <rev> from RCSFILE <file>, writing it to the path <dst> 236 * Currently recognised <flags> are CO_LOCK, CO_UNLOCK and CO_REVDATE. 237 * 238 * Looks up revision based upon <lockname>, <author>, <state> and <date> 239 * 240 * Returns 0 on success, -1 on failure. 241 */ 242 int 243 checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags, 244 const char *lockname, const char *author, const char *state, 245 const char *date) 246 { 247 BUF *bp; 248 u_int i; 249 int fd, lcount; 250 char buf[RCS_REV_BUFSZ]; 251 mode_t mode = DEFFILEMODE; 252 struct stat st; 253 struct rcs_delta *rdp; 254 struct rcs_lock *lkp; 255 char *fdate; 256 const char *fstatus; 257 time_t rcsdate, givendate; 258 RCSNUM *rev; 259 260 rcsdate = givendate = -1; 261 if (date != NULL && (givendate = date_parse(date)) == -1) { 262 warnx("invalid date: %s", date); 263 return -1; 264 } 265 266 if (file->rf_ndelta == 0 && !(flags & QUIET)) 267 (void)fprintf(stderr, 268 "no revisions present; generating empty revision 0.0\n"); 269 270 /* XXX rcsnum_cmp() 271 * Check out the latest revision if <frev> is greater than HEAD 272 */ 273 if (file->rf_ndelta != 0) { 274 for (i = 0; i < file->rf_head->rn_len; i++) { 275 if (file->rf_head->rn_id[i] < frev->rn_id[i]) { 276 frev = file->rf_head; 277 break; 278 } 279 } 280 } 281 282 lcount = 0; 283 TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) { 284 if (!strcmp(lkp->rl_name, lockname)) 285 lcount++; 286 } 287 288 /* 289 * If the user didn't specify any revision, we cycle through 290 * revisions to lookup the first one that matches what he specified. 291 * 292 * If we cannot find one, we return an error. 293 */ 294 rdp = NULL; 295 if (file->rf_ndelta != 0 && frev == file->rf_head) { 296 if (lcount > 1) { 297 warnx("multiple revisions locked by %s; " 298 "please specify one", lockname); 299 return (-1); 300 } 301 302 TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) { 303 if (date != NULL) { 304 fdate = asctime(&rdp->rd_date); 305 if ((rcsdate = date_parse(fdate)) == -1) { 306 warnx("invalid date: %s", fdate); 307 return -1; 308 } 309 if (givendate <= rcsdate) 310 continue; 311 } 312 313 if (author != NULL && 314 strcmp(rdp->rd_author, author)) 315 continue; 316 317 if (state != NULL && 318 strcmp(rdp->rd_state, state)) 319 continue; 320 321 frev = rdp->rd_num; 322 break; 323 } 324 } else if (file->rf_ndelta != 0) { 325 rdp = rcs_findrev(file, frev); 326 } 327 328 if (file->rf_ndelta != 0 && rdp == NULL) { 329 checkout_err_nobranch(file, author, date, state, flags); 330 return (-1); 331 } 332 333 if (file->rf_ndelta == 0) 334 rev = frev; 335 else 336 rev = rdp->rd_num; 337 338 rcsnum_tostr(rev, buf, sizeof(buf)); 339 340 if (file->rf_ndelta != 0 && rdp->rd_locker != NULL) { 341 if (strcmp(lockname, rdp->rd_locker)) { 342 warnx("Revision %s is already locked by %s; %s", 343 buf, rdp->rd_locker, 344 (flags & CO_UNLOCK) ? "use co -r or rcs -u" : ""); 345 return (-1); 346 } 347 } 348 349 if (!(flags & QUIET) && !(flags & NEWFILE) && 350 !(flags & CO_REVERT) && file->rf_ndelta != 0) 351 (void)fprintf(stderr, "revision %s", buf); 352 353 if (file->rf_ndelta != 0) { 354 if ((bp = rcs_getrev(file, rev)) == NULL) { 355 warnx("cannot find revision `%s'", buf); 356 return (-1); 357 } 358 } else { 359 bp = buf_alloc(1); 360 } 361 362 /* 363 * Do keyword expansion if required. 364 */ 365 if (file->rf_ndelta != 0) 366 bp = rcs_kwexp_buf(bp, file, rev); 367 /* 368 * File inherits permissions from its ,v file 369 */ 370 if (file->rf_file != NULL) { 371 if (fstat(fileno(file->rf_file), &st) == -1) 372 err(1, "%s", file->rf_path); 373 file->rf_mode = mode = st.st_mode; 374 } else { 375 mode = file->rf_mode; 376 } 377 378 if (flags & CO_LOCK) { 379 if (file->rf_ndelta != 0) { 380 if (lockname != NULL && 381 rcs_lock_add(file, lockname, rev) < 0) { 382 if (rcs_errno != RCS_ERR_DUPENT) 383 return (-1); 384 } 385 } 386 387 /* File should only be writable by owner. */ 388 mode &= ~(S_IWGRP|S_IWOTH); 389 mode |= S_IWUSR; 390 391 if (file->rf_ndelta != 0) { 392 if (!(flags & QUIET) && !(flags & NEWFILE) && 393 !(flags & CO_REVERT)) 394 (void)fprintf(stderr, " (locked)"); 395 } 396 } else if (flags & CO_UNLOCK) { 397 if (file->rf_ndelta != 0) { 398 if (rcs_lock_remove(file, lockname, rev) < 0) { 399 if (rcs_errno != RCS_ERR_NOENT) 400 return (-1); 401 } 402 } 403 404 /* Strip all write bits from mode */ 405 mode &= ~(S_IWUSR|S_IWGRP|S_IWOTH); 406 407 if (file->rf_ndelta != 0) { 408 if (!(flags & QUIET) && !(flags & NEWFILE) && 409 !(flags & CO_REVERT)) 410 (void)fprintf(stderr, " (unlocked)"); 411 } 412 } 413 414 /* If strict locking is disabled, make file writable by owner. */ 415 if (rcs_lock_getmode(file) == RCS_LOCK_LOOSE) 416 mode |= S_IWUSR; 417 418 if (file->rf_ndelta == 0 && !(flags & QUIET) && 419 ((flags & CO_LOCK) || (flags & CO_UNLOCK))) { 420 (void)fprintf(stderr, "no revisions, so nothing can be %s\n", 421 (flags & CO_LOCK) ? "locked" : "unlocked"); 422 } else if (file->rf_ndelta != 0) { 423 /* XXX - Not a good way to detect if a newline is needed. */ 424 if (!(flags & QUIET) && !(flags & NEWFILE) && 425 !(flags & CO_REVERT)) 426 (void)fprintf(stderr, "\n"); 427 } 428 429 if (flags & CO_LOCK) { 430 if (rcs_errno != RCS_ERR_DUPENT) 431 lcount++; 432 if (!(flags & QUIET) && lcount > 1 && !(flags & CO_REVERT)) 433 warnx("%s: warning: You now have %d locks.", 434 file->rf_path, lcount); 435 } 436 437 if ((flags & (PIPEOUT|FORCE)) == 0 && stat(dst, &st) != -1) { 438 /* 439 * Prompt the user if the file is writable or the file is 440 * not writable but is different from the RCS head version. 441 * This is different from GNU which will silently overwrite 442 * the file regardless of its contents so long as it is 443 * read-only. 444 */ 445 if (st.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) 446 fstatus = "writable"; 447 else if (checkout_file_has_diffs(file, frev, dst) != D_SAME) 448 fstatus = "modified"; 449 else 450 fstatus = NULL; 451 if (fstatus) { 452 (void)fprintf(stderr, "%s %s exists%s; ", fstatus, dst, 453 (getuid() == st.st_uid) ? "" : 454 ", and you do not own it"); 455 (void)fprintf(stderr, "remove it? [ny](n): "); 456 if (rcs_yesno('n') == 'n') { 457 if (!(flags & QUIET) && isatty(STDIN_FILENO)) 458 warnx("%s %s exists; checkout aborted", 459 fstatus, dst); 460 else 461 warnx("checkout aborted"); 462 return (-1); 463 } 464 } 465 } 466 467 if (flags & PIPEOUT) 468 buf_write_fd(bp, STDOUT_FILENO); 469 else { 470 (void)unlink(dst); 471 472 if ((fd = open(dst, O_WRONLY|O_CREAT|O_TRUNC, mode)) < 0) 473 err(1, "%s", dst); 474 475 if (buf_write_fd(bp, fd) < 0) { 476 warnx("failed to write revision to file"); 477 buf_free(bp); 478 (void)close(fd); 479 return (-1); 480 } 481 482 if (fchmod(fd, mode) == -1) 483 warn("%s", dst); 484 485 if (flags & CO_REVDATE) { 486 struct timeval tv[2]; 487 memset(&tv, 0, sizeof(tv)); 488 tv[0].tv_sec = rcs_rev_getdate(file, rev); 489 tv[1].tv_sec = tv[0].tv_sec; 490 if (futimes(fd, (const struct timeval *)&tv) < 0) 491 warn("utimes"); 492 } 493 494 (void)close(fd); 495 } 496 497 buf_free(bp); 498 499 return (0); 500 } 501 502 /* 503 * checkout_err_nobranch() 504 * 505 * XXX - should handle the dates too. 506 */ 507 static void 508 checkout_err_nobranch(RCSFILE *file, const char *author, const char *date, 509 const char *state, int flags) 510 { 511 if (!(flags & CO_AUTHOR)) 512 author = NULL; 513 if (!(flags & CO_STATE)) 514 state = NULL; 515 516 warnx("%s: No revision on branch has%s%s%s%s%s%s.", 517 file->rf_path, 518 date ? " a date before " : "", 519 date ? date : "", 520 author ? " and author " + (date ? 0:4 ) : "", 521 author ? author : "", 522 state ? " and state " + (date || author ? 0:4) : "", 523 state ? state : ""); 524 } 525 526 /* 527 * checkout_file_has_diffs() 528 * 529 * Check for diffs between the working file and its current revision. 530 * Same return values as diffreg() 531 */ 532 static int 533 checkout_file_has_diffs(RCSFILE *rfp, RCSNUM *frev, const char *dst) 534 { 535 char *tempfile; 536 BUF *bp; 537 int ret; 538 539 tempfile = NULL; 540 541 if ((bp = rcs_getrev(rfp, frev)) == NULL) { 542 warnx("failed to load revision"); 543 return (D_ERROR); 544 } 545 if ((bp = rcs_kwexp_buf(bp, rfp, frev)) == NULL) { 546 warnx("failed to expand tags"); 547 return (D_ERROR); 548 } 549 550 (void)xasprintf(&tempfile, "%s/diff.XXXXXXXXXX", rcs_tmpdir); 551 buf_write_stmp(bp, tempfile); 552 buf_empty(bp); 553 554 diff_format = D_RCSDIFF; 555 ret = diffreg(dst, tempfile, bp, D_FORCEASCII); 556 557 buf_free(bp); 558 unlink(tempfile); 559 xfree(tempfile); 560 561 return (ret); 562 } 563