sd_journal/lib.rs
1// sd-journal: rust wrapper on sd-journal implemented in libsystemd
2// Copyright (C) 2020 Christian Klaue ente@ck76.de
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! sd-journal is a rust wrapper for sd-journal in the systemd API of [libsystemd](https://www.freedesktop.org/software/systemd/man/sd-id128.html). sd-journal is part of the [systemd.rs](https://gitlab.com/systemd.rs) project.
18//!
19//! gitlab.com | crates.io | docs.rs
20//! -----------|-----------|--------
21//! [sd-sys](https://gitlab.com/systemd.rs/sd-sys) | [](https://crates.io/crates/sd-sys) | [](https://docs.rs/sd-sys/)
22//! [sd-id128](https://gitlab.com/systemd.rs/sd-id128) | [](https://crates.io/crates/sd-id128) | [](https://docs.rs/sd-id128/)
23//! [sd-journal](https://gitlab.com/systemd.rs/sd-journal) | [](https://crates.io/crates/sd-journal) | [](https://docs.rs/sd-journal)
24//!
25//! systemd.rs is an alternative to the [systemd-rust](https://github.com/jmesmon/rust-systemd) project.
26//!
27//! - systemd.rs is published under the AGPL-3.0 license. Individual/commercial
28//! licenses are available upon request.
29//! - focused coverage of sd-id128 & sd-journal only (currently there are no
30//! plans to extend this coverage)
31//! - good documentation with links to the libsystemd documentation
32//! - 100% coverage of libsystemd within the area of focus
33//! - good test coverage
34//! - focus on usability
35//!
36//! ## Structure
37//!
38//! libsystemd is developed in C around a single struct "journal" with no
39//! differentiation whether a function refers to the journal in total or whether
40//! the fuction relates to a single record within the journal. This library also
41//! offers all the wrapped functions on the main struct `Journal`. Additionally
42//! two iterators are implemented for Journal: `CursorIterator` and
43//! `CursorReverseIterator` which both return a `Result<Cursor, Error>`. All
44//! methods implemented on Cursor do call a method implemented on Journal. For
45//! that reason, documentation of Cursor is always referring back to the
46//! documentation for Journal. libsystemd implements some additional
47//! enumerations. For each of those, an iterator has been implemented as well.
48//!
49//! ## Status & Stability
50//!
51//! This library is still under development. There are various methods marked
52//! with the feature "experimental". These methods are not considered finalized
53//! yet. The documentation of each of these methods contains further
54//! information. Additionally the library structure is currently under
55//! investigation. Currently all methods are implemented for struct Journal.
56//! This may change soon: methods that refer to a single record may be moved to
57//! struct Cursor and methods performing cursor movements (next(), previous()
58//! and similar ones) will return a Cursor.
59//!
60//! ### Planned Development
61//!
62//! - [ ] further rustification
63//! - [ ] remove Cursor methods from Journal
64//! - [ ] CursorMovement return Cursor instead of just a Done
65//! - [ ] additional trait implementation
66//! - [ ] Logger implementation
67//! - [ ] encoding support
68//!
69//! ### Encoding
70//!
71//! Journald stores data as "FIELDNAME=field value". While field names are
72//! strict UTF-8 encoded and field value are usually encoded in UTF-8, field
73//! values may as well be in any encoding including binary data.
74//! This library allows logging to the journal in any encoding although using
75//! UTF-8 only is highly recommended. While reading from the journal this
76//! library will strictly raise an error whenever non-UTF-8 data is encountered.
77//! In future releases decoding support and a lossy decoding may be added.
78//!
79//! ## Examples
80//!
81//! ### cargo.toml
82//!
83//! ```toml
84//! [dependencies]
85//! sdJournal = "0.1"
86//! ```
87//!
88//! ### Logging
89//!
90//! ```rust
91//! use sd_journal::*;
92//! Journal::log_message(Level::Info, "Hello World!").unwrap();
93//! Journal::log_raw_record(&["MESSAGE=Hello World!",
94//! &format!("PRIORITY={}", Level::Info),
95//! &format!("CODE_FILE={}", file!()),
96//! &format!("CODE_LINE={}", line!()),
97//! "CUSTOM_FIELD=42"]).unwrap();
98//! ```
99//!
100//! ### Read Access
101//!
102//! ```rust
103//! use sd_journal::*;
104//! use std::path::PathBuf;
105//!
106//! // load local test data
107//! let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
108//! test_data.push("test-data/");
109//! println!("looking for test data in folder {}", test_data.display());
110//! let journal =
111//! Journal::open_directory(&test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap();
112//!
113//! // loop over journal records
114//! while let Ok(CursorMovement::Done) = journal.next() {
115//! // do something on each cursor, e.g. print the MESSAGE
116//! println!("{}", journal.get_data("MESSAGE").unwrap());
117//! }
118//! ```
119//!
120//! ## License
121//!
122//! sd-journal: a wrapper for sd-journal of libsystemd
123//!
124//! Copyright (C) 2020 Christian Klaue [mail@ck76.de]
125//!
126//! This program is free software: you can redistribute it and/or modify
127//! it under the terms of the GNU Affero General Public License as published by
128//! the Free Software Foundation, either version 3 of the License, or
129//! (at your option) any later version.
130//!
131//! This program is distributed in the hope that it will be useful,
132//! but WITHOUT ANY WARRANTY; without even the implied warranty of
133//! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
134//! GNU Affero General Public License for more details.
135//!
136//! You should have received a copy of the GNU Affero General Public License
137//! along with this program. If not, see <https://www.gnu.org/licenses/>.
138//!
139//! Individual licenses may be granted upon request.
140mod enums;
141pub mod iterators;
142
143use chrono::{Duration, NaiveDateTime};
144pub use enums::{CursorMovement, Enumeration, Error, Event, FileFlags, Level, NamespaceFlags,
145 PathFlags, UserFlags};
146use iterators::{CursorIterator, CursorReverseIterator, FieldNames, Fields, UniqueValues};
147use libc::{c_char, c_int, c_uchar, c_void, iovec, size_t};
148use sd_id128::ID128;
149use sd_sys::journal as ffi;
150use std::{ffi::{CStr, CString},
151 fmt::Debug,
152 os::unix::io::RawFd,
153 path::PathBuf,
154 ptr};
155
156/// A wrapper for sd-journal as offered by libsystemd based on FFI bindings
157/// offered in crate [sd-sys](https://gitlab.com/systemd.rs/sd-sys).
158///
159/// Journal is a fully implemented, wrapper for submitting and querying log
160/// entries from the systemd journal.
161#[derive(Debug)]
162pub struct Journal {
163 ffi: *mut ffi::sd_journal
164}
165
166/// A journal entry record
167#[derive(Debug)]
168pub struct Cursor<'a> {
169 pub(crate) journal: &'a Journal
170}
171
172impl Journal {
173 /// Submits a simple, plain text log message with a chosen syslog level to
174 /// the journal (implements
175 /// [`sd_journal_print()`](<https://www.freedesktop.org/software/systemd/man/sd_journal_print.html#>)).
176 ///
177 /// The message submitted to `log_message()` may be anything that can be
178 /// turned into a vector of bytes. Journald considers non-UTF-8 values as
179 /// valid message although 0-bytes within the message cause an error.
180 ///
181 /// # Examples
182 /// ```
183 /// use sd_journal::*;
184 /// use std::ffi::CString;
185 /// // the following lines are all synonyms
186 /// Journal::log_message(Level::Info, "Hello World!").unwrap();
187 /// Journal::log_message(Level::Info, String::from("Hello World!").as_str()).unwrap();
188 /// Journal::log_message(Level::Info, String::from("Hello World!")).unwrap();
189 /// Journal::log_message(Level::Info, CString::new("Hello World!").unwrap()).unwrap();
190 /// ```
191 ///
192 /// # Return Values
193 /// - Ok(): success
194 /// - Err(Error::SDError): sd-journal returned an error code
195 /// - Err(Error::NullError): the message contained a 0-byte
196 pub fn log_message<T: Into<Vec<u8>>>(level: Level, message: T) -> Result<(), Error> {
197 let c_message = CString::new(message).map_err(Error::NullError)?;
198 let result = unsafe { ffi::sd_journal_print(level as c_int, c_message.as_ptr()) };
199 if result < 0 {
200 return Err(Error::SDError(result));
201 }
202 Ok(())
203 }
204
205 /// Send a raw log record to the journal (implements
206 /// [`sd_journal_sendv()`](<https://www.freedesktop.org/software/systemd/man/sd_journal_print.html#>))
207 ///
208 /// This method may be used to submit structured log entries to the system
209 /// journal. It takes any slice of byte-slices (e.g. &[String] or &[&str]).
210 ///
211 /// For libsystemd a single log record consists of multiple tuples each in
212 /// the format "FIELDNAME=fieldvalue". The field name must be in uppercase
213 /// and consist only of characters, numbers and underscores, and may not
214 /// begin with an underscore. All assignments that do not follow this
215 /// syntax will silently be ignored. A variable may be assigned more than
216 /// one value per entry. Well known field names are defined in enum
217 /// [`Field`](Field) or may be [looked up](https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html#).
218 ///
219 /// The value can be of any size and format, i.e. the data encoding may
220 /// differ from UTF8 and may contain binary coding.
221 ///
222 /// # Examples
223 /// ```
224 /// use sd_journal::*;
225 /// // the first two lines are synonyms
226 /// Journal::log_message(Level::Info, "Hello World!").unwrap();
227 /// Journal::log_raw_record(&["PRIORITY=6", "MESSAGE=Hello World!"]).unwrap();
228 /// // data: &Vec<String>
229 /// Journal::log_raw_record(&vec![format!("PRIORITY={}", Level::Info),
230 /// "MESSAGE=Hello World!".to_string(),
231 /// format!("CODE_LINE={}", line!()),
232 /// format!("CODE_FILE={}", file!()),
233 /// "CUSTOM_FIELD=42".to_string()]).unwrap();
234 /// // data: &[&str]
235 /// Journal::log_raw_record(&["MESSAGE=Hello World!",
236 /// &format!("PRIORITY={}", Level::Info),
237 /// &format!("CODE_FILE={}", file!()),
238 /// &format!("CODE_LINE={}", line!()),
239 /// "CUSTOM_FIELD=42"]).unwrap();
240 /// ```
241 ///
242 /// # Return Values
243 /// - Ok(): success
244 /// - Err(Error::SDError): sd-journal returned an error code
245 pub fn log_raw_record<T: AsRef<[u8]>>(data: &[T]) -> Result<(), Error> {
246 let mut iovec_vec: Vec<iovec> = Vec::new();
247 for field in data {
248 let field = field.as_ref();
249 iovec_vec.push(iovec { iov_base: field.as_ptr() as *mut c_void,
250 iov_len: field.len() });
251 }
252 let result = unsafe { ffi::sd_journal_sendv(iovec_vec.as_ptr(), iovec_vec.len() as c_int) };
253 if result < 0 {
254 return Err(Error::SDError(result));
255 }
256 Ok(())
257 }
258
259 /// Determine the message cataloge entry for a message id (implements
260 /// [`sd_journal_get_catalog_for_message_id()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_catalog.html#)).
261 ///
262 /// # Return Values
263 /// - Ok(String): message catalogue
264 /// - Err(Error::UTF8Error): UTF-8 decoding error occured
265 /// - Err(Error::SDError): sd-journal returned an error code
266 pub fn get_catalog_for_message_id(id: ID128) -> Result<String, Error> {
267 let mut data: *mut c_char = ptr::null_mut();
268 let result =
269 unsafe { ffi::sd_journal_get_catalog_for_message_id(id.into_ffi(), &mut data) };
270 if result < 0 {
271 return Err(Error::SDError(result));
272 }
273 let catalog = unsafe { CStr::from_ptr(data) };
274 let catalog = match catalog.to_str() {
275 Err(error) => {
276 unsafe { libc::free(data as *mut c_void) };
277 Err(Error::UTF8Error(error))?
278 },
279 Ok(value) => value.to_owned()
280 };
281 unsafe { libc::free(data as *mut c_void) };
282 Ok(catalog)
283 }
284
285 /// Open a journal for read access (implements
286 /// [`sd_journal_open()`](https://www.freedesktop.org/software/systemd/man/sd_journal_open.html#)).
287 ///
288 /// Opens the log journal for reading. It will find all journal files
289 /// and interleave them automatically when reading.
290 ///
291 /// # Examples
292 /// ```
293 /// use sd_journal::*;
294 /// Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap();
295 /// Journal::open(FileFlags::LocalOnly, UserFlags::CurrentUserOnly).unwrap();
296 /// ```
297 ///
298 /// # Return values
299 /// - Ok(Journal): initialized journal
300 /// - Err(Error::SDError): sd-journal returned an error code
301 pub fn open(file_flags: FileFlags, user_flags: UserFlags) -> Result<Journal, Error> {
302 let mut pointer = ptr::null_mut() as *mut sd_sys::journal::sd_journal;
303 let flags = file_flags as c_int | user_flags as c_int;
304 let result = unsafe { ffi::sd_journal_open(&mut pointer, flags) };
305 if result < 0 {
306 return Err(Error::SDError(result));
307 }
308 Ok(Journal { ffi: pointer })
309 }
310
311 /// Open the journal for reading records in a specific namespace (implements
312 /// [`sd_journal_open_namespace()`](https://www.freedesktop.org/software/systemd/man/sd_journal_open.html#)).
313 ///
314 /// Opens the log journal for reading on a selected
315 /// [namespace](https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html#Journal%20Namespaces)
316 /// only. It will find all journal files and interleave them automatically
317 /// when reading. This method does not support the
318 /// `SD_JOURNAL_ALL_NAMESPACES` flag. If you want to open all namespaces,
319 /// see `open_all_namespaces()`.
320 ///
321 /// # Return values
322 /// - Ok(Journal): initialized journal
323 /// - Err(Error::SDError): sd-journal returned an error code
324 /// - Err(Error::NullError): the namespace contained a 0-byte
325 pub fn open_namespace<T: Into<Vec<u8>>>(namespace: T,
326 namespace_flags: NamespaceFlags,
327 file_flags: FileFlags,
328 user_flags: UserFlags)
329 -> Result<Journal, Error> {
330 let c_namespace = CString::new(namespace).map_err(Error::NullError)?;
331 let mut pointer = ptr::null_mut() as *mut ffi::sd_journal;
332 let flags = file_flags as c_int | user_flags as c_int | namespace_flags as c_int;
333 let result =
334 unsafe { ffi::sd_journal_open_namespace(&mut pointer, c_namespace.as_ptr(), flags) };
335 if result < 0 {
336 return Err(Error::SDError(result));
337 }
338 let journal = Journal { ffi: pointer };
339 Ok(journal)
340 }
341
342 /// Open the journal for read access including all available namespaces
343 /// (implements
344 /// [`sd_journal_open_namespace()`](https://www.freedesktop.org/software/systemd/man/sd_journal_open.html#)
345 /// with flag `SD_JOURNAL_ALL_NAMESPACES` set).
346 ///
347 /// Opens the log journal for reading for all
348 /// [namespaces](https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html#Journal%20Namespaces).
349 /// It will find all journal files automatically and interleave
350 /// them automatically when reading.
351 ///
352 /// # Return values
353 /// - Ok(Journal): initialized journal
354 /// - Err(Error::SDError): sd-journal returned an error code
355 pub fn open_all_namespaces(file_flags: FileFlags,
356 user_flags: UserFlags)
357 -> Result<Journal, Error> {
358 let mut pointer = ptr::null_mut() as *mut ffi::sd_journal;
359 let flags = file_flags as c_int | user_flags as c_int | ffi::SD_JOURNAL_ALL_NAMESPACES;
360 let result =
361 unsafe { ffi::sd_journal_open_namespace(&mut pointer, std::ptr::null(), flags) };
362 if result < 0 {
363 return Err(Error::SDError(result));
364 }
365 let journal = Journal { ffi: pointer };
366 Ok(journal)
367 }
368
369 /// Open the journal located at a specific path (implements
370 /// [`sd_journal_open_directory()`](https://www.freedesktop.org/software/systemd/man/sd_journal_open.html#)).
371 ///
372 /// Open the journal located at a specific path: takes an *absolute*
373 /// directory path as argument. All journal files in this directory
374 /// will be opened and interleaved automatically.
375 ///
376 /// # Examples
377 /// ```
378 /// use sd_journal::*;
379 /// use std::path::{Path, PathBuf};
380 /// // open the system journal by pointing to root with path flags set to
381 /// // PathToOSRoot
382 /// Journal::open_directory("/", PathFlags::PathToOSRoot, UserFlags::AllUsers).unwrap();
383 /// Journal::open_directory(Path::new("/"), PathFlags::PathToOSRoot, UserFlags::AllUsers).unwrap();
384 /// Journal::open_directory(PathBuf::from("/"),
385 /// PathFlags::PathToOSRoot,
386 /// UserFlags::AllUsers).unwrap();
387 /// // open test data included in a project located in a folder "test-data" in the
388 /// // project root
389 /// let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
390 /// test_data.push("test-data/");
391 /// println!("looking for test data in folder {}", test_data.display());
392 /// Journal::open_directory(test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap();
393 /// ```
394 /// # Return values
395 /// - Ok(Journal): initialized journal
396 /// - Err(Error::SDError): sd-journal returned an error code
397 /// - Err(Error::NullError): the path contains a 0-byte
398 pub fn open_directory<P: Into<PathBuf>>(path: P,
399 path_flags: PathFlags,
400 user_flags: UserFlags)
401 -> Result<Journal, Error> {
402 #[cfg(unix)]
403 use std::os::unix::ffi::OsStringExt;
404 let c_path =
405 CString::new(path.into().into_os_string().into_vec()).map_err(Error::NullError)?;
406 let mut pointer = ptr::null_mut() as *mut ffi::sd_journal;
407 let flags = path_flags as c_int | user_flags as c_int;
408 let result =
409 unsafe { ffi::sd_journal_open_directory(&mut pointer, c_path.as_ptr(), flags) };
410 if result < 0 {
411 return Err(Error::SDError(result));
412 }
413 let journal = Journal { ffi: pointer };
414 Ok(journal)
415 }
416
417 /// Open the journal stored in a list of files (implements
418 /// [`sd_journal_open_files()`](https://www.freedesktop.org/software/systemd/man/sd_journal_open.html#)).
419 ///
420 /// # Examples
421 /// ```
422 /// use sd_journal::*;
423 /// use std::path::PathBuf;
424 /// // to open the curreńt system.journal file in the default location for
425 /// // journals: /var/log/journal/<MACHINE-ID>/system.journal
426 /// let machine_id = sd_id128::ID128::machine_id().unwrap()
427 /// .to_string_sd()
428 /// .unwrap();
429 /// let mut sdjournal_path = PathBuf::from("/var/log/journal/");
430 /// sdjournal_path.push(&machine_id);
431 /// sdjournal_path.push("system.journal");
432 /// println!("looking for sd-journal in {}", sdjournal_path.display());
433 /// Journal::open_files([sdjournal_path]).unwrap();
434 /// // to open test data included in a project located in a folder
435 /// // "test-data" in the project root
436 /// let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
437 /// test_data.push("test-data/test.journal");
438 /// println!("looking for test data in folder {}", test_data.display());
439 /// Journal::open_files([test_data]).unwrap();
440 /// ```
441 ///
442 /// # Return values
443 /// - Ok(Journal): initialized journal
444 /// - Err(Error::SDError): sd-journal returned an error code
445 /// - Err(Error::NullError): a file path contains a 0-byte
446 pub fn open_files<A: Into<Vec<P>>, P: Into<PathBuf>>(files: A) -> Result<Journal, Error> {
447 #[cfg(unix)]
448 use std::os::unix::ffi::OsStringExt;
449 let files: Vec<P> = files.into();
450 let mut c_files_vec: Vec<CString> = Vec::with_capacity(files.len());
451 // convert Vec<PathBuf> to Vec<CString>
452 for file in files {
453 let pb_file: PathBuf = file.into();
454 let os_file = pb_file.into_os_string();
455 c_files_vec.push(CString::new(os_file.into_vec()).map_err(Error::NullError)?);
456 }
457 // convert Vec<CString> to Vec<*const c_char>
458 let mut ptr_vec: Vec<*const c_char> =
459 c_files_vec.iter().map(|file| file.as_ptr()).collect();
460 ptr_vec.push(0 as *const c_char);
461 let mut pointer = std::ptr::null_mut() as *mut ffi::sd_journal;
462 let flags: c_int = 0;
463 let result = unsafe { ffi::sd_journal_open_files(&mut pointer, ptr_vec.as_ptr(), flags) };
464 if result < 0 {
465 return Err(Error::SDError(result));
466 }
467 let journal = Journal { ffi: pointer };
468 Ok(journal)
469 }
470
471 /// Advance the read pointer of the journal by one entry (implements
472 /// [`sd_journal_next()`](https://www.freedesktop.org/software/systemd/man/sd_journal_next.html#)).
473 ///
474 /// This method wraps the sd-journal native function. There is also a
475 /// rustified iterator [`CursorIterator`](CursorIterator) avalaible via
476 /// the `iter()` method or the `IntoIterator` trait implemented for
477 /// `&Journal`.
478 /// Although the official documentation doesn't mention any error handling,
479 /// libsystemd may return an error on performing next().
480 ///
481 /// # Examples
482 /// ```
483 /// # use sd_journal::*;
484 /// # use std::path::PathBuf;
485 /// # let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
486 /// # test_data.push("test-data/");
487 /// # println!("looking for test data in folder {}", test_data.display());
488 /// # let journal = Journal::open_directory(&test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap();
489 /// // loop over a journal & print it's messages
490 /// while let Ok(CursorMovement::Done) = journal.next() {
491 /// // do something on each cursor, e.g. print the MESSAGE
492 /// println!("{}", journal.get_data("MESSAGE").unwrap());
493 /// }
494 /// ```
495 ///
496 /// # Return values
497 /// - Ok(CursorMovement::Done): full success
498 /// - Ok(CursorMovement::EoF): no movement was executed, since the cursor is
499 /// already placed at EoF.
500 /// - Err(Error::SDError): sd-journal returned an error code
501 pub fn next(&self) -> Result<CursorMovement, Error> {
502 let result = unsafe { ffi::sd_journal_next(self.ffi) };
503 if result < 0 {
504 return Err(Error::SDError(result));
505 }
506 if result == 0 {
507 return Ok(CursorMovement::EoF);
508 }
509 Ok(CursorMovement::Done)
510 }
511
512 /// Returns an iterator on the journal.
513 ///
514 /// [CursorIterator](CursorIterator) is the rustified version of the
515 /// `next()` method. Since `next()` may fail, the advanced cursor may be
516 /// invalid. For such reason, the iterator returns a Result<Cursor, _> on
517 /// each `next()` and thus the cursor must be unwrapped first.
518 ///
519 /// # Examples
520 /// ```
521 /// # use sd_journal::*;
522 /// # use std::path::PathBuf;
523 /// # let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
524 /// # test_data.push("test-data/");
525 /// # println!("looking for test data in folder {}", test_data.display());
526 /// # let journal = Journal::open_directory(&test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap();
527 /// // loop over a journal & print it's messages
528 /// for cursor in journal.iter() {
529 /// match cursor {
530 /// Err(_) => break,
531 /// Ok(cursor) => println!("{}", cursor.get_data("MESSAGE").unwrap())
532 /// }
533 /// }
534 /// // ...
535 /// # journal.seek_head().unwrap();
536 /// let cursor = journal.iter_reverse().next().unwrap().unwrap();
537 /// // the following two lines are actually return the same value
538 /// let m1 = cursor.get_data("MESSAGE").unwrap();
539 /// let m2 = journal.get_data("MESSAGE").unwrap();
540 /// assert_eq!(m1, m2);
541 /// ```
542 pub fn iter(&self) -> CursorIterator {
543 CursorIterator { journal: &self }
544 }
545
546 /// Set back the read pointer of the journal by one entry (implements
547 /// [`sd_journal_previous()`](https://www.freedesktop.org/software/systemd/man/sd_journal_next.html#)).
548 ///
549 /// This method wraps the sd-journal native function. There is also a
550 /// rustified iterator [`CursorReverseIterator`](CursorReverseIterator)
551 /// avalaible via the [`iter_reverse()`](Journal::iter_reverse) method.
552 ///
553 /// # Examples
554 /// ```
555 /// # use sd_journal::*;
556 /// # use std::path::PathBuf;
557 /// # let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
558 /// # test_data.push("test-data/");
559 /// # println!("looking for test data in folder {}", test_data.display());
560 /// # let journal = Journal::open_directory(&test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap();
561 /// journal.seek_tail().unwrap();
562 /// // loop over a journal in reverse order & print it's messages
563 /// while let Ok(CursorMovement::Done) = journal.previous() {
564 /// // do something on each cursor, e.g. print the MESSAGE
565 /// println!("{}", journal.get_data("MESSAGE").unwrap());
566 /// }
567 /// ```
568 ///
569 /// # Return values
570 /// - Ok(CursorMovement::Done): full success
571 /// - Ok(CursorMovement::EoF): no movement was executed, since the cursor is
572 /// already placed at EoF.
573 /// - Err(Error::SDError): sd-journal returned an error code
574 pub fn previous(&self) -> Result<CursorMovement, Error> {
575 let result = unsafe { ffi::sd_journal_previous(self.ffi) };
576 if result < 0 {
577 return Err(Error::SDError(result));
578 }
579 if result == 0 {
580 return Ok(CursorMovement::EoF);
581 }
582 Ok(CursorMovement::Done)
583 }
584
585 /// Returns an iterator on the journal that runs in reverse order.
586 ///
587 /// [CursorReverseIterator](CursorReverseIterator) is the rustified version
588 /// of the `previous()` method. Since `previous()` may fail, the advanced
589 /// cursor may be invalid. For such reason, the iterator returns a
590 /// Result<Cursor, _> on each `next()` and thus the cursor must be
591 /// unwrapped first.
592 ///
593 /// # Examples
594 /// ```
595 /// # use sd_journal::*;
596 /// # use std::path::PathBuf;
597 /// # let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
598 /// # test_data.push("test-data/");
599 /// # println!("looking for test data in folder {}", test_data.display());
600 /// # let journal = Journal::open_directory(&test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap();
601 /// journal.seek_tail().unwrap();
602 /// // loop over a journal & print it's messages
603 /// for cursor in journal.iter_reverse() {
604 /// match cursor {
605 /// Err(_) => break,
606 /// Ok(cursor) => println!("{}", cursor.get_data("MESSAGE").unwrap())
607 /// }
608 /// }
609 /// // ...
610 /// # journal.seek_tail().unwrap();
611 /// let cursor = journal.iter_reverse().next().unwrap().unwrap();
612 /// // the following two lines are actually return the same value
613 /// let m1 = cursor.get_data("MESSAGE").unwrap();
614 /// let m2 = journal.get_data("MESSAGE").unwrap();
615 /// assert_eq!(m1, m2);
616 /// ```
617 pub fn iter_reverse(&self) -> CursorReverseIterator {
618 CursorReverseIterator { journal: &self }
619 }
620
621 /// Advance the read pointer of the journal by multiple entries (implements
622 /// [`sd_journal_next_skip()`](https://www.freedesktop.org/software/systemd/man/sd_journal_next.html#)).
623 ///
624 /// # Return values
625 /// - Ok(CursorMovement::Done): full success
626 /// - Ok(CursorMovement::Limited(actual)): the movement was executed but
627 /// limited by the EoF of the journal. The actual movement is given in the
628 /// parameter.
629 /// - Ok(CursorMovement::EoF): no movement was executed, since the cursor is
630 /// already placed at EoF.
631 /// - Err(Error::SDError): sd-journal returned an error code
632 pub fn next_skip(&self, skip: c_int) -> Result<CursorMovement, Error> {
633 if skip < 0 {
634 return Err(Error::RangeError);
635 }
636 let result = unsafe { ffi::sd_journal_next_skip(self.ffi, skip as u64) };
637 if result < 0 {
638 return Err(Error::SDError(result));
639 }
640 if result == 0 {
641 return Ok(CursorMovement::EoF);
642 }
643 if result < skip {
644 return Ok(CursorMovement::Limited(result));
645 }
646 Ok(CursorMovement::Done)
647 }
648
649 /// Set back the read pointer by multiple entries at once (implements
650 /// [`sd_journal_previous_skip()`](https://www.freedesktop.org/software/systemd/man/sd_journal_next.html#)).
651 ///
652 /// - Ok(CursorMovement::Done): full success
653 /// - Ok(CursorMovement::Limited(actual)): the movement was executed but
654 /// limited by the EoF of the journal. The actual movement is given in the
655 /// parameter.
656 /// - Ok(CursorMovement::EoF): no movement was executed, since the cursor is
657 /// already placed at EoF.
658 /// - Err(Error::SDError): sd-journal returned an error code
659 pub fn previous_skip(&self, skip: c_int) -> Result<CursorMovement, Error> {
660 if skip < 0 {
661 return Err(Error::RangeError);
662 }
663 let result = unsafe { ffi::sd_journal_previous_skip(self.ffi, skip as u64) };
664 if result < 0 {
665 return Err(Error::SDError(result));
666 }
667 if result == 0 {
668 return Ok(CursorMovement::EoF);
669 }
670 if result < skip {
671 return Ok(CursorMovement::Limited(result));
672 }
673 Ok(CursorMovement::Done)
674 }
675
676 /// **UNSTABLE API** Seek to the head of the journal (implements
677 /// [`sd_journal_seek_head`](https://www.freedesktop.org/software/systemd/man/sd_journal_seek_head.html#)).
678 ///
679 /// Seek to the beginning of the journal, i.e. to the position **before**
680 /// the oldest available entry. Be aware that after a seek_head() the
681 /// journal cursor does not point to a valid entry. One must perform a
682 /// cursor movement before being able to retrieve data.
683 ///
684 /// # Examples
685 /// ```
686 /// # use sd_journal::*;
687 /// # let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap();
688 /// journal.seek_head().unwrap();
689 /// // seek_head() should be followed by a next() before any previous() --> issues
690 /// journal.next().unwrap();
691 /// // previous() should hit EoF
692 /// assert_eq!(journal.previous(), Ok(CursorMovement::EoF));
693 /// ```
694 ///
695 /// # libsystemd Issues
696 /// While `seek_head()` is supposed to move the cursor before the first
697 /// available journal entry, libsystemd may still successfully perform
698 /// `previous()` cursor movements for multiple times. All these unexpected
699 /// entries will report a full set of data and may appear fully valid
700 /// although be assured they are not. An
701 /// [error](https://github.com/systemd/systemd/issues/17662) has been
702 /// reported to the systemd project. The issue can be avoided if a `next()`
703 /// operation is executed immediately after `seek_head()` before issuing any
704 /// `previous()`. If done, `previous()`thereafter will correctly report EoF.
705 ///
706 /// # Stability
707 /// Due to the issue described above, this method may be changed in future
708 /// to include a `next()` in future releases and thus position the cursor
709 /// rather **on** the first available entry rather than positioning the
710 /// cursor **before** the oldest available entry. Such change is likely and
711 /// will be based on the issue handling of the systemd team as well as on
712 /// user feedback.
713 ///
714 /// # Return values
715 /// - Ok(()): success
716 /// - Err(Error::SDError): sd-journal returned an error code
717 #[cfg(feature = "experimental")]
718 pub fn seek_head(&self) -> Result<(), Error> {
719 let result = unsafe { ffi::sd_journal_seek_head(self.ffi) };
720 if result < 0 {
721 return Err(Error::SDError(result));
722 }
723 Ok(())
724 }
725
726 /// **UNSTABLE API** Seek to the tail of the journal (implements
727 /// [`sd_journal_seek_tail`](https://www.freedesktop.org/software/systemd/man/sd_journal_seek_head.html#)).
728 ///
729 /// Seek to the end of the journal, i.e. the position after the most recent
730 /// available entry. Be aware that after a seek_head() the
731 /// journal cursor does not point to a valid entry. One must perform a
732 /// cursor movement before being able to retrieve data.
733 ///
734 /// # Examples
735 /// ```
736 /// # use sd_journal::*;
737 /// # let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap();
738 /// journal.seek_tail().unwrap();
739 /// // seek_head() should be followed by a previous() before any next() --> issues
740 /// journal.previous().unwrap();
741 /// // next() should hit EoF
742 /// assert_eq!(journal.next(), Ok(CursorMovement::EoF));
743 /// ```
744 ///
745 /// # libsystemd Issues
746 /// While `seek_head()` is supposed to move the cursor before the first
747 /// available journal entry, libsystemd may still successfully perform
748 /// `previous()` cursor movements for multiple times. All these unexpected
749 /// entries will report a full set of data and may appear fully valid
750 /// although be assured they are not. An
751 /// [error](https://github.com/systemd/systemd/issues/17662) has been
752 /// reported to the systemd project. The issue can be avoided if a `next()`
753 /// operation is executed immediately after `seek_head()` before issuing any
754 /// `previous()`. If done, `previous()`thereafter will correctly report EoF.
755 ///
756 /// # Stability
757 /// Due to the issue described above, this method may be changed in future
758 /// to include a `next()` in future releases and thus position the cursor
759 /// rather **on** the first available entry rather than positioning the
760 /// cursor **before** the oldest available entry. Such change is likely and
761 /// will be based on the issue handling of the systemd team as well as on
762 /// user feedback.
763 ///
764 /// # Return values
765 /// - Ok(()): success
766 /// - Err(Error::SDError): sd-journal returned an error code
767 #[cfg(feature = "experimental")]
768 pub fn seek_tail(&self) -> Result<(), Error> {
769 let result = unsafe { ffi::sd_journal_seek_tail(self.ffi) };
770 if result < 0 {
771 return Err(Error::SDError(result));
772 }
773 Ok(())
774 }
775
776 /// **UNSTABLE API** Seek to a monotonic timestamp of a certain boot id
777 /// (implements [`sd_journal_seek_monotonic_usec()`](https://www.freedesktop.org/software/systemd/man/sd_journal_seek_head.html#)).
778 ///
779 /// Seek to a position with the specified monotonic timestamp, i.e.
780 /// `clockMonotonic'. Since monotonic time restarts on every reboot a
781 /// boot ID needs to be specified as well.
782 ///
783 /// # Examples
784 /// ```
785 /// use sd_id128::*;
786 /// use sd_journal::*;
787 /// let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap();
788 /// // get the current system boot id
789 /// let boot_id = ID128::boot_id().unwrap();
790 /// // get the monotonic clock range of today
791 /// let (from, _) = journal.get_monotonic_cutoff(boot_id.clone()).unwrap();
792 /// // seek to the start of journal for the current boot
793 /// journal.seek_monotonic(boot_id.clone(), from).unwrap();
794 /// journal.previous().unwrap();
795 /// // do something with the first cursor of today...
796 /// ```
797 ///
798 /// # libsystemd Issues
799 /// According to the specification of `sd_journal_seek_monotonic_usec()`:
800 ///
801 /// > If no entry exists that matches exactly the specified seek address,
802 /// the next closest is sought to.
803 ///
804 /// Unfortunately libsystemd fails to comply if the monotonic timestamp
805 /// provided points to a position outside the journal range. Lets assume the
806 /// first valid log entry for a certain boot id exists at timestamp 500usec.
807 /// Seeking to anything beyond 500usec will work as expected while seeking
808 /// to anything before 500usec followed by a next() won't position the
809 /// cursor at the first entry of that boot id but rather position the cursor
810 /// at some random position. An
811 /// [issue](https://github.com/systemd/systemd/issues/17763) has been
812 /// reported to the systemd project.
813 ///
814 /// # Stability
815 /// This method expects `chrono::Duration` in the same way as
816 /// `get_monotonic()` for the same reasons: `get_realtime()` refers to
817 /// `chrono::NaiveDateTime`. In future releases this method may be changed
818 /// to microsenconds (u128) or std::time::Duration. Such change is
819 /// reasonable likely and will be made based on user feedback.
820 ///
821 /// # Return values
822 /// - Ok(())
823 /// - Err(Error::SDError): sd-journal returned an error code
824 /// - Err(Error::TimeStampOutOfRange): the `clock_monotonic` time stamp
825 /// either reflects a negative duration or the duration exceeds i64
826 /// microseconds
827 #[cfg(feature = "td_chrono")]
828 #[cfg(feature = "experimental")]
829 pub fn seek_monotonic(&self, boot_id: ID128, clock_monotonic: Duration) -> Result<(), Error> {
830 // let usec = clock_monotonic.to_std()
831 // .map_err(|_| Error::TimeStampOutOfRange)?
832 // .as_micros();
833 // let usec = u64::try_from(usec).map_err(|_| Error::TimeStampOutOfRange);
834 let usec: u64 = match clock_monotonic.num_microseconds() {
835 None => Err(Error::TimeStampOutOfRange)?,
836 Some(t) if t < 0 => Err(Error::TimeStampOutOfRange)?,
837 Some(t) => t as u64
838 };
839
840 let ffi_boot_id = boot_id.into_ffi();
841 let result = unsafe { ffi::sd_journal_seek_monotonic_usec(self.ffi, ffi_boot_id, usec) };
842 if result < 0 {
843 return Err(Error::SDError(result));
844 }
845 Ok(())
846 }
847
848 /// **UNSTABLE API** Seek to realtime timestamp (implements
849 /// [`sd_journal_seek_realtime_usec()`](https://www.freedesktop.org/software/systemd/man/sd_journal_seek_head.html#)).
850 ///
851 /// Seeks to a position with the specified realtime (wallclock) timestamp,
852 /// i.e. 'clockRealtime'. Note that the realtime clock is not necessarily
853 /// monotonic. If a realtime timestamp is ambiguous, it is not defined which
854 /// position is sought to.
855 ///
856 /// # Stability
857 /// Currently the function expects a chrono::NaiveDateTime. In future
858 /// releases this method may be changed to expect microseconds (u128) or
859 /// std::time::Duration although this is very unlikely. Changes will be
860 /// made based on user feedback.
861 ///
862 /// # Return values
863 /// - Ok(())
864 /// - Err(Error::SDError): sd-journal returned an error code
865 #[cfg(feature = "td_chrono")]
866 #[cfg(feature = "experimental")]
867 pub fn seek_realtime(&self, clock_realtime: NaiveDateTime) -> Result<(), Error> {
868 let usec = clock_realtime.timestamp_subsec_micros() as u64
869 + clock_realtime.timestamp() as u64 * 1_000_000;
870 let result = unsafe { ffi::sd_journal_seek_realtime_usec(self.ffi, usec) };
871 if result < 0 {
872 return Err(Error::SDError(result));
873 }
874 Ok(())
875 }
876
877 /// **UNSTABLE API** Seeks the journal to the position of the cursor
878 /// provided (implements [`sd_journal_seek_cursor()`](https://www.freedesktop.org/software/systemd/man/sd_journal_seek_head.html#)).
879 ///
880 /// # Stability
881 /// See [`get_cursor_id()`](get_cursor_id) for reasons why there is a small
882 /// chance this method may be adjusted in future releases.
883 ///
884 /// # Return Values
885 /// - Ok(())
886 /// - Err(Error::SDError): sd-journal returned an error code
887 /// - Err(Error::NullError): a file path contains a 0-byte
888 #[cfg(feature = "experimental")]
889 pub fn seek_cursor_id(&self, cursor_id: String) -> Result<(), Error> {
890 let c_cursor = CString::new(cursor_id).map_err(Error::NullError)?;
891 let result = unsafe { ffi::sd_journal_seek_cursor(self.ffi, c_cursor.as_ptr()) };
892 if result < 0 {
893 return Err(Error::SDError(result));
894 }
895 Ok(())
896 }
897
898 /// Adds a match to filter journal entries (implements
899 /// [`sd_journal_add_match()`](https://www.freedesktop.org/software/systemd/man/sd_journal_add_match.html#)).
900 ///
901 /// Adds a filter on a field that will be applied on cursor movement
902 /// thereafter. The filter must follow the format "FIELDNAME=fieldvalue".
903 ///
904 /// # Examples
905 /// ```
906 /// # use sd_journal::*;
907 /// # let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap();
908 /// journal.add_match("MESSAGE=Hello World!").unwrap();
909 /// # assert_eq!(journal.next().unwrap(), CursorMovement::Done);
910 /// while let Ok(CursorMovement::Done) = journal.next() {
911 /// // do something on the journal entries
912 /// }
913 /// ```
914 ///
915 /// # Return Values
916 /// - Ok(()): done
917 /// - Err(Error::SDError): sd-journal returned an error code
918 pub fn add_match<T: AsRef<[c_uchar]>>(&self, filter: T) -> Result<(), Error> {
919 let filter = filter.as_ref();
920 let result = unsafe {
921 ffi::sd_journal_add_match(self.ffi, filter.as_ptr() as *const c_void, filter.len())
922 };
923 if result < 0 {
924 return Err(Error::SDError(result));
925 }
926 Ok(())
927 }
928
929 /// Adds a disjuntion marker to match definitions (implements
930 /// [`sd_jounal_add_disjunction()`](https://www.freedesktop.org/software/systemd/man/sd_journal_add_match.html#).)
931 ///
932 /// # Return Values
933 /// - Ok(()): done
934 /// - Err(Error::SDError): sd-journal returned an error code
935 pub fn add_disjunction(&self) -> Result<(), Error> {
936 let result = unsafe { ffi::sd_journal_add_disjunction(self.ffi) };
937 if result < 0 {
938 return Err(Error::SDError(result));
939 }
940 Ok(())
941 }
942
943 /// Adds a conjuntion marker to match definitions (implements
944 /// [`sd_jounal_add_conjunction()`](https://www.freedesktop.org/software/systemd/man/sd_journal_add_match.html#).)
945 ///
946 /// # Return Values
947 /// - Ok(()): done
948 /// - Err(Error::SDError): sd-journal returned an error code
949 pub fn add_conjunction(&self) -> Result<(), Error> {
950 let result = unsafe { ffi::sd_journal_add_conjunction(self.ffi) };
951 if result < 0 {
952 return Err(Error::SDError(result));
953 }
954 Ok(())
955 }
956
957 /// Flushes the match definition (implements
958 /// [`sd_journal_flush_matches()`](https://www.freedesktop.org/software/systemd/man/sd_journal_add_match.html#))
959 pub fn flush_matches(&self) {
960 unsafe { ffi::sd_journal_flush_matches(self.ffi) }
961 }
962
963 /// **UNSTABLE API** Determines the timestamps of the first and last entry
964 /// in journal (implements [`sd_journal_get_cutoff_realtime_usec`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_cutoff_realtime_usec.html#)).
965 ///
966 /// # Stability
967 /// Currently the function returns a chrono::NaiveDateTime calculated from
968 /// the microseconds since EPOCH returned by the wrapped libsystemd
969 /// function. In future releases this method may be changed to return
970 /// microseconds (u128) or std::time::Duration although this is very
971 /// unlikely. Changes will be made based on user feedback.
972 ///
973 /// # Return Values:
974 /// - Ok((NaiveDateTime, NaiveDateTime)): (from, to) timestamps of the
975 /// journal
976 /// - Err(Error::SDError): sd-journal returned an error code
977 #[cfg(feature = "td_chrono")]
978 #[cfg(feature = "experimental")]
979 pub fn get_realtime_cutoff(&self) -> Result<(NaiveDateTime, NaiveDateTime), Error> {
980 let mut from_usec: u64 = 0;
981 let mut to_usec: u64 = 0;
982 let result = unsafe {
983 ffi::sd_journal_get_cutoff_realtime_usec(self.ffi, &mut from_usec, &mut to_usec)
984 };
985 if result < 0 {
986 return Err(Error::SDError(result));
987 }
988 let from = NaiveDateTime::from_timestamp((from_usec / 1_000_000) as i64,
989 ((from_usec % 1_000_000) * 1_000) as u32);
990 let to = NaiveDateTime::from_timestamp((to_usec / 1_000_000) as i64,
991 ((to_usec % 1_000_000) * 1_000) as u32);
992 Ok((from, to))
993 }
994
995 /// **UNSTABLE API** Determines the duration since boot of the first and
996 /// last entry in journal for a specific boot id (implements
997 /// [`sd_journal_get_cutoff_realtime_usec()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_cutoff_monotonic_usec.html#)).
998 ///
999 /// # Stability
1000 /// Currently the function returns a chrono::Duration calculated from the
1001 /// microseconds since boot returned by the wrapped libsystemd function. The
1002 /// choice for chrono::Duration has been made based on the return value for
1003 /// `get_realtime()`. In future releases this method may be changed to
1004 /// microsenconds (u128) or std::time::Duration. Such change is reasonable
1005 /// likely and will be made based on user feedback.
1006 ///
1007 /// # Return Values
1008 /// - Ok((Duration, Duration)): (from, to) respective duration since boot
1009 /// - Err(Error::SDError): sd-journal returned an error code
1010 #[cfg(feature = "td_chrono")]
1011 #[cfg(feature = "experimental")]
1012 pub fn get_monotonic_cutoff(&self, boot_id: ID128) -> Result<(Duration, Duration), Error> {
1013 let mut from_usec: u64 = 0;
1014 let mut to_usec: u64 = 0;
1015 let result = unsafe {
1016 ffi::sd_journal_get_cutoff_monotonic_usec(self.ffi,
1017 boot_id.into_ffi(),
1018 &mut from_usec,
1019 &mut to_usec)
1020 };
1021 if result < 0 {
1022 return Err(Error::SDError(result));
1023 }
1024 let from = Duration::seconds((from_usec / 1_000_000) as i64)
1025 + Duration::microseconds((from_usec % 1_000_000) as i64);
1026 let to = Duration::seconds((to_usec / 1_000_000) as i64)
1027 + Duration::microseconds((to_usec % 1_000_000) as i64);
1028 Ok((from, to))
1029 }
1030
1031 /// Sets the data treshold limit for certain methods returning data fields
1032 /// (implements [`sd_journal_set_data_threshold()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_data.html#)).
1033 ///
1034 /// # Return Values
1035 /// - Ok(())
1036 /// - Err(Error::SDError): sd-journal returned an error code
1037 pub fn set_data_treshold(&self, size: size_t) -> Result<(), Error> {
1038 let result = unsafe { ffi::sd_journal_set_data_threshold(self.ffi, size) };
1039 if result < 0 {
1040 return Err(Error::SDError(result));
1041 }
1042 Ok(())
1043 }
1044
1045 /// Gets the currently applied data treshold (implements
1046 /// [`sd_journal_get_data_threshold()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_data.html#)).
1047 ///
1048 /// # Return Values
1049 /// - Ok(size_t)
1050 /// - Err(Error::SDError): sd-journal returned an error code
1051 pub fn get_data_treshold(&self) -> Result<size_t, Error> {
1052 let mut size: size_t = 0;
1053 let result = unsafe { ffi::sd_journal_get_data_threshold(self.ffi, &mut size) };
1054 if result < 0 {
1055 return Err(Error::SDError(result));
1056 }
1057 Ok(size)
1058 }
1059
1060 /// Enumerate the field names of the journal (implements
1061 /// [`sd_journal_enumerate_fields()`](https://www.freedesktop.org/software/systemd/man/sd_journal_enumerate_fields.html#)).
1062 ///
1063 /// This method follows the principle of libsystemd to call this method
1064 /// repeatedly until you reach EoF. See [`iter_fields`](iter_fields)
1065 /// for a rustified iterator over fields.
1066 ///
1067 /// # Return Values
1068 /// - Ok(Enumeration::EoF): no more fields
1069 /// - Ok(Enumeration::Value(String)): field name
1070 /// - Err(Error::SDError): sd-journal returned an error code
1071 pub fn enumerate_field_names(&self) -> Result<Enumeration<String>, Error> {
1072 let mut field: *const c_char = ptr::null();
1073 let result = unsafe { ffi::sd_journal_enumerate_fields(self.ffi, &mut field) };
1074 if result < 0 {
1075 return Err(Error::SDError(result));
1076 }
1077 if result == 0 {
1078 return Ok(Enumeration::EoF);
1079 }
1080 Ok(Enumeration::Value(unsafe {
1081 CStr::from_ptr(field).to_str()
1082 .map_err(Error::UTF8Error)?
1083 .to_owned()
1084 }))
1085 }
1086
1087 /// Restart field enumeration (implements
1088 /// [`sd_journal_restart_fields()`](https://www.freedesktop.org/software/systemd/man/sd_journal_enumerate_fields.html#)).
1089 pub fn restart_field_name_enumeration(&self) {
1090 unsafe { ffi::sd_journal_restart_fields(self.ffi) }
1091 }
1092
1093 /// Get an iterator of the field names of the journal.
1094 ///
1095 /// This is the rustified version of `enumerate_field_names()`.
1096 ///
1097 /// # Examples
1098 /// ```
1099 /// # use sd_journal::*;
1100 /// # let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap();
1101 /// // loop over field names and print them
1102 /// for fieldname in journal.iter_field_names() {
1103 /// println!("{}", fieldname.unwrap());
1104 /// }
1105 /// ```
1106 pub fn iter_field_names<'a>(&'a self) -> FieldNames<'a> {
1107 FieldNames { journal: self }
1108 }
1109
1110 /// Returns a read only file descriptor to be used in polling the journal
1111 /// (implements [`sd_journal_get_fd()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_fd.html#)).
1112 ///
1113 /// # Return Values
1114 /// - Ok(RawFd)
1115 /// - Err(Error::SDError): sd-journal returned an error code
1116 pub fn get_fd(&self) -> Result<RawFd, Error> {
1117 let result = unsafe { ffi::sd_journal_get_fd(self.ffi) };
1118 if result < 0 {
1119 return Err(Error::SDError(result));
1120 }
1121 Ok(result)
1122 }
1123
1124 /// Returns events to be used in polling the journal on the file descriptor
1125 /// (implements [`sd_journal_get_events()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_fd.html#)).
1126 ///
1127 /// # Return Values
1128 /// - Ok(c_int): events to be used in polling the file descriptor
1129 /// - Err(Error::SDError): sd-journal returned an error code
1130 pub fn get_events(&self) -> Result<c_int, Error> {
1131 let result = unsafe { ffi::sd_journal_get_fd(self.ffi) };
1132 if result < 0 {
1133 return Err(Error::SDError(result));
1134 }
1135 Ok(result)
1136 }
1137
1138 /// Returns the timeout to be used in polling the journal on the file
1139 /// descriptor (implements
1140 /// [`sd_journal_get_timeout()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_fd.html#)).
1141 ///
1142 /// # Return Values
1143 /// - Ok(u64): timeout
1144 /// - Err([Error::SDError](Error)): sd-journal returned an error code
1145 pub fn get_timeout(&self) -> Result<u64, Error> {
1146 let mut timeout: u64 = 0;
1147 let result = unsafe { ffi::sd_journal_get_timeout(self.ffi, &mut timeout) };
1148 if result < 0 {
1149 return Err(Error::SDError(result));
1150 }
1151 Ok(timeout)
1152 }
1153
1154 /// Processes events after each wake-up and returns the type of events
1155 /// (implements [`sd_journal_process()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_fd.html#)).
1156 ///
1157 /// # Return Values
1158 /// - Ok(Event): journal wake event
1159 /// - Err(Error::SDError): sd-journal returned an error code
1160 pub fn process(&self) -> Result<Event, Error> {
1161 let result = unsafe { ffi::sd_journal_process(self.ffi) };
1162 match result {
1163 ffi::SD_JOURNAL_NOP => Ok(Event::NOOP),
1164 ffi::SD_JOURNAL_APPEND => Ok(Event::Append),
1165 ffi::SD_JOURNAL_INVALIDATE => Ok(Event::Invalidate),
1166 _ => Err(Error::SDError(result))
1167 }
1168 }
1169
1170 /// Wait for changes in the journal for a maximum period defined in timeout
1171 /// (implements [`sd_journal_wait()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_fd.html#)).
1172 ///
1173 /// Use uint64_t-1 for timeout to wait indefinitely.
1174 ///
1175 /// # Return Values
1176 /// - Ok(Event): journal wake event
1177 /// - Err(Error::SDError): sd-journal returned an error code
1178 pub fn wait(&self, timeout: u64) -> Result<Event, Error> {
1179 let result = unsafe { ffi::sd_journal_wait(self.ffi, timeout) };
1180 match result {
1181 ffi::SD_JOURNAL_NOP => Ok(Event::NOOP),
1182 ffi::SD_JOURNAL_APPEND => Ok(Event::Append),
1183 ffi::SD_JOURNAL_INVALIDATE => Ok(Event::Invalidate),
1184 _ => Err(Error::SDError(result))
1185 }
1186 }
1187
1188 /// Checks whether the journal owns runtime files (implements
1189 /// [`sd_journal_has_runtime_files()`](https://www.freedesktop.org/software/systemd/man/sd_journal_has_runtime_files.html#)).
1190 ///
1191 /// # Return Values
1192 /// - Ok(bool)
1193 /// - Err(Error::SDError): sd-journal returned an error code
1194 pub fn has_runtime_files(&self) -> Result<bool, Error> {
1195 let result = unsafe { ffi::sd_journal_has_runtime_files(self.ffi) };
1196 if result < 0 {
1197 return Err(Error::SDError(result));
1198 }
1199 Ok(result > 0)
1200 }
1201
1202 /// Checks whether the journal owns persistent files (implements
1203 /// [`sd_journal_has_persistent_files()`](https://www.freedesktop.org/software/systemd/man/sd_journal_has_persistent_files.html#)).
1204 ///
1205 /// # Return Values
1206 /// - Ok(bool)
1207 /// - Err(Error::SDError): sd-journal returned an error code
1208 pub fn has_persistent_files(&self) -> Result<bool, Error> {
1209 let result = unsafe { ffi::sd_journal_has_persistent_files(self.ffi) };
1210 if result < 0 {
1211 return Err(Error::SDError(result));
1212 }
1213 Ok(result > 0)
1214 }
1215
1216 /// **UNSTABLE API** Determines the disk space used by the journal in Bytes
1217 /// (implements [`sd_journal_get_usage()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_usage.html#)).
1218 ///
1219 /// # Stability
1220 /// Currently a plain u64 is returned. There is a reasonable chance for a
1221 /// change in future releases towards a SI unit type.
1222 ///
1223 /// # Return Values
1224 /// - Ok(u64): space required in Bytes
1225 /// - Err(Error::SDError): sd-journal returned an error code
1226 #[cfg(feature = "experimental")]
1227 pub fn get_usage(&self) -> Result<u64, Error> {
1228 let mut usage: u64 = 0;
1229 let result = unsafe { ffi::sd_journal_get_usage(self.ffi, &mut usage) };
1230 if result < 0 {
1231 return Err(Error::SDError(result));
1232 }
1233 Ok(usage)
1234 }
1235
1236 /// **UNSTABLE API** Retrieves the realtime timestamp as
1237 /// chrono::NaiveDateTime of the current record (implements
1238 /// [`sd_journal_get_realtime_usec()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_realtime_usec.html#)).
1239 ///
1240 /// # Stability
1241 /// Currently the function returns a chrono::NaiveDateTime calculated from
1242 /// the microseconds since EPOCH returned by the wrapped libsystemd
1243 /// function. In future releases this method may be changed to return
1244 /// microseconds (u128) or std::time::Duration although this is very
1245 /// unlikely. Changes will be made based on user feedback.
1246 ///
1247 /// # Return Values:
1248 /// - Ok(NaiveDateTime): realtime timestamp of current record
1249 /// - Err(Error::SDError): sd-journal returned an error code
1250 #[cfg(feature = "td_chrono")]
1251 #[cfg(feature = "experimental")]
1252 pub fn get_realtime(&self) -> Result<NaiveDateTime, Error> {
1253 let mut usec: u64 = 0;
1254 let result = unsafe { ffi::sd_journal_get_realtime_usec(self.ffi, &mut usec) };
1255 if result < 0 {
1256 return Err(Error::SDError(result));
1257 }
1258 let dt = NaiveDateTime::from_timestamp((usec / 1_000_000) as i64,
1259 ((usec % 1_000_000) * 1_000) as u32);
1260 Ok(dt)
1261 }
1262
1263 /// **UNSTABLE API** Retrieves the monotonic timestamp of the current record
1264 /// altogether with it's boot id (implements [`sd_journal_get_monotonic_usec()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_realtime_usec.html#)).
1265 ///
1266 /// # Stability
1267 /// Currently the function returns a chrono::Duration calculated from the
1268 /// microseconds since boot returned by the wrapped libsystemd function. The
1269 /// choice for chrono::Duration has been made based on the return value for
1270 /// `get_realtime()`. In future releases this method may be changed to
1271 /// microsenconds (u128) or std::time::Duration. Such change is reasonable
1272 /// likely and will be made based on user feedback.
1273 ///
1274 /// # Return Values
1275 /// - Ok(chrono::Duration, ID128): tuple of a monotonic timestamp since boot
1276 /// and boot id
1277 /// - Err(Error::SDError): sd-journal returned an error code
1278 #[cfg(feature = "td_chrono")]
1279 #[cfg(feature = "experimental")]
1280 pub fn get_monotonic(&self) -> Result<(Duration, sd_id128::ID128), Error> {
1281 let mut usec: u64 = 0;
1282 let mut boot_id = ID128::default().into_ffi();
1283 let result =
1284 unsafe { ffi::sd_journal_get_monotonic_usec(self.ffi, &mut usec, &mut boot_id) };
1285 if result < 0 {
1286 return Err(Error::SDError(result));
1287 }
1288 let duration = Duration::seconds((usec / 1_000_000) as i64)
1289 + Duration::microseconds((usec % 1_000_000) as i64);
1290 Ok((duration, ID128::from_ffi(boot_id)))
1291 }
1292
1293 /// **UNSTABLE API** Retrieve a text representation of the cursor
1294 /// (implements [`sd_journal_get_cursor()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_cursor.html#)).
1295 ///
1296 /// # Stability
1297 /// `sd_journal_get_cursor()` returns a ownership of a memory location.
1298 /// Currently the content is copied into a rustified String and the memory
1299 /// freed immediately. In future releases a new data type could be defined
1300 /// which avoids the immediate conversion into a String. The new data type
1301 /// could be handed over into `seek_cursor()`. The chance for such change is
1302 /// low. The decission will be taken based on typical usage scenarios and
1303 /// user feedback.
1304 ///
1305 /// # Return values
1306 /// - Ok(String): cursor representation of sd-journal
1307 /// - Err(Error::SDError): sd-journal returned an error code
1308 /// - Err(Error::UTF8Error): UTF-8 decoding error occured; although this
1309 /// should never happen since the journal internal cursor id is stored in
1310 /// valid UTF-8
1311 #[cfg(feature = "experimental")]
1312 pub fn get_cursor_id(&self) -> Result<String, Error> {
1313 let mut ptr: *mut c_char = ptr::null_mut();
1314 let result = unsafe { ffi::sd_journal_get_cursor(self.ffi, &mut ptr) };
1315 if result < 0 {
1316 return Err(Error::SDError(result));
1317 }
1318 let cursor_id = unsafe { CStr::from_ptr(ptr) };
1319 let cursor_id = match cursor_id.to_str() {
1320 Err(error) => {
1321 unsafe { libc::free(ptr as *mut c_void) };
1322 Err(Error::UTF8Error(error))?
1323 },
1324 Ok(value) => value.to_owned()
1325 };
1326 unsafe { libc::free(ptr as *mut c_void) };
1327 Ok(cursor_id)
1328 }
1329
1330 /// **UNSTABLE API** Checks whether the current journal position matches a
1331 /// cursor id (implements [`sd_journal_get_cursor`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_cursor.html#)).
1332 ///
1333 /// # Stability
1334 /// See [`get_cursor_id()`](get_cursor_id) for reasons why there is a small
1335 /// chance this method may be adjusted in future releases.
1336 ///
1337 /// # Return Values
1338 /// - Ok(bool)
1339 /// - Err(Error::SDError): sd-journal returned an error code
1340 #[cfg(feature = "experimental")]
1341 pub fn cursor_id_matches<S: Into<Vec<u8>>>(&self, cursor_id: S) -> Result<bool, Error> {
1342 let c_cursor = CString::new(cursor_id).map_err(Error::NullError)?;
1343 let result = unsafe { ffi::sd_journal_test_cursor(self.ffi, c_cursor.as_ptr()) };
1344 if result < 0 {
1345 return Err(Error::SDError(result));
1346 }
1347 Ok(result > 0)
1348 }
1349
1350 /// Determine the message cataloge entry for the current record (implements
1351 /// [`sd_journal_get_catalog()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_catalog.html#)).
1352 ///
1353 /// # Return Values
1354 /// - Ok(String): message catalogue
1355 /// - Err(Error::SDError): sd-journal returned an error code
1356 /// - Err(Error::UTF8Error): UTF-8 decoding error occured
1357 pub fn get_catalog(&self) -> Result<String, Error> {
1358 let mut data: *mut c_char = ptr::null_mut();
1359 let result = unsafe { ffi::sd_journal_get_catalog(self.ffi, &mut data) };
1360 if result < 0 {
1361 return Err(Error::SDError(result));
1362 }
1363 let catalog = unsafe { CStr::from_ptr(data) };
1364 let catalog = match catalog.to_str() {
1365 Err(error) => {
1366 unsafe { libc::free(data as *mut c_void) };
1367 Err(Error::UTF8Error(error))?
1368 },
1369 Ok(value) => value.to_owned()
1370 };
1371 unsafe { libc::free(data as *mut c_void) };
1372 Ok(catalog)
1373 }
1374
1375 /// Retrieve the data of a specific field (implements
1376 /// [`sd_journal_get_data()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_data.html#)).
1377 ///
1378 /// Retrieve the data of a specific field. The fieldname must be provided in
1379 /// all upper case letters. See the documentation of well-known
1380 /// [field names](https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html#).
1381 /// Field names may not contain 0x00 bytes (would raise a NullError). If the
1382 /// current entry does not contain the field, an SDError(-2) is returned.
1383 ///
1384 /// # Examples
1385 /// ```
1386 /// # use sd_journal::*;
1387 /// # use std::path::PathBuf;
1388 /// # let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1389 /// # test_data.push("test-data/");
1390 /// # println!("looking for test data in folder {}", test_data.display());
1391 /// # let journal = Journal::open_directory(&test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap();
1392 /// // loop over the journal and print the timestamp and message of each record
1393 /// for cursor in &journal {
1394 /// let cursor = cursor.unwrap();
1395 /// let message = cursor.get_data("MESSAGE")
1396 /// .unwrap_or("[no message available]".to_string());
1397 /// let datetime = cursor.get_realtime().unwrap();
1398 /// println!("{} - {}", datetime, message);
1399 /// }
1400 /// ```
1401 ///
1402 /// # Return values
1403 /// - Ok(String): value in the format FIELDNAME=FIELDVALUE
1404 /// - Err(Error::NullError): the requested field name contains 0-bytes
1405 /// - Err(Error::SDError): sd-journal returned an error code
1406 /// - Err(Error::UTF8Error): UTF-8 decoding error occured
1407 /// - Err(Error::UnexpectedDataFormat): libsystemd is expected to return
1408 /// data in the format `FIELDNAME=field value`. Before returning that
1409 /// data, `FIELDNAME=` are stripped of. If that operation fails, this
1410 /// error is raised.
1411 /// - Err(Error::StringError): if this error is raised, please report an
1412 /// issue against sd_journal
1413 pub fn get_data<F: Into<Vec<u8>>>(&self, field: F) -> Result<String, Error> {
1414 let c_field = CString::new(field).map_err(Error::NullError)?;
1415 let mut data: *const c_void = std::ptr::null_mut();
1416 let mut length: size_t = 0;
1417 let result =
1418 unsafe { ffi::sd_journal_get_data(self.ffi, c_field.as_ptr(), &mut data, &mut length) };
1419 if result < 0 {
1420 return Err(Error::SDError(result));
1421 }
1422 let result = unsafe {
1423 CStr::from_ptr(data as *mut c_char).to_str()
1424 .map_err(Error::UTF8Error)?
1425 };
1426
1427 let field = c_field.into_string().map_err(Error::StringError)?;
1428 let result = match result.strip_prefix(&field) {
1429 None => Err(Error::UnexpectedDataFormat)?,
1430 Some(value) => match value.strip_prefix('=') {
1431 None => Err(Error::UnexpectedDataFormat)?,
1432 Some(value) => value
1433 }
1434 };
1435 Ok(result.to_string())
1436 }
1437
1438 /// Enumerate the fields of the current record (implements
1439 /// [`sd_journal_enumerate_data()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_data.html#)).
1440 ///
1441 /// This is the libsystemd way of iterating over fields. There is also a
1442 /// rustified alternative method [`iter_fields()`](Journal::iter_fields).
1443 ///
1444 /// # Examples
1445 /// ```
1446 /// # use sd_journal::*;
1447 /// # let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap();
1448 /// # journal.next().unwrap();
1449 /// // loop over all fields of the current record and print FIELDNAME: field value
1450 /// while let Ok(Enumeration::Value((field, value))) = journal.enumerate_fields() {
1451 /// println!("{}: {}", field, value)
1452 /// }
1453 /// ```
1454 ///
1455 /// # Return values
1456 /// - Ok(Enumeration::Value(String, String)): field name and value
1457 /// - Ok(Enumeration::EoF): no more fields to enumerate
1458 /// - Err(Error::SDError): sd-journal returned an error code
1459 /// - Err(Error::UTF8Error): UTF-8 decoding error occured
1460 /// - Err(Error::UnexpectedDataFormat): libsystemd is expected to return
1461 /// data in the format `FIELDNAME=field value`. Field name and value are
1462 /// separated at the `=`. If the format does not match, this error is
1463 /// raised.
1464 pub fn enumerate_fields(&self) -> Result<Enumeration<(String, String)>, Error> {
1465 let mut data: *const c_void = ptr::null_mut();
1466 let mut length: size_t = 0;
1467 let result = unsafe { ffi::sd_journal_enumerate_data(self.ffi, &mut data, &mut length) };
1468 if result < 0 {
1469 return Err(Error::SDError(result));
1470 }
1471 if result == 0 {
1472 return Ok(Enumeration::EoF);
1473 }
1474 let result = unsafe {
1475 CStr::from_ptr(data as *const c_char).to_str()
1476 .map_err(Error::UTF8Error)?
1477 .to_owned()
1478 };
1479 let (field, value) = match result.find('=') {
1480 None => Err(Error::UnexpectedDataFormat)?,
1481 Some(index) => result.split_at(index)
1482 };
1483 let value = match value.strip_prefix('=') {
1484 None => Err(Error::UnexpectedDataFormat)?,
1485 Some(value) => value
1486 };
1487 Ok(Enumeration::Value((field.to_owned(), value.to_owned())))
1488 }
1489
1490 /// Enumerate the available & supported fields of the current record
1491 /// (implements [`sd_journal_enumerate_available_data()`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_data.html#)).
1492 ///
1493 /// # Return values
1494 /// - Ok(Enumeration::Value(String, String)): field name and value
1495 /// - Ok(Enumeration::EoF): no more fields to enumerate
1496 /// - Err(Error::SDError): sd-journal returned an error code
1497 /// - Err(Error::UTF8Error): UTF-8 decoding error occured
1498 /// - Err(Error::UnexpectedDataFormat): libsystemd is expected to return
1499 /// data in the format `FIELDNAME=field value`. Field name and value are
1500 /// separated at the `=`. If the format does not match, this error is
1501 /// raised.
1502 pub fn enumerate_available_fields(&self) -> Result<Enumeration<(String, String)>, Error> {
1503 let mut data: *const c_void = ptr::null_mut();
1504 let mut length: size_t = 0;
1505 let result =
1506 unsafe { ffi::sd_journal_enumerate_available_data(self.ffi, &mut data, &mut length) };
1507 if result < 0 {
1508 return Err(Error::SDError(result));
1509 }
1510 if result == 0 {
1511 return Ok(Enumeration::EoF);
1512 }
1513 if result == 0 {
1514 return Ok(Enumeration::EoF);
1515 }
1516 let result = unsafe {
1517 CStr::from_ptr(data as *const c_char).to_str()
1518 .map_err(Error::UTF8Error)?
1519 .to_owned()
1520 };
1521 let (field, value) = match result.find('=') {
1522 None => Err(Error::UnexpectedDataFormat)?,
1523 Some(index) => result.split_at(index)
1524 };
1525 let value = match value.strip_prefix('=') {
1526 None => Err(Error::UnexpectedDataFormat)?,
1527 Some(value) => value
1528 };
1529 Ok(Enumeration::Value((field.to_owned(), value.to_owned())))
1530 }
1531
1532 /// Restart enumeration of fields (implements
1533 /// [`sd_journal_restart_data`](https://www.freedesktop.org/software/systemd/man/sd_journal_get_data.html#)).
1534 pub fn restart_fields_enumeration(&self) {
1535 unsafe {
1536 ffi::sd_journal_restart_data(self.ffi);
1537 }
1538 }
1539
1540 /// Returns an iterator over the fields of the current records.
1541 ///
1542 /// # Examples
1543 /// ```
1544 /// # use sd_journal::*;
1545 /// let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap();
1546 /// # journal.next().unwrap();
1547 /// // The following 2 loops are synonyms
1548 /// while let Ok(Enumeration::Value((field, value))) = journal.enumerate_fields() {
1549 /// println!("{}: {}", field, value);
1550 /// }
1551 /// for field in journal.iter_fields() {
1552 /// let (field, value) = field.unwrap();
1553 /// println!("{}: {}", field, value);
1554 /// }
1555 /// ```
1556 pub fn iter_fields<'a>(&'a self) -> Fields<'a> {
1557 Fields { journal: self }
1558 }
1559
1560 /// Query the journal for unique field values of a certain field (implements
1561 /// [`sd_journal_query_unique()`](https://www.freedesktop.org/software/systemd/man/sd_journal_query_unique.html#)).
1562 ///
1563 /// # libsystemd Issues
1564 /// `sd_journal_query_unique()` and the related functions do not always
1565 /// succeed to return **unique** values, i.e. a value may be returned
1566 /// repeatedly. An [issue](https://github.com/systemd/systemd/issues/18075)
1567 /// has been reported.
1568 ///
1569 /// # Return Values
1570 /// - Ok(())
1571 /// - Err(Error::SDError): sd-journal returned an error code
1572 pub fn query_unique_values<S: Into<Vec<u8>>>(&self, field: S) -> Result<(), Error> {
1573 let c_field = CString::new(field).map_err(Error::NullError)?;
1574 let result = unsafe { ffi::sd_journal_query_unique(self.ffi, c_field.as_ptr()) };
1575 if result < 0 {
1576 return Err(Error::SDError(result));
1577 }
1578 Ok(())
1579 }
1580
1581 /// Enumerate all unique values for the field requested (implements
1582 /// [`sd_journal_enumerate_unique`](https://www.freedesktop.org/software/systemd/man/sd_journal_query_unique.html#)).
1583 ///
1584 /// Return Values
1585 /// - Ok(Enumeration::Value(String)): value
1586 /// - Ok(Enumeration::EoF): no more unique values to enumerate
1587 /// - Err(Error::UTF8Error): UTF-8 decoding error occured
1588 /// - Err(Error::SDError): sd-journal returned an error code
1589 pub fn enumerate_unique_values(&self) -> Result<Enumeration<String>, Error> {
1590 let mut data: *const c_void = ptr::null_mut();
1591 let mut length: size_t = 0;
1592 let result = unsafe { ffi::sd_journal_enumerate_unique(self.ffi, &mut data, &mut length) };
1593 if result < 0 {
1594 return Err(Error::SDError(result));
1595 }
1596 if result == 0 {
1597 return Ok(Enumeration::EoF);
1598 }
1599 let result = unsafe {
1600 CStr::from_ptr(data as *const c_char).to_str()
1601 .map_err(Error::UTF8Error)?
1602 };
1603 let index = match result.find('=') {
1604 None => Err(Error::UnexpectedDataFormat)?,
1605 Some(index) => index
1606 };
1607 let (_, result) = result.split_at(index + 1);
1608 Ok(Enumeration::Value(result.to_owned()))
1609 }
1610
1611 /// Enumerate available unique values for the field requested (implements
1612 /// [`sd_journal_enumerate_available_unique`](https://www.freedesktop.org/software/systemd/man/sd_journal_query_unique.html#)).
1613 ///
1614 /// Return Values:
1615 /// - Ok(Enumeration::Value(String)): value
1616 /// - Ok(Enumeration::EoF): no more unique values to enumerate
1617 /// - Err(Error::UTF8Error): UTF-8 decoding error occured
1618 /// - Err(Error::SDError): sd-journal returned an error code
1619 pub fn enumerate_available_unique_values(&self) -> Result<Enumeration<String>, Error> {
1620 let mut data: *const c_void = ptr::null_mut();
1621 let mut length: size_t = 0;
1622 let result =
1623 unsafe { ffi::sd_journal_enumerate_available_unique(self.ffi, &mut data, &mut length) };
1624 if result < 0 {
1625 return Err(Error::SDError(result));
1626 }
1627 if result == 0 {
1628 return Ok(Enumeration::EoF);
1629 }
1630 Ok(Enumeration::Value(unsafe {
1631 CStr::from_ptr(data as *const c_char).to_str()
1632 .map_err(Error::UTF8Error)?
1633 .to_owned()
1634 }))
1635 }
1636
1637 /// Restart enumeration of unique values (implements
1638 /// [`sd_journal_restart_unique`](https://www.freedesktop.org/software/systemd/man/sd_journal_query_unique.html#)).
1639 pub fn restart_unique_value_enumeration(&self) {
1640 unsafe { ffi::sd_journal_restart_unique(self.ffi) }
1641 }
1642
1643 /// Returns an iterator over unique values of a field.
1644 ///
1645 /// # Examples
1646 /// ```
1647 /// # use sd_journal::*;
1648 /// # use std::path::PathBuf;
1649 /// # let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1650 /// # test_data.push("test-data/");
1651 /// # println!("looking for test data in folder {}", test_data.display());
1652 /// # let journal = Journal::open_directory(&test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap();
1653 /// for value in journal.iter_unique_values("MESSAGE").unwrap() {
1654 /// let value = value.unwrap();
1655 /// println!("{}", value);
1656 /// }
1657 /// ```
1658 ///
1659 /// # Return Values
1660 /// - Ok(UniqueValues)
1661 /// - Err(Error::SDError): sd-journal returned an error code
1662 pub fn iter_unique_values<'a, S: Into<Vec<u8>>>(&'a self,
1663 field: S)
1664 -> Result<UniqueValues<'a>, Error> {
1665 self.query_unique_values(field)?;
1666 Ok(UniqueValues { journal: &self })
1667 }
1668}
1669
1670impl<'a> Cursor<'a> {
1671 /// see [Journal::get_realtime](Journal::get_realtime)
1672 pub fn get_realtime(&self) -> Result<NaiveDateTime, Error> {
1673 self.journal.get_realtime()
1674 }
1675
1676 /// see [Journal::get_monotonic](Journal::get_monotonic)
1677 pub fn get_monotonic(&self) -> Result<(Duration, sd_id128::ID128), Error> {
1678 self.journal.get_monotonic()
1679 }
1680
1681 /// see [Journal::get_cursor_id](Journal::get_cursor_id)
1682 pub fn get_id(&self) -> Result<String, Error> {
1683 self.journal.get_cursor_id()
1684 }
1685
1686 /// see [Journal::cursor_id_matches](Journal::cursor_id_matches)
1687 pub fn id_matches<S: Into<Vec<u8>>>(&self, cursor_id: S) -> Result<bool, Error> {
1688 self.journal.cursor_id_matches(cursor_id)
1689 }
1690
1691 /// see [Journal::get_catalog](Journal::get_catalog)
1692 pub fn get_catalog(&self) -> Result<String, Error> {
1693 self.journal.get_catalog()
1694 }
1695
1696 /// see [Journal::get_data](Journal::get_data)
1697 pub fn get_data<F: Into<Vec<u8>>>(&self, field: F) -> Result<String, Error> {
1698 self.journal.get_data(field)
1699 }
1700
1701 /// see [Journal::enumerate_fields](Journal::enumerate_fields)
1702 pub fn enumerate_fields(&self) -> Result<Enumeration<(String, String)>, Error> {
1703 self.journal.enumerate_fields()
1704 }
1705
1706 /// see [Journal::enumerate_available_fields](Journal::
1707 /// enumerate_available_fields)
1708 pub fn enumerate_available_fields(&self) -> Result<Enumeration<(String, String)>, Error> {
1709 self.journal.enumerate_available_fields()
1710 }
1711
1712 /// see [Journal::restart_fields_enumeration](Journal::
1713 /// restart_fields_enumeration)
1714 pub fn restart_fields_enumeration(&self) {
1715 self.journal.restart_fields_enumeration()
1716 }
1717
1718 /// see [Journal::iter_fields](Journal::iter_fields)
1719 pub fn iter_fields(&self) -> Fields<'a> {
1720 self.journal.iter_fields()
1721 }
1722}