Skip to content

Commit 9e73ae2

Browse files
committed
Synchronize FileLock with base from GHC 8.10
Fixes haskell#228.
1 parent 814e444 commit 9e73ae2

File tree

1 file changed

+153
-70
lines changed

1 file changed

+153
-70
lines changed
Lines changed: 153 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{-# LANGUAGE CPP #-}
22
{-# LANGUAGE InterruptibleFFI #-}
33
{-# LANGUAGE DeriveDataTypeable #-}
4+
{-# LANGUAGE LambdaCase #-}
5+
{-# LANGUAGE MultiWayIf #-}
46

57
-- | This compat module can be removed once base-4.10 (ghc-8.2) is the minimum
68
-- required version. Though note that the locking functionality is not in
@@ -12,14 +14,14 @@ module Hackage.Security.Util.FileLock (
1214
, LockMode(..)
1315
, hLock
1416
, hTryLock
17+
, hUnlock
1518
) where
1619

1720
#if MIN_VERSION_base(4,10,0)
1821

1922
import GHC.IO.Handle.Lock
2023

2124
#else
22-
2325
-- The remainder of this file is a modified copy
2426
-- of GHC.IO.Handle.Lock from ghc-8.2.x
2527
--
@@ -29,28 +31,20 @@ import GHC.IO.Handle.Lock
2931
-- instead since those are known major Unix platforms lacking @flock()@ or
3032
-- having broken one.
3133

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.
3536
#if defined(solaris2_HOST_OS) || defined(aix_HOST_OS)
3637

37-
import Control.Exception (throwIO)
38-
import System.IO (Handle)
39-
40-
#else
38+
#include <sys/file.h>
4139

4240
import Data.Bits
4341
import Data.Function
44-
import Control.Concurrent.MVar
45-
4642
import Foreign.C.Error
4743
import Foreign.C.Types
48-
49-
import GHC.IO.Handle.Types
5044
import GHC.IO.FD
51-
import GHC.IO.Exception
45+
import GHC.IO.Handle.FD
5246

53-
#if defined(mingw32_HOST_OS)
47+
#elif defined(mingw32_HOST_OS)
5448

5549
#if defined(i386_HOST_ARCH)
5650
## define WINDOWS_CCONV stdcall
@@ -62,19 +56,30 @@ import GHC.IO.Exception
6256

6357
#include <windows.h>
6458

59+
import Data.Bits
60+
import Data.Function
61+
import Foreign.C.Error
62+
import Foreign.C.Types
6563
import Foreign.Marshal.Alloc
6664
import Foreign.Marshal.Utils
67-
import Foreign.Ptr
65+
import GHC.IO.FD
66+
import GHC.IO.Handle.FD
67+
import GHC.Ptr
6868
import GHC.Windows
6969

70-
#else /* !defined(mingw32_HOST_OS), so assume unix with flock() */
71-
72-
#include <sys/file.h>
70+
#else
7371

74-
#endif /* !defined(mingw32_HOST_OS) */
72+
import GHC.IO (throwIO)
7573

76-
#endif /* !(defined(solaris2_HOST_OS) || defined(aix_HOST_OS)) */
74+
#endif /* HAVE_FLOCK */
7775

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
7883

7984
-- | Exception thrown by 'hLock' on non-Windows platforms that don't support
8085
-- 'flock'.
@@ -83,7 +88,6 @@ data FileLockingNotSupported = FileLockingNotSupported
8388

8489
instance Exception FileLockingNotSupported
8590

86-
8791
-- | Indicates a mode in which a file should be locked.
8892
data LockMode = SharedLock | ExclusiveLock
8993

@@ -107,25 +111,116 @@ data LockMode = SharedLock | ExclusiveLock
107111
--
108112
-- @since 4.10.0.0
109113
hLock :: Handle -> LockMode -> IO ()
110-
hLock h mode = lockImpl h "hLock" mode True >> return ()
114+
hLock h mode = void $ lockImpl h "hLock" mode True
111115

112116
-- | Non-blocking version of 'hLock'.
113117
--
114118
-- @since 4.10.0.0
115119
hTryLock :: Handle -> LockMode -> IO Bool
116120
hTryLock h mode = lockImpl h "hTryLock" mode False
117121

122+
-- | Release a lock taken with 'hLock' or 'hTryLock'.
123+
hUnlock :: Handle -> IO ()
124+
hUnlock = unlockImpl
125+
118126
----------------------------------------
119127

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
121162

122-
-- | No-op implementation.
123163
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}
125214

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}
127219

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)
129224

130225
lockImpl :: Handle -> String -> LockMode -> Bool -> IO Bool
131226
lockImpl h ctx mode block = do
@@ -137,23 +232,33 @@ lockImpl h ctx mode block = do
137232
-- We want to lock the whole file without looking up its size to be
138233
-- consistent with what flock does. According to documentation of LockFileEx
139234
-- "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
144238
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
149243
where
150-
sizeof_OVERLAPPED :: Int
151244
sizeof_OVERLAPPED = #{size OVERLAPPED}
152245

153246
cmode = case mode of
154247
SharedLock -> 0
155248
ExclusiveLock -> #{const LOCKFILE_EXCLUSIVE_LOCK}
156249

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+
157262
-- https://msdn.microsoft.com/en-us/library/aa297958.aspx
158263
foreign import ccall unsafe "_get_osfhandle"
159264
c_get_osfhandle :: CInt -> IO HANDLE
@@ -162,42 +267,20 @@ foreign import ccall unsafe "_get_osfhandle"
162267
foreign import WINDOWS_CCONV interruptible "LockFileEx"
163268
c_LockFileEx :: HANDLE -> DWORD -> DWORD -> DWORD -> DWORD -> Ptr () -> IO BOOL
164269

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
166273

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
181275

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
184279

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
200283

201-
#endif /* defined(solaris2_HOST_OS) || defined(aix_HOST_OS) */
284+
#endif
202285

203-
#endif /* MIN_VERSION_base */
286+
#endif /* MIN_VERSION_base(4,10,0) */

0 commit comments

Comments
 (0)