blob: 8906be3aaa25561e05754e6a8348931df3a10ded [file] [log] [blame]
Nick Kledzik5fce8c42012-08-01 02:29:50 +00001//===- 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
Chandler Carruthd9903882015-01-14 11:23:27 +000014#include "llvm/Support/FileOutputBuffer.h"
Benjamin Kramer16132e62015-03-23 18:07:13 +000015#include "llvm/ADT/STLExtras.h"
16#include "llvm/ADT/SmallString.h"
17#include "llvm/Support/Errc.h"
Rui Ueyamaa16fe652017-11-01 21:38:14 +000018#include "llvm/Support/Memory.h"
Rafael Espindolad4b24ed2017-01-09 21:52:35 +000019#include "llvm/Support/Path.h"
Rafael Espindola7dbb5772015-09-18 15:17:53 +000020#include "llvm/Support/Signals.h"
Rafael Espindolaa6e9c3e2014-06-12 17:38:55 +000021#include <system_error>
Nick Kledzik5fce8c42012-08-01 02:29:50 +000022
Rafael Espindola7eb1f182014-12-11 20:12:55 +000023#if !defined(_MSC_VER) && !defined(__MINGW32__)
24#include <unistd.h>
25#else
26#include <io.h>
27#endif
28
Rui Ueyamaa16fe652017-11-01 21:38:14 +000029using namespace llvm;
30using namespace llvm::sys;
Nick Kledzik5fce8c42012-08-01 02:29:50 +000031
Rui Ueyamaa16fe652017-11-01 21:38:14 +000032// 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().
35class OnDiskBuffer : public FileOutputBuffer {
36public:
37 OnDiskBuffer(StringRef Path, StringRef TempPath,
38 std::unique_ptr<fs::mapped_file_region> Buf)
39 : FileOutputBuffer(Path), Buffer(std::move(Buf)), TempPath(TempPath) {}
Nick Kledzik5fce8c42012-08-01 02:29:50 +000040
Rafael Espindolae0df3572017-11-08 01:05:44 +000041 static Expected<std::unique_ptr<OnDiskBuffer>>
Rui Ueyamaa16fe652017-11-01 21:38:14 +000042 create(StringRef Path, size_t Size, unsigned Mode);
Nick Kledzik5fce8c42012-08-01 02:29:50 +000043
Rui Ueyamaa16fe652017-11-01 21:38:14 +000044 uint8_t *getBufferStart() const override { return (uint8_t *)Buffer->data(); }
45
46 uint8_t *getBufferEnd() const override {
47 return (uint8_t *)Buffer->data() + Buffer->size();
Nick Kledzik5fce8c42012-08-01 02:29:50 +000048 }
49
Rui Ueyamaa16fe652017-11-01 21:38:14 +000050 size_t getBufferSize() const override { return Buffer->size(); }
51
Rafael Espindola0d7a38a2017-11-08 01:50:29 +000052 Error commit() override {
Rui Ueyamaa16fe652017-11-01 21:38:14 +000053 // Unmap buffer, letting OS flush dirty pages to file on disk.
54 Buffer.reset();
55
56 // Atomically replace the existing file with the new one.
57 auto EC = fs::rename(TempPath, FinalPath);
58 sys::DontRemoveFileOnSignal(TempPath);
Rafael Espindola0d7a38a2017-11-08 01:50:29 +000059 return errorCodeToError(EC);
Rui Ueyamaa16fe652017-11-01 21:38:14 +000060 }
61
62 ~OnDiskBuffer() override {
63 // Close the mapping before deleting the temp file, so that the removal
64 // succeeds.
65 Buffer.reset();
66 fs::remove(TempPath);
67 }
68
69private:
70 std::unique_ptr<fs::mapped_file_region> Buffer;
71 std::string TempPath;
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.
76class InMemoryBuffer : public FileOutputBuffer {
77public:
78 InMemoryBuffer(StringRef Path, MemoryBlock Buf, unsigned Mode)
79 : FileOutputBuffer(Path), Buffer(Buf), Mode(Mode) {}
80
Rafael Espindolae0df3572017-11-08 01:05:44 +000081 static Expected<std::unique_ptr<InMemoryBuffer>>
Rui Ueyamaa16fe652017-11-01 21:38:14 +000082 create(StringRef Path, size_t Size, unsigned Mode) {
83 std::error_code EC;
84 MemoryBlock MB = Memory::allocateMappedMemory(
85 Size, nullptr, sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC);
86 if (EC)
Rafael Espindolae0df3572017-11-08 01:05:44 +000087 return errorCodeToError(EC);
Rui Ueyamaa16fe652017-11-01 21:38:14 +000088 return llvm::make_unique<InMemoryBuffer>(Path, MB, Mode);
89 }
90
91 uint8_t *getBufferStart() const override { return (uint8_t *)Buffer.base(); }
92
93 uint8_t *getBufferEnd() const override {
94 return (uint8_t *)Buffer.base() + Buffer.size();
95 }
96
97 size_t getBufferSize() const override { return Buffer.size(); }
98
Rafael Espindola0d7a38a2017-11-08 01:50:29 +000099 Error commit() override {
Rui Ueyamaa16fe652017-11-01 21:38:14 +0000100 int FD;
101 std::error_code EC;
102 if (auto EC = openFileForWrite(FinalPath, FD, fs::F_None, Mode))
Rafael Espindola0d7a38a2017-11-08 01:50:29 +0000103 return errorCodeToError(EC);
Rui Ueyamaa16fe652017-11-01 21:38:14 +0000104 raw_fd_ostream OS(FD, /*shouldClose=*/true, /*unbuffered=*/true);
105 OS << StringRef((const char *)Buffer.base(), Buffer.size());
Rafael Espindola0d7a38a2017-11-08 01:50:29 +0000106 return Error::success();
Rui Ueyamaa16fe652017-11-01 21:38:14 +0000107 }
108
109private:
110 OwningMemoryBlock Buffer;
111 unsigned Mode;
112};
113
Rafael Espindolae0df3572017-11-08 01:05:44 +0000114Expected<std::unique_ptr<OnDiskBuffer>>
Rui Ueyamaa16fe652017-11-01 21:38:14 +0000115OnDiskBuffer::create(StringRef Path, size_t Size, unsigned Mode) {
116 // Create new file in same directory but with random name.
117 SmallString<128> TempPath;
Nick Kledzik5fce8c42012-08-01 02:29:50 +0000118 int FD;
Rui Ueyamaa16fe652017-11-01 21:38:14 +0000119 if (auto EC = fs::createUniqueFile(Path + ".tmp%%%%%%%", FD, TempPath, Mode))
Rafael Espindolae0df3572017-11-08 01:05:44 +0000120 return errorCodeToError(EC);
Michael J. Spencer7fe24f52012-12-03 22:09:52 +0000121
Rui Ueyamaa16fe652017-11-01 21:38:14 +0000122 sys::RemoveFileOnSignal(TempPath);
Rafael Espindola7dbb5772015-09-18 15:17:53 +0000123
Rui Ueyamada9bc2e2015-03-06 06:07:32 +0000124#ifndef LLVM_ON_WIN32
125 // On Windows, CreateFileMapping (the mmap function on Windows)
126 // automatically extends the underlying file. We don't need to
127 // extend the file beforehand. _chsize (ftruncate on Windows) is
128 // pretty slow just like it writes specified amount of bytes,
Rui Ueyamaa16fe652017-11-01 21:38:14 +0000129 // so we should avoid calling that function.
130 if (auto EC = fs::resize_file(FD, Size))
Rafael Espindolae0df3572017-11-08 01:05:44 +0000131 return errorCodeToError(EC);
Rui Ueyamada9bc2e2015-03-06 06:07:32 +0000132#endif
Rafael Espindolac69f13b2014-12-12 18:13:23 +0000133
Rui Ueyamaa16fe652017-11-01 21:38:14 +0000134 // Mmap it.
135 std::error_code EC;
136 auto MappedFile = llvm::make_unique<fs::mapped_file_region>(
137 FD, fs::mapped_file_region::readwrite, Size, 0, EC);
138 close(FD);
Nick Kledzik5fce8c42012-08-01 02:29:50 +0000139 if (EC)
Rafael Espindolae0df3572017-11-08 01:05:44 +0000140 return errorCodeToError(EC);
Rui Ueyamaa16fe652017-11-01 21:38:14 +0000141 return llvm::make_unique<OnDiskBuffer>(Path, TempPath, std::move(MappedFile));
Michael J. Spencer7fe24f52012-12-03 22:09:52 +0000142}
Nick Kledzik5fce8c42012-08-01 02:29:50 +0000143
Rui Ueyamaa16fe652017-11-01 21:38:14 +0000144// Create an instance of FileOutputBuffer.
Rafael Espindolae0df3572017-11-08 01:05:44 +0000145Expected<std::unique_ptr<FileOutputBuffer>>
Rui Ueyamaa16fe652017-11-01 21:38:14 +0000146FileOutputBuffer::create(StringRef Path, size_t Size, unsigned Flags) {
147 unsigned Mode = fs::all_read | fs::all_write;
148 if (Flags & F_executable)
149 Mode |= fs::all_exe;
Michael J. Spencer7fe24f52012-12-03 22:09:52 +0000150
Rui Ueyamaa16fe652017-11-01 21:38:14 +0000151 fs::file_status Stat;
152 fs::status(Path, Stat);
153
154 // Usually, we want to create OnDiskBuffer to create a temporary file in
155 // the same directory as the destination file and atomically replaces it
156 // by rename(2).
157 //
158 // However, if the destination file is a special file, we don't want to
159 // use rename (e.g. we don't want to replace /dev/null with a regular
160 // file.) If that's the case, we create an in-memory buffer, open the
161 // destination file and write to it on commit().
162 switch (Stat.type()) {
163 case fs::file_type::directory_file:
Rafael Espindolae0df3572017-11-08 01:05:44 +0000164 return errorCodeToError(errc::is_a_directory);
Rui Ueyamaa16fe652017-11-01 21:38:14 +0000165 case fs::file_type::regular_file:
166 case fs::file_type::file_not_found:
167 case fs::file_type::status_error:
168 return OnDiskBuffer::create(Path, Size, Mode);
169 default:
170 return InMemoryBuffer::create(Path, Size, Mode);
Rafael Espindolad4b24ed2017-01-09 21:52:35 +0000171 }
Nick Kledzik5fce8c42012-08-01 02:29:50 +0000172}