Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement getEnv and getEnvironment #232

Merged
merged 6 commits into from
Aug 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 82 additions & 4 deletions System/Win32/Console.hsc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#if __GLASGOW_HASKELL__ >= 709
{-# LANGUAGE Safe #-}
{-# LANGUAGE Trustworthy #-}
#else
{-# LANGUAGE Trustworthy #-}
#endif
Expand Down Expand Up @@ -56,22 +56,32 @@ module System.Win32.Console (
getConsoleScreenBufferInfo,
getCurrentConsoleScreenBufferInfo,
getConsoleScreenBufferInfoEx,
getCurrentConsoleScreenBufferInfoEx
getCurrentConsoleScreenBufferInfoEx,

-- * Env
getEnv,
getEnvironment
) where

#include <windows.h>
#include "alignment.h"
##include "windows_cconv.h"
#include "wincon_compat.h"

import Data.Char (chr)
import System.Win32.Types
import System.Win32.String
import System.Win32.Console.Internal
import Graphics.Win32.Misc
import Graphics.Win32.GDI.Types (COLORREF)

import Foreign.C.String (withCWString)
import GHC.IO (bracket)
import GHC.IO.Exception (IOException(..), IOErrorType(OtherError))
import Foreign.Ptr (plusPtr)
import Foreign.C.Types (CWchar)
import Foreign.C.String (withCWString, CWString)
import Foreign.Storable (Storable(..))
import Foreign.Marshal.Array (peekArray)
import Foreign.Marshal.Array (peekArray, peekArray0)
import Foreign.Marshal.Alloc (alloca)


Expand Down Expand Up @@ -154,3 +164,71 @@ getCurrentConsoleScreenBufferInfoEx :: IO CONSOLE_SCREEN_BUFFER_INFOEX
getCurrentConsoleScreenBufferInfoEx = do
h <- failIf (== nullHANDLE) "getStdHandle" $ getStdHandle sTD_OUTPUT_HANDLE
getConsoleScreenBufferInfoEx h


-- c_GetEnvironmentVariableW :: LPCWSTR -> LPWSTR -> DWORD -> IO DWORD
getEnv :: String -> IO (Maybe String)
getEnv name =
withCWString name $ \c_name -> withTStringBufferLen maxLength $ \(buf, len) -> do
let c_len = fromIntegral len
c_len' <- c_GetEnvironmentVariableW c_name buf c_len
case c_len' of
0 -> do
err_code <- getLastError
if err_code == eERROR_ENVVAR_NOT_FOUND
then return Nothing
else errorWin "GetEnvironmentVariableW"
_ | c_len' > fromIntegral maxLength ->
-- shouldn't happen, because we provide maxLength
ioError (IOError Nothing OtherError "GetEnvironmentVariableW" ("Unexpected return code: " <> show c_len') Nothing Nothing)
| otherwise -> do
let len' = fromIntegral c_len'
Just <$> peekTStringLen (buf, len')
where
-- according to https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew
-- max characters (wide chars): 32767
-- => bytes = 32767 * 2 = 65534
-- +1 byte for NUL (although not needed I think)
maxLength :: Int
maxLength = 65535


getEnvironment :: IO [(String, String)]
getEnvironment = bracket c_GetEnvironmentStringsW c_FreeEnvironmentStrings $ \lpwstr -> do
strs <- builder lpwstr
return (divvy <$> strs)
where
divvy :: String -> (String, String)
divvy str =
case break (=='=') str of
(xs,[]) -> (xs,[]) -- don't barf (like Posix.getEnvironment)
(name,_:value) -> (name,value)

builder :: LPWSTR -> IO [String]
builder ptr = go 0
where
go :: Int -> IO [String]
go off = do
(str, l) <- peekCWStringOff ptr off
if l == 0
then pure []
else (str:) <$> go (((l + 1) * 2) + off)


peekCWStringOff :: CWString -> Int -> IO (String, Int)
peekCWStringOff cp off = do
cs <- peekArray0 wNUL (cp `plusPtr` off)
return (cWcharsToChars cs, length cs)

wNUL :: CWchar
wNUL = 0

cWcharsToChars :: [CWchar] -> [Char]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

guess we could use decodeMultiByte here. but this works too as it's always UTF-16.

cWcharsToChars = map chr . fromUTF16 . map fromIntegral
where
fromUTF16 (c1:c2:wcs)
| 0xd800 <= c1 && c1 <= 0xdbff && 0xdc00 <= c2 && c2 <= 0xdfff =
((c1 - 0xd800)*0x400 + (c2 - 0xdc00) + 0x10000) : fromUTF16 wcs
fromUTF16 (c:wcs) = c : fromUTF16 wcs
fromUTF16 [] = []

9 changes: 9 additions & 0 deletions System/Win32/Console/Internal.hsc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ foreign import WINDOWS_CCONV unsafe "Shellapi.h CommandLineToArgvW"
foreign import WINDOWS_CCONV unsafe "processenv.h GetCommandLineW"
getCommandLineW :: IO LPWSTR

foreign import WINDOWS_CCONV unsafe "processenv.h GetEnvironmentVariableW"
c_GetEnvironmentVariableW :: LPCWSTR -> LPWSTR -> DWORD -> IO DWORD

foreign import WINDOWS_CCONV unsafe "processenv.h GetEnvironmentStringsW"
c_GetEnvironmentStringsW :: IO LPWSTR

foreign import WINDOWS_CCONV unsafe "processenv.h FreeEnvironmentStringsW"
c_FreeEnvironmentStrings :: LPWSTR -> IO Bool

data CONSOLE_SCREEN_BUFFER_INFO = CONSOLE_SCREEN_BUFFER_INFO
{ dwSize :: COORD
, dwCursorPosition :: COORD
Expand Down
2 changes: 2 additions & 0 deletions System/Win32/Types.hsc
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,8 @@ eRROR_MOD_NOT_FOUND = #const ERROR_MOD_NOT_FOUND
eRROR_PROC_NOT_FOUND :: ErrCode
eRROR_PROC_NOT_FOUND = #const ERROR_PROC_NOT_FOUND

eERROR_ENVVAR_NOT_FOUND :: ErrCode
eERROR_ENVVAR_NOT_FOUND = #const ERROR_ENVVAR_NOT_FOUND

errorWin :: String -> IO a
errorWin fn_name = do
Expand Down
95 changes: 92 additions & 3 deletions System/Win32/WindowsString/Console.hsc
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{-# LANGUAGE PackageImports #-}

-----------------------------------------------------------------------------
-- |
-- Module : System.Win32.WindowsString.Console
Expand Down Expand Up @@ -51,7 +53,11 @@ module System.Win32.WindowsString.Console (
getConsoleScreenBufferInfo,
getCurrentConsoleScreenBufferInfo,
getConsoleScreenBufferInfoEx,
getCurrentConsoleScreenBufferInfoEx
getCurrentConsoleScreenBufferInfoEx,

-- * Env
getEnv,
getEnvironment
) where

#include <windows.h>
Expand All @@ -60,13 +66,34 @@ module System.Win32.WindowsString.Console (
#include "wincon_compat.h"

import System.Win32.WindowsString.Types
import System.Win32.WindowsString.String (withTStringBufferLen)
import System.Win32.Console.Internal
import System.Win32.Console hiding (getArgs, commandLineToArgv)
import System.Win32.Console hiding (getArgs, commandLineToArgv, getEnv, getEnvironment)
import System.OsString.Windows
import System.OsString.Internal.Types

import Foreign.C.Types (CWchar)
import Foreign.C.String (CWString)
import Foreign.Ptr (plusPtr)
import Foreign.Storable (Storable(..))
import Foreign.Marshal.Array (peekArray)
import Foreign.Marshal.Array (peekArray, peekArray0)
import Foreign.Marshal.Alloc (alloca)
import GHC.IO (bracket)
import GHC.IO.Exception (IOException(..), IOErrorType(OtherError))

import Prelude hiding (break, length, tail)
import qualified Prelude as P

#if !MIN_VERSION_filepath(1,5,0)
import Data.Coerce
import qualified "filepath" System.OsPath.Data.ByteString.Short.Word16 as BC

tail :: WindowsString -> WindowsString
tail = coerce BC.tail

break :: (WindowsChar -> Bool) -> WindowsString -> (WindowsString, WindowsString)
break = coerce BC.break
#endif


-- | This function can be used to parse command line arguments and return
Expand All @@ -89,3 +116,65 @@ getArgs :: IO [WindowsString]
getArgs = do
getCommandLineW >>= peekTString >>= commandLineToArgv


-- c_GetEnvironmentVariableW :: LPCWSTR -> LPWSTR -> DWORD -> IO DWORD
getEnv :: WindowsString -> IO (Maybe WindowsString)
getEnv name =
withTString name $ \c_name -> withTStringBufferLen maxLength $ \(buf, len) -> do
let c_len = fromIntegral len
c_len' <- c_GetEnvironmentVariableW c_name buf c_len
case c_len' of
0 -> do
err_code <- getLastError
if err_code == eERROR_ENVVAR_NOT_FOUND
then return Nothing
else errorWin "GetEnvironmentVariableW"
_ | c_len' > fromIntegral maxLength ->
-- shouldn't happen, because we provide maxLength
ioError (IOError Nothing OtherError "GetEnvironmentVariableW" ("Unexpected return code: " <> show c_len') Nothing Nothing)
| otherwise -> do
let len' = fromIntegral c_len'
Just <$> peekTStringLen (buf, len')
where
-- according to https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew
-- max characters (wide chars): 32767
-- => bytes = 32767 * 2 = 65534
-- +1 byte for NUL (although not needed I think)
maxLength :: Int
maxLength = 65535


getEnvironment :: IO [(WindowsString, WindowsString)]
getEnvironment = bracket c_GetEnvironmentStringsW c_FreeEnvironmentStrings $ \lpwstr -> do
strs <- builder lpwstr
return (divvy <$> strs)
where
divvy :: WindowsString -> (WindowsString, WindowsString)
divvy str =
case break (== unsafeFromChar '=') str of
(xs,ys)
| ys == mempty -> (xs,ys) -- don't barf (like Posix.getEnvironment)
(name, ys) -> let value = tail ys in (name,value)

builder :: LPWSTR -> IO [WindowsString]
builder ptr = go 0
where
go :: Int -> IO [WindowsString]
go off = do
(str, l) <- peekCWStringOff ptr off
if l == 0
then pure []
else (str:) <$> go (((l + 1) * 2) + off)


peekCWStringOff :: CWString -> Int -> IO (WindowsString, Int)
peekCWStringOff cp off = do
cs <- peekArray0 wNUL (cp `plusPtr` off)
return (cWcharsToChars cs, P.length cs)

wNUL :: CWchar
wNUL = 0

cWcharsToChars :: [CWchar] -> WindowsString
cWcharsToChars = pack . fmap (WindowsChar . fromIntegral)

2 changes: 1 addition & 1 deletion System/Win32/WindowsString/String.hs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import System.Win32.String hiding
)
import System.Win32.WindowsString.Types
import System.OsString.Internal.Types
#if MIN_VERSION_filepath(1, 5, 0)
#if MIN_VERSION_filepath(1,5,0)
import qualified "os-string" System.OsString.Data.ByteString.Short as SBS
#else
import qualified "filepath" System.OsPath.Data.ByteString.Short as SBS
Expand Down
2 changes: 1 addition & 1 deletion System/Win32/WindowsString/Types.hsc
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import qualified System.OsPath.Windows as WS
import System.OsPath.Windows (WindowsPath)
import System.OsString.Windows (decodeWith, encodeWith)
import System.OsString.Internal.Types
#if MIN_VERSION_filepath(1, 5, 0)
#if MIN_VERSION_filepath(1,5,0)
import "os-string" System.OsString.Encoding.Internal (decodeWithBaseWindows)
import qualified "os-string" System.OsString.Data.ByteString.Short.Word16 as SBS
import "os-string" System.OsString.Data.ByteString.Short.Word16 (
Expand Down