Răsfoiți Sursa

Parse & pass BORG_HOSTNAME_IS_UNIQUE env var to enable stale lock killing

Marian Beermann 8 ani în urmă
părinte
comite
676e69cac4

+ 4 - 2
src/borg/cache.py

@@ -75,7 +75,9 @@ class Cache:
         self.key = key
         self.manifest = manifest
         self.path = path or os.path.join(get_cache_dir(), repository.id_str)
-        self.unique_hostname = bool(os.environ.get('BORG_UNIQUE_HOSTNAME'))
+        self.hostname_is_unique = yes(env_var_override='BORG_HOSTNAME_IS_UNIQUE', prompt=False, env_msg=None)
+        if self.hostname_is_unique:
+            logger.info('Enabled removal of stale cache locks')
         self.do_files = do_files
         # Warn user before sending data to a never seen before unencrypted repository
         if not os.path.exists(self.path):
@@ -203,7 +205,7 @@ Chunk index:    {0.total_unique_chunks:20d} {0.total_chunks:20d}"""
     def open(self, lock_wait=None):
         if not os.path.isdir(self.path):
             raise Exception('%s Does not look like a Borg cache' % self.path)
-        self.lock = Lock(os.path.join(self.path, 'lock'), exclusive=True, timeout=lock_wait, kill_stale_locks=self.unique_hostname).acquire()
+        self.lock = Lock(os.path.join(self.path, 'lock'), exclusive=True, timeout=lock_wait, kill_stale_locks=self.hostname_is_unique).acquire()
         self.rollback()
 
     def close(self):

+ 3 - 1
src/borg/helpers.py

@@ -1116,7 +1116,7 @@ DEFAULTISH = ('Default', 'DEFAULT', 'default', 'D', 'd', '', )
 def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
         retry_msg=None, invalid_msg=None, env_msg='{} (from {})',
         falsish=FALSISH, truish=TRUISH, defaultish=DEFAULTISH,
-        default=False, retry=True, env_var_override=None, ofile=None, input=input):
+        default=False, retry=True, env_var_override=None, ofile=None, input=input, prompt=True):
     """Output <msg> (usually a question) and let user input an answer.
     Qualifies the answer according to falsish, truish and defaultish as True, False or <default>.
     If it didn't qualify and retry is False (no retries wanted), return the default [which
@@ -1161,6 +1161,8 @@ def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
             if answer is not None and env_msg:
                 print(env_msg.format(answer, env_var_override), file=ofile)
         if answer is None:
+            if not prompt:
+                return default
             try:
                 answer = input()
             except EOFError:

+ 6 - 7
src/borg/locking.py

@@ -106,7 +106,7 @@ class ExclusiveLock:
         self.path = os.path.abspath(path)
         self.id = id or platform.get_process_id()
         self.unique_name = os.path.join(self.path, "%s.%d-%x" % self.id)
-        self.ok_to_kill_stale_locks = kill_stale_locks
+        self.kill_stale_locks = kill_stale_locks
         self.stale_warning_printed = False
 
     def __enter__(self):
@@ -163,16 +163,16 @@ class ExclusiveLock:
                 thread = int(thread_str)
             except ValueError:
                 # Malformed lock name? Or just some new format we don't understand?
-                # It's safer to just exit
+                # It's safer to just exit.
                 return False
 
             if platform.process_alive(host, pid, thread):
                 return False
 
-            if not self.ok_to_kill_stale_locks:
+            if not self.kill_stale_locks:
                 if not self.stale_warning_printed:
                     # Log this at warning level to hint the user at the ability
-                    logger.warning("Found stale lock %s, but not deleting because BORG_UNIQUE_HOSTNAME is not set.", name)
+                    logger.warning("Found stale lock %s, but not deleting because BORG_HOSTNAME_IS_UNIQUE is not set.", name)
                     self.stale_warning_printed = True
                 return False
 
@@ -213,7 +213,7 @@ class LockRoster:
     def __init__(self, path, id=None, kill_stale_locks=False):
         self.path = path
         self.id = id or platform.get_process_id()
-        self.ok_to_kill_zombie_locks = kill_stale_locks
+        self.kill_stale_locks = kill_stale_locks
 
     def load(self):
         try:
@@ -221,7 +221,7 @@ class LockRoster:
                 data = json.load(f)
 
             # Just nuke the stale locks early on load
-            if self.ok_to_kill_zombie_locks:
+            if self.kill_stale_locks:
                 for key in (SHARED, EXCLUSIVE):
                     try:
                         entries = data[key]
@@ -237,7 +237,6 @@ class LockRoster:
         except (FileNotFoundError, ValueError):
             # no or corrupt/empty roster file?
             data = {}
-
         return data
 
     def save(self, data):

+ 3 - 2
src/borg/platform/posix.pyx

@@ -53,12 +53,13 @@ def process_alive(host, pid, thread):
 
     return local_pid_alive(pid)
 
+
 def local_pid_alive(pid):
     """Return whether *pid* is alive."""
     try:
         # This doesn't work on Windows.
         # This does not kill anything, 0 means "see if we can send a signal to this process or not".
-        # Possible errors: No such process (== stale lock) or permission denied (not a stale lock)
+        # Possible errors: No such process (== stale lock) or permission denied (not a stale lock).
         # If the exception is not raised that means such a pid is valid and we can send a signal to it.
         os.kill(pid, 0)
         return True
@@ -66,5 +67,5 @@ def local_pid_alive(pid):
         if err.errno == errno.ESRCH:
             # ESRCH = no such process
             return False
-        # Any other error (eg. permissions) mean that the process ID refers to a live process
+        # Any other error (eg. permissions) means that the process ID refers to a live process.
         return True

+ 6 - 2
src/borg/remote.py

@@ -18,6 +18,7 @@ from .helpers import get_home_dir
 from .helpers import sysinfo
 from .helpers import bin_to_hex
 from .helpers import replace_placeholders
+from .helpers import yes
 from .repository import Repository
 
 RPC_PROTOCOL_VERSION = 2
@@ -326,12 +327,15 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
                 opts.append('--critical')
             else:
                 raise ValueError('log level missing, fix this code')
+        env_vars = []
+        if yes(env_var_override='BORG_HOSTNAME_IS_UNIQUE', env_msg=None, prompt=False):
+            env_vars.append('BORG_HOSTNAME_IS_UNIQUE=yes')
         if testing:
-            return [sys.executable, '-m', 'borg.archiver', 'serve'] + opts + self.extra_test_args
+            return env_vars + [sys.executable, '-m', 'borg.archiver', 'serve'] + opts + self.extra_test_args
         else:  # pragma: no cover
             remote_path = args.remote_path or os.environ.get('BORG_REMOTE_PATH', 'borg')
             remote_path = replace_placeholders(remote_path)
-            return [remote_path, 'serve'] + opts
+            return env_vars + [remote_path, 'serve'] + opts
 
     def ssh_cmd(self, location):
         """return a ssh command line that can be prefixed to a borg command line"""

+ 5 - 2
src/borg/repository.py

@@ -21,6 +21,7 @@ from .helpers import Error, ErrorWithTraceback, IntegrityError, format_file_size
 from .helpers import Location
 from .helpers import ProgressIndicatorPercent
 from .helpers import bin_to_hex
+from .helpers import yes
 from .locking import Lock, LockError, LockErrorT
 from .logger import create_logger
 from .lrucache import LRUCache
@@ -121,7 +122,9 @@ class Repository:
         self.do_create = create
         self.exclusive = exclusive
         self.append_only = append_only
-        self.unique_hostname = bool(os.environ.get('BORG_UNIQUE_HOSTNAME'))
+        self.hostname_is_unique = yes(env_var_override='BORG_HOSTNAME_IS_UNIQUE', env_msg=None, prompt=False)
+        if self.hostname_is_unique:
+            logger.info('Enabled removal of stale repository locks')
 
     def __del__(self):
         if self.lock:
@@ -255,7 +258,7 @@ class Repository:
         if not os.path.isdir(path):
             raise self.DoesNotExist(path)
         if lock:
-            self.lock = Lock(os.path.join(path, 'lock'), exclusive, timeout=lock_wait, kill_stale_locks=self.unique_hostname).acquire()
+            self.lock = Lock(os.path.join(path, 'lock'), exclusive, timeout=lock_wait, kill_stale_locks=self.hostname_is_unique).acquire()
         else:
             self.lock = None
         self.config = ConfigParser(interpolation=None)