This commit is contained in:
Alexis Métaireau 2024-12-19 09:14:23 +00:00 committed by GitHub
commit 7a479aa6f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 71 additions and 7 deletions

View file

@ -42,6 +42,12 @@ def print_header(s: str) -> None:
type=click.UNPROCESSED,
callback=args.validate_input_filenames,
)
@click.option(
"--debug",
"debug",
flag_value=True,
help="Run Dangerzone in debug mode, to get logs from gVisor.",
)
@click.version_option(version=get_version(), message="%(version)s")
@errors.handle_document_errors
def cli_main(
@ -50,6 +56,7 @@ def cli_main(
filenames: List[str],
archive: bool,
dummy_conversion: bool,
debug: bool,
) -> None:
setup_logging()
@ -58,7 +65,7 @@ def cli_main(
elif is_qubes_native_conversion():
dangerzone = DangerzoneCore(Qubes())
else:
dangerzone = DangerzoneCore(Container())
dangerzone = DangerzoneCore(Container(debug=debug))
display_banner()
if len(filenames) == 1 and output_filename:

View file

@ -5,7 +5,9 @@ import platform
import signal
import subprocess
import sys
import threading
from abc import ABC, abstractmethod
from queue import Queue
from typing import IO, Callable, Iterator, Optional
import fitz
@ -86,11 +88,20 @@ class IsolationProvider(ABC):
Abstracts an isolation provider
"""
def __init__(self) -> None:
if getattr(sys, "dangerzone_dev", False) is True:
def __init__(self, debug: bool = False) -> None:
self.debug = debug
if self.should_capture_stderr():
self.proc_stderr = subprocess.PIPE
else:
self.proc_stderr = subprocess.DEVNULL
self.stderr_queue: Queue = Queue()
def should_capture_stderr(self) -> bool:
return self.debug or getattr(sys, "dangerzone_dev", False)
@staticmethod
def is_runtime_available() -> bool:
return True
@abstractmethod
def install(self) -> bool:
@ -344,9 +355,9 @@ class IsolationProvider(ABC):
)
# Read the stderr of the process only if:
# * Dev mode is enabled.
# * We're in debug mode
# * The process has exited (else we risk hanging).
if getattr(sys, "dangerzone_dev", False) and p.poll() is not None:
if self.should_capture_stderr() and p.poll() is not None:
assert p.stderr
debug_log = read_debug_text(p.stderr, MAX_CONVERSION_LOG_CHARS)
log.info(
@ -355,3 +366,23 @@ class IsolationProvider(ABC):
f"{debug_log}" # no need for an extra newline here
f"{DOC_TO_PIXELS_LOG_END}"
)
def _stream_stderr(self, stderr: IO[bytes], queue: Queue) -> None:
"""Read stderr in a separate thread to avoid blocking"""
try:
for line in stderr:
queue.put(line)
if self.debug:
log.debug(line.decode().strip())
except (ValueError, IOError) as e:
log.debug(f"Stderr stream closed: {e}")
def start_stderr_thread(self, process: subprocess.Popen) -> None:
"""Start a thread to read stderr from the process"""
if process.stderr:
stderr_thread = threading.Thread(
target=self._stream_stderr,
args=(process.stderr, self.stderr_queue),
daemon=True,
)
stderr_thread.start()

View file

@ -154,7 +154,7 @@ class Container(IsolationProvider):
args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=self.proc_stderr,
stderr=subprocess.PIPE,
startupinfo=startupinfo,
# Start the conversion process in a new session, so that we can later on
# kill the process group, without killing the controlling script.
@ -168,6 +168,10 @@ class Container(IsolationProvider):
) -> subprocess.Popen:
container_runtime = container_utils.get_runtime()
security_args = self.get_runtime_security_args()
debug_args = []
if self.debug:
debug_args += ["-e", "RUNSC_DEBUG=1"]
enable_stdin = ["-i"]
set_name = ["--name", name]
prevent_leakage_args = ["--rm"]
@ -177,6 +181,7 @@ class Container(IsolationProvider):
args = (
["run"]
+ security_args
+ debug_args
+ prevent_leakage_args
+ enable_stdin
+ set_name
@ -184,7 +189,14 @@ class Container(IsolationProvider):
+ command
)
args = [container_runtime] + args
return self.exec(args)
args_str = " ".join(shlex.quote(s) for s in args)
log.info("> " + args_str)
process = self.exec(args)
# Start stderr reader thread, attaching it to the process
self.start_stderr_thread(process)
return process
def kill_container(self, name: str) -> None:
"""Terminate a spawned container.

View file

@ -66,11 +66,25 @@ class DangerzoneCore(object):
) -> None:
def convert_doc(document: Document) -> None:
try:
# Clear any existing stderr output
while not self.isolation_provider.stderr_queue.empty():
self.isolation_provider.stderr_queue.get_nowait()
self.isolation_provider.convert(
document,
ocr_lang,
stdout_callback,
)
# Process any stderr output that was captured
while not self.isolation_provider.stderr_queue.empty():
try:
line = self.isolation_provider.stderr_queue.get_nowait()
if stdout_callback:
stdout_callback(True, line.decode().strip(), -1)
except Exception as e:
log.error(f"Error processing stderr: {e}")
except Exception:
log.exception(
f"Unexpected error occurred while converting '{document}'"