the log wrapper in C/C++ accepts formats

This commit is contained in:
2026-03-17 13:22:44 +11:00
parent 2b3fe0630f
commit bdbd94eda2
9 changed files with 259 additions and 7 deletions

View File

@@ -26,6 +26,50 @@ Copy the built PAM module to `/lib/security/` or `/lib64/security/` as needed.
## Logging
Rust backend logs to `/var/log/pam_rust_backend.log` by default.
## C / C++ Logging Wrappers (examples)
This project exposes a simple Rust FFI sink and provides lightweight C and C++ wrappers for convenient logging without changing the Rust side.
Headers:
- `pam-module/include/rust_backend_ffi.h` — canonical FFI declarations and level enum.
- `pam-module/include/rust_backend_logging_c.h` — C `printf`-style helpers.
- `pam-module/include/rust_backend_logging_cpp.h` — C++ `cout`/`cerr`-style RAII stream helpers.
C example (printf-style):
```c
#include "rust_backend_logging_c.h"
// simple formatted info log
RUST_LOGF_INFO("user=%s result=%d", user, result);
```
C notes:
- Formatting uses a fixed buffer (`RUST_LOG_FMT_BUFSZ`, default 2048) and is performed in C; the formatted string is forwarded to Rust.
- If the formatted output is longer than the buffer it will be truncated; `vsnprintf` errors or a NULL format will log a small fallback message.
C++ example (stream-style):
```cpp
#include "rust_backend_ffi.h"
// stream-style; message sent to Rust on destructor
RUST_COUT() << "user=" << user << " authenticated=" << (ok?"yes":"no") << std::flush;
// error example
RUST_CERR() << "authentication failed for user=" << user << std::flush;
```
C++ notes:
- `RUST_COUT()` and `RUST_CERR()` are thin factories that return a `RustLogStream` object which buffers via `std::ostringstream` and sends the accumulated string to `rust_log_event_with_level` when the temporary is destroyed.
- The destructor is `noexcept` and swallow exceptions to avoid crossing FFI boundaries.
Compatibility:
- The canonical ABI function is `rust_log_event_with_level(const char* event, RustLogLevel level)` and is implemented in Rust.
- A legacy `rust_log_event(const char* event)` wrapper remains and maps to `INFO` for backward compatibility.
- Wrappers are header-only and additive; no changes to the Rust backend are required.
## Safety
- Rust panics are contained and never cross FFI.
- C++ exceptions are caught before returning to PAM.

View File

@@ -23,3 +23,15 @@ target_link_libraries(pam_usercontainer PRIVATE ${RUST_STATICLIB})
# Install target
install(TARGETS pam_usercontainer DESTINATION lib/security)
# Examples: small programs demonstrating logging wrappers
set_source_files_properties(examples/logging_example.c PROPERTIES LANGUAGE CXX)
add_executable(logging_example_c examples/logging_example.c)
target_include_directories(logging_example_c PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
add_dependencies(logging_example_c rust_backend_build)
target_link_libraries(logging_example_c PRIVATE ${RUST_STATICLIB})
add_executable(logging_example_cpp examples/logging_example.cpp)
target_include_directories(logging_example_cpp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
add_dependencies(logging_example_cpp rust_backend_build)
target_link_libraries(logging_example_cpp PRIVATE ${RUST_STATICLIB})

View File

@@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache-2.0
// C example demonstrating printf-style Rust logging wrapper
#include "../include/rust_backend_ffi.h"
int main(int argc, char** argv) {
(void)argc; (void)argv;
// Ensure logger is initialized (nullptr -> default path)
rust_init_logging(NULL);
const char* user = "alice";
int result = 0; // pretend success
RUST_LOGF_INFO("C example: user=%s authenticated=%d", user, result);
RUST_LOGF_DEBUG("C example: debug details: x=%d y=%d", 10, 20);
return 0;
}

View File

@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
// C++ example demonstrating stream-style Rust logging wrapper
#include "../include/rust_backend_ffi.h"
#include <string>
int main() {
rust_init_logging(nullptr);
std::string user = "bob";
bool ok = true;
RUST_COUT() << "C++ example: user=" << user << " authenticated=" << (ok?"yes":"no") << std::flush;
RUST_CERR() << "C++ example: an error message for user=" << user << std::flush;
return 0;
}

View File

@@ -44,3 +44,9 @@ void rust_log_event_with_level(const char* event, RustLogLevel level);
#define RUST_LOG_DEBUG(msg) rust_log_event_with_level((msg), RUST_LOG_LEVEL_DEBUG)
#define RUST_LOG_TRACE(msg) rust_log_event_with_level((msg), RUST_LOG_LEVEL_TRACE)
// C wrapper: printf-style helpers (header-only)
#include "rust_backend_logging_c.h"
#ifdef __cplusplus
#include "rust_backend_logging_cpp.h"
#endif

View File

@@ -0,0 +1,74 @@
// SPDX-License-Identifier: Apache-2.0
// C printf-style wrappers for Rust logging FFI
#pragma once
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "rust_backend_ffi.h"
#ifdef __cplusplus
extern "C" {
#endif
// Buffer size for formatted messages. Documented truncation behavior.
#define RUST_LOG_FMT_BUFSZ 2048
static inline void rust_log_vprintf_level(RustLogLevel level, const char* fmt, va_list ap) {
if (fmt == NULL) {
rust_log_event_with_level("(null format)", level);
return;
}
char buf[RUST_LOG_FMT_BUFSZ];
int ret = vsnprintf(buf, RUST_LOG_FMT_BUFSZ, fmt, ap);
if (ret < 0) {
rust_log_event_with_level("(format error)", level);
return;
}
// vsnprintf guarantees NUL-termination as long as size>0
buf[RUST_LOG_FMT_BUFSZ - 1] = '\0';
rust_log_event_with_level(buf, level);
}
static inline void rust_log_printf_level(RustLogLevel level, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
rust_log_vprintf_level(level, fmt, ap);
va_end(ap);
}
// Convenience per-level functions
static inline void rust_logf_fatal(const char* fmt, ...) {
va_list ap; va_start(ap, fmt); rust_log_vprintf_level(RUST_LOG_LEVEL_FATAL, fmt, ap); va_end(ap);
}
static inline void rust_logf_error(const char* fmt, ...) {
va_list ap; va_start(ap, fmt); rust_log_vprintf_level(RUST_LOG_LEVEL_ERROR, fmt, ap); va_end(ap);
}
static inline void rust_logf_warn(const char* fmt, ...) {
va_list ap; va_start(ap, fmt); rust_log_vprintf_level(RUST_LOG_LEVEL_WARN, fmt, ap); va_end(ap);
}
static inline void rust_logf_info(const char* fmt, ...) {
va_list ap; va_start(ap, fmt); rust_log_vprintf_level(RUST_LOG_LEVEL_INFO, fmt, ap); va_end(ap);
}
static inline void rust_logf_debug(const char* fmt, ...) {
va_list ap; va_start(ap, fmt); rust_log_vprintf_level(RUST_LOG_LEVEL_DEBUG, fmt, ap); va_end(ap);
}
static inline void rust_logf_trace(const char* fmt, ...) {
va_list ap; va_start(ap, fmt); rust_log_vprintf_level(RUST_LOG_LEVEL_TRACE, fmt, ap); va_end(ap);
}
// Convenience macros for C/C++ callers (variadic macro)
// Support both C99 and C++ compilers that support variadic macros
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus)
#define RUST_LOGF_FATAL(fmt, ...) rust_log_printf_level(RUST_LOG_LEVEL_FATAL, (fmt), ##__VA_ARGS__)
#define RUST_LOGF_ERROR(fmt, ...) rust_log_printf_level(RUST_LOG_LEVEL_ERROR, (fmt), ##__VA_ARGS__)
#define RUST_LOGF_WARN(fmt, ...) rust_log_printf_level(RUST_LOG_LEVEL_WARN, (fmt), ##__VA_ARGS__)
#define RUST_LOGF_INFO(fmt, ...) rust_log_printf_level(RUST_LOG_LEVEL_INFO, (fmt), ##__VA_ARGS__)
#define RUST_LOGF_DEBUG(fmt, ...) rust_log_printf_level(RUST_LOG_LEVEL_DEBUG, (fmt), ##__VA_ARGS__)
#define RUST_LOGF_TRACE(fmt, ...) rust_log_printf_level(RUST_LOG_LEVEL_TRACE, (fmt), ##__VA_ARGS__)
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,75 @@
// SPDX-License-Identifier: Apache-2.0
// C++ stream-style wrappers for Rust logging FFI
#pragma once
// This header expects `rust_backend_ffi.h` to have been included first
// so that `RustLogLevel` and `rust_log_event_with_level` are available.
#include <sstream>
#include <string>
#include <ostream>
class RustLogStream {
public:
explicit RustLogStream(RustLogLevel level) noexcept : level_(level) {}
// Generic stream insertion
template<typename T>
RustLogStream& operator<<(const T& v) {
try {
oss_ << v;
} catch (...) {
// swallow; avoid exceptions crossing FFI
}
return *this;
}
// Support manipulators like std::endl
RustLogStream& operator<<(std::ostream& (*manip)(std::ostream&)) {
try {
manip(oss_);
} catch (...) {
}
return *this;
}
// Destructor emits buffered message
~RustLogStream() noexcept {
try {
const std::string s = oss_.str();
if (!s.empty()) {
rust_log_event_with_level(s.c_str(), level_);
}
} catch (...) {
// never throw
}
}
// Non-copyable
RustLogStream(const RustLogStream&) = delete;
RustLogStream& operator=(const RustLogStream&) = delete;
// Movable
RustLogStream(RustLogStream&&) = default;
RustLogStream& operator=(RustLogStream&&) = default;
private:
RustLogLevel level_;
std::ostringstream oss_;
};
// Factories and macros
inline RustLogStream rust_log_info_stream() { return RustLogStream(RUST_LOG_LEVEL_INFO); }
inline RustLogStream rust_log_warn_stream() { return RustLogStream(RUST_LOG_LEVEL_WARN); }
inline RustLogStream rust_log_error_stream() { return RustLogStream(RUST_LOG_LEVEL_ERROR); }
inline RustLogStream rust_log_debug_stream() { return RustLogStream(RUST_LOG_LEVEL_DEBUG); }
inline RustLogStream rust_log_trace_stream() { return RustLogStream(RUST_LOG_LEVEL_TRACE); }
inline RustLogStream rust_log_fatal_stream() { return RustLogStream(RUST_LOG_LEVEL_FATAL); }
#define RUST_COUT() rust_log_info_stream()
#define RUST_CERR() rust_log_error_stream()
#define RUST_CDEBUG() rust_log_debug_stream()
// Usage example:
// RUST_COUT() << "User=" << user << " logged in" << std::endl;

View File

@@ -15,7 +15,7 @@ extern "C" {
int pam_sm_authenticate(pam_handle_t* pamh, int flags, int argc, const char** argv) {
try {
rust_init_logging(nullptr); // Ensure logger is initialized
RUST_LOG_INFO("PAM authentication attempt");
RUST_CDEBUG() << "PAM authentication attempt" << std::flush;
const char* user = nullptr;

View File

@@ -7,6 +7,7 @@
use std::ffi::CStr;
use std::os::raw::c_char;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::env;
mod auth;
mod logging;
@@ -15,14 +16,19 @@ mod error;
#[no_mangle]
pub extern "C" fn rust_init_logging(log_path: *const c_char) {
let _ = catch_unwind(AssertUnwindSafe(|| {
let path = unsafe {
if log_path.is_null() {
"/var/log/pam_rust_backend.log"
let env_path = env::var("PAM_RUST_LOG_PATH").ok().and_then(|s| if s.is_empty() { None } else { Some(s) });
let path = if let Some(p) = env_path {
p
} else {
CStr::from_ptr(log_path).to_str().unwrap_or("/var/log/pam_rust_backend.log")
unsafe {
if log_path.is_null() {
"/var/log/pam_rust_backend.log".to_string()
} else {
CStr::from_ptr(log_path).to_str().unwrap_or("/var/log/pam_rust_backend.log").to_string()
}
}
};
logging::init_logger(path);
logging::init_logger(&path);
}));
}