libs: Move cstr!() into its own crate

Make the macro host_supported, for unittests.

This overshadows the homonymous crate from crates.io [1] but importing
and depending on an external crate for <10 lines of code seems overkill.

[1]: https://crates.io/crates/cstr

Bug: 308694211
Test: atest libcstr.tests
Test: m libcstr
Test: m pvmfw_bin rialto_bin vmbase_example_bin
Change-Id: If1bf37034fc004a380b3ba528b9d76393a865d3e
diff --git a/libs/cstr/Android.bp b/libs/cstr/Android.bp
new file mode 100644
index 0000000..4ea87df
--- /dev/null
+++ b/libs/cstr/Android.bp
@@ -0,0 +1,36 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library_rlib {
+    name: "libcstr",
+    crate_name: "cstr",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    host_supported: true,
+    prefer_rlib: true,
+    target: {
+        android: {
+            no_stdlibs: true,
+            stdlibs: [
+                "libcompiler_builtins.rust_sysroot",
+                "libcore.rust_sysroot",
+            ],
+        },
+    },
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
+}
+
+rust_test {
+    name: "libcstr.tests",
+    crate_name: "libcstr_test",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/lib.rs"],
+    test_suites: ["general-tests"],
+    prefer_rlib: true,
+    rustlibs: ["libcstr"],
+}
diff --git a/libs/cstr/src/lib.rs b/libs/cstr/src/lib.rs
new file mode 100644
index 0000000..ddf20fc
--- /dev/null
+++ b/libs/cstr/src/lib.rs
@@ -0,0 +1,50 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Provide a safe const-compatible no_std macro for readable &'static CStr.
+
+#![no_std]
+
+/// Create &CStr out of &str literal
+#[macro_export]
+macro_rules! cstr {
+    ($str:literal) => {{
+        const S: &str = concat!($str, "\0");
+        const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
+            Ok(v) => v,
+            Err(_) => panic!("string contains interior NUL"),
+        };
+        C
+    }};
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::ffi::CString;
+
+    #[test]
+    fn valid_input_string() {
+        let expected = CString::new("aaa").unwrap();
+        assert_eq!(cstr!("aaa"), expected.as_c_str());
+    }
+
+    #[test]
+    fn valid_empty_string() {
+        let expected = CString::new("").unwrap();
+        assert_eq!(cstr!(""), expected.as_c_str());
+    }
+
+    // As cstr!() panics at compile time, tests covering invalid inputs fail to compile!
+}
diff --git a/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index b889ee5..5920d5d 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -37,6 +37,7 @@
         "libcore.rust_sysroot",
     ],
     rustlibs: [
+        "libcstr",
         "liblibfdt_bindgen",
         "libzerocopy_nostd",
     ],
@@ -61,6 +62,7 @@
     ],
     prefer_rlib: true,
     rustlibs: [
+        "libcstr",
         "liblibfdt",
     ],
 }
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index b369390..6cfe5f7 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -31,15 +31,9 @@
 use core::ops::Range;
 use core::ptr;
 use core::result;
+use cstr::cstr;
 use zerocopy::AsBytes as _;
 
-// TODO(b/308694211): Use cstr!() from vmbase
-macro_rules! cstr {
-    ($str:literal) => {{
-        core::ffi::CStr::from_bytes_with_nul(concat!($str, "\0").as_bytes()).unwrap()
-    }};
-}
-
 /// Error type corresponding to libfdt error codes.
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub enum FdtError {
diff --git a/libs/libfdt/tests/api_test.rs b/libs/libfdt/tests/api_test.rs
index d76b1a4..63cbdee 100644
--- a/libs/libfdt/tests/api_test.rs
+++ b/libs/libfdt/tests/api_test.rs
@@ -17,23 +17,12 @@
 //! Integration tests of the library libfdt.
 
 use core::ffi::CStr;
+use cstr::cstr;
 use libfdt::{Fdt, FdtError, FdtNodeMut, Phandle};
 use std::ffi::CString;
 use std::fs;
 use std::ops::Range;
 
-// TODO(b/308694211): Use cstr!() from vmbase
-macro_rules! cstr {
-    ($str:literal) => {{
-        const S: &str = concat!($str, "\0");
-        const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
-            Ok(v) => v,
-            Err(_) => panic!("string contains interior NUL"),
-        };
-        C
-    }};
-}
-
 const TEST_TREE_WITH_ONE_MEMORY_RANGE_PATH: &str = "data/test_tree_one_memory_range.dtb";
 const TEST_TREE_WITH_MULTIPLE_MEMORY_RANGES_PATH: &str =
     "data/test_tree_multiple_memory_ranges.dtb";