Skip to content

Commit 9145ba3

Browse files
Rename and document ReadOptions for readdir
1 parent 070eccb commit 9145ba3

5 files changed

Lines changed: 102 additions & 29 deletions

File tree

core/src/Streamly/FileSystem/DirIO.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ module Streamly.FileSystem.DirIO
2222
#else
2323
ReadOptions
2424
, followSymlinks
25-
, ignoreNonExisting
26-
, ignoreLoopErrors
27-
, ignoreInAccessible
25+
, ignoreMissing
26+
, ignoreSymlinkLoops
27+
, ignoreInaccessible
2828
#endif
2929
-- * Streams
3030
, read

core/src/Streamly/Internal/FileSystem/DirIO.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ module Streamly.Internal.FileSystem.DirIO
8585
, defaultReadOptions
8686
#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)
8787
, followSymlinks
88-
, ignoreNonExisting
89-
, ignoreLoopErrors
90-
, ignoreInAccessible
88+
, ignoreMissing
89+
, ignoreSymlinkLoops
90+
, ignoreInaccessible
9191
#endif
9292

9393
-- * Streams
@@ -161,7 +161,7 @@ import Streamly.Internal.FileSystem.Windows.ReadDir
161161
#else
162162
import Streamly.Internal.FileSystem.Posix.ReadDir
163163
( readEitherChunks, eitherReader, reader, ReadOptions, defaultReadOptions
164-
, followSymlinks, ignoreNonExisting, ignoreLoopErrors, ignoreInAccessible
164+
, followSymlinks, ignoreMissing, ignoreSymlinkLoops, ignoreInaccessible
165165
)
166166
#endif
167167
import qualified Streamly.Internal.Data.Stream as S

core/src/Streamly/Internal/FileSystem/Posix/ReadDir.hsc

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ module Streamly.Internal.FileSystem.Posix.ReadDir
1111
#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)
1212
ReadOptions
1313
, followSymlinks
14-
, ignoreNonExisting
15-
, ignoreLoopErrors
16-
, ignoreInAccessible
14+
, ignoreMissing
15+
, ignoreSymlinkLoops
16+
, ignoreInaccessible
1717
, defaultReadOptions
1818

1919
, readScanWith_
@@ -79,35 +79,101 @@ data {-# CTYPE "struct stat" #-} CStat
7979

8080
newtype DirStream = DirStream (Ptr CDir)
8181

82+
-- NOTE: If we are following symlinks, then we want to determine the type of
83+
-- the link destination not the link itself, so we need to use stat instead of
84+
-- lstat for resolving the symlink.
85+
--
86+
-- For recursive traversal, instead of classifying the dirents using stat, we
87+
-- can leave them unclassified, and deal with ENOTDIR when doing an opendir. We
88+
-- can just ignore that error if it is not a dir. This way we do not need to do
89+
-- stat at all. Or we can basically say don't try to determine the type of
90+
-- symlinks and always try to read symlinks as dirs. We can have an option for
91+
-- classifying symlinks or DT_UNKNOWN as potential dirs.
92+
93+
-- When resolving a symlink we may encounter errors only if the directory entry
94+
-- is a symlink. If the directory entry is not a symlink then stat on it will
95+
-- have permissions, it will not give ELOOP or ENOENT unless the file was
96+
-- deleted or recreated after we read the dirent.
97+
98+
-- | Options controlling the behavior of directory read.
8299
data ReadOptions =
83100
ReadOptions
84101
{ _followSymlinks :: Bool
85-
, _ignoreSymlinkLoopErrors :: Bool
86-
, _ignoreNonExistingFiles :: Bool
87-
, _ignoreInAccessibleFiles :: Bool
102+
, _ignoreELOOP :: Bool
103+
, _ignoreENOENT :: Bool
104+
, _ignoreEACCESS :: Bool
88105
}
89106

107+
-- | Control how symbolic links are handled when determining the type
108+
-- of a directory entry.
109+
--
110+
-- * If set to 'True', symbolic links are resolved before classification.
111+
-- This means a symlink pointing to a directory will be treated as a
112+
-- directory, and a symlink pointing to a file will be treated as a
113+
-- non-directory.
114+
--
115+
-- * If set to 'False', all symbolic links are classified as non-directories,
116+
-- without attempting to resolve their targets.
117+
--
118+
-- Enabling resolution may cause additional errors to occur due to
119+
-- insufficient permissions, broken links, or symlink loops. Such errors
120+
-- can be ignored or handled using the appropriate options.
121+
--
122+
-- The default is 'False'.
123+
--
124+
-- On Windows this option does nothing as of now, symlinks are not followed to
125+
-- determine the type.
90126
followSymlinks :: Bool -> ReadOptions -> ReadOptions
91127
followSymlinks x opts = opts {_followSymlinks = x}
92128

93-
ignoreLoopErrors :: Bool -> ReadOptions -> ReadOptions
94-
ignoreLoopErrors x opts = opts {_ignoreSymlinkLoopErrors = x}
95-
96-
ignoreNonExisting :: Bool -> ReadOptions -> ReadOptions
97-
ignoreNonExisting x opts = opts {_ignoreNonExistingFiles = x}
98-
99-
ignoreInAccessible :: Bool -> ReadOptions -> ReadOptions
100-
ignoreInAccessible x opts = opts {_ignoreInAccessibleFiles = x}
129+
-- | When the 'followSymlinks' option is enabled and a directory entry is a
130+
-- symbolic link, we resolve it to determine the type of the symlink target.
131+
-- This option controls the behavior when encountering symlink loop errors
132+
-- during resolution.
133+
--
134+
-- When set to 'True', symlink loop errors are ignored, and the type is
135+
-- reported as not a directory. When set to 'False', the directory read
136+
-- operation fails with an error.
137+
--
138+
-- The default is 'False'.
139+
ignoreSymlinkLoops :: Bool -> ReadOptions -> ReadOptions
140+
ignoreSymlinkLoops x opts = opts {_ignoreELOOP = x}
141+
142+
-- | When the 'followSymlinks' option is enabled and a directory entry is a
143+
-- symbolic link, we resolve it to determine the type of the symlink target.
144+
-- This option controls the behavior when encountering broken symlink errors
145+
-- during resolution.
146+
--
147+
-- When set to 'True', broken symlink errors are ignored, and the type is
148+
-- reported as not a directory. When set to 'False', the directory read
149+
-- operation fails with an error.
150+
--
151+
-- The default is 'False'.
152+
ignoreMissing :: Bool -> ReadOptions -> ReadOptions
153+
ignoreMissing x opts = opts {_ignoreENOENT = x}
154+
155+
-- | When the 'followSymlinks' option is enabled and a directory entry is a
156+
-- symbolic link, we resolve it to determine the type of the symlink target.
157+
-- This option controls the behavior when encountering permission errors
158+
-- during resolution.
159+
--
160+
-- When set to 'True', any permission errors are ignored, and the type is
161+
-- reported as not a directory. When set to 'False', the directory read
162+
-- operation fails with an error.
163+
--
164+
-- The default is 'False'.
165+
ignoreInaccessible :: Bool -> ReadOptions -> ReadOptions
166+
ignoreInaccessible x opts = opts {_ignoreEACCESS = x}
101167

102168
-- NOTE: The defaultReadOptions emulate the behaviour of "find".
103169
--
104170
defaultReadOptions :: ReadOptions
105171
defaultReadOptions =
106172
ReadOptions
107173
{ _followSymlinks = False
108-
, _ignoreSymlinkLoopErrors = False
109-
, _ignoreNonExistingFiles = False
110-
, _ignoreInAccessibleFiles = False
174+
, _ignoreELOOP = False
175+
, _ignoreENOENT = False
176+
, _ignoreEACCESS = False
111177
}
112178

113179
-- | Minimal read without any metadata.
@@ -305,13 +371,13 @@ statEntryType conf parent dname = do
305371
else EntryIsNotDir
306372
Left errno -> do
307373
if errno == eNOENT
308-
then unless (_ignoreNonExistingFiles conf) $
374+
then unless (_ignoreENOENT conf) $
309375
throwErrno (errMsg path)
310376
else if errno == eACCES
311-
then unless (_ignoreInAccessibleFiles conf) $
377+
then unless (_ignoreEACCESS conf) $
312378
throwErrno (errMsg path)
313379
else if errno == eLOOP
314-
then unless (_ignoreSymlinkLoopErrors conf) $
380+
then unless (_ignoreELOOP conf) $
315381
throwErrno (errMsg path)
316382
else throwErrno (errMsg path)
317383
pure $ EntryIgnored

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ readDirStreamEither _ (DirStream (h, ref, fdata)) =
206206

207207
where
208208

209+
-- XXX: for a symlink the attribute may have a FILE_ATTRIBUTE_DIRECTORY if
210+
-- the symlink was created as a directory symlink, but it might have
211+
-- changed later. To find the real type of the symlink when we have
212+
-- followSymlinks option on we need to check if it is a
213+
-- FILE_ATTRIBUTE_REPARSE_POINT, we need to open the reparse point and find
214+
-- the type.
215+
209216
processEntry ptr = do
210217
let dname = #{ptr WIN32_FIND_DATAW, cFileName} ptr
211218
dattrs :: #{type DWORD} <-

test/Streamly/Test/FileSystem/DirIO.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,13 @@ testSymLinkFollow = do
118118
testCorrectness
119119
sortedAnswerFollowSym
120120
(listDirUnfoldDfs
121-
(Dir.followSymlinks True . Dir.ignoreNonExisting True)
121+
(Dir.followSymlinks True . Dir.ignoreMissing True)
122122
fp)
123123
it "followSymlinks False" $
124124
testCorrectness
125125
sortedAnswerNoFollowSym
126126
(listDirUnfoldDfs
127-
(Dir.followSymlinks False . Dir.ignoreNonExisting True)
127+
(Dir.followSymlinks False . Dir.ignoreMissing True)
128128
fp)
129129

130130
-- | List the current directory recursively

0 commit comments

Comments
 (0)