text: // 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 "devfs.h"
  
  #include <fcntl.h>
  #include <fuchsia/io/c/fidl.h>
  #include <lib/async/cpp/wait.h>
  #include <lib/fdio/directory.h>
  #include <lib/fidl/coding.h>
  #include <lib/fidl/txn_header.h>
  #include <lib/memfs/cpp/vnode.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <zircon/device/vfs.h>
  #include <zircon/fidl.h>
  #include <zircon/status.h>
  #include <zircon/syscalls.h>
  #include <zircon/types.h>
  
  #include <algorithm>
  #include <memory>
  
  #include <ddk/driver.h>
  #include <fbl/intrusive_double_list.h>
  #include <fbl/string.h>
  #include <src/storage/deprecated-fs-fidl-handler/fidl-handler.h>
  
  #include "async_loop_owned_rpc_handler.h"
  #include "coordinator.h"
  #include "lib/fidl/cpp/message_part.h"
  #include "src/devices/lib/log/log.h"
  
  namespace {
  
  // On OpenMsg and Describe Msg are hand-rolled structs to mirror the `OnOpen`
  // event and `Describe` method from fuchsia-io: see
  // zircon/system/fidl/fuchsia-io/io.fidl. We are hand-rolling this because these
  // methods contain NodeInfo unions which is not supported by the C FIDL
  // bindings, and migrating this code to LLCPP is out-of-scope for now.
  struct OnOpenMsg {
    FIDL_ALIGNDECL
  
    // This is the inline, or primary, part of the FIDL message.
    struct {
      fidl_message_header_t hdr;
      int32_t s;
      fidl_xunion_t node_info;
    } primary;
  
    // This is the out-of-line, or secondary, part of the FIDL message, which may
    // or may not be present, depending on the value of .node_info's tag.
    //
    // Note that `directory` is the _only_ NodeInfo union member used in this
    // file, whereas the actual definition of NodeInfo in io.fidl has many more
    // members. Including the other members here would extend the size of the
    // union beyond sizeof(fuchsia_io_DirectoryObject), which means that the
    // outgoing FIDL message would contain extraneous zero bytes.
    //
    // For extra context: Remember that the old-wireformat static unions' payload
    // size is the maximum of its union members' size, whereas the v1-wireformat
    // extensible unions' payload size depends on the specific union member that's
    // present. We are writing the v1 wire format, and this code only ever sets
    // NodeInfo's union member to be a Directory, so `directory` is only NodeInfo
    // union member needed in this definition.
    fuchsia_io_DirectoryObject directory;
  };
  
  // This is a hand-rolled FIDL struct, see OnOpenMsg
  struct DescribeMsg {
    FIDL_ALIGNDECL
  
    // This is the inline, or primary, part of the FIDL message.
    struct {
      fidl_message_header_t hdr;
      fidl_xunion_t node_info;
    } primary;
  
    fuchsia_io_DirectoryObject directory;
  };
  
  // Sets |node_info| to be a Directory, in the encoded form. It is the caller's
  // responsibility to ensure that the directory object is zeroed out.
  void SetNodeInfoAsDirectory(fidl_xunion_t* node_info) {
    // kNodeInfoTagDirectory below is intentionally hard-coded to the ordinal
    // for NodeInfo.directory. We could also look this up in the coding
    // tables, but doing that is arguably less performant and less safe, since
    // we need to search the NodeInfo coding table's fields for the directory
    // union member, and there's questions around what to do if the field
    // isn't found. Given that a union member ordinal is part of its ABI, it's
    // extremely unlikely to ever change, so it's safe enough to hard-code it
    // here. See
    // <https://fuchsia-review.googlesource.com/c/fuchsia/+/383902/2/src/devices/bin/driver_manager/devfs.cc#495>
    // for more context.
    constexpr fidl_xunion_tag_t kNodeInfoTagDirectory = 3ul;
    node_info->tag = kNodeInfoTagDirectory;
  
    node_info->envelope.num_bytes = FIDL_ALIGN(sizeof(fuchsia_io_DirectoryObject));
    node_info->envelope.presence = FIDL_ALLOC_PRESENT;
  }
  
  zx_status_t SendOnOpenEvent(zx_handle_t ch, OnOpenMsg msg, zx_handle_t* handles,
                              uint32_t num_handles) {
    const bool contains_nodeinfo = msg.primary.node_info.tag != fidl_xunion_tag_t(0);
    uint32_t msg_size = contains_nodeinfo ? sizeof(msg) : sizeof(msg.primary);
    fidl::Message fidl_msg(fidl::BytePart(reinterpret_cast<uint8_t*>(&msg), msg_size, msg_size),
                           fidl::HandlePart(handles, num_handles, num_handles));
    return fidl_msg.Write(ch, 0);
  }
  
  uint64_t next_ino = 2;
  
  std::unique_ptr<Devnode> root_devnode;
  
  std::unique_ptr<Devnode> class_devnode;
  
  std::unique_ptr<Devnode> devfs_mkdir(Devnode* parent, const fbl::String& name);
  
  // Dummy node to represent dev/diagnostics directory.
  std::unique_ptr<Devnode> diagnostics_devnode;
  
  // Connection to diagnostics VFS server. Channel is owned by inspect manager.
  zx::unowned_channel diagnostics_channel;
  
  const char kDiagnosticsDirName[] = "diagnostics";
  const size_t kDiagnosticsDirLen = strlen(kDiagnosticsDirName);
  
  zx::channel g_devfs_root;
  
  }  // namespace
  
  struct Watcher : fbl::DoublyLinkedListable<std::unique_ptr<Watcher>> {
    Watcher(Devnode* dn, zx::channel ch, uint32_t mask);
  
    Watcher(const Watcher&) = delete;
    Watcher& operator=(const Watcher&) = delete;
  
    Watcher(Watcher&&) = delete;
    Watcher& operator=(Watcher&&) = delete;
  
    Devnode* devnode = nullptr;
    zx::channel handle;
    uint32_t mask = 0;
  };
  
  Watcher::Watcher(Devnode* dn, zx::channel ch, uint32_t mask)
      : devnode(dn), handle(std::move(ch)), mask(mask) {}
  
  class DcIostate : public fbl::DoublyLinkedListable<DcIostate*>,
                    public AsyncLoopOwnedRpcHandler<DcIostate> {
   public:
    explicit DcIostate(Devnode* dn);
    ~DcIostate();
  
    // Remove this DcIostate from its devnode
    void DetachFromDevnode();
  
    // Claims ownership of |*h| on success
    static zx_status_t Create(Devnode* dn, async_dispatcher_t* dispatcher, zx::channel* h);
  
    static zx_status_t DevfsFidlHandler(fidl_incoming_msg_t* msg, fidl_txn_t* txn, void* cookie,
                                        async_dispatcher_t* dispatcher);
  
    static void HandleRpc(std::unique_ptr<DcIostate> ios, async_dispatcher_t* dispatcher,
                          async::WaitBase* wait, zx_status_t status,
                          const zx_packet_signal_t* signal);
  
   private:
    uint64_t readdir_ino_ = 0;
  
    // pointer to our devnode, nullptr if it has been removed
    Devnode* devnode_ = nullptr;
  };
  
  // BUG(fxbug.dev/32713): We currently never free these after allocating them
  struct Devnode : public fbl::DoublyLinkedListable<Devnode*> {
    explicit Devnode(fbl::String name);
    ~Devnode();
  
    Devnode(const Devnode&) = delete;
    Devnode& operator=(const Devnode&) = delete;
  
    Devnode(Devnode&&) = delete;
    Devnode& operator=(Devnode&&) = delete;
  
    fbl::String name;
    uint64_t ino = 0;
  
    // nullptr if we are a pure directory node,
    // otherwise the device we are referencing
    Device* device = nullptr;
  
    fbl::DoublyLinkedList<std::unique_ptr<Watcher>> watchers;
  
    // list of our child devnodes
    fbl::DoublyLinkedList<Devnode*> children;
  
    // Pointer to our parent, for removing ourselves from its list of
    // children. Our parent must outlive us.
    Devnode* parent = nullptr;
  
    // list of attached iostates
    fbl::DoublyLinkedList<DcIostate*> iostate;
  
    // used to assign unique small device numbers
    // for class device links
    uint32_t seqcount = 0;
  };
  
  namespace {
  
  struct ProtocolInfo {
    const char* name;
    std::unique_ptr<Devnode> devnode;
    uint32_t id;
    uint32_t flags;
  };
  
  ProtocolInfo proto_infos[] = {
  #define DDK_PROTOCOL_DEF(tag, val, name, flags) {name, nullptr, val, flags},
  #include <ddk/protodefs.h>
  };
  
  Devnode* proto_dir(uint32_t id) {
    for (const auto& info : proto_infos) {
      if (info.id == id) {
        return info.devnode.get();
      }
    }
    return nullptr;
  }
  
  void prepopulate_protocol_dirs() {
    class_devnode = devfs_mkdir(root_devnode.get(), "class");
    for (auto& info : proto_infos) {
      if (!(info.flags & PF_NOPUB)) {
        info.devnode = devfs_mkdir(class_devnode.get(), info.name);
      }
    }
  }
  
  void describe_error(zx::channel h, zx_status_t status) {
    OnOpenMsg msg;
    memset(&msg, 0, sizeof(msg));
    fidl_init_txn_header(&msg.primary.hdr, 0, fuchsia_io_NodeOnOpenOrdinal);
    msg.primary.s = status;
    SendOnOpenEvent(h.get(), msg, nullptr, 0);
  }
  
  // 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
  bool devnode_is_dir(const Devnode* dn) {
    if (dn->children.is_empty()) {
      return (dn->device == nullptr) || (!dn->device->device_controller().channel().is_valid()) ||
             (!dn->device->channel()->is_valid());
    }
    return true;
  }
  
  // Local devnodes are ones that we should not hand off OPEN
  // RPCs to the underlying driver_host
  bool devnode_is_local(Devnode* dn) {
    if (dn->device == nullptr) {
      return true;
    }
    if (!dn->device->device_controller().channel().get()) {
      return true;
    }
    if (dn->device->flags & DEV_CTX_MUST_ISOLATE) {
      return true;
    }
    return false;
  }
  
  // Notify a single watcher about the given operation and path.  On failure,
  // frees the watcher.  This can only be called on a watcher that has not yet
  // been added to a Devnode's watchers list.
  void devfs_notify_single(std::unique_ptr<Watcher>* watcher, const fbl::String& name, unsigned op) {
    size_t len = name.length();
    if (!*watcher || len > fuchsia_io_MAX_FILENAME) {
      return;
    }
  
    ZX_ASSERT(!(*watcher)->InContainer());
  
    uint8_t msg[fuchsia_io_MAX_FILENAME + 2];
    const uint32_t msg_len = static_cast<uint32_t>(len + 2);
    msg[0] = static_cast<uint8_t>(op);
    msg[1] = static_cast<uint8_t>(len);
    memcpy(msg + 2, name.c_str(), len);
  
    // convert to mask
    op = (1u << op);
  
    if (!((*watcher)->mask & op)) {
      return;
    }
  
    if ((*watcher)->handle.write(0, msg, msg_len, nullptr, 0) != ZX_OK) {
      watcher->reset();
    }
  }
  
  void devfs_notify(Devnode* dn, const fbl::String& name, unsigned op) {
    if (dn->watchers.is_empty()) {
      return;
    }
  
    size_t len = name.length();
    if (len > fuchsia_io_MAX_FILENAME) {
      return;
    }
  
    uint8_t msg[fuchsia_io_MAX_FILENAME + 2];
    const uint32_t msg_len = static_cast<uint32_t>(len + 2);
    msg[0] = static_cast<uint8_t>(op);
    msg[1] = static_cast<uint8_t>(len);
    memcpy(msg + 2, name.c_str(), len);
  
    // convert to mask
    op = (1u << op);
  
    for (auto itr = dn->watchers.begin(); itr != dn->watchers.end();) {
      auto& cur = *itr;
      // Advance the iterator now instead of at the end of the loop because we
      // may erase the current element from the list.
      ++itr;
  
      if (!(cur.mask & op)) {
        continue;
      }
  
      if (cur.handle.write(0, msg, msg_len, nullptr, 0) != ZX_OK) {
        dn->watchers.erase(cur);
        // The Watcher is free'd here
      }
    }
  }
  
  }  // namespace
  
  zx_status_t devfs_watch(Devnode* dn, zx::channel h, uint32_t mask) {
    auto watcher = std::make_unique<Watcher>(dn, std::move(h), mask);
    if (watcher == nullptr) {
      return ZX_ERR_NO_MEMORY;
    }
  
    // If the watcher has asked for all existing entries, send it all of them
    // followed by the end-of-existing marker (IDLE).
    if (mask & fuchsia_io_WATCH_MASK_EXISTING) {
      for (const auto& child : dn->children) {
        if (child.device && (child.device->flags & DEV_CTX_INVISIBLE)) {
          continue;
        }
        // TODO: send multiple per write
        devfs_notify_single(&watcher, child.name, fuchsia_io_WATCH_EVENT_EXISTING);
      }
      devfs_notify_single(&watcher, "", fuchsia_io_WATCH_EVENT_IDLE);
    }
  
    // Watcher may have been freed by devfs_notify_single, so check before
    // adding.
    if (watcher) {
      dn->watchers.push_front(std::move(watcher));
    }
    return ZX_OK;
  }
  
  namespace {
  
  std::unique_ptr<Devnode> devfs_mknode(const fbl::RefPtr<Device>& dev, const fbl::String& name) {
    auto dn = std::make_unique<Devnode>(name);
    if (!dn) {
      return nullptr;
    }
    dn->ino = next_ino++;
    // TODO(teisenbe): This should probably be refcounted
    dn->device = dev.get();
    return dn;
  }
  
  std::unique_ptr<Devnode> devfs_mkdir(Devnode* parent, const fbl::String& name) {
    std::unique_ptr<Devnode> dn = devfs_mknode(nullptr, name);
    if (dn == nullptr) {
      return nullptr;
    }
    dn->parent = parent;
    parent->children.push_back(dn.get());
    return dn;
  }
  
  Devnode* devfs_lookup(Devnode* parent, const fbl::String& name) {
    for (auto& child : parent->children) {
      if (name == child.name) {
        return &child;
      }
    }
    return nullptr;
  }
  
  zx_status_t fill_dirent(vdirent_t* de, size_t delen, uint64_t ino, const fbl::String& name,
                          uint8_t type) {
    size_t len = name.length();
    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.c_str(), len);
    return static_cast<zx_status_t>(sz);
  }
  
  zx_status_t devfs_readdir(Devnode* dn, uint64_t* ino_inout, void* data, size_t len) {
    char* ptr = static_cast<char*>(data);
    uint64_t ino = *ino_inout;
  
    for (const auto& child : dn->children) {
      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.
        // An exception being /dev/diagnostics which is served by different VFS and
        // should be listed even though it has no DevNode childrens.
        if (child.children.is_empty() && child.ino != diagnostics_devnode->ino) {
          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);
      zx_status_t r = fill_dirent(vdirent, len, ino, child.name, VTYPE_TO_DTYPE(V_TYPE_DIR));
      if (r < 0) {
        break;
      }
      ptr += r;
      len -= r;
    }
  
    *ino_inout = ino;
    return static_cast<zx_status_t>(ptr - static_cast<char*>(data));
  }
  
  zx_status_t devfs_walk(Devnode** dn_inout, char* path) {
    Devnode* dn = *dn_inout;
  
  again:
    if ((path == nullptr) || (path[0] == 0)) {
      *dn_inout = dn;
      return ZX_OK;
    }
    char* name = path;
    if ((path = strchr(path, '/')) != nullptr) {
      *path++ = 0;
    }
    if (name[0] == 0) {
      return ZX_ERR_BAD_PATH;
    }
    for (auto& child : dn->children) {
      if (!strcmp(child.name.c_str(), name)) {
        if (child.device && (child.device->flags & DEV_CTX_INVISIBLE)) {
          continue;
        }
        dn = &child;
        goto again;
      }
    }
    // The path only partially matched.
    return ZX_ERR_NOT_FOUND;
  }
  
  void devfs_open(Devnode* dirdn, async_dispatcher_t* dispatcher, zx_handle_t h, char* path,
                  uint32_t flags) {
    zx::channel ipc(h);
    h = ZX_HANDLE_INVALID;
  
    // Filter requests for diagnostics path and pass it on to diagnostics vfs server.
    if (!strncmp(path, kDiagnosticsDirName, kDiagnosticsDirLen) &&
        (path[kDiagnosticsDirLen] == '\0' || path[kDiagnosticsDirLen] == '/')) {
      char* dir_path = path + kDiagnosticsDirLen;
      char current_dir[] = ".";
      if (dir_path[0] == '/') {
        dir_path++;
      } else {
        dir_path = current_dir;
      }
      fuchsia_io_DirectoryOpen(diagnostics_channel->get(), flags, 0, dir_path, strlen(dir_path),
                               ipc.release());
      return;
    }
  
    if (!strcmp(path, ".")) {
      path = nullptr;
    }
  
    Devnode* dn = dirdn;
    zx_status_t r = devfs_walk(&dn, path);
  
    bool describe = flags & ZX_FS_FLAG_DESCRIBE;
    if (r != ZX_OK) {
      if (describe) {
        describe_error(std::move(ipc), r);
      }
      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 (devnode_is_local(dn) || flags & (ZX_FS_FLAG_NOREMOTE | ZX_FS_FLAG_DIRECTORY)) {
      zx::unowned_channel unowned_ipc(ipc);
      if ((r = DcIostate::Create(dn, dispatcher, &ipc)) != ZX_OK) {
        if (describe) {
          describe_error(std::move(ipc), r);
        }
        return;
      }
      if (describe) {
        OnOpenMsg msg;
        memset(&msg, 0, sizeof(msg));
        fidl_init_txn_header(&msg.primary.hdr, 0, fuchsia_io_NodeOnOpenOrdinal);
  
        msg.primary.s = ZX_OK;
  
        SetNodeInfoAsDirectory(&msg.primary.node_info);
  
        // We don't need to set the union member (i.e. the directory)'s data here,
        // because Directory is an empty struct and has no data. The empty struct
        // is zeroed out by the memset() earlier in this function.
  
        // Writing to unowned_ipc is safe because this is executing on the same
        // thread as the DcAsyncLoop(), so the handle can't be closed underneath us.
        SendOnOpenEvent(unowned_ipc->get(), msg, nullptr, 0);
      }
      return;
    }
  
    // Otherwise we will pass the request on to the remote.
    fuchsia_io_DirectoryOpen(dn->device->device_controller().channel().get(), flags, 0, ".", 1,
                             ipc.release());
  }
  
  void devfs_remove(Devnode* dn) {
    if (dn->InContainer()) {
      dn->parent->children.erase(*dn);
    }
  
    // detach all connected iostates
    while (!dn->iostate.is_empty()) {
      dn->iostate.front().DetachFromDevnode();
    }
  
    // notify own file watcher
    if ((dn->device == nullptr) || !(dn->device->flags & DEV_CTX_INVISIBLE)) {
      devfs_notify(dn, "", fuchsia_io_WATCH_EVENT_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, fuchsia_io_WATCH_EVENT_REMOVED);
        }
      }
      if (dn->device->link == dn) {
        dn->device->link = nullptr;
  
        if (!(dn->device->flags & DEV_CTX_INVISIBLE)) {
          Devnode* dir = proto_dir(dn->device->protocol_id());
          devfs_notify(dir, dn->name, fuchsia_io_WATCH_EVENT_REMOVED);
        }
      }
      dn->device = nullptr;
    }
  
    // destroy all watchers
    dn->watchers.clear();
  
    // detach children
    // They will be unpublished when the devices they're associated with are
    // eventually destroyed.
    dn->children.clear();
  }
  
  }  // namespace
  
  Devnode::Devnode(fbl::String name) : name(std::move(name)) {}
  
  Devnode::~Devnode() { devfs_remove(this); }
  
  DcIostate::DcIostate(Devnode* dn) : devnode_(dn) { devnode_->iostate.push_back(this); }
  
  DcIostate::~DcIostate() { DetachFromDevnode(); }
  
  void DcIostate::DetachFromDevnode() {
    if (devnode_ != nullptr) {
      devnode_->iostate.erase(*this);
      devnode_ = nullptr;
    }
    set_channel(zx::channel());
  }
  
  zx_status_t DcIostate::Create(Devnode* dn, async_dispatcher_t* dispatcher, zx::channel* ipc) {
    auto ios = std::make_unique<DcIostate>(dn);
    if (ios == nullptr) {
      return ZX_ERR_NO_MEMORY;
    }
  
    ios->set_channel(std::move(*ipc));
    zx_status_t status = DcIostate::BeginWait(&ios, dispatcher);
    if (status != ZX_OK) {
      // Take the handle back from |ios| so it doesn't close it when it's
      // destroyed
      *ipc = ios->set_channel(zx::channel());
    }
    return status;
  }
  
  void devfs_advertise(const fbl::RefPtr<Device>& dev) {
    if (dev->link) {
      Devnode* dir = proto_dir(dev->protocol_id());
      devfs_notify(dir, dev->link->name, fuchsia_io_WATCH_EVENT_ADDED);
    }
    if (dev->self->parent) {
      devfs_notify(dev->self->parent, dev->self->name, fuchsia_io_WATCH_EVENT_ADDED);
    }
  }
  
  // TODO: generate a MODIFIED event rather than back to back REMOVED and ADDED
  void devfs_advertise_modified(const fbl::RefPtr<Device>& dev) {
    if (dev->link) {
      Devnode* dir = proto_dir(dev->protocol_id());
      devfs_notify(dir, dev->link->name, fuchsia_io_WATCH_EVENT_REMOVED);
      devfs_notify(dir, dev->link->name, fuchsia_io_WATCH_EVENT_ADDED);
    }
    if (dev->self->parent) {
      devfs_notify(dev->self->parent, dev->self->name, fuchsia_io_WATCH_EVENT_REMOVED);
      devfs_notify(dev->self->parent, dev->self->name, fuchsia_io_WATCH_EVENT_ADDED);
    }
  }
  
  zx_status_t devfs_publish(const fbl::RefPtr<Device>& parent, const fbl::RefPtr<Device>& dev) {
    if ((parent->self == nullptr) || (dev->self != nullptr) || (dev->link != nullptr)) {
      return ZX_ERR_INTERNAL;
    }
  
    std::unique_ptr<Devnode> dnself = devfs_mknode(dev, dev->name());
    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* dir;
    dir = proto_dir(dev->protocol_id());
    if (dir != nullptr) {
      char tmp[32];
      const char* name = dev->name().data();
  
      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;
            goto got_name;
          }
        }
        return ZX_ERR_ALREADY_EXISTS;
      }
  
    got_name:
      std::unique_ptr<Devnode> dnlink = devfs_mknode(dev, name);
      if (dnlink == nullptr) {
        return ZX_ERR_NO_MEMORY;
      }
  
      // add link node to class directory
      dnlink->parent = dir;
      dir->children.push_back(dnlink.get());
      dev->link = dnlink.release();
    }
  
  done:
    // add self node to parent directory
    dnself->parent = parent->self;
    parent->self->children.push_back(dnself.get());
    dev->self = dnself.release();
  
    if (!(dev->flags & DEV_CTX_INVISIBLE)) {
      devfs_advertise(dev);
    }
    return ZX_OK;
  }
  
  // TODO(teisenbe): Ideally this would take a RefPtr, but currently this is
  // invoked in the dtor for Device.
  void devfs_unpublish(Device* dev) {
    if (dev->self != nullptr) {
      delete dev->self;
      dev->self = nullptr;
    }
    if (dev->link != nullptr) {
      delete dev->link;
      dev->link = nullptr;
    }
  }
  
  zx_status_t devfs_connect(const Device* dev, zx::channel client_remote) {
    if (!client_remote.is_valid()) {
      return ZX_ERR_BAD_HANDLE;
    }
    fuchsia_io_DirectoryOpen(dev->device_controller().channel().get(), 0 /* flags */, 0 /* mode */,
                             ".", 1, client_remote.release());
    return ZX_OK;
  }
  
  void devfs_connect_diagnostics(zx::unowned_channel h) { diagnostics_channel = h; }
  
  // Helper macros for |DevfsFidlHandler| 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;
  
  zx_status_t DcIostate::DevfsFidlHandler(fidl_incoming_msg_t* msg, fidl_txn_t* txn, void* cookie,
                                          async_dispatcher_t* dispatcher) {
    auto ios = static_cast<DcIostate*>(cookie);
    Devnode* 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;
    uint64_t ordinal = hdr->ordinal;
    switch (ordinal) {
      case fuchsia_io_NodeCloneOrdinal: {
        DECODE_REQUEST(msg, NodeClone);
        DEFINE_REQUEST(msg, NodeClone);
        zx_handle_t h = request->object;
        uint32_t flags = request->flags;
        if (request->flags & ZX_FS_FLAG_CLONE_SAME_RIGHTS) {
          flags |= ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE;
        }
        char path[] = ".";
        devfs_open(dn, dispatcher, h, path, flags | ZX_FS_FLAG_NOREMOTE);
        return ZX_OK;
      }
      case fuchsia_io_NodeDescribeOrdinal: {
        DECODE_REQUEST(msg, NodeDescribe);
  
        DescribeMsg msg;
        memset(&msg, 0, sizeof(msg));
        fidl_init_txn_header(&msg.primary.hdr, 0, fuchsia_io_NodeDescribeOrdinal);
        SetNodeInfoAsDirectory(&msg.primary.node_info);
  
        fidl_outgoing_msg_t raw_msg = {
            .bytes = reinterpret_cast<uint8_t*>(&msg),
            .handles = nullptr,
            .num_bytes = sizeof(msg),
            .num_handles = 0,
        };
        return txn->reply(txn, &raw_msg);
      }
      case fuchsia_io_DirectoryOpenOrdinal: {
        DECODE_REQUEST(msg, DirectoryOpen);
        DEFINE_REQUEST(msg, DirectoryOpen);
        uint32_t len = static_cast<uint32_t>(request->path.size);
        zx_handle_t h = request->object;
        uint32_t flags = request->flags;
        if (len == 0 || len > fuchsia_io_MAX_PATH) {
          zx_handle_close(h);
        } else {
          char path[fuchsia_io_MAX_PATH + 1];
          memcpy(path, request->path.data, len);
          path[len] = 0;
          devfs_open(dn, dispatcher, h, path, flags);
        }
        return ZX_OK;
      }
      case fuchsia_io_NodeGetAttrOrdinal: {
        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 fuchsia_io_DirectoryRewindOrdinal: {
        DECODE_REQUEST(msg, DirectoryRewind);
        ios->readdir_ino_ = 0;
        return fuchsia_io_DirectoryRewind_reply(txn, ZX_OK);
      }
      case fuchsia_io_DirectoryReadDirentsOrdinal: {
        DECODE_REQUEST(msg, DirectoryReadDirents);
        DEFINE_REQUEST(msg, DirectoryReadDirents);
  
        if (request->max_bytes > fuchsia_io_MAX_BUF) {
          return fuchsia_io_DirectoryReadDirents_reply(txn, ZX_ERR_INVALID_ARGS, nullptr, 0);
        }
  
        uint8_t data[fuchsia_io_MAX_BUF];
        size_t actual = 0;
        r = devfs_readdir(dn, &ios->readdir_ino_, data, request->max_bytes);
        if (r >= 0) {
          actual = r;
          r = ZX_OK;
        }
        return fuchsia_io_DirectoryReadDirents_reply(txn, r, data, actual);
      }
      case fuchsia_io_DirectoryWatchOrdinal: {
        DECODE_REQUEST(msg, DirectoryWatch);
        DEFINE_REQUEST(msg, DirectoryWatch);
        zx::channel watcher(request->watcher);
  
        request->watcher = ZX_HANDLE_INVALID;
        if (request->mask & (~fuchsia_io_WATCH_MASK_ALL) || request->options != 0) {
          return fuchsia_io_DirectoryWatch_reply(txn, ZX_ERR_INVALID_ARGS);
        }
        r = devfs_watch(dn, std::move(watcher), request->mask);
        return fuchsia_io_DirectoryWatch_reply(txn, r);
      }
      case fuchsia_io_DirectoryAdminQueryFilesystemOrdinal: {
        DECODE_REQUEST(msg, DirectoryAdminQueryFilesystem);
        fuchsia_io_FilesystemInfo info;
        memset(&info, 0, sizeof(info));
        strlcpy((char*)info.name, "devfs", fuchsia_io_MAX_FS_NAME_BUFFER);
        return fuchsia_io_DirectoryAdminQueryFilesystem_reply(txn, ZX_OK, &info);
      }
    }  // switch
  
    // close inbound handles so they do not leak
    zx_handle_close_many(msg->handles, msg->num_handles);
    return ZX_ERR_NOT_SUPPORTED;
  }
  
  void DcIostate::HandleRpc(std::unique_ptr<DcIostate> ios, async_dispatcher_t* dispatcher,
                            async::WaitBase* wait, zx_status_t status,
                            const zx_packet_signal_t* signal) {
    if (status != ZX_OK) {
      LOGF(ERROR, "Failed to wait for RPC: %s", zx_status_get_string(status));
      return;
    }
  
    if (signal->observed & ZX_CHANNEL_READABLE) {
      status = fs::ReadMessage(
          wait->object(), [&ios, dispatcher](fidl_incoming_msg_t* msg, fs::FidlConnection* txn) {
            return DcIostate::DevfsFidlHandler(msg, txn->Txn(), ios.get(), dispatcher);
          });
      if (status == ZX_OK) {
        ios->BeginWait(std::move(ios), dispatcher);
        return;
      }
    } else if (signal->observed & ZX_CHANNEL_PEER_CLOSED) {
      fs::CloseMessage([&ios, dispatcher](fidl_incoming_msg_t* msg, fs::FidlConnection* txn) {
        return DcIostate::DevfsFidlHandler(msg, txn->Txn(), ios.get(), dispatcher);
      });
    } else {
      LOGF(FATAL, "Unexpected signal state %#08x", signal->observed);
    }
    // Do not start waiting again, and destroy |ios|
  }
  
  zx::unowned_channel devfs_root_borrow() { return zx::unowned_channel(g_devfs_root); }
  
  zx::channel devfs_root_clone() { return zx::channel(fdio_service_clone(g_devfs_root.get())); }
  
  void devfs_init(const fbl::RefPtr<Device>& device, async_dispatcher_t* dispatcher) {
    root_devnode = std::make_unique<Devnode>("");
    if (!root_devnode) {
      return;
    }
    root_devnode->ino = 1;
  
    prepopulate_protocol_dirs();
  
    // Create dummy diagnostics devnode, so that the directory is listed.
    diagnostics_devnode = devfs_mkdir(root_devnode.get(), "diagnostics");
  
    // TODO(teisenbe): Should this take a reference?
    root_devnode->device = device.get();
    root_devnode->device->self = root_devnode.get();
  
    zx::channel h0, h1;
    if (zx::channel::create(0, &h0, &h1) != ZX_OK) {
      return;
    } else if (DcIostate::Create(root_devnode.get(), dispatcher, &h0) != ZX_OK) {
      return;
    }
  
    g_devfs_root = std::move(h1);
    // This is actually owned by |device| and will be freed in unpublish
    __UNUSED auto ptr = root_devnode.release();
  }
  
  zx_status_t devfs_walk(Devnode* dn, const char* path, fbl::RefPtr<Device>* dev) {
    Devnode* inout = dn;
  
    char path_copy[PATH_MAX];
    if (strlen(path) + 1 > sizeof(path_copy)) {
      return ZX_ERR_BUFFER_TOO_SMALL;
    }
    strcpy(path_copy, path);
  
    zx_status_t status = devfs_walk(&inout, path_copy);
    if (status != ZX_OK) {
      return status;
    }
    *dev = fbl::RefPtr(inout->device);
    return ZX_OK;
  }
  