Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions libsystemd-sys/src/journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ extern "C" {
) -> c_int;

pub fn sd_journal_get_usage(j: *mut sd_journal, bytes: *mut u64) -> c_int;
pub fn sd_journal_has_persistent_files(j: *mut sd_journal) -> c_int;
pub fn sd_journal_has_runtime_files(j: *mut sd_journal) -> c_int;

pub fn sd_journal_query_unique(j: *mut sd_journal, field: *const c_char) -> c_int;
pub fn sd_journal_enumerate_unique(
Expand Down
114 changes: 114 additions & 0 deletions src/journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,32 @@ impl JournalRef {
ffi_result(unsafe { ffi::sd_journal_get_fd(self.as_ptr()) })
}

/// Returns the total disk space used by journal files currently accessible
/// through this `Journal` handle, in bytes.
///
/// Corresponds to `sd_journal_get_usage()`.
pub fn usage(&self) -> Result<u64> {
let mut bytes: u64 = 0;
ffi_result(unsafe { ffi::sd_journal_get_usage(self.as_ptr(), &mut bytes) })?;
Ok(bytes)
}

/// Returns a positive value if the journal is stored on a persistent file system.
///
/// Corresponds to `sd_journal_has_persistent_files()`.
pub fn has_persistent_files(&self) -> Result<bool> {
let r = ffi_result(unsafe { ffi::sd_journal_has_persistent_files(self.as_ptr()) })?;
Ok(r > 0)
}

/// Returns a positive value if the journal is stored on a runtime file system.
///
/// Corresponds to `sd_journal_has_runtime_files()`.
pub fn has_runtime_files(&self) -> Result<bool> {
let r = ffi_result(unsafe { ffi::sd_journal_has_runtime_files(self.as_ptr()) })?;
Ok(r > 0)
}

/// Fields that are longer that this number of bytes _may_ be truncated when retrieved by this [`Journal`]
/// instance.
///
Expand Down Expand Up @@ -789,6 +815,53 @@ impl JournalRef {
Ok(ret)
}

/// Prepare enumeration of unique values for a specified field across all matching entries
///
/// After calling this, use [`enumerate_unique()`] to iterate through the unique values and
/// [`restart_unique()`] to reset the enumeration.
///
/// Corresponds to `sd_journal_query_unique()`.
pub fn query_unique<A: CStrArgument>(&mut self, field: A) -> Result<()> {
let f = field.into_cstr();
crate::ffi_result(unsafe { ffi::sd_journal_query_unique(self.as_ptr(), f.as_ref().as_ptr()) })
.map(|_| ())
}

/// Restart the iteration of unique values previously set up by [`query_unique()`].
///
/// Corresponds to `sd_journal_restart_unique()`.
pub fn restart_unique(&mut self) {
unsafe { ffi::sd_journal_restart_unique(self.as_ptr()) }
}

/// Obtain the next unique value for the field selected with [`query_unique()`].
///
/// The returned [`JournalEntryField`] wraps the raw "FIELD=value" bytes. Use
/// [`JournalEntryField::value()`] to access only the value portion.
///
/// Safety/lifetime notes are identical to those of [`enumerate_data()`]: the returned slice is
/// only valid until the next call to one of the enumeration/get functions on this `JournalRef`.
///
/// Corresponds to `sd_journal_enumerate_unique()`.
pub fn enumerate_unique(&mut self) -> Result<Option<JournalEntryField<'_>>> {
let mut data = MaybeUninit::<*const c_void>::uninit();
let mut data_len = MaybeUninit::uninit();
let r = crate::ffi_result(unsafe {
ffi::sd_journal_enumerate_unique(self.as_ptr(), data.as_mut_ptr(), data_len.as_mut_ptr())
});

let v = r?;

if v == 0 {
return Ok(None);
}

// SAFETY: see notes above; cast void pointer to u8 for slice construction.
let ptr = unsafe { data.assume_init() } as *const u8;
let b = unsafe { std::slice::from_raw_parts(ptr, data_len.assume_init()) };
Ok(Some(b.into()))
}

/// Iterate over journal entries.
///
/// Corresponds to `sd_journal_next()`
Expand Down Expand Up @@ -1030,6 +1103,47 @@ impl JournalRef {
Ok(monotonic_timestamp_us)
}

/// Returns the realtime cutoff timestamps of the journal.
/// (i.e. the timestamps of the first and last entries)
pub fn cutoff_realtime_usec(&self) -> Result<(u64, u64)> {
let mut from: u64 = 0;
let mut to: u64 = 0;
ffi_result(unsafe {
ffi::sd_journal_get_cutoff_realtime_usec(self.as_ptr(), &mut from, &mut to)
})?;
Ok((from, to))
}

/// Returns the realtime cutoff timestamps of the journal as SystemTime.
pub fn cutoff_realtime(&self) -> Result<(time::SystemTime, time::SystemTime)> {
let (from, to) = self.cutoff_realtime_usec()?;
Ok((
system_time_from_realtime_usec(from),
system_time_from_realtime_usec(to),
))
}

/// Returns the monotonic cutoff timestamps of the journal for the given boot ID.
pub fn cutoff_monotonic_usec(&self, boot_id: Id128) -> Result<(u64, u64)> {
let mut from: u64 = 0;
let mut to: u64 = 0;
ffi_result(unsafe {
ffi::sd_journal_get_cutoff_monotonic_usec(
self.as_ptr(),
boot_id.inner,
&mut from,
&mut to,
)
})?;
Ok((from, to))
}

/// Returns the monotonic cutoff timestamps of the journal for the current boot.
pub fn cutoff_monotonic_usec_current_boot(&self) -> Result<(u64, u64)> {
let boot_id = Id128::from_boot()?;
self.cutoff_monotonic_usec(boot_id)
}

/// Adds a match by which to filter the entries of the journal.
/// If a match is applied, only entries with this field set will be iterated.
pub fn match_add<T: Into<Vec<u8>>>(&mut self, key: &str, val: T) -> Result<&mut JournalRef> {
Expand Down
43 changes: 43 additions & 0 deletions tests/journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ fn ts() {
assert_eq!(u1, u2);
}

#[test]
fn test_has_files() {
let j = journal::OpenOptions::default().open().unwrap();
// We can't really assert what these return as it depends on the system,
// but we can at least call them to ensure they don't crash.
let _ = j.has_persistent_files().unwrap();
let _ = j.has_runtime_files().unwrap();
}

#[test]
fn test_timestamp() {
if !have_journal() {
Expand Down Expand Up @@ -199,3 +208,37 @@ fn journal_entry_data_1() {
assert_eq!(jrd.name(), &b"HI"[..]);
assert_eq!(jrd.value(), Some(&b"foo"[..]));
}

#[test]
fn cutoff_realtime() {
if !have_journal() {
return;
}

let j = journal::OpenOptions::default().open().unwrap();
let (from_usec, to_usec) = j.cutoff_realtime_usec().unwrap();
let (from_st, to_st) = j.cutoff_realtime().unwrap();

assert!(from_usec <= to_usec);
assert!(from_st <= to_st);

let boot_id = id128::Id128::from_boot().unwrap();
let (m_from, m_to) = j.cutoff_monotonic_usec(boot_id).unwrap();
assert!(m_from <= m_to);

let (mc_from, mc_to) = j.cutoff_monotonic_usec_current_boot().unwrap();
assert_eq!(m_from, mc_from);
assert_eq!(m_to, mc_to);
}

#[test]
fn has_persistent_files() {
if !have_journal() {
return;
}

let j = journal::OpenOptions::default().open().unwrap();
// We don't necessarily know if the system has persistent files,
// but we can at least call the function and check it doesn't error.
let _ = j.has_persistent_files().unwrap();
}