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