2 * Copyright 2005-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
12 #include <Directory.h>
20 #include <TypeConstants.h>
22 #include <EntryFilter.h>
25 using BPrivate::EntryFilter
;
28 static const char *kCommandName
= "copyattr";
29 static const int kCopyBufferSize
= 64 * 1024; // 64 KB
32 static const char *const *kArgv
;
36 "Usage: %s <options> <source> [ ... ] <destination>\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"
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"
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"
64 "Note, that the behavior of the program differs from the one shipped with\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"
84 " <type> - One of: int, llong, string, mimestr, float, double,\n"
88 // supported attribute types
89 struct supported_attribute_type
{
90 const char *type_name
;
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
},
106 struct AttributeFilter
{
114 void SetTo(const char *name
, type_code type
)
120 bool Filter(const char *name
, type_code type
) const {
121 if (fName
&& strcmp(name
, fName
) != 0)
124 return (fType
== B_ANY_TYPE
|| type
== fType
);
148 AttributeFilter attribute_filter
;
149 EntryFilter entry_filter
;
155 print_usage(bool error
)
158 const char *commandName
= NULL
;
160 if (const char *lastSlash
= strchr(kArgv
[0], '/'))
161 commandName
= lastSlash
+ 1;
163 commandName
= kArgv
[0];
166 if (!commandName
|| strlen(commandName
) == 0)
167 commandName
= kCommandName
;
170 fprintf((error
? stderr
: stdout
), kUsage
, commandName
);
174 // print_usage_and_exit
176 print_usage_and_exit(bool error
)
185 next_arg(int &argi
, bool optional
= false)
189 print_usage_and_exit(true);
193 return kArgv
[argi
++];
199 copy_attributes(const char *sourcePath
, BNode
&source
, const char *destPath
,
200 BNode
&destination
, const Parameters
¶meters
)
202 char attrName
[B_ATTR_NAME_LENGTH
];
203 while (source
.GetNextAttrName(attrName
) == B_OK
) {
206 status_t error
= source
.GetAttrInfo(attrName
, &attrInfo
);
208 fprintf(stderr
, "Error: Failed to get info of attribute \"%s\" "
209 "of file \"%s\": %s\n", attrName
, sourcePath
, strerror(error
));
214 if (!parameters
.attribute_filter
.Filter(attrName
, attrInfo
.type
))
217 // copy the attribute
218 char buffer
[kCopyBufferSize
];
220 off_t bytesLeft
= attrInfo
.size
;
221 // go at least once through the loop, so that empty attribute will be
224 size_t toRead
= kCopyBufferSize
;
225 if ((off_t
)toRead
> bytesLeft
)
229 ssize_t bytesRead
= source
.ReadAttr(attrName
, attrInfo
.type
,
230 offset
, buffer
, toRead
);
232 fprintf(stderr
, "Error: Failed to read attribute \"%s\" "
233 "of file \"%s\": %s\n", attrName
, sourcePath
,
234 strerror(bytesRead
));
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
));
248 bytesLeft
-= bytesRead
;
251 } while (bytesLeft
> 0);
258 copy_file_data(const char *sourcePath
, BFile
&source
, const char *destPath
,
259 BFile
&destination
, const Parameters
¶meters
)
261 char buffer
[kCopyBufferSize
];
265 ssize_t bytesRead
= source
.ReadAt(offset
, buffer
, sizeof(buffer
));
267 fprintf(stderr
, "Error: Failed to read from file \"%s\": %s\n",
268 sourcePath
, strerror(bytesRead
));
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
));
290 copy_entry(const char *sourcePath
, const char *destPath
,
291 const Parameters
¶meters
)
293 // apply entry filter
294 if (!parameters
.entry_filter
.Filter(sourcePath
))
298 struct stat sourceStat
;
299 if (lstat(sourcePath
, &sourceStat
) < 0) {
300 fprintf(stderr
, "Error: Couldn't access \"%s\": %s\n", sourcePath
,
306 struct stat destStat
;
307 bool destExists
= lstat(destPath
, &destStat
) == 0;
309 if (!destExists
&& !parameters
.copy_data
) {
310 fprintf(stderr
, "Error: Destination file \"%s\" does not exist.\n",
315 if (parameters
.verbose
)
316 printf("%s\n", destPath
);
318 // check whether to delete/create the destination
319 bool unlinkDest
= (destExists
&& parameters
.copy_data
);
320 bool createDest
= parameters
.copy_data
;
322 if (S_ISDIR(destStat
.st_mode
)) {
323 if (S_ISDIR(sourceStat
.st_mode
)) {
324 // both are dirs; nothing to do
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
);
337 // unlink the destination
339 if (unlink(destPath
) < 0) {
340 fprintf(stderr
, "Error: Failed to unlink \"%s\": %s\n", destPath
,
349 BDirectory sourceDir
;
350 BNode
*sourceNode
= NULL
;
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
;
360 error
= _sourceNode
.SetTo(sourcePath
);
361 sourceNode
= &_sourceNode
;
365 fprintf(stderr
, "Error: Failed to open \"%s\": %s\n",
366 sourcePath
, strerror(error
));
370 // create the destination
374 BSymLink destSymLink
;
375 BNode
*destNode
= NULL
;
378 if (S_ISDIR(sourceStat
.st_mode
)) {
380 error
= BDirectory().CreateDirectory(destPath
, &destDir
);
382 fprintf(stderr
, "Error: Failed to make directory \"%s\": %s\n",
383 destPath
, strerror(error
));
389 } else if (S_ISREG(sourceStat
.st_mode
)) {
391 error
= BDirectory().CreateFile(destPath
, &destFile
);
393 fprintf(stderr
, "Error: Failed to create file \"%s\": %s\n",
394 destPath
, strerror(error
));
398 destNode
= &destFile
;
400 // copy file contents
401 copy_file_data(sourcePath
, sourceFile
, destPath
, destFile
,
404 } else if (S_ISLNK(sourceStat
.st_mode
)) {
406 char linkTo
[B_PATH_NAME_LENGTH
+ 1];
407 ssize_t bytesRead
= readlink(sourcePath
, linkTo
,
410 fprintf(stderr
, "Error: Failed to read symlink \"%s\": %s\n",
411 sourcePath
, strerror(errno
));
415 // null terminate the link contents
416 linkTo
[bytesRead
] = '\0';
419 error
= BDirectory().CreateSymLink(destPath
, linkTo
, &destSymLink
);
421 fprintf(stderr
, "Error: Failed to create symlink \"%s\": %s\n",
422 destPath
, strerror(error
));
426 destNode
= &destSymLink
;
429 fprintf(stderr
, "Error: Source file \"%s\" has unsupported type.\n",
434 // copy attributes (before setting the permissions!)
435 copy_attributes(sourcePath
, *sourceNode
, destPath
, *destNode
,
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
);
445 destNode
->SetModificationTime(sourceStat
.st_mtime
);
448 // open destination node
449 error
= _destNode
.SetTo(destPath
);
451 fprintf(stderr
, "Error: Failed to open \"%s\": %s\n",
452 destPath
, strerror(error
));
456 destNode
= &_destNode
;
459 copy_attributes(sourcePath
, *sourceNode
, destPath
, *destNode
,
463 // the destination node is no longer needed
467 if (parameters
.recursive
&& S_ISDIR(sourceStat
.st_mode
)) {
468 char buffer
[sizeof(dirent
) + 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) {
476 // construct new entry paths
477 BPath sourceEntryPath
;
478 error
= sourceEntryPath
.SetTo(sourcePath
, entry
->d_name
);
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
));
487 error
= destEntryPath
.SetTo(destPath
, entry
->d_name
);
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
));
496 copy_entry(sourceEntryPath
.Path(), destEntryPath
.Path(),
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
));
511 if (unlink(sourcePath
) < 0) {
512 fprintf(stderr
, "Error: Failed to unlink \"%s\": %s\n",
513 sourcePath
, strerror(errno
));
522 copy_files(const char **sourcePaths
, int sourceCount
,
523 const char *destPath
, const Parameters
¶meters
)
525 // check, if destination exists
527 status_t error
= destEntry
.SetTo(destPath
);
529 fprintf(stderr
, "Error: Couldn't access \"%s\": %s\n", destPath
,
533 bool destExists
= destEntry
.Exists();
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
538 bool destIsDir
= false;
539 if (destExists
&& parameters
.copy_data
) {
541 error
= destEntry
.GetStat(&st
);
543 fprintf(stderr
, "Error: Failed to stat \"%s\": %s\n", destPath
,
548 if (S_ISDIR(st
.st_mode
)) {
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()) {
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 "
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
576 bool copySourceContentsOnly
= false;
578 // skip trailing '/'s
579 int sourceLen
= strlen(sourcePath
);
580 while (sourceLen
> 1 && sourcePath
[sourceLen
- 1] == '/')
583 // find the start of the leaf name
584 int leafStart
= sourceLen
;
585 while (leafStart
> 0 && sourcePath
[leafStart
- 1] != '/')
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;
597 if (destIsDir
&& !copySourceContentsOnly
) {
598 // construct a usable destination entry path
599 // normalize source path
600 BPath normalizedSourcePath
;
601 error
= normalizedSourcePath
.SetTo(sourcePath
);
603 fprintf(stderr
, "Error: Invalid path \"%s\".\n", sourcePath
);
608 error
= destEntryPath
.SetTo(destPath
, normalizedSourcePath
.Leaf());
610 fprintf(stderr
, "Error: Failed to get destination path for "
611 "source \"%s\" and destination directory \"%s\".\n",
612 sourcePath
, destPath
);
616 copy_entry(normalizedSourcePath
.Path(), destEntryPath
.Path(),
619 copy_entry(sourcePath
, destPath
, parameters
);
627 main(int argc
, const char *const *argv
)
633 Parameters parameters
;
634 const char *attributeName
= NULL
;
635 const char *attributeTypeString
= NULL
;
636 const char **files
= new const char*[argc
];
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;
647 } else if (strcmp(arg
, "-h") == 0 || strcmp(arg
, "--help") == 0) {
648 print_usage_and_exit(false);
650 } else if (strcmp(arg
, "-m") == 0 || strcmp(arg
, "--move") == 0) {
651 parameters
.move_files
= true;
653 } else if (strcmp(arg
, "-n") == 0 || strcmp(arg
, "--name") == 0) {
655 fprintf(stderr
, "Error: Only one attribute name can be "
660 attributeName
= next_arg(argi
);
662 } else if (strcmp(arg
, "-r") == 0
663 || strcmp(arg
, "--recursive") == 0) {
664 parameters
.recursive
= true;
666 } else if (strcmp(arg
, "-t") == 0 || strcmp(arg
, "--type") == 0) {
667 if (attributeTypeString
) {
668 fprintf(stderr
, "Error: Only one attribute type can be "
673 attributeTypeString
= next_arg(argi
);
675 } else if (strcmp(arg
, "-v") == 0
676 || strcmp(arg
, "--verbose") == 0) {
677 parameters
.verbose
= true;
679 } else if (strcmp(arg
, "-x") == 0) {
680 parameters
.entry_filter
.AddExcludeFilter(next_arg(argi
), true);
682 } else if (strcmp(arg
, "-X") == 0) {
683 parameters
.entry_filter
.AddExcludeFilter(next_arg(argi
), false);
685 } else if (strcmp(arg
, "-") == 0 || strcmp(arg
, "--") == 0) {
689 fprintf(stderr
, "Error: Invalid option: \"%s\"\n", arg
);
690 print_usage_and_exit(true);
695 files
[fileCount
++] = arg
;
703 fprintf(stderr
, "Error: Not enough file names specified.\n");
704 print_usage_and_exit(true);
708 type_code attributeType
= B_ANY_TYPE
;
709 if (attributeTypeString
) {
711 for (int i
= 0; kSupportedAttributeTypes
[i
].type_name
; i
++) {
712 if (strcmp(attributeTypeString
,
713 kSupportedAttributeTypes
[i
].type_name
) == 0) {
715 attributeType
= kSupportedAttributeTypes
[i
].type
;
721 fprintf(stderr
, "Error: Unsupported attribute type: \"%s\"\n",
722 attributeTypeString
);
727 // init the attribute filter
728 parameters
.attribute_filter
.SetTo(attributeName
, attributeType
);
730 // turn of move_files, if we are not copying the file data
731 parameters
.move_files
&= parameters
.copy_data
;
735 const char *destination
= files
[fileCount
];
736 files
[fileCount
] = NULL
;
737 copy_files(files
, fileCount
, destination
, parameters
);