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