| 1 | //===----------------------------------------------------------------------===// |
| 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 | /// \file |
| 10 | /// This file implements the VirtualOutputBackend types, including: |
| 11 | /// * NullOutputBackend: Outputs to NullOutputBackend are discarded. |
| 12 | /// * FilteringOutputBackend: Filter paths from output. |
| 13 | /// * MirroringOutputBackend: Mirror the output into two different backend. |
| 14 | /// * OnDiskOutputBackend: Write output files to disk. |
| 15 | /// |
| 16 | //===----------------------------------------------------------------------===// |
| 17 | |
| 18 | #include "llvm/Support/VirtualOutputBackends.h" |
| 19 | #include "llvm/ADT/ScopeExit.h" |
| 20 | #include "llvm/Support/FileSystem.h" |
| 21 | #include "llvm/Support/IOSandbox.h" |
| 22 | #include "llvm/Support/LockFileManager.h" |
| 23 | #include "llvm/Support/MemoryBuffer.h" |
| 24 | #include "llvm/Support/Path.h" |
| 25 | #include "llvm/Support/Process.h" |
| 26 | #include "llvm/Support/Signals.h" |
| 27 | #include "llvm/Support/VirtualOutputConfig.h" |
| 28 | #include "llvm/Support/VirtualOutputError.h" |
| 29 | |
| 30 | using namespace llvm; |
| 31 | using namespace llvm::vfs; |
| 32 | |
| 33 | void ProxyOutputBackend::anchor() {} |
| 34 | void OnDiskOutputBackend::anchor() {} |
| 35 | |
| 36 | IntrusiveRefCntPtr<OutputBackend> vfs::makeNullOutputBackend() { |
| 37 | struct NullOutputBackend : public OutputBackend { |
| 38 | IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override { |
| 39 | return const_cast<NullOutputBackend *>(this); |
| 40 | } |
| 41 | Expected<std::unique_ptr<OutputFileImpl>> |
| 42 | createFileImpl(StringRef Path, std::optional<OutputConfig>) override { |
| 43 | return std::make_unique<NullOutputFileImpl>(); |
| 44 | } |
| 45 | }; |
| 46 | |
| 47 | return makeIntrusiveRefCnt<NullOutputBackend>(); |
| 48 | } |
| 49 | |
| 50 | IntrusiveRefCntPtr<OutputBackend> vfs::makeFilteringOutputBackend( |
| 51 | IntrusiveRefCntPtr<OutputBackend> UnderlyingBackend, |
| 52 | std::function<bool(StringRef, std::optional<OutputConfig>)> Filter) { |
| 53 | struct FilteringOutputBackend : public ProxyOutputBackend { |
| 54 | Expected<std::unique_ptr<OutputFileImpl>> |
| 55 | createFileImpl(StringRef Path, |
| 56 | std::optional<OutputConfig> Config) override { |
| 57 | if (Filter(Path, Config)) |
| 58 | return ProxyOutputBackend::createFileImpl(Path, Config); |
| 59 | return std::make_unique<NullOutputFileImpl>(); |
| 60 | } |
| 61 | |
| 62 | IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override { |
| 63 | return makeIntrusiveRefCnt<FilteringOutputBackend>( |
| 64 | A: getUnderlyingBackend().clone(), A: Filter); |
| 65 | } |
| 66 | |
| 67 | FilteringOutputBackend( |
| 68 | IntrusiveRefCntPtr<OutputBackend> UnderlyingBackend, |
| 69 | std::function<bool(StringRef, std::optional<OutputConfig>)> Filter) |
| 70 | : ProxyOutputBackend(std::move(UnderlyingBackend)), |
| 71 | Filter(std::move(Filter)) { |
| 72 | assert(this->Filter && "Expected a non-null function" ); |
| 73 | } |
| 74 | std::function<bool(StringRef, std::optional<OutputConfig>)> Filter; |
| 75 | }; |
| 76 | |
| 77 | return makeIntrusiveRefCnt<FilteringOutputBackend>( |
| 78 | A: std::move(UnderlyingBackend), A: std::move(Filter)); |
| 79 | } |
| 80 | |
| 81 | IntrusiveRefCntPtr<OutputBackend> |
| 82 | vfs::makeMirroringOutputBackend(IntrusiveRefCntPtr<OutputBackend> Backend1, |
| 83 | IntrusiveRefCntPtr<OutputBackend> Backend2) { |
| 84 | struct ProxyOutputBackend1 : public ProxyOutputBackend { |
| 85 | using ProxyOutputBackend::ProxyOutputBackend; |
| 86 | }; |
| 87 | struct ProxyOutputBackend2 : public ProxyOutputBackend { |
| 88 | using ProxyOutputBackend::ProxyOutputBackend; |
| 89 | }; |
| 90 | struct MirroringOutput final : public OutputFileImpl, raw_pwrite_stream { |
| 91 | Error keep() final { |
| 92 | flush(); |
| 93 | return joinErrors(E1: F1->keep(), E2: F2->keep()); |
| 94 | } |
| 95 | Error discard() final { |
| 96 | flush(); |
| 97 | return joinErrors(E1: F1->discard(), E2: F2->discard()); |
| 98 | } |
| 99 | raw_pwrite_stream &getOS() final { return *this; } |
| 100 | |
| 101 | void write_impl(const char *Ptr, size_t Size) override { |
| 102 | F1->getOS().write(Ptr, Size); |
| 103 | F2->getOS().write(Ptr, Size); |
| 104 | } |
| 105 | void pwrite_impl(const char *Ptr, size_t Size, uint64_t Offset) override { |
| 106 | this->flush(); |
| 107 | F1->getOS().pwrite(Ptr, Size, Offset); |
| 108 | F2->getOS().pwrite(Ptr, Size, Offset); |
| 109 | } |
| 110 | uint64_t current_pos() const override { return F1->getOS().tell(); } |
| 111 | size_t preferred_buffer_size() const override { |
| 112 | return PreferredBufferSize; |
| 113 | } |
| 114 | void (uint64_t ) override { |
| 115 | F1->getOS().reserveExtraSpace(ExtraSize); |
| 116 | F2->getOS().reserveExtraSpace(ExtraSize); |
| 117 | } |
| 118 | bool is_displayed() const override { |
| 119 | return F1->getOS().is_displayed() && F2->getOS().is_displayed(); |
| 120 | } |
| 121 | bool has_colors() const override { |
| 122 | return F1->getOS().has_colors() && F2->getOS().has_colors(); |
| 123 | } |
| 124 | void enable_colors(bool enable) override { |
| 125 | raw_pwrite_stream::enable_colors(enable); |
| 126 | F1->getOS().enable_colors(enable); |
| 127 | F2->getOS().enable_colors(enable); |
| 128 | } |
| 129 | |
| 130 | MirroringOutput(std::unique_ptr<OutputFileImpl> F1, |
| 131 | std::unique_ptr<OutputFileImpl> F2) |
| 132 | : PreferredBufferSize(std::max(a: F1->getOS().GetBufferSize(), |
| 133 | b: F1->getOS().GetBufferSize())), |
| 134 | F1(std::move(F1)), F2(std::move(F2)) {} |
| 135 | |
| 136 | size_t PreferredBufferSize; |
| 137 | std::unique_ptr<OutputFileImpl> F1; |
| 138 | std::unique_ptr<OutputFileImpl> F2; |
| 139 | }; |
| 140 | struct MirroringOutputBackend : public ProxyOutputBackend1, |
| 141 | public ProxyOutputBackend2 { |
| 142 | Expected<std::unique_ptr<OutputFileImpl>> |
| 143 | createFileImpl(StringRef Path, |
| 144 | std::optional<OutputConfig> Config) override { |
| 145 | std::unique_ptr<OutputFileImpl> File1; |
| 146 | std::unique_ptr<OutputFileImpl> File2; |
| 147 | if (Error E = |
| 148 | ProxyOutputBackend1::createFileImpl(Path, Config).moveInto(Value&: File1)) |
| 149 | return std::move(E); |
| 150 | if (Error E = |
| 151 | ProxyOutputBackend2::createFileImpl(Path, Config).moveInto(Value&: File2)) |
| 152 | return joinErrors(E1: std::move(E), E2: File1->discard()); |
| 153 | |
| 154 | // Skip the extra indirection if one of these is a null output. |
| 155 | if (isa<NullOutputFileImpl>(Val: *File1)) { |
| 156 | consumeError(Err: File1->discard()); |
| 157 | return std::move(File2); |
| 158 | } |
| 159 | if (isa<NullOutputFileImpl>(Val: *File2)) { |
| 160 | consumeError(Err: File2->discard()); |
| 161 | return std::move(File1); |
| 162 | } |
| 163 | return std::make_unique<MirroringOutput>(args: std::move(File1), |
| 164 | args: std::move(File2)); |
| 165 | } |
| 166 | |
| 167 | IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override { |
| 168 | return IntrusiveRefCntPtr<ProxyOutputBackend1>( |
| 169 | makeIntrusiveRefCnt<MirroringOutputBackend>( |
| 170 | A: ProxyOutputBackend1::getUnderlyingBackend().clone(), |
| 171 | A: ProxyOutputBackend2::getUnderlyingBackend().clone())); |
| 172 | } |
| 173 | void Retain() const { ProxyOutputBackend1::Retain(); } |
| 174 | void Release() const { ProxyOutputBackend1::Release(); } |
| 175 | |
| 176 | MirroringOutputBackend(IntrusiveRefCntPtr<OutputBackend> Backend1, |
| 177 | IntrusiveRefCntPtr<OutputBackend> Backend2) |
| 178 | : ProxyOutputBackend1(std::move(Backend1)), |
| 179 | ProxyOutputBackend2(std::move(Backend2)) {} |
| 180 | }; |
| 181 | |
| 182 | assert(Backend1 && "Expected actual backend" ); |
| 183 | assert(Backend2 && "Expected actual backend" ); |
| 184 | return IntrusiveRefCntPtr<ProxyOutputBackend1>( |
| 185 | makeIntrusiveRefCnt<MirroringOutputBackend>(A: std::move(Backend1), |
| 186 | A: std::move(Backend2))); |
| 187 | } |
| 188 | |
| 189 | static OutputConfig |
| 190 | applySettings(std::optional<OutputConfig> &&Config, |
| 191 | const OnDiskOutputBackend::OutputSettings &Settings) { |
| 192 | if (!Config) |
| 193 | Config = Settings.DefaultConfig; |
| 194 | if (!Settings.UseTemporaries) |
| 195 | Config->setNoAtomicWrite(); |
| 196 | if (!Settings.RemoveOnSignal) |
| 197 | Config->setNoDiscardOnSignal(); |
| 198 | return *Config; |
| 199 | } |
| 200 | |
| 201 | namespace { |
| 202 | class OnDiskOutputFile final : public OutputFileImpl { |
| 203 | public: |
| 204 | Error keep() override; |
| 205 | Error discard() override; |
| 206 | raw_pwrite_stream &getOS() override { |
| 207 | assert(FileOS && "Expected valid file" ); |
| 208 | if (BufferOS) |
| 209 | return *BufferOS; |
| 210 | return *FileOS; |
| 211 | } |
| 212 | |
| 213 | /// Attempt to open a temporary file for \p OutputPath. |
| 214 | /// |
| 215 | /// This tries to open a uniquely-named temporary file for \p OutputPath, |
| 216 | /// possibly also creating any missing directories if \a |
| 217 | /// OnDiskOutputConfig::UseTemporaryCreateMissingDirectories is set in \a |
| 218 | /// Config. |
| 219 | /// |
| 220 | /// \post FD and \a TempPath are initialized if this is successful. |
| 221 | Error tryToCreateTemporary(std::optional<int> &FD); |
| 222 | |
| 223 | Error initializeFile(std::optional<int> &FD); |
| 224 | Error initializeStream(); |
| 225 | Error reset(); |
| 226 | |
| 227 | OnDiskOutputFile(StringRef OutputPath, std::optional<OutputConfig> Config, |
| 228 | const OnDiskOutputBackend::OutputSettings &Settings) |
| 229 | : Config(applySettings(Config: std::move(Config), Settings)), |
| 230 | OutputPath(OutputPath.str()) {} |
| 231 | |
| 232 | OutputConfig Config; |
| 233 | const std::string OutputPath; |
| 234 | std::optional<std::string> TempPath; |
| 235 | std::optional<raw_fd_ostream> FileOS; |
| 236 | std::optional<buffer_ostream> BufferOS; |
| 237 | }; |
| 238 | } // end namespace |
| 239 | |
| 240 | static Error createDirectoriesOnDemand(StringRef OutputPath, |
| 241 | OutputConfig Config, |
| 242 | llvm::function_ref<Error()> CreateFile) { |
| 243 | return handleErrors(E: CreateFile(), Hs: [&](std::unique_ptr<ECError> EC) { |
| 244 | if (EC->convertToErrorCode() != std::errc::no_such_file_or_directory || |
| 245 | Config.getNoImplyCreateDirectories()) |
| 246 | return Error(std::move(EC)); |
| 247 | |
| 248 | StringRef ParentPath = sys::path::parent_path(path: OutputPath); |
| 249 | if (std::error_code EC = sys::fs::create_directories(path: ParentPath)) |
| 250 | return make_error<OutputError>(Args&: ParentPath, Args&: EC); |
| 251 | return CreateFile(); |
| 252 | }); |
| 253 | } |
| 254 | |
| 255 | static sys::fs::OpenFlags generateFlagsFromConfig(OutputConfig Config) { |
| 256 | sys::fs::OpenFlags OF = sys::fs::OF_None; |
| 257 | if (Config.getTextWithCRLF()) |
| 258 | OF |= sys::fs::OF_TextWithCRLF; |
| 259 | else if (Config.getText()) |
| 260 | OF |= sys::fs::OF_Text; |
| 261 | // Don't pass OF_Append if writting to temporary since OF_Append is |
| 262 | // not Atomic Append |
| 263 | if (Config.getAppend() && !Config.getAtomicWrite()) |
| 264 | OF |= sys::fs::OF_Append; |
| 265 | |
| 266 | return OF; |
| 267 | } |
| 268 | |
| 269 | Error OnDiskOutputFile::tryToCreateTemporary(std::optional<int> &FD) { |
| 270 | auto BypassSandbox = sys::sandbox::scopedDisable(); |
| 271 | |
| 272 | // Create a temporary file. |
| 273 | // Insert -%%%%%%%% before the extension (if any), and because some tools |
| 274 | // (noticeable, clang's own GlobalModuleIndex.cpp) glob for build |
| 275 | // artifacts, also append .tmp. |
| 276 | StringRef OutputExtension = sys::path::extension(path: OutputPath); |
| 277 | SmallString<128> ModelPath = |
| 278 | StringRef(OutputPath).drop_back(N: OutputExtension.size()); |
| 279 | ModelPath += "-%%%%%%%%" ; |
| 280 | ModelPath += OutputExtension; |
| 281 | ModelPath += ".tmp" ; |
| 282 | |
| 283 | return createDirectoriesOnDemand(OutputPath, Config, CreateFile: [&]() -> Error { |
| 284 | int NewFD; |
| 285 | SmallString<128> UniquePath; |
| 286 | sys::fs::OpenFlags OF = generateFlagsFromConfig(Config); |
| 287 | if (std::error_code EC = |
| 288 | sys::fs::createUniqueFile(Model: ModelPath, ResultFD&: NewFD, ResultPath&: UniquePath, Flags: OF)) |
| 289 | return make_error<TempFileOutputError>(Args&: ModelPath, Args: OutputPath, Args&: EC); |
| 290 | |
| 291 | if (Config.getDiscardOnSignal()) |
| 292 | sys::RemoveFileOnSignal(Filename: UniquePath); |
| 293 | |
| 294 | TempPath = UniquePath.str().str(); |
| 295 | FD.emplace(args&: NewFD); |
| 296 | return Error::success(); |
| 297 | }); |
| 298 | } |
| 299 | |
| 300 | Error OnDiskOutputFile::initializeFile(std::optional<int> &FD) { |
| 301 | auto BypassSandbox = sys::sandbox::scopedDisable(); |
| 302 | |
| 303 | assert(OutputPath != "-" && "Unexpected request for FD of stdout" ); |
| 304 | |
| 305 | // Disable temporary file for other non-regular files, and if we get a status |
| 306 | // object, also check if in append mode we can write and disable write-through |
| 307 | // buffers if appropriate. |
| 308 | if (Config.getAtomicWrite()) { |
| 309 | sys::fs::file_status Status; |
| 310 | sys::fs::status(path: OutputPath, result&: Status); |
| 311 | if (sys::fs::exists(status: Status)) { |
| 312 | if (!sys::fs::is_regular_file(status: Status)) |
| 313 | Config.setNoAtomicWrite(); |
| 314 | |
| 315 | // In append mode, we will open the file for writing which will need write |
| 316 | // permission. Fail now if it is already clear that we can't write to the |
| 317 | // final destination. |
| 318 | // In non-append mode, we will delete and replace the file. Permission |
| 319 | // bits of the file itself are irrelevant in this case. |
| 320 | if (Config.getAppend() && !sys::fs::can_write(Path: OutputPath)) |
| 321 | return make_error<OutputError>( |
| 322 | Args: OutputPath, |
| 323 | Args: std::make_error_code(e: std::errc::operation_not_permitted)); |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | // If (still) using a temporary file, try to create it (and return success if |
| 328 | // that works). |
| 329 | if (Config.getAtomicWrite()) |
| 330 | if (!errorToBool(Err: tryToCreateTemporary(FD))) |
| 331 | return Error::success(); |
| 332 | |
| 333 | // Not using a temporary file. Open the final output file. |
| 334 | return createDirectoriesOnDemand(OutputPath, Config, CreateFile: [&]() -> Error { |
| 335 | int NewFD; |
| 336 | sys::fs::OpenFlags OF = generateFlagsFromConfig(Config); |
| 337 | if (std::error_code EC = sys::fs::openFileForWrite( |
| 338 | Name: OutputPath, ResultFD&: NewFD, Disp: sys::fs::CD_CreateAlways, Flags: OF)) |
| 339 | return convertToOutputError(OutputPath, EC); |
| 340 | FD.emplace(args&: NewFD); |
| 341 | |
| 342 | if (Config.getDiscardOnSignal()) |
| 343 | sys::RemoveFileOnSignal(Filename: OutputPath); |
| 344 | return Error::success(); |
| 345 | }); |
| 346 | } |
| 347 | |
| 348 | Error OnDiskOutputFile::initializeStream() { |
| 349 | auto BypassSandbox = sys::sandbox::scopedDisable(); |
| 350 | |
| 351 | // Open the file stream. |
| 352 | if (OutputPath == "-" ) { |
| 353 | std::error_code EC; |
| 354 | FileOS.emplace(args: OutputPath, args&: EC); |
| 355 | if (EC) |
| 356 | return make_error<OutputError>(Args: OutputPath, Args&: EC); |
| 357 | } else { |
| 358 | std::optional<int> FD; |
| 359 | if (Error E = initializeFile(FD)) |
| 360 | return E; |
| 361 | FileOS.emplace(args&: *FD, /*shouldClose=*/args: true); |
| 362 | } |
| 363 | |
| 364 | // Buffer the stream if necessary. |
| 365 | if (!FileOS->supportsSeeking() && !Config.getText()) |
| 366 | BufferOS.emplace(args&: *FileOS); |
| 367 | |
| 368 | return Error::success(); |
| 369 | } |
| 370 | |
| 371 | namespace { |
| 372 | class OpenFileRAII { |
| 373 | static const int InvalidFd = -1; |
| 374 | |
| 375 | public: |
| 376 | int Fd = InvalidFd; |
| 377 | |
| 378 | ~OpenFileRAII() { |
| 379 | if (Fd != InvalidFd) |
| 380 | llvm::sys::Process::SafelyCloseFileDescriptor(FD: Fd); |
| 381 | } |
| 382 | }; |
| 383 | |
| 384 | enum class FileDifference : uint8_t { |
| 385 | /// The source and destination paths refer to the exact same file. |
| 386 | IdenticalFile, |
| 387 | /// The source and destination paths refer to separate files with identical |
| 388 | /// contents. |
| 389 | SameContents, |
| 390 | /// The source and destination paths refer to separate files with different |
| 391 | /// contents. |
| 392 | DifferentContents |
| 393 | }; |
| 394 | } // end anonymous namespace |
| 395 | |
| 396 | static Expected<FileDifference> |
| 397 | areFilesDifferent(const llvm::Twine &Source, const llvm::Twine &Destination) { |
| 398 | if (sys::fs::equivalent(A: Source, B: Destination)) |
| 399 | return FileDifference::IdenticalFile; |
| 400 | |
| 401 | OpenFileRAII SourceFile; |
| 402 | sys::fs::file_status SourceStatus; |
| 403 | // If we can't open the source file, fail. |
| 404 | if (std::error_code EC = sys::fs::openFileForRead(Name: Source, ResultFD&: SourceFile.Fd)) |
| 405 | return convertToOutputError(OutputPath: Source, EC); |
| 406 | |
| 407 | // If we can't stat the source file, fail. |
| 408 | if (std::error_code EC = sys::fs::status(F: SourceFile.Fd, Result&: SourceStatus)) |
| 409 | return convertToOutputError(OutputPath: Source, EC); |
| 410 | |
| 411 | OpenFileRAII DestFile; |
| 412 | sys::fs::file_status DestStatus; |
| 413 | // If we can't open the destination file, report different. |
| 414 | if (std::error_code Error = |
| 415 | sys::fs::openFileForRead(Name: Destination, ResultFD&: DestFile.Fd)) |
| 416 | return FileDifference::DifferentContents; |
| 417 | |
| 418 | // If we can't open the destination file, report different. |
| 419 | if (std::error_code Error = sys::fs::status(F: DestFile.Fd, Result&: DestStatus)) |
| 420 | return FileDifference::DifferentContents; |
| 421 | |
| 422 | // If the files are different sizes, they must be different. |
| 423 | uint64_t Size = SourceStatus.getSize(); |
| 424 | if (Size != DestStatus.getSize()) |
| 425 | return FileDifference::DifferentContents; |
| 426 | |
| 427 | // If both files are zero size, they must be the same. |
| 428 | if (Size == 0) |
| 429 | return FileDifference::SameContents; |
| 430 | |
| 431 | // The two files match in size, so we have to compare the bytes to determine |
| 432 | // if they're the same. |
| 433 | std::error_code SourceRegionErr; |
| 434 | sys::fs::mapped_file_region SourceRegion( |
| 435 | sys::fs::convertFDToNativeFile(FD: SourceFile.Fd), |
| 436 | sys::fs::mapped_file_region::readonly, Size, 0, SourceRegionErr); |
| 437 | if (SourceRegionErr) |
| 438 | return convertToOutputError(OutputPath: Source, EC: SourceRegionErr); |
| 439 | |
| 440 | std::error_code DestRegionErr; |
| 441 | sys::fs::mapped_file_region DestRegion( |
| 442 | sys::fs::convertFDToNativeFile(FD: DestFile.Fd), |
| 443 | sys::fs::mapped_file_region::readonly, Size, 0, DestRegionErr); |
| 444 | |
| 445 | if (DestRegionErr) |
| 446 | return FileDifference::DifferentContents; |
| 447 | |
| 448 | if (memcmp(s1: SourceRegion.const_data(), s2: DestRegion.const_data(), n: Size) != 0) |
| 449 | return FileDifference::DifferentContents; |
| 450 | |
| 451 | return FileDifference::SameContents; |
| 452 | } |
| 453 | |
| 454 | Error OnDiskOutputFile::reset() { |
| 455 | auto BypassSandbox = sys::sandbox::scopedDisable(); |
| 456 | |
| 457 | // Destroy the streams to flush them. |
| 458 | BufferOS.reset(); |
| 459 | if (!FileOS) |
| 460 | return Error::success(); |
| 461 | |
| 462 | // Remember the error in raw_fd_ostream to be reported later. |
| 463 | std::error_code EC = FileOS->error(); |
| 464 | // Clear the error to avoid fatal error when reset. |
| 465 | FileOS->clear_error(); |
| 466 | FileOS.reset(); |
| 467 | return errorCodeToError(EC); |
| 468 | } |
| 469 | |
| 470 | Error OnDiskOutputFile::keep() { |
| 471 | auto BypassSandbox = sys::sandbox::scopedDisable(); |
| 472 | |
| 473 | if (auto E = reset()) |
| 474 | return E; |
| 475 | |
| 476 | // Close the file descriptor and remove crash cleanup before exit. |
| 477 | llvm::scope_exit RemoveDiscardOnSignal([&]() { |
| 478 | if (Config.getDiscardOnSignal()) |
| 479 | sys::DontRemoveFileOnSignal(Filename: TempPath ? *TempPath : OutputPath); |
| 480 | }); |
| 481 | |
| 482 | if (!TempPath) |
| 483 | return Error::success(); |
| 484 | |
| 485 | // See if we should append instead of move. |
| 486 | if (Config.getAppend() && OutputPath != "-" ) { |
| 487 | // Read TempFile for the content to append. |
| 488 | auto Content = MemoryBuffer::getFile(Filename: *TempPath); |
| 489 | if (!Content) |
| 490 | return convertToTempFileOutputError(TempPath: *TempPath, OutputPath, |
| 491 | EC: Content.getError()); |
| 492 | while (1) { |
| 493 | // Attempt to lock the output file. |
| 494 | // Only one process is allowed to append to this file at a time. |
| 495 | llvm::LockFileManager Lock(OutputPath); |
| 496 | bool Owned; |
| 497 | if (Error Err = Lock.tryLock().moveInto(Value&: Owned)) { |
| 498 | // If we error acquiring a lock, we cannot ensure appends |
| 499 | // to the trace file are atomic - cannot ensure output correctness. |
| 500 | Lock.unsafeUnlock(); |
| 501 | return convertToOutputError( |
| 502 | OutputPath, EC: std::make_error_code(e: std::errc::no_lock_available)); |
| 503 | } |
| 504 | if (Owned) { |
| 505 | // Lock acquired, perform the write and release the lock. |
| 506 | std::error_code EC; |
| 507 | llvm::raw_fd_ostream Out(OutputPath, EC, llvm::sys::fs::OF_Append); |
| 508 | if (EC) |
| 509 | return convertToOutputError(OutputPath, EC); |
| 510 | Out << (*Content)->getBuffer(); |
| 511 | Out.close(); |
| 512 | Lock.unsafeUnlock(); |
| 513 | if (Out.has_error()) |
| 514 | return convertToOutputError(OutputPath, EC: Out.error()); |
| 515 | // Remove temp file and done. |
| 516 | (void)sys::fs::remove(path: *TempPath); |
| 517 | return Error::success(); |
| 518 | } |
| 519 | // Someone else owns the lock on this file, wait. |
| 520 | switch (Lock.waitForUnlockFor(MaxSeconds: std::chrono::seconds(256))) { |
| 521 | case WaitForUnlockResult::Success: |
| 522 | [[fallthrough]]; |
| 523 | case WaitForUnlockResult::OwnerDied: { |
| 524 | continue; // try again to get the lock. |
| 525 | } |
| 526 | case WaitForUnlockResult::Timeout: { |
| 527 | // We could error on timeout to avoid potentially hanging forever, but |
| 528 | // it may be more likely that an interrupted process failed to clear |
| 529 | // the lock, causing other waiting processes to time-out. Let's clear |
| 530 | // the lock and try again right away. If we do start seeing compiler |
| 531 | // hangs in this location, we will need to re-consider. |
| 532 | Lock.unsafeUnlock(); |
| 533 | continue; |
| 534 | } |
| 535 | } |
| 536 | break; |
| 537 | } |
| 538 | } |
| 539 | |
| 540 | if (Config.getOnlyIfDifferent()) { |
| 541 | auto Result = areFilesDifferent(Source: *TempPath, Destination: OutputPath); |
| 542 | if (!Result) |
| 543 | return Result.takeError(); |
| 544 | switch (*Result) { |
| 545 | case FileDifference::IdenticalFile: |
| 546 | // Do nothing for a self-move. |
| 547 | return Error::success(); |
| 548 | |
| 549 | case FileDifference::SameContents: |
| 550 | // Files are identical; remove the source file. |
| 551 | (void)sys::fs::remove(path: *TempPath); |
| 552 | return Error::success(); |
| 553 | |
| 554 | case FileDifference::DifferentContents: |
| 555 | break; // Rename the file. |
| 556 | } |
| 557 | } |
| 558 | |
| 559 | // Move temporary to the final output path and remove it if that fails. |
| 560 | std::error_code RenameEC = sys::fs::rename(from: *TempPath, to: OutputPath); |
| 561 | if (!RenameEC) |
| 562 | return Error::success(); |
| 563 | |
| 564 | // FIXME: TempPath should be in the same directory as OutputPath but try to |
| 565 | // copy the output to see if makes any difference. If this path is used, |
| 566 | // investigate why we need to copy. |
| 567 | RenameEC = sys::fs::copy_file(From: *TempPath, To: OutputPath); |
| 568 | (void)sys::fs::remove(path: *TempPath); |
| 569 | |
| 570 | if (!RenameEC) |
| 571 | return Error::success(); |
| 572 | |
| 573 | return make_error<TempFileOutputError>(Args&: *TempPath, Args: OutputPath, Args&: RenameEC); |
| 574 | } |
| 575 | |
| 576 | Error OnDiskOutputFile::discard() { |
| 577 | auto BypassSandbox = sys::sandbox::scopedDisable(); |
| 578 | |
| 579 | // Destroy the streams to flush them. |
| 580 | if (auto E = reset()) |
| 581 | return E; |
| 582 | |
| 583 | // Nothing on the filesystem to remove for stdout. |
| 584 | if (OutputPath == "-" ) |
| 585 | return Error::success(); |
| 586 | |
| 587 | auto discardPath = [&](StringRef Path) { |
| 588 | std::error_code EC = sys::fs::remove(path: Path); |
| 589 | sys::DontRemoveFileOnSignal(Filename: Path); |
| 590 | return EC; |
| 591 | }; |
| 592 | |
| 593 | // Clean up the file that's in-progress. |
| 594 | if (!TempPath) |
| 595 | return convertToOutputError(OutputPath, EC: discardPath(OutputPath)); |
| 596 | return convertToTempFileOutputError(TempPath: *TempPath, OutputPath, |
| 597 | EC: discardPath(*TempPath)); |
| 598 | } |
| 599 | |
| 600 | Error OnDiskOutputBackend::makeAbsolute(SmallVectorImpl<char> &Path) const { |
| 601 | // FIXME: Should this really call sys::fs::make_absolute? |
| 602 | auto BypassSandbox = sys::sandbox::scopedDisable(); |
| 603 | return convertToOutputError(OutputPath: StringRef(Path.data(), Path.size()), |
| 604 | EC: sys::fs::make_absolute(path&: Path)); |
| 605 | } |
| 606 | |
| 607 | Expected<std::unique_ptr<OutputFileImpl>> |
| 608 | OnDiskOutputBackend::createFileImpl(StringRef Path, |
| 609 | std::optional<OutputConfig> Config) { |
| 610 | auto BypassSandbox = sys::sandbox::scopedDisable(); |
| 611 | |
| 612 | SmallString<256> AbsPath; |
| 613 | if (Path != "-" ) { |
| 614 | AbsPath = Path; |
| 615 | if (Error E = makeAbsolute(Path&: AbsPath)) |
| 616 | return std::move(E); |
| 617 | Path = AbsPath; |
| 618 | } |
| 619 | |
| 620 | auto File = std::make_unique<OnDiskOutputFile>(args&: Path, args&: Config, args&: Settings); |
| 621 | if (Error E = File->initializeStream()) |
| 622 | return std::move(E); |
| 623 | |
| 624 | return std::move(File); |
| 625 | } |
| 626 | |