1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 """Locked file interface that should work on Unix and Windows pythons.
16
17 This module first tries to use fcntl locking to ensure serialized access
18 to a file, then falls back on a lock file if that is unavialable.
19
20 Usage:
21 f = LockedFile('filename', 'r+b', 'rb')
22 f.open_and_lock()
23 if f.is_locked():
24 print 'Acquired filename with r+b mode'
25 f.file_handle().write('locked data')
26 else:
27 print 'Aquired filename with rb mode'
28 f.unlock_and_close()
29 """
30
31 __author__ = 'cache@google.com (David T McWherter)'
32
33 import errno
34 import logging
35 import os
36 import time
37
38 from oauth2client import util
39
40 logger = logging.getLogger(__name__)
44 """Credentials files must not be symbolic links."""
45
48 """Trying to lock a file that has already been locked by the LockedFile."""
49 pass
50
56
58 """Base class for different locking primitives."""
59
60 - def __init__(self, filename, mode, fallback_mode):
61 """Create an Opener.
62
63 Args:
64 filename: string, The pathname of the file.
65 mode: string, The preferred mode to access the file with.
66 fallback_mode: string, The mode to use if locking fails.
67 """
68 self._locked = False
69 self._filename = filename
70 self._mode = mode
71 self._fallback_mode = fallback_mode
72 self._fh = None
73 self._lock_fd = None
74
76 """Was the file locked."""
77 return self._locked
78
80 """The file handle to the file. Valid only after opened."""
81 return self._fh
82
84 """The filename that is being locked."""
85 return self._filename
86
88 """Open the file and lock it.
89
90 Args:
91 timeout: float, How long to try to lock for.
92 delay: float, How long to wait between retries.
93 """
94 pass
95
97 """Unlock and close the file."""
98 pass
99
102 """Lock files using Posix advisory lock files."""
103
105 """Open the file and lock it.
106
107 Tries to create a .lock file next to the file we're trying to open.
108
109 Args:
110 timeout: float, How long to try to lock for.
111 delay: float, How long to wait between retries.
112
113 Raises:
114 AlreadyLockedException: if the lock is already acquired.
115 IOError: if the open fails.
116 CredentialsFileSymbolicLinkError if the file is a symbolic link.
117 """
118 if self._locked:
119 raise AlreadyLockedException('File %s is already locked' %
120 self._filename)
121 self._locked = False
122
123 validate_file(self._filename)
124 try:
125 self._fh = open(self._filename, self._mode)
126 except IOError as e:
127
128 if e.errno == errno.EACCES:
129 self._fh = open(self._filename, self._fallback_mode)
130 return
131
132 lock_filename = self._posix_lockfile(self._filename)
133 start_time = time.time()
134 while True:
135 try:
136 self._lock_fd = os.open(lock_filename,
137 os.O_CREAT|os.O_EXCL|os.O_RDWR)
138 self._locked = True
139 break
140
141 except OSError as e:
142 if e.errno != errno.EEXIST:
143 raise
144 if (time.time() - start_time) >= timeout:
145 logger.warn('Could not acquire lock %s in %s seconds',
146 lock_filename, timeout)
147
148 if self._fh:
149 self._fh.close()
150 self._fh = open(self._filename, self._fallback_mode)
151 return
152 time.sleep(delay)
153
155 """Unlock a file by removing the .lock file, and close the handle."""
156 if self._locked:
157 lock_filename = self._posix_lockfile(self._filename)
158 os.close(self._lock_fd)
159 os.unlink(lock_filename)
160 self._locked = False
161 self._lock_fd = None
162 if self._fh:
163 self._fh.close()
164
166 """The name of the lock file to use for posix locking."""
167 return '%s.lock' % filename
168
169
170 try:
171 import fcntl
174 """Open, lock, and unlock a file using fcntl.lockf."""
175
177 """Open the file and lock it.
178
179 Args:
180 timeout: float, How long to try to lock for.
181 delay: float, How long to wait between retries
182
183 Raises:
184 AlreadyLockedException: if the lock is already acquired.
185 IOError: if the open fails.
186 CredentialsFileSymbolicLinkError if the file is a symbolic link.
187 """
188 if self._locked:
189 raise AlreadyLockedException('File %s is already locked' %
190 self._filename)
191 start_time = time.time()
192
193 validate_file(self._filename)
194 try:
195 self._fh = open(self._filename, self._mode)
196 except IOError as e:
197
198 if e.errno in (errno.EPERM, errno.EACCES):
199 self._fh = open(self._filename, self._fallback_mode)
200 return
201
202
203 while True:
204 try:
205 fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX)
206 self._locked = True
207 return
208 except IOError as e:
209
210 if timeout == 0:
211 raise e
212 if e.errno != errno.EACCES:
213 raise e
214
215 if (time.time() - start_time) >= timeout:
216 logger.warn('Could not lock %s in %s seconds',
217 self._filename, timeout)
218 if self._fh:
219 self._fh.close()
220 self._fh = open(self._filename, self._fallback_mode)
221 return
222 time.sleep(delay)
223
225 """Close and unlock the file using the fcntl.lockf primitive."""
226 if self._locked:
227 fcntl.lockf(self._fh.fileno(), fcntl.LOCK_UN)
228 self._locked = False
229 if self._fh:
230 self._fh.close()
231 except ImportError:
232 _FcntlOpener = None
233
234
235 try:
236 import pywintypes
237 import win32con
238 import win32file
241 """Open, lock, and unlock a file using windows primitives."""
242
243
244
245 FILE_IN_USE_ERROR = 33
246
247
248
249 FILE_ALREADY_UNLOCKED_ERROR = 158
250
252 """Open the file and lock it.
253
254 Args:
255 timeout: float, How long to try to lock for.
256 delay: float, How long to wait between retries
257
258 Raises:
259 AlreadyLockedException: if the lock is already acquired.
260 IOError: if the open fails.
261 CredentialsFileSymbolicLinkError if the file is a symbolic link.
262 """
263 if self._locked:
264 raise AlreadyLockedException('File %s is already locked' %
265 self._filename)
266 start_time = time.time()
267
268 validate_file(self._filename)
269 try:
270 self._fh = open(self._filename, self._mode)
271 except IOError as e:
272
273 if e.errno == errno.EACCES:
274 self._fh = open(self._filename, self._fallback_mode)
275 return
276
277
278 while True:
279 try:
280 hfile = win32file._get_osfhandle(self._fh.fileno())
281 win32file.LockFileEx(
282 hfile,
283 (win32con.LOCKFILE_FAIL_IMMEDIATELY|
284 win32con.LOCKFILE_EXCLUSIVE_LOCK), 0, -0x10000,
285 pywintypes.OVERLAPPED())
286 self._locked = True
287 return
288 except pywintypes.error as e:
289 if timeout == 0:
290 raise e
291
292
293 if e[0] != _Win32Opener.FILE_IN_USE_ERROR:
294 raise
295
296
297 if (time.time() - start_time) >= timeout:
298 logger.warn('Could not lock %s in %s seconds' % (
299 self._filename, timeout))
300 if self._fh:
301 self._fh.close()
302 self._fh = open(self._filename, self._fallback_mode)
303 return
304 time.sleep(delay)
305
307 """Close and unlock the file using the win32 primitive."""
308 if self._locked:
309 try:
310 hfile = win32file._get_osfhandle(self._fh.fileno())
311 win32file.UnlockFileEx(hfile, 0, -0x10000, pywintypes.OVERLAPPED())
312 except pywintypes.error as e:
313 if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR:
314 raise
315 self._locked = False
316 if self._fh:
317 self._fh.close()
318 except ImportError:
319 _Win32Opener = None
323 """Represent a file that has exclusive access."""
324
325 @util.positional(4)
326 - def __init__(self, filename, mode, fallback_mode, use_native_locking=True):
327 """Construct a LockedFile.
328
329 Args:
330 filename: string, The path of the file to open.
331 mode: string, The mode to try to open the file with.
332 fallback_mode: string, The mode to use if locking fails.
333 use_native_locking: bool, Whether or not fcntl/win32 locking is used.
334 """
335 opener = None
336 if not opener and use_native_locking:
337 if _Win32Opener:
338 opener = _Win32Opener(filename, mode, fallback_mode)
339 if _FcntlOpener:
340 opener = _FcntlOpener(filename, mode, fallback_mode)
341
342 if not opener:
343 opener = _PosixOpener(filename, mode, fallback_mode)
344
345 self._opener = opener
346
348 """Return the filename we were constructed with."""
349 return self._opener._filename
350
352 """Return the file_handle to the opened file."""
353 return self._opener.file_handle()
354
356 """Return whether we successfully locked the file."""
357 return self._opener.is_locked()
358
360 """Open the file, trying to lock it.
361
362 Args:
363 timeout: float, The number of seconds to try to acquire the lock.
364 delay: float, The number of seconds to wait between retry attempts.
365
366 Raises:
367 AlreadyLockedException: if the lock is already acquired.
368 IOError: if the open fails.
369 """
370 self._opener.open_and_lock(timeout, delay)
371
375