co.c revision 1.79
1/* $OpenBSD: co.c,v 1.79 2006/04/19 06:53:41 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 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 cvs_log(LP_ERR, 72 "invalid RCS keyword expansion mode"); 73 (usage)(); 74 exit(1); 75 } 76 break; 77 case 'l': 78 if (flags & CO_UNLOCK) { 79 cvs_log(LP_ERR, "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 pipeout = 1; 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 = xstrdup(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 cvs_log(LP_ERR, "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 /* NOTREACHED */ 119 case 'w': 120 /* if no argument, assume current user */ 121 if (rcs_optarg == NULL) { 122 if ((author = getlogin()) == NULL) 123 fatal("getlogin failed"); 124 } else { 125 author = xstrdup(rcs_optarg); 126 warg = 1; 127 } 128 flags |= CO_AUTHOR; 129 break; 130 case 'x': 131 /* Use blank extension if none given. */ 132 rcs_suffixes = rcs_optarg ? rcs_optarg : ""; 133 break; 134 case 'z': 135 timezone_flag = rcs_optarg; 136 break; 137 default: 138 (usage)(); 139 exit(1); 140 } 141 } 142 143 argc -= rcs_optind; 144 argv += rcs_optind; 145 146 if (argc == 0) { 147 cvs_log(LP_ERR, "no input file"); 148 (usage)(); 149 exit (1); 150 } 151 152 if ((username = getlogin()) == NULL) { 153 cvs_log(LP_ERRNO, "failed to get username"); 154 exit (1); 155 } 156 157 for (i = 0; i < argc; i++) { 158 if (rcs_statfile(argv[i], fpath, sizeof(fpath)) < 0) 159 continue; 160 161 if (!(flags & QUIET)) 162 printf("%s --> %s\n", fpath, 163 (pipeout == 1) ? "standard output" : argv[i]); 164 165 if ((flags & CO_LOCK) && (kflag & RCS_KWEXP_VAL)) { 166 cvs_log(LP_ERR, "%s: cannot combine -kv and -l", fpath); 167 continue; 168 } 169 170 if ((file = rcs_open(fpath, RCS_RDWR|RCS_PARSE_FULLY)) == NULL) 171 continue; 172 173 if (flags & PRESERVETIME) 174 rcs_mtime = rcs_get_mtime(file->rf_path); 175 176 rcs_kwexp_set(file, kflag); 177 178 if (rev_str != NULL) { 179 if ((rev = rcs_getrevnum(rev_str, file)) == NULL) 180 fatal("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 fatal("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 printf("done\n"); 202 203 rcs_close(file); 204 rcsnum_free(rev); 205 206 if (flags & PRESERVETIME) 207 rcs_set_mtime(fpath, rcs_mtime); 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 * Currenly 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 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 = cvs_date_parse(date); 259 260 if (file->rf_ndelta == 0) 261 printf("no revisions present; generating empty revision 0.0\n"); 262 263 /* XXX rcsnum_cmp() 264 * Check out the latest revision if <frev> is greater than HEAD 265 */ 266 if (file->rf_ndelta != 0) { 267 for (i = 0; i < file->rf_head->rn_len; i++) { 268 if (file->rf_head->rn_id[i] < frev->rn_id[i]) { 269 frev = file->rf_head; 270 break; 271 } 272 } 273 } 274 275 lcount = 0; 276 TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) { 277 if (!strcmp(lkp->rl_name, lockname)) 278 lcount++; 279 } 280 281 /* 282 * If the user didn't specify any revision, we cycle through 283 * revisions to lookup the first one that matches what he specified. 284 * 285 * If we cannot find one, we return an error. 286 */ 287 rdp = NULL; 288 if (file->rf_ndelta != 0 && frev == file->rf_head) { 289 if (lcount > 1) { 290 cvs_log(LP_WARN, 291 "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 = cvs_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 strlcpy(msg, "Revision %s is already locked by %s; ", 334 sizeof(msg)); 335 336 if (flags & CO_UNLOCK) { 337 strlcat(msg, "use co -r or rcs -u", 338 sizeof(msg)); 339 } 340 341 cvs_log(LP_ERR, msg, buf, rdp->rd_locker); 342 return (-1); 343 } 344 } 345 346 if (!(flags & QUIET) && !(flags & NEWFILE) && 347 !(flags & CO_REVERT) && file->rf_ndelta != 0) 348 printf("revision %s", buf); 349 350 if (!(flags & QUIET) && (flags & CO_REVERT)) 351 printf("done"); 352 353 if (file->rf_ndelta != 0) { 354 if ((bp = rcs_getrev(file, rev)) == NULL) { 355 cvs_log(LP_ERR, "cannot find revision `%s'", buf); 356 return (-1); 357 } 358 } else { 359 bp = cvs_buf_alloc(1, 0); 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 /* 369 * File inherits permissions from its ,v file 370 */ 371 if (stat(file->rf_path, &st) == -1) 372 fatal("could not stat rcsfile"); 373 374 mode = st.st_mode; 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 mode = st.st_mode & 387 (S_IXUSR|S_IXGRP|S_IXOTH|S_IRUSR|S_IRGRP|S_IROTH); 388 mode |= S_IWUSR; 389 390 if (file->rf_ndelta != 0) { 391 if (!(flags & QUIET) && !(flags & NEWFILE) && 392 !(flags & CO_REVERT)) 393 printf(" (locked)"); 394 } 395 } else if (flags & CO_UNLOCK) { 396 if (file->rf_ndelta != 0) { 397 if (rcs_lock_remove(file, lockname, rev) < 0) { 398 if (rcs_errno != RCS_ERR_NOENT) 399 return (-1); 400 } 401 } 402 403 /* Strip all write bits from mode */ 404 mode = st.st_mode & 405 (S_IXUSR|S_IXGRP|S_IXOTH|S_IRUSR|S_IRGRP|S_IROTH); 406 407 if (file->rf_ndelta != 0) { 408 if (!(flags & QUIET) && !(flags & NEWFILE) && 409 !(flags & CO_REVERT)) 410 printf(" (unlocked)"); 411 } 412 } 413 414 if (file->rf_ndelta == 0 && 415 ((flags & CO_LOCK) || (flags & CO_UNLOCK))) { 416 cvs_log(LP_WARN, "no revisions, so nothing can be %s", 417 (flags & CO_LOCK) ? "locked" : "unlocked"); 418 } else if (file->rf_ndelta != 0) { 419 if (!(flags & QUIET) && !(flags & NEWFILE)) 420 printf("\n"); 421 } 422 423 if (flags & CO_LOCK) { 424 if (rcs_errno != RCS_ERR_DUPENT) 425 lcount++; 426 if (!(flags & QUIET) && lcount > 1 && !(flags & CO_REVERT)) 427 cvs_log(LP_WARN, "%s: warning: You now have %d locks.", 428 file->rf_path, lcount); 429 } 430 431 if (pipeout == 0 && stat(dst, &st) == 0 && !(flags & FORCE)) { 432 /* 433 * XXX - Not sure what is "right". If we go according 434 * to GNU's behavior, an existing file with no writable 435 * bits is overwritten without prompting the user. 436 * 437 * This is dangerous, so we always prompt. 438 * Unfortunately this interferes with an unlocked 439 * checkout followed by a locked checkout, which should 440 * not prompt. One (unimplemented) solution is to check 441 * if the existing file is the same as the checked out 442 * revision, and prompt if there are differences. 443 */ 444 if (st.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) 445 printf("writable "); 446 printf("%s exists%s; ", dst, 447 (getuid() == st.st_uid) ? "" : 448 ", and you do not own it"); 449 printf("remove it? [ny](n): "); 450 /* default is n */ 451 if (cvs_yesno() == -1) { 452 if (!(flags & QUIET) && isatty(STDIN_FILENO)) 453 cvs_log(LP_ERR, 454 "writable %s exists; checkout aborted", 455 dst); 456 else 457 cvs_log(LP_ERR, "checkout aborted"); 458 return (-1); 459 } 460 } 461 462 if (pipeout == 1) { 463 cvs_buf_putc(bp, '\0'); 464 content = cvs_buf_release(bp); 465 printf("%s", content); 466 xfree(content); 467 } else { 468 if (cvs_buf_write(bp, dst, mode) < 0) { 469 cvs_log(LP_ERR, "failed to write revision to file"); 470 cvs_buf_free(bp); 471 return (-1); 472 } 473 cvs_buf_free(bp); 474 if (flags & CO_REVDATE) { 475 struct timeval tv[2]; 476 memset(&tv, 0, sizeof(tv)); 477 tv[0].tv_sec = (long)rcs_rev_getdate(file, rev); 478 tv[1].tv_sec = tv[0].tv_sec; 479 if (utimes(dst, (const struct timeval *)&tv) < 0) 480 cvs_log(LP_ERRNO, "error setting utimes"); 481 } 482 } 483 484 return (0); 485} 486 487/* 488 * checkout_err_nobranch() 489 * 490 * XXX - should handle the dates too. 491 */ 492static void 493checkout_err_nobranch(RCSFILE *file, const char *author, const char *date, 494 const char *state, int flags) 495{ 496 if (!(flags & CO_AUTHOR)) 497 author = NULL; 498 if (!(flags & CO_STATE)) 499 state = NULL; 500 501 cvs_log(LP_ERR, "%s: No revision on branch has%s%s%s%s%s%s.", 502 file->rf_path, 503 date ? " a date before " : "", 504 date ? date : "", 505 author ? " and author " + (date ? 0:4 ) : "", 506 author ? author : "", 507 state ? " and state " + (date || author ? 0:4) : "", 508 state ? state : ""); 509} 510