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