packages feed

cachix-1.1: src/Cachix/Deploy/Lock.hs

module Cachix.Deploy.Lock
  ( defaultLockDirectory,
    getLockDirectory,
    readPidFile,
    withTryLock,
    withTryLockAndPid,
  )
where

import qualified Lukko as Lock
import Protolude hiding ((<.>))
import qualified System.Directory as Directory
import System.FilePath ((<.>), (</>))
import System.Posix (getProcessID)
import System.Posix.Types (CPid (..))

lockExtension :: FilePath
lockExtension = "lock"

pidExtension :: FilePath
pidExtension = "pid"

defaultLockDirectory :: FilePath
defaultLockDirectory = "cachix" </> "deploy" </> "locks"

getLockDirectory :: IO FilePath
getLockDirectory = do
  lockDirectory <- Directory.getXdgDirectory Directory.XdgCache defaultLockDirectory

  Directory.createDirectoryIfMissing True lockDirectory
  Directory.setPermissions lockDirectory $
    Directory.emptyPermissions
      & Directory.setOwnerReadable True
      & Directory.setOwnerWritable True
      & Directory.setOwnerExecutable True
      & Directory.setOwnerSearchable True

  pure lockDirectory

readPidFile :: FilePath -> IO (Maybe CPid)
readPidFile pidFilename = do
  lockDirectory <- getLockDirectory
  pidContents <- readFile (lockDirectory </> pidFilename <.> pidExtension)
  pure (readMaybe pidContents)

-- | Run an IO action with an acquired profile lock. Returns immediately if the profile is already locked.
--
-- Lock files are not deleted after use.
--
-- macOS: if using sudo, make sure to use `-H` to reset the home directory.
withTryLock :: FilePath -> IO a -> IO (Maybe a)
withTryLock lockFilename action = do
  lockDirectory <- getLockDirectory

  let lockFile = lockDirectory </> lockFilename <.> lockExtension

  bracket
    (Lock.fdOpen lockFile)
    (Lock.fdUnlock *> Lock.fdClose)
    $ \fd -> do
      isLocked <- Lock.fdTryLock fd Lock.ExclusiveLock
      if isLocked
        then fmap Just action
        else pure Nothing

withTryLockAndPid :: FilePath -> IO a -> IO (Maybe a)
withTryLockAndPid lockFilename action = do
  lockDirectory <- getLockDirectory

  let pidFile = lockDirectory </> lockFilename <.> pidExtension

  withTryLock lockFilename $ do
    CPid pid <- getProcessID
    writeFile pidFile (show pid)
    action