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