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