1use super::{Error, Result};
7
8pub(crate) struct Peer;
9
10#[crate::interface(
14 name = "org.freedesktop.DBus.Peer",
15 introspection_docs = false,
16 proxy(visibility = "pub")
17)]
18impl Peer {
19 fn ping(&self) {}
22
23 fn get_machine_id(&self) -> Result<String> {
36 #[cfg(any(
38 target_os = "freebsd",
39 target_os = "dragonfly",
40 target_os = "openbsd",
41 target_os = "netbsd"
42 ))]
43 if let Some(id) = read_dbus_machine_id() {
44 return Ok(id);
45 }
46
47 get_platform_machine_id()
48 }
49}
50
51#[cfg(target_os = "linux")]
52fn get_platform_machine_id() -> Result<String> {
53 let mut id = match std::fs::read_to_string("/var/lib/dbus/machine-id") {
54 Ok(id) => id,
55 Err(e) => {
56 if let Ok(id) = std::fs::read_to_string("/etc/machine-id") {
57 id
58 } else {
59 return Err(Error::IOError(format!(
60 "Failed to read from /var/lib/dbus/machine-id or /etc/machine-id: {e}"
61 )));
62 }
63 }
64 };
65
66 let len = id.trim_end().len();
67 id.truncate(len);
68 Ok(id)
69}
70
71#[cfg(target_os = "macos")]
72fn get_platform_machine_id() -> Result<String> {
73 unsafe extern "C" {
74 fn gethostuuid(id: *mut u8, wait: *const libc::timespec) -> libc::c_int;
75 }
76
77 let mut uuid = [0u8; 16];
78 let timeout = libc::timespec {
79 tv_sec: 1,
80 tv_nsec: 0,
81 };
82
83 let ret = unsafe { gethostuuid(uuid.as_mut_ptr(), &timeout) };
84 if ret != 0 {
85 return Err(Error::IOError(format!(
86 "gethostuuid failed: {}",
87 std::io::Error::last_os_error()
88 )));
89 }
90
91 Ok(uuid.iter().map(|b| format!("{b:02x}")).collect())
92}
93
94#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
97fn get_platform_machine_id() -> Result<String> {
98 use std::ffi::CStr;
99
100 let mut buf = [0u8; 64];
101 let mut len = buf.len();
102
103 let mib_name = c"kern.hostuuid";
104 let ret = unsafe {
105 libc::sysctlbyname(
106 mib_name.as_ptr(),
107 buf.as_mut_ptr() as *mut libc::c_void,
108 &mut len,
109 std::ptr::null(),
110 0,
111 )
112 };
113
114 if ret != 0 {
115 return Err(Error::IOError(format!(
116 "sysctlbyname(kern.hostuuid) failed: {}",
117 std::io::Error::last_os_error()
118 )));
119 }
120
121 let uuid_str = CStr::from_bytes_until_nul(&buf[..len])
125 .map_err(|e| Error::IOError(format!("Invalid UTF-8 in hostuuid: {e}")))?
126 .to_str()
127 .map_err(|e| Error::IOError(format!("Invalid UTF-8 in hostuuid: {e}")))?;
128
129 let machine_id: String = uuid_str.chars().filter(|c| *c != '-').collect();
130
131 if machine_id.len() != 32 || !machine_id.chars().all(|c| c.is_ascii_hexdigit()) {
132 return Err(Error::IOError(format!(
133 "Invalid hostuuid format: {uuid_str}"
134 )));
135 }
136
137 Ok(machine_id)
138}
139
140#[cfg(any(
144 target_os = "freebsd",
145 target_os = "dragonfly",
146 target_os = "openbsd",
147 target_os = "netbsd"
148))]
149fn read_dbus_machine_id() -> Option<String> {
150 const MACHINE_ID_PATHS: &[&str] = &[
151 "/var/lib/dbus/machine-id",
152 "/etc/machine-id",
153 "/var/db/dbus/machine-id",
154 ];
155
156 for path in MACHINE_ID_PATHS {
157 if let Ok(mut id) = std::fs::read_to_string(path) {
158 let len = id.trim_end().len();
159 id.truncate(len);
160 if !id.is_empty() {
161 return Some(id);
162 }
163 }
164 }
165 None
166}
167
168#[cfg(any(target_os = "openbsd", target_os = "netbsd"))]
169fn get_platform_machine_id() -> Result<String> {
170 Err(Error::IOError(
173 "No machine-id found. Please ensure D-Bus is properly installed and \
174 /var/db/dbus/machine-id or /etc/machine-id exists."
175 .to_string(),
176 ))
177}
178
179#[cfg(all(
181 unix,
182 not(any(
183 target_os = "linux",
184 target_os = "macos",
185 target_os = "freebsd",
186 target_os = "dragonfly",
187 target_os = "openbsd",
188 target_os = "netbsd"
189 ))
190))]
191fn get_platform_machine_id() -> Result<String> {
192 Err(Error::NotSupported(
193 "get_machine_id is not yet implemented on this platform".to_string(),
194 ))
195}
196
197#[cfg(windows)]
198fn get_platform_machine_id() -> Result<String> {
199 crate::win32::machine_id().map_err(|e| Error::IOError(e.to_string()))
200}
201
202#[cfg(test)]
203mod tests {
204 #[allow(unused)]
205 use super::*;
206
207 #[test]
208 #[cfg(target_os = "linux")]
209 fn linux_machine_id() {
210 if let Ok(id) = get_platform_machine_id() {
211 assert_eq!(id.len(), 32, "machine ID should be 32 hex characters");
212 assert!(
213 id.chars().all(|c| c.is_ascii_hexdigit()),
214 "machine ID should only contain hex characters"
215 );
216 }
217 }
218
219 #[test]
220 #[cfg(target_os = "macos")]
221 fn macos_machine_id() {
222 let id = get_platform_machine_id().expect("gethostuuid should succeed on macOS");
223 assert_eq!(id.len(), 32, "machine ID should be 32 hex characters");
224 assert!(
225 id.chars().all(|c| c.is_ascii_hexdigit()),
226 "machine ID should only contain hex characters"
227 );
228 }
229
230 #[test]
231 #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
232 fn freebsd_machine_id() {
233 let peer = Peer;
237 let id = peer
238 .get_machine_id()
239 .expect("should get machine ID on FreeBSD/DragonFlyBSD");
240 assert_eq!(id.len(), 32, "machine ID should be 32 hex characters");
241 assert!(
242 id.chars().all(|c| c.is_ascii_hexdigit()),
243 "machine ID should only contain hex characters"
244 );
245 }
246
247 #[test]
248 #[cfg(any(target_os = "openbsd", target_os = "netbsd"))]
249 fn openbsd_netbsd_machine_id() {
250 let peer = Peer;
254 if let Ok(id) = peer.get_machine_id() {
255 assert_eq!(id.len(), 32, "machine ID should be 32 hex characters");
256 assert!(
257 id.chars().all(|c| c.is_ascii_hexdigit()),
258 "machine ID should only contain hex characters"
259 );
260 }
261 }
262}