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
27namespace BPrivate {
28
29
30static const size_t kDefaultBufferSize = 1024 * 1024;
31static const size_t kSmallBufferSize = 64 * 1024;
32
33
34// #pragma mark - BCopyEngine
35
36
37BCopyEngine::BCopyEngine(uint32 flags)
38	:
39	fController(NULL),
40	fFlags(flags),
41	fBuffer(NULL),
42	fBufferSize(0)
43{
44}
45
46
47BCopyEngine::~BCopyEngine()
48{
49	delete[] fBuffer;
50}
51
52
53BCopyEngine::BController*
54BCopyEngine::Controller() const
55{
56	return fController;
57}
58
59
60void
61BCopyEngine::SetController(BController* controller)
62{
63	fController = controller;
64}
65
66
67uint32
68BCopyEngine::Flags() const
69{
70	return fFlags;
71}
72
73
74BCopyEngine&
75BCopyEngine::SetFlags(uint32 flags)
76{
77	fFlags = flags;
78	return *this;
79}
80
81
82BCopyEngine&
83BCopyEngine::AddFlags(uint32 flags)
84{
85	fFlags |= flags;
86	return *this;
87}
88
89
90BCopyEngine&
91BCopyEngine::RemoveFlags(uint32 flags)
92{
93	fFlags &= ~flags;
94	return *this;
95}
96
97
98status_t
99BCopyEngine::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
130status_t
131BCopyEngine::_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
337status_t
338BCopyEngine::_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
373status_t
374BCopyEngine::_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
462void
463BCopyEngine::_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
474void
475BCopyEngine::_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
486status_t
487BCopyEngine::_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
504status_t
505BCopyEngine::_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
525BCopyEngine::BController::BController()
526{
527}
528
529
530BCopyEngine::BController::~BController()
531{
532}
533
534
535bool
536BCopyEngine::BController::EntryStarted(const char* path)
537{
538	return true;
539}
540
541
542bool
543BCopyEngine::BController::EntryFinished(const char* path, status_t error)
544{
545	return error == B_OK;
546}
547
548
549bool
550BCopyEngine::BController::AttributeStarted(const char* path,
551	const char* attribute, uint32 attributeType)
552{
553	return true;
554}
555
556
557bool
558BCopyEngine::BController::AttributeFinished(const char* path,
559	const char* attribute, uint32 attributeType, status_t error)
560{
561	return error == B_OK;
562}
563
564
565void
566BCopyEngine::BController::ErrorOccurred(const char* message, status_t error)
567{
568}
569
570
571} // namespace BPrivate
572