1 | //===-Caching.cpp - LLVM Local File Cache ---------------------------------===// |
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 | // This file implements the localCache function, which simplifies creating, |
10 | // adding to, and querying a local file system cache. localCache takes care of |
11 | // periodically pruning older files from the cache using a CachePruningPolicy. |
12 | // |
13 | //===----------------------------------------------------------------------===// |
14 | |
15 | #include "llvm/Support/Caching.h" |
16 | #include "llvm/Support/Errc.h" |
17 | #include "llvm/Support/FileSystem.h" |
18 | #include "llvm/Support/MemoryBuffer.h" |
19 | #include "llvm/Support/Path.h" |
20 | |
21 | #if !defined(_MSC_VER) && !defined(__MINGW32__) |
22 | #include <unistd.h> |
23 | #else |
24 | #include <io.h> |
25 | #endif |
26 | |
27 | using namespace llvm; |
28 | |
29 | Expected<FileCache> llvm::localCache(const Twine &CacheNameRef, |
30 | const Twine &TempFilePrefixRef, |
31 | const Twine &CacheDirectoryPathRef, |
32 | AddBufferFn AddBuffer) { |
33 | |
34 | // Create local copies which are safely captured-by-copy in lambdas |
35 | SmallString<64> CacheName, TempFilePrefix, CacheDirectoryPath; |
36 | CacheNameRef.toVector(Out&: CacheName); |
37 | TempFilePrefixRef.toVector(Out&: TempFilePrefix); |
38 | CacheDirectoryPathRef.toVector(Out&: CacheDirectoryPath); |
39 | |
40 | return [=](unsigned Task, StringRef Key, |
41 | const Twine &ModuleName) -> Expected<AddStreamFn> { |
42 | // This choice of file name allows the cache to be pruned (see pruneCache() |
43 | // in include/llvm/Support/CachePruning.h). |
44 | SmallString<64> EntryPath; |
45 | sys::path::append(path&: EntryPath, a: CacheDirectoryPath, b: "llvmcache-" + Key); |
46 | // First, see if we have a cache hit. |
47 | SmallString<64> ResultPath; |
48 | Expected<sys::fs::file_t> FDOrErr = sys::fs::openNativeFileForRead( |
49 | Name: Twine(EntryPath), Flags: sys::fs::OF_UpdateAtime, RealPath: &ResultPath); |
50 | std::error_code EC; |
51 | if (FDOrErr) { |
52 | ErrorOr<std::unique_ptr<MemoryBuffer>> MBOrErr = |
53 | MemoryBuffer::getOpenFile(FD: *FDOrErr, Filename: EntryPath, |
54 | /*FileSize=*/-1, |
55 | /*RequiresNullTerminator=*/false); |
56 | sys::fs::closeFile(F&: *FDOrErr); |
57 | if (MBOrErr) { |
58 | AddBuffer(Task, ModuleName, std::move(*MBOrErr)); |
59 | return AddStreamFn(); |
60 | } |
61 | EC = MBOrErr.getError(); |
62 | } else { |
63 | EC = errorToErrorCode(Err: FDOrErr.takeError()); |
64 | } |
65 | |
66 | // On Windows we can fail to open a cache file with a permission denied |
67 | // error. This generally means that another process has requested to delete |
68 | // the file while it is still open, but it could also mean that another |
69 | // process has opened the file without the sharing permissions we need. |
70 | // Since the file is probably being deleted we handle it in the same way as |
71 | // if the file did not exist at all. |
72 | if (EC != errc::no_such_file_or_directory && EC != errc::permission_denied) |
73 | return createStringError(EC, S: Twine("Failed to open cache file " ) + |
74 | EntryPath + ": " + EC.message() + "\n" ); |
75 | |
76 | // This file stream is responsible for commiting the resulting file to the |
77 | // cache and calling AddBuffer to add it to the link. |
78 | struct CacheStream : CachedFileStream { |
79 | AddBufferFn AddBuffer; |
80 | sys::fs::TempFile TempFile; |
81 | std::string ModuleName; |
82 | unsigned Task; |
83 | |
84 | CacheStream(std::unique_ptr<raw_pwrite_stream> OS, AddBufferFn AddBuffer, |
85 | sys::fs::TempFile TempFile, std::string EntryPath, |
86 | std::string ModuleName, unsigned Task) |
87 | : CachedFileStream(std::move(OS), std::move(EntryPath)), |
88 | AddBuffer(std::move(AddBuffer)), TempFile(std::move(TempFile)), |
89 | ModuleName(ModuleName), Task(Task) {} |
90 | |
91 | ~CacheStream() { |
92 | // TODO: Manually commit rather than using non-trivial destructor, |
93 | // allowing to replace report_fatal_errors with a return Error. |
94 | |
95 | // Make sure the stream is closed before committing it. |
96 | OS.reset(); |
97 | |
98 | // Open the file first to avoid racing with a cache pruner. |
99 | ErrorOr<std::unique_ptr<MemoryBuffer>> MBOrErr = |
100 | MemoryBuffer::getOpenFile( |
101 | FD: sys::fs::convertFDToNativeFile(FD: TempFile.FD), Filename: ObjectPathName, |
102 | /*FileSize=*/-1, /*RequiresNullTerminator=*/false); |
103 | if (!MBOrErr) |
104 | report_fatal_error(reason: Twine("Failed to open new cache file " ) + |
105 | TempFile.TmpName + ": " + |
106 | MBOrErr.getError().message() + "\n" ); |
107 | |
108 | // On POSIX systems, this will atomically replace the destination if |
109 | // it already exists. We try to emulate this on Windows, but this may |
110 | // fail with a permission denied error (for example, if the destination |
111 | // is currently opened by another process that does not give us the |
112 | // sharing permissions we need). Since the existing file should be |
113 | // semantically equivalent to the one we are trying to write, we give |
114 | // AddBuffer a copy of the bytes we wrote in that case. We do this |
115 | // instead of just using the existing file, because the pruner might |
116 | // delete the file before we get a chance to use it. |
117 | Error E = TempFile.keep(Name: ObjectPathName); |
118 | E = handleErrors(E: std::move(E), Hs: [&](const ECError &E) -> Error { |
119 | std::error_code EC = E.convertToErrorCode(); |
120 | if (EC != errc::permission_denied) |
121 | return errorCodeToError(EC); |
122 | |
123 | auto MBCopy = MemoryBuffer::getMemBufferCopy(InputData: (*MBOrErr)->getBuffer(), |
124 | BufferName: ObjectPathName); |
125 | MBOrErr = std::move(MBCopy); |
126 | |
127 | // FIXME: should we consume the discard error? |
128 | consumeError(Err: TempFile.discard()); |
129 | |
130 | return Error::success(); |
131 | }); |
132 | |
133 | if (E) |
134 | report_fatal_error(reason: Twine("Failed to rename temporary file " ) + |
135 | TempFile.TmpName + " to " + ObjectPathName + ": " + |
136 | toString(E: std::move(E)) + "\n" ); |
137 | |
138 | AddBuffer(Task, ModuleName, std::move(*MBOrErr)); |
139 | } |
140 | }; |
141 | |
142 | return [=](size_t Task, const Twine &ModuleName) |
143 | -> Expected<std::unique_ptr<CachedFileStream>> { |
144 | // Create the cache directory if not already done. Doing this lazily |
145 | // ensures the filesystem isn't mutated until the cache is. |
146 | if (std::error_code EC = sys::fs::create_directories( |
147 | path: CacheDirectoryPath, /*IgnoreExisting=*/true)) |
148 | return createStringError(EC, S: Twine("can't create cache directory " ) + |
149 | CacheDirectoryPath + ": " + |
150 | EC.message()); |
151 | |
152 | // Write to a temporary to avoid race condition |
153 | SmallString<64> TempFilenameModel; |
154 | sys::path::append(path&: TempFilenameModel, a: CacheDirectoryPath, |
155 | b: TempFilePrefix + "-%%%%%%.tmp.o" ); |
156 | Expected<sys::fs::TempFile> Temp = sys::fs::TempFile::create( |
157 | Model: TempFilenameModel, Mode: sys::fs::owner_read | sys::fs::owner_write); |
158 | if (!Temp) |
159 | return createStringError(EC: errc::io_error, |
160 | S: toString(E: Temp.takeError()) + ": " + CacheName + |
161 | ": Can't get a temporary file" ); |
162 | |
163 | // This CacheStream will move the temporary file into the cache when done. |
164 | return std::make_unique<CacheStream>( |
165 | args: std::make_unique<raw_fd_ostream>(args&: Temp->FD, /* ShouldClose */ args: false), |
166 | args: AddBuffer, args: std::move(*Temp), args: std::string(EntryPath), args: ModuleName.str(), |
167 | args&: Task); |
168 | }; |
169 | }; |
170 | } |
171 | |