From 0ab82d57e43261a1cc7788315fbde8f24fd6dd0a Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 6 Aug 2022 12:24:50 +0200 Subject: [PATCH 1/2] ctrl-c must not kill the ssh subprocess, fixes #6912 --- src/borg/helpers/process.py | 13 +++++++++++++ src/borg/remote.py | 6 ++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/borg/helpers/process.py b/src/borg/helpers/process.py index 41aa38815..bbd6e8677 100644 --- a/src/borg/helpers/process.py +++ b/src/borg/helpers/process.py @@ -236,6 +236,19 @@ def __exit__(self, exception_type, exception_value, traceback): sig_int = SigIntManager() +def ignore_sigint(): + """ + Ignore SIGINT, see also issue #6912. + + Ctrl-C will send a SIGINT to both the main process (borg) and subprocesses + (e.g. ssh for remote ssh:// repos), but often we do not want the subprocess + getting killed (e.g. because it is still needed to cleanly shut down borg). + + To avoid that: Popen(..., preexec_fn=ignore_sigint) + """ + signal.signal(signal.SIGINT, signal.SIG_IGN) + + def popen_with_error_handling(cmd_line: str, log_prefix="", **kwargs): """ Handle typical errors raised by subprocess.Popen. Return None if an error occurred, diff --git a/src/borg/remote.py b/src/borg/remote.py index b3a7555e0..016fda757 100644 --- a/src/borg/remote.py +++ b/src/borg/remote.py @@ -26,7 +26,7 @@ from .helpers import sysinfo from .helpers import format_file_size from .helpers import safe_unlink -from .helpers import prepare_subprocess_env +from .helpers import prepare_subprocess_env, ignore_sigint from .logger import create_logger, setup_logging from .helpers import msgpack from .repository import Repository @@ -582,7 +582,9 @@ def __init__( if not testing: borg_cmd = self.ssh_cmd(location) + borg_cmd logger.debug("SSH command line: %s", borg_cmd) - self.p = Popen(borg_cmd, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env) + # we do not want the ssh getting killed by Ctrl-C/SIGINT because it is needed for clean shutdown of borg. + # borg's SIGINT handler tries to write a checkpoint and requires the remote repo connection. + self.p = Popen(borg_cmd, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env, preexec_fn=ignore_sigint) self.stdin_fd = self.p.stdin.fileno() self.stdout_fd = self.p.stdout.fileno() self.stderr_fd = self.p.stderr.fileno() From 4d570497be469f56cd767183f85bdf0c86348f7b Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 6 Aug 2022 12:48:54 +0200 Subject: [PATCH 2/2] ctrl-c must not kill other subprocesses, fixes #6912 There are some other places with subprocesses: - borg create --content-from-command - borg create --paths-from-command - (de)compression filter process of import-tar / export-tar --- src/borg/archiver/create.py | 6 +++--- src/borg/helpers/__init__.py | 3 ++- src/borg/helpers/process.py | 14 ++++++++++++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/borg/archiver/create.py b/src/borg/archiver/create.py index 499f0f6ea..1925f0a7a 100644 --- a/src/borg/archiver/create.py +++ b/src/borg/archiver/create.py @@ -26,7 +26,7 @@ from ..helpers import log_multi from ..helpers import basic_json_data, json_print from ..helpers import flags_root, flags_dir, flags_special_follow, flags_special -from ..helpers import sig_int +from ..helpers import sig_int, ignore_sigint from ..helpers import iter_separated from ..patterns import PatternMatcher from ..platform import get_flags @@ -68,7 +68,7 @@ def create_inner(archive, cache, fso): if not dry_run: try: try: - proc = subprocess.Popen(args.paths, stdout=subprocess.PIPE) + proc = subprocess.Popen(args.paths, stdout=subprocess.PIPE, preexec_fn=ignore_sigint) except (FileNotFoundError, PermissionError) as e: self.print_error("Failed to execute command: %s", e) return self.exit_code @@ -89,7 +89,7 @@ def create_inner(archive, cache, fso): paths_sep = eval_escapes(args.paths_delimiter) if args.paths_delimiter is not None else "\n" if args.paths_from_command: try: - proc = subprocess.Popen(args.paths, stdout=subprocess.PIPE) + proc = subprocess.Popen(args.paths, stdout=subprocess.PIPE, preexec_fn=ignore_sigint) except (FileNotFoundError, PermissionError) as e: self.print_error("Failed to execute command: %s", e) return self.exit_code diff --git a/src/borg/helpers/__init__.py b/src/borg/helpers/__init__.py index e808bc213..cea471506 100644 --- a/src/borg/helpers/__init__.py +++ b/src/borg/helpers/__init__.py @@ -31,7 +31,8 @@ from .parseformat import BaseFormatter, ArchiveFormatter, ItemFormatter, file_status from .parseformat import swidth_slice, ellipsis_truncate from .parseformat import BorgJsonEncoder, basic_json_data, json_print, json_dump, prepare_dump_dict -from .process import daemonize, daemonizing, signal_handler, raising_signal_handler, sig_int, SigHup, SigTerm +from .process import daemonize, daemonizing +from .process import signal_handler, raising_signal_handler, sig_int, ignore_sigint, SigHup, SigTerm from .process import popen_with_error_handling, is_terminal, prepare_subprocess_env, create_filter_process from .progress import ProgressIndicatorPercent, ProgressIndicatorEndless, ProgressIndicatorMessage from .time import parse_timestamp, timestamp, safe_timestamp, safe_s, safe_ns, MAX_S, SUPPORT_32BIT_PLATFORMS diff --git a/src/borg/helpers/process.py b/src/borg/helpers/process.py index bbd6e8677..3d6de7aef 100644 --- a/src/borg/helpers/process.py +++ b/src/borg/helpers/process.py @@ -336,11 +336,21 @@ def create_filter_process(cmd, stream, stream_close, inbound=True): # for us to do something while we block on the process for something different. if inbound: proc = popen_with_error_handling( - cmd, stdout=subprocess.PIPE, stdin=filter_stream, log_prefix="filter-process: ", env=env + cmd, + stdout=subprocess.PIPE, + stdin=filter_stream, + log_prefix="filter-process: ", + env=env, + preexec_fn=ignore_sigint, ) else: proc = popen_with_error_handling( - cmd, stdin=subprocess.PIPE, stdout=filter_stream, log_prefix="filter-process: ", env=env + cmd, + stdin=subprocess.PIPE, + stdout=filter_stream, + log_prefix="filter-process: ", + env=env, + preexec_fn=ignore_sigint, ) if not proc: raise Error(f"filter {cmd}: process creation failed")