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

source code of qtbase/src/corelib/plugin/qlibrary_unix.cpp