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