xref: /haiku/src/tools/fs_shell/command_cp.cpp (revision 2600324b57fa31cdea1627d584d314f2a579c4a8)
1 /*
2  * Copyright 2005-2008, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include "compatibility.h"
7 
8 #include "command_cp.h"
9 
10 #include <fcntl.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <unistd.h>
14 
15 #include <AutoDeleter.h>
16 #include <EntryFilter.h>
17 #include <fs_attr.h>
18 #include <StorageDefs.h>
19 
20 #include "fssh_dirent.h"
21 #include "fssh_errno.h"
22 #include "fssh_errors.h"
23 #include "fssh_fcntl.h"
24 #include "fssh_fs_attr.h"
25 #include "fssh_stat.h"
26 #include "fssh_string.h"
27 #include "fssh_unistd.h"
28 #include "path_util.h"
29 #include "stat_util.h"
30 #include "syscalls.h"
31 
32 
33 using BPrivate::EntryFilter;
34 
35 
36 namespace FSShell {
37 
38 
39 static void *sCopyBuffer = NULL;
40 static const int sCopyBufferSize = 64 * 1024;	// 64 KB
41 
42 struct Options {
43 	Options()
44 		: entryFilter(),
45 		  attributesOnly(false),
46 		  ignoreAttributes(false),
47 		  dereference(true),
48 		  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:
70 	Node() {}
71 	virtual ~Node() {}
72 
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); }
77 
78 	virtual File *ToFile()				{ return NULL; }
79 	virtual Directory *ToDirectory()	{ return NULL; }
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:
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:
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:
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:
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:
143 	HostNode()
144 		: Node(),
145 		  fFD(-1),
146 		  fAttrDir(NULL)
147 	{
148 	}
149 
150 	virtual ~HostNode()
151 	{
152 		if (fFD >= 0)
153 			fssh_close(fFD);
154 		if (fAttrDir)
155 			fs_close_attr_dir(fAttrDir);
156 	}
157 
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 
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 
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 
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 
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 
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:
230 	HostDirectory()
231 		: Directory(),
232 		  HostNode(),
233 		  fDir(NULL)
234 	{
235 	}
236 
237 	virtual ~HostDirectory()
238 	{
239 		if (fDir)
240 			closedir(fDir);
241 	}
242 
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 
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:
286 	HostFile()
287 		: File(),
288 		  HostNode()
289 	{
290 	}
291 
292 	virtual ~HostFile()
293 	{
294 	}
295 
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 
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:
312 	HostSymLink()
313 		: SymLink(),
314 		  HostNode(),
315 		  fPath(NULL)
316 	{
317 	}
318 
319 	virtual ~HostSymLink()
320 	{
321 		if (fPath)
322 			free(fPath);
323 	}
324 
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 
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:
352 	HostFSDomain() {}
353 	virtual ~HostFSDomain() {}
354 
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 
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 
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 
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 
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:
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:
509 	GuestNode()
510 		: Node(),
511 		  fFD(-1),
512 		  fAttrDir(-1)
513 	{
514 	}
515 
516 	virtual ~GuestNode()
517 	{
518 		if (fFD >= 0)
519 			_kern_close(fFD);
520 		if (fAttrDir)
521 			_kern_close(fAttrDir);
522 	}
523 
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 			return fAttrDir;
534 
535 		return FSSH_B_OK;
536 	}
537 
538 	virtual	fssh_ssize_t GetNextAttr(char *name, int size)
539 	{
540 		if (fAttrDir < 0)
541 			return 0;
542 
543 		char buffer[sizeof(fssh_dirent) + B_ATTR_NAME_LENGTH];
544 		struct fssh_dirent *entry = (fssh_dirent *)buffer;
545 		int numRead = _kern_read_dir(fAttrDir, entry, sizeof(buffer), 1);
546 		if (numRead < 0)
547 			return numRead;
548 		if (numRead == 0)
549 			return 0;
550 
551 		int len = strlen(entry->d_name);
552 		if (len >= size)
553 			return FSSH_B_NAME_TOO_LONG;
554 
555 		strcpy(name, entry->d_name);
556 		return 1;
557 	}
558 
559 	virtual fssh_status_t GetAttrInfo(const char *name, fssh_attr_info &info)
560 	{
561 		// open attr
562 		int attrFD = _kern_open_attr(fFD, name, FSSH_O_RDONLY);
563 		if (attrFD < 0)
564 			return attrFD;
565 
566 		// stat attr
567 		struct fssh_stat st;
568 		fssh_status_t error = _kern_read_stat(attrFD, NULL, false, &st,
569 			sizeof(st));
570 
571 		// close attr
572 		_kern_close(attrFD);
573 
574 		if (error != FSSH_B_OK)
575 			return error;
576 
577 		// convert stat to attr info
578 		info.type = st.fssh_st_type;
579 		info.size = st.fssh_st_size;
580 
581 		return FSSH_B_OK;
582 	}
583 
584 	virtual fssh_ssize_t ReadAttr(const char *name, uint32_t type,
585 		fssh_off_t pos, void *buffer, int size)
586 	{
587 		// open attr
588 		int attrFD = _kern_open_attr(fFD, name, FSSH_O_RDONLY);
589 		if (attrFD < 0)
590 			return attrFD;
591 
592 		// stat attr
593 		fssh_ssize_t bytesRead = _kern_read(attrFD, pos, buffer, size);
594 
595 		// close attr
596 		_kern_close(attrFD);
597 
598 		return bytesRead;
599 	}
600 
601 	virtual fssh_ssize_t WriteAttr(const char *name, uint32_t type,
602 		fssh_off_t pos, const void *buffer, int size)
603 	{
604 		// open attr
605 		int attrFD = _kern_create_attr(fFD, name, type, FSSH_O_WRONLY);
606 		if (attrFD < 0)
607 			return attrFD;
608 
609 		// stat attr
610 		fssh_ssize_t bytesWritten = _kern_write(attrFD, pos, buffer, size);
611 
612 		// close attr
613 		_kern_close(attrFD);
614 
615 		return bytesWritten;
616 	}
617 
618 	virtual fssh_status_t RemoveAttr(const char *name)
619 	{
620 		return _kern_remove_attr(fFD, name);
621 	}
622 
623 protected:
624 	int				fFD;
625 	int				fAttrDir;
626 };
627 
628 // GuestDirectory
629 class GuestDirectory : public Directory, public GuestNode {
630 public:
631 	GuestDirectory()
632 		: Directory(),
633 		  GuestNode(),
634 		  fDir(-1)
635 	{
636 	}
637 
638 	virtual ~GuestDirectory()
639 	{
640 		if (fDir)
641 			_kern_close(fDir);
642 	}
643 
644 	virtual fssh_status_t Init(const char *path, int fd,
645 		const struct fssh_stat &st)
646 	{
647 		fssh_status_t error = GuestNode::Init(path, fd, st);
648 		if (error != FSSH_B_OK)
649 			return error;
650 
651 		fDir = _kern_open_dir(fd, NULL);
652 		if (fDir < 0)
653 			return fDir;
654 
655 		return FSSH_B_OK;
656 	}
657 
658 	virtual	fssh_ssize_t GetNextEntry(struct fssh_dirent *entry, int size)
659 	{
660 		return _kern_read_dir(fDir, entry, size, 1);
661 	}
662 
663 private:
664 	int	fDir;
665 };
666 
667 // GuestFile
668 class GuestFile : public File, public GuestNode {
669 public:
670 	GuestFile()
671 		: File(),
672 		  GuestNode()
673 	{
674 	}
675 
676 	virtual ~GuestFile()
677 	{
678 	}
679 
680 	virtual fssh_ssize_t Read(void *buffer, int size)
681 	{
682 		return _kern_read(fFD, -1, buffer, size);
683 	}
684 
685 	virtual fssh_ssize_t Write(const void *buffer, int size)
686 	{
687 		return _kern_write(fFD, -1, buffer, size);
688 	}
689 };
690 
691 // GuestSymLink
692 class GuestSymLink : public SymLink, public GuestNode {
693 public:
694 	GuestSymLink()
695 		: SymLink(),
696 		  GuestNode()
697 	{
698 	}
699 
700 	virtual ~GuestSymLink()
701 	{
702 	}
703 
704 	virtual fssh_ssize_t ReadLink(char *buffer, int _bufferSize)
705 	{
706 		fssh_size_t bufferSize = _bufferSize;
707 		fssh_status_t error = _kern_read_link(fFD, NULL, buffer, &bufferSize);
708 		return (error == FSSH_B_OK ? bufferSize : error);
709 	}
710 };
711 
712 // GuestFSDomain
713 class GuestFSDomain : public FSDomain {
714 public:
715 	GuestFSDomain() {}
716 	virtual ~GuestFSDomain() {}
717 
718 	virtual fssh_status_t Open(const char *path, int openMode, Node *&_node)
719 	{
720 		// open the node
721 		int fd = _kern_open(-1, path, openMode, 0);
722 		if (fd < 0)
723 			return fd;
724 
725 		// stat the node
726 		struct fssh_stat st;
727 		fssh_status_t error = _kern_read_stat(fd, NULL, false, &st, sizeof(st));
728 		if (error < 0) {
729 			_kern_close(fd);
730 			return error;
731 		}
732 
733 		// check the node type and create the node
734 		GuestNode *node = NULL;
735 		switch (st.fssh_st_mode & FSSH_S_IFMT) {
736 			case FSSH_S_IFLNK:
737 				node = new GuestSymLink;
738 				break;
739 			case FSSH_S_IFREG:
740 				node = new GuestFile;
741 				break;
742 			case FSSH_S_IFDIR:
743 				node = new GuestDirectory;
744 				break;
745 			default:
746 				_kern_close(fd);
747 				return FSSH_EINVAL;
748 		}
749 
750 		// init the node
751 		error = node->Init(path, fd, st);
752 			// the node receives ownership of the FD
753 		if (error != FSSH_B_OK) {
754 			delete node;
755 			return error;
756 		}
757 
758 		_node = node;
759 		return FSSH_B_OK;
760 	}
761 
762 	virtual fssh_status_t CreateFile(const char *path,
763 		const struct fssh_stat &st, File *&_file)
764 	{
765 		// create the file
766 		int fd = _kern_open(-1, path, FSSH_O_RDWR | FSSH_O_EXCL | FSSH_O_CREAT,
767 			st.fssh_st_mode & FSSH_S_IUMSK);
768 		if (fd < 0)
769 			return fd;
770 
771 		// apply the other stat fields
772 		fssh_status_t error = _ApplyStat(fd, st);
773 		if (error != FSSH_B_OK) {
774 			_kern_close(fd);
775 			return error;
776 		}
777 
778 		// create the object
779 		GuestFile *file = new GuestFile;
780 		error = file->Init(path, fd, st);
781 		if (error != FSSH_B_OK) {
782 			delete file;
783 			return error;
784 		}
785 
786 		_file = file;
787 		return FSSH_B_OK;
788 	}
789 
790 	virtual fssh_status_t CreateDirectory(const char *path,
791 		const struct fssh_stat &st, Directory *&_dir)
792 	{
793 		// create the dir
794 		fssh_status_t error = _kern_create_dir(-1, path,
795 			st.fssh_st_mode & FSSH_S_IUMSK);
796 		if (error < 0)
797 			return error;
798 
799 		// open the dir node
800 		int fd = _kern_open(-1, path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 0);
801 		if (fd < 0)
802 			return fd;
803 
804 		// apply the other stat fields
805 		error = _ApplyStat(fd, st);
806 		if (error != FSSH_B_OK) {
807 			_kern_close(fd);
808 			return error;
809 		}
810 
811 		// create the object
812 		GuestDirectory *dir = new GuestDirectory;
813 		error = dir->Init(path, fd, st);
814 		if (error != FSSH_B_OK) {
815 			delete dir;
816 			return error;
817 		}
818 
819 		_dir = dir;
820 		return FSSH_B_OK;
821 	}
822 
823 	virtual fssh_status_t CreateSymLink(const char *path, const char *linkTo,
824 		const struct fssh_stat &st, SymLink *&_link)
825 	{
826 		// create the link
827 		fssh_status_t error = _kern_create_symlink(-1, path, linkTo,
828 			st.fssh_st_mode & FSSH_S_IUMSK);
829 		if (error < 0)
830 			return error;
831 
832 		// open the symlink node
833 		int fd = _kern_open(-1, path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 0);
834 		if (fd < 0)
835 			return fd;
836 
837 		// apply the other stat fields
838 		error = _ApplyStat(fd, st);
839 		if (error != FSSH_B_OK) {
840 			_kern_close(fd);
841 			return error;
842 		}
843 
844 		// create the object
845 		GuestSymLink *link = new GuestSymLink;
846 		error = link->Init(path, fd, st);
847 		if (error != FSSH_B_OK) {
848 			delete link;
849 			return error;
850 		}
851 
852 		_link = link;
853 		return FSSH_B_OK;
854 	}
855 
856 	virtual fssh_status_t Unlink(const char *path)
857 	{
858 		return _kern_unlink(-1, path);
859 	}
860 
861 private:
862 	fssh_status_t _ApplyStat(int fd, const struct fssh_stat &st)
863 	{
864 		// TODO: Set times...
865 		return FSSH_B_OK;
866 	}
867 };
868 
869 
870 // #pragma mark -
871 
872 static fssh_status_t copy_entry(FSDomain *sourceDomain, const char *source,
873 	FSDomain *targetDomain, const char *target, const Options &options,
874 	bool dereference);
875 
876 static FSDomain *
877 get_file_domain(const char *target, const char *&fsTarget)
878 {
879 	if (target[0] == ':') {
880 		fsTarget = target + 1;
881 		return new HostFSDomain;
882 	} else {
883 		fsTarget = target;
884 		return new GuestFSDomain;
885 	}
886 }
887 
888 typedef ObjectDeleter<Node> NodeDeleter;
889 typedef ObjectDeleter<FSDomain> DomainDeleter;
890 typedef MemoryDeleter PathDeleter;
891 
892 
893 static fssh_status_t
894 copy_file_contents(const char *source, File *sourceFile, const char *target,
895 	File *targetFile)
896 {
897 	fssh_off_t chunkSize = (sourceFile->Stat().fssh_st_size / 20) / sCopyBufferSize * sCopyBufferSize;
898 	if (chunkSize == 0)
899 		chunkSize = 1;
900 
901 	bool progress = sourceFile->Stat().fssh_st_size > 1024 * 1024;
902 	if (progress) {
903 		printf("%s ", strrchr(target, '/') ? strrchr(target, '/') + 1 : target);
904 		fflush(stdout);
905 	}
906 
907 	fssh_off_t total = 0;
908 	fssh_ssize_t bytesRead;
909 	while ((bytesRead = sourceFile->Read(sCopyBuffer, sCopyBufferSize)) > 0) {
910 		fssh_ssize_t bytesWritten = targetFile->Write(sCopyBuffer, bytesRead);
911 		if (progress && (total % chunkSize) == 0) {
912 			putchar('.');
913 			fflush(stdout);
914 		}
915 		if (bytesWritten < 0) {
916 			fprintf(stderr, "Error while writing to file `%s': %s\n",
917 				target, fssh_strerror(bytesWritten));
918 			return bytesWritten;
919 		}
920 		if (bytesWritten != bytesRead) {
921 			fprintf(stderr, "Could not write all data to file \"%s\".\n",
922 				target);
923 			return FSSH_B_IO_ERROR;
924 		}
925 		total += bytesWritten;
926 	}
927 
928 	if (bytesRead < 0) {
929 		fprintf(stderr, "Error while reading from file `%s': %s\n",
930 			source, fssh_strerror(bytesRead));
931 		return bytesRead;
932 	}
933 
934 	if (progress)
935 		putchar('\n');
936 
937 	return FSSH_B_OK;
938 }
939 
940 
941 static fssh_status_t
942 copy_dir_contents(FSDomain *sourceDomain, const char *source,
943 	Directory *sourceDir, FSDomain *targetDomain, const char *target,
944 	const Options &options)
945 {
946 	char buffer[sizeof(fssh_dirent) + FSSH_B_FILE_NAME_LENGTH];
947 	struct fssh_dirent *entry =  (struct fssh_dirent *)buffer;
948 	fssh_ssize_t numRead;
949 	while ((numRead = sourceDir->GetNextEntry(entry, sizeof(buffer))) > 0) {
950 		// skip "." and ".."
951 		if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
952 			continue;
953 
954 		// compose a new source path name
955 		char *sourceEntry = make_path(source, entry->d_name);
956 		if (!sourceEntry) {
957 			fprintf(stderr, "Error: Failed to allocate source path!\n");
958 			return FSSH_ENOMEM;
959 		}
960 		PathDeleter sourceDeleter(sourceEntry);
961 
962 		// compose a new target path name
963 		char *targetEntry = make_path(target, entry->d_name);
964 		if (!targetEntry) {
965 			fprintf(stderr, "Error: Failed to allocate target path!\n");
966 			return FSSH_ENOMEM;
967 		}
968 		PathDeleter targetDeleter(targetEntry);
969 
970 		fssh_status_t error = copy_entry(sourceDomain, sourceEntry,
971 			targetDomain, targetEntry, options, options.alwaysDereference);
972 		if (error != FSSH_B_OK)
973 			return error;
974 	}
975 
976 	if (numRead < 0) {
977 		fprintf(stderr, "Error reading directory `%s': %s\n", source,
978 			fssh_strerror(numRead));
979 		return numRead;
980 	}
981 
982 	return FSSH_B_OK;
983 }
984 
985 
986 static fssh_status_t
987 copy_attribute(const char *source, Node *sourceNode, const char *target,
988 	Node *targetNode, const char *name, const fssh_attr_info &info)
989 {
990 	// remove the attribute first
991 	targetNode->RemoveAttr(name);
992 
993 	// special case: empty attribute
994 	if (info.size <= 0) {
995 		fssh_ssize_t bytesWritten = targetNode->WriteAttr(name, info.type, 0,
996 			sCopyBuffer, 0);
997 		if (bytesWritten) {
998 			fprintf(stderr, "Error while writing to attribute `%s' of file "
999 				"`%s': %s\n", name, target, fssh_strerror(bytesWritten));
1000 			return bytesWritten;
1001 		}
1002 
1003 		return FSSH_B_OK;
1004 	}
1005 
1006 	// non-empty attribute
1007 	fssh_off_t pos = 0;
1008 	int toCopy = info.size;
1009 	while (toCopy > 0) {
1010 		// read data from source
1011 		int toRead = (toCopy < sCopyBufferSize ? toCopy : sCopyBufferSize);
1012 		fssh_ssize_t bytesRead = sourceNode->ReadAttr(name, info.type, pos,
1013 			sCopyBuffer, toRead);
1014 		if (bytesRead < 0) {
1015 			fprintf(stderr, "Error while reading from attribute `%s' of file "
1016 				"`%s': %s\n", name, source, fssh_strerror(bytesRead));
1017 			return bytesRead;
1018 		}
1019 
1020 		// write data to target
1021 		fssh_ssize_t bytesWritten = targetNode->WriteAttr(name, info.type, pos,
1022 			sCopyBuffer, bytesRead);
1023 		if (bytesWritten < 0) {
1024 			fprintf(stderr, "Error while writing to attribute `%s' of file "
1025 				"`%s': %s\n", name, target, fssh_strerror(bytesWritten));
1026 			return bytesWritten;
1027 		}
1028 
1029 		pos += bytesRead;
1030 		toCopy -= bytesRead;
1031 	}
1032 
1033 	return FSSH_B_OK;
1034 }
1035 
1036 
1037 static fssh_status_t
1038 copy_attributes(const char *source, Node *sourceNode, const char *target,
1039 	Node *targetNode)
1040 {
1041 	char name[B_ATTR_NAME_LENGTH];
1042 	fssh_ssize_t numRead;
1043 	while ((numRead = sourceNode->GetNextAttr(name, sizeof(name))) > 0) {
1044 		fssh_attr_info info;
1045 		// get attribute info
1046 		fssh_status_t error = sourceNode->GetAttrInfo(name, info);
1047 		if (error != FSSH_B_OK) {
1048 			fprintf(stderr, "Error getting info for attribute `%s' of file "
1049 				"`%s': %s\n", name, source, fssh_strerror(error));
1050 			return error;
1051 		}
1052 
1053 		// copy the attribute
1054 		error = copy_attribute(source, sourceNode, target, targetNode, name,
1055 			info);
1056 		if (error != FSSH_B_OK)
1057 			return error;
1058 	}
1059 
1060 	if (numRead < 0) {
1061 		fprintf(stderr, "Error reading attribute directory of `%s': %s\n",
1062 			source, fssh_strerror(numRead));
1063 		return numRead;
1064 	}
1065 
1066 	return FSSH_B_OK;
1067 }
1068 
1069 
1070 static fssh_status_t
1071 copy_entry(FSDomain *sourceDomain, const char *source,
1072 	FSDomain *targetDomain, const char *target, const Options &options,
1073 	bool dereference)
1074 {
1075 	// apply entry filter
1076 	if (!options.entryFilter.Filter(source))
1077 		return FSSH_B_OK;
1078 
1079 	// open the source node
1080 	Node *sourceNode;
1081 	fssh_status_t error = sourceDomain->Open(source,
1082 		FSSH_O_RDONLY | (dereference ? 0 : FSSH_O_NOTRAVERSE),
1083 		sourceNode);
1084 	if (error != FSSH_B_OK) {
1085 		fprintf(stderr, "Error: Failed to open source path `%s': %s\n", source,
1086 			fssh_strerror(error));
1087 		return error;
1088 	}
1089 	NodeDeleter sourceDeleter(sourceNode);
1090 
1091 	// check, if target exists
1092 	Node *targetNode = NULL;
1093 	// try opening with resolving symlinks first
1094 	error = targetDomain->Open(target, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE,
1095 		targetNode);
1096 	NodeDeleter targetDeleter;
1097 	if (error == FSSH_B_OK) {
1098 		// 1. target exists:
1099 		//    check, if it is a dir and, if so, whether source is a dir too
1100 		targetDeleter.SetTo(targetNode);
1101 
1102 		// if the target is a symlink, try resolving it
1103 		if (targetNode->IsSymLink()) {
1104 			Node *resolvedTargetNode;
1105 			error = targetDomain->Open(target, FSSH_O_RDONLY,
1106 				resolvedTargetNode);
1107 			if (error == FSSH_B_OK) {
1108 				targetNode = resolvedTargetNode;
1109 				targetDeleter.SetTo(targetNode);
1110 			}
1111 		}
1112 
1113 		if (sourceNode->IsDirectory() && targetNode->IsDirectory()) {
1114 			// 1.1. target and source are dirs:
1115 			//      -> just copy their contents
1116 			// ...
1117 		} else {
1118 			// 1.2. source and/or target are no dirs
1119 
1120 			if (options.force) {
1121 				// 1.2.1. /force/
1122 				//        -> remove the target and continue with 2.
1123 				targetDeleter.Delete();
1124 				targetNode = NULL;
1125 				error = targetDomain->Unlink(target);
1126 				if (error != FSSH_B_OK) {
1127 					fprintf(stderr, "Error: Failed to remove `%s'\n", target);
1128 					return error;
1129 				}
1130 			} else if (sourceNode->IsFile() && targetNode->IsFile()) {
1131 				// 1.2.1.1. !/force/, but both source and target are files
1132 				//          -> truncate the target file and continue
1133 				targetDeleter.Delete();
1134 				targetNode = NULL;
1135 				error = targetDomain->Open(target, FSSH_O_RDWR | FSSH_O_TRUNC,
1136 					targetNode);
1137 				if (error != FSSH_B_OK) {
1138 					fprintf(stderr, "Error: Failed to open `%s' for writing\n",
1139 						target);
1140 					return error;
1141 				}
1142 			} else {
1143 				// 1.2.1.2. !/force/, source or target isn't a file
1144 				//          -> fail
1145 				fprintf(stderr, "Error: File `%s' does exist.\n", target);
1146 				return FSSH_B_FILE_EXISTS;
1147 			}
1148 		}
1149 	} // else: 2. target doesn't exist: -> just create it
1150 
1151 	// create the target node
1152 	error = FSSH_B_OK;
1153 	if (sourceNode->IsFile()) {
1154 		if (!targetNode) {
1155 			File *file = NULL;
1156 			error = targetDomain->CreateFile(target, sourceNode->Stat(), file);
1157 			if (error == 0)
1158 				targetNode = file;
1159 		}
1160 	} else if (sourceNode->IsDirectory()) {
1161 		// check /recursive/
1162 		if (!options.recursive) {
1163 			fprintf(stderr, "Error: Entry `%s' is a directory.\n", source);
1164 			return FSSH_EISDIR;
1165 		}
1166 
1167 		// create the target only, if it doesn't already exist
1168 		if (!targetNode) {
1169 			Directory *dir = NULL;
1170 			error = targetDomain->CreateDirectory(target, sourceNode->Stat(),
1171 				dir);
1172 			if (error == 0)
1173 				targetNode = dir;
1174 		}
1175 	} else if (sourceNode->IsSymLink()) {
1176 		// read the source link
1177 		SymLink *sourceLink = sourceNode->ToSymLink();
1178 		char linkTo[FSSH_B_PATH_NAME_LENGTH];
1179 		fssh_ssize_t bytesRead = sourceLink->ReadLink(linkTo,
1180 			sizeof(linkTo) - 1);
1181 		if (bytesRead < 0) {
1182 			fprintf(stderr, "Error: Failed to read symlink `%s': %s\n", source,
1183 				fssh_strerror(bytesRead));
1184 		}
1185 		linkTo[bytesRead] = '\0';	// always NULL-terminate
1186 
1187 		// create the target link
1188 		SymLink *link;
1189 		error = targetDomain->CreateSymLink(target, linkTo,
1190 			sourceNode->Stat(),	link);
1191 		if (error == 0)
1192 			targetNode = link;
1193 	} else {
1194 		fprintf(stderr, "Error: Unknown node type. We shouldn't be here!\n");
1195 		return FSSH_EINVAL;
1196 	}
1197 
1198 	if (error != FSSH_B_OK) {
1199 		fprintf(stderr, "Error: Failed to create `%s': %s\n", target,
1200 			fssh_strerror(error));
1201 		return error;
1202 	}
1203 	targetDeleter.SetTo(targetNode);
1204 
1205 	// copy attributes
1206 	if (!options.ignoreAttributes) {
1207 		error = copy_attributes(source, sourceNode, target, targetNode);
1208 		if (error != FSSH_B_OK)
1209 			return error;
1210 	}
1211 
1212 	// copy contents
1213 	if (sourceNode->IsFile()) {
1214 		error = copy_file_contents(source, sourceNode->ToFile(), target,
1215 			targetNode->ToFile());
1216 	} else if (sourceNode->IsDirectory()) {
1217 		error = copy_dir_contents(sourceDomain, source,
1218 			sourceNode->ToDirectory(), targetDomain, target, options);
1219 	}
1220 
1221 	return error;
1222 }
1223 
1224 
1225 fssh_status_t
1226 command_cp(int argc, const char* const* argv)
1227 {
1228 	int sourceCount = 0;
1229 	Options options;
1230 
1231 	const char **sources = new const char*[argc];
1232 	if (!sources) {
1233 		fprintf(stderr, "Error: No memory!\n");
1234 		return FSSH_EINVAL;
1235 	}
1236 	ArrayDeleter<const char*> _(sources);
1237 
1238 	// parse parameters
1239 	for (int argi = 1; argi < argc; argi++) {
1240 		const char *arg = argv[argi];
1241 		if (arg[0] == '-') {
1242 			if (arg[1] == '\0') {
1243 				fprintf(stderr, "Error: Invalid option '-'\n");
1244 				return FSSH_EINVAL;
1245 			}
1246 
1247 			if (arg[1] == '-') {
1248 				if (strcmp(arg, "--ignore-attributes") == 0) {
1249 					options.ignoreAttributes = true;
1250 				} else {
1251 					fprintf(stderr, "Error: Unknown option '%s'\n", arg);
1252 					return FSSH_EINVAL;
1253 				}
1254 			} else {
1255 				for (int i = 1; arg[i]; i++) {
1256 					switch (arg[i]) {
1257 						case 'a':
1258 							options.attributesOnly = true;
1259 						case 'd':
1260 							options.dereference = false;
1261 							break;
1262 						case 'f':
1263 							options.force = true;
1264 							break;
1265 						case 'L':
1266 							options.dereference = true;
1267 							options.alwaysDereference = true;
1268 							break;
1269 						case 'r':
1270 							options.recursive = true;
1271 							break;
1272 						case 'x':
1273 						case 'X':
1274 						{
1275 							const char* pattern;
1276 							if (arg[i + 1] == '\0') {
1277 								if (++argi >= argc) {
1278 									fprintf(stderr, "Error: Option '-%c' need "
1279 										"a pattern as parameter\n", arg[i]);
1280 									return FSSH_EINVAL;
1281 								}
1282 								pattern = argv[argi];
1283 							} else
1284 								pattern = arg + i + 1;
1285 
1286 							options.entryFilter.AddExcludeFilter(pattern,
1287 								arg[i] == 'x');
1288 							break;
1289 						}
1290 						default:
1291 							fprintf(stderr, "Error: Unknown option '-%c'\n",
1292 								arg[i]);
1293 							return FSSH_EINVAL;
1294 					}
1295 				}
1296 			}
1297 		} else {
1298 			sources[sourceCount++] = arg;
1299 		}
1300 	}
1301 
1302 	// check params
1303 	if (sourceCount < 2) {
1304 		fprintf(stderr, "Error: Must specify at least 2 files!\n");
1305 		return FSSH_EINVAL;
1306 	}
1307 
1308 	// check the target
1309 	const char *target = sources[--sourceCount];
1310 	bool targetIsDir = false;
1311 	bool targetExists = false;
1312 	FSDomain *targetDomain = get_file_domain(target, target);
1313 	DomainDeleter targetDomainDeleter(targetDomain);
1314 
1315 	Node *targetNode;
1316 	fssh_status_t error = targetDomain->Open(target, FSSH_O_RDONLY, targetNode);
1317 	if (error == 0) {
1318 		NodeDeleter targetDeleter(targetNode);
1319 		targetExists = true;
1320 
1321 		if (options.attributesOnly) {
1322 			// That's how it should be; we don't care whether the target is
1323 			// a directory or not. We append the attributes to that node in
1324 			// either case.
1325 		} else if (targetNode->IsDirectory()) {
1326 			targetIsDir = true;
1327 		} else {
1328 			if (sourceCount > 1) {
1329 				fprintf(stderr, "Error: Destination `%s' is not a directory!",
1330 					target);
1331 				return FSSH_B_NOT_A_DIRECTORY;
1332 			}
1333 		}
1334 	} else {
1335 		if (options.attributesOnly) {
1336 			fprintf(stderr, "Error: Failed to open target `%s' (it must exist "
1337 				"in attributes only mode): `%s'\n", target,
1338 				fssh_strerror(error));
1339 			return error;
1340 		} else if (sourceCount > 1) {
1341 			fprintf(stderr, "Error: Failed to open destination directory `%s':"
1342 				" `%s'\n", target, fssh_strerror(error));
1343 			return error;
1344 		}
1345 	}
1346 
1347 	// allocate a copy buffer
1348 	sCopyBuffer = malloc(sCopyBufferSize);
1349 	if (!sCopyBuffer) {
1350 		fprintf(stderr, "Error: Failed to allocate copy buffer.\n");
1351 		return FSSH_ENOMEM;
1352 	}
1353 	MemoryDeleter copyBufferDeleter(sCopyBuffer);
1354 
1355 	// open the target node for attributes only mode
1356 	NodeDeleter targetDeleter;
1357 	if (options.attributesOnly) {
1358 		error = targetDomain->Open(target, FSSH_O_WRONLY, targetNode);
1359 		if (error != FSSH_B_OK) {
1360 			fprintf(stderr, "Error: Failed to open target `%s' for writing: "
1361 				"`%s'\n", target, fssh_strerror(error));
1362 			return error;
1363 		}
1364 
1365 		targetDeleter.SetTo(targetNode);
1366 	}
1367 
1368 	// the copy loop
1369 	for (int i = 0; i < sourceCount; i++) {
1370 		const char *source = sources[i];
1371 		FSDomain *sourceDomain = get_file_domain(source, source);
1372 		DomainDeleter sourceDomainDeleter(sourceDomain);
1373 		if (options.attributesOnly) {
1374 			// 0. copy attributes only
1375 			// open the source node
1376 			Node *sourceNode;
1377 			error = sourceDomain->Open(source,
1378 				FSSH_O_RDONLY | (options.dereference ? 0 : FSSH_O_NOTRAVERSE),
1379 				sourceNode);
1380 			if (error != FSSH_B_OK) {
1381 				fprintf(stderr, "Error: Failed to open `%s': %s.\n", source,
1382 					fssh_strerror(error));
1383 				return error;
1384 			}
1385 			NodeDeleter sourceDeleter(sourceNode);
1386 
1387 			// copy the attributes
1388 			error = copy_attributes(source, sourceNode, target, targetNode);
1389 
1390 		} else if (targetExists && targetIsDir) {
1391 			// 1. target exists:
1392 			// 1.1. target is a dir:
1393 			// get the source leaf name
1394 			char leafName[FSSH_B_FILE_NAME_LENGTH];
1395 			error = get_last_path_component(source, leafName, sizeof(leafName));
1396 			if (error != FSSH_B_OK) {
1397 				fprintf(stderr, "Error: Failed to get last path component of "
1398 					"`%s': %s\n", source, fssh_strerror(error));
1399 				return error;
1400 			}
1401 
1402 			if (strcmp(leafName, ".") == 0 || strcmp(leafName, "..") == 0) {
1403 				// 1.1.1. source name is `.' or `..'
1404 				//        -> copy the contents only
1405 				//           (copy_dir_contents())
1406 				// open the source dir
1407 				Node *sourceNode;
1408 				error = sourceDomain->Open(source,
1409 					FSSH_O_RDONLY
1410 						| (options.dereference ? 0 : FSSH_O_NOTRAVERSE),
1411 					sourceNode);
1412 				if (error != FSSH_B_OK) {
1413 					fprintf(stderr, "Error: Failed to open `%s': %s.\n", source,
1414 						fssh_strerror(error));
1415 					return error;
1416 				}
1417 				NodeDeleter sourceDeleter(sourceNode);
1418 
1419 				// check, if it is a dir
1420 				Directory *sourceDir = sourceNode->ToDirectory();
1421 				if (!sourceDir) {
1422 					fprintf(stderr, "Error: Source `%s' is not a directory "
1423 						"although it's last path component is `%s'\n", source,
1424 						leafName);
1425 					return FSSH_EINVAL;
1426 				}
1427 
1428 				error = copy_dir_contents(sourceDomain, source, sourceDir,
1429 					targetDomain, target, options);
1430 			} else {
1431 				// 1.1.2. source has normal name
1432 				//        -> we copy into the dir
1433 				//           (copy_entry(<source>, <target>/<source leaf>))
1434 				// compose a new target path name
1435 				char *targetEntry = make_path(target, leafName);
1436 				if (!targetEntry) {
1437 					fprintf(stderr, "Error: Failed to allocate target path!\n");
1438 					return FSSH_ENOMEM;
1439 				}
1440 				PathDeleter targetDeleter(targetEntry);
1441 
1442 				error = copy_entry(sourceDomain, source, targetDomain,
1443 					targetEntry, options, options.dereference);
1444 			}
1445 		} else {
1446 			// 1.2. target is no dir:
1447 			//      -> if /force/ is given, we replace the target, otherwise
1448 			//         we fail
1449 			//         (copy_entry(<source>, <target>))
1450 			// or
1451 			// 2. target doesn't exist:
1452 			//    -> we create the target as a clone of the source
1453 			//         (copy_entry(<source>, <target>))
1454 			error = copy_entry(sourceDomain, source, targetDomain, target,
1455 				options, options.dereference);
1456 		}
1457 
1458 		if (error != 0)
1459 			return error;
1460 	}
1461 
1462 	return FSSH_B_OK;
1463 }
1464 
1465 
1466 }	// namespace FSShell
1467