# Copyright (c) 2018, Ansible Project # Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) import errno import os import stat import re import pwd import grp import time import shutil import tempfile import traceback import fcntl import sys from contextlib import contextmanager from ansible.module_utils._text import to_bytes, to_native, to_text from ansible.module_utils.six import b, binary_type try: import selinux HAVE_SELINUX = True except ImportError: HAVE_SELINUX = False class LockTimeout(Exception): pass class FileLock(): ''' Currently FileLock is implemented via fcntl.flock on a lock file, however this behaviour may change in the future. Avoid mixing lock types fcntl.flock, fcntl.lockf and module_utils.common.file.FileLock as it will certainly cause unwanted and/or unexpected behaviour ''' def __init__(self): self.lockfd = None @contextmanager def lock_file(self, path, lock_timeout=None): ''' Context for lock acquisition ''' try: self.set_lock(path, lock_timeout) yield finally: self.unlock() def set_lock(self, path, lock_timeout=None): ''' Create a lock file based on path with flock to prevent other processes using given path :kw path: Path (file) to lock :kw lock_timeout: Wait n seconds for lock acquisition, fail if timeout is reached. 0 = Do not wait, fail if lock cannot be acquired immediately, Default is None, wait indefinitely until lock is released. :returns: True ''' tmp_dir = tempfile.gettempdir() lock_path = os.path.join(tmp_dir, 'ansible-{0}.lock'.format(os.path.basename(path))) l_wait = 0.1 r_exception = IOError if sys.version_info[0] == 3: r_exception = BlockingIOError self.lockfd = open(lock_path, 'w') if lock_timeout <= 0: fcntl.flock(self.lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB) os.chmod(lock_path, stat.S_IWRITE | stat.S_IREAD) return True if lock_timeout: e_secs = 0 while e_secs < lock_timeout: try: fcntl.flock(self.lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB) os.chmod(lock_path, stat.S_IWRITE | stat.S_IREAD) return True except r_exception: time.sleep(l_wait) e_secs += l_wait continue self.lockfd.close() raise LockTimeout('{0} sec'.format(lock_timeout)) fcntl.flock(self.lockfd, fcntl.LOCK_EX) os.chmod(lock_path, stat.S_IWRITE | stat.S_IREAD) return True def unlock(self): ''' Unlock the file descriptor locked by set_lock :returns: True ''' if not self.lockfd: return True try: fcntl.flock(self.lockfd, fcntl.LOCK_UN) self.lockfd.close() except ValueError: # file wasn't opened, let context manager fail gracefully pass return True