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
18// Apple's dyld *does* support RTLD_NODELETE and the library remains loaded in
19// memory after the dlclose() call, but their Objective C crashes when running
20// code from unloaded-but-still-loaded plugins.
21# undef RTLD_NODELETE
22#endif
23
24#ifdef Q_OS_ANDROID
25#include <private/qjnihelpers_p.h>
26#include <QtCore/qjnienvironment.h>
27#endif
28
29QT_BEGIN_NAMESPACE
30
31using namespace Qt::StringLiterals;
32
33QStringList QLibraryPrivate::suffixes_sys(const QString &fullVersion)
34{
35 QStringList suffixes;
36#if defined(Q_OS_HPUX)
37 // according to
38 // http://docs.hp.com/en/B2355-90968/linkerdifferencesiapa.htm
39
40 // In PA-RISC (PA-32 and PA-64) shared libraries are suffixed
41 // with .sl. In IPF (32-bit and 64-bit), the shared libraries
42 // are suffixed with .so. For compatibility, the IPF linker
43 // also supports the .sl suffix.
44
45 // But since we don't know if we are built on HPUX or HPUXi,
46 // we support both .sl (and .<version>) and .so suffixes but
47 // .so is preferred.
48# if defined(__ia64)
49 if (!fullVersion.isEmpty()) {
50 suffixes << ".so.%1"_L1.arg(fullVersion);
51 } else {
52 suffixes << ".so"_L1;
53 }
54# endif
55 if (!fullVersion.isEmpty()) {
56 suffixes << ".sl.%1"_L1.arg(fullVersion);
57 suffixes << ".%1"_L1.arg(fullVersion);
58 } else {
59 suffixes << ".sl"_L1;
60 }
61#elif defined(Q_OS_CYGWIN)
62 if (!fullVersion.isEmpty()) {
63 suffixes << "-%1.dll"_L1.arg(fullVersion);
64 } else {
65 suffixes << QStringLiteral(".dll");
66 }
67#elif defined(Q_OS_AIX)
68 suffixes << ".a";
69
70#else
71 if (!fullVersion.isEmpty()) {
72 suffixes << ".so.%1"_L1.arg(args: fullVersion);
73 } else {
74 suffixes << ".so"_L1;
75# ifdef Q_OS_ANDROID
76 suffixes << QStringLiteral(LIBS_SUFFIX);
77# endif
78 }
79#endif
80# ifdef Q_OS_DARWIN
81 if (!fullVersion.isEmpty()) {
82 suffixes << ".%1.bundle"_L1.arg(fullVersion);
83 suffixes << ".%1.dylib"_L1.arg(fullVersion);
84 } else {
85 suffixes << ".bundle"_L1 << ".dylib"_L1;
86 }
87#endif
88 return suffixes;
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 << prefix_sys().toString();
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# ifdef __GLIBC__
184 // prepend "glibc-hwcaps/x86-64-v3/" to each prefix in the list
185 transform(prefixes, [](QString *s) { s->prepend(s: "glibc-hwcaps/x86-64-v3/"_L1); });
186# endif
187 }
188 }
189#endif
190
191 locker.unlock();
192 bool retry = true;
193 Handle hnd = nullptr;
194 for (int prefix = 0; retry && !hnd && prefix < prefixes.size(); prefix++) {
195 for (int suffix = 0; retry && !hnd && suffix < suffixes.size(); suffix++) {
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#endif
217
218 if (!hnd && fileName.startsWith(c: u'/') && QFile::exists(fileName: attempt)) {
219 // We only want to continue if dlopen failed due to that the shared library did not exist.
220 // However, we are only able to apply this check for absolute filenames (since they are
221 // not influenced by the content of LD_LIBRARY_PATH, /etc/ld.so.cache, DT_RPATH etc...)
222 // This is all because dlerror is flawed and cannot tell us the reason why it failed.
223 retry = false;
224 }
225 }
226 }
227
228#ifdef Q_OS_DARWIN
229 if (!hnd) {
230 QByteArray utf8Bundle = fileName.toUtf8();
231 QCFType<CFURLRef> bundleUrl = CFURLCreateFromFileSystemRepresentation(NULL, reinterpret_cast<const UInt8*>(utf8Bundle.data()), utf8Bundle.length(), true);
232 QCFType<CFBundleRef> bundle = CFBundleCreate(NULL, bundleUrl);
233 if (bundle) {
234 QCFType<CFURLRef> url = CFBundleCopyExecutableURL(bundle);
235 char executableFile[FILENAME_MAX];
236 CFURLGetFileSystemRepresentation(url, true, reinterpret_cast<UInt8*>(executableFile), FILENAME_MAX);
237 attempt = QString::fromUtf8(executableFile);
238 hnd = dlopen(QFile::encodeName(attempt), dlFlags);
239 }
240 }
241#endif
242
243 locker.relock();
244 if (!hnd) {
245 errorString = QLibrary::tr(s: "Cannot load library %1: %2")
246 .arg(args: fileName, args: QString::fromLocal8Bit(ba: dlerror()));
247 }
248 if (hnd) {
249 qualifiedFileName = attempt;
250 errorString.clear();
251 }
252 pHnd.storeRelaxed(newValue: hnd);
253 return (hnd != nullptr);
254}
255
256bool QLibraryPrivate::unload_sys()
257{
258 bool doTryUnload = true;
259#ifndef RTLD_NODELETE
260 if (loadHints() & QLibrary::PreventUnloadHint)
261 doTryUnload = false;
262#endif
263 if (doTryUnload && dlclose(handle: pHnd.loadAcquire())) {
264 const char *error = dlerror();
265#if defined (Q_OS_QNX)
266 // Workaround until fixed in QNX; fixes crash in
267 // QtDeclarative auto test "qqmlenginecleanup" for instance
268 if (!qstrcmp(error, "Shared objects still referenced")) // On QNX that's only "informative"
269 return true;
270#endif
271 errorString = QLibrary::tr(s: "Cannot unload library %1: %2")
272 .arg(args: fileName, args: QString::fromLocal8Bit(ba: error));
273 return false;
274 }
275 errorString.clear();
276 return true;
277}
278
279QFunctionPointer QLibraryPrivate::resolve_sys(const char *symbol)
280{
281 QFunctionPointer address = QFunctionPointer(dlsym(handle: pHnd.loadAcquire(), name: symbol));
282 return address;
283}
284
285QT_END_NAMESPACE
286

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