This commit is contained in:
Kovid Goyal 2017-02-01 13:24:33 +05:30
parent 0b9aa7d119
commit f8202dc3e3

View File

@ -1,7 +1,6 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
''' '''
Secure access to locked files from multiple processes. Secure access to locked files from multiple processes.
''' '''
@ -24,8 +23,9 @@ class WindowsExclFile(object):
while timeout > 0: while timeout > 0:
timeout -= 1 timeout -= 1
try: try:
self._handle = w.CreateFile(path, self._handle = w.CreateFile(
w.GENERIC_READ|w.GENERIC_WRITE, # Open for reading and writing path,
w.GENERIC_READ | w.GENERIC_WRITE, # Open for reading and writing
0, # Open exclusive 0, # Open exclusive
None, # No security attributes, ensures handle is not inherited by children None, # No security attributes, ensures handle is not inherited by children
w.OPEN_ALWAYS, # If file does not exist, create it w.OPEN_ALWAYS, # If file does not exist, create it
@ -45,8 +45,8 @@ class WindowsExclFile(object):
def seek(self, amt, frm=0): def seek(self, amt, frm=0):
import win32file as w import win32file as w
if frm not in (0, 1, 2): if frm not in (0, 1, 2):
raise ValueError('Invalid from for seek: %s'%frm) raise ValueError('Invalid from for seek: %s' % frm)
frm = {0:w.FILE_BEGIN, 1: w.FILE_CURRENT, 2:w.FILE_END}[frm] frm = {0: w.FILE_BEGIN, 1: w.FILE_CURRENT, 2: w.FILE_END}[frm]
if frm is w.FILE_END: if frm is w.FILE_END:
amt = 0 - amt amt = 0 - amt
w.SetFilePointer(self._handle, amt, frm) w.SetFilePointer(self._handle, amt, frm)
@ -77,7 +77,7 @@ class WindowsExclFile(object):
return '' return ''
hr, ans = w.ReadFile(self._handle, bytes, None) hr, ans = w.ReadFile(self._handle, bytes, None)
if hr != 0: if hr != 0:
raise IOError('Error reading file: %s'%hr) raise IOError('Error reading file: %s' % hr)
return ans return ans
def readlines(self, sizehint=-1): def readlines(self, sizehint=-1):
@ -118,15 +118,24 @@ def unix_open(path):
has_cloexec = False has_cloexec = False
if hasattr(speedup, 'O_CLOEXEC'): if hasattr(speedup, 'O_CLOEXEC'):
try: try:
fd = os.open(path, flags | speedup.O_CLOEXEC, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) fd = os.open(
path, flags | speedup.O_CLOEXEC, stat.S_IRUSR | stat.S_IWUSR |
stat.S_IRGRP | stat.S_IROTH
)
has_cloexec = True has_cloexec = True
except EnvironmentError as err: except EnvironmentError as err:
if getattr(err, 'errno', None) == errno.EINVAL: # Kernel does not support O_CLOEXEC if getattr(err, 'errno', None) == errno.EINVAL:
fd = os.open(path, flags, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) # Kernel does not support O_CLOEXEC
fd = os.open(
path, flags, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP |
stat.S_IROTH
)
else: else:
raise raise
else: else:
fd = os.open(path, flags, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) fd = os.open(
path, flags, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
)
if not has_cloexec: if not has_cloexec:
fcntl.fcntl(fd, fcntl.F_SETFD, fcntl.FD_CLOEXEC) fcntl.fcntl(fd, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
@ -140,13 +149,14 @@ class ExclusiveFile(object):
self.timeout = timeout self.timeout = timeout
def __enter__(self): def __enter__(self):
self.file = WindowsExclFile(self.path, self.timeout) if iswindows else unix_open(self.path) self.file = WindowsExclFile(self.path, self.timeout
) if iswindows else unix_open(self.path)
self.file.seek(0) self.file.seek(0)
timeout = self.timeout timeout = self.timeout
if not iswindows: if not iswindows:
while self.timeout < 0 or timeout >= 0: while self.timeout < 0 or timeout >= 0:
try: try:
fcntl.flock(self.file.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB) fcntl.flock(self.file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
break break
except IOError: except IOError:
time.sleep(1) time.sleep(1)
@ -168,18 +178,23 @@ def test_exclusive_file(path=None):
# Try same process lock # Try same process lock
try: try:
with ExclusiveFile(f, timeout=1): with ExclusiveFile(f, timeout=1):
raise LockError("ExclusiveFile failed to prevent multiple uses in the same process!") raise LockError(
"ExclusiveFile failed to prevent multiple uses in the same process!"
)
except LockError: except LockError:
pass pass
# Try different process lock # Try different process lock
from calibre.utils.ipc.simple_worker import fork_job from calibre.utils.ipc.simple_worker import fork_job
err = fork_job('calibre.utils.lock', 'test_exclusive_file', (f,))['result'] err = fork_job('calibre.utils.lock', 'test_exclusive_file',
(f, ))['result']
if err is not None: if err is not None:
raise LockError('ExclusiveFile failed with error: %s' % err) raise LockError('ExclusiveFile failed with error: %s' % err)
else: else:
try: try:
with ExclusiveFile(path, timeout=1): with ExclusiveFile(path, timeout=1):
raise Exception('ExclusiveFile failed to prevent multiple uses in different processes!') raise Exception(
'ExclusiveFile failed to prevent multiple uses in different processes!'
)
except LockError: except LockError:
pass pass
except Exception as err: except Exception as err:
@ -196,10 +211,12 @@ def _clean_lock_file(file):
except: except:
pass pass
if iswindows: if iswindows:
def singleinstance(name): def singleinstance(name):
mutexname = 'mutexforsingleinstanceof'+__appname__+name mutexname = 'mutexforsingleinstanceof' + __appname__ + name
mutex = win32event.CreateMutex(None, False, mutexname) mutex = win32event.CreateMutex(None, False, mutexname)
err = win32api.GetLastError() err = win32api.GetLastError()
if err == winerror.ERROR_ALREADY_EXISTS: if err == winerror.ERROR_ALREADY_EXISTS:
# Close this handle other wise this handle will prevent the mutex # Close this handle other wise this handle will prevent the mutex
@ -209,6 +226,7 @@ if iswindows:
atexit.register(win32api.CloseHandle, mutex) atexit.register(win32api.CloseHandle, mutex)
return not err == winerror.ERROR_ALREADY_EXISTS return not err == winerror.ERROR_ALREADY_EXISTS
elif islinux: elif islinux:
def singleinstance(name): def singleinstance(name):
import socket import socket
from calibre.utils.ipc import eintr_retry_call from calibre.utils.ipc import eintr_retry_call
@ -229,18 +247,22 @@ elif islinux:
atexit.register(sock.close) atexit.register(sock.close)
return True return True
elif ishaiku: elif ishaiku:
def singleinstance(name): def singleinstance(name):
# Somebody should fix this. # Somebody should fix this.
return True return True
else: else:
def singleinstance_path(name): def singleinstance_path(name):
home = os.path.expanduser('~') home = os.path.expanduser('~')
if os.access(home, os.W_OK|os.R_OK|os.X_OK): if os.access(home, os.W_OK | os.R_OK | os.X_OK):
basename = __appname__+'_'+name+'.lock' basename = __appname__ + '_' + name + '.lock'
return os.path.expanduser('~/.' + basename) return os.path.expanduser('~/.' + basename)
import tempfile import tempfile
tdir = tempfile.gettempdir() tdir = tempfile.gettempdir()
return os.path.join(tdir, '%s_%s_%s.lock' % (__appname__, name, os.geteuid())) return os.path.join(
tdir, '%s_%s_%s.lock' % (__appname__, name, os.geteuid())
)
def singleinstance(name): def singleinstance(name):
''' '''
@ -255,7 +277,7 @@ else:
old_flags = fcntl.fcntl(f.fileno(), fcntl.F_GETFD) old_flags = fcntl.fcntl(f.fileno(), fcntl.F_GETFD)
fcntl.fcntl(f.fileno(), fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC) fcntl.fcntl(f.fileno(), fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
try: try:
eintr_retry_call(fcntl.lockf, f.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB) eintr_retry_call(fcntl.lockf, f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
atexit.register(_clean_lock_file, f) atexit.register(_clean_lock_file, f)
return True return True
except IOError as err: except IOError as err: