xref: /haiku/src/apps/installer/WorkerThread.cpp (revision 37c7d5d83a2372a6971e383411d5bacbeef0ebdc)
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