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#if QT_CONFIG(textcodec)
39#include <qtextcodec.h>
40#endif
41
42#define fL1S(s) QString::fromLatin1(s)
43
44QT_BEGIN_NAMESPACE
45
46QMakeVfs::QMakeVfs()
47#ifndef PROEVALUATOR_FULL
48 : m_magicMissing(fL1S("missing"))
49 , m_magicExisting(fL1S("existing"))
50#endif
51{
52#if QT_CONFIG(textcodec)
53 m_textCodec = 0;
54#endif
55 ref();
56}
57
58QMakeVfs::~QMakeVfs()
59{
60 deref();
61}
62
63void QMakeVfs::ref()
64{
65#ifdef PROEVALUATOR_THREAD_SAFE
66 QMutexLocker locker(&s_mutex);
67#endif
68 ++s_refCount;
69}
70
71void QMakeVfs::deref()
72{
73#ifdef PROEVALUATOR_THREAD_SAFE
74 QMutexLocker locker(&s_mutex);
75#endif
76 if (!--s_refCount) {
77 s_fileIdCounter = 0;
78 s_fileIdMap.clear();
79 s_idFileMap.clear();
80 }
81}
82
83#ifdef PROPARSER_THREAD_SAFE
84QMutex QMakeVfs::s_mutex;
85#endif
86int QMakeVfs::s_refCount;
87QAtomicInt QMakeVfs::s_fileIdCounter;
88QHash<QString, int> QMakeVfs::s_fileIdMap;
89QHash<int, QString> QMakeVfs::s_idFileMap;
90
91int QMakeVfs::idForFileName(const QString &fn, VfsFlags flags)
92{
93#ifdef PROEVALUATOR_DUAL_VFS
94 {
95# ifdef PROPARSER_THREAD_SAFE
96 QMutexLocker locker(&m_vmutex);
97# endif
98 int idx = (flags & VfsCumulative) ? 1 : 0;
99 if (flags & VfsCreate) {
100 int &id = m_virtualFileIdMap[idx][fn];
101 if (!id) {
102 id = ++s_fileIdCounter;
103 m_virtualIdFileMap[id] = fn;
104 }
105 return id;
106 }
107 int id = m_virtualFileIdMap[idx].value(fn);
108 if (id || (flags & VfsCreatedOnly))
109 return id;
110 }
111#endif
112#ifdef PROPARSER_THREAD_SAFE
113 QMutexLocker locker(&s_mutex);
114#endif
115 if (!(flags & VfsAccessedOnly)) {
116 int &id = s_fileIdMap[fn];
117 if (!id) {
118 id = ++s_fileIdCounter;
119 s_idFileMap[id] = fn;
120 }
121 return id;
122 }
123 return s_fileIdMap.value(fn);
124}
125
126QString QMakeVfs::fileNameForId(int id)
127{
128#ifdef PROEVALUATOR_DUAL_VFS
129 {
130# ifdef PROPARSER_THREAD_SAFE
131 QMutexLocker locker(&m_vmutex);
132# endif
133 const QString &fn = m_virtualIdFileMap.value(id);
134 if (!fn.isEmpty())
135 return fn;
136 }
137#endif
138#ifdef PROPARSER_THREAD_SAFE
139 QMutexLocker locker(&s_mutex);
140#endif
141 return s_idFileMap.value(id);
142}
143
144bool QMakeVfs::writeFile(int id, QIODevice::OpenMode mode, VfsFlags flags,
145 const QString &contents, QString *errStr)
146{
147#ifndef PROEVALUATOR_FULL
148# ifdef PROEVALUATOR_THREAD_SAFE
149 QMutexLocker locker(&m_mutex);
150# endif
151 QString *cont = &m_files[id];
152 Q_UNUSED(flags)
153 if (mode & QIODevice::Append)
154 *cont += contents;
155 else
156 *cont = contents;
157 Q_UNUSED(errStr)
158 return true;
159#else
160 QFileInfo qfi(fileNameForId(id));
161 if (!QDir::current().mkpath(qfi.path())) {
162 *errStr = fL1S("Cannot create parent directory");
163 return false;
164 }
165 QByteArray bytes = contents.toLocal8Bit();
166 QFile cfile(qfi.filePath());
167 if (!(mode & QIODevice::Append) && cfile.open(QIODevice::ReadOnly | QIODevice::Text)) {
168 if (cfile.readAll() == bytes) {
169 if (flags & VfsExecutable) {
170 cfile.setPermissions(cfile.permissions()
171 | QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther);
172 } else {
173 cfile.setPermissions(cfile.permissions()
174 & ~(QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther));
175 }
176 return true;
177 }
178 cfile.close();
179 }
180 if (!cfile.open(mode | QIODevice::WriteOnly | QIODevice::Text)) {
181 *errStr = cfile.errorString();
182 return false;
183 }
184 cfile.write(bytes);
185 cfile.close();
186 if (cfile.error() != QFile::NoError) {
187 *errStr = cfile.errorString();
188 return false;
189 }
190 if (flags & VfsExecutable)
191 cfile.setPermissions(cfile.permissions()
192 | QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther);
193 return true;
194#endif
195}
196
197QMakeVfs::ReadResult QMakeVfs::readFile(int id, QString *contents, QString *errStr)
198{
199#ifndef PROEVALUATOR_FULL
200# ifdef PROEVALUATOR_THREAD_SAFE
201 QMutexLocker locker(&m_mutex);
202# endif
203 auto it = m_files.constFind(id);
204 if (it != m_files.constEnd()) {
205 if (it->constData() == m_magicMissing.constData()) {
206 *errStr = fL1S("No such file or directory");
207 return ReadNotFound;
208 }
209 if (it->constData() != m_magicExisting.constData()) {
210 *contents = *it;
211 return ReadOk;
212 }
213 }
214#endif
215
216 QFile file(fileNameForId(id));
217 if (!file.open(QIODevice::ReadOnly)) {
218 if (!file.exists()) {
219#ifndef PROEVALUATOR_FULL
220 m_files[id] = m_magicMissing;
221#endif
222 *errStr = fL1S("No such file or directory");
223 return ReadNotFound;
224 }
225 *errStr = file.errorString();
226 return ReadOtherError;
227 }
228#ifndef PROEVALUATOR_FULL
229 m_files[id] = m_magicExisting;
230#endif
231
232 QByteArray bcont = file.readAll();
233 if (bcont.startsWith("\xef\xbb\xbf")) {
234 // UTF-8 BOM will cause subtle errors
235 *errStr = fL1S("Unexpected UTF-8 BOM");
236 return ReadOtherError;
237 }
238 *contents =
239#if QT_CONFIG(textcodec)
240 m_textCodec ? m_textCodec->toUnicode(bcont) :
241#endif
242 QString::fromLocal8Bit(bcont);
243 return ReadOk;
244}
245
246bool QMakeVfs::exists(const QString &fn, VfsFlags flags)
247{
248#ifndef PROEVALUATOR_FULL
249# ifdef PROEVALUATOR_THREAD_SAFE
250 QMutexLocker locker(&m_mutex);
251# endif
252 int id = idForFileName(fn, flags);
253 auto it = m_files.constFind(id);
254 if (it != m_files.constEnd())
255 return it->constData() != m_magicMissing.constData();
256#else
257 Q_UNUSED(flags)
258#endif
259 bool ex = IoUtils::fileType(fn) == IoUtils::FileIsRegular;
260#ifndef PROEVALUATOR_FULL
261 m_files[id] = ex ? m_magicExisting : m_magicMissing;
262#endif
263 return ex;
264}
265
266#ifndef PROEVALUATOR_FULL
267// This should be called when the sources may have changed (e.g., VCS update).
268void QMakeVfs::invalidateCache()
269{
270# ifdef PROEVALUATOR_THREAD_SAFE
271 QMutexLocker locker(&m_mutex);
272# endif
273 auto it = m_files.begin(), eit = m_files.end();
274 while (it != eit) {
275 if (it->constData() == m_magicMissing.constData()
276 ||it->constData() == m_magicExisting.constData())
277 it = m_files.erase(it);
278 else
279 ++it;
280 }
281}
282
283// This should be called when generated files may have changed (e.g., actual build).
284void QMakeVfs::invalidateContents()
285{
286# ifdef PROEVALUATOR_THREAD_SAFE
287 QMutexLocker locker(&m_mutex);
288# endif
289 m_files.clear();
290}
291#endif
292
293#if QT_CONFIG(textcodec)
294void QMakeVfs::setTextCodec(const QTextCodec *textCodec)
295{
296 m_textCodec = textCodec;
297}
298#endif
299
300QT_END_NAMESPACE
301