haiku/src/apps/installer/CopyEngine.cpp

583 lines
13 KiB
C++

/*
* Copyright 2008-2009, Stephan Aßmus <superstippi@gmx.de>
* Copyright 2014, Axel Dörfler, axeld@pinc-software.de
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "CopyEngine.h"
#include <new>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <Directory.h>
#include <fs_attr.h>
#include <NodeInfo.h>
#include <Path.h>
#include <SymLink.h>
#include "SemaphoreLocker.h"
#include "ProgressReporter.h"
using std::nothrow;
// #pragma mark - EntryFilter
CopyEngine::EntryFilter::~EntryFilter()
{
}
// #pragma mark - CopyEngine
CopyEngine::CopyEngine(ProgressReporter* reporter, EntryFilter* entryFilter)
:
fBufferQueue(),
fWriterThread(-1),
fQuitting(false),
fAbsoluteSourcePath(),
fBytesRead(0),
fLastBytesRead(0),
fItemsCopied(0),
fLastItemsCopied(0),
fTimeRead(0),
fBytesWritten(0),
fTimeWritten(0),
fCurrentTargetFolder(NULL),
fCurrentItem(NULL),
fProgressReporter(reporter),
fEntryFilter(entryFilter)
{
fWriterThread = spawn_thread(_WriteThreadEntry, "buffer writer",
B_NORMAL_PRIORITY, this);
if (fWriterThread >= B_OK)
resume_thread(fWriterThread);
// ask for a bunch more file descriptors so that nested copying works well
struct rlimit rl;
rl.rlim_cur = 512;
rl.rlim_max = RLIM_SAVED_MAX;
setrlimit(RLIMIT_NOFILE, &rl);
}
CopyEngine::~CopyEngine()
{
while (fBufferQueue.Size() > 0)
snooze(10000);
fQuitting = true;
if (fWriterThread >= B_OK) {
int32 exitValue;
wait_for_thread(fWriterThread, &exitValue);
}
}
void
CopyEngine::ResetTargets(const char* source)
{
// TODO: One could subtract the bytes/items which were added to the
// ProgressReporter before resetting them...
fAbsoluteSourcePath = source;
fBytesRead = 0;
fLastBytesRead = 0;
fItemsCopied = 0;
fLastItemsCopied = 0;
fTimeRead = 0;
fBytesWritten = 0;
fTimeWritten = 0;
fCurrentTargetFolder = NULL;
fCurrentItem = NULL;
}
status_t
CopyEngine::CollectTargets(const char* source, sem_id cancelSemaphore)
{
off_t bytesToCopy = 0;
uint64 itemsToCopy = 0;
status_t ret = _CollectCopyInfo(source, cancelSemaphore, bytesToCopy,
itemsToCopy);
if (ret == B_OK && fProgressReporter != NULL)
fProgressReporter->AddItems(itemsToCopy, bytesToCopy);
return ret;
}
status_t
CopyEngine::Copy(const char* _source, const char* _destination,
sem_id cancelSemaphore, bool copyAttributes)
{
status_t ret;
BEntry source(_source);
ret = source.InitCheck();
if (ret != B_OK)
return ret;
BEntry destination(_destination);
ret = destination.InitCheck();
if (ret != B_OK)
return ret;
return _Copy(source, destination, cancelSemaphore, copyAttributes);
}
status_t
CopyEngine::RemoveFolder(BEntry& entry)
{
BDirectory directory(&entry);
status_t ret = directory.InitCheck();
if (ret != B_OK)
return ret;
BEntry subEntry;
while (directory.GetNextEntry(&subEntry) == B_OK) {
if (subEntry.IsDirectory()) {
ret = CopyEngine::RemoveFolder(subEntry);
if (ret != B_OK)
return ret;
} else {
ret = subEntry.Remove();
if (ret != B_OK)
return ret;
}
}
return entry.Remove();
}
status_t
CopyEngine::_CopyData(const BEntry& _source, const BEntry& _destination,
sem_id cancelSemaphore)
{
SemaphoreLocker lock(cancelSemaphore);
if (cancelSemaphore >= 0 && !lock.IsLocked()) {
// We are supposed to quit
return B_CANCELED;
}
BFile source(&_source, B_READ_ONLY);
status_t ret = source.InitCheck();
if (ret < B_OK)
return ret;
BFile* destination = new (nothrow) BFile(&_destination,
B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
ret = destination->InitCheck();
if (ret < B_OK) {
delete destination;
return ret;
}
int32 loopIteration = 0;
while (true) {
if (fBufferQueue.Size() >= BUFFER_COUNT) {
// the queue is "full", just wait a bit, the
// write thread will empty it
snooze(1000);
continue;
}
// allocate buffer
Buffer* buffer = new (nothrow) Buffer(destination);
if (!buffer || !buffer->buffer) {
delete destination;
delete buffer;
fprintf(stderr, "reading loop: out of memory\n");
return B_NO_MEMORY;
}
// fill buffer
ssize_t read = source.Read(buffer->buffer, buffer->size);
if (read < 0) {
ret = (status_t)read;
fprintf(stderr, "Failed to read data: %s\n", strerror(ret));
delete buffer;
delete destination;
break;
}
fBytesRead += read;
loopIteration += 1;
if (loopIteration % 2 == 0)
_UpdateProgress();
buffer->deleteFile = read == 0;
if (read > 0)
buffer->validBytes = (size_t)read;
else
buffer->validBytes = 0;
// enqueue the buffer
ret = fBufferQueue.Push(buffer);
if (ret < B_OK) {
buffer->deleteFile = false;
delete buffer;
delete destination;
return ret;
}
// quit if done
if (read == 0)
break;
}
return ret;
}
// #pragma mark -
status_t
CopyEngine::_CollectCopyInfo(const char* _source, sem_id cancelSemaphore,
off_t& bytesToCopy, uint64& itemsToCopy)
{
BEntry source(_source);
status_t ret = source.InitCheck();
if (ret < B_OK)
return ret;
struct stat statInfo;
ret = source.GetStat(&statInfo);
if (ret < B_OK)
return ret;
SemaphoreLocker lock(cancelSemaphore);
if (cancelSemaphore >= 0 && !lock.IsLocked()) {
// We are supposed to quit
return B_CANCELED;
}
if (fEntryFilter != NULL
&& !fEntryFilter->ShouldCopyEntry(source,
_RelativeEntryPath(_source), statInfo)) {
// Skip this entry
return B_OK;
}
if (cancelSemaphore >= 0)
lock.Unlock();
if (S_ISDIR(statInfo.st_mode)) {
BDirectory srcFolder(&source);
ret = srcFolder.InitCheck();
if (ret < B_OK)
return ret;
BEntry entry;
while (srcFolder.GetNextEntry(&entry) == B_OK) {
BPath entryPath;
ret = entry.GetPath(&entryPath);
if (ret < B_OK)
return ret;
ret = _CollectCopyInfo(entryPath.Path(), cancelSemaphore,
bytesToCopy, itemsToCopy);
if (ret < B_OK)
return ret;
}
} else if (S_ISLNK(statInfo.st_mode)) {
// link, ignore size
} else {
bytesToCopy += statInfo.st_size;
}
itemsToCopy++;
return B_OK;
}
status_t
CopyEngine::_Copy(BEntry &source, BEntry &destination,
sem_id cancelSemaphore, bool copyAttributes)
{
struct stat sourceInfo;
status_t ret = source.GetStat(&sourceInfo);
if (ret != B_OK)
return ret;
SemaphoreLocker lock(cancelSemaphore);
if (cancelSemaphore >= 0 && !lock.IsLocked()) {
// We are supposed to quit
return B_CANCELED;
}
BPath sourcePath(&source);
ret = sourcePath.InitCheck();
if (ret != B_OK)
return ret;
BPath destPath(&destination);
ret = destPath.InitCheck();
if (ret != B_OK)
return ret;
const char *relativeSourcePath = _RelativeEntryPath(sourcePath.Path());
if (fEntryFilter != NULL
&& !fEntryFilter->ShouldCopyEntry(source, relativeSourcePath,
sourceInfo)) {
// Silently skip the filtered entry.
return B_OK;
}
if (cancelSemaphore >= 0)
lock.Unlock();
bool copyAttributesToTarget = copyAttributes;
// attributes of the current source to the destination will be copied
// when copyAttributes is set to true, but there may be exceptions, so
// allow the recursively used copyAttribute parameter to be overridden
// for the current target.
if (S_ISDIR(sourceInfo.st_mode)) {
BDirectory sourceDirectory(&source);
ret = sourceDirectory.InitCheck();
if (ret != B_OK)
return ret;
if (destination.Exists()) {
if (destination.IsDirectory()) {
// Do not overwrite attributes on folders that exist.
// This should work better when the install target
// already contains a Haiku installation.
copyAttributesToTarget = false;
} else {
ret = destination.Remove();
}
if (ret != B_OK) {
fprintf(stderr, "Failed to make room for folder '%s': "
"%s\n", sourcePath.Path(), strerror(ret));
return ret;
}
}
ret = create_directory(destPath.Path(), 0777);
// Make sure the target path exists, it may have been deleted if
// the existing destination was a file instead of a directory.
if (ret != B_OK && ret != B_FILE_EXISTS) {
fprintf(stderr, "Could not create '%s': %s\n", destPath.Path(),
strerror(ret));
return ret;
}
BDirectory destDirectory(&destination);
ret = destDirectory.InitCheck();
if (ret != B_OK)
return ret;
BEntry entry;
while (sourceDirectory.GetNextEntry(&entry) == B_OK) {
BEntry dest(&destDirectory, entry.Name());
ret = dest.InitCheck();
if (ret != B_OK)
return ret;
ret = _Copy(entry, dest, cancelSemaphore, copyAttributes);
if (ret != B_OK)
return ret;
}
} else {
if (destination.Exists()) {
if (destination.IsDirectory())
ret = CopyEngine::RemoveFolder(destination);
else
ret = destination.Remove();
if (ret != B_OK) {
fprintf(stderr, "Failed to make room for entry '%s': "
"%s\n", sourcePath.Path(), strerror(ret));
return ret;
}
}
fItemsCopied++;
BPath destDirectory;
ret = destPath.GetParent(&destDirectory);
if (ret != B_OK)
return ret;
fCurrentTargetFolder = destDirectory.Path();
fCurrentItem = sourcePath.Leaf();
_UpdateProgress();
if (S_ISLNK(sourceInfo.st_mode)) {
// copy symbolic links
BSymLink srcLink(&source);
ret = srcLink.InitCheck();
if (ret != B_OK)
return ret;
char linkPath[B_PATH_NAME_LENGTH];
ssize_t read = srcLink.ReadLink(linkPath, B_PATH_NAME_LENGTH - 1);
if (read < 0)
return (status_t)read;
BDirectory dstFolder;
ret = destination.GetParent(&dstFolder);
if (ret != B_OK)
return ret;
ret = dstFolder.CreateSymLink(sourcePath.Leaf(), linkPath, NULL);
if (ret != B_OK)
return ret;
} else {
// copy file data
// NOTE: Do not pass the locker, we simply keep holding the lock!
ret = _CopyData(source, destination);
if (ret != B_OK)
return ret;
}
}
if (copyAttributesToTarget) {
// copy attributes to the current target
BNode sourceNode(&source);
BNode targetNode(&destination);
char attrName[B_ATTR_NAME_LENGTH];
while (sourceNode.GetNextAttrName(attrName) == B_OK) {
attr_info info;
if (sourceNode.GetAttrInfo(attrName, &info) != B_OK)
continue;
size_t size = 4096;
uint8 buffer[size];
off_t offset = 0;
ssize_t read = sourceNode.ReadAttr(attrName, info.type,
offset, buffer, std::min((off_t)size, info.size));
// NOTE: It's important to still write the attribute even if
// we have read 0 bytes!
while (read >= 0) {
targetNode.WriteAttr(attrName, info.type, offset, buffer, read);
offset += read;
read = sourceNode.ReadAttr(attrName, info.type,
offset, buffer, std::min((off_t)size, info.size - offset));
if (read == 0)
break;
}
}
// copy basic attributes
destination.SetPermissions(sourceInfo.st_mode);
destination.SetOwner(sourceInfo.st_uid);
destination.SetGroup(sourceInfo.st_gid);
destination.SetModificationTime(sourceInfo.st_mtime);
destination.SetCreationTime(sourceInfo.st_crtime);
}
return B_OK;
}
const char*
CopyEngine::_RelativeEntryPath(const char* absoluteSourcePath) const
{
if (strncmp(absoluteSourcePath, fAbsoluteSourcePath,
fAbsoluteSourcePath.Length()) != 0) {
return absoluteSourcePath;
}
const char* relativePath
= absoluteSourcePath + fAbsoluteSourcePath.Length();
return relativePath[0] == '/' ? relativePath + 1 : relativePath;
}
void
CopyEngine::_UpdateProgress()
{
if (fProgressReporter == NULL)
return;
uint64 items = 0;
if (fLastItemsCopied < fItemsCopied) {
items = fItemsCopied - fLastItemsCopied;
fLastItemsCopied = fItemsCopied;
}
off_t bytes = 0;
if (fLastBytesRead < fBytesRead) {
bytes = fBytesRead - fLastBytesRead;
fLastBytesRead = fBytesRead;
}
fProgressReporter->ItemsWritten(items, bytes, fCurrentItem,
fCurrentTargetFolder);
}
int32
CopyEngine::_WriteThreadEntry(void* cookie)
{
CopyEngine* engine = (CopyEngine*)cookie;
engine->_WriteThread();
return B_OK;
}
void
CopyEngine::_WriteThread()
{
bigtime_t bufferWaitTimeout = 100000;
while (!fQuitting) {
bigtime_t now = system_time();
Buffer* buffer = NULL;
status_t ret = fBufferQueue.Pop(&buffer, bufferWaitTimeout);
if (ret == B_TIMED_OUT) {
// no buffer, timeout
continue;
} else if (ret == B_NO_INIT) {
// real error
return;
} else if (ret != B_OK) {
// no buffer, queue error
snooze(10000);
continue;
}
if (!buffer->deleteFile) {
ssize_t written = buffer->file->Write(buffer->buffer,
buffer->validBytes);
if (written != (ssize_t)buffer->validBytes) {
// TODO: this should somehow be propagated back
// to the main thread!
fprintf(stderr, "Failed to write data: %s\n",
strerror((status_t)written));
}
fBytesWritten += written;
}
delete buffer;
// measure performance
fTimeWritten += system_time() - now;
}
double megaBytes = (double)fBytesWritten / (1024 * 1024);
double seconds = (double)fTimeWritten / 1000000;
if (seconds > 0) {
printf("%.2f MB written (%.2f MB/s)\n", megaBytes,
megaBytes / seconds);
}
}