xref: /haiku/src/bin/copyattr.cpp (revision 4c8e85b316c35a9161f5a1c50ad70bc91c83a76f)
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 
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 	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 
127 private:
128 	const char	*fName;
129 	type_code	fType;
130 };
131 
132 
133 // Parameters
134 struct 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
154 static void
155 print_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
175 static void
176 print_usage_and_exit(bool error)
177 {
178 	print_usage(error);
179 	exit(error ? 1 : 0);
180 }
181 
182 
183 // next_arg
184 static const char *
185 next_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
198 static void
199 copy_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
257 static void
258 copy_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
289 static void
290 copy_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
521 static void
522 copy_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
626 int
627 main(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