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 (destIsDir) { 563 // construct a usable destination entry path 564 // normalize source path 565 BPath normalizedSourcePath; 566 error = normalizedSourcePath.SetTo(sourcePath); 567 if (error != B_OK) { 568 fprintf(stderr, "Error: Invalid path \"%s\".\n", sourcePath); 569 exit(1); 570 } 571 572 BPath destEntryPath; 573 error = destEntryPath.SetTo(destPath, normalizedSourcePath.Leaf()); 574 if (error != B_OK) { 575 fprintf(stderr, "Error: Failed to get destination path for " 576 "source \"%s\" and destination directory \"%s\".\n", 577 sourcePath, destPath); 578 exit(1); 579 } 580 581 copy_entry(normalizedSourcePath.Path(), destEntryPath.Path(), 582 parameters); 583 } else { 584 copy_entry(sourcePath, destPath, parameters); 585 } 586 } 587 } 588 589 // main 590 int 591 main(int argc, const char *const *argv) 592 { 593 kArgc = argc; 594 kArgv = argv; 595 596 // parameters 597 Parameters parameters; 598 const char *attributeName = NULL; 599 const char *attributeTypeString = NULL; 600 const char **files = new const char*[argc]; 601 int fileCount = 0; 602 603 // parse the arguments 604 bool moreOptions = true; 605 for (int argi = 1; argi < argc; ) { 606 const char *arg = argv[argi++]; 607 if (moreOptions && arg[0] == '-') { 608 if (strcmp(arg, "-d") == 0 || strcmp(arg, "--data") == 0) { 609 parameters.copy_data = true; 610 611 } else if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) { 612 print_usage_and_exit(false); 613 614 } else if (strcmp(arg, "-m") == 0 || strcmp(arg, "--move") == 0) { 615 parameters.move_files = true; 616 617 } else if (strcmp(arg, "-n") == 0 || strcmp(arg, "--name") == 0) { 618 if (attributeName) { 619 fprintf(stderr, "Error: Only one attribute name can be " 620 "specified.\n"); 621 exit(1); 622 } 623 624 attributeName = next_arg(argi); 625 626 } else if (strcmp(arg, "-r") == 0 627 || strcmp(arg, "--recursive") == 0) { 628 parameters.recursive = true; 629 630 } else if (strcmp(arg, "-t") == 0 || strcmp(arg, "--type") == 0) { 631 if (attributeTypeString) { 632 fprintf(stderr, "Error: Only one attribute type can be " 633 "specified.\n"); 634 exit(1); 635 } 636 637 attributeTypeString = next_arg(argi); 638 639 } else if (strcmp(arg, "-v") == 0 640 || strcmp(arg, "--verbose") == 0) { 641 parameters.verbose = true; 642 643 } else if (strcmp(arg, "-x") == 0 || strcmp(arg, "--type") == 0) { 644 parameters.entry_filter.AddExcludeFilter(next_arg(argi), true); 645 646 } else if (strcmp(arg, "-X") == 0 || strcmp(arg, "--type") == 0) { 647 parameters.entry_filter.AddExcludeFilter(next_arg(argi), false); 648 649 } else if (strcmp(arg, "-") == 0 || strcmp(arg, "--") == 0) { 650 moreOptions = false; 651 652 } else { 653 fprintf(stderr, "Error: Invalid option: \"%s\"\n", arg); 654 print_usage_and_exit(true); 655 } 656 657 } else { 658 // file 659 files[fileCount++] = arg; 660 } 661 } 662 663 // check parameters 664 665 // enough files 666 if (fileCount < 2) { 667 fprintf(stderr, "Error: Not enough file names specified.\n"); 668 print_usage_and_exit(true); 669 } 670 671 // attribute type 672 type_code attributeType = B_ANY_TYPE; 673 if (attributeTypeString) { 674 bool found = false; 675 for (int i = 0; kSupportedAttributeTypes[i].type_name; i++) { 676 if (strcmp(attributeTypeString, 677 kSupportedAttributeTypes[i].type_name) == 0) { 678 found = true; 679 attributeType = kSupportedAttributeTypes[i].type; 680 break; 681 } 682 } 683 684 if (!found) { 685 fprintf(stderr, "Error: Unsupported attribute type: \"%s\"\n", 686 attributeTypeString); 687 exit(1); 688 } 689 } 690 691 // init the attribute filter 692 parameters.attribute_filter.SetTo(attributeName, attributeType); 693 694 // turn of move_files, if we are not copying the file data 695 parameters.move_files &= parameters.copy_data; 696 697 // do the copying 698 fileCount--; 699 const char *destination = files[fileCount]; 700 files[fileCount] = NULL; 701 copy_files(files, fileCount, destination, parameters); 702 703 return 0; 704 } 705