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