1 /* 2 * Copyright 2005-2008, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 #include "compatibility.h" 7 8 #include "command_cp.h" 9 10 #include <fcntl.h> 11 #include <stdio.h> 12 #include <string.h> 13 #include <unistd.h> 14 15 #include <AutoDeleter.h> 16 #include <EntryFilter.h> 17 #include <fs_attr.h> 18 #include <StorageDefs.h> 19 20 #include "fssh_dirent.h" 21 #include "fssh_errno.h" 22 #include "fssh_errors.h" 23 #include "fssh_fcntl.h" 24 #include "fssh_fs_attr.h" 25 #include "fssh_stat.h" 26 #include "fssh_string.h" 27 #include "fssh_unistd.h" 28 #include "path_util.h" 29 #include "stat_util.h" 30 #include "syscalls.h" 31 32 33 using BPrivate::EntryFilter; 34 35 36 namespace FSShell { 37 38 39 static void *sCopyBuffer = NULL; 40 static const int sCopyBufferSize = 64 * 1024; // 64 KB 41 42 struct Options { 43 Options() 44 : entryFilter(), 45 attributesOnly(false), 46 ignoreAttributes(false), 47 dereference(true), 48 alwaysDereference(false), 49 force(false), 50 recursive(false) 51 { 52 } 53 54 EntryFilter entryFilter; 55 bool attributesOnly; 56 bool ignoreAttributes; 57 bool dereference; 58 bool alwaysDereference; 59 bool force; 60 bool recursive; 61 }; 62 63 class Directory; 64 class File; 65 class SymLink; 66 67 // Node 68 class Node { 69 public: 70 Node() {} 71 virtual ~Node() {} 72 73 const struct fssh_stat &Stat() const { return fStat; } 74 bool IsFile() const { return FSSH_S_ISREG(fStat.fssh_st_mode); } 75 bool IsDirectory() const { return FSSH_S_ISDIR(fStat.fssh_st_mode); } 76 bool IsSymLink() const { return FSSH_S_ISLNK(fStat.fssh_st_mode); } 77 78 virtual File *ToFile() { return NULL; } 79 virtual Directory *ToDirectory() { return NULL; } 80 virtual SymLink *ToSymLink() { return NULL; } 81 82 virtual fssh_ssize_t GetNextAttr(char *name, int size) = 0; 83 virtual fssh_status_t GetAttrInfo(const char *name, 84 fssh_attr_info &info) = 0; 85 virtual fssh_ssize_t ReadAttr(const char *name, uint32_t type, 86 fssh_off_t pos, void *buffer, int size) = 0; 87 virtual fssh_ssize_t WriteAttr(const char *name, uint32_t type, 88 fssh_off_t pos, const void *buffer, int size) = 0; 89 virtual fssh_status_t RemoveAttr(const char *name) = 0; 90 91 protected: 92 struct fssh_stat fStat; // To be initialized by implementing classes. 93 }; 94 95 // Directory 96 class Directory : public virtual Node { 97 public: 98 virtual Directory *ToDirectory() { return this; } 99 100 virtual fssh_ssize_t GetNextEntry(struct fssh_dirent *entry, int size) = 0; 101 }; 102 103 // File 104 class File : public virtual Node { 105 public: 106 virtual File *ToFile() { return this; } 107 108 virtual fssh_ssize_t Read(void *buffer, int size) = 0; 109 virtual fssh_ssize_t Write(const void *buffer, int size) = 0; 110 }; 111 112 // SymLink 113 class SymLink : public virtual Node { 114 public: 115 virtual SymLink *ToSymLink() { return this; } 116 117 virtual fssh_ssize_t ReadLink(char *buffer, int bufferSize) = 0; 118 }; 119 120 // FSDomain 121 class FSDomain { 122 public: 123 virtual ~FSDomain() {} 124 125 virtual fssh_status_t Open(const char *path, int openMode, Node *&node) = 0; 126 127 virtual fssh_status_t CreateFile(const char *path, 128 const struct fssh_stat &st, File *&file) = 0; 129 virtual fssh_status_t CreateDirectory(const char *path, 130 const struct fssh_stat &st, Directory *&dir) = 0; 131 virtual fssh_status_t CreateSymLink(const char *path, const char *linkTo, 132 const struct fssh_stat &st, SymLink *&link) = 0; 133 134 virtual fssh_status_t Unlink(const char *path) = 0; 135 }; 136 137 138 // #pragma mark - 139 140 // HostNode 141 class HostNode : public virtual Node { 142 public: 143 HostNode() 144 : Node(), 145 fFD(-1), 146 fAttrDir(NULL) 147 { 148 } 149 150 virtual ~HostNode() 151 { 152 if (fFD >= 0) 153 fssh_close(fFD); 154 if (fAttrDir) 155 fs_close_attr_dir(fAttrDir); 156 } 157 158 virtual fssh_status_t Init(const char *path, int fd, 159 const struct fssh_stat &st) 160 { 161 fFD = fd; 162 fStat = st; 163 164 // open the attribute directory 165 fAttrDir = fs_fopen_attr_dir(fd); 166 if (!fAttrDir) 167 return fssh_get_errno(); 168 169 return FSSH_B_OK; 170 } 171 172 virtual fssh_ssize_t GetNextAttr(char *name, int size) 173 { 174 if (!fAttrDir) 175 return 0; 176 177 fssh_set_errno(FSSH_B_OK); 178 struct dirent *entry = fs_read_attr_dir(fAttrDir); 179 if (!entry) 180 return fssh_get_errno(); 181 182 int len = strlen(entry->d_name); 183 if (len >= size) 184 return FSSH_B_NAME_TOO_LONG; 185 186 strcpy(name, entry->d_name); 187 return 1; 188 } 189 190 virtual fssh_status_t GetAttrInfo(const char *name, fssh_attr_info &info) 191 { 192 attr_info hostInfo; 193 if (fs_stat_attr(fFD, name, &hostInfo) < 0) 194 return fssh_get_errno(); 195 196 info.type = hostInfo.type; 197 info.size = hostInfo.size; 198 return FSSH_B_OK; 199 } 200 201 virtual fssh_ssize_t ReadAttr(const char *name, uint32_t type, 202 fssh_off_t pos, void *buffer, int size) 203 { 204 fssh_ssize_t bytesRead = fs_read_attr(fFD, name, type, pos, buffer, 205 size); 206 return (bytesRead >= 0 ? bytesRead : fssh_get_errno()); 207 } 208 209 virtual fssh_ssize_t WriteAttr(const char *name, uint32_t type, 210 fssh_off_t pos, const void *buffer, int size) 211 { 212 fssh_ssize_t bytesWritten = fs_write_attr(fFD, name, type, pos, buffer, 213 size); 214 return (bytesWritten >= 0 ? bytesWritten : fssh_get_errno()); 215 } 216 217 virtual fssh_status_t RemoveAttr(const char *name) 218 { 219 return (fs_remove_attr(fFD, name) == 0 ? 0 : fssh_get_errno()); 220 } 221 222 protected: 223 int fFD; 224 DIR *fAttrDir; 225 }; 226 227 // HostDirectory 228 class HostDirectory : public Directory, public HostNode { 229 public: 230 HostDirectory() 231 : Directory(), 232 HostNode(), 233 fDir(NULL) 234 { 235 } 236 237 virtual ~HostDirectory() 238 { 239 if (fDir) 240 closedir(fDir); 241 } 242 243 virtual fssh_status_t Init(const char *path, int fd, 244 const struct fssh_stat &st) 245 { 246 fssh_status_t error = HostNode::Init(path, fd, st); 247 if (error != FSSH_B_OK) 248 return error; 249 250 fDir = opendir(path); 251 if (!fDir) 252 return fssh_get_errno(); 253 254 return FSSH_B_OK; 255 } 256 257 virtual fssh_ssize_t GetNextEntry(struct fssh_dirent *entry, int size) 258 { 259 fssh_set_errno(FSSH_B_OK); 260 struct dirent *hostEntry = readdir(fDir); 261 if (!hostEntry) 262 return fssh_get_errno(); 263 264 int nameLen = strlen(hostEntry->d_name); 265 int recLen = entry->d_name + nameLen + 1 - (char*)entry; 266 if (recLen > size) 267 return FSSH_B_NAME_TOO_LONG; 268 269 #ifdef __BEOS__ 270 entry->d_dev = hostEntry->d_dev; 271 #endif 272 entry->d_ino = hostEntry->d_ino; 273 strcpy(entry->d_name, hostEntry->d_name); 274 entry->d_reclen = recLen; 275 276 return 1; 277 } 278 279 private: 280 DIR *fDir; 281 }; 282 283 // HostFile 284 class HostFile : public File, public HostNode { 285 public: 286 HostFile() 287 : File(), 288 HostNode() 289 { 290 } 291 292 virtual ~HostFile() 293 { 294 } 295 296 virtual fssh_ssize_t Read(void *buffer, int size) 297 { 298 fssh_ssize_t bytesRead = read(fFD, buffer, size); 299 return (bytesRead >= 0 ? bytesRead : fssh_get_errno()); 300 } 301 302 virtual fssh_ssize_t Write(const void *buffer, int size) 303 { 304 fssh_ssize_t bytesWritten = write(fFD, buffer, size); 305 return (bytesWritten >= 0 ? bytesWritten : fssh_get_errno()); 306 } 307 }; 308 309 // HostSymLink 310 class HostSymLink : public SymLink, public HostNode { 311 public: 312 HostSymLink() 313 : SymLink(), 314 HostNode(), 315 fPath(NULL) 316 { 317 } 318 319 virtual ~HostSymLink() 320 { 321 if (fPath) 322 free(fPath); 323 } 324 325 virtual fssh_status_t Init(const char *path, int fd, 326 const struct fssh_stat &st) 327 { 328 fssh_status_t error = HostNode::Init(path, fd, st); 329 if (error != FSSH_B_OK) 330 return error; 331 332 fPath = strdup(path); 333 if (!fPath) 334 return FSSH_B_NO_MEMORY; 335 336 return FSSH_B_OK; 337 } 338 339 virtual fssh_ssize_t ReadLink(char *buffer, int bufferSize) 340 { 341 fssh_ssize_t bytesRead = readlink(fPath, buffer, bufferSize); 342 return (bytesRead >= 0 ? bytesRead : fssh_get_errno()); 343 } 344 345 private: 346 char *fPath; 347 }; 348 349 // HostFSDomain 350 class HostFSDomain : public FSDomain { 351 public: 352 HostFSDomain() {} 353 virtual ~HostFSDomain() {} 354 355 virtual fssh_status_t Open(const char *path, int openMode, Node *&_node) 356 { 357 // open the node 358 int fd = fssh_open(path, openMode); 359 if (fd < 0) 360 return fssh_get_errno(); 361 362 // stat the node 363 struct fssh_stat st; 364 if (fssh_fstat(fd, &st) < 0) { 365 fssh_close(fd); 366 return fssh_get_errno(); 367 } 368 369 // check the node type and create the node 370 HostNode *node = NULL; 371 switch (st.fssh_st_mode & FSSH_S_IFMT) { 372 case FSSH_S_IFLNK: 373 node = new HostSymLink; 374 break; 375 case FSSH_S_IFREG: 376 node = new HostFile; 377 break; 378 case FSSH_S_IFDIR: 379 node = new HostDirectory; 380 break; 381 default: 382 fssh_close(fd); 383 return FSSH_EINVAL; 384 } 385 386 // init the node 387 fssh_status_t error = node->Init(path, fd, st); 388 // the node receives ownership of the FD 389 if (error != FSSH_B_OK) { 390 delete node; 391 return error; 392 } 393 394 _node = node; 395 return FSSH_B_OK; 396 } 397 398 virtual fssh_status_t CreateFile(const char *path, 399 const struct fssh_stat &st, File *&_file) 400 { 401 // create the file 402 int fd = fssh_creat(path, st.fssh_st_mode & FSSH_S_IUMSK); 403 if (fd < 0) 404 return fssh_get_errno(); 405 406 // apply the other stat fields 407 fssh_status_t error = _ApplyStat(fd, st); 408 if (error != FSSH_B_OK) { 409 fssh_close(fd); 410 return error; 411 } 412 413 // create the object 414 HostFile *file = new HostFile; 415 error = file->Init(path, fd, st); 416 if (error != FSSH_B_OK) { 417 delete file; 418 return error; 419 } 420 421 _file = file; 422 return FSSH_B_OK; 423 } 424 425 virtual fssh_status_t CreateDirectory(const char *path, 426 const struct fssh_stat &st, Directory *&_dir) 427 { 428 // create the dir 429 if (fssh_mkdir(path, st.fssh_st_mode & FSSH_S_IUMSK) < 0) 430 return fssh_get_errno(); 431 432 // open the dir node 433 int fd = fssh_open(path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE); 434 if (fd < 0) 435 return fssh_get_errno(); 436 437 // apply the other stat fields 438 fssh_status_t error = _ApplyStat(fd, st); 439 if (error != FSSH_B_OK) { 440 fssh_close(fd); 441 return error; 442 } 443 444 // create the object 445 HostDirectory *dir = new HostDirectory; 446 error = dir->Init(path, fd, st); 447 if (error != FSSH_B_OK) { 448 delete dir; 449 return error; 450 } 451 452 _dir = dir; 453 return FSSH_B_OK; 454 } 455 456 virtual fssh_status_t CreateSymLink(const char *path, const char *linkTo, 457 const struct fssh_stat &st, SymLink *&_link) 458 { 459 // create the link 460 if (symlink(linkTo, path) < 0) 461 return fssh_get_errno(); 462 463 // open the symlink node 464 int fd = fssh_open(path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE); 465 if (fd < 0) 466 return fssh_get_errno(); 467 468 // apply the other stat fields 469 fssh_status_t error = _ApplyStat(fd, st); 470 if (error != FSSH_B_OK) { 471 fssh_close(fd); 472 return error; 473 } 474 475 // create the object 476 HostSymLink *link = new HostSymLink; 477 error = link->Init(path, fd, st); 478 if (error != FSSH_B_OK) { 479 delete link; 480 return error; 481 } 482 483 _link = link; 484 return FSSH_B_OK; 485 } 486 487 488 virtual fssh_status_t Unlink(const char *path) 489 { 490 if (fssh_unlink(path) < 0) 491 return fssh_get_errno(); 492 return FSSH_B_OK; 493 } 494 495 private: 496 fssh_status_t _ApplyStat(int fd, const struct fssh_stat &st) 497 { 498 // TODO: Set times... 499 return FSSH_B_OK; 500 } 501 }; 502 503 504 // #pragma mark - 505 506 // GuestNode 507 class GuestNode : public virtual Node { 508 public: 509 GuestNode() 510 : Node(), 511 fFD(-1), 512 fAttrDir(-1) 513 { 514 } 515 516 virtual ~GuestNode() 517 { 518 if (fFD >= 0) 519 _kern_close(fFD); 520 if (fAttrDir) 521 _kern_close(fAttrDir); 522 } 523 524 virtual fssh_status_t Init(const char *path, int fd, 525 const struct fssh_stat &st) 526 { 527 fFD = fd; 528 fStat = st; 529 530 // open the attribute directory 531 fAttrDir = _kern_open_attr_dir(fd, NULL); 532 if (fAttrDir < 0) 533 return fAttrDir; 534 535 return FSSH_B_OK; 536 } 537 538 virtual fssh_ssize_t GetNextAttr(char *name, int size) 539 { 540 if (fAttrDir < 0) 541 return 0; 542 543 char buffer[sizeof(fssh_dirent) + B_ATTR_NAME_LENGTH]; 544 struct fssh_dirent *entry = (fssh_dirent *)buffer; 545 int numRead = _kern_read_dir(fAttrDir, entry, sizeof(buffer), 1); 546 if (numRead < 0) 547 return numRead; 548 if (numRead == 0) 549 return 0; 550 551 int len = strlen(entry->d_name); 552 if (len >= size) 553 return FSSH_B_NAME_TOO_LONG; 554 555 strcpy(name, entry->d_name); 556 return 1; 557 } 558 559 virtual fssh_status_t GetAttrInfo(const char *name, fssh_attr_info &info) 560 { 561 // open attr 562 int attrFD = _kern_open_attr(fFD, name, FSSH_O_RDONLY); 563 if (attrFD < 0) 564 return attrFD; 565 566 // stat attr 567 struct fssh_stat st; 568 fssh_status_t error = _kern_read_stat(attrFD, NULL, false, &st, 569 sizeof(st)); 570 571 // close attr 572 _kern_close(attrFD); 573 574 if (error != FSSH_B_OK) 575 return error; 576 577 // convert stat to attr info 578 info.type = st.fssh_st_type; 579 info.size = st.fssh_st_size; 580 581 return FSSH_B_OK; 582 } 583 584 virtual fssh_ssize_t ReadAttr(const char *name, uint32_t type, 585 fssh_off_t pos, void *buffer, int size) 586 { 587 // open attr 588 int attrFD = _kern_open_attr(fFD, name, FSSH_O_RDONLY); 589 if (attrFD < 0) 590 return attrFD; 591 592 // stat attr 593 fssh_ssize_t bytesRead = _kern_read(attrFD, pos, buffer, size); 594 595 // close attr 596 _kern_close(attrFD); 597 598 return bytesRead; 599 } 600 601 virtual fssh_ssize_t WriteAttr(const char *name, uint32_t type, 602 fssh_off_t pos, const void *buffer, int size) 603 { 604 // open attr 605 int attrFD = _kern_create_attr(fFD, name, type, FSSH_O_WRONLY); 606 if (attrFD < 0) 607 return attrFD; 608 609 // stat attr 610 fssh_ssize_t bytesWritten = _kern_write(attrFD, pos, buffer, size); 611 612 // close attr 613 _kern_close(attrFD); 614 615 return bytesWritten; 616 } 617 618 virtual fssh_status_t RemoveAttr(const char *name) 619 { 620 return _kern_remove_attr(fFD, name); 621 } 622 623 protected: 624 int fFD; 625 int fAttrDir; 626 }; 627 628 // GuestDirectory 629 class GuestDirectory : public Directory, public GuestNode { 630 public: 631 GuestDirectory() 632 : Directory(), 633 GuestNode(), 634 fDir(-1) 635 { 636 } 637 638 virtual ~GuestDirectory() 639 { 640 if (fDir) 641 _kern_close(fDir); 642 } 643 644 virtual fssh_status_t Init(const char *path, int fd, 645 const struct fssh_stat &st) 646 { 647 fssh_status_t error = GuestNode::Init(path, fd, st); 648 if (error != FSSH_B_OK) 649 return error; 650 651 fDir = _kern_open_dir(fd, NULL); 652 if (fDir < 0) 653 return fDir; 654 655 return FSSH_B_OK; 656 } 657 658 virtual fssh_ssize_t GetNextEntry(struct fssh_dirent *entry, int size) 659 { 660 return _kern_read_dir(fDir, entry, size, 1); 661 } 662 663 private: 664 int fDir; 665 }; 666 667 // GuestFile 668 class GuestFile : public File, public GuestNode { 669 public: 670 GuestFile() 671 : File(), 672 GuestNode() 673 { 674 } 675 676 virtual ~GuestFile() 677 { 678 } 679 680 virtual fssh_ssize_t Read(void *buffer, int size) 681 { 682 return _kern_read(fFD, -1, buffer, size); 683 } 684 685 virtual fssh_ssize_t Write(const void *buffer, int size) 686 { 687 return _kern_write(fFD, -1, buffer, size); 688 } 689 }; 690 691 // GuestSymLink 692 class GuestSymLink : public SymLink, public GuestNode { 693 public: 694 GuestSymLink() 695 : SymLink(), 696 GuestNode() 697 { 698 } 699 700 virtual ~GuestSymLink() 701 { 702 } 703 704 virtual fssh_ssize_t ReadLink(char *buffer, int _bufferSize) 705 { 706 fssh_size_t bufferSize = _bufferSize; 707 fssh_status_t error = _kern_read_link(fFD, NULL, buffer, &bufferSize); 708 return (error == FSSH_B_OK ? bufferSize : error); 709 } 710 }; 711 712 // GuestFSDomain 713 class GuestFSDomain : public FSDomain { 714 public: 715 GuestFSDomain() {} 716 virtual ~GuestFSDomain() {} 717 718 virtual fssh_status_t Open(const char *path, int openMode, Node *&_node) 719 { 720 // open the node 721 int fd = _kern_open(-1, path, openMode, 0); 722 if (fd < 0) 723 return fd; 724 725 // stat the node 726 struct fssh_stat st; 727 fssh_status_t error = _kern_read_stat(fd, NULL, false, &st, sizeof(st)); 728 if (error < 0) { 729 _kern_close(fd); 730 return error; 731 } 732 733 // check the node type and create the node 734 GuestNode *node = NULL; 735 switch (st.fssh_st_mode & FSSH_S_IFMT) { 736 case FSSH_S_IFLNK: 737 node = new GuestSymLink; 738 break; 739 case FSSH_S_IFREG: 740 node = new GuestFile; 741 break; 742 case FSSH_S_IFDIR: 743 node = new GuestDirectory; 744 break; 745 default: 746 _kern_close(fd); 747 return FSSH_EINVAL; 748 } 749 750 // init the node 751 error = node->Init(path, fd, st); 752 // the node receives ownership of the FD 753 if (error != FSSH_B_OK) { 754 delete node; 755 return error; 756 } 757 758 _node = node; 759 return FSSH_B_OK; 760 } 761 762 virtual fssh_status_t CreateFile(const char *path, 763 const struct fssh_stat &st, File *&_file) 764 { 765 // create the file 766 int fd = _kern_open(-1, path, FSSH_O_RDWR | FSSH_O_EXCL | FSSH_O_CREAT, 767 st.fssh_st_mode & FSSH_S_IUMSK); 768 if (fd < 0) 769 return fd; 770 771 // apply the other stat fields 772 fssh_status_t error = _ApplyStat(fd, st); 773 if (error != FSSH_B_OK) { 774 _kern_close(fd); 775 return error; 776 } 777 778 // create the object 779 GuestFile *file = new GuestFile; 780 error = file->Init(path, fd, st); 781 if (error != FSSH_B_OK) { 782 delete file; 783 return error; 784 } 785 786 _file = file; 787 return FSSH_B_OK; 788 } 789 790 virtual fssh_status_t CreateDirectory(const char *path, 791 const struct fssh_stat &st, Directory *&_dir) 792 { 793 // create the dir 794 fssh_status_t error = _kern_create_dir(-1, path, 795 st.fssh_st_mode & FSSH_S_IUMSK); 796 if (error < 0) 797 return error; 798 799 // open the dir node 800 int fd = _kern_open(-1, path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 0); 801 if (fd < 0) 802 return fd; 803 804 // apply the other stat fields 805 error = _ApplyStat(fd, st); 806 if (error != FSSH_B_OK) { 807 _kern_close(fd); 808 return error; 809 } 810 811 // create the object 812 GuestDirectory *dir = new GuestDirectory; 813 error = dir->Init(path, fd, st); 814 if (error != FSSH_B_OK) { 815 delete dir; 816 return error; 817 } 818 819 _dir = dir; 820 return FSSH_B_OK; 821 } 822 823 virtual fssh_status_t CreateSymLink(const char *path, const char *linkTo, 824 const struct fssh_stat &st, SymLink *&_link) 825 { 826 // create the link 827 fssh_status_t error = _kern_create_symlink(-1, path, linkTo, 828 st.fssh_st_mode & FSSH_S_IUMSK); 829 if (error < 0) 830 return error; 831 832 // open the symlink node 833 int fd = _kern_open(-1, path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 0); 834 if (fd < 0) 835 return fd; 836 837 // apply the other stat fields 838 error = _ApplyStat(fd, st); 839 if (error != FSSH_B_OK) { 840 _kern_close(fd); 841 return error; 842 } 843 844 // create the object 845 GuestSymLink *link = new GuestSymLink; 846 error = link->Init(path, fd, st); 847 if (error != FSSH_B_OK) { 848 delete link; 849 return error; 850 } 851 852 _link = link; 853 return FSSH_B_OK; 854 } 855 856 virtual fssh_status_t Unlink(const char *path) 857 { 858 return _kern_unlink(-1, path); 859 } 860 861 private: 862 fssh_status_t _ApplyStat(int fd, const struct fssh_stat &st) 863 { 864 // TODO: Set times... 865 return FSSH_B_OK; 866 } 867 }; 868 869 870 // #pragma mark - 871 872 static fssh_status_t copy_entry(FSDomain *sourceDomain, const char *source, 873 FSDomain *targetDomain, const char *target, const Options &options, 874 bool dereference); 875 876 static FSDomain * 877 get_file_domain(const char *target, const char *&fsTarget) 878 { 879 if (target[0] == ':') { 880 fsTarget = target + 1; 881 return new HostFSDomain; 882 } else { 883 fsTarget = target; 884 return new GuestFSDomain; 885 } 886 } 887 888 typedef ObjectDeleter<Node> NodeDeleter; 889 typedef ObjectDeleter<FSDomain> DomainDeleter; 890 typedef MemoryDeleter PathDeleter; 891 892 893 static fssh_status_t 894 copy_file_contents(const char *source, File *sourceFile, const char *target, 895 File *targetFile) 896 { 897 fssh_off_t chunkSize = (sourceFile->Stat().fssh_st_size / 20) / sCopyBufferSize * sCopyBufferSize; 898 if (chunkSize == 0) 899 chunkSize = 1; 900 901 bool progress = sourceFile->Stat().fssh_st_size > 1024 * 1024; 902 if (progress) { 903 printf("%s ", strrchr(target, '/') ? strrchr(target, '/') + 1 : target); 904 fflush(stdout); 905 } 906 907 fssh_off_t total = 0; 908 fssh_ssize_t bytesRead; 909 while ((bytesRead = sourceFile->Read(sCopyBuffer, sCopyBufferSize)) > 0) { 910 fssh_ssize_t bytesWritten = targetFile->Write(sCopyBuffer, bytesRead); 911 if (progress && (total % chunkSize) == 0) { 912 putchar('.'); 913 fflush(stdout); 914 } 915 if (bytesWritten < 0) { 916 fprintf(stderr, "Error while writing to file `%s': %s\n", 917 target, fssh_strerror(bytesWritten)); 918 return bytesWritten; 919 } 920 if (bytesWritten != bytesRead) { 921 fprintf(stderr, "Could not write all data to file \"%s\".\n", 922 target); 923 return FSSH_B_IO_ERROR; 924 } 925 total += bytesWritten; 926 } 927 928 if (bytesRead < 0) { 929 fprintf(stderr, "Error while reading from file `%s': %s\n", 930 source, fssh_strerror(bytesRead)); 931 return bytesRead; 932 } 933 934 if (progress) 935 putchar('\n'); 936 937 return FSSH_B_OK; 938 } 939 940 941 static fssh_status_t 942 copy_dir_contents(FSDomain *sourceDomain, const char *source, 943 Directory *sourceDir, FSDomain *targetDomain, const char *target, 944 const Options &options) 945 { 946 char buffer[sizeof(fssh_dirent) + FSSH_B_FILE_NAME_LENGTH]; 947 struct fssh_dirent *entry = (struct fssh_dirent *)buffer; 948 fssh_ssize_t numRead; 949 while ((numRead = sourceDir->GetNextEntry(entry, sizeof(buffer))) > 0) { 950 // skip "." and ".." 951 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) 952 continue; 953 954 // compose a new source path name 955 char *sourceEntry = make_path(source, entry->d_name); 956 if (!sourceEntry) { 957 fprintf(stderr, "Error: Failed to allocate source path!\n"); 958 return FSSH_ENOMEM; 959 } 960 PathDeleter sourceDeleter(sourceEntry); 961 962 // compose a new target path name 963 char *targetEntry = make_path(target, entry->d_name); 964 if (!targetEntry) { 965 fprintf(stderr, "Error: Failed to allocate target path!\n"); 966 return FSSH_ENOMEM; 967 } 968 PathDeleter targetDeleter(targetEntry); 969 970 fssh_status_t error = copy_entry(sourceDomain, sourceEntry, 971 targetDomain, targetEntry, options, options.alwaysDereference); 972 if (error != FSSH_B_OK) 973 return error; 974 } 975 976 if (numRead < 0) { 977 fprintf(stderr, "Error reading directory `%s': %s\n", source, 978 fssh_strerror(numRead)); 979 return numRead; 980 } 981 982 return FSSH_B_OK; 983 } 984 985 986 static fssh_status_t 987 copy_attribute(const char *source, Node *sourceNode, const char *target, 988 Node *targetNode, const char *name, const fssh_attr_info &info) 989 { 990 // remove the attribute first 991 targetNode->RemoveAttr(name); 992 993 // special case: empty attribute 994 if (info.size <= 0) { 995 fssh_ssize_t bytesWritten = targetNode->WriteAttr(name, info.type, 0, 996 sCopyBuffer, 0); 997 if (bytesWritten) { 998 fprintf(stderr, "Error while writing to attribute `%s' of file " 999 "`%s': %s\n", name, target, fssh_strerror(bytesWritten)); 1000 return bytesWritten; 1001 } 1002 1003 return FSSH_B_OK; 1004 } 1005 1006 // non-empty attribute 1007 fssh_off_t pos = 0; 1008 int toCopy = info.size; 1009 while (toCopy > 0) { 1010 // read data from source 1011 int toRead = (toCopy < sCopyBufferSize ? toCopy : sCopyBufferSize); 1012 fssh_ssize_t bytesRead = sourceNode->ReadAttr(name, info.type, pos, 1013 sCopyBuffer, toRead); 1014 if (bytesRead < 0) { 1015 fprintf(stderr, "Error while reading from attribute `%s' of file " 1016 "`%s': %s\n", name, source, fssh_strerror(bytesRead)); 1017 return bytesRead; 1018 } 1019 1020 // write data to target 1021 fssh_ssize_t bytesWritten = targetNode->WriteAttr(name, info.type, pos, 1022 sCopyBuffer, bytesRead); 1023 if (bytesWritten < 0) { 1024 fprintf(stderr, "Error while writing to attribute `%s' of file " 1025 "`%s': %s\n", name, target, fssh_strerror(bytesWritten)); 1026 return bytesWritten; 1027 } 1028 1029 pos += bytesRead; 1030 toCopy -= bytesRead; 1031 } 1032 1033 return FSSH_B_OK; 1034 } 1035 1036 1037 static fssh_status_t 1038 copy_attributes(const char *source, Node *sourceNode, const char *target, 1039 Node *targetNode) 1040 { 1041 char name[B_ATTR_NAME_LENGTH]; 1042 fssh_ssize_t numRead; 1043 while ((numRead = sourceNode->GetNextAttr(name, sizeof(name))) > 0) { 1044 fssh_attr_info info; 1045 // get attribute info 1046 fssh_status_t error = sourceNode->GetAttrInfo(name, info); 1047 if (error != FSSH_B_OK) { 1048 fprintf(stderr, "Error getting info for attribute `%s' of file " 1049 "`%s': %s\n", name, source, fssh_strerror(error)); 1050 return error; 1051 } 1052 1053 // copy the attribute 1054 error = copy_attribute(source, sourceNode, target, targetNode, name, 1055 info); 1056 if (error != FSSH_B_OK) 1057 return error; 1058 } 1059 1060 if (numRead < 0) { 1061 fprintf(stderr, "Error reading attribute directory of `%s': %s\n", 1062 source, fssh_strerror(numRead)); 1063 return numRead; 1064 } 1065 1066 return FSSH_B_OK; 1067 } 1068 1069 1070 static fssh_status_t 1071 copy_entry(FSDomain *sourceDomain, const char *source, 1072 FSDomain *targetDomain, const char *target, const Options &options, 1073 bool dereference) 1074 { 1075 // apply entry filter 1076 if (!options.entryFilter.Filter(source)) 1077 return FSSH_B_OK; 1078 1079 // open the source node 1080 Node *sourceNode; 1081 fssh_status_t error = sourceDomain->Open(source, 1082 FSSH_O_RDONLY | (dereference ? 0 : FSSH_O_NOTRAVERSE), 1083 sourceNode); 1084 if (error != FSSH_B_OK) { 1085 fprintf(stderr, "Error: Failed to open source path `%s': %s\n", source, 1086 fssh_strerror(error)); 1087 return error; 1088 } 1089 NodeDeleter sourceDeleter(sourceNode); 1090 1091 // check, if target exists 1092 Node *targetNode = NULL; 1093 // try opening with resolving symlinks first 1094 error = targetDomain->Open(target, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 1095 targetNode); 1096 NodeDeleter targetDeleter; 1097 if (error == FSSH_B_OK) { 1098 // 1. target exists: 1099 // check, if it is a dir and, if so, whether source is a dir too 1100 targetDeleter.SetTo(targetNode); 1101 1102 // if the target is a symlink, try resolving it 1103 if (targetNode->IsSymLink()) { 1104 Node *resolvedTargetNode; 1105 error = targetDomain->Open(target, FSSH_O_RDONLY, 1106 resolvedTargetNode); 1107 if (error == FSSH_B_OK) { 1108 targetNode = resolvedTargetNode; 1109 targetDeleter.SetTo(targetNode); 1110 } 1111 } 1112 1113 if (sourceNode->IsDirectory() && targetNode->IsDirectory()) { 1114 // 1.1. target and source are dirs: 1115 // -> just copy their contents 1116 // ... 1117 } else { 1118 // 1.2. source and/or target are no dirs 1119 1120 if (options.force) { 1121 // 1.2.1. /force/ 1122 // -> remove the target and continue with 2. 1123 targetDeleter.Delete(); 1124 targetNode = NULL; 1125 error = targetDomain->Unlink(target); 1126 if (error != FSSH_B_OK) { 1127 fprintf(stderr, "Error: Failed to remove `%s'\n", target); 1128 return error; 1129 } 1130 } else if (sourceNode->IsFile() && targetNode->IsFile()) { 1131 // 1.2.1.1. !/force/, but both source and target are files 1132 // -> truncate the target file and continue 1133 targetDeleter.Delete(); 1134 targetNode = NULL; 1135 error = targetDomain->Open(target, FSSH_O_RDWR | FSSH_O_TRUNC, 1136 targetNode); 1137 if (error != FSSH_B_OK) { 1138 fprintf(stderr, "Error: Failed to open `%s' for writing\n", 1139 target); 1140 return error; 1141 } 1142 } else { 1143 // 1.2.1.2. !/force/, source or target isn't a file 1144 // -> fail 1145 fprintf(stderr, "Error: File `%s' does exist.\n", target); 1146 return FSSH_B_FILE_EXISTS; 1147 } 1148 } 1149 } // else: 2. target doesn't exist: -> just create it 1150 1151 // create the target node 1152 error = FSSH_B_OK; 1153 if (sourceNode->IsFile()) { 1154 if (!targetNode) { 1155 File *file = NULL; 1156 error = targetDomain->CreateFile(target, sourceNode->Stat(), file); 1157 if (error == 0) 1158 targetNode = file; 1159 } 1160 } else if (sourceNode->IsDirectory()) { 1161 // check /recursive/ 1162 if (!options.recursive) { 1163 fprintf(stderr, "Error: Entry `%s' is a directory.\n", source); 1164 return FSSH_EISDIR; 1165 } 1166 1167 // create the target only, if it doesn't already exist 1168 if (!targetNode) { 1169 Directory *dir = NULL; 1170 error = targetDomain->CreateDirectory(target, sourceNode->Stat(), 1171 dir); 1172 if (error == 0) 1173 targetNode = dir; 1174 } 1175 } else if (sourceNode->IsSymLink()) { 1176 // read the source link 1177 SymLink *sourceLink = sourceNode->ToSymLink(); 1178 char linkTo[FSSH_B_PATH_NAME_LENGTH]; 1179 fssh_ssize_t bytesRead = sourceLink->ReadLink(linkTo, 1180 sizeof(linkTo) - 1); 1181 if (bytesRead < 0) { 1182 fprintf(stderr, "Error: Failed to read symlink `%s': %s\n", source, 1183 fssh_strerror(bytesRead)); 1184 } 1185 linkTo[bytesRead] = '\0'; // always NULL-terminate 1186 1187 // create the target link 1188 SymLink *link; 1189 error = targetDomain->CreateSymLink(target, linkTo, 1190 sourceNode->Stat(), link); 1191 if (error == 0) 1192 targetNode = link; 1193 } else { 1194 fprintf(stderr, "Error: Unknown node type. We shouldn't be here!\n"); 1195 return FSSH_EINVAL; 1196 } 1197 1198 if (error != FSSH_B_OK) { 1199 fprintf(stderr, "Error: Failed to create `%s': %s\n", target, 1200 fssh_strerror(error)); 1201 return error; 1202 } 1203 targetDeleter.SetTo(targetNode); 1204 1205 // copy attributes 1206 if (!options.ignoreAttributes) { 1207 error = copy_attributes(source, sourceNode, target, targetNode); 1208 if (error != FSSH_B_OK) 1209 return error; 1210 } 1211 1212 // copy contents 1213 if (sourceNode->IsFile()) { 1214 error = copy_file_contents(source, sourceNode->ToFile(), target, 1215 targetNode->ToFile()); 1216 } else if (sourceNode->IsDirectory()) { 1217 error = copy_dir_contents(sourceDomain, source, 1218 sourceNode->ToDirectory(), targetDomain, target, options); 1219 } 1220 1221 return error; 1222 } 1223 1224 1225 fssh_status_t 1226 command_cp(int argc, const char* const* argv) 1227 { 1228 int sourceCount = 0; 1229 Options options; 1230 1231 const char **sources = new const char*[argc]; 1232 if (!sources) { 1233 fprintf(stderr, "Error: No memory!\n"); 1234 return FSSH_EINVAL; 1235 } 1236 ArrayDeleter<const char*> _(sources); 1237 1238 // parse parameters 1239 for (int argi = 1; argi < argc; argi++) { 1240 const char *arg = argv[argi]; 1241 if (arg[0] == '-') { 1242 if (arg[1] == '\0') { 1243 fprintf(stderr, "Error: Invalid option '-'\n"); 1244 return FSSH_EINVAL; 1245 } 1246 1247 if (arg[1] == '-') { 1248 if (strcmp(arg, "--ignore-attributes") == 0) { 1249 options.ignoreAttributes = true; 1250 } else { 1251 fprintf(stderr, "Error: Unknown option '%s'\n", arg); 1252 return FSSH_EINVAL; 1253 } 1254 } else { 1255 for (int i = 1; arg[i]; i++) { 1256 switch (arg[i]) { 1257 case 'a': 1258 options.attributesOnly = true; 1259 case 'd': 1260 options.dereference = false; 1261 break; 1262 case 'f': 1263 options.force = true; 1264 break; 1265 case 'L': 1266 options.dereference = true; 1267 options.alwaysDereference = true; 1268 break; 1269 case 'r': 1270 options.recursive = true; 1271 break; 1272 case 'x': 1273 case 'X': 1274 { 1275 const char* pattern; 1276 if (arg[i + 1] == '\0') { 1277 if (++argi >= argc) { 1278 fprintf(stderr, "Error: Option '-%c' need " 1279 "a pattern as parameter\n", arg[i]); 1280 return FSSH_EINVAL; 1281 } 1282 pattern = argv[argi]; 1283 } else 1284 pattern = arg + i + 1; 1285 1286 options.entryFilter.AddExcludeFilter(pattern, 1287 arg[i] == 'x'); 1288 break; 1289 } 1290 default: 1291 fprintf(stderr, "Error: Unknown option '-%c'\n", 1292 arg[i]); 1293 return FSSH_EINVAL; 1294 } 1295 } 1296 } 1297 } else { 1298 sources[sourceCount++] = arg; 1299 } 1300 } 1301 1302 // check params 1303 if (sourceCount < 2) { 1304 fprintf(stderr, "Error: Must specify at least 2 files!\n"); 1305 return FSSH_EINVAL; 1306 } 1307 1308 // check the target 1309 const char *target = sources[--sourceCount]; 1310 bool targetIsDir = false; 1311 bool targetExists = false; 1312 FSDomain *targetDomain = get_file_domain(target, target); 1313 DomainDeleter targetDomainDeleter(targetDomain); 1314 1315 Node *targetNode; 1316 fssh_status_t error = targetDomain->Open(target, FSSH_O_RDONLY, targetNode); 1317 if (error == 0) { 1318 NodeDeleter targetDeleter(targetNode); 1319 targetExists = true; 1320 1321 if (options.attributesOnly) { 1322 // That's how it should be; we don't care whether the target is 1323 // a directory or not. We append the attributes to that node in 1324 // either case. 1325 } else if (targetNode->IsDirectory()) { 1326 targetIsDir = true; 1327 } else { 1328 if (sourceCount > 1) { 1329 fprintf(stderr, "Error: Destination `%s' is not a directory!", 1330 target); 1331 return FSSH_B_NOT_A_DIRECTORY; 1332 } 1333 } 1334 } else { 1335 if (options.attributesOnly) { 1336 fprintf(stderr, "Error: Failed to open target `%s' (it must exist " 1337 "in attributes only mode): `%s'\n", target, 1338 fssh_strerror(error)); 1339 return error; 1340 } else if (sourceCount > 1) { 1341 fprintf(stderr, "Error: Failed to open destination directory `%s':" 1342 " `%s'\n", target, fssh_strerror(error)); 1343 return error; 1344 } 1345 } 1346 1347 // allocate a copy buffer 1348 sCopyBuffer = malloc(sCopyBufferSize); 1349 if (!sCopyBuffer) { 1350 fprintf(stderr, "Error: Failed to allocate copy buffer.\n"); 1351 return FSSH_ENOMEM; 1352 } 1353 MemoryDeleter copyBufferDeleter(sCopyBuffer); 1354 1355 // open the target node for attributes only mode 1356 NodeDeleter targetDeleter; 1357 if (options.attributesOnly) { 1358 error = targetDomain->Open(target, FSSH_O_WRONLY, targetNode); 1359 if (error != FSSH_B_OK) { 1360 fprintf(stderr, "Error: Failed to open target `%s' for writing: " 1361 "`%s'\n", target, fssh_strerror(error)); 1362 return error; 1363 } 1364 1365 targetDeleter.SetTo(targetNode); 1366 } 1367 1368 // the copy loop 1369 for (int i = 0; i < sourceCount; i++) { 1370 const char *source = sources[i]; 1371 FSDomain *sourceDomain = get_file_domain(source, source); 1372 DomainDeleter sourceDomainDeleter(sourceDomain); 1373 if (options.attributesOnly) { 1374 // 0. copy attributes only 1375 // open the source node 1376 Node *sourceNode; 1377 error = sourceDomain->Open(source, 1378 FSSH_O_RDONLY | (options.dereference ? 0 : FSSH_O_NOTRAVERSE), 1379 sourceNode); 1380 if (error != FSSH_B_OK) { 1381 fprintf(stderr, "Error: Failed to open `%s': %s.\n", source, 1382 fssh_strerror(error)); 1383 return error; 1384 } 1385 NodeDeleter sourceDeleter(sourceNode); 1386 1387 // copy the attributes 1388 error = copy_attributes(source, sourceNode, target, targetNode); 1389 1390 } else if (targetExists && targetIsDir) { 1391 // 1. target exists: 1392 // 1.1. target is a dir: 1393 // get the source leaf name 1394 char leafName[FSSH_B_FILE_NAME_LENGTH]; 1395 error = get_last_path_component(source, leafName, sizeof(leafName)); 1396 if (error != FSSH_B_OK) { 1397 fprintf(stderr, "Error: Failed to get last path component of " 1398 "`%s': %s\n", source, fssh_strerror(error)); 1399 return error; 1400 } 1401 1402 if (strcmp(leafName, ".") == 0 || strcmp(leafName, "..") == 0) { 1403 // 1.1.1. source name is `.' or `..' 1404 // -> copy the contents only 1405 // (copy_dir_contents()) 1406 // open the source dir 1407 Node *sourceNode; 1408 error = sourceDomain->Open(source, 1409 FSSH_O_RDONLY 1410 | (options.dereference ? 0 : FSSH_O_NOTRAVERSE), 1411 sourceNode); 1412 if (error != FSSH_B_OK) { 1413 fprintf(stderr, "Error: Failed to open `%s': %s.\n", source, 1414 fssh_strerror(error)); 1415 return error; 1416 } 1417 NodeDeleter sourceDeleter(sourceNode); 1418 1419 // check, if it is a dir 1420 Directory *sourceDir = sourceNode->ToDirectory(); 1421 if (!sourceDir) { 1422 fprintf(stderr, "Error: Source `%s' is not a directory " 1423 "although it's last path component is `%s'\n", source, 1424 leafName); 1425 return FSSH_EINVAL; 1426 } 1427 1428 error = copy_dir_contents(sourceDomain, source, sourceDir, 1429 targetDomain, target, options); 1430 } else { 1431 // 1.1.2. source has normal name 1432 // -> we copy into the dir 1433 // (copy_entry(<source>, <target>/<source leaf>)) 1434 // compose a new target path name 1435 char *targetEntry = make_path(target, leafName); 1436 if (!targetEntry) { 1437 fprintf(stderr, "Error: Failed to allocate target path!\n"); 1438 return FSSH_ENOMEM; 1439 } 1440 PathDeleter targetDeleter(targetEntry); 1441 1442 error = copy_entry(sourceDomain, source, targetDomain, 1443 targetEntry, options, options.dereference); 1444 } 1445 } else { 1446 // 1.2. target is no dir: 1447 // -> if /force/ is given, we replace the target, otherwise 1448 // we fail 1449 // (copy_entry(<source>, <target>)) 1450 // or 1451 // 2. target doesn't exist: 1452 // -> we create the target as a clone of the source 1453 // (copy_entry(<source>, <target>)) 1454 error = copy_entry(sourceDomain, source, targetDomain, target, 1455 options, options.dereference); 1456 } 1457 1458 if (error != 0) 1459 return error; 1460 } 1461 1462 return FSSH_B_OK; 1463 } 1464 1465 1466 } // namespace FSShell 1467