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 "ioutils.h"
30
31#include <qdir.h>
32#include <qfile.h>
33#include <qregularexpression.h>
34
35#ifdef Q_OS_WIN
36# include <windows.h>
37#else
38# include <sys/types.h>
39# include <sys/stat.h>
40# include <unistd.h>
41# include <utime.h>
42# include <fcntl.h>
43# include <errno.h>
44#endif
45
46#define fL1S(s) QString::fromLatin1(s)
47
48QT_BEGIN_NAMESPACE
49
50using namespace QMakeInternal;
51
52IoUtils::FileType IoUtils::fileType(const QString &fileName)
53{
54 Q_ASSERT(fileName.isEmpty() || isAbsolutePath(fileName));
55#ifdef Q_OS_WIN
56 DWORD attr = GetFileAttributesW((WCHAR*)fileName.utf16());
57 if (attr == INVALID_FILE_ATTRIBUTES)
58 return FileNotFound;
59 return (attr & FILE_ATTRIBUTE_DIRECTORY) ? FileIsDir : FileIsRegular;
60#else
61 struct ::stat st;
62 if (::stat(fileName.toLocal8Bit().constData(), &st))
63 return FileNotFound;
64 return S_ISDIR(st.st_mode) ? FileIsDir : S_ISREG(st.st_mode) ? FileIsRegular : FileNotFound;
65#endif
66}
67
68bool IoUtils::isRelativePath(const QString &path)
69{
70#ifdef QMAKE_BUILTIN_PRFS
71 if (path.startsWith(QLatin1String(":/")))
72 return false;
73#endif
74#ifdef Q_OS_WIN
75 // Unlike QFileInfo, this considers only paths with both a drive prefix and
76 // a subsequent (back-)slash absolute:
77 if (path.length() >= 3 && path.at(1) == QLatin1Char(':') && path.at(0).isLetter()
78 && (path.at(2) == QLatin1Char('/') || path.at(2) == QLatin1Char('\\'))) {
79 return false;
80 }
81 // ... unless, of course, they're UNC:
82 if (path.length() >= 2
83 && (path.at(0).unicode() == '\\' || path.at(0).unicode() == '/')
84 && path.at(1) == path.at(0)) {
85 return false;
86 }
87#else
88 if (path.startsWith(QLatin1Char('/')))
89 return false;
90#endif // Q_OS_WIN
91 return true;
92}
93
94QStringView IoUtils::pathName(const QString &fileName)
95{
96 return QStringView{fileName}.left(fileName.lastIndexOf(QLatin1Char('/')) + 1);
97}
98
99QStringView IoUtils::fileName(const QString &fileName)
100{
101 return QStringView(fileName).mid(fileName.lastIndexOf(QLatin1Char('/')) + 1);
102}
103
104QString IoUtils::resolvePath(const QString &baseDir, const QString &fileName)
105{
106 if (fileName.isEmpty())
107 return QString();
108 if (isAbsolutePath(fileName))
109 return QDir::cleanPath(fileName);
110#ifdef Q_OS_WIN // Add drive to otherwise-absolute path:
111 if (fileName.at(0).unicode() == '/' || fileName.at(0).unicode() == '\\') {
112 Q_ASSERT_X(isAbsolutePath(baseDir), "IoUtils::resolvePath", qUtf8Printable(baseDir));
113 return QDir::cleanPath(baseDir.left(2) + fileName);
114 }
115#endif // Q_OS_WIN
116 return QDir::cleanPath(baseDir + QLatin1Char('/') + fileName);
117}
118
119inline static
120bool isSpecialChar(ushort c, const uchar (&iqm)[16])
121{
122 if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))))
123 return true;
124 return false;
125}
126
127inline static
128bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16])
129{
130 for (int x = arg.length() - 1; x >= 0; --x) {
131 if (isSpecialChar(arg.unicode()[x].unicode(), iqm))
132 return true;
133 }
134 return false;
135}
136
137QString IoUtils::shellQuoteUnix(const QString &arg)
138{
139 // Chars that should be quoted (TM). This includes:
140 static const uchar iqm[] = {
141 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8,
142 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78
143 }; // 0-32 \'"$`<>|;&(){}*?#!~[]
144
145 if (!arg.length())
146 return QString::fromLatin1("''");
147
148 QString ret(arg);
149 if (hasSpecialChars(ret, iqm)) {
150 ret.replace(QLatin1Char('\''), QLatin1String("'\\''"));
151 ret.prepend(QLatin1Char('\''));
152 ret.append(QLatin1Char('\''));
153 }
154 return ret;
155}
156
157QString IoUtils::shellQuoteWin(const QString &arg)
158{
159 // Chars that should be quoted (TM). This includes:
160 // - control chars & space
161 // - the shell meta chars "&()<>^|
162 // - the potential separators ,;=
163 static const uchar iqm[] = {
164 0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78,
165 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10
166 };
167 // Shell meta chars that need escaping.
168 static const uchar ism[] = {
169 0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, 0x50,
170 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10
171 }; // &()<>^|
172
173 if (!arg.length())
174 return QString::fromLatin1("\"\"");
175
176 QString ret(arg);
177 if (hasSpecialChars(ret, iqm)) {
178 // The process-level standard quoting allows escaping quotes with backslashes (note
179 // that backslashes don't escape themselves, unless they are followed by a quote).
180 // Consequently, quotes are escaped and their preceding backslashes are doubled.
181 ret.replace(QRegularExpression(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\\""));
182 // Trailing backslashes must be doubled as well, as they are followed by a quote.
183 ret.replace(QRegularExpression(QLatin1String("(\\\\+)$")), QLatin1String("\\1\\1"));
184 // However, the shell also interprets the command, and no backslash-escaping exists
185 // there - a quote always toggles the quoting state, but is nonetheless passed down
186 // to the called process verbatim. In the unquoted state, the circumflex escapes
187 // meta chars (including itself and quotes), and is removed from the command.
188 bool quoted = true;
189 for (int i = 0; i < ret.length(); i++) {
190 QChar c = ret.unicode()[i];
191 if (c.unicode() == '"')
192 quoted = !quoted;
193 else if (!quoted && isSpecialChar(c.unicode(), ism))
194 ret.insert(i++, QLatin1Char('^'));
195 }
196 if (!quoted)
197 ret.append(QLatin1Char('^'));
198 ret.append(QLatin1Char('"'));
199 ret.prepend(QLatin1Char('"'));
200 }
201 return ret;
202}
203
204#if defined(PROEVALUATOR_FULL)
205
206# if defined(Q_OS_WIN)
207static QString windowsErrorCode()
208{
209 wchar_t *string = nullptr;
210 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
211 NULL,
212 GetLastError(),
213 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
214 (LPWSTR)&string,
215 0,
216 NULL);
217 QString ret = QString::fromWCharArray(string);
218 LocalFree((HLOCAL)string);
219 return ret.trimmed();
220}
221# endif
222
223bool IoUtils::touchFile(const QString &targetFileName, const QString &referenceFileName, QString *errorString)
224{
225# ifdef Q_OS_UNIX
226 struct stat st;
227 if (stat(referenceFileName.toLocal8Bit().constData(), &st)) {
228 *errorString = fL1S("Cannot stat() reference file %1: %2.").arg(referenceFileName, fL1S(strerror(errno)));
229 return false;
230 }
231# if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200809L
232 const struct timespec times[2] = { { 0, UTIME_NOW }, st.st_mtim };
233 const bool utimeError = utimensat(AT_FDCWD, targetFileName.toLocal8Bit().constData(), times, 0) < 0;
234# else
235 struct utimbuf utb;
236 utb.actime = time(0);
237 utb.modtime = st.st_mtime;
238 const bool utimeError= utime(targetFileName.toLocal8Bit().constData(), &utb) < 0;
239# endif
240 if (utimeError) {
241 *errorString = fL1S("Cannot touch %1: %2.").arg(targetFileName, fL1S(strerror(errno)));
242 return false;
243 }
244# else
245 HANDLE rHand = CreateFile((wchar_t*)referenceFileName.utf16(),
246 GENERIC_READ, FILE_SHARE_READ,
247 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
248 if (rHand == INVALID_HANDLE_VALUE) {
249 *errorString = fL1S("Cannot open reference file %1: %2").arg(referenceFileName, windowsErrorCode());
250 return false;
251 }
252 FILETIME ft;
253 GetFileTime(rHand, NULL, NULL, &ft);
254 CloseHandle(rHand);
255 HANDLE wHand = CreateFile((wchar_t*)targetFileName.utf16(),
256 GENERIC_WRITE, FILE_SHARE_READ,
257 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
258 if (wHand == INVALID_HANDLE_VALUE) {
259 *errorString = fL1S("Cannot open %1: %2").arg(targetFileName, windowsErrorCode());
260 return false;
261 }
262 SetFileTime(wHand, NULL, NULL, &ft);
263 CloseHandle(wHand);
264# endif
265 return true;
266}
267
268#if defined(QT_BUILD_QMAKE) && defined(Q_OS_UNIX)
269bool IoUtils::readLinkTarget(const QString &symlinkPath, QString *target)
270{
271 const QByteArray localSymlinkPath = QFile::encodeName(symlinkPath);
272# if defined(__GLIBC__) && !defined(PATH_MAX)
273# define PATH_CHUNK_SIZE 256
274 char *s = 0;
275 int len = -1;
276 int size = PATH_CHUNK_SIZE;
277
278 forever {
279 s = (char *)::realloc(s, size);
280 len = ::readlink(localSymlinkPath.constData(), s, size);
281 if (len < 0) {
282 ::free(s);
283 break;
284 }
285 if (len < size)
286 break;
287 size *= 2;
288 }
289# else
290 char s[PATH_MAX+1];
291 int len = readlink(localSymlinkPath.constData(), s, PATH_MAX);
292# endif
293 if (len <= 0)
294 return false;
295 *target = QFile::decodeName(QByteArray(s, len));
296# if defined(__GLIBC__) && !defined(PATH_MAX)
297 ::free(s);
298# endif
299 return true;
300}
301#endif
302
303#endif // PROEVALUATOR_FULL
304
305QT_END_NAMESPACE
306