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 31 32 //#define COPY_TRACE 33 #ifdef COPY_TRACE 34 #define CALLED() printf("CALLED %s\n",__PRETTY_FUNCTION__) 35 #define ERR2(x, y...) fprintf(stderr, "WorkerThread: "x" %s\n", y, strerror(err)) 36 #define ERR(x) fprintf(stderr, "WorkerThread: "x" %s\n", strerror(err)) 37 #else 38 #define CALLED() 39 #define ERR(x) 40 #define ERR2(x, y...) 41 #endif 42 43 const char BOOT_PATH[] = "/boot"; 44 45 extern void SizeAsString(off_t size, char* string); 46 47 48 const uint32 MSG_START_INSTALLING = 'eSRT'; 49 50 51 class SourceVisitor : public BDiskDeviceVisitor { 52 public: 53 SourceVisitor(BMenu* menu); 54 virtual bool Visit(BDiskDevice* device); 55 virtual bool Visit(BPartition* partition, int32 level); 56 57 private: 58 BMenu* fMenu; 59 }; 60 61 62 class TargetVisitor : public BDiskDeviceVisitor { 63 public: 64 TargetVisitor(BMenu* menu); 65 virtual bool Visit(BDiskDevice* device); 66 virtual bool Visit(BPartition* partition, int32 level); 67 68 private: 69 void _MakeLabel(BPartition* partition, char* label, char* menuLabel, 70 bool showContentType); 71 72 BMenu* fMenu; 73 }; 74 75 76 // #pragma mark - WorkerThread 77 78 79 WorkerThread::WorkerThread(InstallerWindow *window) 80 : BLooper("copy_engine"), 81 fWindow(window), 82 fPackages(NULL), 83 fSpaceRequired(0) 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 += path.Path(); 217 _SetStatusMessage("Starting Installation."); 218 system(command.String()); 219 } 220 221 222 void 223 WorkerThread::_LaunchFinishScript(BPath &path) 224 { 225 BPath bootPath; 226 find_directory(B_BEOS_BOOT_DIRECTORY, &bootPath); 227 BString command("/bin/sh "); 228 command += bootPath.Path(); 229 command += "/InstallerFinishScript "; 230 command += path.Path(); 231 _SetStatusMessage("Finishing Installation."); 232 system(command.String()); 233 } 234 235 236 void 237 WorkerThread::_PerformInstall(BMenu *srcMenu, BMenu *targetMenu) 238 { 239 CALLED(); 240 241 BPath targetDirectory, srcDirectory; 242 BDirectory targetDir; 243 BDiskDevice device; 244 BPartition *partition; 245 BVolume targetVolume; 246 status_t err = B_OK; 247 int32 entries = 0; 248 entry_ref testRef; 249 250 BMessenger messenger(fWindow); 251 CopyEngine engine(messenger, new BMessage(MSG_STATUS_MESSAGE)); 252 253 PartitionMenuItem *targetItem = (PartitionMenuItem *)targetMenu->FindMarked(); 254 PartitionMenuItem *srcItem = (PartitionMenuItem *)srcMenu->FindMarked(); 255 if (!srcItem || !targetItem) { 256 ERR("bad menu items\n"); 257 goto error; 258 } 259 260 // check if target is initialized 261 // ask if init or mount as is 262 263 if (fDDRoster.GetPartitionWithID(targetItem->ID(), &device, &partition) == B_OK) { 264 if (!partition->IsMounted()) { 265 if ((err = partition->Mount()) < B_OK) { 266 _SetStatusMessage("The disk can't be mounted. Please choose a " 267 "different disk."); 268 ERR("BPartition::Mount"); 269 goto error; 270 } 271 } 272 if ((err = partition->GetVolume(&targetVolume)) != B_OK) { 273 ERR("BPartition::GetVolume"); 274 goto error; 275 } 276 if ((err = partition->GetMountPoint(&targetDirectory)) != B_OK) { 277 ERR("BPartition::GetMountPoint"); 278 goto error; 279 } 280 } else if (fDDRoster.GetDeviceWithID(targetItem->ID(), &device) == B_OK) { 281 if (!device.IsMounted()) { 282 if ((err = device.Mount()) < B_OK) { 283 _SetStatusMessage("The disk can't be mounted. Please choose a " 284 "different disk."); 285 ERR("BDiskDevice::Mount"); 286 goto error; 287 } 288 } 289 if ((err = device.GetVolume(&targetVolume)) != B_OK) { 290 ERR("BDiskDevice::GetVolume"); 291 goto error; 292 } 293 if ((err = device.GetMountPoint(&targetDirectory)) != B_OK) { 294 ERR("BDiskDevice::GetMountPoint"); 295 goto error; 296 } 297 } else 298 goto error; // shouldn't happen 299 300 // check if target has enough space 301 if ((fSpaceRequired > 0 && targetVolume.FreeBytes() < fSpaceRequired) 302 && ((new BAlert("", "The destination disk may not have enough space. " 303 "Try choosing a different disk or choose to not install optional " 304 "items.", "Try installing anyway", "Cancel", 0, 305 B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go() != 0)) { 306 goto error; 307 } 308 309 if (fDDRoster.GetPartitionWithID(srcItem->ID(), &device, &partition) == B_OK) { 310 if ((err = partition->GetMountPoint(&srcDirectory)) != B_OK) { 311 ERR("BPartition::GetMountPoint"); 312 goto error; 313 } 314 } else if (fDDRoster.GetDeviceWithID(srcItem->ID(), &device) == B_OK) { 315 if ((err = device.GetMountPoint(&srcDirectory)) != B_OK) { 316 ERR("BDiskDevice::GetMountPoint"); 317 goto error; 318 } 319 } else 320 goto error; // shouldn't happen 321 322 // check not installing on itself 323 if (strcmp(srcDirectory.Path(), targetDirectory.Path()) == 0) { 324 _SetStatusMessage("You can't install the contents of a disk onto " 325 "itself. Please choose a different disk."); 326 goto error; 327 } 328 329 // check not installing on boot volume 330 if ((strncmp(BOOT_PATH, targetDirectory.Path(), strlen(BOOT_PATH)) == 0) 331 && ((new BAlert("", "Are you sure you want to install onto the " 332 "current boot disk? The Installer will have to reboot your " 333 "machine if you proceed.", "OK", "Cancel", 0, 334 B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go() != 0)) { 335 _SetStatusMessage("Installation stopped."); 336 goto error; 337 } 338 339 targetDir.SetTo(targetDirectory.Path()); 340 341 // check target volume not empty 342 // NOTE: It's ok if exactly this path exists: home/Desktop/Trash 343 // and nothing else. 344 while (targetDir.GetNextRef(&testRef) == B_OK) { 345 if (strcmp(testRef.name, "home") == 0) { 346 BDirectory homeDir(&testRef); 347 while (homeDir.GetNextRef(&testRef) == B_OK) { 348 if (strcmp(testRef.name, "Desktop") == 0) { 349 BDirectory desktopDir(&testRef); 350 while (desktopDir.GetNextRef(&testRef) == B_OK) { 351 if (strcmp(testRef.name, "Trash") == 0) { 352 BDirectory trashDir(&testRef); 353 while (trashDir.GetNextRef(&testRef) == B_OK) { 354 // Something in the Trash 355 entries++; 356 break; 357 } 358 } else { 359 // Something besides Trash 360 entries++; 361 } 362 363 if (entries > 0) 364 break; 365 } 366 } else { 367 // Something besides Desktop 368 entries++; 369 } 370 371 if (entries > 0) 372 break; 373 } 374 } else { 375 // Something besides home 376 entries++; 377 } 378 379 if (entries > 0) 380 break; 381 } 382 if (entries != 0 383 && ((new BAlert("", "The target volume is not empty. Are you sure you " 384 "want to install anyway?", "Install Anyway", "Cancel", 0, 385 B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go() != 0)) { 386 err = B_CANCELED; 387 goto error; 388 } 389 390 391 _LaunchInitScript(targetDirectory); 392 393 // let the engine collect information for the progress bar later on 394 engine.ResetTargets(); 395 err = engine.CollectTargets(srcDirectory.Path()); 396 if (err != B_OK) 397 goto error; 398 399 // collect selected packages also 400 if (fPackages) { 401 BPath pkgRootDir(srcDirectory.Path(), PACKAGES_DIRECTORY); 402 int32 count = fPackages->CountItems(); 403 for (int32 i = 0; i < count; i++) { 404 Package *p = static_cast<Package*>(fPackages->ItemAt(i)); 405 BPath packageDir(pkgRootDir.Path(), p->Folder()); 406 err = engine.CollectTargets(packageDir.Path()); 407 if (err != B_OK) 408 goto error; 409 } 410 } 411 412 // copy source volume 413 err = engine.CopyFolder(srcDirectory.Path(), targetDirectory.Path(), 414 fCancelLock); 415 if (err != B_OK) 416 goto error; 417 418 // copy selected packages 419 if (fPackages) { 420 BPath pkgRootDir(srcDirectory.Path(), PACKAGES_DIRECTORY); 421 int32 count = fPackages->CountItems(); 422 for (int32 i = 0; i < count; i++) { 423 Package *p = static_cast<Package*>(fPackages->ItemAt(i)); 424 BPath packageDir(pkgRootDir.Path(), p->Folder()); 425 err = engine.CopyFolder(packageDir.Path(), targetDirectory.Path(), 426 fCancelLock); 427 if (err != B_OK) 428 goto error; 429 } 430 } 431 432 _LaunchFinishScript(targetDirectory); 433 434 BMessenger(fWindow).SendMessage(MSG_INSTALL_FINISHED); 435 436 return; 437 error: 438 BMessage statusMessage(MSG_RESET); 439 if (err == B_CANCELED) 440 _SetStatusMessage("Installation canceled."); 441 else 442 statusMessage.AddInt32("error", err); 443 ERR("_PerformInstall failed"); 444 BMessenger(fWindow).SendMessage(&statusMessage); 445 } 446 447 448 void 449 WorkerThread::_SetStatusMessage(const char *status) 450 { 451 BMessage msg(MSG_STATUS_MESSAGE); 452 msg.AddString("status", status); 453 BMessenger(fWindow).SendMessage(&msg); 454 } 455 456 457 // #pragma mark - SourceVisitor 458 459 460 SourceVisitor::SourceVisitor(BMenu *menu) 461 : fMenu(menu) 462 { 463 } 464 465 bool 466 SourceVisitor::Visit(BDiskDevice *device) 467 { 468 return Visit(device, 0); 469 } 470 471 472 bool 473 SourceVisitor::Visit(BPartition *partition, int32 level) 474 { 475 BPath path; 476 if (partition->GetPath(&path) == B_OK) 477 printf("SourceVisitor::Visit(BPartition *) : %s\n", path.Path()); 478 printf("SourceVisitor::Visit(BPartition *) : %s\n", partition->ContentName()); 479 480 if (partition->ContentType() == NULL) 481 return false; 482 483 bool isBootPartition = false; 484 if (partition->IsMounted()) { 485 BPath mountPoint; 486 partition->GetMountPoint(&mountPoint); 487 isBootPartition = strcmp(BOOT_PATH, mountPoint.Path()) == 0; 488 } 489 490 if (!isBootPartition 491 && strcmp(partition->ContentType(), kPartitionTypeBFS) != 0) { 492 // Except only BFS partitions, except this is the boot partition 493 // (ISO9660 with write overlay for example). 494 return false; 495 } 496 497 // TODO: We could probably check if this volume contains 498 // the Haiku kernel or something. Does it make sense to "install" 499 // from your BFS volume containing the music collection? 500 // TODO: Then the check for BFS could also be removed above. 501 502 PartitionMenuItem *item = new PartitionMenuItem(NULL, 503 partition->ContentName(), NULL, new BMessage(SRC_PARTITION), 504 partition->ID()); 505 item->SetMarked(isBootPartition); 506 fMenu->AddItem(item); 507 return false; 508 } 509 510 511 // #pragma mark - TargetVisitor 512 513 514 TargetVisitor::TargetVisitor(BMenu *menu) 515 : fMenu(menu) 516 { 517 } 518 519 520 bool 521 TargetVisitor::Visit(BDiskDevice *device) 522 { 523 if (device->IsReadOnlyMedia()) 524 return false; 525 return Visit(device, 0); 526 } 527 528 529 bool 530 TargetVisitor::Visit(BPartition *partition, int32 level) 531 { 532 BPath path; 533 if (partition->GetPath(&path) == B_OK) 534 printf("TargetVisitor::Visit(BPartition *) : %s\n", path.Path()); 535 printf("TargetVisitor::Visit(BPartition *) : %s\n", partition->ContentName()); 536 537 if (partition->ContentSize() < 20 * 1024 * 1024) { 538 // reject partitions which are too small anyway 539 // TODO: Could depend on the source size 540 printf(" too small\n"); 541 return false; 542 } 543 544 if (partition->CountChildren() > 0) { 545 // Looks like an extended partition, or the device itself. 546 // Do not accept this as target... 547 printf(" no leaf partition\n"); 548 return false; 549 } 550 551 // TODO: After running DriveSetup and doing another scan, it would 552 // be great to pick the partition which just appeared! 553 554 // Only BFS partitions are valid targets, but we want to display the 555 // other partitions as well, in order not to irritate the user. 556 bool isValidTarget = partition->ContentType() != NULL 557 && strcmp(partition->ContentType(), kPartitionTypeBFS) == 0; 558 559 char label[255], menuLabel[255]; 560 _MakeLabel(partition, label, menuLabel, !isValidTarget); 561 PartitionMenuItem* item = new PartitionMenuItem(partition->ContentName(), 562 label, menuLabel, new BMessage(TARGET_PARTITION), partition->ID()); 563 564 item->SetIsValidTarget(isValidTarget); 565 566 567 fMenu->AddItem(item); 568 return false; 569 } 570 571 572 void 573 TargetVisitor::_MakeLabel(BPartition* partition, char* label, char* menuLabel, 574 bool showContentType) 575 { 576 char size[15]; 577 SizeAsString(partition->ContentSize(), size); 578 579 BPath path; 580 partition->GetPath(&path); 581 582 if (showContentType) { 583 const char* type = partition->ContentType(); 584 if (type == NULL) 585 type = "Unknown Type"; 586 587 sprintf(label, "%s - %s [%s] (%s)", partition->ContentName(), size, 588 path.Path(), type); 589 } else { 590 sprintf(label, "%s - %s [%s]", partition->ContentName(), size, 591 path.Path()); 592 } 593 594 sprintf(menuLabel, "%s - %s", partition->ContentName(), size); 595 } 596 597