vfs: check userland buffers before reading them.
[haiku.git] / src / bin / copyattr.cpp
blobfa36fbd05927e77fca9c96d0f2545d78d80cbde1
1 /*
2 * Copyright 2005-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
6 #include <errno.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <unistd.h>
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>
22 #include <EntryFilter.h>
25 using BPrivate::EntryFilter;
28 static const char *kCommandName = "copyattr";
29 static const int kCopyBufferSize = 64 * 1024; // 64 KB
31 static int kArgc;
32 static const char *const *kArgv;
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"
88 // supported attribute types
89 struct supported_attribute_type {
90 const char *type_name;
91 type_code type;
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 },
105 // AttributeFilter
106 struct AttributeFilter {
107 AttributeFilter()
109 fName(NULL),
110 fType(B_ANY_TYPE)
114 void SetTo(const char *name, type_code type)
116 fName = name;
117 fType = type;
120 bool Filter(const char *name, type_code type) const {
121 if (fName && strcmp(name, fName) != 0)
122 return false;
124 return (fType == B_ANY_TYPE || type == fType);
127 private:
128 const char *fName;
129 type_code fType;
133 // Parameters
134 struct Parameters {
135 Parameters()
137 copy_data(false),
138 recursive(false),
139 move_files(false),
140 verbose(false)
144 bool copy_data;
145 bool recursive;
146 bool move_files;
147 bool verbose;
148 AttributeFilter attribute_filter;
149 EntryFilter entry_filter;
153 // print_usage
154 static void
155 print_usage(bool error)
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];
166 if (!commandName || strlen(commandName) == 0)
167 commandName = kCommandName;
169 // print usage
170 fprintf((error ? stderr : stdout), kUsage, commandName);
174 // print_usage_and_exit
175 static void
176 print_usage_and_exit(bool error)
178 print_usage(error);
179 exit(error ? 1 : 0);
183 // next_arg
184 static const char *
185 next_arg(int &argi, bool optional = false)
187 if (argi >= kArgc) {
188 if (!optional)
189 print_usage_and_exit(true);
190 return NULL;
193 return kArgv[argi++];
197 // copy_attributes
198 static void
199 copy_attributes(const char *sourcePath, BNode &source, const char *destPath,
200 BNode &destination, const Parameters &parameters)
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);
213 // filter
214 if (!parameters.attribute_filter.Filter(attrName, attrInfo.type))
215 continue;
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;
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);
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);
248 bytesLeft -= bytesRead;
249 offset += bytesRead;
251 } while (bytesLeft > 0);
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)
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);
272 if (bytesRead == 0)
273 return;
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);
283 offset += bytesRead;
288 // copy_entry
289 static void
290 copy_entry(const char *sourcePath, const char *destPath,
291 const Parameters &parameters)
293 // apply entry filter
294 if (!parameters.entry_filter.Filter(sourcePath))
295 return;
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);
305 // stat destination
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",
311 destPath);
312 exit(1);
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;
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);
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);
346 // open source node
347 BNode _sourceNode;
348 BFile sourceFile;
349 BDirectory sourceDir;
350 BNode *sourceNode = NULL;
351 status_t error;
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;
364 if (error != B_OK) {
365 fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
366 sourcePath, strerror(error));
367 exit(1);
370 // create the destination
371 BNode _destNode;
372 BDirectory destDir;
373 BFile destFile;
374 BSymLink destSymLink;
375 BNode *destNode = NULL;
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);
387 destNode = &destDir;
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);
398 destNode = &destFile;
400 // copy file contents
401 copy_file_data(sourcePath, sourceFile, destPath, destFile,
402 parameters);
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);
415 // null terminate the link contents
416 linkTo[bytesRead] = '\0';
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);
426 destNode = &destSymLink;
428 } else {
429 fprintf(stderr, "Error: Source file \"%s\" has unsupported type.\n",
430 sourcePath);
431 exit(1);
434 // copy attributes (before setting the permissions!)
435 copy_attributes(sourcePath, *sourceNode, destPath, *destNode,
436 parameters);
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);
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);
456 destNode = &_destNode;
458 // copy attributes
459 copy_attributes(sourcePath, *sourceNode, destPath, *destNode,
460 parameters);
463 // the destination node is no longer needed
464 destNode->Unset();
466 // recurse
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) {
473 continue;
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);
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);
495 // copy the entry
496 copy_entry(sourceEntryPath.Path(), destEntryPath.Path(),
497 parameters);
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);
510 } else {
511 if (unlink(sourcePath) < 0) {
512 fprintf(stderr, "Error: Failed to unlink \"%s\": %s\n",
513 sourcePath, strerror(errno));
514 exit(1);
520 // copy_files
521 static void
522 copy_files(const char **sourcePaths, int sourceCount,
523 const char *destPath, const Parameters &parameters)
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);
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
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);
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;
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);
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--;
583 // find the start of the leaf name
584 int leafStart = sourceLen;
585 while (leafStart > 0 && sourcePath[leafStart - 1] != '/')
586 leafStart--;
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);
602 if (error != B_OK) {
603 fprintf(stderr, "Error: Invalid path \"%s\".\n", sourcePath);
604 exit(1);
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);
616 copy_entry(normalizedSourcePath.Path(), destEntryPath.Path(),
617 parameters);
618 } else {
619 copy_entry(sourcePath, destPath, parameters);
625 // main
627 main(int argc, const char *const *argv)
629 kArgc = argc;
630 kArgv = argv;
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;
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) {
654 if (attributeName) {
655 fprintf(stderr, "Error: Only one attribute name can be "
656 "specified.\n");
657 exit(1);
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 "
669 "specified.\n");
670 exit(1);
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) {
686 moreOptions = false;
688 } else {
689 fprintf(stderr, "Error: Invalid option: \"%s\"\n", arg);
690 print_usage_and_exit(true);
693 } else {
694 // file
695 files[fileCount++] = arg;
699 // check parameters
701 // enough files
702 if (fileCount < 2) {
703 fprintf(stderr, "Error: Not enough file names specified.\n");
704 print_usage_and_exit(true);
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;
720 if (!found) {
721 fprintf(stderr, "Error: Unsupported attribute type: \"%s\"\n",
722 attributeTypeString);
723 exit(1);
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;
733 // do the copying
734 fileCount--;
735 const char *destination = files[fileCount];
736 files[fileCount] = NULL;
737 copy_files(files, fileCount, destination, parameters);
739 return 0;