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