blob: 97fee2838ac652ed75f185f09b22420037204982 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "devcoordinator.h"
#include "devmgr.h"
#include "memfs-private.h"
#include <zircon/fidl.h>
#include <zircon/listnode.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <zircon/device/vfs.h>
#include <fuchsia/io/c/fidl.h>
#include <lib/fdio/remoteio.h>
#include <lib/fdio/util.h>
#include <lib/fidl/coding.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct dc_watcher watcher_t;
typedef struct dc_iostate iostate_t;
struct dc_watcher {
watcher_t* next;
devnode_t* devnode;
uint32_t mask;
zx_handle_t handle;
};
struct dc_devnode {
const char* name;
uint64_t ino;
// nullptr if we are a pure directory node,
// otherwise the device we are referencing
device_t* device;
watcher_t* watchers;
// entry in our parent devnode's children list
list_node_t node;
// list of our child devnodes
list_node_t children;
// list of attached iostates
list_node_t iostate;
// used to assign unique small device numbers
// for class device links
uint32_t seqcount;
};
struct dc_iostate {
port_handler_t ph;
// entry in our devnode's iostate list
list_node_t node;
// pointer to our devnode, nullptr if it has been removed
devnode_t* devnode;
uint64_t readdir_ino;
};
extern port_t dc_port;
static uint64_t next_ino = 2;
static devnode_t root_devnode = []() {
devnode_t node = {};
node.name = "";
node.ino = 1;
node.children = LIST_INITIAL_VALUE(root_devnode.children);
node.iostate = LIST_INITIAL_VALUE(root_devnode.iostate);
return node;
}();
static devnode_t* class_devnode;
static zx_status_t dc_fidl_handler(port_handler_t* ph, zx_signals_t signals, uint32_t evt);
static devnode_t* devfs_mkdir(devnode_t* parent, const char* name);
#define PNMAX 16
static const char* proto_name(uint32_t id, char buf[PNMAX]) {
switch (id) {
#define DDK_PROTOCOL_DEF(tag, val, name, flags) case val: return name;
#include <ddk/protodefs.h>
default:
snprintf(buf, PNMAX, "proto-%08x", id);
return buf;
}
}
typedef struct {
const char* name;
devnode_t* devnode;
uint32_t id;
uint32_t flags;
} pinfo_t;
static pinfo_t proto_info[] = {
#define DDK_PROTOCOL_DEF(tag, val, name, flags) { name, nullptr, val, flags },
#include <ddk/protodefs.h>
{ nullptr, nullptr, 0, 0 },
};
static devnode_t* proto_dir(uint32_t id) {
for (pinfo_t* info = proto_info; info->name; info++) {
if (info->id == id) {
return info->devnode;
}
}
return nullptr;
}
static void prepopulate_protocol_dirs(void) {
class_devnode = devfs_mkdir(&root_devnode, "class");
for (pinfo_t* info = proto_info; info->name; info++) {
if (!(info->flags & PF_NOPUB)) {
info->devnode = devfs_mkdir(class_devnode, info->name);
}
}
}
void describe_error(zx_handle_t h, zx_status_t status) {
zxrio_describe_t msg;
memset(&msg, 0, sizeof(msg));
msg.hdr.ordinal = ZXFIDL_ON_OPEN;
msg.status = status;
zx_channel_write(h, 0, &msg, sizeof(zxrio_describe_t), nullptr, 0);
zx_handle_close(h);
}
static zx_status_t iostate_create(devnode_t* dn, zx_handle_t h) {
iostate_t* ios = static_cast<iostate_t*>(calloc(1, sizeof(iostate_t)));
if (ios == nullptr) {
return ZX_ERR_NO_MEMORY;
}
ios->ph.handle = h;
ios->ph.waitfor = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
ios->ph.func = dc_fidl_handler;
ios->devnode = dn;
list_add_tail(&dn->iostate, &ios->node);
zx_status_t r;
if ((r = port_wait(&dc_port, &ios->ph)) < 0) {
list_delete(&ios->node);
free(ios);
}
return r;
}
static void iostate_destroy(iostate_t* ios) {
if (ios->devnode) {
list_delete(&ios->node);
ios->devnode = nullptr;
}
zx_handle_close(ios->ph.handle);
ios->ph.handle = ZX_HANDLE_INVALID;
free(ios);
}
// A devnode is a directory (from stat's perspective) if
// it has children, or if it doesn't have a device, or if
// its device has no rpc handle
static bool devnode_is_dir(devnode_t* dn) {
if (list_is_empty(&dn->children)) {
return (dn->device == nullptr) || (dn->device->hrpc == ZX_HANDLE_INVALID);
}
return true;
}
// Local devnodes are ones that we should not hand off OPEN
// RPCs to the underlying devhost
static bool devnode_is_local(devnode_t* dn) {
if (dn->device == nullptr) {
return true;
}
if (dn->device->hrpc == ZX_HANDLE_INVALID) {
return true;
}
if (dn->device->flags & DEV_CTX_MUST_ISOLATE) {
return true;
}
return false;
}
static void devfs_notify(devnode_t* dn, const char* name, unsigned op) {
watcher_t* w = dn->watchers;
if (w == nullptr) {
return;
}
size_t len = strlen(name);
if (len > VFS_WATCH_NAME_MAX) {
return;
}
uint8_t msg[VFS_WATCH_NAME_MAX + 2];
msg[0] = static_cast<uint8_t>(op);
msg[1] = static_cast<uint8_t>(len);
memcpy(msg + 2, name, len);
// convert to mask
op = (1u << op);
watcher_t** wp;
watcher_t* next;
for (wp = &dn->watchers; w != nullptr; w = next) {
next = w->next;
if (!(w->mask & op)) {
continue;
}
if (zx_channel_write(w->handle, 0, msg, static_cast<uint32_t>(len + 2), nullptr, 0) < 0) {
*wp = next;
zx_handle_close(w->handle);
free(w);
} else {
wp = &w->next;
}
}
}
static zx_status_t devfs_watch(devnode_t* dn, zx_handle_t h, uint32_t mask) {
watcher_t* watcher = static_cast<watcher_t*>(calloc(1, sizeof(watcher_t)));
if (watcher == nullptr) {
zx_handle_close(h);
return ZX_ERR_NO_MEMORY;
}
watcher->devnode = dn;
watcher->next = dn->watchers;
watcher->handle = h;
watcher->mask = mask;
dn->watchers = watcher;
if (mask & VFS_WATCH_MASK_EXISTING) {
devnode_t* child;
list_for_every_entry(&dn->children, child, devnode_t, node) {
if (child->device && (child->device->flags & DEV_CTX_INVISIBLE)) {
continue;
}
//TODO: send multiple per write
devfs_notify(dn, child->name, VFS_WATCH_EVT_EXISTING);
}
devfs_notify(dn, "", VFS_WATCH_EVT_IDLE);
}
// Don't send EXISTING or IDLE events from now on...
watcher->mask &= ~(VFS_WATCH_MASK_EXISTING | VFS_WATCH_MASK_IDLE);
return ZX_OK;
}
// If namelen is nonzero, it is the null-terminator-inclusive length
// of name, which should be copied into the devnode. Otherwise name
// is guaranteed to exist for the lifetime of the devnode.
static devnode_t* devfs_mknode(device_t* dev, const char* name, size_t namelen) {
devnode_t* dn = static_cast<devnode_t*>(calloc(1, sizeof(devnode_t) + namelen));
if (dn == nullptr) {
return nullptr;
}
if (namelen > 0) {
char* p = (char*) (dn + 1);
memcpy(p, name, namelen);
dn->name = p;
} else {
dn->name = name;
}
dn->ino = next_ino++;
dn->device = dev;
list_initialize(&dn->children);
list_initialize(&dn->iostate);
return dn;
}
static devnode_t* devfs_mkdir(devnode_t* parent, const char* name) {
devnode_t* dn = devfs_mknode(nullptr, name, 0);
if (dn == nullptr) {
return nullptr;
}
list_add_tail(&parent->children, &dn->node);
return dn;
}
static devnode_t* devfs_lookup(devnode_t* parent, const char* name) {
devnode_t* child;
list_for_every_entry(&parent->children, child, devnode_t, node) {
if (!strcmp(name, child->name)) {
return child;
}
}
return nullptr;
}
void devfs_advertise(device_t* dev) {
if (dev->link) {
devnode_t* dir = proto_dir(dev->protocol_id);
devfs_notify(dir, dev->link->name, VFS_WATCH_EVT_ADDED);
}
if (dev->parent && dev->parent->self) {
devfs_notify(dev->parent->self, dev->self->name, VFS_WATCH_EVT_ADDED);
}
}
// TODO: generate a MODIFIED event rather than back to back REMOVED and ADDED
void devfs_advertise_modified(device_t* dev) {
if (dev->link) {
devnode_t* dir = proto_dir(dev->protocol_id);
devfs_notify(dir, dev->link->name, VFS_WATCH_EVT_REMOVED);
devfs_notify(dir, dev->link->name, VFS_WATCH_EVT_ADDED);
}
if (dev->parent && dev->parent->self) {
devfs_notify(dev->parent->self, dev->self->name, VFS_WATCH_EVT_REMOVED);
devfs_notify(dev->parent->self, dev->self->name, VFS_WATCH_EVT_ADDED);
}
}
zx_status_t devfs_publish(device_t* parent, device_t* dev) {
if ((parent->self == nullptr) || (dev->self != nullptr) || (dev->link != nullptr)) {
return ZX_ERR_INTERNAL;
}
devnode_t* dnself = devfs_mknode(dev, dev->name, 0);
if (dnself == nullptr) {
return ZX_ERR_NO_MEMORY;
}
if ((dev->protocol_id == ZX_PROTOCOL_TEST_PARENT) ||
(dev->protocol_id == ZX_PROTOCOL_MISC_PARENT) ||
(dev->protocol_id == ZX_PROTOCOL_MISC)) {
// misc devices are singletons, not a class
// in the sense of other device classes.
// They do not get aliases in /dev/class/misc/...
// instead they exist only under their parent
// device.
goto done;
}
// Create link in /dev/class/... if this id has a published class
devnode_t* dir;
dir = proto_dir(dev->protocol_id);
if (dir != nullptr) {
char tmp[32];
const char* name = dev->name;
size_t namelen = 0;
if (dev->protocol_id != ZX_PROTOCOL_CONSOLE) {
for (unsigned n = 0; n < 1000; n++) {
snprintf(tmp, sizeof(tmp), "%03u", (dir->seqcount++) % 1000);
if (devfs_lookup(dir, tmp) == nullptr) {
name = tmp;
namelen = 4;
goto got_name;
}
}
free(dnself);
return ZX_ERR_ALREADY_EXISTS;
got_name:
;
}
devnode_t* dnlink = devfs_mknode(dev, name, namelen);
if (dnlink == nullptr) {
free(dnself);
return ZX_ERR_NO_MEMORY;
}
// add link node to class directory
list_add_tail(&dir->children, &dnlink->node);
dev->link = dnlink;
}
done:
// add self node to parent directory
list_add_tail(&parent->self->children, &dnself->node);
dev->self = dnself;
if (!(dev->flags & DEV_CTX_INVISIBLE)) {
devfs_advertise(dev);
}
return ZX_OK;
}
static void _devfs_remove(devnode_t* dn) {
if (list_in_list(&dn->node)) {
list_delete(&dn->node);
}
// detach all connected iostates
iostate_t* ios;
list_for_every_entry(&dn->iostate, ios, iostate_t, node) {
ios->devnode = nullptr;
zx_handle_close(ios->ph.handle);
ios->ph.handle = ZX_HANDLE_INVALID;
}
// notify own file watcher
if ((dn->device == nullptr) ||
!(dn->device->flags & DEV_CTX_INVISIBLE)) {
devfs_notify(dn, "", VFS_WATCH_EVT_DELETED);
}
// disconnect from device and notify parent/link directory watchers
if (dn->device != nullptr) {
if (dn->device->self == dn) {
dn->device->self = nullptr;
if ((dn->device->parent != nullptr) &&
(dn->device->parent->self != nullptr) &&
!(dn->device->flags & DEV_CTX_INVISIBLE)) {
devfs_notify(dn->device->parent->self, dn->name, VFS_WATCH_EVT_REMOVED);
}
}
if (dn->device->link == dn) {
dn->device->link = nullptr;
if (!(dn->device->flags & DEV_CTX_INVISIBLE)) {
devnode_t* dir = proto_dir(dn->device->protocol_id);
devfs_notify(dir, dn->name, VFS_WATCH_EVT_REMOVED);
}
}
dn->device = nullptr;
}
// destroy all watchers
watcher_t* watcher;
watcher_t* next;
for (watcher = dn->watchers; watcher != nullptr; watcher = next) {
next = watcher->next;
zx_handle_close(watcher->handle);
free(watcher);
}
dn->watchers = nullptr;
// detach children
while (list_remove_head(&dn->children) != nullptr) {
// they will be unpublished when the devices they're
// associated with are eventually destroyed
}
}
void devfs_unpublish(device_t* dev) {
if (dev->self != nullptr) {
_devfs_remove(dev->self);
dev->self = nullptr;
}
if (dev->link != nullptr) {
_devfs_remove(dev->link);
dev->link = nullptr;
}
}
static zx_status_t devfs_walk(devnode_t** _dn, char* path, char** pathout) {
devnode_t* dn = *_dn;
again:
if ((path == nullptr) || (path[0] == 0)) {
*_dn = dn;
return ZX_OK;
}
char* name = path;
char* undo = nullptr;
if ((path = strchr(path, '/')) != nullptr) {
undo = path;
*path++ = 0;
}
if (name[0] == 0) {
return ZX_ERR_BAD_PATH;
}
devnode_t* child;
list_for_every_entry(&dn->children, child, devnode_t, node) {
if (!strcmp(child->name, name)) {
if(child->device && (child->device->flags & DEV_CTX_INVISIBLE)) {
continue;
}
dn = child;
goto again;
}
}
if (dn == *_dn) {
return ZX_ERR_NOT_FOUND;
}
if (undo) {
*undo = '/';
}
*_dn = dn;
*pathout = name;
return ZX_ERR_NEXT;
}
static void devfs_open(devnode_t* dirdn, zx_handle_t h, char* path, uint32_t flags) {
if (!strcmp(path, ".")) {
path = nullptr;
}
devnode_t* dn = dirdn;
zx_status_t r = devfs_walk(&dn, path, &path);
bool describe = flags & ZX_FS_FLAG_DESCRIBE;
bool no_remote = (dn->device == nullptr) || (dn->device->hrpc == ZX_HANDLE_INVALID);
bool local_required = devnode_is_local(dn);
bool local_requested = flags & (ZX_FS_FLAG_NOREMOTE | ZX_FS_FLAG_DIRECTORY);
if (r == ZX_ERR_NEXT) {
// We only partially matched -- there's more path to walk.
if (no_remote || local_required) {
// No remote to pass this on to.
r = ZX_ERR_NOT_FOUND;
} else if (local_requested) {
// Local requested, but this is remote only.
r = ZX_ERR_NOT_SUPPORTED;
} else {
// There is more path to walk, and this node can accept
// remote requests.
r = ZX_OK;
}
} else {
path = (char*) ".";
}
if (r < 0) {
fail:
if (describe) {
describe_error(h, r);
} else {
zx_handle_close(h);
}
return;
}
// If we are a local-only node, or we are asked to not go remote,
// or we are asked to open-as-a-directory, open locally:
if (local_requested || local_required) {
if ((r = iostate_create(dn, h)) < 0) {
goto fail;
}
if (describe) {
zxrio_describe_t msg;
memset(&msg, 0, sizeof(msg));
msg.hdr.ordinal = ZXFIDL_ON_OPEN;
msg.status = ZX_OK;
msg.extra_ptr = (zxrio_node_info_t*)FIDL_ALLOC_PRESENT;
msg.extra.tag = FDIO_PROTOCOL_DIRECTORY;
zx_channel_write(h, 0, &msg, sizeof(zxrio_describe_t), nullptr, 0);
}
return;
}
// Otherwise we will pass the request on to the remote.
fuchsia_io_DirectoryOpen(dn->device->hrpc, flags, 0, path, strlen(path), h);
}
// Double-check that OPEN (the only message we forward)
// cannot be mistaken for an internal dev coordinator RPC message
static_assert((ZXFIDL_OPEN & DC_OP_ID_BIT) == 0, "");
static zx_status_t fill_dirent(vdirent_t* de, size_t delen, uint64_t ino,
const char* name, size_t len, uint8_t type) {
size_t sz = sizeof(vdirent_t) + len;
if (sz > delen || len > NAME_MAX) {
return ZX_ERR_INVALID_ARGS;
}
de->ino = ino;
de->size = static_cast<uint8_t>(len);
de->type = type;
memcpy(de->name, name, len);
return static_cast<zx_status_t>(sz);
}
static zx_status_t devfs_readdir(devnode_t* dn, uint64_t* _ino, void* data, size_t len) {
char* ptr = static_cast<char*>(data);
uint64_t ino = *_ino;
devnode_t* child;
list_for_every_entry(&dn->children, child, devnode_t, node) {
if (child->ino <= ino) {
continue;
}
if (child->device == nullptr) {
// "pure" directories (like /dev/class/$NAME) do not show up
// if they have no children, to avoid clutter and confusion.
// They remain openable, so they can be watched.
if (list_is_empty(&child->children)) {
continue;
}
} else {
// invisible devices also do not show up
if (child->device->flags & DEV_CTX_INVISIBLE) {
continue;
}
}
ino = child->ino;
auto vdirent = reinterpret_cast<vdirent_t*>(ptr);
auto child_name_len = static_cast<uint32_t>(strlen(child->name));
zx_status_t r = fill_dirent(vdirent, len, ino, child->name, child_name_len,
VTYPE_TO_DTYPE(V_TYPE_DIR));
if (r < 0) {
break;
}
ptr += r;
len -= r;
}
*_ino = ino;
return static_cast<zx_status_t>(ptr - static_cast<char*>(data));
}
// Helper macros for |devfs_fidl_handler| which make it easier
// avoid typing generated names.
// Decode the incoming request, returning an error and consuming
// all handles on error.
#define DECODE_REQUEST(MSG, METHOD) \
do { \
zx_status_t r; \
if ((r = fidl_decode_msg(&fuchsia_io_ ## METHOD ##RequestTable, \
msg, nullptr)) != ZX_OK) { \
return r; \
} \
} while(0);
// Define a variable |request| from the incoming method, of
// the requested type.
#define DEFINE_REQUEST(MSG, METHOD) \
fuchsia_io_ ## METHOD ## Request* request = \
(fuchsia_io_ ## METHOD ## Request*) MSG->bytes;
static zx_status_t devfs_fidl_handler(fidl_msg_t* msg, fidl_txn_t* txn, void* cookie) {
auto ios = static_cast<iostate_t*>(cookie);
devnode_t* dn = ios->devnode;
if (dn == nullptr) {
return ZX_ERR_PEER_CLOSED;
}
auto hdr = static_cast<fidl_message_header_t*>(msg->bytes);
zx_status_t r;
switch (hdr->ordinal) {
case ZXFIDL_CLONE: {
DECODE_REQUEST(msg, NodeClone);
DEFINE_REQUEST(msg, NodeClone);
zx_handle_t h = request->object;
uint32_t flags = request->flags;
char path[PATH_MAX];
path[0] = '\0';
devfs_open(dn, h, path, flags | ZX_FS_FLAG_NOREMOTE);
return ZX_OK;
}
case ZXFIDL_DESCRIBE: {
DECODE_REQUEST(msg, NodeDescribe);
fuchsia_io_NodeInfo info;
memset(&info, 0, sizeof(info));
info.tag = fuchsia_io_NodeInfoTag_directory;
return fuchsia_io_NodeDescribe_reply(txn, &info);
}
case ZXFIDL_OPEN: {
DECODE_REQUEST(msg, DirectoryOpen);
DEFINE_REQUEST(msg, DirectoryOpen);
uint32_t len = static_cast<uint32_t>(request->path.size);
char* path = request->path.data;
zx_handle_t h = request->object;
uint32_t flags = request->flags;
if ((len < 1) || (len > 1024)) {
zx_handle_close(h);
} else {
path[len] = '\0';
devfs_open(dn, h, path, flags);
}
return ZX_OK;
}
case ZXFIDL_STAT: {
DECODE_REQUEST(msg, NodeGetAttr);
uint32_t mode;
if (devnode_is_dir(dn)) {
mode = V_TYPE_DIR | V_IRUSR | V_IWUSR;
} else {
mode = V_TYPE_CDEV | V_IRUSR | V_IWUSR;
}
fuchsia_io_NodeAttributes attributes;
memset(&attributes, 0, sizeof(attributes));
attributes.mode = mode;
attributes.content_size = 0;
attributes.link_count = 1;
attributes.id = dn->ino;
return fuchsia_io_NodeGetAttr_reply(txn, ZX_OK, &attributes);
}
case ZXFIDL_REWIND: {
DECODE_REQUEST(msg, DirectoryRewind);
ios->readdir_ino = 0;
return fuchsia_io_DirectoryRewind_reply(txn, ZX_OK);
}
case ZXFIDL_READDIR: {
DECODE_REQUEST(msg, DirectoryReadDirents);
DEFINE_REQUEST(msg, DirectoryReadDirents);
if (request->max_out > ZXFIDL_MAX_MSG_BYTES) {
return fuchsia_io_DirectoryReadDirents_reply(txn, ZX_ERR_INVALID_ARGS, nullptr, 0);
}
uint8_t data[request->max_out];
size_t actual = 0;
r = devfs_readdir(dn, &ios->readdir_ino, data, request->max_out);
if (r >= 0) {
actual = r;
r = ZX_OK;
}
return fuchsia_io_DirectoryReadDirents_reply(txn, r, data, actual);
}
case ZXFIDL_IOCTL: {
DECODE_REQUEST(msg, NodeIoctl);
DEFINE_REQUEST(msg, NodeIoctl);
switch (request->opcode) {
case IOCTL_VFS_WATCH_DIR: {
vfs_watch_dir_t* wd = (vfs_watch_dir_t*) request->in.data;
zx_handle_t* handles = (zx_handle_t*) request->handles.data;
if ((request->in.count != sizeof(vfs_watch_dir_t)) ||
(wd->options != 0) ||
(wd->mask & (~VFS_WATCH_MASK_ALL)) ||
request->handles.count != 1) {
zx_handle_close_many(msg->handles, msg->num_handles);
return fuchsia_io_NodeIoctl_reply(txn, ZX_ERR_INVALID_ARGS, nullptr, 0, nullptr, 0);
}
r = devfs_watch(dn, handles[0], wd->mask);
return fuchsia_io_NodeIoctl_reply(txn, r, nullptr, 0, nullptr, 0);
}
case IOCTL_VFS_QUERY_FS: {
const char* devfs_name = "devfs";
if ((request->max_out < sizeof(vfs_query_info_t) + strlen(devfs_name)) ||
(request->handles.count != 0)) {
zx_handle_close_many(msg->handles, msg->num_handles);
return fuchsia_io_NodeIoctl_reply(txn, ZX_ERR_INVALID_ARGS, nullptr, 0, nullptr, 0);
}
uint8_t out[request->max_out];
vfs_query_info_t* info = (vfs_query_info_t*) out;
memset(info, 0, sizeof(*info));
memcpy(info->name, devfs_name, strlen(devfs_name));
size_t outlen = sizeof(vfs_query_info_t) + strlen(devfs_name);
return fuchsia_io_NodeIoctl_reply(txn, ZX_OK, nullptr, 0, out, outlen);
}
default:
zx_handle_close_many(msg->handles, msg->num_handles);
return fuchsia_io_NodeIoctl_reply(txn, ZX_ERR_NOT_SUPPORTED, nullptr, 0, nullptr, 0);
}
}
}
// close inbound handles so they do not leak
zx_handle_close_many(msg->handles, msg->num_handles);
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t dc_fidl_handler(port_handler_t* ph, zx_signals_t signals, uint32_t evt) {
iostate_t* ios = containerof(ph, iostate_t, ph);
zx_status_t r;
if (signals & ZX_CHANNEL_READABLE) {
if ((r = zxfidl_handler(ph->handle, devfs_fidl_handler, ios)) == ZX_OK) {
return ZX_OK;
}
} else if (signals & ZX_CHANNEL_PEER_CLOSED) {
zxfidl_handler(ZX_HANDLE_INVALID, devfs_fidl_handler, ios);
r = ZX_ERR_STOP;
} else {
printf("dc_fidl_handler: invalid signals %x\n", signals);
exit(0);
}
iostate_destroy(ios);
return r;
}
static zx_handle_t devfs_root;
zx_handle_t devfs_root_clone(void) {
return fdio_service_clone(devfs_root);
}
void devfs_init(zx_handle_t root_job) {
printf("devmgr: init\n");
prepopulate_protocol_dirs();
root_devnode.device = coordinator_init(root_job);
root_devnode.device->self = &root_devnode;
zx_handle_t h0, h1;
if (zx_channel_create(0, &h0, &h1) != ZX_OK) {
return;
} else if (iostate_create(&root_devnode, h0) != ZX_OK) {
zx_handle_close(h0);
zx_handle_close(h1);
return;
}
devfs_root = h1;
}