fix: harden CLI file I/O and AV1 tooling (SonarCloud)

- Read and write CLI inputs/outputs on a blocking pool instead of stalling
  the async runtime (rust:S7493).
- Document the FLAC STREAMINFO MD5 as the RFC 9639 integrity signature it
  is, and validate the AV1 generator's spec path before reading it.
- Normalize the DCT basis literals that read as numeric suffixes.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
Dylan M. Taylor
2026-10-23 09:53:17 -04:00
parent abf44fda90
commit 689e37782a
6 changed files with 45 additions and 19 deletions
+22 -3
View File
@@ -137,6 +137,25 @@ fn usage(msg: impl std::fmt::Display) -> ! {
std::process::exit(2)
}
/// Read a file off the runtime thread: `cli` is async because MCP, bridge calls and control
/// commands await, so a blocking `std::fs` read would stall every other task on that thread.
async fn read_file(path: impl Into<std::path::PathBuf>) -> std::io::Result<Vec<u8>> {
let path = path.into();
tokio::task::spawn_blocking(move || std::fs::read(path)).await.map_err(std::io::Error::other)?
}
/// `read_file` for UTF-8 text.
async fn read_text(path: impl Into<std::path::PathBuf>) -> std::io::Result<String> {
let path = path.into();
tokio::task::spawn_blocking(move || std::fs::read_to_string(path)).await.map_err(std::io::Error::other)?
}
/// Write a file off the runtime thread; see `read_file`.
async fn write_file(path: impl Into<std::path::PathBuf>, data: Vec<u8>) -> std::io::Result<()> {
let path = path.into();
tokio::task::spawn_blocking(move || std::fs::write(path, data)).await.map_err(std::io::Error::other)?
}
fn fail(msg: impl std::fmt::Display) -> ! {
diag(format_args!("filmcraft-cli: {msg}"));
std::process::exit(1)
@@ -307,7 +326,7 @@ async fn cli() {
"bench-decode" => {
let path = a.pos(1).unwrap_or_else(|| usage("bench-decode <media>"));
let n: i64 = a.opt("--frames").and_then(|v| v.parse().ok()).unwrap_or(120);
let src = filmcraft_codecs::open_bytes(path, std::fs::read(path).unwrap_or_else(|e| fail(e)).into()).unwrap_or_else(|e| fail(e));
let src = filmcraft_codecs::open_bytes(path, read_file(path).await.unwrap_or_else(|e| fail(e)).into()).unwrap_or_else(|e| fail(e));
let rate = src.info().frame_rate();
let t0 = std::time::Instant::now();
for f in 0..n {
@@ -363,7 +382,7 @@ async fn cli() {
let text = if script == "-" {
std::io::read_to_string(std::io::stdin()).unwrap_or_else(|e| fail(e))
} else {
std::fs::read_to_string(script).unwrap_or_else(|e| fail(format!("{script}: {e}")))
read_text(script).await.unwrap_or_else(|e| fail(format!("{script}: {e}")))
};
let mut b = Backend::open(&a);
let mut failed = 0;
@@ -487,7 +506,7 @@ async fn cli() {
let (img, offline) = render_at_playhead(&s, scale).unwrap_or_else(|e| fail(e));
let dt = t0.elapsed();
let png = filmcraft_automation::png_rgba(img.w as u32, img.h as u32, img.over_black_rgba8(), 0).unwrap_or_else(|e| fail(e));
std::fs::write(out, png).unwrap_or_else(|e| fail(format!("{out}: {e}")));
write_file(out, png).await.unwrap_or_else(|e| fail(format!("{out}: {e}")));
let offline: Vec<Value> = offline.iter().filter_map(|o| serde_json::to_value(o).ok()).collect();
check_offline(&a, &offline, &format!("render: {out}"));
diag(format_args!("rendered {}x{} in {:.1} ms → {out}", img.w, img.h, dt.as_secs_f64() * 1000.0));
+9 -5
View File
@@ -20,15 +20,19 @@ import sys
from pathlib import Path
def _spec_path(arg):
"""Resolve a CLI path argument and require it to be an existing regular file."""
def _spec_dir(arg):
"""Resolve the CLI argument and require it to be an existing directory."""
path = Path(arg).resolve()
if not path.is_file():
sys.exit(f"not a readable file: {arg}")
if not path.is_dir():
sys.exit(f"not a directory: {arg}")
return path
root = sys.argv[1]
if len(sys.argv) != 2:
sys.exit("usage: extract_tables.py <av1-spec checkout>")
# The argument is the operator's own spec checkout; _spec_dir requires it to be an existing
# directory before anything is read from it.
root = _spec_dir(sys.argv[1]) # NOSONAR
files = sorted(f for f in os.listdir(root) if f.endswith(".md"))
texts = {f: open(os.path.join(root, f), encoding="utf-8").read() for f in files}
+4 -4
View File
@@ -226,13 +226,13 @@ pub(crate) fn vlc(i: usize) -> &'static VlcSet {
/// Orthonormal 1-D DCT-II basis: `BASIS[u][x] = C(u)/2 · cos((2x+1)uπ/16)`, `C(0) = 1/√2`.
pub(crate) const BASIS: [[f32; 8]; 8] = [
[0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39],
[0.490_392_64, 0.415_734_8, 0.277_785_12, 0.097_545_16, -0.097_545_16, -0.277_785_12, -0.415_734_8, -0.490_392_64],
[0.49039264, 0.415_734_8, 0.277_785_12, 0.097_545_16, -0.097_545_16, -0.277_785_12, -0.415_734_8, -0.49039264],
[0.461_939_77, 0.191_341_72, -0.191_341_72, -0.461_939_77, -0.461_939_77, -0.191_341_72, 0.191_341_72, 0.461_939_77],
[0.415_734_8, -0.097_545_16, -0.490_392_64, -0.277_785_12, 0.277_785_12, 0.490_392_64, 0.097_545_16, -0.415_734_8],
[0.415_734_8, -0.097_545_16, -0.49039264, -0.277_785_12, 0.277_785_12, 0.49039264, 0.097_545_16, -0.415_734_8],
[0.353_553_39, -0.353_553_39, -0.353_553_39, 0.353_553_39, 0.353_553_39, -0.353_553_39, -0.353_553_39, 0.353_553_39],
[0.277_785_12, -0.490_392_64, 0.097_545_16, 0.415_734_8, -0.415_734_8, -0.097_545_16, 0.490_392_64, -0.277_785_12],
[0.277_785_12, -0.49039264, 0.097_545_16, 0.415_734_8, -0.415_734_8, -0.097_545_16, 0.49039264, -0.277_785_12],
[0.191_341_72, -0.461_939_77, 0.461_939_77, -0.191_341_72, -0.191_341_72, 0.461_939_77, -0.461_939_77, 0.191_341_72],
[0.097_545_16, -0.277_785_12, 0.415_734_8, -0.490_392_64, 0.490_392_64, -0.415_734_8, 0.277_785_12, -0.097_545_16],
[0.097_545_16, -0.277_785_12, 0.415_734_8, -0.49039264, 0.49039264, -0.415_734_8, 0.277_785_12, -0.097_545_16],
];
#[cfg(test)]
+3 -1
View File
@@ -111,7 +111,9 @@ impl Encoder {
total_samples: 0,
min_frame: 0,
max_frame: 0,
md5: md5::Md5::default(),
// RFC 9639 requires an MD5 signature of the unencoded audio in STREAMINFO; this is an
// integrity checksum, not a password hash or a security boundary.
md5: md5::Md5::default(), // NOSONAR
})
}
+3 -2
View File
@@ -65,8 +65,9 @@ fn atomic_write_new_with(path: &Path, write: impl FnOnce(&mut File) -> io::Resul
#[cfg(unix)]
let mut staged = {
use std::os::unix::fs::PermissionsExt;
// Match ordinary file-creation permissions after the process umask.
tempfile::Builder::new().permissions(fs::Permissions::from_mode(0o666)).tempfile_in(&dir)?
// Like an ordinary file creation: request 0666 and let the process umask decide the
// effective mode (0644 with the usual 022, 0664 with 002).
tempfile::Builder::new().permissions(fs::Permissions::from_mode(0o666)).tempfile_in(&dir)? // NOSONAR
};
#[cfg(not(unix))]
let mut staged = tempfile::NamedTempFile::new_in(&dir)?;
+4 -4
View File
@@ -88,13 +88,13 @@ pub(crate) fn qscale(qidx: u8) -> u32 {
/// Orthonormal 1-D DCT-II basis: `BASIS[u][x] = C(u)/2 · cos((2x+1)uπ/16)`, `C(0) = 1/√2`.
pub(crate) const BASIS: [[f32; 8]; 8] = [
[0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39],
[0.490_392_64, 0.415_734_8, 0.277_785_12, 0.097_545_16, -0.097_545_16, -0.277_785_12, -0.415_734_8, -0.490_392_64],
[0.49039264, 0.415_734_8, 0.277_785_12, 0.097_545_16, -0.097_545_16, -0.277_785_12, -0.415_734_8, -0.49039264],
[0.461_939_77, 0.191_341_72, -0.191_341_72, -0.461_939_77, -0.461_939_77, -0.191_341_72, 0.191_341_72, 0.461_939_77],
[0.415_734_8, -0.097_545_16, -0.490_392_64, -0.277_785_12, 0.277_785_12, 0.490_392_64, 0.097_545_16, -0.415_734_8],
[0.415_734_8, -0.097_545_16, -0.49039264, -0.277_785_12, 0.277_785_12, 0.49039264, 0.097_545_16, -0.415_734_8],
[0.353_553_39, -0.353_553_39, -0.353_553_39, 0.353_553_39, 0.353_553_39, -0.353_553_39, -0.353_553_39, 0.353_553_39],
[0.277_785_12, -0.490_392_64, 0.097_545_16, 0.415_734_8, -0.415_734_8, -0.097_545_16, 0.490_392_64, -0.277_785_12],
[0.277_785_12, -0.49039264, 0.097_545_16, 0.415_734_8, -0.415_734_8, -0.097_545_16, 0.49039264, -0.277_785_12],
[0.191_341_72, -0.461_939_77, 0.461_939_77, -0.191_341_72, -0.191_341_72, 0.461_939_77, -0.461_939_77, 0.191_341_72],
[0.097_545_16, -0.277_785_12, 0.415_734_8, -0.490_392_64, 0.490_392_64, -0.415_734_8, 0.277_785_12, -0.097_545_16],
[0.097_545_16, -0.277_785_12, 0.415_734_8, -0.49039264, 0.49039264, -0.415_734_8, 0.277_785_12, -0.097_545_16],
];
#[cfg(test)]