Fixed #19373 -- Ported Windows file locking from PyWin32 to ctypes

There wasn't any file locking under Windows unless PyWin32 was
installed. This removes that (undocumented) dependency by using ctypes
instead.

Thanks to Anatoly Techtonik for writing the ctypes port upon which this
is based.
This commit is contained in:
Kevin Christopher Henry 2013-09-17 21:34:45 -04:00
parent 07ae47f7f8
commit 6fe26bd3ee
2 changed files with 97 additions and 46 deletions

135
django/core/files/locks.py Normal file → Executable file
View File

@ -1,10 +1,13 @@
""" """
Portable file locking utilities. Portable file locking utilities.
Based partially on example by Jonathan Feignberg <jdf@pobox.com> in the Python Based partially on an example by Jonathan Feignberg in the Python
Cookbook, licensed under the Python Software License. Cookbook [1] (licensed under the Python Software License) and a ctypes port by
Anatoly Techtonik for Roundup [2] (license [3]).
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203 [1] http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203
[2] http://sourceforge.net/p/roundup/code/ci/default/tree/roundup/backends/portalocker.py
[3] http://sourceforge.net/p/roundup/code/ci/default/tree/COPYING.txt
Example Usage:: Example Usage::
@ -13,58 +16,98 @@ Example Usage::
... locks.lock(f, locks.LOCK_EX) ... locks.lock(f, locks.LOCK_EX)
... f.write('Django') ... f.write('Django')
""" """
import os
__all__ = ('LOCK_EX', 'LOCK_SH', 'LOCK_NB', 'lock', 'unlock') __all__ = ('LOCK_EX', 'LOCK_SH', 'LOCK_NB', 'lock', 'unlock')
system_type = None
try: def _fd(f):
import win32con
import win32file
import pywintypes
LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
LOCK_SH = 0
LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
__overlapped = pywintypes.OVERLAPPED()
system_type = 'nt'
except (ImportError, AttributeError):
pass
try:
import fcntl
LOCK_EX = fcntl.LOCK_EX
LOCK_SH = fcntl.LOCK_SH
LOCK_NB = fcntl.LOCK_NB
system_type = 'posix'
except (ImportError, AttributeError):
pass
def fd(f):
"""Get a filedescriptor from something which could be a file or an fd.""" """Get a filedescriptor from something which could be a file or an fd."""
return f.fileno() if hasattr(f, 'fileno') else f return f.fileno() if hasattr(f, 'fileno') else f
if system_type == 'nt':
def lock(file, flags):
hfile = win32file._get_osfhandle(fd(file))
win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped)
def unlock(file): if os.name == 'nt':
hfile = win32file._get_osfhandle(fd(file)) import msvcrt
win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped) from ctypes import (sizeof, c_ulong, c_void_p, c_int64,
elif system_type == 'posix': Structure, Union, POINTER, windll, byref)
def lock(file, flags): from ctypes.wintypes import BOOL, DWORD, HANDLE
fcntl.lockf(fd(file), flags)
def unlock(file): LOCK_SH = 0 # the default
fcntl.lockf(fd(file), fcntl.LOCK_UN) LOCK_NB = 0x1 # LOCKFILE_FAIL_IMMEDIATELY
else: LOCK_EX = 0x2 # LOCKFILE_EXCLUSIVE_LOCK
# File locking is not supported.
LOCK_EX = LOCK_SH = LOCK_NB = None # --- Adapted from the pyserial project ---
# detect size of ULONG_PTR
if sizeof(c_ulong) != sizeof(c_void_p):
ULONG_PTR = c_int64
else:
ULONG_PTR = c_ulong
PVOID = c_void_p
# --- Union inside Structure by stackoverflow:3480240 ---
class _OFFSET(Structure):
_fields_ = [
('Offset', DWORD),
('OffsetHigh', DWORD)]
class _OFFSET_UNION(Union):
_anonymous_ = ['_offset']
_fields_ = [
('_offset', _OFFSET),
('Pointer', PVOID)]
class OVERLAPPED(Structure):
_anonymous_ = ['_offset_union']
_fields_ = [
('Internal', ULONG_PTR),
('InternalHigh', ULONG_PTR),
('_offset_union', _OFFSET_UNION),
('hEvent', HANDLE)]
LPOVERLAPPED = POINTER(OVERLAPPED)
# --- Define function prototypes for extra safety ---
LockFileEx = windll.kernel32.LockFileEx
LockFileEx.restype = BOOL
LockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, DWORD, LPOVERLAPPED]
UnlockFileEx = windll.kernel32.UnlockFileEx
UnlockFileEx.restype = BOOL
UnlockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, LPOVERLAPPED]
def lock(f, flags):
hfile = msvcrt.get_osfhandle(_fd(f))
overlapped = OVERLAPPED()
ret = LockFileEx(hfile, flags, 0, 0, 0xFFFF0000, byref(overlapped))
return bool(ret)
def unlock(f):
hfile = msvcrt.get_osfhandle(_fd(f))
overlapped = OVERLAPPED()
ret = UnlockFileEx(hfile, 0, 0, 0xFFFF0000, byref(overlapped))
return bool(ret)
elif os.name == 'posix':
import fcntl
LOCK_SH = fcntl.LOCK_SH # shared lock
LOCK_NB = fcntl.LOCK_NB # non-blocking
LOCK_EX = fcntl.LOCK_EX
def lock(f, flags):
ret = fcntl.flock(_fd(f), flags)
return (ret == 0)
def unlock(f):
ret = fcntl.flock(_fd(f), fcntl.LOCK_UN)
return (ret == 0)
else: # File locking is not supported.
LOCK_EX = LOCK_SH = LOCK_NB = 0
# Dummy functions that don't do anything. # Dummy functions that don't do anything.
def lock(file, flags): def lock(f, flags):
pass # File is not locked
return False
def unlock(file): def unlock(f):
pass # File is unlocked
return True

View File

@ -415,6 +415,14 @@ Email
* The SMTP :class:`~django.core.mail.backends.smtp.EmailBackend` now accepts a * The SMTP :class:`~django.core.mail.backends.smtp.EmailBackend` now accepts a
:attr:`~django.core.mail.backends.smtp.EmailBackend.timeout` parameter. :attr:`~django.core.mail.backends.smtp.EmailBackend.timeout` parameter.
File Storage
^^^^^^^^^^^^
* File locking on Windows previously depended on the PyWin32 package; if it
wasn't installed, file locking failed silently. That dependency has been
removed, and file locking is now implemented natively on both Windows
and Unix.
File Uploads File Uploads
^^^^^^^^^^^^ ^^^^^^^^^^^^