1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qmakevfs.h"
5
6#include "ioutils.h"
7using namespace QMakeInternal;
8
9#include <qdir.h>
10#include <qfile.h>
11#include <qfileinfo.h>
12
13#define fL1S(s) QString::fromLatin1(s)
14
15QT_BEGIN_NAMESPACE
16
17QMakeVfs::QMakeVfs()
18#ifndef PROEVALUATOR_FULL
19 : m_magicMissing(fL1S("missing"))
20 , m_magicExisting(fL1S("existing"))
21#endif
22{
23 ref();
24}
25
26QMakeVfs::~QMakeVfs()
27{
28 deref();
29}
30
31void QMakeVfs::ref()
32{
33#ifdef PROEVALUATOR_THREAD_SAFE
34 QMutexLocker locker(&s_mutex);
35#endif
36 ++s_refCount;
37}
38
39void QMakeVfs::deref()
40{
41#ifdef PROEVALUATOR_THREAD_SAFE
42 QMutexLocker locker(&s_mutex);
43#endif
44 if (!--s_refCount) {
45 s_fileIdCounter = 0;
46 s_fileIdMap.clear();
47 s_idFileMap.clear();
48 }
49}
50
51#ifdef PROPARSER_THREAD_SAFE
52QMutex QMakeVfs::s_mutex;
53#endif
54int QMakeVfs::s_refCount;
55QAtomicInt QMakeVfs::s_fileIdCounter;
56QHash<QString, int> QMakeVfs::s_fileIdMap;
57QHash<int, QString> QMakeVfs::s_idFileMap;
58
59int QMakeVfs::idForFileName(const QString &fn, VfsFlags flags)
60{
61#ifdef PROEVALUATOR_DUAL_VFS
62 {
63# ifdef PROPARSER_THREAD_SAFE
64 QMutexLocker locker(&m_vmutex);
65# endif
66 int idx = (flags & VfsCumulative) ? 1 : 0;
67 if (flags & VfsCreate) {
68 int &id = m_virtualFileIdMap[idx][fn];
69 if (!id) {
70 id = ++s_fileIdCounter;
71 m_virtualIdFileMap[id] = fn;
72 }
73 return id;
74 }
75 int id = m_virtualFileIdMap[idx].value(fn);
76 if (id || (flags & VfsCreatedOnly))
77 return id;
78 }
79#endif
80#ifdef PROPARSER_THREAD_SAFE
81 QMutexLocker locker(&s_mutex);
82#endif
83 if (!(flags & VfsAccessedOnly)) {
84 int &id = s_fileIdMap[fn];
85 if (!id) {
86 id = ++s_fileIdCounter;
87 s_idFileMap[id] = fn;
88 }
89 return id;
90 }
91 return s_fileIdMap.value(key: fn);
92}
93
94QString QMakeVfs::fileNameForId(int id)
95{
96#ifdef PROEVALUATOR_DUAL_VFS
97 {
98# ifdef PROPARSER_THREAD_SAFE
99 QMutexLocker locker(&m_vmutex);
100# endif
101 const QString &fn = m_virtualIdFileMap.value(id);
102 if (!fn.isEmpty())
103 return fn;
104 }
105#endif
106#ifdef PROPARSER_THREAD_SAFE
107 QMutexLocker locker(&s_mutex);
108#endif
109 return s_idFileMap.value(key: id);
110}
111
112bool QMakeVfs::writeFile(int id, QIODevice::OpenMode mode, VfsFlags flags,
113 const QString &contents, QString *errStr)
114{
115#ifndef PROEVALUATOR_FULL
116# ifdef PROEVALUATOR_THREAD_SAFE
117 QMutexLocker locker(&m_mutex);
118# endif
119 QString *cont = &m_files[id];
120 Q_UNUSED(flags);
121 if (mode & QIODevice::Append)
122 *cont += contents;
123 else
124 *cont = contents;
125 Q_UNUSED(errStr);
126 return true;
127#else
128 QFileInfo qfi(fileNameForId(id));
129 if (!QDir::current().mkpath(dirPath: qfi.path())) {
130 *errStr = fL1S("Cannot create parent directory");
131 return false;
132 }
133 QByteArray bytes = contents.toLocal8Bit();
134 QFile cfile(qfi.filePath());
135 if (!(mode & QIODevice::Append) && cfile.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
136 if (cfile.readAll() == bytes) {
137 if (flags & VfsExecutable) {
138 cfile.setPermissions(cfile.permissions()
139 | QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther);
140 } else {
141 cfile.setPermissions(cfile.permissions()
142 & ~(QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther));
143 }
144 return true;
145 }
146 cfile.close();
147 }
148 if (!cfile.open(flags: mode | QIODevice::WriteOnly | QIODevice::Text)) {
149 *errStr = cfile.errorString();
150 return false;
151 }
152 cfile.write(data: bytes);
153 cfile.close();
154 if (cfile.error() != QFile::NoError) {
155 *errStr = cfile.errorString();
156 return false;
157 }
158 if (flags & VfsExecutable)
159 cfile.setPermissions(cfile.permissions()
160 | QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther);
161 return true;
162#endif
163}
164
165QMakeVfs::ReadResult QMakeVfs::readFile(int id, QString *contents, QString *errStr)
166{
167#ifndef PROEVALUATOR_FULL
168# ifdef PROEVALUATOR_THREAD_SAFE
169 QMutexLocker locker(&m_mutex);
170# endif
171 auto it = m_files.constFind(id);
172 if (it != m_files.constEnd()) {
173 if (it->constData() == m_magicMissing.constData()) {
174 *errStr = fL1S("No such file or directory");
175 return ReadNotFound;
176 }
177 if (it->constData() != m_magicExisting.constData()) {
178 *contents = *it;
179 return ReadOk;
180 }
181 }
182#endif
183
184 QFile file(fileNameForId(id));
185 if (!file.open(flags: QIODevice::ReadOnly)) {
186 if (!file.exists()) {
187#ifndef PROEVALUATOR_FULL
188 m_files[id] = m_magicMissing;
189#endif
190 *errStr = fL1S("No such file or directory");
191 return ReadNotFound;
192 }
193 *errStr = file.errorString();
194 return ReadOtherError;
195 }
196#ifndef PROEVALUATOR_FULL
197 m_files[id] = m_magicExisting;
198#endif
199
200 QByteArray bcont = file.readAll();
201 if (bcont.startsWith(bv: "\xef\xbb\xbf")) {
202 // UTF-8 BOM will cause subtle errors
203 *errStr = fL1S("Unexpected UTF-8 BOM");
204 return ReadOtherError;
205 }
206 *contents = QString::fromLocal8Bit(ba: bcont);
207 return ReadOk;
208}
209
210bool QMakeVfs::exists(const QString &fn, VfsFlags flags)
211{
212#ifndef PROEVALUATOR_FULL
213# ifdef PROEVALUATOR_THREAD_SAFE
214 QMutexLocker locker(&m_mutex);
215# endif
216 int id = idForFileName(fn, flags);
217 auto it = m_files.constFind(id);
218 if (it != m_files.constEnd())
219 return it->constData() != m_magicMissing.constData();
220#else
221 Q_UNUSED(flags);
222#endif
223 bool ex = IoUtils::fileType(fileName: fn) == IoUtils::FileIsRegular;
224#ifndef PROEVALUATOR_FULL
225 m_files[id] = ex ? m_magicExisting : m_magicMissing;
226#endif
227 return ex;
228}
229
230#ifndef PROEVALUATOR_FULL
231// This should be called when the sources may have changed (e.g., VCS update).
232void QMakeVfs::invalidateCache()
233{
234# ifdef PROEVALUATOR_THREAD_SAFE
235 QMutexLocker locker(&m_mutex);
236# endif
237 auto it = m_files.begin(), eit = m_files.end();
238 while (it != eit) {
239 if (it->constData() == m_magicMissing.constData()
240 ||it->constData() == m_magicExisting.constData())
241 it = m_files.erase(it);
242 else
243 ++it;
244 }
245}
246
247// This should be called when generated files may have changed (e.g., actual build).
248void QMakeVfs::invalidateContents()
249{
250# ifdef PROEVALUATOR_THREAD_SAFE
251 QMutexLocker locker(&m_mutex);
252# endif
253 m_files.clear();
254}
255#endif
256
257QT_END_NAMESPACE
258

source code of qtbase/qmake/library/qmakevfs.cpp