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 #if (defined(__BEOS__) || defined(__HAIKU__)) 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 // TODO: check if the file system supports attributes, and fail 534 } 535 536 return FSSH_B_OK; 537 } 538 539 virtual fssh_ssize_t GetNextAttr(char *name, int size) 540 { 541 if (fAttrDir < 0) 542 return 0; 543 544 char buffer[sizeof(fssh_dirent) + B_ATTR_NAME_LENGTH]; 545 struct fssh_dirent *entry = (fssh_dirent *)buffer; 546 int numRead = _kern_read_dir(fAttrDir, entry, sizeof(buffer), 1); 547 if (numRead < 0) 548 return numRead; 549 if (numRead == 0) 550 return 0; 551 552 int len = strlen(entry->d_name); 553 if (len >= size) 554 return FSSH_B_NAME_TOO_LONG; 555 556 strcpy(name, entry->d_name); 557 return 1; 558 } 559 560 virtual fssh_status_t GetAttrInfo(const char *name, fssh_attr_info &info) 561 { 562 // open attr 563 int attrFD = _kern_open_attr(fFD, name, FSSH_O_RDONLY); 564 if (attrFD < 0) 565 return attrFD; 566 567 // stat attr 568 struct fssh_stat st; 569 fssh_status_t error = _kern_read_stat(attrFD, NULL, false, &st, 570 sizeof(st)); 571 572 // close attr 573 _kern_close(attrFD); 574 575 if (error != FSSH_B_OK) 576 return error; 577 578 // convert stat to attr info 579 info.type = st.fssh_st_type; 580 info.size = st.fssh_st_size; 581 582 return FSSH_B_OK; 583 } 584 585 virtual fssh_ssize_t ReadAttr(const char *name, uint32_t type, 586 fssh_off_t pos, void *buffer, int size) 587 { 588 // open attr 589 int attrFD = _kern_open_attr(fFD, name, FSSH_O_RDONLY); 590 if (attrFD < 0) 591 return attrFD; 592 593 // stat attr 594 fssh_ssize_t bytesRead = _kern_read(attrFD, pos, buffer, size); 595 596 // close attr 597 _kern_close(attrFD); 598 599 return bytesRead; 600 } 601 602 virtual fssh_ssize_t WriteAttr(const char *name, uint32_t type, 603 fssh_off_t pos, const void *buffer, int size) 604 { 605 // open attr 606 int attrFD = _kern_create_attr(fFD, name, type, FSSH_O_WRONLY); 607 if (attrFD < 0) 608 return attrFD; 609 610 // stat attr 611 fssh_ssize_t bytesWritten = _kern_write(attrFD, pos, buffer, size); 612 613 // close attr 614 _kern_close(attrFD); 615 616 return bytesWritten; 617 } 618 619 virtual fssh_status_t RemoveAttr(const char *name) 620 { 621 return _kern_remove_attr(fFD, name); 622 } 623 624 protected: 625 int fFD; 626 int fAttrDir; 627 }; 628 629 // GuestDirectory 630 class GuestDirectory : public Directory, public GuestNode { 631 public: 632 GuestDirectory() 633 : Directory(), 634 GuestNode(), 635 fDir(-1) 636 { 637 } 638 639 virtual ~GuestDirectory() 640 { 641 if (fDir) 642 _kern_close(fDir); 643 } 644 645 virtual fssh_status_t Init(const char *path, int fd, 646 const struct fssh_stat &st) 647 { 648 fssh_status_t error = GuestNode::Init(path, fd, st); 649 if (error != FSSH_B_OK) 650 return error; 651 652 fDir = _kern_open_dir(fd, NULL); 653 if (fDir < 0) 654 return fDir; 655 656 return FSSH_B_OK; 657 } 658 659 virtual fssh_ssize_t GetNextEntry(struct fssh_dirent *entry, int size) 660 { 661 return _kern_read_dir(fDir, entry, size, 1); 662 } 663 664 private: 665 int fDir; 666 }; 667 668 // GuestFile 669 class GuestFile : public File, public GuestNode { 670 public: 671 GuestFile() 672 : File(), 673 GuestNode() 674 { 675 } 676 677 virtual ~GuestFile() 678 { 679 } 680 681 virtual fssh_ssize_t Read(void *buffer, int size) 682 { 683 return _kern_read(fFD, -1, buffer, size); 684 } 685 686 virtual fssh_ssize_t Write(const void *buffer, int size) 687 { 688 return _kern_write(fFD, -1, buffer, size); 689 } 690 }; 691 692 // GuestSymLink 693 class GuestSymLink : public SymLink, public GuestNode { 694 public: 695 GuestSymLink() 696 : SymLink(), 697 GuestNode() 698 { 699 } 700 701 virtual ~GuestSymLink() 702 { 703 } 704 705 virtual fssh_ssize_t ReadLink(char *buffer, int _bufferSize) 706 { 707 fssh_size_t bufferSize = _bufferSize; 708 fssh_status_t error = _kern_read_link(fFD, NULL, buffer, &bufferSize); 709 return (error == FSSH_B_OK ? bufferSize : error); 710 } 711 }; 712 713 // GuestFSDomain 714 class GuestFSDomain : public FSDomain { 715 public: 716 GuestFSDomain() {} 717 virtual ~GuestFSDomain() {} 718 719 virtual fssh_status_t Open(const char *path, int openMode, Node *&_node) 720 { 721 // open the node 722 int fd = _kern_open(-1, path, openMode, 0); 723 if (fd < 0) 724 return fd; 725 726 // stat the node 727 struct fssh_stat st; 728 fssh_status_t error = _kern_read_stat(fd, NULL, false, &st, sizeof(st)); 729 if (error < 0) { 730 _kern_close(fd); 731 return error; 732 } 733 734 // check the node type and create the node 735 GuestNode *node = NULL; 736 switch (st.fssh_st_mode & FSSH_S_IFMT) { 737 case FSSH_S_IFLNK: 738 node = new GuestSymLink; 739 break; 740 case FSSH_S_IFREG: 741 node = new GuestFile; 742 break; 743 case FSSH_S_IFDIR: 744 node = new GuestDirectory; 745 break; 746 default: 747 _kern_close(fd); 748 return FSSH_EINVAL; 749 } 750 751 // init the node 752 error = node->Init(path, fd, st); 753 // the node receives ownership of the FD 754 if (error != FSSH_B_OK) { 755 delete node; 756 return error; 757 } 758 759 _node = node; 760 return FSSH_B_OK; 761 } 762 763 virtual fssh_status_t CreateFile(const char *path, 764 const struct fssh_stat &st, File *&_file) 765 { 766 // create the file 767 int fd = _kern_open(-1, path, FSSH_O_RDWR | FSSH_O_EXCL | FSSH_O_CREAT, 768 st.fssh_st_mode & FSSH_S_IUMSK); 769 if (fd < 0) 770 return fd; 771 772 // apply the other stat fields 773 fssh_status_t error = _ApplyStat(fd, st); 774 if (error != FSSH_B_OK) { 775 _kern_close(fd); 776 return error; 777 } 778 779 // create the object 780 GuestFile *file = new GuestFile; 781 error = file->Init(path, fd, st); 782 if (error != FSSH_B_OK) { 783 delete file; 784 return error; 785 } 786 787 _file = file; 788 return FSSH_B_OK; 789 } 790 791 virtual fssh_status_t CreateDirectory(const char *path, 792 const struct fssh_stat &st, Directory *&_dir) 793 { 794 // create the dir 795 fssh_status_t error = _kern_create_dir(-1, path, 796 st.fssh_st_mode & FSSH_S_IUMSK); 797 if (error < 0) 798 return error; 799 800 // open the dir node 801 int fd = _kern_open(-1, path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 0); 802 if (fd < 0) 803 return fd; 804 805 // apply the other stat fields 806 error = _ApplyStat(fd, st); 807 if (error != FSSH_B_OK) { 808 _kern_close(fd); 809 return error; 810 } 811 812 // create the object 813 GuestDirectory *dir = new GuestDirectory; 814 error = dir->Init(path, fd, st); 815 if (error != FSSH_B_OK) { 816 delete dir; 817 return error; 818 } 819 820 _dir = dir; 821 return FSSH_B_OK; 822 } 823 824 virtual fssh_status_t CreateSymLink(const char *path, const char *linkTo, 825 const struct fssh_stat &st, SymLink *&_link) 826 { 827 // create the link 828 fssh_status_t error = _kern_create_symlink(-1, path, linkTo, 829 st.fssh_st_mode & FSSH_S_IUMSK); 830 if (error < 0) 831 return error; 832 833 // open the symlink node 834 int fd = _kern_open(-1, path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 0); 835 if (fd < 0) 836 return fd; 837 838 // apply the other stat fields 839 error = _ApplyStat(fd, st); 840 if (error != FSSH_B_OK) { 841 _kern_close(fd); 842 return error; 843 } 844 845 // create the object 846 GuestSymLink *link = new GuestSymLink; 847 error = link->Init(path, fd, st); 848 if (error != FSSH_B_OK) { 849 delete link; 850 return error; 851 } 852 853 _link = link; 854 return FSSH_B_OK; 855 } 856 857 virtual fssh_status_t Unlink(const char *path) 858 { 859 return _kern_unlink(-1, path); 860 } 861 862 private: 863 fssh_status_t _ApplyStat(int fd, const struct fssh_stat &st) 864 { 865 // TODO: Set times... 866 return FSSH_B_OK; 867 } 868 }; 869 870 871 // #pragma mark - 872 873 static fssh_status_t copy_entry(FSDomain *sourceDomain, const char *source, 874 FSDomain *targetDomain, const char *target, const Options &options, 875 bool dereference); 876 877 static FSDomain * 878 get_file_domain(const char *target, const char *&fsTarget) 879 { 880 if (target[0] == ':') { 881 fsTarget = target + 1; 882 return new HostFSDomain; 883 } else { 884 fsTarget = target; 885 return new GuestFSDomain; 886 } 887 } 888 889 typedef ObjectDeleter<Node> NodeDeleter; 890 typedef ObjectDeleter<FSDomain> DomainDeleter; 891 typedef MemoryDeleter PathDeleter; 892 893 894 static fssh_status_t 895 copy_file_contents(const char *source, File *sourceFile, const char *target, 896 File *targetFile) 897 { 898 fssh_off_t chunkSize = (sourceFile->Stat().fssh_st_size / 20) / sCopyBufferSize * sCopyBufferSize; 899 if (chunkSize == 0) 900 chunkSize = 1; 901 902 bool progress = sourceFile->Stat().fssh_st_size > 1024 * 1024; 903 if (progress) { 904 printf("%s ", strrchr(target, '/') ? strrchr(target, '/') + 1 : target); 905 fflush(stdout); 906 } 907 908 fssh_off_t total = 0; 909 fssh_ssize_t bytesRead; 910 while ((bytesRead = sourceFile->Read(sCopyBuffer, sCopyBufferSize)) > 0) { 911 fssh_ssize_t bytesWritten = targetFile->Write(sCopyBuffer, bytesRead); 912 if (progress && (total % chunkSize) == 0) { 913 putchar('.'); 914 fflush(stdout); 915 } 916 if (bytesWritten < 0) { 917 fprintf(stderr, "Error while writing to file `%s': %s\n", 918 target, fssh_strerror(bytesWritten)); 919 return bytesWritten; 920 } 921 if (bytesWritten != bytesRead) { 922 fprintf(stderr, "Could not write all data to file \"%s\".\n", 923 target); 924 return FSSH_B_IO_ERROR; 925 } 926 total += bytesWritten; 927 } 928 929 if (bytesRead < 0) { 930 fprintf(stderr, "Error while reading from file `%s': %s\n", 931 source, fssh_strerror(bytesRead)); 932 return bytesRead; 933 } 934 935 if (progress) 936 putchar('\n'); 937 938 return FSSH_B_OK; 939 } 940 941 942 static fssh_status_t 943 copy_dir_contents(FSDomain *sourceDomain, const char *source, 944 Directory *sourceDir, FSDomain *targetDomain, const char *target, 945 const Options &options) 946 { 947 char buffer[sizeof(fssh_dirent) + FSSH_B_FILE_NAME_LENGTH]; 948 struct fssh_dirent *entry = (struct fssh_dirent *)buffer; 949 fssh_ssize_t numRead; 950 while ((numRead = sourceDir->GetNextEntry(entry, sizeof(buffer))) > 0) { 951 // skip "." and ".." 952 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) 953 continue; 954 955 // compose a new source path name 956 char *sourceEntry = make_path(source, entry->d_name); 957 if (!sourceEntry) { 958 fprintf(stderr, "Error: Failed to allocate source path!\n"); 959 return FSSH_ENOMEM; 960 } 961 PathDeleter sourceDeleter(sourceEntry); 962 963 // compose a new target path name 964 char *targetEntry = make_path(target, entry->d_name); 965 if (!targetEntry) { 966 fprintf(stderr, "Error: Failed to allocate target path!\n"); 967 return FSSH_ENOMEM; 968 } 969 PathDeleter targetDeleter(targetEntry); 970 971 fssh_status_t error = copy_entry(sourceDomain, sourceEntry, 972 targetDomain, targetEntry, options, options.alwaysDereference); 973 if (error != FSSH_B_OK) 974 return error; 975 } 976 977 if (numRead < 0) { 978 fprintf(stderr, "Error reading directory `%s': %s\n", source, 979 fssh_strerror(numRead)); 980 return numRead; 981 } 982 983 return FSSH_B_OK; 984 } 985 986 987 static fssh_status_t 988 copy_attribute(const char *source, Node *sourceNode, const char *target, 989 Node *targetNode, const char *name, const fssh_attr_info &info) 990 { 991 // remove the attribute first 992 targetNode->RemoveAttr(name); 993 994 // special case: empty attribute 995 if (info.size <= 0) { 996 fssh_ssize_t bytesWritten = targetNode->WriteAttr(name, info.type, 0, 997 sCopyBuffer, 0); 998 if (bytesWritten) { 999 fprintf(stderr, "Error while writing to attribute `%s' of file " 1000 "`%s': %s\n", name, target, fssh_strerror(bytesWritten)); 1001 return bytesWritten; 1002 } 1003 1004 return FSSH_B_OK; 1005 } 1006 1007 // non-empty attribute 1008 fssh_off_t pos = 0; 1009 int toCopy = info.size; 1010 while (toCopy > 0) { 1011 // read data from source 1012 int toRead = (toCopy < sCopyBufferSize ? toCopy : sCopyBufferSize); 1013 fssh_ssize_t bytesRead = sourceNode->ReadAttr(name, info.type, pos, 1014 sCopyBuffer, toRead); 1015 if (bytesRead < 0) { 1016 fprintf(stderr, "Error while reading from attribute `%s' of file " 1017 "`%s': %s\n", name, source, fssh_strerror(bytesRead)); 1018 return bytesRead; 1019 } 1020 1021 // write data to target 1022 fssh_ssize_t bytesWritten = targetNode->WriteAttr(name, info.type, pos, 1023 sCopyBuffer, bytesRead); 1024 if (bytesWritten < 0) { 1025 fprintf(stderr, "Error while writing to attribute `%s' of file " 1026 "`%s': %s\n", name, target, fssh_strerror(bytesWritten)); 1027 return bytesWritten; 1028 } 1029 1030 pos += bytesRead; 1031 toCopy -= bytesRead; 1032 } 1033 1034 return FSSH_B_OK; 1035 } 1036 1037 1038 static fssh_status_t 1039 copy_attributes(const char *source, Node *sourceNode, const char *target, 1040 Node *targetNode) 1041 { 1042 char name[B_ATTR_NAME_LENGTH]; 1043 fssh_ssize_t numRead; 1044 while ((numRead = sourceNode->GetNextAttr(name, sizeof(name))) > 0) { 1045 fssh_attr_info info; 1046 // get attribute info 1047 fssh_status_t error = sourceNode->GetAttrInfo(name, info); 1048 if (error != FSSH_B_OK) { 1049 fprintf(stderr, "Error getting info for attribute `%s' of file " 1050 "`%s': %s\n", name, source, fssh_strerror(error)); 1051 return error; 1052 } 1053 1054 // copy the attribute 1055 error = copy_attribute(source, sourceNode, target, targetNode, name, 1056 info); 1057 if (error != FSSH_B_OK) 1058 return error; 1059 } 1060 1061 if (numRead < 0) { 1062 fprintf(stderr, "Error reading attribute directory of `%s': %s\n", 1063 source, fssh_strerror(numRead)); 1064 return numRead; 1065 } 1066 1067 return FSSH_B_OK; 1068 } 1069 1070 1071 static fssh_status_t 1072 copy_entry(FSDomain *sourceDomain, const char *source, 1073 FSDomain *targetDomain, const char *target, const Options &options, 1074 bool dereference) 1075 { 1076 // apply entry filter 1077 if (!options.entryFilter.Filter(source)) 1078 return FSSH_B_OK; 1079 1080 // open the source node 1081 Node *sourceNode; 1082 fssh_status_t error = sourceDomain->Open(source, 1083 FSSH_O_RDONLY | (dereference ? 0 : FSSH_O_NOTRAVERSE), 1084 sourceNode); 1085 if (error != FSSH_B_OK) { 1086 fprintf(stderr, "Error: Failed to open source path `%s': %s\n", source, 1087 fssh_strerror(error)); 1088 return error; 1089 } 1090 NodeDeleter sourceDeleter(sourceNode); 1091 1092 // check, if target exists 1093 Node *targetNode = NULL; 1094 // try opening with resolving symlinks first 1095 error = targetDomain->Open(target, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 1096 targetNode); 1097 NodeDeleter targetDeleter; 1098 if (error == FSSH_B_OK) { 1099 // 1. target exists: 1100 // check, if it is a dir and, if so, whether source is a dir too 1101 targetDeleter.SetTo(targetNode); 1102 1103 // if the target is a symlink, try resolving it 1104 if (targetNode->IsSymLink()) { 1105 Node *resolvedTargetNode; 1106 error = targetDomain->Open(target, FSSH_O_RDONLY, 1107 resolvedTargetNode); 1108 if (error == FSSH_B_OK) { 1109 targetNode = resolvedTargetNode; 1110 targetDeleter.SetTo(targetNode); 1111 } 1112 } 1113 1114 if (sourceNode->IsDirectory() && targetNode->IsDirectory()) { 1115 // 1.1. target and source are dirs: 1116 // -> just copy their contents 1117 // ... 1118 } else { 1119 // 1.2. source and/or target are no dirs 1120 1121 if (options.force) { 1122 // 1.2.1. /force/ 1123 // -> remove the target and continue with 2. 1124 targetDeleter.Delete(); 1125 targetNode = NULL; 1126 error = targetDomain->Unlink(target); 1127 if (error != FSSH_B_OK) { 1128 fprintf(stderr, "Error: Failed to remove `%s'\n", target); 1129 return error; 1130 } 1131 } else if (sourceNode->IsFile() && targetNode->IsFile()) { 1132 // 1.2.1.1. !/force/, but both source and target are files 1133 // -> truncate the target file and continue 1134 targetDeleter.Delete(); 1135 targetNode = NULL; 1136 error = targetDomain->Open(target, FSSH_O_RDWR | FSSH_O_TRUNC, 1137 targetNode); 1138 if (error != FSSH_B_OK) { 1139 fprintf(stderr, "Error: Failed to open `%s' for writing\n", 1140 target); 1141 return error; 1142 } 1143 } else { 1144 // 1.2.1.2. !/force/, source or target isn't a file 1145 // -> fail 1146 fprintf(stderr, "Error: File `%s' does exist.\n", target); 1147 return FSSH_B_FILE_EXISTS; 1148 } 1149 } 1150 } // else: 2. target doesn't exist: -> just create it 1151 1152 // create the target node 1153 error = FSSH_B_OK; 1154 if (sourceNode->IsFile()) { 1155 if (!targetNode) { 1156 File *file = NULL; 1157 error = targetDomain->CreateFile(target, sourceNode->Stat(), file); 1158 if (error == 0) 1159 targetNode = file; 1160 } 1161 } else if (sourceNode->IsDirectory()) { 1162 // check /recursive/ 1163 if (!options.recursive) { 1164 fprintf(stderr, "Error: Entry `%s' is a directory.\n", source); 1165 return FSSH_EISDIR; 1166 } 1167 1168 // create the target only, if it doesn't already exist 1169 if (!targetNode) { 1170 Directory *dir = NULL; 1171 error = targetDomain->CreateDirectory(target, sourceNode->Stat(), 1172 dir); 1173 if (error == 0) 1174 targetNode = dir; 1175 } 1176 } else if (sourceNode->IsSymLink()) { 1177 // read the source link 1178 SymLink *sourceLink = sourceNode->ToSymLink(); 1179 char linkTo[FSSH_B_PATH_NAME_LENGTH]; 1180 fssh_ssize_t bytesRead = sourceLink->ReadLink(linkTo, 1181 sizeof(linkTo) - 1); 1182 if (bytesRead < 0) { 1183 fprintf(stderr, "Error: Failed to read symlink `%s': %s\n", source, 1184 fssh_strerror(bytesRead)); 1185 } 1186 linkTo[bytesRead] = '\0'; // always NULL-terminate 1187 1188 // create the target link 1189 SymLink *link; 1190 error = targetDomain->CreateSymLink(target, linkTo, 1191 sourceNode->Stat(), link); 1192 if (error == 0) 1193 targetNode = link; 1194 } else { 1195 fprintf(stderr, "Error: Unknown node type. We shouldn't be here!\n"); 1196 return FSSH_EINVAL; 1197 } 1198 1199 if (error != FSSH_B_OK) { 1200 fprintf(stderr, "Error: Failed to create `%s': %s\n", target, 1201 fssh_strerror(error)); 1202 return error; 1203 } 1204 targetDeleter.SetTo(targetNode); 1205 1206 // copy attributes 1207 if (!options.ignoreAttributes) { 1208 error = copy_attributes(source, sourceNode, target, targetNode); 1209 if (error != FSSH_B_OK) 1210 return error; 1211 } 1212 1213 // copy contents 1214 if (sourceNode->IsFile()) { 1215 error = copy_file_contents(source, sourceNode->ToFile(), target, 1216 targetNode->ToFile()); 1217 } else if (sourceNode->IsDirectory()) { 1218 error = copy_dir_contents(sourceDomain, source, 1219 sourceNode->ToDirectory(), targetDomain, target, options); 1220 } 1221 1222 return error; 1223 } 1224 1225 1226 fssh_status_t 1227 command_cp(int argc, const char* const* argv) 1228 { 1229 int sourceCount = 0; 1230 Options options; 1231 1232 const char **sources = new const char*[argc]; 1233 if (!sources) { 1234 fprintf(stderr, "Error: No memory!\n"); 1235 return FSSH_EINVAL; 1236 } 1237 ArrayDeleter<const char*> _(sources); 1238 1239 // parse parameters 1240 for (int argi = 1; argi < argc; argi++) { 1241 const char *arg = argv[argi]; 1242 if (arg[0] == '-') { 1243 if (arg[1] == '\0') { 1244 fprintf(stderr, "Error: Invalid option '-'\n"); 1245 return FSSH_EINVAL; 1246 } 1247 1248 if (arg[1] == '-') { 1249 if (strcmp(arg, "--ignore-attributes") == 0) { 1250 options.ignoreAttributes = true; 1251 } else { 1252 fprintf(stderr, "Error: Unknown option '%s'\n", arg); 1253 return FSSH_EINVAL; 1254 } 1255 } else { 1256 for (int i = 1; arg[i]; i++) { 1257 switch (arg[i]) { 1258 case 'a': 1259 options.attributesOnly = true; 1260 break; 1261 case 'd': 1262 options.dereference = false; 1263 break; 1264 case 'f': 1265 options.force = true; 1266 break; 1267 case 'L': 1268 options.dereference = true; 1269 options.alwaysDereference = true; 1270 break; 1271 case 'r': 1272 options.recursive = true; 1273 break; 1274 case 'x': 1275 case 'X': 1276 { 1277 const char* pattern; 1278 if (arg[i + 1] == '\0') { 1279 if (++argi >= argc) { 1280 fprintf(stderr, "Error: Option '-%c' need " 1281 "a pattern as parameter\n", arg[i]); 1282 return FSSH_EINVAL; 1283 } 1284 pattern = argv[argi]; 1285 } else 1286 pattern = arg + i + 1; 1287 1288 options.entryFilter.AddExcludeFilter(pattern, 1289 arg[i] == 'x'); 1290 break; 1291 } 1292 default: 1293 fprintf(stderr, "Error: Unknown option '-%c'\n", 1294 arg[i]); 1295 return FSSH_EINVAL; 1296 } 1297 } 1298 } 1299 } else { 1300 sources[sourceCount++] = arg; 1301 } 1302 } 1303 1304 // check params 1305 if (sourceCount < 2) { 1306 fprintf(stderr, "Error: Must specify at least 2 files!\n"); 1307 return FSSH_EINVAL; 1308 } 1309 1310 // check the target 1311 const char *target = sources[--sourceCount]; 1312 bool targetIsDir = false; 1313 bool targetExists = false; 1314 FSDomain *targetDomain = get_file_domain(target, target); 1315 DomainDeleter targetDomainDeleter(targetDomain); 1316 1317 Node *targetNode; 1318 fssh_status_t error = targetDomain->Open(target, FSSH_O_RDONLY, targetNode); 1319 if (error == 0) { 1320 NodeDeleter targetDeleter(targetNode); 1321 targetExists = true; 1322 1323 if (options.attributesOnly) { 1324 // That's how it should be; we don't care whether the target is 1325 // a directory or not. We append the attributes to that node in 1326 // either case. 1327 } else if (targetNode->IsDirectory()) { 1328 targetIsDir = true; 1329 } else { 1330 if (sourceCount > 1) { 1331 fprintf(stderr, "Error: Destination `%s' is not a directory!", 1332 target); 1333 return FSSH_B_NOT_A_DIRECTORY; 1334 } 1335 } 1336 } else { 1337 if (options.attributesOnly) { 1338 fprintf(stderr, "Error: Failed to open target `%s' (it must exist " 1339 "in attributes only mode): `%s'\n", target, 1340 fssh_strerror(error)); 1341 return error; 1342 } else if (sourceCount > 1) { 1343 fprintf(stderr, "Error: Failed to open destination directory `%s':" 1344 " `%s'\n", target, fssh_strerror(error)); 1345 return error; 1346 } 1347 } 1348 1349 // allocate a copy buffer 1350 sCopyBuffer = malloc(sCopyBufferSize); 1351 if (!sCopyBuffer) { 1352 fprintf(stderr, "Error: Failed to allocate copy buffer.\n"); 1353 return FSSH_ENOMEM; 1354 } 1355 MemoryDeleter copyBufferDeleter(sCopyBuffer); 1356 1357 // open the target node for attributes only mode 1358 NodeDeleter targetDeleter; 1359 if (options.attributesOnly) { 1360 error = targetDomain->Open(target, FSSH_O_RDONLY, targetNode); 1361 if (error != FSSH_B_OK) { 1362 fprintf(stderr, "Error: Failed to open target `%s' for writing: " 1363 "`%s'\n", target, fssh_strerror(error)); 1364 return error; 1365 } 1366 1367 targetDeleter.SetTo(targetNode); 1368 } 1369 1370 // the copy loop 1371 for (int i = 0; i < sourceCount; i++) { 1372 const char *source = sources[i]; 1373 FSDomain *sourceDomain = get_file_domain(source, source); 1374 DomainDeleter sourceDomainDeleter(sourceDomain); 1375 if (options.attributesOnly) { 1376 // 0. copy attributes only 1377 // open the source node 1378 Node *sourceNode; 1379 error = sourceDomain->Open(source, 1380 FSSH_O_RDONLY | (options.dereference ? 0 : FSSH_O_NOTRAVERSE), 1381 sourceNode); 1382 if (error != FSSH_B_OK) { 1383 fprintf(stderr, "Error: Failed to open `%s': %s.\n", source, 1384 fssh_strerror(error)); 1385 return error; 1386 } 1387 NodeDeleter sourceDeleter(sourceNode); 1388 1389 // copy the attributes 1390 error = copy_attributes(source, sourceNode, target, targetNode); 1391 1392 } else if (targetExists && targetIsDir) { 1393 // 1. target exists: 1394 // 1.1. target is a dir: 1395 // get the source leaf name 1396 char leafName[FSSH_B_FILE_NAME_LENGTH]; 1397 error = get_last_path_component(source, leafName, sizeof(leafName)); 1398 if (error != FSSH_B_OK) { 1399 fprintf(stderr, "Error: Failed to get last path component of " 1400 "`%s': %s\n", source, fssh_strerror(error)); 1401 return error; 1402 } 1403 1404 if (strcmp(leafName, ".") == 0 || strcmp(leafName, "..") == 0) { 1405 // 1.1.1. source name is `.' or `..' 1406 // -> copy the contents only 1407 // (copy_dir_contents()) 1408 // open the source dir 1409 Node *sourceNode; 1410 error = sourceDomain->Open(source, 1411 FSSH_O_RDONLY 1412 | (options.dereference ? 0 : FSSH_O_NOTRAVERSE), 1413 sourceNode); 1414 if (error != FSSH_B_OK) { 1415 fprintf(stderr, "Error: Failed to open `%s': %s.\n", source, 1416 fssh_strerror(error)); 1417 return error; 1418 } 1419 NodeDeleter sourceDeleter(sourceNode); 1420 1421 // check, if it is a dir 1422 Directory *sourceDir = sourceNode->ToDirectory(); 1423 if (!sourceDir) { 1424 fprintf(stderr, "Error: Source `%s' is not a directory " 1425 "although it's last path component is `%s'\n", source, 1426 leafName); 1427 return FSSH_EINVAL; 1428 } 1429 1430 error = copy_dir_contents(sourceDomain, source, sourceDir, 1431 targetDomain, target, options); 1432 } else { 1433 // 1.1.2. source has normal name 1434 // -> we copy into the dir 1435 // (copy_entry(<source>, <target>/<source leaf>)) 1436 // compose a new target path name 1437 char *targetEntry = make_path(target, leafName); 1438 if (!targetEntry) { 1439 fprintf(stderr, "Error: Failed to allocate target path!\n"); 1440 return FSSH_ENOMEM; 1441 } 1442 PathDeleter targetDeleter(targetEntry); 1443 1444 error = copy_entry(sourceDomain, source, targetDomain, 1445 targetEntry, options, options.dereference); 1446 } 1447 } else { 1448 // 1.2. target is no dir: 1449 // -> if /force/ is given, we replace the target, otherwise 1450 // we fail 1451 // (copy_entry(<source>, <target>)) 1452 // or 1453 // 2. target doesn't exist: 1454 // -> we create the target as a clone of the source 1455 // (copy_entry(<source>, <target>)) 1456 error = copy_entry(sourceDomain, source, targetDomain, target, 1457 options, options.dereference); 1458 } 1459 1460 if (error != 0) 1461 return error; 1462 } 1463 1464 return FSSH_B_OK; 1465 } 1466 1467 1468 } // namespace FSShell 1469