{-# LANGUAGE CPP, ExistentialQuantification, ScopedTypeVariables #-}
module Util.DynamicLinker ( ForeignFun(..)
, DynamicLib(..)
, tryLoadLib
, tryLoadFn
) where
#ifdef IDRIS_FFI
import System.Directory
#ifdef mingw32_HOST_OS
import qualified Control.Exception as Exception (IOException, catch)
import Foreign.Ptr (FunPtr, castPtrToFunPtr, nullFunPtr, nullPtr)
import System.FilePath.Windows ((</>))
import System.Win32.DLL
import System.Win32.Types
#else
import Control.Exception (IOException, throwIO, try)
import Foreign.Ptr (FunPtr, nullFunPtr, nullPtr)
#ifdef linux_HOST_OS
import Data.Array (bounds, inRange, (!))
import Data.Functor ((<$>))
import Data.Maybe (catMaybes)
#else
import Data.Array (bounds, (!))
#endif
import System.FilePath.Posix ((</>))
import System.Posix.DynamicLinker
import Text.Regex.TDFA
#endif
#ifdef mingw32_HOST_OS
type DL = HMODULE
#endif
hostDynamicLibExt :: String
#if defined(linux_HOST_OS) || defined(freebsd_HOST_OS) \
|| defined(dragonfly_HOST_OS) || defined(openbsd_HOST_OS) \
|| defined(netbsd_HOST_OS)
hostDynamicLibExt = "so"
#elif defined(darwin_HOST_OS)
hostDynamicLibExt = "dylib"
#elif defined(mingw32_HOST_OS)
hostDynamicLibExt = "dll"
#else
hostDynamicLibExt = error $ unwords
[ "Undefined file extension for dynamic libraries"
, "in Idris' Util.DynamicLinker."
]
#endif
data ForeignFun = forall a. Fun { fun_name :: String
, fun_handle :: FunPtr a
}
data DynamicLib = Lib { lib_name :: String
, lib_handle :: DL
}
instance Eq DynamicLib where
(Lib a _) == (Lib b _) = a == b
firstExisting :: [FilePath] -> IO (Maybe FilePath)
firstExisting [] = return Nothing
firstExisting (f:fs) = do exists <- doesFileExist f
if exists
then return (Just f)
else firstExisting fs
libFileName :: [FilePath] -> String -> IO String
libFileName dirs lib = do let names = [lib, lib ++ "." ++ hostDynamicLibExt]
cwd <- getCurrentDirectory
found <- firstExisting $
map ("."</>) names ++ [d </> f | d <- cwd:dirs, f <- names]
return $ maybe (lib ++ "." ++ hostDynamicLibExt) id found
#ifndef mingw32_HOST_OS
tryLoadLib :: [FilePath] -> String -> IO (Maybe DynamicLib)
tryLoadLib dirs lib = do
filename <- libFileName dirs lib
res :: Either IOException DL <- try $
dlopen filename [RTLD_NOW, RTLD_GLOBAL]
mbDL <- case res of
Right handle -> return $ Just handle
#ifdef linux_HOST_OS
Left ex ->
case matchAllText invalidLibRegex (show ex) of
(x:_) -> do
if inRange (bounds x) 1
then do
let realPath = fst $ x ! 1
fileLines <- lines <$> readFile realPath
let matches = catMaybes $ map
(getLastMatch . matchAllText linkerScriptRegex)
fileLines
mapMFirst (\f -> dlopen f [RTLD_NOW, RTLD_GLOBAL]) matches
else return Nothing
[] -> return Nothing
#else
Left ex -> throwIO ex
#endif
case mbDL of
Just handle -> if undl handle == nullPtr
then return Nothing
else return . Just $ Lib lib handle
Nothing -> return Nothing
getLastMatch :: [MatchText String] -> Maybe String
getLastMatch [] = Nothing
getLastMatch (x:_) = case bounds x of
(low, high) -> if low > high
then Nothing
else Just $ fst $ x ! high
mapMFirst :: (a -> IO b) -> [a] -> IO (Maybe b)
mapMFirst f [] = return Nothing
mapMFirst f (a:as) = do res <- try (f a)
case res of
Left (ex :: IOException) -> mapMFirst f as
Right res -> return $ Just res
invalidLibRegex :: Regex
invalidLibRegex = makeRegex "(([^ \t()])+\\.so([^ \t:()])*):([ \t])*(invalid ELF header|file too short)"
linkerScriptRegex :: Regex
linkerScriptRegex = makeRegex "(GROUP|INPUT) *\\( *([^ )]+)"
tryLoadFn :: String -> DynamicLib -> IO (Maybe ForeignFun)
tryLoadFn fn (Lib _ h) = do cFn <- dlsym h fn
if cFn == nullFunPtr
then return Nothing
else return . Just $ Fun fn cFn
#else
tryLoadLib :: [FilePath] -> String -> IO (Maybe DynamicLib)
tryLoadLib dirs lib = do filename <- libFileName dirs lib
handle <- Exception.catch (loadLibrary filename) nullPtrOnException
if handle == nullPtr
then return Nothing
else return . Just $ Lib lib handle
where nullPtrOnException :: Exception.IOException -> IO DL
nullPtrOnException e = return nullPtr
tryLoadFn :: String -> DynamicLib -> IO (Maybe ForeignFun)
tryLoadFn fn (Lib _ h) = do cFn <- getProcAddress h fn
if cFn == nullPtr
then return Nothing
else return . Just $ Fun fn (castPtrToFunPtr cFn)
#endif
#else
data DynamicLib = Lib { lib_name :: String
, lib_handle :: ()
}
deriving Eq
data ForeignFun = forall a. Fun { fun_name :: String
, fun_handle :: ()
}
tryLoadLib :: [FilePath] -> String -> IO (Maybe DynamicLib)
tryLoadLib fps lib = do putStrLn $ "WARNING: Cannot load '" ++ lib ++ "' at compile time because Idris was compiled without libffi support."
return Nothing
tryLoadFn :: String -> DynamicLib -> IO (Maybe ForeignFun)
tryLoadFn fn lib = do putStrLn $ "WARNING: Cannot load '" ++ fn ++ "' at compile time because Idris was compiled without libffi support."
return Nothing
#endif