1//===-- FileCollector.cpp ---------------------------------------*- 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#include "llvm/Support/FileCollector.h"
10#include "llvm/ADT/SmallString.h"
11#include "llvm/ADT/Twine.h"
12#include "llvm/Support/FileSystem.h"
13#include "llvm/Support/Path.h"
14#include "llvm/Support/Process.h"
15
16using namespace llvm;
17
18static bool isCaseSensitivePath(StringRef Path) {
19  SmallString<256> TmpDest = Path, UpperDest, RealDest;
20
21  // Remove component traversals, links, etc.
22  if (!sys::fs::real_path(Path, TmpDest))
23    return true; // Current default value in vfs.yaml
24  Path = TmpDest;
25
26  // Change path to all upper case and ask for its real path, if the latter
27  // exists and is equal to path, it's not case sensitive. Default to case
28  // sensitive in the absence of real_path, since this is the YAMLVFSWriter
29  // default.
30  UpperDest = Path.upper();
31  if (sys::fs::real_path(UpperDest, RealDest) && Path.equals(RealDest))
32    return false;
33  return true;
34}
35
36FileCollector::FileCollector(std::string Root, std::string OverlayRoot)
37    : Root(std::move(Root)), OverlayRoot(std::move(OverlayRoot)) {
38}
39
40bool FileCollector::getRealPath(StringRef SrcPath,
41                                SmallVectorImpl<char> &Result) {
42  SmallString<256> RealPath;
43  StringRef FileName = sys::path::filename(SrcPath);
44  std::string Directory = sys::path::parent_path(SrcPath).str();
45  auto DirWithSymlink = SymlinkMap.find(Directory);
46
47  // Use real_path to fix any symbolic link component present in a path.
48  // Computing the real path is expensive, cache the search through the parent
49  // path Directory.
50  if (DirWithSymlink == SymlinkMap.end()) {
51    auto EC = sys::fs::real_path(Directory, RealPath);
52    if (EC)
53      return false;
54    SymlinkMap[Directory] = std::string(RealPath.str());
55  } else {
56    RealPath = DirWithSymlink->second;
57  }
58
59  sys::path::append(RealPath, FileName);
60  Result.swap(RealPath);
61  return true;
62}
63
64void FileCollector::addFile(const Twine &File) {
65  std::lock_guard<std::mutex> lock(Mutex);
66  std::string FileStr = File.str();
67  if (markAsSeen(FileStr))
68    addFileImpl(FileStr);
69}
70
71void FileCollector::addDirectory(const Twine &Dir) {
72  assert(sys::fs::is_directory(Dir));
73  std::error_code EC;
74  addDirectoryImpl(Dir, vfs::getRealFileSystem(), EC);
75}
76
77void FileCollector::addFileImpl(StringRef SrcPath) {
78  // We need an absolute src path to append to the root.
79  SmallString<256> AbsoluteSrc = SrcPath;
80  sys::fs::make_absolute(AbsoluteSrc);
81
82  // Canonicalize src to a native path to avoid mixed separator styles.
83  sys::path::native(AbsoluteSrc);
84
85  // Remove redundant leading "./" pieces and consecutive separators.
86  AbsoluteSrc = sys::path::remove_leading_dotslash(AbsoluteSrc);
87
88  // Canonicalize the source path by removing "..", "." components.
89  SmallString<256> VirtualPath = AbsoluteSrc;
90  sys::path::remove_dots(VirtualPath, /*remove_dot_dot=*/true);
91
92  // If a ".." component is present after a symlink component, remove_dots may
93  // lead to the wrong real destination path. Let the source be canonicalized
94  // like that but make sure we always use the real path for the destination.
95  SmallString<256> CopyFrom;
96  if (!getRealPath(AbsoluteSrc, CopyFrom))
97    CopyFrom = VirtualPath;
98
99  SmallString<256> DstPath = StringRef(Root);
100  sys::path::append(DstPath, sys::path::relative_path(CopyFrom));
101
102  // Always map a canonical src path to its real path into the YAML, by doing
103  // this we map different virtual src paths to the same entry in the VFS
104  // overlay, which is a way to emulate symlink inside the VFS; this is also
105  // needed for correctness, not doing that can lead to module redefinition
106  // errors.
107  addFileToMapping(VirtualPath, DstPath);
108}
109
110llvm::vfs::directory_iterator
111FileCollector::addDirectoryImpl(const llvm::Twine &Dir,
112                                IntrusiveRefCntPtr<vfs::FileSystem> FS,
113                                std::error_code &EC) {
114  auto It = FS->dir_begin(Dir, EC);
115  if (EC)
116    return It;
117  addFile(Dir);
118  for (; !EC && It != llvm::vfs::directory_iterator(); It.increment(EC)) {
119    if (It->type() == sys::fs::file_type::regular_file ||
120        It->type() == sys::fs::file_type::directory_file ||
121        It->type() == sys::fs::file_type::symlink_file) {
122      addFile(It->path());
123    }
124  }
125  if (EC)
126    return It;
127  // Return a new iterator.
128  return FS->dir_begin(Dir, EC);
129}
130
131/// Set the access and modification time for the given file from the given
132/// status object.
133static std::error_code
134copyAccessAndModificationTime(StringRef Filename,
135                              const sys::fs::file_status &Stat) {
136  int FD;
137
138  if (auto EC =
139          sys::fs::openFileForWrite(Filename, FD, sys::fs::CD_OpenExisting))
140    return EC;
141
142  if (auto EC = sys::fs::setLastAccessAndModificationTime(
143          FD, Stat.getLastAccessedTime(), Stat.getLastModificationTime()))
144    return EC;
145
146  if (auto EC = sys::Process::SafelyCloseFileDescriptor(FD))
147    return EC;
148
149  return {};
150}
151
152std::error_code FileCollector::copyFiles(bool StopOnError) {
153  auto Err = sys::fs::create_directories(Root, /*IgnoreExisting=*/true);
154  if (Err) {
155    return Err;
156  }
157
158  std::lock_guard<std::mutex> lock(Mutex);
159
160  for (auto &entry : VFSWriter.getMappings()) {
161    // Create directory tree.
162    if (std::error_code EC =
163            sys::fs::create_directories(sys::path::parent_path(entry.RPath),
164                                        /*IgnoreExisting=*/true)) {
165      if (StopOnError)
166        return EC;
167    }
168
169    // Get the status of the original file/directory.
170    sys::fs::file_status Stat;
171    if (std::error_code EC = sys::fs::status(entry.VPath, Stat)) {
172      if (StopOnError)
173        return EC;
174      continue;
175    }
176
177    if (Stat.type() == sys::fs::file_type::directory_file) {
178      // Construct a directory when it's just a directory entry.
179      if (std::error_code EC =
180              sys::fs::create_directories(entry.RPath,
181                                          /*IgnoreExisting=*/true)) {
182        if (StopOnError)
183          return EC;
184      }
185      continue;
186    }
187
188    // Copy file over.
189    if (std::error_code EC = sys::fs::copy_file(entry.VPath, entry.RPath)) {
190      if (StopOnError)
191        return EC;
192    }
193
194    // Copy over permissions.
195    if (auto perms = sys::fs::getPermissions(entry.VPath)) {
196      if (std::error_code EC = sys::fs::setPermissions(entry.RPath, *perms)) {
197        if (StopOnError)
198          return EC;
199      }
200    }
201
202    // Copy over modification time.
203    copyAccessAndModificationTime(entry.RPath, Stat);
204  }
205  return {};
206}
207
208std::error_code FileCollector::writeMapping(StringRef MappingFile) {
209  std::lock_guard<std::mutex> lock(Mutex);
210
211  VFSWriter.setOverlayDir(OverlayRoot);
212  VFSWriter.setCaseSensitivity(isCaseSensitivePath(OverlayRoot));
213  VFSWriter.setUseExternalNames(false);
214
215  std::error_code EC;
216  raw_fd_ostream os(MappingFile, EC, sys::fs::OF_Text);
217  if (EC)
218    return EC;
219
220  VFSWriter.write(os);
221
222  return {};
223}
224
225namespace llvm {
226
227class FileCollectorFileSystem : public vfs::FileSystem {
228public:
229  explicit FileCollectorFileSystem(IntrusiveRefCntPtr<vfs::FileSystem> FS,
230                                   std::shared_ptr<FileCollector> Collector)
231      : FS(std::move(FS)), Collector(std::move(Collector)) {}
232
233  llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override {
234    auto Result = FS->status(Path);
235    if (Result && Result->exists())
236      Collector->addFile(Path);
237    return Result;
238  }
239
240  llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
241  openFileForRead(const Twine &Path) override {
242    auto Result = FS->openFileForRead(Path);
243    if (Result && *Result)
244      Collector->addFile(Path);
245    return Result;
246  }
247
248  llvm::vfs::directory_iterator dir_begin(const llvm::Twine &Dir,
249                                          std::error_code &EC) override {
250    return Collector->addDirectoryImpl(Dir, FS, EC);
251  }
252
253  std::error_code getRealPath(const Twine &Path,
254                              SmallVectorImpl<char> &Output) const override {
255    auto EC = FS->getRealPath(Path, Output);
256    if (!EC) {
257      Collector->addFile(Path);
258      if (Output.size() > 0)
259        Collector->addFile(Output);
260    }
261    return EC;
262  }
263
264  std::error_code isLocal(const Twine &Path, bool &Result) override {
265    return FS->isLocal(Path, Result);
266  }
267
268  llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
269    return FS->getCurrentWorkingDirectory();
270  }
271
272  std::error_code setCurrentWorkingDirectory(const llvm::Twine &Path) override {
273    return FS->setCurrentWorkingDirectory(Path);
274  }
275
276private:
277  IntrusiveRefCntPtr<vfs::FileSystem> FS;
278  std::shared_ptr<FileCollector> Collector;
279};
280
281} // namespace llvm
282
283IntrusiveRefCntPtr<vfs::FileSystem>
284FileCollector::createCollectorVFS(IntrusiveRefCntPtr<vfs::FileSystem> BaseFS,
285                                  std::shared_ptr<FileCollector> Collector) {
286  return new FileCollectorFileSystem(std::move(BaseFS), std::move(Collector));
287}
288