Line data Source code
1 : //===- FileOutputBuffer.cpp - File Output Buffer ----------------*- C++ -*-===//
2 : //
3 : // The LLVM Compiler Infrastructure
4 : //
5 : // This file is distributed under the University of Illinois Open Source
6 : // License. See LICENSE.TXT for details.
7 : //
8 : //===----------------------------------------------------------------------===//
9 : //
10 : // Utility for creating a in-memory buffer that will be written to a file.
11 : //
12 : //===----------------------------------------------------------------------===//
13 :
14 : #include "llvm/Support/FileOutputBuffer.h"
15 : #include "llvm/ADT/STLExtras.h"
16 : #include "llvm/ADT/SmallString.h"
17 : #include "llvm/Support/Errc.h"
18 : #include "llvm/Support/Memory.h"
19 : #include "llvm/Support/Path.h"
20 : #include <system_error>
21 :
22 : #if !defined(_MSC_VER) && !defined(__MINGW32__)
23 : #include <unistd.h>
24 : #else
25 : #include <io.h>
26 : #endif
27 :
28 : using namespace llvm;
29 : using namespace llvm::sys;
30 :
31 : namespace {
32 : // A FileOutputBuffer which creates a temporary file in the same directory
33 : // as the final output file. The final output file is atomically replaced
34 : // with the temporary file on commit().
35 : class OnDiskBuffer : public FileOutputBuffer {
36 : public:
37 : OnDiskBuffer(StringRef Path, fs::TempFile Temp,
38 : std::unique_ptr<fs::mapped_file_region> Buf)
39 5327 : : FileOutputBuffer(Path), Buffer(std::move(Buf)), Temp(std::move(Temp)) {}
40 :
41 674774 : uint8_t *getBufferStart() const override { return (uint8_t *)Buffer->data(); }
42 :
43 201 : uint8_t *getBufferEnd() const override {
44 201 : return (uint8_t *)Buffer->data() + Buffer->size();
45 : }
46 :
47 956 : size_t getBufferSize() const override { return Buffer->size(); }
48 :
49 3097 : Error commit() override {
50 : // Unmap buffer, letting OS flush dirty pages to file on disk.
51 : Buffer.reset();
52 :
53 : // Atomically replace the existing file with the new one.
54 6194 : return Temp.keep(FinalPath);
55 : }
56 :
57 10652 : ~OnDiskBuffer() override {
58 : // Close the mapping before deleting the temp file, so that the removal
59 : // succeeds.
60 5326 : Buffer.reset();
61 10652 : consumeError(Temp.discard());
62 10652 : }
63 5326 :
64 : void discard() override {
65 : // Delete the temp file if it still was open, but keeping the mapping
66 : // active.
67 : consumeError(Temp.discard());
68 5326 : }
69 5326 :
70 : private:
71 : std::unique_ptr<fs::mapped_file_region> Buffer;
72 5326 : fs::TempFile Temp;
73 10652 : };
74 5326 :
75 : // A FileOutputBuffer which keeps data in memory and writes to the final
76 0 : // output file on commit(). This is used only when we cannot use OnDiskBuffer.
77 : class InMemoryBuffer : public FileOutputBuffer {
78 : public:
79 0 : InMemoryBuffer(StringRef Path, MemoryBlock Buf, unsigned Mode)
80 0 : : FileOutputBuffer(Path), Buffer(Buf), 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() + Buffer.size();
86 : }
87 :
88 : size_t getBufferSize() const override { return Buffer.size(); }
89 0 :
90 : Error commit() override {
91 : using namespace sys::fs;
92 0 : int FD;
93 : std::error_code EC;
94 776 : if (auto EC =
95 : openFileForWrite(FinalPath, FD, CD_CreateAlways, OF_None, Mode))
96 0 : return errorCodeToError(EC);
97 0 : raw_fd_ostream OS(FD, /*shouldClose=*/true, /*unbuffered=*/true);
98 : OS << StringRef((const char *)Buffer.base(), Buffer.size());
99 : return Error::success();
100 0 : }
101 :
102 108 : private:
103 : OwningMemoryBlock Buffer;
104 : unsigned Mode;
105 : };
106 108 : } // namespace
107 108 :
108 0 : static Expected<std::unique_ptr<InMemoryBuffer>>
109 216 : createInMemoryBuffer(StringRef Path, size_t Size, unsigned Mode) {
110 108 : std::error_code EC;
111 : MemoryBlock MB = Memory::allocateMappedMemory(
112 : Size, nullptr, sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC);
113 : if (EC)
114 : return errorCodeToError(EC);
115 : return llvm::make_unique<InMemoryBuffer>(Path, MB, Mode);
116 : }
117 :
118 : static Expected<std::unique_ptr<OnDiskBuffer>>
119 : createOnDiskBuffer(StringRef Path, size_t Size, bool InitExisting,
120 : unsigned Mode) {
121 345 : Expected<fs::TempFile> FileOrErr =
122 : fs::TempFile::create(Path + ".tmp%%%%%%%", Mode);
123 : if (!FileOrErr)
124 345 : return FileOrErr.takeError();
125 345 : fs::TempFile File = std::move(*FileOrErr);
126 0 :
127 345 : if (InitExisting) {
128 : if (auto EC = sys::fs::copy_file(Path, File.FD))
129 : return errorCodeToError(EC);
130 : } else {
131 5337 : #ifndef _WIN32
132 : // On Windows, CreateFileMapping (the mmap function on Windows)
133 : // automatically extends the underlying file. We don't need to
134 10674 : // extend the file beforehand. _chsize (ftruncate on Windows) is
135 5337 : // pretty slow just like it writes specified amount of bytes,
136 : // so we should avoid calling that function.
137 10654 : if (auto EC = fs::resize_file(File.FD, Size)) {
138 : consumeError(File.discard());
139 5327 : return errorCodeToError(EC);
140 2 : }
141 0 : #endif
142 : }
143 :
144 : // Mmap it.
145 : std::error_code EC;
146 : auto MappedFile = llvm::make_unique<fs::mapped_file_region>(
147 : File.FD, fs::mapped_file_region::readwrite, Size, 0, EC);
148 : if (EC) {
149 5326 : consumeError(File.discard());
150 0 : return errorCodeToError(EC);
151 0 : }
152 : return llvm::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 10654 : unsigned Mode = fs::all_read | fs::all_write;
160 5327 : if (Flags & F_executable)
161 0 : Mode |= fs::all_exe;
162 0 :
163 : fs::file_status Stat;
164 5327 : fs::status(Path, Stat);
165 :
166 : if ((Flags & F_modify) && Size == size_t(-1)) {
167 : if (Stat.type() == fs::file_type::regular_file)
168 : Size = Stat.getSize();
169 : else if (Stat.type() == fs::file_type::file_not_found)
170 5684 : return errorCodeToError(errc::no_such_file_or_directory);
171 : else
172 5684 : return errorCodeToError(errc::invalid_argument);
173 : }
174 :
175 5684 : // Usually, we want to create OnDiskBuffer to create a temporary file in
176 5684 : // the same directory as the destination file and atomically replaces it
177 : // by rename(2).
178 5684 : //
179 1 : // However, if the destination file is a special file, we don't want to
180 1 : // use rename (e.g. we don't want to replace /dev/null with a regular
181 0 : // file.) If that's the case, we create an in-memory buffer, open the
182 0 : // destination file and write to it on commit().
183 : switch (Stat.type()) {
184 0 : 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 : return createOnDiskBuffer(Path, Size, !!(Flags & F_modify), Mode);
190 : default:
191 : return createInMemoryBuffer(Path, Size, Mode);
192 : }
193 : }
|