text: /*
   * Copyright 2007-2018, Haiku, Inc. All rights reserved.
   * Distributed under the terms of the MIT License.
   *
   * Authors:
   *		Stephan Aßmus, superstippi@gmx.de
   *		Axel Dörfler, axeld@pinc-software.de
   */
  
  
  #include "AutoMounter.h"
  
  #include <new>
  
  #include <string.h>
  #include <unistd.h>
  
  #include <Alert.h>
  #include <AutoLocker.h>
  #include <Catalog.h>
  #include <Debug.h>
  #include <Directory.h>
  #include <DiskDevice.h>
  #include <DiskDeviceRoster.h>
  #include <DiskDeviceList.h>
  #include <DiskDeviceTypes.h>
  #include <DiskSystem.h>
  #include <FindDirectory.h>
  #include <fs_info.h>
  #include <fs_volume.h>
  #include <LaunchRoster.h>
  #include <Locale.h>
  #include <Message.h>
  #include <Node.h>
  #include <NodeMonitor.h>
  #include <Path.h>
  #include <PropertyInfo.h>
  #include <String.h>
  #include <VolumeRoster.h>
  
  #include "MountServer.h"
  
  #include "Utilities.h"
  
  
  #undef B_TRANSLATION_CONTEXT
  #define B_TRANSLATION_CONTEXT "AutoMounter"
  
  
  static const char* kMountServerSettings = "mount_server";
  static const char* kMountFlagsKeyExtension = " mount flags";
  
  static const char* kInitialMountEvent = "initial_volumes_mounted";
  
  
  class MountVisitor : public BDiskDeviceVisitor {
  public:
  								MountVisitor(mount_mode normalMode,
  									mount_mode removableMode,
  									bool initialRescan, BMessage& previous,
  									partition_id deviceID);
  	virtual						~MountVisitor()
  									{}
  
  	virtual	bool				Visit(BDiskDevice* device);
  	virtual	bool				Visit(BPartition* partition, int32 level);
  
  private:
  			bool				_WasPreviouslyMounted(const BPath& path,
  									const BPartition* partition);
  
  private:
  			mount_mode			fNormalMode;
  			mount_mode			fRemovableMode;
  			bool				fInitialRescan;
  			BMessage&			fPrevious;
  			partition_id		fOnlyOnDeviceID;
  };
  
  
  class MountArchivedVisitor : public BDiskDeviceVisitor {
  public:
  								MountArchivedVisitor(
  									const BDiskDeviceList& devices,
  									const BMessage& archived);
  	virtual						~MountArchivedVisitor();
  
  	virtual	bool				Visit(BDiskDevice* device);
  	virtual	bool				Visit(BPartition* partition, int32 level);
  
  private:
  			int					_Score(BPartition* partition);
  
  private:
  			const BDiskDeviceList& fDevices;
  			const BMessage&		fArchived;
  			int					fBestScore;
  			partition_id		fBestID;
  };
  
  
  static bool
  BootedInSafeMode()
  {
  	const char* safeMode = getenv("SAFEMODE");
  	return safeMode != NULL && strcmp(safeMode, "yes") == 0;
  }
  
  
  class ArchiveVisitor : public BDiskDeviceVisitor {
  public:
  								ArchiveVisitor(BMessage& message);
  	virtual						~ArchiveVisitor();
  
  	virtual	bool				Visit(BDiskDevice* device);
  	virtual	bool				Visit(BPartition* partition, int32 level);
  
  private:
  			BMessage&			fMessage;
  };
  
  
  // #pragma mark - MountVisitor
  
  
  MountVisitor::MountVisitor(mount_mode normalMode, mount_mode removableMode,
  		bool initialRescan, BMessage& previous, partition_id deviceID)
  	:
  	fNormalMode(normalMode),
  	fRemovableMode(removableMode),
  	fInitialRescan(initialRescan),
  	fPrevious(previous),
  	fOnlyOnDeviceID(deviceID)
  {
  }
  
  
  bool
  MountVisitor::Visit(BDiskDevice* device)
  {
  	return Visit(device, 0);
  }
  
  
  bool
  MountVisitor::Visit(BPartition* partition, int32 level)
  {
  	if (fOnlyOnDeviceID >= 0) {
  		// only mount partitions on the given device id
  		// or if the partition ID is already matched
  		BPartition* device = partition;
  		while (device->Parent() != NULL) {
  			if (device->ID() == fOnlyOnDeviceID) {
  				// we are happy
  				break;
  			}
  			device = device->Parent();
  		}
  		if (device->ID() != fOnlyOnDeviceID)
  			return false;
  	}
  
  	mount_mode mode = !fInitialRescan && partition->Device()->IsRemovableMedia()
  		? fRemovableMode : fNormalMode;
  	if (mode == kNoVolumes || partition->IsMounted()
  		|| !partition->ContainsFileSystem()) {
  		return false;
  	}
  
  	BPath path;
  	if (partition->GetPath(&path) != B_OK)
  		return false;
  
  	if (mode == kRestorePreviousVolumes) {
  		// mount all volumes that were stored in the settings file
  		if (!_WasPreviouslyMounted(path, partition))
  			return false;
  	} else if (mode == kOnlyBFSVolumes) {
  		if (partition->ContentType() == NULL
  			|| strcmp(partition->ContentType(), kPartitionTypeBFS))
  			return false;
  	}
  
  	uint32 mountFlags;
  	if (!fInitialRescan) {
  		// Ask the user about mount flags if this is not the
  		// initial scan.
  		if (!AutoMounter::_SuggestMountFlags(partition, &mountFlags))
  			return false;
  	} else {
  		BString mountFlagsKey(path.Path());
  		mountFlagsKey << kMountFlagsKeyExtension;
  		if (fPrevious.FindInt32(mountFlagsKey.String(),
  				(int32*)&mountFlags) < B_OK) {
  			mountFlags = 0;
  		}
  	}
  
  	if (partition->Mount(NULL, mountFlags) != B_OK) {
  		// TODO: Error to syslog
  	}
  	return false;
  }
  
  
  bool
  MountVisitor::_WasPreviouslyMounted(const BPath& path,
  	const BPartition* partition)
  {
  	// We only check the legacy config data here; the current method
  	// is implemented in ArchivedVolumeVisitor -- this can be removed
  	// some day.
  	const char* volumeName = NULL;
  	if (partition->ContentName() == NULL
  		|| fPrevious.FindString(path.Path(), &volumeName) != B_OK
  		|| strcmp(volumeName, partition->ContentName()) != 0)
  		return false;
  
  	return true;
  }
  
  
  // #pragma mark - MountArchivedVisitor
  
  
  MountArchivedVisitor::MountArchivedVisitor(const BDiskDeviceList& devices,
  		const BMessage& archived)
  	:
  	fDevices(devices),
  	fArchived(archived),
  	fBestScore(-1),
  	fBestID(-1)
  {
  }
  
  
  MountArchivedVisitor::~MountArchivedVisitor()
  {
  	if (fBestScore >= 6) {
  		uint32 mountFlags = fArchived.GetUInt32("mountFlags", 0);
  		BPartition* partition = fDevices.PartitionWithID(fBestID);
  		if (partition != NULL)
  			partition->Mount(NULL, mountFlags);
  	}
  }
  
  
  bool
  MountArchivedVisitor::Visit(BDiskDevice* device)
  {
  	return Visit(device, 0);
  }
  
  
  bool
  MountArchivedVisitor::Visit(BPartition* partition, int32 level)
  {
  	if (partition->IsMounted() || !partition->ContainsFileSystem())
  		return false;
  
  	int score = _Score(partition);
  	if (score > fBestScore) {
  		fBestScore = score;
  		fBestID = partition->ID();
  	}
  
  	return false;
  }
  
  
  int
  MountArchivedVisitor::_Score(BPartition* partition)
  {
  	BPath path;
  	if (partition->GetPath(&path) != B_OK)
  		return false;
  
  	int score = 0;
  
  	int64 capacity = fArchived.GetInt64("capacity", 0);
  	if (capacity == partition->ContentSize())
  		score += 4;
  
  	BString deviceName = fArchived.GetString("deviceName");
  	if (deviceName == path.Path())
  		score += 3;
  
  	BString volumeName = fArchived.GetString("volumeName");
  	if (volumeName == partition->ContentName())
  		score += 2;
  
  	BString fsName = fArchived.FindString("fsName");
  	if (fsName == partition->ContentType())
  		score += 1;
  
  	uint32 blockSize = fArchived.GetUInt32("blockSize", 0);
  	if (blockSize == partition->BlockSize())
  		score += 1;
  
  	return score;
  }
  
  
  // #pragma mark - ArchiveVisitor
  
  
  ArchiveVisitor::ArchiveVisitor(BMessage& message)
  	:
  	fMessage(message)
  {
  }
  
  
  ArchiveVisitor::~ArchiveVisitor()
  {
  }
  
  
  bool
  ArchiveVisitor::Visit(BDiskDevice* device)
  {
  	return Visit(device, 0);
  }
  
  
  bool
  ArchiveVisitor::Visit(BPartition* partition, int32 level)
  {
  	if (!partition->ContainsFileSystem())
  		return false;
  
  	BPath path;
  	if (partition->GetPath(&path) != B_OK)
  		return false;
  
  	BMessage info;
  	info.AddUInt32("blockSize", partition->BlockSize());
  	info.AddInt64("capacity", partition->ContentSize());
  	info.AddString("deviceName", path.Path());
  	info.AddString("volumeName", partition->ContentName());
  	info.AddString("fsName", partition->ContentType());
  
  	fMessage.AddMessage("info", &info);
  	return false;
  }
  
  
  // #pragma mark -
  
  
  AutoMounter::AutoMounter()
  	:
  	BServer(kMountServerSignature, true, NULL),
  	fNormalMode(kRestorePreviousVolumes),
  	fRemovableMode(kAllVolumes),
  	fEjectWhenUnmounting(true)
  {
  	set_thread_priority(Thread(), B_LOW_PRIORITY);
  
  	if (!BootedInSafeMode()) {
  		_ReadSettings();
  	} else {
  		// defeat automounter in safe mode, don't even care about the settings
  		fNormalMode = kNoVolumes;
  		fRemovableMode = kNoVolumes;
  	}
  
  	BDiskDeviceRoster().StartWatching(this,
  		B_DEVICE_REQUEST_DEVICE | B_DEVICE_REQUEST_DEVICE_LIST);
  	BLaunchRoster().RegisterEvent(this, kInitialMountEvent, B_STICKY_EVENT);
  }
  
  
  AutoMounter::~AutoMounter()
  {
  	BLaunchRoster().UnregisterEvent(this, kInitialMountEvent);
  	BDiskDeviceRoster().StopWatching(this);
  }
  
  
  void
  AutoMounter::ReadyToRun()
  {
  	// Do initial scan
  	_MountVolumes(fNormalMode, fRemovableMode, true);
  	BLaunchRoster().NotifyEvent(this, kInitialMountEvent);
  }
  
  
  void
  AutoMounter::MessageReceived(BMessage* message)
  {
  	switch (message->what) {
  		case kMountVolume:
  			_MountVolume(message);
  			break;
  
  		case kUnmountVolume:
  			_UnmountAndEjectVolume(message);
  			break;
  
  		case kSetAutomounterParams:
  		{
  			bool rescanNow = false;
  			message->FindBool("rescanNow", &rescanNow);
  
  			_UpdateSettingsFromMessage(message);
  			_GetSettings(&fSettings);
  			_WriteSettings();
  
  			if (rescanNow)
  				_MountVolumes(fNormalMode, fRemovableMode);
  			break;
  		}
  
  		case kGetAutomounterParams:
  		{
  			BMessage reply;
  			_GetSettings(&reply);
  			message->SendReply(&reply);
  			break;
  		}
  
  		case kMountAllNow:
  			_MountVolumes(kAllVolumes, kAllVolumes);
  			break;
  
  		case B_DEVICE_UPDATE:
  			int32 event;
  			if (message->FindInt32("event", &event) != B_OK
  				|| (event != B_DEVICE_MEDIA_CHANGED
  					&& event != B_DEVICE_ADDED))
  				break;
  
  			partition_id deviceID;
  			if (message->FindInt32("id", &deviceID) != B_OK)
  				break;
  
  			_MountVolumes(kNoVolumes, fRemovableMode, false, deviceID);
  			break;
  
  #if 0
  		case B_NODE_MONITOR:
  		{
  			int32 opcode;
  			if (message->FindInt32("opcode", &opcode) != B_OK)
  				break;
  
  			switch (opcode) {
  				//	The name of a mount point has changed
  				case B_ENTRY_MOVED: {
  					WRITELOG(("*** Received Mount Point Renamed Notification"));
  
  					const char *newName;
  					if (message->FindString("name", &newName) != B_OK) {
  						WRITELOG(("ERROR: Couldn't find name field in update "
  							"message"));
  						PRINT_OBJECT(*message);
  						break ;
  					}
  
  					//
  					// When the node monitor reports a move, it gives the
  					// parent device and inode that moved.  The problem is
  					// that  the inode is the inode of root *in* the filesystem,
  					// which is generally always the same number for every
  					// filesystem of a type.
  					//
  					// What we'd really like is the device that the moved
  					// volume is mounted on.  Find this by using the
  					// *new* name and directory, and then stat()ing that to
  					// find the device.
  					//
  					dev_t parentDevice;
  					if (message->FindInt32("device", &parentDevice) != B_OK) {
  						WRITELOG(("ERROR: Couldn't find 'device' field in "
  							"update message"));
  						PRINT_OBJECT(*message);
  						break;
  					}
  
  					ino_t toDirectory;
  					if (message->FindInt64("to directory", &toDirectory)
  						!= B_OK) {
  						WRITELOG(("ERROR: Couldn't find 'to directory' field "
  							"in update message"));
  						PRINT_OBJECT(*message);
  						break;
  					}
  
  					entry_ref root_entry(parentDevice, toDirectory, newName);
  
  					BNode entryNode(&root_entry);
  					if (entryNode.InitCheck() != B_OK) {
  						WRITELOG(("ERROR: Couldn't create mount point entry "
  							"node: %s/n", strerror(entryNode.InitCheck())));
  						break;
  					}
  
  					node_ref mountPointNode;
  					if (entryNode.GetNodeRef(&mountPointNode) != B_OK) {
  						WRITELOG(("ERROR: Couldn't get node ref for new mount "
  							"point"));
  						break;
  					}
  
  					WRITELOG(("Attempt to rename device %li to %s",
  						mountPointNode.device, newName));
  
  					Partition *partition = FindPartition(mountPointNode.device);
  					if (partition != NULL) {
  						WRITELOG(("Found device, changing name."));
  
  						BVolume mountVolume(partition->VolumeDeviceID());
  						BDirectory mountDir;
  						mountVolume.GetRootDirectory(&mountDir);
  						BPath dirPath(&mountDir, 0);
  
  						partition->SetMountedAt(dirPath.Path());
  						partition->SetVolumeName(newName);
  						break;
  					} else {
  						WRITELOG(("ERROR: Device %li does not appear to be "
  							"present", mountPointNode.device));
  					}
  				}
  			}
  			break;
  		}
  #endif
  
  		default:
  			BLooper::MessageReceived(message);
  			break;
  	}
  }
  
  
  bool
  AutoMounter::QuitRequested()
  {
  	if (!BootedInSafeMode()) {
  		// Don't write out settings in safe mode - this would overwrite the
  		// normal, non-safe mode settings.
  		_WriteSettings();
  	}
  
  	return true;
  }
  
  
  // #pragma mark - private methods
  
  
  void
  AutoMounter::_MountVolumes(mount_mode normal, mount_mode removable,
  	bool initialRescan, partition_id deviceID)
  {
  	if (normal == kNoVolumes && removable == kNoVolumes)
  		return;
  
  	BDiskDeviceList devices;
  	status_t status = devices.Fetch();
  	if (status != B_OK)
  		return;
  
  	if (normal == kRestorePreviousVolumes) {
  		BMessage archived;
  		for (int32 index = 0;
  				fSettings.FindMessage("info", index, &archived) == B_OK;
  				index++) {
  			MountArchivedVisitor visitor(devices, archived);
  			devices.VisitEachPartition(&visitor);
  		}
  	}
  
  	MountVisitor visitor(normal, removable, initialRescan, fSettings, deviceID);
  	devices.VisitEachPartition(&visitor);
  }
  
  
  void
  AutoMounter::_MountVolume(const BMessage* message)
  {
  	int32 id;
  	if (message->FindInt32("id", &id) != B_OK)
  		return;
  
  	BDiskDeviceRoster roster;
  	BPartition *partition;
  	BDiskDevice device;
  	if (roster.GetPartitionWithID(id, &device, &partition) != B_OK)
  		return;
  
  	uint32 mountFlags;
  	if (!_SuggestMountFlags(partition, &mountFlags))
  		return;
  
  	status_t status = partition->Mount(NULL, mountFlags);
  	if (status < B_OK) {
  		char text[512];
  		snprintf(text, sizeof(text),
  			B_TRANSLATE("Error mounting volume:\n\n%s"), strerror(status));
  		BAlert* alert = new BAlert(B_TRANSLATE("Mount error"), text,
  			B_TRANSLATE("OK"));
  		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
  		alert->Go(NULL);
  	}
  }
  
  
  bool
  AutoMounter::_SuggestForceUnmount(const char* name, status_t error)
  {
  	char text[1024];
  	snprintf(text, sizeof(text),
  		B_TRANSLATE("Could not unmount disk \"%s\":\n\t%s\n\n"
  			"Should unmounting be forced?\n\n"
  			"Note: If an application is currently writing to the volume, "
  			"unmounting it now might result in loss of data.\n"),
  		name, strerror(error));
  
  	BAlert* alert = new BAlert(B_TRANSLATE("Force unmount"), text,
  		B_TRANSLATE("Cancel"), B_TRANSLATE("Force unmount"), NULL,
  		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
  	alert->SetShortcut(0, B_ESCAPE);
  	int32 choice = alert->Go();
  
  	return choice == 1;
  }
  
  
  void
  AutoMounter::_ReportUnmountError(const char* name, status_t error)
  {
  	char text[512];
  	snprintf(text, sizeof(text), B_TRANSLATE("Could not unmount disk "
  		"\"%s\":\n\t%s"), name, strerror(error));
  
  	BAlert* alert = new BAlert(B_TRANSLATE("Unmount error"), text,
  		B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
  	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
  	alert->Go(NULL);
  }
  
  
  void
  AutoMounter::_UnmountAndEjectVolume(BPartition* partition, BPath& mountPoint,
  	const char* name)
  {
  	BDiskDevice deviceStorage;
  	BDiskDevice* device;
  	if (partition == NULL) {
  		// Try to retrieve partition
  		BDiskDeviceRoster().FindPartitionByMountPoint(mountPoint.Path(),
  			&deviceStorage, &partition);
  			device = &deviceStorage;
  	} else {
  		device = partition->Device();
  	}
  
  	status_t status;
  	if (partition != NULL)
  		status = partition->Unmount();
  	else
  		status = fs_unmount_volume(mountPoint.Path(), 0);
  
  	if (status != B_OK) {
  		if (!_SuggestForceUnmount(name, status))
  			return;
  
  		if (partition != NULL)
  			status = partition->Unmount(B_FORCE_UNMOUNT);
  		else
  			status = fs_unmount_volume(mountPoint.Path(), B_FORCE_UNMOUNT);
  	}
  
  	if (status != B_OK) {
  		_ReportUnmountError(name, status);
  		return;
  	}
  
  	if (fEjectWhenUnmounting && partition != NULL) {
  		// eject device if it doesn't have any mounted partitions left
  		class IsMountedVisitor : public BDiskDeviceVisitor {
  		public:
  			IsMountedVisitor()
  				:
  				fHasMounted(false)
  			{
  			}
  
  			virtual bool Visit(BDiskDevice* device)
  			{
  				return Visit(device, 0);
  			}
  
  			virtual bool Visit(BPartition* partition, int32 level)
  			{
  				if (partition->IsMounted()) {
  					fHasMounted = true;
  					return true;
  				}
  
  				return false;
  			}
  
  			bool HasMountedPartitions() const
  			{
  				return fHasMounted;
  			}
  
  		private:
  			bool	fHasMounted;
  		} visitor;
  
  		device->VisitEachDescendant(&visitor);
  
  		if (!visitor.HasMountedPartitions())
  			device->Eject();
  	}
  
  	// remove the directory if it's a directory in rootfs
  	if (dev_for_path(mountPoint.Path()) == dev_for_path("/"))
  		rmdir(mountPoint.Path());
  }
  
  
  void
  AutoMounter::_UnmountAndEjectVolume(BMessage* message)
  {
  	int32 id;
  	if (message->FindInt32("id", &id) == B_OK) {
  		BDiskDeviceRoster roster;
  		BPartition *partition;
  		BDiskDevice device;
  		if (roster.GetPartitionWithID(id, &device, &partition) != B_OK)
  			return;
  
  		BPath path;
  		if (partition->GetMountPoint(&path) == B_OK)
  			_UnmountAndEjectVolume(partition, path, partition->ContentName());
  	} else {
  		// see if we got a dev_t
  
  		dev_t device;
  		if (message->FindInt32("device_id", &device) != B_OK)
  			return;
  
  		BVolume volume(device);
  		status_t status = volume.InitCheck();
  
  		char name[B_FILE_NAME_LENGTH];
  		if (status == B_OK)
  			status = volume.GetName(name);
  		if (status < B_OK)
  			snprintf(name, sizeof(name), "device:%" B_PRIdDEV, device);
  
  		BPath path;
  		if (status == B_OK) {
  			BDirectory mountPoint;
  			status = volume.GetRootDirectory(&mountPoint);
  			if (status == B_OK)
  				status = path.SetTo(&mountPoint, ".");
  		}
  
  		if (status == B_OK)
  			_UnmountAndEjectVolume(NULL, path, name);
  	}
  }
  
  
  void
  AutoMounter::_FromMode(mount_mode mode, bool& all, bool& bfs, bool& restore)
  {
  	all = bfs = restore = false;
  
  	switch (mode) {
  		case kAllVolumes:
  			all = true;
  			break;
  		case kOnlyBFSVolumes:
  			bfs = true;
  			break;
  		case kRestorePreviousVolumes:
  			restore = true;
  			break;
  
  		default:
  			break;
  	}
  }
  
  
  mount_mode
  AutoMounter::_ToMode(bool all, bool bfs, bool restore)
  {
  	if (all)
  		return kAllVolumes;
  	if (bfs)
  		return kOnlyBFSVolumes;
  	if (restore)
  		return kRestorePreviousVolumes;
  
  	return kNoVolumes;
  }
  
  
  void
  AutoMounter::_ReadSettings()
  {
  	BPath directoryPath;
  	if (find_directory(B_USER_SETTINGS_DIRECTORY, &directoryPath, true)
  		!= B_OK) {
  		return;
  	}
  
  	BPath path(directoryPath);
  	path.Append(kMountServerSettings);
  	fPrefsFile.SetTo(path.Path(), O_RDWR);
  
  	if (fPrefsFile.InitCheck() != B_OK) {
  		// no prefs file yet, create a new one
  
  		BDirectory dir(directoryPath.Path());
  		dir.CreateFile(kMountServerSettings, &fPrefsFile);
  		return;
  	}
  
  	ssize_t settingsSize = (ssize_t)fPrefsFile.Seek(0, SEEK_END);
  	if (settingsSize == 0)
  		return;
  
  	ASSERT(settingsSize != 0);
  	char *buffer = new(std::nothrow) char[settingsSize];
  	if (buffer == NULL) {
  		PRINT(("error writing automounter settings, out of memory\n"));
  		return;
  	}
  
  	fPrefsFile.Seek(0, 0);
  	if (fPrefsFile.Read(buffer, (size_t)settingsSize) != settingsSize) {
  		PRINT(("error reading automounter settings\n"));
  		delete [] buffer;
  		return;
  	}
  
  	BMessage message('stng');
  	status_t result = message.Unflatten(buffer);
  	if (result != B_OK) {
  		PRINT(("error %s unflattening automounter settings, size %" B_PRIdSSIZE "\n",
  			strerror(result), settingsSize));
  		delete [] buffer;
  		return;
  	}
  
  	delete [] buffer;
  
  	// update flags and modes from the message
  	_UpdateSettingsFromMessage(&message);
  	// copy the previously mounted partitions
  	fSettings = message;
  }
  
  
  void
  AutoMounter::_WriteSettings()
  {
  	if (fPrefsFile.InitCheck() != B_OK)
  		return;
  
  	BMessage message('stng');
  	_GetSettings(&message);
  
  	ssize_t settingsSize = message.FlattenedSize();
  
  	char* buffer = new(std::nothrow) char[settingsSize];
  	if (buffer == NULL) {
  		PRINT(("error writing automounter settings, out of memory\n"));
  		return;
  	}
  
  	status_t result = message.Flatten(buffer, settingsSize);
  
  	fPrefsFile.Seek(0, SEEK_SET);
  	fPrefsFile.SetSize(0);
  
  	result = fPrefsFile.Write(buffer, (size_t)settingsSize);
  	if (result != settingsSize)
  		PRINT(("error writing automounter settings, %s\n", strerror(result)));
  
  	delete [] buffer;
  }
  
  
  void
  AutoMounter::_UpdateSettingsFromMessage(BMessage* message)
  {
  	// auto mounter settings
  
  	bool all, bfs, restore;
  	if (message->FindBool("autoMountAll", &all) != B_OK)
  		all = true;
  	if (message->FindBool("autoMountAllBFS", &bfs) != B_OK)
  		bfs = false;
  
  	fRemovableMode = _ToMode(all, bfs, false);
  
  	// initial mount settings
  
  	if (message->FindBool("initialMountAll", &all) != B_OK)
  		all = false;
  	if (message->FindBool("initialMountAllBFS", &bfs) != B_OK)
  		bfs = false;
  	if (message->FindBool("initialMountRestore", &restore) != B_OK)
  		restore = true;
  
  	fNormalMode = _ToMode(all, bfs, restore);
  
  	// eject settings
  	bool eject;
  	if (message->FindBool("ejectWhenUnmounting", &eject) == B_OK)
  		fEjectWhenUnmounting = eject;
  }
  
  
  void
  AutoMounter::_GetSettings(BMessage *message)
  {
  	message->MakeEmpty();
  
  	bool all, bfs, restore;
  
  	_FromMode(fNormalMode, all, bfs, restore);
  	message->AddBool("initialMountAll", all);
  	message->AddBool("initialMountAllBFS", bfs);
  	message->AddBool("initialMountRestore", restore);
  
  	_FromMode(fRemovableMode, all, bfs, restore);
  	message->AddBool("autoMountAll", all);
  	message->AddBool("autoMountAllBFS", bfs);
  
  	message->AddBool("ejectWhenUnmounting", fEjectWhenUnmounting);
  
  	// Save mounted volumes so we can optionally mount them on next
  	// startup
  	ArchiveVisitor visitor(*message);
  	BDiskDeviceRoster().VisitEachMountedPartition(&visitor);
  }
  
  
  /*static*/ bool
  AutoMounter::_SuggestMountFlags(const BPartition* partition, uint32* _flags)
  {
  	uint32 mountFlags = 0;
  
  	bool askReadOnly = true;
  	bool isBFS = false;
  
  	if (partition->ContentType() != NULL
  		&& strcmp(partition->ContentType(), kPartitionTypeBFS) == 0) {
  		askReadOnly = false;
  		isBFS = true;
  	}
  
  	BDiskSystem diskSystem;
  	status_t status = partition->GetDiskSystem(&diskSystem);
  	if (status == B_OK && !diskSystem.SupportsWriting())
  		askReadOnly = false;
  
  	if (partition->IsReadOnly())
  		askReadOnly = false;
  
  	if (askReadOnly) {
  		// Suggest to the user to mount read-only until Haiku is more mature.
  		BString string;
  		if (partition->ContentName() != NULL) {
  			char buffer[512];
  			snprintf(buffer, sizeof(buffer),
  				B_TRANSLATE("Mounting volume '%s'\n\n"),
  				partition->ContentName());
  			string << buffer;
  		} else
  			string << B_TRANSLATE("Mounting volume <unnamed volume>\n\n");
  
  		// TODO: Use distro name instead of "Haiku"...
  		string << B_TRANSLATE("The file system on this volume is not the "
  			"Be file system. It is recommended to mount it in read-only "
  			"mode, to prevent unintentional data loss because of bugs "
  			"in Haiku.");
  
  		BAlert* alert = new BAlert(B_TRANSLATE("Mount warning"),
  			string.String(), B_TRANSLATE("Mount read/write"),
  			B_TRANSLATE("Cancel"), B_TRANSLATE("Mount read-only"),
  			B_WIDTH_FROM_WIDEST, B_WARNING_ALERT);
  		alert->SetShortcut(1, B_ESCAPE);
  		int32 choice = alert->Go();
  		switch (choice) {
  			case 0:
  				break;
  			case 1:
  				return false;
  			case 2:
  				mountFlags |= B_MOUNT_READ_ONLY;
  				break;
  		}
  	}
  
  	*_flags = mountFlags;
  	return true;
  }
  
  
  // #pragma mark -
  
  
  int
  main(int argc, char* argv[])
  {
  	AutoMounter app;
  
  	app.Run();
  	return 0;
  }
  
  
  