packages feed

unix-simple-0.1.0.0: src/Unix.hs

{-# OPTIONS_GHC -Wno-missing-signatures #-}
module Unix
    ( fsync, fsyncExn
    , fdatasync, fdatasyncExn
    , ftruncate, ftruncateExn
    , mkdir, mkdirExn
    , preadBuf, preadBufExn
    , pread, preadExn
    , pwriteBuf, pwriteBufExn
    , pwrite, pwriteExn
    , pwriteFull, pwriteFullExn
    , readBuf, readBufExn
    , writeBuf, writeBufExn
    , read, readExn
    , remove, removeExn
    , rmdir, rmdirExn
    , write, writeExn
    , writeFull, writeFullExn

    , OpenFlag(..)
    , open, openExn
    , openat, openatExn
    , o_APPEND
    , o_CLOEXEC
    , o_CREAT
    , o_DIRECTORY
    , o_EXCL
    , o_NOFOLLOW
    , o_NONBLOCK
    , o_NDELAY
    , o_TRUNC
    , o_RDONLY
    , o_WRONLY
    , o_RDWR

    , close, closeExn

    -- * Re-exported for convenience
    , CString
    ) where

import CString
import Foreign.C.Error
import Foreign.ForeignPtr
import Unix.C
import Unix.C.Errors
import Unix.Errors
import Zhp

import qualified Data.ByteString          as BS
import qualified Data.ByteString.Internal as BS

type EIO a = IO (Either Errno a)

fsync :: Fd -> EIO ()
fsync fd = retryEINTR $ orErrno $ void $ c_fsync fd

fsyncExn :: Fd -> IO ()
fsyncExn fd = throwIfErrno $ fsync fd

fdatasync :: Fd -> EIO ()
fdatasync fd = retryEINTR $ orErrno $ void $ c_fdatasync fd

fdatasyncExn :: Fd -> IO ()
fdatasyncExn fd = throwIfErrno $ fdatasync fd

ftruncate :: Fd -> COff -> EIO ()
ftruncate fd off = retryEINTR $ orErrno $ void $ c_ftruncate fd off

ftruncateExn :: Fd -> COff -> IO ()
ftruncateExn fd off =
    throwIfErrno $ ftruncate fd off

preadBuf :: Fd -> Ptr Word8 -> CSize -> COff -> EIO CSsize
preadBuf fd ptr sz off =
    retryEINTR $ orErrno $ c_pread fd ptr sz off

preadBufExn :: Fd -> Ptr Word8 -> CSize -> COff -> IO CSsize
preadBufExn fd ptr sz off =
    throwIfErrno $ preadBuf fd ptr sz off

pread :: Fd -> CSize -> COff -> EIO BS.ByteString
pread fd sz off = do
    fptr <- mallocForeignPtrBytes (fromIntegral sz)
    r <- withForeignPtr fptr $ \ptr -> preadBuf fd ptr sz off
    pure $! case r of
        Left e  -> Left e
        Right v -> Right (BS.fromForeignPtr fptr 0 (fromIntegral v))

preadExn :: Fd -> CSize -> COff -> IO BS.ByteString
preadExn fd sz off = throwIfErrno $ pread fd sz off

pwriteBuf :: Fd -> Ptr Word8 -> CSize -> COff -> EIO CSsize
pwriteBuf fd ptr sz off =
    retryEINTR $ orErrno $ c_pwrite fd ptr sz off

pwriteBufExn :: Fd -> Ptr Word8 -> CSize -> COff -> IO CSsize
pwriteBufExn fd ptr sz off =
    throwIfErrno $ pwriteBuf fd ptr sz off

pwrite :: Fd -> BS.ByteString -> COff -> EIO CSsize
pwrite fd bs off =
    let (fptr, foff, len) = BS.toForeignPtr bs in
    withForeignPtr fptr $ \ptr ->
        pwriteBuf fd (plusPtr ptr foff) (fromIntegral len) off

pwriteExn :: Fd -> BS.ByteString -> COff -> IO CSsize
pwriteExn fd bs off = throwIfErrno $ pwrite fd bs off

pwriteFull :: Fd -> BS.ByteString -> COff -> EIO ()
pwriteFull fd bs off = do
    ret <- pwrite fd bs off
    case ret of
        Left e -> pure $ Left e
        Right v
            | fromIntegral v == BS.length bs ->
                pure $ Right ()
            | otherwise ->
                pwriteFull fd
                    (BS.drop (fromIntegral v) bs)
                    (off + fromIntegral v)

pwriteFullExn :: Fd -> BS.ByteString -> COff -> IO ()
pwriteFullExn fd bs off = throwIfErrno $ pwriteFull fd bs off

readBuf :: Fd -> Ptr Word8 -> CSize -> EIO CSsize
readBuf fd ptr sz =
    retryEINTR $ orErrno $ c_read fd ptr sz

readBufExn :: Fd -> Ptr Word8 -> CSize -> IO CSsize
readBufExn fd ptr sz =
    throwIfErrno $ readBuf fd ptr sz

read :: Fd -> Int -> EIO BS.ByteString
read fd sz = do
    fptr <- mallocForeignPtrBytes sz
    r <- withForeignPtr fptr $ \ptr -> readBuf fd ptr (fromIntegral sz)
    -- TODO(perf): should we trim the buffer to the size actually
    -- read? Maybe if it's below a certain size,
    -- possibly relative to the size of the buffer?
    pure $! case r of
        Left e  -> Left e
        Right v -> Right (BS.fromForeignPtr fptr 0 (fromIntegral v))

readExn :: Fd -> Int -> IO BS.ByteString
readExn fd sz =
    throwIfErrno $ read fd sz

writeBuf :: Fd -> Ptr Word8 -> CSize -> EIO CSsize
writeBuf fd ptr sz =
    retryEINTR $ orErrno $ c_write fd ptr sz

writeBufExn :: Fd -> Ptr Word8 -> CSize -> IO CSsize
writeBufExn fd ptr sz =
    throwIfErrno $ writeBuf fd ptr sz

write :: Fd -> BS.ByteString -> EIO CSsize
write fd bs =
    let (fptr, off, len) = BS.toForeignPtr bs in
    withForeignPtr fptr $ \ptr ->
        writeBuf fd (plusPtr ptr off) (fromIntegral len)

writeExn :: Fd -> BS.ByteString -> IO CSsize
writeExn fd bs =
    throwIfErrno $ write fd bs

-- | Wrapper around write that makes sure the full bytestring is written,
-- handling short writes from the underlying system call.
writeFull :: Fd -> BS.ByteString -> EIO ()
writeFull fd bs = do
    ret <- write fd bs
    case ret of
        Left e -> pure $ Left e
        Right v
            | (fromIntegral v) == BS.length bs -> pure $ Right ()
            | otherwise -> writeFull fd (BS.drop (fromIntegral v) bs)

writeFullExn :: Fd -> BS.ByteString -> IO ()
writeFullExn fd bs =
    throwIfErrno $ writeFull fd bs

remove :: CString -> EIO ()
remove fpath =
    useCStr fpath $ \path ->
        retryEINTR $ orErrno $ () <$ c_remove path

removeExn :: CString -> IO ()
removeExn fpath =
    throwIfErrno $ remove fpath

rmdir :: CString -> EIO ()
rmdir fpath =
    useCStr fpath $ \path ->
        retryEINTR $ orErrno $ () <$ c_rmdir path

rmdirExn :: CString -> IO ()
rmdirExn fpath =
    throwIfErrno $ rmdir fpath

mkdir :: CString -> CMode -> EIO ()
mkdir fpath mode =
    useCStr fpath $ \path ->
        retryEINTR $ orErrno $ () <$ c_mkdir path mode

mkdirExn :: CString -> CMode -> IO ()
mkdirExn fpath mode =
    throwIfErrno $ mkdir fpath mode

newtype OpenFlag = OpenFlag CInt
instance Semigroup OpenFlag where
    (OpenFlag x) <> (OpenFlag y) = OpenFlag (x .|. y)

open :: CString -> OpenFlag -> CMode -> EIO Fd
open fpath (OpenFlag flag) mode =
    useCStr fpath $ \path ->
        retryEINTR $ orErrno $ c_open path flag mode

openExn :: CString -> OpenFlag -> CMode -> IO Fd
openExn path flag mode =
    throwIfErrno $ open path flag mode

openat :: Fd -> CString -> OpenFlag -> CMode -> EIO Fd
openat fd fpath (OpenFlag flag) mode =
    useCStr fpath $ \path ->
        retryEINTR $ orErrno $ c_openat fd path flag mode

openatExn :: Fd -> CString -> OpenFlag -> CMode -> IO Fd
openatExn fd path flag mode =
    throwIfErrno $ openat fd path flag mode

o_APPEND    = OpenFlag c_O_APPEND
o_CLOEXEC   = OpenFlag c_O_CLOEXEC
o_CREAT     = OpenFlag c_O_CREAT
o_DIRECTORY = OpenFlag c_O_DIRECTORY
o_EXCL      = OpenFlag c_O_EXCL
o_NOFOLLOW  = OpenFlag c_O_NOFOLLOW
o_NONBLOCK  = OpenFlag c_O_NONBLOCK
o_NDELAY    = OpenFlag c_O_NDELAY
o_TRUNC     = OpenFlag c_O_TRUNC
o_RDONLY    = OpenFlag c_O_RDONLY
o_WRONLY    = OpenFlag c_O_WRONLY
o_RDWR      = OpenFlag c_O_RDWR

close :: Fd -> EIO ()
close fd =
    orErrno $ void $ c_close fd
    -- We intentionally do not retryEINTR; posix is silent on whether
    -- the file descriptor will be closed after an EINTR return, but on
    -- at least Linux the answer is always yes, so we must not retry
    -- in case the file descriptor has been re-allocated by another thread.

closeExn :: Fd -> IO ()
closeExn fd = throwIfErrno $ close fd