headers/bsd: Add sys/queue.h.
[haiku.git] / src / kits / storage / CopyEngine.cpp
blobdea733fb7ed1549304a46ba2cb6d6b2ec018adfa
1 /*
2 * Copyright 2013, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Ingo Weinhold <ingo_weinhold@gmx.de>
7 */
10 #include <CopyEngine.h>
12 #include <errno.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <unistd.h>
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>
27 namespace BPrivate {
30 static const size_t kDefaultBufferSize = 1024 * 1024;
31 static const size_t kSmallBufferSize = 64 * 1024;
34 // #pragma mark - BCopyEngine
37 BCopyEngine::BCopyEngine(uint32 flags)
39 fController(NULL),
40 fFlags(flags),
41 fBuffer(NULL),
42 fBufferSize(0)
47 BCopyEngine::~BCopyEngine()
49 delete[] fBuffer;
53 BCopyEngine::BController*
54 BCopyEngine::Controller() const
56 return fController;
60 void
61 BCopyEngine::SetController(BController* controller)
63 fController = controller;
67 uint32
68 BCopyEngine::Flags() const
70 return fFlags;
74 BCopyEngine&
75 BCopyEngine::SetFlags(uint32 flags)
77 fFlags = flags;
78 return *this;
82 BCopyEngine&
83 BCopyEngine::AddFlags(uint32 flags)
85 fFlags |= flags;
86 return *this;
90 BCopyEngine&
91 BCopyEngine::RemoveFlags(uint32 flags)
93 fFlags &= ~flags;
94 return *this;
98 status_t
99 BCopyEngine::CopyEntry(const Entry& sourceEntry, const Entry& destEntry)
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;
109 fBufferSize = kSmallBufferSize;
110 } else
111 fBufferSize = kDefaultBufferSize;
114 BPath sourcePathBuffer;
115 const char* sourcePath;
116 status_t error = sourceEntry.GetPath(sourcePathBuffer, sourcePath);
117 if (error != B_OK)
118 return error;
120 BPath destPathBuffer;
121 const char* destPath;
122 error = destEntry.GetPath(destPathBuffer, destPath);
123 if (error != B_OK)
124 return error;
126 return _CopyEntry(sourcePath, destPath);
130 status_t
131 BCopyEngine::_CopyEntry(const char* sourcePath, const char* destPath)
133 // apply entry filter
134 if (fController != NULL && !fController->EntryStarted(sourcePath))
135 return B_OK;
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));
144 // stat destination
145 struct stat destStat;
146 bool destExists = lstat(destPath, &destStat) == 0;
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);
160 if (S_ISDIR(sourceStat.st_mode)) {
161 // both are dirs; nothing to do
162 unlinkDest = false;
163 destExists = false;
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);
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));
180 // open source node
181 BNode _sourceNode;
182 BFile sourceFile;
183 BDirectory sourceDir;
184 BNode* sourceNode = NULL;
185 status_t error;
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;
198 if (error != B_OK) {
199 return _HandleEntryError(sourcePath, error,
200 "Failed to open \"%s\": %s\n", sourcePath, strerror(error));
203 // create the destination
204 BNode _destNode;
205 BDirectory destDir;
206 BFile destFile;
207 BSymLink destSymLink;
208 BNode* destNode = NULL;
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));
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));
230 destNode = &destFile;
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;
239 return error;
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));
251 // null terminate the link contents
252 linkTo[bytesRead] = '\0';
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));
262 destNode = &destSymLink;
264 } else {
265 return _HandleEntryError(sourcePath, B_NOT_SUPPORTED,
266 "Source file \"%s\" has unsupported type.\n", sourcePath);
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;
276 return error;
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);
289 // the destination node is no longer needed
290 destNode->Unset();
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;
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));
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));
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;
326 return error;
331 if (fController != NULL)
332 fController->EntryFinished(sourcePath, B_OK);
333 return B_OK;
337 status_t
338 BCopyEngine::_CopyFileData(const char* sourcePath, BFile& source,
339 const char* destPath, BFile& destination)
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;
351 if (bytesRead == 0)
352 return B_OK;
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;
362 if (bytesWritten != bytesRead) {
363 _NotifyError(B_ERROR, "Failed to write all data to file \"%s\"\n",
364 destPath);
365 return B_ERROR;
368 offset += bytesRead;
373 status_t
374 BCopyEngine::_CopyAttributes(const char* sourcePath, BNode& source,
375 const char* destPath, BNode& destination)
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;
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));
397 continue;
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;
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;
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;
432 if (bytesRead == 0 && offset > 0)
433 break;
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;
448 bytesLeft -= bytesRead;
449 offset += bytesRead;
450 } while (bytesLeft > 0);
452 if (fController != NULL) {
453 fController->AttributeFinished(sourcePath, attrName, attrInfo.type,
454 B_OK);
458 return B_OK;
462 void
463 BCopyEngine::_NotifyError(status_t error, const char* format, ...)
465 if (fController != NULL) {
466 va_list args;
467 va_start(args, format);
468 _NotifyErrorVarArgs(error, format, args);
469 va_end(args);
474 void
475 BCopyEngine::_NotifyErrorVarArgs(status_t error, const char* format,
476 va_list args)
478 if (fController != NULL) {
479 BString message;
480 message.SetToFormatVarArgs(format, args);
481 fController->ErrorOccurred(message, error);
486 status_t
487 BCopyEngine::_HandleEntryError(const char* path, status_t error,
488 const char* format, ...)
490 if (fController == NULL)
491 return error;
493 va_list args;
494 va_start(args, format);
495 _NotifyErrorVarArgs(error, format, args);
496 va_end(args);
498 if (fController->EntryFinished(path, error))
499 return B_OK;
500 return error;
504 status_t
505 BCopyEngine::_HandleAttributeError(const char* path, const char* attribute,
506 uint32 attributeType, status_t error, const char* format, ...)
508 if (fController == NULL)
509 return error;
511 va_list args;
512 va_start(args, format);
513 _NotifyErrorVarArgs(error, format, args);
514 va_end(args);
516 if (fController->AttributeFinished(path, attribute, attributeType, error))
517 return B_OK;
518 return error;
522 // #pragma mark - BController
525 BCopyEngine::BController::BController()
530 BCopyEngine::BController::~BController()
535 bool
536 BCopyEngine::BController::EntryStarted(const char* path)
538 return true;
542 bool
543 BCopyEngine::BController::EntryFinished(const char* path, status_t error)
545 return error == B_OK;
549 bool
550 BCopyEngine::BController::AttributeStarted(const char* path,
551 const char* attribute, uint32 attributeType)
553 return true;
557 bool
558 BCopyEngine::BController::AttributeFinished(const char* path,
559 const char* attribute, uint32 attributeType, status_t error)
561 return error == B_OK;
565 void
566 BCopyEngine::BController::ErrorOccurred(const char* message, status_t error)
571 } // namespace BPrivate