posix-api-0.7.3.0: src/Posix/File.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UnliftedFFITypes #-}
module Posix.File
( -- * Functions
uninterruptibleGetDescriptorFlags
, uninterruptibleGetStatusFlags
, uninterruptibleWriteByteArray
, uninterruptibleWriteBytes
, uninterruptibleWriteBytesCompletely
, uninterruptibleWriteBytesCompletelyErrno
, uninterruptibleReadMutableByteArray
, writeBytesCompletelyErrno
, uninterruptibleOpen
, uninterruptibleOpenMode
, uninterruptibleOpenAtMode
, uninterruptibleOpenUntypedFlags
, uninterruptibleOpenModeUntypedFlags
, writeByteArray
, writeMutableByteArray
, close
, uninterruptibleClose
, uninterruptibleErrorlessClose
, uninterruptibleUnlink
, uninterruptibleLink
-- * Types
, AccessMode (..)
, CreationFlags (..)
, DescriptorFlags (..)
, StatusFlags (..)
-- * File Descriptor Flags
, Types.nonblocking
, Types.append
, isReadOnly
, isWriteOnly
, isReadWrite
-- * Open Access Mode
, Types.readOnly
, Types.writeOnly
, Types.readWrite
-- * File Creation Flags
, Types.create
, Types.truncate
, Types.exclusive
) where
import Assertion (assertByteArrayPinned, assertMutableByteArrayPinned)
import Data.Bits ((.&.), (.|.))
import Data.Bytes.Types (Bytes (Bytes))
import Data.Primitive (ByteArray (..), MutableByteArray (MutableByteArray))
import Foreign.C.Error (Errno (Errno), eOK, getErrno)
import Foreign.C.String.Managed (ManagedCString (..))
import Foreign.C.Types (CInt (..), CSize (..))
import GHC.Exts (ByteArray#, MutableByteArray#, RealWorld)
import Posix.File.Types (AccessMode (..), CreationFlags (..), DescriptorFlags (..), StatusFlags (..))
import System.Posix.Types (CMode (..), CSsize (..), Fd (..))
import qualified Posix.File.Types as Types
{- | Get file descriptor flags. This uses the unsafe FFI to
perform @fcntl(fd,F_GETFD)@.
-}
uninterruptibleGetDescriptorFlags :: Fd -> IO (Either Errno DescriptorFlags)
uninterruptibleGetDescriptorFlags !fd = c_getFdFlags fd >>= errorsFromDescriptorFlags
{- | Get file status flags. This uses the unsafe FFI to
perform @fcntl(fd,F_GETFL)@.
-}
uninterruptibleGetStatusFlags :: Fd -> IO (Either Errno StatusFlags)
uninterruptibleGetStatusFlags !fd = c_getFlFlags fd >>= errorsFromStatusFlags
foreign import ccall unsafe "HaskellPosix.h hs_get_fd_flags"
c_getFdFlags :: Fd -> IO DescriptorFlags
foreign import ccall unsafe "HaskellPosix.h hs_get_fl_flags"
c_getFlFlags :: Fd -> IO StatusFlags
foreign import ccall unsafe "HaskellPosix.h write_offset"
c_unsafe_bytearray_write :: Fd -> ByteArray# -> Int -> CSize -> IO CSsize
foreign import ccall unsafe "HaskellPosix.h write_offset_loop"
c_unsafe_bytearray_write_loop :: Fd -> ByteArray# -> Int -> CSize -> IO Errno
foreign import ccall safe "HaskellPosix.h write_offset_loop"
c_safe_bytearray_write_loop :: Fd -> ByteArray# -> Int -> CSize -> IO Errno
foreign import ccall safe "HaskellPosix.h write_offset"
c_safe_bytearray_write :: Fd -> ByteArray# -> Int -> CSize -> IO CSsize
foreign import ccall safe "HaskellPosix.h write_offset"
c_safe_mutablebytearray_write :: Fd -> MutableByteArray# RealWorld -> Int -> CSize -> IO CSsize
foreign import ccall unsafe "HaskellPosix.h open"
c_unsafe_open :: ByteArray# -> CInt -> IO Fd
foreign import ccall unsafe "HaskellPosix.h open"
c_unsafe_open_mode :: ByteArray# -> CInt -> CMode -> IO Fd
foreign import ccall unsafe "HaskellPosix.h openat"
c_unsafe_openat_mode :: Fd -> ByteArray# -> CInt -> CMode -> IO Fd
foreign import ccall unsafe "HaskellPosix.h unlink"
c_unsafe_unlink :: ByteArray# -> IO CInt
foreign import ccall unsafe "HaskellPosix.h link"
c_unsafe_link :: ByteArray# -> ByteArray# -> IO CInt
foreign import ccall safe "unistd.h close"
c_safe_close :: Fd -> IO CInt
foreign import ccall unsafe "unistd.h close"
c_unsafe_close :: Fd -> IO CInt
uninterruptibleOpen ::
-- | NULL-terminated file name
ManagedCString ->
-- | Access mode
AccessMode ->
-- | Creation flags
CreationFlags ->
-- | Status flags
StatusFlags ->
IO (Either Errno Fd)
uninterruptibleOpen (ManagedCString (ByteArray name)) (AccessMode x) (CreationFlags y) (StatusFlags z) =
c_unsafe_open name (x .|. y .|. z) >>= errorsFromFd
{- | Variant of 'uninterruptibleOpen' that does not help the caller with
the types of the flags.
-}
uninterruptibleOpenUntypedFlags ::
-- | NULL-terminated file name
ManagedCString ->
-- | Flags
CInt ->
IO (Either Errno Fd)
uninterruptibleOpenUntypedFlags (ManagedCString (ByteArray name)) x =
c_unsafe_open name x >>= errorsFromFd
{- | Variant of 'uninterruptibleOpenMode' that does not help the caller with
the types of the flags.
-}
uninterruptibleOpenModeUntypedFlags ::
-- | NULL-terminated file name
ManagedCString ->
-- | Flags
CInt ->
-- | Mode
CMode ->
IO (Either Errno Fd)
uninterruptibleOpenModeUntypedFlags (ManagedCString (ByteArray name)) !x !mode =
c_unsafe_open_mode name x mode >>= errorsFromFd
uninterruptibleOpenMode ::
-- | NULL-terminated file name
ManagedCString ->
-- | Access mode, should include @O_CREAT@
AccessMode ->
-- | Creation flags
CreationFlags ->
-- | Status flags
StatusFlags ->
-- | Permissions assigned to newly created file
CMode ->
IO (Either Errno Fd)
uninterruptibleOpenMode (ManagedCString (ByteArray name)) (AccessMode x) (CreationFlags y) (StatusFlags z) !mode =
c_unsafe_open_mode name (x .|. y .|. z) mode >>= errorsFromFd
{- | Variant of 'uninterruptibleOpenMode' that lets the user specify a
directory file descriptor instead of using the working directory as the
base path.
-}
uninterruptibleOpenAtMode ::
-- | Base directory
Fd ->
-- | NULL-terminated file name
ManagedCString ->
-- | Access mode, should include @O_CREAT@
AccessMode ->
-- | Creation flags
CreationFlags ->
-- | Status flags
StatusFlags ->
-- | Permissions assigned to newly created file
CMode ->
IO (Either Errno Fd)
uninterruptibleOpenAtMode !dirFd (ManagedCString (ByteArray name)) (AccessMode x) (CreationFlags y) (StatusFlags z) !mode =
c_unsafe_openat_mode dirFd name (x .|. y .|. z) mode >>= errorsFromFd
errorsFromDescriptorFlags :: DescriptorFlags -> IO (Either Errno DescriptorFlags)
errorsFromDescriptorFlags r@(DescriptorFlags x) =
if x > (-1)
then pure (Right r)
else fmap Left getErrno
errorsFromStatusFlags :: StatusFlags -> IO (Either Errno StatusFlags)
errorsFromStatusFlags r@(StatusFlags x) =
if x > (-1)
then pure (Right r)
else fmap Left getErrno
{- | Wrapper for @write(2)@ that takes a slice of bytes and an offset.
The byte array backing the slice does not need to be pinned.
-}
uninterruptibleWriteBytesCompletely ::
-- | File descriptor
Fd ->
-- | Source bytes
Bytes ->
IO (Either Errno ())
uninterruptibleWriteBytesCompletely !fd !b = do
e <- uninterruptibleWriteBytesCompletelyErrno fd b
if e == eOK
then pure (Right ())
else pure (Left e)
{- | Variant of 'uninterruptibleWriteBytesCompletely' that uses errno 0
to communicate success.
-}
uninterruptibleWriteBytesCompletelyErrno ::
-- | File descriptor
Fd ->
-- | Source bytes
Bytes ->
IO Errno
uninterruptibleWriteBytesCompletelyErrno !fd (Bytes (ByteArray buf) off len) =
c_unsafe_bytearray_write_loop fd buf off (fromIntegral @Int @CSize len)
{- | Wrapper for @write(2)@ that takes a slice of bytes and an offset.
The byte array backing the slice must be pinned.
-}
writeBytesCompletelyErrno ::
-- | File descriptor
Fd ->
-- | Source bytes
Bytes ->
IO Errno
writeBytesCompletelyErrno !fd (Bytes buf0 off len) =
let !(ByteArray buf1) = assertByteArrayPinned buf0
in c_safe_bytearray_write_loop fd buf1 off (fromIntegral @Int @CSize len)
{- | Wrapper for @write(2)@ that takes a slice of bytes and an offset.
The byte array backing the slice does not need to be pinned.
-}
uninterruptibleWriteBytes ::
-- | File descriptor
Fd ->
-- | Source bytes
Bytes ->
-- | Number of bytes written
IO (Either Errno CSize)
uninterruptibleWriteBytes !fd (Bytes (ByteArray buf) off len) =
c_unsafe_bytearray_write fd buf off (fromIntegral @Int @CSize len)
>>= errorsFromSize
{- | Wrapper for @write(2)@ that takes a byte array and an offset.
The byte array does not need to be pinned.
-}
uninterruptibleWriteByteArray ::
-- | Socket
Fd ->
-- | Source byte array
ByteArray ->
-- | Offset into source array
Int ->
-- | Length in bytes
CSize ->
-- | Number of bytes pushed to send buffer
IO (Either Errno CSize)
uninterruptibleWriteByteArray !fd (ByteArray buf) !off !len =
c_unsafe_bytearray_write fd buf off len >>= errorsFromSize
{- | Wrapper for @write(2)@ that takes a byte array and an offset.
Uses @safe@ FFI. The byte array must be pinned.
-}
writeByteArray ::
-- | File descriptor
Fd ->
-- | Source byte array
ByteArray ->
-- | Offset into source array
Int ->
-- | Length in bytes
CSize ->
-- | Number of bytes pushed to send buffer
IO (Either Errno CSize)
writeByteArray !fd !buf0 !off !len =
let !(ByteArray buf1) = assertByteArrayPinned buf0
in c_safe_bytearray_write fd buf1 off len >>= errorsFromSize
-- writeByteArrayCompletely ::
{- | Variant of 'writeByteArray' that operates on mutable byte array.
Uses @safe@ FFI. The byte array must be pinned.
-}
writeMutableByteArray ::
-- | File descriptor
Fd ->
-- | Source byte array
MutableByteArray RealWorld ->
-- | Offset into source array
Int ->
-- | Length in bytes
CSize ->
-- | Number of bytes pushed to send buffer
IO (Either Errno CSize)
writeMutableByteArray !fd !buf0 !off !len =
let !(MutableByteArray buf1) = assertMutableByteArrayPinned buf0
in c_safe_mutablebytearray_write fd buf1 off len >>= errorsFromSize
uninterruptibleReadMutableByteArray ::
-- | File descriptor
Fd ->
-- | Destination
MutableByteArray RealWorld ->
-- | Destination offset
Int ->
-- | Length in bytes
CSize ->
-- | Number of bytes received
IO (Either Errno CSize)
uninterruptibleReadMutableByteArray !fd !(MutableByteArray !b) !doff !dlen = do
c_unsafe_mutable_byte_array_read fd b doff dlen >>= errorsFromSize
errorsFromSize :: CSsize -> IO (Either Errno CSize)
errorsFromSize r =
if r > (-1)
then pure (Right (cssizeToCSize r))
else fmap Left getErrno
errorsFromFd :: Fd -> IO (Either Errno Fd)
errorsFromFd r =
if r > (-1)
then pure (Right r)
else fmap Left getErrno
uninterruptibleLink ::
-- | Path to existing file
ManagedCString ->
-- | Path to new file
ManagedCString ->
IO (Either Errno ())
uninterruptibleLink (ManagedCString (ByteArray x)) (ManagedCString (ByteArray y)) =
c_unsafe_link x y >>= errorsFromInt_
uninterruptibleUnlink ::
-- | File name
ManagedCString ->
IO (Either Errno ())
uninterruptibleUnlink (ManagedCString (ByteArray x)) =
c_unsafe_unlink x >>= errorsFromInt_
{- | Close a file descriptor.
The <http://pubs.opengroup.org/onlinepubs/009696899/functions/close.html POSIX specification>
includes more details. This uses the safe FFI.
-}
close ::
-- | Socket
Fd ->
IO (Either Errno ())
close fd = c_safe_close fd >>= errorsFromInt_
{- | Close a file descriptor. This uses the unsafe FFI. According to the
<http://pubs.opengroup.org/onlinepubs/009696899/functions/close.html POSIX specification>,
"If @fildes@ refers to a socket, @close()@ shall cause the socket to
be destroyed. If the socket is in connection-mode, and the @SO_LINGER@
option is set for the socket with non-zero linger time, and the socket
has untransmitted data, then @close()@ shall block for up to the current
linger interval until all data is transmitted."
-}
uninterruptibleClose ::
-- | Socket
Fd ->
IO (Either Errno ())
uninterruptibleClose fd = c_unsafe_close fd >>= errorsFromInt_
{- | Close a file descriptor with the unsafe FFI. Do not check for errors.
It is only appropriate to use this when a file descriptor is being
closed to handle an exceptional case. Since the user will want to
propogate the original exception, the exception provided by
'uninterruptibleClose' would just be discarded. This function allows us
to potentially avoid an additional FFI call to 'getErrno'.
-}
uninterruptibleErrorlessClose ::
-- | Socket
Fd ->
IO ()
uninterruptibleErrorlessClose fd = do
_ <- c_unsafe_close fd
pure ()
-- only call this when it is known that the argument is non-negative
cssizeToCSize :: CSsize -> CSize
cssizeToCSize = fromIntegral
isReadOnly :: StatusFlags -> Bool
isReadOnly (StatusFlags x) = x .&. 0b11 == 0
isWriteOnly :: StatusFlags -> Bool
isWriteOnly (StatusFlags x) = x .&. 0b11 == 1
isReadWrite :: StatusFlags -> Bool
isReadWrite (StatusFlags x) = x .&. 0b11 == 2
-- Sometimes, functions that return an int use zero to indicate
-- success and negative one to indicate failure without including
-- additional information in the value.
errorsFromInt_ :: CInt -> IO (Either Errno ())
errorsFromInt_ r =
if r == 0
then pure (Right ())
else fmap Left getErrno
foreign import ccall unsafe "HaskellPosix.h read_offset"
c_unsafe_mutable_byte_array_read :: Fd -> MutableByteArray# RealWorld -> Int -> CSize -> IO CSsize