1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
|
//===--- 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 "clang/Basic/FileManager.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.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"
#include <optional>
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 llvm::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 std::optional<std::string>
tryToOpenTemporaryFile(std::optional<llvm::raw_fd_ostream> &openedStream,
const llvm::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.
llvm::SmallString<128> tempPath;
const llvm::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,
fs::OF_None, perms);
if (EC) {
// Ignore the specific error; the caller has to fall back to not using a
// temporary anyway.
return std::nullopt;
}
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 llvm::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;
std::optional<std::string> temporaryPath;
{
std::optional<llvm::raw_fd_ostream> OS;
if (canUseTemporary.get()) {
temporaryPath = tryToOpenTemporaryFile(OS, outputPath);
if (!temporaryPath) {
assert(!OS.has_value());
// 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.has_value()) {
std::error_code error;
OS.emplace(outputPath, error, fs::OF_None);
if (error) {
return error;
}
}
action(OS.value());
// In addition to scoping the use of 'OS', ending the scope here also
// ensures that it's been flushed (by destroying it).
}
if (!temporaryPath.has_value()) {
// If we didn't use a temporary, we're done!
return std::error_code();
}
return swift::moveFileIfDifferent(temporaryPath.value(), 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(fs::convertFDToNativeFile(sourceFile.fd),
fs::mapped_file_region::readonly,
size, 0, sourceRegionErr);
if (sourceRegionErr) {
return sourceRegionErr;
}
std::error_code destRegionErr;
fs::mapped_file_region destRegion(fs::convertFDToNativeFile(destFile.fd),
fs::mapped_file_region::readonly,
size, 0, destRegionErr);
if (destRegionErr) {
return convertDestinationError(destRegionErr);
}
if (memcmp(sourceRegion.const_data(), destRegion.const_data(), size) != 0) {
return FileDifference::DifferentContents;
}
return FileDifference::SameContents;
}
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,
unsigned BADFRetry) {
llvm::SmallString<256> NameBuf;
llvm::StringRef NameRef = Filename.toStringRef(NameBuf);
if (NameRef == "-")
return llvm::MemoryBuffer::getSTDIN();
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> inputFileOrErr = nullptr;
for (unsigned I = 0; I != BADFRetry + 1; ++ I) {
inputFileOrErr = FS.getBufferForFile(Filename, FileSize,
RequiresNullTerminator, IsVolatile);
if (inputFileOrErr)
return inputFileOrErr;
if (inputFileOrErr.getError().value() != EBADF)
return inputFileOrErr;
}
return inputFileOrErr;
}
|