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    llvm::sys::fs::remove(infoFile);
40  }
41}
42
43std::string FileRemapper::getRemapInfoFile(StringRef outputDir) {
44  assert(!outputDir.empty());
45  SmallString<128> InfoFile = outputDir;
46  llvm::sys::path::append(InfoFile, "remap");
47  return InfoFile.str();
48}
49
50bool FileRemapper::initFromDisk(StringRef outputDir, DiagnosticsEngine &Diag,
51                                bool ignoreIfFilesChanged) {
52  std::string infoFile = getRemapInfoFile(outputDir);
53  return initFromFile(infoFile, Diag, ignoreIfFilesChanged);
54}
55
56bool FileRemapper::initFromFile(StringRef filePath, DiagnosticsEngine &Diag,
57                                bool ignoreIfFilesChanged) {
58  assert(FromToMappings.empty() &&
59         "initFromDisk should be called before any remap calls");
60  std::string infoFile = filePath;
61  bool fileExists = false;
62  llvm::sys::fs::exists(infoFile, fileExists);
63  if (!fileExists)
64    return false;
65
66  std::vector<std::pair<const FileEntry *, const FileEntry *> > pairs;
67
68  llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> fileBuf =
69      llvm::MemoryBuffer::getFile(infoFile.c_str());
70  if (!fileBuf)
71    return report("Error opening file: " + infoFile, Diag);
72
73  SmallVector<StringRef, 64> lines;
74  fileBuf.get()->getBuffer().split(lines, "\n");
75
76  for (unsigned idx = 0; idx+3 <= lines.size(); idx += 3) {
77    StringRef fromFilename = lines[idx];
78    unsigned long long timeModified;
79    if (lines[idx+1].getAsInteger(10, timeModified))
80      return report("Invalid file data: '" + lines[idx+1] + "' not a number",
81                    Diag);
82    StringRef toFilename = lines[idx+2];
83
84    const FileEntry *origFE = FileMgr->getFile(fromFilename);
85    if (!origFE) {
86      if (ignoreIfFilesChanged)
87        continue;
88      return report("File does not exist: " + fromFilename, Diag);
89    }
90    const FileEntry *newFE = FileMgr->getFile(toFilename);
91    if (!newFE) {
92      if (ignoreIfFilesChanged)
93        continue;
94      return report("File does not exist: " + toFilename, Diag);
95    }
96
97    if ((uint64_t)origFE->getModificationTime() != timeModified) {
98      if (ignoreIfFilesChanged)
99        continue;
100      return report("File was modified: " + fromFilename, Diag);
101    }
102
103    pairs.push_back(std::make_pair(origFE, newFE));
104  }
105
106  for (unsigned i = 0, e = pairs.size(); i != e; ++i)
107    remap(pairs[i].first, pairs[i].second);
108
109  return false;
110}
111
112bool FileRemapper::flushToDisk(StringRef outputDir, DiagnosticsEngine &Diag) {
113  using namespace llvm::sys;
114
115  if (fs::create_directory(outputDir))
116    return report("Could not create directory: " + outputDir, Diag);
117
118  std::string infoFile = getRemapInfoFile(outputDir);
119  return flushToFile(infoFile, Diag);
120}
121
122bool FileRemapper::flushToFile(StringRef outputPath, DiagnosticsEngine &Diag) {
123  using namespace llvm::sys;
124
125  std::string errMsg;
126  std::string infoFile = outputPath;
127  llvm::raw_fd_ostream infoOut(infoFile.c_str(), errMsg, llvm::sys::fs::F_None);
128  if (!errMsg.empty())
129    return report(errMsg, Diag);
130
131  for (MappingsTy::iterator
132         I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) {
133
134    const FileEntry *origFE = I->first;
135    SmallString<200> origPath = StringRef(origFE->getName());
136    fs::make_absolute(origPath);
137    infoOut << origPath << '\n';
138    infoOut << (uint64_t)origFE->getModificationTime() << '\n';
139
140    if (const FileEntry *FE = I->second.dyn_cast<const FileEntry *>()) {
141      SmallString<200> newPath = StringRef(FE->getName());
142      fs::make_absolute(newPath);
143      infoOut << newPath << '\n';
144    } else {
145
146      SmallString<64> tempPath;
147      int fd;
148      if (fs::createTemporaryFile(path::filename(origFE->getName()),
149                                  path::extension(origFE->getName()), fd,
150                                  tempPath))
151        return report("Could not create file: " + tempPath.str(), Diag);
152
153      llvm::raw_fd_ostream newOut(fd, /*shouldClose=*/true);
154      llvm::MemoryBuffer *mem = I->second.get<llvm::MemoryBuffer *>();
155      newOut.write(mem->getBufferStart(), mem->getBufferSize());
156      newOut.close();
157
158      const FileEntry *newE = FileMgr->getFile(tempPath);
159      remap(origFE, newE);
160      infoOut << newE->getName() << '\n';
161    }
162  }
163
164  infoOut.close();
165  return false;
166}
167
168bool FileRemapper::overwriteOriginal(DiagnosticsEngine &Diag,
169                                     StringRef outputDir) {
170  using namespace llvm::sys;
171
172  for (MappingsTy::iterator
173         I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) {
174    const FileEntry *origFE = I->first;
175    assert(I->second.is<llvm::MemoryBuffer *>());
176    bool fileExists = false;
177    fs::exists(origFE->getName(), fileExists);
178    if (!fileExists)
179      return report(StringRef("File does not exist: ") + origFE->getName(),
180                    Diag);
181
182    std::string errMsg;
183    llvm::raw_fd_ostream Out(origFE->getName(), errMsg, llvm::sys::fs::F_None);
184    if (!errMsg.empty())
185      return report(errMsg, Diag);
186
187    llvm::MemoryBuffer *mem = I->second.get<llvm::MemoryBuffer *>();
188    Out.write(mem->getBufferStart(), mem->getBufferSize());
189    Out.close();
190  }
191
192  clear(outputDir);
193  return false;
194}
195
196void FileRemapper::applyMappings(PreprocessorOptions &PPOpts) const {
197  for (MappingsTy::const_iterator
198         I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) {
199    if (const FileEntry *FE = I->second.dyn_cast<const FileEntry *>()) {
200      PPOpts.addRemappedFile(I->first->getName(), FE->getName());
201    } else {
202      llvm::MemoryBuffer *mem = I->second.get<llvm::MemoryBuffer *>();
203      PPOpts.addRemappedFile(I->first->getName(), mem);
204    }
205  }
206
207  PPOpts.RetainRemappedFileBuffers = true;
208}
209
210void FileRemapper::remap(StringRef filePath, llvm::MemoryBuffer *memBuf) {
211  remap(getOriginalFile(filePath), memBuf);
212}
213
214void FileRemapper::remap(const FileEntry *file, llvm::MemoryBuffer *memBuf) {
215  assert(file);
216  Target &targ = FromToMappings[file];
217  resetTarget(targ);
218  targ = memBuf;
219}
220
221void FileRemapper::remap(const FileEntry *file, const FileEntry *newfile) {
222  assert(file && newfile);
223  Target &targ = FromToMappings[file];
224  resetTarget(targ);
225  targ = newfile;
226  ToFromMappings[newfile] = file;
227}
228
229const FileEntry *FileRemapper::getOriginalFile(StringRef filePath) {
230  const FileEntry *file = FileMgr->getFile(filePath);
231  // If we are updating a file that overriden an original file,
232  // actually update the original file.
233  llvm::DenseMap<const FileEntry *, const FileEntry *>::iterator
234    I = ToFromMappings.find(file);
235  if (I != ToFromMappings.end()) {
236    file = I->second;
237    assert(FromToMappings.find(file) != FromToMappings.end() &&
238           "Original file not in mappings!");
239  }
240  return file;
241}
242
243void FileRemapper::resetTarget(Target &targ) {
244  if (!targ)
245    return;
246
247  if (llvm::MemoryBuffer *oldmem = targ.dyn_cast<llvm::MemoryBuffer *>()) {
248    delete oldmem;
249  } else {
250    const FileEntry *toFE = targ.get<const FileEntry *>();
251    ToFromMappings.erase(toFE);
252  }
253}
254
255bool FileRemapper::report(const Twine &err, DiagnosticsEngine &Diag) {
256  Diag.Report(Diag.getCustomDiagID(DiagnosticsEngine::Error, "%0"))
257      << err.str();
258  return true;
259}
260