@@ -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
8080newtype 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.
8299data 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.
90126followSymlinks :: Bool -> ReadOptions -> ReadOptions
91127followSymlinks 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--
104170defaultReadOptions :: ReadOptions
105171defaultReadOptions =
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
0 commit comments