Index: lld/ELF/LTO.cpp =================================================================== --- lld/ELF/LTO.cpp +++ lld/ELF/LTO.cpp @@ -164,7 +164,8 @@ Cache)); if (!Config->ThinLTOCacheDir.empty()) - pruneCache(Config->ThinLTOCacheDir, Config->ThinLTOCachePolicy); + check(pruneCache(Config->ThinLTOCacheDir, Config->ThinLTOCachePolicy), + "could not prune ThinLTO cache"); for (unsigned I = 0; I != MaxTasks; ++I) { if (Buff[I].empty()) Index: lld/test/ELF/lto/cache.ll =================================================================== --- lld/test/ELF/lto/cache.ll +++ lld/test/ELF/lto/cache.ll @@ -5,12 +5,16 @@ ; RUN: rm -Rf %t.cache && mkdir %t.cache ; Create a file that will be removed by cache pruning due to age. -; RUN: touch -t 197001010000 %t.cache/foo +; RUN: touch -t 197001010000 %t.cache/llvmcache-foo ; RUN: ld.lld --thinlto-cache-dir=%t.cache --thinlto-cache-policy prune_after=1h -o %t3 %t2.o %t.o ; Two cached objects, plus a timestamp file, minus the file we removed. ; RUN: ls %t.cache | count 3 +; RUN: touch %t.cache/foo +; RUN: not ld.lld --thinlto-cache-dir=%t.cache --thinlto-cache-policy prune_after=1h -o %t3 %t2.o %t.o 2>&1 | FileCheck --check-prefix=ERROR %s +; ERROR: could not prune ThinLTO cache: unexpected file found in cache directory: {{.*}}foo + target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" Index: llvm/include/llvm/Support/CachePruning.h =================================================================== --- llvm/include/llvm/Support/CachePruning.h +++ llvm/include/llvm/Support/CachePruning.h @@ -52,7 +52,12 @@ /// Peform pruning using the supplied policy, returns true if pruning /// occured, i.e. if Policy.Interval was expired. -bool pruneCache(StringRef Path, CachePruningPolicy Policy); +/// +/// As a safeguard against data loss if the user specifies a directory that is +/// not a cache directory, this function requires that all files in this +/// directory have names matching the patterns "llvmcache*" and will not prune +/// files with names matching the pattern "llvmcache.*". +Expected pruneCache(StringRef Path, CachePruningPolicy Policy); } // namespace llvm Index: llvm/lib/LTO/Caching.cpp =================================================================== --- llvm/lib/LTO/Caching.cpp +++ llvm/lib/LTO/Caching.cpp @@ -27,9 +27,11 @@ return errorCodeToError(EC); return [=](unsigned Task, StringRef Key) -> AddStreamFn { - // First, see if we have a cache hit. + // This choice of file name allows the cache to be pruned (see pruneCache() + // in include/llvm/Support/CachePruning.h). SmallString<64> EntryPath; - sys::path::append(EntryPath, CacheDirectoryPath, Key); + sys::path::append(EntryPath, CacheDirectoryPath, "llvmcache-" + Key); + // First, see if we have a cache hit. ErrorOr> MBOrErr = MemoryBuffer::getFile(EntryPath); if (MBOrErr) { @@ -77,10 +79,12 @@ }; return [=](size_t Task) -> std::unique_ptr { - // Write to a temporary to avoid race condition + // Write to a temporary to avoid race condition. The file name was chosen + // to avoid cache pruning. int TempFD; SmallString<64> TempFilenameModel, TempFilename; - sys::path::append(TempFilenameModel, CacheDirectoryPath, "Thin-%%%%%%.tmp.o"); + sys::path::append(TempFilenameModel, CacheDirectoryPath, + "llvmcache.tmp-%%%%%%.o"); std::error_code EC = sys::fs::createUniqueFile(TempFilenameModel, TempFD, TempFilename, sys::fs::owner_read | sys::fs::owner_write); Index: llvm/lib/LTO/ThinLTOCodeGenerator.cpp =================================================================== --- llvm/lib/LTO/ThinLTOCodeGenerator.cpp +++ llvm/lib/LTO/ThinLTOCodeGenerator.cpp @@ -348,7 +348,10 @@ ArrayRef((const uint8_t *)&Entry, sizeof(GlobalValue::GUID))); } - sys::path::append(EntryPath, CachePath, toHex(Hasher.result())); + // This choice of file name allows the cache to be pruned (see pruneCache() + // in include/llvm/Support/CachePruning.h). + sys::path::append(EntryPath, CachePath, + "llvmcache-" + toHex(Hasher.result())); } // Access the path to this entry in the cache. @@ -1023,7 +1026,10 @@ } } - pruneCache(CacheOptions.Path, CacheOptions.Policy); + Expected PruneOrErr = pruneCache(CacheOptions.Path, CacheOptions.Policy); + if (!PruneOrErr) + report_fatal_error("Could not prune ThinLTO cache: " + + toString(PruneOrErr.takeError())); // If statistics were requested, print them out now. if (llvm::AreStatisticsEnabled()) Index: llvm/lib/Support/CachePruning.cpp =================================================================== --- llvm/lib/Support/CachePruning.cpp +++ llvm/lib/Support/CachePruning.cpp @@ -102,7 +102,7 @@ } /// Prune the cache of files that haven't been accessed in a long time. -bool llvm::pruneCache(StringRef Path, CachePruningPolicy Policy) { +Expected llvm::pruneCache(StringRef Path, CachePruningPolicy Policy) { using namespace std::chrono; if (Path.empty()) @@ -180,10 +180,20 @@ // Walk all of the files within this directory. for (sys::fs::directory_iterator File(CachePathNative, EC), FileEnd; File != FileEnd && !EC; File.increment(EC)) { - // Do not touch the timestamp. - if (File->path() == TimestampFile) + StringRef Filename = sys::path::filename(File->path()); + + // Do not touch any files beginning with the string "llvmcache.". This + // includes the timestamp file as well as temporary files created by + // clients. + if (Filename.startswith("llvmcache.")) continue; + // Abort if we see a file with an unexpected name. + if (!Filename.startswith("llvmcache")) + return make_error( + "unexpected file found in cache directory: " + File->path(), + inconvertibleErrorCode()); + // Look at this file. If we can't stat it, there's nothing interesting // there. if (sys::fs::status(File->path(), FileStatus)) { Index: llvm/test/ThinLTO/X86/cache.ll =================================================================== --- llvm/test/ThinLTO/X86/cache.ll +++ llvm/test/ThinLTO/X86/cache.ll @@ -23,11 +23,24 @@ ; RUN: opt -module-hash -module-summary %s -o %t.bc ; RUN: opt -module-hash -module-summary %p/Inputs/cache.ll -o %t2.bc -; Verify that enabling caching is working +; Verify that enabling caching is working, and that the pruner only removes +; files matching the pattern "llvmcache*" if they do not match the pattern +; "llvmcache.*". ; RUN: rm -Rf %t.cache && mkdir %t.cache +; RUN: touch -t 197001010000 %t.cache/llvmcache-foo %t.cache/llvmcache.foo ; RUN: llvm-lto -thinlto-action=run -exported-symbol=globalfunc %t2.bc %t.bc -thinlto-cache-dir %t.cache +; RUN: ls %t.cache | count 4 ; RUN: ls %t.cache/llvmcache.timestamp -; RUN: ls %t.cache | count 3 +; RUN: ls %t.cache/llvmcache.foo +; RUN: not ls %t.cache/llvmcache-foo +; RUN: ls %t.cache/llvmcache-* | count 2 + +; Verify that enabling caching fails with a cache directory containing +; unexpected files. +; RUN: rm -Rf %t.cache && mkdir %t.cache +; RUN: touch %t.cache/foo +; RUN: not llvm-lto -thinlto-action=run -exported-symbol=globalfunc %t2.bc %t.bc -thinlto-cache-dir %t.cache 2>&1 | FileCheck --check-prefix=ERROR %s +; ERROR: Could not prune ThinLTO cache: unexpected file found in cache directory: {{.*}}foo ; Verify that enabling caching is working with llvm-lto2 ; RUN: rm -Rf %t.cache @@ -36,6 +49,7 @@ ; RUN: -r=%t2.bc,_globalfunc,lx \ ; RUN: -r=%t.bc,_globalfunc,plx ; RUN: ls %t.cache | count 2 +; RUN: ls %t.cache/llvmcache-* | count 2 target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-apple-macosx10.11.0"