mirror of
https://review.haiku-os.org/haiku
synced 2025-02-22 21:48:35 +01:00
The dirent struct is not packed, so offsetof(dirent, d_name) != sizeof(dirent). Thus in order not to waste the alignment bytes (which are significant, on x86_64 at least, sizeof(dirent)==32, but offsetof(...)=26.) This is also the most portable way to handle things, and should work just fine in cross-platform code that has a non-zero-sized d_name.
742 lines
19 KiB
C++
742 lines
19 KiB
C++
/*
|
|
* Copyright 2005-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
|
* Distributed under the terms of the MIT License.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <Directory.h>
|
|
#include <Entry.h>
|
|
#include <File.h>
|
|
#include <fs_attr.h>
|
|
#include <Mime.h>
|
|
#include <Node.h>
|
|
#include <Path.h>
|
|
#include <SymLink.h>
|
|
#include <TypeConstants.h>
|
|
|
|
#include <EntryFilter.h>
|
|
|
|
|
|
using BPrivate::EntryFilter;
|
|
|
|
|
|
static const char *kCommandName = "copyattr";
|
|
static const int kCopyBufferSize = 64 * 1024; // 64 KB
|
|
|
|
static int kArgc;
|
|
static const char *const *kArgv;
|
|
|
|
// usage
|
|
const char *kUsage =
|
|
"Usage: %s <options> <source> [ ... ] <destination>\n"
|
|
"\n"
|
|
"Copies attributes from one or more files to another, or copies one or more\n"
|
|
"files or directories, with all or a specified subset of their attributes, to\n"
|
|
"another location.\n"
|
|
"\n"
|
|
"If option \"-d\"/\"--data\" is given, the behavior is similar to \"cp -df\",\n"
|
|
"save that attributes are copied. That is, if more than one source file is\n"
|
|
"given, the destination file must be a directory. If the destination is a\n"
|
|
"directory (or a symlink to a directory), the source files are copied into\n"
|
|
"the destination directory. Entries that are in the way are removed, unless\n"
|
|
"they are directories. If the source is a directory too, the attributes will\n"
|
|
"be copied and, if recursive operation is specified, the program continues\n"
|
|
"copying the contents of the source directory. If the source is not a\n"
|
|
"directory the program aborts with an error message.\n"
|
|
"\n"
|
|
"If option \"-d\"/\"--data\" is not given, only attributes are copied.\n"
|
|
"Regardless of the file type of the destination, the attributes of the source\n"
|
|
"files are copied to it. If an attribute with the same name as one to be\n"
|
|
"copied already exists, it is replaced. If more than one source file is\n"
|
|
"specified the semantics are similar to invoking the program multiple times\n"
|
|
"with the same options and destination and only one source file at a time,\n"
|
|
"in the order the source files are given. If recursive operation is\n"
|
|
"specified, the program recursively copies the attributes of the directory\n"
|
|
"contents; if the destination file is not a directory, or for a source entry\n"
|
|
"there exists no destination entry, the program aborts with an error\n"
|
|
"message.\n"
|
|
"\n"
|
|
"Note, that the behavior of the program differs from the one shipped with\n"
|
|
"BeOS R5.\n"
|
|
"\n"
|
|
"Options:\n"
|
|
" -d, --data - Copy the data of the file(s), too.\n"
|
|
" -h, --help - Print this help text and exit.\n"
|
|
" -m, --move - If -d is given, the source files are removed after\n"
|
|
" being copied. Has no effect otherwise.\n"
|
|
" -n, --name <name> - Only copy the attribute with name <name>.\n"
|
|
" -r, --recursive - Copy directories recursively.\n"
|
|
" -t, --type <type> - Copy only the attributes of type <type>. If -n is\n"
|
|
" specified too, only the attribute matching the name\n"
|
|
" and the type is copied.\n"
|
|
" -x <pattern> - Exclude source entries matching <pattern>.\n"
|
|
" -X <pattern> - Exclude source paths matching <pattern>.\n"
|
|
" -v, --verbose - Print more messages.\n"
|
|
" -, -- - Marks the end of options. The arguments after, even\n"
|
|
" if starting with \"-\" are considered file names.\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
" <type> - One of: int, llong, string, mimestr, float, double,\n"
|
|
" boolean.\n"
|
|
;
|
|
|
|
// supported attribute types
|
|
struct supported_attribute_type {
|
|
const char *type_name;
|
|
type_code type;
|
|
};
|
|
|
|
const supported_attribute_type kSupportedAttributeTypes[] = {
|
|
{ "int", B_INT32_TYPE },
|
|
{ "llong", B_INT64_TYPE },
|
|
{ "string", B_STRING_TYPE },
|
|
{ "mimestr", B_MIME_STRING_TYPE },
|
|
{ "float", B_FLOAT_TYPE },
|
|
{ "double", B_DOUBLE_TYPE },
|
|
{ "boolean", B_BOOL_TYPE },
|
|
{ NULL, 0 },
|
|
};
|
|
|
|
// AttributeFilter
|
|
struct AttributeFilter {
|
|
AttributeFilter()
|
|
:
|
|
fName(NULL),
|
|
fType(B_ANY_TYPE)
|
|
{
|
|
}
|
|
|
|
void SetTo(const char *name, type_code type)
|
|
{
|
|
fName = name;
|
|
fType = type;
|
|
}
|
|
|
|
bool Filter(const char *name, type_code type) const {
|
|
if (fName && strcmp(name, fName) != 0)
|
|
return false;
|
|
|
|
return (fType == B_ANY_TYPE || type == fType);
|
|
}
|
|
|
|
private:
|
|
const char *fName;
|
|
type_code fType;
|
|
};
|
|
|
|
|
|
// Parameters
|
|
struct Parameters {
|
|
Parameters()
|
|
:
|
|
copy_data(false),
|
|
recursive(false),
|
|
move_files(false),
|
|
verbose(false)
|
|
{
|
|
}
|
|
|
|
bool copy_data;
|
|
bool recursive;
|
|
bool move_files;
|
|
bool verbose;
|
|
AttributeFilter attribute_filter;
|
|
EntryFilter entry_filter;
|
|
};
|
|
|
|
|
|
// print_usage
|
|
static void
|
|
print_usage(bool error)
|
|
{
|
|
// get command name
|
|
const char *commandName = NULL;
|
|
if (kArgc > 0) {
|
|
if (const char *lastSlash = strchr(kArgv[0], '/'))
|
|
commandName = lastSlash + 1;
|
|
else
|
|
commandName = kArgv[0];
|
|
}
|
|
|
|
if (!commandName || strlen(commandName) == 0)
|
|
commandName = kCommandName;
|
|
|
|
// print usage
|
|
fprintf((error ? stderr : stdout), kUsage, commandName);
|
|
}
|
|
|
|
|
|
// print_usage_and_exit
|
|
static void
|
|
print_usage_and_exit(bool error)
|
|
{
|
|
print_usage(error);
|
|
exit(error ? 1 : 0);
|
|
}
|
|
|
|
|
|
// next_arg
|
|
static const char *
|
|
next_arg(int &argi, bool optional = false)
|
|
{
|
|
if (argi >= kArgc) {
|
|
if (!optional)
|
|
print_usage_and_exit(true);
|
|
return NULL;
|
|
}
|
|
|
|
return kArgv[argi++];
|
|
}
|
|
|
|
|
|
// copy_attributes
|
|
static void
|
|
copy_attributes(const char *sourcePath, BNode &source, const char *destPath,
|
|
BNode &destination, const Parameters ¶meters)
|
|
{
|
|
char attrName[B_ATTR_NAME_LENGTH];
|
|
while (source.GetNextAttrName(attrName) == B_OK) {
|
|
// get attr info
|
|
attr_info attrInfo;
|
|
status_t error = source.GetAttrInfo(attrName, &attrInfo);
|
|
if (error != B_OK) {
|
|
fprintf(stderr, "Error: Failed to get info of attribute \"%s\" "
|
|
"of file \"%s\": %s\n", attrName, sourcePath, strerror(error));
|
|
exit(1);
|
|
}
|
|
|
|
// filter
|
|
if (!parameters.attribute_filter.Filter(attrName, attrInfo.type))
|
|
continue;
|
|
|
|
// copy the attribute
|
|
char buffer[kCopyBufferSize];
|
|
off_t offset = 0;
|
|
off_t bytesLeft = attrInfo.size;
|
|
// go at least once through the loop, so that empty attribute will be
|
|
// created as well
|
|
do {
|
|
size_t toRead = kCopyBufferSize;
|
|
if ((off_t)toRead > bytesLeft)
|
|
toRead = bytesLeft;
|
|
|
|
// read
|
|
ssize_t bytesRead = source.ReadAttr(attrName, attrInfo.type,
|
|
offset, buffer, toRead);
|
|
if (bytesRead < 0) {
|
|
fprintf(stderr, "Error: Failed to read attribute \"%s\" "
|
|
"of file \"%s\": %s\n", attrName, sourcePath,
|
|
strerror(bytesRead));
|
|
exit(1);
|
|
}
|
|
|
|
// write
|
|
ssize_t bytesWritten = destination.WriteAttr(attrName,
|
|
attrInfo.type, offset, buffer, bytesRead);
|
|
if (bytesWritten < 0) {
|
|
fprintf(stderr, "Error: Failed to write attribute \"%s\" "
|
|
"of file \"%s\": %s\n", attrName, destPath,
|
|
strerror(bytesWritten));
|
|
exit(1);
|
|
}
|
|
|
|
bytesLeft -= bytesRead;
|
|
offset += bytesRead;
|
|
|
|
} while (bytesLeft > 0);
|
|
}
|
|
}
|
|
|
|
|
|
// copy_file_data
|
|
static void
|
|
copy_file_data(const char *sourcePath, BFile &source, const char *destPath,
|
|
BFile &destination, const Parameters ¶meters)
|
|
{
|
|
char buffer[kCopyBufferSize];
|
|
off_t offset = 0;
|
|
while (true) {
|
|
// read
|
|
ssize_t bytesRead = source.ReadAt(offset, buffer, sizeof(buffer));
|
|
if (bytesRead < 0) {
|
|
fprintf(stderr, "Error: Failed to read from file \"%s\": %s\n",
|
|
sourcePath, strerror(bytesRead));
|
|
exit(1);
|
|
}
|
|
|
|
if (bytesRead == 0)
|
|
return;
|
|
|
|
// write
|
|
ssize_t bytesWritten = destination.WriteAt(offset, buffer, bytesRead);
|
|
if (bytesWritten < 0) {
|
|
fprintf(stderr, "Error: Failed to write to file \"%s\": %s\n",
|
|
destPath, strerror(bytesWritten));
|
|
exit(1);
|
|
}
|
|
|
|
offset += bytesRead;
|
|
}
|
|
}
|
|
|
|
|
|
// copy_entry
|
|
static void
|
|
copy_entry(const char *sourcePath, const char *destPath,
|
|
const Parameters ¶meters)
|
|
{
|
|
// apply entry filter
|
|
if (!parameters.entry_filter.Filter(sourcePath))
|
|
return;
|
|
|
|
// stat source
|
|
struct stat sourceStat;
|
|
if (lstat(sourcePath, &sourceStat) < 0) {
|
|
fprintf(stderr, "Error: Couldn't access \"%s\": %s\n", sourcePath,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
// stat destination
|
|
struct stat destStat;
|
|
bool destExists = lstat(destPath, &destStat) == 0;
|
|
|
|
if (!destExists && !parameters.copy_data) {
|
|
fprintf(stderr, "Error: Destination file \"%s\" does not exist.\n",
|
|
destPath);
|
|
exit(1);
|
|
}
|
|
|
|
if (parameters.verbose)
|
|
printf("%s\n", destPath);
|
|
|
|
// check whether to delete/create the destination
|
|
bool unlinkDest = (destExists && parameters.copy_data);
|
|
bool createDest = parameters.copy_data;
|
|
if (destExists) {
|
|
if (S_ISDIR(destStat.st_mode)) {
|
|
if (S_ISDIR(sourceStat.st_mode)) {
|
|
// both are dirs; nothing to do
|
|
unlinkDest = false;
|
|
createDest = false;
|
|
} else if (parameters.copy_data || parameters.recursive) {
|
|
// destination is directory, but source isn't, and mode is
|
|
// not non-recursive attributes-only copy
|
|
fprintf(stderr, "Error: Can't copy \"%s\", since directory "
|
|
"\"%s\" is in the way.\n", sourcePath, destPath);
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// unlink the destination
|
|
if (unlinkDest) {
|
|
if (unlink(destPath) < 0) {
|
|
fprintf(stderr, "Error: Failed to unlink \"%s\": %s\n", destPath,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
// open source node
|
|
BNode _sourceNode;
|
|
BFile sourceFile;
|
|
BDirectory sourceDir;
|
|
BNode *sourceNode = NULL;
|
|
status_t error;
|
|
|
|
if (S_ISDIR(sourceStat.st_mode)) {
|
|
error = sourceDir.SetTo(sourcePath);
|
|
sourceNode = &sourceDir;
|
|
} else if (S_ISREG(sourceStat.st_mode)) {
|
|
error = sourceFile.SetTo(sourcePath, B_READ_ONLY);
|
|
sourceNode = &sourceFile;
|
|
} else {
|
|
error = _sourceNode.SetTo(sourcePath);
|
|
sourceNode = &_sourceNode;
|
|
}
|
|
|
|
if (error != B_OK) {
|
|
fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
|
|
sourcePath, strerror(error));
|
|
exit(1);
|
|
}
|
|
|
|
// create the destination
|
|
BNode _destNode;
|
|
BDirectory destDir;
|
|
BFile destFile;
|
|
BSymLink destSymLink;
|
|
BNode *destNode = NULL;
|
|
|
|
if (createDest) {
|
|
if (S_ISDIR(sourceStat.st_mode)) {
|
|
// create dir
|
|
error = BDirectory().CreateDirectory(destPath, &destDir);
|
|
if (error != B_OK) {
|
|
fprintf(stderr, "Error: Failed to make directory \"%s\": %s\n",
|
|
destPath, strerror(error));
|
|
exit(1);
|
|
}
|
|
|
|
destNode = &destDir;
|
|
|
|
} else if (S_ISREG(sourceStat.st_mode)) {
|
|
// create file
|
|
error = BDirectory().CreateFile(destPath, &destFile);
|
|
if (error != B_OK) {
|
|
fprintf(stderr, "Error: Failed to create file \"%s\": %s\n",
|
|
destPath, strerror(error));
|
|
exit(1);
|
|
}
|
|
|
|
destNode = &destFile;
|
|
|
|
// copy file contents
|
|
copy_file_data(sourcePath, sourceFile, destPath, destFile,
|
|
parameters);
|
|
|
|
} else if (S_ISLNK(sourceStat.st_mode)) {
|
|
// read symlink
|
|
char linkTo[B_PATH_NAME_LENGTH + 1];
|
|
ssize_t bytesRead = readlink(sourcePath, linkTo,
|
|
sizeof(linkTo) - 1);
|
|
if (bytesRead < 0) {
|
|
fprintf(stderr, "Error: Failed to read symlink \"%s\": %s\n",
|
|
sourcePath, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
// null terminate the link contents
|
|
linkTo[bytesRead] = '\0';
|
|
|
|
// create symlink
|
|
error = BDirectory().CreateSymLink(destPath, linkTo, &destSymLink);
|
|
if (error != B_OK) {
|
|
fprintf(stderr, "Error: Failed to create symlink \"%s\": %s\n",
|
|
destPath, strerror(error));
|
|
exit(1);
|
|
}
|
|
|
|
destNode = &destSymLink;
|
|
|
|
} else {
|
|
fprintf(stderr, "Error: Source file \"%s\" has unsupported type.\n",
|
|
sourcePath);
|
|
exit(1);
|
|
}
|
|
|
|
// copy attributes (before setting the permissions!)
|
|
copy_attributes(sourcePath, *sourceNode, destPath, *destNode,
|
|
parameters);
|
|
|
|
// set file owner, group, permissions, times
|
|
destNode->SetOwner(sourceStat.st_uid);
|
|
destNode->SetGroup(sourceStat.st_gid);
|
|
destNode->SetPermissions(sourceStat.st_mode);
|
|
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
|
|
destNode->SetCreationTime(sourceStat.st_crtime);
|
|
#endif
|
|
destNode->SetModificationTime(sourceStat.st_mtime);
|
|
|
|
} else {
|
|
// open destination node
|
|
error = _destNode.SetTo(destPath);
|
|
if (error != B_OK) {
|
|
fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
|
|
destPath, strerror(error));
|
|
exit(1);
|
|
}
|
|
|
|
destNode = &_destNode;
|
|
|
|
// copy attributes
|
|
copy_attributes(sourcePath, *sourceNode, destPath, *destNode,
|
|
parameters);
|
|
}
|
|
|
|
// the destination node is no longer needed
|
|
destNode->Unset();
|
|
|
|
// recurse
|
|
if (parameters.recursive && S_ISDIR(sourceStat.st_mode)) {
|
|
char buffer[offsetof(struct dirent, d_name) + B_FILE_NAME_LENGTH];
|
|
dirent *entry = (dirent*)buffer;
|
|
while (sourceDir.GetNextDirents(entry, sizeof(buffer), 1) == 1) {
|
|
if (strcmp(entry->d_name, ".") == 0
|
|
|| strcmp(entry->d_name, "..") == 0) {
|
|
continue;
|
|
}
|
|
|
|
// construct new entry paths
|
|
BPath sourceEntryPath;
|
|
error = sourceEntryPath.SetTo(sourcePath, entry->d_name);
|
|
if (error != B_OK) {
|
|
fprintf(stderr, "Error: Failed to construct entry path from "
|
|
"dir \"%s\" and name \"%s\": %s\n",
|
|
sourcePath, entry->d_name, strerror(error));
|
|
exit(1);
|
|
}
|
|
|
|
BPath destEntryPath;
|
|
error = destEntryPath.SetTo(destPath, entry->d_name);
|
|
if (error != B_OK) {
|
|
fprintf(stderr, "Error: Failed to construct entry path from "
|
|
"dir \"%s\" and name \"%s\": %s\n",
|
|
destPath, entry->d_name, strerror(error));
|
|
exit(1);
|
|
}
|
|
|
|
// copy the entry
|
|
copy_entry(sourceEntryPath.Path(), destEntryPath.Path(),
|
|
parameters);
|
|
}
|
|
}
|
|
|
|
// remove source in move mode
|
|
if (parameters.move_files) {
|
|
if (S_ISDIR(sourceStat.st_mode)) {
|
|
if (rmdir(sourcePath) < 0) {
|
|
fprintf(stderr, "Error: Failed to remove \"%s\": %s\n",
|
|
sourcePath, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
} else {
|
|
if (unlink(sourcePath) < 0) {
|
|
fprintf(stderr, "Error: Failed to unlink \"%s\": %s\n",
|
|
sourcePath, strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy_files
|
|
static void
|
|
copy_files(const char **sourcePaths, int sourceCount,
|
|
const char *destPath, const Parameters ¶meters)
|
|
{
|
|
// check, if destination exists
|
|
BEntry destEntry;
|
|
status_t error = destEntry.SetTo(destPath);
|
|
if (error != B_OK) {
|
|
fprintf(stderr, "Error: Couldn't access \"%s\": %s\n", destPath,
|
|
strerror(error));
|
|
exit(1);
|
|
}
|
|
bool destExists = destEntry.Exists();
|
|
|
|
// If it exists, check whether it is a directory. In case we don't copy
|
|
// the data, we pretend the destination is no directory, even if it is
|
|
// one.
|
|
bool destIsDir = false;
|
|
if (destExists && parameters.copy_data) {
|
|
struct stat st;
|
|
error = destEntry.GetStat(&st);
|
|
if (error != B_OK) {
|
|
fprintf(stderr, "Error: Failed to stat \"%s\": %s\n", destPath,
|
|
strerror(error));
|
|
exit(1);
|
|
}
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
destIsDir = true;
|
|
} else if (S_ISLNK(st.st_mode)) {
|
|
// a symlink -- check if it refers to a dir
|
|
BEntry resolvedDestEntry;
|
|
if (resolvedDestEntry.SetTo(destPath, true) == B_OK
|
|
&& resolvedDestEntry.IsDirectory()) {
|
|
destIsDir = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we have multiple source files, the destination should be a directory,
|
|
// if we want to copy the file data.
|
|
if (sourceCount > 1 && parameters.copy_data && !destIsDir) {
|
|
fprintf(stderr, "Error: Destination needs to be a directory when "
|
|
"multiple source files are specified and option \"-d\" is "
|
|
"given.\n");
|
|
exit(1);
|
|
}
|
|
|
|
// iterate through the source files
|
|
for (int i = 0; i < sourceCount; i++) {
|
|
const char *sourcePath = sourcePaths[i];
|
|
// If the destination is a directory, we usually want to copy the
|
|
// sources into it. The user might have specified a source path ending
|
|
// in "/." or "/.." however, in which case we copy the contents of the
|
|
// given directory.
|
|
bool copySourceContentsOnly = false;
|
|
if (destIsDir) {
|
|
// skip trailing '/'s
|
|
int sourceLen = strlen(sourcePath);
|
|
while (sourceLen > 1 && sourcePath[sourceLen - 1] == '/')
|
|
sourceLen--;
|
|
|
|
// find the start of the leaf name
|
|
int leafStart = sourceLen;
|
|
while (leafStart > 0 && sourcePath[leafStart - 1] != '/')
|
|
leafStart--;
|
|
|
|
// If the path is the root directory or the leaf is "." or "..",
|
|
// we copy the contents only.
|
|
int leafLen = sourceLen - leafStart;
|
|
if (leafLen == 0 || (leafLen <= 2
|
|
&& strncmp(sourcePath + leafStart, "..", leafLen) == 0)) {
|
|
copySourceContentsOnly = true;
|
|
}
|
|
}
|
|
|
|
if (destIsDir && !copySourceContentsOnly) {
|
|
// construct a usable destination entry path
|
|
// normalize source path
|
|
BPath normalizedSourcePath;
|
|
error = normalizedSourcePath.SetTo(sourcePath);
|
|
if (error != B_OK) {
|
|
fprintf(stderr, "Error: Invalid path \"%s\".\n", sourcePath);
|
|
exit(1);
|
|
}
|
|
|
|
BPath destEntryPath;
|
|
error = destEntryPath.SetTo(destPath, normalizedSourcePath.Leaf());
|
|
if (error != B_OK) {
|
|
fprintf(stderr, "Error: Failed to get destination path for "
|
|
"source \"%s\" and destination directory \"%s\".\n",
|
|
sourcePath, destPath);
|
|
exit(1);
|
|
}
|
|
|
|
copy_entry(normalizedSourcePath.Path(), destEntryPath.Path(),
|
|
parameters);
|
|
} else {
|
|
copy_entry(sourcePath, destPath, parameters);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// main
|
|
int
|
|
main(int argc, const char *const *argv)
|
|
{
|
|
kArgc = argc;
|
|
kArgv = argv;
|
|
|
|
// parameters
|
|
Parameters parameters;
|
|
const char *attributeName = NULL;
|
|
const char *attributeTypeString = NULL;
|
|
const char **files = new const char*[argc];
|
|
int fileCount = 0;
|
|
|
|
// parse the arguments
|
|
bool moreOptions = true;
|
|
for (int argi = 1; argi < argc; ) {
|
|
const char *arg = argv[argi++];
|
|
if (moreOptions && arg[0] == '-') {
|
|
if (strcmp(arg, "-d") == 0 || strcmp(arg, "--data") == 0) {
|
|
parameters.copy_data = true;
|
|
|
|
} else if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
|
|
print_usage_and_exit(false);
|
|
|
|
} else if (strcmp(arg, "-m") == 0 || strcmp(arg, "--move") == 0) {
|
|
parameters.move_files = true;
|
|
|
|
} else if (strcmp(arg, "-n") == 0 || strcmp(arg, "--name") == 0) {
|
|
if (attributeName) {
|
|
fprintf(stderr, "Error: Only one attribute name can be "
|
|
"specified.\n");
|
|
exit(1);
|
|
}
|
|
|
|
attributeName = next_arg(argi);
|
|
|
|
} else if (strcmp(arg, "-r") == 0
|
|
|| strcmp(arg, "--recursive") == 0) {
|
|
parameters.recursive = true;
|
|
|
|
} else if (strcmp(arg, "-t") == 0 || strcmp(arg, "--type") == 0) {
|
|
if (attributeTypeString) {
|
|
fprintf(stderr, "Error: Only one attribute type can be "
|
|
"specified.\n");
|
|
exit(1);
|
|
}
|
|
|
|
attributeTypeString = next_arg(argi);
|
|
|
|
} else if (strcmp(arg, "-v") == 0
|
|
|| strcmp(arg, "--verbose") == 0) {
|
|
parameters.verbose = true;
|
|
|
|
} else if (strcmp(arg, "-x") == 0) {
|
|
parameters.entry_filter.AddExcludeFilter(next_arg(argi), true);
|
|
|
|
} else if (strcmp(arg, "-X") == 0) {
|
|
parameters.entry_filter.AddExcludeFilter(next_arg(argi), false);
|
|
|
|
} else if (strcmp(arg, "-") == 0 || strcmp(arg, "--") == 0) {
|
|
moreOptions = false;
|
|
|
|
} else {
|
|
fprintf(stderr, "Error: Invalid option: \"%s\"\n", arg);
|
|
print_usage_and_exit(true);
|
|
}
|
|
|
|
} else {
|
|
// file
|
|
files[fileCount++] = arg;
|
|
}
|
|
}
|
|
|
|
// check parameters
|
|
|
|
// enough files
|
|
if (fileCount < 2) {
|
|
fprintf(stderr, "Error: Not enough file names specified.\n");
|
|
print_usage_and_exit(true);
|
|
}
|
|
|
|
// attribute type
|
|
type_code attributeType = B_ANY_TYPE;
|
|
if (attributeTypeString) {
|
|
bool found = false;
|
|
for (int i = 0; kSupportedAttributeTypes[i].type_name; i++) {
|
|
if (strcmp(attributeTypeString,
|
|
kSupportedAttributeTypes[i].type_name) == 0) {
|
|
found = true;
|
|
attributeType = kSupportedAttributeTypes[i].type;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
fprintf(stderr, "Error: Unsupported attribute type: \"%s\"\n",
|
|
attributeTypeString);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
// init the attribute filter
|
|
parameters.attribute_filter.SetTo(attributeName, attributeType);
|
|
|
|
// turn of move_files, if we are not copying the file data
|
|
parameters.move_files &= parameters.copy_data;
|
|
|
|
// do the copying
|
|
fileCount--;
|
|
const char *destination = files[fileCount];
|
|
files[fileCount] = NULL;
|
|
copy_files(files, fileCount, destination, parameters);
|
|
delete[] files;
|
|
|
|
return 0;
|
|
}
|