1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the qmake application of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "qmakevfs.h"
30
31#include "ioutils.h"
32using namespace QMakeInternal;
33
34#include <qdir.h>
35#include <qfile.h>
36#include <qfileinfo.h>
37
38#define fL1S(s) QString::fromLatin1(s)
39
40QT_BEGIN_NAMESPACE
41
42QMakeVfs::QMakeVfs()
43#ifndef PROEVALUATOR_FULL
44 : m_magicMissing(fL1S("missing"))
45 , m_magicExisting(fL1S("existing"))
46#endif
47{
48 ref();
49}
50
51QMakeVfs::~QMakeVfs()
52{
53 deref();
54}
55
56void QMakeVfs::ref()
57{
58#ifdef PROEVALUATOR_THREAD_SAFE
59 QMutexLocker locker(&s_mutex);
60#endif
61 ++s_refCount;
62}
63
64void QMakeVfs::deref()
65{
66#ifdef PROEVALUATOR_THREAD_SAFE
67 QMutexLocker locker(&s_mutex);
68#endif
69 if (!--s_refCount) {
70 s_fileIdCounter = 0;
71 s_fileIdMap.clear();
72 s_idFileMap.clear();
73 }
74}
75
76#ifdef PROPARSER_THREAD_SAFE
77QMutex QMakeVfs::s_mutex;
78#endif
79int QMakeVfs::s_refCount;
80QAtomicInt QMakeVfs::s_fileIdCounter;
81QHash<QString, int> QMakeVfs::s_fileIdMap;
82QHash<int, QString> QMakeVfs::s_idFileMap;
83
84int QMakeVfs::idForFileName(const QString &fn, VfsFlags flags)
85{
86#ifdef PROEVALUATOR_DUAL_VFS
87 {
88# ifdef PROPARSER_THREAD_SAFE
89 QMutexLocker locker(&m_vmutex);
90# endif
91 int idx = (flags & VfsCumulative) ? 1 : 0;
92 if (flags & VfsCreate) {
93 int &id = m_virtualFileIdMap[idx][fn];
94 if (!id) {
95 id = ++s_fileIdCounter;
96 m_virtualIdFileMap[id] = fn;
97 }
98 return id;
99 }
100 int id = m_virtualFileIdMap[idx].value(fn);
101 if (id || (flags & VfsCreatedOnly))
102 return id;
103 }
104#endif
105#ifdef PROPARSER_THREAD_SAFE
106 QMutexLocker locker(&s_mutex);
107#endif
108 if (!(flags & VfsAccessedOnly)) {
109 int &id = s_fileIdMap[fn];
110 if (!id) {
111 id = ++s_fileIdCounter;
112 s_idFileMap[id] = fn;
113 }
114 return id;
115 }
116 return s_fileIdMap.value(fn);
117}
118
119QString QMakeVfs::fileNameForId(int id)
120{
121#ifdef PROEVALUATOR_DUAL_VFS
122 {
123# ifdef PROPARSER_THREAD_SAFE
124 QMutexLocker locker(&m_vmutex);
125# endif
126 const QString &fn = m_virtualIdFileMap.value(id);
127 if (!fn.isEmpty())
128 return fn;
129 }
130#endif
131#ifdef PROPARSER_THREAD_SAFE
132 QMutexLocker locker(&s_mutex);
133#endif
134 return s_idFileMap.value(id);
135}
136
137bool QMakeVfs::writeFile(int id, QIODevice::OpenMode mode, VfsFlags flags,
138 const QString &contents, QString *errStr)
139{
140#ifndef PROEVALUATOR_FULL
141# ifdef PROEVALUATOR_THREAD_SAFE
142 QMutexLocker locker(&m_mutex);
143# endif
144 QString *cont = &m_files[id];
145 Q_UNUSED(flags);
146 if (mode & QIODevice::Append)
147 *cont += contents;
148 else
149 *cont = contents;
150 Q_UNUSED(errStr);
151 return true;
152#else
153 QFileInfo qfi(fileNameForId(id));
154 if (!QDir::current().mkpath(qfi.path())) {
155 *errStr = fL1S("Cannot create parent directory");
156 return false;
157 }
158 QByteArray bytes = contents.toLocal8Bit();
159 QFile cfile(qfi.filePath());
160 if (!(mode & QIODevice::Append) && cfile.open(QIODevice::ReadOnly | QIODevice::Text)) {
161 if (cfile.readAll() == bytes) {
162 if (flags & VfsExecutable) {
163 cfile.setPermissions(cfile.permissions()
164 | QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther);
165 } else {
166 cfile.setPermissions(cfile.permissions()
167 & ~(QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther));
168 }
169 return true;
170 }
171 cfile.close();
172 }
173 if (!cfile.open(mode | QIODevice::WriteOnly | QIODevice::Text)) {
174 *errStr = cfile.errorString();
175 return false;
176 }
177 cfile.write(bytes);
178 cfile.close();
179 if (cfile.error() != QFile::NoError) {
180 *errStr = cfile.errorString();
181 return false;
182 }
183 if (flags & VfsExecutable)
184 cfile.setPermissions(cfile.permissions()
185 | QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther);
186 return true;
187#endif
188}
189
190QMakeVfs::ReadResult QMakeVfs::readFile(int id, QString *contents, QString *errStr)
191{
192#ifndef PROEVALUATOR_FULL
193# ifdef PROEVALUATOR_THREAD_SAFE
194 QMutexLocker locker(&m_mutex);
195# endif
196 auto it = m_files.constFind(id);
197 if (it != m_files.constEnd()) {
198 if (it->constData() == m_magicMissing.constData()) {
199 *errStr = fL1S("No such file or directory");
200 return ReadNotFound;
201 }
202 if (it->constData() != m_magicExisting.constData()) {
203 *contents = *it;
204 return ReadOk;
205 }
206 }
207#endif
208
209 QFile file(fileNameForId(id));
210 if (!file.open(QIODevice::ReadOnly)) {
211 if (!file.exists()) {
212#ifndef PROEVALUATOR_FULL
213 m_files[id] = m_magicMissing;
214#endif
215 *errStr = fL1S("No such file or directory");
216 return ReadNotFound;
217 }
218 *errStr = file.errorString();
219 return ReadOtherError;
220 }
221#ifndef PROEVALUATOR_FULL
222 m_files[id] = m_magicExisting;
223#endif
224
225 QByteArray bcont = file.readAll();
226 if (bcont.startsWith("\xef\xbb\xbf")) {
227 // UTF-8 BOM will cause subtle errors
228 *errStr = fL1S("Unexpected UTF-8 BOM");
229 return ReadOtherError;
230 }
231 *contents = QString::fromLocal8Bit(bcont);
232 return ReadOk;
233}
234
235bool QMakeVfs::exists(const QString &fn, VfsFlags flags)
236{
237#ifndef PROEVALUATOR_FULL
238# ifdef PROEVALUATOR_THREAD_SAFE
239 QMutexLocker locker(&m_mutex);
240# endif
241 int id = idForFileName(fn, flags);
242 auto it = m_files.constFind(id);
243 if (it != m_files.constEnd())
244 return it->constData() != m_magicMissing.constData();
245#else
246 Q_UNUSED(flags);
247#endif
248 bool ex = IoUtils::fileType(fn) == IoUtils::FileIsRegular;
249#ifndef PROEVALUATOR_FULL
250 m_files[id] = ex ? m_magicExisting : m_magicMissing;
251#endif
252 return ex;
253}
254
255#ifndef PROEVALUATOR_FULL
256// This should be called when the sources may have changed (e.g., VCS update).
257void QMakeVfs::invalidateCache()
258{
259# ifdef PROEVALUATOR_THREAD_SAFE
260 QMutexLocker locker(&m_mutex);
261# endif
262 auto it = m_files.begin(), eit = m_files.end();
263 while (it != eit) {
264 if (it->constData() == m_magicMissing.constData()
265 ||it->constData() == m_magicExisting.constData())
266 it = m_files.erase(it);
267 else
268 ++it;
269 }
270}
271
272// This should be called when generated files may have changed (e.g., actual build).
273void QMakeVfs::invalidateContents()
274{
275# ifdef PROEVALUATOR_THREAD_SAFE
276 QMutexLocker locker(&m_mutex);
277# endif
278 m_files.clear();
279}
280#endif
281
282QT_END_NAMESPACE
283