{-# LANGUAGE Safe #-}
{-# LANGUAGE CPP #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  System.Environment
-- Copyright   :  (c) The University of Glasgow 2001
-- License     :  BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer  :  libraries@haskell.org
-- Stability   :  provisional
-- Portability :  portable
--
-- Miscellaneous information about the system environment.
--
-----------------------------------------------------------------------------

module System.Environment
    (
      getArgs,
      getProgName,
      getExecutablePath,
      getEnv,
      lookupEnv,
      setEnv,
      unsetEnv,
      withArgs,
      withProgName,
      getEnvironment,
  ) where

import Foreign
import Foreign.C
import System.IO.Error (mkIOError)
import Control.Exception.Base (bracket_, throwIO)
#if defined(mingw32_HOST_OS)
import Control.Exception.Base (bracket)
#endif
-- import GHC.IO
import GHC.IO.Exception
import qualified GHC.Foreign as GHC
import Control.Monad
#if defined(mingw32_HOST_OS)
import GHC.IO.Encoding (argvEncoding)
import GHC.Windows
#else
import GHC.IO.Encoding (getFileSystemEncoding, argvEncoding)
import System.Posix.Internals (withFilePath)
#endif

import System.Environment.ExecutablePath

#if defined(mingw32_HOST_OS)
# if defined(i386_HOST_ARCH)
#  define WINDOWS_CCONV stdcall
# elif defined(x86_64_HOST_ARCH)
#  define WINDOWS_CCONV ccall
# else
#  error Unknown mingw32 arch
# endif
#endif

#include "HsBaseConfig.h"

-- ---------------------------------------------------------------------------
-- getArgs, getProgName, getEnv

-- | Computation 'getArgs' returns a list of the program's command
-- line arguments (not including the program name).
getArgs :: IO [String]
getArgs :: IO [String]
getArgs =
  (Ptr CInt -> IO [String]) -> IO [String]
forall a b. Storable a => (Ptr a -> IO b) -> IO b
alloca ((Ptr CInt -> IO [String]) -> IO [String])
-> (Ptr CInt -> IO [String]) -> IO [String]
forall a b. (a -> b) -> a -> b
$ \ Ptr CInt
p_argc ->
  (Ptr (Ptr CString) -> IO [String]) -> IO [String]
forall a b. Storable a => (Ptr a -> IO b) -> IO b
alloca ((Ptr (Ptr CString) -> IO [String]) -> IO [String])
-> (Ptr (Ptr CString) -> IO [String]) -> IO [String]
forall a b. (a -> b) -> a -> b
$ \ Ptr (Ptr CString)
p_argv -> do
   Ptr CInt -> Ptr (Ptr CString) -> IO ()
getProgArgv Ptr CInt
p_argc Ptr (Ptr CString)
p_argv
   Int
p    <- CInt -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (CInt -> Int) -> IO CInt -> IO Int
forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
`liftM` Ptr CInt -> IO CInt
forall a. Storable a => Ptr a -> IO a
peek Ptr CInt
p_argc
   Ptr CString
argv <- Ptr (Ptr CString) -> IO (Ptr CString)
forall a. Storable a => Ptr a -> IO a
peek Ptr (Ptr CString)
p_argv
   TextEncoding
enc <- IO TextEncoding
argvEncoding
   Int -> Ptr CString -> IO [CString]
forall a. Storable a => Int -> Ptr a -> IO [a]
peekArray (Int
p Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) (Ptr CString -> Int -> Ptr CString
forall a. Storable a => Ptr a -> Int -> Ptr a
advancePtr Ptr CString
argv Int
1) IO [CString] -> ([CString] -> IO [String]) -> IO [String]
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (CString -> IO String) -> [CString] -> IO [String]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (TextEncoding -> CString -> IO String
GHC.peekCString TextEncoding
enc)


foreign import ccall unsafe "getProgArgv"
  getProgArgv :: Ptr CInt -> Ptr (Ptr CString) -> IO ()

{-|
Computation 'getProgName' returns the name of the program as it was
invoked.

However, this is hard-to-impossible to implement on some non-Unix
OSes, so instead, for maximum portability, we just return the leafname
of the program as invoked. Even then there are some differences
between platforms: on Windows, for example, a program invoked as foo
is probably really @FOO.EXE@, and that is what 'getProgName' will return.
-}
getProgName :: IO String
-- Ignore the arguments to hs_init on Windows for the sake of Unicode compat
getProgName :: IO String
getProgName =
  (Ptr CInt -> IO String) -> IO String
forall a b. Storable a => (Ptr a -> IO b) -> IO b
alloca ((Ptr CInt -> IO String) -> IO String)
-> (Ptr CInt -> IO String) -> IO String
forall a b. (a -> b) -> a -> b
$ \ Ptr CInt
p_argc ->
  (Ptr (Ptr CString) -> IO String) -> IO String
forall a b. Storable a => (Ptr a -> IO b) -> IO b
alloca ((Ptr (Ptr CString) -> IO String) -> IO String)
-> (Ptr (Ptr CString) -> IO String) -> IO String
forall a b. (a -> b) -> a -> b
$ \ Ptr (Ptr CString)
p_argv -> do
     Ptr CInt -> Ptr (Ptr CString) -> IO ()
getProgArgv Ptr CInt
p_argc Ptr (Ptr CString)
p_argv
     Ptr CString
argv <- Ptr (Ptr CString) -> IO (Ptr CString)
forall a. Storable a => Ptr a -> IO a
peek Ptr (Ptr CString)
p_argv
     Ptr CString -> IO String
unpackProgName Ptr CString
argv

unpackProgName  :: Ptr (Ptr CChar) -> IO String   -- argv[0]
unpackProgName :: Ptr CString -> IO String
unpackProgName Ptr CString
argv = do
  TextEncoding
enc <- IO TextEncoding
argvEncoding
  String
s <- Ptr CString -> Int -> IO CString
forall a. Storable a => Ptr a -> Int -> IO a
peekElemOff Ptr CString
argv Int
0 IO CString -> (CString -> IO String) -> IO String
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= TextEncoding -> CString -> IO String
GHC.peekCString TextEncoding
enc
  String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> String
basename String
s)

basename :: FilePath -> FilePath
basename :: String -> String
basename String
f = String -> String -> String
go String
f String
f
 where
  go :: String -> String -> String
go String
acc [] = String
acc
  go String
acc (Char
x:String
xs)
    | Char -> Bool
isPathSeparator Char
x = String -> String -> String
go String
xs String
xs
    | Bool
otherwise         = String -> String -> String
go String
acc String
xs

  isPathSeparator :: Char -> Bool
  isPathSeparator :: Char -> Bool
isPathSeparator Char
'/'  = Bool
True
#if defined(mingw32_HOST_OS)
  isPathSeparator '\\' = True
#endif
  isPathSeparator Char
_    = Bool
False


-- | Computation 'getEnv' @var@ returns the value
-- of the environment variable @var@. For the inverse, the
-- `System.Environment.setEnv` function can be used.
--
-- This computation may fail with:
--
--  * 'System.IO.Error.isDoesNotExistError' if the environment variable
--    does not exist.

getEnv :: String -> IO String
getEnv :: String -> IO String
getEnv String
name = String -> IO (Maybe String)
lookupEnv String
name IO (Maybe String) -> (Maybe String -> IO String) -> IO String
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= IO String -> (String -> IO String) -> Maybe String -> IO String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe IO String
forall {a}. IO a
handleError String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return
  where
#if defined(mingw32_HOST_OS)
    handleError = do
        err <- c_GetLastError
        if err == eRROR_ENVVAR_NOT_FOUND
            then ioe_missingEnvVar name
            else throwGetLastError "getEnv"

eRROR_ENVVAR_NOT_FOUND :: DWORD
eRROR_ENVVAR_NOT_FOUND = 203

foreign import WINDOWS_CCONV unsafe "windows.h GetLastError"
  c_GetLastError:: IO DWORD

#else
    handleError :: IO a
handleError = String -> IO a
forall a. String -> IO a
ioe_missingEnvVar String
name
#endif

-- | Return the value of the environment variable @var@, or @Nothing@ if
-- there is no such value.
--
-- For POSIX users, this is equivalent to 'System.Posix.Env.getEnv'.
--
-- @since 4.6.0.0
lookupEnv :: String -> IO (Maybe String)
#if defined(mingw32_HOST_OS)
lookupEnv name = withCWString name $ \s -> try_size s 256
  where
    try_size s size = allocaArray (fromIntegral size) $ \p_value -> do
      res <- c_GetEnvironmentVariable s p_value size
      case res of
        0 -> return Nothing
        _ | res > size -> try_size s res -- Rare: size increased between calls to GetEnvironmentVariable
          | otherwise  -> peekCWString p_value >>= return . Just

foreign import WINDOWS_CCONV unsafe "windows.h GetEnvironmentVariableW"
  c_GetEnvironmentVariable :: LPWSTR -> LPWSTR -> DWORD -> IO DWORD
#else
lookupEnv :: String -> IO (Maybe String)
lookupEnv String
name =
    String -> (CString -> IO (Maybe String)) -> IO (Maybe String)
forall a. String -> (CString -> IO a) -> IO a
withCString String
name ((CString -> IO (Maybe String)) -> IO (Maybe String))
-> (CString -> IO (Maybe String)) -> IO (Maybe String)
forall a b. (a -> b) -> a -> b
$ \CString
s -> do
      CString
litstring <- CString -> IO CString
c_getenv CString
s
      if CString
litstring CString -> CString -> Bool
forall a. Eq a => a -> a -> Bool
/= CString
forall a. Ptr a
nullPtr
        then do TextEncoding
enc <- IO TextEncoding
getFileSystemEncoding
                String
result <- TextEncoding -> CString -> IO String
GHC.peekCString TextEncoding
enc CString
litstring
                Maybe String -> IO (Maybe String)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe String -> IO (Maybe String))
-> Maybe String -> IO (Maybe String)
forall a b. (a -> b) -> a -> b
$ String -> Maybe String
forall a. a -> Maybe a
Just String
result
        else Maybe String -> IO (Maybe String)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe String
forall a. Maybe a
Nothing

foreign import ccall unsafe "getenv"
   c_getenv :: CString -> IO (Ptr CChar)
#endif

ioe_missingEnvVar :: String -> IO a
ioe_missingEnvVar :: forall a. String -> IO a
ioe_missingEnvVar String
name = IOError -> IO a
forall a. IOError -> IO a
ioException (Maybe Handle
-> IOErrorType
-> String
-> String
-> Maybe CInt
-> Maybe String
-> IOError
IOError Maybe Handle
forall a. Maybe a
Nothing IOErrorType
NoSuchThing String
"getEnv"
    String
"no environment variable" Maybe CInt
forall a. Maybe a
Nothing (String -> Maybe String
forall a. a -> Maybe a
Just String
name))

-- | @setEnv name value@ sets the specified environment variable to @value@.
--
-- Early versions of this function operated under the mistaken belief that
-- setting an environment variable to the /empty string/ on Windows removes
-- that environment variable from the environment.  For the sake of
-- compatibility, it adopted that behavior on POSIX.  In particular
--
-- @
-- setEnv name \"\"
-- @
--
-- has the same effect as
--
-- @
-- `unsetEnv` name
-- @
--
-- If you'd like to be able to set environment variables to blank strings,
-- use `System.Environment.Blank.setEnv`.
--
-- Throws `Control.Exception.IOException` if @name@ is the empty string or
-- contains an equals sign.
--
-- @since 4.7.0.0
setEnv :: String -> String -> IO ()
setEnv :: String -> String -> IO ()
setEnv String
key_ String
value_
  | String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
key       = IOError -> IO ()
forall e a. Exception e => e -> IO a
throwIO (IOErrorType -> String -> Maybe Handle -> Maybe String -> IOError
mkIOError IOErrorType
InvalidArgument String
"setEnv" Maybe Handle
forall a. Maybe a
Nothing Maybe String
forall a. Maybe a
Nothing)
  | Char
'=' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
key = IOError -> IO ()
forall e a. Exception e => e -> IO a
throwIO (IOErrorType -> String -> Maybe Handle -> Maybe String -> IOError
mkIOError IOErrorType
InvalidArgument String
"setEnv" Maybe Handle
forall a. Maybe a
Nothing Maybe String
forall a. Maybe a
Nothing)
  | String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
value     = String -> IO ()
unsetEnv String
key
  | Bool
otherwise      = String -> String -> IO ()
setEnv_ String
key String
value
  where
    key :: String
key   = (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'\NUL') String
key_
    value :: String
value = (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'\NUL') String
value_

setEnv_ :: String -> String -> IO ()
#if defined(mingw32_HOST_OS)
setEnv_ key value = withCWString key $ \k -> withCWString value $ \v -> do
  success <- c_SetEnvironmentVariable k v
  unless success (throwGetLastError "setEnv")

foreign import WINDOWS_CCONV unsafe "windows.h SetEnvironmentVariableW"
  c_SetEnvironmentVariable :: LPTSTR -> LPTSTR -> IO Bool
#else

-- NOTE: The 'setenv()' function is not available on all systems, hence we use
-- 'putenv()'.  This leaks memory, but so do common implementations of
-- 'setenv()' (AFAIK).
setEnv_ :: String -> String -> IO ()
setEnv_ String
k String
v = String -> IO ()
putEnv (String
k String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"=" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
v)

putEnv :: String -> IO ()
putEnv :: String -> IO ()
putEnv String
keyvalue = do
  CString
s <- IO TextEncoding
getFileSystemEncoding IO TextEncoding -> (TextEncoding -> IO CString) -> IO CString
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (TextEncoding -> String -> IO CString
`GHC.newCString` String
keyvalue)
  -- IMPORTANT: Do not free `s` after calling putenv!
  --
  -- According to SUSv2, the string passed to putenv becomes part of the
  -- environment.
  (CInt -> Bool) -> String -> IO CInt -> IO ()
forall a. (a -> Bool) -> String -> IO a -> IO ()
throwErrnoIf_ (CInt -> CInt -> Bool
forall a. Eq a => a -> a -> Bool
/= CInt
0) String
"putenv" (CString -> IO CInt
c_putenv CString
s)

foreign import ccall unsafe "putenv" c_putenv :: CString -> IO CInt
#endif

-- | @unsetEnv name@ removes the specified environment variable from the
-- environment of the current process.
--
-- Throws `Control.Exception.IOException` if @name@ is the empty string or
-- contains an equals sign.
--
-- @since 4.7.0.0
unsetEnv :: String -> IO ()
#if defined(mingw32_HOST_OS)
unsetEnv key = withCWString key $ \k -> do
  success <- c_SetEnvironmentVariable k nullPtr
  unless success $ do
    -- We consider unsetting an environment variable that does not exist not as
    -- an error, hence we ignore eRROR_ENVVAR_NOT_FOUND.
    err <- c_GetLastError
    unless (err == eRROR_ENVVAR_NOT_FOUND) $ do
      throwGetLastError "unsetEnv"
#else

#if defined(HAVE_UNSETENV)
unsetEnv :: String -> IO ()
unsetEnv String
key = String -> (CString -> IO ()) -> IO ()
forall a. String -> (CString -> IO a) -> IO a
withFilePath String
key ((CInt -> Bool) -> String -> IO CInt -> IO ()
forall a. (a -> Bool) -> String -> IO a -> IO ()
throwErrnoIf_ (CInt -> CInt -> Bool
forall a. Eq a => a -> a -> Bool
/= CInt
0) String
"unsetEnv" (IO CInt -> IO ()) -> (CString -> IO CInt) -> CString -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CString -> IO CInt
c_unsetenv)
foreign import ccall unsafe "__hsbase_unsetenv" c_unsetenv :: CString -> IO CInt
#else
unsetEnv key = setEnv_ key ""
#endif

#endif

{-|
'withArgs' @args act@ - while executing action @act@, have 'getArgs'
return @args@.
-}
withArgs :: [String] -> IO a -> IO a
withArgs :: forall a. [String] -> IO a -> IO a
withArgs [String]
xs IO a
act = do
   String
p <- IO String
System.Environment.getProgName
   [String] -> IO a -> IO a
forall a. [String] -> IO a -> IO a
withArgv (String
pString -> [String] -> [String]
forall a. a -> [a] -> [a]
:[String]
xs) IO a
act

{-|
'withProgName' @name act@ - while executing action @act@,
have 'getProgName' return @name@.
-}
withProgName :: String -> IO a -> IO a
withProgName :: forall a. String -> IO a -> IO a
withProgName String
nm IO a
act = do
   [String]
xs <- IO [String]
System.Environment.getArgs
   [String] -> IO a -> IO a
forall a. [String] -> IO a -> IO a
withArgv (String
nmString -> [String] -> [String]
forall a. a -> [a] -> [a]
:[String]
xs) IO a
act

-- Worker routine which marshals and replaces an argv vector for
-- the duration of an action.

withArgv :: [String] -> IO a -> IO a
withArgv :: forall a. [String] -> IO a -> IO a
withArgv = [String] -> IO a -> IO a
forall a. [String] -> IO a -> IO a
withProgArgv

withProgArgv :: [String] -> IO a -> IO a
withProgArgv :: forall a. [String] -> IO a -> IO a
withProgArgv [String]
new_args IO a
act = do
  String
pName <- IO String
System.Environment.getProgName
  [String]
existing_args <- IO [String]
System.Environment.getArgs
  IO () -> IO () -> IO a -> IO a
forall a b c. IO a -> IO b -> IO c -> IO c
bracket_ ([String] -> IO ()
setProgArgv [String]
new_args)
           ([String] -> IO ()
setProgArgv (String
pNameString -> [String] -> [String]
forall a. a -> [a] -> [a]
:[String]
existing_args))
           IO a
act

setProgArgv :: [String] -> IO ()
setProgArgv :: [String] -> IO ()
setProgArgv [String]
argv = do
  TextEncoding
enc <- IO TextEncoding
argvEncoding
  TextEncoding -> [String] -> (Int -> Ptr CString -> IO ()) -> IO ()
forall a.
TextEncoding -> [String] -> (Int -> Ptr CString -> IO a) -> IO a
GHC.withCStringsLen TextEncoding
enc [String]
argv ((Int -> Ptr CString -> IO ()) -> IO ())
-> (Int -> Ptr CString -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Int
len Ptr CString
css ->
    CInt -> Ptr CString -> IO ()
c_setProgArgv (Int -> CInt
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
len) Ptr CString
css

-- setProgArgv copies the arguments
foreign import ccall unsafe "setProgArgv"
  c_setProgArgv  :: CInt -> Ptr CString -> IO ()

-- |'getEnvironment' retrieves the entire environment as a
-- list of @(key,value)@ pairs.
--
-- If an environment entry does not contain an @\'=\'@ character,
-- the @key@ is the whole entry and the @value@ is the empty string.
getEnvironment :: IO [(String, String)]

#if defined(mingw32_HOST_OS)
getEnvironment = bracket c_GetEnvironmentStrings c_FreeEnvironmentStrings $ \pBlock ->
    if pBlock == nullPtr then return []
     else go pBlock
  where
    go pBlock = do
        -- The block is terminated by a null byte where there
        -- should be an environment variable of the form X=Y
        c <- peek pBlock
        if c == 0 then return []
         else do
          -- Seek the next pair (or terminating null):
          pBlock' <- seekNull pBlock False
          -- We now know the length in bytes, but ignore it when
          -- getting the actual String:
          str <- peekCWString pBlock
          fmap (divvy str :) $ go pBlock'

    -- Returns pointer to the byte *after* the next null
    seekNull pBlock done = do
        let pBlock' = pBlock `plusPtr` sizeOf (undefined :: CWchar)
        if done then return pBlock'
         else do
           c <- peek pBlock'
           seekNull pBlock' (c == (0 :: Word8 ))

foreign import WINDOWS_CCONV unsafe "windows.h GetEnvironmentStringsW"
  c_GetEnvironmentStrings :: IO (Ptr CWchar)

foreign import WINDOWS_CCONV unsafe "windows.h FreeEnvironmentStringsW"
  c_FreeEnvironmentStrings :: Ptr CWchar -> IO Bool
#else
getEnvironment :: IO [(String, String)]
getEnvironment = do
   Ptr CString
pBlock <- IO (Ptr CString)
getEnvBlock
   if Ptr CString
pBlock Ptr CString -> Ptr CString -> Bool
forall a. Eq a => a -> a -> Bool
== Ptr CString
forall a. Ptr a
nullPtr then [(String, String)] -> IO [(String, String)]
forall (m :: * -> *) a. Monad m => a -> m a
return []
    else do
      TextEncoding
enc <- IO TextEncoding
getFileSystemEncoding
      [String]
stuff <- CString -> Ptr CString -> IO [CString]
forall a. (Storable a, Eq a) => a -> Ptr a -> IO [a]
peekArray0 CString
forall a. Ptr a
nullPtr Ptr CString
pBlock IO [CString] -> ([CString] -> IO [String]) -> IO [String]
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (CString -> IO String) -> [CString] -> IO [String]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (TextEncoding -> CString -> IO String
GHC.peekCString TextEncoding
enc)
      [(String, String)] -> IO [(String, String)]
forall (m :: * -> *) a. Monad m => a -> m a
return ((String -> (String, String)) -> [String] -> [(String, String)]
forall a b. (a -> b) -> [a] -> [b]
map String -> (String, String)
divvy [String]
stuff)

foreign import ccall unsafe "__hscore_environ"
  getEnvBlock :: IO (Ptr CString)
#endif

divvy :: String -> (String, String)
divvy :: String -> (String, String)
divvy String
str =
  case (Char -> Bool) -> String -> (String, String)
forall a. (a -> Bool) -> [a] -> ([a], [a])
break (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
==Char
'=') String
str of
    (String
xs,[])        -> (String
xs,[]) -- don't barf (like Posix.getEnvironment)
    (String
name,Char
_:String
value) -> (String
name,String
value)