1#![allow(non_snake_case)]
2
3use crate::{
4 BootInfo, DirectoryReader, Entry, FileReader, ReaderOptions, SdkError, export_entry_bytes,
5 format_entry_text, json_entry,
6};
7use std::collections::HashSet;
8use std::fmt;
9use std::path::{Path, PathBuf};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum OutputMode {
13 Default,
14 Json,
15 Export,
16}
17
18impl Default for OutputMode {
19 fn default() -> Self {
20 Self::Default
21 }
22}
23
24#[derive(Debug, Clone)]
25pub enum Error {
26 Unsupported,
27 NoEntry,
28 InvalidCursor,
29 EndOfEntries,
30 CorruptFile,
31 Other(String),
32}
33
34impl fmt::Display for Error {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self {
37 Self::Unsupported => write!(f, "operation not supported"),
38 Self::NoEntry => write!(f, "no matching entry"),
39 Self::InvalidCursor => write!(f, "invalid cursor"),
40 Self::EndOfEntries => write!(f, "end of entries"),
41 Self::CorruptFile => write!(f, "corrupt journal file"),
42 Self::Other(msg) => write!(f, "{msg}"),
43 }
44 }
45}
46
47impl std::error::Error for Error {}
48
49pub const ERR_UNSUPPORTED: i32 = 1;
50pub const ERR_NO_ENTRY: i32 = 2;
51pub const ERR_INVALID_CURSOR: i32 = 3;
52pub const ERR_END_OF_ENTRIES: i32 = 4;
53
54enum ReaderKind {
55 File(FileReader),
56 Directory(DirectoryReader),
57}
58
59pub struct SdJournal {
60 reader: ReaderKind,
61 output_mode: OutputMode,
62 field_items: Vec<String>,
63 field_index: usize,
64 unique_items: Vec<Vec<u8>>,
65 unique_index: usize,
66}
67
68pub type UniqueValue = (String, Vec<u8>);
69
70pub fn SdJournalOpen(path: &str, flags: u32) -> std::result::Result<SdJournal, Error> {
71 if flags != 0 {
72 return Err(Error::Unsupported);
73 }
74
75 let path = Path::new(path);
76 let reader = if path.is_dir() {
77 ReaderKind::Directory(DirectoryReader::open(path).map_err(map_error)?)
78 } else {
79 ReaderKind::File(FileReader::open(path).map_err(map_error)?)
80 };
81
82 Ok(SdJournal::new(reader))
83}
84
85pub fn SdJournalOpenFile(path: &str, flags: u32) -> std::result::Result<SdJournal, Error> {
86 SdJournalOpenFileWithOptions(path, flags, ReaderOptions::default())
87}
88
89pub fn SdJournalOpenFileWithOptions(
90 path: &str,
91 flags: u32,
92 options: ReaderOptions,
93) -> std::result::Result<SdJournal, Error> {
94 if flags != 0 {
95 return Err(Error::Unsupported);
96 }
97 Ok(SdJournal::new(ReaderKind::File(
98 FileReader::open_with_options(path, options).map_err(map_error)?,
99 )))
100}
101
102pub fn SdJournalOpenDirectory(path: &str, flags: u32) -> std::result::Result<SdJournal, Error> {
103 SdJournalOpenDirectoryWithOptions(path, flags, ReaderOptions::default())
104}
105
106pub fn SdJournalOpenDirectoryWithOptions(
107 path: &str,
108 flags: u32,
109 options: ReaderOptions,
110) -> std::result::Result<SdJournal, Error> {
111 if flags != 0 {
112 return Err(Error::Unsupported);
113 }
114 Ok(SdJournal::new(ReaderKind::Directory(
115 DirectoryReader::open_with_options(path, options).map_err(map_error)?,
116 )))
117}
118
119pub fn SdJournalOpenFiles(paths: &[&str], flags: u32) -> std::result::Result<SdJournal, Error> {
120 SdJournalOpenFilesWithOptions(paths, flags, ReaderOptions::default())
121}
122
123pub fn SdJournalOpenFilesWithOptions(
124 paths: &[&str],
125 flags: u32,
126 options: ReaderOptions,
127) -> std::result::Result<SdJournal, Error> {
128 if flags != 0 {
129 return Err(Error::Unsupported);
130 }
131 let paths = dedupe_open_file_paths(paths);
132 if paths.len() == 1 {
133 return SdJournalOpenFileWithOptions(&paths[0], flags, options);
134 }
135 Ok(SdJournal::new(ReaderKind::Directory(
136 DirectoryReader::open_files_with_options(&paths, options).map_err(map_error)?,
137 )))
138}
139
140fn dedupe_open_file_paths(paths: &[&str]) -> Vec<String> {
141 let mut out = Vec::with_capacity(paths.len());
142 let mut seen = HashSet::with_capacity(paths.len());
143 for path in paths {
144 let key = canonical_open_file_key(Path::new(path));
145 if seen.insert(key) {
146 out.push((*path).to_string());
147 }
148 }
149 out
150}
151
152fn canonical_open_file_key(path: &Path) -> PathBuf {
153 std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
154}
155
156pub fn SdJournalClose(j: SdJournal) {
157 drop(j);
158}
159
160impl SdJournal {
161 fn new(reader: ReaderKind) -> Self {
162 Self {
163 reader,
164 output_mode: OutputMode::Default,
165 field_items: Vec::new(),
166 field_index: 0,
167 unique_items: Vec::new(),
168 unique_index: 0,
169 }
170 }
171
172 fn reset_iterators(&mut self) {
173 match &mut self.reader {
174 ReaderKind::File(reader) => reader.clear_entry_data_state(),
175 ReaderKind::Directory(reader) => reader.clear_entry_data_state(),
176 }
177 self.field_items.clear();
178 self.field_index = 0;
179 self.unique_items.clear();
180 self.unique_index = 0;
181 }
182
183 pub fn add_match(&mut self, data: &[u8]) {
184 self.reset_iterators();
185 match &mut self.reader {
186 ReaderKind::File(reader) => reader.add_match(data),
187 ReaderKind::Directory(reader) => reader.add_match(data),
188 }
189 }
190
191 pub fn add_conjunction(&mut self) -> std::result::Result<(), Error> {
192 self.reset_iterators();
193 match &mut self.reader {
194 ReaderKind::File(reader) => reader.add_conjunction(),
195 ReaderKind::Directory(reader) => reader.add_conjunction(),
196 }
197 .map_err(map_error)
198 }
199
200 pub fn add_disjunction(&mut self) -> std::result::Result<(), Error> {
201 self.reset_iterators();
202 match &mut self.reader {
203 ReaderKind::File(reader) => reader.add_disjunction(),
204 ReaderKind::Directory(reader) => reader.add_disjunction(),
205 }
206 .map_err(map_error)
207 }
208
209 pub fn flush_matches(&mut self) {
210 self.reset_iterators();
211 match &mut self.reader {
212 ReaderKind::File(reader) => reader.flush_matches(),
213 ReaderKind::Directory(reader) => reader.flush_matches(),
214 }
215 }
216
217 pub fn next(&mut self) -> std::result::Result<i32, Error> {
218 self.reset_iterators();
219 let advanced = match &mut self.reader {
220 ReaderKind::File(reader) => reader.next(),
221 ReaderKind::Directory(reader) => reader.next(),
222 }
223 .map_err(map_error)?;
224 Ok(i32::from(advanced))
225 }
226
227 pub fn previous(&mut self) -> std::result::Result<i32, Error> {
228 self.reset_iterators();
229 let advanced = match &mut self.reader {
230 ReaderKind::File(reader) => reader.previous(),
231 ReaderKind::Directory(reader) => reader.previous(),
232 }
233 .map_err(map_error)?;
234 Ok(i32::from(advanced))
235 }
236
237 pub fn seek_head(&mut self) {
238 self.reset_iterators();
239 match &mut self.reader {
240 ReaderKind::File(reader) => reader.seek_head(),
241 ReaderKind::Directory(reader) => reader.seek_head(),
242 }
243 }
244
245 pub fn seek_tail(&mut self) {
246 self.reset_iterators();
247 match &mut self.reader {
248 ReaderKind::File(reader) => reader.seek_tail(),
249 ReaderKind::Directory(reader) => reader.seek_tail(),
250 }
251 }
252
253 pub fn seek_realtime_usec(&mut self, usec: u64) {
254 self.reset_iterators();
255 match &mut self.reader {
256 ReaderKind::File(reader) => reader.seek_realtime(usec),
257 ReaderKind::Directory(reader) => reader.seek_realtime(usec),
258 }
259 }
260
261 pub fn seek_cursor(&mut self, cursor: &str) -> std::result::Result<(), Error> {
262 self.reset_iterators();
263 match &mut self.reader {
264 ReaderKind::File(reader) => reader.seek_cursor(cursor),
265 ReaderKind::Directory(reader) => reader.seek_cursor(cursor),
266 }
267 .map_err(map_error)
268 }
269
270 pub fn get_entry(&mut self) -> std::result::Result<Entry, Error> {
271 match &mut self.reader {
272 ReaderKind::File(reader) => reader.get_entry(),
273 ReaderKind::Directory(reader) => reader.get_entry(),
274 }
275 .map_err(map_error)
276 }
277
278 pub fn get_realtime_usec(&self) -> std::result::Result<u64, Error> {
279 match &self.reader {
280 ReaderKind::File(reader) => reader.get_realtime_usec(),
281 ReaderKind::Directory(reader) => reader.get_realtime_usec(),
282 }
283 .map_err(map_error)
284 }
285
286 pub fn get_cursor(&self) -> std::result::Result<String, Error> {
287 match &self.reader {
288 ReaderKind::File(reader) => reader.get_cursor(),
289 ReaderKind::Directory(reader) => reader.get_cursor(),
290 }
291 .map_err(map_error)
292 }
293
294 pub fn get_seqnum(&self) -> std::result::Result<(u64, [u8; 16]), Error> {
295 match &self.reader {
296 ReaderKind::File(reader) => reader.get_seqnum(),
297 ReaderKind::Directory(reader) => reader.get_seqnum(),
298 }
299 .map_err(map_error)
300 }
301
302 pub fn get_monotonic_usec(&self) -> std::result::Result<(u64, [u8; 16]), Error> {
303 match &self.reader {
304 ReaderKind::File(reader) => reader.get_monotonic_usec(),
305 ReaderKind::Directory(reader) => reader.get_monotonic_usec(),
306 }
307 .map_err(map_error)
308 }
309
310 pub fn test_cursor(&self, cursor: &str) -> std::result::Result<bool, Error> {
311 match &self.reader {
312 ReaderKind::File(reader) => reader.test_cursor(cursor),
313 ReaderKind::Directory(reader) => reader.test_cursor(cursor),
314 }
315 .map_err(map_error)
316 }
317
318 pub fn restart_data(&mut self) -> std::result::Result<(), Error> {
319 match &mut self.reader {
320 ReaderKind::File(reader) => reader.entry_data_restart(),
321 ReaderKind::Directory(reader) => reader.entry_data_restart(),
322 }
323 .map_err(map_error)
324 }
325
326 pub fn enumerate_available_data(&mut self) -> std::result::Result<Option<&[u8]>, Error> {
327 match &mut self.reader {
328 ReaderKind::File(reader) => reader.enumerate_entry_payload(),
329 ReaderKind::Directory(reader) => reader.enumerate_entry_payload(),
330 }
331 .map_err(map_error)
332 }
333
334 pub fn enumerate_fields(&mut self) -> std::result::Result<Vec<String>, Error> {
335 match &mut self.reader {
336 ReaderKind::File(reader) => enumerate_file_fields(reader),
337 ReaderKind::Directory(reader) => reader.enumerate_fields(),
338 }
339 .map_err(map_error)
340 }
341
342 pub fn restart_fields(&mut self) -> std::result::Result<(), Error> {
343 self.field_items = self.enumerate_fields()?;
344 self.field_index = 0;
345 Ok(())
346 }
347
348 pub fn enumerate_field(&mut self) -> std::result::Result<Option<String>, Error> {
349 if self.field_index >= self.field_items.len() {
350 return Ok(None);
351 }
352 let item = self.field_items[self.field_index].clone();
353 self.field_index += 1;
354 Ok(Some(item))
355 }
356
357 fn query_unique_values(&mut self, field: &str) -> std::result::Result<Vec<Vec<u8>>, Error> {
358 let mut values = Vec::new();
359 self.visit_unique_values(field, |value| {
360 values.push(value.to_vec());
361 Ok(())
362 })?;
363 Ok(values)
364 }
365
366 pub fn visit_unique_values<F>(
367 &mut self,
368 field: &str,
369 visitor: F,
370 ) -> std::result::Result<(), Error>
371 where
372 F: FnMut(&[u8]) -> std::result::Result<(), Error>,
373 {
374 let mut visitor = visitor;
375 let mut visitor_error = None;
376 let result = {
377 let mut sdk_visitor = |value: &[u8]| match visitor(value) {
378 Ok(()) => Ok(()),
379 Err(err) => {
380 visitor_error = Some(err);
381 Err(crate::SdkError::VerificationError(
382 "unique value visitor failed".to_string(),
383 ))
384 }
385 };
386 match &mut self.reader {
387 ReaderKind::File(reader) => reader.visit_unique_values(field, &mut sdk_visitor),
388 ReaderKind::Directory(reader) => {
389 reader.visit_unique_values(field, &mut sdk_visitor)
390 }
391 }
392 };
393 result.map_err(|err| visitor_error.take().unwrap_or_else(|| map_error(err)))
394 }
395
396 pub fn query_unique(&mut self, field: &str) -> std::result::Result<Vec<UniqueValue>, Error> {
397 Ok(self
398 .query_unique_values(field)?
399 .into_iter()
400 .map(|value| (field.to_string(), value))
401 .collect())
402 }
403
404 pub fn query_unique_state(&mut self, field: &str) -> std::result::Result<(), Error> {
405 let values = self.query_unique_values(field)?;
406 self.unique_items = values
407 .into_iter()
408 .map(|value| payload_from_field_value(field, &value))
409 .collect();
410 self.unique_index = 0;
411 Ok(())
412 }
413
414 pub fn restart_unique(&mut self) {
415 self.unique_index = 0;
416 }
417
418 pub fn enumerate_available_unique(&mut self) -> std::result::Result<Option<Vec<u8>>, Error> {
419 if self.unique_index >= self.unique_items.len() {
420 return Ok(None);
421 }
422 let item = self.unique_items[self.unique_index].clone();
423 self.unique_index += 1;
424 Ok(Some(item))
425 }
426
427 pub fn list_boots(&self) -> Vec<BootInfo> {
428 match &self.reader {
429 ReaderKind::File(reader) => {
430 let header = reader.cached_header().header;
431 vec![BootInfo {
432 index: 0,
433 boot_id: hex::encode(header.tail_entry_boot_id),
434 first_entry: header.head_entry_realtime as i64,
435 last_entry: header.tail_entry_realtime as i64,
436 }]
437 }
438 ReaderKind::Directory(reader) => reader.list_boots(),
439 }
440 }
441
442 pub fn set_output_mode(&mut self, mode: OutputMode) {
443 self.output_mode = mode;
444 }
445
446 pub fn process_output(&self, entry: &Entry) -> std::result::Result<Vec<u8>, Error> {
447 match self.output_mode {
448 OutputMode::Default => Ok(format_entry_text(entry)),
449 OutputMode::Export => Ok(export_entry_bytes(entry)),
450 OutputMode::Json => {
451 let mut out = serde_json::to_vec(&json_entry(entry))
452 .map_err(|err| Error::Other(err.to_string()))?;
453 out.push(b'\n');
454 Ok(out)
455 }
456 }
457 }
458}
459
460pub fn SdJournalAddMatch(j: &mut SdJournal, data: &[u8]) -> std::result::Result<(), Error> {
461 crate::parse_match_bytes(data).map_err(|_| Error::Other("EINVAL".to_string()))?;
462 j.add_match(data);
463 Ok(())
464}
465
466pub fn SdJournalAddDisjunction(j: &mut SdJournal) -> std::result::Result<(), Error> {
467 j.add_disjunction()
468}
469
470pub fn SdJournalAddConjunction(j: &mut SdJournal) -> std::result::Result<(), Error> {
471 j.add_conjunction()
472}
473
474pub fn SdJournalFlushMatches(j: &mut SdJournal) -> std::result::Result<(), Error> {
475 j.flush_matches();
476 Ok(())
477}
478
479pub fn SdJournalNext(j: &mut SdJournal) -> std::result::Result<i32, Error> {
480 j.next()
481}
482
483pub fn SdJournalNextSkip(j: &mut SdJournal, skip: u64) -> std::result::Result<i32, Error> {
484 let mut advanced = 0;
485 for _ in 0..skip {
486 if j.next()? == 0 {
487 break;
488 }
489 advanced += 1;
490 }
491 Ok(advanced)
492}
493
494pub fn SdJournalPrevious(j: &mut SdJournal) -> std::result::Result<i32, Error> {
495 j.previous()
496}
497
498pub fn SdJournalPreviousSkip(j: &mut SdJournal, skip: u64) -> std::result::Result<i32, Error> {
499 let mut advanced = 0;
500 for _ in 0..skip {
501 if j.previous()? == 0 {
502 break;
503 }
504 advanced += 1;
505 }
506 Ok(advanced)
507}
508
509pub fn SdJournalSeekHead(j: &mut SdJournal) -> std::result::Result<(), Error> {
510 j.seek_head();
511 Ok(())
512}
513
514pub fn SdJournalSeekTail(j: &mut SdJournal) -> std::result::Result<(), Error> {
515 j.seek_tail();
516 Ok(())
517}
518
519pub fn SdJournalSeekRealtimeUsec(j: &mut SdJournal, usec: u64) -> std::result::Result<(), Error> {
520 j.seek_realtime_usec(usec);
521 Ok(())
522}
523
524pub fn SdJournalSeekCursor(j: &mut SdJournal, cursor: &str) -> std::result::Result<(), Error> {
525 j.seek_cursor(cursor)
526}
527
528pub fn SdJournalGetRealtimeUsec(j: &SdJournal) -> std::result::Result<u64, Error> {
529 j.get_realtime_usec()
530}
531
532pub fn SdJournalGetSeqnum(j: &SdJournal) -> std::result::Result<(u64, [u8; 16]), Error> {
533 j.get_seqnum()
534}
535
536pub fn SdJournalGetMonotonicUsec(j: &SdJournal) -> std::result::Result<(u64, [u8; 16]), Error> {
537 j.get_monotonic_usec()
538}
539
540pub fn SdJournalGetCursor(j: &SdJournal) -> std::result::Result<String, Error> {
541 j.get_cursor()
542}
543
544pub fn SdJournalTestCursor(j: &SdJournal, cursor: &str) -> std::result::Result<bool, Error> {
545 j.test_cursor(cursor)
546}
547
548pub fn SdJournalGetEntry(j: &mut SdJournal) -> std::result::Result<Entry, Error> {
549 j.get_entry()
550}
551
552pub fn SdJournalGetData(j: &mut SdJournal, field: &str) -> std::result::Result<Vec<u8>, Error> {
553 let found = match &mut j.reader {
554 ReaderKind::File(reader) => reader.get_entry_payload(field.as_bytes()),
555 ReaderKind::Directory(reader) => reader.get_entry_payload(field.as_bytes()),
556 }
557 .map_err(map_error)?;
558 found.ok_or(Error::NoEntry)
559}
560
561pub fn SdJournalRestartData(j: &mut SdJournal) -> std::result::Result<(), Error> {
562 j.restart_data()
563}
564
565pub fn SdJournalEnumerateAvailableData(
566 j: &mut SdJournal,
567) -> std::result::Result<Option<&[u8]>, Error> {
568 j.enumerate_available_data()
569}
570
571pub fn SdJournalEnumerateFields(j: &mut SdJournal) -> std::result::Result<Vec<String>, Error> {
572 j.enumerate_fields()
573}
574
575pub fn SdJournalRestartFields(j: &mut SdJournal) -> std::result::Result<(), Error> {
576 j.restart_fields()
577}
578
579pub fn SdJournalEnumerateField(j: &mut SdJournal) -> std::result::Result<Option<String>, Error> {
580 j.enumerate_field()
581}
582
583pub fn SdJournalListBoots(j: &mut SdJournal) -> std::result::Result<Vec<BootInfo>, Error> {
584 Ok(j.list_boots())
585}
586
587pub fn SdJournalQueryUnique(
588 j: &mut SdJournal,
589 field: &str,
590) -> std::result::Result<Vec<UniqueValue>, Error> {
591 j.query_unique(field)
592}
593
594pub fn SdJournalVisitUniqueValues<F>(
595 j: &mut SdJournal,
596 field: &str,
597 visitor: F,
598) -> std::result::Result<(), Error>
599where
600 F: FnMut(&[u8]) -> std::result::Result<(), Error>,
601{
602 j.visit_unique_values(field, visitor)
603}
604
605pub fn SdJournalQueryUniqueState(j: &mut SdJournal, field: &str) -> std::result::Result<(), Error> {
606 j.query_unique_state(field)
607}
608
609pub fn SdJournalRestartUnique(j: &mut SdJournal) -> std::result::Result<(), Error> {
610 j.restart_unique();
611 Ok(())
612}
613
614pub fn SdJournalEnumerateAvailableUnique(
615 j: &mut SdJournal,
616) -> std::result::Result<Option<Vec<u8>>, Error> {
617 j.enumerate_available_unique()
618}
619
620pub fn SdJournalSetOutputMode(j: &mut SdJournal, mode: OutputMode) {
621 j.set_output_mode(mode);
622}
623
624pub fn SdJournalProcessOutput(j: &SdJournal, entry: &Entry) -> std::result::Result<Vec<u8>, Error> {
625 j.process_output(entry)
626}
627
628fn enumerate_file_fields(reader: &mut FileReader) -> crate::Result<Vec<String>> {
629 reader.enumerate_fields()
630}
631
632fn payload_from_field_value(field: &str, value: &[u8]) -> Vec<u8> {
633 let mut payload = Vec::with_capacity(field.len() + 1 + value.len());
634 payload.extend_from_slice(field.as_bytes());
635 payload.push(b'=');
636 payload.extend_from_slice(value);
637 payload
638}
639
640fn map_error(err: SdkError) -> Error {
641 match err {
642 SdkError::NoEntry => Error::NoEntry,
643 SdkError::InvalidCursor(_) => Error::InvalidCursor,
644 SdkError::Unsupported(_) => Error::Unsupported,
645 SdkError::DecompressionFailed(msg) => Error::Other(msg),
646 SdkError::InvalidPath(msg) => Error::Other(msg),
647 SdkError::Journal(err) => Error::Other(err.to_string()),
648 SdkError::VerificationError(msg) => {
649 Error::Other(format!("journal verification failed: corrupt file: {msg}"))
650 }
651 }
652}