#![allow(non_snake_case)]
use crate::{
BootInfo, DirectoryReader, Entry, FileReader, ReaderOptions, SdkError, export_entry_bytes,
format_entry_text, json_entry,
};
use std::fmt;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputMode {
Default,
Json,
Export,
}
impl Default for OutputMode {
fn default() -> Self {
Self::Default
}
}
#[derive(Debug, Clone)]
pub enum Error {
Unsupported,
NoEntry,
InvalidCursor,
EndOfEntries,
CorruptFile,
Other(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unsupported => write!(f, "operation not supported"),
Self::NoEntry => write!(f, "no matching entry"),
Self::InvalidCursor => write!(f, "invalid cursor"),
Self::EndOfEntries => write!(f, "end of entries"),
Self::CorruptFile => write!(f, "corrupt journal file"),
Self::Other(msg) => write!(f, "{msg}"),
}
}
}
impl std::error::Error for Error {}
pub const ERR_UNSUPPORTED: i32 = 1;
pub const ERR_NO_ENTRY: i32 = 2;
pub const ERR_INVALID_CURSOR: i32 = 3;
pub const ERR_END_OF_ENTRIES: i32 = 4;
enum ReaderKind {
File(FileReader),
Directory(DirectoryReader),
}
pub struct SdJournal {
reader: ReaderKind,
output_mode: OutputMode,
field_items: Vec<String>,
field_index: usize,
unique_items: Vec<Vec<u8>>,
unique_index: usize,
}
pub type UniqueValue = (String, Vec<u8>);
pub fn SdJournalOpen(path: &str, flags: u32) -> std::result::Result<SdJournal, Error> {
if flags != 0 {
return Err(Error::Unsupported);
}
let path = Path::new(path);
let reader = if path.is_dir() {
ReaderKind::Directory(DirectoryReader::open(path).map_err(map_error)?)
} else {
ReaderKind::File(FileReader::open(path).map_err(map_error)?)
};
Ok(SdJournal::new(reader))
}
pub fn SdJournalOpenFile(path: &str, flags: u32) -> std::result::Result<SdJournal, Error> {
SdJournalOpenFileWithOptions(path, flags, ReaderOptions::default())
}
pub fn SdJournalOpenFileWithOptions(
path: &str,
flags: u32,
options: ReaderOptions,
) -> std::result::Result<SdJournal, Error> {
if flags != 0 {
return Err(Error::Unsupported);
}
Ok(SdJournal::new(ReaderKind::File(
FileReader::open_with_options(path, options).map_err(map_error)?,
)))
}
pub fn SdJournalOpenDirectory(path: &str, flags: u32) -> std::result::Result<SdJournal, Error> {
SdJournalOpenDirectoryWithOptions(path, flags, ReaderOptions::default())
}
pub fn SdJournalOpenDirectoryWithOptions(
path: &str,
flags: u32,
options: ReaderOptions,
) -> std::result::Result<SdJournal, Error> {
if flags != 0 {
return Err(Error::Unsupported);
}
Ok(SdJournal::new(ReaderKind::Directory(
DirectoryReader::open_with_options(path, options).map_err(map_error)?,
)))
}
pub fn SdJournalOpenFiles(paths: &[&str], flags: u32) -> std::result::Result<SdJournal, Error> {
SdJournalOpenFilesWithOptions(paths, flags, ReaderOptions::default())
}
pub fn SdJournalOpenFilesWithOptions(
paths: &[&str],
flags: u32,
options: ReaderOptions,
) -> std::result::Result<SdJournal, Error> {
if flags != 0 {
return Err(Error::Unsupported);
}
if paths.len() == 1 {
return SdJournalOpenFileWithOptions(paths[0], flags, options);
}
Ok(SdJournal::new(ReaderKind::Directory(
DirectoryReader::open_files_with_options(paths, options).map_err(map_error)?,
)))
}
pub fn SdJournalClose(j: SdJournal) {
drop(j);
}
impl SdJournal {
fn new(reader: ReaderKind) -> Self {
Self {
reader,
output_mode: OutputMode::Default,
field_items: Vec::new(),
field_index: 0,
unique_items: Vec::new(),
unique_index: 0,
}
}
fn reset_iterators(&mut self) {
match &mut self.reader {
ReaderKind::File(reader) => reader.clear_entry_data_state(),
ReaderKind::Directory(reader) => reader.clear_entry_data_state(),
}
self.field_items.clear();
self.field_index = 0;
self.unique_items.clear();
self.unique_index = 0;
}
pub fn add_match(&mut self, data: &[u8]) {
self.reset_iterators();
match &mut self.reader {
ReaderKind::File(reader) => reader.add_match(data),
ReaderKind::Directory(reader) => reader.add_match(data),
}
}
pub fn add_conjunction(&mut self) -> std::result::Result<(), Error> {
self.reset_iterators();
match &mut self.reader {
ReaderKind::File(reader) => reader.add_conjunction(),
ReaderKind::Directory(reader) => reader.add_conjunction(),
}
.map_err(map_error)
}
pub fn add_disjunction(&mut self) -> std::result::Result<(), Error> {
self.reset_iterators();
match &mut self.reader {
ReaderKind::File(reader) => reader.add_disjunction(),
ReaderKind::Directory(reader) => reader.add_disjunction(),
}
.map_err(map_error)
}
pub fn flush_matches(&mut self) {
self.reset_iterators();
match &mut self.reader {
ReaderKind::File(reader) => reader.flush_matches(),
ReaderKind::Directory(reader) => reader.flush_matches(),
}
}
pub fn next(&mut self) -> std::result::Result<i32, Error> {
self.reset_iterators();
let advanced = match &mut self.reader {
ReaderKind::File(reader) => reader.next(),
ReaderKind::Directory(reader) => reader.next(),
}
.map_err(map_error)?;
Ok(i32::from(advanced))
}
pub fn previous(&mut self) -> std::result::Result<i32, Error> {
self.reset_iterators();
let advanced = match &mut self.reader {
ReaderKind::File(reader) => reader.previous(),
ReaderKind::Directory(reader) => reader.previous(),
}
.map_err(map_error)?;
Ok(i32::from(advanced))
}
pub fn seek_head(&mut self) {
self.reset_iterators();
match &mut self.reader {
ReaderKind::File(reader) => reader.seek_head(),
ReaderKind::Directory(reader) => reader.seek_head(),
}
}
pub fn seek_tail(&mut self) {
self.reset_iterators();
match &mut self.reader {
ReaderKind::File(reader) => reader.seek_tail(),
ReaderKind::Directory(reader) => reader.seek_tail(),
}
}
pub fn seek_realtime_usec(&mut self, usec: u64) {
self.reset_iterators();
match &mut self.reader {
ReaderKind::File(reader) => reader.seek_realtime(usec),
ReaderKind::Directory(reader) => reader.seek_realtime(usec),
}
}
pub fn seek_cursor(&mut self, cursor: &str) -> std::result::Result<(), Error> {
self.reset_iterators();
match &mut self.reader {
ReaderKind::File(reader) => reader.seek_cursor(cursor),
ReaderKind::Directory(reader) => reader.seek_cursor(cursor),
}
.map_err(map_error)
}
pub fn get_entry(&mut self) -> std::result::Result<Entry, Error> {
match &mut self.reader {
ReaderKind::File(reader) => reader.get_entry(),
ReaderKind::Directory(reader) => reader.get_entry(),
}
.map_err(map_error)
}
pub fn get_realtime_usec(&self) -> std::result::Result<u64, Error> {
match &self.reader {
ReaderKind::File(reader) => reader.get_realtime_usec(),
ReaderKind::Directory(reader) => reader.get_realtime_usec(),
}
.map_err(map_error)
}
pub fn get_cursor(&self) -> std::result::Result<String, Error> {
match &self.reader {
ReaderKind::File(reader) => reader.get_cursor(),
ReaderKind::Directory(reader) => reader.get_cursor(),
}
.map_err(map_error)
}
pub fn get_seqnum(&self) -> std::result::Result<(u64, [u8; 16]), Error> {
match &self.reader {
ReaderKind::File(reader) => reader.get_seqnum(),
ReaderKind::Directory(reader) => reader.get_seqnum(),
}
.map_err(map_error)
}
pub fn get_monotonic_usec(&self) -> std::result::Result<(u64, [u8; 16]), Error> {
match &self.reader {
ReaderKind::File(reader) => reader.get_monotonic_usec(),
ReaderKind::Directory(reader) => reader.get_monotonic_usec(),
}
.map_err(map_error)
}
pub fn test_cursor(&self, cursor: &str) -> std::result::Result<bool, Error> {
match &self.reader {
ReaderKind::File(reader) => reader.test_cursor(cursor),
ReaderKind::Directory(reader) => reader.test_cursor(cursor),
}
.map_err(map_error)
}
pub fn restart_data(&mut self) -> std::result::Result<(), Error> {
match &mut self.reader {
ReaderKind::File(reader) => reader.entry_data_restart(),
ReaderKind::Directory(reader) => reader.entry_data_restart(),
}
.map_err(map_error)
}
pub fn enumerate_available_data(&mut self) -> std::result::Result<Option<&[u8]>, Error> {
match &mut self.reader {
ReaderKind::File(reader) => reader.enumerate_entry_payload(),
ReaderKind::Directory(reader) => reader.enumerate_entry_payload(),
}
.map_err(map_error)
}
pub fn enumerate_fields(&mut self) -> std::result::Result<Vec<String>, Error> {
match &mut self.reader {
ReaderKind::File(reader) => enumerate_file_fields(reader),
ReaderKind::Directory(reader) => reader.enumerate_fields(),
}
.map_err(map_error)
}
pub fn restart_fields(&mut self) -> std::result::Result<(), Error> {
self.field_items = self.enumerate_fields()?;
self.field_index = 0;
Ok(())
}
pub fn enumerate_field(&mut self) -> std::result::Result<Option<String>, Error> {
if self.field_index >= self.field_items.len() {
return Ok(None);
}
let item = self.field_items[self.field_index].clone();
self.field_index += 1;
Ok(Some(item))
}
fn query_unique_values(&mut self, field: &str) -> std::result::Result<Vec<Vec<u8>>, Error> {
let mut values = Vec::new();
self.visit_unique_values(field, |value| {
values.push(value.to_vec());
Ok(())
})?;
Ok(values)
}
pub fn visit_unique_values<F>(
&mut self,
field: &str,
visitor: F,
) -> std::result::Result<(), Error>
where
F: FnMut(&[u8]) -> std::result::Result<(), Error>,
{
let mut visitor = visitor;
let mut visitor_error = None;
let result = {
let mut sdk_visitor = |value: &[u8]| match visitor(value) {
Ok(()) => Ok(()),
Err(err) => {
visitor_error = Some(err);
Err(crate::SdkError::VerificationError(
"unique value visitor failed".to_string(),
))
}
};
match &mut self.reader {
ReaderKind::File(reader) => reader.visit_unique_values(field, &mut sdk_visitor),
ReaderKind::Directory(reader) => {
reader.visit_unique_values(field, &mut sdk_visitor)
}
}
};
result.map_err(|err| visitor_error.take().unwrap_or_else(|| map_error(err)))
}
pub fn query_unique(&mut self, field: &str) -> std::result::Result<Vec<UniqueValue>, Error> {
Ok(self
.query_unique_values(field)?
.into_iter()
.map(|value| (field.to_string(), value))
.collect())
}
pub fn query_unique_state(&mut self, field: &str) -> std::result::Result<(), Error> {
let values = self.query_unique_values(field)?;
self.unique_items = values
.into_iter()
.map(|value| payload_from_field_value(field, &value))
.collect();
self.unique_index = 0;
Ok(())
}
pub fn restart_unique(&mut self) {
self.unique_index = 0;
}
pub fn enumerate_available_unique(&mut self) -> std::result::Result<Option<Vec<u8>>, Error> {
if self.unique_index >= self.unique_items.len() {
return Ok(None);
}
let item = self.unique_items[self.unique_index].clone();
self.unique_index += 1;
Ok(Some(item))
}
pub fn list_boots(&self) -> Vec<BootInfo> {
match &self.reader {
ReaderKind::File(reader) => {
let header = reader.cached_header().header;
vec![BootInfo {
index: 0,
boot_id: hex::encode(header.tail_entry_boot_id),
first_entry: header.head_entry_realtime as i64,
last_entry: header.tail_entry_realtime as i64,
}]
}
ReaderKind::Directory(reader) => reader.list_boots(),
}
}
pub fn set_output_mode(&mut self, mode: OutputMode) {
self.output_mode = mode;
}
pub fn process_output(&self, entry: &Entry) -> std::result::Result<Vec<u8>, Error> {
match self.output_mode {
OutputMode::Default => Ok(format_entry_text(entry)),
OutputMode::Export => Ok(export_entry_bytes(entry)),
OutputMode::Json => {
let mut out = serde_json::to_vec(&json_entry(entry))
.map_err(|err| Error::Other(err.to_string()))?;
out.push(b'\n');
Ok(out)
}
}
}
}
pub fn SdJournalAddMatch(j: &mut SdJournal, data: &[u8]) -> std::result::Result<(), Error> {
crate::parse_match_bytes(data).map_err(|_| Error::Other("EINVAL".to_string()))?;
j.add_match(data);
Ok(())
}
pub fn SdJournalAddDisjunction(j: &mut SdJournal) -> std::result::Result<(), Error> {
j.add_disjunction()
}
pub fn SdJournalAddConjunction(j: &mut SdJournal) -> std::result::Result<(), Error> {
j.add_conjunction()
}
pub fn SdJournalFlushMatches(j: &mut SdJournal) -> std::result::Result<(), Error> {
j.flush_matches();
Ok(())
}
pub fn SdJournalNext(j: &mut SdJournal) -> std::result::Result<i32, Error> {
j.next()
}
pub fn SdJournalNextSkip(j: &mut SdJournal, skip: u64) -> std::result::Result<i32, Error> {
let mut advanced = 0;
for _ in 0..skip {
if j.next()? == 0 {
break;
}
advanced += 1;
}
Ok(advanced)
}
pub fn SdJournalPrevious(j: &mut SdJournal) -> std::result::Result<i32, Error> {
j.previous()
}
pub fn SdJournalPreviousSkip(j: &mut SdJournal, skip: u64) -> std::result::Result<i32, Error> {
let mut advanced = 0;
for _ in 0..skip {
if j.previous()? == 0 {
break;
}
advanced += 1;
}
Ok(advanced)
}
pub fn SdJournalSeekHead(j: &mut SdJournal) -> std::result::Result<(), Error> {
j.seek_head();
Ok(())
}
pub fn SdJournalSeekTail(j: &mut SdJournal) -> std::result::Result<(), Error> {
j.seek_tail();
Ok(())
}
pub fn SdJournalSeekRealtimeUsec(j: &mut SdJournal, usec: u64) -> std::result::Result<(), Error> {
j.seek_realtime_usec(usec);
Ok(())
}
pub fn SdJournalSeekCursor(j: &mut SdJournal, cursor: &str) -> std::result::Result<(), Error> {
j.seek_cursor(cursor)
}
pub fn SdJournalGetRealtimeUsec(j: &SdJournal) -> std::result::Result<u64, Error> {
j.get_realtime_usec()
}
pub fn SdJournalGetSeqnum(j: &SdJournal) -> std::result::Result<(u64, [u8; 16]), Error> {
j.get_seqnum()
}
pub fn SdJournalGetMonotonicUsec(j: &SdJournal) -> std::result::Result<(u64, [u8; 16]), Error> {
j.get_monotonic_usec()
}
pub fn SdJournalGetCursor(j: &SdJournal) -> std::result::Result<String, Error> {
j.get_cursor()
}
pub fn SdJournalTestCursor(j: &SdJournal, cursor: &str) -> std::result::Result<bool, Error> {
j.test_cursor(cursor)
}
pub fn SdJournalGetEntry(j: &mut SdJournal) -> std::result::Result<Entry, Error> {
j.get_entry()
}
pub fn SdJournalGetData(j: &mut SdJournal, field: &str) -> std::result::Result<Vec<u8>, Error> {
let found = match &mut j.reader {
ReaderKind::File(reader) => reader.get_entry_payload(field.as_bytes()),
ReaderKind::Directory(reader) => reader.get_entry_payload(field.as_bytes()),
}
.map_err(map_error)?;
found.ok_or(Error::NoEntry)
}
pub fn SdJournalRestartData(j: &mut SdJournal) -> std::result::Result<(), Error> {
j.restart_data()
}
pub fn SdJournalEnumerateAvailableData(
j: &mut SdJournal,
) -> std::result::Result<Option<&[u8]>, Error> {
j.enumerate_available_data()
}
pub fn SdJournalEnumerateFields(j: &mut SdJournal) -> std::result::Result<Vec<String>, Error> {
j.enumerate_fields()
}
pub fn SdJournalRestartFields(j: &mut SdJournal) -> std::result::Result<(), Error> {
j.restart_fields()
}
pub fn SdJournalEnumerateField(j: &mut SdJournal) -> std::result::Result<Option<String>, Error> {
j.enumerate_field()
}
pub fn SdJournalListBoots(j: &mut SdJournal) -> std::result::Result<Vec<BootInfo>, Error> {
Ok(j.list_boots())
}
pub fn SdJournalQueryUnique(
j: &mut SdJournal,
field: &str,
) -> std::result::Result<Vec<UniqueValue>, Error> {
j.query_unique(field)
}
pub fn SdJournalVisitUniqueValues<F>(
j: &mut SdJournal,
field: &str,
visitor: F,
) -> std::result::Result<(), Error>
where
F: FnMut(&[u8]) -> std::result::Result<(), Error>,
{
j.visit_unique_values(field, visitor)
}
pub fn SdJournalQueryUniqueState(j: &mut SdJournal, field: &str) -> std::result::Result<(), Error> {
j.query_unique_state(field)
}
pub fn SdJournalRestartUnique(j: &mut SdJournal) -> std::result::Result<(), Error> {
j.restart_unique();
Ok(())
}
pub fn SdJournalEnumerateAvailableUnique(
j: &mut SdJournal,
) -> std::result::Result<Option<Vec<u8>>, Error> {
j.enumerate_available_unique()
}
pub fn SdJournalSetOutputMode(j: &mut SdJournal, mode: OutputMode) {
j.set_output_mode(mode);
}
pub fn SdJournalProcessOutput(j: &SdJournal, entry: &Entry) -> std::result::Result<Vec<u8>, Error> {
j.process_output(entry)
}
fn enumerate_file_fields(reader: &mut FileReader) -> crate::Result<Vec<String>> {
reader.enumerate_fields()
}
fn payload_from_field_value(field: &str, value: &[u8]) -> Vec<u8> {
let mut payload = Vec::with_capacity(field.len() + 1 + value.len());
payload.extend_from_slice(field.as_bytes());
payload.push(b'=');
payload.extend_from_slice(value);
payload
}
fn map_error(err: SdkError) -> Error {
match err {
SdkError::NoEntry => Error::NoEntry,
SdkError::InvalidCursor(_) => Error::InvalidCursor,
SdkError::Unsupported(_) => Error::Unsupported,
SdkError::DecompressionFailed(msg) => Error::Other(msg),
SdkError::InvalidPath(msg) => Error::Other(msg),
SdkError::Journal(err) => Error::Other(err.to_string()),
SdkError::VerificationError(msg) => {
Error::Other(format!("journal verification failed: corrupt file: {msg}"))
}
}
}