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