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