xref: /haiku/src/kits/storage/CopyEngine.cpp (revision f73f5d4c42a01ece688cbb57b5d332cc0f68b2c6)
1 /*
2  * Copyright 2013, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Ingo Weinhold <ingo_weinhold@gmx.de>
7  */
8 
9 
10 #include <CopyEngine.h>
11 
12 #include <errno.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <unistd.h>
17 
18 #include <Directory.h>
19 #include <Entry.h>
20 #include <File.h>
21 #include <fs_attr.h>
22 #include <Path.h>
23 #include <SymLink.h>
24 #include <TypeConstants.h>
25 
26 
27 namespace BPrivate {
28 
29 
30 static const size_t kDefaultBufferSize = 1024 * 1024;
31 static const size_t kSmallBufferSize = 64 * 1024;
32 
33 
34 // #pragma mark - BCopyEngine
35 
36 
37 BCopyEngine::BCopyEngine(uint32 flags)
38 	:
39 	fController(NULL),
40 	fFlags(flags),
41 	fBuffer(NULL),
42 	fBufferSize(0)
43 {
44 }
45 
46 
47 BCopyEngine::~BCopyEngine()
48 {
49 	delete[] fBuffer;
50 }
51 
52 
53 BCopyEngine::BController*
54 BCopyEngine::Controller() const
55 {
56 	return fController;
57 }
58 
59 
60 void
61 BCopyEngine::SetController(BController* controller)
62 {
63 	fController = controller;
64 }
65 
66 
67 uint32
68 BCopyEngine::Flags() const
69 {
70 	return fFlags;
71 }
72 
73 
74 BCopyEngine&
75 BCopyEngine::SetFlags(uint32 flags)
76 {
77 	fFlags = flags;
78 	return *this;
79 }
80 
81 
82 BCopyEngine&
83 BCopyEngine::AddFlags(uint32 flags)
84 {
85 	fFlags |= flags;
86 	return *this;
87 }
88 
89 
90 BCopyEngine&
91 BCopyEngine::RemoveFlags(uint32 flags)
92 {
93 	fFlags &= ~flags;
94 	return *this;
95 }
96 
97 
98 status_t
99 BCopyEngine::CopyEntry(const Entry& sourceEntry, const Entry& destEntry)
100 {
101 	if (fBuffer == NULL) {
102 		fBuffer = new(std::nothrow) char[kDefaultBufferSize];
103 		if (fBuffer == NULL) {
104 			fBuffer = new(std::nothrow) char[kSmallBufferSize];
105 			if (fBuffer == NULL) {
106 				_NotifyError(B_NO_MEMORY, "Failed to allocate buffer");
107 				return B_NO_MEMORY;
108 			}
109 			fBufferSize = kSmallBufferSize;
110 		} else
111 			fBufferSize = kDefaultBufferSize;
112 	}
113 
114 	BPath sourcePathBuffer;
115 	const char* sourcePath;
116 	status_t error = sourceEntry.GetPath(sourcePathBuffer, sourcePath);
117 	if (error != B_OK)
118 		return error;
119 
120 	BPath destPathBuffer;
121 	const char* destPath;
122 	error = destEntry.GetPath(destPathBuffer, destPath);
123 	if (error != B_OK)
124 		return error;
125 
126 	return _CopyEntry(sourcePath, destPath);
127 }
128 
129 
130 status_t
131 BCopyEngine::_CopyEntry(const char* sourcePath, const char* destPath)
132 {
133 	// apply entry filter
134 	if (fController != NULL && !fController->EntryStarted(sourcePath))
135 		return B_OK;
136 
137 	// stat source
138 	struct stat sourceStat;
139 	if (lstat(sourcePath, &sourceStat) < 0) {
140 		return _HandleEntryError(sourcePath, errno,
141 			"Couldn't access \"%s\": %s\n", sourcePath, strerror(errno));
142 	}
143 
144 	// stat destination
145 	struct stat destStat;
146 	bool destExists = lstat(destPath, &destStat) == 0;
147 
148 	// check whether to delete/create the destination
149 	bool unlinkDest = destExists;
150 	bool createDest = true;
151 	if (destExists) {
152 		if (S_ISDIR(destStat.st_mode)) {
153 			if (!S_ISDIR(sourceStat.st_mode)
154 				|| (fFlags & MERGE_EXISTING_DIRECTORIES) == 0) {
155 				return _HandleEntryError(sourcePath, B_FILE_EXISTS,
156 					"Can't copy \"%s\", since directory \"%s\" is in the "
157 					"way.\n", sourcePath, destPath);
158 			}
159 
160 			if (S_ISDIR(sourceStat.st_mode)) {
161 				// both are dirs; nothing to do
162 				unlinkDest = false;
163 				destExists = false;
164 			}
165 		} else if ((fFlags & UNLINK_DESTINATION) == 0) {
166 			return _HandleEntryError(sourcePath, B_FILE_EXISTS,
167 				"Can't copy \"%s\", since entry \"%s\" is in the way.\n",
168 				sourcePath, destPath);
169 		}
170 	}
171 
172 	// unlink the destination
173 	if (unlinkDest) {
174 		if (unlink(destPath) < 0) {
175 			return _HandleEntryError(sourcePath, errno,
176 				"Failed to unlink \"%s\": %s\n", destPath, strerror(errno));
177 		}
178 	}
179 
180 	// open source node
181 	BNode _sourceNode;
182 	BFile sourceFile;
183 	BDirectory sourceDir;
184 	BNode* sourceNode = NULL;
185 	status_t error;
186 
187 	if (S_ISDIR(sourceStat.st_mode)) {
188 		error = sourceDir.SetTo(sourcePath);
189 		sourceNode = &sourceDir;
190 	} else if (S_ISREG(sourceStat.st_mode)) {
191 		error = sourceFile.SetTo(sourcePath, B_READ_ONLY);
192 		sourceNode = &sourceFile;
193 	} else {
194 		error = _sourceNode.SetTo(sourcePath);
195 		sourceNode = &_sourceNode;
196 	}
197 
198 	if (error != B_OK) {
199 		return _HandleEntryError(sourcePath, error,
200 			"Failed to open \"%s\": %s\n", sourcePath, strerror(error));
201 	}
202 
203 	// create the destination
204 	BNode _destNode;
205 	BDirectory destDir;
206 	BFile destFile;
207 	BSymLink destSymLink;
208 	BNode* destNode = NULL;
209 
210 	if (createDest) {
211 		if (S_ISDIR(sourceStat.st_mode)) {
212 			// create dir
213 			error = BDirectory().CreateDirectory(destPath, &destDir);
214 			if (error != B_OK) {
215 				return _HandleEntryError(sourcePath, error,
216 					"Failed to make directory \"%s\": %s\n", destPath,
217 					strerror(error));
218 			}
219 
220 			destNode = &destDir;
221 		} else if (S_ISREG(sourceStat.st_mode)) {
222 			// create file
223 			error = BDirectory().CreateFile(destPath, &destFile);
224 			if (error != B_OK) {
225 				return _HandleEntryError(sourcePath, error,
226 					"Failed to create file \"%s\": %s\n", destPath,
227 					strerror(error));
228 			}
229 
230 			destNode = &destFile;
231 
232 			// copy file contents
233 			error = _CopyFileData(sourcePath, sourceFile, destPath, destFile);
234 			if (error != B_OK) {
235 				if (fController != NULL
236 					&& fController->EntryFinished(sourcePath, error)) {
237 					return B_OK;
238 				}
239 				return error;
240 			}
241 		} else if (S_ISLNK(sourceStat.st_mode)) {
242 			// read symlink
243 			char* linkTo = fBuffer;
244 			ssize_t bytesRead = readlink(sourcePath, linkTo, fBufferSize - 1);
245 			if (bytesRead < 0) {
246 				return _HandleEntryError(sourcePath, errno,
247 					"Failed to read symlink \"%s\": %s\n", sourcePath,
248 					strerror(errno));
249 			}
250 
251 			// null terminate the link contents
252 			linkTo[bytesRead] = '\0';
253 
254 			// create symlink
255 			error = BDirectory().CreateSymLink(destPath, linkTo, &destSymLink);
256 			if (error != B_OK) {
257 				return _HandleEntryError(sourcePath, error,
258 					"Failed to create symlink \"%s\": %s\n", destPath,
259 					strerror(error));
260 			}
261 
262 			destNode = &destSymLink;
263 
264 		} else {
265 			return _HandleEntryError(sourcePath, B_NOT_SUPPORTED,
266 				"Source file \"%s\" has unsupported type.\n", sourcePath);
267 		}
268 
269 		// copy attributes (before setting the permissions!)
270 		error = _CopyAttributes(sourcePath, *sourceNode, destPath, *destNode);
271 		if (error != B_OK) {
272 			if (fController != NULL
273 				&& fController->EntryFinished(sourcePath, error)) {
274 				return B_OK;
275 			}
276 			return error;
277 		}
278 
279 		// set file owner, group, permissions, times
280 		destNode->SetOwner(sourceStat.st_uid);
281 		destNode->SetGroup(sourceStat.st_gid);
282 		destNode->SetPermissions(sourceStat.st_mode);
283 		#ifdef HAIKU_TARGET_PLATFORM_HAIKU
284 			destNode->SetCreationTime(sourceStat.st_crtime);
285 		#endif
286 		destNode->SetModificationTime(sourceStat.st_mtime);
287 	}
288 
289 	// the destination node is no longer needed
290 	destNode->Unset();
291 
292 	// recurse
293 	if ((fFlags & COPY_RECURSIVELY) != 0 && S_ISDIR(sourceStat.st_mode)) {
294 		char buffer[sizeof(dirent) + B_FILE_NAME_LENGTH];
295 		dirent *entry = (dirent*)buffer;
296 		while (sourceDir.GetNextDirents(entry, sizeof(buffer), 1) == 1) {
297 			if (strcmp(entry->d_name, ".") == 0
298 				|| strcmp(entry->d_name, "..") == 0) {
299 				continue;
300 			}
301 
302 			// construct new entry paths
303 			BPath sourceEntryPath;
304 			error = sourceEntryPath.SetTo(sourcePath, entry->d_name);
305 			if (error != B_OK) {
306 				return _HandleEntryError(sourcePath, error,
307 					"Failed to construct entry path from dir \"%s\" and name "
308 					"\"%s\": %s\n", sourcePath, entry->d_name, strerror(error));
309 			}
310 
311 			BPath destEntryPath;
312 			error = destEntryPath.SetTo(destPath, entry->d_name);
313 			if (error != B_OK) {
314 				return _HandleEntryError(sourcePath, error,
315 					"Failed to construct entry path from dir \"%s\" and name "
316 					"\"%s\": %s\n", destPath, entry->d_name, strerror(error));
317 			}
318 
319 			// copy the entry
320 			error = _CopyEntry(sourceEntryPath.Path(), destEntryPath.Path());
321 			if (error != B_OK) {
322 				if (fController != NULL
323 					&& fController->EntryFinished(sourcePath, error)) {
324 					return B_OK;
325 				}
326 				return error;
327 			}
328 		}
329 	}
330 
331 	if (fController != NULL)
332 		fController->EntryFinished(sourcePath, B_OK);
333 	return B_OK;
334 }
335 
336 
337 status_t
338 BCopyEngine::_CopyFileData(const char* sourcePath, BFile& source,
339 	const char* destPath, BFile& destination)
340 {
341 	off_t offset = 0;
342 	while (true) {
343 		// read
344 		ssize_t bytesRead = source.ReadAt(offset, fBuffer, fBufferSize);
345 		if (bytesRead < 0) {
346 			_NotifyError(bytesRead, "Failed to read from file \"%s\": %s\n",
347 				sourcePath, strerror(bytesRead));
348 			return bytesRead;
349 		}
350 
351 		if (bytesRead == 0)
352 			return B_OK;
353 
354 		// write
355 		ssize_t bytesWritten = destination.WriteAt(offset, fBuffer, bytesRead);
356 		if (bytesWritten < 0) {
357 			_NotifyError(bytesWritten, "Failed to write to file \"%s\": %s\n",
358 				destPath, strerror(bytesWritten));
359 			return bytesWritten;
360 		}
361 
362 		if (bytesWritten != bytesRead) {
363 			_NotifyError(B_ERROR, "Failed to write all data to file \"%s\"\n",
364 				destPath);
365 			return B_ERROR;
366 		}
367 
368 		offset += bytesRead;
369 	}
370 }
371 
372 
373 status_t
374 BCopyEngine::_CopyAttributes(const char* sourcePath, BNode& source,
375 	const char* destPath, BNode& destination)
376 {
377 	char attrName[B_ATTR_NAME_LENGTH];
378 	while (source.GetNextAttrName(attrName) == B_OK) {
379 		// get attr info
380 		attr_info attrInfo;
381 		status_t error = source.GetAttrInfo(attrName, &attrInfo);
382 		if (error != B_OK) {
383 			// Delay reporting/handling the error until the controller has been
384 			// asked whether it is interested.
385 			attrInfo.type = B_ANY_TYPE;
386 		}
387 
388 		// filter
389 		if (fController != NULL
390 			&& !fController->AttributeStarted(sourcePath, attrName,
391 				attrInfo.type)) {
392 			if (error != B_OK) {
393 				_NotifyError(error, "Failed to get info of attribute \"%s\" "
394 					"of file \"%s\": %s\n", attrName, sourcePath,
395 					strerror(error));
396 			}
397 			continue;
398 		}
399 
400 		if (error != B_OK) {
401 			error = _HandleAttributeError(sourcePath, attrName, attrInfo.type,
402 				error, "Failed to get info of attribute \"%s\" of file \"%s\": "
403 				"%s\n", attrName, sourcePath, strerror(error));
404 			if (error != B_OK)
405 				return error;
406 			continue;
407 		}
408 
409 		// copy the attribute
410 		off_t offset = 0;
411 		off_t bytesLeft = attrInfo.size;
412 		// go at least once through the loop, so that an empty attribute will be
413 		// created as well
414 		do {
415 			size_t toRead = fBufferSize;
416 			if ((off_t)toRead > bytesLeft)
417 				toRead = bytesLeft;
418 
419 			// read
420 			ssize_t bytesRead = source.ReadAttr(attrName, attrInfo.type,
421 				offset, fBuffer, toRead);
422 			if (bytesRead < 0) {
423 				error = _HandleAttributeError(sourcePath, attrName,
424 					attrInfo.type, bytesRead, "Failed to read attribute \"%s\" "
425 					"of file \"%s\": %s\n", attrName, sourcePath,
426 					strerror(bytesRead));
427 				if (error != B_OK)
428 					return error;
429 				break;
430 			}
431 
432 			if (bytesRead == 0 && offset > 0)
433 				break;
434 
435 			// write
436 			ssize_t bytesWritten = destination.WriteAttr(attrName,
437 				attrInfo.type, offset, fBuffer, bytesRead);
438 			if (bytesWritten < 0) {
439 				error = _HandleAttributeError(sourcePath, attrName,
440 					attrInfo.type, bytesWritten, "Failed to write attribute "
441 					"\"%s\" of file \"%s\": %s\n", attrName, destPath,
442 					strerror(bytesWritten));
443 				if (error != B_OK)
444 					return error;
445 				break;
446 			}
447 
448 			bytesLeft -= bytesRead;
449 			offset += bytesRead;
450 		} while (bytesLeft > 0);
451 
452 		if (fController != NULL) {
453 			fController->AttributeFinished(sourcePath, attrName, attrInfo.type,
454 				B_OK);
455 		}
456 	}
457 
458 	return B_OK;
459 }
460 
461 
462 void
463 BCopyEngine::_NotifyError(status_t error, const char* format, ...)
464 {
465 	if (fController != NULL) {
466 		va_list args;
467 		va_start(args, format);
468 		_NotifyErrorVarArgs(error, format, args);
469 		va_end(args);
470 	}
471 }
472 
473 
474 void
475 BCopyEngine::_NotifyErrorVarArgs(status_t error, const char* format,
476 	va_list args)
477 {
478 	if (fController != NULL) {
479 		BString message;
480 		message.SetToFormatVarArgs(format, args);
481 		fController->ErrorOccurred(message, error);
482 	}
483 }
484 
485 
486 status_t
487 BCopyEngine::_HandleEntryError(const char* path, status_t error,
488 	const char* format, ...)
489 {
490 	if (fController == NULL)
491 		return error;
492 
493 	va_list args;
494 	va_start(args, format);
495 	_NotifyErrorVarArgs(error, format, args);
496 	va_end(args);
497 
498 	if (fController->EntryFinished(path, error))
499 		return B_OK;
500 	return error;
501 }
502 
503 
504 status_t
505 BCopyEngine::_HandleAttributeError(const char* path, const char* attribute,
506 	uint32 attributeType, status_t error, const char* format, ...)
507 {
508 	if (fController == NULL)
509 		return error;
510 
511 	va_list args;
512 	va_start(args, format);
513 	_NotifyErrorVarArgs(error, format, args);
514 	va_end(args);
515 
516 	if (fController->AttributeFinished(path, attribute, attributeType, error))
517 		return B_OK;
518 	return error;
519 }
520 
521 
522 // #pragma mark - BController
523 
524 
525 BCopyEngine::BController::BController()
526 {
527 }
528 
529 
530 BCopyEngine::BController::~BController()
531 {
532 }
533 
534 
535 bool
536 BCopyEngine::BController::EntryStarted(const char* path)
537 {
538 	return true;
539 }
540 
541 
542 bool
543 BCopyEngine::BController::EntryFinished(const char* path, status_t error)
544 {
545 	return error == B_OK;
546 }
547 
548 
549 bool
550 BCopyEngine::BController::AttributeStarted(const char* path,
551 	const char* attribute, uint32 attributeType)
552 {
553 	return true;
554 }
555 
556 
557 bool
558 BCopyEngine::BController::AttributeFinished(const char* path,
559 	const char* attribute, uint32 attributeType, status_t error)
560 {
561 	return error == B_OK;
562 }
563 
564 
565 void
566 BCopyEngine::BController::ErrorOccurred(const char* message, status_t error)
567 {
568 }
569 
570 
571 } // namespace BPrivate
572