1 //===- FileOutputBuffer.cpp - File Output Buffer ----------------*- C++ -*-===// 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 // Utility for creating a in-memory buffer that will be written to a file. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "llvm/Support/FileOutputBuffer.h" 14 #include "llvm/Support/Errc.h" 15 #include "llvm/Support/FileSystem.h" 16 #include "llvm/Support/Memory.h" 17 #include <system_error> 18 19 #if !defined(_MSC_VER) && !defined(__MINGW32__) 20 #include <unistd.h> 21 #else 22 #include <io.h> 23 #endif 24 25 using namespace llvm; 26 using namespace llvm::sys; 27 28 namespace { 29 // A FileOutputBuffer which creates a temporary file in the same directory 30 // as the final output file. The final output file is atomically replaced 31 // with the temporary file on commit(). 32 class OnDiskBuffer : public FileOutputBuffer { 33 public: 34 OnDiskBuffer(StringRef Path, fs::TempFile Temp, fs::mapped_file_region Buf) 35 : FileOutputBuffer(Path), Buffer(std::move(Buf)), Temp(std::move(Temp)) {} 36 37 uint8_t *getBufferStart() const override { return (uint8_t *)Buffer.data(); } 38 39 uint8_t *getBufferEnd() const override { 40 return (uint8_t *)Buffer.data() + Buffer.size(); 41 } 42 43 size_t getBufferSize() const override { return Buffer.size(); } 44 45 Error commit() override { 46 // Unmap buffer, letting OS flush dirty pages to file on disk. 47 Buffer.unmap(); 48 49 // Atomically replace the existing file with the new one. 50 return Temp.keep(FinalPath); 51 } 52 53 ~OnDiskBuffer() override { 54 // Close the mapping before deleting the temp file, so that the removal 55 // succeeds. 56 Buffer.unmap(); 57 consumeError(Temp.discard()); 58 } 59 60 void discard() override { 61 // Delete the temp file if it still was open, but keeping the mapping 62 // active. 63 consumeError(Temp.discard()); 64 } 65 66 private: 67 fs::mapped_file_region Buffer; 68 fs::TempFile Temp; 69 }; 70 71 // A FileOutputBuffer which keeps data in memory and writes to the final 72 // output file on commit(). This is used only when we cannot use OnDiskBuffer. 73 class InMemoryBuffer : public FileOutputBuffer { 74 public: 75 InMemoryBuffer(StringRef Path, MemoryBlock Buf, std::size_t BufSize, 76 unsigned Mode) 77 : FileOutputBuffer(Path), Buffer(Buf), BufferSize(BufSize), 78 Mode(Mode) {} 79 80 uint8_t *getBufferStart() const override { return (uint8_t *)Buffer.base(); } 81 82 uint8_t *getBufferEnd() const override { 83 return (uint8_t *)Buffer.base() + BufferSize; 84 } 85 86 size_t getBufferSize() const override { return BufferSize; } 87 88 Error commit() override { 89 if (FinalPath == "-") { 90 llvm::outs() << StringRef((const char *)Buffer.base(), BufferSize); 91 llvm::outs().flush(); 92 return Error::success(); 93 } 94 95 using namespace sys::fs; 96 int FD; 97 std::error_code EC; 98 if (auto EC = 99 openFileForWrite(FinalPath, FD, CD_CreateAlways, OF_None, Mode)) 100 return errorCodeToError(EC); 101 raw_fd_ostream OS(FD, /*shouldClose=*/true, /*unbuffered=*/true); 102 OS << StringRef((const char *)Buffer.base(), BufferSize); 103 return Error::success(); 104 } 105 106 private: 107 // Buffer may actually contain a larger memory block than BufferSize 108 OwningMemoryBlock Buffer; 109 size_t BufferSize; 110 unsigned Mode; 111 }; 112 } // namespace 113 114 static Expected<std::unique_ptr<InMemoryBuffer>> 115 createInMemoryBuffer(StringRef Path, size_t Size, unsigned Mode) { 116 std::error_code EC; 117 MemoryBlock MB = Memory::allocateMappedMemory( 118 Size, nullptr, sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC); 119 if (EC) 120 return errorCodeToError(EC); 121 return std::make_unique<InMemoryBuffer>(Path, MB, Size, Mode); 122 } 123 124 static Expected<std::unique_ptr<FileOutputBuffer>> 125 createOnDiskBuffer(StringRef Path, size_t Size, unsigned Mode) { 126 Expected<fs::TempFile> FileOrErr = 127 fs::TempFile::create(Path + ".tmp%%%%%%%", Mode); 128 if (!FileOrErr) 129 return FileOrErr.takeError(); 130 fs::TempFile File = std::move(*FileOrErr); 131 132 if (auto EC = fs::resize_file_before_mapping_readwrite(File.FD, Size)) { 133 consumeError(File.discard()); 134 return errorCodeToError(EC); 135 } 136 137 // Mmap it. 138 std::error_code EC; 139 fs::mapped_file_region MappedFile = 140 fs::mapped_file_region(fs::convertFDToNativeFile(File.FD), 141 fs::mapped_file_region::readwrite, Size, 0, EC); 142 143 // mmap(2) can fail if the underlying filesystem does not support it. 144 // If that happens, we fall back to in-memory buffer as the last resort. 145 if (EC) { 146 consumeError(File.discard()); 147 return createInMemoryBuffer(Path, Size, Mode); 148 } 149 150 return std::make_unique<OnDiskBuffer>(Path, std::move(File), 151 std::move(MappedFile)); 152 } 153 154 // Create an instance of FileOutputBuffer. 155 Expected<std::unique_ptr<FileOutputBuffer>> 156 FileOutputBuffer::create(StringRef Path, size_t Size, unsigned Flags) { 157 // Handle "-" as stdout just like llvm::raw_ostream does. 158 if (Path == "-") 159 return createInMemoryBuffer("-", Size, /*Mode=*/0); 160 161 unsigned Mode = fs::all_read | fs::all_write; 162 if (Flags & F_executable) 163 Mode |= fs::all_exe; 164 165 // If Size is zero, don't use mmap which will fail with EINVAL. 166 if (Size == 0) 167 return createInMemoryBuffer(Path, Size, Mode); 168 169 fs::file_status Stat; 170 fs::status(Path, Stat); 171 172 // Usually, we want to create OnDiskBuffer to create a temporary file in 173 // the same directory as the destination file and atomically replaces it 174 // by rename(2). 175 // 176 // However, if the destination file is a special file, we don't want to 177 // use rename (e.g. we don't want to replace /dev/null with a regular 178 // file.) If that's the case, we create an in-memory buffer, open the 179 // destination file and write to it on commit(). 180 switch (Stat.type()) { 181 case fs::file_type::directory_file: 182 return errorCodeToError(errc::is_a_directory); 183 case fs::file_type::regular_file: 184 case fs::file_type::file_not_found: 185 case fs::file_type::status_error: 186 if (Flags & F_no_mmap) 187 return createInMemoryBuffer(Path, Size, Mode); 188 else 189 return createOnDiskBuffer(Path, Size, Mode); 190 default: 191 return createInMemoryBuffer(Path, Size, Mode); 192 } 193 } 194