From 06da81f012843e3aec1bba94dcd5ac837492cafb Mon Sep 17 00:00:00 2001 From: Michael Lotz Date: Thu, 3 Apr 2008 18:14:11 +0000 Subject: [PATCH] Implement node monitoring in the kernel disk device manager. * Added {Create|Delete}Device() analogous to {Create|Delete}FileDevice * Added a small DeviceWatcher class that reacts to entry creation/removal * Implemented a way to start/stop node monitoring * Start watching for devices after the boot volume has been mounted and the the second initial scan was run The disk device manager now creates and scans a device when a "raw" node is published and deletes the device on removal. This makes hot-plugging of disk devices (for example memory sticks using usb_disk) work. Their partitions will be scanned and published so they can be mounted. Somehow the removal of the partitions does not yet work however, any insights are welcome. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@24777 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- .../disk_device_manager/KDiskDeviceManager.h | 13 +- .../KDiskDeviceManager.cpp | 206 +++++++++++++++++- src/system/kernel/fs/vfs_boot.cpp | 4 +- 3 files changed, 214 insertions(+), 9 deletions(-) diff --git a/headers/private/kernel/disk_device_manager/KDiskDeviceManager.h b/headers/private/kernel/disk_device_manager/KDiskDeviceManager.h index 465282588e..015971d188 100644 --- a/headers/private/kernel/disk_device_manager/KDiskDeviceManager.h +++ b/headers/private/kernel/disk_device_manager/KDiskDeviceManager.h @@ -65,6 +65,9 @@ public: status_t ScanPartition(KPartition* partition); + partition_id CreateDevice(const char *path, bool *newlyCreated = NULL); + status_t DeleteDevice(const char *path); + partition_id CreateFileDevice(const char* filePath, bool* newlyCreated = NULL); status_t DeleteFileDevice(const char *filePath); @@ -90,13 +93,9 @@ public: KDiskSystem *LoadDiskSystem(disk_system_id id); KDiskSystem *LoadNextDiskSystem(int32 *cookie); - // Watching - - // TODO: Watching service for the kernel. The userland watching is handled - // by the registrar. - status_t InitialDeviceScan(); status_t RescanDiskSystems(); + status_t StartMonitoring(); private: static status_t _CheckMediaStatusDaemon(void* self); @@ -117,10 +116,13 @@ private: status_t _ScanPartition(KPartition *partition); // used by the other _ScanPartition() version only + status_t _AddRemoveMonitoring(const char *path, bool add); + struct DeviceMap; struct DiskSystemMap; struct PartitionMap; struct PartitionSet; + class DeviceWatcher; BLocker fLock; DeviceMap *fDevices; @@ -129,6 +131,7 @@ private: PartitionSet *fObsoletePartitions; thread_id fMediaChecker; volatile bool fTerminating; + DeviceWatcher *fDeviceWatcher; static KDiskDeviceManager *sDefaultManager; }; diff --git a/src/system/kernel/disk_device_manager/KDiskDeviceManager.cpp b/src/system/kernel/disk_device_manager/KDiskDeviceManager.cpp index 2b24e039a6..3bf313103b 100644 --- a/src/system/kernel/disk_device_manager/KDiskDeviceManager.cpp +++ b/src/system/kernel/disk_device_manager/KDiskDeviceManager.cpp @@ -23,6 +23,11 @@ #include #include +#include +#include +#include +#include + #include #include #include @@ -87,6 +92,73 @@ struct KDiskDeviceManager::PartitionSet : VectorSet { }; +// DeviceWatcher +class KDiskDeviceManager::DeviceWatcher : public NotificationListener { + public: + DeviceWatcher(KDiskDeviceManager *manager) + : fManager(manager) + { + } + + virtual ~DeviceWatcher() + { + } + + virtual void EventOccured(NotificationService &service, + const KMessage *event) + { + int32 opCode = event->GetInt32("opcode", -1); + switch (opCode) { + case B_ENTRY_CREATED: + case B_ENTRY_REMOVED: + { + const char *name = event->GetString("name", ""); + dev_t device = event->GetInt32("device", -1); + ino_t directory = event->GetInt64("directory", -1); + ino_t node = event->GetInt64("node", -1); + + // TODO: it would be nice if we could stat based on the + // node here instead of having to resolve the path + KPath path(B_PATH_NAME_LENGTH + 1); + if (path.InitCheck() != B_OK + || vfs_entry_ref_to_path(device, directory, name, + path.LockBuffer(), path.BufferSize()) != B_OK) + break; + path.UnlockBuffer(); + + struct stat st; + if (lstat(path.Path(), &st) != 0) + break; + + if (S_ISDIR(st.st_mode)) { + if (opCode == B_ENTRY_CREATED) + add_node_listener(device, node, B_WATCH_DIRECTORY, + *this); + else + remove_node_listener(device, node, *this); + } else { + if (strcmp(name, "raw") == 0) { + // a new raw device was added/removed + if (opCode == B_ENTRY_CREATED) + fManager->CreateDevice(path.Path()); + else + fManager->DeleteDevice(path.Path()); + } + } + + break; + } + + default: + break; + } + } + + private: + KDiskDeviceManager *fManager; +}; + + static bool is_active_job_status(uint32 status) { @@ -105,7 +177,8 @@ KDiskDeviceManager::KDiskDeviceManager() fDiskSystems(new(nothrow) DiskSystemMap), fObsoletePartitions(new(nothrow) PartitionSet), fMediaChecker(-1), - fTerminating(false) + fTerminating(false), + fDeviceWatcher(new(nothrow) DeviceWatcher(this)) { if (InitCheck() != B_OK) return; @@ -129,6 +202,9 @@ KDiskDeviceManager::~KDiskDeviceManager() status_t result; wait_for_thread(fMediaChecker, &result); + // stop all node monitoring + _AddRemoveMonitoring("/dev/disk", false); + // remove all devices for (int32 cookie = 0; KDiskDevice *device = NextDevice(&cookie);) { PartitionRegistrar _(device); @@ -488,6 +564,78 @@ KDiskDeviceManager::ScanPartition(KPartition* partition) } +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) { + delete device; + return error; + } + + if (error == B_OK) { + // scan for partitions + _ScanPartition(device, false); + device->UnmarkBusy(true); + + 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, true); + if (DeviceWriteLocker locker = device) { + if (_RemoveDevice(device)) + return B_OK; + } + + return B_ERROR; +} + + // CreateFileDevice partition_id KDiskDeviceManager::CreateFileDevice(const char *filePath, bool* newlyCreated) @@ -758,6 +906,18 @@ KDiskDeviceManager::InitialDeviceScan() return error; } + +status_t +KDiskDeviceManager::StartMonitoring() +{ + // do another scan, this will populate the devfs directories + InitialDeviceScan(); + // start monitoring all dirs under /dev/disk + status_t result = _AddRemoveMonitoring("/dev/disk", true); + return result; +} + + // _RescanDiskSystems status_t KDiskDeviceManager::_RescanDiskSystems(bool fileSystems) @@ -1126,6 +1286,50 @@ KDiskDeviceManager::_ScanPartition(KPartition *partition) } +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() { diff --git a/src/system/kernel/fs/vfs_boot.cpp b/src/system/kernel/fs/vfs_boot.cpp index 0bee98cb16..86ed212ca9 100644 --- a/src/system/kernel/fs/vfs_boot.cpp +++ b/src/system/kernel/fs/vfs_boot.cpp @@ -516,8 +516,6 @@ vfs_mount_boot_file_system(kernel_args *args) // search for other disk systems KDiskDeviceManager *manager = KDiskDeviceManager::Default(); manager->RescanDiskSystems(); - manager->InitialDeviceScan(); - // TODO: later, the DDM should notice when there are new - // partitions/devices available + manager->StartMonitoring(); }