std::filesystem Implementation Corner Cases, part 2

2020-02-08 23:30

A question on StackOverflow was pointing at another corner case where the current implementations of std::filesystem differ, it is about the handling of a trailing directory seperator when calling std::filesystem::create_directories().

[See also: std::filesystem Implementation Corner Cases, part 1]


create_directories [fs.op.create_directories]

Given a p with:

auto p = fs::path("a/b/c/");

where there is none of the directories in that path existing in the current directory and we have enough rights to create directories we get:

Clang >9.0.1 (godbolt trunk) and MSVC 19.16/19.23:

FAILED:
  CHECK( fs::create_directories(t.path() / "d/e/f/", ec) )
with expansion:
  false


I made a godbolt example: https://godbolt.org/z/5883uY

#include <filesystem>
#include <iostream>
#include <system_error>

int main() {
    std::error_code ec;
    std::cout << std::boolalpha << std::filesystem::exists("a/b/c/") << "/";
    std::cout << std::filesystem::create_directories("a/b/c/", ec) << "/";
    std::cout << !!ec << "/" << std::filesystem::exists("a/b/c/");
}


GCC:

false/true/false/true

So 1) false, as it doesn’t exists, 2) true, as create_directories() created it, 3) false, as there is no error, and 4) true, the path now exists.


Clang (trunk) (and MSVC as tested via Appveyor):

false/false/false/true

This is 1) false, as it doesn’t exist, 2) false, telling us it didn’t create a directory, 3) false, there was no error, and 4) true, as the path now exists.


The directories are created but the function returns false, while not setting any error condition in ec.

The standard reads: “Returns: true if a new directory was created, otherwise false. The signature with argument ec returns false if an error occurs.”

Clearly there should have been either a return of true or an error in ec. The function did create three directory, but tells us it didn’t.

Analyzing the source of libc++ and MSVC (github) for this, it can be seen, that both create the series of directory creations (libc++ uses recursion, MSVC iteration):

"a"
"a/b"
"a/b/c"
"a/b/c/"

The returned bool is for both the result of the last directory creation, and both return false because the last call does nothing as the last two are the same from the perspective of CreateDirectoryW and ::mkdir.

My tests on Travis using Clang 7, 8 and 9 didn’t show this behaviour, which irritates me a bit.