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

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