tipc-log 0.1.0

Trusty log backend for TIPC (replaces trusty-log)
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 KylinSoft Co., Ltd. <https://www.kylinos.cn/>
// See LICENSES for license details.

//! Trusty simple logger backend (replaces `trusty-log`).
//!
//! Logs to stderr based on a compile-time configured log level.
//! In `no_std` (TEE) builds, writes directly via the `writev` syscall
//! without depending on `libc-trusty` or `libstd-rust`.
#![allow(unsafe_op_in_unsafe_fn)]
#![no_std]

use core::sync::atomic::{AtomicBool, Ordering};

use log::{Level, Log, Metadata, Record};

// I/O backend

mod io {
    use core::ffi::c_void;

    /// `iovec` struct matching the kernel's `struct iovec`.
    #[repr(C)]
    struct Iovec {
        iov_base: *const c_void,
        iov_len: usize,
    }

    /// `writev` syscall number.
    ///
    /// - LK/Trusty kernel: `0x01` (architecture-independent)
    /// - x-kernel (aarch64 Linux): `66`
    /// - x-kernel (x86_64 Linux): `20`
    #[cfg(feature = "lk")]
    const SYS_WRITEV: usize = 0x01;
    #[cfg(all(not(feature = "lk"), target_arch = "aarch64"))]
    const SYS_WRITEV: usize = 66;
    #[cfg(all(not(feature = "lk"), target_arch = "x86_64"))]
    const SYS_WRITEV: usize = 20;

    /// Write `data` to file descriptor `fd` via the `writev` syscall.
    ///
    /// # Safety
    ///
    /// `data` must be a valid memory region of `data.len()` bytes.
    #[cfg(target_arch = "aarch64")]
    pub unsafe fn write(fd: i32, data: &[u8]) {
        let iov = Iovec { iov_base: data.as_ptr() as *const c_void, iov_len: data.len() };
        // SAFETY: `iov` is valid and `writev` is a trusted kernel syscall.
        //
        // LK uses `x12` for the syscall number; x-kernel uses `x8`
        // (standard AArch64 Linux convention).
        #[cfg(feature = "lk")]
        core::arch::asm!(
            "svc #0",
            in("x12") SYS_WRITEV,
            inlateout("x0") fd => _,
            in("x1") &iov,
            in("x2") 1usize,
            options(nostack)
        );
        #[cfg(not(feature = "lk"))]
        core::arch::asm!(
            "svc #0",
            in("x8") SYS_WRITEV,
            inlateout("x0") fd => _,
            in("x1") &iov,
            in("x2") 1usize,
            options(nostack)
        );
    }

    /// Write `data` to file descriptor `fd` via the `writev` syscall.
    ///
    /// # Safety
    ///
    /// `data` must be a valid memory region of `data.len()` bytes.
    #[cfg(target_arch = "x86_64")]
    pub unsafe fn write(fd: i32, data: &[u8]) {
        let iov = Iovec { iov_base: data.as_ptr() as *const c_void, iov_len: data.len() };
        // SAFETY: `iov` is valid and `writev` is a trusted kernel syscall.
        // x86_64 uses `rax` for the syscall number in both LK and x-kernel.
        // `rcx` and `r11` are clobbered by the `syscall` instruction.
        core::arch::asm!(
            "syscall",
            inlateout("rax") SYS_WRITEV => _,
            in("rdi") fd as usize,
            in("rsi") &iov,
            in("rdx") 1usize,
            out("rcx") _,
            out("r11") _,
            options(nostack)
        );
    }
}

// Logger

/// Closure type for custom log formatting.
pub type FormatFn = alloc::boxed::Box<dyn Fn(&Record) -> alloc::string::String + Sync + Send>;

extern crate alloc;

/// Logger configuration.
pub struct TrustyLoggerConfig {
    log_level: log::Level,
    custom_format: Option<FormatFn>,
}

impl TrustyLoggerConfig {
    pub const fn new() -> Self {
        TrustyLoggerConfig { log_level: Level::Info, custom_format: None }
    }

    pub fn with_min_level(mut self, level: log::Level) -> Self {
        self.log_level = level;
        self
    }

    pub fn format<F>(mut self, format: F) -> Self
    where
        F: Fn(&Record) -> alloc::string::String + Sync + Send + 'static,
    {
        self.custom_format = Some(alloc::boxed::Box::new(format));
        self
    }
}

impl Default for TrustyLoggerConfig {
    fn default() -> Self {
        TrustyLoggerConfig::new()
    }
}

/// The global logger instance.
pub struct TrustyLogger {
    config: TrustyLoggerConfig,
}

impl TrustyLogger {
    pub const fn new(config: TrustyLoggerConfig) -> Self {
        TrustyLogger { config }
    }
}

impl Log for TrustyLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= self.config.log_level
    }

    fn log(&self, record: &Record) {
        if self.enabled(record.metadata()) {
            let message = match &self.config.custom_format {
                Some(fmt_fn) => fmt_fn(record),
                None => alloc::format!("{} - {}\n", record.level(), record.args()),
            };
            // SAFETY: `message` is a valid byte slice; `writev` writes to stderr (fd=2).
            unsafe {
                io::write(2, message.as_bytes());
            }
        }
    }

    fn flush(&self) {}
}

// Global logger initialization

static LOGGER_INITIALIZED: AtomicBool = AtomicBool::new(false);

/// Initialize the global logger with default configuration (`Level::Info`).
pub fn init() {
    init_with_config(TrustyLoggerConfig::default());
}

/// Initialize the global logger with custom configuration.
///
/// If the logger is already initialized, this is a no-op (matching official
/// `trusty-log` semantics via `OnceLock::get_or_init`).
pub fn init_with_config(config: TrustyLoggerConfig) {
    if LOGGER_INITIALIZED.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_ok()
    {
        let log_level_filter = config.log_level.to_level_filter();
        // SAFETY: `Box::leak` extends the logger's lifetime to `'static`.
        // This branch only runs once due to the `AtomicBool` guard above.
        let logger = alloc::boxed::Box::leak(alloc::boxed::Box::new(TrustyLogger::new(config)));
        log::set_logger(logger).expect("Could not set global logger");
        log::set_max_level(log_level_filter);
    }
}