fix(sync): stop uvc helper from guessing /dev/video0
This commit is contained in:
parent
b65bdfb259
commit
1462f736f4
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1642,7 +1642,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.14.39"
|
version = "0.14.40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1676,7 +1676,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.14.39"
|
version = "0.14.40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1688,7 +1688,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.14.39"
|
version = "0.14.40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.14.39"
|
version = "0.14.40"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.14.39"
|
version = "0.14.40"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
|||||||
@ -8,9 +8,42 @@ if [[ -r /etc/lesavka/uvc.env ]]; then
|
|||||||
source /etc/lesavka/uvc.env
|
source /etc/lesavka/uvc.env
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DEV=${LESAVKA_UVC_DEV:-/dev/v4l/by-path/platform-1000480000.usb-video-index0}
|
resolve_default_uvc_dev() {
|
||||||
if [[ ! -e "$DEV" ]]; then
|
local ctrl=""
|
||||||
DEV=/dev/video0
|
ctrl=$(ls /sys/class/udc 2>/dev/null | head -n1 || true)
|
||||||
|
if [[ -n $ctrl ]]; then
|
||||||
|
printf '/dev/v4l/by-path/platform-%s-video-index0\n' "$ctrl"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local candidate=""
|
||||||
|
candidate=$(ls /dev/v4l/by-path/platform-*-video-index0 2>/dev/null | head -n1 || true)
|
||||||
|
[[ -n $candidate ]] || return 1
|
||||||
|
printf '%s\n' "$candidate"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_uvc_dev() {
|
||||||
|
local dev="$1"
|
||||||
|
for _ in {1..50}; do
|
||||||
|
[[ -e $dev ]] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -n ${LESAVKA_UVC_DEV:-} ]]; then
|
||||||
|
DEV=${LESAVKA_UVC_DEV}
|
||||||
|
else
|
||||||
|
DEV=$(resolve_default_uvc_dev || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z ${DEV:-} ]]; then
|
||||||
|
echo "[lesavka-uvc] no gadget video_output node discovered; waiting for /dev/v4l/by-path/platform-*-video-index0 or set LESAVKA_UVC_DEV" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! wait_for_uvc_dev "$DEV"; then
|
||||||
|
echo "[lesavka-uvc] gadget video_output node is still absent: $DEV" >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec /usr/local/bin/lesavka-uvc --device "$DEV"
|
exec /usr/local/bin/lesavka-uvc --device "$DEV"
|
||||||
|
|||||||
@ -10,7 +10,7 @@ bench = false
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.14.39"
|
version = "0.14.40"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,20 @@ fn uvc_by_path_root() -> String {
|
|||||||
std::env::var("LESAVKA_UVC_BY_PATH_ROOT").unwrap_or_else(|_| "/dev/v4l/by-path".to_string())
|
std::env::var("LESAVKA_UVC_BY_PATH_ROOT").unwrap_or_else(|_| "/dev/v4l/by-path".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn any_platform_uvc_by_path() -> Option<String> {
|
||||||
|
let root = uvc_by_path_root();
|
||||||
|
let root = Path::new(&root);
|
||||||
|
let entries = std::fs::read_dir(root).ok()?;
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let name = entry.file_name();
|
||||||
|
let name = name.to_string_lossy();
|
||||||
|
if name.starts_with("platform-") && name.ends_with("-video-index0") {
|
||||||
|
return Some(entry.path().to_string_lossy().into_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Pick the UVC gadget video node.
|
/// Pick the UVC gadget video node.
|
||||||
///
|
///
|
||||||
/// Inputs: none; the function inspects environment overrides and udev state.
|
/// Inputs: none; the function inspects environment overrides and udev state.
|
||||||
@ -51,7 +65,10 @@ pub fn pick_uvc_device() -> anyhow::Result<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut fallback: Option<String> = None;
|
if let Some(by_path) = any_platform_uvc_by_path() {
|
||||||
|
return Ok(by_path);
|
||||||
|
}
|
||||||
|
|
||||||
if std::env::var("LESAVKA_UVC_SKIP_UDEV").is_err()
|
if std::env::var("LESAVKA_UVC_SKIP_UDEV").is_err()
|
||||||
&& let Ok(mut enumerator) = udev::Enumerator::new()
|
&& let Ok(mut enumerator) = udev::Enumerator::new()
|
||||||
{
|
{
|
||||||
@ -82,19 +99,12 @@ pub fn pick_uvc_device() -> anyhow::Result<String> {
|
|||||||
{
|
{
|
||||||
return Ok(node);
|
return Ok(node);
|
||||||
}
|
}
|
||||||
if fallback.is_none() {
|
|
||||||
fallback = Some(node);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(node) = fallback {
|
|
||||||
return Ok(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(anyhow::anyhow!(
|
Err(anyhow::anyhow!(
|
||||||
"no video_output v4l2 node found; set LESAVKA_UVC_DEV"
|
"no Lesavka video_output v4l2 node found; wait for /dev/v4l/by-path/platform-<udc>-video-index0 or set LESAVKA_UVC_DEV"
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -149,6 +149,37 @@ fn pick_uvc_device_prefers_controller_by_path_override_root() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn pick_uvc_device_uses_platform_by_path_when_controller_is_unknown() {
|
||||||
|
let dir = tempdir().expect("tempdir");
|
||||||
|
let sys_root = dir.path().join("sys");
|
||||||
|
let by_path = dir.path().join("v4l/by-path");
|
||||||
|
fs::create_dir_all(&sys_root).expect("create fake sys root");
|
||||||
|
fs::create_dir_all(&by_path).expect("create fake by-path root");
|
||||||
|
let expected = by_path.join("platform-fallback.usb-video-index0");
|
||||||
|
fs::write(&expected, "").expect("touch by-path node");
|
||||||
|
|
||||||
|
temp_env::with_var(
|
||||||
|
"LESAVKA_GADGET_SYSFS_ROOT",
|
||||||
|
Some(sys_root.to_string_lossy().to_string()),
|
||||||
|
|| {
|
||||||
|
temp_env::with_var(
|
||||||
|
"LESAVKA_UVC_BY_PATH_ROOT",
|
||||||
|
Some(by_path.to_string_lossy().to_string()),
|
||||||
|
|| {
|
||||||
|
temp_env::with_var("LESAVKA_UVC_SKIP_UDEV", Some("1"), || {
|
||||||
|
temp_env::with_var("LESAVKA_UVC_DEV", None::<&str>, || {
|
||||||
|
let picked = pick_uvc_device().expect("pick platform by-path device");
|
||||||
|
assert_eq!(picked, expected.to_string_lossy());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn pick_uvc_device_errors_when_overrides_disable_all_discovery_paths() {
|
fn pick_uvc_device_errors_when_overrides_disable_all_discovery_paths() {
|
||||||
@ -169,7 +200,14 @@ fn pick_uvc_device_errors_when_overrides_disable_all_discovery_paths() {
|
|||||||
temp_env::with_var("LESAVKA_UVC_SKIP_UDEV", Some("1"), || {
|
temp_env::with_var("LESAVKA_UVC_SKIP_UDEV", Some("1"), || {
|
||||||
temp_env::with_var("LESAVKA_UVC_DEV", None::<&str>, || {
|
temp_env::with_var("LESAVKA_UVC_DEV", None::<&str>, || {
|
||||||
let err = pick_uvc_device().expect_err("missing paths should error");
|
let err = pick_uvc_device().expect_err("missing paths should error");
|
||||||
assert!(err.to_string().contains("LESAVKA_UVC_DEV"));
|
assert!(
|
||||||
|
err.to_string().contains("LESAVKA_UVC_DEV"),
|
||||||
|
"error should mention explicit override escape hatch: {err:#}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
err.to_string().contains("platform-<udc>-video-index0"),
|
||||||
|
"error should point operators at the gadget by-path node: {err:#}"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
32
testing/tests/server_uvc_script_contract.rs
Normal file
32
testing/tests/server_uvc_script_contract.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//! Contract tests for the standalone UVC launcher shell script.
|
||||||
|
//!
|
||||||
|
//! Scope: statically guard `scripts/daemon/lesavka-uvc.sh`.
|
||||||
|
//! Targets: gadget-node discovery and wrong-device avoidance.
|
||||||
|
//! Why: falling back to an unrelated `/dev/video0` can wedge the helper in
|
||||||
|
//! kernel space and poison later gadget recovery loops.
|
||||||
|
|
||||||
|
const UVC_SCRIPT: &str = include_str!("../../scripts/daemon/lesavka-uvc.sh");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uvc_script_waits_for_gadget_by_path_node() {
|
||||||
|
for expected in [
|
||||||
|
"resolve_default_uvc_dev()",
|
||||||
|
"wait_for_uvc_dev()",
|
||||||
|
"platform-%s-video-index0",
|
||||||
|
"platform-*-video-index0",
|
||||||
|
"gadget video_output node is still absent",
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
UVC_SCRIPT.contains(expected),
|
||||||
|
"lesavka-uvc launcher guard missing: {expected}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uvc_script_refuses_unrelated_video0_fallback() {
|
||||||
|
assert!(
|
||||||
|
!UVC_SCRIPT.contains("DEV=/dev/video0"),
|
||||||
|
"lesavka-uvc launcher must not fall back to an unrelated /dev/video0"
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user