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