/* * Copyright 2009, Stephan Aßmus . * Copyright 2005-2008, Jérôme DUVAL. * All rights reserved. Distributed under the terms of the MIT License. */ #include "WorkerThread.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AutoLocker.h" #include "CopyEngine.h" #include "InstallerWindow.h" #include "PackageViews.h" #include "PartitionMenuItem.h" #include "ProgressReporter.h" #include "StringForSize.h" #include "UnzipEngine.h" #define TR_CONTEXT "InstallProgress" //#define COPY_TRACE #ifdef COPY_TRACE #define CALLED() printf("CALLED %s\n",__PRETTY_FUNCTION__) #define ERR2(x, y...) fprintf(stderr, "WorkerThread: "x" %s\n", y, strerror(err)) #define ERR(x) fprintf(stderr, "WorkerThread: "x" %s\n", strerror(err)) #else #define CALLED() #define ERR(x) #define ERR2(x, y...) #endif const char BOOT_PATH[] = "/boot"; const uint32 MSG_START_INSTALLING = 'eSRT'; class SourceVisitor : public BDiskDeviceVisitor { public: SourceVisitor(BMenu* menu); virtual bool Visit(BDiskDevice* device); virtual bool Visit(BPartition* partition, int32 level); private: BMenu* fMenu; }; class TargetVisitor : public BDiskDeviceVisitor { public: TargetVisitor(BMenu* menu); virtual bool Visit(BDiskDevice* device); virtual bool Visit(BPartition* partition, int32 level); private: BMenu* fMenu; }; // #pragma mark - WorkerThread WorkerThread::WorkerThread(InstallerWindow *window) : BLooper("copy_engine"), fWindow(window), fPackages(NULL), fSpaceRequired(0), fCancelSemaphore(-1) { Run(); } void WorkerThread::MessageReceived(BMessage* message) { CALLED(); switch (message->what) { case MSG_START_INSTALLING: _PerformInstall(fWindow->GetSourceMenu(), fWindow->GetTargetMenu()); break; case MSG_WRITE_BOOT_SECTOR: { int32 id; if (message->FindInt32("id", &id) != B_OK) { _SetStatusMessage(TR("Boot sector not written because of an " " internal error.")); break; } // TODO: Refactor with _PerformInstall() BPath targetDirectory; BDiskDevice device; BPartition* partition; if (fDDRoster.GetPartitionWithID(id, &device, &partition) == B_OK) { if (!partition->IsMounted()) { if (partition->Mount() < B_OK) { _SetStatusMessage(TR("The partition can't be mounted. " "Please choose a different partition.")); break; } } if (partition->GetMountPoint(&targetDirectory) != B_OK) { _SetStatusMessage(TR("The mount point could not be " "retrieved.")); break; } } else if (fDDRoster.GetDeviceWithID(id, &device) == B_OK) { if (!device.IsMounted()) { if (device.Mount() < B_OK) { _SetStatusMessage(TR("The disk can't be mounted. " "Please choose a different disk.")); break; } } if (device.GetMountPoint(&targetDirectory) != B_OK) { _SetStatusMessage(TR("The mount point could not be " "retrieved.")); break; } } _LaunchFinishScript(targetDirectory); // TODO: Get error from executing script! _SetStatusMessage(TR("Boot sector successfully written.")); } default: BLooper::MessageReceived(message); } } void WorkerThread::ScanDisksPartitions(BMenu *srcMenu, BMenu *targetMenu) { // NOTE: This is actually executed in the window thread. BDiskDevice device; BPartition *partition = NULL; printf("\nScanDisksPartitions source partitions begin\n"); SourceVisitor srcVisitor(srcMenu); fDDRoster.VisitEachMountedPartition(&srcVisitor, &device, &partition); printf("\nScanDisksPartitions target partitions begin\n"); TargetVisitor targetVisitor(targetMenu); fDDRoster.VisitEachPartition(&targetVisitor, &device, &partition); } void WorkerThread::SetPackagesList(BList *list) { // Executed in window thread. BAutolock _(this); delete fPackages; fPackages = list; } void WorkerThread::StartInstall() { // Executed in window thread. PostMessage(MSG_START_INSTALLING, this); } void WorkerThread::WriteBootSector(BMenu* targetMenu) { // Executed in window thread. CALLED(); PartitionMenuItem* item = (PartitionMenuItem*)targetMenu->FindMarked(); if (item == NULL) { ERR("bad menu items\n"); return; } BMessage message(MSG_WRITE_BOOT_SECTOR); message.AddInt32("id", item->ID()); PostMessage(&message, this); } // #pragma mark - void WorkerThread::_LaunchInitScript(BPath &path) { BPath bootPath; find_directory(B_BEOS_BOOT_DIRECTORY, &bootPath); BString command("/bin/sh "); command += bootPath.Path(); command += "/InstallerInitScript "; command += "\""; command += path.Path(); command += "\""; _SetStatusMessage(TR("Starting Installation.")); system(command.String()); } void WorkerThread::_LaunchFinishScript(BPath &path) { BPath bootPath; find_directory(B_BEOS_BOOT_DIRECTORY, &bootPath); BString command("/bin/sh "); command += bootPath.Path(); command += "/InstallerFinishScript "; command += "\""; command += path.Path(); command += "\""; _SetStatusMessage(TR("Finishing Installation.")); system(command.String()); } void WorkerThread::_PerformInstall(BMenu* srcMenu, BMenu* targetMenu) { CALLED(); BPath targetDirectory, srcDirectory, trashPath, testPath; BDirectory targetDir; BDiskDevice device; BPartition* partition; BVolume targetVolume; status_t err = B_OK; int32 entries = 0; entry_ref testRef; const char* mountError = TR("The disk can't be mounted. Please choose " "a different disk."); BMessenger messenger(fWindow); ProgressReporter reporter(messenger, new BMessage(MSG_STATUS_MESSAGE)); CopyEngine engine(&reporter); BList unzipEngines; PartitionMenuItem* targetItem = (PartitionMenuItem*)targetMenu->FindMarked(); PartitionMenuItem* srcItem = (PartitionMenuItem*)srcMenu->FindMarked(); if (!srcItem || !targetItem) { ERR("bad menu items\n"); goto error; } // check if target is initialized // ask if init or mount as is if (fDDRoster.GetPartitionWithID(targetItem->ID(), &device, &partition) == B_OK) { if (!partition->IsMounted()) { if ((err = partition->Mount()) < B_OK) { _SetStatusMessage(mountError); ERR("BPartition::Mount"); goto error; } } if ((err = partition->GetVolume(&targetVolume)) != B_OK) { ERR("BPartition::GetVolume"); goto error; } if ((err = partition->GetMountPoint(&targetDirectory)) != B_OK) { ERR("BPartition::GetMountPoint"); goto error; } } else if (fDDRoster.GetDeviceWithID(targetItem->ID(), &device) == B_OK) { if (!device.IsMounted()) { if ((err = device.Mount()) < B_OK) { _SetStatusMessage(mountError); ERR("BDiskDevice::Mount"); goto error; } } if ((err = device.GetVolume(&targetVolume)) != B_OK) { ERR("BDiskDevice::GetVolume"); goto error; } if ((err = device.GetMountPoint(&targetDirectory)) != B_OK) { ERR("BDiskDevice::GetMountPoint"); goto error; } } else goto error; // shouldn't happen // check if target has enough space if ((fSpaceRequired > 0 && targetVolume.FreeBytes() < fSpaceRequired) && ((new BAlert("", TR("The destination disk may not have enough " "space. Try choosing a different disk or choose to not install " "optional items."), TR("Try installing anyway"), TR("Cancel"), 0, B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go() != 0)) { goto error; } if (fDDRoster.GetPartitionWithID(srcItem->ID(), &device, &partition) == B_OK) { if ((err = partition->GetMountPoint(&srcDirectory)) != B_OK) { ERR("BPartition::GetMountPoint"); goto error; } } else if (fDDRoster.GetDeviceWithID(srcItem->ID(), &device) == B_OK) { if ((err = device.GetMountPoint(&srcDirectory)) != B_OK) { ERR("BDiskDevice::GetMountPoint"); goto error; } } else goto error; // shouldn't happen // check not installing on itself if (strcmp(srcDirectory.Path(), targetDirectory.Path()) == 0) { _SetStatusMessage(TR("You can't install the contents of a disk onto " "itself. Please choose a different disk.")); goto error; } // check not installing on boot volume if ((strncmp(BOOT_PATH, targetDirectory.Path(), strlen(BOOT_PATH)) == 0) && ((new BAlert("", TR("Are you sure you want to install onto the " "current boot disk? The Installer will have to reboot your " "machine if you proceed."), TR("OK"), TR("Cancel"), 0, B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go() != 0)) { _SetStatusMessage("Installation stopped."); goto error; } // check if target volume's trash dir has anything in it // (target volume w/ only an empty trash dir is considered // an empty volume) if (find_directory(B_TRASH_DIRECTORY, &trashPath, false, &targetVolume) == B_OK && targetDir.SetTo(trashPath.Path()) == B_OK) { while (targetDir.GetNextRef(&testRef) == B_OK) { // Something in the Trash entries++; break; } } targetDir.SetTo(targetDirectory.Path()); // check if target volume otherwise has any entries while (entries == 0 && targetDir.GetNextRef(&testRef) == B_OK) { if (testPath.SetTo(&testRef) == B_OK && testPath != trashPath) entries++; } if (entries != 0 && ((new BAlert("", TR("The target volume is not empty. Are you sure you " "want to install anyway?\n\nNote: The 'system' folder will be a " "clean copy from the source volume, all other folders will be " "merged, whereas files and links that exist on both the source " "and target volume will be overwritten with the source volume " "version."), TR("Install anyway"), TR("Cancel"), 0, B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go() != 0)) { // TODO: Would be cool to offer the option here to clean additional // folders at the user's choice (like /boot/common and /boot/develop). err = B_CANCELED; goto error; } // Begin actuall installation _LaunchInitScript(targetDirectory); // let the engine collect information for the progress bar later on engine.ResetTargets(); err = engine.CollectTargets(srcDirectory.Path(), fCancelSemaphore); if (err != B_OK) goto error; // collect selected packages also if (fPackages) { BPath pkgRootDir(srcDirectory.Path(), PACKAGES_DIRECTORY); int32 count = fPackages->CountItems(); for (int32 i = 0; i < count; i++) { Package *p = static_cast(fPackages->ItemAt(i)); BPath packageDir(pkgRootDir.Path(), p->Folder()); err = engine.CollectTargets(packageDir.Path(), fCancelSemaphore); if (err != B_OK) goto error; } } // collect information about all zip packages err = _ProcessZipPackages(srcDirectory.Path(), targetDirectory.Path(), &reporter, unzipEngines); if (err != B_OK) goto error; reporter.StartTimer(); // copy source volume err = engine.CopyFolder(srcDirectory.Path(), targetDirectory.Path(), fCancelSemaphore); if (err != B_OK) goto error; // copy selected packages if (fPackages) { BPath pkgRootDir(srcDirectory.Path(), PACKAGES_DIRECTORY); int32 count = fPackages->CountItems(); for (int32 i = 0; i < count; i++) { Package *p = static_cast(fPackages->ItemAt(i)); BPath packageDir(pkgRootDir.Path(), p->Folder()); err = engine.CopyFolder(packageDir.Path(), targetDirectory.Path(), fCancelSemaphore); if (err != B_OK) goto error; } } // Extract all zip packages. If an error occured, delete the rest of // the engines, but stop extracting. for (int32 i = 0; i < unzipEngines.CountItems(); i++) { UnzipEngine* engine = reinterpret_cast( unzipEngines.ItemAtFast(i)); if (err == B_OK) err = engine->UnzipPackage(); delete engine; } if (err != B_OK) goto error; _LaunchFinishScript(targetDirectory); BMessenger(fWindow).SendMessage(MSG_INSTALL_FINISHED); return; error: BMessage statusMessage(MSG_RESET); if (err == B_CANCELED) _SetStatusMessage(TR("Installation canceled.")); else statusMessage.AddInt32("error", err); ERR("_PerformInstall failed"); BMessenger(fWindow).SendMessage(&statusMessage); } status_t WorkerThread::_ProcessZipPackages(const char* sourcePath, const char* targetPath, ProgressReporter* reporter, BList& unzipEngines) { // TODO: Put those in the optional packages list view // TODO: Implement mechanism to handle dependencies between these // packages. (Selecting one will auto-select others.) BPath pkgRootDir(sourcePath, PACKAGES_DIRECTORY); BDirectory directory(pkgRootDir.Path()); BEntry entry; while (directory.GetNextEntry(&entry) == B_OK) { char name[B_FILE_NAME_LENGTH]; if (entry.GetName(name) != B_OK) continue; int nameLength = strlen(name); if (nameLength <= 0) continue; char* nameExtension = name + nameLength - 4; if (strcasecmp(nameExtension, ".zip") != 0) continue; printf("found .zip package: %s\n", name); UnzipEngine* unzipEngine = new(std::nothrow) UnzipEngine(reporter, fCancelSemaphore); if (unzipEngine == NULL || !unzipEngines.AddItem(unzipEngine)) { delete unzipEngine; return B_NO_MEMORY; } BPath path; entry.GetPath(&path); status_t ret = unzipEngine->SetTo(path.Path(), targetPath); if (ret != B_OK) return ret; reporter->AddItems(unzipEngine->ItemsToUncompress(), unzipEngine->BytesToUncompress()); } return B_OK; } void WorkerThread::_SetStatusMessage(const char *status) { BMessage msg(MSG_STATUS_MESSAGE); msg.AddString("status", status); BMessenger(fWindow).SendMessage(&msg); } static void make_partition_label(BPartition* partition, char* label, char* menuLabel, bool showContentType) { char size[15]; string_for_size(partition->Size(), size, sizeof(size)); BPath path; partition->GetPath(&path); if (showContentType) { const char* type = partition->ContentType(); if (type == NULL) type = TR_CMT("Unknown Type", "Partition content type"); sprintf(label, "%s - %s [%s] (%s)", partition->ContentName(), size, path.Path(), type); } else { sprintf(label, "%s - %s [%s]", partition->ContentName(), size, path.Path()); } sprintf(menuLabel, "%s - %s", partition->ContentName(), size); } // #pragma mark - SourceVisitor SourceVisitor::SourceVisitor(BMenu *menu) : fMenu(menu) { } bool SourceVisitor::Visit(BDiskDevice *device) { return Visit(device, 0); } bool SourceVisitor::Visit(BPartition *partition, int32 level) { BPath path; if (partition->GetPath(&path) == B_OK) printf("SourceVisitor::Visit(BPartition *) : %s\n", path.Path()); printf("SourceVisitor::Visit(BPartition *) : %s\n", partition->ContentName()); if (partition->ContentType() == NULL) return false; bool isBootPartition = false; if (partition->IsMounted()) { BPath mountPoint; partition->GetMountPoint(&mountPoint); isBootPartition = strcmp(BOOT_PATH, mountPoint.Path()) == 0; } if (!isBootPartition && strcmp(partition->ContentType(), kPartitionTypeBFS) != 0) { // Except only BFS partitions, except this is the boot partition // (ISO9660 with write overlay for example). return false; } // TODO: We could probably check if this volume contains // the Haiku kernel or something. Does it make sense to "install" // from your BFS volume containing the music collection? // TODO: Then the check for BFS could also be removed above. char label[255]; char menuLabel[255]; make_partition_label(partition, label, menuLabel, false); PartitionMenuItem* item = new PartitionMenuItem(partition->ContentName(), label, menuLabel, new BMessage(SOURCE_PARTITION), partition->ID()); item->SetMarked(isBootPartition); fMenu->AddItem(item); return false; } // #pragma mark - TargetVisitor TargetVisitor::TargetVisitor(BMenu *menu) : fMenu(menu) { } bool TargetVisitor::Visit(BDiskDevice *device) { if (device->IsReadOnlyMedia()) return false; return Visit(device, 0); } bool TargetVisitor::Visit(BPartition *partition, int32 level) { BPath path; if (partition->GetPath(&path) == B_OK) printf("TargetVisitor::Visit(BPartition *) : %s\n", path.Path()); printf("TargetVisitor::Visit(BPartition *) : %s\n", partition->ContentName()); if (partition->ContentSize() < 20 * 1024 * 1024) { // reject partitions which are too small anyway // TODO: Could depend on the source size printf(" too small\n"); return false; } if (partition->CountChildren() > 0) { // Looks like an extended partition, or the device itself. // Do not accept this as target... printf(" no leaf partition\n"); return false; } // TODO: After running DriveSetup and doing another scan, it would // be great to pick the partition which just appeared! // Only BFS partitions are valid targets, but we want to display the // other partitions as well, in order not to irritate the user. bool isValidTarget = partition->ContentType() != NULL && strcmp(partition->ContentType(), kPartitionTypeBFS) == 0; char label[255]; char menuLabel[255]; make_partition_label(partition, label, menuLabel, !isValidTarget); PartitionMenuItem* item = new PartitionMenuItem(partition->ContentName(), label, menuLabel, new BMessage(TARGET_PARTITION), partition->ID()); item->SetIsValidTarget(isValidTarget); fMenu->AddItem(item); return false; }