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