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