Skip to content

Commit 0c2d3a4

Browse files
committed
Cleanup abspath to have python-like semantics
1 parent 239bcfe commit 0c2d3a4

3 files changed

Lines changed: 98 additions & 8 deletions

File tree

wobble/sys-test.cc

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <cstdlib>
99
#include <set>
1010
#include <unistd.h>
11+
#include <thread>
1112

1213
using namespace std;
1314
using namespace wobble::sys;
@@ -43,13 +44,52 @@ add_method("isdir", []() {
4344
wassert(actual(isdir("testdir")).istrue());
4445
});
4546

47+
#pragma GCC diagnostic pop
48+
4649
add_method("abspath", []() {
47-
std::string cwd = std::filesystem::current_path().string();
48-
wassert(actual(abspath(".")) == cwd + "/");
49-
wassert(actual(abspath("foo")) == cwd + "/foo");
50-
wassert(actual(abspath("foo/")) == cwd + "/foo/");
50+
auto cwd = std::filesystem::current_path();
51+
wassert(actual(abspath(".")) == cwd);
52+
wassert(actual(abspath("foo")) == cwd / "foo");
53+
wassert(actual(abspath("foo/")) == cwd / "foo/");
54+
});
55+
56+
add_method("abspath_concurrency", []() {
57+
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118733
58+
std::filesystem::path workdir("testdir");
59+
rmtree_ifexists(workdir);
60+
std::filesystem::create_directory(workdir);
61+
62+
auto expected = std::filesystem::absolute(workdir) / "dir";
63+
64+
auto testpath = workdir / "dir";
65+
bool done = false;
66+
67+
auto glitch = [&done, &testpath] {
68+
while (! done)
69+
{
70+
std::filesystem::remove(testpath);
71+
std::filesystem::create_directory(testpath);
72+
}
73+
};
74+
75+
std::thread glitcher(glitch);
76+
77+
int failed_iteration = -1;
78+
std::string failed_message;
79+
for (unsigned iteration = 0; iteration < 1000; ++iteration)
80+
try {
81+
wassert(actual(abspath(testpath)) == expected);
82+
} catch (std::exception& e) {
83+
failed_iteration = iteration;
84+
failed_message = e.what();
85+
break;
86+
}
87+
done = true;
88+
glitcher.join();
89+
90+
if (failed_iteration != -1)
91+
wfail_test("abspath failed at iteration " + std::to_string(failed_iteration) + ": " + failed_message);
5192
});
52-
#pragma GCC diagnostic pop
5393

5494
add_method("timestamp", []() {
5595
using namespace wobble;

wobble/sys.cc

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,57 @@ mode_t umask(mode_t mask)
219219
return ::umask(mask);
220220
}
221221

222-
std::string abspath(const std::string& pathname)
222+
std::filesystem::path abspath(const char* file)
223223
{
224-
return std::filesystem::absolute(pathname).lexically_normal().native();
224+
return abspath(std::filesystem::path(file));
225+
}
226+
std::string abspath(const std::string& path)
227+
{
228+
return abspath(std::filesystem::path(path)).native();
229+
}
230+
231+
std::filesystem::path abspath(const std::filesystem::path& path)
232+
{
233+
// weakly_canonical is defined as "the result of calling canonical() with a
234+
// path argument composed of the leading elements of p that exist (as
235+
// determined by status(p) or status(p, ec)), if any, followed by the
236+
// elements of p that do not exist."
237+
//
238+
// This means that if no lead components of the path exist then the
239+
// resulting path is not made absolute, and we need to work around that.
240+
if (!path.is_absolute())
241+
return abspath(std::filesystem::current_path() / path);
242+
243+
// This is further and needlessly complicated because we need to work
244+
// around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118733
245+
unsigned retry = 0;
246+
while (true)
247+
{
248+
std::error_code code;
249+
auto result = std::filesystem::weakly_canonical(path, code);
250+
if (!code)
251+
{
252+
// fprintf(stderr, "%s: ok in %u tries\n", path.c_str(), retry+1);
253+
return result;
254+
}
255+
256+
if (code == std::errc::no_such_file_or_directory)
257+
{
258+
++retry;
259+
if (retry > 50)
260+
throw std::system_error(code);
261+
}
262+
else
263+
throw std::system_error(code);
264+
}
265+
266+
// Alternative implementation that however may not work on all platforms
267+
// since, formally, "[std::filesystem::absolute] Implementations are
268+
// encouraged to not consider p not existing to be an error", but they do
269+
// not mandate it, and if they did, they might still be affected by the
270+
// undefined behaviour outlined in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118733
271+
//
272+
// return std::filesystem::absolute(path).lexically_normal();
225273
}
226274

227275

wobble/sys.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ void chroot(const std::filesystem::path& dir);
133133
mode_t umask(mode_t mask);
134134

135135
/// Get the absolute path of a file
136-
[[deprecated("Use std::filesystem::canonical")]] std::string abspath(const std::string& pathname);
136+
std::filesystem::path abspath(const std::filesystem::path& path);
137+
[[deprecated("Use abspath(const std::filesystem::path&)")]] std::string abspath(const std::string& pathname);
138+
std::filesystem::path abspath(const char* path);
137139

138140
/**
139141
* Wraps a mmapped memory area, unmapping it on destruction.

0 commit comments

Comments
 (0)