libfdt: FdtNodeMut::nop()

This makes self no-op node. Effectively removes from the FDT.

Bug: 277993056
Test: atest liblibfdt.integration_test
Change-Id: Ia0b2213510f5b04ea42a093beddee2b639cee06a
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 2a547e8..5683ce3 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -627,7 +627,7 @@
         fdt_err_expect_zero(ret)
     }
 
-    /// Sets the given property with FDT_NOP, effectively removing it from the DT.
+    /// Deletes the given property effectively from DT, by setting it with FDT_NOP.
     pub fn nop_property(&mut self, name: &CStr) -> Result<()> {
         // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor) when the
         // library locates the node's property.
@@ -711,7 +711,7 @@
     // node, and delete the current node, the Rust borrow checker kicks in. The next node has a
     // mutable reference to DT, so we can't use current node (which also has a mutable reference to
     // DT).
-    pub fn delete_and_next_compatible(self, compatible: &CStr) -> Result<Option<Self>> {
+    pub fn delete_and_next_compatible(mut self, compatible: &CStr) -> Result<Option<Self>> {
         // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
         let ret = unsafe {
             libfdt_bindgen::fdt_node_offset_by_compatible(
@@ -722,13 +722,33 @@
         };
         let next_offset = fdt_err_or_option(ret)?;
 
-        // SAFETY: fdt_nop_node alter only the bytes in the blob which contain the node and its
-        // properties and subnodes, and will not alter or move any other part of the tree.
-        let ret = unsafe { libfdt_bindgen::fdt_nop_node(self.fdt.as_mut_ptr(), self.offset) };
-        fdt_err_expect_zero(ret)?;
+        if Some(self.offset) == next_offset {
+            return Err(FdtError::Internal);
+        }
+
+        // SAFETY: nop_self() only touches bytes of the self and its properties and subnodes, and
+        // doesn't alter any other blob in the tree. self.fdt and next_offset would remain valid.
+        unsafe { self.nop_self()? };
 
         Ok(next_offset.map(|offset| Self { fdt: self.fdt, offset }))
     }
+
+    /// Deletes this node effectively from DT, by setting it with FDT_NOP
+    pub fn nop(mut self) -> Result<()> {
+        // SAFETY: This consumes self, so invalid node wouldn't be used any further
+        unsafe { self.nop_self() }
+    }
+
+    /// Deletes this node effectively from DT, by setting it with FDT_NOP.
+    /// This only changes bytes of the node and its properties and subnodes, and doesn't alter or
+    /// move any other part of the tree.
+    /// SAFETY: This node is no longer valid.
+    unsafe fn nop_self(&mut self) -> Result<()> {
+        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
+        let ret = unsafe { libfdt_bindgen::fdt_nop_node(self.fdt.as_mut_ptr(), self.offset) };
+
+        fdt_err_expect_zero(ret)
+    }
 }
 
 /// Wrapper around low-level libfdt functions.
diff --git a/libs/libfdt/tests/api_test.rs b/libs/libfdt/tests/api_test.rs
index 5484185..65ebc4d 100644
--- a/libs/libfdt/tests/api_test.rs
+++ b/libs/libfdt/tests/api_test.rs
@@ -169,3 +169,23 @@
     let node = fdt.node_with_phandle(Phandle::new(0x22).unwrap()).unwrap().unwrap();
     assert_eq!(node.name().unwrap().to_str().unwrap(), "node_abc");
 }
+
+#[test]
+fn node_nop() {
+    let mut data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
+    let fdt = Fdt::from_mut_slice(&mut data).unwrap();
+
+    fdt.node_with_phandle(Phandle::new(0xFF).unwrap()).unwrap().unwrap();
+    let node = fdt.node_mut(cstr!("/node_z/node_zz")).unwrap().unwrap();
+
+    node.nop().unwrap();
+
+    assert!(fdt.node_with_phandle(Phandle::new(0xFF).unwrap()).unwrap().is_none());
+    assert!(fdt.node(cstr!("/node_z/node_zz")).unwrap().is_none());
+
+    fdt.unpack().unwrap();
+    fdt.pack().unwrap();
+
+    assert!(fdt.node_with_phandle(Phandle::new(0xFF).unwrap()).unwrap().is_none());
+    assert!(fdt.node(cstr!("/node_z/node_zz")).unwrap().is_none());
+}