xref: /haiku/src/apps/installer/WorkerThread.cpp (revision 991dadd6324f7b7a68e94743a39ebae789823228)
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 <errno.h>
10 #include <stdio.h>
11 
12 #include <set>
13 #include <string>
14 
15 #include <Alert.h>
16 #include <Autolock.h>
17 #include <Catalog.h>
18 #include <Directory.h>
19 #include <DiskDeviceVisitor.h>
20 #include <DiskDeviceTypes.h>
21 #include <FindDirectory.h>
22 #include <fs_index.h>
23 #include <Locale.h>
24 #include <Menu.h>
25 #include <MenuItem.h>
26 #include <Message.h>
27 #include <Messenger.h>
28 #include <Path.h>
29 #include <String.h>
30 #include <VolumeRoster.h>
31 
32 #include "AutoLocker.h"
33 #include "CopyEngine.h"
34 #include "InstallerDefs.h"
35 #include "PackageViews.h"
36 #include "PartitionMenuItem.h"
37 #include "ProgressReporter.h"
38 #include "StringForSize.h"
39 #include "UnzipEngine.h"
40 
41 
42 #define B_TRANSLATION_CONTEXT "InstallProgress"
43 
44 
45 //#define COPY_TRACE
46 #ifdef COPY_TRACE
47 #define CALLED() 		printf("CALLED %s\n",__PRETTY_FUNCTION__)
48 #define ERR2(x, y...)	fprintf(stderr, "WorkerThread: "x" %s\n", y, strerror(err))
49 #define ERR(x)			fprintf(stderr, "WorkerThread: "x" %s\n", strerror(err))
50 #else
51 #define CALLED()
52 #define ERR(x)
53 #define ERR2(x, y...)
54 #endif
55 
56 const char BOOT_PATH[] = "/boot";
57 
58 const uint32 MSG_START_INSTALLING = 'eSRT';
59 
60 
61 class SourceVisitor : public BDiskDeviceVisitor {
62 public:
63 	SourceVisitor(BMenu* menu);
64 	virtual bool Visit(BDiskDevice* device);
65 	virtual bool Visit(BPartition* partition, int32 level);
66 
67 private:
68 	BMenu* fMenu;
69 };
70 
71 
72 class TargetVisitor : public BDiskDeviceVisitor {
73 public:
74 	TargetVisitor(BMenu* menu);
75 	virtual bool Visit(BDiskDevice* device);
76 	virtual bool Visit(BPartition* partition, int32 level);
77 
78 private:
79 	BMenu* fMenu;
80 };
81 
82 
83 // #pragma mark - WorkerThread
84 
85 
86 class WorkerThread::EntryFilter : public CopyEngine::EntryFilter {
87 public:
88 	EntryFilter(const char* sourceDirectory)
89 		:
90 		fIgnorePaths(),
91 		fSourceDevice(-1)
92 	{
93 		try {
94 			fIgnorePaths.insert(kPackagesDirectoryPath);
95 			fIgnorePaths.insert(kSourcesDirectoryPath);
96 			fIgnorePaths.insert("rr_moved");
97 			fIgnorePaths.insert("boot.catalog");
98 			fIgnorePaths.insert("haiku-boot-floppy.image");
99 			fIgnorePaths.insert("system/var/swap");
100 			fIgnorePaths.insert("system/var/shared_memory");
101 
102 			fPackageFSRootPaths.insert("system");
103 			fPackageFSRootPaths.insert("home/config");
104 		} catch (std::bad_alloc&) {
105 		}
106 
107 		struct stat st;
108 		if (stat(sourceDirectory, &st) == 0)
109 			fSourceDevice = st.st_dev;
110 	}
111 
112 	virtual bool ShouldCopyEntry(const BEntry& entry, const char* path,
113 		const struct stat& statInfo, int32 level) const
114 	{
115 		if (fIgnorePaths.find(path) != fIgnorePaths.end()) {
116 			printf("ignoring '%s'.\n", path);
117 			return false;
118 		}
119 
120 		if (statInfo.st_dev != fSourceDevice) {
121 			// Allow that only for the root of the packagefs mounts, since
122 			// those contain directories that shine through from the
123 			// underlying volume.
124 			if (fPackageFSRootPaths.find(path) == fPackageFSRootPaths.end())
125 				return false;
126 		}
127 
128 		return true;
129 	}
130 
131 	virtual bool ShouldClobberFolder(const BEntry& entry, const char* path,
132 		const struct stat& statInfo, int32 level) const
133 	{
134 		if (level == 1 && S_ISDIR(statInfo.st_mode)) {
135 			if (strcmp("system", path) == 0) {
136 				printf("clobbering '%s'.\n", path);
137 				return true;
138 			}
139 		}
140 		return false;
141 	}
142 
143 private:
144 	std::set<std::string>	fIgnorePaths;
145 	std::set<std::string>	fPackageFSRootPaths;
146 	dev_t					fSourceDevice;
147 };
148 
149 
150 // #pragma mark - WorkerThread
151 
152 
153 WorkerThread::WorkerThread(const BMessenger& owner)
154 	:
155 	BLooper("copy_engine"),
156 	fOwner(owner),
157 	fPackages(NULL),
158 	fSpaceRequired(0),
159 	fCancelSemaphore(-1)
160 {
161 	Run();
162 }
163 
164 
165 void
166 WorkerThread::MessageReceived(BMessage* message)
167 {
168 	CALLED();
169 
170 	switch (message->what) {
171 		case MSG_START_INSTALLING:
172 			_PerformInstall(message->GetInt32("source", -1),
173 				message->GetInt32("target", -1));
174 			break;
175 
176 		case MSG_WRITE_BOOT_SECTOR:
177 		{
178 			int32 id;
179 			if (message->FindInt32("id", &id) != B_OK) {
180 				_SetStatusMessage(B_TRANSLATE("Boot sector not written "
181 					"because of an internal error."));
182 				break;
183 			}
184 
185 			// TODO: Refactor with _PerformInstall()
186 			BPath targetDirectory;
187 			BDiskDevice device;
188 			BPartition* partition;
189 
190 			if (fDDRoster.GetPartitionWithID(id, &device, &partition) == B_OK) {
191 				if (!partition->IsMounted()) {
192 					if (partition->Mount() < B_OK) {
193 						_SetStatusMessage(B_TRANSLATE("The partition can't be "
194 							"mounted. Please choose a different partition."));
195 						break;
196 					}
197 				}
198 				if (partition->GetMountPoint(&targetDirectory) != B_OK) {
199 					_SetStatusMessage(B_TRANSLATE("The mount point could not "
200 						"be retrieved."));
201 					break;
202 				}
203 			} else if (fDDRoster.GetDeviceWithID(id, &device) == B_OK) {
204 				if (!device.IsMounted()) {
205 					if (device.Mount() < B_OK) {
206 						_SetStatusMessage(B_TRANSLATE("The disk can't be "
207 							"mounted. Please choose a different disk."));
208 						break;
209 					}
210 				}
211 				if (device.GetMountPoint(&targetDirectory) != B_OK) {
212 					_SetStatusMessage(B_TRANSLATE("The mount point could not "
213 						"be retrieved."));
214 					break;
215 				}
216 			}
217 
218 			_LaunchFinishScript(targetDirectory);
219 			// TODO: Get error from executing script!
220 			_SetStatusMessage(
221 				B_TRANSLATE("Boot sector successfully written."));
222 		}
223 		default:
224 			BLooper::MessageReceived(message);
225 	}
226 }
227 
228 
229 
230 
231 void
232 WorkerThread::ScanDisksPartitions(BMenu *srcMenu, BMenu *targetMenu)
233 {
234 	// NOTE: This is actually executed in the window thread.
235 	BDiskDevice device;
236 	BPartition *partition = NULL;
237 
238 	printf("\nScanDisksPartitions source partitions begin\n");
239 	SourceVisitor srcVisitor(srcMenu);
240 	fDDRoster.VisitEachMountedPartition(&srcVisitor, &device, &partition);
241 
242 	printf("\nScanDisksPartitions target partitions begin\n");
243 	TargetVisitor targetVisitor(targetMenu);
244 	fDDRoster.VisitEachPartition(&targetVisitor, &device, &partition);
245 }
246 
247 
248 void
249 WorkerThread::SetPackagesList(BList *list)
250 {
251 	// Executed in window thread.
252 	BAutolock _(this);
253 
254 	delete fPackages;
255 	fPackages = list;
256 }
257 
258 
259 void
260 WorkerThread::StartInstall(partition_id sourcePartitionID,
261 	partition_id targetPartitionID)
262 {
263 	// Executed in window thread.
264 	BMessage message(MSG_START_INSTALLING);
265 	message.AddInt32("source", sourcePartitionID);
266 	message.AddInt32("target", targetPartitionID);
267 
268 	PostMessage(&message, this);
269 }
270 
271 
272 void
273 WorkerThread::WriteBootSector(BMenu* targetMenu)
274 {
275 	// Executed in window thread.
276 	CALLED();
277 
278 	PartitionMenuItem* item = (PartitionMenuItem*)targetMenu->FindMarked();
279 	if (item == NULL) {
280 		ERR("bad menu items\n");
281 		return;
282 	}
283 
284 	BMessage message(MSG_WRITE_BOOT_SECTOR);
285 	message.AddInt32("id", item->ID());
286 	PostMessage(&message, this);
287 }
288 
289 
290 // #pragma mark -
291 
292 
293 void
294 WorkerThread::_LaunchInitScript(BPath &path)
295 {
296 	BPath bootPath;
297 	find_directory(B_BEOS_BOOT_DIRECTORY, &bootPath);
298 	BString command("/bin/sh ");
299 	command += bootPath.Path();
300 	command += "/InstallerInitScript ";
301 	command += "\"";
302 	command += path.Path();
303 	command += "\"";
304 	_SetStatusMessage(B_TRANSLATE("Starting Installation."));
305 	system(command.String());
306 }
307 
308 
309 void
310 WorkerThread::_LaunchFinishScript(BPath &path)
311 {
312 	BPath bootPath;
313 	find_directory(B_BEOS_BOOT_DIRECTORY, &bootPath);
314 	BString command("/bin/sh ");
315 	command += bootPath.Path();
316 	command += "/InstallerFinishScript ";
317 	command += "\"";
318 	command += path.Path();
319 	command += "\"";
320 	_SetStatusMessage(B_TRANSLATE("Finishing Installation."));
321 	system(command.String());
322 }
323 
324 
325 status_t
326 WorkerThread::_PerformInstall(partition_id sourcePartitionID,
327 	partition_id targetPartitionID)
328 {
329 	CALLED();
330 
331 	BPath targetDirectory;
332 	BPath srcDirectory;
333 	BPath trashPath;
334 	BPath testPath;
335 	BDirectory targetDir;
336 	BDiskDevice device;
337 	BPartition* partition;
338 	BVolume targetVolume;
339 	status_t err = B_OK;
340 	int32 entries = 0;
341 	entry_ref testRef;
342 	const char* mountError = B_TRANSLATE("The disk can't be mounted. Please "
343 		"choose a different disk.");
344 
345 	if (sourcePartitionID < 0 || targetPartitionID < 0) {
346 		ERR("bad source or target partition ID\n");
347 		return _InstallationError(err);
348 	}
349 
350 	// check if target is initialized
351 	// ask if init or mount as is
352 	if (fDDRoster.GetPartitionWithID(targetPartitionID, &device,
353 			&partition) == B_OK) {
354 		if (!partition->IsMounted()) {
355 			if ((err = partition->Mount()) < B_OK) {
356 				_SetStatusMessage(mountError);
357 				ERR("BPartition::Mount");
358 				return _InstallationError(err);
359 			}
360 		}
361 		if ((err = partition->GetVolume(&targetVolume)) != B_OK) {
362 			ERR("BPartition::GetVolume");
363 			return _InstallationError(err);
364 		}
365 		if ((err = partition->GetMountPoint(&targetDirectory)) != B_OK) {
366 			ERR("BPartition::GetMountPoint");
367 			return _InstallationError(err);
368 		}
369 	} else if (fDDRoster.GetDeviceWithID(targetPartitionID, &device) == B_OK) {
370 		if (!device.IsMounted()) {
371 			if ((err = device.Mount()) < B_OK) {
372 				_SetStatusMessage(mountError);
373 				ERR("BDiskDevice::Mount");
374 				return _InstallationError(err);
375 			}
376 		}
377 		if ((err = device.GetVolume(&targetVolume)) != B_OK) {
378 			ERR("BDiskDevice::GetVolume");
379 			return _InstallationError(err);
380 		}
381 		if ((err = device.GetMountPoint(&targetDirectory)) != B_OK) {
382 			ERR("BDiskDevice::GetMountPoint");
383 			return _InstallationError(err);
384 		}
385 	} else
386 		return _InstallationError(err);  // shouldn't happen
387 
388 	// check if target has enough space
389 	if (fSpaceRequired > 0 && targetVolume.FreeBytes() < fSpaceRequired) {
390 		BAlert* alert = new BAlert("", B_TRANSLATE("The destination disk may "
391 			"not have enough space. Try choosing a different disk or choose "
392 			"to not install optional items."),
393 			B_TRANSLATE("Try installing anyway"), B_TRANSLATE("Cancel"), 0,
394 			B_WIDTH_AS_USUAL, B_STOP_ALERT);
395 		alert->SetShortcut(1, B_ESCAPE);
396 		if (alert->Go() != 0)
397 			return _InstallationError(err);
398 	}
399 
400 	if (fDDRoster.GetPartitionWithID(sourcePartitionID, &device, &partition)
401 			== B_OK) {
402 		if ((err = partition->GetMountPoint(&srcDirectory)) != B_OK) {
403 			ERR("BPartition::GetMountPoint");
404 			return _InstallationError(err);
405 		}
406 	} else if (fDDRoster.GetDeviceWithID(sourcePartitionID, &device) == B_OK) {
407 		if ((err = device.GetMountPoint(&srcDirectory)) != B_OK) {
408 			ERR("BDiskDevice::GetMountPoint");
409 			return _InstallationError(err);
410 		}
411 	} else
412 		return _InstallationError(err); // shouldn't happen
413 
414 	// check not installing on itself
415 	if (strcmp(srcDirectory.Path(), targetDirectory.Path()) == 0) {
416 		_SetStatusMessage(B_TRANSLATE("You can't install the contents of a "
417 			"disk onto itself. Please choose a different disk."));
418 		return _InstallationError(err);
419 	}
420 
421 	// check not installing on boot volume
422 	if (strncmp(BOOT_PATH, targetDirectory.Path(), strlen(BOOT_PATH)) == 0) {
423 		BAlert* alert = new BAlert("", B_TRANSLATE("Are you sure you want to "
424 			"install onto the current boot disk? The Installer will have to "
425 			"reboot your machine if you proceed."), B_TRANSLATE("OK"),
426 			B_TRANSLATE("Cancel"), 0, B_WIDTH_AS_USUAL, B_STOP_ALERT);
427 		alert->SetShortcut(1, B_ESCAPE);
428 		if (alert->Go() != 0) {
429 			_SetStatusMessage("Installation stopped.");
430 			return _InstallationError(err);
431 		}
432 	}
433 
434 	// check if target volume's trash dir has anything in it
435 	// (target volume w/ only an empty trash dir is considered
436 	// an empty volume)
437 	if (find_directory(B_TRASH_DIRECTORY, &trashPath, false,
438 		&targetVolume) == B_OK && targetDir.SetTo(trashPath.Path()) == B_OK) {
439 			while (targetDir.GetNextRef(&testRef) == B_OK) {
440 				// Something in the Trash
441 				entries++;
442 				break;
443 			}
444 	}
445 
446 	targetDir.SetTo(targetDirectory.Path());
447 
448 	// check if target volume otherwise has any entries
449 	while (entries == 0 && targetDir.GetNextRef(&testRef) == B_OK) {
450 		if (testPath.SetTo(&testRef) == B_OK && testPath != trashPath)
451 			entries++;
452 	}
453 
454 	if (entries != 0) {
455 		BAlert* alert = new BAlert("", B_TRANSLATE("The target volume is not "
456 			"empty. Are you sure you want to install anyway?\n\nNote: The "
457 			"'system' folder will be a clean copy from the source volume, all "
458 			"other folders will be merged, whereas files and links that exist "
459 			"on both the source and target volume will be overwritten with "
460 			"the source volume version."),
461 			B_TRANSLATE("Install anyway"), B_TRANSLATE("Cancel"), 0,
462 			B_WIDTH_AS_USUAL, B_STOP_ALERT);
463 		alert->SetShortcut(1, B_ESCAPE);
464 		if (alert->Go() != 0) {
465 		// TODO: Would be cool to offer the option here to clean additional
466 		// folders at the user's choice.
467 			return _InstallationError(B_CANCELED);
468 		}
469 	}
470 
471 	// Begin actual installation
472 
473 	ProgressReporter reporter(fOwner, new BMessage(MSG_STATUS_MESSAGE));
474 	EntryFilter entryFilter(srcDirectory.Path());
475 	CopyEngine engine(&reporter, &entryFilter);
476 	BList unzipEngines;
477 
478 	_LaunchInitScript(targetDirectory);
479 
480 	// Create the default indices which should always be present on a proper
481 	// boot volume. We don't care if the source volume does not have them.
482 	// After all, the user might be re-installing to another drive and may
483 	// want problems fixed along the way...
484 	err = _CreateDefaultIndices(targetDirectory);
485 	if (err != B_OK)
486 		return _InstallationError(err);
487 	// Mirror all the indices which are present on the source volume onto
488 	// the target volume.
489 	err = _MirrorIndices(srcDirectory, targetDirectory);
490 	if (err != B_OK)
491 		return _InstallationError(err);
492 
493 	// Let the engine collect information for the progress bar later on
494 	engine.ResetTargets(srcDirectory.Path());
495 	err = engine.CollectTargets(srcDirectory.Path(), fCancelSemaphore);
496 	if (err != B_OK)
497 		return _InstallationError(err);
498 
499 	// Collect selected packages also
500 	if (fPackages) {
501 		BPath pkgRootDir(srcDirectory.Path(), kPackagesDirectoryPath);
502 		int32 count = fPackages->CountItems();
503 		for (int32 i = 0; i < count; i++) {
504 			Package *p = static_cast<Package*>(fPackages->ItemAt(i));
505 			BPath packageDir(pkgRootDir.Path(), p->Folder());
506 			err = engine.CollectTargets(packageDir.Path(), fCancelSemaphore);
507 			if (err != B_OK)
508 				return _InstallationError(err);
509 		}
510 	}
511 
512 	// collect information about all zip packages
513 	err = _ProcessZipPackages(srcDirectory.Path(), targetDirectory.Path(),
514 		&reporter, unzipEngines);
515 	if (err != B_OK)
516 		return _InstallationError(err);
517 
518 	reporter.StartTimer();
519 
520 	// copy source volume
521 	err = engine.CopyFolder(srcDirectory.Path(), targetDirectory.Path(),
522 		fCancelSemaphore);
523 	if (err != B_OK)
524 		return _InstallationError(err);
525 
526 	// copy selected packages
527 	if (fPackages) {
528 		BPath pkgRootDir(srcDirectory.Path(), kPackagesDirectoryPath);
529 		int32 count = fPackages->CountItems();
530 		for (int32 i = 0; i < count; i++) {
531 			Package *p = static_cast<Package*>(fPackages->ItemAt(i));
532 			BPath packageDir(pkgRootDir.Path(), p->Folder());
533 			err = engine.CopyFolder(packageDir.Path(), targetDirectory.Path(),
534 				fCancelSemaphore);
535 			if (err != B_OK)
536 				return _InstallationError(err);
537 		}
538 	}
539 
540 	// Extract all zip packages. If an error occured, delete the rest of
541 	// the engines, but stop extracting.
542 	for (int32 i = 0; i < unzipEngines.CountItems(); i++) {
543 		UnzipEngine* engine = reinterpret_cast<UnzipEngine*>(
544 			unzipEngines.ItemAtFast(i));
545 		if (err == B_OK)
546 			err = engine->UnzipPackage();
547 		delete engine;
548 	}
549 	if (err != B_OK)
550 		return _InstallationError(err);
551 
552 	_LaunchFinishScript(targetDirectory);
553 
554 	fOwner.SendMessage(MSG_INSTALL_FINISHED);
555 	return B_OK;
556 }
557 
558 
559 status_t
560 WorkerThread::_InstallationError(status_t error)
561 {
562 	BMessage statusMessage(MSG_RESET);
563 	if (error == B_CANCELED)
564 		_SetStatusMessage(B_TRANSLATE("Installation canceled."));
565 	else
566 		statusMessage.AddInt32("error", error);
567 	ERR("_PerformInstall failed");
568 	fOwner.SendMessage(&statusMessage);
569 	return error;
570 }
571 
572 
573 status_t
574 WorkerThread::_MirrorIndices(const BPath& sourceDirectory,
575 	const BPath& targetDirectory) const
576 {
577 	dev_t sourceDevice = dev_for_path(sourceDirectory.Path());
578 	if (sourceDevice < 0)
579 		return (status_t)sourceDevice;
580 	dev_t targetDevice = dev_for_path(targetDirectory.Path());
581 	if (targetDevice < 0)
582 		return (status_t)targetDevice;
583 	DIR* indices = fs_open_index_dir(sourceDevice);
584 	if (indices == NULL) {
585 		printf("%s: fs_open_index_dir(): (%d) %s\n", sourceDirectory.Path(),
586 			errno, strerror(errno));
587 		// Opening the index directory will fail for example on ISO-Live
588 		// CDs. The default indices have already been created earlier, so
589 		// we simply bail.
590 		return B_OK;
591 	}
592 	while (dirent* index = fs_read_index_dir(indices)) {
593 		if (strcmp(index->d_name, "name") == 0
594 			|| strcmp(index->d_name, "size") == 0
595 			|| strcmp(index->d_name, "last_modified") == 0) {
596 			continue;
597 		}
598 
599 		index_info info;
600 		if (fs_stat_index(sourceDevice, index->d_name, &info) != B_OK) {
601 			printf("Failed to mirror index %s: fs_stat_index(): (%d) %s\n",
602 				index->d_name, errno, strerror(errno));
603 			continue;
604 		}
605 
606 		uint32 flags = 0;
607 			// Flags are always 0 for the moment.
608 		if (fs_create_index(targetDevice, index->d_name, info.type, flags)
609 			!= B_OK) {
610 			if (errno == B_FILE_EXISTS)
611 				continue;
612 			printf("Failed to mirror index %s: fs_create_index(): (%d) %s\n",
613 				index->d_name, errno, strerror(errno));
614 			continue;
615 		}
616 	}
617 	fs_close_index_dir(indices);
618 	return B_OK;
619 }
620 
621 
622 status_t
623 WorkerThread::_CreateDefaultIndices(const BPath& targetDirectory) const
624 {
625 	dev_t targetDevice = dev_for_path(targetDirectory.Path());
626 	if (targetDevice < 0)
627 		return (status_t)targetDevice;
628 
629 	struct IndexInfo {
630 		const char* name;
631 		uint32_t	type;
632 	};
633 
634 	const IndexInfo defaultIndices[] = {
635 		{ "BEOS:APP_SIG", B_STRING_TYPE },
636 		{ "BEOS:LOCALE_LANGUAGE", B_STRING_TYPE },
637 		{ "BEOS:LOCALE_SIGNATURE", B_STRING_TYPE },
638 		{ "_trk/qrylastchange", B_INT32_TYPE },
639 		{ "_trk/recentQuery", B_INT32_TYPE },
640 		{ "be:deskbar_item_status", B_STRING_TYPE }
641 	};
642 
643 	uint32 flags = 0;
644 		// Flags are always 0 for the moment.
645 
646 	for (uint32 i = 0; i < sizeof(defaultIndices) / sizeof(IndexInfo); i++) {
647 		const IndexInfo& info = defaultIndices[i];
648 		if (fs_create_index(targetDevice, info.name, info.type, flags)
649 			!= B_OK) {
650 			if (errno == B_FILE_EXISTS)
651 				continue;
652 			printf("Failed to create index %s: fs_create_index(): (%d) %s\n",
653 				info.name, errno, strerror(errno));
654 			return errno;
655 		}
656 	}
657 
658 	return B_OK;
659 }
660 
661 
662 status_t
663 WorkerThread::_ProcessZipPackages(const char* sourcePath,
664 	const char* targetPath, ProgressReporter* reporter, BList& unzipEngines)
665 {
666 	// TODO: Put those in the optional packages list view
667 	// TODO: Implement mechanism to handle dependencies between these
668 	// packages. (Selecting one will auto-select others.)
669 	BPath pkgRootDir(sourcePath, kPackagesDirectoryPath);
670 	BDirectory directory(pkgRootDir.Path());
671 	BEntry entry;
672 	while (directory.GetNextEntry(&entry) == B_OK) {
673 		char name[B_FILE_NAME_LENGTH];
674 		if (entry.GetName(name) != B_OK)
675 			continue;
676 		int nameLength = strlen(name);
677 		if (nameLength <= 0)
678 			continue;
679 		char* nameExtension = name + nameLength - 4;
680 		if (strcasecmp(nameExtension, ".zip") != 0)
681 			continue;
682 		printf("found .zip package: %s\n", name);
683 
684 		UnzipEngine* unzipEngine = new(std::nothrow) UnzipEngine(reporter,
685 			fCancelSemaphore);
686 		if (unzipEngine == NULL || !unzipEngines.AddItem(unzipEngine)) {
687 			delete unzipEngine;
688 			return B_NO_MEMORY;
689 		}
690 		BPath path;
691 		entry.GetPath(&path);
692 		status_t ret = unzipEngine->SetTo(path.Path(), targetPath);
693 		if (ret != B_OK)
694 			return ret;
695 
696 		reporter->AddItems(unzipEngine->ItemsToUncompress(),
697 			unzipEngine->BytesToUncompress());
698 	}
699 
700 	return B_OK;
701 }
702 
703 
704 void
705 WorkerThread::_SetStatusMessage(const char *status)
706 {
707 	BMessage msg(MSG_STATUS_MESSAGE);
708 	msg.AddString("status", status);
709 	fOwner.SendMessage(&msg);
710 }
711 
712 
713 static void
714 make_partition_label(BPartition* partition, char* label, char* menuLabel,
715 	bool showContentType)
716 {
717 	char size[20];
718 	string_for_size(partition->Size(), size, sizeof(size));
719 
720 	BPath path;
721 	partition->GetPath(&path);
722 
723 	if (showContentType) {
724 		const char* type = partition->ContentType();
725 		if (type == NULL)
726 			type = B_TRANSLATE_COMMENT("Unknown Type", "Partition content type");
727 
728 		sprintf(label, "%s - %s [%s] (%s)", partition->ContentName(), size,
729 			path.Path(), type);
730 	} else {
731 		sprintf(label, "%s - %s [%s]", partition->ContentName(), size,
732 			path.Path());
733 	}
734 
735 	sprintf(menuLabel, "%s - %s", partition->ContentName(), size);
736 }
737 
738 
739 // #pragma mark - SourceVisitor
740 
741 
742 SourceVisitor::SourceVisitor(BMenu *menu)
743 	: fMenu(menu)
744 {
745 }
746 
747 bool
748 SourceVisitor::Visit(BDiskDevice *device)
749 {
750 	return Visit(device, 0);
751 }
752 
753 
754 bool
755 SourceVisitor::Visit(BPartition *partition, int32 level)
756 {
757 	BPath path;
758 	if (partition->GetPath(&path) == B_OK)
759 		printf("SourceVisitor::Visit(BPartition *) : %s\n", path.Path());
760 	printf("SourceVisitor::Visit(BPartition *) : %s\n",
761 		partition->ContentName());
762 
763 	if (partition->ContentType() == NULL)
764 		return false;
765 
766 	bool isBootPartition = false;
767 	if (partition->IsMounted()) {
768 		BPath mountPoint;
769 		partition->GetMountPoint(&mountPoint);
770 		isBootPartition = strcmp(BOOT_PATH, mountPoint.Path()) == 0;
771 	}
772 
773 	if (!isBootPartition
774 		&& strcmp(partition->ContentType(), kPartitionTypeBFS) != 0) {
775 		// Except only BFS partitions, except this is the boot partition
776 		// (ISO9660 with write overlay for example).
777 		return false;
778 	}
779 
780 	// TODO: We could probably check if this volume contains
781 	// the Haiku kernel or something. Does it make sense to "install"
782 	// from your BFS volume containing the music collection?
783 	// TODO: Then the check for BFS could also be removed above.
784 
785 	char label[255];
786 	char menuLabel[255];
787 	make_partition_label(partition, label, menuLabel, false);
788 	PartitionMenuItem* item = new PartitionMenuItem(partition->ContentName(),
789 		label, menuLabel, new BMessage(SOURCE_PARTITION), partition->ID());
790 	item->SetMarked(isBootPartition);
791 	fMenu->AddItem(item);
792 	return false;
793 }
794 
795 
796 // #pragma mark - TargetVisitor
797 
798 
799 TargetVisitor::TargetVisitor(BMenu *menu)
800 	: fMenu(menu)
801 {
802 }
803 
804 
805 bool
806 TargetVisitor::Visit(BDiskDevice *device)
807 {
808 	if (device->IsReadOnlyMedia())
809 		return false;
810 	return Visit(device, 0);
811 }
812 
813 
814 bool
815 TargetVisitor::Visit(BPartition *partition, int32 level)
816 {
817 	BPath path;
818 	if (partition->GetPath(&path) == B_OK)
819 		printf("TargetVisitor::Visit(BPartition *) : %s\n", path.Path());
820 	printf("TargetVisitor::Visit(BPartition *) : %s\n",
821 		partition->ContentName());
822 
823 	if (partition->ContentSize() < 20 * 1024 * 1024) {
824 		// reject partitions which are too small anyway
825 		// TODO: Could depend on the source size
826 		printf("  too small\n");
827 		return false;
828 	}
829 
830 	if (partition->CountChildren() > 0) {
831 		// Looks like an extended partition, or the device itself.
832 		// Do not accept this as target...
833 		printf("  no leaf partition\n");
834 		return false;
835 	}
836 
837 	// TODO: After running DriveSetup and doing another scan, it would
838 	// be great to pick the partition which just appeared!
839 
840 	bool isBootPartition = false;
841 	if (partition->IsMounted()) {
842 		BPath mountPoint;
843 		partition->GetMountPoint(&mountPoint);
844 		isBootPartition = strcmp(BOOT_PATH, mountPoint.Path()) == 0;
845 	}
846 
847 	// Only non-boot BFS partitions are valid targets, but we want to display the
848 	// other partitions as well, in order not to irritate the user.
849 	bool isValidTarget = isBootPartition == false
850 		&& partition->ContentType() != NULL
851 		&& strcmp(partition->ContentType(), kPartitionTypeBFS) == 0;
852 
853 	char label[255];
854 	char menuLabel[255];
855 	make_partition_label(partition, label, menuLabel, !isValidTarget);
856 	PartitionMenuItem* item = new PartitionMenuItem(partition->ContentName(),
857 		label, menuLabel, new BMessage(TARGET_PARTITION), partition->ID());
858 
859 	item->SetIsValidTarget(isValidTarget);
860 
861 
862 	fMenu->AddItem(item);
863 	return false;
864 }
865 
866