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