[Haskell-beginners] FFI. unIO safety considerations

Folsk Pratima folsk0pratima at cock.li
Fri Apr 12 15:27:57 UTC 2024


Also see
[Haskell-beginners] FFI to POSIX libc; strerror (errnum);  unfreed memory

Exactly how unsafe this code is? What do I do about it? It is so very
stupid to have a pure function strerror_r in the IO monad!

StrError.hs


import Control.Exception
import Control.Monad.ST
import Control.Monad.ST.Unsafe
import Data.Word
import Foreign.C.String
import Foreign.C.String
import Foreign.C.Types
import Foreign.Marshal
import Foreign.Ptr
import Foreign.Storable

foreign import ccall unsafe "strerror_r_portalbe.c strerror_r_portable" c_strerror_r
    :: CInt -> Ptr CChar -> CSize -> IO CInt

memSet :: Ptr a -> Word8 -> Word32 -> IO (Ptr a)
memSet ptr _ 0 = return ptr
memSet ptr byte size = do
    let voidptr = castPtr ptr :: Ptr Word8
        acts =
            map
                (\i -> pokeByteOff voidptr i byte)
                [0 .. fromIntegral (size - 1)]
    mapM_ id acts
    return ptr

strError :: Int -> String
strError errnum = runST $ unsafeIOToST ioString
  where
    buflen = 512 :: CSize
    ioString =
        allocaBytes (fromIntegral buflen) $ \ptr -> do
            zeroptr <- memSet ptr 0 (fromIntegral buflen)
            code <- c_strerror_r (fromIntegral errnum) ptr buflen
            -- heuristic!
            case code of
                22 -> return $ "Unknown error " ++ show errnum
                -- this is very dangerous, as far as I understand, and nobody in
                -- his right might should not do it if he later intends to
                -- unsafely escape IO
                34 ->
                    throwIO $
                    userError $
                    "strError: " ++
                    show code ++
                    ": ERANGE: " ++
                    "Numerical result out of range: " ++
                    "this is internal an error, which means not enough space " ++
                    "was allocated to store the error. You can not recover"
                _ -> peekCAString ptr

main = mapM_ (putStrLn) $ map strError [1 .. 1000]




strerror_portable.c


#define _POSIX_C_SOURCE 200112L
#include <string.h>

int
strerror_r_portable (int e, char *c, size_t s)
{
    return strerror_r (e, c, s);
}




The C code is needed because GHC uses _GNU_SOURCE, which I personally do not
want to use. Besides, I do not know how to predict whether GHC will define
_GNU_SOURCE or not, so this also feels more reliable.

To compile, do
$ mkdir lib
$ gcc -c -o lib/portable.o strerror_portable.c
$ ar -csr lib/portable.a lib/portable.o
$ ghc StrError.hs lib/portable.a

No leaks, of course.


More information about the Beginners mailing list