Fix FreeBSD build.
[haiku.git] / src / tools / fs_shell / command_cp.cpp
blobc7d44f8d6a80a26f7f06bce5a2456b337b6be3ee
1 /*
2 * Copyright 2005-2008, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
6 #include "compatibility.h"
8 #include "command_cp.h"
10 #include <fcntl.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <unistd.h>
15 #include <AutoDeleter.h>
16 #include <EntryFilter.h>
17 #include <fs_attr.h>
18 #include <StorageDefs.h>
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"
33 using BPrivate::EntryFilter;
36 namespace FSShell {
39 static void *sCopyBuffer = NULL;
40 static const int sCopyBufferSize = 64 * 1024; // 64 KB
42 struct Options {
43 Options()
44 : entryFilter(),
45 attributesOnly(false),
46 ignoreAttributes(false),
47 dereference(true),
48 alwaysDereference(false),
49 force(false),
50 recursive(false)
54 EntryFilter entryFilter;
55 bool attributesOnly;
56 bool ignoreAttributes;
57 bool dereference;
58 bool alwaysDereference;
59 bool force;
60 bool recursive;
63 class Directory;
64 class File;
65 class SymLink;
67 // Node
68 class Node {
69 public:
70 Node() {}
71 virtual ~Node() {}
73 const struct fssh_stat &Stat() const { return fStat; }
74 bool IsFile() const { return FSSH_S_ISREG(fStat.fssh_st_mode); }
75 bool IsDirectory() const { return FSSH_S_ISDIR(fStat.fssh_st_mode); }
76 bool IsSymLink() const { return FSSH_S_ISLNK(fStat.fssh_st_mode); }
78 virtual File *ToFile() { return NULL; }
79 virtual Directory *ToDirectory() { return NULL; }
80 virtual SymLink *ToSymLink() { return NULL; }
82 virtual fssh_ssize_t GetNextAttr(char *name, int size) = 0;
83 virtual fssh_status_t GetAttrInfo(const char *name,
84 fssh_attr_info &info) = 0;
85 virtual fssh_ssize_t ReadAttr(const char *name, uint32_t type,
86 fssh_off_t pos, void *buffer, int size) = 0;
87 virtual fssh_ssize_t WriteAttr(const char *name, uint32_t type,
88 fssh_off_t pos, const void *buffer, int size) = 0;
89 virtual fssh_status_t RemoveAttr(const char *name) = 0;
91 protected:
92 struct fssh_stat fStat; // To be initialized by implementing classes.
95 // Directory
96 class Directory : public virtual Node {
97 public:
98 virtual Directory *ToDirectory() { return this; }
100 virtual fssh_ssize_t GetNextEntry(struct fssh_dirent *entry, int size) = 0;
103 // File
104 class File : public virtual Node {
105 public:
106 virtual File *ToFile() { return this; }
108 virtual fssh_ssize_t Read(void *buffer, int size) = 0;
109 virtual fssh_ssize_t Write(const void *buffer, int size) = 0;
112 // SymLink
113 class SymLink : public virtual Node {
114 public:
115 virtual SymLink *ToSymLink() { return this; }
117 virtual fssh_ssize_t ReadLink(char *buffer, int bufferSize) = 0;
120 // FSDomain
121 class FSDomain {
122 public:
123 virtual ~FSDomain() {}
125 virtual fssh_status_t Open(const char *path, int openMode, Node *&node) = 0;
127 virtual fssh_status_t CreateFile(const char *path,
128 const struct fssh_stat &st, File *&file) = 0;
129 virtual fssh_status_t CreateDirectory(const char *path,
130 const struct fssh_stat &st, Directory *&dir) = 0;
131 virtual fssh_status_t CreateSymLink(const char *path, const char *linkTo,
132 const struct fssh_stat &st, SymLink *&link) = 0;
134 virtual fssh_status_t Unlink(const char *path) = 0;
138 // #pragma mark -
140 // HostNode
141 class HostNode : public virtual Node {
142 public:
143 HostNode()
144 : Node(),
145 fFD(-1),
146 fAttrDir(NULL)
150 virtual ~HostNode()
152 if (fFD >= 0)
153 fssh_close(fFD);
154 if (fAttrDir)
155 fs_close_attr_dir(fAttrDir);
158 virtual fssh_status_t Init(const char *path, int fd,
159 const struct fssh_stat &st)
161 fFD = fd;
162 fStat = st;
164 // open the attribute directory
165 fAttrDir = fs_fopen_attr_dir(fd);
166 if (!fAttrDir)
167 return fssh_get_errno();
169 return FSSH_B_OK;
172 virtual fssh_ssize_t GetNextAttr(char *name, int size)
174 if (!fAttrDir)
175 return 0;
177 fssh_set_errno(FSSH_B_OK);
178 struct dirent *entry = fs_read_attr_dir(fAttrDir);
179 if (!entry)
180 return fssh_get_errno();
182 int len = strlen(entry->d_name);
183 if (len >= size)
184 return FSSH_B_NAME_TOO_LONG;
186 strcpy(name, entry->d_name);
187 return 1;
190 virtual fssh_status_t GetAttrInfo(const char *name, fssh_attr_info &info)
192 attr_info hostInfo;
193 if (fs_stat_attr(fFD, name, &hostInfo) < 0)
194 return fssh_get_errno();
196 info.type = hostInfo.type;
197 info.size = hostInfo.size;
198 return FSSH_B_OK;
201 virtual fssh_ssize_t ReadAttr(const char *name, uint32_t type,
202 fssh_off_t pos, void *buffer, int size)
204 fssh_ssize_t bytesRead = fs_read_attr(fFD, name, type, pos, buffer,
205 size);
206 return (bytesRead >= 0 ? bytesRead : fssh_get_errno());
209 virtual fssh_ssize_t WriteAttr(const char *name, uint32_t type,
210 fssh_off_t pos, const void *buffer, int size)
212 fssh_ssize_t bytesWritten = fs_write_attr(fFD, name, type, pos, buffer,
213 size);
214 return (bytesWritten >= 0 ? bytesWritten : fssh_get_errno());
217 virtual fssh_status_t RemoveAttr(const char *name)
219 return (fs_remove_attr(fFD, name) == 0 ? 0 : fssh_get_errno());
222 protected:
223 int fFD;
224 DIR *fAttrDir;
227 // HostDirectory
228 class HostDirectory : public Directory, public HostNode {
229 public:
230 HostDirectory()
231 : Directory(),
232 HostNode(),
233 fDir(NULL)
237 virtual ~HostDirectory()
239 if (fDir)
240 closedir(fDir);
243 virtual fssh_status_t Init(const char *path, int fd,
244 const struct fssh_stat &st)
246 fssh_status_t error = HostNode::Init(path, fd, st);
247 if (error != FSSH_B_OK)
248 return error;
250 fDir = opendir(path);
251 if (!fDir)
252 return fssh_get_errno();
254 return FSSH_B_OK;
257 virtual fssh_ssize_t GetNextEntry(struct fssh_dirent *entry, int size)
259 fssh_set_errno(FSSH_B_OK);
260 struct dirent *hostEntry = readdir(fDir);
261 if (!hostEntry)
262 return fssh_get_errno();
264 int nameLen = strlen(hostEntry->d_name);
265 int recLen = entry->d_name + nameLen + 1 - (char*)entry;
266 if (recLen > size)
267 return FSSH_B_NAME_TOO_LONG;
269 #if (defined(__BEOS__) || defined(__HAIKU__))
270 entry->d_dev = hostEntry->d_dev;
271 #endif
272 entry->d_ino = hostEntry->d_ino;
273 strcpy(entry->d_name, hostEntry->d_name);
274 entry->d_reclen = recLen;
276 return 1;
279 private:
280 DIR *fDir;
283 // HostFile
284 class HostFile : public File, public HostNode {
285 public:
286 HostFile()
287 : File(),
288 HostNode()
292 virtual ~HostFile()
296 virtual fssh_ssize_t Read(void *buffer, int size)
298 fssh_ssize_t bytesRead = read(fFD, buffer, size);
299 return (bytesRead >= 0 ? bytesRead : fssh_get_errno());
302 virtual fssh_ssize_t Write(const void *buffer, int size)
304 fssh_ssize_t bytesWritten = write(fFD, buffer, size);
305 return (bytesWritten >= 0 ? bytesWritten : fssh_get_errno());
309 // HostSymLink
310 class HostSymLink : public SymLink, public HostNode {
311 public:
312 HostSymLink()
313 : SymLink(),
314 HostNode(),
315 fPath(NULL)
319 virtual ~HostSymLink()
321 if (fPath)
322 free(fPath);
325 virtual fssh_status_t Init(const char *path, int fd,
326 const struct fssh_stat &st)
328 fssh_status_t error = HostNode::Init(path, fd, st);
329 if (error != FSSH_B_OK)
330 return error;
332 fPath = strdup(path);
333 if (!fPath)
334 return FSSH_B_NO_MEMORY;
336 return FSSH_B_OK;
339 virtual fssh_ssize_t ReadLink(char *buffer, int bufferSize)
341 fssh_ssize_t bytesRead = readlink(fPath, buffer, bufferSize);
342 return (bytesRead >= 0 ? bytesRead : fssh_get_errno());
345 private:
346 char *fPath;
349 // HostFSDomain
350 class HostFSDomain : public FSDomain {
351 public:
352 HostFSDomain() {}
353 virtual ~HostFSDomain() {}
355 virtual fssh_status_t Open(const char *path, int openMode, Node *&_node)
357 // open the node
358 int fd = fssh_open(path, openMode);
359 if (fd < 0)
360 return fssh_get_errno();
362 // stat the node
363 struct fssh_stat st;
364 if (fssh_fstat(fd, &st) < 0) {
365 fssh_close(fd);
366 return fssh_get_errno();
369 // check the node type and create the node
370 HostNode *node = NULL;
371 switch (st.fssh_st_mode & FSSH_S_IFMT) {
372 case FSSH_S_IFLNK:
373 node = new HostSymLink;
374 break;
375 case FSSH_S_IFREG:
376 node = new HostFile;
377 break;
378 case FSSH_S_IFDIR:
379 node = new HostDirectory;
380 break;
381 default:
382 fssh_close(fd);
383 return FSSH_EINVAL;
386 // init the node
387 fssh_status_t error = node->Init(path, fd, st);
388 // the node receives ownership of the FD
389 if (error != FSSH_B_OK) {
390 delete node;
391 return error;
394 _node = node;
395 return FSSH_B_OK;
398 virtual fssh_status_t CreateFile(const char *path,
399 const struct fssh_stat &st, File *&_file)
401 // create the file
402 int fd = fssh_creat(path, st.fssh_st_mode & FSSH_S_IUMSK);
403 if (fd < 0)
404 return fssh_get_errno();
406 // apply the other stat fields
407 fssh_status_t error = _ApplyStat(fd, st);
408 if (error != FSSH_B_OK) {
409 fssh_close(fd);
410 return error;
413 // create the object
414 HostFile *file = new HostFile;
415 error = file->Init(path, fd, st);
416 if (error != FSSH_B_OK) {
417 delete file;
418 return error;
421 _file = file;
422 return FSSH_B_OK;
425 virtual fssh_status_t CreateDirectory(const char *path,
426 const struct fssh_stat &st, Directory *&_dir)
428 // create the dir
429 if (fssh_mkdir(path, st.fssh_st_mode & FSSH_S_IUMSK) < 0)
430 return fssh_get_errno();
432 // open the dir node
433 int fd = fssh_open(path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE);
434 if (fd < 0)
435 return fssh_get_errno();
437 // apply the other stat fields
438 fssh_status_t error = _ApplyStat(fd, st);
439 if (error != FSSH_B_OK) {
440 fssh_close(fd);
441 return error;
444 // create the object
445 HostDirectory *dir = new HostDirectory;
446 error = dir->Init(path, fd, st);
447 if (error != FSSH_B_OK) {
448 delete dir;
449 return error;
452 _dir = dir;
453 return FSSH_B_OK;
456 virtual fssh_status_t CreateSymLink(const char *path, const char *linkTo,
457 const struct fssh_stat &st, SymLink *&_link)
459 // create the link
460 if (symlink(linkTo, path) < 0)
461 return fssh_get_errno();
463 // open the symlink node
464 int fd = fssh_open(path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE);
465 if (fd < 0)
466 return fssh_get_errno();
468 // apply the other stat fields
469 fssh_status_t error = _ApplyStat(fd, st);
470 if (error != FSSH_B_OK) {
471 fssh_close(fd);
472 return error;
475 // create the object
476 HostSymLink *link = new HostSymLink;
477 error = link->Init(path, fd, st);
478 if (error != FSSH_B_OK) {
479 delete link;
480 return error;
483 _link = link;
484 return FSSH_B_OK;
488 virtual fssh_status_t Unlink(const char *path)
490 if (fssh_unlink(path) < 0)
491 return fssh_get_errno();
492 return FSSH_B_OK;
495 private:
496 fssh_status_t _ApplyStat(int fd, const struct fssh_stat &st)
498 // TODO: Set times...
499 return FSSH_B_OK;
504 // #pragma mark -
506 // GuestNode
507 class GuestNode : public virtual Node {
508 public:
509 GuestNode()
510 : Node(),
511 fFD(-1),
512 fAttrDir(-1)
516 virtual ~GuestNode()
518 if (fFD >= 0)
519 _kern_close(fFD);
520 if (fAttrDir)
521 _kern_close(fAttrDir);
524 virtual fssh_status_t Init(const char *path, int fd,
525 const struct fssh_stat &st)
527 fFD = fd;
528 fStat = st;
530 // open the attribute directory
531 fAttrDir = _kern_open_attr_dir(fd, NULL);
532 if (fAttrDir < 0) {
533 // TODO: check if the file system supports attributes, and fail
536 return FSSH_B_OK;
539 virtual fssh_ssize_t GetNextAttr(char *name, int size)
541 if (fAttrDir < 0)
542 return 0;
544 char buffer[sizeof(fssh_dirent) + B_ATTR_NAME_LENGTH];
545 struct fssh_dirent *entry = (fssh_dirent *)buffer;
546 int numRead = _kern_read_dir(fAttrDir, entry, sizeof(buffer), 1);
547 if (numRead < 0)
548 return numRead;
549 if (numRead == 0)
550 return 0;
552 int len = strlen(entry->d_name);
553 if (len >= size)
554 return FSSH_B_NAME_TOO_LONG;
556 strcpy(name, entry->d_name);
557 return 1;
560 virtual fssh_status_t GetAttrInfo(const char *name, fssh_attr_info &info)
562 // open attr
563 int attrFD = _kern_open_attr(fFD, name, FSSH_O_RDONLY);
564 if (attrFD < 0)
565 return attrFD;
567 // stat attr
568 struct fssh_stat st;
569 fssh_status_t error = _kern_read_stat(attrFD, NULL, false, &st,
570 sizeof(st));
572 // close attr
573 _kern_close(attrFD);
575 if (error != FSSH_B_OK)
576 return error;
578 // convert stat to attr info
579 info.type = st.fssh_st_type;
580 info.size = st.fssh_st_size;
582 return FSSH_B_OK;
585 virtual fssh_ssize_t ReadAttr(const char *name, uint32_t type,
586 fssh_off_t pos, void *buffer, int size)
588 // open attr
589 int attrFD = _kern_open_attr(fFD, name, FSSH_O_RDONLY);
590 if (attrFD < 0)
591 return attrFD;
593 // stat attr
594 fssh_ssize_t bytesRead = _kern_read(attrFD, pos, buffer, size);
596 // close attr
597 _kern_close(attrFD);
599 return bytesRead;
602 virtual fssh_ssize_t WriteAttr(const char *name, uint32_t type,
603 fssh_off_t pos, const void *buffer, int size)
605 // open attr
606 int attrFD = _kern_create_attr(fFD, name, type, FSSH_O_WRONLY);
607 if (attrFD < 0)
608 return attrFD;
610 // stat attr
611 fssh_ssize_t bytesWritten = _kern_write(attrFD, pos, buffer, size);
613 // close attr
614 _kern_close(attrFD);
616 return bytesWritten;
619 virtual fssh_status_t RemoveAttr(const char *name)
621 return _kern_remove_attr(fFD, name);
624 protected:
625 int fFD;
626 int fAttrDir;
629 // GuestDirectory
630 class GuestDirectory : public Directory, public GuestNode {
631 public:
632 GuestDirectory()
633 : Directory(),
634 GuestNode(),
635 fDir(-1)
639 virtual ~GuestDirectory()
641 if (fDir)
642 _kern_close(fDir);
645 virtual fssh_status_t Init(const char *path, int fd,
646 const struct fssh_stat &st)
648 fssh_status_t error = GuestNode::Init(path, fd, st);
649 if (error != FSSH_B_OK)
650 return error;
652 fDir = _kern_open_dir(fd, NULL);
653 if (fDir < 0)
654 return fDir;
656 return FSSH_B_OK;
659 virtual fssh_ssize_t GetNextEntry(struct fssh_dirent *entry, int size)
661 return _kern_read_dir(fDir, entry, size, 1);
664 private:
665 int fDir;
668 // GuestFile
669 class GuestFile : public File, public GuestNode {
670 public:
671 GuestFile()
672 : File(),
673 GuestNode()
677 virtual ~GuestFile()
681 virtual fssh_ssize_t Read(void *buffer, int size)
683 return _kern_read(fFD, -1, buffer, size);
686 virtual fssh_ssize_t Write(const void *buffer, int size)
688 return _kern_write(fFD, -1, buffer, size);
692 // GuestSymLink
693 class GuestSymLink : public SymLink, public GuestNode {
694 public:
695 GuestSymLink()
696 : SymLink(),
697 GuestNode()
701 virtual ~GuestSymLink()
705 virtual fssh_ssize_t ReadLink(char *buffer, int _bufferSize)
707 fssh_size_t bufferSize = _bufferSize;
708 fssh_status_t error = _kern_read_link(fFD, NULL, buffer, &bufferSize);
709 return (error == FSSH_B_OK ? bufferSize : error);
713 // GuestFSDomain
714 class GuestFSDomain : public FSDomain {
715 public:
716 GuestFSDomain() {}
717 virtual ~GuestFSDomain() {}
719 virtual fssh_status_t Open(const char *path, int openMode, Node *&_node)
721 // open the node
722 int fd = _kern_open(-1, path, openMode, 0);
723 if (fd < 0)
724 return fd;
726 // stat the node
727 struct fssh_stat st;
728 fssh_status_t error = _kern_read_stat(fd, NULL, false, &st, sizeof(st));
729 if (error < 0) {
730 _kern_close(fd);
731 return error;
734 // check the node type and create the node
735 GuestNode *node = NULL;
736 switch (st.fssh_st_mode & FSSH_S_IFMT) {
737 case FSSH_S_IFLNK:
738 node = new GuestSymLink;
739 break;
740 case FSSH_S_IFREG:
741 node = new GuestFile;
742 break;
743 case FSSH_S_IFDIR:
744 node = new GuestDirectory;
745 break;
746 default:
747 _kern_close(fd);
748 return FSSH_EINVAL;
751 // init the node
752 error = node->Init(path, fd, st);
753 // the node receives ownership of the FD
754 if (error != FSSH_B_OK) {
755 delete node;
756 return error;
759 _node = node;
760 return FSSH_B_OK;
763 virtual fssh_status_t CreateFile(const char *path,
764 const struct fssh_stat &st, File *&_file)
766 // create the file
767 int fd = _kern_open(-1, path, FSSH_O_RDWR | FSSH_O_EXCL | FSSH_O_CREAT,
768 st.fssh_st_mode & FSSH_S_IUMSK);
769 if (fd < 0)
770 return fd;
772 // apply the other stat fields
773 fssh_status_t error = _ApplyStat(fd, st);
774 if (error != FSSH_B_OK) {
775 _kern_close(fd);
776 return error;
779 // create the object
780 GuestFile *file = new GuestFile;
781 error = file->Init(path, fd, st);
782 if (error != FSSH_B_OK) {
783 delete file;
784 return error;
787 _file = file;
788 return FSSH_B_OK;
791 virtual fssh_status_t CreateDirectory(const char *path,
792 const struct fssh_stat &st, Directory *&_dir)
794 // create the dir
795 fssh_status_t error = _kern_create_dir(-1, path,
796 st.fssh_st_mode & FSSH_S_IUMSK);
797 if (error < 0)
798 return error;
800 // open the dir node
801 int fd = _kern_open(-1, path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 0);
802 if (fd < 0)
803 return fd;
805 // apply the other stat fields
806 error = _ApplyStat(fd, st);
807 if (error != FSSH_B_OK) {
808 _kern_close(fd);
809 return error;
812 // create the object
813 GuestDirectory *dir = new GuestDirectory;
814 error = dir->Init(path, fd, st);
815 if (error != FSSH_B_OK) {
816 delete dir;
817 return error;
820 _dir = dir;
821 return FSSH_B_OK;
824 virtual fssh_status_t CreateSymLink(const char *path, const char *linkTo,
825 const struct fssh_stat &st, SymLink *&_link)
827 // create the link
828 fssh_status_t error = _kern_create_symlink(-1, path, linkTo,
829 st.fssh_st_mode & FSSH_S_IUMSK);
830 if (error < 0)
831 return error;
833 // open the symlink node
834 int fd = _kern_open(-1, path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 0);
835 if (fd < 0)
836 return fd;
838 // apply the other stat fields
839 error = _ApplyStat(fd, st);
840 if (error != FSSH_B_OK) {
841 _kern_close(fd);
842 return error;
845 // create the object
846 GuestSymLink *link = new GuestSymLink;
847 error = link->Init(path, fd, st);
848 if (error != FSSH_B_OK) {
849 delete link;
850 return error;
853 _link = link;
854 return FSSH_B_OK;
857 virtual fssh_status_t Unlink(const char *path)
859 return _kern_unlink(-1, path);
862 private:
863 fssh_status_t _ApplyStat(int fd, const struct fssh_stat &st)
865 // TODO: Set times...
866 return FSSH_B_OK;
871 // #pragma mark -
873 static fssh_status_t copy_entry(FSDomain *sourceDomain, const char *source,
874 FSDomain *targetDomain, const char *target, const Options &options,
875 bool dereference);
877 static FSDomain *
878 get_file_domain(const char *target, const char *&fsTarget)
880 if (target[0] == ':') {
881 fsTarget = target + 1;
882 return new HostFSDomain;
883 } else {
884 fsTarget = target;
885 return new GuestFSDomain;
889 typedef ObjectDeleter<Node> NodeDeleter;
890 typedef ObjectDeleter<FSDomain> DomainDeleter;
891 typedef MemoryDeleter PathDeleter;
894 static fssh_status_t
895 copy_file_contents(const char *source, File *sourceFile, const char *target,
896 File *targetFile)
898 fssh_off_t chunkSize = (sourceFile->Stat().fssh_st_size / 20) / sCopyBufferSize * sCopyBufferSize;
899 if (chunkSize == 0)
900 chunkSize = 1;
902 bool progress = sourceFile->Stat().fssh_st_size > 1024 * 1024;
903 if (progress) {
904 printf("%s ", strrchr(target, '/') ? strrchr(target, '/') + 1 : target);
905 fflush(stdout);
908 fssh_off_t total = 0;
909 fssh_ssize_t bytesRead;
910 while ((bytesRead = sourceFile->Read(sCopyBuffer, sCopyBufferSize)) > 0) {
911 fssh_ssize_t bytesWritten = targetFile->Write(sCopyBuffer, bytesRead);
912 if (progress && (total % chunkSize) == 0) {
913 putchar('.');
914 fflush(stdout);
916 if (bytesWritten < 0) {
917 fprintf(stderr, "Error while writing to file `%s': %s\n",
918 target, fssh_strerror(bytesWritten));
919 return bytesWritten;
921 if (bytesWritten != bytesRead) {
922 fprintf(stderr, "Could not write all data to file \"%s\".\n",
923 target);
924 return FSSH_B_IO_ERROR;
926 total += bytesWritten;
929 if (bytesRead < 0) {
930 fprintf(stderr, "Error while reading from file `%s': %s\n",
931 source, fssh_strerror(bytesRead));
932 return bytesRead;
935 if (progress)
936 putchar('\n');
938 return FSSH_B_OK;
942 static fssh_status_t
943 copy_dir_contents(FSDomain *sourceDomain, const char *source,
944 Directory *sourceDir, FSDomain *targetDomain, const char *target,
945 const Options &options)
947 char buffer[sizeof(fssh_dirent) + FSSH_B_FILE_NAME_LENGTH];
948 struct fssh_dirent *entry = (struct fssh_dirent *)buffer;
949 fssh_ssize_t numRead;
950 while ((numRead = sourceDir->GetNextEntry(entry, sizeof(buffer))) > 0) {
951 // skip "." and ".."
952 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
953 continue;
955 // compose a new source path name
956 char *sourceEntry = make_path(source, entry->d_name);
957 if (!sourceEntry) {
958 fprintf(stderr, "Error: Failed to allocate source path!\n");
959 return FSSH_ENOMEM;
961 PathDeleter sourceDeleter(sourceEntry);
963 // compose a new target path name
964 char *targetEntry = make_path(target, entry->d_name);
965 if (!targetEntry) {
966 fprintf(stderr, "Error: Failed to allocate target path!\n");
967 return FSSH_ENOMEM;
969 PathDeleter targetDeleter(targetEntry);
971 fssh_status_t error = copy_entry(sourceDomain, sourceEntry,
972 targetDomain, targetEntry, options, options.alwaysDereference);
973 if (error != FSSH_B_OK)
974 return error;
977 if (numRead < 0) {
978 fprintf(stderr, "Error reading directory `%s': %s\n", source,
979 fssh_strerror(numRead));
980 return numRead;
983 return FSSH_B_OK;
987 static fssh_status_t
988 copy_attribute(const char *source, Node *sourceNode, const char *target,
989 Node *targetNode, const char *name, const fssh_attr_info &info)
991 // remove the attribute first
992 targetNode->RemoveAttr(name);
994 // special case: empty attribute
995 if (info.size <= 0) {
996 fssh_ssize_t bytesWritten = targetNode->WriteAttr(name, info.type, 0,
997 sCopyBuffer, 0);
998 if (bytesWritten) {
999 fprintf(stderr, "Error while writing to attribute `%s' of file "
1000 "`%s': %s\n", name, target, fssh_strerror(bytesWritten));
1001 return bytesWritten;
1004 return FSSH_B_OK;
1007 // non-empty attribute
1008 fssh_off_t pos = 0;
1009 int toCopy = info.size;
1010 while (toCopy > 0) {
1011 // read data from source
1012 int toRead = (toCopy < sCopyBufferSize ? toCopy : sCopyBufferSize);
1013 fssh_ssize_t bytesRead = sourceNode->ReadAttr(name, info.type, pos,
1014 sCopyBuffer, toRead);
1015 if (bytesRead < 0) {
1016 fprintf(stderr, "Error while reading from attribute `%s' of file "
1017 "`%s': %s\n", name, source, fssh_strerror(bytesRead));
1018 return bytesRead;
1021 // write data to target
1022 fssh_ssize_t bytesWritten = targetNode->WriteAttr(name, info.type, pos,
1023 sCopyBuffer, bytesRead);
1024 if (bytesWritten < 0) {
1025 fprintf(stderr, "Error while writing to attribute `%s' of file "
1026 "`%s': %s\n", name, target, fssh_strerror(bytesWritten));
1027 return bytesWritten;
1030 pos += bytesRead;
1031 toCopy -= bytesRead;
1034 return FSSH_B_OK;
1038 static fssh_status_t
1039 copy_attributes(const char *source, Node *sourceNode, const char *target,
1040 Node *targetNode)
1042 char name[B_ATTR_NAME_LENGTH];
1043 fssh_ssize_t numRead;
1044 while ((numRead = sourceNode->GetNextAttr(name, sizeof(name))) > 0) {
1045 fssh_attr_info info;
1046 // get attribute info
1047 fssh_status_t error = sourceNode->GetAttrInfo(name, info);
1048 if (error != FSSH_B_OK) {
1049 fprintf(stderr, "Error getting info for attribute `%s' of file "
1050 "`%s': %s\n", name, source, fssh_strerror(error));
1051 return error;
1054 // copy the attribute
1055 error = copy_attribute(source, sourceNode, target, targetNode, name,
1056 info);
1057 if (error != FSSH_B_OK)
1058 return error;
1061 if (numRead < 0) {
1062 fprintf(stderr, "Error reading attribute directory of `%s': %s\n",
1063 source, fssh_strerror(numRead));
1064 return numRead;
1067 return FSSH_B_OK;
1071 static fssh_status_t
1072 copy_entry(FSDomain *sourceDomain, const char *source,
1073 FSDomain *targetDomain, const char *target, const Options &options,
1074 bool dereference)
1076 // apply entry filter
1077 if (!options.entryFilter.Filter(source))
1078 return FSSH_B_OK;
1080 // open the source node
1081 Node *sourceNode;
1082 fssh_status_t error = sourceDomain->Open(source,
1083 FSSH_O_RDONLY | (dereference ? 0 : FSSH_O_NOTRAVERSE),
1084 sourceNode);
1085 if (error != FSSH_B_OK) {
1086 fprintf(stderr, "Error: Failed to open source path `%s': %s\n", source,
1087 fssh_strerror(error));
1088 return error;
1090 NodeDeleter sourceDeleter(sourceNode);
1092 // check, if target exists
1093 Node *targetNode = NULL;
1094 // try opening with resolving symlinks first
1095 error = targetDomain->Open(target, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE,
1096 targetNode);
1097 NodeDeleter targetDeleter;
1098 if (error == FSSH_B_OK) {
1099 // 1. target exists:
1100 // check, if it is a dir and, if so, whether source is a dir too
1101 targetDeleter.SetTo(targetNode);
1103 // if the target is a symlink, try resolving it
1104 if (targetNode->IsSymLink()) {
1105 Node *resolvedTargetNode;
1106 error = targetDomain->Open(target, FSSH_O_RDONLY,
1107 resolvedTargetNode);
1108 if (error == FSSH_B_OK) {
1109 targetNode = resolvedTargetNode;
1110 targetDeleter.SetTo(targetNode);
1114 if (sourceNode->IsDirectory() && targetNode->IsDirectory()) {
1115 // 1.1. target and source are dirs:
1116 // -> just copy their contents
1117 // ...
1118 } else {
1119 // 1.2. source and/or target are no dirs
1121 if (options.force) {
1122 // 1.2.1. /force/
1123 // -> remove the target and continue with 2.
1124 targetDeleter.Delete();
1125 targetNode = NULL;
1126 error = targetDomain->Unlink(target);
1127 if (error != FSSH_B_OK) {
1128 fprintf(stderr, "Error: Failed to remove `%s'\n", target);
1129 return error;
1131 } else if (sourceNode->IsFile() && targetNode->IsFile()) {
1132 // 1.2.1.1. !/force/, but both source and target are files
1133 // -> truncate the target file and continue
1134 targetDeleter.Delete();
1135 targetNode = NULL;
1136 error = targetDomain->Open(target, FSSH_O_RDWR | FSSH_O_TRUNC,
1137 targetNode);
1138 if (error != FSSH_B_OK) {
1139 fprintf(stderr, "Error: Failed to open `%s' for writing\n",
1140 target);
1141 return error;
1143 } else {
1144 // 1.2.1.2. !/force/, source or target isn't a file
1145 // -> fail
1146 fprintf(stderr, "Error: File `%s' does exist.\n", target);
1147 return FSSH_B_FILE_EXISTS;
1150 } // else: 2. target doesn't exist: -> just create it
1152 // create the target node
1153 error = FSSH_B_OK;
1154 if (sourceNode->IsFile()) {
1155 if (!targetNode) {
1156 File *file = NULL;
1157 error = targetDomain->CreateFile(target, sourceNode->Stat(), file);
1158 if (error == 0)
1159 targetNode = file;
1161 } else if (sourceNode->IsDirectory()) {
1162 // check /recursive/
1163 if (!options.recursive) {
1164 fprintf(stderr, "Error: Entry `%s' is a directory.\n", source);
1165 return FSSH_EISDIR;
1168 // create the target only, if it doesn't already exist
1169 if (!targetNode) {
1170 Directory *dir = NULL;
1171 error = targetDomain->CreateDirectory(target, sourceNode->Stat(),
1172 dir);
1173 if (error == 0)
1174 targetNode = dir;
1176 } else if (sourceNode->IsSymLink()) {
1177 // read the source link
1178 SymLink *sourceLink = sourceNode->ToSymLink();
1179 char linkTo[FSSH_B_PATH_NAME_LENGTH];
1180 fssh_ssize_t bytesRead = sourceLink->ReadLink(linkTo,
1181 sizeof(linkTo) - 1);
1182 if (bytesRead < 0) {
1183 fprintf(stderr, "Error: Failed to read symlink `%s': %s\n", source,
1184 fssh_strerror(bytesRead));
1186 linkTo[bytesRead] = '\0'; // always NULL-terminate
1188 // create the target link
1189 SymLink *link;
1190 error = targetDomain->CreateSymLink(target, linkTo,
1191 sourceNode->Stat(), link);
1192 if (error == 0)
1193 targetNode = link;
1194 } else {
1195 fprintf(stderr, "Error: Unknown node type. We shouldn't be here!\n");
1196 return FSSH_EINVAL;
1199 if (error != FSSH_B_OK) {
1200 fprintf(stderr, "Error: Failed to create `%s': %s\n", target,
1201 fssh_strerror(error));
1202 return error;
1204 targetDeleter.SetTo(targetNode);
1206 // copy attributes
1207 if (!options.ignoreAttributes) {
1208 error = copy_attributes(source, sourceNode, target, targetNode);
1209 if (error != FSSH_B_OK)
1210 return error;
1213 // copy contents
1214 if (sourceNode->IsFile()) {
1215 error = copy_file_contents(source, sourceNode->ToFile(), target,
1216 targetNode->ToFile());
1217 } else if (sourceNode->IsDirectory()) {
1218 error = copy_dir_contents(sourceDomain, source,
1219 sourceNode->ToDirectory(), targetDomain, target, options);
1222 return error;
1226 fssh_status_t
1227 command_cp(int argc, const char* const* argv)
1229 int sourceCount = 0;
1230 Options options;
1232 const char **sources = new const char*[argc];
1233 if (!sources) {
1234 fprintf(stderr, "Error: No memory!\n");
1235 return FSSH_EINVAL;
1237 ArrayDeleter<const char*> _(sources);
1239 // parse parameters
1240 for (int argi = 1; argi < argc; argi++) {
1241 const char *arg = argv[argi];
1242 if (arg[0] == '-') {
1243 if (arg[1] == '\0') {
1244 fprintf(stderr, "Error: Invalid option '-'\n");
1245 return FSSH_EINVAL;
1248 if (arg[1] == '-') {
1249 if (strcmp(arg, "--ignore-attributes") == 0) {
1250 options.ignoreAttributes = true;
1251 } else {
1252 fprintf(stderr, "Error: Unknown option '%s'\n", arg);
1253 return FSSH_EINVAL;
1255 } else {
1256 for (int i = 1; arg[i]; i++) {
1257 switch (arg[i]) {
1258 case 'a':
1259 options.attributesOnly = true;
1260 break;
1261 case 'd':
1262 options.dereference = false;
1263 break;
1264 case 'f':
1265 options.force = true;
1266 break;
1267 case 'L':
1268 options.dereference = true;
1269 options.alwaysDereference = true;
1270 break;
1271 case 'r':
1272 options.recursive = true;
1273 break;
1274 case 'x':
1275 case 'X':
1277 const char* pattern;
1278 if (arg[i + 1] == '\0') {
1279 if (++argi >= argc) {
1280 fprintf(stderr, "Error: Option '-%c' need "
1281 "a pattern as parameter\n", arg[i]);
1282 return FSSH_EINVAL;
1284 pattern = argv[argi];
1285 } else
1286 pattern = arg + i + 1;
1288 options.entryFilter.AddExcludeFilter(pattern,
1289 arg[i] == 'x');
1290 break;
1292 default:
1293 fprintf(stderr, "Error: Unknown option '-%c'\n",
1294 arg[i]);
1295 return FSSH_EINVAL;
1299 } else {
1300 sources[sourceCount++] = arg;
1304 // check params
1305 if (sourceCount < 2) {
1306 fprintf(stderr, "Error: Must specify at least 2 files!\n");
1307 return FSSH_EINVAL;
1310 // check the target
1311 const char *target = sources[--sourceCount];
1312 bool targetIsDir = false;
1313 bool targetExists = false;
1314 FSDomain *targetDomain = get_file_domain(target, target);
1315 DomainDeleter targetDomainDeleter(targetDomain);
1317 Node *targetNode;
1318 fssh_status_t error = targetDomain->Open(target, FSSH_O_RDONLY, targetNode);
1319 if (error == 0) {
1320 NodeDeleter targetDeleter(targetNode);
1321 targetExists = true;
1323 if (options.attributesOnly) {
1324 // That's how it should be; we don't care whether the target is
1325 // a directory or not. We append the attributes to that node in
1326 // either case.
1327 } else if (targetNode->IsDirectory()) {
1328 targetIsDir = true;
1329 } else {
1330 if (sourceCount > 1) {
1331 fprintf(stderr, "Error: Destination `%s' is not a directory!",
1332 target);
1333 return FSSH_B_NOT_A_DIRECTORY;
1336 } else {
1337 if (options.attributesOnly) {
1338 fprintf(stderr, "Error: Failed to open target `%s' (it must exist "
1339 "in attributes only mode): `%s'\n", target,
1340 fssh_strerror(error));
1341 return error;
1342 } else if (sourceCount > 1) {
1343 fprintf(stderr, "Error: Failed to open destination directory `%s':"
1344 " `%s'\n", target, fssh_strerror(error));
1345 return error;
1349 // allocate a copy buffer
1350 sCopyBuffer = malloc(sCopyBufferSize);
1351 if (!sCopyBuffer) {
1352 fprintf(stderr, "Error: Failed to allocate copy buffer.\n");
1353 return FSSH_ENOMEM;
1355 MemoryDeleter copyBufferDeleter(sCopyBuffer);
1357 // open the target node for attributes only mode
1358 NodeDeleter targetDeleter;
1359 if (options.attributesOnly) {
1360 error = targetDomain->Open(target, FSSH_O_RDONLY, targetNode);
1361 if (error != FSSH_B_OK) {
1362 fprintf(stderr, "Error: Failed to open target `%s' for writing: "
1363 "`%s'\n", target, fssh_strerror(error));
1364 return error;
1367 targetDeleter.SetTo(targetNode);
1370 // the copy loop
1371 for (int i = 0; i < sourceCount; i++) {
1372 const char *source = sources[i];
1373 FSDomain *sourceDomain = get_file_domain(source, source);
1374 DomainDeleter sourceDomainDeleter(sourceDomain);
1375 if (options.attributesOnly) {
1376 // 0. copy attributes only
1377 // open the source node
1378 Node *sourceNode;
1379 error = sourceDomain->Open(source,
1380 FSSH_O_RDONLY | (options.dereference ? 0 : FSSH_O_NOTRAVERSE),
1381 sourceNode);
1382 if (error != FSSH_B_OK) {
1383 fprintf(stderr, "Error: Failed to open `%s': %s.\n", source,
1384 fssh_strerror(error));
1385 return error;
1387 NodeDeleter sourceDeleter(sourceNode);
1389 // copy the attributes
1390 error = copy_attributes(source, sourceNode, target, targetNode);
1392 } else if (targetExists && targetIsDir) {
1393 // 1. target exists:
1394 // 1.1. target is a dir:
1395 // get the source leaf name
1396 char leafName[FSSH_B_FILE_NAME_LENGTH];
1397 error = get_last_path_component(source, leafName, sizeof(leafName));
1398 if (error != FSSH_B_OK) {
1399 fprintf(stderr, "Error: Failed to get last path component of "
1400 "`%s': %s\n", source, fssh_strerror(error));
1401 return error;
1404 if (strcmp(leafName, ".") == 0 || strcmp(leafName, "..") == 0) {
1405 // 1.1.1. source name is `.' or `..'
1406 // -> copy the contents only
1407 // (copy_dir_contents())
1408 // open the source dir
1409 Node *sourceNode;
1410 error = sourceDomain->Open(source,
1411 FSSH_O_RDONLY
1412 | (options.dereference ? 0 : FSSH_O_NOTRAVERSE),
1413 sourceNode);
1414 if (error != FSSH_B_OK) {
1415 fprintf(stderr, "Error: Failed to open `%s': %s.\n", source,
1416 fssh_strerror(error));
1417 return error;
1419 NodeDeleter sourceDeleter(sourceNode);
1421 // check, if it is a dir
1422 Directory *sourceDir = sourceNode->ToDirectory();
1423 if (!sourceDir) {
1424 fprintf(stderr, "Error: Source `%s' is not a directory "
1425 "although it's last path component is `%s'\n", source,
1426 leafName);
1427 return FSSH_EINVAL;
1430 error = copy_dir_contents(sourceDomain, source, sourceDir,
1431 targetDomain, target, options);
1432 } else {
1433 // 1.1.2. source has normal name
1434 // -> we copy into the dir
1435 // (copy_entry(<source>, <target>/<source leaf>))
1436 // compose a new target path name
1437 char *targetEntry = make_path(target, leafName);
1438 if (!targetEntry) {
1439 fprintf(stderr, "Error: Failed to allocate target path!\n");
1440 return FSSH_ENOMEM;
1442 PathDeleter targetDeleter(targetEntry);
1444 error = copy_entry(sourceDomain, source, targetDomain,
1445 targetEntry, options, options.dereference);
1447 } else {
1448 // 1.2. target is no dir:
1449 // -> if /force/ is given, we replace the target, otherwise
1450 // we fail
1451 // (copy_entry(<source>, <target>))
1452 // or
1453 // 2. target doesn't exist:
1454 // -> we create the target as a clone of the source
1455 // (copy_entry(<source>, <target>))
1456 error = copy_entry(sourceDomain, source, targetDomain, target,
1457 options, options.dereference);
1460 if (error != 0)
1461 return error;
1464 return FSSH_B_OK;
1468 } // namespace FSShell