zbus/fdo/
peer.rs

1//! D-Bus standard interfaces.
2//!
3//! The D-Bus specification defines the message bus messages and some standard interfaces that may
4//! be useful across various D-Bus applications. This module provides their proxy.
5
6use super::{Error, Result};
7
8pub(crate) struct Peer;
9
10/// Service-side implementation for the `org.freedesktop.DBus.Peer` interface.
11/// This interface is implemented automatically for any object registered to the
12/// [ObjectServer](crate::ObjectServer).
13#[crate::interface(
14    name = "org.freedesktop.DBus.Peer",
15    introspection_docs = false,
16    proxy(visibility = "pub")
17)]
18impl Peer {
19    /// On receipt, an application should do nothing other than reply as usual. It does not matter
20    /// which object path a ping is sent to.
21    fn ping(&self) {}
22
23    /// An application should reply the containing a hex-encoded UUID representing the identity of
24    /// the machine the process is running on. This UUID must be the same for all processes on a
25    /// single system at least until that system next reboots. It should be the same across reboots
26    /// if possible, but this is not always possible to implement and is not guaranteed. It does not
27    /// matter which object path a GetMachineId is sent to.
28    ///
29    /// This method is implemented for:
30    /// - Linux: Reads from `/var/lib/dbus/machine-id` or `/etc/machine-id`
31    /// - macOS: Uses `gethostuuid()` system call
32    /// - FreeBSD/DragonFlyBSD: Reads from standard D-Bus locations, falls back to `kern.hostuuid`
33    /// - OpenBSD/NetBSD: Reads from standard D-Bus locations (`/var/db/dbus/machine-id`, etc.)
34    /// - Windows: Uses Windows hardware profile GUID
35    fn get_machine_id(&self) -> Result<String> {
36        // On *BSD platforms, first try standard D-Bus machine-id locations
37        #[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/// Get the machine ID on FreeBSD or DragonFlyBSD using the kern.hostuuid sysctl.
95/// This returns a UUID that is typically generated at install time and persists across reboots.
96#[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    // The sysctl returns a null-terminated UUID string (e.g.
122    // "01234567-89ab-cdef-0123-456789abcdef"). Remove hyphens to convert to the expected
123    // 32-character hex format.
124    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/// Try to read machine ID from standard D-Bus locations.
141/// Used on *BSD platforms as the primary method before falling back to platform-specific
142/// mechanisms.
143#[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    // OpenBSD and NetBSD don't have a built-in machine UUID mechanism.
171    // The D-Bus package typically creates /var/db/dbus/machine-id on installation.
172    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/// Fallback for other Unix platforms not explicitly supported.
180#[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        // On FreeBSD/DragonFlyBSD, we should always be able to get a machine ID
234        // either from D-Bus machine-id files or from kern.hostuuid sysctl.
235        // Test the combined logic through Peer interface
236        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        // On OpenBSD/NetBSD, machine ID is only available if D-Bus is installed
251        // and has created the machine-id file.
252        // Test the combined logic through Peer interface
253        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}