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