2 * Copyright 2007-2010, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
6 * Stephan Aßmus, superstippi@gmx.de
7 * Axel Dörfler, axeld@pinc-software.de
11 #include "AutoMounter.h"
19 #include <AutoLocker.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>
30 #include <fs_volume.h>
31 #include <LaunchRoster.h>
35 #include <NodeMonitor.h>
37 #include <PropertyInfo.h>
39 #include <VolumeRoster.h>
41 #include "MountServer.h"
44 #undef B_TRANSLATION_CONTEXT
45 #define B_TRANSLATION_CONTEXT "AutoMounter"
48 static const char* kMountServerSettings
= "mount_server";
49 static const char* kMountFlagsKeyExtension
= " mount flags";
51 static const char* kInitialMountEvent
= "initial_volumes_mounted";
57 const char *safeMode
= getenv("SAFEMODE");
58 return (safeMode
&& strcmp(safeMode
, "yes") == 0);
65 AutoMounter::AutoMounter()
67 BServer(kMountServerSignature
, true, NULL
),
68 fNormalMode(kRestorePreviousVolumes
),
69 fRemovableMode(kAllVolumes
),
70 fEjectWhenUnmounting(true)
72 set_thread_priority(Thread(), B_LOW_PRIORITY
);
74 if (!BootedInSafeMode()) {
77 // defeat automounter in safe mode, don't even care about the settings
78 fNormalMode
= kNoVolumes
;
79 fRemovableMode
= kNoVolumes
;
82 BDiskDeviceRoster().StartWatching(this,
83 B_DEVICE_REQUEST_DEVICE
| B_DEVICE_REQUEST_DEVICE_LIST
);
84 BLaunchRoster().RegisterEvent(this, kInitialMountEvent
, B_STICKY_EVENT
);
88 AutoMounter::~AutoMounter()
90 BLaunchRoster().UnregisterEvent(this, kInitialMountEvent
);
91 BDiskDeviceRoster().StopWatching(this);
96 AutoMounter::ReadyToRun()
99 _MountVolumes(fNormalMode
, fRemovableMode
, true);
100 BLaunchRoster().NotifyEvent(this, kInitialMountEvent
);
105 AutoMounter::MessageReceived(BMessage
* message
)
107 switch (message
->what
) {
109 _MountVolume(message
);
113 _UnmountAndEjectVolume(message
);
116 case kSetAutomounterParams
:
118 bool rescanNow
= false;
119 message
->FindBool("rescanNow", &rescanNow
);
121 _UpdateSettingsFromMessage(message
);
122 _GetSettings(&fSettings
);
126 _MountVolumes(fNormalMode
, fRemovableMode
);
130 case kGetAutomounterParams
:
133 _GetSettings(&reply
);
134 message
->SendReply(&reply
);
139 _MountVolumes(kAllVolumes
, kAllVolumes
);
142 case B_DEVICE_UPDATE
:
144 if (message
->FindInt32("event", &event
) != B_OK
145 || (event
!= B_DEVICE_MEDIA_CHANGED
146 && event
!= B_DEVICE_ADDED
))
149 partition_id deviceID
;
150 if (message
->FindInt32("id", &deviceID
) != B_OK
)
153 _MountVolumes(kNoVolumes
, fRemovableMode
, false, deviceID
);
160 if (message
->FindInt32("opcode", &opcode
) != B_OK
)
164 // The name of a mount point has changed
165 case B_ENTRY_MOVED
: {
166 WRITELOG(("*** Received Mount Point Renamed Notification"));
169 if (message
->FindString("name", &newName
) != B_OK
) {
170 WRITELOG(("ERROR: Couldn't find name field in update "
172 PRINT_OBJECT(*message
);
177 // When the node monitor reports a move, it gives the
178 // parent device and inode that moved. The problem is
179 // that the inode is the inode of root *in* the filesystem,
180 // which is generally always the same number for every
181 // filesystem of a type.
183 // What we'd really like is the device that the moved
184 // volume is mounted on. Find this by using the
185 // *new* name and directory, and then stat()ing that to
189 if (message
->FindInt32("device", &parentDevice
) != B_OK
) {
190 WRITELOG(("ERROR: Couldn't find 'device' field in "
192 PRINT_OBJECT(*message
);
197 if (message
->FindInt64("to directory", &toDirectory
)
199 WRITELOG(("ERROR: Couldn't find 'to directory' field "
200 "in update message"));
201 PRINT_OBJECT(*message
);
205 entry_ref
root_entry(parentDevice
, toDirectory
, newName
);
207 BNode
entryNode(&root_entry
);
208 if (entryNode
.InitCheck() != B_OK
) {
209 WRITELOG(("ERROR: Couldn't create mount point entry "
210 "node: %s/n", strerror(entryNode
.InitCheck())));
214 node_ref mountPointNode
;
215 if (entryNode
.GetNodeRef(&mountPointNode
) != B_OK
) {
216 WRITELOG(("ERROR: Couldn't get node ref for new mount "
221 WRITELOG(("Attempt to rename device %li to %s",
222 mountPointNode
.device
, newName
));
224 Partition
*partition
= FindPartition(mountPointNode
.device
);
225 if (partition
!= NULL
) {
226 WRITELOG(("Found device, changing name."));
228 BVolume
mountVolume(partition
->VolumeDeviceID());
230 mountVolume
.GetRootDirectory(&mountDir
);
231 BPath
dirPath(&mountDir
, 0);
233 partition
->SetMountedAt(dirPath
.Path());
234 partition
->SetVolumeName(newName
);
237 WRITELOG(("ERROR: Device %li does not appear to be "
238 "present", mountPointNode
.device
));
247 BLooper::MessageReceived(message
);
254 AutoMounter::QuitRequested()
256 if (!BootedInSafeMode()) {
257 // Don't write out settings in safe mode - this would overwrite the
258 // normal, non-safe mode settings.
266 // #pragma mark - private methods
270 AutoMounter::_MountVolumes(mount_mode normal
, mount_mode removable
,
271 bool initialRescan
, partition_id deviceID
)
273 if (normal
== kNoVolumes
&& removable
== kNoVolumes
)
276 class InitialMountVisitor
: public BDiskDeviceVisitor
{
278 InitialMountVisitor(mount_mode normalMode
, mount_mode removableMode
,
279 bool initialRescan
, BMessage
& previous
,
280 partition_id deviceID
)
282 fNormalMode(normalMode
),
283 fRemovableMode(removableMode
),
284 fInitialRescan(initialRescan
),
286 fOnlyOnDeviceID(deviceID
)
291 ~InitialMountVisitor()
296 Visit(BDiskDevice
* device
)
298 return Visit(device
, 0);
302 Visit(BPartition
* partition
, int32 level
)
304 if (fOnlyOnDeviceID
>= 0) {
305 // only mount partitions on the given device id
306 // or if the partition ID is already matched
307 BPartition
* device
= partition
;
308 while (device
->Parent() != NULL
) {
309 if (device
->ID() == fOnlyOnDeviceID
) {
313 device
= device
->Parent();
315 if (device
->ID() != fOnlyOnDeviceID
)
319 mount_mode mode
= !fInitialRescan
320 && partition
->Device()->IsRemovableMedia()
321 ? fRemovableMode
: fNormalMode
;
322 if (mode
== kNoVolumes
323 || partition
->IsMounted()
324 || !partition
->ContainsFileSystem())
328 if (partition
->GetPath(&path
) != B_OK
)
331 if (mode
== kRestorePreviousVolumes
) {
332 // mount all volumes that were stored in the settings file
333 const char *volumeName
= NULL
;
334 if (partition
->ContentName() == NULL
335 || fPrevious
.FindString(path
.Path(), &volumeName
)
337 || strcmp(volumeName
, partition
->ContentName()))
339 } else if (mode
== kOnlyBFSVolumes
) {
340 if (partition
->ContentType() == NULL
341 || strcmp(partition
->ContentType(), kPartitionTypeBFS
))
346 if (!fInitialRescan
) {
347 // Ask the user about mount flags if this is not the
349 if (!_SuggestMountFlags(partition
, &mountFlags
))
352 BString
mountFlagsKey(path
.Path());
353 mountFlagsKey
<< kMountFlagsKeyExtension
;
354 if (fPrevious
.FindInt32(mountFlagsKey
.String(),
355 (int32
*)&mountFlags
) < B_OK
) {
360 if (partition
->Mount(NULL
, mountFlags
) != B_OK
) {
361 // TODO: Error to syslog
367 mount_mode fNormalMode
;
368 mount_mode fRemovableMode
;
371 partition_id fOnlyOnDeviceID
;
372 } visitor(normal
, removable
, initialRescan
, fSettings
, deviceID
);
374 BDiskDeviceList devices
;
375 status_t status
= devices
.Fetch();
377 devices
.VisitEachPartition(&visitor
);
382 AutoMounter::_MountVolume(const BMessage
* message
)
385 if (message
->FindInt32("id", &id
) != B_OK
)
388 BDiskDeviceRoster roster
;
389 BPartition
*partition
;
391 if (roster
.GetPartitionWithID(id
, &device
, &partition
) != B_OK
)
395 if (!_SuggestMountFlags(partition
, &mountFlags
))
398 status_t status
= partition
->Mount(NULL
, mountFlags
);
401 snprintf(text
, sizeof(text
),
402 B_TRANSLATE("Error mounting volume:\n\n%s"), strerror(status
));
403 BAlert
* alert
= new BAlert(B_TRANSLATE("Mount error"), text
,
405 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
412 AutoMounter::_SuggestForceUnmount(const char* name
, status_t error
)
415 snprintf(text
, sizeof(text
),
416 B_TRANSLATE("Could not unmount disk \"%s\":\n\t%s\n\n"
417 "Should unmounting be forced?\n\n"
418 "Note: If an application is currently writing to the volume, "
419 "unmounting it now might result in loss of data.\n"),
420 name
, strerror(error
));
422 BAlert
* alert
= new BAlert(B_TRANSLATE("Force unmount"), text
,
423 B_TRANSLATE("Cancel"), B_TRANSLATE("Force unmount"), NULL
,
424 B_WIDTH_AS_USUAL
, B_WARNING_ALERT
);
425 alert
->SetShortcut(0, B_ESCAPE
);
426 int32 choice
= alert
->Go();
433 AutoMounter::_ReportUnmountError(const char* name
, status_t error
)
436 snprintf(text
, sizeof(text
), B_TRANSLATE("Could not unmount disk "
437 "\"%s\":\n\t%s"), name
, strerror(error
));
439 BAlert
* alert
= new BAlert(B_TRANSLATE("Unmount error"), text
,
440 B_TRANSLATE("OK"), NULL
, NULL
, B_WIDTH_AS_USUAL
, B_WARNING_ALERT
);
441 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
447 AutoMounter::_UnmountAndEjectVolume(BPartition
* partition
, BPath
& mountPoint
,
450 BDiskDevice deviceStorage
;
452 if (partition
== NULL
) {
453 // Try to retrieve partition
454 BDiskDeviceRoster().FindPartitionByMountPoint(mountPoint
.Path(),
455 &deviceStorage
, &partition
);
456 device
= &deviceStorage
;
458 device
= partition
->Device();
462 if (partition
!= NULL
)
463 status
= partition
->Unmount();
465 status
= fs_unmount_volume(mountPoint
.Path(), 0);
467 if (status
!= B_OK
) {
468 if (!_SuggestForceUnmount(name
, status
))
471 if (partition
!= NULL
)
472 status
= partition
->Unmount(B_FORCE_UNMOUNT
);
474 status
= fs_unmount_volume(mountPoint
.Path(), B_FORCE_UNMOUNT
);
477 if (status
!= B_OK
) {
478 _ReportUnmountError(name
, status
);
482 if (fEjectWhenUnmounting
&& partition
!= NULL
) {
483 // eject device if it doesn't have any mounted partitions left
484 class IsMountedVisitor
: public BDiskDeviceVisitor
{
492 virtual bool Visit(BDiskDevice
* device
)
494 return Visit(device
, 0);
497 virtual bool Visit(BPartition
* partition
, int32 level
)
499 if (partition
->IsMounted()) {
507 bool HasMountedPartitions() const
516 device
->VisitEachDescendant(&visitor
);
518 if (!visitor
.HasMountedPartitions())
522 // remove the directory if it's a directory in rootfs
523 if (dev_for_path(mountPoint
.Path()) == dev_for_path("/"))
524 rmdir(mountPoint
.Path());
529 AutoMounter::_UnmountAndEjectVolume(BMessage
* message
)
532 if (message
->FindInt32("id", &id
) == B_OK
) {
533 BDiskDeviceRoster roster
;
534 BPartition
*partition
;
536 if (roster
.GetPartitionWithID(id
, &device
, &partition
) != B_OK
)
540 if (partition
->GetMountPoint(&path
) == B_OK
)
541 _UnmountAndEjectVolume(partition
, path
, partition
->ContentName());
543 // see if we got a dev_t
546 if (message
->FindInt32("device_id", &device
) != B_OK
)
549 BVolume
volume(device
);
550 status_t status
= volume
.InitCheck();
552 char name
[B_FILE_NAME_LENGTH
];
554 status
= volume
.GetName(name
);
556 snprintf(name
, sizeof(name
), "device:%" B_PRIdDEV
, device
);
559 if (status
== B_OK
) {
560 BDirectory mountPoint
;
561 status
= volume
.GetRootDirectory(&mountPoint
);
563 status
= path
.SetTo(&mountPoint
, ".");
567 _UnmountAndEjectVolume(NULL
, path
, name
);
573 AutoMounter::_FromMode(mount_mode mode
, bool& all
, bool& bfs
, bool& restore
)
575 all
= bfs
= restore
= false;
581 case kOnlyBFSVolumes
:
584 case kRestorePreviousVolumes
:
594 AutoMounter::mount_mode
595 AutoMounter::_ToMode(bool all
, bool bfs
, bool restore
)
600 return kOnlyBFSVolumes
;
602 return kRestorePreviousVolumes
;
609 AutoMounter::_ReadSettings()
612 if (find_directory(B_USER_SETTINGS_DIRECTORY
, &directoryPath
, true)
617 BPath
path(directoryPath
);
618 path
.Append(kMountServerSettings
);
619 fPrefsFile
.SetTo(path
.Path(), O_RDWR
);
621 if (fPrefsFile
.InitCheck() != B_OK
) {
622 // no prefs file yet, create a new one
624 BDirectory
dir(directoryPath
.Path());
625 dir
.CreateFile(kMountServerSettings
, &fPrefsFile
);
629 ssize_t settingsSize
= (ssize_t
)fPrefsFile
.Seek(0, SEEK_END
);
630 if (settingsSize
== 0)
633 ASSERT(settingsSize
!= 0);
634 char *buffer
= new(std::nothrow
) char[settingsSize
];
635 if (buffer
== NULL
) {
636 PRINT(("error writing automounter settings, out of memory\n"));
640 fPrefsFile
.Seek(0, 0);
641 if (fPrefsFile
.Read(buffer
, (size_t)settingsSize
) != settingsSize
) {
642 PRINT(("error reading automounter settings\n"));
647 BMessage
message('stng');
648 status_t result
= message
.Unflatten(buffer
);
649 if (result
!= B_OK
) {
650 PRINT(("error %s unflattening automounter settings, size %" B_PRIdSSIZE
"\n",
651 strerror(result
), settingsSize
));
658 // update flags and modes from the message
659 _UpdateSettingsFromMessage(&message
);
660 // copy the previously mounted partitions
666 AutoMounter::_WriteSettings()
668 if (fPrefsFile
.InitCheck() != B_OK
)
671 BMessage
message('stng');
672 _GetSettings(&message
);
674 ssize_t settingsSize
= message
.FlattenedSize();
676 char *buffer
= new(std::nothrow
) char[settingsSize
];
677 if (buffer
== NULL
) {
678 PRINT(("error writing automounter settings, out of memory\n"));
682 status_t result
= message
.Flatten(buffer
, settingsSize
);
684 fPrefsFile
.Seek(0, SEEK_SET
);
685 result
= fPrefsFile
.Write(buffer
, (size_t)settingsSize
);
686 if (result
!= settingsSize
)
687 PRINT(("error writing automounter settings, %s\n", strerror(result
)));
694 AutoMounter::_UpdateSettingsFromMessage(BMessage
* message
)
696 // auto mounter settings
698 bool all
, bfs
, restore
;
699 if (message
->FindBool("autoMountAll", &all
) != B_OK
)
701 if (message
->FindBool("autoMountAllBFS", &bfs
) != B_OK
)
704 fRemovableMode
= _ToMode(all
, bfs
, false);
706 // initial mount settings
708 if (message
->FindBool("initialMountAll", &all
) != B_OK
)
710 if (message
->FindBool("initialMountAllBFS", &bfs
) != B_OK
)
712 if (message
->FindBool("initialMountRestore", &restore
) != B_OK
)
715 fNormalMode
= _ToMode(all
, bfs
, restore
);
719 if (message
->FindBool("ejectWhenUnmounting", &eject
) == B_OK
)
720 fEjectWhenUnmounting
= eject
;
725 AutoMounter::_GetSettings(BMessage
*message
)
727 message
->MakeEmpty();
729 bool all
, bfs
, restore
;
731 _FromMode(fNormalMode
, all
, bfs
, restore
);
732 message
->AddBool("initialMountAll", all
);
733 message
->AddBool("initialMountAllBFS", bfs
);
734 message
->AddBool("initialMountRestore", restore
);
736 _FromMode(fRemovableMode
, all
, bfs
, restore
);
737 message
->AddBool("autoMountAll", all
);
738 message
->AddBool("autoMountAllBFS", bfs
);
740 message
->AddBool("ejectWhenUnmounting", fEjectWhenUnmounting
);
742 // Save mounted volumes so we can optionally mount them on next
744 BVolumeRoster volumeRoster
;
746 while (volumeRoster
.GetNextVolume(&volume
) == B_OK
) {
748 if (fs_stat_dev(volume
.Device(), &info
) == 0
749 && (info
.flags
& (B_FS_IS_REMOVABLE
| B_FS_IS_PERSISTENT
)) != 0) {
750 message
->AddString(info
.device_name
, info
.volume_name
);
752 BString
mountFlagsKey(info
.device_name
);
753 mountFlagsKey
<< kMountFlagsKeyExtension
;
754 uint32 mountFlags
= 0;
755 if (volume
.IsReadOnly())
756 mountFlags
|= B_MOUNT_READ_ONLY
;
757 message
->AddInt32(mountFlagsKey
.String(), mountFlags
);
764 AutoMounter::_SuggestMountFlags(const BPartition
* partition
, uint32
* _flags
)
766 uint32 mountFlags
= 0;
768 bool askReadOnly
= true;
771 if (partition
->ContentType() != NULL
772 && strcmp(partition
->ContentType(), kPartitionTypeBFS
) == 0) {
779 BDiskSystem diskSystem
;
780 status_t status
= partition
->GetDiskSystem(&diskSystem
);
781 if (status
== B_OK
&& !diskSystem
.SupportsWriting())
784 if (partition
->IsReadOnly())
788 // Suggest to the user to mount read-only until Haiku is more mature.
790 if (partition
->ContentName() != NULL
) {
792 snprintf(buffer
, sizeof(buffer
),
793 B_TRANSLATE("Mounting volume '%s'\n\n"),
794 partition
->ContentName());
797 string
<< B_TRANSLATE("Mounting volume <unnamed volume>\n\n");
799 // TODO: Use distro name instead of "Haiku"...
801 string
<< B_TRANSLATE("The file system on this volume is not the "
802 "Haiku file system. It is strongly suggested to mount it in "
803 "read-only mode. This will prevent unintentional data loss "
804 "because of errors in Haiku.");
806 string
<< B_TRANSLATE("It is suggested to mount all additional "
807 "Haiku volumes in read-only mode. This will prevent "
808 "unintentional data loss because of errors in Haiku.");
811 BAlert
* alert
= new BAlert(B_TRANSLATE("Mount warning"),
812 string
.String(), B_TRANSLATE("Mount read/write"),
813 B_TRANSLATE("Cancel"), B_TRANSLATE("Mount read-only"),
814 B_WIDTH_FROM_WIDEST
, B_WARNING_ALERT
);
815 alert
->SetShortcut(1, B_ESCAPE
);
816 int32 choice
= alert
->Go();
823 mountFlags
|= B_MOUNT_READ_ONLY
;
828 *_flags
= mountFlags
;
837 main(int argc
, char* argv
[])