1cef7893435aa41160dd1255c43cb8498279738ccChris Craik# Copyright 2016 The Chromium Authors. All rights reserved.
2cef7893435aa41160dd1255c43cb8498279738ccChris Craik# Use of this source code is governed by a BSD-style license that can be
3cef7893435aa41160dd1255c43cb8498279738ccChris Craik# found in the LICENSE file.
4cef7893435aa41160dd1255c43cb8498279738ccChris Craik
5cef7893435aa41160dd1255c43cb8498279738ccChris Craikimport contextlib
6cef7893435aa41160dd1255c43cb8498279738ccChris Craikimport os
7cef7893435aa41160dd1255c43cb8498279738ccChris Craik
8cef7893435aa41160dd1255c43cb8498279738ccChris CraikLOCK_EX = None  # Exclusive lock
9cef7893435aa41160dd1255c43cb8498279738ccChris CraikLOCK_SH = None  # Shared lock
10cef7893435aa41160dd1255c43cb8498279738ccChris CraikLOCK_NB = None  # Non-blocking (LockException is raised if resource is locked)
11cef7893435aa41160dd1255c43cb8498279738ccChris Craik
12cef7893435aa41160dd1255c43cb8498279738ccChris Craik
13cef7893435aa41160dd1255c43cb8498279738ccChris Craikclass LockException(Exception):
14cef7893435aa41160dd1255c43cb8498279738ccChris Craik  pass
15cef7893435aa41160dd1255c43cb8498279738ccChris Craik
16cef7893435aa41160dd1255c43cb8498279738ccChris Craik
17cef7893435aa41160dd1255c43cb8498279738ccChris Craikif os.name == 'nt':
18cef7893435aa41160dd1255c43cb8498279738ccChris Craik  import win32con    # pylint: disable=import-error
19cef7893435aa41160dd1255c43cb8498279738ccChris Craik  import win32file   # pylint: disable=import-error
20cef7893435aa41160dd1255c43cb8498279738ccChris Craik  import pywintypes  # pylint: disable=import-error
21cef7893435aa41160dd1255c43cb8498279738ccChris Craik  LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
22cef7893435aa41160dd1255c43cb8498279738ccChris Craik  LOCK_SH = 0  # the default
23cef7893435aa41160dd1255c43cb8498279738ccChris Craik  LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
24cef7893435aa41160dd1255c43cb8498279738ccChris Craik  _OVERLAPPED = pywintypes.OVERLAPPED()
25cef7893435aa41160dd1255c43cb8498279738ccChris Craikelif os.name == 'posix':
26cef7893435aa41160dd1255c43cb8498279738ccChris Craik  import fcntl       # pylint: disable=import-error
27cef7893435aa41160dd1255c43cb8498279738ccChris Craik  LOCK_EX = fcntl.LOCK_EX
28cef7893435aa41160dd1255c43cb8498279738ccChris Craik  LOCK_SH = fcntl.LOCK_SH
29cef7893435aa41160dd1255c43cb8498279738ccChris Craik  LOCK_NB = fcntl.LOCK_NB
30cef7893435aa41160dd1255c43cb8498279738ccChris Craik
31cef7893435aa41160dd1255c43cb8498279738ccChris Craik
32cef7893435aa41160dd1255c43cb8498279738ccChris Craik@contextlib.contextmanager
33cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef FileLock(target_file, flags):
34cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """ Lock the target file. Similar to AcquireFileLock but allow user to write:
35cef7893435aa41160dd1255c43cb8498279738ccChris Craik        with FileLock(f, LOCK_EX):
36cef7893435aa41160dd1255c43cb8498279738ccChris Craik           ...do stuff on file f without worrying about race condition
37cef7893435aa41160dd1255c43cb8498279738ccChris Craik    Args: see AcquireFileLock's documentation.
38cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """
39cef7893435aa41160dd1255c43cb8498279738ccChris Craik  AcquireFileLock(target_file, flags)
40cef7893435aa41160dd1255c43cb8498279738ccChris Craik  try:
41cef7893435aa41160dd1255c43cb8498279738ccChris Craik    yield
42cef7893435aa41160dd1255c43cb8498279738ccChris Craik  finally:
43cef7893435aa41160dd1255c43cb8498279738ccChris Craik    ReleaseFileLock(target_file)
44cef7893435aa41160dd1255c43cb8498279738ccChris Craik
45cef7893435aa41160dd1255c43cb8498279738ccChris Craik
46cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef AcquireFileLock(target_file, flags):
47cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """ Lock the target file. Note that if |target_file| is closed, the lock is
48cef7893435aa41160dd1255c43cb8498279738ccChris Craik    automatically released.
49cef7893435aa41160dd1255c43cb8498279738ccChris Craik  Args:
50cef7893435aa41160dd1255c43cb8498279738ccChris Craik    target_file: file handle of the file to acquire lock.
51cef7893435aa41160dd1255c43cb8498279738ccChris Craik    flags: can be any of the type LOCK_EX, LOCK_SH, LOCK_NB, or a bitwise
52cef7893435aa41160dd1255c43cb8498279738ccChris Craik      OR combination of flags.
53cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """
54cef7893435aa41160dd1255c43cb8498279738ccChris Craik  assert flags in (
55cef7893435aa41160dd1255c43cb8498279738ccChris Craik      LOCK_EX, LOCK_SH, LOCK_NB, LOCK_EX | LOCK_NB, LOCK_SH | LOCK_NB)
56cef7893435aa41160dd1255c43cb8498279738ccChris Craik  if os.name == 'nt':
57cef7893435aa41160dd1255c43cb8498279738ccChris Craik    _LockImplWin(target_file, flags)
58cef7893435aa41160dd1255c43cb8498279738ccChris Craik  elif os.name == 'posix':
59cef7893435aa41160dd1255c43cb8498279738ccChris Craik    _LockImplPosix(target_file, flags)
60cef7893435aa41160dd1255c43cb8498279738ccChris Craik  else:
61cef7893435aa41160dd1255c43cb8498279738ccChris Craik    raise NotImplementedError('%s is not supported' % os.name)
62cef7893435aa41160dd1255c43cb8498279738ccChris Craik
63cef7893435aa41160dd1255c43cb8498279738ccChris Craik
64cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef ReleaseFileLock(target_file):
65cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """ Unlock the target file.
66cef7893435aa41160dd1255c43cb8498279738ccChris Craik  Args:
67cef7893435aa41160dd1255c43cb8498279738ccChris Craik    target_file: file handle of the file to release the lock.
68cef7893435aa41160dd1255c43cb8498279738ccChris Craik  """
69cef7893435aa41160dd1255c43cb8498279738ccChris Craik  if os.name == 'nt':
70cef7893435aa41160dd1255c43cb8498279738ccChris Craik    _UnlockImplWin(target_file)
71cef7893435aa41160dd1255c43cb8498279738ccChris Craik  elif os.name == 'posix':
72cef7893435aa41160dd1255c43cb8498279738ccChris Craik    _UnlockImplPosix(target_file)
73cef7893435aa41160dd1255c43cb8498279738ccChris Craik  else:
74cef7893435aa41160dd1255c43cb8498279738ccChris Craik    raise NotImplementedError('%s is not supported' % os.name)
75cef7893435aa41160dd1255c43cb8498279738ccChris Craik
76cef7893435aa41160dd1255c43cb8498279738ccChris Craik# These implementations are based on
77cef7893435aa41160dd1255c43cb8498279738ccChris Craik# http://code.activestate.com/recipes/65203/
78cef7893435aa41160dd1255c43cb8498279738ccChris Craik
79cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef _LockImplWin(target_file, flags):
80cef7893435aa41160dd1255c43cb8498279738ccChris Craik  hfile = win32file._get_osfhandle(target_file.fileno())
81cef7893435aa41160dd1255c43cb8498279738ccChris Craik  try:
82cef7893435aa41160dd1255c43cb8498279738ccChris Craik    win32file.LockFileEx(hfile, flags, 0, -0x10000, _OVERLAPPED)
83cef7893435aa41160dd1255c43cb8498279738ccChris Craik  except pywintypes.error, exc_value:
84cef7893435aa41160dd1255c43cb8498279738ccChris Craik    if exc_value[0] == 33:
85cef7893435aa41160dd1255c43cb8498279738ccChris Craik      raise LockException('Error trying acquiring lock of %s: %s' %
86cef7893435aa41160dd1255c43cb8498279738ccChris Craik                          (target_file.name, exc_value[2]))
87cef7893435aa41160dd1255c43cb8498279738ccChris Craik    else:
88cef7893435aa41160dd1255c43cb8498279738ccChris Craik      raise
89cef7893435aa41160dd1255c43cb8498279738ccChris Craik
90cef7893435aa41160dd1255c43cb8498279738ccChris Craik
91cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef _UnlockImplWin(target_file):
92cef7893435aa41160dd1255c43cb8498279738ccChris Craik  hfile = win32file._get_osfhandle(target_file.fileno())
93cef7893435aa41160dd1255c43cb8498279738ccChris Craik  try:
94cef7893435aa41160dd1255c43cb8498279738ccChris Craik    win32file.UnlockFileEx(hfile, 0, -0x10000, _OVERLAPPED)
95cef7893435aa41160dd1255c43cb8498279738ccChris Craik  except pywintypes.error, exc_value:
96cef7893435aa41160dd1255c43cb8498279738ccChris Craik    if exc_value[0] == 158:
97cef7893435aa41160dd1255c43cb8498279738ccChris Craik      # error: (158, 'UnlockFileEx', 'The segment is already unlocked.')
98cef7893435aa41160dd1255c43cb8498279738ccChris Craik      # To match the 'posix' implementation, silently ignore this error
99cef7893435aa41160dd1255c43cb8498279738ccChris Craik      pass
100cef7893435aa41160dd1255c43cb8498279738ccChris Craik    else:
101cef7893435aa41160dd1255c43cb8498279738ccChris Craik      # Q:  Are there exceptions/codes we should be dealing with here?
102cef7893435aa41160dd1255c43cb8498279738ccChris Craik      raise
103cef7893435aa41160dd1255c43cb8498279738ccChris Craik
104cef7893435aa41160dd1255c43cb8498279738ccChris Craik
105cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef _LockImplPosix(target_file, flags):
106cef7893435aa41160dd1255c43cb8498279738ccChris Craik  try:
107cef7893435aa41160dd1255c43cb8498279738ccChris Craik    fcntl.flock(target_file.fileno(), flags)
108cef7893435aa41160dd1255c43cb8498279738ccChris Craik  except IOError, exc_value:
109cef7893435aa41160dd1255c43cb8498279738ccChris Craik    if exc_value[0] == 11 or exc_value[0] == 35:
110cef7893435aa41160dd1255c43cb8498279738ccChris Craik      raise LockException('Error trying acquiring lock of %s: %s' %
111cef7893435aa41160dd1255c43cb8498279738ccChris Craik                          (target_file.name, exc_value[1]))
112cef7893435aa41160dd1255c43cb8498279738ccChris Craik    else:
113cef7893435aa41160dd1255c43cb8498279738ccChris Craik      raise
114cef7893435aa41160dd1255c43cb8498279738ccChris Craik
115cef7893435aa41160dd1255c43cb8498279738ccChris Craik
116cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef _UnlockImplPosix(target_file):
117cef7893435aa41160dd1255c43cb8498279738ccChris Craik  fcntl.flock(target_file.fileno(), fcntl.LOCK_UN)
118