1
1
{-# LANGUAGE CPP #-}
2
2
{-# LANGUAGE InterruptibleFFI #-}
3
3
{-# LANGUAGE DeriveDataTypeable #-}
4
+ {-# LANGUAGE LambdaCase #-}
5
+ {-# LANGUAGE MultiWayIf #-}
4
6
5
7
-- | This compat module can be removed once base-4.10 (ghc-8.2) is the minimum
6
8
-- required version. Though note that the locking functionality is not in
@@ -12,14 +14,14 @@ module Hackage.Security.Util.FileLock (
12
14
, LockMode (.. )
13
15
, hLock
14
16
, hTryLock
17
+ , hUnlock
15
18
) where
16
19
17
20
#if MIN_VERSION_base(4,10,0)
18
21
19
22
import GHC.IO.Handle.Lock
20
23
21
24
#else
22
-
23
25
-- The remainder of this file is a modified copy
24
26
-- of GHC.IO.Handle.Lock from ghc-8.2.x
25
27
--
@@ -29,28 +31,20 @@ import GHC.IO.Handle.Lock
29
31
-- instead since those are known major Unix platforms lacking @flock()@ or
30
32
-- having broken one.
31
33
32
- import Control.Exception (Exception )
33
- import Data.Typeable
34
-
34
+ -- N.B. ideally we would make this condition a #define but sadly this breaks
35
+ -- older hsc2hs versions.
35
36
#if defined(solaris2_HOST_OS) || defined(aix_HOST_OS)
36
37
37
- import Control.Exception (throwIO )
38
- import System.IO (Handle )
39
-
40
- #else
38
+ #include <sys/file.h>
41
39
42
40
import Data.Bits
43
41
import Data.Function
44
- import Control.Concurrent.MVar
45
-
46
42
import Foreign.C.Error
47
43
import Foreign.C.Types
48
-
49
- import GHC.IO.Handle.Types
50
44
import GHC.IO.FD
51
- import GHC.IO.Exception
45
+ import GHC.IO.Handle.FD
52
46
53
- #if defined(mingw32_HOST_OS)
47
+ #elif defined(mingw32_HOST_OS)
54
48
55
49
#if defined(i386_HOST_ARCH)
56
50
## define WINDOWS_CCONV stdcall
@@ -62,19 +56,30 @@ import GHC.IO.Exception
62
56
63
57
#include <windows.h>
64
58
59
+ import Data.Bits
60
+ import Data.Function
61
+ import Foreign.C.Error
62
+ import Foreign.C.Types
65
63
import Foreign.Marshal.Alloc
66
64
import Foreign.Marshal.Utils
67
- import Foreign.Ptr
65
+ import GHC.IO.FD
66
+ import GHC.IO.Handle.FD
67
+ import GHC.Ptr
68
68
import GHC.Windows
69
69
70
- #else /* !defined(mingw32_HOST_OS), so assume unix with flock() */
71
-
72
- #include <sys/file.h>
70
+ #else
73
71
74
- #endif /* !defined(mingw32_HOST_OS) */
72
+ import GHC.IO ( throwIO )
75
73
76
- #endif /* !(defined(solaris2_HOST_OS) || defined(aix_HOST_OS)) */
74
+ #endif /* HAVE_FLOCK */
77
75
76
+ import Control.Monad (void )
77
+ import Data.Functor
78
+ import GHC.Base
79
+ import GHC.Exception
80
+ import GHC.IO.Handle.Types
81
+ import GHC.Show
82
+ import Data.Typeable
78
83
79
84
-- | Exception thrown by 'hLock' on non-Windows platforms that don't support
80
85
-- 'flock'.
@@ -83,7 +88,6 @@ data FileLockingNotSupported = FileLockingNotSupported
83
88
84
89
instance Exception FileLockingNotSupported
85
90
86
-
87
91
-- | Indicates a mode in which a file should be locked.
88
92
data LockMode = SharedLock | ExclusiveLock
89
93
@@ -107,25 +111,116 @@ data LockMode = SharedLock | ExclusiveLock
107
111
--
108
112
-- @since 4.10.0.0
109
113
hLock :: Handle -> LockMode -> IO ()
110
- hLock h mode = lockImpl h " hLock" mode True >> return ()
114
+ hLock h mode = void $ lockImpl h " hLock" mode True
111
115
112
116
-- | Non-blocking version of 'hLock'.
113
117
--
114
118
-- @since 4.10.0.0
115
119
hTryLock :: Handle -> LockMode -> IO Bool
116
120
hTryLock h mode = lockImpl h " hTryLock" mode False
117
121
122
+ -- | Release a lock taken with 'hLock' or 'hTryLock'.
123
+ hUnlock :: Handle -> IO ()
124
+ hUnlock = unlockImpl
125
+
118
126
----------------------------------------
119
127
120
- #if defined(solaris2_HOST_OS) || defined(aix_HOST_OS)
128
+ #if HAVE_OFD_LOCKING
129
+ -- Linux open file descriptor locking.
130
+ --
131
+ -- We prefer this over BSD locking (e.g. flock) since the latter appears to
132
+ -- break in some NFS configurations. Note that we intentionally do not try to
133
+ -- use ordinary POSIX file locking due to its peculiar semantics under
134
+ -- multi-threaded environments.
135
+
136
+ foreign import ccall interruptible " fcntl"
137
+ c_fcntl :: CInt -> CInt -> Ptr () -> IO CInt
138
+
139
+ data FLock = FLock { l_type :: CShort
140
+ , l_whence :: CShort
141
+ , l_start :: COff
142
+ , l_len :: COff
143
+ , l_pid :: CPid
144
+ }
145
+
146
+ instance Storable FLock where
147
+ sizeOf _ = # {size flock}
148
+ alignment _ = # {alignment flock}
149
+ poke ptr x = do
150
+ fillBytes ptr 0 (sizeOf x)
151
+ # {poke flock, l_type} ptr (l_type x)
152
+ # {poke flock, l_whence} ptr (l_whence x)
153
+ # {poke flock, l_start} ptr (l_start x)
154
+ # {poke flock, l_len} ptr (l_len x)
155
+ # {poke flock, l_pid} ptr (l_pid x)
156
+ peek ptr = do
157
+ FLock <$> # {peek flock, l_type} ptr
158
+ <*> # {peek flock, l_whence} ptr
159
+ <*> # {peek flock, l_start} ptr
160
+ <*> # {peek flock, l_len} ptr
161
+ <*> # {peek flock, l_pid} ptr
121
162
122
- -- | No-op implementation.
123
163
lockImpl :: Handle -> String -> LockMode -> Bool -> IO Bool
124
- lockImpl _ _ _ _ = throwIO FileLockingNotSupported
164
+ lockImpl h ctx mode block = do
165
+ FD {fdFD = fd} <- handleToFd h
166
+ with flock $ \ flock_ptr -> fix $ \ retry -> do
167
+ ret <- with flock $ fcntl fd mode flock_ptr
168
+ case ret of
169
+ 0 -> return True
170
+ _ -> getErrno >>= \ errno -> if
171
+ | not block && errno == eWOULDBLOCK -> return False
172
+ | errno == eINTR -> retry
173
+ | otherwise -> ioException $ errnoToIOError ctx errno (Just h) Nothing
174
+ where
175
+ flock = FLock { l_type = case mode of
176
+ SharedLock -> # {const F_RDLCK }
177
+ ExclusiveLock -> # {const F_WRLCK }
178
+ , l_whence = # {const SEEK_SET }
179
+ , l_start = 0
180
+ , l_len = 0
181
+ }
182
+ mode
183
+ | block = # {const F_SETLKW }
184
+ | otherwise = # {const F_SETLK }
185
+
186
+ unlockImpl :: Handle -> IO ()
187
+ unlockImpl h = do
188
+ FD {fdFD = fd} <- handleToFd h
189
+ let flock = FLock { l_type = # {const F_UNLCK }
190
+ , l_whence = # {const SEEK_SET }
191
+ , l_start = 0
192
+ , l_len = 0
193
+ }
194
+ throwErrnoIfMinus1_ " hUnlock"
195
+ $ with flock $ c_fcntl fd # {const F_SETLK }
196
+
197
+ #elif HAVE_FLOCK
198
+
199
+ lockImpl :: Handle -> String -> LockMode -> Bool -> IO Bool
200
+ lockImpl h ctx mode block = do
201
+ FD {fdFD = fd} <- handleToFd h
202
+ let flags = cmode .|. (if block then 0 else # {const LOCK_NB })
203
+ fix $ \ retry -> c_flock fd flags >>= \ case
204
+ 0 -> return True
205
+ _ -> getErrno >>= \ errno -> if
206
+ | not block
207
+ , errno == eAGAIN || errno == eACCES -> return False
208
+ | errno == eINTR -> retry
209
+ | otherwise -> ioException $ errnoToIOError ctx errno (Just h) Nothing
210
+ where
211
+ cmode = case mode of
212
+ SharedLock -> # {const LOCK_SH }
213
+ ExclusiveLock -> # {const LOCK_EX }
125
214
126
- #else /* !(defined(solaris2_HOST_OS) || defined(aix_HOST_OS)) */
215
+ unlockImpl :: Handle -> IO ()
216
+ unlockImpl h = do
217
+ FD {fdFD = fd} <- handleToFd h
218
+ throwErrnoIfMinus1_ " flock" $ c_flock fd # {const LOCK_UN }
127
219
128
- #if defined(mingw32_HOST_OS)
220
+ foreign import ccall interruptible " flock"
221
+ c_flock :: CInt -> CInt -> IO CInt
222
+
223
+ #elif defined(mingw32_HOST_OS)
129
224
130
225
lockImpl :: Handle -> String -> LockMode -> Bool -> IO Bool
131
226
lockImpl h ctx mode block = do
@@ -137,23 +232,33 @@ lockImpl h ctx mode block = do
137
232
-- We want to lock the whole file without looking up its size to be
138
233
-- consistent with what flock does. According to documentation of LockFileEx
139
234
-- "locking a region that goes beyond the current end-of-file position is
140
- -- not an error", however e.g. Windows 10 doesn't accept maximum possible
141
- -- value (a pair of MAXDWORDs) for mysterious reasons. Work around that by
142
- -- trying 2^32-1.
143
- fix $ \ retry -> c_LockFileEx wh flags 0 0xffffffff 0x0 ovrlpd >>= \ b -> case b of
235
+ -- not an error", hence we pass maximum value as the number of bytes to
236
+ -- lock.
237
+ fix $ \ retry -> c_LockFileEx wh flags 0 0xffffffff 0xffffffff ovrlpd >>= \ case
144
238
True -> return True
145
- False -> getLastError >>= \ err -> case () of
146
- () | not block && err == # {const ERROR_LOCK_VIOLATION } -> return False
147
- | err == # {const ERROR_OPERATION_ABORTED } -> retry
148
- | otherwise -> failWith ctx err
239
+ False -> getLastError >>= \ err -> if
240
+ | not block && err == # {const ERROR_LOCK_VIOLATION } -> return False
241
+ | err == # {const ERROR_OPERATION_ABORTED } -> retry
242
+ | otherwise -> failWith ctx err
149
243
where
150
- sizeof_OVERLAPPED :: Int
151
244
sizeof_OVERLAPPED = # {size OVERLAPPED }
152
245
153
246
cmode = case mode of
154
247
SharedLock -> 0
155
248
ExclusiveLock -> # {const LOCKFILE_EXCLUSIVE_LOCK }
156
249
250
+ unlockImpl :: Handle -> IO ()
251
+ unlockImpl h = do
252
+ FD {fdFD = fd} <- handleToFd h
253
+ wh <- throwErrnoIf (== iNVALID_HANDLE_VALUE) " hUnlock" $ c_get_osfhandle fd
254
+ allocaBytes sizeof_OVERLAPPED $ \ ovrlpd -> do
255
+ fillBytes ovrlpd 0 sizeof_OVERLAPPED
256
+ c_UnlockFileEx wh 0 0xffffffff 0xffffffff ovrlpd >>= \ case
257
+ True -> return ()
258
+ False -> getLastError >>= failWith " hUnlock"
259
+ where
260
+ sizeof_OVERLAPPED = # {size OVERLAPPED }
261
+
157
262
-- https://msdn.microsoft.com/en-us/library/aa297958.aspx
158
263
foreign import ccall unsafe " _get_osfhandle"
159
264
c_get_osfhandle :: CInt -> IO HANDLE
@@ -162,42 +267,20 @@ foreign import ccall unsafe "_get_osfhandle"
162
267
foreign import WINDOWS_CCONV interruptible " LockFileEx"
163
268
c_LockFileEx :: HANDLE -> DWORD -> DWORD -> DWORD -> DWORD -> Ptr () -> IO BOOL
164
269
165
- #else /* !defined(mingw32_HOST_OS), so assume unix with flock() */
270
+ -- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365716.aspx
271
+ foreign import WINDOWS_CCONV interruptible " UnlockFileEx"
272
+ c_UnlockFileEx :: HANDLE -> DWORD -> DWORD -> DWORD -> Ptr () -> IO BOOL
166
273
167
- lockImpl :: Handle -> String -> LockMode -> Bool -> IO Bool
168
- lockImpl h ctx mode block = do
169
- FD {fdFD = fd} <- handleToFd h
170
- let flags = cmode .|. (if block then 0 else # {const LOCK_NB })
171
- fix $ \ retry -> c_flock fd flags >>= \ n -> case n of
172
- 0 -> return True
173
- _ -> getErrno >>= \ errno -> case () of
174
- () | not block && errno == eWOULDBLOCK -> return False
175
- | errno == eINTR -> retry
176
- | otherwise -> ioException $ errnoToIOError ctx errno (Just h) Nothing
177
- where
178
- cmode = case mode of
179
- SharedLock -> # {const LOCK_SH }
180
- ExclusiveLock -> # {const LOCK_EX }
274
+ #else
181
275
182
- foreign import ccall interruptible " flock"
183
- c_flock :: CInt -> CInt -> IO CInt
276
+ -- | No-op implementation.
277
+ lockImpl :: Handle -> String -> LockMode -> Bool -> IO Bool
278
+ lockImpl _ _ _ _ = throwIO FileLockingNotSupported
184
279
185
- #endif /* !defined(mingw32_HOST_OS) */
186
-
187
- -- | Turn an existing Handle into a file descriptor. This function throws an
188
- -- IOError if the Handle does not reference a file descriptor.
189
- handleToFd :: Handle -> IO FD
190
- handleToFd h = case h of
191
- FileHandle _ mv -> do
192
- Handle__ {haDevice = dev} <- readMVar mv
193
- case cast dev of
194
- Just fd -> return fd
195
- Nothing -> throwErr " not a file descriptor"
196
- DuplexHandle {} -> throwErr " not a file handle"
197
- where
198
- throwErr msg = ioException $ IOError (Just h)
199
- InappropriateType " handleToFd" msg Nothing Nothing
280
+ -- | No-op implementation.
281
+ unlockImpl :: Handle -> IO ()
282
+ unlockImpl _ = throwIO FileLockingNotSupported
200
283
201
- #endif /* defined(solaris2_HOST_OS) || defined(aix_HOST_OS) */
284
+ #endif
202
285
203
- #endif /* MIN_VERSION_base */
286
+ #endif /* MIN_VERSION_base(4,10,0) */
0 commit comments