| // Copyright 2021 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. |
| |
| use std::collections::btree_map::Entry; |
| use std::collections::BTreeMap; |
| use std::fmt; |
| use std::sync::{Arc, Weak}; |
| |
| use crate::fs::socket::*; |
| use crate::fs::*; |
| use crate::lock::{RwLock, RwLockWriteGuard}; |
| use crate::types::*; |
| |
| struct DirEntryState { |
| /// The parent DirEntry. |
| /// |
| /// The DirEntry tree has strong references from child-to-parent and weak |
| /// references from parent-to-child. This design ensures that the parent |
| /// chain is always populated in the cache, but some children might be |
| /// missing from the cache. |
| parent: Option<DirEntryHandle>, |
| |
| /// The name that this parent calls this child. |
| /// |
| /// This name might not be reflected in the full path in the namespace that |
| /// contains this DirEntry. For example, this DirEntry might be the root of |
| /// a chroot. |
| /// |
| /// Most callers that want to work with names for DirEntries should use the |
| /// NamespaceNodes. |
| local_name: FsString, |
| |
| /// A partial cache of the children of this DirEntry. |
| /// |
| /// DirEntries are added to this cache when they are looked up and removed |
| /// when they are no longer referenced. |
| children: BTreeMap<FsString, Weak<DirEntry>>, |
| |
| /// The number of filesystem mounted on the directory entry. |
| mount_count: u32, |
| } |
| |
| /// An entry in a directory. |
| /// |
| /// This structure assigns a name to an FsNode in a given file system. An |
| /// FsNode might have multiple directory entries, for example if there are more |
| /// than one hard link to the same FsNode. In those cases, each hard link will |
| /// have a different parent and a different local_name because each hard link |
| /// has its own DirEntry object. |
| /// |
| /// A directory cannot have more than one hard link, which means there is a |
| /// single DirEntry for each Directory FsNode. That invariant lets us store the |
| /// children for a directory in the DirEntry rather than in the FsNode. |
| pub struct DirEntry { |
| /// The FsNode referenced by this DirEntry. |
| /// |
| /// A given FsNode can be referenced by multiple DirEntry objects, for |
| /// example if there are multiple hard links to a given FsNode. |
| pub node: FsNodeHandle, |
| |
| /// The mutable state for this DirEntry. |
| state: RwLock<DirEntryState>, |
| } |
| |
| pub type DirEntryHandle = Arc<DirEntry>; |
| |
| impl DirEntry { |
| pub fn new( |
| node: FsNodeHandle, |
| parent: Option<DirEntryHandle>, |
| local_name: FsString, |
| ) -> DirEntryHandle { |
| Arc::new(DirEntry { |
| node, |
| state: RwLock::new(DirEntryState { |
| parent, |
| local_name, |
| children: BTreeMap::new(), |
| mount_count: 0, |
| }), |
| }) |
| } |
| |
| /// Register that a filesystem is mounted on the directory. |
| pub fn register_mount(&self) { |
| self.state.write().mount_count += 1; |
| } |
| |
| /// Unregister that a filesystem is mounted on the directory. |
| pub fn unregister_mount(&self) { |
| let mut state = self.state.write(); |
| assert!(state.mount_count > 0); |
| state.mount_count -= 1; |
| } |
| |
| /// The name that this node's parent calls this node. |
| /// |
| /// If this node is mounted in a namespace, the parent of this node in that |
| /// namespace might have a different name for the point in the namespace at |
| /// which this node is mounted. |
| pub fn local_name(&self) -> FsString { |
| self.state.read().local_name.clone() |
| } |
| |
| /// The parent DirEntry object. |
| /// |
| /// Returns None if this DirEntry is the root of its file system. |
| /// |
| /// Be aware that the root of one file system might be mounted as a child |
| /// in another file system. For that reason, consider walking the |
| /// NamespaceNode tree (which understands mounts) rather than the DirEntry |
| /// tree. |
| pub fn parent(&self) -> Option<DirEntryHandle> { |
| self.state.read().parent.clone() |
| } |
| |
| /// The parent DirEntry object or this DirEntry if this entry is the root. |
| /// |
| /// Useful when traversing up the tree if you always want to find a parent |
| /// (e.g., for ".."). |
| /// |
| /// Be aware that the root of one file system might be mounted as a child |
| /// in another file system. For that reason, consider walking the |
| /// NamespaceNode tree (which understands mounts) rather than the DirEntry |
| /// tree. |
| pub fn parent_or_self(self: &DirEntryHandle) -> DirEntryHandle { |
| self.state.read().parent.as_ref().unwrap_or(self).clone() |
| } |
| |
| /// Whether the given name has special semantics as a directory entry. |
| /// |
| /// Specifically, whether the name is empty (which means "self"), dot |
| /// (which also means "self"), or dot dot (which means "parent"). |
| pub fn is_reserved_name(name: &FsStr) -> bool { |
| name.is_empty() || name == b"." || name == b".." |
| } |
| |
| /// Look up a directory entry with the given name as direct child of this |
| /// entry. |
| pub fn component_lookup(self: &DirEntryHandle, name: &FsStr) -> Result<DirEntryHandle, Errno> { |
| let (node, _) = self.get_or_create_child(name, || { |
| if !self.node.info().mode.contains(FileMode::IWUSR) { |
| return error!(EACCES); |
| } |
| self.node.lookup(name) |
| })?; |
| Ok(node) |
| } |
| |
| /// Creates a new DirEntry |
| /// |
| /// The create_node_fn function is called to create the underlying FsNode |
| /// for the DirEntry. |
| /// |
| /// If the entry already exists, create_node_fn is not called, and EEXIST is |
| /// returned. |
| fn create_entry<F>( |
| self: &DirEntryHandle, |
| name: &FsStr, |
| mode: FileMode, |
| dev: DeviceType, |
| create_node_fn: F, |
| ) -> Result<DirEntryHandle, Errno> |
| where |
| F: FnOnce() -> Result<FsNodeHandle, Errno>, |
| { |
| assert!(mode & FileMode::IFMT != FileMode::EMPTY, "mknod called without node type."); |
| if DirEntry::is_reserved_name(name) { |
| return error!(EEXIST); |
| } |
| // TODO: Do we need to check name for embedded "/" or NUL characters? |
| if name.len() > NAME_MAX as usize { |
| return error!(ENAMETOOLONG); |
| } |
| let (entry, exists) = self.get_or_create_child(name, || { |
| let node = create_node_fn()?; |
| let mut info = node.info_write(); |
| info.mode = mode; |
| if mode.is_blk() || mode.is_chr() { |
| info.rdev = dev; |
| } |
| std::mem::drop(info); |
| Ok(node) |
| })?; |
| if exists { |
| return error!(EEXIST); |
| } |
| self.node.touch(); |
| entry.node.fs().did_create_dir_entry(&entry); |
| Ok(entry) |
| } |
| |
| #[cfg(test)] |
| pub fn create_dir(self: &DirEntryHandle, name: &FsStr) -> Result<DirEntryHandle, Errno> { |
| // TODO: apply_umask |
| self.create_entry(name, FileMode::IFDIR | FileMode::ALLOW_ALL, DeviceType::NONE, || { |
| self.node.mkdir(name) |
| }) |
| } |
| |
| pub fn create_node( |
| self: &DirEntryHandle, |
| name: &FsStr, |
| mode: FileMode, |
| dev: DeviceType, |
| ) -> Result<DirEntryHandle, Errno> { |
| self.create_entry(name, mode, dev, || { |
| if mode.is_dir() { |
| self.node.mkdir(name) |
| } else { |
| let node = self.node.mknod(name, mode)?; |
| if mode.is_sock() { |
| node.set_socket(Socket::new(SocketDomain::Unix, SocketType::Stream)); |
| } |
| Ok(node) |
| } |
| }) |
| } |
| |
| pub fn bind_socket( |
| self: &DirEntryHandle, |
| name: &FsStr, |
| socket: SocketHandle, |
| socket_address: SocketAddress, |
| mode: FileMode, |
| ) -> Result<DirEntryHandle, Errno> { |
| self.create_entry(name, mode, DeviceType::NONE, || { |
| let node = self.node.mknod(name, mode)?; |
| if let Some(unix_socket) = socket.downcast_socket::<UnixSocket>() { |
| unix_socket.bind_socket_to_node(&socket, socket_address, &node)?; |
| } else { |
| return error!(ENOTSUP); |
| } |
| Ok(node) |
| }) |
| } |
| |
| pub fn add_node_ops( |
| self: &DirEntryHandle, |
| name: &FsStr, |
| mode: FileMode, |
| ops: impl FsNodeOps + 'static, |
| ) -> Result<DirEntryHandle, Errno> { |
| self.add_node_ops_dev(name, mode, DeviceType::NONE, ops) |
| } |
| |
| pub fn add_node_ops_dev( |
| self: &DirEntryHandle, |
| name: &FsStr, |
| mode: FileMode, |
| dev: DeviceType, |
| ops: impl FsNodeOps + 'static, |
| ) -> Result<DirEntryHandle, Errno> { |
| self.create_entry(name, mode, dev, || Ok(self.node.fs().create_node(Box::new(ops), mode))) |
| } |
| |
| pub fn create_symlink( |
| self: &DirEntryHandle, |
| name: &FsStr, |
| target: &FsStr, |
| ) -> Result<DirEntryHandle, Errno> { |
| self.create_entry(name, FileMode::IFLNK | FileMode::ALLOW_ALL, DeviceType::NONE, || { |
| self.node.create_symlink(name, target) |
| }) |
| } |
| |
| pub fn link(self: &DirEntryHandle, name: &FsStr, child: &FsNodeHandle) -> Result<(), Errno> { |
| if DirEntry::is_reserved_name(name) { |
| return error!(EEXIST); |
| } |
| let (entry, exists) = self.get_or_create_child(name, || { |
| self.node.link(name, child)?; |
| Ok(child.clone()) |
| })?; |
| if exists { |
| return error!(EEXIST); |
| } |
| self.node.touch(); |
| entry.node.fs().did_create_dir_entry(&entry); |
| Ok(()) |
| } |
| |
| pub fn unlink(self: &DirEntryHandle, name: &FsStr, kind: UnlinkKind) -> Result<(), Errno> { |
| assert!(!DirEntry::is_reserved_name(name)); |
| let mut state = self.state.write(); |
| |
| let child = self.component_lookup_locked(&mut state, name)?; |
| let child_state = child.state.read(); |
| |
| if child_state.mount_count > 0 { |
| return error!(EBUSY); |
| } |
| |
| match kind { |
| UnlinkKind::Directory => { |
| if !child.node.is_dir() { |
| return error!(ENOTDIR); |
| } |
| // This check only covers whether the cache is non-empty. |
| // We actually need to check whether the underlying directory is |
| // empty by asking the node via remove below. |
| if !child_state.children.is_empty() { |
| return error!(ENOTEMPTY); |
| } |
| } |
| UnlinkKind::NonDirectory => { |
| if child.node.is_dir() { |
| return error!(EISDIR); |
| } |
| } |
| } |
| |
| self.node.unlink(name, &child.node)?; |
| state.children.remove(name); |
| |
| std::mem::drop(child_state); |
| // We drop the state lock before we drop the child so that we do |
| // not trigger a deadlock in the Drop trait for FsNode, which attempts |
| // to remove the FsNode from its parent's child list. |
| std::mem::drop(state); |
| |
| self.node.fs().will_destroy_dir_entry(&child); |
| |
| std::mem::drop(child); |
| Ok(()) |
| } |
| |
| /// Returns whether this entry is a descendant of |other|. |
| pub fn is_descendant_of(self: &DirEntryHandle, other: &DirEntryHandle) -> bool { |
| let mut current = self.clone(); |
| loop { |
| if Arc::ptr_eq(¤t, &other) { |
| // We found |other|. |
| return true; |
| } |
| if let Some(next) = current.parent() { |
| current = next; |
| } else { |
| // We reached the root of the file system. |
| return false; |
| } |
| } |
| } |
| |
| /// Rename the file with old_basename in old_parent to new_basename in |
| /// new_parent. |
| /// |
| /// old_parent and new_parent must belong to the same file system. |
| pub fn rename( |
| old_parent_name: &NamespaceNode, |
| old_basename: &FsStr, |
| new_parent_name: &NamespaceNode, |
| new_basename: &FsStr, |
| ) -> Result<(), Errno> { |
| // If either the old_basename or the new_basename is a reserved name |
| // (e.g., "." or ".."), then we cannot do the rename. |
| if DirEntry::is_reserved_name(old_basename) || DirEntry::is_reserved_name(new_basename) { |
| return error!(EBUSY); |
| } |
| |
| let old_parent = &old_parent_name.entry; |
| let new_parent = &new_parent_name.entry; |
| |
| // If the names and parents are the same, then there's nothing to do |
| // and we can report success. |
| if Arc::ptr_eq(old_parent, new_parent) && old_basename == new_basename { |
| return Ok(()); |
| } |
| |
| // We need to hold these DirEntryHandles until after we drop all the |
| // locks so that we do not deadlock when we drop them. |
| let (_renamed, maybe_replaced) = { |
| // The mount_eq check in sys_renameat ensures that the nodes we're |
| // touching are part of the same file system. It doesn't matter |
| // where we grab the FileSystem reference from. |
| let fs = old_parent.node.fs(); |
| |
| // Before we take any locks, we need to take the rename mutex on |
| // the file system. This lock ensures that no other rename |
| // operations are happening in this file system while we're |
| // analyzing this rename operation. |
| // |
| // For example, we grab writer locks on both old_parent and |
| // new_parent. If there was another rename operation in flight with |
| // old_parent and new_parent reversed, then we could deadlock while |
| // trying to acquire these locks. |
| let _lock = fs.rename_mutex.lock(); |
| |
| // Compute the list of ancestors of new_parent to check whether new_parent is a |
| // descendant of the renamed node. This must be computed before taking any lock to |
| // prevent lock inversions. |
| let mut new_parent_ancestor_list = Vec::<DirEntryHandle>::new(); |
| { |
| let mut current = Some(new_parent.clone()); |
| while let Some(entry) = current { |
| current = entry.parent(); |
| new_parent_ancestor_list.push(entry); |
| } |
| } |
| |
| // We cannot simply grab the locks on old_parent and new_parent |
| // independently because old_parent and new_parent might be the |
| // same directory entry. Instead, we use the RenameGuard helper to |
| // grab the appropriate locks. |
| let mut state = RenameGuard::lock(old_parent, new_parent); |
| |
| // Now that we know the old_parent child list cannot change, we |
| // establish the DirEntry that we are going to try to rename. |
| let renamed = old_parent.component_lookup_locked(state.old_parent(), old_basename)?; |
| |
| // Check whether the renamed entry is a mountpoint. |
| // TODO: We should hold a read lock on the mount points for this |
| // namespace to prevent the child from becoming a mount point |
| // while this function is executing. |
| if old_parent_name.child_is_mountpoint(&renamed) { |
| return error!(EBUSY); |
| } |
| |
| // If new_parent is a descendant of renamed, the operation would |
| // create a cycle. That's disallowed. |
| if new_parent_ancestor_list.into_iter().any(|entry| Arc::ptr_eq(&entry, &renamed)) { |
| return error!(EINVAL); |
| } |
| |
| // We need to check if there is already a DirEntry with |
| // new_basename in new_parent. If so, there are additional checks |
| // we need to perform. |
| let maybe_replaced = |
| match new_parent.component_lookup_locked(state.new_parent(), new_basename) { |
| Ok(replaced) => { |
| // Sayeth https://man7.org/linux/man-pages/man2/rename.2.html: |
| // |
| // "If oldpath and newpath are existing hard links referring to the |
| // same file, then rename() does nothing, and returns a success |
| // status." |
| if Arc::ptr_eq(&renamed.node, &replaced.node) { |
| return Ok(()); |
| } |
| |
| // Sayeth https://man7.org/linux/man-pages/man2/rename.2.html: |
| // |
| // "oldpath can specify a directory. In this case, newpath must" |
| // either not exist, or it must specify an empty directory." |
| if replaced.node.is_dir() { |
| // Check whether the replaced entry is a mountpoint. |
| // TODO: We should hold a read lock on the mount points for this |
| // namespace to prevent the child from becoming a mount point |
| // while this function is executing. |
| if new_parent_name.child_is_mountpoint(&replaced) { |
| return error!(EBUSY); |
| } |
| } else if renamed.node.is_dir() { |
| return error!(ENOTDIR); |
| } |
| Some(replaced) |
| } |
| // It's fine for the lookup to fail to find a child. |
| Err(errno) if errno == errno!(ENOENT) => None, |
| // However, other errors are fatal. |
| Err(e) => return Err(e), |
| }; |
| |
| // We've found all the errors that we know how to find. Ask the |
| // file system to actually execute the rename operation. Once the |
| // file system has executed the rename, we are no longer allowed to |
| // fail because we will not be able to return the system to a |
| // consistent state. |
| fs.rename( |
| &old_parent.node, |
| old_basename, |
| &new_parent.node, |
| new_basename, |
| &renamed.node, |
| maybe_replaced.as_ref().map(|replaced| &replaced.node), |
| )?; |
| |
| { |
| // We need to update the parent and local name for the DirEntry |
| // we are renaming to reflect its new parent and its new name. |
| let mut renamed_state = renamed.state.write(); |
| renamed_state.parent = Some(new_parent.clone()); |
| renamed_state.local_name = new_basename.to_owned(); |
| } |
| // Actually add the renamed child to the new_parent's child list. |
| // This operation implicitly removes the replaced child (if any) |
| // from the child list. |
| state.new_parent().children.insert(new_basename.to_owned(), Arc::downgrade(&renamed)); |
| |
| // Finally, remove the renamed child from the old_parent's child |
| // list. |
| state.old_parent().children.remove(old_basename); |
| |
| (renamed, maybe_replaced) |
| }; |
| |
| if let Some(replaced) = maybe_replaced { |
| replaced.node.fs().will_destroy_dir_entry(&replaced); |
| } |
| |
| Ok(()) |
| } |
| |
| fn component_lookup_locked( |
| self: &DirEntryHandle, |
| state: &mut DirEntryState, |
| name: &FsStr, |
| ) -> Result<DirEntryHandle, Errno> { |
| assert!(!DirEntry::is_reserved_name(name)); |
| let (node, _) = self.get_or_create_child_locked(state, name, || self.node.lookup(name))?; |
| Ok(node) |
| } |
| |
| pub fn get_children<F, T>(&self, callback: F) -> T |
| where |
| F: FnOnce(&BTreeMap<FsString, Weak<DirEntry>>) -> T, |
| { |
| let state = self.state.read(); |
| callback(&state.children) |
| } |
| |
| fn get_or_create_child<F>( |
| self: &DirEntryHandle, |
| name: &FsStr, |
| create_fn: F, |
| ) -> Result<(DirEntryHandle, bool), Errno> |
| where |
| F: FnOnce() -> Result<FsNodeHandle, Errno>, |
| { |
| assert!(!DirEntry::is_reserved_name(name)); |
| // Only directories can have children. |
| if !self.node.is_dir() { |
| return error!(ENOTDIR); |
| } |
| // Check if the child is already in children. In that case, we can |
| // simply return the child and we do not need to call init_fn. |
| let state = self.state.read(); |
| if let Some(child) = state.children.get(name).and_then(Weak::upgrade) { |
| return Ok((child, true)); |
| } |
| std::mem::drop(state); |
| |
| let mut state = self.state.write(); |
| self.get_or_create_child_locked(&mut state, name, create_fn) |
| } |
| |
| fn get_or_create_child_locked<F>( |
| self: &DirEntryHandle, |
| state: &mut DirEntryState, |
| name: &FsStr, |
| create_fn: F, |
| ) -> Result<(DirEntryHandle, bool), Errno> |
| where |
| F: FnOnce() -> Result<FsNodeHandle, Errno>, |
| { |
| let create_child = || { |
| let node = create_fn()?; |
| assert!( |
| node.info().mode & FileMode::IFMT != FileMode::EMPTY, |
| "FsNode initialization did not populate the FileMode in FsNodeInfo." |
| ); |
| let entry = DirEntry::new(node, Some(self.clone()), name.to_vec()); |
| #[cfg(any(test, debug_assertions))] |
| { |
| // Take the lock on child while holding the one on the parent to ensure any wrong ordering |
| // will trigger the tracing-mutex at the right call site. |
| let _l1 = entry.state.read(); |
| } |
| Ok(entry) |
| }; |
| |
| match state.children.entry(name.to_vec()) { |
| Entry::Vacant(entry) => { |
| let child = create_child()?; |
| entry.insert(Arc::downgrade(&child)); |
| Ok((child, false)) |
| } |
| Entry::Occupied(mut entry) => { |
| // It's possible that the upgrade will succeed this time around |
| // because we dropped the read lock before acquiring the write |
| // lock. Another thread might have populated this entry while |
| // we were not holding any locks. |
| if let Some(child) = Weak::upgrade(entry.get()) { |
| return Ok((child, true)); |
| } |
| let child = create_child()?; |
| entry.insert(Arc::downgrade(&child)); |
| Ok((child, false)) |
| } |
| } |
| } |
| |
| // This function is only useful for tests and has some oddities. |
| // |
| // For example, not all the children might have been looked up yet, which |
| // means the returned vector could be missing some names. |
| // |
| // Also, the vector might have "extra" names that are in the process of |
| // being looked up. If the lookup fails, they'll be removed. |
| #[cfg(test)] |
| pub fn copy_child_names(&self) -> Vec<FsString> { |
| self.state |
| .read() |
| .children |
| .values() |
| .filter_map(|child| Weak::upgrade(child).map(|c| c.local_name())) |
| .collect() |
| } |
| |
| fn internal_remove_child(&self, child: &mut DirEntry) { |
| let local_name = child.local_name(); |
| let mut state = self.state.write(); |
| if let Some(weak_child) = state.children.get(&local_name) { |
| // If this entry is occupied, we need to check whether child is |
| // the current occupant. If so, we should remove the entry |
| // because the child no longer exists. |
| if std::ptr::eq(weak_child.as_ptr(), child) { |
| state.children.remove(&local_name); |
| } |
| } |
| } |
| } |
| |
| impl fmt::Debug for DirEntry { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| let mut parents = vec![]; |
| let mut maybe_parent = self.parent(); |
| while let Some(parent) = maybe_parent { |
| parents.push(String::from_utf8_lossy(&parent.local_name()).into_owned()); |
| maybe_parent = parent.parent(); |
| } |
| let mut builder = f.debug_struct("DirEntry"); |
| builder.field("id", &(self as *const DirEntry)); |
| builder.field("local_name", &String::from_utf8_lossy(&self.local_name())); |
| if parents.len() > 0 { |
| builder.field("parents", &parents); |
| } |
| builder.finish() |
| } |
| } |
| |
| struct RenameGuard<'a, 'b> { |
| old_parent_guard: RwLockWriteGuard<'a, DirEntryState>, |
| new_parent_guard: Option<RwLockWriteGuard<'b, DirEntryState>>, |
| } |
| |
| impl<'a, 'b> RenameGuard<'a, 'b> { |
| fn lock(old_parent: &'a DirEntryHandle, new_parent: &'b DirEntryHandle) -> Self { |
| if Arc::ptr_eq(&old_parent, &new_parent) { |
| Self { old_parent_guard: old_parent.state.write(), new_parent_guard: None } |
| } else { |
| // Following gVisor, these locks are taken in ancestor-to-descendant order. |
| // Moreover, if the node are not comparable, they are taken from smallest inode to |
| // biggest. |
| if new_parent.is_descendant_of(old_parent) |
| || (!old_parent.is_descendant_of(new_parent) |
| && old_parent.node.inode_num < new_parent.node.inode_num) |
| { |
| let old_parent_guard = old_parent.state.write(); |
| let new_parent_guard = new_parent.state.write(); |
| Self { old_parent_guard, new_parent_guard: Some(new_parent_guard) } |
| } else { |
| let new_parent_guard = new_parent.state.write(); |
| let old_parent_guard = old_parent.state.write(); |
| Self { old_parent_guard, new_parent_guard: Some(new_parent_guard) } |
| } |
| } |
| } |
| |
| fn old_parent(&mut self) -> &mut DirEntryState { |
| &mut self.old_parent_guard |
| } |
| |
| fn new_parent(&mut self) -> &mut DirEntryState { |
| if let Some(new_guard) = self.new_parent_guard.as_mut() { |
| new_guard |
| } else { |
| &mut self.old_parent_guard |
| } |
| } |
| } |
| |
| /// The Drop trait for DirEntry removes the entry from the child list of the |
| /// parent entry, which means we cannot drop DirEntry objects while holding a |
| /// lock on the parent's child list. |
| impl Drop for DirEntry { |
| fn drop(&mut self) { |
| let maybe_parent = self.state.write().parent.take(); |
| if let Some(parent) = maybe_parent { |
| parent.internal_remove_child(self); |
| } |
| } |
| } |