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