15fce8c4fSNick Kledzik //===- FileOutputBuffer.cpp - File Output Buffer ----------------*- C++ -*-===//
25fce8c4fSNick Kledzik //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
65fce8c4fSNick Kledzik //
75fce8c4fSNick Kledzik //===----------------------------------------------------------------------===//
85fce8c4fSNick Kledzik //
95fce8c4fSNick Kledzik // Utility for creating a in-memory buffer that will be written to a file.
105fce8c4fSNick Kledzik //
115fce8c4fSNick Kledzik //===----------------------------------------------------------------------===//
125fce8c4fSNick Kledzik
13d9903888SChandler Carruth #include "llvm/Support/FileOutputBuffer.h"
1416132e6fSBenjamin Kramer #include "llvm/Support/Errc.h"
15f6417f5dSSimon Pilgrim #include "llvm/Support/FileSystem.h"
16a16fe65bSRui Ueyama #include "llvm/Support/Memory.h"
17a6e9c3e4SRafael Espindola #include <system_error>
185fce8c4fSNick Kledzik
197eb1f185SRafael Espindola #if !defined(_MSC_VER) && !defined(__MINGW32__)
207eb1f185SRafael Espindola #include <unistd.h>
217eb1f185SRafael Espindola #else
227eb1f185SRafael Espindola #include <io.h>
237eb1f185SRafael Espindola #endif
247eb1f185SRafael Espindola
25a16fe65bSRui Ueyama using namespace llvm;
26a16fe65bSRui Ueyama using namespace llvm::sys;
275fce8c4fSNick Kledzik
2851ebcaafSBenjamin Kramer namespace {
29a16fe65bSRui Ueyama // A FileOutputBuffer which creates a temporary file in the same directory
30a16fe65bSRui Ueyama // as the final output file. The final output file is atomically replaced
31a16fe65bSRui Ueyama // with the temporary file on commit().
32a16fe65bSRui Ueyama class OnDiskBuffer : public FileOutputBuffer {
33a16fe65bSRui Ueyama public:
OnDiskBuffer(StringRef Path,fs::TempFile Temp,fs::mapped_file_region Buf)34*0db6488aSDuncan P. N. Exon Smith OnDiskBuffer(StringRef Path, fs::TempFile Temp, fs::mapped_file_region Buf)
3558fe67a9SRafael Espindola : FileOutputBuffer(Path), Buffer(std::move(Buf)), Temp(std::move(Temp)) {}
365fce8c4fSNick Kledzik
getBufferStart() const37*0db6488aSDuncan P. N. Exon Smith uint8_t *getBufferStart() const override { return (uint8_t *)Buffer.data(); }
38a16fe65bSRui Ueyama
getBufferEnd() const39a16fe65bSRui Ueyama uint8_t *getBufferEnd() const override {
40*0db6488aSDuncan P. N. Exon Smith return (uint8_t *)Buffer.data() + Buffer.size();
41a16fe65bSRui Ueyama }
42a16fe65bSRui Ueyama
getBufferSize() const43*0db6488aSDuncan P. N. Exon Smith size_t getBufferSize() const override { return Buffer.size(); }
44a16fe65bSRui Ueyama
commit()450d7a38a8SRafael Espindola Error commit() override {
46a16fe65bSRui Ueyama // Unmap buffer, letting OS flush dirty pages to file on disk.
47*0db6488aSDuncan P. N. Exon Smith Buffer.unmap();
48a16fe65bSRui Ueyama
49a16fe65bSRui Ueyama // Atomically replace the existing file with the new one.
5058fe67a9SRafael Espindola return Temp.keep(FinalPath);
51a16fe65bSRui Ueyama }
52a16fe65bSRui Ueyama
~OnDiskBuffer()53a16fe65bSRui Ueyama ~OnDiskBuffer() override {
541a4398a1SReid Kleckner // Close the mapping before deleting the temp file, so that the removal
551a4398a1SReid Kleckner // succeeds.
56*0db6488aSDuncan P. N. Exon Smith Buffer.unmap();
5758fe67a9SRafael Espindola consumeError(Temp.discard());
585fce8c4fSNick Kledzik }
595fce8c4fSNick Kledzik
discard()604153e9fbSMartin Storsjo void discard() override {
614153e9fbSMartin Storsjo // Delete the temp file if it still was open, but keeping the mapping
624153e9fbSMartin Storsjo // active.
634153e9fbSMartin Storsjo consumeError(Temp.discard());
644153e9fbSMartin Storsjo }
654153e9fbSMartin Storsjo
66a16fe65bSRui Ueyama private:
67*0db6488aSDuncan P. N. Exon Smith fs::mapped_file_region Buffer;
6858fe67a9SRafael Espindola fs::TempFile Temp;
69a16fe65bSRui Ueyama };
70a16fe65bSRui Ueyama
71a16fe65bSRui Ueyama // A FileOutputBuffer which keeps data in memory and writes to the final
72a16fe65bSRui Ueyama // output file on commit(). This is used only when we cannot use OnDiskBuffer.
73a16fe65bSRui Ueyama class InMemoryBuffer : public FileOutputBuffer {
74a16fe65bSRui Ueyama public:
InMemoryBuffer(StringRef Path,MemoryBlock Buf,std::size_t BufSize,unsigned Mode)7593d2bddaSLang Hames InMemoryBuffer(StringRef Path, MemoryBlock Buf, std::size_t BufSize,
7693d2bddaSLang Hames unsigned Mode)
7793d2bddaSLang Hames : FileOutputBuffer(Path), Buffer(Buf), BufferSize(BufSize),
7893d2bddaSLang Hames Mode(Mode) {}
79a16fe65bSRui Ueyama
getBufferStart() const80a16fe65bSRui Ueyama uint8_t *getBufferStart() const override { return (uint8_t *)Buffer.base(); }
81a16fe65bSRui Ueyama
getBufferEnd() const82a16fe65bSRui Ueyama uint8_t *getBufferEnd() const override {
8393d2bddaSLang Hames return (uint8_t *)Buffer.base() + BufferSize;
84a16fe65bSRui Ueyama }
85a16fe65bSRui Ueyama
getBufferSize() const8693d2bddaSLang Hames size_t getBufferSize() const override { return BufferSize; }
87a16fe65bSRui Ueyama
commit()880d7a38a8SRafael Espindola Error commit() override {
894063cfc7SRui Ueyama if (FinalPath == "-") {
9093d2bddaSLang Hames llvm::outs() << StringRef((const char *)Buffer.base(), BufferSize);
914063cfc7SRui Ueyama llvm::outs().flush();
924063cfc7SRui Ueyama return Error::success();
934063cfc7SRui Ueyama }
944063cfc7SRui Ueyama
951f67a3cbSZachary Turner using namespace sys::fs;
96d4b24edaSRafael Espindola int FD;
97a16fe65bSRui Ueyama std::error_code EC;
98154a72ddSZachary Turner if (auto EC =
99154a72ddSZachary Turner openFileForWrite(FinalPath, FD, CD_CreateAlways, OF_None, Mode))
1000d7a38a8SRafael Espindola return errorCodeToError(EC);
101a16fe65bSRui Ueyama raw_fd_ostream OS(FD, /*shouldClose=*/true, /*unbuffered=*/true);
10293d2bddaSLang Hames OS << StringRef((const char *)Buffer.base(), BufferSize);
1030d7a38a8SRafael Espindola return Error::success();
104d4b24edaSRafael Espindola }
105d4b24edaSRafael Espindola
106a16fe65bSRui Ueyama private:
10793d2bddaSLang Hames // Buffer may actually contain a larger memory block than BufferSize
108a16fe65bSRui Ueyama OwningMemoryBlock Buffer;
10993d2bddaSLang Hames size_t BufferSize;
110a16fe65bSRui Ueyama unsigned Mode;
111a16fe65bSRui Ueyama };
11251ebcaafSBenjamin Kramer } // namespace
113a16fe65bSRui Ueyama
114f6490e04SRui Ueyama static Expected<std::unique_ptr<InMemoryBuffer>>
createInMemoryBuffer(StringRef Path,size_t Size,unsigned Mode)115f6490e04SRui Ueyama createInMemoryBuffer(StringRef Path, size_t Size, unsigned Mode) {
116f6490e04SRui Ueyama std::error_code EC;
117f6490e04SRui Ueyama MemoryBlock MB = Memory::allocateMappedMemory(
118f6490e04SRui Ueyama Size, nullptr, sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC);
119f6490e04SRui Ueyama if (EC)
120f6490e04SRui Ueyama return errorCodeToError(EC);
1210eaee545SJonas Devlieghere return std::make_unique<InMemoryBuffer>(Path, MB, Size, Mode);
122f6490e04SRui Ueyama }
123f6490e04SRui Ueyama
12421d451caSRui Ueyama static Expected<std::unique_ptr<FileOutputBuffer>>
createOnDiskBuffer(StringRef Path,size_t Size,unsigned Mode)125021de7cfSAlexey Lapshin createOnDiskBuffer(StringRef Path, size_t Size, unsigned Mode) {
12658fe67a9SRafael Espindola Expected<fs::TempFile> FileOrErr =
12758fe67a9SRafael Espindola fs::TempFile::create(Path + ".tmp%%%%%%%", Mode);
12858fe67a9SRafael Espindola if (!FileOrErr)
12958fe67a9SRafael Espindola return FileOrErr.takeError();
13058fe67a9SRafael Espindola fs::TempFile File = std::move(*FileOrErr);
1317dbb5778SRafael Espindola
132429088b9SDuncan P. N. Exon Smith if (auto EC = fs::resize_file_before_mapping_readwrite(File.FD, Size)) {
13358fe67a9SRafael Espindola consumeError(File.discard());
134e0df357dSRafael Espindola return errorCodeToError(EC);
13558fe67a9SRafael Espindola }
136c69f13bfSRafael Espindola
137a16fe65bSRui Ueyama // Mmap it.
138a16fe65bSRui Ueyama std::error_code EC;
139*0db6488aSDuncan P. N. Exon Smith fs::mapped_file_region MappedFile =
140*0db6488aSDuncan P. N. Exon Smith fs::mapped_file_region(fs::convertFDToNativeFile(File.FD),
141*0db6488aSDuncan P. N. Exon Smith fs::mapped_file_region::readwrite, Size, 0, EC);
14221d451caSRui Ueyama
14321d451caSRui Ueyama // mmap(2) can fail if the underlying filesystem does not support it.
14421d451caSRui Ueyama // If that happens, we fall back to in-memory buffer as the last resort.
14558fe67a9SRafael Espindola if (EC) {
14658fe67a9SRafael Espindola consumeError(File.discard());
14721d451caSRui Ueyama return createInMemoryBuffer(Path, Size, Mode);
14858fe67a9SRafael Espindola }
14921d451caSRui Ueyama
1500eaee545SJonas Devlieghere return std::make_unique<OnDiskBuffer>(Path, std::move(File),
15158fe67a9SRafael Espindola std::move(MappedFile));
1525fce8c4fSNick Kledzik }
1535fce8c4fSNick Kledzik
154a16fe65bSRui Ueyama // Create an instance of FileOutputBuffer.
155e0df357dSRafael Espindola Expected<std::unique_ptr<FileOutputBuffer>>
create(StringRef Path,size_t Size,unsigned Flags)156021de7cfSAlexey Lapshin FileOutputBuffer::create(StringRef Path, size_t Size, unsigned Flags) {
1574063cfc7SRui Ueyama // Handle "-" as stdout just like llvm::raw_ostream does.
1584063cfc7SRui Ueyama if (Path == "-")
1594063cfc7SRui Ueyama return createInMemoryBuffer("-", Size, /*Mode=*/0);
1604063cfc7SRui Ueyama
161a16fe65bSRui Ueyama unsigned Mode = fs::all_read | fs::all_write;
162a16fe65bSRui Ueyama if (Flags & F_executable)
163a16fe65bSRui Ueyama Mode |= fs::all_exe;
1645fce8c4fSNick Kledzik
1659d53db2aSFangrui Song // If Size is zero, don't use mmap which will fail with EINVAL.
1669d53db2aSFangrui Song if (Size == 0)
1679d53db2aSFangrui Song return createInMemoryBuffer(Path, Size, Mode);
1689d53db2aSFangrui Song
169a16fe65bSRui Ueyama fs::file_status Stat;
170a16fe65bSRui Ueyama fs::status(Path, Stat);
171d4b24edaSRafael Espindola
172a16fe65bSRui Ueyama // Usually, we want to create OnDiskBuffer to create a temporary file in
173a16fe65bSRui Ueyama // the same directory as the destination file and atomically replaces it
174a16fe65bSRui Ueyama // by rename(2).
175a16fe65bSRui Ueyama //
176a16fe65bSRui Ueyama // However, if the destination file is a special file, we don't want to
177a16fe65bSRui Ueyama // use rename (e.g. we don't want to replace /dev/null with a regular
178a16fe65bSRui Ueyama // file.) If that's the case, we create an in-memory buffer, open the
179a16fe65bSRui Ueyama // destination file and write to it on commit().
180a16fe65bSRui Ueyama switch (Stat.type()) {
181a16fe65bSRui Ueyama case fs::file_type::directory_file:
182e0df357dSRafael Espindola return errorCodeToError(errc::is_a_directory);
183a16fe65bSRui Ueyama case fs::file_type::regular_file:
184a16fe65bSRui Ueyama case fs::file_type::file_not_found:
185a16fe65bSRui Ueyama case fs::file_type::status_error:
18668142324SNick Terrell if (Flags & F_no_mmap)
18768142324SNick Terrell return createInMemoryBuffer(Path, Size, Mode);
18868142324SNick Terrell else
189021de7cfSAlexey Lapshin return createOnDiskBuffer(Path, Size, Mode);
190a16fe65bSRui Ueyama default:
191f6490e04SRui Ueyama return createInMemoryBuffer(Path, Size, Mode);
1925fce8c4fSNick Kledzik }
193a16fe65bSRui Ueyama }
194