2 * Copyright 2008-2009, Stephan Aßmus <superstippi@gmx.de>
3 * Copyright 2014, Axel Dörfler, axeld@pinc-software.de
4 * All rights reserved. Distributed under the terms of the MIT License.
8 #include "CopyEngine.h"
15 #include <sys/resource.h>
17 #include <Directory.h>
23 #include "SemaphoreLocker.h"
24 #include "ProgressReporter.h"
30 // #pragma mark - EntryFilter
33 CopyEngine::EntryFilter::~EntryFilter()
38 // #pragma mark - CopyEngine
41 CopyEngine::CopyEngine(ProgressReporter
* reporter
, EntryFilter
* entryFilter
)
47 fAbsoluteSourcePath(),
61 fCurrentTargetFolder(NULL
),
64 fProgressReporter(reporter
),
65 fEntryFilter(entryFilter
)
67 fWriterThread
= spawn_thread(_WriteThreadEntry
, "buffer writer",
68 B_NORMAL_PRIORITY
, this);
70 if (fWriterThread
>= B_OK
)
71 resume_thread(fWriterThread
);
73 // ask for a bunch more file descriptors so that nested copying works well
76 rl
.rlim_max
= RLIM_SAVED_MAX
;
77 setrlimit(RLIMIT_NOFILE
, &rl
);
81 CopyEngine::~CopyEngine()
83 while (fBufferQueue
.Size() > 0)
87 if (fWriterThread
>= B_OK
) {
89 wait_for_thread(fWriterThread
, &exitValue
);
95 CopyEngine::ResetTargets(const char* source
)
97 // TODO: One could subtract the bytes/items which were added to the
98 // ProgressReporter before resetting them...
100 fAbsoluteSourcePath
= source
;
105 fLastItemsCopied
= 0;
114 fCurrentTargetFolder
= NULL
;
120 CopyEngine::CollectTargets(const char* source
, sem_id cancelSemaphore
)
123 status_t ret
= _CollectCopyInfo(source
, level
, cancelSemaphore
);
124 if (ret
== B_OK
&& fProgressReporter
!= NULL
)
125 fProgressReporter
->AddItems(fItemsToCopy
, fBytesToCopy
);
131 CopyEngine::CopyFolder(const char* source
, const char* destination
,
132 sem_id cancelSemaphore
)
135 return _CopyFolder(source
, destination
, level
, cancelSemaphore
);
140 CopyEngine::CopyFile(const BEntry
& _source
, const BEntry
& _destination
,
141 sem_id cancelSemaphore
)
143 SemaphoreLocker
lock(cancelSemaphore
);
144 if (cancelSemaphore
>= 0 && !lock
.IsLocked()) {
145 // We are supposed to quit
149 BFile
source(&_source
, B_READ_ONLY
);
150 status_t ret
= source
.InitCheck();
154 BFile
* destination
= new (nothrow
) BFile(&_destination
,
155 B_WRITE_ONLY
| B_CREATE_FILE
| B_ERASE_FILE
);
156 ret
= destination
->InitCheck();
162 int32 loopIteration
= 0;
165 if (fBufferQueue
.Size() >= BUFFER_COUNT
) {
166 // the queue is "full", just wait a bit, the
167 // write thread will empty it
173 Buffer
* buffer
= new (nothrow
) Buffer(destination
);
174 if (!buffer
|| !buffer
->buffer
) {
177 fprintf(stderr
, "reading loop: out of memory\n");
182 ssize_t read
= source
.Read(buffer
->buffer
, buffer
->size
);
184 ret
= (status_t
)read
;
185 fprintf(stderr
, "Failed to read data: %s\n", strerror(ret
));
193 if (loopIteration
% 2 == 0)
196 buffer
->deleteFile
= read
== 0;
198 buffer
->validBytes
= (size_t)read
;
200 buffer
->validBytes
= 0;
202 // enqueue the buffer
203 ret
= fBufferQueue
.Push(buffer
);
205 buffer
->deleteFile
= false;
224 CopyEngine::_CollectCopyInfo(const char* _source
, int32
& level
,
225 sem_id cancelSemaphore
)
229 BDirectory
source(_source
);
230 status_t ret
= source
.InitCheck();
235 while (source
.GetNextEntry(&entry
) == B_OK
) {
236 SemaphoreLocker
lock(cancelSemaphore
);
237 if (cancelSemaphore
>= 0 && !lock
.IsLocked()) {
238 // We are supposed to quit
242 struct stat statInfo
;
243 entry
.GetStat(&statInfo
);
245 BPath sourceEntryPath
;
246 status_t ret
= entry
.GetPath(&sourceEntryPath
);
250 if (fEntryFilter
!= NULL
251 && !fEntryFilter
->ShouldCopyEntry(entry
,
252 _RelativeEntryPath(sourceEntryPath
.Path()), statInfo
, level
)) {
256 if (S_ISDIR(statInfo
.st_mode
)) {
257 // handle recursive directory copy
259 ret
= entry
.GetPath(&srcFolder
);
263 if (cancelSemaphore
>= 0)
266 ret
= _CollectCopyInfo(srcFolder
.Path(), level
, cancelSemaphore
);
269 } else if (S_ISLNK(statInfo
.st_mode
)) {
273 fBytesToCopy
+= statInfo
.st_size
;
285 CopyEngine::_CopyFolder(const char* _source
, const char* _destination
,
286 int32
& level
, sem_id cancelSemaphore
)
289 fCurrentTargetFolder
= _destination
;
291 BDirectory
source(_source
);
292 status_t ret
= source
.InitCheck();
296 ret
= create_directory(_destination
, 0777);
297 if (ret
< B_OK
&& ret
!= B_FILE_EXISTS
) {
298 fprintf(stderr
, "Could not create '%s': %s\n", _destination
,
303 BDirectory
destination(_destination
);
304 ret
= destination
.InitCheck();
309 while (source
.GetNextEntry(&entry
) == B_OK
) {
310 SemaphoreLocker
lock(cancelSemaphore
);
311 if (cancelSemaphore
>= 0 && !lock
.IsLocked()) {
312 // We are supposed to quit
316 const char* name
= entry
.Name();
317 BPath sourceEntryPath
;
318 status_t ret
= entry
.GetPath(&sourceEntryPath
);
321 const char* relativeSourceEntryPath
322 = _RelativeEntryPath(sourceEntryPath
.Path());
324 struct stat statInfo
;
325 entry
.GetStat(&statInfo
);
327 if (fEntryFilter
!= NULL
328 && !fEntryFilter
->ShouldCopyEntry(entry
, relativeSourceEntryPath
,
337 BEntry
copy(&destination
, name
);
338 bool copyAttributes
= true;
340 if (S_ISDIR(statInfo
.st_mode
)) {
341 // handle recursive directory copy
345 if (copy
.IsDirectory()) {
347 && fEntryFilter
->ShouldClobberFolder(entry
,
348 relativeSourceEntryPath
, statInfo
, level
)) {
349 ret
= _RemoveFolder(copy
);
351 // Do not overwrite attributes on folders that exist.
352 // This should work better when the install target
353 // already contains a Haiku installation.
354 copyAttributes
= false;
360 fprintf(stderr
, "Failed to make room for folder '%s': "
361 "%s\n", name
, strerror(ret
));
367 ret
= copy
.GetPath(&dstFolder
);
371 if (cancelSemaphore
>= 0)
374 ret
= _CopyFolder(sourceEntryPath
.Path(), dstFolder
.Path(), level
,
379 if (cancelSemaphore
>= 0 && !lock
.Lock()) {
380 // We are supposed to quit
385 if (copy
.IsDirectory())
386 ret
= _RemoveFolder(copy
);
390 fprintf(stderr
, "Failed to make room for entry '%s': "
391 "%s\n", name
, strerror(ret
));
395 if (S_ISLNK(statInfo
.st_mode
)) {
396 // copy symbolic links
397 BSymLink
srcLink(&entry
);
401 char linkPath
[B_PATH_NAME_LENGTH
];
402 ssize_t read
= srcLink
.ReadLink(linkPath
, B_PATH_NAME_LENGTH
- 1);
404 return (status_t
)read
;
406 // just in case it already exists...
410 ret
= destination
.CreateSymLink(name
, linkPath
, &dstLink
);
415 // NOTE: Do not pass the locker, we simply keep holding the lock!
416 ret
= CopyFile(entry
, copy
);
425 ret
= copy
.SetTo(&destination
, name
);
430 BNode
sourceNode(&entry
);
431 BNode
targetNode(©
);
432 char attrName
[B_ATTR_NAME_LENGTH
];
433 while (sourceNode
.GetNextAttrName(attrName
) == B_OK
) {
435 if (sourceNode
.GetAttrInfo(attrName
, &info
) < B_OK
)
440 ssize_t read
= sourceNode
.ReadAttr(attrName
, info
.type
,
441 offset
, buffer
, std::min((off_t
)size
, info
.size
));
442 // NOTE: It's important to still write the attribute even if
443 // we have read 0 bytes!
445 targetNode
.WriteAttr(attrName
, info
.type
, offset
, buffer
, read
);
447 read
= sourceNode
.ReadAttr(attrName
, info
.type
,
448 offset
, buffer
, std::min((off_t
)size
, info
.size
- offset
));
454 // copy basic attributes
455 copy
.SetPermissions(statInfo
.st_mode
);
456 copy
.SetOwner(statInfo
.st_uid
);
457 copy
.SetGroup(statInfo
.st_gid
);
458 copy
.SetModificationTime(statInfo
.st_mtime
);
459 copy
.SetCreationTime(statInfo
.st_crtime
);
468 CopyEngine::_RemoveFolder(BEntry
& entry
)
470 BDirectory
directory(&entry
);
471 status_t ret
= directory
.InitCheck();
476 while (directory
.GetNextEntry(&subEntry
) == B_OK
) {
477 if (subEntry
.IsDirectory()) {
478 ret
= _RemoveFolder(subEntry
);
482 ret
= subEntry
.Remove();
487 return entry
.Remove();
492 CopyEngine::_RelativeEntryPath(const char* absoluteSourcePath
) const
494 if (strncmp(absoluteSourcePath
, fAbsoluteSourcePath
,
495 fAbsoluteSourcePath
.Length()) != 0) {
496 return absoluteSourcePath
;
499 const char* relativePath
500 = absoluteSourcePath
+ fAbsoluteSourcePath
.Length();
501 return relativePath
[0] == '/' ? relativePath
+ 1 : relativePath
;
506 CopyEngine::_UpdateProgress()
508 if (fProgressReporter
== NULL
)
512 if (fLastItemsCopied
< fItemsCopied
) {
513 items
= fItemsCopied
- fLastItemsCopied
;
514 fLastItemsCopied
= fItemsCopied
;
518 if (fLastBytesRead
< fBytesRead
) {
519 bytes
= fBytesRead
- fLastBytesRead
;
520 fLastBytesRead
= fBytesRead
;
523 fProgressReporter
->ItemsWritten(items
, bytes
, fCurrentItem
,
524 fCurrentTargetFolder
);
529 CopyEngine::_WriteThreadEntry(void* cookie
)
531 CopyEngine
* engine
= (CopyEngine
*)cookie
;
532 engine
->_WriteThread();
538 CopyEngine::_WriteThread()
540 bigtime_t bufferWaitTimeout
= 100000;
544 bigtime_t now
= system_time();
546 Buffer
* buffer
= NULL
;
547 status_t ret
= fBufferQueue
.Pop(&buffer
, bufferWaitTimeout
);
548 if (ret
== B_TIMED_OUT
) {
549 // no buffer, timeout
551 } else if (ret
== B_NO_INIT
) {
554 } else if (ret
!= B_OK
) {
555 // no buffer, queue error
560 if (!buffer
->deleteFile
) {
561 ssize_t written
= buffer
->file
->Write(buffer
->buffer
,
563 if (written
!= (ssize_t
)buffer
->validBytes
) {
564 // TODO: this should somehow be propagated back
565 // to the main thread!
566 fprintf(stderr
, "Failed to write data: %s\n",
567 strerror((status_t
)written
));
569 fBytesWritten
+= written
;
574 // measure performance
575 fTimeWritten
+= system_time() - now
;
578 double megaBytes
= (double)fBytesWritten
/ (1024 * 1024);
579 double seconds
= (fTimeWritten
/ 1000000);
581 printf("%.2f MB written (%.2f MB/s)\n", megaBytes
,
582 megaBytes
/ seconds
);