From 54137bdf86a1695ee2a7ef8f6e507477a17873d4 Mon Sep 17 00:00:00 2001 From: Admin Date: Thu, 24 Sep 2026 09:56:17 +0000 Subject: [PATCH] fix(linux): move captures out of protected CUPS cache --- package.json | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/capture.rs | 2 +- src-tauri/src/printers.rs | 64 +++++++++++++++++++++++++++++++++++---- src-tauri/tauri.conf.json | 2 +- src/main.ts | 2 +- 6 files changed, 63 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index e4f3fbe..8ff2472 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fieldlens/print", - "version": "0.1.14", + "version": "0.1.15", "private": true, "type": "module", "scripts": { diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9356530..84e3242 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fieldlens-print" -version = "0.1.14" +version = "0.1.15" description = "FieldLens print-stream barcode capture companion" authors = ["Arrowstack"] edition = "2021" diff --git a/src-tauri/src/capture.rs b/src-tauri/src/capture.rs index a1a0208..5de9d19 100644 --- a/src-tauri/src/capture.rs +++ b/src-tauri/src/capture.rs @@ -25,7 +25,7 @@ use crate::{ }; #[cfg(target_os = "linux")] -const SYSTEM_CUPS_SPOOL_DIR: &str = "/var/cache/cups/fieldlens-print"; +const SYSTEM_CUPS_SPOOL_DIR: &str = "/var/lib/fieldlens-print/capture"; #[cfg(target_os = "linux")] const TEST_CAPTURE_SINK_ADDR: &str = "127.0.0.1:9101"; diff --git a/src-tauri/src/printers.rs b/src-tauri/src/printers.rs index 5aba538..01fb1f1 100644 --- a/src-tauri/src/printers.rs +++ b/src-tauri/src/printers.rs @@ -20,14 +20,14 @@ const TEST_CAPTURE_DEVICE_URI: &str = "socket://127.0.0.1:9101"; const WINDOWS_PORT_NAME: &str = "FieldLensPrint-9100"; #[cfg(target_os = "linux")] -const FIELDLENS_CUPS_SPOOL_DIR: &str = "/var/cache/cups/fieldlens-print"; +const FIELDLENS_CUPS_SPOOL_DIR: &str = "/var/lib/fieldlens-print/capture"; #[cfg(target_os = "linux")] const FIELDLENS_CUPS_FILTER: &str = r#"#!/usr/bin/python3 import os import re import sys -CACHE = os.path.join(os.environ.get("CUPS_CACHEDIR", "/var/cache/cups"), "fieldlens-print") +CACHE = "/var/lib/fieldlens-print/capture"\nOWNER_UID = __FIELDLENS_OWNER_UID__\nOWNER_GID = __FIELDLENS_OWNER_GID__ def safe(value): value = re.sub(r"[^A-Za-z0-9._-]+", "-", value or "job").strip("-") @@ -84,7 +84,13 @@ try: capture.flush() capture.close() capture = None - os.chmod(part_path, 0o644) + os.chmod(part_path, 0o600) + try: + os.chown(part_path, OWNER_UID, OWNER_GID) + except PermissionError: + # On this Linux lane CUPS runs filters as root. Keep printing alive + # if a future CUPS policy drops privileges; repair can surface it. + pass os.replace(part_path, final_path) captured_ok = True print(f"INFO: FieldLens captured {name}", file=sys.stderr) @@ -111,9 +117,12 @@ target_printer = sys.argv[1] target_port = sys.argv[2] temp_filter = sys.argv[3] filter_path = sys.argv[4] +owner_uid = int(sys.argv[5]) +owner_gid = int(sys.argv[6]) source_ppd = os.path.join("/etc/cups/ppd", f"{target_printer}.ppd") -spool_dir = "/var/cache/cups/fieldlens-print" +spool_dir = "/var/lib/fieldlens-print/capture" +old_spool_dir = "/var/cache/cups/fieldlens-print" with open(source_ppd, "r", encoding="utf-8", errors="replace") as source: original = source.read() @@ -142,7 +151,31 @@ try: target.write(cleaned) subprocess.run(["install", "-m", "0755", temp_filter, filter_path], check=True) - subprocess.run(["install", "-d", "-m", "0777", spool_dir], check=True) + + os.makedirs(spool_dir, exist_ok=True) + os.chown(spool_dir, owner_uid, owner_gid) + os.chmod(spool_dir, 0o700) + + # Bring forward captures stranded by 0.1.13/0.1.14 in CUPS' protected + # cache. Ignore directories and partial writes. + if os.path.isdir(old_spool_dir): + for name in os.listdir(old_spool_dir): + source = os.path.join(old_spool_dir, name) + if not os.path.isfile(source) or name.endswith(".part"): + continue + target = os.path.join(spool_dir, name) + if os.path.exists(target): + root, ext = os.path.splitext(name) + target = os.path.join(spool_dir, f"{root}-migrated{ext}") + try: + os.replace(source, target) + except OSError: + continue + try: + os.chown(target, owner_uid, owner_gid) + os.chmod(target, 0o600) + except OSError: + pass subprocess.run([ "lpadmin", "-p", "FieldLens-Print", "-E", @@ -685,7 +718,24 @@ fn install_linux_virtual_printer(target_printer: &str) -> Result<()> { uuid::Uuid::new_v4() )); - fs::write(&temp_filter, FIELDLENS_CUPS_FILTER) + let uid = Command::new("id") + .arg("-u") + .output() + .context("read current Linux user id")?; + let gid = Command::new("id") + .arg("-g") + .output() + .context("read current Linux group id")?; + if !uid.status.success() || !gid.status.success() { + return Err(anyhow!("could not determine the current Linux user for capture ownership")); + } + let uid = String::from_utf8_lossy(&uid.stdout).trim().to_string(); + let gid = String::from_utf8_lossy(&gid.stdout).trim().to_string(); + + let filter_script = FIELDLENS_CUPS_FILTER + .replace("__FIELDLENS_OWNER_UID__", &uid) + .replace("__FIELDLENS_OWNER_GID__", &gid); + fs::write(&temp_filter, filter_script) .context("write FieldLens CUPS capture filter")?; fs::write(&temp_helper, FIELDLENS_CUPS_REPAIR_HELPER) .context("write FieldLens CUPS repair helper")?; @@ -697,6 +747,8 @@ fn install_linux_virtual_printer(target_printer: &str) -> Result<()> { .arg(&target.port) .arg(&temp_filter) .arg(&filter_path) + .arg(&uid) + .arg(&gid) .status() .context("run elevated FieldLens CUPS repair")?; diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 6f6b09c..ca47c2e 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "FieldLens Print", - "version": "0.1.14", + "version": "0.1.15", "identifier": "dev.arrowstack.fieldlens.print", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/main.ts b/src/main.ts index 140e168..2f3e603 100644 --- a/src/main.ts +++ b/src/main.ts @@ -85,7 +85,7 @@ let config: AppConfig let jobs: Job[] = [] let status: Status let loginTimer: number | undefined -let currentVersion = '0.1.14' +let currentVersion = '0.1.15' let bundleType: BundleType | null = null let availableUpdate: Update | null = null let availableManualUpdate: ManualUpdate | null = null