From 3c9b87ab54ab15739dfd5396673c949447452ca7 Mon Sep 17 00:00:00 2001 From: Chandler Carruth Date: Tue, 19 Aug 2025 19:54:22 -0700 Subject: [PATCH] Add some more operations to the filesystem library (#5968) Specifically this adds `WriteStream` to get an LLVM-style `raw_fd_ostream` for an open file, and `Rename` corresponding to `rename` and `renameat` Unix-like system calls. Some basic testing for both is added as well. This was split out of work to switch the runtimes building to use the new filesystem library. --- common/filesystem.h | 36 +++++++++++++++++++++ common/filesystem_test.cpp | 65 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/common/filesystem.h b/common/filesystem.h index 7eae873167b5..70e58548fd8b 100644 --- a/common/filesystem.h +++ b/common/filesystem.h @@ -323,6 +323,13 @@ class Internal::FileRefBase { auto WriteFromBuffer(llvm::ArrayRef buffer) -> ErrorOr, FdError>; + // Returns an LLVM `raw_fd_ostream` that writes to this file. + // + // Note that this doesn't expose any write errors here, those will surface + // through the `raw_fd_ostream` API. The stream will also not close the file + // which remains owned by the owning `File` object. + auto WriteStream() -> llvm::raw_fd_ostream; + // Reads the file until EOF into the returned string. // // This method will retry any recoverable errors and work to completely read @@ -416,6 +423,8 @@ class FileRef : public Internal::FileRefBase { auto WriteFromBuffer(llvm::ArrayRef buffer) -> ErrorOr, FdError> requires Writeable; + auto WriteStream() -> llvm::raw_fd_ostream + requires Writeable; auto ReadToString() -> ErrorOr requires Readable; auto WriteFromString(llvm::StringRef str) -> ErrorOr @@ -673,6 +682,11 @@ class DirRef { CreationOptions creation_options = CreateAlways) -> ErrorOr; + // Moves a file from one directory to another directory. + auto Rename(const std::filesystem::path& path, DirRef target_dir, + const std::filesystem::path& target_path) + -> ErrorOr; + // Changes the current working directory to this directory. auto Chdir() -> ErrorOr; @@ -1189,6 +1203,10 @@ inline auto Internal::FileRefBase::WriteFromBuffer( } } +inline auto Internal::FileRefBase::WriteStream() -> llvm::raw_fd_ostream { + return llvm::raw_fd_ostream(fd_, /*shouldClose=*/false); +} + inline auto Internal::FileRefBase::Close() && -> ErrorOr { // Put the file in a moved-from state immediately as it is invalid to // retry closing or use the file in any way even if the close fails. @@ -1242,6 +1260,13 @@ auto FileRef::WriteFromBuffer(llvm::ArrayRef buffer) return FileRefBase::WriteFromBuffer(buffer); } +template +auto FileRef::WriteStream() -> llvm::raw_fd_ostream + requires Writeable +{ + return FileRefBase::WriteStream(); +} + template auto FileRef::WriteFromString(llvm::StringRef str) -> ErrorOr @@ -1351,6 +1376,17 @@ inline auto DirRef::OpenReadWrite(const std::filesystem::path& path, flags); } +inline auto DirRef::Rename(const std::filesystem::path& path, DirRef target_dir, + const std::filesystem::path& target_path) + -> ErrorOr { + if (renameat(dfd_, path.c_str(), target_dir.dfd_, target_path.c_str()) == + -1) { + return PathError(errno, "Dir::Rename on '{0}' relative to '{1}'", path, + dfd_); + } + return Success(); +} + inline auto DirRef::Chdir() -> ErrorOr { if (fchdir(dfd_) == -1) { return FdError(errno, "Dir::Chdir on '{0}'", dfd_); diff --git a/common/filesystem_test.cpp b/common/filesystem_test.cpp index 3a2afa74d81d..7f592e9b2956 100644 --- a/common/filesystem_test.cpp +++ b/common/filesystem_test.cpp @@ -327,5 +327,70 @@ TEST_F(FilesystemTest, Chdir) { (*std::move(f)).Close().Check(); } +TEST_F(FilesystemTest, WriteStream) { + std::string content_str = "0123456789"; + auto write = dir_.OpenWriteOnly("test", CreationOptions::CreateNew); + ASSERT_THAT(write, IsSuccess(_)); + { + llvm::raw_fd_ostream os = write->WriteStream(); + os << content_str; + EXPECT_FALSE(os.has_error()) << os.error(); + } + (*std::move(write)).Close().Check(); + + EXPECT_THAT(dir_.ReadFileToString("test"), IsSuccess(Eq(content_str))); +} + +TEST_F(FilesystemTest, Rename) { + // Rename a file within a directory. + ASSERT_THAT(dir_.WriteFileFromString("file1", "content1"), IsSuccess(_)); + EXPECT_THAT(dir_.Rename("file1", dir_, "file2"), IsSuccess(_)); + EXPECT_THAT(dir_.ReadFileToString("file2"), IsSuccess(Eq("content1"))); + auto read_missing = dir_.ReadFileToString("file1"); + EXPECT_FALSE(read_missing.ok()); + EXPECT_TRUE(read_missing.error().no_entity()); + + // Rename a file between two directories. + auto d1 = *dir_.CreateDirectories("subdir1"); + EXPECT_THAT(dir_.Rename("file2", d1, "file1"), IsSuccess(_)); + EXPECT_THAT(d1.ReadFileToString("file1"), IsSuccess(Eq("content1"))); + auto d2 = *dir_.CreateDirectories("subdir2"); + EXPECT_THAT(d1.Rename("file1", d2, "file1"), IsSuccess(_)); + EXPECT_THAT(d2.ReadFileToString("file1"), IsSuccess(Eq("content1"))); + // Close the first directory. + d1 = Filesystem::Dir(); + EXPECT_THAT(dir_.Rmdir("subdir1"), IsSuccess(_)) + << "Directory should have bene empty!"; + + // Rename directories. + ASSERT_THAT(dir_.ReadFileToString(std::filesystem::path("subdir2") / "file1"), + IsSuccess(Eq("content1"))); + EXPECT_THAT(dir_.Rename("subdir2", dir_, "subdir1"), IsSuccess(_)); + EXPECT_THAT(dir_.ReadFileToString(std::filesystem::path("subdir1") / "file1"), + IsSuccess(Eq("content1"))); + + // The open directory `d2` should survive the rename and point at the same + // directory. + EXPECT_THAT(d2.ReadFileToString("file1"), IsSuccess(Eq("content1"))); + EXPECT_THAT(d2.WriteFileFromString("file2", "content2"), IsSuccess(_)); + EXPECT_THAT(dir_.ReadFileToString(std::filesystem::path("subdir1") / "file2"), + IsSuccess(Eq("content2"))); + + // Rename over an existing file. + EXPECT_THAT(d2.Rename("file2", d2, "file1"), IsSuccess(_)); + EXPECT_THAT(d2.ReadFileToString("file1"), IsSuccess(Eq("content2"))); + + // Test error calls as well. + auto result = dir_.Rename("missing1", dir_, "missing2"); + EXPECT_TRUE(result.error().no_entity()) << result.error(); + result = d2.Rename("file1", dir_, + std::filesystem::path("missing_subdir") / "file2"); + EXPECT_TRUE(result.error().no_entity()) << result.error(); + // Note that `d2` was renamed `subdir1` above, which is why this creates + // infinite subdirectories. + result = dir_.Rename("subdir1", d2, "infinite_subdirs"); + EXPECT_THAT(result.error().unix_errnum(), EINVAL) << result.error(); +} + } // namespace } // namespace Carbon::Filesystem