blob: 9a323d7c49725f4dafdbc7b2a38e43493dab98d4 [file] [log] [blame]
//===--- FileSystem.cpp - Extra helpers for manipulating files ------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "swift/Basic/FileSystem.h"
#include "swift/Basic/LLVM.h"
#include "clang/Basic/FileManager.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/VirtualFileSystem.h"
using namespace swift;
namespace {
class OpenFileRAII {
static const int INVALID_FD = -1;
public:
int fd = INVALID_FD;
~OpenFileRAII() {
if (fd != INVALID_FD)
llvm::sys::Process::SafelyCloseFileDescriptor(fd);
}
};
} // end anonymous namespace
/// Does some simple checking to see if a temporary file can be written next to
/// \p outputPath and then renamed into place.
///
/// Helper for swift::atomicallyWritingToFile.
///
/// If the result is an error, the write won't succeed at all, and the calling
/// operation should bail out early.
static llvm::ErrorOr<bool>
canUseTemporaryForWrite(const StringRef outputPath) {
namespace fs = llvm::sys::fs;
if (outputPath == "-") {
// Special case: "-" represents stdout, and LLVM's output stream APIs are
// aware of this. It doesn't make sense to use a temporary in this case.
return false;
}
fs::file_status status;
(void)fs::status(outputPath, status);
if (!fs::exists(status)) {
// Assume we'll be able to write to both a temporary file and to the final
// destination if the final destination doesn't exist yet.
return true;
}
// Fail early if we can't write to the final destination.
if (!fs::can_write(outputPath))
return llvm::make_error_code(llvm::errc::operation_not_permitted);
// Only use a temporary if the output is a regular file. This handles
// things like '-o /dev/null'
return fs::is_regular_file(status);
}
/// Attempts to open a temporary file next to \p outputPath, with the intent
/// that once the file has been written it will be renamed into place.
///
/// Helper for swift::atomicallyWritingToFile.
///
/// \param[out] openedStream On success, a stream opened for writing to the
/// temporary file that was just created.
/// \param outputPath The path to the final output file, which is used to decide
/// where to put the temporary.
///
/// \returns The path to the temporary file that was opened, or \c None if the
/// file couldn't be created.
static Optional<std::string>
tryToOpenTemporaryFile(Optional<llvm::raw_fd_ostream> &openedStream,
const StringRef outputPath) {
namespace fs = llvm::sys::fs;
// Create a temporary file path.
// Insert a placeholder for a random suffix before the extension (if any).
// Then because some tools glob for build artifacts (such as clang's own
// GlobalModuleIndex.cpp), also append .tmp.
SmallString<128> tempPath;
const StringRef outputExtension = llvm::sys::path::extension(outputPath);
tempPath = outputPath.drop_back(outputExtension.size());
tempPath += "-%%%%%%%%";
tempPath += outputExtension;
tempPath += ".tmp";
int fd;
const unsigned perms = fs::all_read | fs::all_write;
std::error_code EC = fs::createUniqueFile(tempPath, fd, tempPath, perms);
if (EC) {
// Ignore the specific error; the caller has to fall back to not using a
// temporary anyway.
return None;
}
openedStream.emplace(fd, /*shouldClose=*/true);
// Make sure the temporary file gets removed if we crash.
llvm::sys::RemoveFileOnSignal(tempPath);
return tempPath.str().str();
}
std::error_code swift::atomicallyWritingToFile(
const StringRef outputPath,
const llvm::function_ref<void(llvm::raw_pwrite_stream &)> action) {
namespace fs = llvm::sys::fs;
// FIXME: This is mostly a simplified version of
// clang::CompilerInstance::createOutputFile. It would be great to share the
// implementation.
assert(!outputPath.empty());
llvm::ErrorOr<bool> canUseTemporary = canUseTemporaryForWrite(outputPath);
if (std::error_code error = canUseTemporary.getError())
return error;
Optional<std::string> temporaryPath;
{
Optional<llvm::raw_fd_ostream> OS;
if (canUseTemporary.get()) {
temporaryPath = tryToOpenTemporaryFile(OS, outputPath);
if (!temporaryPath) {
assert(!OS.hasValue());
// If we failed to create the temporary, fall back to writing to the
// file directly. This handles the corner case where we cannot write to
// the directory, but can write to the file.
}
}
if (!OS.hasValue()) {
std::error_code error;
OS.emplace(outputPath, error, fs::F_None);
if (error)
return error;
}
action(OS.getValue());
// In addition to scoping the use of 'OS', ending the scope here also
// ensures that it's been flushed (by destroying it).
}
if (!temporaryPath.hasValue()) {
// If we didn't use a temporary, we're done!
return std::error_code();
}
return swift::moveFileIfDifferent(temporaryPath.getValue(), outputPath);
}
llvm::ErrorOr<FileDifference>
swift::areFilesDifferent(const llvm::Twine &source,
const llvm::Twine &destination,
bool allowDestinationErrors) {
namespace fs = llvm::sys::fs;
if (fs::equivalent(source, destination))
return FileDifference::IdenticalFile;
OpenFileRAII sourceFile;
fs::file_status sourceStatus;
if (std::error_code error = fs::openFileForRead(source, sourceFile.fd)) {
// If we can't open the source file, fail.
return error;
}
if (std::error_code error = fs::status(sourceFile.fd, sourceStatus)) {
// If we can't stat the source file, fail.
return error;
}
/// Converts an error from the destination file into either an error or
/// DifferentContents return, depending on `allowDestinationErrors`.
auto convertDestinationError = [=](std::error_code error) ->
llvm::ErrorOr<FileDifference> {
if (allowDestinationErrors)
return FileDifference::DifferentContents;
return error;
};
OpenFileRAII destFile;
fs::file_status destStatus;
if (std::error_code error = fs::openFileForRead(destination, destFile.fd)) {
// If we can't open the destination file, fail in the specified fashion.
return convertDestinationError(error);
}
if (std::error_code error = fs::status(destFile.fd, destStatus)) {
// If we can't open the destination file, fail in the specified fashion.
return convertDestinationError(error);
}
uint64_t size = sourceStatus.getSize();
if (size != destStatus.getSize())
// If the files are different sizes, they must be different.
return FileDifference::DifferentContents;
if (size == 0)
// If both files are zero size, they must be the same.
return FileDifference::SameContents;
// The two files match in size, so we have to compare the bytes to determine
// if they're the same.
std::error_code sourceRegionErr;
fs::mapped_file_region sourceRegion(sourceFile.fd,
fs::mapped_file_region::readonly,
size, 0, sourceRegionErr);
if (sourceRegionErr)
return sourceRegionErr;
std::error_code destRegionErr;
fs::mapped_file_region destRegion(destFile.fd,
fs::mapped_file_region::readonly,
size, 0, destRegionErr);
if (destRegionErr)
return convertDestinationError(destRegionErr);
if (0 == memcmp(sourceRegion.const_data(), destRegion.const_data(), size))
return FileDifference::SameContents;
return FileDifference::DifferentContents;
}
std::error_code swift::moveFileIfDifferent(const llvm::Twine &source,
const llvm::Twine &destination) {
namespace fs = llvm::sys::fs;
auto result = areFilesDifferent(source, destination,
/*allowDestinationErrors=*/true);
if (!result)
return result.getError();
switch (*result) {
case FileDifference::IdenticalFile:
// Do nothing for a self-move.
return std::error_code();
case FileDifference::SameContents:
// Files are identical; remove the source file.
return fs::remove(source);
case FileDifference::DifferentContents:
// Files are different; overwrite the destination file.
return fs::rename(source, destination);
}
llvm_unreachable("Unhandled FileDifference in switch");
}
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
swift::vfs::getFileOrSTDIN(llvm::vfs::FileSystem &FS,
const llvm::Twine &Filename,
int64_t FileSize,
bool RequiresNullTerminator,
bool IsVolatile) {
llvm::SmallString<256> NameBuf;
llvm::StringRef NameRef = Filename.toStringRef(NameBuf);
if (NameRef == "-")
return llvm::MemoryBuffer::getSTDIN();
return FS.getBufferForFile(Filename, FileSize,
RequiresNullTerminator, IsVolatile);
}