1# Copyright 2016 Google Inc. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import errno 16import fcntl 17import time 18 19from oauth2client.contrib import locked_file 20 21 22class _FcntlOpener(locked_file._Opener): 23 """Open, lock, and unlock a file using fcntl.lockf.""" 24 25 def open_and_lock(self, timeout, delay): 26 """Open the file and lock it. 27 28 Args: 29 timeout: float, How long to try to lock for. 30 delay: float, How long to wait between retries 31 32 Raises: 33 AlreadyLockedException: if the lock is already acquired. 34 IOError: if the open fails. 35 CredentialsFileSymbolicLinkError: if the file is a symbolic 36 link. 37 """ 38 if self._locked: 39 raise locked_file.AlreadyLockedException( 40 'File {0} is already locked'.format(self._filename)) 41 start_time = time.time() 42 43 locked_file.validate_file(self._filename) 44 try: 45 self._fh = open(self._filename, self._mode) 46 except IOError as e: 47 # If we can't access with _mode, try _fallback_mode and 48 # don't lock. 49 if e.errno in (errno.EPERM, errno.EACCES): 50 self._fh = open(self._filename, self._fallback_mode) 51 return 52 53 # We opened in _mode, try to lock the file. 54 while True: 55 try: 56 fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX) 57 self._locked = True 58 return 59 except IOError as e: 60 # If not retrying, then just pass on the error. 61 if timeout == 0: 62 raise 63 if e.errno != errno.EACCES: 64 raise 65 # We could not acquire the lock. Try again. 66 if (time.time() - start_time) >= timeout: 67 locked_file.logger.warn('Could not lock %s in %s seconds', 68 self._filename, timeout) 69 if self._fh: 70 self._fh.close() 71 self._fh = open(self._filename, self._fallback_mode) 72 return 73 time.sleep(delay) 74 75 def unlock_and_close(self): 76 """Close and unlock the file using the fcntl.lockf primitive.""" 77 if self._locked: 78 fcntl.lockf(self._fh.fileno(), fcntl.LOCK_UN) 79 self._locked = False 80 if self._fh: 81 self._fh.close() 82