Index: third_party/oauth2client/locked_file.py |
diff --git a/third_party/oauth2client/locked_file.py b/third_party/oauth2client/locked_file.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..31514dcf359254352d063109364dbe244fc4df2e |
--- /dev/null |
+++ b/third_party/oauth2client/locked_file.py |
@@ -0,0 +1,373 @@ |
+# Copyright 2011 Google Inc. |
+# |
+# Licensed under the Apache License, Version 2.0 (the "License"); |
+# you may not use this file except in compliance with the License. |
+# You may obtain a copy of the License at |
+# |
+# http://www.apache.org/licenses/LICENSE-2.0 |
+# |
+# Unless required by applicable law or agreed to in writing, software |
+# distributed under the License is distributed on an "AS IS" BASIS, |
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+# See the License for the specific language governing permissions and |
+# limitations under the License. |
+ |
+"""Locked file interface that should work on Unix and Windows pythons. |
+ |
+This module first tries to use fcntl locking to ensure serialized access |
+to a file, then falls back on a lock file if that is unavialable. |
+ |
+Usage: |
+ f = LockedFile('filename', 'r+b', 'rb') |
+ f.open_and_lock() |
+ if f.is_locked(): |
+ print 'Acquired filename with r+b mode' |
+ f.file_handle().write('locked data') |
+ else: |
+ print 'Aquired filename with rb mode' |
+ f.unlock_and_close() |
+""" |
+ |
+__author__ = 'cache@google.com (David T McWherter)' |
+ |
+import errno |
+import logging |
+import os |
+import time |
+ |
+from oauth2client import util |
+ |
+logger = logging.getLogger(__name__) |
+ |
+ |
+class CredentialsFileSymbolicLinkError(Exception): |
+ """Credentials files must not be symbolic links.""" |
+ |
+ |
+class AlreadyLockedException(Exception): |
+ """Trying to lock a file that has already been locked by the LockedFile.""" |
+ pass |
+ |
+ |
+def validate_file(filename): |
+ if os.path.islink(filename): |
+ raise CredentialsFileSymbolicLinkError( |
+ 'File: %s is a symbolic link.' % filename) |
+ |
+class _Opener(object): |
+ """Base class for different locking primitives.""" |
+ |
+ def __init__(self, filename, mode, fallback_mode): |
+ """Create an Opener. |
+ |
+ Args: |
+ filename: string, The pathname of the file. |
+ mode: string, The preferred mode to access the file with. |
+ fallback_mode: string, The mode to use if locking fails. |
+ """ |
+ self._locked = False |
+ self._filename = filename |
+ self._mode = mode |
+ self._fallback_mode = fallback_mode |
+ self._fh = None |
+ |
+ def is_locked(self): |
+ """Was the file locked.""" |
+ return self._locked |
+ |
+ def file_handle(self): |
+ """The file handle to the file. Valid only after opened.""" |
+ return self._fh |
+ |
+ def filename(self): |
+ """The filename that is being locked.""" |
+ return self._filename |
+ |
+ def open_and_lock(self, timeout, delay): |
+ """Open the file and lock it. |
+ |
+ Args: |
+ timeout: float, How long to try to lock for. |
+ delay: float, How long to wait between retries. |
+ """ |
+ pass |
+ |
+ def unlock_and_close(self): |
+ """Unlock and close the file.""" |
+ pass |
+ |
+ |
+class _PosixOpener(_Opener): |
+ """Lock files using Posix advisory lock files.""" |
+ |
+ def open_and_lock(self, timeout, delay): |
+ """Open the file and lock it. |
+ |
+ Tries to create a .lock file next to the file we're trying to open. |
+ |
+ Args: |
+ timeout: float, How long to try to lock for. |
+ delay: float, How long to wait between retries. |
+ |
+ Raises: |
+ AlreadyLockedException: if the lock is already acquired. |
+ IOError: if the open fails. |
+ CredentialsFileSymbolicLinkError if the file is a symbolic link. |
+ """ |
+ if self._locked: |
+ raise AlreadyLockedException('File %s is already locked' % |
+ self._filename) |
+ self._locked = False |
+ |
+ validate_file(self._filename) |
+ try: |
+ self._fh = open(self._filename, self._mode) |
+ except IOError, e: |
+ # If we can't access with _mode, try _fallback_mode and don't lock. |
+ if e.errno == errno.EACCES: |
+ self._fh = open(self._filename, self._fallback_mode) |
+ return |
+ |
+ lock_filename = self._posix_lockfile(self._filename) |
+ start_time = time.time() |
+ while True: |
+ try: |
+ self._lock_fd = os.open(lock_filename, |
+ os.O_CREAT|os.O_EXCL|os.O_RDWR) |
+ self._locked = True |
+ break |
+ |
+ except OSError, e: |
+ if e.errno != errno.EEXIST: |
+ raise |
+ if (time.time() - start_time) >= timeout: |
+ logger.warn('Could not acquire lock %s in %s seconds' % ( |
+ lock_filename, timeout)) |
+ # Close the file and open in fallback_mode. |
+ if self._fh: |
+ self._fh.close() |
+ self._fh = open(self._filename, self._fallback_mode) |
+ return |
+ time.sleep(delay) |
+ |
+ def unlock_and_close(self): |
+ """Unlock a file by removing the .lock file, and close the handle.""" |
+ if self._locked: |
+ lock_filename = self._posix_lockfile(self._filename) |
+ os.close(self._lock_fd) |
+ os.unlink(lock_filename) |
+ self._locked = False |
+ self._lock_fd = None |
+ if self._fh: |
+ self._fh.close() |
+ |
+ def _posix_lockfile(self, filename): |
+ """The name of the lock file to use for posix locking.""" |
+ return '%s.lock' % filename |
+ |
+ |
+try: |
+ import fcntl |
+ |
+ class _FcntlOpener(_Opener): |
+ """Open, lock, and unlock a file using fcntl.lockf.""" |
+ |
+ def open_and_lock(self, timeout, delay): |
+ """Open the file and lock it. |
+ |
+ Args: |
+ timeout: float, How long to try to lock for. |
+ delay: float, How long to wait between retries |
+ |
+ Raises: |
+ AlreadyLockedException: if the lock is already acquired. |
+ IOError: if the open fails. |
+ CredentialsFileSymbolicLinkError if the file is a symbolic link. |
+ """ |
+ if self._locked: |
+ raise AlreadyLockedException('File %s is already locked' % |
+ self._filename) |
+ start_time = time.time() |
+ |
+ validate_file(self._filename) |
+ try: |
+ self._fh = open(self._filename, self._mode) |
+ except IOError, e: |
+ # If we can't access with _mode, try _fallback_mode and don't lock. |
+ if e.errno == errno.EACCES: |
+ self._fh = open(self._filename, self._fallback_mode) |
+ return |
+ |
+ # We opened in _mode, try to lock the file. |
+ while True: |
+ try: |
+ fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX) |
+ self._locked = True |
+ return |
+ except IOError, e: |
+ # If not retrying, then just pass on the error. |
+ if timeout == 0: |
+ raise e |
+ if e.errno != errno.EACCES: |
+ raise e |
+ # We could not acquire the lock. Try again. |
+ if (time.time() - start_time) >= timeout: |
+ logger.warn('Could not lock %s in %s seconds' % ( |
+ self._filename, timeout)) |
+ if self._fh: |
+ self._fh.close() |
+ self._fh = open(self._filename, self._fallback_mode) |
+ return |
+ time.sleep(delay) |
+ |
+ def unlock_and_close(self): |
+ """Close and unlock the file using the fcntl.lockf primitive.""" |
+ if self._locked: |
+ fcntl.lockf(self._fh.fileno(), fcntl.LOCK_UN) |
+ self._locked = False |
+ if self._fh: |
+ self._fh.close() |
+except ImportError: |
+ _FcntlOpener = None |
+ |
+ |
+try: |
+ import pywintypes |
+ import win32con |
+ import win32file |
+ |
+ class _Win32Opener(_Opener): |
+ """Open, lock, and unlock a file using windows primitives.""" |
+ |
+ # Error #33: |
+ # 'The process cannot access the file because another process' |
+ FILE_IN_USE_ERROR = 33 |
+ |
+ # Error #158: |
+ # 'The segment is already unlocked.' |
+ FILE_ALREADY_UNLOCKED_ERROR = 158 |
+ |
+ def open_and_lock(self, timeout, delay): |
+ """Open the file and lock it. |
+ |
+ Args: |
+ timeout: float, How long to try to lock for. |
+ delay: float, How long to wait between retries |
+ |
+ Raises: |
+ AlreadyLockedException: if the lock is already acquired. |
+ IOError: if the open fails. |
+ CredentialsFileSymbolicLinkError if the file is a symbolic link. |
+ """ |
+ if self._locked: |
+ raise AlreadyLockedException('File %s is already locked' % |
+ self._filename) |
+ start_time = time.time() |
+ |
+ validate_file(self._filename) |
+ try: |
+ self._fh = open(self._filename, self._mode) |
+ except IOError, e: |
+ # If we can't access with _mode, try _fallback_mode and don't lock. |
+ if e.errno == errno.EACCES: |
+ self._fh = open(self._filename, self._fallback_mode) |
+ return |
+ |
+ # We opened in _mode, try to lock the file. |
+ while True: |
+ try: |
+ hfile = win32file._get_osfhandle(self._fh.fileno()) |
+ win32file.LockFileEx( |
+ hfile, |
+ (win32con.LOCKFILE_FAIL_IMMEDIATELY| |
+ win32con.LOCKFILE_EXCLUSIVE_LOCK), 0, -0x10000, |
+ pywintypes.OVERLAPPED()) |
+ self._locked = True |
+ return |
+ except pywintypes.error, e: |
+ if timeout == 0: |
+ raise e |
+ |
+ # If the error is not that the file is already in use, raise. |
+ if e[0] != _Win32Opener.FILE_IN_USE_ERROR: |
+ raise |
+ |
+ # We could not acquire the lock. Try again. |
+ if (time.time() - start_time) >= timeout: |
+ logger.warn('Could not lock %s in %s seconds' % ( |
+ self._filename, timeout)) |
+ if self._fh: |
+ self._fh.close() |
+ self._fh = open(self._filename, self._fallback_mode) |
+ return |
+ time.sleep(delay) |
+ |
+ def unlock_and_close(self): |
+ """Close and unlock the file using the win32 primitive.""" |
+ if self._locked: |
+ try: |
+ hfile = win32file._get_osfhandle(self._fh.fileno()) |
+ win32file.UnlockFileEx(hfile, 0, -0x10000, pywintypes.OVERLAPPED()) |
+ except pywintypes.error, e: |
+ if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR: |
+ raise |
+ self._locked = False |
+ if self._fh: |
+ self._fh.close() |
+except ImportError: |
+ _Win32Opener = None |
+ |
+ |
+class LockedFile(object): |
+ """Represent a file that has exclusive access.""" |
+ |
+ @util.positional(4) |
+ def __init__(self, filename, mode, fallback_mode, use_native_locking=True): |
+ """Construct a LockedFile. |
+ |
+ Args: |
+ filename: string, The path of the file to open. |
+ mode: string, The mode to try to open the file with. |
+ fallback_mode: string, The mode to use if locking fails. |
+ use_native_locking: bool, Whether or not fcntl/win32 locking is used. |
+ """ |
+ opener = None |
+ if not opener and use_native_locking: |
+ if _Win32Opener: |
+ opener = _Win32Opener(filename, mode, fallback_mode) |
+ if _FcntlOpener: |
+ opener = _FcntlOpener(filename, mode, fallback_mode) |
+ |
+ if not opener: |
+ opener = _PosixOpener(filename, mode, fallback_mode) |
+ |
+ self._opener = opener |
+ |
+ def filename(self): |
+ """Return the filename we were constructed with.""" |
+ return self._opener._filename |
+ |
+ def file_handle(self): |
+ """Return the file_handle to the opened file.""" |
+ return self._opener.file_handle() |
+ |
+ def is_locked(self): |
+ """Return whether we successfully locked the file.""" |
+ return self._opener.is_locked() |
+ |
+ def open_and_lock(self, timeout=0, delay=0.05): |
+ """Open the file, trying to lock it. |
+ |
+ Args: |
+ timeout: float, The number of seconds to try to acquire the lock. |
+ delay: float, The number of seconds to wait between retry attempts. |
+ |
+ Raises: |
+ AlreadyLockedException: if the lock is already acquired. |
+ IOError: if the open fails. |
+ """ |
+ self._opener.open_and_lock(timeout, delay) |
+ |
+ def unlock_and_close(self): |
+ """Unlock and close a file.""" |
+ self._opener.unlock_and_close() |