xref: /freebsd/contrib/llvm-project/llvm/lib/Support/FileOutputBuffer.cpp (revision 8bcb0991864975618c09697b1aca10683346d9f0)
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/ADT/SmallString.h"
160b57cec5SDimitry Andric #include "llvm/Support/Errc.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:
360b57cec5SDimitry Andric   OnDiskBuffer(StringRef Path, fs::TempFile Temp,
370b57cec5SDimitry Andric                std::unique_ptr<fs::mapped_file_region> Buf)
380b57cec5SDimitry Andric       : FileOutputBuffer(Path), Buffer(std::move(Buf)), Temp(std::move(Temp)) {}
390b57cec5SDimitry Andric 
400b57cec5SDimitry Andric   uint8_t *getBufferStart() const override { return (uint8_t *)Buffer->data(); }
410b57cec5SDimitry Andric 
420b57cec5SDimitry Andric   uint8_t *getBufferEnd() const override {
430b57cec5SDimitry Andric     return (uint8_t *)Buffer->data() + Buffer->size();
440b57cec5SDimitry Andric   }
450b57cec5SDimitry Andric 
460b57cec5SDimitry Andric   size_t getBufferSize() const override { return Buffer->size(); }
470b57cec5SDimitry Andric 
480b57cec5SDimitry Andric   Error commit() override {
490b57cec5SDimitry Andric     // Unmap buffer, letting OS flush dirty pages to file on disk.
500b57cec5SDimitry Andric     Buffer.reset();
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.
590b57cec5SDimitry Andric     Buffer.reset();
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:
700b57cec5SDimitry Andric   std::unique_ptr<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);
124*8bcb0991SDimitry 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 
1350b57cec5SDimitry Andric #ifndef _WIN32
1360b57cec5SDimitry Andric   // On Windows, CreateFileMapping (the mmap function on Windows)
1370b57cec5SDimitry Andric   // automatically extends the underlying file. We don't need to
1380b57cec5SDimitry Andric   // extend the file beforehand. _chsize (ftruncate on Windows) is
1390b57cec5SDimitry Andric   // pretty slow just like it writes specified amount of bytes,
1400b57cec5SDimitry Andric   // so we should avoid calling that function.
1410b57cec5SDimitry Andric   if (auto EC = fs::resize_file(File.FD, Size)) {
1420b57cec5SDimitry Andric     consumeError(File.discard());
1430b57cec5SDimitry Andric     return errorCodeToError(EC);
1440b57cec5SDimitry Andric   }
1450b57cec5SDimitry Andric #endif
1460b57cec5SDimitry Andric 
1470b57cec5SDimitry Andric   // Mmap it.
1480b57cec5SDimitry Andric   std::error_code EC;
149*8bcb0991SDimitry Andric   auto MappedFile = std::make_unique<fs::mapped_file_region>(
1500b57cec5SDimitry Andric       fs::convertFDToNativeFile(File.FD), fs::mapped_file_region::readwrite,
1510b57cec5SDimitry Andric       Size, 0, EC);
1520b57cec5SDimitry Andric 
1530b57cec5SDimitry Andric   // mmap(2) can fail if the underlying filesystem does not support it.
1540b57cec5SDimitry Andric   // If that happens, we fall back to in-memory buffer as the last resort.
1550b57cec5SDimitry Andric   if (EC) {
1560b57cec5SDimitry Andric     consumeError(File.discard());
1570b57cec5SDimitry Andric     return createInMemoryBuffer(Path, Size, Mode);
1580b57cec5SDimitry Andric   }
1590b57cec5SDimitry Andric 
160*8bcb0991SDimitry Andric   return std::make_unique<OnDiskBuffer>(Path, std::move(File),
1610b57cec5SDimitry Andric                                          std::move(MappedFile));
1620b57cec5SDimitry Andric }
1630b57cec5SDimitry Andric 
1640b57cec5SDimitry Andric // Create an instance of FileOutputBuffer.
1650b57cec5SDimitry Andric Expected<std::unique_ptr<FileOutputBuffer>>
1660b57cec5SDimitry Andric FileOutputBuffer::create(StringRef Path, size_t Size, unsigned Flags) {
1670b57cec5SDimitry Andric   // Handle "-" as stdout just like llvm::raw_ostream does.
1680b57cec5SDimitry Andric   if (Path == "-")
1690b57cec5SDimitry Andric     return createInMemoryBuffer("-", Size, /*Mode=*/0);
1700b57cec5SDimitry Andric 
1710b57cec5SDimitry Andric   unsigned Mode = fs::all_read | fs::all_write;
1720b57cec5SDimitry Andric   if (Flags & F_executable)
1730b57cec5SDimitry Andric     Mode |= fs::all_exe;
1740b57cec5SDimitry Andric 
1750b57cec5SDimitry Andric   fs::file_status Stat;
1760b57cec5SDimitry Andric   fs::status(Path, Stat);
1770b57cec5SDimitry Andric 
1780b57cec5SDimitry Andric   // Usually, we want to create OnDiskBuffer to create a temporary file in
1790b57cec5SDimitry Andric   // the same directory as the destination file and atomically replaces it
1800b57cec5SDimitry Andric   // by rename(2).
1810b57cec5SDimitry Andric   //
1820b57cec5SDimitry Andric   // However, if the destination file is a special file, we don't want to
1830b57cec5SDimitry Andric   // use rename (e.g. we don't want to replace /dev/null with a regular
1840b57cec5SDimitry Andric   // file.) If that's the case, we create an in-memory buffer, open the
1850b57cec5SDimitry Andric   // destination file and write to it on commit().
1860b57cec5SDimitry Andric   switch (Stat.type()) {
1870b57cec5SDimitry Andric   case fs::file_type::directory_file:
1880b57cec5SDimitry Andric     return errorCodeToError(errc::is_a_directory);
1890b57cec5SDimitry Andric   case fs::file_type::regular_file:
1900b57cec5SDimitry Andric   case fs::file_type::file_not_found:
1910b57cec5SDimitry Andric   case fs::file_type::status_error:
1920b57cec5SDimitry Andric     return createOnDiskBuffer(Path, Size, Mode);
1930b57cec5SDimitry Andric   default:
1940b57cec5SDimitry Andric     return createInMemoryBuffer(Path, Size, Mode);
1950b57cec5SDimitry Andric   }
1960b57cec5SDimitry Andric }
197