• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 time
17
18import pywintypes
19import win32con
20import win32file
21
22from oauth2client.contrib import locked_file
23
24
25class _Win32Opener(locked_file._Opener):
26    """Open, lock, and unlock a file using windows primitives."""
27
28    # Error #33:
29    #  'The process cannot access the file because another process'
30    FILE_IN_USE_ERROR = 33
31
32    # Error #158:
33    #  'The segment is already unlocked.'
34    FILE_ALREADY_UNLOCKED_ERROR = 158
35
36    def open_and_lock(self, timeout, delay):
37        """Open the file and lock it.
38
39        Args:
40            timeout: float, How long to try to lock for.
41            delay: float, How long to wait between retries
42
43        Raises:
44            AlreadyLockedException: if the lock is already acquired.
45            IOError: if the open fails.
46            CredentialsFileSymbolicLinkError: if the file is a symbolic
47                                              link.
48        """
49        if self._locked:
50            raise locked_file.AlreadyLockedException(
51                'File {0} is already locked'.format(self._filename))
52        start_time = time.time()
53
54        locked_file.validate_file(self._filename)
55        try:
56            self._fh = open(self._filename, self._mode)
57        except IOError as e:
58            # If we can't access with _mode, try _fallback_mode
59            # and don't lock.
60            if e.errno == errno.EACCES:
61                self._fh = open(self._filename, self._fallback_mode)
62                return
63
64        # We opened in _mode, try to lock the file.
65        while True:
66            try:
67                hfile = win32file._get_osfhandle(self._fh.fileno())
68                win32file.LockFileEx(
69                    hfile,
70                    (win32con.LOCKFILE_FAIL_IMMEDIATELY |
71                     win32con.LOCKFILE_EXCLUSIVE_LOCK), 0, -0x10000,
72                    pywintypes.OVERLAPPED())
73                self._locked = True
74                return
75            except pywintypes.error as e:
76                if timeout == 0:
77                    raise
78
79                # If the error is not that the file is already
80                # in use, raise.
81                if e[0] != _Win32Opener.FILE_IN_USE_ERROR:
82                    raise
83
84                # We could not acquire the lock. Try again.
85                if (time.time() - start_time) >= timeout:
86                    locked_file.logger.warn('Could not lock %s in %s seconds',
87                                            self._filename, timeout)
88                    if self._fh:
89                        self._fh.close()
90                    self._fh = open(self._filename, self._fallback_mode)
91                    return
92                time.sleep(delay)
93
94    def unlock_and_close(self):
95        """Close and unlock the file using the win32 primitive."""
96        if self._locked:
97            try:
98                hfile = win32file._get_osfhandle(self._fh.fileno())
99                win32file.UnlockFileEx(hfile, 0, -0x10000,
100                                       pywintypes.OVERLAPPED())
101            except pywintypes.error as e:
102                if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR:
103                    raise
104        self._locked = False
105        if self._fh:
106            self._fh.close()
107