Nick Kledzik | 5fce8c4 | 2012-08-01 02:29:50 +0000 | [diff] [blame] | 1 | //===- FileOutputBuffer.cpp - File Output Buffer ----------------*- C++ -*-===// |
| 2 | // |
Chandler Carruth | 2946cd7 | 2019-01-19 08:50:56 +0000 | [diff] [blame] | 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 |
Nick Kledzik | 5fce8c4 | 2012-08-01 02:29:50 +0000 | [diff] [blame] | 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // Utility for creating a in-memory buffer that will be written to a file. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
Chandler Carruth | d990388 | 2015-01-14 11:23:27 +0000 | [diff] [blame] | 13 | #include "llvm/Support/FileOutputBuffer.h" |
Benjamin Kramer | 16132e6 | 2015-03-23 18:07:13 +0000 | [diff] [blame] | 14 | #include "llvm/ADT/STLExtras.h" |
| 15 | #include "llvm/ADT/SmallString.h" |
| 16 | #include "llvm/Support/Errc.h" |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 17 | #include "llvm/Support/Memory.h" |
Rafael Espindola | d4b24ed | 2017-01-09 21:52:35 +0000 | [diff] [blame] | 18 | #include "llvm/Support/Path.h" |
Rafael Espindola | a6e9c3e | 2014-06-12 17:38:55 +0000 | [diff] [blame] | 19 | #include <system_error> |
Nick Kledzik | 5fce8c4 | 2012-08-01 02:29:50 +0000 | [diff] [blame] | 20 | |
Rafael Espindola | 7eb1f18 | 2014-12-11 20:12:55 +0000 | [diff] [blame] | 21 | #if !defined(_MSC_VER) && !defined(__MINGW32__) |
| 22 | #include <unistd.h> |
| 23 | #else |
| 24 | #include <io.h> |
| 25 | #endif |
| 26 | |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 27 | using namespace llvm; |
| 28 | using namespace llvm::sys; |
Nick Kledzik | 5fce8c4 | 2012-08-01 02:29:50 +0000 | [diff] [blame] | 29 | |
Benjamin Kramer | 51ebcaa | 2017-11-24 14:55:41 +0000 | [diff] [blame] | 30 | namespace { |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 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: |
Rafael Espindola | 58fe67a | 2017-11-13 18:33:44 +0000 | [diff] [blame] | 36 | OnDiskBuffer(StringRef Path, fs::TempFile Temp, |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 37 | std::unique_ptr<fs::mapped_file_region> Buf) |
Rafael Espindola | 58fe67a | 2017-11-13 18:33:44 +0000 | [diff] [blame] | 38 | : FileOutputBuffer(Path), Buffer(std::move(Buf)), Temp(std::move(Temp)) {} |
Nick Kledzik | 5fce8c4 | 2012-08-01 02:29:50 +0000 | [diff] [blame] | 39 | |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 40 | uint8_t *getBufferStart() const override { return (uint8_t *)Buffer->data(); } |
| 41 | |
| 42 | uint8_t *getBufferEnd() const override { |
| 43 | return (uint8_t *)Buffer->data() + Buffer->size(); |
Nick Kledzik | 5fce8c4 | 2012-08-01 02:29:50 +0000 | [diff] [blame] | 44 | } |
| 45 | |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 46 | size_t getBufferSize() const override { return Buffer->size(); } |
| 47 | |
Rafael Espindola | 0d7a38a | 2017-11-08 01:50:29 +0000 | [diff] [blame] | 48 | Error commit() override { |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 49 | // Unmap buffer, letting OS flush dirty pages to file on disk. |
| 50 | Buffer.reset(); |
| 51 | |
| 52 | // Atomically replace the existing file with the new one. |
Rafael Espindola | 58fe67a | 2017-11-13 18:33:44 +0000 | [diff] [blame] | 53 | return Temp.keep(FinalPath); |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 54 | } |
| 55 | |
| 56 | ~OnDiskBuffer() override { |
| 57 | // Close the mapping before deleting the temp file, so that the removal |
| 58 | // succeeds. |
| 59 | Buffer.reset(); |
Rafael Espindola | 58fe67a | 2017-11-13 18:33:44 +0000 | [diff] [blame] | 60 | consumeError(Temp.discard()); |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 61 | } |
| 62 | |
Martin Storsjo | 4153e9f | 2018-08-24 18:36:22 +0000 | [diff] [blame] | 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 | |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 69 | private: |
| 70 | std::unique_ptr<fs::mapped_file_region> Buffer; |
Rafael Espindola | 58fe67a | 2017-11-13 18:33:44 +0000 | [diff] [blame] | 71 | fs::TempFile Temp; |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 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, unsigned Mode) |
| 79 | : FileOutputBuffer(Path), Buffer(Buf), Mode(Mode) {} |
| 80 | |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 81 | uint8_t *getBufferStart() const override { return (uint8_t *)Buffer.base(); } |
| 82 | |
| 83 | uint8_t *getBufferEnd() const override { |
| 84 | return (uint8_t *)Buffer.base() + Buffer.size(); |
| 85 | } |
| 86 | |
| 87 | size_t getBufferSize() const override { return Buffer.size(); } |
| 88 | |
Rafael Espindola | 0d7a38a | 2017-11-08 01:50:29 +0000 | [diff] [blame] | 89 | Error commit() override { |
Rui Ueyama | 4063cfc | 2019-01-22 18:44:04 +0000 | [diff] [blame] | 90 | if (FinalPath == "-") { |
| 91 | llvm::outs() << StringRef((const char *)Buffer.base(), Buffer.size()); |
| 92 | llvm::outs().flush(); |
| 93 | return Error::success(); |
| 94 | } |
| 95 | |
Zachary Turner | 1f67a3c | 2018-06-07 19:58:58 +0000 | [diff] [blame] | 96 | using namespace sys::fs; |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 97 | int FD; |
| 98 | std::error_code EC; |
Zachary Turner | 154a72d | 2018-06-07 20:07:08 +0000 | [diff] [blame] | 99 | if (auto EC = |
| 100 | openFileForWrite(FinalPath, FD, CD_CreateAlways, OF_None, Mode)) |
Rafael Espindola | 0d7a38a | 2017-11-08 01:50:29 +0000 | [diff] [blame] | 101 | return errorCodeToError(EC); |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 102 | raw_fd_ostream OS(FD, /*shouldClose=*/true, /*unbuffered=*/true); |
| 103 | OS << StringRef((const char *)Buffer.base(), Buffer.size()); |
Rafael Espindola | 0d7a38a | 2017-11-08 01:50:29 +0000 | [diff] [blame] | 104 | return Error::success(); |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 105 | } |
| 106 | |
| 107 | private: |
| 108 | OwningMemoryBlock Buffer; |
| 109 | unsigned Mode; |
| 110 | }; |
Benjamin Kramer | 51ebcaa | 2017-11-24 14:55:41 +0000 | [diff] [blame] | 111 | } // namespace |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 112 | |
Rui Ueyama | f6490e0 | 2017-11-08 22:57:48 +0000 | [diff] [blame] | 113 | static Expected<std::unique_ptr<InMemoryBuffer>> |
| 114 | createInMemoryBuffer(StringRef Path, size_t Size, unsigned Mode) { |
| 115 | std::error_code EC; |
| 116 | MemoryBlock MB = Memory::allocateMappedMemory( |
| 117 | Size, nullptr, sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC); |
| 118 | if (EC) |
| 119 | return errorCodeToError(EC); |
| 120 | return llvm::make_unique<InMemoryBuffer>(Path, MB, Mode); |
| 121 | } |
| 122 | |
Rui Ueyama | 21d451c | 2019-01-22 21:49:56 +0000 | [diff] [blame] | 123 | static Expected<std::unique_ptr<FileOutputBuffer>> |
Rui Ueyama | 8e7600d | 2019-01-19 00:07:57 +0000 | [diff] [blame] | 124 | createOnDiskBuffer(StringRef Path, size_t Size, unsigned Mode) { |
Rafael Espindola | 58fe67a | 2017-11-13 18:33:44 +0000 | [diff] [blame] | 125 | Expected<fs::TempFile> FileOrErr = |
| 126 | fs::TempFile::create(Path + ".tmp%%%%%%%", Mode); |
| 127 | if (!FileOrErr) |
| 128 | return FileOrErr.takeError(); |
| 129 | fs::TempFile File = std::move(*FileOrErr); |
Rafael Espindola | 7dbb577 | 2015-09-18 15:17:53 +0000 | [diff] [blame] | 130 | |
Nico Weber | 712e8d2 | 2018-04-29 00:45:03 +0000 | [diff] [blame] | 131 | #ifndef _WIN32 |
Rui Ueyama | 8e7600d | 2019-01-19 00:07:57 +0000 | [diff] [blame] | 132 | // On Windows, CreateFileMapping (the mmap function on Windows) |
| 133 | // automatically extends the underlying file. We don't need to |
| 134 | // extend the file beforehand. _chsize (ftruncate on Windows) is |
| 135 | // pretty slow just like it writes specified amount of bytes, |
| 136 | // so we should avoid calling that function. |
| 137 | if (auto EC = fs::resize_file(File.FD, Size)) { |
| 138 | consumeError(File.discard()); |
| 139 | return errorCodeToError(EC); |
Zachary Turner | 1adca7c | 2018-06-28 18:49:09 +0000 | [diff] [blame] | 140 | } |
Rui Ueyama | 8e7600d | 2019-01-19 00:07:57 +0000 | [diff] [blame] | 141 | #endif |
Rafael Espindola | c69f13b | 2014-12-12 18:13:23 +0000 | [diff] [blame] | 142 | |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 143 | // Mmap it. |
| 144 | std::error_code EC; |
| 145 | auto MappedFile = llvm::make_unique<fs::mapped_file_region>( |
Rafael Espindola | 58fe67a | 2017-11-13 18:33:44 +0000 | [diff] [blame] | 146 | File.FD, fs::mapped_file_region::readwrite, Size, 0, EC); |
Rui Ueyama | 21d451c | 2019-01-22 21:49:56 +0000 | [diff] [blame] | 147 | |
| 148 | // mmap(2) can fail if the underlying filesystem does not support it. |
| 149 | // If that happens, we fall back to in-memory buffer as the last resort. |
Rafael Espindola | 58fe67a | 2017-11-13 18:33:44 +0000 | [diff] [blame] | 150 | if (EC) { |
| 151 | consumeError(File.discard()); |
Rui Ueyama | 21d451c | 2019-01-22 21:49:56 +0000 | [diff] [blame] | 152 | return createInMemoryBuffer(Path, Size, Mode); |
Rafael Espindola | 58fe67a | 2017-11-13 18:33:44 +0000 | [diff] [blame] | 153 | } |
Rui Ueyama | 21d451c | 2019-01-22 21:49:56 +0000 | [diff] [blame] | 154 | |
Rafael Espindola | 58fe67a | 2017-11-13 18:33:44 +0000 | [diff] [blame] | 155 | return llvm::make_unique<OnDiskBuffer>(Path, std::move(File), |
| 156 | std::move(MappedFile)); |
Michael J. Spencer | 7fe24f5 | 2012-12-03 22:09:52 +0000 | [diff] [blame] | 157 | } |
Nick Kledzik | 5fce8c4 | 2012-08-01 02:29:50 +0000 | [diff] [blame] | 158 | |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 159 | // Create an instance of FileOutputBuffer. |
Rafael Espindola | e0df357 | 2017-11-08 01:05:44 +0000 | [diff] [blame] | 160 | Expected<std::unique_ptr<FileOutputBuffer>> |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 161 | FileOutputBuffer::create(StringRef Path, size_t Size, unsigned Flags) { |
Rui Ueyama | 4063cfc | 2019-01-22 18:44:04 +0000 | [diff] [blame] | 162 | // Handle "-" as stdout just like llvm::raw_ostream does. |
| 163 | if (Path == "-") |
| 164 | return createInMemoryBuffer("-", Size, /*Mode=*/0); |
| 165 | |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 166 | unsigned Mode = fs::all_read | fs::all_write; |
| 167 | if (Flags & F_executable) |
| 168 | Mode |= fs::all_exe; |
Michael J. Spencer | 7fe24f5 | 2012-12-03 22:09:52 +0000 | [diff] [blame] | 169 | |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 170 | fs::file_status Stat; |
| 171 | fs::status(Path, Stat); |
| 172 | |
| 173 | // Usually, we want to create OnDiskBuffer to create a temporary file in |
| 174 | // the same directory as the destination file and atomically replaces it |
| 175 | // by rename(2). |
| 176 | // |
| 177 | // However, if the destination file is a special file, we don't want to |
| 178 | // use rename (e.g. we don't want to replace /dev/null with a regular |
| 179 | // file.) If that's the case, we create an in-memory buffer, open the |
| 180 | // destination file and write to it on commit(). |
| 181 | switch (Stat.type()) { |
| 182 | case fs::file_type::directory_file: |
Rafael Espindola | e0df357 | 2017-11-08 01:05:44 +0000 | [diff] [blame] | 183 | return errorCodeToError(errc::is_a_directory); |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 184 | case fs::file_type::regular_file: |
| 185 | case fs::file_type::file_not_found: |
| 186 | case fs::file_type::status_error: |
Rui Ueyama | 8e7600d | 2019-01-19 00:07:57 +0000 | [diff] [blame] | 187 | return createOnDiskBuffer(Path, Size, Mode); |
Rui Ueyama | a16fe65 | 2017-11-01 21:38:14 +0000 | [diff] [blame] | 188 | default: |
Rui Ueyama | f6490e0 | 2017-11-08 22:57:48 +0000 | [diff] [blame] | 189 | return createInMemoryBuffer(Path, Size, Mode); |
Rafael Espindola | d4b24ed | 2017-01-09 21:52:35 +0000 | [diff] [blame] | 190 | } |
Nick Kledzik | 5fce8c4 | 2012-08-01 02:29:50 +0000 | [diff] [blame] | 191 | } |