xref: /haiku/src/servers/mount/AutoMounter.cpp (revision 02354704729d38c3b078c696adc1bbbd33cbcf72)
1 /*
2  * Copyright 2007-2018, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus, superstippi@gmx.de
7  *		Axel Dörfler, axeld@pinc-software.de
8  */
9 
10 
11 #include "AutoMounter.h"
12 
13 #include <new>
14 
15 #include <string.h>
16 #include <unistd.h>
17 
18 #include <Alert.h>
19 #include <AutoLocker.h>
20 #include <Catalog.h>
21 #include <Debug.h>
22 #include <Directory.h>
23 #include <DiskDevice.h>
24 #include <DiskDeviceRoster.h>
25 #include <DiskDeviceList.h>
26 #include <DiskDeviceTypes.h>
27 #include <DiskSystem.h>
28 #include <FindDirectory.h>
29 #include <fs_info.h>
30 #include <fs_volume.h>
31 #include <LaunchRoster.h>
32 #include <Locale.h>
33 #include <Message.h>
34 #include <Node.h>
35 #include <NodeMonitor.h>
36 #include <Path.h>
37 #include <PropertyInfo.h>
38 #include <String.h>
39 #include <VolumeRoster.h>
40 
41 #include "MountServer.h"
42 
43 #include "Utilities.h"
44 
45 
46 #undef B_TRANSLATION_CONTEXT
47 #define B_TRANSLATION_CONTEXT "AutoMounter"
48 
49 
50 static const char* kMountServerSettings = "mount_server";
51 static const char* kMountFlagsKeyExtension = " mount flags";
52 
53 static const char* kInitialMountEvent = "initial_volumes_mounted";
54 
55 
56 class MountVisitor : public BDiskDeviceVisitor {
57 public:
58 								MountVisitor(mount_mode normalMode,
59 									mount_mode removableMode,
60 									bool initialRescan, BMessage& previous,
61 									partition_id deviceID);
62 	virtual						~MountVisitor()
63 									{}
64 
65 	virtual	bool				Visit(BDiskDevice* device);
66 	virtual	bool				Visit(BPartition* partition, int32 level);
67 
68 private:
69 			bool				_WasPreviouslyMounted(const BPath& path,
70 									const BPartition* partition);
71 
72 private:
73 			mount_mode			fNormalMode;
74 			mount_mode			fRemovableMode;
75 			bool				fInitialRescan;
76 			BMessage&			fPrevious;
77 			partition_id		fOnlyOnDeviceID;
78 };
79 
80 
81 class MountArchivedVisitor : public BDiskDeviceVisitor {
82 public:
83 								MountArchivedVisitor(
84 									const BDiskDeviceList& devices,
85 									const BMessage& archived);
86 	virtual						~MountArchivedVisitor();
87 
88 	virtual	bool				Visit(BDiskDevice* device);
89 	virtual	bool				Visit(BPartition* partition, int32 level);
90 
91 private:
92 			int					_Score(BPartition* partition);
93 
94 private:
95 			const BDiskDeviceList& fDevices;
96 			const BMessage&		fArchived;
97 			int					fBestScore;
98 			partition_id		fBestID;
99 };
100 
101 
102 static bool
103 BootedInSafeMode()
104 {
105 	const char* safeMode = getenv("SAFEMODE");
106 	return safeMode != NULL && strcmp(safeMode, "yes") == 0;
107 }
108 
109 
110 class ArchiveVisitor : public BDiskDeviceVisitor {
111 public:
112 								ArchiveVisitor(BMessage& message);
113 	virtual						~ArchiveVisitor();
114 
115 	virtual	bool				Visit(BDiskDevice* device);
116 	virtual	bool				Visit(BPartition* partition, int32 level);
117 
118 private:
119 			BMessage&			fMessage;
120 };
121 
122 
123 // #pragma mark - MountVisitor
124 
125 
126 MountVisitor::MountVisitor(mount_mode normalMode, mount_mode removableMode,
127 		bool initialRescan, BMessage& previous, partition_id deviceID)
128 	:
129 	fNormalMode(normalMode),
130 	fRemovableMode(removableMode),
131 	fInitialRescan(initialRescan),
132 	fPrevious(previous),
133 	fOnlyOnDeviceID(deviceID)
134 {
135 }
136 
137 
138 bool
139 MountVisitor::Visit(BDiskDevice* device)
140 {
141 	return Visit(device, 0);
142 }
143 
144 
145 bool
146 MountVisitor::Visit(BPartition* partition, int32 level)
147 {
148 	if (fOnlyOnDeviceID >= 0) {
149 		// only mount partitions on the given device id
150 		// or if the partition ID is already matched
151 		BPartition* device = partition;
152 		while (device->Parent() != NULL) {
153 			if (device->ID() == fOnlyOnDeviceID) {
154 				// we are happy
155 				break;
156 			}
157 			device = device->Parent();
158 		}
159 		if (device->ID() != fOnlyOnDeviceID)
160 			return false;
161 	}
162 
163 	mount_mode mode = !fInitialRescan && partition->Device()->IsRemovableMedia()
164 		? fRemovableMode : fNormalMode;
165 	if (mode == kNoVolumes || partition->IsMounted()
166 		|| !partition->ContainsFileSystem()) {
167 		return false;
168 	}
169 
170 	BPath path;
171 	if (partition->GetPath(&path) != B_OK)
172 		return false;
173 
174 	if (mode == kRestorePreviousVolumes) {
175 		// mount all volumes that were stored in the settings file
176 		if (!_WasPreviouslyMounted(path, partition))
177 			return false;
178 	} else if (mode == kOnlyBFSVolumes) {
179 		if (partition->ContentType() == NULL
180 			|| strcmp(partition->ContentType(), kPartitionTypeBFS))
181 			return false;
182 	}
183 
184 	uint32 mountFlags;
185 	if (!fInitialRescan) {
186 		// Ask the user about mount flags if this is not the
187 		// initial scan.
188 		if (!AutoMounter::_SuggestMountFlags(partition, &mountFlags))
189 			return false;
190 	} else {
191 		BString mountFlagsKey(path.Path());
192 		mountFlagsKey << kMountFlagsKeyExtension;
193 		if (fPrevious.FindInt32(mountFlagsKey.String(),
194 				(int32*)&mountFlags) < B_OK) {
195 			mountFlags = 0;
196 		}
197 	}
198 
199 	if (partition->Mount(NULL, mountFlags) != B_OK) {
200 		// TODO: Error to syslog
201 	}
202 	return false;
203 }
204 
205 
206 bool
207 MountVisitor::_WasPreviouslyMounted(const BPath& path,
208 	const BPartition* partition)
209 {
210 	// We only check the legacy config data here; the current method
211 	// is implemented in ArchivedVolumeVisitor -- this can be removed
212 	// some day.
213 	const char* volumeName = NULL;
214 	if (partition->ContentName() == NULL
215 		|| fPrevious.FindString(path.Path(), &volumeName) != B_OK
216 		|| strcmp(volumeName, partition->ContentName()) != 0)
217 		return false;
218 
219 	return true;
220 }
221 
222 
223 // #pragma mark - MountArchivedVisitor
224 
225 
226 MountArchivedVisitor::MountArchivedVisitor(const BDiskDeviceList& devices,
227 		const BMessage& archived)
228 	:
229 	fDevices(devices),
230 	fArchived(archived),
231 	fBestScore(-1),
232 	fBestID(-1)
233 {
234 }
235 
236 
237 MountArchivedVisitor::~MountArchivedVisitor()
238 {
239 	if (fBestScore >= 6) {
240 		uint32 mountFlags = fArchived.GetUInt32("mountFlags", 0);
241 		BPartition* partition = fDevices.PartitionWithID(fBestID);
242 		if (partition != NULL)
243 			partition->Mount(NULL, mountFlags);
244 	}
245 }
246 
247 
248 bool
249 MountArchivedVisitor::Visit(BDiskDevice* device)
250 {
251 	return Visit(device, 0);
252 }
253 
254 
255 bool
256 MountArchivedVisitor::Visit(BPartition* partition, int32 level)
257 {
258 	if (partition->IsMounted() || !partition->ContainsFileSystem())
259 		return false;
260 
261 	int score = _Score(partition);
262 	if (score > fBestScore) {
263 		fBestScore = score;
264 		fBestID = partition->ID();
265 	}
266 
267 	return false;
268 }
269 
270 
271 int
272 MountArchivedVisitor::_Score(BPartition* partition)
273 {
274 	BPath path;
275 	if (partition->GetPath(&path) != B_OK)
276 		return false;
277 
278 	int score = 0;
279 
280 	int64 capacity = fArchived.GetInt64("capacity", 0);
281 	if (capacity == partition->ContentSize())
282 		score += 4;
283 
284 	BString deviceName = fArchived.GetString("deviceName");
285 	if (deviceName == path.Path())
286 		score += 3;
287 
288 	BString volumeName = fArchived.GetString("volumeName");
289 	if (volumeName == partition->ContentName())
290 		score += 2;
291 
292 	BString fsName = fArchived.FindString("fsName");
293 	if (fsName == partition->ContentType())
294 		score += 1;
295 
296 	uint32 blockSize = fArchived.GetUInt32("blockSize", 0);
297 	if (blockSize == partition->BlockSize())
298 		score += 1;
299 
300 	return score;
301 }
302 
303 
304 // #pragma mark - ArchiveVisitor
305 
306 
307 ArchiveVisitor::ArchiveVisitor(BMessage& message)
308 	:
309 	fMessage(message)
310 {
311 }
312 
313 
314 ArchiveVisitor::~ArchiveVisitor()
315 {
316 }
317 
318 
319 bool
320 ArchiveVisitor::Visit(BDiskDevice* device)
321 {
322 	return Visit(device, 0);
323 }
324 
325 
326 bool
327 ArchiveVisitor::Visit(BPartition* partition, int32 level)
328 {
329 	if (!partition->ContainsFileSystem())
330 		return false;
331 
332 	BPath path;
333 	if (partition->GetPath(&path) != B_OK)
334 		return false;
335 
336 	BMessage info;
337 	info.AddUInt32("blockSize", partition->BlockSize());
338 	info.AddInt64("capacity", partition->ContentSize());
339 	info.AddString("deviceName", path.Path());
340 	info.AddString("volumeName", partition->ContentName());
341 	info.AddString("fsName", partition->ContentType());
342 
343 	fMessage.AddMessage("info", &info);
344 	return false;
345 }
346 
347 
348 // #pragma mark -
349 
350 
351 AutoMounter::AutoMounter()
352 	:
353 	BServer(kMountServerSignature, false, NULL),
354 	fNormalMode(kRestorePreviousVolumes),
355 	fRemovableMode(kAllVolumes),
356 	fEjectWhenUnmounting(true)
357 {
358 	set_thread_priority(Thread(), B_LOW_PRIORITY);
359 
360 	if (!BootedInSafeMode()) {
361 		_ReadSettings();
362 	} else {
363 		// defeat automounter in safe mode, don't even care about the settings
364 		fNormalMode = kNoVolumes;
365 		fRemovableMode = kNoVolumes;
366 	}
367 
368 	BDiskDeviceRoster().StartWatching(this,
369 		B_DEVICE_REQUEST_DEVICE | B_DEVICE_REQUEST_DEVICE_LIST);
370 	BLaunchRoster().RegisterEvent(this, kInitialMountEvent, B_STICKY_EVENT);
371 }
372 
373 
374 AutoMounter::~AutoMounter()
375 {
376 	BLaunchRoster().UnregisterEvent(this, kInitialMountEvent);
377 	BDiskDeviceRoster().StopWatching(this);
378 }
379 
380 
381 void
382 AutoMounter::ReadyToRun()
383 {
384 	// Do initial scan
385 	_MountVolumes(fNormalMode, fRemovableMode, true);
386 	BLaunchRoster().NotifyEvent(this, kInitialMountEvent);
387 }
388 
389 
390 void
391 AutoMounter::MessageReceived(BMessage* message)
392 {
393 	switch (message->what) {
394 		case kMountVolume:
395 			_MountVolume(message);
396 			break;
397 
398 		case kUnmountVolume:
399 			_UnmountAndEjectVolume(message);
400 			break;
401 
402 		case kSetAutomounterParams:
403 		{
404 			bool rescanNow = false;
405 			message->FindBool("rescanNow", &rescanNow);
406 
407 			_UpdateSettingsFromMessage(message);
408 			_GetSettings(&fSettings);
409 			_WriteSettings();
410 
411 			if (rescanNow)
412 				_MountVolumes(fNormalMode, fRemovableMode);
413 			break;
414 		}
415 
416 		case kGetAutomounterParams:
417 		{
418 			BMessage reply;
419 			_GetSettings(&reply);
420 			message->SendReply(&reply);
421 			break;
422 		}
423 
424 		case kMountAllNow:
425 			_MountVolumes(kAllVolumes, kAllVolumes);
426 			break;
427 
428 		case B_DEVICE_UPDATE:
429 			int32 event;
430 			if (message->FindInt32("event", &event) != B_OK
431 				|| (event != B_DEVICE_MEDIA_CHANGED
432 					&& event != B_DEVICE_ADDED))
433 				break;
434 
435 			partition_id deviceID;
436 			if (message->FindInt32("id", &deviceID) != B_OK)
437 				break;
438 
439 			_MountVolumes(kNoVolumes, fRemovableMode, false, deviceID);
440 			break;
441 
442 #if 0
443 		case B_NODE_MONITOR:
444 		{
445 			int32 opcode;
446 			if (message->FindInt32("opcode", &opcode) != B_OK)
447 				break;
448 
449 			switch (opcode) {
450 				//	The name of a mount point has changed
451 				case B_ENTRY_MOVED: {
452 					WRITELOG(("*** Received Mount Point Renamed Notification"));
453 
454 					const char *newName;
455 					if (message->FindString("name", &newName) != B_OK) {
456 						WRITELOG(("ERROR: Couldn't find name field in update "
457 							"message"));
458 						PRINT_OBJECT(*message);
459 						break ;
460 					}
461 
462 					//
463 					// When the node monitor reports a move, it gives the
464 					// parent device and inode that moved.  The problem is
465 					// that  the inode is the inode of root *in* the filesystem,
466 					// which is generally always the same number for every
467 					// filesystem of a type.
468 					//
469 					// What we'd really like is the device that the moved
470 					// volume is mounted on.  Find this by using the
471 					// *new* name and directory, and then stat()ing that to
472 					// find the device.
473 					//
474 					dev_t parentDevice;
475 					if (message->FindInt32("device", &parentDevice) != B_OK) {
476 						WRITELOG(("ERROR: Couldn't find 'device' field in "
477 							"update message"));
478 						PRINT_OBJECT(*message);
479 						break;
480 					}
481 
482 					ino_t toDirectory;
483 					if (message->FindInt64("to directory", &toDirectory)
484 						!= B_OK) {
485 						WRITELOG(("ERROR: Couldn't find 'to directory' field "
486 							"in update message"));
487 						PRINT_OBJECT(*message);
488 						break;
489 					}
490 
491 					entry_ref root_entry(parentDevice, toDirectory, newName);
492 
493 					BNode entryNode(&root_entry);
494 					if (entryNode.InitCheck() != B_OK) {
495 						WRITELOG(("ERROR: Couldn't create mount point entry "
496 							"node: %s/n", strerror(entryNode.InitCheck())));
497 						break;
498 					}
499 
500 					node_ref mountPointNode;
501 					if (entryNode.GetNodeRef(&mountPointNode) != B_OK) {
502 						WRITELOG(("ERROR: Couldn't get node ref for new mount "
503 							"point"));
504 						break;
505 					}
506 
507 					WRITELOG(("Attempt to rename device %li to %s",
508 						mountPointNode.device, newName));
509 
510 					Partition *partition = FindPartition(mountPointNode.device);
511 					if (partition != NULL) {
512 						WRITELOG(("Found device, changing name."));
513 
514 						BVolume mountVolume(partition->VolumeDeviceID());
515 						BDirectory mountDir;
516 						mountVolume.GetRootDirectory(&mountDir);
517 						BPath dirPath(&mountDir, 0);
518 
519 						partition->SetMountedAt(dirPath.Path());
520 						partition->SetVolumeName(newName);
521 						break;
522 					} else {
523 						WRITELOG(("ERROR: Device %li does not appear to be "
524 							"present", mountPointNode.device));
525 					}
526 				}
527 			}
528 			break;
529 		}
530 #endif
531 
532 		default:
533 			BLooper::MessageReceived(message);
534 			break;
535 	}
536 }
537 
538 
539 bool
540 AutoMounter::QuitRequested()
541 {
542 	if (!BootedInSafeMode()) {
543 		// Don't write out settings in safe mode - this would overwrite the
544 		// normal, non-safe mode settings.
545 		_WriteSettings();
546 	}
547 
548 	return true;
549 }
550 
551 
552 // #pragma mark - private methods
553 
554 
555 void
556 AutoMounter::_MountVolumes(mount_mode normal, mount_mode removable,
557 	bool initialRescan, partition_id deviceID)
558 {
559 	if (normal == kNoVolumes && removable == kNoVolumes)
560 		return;
561 
562 	BDiskDeviceList devices;
563 	status_t status = devices.Fetch();
564 	if (status != B_OK)
565 		return;
566 
567 	if (normal == kRestorePreviousVolumes) {
568 		BMessage archived;
569 		for (int32 index = 0;
570 				fSettings.FindMessage("info", index, &archived) == B_OK;
571 				index++) {
572 			MountArchivedVisitor visitor(devices, archived);
573 			devices.VisitEachPartition(&visitor);
574 		}
575 	}
576 
577 	MountVisitor visitor(normal, removable, initialRescan, fSettings, deviceID);
578 	devices.VisitEachPartition(&visitor);
579 }
580 
581 
582 void
583 AutoMounter::_MountVolume(const BMessage* message)
584 {
585 	int32 id;
586 	if (message->FindInt32("id", &id) != B_OK)
587 		return;
588 
589 	BDiskDeviceRoster roster;
590 	BPartition *partition;
591 	BDiskDevice device;
592 	if (roster.GetPartitionWithID(id, &device, &partition) != B_OK)
593 		return;
594 
595 	uint32 mountFlags;
596 	if (!_SuggestMountFlags(partition, &mountFlags))
597 		return;
598 
599 	status_t status = partition->Mount(NULL, mountFlags);
600 	if (status < B_OK && InitGUIContext() == B_OK) {
601 		char text[512];
602 		snprintf(text, sizeof(text),
603 			B_TRANSLATE("Error mounting volume:\n\n%s"), strerror(status));
604 		BAlert* alert = new BAlert(B_TRANSLATE("Mount error"), text,
605 			B_TRANSLATE("OK"));
606 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
607 		alert->Go(NULL);
608 	}
609 }
610 
611 
612 bool
613 AutoMounter::_SuggestForceUnmount(const char* name, status_t error)
614 {
615 	if (InitGUIContext() != B_OK)
616 		return false;
617 
618 	char text[1024];
619 	snprintf(text, sizeof(text),
620 		B_TRANSLATE("Could not unmount disk \"%s\":\n\t%s\n\n"
621 			"Should unmounting be forced?\n\n"
622 			"Note: If an application is currently writing to the volume, "
623 			"unmounting it now might result in loss of data.\n"),
624 		name, strerror(error));
625 
626 	BAlert* alert = new BAlert(B_TRANSLATE("Force unmount"), text,
627 		B_TRANSLATE("Cancel"), B_TRANSLATE("Force unmount"), NULL,
628 		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
629 	alert->SetShortcut(0, B_ESCAPE);
630 	int32 choice = alert->Go();
631 
632 	return choice == 1;
633 }
634 
635 
636 void
637 AutoMounter::_ReportUnmountError(const char* name, status_t error)
638 {
639 	if (InitGUIContext() != B_OK)
640 		return;
641 
642 	char text[512];
643 	snprintf(text, sizeof(text), B_TRANSLATE("Could not unmount disk "
644 		"\"%s\":\n\t%s"), name, strerror(error));
645 
646 	BAlert* alert = new BAlert(B_TRANSLATE("Unmount error"), text,
647 		B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
648 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
649 	alert->Go(NULL);
650 }
651 
652 
653 void
654 AutoMounter::_UnmountAndEjectVolume(BPartition* partition, BPath& mountPoint,
655 	const char* name)
656 {
657 	BDiskDevice deviceStorage;
658 	BDiskDevice* device;
659 	if (partition == NULL) {
660 		// Try to retrieve partition
661 		BDiskDeviceRoster().FindPartitionByMountPoint(mountPoint.Path(),
662 			&deviceStorage, &partition);
663 			device = &deviceStorage;
664 	} else {
665 		device = partition->Device();
666 	}
667 
668 	status_t status;
669 	if (partition != NULL)
670 		status = partition->Unmount();
671 	else
672 		status = fs_unmount_volume(mountPoint.Path(), 0);
673 
674 	if (status != B_OK) {
675 		if (!_SuggestForceUnmount(name, status))
676 			return;
677 
678 		if (partition != NULL)
679 			status = partition->Unmount(B_FORCE_UNMOUNT);
680 		else
681 			status = fs_unmount_volume(mountPoint.Path(), B_FORCE_UNMOUNT);
682 	}
683 
684 	if (status != B_OK) {
685 		_ReportUnmountError(name, status);
686 		return;
687 	}
688 
689 	if (fEjectWhenUnmounting && partition != NULL) {
690 		// eject device if it doesn't have any mounted partitions left
691 		class IsMountedVisitor : public BDiskDeviceVisitor {
692 		public:
693 			IsMountedVisitor()
694 				:
695 				fHasMounted(false)
696 			{
697 			}
698 
699 			virtual bool Visit(BDiskDevice* device)
700 			{
701 				return Visit(device, 0);
702 			}
703 
704 			virtual bool Visit(BPartition* partition, int32 level)
705 			{
706 				if (partition->IsMounted()) {
707 					fHasMounted = true;
708 					return true;
709 				}
710 
711 				return false;
712 			}
713 
714 			bool HasMountedPartitions() const
715 			{
716 				return fHasMounted;
717 			}
718 
719 		private:
720 			bool	fHasMounted;
721 		} visitor;
722 
723 		device->VisitEachDescendant(&visitor);
724 
725 		if (!visitor.HasMountedPartitions())
726 			device->Eject();
727 	}
728 
729 	// remove the directory if it's a directory in rootfs
730 	if (dev_for_path(mountPoint.Path()) == dev_for_path("/"))
731 		rmdir(mountPoint.Path());
732 }
733 
734 
735 void
736 AutoMounter::_UnmountAndEjectVolume(BMessage* message)
737 {
738 	int32 id;
739 	if (message->FindInt32("id", &id) == B_OK) {
740 		BDiskDeviceRoster roster;
741 		BPartition *partition;
742 		BDiskDevice device;
743 		if (roster.GetPartitionWithID(id, &device, &partition) != B_OK)
744 			return;
745 
746 		BPath path;
747 		if (partition->GetMountPoint(&path) == B_OK)
748 			_UnmountAndEjectVolume(partition, path, partition->ContentName());
749 	} else {
750 		// see if we got a dev_t
751 
752 		dev_t device;
753 		if (message->FindInt32("device_id", &device) != B_OK)
754 			return;
755 
756 		BVolume volume(device);
757 		status_t status = volume.InitCheck();
758 
759 		char name[B_FILE_NAME_LENGTH];
760 		if (status == B_OK)
761 			status = volume.GetName(name);
762 		if (status < B_OK)
763 			snprintf(name, sizeof(name), "device:%" B_PRIdDEV, device);
764 
765 		BPath path;
766 		if (status == B_OK) {
767 			BDirectory mountPoint;
768 			status = volume.GetRootDirectory(&mountPoint);
769 			if (status == B_OK)
770 				status = path.SetTo(&mountPoint, ".");
771 		}
772 
773 		if (status == B_OK)
774 			_UnmountAndEjectVolume(NULL, path, name);
775 	}
776 }
777 
778 
779 void
780 AutoMounter::_FromMode(mount_mode mode, bool& all, bool& bfs, bool& restore)
781 {
782 	all = bfs = restore = false;
783 
784 	switch (mode) {
785 		case kAllVolumes:
786 			all = true;
787 			break;
788 		case kOnlyBFSVolumes:
789 			bfs = true;
790 			break;
791 		case kRestorePreviousVolumes:
792 			restore = true;
793 			break;
794 
795 		default:
796 			break;
797 	}
798 }
799 
800 
801 mount_mode
802 AutoMounter::_ToMode(bool all, bool bfs, bool restore)
803 {
804 	if (all)
805 		return kAllVolumes;
806 	if (bfs)
807 		return kOnlyBFSVolumes;
808 	if (restore)
809 		return kRestorePreviousVolumes;
810 
811 	return kNoVolumes;
812 }
813 
814 
815 void
816 AutoMounter::_ReadSettings()
817 {
818 	BPath directoryPath;
819 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &directoryPath, true)
820 		!= B_OK) {
821 		return;
822 	}
823 
824 	BPath path(directoryPath);
825 	path.Append(kMountServerSettings);
826 	fPrefsFile.SetTo(path.Path(), O_RDWR);
827 
828 	if (fPrefsFile.InitCheck() != B_OK) {
829 		// no prefs file yet, create a new one
830 
831 		BDirectory dir(directoryPath.Path());
832 		dir.CreateFile(kMountServerSettings, &fPrefsFile);
833 		return;
834 	}
835 
836 	ssize_t settingsSize = (ssize_t)fPrefsFile.Seek(0, SEEK_END);
837 	if (settingsSize == 0)
838 		return;
839 
840 	ASSERT(settingsSize != 0);
841 	char *buffer = new(std::nothrow) char[settingsSize];
842 	if (buffer == NULL) {
843 		PRINT(("error writing automounter settings, out of memory\n"));
844 		return;
845 	}
846 
847 	fPrefsFile.Seek(0, 0);
848 	if (fPrefsFile.Read(buffer, (size_t)settingsSize) != settingsSize) {
849 		PRINT(("error reading automounter settings\n"));
850 		delete [] buffer;
851 		return;
852 	}
853 
854 	BMessage message('stng');
855 	status_t result = message.Unflatten(buffer);
856 	if (result != B_OK) {
857 		PRINT(("error %s unflattening automounter settings, size %" B_PRIdSSIZE "\n",
858 			strerror(result), settingsSize));
859 		delete [] buffer;
860 		return;
861 	}
862 
863 	delete [] buffer;
864 
865 	// update flags and modes from the message
866 	_UpdateSettingsFromMessage(&message);
867 	// copy the previously mounted partitions
868 	fSettings = message;
869 }
870 
871 
872 void
873 AutoMounter::_WriteSettings()
874 {
875 	if (fPrefsFile.InitCheck() != B_OK)
876 		return;
877 
878 	BMessage message('stng');
879 	_GetSettings(&message);
880 
881 	ssize_t settingsSize = message.FlattenedSize();
882 
883 	char* buffer = new(std::nothrow) char[settingsSize];
884 	if (buffer == NULL) {
885 		PRINT(("error writing automounter settings, out of memory\n"));
886 		return;
887 	}
888 
889 	status_t result = message.Flatten(buffer, settingsSize);
890 
891 	fPrefsFile.Seek(0, SEEK_SET);
892 	fPrefsFile.SetSize(0);
893 
894 	result = fPrefsFile.Write(buffer, (size_t)settingsSize);
895 	if (result != settingsSize)
896 		PRINT(("error writing automounter settings, %s\n", strerror(result)));
897 
898 	delete [] buffer;
899 }
900 
901 
902 void
903 AutoMounter::_UpdateSettingsFromMessage(BMessage* message)
904 {
905 	// auto mounter settings
906 
907 	bool all, bfs, restore;
908 	if (message->FindBool("autoMountAll", &all) != B_OK)
909 		all = true;
910 	if (message->FindBool("autoMountAllBFS", &bfs) != B_OK)
911 		bfs = false;
912 
913 	fRemovableMode = _ToMode(all, bfs, false);
914 
915 	// initial mount settings
916 
917 	if (message->FindBool("initialMountAll", &all) != B_OK)
918 		all = false;
919 	if (message->FindBool("initialMountAllBFS", &bfs) != B_OK)
920 		bfs = false;
921 	if (message->FindBool("initialMountRestore", &restore) != B_OK)
922 		restore = true;
923 
924 	fNormalMode = _ToMode(all, bfs, restore);
925 
926 	// eject settings
927 	bool eject;
928 	if (message->FindBool("ejectWhenUnmounting", &eject) == B_OK)
929 		fEjectWhenUnmounting = eject;
930 }
931 
932 
933 void
934 AutoMounter::_GetSettings(BMessage *message)
935 {
936 	message->MakeEmpty();
937 
938 	bool all, bfs, restore;
939 
940 	_FromMode(fNormalMode, all, bfs, restore);
941 	message->AddBool("initialMountAll", all);
942 	message->AddBool("initialMountAllBFS", bfs);
943 	message->AddBool("initialMountRestore", restore);
944 
945 	_FromMode(fRemovableMode, all, bfs, restore);
946 	message->AddBool("autoMountAll", all);
947 	message->AddBool("autoMountAllBFS", bfs);
948 
949 	message->AddBool("ejectWhenUnmounting", fEjectWhenUnmounting);
950 
951 	// Save mounted volumes so we can optionally mount them on next
952 	// startup
953 	ArchiveVisitor visitor(*message);
954 	BDiskDeviceRoster().VisitEachMountedPartition(&visitor);
955 }
956 
957 
958 /*static*/ bool
959 AutoMounter::_SuggestMountFlags(const BPartition* partition, uint32* _flags)
960 {
961 	uint32 mountFlags = 0;
962 
963 	bool askReadOnly = true;
964 
965 	if (partition->ContentType() != NULL
966 		&& strcmp(partition->ContentType(), kPartitionTypeBFS) == 0) {
967 		askReadOnly = false;
968 	}
969 
970 	BDiskSystem diskSystem;
971 	status_t status = partition->GetDiskSystem(&diskSystem);
972 	if (status == B_OK && !diskSystem.SupportsWriting())
973 		askReadOnly = false;
974 
975 	if (partition->IsReadOnly())
976 		askReadOnly = false;
977 
978 	if (askReadOnly && ((BServer*)be_app)->InitGUIContext() != B_OK) {
979 		// Mount read-only, just to be safe.
980 		mountFlags |= B_MOUNT_READ_ONLY;
981 		askReadOnly = false;
982 	}
983 
984 	if (askReadOnly) {
985 		// Suggest to the user to mount read-only until Haiku is more mature.
986 		BString string;
987 		if (partition->ContentName() != NULL) {
988 			char buffer[512];
989 			snprintf(buffer, sizeof(buffer),
990 				B_TRANSLATE("Mounting volume '%s'\n\n"),
991 				partition->ContentName());
992 			string << buffer;
993 		} else
994 			string << B_TRANSLATE("Mounting volume <unnamed volume>\n\n");
995 
996 		// TODO: Use distro name instead of "Haiku"...
997 		string << B_TRANSLATE("The file system on this volume is not the "
998 			"Be file system. It is recommended to mount it in read-only "
999 			"mode, to prevent unintentional data loss because of bugs "
1000 			"in Haiku.");
1001 
1002 		BAlert* alert = new BAlert(B_TRANSLATE("Mount warning"),
1003 			string.String(), B_TRANSLATE("Mount read/write"),
1004 			B_TRANSLATE("Cancel"), B_TRANSLATE("Mount read-only"),
1005 			B_WIDTH_FROM_WIDEST, B_WARNING_ALERT);
1006 		alert->SetShortcut(1, B_ESCAPE);
1007 		int32 choice = alert->Go();
1008 		switch (choice) {
1009 			case 0:
1010 				break;
1011 			case 1:
1012 				return false;
1013 			case 2:
1014 				mountFlags |= B_MOUNT_READ_ONLY;
1015 				break;
1016 		}
1017 	}
1018 
1019 	*_flags = mountFlags;
1020 	return true;
1021 }
1022 
1023 
1024 // #pragma mark -
1025 
1026 
1027 int
1028 main(int argc, char* argv[])
1029 {
1030 	AutoMounter app;
1031 
1032 	app.Run();
1033 	return 0;
1034 }
1035 
1036 
1037