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