/* * Copyright 2008-2009, Stephan Aßmus * Copyright 2014, Axel Dörfler, axeld@pinc-software.de * All rights reserved. Distributed under the terms of the MIT License. */ #include "CopyEngine.h" #include #include #include #include #include #include #include #include #include #include #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); } }