/* * Copyright 2005-2009, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 [ ... ] \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 - Only copy the attribute with name .\n" " -r, --recursive - Copy directories recursively.\n" " -t, --type - Copy only the attributes of type . If -n is\n" " specified too, only the attribute matching the name\n" " and the type is copied.\n" " -x - Exclude source entries matching .\n" " -X - Exclude source paths matching .\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" " - 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; }