usb_ecm: Use the current configuration instead of a fixed one.
[haiku.git] / src / servers / mount / AutoMounter.cpp
blob4c630588287106963a78e74401fcbcfb691e55f4
1 /*
2 * Copyright 2007-2010, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Stephan Aßmus, superstippi@gmx.de
7 * Axel Dörfler, axeld@pinc-software.de
8 */
11 #include "AutoMounter.h"
13 #include <new>
15 #include <string.h>
16 #include <unistd.h>
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 <LaunchRoster.h>
32 #include <Locale.h>
33 #include <Message.h>
34 #include <Node.h>
35 #include <NodeMonitor.h>
36 #include <Path.h>
37 #include <PropertyInfo.h>
38 #include <String.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";
54 static bool
55 BootedInSafeMode()
57 const char *safeMode = getenv("SAFEMODE");
58 return (safeMode && strcmp(safeMode, "yes") == 0);
62 // #pragma mark -
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()) {
75 _ReadSettings();
76 } else {
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);
95 void
96 AutoMounter::ReadyToRun()
98 // Do initial scan
99 _MountVolumes(fNormalMode, fRemovableMode, true);
100 BLaunchRoster().NotifyEvent(this, kInitialMountEvent);
104 void
105 AutoMounter::MessageReceived(BMessage* message)
107 switch (message->what) {
108 case kMountVolume:
109 _MountVolume(message);
110 break;
112 case kUnmountVolume:
113 _UnmountAndEjectVolume(message);
114 break;
116 case kSetAutomounterParams:
118 bool rescanNow = false;
119 message->FindBool("rescanNow", &rescanNow);
121 _UpdateSettingsFromMessage(message);
122 _GetSettings(&fSettings);
123 _WriteSettings();
125 if (rescanNow)
126 _MountVolumes(fNormalMode, fRemovableMode);
127 break;
130 case kGetAutomounterParams:
132 BMessage reply;
133 _GetSettings(&reply);
134 message->SendReply(&reply);
135 break;
138 case kMountAllNow:
139 _MountVolumes(kAllVolumes, kAllVolumes);
140 break;
142 case B_DEVICE_UPDATE:
143 int32 event;
144 if (message->FindInt32("event", &event) != B_OK
145 || (event != B_DEVICE_MEDIA_CHANGED
146 && event != B_DEVICE_ADDED))
147 break;
149 partition_id deviceID;
150 if (message->FindInt32("id", &deviceID) != B_OK)
151 break;
153 _MountVolumes(kNoVolumes, fRemovableMode, false, deviceID);
154 break;
156 #if 0
157 case B_NODE_MONITOR:
159 int32 opcode;
160 if (message->FindInt32("opcode", &opcode) != B_OK)
161 break;
163 switch (opcode) {
164 // The name of a mount point has changed
165 case B_ENTRY_MOVED: {
166 WRITELOG(("*** Received Mount Point Renamed Notification"));
168 const char *newName;
169 if (message->FindString("name", &newName) != B_OK) {
170 WRITELOG(("ERROR: Couldn't find name field in update "
171 "message"));
172 PRINT_OBJECT(*message);
173 break ;
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
186 // find the device.
188 dev_t parentDevice;
189 if (message->FindInt32("device", &parentDevice) != B_OK) {
190 WRITELOG(("ERROR: Couldn't find 'device' field in "
191 "update message"));
192 PRINT_OBJECT(*message);
193 break;
196 ino_t toDirectory;
197 if (message->FindInt64("to directory", &toDirectory)
198 != B_OK) {
199 WRITELOG(("ERROR: Couldn't find 'to directory' field "
200 "in update message"));
201 PRINT_OBJECT(*message);
202 break;
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())));
211 break;
214 node_ref mountPointNode;
215 if (entryNode.GetNodeRef(&mountPointNode) != B_OK) {
216 WRITELOG(("ERROR: Couldn't get node ref for new mount "
217 "point"));
218 break;
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());
229 BDirectory mountDir;
230 mountVolume.GetRootDirectory(&mountDir);
231 BPath dirPath(&mountDir, 0);
233 partition->SetMountedAt(dirPath.Path());
234 partition->SetVolumeName(newName);
235 break;
236 } else {
237 WRITELOG(("ERROR: Device %li does not appear to be "
238 "present", mountPointNode.device));
242 break;
244 #endif
246 default:
247 BLooper::MessageReceived(message);
248 break;
253 bool
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.
259 _WriteSettings();
262 return true;
266 // #pragma mark - private methods
269 void
270 AutoMounter::_MountVolumes(mount_mode normal, mount_mode removable,
271 bool initialRescan, partition_id deviceID)
273 if (normal == kNoVolumes && removable == kNoVolumes)
274 return;
276 class InitialMountVisitor : public BDiskDeviceVisitor {
277 public:
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),
285 fPrevious(previous),
286 fOnlyOnDeviceID(deviceID)
290 virtual
291 ~InitialMountVisitor()
295 virtual bool
296 Visit(BDiskDevice* device)
298 return Visit(device, 0);
301 virtual bool
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) {
310 // we are happy
311 break;
313 device = device->Parent();
315 if (device->ID() != fOnlyOnDeviceID)
316 return false;
319 mount_mode mode = !fInitialRescan
320 && partition->Device()->IsRemovableMedia()
321 ? fRemovableMode : fNormalMode;
322 if (mode == kNoVolumes
323 || partition->IsMounted()
324 || !partition->ContainsFileSystem())
325 return false;
327 BPath path;
328 if (partition->GetPath(&path) != B_OK)
329 return false;
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)
336 != B_OK
337 || strcmp(volumeName, partition->ContentName()))
338 return false;
339 } else if (mode == kOnlyBFSVolumes) {
340 if (partition->ContentType() == NULL
341 || strcmp(partition->ContentType(), kPartitionTypeBFS))
342 return false;
345 uint32 mountFlags;
346 if (!fInitialRescan) {
347 // Ask the user about mount flags if this is not the
348 // initial scan.
349 if (!_SuggestMountFlags(partition, &mountFlags))
350 return false;
351 } else {
352 BString mountFlagsKey(path.Path());
353 mountFlagsKey << kMountFlagsKeyExtension;
354 if (fPrevious.FindInt32(mountFlagsKey.String(),
355 (int32*)&mountFlags) < B_OK) {
356 mountFlags = 0;
360 if (partition->Mount(NULL, mountFlags) != B_OK) {
361 // TODO: Error to syslog
363 return false;
366 private:
367 mount_mode fNormalMode;
368 mount_mode fRemovableMode;
369 bool fInitialRescan;
370 BMessage& fPrevious;
371 partition_id fOnlyOnDeviceID;
372 } visitor(normal, removable, initialRescan, fSettings, deviceID);
374 BDiskDeviceList devices;
375 status_t status = devices.Fetch();
376 if (status == B_OK)
377 devices.VisitEachPartition(&visitor);
381 void
382 AutoMounter::_MountVolume(const BMessage* message)
384 int32 id;
385 if (message->FindInt32("id", &id) != B_OK)
386 return;
388 BDiskDeviceRoster roster;
389 BPartition *partition;
390 BDiskDevice device;
391 if (roster.GetPartitionWithID(id, &device, &partition) != B_OK)
392 return;
394 uint32 mountFlags;
395 if (!_SuggestMountFlags(partition, &mountFlags))
396 return;
398 status_t status = partition->Mount(NULL, mountFlags);
399 if (status < B_OK) {
400 char text[512];
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,
404 B_TRANSLATE("OK"));
405 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
406 alert->Go(NULL);
411 bool
412 AutoMounter::_SuggestForceUnmount(const char* name, status_t error)
414 char text[1024];
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();
428 return choice == 1;
432 void
433 AutoMounter::_ReportUnmountError(const char* name, status_t error)
435 char text[512];
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);
442 alert->Go(NULL);
446 void
447 AutoMounter::_UnmountAndEjectVolume(BPartition* partition, BPath& mountPoint,
448 const char* name)
450 BDiskDevice deviceStorage;
451 BDiskDevice* device;
452 if (partition == NULL) {
453 // Try to retrieve partition
454 BDiskDeviceRoster().FindPartitionByMountPoint(mountPoint.Path(),
455 &deviceStorage, &partition);
456 device = &deviceStorage;
457 } else {
458 device = partition->Device();
461 status_t status;
462 if (partition != NULL)
463 status = partition->Unmount();
464 else
465 status = fs_unmount_volume(mountPoint.Path(), 0);
467 if (status != B_OK) {
468 if (!_SuggestForceUnmount(name, status))
469 return;
471 if (partition != NULL)
472 status = partition->Unmount(B_FORCE_UNMOUNT);
473 else
474 status = fs_unmount_volume(mountPoint.Path(), B_FORCE_UNMOUNT);
477 if (status != B_OK) {
478 _ReportUnmountError(name, status);
479 return;
482 if (fEjectWhenUnmounting && partition != NULL) {
483 // eject device if it doesn't have any mounted partitions left
484 class IsMountedVisitor : public BDiskDeviceVisitor {
485 public:
486 IsMountedVisitor()
488 fHasMounted(false)
492 virtual bool Visit(BDiskDevice* device)
494 return Visit(device, 0);
497 virtual bool Visit(BPartition* partition, int32 level)
499 if (partition->IsMounted()) {
500 fHasMounted = true;
501 return true;
504 return false;
507 bool HasMountedPartitions() const
509 return fHasMounted;
512 private:
513 bool fHasMounted;
514 } visitor;
516 device->VisitEachDescendant(&visitor);
518 if (!visitor.HasMountedPartitions())
519 device->Eject();
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());
528 void
529 AutoMounter::_UnmountAndEjectVolume(BMessage* message)
531 int32 id;
532 if (message->FindInt32("id", &id) == B_OK) {
533 BDiskDeviceRoster roster;
534 BPartition *partition;
535 BDiskDevice device;
536 if (roster.GetPartitionWithID(id, &device, &partition) != B_OK)
537 return;
539 BPath path;
540 if (partition->GetMountPoint(&path) == B_OK)
541 _UnmountAndEjectVolume(partition, path, partition->ContentName());
542 } else {
543 // see if we got a dev_t
545 dev_t device;
546 if (message->FindInt32("device_id", &device) != B_OK)
547 return;
549 BVolume volume(device);
550 status_t status = volume.InitCheck();
552 char name[B_FILE_NAME_LENGTH];
553 if (status == B_OK)
554 status = volume.GetName(name);
555 if (status < B_OK)
556 snprintf(name, sizeof(name), "device:%" B_PRIdDEV, device);
558 BPath path;
559 if (status == B_OK) {
560 BDirectory mountPoint;
561 status = volume.GetRootDirectory(&mountPoint);
562 if (status == B_OK)
563 status = path.SetTo(&mountPoint, ".");
566 if (status == B_OK)
567 _UnmountAndEjectVolume(NULL, path, name);
572 void
573 AutoMounter::_FromMode(mount_mode mode, bool& all, bool& bfs, bool& restore)
575 all = bfs = restore = false;
577 switch (mode) {
578 case kAllVolumes:
579 all = true;
580 break;
581 case kOnlyBFSVolumes:
582 bfs = true;
583 break;
584 case kRestorePreviousVolumes:
585 restore = true;
586 break;
588 default:
589 break;
594 AutoMounter::mount_mode
595 AutoMounter::_ToMode(bool all, bool bfs, bool restore)
597 if (all)
598 return kAllVolumes;
599 if (bfs)
600 return kOnlyBFSVolumes;
601 if (restore)
602 return kRestorePreviousVolumes;
604 return kNoVolumes;
608 void
609 AutoMounter::_ReadSettings()
611 BPath directoryPath;
612 if (find_directory(B_USER_SETTINGS_DIRECTORY, &directoryPath, true)
613 != B_OK) {
614 return;
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);
626 return;
629 ssize_t settingsSize = (ssize_t)fPrefsFile.Seek(0, SEEK_END);
630 if (settingsSize == 0)
631 return;
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"));
637 return;
640 fPrefsFile.Seek(0, 0);
641 if (fPrefsFile.Read(buffer, (size_t)settingsSize) != settingsSize) {
642 PRINT(("error reading automounter settings\n"));
643 delete [] buffer;
644 return;
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));
652 delete [] buffer;
653 return;
656 delete [] buffer;
658 // update flags and modes from the message
659 _UpdateSettingsFromMessage(&message);
660 // copy the previously mounted partitions
661 fSettings = message;
665 void
666 AutoMounter::_WriteSettings()
668 if (fPrefsFile.InitCheck() != B_OK)
669 return;
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"));
679 return;
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)));
689 delete [] buffer;
693 void
694 AutoMounter::_UpdateSettingsFromMessage(BMessage* message)
696 // auto mounter settings
698 bool all, bfs, restore;
699 if (message->FindBool("autoMountAll", &all) != B_OK)
700 all = true;
701 if (message->FindBool("autoMountAllBFS", &bfs) != B_OK)
702 bfs = false;
704 fRemovableMode = _ToMode(all, bfs, false);
706 // initial mount settings
708 if (message->FindBool("initialMountAll", &all) != B_OK)
709 all = false;
710 if (message->FindBool("initialMountAllBFS", &bfs) != B_OK)
711 bfs = false;
712 if (message->FindBool("initialMountRestore", &restore) != B_OK)
713 restore = true;
715 fNormalMode = _ToMode(all, bfs, restore);
717 // eject settings
718 bool eject;
719 if (message->FindBool("ejectWhenUnmounting", &eject) == B_OK)
720 fEjectWhenUnmounting = eject;
724 void
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
743 // startup
744 BVolumeRoster volumeRoster;
745 BVolume volume;
746 while (volumeRoster.GetNextVolume(&volume) == B_OK) {
747 fs_info info;
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);
763 /*static*/ bool
764 AutoMounter::_SuggestMountFlags(const BPartition* partition, uint32* _flags)
766 uint32 mountFlags = 0;
768 bool askReadOnly = true;
769 bool isBFS = false;
771 if (partition->ContentType() != NULL
772 && strcmp(partition->ContentType(), kPartitionTypeBFS) == 0) {
773 #if 0
774 askReadOnly = false;
775 #endif
776 isBFS = true;
779 BDiskSystem diskSystem;
780 status_t status = partition->GetDiskSystem(&diskSystem);
781 if (status == B_OK && !diskSystem.SupportsWriting())
782 askReadOnly = false;
784 if (partition->IsReadOnly())
785 askReadOnly = false;
787 if (askReadOnly) {
788 // Suggest to the user to mount read-only until Haiku is more mature.
789 BString string;
790 if (partition->ContentName() != NULL) {
791 char buffer[512];
792 snprintf(buffer, sizeof(buffer),
793 B_TRANSLATE("Mounting volume '%s'\n\n"),
794 partition->ContentName());
795 string << buffer;
796 } else
797 string << B_TRANSLATE("Mounting volume <unnamed volume>\n\n");
799 // TODO: Use distro name instead of "Haiku"...
800 if (!isBFS) {
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.");
805 } else {
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();
817 switch (choice) {
818 case 0:
819 break;
820 case 1:
821 return false;
822 case 2:
823 mountFlags |= B_MOUNT_READ_ONLY;
824 break;
828 *_flags = mountFlags;
829 return true;
833 // #pragma mark -
837 main(int argc, char* argv[])
839 AutoMounter app;
841 app.Run();
842 return 0;