1 /* 2 * Copyright 2009, Stephan Aßmus <superstippi@gmx.de>. 3 * Copyright 2005-2008, Jérôme DUVAL. 4 * All rights reserved. Distributed under the terms of the MIT License. 5 */ 6 7 #include "WorkerThread.h" 8 9 #include <stdio.h> 10 11 #include <Alert.h> 12 #include <Autolock.h> 13 #include <Catalog.h> 14 #include <Directory.h> 15 #include <DiskDeviceVisitor.h> 16 #include <DiskDeviceTypes.h> 17 #include <FindDirectory.h> 18 #include <Menu.h> 19 #include <MenuItem.h> 20 #include <Message.h> 21 #include <Messenger.h> 22 #include <Path.h> 23 #include <String.h> 24 #include <VolumeRoster.h> 25 26 #include "AutoLocker.h" 27 #include "CopyEngine.h" 28 #include "InstallerWindow.h" 29 #include "PackageViews.h" 30 #include "PartitionMenuItem.h" 31 #include "ProgressReporter.h" 32 #include "StringForSize.h" 33 #include "UnzipEngine.h" 34 35 36 #define TR_CONTEXT "InstallProgress" 37 38 39 //#define COPY_TRACE 40 #ifdef COPY_TRACE 41 #define CALLED() printf("CALLED %s\n",__PRETTY_FUNCTION__) 42 #define ERR2(x, y...) fprintf(stderr, "WorkerThread: "x" %s\n", y, strerror(err)) 43 #define ERR(x) fprintf(stderr, "WorkerThread: "x" %s\n", strerror(err)) 44 #else 45 #define CALLED() 46 #define ERR(x) 47 #define ERR2(x, y...) 48 #endif 49 50 const char BOOT_PATH[] = "/boot"; 51 52 const uint32 MSG_START_INSTALLING = 'eSRT'; 53 54 55 class SourceVisitor : public BDiskDeviceVisitor { 56 public: 57 SourceVisitor(BMenu* menu); 58 virtual bool Visit(BDiskDevice* device); 59 virtual bool Visit(BPartition* partition, int32 level); 60 61 private: 62 BMenu* fMenu; 63 }; 64 65 66 class TargetVisitor : public BDiskDeviceVisitor { 67 public: 68 TargetVisitor(BMenu* menu); 69 virtual bool Visit(BDiskDevice* device); 70 virtual bool Visit(BPartition* partition, int32 level); 71 72 private: 73 BMenu* fMenu; 74 }; 75 76 77 // #pragma mark - WorkerThread 78 79 80 WorkerThread::WorkerThread(InstallerWindow *window) 81 : 82 BLooper("copy_engine"), 83 fWindow(window), 84 fPackages(NULL), 85 fSpaceRequired(0), 86 fCancelSemaphore(-1) 87 { 88 Run(); 89 } 90 91 92 void 93 WorkerThread::MessageReceived(BMessage* message) 94 { 95 CALLED(); 96 97 switch (message->what) { 98 case MSG_START_INSTALLING: 99 _PerformInstall(fWindow->GetSourceMenu(), fWindow->GetTargetMenu()); 100 break; 101 102 case MSG_WRITE_BOOT_SECTOR: 103 { 104 int32 id; 105 if (message->FindInt32("id", &id) != B_OK) { 106 _SetStatusMessage(TR("Boot sector not written because of an " 107 " internal error.")); 108 break; 109 } 110 111 // TODO: Refactor with _PerformInstall() 112 BPath targetDirectory; 113 BDiskDevice device; 114 BPartition* partition; 115 116 if (fDDRoster.GetPartitionWithID(id, &device, &partition) == B_OK) { 117 if (!partition->IsMounted()) { 118 if (partition->Mount() < B_OK) { 119 _SetStatusMessage(TR("The partition can't be mounted. " 120 "Please choose a different partition.")); 121 break; 122 } 123 } 124 if (partition->GetMountPoint(&targetDirectory) != B_OK) { 125 _SetStatusMessage(TR("The mount point could not be " 126 "retrieved.")); 127 break; 128 } 129 } else if (fDDRoster.GetDeviceWithID(id, &device) == B_OK) { 130 if (!device.IsMounted()) { 131 if (device.Mount() < B_OK) { 132 _SetStatusMessage(TR("The disk can't be mounted. " 133 "Please choose a different disk.")); 134 break; 135 } 136 } 137 if (device.GetMountPoint(&targetDirectory) != B_OK) { 138 _SetStatusMessage(TR("The mount point could not be " 139 "retrieved.")); 140 break; 141 } 142 } 143 144 _LaunchFinishScript(targetDirectory); 145 // TODO: Get error from executing script! 146 _SetStatusMessage(TR("Boot sector successfully written.")); 147 } 148 default: 149 BLooper::MessageReceived(message); 150 } 151 } 152 153 154 155 156 void 157 WorkerThread::ScanDisksPartitions(BMenu *srcMenu, BMenu *targetMenu) 158 { 159 // NOTE: This is actually executed in the window thread. 160 BDiskDevice device; 161 BPartition *partition = NULL; 162 163 printf("\nScanDisksPartitions source partitions begin\n"); 164 SourceVisitor srcVisitor(srcMenu); 165 fDDRoster.VisitEachMountedPartition(&srcVisitor, &device, &partition); 166 167 printf("\nScanDisksPartitions target partitions begin\n"); 168 TargetVisitor targetVisitor(targetMenu); 169 fDDRoster.VisitEachPartition(&targetVisitor, &device, &partition); 170 } 171 172 173 void 174 WorkerThread::SetPackagesList(BList *list) 175 { 176 // Executed in window thread. 177 BAutolock _(this); 178 179 delete fPackages; 180 fPackages = list; 181 } 182 183 184 void 185 WorkerThread::StartInstall() 186 { 187 // Executed in window thread. 188 PostMessage(MSG_START_INSTALLING, this); 189 } 190 191 192 void 193 WorkerThread::WriteBootSector(BMenu* targetMenu) 194 { 195 // Executed in window thread. 196 CALLED(); 197 198 PartitionMenuItem* item = (PartitionMenuItem*)targetMenu->FindMarked(); 199 if (item == NULL) { 200 ERR("bad menu items\n"); 201 return; 202 } 203 204 BMessage message(MSG_WRITE_BOOT_SECTOR); 205 message.AddInt32("id", item->ID()); 206 PostMessage(&message, this); 207 } 208 209 210 // #pragma mark - 211 212 213 void 214 WorkerThread::_LaunchInitScript(BPath &path) 215 { 216 BPath bootPath; 217 find_directory(B_BEOS_BOOT_DIRECTORY, &bootPath); 218 BString command("/bin/sh "); 219 command += bootPath.Path(); 220 command += "/InstallerInitScript "; 221 command += "\""; 222 command += path.Path(); 223 command += "\""; 224 _SetStatusMessage(TR("Starting Installation.")); 225 system(command.String()); 226 } 227 228 229 void 230 WorkerThread::_LaunchFinishScript(BPath &path) 231 { 232 BPath bootPath; 233 find_directory(B_BEOS_BOOT_DIRECTORY, &bootPath); 234 BString command("/bin/sh "); 235 command += bootPath.Path(); 236 command += "/InstallerFinishScript "; 237 command += "\""; 238 command += path.Path(); 239 command += "\""; 240 _SetStatusMessage(TR("Finishing Installation.")); 241 system(command.String()); 242 } 243 244 245 void 246 WorkerThread::_PerformInstall(BMenu* srcMenu, BMenu* targetMenu) 247 { 248 CALLED(); 249 250 BPath targetDirectory, srcDirectory, trashPath, testPath; 251 BDirectory targetDir; 252 BDiskDevice device; 253 BPartition* partition; 254 BVolume targetVolume; 255 status_t err = B_OK; 256 int32 entries = 0; 257 entry_ref testRef; 258 const char* mountError = TR("The disk can't be mounted. Please choose " 259 "a different disk."); 260 261 BMessenger messenger(fWindow); 262 ProgressReporter reporter(messenger, new BMessage(MSG_STATUS_MESSAGE)); 263 CopyEngine engine(&reporter); 264 BList unzipEngines; 265 266 PartitionMenuItem* targetItem = (PartitionMenuItem*)targetMenu->FindMarked(); 267 PartitionMenuItem* srcItem = (PartitionMenuItem*)srcMenu->FindMarked(); 268 if (!srcItem || !targetItem) { 269 ERR("bad menu items\n"); 270 goto error; 271 } 272 273 // check if target is initialized 274 // ask if init or mount as is 275 if (fDDRoster.GetPartitionWithID(targetItem->ID(), &device, 276 &partition) == B_OK) { 277 if (!partition->IsMounted()) { 278 if ((err = partition->Mount()) < B_OK) { 279 _SetStatusMessage(mountError); 280 ERR("BPartition::Mount"); 281 goto error; 282 } 283 } 284 if ((err = partition->GetVolume(&targetVolume)) != B_OK) { 285 ERR("BPartition::GetVolume"); 286 goto error; 287 } 288 if ((err = partition->GetMountPoint(&targetDirectory)) != B_OK) { 289 ERR("BPartition::GetMountPoint"); 290 goto error; 291 } 292 } else if (fDDRoster.GetDeviceWithID(targetItem->ID(), &device) == B_OK) { 293 if (!device.IsMounted()) { 294 if ((err = device.Mount()) < B_OK) { 295 _SetStatusMessage(mountError); 296 ERR("BDiskDevice::Mount"); 297 goto error; 298 } 299 } 300 if ((err = device.GetVolume(&targetVolume)) != B_OK) { 301 ERR("BDiskDevice::GetVolume"); 302 goto error; 303 } 304 if ((err = device.GetMountPoint(&targetDirectory)) != B_OK) { 305 ERR("BDiskDevice::GetMountPoint"); 306 goto error; 307 } 308 } else 309 goto error; // shouldn't happen 310 311 // check if target has enough space 312 if ((fSpaceRequired > 0 && targetVolume.FreeBytes() < fSpaceRequired) 313 && ((new BAlert("", TR("The destination disk may not have enough " 314 "space. Try choosing a different disk or choose to not install " 315 "optional items."), TR("Try installing anyway"), TR("Cancel"), 0, 316 B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go() != 0)) { 317 goto error; 318 } 319 320 if (fDDRoster.GetPartitionWithID(srcItem->ID(), &device, &partition) == B_OK) { 321 if ((err = partition->GetMountPoint(&srcDirectory)) != B_OK) { 322 ERR("BPartition::GetMountPoint"); 323 goto error; 324 } 325 } else if (fDDRoster.GetDeviceWithID(srcItem->ID(), &device) == B_OK) { 326 if ((err = device.GetMountPoint(&srcDirectory)) != B_OK) { 327 ERR("BDiskDevice::GetMountPoint"); 328 goto error; 329 } 330 } else 331 goto error; // shouldn't happen 332 333 // check not installing on itself 334 if (strcmp(srcDirectory.Path(), targetDirectory.Path()) == 0) { 335 _SetStatusMessage(TR("You can't install the contents of a disk onto " 336 "itself. Please choose a different disk.")); 337 goto error; 338 } 339 340 // check not installing on boot volume 341 if ((strncmp(BOOT_PATH, targetDirectory.Path(), strlen(BOOT_PATH)) == 0) 342 && ((new BAlert("", TR("Are you sure you want to install onto the " 343 "current boot disk? The Installer will have to reboot your " 344 "machine if you proceed."), TR("OK"), TR("Cancel"), 0, 345 B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go() != 0)) { 346 _SetStatusMessage("Installation stopped."); 347 goto error; 348 } 349 350 // check if target volume's trash dir has anything in it 351 // (target volume w/ only an empty trash dir is considered 352 // an empty volume) 353 if (find_directory(B_TRASH_DIRECTORY, &trashPath, false, 354 &targetVolume) == B_OK && targetDir.SetTo(trashPath.Path()) == B_OK) { 355 while (targetDir.GetNextRef(&testRef) == B_OK) { 356 // Something in the Trash 357 entries++; 358 break; 359 } 360 } 361 362 targetDir.SetTo(targetDirectory.Path()); 363 364 // check if target volume otherwise has any entries 365 while (entries == 0 && targetDir.GetNextRef(&testRef) == B_OK) { 366 if (testPath.SetTo(&testRef) == B_OK && testPath != trashPath) 367 entries++; 368 } 369 370 if (entries != 0 371 && ((new BAlert("", TR("The target volume is not empty. Are you sure you " 372 "want to install anyway?\n\nNote: The 'system' folder will be a " 373 "clean copy from the source volume, all other folders will be " 374 "merged, whereas files and links that exist on both the source " 375 "and target volume will be overwritten with the source volume " 376 "version."), 377 TR("Install anyway"), TR("Cancel"), 0, 378 B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go() != 0)) { 379 // TODO: Would be cool to offer the option here to clean additional 380 // folders at the user's choice (like /boot/common and /boot/develop). 381 err = B_CANCELED; 382 goto error; 383 } 384 385 // Begin actuall installation 386 387 _LaunchInitScript(targetDirectory); 388 389 // let the engine collect information for the progress bar later on 390 engine.ResetTargets(); 391 err = engine.CollectTargets(srcDirectory.Path(), fCancelSemaphore); 392 if (err != B_OK) 393 goto error; 394 395 // collect selected packages also 396 if (fPackages) { 397 BPath pkgRootDir(srcDirectory.Path(), PACKAGES_DIRECTORY); 398 int32 count = fPackages->CountItems(); 399 for (int32 i = 0; i < count; i++) { 400 Package *p = static_cast<Package*>(fPackages->ItemAt(i)); 401 BPath packageDir(pkgRootDir.Path(), p->Folder()); 402 err = engine.CollectTargets(packageDir.Path(), fCancelSemaphore); 403 if (err != B_OK) 404 goto error; 405 } 406 } 407 408 // collect information about all zip packages 409 err = _ProcessZipPackages(srcDirectory.Path(), targetDirectory.Path(), 410 &reporter, unzipEngines); 411 if (err != B_OK) 412 goto error; 413 414 reporter.StartTimer(); 415 416 // copy source volume 417 err = engine.CopyFolder(srcDirectory.Path(), targetDirectory.Path(), 418 fCancelSemaphore); 419 if (err != B_OK) 420 goto error; 421 422 // copy selected packages 423 if (fPackages) { 424 BPath pkgRootDir(srcDirectory.Path(), PACKAGES_DIRECTORY); 425 int32 count = fPackages->CountItems(); 426 for (int32 i = 0; i < count; i++) { 427 Package *p = static_cast<Package*>(fPackages->ItemAt(i)); 428 BPath packageDir(pkgRootDir.Path(), p->Folder()); 429 err = engine.CopyFolder(packageDir.Path(), targetDirectory.Path(), 430 fCancelSemaphore); 431 if (err != B_OK) 432 goto error; 433 } 434 } 435 436 // Extract all zip packages. If an error occured, delete the rest of 437 // the engines, but stop extracting. 438 for (int32 i = 0; i < unzipEngines.CountItems(); i++) { 439 UnzipEngine* engine = reinterpret_cast<UnzipEngine*>( 440 unzipEngines.ItemAtFast(i)); 441 if (err == B_OK) 442 err = engine->UnzipPackage(); 443 delete engine; 444 } 445 if (err != B_OK) 446 goto error; 447 448 _LaunchFinishScript(targetDirectory); 449 450 BMessenger(fWindow).SendMessage(MSG_INSTALL_FINISHED); 451 452 return; 453 error: 454 BMessage statusMessage(MSG_RESET); 455 if (err == B_CANCELED) 456 _SetStatusMessage(TR("Installation canceled.")); 457 else 458 statusMessage.AddInt32("error", err); 459 ERR("_PerformInstall failed"); 460 BMessenger(fWindow).SendMessage(&statusMessage); 461 } 462 463 464 status_t 465 WorkerThread::_ProcessZipPackages(const char* sourcePath, 466 const char* targetPath, ProgressReporter* reporter, BList& unzipEngines) 467 { 468 // TODO: Put those in the optional packages list view 469 // TODO: Implement mechanism to handle dependencies between these 470 // packages. (Selecting one will auto-select others.) 471 BPath pkgRootDir(sourcePath, PACKAGES_DIRECTORY); 472 BDirectory directory(pkgRootDir.Path()); 473 BEntry entry; 474 while (directory.GetNextEntry(&entry) == B_OK) { 475 char name[B_FILE_NAME_LENGTH]; 476 if (entry.GetName(name) != B_OK) 477 continue; 478 int nameLength = strlen(name); 479 if (nameLength <= 0) 480 continue; 481 char* nameExtension = name + nameLength - 4; 482 if (strcasecmp(nameExtension, ".zip") != 0) 483 continue; 484 printf("found .zip package: %s\n", name); 485 486 UnzipEngine* unzipEngine = new(std::nothrow) UnzipEngine(reporter, 487 fCancelSemaphore); 488 if (unzipEngine == NULL || !unzipEngines.AddItem(unzipEngine)) { 489 delete unzipEngine; 490 return B_NO_MEMORY; 491 } 492 BPath path; 493 entry.GetPath(&path); 494 status_t ret = unzipEngine->SetTo(path.Path(), targetPath); 495 if (ret != B_OK) 496 return ret; 497 498 reporter->AddItems(unzipEngine->ItemsToUncompress(), 499 unzipEngine->BytesToUncompress()); 500 } 501 502 return B_OK; 503 } 504 505 506 void 507 WorkerThread::_SetStatusMessage(const char *status) 508 { 509 BMessage msg(MSG_STATUS_MESSAGE); 510 msg.AddString("status", status); 511 BMessenger(fWindow).SendMessage(&msg); 512 } 513 514 515 static void 516 make_partition_label(BPartition* partition, char* label, char* menuLabel, 517 bool showContentType) 518 { 519 char size[15]; 520 string_for_size(partition->Size(), size, sizeof(size)); 521 522 BPath path; 523 partition->GetPath(&path); 524 525 if (showContentType) { 526 const char* type = partition->ContentType(); 527 if (type == NULL) 528 type = TR_CMT("Unknown Type", "Partition content type"); 529 530 sprintf(label, "%s - %s [%s] (%s)", partition->ContentName(), size, 531 path.Path(), type); 532 } else { 533 sprintf(label, "%s - %s [%s]", partition->ContentName(), size, 534 path.Path()); 535 } 536 537 sprintf(menuLabel, "%s - %s", partition->ContentName(), size); 538 } 539 540 541 // #pragma mark - SourceVisitor 542 543 544 SourceVisitor::SourceVisitor(BMenu *menu) 545 : fMenu(menu) 546 { 547 } 548 549 bool 550 SourceVisitor::Visit(BDiskDevice *device) 551 { 552 return Visit(device, 0); 553 } 554 555 556 bool 557 SourceVisitor::Visit(BPartition *partition, int32 level) 558 { 559 BPath path; 560 if (partition->GetPath(&path) == B_OK) 561 printf("SourceVisitor::Visit(BPartition *) : %s\n", path.Path()); 562 printf("SourceVisitor::Visit(BPartition *) : %s\n", 563 partition->ContentName()); 564 565 if (partition->ContentType() == NULL) 566 return false; 567 568 bool isBootPartition = false; 569 if (partition->IsMounted()) { 570 BPath mountPoint; 571 partition->GetMountPoint(&mountPoint); 572 isBootPartition = strcmp(BOOT_PATH, mountPoint.Path()) == 0; 573 } 574 575 if (!isBootPartition 576 && strcmp(partition->ContentType(), kPartitionTypeBFS) != 0) { 577 // Except only BFS partitions, except this is the boot partition 578 // (ISO9660 with write overlay for example). 579 return false; 580 } 581 582 // TODO: We could probably check if this volume contains 583 // the Haiku kernel or something. Does it make sense to "install" 584 // from your BFS volume containing the music collection? 585 // TODO: Then the check for BFS could also be removed above. 586 587 char label[255]; 588 char menuLabel[255]; 589 make_partition_label(partition, label, menuLabel, false); 590 PartitionMenuItem* item = new PartitionMenuItem(partition->ContentName(), 591 label, menuLabel, new BMessage(SOURCE_PARTITION), partition->ID()); 592 item->SetMarked(isBootPartition); 593 fMenu->AddItem(item); 594 return false; 595 } 596 597 598 // #pragma mark - TargetVisitor 599 600 601 TargetVisitor::TargetVisitor(BMenu *menu) 602 : fMenu(menu) 603 { 604 } 605 606 607 bool 608 TargetVisitor::Visit(BDiskDevice *device) 609 { 610 if (device->IsReadOnlyMedia()) 611 return false; 612 return Visit(device, 0); 613 } 614 615 616 bool 617 TargetVisitor::Visit(BPartition *partition, int32 level) 618 { 619 BPath path; 620 if (partition->GetPath(&path) == B_OK) 621 printf("TargetVisitor::Visit(BPartition *) : %s\n", path.Path()); 622 printf("TargetVisitor::Visit(BPartition *) : %s\n", 623 partition->ContentName()); 624 625 if (partition->ContentSize() < 20 * 1024 * 1024) { 626 // reject partitions which are too small anyway 627 // TODO: Could depend on the source size 628 printf(" too small\n"); 629 return false; 630 } 631 632 if (partition->CountChildren() > 0) { 633 // Looks like an extended partition, or the device itself. 634 // Do not accept this as target... 635 printf(" no leaf partition\n"); 636 return false; 637 } 638 639 // TODO: After running DriveSetup and doing another scan, it would 640 // be great to pick the partition which just appeared! 641 642 // Only BFS partitions are valid targets, but we want to display the 643 // other partitions as well, in order not to irritate the user. 644 bool isValidTarget = partition->ContentType() != NULL 645 && strcmp(partition->ContentType(), kPartitionTypeBFS) == 0; 646 647 char label[255]; 648 char menuLabel[255]; 649 make_partition_label(partition, label, menuLabel, !isValidTarget); 650 PartitionMenuItem* item = new PartitionMenuItem(partition->ContentName(), 651 label, menuLabel, new BMessage(TARGET_PARTITION), partition->ID()); 652 653 item->SetIsValidTarget(isValidTarget); 654 655 656 fMenu->AddItem(item); 657 return false; 658 } 659 660