packages feed

ephemeral-pg-0.3.0.0: src/EphemeralPg/Process.hs

-- | Common process utilities for PostgreSQL command execution.
module EphemeralPg.Process
  ( -- * Process execution
    runProcessCapture,

    -- * Utilities
    findExecutable,
    getCurrentUser,
  )
where

import Control.Exception (IOException, try)
import Data.ByteString qualified as BS
import Data.Function ((&))
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.Encoding qualified as T
import System.Directory qualified as Dir
import System.Exit (ExitCode (..))
import System.IO (SeekMode (AbsoluteSeek), hFlush, hSeek)
import System.IO.Temp (withSystemTempFile)
import System.Posix.User (getEffectiveUserName)
import System.Process.Typed
  ( proc,
    runProcess,
    setStderr,
    setStdout,
    useHandleOpen,
  )

-- | Run a process and capture its output.
runProcessCapture ::
  -- | Executable
  FilePath ->
  -- | Arguments
  [String] ->
  -- | (exit code, stdout, stderr)
  IO (ExitCode, Text, Text)
runProcessCapture exe args =
  withSystemTempFile "ephpg-stdout" $ \outPath out -> do
    Dir.removeFile outPath
    withSystemTempFile "ephpg-stderr" $ \errPath err -> do
      Dir.removeFile errPath
      -- Anonymous file-backed output avoids pipe-reader cleanup waiting for a
      -- child that has not yet been terminated during asynchronous cancellation.
      -- Unlink before launching so SIGKILL cannot leave output files behind.
      let config = proc exe args & setStdout (useHandleOpen out) & setStderr (useHandleOpen err)
      exitCode <- runProcess config
      hFlush out
      hFlush err
      hSeek out AbsoluteSeek 0
      hSeek err AbsoluteSeek 0
      stdout <- BS.hGetContents out
      stderr <- BS.hGetContents err
      pure (exitCode, T.decodeUtf8Lenient stdout, T.decodeUtf8Lenient stderr)

-- | Find an executable in PATH.
findExecutable :: String -> IO (Maybe FilePath)
findExecutable = Dir.findExecutable

-- | Get the current effective username.
getCurrentUser :: IO Text
getCurrentUser = do
  result <- try getEffectiveUserName
  case result of
    Left (_ :: IOException) -> pure "postgres"
    Right name -> pure $ T.pack name