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