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
This commit is contained in:
Michael Lotz 2008-04-03 18:14:11 +00:00
parent bacaf1148c
commit 06da81f012
3 changed files with 214 additions and 9 deletions

View File

@ -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;
};

View File

@ -23,6 +23,11 @@
#include <KernelExport.h>
#include <util/kernel_cpp.h>
#include <NodeMonitor.h>
#include <node_monitor.h>
#include <Notifications.h>
#include <vfs.h>
#include <dirent.h>
#include <errno.h>
#include <module.h>
@ -87,6 +92,73 @@ struct KDiskDeviceManager::PartitionSet : VectorSet<KPartition*> {
};
// 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()
{

View File

@ -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();
}