1 /* $OpenBSD: checkout.c,v 1.172 2020/10/19 19:51:20 naddy Exp $ */ 2 /* 3 * Copyright (c) 2006 Joris Vink <joris@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <sys/types.h> 19 #include <sys/dirent.h> 20 #include <sys/stat.h> 21 #include <sys/time.h> 22 23 #include <errno.h> 24 #include <fcntl.h> 25 #include <libgen.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <time.h> 29 #include <unistd.h> 30 31 #include "cvs.h" 32 #include "diff.h" 33 #include "remote.h" 34 35 static void checkout_check_repository(int, char **); 36 static int checkout_classify(const char *, const char *); 37 static void checkout_repository(const char *, const char *); 38 39 extern int print_stdout; 40 extern int prune_dirs; 41 extern int build_dirs; 42 43 static int flags = CR_REPO | CR_RECURSE_DIRS; 44 static int Aflag = 0; 45 static char *dflag = NULL; 46 static char *koptstr = NULL; 47 static char *dateflag = NULL; 48 49 static int nflag = 0; 50 51 char *checkout_target_dir = NULL; 52 53 time_t cvs_specified_date = -1; 54 time_t cvs_directory_date = -1; 55 int disable_fast_checkout = 0; 56 57 struct cvs_cmd cvs_cmd_checkout = { 58 CVS_OP_CHECKOUT, CVS_USE_WDIR, "checkout", 59 { "co", "get" }, 60 "Checkout a working copy of a repository", 61 "[-AcflNnPpRs] [-D date | -r tag] [-d dir] [-j rev] [-k mode] " 62 "[-t id] module ...", 63 "AcD:d:fj:k:lNnPpRr:st:", 64 NULL, 65 cvs_checkout 66 }; 67 68 struct cvs_cmd cvs_cmd_export = { 69 CVS_OP_EXPORT, CVS_USE_WDIR, "export", 70 { "exp", "ex" }, 71 "Export sources from CVS, similar to checkout", 72 "[-flNnR] [-d dir] [-k mode] -D date | -r rev module ...", 73 "D:d:k:flNnRr:", 74 NULL, 75 cvs_export 76 }; 77 78 int 79 cvs_checkout(int argc, char **argv) 80 { 81 int ch; 82 83 while ((ch = getopt(argc, argv, cvs_cmd_checkout.cmd_opts)) != -1) { 84 switch (ch) { 85 case 'A': 86 Aflag = 1; 87 if (koptstr == NULL) 88 reset_option = 1; 89 if (cvs_specified_tag == NULL) 90 reset_tag = 1; 91 break; 92 case 'c': 93 cvs_modules_list(); 94 exit(0); 95 case 'D': 96 dateflag = optarg; 97 if ((cvs_specified_date = date_parse(dateflag)) == -1) 98 fatal("invalid date: %s", dateflag); 99 reset_tag = 0; 100 break; 101 case 'd': 102 if (dflag != NULL) 103 fatal("-d specified two or more times"); 104 dflag = optarg; 105 checkout_target_dir = dflag; 106 107 if (cvs_server_active == 1) 108 disable_fast_checkout = 1; 109 break; 110 case 'j': 111 if (cvs_join_rev1 == NULL) 112 cvs_join_rev1 = optarg; 113 else if (cvs_join_rev2 == NULL) 114 cvs_join_rev2 = optarg; 115 else 116 fatal("too many -j options"); 117 break; 118 case 'k': 119 reset_option = 0; 120 koptstr = optarg; 121 kflag = rcs_kflag_get(koptstr); 122 if (RCS_KWEXP_INVAL(kflag)) { 123 cvs_log(LP_ERR, 124 "invalid RCS keyword expansion mode"); 125 fatal("%s", cvs_cmd_checkout.cmd_synopsis); 126 } 127 break; 128 case 'l': 129 flags &= ~CR_RECURSE_DIRS; 130 break; 131 case 'N': 132 break; 133 case 'n': 134 nflag = 1; 135 break; 136 case 'P': 137 prune_dirs = 1; 138 break; 139 case 'p': 140 cmdp->cmd_flags &= ~CVS_USE_WDIR; 141 print_stdout = 1; 142 cvs_noexec = 1; 143 nflag = 1; 144 break; 145 case 'R': 146 flags |= CR_RECURSE_DIRS; 147 break; 148 case 'r': 149 reset_tag = 0; 150 cvs_specified_tag = optarg; 151 break; 152 default: 153 fatal("%s", cvs_cmd_checkout.cmd_synopsis); 154 } 155 } 156 157 argc -= optind; 158 argv += optind; 159 160 if (argc == 0) 161 fatal("%s", cvs_cmd_checkout.cmd_synopsis); 162 163 if (cvs_server_active == 1 && disable_fast_checkout != 1) { 164 cmdp->cmd_flags &= ~CVS_USE_WDIR; 165 cvs_noexec = 1; 166 } 167 168 checkout_check_repository(argc, argv); 169 170 if (cvs_server_active == 1 && disable_fast_checkout != 1) 171 cvs_noexec = 0; 172 173 return (0); 174 } 175 176 int 177 cvs_export(int argc, char **argv) 178 { 179 int ch; 180 181 prune_dirs = 1; 182 183 while ((ch = getopt(argc, argv, cvs_cmd_export.cmd_opts)) != -1) { 184 switch (ch) { 185 case 'd': 186 if (dflag != NULL) 187 fatal("-d specified two or more times"); 188 dflag = optarg; 189 checkout_target_dir = dflag; 190 191 if (cvs_server_active == 1) 192 disable_fast_checkout = 1; 193 break; 194 case 'k': 195 koptstr = optarg; 196 kflag = rcs_kflag_get(koptstr); 197 if (RCS_KWEXP_INVAL(kflag)) { 198 cvs_log(LP_ERR, 199 "invalid RCS keyword expansion mode"); 200 fatal("%s", cvs_cmd_export.cmd_synopsis); 201 } 202 break; 203 case 'l': 204 flags &= ~CR_RECURSE_DIRS; 205 break; 206 case 'N': 207 break; 208 case 'R': 209 flags |= CR_RECURSE_DIRS; 210 break; 211 case 'r': 212 cvs_specified_tag = optarg; 213 break; 214 default: 215 fatal("%s", cvs_cmd_export.cmd_synopsis); 216 } 217 } 218 219 argc -= optind; 220 argv += optind; 221 222 if (cvs_specified_tag == NULL) 223 fatal("must specify a tag or date"); 224 225 if (argc == 0) 226 fatal("%s", cvs_cmd_export.cmd_synopsis); 227 228 checkout_check_repository(argc, argv); 229 230 return (0); 231 } 232 233 static void 234 checkout_check_repository(int argc, char **argv) 235 { 236 int i; 237 char *wdir, *d; 238 struct cvs_recursion cr; 239 struct module_checkout *mc; 240 struct cvs_ignpat *ip; 241 struct cvs_filelist *fl, *nxt; 242 char repo[PATH_MAX], fpath[PATH_MAX], path[PATH_MAX], *f[1]; 243 244 build_dirs = print_stdout ? 0 : 1; 245 246 if (cvsroot_is_remote()) { 247 cvs_client_connect_to_server(); 248 249 if (cvs_specified_tag != NULL) 250 cvs_client_send_request("Argument -r%s", 251 cvs_specified_tag); 252 if (Aflag) 253 cvs_client_send_request("Argument -A"); 254 255 if (dateflag != NULL) 256 cvs_client_send_request("Argument -D%s", dateflag); 257 258 if (kflag) 259 cvs_client_send_request("Argument -k%s", koptstr); 260 261 if (dflag != NULL) 262 cvs_client_send_request("Argument -d%s", dflag); 263 264 if (!(flags & CR_RECURSE_DIRS)) 265 cvs_client_send_request("Argument -l"); 266 267 if (cvs_cmdop == CVS_OP_CHECKOUT && prune_dirs == 1) 268 cvs_client_send_request("Argument -P"); 269 270 if (print_stdout == 1) 271 cvs_client_send_request("Argument -p"); 272 273 if (nflag == 1) 274 cvs_client_send_request("Argument -n"); 275 276 cr.enterdir = NULL; 277 cr.leavedir = NULL; 278 if (print_stdout) 279 cr.fileproc = NULL; 280 else 281 cr.fileproc = cvs_client_sendfile; 282 283 flags &= ~CR_REPO; 284 cr.flags = flags; 285 286 if (cvs_cmdop != CVS_OP_EXPORT) 287 cvs_file_run(argc, argv, &cr); 288 289 cvs_client_send_files(argv, argc); 290 cvs_client_senddir("."); 291 292 cvs_client_send_request("%s", 293 (cvs_cmdop == CVS_OP_CHECKOUT) ? "co" : "export"); 294 295 cvs_client_get_responses(); 296 297 return; 298 } 299 300 for (i = 0; i < argc; i++) { 301 mc = cvs_module_lookup(argv[i]); 302 current_module = mc; 303 304 RB_FOREACH(fl, cvs_flisthead, &(mc->mc_ignores)) 305 cvs_file_ignore(fl->file_path, &checkout_ign_pats); 306 307 RB_FOREACH(fl, cvs_flisthead, &(mc->mc_modules)) { 308 module_repo_root = NULL; 309 310 (void)xsnprintf(repo, sizeof(repo), "%s/%s", 311 current_cvsroot->cr_dir, fl->file_path); 312 313 if (!(mc->mc_flags & MODULE_ALIAS) || dflag != NULL) 314 module_repo_root = xstrdup(fl->file_path); 315 316 if (mc->mc_flags & MODULE_NORECURSE) 317 flags &= ~CR_RECURSE_DIRS; 318 319 if (dflag != NULL) 320 wdir = dflag; 321 else if (mc->mc_flags & MODULE_ALIAS) 322 wdir = fl->file_path; 323 else 324 wdir = mc->mc_name; 325 326 switch (checkout_classify(repo, fl->file_path)) { 327 case CVS_FILE: 328 cr.fileproc = cvs_update_local; 329 cr.flags = flags; 330 331 if (!(mc->mc_flags & MODULE_ALIAS)) { 332 if (strlcpy(path, fl->file_path, 333 sizeof(path)) >= sizeof(path)) 334 fatal("%s: truncation", 335 __func__); 336 module_repo_root = 337 xstrdup(dirname(path)); 338 d = wdir; 339 if (strlcpy(path, fl->file_path, 340 sizeof(path)) >= sizeof(path)) 341 fatal("%s: truncation", 342 __func__); 343 (void)xsnprintf(fpath, sizeof(fpath), 344 "%s/%s", d, basename(path)); 345 } else { 346 if (strlcpy(path, wdir, 347 sizeof(path)) >= sizeof(path)) 348 fatal("%s: truncation", 349 __func__); 350 d = dirname(path); 351 strlcpy(fpath, fl->file_path, 352 sizeof(fpath)); 353 } 354 355 if (build_dirs == 1) 356 cvs_mkpath(d, cvs_specified_tag); 357 358 f[0] = fpath; 359 cvs_file_run(1, f, &cr); 360 break; 361 case CVS_DIR: 362 if (build_dirs == 1) 363 cvs_mkpath(wdir, cvs_specified_tag); 364 checkout_repository(repo, wdir); 365 break; 366 default: 367 break; 368 } 369 370 if (nflag != 1 && mc->mc_prog != NULL && 371 mc->mc_flags & MODULE_RUN_ON_CHECKOUT) 372 cvs_exec(mc->mc_prog, NULL, 0); 373 374 free(module_repo_root); 375 } 376 377 if (mc->mc_canfree == 1) { 378 for (fl = RB_MIN(cvs_flisthead, &(mc->mc_modules)); 379 fl != NULL; fl = nxt) { 380 nxt = RB_NEXT(cvs_flisthead, 381 &(mc->mc_modules), fl); 382 RB_REMOVE(cvs_flisthead, 383 &(mc->mc_modules), fl); 384 free(fl->file_path); 385 free(fl); 386 } 387 } 388 389 while ((ip = TAILQ_FIRST(&checkout_ign_pats)) != NULL) { 390 TAILQ_REMOVE(&checkout_ign_pats, ip, ip_list); 391 free(ip); 392 } 393 394 free(mc); 395 } 396 } 397 398 static int 399 checkout_classify(const char *repo, const char *arg) 400 { 401 char *d, dbuf[PATH_MAX], *f, fbuf[PATH_MAX], fpath[PATH_MAX]; 402 struct stat sb; 403 404 if (stat(repo, &sb) == 0) { 405 if (S_ISDIR(sb.st_mode)) 406 return CVS_DIR; 407 } 408 409 if (strlcpy(dbuf, repo, sizeof(dbuf)) >= sizeof(dbuf)) 410 fatal("checkout_classify: truncation"); 411 d = dirname(dbuf); 412 413 if (strlcpy(fbuf, repo, sizeof(fbuf)) >= sizeof(fbuf)) 414 fatal("checkout_classify: truncation"); 415 f = basename(fbuf); 416 417 (void)xsnprintf(fpath, sizeof(fpath), "%s/%s%s", d, f, RCS_FILE_EXT); 418 if (stat(fpath, &sb) == 0) { 419 if (!S_ISREG(sb.st_mode)) { 420 cvs_log(LP_ERR, "ignoring %s: not a regular file", arg); 421 return 0; 422 } 423 return CVS_FILE; 424 } 425 426 (void)xsnprintf(fpath, sizeof(fpath), "%s/%s/%s%s", 427 d, CVS_PATH_ATTIC, f, RCS_FILE_EXT); 428 if (stat(fpath, &sb) == 0) { 429 if (!S_ISREG(sb.st_mode)) { 430 cvs_log(LP_ERR, "ignoring %s: not a regular file", arg); 431 return 0; 432 } 433 return CVS_FILE; 434 } 435 436 cvs_log(LP_ERR, "cannot find module `%s' - ignored", arg); 437 return 0; 438 } 439 440 static void 441 checkout_repository(const char *repobase, const char *wdbase) 442 { 443 struct cvs_flisthead fl, dl; 444 struct cvs_recursion cr; 445 446 RB_INIT(&fl); 447 RB_INIT(&dl); 448 449 cvs_history_add((cvs_cmdop == CVS_OP_CHECKOUT) ? 450 CVS_HISTORY_CHECKOUT : CVS_HISTORY_EXPORT, NULL, wdbase); 451 452 if (print_stdout) { 453 cr.enterdir = NULL; 454 cr.leavedir = NULL; 455 } else { 456 cr.enterdir = cvs_update_enterdir; 457 if (cvs_server_active == 1) { 458 if (disable_fast_checkout != 1) 459 cr.leavedir = NULL; 460 else 461 cr.leavedir = cvs_update_leavedir; 462 } else { 463 cr.leavedir = prune_dirs ? cvs_update_leavedir : NULL; 464 } 465 } 466 cr.fileproc = cvs_update_local; 467 cr.flags = flags; 468 469 cvs_repository_lock(repobase, 0); 470 cvs_repository_getdir(repobase, wdbase, &fl, &dl, 471 flags & CR_RECURSE_DIRS ? REPOSITORY_DODIRS : 0); 472 473 cvs_file_walklist(&fl, &cr); 474 cvs_file_freelist(&fl); 475 476 cvs_repository_unlock(repobase); 477 478 cvs_file_walklist(&dl, &cr); 479 cvs_file_freelist(&dl); 480 } 481 482 void 483 cvs_checkout_file(struct cvs_file *cf, RCSNUM *rnum, char *tag, int co_flags) 484 { 485 BUF *bp; 486 mode_t mode; 487 int cf_kflag, exists; 488 time_t rcstime; 489 CVSENTRIES *ent; 490 struct timeval tv[2]; 491 struct tm datetm; 492 char *entry, *tosend; 493 char kbuf[8], sticky[CVS_REV_BUFSZ], rev[CVS_REV_BUFSZ]; 494 char timebuf[CVS_TIME_BUFSZ], tbuf[CVS_TIME_BUFSZ]; 495 static char lastwd[PATH_MAX]; 496 497 exists = 0; 498 tosend = NULL; 499 500 if (!(co_flags & CO_REMOVE)) 501 rcsnum_tostr(rnum, rev, sizeof(rev)); 502 else 503 rev[0] = '\0'; 504 505 cvs_log(LP_TRACE, "cvs_checkout_file(%s, %s, %d) -> %s", 506 cf->file_path, rev, co_flags, 507 (cvs_server_active) ? "to client" : "to disk"); 508 509 if (co_flags & CO_DUMP) { 510 rcs_rev_write_fd(cf->file_rcs, rnum, STDOUT_FILENO, 0); 511 return; 512 } 513 514 if (cvs_server_active == 0) { 515 (void)unlink(cf->file_path); 516 517 if (!(co_flags & CO_MERGE)) { 518 if (cf->file_flags & FILE_ON_DISK) { 519 exists = 1; 520 (void)close(cf->fd); 521 } 522 523 cf->fd = open(cf->file_path, 524 O_CREAT | O_RDWR | O_TRUNC); 525 if (cf->fd == -1) 526 fatal("cvs_checkout_file: open: %s", 527 strerror(errno)); 528 529 rcs_rev_write_fd(cf->file_rcs, rnum, cf->fd, 0); 530 cf->file_flags |= FILE_ON_DISK; 531 } else { 532 cvs_merge_file(cf, (cvs_join_rev1 == NULL)); 533 } 534 535 mode = cf->file_rcs->rf_mode; 536 mode |= S_IWUSR; 537 538 if (fchmod(cf->fd, mode) == -1) 539 fatal("cvs_checkout_file: fchmod: %s", strerror(errno)); 540 541 if ((exists == 0) && (cf->file_ent == NULL) && 542 !(co_flags & CO_MERGE)) 543 rcstime = rcs_rev_getdate(cf->file_rcs, rnum); 544 else 545 time(&rcstime); 546 547 tv[0].tv_sec = rcstime; 548 tv[0].tv_usec = 0; 549 tv[1] = tv[0]; 550 if (futimes(cf->fd, tv) == -1) 551 fatal("cvs_checkout_file: futimes: %s", 552 strerror(errno)); 553 } else { 554 time(&rcstime); 555 } 556 557 gmtime_r(&rcstime, &datetm); 558 asctime_r(&datetm, tbuf); 559 tbuf[strcspn(tbuf, "\n")] = '\0'; 560 561 if (co_flags & CO_MERGE) { 562 (void)xsnprintf(timebuf, sizeof(timebuf), "Result of merge+%s", 563 tbuf); 564 } else { 565 strlcpy(timebuf, tbuf, sizeof(timebuf)); 566 } 567 568 if (reset_tag) { 569 sticky[0] = '\0'; 570 } else if (co_flags & CO_SETSTICKY) 571 if (tag != NULL) 572 (void)xsnprintf(sticky, sizeof(sticky), "T%s", tag); 573 else if (cvs_specified_date != -1) { 574 gmtime_r(&cvs_specified_date, &datetm); 575 (void)strftime(sticky, sizeof(sticky), 576 "D"CVS_DATE_FMT, &datetm); 577 } else if (cvs_directory_date != -1) { 578 gmtime_r(&cvs_directory_date, &datetm); 579 (void)strftime(sticky, sizeof(sticky), 580 "D"CVS_DATE_FMT, &datetm); 581 } else 582 (void)xsnprintf(sticky, sizeof(sticky), "T%s", rev); 583 else if (cf->file_ent != NULL && cf->file_ent->ce_tag != NULL) 584 (void)xsnprintf(sticky, sizeof(sticky), "T%s", 585 cf->file_ent->ce_tag); 586 else 587 sticky[0] = '\0'; 588 589 kbuf[0] = '\0'; 590 if (cf->file_rcs != NULL && cf->file_rcs->rf_expand != NULL) { 591 cf_kflag = rcs_kflag_get(cf->file_rcs->rf_expand); 592 if (kflag || cf_kflag != RCS_KWEXP_DEFAULT) 593 (void)xsnprintf(kbuf, sizeof(kbuf), 594 "-k%s", cf->file_rcs->rf_expand); 595 } else if (!reset_option && cf->file_ent != NULL) { 596 if (cf->file_ent->ce_opts != NULL) 597 strlcpy(kbuf, cf->file_ent->ce_opts, sizeof(kbuf)); 598 } 599 600 entry = xmalloc(CVS_ENT_MAXLINELEN); 601 cvs_ent_line_str(cf->file_name, rev, timebuf, kbuf, sticky, 0, 0, 602 entry, CVS_ENT_MAXLINELEN); 603 604 if (cvs_server_active == 0) { 605 if (!(co_flags & CO_REMOVE) && cvs_cmdop != CVS_OP_EXPORT) { 606 ent = cvs_ent_open(cf->file_wd); 607 cvs_ent_add(ent, entry); 608 cf->file_ent = cvs_ent_parse(entry); 609 } 610 } else { 611 if (co_flags & CO_MERGE) { 612 (void)unlink(cf->file_path); 613 cvs_merge_file(cf, (cvs_join_rev1 == NULL)); 614 tosend = cf->file_path; 615 } 616 617 /* 618 * If this file has a tag, push out the Directory with the 619 * tag to the client. Except when this file was explicitly 620 * specified on the command line. 621 */ 622 if (tag != NULL && strcmp(cf->file_wd, lastwd) && 623 !(cf->file_flags & FILE_USER_SUPPLIED)) { 624 strlcpy(lastwd, cf->file_wd, PATH_MAX); 625 cvs_server_set_sticky(cf->file_wd, sticky); 626 } 627 628 if (co_flags & CO_COMMIT) 629 cvs_server_update_entry("Updated", cf); 630 else if (co_flags & CO_MERGE) 631 cvs_server_update_entry("Merged", cf); 632 else if (co_flags & CO_REMOVE) 633 cvs_server_update_entry("Removed", cf); 634 else 635 cvs_server_update_entry("Updated", cf); 636 637 if (!(co_flags & CO_REMOVE)) { 638 cvs_remote_output(entry); 639 640 if (!(co_flags & CO_MERGE)) { 641 mode = cf->file_rcs->rf_mode; 642 mode |= S_IWUSR; 643 bp = rcs_rev_getbuf(cf->file_rcs, rnum, 0); 644 cvs_remote_send_file_buf(cf->file_path, 645 bp, mode); 646 } else { 647 cvs_remote_send_file(tosend, cf->fd); 648 } 649 } 650 } 651 652 free(entry); 653 } 654