mirror of
https://github.com/dlang/phobos.git
synced 2025-04-29 06:30:28 +03:00
std.file: Fix race condition when mkdirRecurse runs concurrently
mkdirRecurse would crash if a directory was created between its exists() and mkdir() calls. This could occur if mkdirRecurse was called at the same time from mutiple threads or processes. Instead of relying on the exists() check to indicate whether the next mkdir() should succeed, we explicitly ignore "already exists" errors from the OS. Note that this changes the behavior of mkdirRecurse when the leaf directory already existed: previously it would throw, whereas now it will silently succeed. The new behavior is conformant with some other implementations of "recursive mkdir": the -p flag of the GNU tool, Ruby's mkpath, Java's mkdirs, and Perl's make_path. (On the other hand, the old behavior was equivalent to the behavior of Python's makedirs).
This commit is contained in:
parent
293d9c6238
commit
d3f9e24f91
1 changed files with 40 additions and 1 deletions
41
std/file.d
41
std/file.d
|
@ -1366,6 +1366,27 @@ void mkdir(in char[] pathname)
|
|||
}
|
||||
}
|
||||
|
||||
// Same as mkdir but ignores "already exists" errors.
|
||||
// Returns: "true" if the directory was created,
|
||||
// "false" if it already existed.
|
||||
private bool ensureDirExists(in char[] pathname)
|
||||
{
|
||||
version(Windows)
|
||||
{
|
||||
if (CreateDirectoryW(std.utf.toUTF16z(pathname), null))
|
||||
return true;
|
||||
cenforce(GetLastError() == ERROR_ALREADY_EXISTS, pathname.idup);
|
||||
}
|
||||
else version(Posix)
|
||||
{
|
||||
if (core.sys.posix.sys.stat.mkdir(toStringz(pathname), octal!777) == 0)
|
||||
return true;
|
||||
cenforce(errno == EEXIST, pathname);
|
||||
}
|
||||
enforce(pathname.isDir, new FileException(pathname.idup));
|
||||
return false;
|
||||
}
|
||||
|
||||
/****************************************************
|
||||
* Make directory and all parent directories as needed.
|
||||
*
|
||||
|
@ -1388,12 +1409,30 @@ void mkdirRecurse(in char[] pathname)
|
|||
}
|
||||
if (!baseName(pathname).empty)
|
||||
{
|
||||
mkdir(pathname);
|
||||
ensureDirExists(pathname);
|
||||
}
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
{
|
||||
immutable basepath = deleteme ~ "_dir";
|
||||
scope(exit) rmdirRecurse(basepath);
|
||||
|
||||
auto path = buildPath(basepath, "a", "..", "b");
|
||||
mkdirRecurse(path);
|
||||
path = path.buildNormalizedPath;
|
||||
assert(path.isDir);
|
||||
|
||||
path = buildPath(basepath, "c");
|
||||
write(path, "");
|
||||
assertThrown!FileException(mkdirRecurse(path));
|
||||
|
||||
path = buildPath(basepath, "d");
|
||||
mkdirRecurse(path);
|
||||
mkdirRecurse(path); // should not throw
|
||||
}
|
||||
|
||||
// bug3570
|
||||
{
|
||||
immutable basepath = deleteme ~ "_dir";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue