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 targetDeleter.SetTo(targetNode); 1161 } 1162 } 1163 } else if (sourceNode->IsDirectory()) { 1164 // check /recursive/ 1165 if (!options.recursive) { 1166 fprintf(stderr, "Error: Entry `%s' is a directory.\n", source); 1167 return FSSH_EISDIR; 1168 } 1169 1170 // create the target only, if it doesn't already exist 1171 if (!targetNode) { 1172 Directory *dir = NULL; 1173 error = targetDomain->CreateDirectory(target, sourceNode->Stat(), 1174 dir); 1175 if (error == 0) { 1176 targetNode = dir; 1177 targetDeleter.SetTo(targetNode); 1178 } 1179 } 1180 } else if (sourceNode->IsSymLink()) { 1181 // read the source link 1182 SymLink *sourceLink = sourceNode->ToSymLink(); 1183 char linkTo[FSSH_B_PATH_NAME_LENGTH]; 1184 fssh_ssize_t bytesRead = sourceLink->ReadLink(linkTo, 1185 sizeof(linkTo) - 1); 1186 if (bytesRead < 0) { 1187 fprintf(stderr, "Error: Failed to read symlink `%s': %s\n", source, 1188 fssh_strerror(bytesRead)); 1189 } 1190 linkTo[bytesRead] = '\0'; // always NULL-terminate 1191 1192 // create the target link 1193 SymLink *link; 1194 error = targetDomain->CreateSymLink(target, linkTo, 1195 sourceNode->Stat(), link); 1196 if (error == 0) { 1197 targetNode = link; 1198 targetDeleter.SetTo(targetNode); 1199 } 1200 } else { 1201 fprintf(stderr, "Error: Unknown node type. We shouldn't be here!\n"); 1202 return FSSH_EINVAL; 1203 } 1204 1205 if (error != FSSH_B_OK) { 1206 fprintf(stderr, "Error: Failed to create `%s': %s\n", target, 1207 fssh_strerror(error)); 1208 return error; 1209 } 1210 1211 // copy attributes 1212 if (!options.ignoreAttributes) { 1213 error = copy_attributes(source, sourceNode, target, targetNode); 1214 if (error != FSSH_B_OK) 1215 return error; 1216 } 1217 1218 // copy contents 1219 if (sourceNode->IsFile()) { 1220 error = copy_file_contents(source, sourceNode->ToFile(), target, 1221 targetNode->ToFile()); 1222 } else if (sourceNode->IsDirectory()) { 1223 error = copy_dir_contents(sourceDomain, source, 1224 sourceNode->ToDirectory(), targetDomain, target, options); 1225 } 1226 1227 return error; 1228 } 1229 1230 1231 fssh_status_t 1232 command_cp(int argc, const char* const* argv) 1233 { 1234 int sourceCount = 0; 1235 Options options; 1236 1237 const char **sources = new const char*[argc]; 1238 if (!sources) { 1239 fprintf(stderr, "Error: No memory!\n"); 1240 return FSSH_EINVAL; 1241 } 1242 ArrayDeleter<const char*> _(sources); 1243 1244 // parse parameters 1245 for (int argi = 1; argi < argc; argi++) { 1246 const char *arg = argv[argi]; 1247 if (arg[0] == '-') { 1248 if (arg[1] == '\0') { 1249 fprintf(stderr, "Error: Invalid option '-'\n"); 1250 return FSSH_EINVAL; 1251 } 1252 1253 if (arg[1] == '-') { 1254 if (strcmp(arg, "--ignore-attributes") == 0) { 1255 options.ignoreAttributes = true; 1256 } else { 1257 fprintf(stderr, "Error: Unknown option '%s'\n", arg); 1258 return FSSH_EINVAL; 1259 } 1260 } else { 1261 for (int i = 1; arg[i]; i++) { 1262 switch (arg[i]) { 1263 case 'a': 1264 options.attributesOnly = true; 1265 break; 1266 case 'd': 1267 options.dereference = false; 1268 break; 1269 case 'f': 1270 options.force = true; 1271 break; 1272 case 'L': 1273 options.dereference = true; 1274 options.alwaysDereference = true; 1275 break; 1276 case 'r': 1277 options.recursive = true; 1278 break; 1279 case 'x': 1280 case 'X': 1281 { 1282 const char* pattern; 1283 if (arg[i + 1] == '\0') { 1284 if (++argi >= argc) { 1285 fprintf(stderr, "Error: Option '-%c' need " 1286 "a pattern as parameter\n", arg[i]); 1287 return FSSH_EINVAL; 1288 } 1289 pattern = argv[argi]; 1290 } else 1291 pattern = arg + i + 1; 1292 1293 options.entryFilter.AddExcludeFilter(pattern, 1294 arg[i] == 'x'); 1295 break; 1296 } 1297 default: 1298 fprintf(stderr, "Error: Unknown option '-%c'\n", 1299 arg[i]); 1300 return FSSH_EINVAL; 1301 } 1302 } 1303 } 1304 } else { 1305 sources[sourceCount++] = arg; 1306 } 1307 } 1308 1309 // check params 1310 if (sourceCount < 2) { 1311 fprintf(stderr, "Error: Must specify at least 2 files!\n"); 1312 return FSSH_EINVAL; 1313 } 1314 1315 // check the target 1316 const char *target = sources[--sourceCount]; 1317 bool targetIsDir = false; 1318 bool targetExists = false; 1319 FSDomain *targetDomain = get_file_domain(target, target); 1320 DomainDeleter targetDomainDeleter(targetDomain); 1321 1322 Node *targetNode; 1323 fssh_status_t error = targetDomain->Open(target, FSSH_O_RDONLY, targetNode); 1324 if (error == 0) { 1325 NodeDeleter targetDeleter(targetNode); 1326 targetExists = true; 1327 1328 if (options.attributesOnly) { 1329 // That's how it should be; we don't care whether the target is 1330 // a directory or not. We append the attributes to that node in 1331 // either case. 1332 } else if (targetNode->IsDirectory()) { 1333 targetIsDir = true; 1334 } else { 1335 if (sourceCount > 1) { 1336 fprintf(stderr, "Error: Destination `%s' is not a directory!", 1337 target); 1338 return FSSH_B_NOT_A_DIRECTORY; 1339 } 1340 } 1341 } else { 1342 if (options.attributesOnly) { 1343 fprintf(stderr, "Error: Failed to open target `%s' (it must exist " 1344 "in attributes only mode): `%s'\n", target, 1345 fssh_strerror(error)); 1346 return error; 1347 } else if (sourceCount > 1) { 1348 fprintf(stderr, "Error: Failed to open destination directory `%s':" 1349 " `%s'\n", target, fssh_strerror(error)); 1350 return error; 1351 } 1352 } 1353 1354 // allocate a copy buffer 1355 sCopyBuffer = malloc(sCopyBufferSize); 1356 if (!sCopyBuffer) { 1357 fprintf(stderr, "Error: Failed to allocate copy buffer.\n"); 1358 return FSSH_ENOMEM; 1359 } 1360 MemoryDeleter copyBufferDeleter(sCopyBuffer); 1361 1362 // open the target node for attributes only mode 1363 NodeDeleter targetDeleter; 1364 if (options.attributesOnly) { 1365 error = targetDomain->Open(target, FSSH_O_RDONLY, targetNode); 1366 if (error != FSSH_B_OK) { 1367 fprintf(stderr, "Error: Failed to open target `%s' for writing: " 1368 "`%s'\n", target, fssh_strerror(error)); 1369 return error; 1370 } 1371 1372 targetDeleter.SetTo(targetNode); 1373 } 1374 1375 // the copy loop 1376 for (int i = 0; i < sourceCount; i++) { 1377 const char *source = sources[i]; 1378 FSDomain *sourceDomain = get_file_domain(source, source); 1379 DomainDeleter sourceDomainDeleter(sourceDomain); 1380 if (options.attributesOnly) { 1381 // 0. copy attributes only 1382 // open the source node 1383 Node *sourceNode; 1384 error = sourceDomain->Open(source, 1385 FSSH_O_RDONLY | (options.dereference ? 0 : FSSH_O_NOTRAVERSE), 1386 sourceNode); 1387 if (error != FSSH_B_OK) { 1388 fprintf(stderr, "Error: Failed to open `%s': %s.\n", source, 1389 fssh_strerror(error)); 1390 return error; 1391 } 1392 NodeDeleter sourceDeleter(sourceNode); 1393 1394 // copy the attributes 1395 error = copy_attributes(source, sourceNode, target, targetNode); 1396 1397 } else if (targetExists && targetIsDir) { 1398 // 1. target exists: 1399 // 1.1. target is a dir: 1400 // get the source leaf name 1401 char leafName[FSSH_B_FILE_NAME_LENGTH]; 1402 error = get_last_path_component(source, leafName, sizeof(leafName)); 1403 if (error != FSSH_B_OK) { 1404 fprintf(stderr, "Error: Failed to get last path component of " 1405 "`%s': %s\n", source, fssh_strerror(error)); 1406 return error; 1407 } 1408 1409 if (strcmp(leafName, ".") == 0 || strcmp(leafName, "..") == 0) { 1410 // 1.1.1. source name is `.' or `..' 1411 // -> copy the contents only 1412 // (copy_dir_contents()) 1413 // open the source dir 1414 Node *sourceNode; 1415 error = sourceDomain->Open(source, 1416 FSSH_O_RDONLY 1417 | (options.dereference ? 0 : FSSH_O_NOTRAVERSE), 1418 sourceNode); 1419 if (error != FSSH_B_OK) { 1420 fprintf(stderr, "Error: Failed to open `%s': %s.\n", source, 1421 fssh_strerror(error)); 1422 return error; 1423 } 1424 NodeDeleter sourceDeleter(sourceNode); 1425 1426 // check, if it is a dir 1427 Directory *sourceDir = sourceNode->ToDirectory(); 1428 if (!sourceDir) { 1429 fprintf(stderr, "Error: Source `%s' is not a directory " 1430 "although it's last path component is `%s'\n", source, 1431 leafName); 1432 return FSSH_EINVAL; 1433 } 1434 1435 error = copy_dir_contents(sourceDomain, source, sourceDir, 1436 targetDomain, target, options); 1437 } else { 1438 // 1.1.2. source has normal name 1439 // -> we copy into the dir 1440 // (copy_entry(<source>, <target>/<source leaf>)) 1441 // compose a new target path name 1442 char *targetEntry = make_path(target, leafName); 1443 if (!targetEntry) { 1444 fprintf(stderr, "Error: Failed to allocate target path!\n"); 1445 return FSSH_ENOMEM; 1446 } 1447 PathDeleter targetDeleter(targetEntry); 1448 1449 error = copy_entry(sourceDomain, source, targetDomain, 1450 targetEntry, options, options.dereference); 1451 } 1452 } else { 1453 // 1.2. target is no dir: 1454 // -> if /force/ is given, we replace the target, otherwise 1455 // we fail 1456 // (copy_entry(<source>, <target>)) 1457 // or 1458 // 2. target doesn't exist: 1459 // -> we create the target as a clone of the source 1460 // (copy_entry(<source>, <target>)) 1461 error = copy_entry(sourceDomain, source, targetDomain, target, 1462 options, options.dereference); 1463 } 1464 1465 if (error != 0) 1466 return error; 1467 } 1468 1469 return FSSH_B_OK; 1470 } 1471 1472 1473 } // namespace FSShell 1474