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