1import os
2import sys
3from . import exceptions
4from . import constants
5
6
7if os.name == 'nt':  # pragma: no cover
8    import win32con
9    import win32file
10    import pywintypes
11    import winerror
12    import msvcrt
13    __overlapped = pywintypes.OVERLAPPED()
14
15    if sys.version_info.major == 2:
16        lock_length = -1
17    else:
18        lock_length = int(2**31 - 1)
19
20    def lock(file_, flags):
21        if flags & constants.LOCK_SH:
22            if sys.version_info.major == 2:
23                if flags & constants.LOCK_NB:
24                    mode = win32con.LOCKFILE_FAIL_IMMEDIATELY
25                else:
26                    mode = 0
27
28            else:
29                if flags & constants.LOCK_NB:
30                    mode = msvcrt.LK_NBRLCK
31                else:
32                    mode = msvcrt.LK_RLCK
33
34            # is there any reason not to reuse the following structure?
35            hfile = win32file._get_osfhandle(file_.fileno())
36            try:
37                win32file.LockFileEx(hfile, mode, 0, -0x10000, __overlapped)
38            except pywintypes.error as exc_value:
39                # error: (33, 'LockFileEx', 'The process cannot access the file
40                # because another process has locked a portion of the file.')
41                if exc_value.winerror == winerror.ERROR_LOCK_VIOLATION:
42                    raise exceptions.LockException(
43                        exceptions.LockException.LOCK_FAILED,
44                        exc_value.strerror,
45                        fh=file_)
46                else:
47                    # Q:  Are there exceptions/codes we should be dealing with
48                    # here?
49                    raise
50        else:
51            mode = win32con.LOCKFILE_EXCLUSIVE_LOCK
52            if flags & constants.LOCK_NB:
53                mode |= win32con.LOCKFILE_FAIL_IMMEDIATELY
54
55            if flags & constants.LOCK_NB:
56                mode = msvcrt.LK_NBLCK
57            else:
58                mode = msvcrt.LK_LOCK
59
60            # windows locks byte ranges, so make sure to lock from file start
61            try:
62                savepos = file_.tell()
63                if savepos:
64                    # [ ] test exclusive lock fails on seek here
65                    # [ ] test if shared lock passes this point
66                    file_.seek(0)
67                    # [x] check if 0 param locks entire file (not documented in
68                    #     Python)
69                    # [x] fails with "IOError: [Errno 13] Permission denied",
70                    #     but -1 seems to do the trick
71
72                try:
73                    msvcrt.locking(file_.fileno(), mode, lock_length)
74                except IOError as exc_value:
75                    # [ ] be more specific here
76                    raise exceptions.LockException(
77                        exceptions.LockException.LOCK_FAILED,
78                        exc_value.strerror,
79                        fh=file_)
80                finally:
81                    if savepos:
82                        file_.seek(savepos)
83            except IOError as exc_value:
84                raise exceptions.LockException(
85                    exceptions.LockException.LOCK_FAILED, exc_value.strerror,
86                    fh=file_)
87
88    def unlock(file_):
89        try:
90            savepos = file_.tell()
91            if savepos:
92                file_.seek(0)
93
94            try:
95                msvcrt.locking(file_.fileno(), constants.LOCK_UN, lock_length)
96            except IOError as exc_value:
97                if exc_value.strerror == 'Permission denied':
98                    hfile = win32file._get_osfhandle(file_.fileno())
99                    try:
100                        win32file.UnlockFileEx(
101                            hfile, 0, -0x10000, __overlapped)
102                    except pywintypes.error as exc_value:
103                        if exc_value.winerror == winerror.ERROR_NOT_LOCKED:
104                            # error: (158, 'UnlockFileEx',
105                            #         'The segment is already unlocked.')
106                            # To match the 'posix' implementation, silently
107                            # ignore this error
108                            pass
109                        else:
110                            # Q:  Are there exceptions/codes we should be
111                            # dealing with here?
112                            raise
113                else:
114                    raise exceptions.LockException(
115                        exceptions.LockException.LOCK_FAILED,
116                        exc_value.strerror,
117                        fh=file_)
118            finally:
119                if savepos:
120                    file_.seek(savepos)
121        except IOError as exc_value:
122            raise exceptions.LockException(
123                exceptions.LockException.LOCK_FAILED, exc_value.strerror,
124                fh=file_)
125
126elif os.name == 'posix':  # pragma: no cover
127    import fcntl
128
129    def lock(file_, flags):
130        locking_exceptions = IOError,
131        try:  # pragma: no cover
132            locking_exceptions += BlockingIOError,
133        except NameError:  # pragma: no cover
134            pass
135
136        try:
137            fcntl.flock(file_.fileno(), flags)
138        except locking_exceptions as exc_value:
139            # The exception code varies on different systems so we'll catch
140            # every IO error
141            raise exceptions.LockException(exc_value, fh=file_)
142
143    def unlock(file_):
144        fcntl.flock(file_.fileno(), constants.LOCK_UN)
145
146else:  # pragma: no cover
147    raise RuntimeError('PortaLocker only defined for nt and posix platforms')
148
149