rtld-0.0.2: System/RTLD.hsc
-------------------------------------------------------------------------------
-- |
-- Module : System.RTLD
-- Copyright : Copyright © 2012-2014 Krzysztof Kardzis
-- License : ISC License (MIT/BSD-style, see LICENSE file for details)
--
-- Maintainer : Krzysztof Kardzis <kkardzis@gmail.com>
-- Stability : experimental
-- Portability : non-portable
--
-- <<https://ga-beacon.appspot.com/UA-53767359-1/hackage/rtld/RTLD>>
-------------------------------------------------------------------------------
{-# LANGUAGE ForeignFunctionInterface #-}
module System.RTLD
( RTLD (..)
, LIBH, dynload, dynfree, dynfunc, dynfail
, SYMTABENTRY (..)
, RTSO (..)
, rtload
, rtfree
) where
import Foreign.C.String
import Foreign.C.Types
import Foreign.Ptr
import Foreign.Storable
import Foreign.Marshal.Array
import Foreign.Marshal.Utils (toBool)
#ifdef WINRTLD
import Foreign.Marshal.Alloc (alloca)
#endif
import Control.Concurrent (MVar, modifyMVar_)
import Control.Exception (bracket_, bracketOnError)
import Control.Monad (when, void, foldM)
#include "RTLD.h"
-------------------------------------------------------------------------------
class RTLD so where
withlib :: so -> IO a -> IO a
loadlib :: so -> IO ()
freelib :: so -> IO ()
withlib so = bracket_ (loadlib so) (freelib so)
-------------------------------------------------------------------------------
type LIBH = Ptr ()
-------------------------------------------------------------------------------
#ifdef WINRTLD
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
dynload :: String -> IO LIBH
dynload xs = withCString xs loadLibrary
dynfunc :: LIBH -> Ptr CChar -> IO (FunPtr ())
dynfunc = getProcAddress
dynfree :: LIBH -> IO Bool
dynfree hm = fmap toBool (freeLibrary hm)
dynfail :: IO String
dynfail = alloca $ \csptr -> do
err <- getLastError
st <- formatMessage dwflags nullPtr err 0 csptr 0 nullPtr
xs <- if (st==0) then return "" else peek csptr >>= peekCString
_ <- peek csptr >>= \cs -> localFree (castPtr cs)
return $ concat ["(", show err, ") ", xs]
dwflags :: DWORD
dwflags = #{const (FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM)}
type FARPROC = FunPtr ()
type HMODULE = Ptr ()
type HLOCAL = Ptr ()
type LPCVOID = Ptr ()
type LPCSTR = Ptr CChar
type LPSTR = Ptr (Ptr CChar) -- actually should be (Ptr CChar)
type DWORD = CULong
type BOOL = CInt
foreign import stdcall "LoadLibraryA"
loadLibrary :: LPCSTR -> IO HMODULE
foreign import stdcall "GetProcAddress"
getProcAddress :: HMODULE -> LPCSTR -> IO FARPROC
foreign import stdcall "FreeLibrary"
freeLibrary :: HMODULE -> IO BOOL
foreign import stdcall "GetLastError"
getLastError :: IO DWORD
foreign import stdcall "FormatMessageA"
formatMessage :: DWORD -> LPCVOID -> DWORD -> DWORD
-> LPSTR -> DWORD -> Ptr () -> IO DWORD
foreign import stdcall "LocalFree"
localFree :: HLOCAL -> IO HLOCAL
-------------------------------------------------------------------------------
#else
#include <dlfcn.h>
dynload :: String -> IO LIBH
dynload xs = withCString xs (\cs -> dlopen cs #{const RTLD_LAZY})
dynfunc :: LIBH -> Ptr CChar -> IO (FunPtr ())
dynfunc = dlsym
dynfree :: LIBH -> IO Bool
dynfree hm = fmap (not . toBool) (dlclose hm)
dynfail :: IO String
dynfail = dlerror >>= peekCString
foreign import ccall "dlopen" dlopen :: Ptr CChar -> CInt -> IO (Ptr ())
foreign import ccall "dlsym" dlsym :: Ptr () -> Ptr CChar -> IO (FunPtr ())
foreign import ccall "dlclose" dlclose :: Ptr () -> IO CInt
foreign import ccall "dlerror" dlerror :: IO (Ptr CChar)
#endif
-------------------------------------------------------------------------------
newtype SYMTABENTRY = RTSYM (CInt,CInt,(Ptr CChar))
instance Storable SYMTABENTRY where
sizeOf _ = #{size SYMTABENTRY}
alignment _ = #{alignof SYMTABENTRY}
poke _ _ = undefined
peek ptr = do
vmin <- #{peek SYMTABENTRY, vmin} ptr
vmax <- #{peek SYMTABENTRY, vmax} ptr
name <- #{peek SYMTABENTRY, name} ptr
return (RTSYM (vmin,vmax,name))
-------------------------------------------------------------------------------
data RTSO a = RTSO
{ rtPKGMVAR :: MVar (Maybe (a, LIBH, Int))
, rtPKGNAME :: String
, rtLIBNAME :: a -> String
, rtSONAMES :: a -> [String]
, rtONLOAD :: a -> IO ()
, rtONFREE :: a -> IO ()
, rtGETAPI :: a -> IO (Maybe a)
, rtSYMTAB :: Ptr SYMTABENTRY
, rtADRTAB :: Ptr (FunPtr ())
, rtTABLEN :: Int
}
-------------------------------------------------------------------------------
rtload :: (Ord a, Enum a, Bounded a) => RTSO a -> a -> IO ()
rtload rtso rqApi =
modifyMVar_ (rtPKGMVAR rtso) $ \state -> case state of
Nothing -> libloadA rtso rqApi >>= \(ldApi,h) -> return (Just (ldApi,h,1))
Just (ldApi,h,x) -> libtest rtso rqApi ldApi >> return (Just (ldApi,h,x+1))
rtfree :: RTSO a -> IO ()
rtfree rtso =
modifyMVar_ (rtPKGMVAR rtso) $ \state -> case state of
Nothing -> return Nothing
Just (ldApi,h,1) -> libfree rtso ldApi h >> return Nothing
Just (ldApi,h,x) -> return (Just (ldApi,h,x-1))
-------------------------------------------------------------------------------
libloadA :: (Ord a, Enum a, Bounded a) => RTSO a -> a -> IO (a, LIBH)
libloadA rtso rqApi =
bracketOnError
(foldM mloadfile Nothing (rtSONAMES rtso rqApi))
(maybe (return ()) (void . dynfree . fst))
(maybe (libloadfailA rtso rqApi) (libloadB rtso rqApi))
mloadfile :: Maybe (LIBH, String) -> String -> IO (Maybe (LIBH, String))
mloadfile mlh@(Just _) _ = return mlh
mloadfile Nothing soname =
let check libh = if (libh==nullPtr) then Nothing else (Just (libh,soname))
in fmap check (dynload soname)
libloadfailA :: RTSO a -> a -> IO (a, LIBH)
libloadfailA rtso rqApi = error $ concat
[ rtPKGNAME rtso, " failed to load ", rtLIBNAME rtso rqApi
, " ", show (rtSONAMES rtso rqApi) ]
-------------------------------------------------------------------------------
libloadB :: (Ord a, Enum a, Bounded a)
=> RTSO a -> a -> (LIBH, String) -> IO (a, LIBH)
libloadB rtso rqApi so@(libh, soname) =
bracketOnError (symload rtso libh) (const $ symfree rtso)
(maybe (libloadfailB rtso rqApi soname) (libloadC rtso rqApi so))
libloadC :: (Ord a) => RTSO a -> a -> (LIBH, String) -> a -> IO (a, LIBH)
libloadC rtso rqApi (libh, soname) smApi =
bracketOnError (rtONLOAD rtso smApi) (\_ -> rtONFREE rtso smApi)
$ \_ -> do
ldApiM <- rtGETAPI rtso smApi
case ldApiM of
Nothing -> libloadfailB rtso rqApi soname
Just ldApi -> if (ldApi>=rqApi && smApi>=rqApi)
then return ((min ldApi smApi), libh)
else libloadfailB rtso rqApi soname
libloadfailB :: RTSO a -> a -> String -> IO (a, LIBH)
libloadfailB rtso rqApi soname = error $ concat
[ rtPKGNAME rtso, " failed to load ", rtLIBNAME rtso rqApi
, " ('", soname, "' found, but version doesn't match)" ]
-------------------------------------------------------------------------------
libtest :: (Ord a) => RTSO a -> a -> a -> IO ()
libtest rtso rqApi ldApi = when (rqApi > ldApi) (libtestfail rtso rqApi ldApi)
libtestfail :: RTSO a -> a -> a -> IO ()
libtestfail rtso rqApi ldApi = error $ concat
[ rtPKGNAME rtso, " version mismatch: "
, rtLIBNAME rtso rqApi, " required, but "
, rtLIBNAME rtso ldApi, " already loaded" ]
-------------------------------------------------------------------------------
libfree :: RTSO a -> a -> LIBH -> IO ()
libfree rtso ldApi libh = do
rtONFREE rtso ldApi
x <- symfree rtso >> dynfree libh
when (not x) (libfreefail rtso ldApi)
libfreefail :: RTSO a -> a -> IO ()
libfreefail rtso ldApi = error $ concat
[ rtPKGNAME rtso, " failed to free ", rtLIBNAME rtso ldApi ]
-------------------------------------------------------------------------------
symload :: (Ord a, Enum a, Bounded a) => RTSO a -> LIBH -> IO (Maybe a)
symload rtso libh = do
(vmins,vmaxs,names) <- fmap vpack $ peekArray (rtTABLEN rtso) (rtSYMTAB rtso)
addrs <- mapM (dynfunc libh) names
pokeArray (rtADRTAB rtso) addrs
return $ case (vtags (zip3 vmins vmaxs addrs)) of
[] -> Nothing
xs -> Just (maximum xs)
symfree :: RTSO a -> IO ()
symfree rtso =
pokeArray (rtADRTAB rtso) (take (rtTABLEN rtso) (repeat nullFunPtr))
vpack :: (Enum a) => [SYMTABENTRY] -> ([a], [a], [Ptr CChar])
vpack xs = unzip3 $ map (\(RTSYM (x,y,z)) -> (foo x, foo y, z)) xs
where foo = toEnum . fromIntegral
vtags :: (Ord a, Enum a, Bounded a) => [(a,a,FunPtr ())] -> [a]
vtags xs = filter (\x -> all (vtest x) xs) [minBound .. maxBound]
vtest :: (Ord a, Enum a, Bounded a) => a -> (a,a,FunPtr ()) -> Bool
vtest vnom (vmin,vmax,addr) = (addr/=nullFunPtr) || (vnom<vmin) || (vnom>vmax)