leapp/0006-Add-process-lock.patch
Toshio Kuratomi 853180dbcf LEAPP Release CTC-1 8.10/9.5
- Minor improvement of the summary overview in the console output.
- Store metadata about the LEAPP plugins inside leapp audit db.
- Prevent leapp from starting if an instance is already runnning.
- Resolves: RHEL-27848, RHEL-25407
2024-05-13 10:34:57 -07:00

176 lines
5.2 KiB
Diff

From b64c44bfb741e17650c7c0d65f25fc4ef67fdf19 Mon Sep 17 00:00:00 2001
From: David Kubek <dkubek@redhat.com>
Date: Thu, 22 Feb 2024 12:51:04 +0100
Subject: [PATCH 6/7] Add process lock
This commit addresses the potential risk of running multiple instances
of Leapp simultaneously on a single system. It implements a simple lock
mechanism to prevent concurrent executions on a single system using a
simple BSD lock (`flock(2)`).
Lock is acquired at the start of the execution and a PID number is
stored in lockfile. The PID in lockfile currently has purely
informational character.
---
leapp/cli/__init__.py | 10 ++++--
leapp/config.py | 3 ++
leapp/exceptions.py | 4 +++
leapp/utils/lock.py | 83 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 97 insertions(+), 3 deletions(-)
create mode 100644 leapp/utils/lock.py
diff --git a/leapp/cli/__init__.py b/leapp/cli/__init__.py
index fe997d4..ab16198 100644
--- a/leapp/cli/__init__.py
+++ b/leapp/cli/__init__.py
@@ -2,12 +2,12 @@ import os
import pkgutil
import socket
import sys
-import textwrap
from leapp import VERSION
from leapp.cli import commands
-from leapp.exceptions import UnknownCommandError
+from leapp.exceptions import UnknownCommandError, ProcessLockError
from leapp.utils.clicmd import command
+from leapp.utils.lock import leapp_lock
@command('')
@@ -42,7 +42,8 @@ def main():
os.environ['LEAPP_HOSTNAME'] = socket.getfqdn()
_load_commands(cli.command)
try:
- cli.command.execute('leapp version {}'.format(VERSION))
+ with leapp_lock():
+ cli.command.execute('leapp version {}'.format(VERSION))
except UnknownCommandError as e:
bad_cmd = (
"Command \"{CMD}\" is unknown.\nMost likely there is a typo in the command or particular "
@@ -54,3 +55,6 @@ def main():
bad_cmd = "No such argument {CMD}"
print(bad_cmd.format(CMD=e.requested))
sys.exit(1)
+ except ProcessLockError as e:
+ sys.stderr.write('{}\nAborting.\n'.format(e.message))
+ sys.exit(1)
diff --git a/leapp/config.py b/leapp/config.py
index 18c357d..2487e0f 100644
--- a/leapp/config.py
+++ b/leapp/config.py
@@ -40,6 +40,9 @@ _CONFIG_DEFAULTS = {
'dir': '/var/log/leapp/',
'files': ','.join(_FILES_TO_ARCHIVE),
},
+ 'lock': {
+ 'path': '/var/run/leapp.pid'
+ },
'logs': {
'dir': '/var/log/leapp/',
'files': ','.join(_LOGS),
diff --git a/leapp/exceptions.py b/leapp/exceptions.py
index 43c5905..9463a5c 100644
--- a/leapp/exceptions.py
+++ b/leapp/exceptions.py
@@ -148,3 +148,7 @@ class RequestStopAfterPhase(LeappError):
def __init__(self):
super(RequestStopAfterPhase, self).__init__('Stop after phase has been requested.')
+
+
+class ProcessLockError(LeappError):
+ """ This exception is used to represent an error within the process locking mechanism. """
diff --git a/leapp/utils/lock.py b/leapp/utils/lock.py
new file mode 100644
index 0000000..33e825c
--- /dev/null
+++ b/leapp/utils/lock.py
@@ -0,0 +1,83 @@
+import os
+import fcntl
+import logging
+
+from leapp.config import get_config
+from leapp.exceptions import ProcessLockError
+
+
+def leapp_lock(lockfile=None):
+ return ProcessLock(lockfile=lockfile)
+
+
+def _acquire_lock(fd):
+ try:
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ return True
+ except OSError:
+ return False
+
+
+def _clear_lock(fd):
+ os.lseek(fd, 0, os.SEEK_SET)
+ os.ftruncate(fd, 0)
+
+
+def _read_pid(fd):
+ return os.read(fd, 20)
+
+
+def _write_pid(fd, pid):
+ _clear_lock(fd)
+ os.write(fd, str(pid).encode('utf-8'))
+
+
+class ProcessLock(object):
+
+ def __init__(self, lockfile=None):
+ self.log = logging.getLogger('leapp.utils.lock')
+ self.lockfile = lockfile if lockfile else get_config().get('lock', 'path')
+
+ self.fd = None
+
+ def _get_pid_from_lockfile(self):
+ running_pid = _read_pid(self.fd)
+ self.log.debug("_get_pid_from_lockfile: running_pid=%s", running_pid)
+ running_pid = int(running_pid)
+
+ return running_pid
+
+ def _try_lock(self, pid):
+ if not _acquire_lock(self.fd):
+ try:
+ running_pid = self._get_pid_from_lockfile()
+ except ValueError:
+ process_msg = ''
+ else:
+ process_msg = ' by process with PID {}'.format(running_pid)
+
+ msg = (
+ 'Leapp is currently locked{} and cannot be started.\n'
+ 'Please ensure no other instance of leapp is running and then delete the lockfile at {} and try again.'
+ ).format(process_msg, self.lockfile)
+ raise ProcessLockError(msg)
+
+ try:
+ _write_pid(self.fd, pid)
+ except OSError:
+ raise ProcessLockError('Could not write PID to lockfile.')
+
+ def __enter__(self):
+ my_pid = os.getpid()
+
+ self.fd = os.open(self.lockfile, os.O_CREAT | os.O_RDWR, 0o600)
+ try:
+ self._try_lock(my_pid)
+ except ProcessLockError:
+ os.close(self.fd)
+ raise
+
+ def __exit__(self, *exc_args):
+ _clear_lock(self.fd)
+ os.close(self.fd)
+ os.unlink(self.lockfile)
--
2.42.0