1//===- VirtualFileSystem.h - Virtual File System Layer ----------*- C++ -*-===//
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/// Defines the virtual file system interface vfs::FileSystem.
11//
12//===----------------------------------------------------------------------===//
13
14#ifndef LLVM_SUPPORT_VIRTUALFILESYSTEM_H
15#define LLVM_SUPPORT_VIRTUALFILESYSTEM_H
16
17#include "llvm/ADT/IntrusiveRefCntPtr.h"
18#include "llvm/ADT/STLFunctionalExtras.h"
19#include "llvm/ADT/SmallVector.h"
20#include "llvm/ADT/StringRef.h"
21#include "llvm/Support/Chrono.h"
22#include "llvm/Support/Errc.h"
23#include "llvm/Support/Error.h"
24#include "llvm/Support/ErrorOr.h"
25#include "llvm/Support/ExtensibleRTTI.h"
26#include "llvm/Support/FileSystem.h"
27#include "llvm/Support/Path.h"
28#include "llvm/Support/SourceMgr.h"
29#include <cassert>
30#include <cstdint>
31#include <ctime>
32#include <memory>
33#include <optional>
34#include <stack>
35#include <string>
36#include <system_error>
37#include <utility>
38#include <vector>
39
40namespace llvm {
41
42class MemoryBuffer;
43class MemoryBufferRef;
44class Twine;
45
46namespace vfs {
47
48/// The result of a \p status operation.
49class Status {
50 std::string Name;
51 llvm::sys::fs::UniqueID UID;
52 llvm::sys::TimePoint<> MTime;
53 uint32_t User;
54 uint32_t Group;
55 uint64_t Size;
56 llvm::sys::fs::file_type Type = llvm::sys::fs::file_type::status_error;
57 llvm::sys::fs::perms Perms;
58
59public:
60 /// Whether this entity has an external path different from the virtual path,
61 /// and the external path is exposed by leaking it through the abstraction.
62 /// For example, a RedirectingFileSystem will set this for paths where
63 /// UseExternalName is true.
64 ///
65 /// FIXME: Currently the external path is exposed by replacing the virtual
66 /// path in this Status object. Instead, we should leave the path in the
67 /// Status intact (matching the requested virtual path) - see
68 /// FileManager::getFileRef for how we plan to fix this.
69 bool ExposesExternalVFSPath = false;
70
71 Status() = default;
72 Status(const llvm::sys::fs::file_status &Status);
73 Status(const Twine &Name, llvm::sys::fs::UniqueID UID,
74 llvm::sys::TimePoint<> MTime, uint32_t User, uint32_t Group,
75 uint64_t Size, llvm::sys::fs::file_type Type,
76 llvm::sys::fs::perms Perms);
77
78 /// Get a copy of a Status with a different size.
79 static Status copyWithNewSize(const Status &In, uint64_t NewSize);
80 /// Get a copy of a Status with a different name.
81 static Status copyWithNewName(const Status &In, const Twine &NewName);
82 static Status copyWithNewName(const llvm::sys::fs::file_status &In,
83 const Twine &NewName);
84
85 /// Returns the name that should be used for this file or directory.
86 StringRef getName() const { return Name; }
87
88 /// @name Status interface from llvm::sys::fs
89 /// @{
90 llvm::sys::fs::file_type getType() const { return Type; }
91 llvm::sys::fs::perms getPermissions() const { return Perms; }
92 llvm::sys::TimePoint<> getLastModificationTime() const { return MTime; }
93 llvm::sys::fs::UniqueID getUniqueID() const { return UID; }
94 uint32_t getUser() const { return User; }
95 uint32_t getGroup() const { return Group; }
96 uint64_t getSize() const { return Size; }
97 /// @}
98 /// @name Status queries
99 /// These are static queries in llvm::sys::fs.
100 /// @{
101 bool equivalent(const Status &Other) const;
102 bool isDirectory() const;
103 bool isRegularFile() const;
104 bool isOther() const;
105 bool isSymlink() const;
106 bool isStatusKnown() const;
107 bool exists() const;
108 /// @}
109};
110
111/// Represents an open file.
112class File {
113public:
114 /// Destroy the file after closing it (if open).
115 /// Sub-classes should generally call close() inside their destructors. We
116 /// cannot do that from the base class, since close is virtual.
117 virtual ~File();
118
119 /// Get the status of the file.
120 virtual llvm::ErrorOr<Status> status() = 0;
121
122 /// Get the name of the file
123 virtual llvm::ErrorOr<std::string> getName() {
124 if (auto Status = status())
125 return Status->getName().str();
126 else
127 return Status.getError();
128 }
129
130 /// Get the contents of the file as a \p MemoryBuffer.
131 virtual llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
132 getBuffer(const Twine &Name, int64_t FileSize = -1,
133 bool RequiresNullTerminator = true, bool IsVolatile = false) = 0;
134
135 /// Closes the file.
136 virtual std::error_code close() = 0;
137
138 // Get the same file with a different path.
139 static ErrorOr<std::unique_ptr<File>>
140 getWithPath(ErrorOr<std::unique_ptr<File>> Result, const Twine &P);
141
142protected:
143 // Set the file's underlying path.
144 virtual void setPath(const Twine &Path) {}
145};
146
147/// A member of a directory, yielded by a directory_iterator.
148/// Only information available on most platforms is included.
149class directory_entry {
150 std::string Path;
151 llvm::sys::fs::file_type Type = llvm::sys::fs::file_type::type_unknown;
152
153public:
154 directory_entry() = default;
155 directory_entry(std::string Path, llvm::sys::fs::file_type Type)
156 : Path(std::move(Path)), Type(Type) {}
157
158 llvm::StringRef path() const { return Path; }
159 llvm::sys::fs::file_type type() const { return Type; }
160};
161
162namespace detail {
163
164/// An interface for virtual file systems to provide an iterator over the
165/// (non-recursive) contents of a directory.
166struct DirIterImpl {
167 virtual ~DirIterImpl();
168
169 /// Sets \c CurrentEntry to the next entry in the directory on success,
170 /// to directory_entry() at end, or returns a system-defined \c error_code.
171 virtual std::error_code increment() = 0;
172
173 directory_entry CurrentEntry;
174};
175
176} // namespace detail
177
178/// An input iterator over the entries in a virtual path, similar to
179/// llvm::sys::fs::directory_iterator.
180class directory_iterator {
181 std::shared_ptr<detail::DirIterImpl> Impl; // Input iterator semantics on copy
182
183public:
184 directory_iterator(std::shared_ptr<detail::DirIterImpl> I)
185 : Impl(std::move(I)) {
186 assert(Impl.get() != nullptr && "requires non-null implementation");
187 if (Impl->CurrentEntry.path().empty())
188 Impl.reset(); // Normalize the end iterator to Impl == nullptr.
189 }
190
191 /// Construct an 'end' iterator.
192 directory_iterator() = default;
193
194 /// Equivalent to operator++, with an error code.
195 directory_iterator &increment(std::error_code &EC) {
196 assert(Impl && "attempting to increment past end");
197 EC = Impl->increment();
198 if (Impl->CurrentEntry.path().empty())
199 Impl.reset(); // Normalize the end iterator to Impl == nullptr.
200 return *this;
201 }
202
203 const directory_entry &operator*() const { return Impl->CurrentEntry; }
204 const directory_entry *operator->() const { return &Impl->CurrentEntry; }
205
206 bool operator==(const directory_iterator &RHS) const {
207 if (Impl && RHS.Impl)
208 return Impl->CurrentEntry.path() == RHS.Impl->CurrentEntry.path();
209 return !Impl && !RHS.Impl;
210 }
211 bool operator!=(const directory_iterator &RHS) const {
212 return !(*this == RHS);
213 }
214};
215
216class FileSystem;
217
218namespace detail {
219
220/// Keeps state for the recursive_directory_iterator.
221struct RecDirIterState {
222 std::stack<directory_iterator, std::vector<directory_iterator>> Stack;
223 bool HasNoPushRequest = false;
224};
225
226} // end namespace detail
227
228/// An input iterator over the recursive contents of a virtual path,
229/// similar to llvm::sys::fs::recursive_directory_iterator.
230class recursive_directory_iterator {
231 FileSystem *FS;
232 std::shared_ptr<detail::RecDirIterState>
233 State; // Input iterator semantics on copy.
234
235public:
236 recursive_directory_iterator(FileSystem &FS, const Twine &Path,
237 std::error_code &EC);
238
239 /// Construct an 'end' iterator.
240 recursive_directory_iterator() = default;
241
242 /// Equivalent to operator++, with an error code.
243 recursive_directory_iterator &increment(std::error_code &EC);
244
245 const directory_entry &operator*() const { return *State->Stack.top(); }
246 const directory_entry *operator->() const { return &*State->Stack.top(); }
247
248 bool operator==(const recursive_directory_iterator &Other) const {
249 return State == Other.State; // identity
250 }
251 bool operator!=(const recursive_directory_iterator &RHS) const {
252 return !(*this == RHS);
253 }
254
255 /// Gets the current level. Starting path is at level 0.
256 int level() const {
257 assert(!State->Stack.empty() &&
258 "Cannot get level without any iteration state");
259 return State->Stack.size() - 1;
260 }
261
262 void no_push() { State->HasNoPushRequest = true; }
263};
264
265/// The virtual file system interface.
266class FileSystem : public llvm::ThreadSafeRefCountedBase<FileSystem>,
267 public RTTIExtends<FileSystem, RTTIRoot> {
268public:
269 static const char ID;
270 virtual ~FileSystem();
271
272 /// Get the status of the entry at \p Path, if one exists.
273 virtual llvm::ErrorOr<Status> status(const Twine &Path) = 0;
274
275 /// Get a \p File object for the file at \p Path, if one exists.
276 virtual llvm::ErrorOr<std::unique_ptr<File>>
277 openFileForRead(const Twine &Path) = 0;
278
279 /// This is a convenience method that opens a file, gets its content and then
280 /// closes the file.
281 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
282 getBufferForFile(const Twine &Name, int64_t FileSize = -1,
283 bool RequiresNullTerminator = true, bool IsVolatile = false);
284
285 /// Get a directory_iterator for \p Dir.
286 /// \note The 'end' iterator is directory_iterator().
287 virtual directory_iterator dir_begin(const Twine &Dir,
288 std::error_code &EC) = 0;
289
290 /// Set the working directory. This will affect all following operations on
291 /// this file system and may propagate down for nested file systems.
292 virtual std::error_code setCurrentWorkingDirectory(const Twine &Path) = 0;
293
294 /// Get the working directory of this file system.
295 virtual llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const = 0;
296
297 /// Gets real path of \p Path e.g. collapse all . and .. patterns, resolve
298 /// symlinks. For real file system, this uses `llvm::sys::fs::real_path`.
299 /// This returns errc::operation_not_permitted if not implemented by subclass.
300 virtual std::error_code getRealPath(const Twine &Path,
301 SmallVectorImpl<char> &Output) const;
302
303 /// Check whether a file exists. Provided for convenience.
304 bool exists(const Twine &Path);
305
306 /// Is the file mounted on a local filesystem?
307 virtual std::error_code isLocal(const Twine &Path, bool &Result);
308
309 /// Make \a Path an absolute path.
310 ///
311 /// Makes \a Path absolute using the current directory if it is not already.
312 /// An empty \a Path will result in the current directory.
313 ///
314 /// /absolute/path => /absolute/path
315 /// relative/../path => <current-directory>/relative/../path
316 ///
317 /// \param Path A path that is modified to be an absolute path.
318 /// \returns success if \a path has been made absolute, otherwise a
319 /// platform-specific error_code.
320 virtual std::error_code makeAbsolute(SmallVectorImpl<char> &Path) const;
321
322 enum class PrintType { Summary, Contents, RecursiveContents };
323 void print(raw_ostream &OS, PrintType Type = PrintType::Contents,
324 unsigned IndentLevel = 0) const {
325 printImpl(OS, Type, IndentLevel);
326 }
327
328 using VisitCallbackTy = llvm::function_ref<void(FileSystem &)>;
329 virtual void visitChildFileSystems(VisitCallbackTy Callback) {}
330 void visit(VisitCallbackTy Callback) {
331 Callback(*this);
332 visitChildFileSystems(Callback);
333 }
334
335#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
336 LLVM_DUMP_METHOD void dump() const;
337#endif
338
339protected:
340 virtual void printImpl(raw_ostream &OS, PrintType Type,
341 unsigned IndentLevel) const {
342 printIndent(OS, IndentLevel);
343 OS << "FileSystem\n";
344 }
345
346 void printIndent(raw_ostream &OS, unsigned IndentLevel) const {
347 for (unsigned i = 0; i < IndentLevel; ++i)
348 OS << " ";
349 }
350};
351
352/// Gets an \p vfs::FileSystem for the 'real' file system, as seen by
353/// the operating system.
354/// The working directory is linked to the process's working directory.
355/// (This is usually thread-hostile).
356IntrusiveRefCntPtr<FileSystem> getRealFileSystem();
357
358/// Create an \p vfs::FileSystem for the 'real' file system, as seen by
359/// the operating system.
360/// It has its own working directory, independent of (but initially equal to)
361/// that of the process.
362std::unique_ptr<FileSystem> createPhysicalFileSystem();
363
364/// A file system that allows overlaying one \p AbstractFileSystem on top
365/// of another.
366///
367/// Consists of a stack of >=1 \p FileSystem objects, which are treated as being
368/// one merged file system. When there is a directory that exists in more than
369/// one file system, the \p OverlayFileSystem contains a directory containing
370/// the union of their contents. The attributes (permissions, etc.) of the
371/// top-most (most recently added) directory are used. When there is a file
372/// that exists in more than one file system, the file in the top-most file
373/// system overrides the other(s).
374class OverlayFileSystem : public RTTIExtends<OverlayFileSystem, FileSystem> {
375 using FileSystemList = SmallVector<IntrusiveRefCntPtr<FileSystem>, 1>;
376
377 /// The stack of file systems, implemented as a list in order of
378 /// their addition.
379 FileSystemList FSList;
380
381public:
382 static const char ID;
383 OverlayFileSystem(IntrusiveRefCntPtr<FileSystem> Base);
384
385 /// Pushes a file system on top of the stack.
386 void pushOverlay(IntrusiveRefCntPtr<FileSystem> FS);
387
388 llvm::ErrorOr<Status> status(const Twine &Path) override;
389 llvm::ErrorOr<std::unique_ptr<File>>
390 openFileForRead(const Twine &Path) override;
391 directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override;
392 llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override;
393 std::error_code setCurrentWorkingDirectory(const Twine &Path) override;
394 std::error_code isLocal(const Twine &Path, bool &Result) override;
395 std::error_code getRealPath(const Twine &Path,
396 SmallVectorImpl<char> &Output) const override;
397
398 using iterator = FileSystemList::reverse_iterator;
399 using const_iterator = FileSystemList::const_reverse_iterator;
400 using reverse_iterator = FileSystemList::iterator;
401 using const_reverse_iterator = FileSystemList::const_iterator;
402 using range = iterator_range<iterator>;
403 using const_range = iterator_range<const_iterator>;
404
405 /// Get an iterator pointing to the most recently added file system.
406 iterator overlays_begin() { return FSList.rbegin(); }
407 const_iterator overlays_begin() const { return FSList.rbegin(); }
408
409 /// Get an iterator pointing one-past the least recently added file system.
410 iterator overlays_end() { return FSList.rend(); }
411 const_iterator overlays_end() const { return FSList.rend(); }
412
413 /// Get an iterator pointing to the least recently added file system.
414 reverse_iterator overlays_rbegin() { return FSList.begin(); }
415 const_reverse_iterator overlays_rbegin() const { return FSList.begin(); }
416
417 /// Get an iterator pointing one-past the most recently added file system.
418 reverse_iterator overlays_rend() { return FSList.end(); }
419 const_reverse_iterator overlays_rend() const { return FSList.end(); }
420
421 range overlays_range() { return llvm::reverse(C&: FSList); }
422 const_range overlays_range() const { return llvm::reverse(C: FSList); }
423
424protected:
425 void printImpl(raw_ostream &OS, PrintType Type,
426 unsigned IndentLevel) const override;
427 void visitChildFileSystems(VisitCallbackTy Callback) override;
428};
429
430/// By default, this delegates all calls to the underlying file system. This
431/// is useful when derived file systems want to override some calls and still
432/// proxy other calls.
433class ProxyFileSystem : public RTTIExtends<ProxyFileSystem, FileSystem> {
434public:
435 static const char ID;
436 explicit ProxyFileSystem(IntrusiveRefCntPtr<FileSystem> FS)
437 : FS(std::move(FS)) {}
438
439 llvm::ErrorOr<Status> status(const Twine &Path) override {
440 return FS->status(Path);
441 }
442 llvm::ErrorOr<std::unique_ptr<File>>
443 openFileForRead(const Twine &Path) override {
444 return FS->openFileForRead(Path);
445 }
446 directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override {
447 return FS->dir_begin(Dir, EC);
448 }
449 llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
450 return FS->getCurrentWorkingDirectory();
451 }
452 std::error_code setCurrentWorkingDirectory(const Twine &Path) override {
453 return FS->setCurrentWorkingDirectory(Path);
454 }
455 std::error_code getRealPath(const Twine &Path,
456 SmallVectorImpl<char> &Output) const override {
457 return FS->getRealPath(Path, Output);
458 }
459 std::error_code isLocal(const Twine &Path, bool &Result) override {
460 return FS->isLocal(Path, Result);
461 }
462
463protected:
464 FileSystem &getUnderlyingFS() const { return *FS; }
465 void visitChildFileSystems(VisitCallbackTy Callback) override {
466 if (FS) {
467 Callback(*FS);
468 FS->visitChildFileSystems(Callback);
469 }
470 }
471
472private:
473 IntrusiveRefCntPtr<FileSystem> FS;
474
475 virtual void anchor() override;
476};
477
478namespace detail {
479
480class InMemoryDirectory;
481class InMemoryNode;
482
483struct NewInMemoryNodeInfo {
484 llvm::sys::fs::UniqueID DirUID;
485 StringRef Path;
486 StringRef Name;
487 time_t ModificationTime;
488 std::unique_ptr<llvm::MemoryBuffer> Buffer;
489 uint32_t User;
490 uint32_t Group;
491 llvm::sys::fs::file_type Type;
492 llvm::sys::fs::perms Perms;
493
494 Status makeStatus() const;
495};
496
497class NamedNodeOrError {
498 ErrorOr<std::pair<llvm::SmallString<128>, const detail::InMemoryNode *>>
499 Value;
500
501public:
502 NamedNodeOrError(llvm::SmallString<128> Name,
503 const detail::InMemoryNode *Node)
504 : Value(std::make_pair(x&: Name, y&: Node)) {}
505 NamedNodeOrError(std::error_code EC) : Value(EC) {}
506 NamedNodeOrError(llvm::errc EC) : Value(EC) {}
507
508 StringRef getName() const { return (*Value).first; }
509 explicit operator bool() const { return static_cast<bool>(Value); }
510 operator std::error_code() const { return Value.getError(); }
511 std::error_code getError() const { return Value.getError(); }
512 const detail::InMemoryNode *operator*() const { return (*Value).second; }
513};
514
515} // namespace detail
516
517/// An in-memory file system.
518class InMemoryFileSystem : public RTTIExtends<InMemoryFileSystem, FileSystem> {
519 std::unique_ptr<detail::InMemoryDirectory> Root;
520 std::string WorkingDirectory;
521 bool UseNormalizedPaths = true;
522
523public:
524 static const char ID;
525
526private:
527 using MakeNodeFn = llvm::function_ref<std::unique_ptr<detail::InMemoryNode>(
528 detail::NewInMemoryNodeInfo)>;
529
530 /// Create node with \p MakeNode and add it into this filesystem at \p Path.
531 bool addFile(const Twine &Path, time_t ModificationTime,
532 std::unique_ptr<llvm::MemoryBuffer> Buffer,
533 std::optional<uint32_t> User, std::optional<uint32_t> Group,
534 std::optional<llvm::sys::fs::file_type> Type,
535 std::optional<llvm::sys::fs::perms> Perms, MakeNodeFn MakeNode);
536
537 /// Looks up the in-memory node for the path \p P.
538 /// If \p FollowFinalSymlink is true, the returned node is guaranteed to
539 /// not be a symlink and its path may differ from \p P.
540 detail::NamedNodeOrError lookupNode(const Twine &P, bool FollowFinalSymlink,
541 size_t SymlinkDepth = 0) const;
542
543 class DirIterator;
544
545public:
546 explicit InMemoryFileSystem(bool UseNormalizedPaths = true);
547 ~InMemoryFileSystem() override;
548
549 /// Add a file containing a buffer or a directory to the VFS with a
550 /// path. The VFS owns the buffer. If present, User, Group, Type
551 /// and Perms apply to the newly-created file or directory.
552 /// \return true if the file or directory was successfully added,
553 /// false if the file or directory already exists in the file system with
554 /// different contents.
555 bool addFile(const Twine &Path, time_t ModificationTime,
556 std::unique_ptr<llvm::MemoryBuffer> Buffer,
557 std::optional<uint32_t> User = std::nullopt,
558 std::optional<uint32_t> Group = std::nullopt,
559 std::optional<llvm::sys::fs::file_type> Type = std::nullopt,
560 std::optional<llvm::sys::fs::perms> Perms = std::nullopt);
561
562 /// Add a hard link to a file.
563 ///
564 /// Here hard links are not intended to be fully equivalent to the classical
565 /// filesystem. Both the hard link and the file share the same buffer and
566 /// status (and thus have the same UniqueID). Because of this there is no way
567 /// to distinguish between the link and the file after the link has been
568 /// added.
569 ///
570 /// The \p Target path must be an existing file or a hardlink. The
571 /// \p NewLink file must not have been added before. The \p Target
572 /// path must not be a directory. The \p NewLink node is added as a hard
573 /// link which points to the resolved file of \p Target node.
574 /// \return true if the above condition is satisfied and hardlink was
575 /// successfully created, false otherwise.
576 bool addHardLink(const Twine &NewLink, const Twine &Target);
577
578 /// Arbitrary max depth to search through symlinks. We can get into problems
579 /// if a link links to a link that links back to the link, for example.
580 static constexpr size_t MaxSymlinkDepth = 16;
581
582 /// Add a symbolic link. Unlike a HardLink, because \p Target doesn't need
583 /// to refer to a file (or refer to anything, as it happens). Also, an
584 /// in-memory directory for \p Target isn't automatically created.
585 bool
586 addSymbolicLink(const Twine &NewLink, const Twine &Target,
587 time_t ModificationTime,
588 std::optional<uint32_t> User = std::nullopt,
589 std::optional<uint32_t> Group = std::nullopt,
590 std::optional<llvm::sys::fs::perms> Perms = std::nullopt);
591
592 /// Add a buffer to the VFS with a path. The VFS does not own the buffer.
593 /// If present, User, Group, Type and Perms apply to the newly-created file
594 /// or directory.
595 /// \return true if the file or directory was successfully added,
596 /// false if the file or directory already exists in the file system with
597 /// different contents.
598 bool addFileNoOwn(const Twine &Path, time_t ModificationTime,
599 const llvm::MemoryBufferRef &Buffer,
600 std::optional<uint32_t> User = std::nullopt,
601 std::optional<uint32_t> Group = std::nullopt,
602 std::optional<llvm::sys::fs::file_type> Type = std::nullopt,
603 std::optional<llvm::sys::fs::perms> Perms = std::nullopt);
604
605 std::string toString() const;
606
607 /// Return true if this file system normalizes . and .. in paths.
608 bool useNormalizedPaths() const { return UseNormalizedPaths; }
609
610 llvm::ErrorOr<Status> status(const Twine &Path) override;
611 llvm::ErrorOr<std::unique_ptr<File>>
612 openFileForRead(const Twine &Path) override;
613 directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override;
614
615 llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
616 return WorkingDirectory;
617 }
618 /// Canonicalizes \p Path by combining with the current working
619 /// directory and normalizing the path (e.g. remove dots). If the current
620 /// working directory is not set, this returns errc::operation_not_permitted.
621 ///
622 /// This doesn't resolve symlinks as they are not supported in in-memory file
623 /// system.
624 std::error_code getRealPath(const Twine &Path,
625 SmallVectorImpl<char> &Output) const override;
626 std::error_code isLocal(const Twine &Path, bool &Result) override;
627 std::error_code setCurrentWorkingDirectory(const Twine &Path) override;
628
629protected:
630 void printImpl(raw_ostream &OS, PrintType Type,
631 unsigned IndentLevel) const override;
632};
633
634/// Get a globally unique ID for a virtual file or directory.
635llvm::sys::fs::UniqueID getNextVirtualUniqueID();
636
637/// Gets a \p FileSystem for a virtual file system described in YAML
638/// format.
639std::unique_ptr<FileSystem>
640getVFSFromYAML(std::unique_ptr<llvm::MemoryBuffer> Buffer,
641 llvm::SourceMgr::DiagHandlerTy DiagHandler,
642 StringRef YAMLFilePath, void *DiagContext = nullptr,
643 IntrusiveRefCntPtr<FileSystem> ExternalFS = getRealFileSystem());
644
645struct YAMLVFSEntry {
646 template <typename T1, typename T2>
647 YAMLVFSEntry(T1 &&VPath, T2 &&RPath, bool IsDirectory = false)
648 : VPath(std::forward<T1>(VPath)), RPath(std::forward<T2>(RPath)),
649 IsDirectory(IsDirectory) {}
650 std::string VPath;
651 std::string RPath;
652 bool IsDirectory = false;
653};
654
655class RedirectingFSDirIterImpl;
656class RedirectingFileSystemParser;
657
658/// A virtual file system parsed from a YAML file.
659///
660/// Currently, this class allows creating virtual files and directories. Virtual
661/// files map to existing external files in \c ExternalFS, and virtual
662/// directories may either map to existing directories in \c ExternalFS or list
663/// their contents in the form of other virtual directories and/or files.
664///
665/// The basic structure of the parsed file is:
666/// \verbatim
667/// {
668/// 'version': <version number>,
669/// <optional configuration>
670/// 'roots': [
671/// <directory entries>
672/// ]
673/// }
674/// \endverbatim
675///
676/// The roots may be absolute or relative. If relative they will be made
677/// absolute against either current working directory or the directory where
678/// the Overlay YAML file is located, depending on the 'root-relative'
679/// configuration.
680///
681/// All configuration options are optional.
682/// 'case-sensitive': <boolean, default=(true for Posix, false for Windows)>
683/// 'use-external-names': <boolean, default=true>
684/// 'root-relative': <string, one of 'cwd' or 'overlay-dir', default='cwd'>
685/// 'overlay-relative': <boolean, default=false>
686/// 'fallthrough': <boolean, default=true, deprecated - use 'redirecting-with'
687/// instead>
688/// 'redirecting-with': <string, one of 'fallthrough', 'fallback', or
689/// 'redirect-only', default='fallthrough'>
690///
691/// To clarify, 'root-relative' option will prepend the current working
692/// directory, or the overlay directory to the 'roots->name' field only if
693/// 'roots->name' is a relative path. On the other hand, when 'overlay-relative'
694/// is set to 'true', external paths will always be prepended with the overlay
695/// directory, even if external paths are not relative paths. The
696/// 'root-relative' option has no interaction with the 'overlay-relative'
697/// option.
698///
699/// Virtual directories that list their contents are represented as
700/// \verbatim
701/// {
702/// 'type': 'directory',
703/// 'name': <string>,
704/// 'contents': [ <file or directory entries> ]
705/// }
706/// \endverbatim
707///
708/// The default attributes for such virtual directories are:
709/// \verbatim
710/// MTime = now() when created
711/// Perms = 0777
712/// User = Group = 0
713/// Size = 0
714/// UniqueID = unspecified unique value
715/// \endverbatim
716///
717/// When a path prefix matches such a directory, the next component in the path
718/// is matched against the entries in the 'contents' array.
719///
720/// Re-mapped directories, on the other hand, are represented as
721/// /// \verbatim
722/// {
723/// 'type': 'directory-remap',
724/// 'name': <string>,
725/// 'use-external-name': <boolean>, # Optional
726/// 'external-contents': <path to external directory>
727/// }
728/// \endverbatim
729///
730/// and inherit their attributes from the external directory. When a path
731/// prefix matches such an entry, the unmatched components are appended to the
732/// 'external-contents' path, and the resulting path is looked up in the
733/// external file system instead.
734///
735/// Re-mapped files are represented as
736/// \verbatim
737/// {
738/// 'type': 'file',
739/// 'name': <string>,
740/// 'use-external-name': <boolean>, # Optional
741/// 'external-contents': <path to external file>
742/// }
743/// \endverbatim
744///
745/// Their attributes and file contents are determined by looking up the file at
746/// their 'external-contents' path in the external file system.
747///
748/// For 'file', 'directory' and 'directory-remap' entries the 'name' field may
749/// contain multiple path components (e.g. /path/to/file). However, any
750/// directory in such a path that contains more than one child must be uniquely
751/// represented by a 'directory' entry.
752///
753/// When the 'use-external-name' field is set, calls to \a vfs::File::status()
754/// give the external (remapped) filesystem name instead of the name the file
755/// was accessed by. This is an intentional leak through the \a
756/// RedirectingFileSystem abstraction layer. It enables clients to discover
757/// (and use) the external file location when communicating with users or tools
758/// that don't use the same VFS overlay.
759///
760/// FIXME: 'use-external-name' causes behaviour that's inconsistent with how
761/// "real" filesystems behave. Maybe there should be a separate channel for
762/// this information.
763class RedirectingFileSystem
764 : public RTTIExtends<RedirectingFileSystem, vfs::FileSystem> {
765public:
766 static const char ID;
767 enum EntryKind { EK_Directory, EK_DirectoryRemap, EK_File };
768 enum NameKind { NK_NotSet, NK_External, NK_Virtual };
769
770 /// The type of redirection to perform.
771 enum class RedirectKind {
772 /// Lookup the redirected path first (ie. the one specified in
773 /// 'external-contents') and if that fails "fallthrough" to a lookup of the
774 /// originally provided path.
775 Fallthrough,
776 /// Lookup the provided path first and if that fails, "fallback" to a
777 /// lookup of the redirected path.
778 Fallback,
779 /// Only lookup the redirected path, do not lookup the originally provided
780 /// path.
781 RedirectOnly
782 };
783
784 /// The type of relative path used by Roots.
785 enum class RootRelativeKind {
786 /// The roots are relative to the current working directory.
787 CWD,
788 /// The roots are relative to the directory where the Overlay YAML file
789 // locates.
790 OverlayDir
791 };
792
793 /// A single file or directory in the VFS.
794 class Entry {
795 EntryKind Kind;
796 std::string Name;
797
798 public:
799 Entry(EntryKind K, StringRef Name) : Kind(K), Name(Name) {}
800 virtual ~Entry() = default;
801
802 StringRef getName() const { return Name; }
803 EntryKind getKind() const { return Kind; }
804 };
805
806 /// A directory in the vfs with explicitly specified contents.
807 class DirectoryEntry : public Entry {
808 std::vector<std::unique_ptr<Entry>> Contents;
809 Status S;
810
811 public:
812 /// Constructs a directory entry with explicitly specified contents.
813 DirectoryEntry(StringRef Name, std::vector<std::unique_ptr<Entry>> Contents,
814 Status S)
815 : Entry(EK_Directory, Name), Contents(std::move(Contents)),
816 S(std::move(S)) {}
817
818 /// Constructs an empty directory entry.
819 DirectoryEntry(StringRef Name, Status S)
820 : Entry(EK_Directory, Name), S(std::move(S)) {}
821
822 Status getStatus() { return S; }
823
824 void addContent(std::unique_ptr<Entry> Content) {
825 Contents.push_back(x: std::move(Content));
826 }
827
828 Entry *getLastContent() const { return Contents.back().get(); }
829
830 using iterator = decltype(Contents)::iterator;
831
832 iterator contents_begin() { return Contents.begin(); }
833 iterator contents_end() { return Contents.end(); }
834
835 static bool classof(const Entry *E) { return E->getKind() == EK_Directory; }
836 };
837
838 /// A file or directory in the vfs that is mapped to a file or directory in
839 /// the external filesystem.
840 class RemapEntry : public Entry {
841 std::string ExternalContentsPath;
842 NameKind UseName;
843
844 protected:
845 RemapEntry(EntryKind K, StringRef Name, StringRef ExternalContentsPath,
846 NameKind UseName)
847 : Entry(K, Name), ExternalContentsPath(ExternalContentsPath),
848 UseName(UseName) {}
849
850 public:
851 StringRef getExternalContentsPath() const { return ExternalContentsPath; }
852
853 /// Whether to use the external path as the name for this file or directory.
854 bool useExternalName(bool GlobalUseExternalName) const {
855 return UseName == NK_NotSet ? GlobalUseExternalName
856 : (UseName == NK_External);
857 }
858
859 NameKind getUseName() const { return UseName; }
860
861 static bool classof(const Entry *E) {
862 switch (E->getKind()) {
863 case EK_DirectoryRemap:
864 [[fallthrough]];
865 case EK_File:
866 return true;
867 case EK_Directory:
868 return false;
869 }
870 llvm_unreachable("invalid entry kind");
871 }
872 };
873
874 /// A directory in the vfs that maps to a directory in the external file
875 /// system.
876 class DirectoryRemapEntry : public RemapEntry {
877 public:
878 DirectoryRemapEntry(StringRef Name, StringRef ExternalContentsPath,
879 NameKind UseName)
880 : RemapEntry(EK_DirectoryRemap, Name, ExternalContentsPath, UseName) {}
881
882 static bool classof(const Entry *E) {
883 return E->getKind() == EK_DirectoryRemap;
884 }
885 };
886
887 /// A file in the vfs that maps to a file in the external file system.
888 class FileEntry : public RemapEntry {
889 public:
890 FileEntry(StringRef Name, StringRef ExternalContentsPath, NameKind UseName)
891 : RemapEntry(EK_File, Name, ExternalContentsPath, UseName) {}
892
893 static bool classof(const Entry *E) { return E->getKind() == EK_File; }
894 };
895
896 /// Represents the result of a path lookup into the RedirectingFileSystem.
897 struct LookupResult {
898 /// Chain of parent directory entries for \c E.
899 llvm::SmallVector<Entry *, 32> Parents;
900
901 /// The entry the looked-up path corresponds to.
902 Entry *E;
903
904 private:
905 /// When the found Entry is a DirectoryRemapEntry, stores the path in the
906 /// external file system that the looked-up path in the virtual file system
907 // corresponds to.
908 std::optional<std::string> ExternalRedirect;
909
910 public:
911 LookupResult(Entry *E, sys::path::const_iterator Start,
912 sys::path::const_iterator End);
913
914 /// If the found Entry maps the input path to a path in the external
915 /// file system (i.e. it is a FileEntry or DirectoryRemapEntry), returns
916 /// that path.
917 std::optional<StringRef> getExternalRedirect() const {
918 if (isa<DirectoryRemapEntry>(Val: E))
919 return StringRef(*ExternalRedirect);
920 if (auto *FE = dyn_cast<FileEntry>(Val: E))
921 return FE->getExternalContentsPath();
922 return std::nullopt;
923 }
924
925 /// Get the (canonical) path of the found entry. This uses the as-written
926 /// path components from the VFS specification.
927 void getPath(llvm::SmallVectorImpl<char> &Path) const;
928 };
929
930private:
931 friend class RedirectingFSDirIterImpl;
932 friend class RedirectingFileSystemParser;
933
934 /// Canonicalize path by removing ".", "..", "./", components. This is
935 /// a VFS request, do not bother about symlinks in the path components
936 /// but canonicalize in order to perform the correct entry search.
937 std::error_code makeCanonical(SmallVectorImpl<char> &Path) const;
938
939 /// Get the File status, or error, from the underlying external file system.
940 /// This returns the status with the originally requested name, while looking
941 /// up the entry using the canonical path.
942 ErrorOr<Status> getExternalStatus(const Twine &CanonicalPath,
943 const Twine &OriginalPath) const;
944
945 /// Make \a Path an absolute path.
946 ///
947 /// Makes \a Path absolute using the \a WorkingDir if it is not already.
948 ///
949 /// /absolute/path => /absolute/path
950 /// relative/../path => <WorkingDir>/relative/../path
951 ///
952 /// \param WorkingDir A path that will be used as the base Dir if \a Path
953 /// is not already absolute.
954 /// \param Path A path that is modified to be an absolute path.
955 /// \returns success if \a path has been made absolute, otherwise a
956 /// platform-specific error_code.
957 std::error_code makeAbsolute(StringRef WorkingDir,
958 SmallVectorImpl<char> &Path) const;
959
960 // In a RedirectingFileSystem, keys can be specified in Posix or Windows
961 // style (or even a mixture of both), so this comparison helper allows
962 // slashes (representing a root) to match backslashes (and vice versa). Note
963 // that, other than the root, path components should not contain slashes or
964 // backslashes.
965 bool pathComponentMatches(llvm::StringRef lhs, llvm::StringRef rhs) const {
966 if ((CaseSensitive ? lhs.equals(RHS: rhs) : lhs.equals_insensitive(RHS: rhs)))
967 return true;
968 return (lhs == "/" && rhs == "\\") || (lhs == "\\" && rhs == "/");
969 }
970
971 /// The root(s) of the virtual file system.
972 std::vector<std::unique_ptr<Entry>> Roots;
973
974 /// The current working directory of the file system.
975 std::string WorkingDirectory;
976
977 /// The file system to use for external references.
978 IntrusiveRefCntPtr<FileSystem> ExternalFS;
979
980 /// This represents the directory path that the YAML file is located.
981 /// This will be prefixed to each 'external-contents' if IsRelativeOverlay
982 /// is set. This will also be prefixed to each 'roots->name' if RootRelative
983 /// is set to RootRelativeKind::OverlayDir and the path is relative.
984 std::string OverlayFileDir;
985
986 /// @name Configuration
987 /// @{
988
989 /// Whether to perform case-sensitive comparisons.
990 ///
991 /// Currently, case-insensitive matching only works correctly with ASCII.
992 bool CaseSensitive = is_style_posix(S: sys::path::Style::native);
993
994 /// IsRelativeOverlay marks whether a OverlayFileDir path must
995 /// be prefixed in every 'external-contents' when reading from YAML files.
996 bool IsRelativeOverlay = false;
997
998 /// Whether to use to use the value of 'external-contents' for the
999 /// names of files. This global value is overridable on a per-file basis.
1000 bool UseExternalNames = true;
1001
1002 /// True if this FS has redirected a lookup. This does not include
1003 /// fallthrough.
1004 mutable bool HasBeenUsed = false;
1005
1006 /// Used to enable or disable updating `HasBeenUsed`.
1007 bool UsageTrackingActive = false;
1008
1009 /// Determines the lookups to perform, as well as their order. See
1010 /// \c RedirectKind for details.
1011 RedirectKind Redirection = RedirectKind::Fallthrough;
1012
1013 /// Determine the prefix directory if the roots are relative paths. See
1014 /// \c RootRelativeKind for details.
1015 RootRelativeKind RootRelative = RootRelativeKind::CWD;
1016 /// @}
1017
1018 RedirectingFileSystem(IntrusiveRefCntPtr<FileSystem> ExternalFS);
1019
1020 /// Looks up the path <tt>[Start, End)</tt> in \p From, possibly recursing
1021 /// into the contents of \p From if it is a directory. Returns a LookupResult
1022 /// giving the matched entry and, if that entry is a FileEntry or
1023 /// DirectoryRemapEntry, the path it redirects to in the external file system.
1024 ErrorOr<LookupResult>
1025 lookupPathImpl(llvm::sys::path::const_iterator Start,
1026 llvm::sys::path::const_iterator End, Entry *From,
1027 llvm::SmallVectorImpl<Entry *> &Entries) const;
1028
1029 /// Get the status for a path with the provided \c LookupResult.
1030 ErrorOr<Status> status(const Twine &CanonicalPath, const Twine &OriginalPath,
1031 const LookupResult &Result);
1032
1033public:
1034 /// Looks up \p Path in \c Roots and returns a LookupResult giving the
1035 /// matched entry and, if the entry was a FileEntry or DirectoryRemapEntry,
1036 /// the path it redirects to in the external file system.
1037 ErrorOr<LookupResult> lookupPath(StringRef Path) const;
1038
1039 /// Parses \p Buffer, which is expected to be in YAML format and
1040 /// returns a virtual file system representing its contents.
1041 static std::unique_ptr<RedirectingFileSystem>
1042 create(std::unique_ptr<MemoryBuffer> Buffer,
1043 SourceMgr::DiagHandlerTy DiagHandler, StringRef YAMLFilePath,
1044 void *DiagContext, IntrusiveRefCntPtr<FileSystem> ExternalFS);
1045
1046 /// Redirect each of the remapped files from first to second.
1047 static std::unique_ptr<RedirectingFileSystem>
1048 create(ArrayRef<std::pair<std::string, std::string>> RemappedFiles,
1049 bool UseExternalNames, FileSystem &ExternalFS);
1050
1051 ErrorOr<Status> status(const Twine &Path) override;
1052 ErrorOr<std::unique_ptr<File>> openFileForRead(const Twine &Path) override;
1053
1054 std::error_code getRealPath(const Twine &Path,
1055 SmallVectorImpl<char> &Output) const override;
1056
1057 llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override;
1058
1059 std::error_code setCurrentWorkingDirectory(const Twine &Path) override;
1060
1061 std::error_code isLocal(const Twine &Path, bool &Result) override;
1062
1063 std::error_code makeAbsolute(SmallVectorImpl<char> &Path) const override;
1064
1065 directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override;
1066
1067 void setOverlayFileDir(StringRef PrefixDir);
1068
1069 StringRef getOverlayFileDir() const;
1070
1071 /// Sets the redirection kind to \c Fallthrough if true or \c RedirectOnly
1072 /// otherwise. Will removed in the future, use \c setRedirection instead.
1073 void setFallthrough(bool Fallthrough);
1074
1075 void setRedirection(RedirectingFileSystem::RedirectKind Kind);
1076
1077 std::vector<llvm::StringRef> getRoots() const;
1078
1079 bool hasBeenUsed() const { return HasBeenUsed; };
1080 void clearHasBeenUsed() { HasBeenUsed = false; }
1081
1082 void setUsageTrackingActive(bool Active) { UsageTrackingActive = Active; }
1083
1084 void printEntry(raw_ostream &OS, Entry *E, unsigned IndentLevel = 0) const;
1085
1086protected:
1087 void printImpl(raw_ostream &OS, PrintType Type,
1088 unsigned IndentLevel) const override;
1089 void visitChildFileSystems(VisitCallbackTy Callback) override;
1090};
1091
1092/// Collect all pairs of <virtual path, real path> entries from the
1093/// \p YAMLFilePath. This is used by the module dependency collector to forward
1094/// the entries into the reproducer output VFS YAML file.
1095void collectVFSFromYAML(
1096 std::unique_ptr<llvm::MemoryBuffer> Buffer,
1097 llvm::SourceMgr::DiagHandlerTy DiagHandler, StringRef YAMLFilePath,
1098 SmallVectorImpl<YAMLVFSEntry> &CollectedEntries,
1099 void *DiagContext = nullptr,
1100 IntrusiveRefCntPtr<FileSystem> ExternalFS = getRealFileSystem());
1101
1102class YAMLVFSWriter {
1103 std::vector<YAMLVFSEntry> Mappings;
1104 std::optional<bool> IsCaseSensitive;
1105 std::optional<bool> IsOverlayRelative;
1106 std::optional<bool> UseExternalNames;
1107 std::string OverlayDir;
1108
1109 void addEntry(StringRef VirtualPath, StringRef RealPath, bool IsDirectory);
1110
1111public:
1112 YAMLVFSWriter() = default;
1113
1114 void addFileMapping(StringRef VirtualPath, StringRef RealPath);
1115 void addDirectoryMapping(StringRef VirtualPath, StringRef RealPath);
1116
1117 void setCaseSensitivity(bool CaseSensitive) {
1118 IsCaseSensitive = CaseSensitive;
1119 }
1120
1121 void setUseExternalNames(bool UseExtNames) { UseExternalNames = UseExtNames; }
1122
1123 void setOverlayDir(StringRef OverlayDirectory) {
1124 IsOverlayRelative = true;
1125 OverlayDir.assign(str: OverlayDirectory.str());
1126 }
1127
1128 const std::vector<YAMLVFSEntry> &getMappings() const { return Mappings; }
1129
1130 void write(llvm::raw_ostream &OS);
1131};
1132
1133} // namespace vfs
1134} // namespace llvm
1135
1136#endif // LLVM_SUPPORT_VIRTUALFILESYSTEM_H
1137

source code of llvm/include/llvm/Support/VirtualFileSystem.h