blob: 8c839c0386e61a2f7a77d37e7ecd4de5f68efbb3 [file] [log] [blame] [edit]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::LogFile;
use std::ffi::c_char;
#[cfg(feature = "chromeos")]
use libchromeos::syslog;
#[cxx::bridge(namespace = "gravedigger::rust")]
pub mod ffi {
#[derive(Debug)]
pub enum SeekLocation {
Begin,
BeforeEnd,
End,
}
extern "Rust" {
type LogFile;
type CreateResult;
fn try_init(ident: &str) -> bool;
fn log_file_exists(name: &str) -> bool;
fn file_path_exists(path: &str) -> bool;
fn new_log_file(name: &str) -> Box<LogFile>;
fn new_log_file_from_path(path: &str) -> Box<CreateResult>;
fn result_to_logfile(result: Box<CreateResult>) -> Box<LogFile>;
fn get_inode(self: &LogFile) -> u64;
fn get_file_length(self: &LogFile) -> i64;
fn read_to_slice(log_file: &mut Box<LogFile>, buf: &mut [u8]) -> i64;
unsafe fn read_to_char_ptr(
log_file: &mut Box<LogFile>,
data: *mut c_char,
size: i64,
) -> i64;
fn seek(self: &mut LogFile, location: SeekLocation) -> bool;
fn has_value(self: &CreateResult) -> bool;
}
}
/// Thin wrapper struct to communicate errors to the C++ side.
pub struct CreateResult {
file: Option<LogFile>,
}
impl CreateResult {
pub fn has_value(&self) -> bool {
self.file.is_some()
}
}
#[cfg(feature = "chromeos")]
fn try_init(ident: &str) -> bool {
syslog::init(ident.to_string(), true).is_ok()
}
#[cfg(not(feature = "chromeos"))]
fn try_init(ident: &str) -> bool {
!ident.is_empty()
}
/// Check if a logfile exists in the expected directory for SOUL.
fn log_file_exists(name: &str) -> bool {
LogFile::exists(name)
}
/// CHeck if a file exists at the provided `path`.
fn file_path_exists(path: &str) -> bool {
LogFile::path_exists(path)
}
/// Create a new logfile from the file that exists at `path`.
///
/// Panics if the file doesn't exist or can't be read.
fn new_log_file_from_path(path: &str) -> Box<CreateResult> {
match LogFile::from_path(path) {
Ok(log_file) => Box::new(CreateResult {
file: Some(log_file),
}),
Err(err) => {
log::error!("Couldn't create logfile because: {err}");
Box::new(CreateResult { file: None })
}
}
}
/// Convert the initial `CreateResult` into a `LogFile`.
///
/// Panics if `result` doesn't hold a `LogFile`. Check this before with
/// `result.has_value()`.
fn result_to_logfile(result: Box<CreateResult>) -> Box<LogFile> {
match result.file {
Some(file) => Box::new(file),
None => panic!("log file must have been created successfully before"),
}
}
/// Create a new virtual logfile.
fn new_log_file(name: &str) -> Box<LogFile> {
let log_file = LogFile::new(name).expect("Couldn't create LogFile object");
Box::new(log_file)
}
/// Read up to `size` bytes into `data` from the `log_file`.
///
/// Returns a positive number if bytes have been read or a negative error code
/// if something went wrong.
///
/// `size` must not be negative and `data` must not be null.
/// Will do an internal conversion from c_char to u8.
fn read_to_char_ptr(log_file: &mut Box<LogFile>, data: *mut c_char, size: i64) -> i64 {
if size < 0 {
log::warn!("Reading negative {size} is not supported");
return -1;
} else if size == 0 {
log::debug!("Requested to read 0 bytes. Doing nothing");
return 0;
}
if data.is_null() {
log::error!("Passed a nullptr as data");
return -1;
}
let len = size as usize;
let slice = unsafe { std::slice::from_raw_parts_mut(data.cast::<u8>(), len) };
read_to_slice(log_file, slice)
}
/// Read bytes from `log_file` until `buf` is full.
///
/// Returns a positive number if bytes have been read or a negative error code
/// if something went wrong.
fn read_to_slice(log_file: &mut Box<LogFile>, buf: &mut [u8]) -> i64 {
if buf.len() == 0 {
log::debug!("Requested to read 0 bytes. Doing nothing");
return 0;
}
match log_file.read(buf) {
Ok(size) => return size,
Err(_err) => return -1,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::TestLogFile;
fn empty_log_file() -> (TestLogFile, Box<LogFile>) {
valid_log_file("")
}
fn valid_log_file(data: &str) -> (TestLogFile, Box<LogFile>) {
let test_file = TestLogFile::with_content(data);
let log_file = test_file.as_logfile();
(test_file, Box::new(log_file))
}
#[test]
#[should_panic(expected = "No such file")]
fn die_on_non_existing_file() {
new_log_file("filename doesn't exist");
}
#[test]
#[should_panic(expected = "Couldn't create LogFile")]
fn die_on_path_instead_of_name() {
new_log_file("Relative/Path");
}
#[test]
fn read_negative_size() {
let (_test_file, mut logfile) = empty_log_file();
assert_eq!(read_to_char_ptr(&mut logfile, std::ptr::null_mut(), -1), -1);
}
#[test]
fn read_zero_bytes() {
let (_test_file, mut logfile) = empty_log_file();
assert_eq!(read_to_char_ptr(&mut logfile, std::ptr::null_mut(), 0), 0);
}
#[test]
fn read_into_nullptr() {
let (_test_file, mut logfile) = empty_log_file();
assert_eq!(read_to_char_ptr(&mut logfile, std::ptr::null_mut(), 1), -1);
}
#[test]
fn read_one_byte() {
let (_test_file, mut logfile) = valid_log_file("foo");
let mut buf: c_char = 0;
assert_eq!(read_to_char_ptr(&mut logfile, &mut buf, 1), 1);
assert_eq!(buf, 'f' as c_char);
}
#[test]
fn read_to_slice_vec() {
let (_test_file, mut logfile) = valid_log_file("foo");
let mut buf = vec![0; 3];
assert_eq!(read_to_slice(&mut logfile, &mut buf), 3);
assert_eq!(buf, b"foo");
}
#[test]
fn read_to_slice_empty() {
let (_test_file, mut logfile) = valid_log_file("foo");
let mut buf = vec![];
assert_eq!(read_to_slice(&mut logfile, &mut buf), 0);
}
#[test]
fn log_file_exists_test() {
assert!(log_file_exists("test_log_file"));
assert!(!log_file_exists("filename doesn't exist"));
}
#[test]
fn file_paths_exist() {
assert!(file_path_exists(
format!("{}/tests/test_log_file", env!("CARGO_MANIFEST_DIR")).as_str()
));
assert_eq!(file_path_exists("relative/path"), false);
}
}