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