xref: /haiku/src/servers/mount/AutoMounter.cpp (revision b289aaf66bbf6e173aa90fa194fc256965f1b34d)
1 /*
2  * Copyright 2007-2009, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include "AutoMounter.h"
7 
8 #include <new>
9 
10 #include <string.h>
11 #include <unistd.h>
12 
13 #include <Alert.h>
14 #include <AutoLocker.h>
15 #include <Debug.h>
16 #include <Directory.h>
17 #include <DiskDevice.h>
18 #include <DiskDeviceRoster.h>
19 #include <DiskDeviceList.h>
20 #include <DiskDeviceTypes.h>
21 #include <DiskSystem.h>
22 #include <FindDirectory.h>
23 #include <fs_info.h>
24 #include <fs_volume.h>
25 #include <Message.h>
26 #include <Node.h>
27 #include <NodeMonitor.h>
28 #include <Path.h>
29 #include <PropertyInfo.h>
30 #include <String.h>
31 #include <VolumeRoster.h>
32 
33 //#include "AutoMounterSettings.h"
34 #include "MountServer.h"
35 
36 
37 static const char* kMountServerSettings = "mount_server";
38 static const uint32 kMsgInitialScan = 'insc';
39 static const char* kMountFlagsKeyExtension = " mount flags";
40 
41 
42 bool
43 BootedInSafeMode()
44 {
45 	const char *safeMode = getenv("SAFEMODE");
46 	return (safeMode && strcmp(safeMode, "yes") == 0);
47 }
48 
49 
50 // #pragma mark -
51 
52 
53 AutoMounter::AutoMounter()
54 	:
55 	BApplication(kMountServerSignature),
56 	fNormalMode(kRestorePreviousVolumes),
57 	fRemovableMode(kAllVolumes),
58 	fEjectWhenUnmounting(true)
59 {
60 	set_thread_priority(Thread(), B_LOW_PRIORITY);
61 
62 	if (!BootedInSafeMode()) {
63 		_ReadSettings();
64 	} else {
65 		// defeat automounter in safe mode, don't even care about the settings
66 		fNormalMode = kNoVolumes;
67 		fRemovableMode = kNoVolumes;
68 	}
69 
70 	BDiskDeviceRoster().StartWatching(this,
71 		B_DEVICE_REQUEST_DEVICE | B_DEVICE_REQUEST_DEVICE_LIST);
72 }
73 
74 
75 AutoMounter::~AutoMounter()
76 {
77 	BDiskDeviceRoster().StopWatching(this);
78 }
79 
80 
81 void
82 AutoMounter::MessageReceived(BMessage* message)
83 {
84 	switch (message->what) {
85 		case B_EXECUTE_PROPERTY:
86 		{
87 			int32 index;
88 			BMessage specifier;
89 			int32 what;
90 			const char* property = NULL;
91 			if (message->GetCurrentSpecifier(&index, &specifier, &what,
92 					&property) < B_OK
93 				|| !_ScriptReceived(message, index, &specifier, what,
94 					property)) {
95 				BApplication::MessageReceived(message);
96 			}
97 			break;
98 		}
99 
100 		case kMsgInitialScan:
101 			_MountVolumes(fNormalMode, fRemovableMode, true);
102 			break;
103 
104 		case kMountVolume:
105 			_MountVolume(message);
106 			break;
107 
108 		case kUnmountVolume:
109 			_UnmountAndEjectVolume(message);
110 			break;
111 
112 		case kSetAutomounterParams:
113 		{
114 			bool rescanNow = false;
115 			message->FindBool("rescanNow", &rescanNow);
116 
117 			_UpdateSettingsFromMessage(message);
118 			_GetSettings(&fSettings);
119 			_WriteSettings();
120 
121 			if (rescanNow)
122 				_MountVolumes(fNormalMode, fRemovableMode);
123 			break;
124 		}
125 
126 		case kGetAutomounterParams:
127 		{
128 			BMessage reply;
129 			_GetSettings(&reply);
130 			message->SendReply(&reply);
131 			break;
132 		}
133 
134 		case kMountAllNow:
135 			_MountVolumes(kAllVolumes, kAllVolumes);
136 			break;
137 
138 		case B_DEVICE_UPDATE:
139 			int32 event;
140 			if (message->FindInt32("event", &event) != B_OK
141 				|| (event != B_DEVICE_MEDIA_CHANGED
142 					&& event != B_DEVICE_ADDED))
143 				break;
144 
145 			partition_id deviceID;
146 			if (message->FindInt32("id", &deviceID) != B_OK)
147 				break;
148 
149 			_MountVolumes(kNoVolumes, fRemovableMode, false, deviceID);
150 			break;
151 
152 #if 0
153 		case B_NODE_MONITOR:
154 		{
155 			int32 opcode;
156 			if (message->FindInt32("opcode", &opcode) != B_OK)
157 				break;
158 
159 			switch (opcode) {
160 				//	The name of a mount point has changed
161 				case B_ENTRY_MOVED: {
162 					WRITELOG(("*** Received Mount Point Renamed Notification"));
163 
164 					const char *newName;
165 					if (message->FindString("name", &newName) != B_OK) {
166 						WRITELOG(("ERROR: Couldn't find name field in update message"));
167 						PRINT_OBJECT(*message);
168 						break ;
169 					}
170 
171 					//
172 					// When the node monitor reports a move, it gives the
173 					// parent device and inode that moved.  The problem is
174 					// that  the inode is the inode of root *in* the filesystem,
175 					// which is generally always the same number for every
176 					// filesystem of a type.
177 					//
178 					// What we'd really like is the device that the moved
179 					// volume is mounted on.  Find this by using the
180 					// *new* name and directory, and then stat()ing that to
181 					// find the device.
182 					//
183 					dev_t parentDevice;
184 					if (message->FindInt32("device", &parentDevice) != B_OK) {
185 						WRITELOG(("ERROR: Couldn't find 'device' field in update"
186 							" message"));
187 						PRINT_OBJECT(*message);
188 						break;
189 					}
190 
191 					ino_t toDirectory;
192 					if (message->FindInt64("to directory", &toDirectory)!=B_OK){
193 						WRITELOG(("ERROR: Couldn't find 'to directory' field in update"
194 						  "message"));
195 						PRINT_OBJECT(*message);
196 						break;
197 					}
198 
199 					entry_ref root_entry(parentDevice, toDirectory, newName);
200 
201 					BNode entryNode(&root_entry);
202 					if (entryNode.InitCheck() != B_OK) {
203 						WRITELOG(("ERROR: Couldn't create mount point entry node: %s/n",
204 							strerror(entryNode.InitCheck())));
205 						break;
206 					}
207 
208 					node_ref mountPointNode;
209 					if (entryNode.GetNodeRef(&mountPointNode) != B_OK) {
210 						WRITELOG(("ERROR: Couldn't get node ref for new mount point"));
211 						break;
212 					}
213 
214 					WRITELOG(("Attempt to rename device %li to %s", mountPointNode.device,
215 						newName));
216 
217 					Partition *partition = FindPartition(mountPointNode.device);
218 					if (partition != NULL) {
219 						WRITELOG(("Found device, changing name."));
220 
221 						BVolume mountVolume(partition->VolumeDeviceID());
222 						BDirectory mountDir;
223 						mountVolume.GetRootDirectory(&mountDir);
224 						BPath dirPath(&mountDir, 0);
225 
226 						partition->SetMountedAt(dirPath.Path());
227 						partition->SetVolumeName(newName);
228 						break;
229 					} else {
230 						WRITELOG(("ERROR: Device %li does not appear to be present",
231 							mountPointNode.device));
232 					}
233 				}
234 			}
235 			break;
236 		}
237 #endif
238 
239 		default:
240 			BLooper::MessageReceived(message);
241 			break;
242 	}
243 }
244 
245 
246 bool
247 AutoMounter::QuitRequested()
248 {
249 	if (!BootedInSafeMode()) {
250 		// don't write out settings in safe mode - this would overwrite the
251 		// normal, non-safe mode settings
252 		_WriteSettings();
253 	}
254 
255 	return true;
256 }
257 
258 
259 // #pragma mark - scripting
260 
261 
262 const uint32 kApplication = 0;
263 
264 static property_info sPropertyInfo[] = {
265 	{
266 		"InitialScan",
267 		{B_EXECUTE_PROPERTY},
268 		{B_DIRECT_SPECIFIER},
269 		NULL, kApplication,
270 		{B_STRING_TYPE},
271 		{},
272 		{}
273 	},
274 	{}
275 };
276 
277 
278 BHandler*
279 AutoMounter::ResolveSpecifier(BMessage* message, int32 index,
280 	BMessage* specifier, int32 what, const char* property)
281 {
282 	BPropertyInfo propInfo(sPropertyInfo);
283 
284 	uint32 data;
285 	if (propInfo.FindMatch(message, 0, specifier, what, property, &data) >= 0) {
286 		if (data == kApplication)
287 			return this;
288 
289 		BMessage reply(B_MESSAGE_NOT_UNDERSTOOD);
290 		reply.AddInt32("error", B_ERROR);
291 		reply.AddString("message", "Unkown specifier.");
292 		message->SendReply(&reply);
293 		return NULL;
294 	}
295 
296 	return BApplication::ResolveSpecifier(message, index, specifier, what,
297 		property);
298 }
299 
300 
301 status_t
302 AutoMounter::GetSupportedSuites(BMessage* data)
303 {
304 	if (data == NULL)
305 		return B_BAD_VALUE;
306 
307 	status_t status = data->AddString("suites",
308 		"suite/vnd.Haiku-mount_server");
309 	if (status != B_OK)
310 		return status;
311 
312 	BPropertyInfo propertyInfo(sPropertyInfo);
313 	status = data->AddFlat("messages", &propertyInfo);
314 	if (status != B_OK)
315 		return status;
316 
317 	return BApplication::GetSupportedSuites(data);
318 }
319 
320 
321 bool
322 AutoMounter::_ScriptReceived(BMessage *message, int32 index,
323 	BMessage *specifier, int32 what, const char *property)
324 {
325 	BMessage reply(B_REPLY);
326 	status_t err = B_BAD_SCRIPT_SYNTAX;
327 
328 	switch (message->what) {
329 		case B_EXECUTE_PROPERTY:
330 			if (strcmp("InitialScan", property) == 0) {
331 printf("performing initial scan.\n");
332 				_MountVolumes(fNormalMode, fRemovableMode, true);
333 				err = reply.AddString("result", "Previous volumes mounted.");
334 			}
335 			break;
336 	}
337 
338 	if (err == B_BAD_SCRIPT_SYNTAX)
339 		return false;
340 
341 	if (err != B_OK) {
342 		reply.what = B_MESSAGE_NOT_UNDERSTOOD;
343 		reply.AddString("message", strerror(err));
344 	}
345 	reply.AddInt32("error", err);
346 	message->SendReply(&reply);
347 	return true;
348 }
349 
350 
351 // #pragma mark -
352 
353 
354 static bool
355 suggest_mount_flags(const BPartition* partition, uint32* _flags)
356 {
357 	uint32 mountFlags = 0;
358 
359 	bool askReadOnly = true;
360 	bool isBFS = false;
361 
362 	if (partition->ContentType() != NULL
363 		&& strcmp(partition->ContentType(), kPartitionTypeBFS) == 0) {
364 #if 0
365 		askReadOnly = false;
366 #endif
367 		isBFS = true;
368 	}
369 
370 	BDiskSystem diskSystem;
371 	status_t status = partition->GetDiskSystem(&diskSystem);
372 	if (status == B_OK && !diskSystem.SupportsWriting())
373 		askReadOnly = false;
374 
375 	if (partition->IsReadOnly())
376 		askReadOnly = false;
377 
378 	if (askReadOnly) {
379 		// Suggest to the user to mount read-only until Haiku is more mature.
380 		BString string;
381 		string << "Mounting volume ";
382 		if (partition->ContentName() != NULL)
383 			string << "'" << partition->ContentName() << "'\n\n";
384 		else
385 			string << "<unnamed volume>\n\n";
386 		// TODO: Use distro name instead of "Haiku"...
387 		if (!isBFS) {
388 			string << "The file system on this volume is not the Haiku file "
389 				"system. It is strongly suggested to mount it in read-only "
390 				"mode. ";
391 		} else {
392 			string << "It is suggested to mount all additional Haiku volumes "
393 				"in read-only mode. ";
394 		}
395 		string << "This will prevent unintentional data loss because of "
396 			"errors in Haiku.";
397 		BAlert* alert = new BAlert("Mount warning", string.String(),
398 			"Mount read/write", "Cancel", "Mount read-only",
399 			B_WIDTH_FROM_WIDEST, B_WARNING_ALERT);
400 		alert->SetShortcut(1, B_ESCAPE);
401 		int32 choice = alert->Go();
402 		switch (choice) {
403 			case 0:
404 				break;
405 			case 1:
406 				return false;
407 			case 2:
408 				mountFlags |= B_MOUNT_READ_ONLY;
409 				break;
410 		}
411 	}
412 
413 	*_flags = mountFlags;
414 	return true;
415 }
416 
417 
418 void
419 AutoMounter::_MountVolumes(mount_mode normal, mount_mode removable,
420 	bool initialRescan, partition_id deviceID)
421 {
422 	if (normal == kNoVolumes && removable == kNoVolumes)
423 		return;
424 
425 	class InitialMountVisitor : public BDiskDeviceVisitor {
426 		public:
427 			InitialMountVisitor(mount_mode normalMode, mount_mode removableMode,
428 					bool initialRescan, BMessage& previous,
429 					partition_id deviceID)
430 				:
431 				fNormalMode(normalMode),
432 				fRemovableMode(removableMode),
433 				fInitialRescan(initialRescan),
434 				fPrevious(previous),
435 				fOnlyOnDeviceID(deviceID)
436 			{
437 			}
438 
439 			virtual
440 			~InitialMountVisitor()
441 			{
442 			}
443 
444 			virtual bool
445 			Visit(BDiskDevice* device)
446 			{
447 				return Visit(device, 0);
448 			}
449 
450 			virtual bool
451 			Visit(BPartition* partition, int32 level)
452 			{
453 				if (fOnlyOnDeviceID >= 0) {
454 					// only mount partitions on the given device id
455 					// or if the partition ID is already matched
456 					BPartition* device = partition;
457 					while (device->Parent() != NULL) {
458 						if (device->ID() == fOnlyOnDeviceID) {
459 							// we are happy
460 							break;
461 						}
462 						device = device->Parent();
463 					}
464 					if (device->ID() != fOnlyOnDeviceID)
465 						return false;
466 				}
467 
468 				mount_mode mode = !fInitialRescan
469 					&& partition->Device()->IsRemovableMedia()
470 					? fRemovableMode : fNormalMode;
471 				if (mode == kNoVolumes
472 					|| partition->IsMounted()
473 					|| !partition->ContainsFileSystem())
474 					return false;
475 
476 				BPath path;
477 				if (partition->GetPath(&path) != B_OK)
478 					return false;
479 
480 				if (mode == kRestorePreviousVolumes) {
481 					// mount all volumes that were stored in the settings file
482 					const char *volumeName = NULL;
483 					if (partition->ContentName() == NULL
484 						|| fPrevious.FindString(path.Path(), &volumeName)
485 							!= B_OK
486 						|| strcmp(volumeName, partition->ContentName()))
487 						return false;
488 				} else if (mode == kOnlyBFSVolumes) {
489 					if (partition->ContentType() == NULL
490 						|| strcmp(partition->ContentType(), kPartitionTypeBFS))
491 						return false;
492 				}
493 
494 				uint32 mountFlags;
495 				if (!fInitialRescan) {
496 					// Ask the user about mount flags if this is not the
497 					// initial scan.
498 					if (!suggest_mount_flags(partition, &mountFlags))
499 						return false;
500 				} else {
501 					BString mountFlagsKey(path.Path());
502 					mountFlagsKey << kMountFlagsKeyExtension;
503 					if (fPrevious.FindInt32(mountFlagsKey.String(),
504 							(int32*)&mountFlags) < B_OK) {
505 						mountFlags = 0;
506 					}
507 				}
508 
509 				if (partition->Mount(NULL, mountFlags) != B_OK) {
510 					// TODO: Error to syslog
511 				}
512 				return false;
513 			}
514 
515 		private:
516 			mount_mode		fNormalMode;
517 			mount_mode		fRemovableMode;
518 			bool			fInitialRescan;
519 			BMessage&		fPrevious;
520 			partition_id	fOnlyOnDeviceID;
521 	} visitor(normal, removable, initialRescan, fSettings, deviceID);
522 
523 	BDiskDeviceList devices;
524 	status_t status = devices.Fetch();
525 	if (status == B_OK)
526 		devices.VisitEachPartition(&visitor);
527 }
528 
529 
530 void
531 AutoMounter::_MountVolume(const BMessage* message)
532 {
533 	int32 id;
534 	if (message->FindInt32("id", &id) != B_OK)
535 		return;
536 
537 	BDiskDeviceRoster roster;
538 	BPartition *partition;
539 	BDiskDevice device;
540 	if (roster.GetPartitionWithID(id, &device, &partition) != B_OK)
541 		return;
542 
543 	uint32 mountFlags;
544 	if (!suggest_mount_flags(partition, &mountFlags))
545 		return;
546 
547 	status_t status = partition->Mount(NULL, mountFlags);
548 	if (status < B_OK) {
549 		BString string;
550 		string << "Error mounting volume. (" << strerror(status) << ")";
551 			(new BAlert("", string.String(), "OK"))->Go(NULL);
552 	}
553 }
554 
555 
556 bool
557 AutoMounter::_SuggestForceUnmount(const char* name, status_t error)
558 {
559 	BString text;
560 	text << "Could not unmount disk \"" << name << "\":\n\t";
561 	text << strerror(error);
562 	text << "\n\nShould unmounting be forced?\n\n"
563 		"Note: If an application is currently writing to the volume, "
564 		"unmounting it now might result in loss of data.\n";
565 
566 	BAlert* alert = new BAlert("", text.String(), "Cancel", "Force unmount",
567 		NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
568 	alert->SetShortcut(0, B_ESCAPE);
569 	int32 choice = alert->Go();
570 
571 	return choice == 1;
572 }
573 
574 
575 void
576 AutoMounter::_ReportUnmountError(const char* name, status_t error)
577 {
578 	BString text;
579 	text << "Could not unmount disk \"" << name << "\":\n\t";
580 	text << strerror(error);
581 
582 	(new BAlert("", text.String(), "OK", NULL, NULL, B_WIDTH_AS_USUAL,
583 		B_WARNING_ALERT))->Go(NULL);
584 }
585 
586 
587 void
588 AutoMounter::_UnmountAndEjectVolume(BPartition* partition, BPath& mountPoint,
589 	const char* name)
590 {
591 	BDiskDevice device;
592 	if (partition == NULL) {
593 		// Try to retrieve partition
594 		BDiskDeviceRoster().FindPartitionByMountPoint(mountPoint.Path(),
595 			&device, &partition);
596 	}
597 
598 	status_t status;
599 	if (partition != NULL)
600 		status = partition->Unmount();
601 	else
602 		status = fs_unmount_volume(mountPoint.Path(), 0);
603 
604 	if (status < B_OK) {
605 		if (!_SuggestForceUnmount(name, status))
606 			return;
607 
608 		if (partition != NULL)
609 			status = partition->Unmount(B_FORCE_UNMOUNT);
610 		else
611 			status = fs_unmount_volume(mountPoint.Path(), B_FORCE_UNMOUNT);
612 	}
613 
614 	if (status < B_OK) {
615 		_ReportUnmountError(partition->ContentName(), status);
616 		return;
617 	}
618 
619 	if (fEjectWhenUnmounting && partition != NULL) {
620 		// eject device if it doesn't have any mounted partitions left
621 		class IsMountedVisitor : public BDiskDeviceVisitor {
622 		public:
623 			IsMountedVisitor()
624 				:
625 				fHasMounted(false)
626 			{
627 			}
628 
629 			virtual bool Visit(BDiskDevice* device)
630 			{
631 				return Visit(device, 0);
632 			}
633 
634 			virtual bool Visit(BPartition* partition, int32 level)
635 			{
636 				if (partition->IsMounted()) {
637 					fHasMounted = true;
638 					return true;
639 				}
640 
641 				return false;
642 			}
643 
644 			bool HasMountedPartitions() const
645 			{
646 				return fHasMounted;
647 			}
648 
649 		private:
650 			bool	fHasMounted;
651 		} visitor;
652 
653 		partition->Device()->VisitEachDescendant(&visitor);
654 
655 		if (!visitor.HasMountedPartitions())
656 			partition->Device()->Eject();
657 	}
658 
659 	// remove the directory if it's a directory in rootfs
660 	if (dev_for_path(mountPoint.Path()) == dev_for_path("/"))
661 		rmdir(mountPoint.Path());
662 }
663 
664 
665 void
666 AutoMounter::_UnmountAndEjectVolume(BMessage* message)
667 {
668 	int32 id;
669 	if (message->FindInt32("id", &id) == B_OK) {
670 		BDiskDeviceRoster roster;
671 		BPartition *partition;
672 		BDiskDevice device;
673 		if (roster.GetPartitionWithID(id, &device, &partition) != B_OK)
674 			return;
675 
676 		BPath path;
677 		if (partition->GetMountPoint(&path) == B_OK)
678 			_UnmountAndEjectVolume(partition, path, partition->ContentName());
679 	} else {
680 		// see if we got a dev_t
681 
682 		dev_t device;
683 		if (message->FindInt32("device_id", &device) != B_OK)
684 			return;
685 
686 		BVolume volume(device);
687 		status_t status = volume.InitCheck();
688 
689 		char name[B_FILE_NAME_LENGTH];
690 		if (status == B_OK)
691 			status = volume.GetName(name);
692 		if (status < B_OK)
693 			snprintf(name, sizeof(name), "device:%ld", device);
694 
695 		BPath path;
696 		if (status == B_OK) {
697 			BDirectory mountPoint;
698 			status = volume.GetRootDirectory(&mountPoint);
699 			if (status == B_OK)
700 				status = path.SetTo(&mountPoint, ".");
701 		}
702 
703 		if (status == B_OK)
704 			_UnmountAndEjectVolume(NULL, path, name);
705 	}
706 }
707 
708 
709 void
710 AutoMounter::_FromMode(mount_mode mode, bool& all, bool& bfs, bool& restore)
711 {
712 	all = bfs = restore = false;
713 
714 	switch (mode) {
715 		case kAllVolumes:
716 			all = true;
717 			break;
718 		case kOnlyBFSVolumes:
719 			bfs = true;
720 			break;
721 		case kRestorePreviousVolumes:
722 			restore = true;
723 			break;
724 
725 		default:
726 			break;
727 	}
728 }
729 
730 
731 AutoMounter::mount_mode
732 AutoMounter::_ToMode(bool all, bool bfs, bool restore)
733 {
734 	if (all)
735 		return kAllVolumes;
736 	if (bfs)
737 		return kOnlyBFSVolumes;
738 	if (restore)
739 		return kRestorePreviousVolumes;
740 
741 	return kNoVolumes;
742 }
743 
744 
745 void
746 AutoMounter::_ReadSettings()
747 {
748 	BPath directoryPath;
749 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &directoryPath, true)
750 		!= B_OK) {
751 		return;
752 	}
753 
754 	BPath path(directoryPath);
755 	path.Append(kMountServerSettings);
756 	fPrefsFile.SetTo(path.Path(), O_RDWR);
757 
758 	if (fPrefsFile.InitCheck() != B_OK) {
759 		// no prefs file yet, create a new one
760 
761 		BDirectory dir(directoryPath.Path());
762 		dir.CreateFile(kMountServerSettings, &fPrefsFile);
763 		return;
764 	}
765 
766 	ssize_t settingsSize = (ssize_t)fPrefsFile.Seek(0, SEEK_END);
767 	if (settingsSize == 0)
768 		return;
769 
770 	ASSERT(settingsSize != 0);
771 	char *buffer = new(std::nothrow) char[settingsSize];
772 	if (buffer == NULL) {
773 		PRINT(("error writing automounter settings, out of memory\n"));
774 		return;
775 	}
776 
777 	fPrefsFile.Seek(0, 0);
778 	if (fPrefsFile.Read(buffer, (size_t)settingsSize) != settingsSize) {
779 		PRINT(("error reading automounter settings\n"));
780 		delete [] buffer;
781 		return;
782 	}
783 
784 	BMessage message('stng');
785 	status_t result = message.Unflatten(buffer);
786 	if (result != B_OK) {
787 		PRINT(("error %s unflattening automounter settings, size %d\n",
788 			strerror(result), settingsSize));
789 		delete [] buffer;
790 		return;
791 	}
792 
793 	delete [] buffer;
794 
795 	// update flags and modes from the message
796 	_UpdateSettingsFromMessage(&message);
797 	// copy the previously mounted partitions
798 	fSettings = message;
799 }
800 
801 
802 void
803 AutoMounter::_WriteSettings()
804 {
805 	if (fPrefsFile.InitCheck() != B_OK)
806 		return;
807 
808 	BMessage message('stng');
809 	_GetSettings(&message);
810 
811 	ssize_t settingsSize = message.FlattenedSize();
812 
813 	char *buffer = new(std::nothrow) char[settingsSize];
814 	if (buffer == NULL) {
815 		PRINT(("error writing automounter settings, out of memory\n"));
816 		return;
817 	}
818 
819 	status_t result = message.Flatten(buffer, settingsSize);
820 
821 	fPrefsFile.Seek(0, SEEK_SET);
822 	result = fPrefsFile.Write(buffer, (size_t)settingsSize);
823 	if (result != settingsSize)
824 		PRINT(("error writing automounter settings, %s\n", strerror(result)));
825 
826 	delete [] buffer;
827 }
828 
829 
830 void
831 AutoMounter::_UpdateSettingsFromMessage(BMessage* message)
832 {
833 	// auto mounter settings
834 
835 	bool all, bfs, restore;
836 	if (message->FindBool("autoMountAll", &all) != B_OK)
837 		all = true;
838 	if (message->FindBool("autoMountAllBFS", &bfs) != B_OK)
839 		bfs = false;
840 
841 	fRemovableMode = _ToMode(all, bfs, false);
842 
843 	// initial mount settings
844 
845 	if (message->FindBool("initialMountAll", &all) != B_OK)
846 		all = false;
847 	if (message->FindBool("initialMountAllBFS", &bfs) != B_OK)
848 		bfs = false;
849 	if (message->FindBool("initialMountRestore", &restore) != B_OK)
850 		restore = true;
851 
852 	fNormalMode = _ToMode(all, bfs, restore);
853 
854 	// eject settings
855 	bool eject;
856 	if (message->FindBool("ejectWhenUnmounting", &eject) == B_OK)
857 		fEjectWhenUnmounting = eject;
858 }
859 
860 
861 void
862 AutoMounter::_GetSettings(BMessage *message)
863 {
864 	message->MakeEmpty();
865 
866 	bool all, bfs, restore;
867 
868 	_FromMode(fNormalMode, all, bfs, restore);
869 	message->AddBool("initialMountAll", all);
870 	message->AddBool("initialMountAllBFS", bfs);
871 	message->AddBool("initialMountRestore", restore);
872 
873 	_FromMode(fRemovableMode, all, bfs, restore);
874 	message->AddBool("autoMountAll", all);
875 	message->AddBool("autoMountAllBFS", bfs);
876 
877 	message->AddBool("ejectWhenUnmounting", fEjectWhenUnmounting);
878 
879 	// Save mounted volumes so we can optionally mount them on next
880 	// startup
881 	BVolumeRoster volumeRoster;
882 	BVolume volume;
883 	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
884         fs_info info;
885         if (fs_stat_dev(volume.Device(), &info) == 0
886 			&& (info.flags & (B_FS_IS_REMOVABLE | B_FS_IS_PERSISTENT)) != 0) {
887 			message->AddString(info.device_name, info.volume_name);
888 
889 			BString mountFlagsKey(info.device_name);
890 			mountFlagsKey << kMountFlagsKeyExtension;
891 			uint32 mountFlags = 0;
892 			if (volume.IsReadOnly())
893 				mountFlags |= B_MOUNT_READ_ONLY;
894 			message->AddInt32(mountFlagsKey.String(), mountFlags);
895 		}
896 	}
897 }
898 
899 
900 // #pragma mark -
901 
902 
903 int
904 main(int argc, char* argv[])
905 {
906 	AutoMounter app;
907 	app.Run();
908 	return 0;
909 }
910 
911 
912