Use a temporary file for output which gets renamed after all the writing is finished.

This mainly prevents failures and/or crashes when multiple processes try to read/write the same PCH file. (rdar://8392711&8294781); suggestion & review by Daniel!

llvm-svn: 114187
This commit is contained in:
Argyrios Kyrtzidis 2010-09-17 17:38:48 +00:00
parent 922563cc6d
commit d059997000
4 changed files with 79 additions and 20 deletions

View File

@ -69,6 +69,8 @@ def err_fe_pch_file_modified : Error<
DefaultFatal;
def err_fe_unable_to_open_output : Error<
"unable to open output file '%0': '%1'">;
def err_fe_unable_to_rename_temp : Error<
"unable to rename temporary '%0' to output file '%1': '%2'">;
def err_fe_unable_to_open_logfile : Error<
"unable to open logfile file '%0': '%1'">;
def err_fe_pth_file_has_no_source_header : Error<

View File

@ -95,8 +95,23 @@ class CompilerInstance {
/// The frontend timer
llvm::OwningPtr<llvm::Timer> FrontendTimer;
/// \brief Holds information about the output file.
///
/// If TempFilename is not empty we must rename it to Filename at the end.
/// TempFilename may be empty and Filename non empty if creating the temporary
/// failed.
struct OutputFile {
std::string Filename;
std::string TempFilename;
llvm::raw_ostream *OS;
OutputFile(const std::string &filename, const std::string &tempFilename,
llvm::raw_ostream *os)
: Filename(filename), TempFilename(tempFilename), OS(os) { }
};
/// The list of active output files.
std::list< std::pair<std::string, llvm::raw_ostream*> > OutputFiles;
std::list<OutputFile> OutputFiles;
void operator=(const CompilerInstance &); // DO NOT IMPLEMENT
CompilerInstance(const CompilerInstance&); // DO NOT IMPLEMENT
@ -442,9 +457,8 @@ public:
/// addOutputFile - Add an output file onto the list of tracked output files.
///
/// \param Path - The path to the output file, or empty.
/// \param OS - The output stream, which should be non-null.
void addOutputFile(llvm::StringRef Path, llvm::raw_ostream *OS);
/// \param OutFile - The output file info.
void addOutputFile(const OutputFile &OutFile);
/// clearOutputFiles - Clear the output file list, destroying the contained
/// output streams.
@ -565,7 +579,8 @@ public:
///
/// If \arg OutputPath is empty, then createOutputFile will derive an output
/// path location as \arg BaseInput, with any suffix removed, and \arg
/// Extension appended.
/// Extension appended. If OutputPath is not stdout createOutputFile will
/// create a new temporary file that must be renamed to OutputPath in the end.
///
/// \param OutputPath - If given, the path to the output file.
/// \param Error [out] - On failure, the error message.
@ -575,11 +590,14 @@ public:
/// \param Binary - The mode to open the file in.
/// \param ResultPathName [out] - If given, the result path name will be
/// stored here on success.
/// \param TempPathName [out] - If given, the temporary file path name
/// will be stored here on success.
static llvm::raw_fd_ostream *
createOutputFile(llvm::StringRef OutputPath, std::string &Error,
bool Binary = true, llvm::StringRef BaseInput = "",
llvm::StringRef Extension = "",
std::string *ResultPathName = 0);
std::string *ResultPathName = 0,
std::string *TempPathName = 0);
/// }
/// @name Initialization Utility Methods

View File

@ -35,6 +35,7 @@
#include "llvm/System/Host.h"
#include "llvm/System/Path.h"
#include "llvm/System/Program.h"
#include "llvm/System/Signals.h"
using namespace clang;
CompilerInstance::CompilerInstance()
@ -370,18 +371,30 @@ void CompilerInstance::createSema(bool CompleteTranslationUnit,
// Output Files
void CompilerInstance::addOutputFile(llvm::StringRef Path,
llvm::raw_ostream *OS) {
assert(OS && "Attempt to add empty stream to output list!");
OutputFiles.push_back(std::make_pair(Path, OS));
void CompilerInstance::addOutputFile(const OutputFile &OutFile) {
assert(OutFile.OS && "Attempt to add empty stream to output list!");
OutputFiles.push_back(OutFile);
}
void CompilerInstance::clearOutputFiles(bool EraseFiles) {
for (std::list< std::pair<std::string, llvm::raw_ostream*> >::iterator
for (std::list<OutputFile>::iterator
it = OutputFiles.begin(), ie = OutputFiles.end(); it != ie; ++it) {
delete it->second;
if (EraseFiles && !it->first.empty())
llvm::sys::Path(it->first).eraseFromDisk();
delete it->OS;
if (!it->TempFilename.empty()) {
llvm::sys::Path TempPath(it->TempFilename);
if (EraseFiles)
TempPath.eraseFromDisk();
else {
std::string Error;
if (TempPath.renamePathOnDisk(llvm::sys::Path(it->Filename), &Error)) {
getDiagnostics().Report(diag::err_fe_unable_to_rename_temp)
<< it->TempFilename << it->Filename << Error;
TempPath.eraseFromDisk();
}
}
} else if (!it->Filename.empty() && EraseFiles)
llvm::sys::Path(it->Filename).eraseFromDisk();
}
OutputFiles.clear();
}
@ -399,10 +412,11 @@ CompilerInstance::createOutputFile(llvm::StringRef OutputPath,
bool Binary,
llvm::StringRef InFile,
llvm::StringRef Extension) {
std::string Error, OutputPathName;
std::string Error, OutputPathName, TempPathName;
llvm::raw_fd_ostream *OS = createOutputFile(OutputPath, Error, Binary,
InFile, Extension,
&OutputPathName);
&OutputPathName,
&TempPathName);
if (!OS) {
getDiagnostics().Report(diag::err_fe_unable_to_open_output)
<< OutputPath << Error;
@ -411,7 +425,8 @@ CompilerInstance::createOutputFile(llvm::StringRef OutputPath,
// Add the output file -- but don't try to remove "-", since this means we are
// using stdin.
addOutputFile((OutputPathName != "-") ? OutputPathName : "", OS);
addOutputFile(OutputFile((OutputPathName != "-") ? OutputPathName : "",
TempPathName, OS));
return OS;
}
@ -422,8 +437,9 @@ CompilerInstance::createOutputFile(llvm::StringRef OutputPath,
bool Binary,
llvm::StringRef InFile,
llvm::StringRef Extension,
std::string *ResultPathName) {
std::string OutFile;
std::string *ResultPathName,
std::string *TempPathName) {
std::string OutFile, TempFile;
if (!OutputPath.empty()) {
OutFile = OutputPath;
} else if (InFile == "-") {
@ -436,15 +452,37 @@ CompilerInstance::createOutputFile(llvm::StringRef OutputPath,
} else {
OutFile = "-";
}
if (OutFile != "-") {
llvm::sys::Path OutPath(OutFile);
// Only create the temporary if we can actually write to OutPath, otherwise
// we want to fail early.
if (!OutPath.exists() ||
(OutPath.isRegularFile() && OutPath.canWrite())) {
// Create a temporary file.
llvm::sys::Path TempPath(OutFile);
if (!TempPath.createTemporaryFileOnDisk())
TempFile = TempPath.str();
}
}
std::string OSFile = OutFile;
if (!TempFile.empty())
OSFile = TempFile;
llvm::OwningPtr<llvm::raw_fd_ostream> OS(
new llvm::raw_fd_ostream(OutFile.c_str(), Error,
new llvm::raw_fd_ostream(OSFile.c_str(), Error,
(Binary ? llvm::raw_fd_ostream::F_Binary : 0)));
if (!Error.empty())
return 0;
// Make sure the out stream file gets removed if we crash.
llvm::sys::RemoveFileOnSignal(llvm::sys::Path(OSFile));
if (ResultPathName)
*ResultPathName = OutFile;
if (TempPathName)
*TempPathName = TempFile;
return OS.take();
}

View File

@ -1,6 +1,7 @@
// RUN: rm -f %t1.bc
// RUN: %clang_cc1 -DPASS %s -emit-llvm-bc -o %t1.bc
// RUN: test -f %t1.bc
// RUN: rm -f %t1.bc
// RUN: not %clang_cc1 %s -emit-llvm-bc -o %t1.bc
// RUN: not test -f %t1.bc