co.c revision 1.85
1/* $OpenBSD: co.c,v 1.85 2006/04/26 02:55:13 joris 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 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 /* NOTREACHED */ 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 = xstrdup(rcs_optarg); 125 warg = 1; 126 } 127 flags |= CO_AUTHOR; 128 break; 129 case 'x': 130 /* Use blank extension if none given. */ 131 rcs_suffixes = rcs_optarg ? rcs_optarg : ""; 132 break; 133 case 'z': 134 timezone_flag = rcs_optarg; 135 break; 136 default: 137 (usage)(); 138 exit(1); 139 } 140 } 141 142 argc -= rcs_optind; 143 argv += rcs_optind; 144 145 if (argc == 0) { 146 warnx("no input file"); 147 (usage)(); 148 exit (1); 149 } 150 151 if ((username = getlogin()) == NULL) 152 err(1, "getlogin"); 153 154 for (i = 0; i < argc; i++) { 155 if (rcs_statfile(argv[i], fpath, sizeof(fpath), flags) < 0) 156 continue; 157 158 if (!(flags & QUIET)) 159 printf("%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 continue; 165 } 166 167 if ((file = rcs_open(fpath, RCS_RDWR|RCS_PARSE_FULLY)) == NULL) 168 continue; 169 170 if (flags & PRESERVETIME) 171 rcs_mtime = rcs_get_mtime(file->rf_path); 172 173 rcs_kwexp_set(file, kflag); 174 175 if (rev_str != NULL) { 176 if ((rev = rcs_getrevnum(rev_str, file)) == NULL) 177 errx(1, "invalid revision: %s", rev_str); 178 } else { 179 /* no revisions in RCS file, generate empty 0.0 */ 180 if (file->rf_ndelta == 0) { 181 rev = rcsnum_parse("0.0"); 182 if (rev == NULL) 183 errx(1, "failed to generate rev 0.0"); 184 } else { 185 rev = rcsnum_alloc(); 186 rcsnum_cpy(file->rf_head, rev, 0); 187 } 188 } 189 190 if ((status = checkout_rev(file, rev, argv[i], flags, 191 username, author, state, date)) < 0) { 192 rcs_close(file); 193 rcsnum_free(rev); 194 continue; 195 } 196 197 if (!(flags & QUIET)) 198 printf("done\n"); 199 200 rcs_close(file); 201 rcsnum_free(rev); 202 203 if (flags & PRESERVETIME) 204 rcs_set_mtime(fpath, rcs_mtime); 205 } 206 207 if (author != NULL && warg) 208 xfree(author); 209 210 if (date != NULL) 211 xfree(date); 212 213 if (state != NULL) 214 xfree(state); 215 216 return (status); 217} 218 219void 220checkout_usage(void) 221{ 222 fprintf(stderr, 223 "usage: co [-TV] [-ddate] [-f[rev]] [-I[rev]] [-kmode] [-l[rev]]\n" 224 " [-M[rev]] [-p[rev]] [-q[rev]] [-r[rev]] [-sstate]\n" 225 " [-u[rev]] [-w[user]] [-xsuffixes] [-ztz] file ...\n"); 226} 227 228/* 229 * Checkout revision <rev> from RCSFILE <file>, writing it to the path <dst> 230 * Currenly recognised <flags> are CO_LOCK, CO_UNLOCK and CO_REVDATE. 231 * 232 * Looks up revision based upon <lockname>, <author>, <state> and <date> 233 * 234 * Returns 0 on success, -1 on failure. 235 */ 236int 237checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags, 238 const char *lockname, const char *author, const char *state, 239 const char *date) 240{ 241 BUF *bp; 242 u_int i; 243 int lcount; 244 char buf[16]; 245 mode_t mode = 0444; 246 struct stat st; 247 struct rcs_delta *rdp; 248 struct rcs_lock *lkp; 249 char *content, msg[128], *fdate; 250 time_t rcsdate, givendate; 251 RCSNUM *rev; 252 253 rcsdate = givendate = -1; 254 if (date != NULL) 255 givendate = rcs_date_parse(date); 256 257 if (file->rf_ndelta == 0) 258 printf("no revisions present; generating empty revision 0.0\n"); 259 260 /* XXX rcsnum_cmp() 261 * Check out the latest revision if <frev> is greater than HEAD 262 */ 263 if (file->rf_ndelta != 0) { 264 for (i = 0; i < file->rf_head->rn_len; i++) { 265 if (file->rf_head->rn_id[i] < frev->rn_id[i]) { 266 frev = file->rf_head; 267 break; 268 } 269 } 270 } 271 272 lcount = 0; 273 TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) { 274 if (!strcmp(lkp->rl_name, lockname)) 275 lcount++; 276 } 277 278 /* 279 * If the user didn't specify any revision, we cycle through 280 * revisions to lookup the first one that matches what he specified. 281 * 282 * If we cannot find one, we return an error. 283 */ 284 rdp = NULL; 285 if (file->rf_ndelta != 0 && frev == file->rf_head) { 286 if (lcount > 1) { 287 warnx("multiple revisions locked by %s; " 288 "please specify one", lockname); 289 return (-1); 290 } 291 292 TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) { 293 if (date != NULL) { 294 fdate = asctime(&rdp->rd_date); 295 rcsdate = rcs_date_parse(fdate); 296 if (givendate <= rcsdate) 297 continue; 298 } 299 300 if (author != NULL && 301 strcmp(rdp->rd_author, author)) 302 continue; 303 304 if (state != NULL && 305 strcmp(rdp->rd_state, state)) 306 continue; 307 308 frev = rdp->rd_num; 309 break; 310 } 311 } else if (file->rf_ndelta != 0) { 312 rdp = rcs_findrev(file, frev); 313 } 314 315 if (file->rf_ndelta != 0 && rdp == NULL) { 316 checkout_err_nobranch(file, author, date, state, flags); 317 return (-1); 318 } 319 320 if (file->rf_ndelta == 0) 321 rev = frev; 322 else 323 rev = rdp->rd_num; 324 325 rcsnum_tostr(rev, buf, sizeof(buf)); 326 327 if (file->rf_ndelta != 0 && rdp->rd_locker != NULL) { 328 if (strcmp(lockname, rdp->rd_locker)) { 329 strlcpy(msg, "Revision %s is already locked by %s; ", 330 sizeof(msg)); 331 332 if (flags & CO_UNLOCK) { 333 strlcat(msg, "use co -r or rcs -u", 334 sizeof(msg)); 335 } 336 337 warnx(msg, buf, rdp->rd_locker); 338 return (-1); 339 } 340 } 341 342 if (!(flags & QUIET) && !(flags & NEWFILE) && 343 !(flags & CO_REVERT) && file->rf_ndelta != 0) 344 printf("revision %s", buf); 345 346 if (!(flags & QUIET) && (flags & CO_REVERT)) 347 printf("done"); 348 349 if (file->rf_ndelta != 0) { 350 if ((bp = rcs_getrev(file, rev)) == NULL) { 351 warnx("cannot find revision `%s'", buf); 352 return (-1); 353 } 354 } else { 355 bp = rcs_buf_alloc(1, 0); 356 } 357 358 /* 359 * Do keyword expansion if required. 360 */ 361 if (file->rf_ndelta != 0) 362 bp = rcs_kwexp_buf(bp, file, rev); 363 364 /* 365 * File inherits permissions from its ,v file 366 */ 367 if (stat(file->rf_path, &st) == -1) 368 err(1, "%s", file->rf_path); 369 370 mode = st.st_mode; 371 372 if (flags & CO_LOCK) { 373 if (file->rf_ndelta != 0) { 374 if (lockname != NULL && 375 rcs_lock_add(file, lockname, rev) < 0) { 376 if (rcs_errno != RCS_ERR_DUPENT) 377 return (-1); 378 } 379 } 380 381 /* Strip all write bits from mode */ 382 mode = st.st_mode & 383 (S_IXUSR|S_IXGRP|S_IXOTH|S_IRUSR|S_IRGRP|S_IROTH); 384 mode |= S_IWUSR; 385 386 if (file->rf_ndelta != 0) { 387 if (!(flags & QUIET) && !(flags & NEWFILE) && 388 !(flags & CO_REVERT)) 389 printf(" (locked)"); 390 } 391 } else if (flags & CO_UNLOCK) { 392 if (file->rf_ndelta != 0) { 393 if (rcs_lock_remove(file, lockname, rev) < 0) { 394 if (rcs_errno != RCS_ERR_NOENT) 395 return (-1); 396 } 397 } 398 399 /* Strip all write bits from mode */ 400 mode = st.st_mode & 401 (S_IXUSR|S_IXGRP|S_IXOTH|S_IRUSR|S_IRGRP|S_IROTH); 402 403 if (file->rf_ndelta != 0) { 404 if (!(flags & QUIET) && !(flags & NEWFILE) && 405 !(flags & CO_REVERT)) 406 printf(" (unlocked)"); 407 } 408 } 409 410 if (file->rf_ndelta == 0 && 411 ((flags & CO_LOCK) || (flags & CO_UNLOCK))) { 412 warnx("no revisions, so nothing can be %s", 413 (flags & CO_LOCK) ? "locked" : "unlocked"); 414 } else if (file->rf_ndelta != 0) { 415 if (!(flags & QUIET) && !(flags & NEWFILE)) 416 printf("\n"); 417 } 418 419 if (flags & CO_LOCK) { 420 if (rcs_errno != RCS_ERR_DUPENT) 421 lcount++; 422 if (!(flags & QUIET) && lcount > 1 && !(flags & CO_REVERT)) 423 warnx("%s: warning: You now have %d locks.", 424 file->rf_path, lcount); 425 } 426 427 if (!(flags & PIPEOUT) && stat(dst, &st) == 0 && !(flags & FORCE)) { 428 /* 429 * XXX - Not sure what is "right". If we go according 430 * to GNU's behavior, an existing file with no writable 431 * bits is overwritten without prompting the user. 432 * 433 * This is dangerous, so we always prompt. 434 * Unfortunately this interferes with an unlocked 435 * checkout followed by a locked checkout, which should 436 * not prompt. One (unimplemented) solution is to check 437 * if the existing file is the same as the checked out 438 * revision, and prompt if there are differences. 439 */ 440 if (st.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) 441 printf("writable "); 442 printf("%s exists%s; ", dst, 443 (getuid() == st.st_uid) ? "" : 444 ", and you do not own it"); 445 printf("remove it? [ny](n): "); 446 /* default is n */ 447 if (rcs_yesno() == -1) { 448 if (!(flags & QUIET) && isatty(STDIN_FILENO)) 449 warnx("writable %s exists; " 450 "checkout aborted", dst); 451 else 452 warnx("checkout aborted"); 453 return (-1); 454 } 455 } 456 457 if (flags & PIPEOUT) { 458 rcs_buf_putc(bp, '\0'); 459 content = rcs_buf_release(bp); 460 printf("%s", content); 461 xfree(content); 462 } else { 463 if (rcs_buf_write(bp, dst, mode) < 0) { 464 warnx("failed to write revision to file"); 465 rcs_buf_free(bp); 466 return (-1); 467 } 468 rcs_buf_free(bp); 469 if (flags & CO_REVDATE) { 470 struct timeval tv[2]; 471 memset(&tv, 0, sizeof(tv)); 472 tv[0].tv_sec = (long)rcs_rev_getdate(file, rev); 473 tv[1].tv_sec = tv[0].tv_sec; 474 if (utimes(dst, (const struct timeval *)&tv) < 0) 475 warn("utimes"); 476 } 477 } 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