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	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
225MountArchivedVisitor::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
236MountArchivedVisitor::~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
247bool
248MountArchivedVisitor::Visit(BDiskDevice* device)
249{
250	return Visit(device, 0);
251}
252
253
254bool
255MountArchivedVisitor::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
270int
271MountArchivedVisitor::_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
306ArchiveVisitor::ArchiveVisitor(BMessage& message)
307	:
308	fMessage(message)
309{
310}
311
312
313ArchiveVisitor::~ArchiveVisitor()
314{
315}
316
317
318bool
319ArchiveVisitor::Visit(BDiskDevice* device)
320{
321	return Visit(device, 0);
322}
323
324
325bool
326ArchiveVisitor::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
359AutoMounter::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
382AutoMounter::~AutoMounter()
383{
384	BLaunchRoster().UnregisterEvent(this, kInitialMountEvent);
385	BDiskDeviceRoster().StopWatching(this);
386}
387
388
389void
390AutoMounter::ReadyToRun()
391{
392	// Do initial scan
393	_MountVolumes(fNormalMode, fRemovableMode, true);
394	BLaunchRoster().NotifyEvent(this, kInitialMountEvent);
395}
396
397
398void
399AutoMounter::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
547bool
548AutoMounter::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
563void
564AutoMounter::_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
590void
591AutoMounter::_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
620bool
621AutoMounter::_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
644void
645AutoMounter::_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
661void
662AutoMounter::_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
743void
744AutoMounter::_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
787void
788AutoMounter::_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
809mount_mode
810AutoMounter::_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
823void
824AutoMounter::_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
880void
881AutoMounter::_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
910void
911AutoMounter::_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
941void
942AutoMounter::_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
967AutoMounter::_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
1029int
1030main(int argc, char* argv[])
1031{
1032	AutoMounter app;
1033
1034	app.Run();
1035	return 0;
1036}
1037
1038
1039