xref: /freebsd/contrib/llvm-project/llvm/lib/Support/FileOutputBuffer.cpp (revision 5f757f3ff9144b609b3c433dfd370cc6bdc191ad)
10b57cec5SDimitry Andric //===- FileOutputBuffer.cpp - File Output Buffer ----------------*- C++ -*-===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
90b57cec5SDimitry Andric // Utility for creating a in-memory buffer that will be written to a file.
100b57cec5SDimitry Andric //
110b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
120b57cec5SDimitry Andric 
130b57cec5SDimitry Andric #include "llvm/Support/FileOutputBuffer.h"
140b57cec5SDimitry Andric #include "llvm/Support/Errc.h"
155ffd83dbSDimitry Andric #include "llvm/Support/FileSystem.h"
160b57cec5SDimitry Andric #include "llvm/Support/Memory.h"
17*5f757f3fSDimitry Andric #include "llvm/Support/TimeProfiler.h"
180b57cec5SDimitry Andric #include <system_error>
190b57cec5SDimitry Andric 
200b57cec5SDimitry Andric #if !defined(_MSC_VER) && !defined(__MINGW32__)
210b57cec5SDimitry Andric #include <unistd.h>
220b57cec5SDimitry Andric #else
230b57cec5SDimitry Andric #include <io.h>
240b57cec5SDimitry Andric #endif
250b57cec5SDimitry Andric 
260b57cec5SDimitry Andric using namespace llvm;
270b57cec5SDimitry Andric using namespace llvm::sys;
280b57cec5SDimitry Andric 
290b57cec5SDimitry Andric namespace {
300b57cec5SDimitry Andric // A FileOutputBuffer which creates a temporary file in the same directory
310b57cec5SDimitry Andric // as the final output file. The final output file is atomically replaced
320b57cec5SDimitry Andric // with the temporary file on commit().
330b57cec5SDimitry Andric class OnDiskBuffer : public FileOutputBuffer {
340b57cec5SDimitry Andric public:
35fe6060f1SDimitry Andric   OnDiskBuffer(StringRef Path, fs::TempFile Temp, fs::mapped_file_region Buf)
360b57cec5SDimitry Andric       : FileOutputBuffer(Path), Buffer(std::move(Buf)), Temp(std::move(Temp)) {}
370b57cec5SDimitry Andric 
38fe6060f1SDimitry Andric   uint8_t *getBufferStart() const override { return (uint8_t *)Buffer.data(); }
390b57cec5SDimitry Andric 
400b57cec5SDimitry Andric   uint8_t *getBufferEnd() const override {
41fe6060f1SDimitry Andric     return (uint8_t *)Buffer.data() + Buffer.size();
420b57cec5SDimitry Andric   }
430b57cec5SDimitry Andric 
44fe6060f1SDimitry Andric   size_t getBufferSize() const override { return Buffer.size(); }
450b57cec5SDimitry Andric 
460b57cec5SDimitry Andric   Error commit() override {
47*5f757f3fSDimitry Andric     llvm::TimeTraceScope timeScope("Commit buffer to disk");
48*5f757f3fSDimitry Andric 
490b57cec5SDimitry Andric     // Unmap buffer, letting OS flush dirty pages to file on disk.
50fe6060f1SDimitry Andric     Buffer.unmap();
510b57cec5SDimitry Andric 
520b57cec5SDimitry Andric     // Atomically replace the existing file with the new one.
530b57cec5SDimitry Andric     return Temp.keep(FinalPath);
540b57cec5SDimitry Andric   }
550b57cec5SDimitry Andric 
560b57cec5SDimitry Andric   ~OnDiskBuffer() override {
570b57cec5SDimitry Andric     // Close the mapping before deleting the temp file, so that the removal
580b57cec5SDimitry Andric     // succeeds.
59fe6060f1SDimitry Andric     Buffer.unmap();
600b57cec5SDimitry Andric     consumeError(Temp.discard());
610b57cec5SDimitry Andric   }
620b57cec5SDimitry Andric 
630b57cec5SDimitry Andric   void discard() override {
640b57cec5SDimitry Andric     // Delete the temp file if it still was open, but keeping the mapping
650b57cec5SDimitry Andric     // active.
660b57cec5SDimitry Andric     consumeError(Temp.discard());
670b57cec5SDimitry Andric   }
680b57cec5SDimitry Andric 
690b57cec5SDimitry Andric private:
70fe6060f1SDimitry Andric   fs::mapped_file_region Buffer;
710b57cec5SDimitry Andric   fs::TempFile Temp;
720b57cec5SDimitry Andric };
730b57cec5SDimitry Andric 
740b57cec5SDimitry Andric // A FileOutputBuffer which keeps data in memory and writes to the final
750b57cec5SDimitry Andric // output file on commit(). This is used only when we cannot use OnDiskBuffer.
760b57cec5SDimitry Andric class InMemoryBuffer : public FileOutputBuffer {
770b57cec5SDimitry Andric public:
780b57cec5SDimitry Andric   InMemoryBuffer(StringRef Path, MemoryBlock Buf, std::size_t BufSize,
790b57cec5SDimitry Andric                  unsigned Mode)
800b57cec5SDimitry Andric       : FileOutputBuffer(Path), Buffer(Buf), BufferSize(BufSize),
810b57cec5SDimitry Andric         Mode(Mode) {}
820b57cec5SDimitry Andric 
830b57cec5SDimitry Andric   uint8_t *getBufferStart() const override { return (uint8_t *)Buffer.base(); }
840b57cec5SDimitry Andric 
850b57cec5SDimitry Andric   uint8_t *getBufferEnd() const override {
860b57cec5SDimitry Andric     return (uint8_t *)Buffer.base() + BufferSize;
870b57cec5SDimitry Andric   }
880b57cec5SDimitry Andric 
890b57cec5SDimitry Andric   size_t getBufferSize() const override { return BufferSize; }
900b57cec5SDimitry Andric 
910b57cec5SDimitry Andric   Error commit() override {
920b57cec5SDimitry Andric     if (FinalPath == "-") {
930b57cec5SDimitry Andric       llvm::outs() << StringRef((const char *)Buffer.base(), BufferSize);
940b57cec5SDimitry Andric       llvm::outs().flush();
950b57cec5SDimitry Andric       return Error::success();
960b57cec5SDimitry Andric     }
970b57cec5SDimitry Andric 
980b57cec5SDimitry Andric     using namespace sys::fs;
990b57cec5SDimitry Andric     int FD;
1000b57cec5SDimitry Andric     std::error_code EC;
1010b57cec5SDimitry Andric     if (auto EC =
1020b57cec5SDimitry Andric             openFileForWrite(FinalPath, FD, CD_CreateAlways, OF_None, Mode))
1030b57cec5SDimitry Andric       return errorCodeToError(EC);
1040b57cec5SDimitry Andric     raw_fd_ostream OS(FD, /*shouldClose=*/true, /*unbuffered=*/true);
1050b57cec5SDimitry Andric     OS << StringRef((const char *)Buffer.base(), BufferSize);
1060b57cec5SDimitry Andric     return Error::success();
1070b57cec5SDimitry Andric   }
1080b57cec5SDimitry Andric 
1090b57cec5SDimitry Andric private:
1100b57cec5SDimitry Andric   // Buffer may actually contain a larger memory block than BufferSize
1110b57cec5SDimitry Andric   OwningMemoryBlock Buffer;
1120b57cec5SDimitry Andric   size_t BufferSize;
1130b57cec5SDimitry Andric   unsigned Mode;
1140b57cec5SDimitry Andric };
1150b57cec5SDimitry Andric } // namespace
1160b57cec5SDimitry Andric 
1170b57cec5SDimitry Andric static Expected<std::unique_ptr<InMemoryBuffer>>
1180b57cec5SDimitry Andric createInMemoryBuffer(StringRef Path, size_t Size, unsigned Mode) {
1190b57cec5SDimitry Andric   std::error_code EC;
1200b57cec5SDimitry Andric   MemoryBlock MB = Memory::allocateMappedMemory(
1210b57cec5SDimitry Andric       Size, nullptr, sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC);
1220b57cec5SDimitry Andric   if (EC)
1230b57cec5SDimitry Andric     return errorCodeToError(EC);
1248bcb0991SDimitry Andric   return std::make_unique<InMemoryBuffer>(Path, MB, Size, Mode);
1250b57cec5SDimitry Andric }
1260b57cec5SDimitry Andric 
1270b57cec5SDimitry Andric static Expected<std::unique_ptr<FileOutputBuffer>>
1280b57cec5SDimitry Andric createOnDiskBuffer(StringRef Path, size_t Size, unsigned Mode) {
1290b57cec5SDimitry Andric   Expected<fs::TempFile> FileOrErr =
1300b57cec5SDimitry Andric       fs::TempFile::create(Path + ".tmp%%%%%%%", Mode);
1310b57cec5SDimitry Andric   if (!FileOrErr)
1320b57cec5SDimitry Andric     return FileOrErr.takeError();
1330b57cec5SDimitry Andric   fs::TempFile File = std::move(*FileOrErr);
1340b57cec5SDimitry Andric 
135fe6060f1SDimitry Andric   if (auto EC = fs::resize_file_before_mapping_readwrite(File.FD, Size)) {
1360b57cec5SDimitry Andric     consumeError(File.discard());
1370b57cec5SDimitry Andric     return errorCodeToError(EC);
1380b57cec5SDimitry Andric   }
1390b57cec5SDimitry Andric 
1400b57cec5SDimitry Andric   // Mmap it.
1410b57cec5SDimitry Andric   std::error_code EC;
142fe6060f1SDimitry Andric   fs::mapped_file_region MappedFile =
143fe6060f1SDimitry Andric       fs::mapped_file_region(fs::convertFDToNativeFile(File.FD),
144fe6060f1SDimitry Andric                              fs::mapped_file_region::readwrite, Size, 0, EC);
1450b57cec5SDimitry Andric 
1460b57cec5SDimitry Andric   // mmap(2) can fail if the underlying filesystem does not support it.
1470b57cec5SDimitry Andric   // If that happens, we fall back to in-memory buffer as the last resort.
1480b57cec5SDimitry Andric   if (EC) {
1490b57cec5SDimitry Andric     consumeError(File.discard());
1500b57cec5SDimitry Andric     return createInMemoryBuffer(Path, Size, Mode);
1510b57cec5SDimitry Andric   }
1520b57cec5SDimitry Andric 
1538bcb0991SDimitry Andric   return std::make_unique<OnDiskBuffer>(Path, std::move(File),
1540b57cec5SDimitry Andric                                          std::move(MappedFile));
1550b57cec5SDimitry Andric }
1560b57cec5SDimitry Andric 
1570b57cec5SDimitry Andric // Create an instance of FileOutputBuffer.
1580b57cec5SDimitry Andric Expected<std::unique_ptr<FileOutputBuffer>>
1590b57cec5SDimitry Andric FileOutputBuffer::create(StringRef Path, size_t Size, unsigned Flags) {
1600b57cec5SDimitry Andric   // Handle "-" as stdout just like llvm::raw_ostream does.
1610b57cec5SDimitry Andric   if (Path == "-")
1620b57cec5SDimitry Andric     return createInMemoryBuffer("-", Size, /*Mode=*/0);
1630b57cec5SDimitry Andric 
1640b57cec5SDimitry Andric   unsigned Mode = fs::all_read | fs::all_write;
1650b57cec5SDimitry Andric   if (Flags & F_executable)
1660b57cec5SDimitry Andric     Mode |= fs::all_exe;
1670b57cec5SDimitry Andric 
1685ffd83dbSDimitry Andric   // If Size is zero, don't use mmap which will fail with EINVAL.
1695ffd83dbSDimitry Andric   if (Size == 0)
1705ffd83dbSDimitry Andric     return createInMemoryBuffer(Path, Size, Mode);
1715ffd83dbSDimitry Andric 
1720b57cec5SDimitry Andric   fs::file_status Stat;
1730b57cec5SDimitry Andric   fs::status(Path, Stat);
1740b57cec5SDimitry Andric 
1750b57cec5SDimitry Andric   // Usually, we want to create OnDiskBuffer to create a temporary file in
1760b57cec5SDimitry Andric   // the same directory as the destination file and atomically replaces it
1770b57cec5SDimitry Andric   // by rename(2).
1780b57cec5SDimitry Andric   //
1790b57cec5SDimitry Andric   // However, if the destination file is a special file, we don't want to
1800b57cec5SDimitry Andric   // use rename (e.g. we don't want to replace /dev/null with a regular
1810b57cec5SDimitry Andric   // file.) If that's the case, we create an in-memory buffer, open the
1820b57cec5SDimitry Andric   // destination file and write to it on commit().
1830b57cec5SDimitry Andric   switch (Stat.type()) {
1840b57cec5SDimitry Andric   case fs::file_type::directory_file:
1850b57cec5SDimitry Andric     return errorCodeToError(errc::is_a_directory);
1860b57cec5SDimitry Andric   case fs::file_type::regular_file:
1870b57cec5SDimitry Andric   case fs::file_type::file_not_found:
1880b57cec5SDimitry Andric   case fs::file_type::status_error:
189480093f4SDimitry Andric     if (Flags & F_no_mmap)
190480093f4SDimitry Andric       return createInMemoryBuffer(Path, Size, Mode);
191480093f4SDimitry Andric     else
1920b57cec5SDimitry Andric       return createOnDiskBuffer(Path, Size, Mode);
1930b57cec5SDimitry Andric   default:
1940b57cec5SDimitry Andric     return createInMemoryBuffer(Path, Size, Mode);
1950b57cec5SDimitry Andric   }
1960b57cec5SDimitry Andric }
197