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