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