1 //===- llvm/unittest/Support/ReplaceFileTest.cpp - unit tests -------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "llvm/Support/Errc.h" 10 #include "llvm/Support/ErrorHandling.h" 11 #include "llvm/Support/FileSystem.h" 12 #include "llvm/Support/MemoryBuffer.h" 13 #include "llvm/Support/Path.h" 14 #include "llvm/Support/Process.h" 15 #include "gtest/gtest.h" 16 17 using namespace llvm; 18 using namespace llvm::sys; 19 20 #define ASSERT_NO_ERROR(x) ASSERT_EQ(x, std::error_code()) 21 22 namespace { 23 std::error_code CreateFileWithContent(const SmallString<128> &FilePath, 24 const StringRef &content) { 25 int FD = 0; 26 if (std::error_code ec = fs::openFileForWrite(FilePath, FD)) 27 return ec; 28 29 const bool ShouldClose = true; 30 raw_fd_ostream OS(FD, ShouldClose); 31 OS << content; 32 33 return std::error_code(); 34 } 35 36 class ScopedFD { 37 int FD; 38 39 ScopedFD(const ScopedFD &) = delete; 40 ScopedFD &operator=(const ScopedFD &) = delete; 41 42 public: 43 explicit ScopedFD(int Descriptor) : FD(Descriptor) {} 44 ~ScopedFD() { Process::SafelyCloseFileDescriptor(FD); } 45 }; 46 47 bool FDHasContent(int FD, StringRef Content) { 48 auto Buffer = 49 MemoryBuffer::getOpenFile(sys::fs::convertFDToNativeFile(FD), "", -1); 50 assert(Buffer); 51 return Buffer.get()->getBuffer() == Content; 52 } 53 54 bool FileHasContent(StringRef File, StringRef Content) { 55 int FD = 0; 56 auto EC = fs::openFileForRead(File, FD); 57 (void)EC; 58 assert(!EC); 59 ScopedFD EventuallyCloseIt(FD); 60 return FDHasContent(FD, Content); 61 } 62 63 TEST(rename, FileOpenedForReadingCanBeReplaced) { 64 // Create unique temporary directory for this test. 65 SmallString<128> TestDirectory; 66 ASSERT_NO_ERROR(fs::createUniqueDirectory( 67 "FileOpenedForReadingCanBeReplaced-test", TestDirectory)); 68 69 // Add a couple of files to the test directory. 70 SmallString<128> SourceFileName(TestDirectory); 71 path::append(SourceFileName, "source"); 72 73 SmallString<128> TargetFileName(TestDirectory); 74 path::append(TargetFileName, "target"); 75 76 ASSERT_NO_ERROR(CreateFileWithContent(SourceFileName, "!!source!!")); 77 ASSERT_NO_ERROR(CreateFileWithContent(TargetFileName, "!!target!!")); 78 79 { 80 // Open the target file for reading. 81 int ReadFD = 0; 82 ASSERT_NO_ERROR(fs::openFileForRead(TargetFileName, ReadFD)); 83 ScopedFD EventuallyCloseIt(ReadFD); 84 85 // Confirm we can replace the file while it is open. 86 EXPECT_TRUE(!fs::rename(SourceFileName, TargetFileName)); 87 88 // We should still be able to read the old data through the existing 89 // descriptor. 90 EXPECT_TRUE(FDHasContent(ReadFD, "!!target!!")); 91 92 // The source file should no longer exist 93 EXPECT_FALSE(fs::exists(SourceFileName)); 94 } 95 96 // If we obtain a new descriptor for the target file, we should find that it 97 // contains the content that was in the source file. 98 EXPECT_TRUE(FileHasContent(TargetFileName, "!!source!!")); 99 100 // Rename the target file back to the source file name to confirm that rename 101 // still works if the destination does not already exist. 102 EXPECT_TRUE(!fs::rename(TargetFileName, SourceFileName)); 103 EXPECT_FALSE(fs::exists(TargetFileName)); 104 ASSERT_TRUE(fs::exists(SourceFileName)); 105 106 // Clean up. 107 ASSERT_NO_ERROR(fs::remove(SourceFileName)); 108 ASSERT_NO_ERROR(fs::remove(TestDirectory.str())); 109 } 110 111 TEST(rename, ExistingTemp) { 112 // Test that existing .tmpN files don't get deleted by the Windows 113 // sys::fs::rename implementation. 114 SmallString<128> TestDirectory; 115 ASSERT_NO_ERROR( 116 fs::createUniqueDirectory("ExistingTemp-test", TestDirectory)); 117 118 SmallString<128> SourceFileName(TestDirectory); 119 path::append(SourceFileName, "source"); 120 121 SmallString<128> TargetFileName(TestDirectory); 122 path::append(TargetFileName, "target"); 123 124 SmallString<128> TargetTmp0FileName(TestDirectory); 125 path::append(TargetTmp0FileName, "target.tmp0"); 126 127 SmallString<128> TargetTmp1FileName(TestDirectory); 128 path::append(TargetTmp1FileName, "target.tmp1"); 129 130 ASSERT_NO_ERROR(CreateFileWithContent(SourceFileName, "!!source!!")); 131 ASSERT_NO_ERROR(CreateFileWithContent(TargetFileName, "!!target!!")); 132 ASSERT_NO_ERROR(CreateFileWithContent(TargetTmp0FileName, "!!target.tmp0!!")); 133 134 { 135 // Use mapped_file_region to make sure that the destination file is mmap'ed. 136 // This will cause SetInformationByHandle to fail when renaming to the 137 // destination, and we will follow the code path that tries to give target 138 // a temporary name. 139 int TargetFD; 140 std::error_code EC; 141 ASSERT_NO_ERROR(fs::openFileForRead(TargetFileName, TargetFD)); 142 ScopedFD X(TargetFD); 143 sys::fs::mapped_file_region MFR(sys::fs::convertFDToNativeFile(TargetFD), 144 sys::fs::mapped_file_region::readonly, 10, 145 0, EC); 146 ASSERT_FALSE(EC); 147 148 ASSERT_NO_ERROR(fs::rename(SourceFileName, TargetFileName)); 149 150 #ifdef _WIN32 151 // Make sure that target was temporarily renamed to target.tmp1 on Windows. 152 // This is signified by a permission denied error as opposed to no such file 153 // or directory when trying to open it. 154 int Tmp1FD; 155 EXPECT_EQ(errc::permission_denied, 156 fs::openFileForRead(TargetTmp1FileName, Tmp1FD)); 157 #endif 158 } 159 160 EXPECT_TRUE(FileHasContent(TargetTmp0FileName, "!!target.tmp0!!")); 161 162 ASSERT_NO_ERROR(fs::remove(TargetFileName)); 163 ASSERT_NO_ERROR(fs::remove(TargetTmp0FileName)); 164 ASSERT_NO_ERROR(fs::remove(TestDirectory.str())); 165 } 166 167 } // anonymous namespace 168