Skip to content

Commit c2b2cd0

Browse files
benlangmuirartemcm
authored andcommitted
[llvm][vfs] Make vfs::FileSystem::exists() virtual NFC
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.
1 parent 5601e35 commit c2b2cd0

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) const;
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
@@ -439,6 +439,15 @@ ErrorOr<Status> OverlayFileSystem::status(const Twine &Path) {
439439
return make_error_code(llvm::errc::no_such_file_or_directory);
440440
}
441441

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

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

24362492
/// 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)