1 /* 2 * Copyright 2005-2008, 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 108 AttributeFilter() 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 : copy_data(false), 137 recursive(false), 138 move_files(false), 139 verbose(false) 140 { 141 } 142 143 bool copy_data; 144 bool recursive; 145 bool move_files; 146 bool verbose; 147 AttributeFilter attribute_filter; 148 EntryFilter entry_filter; 149 }; 150 151 152 // print_usage 153 static void 154 print_usage(bool error) 155 { 156 // get command name 157 const char *commandName = NULL; 158 if (kArgc > 0) { 159 if (const char *lastSlash = strchr(kArgv[0], '/')) 160 commandName = lastSlash + 1; 161 else 162 commandName = kArgv[0]; 163 } 164 165 if (!commandName || strlen(commandName) == 0) 166 commandName = kCommandName; 167 168 // print usage 169 fprintf((error ? stderr : stdout), kUsage, commandName); 170 } 171 172 // print_usage_and_exit 173 static void 174 print_usage_and_exit(bool error) 175 { 176 print_usage(error); 177 exit(error ? 1 : 0); 178 } 179 180 // next_arg 181 static const char * 182 next_arg(int &argi, bool optional = false) 183 { 184 if (argi >= kArgc) { 185 if (!optional) 186 print_usage_and_exit(true); 187 return NULL; 188 } 189 190 return kArgv[argi++]; 191 } 192 193 // copy_attributes 194 static void 195 copy_attributes(const char *sourcePath, BNode &source, 196 const char *destPath, BNode &destination, 197 const Parameters ¶meters) 198 { 199 char attrName[B_ATTR_NAME_LENGTH]; 200 while (source.GetNextAttrName(attrName) == B_OK) { 201 // get attr info 202 attr_info attrInfo; 203 status_t error = source.GetAttrInfo(attrName, &attrInfo); 204 if (error != B_OK) { 205 fprintf(stderr, "Error: Failed to get info of attribute \"%s\" " 206 "of file \"%s\": %s\n", attrName, sourcePath, strerror(error)); 207 exit(1); 208 } 209 210 // filter 211 if (!parameters.attribute_filter.Filter(attrName, attrInfo.type)) 212 continue; 213 214 // copy the attribute 215 char buffer[kCopyBufferSize]; 216 off_t offset = 0; 217 off_t bytesLeft = attrInfo.size; 218 // go at least once through the loop, so that empty attribute will be 219 // created as well 220 do { 221 size_t toRead = kCopyBufferSize; 222 if (toRead > bytesLeft) 223 toRead = bytesLeft; 224 225 // read 226 ssize_t bytesRead = source.ReadAttr(attrName, attrInfo.type, 227 offset, buffer, toRead); 228 if (bytesRead < 0) { 229 fprintf(stderr, "Error: Failed to read attribute \"%s\" " 230 "of file \"%s\": %s\n", attrName, sourcePath, 231 strerror(bytesRead)); 232 exit(1); 233 } 234 235 // write 236 ssize_t bytesWritten = destination.WriteAttr(attrName, 237 attrInfo.type, offset, buffer, bytesRead); 238 if (bytesWritten < 0) { 239 fprintf(stderr, "Error: Failed to write attribute \"%s\" " 240 "of file \"%s\": %s\n", attrName, destPath, 241 strerror(bytesWritten)); 242 exit(1); 243 } 244 245 bytesLeft -= bytesRead; 246 offset += bytesRead; 247 248 } while (bytesLeft > 0); 249 } 250 } 251 252 // copy_file_data 253 static void 254 copy_file_data(const char *sourcePath, BFile &source, 255 const char *destPath, BFile &destination, const Parameters ¶meters) 256 { 257 char buffer[kCopyBufferSize]; 258 off_t offset = 0; 259 while (true) { 260 // read 261 ssize_t bytesRead = source.ReadAt(offset, buffer, sizeof(buffer)); 262 if (bytesRead < 0) { 263 fprintf(stderr, "Error: Failed to read from file \"%s\": %s\n", 264 sourcePath, strerror(bytesRead)); 265 exit(1); 266 } 267 268 if (bytesRead == 0) 269 return; 270 271 // write 272 ssize_t bytesWritten = destination.WriteAt(offset, buffer, bytesRead); 273 if (bytesWritten < 0) { 274 fprintf(stderr, "Error: Failed to write to file \"%s\": %s\n", 275 destPath, strerror(bytesWritten)); 276 exit(1); 277 } 278 279 offset += bytesRead; 280 } 281 } 282 283 // copy_entry 284 static void 285 copy_entry(const char *sourcePath, const char *destPath, 286 const Parameters ¶meters) 287 { 288 // apply entry filter 289 if (!parameters.entry_filter.Filter(sourcePath)) 290 return; 291 292 // stat source 293 struct stat sourceStat; 294 if (lstat(sourcePath, &sourceStat) < 0) { 295 fprintf(stderr, "Error: Couldn't access \"%s\": %s\n", sourcePath, 296 strerror(errno)); 297 exit(1); 298 } 299 300 // stat destination 301 struct stat destStat; 302 bool destExists = (lstat(destPath, &destStat) == 0); 303 304 if (!destExists && !parameters.copy_data) { 305 fprintf(stderr, "Error: Destination file \"%s\" does not exist.\n", 306 destPath); 307 exit(1); 308 } 309 310 if (parameters.verbose) 311 printf("%s\n", destPath); 312 313 // check whether to delete/create the destination 314 bool unlinkDest = (destExists && parameters.copy_data); 315 bool createDest = parameters.copy_data; 316 if (destExists) { 317 if (S_ISDIR(destStat.st_mode)) { 318 if (S_ISDIR(sourceStat.st_mode)) { 319 // both are dirs; nothing to do 320 unlinkDest = false; 321 createDest = false; 322 } else if (parameters.copy_data || parameters.recursive) { 323 // destination is directory, but source isn't, and mode is 324 // not non-recursive attributes-only copy 325 fprintf(stderr, "Error: Can't copy \"%s\", since directory " 326 "\"%s\" is in the way.\n", sourcePath, destPath); 327 exit(1); 328 } 329 } 330 } 331 332 // unlink the destination 333 if (unlinkDest) { 334 if (unlink(destPath) < 0) { 335 fprintf(stderr, "Error: Failed to unlink \"%s\": %s\n", destPath, 336 strerror(errno)); 337 exit(1); 338 } 339 } 340 341 // open source node 342 BNode _sourceNode; 343 BFile sourceFile; 344 BDirectory sourceDir; 345 BNode *sourceNode = NULL; 346 status_t error; 347 348 if (S_ISDIR(sourceStat.st_mode)) { 349 error = sourceDir.SetTo(sourcePath); 350 sourceNode = &sourceDir; 351 } else if (S_ISREG(sourceStat.st_mode)) { 352 error = sourceFile.SetTo(sourcePath, B_READ_ONLY); 353 sourceNode = &sourceFile; 354 } else { 355 error = _sourceNode.SetTo(sourcePath); 356 sourceNode = &_sourceNode; 357 } 358 359 if (error != B_OK) { 360 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", 361 sourcePath, strerror(error)); 362 exit(1); 363 } 364 365 // create the destination 366 BNode _destNode; 367 BDirectory destDir; 368 BFile destFile; 369 BSymLink destSymLink; 370 BNode *destNode = NULL; 371 372 if (createDest) { 373 if (S_ISDIR(sourceStat.st_mode)) { 374 // create dir 375 error = BDirectory().CreateDirectory(destPath, &destDir); 376 if (error != B_OK) { 377 fprintf(stderr, "Error: Failed to make directory \"%s\": %s\n", 378 destPath, strerror(error)); 379 exit(1); 380 } 381 382 destNode = &destDir; 383 384 } else if (S_ISREG(sourceStat.st_mode)) { 385 // create file 386 error = BDirectory().CreateFile(destPath, &destFile); 387 if (error != B_OK) { 388 fprintf(stderr, "Error: Failed to create file \"%s\": %s\n", 389 destPath, strerror(error)); 390 exit(1); 391 } 392 393 destNode = &destFile; 394 395 // copy file contents 396 copy_file_data(sourcePath, sourceFile, destPath, destFile, 397 parameters); 398 399 } else if (S_ISLNK(sourceStat.st_mode)) { 400 // read symlink 401 char linkTo[B_PATH_NAME_LENGTH + 1]; 402 ssize_t bytesRead = readlink(sourcePath, linkTo, 403 sizeof(linkTo) - 1); 404 if (bytesRead < 0) { 405 fprintf(stderr, "Error: Failed to read symlink \"%s\": %s\n", 406 sourcePath, strerror(error)); 407 exit(1); 408 } 409 410 // null terminate the link contents 411 linkTo[bytesRead] = '\0'; 412 413 // create symlink 414 error = BDirectory().CreateSymLink(destPath, linkTo, &destSymLink); 415 if (error != B_OK) { 416 fprintf(stderr, "Error: Failed to create symlink \"%s\": %s\n", 417 destPath, strerror(error)); 418 exit(1); 419 } 420 421 destNode = &destSymLink; 422 423 } else { 424 fprintf(stderr, "Error: Source file \"%s\" has unsupported type.\n", 425 sourcePath); 426 exit(1); 427 } 428 429 // set file owner, group, permissions, times 430 destNode->SetOwner(sourceStat.st_uid); 431 destNode->SetGroup(sourceStat.st_gid); 432 destNode->SetPermissions(sourceStat.st_mode); 433 #ifdef __BEOS__ 434 destNode->SetCreationTime(sourceStat.st_crtime); 435 #endif 436 destNode->SetModificationTime(sourceStat.st_mtime); 437 438 } else { 439 // open destination node 440 error = _destNode.SetTo(destPath); 441 if (error != B_OK) { 442 fprintf(stderr, "Error: Failed to open \"%s\": %s\n", 443 destPath, strerror(error)); 444 exit(1); 445 } 446 447 destNode = &_destNode; 448 } 449 450 // copy attributes 451 copy_attributes(sourcePath, *sourceNode, destPath, *destNode, parameters); 452 453 // the destination node is no longer needed 454 destNode->Unset(); 455 456 // recurse 457 if (parameters.recursive && S_ISDIR(sourceStat.st_mode)) { 458 char buffer[sizeof(dirent) + B_FILE_NAME_LENGTH]; 459 dirent *entry = (dirent*)buffer; 460 while (sourceDir.GetNextDirents(entry, sizeof(buffer), 1) == 1) { 461 if (strcmp(entry->d_name, ".") == 0 462 || strcmp(entry->d_name, "..") == 0) { 463 continue; 464 } 465 466 // construct new entry paths 467 BPath sourceEntryPath; 468 error = sourceEntryPath.SetTo(sourcePath, entry->d_name); 469 if (error != B_OK) { 470 fprintf(stderr, "Error: Failed to construct entry path from " 471 "dir \"%s\" and name \"%s\": %s\n", 472 sourcePath, entry->d_name, strerror(error)); 473 exit(1); 474 } 475 476 BPath destEntryPath; 477 error = destEntryPath.SetTo(destPath, entry->d_name); 478 if (error != B_OK) { 479 fprintf(stderr, "Error: Failed to construct entry path from " 480 "dir \"%s\" and name \"%s\": %s\n", 481 destPath, entry->d_name, strerror(error)); 482 exit(1); 483 } 484 485 // copy the entry 486 copy_entry(sourceEntryPath.Path(), destEntryPath.Path(), 487 parameters); 488 } 489 } 490 491 // remove source in move mode 492 if (parameters.move_files) { 493 if (S_ISDIR(sourceStat.st_mode)) { 494 if (rmdir(sourcePath) < 0) { 495 fprintf(stderr, "Error: Failed to remove \"%s\": %s\n", 496 sourcePath, strerror(errno)); 497 exit(1); 498 } 499 500 } else { 501 if (unlink(sourcePath) < 0) { 502 fprintf(stderr, "Error: Failed to unlink \"%s\": %s\n", 503 sourcePath, strerror(errno)); 504 exit(1); 505 } 506 } 507 } 508 } 509 510 // copy_files 511 static void 512 copy_files(const char **sourcePaths, int sourceCount, 513 const char *destPath, const Parameters ¶meters) 514 { 515 // check, if destination exists 516 BEntry destEntry; 517 status_t error = destEntry.SetTo(destPath); 518 if (error != B_OK) { 519 fprintf(stderr, "Error: Couldn't access \"%s\": %s\n", destPath, 520 strerror(error)); 521 exit(1); 522 } 523 bool destExists = destEntry.Exists(); 524 525 // If it exists, check whether it is a directory. In case we don't copy 526 // the data, we pretend the destination is no directory, even if it is 527 // one. 528 bool destIsDir = false; 529 if (destExists && parameters.copy_data) { 530 struct stat st; 531 error = destEntry.GetStat(&st); 532 if (error != B_OK) { 533 fprintf(stderr, "Error: Failed to stat \"%s\": %s\n", destPath, 534 strerror(error)); 535 exit(1); 536 } 537 538 if (S_ISDIR(st.st_mode)) { 539 destIsDir = true; 540 } else if (S_ISLNK(st.st_mode)) { 541 // a symlink -- check if it refers to a dir 542 BEntry resolvedDestEntry; 543 if (resolvedDestEntry.SetTo(destPath, true) == B_OK 544 && resolvedDestEntry.IsDirectory()) { 545 destIsDir = true; 546 } 547 } 548 } 549 550 // If we have multiple source files, the destination should be a directory, 551 // if we want to copy the file data. 552 if (sourceCount > 1 && parameters.copy_data && !destIsDir) { 553 fprintf(stderr, "Error: Destination needs to be a directory when " 554 "multiple source files are specified and option \"-d\" is " 555 "given.\n"); 556 exit(1); 557 } 558 559 // iterate through the source files 560 for (int i = 0; i < sourceCount; i++) { 561 const char *sourcePath = sourcePaths[i]; 562 // If the destination is a directory, we usually want to copy the 563 // sources into it. The user might have specified a source path ending 564 // in "/." or "/.." however, in which case we copy the contents of the 565 // given directory. 566 bool copySourceContentsOnly = false; 567 if (destIsDir) { 568 // skip trailing '/'s 569 int sourceLen = strlen(sourcePath); 570 while (sourceLen > 1 && sourcePath[sourceLen - 1] == '/') 571 sourceLen--; 572 573 // find the start of the leaf name 574 int leafStart = sourceLen; 575 while (leafStart > 0 && sourcePath[leafStart - 1] != '/') 576 leafStart--; 577 578 // If the path is the root directory or the leaf is "." or "..", 579 // we copy the contents only. 580 int leafLen = sourceLen - leafStart; 581 if (leafLen == 0 || leafLen <= 2 582 && strncmp(sourcePath + leafStart, "..", leafLen) == 0) { 583 copySourceContentsOnly = true; 584 } 585 } 586 587 if (destIsDir && !copySourceContentsOnly) { 588 // construct a usable destination entry path 589 // normalize source path 590 BPath normalizedSourcePath; 591 error = normalizedSourcePath.SetTo(sourcePath); 592 if (error != B_OK) { 593 fprintf(stderr, "Error: Invalid path \"%s\".\n", sourcePath); 594 exit(1); 595 } 596 597 BPath destEntryPath; 598 error = destEntryPath.SetTo(destPath, normalizedSourcePath.Leaf()); 599 if (error != B_OK) { 600 fprintf(stderr, "Error: Failed to get destination path for " 601 "source \"%s\" and destination directory \"%s\".\n", 602 sourcePath, destPath); 603 exit(1); 604 } 605 606 copy_entry(normalizedSourcePath.Path(), destEntryPath.Path(), 607 parameters); 608 } else { 609 copy_entry(sourcePath, destPath, parameters); 610 } 611 } 612 } 613 614 // main 615 int 616 main(int argc, const char *const *argv) 617 { 618 kArgc = argc; 619 kArgv = argv; 620 621 // parameters 622 Parameters parameters; 623 const char *attributeName = NULL; 624 const char *attributeTypeString = NULL; 625 const char **files = new const char*[argc]; 626 int fileCount = 0; 627 628 // parse the arguments 629 bool moreOptions = true; 630 for (int argi = 1; argi < argc; ) { 631 const char *arg = argv[argi++]; 632 if (moreOptions && arg[0] == '-') { 633 if (strcmp(arg, "-d") == 0 || strcmp(arg, "--data") == 0) { 634 parameters.copy_data = true; 635 636 } else if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) { 637 print_usage_and_exit(false); 638 639 } else if (strcmp(arg, "-m") == 0 || strcmp(arg, "--move") == 0) { 640 parameters.move_files = true; 641 642 } else if (strcmp(arg, "-n") == 0 || strcmp(arg, "--name") == 0) { 643 if (attributeName) { 644 fprintf(stderr, "Error: Only one attribute name can be " 645 "specified.\n"); 646 exit(1); 647 } 648 649 attributeName = next_arg(argi); 650 651 } else if (strcmp(arg, "-r") == 0 652 || strcmp(arg, "--recursive") == 0) { 653 parameters.recursive = true; 654 655 } else if (strcmp(arg, "-t") == 0 || strcmp(arg, "--type") == 0) { 656 if (attributeTypeString) { 657 fprintf(stderr, "Error: Only one attribute type can be " 658 "specified.\n"); 659 exit(1); 660 } 661 662 attributeTypeString = next_arg(argi); 663 664 } else if (strcmp(arg, "-v") == 0 665 || strcmp(arg, "--verbose") == 0) { 666 parameters.verbose = true; 667 668 } else if (strcmp(arg, "-x") == 0) { 669 parameters.entry_filter.AddExcludeFilter(next_arg(argi), true); 670 671 } else if (strcmp(arg, "-X") == 0) { 672 parameters.entry_filter.AddExcludeFilter(next_arg(argi), false); 673 674 } else if (strcmp(arg, "-") == 0 || strcmp(arg, "--") == 0) { 675 moreOptions = false; 676 677 } else { 678 fprintf(stderr, "Error: Invalid option: \"%s\"\n", arg); 679 print_usage_and_exit(true); 680 } 681 682 } else { 683 // file 684 files[fileCount++] = arg; 685 } 686 } 687 688 // check parameters 689 690 // enough files 691 if (fileCount < 2) { 692 fprintf(stderr, "Error: Not enough file names specified.\n"); 693 print_usage_and_exit(true); 694 } 695 696 // attribute type 697 type_code attributeType = B_ANY_TYPE; 698 if (attributeTypeString) { 699 bool found = false; 700 for (int i = 0; kSupportedAttributeTypes[i].type_name; i++) { 701 if (strcmp(attributeTypeString, 702 kSupportedAttributeTypes[i].type_name) == 0) { 703 found = true; 704 attributeType = kSupportedAttributeTypes[i].type; 705 break; 706 } 707 } 708 709 if (!found) { 710 fprintf(stderr, "Error: Unsupported attribute type: \"%s\"\n", 711 attributeTypeString); 712 exit(1); 713 } 714 } 715 716 // init the attribute filter 717 parameters.attribute_filter.SetTo(attributeName, attributeType); 718 719 // turn of move_files, if we are not copying the file data 720 parameters.move_files &= parameters.copy_data; 721 722 // do the copying 723 fileCount--; 724 const char *destination = files[fileCount]; 725 files[fileCount] = NULL; 726 copy_files(files, fileCount, destination, parameters); 727 728 return 0; 729 } 730