1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4/*!
5 \since 4.3
6 \class QDirIterator
7 \inmodule QtCore
8 \brief The QDirIterator class provides an iterator for directory entrylists.
9
10 You can use QDirIterator to navigate entries of a directory one at a time.
11 It is similar to QDir::entryList() and QDir::entryInfoList(), but because
12 it lists entries one at a time instead of all at once, it scales better
13 and is more suitable for large directories. It also supports listing
14 directory contents recursively, and following symbolic links. Unlike
15 QDir::entryList(), QDirIterator does not support sorting.
16
17 The QDirIterator constructor takes a QDir or a directory as
18 argument. After construction, the iterator is located before the first
19 directory entry. Here's how to iterate over all the entries sequentially:
20
21 \snippet code/src_corelib_io_qdiriterator.cpp 0
22
23 Here's how to find and read all files filtered by name, recursively:
24
25 \snippet code/src_corelib_io_qdiriterator.cpp 1
26
27 The next() and nextFileInfo() functions advance the iterator and return
28 the path or the QFileInfo of the next directory entry. You can also call
29 filePath() or fileInfo() to get the current file path or QFileInfo without
30 first advancing the iterator. The fileName() function returns only the
31 name of the file, similar to how QDir::entryList() works.
32
33 Unlike Qt's container iterators, QDirIterator is uni-directional (i.e.,
34 you cannot iterate directories in reverse order) and does not allow random
35 access.
36
37 \sa QDir, QDir::entryList()
38*/
39
40/*! \enum QDirIterator::IteratorFlag
41
42 This enum describes flags that you can combine to configure the behavior
43 of QDirIterator.
44
45 \value NoIteratorFlags The default value, representing no flags. The
46 iterator will return entries for the assigned path.
47
48 \value Subdirectories List entries inside all subdirectories as well.
49
50 \value FollowSymlinks When combined with Subdirectories, this flag
51 enables iterating through all subdirectories of the assigned path,
52 following all symbolic links. Symbolic link loops (e.g., "link" => "." or
53 "link" => "..") are automatically detected and ignored.
54*/
55
56#include "qdiriterator.h"
57#include "qdir_p.h"
58#include "qabstractfileengine_p.h"
59
60#include <QtCore/qset.h>
61#include <QtCore/qstack.h>
62#include <QtCore/qvariant.h>
63#if QT_CONFIG(regularexpression)
64#include <QtCore/qregularexpression.h>
65#endif
66
67#include <QtCore/private/qfilesystemiterator_p.h>
68#include <QtCore/private/qfilesystementry_p.h>
69#include <QtCore/private/qfilesystemmetadata_p.h>
70#include <QtCore/private/qfilesystemengine_p.h>
71#include <QtCore/private/qfileinfo_p.h>
72#include <QtCore/private/qduplicatetracker_p.h>
73
74#include <memory>
75
76QT_BEGIN_NAMESPACE
77
78using namespace Qt::StringLiterals;
79
80template <class Iterator>
81class QDirIteratorPrivateIteratorStack : public QStack<Iterator *>
82{
83public:
84 ~QDirIteratorPrivateIteratorStack()
85 {
86 qDeleteAll(*this);
87 }
88};
89
90class QDirIteratorPrivate
91{
92public:
93 QDirIteratorPrivate(const QFileSystemEntry &entry, const QStringList &nameFilters,
94 QDir::Filters _filters, QDirIterator::IteratorFlags flags, bool resolveEngine = true);
95
96 void advance();
97
98 bool entryMatches(const QString & fileName, const QFileInfo &fileInfo);
99 void pushDirectory(const QFileInfo &fileInfo);
100 void checkAndPushDirectory(const QFileInfo &);
101 bool matchesFilters(const QString &fileName, const QFileInfo &fi) const;
102
103 std::unique_ptr<QAbstractFileEngine> engine;
104
105 QFileSystemEntry dirEntry;
106 const QStringList nameFilters;
107 const QDir::Filters filters;
108 const QDirIterator::IteratorFlags iteratorFlags;
109
110#if QT_CONFIG(regularexpression)
111 QList<QRegularExpression> nameRegExps;
112#endif
113
114 QDirIteratorPrivateIteratorStack<QAbstractFileEngineIterator> fileEngineIterators;
115#ifndef QT_NO_FILESYSTEMITERATOR
116 QDirIteratorPrivateIteratorStack<QFileSystemIterator> nativeIterators;
117#endif
118
119 QFileInfo currentFileInfo;
120 QFileInfo nextFileInfo;
121
122 // Loop protection
123 QDuplicateTracker<QString> visitedLinks;
124};
125
126/*!
127 \internal
128*/
129QDirIteratorPrivate::QDirIteratorPrivate(const QFileSystemEntry &entry, const QStringList &nameFilters,
130 QDir::Filters _filters, QDirIterator::IteratorFlags flags, bool resolveEngine)
131 : dirEntry(entry)
132 , nameFilters(nameFilters.contains(str: "*"_L1) ? QStringList() : nameFilters)
133 , filters(QDir::NoFilter == _filters ? QDir::AllEntries : _filters)
134 , iteratorFlags(flags)
135{
136#if QT_CONFIG(regularexpression)
137 nameRegExps.reserve(asize: nameFilters.size());
138 for (const auto &filter : nameFilters) {
139 auto re = QRegularExpression::fromWildcard(pattern: filter, cs: (filters & QDir::CaseSensitive ?
140 Qt::CaseSensitive : Qt::CaseInsensitive));
141 nameRegExps.append(t: re);
142 }
143#endif
144 QFileSystemMetaData metaData;
145 if (resolveEngine)
146 engine.reset(p: QFileSystemEngine::resolveEntryAndCreateLegacyEngine(entry&: dirEntry, data&: metaData));
147 QFileInfo fileInfo(new QFileInfoPrivate(dirEntry, metaData));
148
149 // Populate fields for hasNext() and next()
150 pushDirectory(fileInfo);
151 advance();
152}
153
154/*!
155 \internal
156*/
157void QDirIteratorPrivate::pushDirectory(const QFileInfo &fileInfo)
158{
159 QString path = fileInfo.filePath();
160
161#ifdef Q_OS_WIN
162 if (fileInfo.isSymLink())
163 path = fileInfo.canonicalFilePath();
164#endif
165
166 if ((iteratorFlags & QDirIterator::FollowSymlinks)) {
167 // Stop link loops
168 if (visitedLinks.hasSeen(s: fileInfo.canonicalFilePath()))
169 return;
170 }
171
172 if (engine) {
173 engine->setFileName(path);
174 QAbstractFileEngineIterator *it = engine->beginEntryList(filters, filterNames: nameFilters);
175 if (it) {
176 it->setPath(path);
177 fileEngineIterators << it;
178 } else {
179 // No iterator; no entry list.
180 }
181 } else {
182#ifndef QT_NO_FILESYSTEMITERATOR
183 QFileSystemIterator *it = new QFileSystemIterator(fileInfo.d_ptr->fileEntry,
184 filters, nameFilters, iteratorFlags);
185 nativeIterators << it;
186#else
187 qWarning("Qt was built with -no-feature-filesystemiterator: no files/plugins will be found!");
188#endif
189 }
190}
191
192inline bool QDirIteratorPrivate::entryMatches(const QString & fileName, const QFileInfo &fileInfo)
193{
194 checkAndPushDirectory(fileInfo);
195
196 if (matchesFilters(fileName, fi: fileInfo)) {
197 currentFileInfo = nextFileInfo;
198 nextFileInfo = fileInfo;
199
200 //We found a matching entry.
201 return true;
202 }
203
204 return false;
205}
206
207/*!
208 \internal
209*/
210void QDirIteratorPrivate::advance()
211{
212 if (engine) {
213 while (!fileEngineIterators.isEmpty()) {
214 // Find the next valid iterator that matches the filters.
215 QAbstractFileEngineIterator *it;
216 while (it = fileEngineIterators.top(), it->hasNext()) {
217 it->next();
218 if (entryMatches(fileName: it->currentFileName(), fileInfo: it->currentFileInfo()))
219 return;
220 }
221
222 fileEngineIterators.pop();
223 delete it;
224 }
225 } else {
226#ifndef QT_NO_FILESYSTEMITERATOR
227 QFileSystemEntry nextEntry;
228 QFileSystemMetaData nextMetaData;
229
230 while (!nativeIterators.isEmpty()) {
231 // Find the next valid iterator that matches the filters.
232 QFileSystemIterator *it;
233 while (it = nativeIterators.top(), it->advance(fileEntry&: nextEntry, metaData&: nextMetaData)) {
234 QFileInfo info(new QFileInfoPrivate(nextEntry, nextMetaData));
235
236 if (entryMatches(fileName: nextEntry.fileName(), fileInfo: info))
237 return;
238 nextMetaData = QFileSystemMetaData();
239 }
240
241 nativeIterators.pop();
242 delete it;
243 }
244#endif
245 }
246
247 currentFileInfo = nextFileInfo;
248 nextFileInfo = QFileInfo();
249}
250
251/*!
252 \internal
253 */
254void QDirIteratorPrivate::checkAndPushDirectory(const QFileInfo &fileInfo)
255{
256 // If we're doing flat iteration, we're done.
257 if (!(iteratorFlags & QDirIterator::Subdirectories))
258 return;
259
260 // Never follow non-directory entries
261 if (!fileInfo.isDir())
262 return;
263
264 // Follow symlinks only when asked
265 if (!(iteratorFlags & QDirIterator::FollowSymlinks) && fileInfo.isSymLink())
266 return;
267
268 // Never follow . and ..
269 QString fileName = fileInfo.fileName();
270 if ("."_L1 == fileName || ".."_L1 == fileName)
271 return;
272
273 // No hidden directories unless requested
274 if (!(filters & QDir::AllDirs) && !(filters & QDir::Hidden) && fileInfo.isHidden())
275 return;
276
277 pushDirectory(fileInfo);
278}
279
280/*!
281 \internal
282
283 This convenience function implements the iterator's filtering logics and
284 applies then to the current directory entry.
285
286 It returns \c true if the current entry matches the filters (i.e., the
287 current entry will be returned as part of the directory iteration);
288 otherwise, false is returned.
289*/
290
291bool QDirIteratorPrivate::matchesFilters(const QString &fileName, const QFileInfo &fi) const
292{
293 if (fileName.isEmpty())
294 return false;
295
296 // filter . and ..?
297 const qsizetype fileNameSize = fileName.size();
298 const bool dotOrDotDot = fileName[0] == u'.'
299 && ((fileNameSize == 1)
300 ||(fileNameSize == 2 && fileName[1] == u'.'));
301 if ((filters & QDir::NoDot) && dotOrDotDot && fileNameSize == 1)
302 return false;
303 if ((filters & QDir::NoDotDot) && dotOrDotDot && fileNameSize == 2)
304 return false;
305
306 // name filter
307#if QT_CONFIG(regularexpression)
308 // Pass all entries through name filters, except dirs if the AllDirs
309 if (!nameFilters.isEmpty() && !((filters & QDir::AllDirs) && fi.isDir())) {
310 bool matched = false;
311 for (const auto &re : nameRegExps) {
312 if (re.match(subject: fileName).hasMatch()) {
313 matched = true;
314 break;
315 }
316 }
317 if (!matched)
318 return false;
319 }
320#endif
321 // skip symlinks
322 const bool skipSymlinks = filters.testAnyFlag(flag: QDir::NoSymLinks);
323 const bool includeSystem = filters.testAnyFlag(flag: QDir::System);
324 if (skipSymlinks && fi.isSymLink()) {
325 // The only reason to save this file is if it is a broken link and we are requesting system files.
326 if (!includeSystem || fi.exists())
327 return false;
328 }
329
330 // filter hidden
331 const bool includeHidden = filters.testAnyFlag(flag: QDir::Hidden);
332 if (!includeHidden && !dotOrDotDot && fi.isHidden())
333 return false;
334
335 // filter system files
336 if (!includeSystem && (!(fi.isFile() || fi.isDir() || fi.isSymLink())
337 || (!fi.exists() && fi.isSymLink())))
338 return false;
339
340 // skip directories
341 const bool skipDirs = !(filters & (QDir::Dirs | QDir::AllDirs));
342 if (skipDirs && fi.isDir())
343 return false;
344
345 // skip files
346 const bool skipFiles = !(filters & QDir::Files);
347 if (skipFiles && fi.isFile())
348 // Basically we need a reason not to exclude this file otherwise we just eliminate it.
349 return false;
350
351 // filter permissions
352 const bool filterPermissions = ((filters & QDir::PermissionMask)
353 && (filters & QDir::PermissionMask) != QDir::PermissionMask);
354 const bool doWritable = !filterPermissions || (filters & QDir::Writable);
355 const bool doExecutable = !filterPermissions || (filters & QDir::Executable);
356 const bool doReadable = !filterPermissions || (filters & QDir::Readable);
357 if (filterPermissions
358 && ((doReadable && !fi.isReadable())
359 || (doWritable && !fi.isWritable())
360 || (doExecutable && !fi.isExecutable()))) {
361 return false;
362 }
363
364 return true;
365}
366
367/*!
368 Constructs a QDirIterator that can iterate over \a dir's entrylist, using
369 \a dir's name filters and regular filters. You can pass options via \a
370 flags to decide how the directory should be iterated.
371
372 By default, \a flags is NoIteratorFlags, which provides the same behavior
373 as in QDir::entryList().
374
375 The sorting in \a dir is ignored.
376
377 \note To list symlinks that point to non existing files, QDir::System must be
378 passed to the flags.
379
380 \sa hasNext(), next(), IteratorFlags
381*/
382QDirIterator::QDirIterator(const QDir &dir, IteratorFlags flags)
383{
384 const QDirPrivate *other = dir.d_ptr.constData();
385 d.reset(other: new QDirIteratorPrivate(other->dirEntry, other->nameFilters, other->filters, flags, bool(other->fileEngine)));
386}
387
388/*!
389 Constructs a QDirIterator that can iterate over \a path, with no name
390 filtering and \a filters for entry filtering. You can pass options via \a
391 flags to decide how the directory should be iterated.
392
393 By default, \a filters is QDir::NoFilter, and \a flags is NoIteratorFlags,
394 which provides the same behavior as in QDir::entryList().
395
396 \note To list symlinks that point to non existing files, QDir::System must be
397 passed to the flags.
398
399 \sa hasNext(), next(), IteratorFlags
400*/
401QDirIterator::QDirIterator(const QString &path, QDir::Filters filters, IteratorFlags flags)
402 : d(new QDirIteratorPrivate(QFileSystemEntry(path), QStringList(), filters, flags))
403{
404}
405
406/*!
407 Constructs a QDirIterator that can iterate over \a path. You can pass
408 options via \a flags to decide how the directory should be iterated.
409
410 By default, \a flags is NoIteratorFlags, which provides the same behavior
411 as in QDir::entryList().
412
413 \note To list symlinks that point to non existing files, QDir::System must be
414 passed to the flags.
415
416 \sa hasNext(), next(), IteratorFlags
417*/
418QDirIterator::QDirIterator(const QString &path, IteratorFlags flags)
419 : d(new QDirIteratorPrivate(QFileSystemEntry(path), QStringList(), QDir::NoFilter, flags))
420{
421}
422
423/*!
424 Constructs a QDirIterator that can iterate over \a path, using \a
425 nameFilters and \a filters. You can pass options via \a flags to decide
426 how the directory should be iterated.
427
428 By default, \a flags is NoIteratorFlags, which provides the same behavior
429 as QDir::entryList().
430
431 For example, the following iterator could be used to iterate over audio
432 files:
433
434 \snippet code/src_corelib_io_qdiriterator.cpp 2
435
436 \note To list symlinks that point to non existing files, QDir::System must be
437 passed to the flags.
438
439 \sa hasNext(), next(), IteratorFlags, QDir::setNameFilters()
440*/
441QDirIterator::QDirIterator(const QString &path, const QStringList &nameFilters,
442 QDir::Filters filters, IteratorFlags flags)
443 : d(new QDirIteratorPrivate(QFileSystemEntry(path), nameFilters, filters, flags))
444{
445}
446
447/*!
448 Destroys the QDirIterator.
449*/
450QDirIterator::~QDirIterator()
451{
452}
453
454/*!
455 Advances the iterator to the next entry, and returns the file path of this
456 new entry. If hasNext() returns \c false, this function does nothing, and
457 returns an empty QString.
458
459 You can call fileName() or filePath() to get the current entry's file name
460 or path, or fileInfo() to get a QFileInfo for the current entry.
461
462 Call nextFileInfo() instead of next() if you're interested in the QFileInfo.
463
464 \sa hasNext(), nextFileInfo(), fileName(), filePath(), fileInfo()
465*/
466QString QDirIterator::next()
467{
468 d->advance();
469 return filePath();
470}
471
472/*!
473 \since 6.3
474
475 Advances the iterator to the next entry, and returns the file info of this
476 new entry. If hasNext() returns \c false, this function does nothing, and
477 returns an empty QFileInfo.
478
479 You can call fileName() or filePath() to get the current entry's file name
480 or path, or fileInfo() to get a QFileInfo for the current entry.
481
482 Call next() instead of nextFileInfo() when all you need is the filePath().
483
484 \sa hasNext(), fileName(), filePath(), fileInfo()
485*/
486QFileInfo QDirIterator::nextFileInfo()
487{
488 d->advance();
489 return fileInfo();
490}
491
492/*!
493 Returns \c true if there is at least one more entry in the directory;
494 otherwise, false is returned.
495
496 \sa next(), nextFileInfo(), fileName(), filePath(), fileInfo()
497*/
498bool QDirIterator::hasNext() const
499{
500 if (d->engine)
501 return !d->fileEngineIterators.isEmpty();
502 else
503#ifndef QT_NO_FILESYSTEMITERATOR
504 return !d->nativeIterators.isEmpty();
505#else
506 return false;
507#endif
508}
509
510/*!
511 Returns the file name for the current directory entry, without the path
512 prepended.
513
514 This function is convenient when iterating a single directory. When using
515 the QDirIterator::Subdirectories flag, you can use filePath() to get the
516 full path.
517
518 \sa filePath(), fileInfo()
519*/
520QString QDirIterator::fileName() const
521{
522 return d->currentFileInfo.fileName();
523}
524
525/*!
526 Returns the full file path for the current directory entry.
527
528 \sa fileInfo(), fileName()
529*/
530QString QDirIterator::filePath() const
531{
532 return d->currentFileInfo.filePath();
533}
534
535/*!
536 Returns a QFileInfo for the current directory entry.
537
538 \sa filePath(), fileName()
539*/
540QFileInfo QDirIterator::fileInfo() const
541{
542 return d->currentFileInfo;
543}
544
545/*!
546 Returns the base directory of the iterator.
547*/
548QString QDirIterator::path() const
549{
550 return d->dirEntry.filePath();
551}
552
553QT_END_NAMESPACE
554

source code of qtbase/src/corelib/io/qdiriterator.cpp