root/src/system/kernel/device_manager/legacy_drivers.cpp
/*
 * Copyright 2002-2011, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#include "legacy_drivers.h"

#include <dirent.h>
#include <errno.h>
#include <new>
#include <stdio.h>

#include <FindDirectory.h>
#include <image.h>
#include <NodeMonitor.h>

#include <boot_device.h>
#include <boot/kernel_args.h>
#include <elf.h>
#include <find_directory_private.h>
#include <fs/devfs.h>
#include <fs/KPath.h>
#include <fs/node_monitor.h>
#include <Notifications.h>
#include <safemode.h>
#include <util/DoublyLinkedList.h>
#include <util/OpenHashTable.h>
#include <util/Stack.h>
#include <vfs.h>

#include "AbstractModuleDevice.h"
#include "devfs_private.h"


//#define TRACE_LEGACY_DRIVERS
#ifdef TRACE_LEGACY_DRIVERS
#       define TRACE(x) dprintf x
#else
#       define TRACE(x)
#endif

#define DRIVER_HASH_SIZE 16


namespace {

struct legacy_driver;

class LegacyDevice : public AbstractModuleDevice,
        public DoublyLinkedListLinkImpl<LegacyDevice> {
public:
                                                        LegacyDevice(legacy_driver* driver,
                                                                const char* path, device_hooks* hooks);
        virtual                                 ~LegacyDevice();

                        status_t                InitCheck() const;

        virtual status_t                InitDevice();
        virtual void                    UninitDevice();

        virtual void                    Removed();

                        void                    SetHooks(device_hooks* hooks);

                        legacy_driver*  Driver() const { return fDriver; }
                        const char*             Path() const { return fPath; }
                        device_hooks*   Hooks() const { return fHooks; }

        virtual status_t                Open(const char* path, int openMode,
                                                                void** _cookie);
        virtual status_t                Select(void* cookie, uint8 event, selectsync* sync);

        virtual status_t                Control(void* cookie, int32 op, void* buffer, size_t length);

                        bool                    Republished() const { return fRepublished; }
                        void                    SetRepublished(bool republished)
                                                                { fRepublished = republished; }

                        void                    SetRemovedFromParent(bool removed)
                                                                { fRemovedFromParent = removed; }

private:
        legacy_driver*                  fDriver;
        const char*                             fPath;
        device_hooks*                   fHooks;
        bool                                    fRepublished;
        bool                                    fRemovedFromParent;
};

typedef DoublyLinkedList<LegacyDevice> DeviceList;

struct legacy_driver {
        legacy_driver*  next;
        const char*             path;
        const char*             name;
        dev_t                   device;
        ino_t                   node;
        timespec                last_modified;
        image_id                image;
        uint32                  devices_used;
        bool                    binary_updated;
        int32                   priority;
        DeviceList              devices;

        // driver image information
        int32                   api_version;
        device_hooks*   (*find_device)(const char *);
        const char**    (*publish_devices)(void);
        status_t                (*uninit_driver)(void);
        status_t                (*uninit_hardware)(void);
};


enum driver_event_type {
        kAddDriver,
        kRemoveDriver,
        kAddWatcher,
        kRemoveWatcher
};

struct driver_event : DoublyLinkedListLinkImpl<driver_event> {
        driver_event(driver_event_type _type) : type(_type) {}

        struct ref {
                dev_t           device;
                ino_t           node;
        };

        driver_event_type       type;
        union {
                char                    path[B_PATH_NAME_LENGTH];
                ref                             node;
        };
};

typedef DoublyLinkedList<driver_event> DriverEventList;


struct driver_entry : DoublyLinkedListLinkImpl<driver_entry> {
        char*                   path;
        dev_t                   device;
        ino_t                   node;
        int32                   busses;
};

typedef DoublyLinkedList<driver_entry> DriverEntryList;


struct node_entry : DoublyLinkedListLinkImpl<node_entry> {
};

typedef DoublyLinkedList<node_entry> NodeList;


struct directory_node_entry {
        directory_node_entry*   hash_link;
        ino_t                                   node;
};

struct DirectoryNodeHashDefinition {
        typedef ino_t* KeyType;
        typedef directory_node_entry ValueType;

        size_t HashKey(ino_t* key) const
                { return _Hash(*key); }
        size_t Hash(directory_node_entry* entry) const
                { return _Hash(entry->node); }
        bool Compare(ino_t* key, directory_node_entry* entry) const
                { return *key == entry->node; }
        directory_node_entry*&
                GetLink(directory_node_entry* entry) const
                { return entry->hash_link; }

        uint32 _Hash(ino_t node) const
                { return (uint32)(node >> 32) + (uint32)node; }
};

typedef BOpenHashTable<DirectoryNodeHashDefinition> DirectoryNodeHash;

class DirectoryIterator {
public:
                                                DirectoryIterator(const char *path,
                                                        const char *subPath = NULL, bool recursive = false);
                                                ~DirectoryIterator();

                        void            SetTo(const char *path, const char *subPath = NULL,
                                                        bool recursive = false);

                        status_t        GetNext(KPath &path, struct stat &stat);
                        const char*     CurrentName() const { return fCurrentName; }

                        void            Unset();
                        void            AddPath(const char *path, const char *subPath = NULL);

private:
        Stack<KPath*>           fPaths;
        bool                            fRecursive;
        DIR*                            fDirectory;
        KPath*                          fBasePath;
        const char*                     fCurrentName;
};


class DirectoryWatcher : public NotificationListener {
public:
                                                DirectoryWatcher();
        virtual                         ~DirectoryWatcher();

        virtual void            EventOccurred(NotificationService& service,
                                                        const KMessage* event);
};

class DriverWatcher : public NotificationListener {
public:
                                                DriverWatcher();
        virtual                         ~DriverWatcher();

        virtual void            EventOccurred(NotificationService& service,
                                                        const KMessage* event);
};


struct DriverHash {
        typedef const char*                     KeyType;
        typedef legacy_driver           ValueType;

        size_t HashKey(KeyType key) const
        {
                return hash_hash_string(key);
        }

        size_t Hash(ValueType* driver) const
        {
                return HashKey(driver->name);
        }

        bool Compare(KeyType key, ValueType* driver) const
        {
                return strcmp(driver->name, key) == 0;
        }

        ValueType*& GetLink(ValueType* value) const
        {
                return value->next;
        }
};

typedef BOpenHashTable<DriverHash> DriverTable;


}       // unnamed namespace


static status_t unload_driver(legacy_driver *driver);
static status_t load_driver(legacy_driver *driver);


static const directory_which kDriverPaths[] = {
        B_USER_NONPACKAGED_ADDONS_DIRECTORY,
        B_USER_ADDONS_DIRECTORY,
        B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY,
        B_SYSTEM_ADDONS_DIRECTORY
};

static DriverWatcher sDriverWatcher;
static int32 sDriverEventsPending;
static DriverEventList sDriverEvents;
static mutex sDriverEventsLock = MUTEX_INITIALIZER("driver events");
        // inner lock, protects the sDriverEvents list only
static DirectoryWatcher sDirectoryWatcher;
static DirectoryNodeHash sDirectoryNodeHash;
static recursive_lock sLock;
static bool sWatching;

static DriverTable* sDriverHash;


//      #pragma mark - driver private


/*!     Collects all published devices of a driver, compares them to what the
        driver would publish now, and then publishes/unpublishes the devices
        as needed.
        If the driver does not publish any devices anymore, it is unloaded.
*/
static status_t
republish_driver(legacy_driver* driver)
{
        if (driver->image < 0) {
                // The driver is not yet loaded - go through the normal load procedure
                return load_driver(driver);
        }

        // mark all devices
        DeviceList::Iterator iterator = driver->devices.GetIterator();
        while (LegacyDevice* device = iterator.Next()) {
                device->SetRepublished(false);
        }

        // now ask the driver for it's currently published devices
        const char** devicePaths = driver->publish_devices();

        int32 exported = 0;
        for (; devicePaths != NULL && devicePaths[0]; devicePaths++) {
                LegacyDevice* device;

                iterator = driver->devices.GetIterator();
                while ((device = iterator.Next()) != NULL) {
                        if (!strncmp(device->Path(), devicePaths[0], B_PATH_NAME_LENGTH)) {
                                // mark device as republished
                                device->SetRepublished(true);
                                exported++;
                                break;
                        }
                }

                device_hooks* hooks = driver->find_device(devicePaths[0]);
                if (hooks == NULL)
                        continue;

                if (device != NULL) {
                        // update hooks
                        device->SetHooks(hooks);
                        continue;
                }

                // the device was not present before -> publish it now
                TRACE(("devfs: publishing new device \"%s\"\n", devicePaths[0]));
                device = new(std::nothrow) LegacyDevice(driver, devicePaths[0], hooks);
                if (device != NULL && device->InitCheck() == B_OK
                        && devfs_publish_device(devicePaths[0], device) == B_OK) {
                        driver->devices.Add(device);
                        exported++;
                } else
                        delete device;
        }

        // remove all devices that weren't republished
        iterator = driver->devices.GetIterator();
        while (LegacyDevice* device = iterator.Next()) {
                if (device->Republished())
                        continue;

                TRACE(("devfs: unpublishing no more present \"%s\"\n", device->Path()));
                iterator.Remove();
                device->SetRemovedFromParent(true);

                devfs_unpublish_device(device, true);
        }

        if (exported == 0 && driver->devices_used == 0 && gBootDevice >= 0) {
                TRACE(("devfs: driver \"%s\" does not publish any more nodes and is "
                        "unloaded\n", driver->path));
                unload_driver(driver);
        }

        return B_OK;
}


static status_t
load_driver(legacy_driver* driver)
{
        status_t (*init_hardware)(void);
        status_t (*init_driver)(void);
        status_t status;

        driver->binary_updated = false;

        // load the module
        image_id image = driver->image;
        if (image < 0) {
                image = load_kernel_add_on(driver->path);
                if (image < 0)
                        return image;
        }

        // For a valid device driver the following exports are required

        int32* apiVersion;
        if (get_image_symbol(image, "api_version", B_SYMBOL_TYPE_DATA,
                        (void**)&apiVersion) == B_OK) {
#if B_CUR_DRIVER_API_VERSION != 2
                // just in case someone decides to bump up the api version
#error Add checks here for new vs old api version!
#endif
                if (*apiVersion > B_CUR_DRIVER_API_VERSION) {
                        dprintf("devfs: \"%s\" api_version %" B_PRId32 " not handled\n",
                                driver->name, *apiVersion);
                        status = B_BAD_VALUE;
                        goto error1;
                }
                if (*apiVersion < 1) {
                        dprintf("devfs: \"%s\" api_version invalid\n", driver->name);
                        status = B_BAD_VALUE;
                        goto error1;
                }

                driver->api_version = *apiVersion;
        } else
                dprintf("devfs: \"%s\" api_version missing\n", driver->name);

        if (get_image_symbol(image, "publish_devices", B_SYMBOL_TYPE_TEXT,
                                (void**)&driver->publish_devices) != B_OK
                || get_image_symbol(image, "find_device", B_SYMBOL_TYPE_TEXT,
                                (void**)&driver->find_device) != B_OK) {
                dprintf("devfs: \"%s\" mandatory driver symbol(s) missing!\n",
                        driver->name);
                status = B_BAD_VALUE;
                goto error1;
        }

        // Init the driver

        if (get_image_symbol(image, "init_hardware", B_SYMBOL_TYPE_TEXT,
                        (void**)&init_hardware) == B_OK
                && (status = init_hardware()) != B_OK) {
                TRACE(("%s: init_hardware() failed: %s\n", driver->name,
                        strerror(status)));
                status = ENXIO;
                goto error1;
        }

        if (get_image_symbol(image, "init_driver", B_SYMBOL_TYPE_TEXT,
                        (void**)&init_driver) == B_OK
                && (status = init_driver()) != B_OK) {
                TRACE(("%s: init_driver() failed: %s\n", driver->name,
                        strerror(status)));
                status = ENXIO;
                goto error2;
        }

        // resolve and cache those for the driver unload code
        if (get_image_symbol(image, "uninit_driver", B_SYMBOL_TYPE_TEXT,
                (void**)&driver->uninit_driver) != B_OK)
                driver->uninit_driver = NULL;
        if (get_image_symbol(image, "uninit_hardware", B_SYMBOL_TYPE_TEXT,
                (void**)&driver->uninit_hardware) != B_OK)
                driver->uninit_hardware = NULL;

        // The driver has successfully been initialized, now we can
        // finally publish its device entries

        driver->image = image;
        return republish_driver(driver);

error2:
        if (driver->uninit_hardware)
                driver->uninit_hardware();

error1:
        if (driver->image < 0) {
                unload_kernel_add_on(image);
                driver->image = status;
        }

        return status;
}


static status_t
unload_driver(legacy_driver* driver)
{
        if (driver->image < 0) {
                // driver is not currently loaded
                return B_NO_INIT;
        }

        if (driver->uninit_driver)
                driver->uninit_driver();

        if (driver->uninit_hardware)
                driver->uninit_hardware();

        unload_kernel_add_on(driver->image);
        driver->image = -1;
        driver->binary_updated = false;
        driver->find_device = NULL;
        driver->publish_devices = NULL;
        driver->uninit_driver = NULL;
        driver->uninit_hardware = NULL;

        return B_OK;
}


/*!     Unpublishes all devices belonging to the \a driver. */
static void
unpublish_driver(legacy_driver* driver)
{
        while (LegacyDevice* device = driver->devices.RemoveHead()) {
                device->SetRemovedFromParent(true);
                devfs_unpublish_device(device, true);
        }
}


static void
change_driver_watcher(dev_t device, ino_t node, bool add)
{
        if (device == -1)
                return;

        driver_event* event = new (std::nothrow) driver_event(
                add ? kAddWatcher : kRemoveWatcher);
        if (event == NULL)
                return;

        event->node.device = device;
        event->node.node = node;

        MutexLocker _(sDriverEventsLock);
        sDriverEvents.Add(event);

        atomic_add(&sDriverEventsPending, 1);
}


static int32
get_priority(const char* path)
{
        // TODO: would it be better to initialize a static structure here
        // using find_directory()?
        const directory_which whichPath[] = {
                B_SYSTEM_DIRECTORY,
                B_SYSTEM_NONPACKAGED_DIRECTORY,
                B_USER_DIRECTORY
        };
        KPath pathBuffer;

        for (uint32 index = 0; index < B_COUNT_OF(whichPath); index++) {
                if (__find_directory(whichPath[index], gBootDevice, false,
                        pathBuffer.LockBuffer(), pathBuffer.BufferSize()) == B_OK) {
                        pathBuffer.UnlockBuffer();
                        if (strncmp(pathBuffer.Path(), path, pathBuffer.Length()) == 0)
                                return index;
                } else
                        pathBuffer.UnlockBuffer();
        }

        return -1;
}


static const char*
get_leaf(const char* path)
{
        const char* name = strrchr(path, '/');
        if (name == NULL)
                return path;

        return name + 1;
}


static legacy_driver*
find_driver(dev_t device, ino_t node)
{
        DriverTable::Iterator iterator(sDriverHash);
        while (iterator.HasNext()) {
                legacy_driver* driver = iterator.Next();
                if (driver->device == device && driver->node == node)
                        return driver;
        }

        return NULL;
}


static status_t
add_driver(const char* path, image_id image)
{
        // Check if we already know this driver

        struct stat stat;
        if (image >= 0) {
                // The image ID should be a small number and hopefully the boot FS
                // doesn't use small negative values -- if it is inode based, we should
                // be relatively safe.
                stat.st_dev = -1;
                stat.st_ino = -1;
        } else {
                if (::stat(path, &stat) != 0)
                        return errno;
        }

        int32 priority = get_priority(path);

        RecursiveLocker _(sLock);

        legacy_driver* driver = sDriverHash->Lookup(get_leaf(path));
        if (driver != NULL) {
                // we know this driver
                if (strcmp(driver->path, path) != 0 && priority >= driver->priority) {
                        // TODO: do properly, but for now we just update the path if it
                        // isn't the same anymore so rescanning of drivers will work in
                        // case this driver was loaded so early that it has a boot module
                        // path and not a proper driver path
                        free((char*)driver->path);
                        driver->path = strdup(path);
                        driver->name = get_leaf(driver->path);
                        driver->binary_updated = true;
                }

                // TODO: check if this driver is a different one and has precedence
                // (ie. common supersedes system).
                //dprintf("new driver has priority %ld, old %ld\n", priority, driver->priority);
                if (priority >= driver->priority) {
                        driver->binary_updated = true;
                        return B_OK;
                }

                // TODO: test for changes here and/or via node monitoring and reload
                //      the driver if necessary
                if (driver->image < B_OK)
                        return driver->image;

                return B_OK;
        }

        // we don't know this driver, create a new entry for it

        driver = (legacy_driver*)malloc(sizeof(legacy_driver));
        if (driver == NULL)
                return B_NO_MEMORY;

        driver->path = strdup(path);
        if (driver->path == NULL) {
                free(driver);
                return B_NO_MEMORY;
        }

        driver->name = get_leaf(driver->path);
        driver->device = stat.st_dev;
        driver->node = stat.st_ino;
        driver->image = image;
        driver->last_modified = stat.st_mtim;
        driver->devices_used = 0;
        driver->binary_updated = false;
        driver->priority = priority;

        driver->api_version = 1;
        driver->find_device = NULL;
        driver->publish_devices = NULL;
        driver->uninit_driver = NULL;
        driver->uninit_hardware = NULL;
        new(&driver->devices) DeviceList;

        sDriverHash->Insert(driver);
        if (stat.st_dev > 0)
                change_driver_watcher(stat.st_dev, stat.st_ino, true);

        // Even if loading the driver fails - its entry will stay with us
        // so that we don't have to go through it again
        return load_driver(driver);
}


/*!     This is no longer part of the public kernel API, so we just export the
        symbol
*/
extern "C" status_t load_driver_symbols(const char* driverName);
status_t
load_driver_symbols(const char* driverName)
{
        // This is done globally for the whole kernel via the settings file.
        // We don't have to do anything here.

        return B_OK;
}


static status_t
reload_driver(legacy_driver* driver)
{
        dprintf("devfs: reload driver \"%s\" (%" B_PRIdDEV ", %" B_PRIdINO ")\n",
                driver->name, driver->device, driver->node);

        unload_driver(driver);

        struct stat stat;
        if (::stat(driver->path, &stat) == 0
                && (stat.st_dev != driver->device || stat.st_ino != driver->node)) {
                // The driver file has been changed, so we need to update its listener
                change_driver_watcher(driver->device, driver->node, false);

                driver->device = stat.st_dev;
                driver->node = stat.st_ino;

                change_driver_watcher(driver->device, driver->node, true);
        }

        status_t status = load_driver(driver);
        if (status != B_OK)
                unpublish_driver(driver);

        return status;
}


static void
handle_driver_events(void* /*_fs*/, int /*iteration*/)
{
        if (atomic_and(&sDriverEventsPending, 0) == 0)
                return;

        // something happened, let's see what it was

        while (true) {
                MutexLocker eventLocker(sDriverEventsLock);

                driver_event* event = sDriverEvents.RemoveHead();
                if (event == NULL)
                        break;

                eventLocker.Unlock();
                TRACE(("driver event %p, type %d\n", event, event->type));

                switch (event->type) {
                        case kAddDriver:
                        {
                                // Add new drivers
                                RecursiveLocker locker(sLock);
                                TRACE(("  add driver %p\n", event->path));

                                legacy_driver* driver = sDriverHash->Lookup(
                                        get_leaf(event->path));
                                if (driver == NULL)
                                        legacy_driver_add(event->path);
                                else if (get_priority(event->path) >= driver->priority)
                                        driver->binary_updated = true;
                                break;
                        }

                        case kRemoveDriver:
                        {
                                // Mark removed drivers as updated
                                RecursiveLocker locker(sLock);
                                TRACE(("  remove driver %p\n", event->path));

                                legacy_driver* driver = sDriverHash->Lookup(
                                        get_leaf(event->path));
                                if (driver != NULL
                                        && get_priority(event->path) >= driver->priority)
                                        driver->binary_updated = true;
                                break;
                        }

                        case kAddWatcher:
                                TRACE(("  add watcher %" B_PRId32 ":%" B_PRIdINO "\n", event->node.device,
                                        event->node.node));
                                add_node_listener(event->node.device, event->node.node,
                                        B_WATCH_STAT | B_WATCH_NAME, sDriverWatcher);
                                break;

                        case kRemoveWatcher:
                                TRACE(("  remove watcher %" B_PRId32 ":%" B_PRIdINO "\n", event->node.device,
                                        event->node.node));
                                remove_node_listener(event->node.device, event->node.node,
                                        sDriverWatcher);
                                break;
                }

                delete event;
        }

        // Reload updated drivers

        RecursiveLocker locker(sLock);

        DriverTable::Iterator iterator(sDriverHash);
        while (iterator.HasNext()) {
                legacy_driver* driver = iterator.Next();

                if (!driver->binary_updated || driver->devices_used != 0)
                        continue;

                // try to reload the driver
                reload_driver(driver);
        }

        locker.Unlock();
}


//      #pragma mark - DriverWatcher


DriverWatcher::DriverWatcher()
{
}


DriverWatcher::~DriverWatcher()
{
}


void
DriverWatcher::EventOccurred(NotificationService& service,
        const KMessage* event)
{
        int32 opcode = event->GetInt32("opcode", -1);
        if (opcode != B_STAT_CHANGED
                || (event->GetInt32("fields", 0) & B_STAT_MODIFICATION_TIME) == 0)
                return;

        RecursiveLocker locker(sLock);

        legacy_driver* driver = find_driver(event->GetInt32("device", -1),
                event->GetInt64("node", 0));
        if (driver == NULL)
                return;

        driver->binary_updated = true;

        if (driver->devices_used == 0) {
                // trigger a reload of the driver
                atomic_add(&sDriverEventsPending, 1);
        } else {
                // driver is in use right now
                dprintf("devfs: changed driver \"%s\" is still in use\n", driver->name);
        }
}


static void
dump_driver(legacy_driver* driver)
{
        kprintf("DEVFS DRIVER: %p\n", driver);
        kprintf(" name:           %s\n", driver->name);
        kprintf(" path:           %s\n", driver->path);
        kprintf(" image:          %" B_PRId32 "\n", driver->image);
        kprintf(" device:         %" B_PRIdDEV "\n", driver->device);
        kprintf(" node:           %" B_PRIdINO "\n", driver->node);
        kprintf(" last modified:  %" B_PRIdTIME ".%ld\n", driver->last_modified.tv_sec,
                driver->last_modified.tv_nsec);
        kprintf(" devs used:      %" B_PRIu32 "\n", driver->devices_used);
        kprintf(" devs published: %" B_PRId32 "\n", driver->devices.Count());
        kprintf(" binary updated: %d\n", driver->binary_updated);
        kprintf(" priority:       %" B_PRId32 "\n", driver->priority);
        kprintf(" api version:    %" B_PRId32 "\n", driver->api_version);
        kprintf(" hooks:          find_device %p, publish_devices %p\n"
                "                 uninit_driver %p, uninit_hardware %p\n",
                driver->find_device, driver->publish_devices, driver->uninit_driver,
                driver->uninit_hardware);
}


static int
dump_device(int argc, char** argv)
{
        if (argc < 2 || !strcmp(argv[1], "--help")) {
                kprintf("usage: %s [device]\n", argv[0]);
                return 0;
        }

        LegacyDevice* device = (LegacyDevice*)parse_expression(argv[1]);

        kprintf("LEGACY DEVICE: %p\n", device);
        kprintf(" path:     %s\n", device->Path());
        kprintf(" hooks:    %p\n", device->Hooks());
        device_hooks* hooks = device->Hooks();
        kprintf("  close()     %p\n", hooks->close);
        kprintf("  free()      %p\n", hooks->free);
        kprintf("  control()   %p\n", hooks->control);
        kprintf("  read()      %p\n", hooks->read);
        kprintf("  write()     %p\n", hooks->write);
        kprintf("  select()    %p\n", hooks->select);
        kprintf("  deselect()  %p\n", hooks->deselect);
        dump_driver(device->Driver());

        return 0;
}


static int
dump_driver(int argc, char** argv)
{
        if (argc < 2) {
                // print list of all drivers
                kprintf("address    image used publ.   pri name\n");
                DriverTable::Iterator iterator(sDriverHash);
                while (iterator.HasNext()) {
                        legacy_driver* driver = iterator.Next();

                        kprintf("%p  %5" B_PRId32 " %3" B_PRIu32 " %5" B_PRId32 " %c "
                                "%3" B_PRId32 " %s\n", driver,
                                driver->image < 0 ? -1 : driver->image,
                                driver->devices_used, driver->devices.Count(),
                                driver->binary_updated ? 'U' : ' ', driver->priority,
                                driver->name);
                }

                return 0;
        }

        if (!strcmp(argv[1], "--help")) {
                kprintf("usage: %s [name]\n", argv[0]);
                return 0;
        }

        legacy_driver* driver = sDriverHash->Lookup(argv[1]);
        if (driver == NULL) {
                kprintf("Driver named \"%s\" not found.\n", argv[1]);
                return 0;
        }

        dump_driver(driver);
        return 0;
}


//      #pragma mark -


DirectoryIterator::DirectoryIterator(const char* path, const char* subPath,
                bool recursive)
        :
        fDirectory(NULL),
        fBasePath(NULL),
        fCurrentName(NULL)
{
        SetTo(path, subPath, recursive);
}


DirectoryIterator::~DirectoryIterator()
{
        Unset();
}


void
DirectoryIterator::SetTo(const char* path, const char* subPath, bool recursive)
{
        Unset();
        fRecursive = recursive;

        const bool disableUserAddOns = get_safemode_boolean(B_SAFEMODE_DISABLE_USER_ADD_ONS, false);

        if (path == NULL) {
                // add default paths in reverse order as AddPath() will add on a stack
                KPath pathBuffer;
                for (int32 i = B_COUNT_OF(kDriverPaths) - 1; i >= 0; i--) {
                        if (i < 3 && disableUserAddOns)
                                continue;

                        if (__find_directory(kDriverPaths[i], gBootDevice, true,
                                        pathBuffer.LockBuffer(), pathBuffer.BufferSize()) == B_OK) {
                                pathBuffer.UnlockBuffer();
                                pathBuffer.Append("kernel");
                                AddPath(pathBuffer.Path(), subPath);
                        } else
                                pathBuffer.UnlockBuffer();
                }
        } else
                AddPath(path, subPath);
}


status_t
DirectoryIterator::GetNext(KPath& path, struct stat& stat)
{
next_directory:
        while (fDirectory == NULL) {
                delete fBasePath;
                fBasePath = NULL;

                if (!fPaths.Pop(&fBasePath))
                        return B_ENTRY_NOT_FOUND;

                fDirectory = opendir(fBasePath->Path());
        }

next_entry:
        struct dirent* dirent = readdir(fDirectory);
        if (dirent == NULL) {
                // get over to next directory on the stack
                closedir(fDirectory);
                fDirectory = NULL;

                goto next_directory;
        }

        if (!strcmp(dirent->d_name, "..") || !strcmp(dirent->d_name, "."))
                goto next_entry;

        fCurrentName = dirent->d_name;

        path.SetTo(fBasePath->Path());
        path.Append(fCurrentName);

        if (::stat(path.Path(), &stat) != 0)
                goto next_entry;

        if (S_ISDIR(stat.st_mode) && fRecursive) {
                KPath* nextPath = new(nothrow) KPath(path);
                if (!nextPath)
                        return B_NO_MEMORY;
                if (fPaths.Push(nextPath) != B_OK)
                        return B_NO_MEMORY;

                goto next_entry;
        }

        return B_OK;
}


void
DirectoryIterator::Unset()
{
        if (fDirectory != NULL) {
                closedir(fDirectory);
                fDirectory = NULL;
        }

        delete fBasePath;
        fBasePath = NULL;

        KPath* path;
        while (fPaths.Pop(&path))
                delete path;
}


void
DirectoryIterator::AddPath(const char* basePath, const char* subPath)
{
        KPath* path = new(nothrow) KPath(basePath);
        if (!path)
                panic("out of memory");
        if (subPath != NULL)
                path->Append(subPath);

        fPaths.Push(path);
}


//      #pragma mark -


DirectoryWatcher::DirectoryWatcher()
{
}


DirectoryWatcher::~DirectoryWatcher()
{
}


void
DirectoryWatcher::EventOccurred(NotificationService& service,
        const KMessage* event)
{
        int32 opcode = event->GetInt32("opcode", -1);
        dev_t device = event->GetInt32("device", -1);
        ino_t directory = event->GetInt64("directory", -1);
        const char* name = event->GetString("name", NULL);

        if (opcode == B_ENTRY_MOVED) {
                // Determine whether it's a move within, out of, or into one
                // of our watched directories.
                ino_t from = event->GetInt64("from directory", -1);
                ino_t to = event->GetInt64("to directory", -1);
                if (sDirectoryNodeHash.Lookup(&from) == NULL) {
                        directory = to;
                        opcode = B_ENTRY_CREATED;
                } else if (sDirectoryNodeHash.Lookup(&to) == NULL) {
                        directory = from;
                        opcode = B_ENTRY_REMOVED;
                } else {
                        // Move within, don't do anything for now
                        // TODO: adjust driver priority if necessary
                        return;
                }
        }

        KPath path(B_PATH_NAME_LENGTH + 1);
        if (path.InitCheck() != B_OK || vfs_entry_ref_to_path(device, directory,
                        name, true, path.LockBuffer(), path.BufferSize()) != B_OK)
                return;

        path.UnlockBuffer();

        dprintf("driver \"%s\" %s\n", path.Leaf(),
                opcode == B_ENTRY_CREATED ? "added" : "removed");

        driver_event* driverEvent = new(std::nothrow) driver_event(
                opcode == B_ENTRY_CREATED ? kAddDriver : kRemoveDriver);
        if (driverEvent == NULL)
                return;

        strlcpy(driverEvent->path, path.Path(), sizeof(driverEvent->path));

        MutexLocker _(sDriverEventsLock);
        sDriverEvents.Add(driverEvent);
        atomic_add(&sDriverEventsPending, 1);
}


//      #pragma mark -


static void
start_watching(const char* base, const char* sub)
{
        KPath path(base);
        path.Append(sub);

        // TODO: create missing directories?
        struct stat stat;
        if (::stat(path.Path(), &stat) != 0)
                return;

        add_node_listener(stat.st_dev, stat.st_ino, B_WATCH_DIRECTORY,
                sDirectoryWatcher);

        directory_node_entry* entry = new(std::nothrow) directory_node_entry;
        if (entry != NULL) {
                entry->node = stat.st_ino;
                sDirectoryNodeHash.Insert(entry);
        }
}


static struct driver_entry*
new_driver_entry(const char* path, dev_t device, ino_t node)
{
        driver_entry* entry = new(std::nothrow) driver_entry;
        if (entry == NULL)
                return NULL;

        entry->path = strdup(path);
        if (entry->path == NULL) {
                delete entry;
                return NULL;
        }

        entry->device = device;
        entry->node = node;
        entry->busses = 0;
        return entry;
}


/*!     Iterates over the given list and tries to load all drivers in that list.
        The list is emptied and freed during the traversal.
*/
static status_t
try_drivers(DriverEntryList& list)
{
        while (true) {
                driver_entry* entry = list.RemoveHead();
                if (entry == NULL)
                        break;

                image_id image = load_kernel_add_on(entry->path);
                if (image >= 0) {
                        // check if it's an old-style driver
                        if (legacy_driver_add(entry->path) == B_OK) {
                                // we have a driver
                                dprintf("loaded driver %s\n", entry->path);
                        }

                        unload_kernel_add_on(image);
                }

                free(entry->path);
                delete entry;
        }

        return B_OK;
}


static status_t
probe_for_drivers(const char* type)
{
        TRACE(("probe_for_drivers(type = %s)\n", type));

        if (gBootDevice < 0)
                return B_OK;

        DriverEntryList drivers;

        // build list of potential drivers for that type

        DirectoryIterator iterator(NULL, type, false);
        struct stat stat;
        KPath path;

        while (iterator.GetNext(path, stat) == B_OK) {
                if (S_ISDIR(stat.st_mode)) {
                        add_node_listener(stat.st_dev, stat.st_ino, B_WATCH_DIRECTORY,
                                sDirectoryWatcher);

                        directory_node_entry* entry
                                = new(std::nothrow) directory_node_entry;
                        if (entry != NULL) {
                                entry->node = stat.st_ino;
                                sDirectoryNodeHash.Insert(entry);
                        }

                        // We need to make sure that drivers in ie. "audio/raw/" can
                        // be found as well - therefore, we must make sure that "audio"
                        // exists on /dev.

                        size_t length = strlen("drivers/dev");
                        if (strncmp(type, "drivers/dev", length))
                                continue;

                        path.SetTo(type);
                        path.Append(iterator.CurrentName());
                        devfs_publish_directory(path.Path() + length + 1);
                        continue;
                }

                driver_entry* entry = new_driver_entry(path.Path(), stat.st_dev,
                        stat.st_ino);
                if (entry == NULL)
                        return B_NO_MEMORY;

                TRACE(("found potential driver: %s\n", path.Path()));
                drivers.Add(entry);
        }

        if (drivers.IsEmpty())
                return B_OK;

        // ToDo: do something with the remaining drivers... :)
        try_drivers(drivers);
        return B_OK;
}


//      #pragma mark - LegacyDevice


LegacyDevice::LegacyDevice(legacy_driver* driver, const char* path,
                device_hooks* hooks)
        :
        fDriver(driver),
        fRepublished(true),
        fRemovedFromParent(false)
{
        fDeviceModule = (device_module_info*)malloc(sizeof(device_module_info));
        if (fDeviceModule != NULL) {
                memset(fDeviceModule, 0, sizeof(device_module_info));
                SetHooks(hooks);
        }

        fDeviceData = this;
        fPath = strdup(path);

}


LegacyDevice::~LegacyDevice()
{
        free(fDeviceModule);
        free((char*)fPath);
}


status_t
LegacyDevice::InitCheck() const
{
        return fDeviceModule != NULL && fPath != NULL ? B_OK : B_NO_MEMORY;
}


status_t
LegacyDevice::InitDevice()
{
        RecursiveLocker _(sLock);

        if (fInitialized++ > 0)
                return B_OK;

        if (fDriver != NULL && fDriver->devices_used == 0
                && (fDriver->image < 0 || fDriver->binary_updated)) {
                status_t status = reload_driver(fDriver);
                if (status < B_OK)
                        return status;
        }

        if (fDriver != NULL)
                fDriver->devices_used++;

        return B_OK;
}


void
LegacyDevice::UninitDevice()
{
        RecursiveLocker _(sLock);

        if (fInitialized-- > 1)
                return;

        if (fDriver != NULL) {
                if (--fDriver->devices_used == 0 && fDriver->devices.IsEmpty())
                        unload_driver(fDriver);
                fDriver = NULL;
        }
}


void
LegacyDevice::Removed()
{
        RecursiveLocker _(sLock);

        if (!fRemovedFromParent && fDriver != NULL)
                fDriver->devices.Remove(this);

        delete this;
}


status_t
LegacyDevice::Control(void* _cookie, int32 op, void* buffer, size_t length)
{
        switch (op) {
                case B_GET_DRIVER_FOR_DEVICE:
                        if (length != 0 && length <= strlen(fDriver->path))
                                return ERANGE;
                        return user_strlcpy(static_cast<char*>(buffer), fDriver->path, length);
                default:
                        return AbstractModuleDevice::Control(_cookie, op, buffer, length);
        }
}


void
LegacyDevice::SetHooks(device_hooks* hooks)
{
        // TODO: setup compatibility layer!
        fHooks = hooks;

        fDeviceModule->close = hooks->close;
        fDeviceModule->free = hooks->free;
        fDeviceModule->control = hooks->control;
        fDeviceModule->read = hooks->read;
        fDeviceModule->write = hooks->write;

        if (fDriver == NULL || fDriver->api_version >= 2) {
                // According to Be newsletter, vol II, issue 36,
                // version 2 added readv/writev, which we don't support, but also
                // select/deselect.
                if (hooks->select != NULL) {
                        // Note we set the module's select to a non-null value to indicate
                        // that we have select. HasSelect() will therefore return the
                        // correct answer. As Select() is virtual our compatibility
                        // version below is going to be called though, that redirects to
                        // the proper select hook, so it is ok to set it to an invalid
                        // address here.
                        fDeviceModule->select = (status_t (*)(void*, uint8, selectsync*))~0;
                }

                fDeviceModule->deselect = hooks->deselect;
        }
}


status_t
LegacyDevice::Open(const char* path, int openMode, void** _cookie)
{
        return Hooks()->open(path, openMode, _cookie);
}


status_t
LegacyDevice::Select(void* cookie, uint8 event, selectsync* sync)
{
        return Hooks()->select(cookie, event, 0, sync);
}


//      #pragma mark - kernel private API


extern "C" void
legacy_driver_add_preloaded(kernel_args* args)
{
        // NOTE: This function does not exit in case of error, since it
        // needs to unload the images then. Also the return code of
        // the path operations is kept separate from the add_driver()
        // success, so that even if add_driver() fails for one driver, it
        // is still tried for the other drivers.
        // NOTE: The initialization success of the path objects is implicitely
        // checked by the immediately following functions.
        KPath basePath;
        status_t status = __find_directory(B_SYSTEM_ADDONS_DIRECTORY,
                gBootDevice, false, basePath.LockBuffer(), basePath.BufferSize());
        if (status != B_OK) {
                dprintf("legacy_driver_add_preloaded: find_directory() failed: "
                        "%s\n", strerror(status));
        }
        basePath.UnlockBuffer();
        if (status == B_OK)
                status = basePath.Append("kernel");
        if (status != B_OK) {
                dprintf("legacy_driver_add_preloaded: constructing base driver "
                        "path failed: %s\n", strerror(status));
                return;
        }

        struct preloaded_image* image;
        for (image = args->preloaded_images; image != NULL; image = image->next) {
                if (image->is_module || image->id < 0)
                        continue;

                KPath imagePath(basePath);
                status = imagePath.Append(image->name);

                // try to add the driver
                TRACE(("legacy_driver_add_preloaded: adding driver %s\n",
                        imagePath.Path()));

                if (status == B_OK)
                        status = add_driver(imagePath.Path(), image->id);
                if (status != B_OK) {
                        dprintf("legacy_driver_add_preloaded: Failed to add \"%s\": %s\n",
                                (char*)image->name, strerror(status));
                        unload_kernel_add_on(image->id);
                }
        }
}


extern "C" status_t
legacy_driver_add(const char* path)
{
        return add_driver(path, -1);
}


extern "C" status_t
legacy_driver_publish(const char* path, device_hooks* hooks)
{
        // we don't have a driver, just publish the hooks
        LegacyDevice* device = new(std::nothrow) LegacyDevice(NULL, path, hooks);
        if (device == NULL)
                return B_NO_MEMORY;

        status_t status = device->InitCheck();
        if (status == B_OK)
                status = devfs_publish_device(path, device);

        if (status != B_OK)
                delete device;

        return status;
}


extern "C" status_t
legacy_driver_rescan(const char* driverName)
{
        RecursiveLocker locker(sLock);

        legacy_driver* driver = sDriverHash->Lookup(driverName);
        if (driver == NULL)
                return B_ENTRY_NOT_FOUND;

        // Republish the driver's entries
        return republish_driver(driver);
}


extern "C" status_t
legacy_driver_probe(const char* subPath)
{
        TRACE(("legacy_driver_probe(type = %s)\n", subPath));

        char devicePath[64];
        snprintf(devicePath, sizeof(devicePath), "drivers/dev%s%s",
                subPath[0] ? "/" : "", subPath);

        if (!sWatching && gBootDevice > 0) {
                // We're probing the actual boot volume for the first time,
                // let's watch its driver directories for changes
                KPath path;

                new(&sDirectoryWatcher) DirectoryWatcher;

                bool disableUserAddOns = get_safemode_boolean(
                        B_SAFEMODE_DISABLE_USER_ADD_ONS, false);

                for (uint32 i = 0; i < sizeof(kDriverPaths) / sizeof(kDriverPaths[0]); i++) {
                        if (i < 3 && disableUserAddOns)
                                continue;

                        if (__find_directory(kDriverPaths[i], gBootDevice, true,
                                        path.LockBuffer(), path.BufferSize()) == B_OK) {
                                path.UnlockBuffer();
                                path.Append("kernel/drivers");

                                start_watching(path.Path(), "bin");
                        } else
                                path.UnlockBuffer();
                }

                sWatching = true;
        }

        return probe_for_drivers(devicePath);
}


extern "C" status_t
legacy_driver_init(void)
{
        sDriverHash = new(std::nothrow) DriverTable();
        if (sDriverHash == NULL || sDriverHash->Init(DRIVER_HASH_SIZE) != B_OK)
                return B_NO_MEMORY;

        recursive_lock_init(&sLock, "legacy driver");

        new(&sDriverWatcher) DriverWatcher;
        new(&sDriverEvents) DriverEventList;

        register_kernel_daemon(&handle_driver_events, NULL, 10);
                // once every second

        add_debugger_command("legacy_driver", &dump_driver,
                "info about a legacy driver entry");
        add_debugger_command("legacy_device", &dump_device,
                "info about a legacy device");

        return B_OK;
}