nros/host_env.rs
1//! Issue 1763 — the boot-config ENV rung for a `#![no_std]` image that still
2//! runs inside a host process, read through the C library's `getenv`.
3//!
4//! [`crate::env`] reads the same variables through `std::env`, so it needs
5//! `std`. Until issue 1763 every image that had an environment also had `std`,
6//! so that was one fact. It stopped being one when the host simulators dropped
7//! libstd: threadx-linux's C/C++ runtime staticlib (issue 1763) and
8//! freertos-posix's (issue 1778) are `alloc`, but they are still Linux
9//! processes with a C library and an environment. Without this module those
10//! images silently stopped honouring `$ROS_DOMAIN_ID` and `$NROS_LOCATOR`.
11//! That was measured on freertos-posix: the C and C++ cells, run in parallel
12//! with distinct domains, both fell back to the baked domain and heard each
13//! other, so the C++ listener printed a `Received:` before its own talker
14//! published.
15//!
16//! The variables, their precedence and their errors are the SAME as
17//! [`crate::env`]'s. Only the reader differs. Issue 0687's rule still holds:
18//! the edge that has an environment reads it, and the core takes values. The
19//! edge is "a C library is linked", which on this tree is `target_os =
20//! "linux"`. A cross target has no `getenv` to call, and this module does not
21//! exist there.
22//!
23//! Strings are borrowed from the process environment, which outlives the boot.
24//! Nothing in nano-ros calls `setenv` after boot, and the std reader made the
25//! same assumption by caching its first read.
26
27use core::ffi::{CStr, c_char};
28
29use crate::retired_env::RETIRED_ENV_VARS;
30use nros_node::{BootConfig, BootConfigError, EnvRung, ExecutorConfig, RMW_SELECTOR_CAP};
31use nros_rmw::SessionMode;
32
33unsafe extern "C" {
34 fn getenv(name: *const c_char) -> *const c_char;
35}
36
37/// `$NAME` as a C string, or `None` when unset — the one `getenv` call.
38fn raw(name: &CStr) -> Option<&'static CStr> {
39 // SAFETY: `name` is NUL-terminated; `getenv` returns NULL or a pointer to
40 // a NUL-terminated string in the process environment, which lives for the
41 // process, so a non-NULL result is a valid `'static` C string.
42 unsafe {
43 let p = getenv(name.as_ptr());
44 (!p.is_null()).then(|| CStr::from_ptr(p))
45 }
46}
47
48/// `$NAME` as UTF-8, or `None` when unset or not UTF-8 (`std::env::var`'s
49/// `Err` cases, which the std reader also treats as unset).
50fn var(name: &CStr) -> Option<&'static str> {
51 raw(name)?.to_str().ok()
52}
53
54fn non_empty(v: Option<&'static str>) -> Option<&'static str> {
55 v.filter(|s| !s.is_empty())
56}
57
58/// The env rung, read with `getenv`. Mirrors `crate::env::env_rung`.
59fn env_rung() -> Result<EnvRung<'static>, BootConfigError> {
60 // phase-484 W8c — mirrors `crate::env::env_rung`: a set retired alias is
61 // refused (and logged, for the C / C++ shims that only see a return code).
62 for &(name, replacement) in RETIRED_ENV_VARS {
63 let mut cname = [0u8; 32];
64 cname[..name.len()].copy_from_slice(name.as_bytes());
65 let cname = CStr::from_bytes_until_nul(&cname).expect("retired env name fits");
66 // Only presence is tested, so a non-UTF-8 value still refuses.
67 if raw(cname).is_some() {
68 let err = BootConfigError::RetiredEnvVar { name, replacement };
69 nros_log::log_error!(nros_log::get_logger("nros"), "{}", err);
70 return Err(err);
71 }
72 }
73 let locator = var(c"NROS_LOCATOR");
74 let mode_str = var(c"NROS_SESSION_MODE");
75 let mode = match mode_str {
76 Some("peer") => SessionMode::Peer,
77 _ => SessionMode::Client,
78 };
79 let domain_id = match non_empty(var(c"ROS_DOMAIN_ID")) {
80 Some(s) => Some(
81 s.trim()
82 .parse::<u32>()
83 .map_err(|_| BootConfigError::DomainIdParse)?,
84 ),
85 None => None,
86 };
87 let rmw = non_empty(var(c"NROS_RMW")).filter(|s| s.len() <= RMW_SELECTOR_CAP);
88 Ok(EnvRung {
89 locator,
90 domain_id,
91 mode: Some(mode),
92 node_name: non_empty(var(c"NROS_NODE_NAME")),
93 namespace: non_empty(var(c"NROS_NODE_NAMESPACE")),
94 rmw,
95 })
96}
97
98/// [`ExecutorConfig::try_resolve_with`] with the `getenv` rung — the
99/// `#![no_std]` hosted twin of [`crate::env::try_resolve_hosted`].
100pub fn try_resolve_hosted<'a>(
101 baked: BootConfig<'a>,
102) -> Result<ExecutorConfig<'a>, BootConfigError> {
103 ExecutorConfig::try_resolve_with(baked, Some(env_rung()?))
104}