packages feed

sandwich-contexts-0.3.0.1: lib/Test/Sandwich/Contexts/UnixSocketPath.hs

{-# LANGUAGE CPP #-}

module Test.Sandwich.Contexts.UnixSocketPath (
  withUnixSocketDirectory
  , maxUnixSocketLength
  ) where

import Control.Monad.IO.Unlift
import Relude
import System.IO.Error (IOError)
import Test.Sandwich.Expectations (expectationFailure)
import UnliftIO.Directory
import UnliftIO.Exception
import UnliftIO.Temporary


-- | The longest allowed path for a Unix socket on the current system.
maxUnixSocketLength :: Int
#ifdef mingw32_HOST_OS
maxUnixSocketLength = maxBound
#elif darwin_host_os
maxUnixSocketLength = 103 -- macOS: 104 with null terminator
#else
maxUnixSocketLength = 107 -- Linux: 108 with null terminator
#endif

-- | Create a temporary directory in which a Unix socket can be safely created,
-- bearing in mind the longest allowed Unix socket path on the system.
withUnixSocketDirectory :: (MonadUnliftIO m)
  -- | Name template, as passed to 'withSystemTempDirectory'
  => String
  -- | Amount of headroom to leave for a file name in this directory,
  -- before hitting the 'maxUnixSocketLength'
  -> Int
  -- | Callback
  -> (FilePath -> m a) -> m a
withUnixSocketDirectory nameTemplate headroom action = do
  withSystemTempDirectory nameTemplate $ \dir ->
    if | length dir + headroom <= maxUnixSocketLength -> action dir
       | otherwise -> withShortTempDir nameTemplate headroom action

withShortTempDir :: (
  MonadUnliftIO m
  )
  => String
  -> Int
  -> (FilePath -> m a)
  -> m a
withShortTempDir nameTemplate headroom action = doesDirectoryExist "/tmp" >>= \case
  True -> isDirectoryWritable "/tmp" >>= \case
    True -> withTempDirectory "/tmp" nameTemplate $ \dir ->
      if | length dir + headroom <= maxUnixSocketLength -> action dir
         | otherwise -> doFail
    False -> doFail
  _ -> doFail
  where
    doFail = expectationFailure "Couldn't create a short enough Unix socket path on this system."

isDirectoryWritable :: MonadUnliftIO m => FilePath -> m Bool
isDirectoryWritable dir = do
  try (getPermissions dir) >>= \case
    Left (_ :: IOError) -> return False
    Right perms -> return $ writable perms