Skip to content

Commit 5889874

Browse files
artemcmbenlangmuir
andauthored
[llvm][vfs] Make vfs::FileSystem::exists() virtual NFC (#88575)
Allow a `vfs::FileSystem` to provide a more efficient implementation of `exists()` if they are able to. The existing `FileSystem` implementations continue to default to using `status()` except that overlay, proxy, and redirecting filesystems are taught to forward calls to `exists()` correctly to their wrapped/external filesystem. Co-authored-by: Ben Langmuir <[email protected]>
1 parent dfafe38 commit 5889874

File tree

3 files changed

+227
-2
lines changed

3 files changed

+227
-2
lines changed

llvm/include/llvm/Support/VirtualFileSystem.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,9 @@ class FileSystem : public llvm::ThreadSafeRefCountedBase<FileSystem>,
300300
virtual std::error_code getRealPath(const Twine &Path,
301301
SmallVectorImpl<char> &Output);
302302

303-
/// Check whether a file exists. Provided for convenience.
304-
bool exists(const Twine &Path);
303+
/// Check whether \p Path exists. By default this uses \c status(), but
304+
/// filesystems may provide a more efficient implementation if available.
305+
virtual bool exists(const Twine &Path);
305306

306307
/// Is the file mounted on a local filesystem?
307308
virtual std::error_code isLocal(const Twine &Path, bool &Result);
@@ -386,6 +387,7 @@ class OverlayFileSystem : public RTTIExtends<OverlayFileSystem, FileSystem> {
386387
void pushOverlay(IntrusiveRefCntPtr<FileSystem> FS);
387388

388389
llvm::ErrorOr<Status> status(const Twine &Path) override;
390+
bool exists(const Twine &Path) override;
389391
llvm::ErrorOr<std::unique_ptr<File>>
390392
openFileForRead(const Twine &Path) override;
391393
directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override;
@@ -439,6 +441,7 @@ class ProxyFileSystem : public RTTIExtends<ProxyFileSystem, FileSystem> {
439441
llvm::ErrorOr<Status> status(const Twine &Path) override {
440442
return FS->status(Path);
441443
}
444+
bool exists(const Twine &Path) override { return FS->exists(Path); }
442445
llvm::ErrorOr<std::unique_ptr<File>>
443446
openFileForRead(const Twine &Path) override {
444447
return FS->openFileForRead(Path);
@@ -1044,6 +1047,7 @@ class RedirectingFileSystem
10441047
bool UseExternalNames, FileSystem &ExternalFS);
10451048

10461049
ErrorOr<Status> status(const Twine &Path) override;
1050+
bool exists(const Twine &Path) override;
10471051
ErrorOr<std::unique_ptr<File>> openFileForRead(const Twine &Path) override;
10481052

10491053
std::error_code getRealPath(const Twine &Path,

llvm/lib/Support/VirtualFileSystem.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,15 @@ ErrorOr<Status> OverlayFileSystem::status(const Twine &Path) {
438438
return make_error_code(llvm::errc::no_such_file_or_directory);
439439
}
440440

441+
bool OverlayFileSystem::exists(const Twine &Path) {
442+
// FIXME: handle symlinks that cross file systems
443+
for (iterator I = overlays_begin(), E = overlays_end(); I != E; ++I) {
444+
if ((*I)->exists(Path))
445+
return true;
446+
}
447+
return false;
448+
}
449+
441450
ErrorOr<std::unique_ptr<File>>
442451
OverlayFileSystem::openFileForRead(const llvm::Twine &Path) {
443452
// FIXME: handle symlinks that cross file systems
@@ -2428,6 +2437,53 @@ ErrorOr<Status> RedirectingFileSystem::status(const Twine &OriginalPath) {
24282437
return S;
24292438
}
24302439

2440+
bool RedirectingFileSystem::exists(const Twine &OriginalPath) {
2441+
SmallString<256> Path;
2442+
OriginalPath.toVector(Path);
2443+
2444+
if (makeAbsolute(Path))
2445+
return false;
2446+
2447+
if (Redirection == RedirectKind::Fallback) {
2448+
// Attempt to find the original file first, only falling back to the
2449+
// mapped file if that fails.
2450+
if (ExternalFS->exists(Path))
2451+
return true;
2452+
}
2453+
2454+
ErrorOr<RedirectingFileSystem::LookupResult> Result = lookupPath(Path);
2455+
if (!Result) {
2456+
// Was not able to map file, fallthrough to using the original path if
2457+
// that was the specified redirection type.
2458+
if (Redirection == RedirectKind::Fallthrough &&
2459+
isFileNotFound(Result.getError()))
2460+
return ExternalFS->exists(Path);
2461+
return false;
2462+
}
2463+
2464+
std::optional<StringRef> ExtRedirect = Result->getExternalRedirect();
2465+
if (!ExtRedirect) {
2466+
assert(isa<RedirectingFileSystem::DirectoryEntry>(Result->E));
2467+
return true;
2468+
}
2469+
2470+
SmallString<256> RemappedPath((*ExtRedirect).str());
2471+
if (makeAbsolute(RemappedPath))
2472+
return false;
2473+
2474+
if (ExternalFS->exists(RemappedPath))
2475+
return true;
2476+
2477+
if (Redirection == RedirectKind::Fallthrough) {
2478+
// Mapped the file but it wasn't found in the underlying filesystem,
2479+
// fallthrough to using the original path if that was the specified
2480+
// redirection type.
2481+
return ExternalFS->exists(Path);
2482+
}
2483+
2484+
return false;
2485+
}
2486+
24312487
namespace {
24322488

24332489
/// Provide a file wrapper with an overriden status.

llvm/unittests/Support/VirtualFileSystemTest.cpp

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,21 @@ class ErrorDummyFileSystem : public DummyFileSystem {
201201
}
202202
};
203203

204+
/// A version of \c DummyFileSystem that aborts on \c status() to test that
205+
/// \c exists() is being used.
206+
class NoStatusDummyFileSystem : public DummyFileSystem {
207+
public:
208+
ErrorOr<vfs::Status> status(const Twine &Path) override {
209+
llvm::report_fatal_error(
210+
"unexpected call to NoStatusDummyFileSystem::status");
211+
}
212+
213+
bool exists(const Twine &Path) override {
214+
auto Status = DummyFileSystem::status(Path);
215+
return Status && Status->exists();
216+
}
217+
};
218+
204219
/// Replace back-slashes by front-slashes.
205220
std::string getPosixPath(const Twine &S) {
206221
SmallString<128> Result;
@@ -968,6 +983,30 @@ TEST(OverlayFileSystemTest, PrintOutput) {
968983
Output);
969984
}
970985

986+
TEST(OverlayFileSystemTest, Exists) {
987+
IntrusiveRefCntPtr<DummyFileSystem> Lower(new NoStatusDummyFileSystem());
988+
IntrusiveRefCntPtr<DummyFileSystem> Upper(new NoStatusDummyFileSystem());
989+
IntrusiveRefCntPtr<vfs::OverlayFileSystem> O(
990+
new vfs::OverlayFileSystem(Lower));
991+
O->pushOverlay(Upper);
992+
993+
Lower->addDirectory("/both");
994+
Upper->addDirectory("/both");
995+
Lower->addRegularFile("/both/lower_file");
996+
Upper->addRegularFile("/both/upper_file");
997+
Lower->addDirectory("/lower");
998+
Upper->addDirectory("/upper");
999+
1000+
EXPECT_TRUE(O->exists("/both"));
1001+
EXPECT_TRUE(O->exists("/both"));
1002+
EXPECT_TRUE(O->exists("/both/lower_file"));
1003+
EXPECT_TRUE(O->exists("/both/upper_file"));
1004+
EXPECT_TRUE(O->exists("/lower"));
1005+
EXPECT_TRUE(O->exists("/upper"));
1006+
EXPECT_FALSE(O->exists("/both/nope"));
1007+
EXPECT_FALSE(O->exists("/nope"));
1008+
}
1009+
9711010
TEST(ProxyFileSystemTest, Basic) {
9721011
IntrusiveRefCntPtr<vfs::InMemoryFileSystem> Base(
9731012
new vfs::InMemoryFileSystem());
@@ -3364,6 +3403,11 @@ TEST(RedirectingFileSystemTest, ExternalPaths) {
33643403
SeenPaths.push_back(Dir.str());
33653404
return ProxyFileSystem::dir_begin(Dir, EC);
33663405
}
3406+
3407+
bool exists(const Twine &Path) override {
3408+
SeenPaths.push_back(Path.str());
3409+
return ProxyFileSystem::exists(Path);
3410+
}
33673411
};
33683412

33693413
std::error_code EC;
@@ -3395,3 +3439,124 @@ TEST(RedirectingFileSystemTest, ExternalPaths) {
33953439

33963440
EXPECT_EQ(CheckFS->SeenPaths, Expected);
33973441
}
3442+
3443+
TEST(RedirectingFileSystemTest, Exists) {
3444+
IntrusiveRefCntPtr<DummyFileSystem> Dummy(new NoStatusDummyFileSystem());
3445+
auto YAML =
3446+
MemoryBuffer::getMemBuffer("{\n"
3447+
" 'version': 0,\n"
3448+
" 'roots': [\n"
3449+
" {\n"
3450+
" 'type': 'directory-remap',\n"
3451+
" 'name': '/dremap',\n"
3452+
" 'external-contents': '/a',\n"
3453+
" },"
3454+
" {\n"
3455+
" 'type': 'directory-remap',\n"
3456+
" 'name': '/dmissing',\n"
3457+
" 'external-contents': '/dmissing',\n"
3458+
" },"
3459+
" {\n"
3460+
" 'type': 'directory',\n"
3461+
" 'name': '/both',\n"
3462+
" 'contents': [\n"
3463+
" {\n"
3464+
" 'type': 'file',\n"
3465+
" 'name': 'vfile',\n"
3466+
" 'external-contents': '/c'\n"
3467+
" }\n"
3468+
" ]\n"
3469+
" },\n"
3470+
" {\n"
3471+
" 'type': 'directory',\n"
3472+
" 'name': '/vdir',\n"
3473+
" 'contents': ["
3474+
" {\n"
3475+
" 'type': 'directory-remap',\n"
3476+
" 'name': 'dremap',\n"
3477+
" 'external-contents': '/b'\n"
3478+
" },\n"
3479+
" {\n"
3480+
" 'type': 'file',\n"
3481+
" 'name': 'missing',\n"
3482+
" 'external-contents': '/missing'\n"
3483+
" },\n"
3484+
" {\n"
3485+
" 'type': 'file',\n"
3486+
" 'name': 'vfile',\n"
3487+
" 'external-contents': '/c'\n"
3488+
" }]\n"
3489+
" }]\n"
3490+
"}");
3491+
3492+
Dummy->addDirectory("/a");
3493+
Dummy->addRegularFile("/a/foo");
3494+
Dummy->addDirectory("/b");
3495+
Dummy->addRegularFile("/c");
3496+
Dummy->addRegularFile("/both/foo");
3497+
3498+
auto Redirecting = vfs::RedirectingFileSystem::create(
3499+
std::move(YAML), nullptr, "", nullptr, Dummy);
3500+
3501+
EXPECT_TRUE(Redirecting->exists("/dremap"));
3502+
EXPECT_FALSE(Redirecting->exists("/dmissing"));
3503+
EXPECT_FALSE(Redirecting->exists("/unknown"));
3504+
EXPECT_TRUE(Redirecting->exists("/both"));
3505+
EXPECT_TRUE(Redirecting->exists("/both/foo"));
3506+
EXPECT_TRUE(Redirecting->exists("/both/vfile"));
3507+
EXPECT_TRUE(Redirecting->exists("/vdir"));
3508+
EXPECT_TRUE(Redirecting->exists("/vdir/dremap"));
3509+
EXPECT_FALSE(Redirecting->exists("/vdir/missing"));
3510+
EXPECT_TRUE(Redirecting->exists("/vdir/vfile"));
3511+
EXPECT_FALSE(Redirecting->exists("/vdir/unknown"));
3512+
}
3513+
3514+
TEST(RedirectingFileSystemTest, ExistsFallback) {
3515+
IntrusiveRefCntPtr<DummyFileSystem> Dummy(new NoStatusDummyFileSystem());
3516+
auto YAML =
3517+
MemoryBuffer::getMemBuffer("{\n"
3518+
" 'version': 0,\n"
3519+
" 'redirecting-with': 'fallback',\n"
3520+
" 'roots': [\n"
3521+
" {\n"
3522+
" 'type': 'file',\n"
3523+
" 'name': '/fallback',\n"
3524+
" 'external-contents': '/missing',\n"
3525+
" },"
3526+
" ]\n"
3527+
"}");
3528+
3529+
Dummy->addRegularFile("/fallback");
3530+
3531+
auto Redirecting = vfs::RedirectingFileSystem::create(
3532+
std::move(YAML), nullptr, "", nullptr, Dummy);
3533+
3534+
EXPECT_TRUE(Redirecting->exists("/fallback"));
3535+
EXPECT_FALSE(Redirecting->exists("/missing"));
3536+
}
3537+
3538+
TEST(RedirectingFileSystemTest, ExistsRedirectOnly) {
3539+
IntrusiveRefCntPtr<DummyFileSystem> Dummy(new NoStatusDummyFileSystem());
3540+
auto YAML =
3541+
MemoryBuffer::getMemBuffer("{\n"
3542+
" 'version': 0,\n"
3543+
" 'redirecting-with': 'redirect-only',\n"
3544+
" 'roots': [\n"
3545+
" {\n"
3546+
" 'type': 'file',\n"
3547+
" 'name': '/vfile',\n"
3548+
" 'external-contents': '/a',\n"
3549+
" },"
3550+
" ]\n"
3551+
"}");
3552+
3553+
Dummy->addRegularFile("/a");
3554+
Dummy->addRegularFile("/b");
3555+
3556+
auto Redirecting = vfs::RedirectingFileSystem::create(
3557+
std::move(YAML), nullptr, "", nullptr, Dummy);
3558+
3559+
EXPECT_FALSE(Redirecting->exists("/a"));
3560+
EXPECT_FALSE(Redirecting->exists("/b"));
3561+
EXPECT_TRUE(Redirecting->exists("/vfile"));
3562+
}

0 commit comments

Comments
 (0)