[devcoordinator] Encapsulate Device::children

This no longer exposes the underlying list outside of the child class

Change-Id: Ib70437cc84a9649f43fa0abc3c93caff73aab08c
diff --git a/zircon/system/core/devmgr/devcoordinator/coordinator.cpp b/zircon/system/core/devmgr/devcoordinator/coordinator.cpp
index 5c79032..9072e29 100644
--- a/zircon/system/core/devmgr/devcoordinator/coordinator.cpp
+++ b/zircon/system/core/devmgr/devcoordinator/coordinator.cpp
@@ -294,7 +294,7 @@
         indent++;
         DumpDevice(vmo, dev->proxy.get(), indent);
     }
-    for (const auto& child : dev->children) {
+    for (const auto& child : dev->children()) {
         DumpDevice(vmo, &child, indent + 1);
     }
 }
@@ -345,7 +345,7 @@
     if (dev->proxy) {
         DumpDeviceProps(vmo, dev->proxy.get());
     }
-    for (const auto& child : dev->children) {
+    for (const auto& child : dev->children()) {
         DumpDeviceProps(vmo, &child);
     }
 }
@@ -765,12 +765,9 @@
     // if we have a parent, disconnect and downref it
     fbl::RefPtr<Device> parent = dev->parent();
     if (parent != nullptr) {
-        dev->set_parent(nullptr);
-        if (dev->flags & DEV_CTX_PROXY) {
-            parent->proxy = nullptr;
-        } else {
-            parent->children.erase(*dev);
-            if (parent->children.is_empty()) {
+        dev->DetachFromParent();
+        if (!(dev->flags & DEV_CTX_PROXY)) {
+            if (parent->children().is_empty()) {
                 parent->flags &= (~DEV_CTX_BOUND);
 
                 // TODO: This code is to cause the bind process to
diff --git a/zircon/system/core/devmgr/devcoordinator/device.cpp b/zircon/system/core/devmgr/devcoordinator/device.cpp
index 028b743..845094a 100644
--- a/zircon/system/core/devmgr/devcoordinator/device.cpp
+++ b/zircon/system/core/devmgr/devcoordinator/device.cpp
@@ -104,7 +104,7 @@
         // TODO host == nullptr should be impossible
         dev->host_->devices().push_back(dev.get());
     }
-    real_parent->children.push_back(dev.get());
+    real_parent->children_.push_back(dev.get());
     log(DEVLC, "devcoord: dev %p name='%s' (child)\n", real_parent.get(), real_parent->name.data());
 
     *device = std::move(dev);
@@ -186,6 +186,15 @@
     return ZX_OK;
 }
 
+void Device::DetachFromParent() {
+    if (this->flags & DEV_CTX_PROXY) {
+        parent_->proxy = nullptr;
+    } else {
+        parent_->children_.erase(*this);
+    }
+    parent_ = nullptr;
+}
+
 zx_status_t Device::SignalReadyForBind(zx::duration delay) {
     return publish_task_.PostDelayed(this->coordinator->dispatcher(), delay);
 }
diff --git a/zircon/system/core/devmgr/devcoordinator/device.h b/zircon/system/core/devmgr/devcoordinator/device.h
index 6c6007d..800eab25 100644
--- a/zircon/system/core/devmgr/devcoordinator/device.h
+++ b/zircon/system/core/devmgr/devcoordinator/device.h
@@ -77,6 +77,71 @@
                           async::WaitBase* wait, zx_status_t status,
                           const zx_packet_signal_t* signal);
 
+    // Node for entry in device child list
+    struct Node {
+        static fbl::DoublyLinkedListNodeState<Device*>& node_state(Device& obj) {
+            return obj.node_;
+        }
+    };
+
+    // This iterator provides access to a list of devices that does not provide
+    // mechanisms for mutating that list.  With this, a user can get mutable
+    // access to a device in the list.  This is achieved by making the linked
+    // list iterator opaque. It is not safe to modify the underlying list while
+    // this iterator is in use.
+    template <typename IterType, typename ValueType>
+    class ChildListIterator {
+    public:
+        explicit ChildListIterator(IterType itr) : itr_(std::move(itr)) {}
+
+        ChildListIterator& operator++() { ++itr_; return *this; }
+        ChildListIterator operator++(int) {
+            auto other = *this;
+            ++*this;
+            return other;
+        }
+        ValueType& operator*() const { return *itr_; }
+        bool operator==(const ChildListIterator& other) const { return itr_ == other.itr_; }
+        bool operator!=(const ChildListIterator& other) const { return itr_ != other.itr_; }
+    private:
+        IterType itr_;
+    };
+
+    // This class exists to allow consumers of the Device class to write
+    //   for (auto& child : dev->children())
+    // and get mutable access to the children without getting mutable access to
+    // the list.
+    template <typename ListType, typename IterType>
+    class ChildListIteratorFactory {
+    public:
+        explicit ChildListIteratorFactory(ListType& list) : list_(list) {}
+
+        IterType begin() const { return IterType(list_.begin()); }
+        IterType end() const { return IterType(list_.end()); }
+
+        bool is_empty() const { return begin() == end(); }
+    private:
+        ListType& list_;
+    };
+
+    using NonConstChildListIterator =
+            ChildListIterator<fbl::DoublyLinkedList<Device*, Node>::iterator, Device>;
+    using ConstChildListIterator =
+            ChildListIterator<fbl::DoublyLinkedList<Device*, Node>::const_iterator, const Device>;
+    using NonConstChildListIteratorFactory = ChildListIteratorFactory<
+            fbl::DoublyLinkedList<Device*, Node>, NonConstChildListIterator>;
+    using ConstChildListIteratorFactory = ChildListIteratorFactory<
+            const fbl::DoublyLinkedList<Device*, Node>, ConstChildListIterator>;
+    // We do not want to expose the list itself for mutation, even if the
+    // children are allowed to be mutated.  We manage this by making the
+    // iterator opaque.
+    NonConstChildListIteratorFactory children() {
+        return NonConstChildListIteratorFactory(children_);
+    }
+    ConstChildListIteratorFactory children() const {
+        return ConstChildListIteratorFactory(children_);
+    }
+
     // Signal that this device is ready for bind to happen.  This should happen
     // either immediately after the device is created, if it's created visible,
     // or after it becomes visible.
@@ -110,13 +175,6 @@
     // or once the device becomes visible.
     zx::channel client_remote;
 
-    // listnode for this device in its parent's
-    // list-of-children
-    fbl::DoublyLinkedListNodeState<Device*> node;
-    struct Node {
-        static fbl::DoublyLinkedListNodeState<Device*>& node_state(Device& obj) { return obj.node; }
-    };
-
     // listnode for this device in its devhost's
     // list-of-devices
     fbl::DoublyLinkedListNodeState<Device*> dhnode;
@@ -126,9 +184,6 @@
         }
     };
 
-    // list of all child devices of this device
-    fbl::DoublyLinkedList<Device*, Node> children;
-
     // listnode for this device in the all devices list
     fbl::DoublyLinkedListNodeState<Device*> anode;
     struct AllDevicesNode {
@@ -137,6 +192,9 @@
         }
     };
 
+    // Break the relationship between this device object and its parent
+    void DetachFromParent();
+
     // Metadata entries associated to this device.
     fbl::DoublyLinkedList<fbl::unique_ptr<Metadata>, Metadata::Node> metadata;
 
@@ -198,7 +256,7 @@
         kSuspended,
     };
 
-    State state() { return state_; }
+    State state() const { return state_; }
 
 private:
     zx_status_t HandleRead();
@@ -212,6 +270,12 @@
 
     async::TaskClosure publish_task_;
 
+    // listnode for this device in its parent's list-of-children
+    fbl::DoublyLinkedListNodeState<Device*> node_;
+
+    // list of all child devices of this device
+    fbl::DoublyLinkedList<Device*, Node> children_;
+
     // - If this device is part of a composite device, this is inhabited by
     //   CompositeDeviceComponent* and it points to the component that matched it.
     //   Note that this is only set on the device that matched the component, not
diff --git a/zircon/system/core/devmgr/devcoordinator/suspend-task.cpp b/zircon/system/core/devmgr/devcoordinator/suspend-task.cpp
index 2e91131..6035380 100644
--- a/zircon/system/core/devmgr/devcoordinator/suspend-task.cpp
+++ b/zircon/system/core/devmgr/devcoordinator/suspend-task.cpp
@@ -21,7 +21,7 @@
 
 void SuspendTask::Run() {
     bool found_more_dependencies = false;
-    for (auto& child : device_->children) {
+    for (auto& child : device_->children()) {
         // Use a switch statement here so that this gets reconsidered if we add
         // more states.
         switch (child.state()) {