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