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