Skip to content

Commit f88c411

Browse files
committed
Implement getSearchPath
1 parent a31bf53 commit f88c411

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed

System/OsPath/Common.hs

+172
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{-# LANGUAGE TypeApplications #-}
2+
{-# LANGUAGE PackageImports #-}
23
-- This template expects CPP definitions for:
34
--
45
-- WINDOWS defined? = no | yes | no
@@ -18,6 +19,19 @@
1819
#define POSIX_DOC
1920
#endif
2021

22+
#ifdef mingw32_HOST_OS
23+
#ifndef __WINDOWS_CCONV_H
24+
#define __WINDOWS_CCONV_H
25+
#if defined(i386_HOST_ARCH)
26+
# define WINDOWS_CCONV stdcall
27+
#elif defined(x86_64_HOST_ARCH)
28+
# define WINDOWS_CCONV ccall
29+
#else
30+
# error Unknown mingw32 arch
31+
#endif
32+
#endif
33+
#endif
34+
2135
#ifdef WINDOWS
2236
module System.OsPath.Windows
2337
#elif defined(POSIX)
@@ -75,6 +89,10 @@ module System.OsPath
7589

7690
-- * $PATH methods
7791
, splitSearchPath,
92+
#if defined(WINDOWS) || defined(POSIX)
93+
#else
94+
getSearchPath,
95+
#endif
7896

7997
-- * Extension functions
8098
splitExtension,
@@ -173,8 +191,27 @@ import System.OsString ( unsafeFromChar, toChar )
173191

174192
#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
175193
import qualified System.OsPath.Windows as C
194+
import Control.Exception (throwIO, try)
195+
import Data.Char (isSpace)
196+
import Data.Bifunctor ( first )
197+
import Data.Word (Word32, Word8)
198+
import Foreign.C.Error (errnoToIOError, Errno(..))
199+
import Foreign.C.String (withCWString, peekCWString)
200+
import Foreign.C.Types (CWchar, CInt(..))
201+
import Foreign.Ptr (nullPtr, Ptr)
202+
import GHC.IO.Exception
203+
import GHC.Ptr (castPtr)
204+
import Numeric (showHex)
205+
import System.IO.Error (ioeSetErrorString)
206+
import qualified "os-string" System.OsString.Data.ByteString.Short as B
207+
import "os-string" System.OsString.Data.ByteString.Short.Word16 (useAsCWString, useAsCWStringLen, packCWStringLen)
176208
#else
177209
import qualified System.OsPath.Posix as C
210+
import GHC.IO.Exception
211+
import Control.Exception (try)
212+
import Foreign
213+
import Foreign.C
214+
import qualified "os-string" System.OsString.Data.ByteString.Short as B
178215
#endif
179216

180217
import Data.Bifunctor
@@ -1467,3 +1504,138 @@ decodeFS (OsString (PosixString x)) = decodeWithBasePosix x
14671504

14681505
#endif
14691506

1507+
#ifdef WINDOWS
1508+
#elif defined(POSIX)
1509+
#else
1510+
getSearchPath :: IO [OsPath]
1511+
#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
1512+
getSearchPath = do
1513+
path <- getEnv name >>= maybe handleError return
1514+
pure $ splitSearchPath (OsString path)
1515+
where
1516+
name = C.unsafeEncodeUtf "PATH"
1517+
handleError = do
1518+
err <- getLastError
1519+
if err == eERROR_ENVVAR_NOT_FOUND
1520+
then ioe_missingEnvVar name
1521+
else failWith "getSearchPath" err
1522+
1523+
eERROR_ENVVAR_NOT_FOUND :: DWORD
1524+
eERROR_ENVVAR_NOT_FOUND = 203
1525+
1526+
foreign import ccall unsafe "windows.h GetLastError"
1527+
c_GetLastError:: IO DWORD
1528+
1529+
ioe_missingEnvVar :: C.WindowsString -> IO a
1530+
ioe_missingEnvVar name = do
1531+
name' <- either (const (fmap C.toChar . C.unpack $ name)) id <$> try @IOException (C.decodeFS name)
1532+
ioException (IOError Nothing NoSuchThing "getSearchPath"
1533+
"no environment variable" Nothing (Just name'))
1534+
1535+
failWith :: String -> ErrCode -> IO a
1536+
failWith fn_name err_code = do
1537+
c_msg <- getErrorMessage err_code
1538+
msg <- if c_msg == nullPtr
1539+
then return $ "Error 0x" ++ Numeric.showHex err_code ""
1540+
else do msg <- peekCWString c_msg
1541+
-- We ignore failure of freeing c_msg, given we're already failing
1542+
_ <- localFree c_msg
1543+
return msg
1544+
-- turn GetLastError() into errno, which errnoToIOError knows how to convert
1545+
-- to an IOException we can throw.
1546+
errno <- c_maperrno_func err_code
1547+
let msg' = reverse $ dropWhile isSpace $ reverse msg -- drop trailing \n
1548+
ioerror = errnoToIOError fn_name errno Nothing Nothing
1549+
`ioeSetErrorString` msg'
1550+
throwIO ioerror
1551+
1552+
errorWin :: String -> IO a
1553+
errorWin fn_name = do
1554+
err_code <- getLastError
1555+
failWith fn_name err_code
1556+
1557+
foreign import WINDOWS_CCONV unsafe "windows.h LocalFree"
1558+
localFree :: Ptr a -> IO (Ptr a)
1559+
1560+
foreign import ccall unsafe "errors.h"
1561+
getErrorMessage :: DWORD -> IO LPWSTR
1562+
1563+
foreign import ccall unsafe "maperrno_func"
1564+
c_maperrno_func :: ErrCode -> IO Errno
1565+
1566+
getEnv :: C.WindowsString -> IO (Maybe C.WindowsString)
1567+
getEnv name =
1568+
withTString name $ \c_name -> withTStringBufferLen maxLength $ \(buf, len) -> do
1569+
let c_len = fromIntegral len
1570+
c_len' <- c_GetEnvironmentVariableW c_name buf c_len
1571+
case c_len' of
1572+
0 -> do
1573+
err_code <- getLastError
1574+
if err_code == eERROR_ENVVAR_NOT_FOUND
1575+
then return Nothing
1576+
else errorWin "GetEnvironmentVariableW"
1577+
_ | c_len' > fromIntegral maxLength ->
1578+
ioError (IOError Nothing OtherError "GetEnvironmentVariableW" ("Unexpected return code: " <> show c_len') Nothing Nothing)
1579+
| otherwise -> do
1580+
let len' = fromIntegral c_len'
1581+
Just <$> peekTStringLen (buf, len')
1582+
where
1583+
maxLength :: Int
1584+
maxLength = 65535
1585+
1586+
foreign import WINDOWS_CCONV unsafe "windows.h GetLastError"
1587+
getLastError :: IO ErrCode
1588+
1589+
foreign import WINDOWS_CCONV unsafe "processenv.h GetEnvironmentVariableW"
1590+
c_GetEnvironmentVariableW :: LPCWSTR -> LPWSTR -> DWORD -> IO DWORD
1591+
1592+
withTStringBufferLen :: Int -> ((LPTSTR, Int) -> IO a) -> IO a
1593+
withTStringBufferLen maxLength
1594+
= let dummyBuffer = WindowsString $ B.pack $ replicate (if even maxLength then maxLength else maxLength + 1) _nul
1595+
in withTStringLen dummyBuffer
1596+
1597+
_nul :: Word8
1598+
_nul = 0x00
1599+
1600+
withTString :: C.WindowsString -> (LPTSTR -> IO a) -> IO a
1601+
withTString (WindowsString str) f = useAsCWString str (\ptr -> f (castPtr ptr))
1602+
1603+
withTStringLen :: C.WindowsString -> ((LPTSTR, Int) -> IO a) -> IO a
1604+
withTStringLen (WindowsString str) f = useAsCWStringLen str (\(ptr, len) -> f (castPtr ptr, len))
1605+
1606+
peekTStringLen :: (LPCTSTR, Int) -> IO C.WindowsString
1607+
peekTStringLen = fmap WindowsString . packCWStringLen . first castPtr
1608+
1609+
type DWORD = Word32
1610+
type ErrCode = DWORD
1611+
type LPWSTR = Ptr CWchar
1612+
type LPCWSTR = LPWSTR
1613+
type LPTSTR = Ptr TCHAR
1614+
type LPCTSTR = LPTSTR
1615+
type TCHAR = CWchar
1616+
1617+
#else
1618+
getSearchPath = do
1619+
path <- getEnv name >>= maybe (ioe_missingEnvVar name) return
1620+
pure $ splitSearchPath (OsString path)
1621+
where
1622+
name = C.unsafeEncodeUtf "PATH"
1623+
1624+
ioe_missingEnvVar :: C.PosixPath -> IO a
1625+
ioe_missingEnvVar name = do
1626+
name' <- either (const (fmap C.toChar . C.unpack $ name)) id <$> try @IOException (C.decodeFS name)
1627+
ioException (IOError Nothing NoSuchThing "getSearchPath"
1628+
"no environment variable" Nothing (Just name'))
1629+
1630+
getEnv :: PosixString -> IO (Maybe PosixString)
1631+
getEnv (PS name) = do
1632+
litstring <- B.useAsCString name c_getenv
1633+
if litstring /= nullPtr
1634+
then (Just . PS) <$> B.packCString litstring
1635+
else return Nothing
1636+
1637+
foreign import ccall unsafe "getenv"
1638+
c_getenv :: CString -> IO CString
1639+
#endif
1640+
1641+
#endif

0 commit comments

Comments
 (0)