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
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
189static OutputConfig
190applySettings(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
201namespace {
202class OnDiskOutputFile final : public OutputFileImpl {
203public:
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
240static 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
255static 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
269Error 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
300Error 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
348Error 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
371namespace {
372class OpenFileRAII {
373 static const int InvalidFd = -1;
374
375public:
376 int Fd = InvalidFd;
377
378 ~OpenFileRAII() {
379 if (Fd != InvalidFd)
380 llvm::sys::Process::SafelyCloseFileDescriptor(FD: Fd);
381 }
382};
383
384enum 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
396static Expected<FileDifference>
397areFilesDifferent(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
454Error 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
470Error 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
576Error 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
600Error 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
607Expected<std::unique_ptr<OutputFileImpl>>
608OnDiskOutputBackend::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