1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Copyright (C) 2020 Intel Corporation
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the QtCore module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU Lesser General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU Lesser
20** General Public License version 3 as published by the Free Software
21** Foundation and appearing in the file LICENSE.LGPL3 included in the
22** packaging of this file. Please review the following information to
23** ensure the GNU Lesser General Public License version 3 requirements
24** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25**
26** GNU General Public License Usage
27** Alternatively, this file may be used under the terms of the GNU
28** General Public License version 2.0 or (at your option) the GNU General
29** Public license version 3 or any later version approved by the KDE Free
30** Qt Foundation. The licenses are as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32** included in the packaging of this file. Please review the following
33** information to ensure the GNU General Public License requirements will
34** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35** https://www.gnu.org/licenses/gpl-3.0.html.
36**
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41#include "qplatformdefs.h"
42
43#include <qfile.h>
44#include "qlibrary_p.h"
45#include <qcoreapplication.h>
46#include <private/qfilesystementry_p.h>
47#include <private/qsimd_p.h>
48
49#include <dlfcn.h>
50
51#ifdef Q_OS_MAC
52# include <private/qcore_mac_p.h>
53#endif
54
55#ifdef Q_OS_ANDROID
56# include <private/qjnihelpers_p.h>
57#endif
58
59QT_BEGIN_NAMESPACE
60
61static QString qdlerror()
62{
63 const char *err = dlerror();
64 return err ? QLatin1Char('(') + QString::fromLocal8Bit(err) + QLatin1Char(')') : QString();
65}
66
67QStringList QLibraryPrivate::suffixes_sys(const QString &fullVersion)
68{
69 QStringList suffixes;
70#if defined(Q_OS_HPUX)
71 // according to
72 // http://docs.hp.com/en/B2355-90968/linkerdifferencesiapa.htm
73
74 // In PA-RISC (PA-32 and PA-64) shared libraries are suffixed
75 // with .sl. In IPF (32-bit and 64-bit), the shared libraries
76 // are suffixed with .so. For compatibility, the IPF linker
77 // also supports the .sl suffix.
78
79 // But since we don't know if we are built on HPUX or HPUXi,
80 // we support both .sl (and .<version>) and .so suffixes but
81 // .so is preferred.
82# if defined(__ia64)
83 if (!fullVersion.isEmpty()) {
84 suffixes << QLatin1String(".so.%1").arg(fullVersion);
85 } else {
86 suffixes << QLatin1String(".so");
87 }
88# endif
89 if (!fullVersion.isEmpty()) {
90 suffixes << QLatin1String(".sl.%1").arg(fullVersion);
91 suffixes << QLatin1String(".%1").arg(fullVersion);
92 } else {
93 suffixes << QLatin1String(".sl");
94 }
95#elif defined(Q_OS_AIX)
96 suffixes << ".a";
97
98#else
99 if (!fullVersion.isEmpty()) {
100 suffixes << QLatin1String(".so.%1").arg(fullVersion);
101 } else {
102 suffixes << QLatin1String(".so");
103# ifdef Q_OS_ANDROID
104 suffixes << QStringLiteral(LIBS_SUFFIX);
105# endif
106 }
107#endif
108# ifdef Q_OS_MAC
109 if (!fullVersion.isEmpty()) {
110 suffixes << QLatin1String(".%1.bundle").arg(fullVersion);
111 suffixes << QLatin1String(".%1.dylib").arg(fullVersion);
112 } else {
113 suffixes << QLatin1String(".bundle") << QLatin1String(".dylib");
114 }
115#endif
116 return suffixes;
117}
118
119QStringList QLibraryPrivate::prefixes_sys()
120{
121 return QStringList() << QLatin1String("lib");
122}
123
124bool QLibraryPrivate::load_sys()
125{
126 QMutexLocker locker(&mutex);
127 QString attempt;
128 QFileSystemEntry fsEntry(fileName);
129
130 QString path = fsEntry.path();
131 QString name = fsEntry.fileName();
132 if (path == QLatin1String(".") && !fileName.startsWith(path))
133 path.clear();
134 else
135 path += QLatin1Char('/');
136
137 QStringList suffixes;
138 QStringList prefixes;
139 if (pluginState != IsAPlugin) {
140 prefixes = prefixes_sys();
141 suffixes = suffixes_sys(fullVersion);
142 }
143 int dlFlags = 0;
144 int loadHints = this->loadHints();
145 if (loadHints & QLibrary::ResolveAllSymbolsHint) {
146 dlFlags |= RTLD_NOW;
147 } else {
148 dlFlags |= RTLD_LAZY;
149 }
150 if (loadHints & QLibrary::ExportExternalSymbolsHint) {
151 dlFlags |= RTLD_GLOBAL;
152 }
153#if !defined(Q_OS_CYGWIN)
154 else {
155 dlFlags |= RTLD_LOCAL;
156 }
157#endif
158#if defined(RTLD_DEEPBIND)
159 if (loadHints & QLibrary::DeepBindHint)
160 dlFlags |= RTLD_DEEPBIND;
161#endif
162
163 // Provide access to RTLD_NODELETE flag on Unix
164 // From GNU documentation on RTLD_NODELETE:
165 // Do not unload the library during dlclose(). Consequently, the
166 // library's specific static variables are not reinitialized if the
167 // library is reloaded with dlopen() at a later time.
168#if defined(RTLD_NODELETE)
169 if (loadHints & QLibrary::PreventUnloadHint) {
170# ifdef Q_OS_ANDROID // RTLD_NODELETE flag is supported by Android 23+
171 if (QtAndroidPrivate::androidSdkVersion() > 22)
172# endif
173 dlFlags |= RTLD_NODELETE;
174 }
175#endif
176
177#if defined(Q_OS_AIX) // Not sure if any other platform actually support this thing.
178 if (loadHints & QLibrary::LoadArchiveMemberHint) {
179 dlFlags |= RTLD_MEMBER;
180 }
181#endif
182
183 // If the filename is an absolute path then we want to try that first as it is most likely
184 // what the callee wants. If we have been given a non-absolute path then lets try the
185 // native library name first to avoid unnecessary calls to dlopen().
186 if (fsEntry.isAbsolute()) {
187 suffixes.prepend(QString());
188 prefixes.prepend(QString());
189 } else {
190 suffixes.append(QString());
191 prefixes.append(QString());
192 }
193
194#if defined(Q_PROCESSOR_X86) && !defined(Q_OS_DARWIN)
195 if (qCpuHasFeature(ArchHaswell)) {
196 auto transform = [](QStringList &list, void (*f)(QString *)) {
197 QStringList tmp;
198 qSwap(tmp, list);
199 list.reserve(tmp.size() * 2);
200 for (const QString &s : qAsConst(tmp)) {
201 QString modifiedPath = s;
202 f(&modifiedPath);
203 list.append(modifiedPath);
204 list.append(s);
205 }
206 };
207 if (pluginState == IsAPlugin) {
208 // add ".avx2" to each suffix in the list
209 transform(suffixes, [](QString *s) { s->append(QLatin1String(".avx2")); });
210 } else {
211 // prepend "haswell/" to each prefix in the list
212 transform(prefixes, [](QString *s) { s->prepend(QLatin1String("haswell/")); });
213 }
214 }
215#endif
216
217 locker.unlock();
218 bool retry = true;
219 Handle hnd = nullptr;
220 for (int prefix = 0; retry && !hnd && prefix < prefixes.size(); prefix++) {
221 for (int suffix = 0; retry && !hnd && suffix < suffixes.size(); suffix++) {
222 if (!prefixes.at(prefix).isEmpty() && name.startsWith(prefixes.at(prefix)))
223 continue;
224 if (path.isEmpty() && prefixes.at(prefix).contains(QLatin1Char('/')))
225 continue;
226 if (!suffixes.at(suffix).isEmpty() && name.endsWith(suffixes.at(suffix)))
227 continue;
228 if (loadHints & QLibrary::LoadArchiveMemberHint) {
229 attempt = name;
230 int lparen = attempt.indexOf(QLatin1Char('('));
231 if (lparen == -1)
232 lparen = attempt.count();
233 attempt = path + prefixes.at(prefix) + attempt.insert(lparen, suffixes.at(suffix));
234 } else {
235 attempt = path + prefixes.at(prefix) + name + suffixes.at(suffix);
236 }
237
238 hnd = dlopen(QFile::encodeName(attempt), dlFlags);
239#ifdef Q_OS_ANDROID
240 if (!hnd) {
241 auto attemptFromBundle = attempt;
242 hnd = dlopen(QFile::encodeName(attemptFromBundle.replace(QLatin1Char('/'), QLatin1Char('_'))), dlFlags);
243 }
244 if (hnd) {
245 using JniOnLoadPtr = jint (*)(JavaVM *vm, void *reserved);
246 JniOnLoadPtr jniOnLoad = reinterpret_cast<JniOnLoadPtr>(dlsym(pHnd, "JNI_OnLoad"));
247 if (jniOnLoad && jniOnLoad(QtAndroidPrivate::javaVM(), nullptr) == JNI_ERR) {
248 dlclose(hnd);
249 pHnd = nullptr;
250 }
251 }
252#endif
253
254 if (!hnd && fileName.startsWith(QLatin1Char('/')) && QFile::exists(attempt)) {
255 // We only want to continue if dlopen failed due to that the shared library did not exist.
256 // However, we are only able to apply this check for absolute filenames (since they are
257 // not influenced by the content of LD_LIBRARY_PATH, /etc/ld.so.cache, DT_RPATH etc...)
258 // This is all because dlerror is flawed and cannot tell us the reason why it failed.
259 retry = false;
260 }
261 }
262 }
263
264#ifdef Q_OS_MAC
265 if (!hnd) {
266 QByteArray utf8Bundle = fileName.toUtf8();
267 QCFType<CFURLRef> bundleUrl = CFURLCreateFromFileSystemRepresentation(NULL, reinterpret_cast<const UInt8*>(utf8Bundle.data()), utf8Bundle.length(), true);
268 QCFType<CFBundleRef> bundle = CFBundleCreate(NULL, bundleUrl);
269 if (bundle) {
270 QCFType<CFURLRef> url = CFBundleCopyExecutableURL(bundle);
271 char executableFile[FILENAME_MAX];
272 CFURLGetFileSystemRepresentation(url, true, reinterpret_cast<UInt8*>(executableFile), FILENAME_MAX);
273 attempt = QString::fromUtf8(executableFile);
274 hnd = dlopen(QFile::encodeName(attempt), dlFlags);
275 }
276 }
277#endif
278
279 locker.relock();
280 if (!hnd) {
281 errorString = QLibrary::tr("Cannot load library %1: %2").arg(fileName, qdlerror());
282 }
283 if (hnd) {
284 qualifiedFileName = attempt;
285 errorString.clear();
286 }
287 pHnd.storeRelaxed(hnd);
288 return (hnd != nullptr);
289}
290
291bool QLibraryPrivate::unload_sys()
292{
293 if (dlclose(pHnd.loadAcquire())) {
294#if defined (Q_OS_QNX) // Workaround until fixed in QNX; fixes crash in
295 char *error = dlerror(); // QtDeclarative auto test "qqmlenginecleanup" for instance
296 if (!qstrcmp(error, "Shared objects still referenced")) // On QNX that's only "informative"
297 return true;
298 errorString = QLibrary::tr("Cannot unload library %1: %2").arg(fileName,
299 QLatin1String(error));
300#else
301 errorString = QLibrary::tr("Cannot unload library %1: %2").arg(fileName, qdlerror());
302#endif
303 return false;
304 }
305 errorString.clear();
306 return true;
307}
308
309#if defined(Q_OS_LINUX)
310Q_CORE_EXPORT QFunctionPointer qt_linux_find_symbol_sys(const char *symbol)
311{
312 return QFunctionPointer(dlsym(RTLD_DEFAULT, symbol));
313}
314#endif
315
316#ifdef Q_OS_MAC
317Q_CORE_EXPORT QFunctionPointer qt_mac_resolve_sys(void *handle, const char *symbol)
318{
319 return QFunctionPointer(dlsym(handle, symbol));
320}
321#endif
322
323QFunctionPointer QLibraryPrivate::resolve_sys(const char *symbol)
324{
325 QFunctionPointer address = QFunctionPointer(dlsym(pHnd.loadAcquire(), symbol));
326 return address;
327}
328
329QT_END_NAMESPACE
330