{-# LANGUAGE InterruptibleFFI #-} module System.FileLock.Internal.Flock #ifndef USE_FLOCK () where #else (Lock, lock, tryLock, unlock) where #include import Control.Applicative import Control.Concurrent (yield) import qualified Control.Exception as E import Data.Bits import Foreign.C.Error import Foreign.C.Types import System.Posix.Files import System.Posix.IO (openFd, closeFd, defaultFileFlags, OpenMode(..), setFdOption, FdOption(..)) import System.Posix.Types import Prelude type Lock = Fd lock :: FilePath -> Bool -> IO Lock lock path exclusive = do fd <- open path (`E.onException` closeFd fd) $ do True <- flock fd exclusive True return fd tryLock :: FilePath -> Bool -> IO (Maybe Lock) tryLock path exclusive = do fd <- open path (`E.onException` closeFd fd) $ do success <- flock fd exclusive False if success then return $ Just $ fd else Nothing <$ closeFd fd unlock :: Lock -> IO () unlock fd = closeFd fd open :: FilePath -> IO Fd open path = do #if MIN_VERSION_unix(2,8,0) fd <- openFd path WriteOnly defaultFileFlags #else fd <- openFd path WriteOnly (Just stdFileMode) defaultFileFlags #endif -- Ideally, we would open the file descriptor with CLOEXEC enabled, but since -- unix 2.8 hasn't been released yet and we want backwards compatibility with -- older releases, we set CLOEXEC after opening the file descriptor. This -- may seem like a race condition at first. However, since the lock is always -- taken after CLOEXEC is set, the worst that can happen is that a child -- process inherits the open FD in an unlocked state. While non-ideal from a -- performance standpoint, it doesn't introduce any locking bugs. setFdOption fd CloseOnExec True return fd flock :: Fd -> Bool -> Bool -> IO Bool flock (Fd fd) exclusive block = do r <- c_flock fd $ modeOp .|. blockOp if r == 0 then return True -- success else do errno <- getErrno case () of _ | errno == eWOULDBLOCK -> return False -- already taken | errno == eINTR -> do -- If InterruptibleFFI interrupted the syscall with EINTR, -- we need to give the accompanying Haskell exception a chance to bubble. -- See also https://gitlab.haskell.org/ghc/ghc/issues/8684#note_142404. E.interruptible yield flock (Fd fd) exclusive block | otherwise -> throwErrno "flock" where modeOp = case exclusive of False -> #{const LOCK_SH} True -> #{const LOCK_EX} blockOp = case block of True -> 0 False -> #{const LOCK_NB} -- `interruptible` so that async exceptions like `timeout` can stop it -- when used in blocking mode (without `LOCK_NB`). foreign import ccall interruptible "flock" c_flock :: CInt -> CInt -> IO CInt #endif /* USE_FLOCK */