1 /* 2 * Copyright 2005-2009, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 #include <errno.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <string.h> 10 #include <unistd.h> 11 12 #include <Directory.h> 13 #include <Entry.h> 14 #include <File.h> 15 #include <fs_attr.h> 16 #include <Mime.h> 17 #include <Node.h> 18 #include <Path.h> 19 #include <SymLink.h> 20 #include <TypeConstants.h> 21 22 #include <EntryFilter.h> 23 24 25 using BPrivate::EntryFilter; 26 27 28 static const char *kCommandName = "copyattr"; 29 static const int kCopyBufferSize = 64 * 1024; // 64 KB 30 31 static int kArgc; 32 static const char *const *kArgv; 33 34 // usage 35 const char *kUsage = 36 "Usage: %s <options> <source> [ ... ] <destination>\n" 37 "\n" 38 "Copies attributes from one or more files to another, or copies one or more\n" 39 "files or directories, with all or a specified subset of their attributes, to\n" 40 "another location.\n" 41 "\n" 42 "If option \"-d\"/\"--data\" is given, the behavior is similar to \"cp -df\",\n" 43 "save that attributes are copied. That is, if more than one source file is\n" 44 "given, the destination file must be a directory. If the destination is a\n" 45 "directory (or a symlink to a directory), the source files are copied into\n" 46 "the destination directory. Entries that are in the way are removed, unless\n" 47 "they are directories. If the source is a directory too, the attributes will\n" 48 "be copied and, if recursive operation is specified, the program continues\n" 49 "copying the contents of the source directory. If the source is not a\n" 50 "directory the program aborts with an error message.\n" 51 "\n" 52 "If option \"-d\"/\"--data\" is not given, only attributes are copied.\n" 53 "Regardless of the file type of the destination, the attributes of the source\n" 54 "files are copied to it. If an attribute with the same name as one to be\n" 55 "copied already exists, it is replaced. If more than one source file is\n" 56 "specified the semantics are similar to invoking the program multiple times\n" 57 "with the same options and destination and only one source file at a time,\n" 58 "in the order the source files are given. If recursive operation is\n" 59 "specified, the program recursively copies the attributes of the directory\n" 60 "contents; if the destination file is not a directory, or for a source entry\n" 61 "there exists no destination entry, the program aborts with an error\n" 62 "message.\n" 63 "\n" 64 "Note, that the behavior of the program differs from the one shipped with\n" 65 "BeOS R5.\n" 66 "\n" 67 "Options:\n" 68 " -d, --data - Copy the data of the file(s), too.\n" 69 " -h, --help - Print this help text and exit.\n" 70 " -m, --move - If -d is given, the source files are removed after\n" 71 " being copied. Has no effect otherwise.\n" 72 " -n, --name <name> - Only copy the attribute with name <name>.\n" 73 " -r, --recursive - Copy directories recursively.\n" 74 " -t, --type <type> - Copy only the attributes of type <type>. If -n is\n" 75 " specified too, only the attribute matching the name\n" 76 " and the type is copied.\n" 77 " -x <pattern> - Exclude source entries matching <pattern>.\n" 78 " -X <pattern> - Exclude source paths matching <pattern>.\n" 79 " -v, --verbose - Print more messages.\n" 80 " -, -- - Marks the end of options. The arguments after, even\n" 81 " if starting with \"-\" are considered file names.\n" 82 "\n" 83 "Parameters:\n" 84 " <type> - One of: int, llong, string, mimestr, float, double,\n" 85 " boolean.\n" 86 ; 87 88 // supported attribute types 89 struct supported_attribute_type { 90 const char *type_name; 91 type_code type; 92 }; 93 94 const supported_attribute_type kSupportedAttributeTypes[] = { 95 { "int", B_INT32_TYPE }, 96 { "llong", B_INT64_TYPE }, 97 { "string", B_STRING_TYPE }, 98 { "mimestr", B_MIME_STRING_TYPE }, 99 { "float", B_FLOAT_TYPE }, 100 { "double", B_DOUBLE_TYPE }, 101 { "boolean", B_BOOL_TYPE }, 102 { NULL, 0 }, 103 }; 104 105 // AttributeFilter 106 struct AttributeFilter { 107 AttributeFilter() 108 : 109 fName(NULL), 110 fType(B_ANY_TYPE) 111 { 112 } 113 114 void SetTo(const char *name, type_code type) 115 { 116 fName = name; 117 fType = type; 118 } 119 120 bool Filter(const char *name, type_code type) const { 121 if (fName && strcmp(name, fName) != 0) 122 return false; 123 124 return (fType == B_ANY_TYPE || type == fType); 125 } 126 127 private: 128 const char *fName; 129 type_code fType; 130 }; 131 132 133 // Parameters 134 struct Parameters { 135 Parameters() 136 : 137 copy_data(false), 138 recursive(false), 139 move_files(false), 140 verbose(false) 141 { 142 } 143 144 bool copy_data; 145 bool recursive; 146 bool move_files; 147 bool verbose; 148 AttributeFilter attribute_filter; 149 EntryFilter entry_filter; 150 }; 151 152 153 // print_usage 154 static void 155 print_usage(bool error) 156 { 157 // get command name 158 const char *commandName = NULL; 159 if (kArgc > 0) { 160 if (const char *lastSlash = strchr(kArgv[0], '/')) 161 commandName = lastSlash + 1; 162 else 163 commandName = kArgv[0]; 164 } 165 166 if (!commandName || strlen(commandName) == 0) 167 commandName = kCommandName; 168 169 // print usage 170 fprintf((error ? stderr : stdout), kUsage, commandName); 171 } 172 173 174 // print_usage_and_exit 175 static void 176 print_usage_and_exit(bool error) 177 { 178 print_usage(error); 179 exit(error ? 1 : 0); 180 } 181 182 183 // next_arg 184 static const char * 185 next_arg(int &argi, bool optional = false) 186 { 187 if (argi >= kArgc) { 188 if (!optional) 189 print_usage_and_exit(true); 190 return NULL; 191 } 192 193 return kArgv[argi++]; 194 } 195 196 197 // copy_attributes 198 static void 199 copy_attributes(const char *sourcePath, BNode &source, const char *destPath, 200 BNode &destination, const Parameters ¶meters) 201 { 202 char attrName[B_ATTR_NAME_LENGTH]; 203 while (source.GetNextAttrName(attrName) == B_OK) { 204 // get attr info 205 attr_info attrInfo; 206 status_t error = source.GetAttrInfo(attrName, &attrInfo); 207 if (error != B_OK) { 208 fprintf(stderr, "Error: Failed to get info of attribute \"%s\" " 209 "of file \"%s\": %s\n", attrName, sourcePath, strerror(error)); 210 exit(1); 211 } 212 213 // filter 214 if (!parameters.attribute_filter.Filter(attrName, attrInfo.type)) 215 continue; 216 217 // copy the attribute 218 char buffer[kCopyBufferSize]; 219 off_t offset = 0; 220 off_t bytesLeft = attrInfo.size; 221 // go at least once through the loop, so that empty attribute will be 222 // created as well 223 do { 224 size_t toRead = kCopyBufferSize; 225 if (toRead > bytesLeft) 226 toRead = bytesLeft; 227 228 // read 229 ssize_t bytesRead = source.ReadAttr(attrName, attrInfo.type, 230 offset, buffer, toRead); 231 if (bytesRead < 0) { 232 fprintf(stderr, "Error: Failed to read attribute \"%s\" " 233 "of file \"%s\": %s\n", attrName, sourcePath, 234 strerror(bytesRead)); 235 exit(1); 236 } 237 238 // write 239 ssize_t bytesWritten = destination.WriteAttr(attrName, 240 attrInfo.type, offset, buffer, bytesRead); 241 if (bytesWritten < 0) { 242 fprintf(stderr, "Error: Failed to write attribute \"%s\" " 243 "of file \"%s\": %s\n", attrName, destPath, 244 strerror(bytesWritten)); 245 exit(1); 246 } 247 248 bytesLeft -= bytesRead; 249 offset += bytesRead; 250 251 } while (bytesLeft > 0); 252 } 253 } 254 255 256 // copy_file_data 257 static void 258 copy_file_data(const char *sourcePath, BFile &source, const char *destPath, 259 BFile &destination, const Parameters ¶meters) 260 { 261 char buffer[kCopyBufferSize]; 262 off_t offset = 0; 263 while (true) { 264 // read 265 ssize_t bytesRead = source.ReadAt(offset, buffer, sizeof(buffer)); 266 if (bytesRead < 0) { 267 fprintf(stderr, "Error: Failed to read from file \"%s\": %s\n", 268 sourcePath, strerror(bytesRead)); 269 exit(1); 270 } 271 272 if (bytesRead == 0) 273 return; 274 275 // write 276 ssize_t bytesWritten = destination.WriteAt(offset, buffer, bytesRead); 277 if (bytesWritten < 0) { 278 fprintf(stderr, "Error: Failed to write to file \"%s\": %s\n", 279 destPath, strerror(bytesWritten)); 280 exit(1); 281 } 282 283 offset += bytesRead; 284 } 285 } 286 287 288 // copy_entry 289 static void 290 copy_entry(const char *sourcePath, const char *destPath, 291 const Parameters ¶meters) 292 { 293 // apply entry filter 294 if (!parameters.entry_filter.Filter(sourcePath)) 295 return; 296 297 // stat source 298 struct stat sourceStat; 299 if (lstat(sourcePath, &sourceStat) < 0) { 300 fprintf(stderr, "Error: Couldn't access \"%s\": %s\n", sourcePath, 301 strerror(errno)); 302 exit(1); 303 } 304 305 // stat destination 306 struct stat destStat; 307 bool destExists = lstat(destPath, &destStat) == 0; 308 309 if (!destExists && !parameters.copy_data) { 310 fprintf(stderr, "Error: Destination file \"%s\" does not exist.\n", 311 destPath); 312 exit(1); 313 } 314 315 if (parameters.verbose) 316 printf("%s\n", destPath); 317 318 // check whether to delete/create the destination 319 bool unlinkDest = (destExists && parameters.copy_data); 320 bool createDest = parameters.copy_data; 321 if (destExists) { 322 if (S_ISDIR(destStat.st_mode)) { 323 if (S_ISDIR(sourceStat.st_mode)) { 324 // both are dirs; nothing to do 325 unlinkDest = false; 326 createDest = false; 327 } else if (parameters.copy_data || parameters.recursive) { 328 // destination is directory, but source isn't, and mode is 329 // not non-recursive attributes-only copy 330 fprintf(stderr, "Error: Can't copy \"%s\", since directory " 331 "\"%s\" is in the way.\n", sourcePath, destPath); 332 exit(1); 333 } 334 } 335 } 336 337 // unlink the destination 338 if (unlinkDest) { 339 if (unlink(destPath) < 0) { 340 fprintf(stderr, "Error: Failed to unlink \"%s\": %s\n", destPath, 341 strerror(errno)); 342 exit(1); 343 } 344 } 345 346 // open source node 347 BNode _sourceNode; 348 BFile sourceFile; 349 BDirectory sourceDir; 350 BNode *sourceNode = NULL; 351 status_t error; 352 353 if (S_ISDIR(sourceStat.st_mode)) { 354 error = sourceDir.SetTo(sourcePath); 355 sourceNode = &sourceDir; 356 } else if (S_ISREG(sourceStat.st_mode)) { 357 error = sourceFile.SetTo(sourcePath, B_READ_ONLY); 358 sourceNode = &sourceFile; 359 } else { 360 error = _sourceNode.SetTo(sourcePath); 361 sourceNode = &_sourceNode; 362 } 363 364 if (error != B_OK) { 365 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", 366 sourcePath, strerror(error)); 367 exit(1); 368 } 369 370 // create the destination 371 BNode _destNode; 372 BDirectory destDir; 373 BFile destFile; 374 BSymLink destSymLink; 375 BNode *destNode = NULL; 376 377 if (createDest) { 378 if (S_ISDIR(sourceStat.st_mode)) { 379 // create dir 380 error = BDirectory().CreateDirectory(destPath, &destDir); 381 if (error != B_OK) { 382 fprintf(stderr, "Error: Failed to make directory \"%s\": %s\n", 383 destPath, strerror(error)); 384 exit(1); 385 } 386 387 destNode = &destDir; 388 389 } else if (S_ISREG(sourceStat.st_mode)) { 390 // create file 391 error = BDirectory().CreateFile(destPath, &destFile); 392 if (error != B_OK) { 393 fprintf(stderr, "Error: Failed to create file \"%s\": %s\n", 394 destPath, strerror(error)); 395 exit(1); 396 } 397 398 destNode = &destFile; 399 400 // copy file contents 401 copy_file_data(sourcePath, sourceFile, destPath, destFile, 402 parameters); 403 404 } else if (S_ISLNK(sourceStat.st_mode)) { 405 // read symlink 406 char linkTo[B_PATH_NAME_LENGTH + 1]; 407 ssize_t bytesRead = readlink(sourcePath, linkTo, 408 sizeof(linkTo) - 1); 409 if (bytesRead < 0) { 410 fprintf(stderr, "Error: Failed to read symlink \"%s\": %s\n", 411 sourcePath, strerror(error)); 412 exit(1); 413 } 414 415 // null terminate the link contents 416 linkTo[bytesRead] = '\0'; 417 418 // create symlink 419 error = BDirectory().CreateSymLink(destPath, linkTo, &destSymLink); 420 if (error != B_OK) { 421 fprintf(stderr, "Error: Failed to create symlink \"%s\": %s\n", 422 destPath, strerror(error)); 423 exit(1); 424 } 425 426 destNode = &destSymLink; 427 428 } else { 429 fprintf(stderr, "Error: Source file \"%s\" has unsupported type.\n", 430 sourcePath); 431 exit(1); 432 } 433 434 // copy attributes (before setting the permissions!) 435 copy_attributes(sourcePath, *sourceNode, destPath, *destNode, 436 parameters); 437 438 // set file owner, group, permissions, times 439 destNode->SetOwner(sourceStat.st_uid); 440 destNode->SetGroup(sourceStat.st_gid); 441 destNode->SetPermissions(sourceStat.st_mode); 442 #if (defined(__BEOS__) || defined(__HAIKU__)) 443 destNode->SetCreationTime(sourceStat.st_crtime); 444 #endif 445 destNode->SetModificationTime(sourceStat.st_mtime); 446 447 } else { 448 // open destination node 449 error = _destNode.SetTo(destPath); 450 if (error != B_OK) { 451 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", 452 destPath, strerror(error)); 453 exit(1); 454 } 455 456 destNode = &_destNode; 457 458 // copy attributes 459 copy_attributes(sourcePath, *sourceNode, destPath, *destNode, 460 parameters); 461 } 462 463 // the destination node is no longer needed 464 destNode->Unset(); 465 466 // recurse 467 if (parameters.recursive && S_ISDIR(sourceStat.st_mode)) { 468 char buffer[sizeof(dirent) + B_FILE_NAME_LENGTH]; 469 dirent *entry = (dirent*)buffer; 470 while (sourceDir.GetNextDirents(entry, sizeof(buffer), 1) == 1) { 471 if (strcmp(entry->d_name, ".") == 0 472 || strcmp(entry->d_name, "..") == 0) { 473 continue; 474 } 475 476 // construct new entry paths 477 BPath sourceEntryPath; 478 error = sourceEntryPath.SetTo(sourcePath, entry->d_name); 479 if (error != B_OK) { 480 fprintf(stderr, "Error: Failed to construct entry path from " 481 "dir \"%s\" and name \"%s\": %s\n", 482 sourcePath, entry->d_name, strerror(error)); 483 exit(1); 484 } 485 486 BPath destEntryPath; 487 error = destEntryPath.SetTo(destPath, entry->d_name); 488 if (error != B_OK) { 489 fprintf(stderr, "Error: Failed to construct entry path from " 490 "dir \"%s\" and name \"%s\": %s\n", 491 destPath, entry->d_name, strerror(error)); 492 exit(1); 493 } 494 495 // copy the entry 496 copy_entry(sourceEntryPath.Path(), destEntryPath.Path(), 497 parameters); 498 } 499 } 500 501 // remove source in move mode 502 if (parameters.move_files) { 503 if (S_ISDIR(sourceStat.st_mode)) { 504 if (rmdir(sourcePath) < 0) { 505 fprintf(stderr, "Error: Failed to remove \"%s\": %s\n", 506 sourcePath, strerror(errno)); 507 exit(1); 508 } 509 510 } else { 511 if (unlink(sourcePath) < 0) { 512 fprintf(stderr, "Error: Failed to unlink \"%s\": %s\n", 513 sourcePath, strerror(errno)); 514 exit(1); 515 } 516 } 517 } 518 } 519 520 // copy_files 521 static void 522 copy_files(const char **sourcePaths, int sourceCount, 523 const char *destPath, const Parameters ¶meters) 524 { 525 // check, if destination exists 526 BEntry destEntry; 527 status_t error = destEntry.SetTo(destPath); 528 if (error != B_OK) { 529 fprintf(stderr, "Error: Couldn't access \"%s\": %s\n", destPath, 530 strerror(error)); 531 exit(1); 532 } 533 bool destExists = destEntry.Exists(); 534 535 // If it exists, check whether it is a directory. In case we don't copy 536 // the data, we pretend the destination is no directory, even if it is 537 // one. 538 bool destIsDir = false; 539 if (destExists && parameters.copy_data) { 540 struct stat st; 541 error = destEntry.GetStat(&st); 542 if (error != B_OK) { 543 fprintf(stderr, "Error: Failed to stat \"%s\": %s\n", destPath, 544 strerror(error)); 545 exit(1); 546 } 547 548 if (S_ISDIR(st.st_mode)) { 549 destIsDir = true; 550 } else if (S_ISLNK(st.st_mode)) { 551 // a symlink -- check if it refers to a dir 552 BEntry resolvedDestEntry; 553 if (resolvedDestEntry.SetTo(destPath, true) == B_OK 554 && resolvedDestEntry.IsDirectory()) { 555 destIsDir = true; 556 } 557 } 558 } 559 560 // If we have multiple source files, the destination should be a directory, 561 // if we want to copy the file data. 562 if (sourceCount > 1 && parameters.copy_data && !destIsDir) { 563 fprintf(stderr, "Error: Destination needs to be a directory when " 564 "multiple source files are specified and option \"-d\" is " 565 "given.\n"); 566 exit(1); 567 } 568 569 // iterate through the source files 570 for (int i = 0; i < sourceCount; i++) { 571 const char *sourcePath = sourcePaths[i]; 572 // If the destination is a directory, we usually want to copy the 573 // sources into it. The user might have specified a source path ending 574 // in "/." or "/.." however, in which case we copy the contents of the 575 // given directory. 576 bool copySourceContentsOnly = false; 577 if (destIsDir) { 578 // skip trailing '/'s 579 int sourceLen = strlen(sourcePath); 580 while (sourceLen > 1 && sourcePath[sourceLen - 1] == '/') 581 sourceLen--; 582 583 // find the start of the leaf name 584 int leafStart = sourceLen; 585 while (leafStart > 0 && sourcePath[leafStart - 1] != '/') 586 leafStart--; 587 588 // If the path is the root directory or the leaf is "." or "..", 589 // we copy the contents only. 590 int leafLen = sourceLen - leafStart; 591 if (leafLen == 0 || (leafLen <= 2 592 && strncmp(sourcePath + leafStart, "..", leafLen) == 0)) { 593 copySourceContentsOnly = true; 594 } 595 } 596 597 if (destIsDir && !copySourceContentsOnly) { 598 // construct a usable destination entry path 599 // normalize source path 600 BPath normalizedSourcePath; 601 error = normalizedSourcePath.SetTo(sourcePath); 602 if (error != B_OK) { 603 fprintf(stderr, "Error: Invalid path \"%s\".\n", sourcePath); 604 exit(1); 605 } 606 607 BPath destEntryPath; 608 error = destEntryPath.SetTo(destPath, normalizedSourcePath.Leaf()); 609 if (error != B_OK) { 610 fprintf(stderr, "Error: Failed to get destination path for " 611 "source \"%s\" and destination directory \"%s\".\n", 612 sourcePath, destPath); 613 exit(1); 614 } 615 616 copy_entry(normalizedSourcePath.Path(), destEntryPath.Path(), 617 parameters); 618 } else { 619 copy_entry(sourcePath, destPath, parameters); 620 } 621 } 622 } 623 624 625 // main 626 int 627 main(int argc, const char *const *argv) 628 { 629 kArgc = argc; 630 kArgv = argv; 631 632 // parameters 633 Parameters parameters; 634 const char *attributeName = NULL; 635 const char *attributeTypeString = NULL; 636 const char **files = new const char*[argc]; 637 int fileCount = 0; 638 639 // parse the arguments 640 bool moreOptions = true; 641 for (int argi = 1; argi < argc; ) { 642 const char *arg = argv[argi++]; 643 if (moreOptions && arg[0] == '-') { 644 if (strcmp(arg, "-d") == 0 || strcmp(arg, "--data") == 0) { 645 parameters.copy_data = true; 646 647 } else if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) { 648 print_usage_and_exit(false); 649 650 } else if (strcmp(arg, "-m") == 0 || strcmp(arg, "--move") == 0) { 651 parameters.move_files = true; 652 653 } else if (strcmp(arg, "-n") == 0 || strcmp(arg, "--name") == 0) { 654 if (attributeName) { 655 fprintf(stderr, "Error: Only one attribute name can be " 656 "specified.\n"); 657 exit(1); 658 } 659 660 attributeName = next_arg(argi); 661 662 } else if (strcmp(arg, "-r") == 0 663 || strcmp(arg, "--recursive") == 0) { 664 parameters.recursive = true; 665 666 } else if (strcmp(arg, "-t") == 0 || strcmp(arg, "--type") == 0) { 667 if (attributeTypeString) { 668 fprintf(stderr, "Error: Only one attribute type can be " 669 "specified.\n"); 670 exit(1); 671 } 672 673 attributeTypeString = next_arg(argi); 674 675 } else if (strcmp(arg, "-v") == 0 676 || strcmp(arg, "--verbose") == 0) { 677 parameters.verbose = true; 678 679 } else if (strcmp(arg, "-x") == 0) { 680 parameters.entry_filter.AddExcludeFilter(next_arg(argi), true); 681 682 } else if (strcmp(arg, "-X") == 0) { 683 parameters.entry_filter.AddExcludeFilter(next_arg(argi), false); 684 685 } else if (strcmp(arg, "-") == 0 || strcmp(arg, "--") == 0) { 686 moreOptions = false; 687 688 } else { 689 fprintf(stderr, "Error: Invalid option: \"%s\"\n", arg); 690 print_usage_and_exit(true); 691 } 692 693 } else { 694 // file 695 files[fileCount++] = arg; 696 } 697 } 698 699 // check parameters 700 701 // enough files 702 if (fileCount < 2) { 703 fprintf(stderr, "Error: Not enough file names specified.\n"); 704 print_usage_and_exit(true); 705 } 706 707 // attribute type 708 type_code attributeType = B_ANY_TYPE; 709 if (attributeTypeString) { 710 bool found = false; 711 for (int i = 0; kSupportedAttributeTypes[i].type_name; i++) { 712 if (strcmp(attributeTypeString, 713 kSupportedAttributeTypes[i].type_name) == 0) { 714 found = true; 715 attributeType = kSupportedAttributeTypes[i].type; 716 break; 717 } 718 } 719 720 if (!found) { 721 fprintf(stderr, "Error: Unsupported attribute type: \"%s\"\n", 722 attributeTypeString); 723 exit(1); 724 } 725 } 726 727 // init the attribute filter 728 parameters.attribute_filter.SetTo(attributeName, attributeType); 729 730 // turn of move_files, if we are not copying the file data 731 parameters.move_files &= parameters.copy_data; 732 733 // do the copying 734 fileCount--; 735 const char *destination = files[fileCount]; 736 files[fileCount] = NULL; 737 copy_files(files, fileCount, destination, parameters); 738 739 return 0; 740 } 741