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