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