1//===----------------------------------------------------------------------===//
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#include "clang/Serialization/ModuleCache.h"
10
11#include "clang/Serialization/InMemoryModuleCache.h"
12#include "clang/Serialization/ModuleFile.h"
13#include "llvm/Support/Error.h"
14#include "llvm/Support/FileSystem.h"
15#include "llvm/Support/IOSandbox.h"
16#include "llvm/Support/LockFileManager.h"
17#include "llvm/Support/Path.h"
18
19using namespace clang;
20
21/// Write a new timestamp file with the given path.
22static void writeTimestampFile(StringRef TimestampFile) {
23 std::error_code EC;
24 llvm::raw_fd_ostream Out(TimestampFile.str(), EC, llvm::sys::fs::OF_None);
25}
26
27void clang::maybePruneImpl(StringRef Path, time_t PruneInterval,
28 time_t PruneAfter) {
29 if (PruneInterval <= 0 || PruneAfter <= 0)
30 return;
31
32 // This is a compiler-internal input/output, let's bypass the sandbox.
33 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
34
35 llvm::SmallString<128> TimestampFile(Path);
36 llvm::sys::path::append(path&: TimestampFile, a: "modules.timestamp");
37
38 // Try to stat() the timestamp file.
39 llvm::sys::fs::file_status StatBuf;
40 if (std::error_code EC = llvm::sys::fs::status(path: TimestampFile, result&: StatBuf)) {
41 // If the timestamp file wasn't there, create one now.
42 if (EC == std::errc::no_such_file_or_directory)
43 writeTimestampFile(TimestampFile);
44 return;
45 }
46
47 // Check whether the time stamp is older than our pruning interval.
48 // If not, do nothing.
49 time_t TimestampModTime =
50 llvm::sys::toTimeT(TP: StatBuf.getLastModificationTime());
51 time_t CurrentTime = time(timer: nullptr);
52 if (CurrentTime - TimestampModTime <= PruneInterval)
53 return;
54
55 // Write a new timestamp file so that nobody else attempts to prune.
56 // There is a benign race condition here, if two Clang instances happen to
57 // notice at the same time that the timestamp is out-of-date.
58 writeTimestampFile(TimestampFile);
59
60 // Walk the entire module cache, looking for unused module files and module
61 // indices.
62 std::error_code EC;
63 auto TryPruneFile = [&](StringRef FilePath) {
64 // We only care about module and global module index files.
65 StringRef Filename = llvm::sys::path::filename(path: FilePath);
66 StringRef Extension = llvm::sys::path::extension(path: FilePath);
67 if (Extension != ".pcm" && Extension != ".timestamp" &&
68 Filename != "modules.idx")
69 return;
70
71 // Don't prune the pruning timestamp file.
72 if (Filename == "modules.timestamp")
73 return;
74
75 // Look at this file. If we can't stat it, there's nothing interesting
76 // there.
77 if (llvm::sys::fs::status(path: FilePath, result&: StatBuf))
78 return;
79
80 // If the file has been used recently enough, leave it there.
81 time_t FileAccessTime = llvm::sys::toTimeT(TP: StatBuf.getLastAccessedTime());
82 if (CurrentTime - FileAccessTime <= PruneAfter)
83 return;
84
85 // Remove the file.
86 llvm::sys::fs::remove(path: FilePath);
87
88 // Remove the timestamp file created by implicit module builds.
89 std::string TimestampFilename = FilePath.str() + ".timestamp";
90 llvm::sys::fs::remove(path: TimestampFilename);
91 };
92
93 for (llvm::sys::fs::directory_iterator Dir(Path, EC), DirEnd;
94 Dir != DirEnd && !EC; Dir.increment(ec&: EC)) {
95 // If we don't have a directory, try to prune it as a file in the root.
96 if (!llvm::sys::fs::is_directory(Path: Dir->path())) {
97 TryPruneFile(Dir->path());
98 continue;
99 }
100
101 // Walk all the files within this directory.
102 for (llvm::sys::fs::directory_iterator File(Dir->path(), EC), FileEnd;
103 File != FileEnd && !EC; File.increment(ec&: EC))
104 TryPruneFile(File->path());
105
106 // If we removed all the files in the directory, remove the directory
107 // itself.
108 if (llvm::sys::fs::directory_iterator(Dir->path(), EC) ==
109 llvm::sys::fs::directory_iterator() &&
110 !EC)
111 llvm::sys::fs::remove(path: Dir->path());
112 }
113}
114
115std::error_code clang::writeImpl(StringRef Path, llvm::MemoryBufferRef Buffer) {
116 StringRef Extension = llvm::sys::path::extension(path: Path);
117 SmallString<128> ModelPath = StringRef(Path).drop_back(N: Extension.size());
118 ModelPath += "-%%%%%%%%";
119 ModelPath += Extension;
120 ModelPath += ".tmp";
121
122 std::error_code EC;
123 int FD;
124 SmallString<128> TmpPath;
125 if ((EC = llvm::sys::fs::createUniqueFile(Model: ModelPath, ResultFD&: FD, ResultPath&: TmpPath))) {
126 if (EC != std::errc::no_such_file_or_directory)
127 return EC;
128
129 StringRef Dir = llvm::sys::path::parent_path(path: Path);
130 if (std::error_code InnerEC = llvm::sys::fs::create_directories(path: Dir))
131 return InnerEC;
132
133 if ((EC = llvm::sys::fs::createUniqueFile(Model: ModelPath, ResultFD&: FD, ResultPath&: TmpPath)))
134 return EC;
135 }
136
137 {
138 llvm::raw_fd_ostream OS(FD, /*shouldClose=*/true);
139 OS << Buffer.getBuffer();
140 }
141
142 if ((EC = llvm::sys::fs::rename(from: TmpPath, to: Path)))
143 return EC;
144
145 return {};
146}
147
148Expected<std::unique_ptr<llvm::MemoryBuffer>>
149clang::readImpl(StringRef FileName, off_t &Size, time_t &ModTime) {
150 Expected<llvm::sys::fs::file_t> FD =
151 llvm::sys::fs::openNativeFileForRead(Name: FileName);
152 if (!FD)
153 return FD.takeError();
154 llvm::sys::fs::file_status Status;
155 if (std::error_code EC = llvm::sys::fs::status(FD: *FD, Result&: Status))
156 return llvm::errorCodeToError(EC);
157 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Buf =
158 llvm::MemoryBuffer::getOpenFile(FD: *FD, Filename: FileName, FileSize: Status.getSize(),
159 /*RequiresNullTerminator=*/false);
160 if (!Buf)
161 return llvm::errorCodeToError(EC: Buf.getError());
162 Size = Status.getSize();
163 ModTime = llvm::sys::toTimeT(TP: Status.getLastModificationTime());
164 return std::move(*Buf);
165}
166
167namespace {
168class CrossProcessModuleCache : public ModuleCache {
169 InMemoryModuleCache InMemory;
170
171public:
172 void prepareForGetLock(StringRef ModuleFilename) override {
173 // This is a compiler-internal input/output, let's bypass the sandbox.
174 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
175
176 // FIXME: Do this in LockFileManager and only if the directory doesn't
177 // exist.
178 StringRef Dir = llvm::sys::path::parent_path(path: ModuleFilename);
179 llvm::sys::fs::create_directories(path: Dir);
180 }
181
182 std::unique_ptr<llvm::AdvisoryLock>
183 getLock(StringRef ModuleFilename) override {
184 return std::make_unique<llvm::LockFileManager>(args&: ModuleFilename);
185 }
186
187 std::time_t getModuleTimestamp(StringRef ModuleFilename) override {
188 // This is a compiler-internal input/output, let's bypass the sandbox.
189 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
190
191 std::string TimestampFilename =
192 serialization::ModuleFile::getTimestampFilename(FileName: ModuleFilename);
193 llvm::sys::fs::file_status Status;
194 if (llvm::sys::fs::status(path: TimestampFilename, result&: Status) != std::error_code{})
195 return 0;
196 return llvm::sys::toTimeT(TP: Status.getLastModificationTime());
197 }
198
199 void updateModuleTimestamp(StringRef ModuleFilename) override {
200 // This is a compiler-internal input/output, let's bypass the sandbox.
201 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
202
203 // Overwrite the timestamp file contents so that file's mtime changes.
204 std::error_code EC;
205 llvm::raw_fd_ostream OS(
206 serialization::ModuleFile::getTimestampFilename(FileName: ModuleFilename), EC,
207 llvm::sys::fs::OF_TextWithCRLF);
208 if (EC)
209 return;
210 OS << "Timestamp file\n";
211 OS.close();
212 OS.clear_error(); // Avoid triggering a fatal error.
213 }
214
215 void maybePrune(StringRef Path, time_t PruneInterval,
216 time_t PruneAfter) override {
217 // This is a compiler-internal input/output, let's bypass the sandbox.
218 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
219
220 maybePruneImpl(Path, PruneInterval, PruneAfter);
221 }
222
223 InMemoryModuleCache &getInMemoryModuleCache() override { return InMemory; }
224 const InMemoryModuleCache &getInMemoryModuleCache() const override {
225 return InMemory;
226 }
227
228 std::error_code write(StringRef Path, llvm::MemoryBufferRef Buffer) override {
229 // This is a compiler-internal input/output, let's bypass the sandbox.
230 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
231
232 return writeImpl(Path, Buffer);
233 }
234
235 Expected<std::unique_ptr<llvm::MemoryBuffer>>
236 read(StringRef FileName, off_t &Size, time_t &ModTime) override {
237 // This is a compiler-internal input/output, let's bypass the sandbox.
238 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
239
240 return readImpl(FileName, Size, ModTime);
241 }
242};
243} // namespace
244
245std::shared_ptr<ModuleCache> clang::createCrossProcessModuleCache() {
246 return std::make_shared<CrossProcessModuleCache>();
247}
248