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