Repair ClamAV runtime PID path

This commit is contained in:
Mario Fetka
2026-07-19 08:09:13 +02:00
parent 819fa95ee1
commit 29b3fb2a6d
2 changed files with 40 additions and 1 deletions
@@ -59,6 +59,7 @@ class ScannerConfigurationTest(unittest.TestCase):
original = (
"# Distribution configuration\n"
"LocalSocket /run/clamav/clamd.sock\n"
"PidFile /run/clamd.pid\n"
"TCPAddr 0.0.0.0\n"
"TCPSocket 9999\n"
"TCPAddr 192.0.2.2\n"
@@ -66,6 +67,7 @@ class ScannerConfigurationTest(unittest.TestCase):
)
edited = edit_clamd_configuration(original)
self.assertIn("LocalSocket /run/clamav/clamd.sock", edited)
self.assertIn("PidFile /run/clamav/clamd.pid", edited)
self.assertIn("LogTime yes", edited)
self.assertIn("TCPAddr 127.0.0.1", edited)
self.assertIn("TCPSocket 3310", edited)
@@ -30,7 +30,7 @@ import shutil
import subprocess
import time
from dataclasses import dataclass, replace
from pathlib import Path
from pathlib import Path, PurePosixPath
from typing import Callable, Mapping, Sequence
from .probes import ProbeResult, probe_clamav, probe_spamd
@@ -230,9 +230,46 @@ def _read_configuration(path: Path) -> str:
from error
def _clamd_runtime_pid_file(content: str) -> str | None:
"""Move a root-level runtime PID beside clamd's private socket.
Several distribution configurations still use ``/run/clamd.pid`` while
telling clamd to drop privileges. The daemon can create that file while
privileged, but cannot remove it during a clean shutdown. Reuse the
distribution's existing LocalSocket directory instead of hard-coding a
distro-specific runtime directory.
"""
values: dict[str, str] = {}
for line in content.splitlines():
if not line.strip() or line.lstrip().startswith("#"):
continue
try:
fields = shlex.split(line, comments=True, posix=True)
except ValueError:
continue
if len(fields) >= 2 and fields[0].lower() in {"localsocket", "pidfile"}:
values.setdefault(fields[0].lower(), fields[1])
if "localsocket" not in values or "pidfile" not in values:
return None
socket = PurePosixPath(values["localsocket"])
pid = PurePosixPath(values["pidfile"])
if not socket.is_absolute() or not pid.is_absolute():
return None
runtime_roots = {PurePosixPath("/run"), PurePosixPath("/var/run")}
if pid.parent not in runtime_roots or socket.parent in runtime_roots:
return None
if not any(root in socket.parents for root in runtime_roots):
return None
return str(socket.parent / "clamd.pid")
def edit_clamd_configuration(content: str) -> str:
"""Return clamd configuration with one safe active value per setting."""
desired = dict(CLAMD_SETTINGS)
runtime_pid_file = _clamd_runtime_pid_file(content)
if runtime_pid_file is not None:
desired["PidFile"] = runtime_pid_file
canonical = {name.lower(): name for name in desired}
seen: set[str] = set()
output: list[str] = []