Files
punktfunk/packaging/windows/drivers/pf-vdisplay/src/log.rs
T
enricobuehler bf577044f1 refactor(windows-drivers): pod_init! macro — 27 unsafe { mem::zeroed() } POD inits -> 1 (Goal-3 #3)
The driver zero-initialised C POD structs (IddCx/WDF descriptors) with 27
scattered `let mut x: T = unsafe { core::mem::zeroed() };`, each carrying its own
`// SAFETY` about the all-zero bit pattern being valid + the caller setting `.Size`
etc. right after.

Replace with one `pod_init!(T)` macro (in log.rs, reachable everywhere via the
existing `#[macro_use] mod log;` — same mechanism as `dbglog!`) that owns the
single `unsafe { zeroed::<T>() }` + the SAFETY rationale. All 27 sites
(adapter 6, callbacks 3, entry 4, monitor 10, swap_chain_processor 4) now read
`let mut x = pod_init!(T)`. Zero behavior change (mem::zeroed semantics identical);
the type is passed explicitly so no inference depends on the removed annotation.

27 `unsafe` blocks → 1. Driver still `deny(unsafe_op_in_unsafe_fn)`-clean (the
macro expands to an explicit `unsafe {}`; the one nested-in-user-unsafe site is
fine — no `unused_unsafe` for macro-generated blocks). Driver-only (CI-gated);
adversarially reviewed (macro scoping, all sites, no leftover raw zeroed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 06:01:02 +00:00

72 lines
3.1 KiB
Rust

//! Minimal driver logger. `OutputDebugStringA` always (ETW/DebugView); the optional world-writable file
//! (`C:\Users\Public\pfvd-driver.log`, readable over SSH) is now OPT-IN — debug builds, or the
//! `PFVD_DEBUG_LOG` env var, only — so a RELEASE build never writes it (audit §4.4: it was an
//! info-leak/DoS surface). Best-effort; ignores all errors. Production driver-state visibility is the
//! SharedHeader `driver_status` channel, not this file.
unsafe extern "system" {
fn OutputDebugStringA(s: *const u8);
}
/// Whether the world-writable bring-up file log is enabled (resolved once). Off in release builds unless
/// `PFVD_DEBUG_LOG` is set.
fn file_log_enabled() -> bool {
use std::sync::OnceLock;
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| cfg!(debug_assertions) || std::env::var_os("PFVD_DEBUG_LOG").is_some())
}
/// Process-lifetime append handle to the bring-up log, opened ONCE (by whichever thread logs first) and
/// shared via a `Mutex` — so the swap-chain WORKER thread's writes land too. Per-call open/append raced
/// the control thread and/or could fail under the worker's restricted token, hiding exactly the
/// swap-chain-processor lines a game-break repro needs (game-capture bug S3). `flush` after each line so a
/// crash/stall doesn't lose the tail.
fn file_appender() -> Option<&'static std::sync::Mutex<std::fs::File>> {
use std::sync::OnceLock;
static APPENDER: OnceLock<Option<std::sync::Mutex<std::fs::File>>> = OnceLock::new();
APPENDER
.get_or_init(|| {
if !file_log_enabled() {
return None;
}
std::fs::OpenOptions::new()
.create(true)
.append(true)
.open("C:\\Users\\Public\\pfvd-driver.log")
.ok()
.map(std::sync::Mutex::new)
})
.as_ref()
}
pub fn log(s: &str) {
if let Ok(c) = std::ffi::CString::new(s) {
// SAFETY: `c` is a valid NUL-terminated string for the duration of the call.
unsafe { OutputDebugStringA(c.as_ptr().cast()) };
}
use std::io::Write;
if let Some(m) = file_appender() {
if let Ok(mut f) = m.lock() {
let _ = writeln!(f, "{s}");
let _ = f.flush();
}
}
}
macro_rules! dbglog {
($($a:tt)*) => { $crate::log::log(&::std::format!($($a)*)) };
}
/// Zero-initialise a C POD struct (windows-rs / WDK / IddCx). These are `#[repr(C)]` framework structs
/// whose all-zero bit pattern is a valid zero-initialised value; the caller stamps the required
/// `.Size`/etc fields immediately after. Centralises the `unsafe { core::mem::zeroed() }` the IddCx/WDF
/// bring-up needs — pass the type EXPLICITLY (`pod_init!(T)`) so it works without a binding annotation.
/// Made crate-visible by the same `#[macro_use] mod log;` in `lib.rs` that exports `dbglog!`.
macro_rules! pod_init {
($t:ty) => {{
// SAFETY: $t is a C POD (windows-rs/WDK/IddCx struct); its all-zero bit pattern is a valid
// zero-initialised value and the caller sets the required .Size/etc fields immediately after.
unsafe { ::core::mem::zeroed::<$t>() }
}};
}