From dbb93d343721e35a976f814cb42bc4562b10957a Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Thu, 13 Mar 2025 02:02:09 +0100 Subject: [PATCH 01/11] Remove range support from `DirIteratorImpl.__ctor` Was unused, untested and potentially broken. --- std/file.d | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/std/file.d b/std/file.d index c30e3a061b7..42c3d826251 100644 --- a/std/file.d +++ b/std/file.d @@ -4797,34 +4797,20 @@ private struct DirIteratorImpl } } - this(R)(R pathname, SpanMode mode, bool followSymlink) - if (isSomeFiniteCharInputRange!R) + this(string pathname, SpanMode mode, bool followSymlink) { + import std.path : absolutePath, isAbsolute; + _mode = mode; _followSymlink = followSymlink; - static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char)) - { - import std.path : absolutePath, isAbsolute; - string pathnameStr; - if (pathname.isAbsolute) - pathnameStr = pathname; - else - { - pathnameStr = pathname.absolutePath; - const offset = (pathnameStr.length - pathname.length); - _pathPrefix = pathnameStr[0 .. offset]; - } - } + string pathnameStr; + if (pathname.isAbsolute) + pathnameStr = pathname; else { - import std.algorithm.searching : count; - import std.array : array; - import std.path : asAbsolutePath; - import std.utf : byChar; - string pathnameStr = pathname.asAbsolutePath.array; - const pathnameCount = pathname.byChar.count; - const offset = (pathnameStr.length - pathnameCount); + pathnameStr = pathname.absolutePath; + const offset = (pathnameStr.length - pathname.length); _pathPrefix = pathnameStr[0 .. offset]; } From a713259b7cbb1a9652ce5d7071e262362ddce7be Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Thu, 13 Mar 2025 02:29:37 +0100 Subject: [PATCH 02/11] Cleanup `DirIteratorImpl.__ctor` --- std/file.d | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/std/file.d b/std/file.d index 42c3d826251..4ad6400cb32 100644 --- a/std/file.d +++ b/std/file.d @@ -4804,17 +4804,17 @@ private struct DirIteratorImpl _mode = mode; _followSymlink = followSymlink; - string pathnameStr; - if (pathname.isAbsolute) - pathnameStr = pathname; - else + if (!pathname.isAbsolute) { - pathnameStr = pathname.absolutePath; - const offset = (pathnameStr.length - pathname.length); - _pathPrefix = pathnameStr[0 .. offset]; + const pathnameRel = pathname; + alias pathnameAbs = pathname; + pathname = pathname.absolutePath; + + const offset = pathnameAbs.length - pathnameRel.length; + _pathPrefix = pathnameAbs[0 .. offset]; } - if (stepIn(pathnameStr)) + if (stepIn(pathname)) { if (_mode == SpanMode.depth) while (mayStepIn()) From 1b7c7cd0b1ad49a426e95e3cf7809ded2fc74cdf Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Thu, 13 Mar 2025 01:32:12 +0100 Subject: [PATCH 03/11] Add `prefix` to `DirEntry` --- std/file.d | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/std/file.d b/std/file.d index 4ad6400cb32..bbdbe720f14 100644 --- a/std/file.d +++ b/std/file.d @@ -3652,13 +3652,25 @@ version (StdDdoc) +/ this(return scope string path); + /++ + Constructs a `DirEntry` for the given file (or directory). + + Params: + path = The file (or directory) to get a DirEntry for. + prefix = A prefix to chomp off the path when querying the name of the DirEntry. + + Throws: + $(LREF FileException) if the file does not exist. + +/ + this(return scope string path, return scope string prefix); + version (Windows) { - private this(string path, in WIN32_FIND_DATAW *fd); + private this(string path, in WIN32_FIND_DATAW *fd, string prefix = null); } else version (Posix) { - private this(string path, core.sys.posix.dirent.dirent* fd); + private this(string path, core.sys.posix.dirent.dirent* fd, string prefix = null); } /++ @@ -3675,6 +3687,7 @@ assert(de2.name == "/usr/share/include"); +/ @property string name() const return scope; + @property string nameWithPrefix() const return scope; /++ Returns whether the file represented by this `DirEntry` is a @@ -3831,13 +3844,21 @@ else version (Windows) } } - private this(string path, WIN32_FIND_DATAW *fd) @trusted + this(return scope string path, return scope string prefix) + { + _prefix = prefix; + this(path); + } + + private this(string path, WIN32_FIND_DATAW *fd, string prefix = null) @trusted { import core.stdc.wchar_ : wcslen; import std.conv : to; import std.datetime.systime : FILETIMEToSysTime; import std.path : buildPath; + _prefix = prefix; + fd.cFileName[$ - 1] = 0; size_t clength = wcslen(&fd.cFileName[0]); @@ -3850,6 +3871,12 @@ else version (Windows) } @property string name() const pure nothrow return scope + { + import std.string : chompPrefix; + return _name.chompPrefix(_prefix); + } + + @property string nameWithPrefix() const pure nothrow return scope { return _name; } @@ -3904,6 +3931,7 @@ else version (Windows) private: string _name; /// The file or directory represented by this DirEntry. + string _prefix; /// A prefix to be chomped off the name (e.g. parent directories of an absolute path). SysTime _timeCreated; /// The time when the file was created. SysTime _timeLastAccessed; /// The time when the file was last accessed. @@ -3933,10 +3961,12 @@ else version (Posix) _dTypeSet = false; } - private this(string path, core.sys.posix.dirent.dirent* fd) @safe + private this(string path, core.sys.posix.dirent.dirent* fd, string prefix = null) @safe { import std.path : buildPath; + _prefix = prefix; + static if (is(typeof(fd.d_namlen))) immutable len = fd.d_namlen; else @@ -3973,6 +4003,12 @@ else version (Posix) } @property string name() const pure nothrow return scope + { + import std.string : chompPrefix; + return _name.chompPrefix(_prefix); + } + + @property string nameWithPrefix() const pure nothrow return scope { return _name; } @@ -4107,6 +4143,7 @@ else version (Posix) } string _name; /// The file or directory represented by this DirEntry. + string _prefix; /// A prefix to be chomped off the name (e.g. parent directories of an absolute path). stat_t _statBuf = void; /// The result of stat(). uint _lstatMode; /// The stat mode from lstat(). From 147bc37283704cc2c5e73459317e8355e017068e Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Thu, 13 Mar 2025 01:33:59 +0100 Subject: [PATCH 04/11] Rename `_prefix` to `_namePrefix` --- std/file.d | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/std/file.d b/std/file.d index bbdbe720f14..875ed130290 100644 --- a/std/file.d +++ b/std/file.d @@ -3846,7 +3846,7 @@ else version (Windows) this(return scope string path, return scope string prefix) { - _prefix = prefix; + _namePrefix = prefix; this(path); } @@ -3857,7 +3857,7 @@ else version (Windows) import std.datetime.systime : FILETIMEToSysTime; import std.path : buildPath; - _prefix = prefix; + _namePrefix = prefix; fd.cFileName[$ - 1] = 0; @@ -3873,7 +3873,7 @@ else version (Windows) @property string name() const pure nothrow return scope { import std.string : chompPrefix; - return _name.chompPrefix(_prefix); + return _name.chompPrefix(_namePrefix); } @property string nameWithPrefix() const pure nothrow return scope @@ -3931,7 +3931,7 @@ else version (Windows) private: string _name; /// The file or directory represented by this DirEntry. - string _prefix; /// A prefix to be chomped off the name (e.g. parent directories of an absolute path). + string _namePrefix; /// A prefix to be chomped off the name (e.g. parent directories of an absolute path). SysTime _timeCreated; /// The time when the file was created. SysTime _timeLastAccessed; /// The time when the file was last accessed. @@ -3965,7 +3965,7 @@ else version (Posix) { import std.path : buildPath; - _prefix = prefix; + _namePrefix = prefix; static if (is(typeof(fd.d_namlen))) immutable len = fd.d_namlen; @@ -4005,7 +4005,7 @@ else version (Posix) @property string name() const pure nothrow return scope { import std.string : chompPrefix; - return _name.chompPrefix(_prefix); + return _name.chompPrefix(_namePrefix); } @property string nameWithPrefix() const pure nothrow return scope @@ -4143,7 +4143,7 @@ else version (Posix) } string _name; /// The file or directory represented by this DirEntry. - string _prefix; /// A prefix to be chomped off the name (e.g. parent directories of an absolute path). + string _namePrefix; /// A prefix to be chomped off the name (e.g. parent directories of an absolute path). stat_t _statBuf = void; /// The result of stat(). uint _lstatMode; /// The stat mode from lstat(). From e55367f9046e67723b2208c7cb59944310ad71d9 Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Thu, 13 Mar 2025 02:00:08 +0100 Subject: [PATCH 05/11] Chomp prefix in `DirEntry` instead of `DirIteratorImpl` --- std/file.d | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/std/file.d b/std/file.d index 875ed130290..bf4fae4d376 100644 --- a/std/file.d +++ b/std/file.d @@ -3687,6 +3687,12 @@ assert(de2.name == "/usr/share/include"); +/ @property string name() const return scope; + /++ + Returns the path to the file represented by this `DirEntry`. + + Unlike `name`, this property returns the internal name as-is, + potentially starting with an unexpected prefix. + +/ @property string nameWithPrefix() const return scope; /++ @@ -3881,6 +3887,11 @@ else version (Windows) return _name; } + private @property string namePrefix() const pure nothrow return scope + { + return _namePrefix; + } + @property bool isDir() const pure nothrow scope { return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; @@ -4013,6 +4024,11 @@ else version (Posix) return _name; } + private @property string namePrefix() const pure nothrow return scope + { + return _namePrefix; + } + @property bool isDir() scope { _ensureStatOrLStatDone(); @@ -4675,7 +4691,7 @@ private struct DirIteratorImpl DirEntry _cur; DirHandle[] _stack; DirEntry[] _stashed; //used in depth first mode - string _pathPrefix = null; + string _namePrefix = null; //stack helpers void pushExtra(DirEntry de) @@ -4733,7 +4749,6 @@ private struct DirIteratorImpl bool toNext(bool fetch, scope WIN32_FIND_DATAW* findinfo) @trusted { import core.stdc.wchar_ : wcscmp; - import std.string : chompPrefix; if (fetch) { @@ -4750,7 +4765,7 @@ private struct DirIteratorImpl popDirStack(); return false; } - _cur = DirEntry(_stack[$-1].dirpath.chompPrefix(_pathPrefix), findinfo); + _cur = DirEntry(_stack[$-1].dirpath, findinfo, _namePrefix); return true; } @@ -4795,8 +4810,6 @@ private struct DirIteratorImpl bool next() @trusted { - import std.string : chompPrefix; - if (_stack.length == 0) return false; @@ -4806,7 +4819,7 @@ private struct DirIteratorImpl if (core.stdc.string.strcmp(&fdata.d_name[0], ".") && core.stdc.string.strcmp(&fdata.d_name[0], "..")) { - _cur = DirEntry(_stack[$-1].dirpath.chompPrefix(_pathPrefix), fdata); + _cur = DirEntry(_stack[$-1].dirpath, fdata, _namePrefix); return true; } } @@ -4848,7 +4861,7 @@ private struct DirIteratorImpl pathname = pathname.absolutePath; const offset = pathnameAbs.length - pathnameRel.length; - _pathPrefix = pathnameAbs[0 .. offset]; + _namePrefix = pathnameAbs[0 .. offset]; } if (stepIn(pathname)) @@ -4857,7 +4870,7 @@ private struct DirIteratorImpl while (mayStepIn()) { auto thisDir = _cur; - if (stepIn(_cur.name)) + if (stepIn(_cur.nameWithPrefix)) { pushExtra(thisDir); } @@ -4887,7 +4900,7 @@ private struct DirIteratorImpl while (mayStepIn()) { auto thisDir = _cur; - if (stepIn(_cur.name)) + if (stepIn(_cur.nameWithPrefix)) { pushExtra(thisDir); } @@ -4901,7 +4914,7 @@ private struct DirIteratorImpl case SpanMode.breadth: if (mayStepIn()) { - if (!stepIn(_cur.name)) + if (!stepIn(_cur.nameWithPrefix)) while (!empty && !next()){} } else From 77aaea0bbb507a1234921dc3e75c59df0e54c557 Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Thu, 13 Mar 2025 04:18:11 +0100 Subject: [PATCH 06/11] Add unittest --- std/file.d | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/std/file.d b/std/file.d index bf4fae4d376..101c3a4de7d 100644 --- a/std/file.d +++ b/std/file.d @@ -5163,15 +5163,41 @@ auto dirEntries(bool useDIP1000 = dip1000Enabled) // https://issues.dlang.org/show_bug.cgi?id=15146 dirEntries("", SpanMode.shallow).walkLength(); +} - // https://github.com/dlang/phobos/issues/9584 - string cwd = getcwd(); - foreach (string entry; dirEntries(testdir, SpanMode.shallow)) - { - if (entry.isDir) - chdir(entry); - } - chdir(cwd); // needed for the directories to be removed +// https://github.com/dlang/phobos/issues/9584 +@safe unittest +{ + import std.path : absolutePath, buildPath; + + string root = deleteme(); + mkdirRecurse(root); + scope (exit) rmdirRecurse(root); + + mkdirRecurse(root.buildPath("1", "2")); + mkdirRecurse(root.buildPath("3", "4")); + mkdirRecurse(root.buildPath("3", "5", "6")); + + const origWD = getcwd(); + chdir(root); + scope(exit) chdir(origWD); + + // When issue #9584 is triggered, + // one of the `isDir` calls fails with "No such file or directory". + foreach (string entry; ".".dirEntries(SpanMode.shallow)) + { + if (entry.isDir) + { + foreach (string subEntry; entry.dirEntries(SpanMode.shallow)) + { + if (subEntry.isDir) + { + chdir(subEntry.absolutePath); + assert(subEntry.absolutePath); + } + } + } + } } /// Ditto From 24cdc1ba0c66a562b5ee3988343232c01f94012f Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Sun, 23 Mar 2025 07:16:14 +0100 Subject: [PATCH 07/11] Add Phobos v3 note(s) --- std/file.d | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/std/file.d b/std/file.d index 101c3a4de7d..5aee5e698aa 100644 --- a/std/file.d +++ b/std/file.d @@ -3829,6 +3829,11 @@ else version (Windows) { @safe: public: + /+ + Note for Phobos v3: + This has caused user confusion in cases where nested directory trees are interated. + See for details. + +/ alias name this; this(return scope string path) @@ -3958,6 +3963,11 @@ else version (Posix) { @safe: public: + /+ + Note for Phobos v3: + This has caused user confusion in cases where nested directory trees are interated. + See for details. + +/ alias name this; this(return scope string path) From ef0be36e593344493e195c95b3a413d931724fc5 Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Sun, 23 Mar 2025 07:16:35 +0100 Subject: [PATCH 08/11] Rework unittest --- std/file.d | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/std/file.d b/std/file.d index 5aee5e698aa..2c97d851111 100644 --- a/std/file.d +++ b/std/file.d @@ -5192,21 +5192,22 @@ auto dirEntries(bool useDIP1000 = dip1000Enabled) chdir(root); scope(exit) chdir(origWD); - // When issue #9584 is triggered, - // one of the `isDir` calls fails with "No such file or directory". - foreach (string entry; ".".dirEntries(SpanMode.shallow)) + /* + This wouldn't work if `entry` were a `string` – for obvious reasons: + One cannot (reliably) iterate nested directory trees using relative path strings + while changing directories in between. + + The expected error would be something along the lines of: + > Failed to stat file `./3/5': No such file or directory + + See for further details. + */ + foreach (DirEntry entry; ".".dirEntries(SpanMode.shallow)) { if (entry.isDir) - { - foreach (string subEntry; entry.dirEntries(SpanMode.shallow)) - { + foreach (DirEntry subEntry; entry.dirEntries(SpanMode.shallow)) if (subEntry.isDir) - { - chdir(subEntry.absolutePath); - assert(subEntry.absolutePath); - } - } - } + chdir(subEntry.absolutePath); // ← } } From 84fa21ccfcbae9d9ccf624d44f0851c7020dcc22 Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Sun, 23 Mar 2025 09:10:58 +0100 Subject: [PATCH 09/11] Fix iteration of nested directory trees --- std/file.d | 162 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 121 insertions(+), 41 deletions(-) diff --git a/std/file.d b/std/file.d index 2c97d851111..aa509b8cfcf 100644 --- a/std/file.d +++ b/std/file.d @@ -4979,13 +4979,17 @@ alias DirIterator = _DirIterator!dip1000Enabled; operating system / filesystem, and may not follow any particular sorting. Params: + Path = Type of the directory path. + Can be either a `string` or a `DirEntry`. + useDIP1000 = used to instantiate this function separately for code with and without -preview=dip1000 compiler switch, because it affects the ABI of this function. Set automatically - don't touch. path = The directory to iterate over. - If empty, the current directory will be iterated. + If an empty string (or data that implicitly converts to one) is + provided, the current directory will be iterated. pattern = Optional string with wildcards, such as $(RED "*.d"). When present, it is used to filter the @@ -5073,8 +5077,11 @@ scan(""); // For some reason, doing the same alias-to-a-template trick as with DirIterator // does not work here. -auto dirEntries(bool useDIP1000 = dip1000Enabled) - (string path, SpanMode mode, bool followSymlink = true) +// The template constraint is necessary to prevent this overload from matching +// `DirEntry`. Said type has an `alias this` member of type `string`. +auto dirEntries(Path, bool useDIP1000 = dip1000Enabled) + (const Path path, SpanMode mode, bool followSymlink = true) +if (is(Path == string)) { return _DirIterator!useDIP1000(path, mode, followSymlink); } @@ -5175,46 +5182,11 @@ auto dirEntries(bool useDIP1000 = dip1000Enabled) dirEntries("", SpanMode.shallow).walkLength(); } -// https://github.com/dlang/phobos/issues/9584 -@safe unittest -{ - import std.path : absolutePath, buildPath; - - string root = deleteme(); - mkdirRecurse(root); - scope (exit) rmdirRecurse(root); - - mkdirRecurse(root.buildPath("1", "2")); - mkdirRecurse(root.buildPath("3", "4")); - mkdirRecurse(root.buildPath("3", "5", "6")); - - const origWD = getcwd(); - chdir(root); - scope(exit) chdir(origWD); - - /* - This wouldn't work if `entry` were a `string` – for obvious reasons: - One cannot (reliably) iterate nested directory trees using relative path strings - while changing directories in between. - - The expected error would be something along the lines of: - > Failed to stat file `./3/5': No such file or directory - - See for further details. - */ - foreach (DirEntry entry; ".".dirEntries(SpanMode.shallow)) - { - if (entry.isDir) - foreach (DirEntry subEntry; entry.dirEntries(SpanMode.shallow)) - if (subEntry.isDir) - chdir(subEntry.absolutePath); // ← - } -} - /// Ditto -auto dirEntries(bool useDIP1000 = dip1000Enabled) - (string path, string pattern, SpanMode mode, +auto dirEntries(Path, bool useDIP1000 = dip1000Enabled) + (const Path path, string pattern, SpanMode mode, bool followSymlink = true) +if (is(Path == string)) // necessary, see comment on previous overload for details { import std.algorithm.iteration : filter; import std.path : globMatch, baseName; @@ -5334,6 +5306,114 @@ auto dirEntries(bool useDIP1000 = dip1000Enabled) assertThrown!Exception(dirEntries("237f5babd6de21f40915826699582e36", "*.bin", SpanMode.depth)); } +@safe unittest +{ + // This is why all the template constraints on `dirEntries` are necessary. + static assert(isImplicitlyConvertible!(DirEntry, string)); +} + +/// Ditto +auto dirEntries(Path, bool useDIP1000 = dip1000Enabled) + (Path path, SpanMode mode, bool followSymlink = true) +if (isImplicitlyConvertible!(Path, string) && !is(Path == string) && !is(Path == DirEntry)) +{ + return dirEntries!(string, useDIP1000)(path, mode, followSymlink); +} + +/// Ditto +auto dirEntries(Path, bool useDIP1000 = dip1000Enabled) + (Path path, string pattern, SpanMode mode, + bool followSymlink = true) +if (isImplicitlyConvertible!(Path, string) && !is(Path == string) && !is(Path == DirEntry)) +{ + return dirEntries!(string, useDIP1000)( + path, pattern, mode, + followSymlink + ); +} + +@safe unittest +{ + static struct Wrapper + { + string data; + alias data this; + } + + string root = deleteme(); + mkdirRecurse(root); + scope (exit) rmdirRecurse(root); + + auto wrapped = Wrapper(root); + foreach(entry; dirEntries(wrapped, SpanMode.shallow)) {} +} + +/// Ditto +auto dirEntries(Path, bool useDIP1000 = dip1000Enabled) + (Path path, SpanMode mode, bool followSymlink = true) +if (is(Path == DirEntry)) +{ + return dirEntries!(string, useDIP1000)(path.nameWithPrefix, mode, followSymlink); +} + +/// Ditto +auto dirEntries(Path, bool useDIP1000 = dip1000Enabled) + (Path path, string pattern, SpanMode mode, + bool followSymlink = true) +if (is(Path == DirEntry)) +{ + return dirEntries!(string, useDIP1000)( + path.nameWithPrefix, pattern, mode, + followSymlink + ); +} + +// https://github.com/dlang/phobos/issues/9584 +@safe unittest +{ + import std.path : absolutePath, buildPath; + + string root = deleteme(); + mkdirRecurse(root); + scope (exit) rmdirRecurse(root); + + mkdirRecurse(root.buildPath("1", "2")); + mkdirRecurse(root.buildPath("3", "4")); + mkdirRecurse(root.buildPath("3", "5", "6")); + + const origWD = getcwd(); + + /* + This wouldn't work if `entry` were a `string` – for fair reasons: + One cannot (reliably) iterate nested directory trees using relative path strings + while changing directories in between. + + The expected error would be something along the lines of: + > Failed to stat file `./3/5': No such file or directory + + See for further details. + */ + chdir(root); + scope(exit) chdir(origWD); + foreach (DirEntry entry; ".".dirEntries(SpanMode.shallow)) + { + if (entry.isDir) + foreach (DirEntry subEntry; entry.dirEntries(SpanMode.shallow)) + if (subEntry.isDir) + chdir(subEntry.absolutePath); // ← + } + + chdir(root); + scope(exit) chdir(origWD); + foreach (DirEntry entry; ".".dirEntries("*", SpanMode.shallow)) + { + if (entry.isDir) + foreach (DirEntry subEntry; entry.dirEntries("*", SpanMode.shallow)) + if (subEntry.isDir) + chdir(subEntry.absolutePath); // ← + } +} + /** * Reads a file line by line and parses the line into a single value or a * $(REF Tuple, std,typecons) of values depending on the length of `Types`. From ccca78d0ef6dc18298c2d6acec48f8029c49cfec Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Sun, 23 Mar 2025 09:12:05 +0100 Subject: [PATCH 10/11] Remove markers --- std/file.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/file.d b/std/file.d index aa509b8cfcf..4e37e0ba99a 100644 --- a/std/file.d +++ b/std/file.d @@ -5400,7 +5400,7 @@ if (is(Path == DirEntry)) if (entry.isDir) foreach (DirEntry subEntry; entry.dirEntries(SpanMode.shallow)) if (subEntry.isDir) - chdir(subEntry.absolutePath); // ← + chdir(subEntry.absolutePath); } chdir(root); @@ -5410,7 +5410,7 @@ if (is(Path == DirEntry)) if (entry.isDir) foreach (DirEntry subEntry; entry.dirEntries("*", SpanMode.shallow)) if (subEntry.isDir) - chdir(subEntry.absolutePath); // ← + chdir(subEntry.absolutePath); } } From 1f577d203c04c4e0f0659e9958c99cc2478112c4 Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Sun, 23 Mar 2025 09:18:24 +0100 Subject: [PATCH 11/11] Add pitfall warning to documentation --- std/file.d | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/std/file.d b/std/file.d index 4e37e0ba99a..7e48c3b8455 100644 --- a/std/file.d +++ b/std/file.d @@ -4978,6 +4978,11 @@ alias DirIterator = _DirIterator!dip1000Enabled; Note: The order of returned directory entries is as it is provided by the operating system / filesystem, and may not follow any particular sorting. + Pitfall: In cases where a change of the working directory (`chdir`) can occur, + it's recommended that one either uses absolute paths + or avoids converting `DirEntry` structures to `string`. + For further details see $(LINK2 https://github.com/dlang/phobos/issues/9584, #9584 on GitHub). + Params: Path = Type of the directory path. Can be either a `string` or a `DirEntry`.