/* * Copyright 2004-2018, Haiku, Inc. * Copyright 2003-2004, Ingo Weinhold, bonefish@cs.tu-berlin.de. * All rights reserved. Distributed under the terms of the MIT License. */ #include "KDiskDevice.h" #include "KDiskDeviceManager.h" #include "KDiskDeviceUtils.h" #include "KDiskSystem.h" #include "KFileDiskDevice.h" #include "KFileSystem.h" #include "KPartition.h" #include "KPartitioningSystem.h" #include "KPartitionVisitor.h" #include "KPath.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define TRACE_KDISK_DEVICE_MANAGER #ifdef TRACE_KDISK_DEVICE_MANAGER # define TRACE TRACE_ALWAYS #else # define TRACE(x...) do { } while (false) #endif #define TRACE_ALWAYS(x...) dprintf("disk_device_manager: " x) #define TRACE_ERROR(x...) dprintf("disk_device_manager: error: " x) // directories for partitioning and file system modules static const char* kPartitioningSystemPrefix = "partitioning_systems"; static const char* kFileSystemPrefix = "file_systems"; // singleton instance KDiskDeviceManager* KDiskDeviceManager::sDefaultManager = NULL; struct device_event { int32 opcode; const char* name; dev_t device; ino_t directory; ino_t node; }; struct GetPartitionID { inline partition_id operator()(const KPartition* partition) const { return partition->ID(); } }; struct GetDiskSystemID { inline disk_system_id operator()(const KDiskSystem* system) const { return system->ID(); } }; struct KDiskDeviceManager::PartitionMap : VectorMap > { }; struct KDiskDeviceManager::DeviceMap : VectorMap > { }; struct KDiskDeviceManager::DiskSystemMap : VectorMap > { }; struct KDiskDeviceManager::PartitionSet : VectorSet { }; class KDiskDeviceManager::DiskSystemWatcher : public NotificationListener { public: DiskSystemWatcher(KDiskDeviceManager* manager) : fManager(manager) { } virtual ~DiskSystemWatcher() { } virtual void EventOccurred(NotificationService& service, const KMessage* event) { if (event->GetInt32("opcode", -1) != B_ENTRY_REMOVED) fManager->RescanDiskSystems(); } private: KDiskDeviceManager* fManager; }; class KDiskDeviceManager::DeviceWatcher : public NotificationListener { public: DeviceWatcher() { } virtual ~DeviceWatcher() { } virtual void EventOccurred(NotificationService& service, const KMessage* event) { int32 opcode = event->GetInt32("opcode", -1); switch (opcode) { case B_ENTRY_CREATED: case B_ENTRY_REMOVED: { device_event* deviceEvent = new(std::nothrow) device_event; if (deviceEvent == NULL) break; const char* name = event->GetString("name", NULL); if (name != NULL) deviceEvent->name = strdup(name); else deviceEvent->name = NULL; deviceEvent->opcode = opcode; deviceEvent->device = event->GetInt32("device", -1); deviceEvent->directory = event->GetInt64("directory", -1); deviceEvent->node = event->GetInt64("node", -1); struct stat stat; if (vfs_stat_node_ref(deviceEvent->device, deviceEvent->node, &stat) != 0) { delete deviceEvent; break; } if (S_ISDIR(stat.st_mode)) { if (opcode == B_ENTRY_CREATED) { add_node_listener(deviceEvent->device, deviceEvent->node, B_WATCH_DIRECTORY, *this); } else { remove_node_listener(deviceEvent->device, deviceEvent->node, *this); } delete deviceEvent; break; } // TODO: a real in-kernel DPC mechanism would be preferred... thread_id thread = spawn_kernel_thread(_HandleDeviceEvent, "device event", B_NORMAL_PRIORITY, deviceEvent); if (thread < 0) delete deviceEvent; else resume_thread(thread); break; } default: break; } } static status_t _HandleDeviceEvent(void* _event) { device_event* event = (device_event*)_event; if (strcmp(event->name, "raw") == 0) { // a new raw device was added/removed KPath path; if (path.InitCheck() != B_OK || vfs_entry_ref_to_path(event->device, event->directory, event->name, true, path.LockBuffer(), path.BufferSize()) != B_OK) { delete event; return B_ERROR; } path.UnlockBuffer(); if (event->opcode == B_ENTRY_CREATED) KDiskDeviceManager::Default()->CreateDevice(path.Path()); else KDiskDeviceManager::Default()->DeleteDevice(path.Path()); } delete event; return B_OK; } }; class KDiskDeviceManager::DiskNotifications : public DefaultUserNotificationService { public: DiskNotifications() : DefaultUserNotificationService("disk devices") { } virtual ~DiskNotifications() { } }; // #pragma mark - KDiskDeviceManager::KDiskDeviceManager() : fDevices(new(nothrow) DeviceMap), fPartitions(new(nothrow) PartitionMap), fDiskSystems(new(nothrow) DiskSystemMap), fObsoletePartitions(new(nothrow) PartitionSet), fMediaChecker(-1), fTerminating(false), fDiskSystemWatcher(NULL), fDeviceWatcher(new(nothrow) DeviceWatcher()), fNotifications(new(nothrow) DiskNotifications) { recursive_lock_init(&fLock, "disk device manager"); if (InitCheck() != B_OK) return; fNotifications->Register(); RescanDiskSystems(); fMediaChecker = spawn_kernel_thread(_CheckMediaStatusDaemon, "media checker", B_NORMAL_PRIORITY, this); if (fMediaChecker >= 0) resume_thread(fMediaChecker); TRACE("number of disk systems: %" B_PRId32 "\n", CountDiskSystems()); // TODO: Watch the disk systems and the relevant directories. } KDiskDeviceManager::~KDiskDeviceManager() { fTerminating = true; status_t result; wait_for_thread(fMediaChecker, &result); // stop all node monitoring _AddRemoveMonitoring("/dev/disk", false); delete fDeviceWatcher; // remove all devices for (int32 cookie = 0; KDiskDevice* device = NextDevice(&cookie);) { PartitionRegistrar _(device); _RemoveDevice(device); } // some sanity checks if (fPartitions->Count() > 0) { TRACE_ALWAYS("WARNING: There are still %" B_PRId32 " unremoved partitions!\n", fPartitions->Count()); for (PartitionMap::Iterator it = fPartitions->Begin(); it != fPartitions->End(); ++it) { TRACE(" partition: %" B_PRId32 "\n", it->Value()->ID()); } } if (fObsoletePartitions->Count() > 0) { TRACE_ALWAYS("WARNING: There are still %" B_PRId32 " obsolete partitions!\n", fObsoletePartitions->Count()); for (PartitionSet::Iterator it = fObsoletePartitions->Begin(); it != fObsoletePartitions->End(); ++it) { TRACE(" partition: %" B_PRId32 "\n", (*it)->ID()); } } // remove all disk systems for (int32 cookie = 0; KDiskSystem* diskSystem = NextDiskSystem(&cookie);) { fDiskSystems->Remove(diskSystem->ID()); if (diskSystem->IsLoaded()) { TRACE_ALWAYS("WARNING: Disk system `%s' (%" B_PRId32 ") is still loaded!\n", diskSystem->Name(), diskSystem->ID()); } else delete diskSystem; } fNotifications->Unregister(); // delete the containers delete fPartitions; delete fDevices; delete fDiskSystems; delete fObsoletePartitions; } status_t KDiskDeviceManager::InitCheck() const { if (fPartitions == NULL || fDevices == NULL || fDiskSystems == NULL || fObsoletePartitions == NULL || fNotifications == NULL) return B_NO_MEMORY; return B_OK; } /*! This creates the system's default DiskDeviceManager. The creation is not thread-safe, and shouldn't be done more than once. */ status_t KDiskDeviceManager::CreateDefault() { if (sDefaultManager != NULL) return B_OK; sDefaultManager = new(nothrow) KDiskDeviceManager; if (sDefaultManager == NULL) return B_NO_MEMORY; return sDefaultManager->InitCheck(); } /*! This deletes the default DiskDeviceManager. The deletion is not thread-safe either, you should make sure that it's called only once. */ void KDiskDeviceManager::DeleteDefault() { delete sDefaultManager; sDefaultManager = NULL; } KDiskDeviceManager* KDiskDeviceManager::Default() { return sDefaultManager; } bool KDiskDeviceManager::Lock() { return recursive_lock_lock(&fLock) == B_OK; } void KDiskDeviceManager::Unlock() { recursive_lock_unlock(&fLock); } DefaultUserNotificationService& KDiskDeviceManager::Notifications() { return *fNotifications; } void KDiskDeviceManager::Notify(const KMessage& event, uint32 eventMask) { fNotifications->Notify(event, eventMask); } KDiskDevice* KDiskDeviceManager::FindDevice(const char* path) { for (int32 cookie = 0; KDiskDevice* device = NextDevice(&cookie); ) { if (device->Path() && !strcmp(path, device->Path())) return device; } return NULL; } KDiskDevice* KDiskDeviceManager::FindDevice(partition_id id, bool deviceOnly) { if (KPartition* partition = FindPartition(id)) { KDiskDevice* device = partition->Device(); if (!deviceOnly || id == device->ID()) return device; } return NULL; } KPartition* KDiskDeviceManager::FindPartition(const char* path) { // TODO: Optimize! KPath partitionPath; if (partitionPath.InitCheck() != B_OK) return NULL; for (PartitionMap::Iterator iterator = fPartitions->Begin(); iterator != fPartitions->End(); ++iterator) { KPartition* partition = iterator->Value(); if (partition->GetPath(&partitionPath) == B_OK && partitionPath == path) { return partition; } } return NULL; } KPartition* KDiskDeviceManager::FindPartition(partition_id id) { PartitionMap::Iterator iterator = fPartitions->Find(id); if (iterator != fPartitions->End()) return iterator->Value(); return NULL; } KFileDiskDevice* KDiskDeviceManager::FindFileDevice(const char* filePath) { for (int32 cookie = 0; KDiskDevice* device = NextDevice(&cookie); ) { KFileDiskDevice* fileDevice = dynamic_cast(device); if (fileDevice && fileDevice->FilePath() && !strcmp(filePath, fileDevice->FilePath())) { return fileDevice; } } return NULL; } KDiskDevice* KDiskDeviceManager::RegisterDevice(const char* path) { if (ManagerLocker locker = this) { for (int32 i = 0; i < 2; i++) { if (KDiskDevice* device = FindDevice(path)) { device->Register(); return device; } // if the device is not known yet, create it and try again const char* leaf = strrchr(path, '/'); if (i == 0 && !strncmp(path, "/dev/disk", 9) && !strcmp(leaf + 1, "raw") && CreateDevice(path) < B_OK) break; } } return NULL; } KDiskDevice* KDiskDeviceManager::RegisterDevice(partition_id id, bool deviceOnly) { if (ManagerLocker locker = this) { if (KDiskDevice* device = FindDevice(id, deviceOnly)) { device->Register(); return device; } } return NULL; } KDiskDevice* KDiskDeviceManager::RegisterNextDevice(int32* cookie) { if (!cookie) return NULL; if (ManagerLocker locker = this) { if (KDiskDevice* device = NextDevice(cookie)) { device->Register(); return device; } } return NULL; } KPartition* KDiskDeviceManager::RegisterPartition(const char* path) { if (ManagerLocker locker = this) { for (int32 i = 0; i < 2; i++) { if (KPartition* partition = FindPartition(path)) { partition->Register(); return partition; } // if the device is not known yet, create it and try again const char* leaf = strrchr(path, '/'); if (i == 0 && !strncmp(path, "/dev/disk", 9) && !strcmp(leaf + 1, "raw") && CreateDevice(path) < B_OK) break; } } return NULL; } KPartition* KDiskDeviceManager::RegisterPartition(partition_id id) { if (ManagerLocker locker = this) { if (KPartition* partition = FindPartition(id)) { partition->Register(); return partition; } } return NULL; } KFileDiskDevice* KDiskDeviceManager::RegisterFileDevice(const char* filePath) { if (ManagerLocker locker = this) { if (KFileDiskDevice* device = FindFileDevice(filePath)) { device->Register(); return device; } } return NULL; } KDiskDevice* KDiskDeviceManager::ReadLockDevice(partition_id id, bool deviceOnly) { // register device KDiskDevice* device = RegisterDevice(id, deviceOnly); if (!device) return NULL; // lock device if (device->ReadLock()) return device; device->Unregister(); return NULL; } KDiskDevice* KDiskDeviceManager::WriteLockDevice(partition_id id, bool deviceOnly) { // register device KDiskDevice* device = RegisterDevice(id, deviceOnly); if (!device) return NULL; // lock device if (device->WriteLock()) return device; device->Unregister(); return NULL; } KPartition* KDiskDeviceManager::ReadLockPartition(partition_id id) { // register partition KPartition* partition = RegisterPartition(id); if (!partition) return NULL; // get and register the device KDiskDevice* device = NULL; if (ManagerLocker locker = this) { device = partition->Device(); if (device) device->Register(); } // lock the device if (device && device->ReadLock()) { // final check, if the partition still belongs to the device if (partition->Device() == device) return partition; device->ReadUnlock(); } // cleanup on failure if (device) device->Unregister(); partition->Unregister(); return NULL; } KPartition* KDiskDeviceManager::WriteLockPartition(partition_id id) { // register partition KPartition* partition = RegisterPartition(id); if (!partition) return NULL; // get and register the device KDiskDevice* device = NULL; if (ManagerLocker locker = this) { device = partition->Device(); if (device) device->Register(); } // lock the device if (device && device->WriteLock()) { // final check, if the partition still belongs to the device if (partition->Device() == device) return partition; device->WriteUnlock(); } // cleanup on failure if (device) device->Unregister(); partition->Unregister(); return NULL; } status_t KDiskDeviceManager::ScanPartition(KPartition* partition) { // TODO: This won't do. Locking the DDM while scanning the partition is not a // good idea. Even locking the device doesn't feel right. Marking the partition // busy and passing the disk system a temporary clone of the partition_data // should work as well. if (DeviceWriteLocker deviceLocker = partition->Device()) { if (ManagerLocker locker = this) return _ScanPartition(partition, false); } return B_ERROR; } partition_id KDiskDeviceManager::CreateDevice(const char* path, bool* newlyCreated) { if (!path) return B_BAD_VALUE; status_t error = B_ERROR; if (ManagerLocker locker = this) { KDiskDevice* device = FindDevice(path); if (device != NULL) { // we already know this device if (newlyCreated) *newlyCreated = false; return device->ID(); } // create a KDiskDevice for it device = new(nothrow) KDiskDevice; if (!device) return B_NO_MEMORY; // initialize and add the device error = device->SetTo(path); // Note: Here we are allowed to lock a device although already having // the manager locked, since it is not yet added to the manager. DeviceWriteLocker deviceLocker(device); if (error == B_OK && !deviceLocker.IsLocked()) error = B_ERROR; if (error == B_OK && !_AddDevice(device)) error = B_NO_MEMORY; // cleanup on error if (error != B_OK) { deviceLocker.Unlock(); delete device; return error; } // scan for partitions _ScanPartition(device, false); device->UnmarkBusy(true); _NotifyDeviceEvent(device, B_DEVICE_ADDED, B_DEVICE_REQUEST_DEVICE_LIST); if (newlyCreated) *newlyCreated = true; return device->ID(); } return error; } status_t KDiskDeviceManager::DeleteDevice(const char* path) { KDiskDevice* device = FindDevice(path); if (device == NULL) return B_ENTRY_NOT_FOUND; PartitionRegistrar _(device, false); if (DeviceWriteLocker locker = device) { if (_RemoveDevice(device)) return B_OK; } return B_ERROR; } partition_id KDiskDeviceManager::CreateFileDevice(const char* filePath, bool* newlyCreated) { if (!filePath) return B_BAD_VALUE; // normalize the file path KPath normalizedFilePath; status_t error = normalizedFilePath.SetTo(filePath, KPath::NORMALIZE); if (error != B_OK) return error; filePath = normalizedFilePath.Path(); KFileDiskDevice* device = NULL; if (ManagerLocker locker = this) { // check, if the device does already exist if ((device = FindFileDevice(filePath))) { if (newlyCreated) *newlyCreated = false; return device->ID(); } // allocate a KFileDiskDevice device = new(nothrow) KFileDiskDevice; if (!device) return B_NO_MEMORY; // initialize and add the device error = device->SetTo(filePath); // Note: Here we are allowed to lock a device although already having // the manager locked, since it is not yet added to the manager. DeviceWriteLocker deviceLocker(device); if (error == B_OK && !deviceLocker.IsLocked()) error = B_ERROR; if (error == B_OK && !_AddDevice(device)) error = B_NO_MEMORY; // scan device if (error == B_OK) { _ScanPartition(device, false); device->UnmarkBusy(true); _NotifyDeviceEvent(device, B_DEVICE_ADDED, B_DEVICE_REQUEST_DEVICE_LIST); if (newlyCreated) *newlyCreated = true; return device->ID(); } // cleanup on failure deviceLocker.Unlock(); delete device; } else error = B_ERROR; return error; } status_t KDiskDeviceManager::DeleteFileDevice(const char* filePath) { if (KFileDiskDevice* device = RegisterFileDevice(filePath)) { PartitionRegistrar _(device, true); if (DeviceWriteLocker locker = device) { if (_RemoveDevice(device)) return B_OK; } } return B_ERROR; } status_t KDiskDeviceManager::DeleteFileDevice(partition_id id) { if (KDiskDevice* device = RegisterDevice(id)) { PartitionRegistrar _(device, true); if (!dynamic_cast(device) || id != device->ID()) return B_ENTRY_NOT_FOUND; if (DeviceWriteLocker locker = device) { if (_RemoveDevice(device)) return B_OK; } } return B_ERROR; } int32 KDiskDeviceManager::CountDevices() { return fDevices->Count(); } KDiskDevice* KDiskDeviceManager::NextDevice(int32* cookie) { if (!cookie) return NULL; DeviceMap::Iterator it = fDevices->FindClose(*cookie, false); if (it != fDevices->End()) { KDiskDevice* device = it->Value(); *cookie = device->ID() + 1; return device; } return NULL; } bool KDiskDeviceManager::PartitionAdded(KPartition* partition) { return partition && fPartitions->Put(partition->ID(), partition) == B_OK; } bool KDiskDeviceManager::PartitionRemoved(KPartition* partition) { if (partition && partition->PrepareForRemoval() && fPartitions->Remove(partition->ID())) { // TODO: If adding the partition to the obsolete list fails (due to lack // of memory), we can't do anything about it. We will leak memory then. fObsoletePartitions->Insert(partition); partition->MarkObsolete(); return true; } return false; } bool KDiskDeviceManager::DeletePartition(KPartition* partition) { if (partition && partition->IsObsolete() && partition->CountReferences() == 0 && partition->PrepareForDeletion() && fObsoletePartitions->Remove(partition)) { delete partition; return true; } return false; } KDiskSystem* KDiskDeviceManager::FindDiskSystem(const char* name, bool byPrettyName) { for (int32 cookie = 0; KDiskSystem* diskSystem = NextDiskSystem(&cookie);) { if (byPrettyName) { if (strcmp(name, diskSystem->PrettyName()) == 0) return diskSystem; } else { if (strcmp(name, diskSystem->Name()) == 0) return diskSystem; } } return NULL; } KDiskSystem* KDiskDeviceManager::FindDiskSystem(disk_system_id id) { DiskSystemMap::Iterator it = fDiskSystems->Find(id); if (it != fDiskSystems->End()) return it->Value(); return NULL; } int32 KDiskDeviceManager::CountDiskSystems() { return fDiskSystems->Count(); } KDiskSystem* KDiskDeviceManager::NextDiskSystem(int32* cookie) { if (!cookie) return NULL; DiskSystemMap::Iterator it = fDiskSystems->FindClose(*cookie, false); if (it != fDiskSystems->End()) { KDiskSystem* diskSystem = it->Value(); *cookie = diskSystem->ID() + 1; return diskSystem; } return NULL; } KDiskSystem* KDiskDeviceManager::LoadDiskSystem(const char* name, bool byPrettyName) { KDiskSystem* diskSystem = NULL; if (ManagerLocker locker = this) { diskSystem = FindDiskSystem(name, byPrettyName); if (diskSystem && diskSystem->Load() != B_OK) diskSystem = NULL; } return diskSystem; } KDiskSystem* KDiskDeviceManager::LoadDiskSystem(disk_system_id id) { KDiskSystem* diskSystem = NULL; if (ManagerLocker locker = this) { diskSystem = FindDiskSystem(id); if (diskSystem && diskSystem->Load() != B_OK) diskSystem = NULL; } return diskSystem; } KDiskSystem* KDiskDeviceManager::LoadNextDiskSystem(int32* cookie) { if (!cookie) return NULL; if (ManagerLocker locker = this) { if (KDiskSystem* diskSystem = NextDiskSystem(cookie)) { if (diskSystem->Load() == B_OK) { *cookie = diskSystem->ID() + 1; return diskSystem; } } } return NULL; } status_t KDiskDeviceManager::InitialDeviceScan() { // scan for devices if (ManagerLocker locker = this) { status_t error = _Scan("/dev/disk"); if (error != B_OK) return error; } // scan the devices for partitions int32 cookie = 0; status_t status = B_OK; while (KDiskDevice* device = RegisterNextDevice(&cookie)) { PartitionRegistrar _(device, true); if (DeviceWriteLocker deviceLocker = device) { if (ManagerLocker locker = this) { status_t error = _ScanPartition(device, false); device->UnmarkBusy(true); if (error != B_OK) status = error; // Even if we could not scan this partition, we want to try // and scan the rest. Just because one partition is invalid // or unscannable does not mean the ones after it are. } else return B_ERROR; } else return B_ERROR; } return status; } status_t KDiskDeviceManager::StartMonitoring() { // do another scan, this will populate the devfs directories InitialDeviceScan(); // start monitoring the disk systems fDiskSystemWatcher = new(std::nothrow) DiskSystemWatcher(this); if (fDiskSystemWatcher != NULL) { start_watching_modules(kFileSystemPrefix, *fDiskSystemWatcher); start_watching_modules(kPartitioningSystemPrefix, *fDiskSystemWatcher); } // start monitoring all dirs under /dev/disk return _AddRemoveMonitoring("/dev/disk", true); } status_t KDiskDeviceManager::_RescanDiskSystems(DiskSystemMap& addedSystems, bool fileSystems) { void* cookie = open_module_list(fileSystems ? kFileSystemPrefix : kPartitioningSystemPrefix); if (cookie == NULL) return B_NO_MEMORY; while (true) { KPath name; if (name.InitCheck() != B_OK) break; size_t nameLength = name.BufferSize(); if (read_next_module_name(cookie, name.LockBuffer(), &nameLength) != B_OK) { break; } name.UnlockBuffer(); if (FindDiskSystem(name.Path())) continue; if (fileSystems) { TRACE("file system: %s\n", name.Path()); _AddFileSystem(name.Path()); } else { TRACE("partitioning system: %s\n", name.Path()); _AddPartitioningSystem(name.Path()); } if (KDiskSystem* system = FindDiskSystem(name.Path())) addedSystems.Put(system->ID(), system); } close_module_list(cookie); return B_OK; } /*! Rescan the existing disk systems. This is called after the boot device has become available. */ status_t KDiskDeviceManager::RescanDiskSystems() { DiskSystemMap addedSystems; Lock(); // rescan for partitioning and file systems _RescanDiskSystems(addedSystems, false); _RescanDiskSystems(addedSystems, true); Unlock(); // rescan existing devices with the new disk systems int32 cookie = 0; status_t status = B_OK; while (KDiskDevice* device = RegisterNextDevice(&cookie)) { PartitionRegistrar _(device, true); if (DeviceWriteLocker deviceLocker = device) { if (ManagerLocker locker = this) { status_t error = _ScanPartition(device, false, &addedSystems); device->UnmarkBusy(true); if (error != B_OK) status = error; // See comment in InitialDeviceScan(). } else return B_ERROR; } else return B_ERROR; } return status; } status_t KDiskDeviceManager::_AddPartitioningSystem(const char* name) { if (!name) return B_BAD_VALUE; KDiskSystem* diskSystem = new(nothrow) KPartitioningSystem(name); if (!diskSystem) return B_NO_MEMORY; return _AddDiskSystem(diskSystem); } status_t KDiskDeviceManager::_AddFileSystem(const char* name) { if (!name) return B_BAD_VALUE; KDiskSystem* diskSystem = new(nothrow) KFileSystem(name); if (!diskSystem) return B_NO_MEMORY; return _AddDiskSystem(diskSystem); } status_t KDiskDeviceManager::_AddDiskSystem(KDiskSystem* diskSystem) { if (!diskSystem) return B_BAD_VALUE; TRACE("KDiskDeviceManager::_AddDiskSystem(%s)\n", diskSystem->Name()); status_t error = diskSystem->Init(); if (error != B_OK) { TRACE(" initialization failed: %s\n", strerror(error)); } if (error == B_OK) error = fDiskSystems->Put(diskSystem->ID(), diskSystem); if (error != B_OK) delete diskSystem; TRACE("KDiskDeviceManager::_AddDiskSystem() done: %s\n", strerror(error)); return error; } bool KDiskDeviceManager::_AddDevice(KDiskDevice* device) { if (!device || !PartitionAdded(device)) return false; if (fDevices->Put(device->ID(), device) == B_OK) return true; PartitionRemoved(device); return false; } bool KDiskDeviceManager::_RemoveDevice(KDiskDevice* device) { if (device != NULL && fDevices->Remove(device->ID()) && PartitionRemoved(device)) { _NotifyDeviceEvent(device, B_DEVICE_REMOVED, B_DEVICE_REQUEST_DEVICE_LIST); return true; } return false; } #if 0 /*! The device must be write locked, the manager must be locked. */ status_t KDiskDeviceManager::_UpdateBusyPartitions(KDiskDevice *device) { if (!device) return B_BAD_VALUE; // mark all partitions un-busy struct UnmarkBusyVisitor : KPartitionVisitor { virtual bool VisitPre(KPartition *partition) { partition->ClearFlags(B_PARTITION_BUSY | B_PARTITION_DESCENDANT_BUSY); return false; } } visitor; device->VisitEachDescendant(&visitor); // Iterate through all job queues and all jobs scheduled or in // progress and mark their scope busy. for (int32 cookie = 0; KDiskDeviceJobQueue *jobQueue = NextJobQueue(&cookie); ) { if (jobQueue->Device() != device) continue; for (int32 i = jobQueue->ActiveJobIndex(); KDiskDeviceJob *job = jobQueue->JobAt(i); i++) { if (job->Status() != B_DISK_DEVICE_JOB_IN_PROGRESS && job->Status() != B_DISK_DEVICE_JOB_SCHEDULED) { continue; } KPartition *partition = FindPartition(job->ScopeID()); if (!partition || partition->Device() != device) continue; partition->AddFlags(B_PARTITION_BUSY); } } // mark all anscestors of busy partitions descendant busy and all // descendants busy struct MarkBusyVisitor : KPartitionVisitor { virtual bool VisitPre(KPartition *partition) { // parent busy => child busy if (partition->Parent() && partition->Parent()->IsBusy()) partition->AddFlags(B_PARTITION_BUSY); return false; } virtual bool VisitPost(KPartition *partition) { // child [descendant] busy => parent descendant busy if ((partition->IsBusy() || partition->IsDescendantBusy()) && partition->Parent()) { partition->Parent()->AddFlags(B_PARTITION_DESCENDANT_BUSY); } return false; } } visitor2; device->VisitEachDescendant(&visitor2); return B_OK; } #endif status_t KDiskDeviceManager::_Scan(const char* path) { TRACE("KDiskDeviceManager::_Scan(%s)\n", path); status_t error = B_ENTRY_NOT_FOUND; struct stat st; if (lstat(path, &st) < 0) { return errno; } if (S_ISDIR(st.st_mode)) { // a directory: iterate through its contents DIR* dir = opendir(path); if (!dir) return errno; while (dirent* entry = readdir(dir)) { // skip "." and ".." if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; KPath entryPath; if (entryPath.SetPath(path) != B_OK || entryPath.Append(entry->d_name) != B_OK) { continue; } if (_Scan(entryPath.Path()) == B_OK) error = B_OK; } closedir(dir); } else { // not a directory // check, if it is named "raw" int32 len = strlen(path); int32 leafLen = strlen("/raw"); if (len <= leafLen || strcmp(path + len - leafLen, "/raw")) return B_ERROR; if (FindDevice(path) != NULL) { // we already know this device return B_OK; } TRACE(" found device: %s\n", path); // create a KDiskDevice for it KDiskDevice* device = new(nothrow) KDiskDevice; if (!device) return B_NO_MEMORY; // init the KDiskDevice error = device->SetTo(path); // add the device if (error == B_OK && !_AddDevice(device)) error = B_NO_MEMORY; // cleanup on error if (error != B_OK) delete device; } return error; } /*! The device must be write locked, the manager must be locked. */ status_t KDiskDeviceManager::_ScanPartition(KPartition* partition, bool async, DiskSystemMap* restrictScan) { // TODO: There's no reason why the manager needs to be locked anymore. if (!partition) return B_BAD_VALUE; // TODO: Reimplement asynchronous scanning, if we really need it. #if 0 if (async) { // create a new job queue for the device KDiskDeviceJobQueue *jobQueue = new(nothrow) KDiskDeviceJobQueue; if (!jobQueue) return B_NO_MEMORY; jobQueue->SetDevice(partition->Device()); // create a job for scanning the device and add it to the job queue KDiskDeviceJob *job = fJobFactory->CreateScanPartitionJob(partition->ID()); if (!job) { delete jobQueue; return B_NO_MEMORY; } if (!jobQueue->AddJob(job)) { delete jobQueue; delete job; return B_NO_MEMORY; } // add the job queue status_t error = AddJobQueue(jobQueue); if (error != B_OK) delete jobQueue; return error; } #endif // scan synchronously return _ScanPartition(partition, restrictScan); } status_t KDiskDeviceManager::_ScanPartition(KPartition* partition, DiskSystemMap* restrictScan) { // the partition's device must be write-locked if (partition == NULL) return B_BAD_VALUE; if (!partition->Device()->HasMedia() || partition->IsMounted()) return B_OK; if (partition->CountChildren() > 0) { // Since this partition has already children, we don't scan it // again, but only its children. for (int32 i = 0; KPartition* child = partition->ChildAt(i); i++) { _ScanPartition(child, restrictScan); } return B_OK; } KPath partitionPath; partition->GetPath(&partitionPath); // This happens with some copy protected CDs or eventually other issues. // Just ignore the partition... if (partition->Offset() < 0 || partition->BlockSize() == 0 || partition->Size() <= 0) { TRACE_ALWAYS("Partition %s has invalid parameters, ignoring it.\n", partitionPath.Path()); return B_BAD_DATA; } TRACE("KDiskDeviceManager::_ScanPartition(%s)\n", partitionPath.Path()); // publish the partition status_t error = B_OK; if (!partition->IsPublished()) { error = partition->PublishDevice(); if (error != B_OK) return error; } DiskSystemMap* diskSystems = restrictScan; if (diskSystems == NULL) diskSystems = fDiskSystems; // find the disk system that returns the best priority for this partition float bestPriority = partition->DiskSystemPriority(); KDiskSystem* bestDiskSystem = NULL; void* bestCookie = NULL; for (DiskSystemMap::Iterator iterator = diskSystems->Begin(); iterator != diskSystems->End(); iterator++) { KDiskSystem* diskSystem = iterator->Value(); if (diskSystem->Load() != B_OK) continue; TRACE(" trying: %s\n", diskSystem->Name()); void* cookie = NULL; float priority = diskSystem->Identify(partition, &cookie); TRACE(" returned: %g\n", priority); if (priority >= 0 && priority > bestPriority) { // new best disk system if (bestDiskSystem != NULL) { bestDiskSystem->FreeIdentifyCookie(partition, bestCookie); bestDiskSystem->Unload(); } bestPriority = priority; bestDiskSystem = diskSystem; bestCookie = cookie; } else { // disk system doesn't identify the partition or worse than our // current favorite if (priority >= 0) diskSystem->FreeIdentifyCookie(partition, cookie); diskSystem->Unload(); } } // now, if we have found a disk system, let it scan the partition if (bestDiskSystem != NULL) { TRACE(" scanning with: %s\n", bestDiskSystem->Name()); error = bestDiskSystem->Scan(partition, bestCookie); bestDiskSystem->FreeIdentifyCookie(partition, bestCookie); if (error == B_OK) { partition->SetDiskSystem(bestDiskSystem, bestPriority); for (int32 i = 0; KPartition* child = partition->ChildAt(i); i++) _ScanPartition(child, restrictScan); } else { // TODO: Handle the error. TRACE_ERROR("scanning failed: %s\n", strerror(error)); } // now we can safely unload the disk system -- it has been loaded by // the partition(s) and thus will not really be unloaded bestDiskSystem->Unload(); } else { // contents not recognized // nothing to be done -- partitions are created as unrecognized } return error; } status_t KDiskDeviceManager::_AddRemoveMonitoring(const char* path, bool add) { struct stat st; if (lstat(path, &st) < 0) return errno; status_t error = B_ENTRY_NOT_FOUND; if (S_ISDIR(st.st_mode)) { if (add) { error = add_node_listener(st.st_dev, st.st_ino, B_WATCH_DIRECTORY, *fDeviceWatcher); } else { error = remove_node_listener(st.st_dev, st.st_ino, *fDeviceWatcher); } if (error != B_OK) return error; DIR* dir = opendir(path); if (!dir) return errno; while (dirent* entry = readdir(dir)) { // skip "." and ".." if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; KPath entryPath; if (entryPath.SetPath(path) != B_OK || entryPath.Append(entry->d_name) != B_OK) { continue; } if (_AddRemoveMonitoring(entryPath.Path(), add) == B_OK) error = B_OK; } closedir(dir); } return error; } status_t KDiskDeviceManager::_CheckMediaStatus() { while (!fTerminating) { int32 cookie = 0; while (KDiskDevice* device = RegisterNextDevice(&cookie)) { PartitionRegistrar _(device, true); DeviceWriteLocker locker(device); if (device->IsBusy(true)) continue; bool hadMedia = device->HasMedia(); bool changedMedia = device->MediaChanged(); device->UpdateMediaStatusIfNeeded(); // Detect if there was any status change since last check. if ((!device->MediaChanged() && (device->HasMedia() || !hadMedia)) || !(hadMedia != device->HasMedia() || changedMedia != device->MediaChanged())) continue; device->MarkBusy(true); device->UninitializeMedia(); if (device->MediaChanged()) { dprintf("Media changed from %s\n", device->Path()); device->UpdateGeometry(); _ScanPartition(device, false); _NotifyDeviceEvent(device, B_DEVICE_MEDIA_CHANGED, B_DEVICE_REQUEST_DEVICE); } else if (!device->HasMedia() && hadMedia) { dprintf("Media removed from %s\n", device->Path()); } device->UnmarkBusy(true); } snooze(1000000); } return 0; } status_t KDiskDeviceManager::_CheckMediaStatusDaemon(void* self) { return ((KDiskDeviceManager*)self)->_CheckMediaStatus(); } void KDiskDeviceManager::_NotifyDeviceEvent(KDiskDevice* device, int32 event, uint32 mask) { char messageBuffer[512]; KMessage message; message.SetTo(messageBuffer, sizeof(messageBuffer), B_DEVICE_UPDATE); message.AddInt32("event", event); message.AddInt32("id", device->ID()); message.AddString("device", device->Path()); fNotifications->Notify(message, mask); }