xref: /freebsd/contrib/llvm-project/llvm/lib/Support/FileOutputBuffer.cpp (revision fe6060f10f634930ff71b7c50291ddc610da2475)
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/ADT/STLExtras.h"
150b57cec5SDimitry Andric #include "llvm/Support/Errc.h"
165ffd83dbSDimitry Andric #include "llvm/Support/FileSystem.h"
170b57cec5SDimitry Andric #include "llvm/Support/Memory.h"
180b57cec5SDimitry Andric #include "llvm/Support/Path.h"
190b57cec5SDimitry Andric #include <system_error>
200b57cec5SDimitry Andric 
210b57cec5SDimitry Andric #if !defined(_MSC_VER) && !defined(__MINGW32__)
220b57cec5SDimitry Andric #include <unistd.h>
230b57cec5SDimitry Andric #else
240b57cec5SDimitry Andric #include <io.h>
250b57cec5SDimitry Andric #endif
260b57cec5SDimitry Andric 
270b57cec5SDimitry Andric using namespace llvm;
280b57cec5SDimitry Andric using namespace llvm::sys;
290b57cec5SDimitry Andric 
300b57cec5SDimitry Andric namespace {
310b57cec5SDimitry Andric // A FileOutputBuffer which creates a temporary file in the same directory
320b57cec5SDimitry Andric // as the final output file. The final output file is atomically replaced
330b57cec5SDimitry Andric // with the temporary file on commit().
340b57cec5SDimitry Andric class OnDiskBuffer : public FileOutputBuffer {
350b57cec5SDimitry Andric public:
36*fe6060f1SDimitry Andric   OnDiskBuffer(StringRef Path, fs::TempFile Temp, fs::mapped_file_region Buf)
370b57cec5SDimitry Andric       : FileOutputBuffer(Path), Buffer(std::move(Buf)), Temp(std::move(Temp)) {}
380b57cec5SDimitry Andric 
39*fe6060f1SDimitry Andric   uint8_t *getBufferStart() const override { return (uint8_t *)Buffer.data(); }
400b57cec5SDimitry Andric 
410b57cec5SDimitry Andric   uint8_t *getBufferEnd() const override {
42*fe6060f1SDimitry Andric     return (uint8_t *)Buffer.data() + Buffer.size();
430b57cec5SDimitry Andric   }
440b57cec5SDimitry Andric 
45*fe6060f1SDimitry Andric   size_t getBufferSize() const override { return Buffer.size(); }
460b57cec5SDimitry Andric 
470b57cec5SDimitry Andric   Error commit() override {
480b57cec5SDimitry Andric     // Unmap buffer, letting OS flush dirty pages to file on disk.
49*fe6060f1SDimitry Andric     Buffer.unmap();
500b57cec5SDimitry Andric 
510b57cec5SDimitry Andric     // Atomically replace the existing file with the new one.
520b57cec5SDimitry Andric     return Temp.keep(FinalPath);
530b57cec5SDimitry Andric   }
540b57cec5SDimitry Andric 
550b57cec5SDimitry Andric   ~OnDiskBuffer() override {
560b57cec5SDimitry Andric     // Close the mapping before deleting the temp file, so that the removal
570b57cec5SDimitry Andric     // succeeds.
58*fe6060f1SDimitry Andric     Buffer.unmap();
590b57cec5SDimitry Andric     consumeError(Temp.discard());
600b57cec5SDimitry Andric   }
610b57cec5SDimitry Andric 
620b57cec5SDimitry Andric   void discard() override {
630b57cec5SDimitry Andric     // Delete the temp file if it still was open, but keeping the mapping
640b57cec5SDimitry Andric     // active.
650b57cec5SDimitry Andric     consumeError(Temp.discard());
660b57cec5SDimitry Andric   }
670b57cec5SDimitry Andric 
680b57cec5SDimitry Andric private:
69*fe6060f1SDimitry Andric   fs::mapped_file_region Buffer;
700b57cec5SDimitry Andric   fs::TempFile Temp;
710b57cec5SDimitry Andric };
720b57cec5SDimitry Andric 
730b57cec5SDimitry Andric // A FileOutputBuffer which keeps data in memory and writes to the final
740b57cec5SDimitry Andric // output file on commit(). This is used only when we cannot use OnDiskBuffer.
750b57cec5SDimitry Andric class InMemoryBuffer : public FileOutputBuffer {
760b57cec5SDimitry Andric public:
770b57cec5SDimitry Andric   InMemoryBuffer(StringRef Path, MemoryBlock Buf, std::size_t BufSize,
780b57cec5SDimitry Andric                  unsigned Mode)
790b57cec5SDimitry Andric       : FileOutputBuffer(Path), Buffer(Buf), BufferSize(BufSize),
800b57cec5SDimitry Andric         Mode(Mode) {}
810b57cec5SDimitry Andric 
820b57cec5SDimitry Andric   uint8_t *getBufferStart() const override { return (uint8_t *)Buffer.base(); }
830b57cec5SDimitry Andric 
840b57cec5SDimitry Andric   uint8_t *getBufferEnd() const override {
850b57cec5SDimitry Andric     return (uint8_t *)Buffer.base() + BufferSize;
860b57cec5SDimitry Andric   }
870b57cec5SDimitry Andric 
880b57cec5SDimitry Andric   size_t getBufferSize() const override { return BufferSize; }
890b57cec5SDimitry Andric 
900b57cec5SDimitry Andric   Error commit() override {
910b57cec5SDimitry Andric     if (FinalPath == "-") {
920b57cec5SDimitry Andric       llvm::outs() << StringRef((const char *)Buffer.base(), BufferSize);
930b57cec5SDimitry Andric       llvm::outs().flush();
940b57cec5SDimitry Andric       return Error::success();
950b57cec5SDimitry Andric     }
960b57cec5SDimitry Andric 
970b57cec5SDimitry Andric     using namespace sys::fs;
980b57cec5SDimitry Andric     int FD;
990b57cec5SDimitry Andric     std::error_code EC;
1000b57cec5SDimitry Andric     if (auto EC =
1010b57cec5SDimitry Andric             openFileForWrite(FinalPath, FD, CD_CreateAlways, OF_None, Mode))
1020b57cec5SDimitry Andric       return errorCodeToError(EC);
1030b57cec5SDimitry Andric     raw_fd_ostream OS(FD, /*shouldClose=*/true, /*unbuffered=*/true);
1040b57cec5SDimitry Andric     OS << StringRef((const char *)Buffer.base(), BufferSize);
1050b57cec5SDimitry Andric     return Error::success();
1060b57cec5SDimitry Andric   }
1070b57cec5SDimitry Andric 
1080b57cec5SDimitry Andric private:
1090b57cec5SDimitry Andric   // Buffer may actually contain a larger memory block than BufferSize
1100b57cec5SDimitry Andric   OwningMemoryBlock Buffer;
1110b57cec5SDimitry Andric   size_t BufferSize;
1120b57cec5SDimitry Andric   unsigned Mode;
1130b57cec5SDimitry Andric };
1140b57cec5SDimitry Andric } // namespace
1150b57cec5SDimitry Andric 
1160b57cec5SDimitry Andric static Expected<std::unique_ptr<InMemoryBuffer>>
1170b57cec5SDimitry Andric createInMemoryBuffer(StringRef Path, size_t Size, unsigned Mode) {
1180b57cec5SDimitry Andric   std::error_code EC;
1190b57cec5SDimitry Andric   MemoryBlock MB = Memory::allocateMappedMemory(
1200b57cec5SDimitry Andric       Size, nullptr, sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC);
1210b57cec5SDimitry Andric   if (EC)
1220b57cec5SDimitry Andric     return errorCodeToError(EC);
1238bcb0991SDimitry Andric   return std::make_unique<InMemoryBuffer>(Path, MB, Size, Mode);
1240b57cec5SDimitry Andric }
1250b57cec5SDimitry Andric 
1260b57cec5SDimitry Andric static Expected<std::unique_ptr<FileOutputBuffer>>
1270b57cec5SDimitry Andric createOnDiskBuffer(StringRef Path, size_t Size, unsigned Mode) {
1280b57cec5SDimitry Andric   Expected<fs::TempFile> FileOrErr =
1290b57cec5SDimitry Andric       fs::TempFile::create(Path + ".tmp%%%%%%%", Mode);
1300b57cec5SDimitry Andric   if (!FileOrErr)
1310b57cec5SDimitry Andric     return FileOrErr.takeError();
1320b57cec5SDimitry Andric   fs::TempFile File = std::move(*FileOrErr);
1330b57cec5SDimitry Andric 
134*fe6060f1SDimitry Andric   if (auto EC = fs::resize_file_before_mapping_readwrite(File.FD, Size)) {
1350b57cec5SDimitry Andric     consumeError(File.discard());
1360b57cec5SDimitry Andric     return errorCodeToError(EC);
1370b57cec5SDimitry Andric   }
1380b57cec5SDimitry Andric 
1390b57cec5SDimitry Andric   // Mmap it.
1400b57cec5SDimitry Andric   std::error_code EC;
141*fe6060f1SDimitry Andric   fs::mapped_file_region MappedFile =
142*fe6060f1SDimitry Andric       fs::mapped_file_region(fs::convertFDToNativeFile(File.FD),
143*fe6060f1SDimitry Andric                              fs::mapped_file_region::readwrite, Size, 0, EC);
1440b57cec5SDimitry Andric 
1450b57cec5SDimitry Andric   // mmap(2) can fail if the underlying filesystem does not support it.
1460b57cec5SDimitry Andric   // If that happens, we fall back to in-memory buffer as the last resort.
1470b57cec5SDimitry Andric   if (EC) {
1480b57cec5SDimitry Andric     consumeError(File.discard());
1490b57cec5SDimitry Andric     return createInMemoryBuffer(Path, Size, Mode);
1500b57cec5SDimitry Andric   }
1510b57cec5SDimitry Andric 
1528bcb0991SDimitry Andric   return std::make_unique<OnDiskBuffer>(Path, std::move(File),
1530b57cec5SDimitry Andric                                          std::move(MappedFile));
1540b57cec5SDimitry Andric }
1550b57cec5SDimitry Andric 
1560b57cec5SDimitry Andric // Create an instance of FileOutputBuffer.
1570b57cec5SDimitry Andric Expected<std::unique_ptr<FileOutputBuffer>>
1580b57cec5SDimitry Andric FileOutputBuffer::create(StringRef Path, size_t Size, unsigned Flags) {
1590b57cec5SDimitry Andric   // Handle "-" as stdout just like llvm::raw_ostream does.
1600b57cec5SDimitry Andric   if (Path == "-")
1610b57cec5SDimitry Andric     return createInMemoryBuffer("-", Size, /*Mode=*/0);
1620b57cec5SDimitry Andric 
1630b57cec5SDimitry Andric   unsigned Mode = fs::all_read | fs::all_write;
1640b57cec5SDimitry Andric   if (Flags & F_executable)
1650b57cec5SDimitry Andric     Mode |= fs::all_exe;
1660b57cec5SDimitry Andric 
1675ffd83dbSDimitry Andric   // If Size is zero, don't use mmap which will fail with EINVAL.
1685ffd83dbSDimitry Andric   if (Size == 0)
1695ffd83dbSDimitry Andric     return createInMemoryBuffer(Path, Size, Mode);
1705ffd83dbSDimitry Andric 
1710b57cec5SDimitry Andric   fs::file_status Stat;
1720b57cec5SDimitry Andric   fs::status(Path, Stat);
1730b57cec5SDimitry Andric 
1740b57cec5SDimitry Andric   // Usually, we want to create OnDiskBuffer to create a temporary file in
1750b57cec5SDimitry Andric   // the same directory as the destination file and atomically replaces it
1760b57cec5SDimitry Andric   // by rename(2).
1770b57cec5SDimitry Andric   //
1780b57cec5SDimitry Andric   // However, if the destination file is a special file, we don't want to
1790b57cec5SDimitry Andric   // use rename (e.g. we don't want to replace /dev/null with a regular
1800b57cec5SDimitry Andric   // file.) If that's the case, we create an in-memory buffer, open the
1810b57cec5SDimitry Andric   // destination file and write to it on commit().
1820b57cec5SDimitry Andric   switch (Stat.type()) {
1830b57cec5SDimitry Andric   case fs::file_type::directory_file:
1840b57cec5SDimitry Andric     return errorCodeToError(errc::is_a_directory);
1850b57cec5SDimitry Andric   case fs::file_type::regular_file:
1860b57cec5SDimitry Andric   case fs::file_type::file_not_found:
1870b57cec5SDimitry Andric   case fs::file_type::status_error:
188480093f4SDimitry Andric     if (Flags & F_no_mmap)
189480093f4SDimitry Andric       return createInMemoryBuffer(Path, Size, Mode);
190480093f4SDimitry Andric     else
1910b57cec5SDimitry Andric       return createOnDiskBuffer(Path, Size, Mode);
1920b57cec5SDimitry Andric   default:
1930b57cec5SDimitry Andric     return createInMemoryBuffer(Path, Size, Mode);
1940b57cec5SDimitry Andric   }
1950b57cec5SDimitry Andric }
196