@ -1,24 +1,21 @@
# Copyright (c) 2018, Ansible Project
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
from __future__ import ( absolute_import , division , print_function )
from __future__ import ( absolute_import , division , print_function )
__metaclass__ = type
__metaclass__ = type
import errno
import fcntl
import os
import os
import stat
import re
import re
import pwd
import stat
import grp
import time
import shutil
import traceback
import fcntl
import sys
import sys
import time
from contextlib import contextmanager
from contextlib import contextmanager
from ansible . module_utils . _text import to_bytes , to_native , to_text
from ansible . module_utils . _text import to_bytes
from ansible . module_utils . six import b, binary_type
from ansible . module_utils . six import PY3
try :
try :
import selinux
import selinux
@ -62,6 +59,13 @@ _EXEC_PERM_BITS = 0o0111 # execute permission bits
_DEFAULT_PERM = 0o0666 # default file permission bits
_DEFAULT_PERM = 0o0666 # default file permission bits
# Ensure we use flock on e.g. FreeBSD, MacOSX and Solaris
if sys . platform . startswith ( ' linux ' ) :
filelock = fcntl . lockf
else :
filelock = fcntl . flock
def is_executable ( path ) :
def is_executable ( path ) :
# This function's signature needs to be repeated
# This function's signature needs to be repeated
# as the first line of its docstring.
# as the first line of its docstring.
@ -114,89 +118,88 @@ class LockTimeout(Exception):
pass
pass
class FileLock :
# NOTE: Using the open_locked() context manager it is absolutely mandatory
'''
# to not open or close the same file within the existing context.
Currently FileLock is implemented via fcntl . flock on a lock file , however this
# It is essential to reuse the returned file descriptor only.
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
@contextmanager
def lock_file ( self , path , tmpdir , lock_timeout = None ) :
def open_locked ( path , check_mode = False , lock_timeout = 15 ) :
'''
'''
Context for lock acquisition
Context managed for opening files with lock acquisition
: 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 ,
Less than 0 or None = wait indefinitely until lock is released
Default is wait 15 s .
: returns : file descriptor
'''
'''
try :
if check_mode :
self . set_lock ( path , tmpdir , lock_timeout )
b_path = to_bytes ( path , errors = ' surrogate_or_strict ' )
yield
fd = open ( b_path , ' ab+ ' )
finally :
fd . seek ( 0 ) # Due to a difference in behavior between PY2 and PY3 we need to seek(0) on PY3
self . unlock ( )
else :
fd = lock ( path , check_mode , lock_timeout )
yield fd
fd . close ( )
def set_lock ( self , path , tmpdir , lock_timeout = None ) :
def lock ( path , check_mode = False , lock_timeout = 15 ) :
'''
'''
Create a lock file based on path with flock to prevent other processes
Set lock on given path via fcntl . flock ( ) , note that using
using given path .
locks does not guarantee exclusiveness unless all accessing
Please note that currently file locking only works when it ' s executed by
processes honor locks .
the same user , I . E single user scenarios
: kw path : Path ( file ) to lock
: kw path : Path ( file ) to lock
: kw tmpdir : Path where to place the temporary . lock file
: kw lock_timeout :
: kw lock_timeout :
Wait n seconds for lock acquisition , fail if timeout is reached .
Wait n seconds for lock acquisition , fail if timeout is reached .
0 = Do not wait , fail if lock cannot be acquired immediately ,
0 = Do not wait , fail if lock cannot be acquired immediately ,
Default is None , wait indefinitely until lock is released .
Less than 0 or None = wait indefinitely until lock is released
: returns : True
Default is wait 15 s .
: returns : file descriptor
'''
'''
lock_path = os . path . join ( tmpdir , ' ansible- {0} .lock ' . format ( os . path . basename ( path ) ) )
b_path = to_bytes ( path , errors = ' surrogate_or_strict ' )
l_wait = 0.1
wait = 0.1
r_exception = IOError
if sys . version_info [ 0 ] == 3 :
lock_exception = IOError
r_exception = BlockingIOError
if PY3 :
lock_exception = OSError
self . lockfd = open ( lock_path , ' w ' )
if not os . path . exists ( b_path ) :
if lock_timeout < = 0 :
raise IOError ( ' {0} does not exist ' . format ( path ) )
fcntl . flock ( self . lockfd , fcntl . LOCK_EX | fcntl . LOCK_NB )
os . chmod ( lock_path , stat . S_IWRITE | stat . S_IREAD )
if lock_timeout is None or lock_timeout < 0 :
return True
fd = open ( b_path , ' ab+ ' )
fd . seek ( 0 ) # Due to a difference in behavior between PY2 and PY3 we need to seek(0) on PY3
if lock_timeout :
filelock ( fd , fcntl . LOCK_EX )
e_secs = 0
return fd
while e_secs < lock_timeout :
if lock_timeout > = 0 :
total_wait = 0
while total_wait < = lock_timeout :
fd = open ( b_path , ' ab+ ' )
fd . seek ( 0 ) # Due to a difference in behavior between PY2 and PY3 we need to seek(0) on PY3
try :
try :
fcntl . flock ( self . lockfd , fcntl . LOCK_EX | fcntl . LOCK_NB )
filelock ( fd, fcntl . LOCK_EX | fcntl . LOCK_NB )
os . chmod ( lock_path , stat . S_IWRITE | stat . S_IREAD )
return fd
return True
except lock_exception :
except r_exception :
fd . close ( )
time . sleep ( l_ wait)
time . sleep ( wait)
e_secs + = l_ wait
total_wait + = wait
continue
continue
self . lockfd . close ( )
fd . close ( )
raise LockTimeout ( ' {0} sec ' . format ( lock_timeout ) )
raise LockTimeout ( ' Waited {0} seconds for lock on {1} ' . format ( total_wait , path ) )
fcntl . flock ( self . lockfd , fcntl . LOCK_EX )
os . chmod ( lock_path , stat . S_IWRITE | stat . S_IREAD )
return True
def unlock ( self ) :
def unlock ( fd ) :
'''
'''
Make sure lock file is available for everyone and Unlock the file descriptor
Make sure lock file is available for everyone and Unlock the file descriptor
locked by set_lock
locked by set_lock
: returns : True
: kw fd : File descriptor of file to unlock
'''
'''
if not self . lockfd :
return True
try :
try :
fcntl . flock ( self . lockfd , fcntl . LOCK_UN )
filelock ( fd , fcntl . LOCK_UN )
self . lockfd . close ( )
except ValueError : # File was not opened, let context manager fail gracefully
except ValueError : # file wasn't opened, let context manager fail gracefully
pass
pass
return True