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