haiku/src/bin/copyattr.cpp
Augustin Cavalier 711e2dc05b Adjust all struct dirent creations (again), this time to use offsetof().
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.
2021-12-01 17:05:40 -05:00

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 &parameters)
{
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 &parameters)
{
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 &parameters)
{
// 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 &parameters)
{
// 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;
}