1//===--- FileRemapper.cpp - File Remapping Helper -------------------------===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "clang/ARCMigrate/FileRemapper.h"
11#include "clang/Basic/Diagnostic.h"
12#include "clang/Basic/FileManager.h"
13#include "clang/Lex/PreprocessorOptions.h"
14#include "llvm/Support/FileSystem.h"
15#include "llvm/Support/MemoryBuffer.h"
16#include "llvm/Support/Path.h"
17#include "llvm/Support/raw_ostream.h"
18#include <fstream>
19
20using namespace clang;
21using namespace arcmt;
22
23FileRemapper::FileRemapper() {
24  FileMgr.reset(new FileManager(FileSystemOptions()));
25}
26
27FileRemapper::~FileRemapper() {
28  clear();
29}
30
31void FileRemapper::clear(StringRef outputDir) {
32  for (MappingsTy::iterator
33         I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I)
34    resetTarget(I->second);
35  FromToMappings.clear();
36  assert(ToFromMappings.empty());
37  if (!outputDir.empty()) {
38    std::string infoFile = getRemapInfoFile(outputDir);
39    bool existed;
40    llvm::sys::fs::remove(infoFile, existed);
41  }
42}
43
44std::string FileRemapper::getRemapInfoFile(StringRef outputDir) {
45  assert(!outputDir.empty());
46  llvm::sys::Path dir(outputDir);
47  llvm::sys::Path infoFile = dir;
48  infoFile.appendComponent("remap");
49  return infoFile.str();
50}
51
52bool FileRemapper::initFromDisk(StringRef outputDir, DiagnosticsEngine &Diag,
53                                bool ignoreIfFilesChanged) {
54  std::string infoFile = getRemapInfoFile(outputDir);
55  return initFromFile(infoFile, Diag, ignoreIfFilesChanged);
56}
57
58bool FileRemapper::initFromFile(StringRef filePath, DiagnosticsEngine &Diag,
59                                bool ignoreIfFilesChanged) {
60  assert(FromToMappings.empty() &&
61         "initFromDisk should be called before any remap calls");
62  std::string infoFile = filePath;
63  bool fileExists = false;
64  llvm::sys::fs::exists(infoFile, fileExists);
65  if (!fileExists)
66    return false;
67
68  std::vector<std::pair<const FileEntry *, const FileEntry *> > pairs;
69
70  OwningPtr<llvm::MemoryBuffer> fileBuf;
71  if (llvm::MemoryBuffer::getFile(infoFile.c_str(), fileBuf))
72    return report("Error opening file: " + infoFile, Diag);
73
74  SmallVector<StringRef, 64> lines;
75  fileBuf->getBuffer().split(lines, "\n");
76
77  for (unsigned idx = 0; idx+3 <= lines.size(); idx += 3) {
78    StringRef fromFilename = lines[idx];
79    unsigned long long timeModified;
80    if (lines[idx+1].getAsInteger(10, timeModified))
81      return report("Invalid file data: '" + lines[idx+1] + "' not a number",
82                    Diag);
83    StringRef toFilename = lines[idx+2];
84
85    const FileEntry *origFE = FileMgr->getFile(fromFilename);
86    if (!origFE) {
87      if (ignoreIfFilesChanged)
88        continue;
89      return report("File does not exist: " + fromFilename, Diag);
90    }
91    const FileEntry *newFE = FileMgr->getFile(toFilename);
92    if (!newFE) {
93      if (ignoreIfFilesChanged)
94        continue;
95      return report("File does not exist: " + toFilename, Diag);
96    }
97
98    if ((uint64_t)origFE->getModificationTime() != timeModified) {
99      if (ignoreIfFilesChanged)
100        continue;
101      return report("File was modified: " + fromFilename, Diag);
102    }
103
104    pairs.push_back(std::make_pair(origFE, newFE));
105  }
106
107  for (unsigned i = 0, e = pairs.size(); i != e; ++i)
108    remap(pairs[i].first, pairs[i].second);
109
110  return false;
111}
112
113bool FileRemapper::flushToDisk(StringRef outputDir, DiagnosticsEngine &Diag) {
114  using namespace llvm::sys;
115
116  bool existed;
117  if (fs::create_directory(outputDir, existed) != llvm::errc::success)
118    return report("Could not create directory: " + outputDir, Diag);
119
120  std::string infoFile = getRemapInfoFile(outputDir);
121  return flushToFile(infoFile, Diag);
122}
123
124bool FileRemapper::flushToFile(StringRef outputPath, DiagnosticsEngine &Diag) {
125  using namespace llvm::sys;
126
127  std::string errMsg;
128  std::string infoFile = outputPath;
129  llvm::raw_fd_ostream infoOut(infoFile.c_str(), errMsg,
130                               llvm::raw_fd_ostream::F_Binary);
131  if (!errMsg.empty())
132    return report(errMsg, Diag);
133
134  for (MappingsTy::iterator
135         I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) {
136
137    const FileEntry *origFE = I->first;
138    SmallString<200> origPath = StringRef(origFE->getName());
139    fs::make_absolute(origPath);
140    infoOut << origPath << '\n';
141    infoOut << (uint64_t)origFE->getModificationTime() << '\n';
142
143    if (const FileEntry *FE = I->second.dyn_cast<const FileEntry *>()) {
144      SmallString<200> newPath = StringRef(FE->getName());
145      fs::make_absolute(newPath);
146      infoOut << newPath << '\n';
147    } else {
148
149      SmallString<64> tempPath;
150      tempPath = path::filename(origFE->getName());
151      tempPath += "-%%%%%%%%";
152      tempPath += path::extension(origFE->getName());
153      int fd;
154      if (fs::unique_file(tempPath.str(), fd, tempPath) != llvm::errc::success)
155        return report("Could not create file: " + tempPath.str(), Diag);
156
157      llvm::raw_fd_ostream newOut(fd, /*shouldClose=*/true);
158      llvm::MemoryBuffer *mem = I->second.get<llvm::MemoryBuffer *>();
159      newOut.write(mem->getBufferStart(), mem->getBufferSize());
160      newOut.close();
161
162      const FileEntry *newE = FileMgr->getFile(tempPath);
163      remap(origFE, newE);
164      infoOut << newE->getName() << '\n';
165    }
166  }
167
168  infoOut.close();
169  return false;
170}
171
172bool FileRemapper::overwriteOriginal(DiagnosticsEngine &Diag,
173                                     StringRef outputDir) {
174  using namespace llvm::sys;
175
176  for (MappingsTy::iterator
177         I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) {
178    const FileEntry *origFE = I->first;
179    if (const FileEntry *newFE = I->second.dyn_cast<const FileEntry *>()) {
180      if (fs::copy_file(newFE->getName(), origFE->getName(),
181                 fs::copy_option::overwrite_if_exists) != llvm::errc::success)
182        return report(StringRef("Could not copy file '") + newFE->getName() +
183                      "' to file '" + origFE->getName() + "'", Diag);
184    } else {
185
186      bool fileExists = false;
187      fs::exists(origFE->getName(), fileExists);
188      if (!fileExists)
189        return report(StringRef("File does not exist: ") + origFE->getName(),
190                      Diag);
191
192      std::string errMsg;
193      llvm::raw_fd_ostream Out(origFE->getName(), errMsg,
194                               llvm::raw_fd_ostream::F_Binary);
195      if (!errMsg.empty())
196        return report(errMsg, Diag);
197
198      llvm::MemoryBuffer *mem = I->second.get<llvm::MemoryBuffer *>();
199      Out.write(mem->getBufferStart(), mem->getBufferSize());
200      Out.close();
201    }
202  }
203
204  clear(outputDir);
205  return false;
206}
207
208void FileRemapper::applyMappings(PreprocessorOptions &PPOpts) const {
209  for (MappingsTy::const_iterator
210         I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) {
211    if (const FileEntry *FE = I->second.dyn_cast<const FileEntry *>()) {
212      PPOpts.addRemappedFile(I->first->getName(), FE->getName());
213    } else {
214      llvm::MemoryBuffer *mem = I->second.get<llvm::MemoryBuffer *>();
215      PPOpts.addRemappedFile(I->first->getName(), mem);
216    }
217  }
218
219  PPOpts.RetainRemappedFileBuffers = true;
220}
221
222void FileRemapper::transferMappingsAndClear(PreprocessorOptions &PPOpts) {
223  for (MappingsTy::iterator
224         I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) {
225    if (const FileEntry *FE = I->second.dyn_cast<const FileEntry *>()) {
226      PPOpts.addRemappedFile(I->first->getName(), FE->getName());
227    } else {
228      llvm::MemoryBuffer *mem = I->second.get<llvm::MemoryBuffer *>();
229      PPOpts.addRemappedFile(I->first->getName(), mem);
230    }
231    I->second = Target();
232  }
233
234  PPOpts.RetainRemappedFileBuffers = false;
235  clear();
236}
237
238void FileRemapper::remap(StringRef filePath, llvm::MemoryBuffer *memBuf) {
239  remap(getOriginalFile(filePath), memBuf);
240}
241
242void FileRemapper::remap(StringRef filePath, StringRef newPath) {
243  const FileEntry *file = getOriginalFile(filePath);
244  const FileEntry *newfile = FileMgr->getFile(newPath);
245  remap(file, newfile);
246}
247
248void FileRemapper::remap(const FileEntry *file, llvm::MemoryBuffer *memBuf) {
249  assert(file);
250  Target &targ = FromToMappings[file];
251  resetTarget(targ);
252  targ = memBuf;
253}
254
255void FileRemapper::remap(const FileEntry *file, const FileEntry *newfile) {
256  assert(file && newfile);
257  Target &targ = FromToMappings[file];
258  resetTarget(targ);
259  targ = newfile;
260  ToFromMappings[newfile] = file;
261}
262
263const FileEntry *FileRemapper::getOriginalFile(StringRef filePath) {
264  const FileEntry *file = FileMgr->getFile(filePath);
265  // If we are updating a file that overriden an original file,
266  // actually update the original file.
267  llvm::DenseMap<const FileEntry *, const FileEntry *>::iterator
268    I = ToFromMappings.find(file);
269  if (I != ToFromMappings.end()) {
270    file = I->second;
271    assert(FromToMappings.find(file) != FromToMappings.end() &&
272           "Original file not in mappings!");
273  }
274  return file;
275}
276
277void FileRemapper::resetTarget(Target &targ) {
278  if (!targ)
279    return;
280
281  if (llvm::MemoryBuffer *oldmem = targ.dyn_cast<llvm::MemoryBuffer *>()) {
282    delete oldmem;
283  } else {
284    const FileEntry *toFE = targ.get<const FileEntry *>();
285    ToFromMappings.erase(toFE);
286  }
287}
288
289bool FileRemapper::report(const Twine &err, DiagnosticsEngine &Diag) {
290  SmallString<128> buf;
291  unsigned ID = Diag.getDiagnosticIDs()->getCustomDiagID(DiagnosticIDs::Error,
292                                                         err.toStringRef(buf));
293  Diag.Report(ID);
294  return true;
295}
296