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
50static const char* kMountServerSettings = "mount_server";
51static const char* kMountFlagsKeyExtension = " mount flags";
52
53static const char* kInitialMountEvent = "initial_volumes_mounted";
54
55
56class MountVisitor : public BDiskDeviceVisitor {
57public:
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
68private:
69			bool				_WasPreviouslyMounted(const BPath& path,
70									const BPartition* partition);
71
72private:
73			mount_mode			fNormalMode;
74			mount_mode			fRemovableMode;
75			bool				fInitialRescan;
76			BMessage&			fPrevious;
77			partition_id		fOnlyOnDeviceID;
78};
79
80
81class MountArchivedVisitor : public BDiskDeviceVisitor {
82public:
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
91private:
92			int					_Score(BPartition* partition);
93
94private:
95			const BDiskDeviceList& fDevices;
96			const BMessage&		fArchived;
97			int					fBestScore;
98			partition_id		fBestID;
99};
100
101
102static bool
103BootedInSafeMode()
104{
105	const char* safeMode = getenv("SAFEMODE");
106	return safeMode != NULL && strcmp(safeMode, "yes") == 0;
107}
108
109
110class ArchiveVisitor : public BDiskDeviceVisitor {
111public:
112								ArchiveVisitor(BMessage& message);
113	virtual						~ArchiveVisitor();
114
115	virtual	bool				Visit(BDiskDevice* device);
116	virtual	bool				Visit(BPartition* partition, int32 level);
117
118private:
119			BMessage&			fMessage;
120};
121
122
123// #pragma mark - MountVisitor
124
125
126MountVisitor::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
138bool
139MountVisitor::Visit(BDiskDevice* device)
140{
141	return Visit(device, 0);
142}
143
144
145bool
146MountVisitor::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
206bool
207MountVisitor::_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
226MountArchivedVisitor::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
237MountArchivedVisitor::~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
248bool
249MountArchivedVisitor::Visit(BDiskDevice* device)
250{
251	return Visit(device, 0);
252}
253
254
255bool
256MountArchivedVisitor::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
271int
272MountArchivedVisitor::_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
307ArchiveVisitor::ArchiveVisitor(BMessage& message)
308	:
309	fMessage(message)
310{
311}
312
313
314ArchiveVisitor::~ArchiveVisitor()
315{
316}
317
318
319bool
320ArchiveVisitor::Visit(BDiskDevice* device)
321{
322	return Visit(device, 0);
323}
324
325
326bool
327ArchiveVisitor::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
351AutoMounter::AutoMounter()
352	:
353	BServer(kMountServerSignature, true, 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
374AutoMounter::~AutoMounter()
375{
376	BLaunchRoster().UnregisterEvent(this, kInitialMountEvent);
377	BDiskDeviceRoster().StopWatching(this);
378}
379
380
381void
382AutoMounter::ReadyToRun()
383{
384	// Do initial scan
385	_MountVolumes(fNormalMode, fRemovableMode, true);
386	BLaunchRoster().NotifyEvent(this, kInitialMountEvent);
387}
388
389
390void
391AutoMounter::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
539bool
540AutoMounter::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
555void
556AutoMounter::_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
582void
583AutoMounter::_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) {
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
612bool
613AutoMounter::_SuggestForceUnmount(const char* name, status_t error)
614{
615	char text[1024];
616	snprintf(text, sizeof(text),
617		B_TRANSLATE("Could not unmount disk \"%s\":\n\t%s\n\n"
618			"Should unmounting be forced?\n\n"
619			"Note: If an application is currently writing to the volume, "
620			"unmounting it now might result in loss of data.\n"),
621		name, strerror(error));
622
623	BAlert* alert = new BAlert(B_TRANSLATE("Force unmount"), text,
624		B_TRANSLATE("Cancel"), B_TRANSLATE("Force unmount"), NULL,
625		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
626	alert->SetShortcut(0, B_ESCAPE);
627	int32 choice = alert->Go();
628
629	return choice == 1;
630}
631
632
633void
634AutoMounter::_ReportUnmountError(const char* name, status_t error)
635{
636	char text[512];
637	snprintf(text, sizeof(text), B_TRANSLATE("Could not unmount disk "
638		"\"%s\":\n\t%s"), name, strerror(error));
639
640	BAlert* alert = new BAlert(B_TRANSLATE("Unmount error"), text,
641		B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
642	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
643	alert->Go(NULL);
644}
645
646
647void
648AutoMounter::_UnmountAndEjectVolume(BPartition* partition, BPath& mountPoint,
649	const char* name)
650{
651	BDiskDevice deviceStorage;
652	BDiskDevice* device;
653	if (partition == NULL) {
654		// Try to retrieve partition
655		BDiskDeviceRoster().FindPartitionByMountPoint(mountPoint.Path(),
656			&deviceStorage, &partition);
657			device = &deviceStorage;
658	} else {
659		device = partition->Device();
660	}
661
662	status_t status;
663	if (partition != NULL)
664		status = partition->Unmount();
665	else
666		status = fs_unmount_volume(mountPoint.Path(), 0);
667
668	if (status != B_OK) {
669		if (!_SuggestForceUnmount(name, status))
670			return;
671
672		if (partition != NULL)
673			status = partition->Unmount(B_FORCE_UNMOUNT);
674		else
675			status = fs_unmount_volume(mountPoint.Path(), B_FORCE_UNMOUNT);
676	}
677
678	if (status != B_OK) {
679		_ReportUnmountError(name, status);
680		return;
681	}
682
683	if (fEjectWhenUnmounting && partition != NULL) {
684		// eject device if it doesn't have any mounted partitions left
685		class IsMountedVisitor : public BDiskDeviceVisitor {
686		public:
687			IsMountedVisitor()
688				:
689				fHasMounted(false)
690			{
691			}
692
693			virtual bool Visit(BDiskDevice* device)
694			{
695				return Visit(device, 0);
696			}
697
698			virtual bool Visit(BPartition* partition, int32 level)
699			{
700				if (partition->IsMounted()) {
701					fHasMounted = true;
702					return true;
703				}
704
705				return false;
706			}
707
708			bool HasMountedPartitions() const
709			{
710				return fHasMounted;
711			}
712
713		private:
714			bool	fHasMounted;
715		} visitor;
716
717		device->VisitEachDescendant(&visitor);
718
719		if (!visitor.HasMountedPartitions())
720			device->Eject();
721	}
722
723	// remove the directory if it's a directory in rootfs
724	if (dev_for_path(mountPoint.Path()) == dev_for_path("/"))
725		rmdir(mountPoint.Path());
726}
727
728
729void
730AutoMounter::_UnmountAndEjectVolume(BMessage* message)
731{
732	int32 id;
733	if (message->FindInt32("id", &id) == B_OK) {
734		BDiskDeviceRoster roster;
735		BPartition *partition;
736		BDiskDevice device;
737		if (roster.GetPartitionWithID(id, &device, &partition) != B_OK)
738			return;
739
740		BPath path;
741		if (partition->GetMountPoint(&path) == B_OK)
742			_UnmountAndEjectVolume(partition, path, partition->ContentName());
743	} else {
744		// see if we got a dev_t
745
746		dev_t device;
747		if (message->FindInt32("device_id", &device) != B_OK)
748			return;
749
750		BVolume volume(device);
751		status_t status = volume.InitCheck();
752
753		char name[B_FILE_NAME_LENGTH];
754		if (status == B_OK)
755			status = volume.GetName(name);
756		if (status < B_OK)
757			snprintf(name, sizeof(name), "device:%" B_PRIdDEV, device);
758
759		BPath path;
760		if (status == B_OK) {
761			BDirectory mountPoint;
762			status = volume.GetRootDirectory(&mountPoint);
763			if (status == B_OK)
764				status = path.SetTo(&mountPoint, ".");
765		}
766
767		if (status == B_OK)
768			_UnmountAndEjectVolume(NULL, path, name);
769	}
770}
771
772
773void
774AutoMounter::_FromMode(mount_mode mode, bool& all, bool& bfs, bool& restore)
775{
776	all = bfs = restore = false;
777
778	switch (mode) {
779		case kAllVolumes:
780			all = true;
781			break;
782		case kOnlyBFSVolumes:
783			bfs = true;
784			break;
785		case kRestorePreviousVolumes:
786			restore = true;
787			break;
788
789		default:
790			break;
791	}
792}
793
794
795mount_mode
796AutoMounter::_ToMode(bool all, bool bfs, bool restore)
797{
798	if (all)
799		return kAllVolumes;
800	if (bfs)
801		return kOnlyBFSVolumes;
802	if (restore)
803		return kRestorePreviousVolumes;
804
805	return kNoVolumes;
806}
807
808
809void
810AutoMounter::_ReadSettings()
811{
812	BPath directoryPath;
813	if (find_directory(B_USER_SETTINGS_DIRECTORY, &directoryPath, true)
814		!= B_OK) {
815		return;
816	}
817
818	BPath path(directoryPath);
819	path.Append(kMountServerSettings);
820	fPrefsFile.SetTo(path.Path(), O_RDWR);
821
822	if (fPrefsFile.InitCheck() != B_OK) {
823		// no prefs file yet, create a new one
824
825		BDirectory dir(directoryPath.Path());
826		dir.CreateFile(kMountServerSettings, &fPrefsFile);
827		return;
828	}
829
830	ssize_t settingsSize = (ssize_t)fPrefsFile.Seek(0, SEEK_END);
831	if (settingsSize == 0)
832		return;
833
834	ASSERT(settingsSize != 0);
835	char *buffer = new(std::nothrow) char[settingsSize];
836	if (buffer == NULL) {
837		PRINT(("error writing automounter settings, out of memory\n"));
838		return;
839	}
840
841	fPrefsFile.Seek(0, 0);
842	if (fPrefsFile.Read(buffer, (size_t)settingsSize) != settingsSize) {
843		PRINT(("error reading automounter settings\n"));
844		delete [] buffer;
845		return;
846	}
847
848	BMessage message('stng');
849	status_t result = message.Unflatten(buffer);
850	if (result != B_OK) {
851		PRINT(("error %s unflattening automounter settings, size %" B_PRIdSSIZE "\n",
852			strerror(result), settingsSize));
853		delete [] buffer;
854		return;
855	}
856
857	delete [] buffer;
858
859	// update flags and modes from the message
860	_UpdateSettingsFromMessage(&message);
861	// copy the previously mounted partitions
862	fSettings = message;
863}
864
865
866void
867AutoMounter::_WriteSettings()
868{
869	if (fPrefsFile.InitCheck() != B_OK)
870		return;
871
872	BMessage message('stng');
873	_GetSettings(&message);
874
875	ssize_t settingsSize = message.FlattenedSize();
876
877	char* buffer = new(std::nothrow) char[settingsSize];
878	if (buffer == NULL) {
879		PRINT(("error writing automounter settings, out of memory\n"));
880		return;
881	}
882
883	status_t result = message.Flatten(buffer, settingsSize);
884
885	fPrefsFile.Seek(0, SEEK_SET);
886	fPrefsFile.SetSize(0);
887
888	result = fPrefsFile.Write(buffer, (size_t)settingsSize);
889	if (result != settingsSize)
890		PRINT(("error writing automounter settings, %s\n", strerror(result)));
891
892	delete [] buffer;
893}
894
895
896void
897AutoMounter::_UpdateSettingsFromMessage(BMessage* message)
898{
899	// auto mounter settings
900
901	bool all, bfs, restore;
902	if (message->FindBool("autoMountAll", &all) != B_OK)
903		all = true;
904	if (message->FindBool("autoMountAllBFS", &bfs) != B_OK)
905		bfs = false;
906
907	fRemovableMode = _ToMode(all, bfs, false);
908
909	// initial mount settings
910
911	if (message->FindBool("initialMountAll", &all) != B_OK)
912		all = false;
913	if (message->FindBool("initialMountAllBFS", &bfs) != B_OK)
914		bfs = false;
915	if (message->FindBool("initialMountRestore", &restore) != B_OK)
916		restore = true;
917
918	fNormalMode = _ToMode(all, bfs, restore);
919
920	// eject settings
921	bool eject;
922	if (message->FindBool("ejectWhenUnmounting", &eject) == B_OK)
923		fEjectWhenUnmounting = eject;
924}
925
926
927void
928AutoMounter::_GetSettings(BMessage *message)
929{
930	message->MakeEmpty();
931
932	bool all, bfs, restore;
933
934	_FromMode(fNormalMode, all, bfs, restore);
935	message->AddBool("initialMountAll", all);
936	message->AddBool("initialMountAllBFS", bfs);
937	message->AddBool("initialMountRestore", restore);
938
939	_FromMode(fRemovableMode, all, bfs, restore);
940	message->AddBool("autoMountAll", all);
941	message->AddBool("autoMountAllBFS", bfs);
942
943	message->AddBool("ejectWhenUnmounting", fEjectWhenUnmounting);
944
945	// Save mounted volumes so we can optionally mount them on next
946	// startup
947	ArchiveVisitor visitor(*message);
948	BDiskDeviceRoster().VisitEachMountedPartition(&visitor);
949}
950
951
952/*static*/ bool
953AutoMounter::_SuggestMountFlags(const BPartition* partition, uint32* _flags)
954{
955	uint32 mountFlags = 0;
956
957	bool askReadOnly = true;
958	bool isBFS = false;
959
960	if (partition->ContentType() != NULL
961		&& strcmp(partition->ContentType(), kPartitionTypeBFS) == 0) {
962		askReadOnly = false;
963		isBFS = true;
964	}
965
966	BDiskSystem diskSystem;
967	status_t status = partition->GetDiskSystem(&diskSystem);
968	if (status == B_OK && !diskSystem.SupportsWriting())
969		askReadOnly = false;
970
971	if (partition->IsReadOnly())
972		askReadOnly = false;
973
974	if (askReadOnly) {
975		// Suggest to the user to mount read-only until Haiku is more mature.
976		BString string;
977		if (partition->ContentName() != NULL) {
978			char buffer[512];
979			snprintf(buffer, sizeof(buffer),
980				B_TRANSLATE("Mounting volume '%s'\n\n"),
981				partition->ContentName());
982			string << buffer;
983		} else
984			string << B_TRANSLATE("Mounting volume <unnamed volume>\n\n");
985
986		// TODO: Use distro name instead of "Haiku"...
987		string << B_TRANSLATE("The file system on this volume is not the "
988			"Be file system. It is recommended to mount it in read-only "
989			"mode, to prevent unintentional data loss because of bugs "
990			"in Haiku.");
991
992		BAlert* alert = new BAlert(B_TRANSLATE("Mount warning"),
993			string.String(), B_TRANSLATE("Mount read/write"),
994			B_TRANSLATE("Cancel"), B_TRANSLATE("Mount read-only"),
995			B_WIDTH_FROM_WIDEST, B_WARNING_ALERT);
996		alert->SetShortcut(1, B_ESCAPE);
997		int32 choice = alert->Go();
998		switch (choice) {
999			case 0:
1000				break;
1001			case 1:
1002				return false;
1003			case 2:
1004				mountFlags |= B_MOUNT_READ_ONLY;
1005				break;
1006		}
1007	}
1008
1009	*_flags = mountFlags;
1010	return true;
1011}
1012
1013
1014// #pragma mark -
1015
1016
1017int
1018main(int argc, char* argv[])
1019{
1020	AutoMounter app;
1021
1022	app.Run();
1023	return 0;
1024}
1025
1026
1027