From 2f4addf9e008d60f1564360c2eed77bb51ec2684 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Thu, 16 May 2024 01:06:30 +0800 Subject: [PATCH 1/6] Implement getEnv and getEnvironment --- System/Win32/Console.hsc | 79 +++++++++++++++++++++++++++++-- System/Win32/Console/Internal.hsc | 9 ++++ System/Win32/Types.hsc | 2 + 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/System/Win32/Console.hsc b/System/Win32/Console.hsc index 4bb884f..e09e185 100644 --- a/System/Win32/Console.hsc +++ b/System/Win32/Console.hsc @@ -1,5 +1,5 @@ #if __GLASGOW_HASKELL__ >= 709 -{-# LANGUAGE Safe #-} +{-# LANGUAGE Trustworthy #-} #else {-# LANGUAGE Trustworthy #-} #endif @@ -56,7 +56,11 @@ module System.Win32.Console ( getConsoleScreenBufferInfo, getCurrentConsoleScreenBufferInfo, getConsoleScreenBufferInfoEx, - getCurrentConsoleScreenBufferInfoEx + getCurrentConsoleScreenBufferInfoEx, + + -- * Env + getEnv, + getEnvironment ) where #include @@ -64,14 +68,19 @@ module System.Win32.Console ( ##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 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) @@ -154,3 +163,65 @@ 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 + if c_len' == 0 + then do + err_code <- getLastError + if err_code == eERROR_ENVVAR_NOT_FOUND + then return Nothing + else errorWin "GetEnvironmentVariableW" + else 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 + maxLength :: Int + maxLength = 32767 + + +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] +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 [] = [] + diff --git a/System/Win32/Console/Internal.hsc b/System/Win32/Console/Internal.hsc index 89f4ccb..8299905 100644 --- a/System/Win32/Console/Internal.hsc +++ b/System/Win32/Console/Internal.hsc @@ -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 diff --git a/System/Win32/Types.hsc b/System/Win32/Types.hsc index 16dd565..01dbcc3 100755 --- a/System/Win32/Types.hsc +++ b/System/Win32/Types.hsc @@ -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 From d905155777a262384c46f78f55d85c76ee07fe09 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Fri, 17 May 2024 19:57:32 +0800 Subject: [PATCH 2/6] Be more mindful about return value of c_GetEnvironmentVariableW --- System/Win32/Console.hsc | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/System/Win32/Console.hsc b/System/Win32/Console.hsc index e09e185..bee0dc4 100644 --- a/System/Win32/Console.hsc +++ b/System/Win32/Console.hsc @@ -76,6 +76,7 @@ import Graphics.Win32.Misc import Graphics.Win32.GDI.Types (COLORREF) import GHC.IO (bracket) +import GHC.IO.Exception (IOException(..), IOErrorType(OtherError), ioError) import Foreign.Ptr (plusPtr) import Foreign.C.Types (CWchar) import Foreign.C.String (withCWString, CWString) @@ -171,15 +172,18 @@ 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 - if c_len' == 0 - then do - err_code <- getLastError - if err_code == eERROR_ENVVAR_NOT_FOUND - then return Nothing - else errorWin "GetEnvironmentVariableW" - else do - let len' = fromIntegral c_len' - Just <$> peekTStringLen (buf, 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 maxLength :: Int From 9789c0335fb546b39b2bb065c36335e3f39b3b7d Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Sat, 18 May 2024 12:37:35 +0800 Subject: [PATCH 3/6] Fix maxLength --- System/Win32/Console.hsc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/System/Win32/Console.hsc b/System/Win32/Console.hsc index bee0dc4..cb898bc 100644 --- a/System/Win32/Console.hsc +++ b/System/Win32/Console.hsc @@ -186,8 +186,11 @@ getEnv name = 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 = 32767 + maxLength = 65535 getEnvironment :: IO [(String, String)] From ac2bba4f7b09b06d168cfe10271ffbeafe722917 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Mon, 22 Jul 2024 22:58:46 +0800 Subject: [PATCH 4/6] Add WindowsString variant for getEnv etc. --- System/Win32/WindowsString/Console.hsc | 84 +++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/System/Win32/WindowsString/Console.hsc b/System/Win32/WindowsString/Console.hsc index 17e8185..e68c9fe 100644 --- a/System/Win32/WindowsString/Console.hsc +++ b/System/Win32/WindowsString/Console.hsc @@ -1,3 +1,6 @@ +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE ViewPatterns #-} + ----------------------------------------------------------------------------- -- | -- Module : System.Win32.WindowsString.Console @@ -51,7 +54,11 @@ module System.Win32.WindowsString.Console ( getConsoleScreenBufferInfo, getCurrentConsoleScreenBufferInfo, getConsoleScreenBufferInfoEx, - getCurrentConsoleScreenBufferInfoEx + getCurrentConsoleScreenBufferInfoEx, + + -- * Env + getEnv, + getEnvironment ) where #include @@ -60,13 +67,23 @@ 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 -- | This function can be used to parse command line arguments and return @@ -89,3 +106,64 @@ 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,[pstr||]) -> (xs,[pstr||]) -- 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) + From 7be3cdf01214bc8c78af7fcaf03924a0015ddbad Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Mon, 22 Jul 2024 22:59:04 +0800 Subject: [PATCH 5/6] Clean up import --- System/Win32/Console.hsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/System/Win32/Console.hsc b/System/Win32/Console.hsc index cb898bc..64d692a 100644 --- a/System/Win32/Console.hsc +++ b/System/Win32/Console.hsc @@ -76,7 +76,7 @@ import Graphics.Win32.Misc import Graphics.Win32.GDI.Types (COLORREF) import GHC.IO (bracket) -import GHC.IO.Exception (IOException(..), IOErrorType(OtherError), ioError) +import GHC.IO.Exception (IOException(..), IOErrorType(OtherError)) import Foreign.Ptr (plusPtr) import Foreign.C.Types (CWchar) import Foreign.C.String (withCWString, CWString) From 15f4e2d4d4e3c3dca7e0b781f43a8b4bf7c3be95 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 23 Jul 2024 15:59:47 +0800 Subject: [PATCH 6/6] Fix build with -f-os-string --- System/Win32/WindowsString/Console.hsc | 17 ++++++++++++++--- System/Win32/WindowsString/String.hs | 2 +- System/Win32/WindowsString/Types.hsc | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/System/Win32/WindowsString/Console.hsc b/System/Win32/WindowsString/Console.hsc index e68c9fe..e5b884f 100644 --- a/System/Win32/WindowsString/Console.hsc +++ b/System/Win32/WindowsString/Console.hsc @@ -1,5 +1,4 @@ -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE PackageImports #-} ----------------------------------------------------------------------------- -- | @@ -85,6 +84,17 @@ 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 -- the split up arguments as elements in a list. @@ -142,7 +152,8 @@ getEnvironment = bracket c_GetEnvironmentStringsW c_FreeEnvironmentStrings $ \lp divvy :: WindowsString -> (WindowsString, WindowsString) divvy str = case break (== unsafeFromChar '=') str of - (xs,[pstr||]) -> (xs,[pstr||]) -- don't barf (like Posix.getEnvironment) + (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] diff --git a/System/Win32/WindowsString/String.hs b/System/Win32/WindowsString/String.hs index ab618c3..34d2cc0 100644 --- a/System/Win32/WindowsString/String.hs +++ b/System/Win32/WindowsString/String.hs @@ -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 diff --git a/System/Win32/WindowsString/Types.hsc b/System/Win32/WindowsString/Types.hsc index 26c8054..20b0582 100644 --- a/System/Win32/WindowsString/Types.hsc +++ b/System/Win32/WindowsString/Types.hsc @@ -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 (