Skip to content

Commit 6a26465

Browse files
Fix Windows Syscall module for Path/Array API changes
Cleanup Windows FFI imports Replace unsafeJoin with appendW16CString in Win Readdir
1 parent d06e4e5 commit 6a26465

5 files changed

Lines changed: 78 additions & 50 deletions

File tree

core/src/Streamly/Internal/FileSystem/Path/Common.hs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,7 @@ module Streamly.Internal.FileSystem.Path.Common
5959
, unsafeAppend
6060
, appendCString
6161
, appendCString'
62-
, appendCWString
63-
, appendCWString'
62+
, appendCStringWith
6463
, unsafeJoinPaths
6564
-- , joinRoot -- XXX append should be enough, see joinRootBody
6665

@@ -128,7 +127,7 @@ import Data.Function ((&))
128127
import Data.Functor.Identity (Identity(..))
129128
import Data.Word (Word8, Word16)
130129
import Foreign (castPtr)
131-
import Foreign.C (CString, CSize(..), CWchar, CWString)
130+
import Foreign.C (CString, CSize(..))
132131
import GHC.Base (unsafeChr, Addr#)
133132
import GHC.Ptr (Ptr(..))
134133
import Language.Haskell.TH (Q, Exp)
@@ -1541,19 +1540,6 @@ appendCString' :: OS -> Array Word8 -> CString -> IO (Array Word8)
15411540
appendCString' os arr cstr =
15421541
appendCStringWith MutArray.emptyOf' c_strlen_pinned os arr (castPtr cstr)
15431542

1544-
foreign import ccall unsafe "wchar.h wcslen" c_wcslen_pinned
1545-
:: Addr# -> IO CSize
1546-
1547-
-- | NOTE: CWchar is 16-bit wide on Windows and 32-bit wide on Posix. wcslen is
1548-
-- available on both Posix and Windows and counts accordingly in units of
1549-
-- 2-bytes or 4-bytes.
1550-
{-# INLINE appendCWString #-}
1551-
appendCWString :: OS -> Array CWchar -> CWString -> IO (Array CWchar)
1552-
appendCWString = appendCStringWith MutArray.emptyOf c_wcslen_pinned
1553-
1554-
{-# INLINE appendCWString' #-}
1555-
appendCWString' :: OS -> Array CWchar -> CWString -> IO (Array CWchar)
1556-
appendCWString' = appendCStringWith MutArray.emptyOf' c_wcslen_pinned
15571543

15581544
{-# INLINE doAppend #-}
15591545
doAppend :: (Unbox a, Integral a) => OS -> Array a -> Array a -> Array a

core/src/Streamly/Internal/Syscall/Windows.hs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,24 @@ module Streamly.Internal.Syscall.Windows
2727

2828
import Control.Monad (when)
2929
import Data.Word (Word16)
30+
import Foreign (Ptr)
31+
import Foreign.C (CWchar(..), CWString(..))
3032
import System.Win32.Types (BOOL, DWORD, LPTSTR, UINT, failIfFalse_)
3133
import qualified System.Win32 as Win32 (failWith)
3234
import Streamly.Internal.Data.Array.Type (Array(..))
3335
import qualified Streamly.Internal.Data.Array as Array
3436
import qualified Streamly.Internal.Data.MutByteArray as MutByteArray
3537
import Streamly.Internal.Data.MutByteArray.Type
3638
(MutByteArray, PinnedState(..), unsafeAsPtr)
39+
import Streamly.Internal.FileSystem.WindowsPath (WindowsPath)
40+
import qualified Streamly.Internal.FileSystem.WindowsPath as Path
3741
import Streamly.Internal.Syscall.Common (retry)
3842

39-
foreign import WINDOWS_CCONV unsafe "GetCurrentDirectoryW"
40-
c_getCurrentDirectory :: DWORD -> LPTSTR -> IO UINT
43+
-- Non-explicit import
44+
import Streamly.Internal.Syscall.Windows.Common
45+
46+
foreign import WINDOWS_CCONV unsafe "windows.h GetCurrentDirectoryW"
47+
c_getCurrentDirectory :: DWORD -> LPTSTR -> IO DWORD
4148

4249
foreign import ccall unsafe "windows.h GetLastError"
4350
c_GetLastError :: IO DWORD
@@ -66,10 +73,9 @@ getCwd = do
6673
mba <- MutByteArray.rightSizeAs Unpinned (len * 2) arr
6774
return (Array mba 0 (fromIntegral (len * 2)))
6875

69-
foreign import WINDOWS_CCONV unsafe "SetCurrentDirectoryW"
70-
c_setCurrentDirectory :: LPTSTR -> IO BOOL
76+
foreign import WINDOWS_CCONV unsafe "windows.h SetCurrentDirectoryW"
77+
c_SetCurrentDirectoryW :: LPTSTR -> IO BOOL
7178

72-
setCwd :: Array Word16 -> IO ()
73-
setCwd arr =
74-
Array.asCWString arr (failIfFalse_ "setCwd" . c_setCurrentDirectory)
79+
setCwd :: WindowsPath -> IO ()
80+
setCwd arr = asCWString arr (failIfFalse_ "setCwd" . c_SetCurrentDirectoryW)
7581
#endif
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-- |
2+
-- Module : Streamly.Internal.Syscall.Windows.Common
3+
-- Copyright : (c) 2026 Composewell Technologies
4+
-- License : BSD-3-Clause
5+
-- Maintainer : streamly@composewell.com
6+
-- Portability : GHC
7+
8+
module Streamly.Internal.Syscall.Windows.Common where
9+
10+
import Foreign.C (CWString)
11+
import Foreign.Ptr (castPtr)
12+
import Streamly.Internal.FileSystem.WindowsPath (WindowsPath)
13+
import qualified Streamly.Internal.FileSystem.WindowsPath as Path
14+
15+
asCWString :: WindowsPath -> (CWString -> IO a) -> IO a
16+
asCWString p act = Path.asW16CString p $ \ptr -> act (castPtr ptr)

core/src/Streamly/Internal/Syscall/Windows/File.hsc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ import GHC.IO.Handle.FD (fdToHandle')
3333
import qualified Streamly.Internal.FileSystem.File.Common as File
3434
import qualified Streamly.Internal.FileSystem.WindowsPath as Path
3535

36+
-- Non-explicit imports
3637
import Data.Bits
3738
import Foreign.Ptr
39+
import Streamly.Internal.Syscall.Windows.Common
3840
import System.Win32 as Win32 hiding (createFile, failIfWithRetry)
3941

4042
#include <windows.h>
@@ -87,7 +89,7 @@ createFile ::
8789
-> Maybe Win32.HANDLE
8890
-> IO Win32.HANDLE
8991
createFile name access share mb_attr mode flag mb_h =
90-
Path.asCWString name $ \c_name ->
92+
asCWString name $ \c_name ->
9193
failIfWithRetry
9294
(== iNVALID_HANDLE_VALUE)
9395
(unwords ["CreateFile", Path.toString name])

core/src/Streamly/Internal/Syscall/Windows/ReadDir.hsc

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
-- Maintainer : streamly@composewell.com
77
-- Portability : GHC
88

9+
{-# LANGUAGE UnliftedFFITypes #-}
10+
911
module Streamly.Internal.Syscall.Windows.ReadDir
1012
(
1113
#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
@@ -29,6 +31,7 @@ import Control.Monad.Catch (MonadCatch)
2931
import Control.Monad.IO.Class (MonadIO(..))
3032
import Data.Char (ord, isSpace)
3133
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
34+
import GHC.Base (Addr##)
3235
import Foreign.C
3336
( CInt(..), CSize(..), CWchar(..), Errno(..)
3437
, errnoToIOError, peekCWString
@@ -44,12 +47,15 @@ import Streamly.Internal.FileSystem.WindowsPath (WindowsPath(..))
4447
import System.IO.Error (ioeSetErrorString)
4548

4649
import qualified Streamly.Internal.Data.Array as Array
50+
import qualified Streamly.Internal.Data.MutArray as MutArray
4751
import qualified Streamly.Internal.Data.MutByteArray as MutByteArray
4852
import qualified Streamly.Internal.Data.Unfold as UF (bracketIO)
53+
import qualified Streamly.Internal.FileSystem.Path.Common as PathC
4954
import qualified Streamly.Internal.FileSystem.WindowsPath as Path
5055
import qualified System.Win32 as Win32 (failWith)
5156

5257
import Streamly.Internal.FileSystem.DirOptions
58+
import Streamly.Internal.Syscall.Windows.Common (asCWString)
5359
import Foreign hiding (void)
5460

5561
#include <windows.h>
@@ -95,9 +101,12 @@ foreign import ccall unsafe "windows.h LocalFree"
95101
localFree :: Ptr a -> IO (Ptr a)
96102

97103
------------------------------------------------------------------------------
98-
-- Haskell C APIs
104+
-- FFI imports/Haskell C APIs
99105
------------------------------------------------------------------------------
100106

107+
foreign import ccall unsafe "string.h memcpy" c_memcpy
108+
:: Ptr Word8 -> Ptr Word8 -> CSize -> IO (Ptr Word8)
109+
101110
foreign import ccall unsafe "maperrno_func" -- in base/cbits/Win32Utils.c
102111
c_maperrno_func :: ErrCode -> IO Errno
103112

@@ -144,6 +153,32 @@ failIf p wh act = do
144153
iNVALID_HANDLE_VALUE :: HANDLE
145154
iNVALID_HANDLE_VALUE = castUINTPtrToPtr maxBound
146155

156+
------------------------------------------------------------------------------
157+
-- Path string manipulation
158+
------------------------------------------------------------------------------
159+
160+
foreign import ccall unsafe "wchar.h wcslen" c_wcslen
161+
:: Ptr CWchar -> IO CSize
162+
163+
foreign import ccall unsafe "wchar.h wcslen" c_wcslen_pinned
164+
:: Addr## -> IO CSize
165+
166+
-- This is defined here and not in Path module because wcslen is a platform
167+
-- specific function and uses 32-bit wide chars on posix and 16-bit wide chars
168+
-- on Windows. We cannot have it in WindowsPath module because that module is
169+
-- plaform agnostic and works on Posix as well.
170+
--
171+
{-# INLINE appendW16CString #-}
172+
appendW16CString :: WindowsPath -> Ptr CWchar -> IO WindowsPath
173+
appendW16CString (WindowsPath arr) str =
174+
fmap WindowsPath
175+
$ PathC.appendCStringWith
176+
MutArray.emptyOf
177+
c_wcslen_pinned
178+
PathC.Windows
179+
arr
180+
(castPtr str)
181+
147182
------------------------------------------------------------------------------
148183
-- Dir stream implementation
149184
------------------------------------------------------------------------------
@@ -158,14 +193,13 @@ openDirStream p = do
158193
fp_finddata <- mallocForeignPtrBytes (# const sizeof(WIN32_FIND_DATAW) )
159194
withForeignPtr fp_finddata $ \dataPtr -> do
160195
handle <-
161-
-- XXX should it be asCWString, so we do not need to use castPtr
162-
Array.asCStringUnsafe (Path.toArray path) $ \pathPtr -> do
196+
asCWString path $ \pathPtr -> do
163197
-- XXX Use getLastError to distinguish the case when no
164198
-- matching file is found. See the doc of FindFirstFileW.
165199
failIf
166200
(== iNVALID_HANDLE_VALUE)
167201
("FindFirstFileW: " ++ Path.toString path)
168-
$ c_FindFirstFileW (castPtr pathPtr) dataPtr
202+
$ c_FindFirstFileW pathPtr dataPtr
169203
ref <- newIORef True
170204
return $ DirStream (handle, ref, fp_finddata)
171205

@@ -358,11 +392,8 @@ readEitherChunks _confMod alldirs =
358392
if isMeta
359393
then return $ Skip st
360394
else do
361-
arr <- liftIO $ Array.fromW16CString dname
362-
let path =
363-
Path.unsafeJoin curdir
364-
(Path.unsafeFromArray arr)
365-
dirs1 = path : dirs
395+
path <- liftIO $ appendW16CString curdir dname
396+
let dirs1 = path : dirs
366397
ndirs1 = ndirs + 1
367398
if ndirs1 >= dirMax
368399
then return $ Yield (Left dirs1)
@@ -371,11 +402,8 @@ readEitherChunks _confMod alldirs =
371402
(ChunkStreamLoop
372403
curdir xs ds dirs1 ndirs1 files nfiles)
373404
else do
374-
arr <- liftIO $ Array.fromW16CString dname
375-
let path =
376-
Path.unsafeJoin curdir
377-
(Path.unsafeFromArray arr)
378-
files1 = path : files
405+
path <- liftIO $ appendW16CString curdir dname
406+
let files1 = path : files
379407
nfiles1 = nfiles + 1
380408
if nfiles1 >= fileMax
381409
then return $ Yield (Right files1)
@@ -392,12 +420,6 @@ readEitherChunks _confMod alldirs =
392420
-- Chunked byte-buffered reads
393421
------------------------------------------------------------------------------
394422

395-
foreign import ccall unsafe "string.h memcpy" c_memcpy
396-
:: Ptr Word8 -> Ptr Word8 -> CSize -> IO (Ptr Word8)
397-
398-
foreign import ccall unsafe "wchar.h wcslen" c_wcslen
399-
:: Ptr CWchar -> IO CSize
400-
401423
-- Split a list in half.
402424
splitHalf :: [a] -> ([a], [a])
403425
splitHalf xxs = split xxs xxs
@@ -430,8 +452,6 @@ data ChunkStreamByteState =
430452
MutByteArray
431453
Int
432454

433-
-- TODO: instead of unsafeJoin use appendCWString
434-
--
435455
-- NOTE: Unlike posix on Windows the file attribute to determine whether it is
436456
-- a directory or not is always available so we do not need the code to handle
437457
-- the case when they are not available, on Posix we need to use stat
@@ -597,10 +617,8 @@ readEitherByteChunks _confMod alldirs =
597617

598618
{-# INLINE handleDirEnt #-}
599619
handleDirEnt pos dname = do
600-
arr <- liftIO $ Array.fromW16CString dname
601-
let path =
602-
Path.unsafeJoin curdir (Path.unsafeFromArray arr)
603-
dirs1 = path : dirs
620+
path <- liftIO $ appendW16CString curdir dname
621+
let dirs1 = path : dirs
604622
r <- copyToBuf mbarr pos curdir dname
605623
case r of
606624
Just pos1 -> goInner dirs1 pos1

0 commit comments

Comments
 (0)