xref: /haiku/src/bin/copyattr.cpp (revision 24159a0c7d6d6dcba9f2a0c1a7c08d2c8167f21b)
1 /*
2  * Copyright 2005, Ingo Weinhold, bonefish@users.sf.net.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include <errno.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <unistd.h>
11 
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>
21 
22 static const char *kCommandName = "copyattr";
23 static const int kCopyBufferSize = 64 * 1024;	// 64 KB
24 
25 static int kArgc;
26 static const char *const *kArgv;
27 
28 // usage
29 const char *kUsage =
30 "Usage: %s <options> <source> [ ... ] <destination>\n"
31 "\n"
32 "Copies attributes from one or more files to another, or copies one or more\n"
33 "files or directories, with all or a specified subset of their attributes, to\n"
34 "another location.\n"
35 "\n"
36 "If option \"-d\"/\"--data\" is given, the behavior is similar to \"cp -df\",\n"
37 "save that attributes are copied. That is, if more than one source file is\n"
38 "given, the destination file must be a directory. If the destination is a\n"
39 "directory (or a symlink to a directory), the source files are copied into\n"
40 "the destination directory. Entries that are in the way are removed, unless\n"
41 "they are directories. If the source is a directory too, the attributes will\n"
42 "be copied and, if recursive operation is specified, the program continues\n"
43 "copying the contents of the source directory. If the source is not a\n"
44 "directory the program aborts with an error message.\n"
45 "\n"
46 "If option \"-d\"/\"--data\" is not given, only attributes are copied.\n"
47 "Regardless of the file type of the destination, the attributes of the source\n"
48 "files are copied to it. If an attribute with the same name as one to be\n"
49 "copied already exists, it is replaced. If more than one source file is\n"
50 "specified the semantics are similar to invoking the program multiple times\n"
51 "with the same options and destination and only one source file at a time,\n"
52 "in the order the source files are given. If recursive operation is\n"
53 "specified, the program recursively copies the attributes of the directory\n"
54 "contents; if the destination file is not a directory, or for a source entry\n"
55 "there exists no destination entry, the program aborts with an error\n"
56 "message.\n"
57 "\n"
58 "Note, that the behavior of the program differs from the one shipped with\n"
59 "BeOS R5.\n"
60 "\n"
61 "Options:\n"
62 "  -d, --data         - Copy the data of the file(s), too.\n"
63 "  -h, --help         - Print this help text and exit.\n"
64 "  -m, --move         - If -d is given, the source files are removed after\n"
65 "                       being copied. Has no effect otherwise.\n"
66 "  -n, --name <name>  - Only copy the attribute with name <name>.\n"
67 "  -r, --recursive    - Copy directories recursively.\n"
68 "  -t, --type <type>  - Copy only the attributes of type <type>. If -n is\n"
69 "                       specified too, only the attribute matching the name\n"
70 "                       and the type is copied.\n"
71 "  -v, --verbose      - Print more messages.\n"
72 "   -, --             - Marks the end of options. The arguments after, even\n"
73 "                       if starting with \"-\" are considered file names.\n"
74 "\n"
75 "Parameters:\n"
76 "  <type>             - One of: int, llong, string, mimestr, float, double,\n"
77 "                       boolean.\n"
78 ;
79 
80 // supported attribute types
81 struct supported_attribute_type {
82 	const char	*type_name;
83 	type_code	type;
84 };
85 
86 const supported_attribute_type kSupportedAttributeTypes[] = {
87 	{ "int",		B_INT32_TYPE },
88 	{ "llong",		B_INT64_TYPE },
89 	{ "string",		B_STRING_TYPE },
90 	{ "mimestr",	B_MIME_STRING_TYPE },
91 	{ "float",		B_FLOAT_TYPE },
92 	{ "double",		B_DOUBLE_TYPE },
93 	{ "boolean",	B_BOOL_TYPE },
94 	{ NULL, 0 },
95 };
96 
97 // AttributeFilter
98 struct AttributeFilter {
99 
100 	AttributeFilter()
101 		: fName(NULL),
102 		  fType(B_ANY_TYPE)
103 	{
104 	}
105 
106 	void SetTo(const char *name, type_code type)
107 	{
108 		fName = name;
109 		fType = type;
110 	}
111 
112 	bool Filter(const char *name, type_code type) const {
113 		if (fName && strcmp(name, fName) != 0)
114 			return false;
115 
116 		return (fType == B_ANY_TYPE || type == fType);
117 	}
118 
119 private:
120 	const char	*fName;
121 	type_code	fType;
122 };
123 
124 // Parameters
125 struct Parameters {
126 	Parameters()
127 		: copy_data(false),
128 		  recursive(false),
129 		  move_files(false),
130 		  verbose(false)
131 	{
132 	}
133 
134 	bool			copy_data;
135 	bool			recursive;
136 	bool			move_files;
137 	bool			verbose;
138 	AttributeFilter	attribute_filter;
139 };
140 
141 
142 // print_usage
143 static void
144 print_usage(bool error)
145 {
146 	// get command name
147 	const char *commandName = NULL;
148 	if (kArgc > 0) {
149 		if (const char *lastSlash = strchr(kArgv[0], '/'))
150 			commandName = lastSlash + 1;
151 		else
152 			commandName = kArgv[0];
153 	}
154 
155 	if (!commandName || strlen(commandName) == 0)
156 		commandName = kCommandName;
157 
158 	// print usage
159 	fprintf((error ? stderr : stdout), kUsage, commandName);
160 }
161 
162 // print_usage_and_exit
163 static void
164 print_usage_and_exit(bool error)
165 {
166 	print_usage(error);
167 	exit(error ? 1 : 0);
168 }
169 
170 // next_arg
171 static const char *
172 next_arg(int &argi, bool optional = false)
173 {
174 	if (argi >= kArgc) {
175 		if (!optional)
176 			print_usage_and_exit(true);
177 		return NULL;
178 	}
179 
180 	return kArgv[argi++];
181 }
182 
183 // copy_attributes
184 static void
185 copy_attributes(const char *sourcePath, BNode &source,
186 	const char *destPath, BNode &destination,
187 	const Parameters &parameters)
188 {
189 	char attrName[B_ATTR_NAME_LENGTH];
190 	while (source.GetNextAttrName(attrName) == B_OK) {
191 		// get attr info
192 		attr_info attrInfo;
193 		status_t error = source.GetAttrInfo(attrName, &attrInfo);
194 		if (error != B_OK) {
195 			fprintf(stderr, "Error: Failed to get info of attribute \"%s\" "
196 				"of file \"%s\": %s\n", attrName, sourcePath, strerror(error));
197 			exit(1);
198 		}
199 
200 		// filter
201 		if (!parameters.attribute_filter.Filter(attrName, attrInfo.type))
202 			continue;
203 
204 		// copy the attribute
205 		char buffer[kCopyBufferSize];
206 		off_t offset = 0;
207 		off_t bytesLeft = attrInfo.size;
208 		// go at least once through the loop, so that empty attribute will be
209 		// created as well
210 		do {
211 			size_t toRead = kCopyBufferSize;
212 			if (toRead > bytesLeft)
213 				toRead = bytesLeft;
214 
215 			// read
216 			ssize_t bytesRead = source.ReadAttr(attrName, attrInfo.type,
217 				offset, buffer, toRead);
218 			if (bytesRead < 0) {
219 				fprintf(stderr, "Error: Failed to read attribute \"%s\" "
220 					"of file \"%s\": %s\n", attrName, sourcePath,
221 					strerror(bytesRead));
222 				exit(1);
223 			}
224 
225 			// write
226 			ssize_t bytesWritten = destination.WriteAttr(attrName,
227 				attrInfo.type, offset, buffer, bytesRead);
228 			if (bytesWritten < 0) {
229 				fprintf(stderr, "Error: Failed to write attribute \"%s\" "
230 					"of file \"%s\": %s\n", attrName, destPath,
231 					strerror(bytesWritten));
232 				exit(1);
233 			}
234 
235 			bytesLeft -= bytesRead;
236 			offset += bytesRead;
237 
238 		} while (bytesLeft > 0);
239 	}
240 }
241 
242 // copy_file_data
243 static void
244 copy_file_data(const char *sourcePath, BFile &source,
245 	const char *destPath, BFile &destination, const Parameters &parameters)
246 {
247 	char buffer[kCopyBufferSize];
248 	off_t offset = 0;
249 	while (true) {
250 		// read
251 		ssize_t bytesRead = source.ReadAt(offset, buffer, sizeof(buffer));
252 		if (bytesRead < 0) {
253 			fprintf(stderr, "Error: Failed to read from file \"%s\": %s\n",
254 				sourcePath, strerror(bytesRead));
255 			exit(1);
256 		}
257 
258 		if (bytesRead == 0)
259 			return;
260 
261 		// write
262 		ssize_t bytesWritten = destination.WriteAt(offset, buffer, bytesRead);
263 		if (bytesWritten < 0) {
264 			fprintf(stderr, "Error: Failed to write to file \"%s\": %s\n",
265 				destPath, strerror(bytesWritten));
266 			exit(1);
267 		}
268 
269 		offset += bytesRead;
270 	}
271 }
272 
273 // copy_entry
274 static void
275 copy_entry(const char *sourcePath, const char *destPath,
276 	const Parameters &parameters)
277 {
278 	// stat source
279 	struct stat sourceStat;
280 	if (lstat(sourcePath, &sourceStat) < 0) {
281 		fprintf(stderr, "Error: Couldn't access \"%s\": %s\n", sourcePath,
282 			strerror(errno));
283 		exit(1);
284 	}
285 
286 	// stat destination
287 	struct stat destStat;
288 	bool destExists = (lstat(destPath, &destStat) == 0);
289 
290 	if (!destExists && !parameters.copy_data) {
291 		fprintf(stderr, "Error: Destination file \"%s\" does not exist.\n",
292 			destPath);
293 		exit(1);
294 	}
295 
296 	if (parameters.verbose)
297 		printf("%s\n", destPath);
298 
299 	// check whether to delete/create the destination
300 	bool unlinkDest = (destExists && parameters.copy_data);
301 	bool createDest = parameters.copy_data;
302 	if (destExists) {
303 		if (S_ISDIR(destStat.st_mode)) {
304 			if (S_ISDIR(sourceStat.st_mode)) {
305 				// both are dirs; nothing to do
306 				unlinkDest = false;
307 				createDest = false;
308 			} else if (parameters.copy_data || parameters.recursive) {
309 				// destination is directory, but source isn't, and mode is
310 				// not non-recursive attributes-only copy
311 				fprintf(stderr, "Error: Can't copy \"%s\", since directory "
312 					"\"%s\" is in the way.\n", sourcePath, destPath);
313 				exit(1);
314 			}
315 		}
316 	}
317 
318 	// unlink the destination
319 	if (unlinkDest) {
320 		if (unlink(destPath) < 0) {
321 			fprintf(stderr, "Error: Failed to unlink \"%s\": %s\n", destPath,
322 				strerror(errno));
323 			exit(1);
324 		}
325 	}
326 
327 	// open source node
328 	BNode _sourceNode;
329 	BFile sourceFile;
330 	BDirectory sourceDir;
331 	BNode *sourceNode = NULL;
332 	status_t error;
333 
334 	if (S_ISDIR(sourceStat.st_mode)) {
335 		error = sourceDir.SetTo(sourcePath);
336 		sourceNode = &sourceDir;
337 	} else if (S_ISREG(sourceStat.st_mode)) {
338 		error = sourceFile.SetTo(sourcePath, B_READ_ONLY);
339 		sourceNode = &sourceFile;
340 	} else {
341 		error = _sourceNode.SetTo(sourcePath);
342 		sourceNode = &_sourceNode;
343 	}
344 
345 	if (error != B_OK) {
346 		fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
347 			sourcePath, strerror(error));
348 		exit(1);
349 	}
350 
351 	// create the destination
352 	BNode _destNode;
353 	BDirectory destDir;
354 	BFile destFile;
355 	BSymLink destSymLink;
356 	BNode *destNode = NULL;
357 
358 	if (createDest) {
359 		if (S_ISDIR(sourceStat.st_mode)) {
360 			// create dir
361 			error = BDirectory().CreateDirectory(destPath, &destDir);
362 			if (error != B_OK) {
363 				fprintf(stderr, "Error: Failed to make directory \"%s\": %s\n",
364 					destPath, strerror(error));
365 				exit(1);
366 			}
367 
368 			destNode = &destDir;
369 
370 		} else if (S_ISREG(sourceStat.st_mode)) {
371 			// create file
372 			error = BDirectory().CreateFile(destPath, &destFile);
373 			if (error != B_OK) {
374 				fprintf(stderr, "Error: Failed to create file \"%s\": %s\n",
375 					destPath, strerror(error));
376 				exit(1);
377 			}
378 
379 			destNode = &destFile;
380 
381 			// copy file contents
382 			copy_file_data(sourcePath, sourceFile, destPath, destFile,
383 				parameters);
384 
385 		} else if (S_ISLNK(sourceStat.st_mode)) {
386 			// read symlink
387 			char linkTo[B_PATH_NAME_LENGTH + 1];
388 			ssize_t bytesRead = readlink(sourcePath, linkTo,
389 				sizeof(linkTo) - 1);
390 			if (bytesRead < 0) {
391 				fprintf(stderr, "Error: Failed to read symlink \"%s\": %s\n",
392 					sourcePath, strerror(error));
393 				exit(1);
394 			}
395 
396 			// null terminate the link contents
397 			linkTo[bytesRead] = '\0';
398 
399 			// create symlink
400 			error = BDirectory().CreateSymLink(destPath, linkTo, &destSymLink);
401 			if (error != B_OK) {
402 				fprintf(stderr, "Error: Failed to create symlink \"%s\": %s\n",
403 					destPath, strerror(error));
404 				exit(1);
405 			}
406 
407 			destNode = &destSymLink;
408 
409 		} else {
410 			fprintf(stderr, "Error: Source file \"%s\" has unsupported type.\n",
411 				sourcePath);
412 			exit(1);
413 		}
414 
415 		// set file owner, group, permissions, times
416 		destNode->SetOwner(sourceStat.st_uid);
417 		destNode->SetGroup(sourceStat.st_gid);
418 		destNode->SetPermissions(sourceStat.st_mode);
419 		#ifdef __BEOS__
420 			destNode->SetCreationTime(sourceStat.st_crtime);
421 		#endif
422 		destNode->SetModificationTime(sourceStat.st_mtime);
423 
424 	} else {
425 		// open destination node
426 		error = _destNode.SetTo(destPath);
427 		if (error != B_OK) {
428 			fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
429 				destPath, strerror(error));
430 			exit(1);
431 		}
432 
433 		destNode = &_destNode;
434 	}
435 
436 	// copy attributes
437 	copy_attributes(sourcePath, *sourceNode, destPath, *destNode, parameters);
438 
439 	// the destination node is no longer needed
440 	destNode->Unset();
441 
442 	// recurse
443 	if (parameters.recursive && S_ISDIR(sourceStat.st_mode)) {
444 		char buffer[sizeof(dirent) + B_FILE_NAME_LENGTH];
445 		dirent *entry = (dirent*)buffer;
446 		while (sourceDir.GetNextDirents(entry, sizeof(buffer), 1) == 1) {
447 			if (strcmp(entry->d_name, ".") == 0
448 				|| strcmp(entry->d_name, "..") == 0) {
449 				continue;
450 			}
451 
452 			// construct new entry paths
453 			BPath sourceEntryPath;
454 			error = sourceEntryPath.SetTo(sourcePath, entry->d_name);
455 			if (error != B_OK) {
456 				fprintf(stderr, "Error: Failed to construct entry path from "
457 					"dir \"%s\" and name \"%s\": %s\n",
458 					sourcePath, entry->d_name, strerror(error));
459 				exit(1);
460 			}
461 
462 			BPath destEntryPath;
463 			error = destEntryPath.SetTo(destPath, entry->d_name);
464 			if (error != B_OK) {
465 				fprintf(stderr, "Error: Failed to construct entry path from "
466 					"dir \"%s\" and name \"%s\": %s\n",
467 					destPath, entry->d_name, strerror(error));
468 				exit(1);
469 			}
470 
471 			// copy the entry
472 			copy_entry(sourceEntryPath.Path(), destEntryPath.Path(),
473 				parameters);
474 		}
475 	}
476 
477 	// remove source in move mode
478 	if (parameters.move_files) {
479 		if (S_ISDIR(sourceStat.st_mode)) {
480 			if (rmdir(sourcePath) < 0) {
481 				fprintf(stderr, "Error: Failed to remove \"%s\": %s\n",
482 					sourcePath, strerror(errno));
483 				exit(1);
484 			}
485 
486 		} else {
487 			if (unlink(sourcePath) < 0) {
488 				fprintf(stderr, "Error: Failed to unlink \"%s\": %s\n",
489 					sourcePath, strerror(errno));
490 				exit(1);
491 			}
492 		}
493 	}
494 }
495 
496 // copy_files
497 static void
498 copy_files(const char **sourcePaths, int sourceCount,
499 	const char *destPath, const Parameters &parameters)
500 {
501 	// check, if destination exists
502 	BEntry destEntry;
503 	status_t error = destEntry.SetTo(destPath);
504 	if (error != B_OK) {
505 		fprintf(stderr, "Error: Couldn't access \"%s\": %s\n", destPath,
506 			strerror(error));
507 		exit(1);
508 	}
509 	bool destExists = destEntry.Exists();
510 
511 	// If it exists, check whether it is a directory. In case we don't copy
512 	// the data, we pretend the destination is no directory, even if it is
513 	// one.
514 	bool destIsDir = false;
515 	if (destExists && parameters.copy_data) {
516 		struct stat st;
517 		error = destEntry.GetStat(&st);
518 		if (error != B_OK) {
519 			fprintf(stderr, "Error: Failed to stat \"%s\": %s\n", destPath,
520 				strerror(error));
521 			exit(1);
522 		}
523 
524 		if (S_ISDIR(st.st_mode)) {
525 			destIsDir = true;
526 		} else if (S_ISLNK(st.st_mode)) {
527 			// a symlink -- check if it refers to a dir
528 			BEntry resolvedDestEntry;
529 			if (resolvedDestEntry.SetTo(destPath, true) == B_OK
530 				&& resolvedDestEntry.IsDirectory()) {
531 				destIsDir = true;
532 			}
533 		}
534 	}
535 
536 	// If we have multiple source files, the destination should be a directory,
537 	// if we want to copy the file data.
538 	if (sourceCount > 1 && parameters.copy_data && !destIsDir) {
539 		fprintf(stderr, "Error: Destination needs to be a directory when "
540 			"multiple source files are specified and option \"-d\" is "
541 			"given.\n");
542 		exit(1);
543 	}
544 
545 	// iterate through the source files
546 	for (int i = 0; i < sourceCount; i++) {
547 		const char *sourcePath = sourcePaths[i];
548 		if (destIsDir) {
549 			// construct a usable destination entry path
550 			// normalize source path
551 			BPath normalizedSourcePath;
552 			error = normalizedSourcePath.SetTo(sourcePath);
553 			if (error != B_OK) {
554 				fprintf(stderr, "Error: Invalid path \"%s\".\n", sourcePath);
555 				exit(1);
556 			}
557 
558 			BPath destEntryPath;
559 			error = destEntryPath.SetTo(destPath, normalizedSourcePath.Leaf());
560 			if (error != B_OK) {
561 				fprintf(stderr, "Error: Failed to get destination path for "
562 					"source \"%s\" and destination directory \"%s\".\n",
563 					sourcePath, destPath);
564 				exit(1);
565 			}
566 
567 			copy_entry(normalizedSourcePath.Path(), destEntryPath.Path(),
568 				parameters);
569 		} else {
570 			copy_entry(sourcePath, destPath, parameters);
571 		}
572 	}
573 }
574 
575 // main
576 int
577 main(int argc, const char *const *argv)
578 {
579 	kArgc = argc;
580 	kArgv = argv;
581 
582 	// parameters
583 	Parameters parameters;
584 	const char *attributeName = NULL;
585 	const char *attributeTypeString = NULL;
586 	const char **files = new const char*[argc];
587 	int fileCount = 0;
588 
589 	// parse the arguments
590 	bool moreOptions = true;
591 	for (int argi = 1; argi < argc; ) {
592 		const char *arg = argv[argi++];
593 		if (moreOptions && arg[0] == '-') {
594 			if (strcmp(arg, "-d") == 0 || strcmp(arg, "--data") == 0) {
595 				parameters.copy_data = true;
596 
597 			} else if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
598 				print_usage_and_exit(false);
599 
600 			} else if (strcmp(arg, "-m") == 0 || strcmp(arg, "--move") == 0) {
601 				parameters.move_files = true;
602 
603 			} else if (strcmp(arg, "-n") == 0 || strcmp(arg, "--name") == 0) {
604 				if (attributeName) {
605 					fprintf(stderr, "Error: Only one attribute name can be "
606 						"specified.\n");
607 					exit(1);
608 				}
609 
610 				attributeName = next_arg(argi);
611 
612 			} else if (strcmp(arg, "-r") == 0
613 					|| strcmp(arg, "--recursive") == 0) {
614 				parameters.recursive = true;
615 
616 			} else if (strcmp(arg, "-t") == 0 || strcmp(arg, "--type") == 0) {
617 				if (attributeTypeString) {
618 					fprintf(stderr, "Error: Only one attribute type can be "
619 						"specified.\n");
620 					exit(1);
621 				}
622 
623 				attributeTypeString = next_arg(argi);
624 
625 			} else if (strcmp(arg, "-v") == 0
626 					|| strcmp(arg, "--verbose") == 0) {
627 				parameters.verbose = true;
628 
629 			} else if (strcmp(arg, "-") == 0 || strcmp(arg, "--") == 0) {
630 				moreOptions = false;
631 
632 			} else {
633 				fprintf(stderr, "Error: Invalid option: \"%s\"\n", arg);
634 				print_usage_and_exit(true);
635 			}
636 
637 		} else {
638 			// file
639 			files[fileCount++] = arg;
640 		}
641 	}
642 
643 	// check parameters
644 
645 	// enough files
646 	if (fileCount < 2) {
647 		fprintf(stderr, "Error: Not enough file names specified.\n");
648 		print_usage_and_exit(true);
649 	}
650 
651 	// attribute type
652 	type_code attributeType = B_ANY_TYPE;
653 	if (attributeTypeString) {
654 		bool found = false;
655 		for (int i = 0; kSupportedAttributeTypes[i].type_name; i++) {
656 			if (strcmp(attributeTypeString,
657 					kSupportedAttributeTypes[i].type_name) == 0) {
658 				found = true;
659 				attributeType = kSupportedAttributeTypes[i].type;
660 				break;
661 			}
662 		}
663 
664 		if (!found) {
665 			fprintf(stderr, "Error: Unsupported attribute type: \"%s\"\n",
666 				attributeTypeString);
667 			exit(1);
668 		}
669 	}
670 
671 	// init the attribute filter
672 	parameters.attribute_filter.SetTo(attributeName, attributeType);
673 
674 	// turn of move_files, if we are not copying the file data
675 	parameters.move_files &= parameters.copy_data;
676 
677 	// do the copying
678 	fileCount--;
679 	const char *destination = files[fileCount];
680 	files[fileCount] = NULL;
681 	copy_files(files, fileCount, destination, parameters);
682 
683 	return 0;
684 }
685