1#[cfg(any(target_os = "freebsd", windows, test))]
8mod state;
9
10#[cfg(target_os = "linux")]
11#[path = "platform/linux.rs"]
12mod platform;
13#[cfg(target_os = "freebsd")]
14#[path = "platform/freebsd.rs"]
15mod platform;
16#[cfg(target_os = "macos")]
17#[path = "platform/macos.rs"]
18mod platform;
19#[cfg(windows)]
20#[path = "platform/windows.rs"]
21mod platform;
22#[cfg(not(any(
23 target_os = "linux",
24 target_os = "freebsd",
25 target_os = "macos",
26 windows
27)))]
28#[path = "platform/other.rs"]
29mod platform;
30
31use journal_common::EntryTimestamps;
32use std::io;
33use std::path::PathBuf;
34use std::sync::{Arc, Mutex};
35use std::time::{SystemTime, UNIX_EPOCH};
36use uuid::Uuid;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum BootIdSource {
41 Unknown,
43 Native,
45 StateBacked,
47 Degraded,
52}
53
54impl BootIdSource {
55 pub fn as_str(self) -> &'static str {
56 match self {
57 Self::Unknown => "unknown",
58 Self::Native => "native",
59 Self::StateBacked => "state-backed",
60 Self::Degraded => "degraded",
61 }
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct Diagnostics {
68 pub machine_id_source: String,
69 pub boot_id_source: BootIdSource,
70 pub boot_id_path: Option<PathBuf>,
71 pub monotonic_source: String,
72 pub monotonic_source_detail: String,
73 pub degraded_reason: Option<String>,
74}
75
76impl Default for Diagnostics {
77 fn default() -> Self {
78 Self {
79 machine_id_source: String::new(),
80 boot_id_source: BootIdSource::Unknown,
81 boot_id_path: None,
82 monotonic_source: String::new(),
83 monotonic_source_detail: String::new(),
84 degraded_reason: None,
85 }
86 }
87}
88
89#[derive(Clone, Default)]
94pub struct LoadOptions {
95 pub state_dir: Option<PathBuf>,
96 pub state_file_name: Option<String>,
97 pub state_path: Option<PathBuf>,
98 pub monotonic_now: Option<Arc<dyn Fn() -> io::Result<u64> + Send + Sync>>,
99 pub monotonic_label: Option<String>,
100}
101
102impl LoadOptions {
103 pub fn with_state_dir(mut self, path: impl Into<PathBuf>) -> Self {
104 self.state_dir = Some(path.into());
105 self
106 }
107
108 pub fn with_state_file_name(mut self, name: impl Into<String>) -> Self {
109 self.state_file_name = Some(name.into());
110 self
111 }
112
113 pub fn with_state_path(mut self, path: impl Into<PathBuf>) -> Self {
114 self.state_path = Some(path.into());
115 self
116 }
117
118 pub fn with_monotonic_now(
119 mut self,
120 label: impl Into<String>,
121 source: Arc<dyn Fn() -> io::Result<u64> + Send + Sync>,
122 ) -> Self {
123 self.monotonic_label = Some(label.into());
124 self.monotonic_now = Some(source);
125 self
126 }
127
128 #[cfg(any(target_os = "freebsd", windows))]
129 pub(crate) fn resolve_state_path(
130 &self,
131 default_dir: impl AsRef<std::path::Path>,
132 default_file_name: impl AsRef<str>,
133 ) -> PathBuf {
134 if let Some(path) = &self.state_path {
135 return path.clone();
136 }
137 let dir = self
138 .state_dir
139 .clone()
140 .unwrap_or_else(|| default_dir.as_ref().to_path_buf());
141 let file_name = self
142 .state_file_name
143 .clone()
144 .unwrap_or_else(|| default_file_name.as_ref().to_string());
145 dir.join(file_name)
146 }
147}
148
149pub struct LocalJournalProvider {
151 machine_id: Uuid,
152 boot_id: Uuid,
153 diagnostics: Diagnostics,
154 monotonic_now: Arc<dyn Fn() -> io::Result<u64> + Send + Sync>,
155 monotonic_label: String,
156 last_monotonic: Mutex<u64>,
157}
158
159impl LocalJournalProvider {
160 pub fn load(options: LoadOptions) -> io::Result<Self> {
161 platform::load(options)
162 }
163
164 pub fn machine_id(&self) -> Uuid {
165 self.machine_id
166 }
167
168 pub fn boot_id(&self) -> Uuid {
169 self.boot_id
170 }
171
172 pub fn diagnostics(&self) -> &Diagnostics {
173 &self.diagnostics
174 }
175
176 pub fn monotonic_source(&self) -> &str {
177 &self.monotonic_label
178 }
179
180 pub fn realtime_usec(&self) -> io::Result<u64> {
181 realtime_usec()
182 }
183
184 pub fn monotonic_usec(&self) -> io::Result<u64> {
188 let mut now = (self.monotonic_now)()?;
189 let mut last = self
190 .last_monotonic
191 .lock()
192 .map_err(|_| io::Error::other("monotonic mutex poisoned"))?;
193 if now <= *last {
194 now = last
195 .checked_add(1)
196 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "monotonic overflow"))?;
197 }
198 *last = now;
199 Ok(now)
200 }
201
202 pub fn entry_timestamps(&self) -> io::Result<EntryTimestamps> {
204 Ok(EntryTimestamps::default()
205 .with_entry_realtime_usec(self.realtime_usec()?)
206 .with_entry_monotonic_usec(self.monotonic_usec()?))
207 }
208
209 pub(crate) fn new(
210 machine_id: Uuid,
211 boot_id: Uuid,
212 diagnostics: Diagnostics,
213 monotonic_label: impl Into<String>,
214 monotonic_now: Arc<dyn Fn() -> io::Result<u64> + Send + Sync>,
215 ) -> Self {
216 let monotonic_label = monotonic_label.into();
217 let mut diagnostics = diagnostics;
218 diagnostics.monotonic_source = monotonic_label.clone();
219 if diagnostics.monotonic_source_detail.is_empty() {
220 diagnostics.monotonic_source_detail = monotonic_label.clone();
221 }
222 Self {
223 machine_id,
224 boot_id,
225 diagnostics,
226 monotonic_now,
227 monotonic_label,
228 last_monotonic: Mutex::new(0),
229 }
230 }
231}
232
233pub fn load(options: LoadOptions) -> io::Result<LocalJournalProvider> {
235 LocalJournalProvider::load(options)
236}
237
238pub fn must_load(options: LoadOptions) -> LocalJournalProvider {
240 load(options).unwrap_or_else(|err| panic!("journal host helper failed: {err}"))
241}
242
243pub(crate) fn realtime_usec() -> io::Result<u64> {
244 let duration = SystemTime::now()
245 .duration_since(UNIX_EPOCH)
246 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
247 u64::try_from(duration.as_micros())
248 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "realtime microseconds overflow"))
249}
250
251pub(crate) fn parse_uuid_text(text: &str) -> io::Result<Uuid> {
252 Uuid::try_parse(text.trim())
253 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
254 .and_then(reject_zero_uuid)
255}
256
257pub(crate) fn reject_zero_uuid(id: Uuid) -> io::Result<Uuid> {
258 if id.is_nil() {
259 return Err(io::Error::new(
260 io::ErrorKind::InvalidData,
261 "uuid is all zeros",
262 ));
263 }
264 Ok(id)
265}
266
267#[cfg(any(target_os = "freebsd", windows, test))]
268pub(crate) fn uuid_compact(id: Uuid) -> String {
269 id.as_simple().to_string()
270}
271
272#[cfg(unix)]
273pub(crate) fn clock_gettime_usec(clock_id: libc::clockid_t) -> io::Result<u64> {
274 let mut ts = std::mem::MaybeUninit::<libc::timespec>::uninit();
275 let rc = unsafe { libc::clock_gettime(clock_id, ts.as_mut_ptr()) };
277 if rc != 0 {
278 return Err(io::Error::last_os_error());
279 }
280 let ts = unsafe { ts.assume_init() };
282 let sec = u64::try_from(ts.tv_sec).map_err(|_| {
283 io::Error::new(
284 io::ErrorKind::InvalidData,
285 "clock_gettime returned negative seconds",
286 )
287 })?;
288 let nsec = u64::try_from(ts.tv_nsec).map_err(|_| {
289 io::Error::new(
290 io::ErrorKind::InvalidData,
291 "clock_gettime returned negative nanoseconds",
292 )
293 })?;
294 sec.checked_mul(1_000_000)
295 .and_then(|value| value.checked_add(nsec / 1_000))
296 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "clock microseconds overflow"))
297}
298
299#[cfg(any(target_os = "freebsd", target_os = "macos"))]
300pub(crate) fn sysctl_string(name: &str) -> io::Result<String> {
301 let raw = sysctl_raw(name)?;
302 let end = raw.iter().position(|byte| *byte == 0).unwrap_or(raw.len());
303 String::from_utf8(raw[..end].to_vec())
304 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
305}
306
307#[cfg(any(target_os = "freebsd", target_os = "macos"))]
308pub(crate) fn sysctl_raw(name: &str) -> io::Result<Vec<u8>> {
309 let name = std::ffi::CString::new(name)
310 .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
311 let mut len = 0usize;
312 let rc = unsafe {
314 libc::sysctlbyname(
315 name.as_ptr(),
316 std::ptr::null_mut(),
317 &mut len,
318 std::ptr::null_mut(),
319 0,
320 )
321 };
322 if rc != 0 {
323 return Err(io::Error::last_os_error());
324 }
325 let mut buf = vec![0u8; len];
326 let rc = unsafe {
328 libc::sysctlbyname(
329 name.as_ptr(),
330 buf.as_mut_ptr().cast(),
331 &mut len,
332 std::ptr::null_mut(),
333 0,
334 )
335 };
336 if rc != 0 {
337 return Err(io::Error::last_os_error());
338 }
339 buf.truncate(len);
340 Ok(buf)
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346 use std::sync::atomic::{AtomicU64, Ordering};
347
348 #[test]
349 fn provider_monotonic_is_strictly_increasing() {
350 let counter = Arc::new(AtomicU64::new(5));
351 let source = {
352 let counter = Arc::clone(&counter);
353 Arc::new(move || Ok(counter.load(Ordering::SeqCst)))
354 };
355 let provider = LocalJournalProvider::new(
356 Uuid::from_u128(1),
357 Uuid::from_u128(2),
358 Diagnostics::default(),
359 "test",
360 source,
361 );
362 assert_eq!(provider.monotonic_usec().unwrap(), 5);
363 assert_eq!(provider.monotonic_usec().unwrap(), 6);
364 counter.store(10, Ordering::SeqCst);
365 assert_eq!(provider.monotonic_usec().unwrap(), 10);
366 }
367}