Writing the output PDF to stdout (ocrmypdf in.pdf -) previously relied on an honor system: no in-process code -- third-party libraries, plugins, or stray print() calls -- was supposed to write to stdout, enforced only indirectly. A single accidental write to fd 1 would silently corrupt the output PDF. Enforce this at the OS level. At CLI startup, before plugins load or any worker process/thread starts, save the real stdout via os.dup() and point fd 1 at stderr, so stray writes are diverted to stderr while only the final "produce the PDF" step writes to the preserved descriptor. Exposed as the opt-in public API function configure_stdout_protection(), mirroring configure_logging(); it is not enabled inside ocr() so in-process library users keep their own stdout. Also fix check_requested_output_file() to test the preserved real stdout for tty-ness, since after the redirect sys.stdout reports stderr's status. Fold unreleased v17.7.2 notes into v17.8.0.
90 lines
2.5 KiB
Python
Executable File
90 lines
2.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
|
# SPDX-License-Identifier: MPL-2.0
|
|
|
|
"""ocrmypdf command line entrypoint."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import multiprocessing
|
|
import os
|
|
import signal
|
|
import sys
|
|
from contextlib import suppress
|
|
|
|
from ocrmypdf import __version__
|
|
from ocrmypdf._pipelines.ocr import run_pipeline_cli
|
|
from ocrmypdf._validation import check_options
|
|
from ocrmypdf.api import Verbosity, configure_logging, configure_stdout_protection
|
|
from ocrmypdf.cli import get_options_and_plugins
|
|
from ocrmypdf.exceptions import (
|
|
BadArgsError,
|
|
ExitCode,
|
|
InputFileError,
|
|
MissingDependencyError,
|
|
)
|
|
|
|
log = logging.getLogger('ocrmypdf')
|
|
|
|
|
|
def sigbus(*args):
|
|
"""Handle SIGBUS signals.
|
|
|
|
pikepdf, depending on configuration, may use mmap so SIGBUS is a
|
|
possibility.
|
|
"""
|
|
raise InputFileError("Lost access to the input file")
|
|
|
|
|
|
def run(args=None):
|
|
"""Run the ocrmypdf command line interface."""
|
|
# Protect the real stdout before loading plugins or starting any worker
|
|
# processes/threads, so that only our final PDF output can reach it and
|
|
# stray writes from plugins or libraries are diverted to stderr.
|
|
configure_stdout_protection()
|
|
|
|
options, plugin_manager = get_options_and_plugins(args=args)
|
|
|
|
with suppress(AttributeError, PermissionError):
|
|
os.nice(5)
|
|
|
|
verbosity = options.verbose
|
|
if not os.isatty(sys.stderr.fileno()):
|
|
options.progress_bar = False
|
|
if options.quiet:
|
|
verbosity = Verbosity.quiet
|
|
options.progress_bar = False
|
|
configure_logging(
|
|
verbosity,
|
|
progress_bar_friendly=options.progress_bar,
|
|
manage_root_logger=True,
|
|
plugin_manager=plugin_manager,
|
|
)
|
|
log.debug('ocrmypdf %s', __version__)
|
|
try:
|
|
check_options(options, plugin_manager)
|
|
except ValueError as e:
|
|
log.error(e)
|
|
return ExitCode.bad_args
|
|
except BadArgsError as e:
|
|
log.error(e)
|
|
return e.exit_code
|
|
except MissingDependencyError as e:
|
|
log.error(e)
|
|
return ExitCode.missing_dependency
|
|
|
|
with suppress(AttributeError, OSError):
|
|
signal.signal(signal.SIGBUS, sigbus)
|
|
|
|
result = run_pipeline_cli(options=options, plugin_manager=plugin_manager)
|
|
return result
|
|
|
|
|
|
if __name__ == '__main__':
|
|
multiprocessing.freeze_support()
|
|
if sys.platform not in ('win32', 'darwin'):
|
|
with suppress(RuntimeError):
|
|
multiprocessing.set_start_method('forkserver')
|
|
sys.exit(run())
|