Преглед на файлове

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.
Chandler Carruth преди 8 месеца
родител
ревизия
3c9b87ab54
променени са 2 файла, в които са добавени 101 реда и са изтрити 0 реда
  1. 36 0
      common/filesystem.h
  2. 65 0
      common/filesystem_test.cpp

+ 36 - 0
common/filesystem.h

@@ -323,6 +323,13 @@ class Internal::FileRefBase {
   auto WriteFromBuffer(llvm::ArrayRef<std::byte> buffer)
       -> ErrorOr<llvm::ArrayRef<std::byte>, 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<std::byte> buffer)
       -> ErrorOr<llvm::ArrayRef<std::byte>, FdError>
     requires Writeable;
+  auto WriteStream() -> llvm::raw_fd_ostream
+    requires Writeable;
   auto ReadToString() -> ErrorOr<std::string, FdError>
     requires Readable;
   auto WriteFromString(llvm::StringRef str) -> ErrorOr<Success, FdError>
@@ -673,6 +682,11 @@ class DirRef {
                            CreationOptions creation_options = CreateAlways)
       -> ErrorOr<Success, PathError>;
 
+  // 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<Success, PathError>;
+
   // Changes the current working directory to this directory.
   auto Chdir() -> ErrorOr<Success, FdError>;
 
@@ -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<Success, FdError> {
   // 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<A>::WriteFromBuffer(llvm::ArrayRef<std::byte> buffer)
   return FileRefBase::WriteFromBuffer(buffer);
 }
 
+template <OpenAccess A>
+auto FileRef<A>::WriteStream() -> llvm::raw_fd_ostream
+  requires Writeable
+{
+  return FileRefBase::WriteStream();
+}
+
 template <OpenAccess A>
 auto FileRef<A>::WriteFromString(llvm::StringRef str)
     -> ErrorOr<Success, FdError>
@@ -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<Success, PathError> {
+  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<Success, FdError> {
   if (fchdir(dfd_) == -1) {
     return FdError(errno, "Dir::Chdir on '{0}'", dfd_);

+ 65 - 0
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