# Copyright (c) 2018, Ansible Project # Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) from __future__ import (absolute_import, division, print_function) __metaclass__ = type 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