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
30using namespace llvm;
31using namespace llvm::vfs;
32
33void ProxyOutputBackend::anchor() {}
34void OnDiskOutputBackend::anchor() {}
35
36IntrusiveRefCntPtr<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
50IntrusiveRefCntPtr<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
81IntrusiveRefCntPtr<OutputBackend>
82vfs::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 reserveExtraSpace(uint64_t ExtraSize) 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
192static OutputConfig
193applySettings(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
204namespace {
205class OnDiskOutputFile final : public OutputFileImpl {
206public:
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
243static 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
258static 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
272Error 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
303Error 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
347Error 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
370namespace {
371class OpenFileRAII {
372 static const int InvalidFd = -1;
373
374public:
375 int Fd = InvalidFd;
376
377 ~OpenFileRAII() {
378 if (Fd != InvalidFd)
379 llvm::sys::Process::SafelyCloseFileDescriptor(FD: Fd);
380 }
381};
382
383enum 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
395static Expected<FileDifference>
396areFilesDifferent(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
453Error 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
469Error 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
575Error 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
599Error 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
606Expected<std::unique_ptr<OutputFileImpl>>
607OnDiskOutputBackend::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