1 /*- 2 * CPDUP.C 3 * 4 * CPDUP <options> source destination 5 * 6 * (c) Copyright 1997-1999 by Matthew Dillon and Dima Ruban. Permission to 7 * use and distribute based on the FreeBSD copyright. Supplied as-is, 8 * USE WITH EXTREME CAUTION. 9 * 10 * This program attempts to duplicate the source onto the destination as 11 * exactly as possible, retaining modify times, flags, perms, uid, and gid. 12 * It can duplicate devices, files (including hardlinks), softlinks, 13 * directories, and so forth. It is recursive by default! The duplication 14 * is inclusive of removal of files/directories on the destination that do 15 * not exist on the source. This program supports a per-directory exception 16 * file called .cpignore, or a user-specified exception file. 17 * 18 * Safety features: 19 * 20 * - does not cross partition boundries on source 21 * - asks for confirmation on deletions unless -i0 is specified 22 * - refuses to replace a destination directory with a source file 23 * unless -s0 is specified. 24 * - terminates on error 25 * 26 * Copying features: 27 * 28 * - does not copy file if mtime, flags, perms, and size match unless 29 * forced 30 * 31 * - copies to temporary and renames-over the original, allowing 32 * you to update live systems 33 * 34 * - copies uid, gid, mtime, perms, flags, softlinks, devices, hardlinks, 35 * and recurses through directories. 36 * 37 * - accesses a per-directory exclusion file, .cpignore, containing 38 * standard wildcarded ( ? / * style, NOT regex) exclusions. 39 * 40 * - tries to play permissions and flags smart in regards to overwriting 41 * schg files and doing related stuff. 42 * 43 * - Can do MD5 consistancy checks 44 * 45 * - Is able to do incremental mirroring/backups via hardlinks from 46 * the 'previous' version (supplied with -H path). 47 * 48 * $DragonFly: src/bin/cpdup/cpdup.c,v 1.32 2008/11/11 04:36:00 dillon Exp $ 49 */ 50 51 /*- 52 * Example: cc -O cpdup.c -o cpdup -lmd 53 * 54 * ".MD5.CHECKSUMS" contains md5 checksumms for the current directory. 55 * This file is stored on the source. 56 */ 57 58 #include "cpdup.h" 59 #include "hclink.h" 60 #include "hcproto.h" 61 62 #define HSIZE 8192 63 #define HMASK (HSIZE-1) 64 #define HLSIZE 8192 65 #define HLMASK (HLSIZE - 1) 66 67 #define GETBUFSIZE 8192 68 #define GETPATHSIZE 2048 69 #define GETLINKSIZE 1024 70 #define GETIOSIZE 65536 71 72 #ifndef _ST_FLAGS_PRESENT_ 73 #define st_flags st_mode 74 #endif 75 76 typedef struct Node { 77 struct Node *no_Next; 78 struct Node *no_HNext; 79 struct stat *no_Stat; 80 int no_Value; 81 char no_Name[4]; 82 } Node; 83 84 typedef struct List { 85 Node li_Node; 86 Node *li_Hash[HSIZE]; 87 } List; 88 89 struct hlink { 90 ino_t ino; 91 ino_t dino; 92 int refs; 93 struct hlink *next; 94 struct hlink *prev; 95 nlink_t nlinked; 96 char name[]; 97 }; 98 99 typedef struct copy_info { 100 char *spath; 101 char *dpath; 102 dev_t sdevNo; 103 dev_t ddevNo; 104 } *copy_info_t; 105 106 static struct hlink *hltable[HLSIZE]; 107 108 static void RemoveRecur(const char *dpath, dev_t devNo, struct stat *dstat); 109 static void InitList(List *list); 110 static void ResetList(List *list); 111 static Node *IterateList(List *list, Node *node, int n); 112 static int AddList(List *list, const char *name, int n, struct stat *st); 113 static int getbool(const char *str); 114 static char *SplitRemote(char **pathp); 115 static int ChgrpAllowed(gid_t g); 116 static int OwnerMatch(struct stat *st1, struct stat *st2); 117 #ifdef _ST_FLAGS_PRESENT_ 118 static int FlagsMatch(struct stat *st1, struct stat *st2); 119 #else 120 #define FlagsMatch(st1, st2) 1 121 #endif 122 static struct hlink *hltlookup(struct stat *); 123 static struct hlink *hltadd(struct stat *, const char *); 124 static char *checkHLPath(struct stat *st, const char *spath, const char *dpath); 125 static int validate_check(const char *spath, const char *dpath); 126 static int shash(const char *s); 127 static void hltdelete(struct hlink *); 128 static void hltsetdino(struct hlink *, ino_t); 129 static int YesNo(const char *path); 130 static int xrename(const char *src, const char *dst, u_long flags); 131 static int xlink(const char *src, const char *dst, u_long flags); 132 static int xremove(struct HostConf *host, const char *path); 133 static int xrmdir(struct HostConf *host, const char *path); 134 static int DoCopy(copy_info_t info, struct stat *stat1, int depth); 135 static int ScanDir(List *list, struct HostConf *host, const char *path, 136 int64_t *CountReadBytes, int n); 137 138 int AskConfirmation = 1; 139 int SafetyOpt = 1; 140 int ForceOpt; 141 int DeviceOpt = 1; 142 int VerboseOpt; 143 int DirShowOpt; 144 int NotForRealOpt; 145 int QuietOpt; 146 int NoRemoveOpt; 147 int UseMD5Opt; 148 int UseFSMIDOpt; 149 int SummaryOpt; 150 int CompressOpt; 151 int SlaveOpt; 152 int ReadOnlyOpt; 153 int ValidateOpt; 154 int ssh_argc; 155 const char *ssh_argv[16]; 156 int DstRootPrivs; 157 158 const char *UseCpFile; 159 const char *MD5CacheFile; 160 const char *FSMIDCacheFile; 161 const char *UseHLPath; 162 163 static int DstBaseLen; 164 static int HardLinkCount; 165 static int GroupCount; 166 static gid_t *GroupList; 167 168 int64_t CountSourceBytes; 169 int64_t CountSourceItems; 170 int64_t CountCopiedItems; 171 int64_t CountSourceReadBytes; 172 int64_t CountTargetReadBytes; 173 int64_t CountWriteBytes; 174 int64_t CountRemovedItems; 175 int64_t CountLinkedItems; 176 177 static struct HostConf SrcHost; 178 static struct HostConf DstHost; 179 180 int 181 main(int ac, char **av) 182 { 183 int i; 184 int opt; 185 char *src = NULL; 186 char *dst = NULL; 187 char *ptr; 188 struct timeval start; 189 struct copy_info info; 190 191 signal(SIGPIPE, SIG_IGN); 192 193 gettimeofday(&start, NULL); 194 opterr = 0; 195 while ((opt = getopt(ac, av, ":CdnF:fH:Ii:j:K:klM:mopqRSs:uVvX:x")) != -1) { 196 switch (opt) { 197 /* TODO: sort the branches */ 198 case 'C': 199 CompressOpt = 1; 200 break; 201 case 'v': 202 ++VerboseOpt; 203 break; 204 case 'd': 205 DirShowOpt = 1; 206 break; 207 case 'n': 208 NotForRealOpt = 1; 209 break; 210 case 'l': 211 setlinebuf(stdout); 212 setlinebuf(stderr); 213 break; 214 case 'V': 215 ++ValidateOpt; 216 break; 217 case 'I': 218 SummaryOpt = 1; 219 break; 220 case 'o': 221 NoRemoveOpt = 1; 222 break; 223 case 'x': 224 UseCpFile = ".cpignore"; 225 break; 226 case 'X': 227 UseCpFile = optarg; 228 break; 229 case 'H': 230 UseHLPath = optarg; 231 break; 232 case 'F': 233 if (ssh_argc >= 16) 234 fatal("too many -F options"); 235 ssh_argv[ssh_argc++] = optarg; 236 break; 237 case 'S': 238 SlaveOpt = 1; 239 break; 240 case 'R': 241 ReadOnlyOpt = 1; 242 break; 243 case 'f': 244 ForceOpt = 1; 245 break; 246 case 'i': 247 AskConfirmation = getbool(optarg); 248 break; 249 case 'j': 250 DeviceOpt = getbool(optarg); 251 break; 252 case 's': 253 SafetyOpt = getbool(optarg); 254 break; 255 case 'q': 256 QuietOpt = 1; 257 break; 258 case 'k': 259 UseFSMIDOpt = 1; 260 FSMIDCacheFile = ".FSMID.CHECK"; 261 break; 262 case 'K': 263 UseFSMIDOpt = 1; 264 FSMIDCacheFile = optarg; 265 break; 266 case 'M': 267 UseMD5Opt = 1; 268 MD5CacheFile = optarg; 269 break; 270 case 'm': 271 UseMD5Opt = 1; 272 MD5CacheFile = ".MD5.CHECKSUMS"; 273 break; 274 case 'u': 275 setvbuf(stdout, NULL, _IOLBF, 0); 276 break; 277 case ':': 278 fatal("missing argument for option: -%c\n", optopt); 279 /* not reached */ 280 break; 281 case '?': 282 fatal("illegal option: -%c\n", optopt); 283 /* not reached */ 284 break; 285 default: 286 fatal(NULL); 287 /* not reached */ 288 break; 289 } 290 } 291 ac -= optind; 292 av += optind; 293 if (ac > 0) 294 src = av[0]; 295 if (ac > 1) 296 dst = av[1]; 297 if (ac > 2) 298 fatal("too many arguments"); 299 300 /* 301 * If we are told to go into slave mode, run the HC protocol 302 */ 303 if (SlaveOpt) { 304 DstRootPrivs = (geteuid() == 0); 305 hc_slave(0, 1); 306 exit(0); 307 } 308 309 /* 310 * Extract the source and/or/neither target [user@]host and 311 * make any required connections. 312 */ 313 if (src && (ptr = SplitRemote(&src)) != NULL) { 314 SrcHost.host = src; 315 src = ptr; 316 if (UseMD5Opt) 317 fatal("The MD5 options are not currently supported for remote sources"); 318 if (hc_connect(&SrcHost, ReadOnlyOpt) < 0) 319 exit(1); 320 } else if (ReadOnlyOpt) 321 fatal("The -R option is only supported for remote sources"); 322 323 if (dst && (ptr = SplitRemote(&dst)) != NULL) { 324 DstHost.host = dst; 325 dst = ptr; 326 if (UseFSMIDOpt) 327 fatal("The FSMID options are not currently supported for remote targets"); 328 if (hc_connect(&DstHost, 0) < 0) 329 exit(1); 330 } 331 332 /* 333 * dst may be NULL only if -m option is specified, 334 * which forces an update of the MD5 checksums 335 */ 336 if (dst == NULL && UseMD5Opt == 0) { 337 fatal(NULL); 338 /* not reached */ 339 } 340 341 if (dst) { 342 DstRootPrivs = (hc_geteuid(&DstHost) == 0); 343 if (!DstRootPrivs) 344 GroupCount = hc_getgroups(&DstHost, &GroupList); 345 } 346 #if 0 347 /* XXXX DEBUG */ 348 fprintf(stderr, "DstRootPrivs == %s\n", DstRootPrivs ? "true" : "false"); 349 fprintf(stderr, "GroupCount == %d\n", GroupCount); 350 for (i = 0; i < GroupCount; i++) 351 fprintf(stderr, "Group[%d] == %d\n", i, GroupList[i]); 352 #endif 353 354 bzero(&info, sizeof(info)); 355 if (dst) { 356 DstBaseLen = strlen(dst); 357 info.spath = src; 358 info.dpath = dst; 359 info.sdevNo = (dev_t)-1; 360 info.ddevNo = (dev_t)-1; 361 i = DoCopy(&info, NULL, -1); 362 } else { 363 info.spath = src; 364 info.dpath = NULL; 365 info.sdevNo = (dev_t)-1; 366 info.ddevNo = (dev_t)-1; 367 i = DoCopy(&info, NULL, -1); 368 } 369 #ifndef NOMD5 370 md5_flush(); 371 #endif 372 fsmid_flush(); 373 374 if (SummaryOpt && i == 0) { 375 double duration; 376 struct timeval end; 377 378 gettimeofday(&end, NULL); 379 #if 0 380 /* don't count stat's in our byte statistics */ 381 CountSourceBytes += sizeof(struct stat) * CountSourceItems; 382 CountSourceReadBytes += sizeof(struct stat) * CountSourceItems; 383 CountWriteBytes += sizeof(struct stat) * CountCopiedItems; 384 CountWriteBytes += sizeof(struct stat) * CountRemovedItems; 385 #endif 386 387 duration = (end.tv_sec - start.tv_sec); 388 duration += (double)(end.tv_usec - start.tv_usec) / 1000000.0; 389 if (duration == 0.0) 390 duration = 1.0; 391 logstd("cpdup completed successfully\n"); 392 logstd("%lld bytes source, %lld src bytes read, %lld tgt bytes read\n" 393 "%lld bytes written (%.1fX speedup)\n", 394 (long long)CountSourceBytes, 395 (long long)CountSourceReadBytes, 396 (long long)CountTargetReadBytes, 397 (long long)CountWriteBytes, 398 ((double)CountSourceBytes * 2.0) / ((double)(CountSourceReadBytes + CountTargetReadBytes + CountWriteBytes))); 399 logstd("%lld source items, %lld items copied, %lld items linked, " 400 "%lld things deleted\n", 401 (long long)CountSourceItems, 402 (long long)CountCopiedItems, 403 (long long)CountLinkedItems, 404 (long long)CountRemovedItems); 405 logstd("%.1f seconds %5d Kbytes/sec synced %5d Kbytes/sec scanned\n", 406 duration, 407 (int)((CountSourceReadBytes + CountTargetReadBytes + CountWriteBytes) / duration / 1024.0), 408 (int)(CountSourceBytes / duration / 1024.0)); 409 } 410 exit((i == 0) ? 0 : 1); 411 } 412 413 static int 414 getbool(const char *str) 415 { 416 if (strcmp(str, "0") == 0) 417 return (0); 418 if (strcmp(str, "1") == 0) 419 return (1); 420 fatal("option requires boolean argument (0 or 1): -%c\n", optopt); 421 /* not reached */ 422 return (0); 423 } 424 425 /* 426 * Check if path specifies a remote path, using the same syntax as scp(1), 427 * i.e. a path is considered remote if the first colon is not preceded by 428 * a slash, so e.g. "./foo:bar" is considered local. 429 * If a remote path is detected, the colon is replaced with a null byte, 430 * and the return value is a pointer to the next character. 431 * Otherwise NULL is returned. 432 * 433 * A path prefix of localhost is the same as a locally specified file or 434 * directory path, but prevents any further interpretation of the path 435 * as being a remote hostname (for paths that have colons in them). 436 */ 437 static char * 438 SplitRemote(char **pathp) 439 { 440 int cindex; 441 char *path = *pathp; 442 443 if (path[(cindex = strcspn(path, ":/"))] == ':') { 444 path[cindex++] = 0; 445 if (strcmp(path, "localhost") != 0) 446 return (path + cindex); 447 *pathp = path + cindex; 448 } 449 return (NULL); 450 } 451 452 /* 453 * Check if group g is in our GroupList. 454 * 455 * Typically the number of groups a user belongs to isn't large 456 * enough to warrant more effort than a simple linear search. 457 * However, we perform an optimization by moving a group to the 458 * top of the list when we have a hit. This assumes that there 459 * isn't much variance in the gids of files that a non-root user 460 * copies. So most of the time the search will terminate on the 461 * first element of the list. 462 */ 463 static int 464 ChgrpAllowed(gid_t g) 465 { 466 int i; 467 468 for (i = 0; i < GroupCount; i++) 469 if (GroupList[i] == g) { 470 if (i > 0) { 471 /* Optimize: Move g to the front of the list. */ 472 for (; i > 0; i--) 473 GroupList[i] = GroupList[i - 1]; 474 GroupList[0] = g; 475 } 476 return (1); 477 } 478 return (0); 479 } 480 481 /* 482 * The following two functions return true if the ownership (UID + GID) 483 * or the flags of two files match, respectively. 484 * 485 * Only perform weak checking if we don't have sufficient privileges on 486 * the target machine, so we don't waste transfers with things that are 487 * bound to fail anyway. 488 */ 489 static int 490 OwnerMatch(struct stat *st1, struct stat *st2) 491 { 492 if (DstRootPrivs) 493 /* Both UID and GID must match. */ 494 return (st1->st_uid == st2->st_uid && st1->st_gid == st2->st_gid); 495 else 496 /* Ignore UID, and also ignore GID if we can't chgrp to that group. */ 497 return (st1->st_gid == st2->st_gid || !ChgrpAllowed(st1->st_gid)); 498 } 499 500 #ifdef _ST_FLAGS_PRESENT_ 501 static int 502 FlagsMatch(struct stat *st1, struct stat *st2) 503 { 504 /* 505 * Ignore UF_ARCHIVE. It gets set automatically by the filesystem, for 506 * filesystems that support it. If the destination filesystem supports it, but 507 * it's cleared on the source file, then multiple invocations of cpdup would 508 * all try to copy the file because the flags wouldn't match. 509 * 510 * When unpriveleged, ignore flags we can't set 511 */ 512 u_long ignored = DstRootPrivs ? 0 : SF_SETTABLE; 513 514 #ifdef UF_ARCHIVE 515 ignored |= UF_ARCHIVE; 516 #endif 517 return (((st1->st_flags ^ st2->st_flags) & ~ignored) == 0); 518 } 519 #endif 520 521 522 static struct hlink * 523 hltlookup(struct stat *stp) 524 { 525 struct hlink *hl; 526 int n; 527 528 n = stp->st_ino & HLMASK; 529 530 for (hl = hltable[n]; hl; hl = hl->next) { 531 if (hl->ino == stp->st_ino) { 532 ++hl->refs; 533 return hl; 534 } 535 } 536 537 return NULL; 538 } 539 540 static struct hlink * 541 hltadd(struct stat *stp, const char *path) 542 { 543 struct hlink *new; 544 int plen = strlen(path); 545 int n; 546 547 new = malloc(offsetof(struct hlink, name[plen + 1])); 548 if (new == NULL) 549 fatal("out of memory"); 550 ++HardLinkCount; 551 552 /* initialize and link the new element into the table */ 553 new->ino = stp->st_ino; 554 new->dino = (ino_t)-1; 555 new->refs = 1; 556 bcopy(path, new->name, plen + 1); 557 new->nlinked = 1; 558 new->prev = NULL; 559 n = stp->st_ino & HLMASK; 560 new->next = hltable[n]; 561 if (hltable[n]) 562 hltable[n]->prev = new; 563 hltable[n] = new; 564 565 return new; 566 } 567 568 static void 569 hltsetdino(struct hlink *hl, ino_t inum) 570 { 571 hl->dino = inum; 572 } 573 574 static void 575 hltdelete(struct hlink *hl) 576 { 577 assert(hl->refs == 1); 578 --hl->refs; 579 if (hl->prev) { 580 if (hl->next) 581 hl->next->prev = hl->prev; 582 hl->prev->next = hl->next; 583 } else { 584 if (hl->next) 585 hl->next->prev = NULL; 586 587 hltable[hl->ino & HLMASK] = hl->next; 588 } 589 --HardLinkCount; 590 free(hl); 591 } 592 593 static void 594 hltrels(struct hlink *hl) 595 { 596 assert(hl->refs == 1); 597 --hl->refs; 598 } 599 600 /* 601 * If UseHLPath is defined check to see if the file in question is 602 * the same as the source file, and if it is return a pointer to the 603 * -H path based file for hardlinking. Else return NULL. 604 */ 605 static char * 606 checkHLPath(struct stat *st1, const char *spath, const char *dpath) 607 { 608 struct stat sthl; 609 char *hpath; 610 int error; 611 612 asprintf(&hpath, "%s%s", UseHLPath, dpath + DstBaseLen); 613 614 /* 615 * stat info matches ? 616 */ 617 if (hc_stat(&DstHost, hpath, &sthl) < 0 || 618 st1->st_size != sthl.st_size || 619 st1->st_mtime != sthl.st_mtime || 620 !OwnerMatch(st1, &sthl) || 621 !FlagsMatch(st1, &sthl) 622 ) { 623 free(hpath); 624 return(NULL); 625 } 626 627 /* 628 * If ForceOpt or ValidateOpt is set we have to compare the files 629 */ 630 if (ForceOpt || ValidateOpt) { 631 error = validate_check(spath, hpath); 632 if (error) { 633 free(hpath); 634 hpath = NULL; 635 } 636 } 637 return(hpath); 638 } 639 640 /* 641 * Return 0 if the contents of the file <spath> matches the contents of 642 * the file <dpath>. 643 */ 644 static int 645 validate_check(const char *spath, const char *dpath) 646 { 647 int error; 648 int fd1; 649 int fd2; 650 651 fd1 = hc_open(&SrcHost, spath, O_RDONLY, 0); 652 fd2 = hc_open(&DstHost, dpath, O_RDONLY, 0); 653 error = -1; 654 655 if (fd1 >= 0 && fd2 >= 0) { 656 int n; 657 int x; 658 char *iobuf1 = malloc(GETIOSIZE); 659 char *iobuf2 = malloc(GETIOSIZE); 660 661 while ((n = hc_read(&SrcHost, fd1, iobuf1, GETIOSIZE)) > 0) { 662 CountSourceReadBytes += n; 663 x = hc_read(&DstHost, fd2, iobuf2, GETIOSIZE); 664 if (x > 0) 665 CountTargetReadBytes += x; 666 if (x != n) 667 break; 668 if (bcmp(iobuf1, iobuf2, n) != 0) 669 break; 670 } 671 free(iobuf1); 672 free(iobuf2); 673 if (n == 0) 674 error = 0; 675 } 676 if (fd1 >= 0) 677 hc_close(&SrcHost, fd1); 678 if (fd2 >= 0) 679 hc_close(&DstHost, fd2); 680 return (error); 681 } 682 683 int 684 DoCopy(copy_info_t info, struct stat *stat1, int depth) 685 { 686 const char *spath = info->spath; 687 const char *dpath = info->dpath; 688 dev_t sdevNo = info->sdevNo; 689 dev_t ddevNo = info->ddevNo; 690 struct stat st1; 691 struct stat st2; 692 unsigned long st2_flags; 693 int r, mres, fres, st2Valid; 694 struct hlink *hln; 695 uint64_t size; 696 697 r = mres = fres = st2Valid = 0; 698 st2_flags = 0; 699 size = 0; 700 hln = NULL; 701 702 if (stat1 == NULL) { 703 if (hc_lstat(&SrcHost, spath, &st1) != 0) { 704 r = 1; 705 goto done; 706 } 707 stat1 = &st1; 708 } 709 #ifdef SF_SNAPSHOT 710 /* skip snapshot files because they're sparse and _huge_ */ 711 if (stat1->st_flags & SF_SNAPSHOT) 712 return(0); 713 #endif 714 st2.st_mode = 0; /* in case lstat fails */ 715 st2.st_flags = 0; /* in case lstat fails */ 716 if (dpath && hc_lstat(&DstHost, dpath, &st2) == 0) { 717 st2Valid = 1; 718 #ifdef _ST_FLAGS_PRESENT_ 719 st2_flags = st2.st_flags; 720 #endif 721 } 722 723 if (S_ISREG(stat1->st_mode)) 724 size = stat1->st_size; 725 726 /* 727 * Handle hardlinks 728 */ 729 730 if (S_ISREG(stat1->st_mode) && stat1->st_nlink > 1 && dpath) { 731 if ((hln = hltlookup(stat1)) != NULL) { 732 hln->nlinked++; 733 734 if (st2Valid) { 735 if (st2.st_ino == hln->dino) { 736 /* 737 * hard link is already correct, nothing to do 738 */ 739 if (VerboseOpt >= 3) 740 logstd("%-32s nochange\n", (dpath) ? dpath : spath); 741 if (hln->nlinked == stat1->st_nlink) { 742 hltdelete(hln); 743 hln = NULL; 744 } 745 CountSourceItems++; 746 r = 0; 747 goto done; 748 } else { 749 /* 750 * hard link is not correct, attempt to unlink it 751 */ 752 if (xremove(&DstHost, dpath) < 0) { 753 logerr("%-32s hardlink: unable to unlink: %s\n", 754 ((dpath) ? dpath : spath), strerror(errno)); 755 hltdelete(hln); 756 hln = NULL; 757 ++r; 758 goto done; 759 } 760 } 761 } 762 763 if (xlink(hln->name, dpath, stat1->st_flags) < 0) { 764 int tryrelink = (errno == EMLINK); 765 logerr("%-32s hardlink: unable to link to %s: %s\n", 766 (dpath ? dpath : spath), hln->name, strerror(errno) 767 ); 768 hltdelete(hln); 769 hln = NULL; 770 if (tryrelink) { 771 logerr("%-20s hardlink: will attempt to copy normally\n", 772 (dpath ? dpath : spath)); 773 goto relink; 774 } 775 ++r; 776 } else { 777 if (hln->nlinked == stat1->st_nlink) { 778 hltdelete(hln); 779 hln = NULL; 780 } 781 if (r == 0) { 782 if (VerboseOpt) { 783 logstd("%-32s hardlink: %s\n", 784 (dpath ? dpath : spath), 785 (st2Valid ? "relinked" : "linked") 786 ); 787 } 788 CountSourceItems++; 789 CountCopiedItems++; 790 r = 0; 791 goto done; 792 } 793 } 794 } else { 795 /* 796 * first instance of hardlink must be copied normally 797 */ 798 relink: 799 hln = hltadd(stat1, dpath); 800 } 801 } 802 803 /* 804 * Do we need to copy the file/dir/link/whatever? Early termination 805 * if we do not. Always redo links. Directories are always traversed 806 * except when the FSMID options are used. 807 * 808 * NOTE: st2Valid is true only if dpath != NULL *and* dpath stats good. 809 */ 810 811 if ( 812 st2Valid 813 && stat1->st_mode == st2.st_mode 814 && FlagsMatch(stat1, &st2) 815 ) { 816 if (S_ISLNK(stat1->st_mode) || S_ISDIR(stat1->st_mode)) { 817 /* 818 * If FSMID tracking is turned on we can avoid recursing through 819 * an entire directory subtree if the FSMID matches. 820 */ 821 #ifdef _ST_FSMID_PRESENT_ 822 if (ForceOpt == 0 && 823 (UseFSMIDOpt && (fres = fsmid_check(stat1->st_fsmid, dpath)) == 0) 824 ) { 825 if (VerboseOpt >= 3) { 826 if (UseFSMIDOpt) /* always true!?! */ 827 logstd("%-32s fsmid-nochange\n", (dpath ? dpath : spath)); 828 else 829 logstd("%-32s nochange\n", (dpath ? dpath : spath)); 830 } 831 r = 0; 832 goto done; 833 } 834 #endif 835 } else { 836 if (ForceOpt == 0 && 837 stat1->st_size == st2.st_size && 838 (ValidateOpt == 2 || stat1->st_mtime == st2.st_mtime) && 839 OwnerMatch(stat1, &st2) 840 #ifndef NOMD5 841 && (UseMD5Opt == 0 || !S_ISREG(stat1->st_mode) || 842 (mres = md5_check(spath, dpath)) == 0) 843 #endif 844 #ifdef _ST_FSMID_PRESENT_ 845 && (UseFSMIDOpt == 0 || 846 (fres = fsmid_check(stat1->st_fsmid, dpath)) == 0) 847 #endif 848 && (ValidateOpt == 0 || !S_ISREG(stat1->st_mode) || 849 validate_check(spath, dpath) == 0) 850 ) { 851 /* 852 * The files are identical, but if we are running as 853 * root we might need to adjust ownership/group/flags. 854 */ 855 int changedown = 0; 856 int changedflags = 0; 857 858 if (hln) 859 hltsetdino(hln, st2.st_ino); 860 861 if (!OwnerMatch(stat1, &st2)) { 862 hc_chown(&DstHost, dpath, stat1->st_uid, stat1->st_gid); 863 changedown = 1; 864 } 865 #ifdef _ST_FLAGS_PRESENT_ 866 if (!FlagsMatch(stat1, &st2)) { 867 hc_chflags(&DstHost, dpath, stat1->st_flags); 868 changedflags = 1; 869 } 870 #endif 871 if (VerboseOpt >= 3) { 872 #ifndef NOMD5 873 if (UseMD5Opt) { 874 logstd("%-32s md5-nochange", 875 (dpath ? dpath : spath)); 876 } else 877 #endif 878 if (UseFSMIDOpt) { 879 logstd("%-32s fsmid-nochange", 880 (dpath ? dpath : spath)); 881 } else if (ValidateOpt) { 882 logstd("%-32s nochange (contents validated)", 883 (dpath ? dpath : spath)); 884 } else { 885 logstd("%-32s nochange", (dpath ? dpath : spath)); 886 } 887 if (changedown) 888 logstd(" (uid/gid differ)"); 889 if (changedflags) 890 logstd(" (flags differ)"); 891 logstd("\n"); 892 } 893 CountSourceBytes += size; 894 CountSourceItems++; 895 r = 0; 896 goto done; 897 } 898 } 899 } 900 if (st2Valid && !S_ISDIR(stat1->st_mode) && S_ISDIR(st2.st_mode)) { 901 if (SafetyOpt) { 902 logerr("%-32s SAFETY - refusing to copy file over directory\n", 903 (dpath ? dpath : spath) 904 ); 905 ++r; /* XXX */ 906 r = 0; 907 goto done; /* continue with the cpdup anyway */ 908 } 909 if (QuietOpt == 0 || AskConfirmation) { 910 logstd("%-32s WARNING: non-directory source will blow away\n" 911 "%-32s preexisting dest directory, continuing anyway!\n", 912 ((dpath) ? dpath : spath), ""); 913 } 914 if (dpath) 915 RemoveRecur(dpath, ddevNo, &st2); 916 st2Valid = 0; 917 } 918 919 /* 920 * The various comparisons failed, copy it. 921 */ 922 if (S_ISDIR(stat1->st_mode)) { 923 int skipdir = 0; 924 925 if (fres < 0) 926 logerr("%-32s/ fsmid-CHECK-FAILED\n", (dpath) ? dpath : spath); 927 928 if (dpath) { 929 if (!st2Valid || S_ISDIR(st2.st_mode) == 0) { 930 if (st2Valid) 931 xremove(&DstHost, dpath); 932 if (hc_mkdir(&DstHost, dpath, stat1->st_mode | 0700) != 0) { 933 logerr("%s: mkdir failed: %s\n", 934 (dpath ? dpath : spath), strerror(errno)); 935 r = 1; 936 skipdir = 1; 937 } 938 if (hc_lstat(&DstHost, dpath, &st2) != 0) { 939 if (NotForRealOpt == 0) 940 logerr("%s: lstat of newly made dir failed: %s\n", 941 (dpath ? dpath : spath), strerror(errno)); 942 st2Valid = 0; 943 r = 1; 944 skipdir = 1; 945 } 946 else { 947 st2Valid = 1; 948 if (!OwnerMatch(stat1, &st2) && 949 hc_chown(&DstHost, dpath, stat1->st_uid, stat1->st_gid) != 0 950 ) { 951 logerr("%s: chown of newly made dir failed: %s\n", 952 (dpath ? dpath : spath), strerror(errno)); 953 r = 1; 954 /* Note that we should not set skipdir = 1 here. */ 955 } 956 } 957 if (VerboseOpt) 958 logstd("%-32s mkdir-ok\n", (dpath ? dpath : spath)); 959 CountCopiedItems++; 960 } else { 961 /* 962 * Directory must be scanable by root for cpdup to 963 * work. We'll fix it later if the directory isn't 964 * supposed to be readable ( which is why we fixup 965 * st2.st_mode to match what we did ). 966 */ 967 if ((st2.st_mode & 0700) != 0700) { 968 hc_chmod(&DstHost, dpath, st2.st_mode | 0700); 969 st2.st_mode |= 0700; 970 } 971 if (VerboseOpt >= 2) 972 logstd("%s\n", dpath ? dpath : spath); 973 } 974 } 975 976 /* 977 * When copying a directory, stop if the source crosses a mount 978 * point. 979 */ 980 if (sdevNo != (dev_t)-1 && stat1->st_dev != sdevNo) 981 skipdir = 1; 982 else 983 sdevNo = stat1->st_dev; 984 985 /* 986 * When copying a directory, stop if the destination crosses 987 * a mount point. 988 * 989 * The target directory will have been created and stat'd 990 * for st2 if it did not previously exist. st2Valid is left 991 * as a flag. If the stat failed st2 will still only have its 992 * default initialization. 993 * 994 * So we simply assume here that the directory is within the 995 * current target mount if we had to create it (aka st2Valid is 0) 996 * and we leave ddevNo alone. 997 */ 998 if (st2Valid) { 999 if (ddevNo != (dev_t)-1 && st2.st_dev != ddevNo) 1000 skipdir = 1; 1001 else 1002 ddevNo = st2.st_dev; 1003 } 1004 1005 if (!skipdir) { 1006 List *list = malloc(sizeof(List)); 1007 Node *node; 1008 1009 if (DirShowOpt) 1010 logstd("Scanning %s ...\n", spath); 1011 InitList(list); 1012 if (ScanDir(list, &SrcHost, spath, &CountSourceReadBytes, 0) == 0) { 1013 node = NULL; 1014 while ((node = IterateList(list, node, 0)) != NULL) { 1015 char *nspath; 1016 char *ndpath = NULL; 1017 1018 nspath = mprintf("%s/%s", spath, node->no_Name); 1019 if (dpath) 1020 ndpath = mprintf("%s/%s", dpath, node->no_Name); 1021 1022 info->spath = nspath; 1023 info->dpath = ndpath; 1024 info->sdevNo = sdevNo; 1025 info->ddevNo = ddevNo; 1026 if (depth < 0) 1027 r += DoCopy(info, node->no_Stat, depth); 1028 else 1029 r += DoCopy(info, node->no_Stat, depth + 1); 1030 free(nspath); 1031 if (ndpath) 1032 free(ndpath); 1033 info->spath = NULL; 1034 info->dpath = NULL; 1035 } 1036 1037 /* 1038 * Remove files/directories from destination that do not appear 1039 * in the source. 1040 */ 1041 if (dpath && ScanDir(list, &DstHost, dpath, 1042 &CountTargetReadBytes, 3) == 0) { 1043 node = NULL; 1044 while ((node = IterateList(list, node, 3)) != NULL) { 1045 /* 1046 * If object does not exist in source or .cpignore 1047 * then recursively remove it. 1048 */ 1049 char *ndpath; 1050 1051 ndpath = mprintf("%s/%s", dpath, node->no_Name); 1052 RemoveRecur(ndpath, ddevNo, node->no_Stat); 1053 free(ndpath); 1054 } 1055 } 1056 } 1057 ResetList(list); 1058 free(list); 1059 } 1060 1061 if (dpath && st2Valid) { 1062 struct timeval tv[2]; 1063 1064 if (ForceOpt || !OwnerMatch(stat1, &st2)) 1065 hc_chown(&DstHost, dpath, stat1->st_uid, stat1->st_gid); 1066 if (stat1->st_mode != st2.st_mode) 1067 hc_chmod(&DstHost, dpath, stat1->st_mode); 1068 #ifdef _ST_FLAGS_PRESENT_ 1069 if (!FlagsMatch(stat1, &st2)) 1070 hc_chflags(&DstHost, dpath, stat1->st_flags); 1071 #endif 1072 if (ForceOpt || stat1->st_mtime != st2.st_mtime) { 1073 bzero(tv, sizeof(tv)); 1074 tv[0].tv_sec = stat1->st_mtime; 1075 tv[1].tv_sec = stat1->st_mtime; 1076 hc_utimes(&DstHost, dpath, tv); 1077 } 1078 } 1079 } else if (dpath == NULL) { 1080 /* 1081 * If dpath is NULL, we are just updating the MD5 1082 */ 1083 #ifndef NOMD5 1084 if (UseMD5Opt && S_ISREG(stat1->st_mode)) { 1085 mres = md5_check(spath, NULL); 1086 1087 if (VerboseOpt > 1) { 1088 if (mres < 0) 1089 logstd("%-32s md5-update\n", (dpath) ? dpath : spath); 1090 else 1091 logstd("%-32s md5-ok\n", (dpath) ? dpath : spath); 1092 } else if (!QuietOpt && mres < 0) { 1093 logstd("%-32s md5-update\n", (dpath) ? dpath : spath); 1094 } 1095 } 1096 #endif 1097 } else if (S_ISREG(stat1->st_mode)) { 1098 char *path; 1099 char *hpath; 1100 int fd1; 1101 int fd2; 1102 1103 if (st2Valid) 1104 path = mprintf("%s.tmp%d", dpath, (int)getpid()); 1105 else 1106 path = mprintf("%s", dpath); 1107 1108 /* 1109 * Handle check failure message. 1110 */ 1111 #ifndef NOMD5 1112 if (mres < 0) 1113 logerr("%-32s md5-CHECK-FAILED\n", (dpath) ? dpath : spath); 1114 else 1115 #endif 1116 if (fres < 0) 1117 logerr("%-32s fsmid-CHECK-FAILED\n", (dpath) ? dpath : spath); 1118 1119 /* 1120 * Not quite ready to do the copy yet. If UseHLPath is defined, 1121 * see if we can hardlink instead. 1122 * 1123 * If we can hardlink, and the target exists, we have to remove it 1124 * first or the hardlink will fail. This can occur in a number of 1125 * situations but most typically when the '-f -H' combination is 1126 * used. 1127 */ 1128 if (UseHLPath && (hpath = checkHLPath(stat1, spath, dpath)) != NULL) { 1129 if (st2Valid) 1130 xremove(&DstHost, dpath); 1131 if (hc_link(&DstHost, hpath, dpath) == 0) { 1132 ++CountLinkedItems; 1133 if (VerboseOpt) { 1134 logstd("%-32s hardlinked(-H)\n", 1135 (dpath ? dpath : spath)); 1136 } 1137 free(hpath); 1138 goto skip_copy; 1139 } 1140 /* 1141 * Shucks, we may have hit a filesystem hard linking limit, 1142 * we have to copy instead. 1143 */ 1144 free(hpath); 1145 } 1146 1147 if ((fd1 = hc_open(&SrcHost, spath, O_RDONLY, 0)) >= 0) { 1148 if ((fd2 = hc_open(&DstHost, path, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) { 1149 /* 1150 * There could be a .tmp file from a previously interrupted 1151 * run, delete and retry. Fail if we still can't get at it. 1152 */ 1153 #ifdef _ST_FLAGS_PRESENT_ 1154 hc_chflags(&DstHost, path, 0); 1155 #endif 1156 hc_remove(&DstHost, path); 1157 fd2 = hc_open(&DstHost, path, O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, 0600); 1158 } 1159 if (fd2 >= 0) { 1160 const char *op; 1161 char *iobuf1 = malloc(GETIOSIZE); 1162 int n; 1163 1164 /* 1165 * Matt: What about holes? 1166 */ 1167 op = "read"; 1168 while ((n = hc_read(&SrcHost, fd1, iobuf1, GETIOSIZE)) > 0) { 1169 op = "write"; 1170 if (hc_write(&DstHost, fd2, iobuf1, n) != n) 1171 break; 1172 op = "read"; 1173 } 1174 hc_close(&DstHost, fd2); 1175 if (n == 0) { 1176 struct timeval tv[2]; 1177 1178 bzero(tv, sizeof(tv)); 1179 tv[0].tv_sec = stat1->st_mtime; 1180 tv[1].tv_sec = stat1->st_mtime; 1181 1182 if (DstRootPrivs || ChgrpAllowed(stat1->st_gid)) 1183 hc_chown(&DstHost, path, stat1->st_uid, stat1->st_gid); 1184 hc_chmod(&DstHost, path, stat1->st_mode); 1185 #ifdef _ST_FLAGS_PRESENT_ 1186 if (stat1->st_flags & (UF_IMMUTABLE|SF_IMMUTABLE)) 1187 hc_utimes(&DstHost, path, tv); 1188 #else 1189 hc_utimes(&DstHost, path, tv); 1190 #endif 1191 if (st2Valid && xrename(path, dpath, st2_flags) != 0) { 1192 logerr("%-32s rename-after-copy failed: %s\n", 1193 (dpath ? dpath : spath), strerror(errno) 1194 ); 1195 ++r; 1196 } else { 1197 if (VerboseOpt) 1198 logstd("%-32s copy-ok\n", (dpath ? dpath : spath)); 1199 #ifdef _ST_FLAGS_PRESENT_ 1200 if (DstRootPrivs ? stat1->st_flags : stat1->st_flags & UF_SETTABLE) 1201 hc_chflags(&DstHost, dpath, stat1->st_flags); 1202 #endif 1203 } 1204 #ifdef _ST_FLAGS_PRESENT_ 1205 if ((stat1->st_flags & (UF_IMMUTABLE|SF_IMMUTABLE)) == 0) 1206 hc_utimes(&DstHost, dpath, tv); 1207 #endif 1208 CountSourceReadBytes += size; 1209 CountWriteBytes += size; 1210 CountSourceBytes += size; 1211 CountSourceItems++; 1212 CountCopiedItems++; 1213 } else { 1214 logerr("%-32s %s failed: %s\n", 1215 (dpath ? dpath : spath), op, strerror(errno) 1216 ); 1217 hc_remove(&DstHost, path); 1218 ++r; 1219 } 1220 free(iobuf1); 1221 } else { 1222 logerr("%-32s create (uid %d, euid %d) failed: %s\n", 1223 (dpath ? dpath : spath), getuid(), geteuid(), 1224 strerror(errno) 1225 ); 1226 ++r; 1227 } 1228 hc_close(&SrcHost, fd1); 1229 } else { 1230 logerr("%-32s copy: open failed: %s\n", 1231 (dpath ? dpath : spath), 1232 strerror(errno) 1233 ); 1234 ++r; 1235 } 1236 skip_copy: 1237 free(path); 1238 1239 if (hln) { 1240 if (!r && hc_stat(&DstHost, dpath, &st2) == 0) { 1241 hltsetdino(hln, st2.st_ino); 1242 } else { 1243 hltdelete(hln); 1244 hln = NULL; 1245 } 1246 } 1247 } else if (S_ISLNK(stat1->st_mode)) { 1248 char *link1 = malloc(GETLINKSIZE); 1249 char *link2 = malloc(GETLINKSIZE); 1250 char *path; 1251 int n1; 1252 int n2; 1253 1254 n1 = hc_readlink(&SrcHost, spath, link1, GETLINKSIZE - 1); 1255 if (st2Valid) { 1256 path = mprintf("%s.tmp%d", dpath, (int)getpid()); 1257 n2 = hc_readlink(&DstHost, dpath, link2, GETLINKSIZE - 1); 1258 } else { 1259 path = mprintf("%s", dpath); 1260 n2 = -1; 1261 } 1262 if (n1 >= 0) { 1263 if (ForceOpt || n1 != n2 || bcmp(link1, link2, n1) != 0) { 1264 hc_umask(&DstHost, ~stat1->st_mode); 1265 xremove(&DstHost, path); 1266 link1[n1] = 0; 1267 if (hc_symlink(&DstHost, link1, path) < 0) { 1268 logerr("%-32s symlink (%s->%s) failed: %s\n", 1269 (dpath ? dpath : spath), link1, path, 1270 strerror(errno) 1271 ); 1272 ++r; 1273 } else { 1274 if (DstRootPrivs || ChgrpAllowed(stat1->st_gid)) 1275 hc_lchown(&DstHost, path, stat1->st_uid, stat1->st_gid); 1276 /* 1277 * there is no lchmod() or lchflags(), we 1278 * cannot chmod or chflags a softlink. 1279 */ 1280 if (st2Valid && xrename(path, dpath, st2_flags) != 0) { 1281 logerr("%-32s rename softlink (%s->%s) failed: %s\n", 1282 (dpath ? dpath : spath), 1283 path, dpath, strerror(errno)); 1284 } else if (VerboseOpt) { 1285 logstd("%-32s softlink-ok\n", (dpath ? dpath : spath)); 1286 } 1287 hc_umask(&DstHost, 000); 1288 CountWriteBytes += n1; 1289 CountCopiedItems++; 1290 } 1291 } else { 1292 if (VerboseOpt >= 3) 1293 logstd("%-32s nochange", (dpath ? dpath : spath)); 1294 if (!OwnerMatch(stat1, &st2)) { 1295 hc_lchown(&DstHost, dpath, stat1->st_uid, stat1->st_gid); 1296 if (VerboseOpt >= 3) 1297 logstd(" (uid/gid differ)"); 1298 } 1299 if (VerboseOpt >= 3) 1300 logstd("\n"); 1301 } 1302 CountSourceBytes += n1; 1303 CountSourceReadBytes += n1; 1304 if (n2 > 0) 1305 CountTargetReadBytes += n2; 1306 CountSourceItems++; 1307 } else { 1308 r = 1; 1309 logerr("%-32s softlink-failed\n", (dpath ? dpath : spath)); 1310 } 1311 free(link1); 1312 free(link2); 1313 free(path); 1314 } else if ((S_ISCHR(stat1->st_mode) || S_ISBLK(stat1->st_mode)) && DeviceOpt) { 1315 char *path = NULL; 1316 1317 if (ForceOpt || 1318 st2Valid == 0 || 1319 stat1->st_mode != st2.st_mode || 1320 stat1->st_rdev != st2.st_rdev || 1321 !OwnerMatch(stat1, &st2) 1322 ) { 1323 if (st2Valid) { 1324 path = mprintf("%s.tmp%d", dpath, (int)getpid()); 1325 xremove(&DstHost, path); 1326 } else { 1327 path = mprintf("%s", dpath); 1328 } 1329 1330 if (hc_mknod(&DstHost, path, stat1->st_mode, stat1->st_rdev) == 0) { 1331 hc_chmod(&DstHost, path, stat1->st_mode); 1332 hc_chown(&DstHost, path, stat1->st_uid, stat1->st_gid); 1333 if (st2Valid) 1334 xremove(&DstHost, dpath); 1335 if (st2Valid && xrename(path, dpath, st2_flags) != 0) { 1336 logerr("%-32s dev-rename-after-create failed: %s\n", 1337 (dpath ? dpath : spath), 1338 strerror(errno) 1339 ); 1340 } else if (VerboseOpt) { 1341 logstd("%-32s dev-ok\n", (dpath ? dpath : spath)); 1342 } 1343 CountCopiedItems++; 1344 } else { 1345 r = 1; 1346 logerr("%-32s dev failed: %s\n", 1347 (dpath ? dpath : spath), strerror(errno) 1348 ); 1349 } 1350 } else { 1351 if (VerboseOpt >= 3) 1352 logstd("%-32s nochange\n", (dpath ? dpath : spath)); 1353 } 1354 if (path) 1355 free(path); 1356 CountSourceItems++; 1357 } 1358 done: 1359 if (hln) { 1360 if (hln->dino == (ino_t)-1) { 1361 hltdelete(hln); 1362 /*hln = NULL; unneeded */ 1363 } else { 1364 hltrels(hln); 1365 } 1366 } 1367 return (r); 1368 } 1369 1370 int 1371 ScanDir(List *list, struct HostConf *host, const char *path, 1372 int64_t *CountReadBytes, int n) 1373 { 1374 DIR *dir; 1375 struct HCDirEntry *den; 1376 struct stat *statptr; 1377 1378 if (n == 0) { 1379 /* 1380 * scan .cpignore file for files/directories to ignore 1381 * (only in the source directory, i.e. if n == 0). 1382 */ 1383 if (UseCpFile) { 1384 int fd; 1385 int nread; 1386 int bufused; 1387 char *buf = malloc(GETBUFSIZE); 1388 char *nl, *next; 1389 char *fpath; 1390 1391 if (UseCpFile[0] == '/') { 1392 fpath = mprintf("%s", UseCpFile); 1393 } else { 1394 fpath = mprintf("%s/%s", path, UseCpFile); 1395 } 1396 AddList(list, strrchr(fpath, '/') + 1, 1, NULL); 1397 if ((fd = hc_open(host, fpath, O_RDONLY, 0)) >= 0) { 1398 bufused = 0; 1399 while ((nread = hc_read(host, fd, buf + bufused, 1400 GETBUFSIZE - bufused - 1)) > 0) { 1401 *CountReadBytes += nread; 1402 bufused += nread; 1403 buf[bufused] = 0; 1404 for (next = buf; (nl = strchr(next, '\n')); next = nl+1) { 1405 *nl = 0; 1406 AddList(list, next, 1, NULL); 1407 } 1408 bufused = strlen(next); 1409 if (bufused) 1410 bcopy(next, buf, bufused); 1411 } 1412 if (bufused) { 1413 /* last line has no trailing newline */ 1414 buf[bufused] = 0; 1415 AddList(list, buf, 1, NULL); 1416 } 1417 hc_close(host, fd); 1418 } 1419 free(fpath); 1420 free(buf); 1421 } 1422 1423 /* 1424 * Automatically exclude MD5CacheFile that we create on the 1425 * source from the copy to the destination. 1426 * 1427 * Automatically exclude a FSMIDCacheFile on the source that 1428 * would otherwise overwrite the one we maintain on the target. 1429 */ 1430 if (UseMD5Opt) 1431 AddList(list, MD5CacheFile, 1, NULL); 1432 if (UseFSMIDOpt) 1433 AddList(list, FSMIDCacheFile, 1, NULL); 1434 } 1435 1436 if ((dir = hc_opendir(host, path)) == NULL) 1437 return (1); 1438 while ((den = hc_readdir(host, dir, &statptr)) != NULL) { 1439 /* 1440 * ignore . and .. 1441 */ 1442 if (strcmp(den->d_name, ".") != 0 && strcmp(den->d_name, "..") != 0) 1443 AddList(list, den->d_name, n, statptr); 1444 } 1445 hc_closedir(host, dir); 1446 1447 return (0); 1448 } 1449 1450 /* 1451 * RemoveRecur() 1452 */ 1453 1454 static void 1455 RemoveRecur(const char *dpath, dev_t devNo, struct stat *dstat) 1456 { 1457 struct stat st; 1458 1459 if (dstat == NULL) { 1460 if (hc_lstat(&DstHost, dpath, &st) == 0) 1461 dstat = &st; 1462 } 1463 if (dstat != NULL) { 1464 if (devNo == (dev_t)-1) 1465 devNo = dstat->st_dev; 1466 if (dstat->st_dev == devNo) { 1467 if (S_ISDIR(dstat->st_mode)) { 1468 DIR *dir; 1469 1470 if ((dir = hc_opendir(&DstHost, dpath)) != NULL) { 1471 List *list = malloc(sizeof(List)); 1472 Node *node = NULL; 1473 struct HCDirEntry *den; 1474 1475 InitList(list); 1476 while ((den = hc_readdir(&DstHost, dir, &dstat)) != NULL) { 1477 if (strcmp(den->d_name, ".") == 0) 1478 continue; 1479 if (strcmp(den->d_name, "..") == 0) 1480 continue; 1481 AddList(list, den->d_name, 3, dstat); 1482 } 1483 hc_closedir(&DstHost, dir); 1484 while ((node = IterateList(list, node, 3)) != NULL) { 1485 char *ndpath; 1486 1487 ndpath = mprintf("%s/%s", dpath, node->no_Name); 1488 RemoveRecur(ndpath, devNo, node->no_Stat); 1489 free(ndpath); 1490 } 1491 ResetList(list); 1492 free(list); 1493 } 1494 if (AskConfirmation && NoRemoveOpt == 0) { 1495 if (YesNo(dpath)) { 1496 if (xrmdir(&DstHost, dpath) < 0) { 1497 logerr("%-32s rmdir failed: %s\n", 1498 dpath, strerror(errno) 1499 ); 1500 } 1501 CountRemovedItems++; 1502 } 1503 } else { 1504 if (NoRemoveOpt) { 1505 if (VerboseOpt) 1506 logstd("%-32s not-removed\n", dpath); 1507 } else if (xrmdir(&DstHost, dpath) == 0) { 1508 if (VerboseOpt) 1509 logstd("%-32s rmdir-ok\n", dpath); 1510 CountRemovedItems++; 1511 } else { 1512 logerr("%-32s rmdir failed: %s\n", 1513 dpath, strerror(errno) 1514 ); 1515 } 1516 } 1517 } else { 1518 if (AskConfirmation && NoRemoveOpt == 0) { 1519 if (YesNo(dpath)) { 1520 if (xremove(&DstHost, dpath) < 0) { 1521 logerr("%-32s remove failed: %s\n", 1522 dpath, strerror(errno) 1523 ); 1524 } 1525 CountRemovedItems++; 1526 } 1527 } else { 1528 if (NoRemoveOpt) { 1529 if (VerboseOpt) 1530 logstd("%-32s not-removed\n", dpath); 1531 } else if (xremove(&DstHost, dpath) == 0) { 1532 if (VerboseOpt) 1533 logstd("%-32s remove-ok\n", dpath); 1534 CountRemovedItems++; 1535 } else { 1536 logerr("%-32s remove failed: %s\n", 1537 dpath, strerror(errno) 1538 ); 1539 } 1540 } 1541 } 1542 } 1543 } 1544 } 1545 1546 static void 1547 InitList(List *list) 1548 { 1549 bzero(list, sizeof(List)); 1550 list->li_Node.no_Next = &list->li_Node; 1551 } 1552 1553 static void 1554 ResetList(List *list) 1555 { 1556 Node *node; 1557 1558 while ((node = list->li_Node.no_Next) != &list->li_Node) { 1559 list->li_Node.no_Next = node->no_Next; 1560 if (node->no_Stat != NULL) 1561 free(node->no_Stat); 1562 free(node); 1563 } 1564 InitList(list); 1565 } 1566 1567 static Node * 1568 IterateList(List *list, Node *node, int n) 1569 { 1570 if (node == NULL) 1571 node = list->li_Node.no_Next; 1572 else 1573 node = node->no_Next; 1574 while (node->no_Value != n && node != &list->li_Node) 1575 node = node->no_Next; 1576 return (node == &list->li_Node ? NULL : node); 1577 } 1578 1579 static int 1580 AddList(List *list, const char *name, int n, struct stat *st) 1581 { 1582 Node *node; 1583 int hv; 1584 1585 /* 1586 * Scan against wildcards. Only a node value of 1 can be a wildcard 1587 * ( usually scanned from .cpignore ) 1588 */ 1589 1590 for (node = list->li_Hash[0]; node; node = node->no_HNext) { 1591 if (strcmp(name, node->no_Name) == 0 || 1592 (n != 1 && node->no_Value == 1 && 1593 fnmatch(node->no_Name, name, 0) == 0) 1594 ) { 1595 return(node->no_Value); 1596 } 1597 } 1598 1599 /* 1600 * Look for exact match 1601 */ 1602 1603 hv = shash(name); 1604 for (node = list->li_Hash[hv]; node; node = node->no_HNext) { 1605 if (strcmp(name, node->no_Name) == 0) { 1606 return(node->no_Value); 1607 } 1608 } 1609 node = malloc(sizeof(Node) + strlen(name) + 1); 1610 if (node == NULL) 1611 fatal("out of memory"); 1612 1613 node->no_Next = list->li_Node.no_Next; 1614 list->li_Node.no_Next = node; 1615 1616 node->no_HNext = list->li_Hash[hv]; 1617 list->li_Hash[hv] = node; 1618 1619 strcpy(node->no_Name, name); 1620 node->no_Value = n; 1621 node->no_Stat = st; 1622 1623 return(n); 1624 } 1625 1626 static int 1627 shash(const char *s) 1628 { 1629 int hv; 1630 1631 hv = 0xA4FB3255; 1632 1633 while (*s) { 1634 if (*s == '*' || *s == '?' || 1635 *s == '{' || *s == '}' || 1636 *s == '[' || *s == ']' || 1637 *s == '|' 1638 ) { 1639 return(0); 1640 } 1641 hv = (hv << 5) ^ *s ^ (hv >> 23); 1642 ++s; 1643 } 1644 return(((hv >> 16) ^ hv) & HMASK); 1645 } 1646 1647 static int 1648 YesNo(const char *path) 1649 { 1650 int ch, first; 1651 1652 fprintf(stderr, "remove %s (Yes/No) [No]? ", path); 1653 fflush(stderr); 1654 1655 first = ch = getchar(); 1656 while (ch != '\n' && ch != EOF) 1657 ch = getchar(); 1658 return ((first == 'y' || first == 'Y')); 1659 } 1660 1661 /* 1662 * xrename() - rename with override 1663 * 1664 * If the rename fails, attempt to override st_flags on the 1665 * destination and rename again. If that fails too, try to 1666 * set the flags back the way they were and give up. 1667 */ 1668 1669 static int 1670 xrename(const char *src, const char *dst, u_long flags) 1671 { 1672 int r; 1673 1674 if ((r = hc_rename(&DstHost, src, dst)) < 0) { 1675 #ifdef _ST_FLAGS_PRESENT_ 1676 hc_chflags(&DstHost, dst, 0); 1677 if ((r = hc_rename(&DstHost, src, dst)) < 0) 1678 hc_chflags(&DstHost, dst, flags); 1679 #endif 1680 } 1681 return(r); 1682 } 1683 1684 static int 1685 xlink(const char *src, const char *dst, u_long flags) 1686 { 1687 int r; 1688 #ifdef _ST_FLAGS_PRESENT_ 1689 int e; 1690 #endif 1691 1692 if ((r = hc_link(&DstHost, src, dst)) < 0) { 1693 #ifdef _ST_FLAGS_PRESENT_ 1694 hc_chflags(&DstHost, src, 0); 1695 r = hc_link(&DstHost, src, dst); 1696 e = errno; 1697 hc_chflags(&DstHost, src, flags); 1698 errno = e; 1699 #endif 1700 } 1701 if (r == 0) 1702 ++CountLinkedItems; 1703 return(r); 1704 } 1705 1706 static int 1707 xremove(struct HostConf *host, const char *path) 1708 { 1709 int res; 1710 1711 res = hc_remove(host, path); 1712 #ifdef _ST_FLAGS_PRESENT_ 1713 if (res == -EPERM) { 1714 hc_chflags(host, path, 0); 1715 res = hc_remove(host, path); 1716 } 1717 #endif 1718 return(res); 1719 } 1720 1721 static int 1722 xrmdir(struct HostConf *host, const char *path) 1723 { 1724 int res; 1725 1726 res = hc_rmdir(host, path); 1727 #ifdef _ST_FLAGS_PRESENT_ 1728 if (res == -EPERM) { 1729 hc_chflags(host, path, 0); 1730 res = hc_rmdir(host, path); 1731 } 1732 #endif 1733 return(res); 1734 } 1735