1/*
2 * Copyright 2005-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6#include <errno.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <unistd.h>
11
12#include <Directory.h>
13#include <Entry.h>
14#include <File.h>
15#include <fs_attr.h>
16#include <Mime.h>
17#include <Node.h>
18#include <Path.h>
19#include <SymLink.h>
20#include <TypeConstants.h>
21
22#include <EntryFilter.h>
23
24
25using BPrivate::EntryFilter;
26
27
28static const char *kCommandName = "copyattr";
29static const int kCopyBufferSize = 64 * 1024;	// 64 KB
30
31static int kArgc;
32static const char *const *kArgv;
33
34// usage
35const char *kUsage =
36"Usage: %s <options> <source> [ ... ] <destination>\n"
37"\n"
38"Copies attributes from one or more files to another, or copies one or more\n"
39"files or directories, with all or a specified subset of their attributes, to\n"
40"another location.\n"
41"\n"
42"If option \"-d\"/\"--data\" is given, the behavior is similar to \"cp -df\",\n"
43"save that attributes are copied. That is, if more than one source file is\n"
44"given, the destination file must be a directory. If the destination is a\n"
45"directory (or a symlink to a directory), the source files are copied into\n"
46"the destination directory. Entries that are in the way are removed, unless\n"
47"they are directories. If the source is a directory too, the attributes will\n"
48"be copied and, if recursive operation is specified, the program continues\n"
49"copying the contents of the source directory. If the source is not a\n"
50"directory the program aborts with an error message.\n"
51"\n"
52"If option \"-d\"/\"--data\" is not given, only attributes are copied.\n"
53"Regardless of the file type of the destination, the attributes of the source\n"
54"files are copied to it. If an attribute with the same name as one to be\n"
55"copied already exists, it is replaced. If more than one source file is\n"
56"specified the semantics are similar to invoking the program multiple times\n"
57"with the same options and destination and only one source file at a time,\n"
58"in the order the source files are given. If recursive operation is\n"
59"specified, the program recursively copies the attributes of the directory\n"
60"contents; if the destination file is not a directory, or for a source entry\n"
61"there exists no destination entry, the program aborts with an error\n"
62"message.\n"
63"\n"
64"Note, that the behavior of the program differs from the one shipped with\n"
65"BeOS R5.\n"
66"\n"
67"Options:\n"
68"  -d, --data         - Copy the data of the file(s), too.\n"
69"  -h, --help         - Print this help text and exit.\n"
70"  -m, --move         - If -d is given, the source files are removed after\n"
71"                       being copied. Has no effect otherwise.\n"
72"  -n, --name <name>  - Only copy the attribute with name <name>.\n"
73"  -r, --recursive    - Copy directories recursively.\n"
74"  -t, --type <type>  - Copy only the attributes of type <type>. If -n is\n"
75"                       specified too, only the attribute matching the name\n"
76"                       and the type is copied.\n"
77"  -x <pattern>       - Exclude source entries matching <pattern>.\n"
78"  -X <pattern>       - Exclude source paths matching <pattern>.\n"
79"  -v, --verbose      - Print more messages.\n"
80"   -, --             - Marks the end of options. The arguments after, even\n"
81"                       if starting with \"-\" are considered file names.\n"
82"\n"
83"Parameters:\n"
84"  <type>             - One of: int, llong, string, mimestr, float, double,\n"
85"                       boolean.\n"
86;
87
88// supported attribute types
89struct supported_attribute_type {
90	const char	*type_name;
91	type_code	type;
92};
93
94const supported_attribute_type kSupportedAttributeTypes[] = {
95	{ "int",		B_INT32_TYPE },
96	{ "llong",		B_INT64_TYPE },
97	{ "string",		B_STRING_TYPE },
98	{ "mimestr",	B_MIME_STRING_TYPE },
99	{ "float",		B_FLOAT_TYPE },
100	{ "double",		B_DOUBLE_TYPE },
101	{ "boolean",	B_BOOL_TYPE },
102	{ NULL, 0 },
103};
104
105// AttributeFilter
106struct AttributeFilter {
107	AttributeFilter()
108		:
109		fName(NULL),
110		fType(B_ANY_TYPE)
111	{
112	}
113
114	void SetTo(const char *name, type_code type)
115	{
116		fName = name;
117		fType = type;
118	}
119
120	bool Filter(const char *name, type_code type) const {
121		if (fName && strcmp(name, fName) != 0)
122			return false;
123
124		return (fType == B_ANY_TYPE || type == fType);
125	}
126
127private:
128	const char	*fName;
129	type_code	fType;
130};
131
132
133// Parameters
134struct Parameters {
135	Parameters()
136		:
137		copy_data(false),
138		recursive(false),
139		move_files(false),
140		verbose(false)
141	{
142	}
143
144	bool			copy_data;
145	bool			recursive;
146	bool			move_files;
147	bool			verbose;
148	AttributeFilter	attribute_filter;
149	EntryFilter		entry_filter;
150};
151
152
153// print_usage
154static void
155print_usage(bool error)
156{
157	// get command name
158	const char *commandName = NULL;
159	if (kArgc > 0) {
160		if (const char *lastSlash = strchr(kArgv[0], '/'))
161			commandName = lastSlash + 1;
162		else
163			commandName = kArgv[0];
164	}
165
166	if (!commandName || strlen(commandName) == 0)
167		commandName = kCommandName;
168
169	// print usage
170	fprintf((error ? stderr : stdout), kUsage, commandName);
171}
172
173
174// print_usage_and_exit
175static void
176print_usage_and_exit(bool error)
177{
178	print_usage(error);
179	exit(error ? 1 : 0);
180}
181
182
183// next_arg
184static const char *
185next_arg(int &argi, bool optional = false)
186{
187	if (argi >= kArgc) {
188		if (!optional)
189			print_usage_and_exit(true);
190		return NULL;
191	}
192
193	return kArgv[argi++];
194}
195
196
197// copy_attributes
198static void
199copy_attributes(const char *sourcePath, BNode &source, const char *destPath,
200	BNode &destination, const Parameters &parameters)
201{
202	char attrName[B_ATTR_NAME_LENGTH];
203	while (source.GetNextAttrName(attrName) == B_OK) {
204		// get attr info
205		attr_info attrInfo;
206		status_t error = source.GetAttrInfo(attrName, &attrInfo);
207		if (error != B_OK) {
208			fprintf(stderr, "Error: Failed to get info of attribute \"%s\" "
209				"of file \"%s\": %s\n", attrName, sourcePath, strerror(error));
210			exit(1);
211		}
212
213		// filter
214		if (!parameters.attribute_filter.Filter(attrName, attrInfo.type))
215			continue;
216
217		// copy the attribute
218		char buffer[kCopyBufferSize];
219		off_t offset = 0;
220		off_t bytesLeft = attrInfo.size;
221		// go at least once through the loop, so that empty attribute will be
222		// created as well
223		do {
224			size_t toRead = kCopyBufferSize;
225			if ((off_t)toRead > bytesLeft)
226				toRead = bytesLeft;
227
228			// read
229			ssize_t bytesRead = source.ReadAttr(attrName, attrInfo.type,
230				offset, buffer, toRead);
231			if (bytesRead < 0) {
232				fprintf(stderr, "Error: Failed to read attribute \"%s\" "
233					"of file \"%s\": %s\n", attrName, sourcePath,
234					strerror(bytesRead));
235				exit(1);
236			}
237
238			// write
239			ssize_t bytesWritten = destination.WriteAttr(attrName,
240				attrInfo.type, offset, buffer, bytesRead);
241			if (bytesWritten < 0) {
242				fprintf(stderr, "Error: Failed to write attribute \"%s\" "
243					"of file \"%s\": %s\n", attrName, destPath,
244					strerror(bytesWritten));
245				exit(1);
246			}
247
248			bytesLeft -= bytesRead;
249			offset += bytesRead;
250
251		} while (bytesLeft > 0);
252	}
253}
254
255
256// copy_file_data
257static void
258copy_file_data(const char *sourcePath, BFile &source, const char *destPath,
259	BFile &destination, const Parameters &parameters)
260{
261	char buffer[kCopyBufferSize];
262	off_t offset = 0;
263	while (true) {
264		// read
265		ssize_t bytesRead = source.ReadAt(offset, buffer, sizeof(buffer));
266		if (bytesRead < 0) {
267			fprintf(stderr, "Error: Failed to read from file \"%s\": %s\n",
268				sourcePath, strerror(bytesRead));
269			exit(1);
270		}
271
272		if (bytesRead == 0)
273			return;
274
275		// write
276		ssize_t bytesWritten = destination.WriteAt(offset, buffer, bytesRead);
277		if (bytesWritten < 0) {
278			fprintf(stderr, "Error: Failed to write to file \"%s\": %s\n",
279				destPath, strerror(bytesWritten));
280			exit(1);
281		}
282
283		offset += bytesRead;
284	}
285}
286
287
288// copy_entry
289static void
290copy_entry(const char *sourcePath, const char *destPath,
291	const Parameters &parameters)
292{
293	// apply entry filter
294	if (!parameters.entry_filter.Filter(sourcePath))
295		return;
296
297	// stat source
298	struct stat sourceStat;
299	if (lstat(sourcePath, &sourceStat) < 0) {
300		fprintf(stderr, "Error: Couldn't access \"%s\": %s\n", sourcePath,
301			strerror(errno));
302		exit(1);
303	}
304
305	// stat destination
306	struct stat destStat;
307	bool destExists = lstat(destPath, &destStat) == 0;
308
309	if (!destExists && !parameters.copy_data) {
310		fprintf(stderr, "Error: Destination file \"%s\" does not exist.\n",
311			destPath);
312		exit(1);
313	}
314
315	if (parameters.verbose)
316		printf("%s\n", destPath);
317
318	// check whether to delete/create the destination
319	bool unlinkDest = (destExists && parameters.copy_data);
320	bool createDest = parameters.copy_data;
321	if (destExists) {
322		if (S_ISDIR(destStat.st_mode)) {
323			if (S_ISDIR(sourceStat.st_mode)) {
324				// both are dirs; nothing to do
325				unlinkDest = false;
326				createDest = false;
327			} else if (parameters.copy_data || parameters.recursive) {
328				// destination is directory, but source isn't, and mode is
329				// not non-recursive attributes-only copy
330				fprintf(stderr, "Error: Can't copy \"%s\", since directory "
331					"\"%s\" is in the way.\n", sourcePath, destPath);
332				exit(1);
333			}
334		}
335	}
336
337	// unlink the destination
338	if (unlinkDest) {
339		if (unlink(destPath) < 0) {
340			fprintf(stderr, "Error: Failed to unlink \"%s\": %s\n", destPath,
341				strerror(errno));
342			exit(1);
343		}
344	}
345
346	// open source node
347	BNode _sourceNode;
348	BFile sourceFile;
349	BDirectory sourceDir;
350	BNode *sourceNode = NULL;
351	status_t error;
352
353	if (S_ISDIR(sourceStat.st_mode)) {
354		error = sourceDir.SetTo(sourcePath);
355		sourceNode = &sourceDir;
356	} else if (S_ISREG(sourceStat.st_mode)) {
357		error = sourceFile.SetTo(sourcePath, B_READ_ONLY);
358		sourceNode = &sourceFile;
359	} else {
360		error = _sourceNode.SetTo(sourcePath);
361		sourceNode = &_sourceNode;
362	}
363
364	if (error != B_OK) {
365		fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
366			sourcePath, strerror(error));
367		exit(1);
368	}
369
370	// create the destination
371	BNode _destNode;
372	BDirectory destDir;
373	BFile destFile;
374	BSymLink destSymLink;
375	BNode *destNode = NULL;
376
377	if (createDest) {
378		if (S_ISDIR(sourceStat.st_mode)) {
379			// create dir
380			error = BDirectory().CreateDirectory(destPath, &destDir);
381			if (error != B_OK) {
382				fprintf(stderr, "Error: Failed to make directory \"%s\": %s\n",
383					destPath, strerror(error));
384				exit(1);
385			}
386
387			destNode = &destDir;
388
389		} else if (S_ISREG(sourceStat.st_mode)) {
390			// create file
391			error = BDirectory().CreateFile(destPath, &destFile);
392			if (error != B_OK) {
393				fprintf(stderr, "Error: Failed to create file \"%s\": %s\n",
394					destPath, strerror(error));
395				exit(1);
396			}
397
398			destNode = &destFile;
399
400			// copy file contents
401			copy_file_data(sourcePath, sourceFile, destPath, destFile,
402				parameters);
403
404		} else if (S_ISLNK(sourceStat.st_mode)) {
405			// read symlink
406			char linkTo[B_PATH_NAME_LENGTH + 1];
407			ssize_t bytesRead = readlink(sourcePath, linkTo,
408				sizeof(linkTo) - 1);
409			if (bytesRead < 0) {
410				fprintf(stderr, "Error: Failed to read symlink \"%s\": %s\n",
411					sourcePath, strerror(errno));
412				exit(1);
413			}
414
415			// null terminate the link contents
416			linkTo[bytesRead] = '\0';
417
418			// create symlink
419			error = BDirectory().CreateSymLink(destPath, linkTo, &destSymLink);
420			if (error != B_OK) {
421				fprintf(stderr, "Error: Failed to create symlink \"%s\": %s\n",
422					destPath, strerror(error));
423				exit(1);
424			}
425
426			destNode = &destSymLink;
427
428		} else {
429			fprintf(stderr, "Error: Source file \"%s\" has unsupported type.\n",
430				sourcePath);
431			exit(1);
432		}
433
434		// copy attributes (before setting the permissions!)
435		copy_attributes(sourcePath, *sourceNode, destPath, *destNode,
436			parameters);
437
438		// set file owner, group, permissions, times
439		destNode->SetOwner(sourceStat.st_uid);
440		destNode->SetGroup(sourceStat.st_gid);
441		destNode->SetPermissions(sourceStat.st_mode);
442		#ifdef HAIKU_TARGET_PLATFORM_HAIKU
443			destNode->SetCreationTime(sourceStat.st_crtime);
444		#endif
445		destNode->SetModificationTime(sourceStat.st_mtime);
446
447	} else {
448		// open destination node
449		error = _destNode.SetTo(destPath);
450		if (error != B_OK) {
451			fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
452				destPath, strerror(error));
453			exit(1);
454		}
455
456		destNode = &_destNode;
457
458		// copy attributes
459		copy_attributes(sourcePath, *sourceNode, destPath, *destNode,
460			parameters);
461	}
462
463	// the destination node is no longer needed
464	destNode->Unset();
465
466	// recurse
467	if (parameters.recursive && S_ISDIR(sourceStat.st_mode)) {
468		char buffer[offsetof(struct dirent, d_name) + B_FILE_NAME_LENGTH];
469		dirent *entry = (dirent*)buffer;
470		while (sourceDir.GetNextDirents(entry, sizeof(buffer), 1) == 1) {
471			if (strcmp(entry->d_name, ".") == 0
472				|| strcmp(entry->d_name, "..") == 0) {
473				continue;
474			}
475
476			// construct new entry paths
477			BPath sourceEntryPath;
478			error = sourceEntryPath.SetTo(sourcePath, entry->d_name);
479			if (error != B_OK) {
480				fprintf(stderr, "Error: Failed to construct entry path from "
481					"dir \"%s\" and name \"%s\": %s\n",
482					sourcePath, entry->d_name, strerror(error));
483				exit(1);
484			}
485
486			BPath destEntryPath;
487			error = destEntryPath.SetTo(destPath, entry->d_name);
488			if (error != B_OK) {
489				fprintf(stderr, "Error: Failed to construct entry path from "
490					"dir \"%s\" and name \"%s\": %s\n",
491					destPath, entry->d_name, strerror(error));
492				exit(1);
493			}
494
495			// copy the entry
496			copy_entry(sourceEntryPath.Path(), destEntryPath.Path(),
497				parameters);
498		}
499	}
500
501	// remove source in move mode
502	if (parameters.move_files) {
503		if (S_ISDIR(sourceStat.st_mode)) {
504			if (rmdir(sourcePath) < 0) {
505				fprintf(stderr, "Error: Failed to remove \"%s\": %s\n",
506					sourcePath, strerror(errno));
507				exit(1);
508			}
509
510		} else {
511			if (unlink(sourcePath) < 0) {
512				fprintf(stderr, "Error: Failed to unlink \"%s\": %s\n",
513					sourcePath, strerror(errno));
514				exit(1);
515			}
516		}
517	}
518}
519
520// copy_files
521static void
522copy_files(const char **sourcePaths, int sourceCount,
523	const char *destPath, const Parameters &parameters)
524{
525	// check, if destination exists
526	BEntry destEntry;
527	status_t error = destEntry.SetTo(destPath);
528	if (error != B_OK) {
529		fprintf(stderr, "Error: Couldn't access \"%s\": %s\n", destPath,
530			strerror(error));
531		exit(1);
532	}
533	bool destExists = destEntry.Exists();
534
535	// If it exists, check whether it is a directory. In case we don't copy
536	// the data, we pretend the destination is no directory, even if it is
537	// one.
538	bool destIsDir = false;
539	if (destExists && parameters.copy_data) {
540		struct stat st;
541		error = destEntry.GetStat(&st);
542		if (error != B_OK) {
543			fprintf(stderr, "Error: Failed to stat \"%s\": %s\n", destPath,
544				strerror(error));
545			exit(1);
546		}
547
548		if (S_ISDIR(st.st_mode)) {
549			destIsDir = true;
550		} else if (S_ISLNK(st.st_mode)) {
551			// a symlink -- check if it refers to a dir
552			BEntry resolvedDestEntry;
553			if (resolvedDestEntry.SetTo(destPath, true) == B_OK
554				&& resolvedDestEntry.IsDirectory()) {
555				destIsDir = true;
556			}
557		}
558	}
559
560	// If we have multiple source files, the destination should be a directory,
561	// if we want to copy the file data.
562	if (sourceCount > 1 && parameters.copy_data && !destIsDir) {
563		fprintf(stderr, "Error: Destination needs to be a directory when "
564			"multiple source files are specified and option \"-d\" is "
565			"given.\n");
566		exit(1);
567	}
568
569	// iterate through the source files
570	for (int i = 0; i < sourceCount; i++) {
571		const char *sourcePath = sourcePaths[i];
572		// If the destination is a directory, we usually want to copy the
573		// sources into it. The user might have specified a source path ending
574		// in "/." or "/.." however, in which case we copy the contents of the
575		// given directory.
576		bool copySourceContentsOnly = false;
577		if (destIsDir) {
578			// skip trailing '/'s
579			int sourceLen = strlen(sourcePath);
580			while (sourceLen > 1 && sourcePath[sourceLen - 1] == '/')
581				sourceLen--;
582
583			// find the start of the leaf name
584			int leafStart = sourceLen;
585			while (leafStart > 0 && sourcePath[leafStart - 1] != '/')
586				leafStart--;
587
588			// If the path is the root directory or the leaf is "." or "..",
589			// we copy the contents only.
590			int leafLen = sourceLen - leafStart;
591			if (leafLen == 0 || (leafLen <= 2
592					&& strncmp(sourcePath + leafStart, "..", leafLen) == 0)) {
593				copySourceContentsOnly = true;
594			}
595		}
596
597		if (destIsDir && !copySourceContentsOnly) {
598			// construct a usable destination entry path
599			// normalize source path
600			BPath normalizedSourcePath;
601			error = normalizedSourcePath.SetTo(sourcePath);
602			if (error != B_OK) {
603				fprintf(stderr, "Error: Invalid path \"%s\".\n", sourcePath);
604				exit(1);
605			}
606
607			BPath destEntryPath;
608			error = destEntryPath.SetTo(destPath, normalizedSourcePath.Leaf());
609			if (error != B_OK) {
610				fprintf(stderr, "Error: Failed to get destination path for "
611					"source \"%s\" and destination directory \"%s\".\n",
612					sourcePath, destPath);
613				exit(1);
614			}
615
616			copy_entry(normalizedSourcePath.Path(), destEntryPath.Path(),
617				parameters);
618		} else {
619			copy_entry(sourcePath, destPath, parameters);
620		}
621	}
622}
623
624
625// main
626int
627main(int argc, const char *const *argv)
628{
629	kArgc = argc;
630	kArgv = argv;
631
632	// parameters
633	Parameters parameters;
634	const char *attributeName = NULL;
635	const char *attributeTypeString = NULL;
636	const char **files = new const char*[argc];
637	int fileCount = 0;
638
639	// parse the arguments
640	bool moreOptions = true;
641	for (int argi = 1; argi < argc; ) {
642		const char *arg = argv[argi++];
643		if (moreOptions && arg[0] == '-') {
644			if (strcmp(arg, "-d") == 0 || strcmp(arg, "--data") == 0) {
645				parameters.copy_data = true;
646
647			} else if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
648				print_usage_and_exit(false);
649
650			} else if (strcmp(arg, "-m") == 0 || strcmp(arg, "--move") == 0) {
651				parameters.move_files = true;
652
653			} else if (strcmp(arg, "-n") == 0 || strcmp(arg, "--name") == 0) {
654				if (attributeName) {
655					fprintf(stderr, "Error: Only one attribute name can be "
656						"specified.\n");
657					exit(1);
658				}
659
660				attributeName = next_arg(argi);
661
662			} else if (strcmp(arg, "-r") == 0
663					|| strcmp(arg, "--recursive") == 0) {
664				parameters.recursive = true;
665
666			} else if (strcmp(arg, "-t") == 0 || strcmp(arg, "--type") == 0) {
667				if (attributeTypeString) {
668					fprintf(stderr, "Error: Only one attribute type can be "
669						"specified.\n");
670					exit(1);
671				}
672
673				attributeTypeString = next_arg(argi);
674
675			} else if (strcmp(arg, "-v") == 0
676					|| strcmp(arg, "--verbose") == 0) {
677				parameters.verbose = true;
678
679 			} else if (strcmp(arg, "-x") == 0) {
680 				parameters.entry_filter.AddExcludeFilter(next_arg(argi), true);
681
682 			} else if (strcmp(arg, "-X") == 0) {
683 				parameters.entry_filter.AddExcludeFilter(next_arg(argi), false);
684
685			} else if (strcmp(arg, "-") == 0 || strcmp(arg, "--") == 0) {
686				moreOptions = false;
687
688			} else {
689				fprintf(stderr, "Error: Invalid option: \"%s\"\n", arg);
690				print_usage_and_exit(true);
691			}
692
693		} else {
694			// file
695			files[fileCount++] = arg;
696		}
697	}
698
699	// check parameters
700
701	// enough files
702	if (fileCount < 2) {
703		fprintf(stderr, "Error: Not enough file names specified.\n");
704		print_usage_and_exit(true);
705	}
706
707	// attribute type
708	type_code attributeType = B_ANY_TYPE;
709	if (attributeTypeString) {
710		bool found = false;
711		for (int i = 0; kSupportedAttributeTypes[i].type_name; i++) {
712			if (strcmp(attributeTypeString,
713					kSupportedAttributeTypes[i].type_name) == 0) {
714				found = true;
715				attributeType = kSupportedAttributeTypes[i].type;
716				break;
717			}
718		}
719
720		if (!found) {
721			fprintf(stderr, "Error: Unsupported attribute type: \"%s\"\n",
722				attributeTypeString);
723			exit(1);
724		}
725	}
726
727	// init the attribute filter
728	parameters.attribute_filter.SetTo(attributeName, attributeType);
729
730	// turn of move_files, if we are not copying the file data
731	parameters.move_files &= parameters.copy_data;
732
733	// do the copying
734	fileCount--;
735	const char *destination = files[fileCount];
736	files[fileCount] = NULL;
737	copy_files(files, fileCount, destination, parameters);
738	delete[] files;
739
740	return 0;
741}
742