Skip to content

Commit c1b99aa

Browse files
committed
Add FileName::Resolve
1 parent 3efdaf3 commit c1b99aa

File tree

3 files changed

+67
-0
lines changed

3 files changed

+67
-0
lines changed

elisp/private/tools/system.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,42 @@ absl::StatusOr<FileName> FileName::MakeRelative(const FileName& base) const {
486486
return FileName::FromString(rel);
487487
}
488488

489+
absl::StatusOr<FileName> FileName::Resolve() const {
490+
#ifdef _WIN32
491+
constexpr DWORD access = 0;
492+
constexpr DWORD share = 0;
493+
constexpr DWORD disposition = OPEN_EXISTING;
494+
constexpr DWORD open_flags = FILE_FLAG_BACKUP_SEMANTICS;
495+
const HANDLE handle = ::CreateFileW(this->pointer(), access, share, nullptr,
496+
disposition, open_flags, nullptr);
497+
if (handle == INVALID_HANDLE_VALUE) {
498+
return WindowsStatus("CreateFileW", absl::Hex(access), absl::Hex(share),
499+
nullptr, disposition, absl::Hex(open_flags), nullptr);
500+
}
501+
const absl::Cleanup cleanup = [handle] {
502+
if (!::CloseHandle(handle)) LOG(ERROR) << WindowsStatus("CloseHandle");
503+
};
504+
std::array<wchar_t, 0x8000> buffer;
505+
constexpr DWORD name_flags = FILE_NAME_NORMALIZED | VOLUME_NAME_DOS;
506+
const DWORD length = ::GetFinalPathNameByHandleW(handle, buffer.data(),
507+
buffer.size(), name_flags);
508+
if (length == 0) {
509+
return WindowsStatus("GetFinalPathNameByHandleW", kEllipsis, kEllipsis,
510+
absl::Hex(buffer.size()), absl::Hex(name_flags));
511+
}
512+
if (length >= buffer.size()) {
513+
return absl::FailedPreconditionError(absl::StrFormat(
514+
"Resolved filename is too long (%d characters)", length));
515+
}
516+
return FileName::FromString(std::wstring_view(buffer.data(), length));
517+
#else
518+
char* const absl_nullable result = realpath(this->pointer(), nullptr);
519+
if (result == nullptr) return ErrnoStatus("realpath", *this, nullptr);
520+
const absl::Cleanup cleanup = [result] { std::free(result); };
521+
return FileName::FromString(result);
522+
#endif
523+
}
524+
489525
absl::StatusOr<std::string> ReadFile(const FileName& file) {
490526
std::ifstream stream(file.string(), std::ios::in | std::ios::binary);
491527
if (!stream.is_open() || !stream.good()) {

elisp/private/tools/system.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class FileName final {
9292

9393
bool IsAbsolute() const;
9494
absl::StatusOr<FileName> MakeAbsolute() const;
95+
absl::StatusOr<FileName> Resolve() const;
9596
absl::StatusOr<FileName> MakeRelative(const FileName& base) const;
9697

9798
friend bool operator==(const FileName& a, const FileName& b) {

tests/tools/system_test.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,36 @@ TEST(MakeRelativeTest, Relativizes) {
449449
}
450450
}
451451

452+
TEST(FileNameTest, ResolveRejectsNonExisting) {
453+
const absl::StatusOr<FileName> dir = TempDir();
454+
ASSERT_THAT(dir, IsOk());
455+
456+
const FileName file =
457+
dir->Child(RULES_ELISP_NATIVE_LITERAL("nonexisting")).value();
458+
459+
EXPECT_THAT(file.Resolve(), StatusIs(absl::StatusCode::kNotFound));
460+
}
461+
462+
TEST(FileNameTest, ResolvesRegularFile) {
463+
const absl::StatusOr<FileName> dir = TempDir();
464+
ASSERT_THAT(dir, IsOk());
465+
466+
const FileName file = dir->Child(RULES_ELISP_NATIVE_LITERAL("file")).value();
467+
EXPECT_THAT(WriteFile(file, "contents"), IsOk());
468+
const absl::Cleanup cleanup = [&file] { EXPECT_THAT(Unlink(file), IsOk()); };
469+
470+
const absl::StatusOr<FileName> resolved = file.Resolve();
471+
ASSERT_THAT(resolved, IsOk());
472+
EXPECT_THAT(ReadFile(*resolved), IsOkAndHolds("contents"));
473+
}
474+
475+
TEST(FileNameTest, ResolvesDirectory) {
476+
const absl::StatusOr<FileName> dir = TempDir();
477+
ASSERT_THAT(dir, IsOk());
478+
479+
EXPECT_THAT(dir->Resolve(), IsOk());
480+
}
481+
452482
TEST(FileExistsTest, TestsThatFileExists) {
453483
const absl::StatusOr<FileName> dir = TempDir();
454484
ASSERT_THAT(dir, IsOk());

0 commit comments

Comments
 (0)