startup: report PC/SC access denial instead of a silent serial fallback

When no reader was named and readers() failed (e.g. SCARD_E_NO_ACCESS -
the user may not use pcscd), the auto-detect swallowed the error and pySim
fell back to the Serial transport, failing on /dev/ttyUSB0 while the
server still printed "listening".  The startup now probes PC/SC,
classifies the failure by hresult (access/service/reader), reports the
cause with the pcscd group/polkit and pcsc_scan guidance and exits 2; the
python -m guard uses sys.exit(main()).  An explicit -d/--device keeps the
serial path; a reader without a card stays a normal cardless start; a
readerless start exits with its own message.

start.sh runs the same access pre-flight before launching (readers() with
a 3x retry for socket activation; -d is detected and skips it).  READMEs,
CHANGELOG (folded into the unreleased 3.38.1) and AGENTS updated.

Tests: tests/test_pcsc_startup.py (16: the error classifier, the probe,
the report guidance, the serial-arg detection and the source pins).
1083 Python + 1075 frontend.
This commit is contained in:
2026-10-25 00:29:26 +03:00
parent 0231df905c
commit a269f8bd2e
6 changed files with 333 additions and 31 deletions
+43 -7
View File
@@ -16,14 +16,50 @@ LOG_FILE="$SCRIPT_DIR/simple_lastrun.log"
exec > >(tee "$LOG_FILE") 2>&1
export PYTHONUNBUFFERED=1 # keep the log live: a piped stdout is block-buffered
# Auto-detect reader
# PC/SC pre-flight (v3.38.1): a user without access to pcscd must see the real
# cause - the server no longer falls back to /dev/ttyUSB0 silently. An
# explicit serial reader (-d/--device) keeps the serial path.
SERIAL=0
for arg in "$@"; do
case "$arg" in
-d|--device|--device=*|-d*) SERIAL=1 ;;
esac
done
READER_ARGS=""
if command -v pcscd > /dev/null 2>&1; then
# Give pcscd a moment if it's not running yet (USB enumeration delay)
if ! pgrep -x pcscd > /dev/null 2>&1 && ! pgrep -x pcscd.bin > /dev/null 2>&1; then
sleep 1
fi
if pgrep -x pcscd > /dev/null 2>&1 || pgrep -x pcscd.bin > /dev/null 2>&1; then
if [ "$SERIAL" -eq 0 ]; then
PYTHON_BIN="$VENV_DIR/bin/python"
[ -x "$PYTHON_BIN" ] || PYTHON_BIN="$(command -v python3 || true)"
if [ -n "$PYTHON_BIN" ]; then
PCSC_ERR="$("$PYTHON_BIN" - <<'PY' 2>&1
import time
from smartcard.System import readers
last = None
for _ in range(3):
try:
readers()
raise SystemExit(0)
except SystemExit:
raise
except Exception as e:
last = e
hr = getattr(e, 'hresult', None)
if hr == 0x80100027 or 'access denied' in str(e).lower():
break # permissions do not fix themselves
time.sleep(1) # pcscd may still be starting (socket activation)
hr = getattr(last, 'hresult', -1)
msg = str(last) or 'PC/SC error'
print(msg + ((' (hresult=0x%08X)' % hr) if hr not in (None, -1) else ''))
raise SystemExit(1)
PY
)" || {
echo "Error: PC/SC is not accessible: ${PCSC_ERR:-unknown error}"
echo " The server will not fall back to a serial reader silently."
echo " - add the user to the pcscd group: sudo usermod -aG pcscd \"$USER\" (then log out/in),"
echo " or configure the polkit rule that allows the user to use pcscd;"
echo " - 'sudo systemctl start pcscd' if the service is not running;"
echo " - 'pcsc_scan' shows the same 'Access denied' error when access is denied."
exit 2
}
READER_ARGS="-p 0"
fi
fi