From 7d9ce36d6f8ec72696192c5e2a27e10ea41cd581 Mon Sep 17 00:00:00 2001 From: Matt Gaylor Date: Wed, 29 Oct 2025 16:28:42 -0700 Subject: [PATCH 1/2] Adding file logging to bindings --- Cargo.lock | 12 +++++++++++ c2pa_c_ffi/Cargo.toml | 3 +++ c2pa_c_ffi/src/c_api.rs | 45 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index b9ec38d6f..ccc613dc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -541,6 +541,9 @@ version = "0.68.0" dependencies = [ "c2pa", "cbindgen", + "chrono", + "fern", + "log", "scopeguard", "serde", "serde_json", @@ -1375,6 +1378,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fern" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" +dependencies = [ + "log", +] + [[package]] name = "ff" version = "0.13.1" diff --git a/c2pa_c_ffi/Cargo.toml b/c2pa_c_ffi/Cargo.toml index d251c5cfa..93dbfcb7e 100644 --- a/c2pa_c_ffi/Cargo.toml +++ b/c2pa_c_ffi/Cargo.toml @@ -34,6 +34,9 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0.64" tokio = { version = "1.36", features = ["rt-multi-thread", "rt"] } +log = "0.4" +fern = "0.6" +chrono = "0.4" # For timestamps [dev-dependencies] tempfile = "3.7.0" diff --git a/c2pa_c_ffi/src/c_api.rs b/c2pa_c_ffi/src/c_api.rs index c936fb11c..988c0fb45 100644 --- a/c2pa_c_ffi/src/c_api.rs +++ b/c2pa_c_ffi/src/c_api.rs @@ -17,6 +17,9 @@ use std::{ ptr, }; +use chrono::Local; +use fern::Dispatch; + /// Validates that a buffer size is within safe bounds and doesn't cause integer overflow /// when used with pointer arithmetic. /// @@ -65,7 +68,7 @@ unsafe fn safe_slice_from_raw_parts( if !is_safe_buffer_size(len, ptr) { return Err(Error::Other(format!( - "Buffer size {len} is invalid for parameter '{param_name}'", + "Buffer size {len} is invalid for parameter '{param_name}'" ))); } @@ -297,6 +300,36 @@ pub unsafe extern "C" fn c2pa_version() -> *mut c_char { to_c_string(version) } +/// Sets up file logging. +/// The logger will append any logged text to the end of the log_file provided. +/// +/// # Safety +/// Reads from NULL-terminated C strings. +#[no_mangle] +pub unsafe extern "C" fn c2pa_init_file_logging(log_file: *const c_char) -> c_int { + let log_file = from_cstr_or_return_int!(log_file); + let result = Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "[{}][{}] {}", + Local::now().format("%Y-%m-%d %H:%M:%S"), + record.level(), + message + )) + }) + .level(log::LevelFilter::Info) + .chain(std::io::stdout()) + // Log to a file + .chain(fern::log_file(log_file).unwrap()) + .apply(); + + if result.is_ok() { + 0 + } else { + -1 + } +} + /// Returns the last error message. /// /// # Safety @@ -1785,6 +1818,10 @@ mod tests { #[test] #[cfg(feature = "file_io")] fn test_c2pa_sign_file_null_source_path() { + let log_path_str = "c2pa_test.log"; + let log_path = CString::new(log_path_str).unwrap(); + unsafe { c2pa_init_file_logging(log_path.as_ptr()) }; + let dest_path = CString::new("/tmp/output.jpg").unwrap(); let manifest = CString::new("{}").unwrap(); let signer_info = C2paSignerInfo { @@ -1806,6 +1843,12 @@ mod tests { let error = unsafe { c2pa_error() }; let error_str = unsafe { CString::from_raw(error) }; assert_eq!(error_str.to_str().unwrap(), "NullParameter: source_path"); + + use std::fs; + let content = fs::read(log_path_str); + assert!(content.is_ok()); + assert!(content.unwrap().is_empty()); + let _ = fs::remove_file(log_path_str); } #[test] From 5de8f9a1f2f3c91ac7e6f6cb8f47bf8a5aa3c347 Mon Sep 17 00:00:00 2001 From: Matt Gaylor Date: Tue, 11 Nov 2025 15:42:19 -0800 Subject: [PATCH 2/2] Adding better error handling for the file logger --- c2pa_c_ffi/src/c_api.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/c2pa_c_ffi/src/c_api.rs b/c2pa_c_ffi/src/c_api.rs index 988c0fb45..0455df3ee 100644 --- a/c2pa_c_ffi/src/c_api.rs +++ b/c2pa_c_ffi/src/c_api.rs @@ -308,6 +308,13 @@ pub unsafe extern "C" fn c2pa_version() -> *mut c_char { #[no_mangle] pub unsafe extern "C" fn c2pa_init_file_logging(log_file: *const c_char) -> c_int { let log_file = from_cstr_or_return_int!(log_file); + + // Attempt to open the log file, return -1 if it fails (e.g., parent directory doesn't exist) + let log_file_handle = match fern::log_file(log_file) { + Ok(handle) => handle, + Err(_) => return -1, + }; + let result = Dispatch::new() .format(|out, message, record| { out.finish(format_args!( @@ -320,7 +327,7 @@ pub unsafe extern "C" fn c2pa_init_file_logging(log_file: *const c_char) -> c_in .level(log::LevelFilter::Info) .chain(std::io::stdout()) // Log to a file - .chain(fern::log_file(log_file).unwrap()) + .chain(log_file_handle) .apply(); if result.is_ok() {