diff --git a/bench-test-lib/src/BenchTestLib/DirIO.hs b/bench-test-lib/src/BenchTestLib/DirIO.hs index b4e0355e41..dd9e588903 100644 --- a/bench-test-lib/src/BenchTestLib/DirIO.hs +++ b/bench-test-lib/src/BenchTestLib/DirIO.hs @@ -60,9 +60,6 @@ import qualified Streamly.Data.Unfold as Unfold import qualified Streamly.Internal.Data.Unfold as Unfold import qualified Streamly.Internal.FileSystem.DirIO as Dir import qualified Streamly.FileSystem.Path as Path -#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__) -import qualified Streamly.Internal.FileSystem.Posix.ReadDir as Dir -#endif -------------------------------------------------------------------------------- -- Helpers diff --git a/core/src/Streamly/Internal/Data/Array.hs b/core/src/Streamly/Internal/Data/Array.hs index 44ae6d8f3b..3b9a012159 100644 --- a/core/src/Streamly/Internal/Data/Array.hs +++ b/core/src/Streamly/Internal/Data/Array.hs @@ -43,8 +43,9 @@ module Streamly.Internal.Data.Array , cast , asBytes , unsafeCast - , asCStringUnsafe -- XXX asCString - , asCWString + , asCString -- XXX can remove + , asCWString -- XXX can remove + , asNullTerminatedPtr -- * Subarrays -- , sliceOffLen @@ -107,6 +108,7 @@ module Streamly.Internal.Data.Array , splitOn , fold , foldBreakChunksK + , asCStringUnsafe ) where @@ -121,7 +123,9 @@ import Control.Monad.IO.Class (MonadIO(..)) import Data.Functor.Identity (Identity(..)) import Data.Proxy (Proxy(..)) import Data.Word (Word8) +import Foreign (Ptr, castPtr) import Foreign.C.String (CString, CWString) +import Foreign.C.Types (CWchar) import GHC.Types (SPEC(..)) import Streamly.Internal.Data.Unbox (Unbox(..)) import Prelude hiding (length, null, last, map, (!!), read, concat) @@ -396,12 +400,20 @@ streamTransform f arr = -- Casts ------------------------------------------------------------------------------- --- | Cast an array having elements of type @a@ into an array having elements of --- type @b@. The array size must be a multiple of the size of type @b@ --- otherwise accessing the last element of the array may result into a crash or --- a random value. +-- | Unsafely reinterpret an array of @a@ as an array of @b@. -- --- /Pre-release/ +-- The 'byteLength' of the array must be a multiple of the size of @b@. +-- Additionally, the start offset of the array must satisfy the alignment +-- requirements of @b@ whenever the target platform or element access +-- operations require aligned memory accesses. +-- +-- Violating these requirements results in undefined behavior, including +-- possible crashes or invalid values. +-- +-- If @a@ and @b@ have the same runtime representation, prefer using +-- 'coerce', which is safe: +-- +-- >> unsafeCast = coerce -- unsafeCast, castUnsafe :: Array a -> Array b unsafeCast (Array contents start end) = @@ -414,11 +426,14 @@ RENAME(castUnsafe,unsafeCast) asBytes :: Array a -> Array Word8 asBytes = unsafeCast --- | Cast an array having elements of type @a@ into an array having elements of --- type @b@. The length of the array should be a multiple of the size of the --- target element otherwise 'Nothing' is returned. +-- | Attempt to reinterpret an array as containing elements of type @b@. -- +-- Returns 'Nothing' if the byte length of the array is not a multiple +-- of the size of @b@. -- +-- This function does not validate alignment requirements for @b@. +-- If stricter alignment is required, copy the array into a properly +-- aligned buffer before casting. cast :: forall a b. (Unbox b) => Array a -> Maybe (Array b) cast arr = let len = byteLength arr @@ -436,11 +451,23 @@ cast arr = -- -- /Pre-release/ -- +{-# DEPRECATED asCStringUnsafe "Please use asCString instead." #-} asCStringUnsafe :: Array a -> (CString -> IO b) -> IO b -asCStringUnsafe arr = MA.asCString (unsafeThaw arr) +asCStringUnsafe arr act = + MA.asCString (unsafeThaw (unsafeCast arr)) $ \ptr -> act (castPtr ptr) --- | Convert an array of any element type into a null terminated CWString Ptr. --- The array is copied to pinned memory. +-- | Use an array of CChar as a null terminated CString Ptr in pinned memory. +-- +-- /Unsafe/ +-- +-- /O(n) Time: (creates a copy of the array)/ +-- +-- /Pre-release/ +-- +asCString :: Array Word8 -> (Ptr Word8 -> IO b) -> IO b +asCString arr = MA.asCString (unsafeThaw arr) + +-- | Use an array of CWchar as a null terminated CWString Ptr in pinned memory. -- -- /Unsafe/ -- @@ -448,9 +475,13 @@ asCStringUnsafe arr = MA.asCString (unsafeThaw arr) -- -- /Pre-release/ -- -asCWString :: Array a -> (CWString -> IO b) -> IO b +asCWString :: Array CWchar -> (CWString -> IO b) -> IO b asCWString arr = MA.asCWString (unsafeThaw arr) +{-# INLINE asNullTerminatedPtr #-} +asNullTerminatedPtr :: (Unbox a, Num a) => Array a -> (Ptr a -> IO b) -> IO b +asNullTerminatedPtr arr = MA.asNullTerminatedPtr (unsafeThaw arr) + ------------------------------------------------------------------------------- -- Folds ------------------------------------------------------------------------------- diff --git a/core/src/Streamly/Internal/Data/MutArray/Type.hs b/core/src/Streamly/Internal/Data/MutArray/Type.hs index fb58bf31bc..6b83e22156 100644 --- a/core/src/Streamly/Internal/Data/MutArray/Type.hs +++ b/core/src/Streamly/Internal/Data/MutArray/Type.hs @@ -46,8 +46,9 @@ module Streamly.Internal.Data.MutArray.Type , unsafeCast , asBytes , unsafeAsPtr -- XXX asPtr - , asCString - , asCWString + , asCString -- XXX can remove + , asCWString -- XXX can remove + , asNullTerminatedPtr -- ** Construction , empty @@ -519,8 +520,8 @@ import Data.Char (ord) import Data.Functor.Identity (Identity(..)) import Data.Proxy (Proxy(..)) import Data.Word (Word8, Word16) -import Foreign.C.String (CString, CWString) -import Foreign.C.Types (CSize(..), CChar, CWchar) +import Foreign.C.String (CWString) +import Foreign.C.Types (CSize(..), CWchar) import Foreign.Ptr (plusPtr, castPtr) import Streamly.Internal.Data.MutByteArray.Type ( MutByteArray(..) @@ -3263,7 +3264,7 @@ unsafeSplice dst src = do (arrContents src) startSrc (arrContents dst) endDst srcLen return $ dst {arrEnd = endDst + srcLen} --- | Append specified number of bytes from a given pointer to the MutArray. +-- | Append specified number of Ptr items from a given pointer to the MutArray. -- -- /Unsafe:/ -- @@ -3274,20 +3275,22 @@ unsafeSplice dst src = do -- 3. the pointer passed is valid up to the given length. -- {-# INLINE unsafeAppendPtrN #-} -unsafeAppendPtrN :: MonadIO m => - MutArray Word8 -> Ptr Word8 -> Int -> m (MutArray Word8) -unsafeAppendPtrN arr ptr ptrLen = do - let newEnd = arrEnd arr + ptrLen +unsafeAppendPtrN :: forall m a. (MonadIO m, Unbox a) => + MutArray a -> Ptr a -> Int -> m (MutArray a) +unsafeAppendPtrN arr ptr count = do + let byteCount = count * SIZE_OF(a) + let newEnd = arrEnd arr + byteCount assertM(newEnd <= arrBound arr) - Unboxed.unsafePutPtrN ptr (arrContents arr) (arrEnd arr) ptrLen + Unboxed.unsafePutPtrN + (castPtr ptr) (arrContents arr) (arrEnd arr) byteCount return $ arr {arrEnd = newEnd} {-# INLINE appendPtrN #-} -appendPtrN :: MonadIO m => - MutArray Word8 -> Ptr Word8 -> Int -> m (MutArray Word8) -appendPtrN arr ptr ptrLen = do - arr1 <- growBy ptrLen arr - unsafeAppendPtrN arr1 ptr ptrLen +appendPtrN :: (Unbox a, MonadIO m) => + MutArray a -> Ptr a -> Int -> m (MutArray a) +appendPtrN arr ptr count = do + arr1 <- growBy count arr + unsafeAppendPtrN arr1 ptr count -- | @spliceWith sizer dst src@ mutates @dst@ to append @src@. If there is no -- reserved space available in @dst@ it is reallocated to a size determined by @@ -3662,27 +3665,27 @@ unsafeCreateWithPtr' cap pop = do ++ "length = " ++ show len ++ ", " ++ "capacity = " ++ show cap -asCString :: MutArray a -> (CString -> IO b) -> IO b -asCString arr act = do +{-# INLINE asNullTerminatedPtr #-} +asNullTerminatedPtr :: forall a b. (Unbox a, Num a) => + MutArray a -> (Ptr a -> IO b) -> IO b +asNullTerminatedPtr arr act = do let pinned = isPinned arr - req = byteLength arr + SIZE_OF(CChar) + req = byteLength arr + SIZE_OF(a) arr1 <- if byteCapacity arr < req || not pinned - then reallocExplicitAs Pinned 1 req arr + then reallocExplicitAs Pinned (SIZE_OF(a)) req arr else return arr - arr2 :: MutArray CChar <- snocUnsafe (unsafeCast arr1) (0 :: CChar) - unsafeAsPtr arr2 $ \ptr _ -> act (castPtr ptr) + arr2 <- snocUnsafe arr1 (0 :: a) + unsafeAsPtr arr2 $ \ptr _ -> act ptr -asCWString :: MutArray a -> (CWString -> IO b) -> IO b -asCWString arr act = do - let pinned = isPinned arr - req = byteLength arr + SIZE_OF(CWchar) - arr1 <- - if byteCapacity arr < req || not pinned - then reallocExplicitAs Pinned 1 req arr - else return arr - arr2 :: MutArray CWchar <- snocUnsafe (unsafeCast arr1) (0 :: CWchar) - unsafeAsPtr arr2 $ \ptr _ -> act (castPtr ptr) +-- | Provides a pinned, null terminated CString pointer (Ptr Word8). +asCString :: MutArray Word8 -> (Ptr Word8 -> IO b) -> IO b +asCString = asNullTerminatedPtr + +-- | Provides a pinned, null terminated CWString pointer (Ptr CWchar). +-- Note CWchar is 16-bit wide on Windows and 32-bit wide on Posix. +asCWString :: MutArray CWchar -> (CWString -> IO b) -> IO b +asCWString = asNullTerminatedPtr ------------------------------------------------------------------------------- -- Equality diff --git a/core/src/Streamly/Internal/FileSystem/DirIO.hs b/core/src/Streamly/Internal/FileSystem/DirIO.hs index 04dd1a5b7b..a80dbb7551 100644 --- a/core/src/Streamly/Internal/FileSystem/DirIO.hs +++ b/core/src/Streamly/Internal/FileSystem/DirIO.hs @@ -127,7 +127,9 @@ module Streamly.Internal.FileSystem.DirIO , readDirs , readEither , readEitherPaths - , readEitherChunks + , OS.readEitherChunks + , OS.readEitherByteChunks + , readEitherChunksPortable -- We can implement this in terms of readAttrsRecursive without losing -- perf. @@ -137,10 +139,10 @@ module Streamly.Internal.FileSystem.DirIO -- * Unfolds -- | Use the more convenient stream APIs instead of unfolds where possible. - , reader + , OS.reader , fileReader , dirReader - , eitherReader + , OS.eitherReader , eitherReaderPaths {- @@ -178,12 +180,11 @@ import Data.Either (isRight, isLeft, fromLeft, fromRight) import Streamly.Data.Stream (Stream) import Streamly.Internal.Data.Unfold.Type (Unfold(..)) import Streamly.Internal.FileSystem.Path (Path) -#if defined(mingw32_HOST_OS) || defined(__MINGW32__) import qualified Streamly.Internal.Data.Fold as Fold -import Streamly.Internal.FileSystem.Windows.ReadDir (eitherReader, reader) +#if defined(mingw32_HOST_OS) || defined(__MINGW32__) +import qualified Streamly.Internal.Syscall.Windows.ReadDir as OS #else -import Streamly.Internal.FileSystem.Posix.ReadDir - ( readEitherChunks, eitherReader, reader) +import qualified Streamly.Internal.Syscall.Posix.ReadDir as OS #endif import qualified Streamly.Internal.Data.Stream as S import qualified Streamly.Data.Unfold as UF @@ -337,7 +338,7 @@ eitherReaderPaths ::(MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> eitherReaderPaths f = let () = Path.join in fmap (\(dir, x) -> bimap (dir ) (dir ) x) - $ UF.carry (eitherReader f) + $ UF.carry (OS.eitherReader f) -- -- | Read files only. @@ -347,7 +348,7 @@ eitherReaderPaths f = {-# INLINE fileReader #-} fileReader :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> Unfold m Path Path -fileReader f = fmap (fromRight undefined) $ UF.filter isRight (eitherReader f) +fileReader f = fmap (fromRight undefined) $ UF.filter isRight (OS.eitherReader f) -- | Read directories only. Filter out "." and ".." entries. -- @@ -356,7 +357,7 @@ fileReader f = fmap (fromRight undefined) $ UF.filter isRight (eitherReader f) {-# INLINE dirReader #-} dirReader :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> Unfold m Path Path -dirReader f = fmap (fromLeft undefined) $ UF.filter isLeft (eitherReader f) +dirReader f = fmap (fromLeft undefined) $ UF.filter isLeft (OS.eitherReader f) -- | Raw read of a directory. -- @@ -364,7 +365,7 @@ dirReader f = fmap (fromLeft undefined) $ UF.filter isLeft (eitherReader f) {-# INLINE read #-} read :: (MonadIO m, MonadCatch m) => Path -> Stream m Path -read = S.unfold reader +read = S.unfold OS.reader -- | Read directories as Left and files as Right. Filter out "." and ".." -- entries. The output contains the names of the directories and files. @@ -373,7 +374,7 @@ read = S.unfold reader {-# INLINE readEither #-} readEither :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> Path -> Stream m (Either Path Path) -readEither f = S.unfold (eitherReader f) +readEither f = S.unfold (OS.eitherReader f) -- | Like 'readEither' but prefix the names of the files and directories with -- the supplied directory path. @@ -384,20 +385,12 @@ readEitherPaths f dir = let () = Path.join in fmap (bimap (dir ) (dir )) $ readEither f dir -#if defined(mingw32_HOST_OS) || defined(__MINGW32__) --- XXX Implement a custom version of readEitherChunks (like for Posix) for --- windows as well. Also implement readEitherByteChunks. --- --- XXX For a fast custom implementation of traversal, the Right could be the --- final array chunk including all files and dirs to be written to IO. The Left --- could be list of dirs to be traversed. --- --- This is a generic (but slower?) version of readEitherChunks using --- eitherReaderPaths. -{-# INLINE readEitherChunks #-} -readEitherChunks :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> +-- | A generic (but slower) version of 'readEitherChunks' using +-- 'eitherReaderPaths'. +{-# INLINE readEitherChunksPortable #-} +readEitherChunksPortable :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) -> [Path] -> Stream m (Either [Path] [Path]) -readEitherChunks f dirs = +readEitherChunksPortable f dirs = -- XXX Need to use a take to limit the group size. There will be separate -- limits for dir and files groups. S.groupsWhile grouper collector @@ -422,7 +415,6 @@ readEitherChunks f dirs = Right _ -> Left [x1] -- initial _ -> either (\xs -> Left (x1:xs)) Right b Right x1 -> fmap (x1:) b -#endif -- | Read files only. -- diff --git a/core/src/Streamly/Internal/FileSystem/FileIO.hs b/core/src/Streamly/Internal/FileSystem/FileIO.hs index 7b7db680d1..e5bd4cf8ce 100644 --- a/core/src/Streamly/Internal/FileSystem/FileIO.hs +++ b/core/src/Streamly/Internal/FileSystem/FileIO.hs @@ -96,9 +96,9 @@ import qualified Streamly.Internal.Data.Fold.Type as FL (Step(..), snoc, reduce) import qualified Streamly.Internal.FileSystem.Handle as FH #if !defined(mingw32_HOST_OS) && !defined(__MINGW32__) -import qualified Streamly.Internal.FileSystem.Posix.File as File +import qualified Streamly.Internal.Syscall.Posix.File as File #else -import qualified Streamly.Internal.FileSystem.Windows.File as File +import qualified Streamly.Internal.Syscall.Windows.File as File #endif #include "inline.hs" diff --git a/core/src/Streamly/Internal/FileSystem/Path/Common.hs b/core/src/Streamly/Internal/FileSystem/Path/Common.hs index a74bff3321..d58bf6c8e5 100644 --- a/core/src/Streamly/Internal/FileSystem/Path/Common.hs +++ b/core/src/Streamly/Internal/FileSystem/Path/Common.hs @@ -59,6 +59,7 @@ module Streamly.Internal.FileSystem.Path.Common , unsafeAppend , appendCString , appendCString' + , appendCStringWith , unsafeJoinPaths -- , joinRoot -- XXX append should be enough, see joinRootBody @@ -1506,36 +1507,39 @@ mkQ f = -- XXX Scan this entire module for pushing array operations to MutArray or -- MutByteArray modules. --- See also cstringLength# in GHC.CString in ghc-prim -foreign import ccall unsafe "string.h strlen" c_strlen_pinned - :: Addr# -> IO CSize - {-# INLINE appendCStringWith #-} -appendCStringWith :: - (Int -> IO (MutArray Word8)) +appendCStringWith :: (Unbox a, Integral a) => + (Int -> IO (MutArray a)) + -> (Addr# -> IO CSize) -> OS - -> Array Word8 - -> CString - -> IO (Array Word8) -appendCStringWith create os a b@(Ptr addrB#) = do - let lenA = Array.length a - lenB <- fmap fromIntegral $ c_strlen_pinned addrB# - assertM(lenA /= 0 && lenB /= 0) - let len = lenA + 1 + lenB - arr <- create len - arr1 <- MutArray.unsafeSplice arr (Array.unsafeThaw a) + -> Array a + -> Ptr a + -> IO (Array a) +appendCStringWith create strlen os origArr origStr@(Ptr strAddr#) = do + let countArr = Array.length origArr + countStr <- fmap fromIntegral $ strlen strAddr# + assertM(countArr /= 0 && countStr /= 0) + let count = countArr + 1 + countStr + arr <- create count + arr1 <- MutArray.unsafeSplice arr (Array.unsafeThaw origArr) arr2 <- MutArray.unsafeSnoc arr1 (charToWord (primarySeparator os)) - arr3 :: MutArray.MutArray Word8 <- - MutArray.unsafeAppendPtrN arr2 (castPtr b) lenB + arr3 <- MutArray.unsafeAppendPtrN arr2 origStr countStr return (Array.unsafeFreeze arr3) +-- See also cstringLength# in GHC.CString in ghc-prim +foreign import ccall unsafe "string.h strlen" c_strlen_pinned + :: Addr# -> IO CSize + {-# INLINE appendCString #-} appendCString :: OS -> Array Word8 -> CString -> IO (Array Word8) -appendCString = appendCStringWith MutArray.emptyOf +appendCString os arr cstr = + appendCStringWith MutArray.emptyOf c_strlen_pinned os arr (castPtr cstr) {-# INLINE appendCString' #-} appendCString' :: OS -> Array Word8 -> CString -> IO (Array Word8) -appendCString' = appendCStringWith MutArray.emptyOf' +appendCString' os arr cstr = + appendCStringWith MutArray.emptyOf' c_strlen_pinned os arr (castPtr cstr) + {-# INLINE doAppend #-} doAppend :: (Unbox a, Integral a) => OS -> Array a -> Array a -> Array a diff --git a/core/src/Streamly/Internal/FileSystem/PathIO.hs b/core/src/Streamly/Internal/FileSystem/PathIO.hs index 8ccd92137a..5652ff406e 100644 --- a/core/src/Streamly/Internal/FileSystem/PathIO.hs +++ b/core/src/Streamly/Internal/FileSystem/PathIO.hs @@ -52,8 +52,7 @@ getCurrentDirectory = modifyError $ do -- | Set the current working directory. setCurrentDirectory :: Path -> IO () -setCurrentDirectory p = modifyError $ - Syscall.setCwd (toArray p) +setCurrentDirectory p = modifyError $ Syscall.setCwd p where diff --git a/core/src/Streamly/Internal/FileSystem/PosixPath.hs b/core/src/Streamly/Internal/FileSystem/PosixPath.hs index 08e3cba0e4..4682e7aedd 100644 --- a/core/src/Streamly/Internal/FileSystem/PosixPath.hs +++ b/core/src/Streamly/Internal/FileSystem/PosixPath.hs @@ -16,6 +16,7 @@ -- encoding. #if defined(IS_PORTABLE) +-- In the portable module these are defined as type aliases to actual types #define OS_PATH_TYPE Path #define OS_WORD_TYPE OsWord #define OS_CSTRING_TYPE OsCString @@ -23,8 +24,8 @@ #elif defined(IS_WINDOWS) #define OS_PATH_TYPE WindowsPath #define OS_WORD_TYPE Word16 -#define OS_CSTRING_TYPE CWString -#define AS_OS_CSTRING asCWString +#define OS_CSTRING_TYPE Ptr Word16 +#define AS_OS_CSTRING asW16CString #else #define OS_PATH_TYPE PosixPath #define OS_WORD_TYPE Word8 @@ -37,7 +38,7 @@ #define OS_NAME Windows #define OS_PATH WindowsPath #define OS_WORD Word16 -#define OS_CSTRING CWString +#define OS_CSTRING Ptr Word16 #define UNICODE_ENCODER encodeUtf16le' #define UNICODE_DECODER decodeUtf16le' #define UNICODE_DECODER_LAX decodeUtf16le @@ -152,8 +153,8 @@ module Streamly.Internal.FileSystem.OS_PATH_TYPE -- , concat , unsafeJoin #ifndef IS_WINDOWS - , joinCStr - , joinCStr' + , appendCString + , appendCString' #endif , join , joinDir @@ -223,9 +224,10 @@ import Data.Maybe (fromJust, isJust) import Data.Word (Word8) #ifndef IS_WINDOWS import Foreign.C (CString) +import Foreign (castPtr) #else import Data.Word (Word16) -import Foreign.C (CWString) +import Foreign (Ptr) #endif import Language.Haskell.TH.Syntax (lift) import Streamly.Internal.Data.Array (Array(..)) @@ -869,13 +871,14 @@ instance Show OS_PATH where -- system calls on Posix. {-# INLINE AS_OS_CSTRING #-} AS_OS_CSTRING :: OS_PATH_TYPE -> (OS_CSTRING_TYPE -> IO a) -> IO a -AS_OS_CSTRING p = Array.asCStringUnsafe (toArray p) +AS_OS_CSTRING p act = + Array.asNullTerminatedPtr (toArray p) $ \ptr -> act (castPtr ptr) #else -- | Use the path as a pinned CWString. Useful for using a WindowsPath in -- system calls on Windows. {-# INLINE AS_OS_CSTRING #-} AS_OS_CSTRING :: OS_PATH_TYPE -> (OS_CSTRING_TYPE -> IO a) -> IO a -AS_OS_CSTRING p = Array.asCWString (toArray p) +AS_OS_CSTRING p = Array.asNullTerminatedPtr (toArray p) #endif ------------------------------------------------------------------------------ @@ -982,25 +985,33 @@ joinDir Common.OS_NAME (Common.toString Unicode.UNICODE_DECODER) a b #endif +-- NOTE: Even though appendCString is portable and can be exposed for both +-- Windows and Posix, appendCWString is different (16-bit vs 32-bit) on Windows +-- and Posix. Since WindowsPath module is designed to work on both Windows and +-- Posix, we cannot expose appendCWString as it would use 32-bit string on +-- Posix even in the Windows module. We will need a fixed appendW16CString for +-- that. + -- XXX This can be pure, like append. --- XXX add appendCWString for Windows? #ifndef IS_WINDOWS -- | Append a separator and a CString to the Array. This is like 'unsafeJoin' -- but always inserts a separator between the two paths even if the first path -- has a trailing separator or second path has a leading separator. -- -joinCStr :: OS_PATH_TYPE -> CString -> IO OS_PATH_TYPE -joinCStr (OS_PATH a) str = +{-# INLINE appendCString #-} +appendCString :: OS_PATH_TYPE -> CString -> IO OS_PATH_TYPE +appendCString (OS_PATH a) str = fmap OS_PATH $ Common.appendCString Common.OS_NAME a str --- | Like 'joinCStr' but creates a pinned path. +-- | Like 'appendCString' but creates a pinned path. -- -joinCStr' :: +{-# INLINE appendCString' #-} +appendCString' :: OS_PATH_TYPE -> CString -> IO OS_PATH_TYPE -joinCStr' +appendCString' (OS_PATH a) str = fmap OS_PATH $ Common.appendCString' diff --git a/core/src/Streamly/Internal/FileSystem/Windows/ReadDir.hsc b/core/src/Streamly/Internal/FileSystem/Windows/ReadDir.hsc deleted file mode 100644 index c17cddac05..0000000000 --- a/core/src/Streamly/Internal/FileSystem/Windows/ReadDir.hsc +++ /dev/null @@ -1,273 +0,0 @@ --- | --- Module : Streamly.Internal.FileSystem.Windows.ReadDir --- Copyright : (c) 2024 Composewell Technologies --- --- License : BSD3 --- Maintainer : streamly@composewell.com --- Portability : GHC - -module Streamly.Internal.FileSystem.Windows.ReadDir - ( -#if defined(mingw32_HOST_OS) || defined(__MINGW32__) - DirStream - , openDirStream - , closeDirStream - , readDirStreamEither - , eitherReader - , reader -#endif - ) -where - -#if defined(mingw32_HOST_OS) || defined(__MINGW32__) - -import Control.Exception (throwIO) -import Control.Monad (void) -import Control.Monad.Catch (MonadCatch) -import Control.Monad.IO.Class (MonadIO(..)) -import Data.Char (ord, isSpace) -import Data.IORef (IORef, newIORef, readIORef, writeIORef) -import Foreign.C (CInt(..), CWchar(..), Errno(..), errnoToIOError, peekCWString) -import Numeric (showHex) -import Streamly.Internal.Data.Unfold.Type (Unfold(..)) -import Streamly.Internal.Data.Stream (Step(..)) -import Streamly.Internal.FileSystem.Path (Path) -import Streamly.Internal.FileSystem.WindowsPath (WindowsPath(..)) -import System.IO.Error (ioeSetErrorString) - -import qualified Streamly.Internal.Data.Array as Array -import qualified Streamly.Internal.Data.Unfold as UF (bracketIO) -import qualified Streamly.Internal.FileSystem.WindowsPath as Path -import qualified System.Win32 as Win32 (failWith) - -import Streamly.Internal.FileSystem.DirOptions -import Foreign hiding (void) - -#include - --- Note on A vs W suffix in APIs. --- CreateFile vs. CreateFileW: CreateFile is a macro that expands to --- CreateFileA or CreateFileW depending on whether Unicode support (UNICODE and --- _UNICODE preprocessor macros) is enabled in your project. To ensure --- consistent Unicode support, explicitly use CreateFileW. - ------------------------------------------------------------------------------- --- Types ------------------------------------------------------------------------------- - -type BOOL = Bool -type DWORD = Word32 - -type UINT_PTR = Word -type ErrCode = DWORD -type LPCTSTR = Ptr CWchar -type WIN32_FIND_DATA = () -type HANDLE = Ptr () - ------------------------------------------------------------------------------- --- Windows C APIs ------------------------------------------------------------------------------- - --- XXX Note for i386, stdcall is needed instead of ccall, see Win32 --- package/windows_cconv.h. We support only x86_64 for now. -foreign import ccall unsafe "windows.h FindFirstFileW" - c_FindFirstFileW :: LPCTSTR -> Ptr WIN32_FIND_DATA -> IO HANDLE - -foreign import ccall unsafe "windows.h FindNextFileW" - c_FindNextFileW :: HANDLE -> Ptr WIN32_FIND_DATA -> IO BOOL - -foreign import ccall unsafe "windows.h FindClose" - c_FindClose :: HANDLE -> IO BOOL - -foreign import ccall unsafe "windows.h GetLastError" - getLastError :: IO ErrCode - -foreign import ccall unsafe "windows.h LocalFree" - localFree :: Ptr a -> IO (Ptr a) - ------------------------------------------------------------------------------- --- Haskell C APIs ------------------------------------------------------------------------------- - -foreign import ccall unsafe "maperrno_func" -- in base/cbits/Win32Utils.c - c_maperrno_func :: ErrCode -> IO Errno - ------------------------------------------------------------------------------- --- Error Handling ------------------------------------------------------------------------------- - --- XXX getErrorMessage and castUINTPtrToPtr require c code, so left out for --- now. Once we replace these we can remove dependency on Win32. We can --- possibly implement these in Haskell by directly calling the Windows API. - -foreign import ccall unsafe "getErrorMessage" - getErrorMessage :: DWORD -> IO (Ptr CWchar) - -foreign import ccall unsafe "castUINTPtrToPtr" - castUINTPtrToPtr :: UINT_PTR -> Ptr a - -failWith :: String -> ErrCode -> IO a -failWith fn_name err_code = do - c_msg <- getErrorMessage err_code - msg <- if c_msg == nullPtr - then return $ "Error 0x" ++ Numeric.showHex err_code "" - else do - msg <- peekCWString c_msg - -- We ignore failure of freeing c_msg, given we're already failing - _ <- localFree c_msg - return msg - errno <- c_maperrno_func err_code - let msg' = reverse $ dropWhile isSpace $ reverse msg -- drop trailing \n - ioerror = errnoToIOError fn_name errno Nothing Nothing - `ioeSetErrorString` msg' - throwIO ioerror - -errorWin :: String -> IO a -errorWin fn_name = do - err_code <- getLastError - failWith fn_name err_code - -failIf :: (a -> Bool) -> String -> IO a -> IO a -failIf p wh act = do - v <- act - if p v then errorWin wh else return v - -iNVALID_HANDLE_VALUE :: HANDLE -iNVALID_HANDLE_VALUE = castUINTPtrToPtr maxBound - ------------------------------------------------------------------------------- --- Dir stream implementation ------------------------------------------------------------------------------- - --- XXX Define this as data and unpack three fields? -newtype DirStream = - DirStream (HANDLE, IORef Bool, ForeignPtr WIN32_FIND_DATA) - -openDirStream :: WindowsPath -> IO DirStream -openDirStream p = do - let path = Path.unsafeJoin p $ Path.unsafeFromString "*" - fp_finddata <- mallocForeignPtrBytes (# const sizeof(WIN32_FIND_DATAW) ) - withForeignPtr fp_finddata $ \dataPtr -> do - handle <- - Array.asCStringUnsafe (Path.toArray path) $ \pathPtr -> do - -- XXX Use getLastError to distinguish the case when no - -- matching file is found. See the doc of FindFirstFileW. - failIf - (== iNVALID_HANDLE_VALUE) - ("FindFirstFileW: " ++ Path.toString path) - $ c_FindFirstFileW (castPtr pathPtr) dataPtr - ref <- newIORef True - return $ DirStream (handle, ref, fp_finddata) - -closeDirStream :: DirStream -> IO () -closeDirStream (DirStream (h, _, _)) = void (c_FindClose h) - --- XXX Keep this in sync with the isMetaDir function in Posix readdir module. -isMetaDir :: Ptr CWchar -> IO Bool -isMetaDir dname = do - -- XXX Assuming UTF16LE encoding - c1 <- peek dname - if (c1 /= fromIntegral (ord '.')) - then return False - else do - c2 :: Word8 <- peekByteOff dname 1 - if (c2 == 0) - then return True - else if (c2 /= fromIntegral (ord '.')) - then return False - else do - c3 :: Word8 <- peekByteOff dname 2 - if (c3 == 0) - then return True - else return False - -readDirStreamEither :: - (ReadOptions -> ReadOptions) -> - DirStream -> IO (Maybe (Either WindowsPath WindowsPath)) -readDirStreamEither _ (DirStream (h, ref, fdata)) = - withForeignPtr fdata $ \ptr -> do - firstTime <- readIORef ref - if firstTime - then do - writeIORef ref False - processEntry ptr - else findNext ptr - - where - - -- XXX: for a symlink the attribute may have a FILE_ATTRIBUTE_DIRECTORY if - -- the symlink was created as a directory symlink, but it might have - -- changed later. To find the real type of the symlink when we have - -- followSymlinks option on we need to check if it is a - -- FILE_ATTRIBUTE_REPARSE_POINT, we need to open the reparse point and find - -- the type. - - processEntry ptr = do - let dname = #{ptr WIN32_FIND_DATAW, cFileName} ptr - dattrs :: #{type DWORD} <- - #{peek WIN32_FIND_DATAW, dwFileAttributes} ptr - name <- Array.fromW16CString dname - if (dattrs .&. (#const FILE_ATTRIBUTE_DIRECTORY) /= 0) - then do - isMeta <- isMetaDir dname - if isMeta - then findNext ptr - else return (Just (Left (Path.unsafeFromArray name))) - else return (Just (Right (Path.unsafeFromArray name))) - - findNext ptr = do - retval <- liftIO $ c_FindNextFileW h ptr - if (retval) - then processEntry ptr - else do - err <- getLastError - if err == (# const ERROR_NO_MORE_FILES ) - then return Nothing - -- XXX Print the path in the error message - else Win32.failWith "findNextFile" err - -{-# INLINE streamEitherReader #-} -streamEitherReader :: MonadIO m => - (ReadOptions -> ReadOptions) -> - Unfold m DirStream (Either Path Path) -streamEitherReader f = Unfold step return - where - - step strm = do - r <- liftIO $ readDirStreamEither f strm - case r of - Nothing -> return Stop - Just x -> return $ Yield x strm - -{-# INLINE streamReader #-} -streamReader :: MonadIO m => Unfold m DirStream Path -streamReader = fmap (either id id) (streamEitherReader id) - --- | Read a directory emitting a stream with names of the children. Filter out --- "." and ".." entries. --- --- /Internal/ - -{-# INLINE reader #-} -reader :: (MonadIO m, MonadCatch m) => Unfold m Path Path -reader = --- XXX Instead of using bracketIO for each iteration of the loop we should --- instead yield a buffer of dir entries in each iteration and then use an --- unfold and concat to flatten those entries. That should improve the --- performance. - UF.bracketIO openDirStream closeDirStream streamReader - --- | Read directories as Left and files as Right. Filter out "." and ".." --- entries. --- --- /Internal/ --- -{-# INLINE eitherReader #-} -eitherReader :: (MonadIO m, MonadCatch m) => - (ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path) -eitherReader f = - -- XXX The measured overhead of bracketIO is not noticeable, if it turns - -- out to be a problem for small filenames we can use getdents64 to use - -- chunked read to avoid the overhead. - UF.bracketIO openDirStream closeDirStream (streamEitherReader f) -#endif diff --git a/core/src/Streamly/Internal/Syscall/Posix.hsc b/core/src/Streamly/Internal/Syscall/Posix.hsc index 6e77c3e6d8..973430ae16 100644 --- a/core/src/Streamly/Internal/Syscall/Posix.hsc +++ b/core/src/Streamly/Internal/Syscall/Posix.hsc @@ -35,11 +35,12 @@ import Foreign.Storable (peekByteOff) import GHC.Base (Addr##) import GHC.Ptr (Ptr(..)) import Streamly.Internal.Data.Array.Type (Array(..)) -import qualified Streamly.Internal.Data.Array as Array import qualified Streamly.Internal.Data.MutByteArray as MutByteArray import Streamly.Internal.Data.MutByteArray.Type (MutByteArray, PinnedState(..), unsafeAsPtr) import Streamly.Internal.Syscall.Common (retry) +import Streamly.Internal.FileSystem.PosixPath (PosixPath) +import qualified Streamly.Internal.FileSystem.PosixPath as Path import System.Posix.Types (CMode) #include @@ -81,9 +82,9 @@ getCwd = do foreign import ccall unsafe "chdir" c_chdir :: CString -> IO CInt -setCwd :: Array Word8 -> IO () -setCwd arr = - Array.asCStringUnsafe arr $ +setCwd :: PosixPath -> IO () +setCwd path = + Path.asCString path $ throwErrnoIfMinus1_ "setCwd" . c_chdir -------------------------------------------------------------------------------- diff --git a/core/src/Streamly/Internal/FileSystem/Posix/Errno.hs b/core/src/Streamly/Internal/Syscall/Posix/Errno.hs similarity index 94% rename from core/src/Streamly/Internal/FileSystem/Posix/Errno.hs rename to core/src/Streamly/Internal/Syscall/Posix/Errno.hs index b4c6ce5166..14d55e5ec5 100644 --- a/core/src/Streamly/Internal/FileSystem/Posix/Errno.hs +++ b/core/src/Streamly/Internal/Syscall/Posix/Errno.hs @@ -1,12 +1,12 @@ -- | --- Module : Streamly.Internal.FileSystem.Posix.Errno +-- Module : Streamly.Internal.Syscall.Posix.Errno -- Copyright : (c) 2024 Composewell Technologies -- -- License : BSD3 -- Maintainer : streamly@composewell.com -- Portability : GHC -module Streamly.Internal.FileSystem.Posix.Errno +module Streamly.Internal.Syscall.Posix.Errno ( #if !defined(mingw32_HOST_OS) && !defined(__MINGW32__) throwErrnoPath diff --git a/core/src/Streamly/Internal/FileSystem/Posix/File.hsc b/core/src/Streamly/Internal/Syscall/Posix/File.hsc similarity index 98% rename from core/src/Streamly/Internal/FileSystem/Posix/File.hsc rename to core/src/Streamly/Internal/Syscall/Posix/File.hsc index e42c5835c6..4399fc7302 100644 --- a/core/src/Streamly/Internal/FileSystem/Posix/File.hsc +++ b/core/src/Streamly/Internal/Syscall/Posix/File.hsc @@ -1,4 +1,4 @@ -module Streamly.Internal.FileSystem.Posix.File +module Streamly.Internal.Syscall.Posix.File ( #if !defined(mingw32_HOST_OS) && !defined(__MINGW32__) @@ -85,7 +85,7 @@ import Foreign.C.Error (throwErrnoIfMinus1_) import Foreign.C.String (CString) import Foreign.C.Types (CInt(..)) import GHC.IO.Handle.FD (fdToHandle) -import Streamly.Internal.FileSystem.Posix.Errno (throwErrnoPathIfMinus1Retry) +import Streamly.Internal.Syscall.Posix.Errno (throwErrnoPathIfMinus1Retry) import Streamly.Internal.FileSystem.PosixPath (PosixPath) import System.IO (IOMode(..), Handle) import System.Posix.Types (Fd(..), CMode(..)) diff --git a/core/src/Streamly/Internal/FileSystem/Posix/ReadDir.hsc b/core/src/Streamly/Internal/Syscall/Posix/ReadDir.hsc similarity index 94% rename from core/src/Streamly/Internal/FileSystem/Posix/ReadDir.hsc rename to core/src/Streamly/Internal/Syscall/Posix/ReadDir.hsc index fb68c9b38a..7c3b7c3658 100644 --- a/core/src/Streamly/Internal/FileSystem/Posix/ReadDir.hsc +++ b/core/src/Streamly/Internal/Syscall/Posix/ReadDir.hsc @@ -1,12 +1,12 @@ -- | --- Module : Streamly.Internal.FileSystem.Posix.ReadDir +-- Module : Streamly.Internal.Syscall.Posix.ReadDir -- Copyright : (c) 2024 Composewell Technologies -- -- License : BSD3 -- Maintainer : streamly@composewell.com -- Portability : GHC -module Streamly.Internal.FileSystem.Posix.ReadDir +module Streamly.Internal.Syscall.Posix.ReadDir ( #if !defined(mingw32_HOST_OS) && !defined(__MINGW32__) readScanWith_ @@ -48,8 +48,8 @@ import Streamly.Internal.Data.Scanl (Scanl) import Streamly.Internal.Data.Stream (Stream(..), Step(..)) import Streamly.Internal.Data.Unfold.Type (Unfold(..)) import Streamly.Internal.FileSystem.Path (Path) -import Streamly.Internal.FileSystem.Posix.Errno (throwErrnoPathIfNullRetry) -import Streamly.Internal.FileSystem.Posix.File +import Streamly.Internal.Syscall.Posix.Errno (throwErrnoPathIfNullRetry) +import Streamly.Internal.Syscall.Posix.File (defaultOpenFlags, openAt, close) import Streamly.Internal.FileSystem.PosixPath (PosixPath(..)) import System.Posix.Types (Fd(..)) @@ -57,7 +57,6 @@ import System.Posix.Types (Fd(..)) import qualified Streamly.Internal.Data.Array as Array import qualified Streamly.Internal.Data.MutByteArray as MutByteArray import qualified Streamly.Internal.Data.Unfold as UF (bracketIO) -import qualified Streamly.Internal.FileSystem.Path.Common as PathC import qualified Streamly.Internal.FileSystem.PosixPath as Path import Streamly.Internal.FileSystem.DirOptions @@ -156,7 +155,7 @@ openDirStreamCString s = do -- {-# INLINE openDirStream #-} openDirStream :: PosixPath -> IO DirStream openDirStream p = - Array.asCStringUnsafe (Path.toArray p) $ \s -> do + Path.asCString p $ \s -> do -- openDirStreamCString s dirp <- throwErrnoPathIfNullRetry "openDirStream" p $ c_opendir s return (DirStream dirp) @@ -214,8 +213,8 @@ statEntryType statEntryType conf parent dname = do -- XXX We can create a pinned array right here since the next call pins -- it anyway. - path <- appendCString parent dname - Array.asCStringUnsafe (Path.toArray path) $ \cStr -> do + path <- Path.appendCString parent dname + Path.asCString path $ \cStr -> do res <- stat (_followSymlinks conf) cStr case res of Right mode -> pure $ @@ -372,12 +371,6 @@ eitherReader confMod = -- chunked read to avoid the overhead. UF.bracketIO before after (streamEitherReader confMod) -{-# INLINE appendCString #-} -appendCString :: PosixPath -> CString -> IO PosixPath -appendCString (PosixPath a) b = do - arr <- PathC.appendCString PathC.Posix a b - pure $ PosixPath arr - {-# ANN type ChunkStreamState Fuse #-} data ChunkStreamState = ChunkStreamInit [PosixPath] [PosixPath] Int [PosixPath] Int @@ -390,10 +383,27 @@ data ChunkStreamState = [PosixPath] -- files buffered Int -- file count --- XXX We can use a fold for collecting files and dirs. --- A fold may be useful to translate the output to whatever format we want, we --- can add a prefix or we can colorize it. The Right output would be the output --- of the fold which can be any type not just a Path. +-- XXX By using a fold we can unify readEitherChunks and readEitherByteChunks. +-- For chunks we need to pass toList as the fold, and for ByteChunks we can +-- pass a Fold m Path (Array Word). The Right output would be the output of the +-- fold which can be any type not just a Path. The fold itself can perform +-- transformations on the path e.g. colorization, filtering. In general we can +-- use a (Path -> Maybe Path) function to do the transformations, for Path +-- stream we can map it over the stream and for Folds we can use it to build +-- the fold. +-- +-- When using a fold compared to directly writing to an Array we have to make +-- one more copy of each Path element to copy it from the readdir buffer +-- (because it will go away) to a Path data type. However, the Path will be +-- quickly written down to the Array while it is still hot in L1 cache, so +-- there should not be much noticeable difference in perf. +-- +-- Things that need to be controlled at the level of readdir and not after +-- should be passed as readdir options e.g. maxDepth could be one such option +-- because readdir has the visibility over it post-filtering would be +-- inefficient. If we have to control it outside readdir we can efficiently +-- control it by controlling the depth of the directories being fed back to +-- concatIterate. -- XXX We can write a two fold scan to buffer and yield whichever fills first -- like foldMany, it would be foldEither. @@ -439,7 +449,7 @@ readEitherChunks confMod alldirs = etype <- liftIO $ getEntryType conf curdir dname dtype case etype of EntryIsDir -> do - path <- liftIO $ appendCString curdir dname + path <- liftIO $ Path.appendCString curdir dname let dirs1 = path : dirs ndirs1 = ndirs + 1 in if ndirs1 >= dirMax @@ -448,7 +458,7 @@ readEitherChunks confMod alldirs = else return $ Skip (ChunkStreamLoop curdir xs dirp dirs1 ndirs1 files nfiles) EntryIsNotDir -> do - path <- liftIO $ appendCString curdir dname + path <- liftIO $ Path.appendCString curdir dname let files1 = path : files nfiles1 = nfiles + 1 in if nfiles1 >= fileMax @@ -476,6 +486,7 @@ foreign import ccall unsafe "string.h memcpy" c_memcpy foreign import ccall unsafe "string.h strlen" c_strlen :: Ptr CChar -> IO CSize +-- XXX Move this to common -- Split a list in half. splitHalf :: [a] -> ([a], [a]) splitHalf xxs = split xxs xxs @@ -690,7 +701,7 @@ readEitherByteChunks confMod alldirs = {-# INLINE handleDirEnt #-} handleDirEnt pos dname = do - path <- liftIO $ appendCString curdir dname + path <- liftIO $ Path.appendCString curdir dname let dirs1 = path : dirs r <- copyToBuf mbarr pos curdir dname case r of diff --git a/core/src/Streamly/Internal/Syscall/Windows.hs b/core/src/Streamly/Internal/Syscall/Windows.hs index 03eead05f3..69db078c0e 100644 --- a/core/src/Streamly/Internal/Syscall/Windows.hs +++ b/core/src/Streamly/Internal/Syscall/Windows.hs @@ -27,6 +27,8 @@ module Streamly.Internal.Syscall.Windows import Control.Monad (when) import Data.Word (Word16) +import Foreign (Ptr) +import Foreign.C (CWchar(..), CWString(..)) import System.Win32.Types (BOOL, DWORD, LPTSTR, UINT, failIfFalse_) import qualified System.Win32 as Win32 (failWith) import Streamly.Internal.Data.Array.Type (Array(..)) @@ -34,10 +36,15 @@ import qualified Streamly.Internal.Data.Array as Array import qualified Streamly.Internal.Data.MutByteArray as MutByteArray import Streamly.Internal.Data.MutByteArray.Type (MutByteArray, PinnedState(..), unsafeAsPtr) +import Streamly.Internal.FileSystem.WindowsPath (WindowsPath) +import qualified Streamly.Internal.FileSystem.WindowsPath as Path import Streamly.Internal.Syscall.Common (retry) -foreign import WINDOWS_CCONV unsafe "GetCurrentDirectoryW" - c_getCurrentDirectory :: DWORD -> LPTSTR -> IO UINT +-- Non-explicit import +import Streamly.Internal.Syscall.Windows.Common + +foreign import WINDOWS_CCONV unsafe "windows.h GetCurrentDirectoryW" + c_getCurrentDirectory :: DWORD -> LPTSTR -> IO DWORD foreign import ccall unsafe "windows.h GetLastError" c_GetLastError :: IO DWORD @@ -66,10 +73,9 @@ getCwd = do mba <- MutByteArray.rightSizeAs Unpinned (len * 2) arr return (Array mba 0 (fromIntegral (len * 2))) -foreign import WINDOWS_CCONV unsafe "SetCurrentDirectoryW" - c_setCurrentDirectory :: LPTSTR -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h SetCurrentDirectoryW" + c_SetCurrentDirectoryW :: LPTSTR -> IO BOOL -setCwd :: Array Word16 -> IO () -setCwd arr = - Array.asCWString arr (failIfFalse_ "setCwd" . c_setCurrentDirectory) +setCwd :: WindowsPath -> IO () +setCwd arr = asCWString arr (failIfFalse_ "setCwd" . c_SetCurrentDirectoryW) #endif diff --git a/core/src/Streamly/Internal/Syscall/Windows/Common.hs b/core/src/Streamly/Internal/Syscall/Windows/Common.hs new file mode 100644 index 0000000000..d5ea57318e --- /dev/null +++ b/core/src/Streamly/Internal/Syscall/Windows/Common.hs @@ -0,0 +1,19 @@ +-- | +-- Module : Streamly.Internal.Syscall.Windows.Common +-- Copyright : (c) 2026 Composewell Technologies +-- License : BSD-3-Clause +-- Maintainer : streamly@composewell.com +-- Portability : GHC + +module Streamly.Internal.Syscall.Windows.Common + ( asCWString + ) +where + +import Foreign.C (CWString) +import Foreign.Ptr (castPtr) +import Streamly.Internal.FileSystem.WindowsPath (WindowsPath) +import qualified Streamly.Internal.FileSystem.WindowsPath as Path + +asCWString :: WindowsPath -> (CWString -> IO a) -> IO a +asCWString p act = Path.asW16CString p $ \ptr -> act (castPtr ptr) diff --git a/core/src/Streamly/Internal/FileSystem/Windows/File.hsc b/core/src/Streamly/Internal/Syscall/Windows/File.hsc similarity index 97% rename from core/src/Streamly/Internal/FileSystem/Windows/File.hsc rename to core/src/Streamly/Internal/Syscall/Windows/File.hsc index 1c61e8ea8a..206c311040 100644 --- a/core/src/Streamly/Internal/FileSystem/Windows/File.hsc +++ b/core/src/Streamly/Internal/Syscall/Windows/File.hsc @@ -1,6 +1,6 @@ -- XXX When introducing platform specifc API, see Posix/File.hsc and design in -- the same consistent way. -module Streamly.Internal.FileSystem.Windows.File +module Streamly.Internal.Syscall.Windows.File ( #if defined(mingw32_HOST_OS) || defined(__MINGW32__) -- * Handle based @@ -33,8 +33,10 @@ import GHC.IO.Handle.FD (fdToHandle') import qualified Streamly.Internal.FileSystem.File.Common as File import qualified Streamly.Internal.FileSystem.WindowsPath as Path +-- Non-explicit imports import Data.Bits import Foreign.Ptr +import Streamly.Internal.Syscall.Windows.Common import System.Win32 as Win32 hiding (createFile, failIfWithRetry) #include @@ -87,7 +89,7 @@ createFile :: -> Maybe Win32.HANDLE -> IO Win32.HANDLE createFile name access share mb_attr mode flag mb_h = - Path.asCWString name $ \c_name -> + asCWString name $ \c_name -> failIfWithRetry (== iNVALID_HANDLE_VALUE) (unwords ["CreateFile", Path.toString name]) diff --git a/core/src/Streamly/Internal/Syscall/Windows/ReadDir.hsc b/core/src/Streamly/Internal/Syscall/Windows/ReadDir.hsc new file mode 100644 index 0000000000..1e7123c921 --- /dev/null +++ b/core/src/Streamly/Internal/Syscall/Windows/ReadDir.hsc @@ -0,0 +1,635 @@ +-- | +-- Module : Streamly.Internal.Syscall.Windows.ReadDir +-- Copyright : (c) 2024 Composewell Technologies +-- +-- License : BSD3 +-- Maintainer : streamly@composewell.com +-- Portability : GHC + +{-# LANGUAGE UnliftedFFITypes #-} + +module Streamly.Internal.Syscall.Windows.ReadDir + ( +#if defined(mingw32_HOST_OS) || defined(__MINGW32__) + DirStream + , openDirStream + , closeDirStream + , readDirStreamEither + , readEitherChunks + , readEitherByteChunks + , eitherReader + , reader +#endif + ) +where + +#if defined(mingw32_HOST_OS) || defined(__MINGW32__) + +import Control.Exception (throwIO) +import Control.Monad (void) +import Control.Monad.Catch (MonadCatch) +import Control.Monad.IO.Class (MonadIO(..)) +import Data.Char (ord, isSpace) +import Data.IORef (IORef, newIORef, readIORef, writeIORef) +import GHC.Base (Addr##) +import Foreign.C + ( CInt(..), CSize(..), CWchar(..), Errno(..) + , errnoToIOError, peekCWString + ) +import Fusion.Plugin.Types (Fuse(..)) +import Numeric (showHex) +import Streamly.Internal.Data.Array (Array(..)) +import Streamly.Internal.Data.MutByteArray (MutByteArray) +import Streamly.Internal.Data.Unfold.Type (Unfold(..)) +import Streamly.Internal.Data.Stream (Stream(..), Step(..)) +import Streamly.Internal.FileSystem.Path (Path) +import Streamly.Internal.FileSystem.WindowsPath (WindowsPath(..)) +import System.IO.Error (ioeSetErrorString) + +import qualified Streamly.Internal.Data.Array as Array +import qualified Streamly.Internal.Data.MutArray as MutArray +import qualified Streamly.Internal.Data.MutByteArray as MutByteArray +import qualified Streamly.Internal.Data.Unfold as UF (bracketIO) +import qualified Streamly.Internal.FileSystem.Path.Common as PathC +import qualified Streamly.Internal.FileSystem.WindowsPath as Path +import qualified System.Win32 as Win32 (failWith) + +import Streamly.Internal.FileSystem.DirOptions +import Streamly.Internal.Syscall.Windows.Common (asCWString) +import Foreign hiding (void) + +#include + +-- Note on A vs W suffix in APIs. +-- CreateFile vs. CreateFileW: CreateFile is a macro that expands to +-- CreateFileA or CreateFileW depending on whether Unicode support (UNICODE and +-- _UNICODE preprocessor macros) is enabled in your project. To ensure +-- consistent Unicode support, explicitly use CreateFileW. + +------------------------------------------------------------------------------ +-- Types +------------------------------------------------------------------------------ + +type BOOL = Bool +type DWORD = Word32 + +type UINT_PTR = Word +type ErrCode = DWORD +type LPCTSTR = Ptr CWchar +type WIN32_FIND_DATA = () +type HANDLE = Ptr () + +------------------------------------------------------------------------------ +-- Windows C APIs +------------------------------------------------------------------------------ + +-- XXX Note for i386, stdcall is needed instead of ccall, see Win32 +-- package/windows_cconv.h. We support only x86_64 for now. +foreign import ccall unsafe "windows.h FindFirstFileW" + c_FindFirstFileW :: LPCTSTR -> Ptr WIN32_FIND_DATA -> IO HANDLE + +foreign import ccall unsafe "windows.h FindNextFileW" + c_FindNextFileW :: HANDLE -> Ptr WIN32_FIND_DATA -> IO BOOL + +foreign import ccall unsafe "windows.h FindClose" + c_FindClose :: HANDLE -> IO BOOL + +foreign import ccall unsafe "windows.h GetLastError" + getLastError :: IO ErrCode + +foreign import ccall unsafe "windows.h LocalFree" + localFree :: Ptr a -> IO (Ptr a) + +------------------------------------------------------------------------------ +-- FFI imports/Haskell C APIs +------------------------------------------------------------------------------ + +foreign import ccall unsafe "string.h memcpy" c_memcpy + :: Ptr Word8 -> Ptr Word8 -> CSize -> IO (Ptr Word8) + +foreign import ccall unsafe "maperrno_func" -- in base/cbits/Win32Utils.c + c_maperrno_func :: ErrCode -> IO Errno + +------------------------------------------------------------------------------ +-- Error Handling +------------------------------------------------------------------------------ + +-- XXX getErrorMessage and castUINTPtrToPtr require c code, so left out for +-- now. Once we replace these we can remove dependency on Win32. We can +-- possibly implement these in Haskell by directly calling the Windows API. + +foreign import ccall unsafe "getErrorMessage" + getErrorMessage :: DWORD -> IO (Ptr CWchar) + +foreign import ccall unsafe "castUINTPtrToPtr" + castUINTPtrToPtr :: UINT_PTR -> Ptr a + +failWith :: String -> ErrCode -> IO a +failWith fn_name err_code = do + c_msg <- getErrorMessage err_code + msg <- if c_msg == nullPtr + then return $ "Error 0x" ++ Numeric.showHex err_code "" + else do + msg <- peekCWString c_msg + -- We ignore failure of freeing c_msg, given we're already failing + _ <- localFree c_msg + return msg + errno <- c_maperrno_func err_code + let msg' = reverse $ dropWhile isSpace $ reverse msg -- drop trailing \n + ioerror = errnoToIOError fn_name errno Nothing Nothing + `ioeSetErrorString` msg' + throwIO ioerror + +errorWin :: String -> IO a +errorWin fn_name = do + err_code <- getLastError + failWith fn_name err_code + +failIf :: (a -> Bool) -> String -> IO a -> IO a +failIf p wh act = do + v <- act + if p v then errorWin wh else return v + +iNVALID_HANDLE_VALUE :: HANDLE +iNVALID_HANDLE_VALUE = castUINTPtrToPtr maxBound + +------------------------------------------------------------------------------ +-- Path string manipulation +------------------------------------------------------------------------------ + +foreign import ccall unsafe "wchar.h wcslen" c_wcslen + :: Ptr CWchar -> IO CSize + +foreign import ccall unsafe "wchar.h wcslen" c_wcslen_pinned + :: Addr## -> IO CSize + +-- This is defined here and not in Path module because wcslen is a platform +-- specific function and uses 32-bit wide chars on posix and 16-bit wide chars +-- on Windows. We cannot have it in WindowsPath module because that module is +-- plaform agnostic and works on Posix as well. +-- +{-# INLINE appendW16CString #-} +appendW16CString :: WindowsPath -> Ptr CWchar -> IO WindowsPath +appendW16CString (WindowsPath arr) str = + fmap WindowsPath + $ PathC.appendCStringWith + MutArray.emptyOf + c_wcslen_pinned + PathC.Windows + arr + (castPtr str) + +------------------------------------------------------------------------------ +-- Dir stream implementation +------------------------------------------------------------------------------ + +-- XXX Define this as data and unpack three fields? +newtype DirStream = + DirStream (HANDLE, IORef Bool, ForeignPtr WIN32_FIND_DATA) + +openDirStream :: WindowsPath -> IO DirStream +openDirStream p = do + let path = Path.unsafeJoin p $ Path.unsafeFromString "*" + fp_finddata <- mallocForeignPtrBytes (# const sizeof(WIN32_FIND_DATAW) ) + withForeignPtr fp_finddata $ \dataPtr -> do + handle <- + asCWString path $ \pathPtr -> do + -- XXX Use getLastError to distinguish the case when no + -- matching file is found. See the doc of FindFirstFileW. + failIf + (== iNVALID_HANDLE_VALUE) + ("FindFirstFileW: " ++ Path.toString path) + $ c_FindFirstFileW pathPtr dataPtr + ref <- newIORef True + return $ DirStream (handle, ref, fp_finddata) + +closeDirStream :: DirStream -> IO () +closeDirStream (DirStream (h, _, _)) = void (c_FindClose h) + +-- XXX Keep this in sync with the isMetaDir function in Posix readdir module. +isMetaDir :: Ptr CWchar -> IO Bool +isMetaDir dname = do + -- XXX Assuming UTF16LE encoding + c1 <- peek dname + if (c1 /= fromIntegral (ord '.')) + then return False + else do + c2 :: Word8 <- peekByteOff dname 1 + if (c2 == 0) + then return True + else if (c2 /= fromIntegral (ord '.')) + then return False + else do + c3 :: Word8 <- peekByteOff dname 2 + if (c3 == 0) + then return True + else return False + +readDirStreamEither :: + (ReadOptions -> ReadOptions) -> + DirStream -> IO (Maybe (Either WindowsPath WindowsPath)) +readDirStreamEither _ (DirStream (h, ref, fdata)) = + withForeignPtr fdata $ \ptr -> do + firstTime <- readIORef ref + if firstTime + then do + writeIORef ref False + processEntry ptr + else findNext ptr + + where + + -- XXX: for a symlink the attribute may have a FILE_ATTRIBUTE_DIRECTORY if + -- the symlink was created as a directory symlink, but it might have + -- changed later. To find the real type of the symlink when we have + -- followSymlinks option on we need to check if it is a + -- FILE_ATTRIBUTE_REPARSE_POINT, we need to open the reparse point and find + -- the type. + + processEntry ptr = do + let dname = #{ptr WIN32_FIND_DATAW, cFileName} ptr + dattrs :: #{type DWORD} <- + #{peek WIN32_FIND_DATAW, dwFileAttributes} ptr + name <- Array.fromW16CString dname + if (dattrs .&. (#const FILE_ATTRIBUTE_DIRECTORY) /= 0) + then do + isMeta <- isMetaDir dname + if isMeta + then findNext ptr + else return (Just (Left (Path.unsafeFromArray name))) + else return (Just (Right (Path.unsafeFromArray name))) + + findNext ptr = do + retval <- liftIO $ c_FindNextFileW h ptr + if (retval) + then processEntry ptr + else do + err <- getLastError + if err == (# const ERROR_NO_MORE_FILES ) + then return Nothing + -- XXX Print the path in the error message + else Win32.failWith "findNextFile" err + +{-# INLINE streamEitherReader #-} +streamEitherReader :: MonadIO m => + (ReadOptions -> ReadOptions) -> + Unfold m DirStream (Either Path Path) +streamEitherReader f = Unfold step return + where + + step strm = do + r <- liftIO $ readDirStreamEither f strm + case r of + Nothing -> return Stop + Just x -> return $ Yield x strm + +{-# INLINE streamReader #-} +streamReader :: MonadIO m => Unfold m DirStream Path +streamReader = fmap (either id id) (streamEitherReader id) + +-- | Read a directory emitting a stream with names of the children. Filter out +-- "." and ".." entries. +-- +-- /Internal/ + +{-# INLINE reader #-} +reader :: (MonadIO m, MonadCatch m) => Unfold m Path Path +reader = +-- XXX Instead of using bracketIO for each iteration of the loop we should +-- instead yield a buffer of dir entries in each iteration and then use an +-- unfold and concat to flatten those entries. That should improve the +-- performance. + UF.bracketIO openDirStream closeDirStream streamReader + +-- | Read directories as Left and files as Right. Filter out "." and ".." +-- entries. +-- +-- /Internal/ +-- +{-# INLINE eitherReader #-} +eitherReader :: (MonadIO m, MonadCatch m) => + (ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path) +eitherReader f = + -- XXX The measured overhead of bracketIO is not noticeable, if it turns + -- out to be a problem for small filenames we can use getdents64 to use + -- chunked read to avoid the overhead. + UF.bracketIO openDirStream closeDirStream (streamEitherReader f) + +------------------------------------------------------------------------------ +-- Chunked path-list reads +------------------------------------------------------------------------------ + +{-# ANN type ChunkStreamState Fuse #-} +data ChunkStreamState = + ChunkStreamInit [WindowsPath] [WindowsPath] Int [WindowsPath] Int + | ChunkStreamLoop + WindowsPath -- current dir path + [WindowsPath] -- remaining dirs + DirStream -- current dir stream + [WindowsPath] -- dirs buffered + Int -- dir count + [WindowsPath] -- files buffered + Int -- file count + +-- | Like 'readEitherByteChunks' but yields lists of 'WindowsPath' instead of +-- byte buffers. Directories are emitted as 'Left' and files as 'Right'. Meta +-- entries (\".\" and \"..\") are filtered out. +{-# INLINE readEitherChunks #-} +readEitherChunks + :: MonadIO m + => (ReadOptions -> ReadOptions) + -> [WindowsPath] -> Stream m (Either [WindowsPath] [WindowsPath]) +readEitherChunks _confMod alldirs = + Stream step (ChunkStreamInit alldirs [] 0 [] 0) + + where + + -- We want to keep the dir batching as low as possible for better + -- concurrency esp when the number of dirs is low. + dirMax = 4 + fileMax = 1000 + + -- Returns Just (dname, dattrs) on success, Nothing at end of stream. + readNextEntry (DirStream (h, ref, fdata)) = + withForeignPtr fdata $ \ptr -> do + firstTime <- readIORef ref + success <- + if firstTime + then writeIORef ref False >> return True + else c_FindNextFileW h ptr + if success + then do + let dname = #{ptr WIN32_FIND_DATAW, cFileName} ptr + dattrs :: #{type DWORD} <- + #{peek WIN32_FIND_DATAW, dwFileAttributes} ptr + return (Just (dname, dattrs)) + else do + err <- getLastError + if err == (# const ERROR_NO_MORE_FILES ) + then return Nothing + else Win32.failWith "findNextFile" err + + step _ (ChunkStreamInit (x:xs) dirs ndirs files nfiles) = do + ds <- liftIO $ openDirStream x + return $ Skip (ChunkStreamLoop x xs ds dirs ndirs files nfiles) + + step _ (ChunkStreamInit [] [] _ [] _) = + return Stop + + step _ (ChunkStreamInit [] [] _ files _) = + return $ Yield (Right files) (ChunkStreamInit [] [] 0 [] 0) + + step _ (ChunkStreamInit [] dirs _ files _) = + return $ Yield (Left dirs) (ChunkStreamInit [] [] 0 files 0) + + step _ st@(ChunkStreamLoop curdir xs ds dirs ndirs files nfiles) = do + r <- liftIO $ readNextEntry ds + case r of + Just (dname, dattrs) -> + if (dattrs .&. (#const FILE_ATTRIBUTE_DIRECTORY) /= 0) + then do + isMeta <- liftIO $ isMetaDir dname + if isMeta + then return $ Skip st + else do + path <- liftIO $ appendW16CString curdir dname + let dirs1 = path : dirs + ndirs1 = ndirs + 1 + if ndirs1 >= dirMax + then return $ Yield (Left dirs1) + (ChunkStreamLoop curdir xs ds [] 0 files nfiles) + else return $ Skip + (ChunkStreamLoop + curdir xs ds dirs1 ndirs1 files nfiles) + else do + path <- liftIO $ appendW16CString curdir dname + let files1 = path : files + nfiles1 = nfiles + 1 + if nfiles1 >= fileMax + then return $ Yield (Right files1) + (ChunkStreamLoop curdir xs ds dirs ndirs [] 0) + else return $ Skip + (ChunkStreamLoop + curdir xs ds dirs ndirs files1 nfiles1) + Nothing -> do + -- XXX Exception safety + liftIO $ closeDirStream ds + return $ Skip (ChunkStreamInit xs dirs ndirs files nfiles) + +------------------------------------------------------------------------------ +-- Chunked byte-buffered reads +------------------------------------------------------------------------------ + +-- Split a list in half. +splitHalf :: [a] -> ([a], [a]) +splitHalf xxs = split xxs xxs + + where + + split (x:xs) (_:_:ys) = + let (f, s) = split xs ys + in (x:f, s) + split xs _ = ([], xs) + +{-# ANN type ChunkStreamByteState Fuse #-} +data ChunkStreamByteState = + ChunkStreamByteInit + | ChunkStreamByteStop + | ChunkStreamByteLoop + WindowsPath -- current dir path + [WindowsPath] -- remaining dirs + DirStream -- current dir stream + MutByteArray + Int + | ChunkStreamReallocBuf + (Ptr CWchar) -- pending item name + WindowsPath -- current dir path + [WindowsPath] -- remaining dirs + DirStream -- current dir stream + MutByteArray + Int + | ChunkStreamDrainBuf + MutByteArray + Int + +-- NOTE: Unlike posix on Windows the file attribute to determine whether it is +-- a directory or not is always available so we do not need the code to handle +-- the case when they are not available, on Posix we need to use stat +-- explicitly in that case. + +-- | This function may not traverse all the directories supplied and it may +-- traverse the directories recursively. Left contains those directories that +-- were not traversed by this function, these may be the directories that were +-- supplied as input as well as newly discovered directories during traversal. +-- To traverse the entire tree we have to iterate this function on the Left +-- output. +-- +-- Right is a buffer containing UTF-16LE encoded directories and files +-- separated by newlines, with the parent path joined to each child name by a +-- backslash. +-- +{-# INLINE readEitherByteChunks #-} +readEitherByteChunks :: MonadIO m => + (ReadOptions -> ReadOptions) -> + [WindowsPath] -> Stream m (Either [WindowsPath] (Array Word8)) +readEitherByteChunks _confMod alldirs = + Stream step ChunkStreamByteInit + + where + + bufSize = 32000 + + -- The output is UTF-16LE encoded. The format per entry is: + -- dirPath ++ '\\' ++ name ++ '\n', where each character occupies 2 bytes. + copyToBuf dstArr pos dirPath name = do + nameLen <- fmap ((* 2) . fromIntegral) (liftIO $ c_wcslen name) + MutByteArray.unsafeAsPtr dstArr (\ptr -> liftIO $ do + let WindowsPath (Array dirArr start end) = dirPath + dirLen = end - start + endDir = pos + dirLen + -- separator (2 bytes) + newline (2 bytes) + endPos = endDir + nameLen + 4 + sepOff = ptr `plusPtr` endDir + nameOff = sepOff `plusPtr` 2 + nlOff = nameOff `plusPtr` nameLen + if (endPos < bufSize) + then do + MutByteArray.unsafePutSlice dirArr start dstArr pos dirLen + -- '\\' as UTF-16LE: 0x5C 0x00 + poke sepOff (92 :: Word8) + poke (sepOff `plusPtr` 1) (0 :: Word8) + _ <- c_memcpy nameOff (castPtr name) (fromIntegral nameLen) + -- '\n' as UTF-16LE: 0x0A 0x00 + poke nlOff (10 :: Word8) + poke (nlOff `plusPtr` 1) (0 :: Word8) + return (Just endPos) + else return Nothing + ) + + -- Returns Just (dname, dattrs) on success, Nothing at end of stream. The + -- returned dname pointer is valid until the next call to readNextEntry on + -- the same DirStream. + readNextEntry (DirStream (h, ref, fdata)) = + withForeignPtr fdata $ \ptr -> do + firstTime <- readIORef ref + success <- + if firstTime + then writeIORef ref False >> return True + else c_FindNextFileW h ptr + if success + then do + let dname = #{ptr WIN32_FIND_DATAW, cFileName} ptr + dattrs :: #{type DWORD} <- + #{peek WIN32_FIND_DATAW, dwFileAttributes} ptr + return (Just (dname, dattrs)) + else do + err <- getLastError + if err == (# const ERROR_NO_MORE_FILES ) + then return Nothing + else Win32.failWith "findNextFile" err + + step _ ChunkStreamByteInit = do + mbarr <- liftIO $ MutByteArray.new' bufSize + case alldirs of + (x:xs) -> do + ds <- liftIO $ openDirStream x + return $ Skip $ ChunkStreamByteLoop x xs ds mbarr 0 + [] -> return Stop + + step _ ChunkStreamByteStop = return Stop + + step _ (ChunkStreamReallocBuf pending curdir xs ds mbarr pos) = do + mbarr1 <- liftIO $ MutByteArray.new' bufSize + r1 <- copyToBuf mbarr1 0 curdir pending + case r1 of + Just pos2 -> + return $ Yield (Right (Array mbarr 0 pos)) + -- When we come in this state we have emitted dirs + (ChunkStreamByteLoop curdir xs ds mbarr1 pos2) + Nothing -> error "Dirname too big for bufSize" + + step _ (ChunkStreamDrainBuf mbarr pos) = + if pos == 0 + then return Stop + else return $ Yield (Right (Array mbarr 0 pos)) ChunkStreamByteStop + + step _ (ChunkStreamByteLoop icurdir ixs ids mbarr ipos) = + goOuter icurdir ids ixs ipos + + where + + -- This is recursed only when we open the next dir. + -- Encapsulates curdir and ds as static arguments. + goOuter curdir ds = goInner + + where + + -- This is recursed each time we find a dir. + -- Encapsulates dirs as static argument. + goInner dirs = nextEntry + + where + + {-# INLINE nextEntry #-} + nextEntry pos = do + r <- liftIO $ readNextEntry ds + case r of + Just (dname, dattrs) -> + handleDentry pos dname dattrs + Nothing -> handleEnd pos + + handleEnd pos = do + -- XXX Exception safety + liftIO $ closeDirStream ds + openNextDir pos + + openNextDir pos = + case dirs of + (x:xs) -> do + ds1 <- liftIO $ openDirStream x + goOuter x ds1 xs pos + [] -> + if pos == 0 + then return Stop + else return + $ Yield + (Right (Array mbarr 0 pos)) + ChunkStreamByteStop + + splitAndRealloc pos dname xs1 = + case xs1 of + [] -> + return $ Skip + (ChunkStreamReallocBuf dname curdir + [] ds mbarr pos) + _ -> do + let (h,t) = splitHalf xs1 + return $ Yield (Left t) + (ChunkStreamReallocBuf dname curdir + h ds mbarr pos) + + {-# INLINE handleFileEnt #-} + handleFileEnt pos dname = do + r <- copyToBuf mbarr pos curdir dname + case r of + Just pos1 -> nextEntry pos1 + Nothing -> splitAndRealloc pos dname dirs + + {-# INLINE handleDirEnt #-} + handleDirEnt pos dname = do + path <- liftIO $ appendW16CString curdir dname + let dirs1 = path : dirs + r <- copyToBuf mbarr pos curdir dname + case r of + Just pos1 -> goInner dirs1 pos1 + Nothing -> splitAndRealloc pos dname dirs1 + + handleDentry pos dname dattrs = + if (dattrs .&. (#const FILE_ATTRIBUTE_DIRECTORY) /= 0) + then do + isMeta <- liftIO $ isMetaDir dname + if isMeta + then nextEntry pos + else handleDirEnt pos dname + else handleFileEnt pos dname +#endif diff --git a/core/streamly-core.cabal b/core/streamly-core.cabal index d7498c1f98..071b00ab7e 100644 --- a/core/streamly-core.cabal +++ b/core/streamly-core.cabal @@ -413,14 +413,15 @@ library , Streamly.Internal.FileSystem.PathIO , Streamly.Internal.FileSystem.Handle , Streamly.Internal.FileSystem.File.Common - , Streamly.Internal.FileSystem.Posix.Errno - , Streamly.Internal.FileSystem.Posix.File - , Streamly.Internal.FileSystem.Posix.ReadDir + , Streamly.Internal.Syscall.Posix.Errno + , Streamly.Internal.Syscall.Posix.File + , Streamly.Internal.Syscall.Posix.ReadDir , Streamly.Internal.Syscall.Common , Streamly.Internal.Syscall.Posix , Streamly.Internal.Syscall.Windows - , Streamly.Internal.FileSystem.Windows.ReadDir - , Streamly.Internal.FileSystem.Windows.File + , Streamly.Internal.Syscall.Windows.Common + , Streamly.Internal.Syscall.Windows.ReadDir + , Streamly.Internal.Syscall.Windows.File , Streamly.Internal.FileSystem.FileIO , Streamly.Internal.FileSystem.DirIO diff --git a/src/Streamly/Internal/FileSystem/Event/Linux.hs b/src/Streamly/Internal/FileSystem/Event/Linux.hs index 3b0e417b7a..b77dee7fbe 100644 --- a/src/Streamly/Internal/FileSystem/Event/Linux.hs +++ b/src/Streamly/Internal/FileSystem/Event/Linux.hs @@ -167,7 +167,7 @@ import Data.Word (Word8, Word32) import Foreign.C.Error (throwErrnoIfMinus1) import Foreign.C.String (CString) import Foreign.C.Types (CInt(..), CUInt(..)) -import Foreign.Ptr (Ptr) +import Foreign.Ptr (Ptr, castPtr) import Foreign.Storable (peek, peekByteOff, sizeOf) import GHC.IO.Device (IODeviceType(Stream)) import GHC.IO.FD (fdFD, FD(..)) @@ -191,7 +191,7 @@ import qualified Streamly.Unicode.Stream as U import qualified Streamly.Internal.FileSystem.Path as Path import qualified Streamly.Internal.Data.Array as A - ( asCStringUnsafe, unsafePinnedAsPtr + ( asNullTerminatedPtr, unsafePinnedAsPtr , unsafeSliceOffLen, read ) import qualified Streamly.Internal.FileSystem.DirIO as Dir @@ -715,9 +715,9 @@ addToWatch cfg@Config{..} watch0@(Watch handle wdMap) root0 path0 = do -- -- XXX The file may have even got deleted and then recreated which we will -- never get to know, document this. - wd <- A.asCStringUnsafe absPath $ \pathPtr -> + wd <- A.asNullTerminatedPtr absPath $ \pathPtr -> throwErrnoIfMinus1 ("addToWatch: " ++ utf8ToString absPath) $ - c_inotify_add_watch (fdFD fd) pathPtr (CUInt createFlags) + c_inotify_add_watch (fdFD fd) (castPtr pathPtr) (CUInt createFlags) -- We add the parent first so that we start getting events for any new -- creates and add the new subdirectories on creates while we are adding diff --git a/test/Streamly/Test/FileSystem/Handle.hs b/test/Streamly/Test/FileSystem/Handle.hs index 86c817f78b..24105225e5 100644 --- a/test/Streamly/Test/FileSystem/Handle.hs +++ b/test/Streamly/Test/FileSystem/Handle.hs @@ -24,9 +24,9 @@ import System.IO ) import System.IO.Temp (withSystemTempDirectory) #if !defined(mingw32_HOST_OS) && !defined(__MINGW32__) -import Streamly.Internal.FileSystem.Posix.File (openFile, withFile) +import Streamly.Internal.Syscall.Posix.File (openFile, withFile) #else -import Streamly.Internal.FileSystem.Windows.File (openFile, withFile) +import Streamly.Internal.Syscall.Windows.File (openFile, withFile) #endif import Test.QuickCheck (Property, forAll, Gen, vectorOf, choose) import Test.QuickCheck.Monadic (monadicIO, assert, run)