|
1 | 1 | {-# LANGUAGE TypeApplications #-}
|
| 2 | +{-# LANGUAGE PackageImports #-} |
2 | 3 | -- This template expects CPP definitions for:
|
3 | 4 | --
|
4 | 5 | -- WINDOWS defined? = no | yes | no
|
|
18 | 19 | #define POSIX_DOC
|
19 | 20 | #endif
|
20 | 21 |
|
| 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 | + |
21 | 35 | #ifdef WINDOWS
|
22 | 36 | module System.OsPath.Windows
|
23 | 37 | #elif defined(POSIX)
|
@@ -75,6 +89,10 @@ module System.OsPath
|
75 | 89 |
|
76 | 90 | -- * $PATH methods
|
77 | 91 | , splitSearchPath,
|
| 92 | +#if defined(WINDOWS) || defined(POSIX) |
| 93 | +#else |
| 94 | + getSearchPath, |
| 95 | +#endif |
78 | 96 |
|
79 | 97 | -- * Extension functions
|
80 | 98 | splitExtension,
|
@@ -173,8 +191,27 @@ import System.OsString ( unsafeFromChar, toChar )
|
173 | 191 |
|
174 | 192 | #if defined(mingw32_HOST_OS) || defined(__MINGW32__)
|
175 | 193 | 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) |
176 | 208 | #else
|
177 | 209 | 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 |
178 | 215 | #endif
|
179 | 216 |
|
180 | 217 | import Data.Bifunctor
|
@@ -1467,3 +1504,138 @@ decodeFS (OsString (PosixString x)) = decodeWithBasePosix x
|
1467 | 1504 |
|
1468 | 1505 | #endif
|
1469 | 1506 |
|
| 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