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