
#ifdef Q_WS_WIN
# include "QtCore/qt_windows.h"
#endif
#include "QtCore/qlibrary.h"
#include "QtCore/qpointer.h"
#include "QtCore/qstringlist.h"
#include "QtCore/qplugin.h"


#include "qplatformdefs.h"

#include "qplugin.h"
#include "qpluginloader.h"
#include <qfileinfo.h>
#include "qdebug.h"

#include "qplatformdefs.h"
#include "qlibrary.h"

#include <qstringlist.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qmutex.h>
#include <qmap.h>
#include <qsettings.h>
#include <qdatetime.h>
#include <qcoreapplication.h>
#include <qvector.h>
#include <qdir.h>


#ifdef Q_OS_MAC
#  include <private/qcore_mac_p.h>
#endif
#ifndef NO_ERRNO_H
#include <errno.h>
#endif // NO_ERROR_H

#include "qplatformdefs.h"

#ifdef Q_OS_MAC
#  include <private/qcore_mac_p.h>
#endif

#if defined(QT_AOUT_UNDERSCORE)
#include <string.h>
#endif


#if !defined(QT_HPUX_LD)
#include <dlfcn.h>
#endif

QT_BEGIN_NAMESPACE

bool qt_debug_component();

class PatchedLibraryPrivate
{
public:

#ifdef Q_WS_WIN
    HINSTANCE
#else
    void *
#endif
    pHnd;

    QString fileName, qualifiedFileName;
    QString fullVersion;

    bool load();
    bool loadPlugin(); // loads and resolves instance
    bool unload();
    void release();
    void *resolve(const char *);

    static PatchedLibraryPrivate *findOrCreate(const QString &fileName, const QString &version = QString());

    QtPluginInstanceFunction instance;
    uint qt_version;
    QString lastModified;

    QString errorString;
    QLibrary::LoadHints loadHints;

    bool isPlugin(QSettings *settings = 0);


private:
    explicit PatchedLibraryPrivate(const QString &canonicalFileName, const QString &version);
    ~PatchedLibraryPrivate();

    bool load_sys();
    bool unload_sys();
    void *resolve_sys(const char *);

    QAtomicInt libraryRefCount;
    QAtomicInt libraryUnloadCount;

    enum {IsAPlugin, IsNotAPlugin, MightBeAPlugin } pluginState;
    friend class PatchedLibraryPrivateHasFriends;
};

class PatchedPluginLoader
{
    Q_PROPERTY(QString fileName READ fileName WRITE setFileName)
    Q_PROPERTY(QLibrary::LoadHints loadHints READ loadHints WRITE setLoadHints)
public:
    explicit PatchedPluginLoader(QObject *parent = 0);
    explicit PatchedPluginLoader(const QString &fileName, QObject *parent = 0);
    ~PatchedPluginLoader();

    QObject *instance();

    bool load();
    bool unload();
    bool isLoaded() const;

    void setFileName(const QString &fileName);
    QString fileName() const;

    QString errorString() const;

    void setLoadHints(QLibrary::LoadHints loadHints);
    QLibrary::LoadHints loadHints() const;

private:
    PatchedLibraryPrivate *d;
    bool did_load;
    Q_DISABLE_COPY(PatchedPluginLoader)
};

//#define QT_DEBUG_COMPONENT

#ifdef QT_NO_DEBUG
#  define QLIBRARY_AS_DEBUG false
#else
#  define QLIBRARY_AS_DEBUG true
#endif

#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
// We don't use separate debug and release libs on UNIX, so we want
// to allow loading plugins, regardless of how they were built.
#  define QT_NO_DEBUG_PLUGIN_CHECK
#endif

Q_GLOBAL_STATIC(QMutex, patched_qt_library_mutex)


#ifndef QT_NO_PLUGIN_CHECK
struct qt_token_info
{
    qt_token_info(const char *f, const ulong fc)
        : fields(f), field_count(fc), results(fc), lengths(fc)
    {
        results.fill(0);
        lengths.fill(0);
    }

    const char *fields;
    const ulong field_count;

    QVector<const char *> results;
    QVector<ulong> lengths;
};

/*
  return values:
       1 parse ok
       0 eos
      -1 parse error
*/
static int qt_tokenize(const char *s, ulong s_len, ulong *advance,
                        qt_token_info &token_info)
{
    ulong pos = 0, field = 0, fieldlen = 0;
    char current;
    int ret = -1;
    *advance = 0;
    for (;;) {
        current = s[pos];

        // next char
        ++pos;
        ++fieldlen;
        ++*advance;

        if (! current || pos == s_len + 1) {
            // save result
            token_info.results[(int)field] = s;
            token_info.lengths[(int)field] = fieldlen - 1;

            // end of string
            ret = 0;
            break;
        }

        if (current == token_info.fields[field]) {
            // save result
            token_info.results[(int)field] = s;
            token_info.lengths[(int)field] = fieldlen - 1;

            // end of field
            fieldlen = 0;
            ++field;
            if (field == token_info.field_count - 1) {
                // parse ok
                ret = 1;
            }
            if (field == token_info.field_count) {
                // done parsing
                break;
            }

            // reset string and its length
            s = s + pos;
            s_len -= pos;
            pos = 0;
        }
    }

    return ret;
}

/*
  returns true if the string s was correctly parsed, false otherwise.
*/
static bool qt_parse_pattern(const char *s, uint *version, bool *debug, QByteArray *key)
{
    bool ret = true;

    qt_token_info pinfo("=\n", 2);
    int parse;
    ulong at = 0, advance, parselen = qstrlen(s);
    do {
        parse = qt_tokenize(s + at, parselen, &advance, pinfo);
        if (parse == -1) {
            ret = false;
            break;
        }

        at += advance;
        parselen -= advance;

        if (qstrncmp("version", pinfo.results[0], pinfo.lengths[0]) == 0) {
            // parse version string
            qt_token_info pinfo2("..-", 3);
            if (qt_tokenize(pinfo.results[1], pinfo.lengths[1],
                              &advance, pinfo2) != -1) {
                QByteArray m(pinfo2.results[0], pinfo2.lengths[0]);
                QByteArray n(pinfo2.results[1], pinfo2.lengths[1]);
                QByteArray p(pinfo2.results[2], pinfo2.lengths[2]);
                *version  = (m.toUInt() << 16) | (n.toUInt() << 8) | p.toUInt();
            } else {
                ret = false;
                break;
            }
        } else if (qstrncmp("debug", pinfo.results[0], pinfo.lengths[0]) == 0) {
            *debug = qstrncmp("true", pinfo.results[1], pinfo.lengths[1]) == 0;
        } else if (qstrncmp("buildkey", pinfo.results[0],
                              pinfo.lengths[0]) == 0){
            // save buildkey
            *key = QByteArray(pinfo.results[1], pinfo.lengths[1]);
        }
    } while (parse == 1 && parselen > 0);

    return ret;
}
#endif // QT_NO_PLUGIN_CHECK

#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(QT_NO_PLUGIN_CHECK)

#if defined(Q_OS_FREEBSD) || defined(Q_OS_LINUX)
#  define USE_MMAP
QT_BEGIN_INCLUDE_NAMESPACE
#  include <sys/types.h>
#  include <sys/mman.h>
QT_END_INCLUDE_NAMESPACE
#endif // Q_OS_FREEBSD || Q_OS_LINUX

static long qt_find_pattern(const char *s, ulong s_len,
                             const char *pattern, ulong p_len)
{
    /*
      we search from the end of the file because on the supported
      systems, the read-only data/text segments are placed at the end
      of the file.  HOWEVER, when building with debugging enabled, all
      the debug symbols are placed AFTER the data/text segments.

      what does this mean?  when building in release mode, the search
      is fast because the data we are looking for is at the end of the
      file... when building in debug mode, the search is slower
      because we have to skip over all the debugging symbols first
    */

    if (! s || ! pattern || p_len > s_len) return -1;
    ulong i, hs = 0, hp = 0, delta = s_len - p_len;

    for (i = 0; i < p_len; ++i) {
        hs += s[delta + i];
        hp += pattern[i];
    }
    i = delta;
    for (;;) {
        if (hs == hp && qstrncmp(s + i, pattern, p_len) == 0)
            return i;
        if (i == 0)
            break;
        --i;
        hs -= s[i + p_len];
        hs += s[i];
    }

    return -1;
}

/*
  This opens the specified library, mmaps it into memory, and searches
  for the QT_PLUGIN_VERIFICATION_DATA.  The advantage of this approach is that
  we can get the verification data without have to actually load the library.
  This lets us detect mismatches more safely.

  Returns false if version/key information is not present, or if the
                information could not be read.
  Returns  true if version/key information is present and successfully read.
*/
static bool qt_unix_query(const QString &library, uint *version, bool *debug, QByteArray *key, PatchedLibraryPrivate *lib = 0)
{
    QFile file(library);
    if (!file.open(QIODevice::ReadOnly)) {
        if (lib)
            lib->errorString = file.errorString();
        if (qt_debug_component()) {
            qWarning("%s: %s", (const char*) QFile::encodeName(library),
                qPrintable(qt_error_string(errno)));
        }
        return false;
    }

    QByteArray data;
    char *filedata = 0;
    ulong fdlen = 0;

#ifdef USE_MMAP
    char *mapaddr = 0;
    size_t maplen = file.size();
    mapaddr = (char *) mmap(mapaddr, maplen, PROT_READ, MAP_PRIVATE, file.handle(), 0);
    if (mapaddr != MAP_FAILED) {
        // mmap succeeded
        filedata = mapaddr;
        fdlen = maplen;
    } else {
        // mmap failed
        if (qt_debug_component()) {
            qWarning("mmap: %s", qPrintable(qt_error_string(errno)));
        }
        if (lib)
            lib->errorString = QLibrary::tr("Could not mmap '%1': %2")
                .arg(library)
                .arg(qt_error_string());
#endif // USE_MMAP
        // try reading the data into memory instead
        data = file.readAll();
        filedata = data.data();
        fdlen = data.size();
#ifdef USE_MMAP
    }
#endif // USE_MMAP

    // verify that the pattern is present in the plugin
    const char pattern[] = "pattern=QT_PLUGIN_VERIFICATION_DATA";
    const ulong plen = qstrlen(pattern);
    long pos = qt_find_pattern(filedata, fdlen, pattern, plen);

    bool ret = false;
    if (pos >= 0)
        ret = qt_parse_pattern(filedata + pos, version, debug, key);

    if (!ret && lib)
        lib->errorString = QLibrary::tr("Plugin verification data mismatch in '%1'").arg(library);
#ifdef USE_MMAP
    if (mapaddr != MAP_FAILED && munmap(mapaddr, maplen) != 0) {
        if (qt_debug_component())
            qWarning("munmap: %s", qPrintable(qt_error_string(errno)));
        if (lib)
            lib->errorString = QLibrary::tr("Could not unmap '%1': %2")
                .arg(library)
                .arg( qt_error_string() );
    }
#endif // USE_MMAP

    file.close();
    return ret;
}

#endif // Q_OS_UNIX && !Q_OS_MAC && !defined(QT_NO_PLUGIN_CHECK)

typedef QMap<QString, PatchedLibraryPrivate*> LibraryMap;
Q_GLOBAL_STATIC(LibraryMap, libraryMap)

PatchedLibraryPrivate::PatchedLibraryPrivate(const QString &canonicalFileName, const QString &version)
    :pHnd(0), fileName(canonicalFileName), fullVersion(version), instance(0), qt_version(0),
     libraryRefCount(1), libraryUnloadCount(0), pluginState(MightBeAPlugin)
{ libraryMap()->insert(canonicalFileName, this); }

PatchedLibraryPrivate *PatchedLibraryPrivate::findOrCreate(const QString &fileName, const QString &version)
{
    QMutexLocker locker(patched_qt_library_mutex());
    if (PatchedLibraryPrivate *lib = libraryMap()->value(fileName)) {
        lib->libraryRefCount.ref();
        return lib;
    }

    return new PatchedLibraryPrivate(fileName, version);
}

PatchedLibraryPrivate::~PatchedLibraryPrivate()
{
    LibraryMap * const map = libraryMap();
    if (map) {
        PatchedLibraryPrivate *that = map->take(fileName);
        Q_ASSERT(this == that);
	Q_UNUSED(that);
    }
}

void *PatchedLibraryPrivate::resolve(const char *symbol)
{
    if (!pHnd)
        return 0;
    return resolve_sys(symbol);
}


bool PatchedLibraryPrivate::load()
{
    libraryUnloadCount.ref();
    if (pHnd)
        return true;
    if (fileName.isEmpty())
        return false;
    return load_sys();
}

bool PatchedLibraryPrivate::unload()
{
    if (!pHnd)
        return false;
    if (!libraryUnloadCount.deref()) { // only unload if ALL QLibrary instance wanted to
        if (instance)
            delete instance();
        if  (unload_sys()) {
            instance = 0;
            pHnd = 0;
        }
    }

    return (pHnd == 0);
}

void PatchedLibraryPrivate::release()
{
    QMutexLocker locker(patched_qt_library_mutex());
    if (!libraryRefCount.deref())
        delete this;
}

bool PatchedLibraryPrivate::loadPlugin()
{
    if (instance) {
        libraryUnloadCount.ref();
        return true;
    }
    if (load()) {
        instance = (QtPluginInstanceFunction)resolve("qt_plugin_instance");
        return instance;
    }
    return false;
}

/*!
    Returns true if \a fileName has a valid suffix for a loadable
    library; otherwise returns false.

    \table
    \header \i Platform \i Valid suffixes
    \row \i Windows     \i \c .dll
    \row \i Unix/Linux  \i \c .so
    \row \i AIX  \i \c .a
    \row \i HP-UX       \i \c .sl, \c .so (HP-UXi)
    \row \i Mac OS X    \i \c .dylib, \c .bundle, \c .so
    \endtable

    Trailing versioning numbers on Unix are ignored.
 */
bool QLibrary::isLibrary(const QString &fileName)
{
#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
    return fileName.endsWith(QLatin1String(".dll"));
#else
    QString completeSuffix = QFileInfo(fileName).completeSuffix();
    if (completeSuffix.isEmpty())
        return false;
    QStringList suffixes = completeSuffix.split(QLatin1Char('.'));
# if defined(Q_OS_DARWIN)

    // On Mac, libs look like libmylib.1.0.0.dylib
    const QString lastSuffix = suffixes.at(suffixes.count() - 1);
    const QString firstSuffix = suffixes.at(0);

    bool valid = (lastSuffix == QLatin1String("dylib")
            || firstSuffix == QLatin1String("so")
            || firstSuffix == QLatin1String("bundle"));

    return valid;
# else  // Generic Unix
    QStringList validSuffixList;

#  if defined(Q_OS_HPUX)
/*
    See "HP-UX Linker and Libraries User's Guide", section "Link-time Differences between PA-RISC and IPF":
    "In PA-RISC (PA-32 and PA-64) shared libraries are suffixed with .sl. In IPF (32-bit and 64-bit),
    the shared libraries are suffixed with .so. For compatibility, the IPF linker also supports the .sl suffix."
 */
    validSuffixList << QLatin1String("sl");
#   if defined __ia64
    validSuffixList << QLatin1String("so");
#   endif
#  elif defined(Q_OS_AIX)
    validSuffixList << QLatin1String("a") << QLatin1String("so");
#  elif defined(Q_OS_UNIX)
    validSuffixList << QLatin1String("so");
#  endif

    // Examples of valid library names:
    //  libfoo.so
    //  libfoo.so.0
    //  libfoo.so.0.3
    //  libfoo-0.3.so
    //  libfoo-0.3.so.0.3.0

    int suffix;
    int suffixPos = -1;
    for (suffix = 0; suffix < validSuffixList.count() && suffixPos == -1; ++suffix)
        suffixPos = suffixes.indexOf(validSuffixList.at(suffix));

    bool valid = suffixPos != -1;
    for (int i = suffixPos + 1; i < suffixes.count() && valid; ++i)
        if (i != suffixPos)
            suffixes.at(i).toInt(&valid);
    return valid;
# endif
#endif

}

bool PatchedLibraryPrivate::isPlugin(QSettings *settings)
{
    errorString.clear();
    if (pluginState != MightBeAPlugin)
        return pluginState == IsAPlugin;

#ifndef QT_NO_PLUGIN_CHECK
    bool debug = !QLIBRARY_AS_DEBUG;
    QByteArray key;
    bool success = false;

    QFileInfo fileinfo(fileName);

#ifndef QT_NO_DATESTRING
    lastModified  = fileinfo.lastModified().toString(Qt::ISODate);
#endif
    QString regkey = QString::fromLatin1("Qt Plugin Cache %1.%2.%3/%4")
                     .arg((QT_VERSION & 0xff0000) >> 16)
                     .arg((QT_VERSION & 0xff00) >> 8)
                     .arg(QLIBRARY_AS_DEBUG ? QLatin1String("debug") : QLatin1String("false"))
                     .arg(fileName);
    QStringList reg;
#ifndef QT_NO_SETTINGS
    if (!settings) {
        static QSettings *staticSettings =
            new QSettings(QSettings::UserScope, QLatin1String("Trolltech"));
        settings = staticSettings;
    }
    reg = settings->value(regkey).toStringList();
#endif
    if (reg.count() == 4 && lastModified == reg.at(3)) {
        qt_version = reg.at(0).toUInt(0, 16);
        debug = bool(reg.at(1).toInt());
        key = reg.at(2).toLatin1();
        success = qt_version != 0;
    } else {
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
        if (!pHnd) {
            // use unix shortcut to avoid loading the library
            success = qt_unix_query(fileName, &qt_version, &debug, &key, this);
        } else
#endif
        {
            bool temporary_load = false;
#ifdef Q_OS_WIN
            HMODULE hTempModule = 0;
#endif
            if (!pHnd) {
#ifdef Q_OS_WIN
                QT_WA({
                    hTempModule = ::LoadLibraryExW((wchar_t*)QDir::toNativeSeparators(fileName).utf16(), 0, DONT_RESOLVE_DLL_REFERENCES);
                } , {
                    temporary_load = load_sys();
                });
#else
                temporary_load =  load_sys();
#endif
            }
#  ifdef Q_CC_BOR
            typedef const char * __stdcall (*QtPluginQueryVerificationDataFunction)();
#  else
            typedef const char * (*QtPluginQueryVerificationDataFunction)();
#  endif
#ifdef Q_OS_WIN
            QtPluginQueryVerificationDataFunction qtPluginQueryVerificationDataFunction = hTempModule
                ? (QtPluginQueryVerificationDataFunction)
#ifdef Q_OS_WINCE
                    ::GetProcAddressW(hTempModule, L"qt_plugin_query_verification_data")
#else
                    ::GetProcAddress(hTempModule, "qt_plugin_query_verification_data")
#endif
                : (QtPluginQueryVerificationDataFunction) resolve("qt_plugin_query_verification_data");
#else
            QtPluginQueryVerificationDataFunction qtPluginQueryVerificationDataFunction =
                (QtPluginQueryVerificationDataFunction) resolve("qt_plugin_query_verification_data");
#endif

            if (!qtPluginQueryVerificationDataFunction
                || !qt_parse_pattern(qtPluginQueryVerificationDataFunction(), &qt_version, &debug, &key)) {
                qt_version = 0;
                key = "unknown";
                if (temporary_load)
                    unload_sys();
            } else {
                success = true;
            }
#ifdef Q_OS_WIN
            if (hTempModule) {
                BOOL ok = ::FreeLibrary(hTempModule);
                if (ok) {
                    hTempModule = 0;
                }

            }
#endif
        }

        QStringList queried;
        queried << QString::number(qt_version,16)
                << QString::number((int)debug)
                << QLatin1String(key)
                << lastModified;
#ifndef QT_NO_SETTINGS
        settings->setValue(regkey, queried);
#endif
    }

    if (!success) {
        if (errorString.isEmpty()){
            if (fileName.isEmpty())
                errorString = QLibrary::tr("The shared library was not found.");
            else
                errorString = QLibrary::tr("The file '%1' is not a valid Qt plugin.").arg(fileName);
        }
        return false;
    }

    pluginState = IsNotAPlugin; // be pessimistic

    if ((qt_version > QT_VERSION) || ((QT_VERSION & 0xff0000) > (qt_version & 0xff0000))) {
        if (qt_debug_component()) {
            qWarning("In %s:\n"
                 "  Plugin uses incompatible Qt library (%d.%d.%d) [%s]",
                 (const char*) QFile::encodeName(fileName),
                 (qt_version&0xff0000) >> 16, (qt_version&0xff00) >> 8, qt_version&0xff,
                 debug ? "debug" : "release");
        }
        errorString = QLibrary::tr("The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5]")
            .arg(fileName)
            .arg((qt_version&0xff0000) >> 16)
            .arg((qt_version&0xff00) >> 8)
            .arg(qt_version&0xff)
            .arg(debug ? QLatin1String("debug") : QLatin1String("release"));
    } else if (key != QT_BUILD_KEY
#ifdef QT_BUILD_KEY_COMPAT
               // be sure to load plugins using an older but compatible build key
               && key != QT_BUILD_KEY_COMPAT
#endif
               ) {
        if (qt_debug_component()) {
            qWarning("In %s:\n"
                 "  Plugin uses incompatible Qt library\n"
                 "  expected build key \"%s\", got \"%s\"",
                 (const char*) QFile::encodeName(fileName),
                 QT_BUILD_KEY,
                 key.isEmpty() ? "<null>" : (const char *) key);
        }
        errorString = QLibrary::tr("The plugin '%1' uses incompatible Qt library."
                 " Expected build key \"%2\", got \"%3\"")
                 .arg(fileName)
                 .arg(QLatin1String(QT_BUILD_KEY))
                 .arg(key.isEmpty() ? QLatin1String("<null>") : QLatin1String((const char *) key));
#ifndef QT_NO_DEBUG_PLUGIN_CHECK
    } else if(debug != QLIBRARY_AS_DEBUG) {
        //don't issue a qWarning since we will hopefully find a non-debug? --Sam
        errorString = QLibrary::tr("The plugin '%1' uses incompatible Qt library."
                 " (Cannot mix debug and release libraries.)").arg(fileName);
#endif
    } else {
        pluginState = IsAPlugin;
    }

    return pluginState == IsAPlugin;
#else
    Q_UNUSED(settings);
    return pluginState == MightBeAPlugin;
#endif
}

/* Internal, for debugging */
bool qt_debug_component()
{
#if defined(QT_DEBUG_COMPONENT)
    return true;    //compatibility?
#else
    static int debug_env = -1;
    if (debug_env == -1)
       debug_env = QT_PREPEND_NAMESPACE(qgetenv)("QT_DEBUG_PLUGINS").toInt();

    return debug_env != 0;
#endif
}

PatchedPluginLoader::PatchedPluginLoader(QObject *)
    : d(0), did_load(false)
{
}

PatchedPluginLoader::PatchedPluginLoader(const QString &fileName, QObject *)
    : d(0), did_load(false)
{
    setFileName(fileName);
}

PatchedPluginLoader::~PatchedPluginLoader()
{
    if (d)
        d->release();
}

QObject *PatchedPluginLoader::instance()
{
    if (!load())
        return 0;
    if (d->instance)
        return d->instance();
    return 0;
}

bool PatchedPluginLoader::load()
{
    if (!d || d->fileName.isEmpty())
        return false;
    if (did_load)
        return d->pHnd && d->instance;
    if (!d->isPlugin())
        return false;
    did_load = true;
    return d->loadPlugin();
}

bool PatchedPluginLoader::unload()
{
    if (did_load) {
        did_load = false;
        return d->unload();
    }
    if (d)  // Ouch
        d->errorString = QLibrary::tr("The plugin was not loaded.");
    return false;
}

bool PatchedPluginLoader::isLoaded() const
{
    return d && d->pHnd && d->instance;
}

void PatchedPluginLoader::setFileName(const QString &fileName)
{
#if defined(QT_SHARED)
    QLibrary::LoadHints lh;
    if (d) {
        lh = d->loadHints;
        d->release();
        d = 0;
        did_load = false;
    }
    QString fn = QFileInfo(fileName).canonicalFilePath();
    d = PatchedLibraryPrivate::findOrCreate(fn);
    d->loadHints = lh;
    if (fn.isEmpty())
        d->errorString = QLibrary::tr("The shared library was not found.");
#else
    if (qt_debug_component()) {
        qWarning("Cannot load %s into a statically linked Qt library.",
            (const char*)QFile::encodeName(fileName));
    }
    Q_UNUSED(fileName);
#endif
}

QString PatchedPluginLoader::fileName() const
{
    if (d)
        return d->fileName;
    return QString();
}

QString PatchedPluginLoader::errorString() const
{
    return (!d || d->errorString.isEmpty()) ? QLibrary::tr("Unknown error") : d->errorString;
}

void PatchedPluginLoader::setLoadHints(QLibrary::LoadHints loadHints)
{
    if (!d) {
        d = PatchedLibraryPrivate::findOrCreate(QString());   // ugly, but we need a d-ptr
        d->errorString.clear();
    }
    d->loadHints = loadHints;
}

QLibrary::LoadHints PatchedPluginLoader::loadHints() const
{
    if (!d) {
        PatchedPluginLoader *that = const_cast<PatchedPluginLoader *>(this);
        that->d = PatchedLibraryPrivate::findOrCreate(QString());   // ugly, but we need a d-ptr
        that->d->errorString.clear();
    }
    return d->loadHints;
}




#ifdef Q_OS_UNIX



static QString qdlerror()
{
#if !defined(QT_HPUX_LD)
    const char *err = dlerror();
#else
    const char *err = strerror(errno);
#endif
    return err ? QLatin1String("(")+QString::fromLocal8Bit(err) + QLatin1String(")"): QString();
}

bool PatchedLibraryPrivate::load_sys()
{
    QFileInfo fi(fileName);
    QString path = fi.path();
    QString name = fi.fileName();
    if (path == QLatin1String(".") && !fileName.startsWith(path))
        path.clear();
    else
        path += QLatin1Char('/');

    // The first filename we want to attempt to load is the filename as the callee specified.
    // Thus, the first attempt we do must be with an empty prefix and empty suffix.
    QStringList suffixes(QLatin1String("")), prefixes(QLatin1String(""));
    if (pluginState != IsAPlugin) {
        prefixes << QLatin1String("lib");
#if defined(Q_OS_HPUX)
        // according to
        // http://docs.hp.com/en/B2355-90968/linkerdifferencesiapa.htm

        // In PA-RISC (PA-32 and PA-64) shared libraries are suffixed
        // with .sl. In IPF (32-bit and 64-bit), the shared libraries
        // are suffixed with .so. For compatibility, the IPF linker
        // also supports the .sl suffix.

        // But since we don't know if we are built on HPUX or HPUXi,
        // we support both .sl (and .<version>) and .so suffixes but
        // .so is preferred.
# if defined(__ia64)
        if (!fullVersion.isEmpty()) {
            suffixes << QString::fromLatin1(".so.%1").arg(fullVersion);
        } else {
            suffixes << QLatin1String(".so");
        }
# endif
        if (!fullVersion.isEmpty()) {
            suffixes << QString::fromLatin1(".sl.%1").arg(fullVersion);
            suffixes << QString::fromLatin1(".%1").arg(fullVersion);
        } else {
            suffixes << QLatin1String(".sl");
        }
#elif defined(Q_OS_AIX)
        suffixes << ".a";
#else
        if (!fullVersion.isEmpty()) {
            suffixes << QString::fromLatin1(".so.%1").arg(fullVersion);
        } else {
            suffixes << QLatin1String(".so");
        }
#endif
# ifdef Q_OS_MAC
        if (!fullVersion.isEmpty()) {
            suffixes << QString::fromLatin1(".%1.bundle").arg(fullVersion);
            suffixes << QString::fromLatin1(".%1.dylib").arg(fullVersion);
        } else {
            suffixes << QLatin1String(".bundle") << QLatin1String(".dylib");
        }
#endif
    }
    int dlFlags = 0;
#if defined(QT_HPUX_LD)
    dlFlags = DYNAMIC_PATH | BIND_NONFATAL;
    if (loadHints & QLibrary::ResolveAllSymbolsHint) {
        dlFlags |= BIND_IMMEDIATE;
    } else {
        dlFlags |= BIND_DEFERRED;
    }
#else
    if (loadHints & QLibrary::ResolveAllSymbolsHint) {
        dlFlags |= RTLD_NOW;
    } else {
        dlFlags |= RTLD_LAZY;
    }
    if (loadHints & QLibrary::ExportExternalSymbolsHint) {
        dlFlags |= RTLD_GLOBAL;
    }
#if !defined(Q_OS_CYGWIN)
    else {
#if defined(Q_OS_MAC)
        if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4)
#endif        
        dlFlags |= RTLD_LOCAL;
    }
#endif
#if defined(Q_OS_AIX)	// Not sure if any other platform actually support this thing.
    if (loadHints & QLibrary::LoadArchiveMemberHint) {
        dlFlags |= RTLD_MEMBER;
    }
#endif
#endif // QT_HPUX_LD
    QString attempt;
    bool retry = true;
    for(int prefix = 0; retry && !pHnd && prefix < prefixes.size(); prefix++) {
        for(int suffix = 0; retry && !pHnd && suffix < suffixes.size(); suffix++) {
            if (!prefixes.at(prefix).isEmpty() && name.startsWith(prefixes.at(prefix)))
                continue;
            if (!suffixes.at(suffix).isEmpty() && name.endsWith(suffixes.at(suffix)))
                continue;
            if (loadHints & QLibrary::LoadArchiveMemberHint) {
                attempt = name;
                int lparen = attempt.indexOf(QLatin1Char('('));
                if (lparen == -1)
                    lparen = attempt.count();
                attempt = path + prefixes.at(prefix) + attempt.insert(lparen, suffixes.at(suffix));
            } else {
                attempt = path + prefixes.at(prefix) + name + suffixes.at(suffix);
            }
#if defined(QT_HPUX_LD)
            pHnd = (void*)shl_load(QFile::encodeName(attempt), dlFlags, 0);
#else
            pHnd = dlopen(QFile::encodeName(attempt), dlFlags);
#endif
            if (!pHnd && fileName.startsWith(QLatin1Char('/')) && QFile::exists(attempt)) {
                // We only want to continue if dlopen failed due to that the shared library did not exist.
                // However, we are only able to apply this check for absolute filenames (since they are
                // not influenced by the content of LD_LIBRARY_PATH, /etc/ld.so.cache, DT_RPATH etc...)
                // This is all because dlerror is flawed and cannot tell us the reason why it failed.
                retry = false;
            }
        }
    }

#ifdef Q_OS_MAC
    if (!pHnd) {
        if (CFBundleRef bundle = CFBundleGetBundleWithIdentifier(QCFString(fileName))) {
            QCFType<CFURLRef> url = CFBundleCopyExecutableURL(bundle);
            QCFString str = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
            pHnd = dlopen(QFile::encodeName(str), dlFlags);
            attempt = str;
        }
    }
# endif
    if (!pHnd) {
        errorString = QLibrary::tr("Cannot load library %1: %2").arg(fileName).arg(qdlerror());
    }
    if (pHnd) {
        qualifiedFileName = attempt;
        errorString.clear();
    }
    return (pHnd != 0);
}

bool PatchedLibraryPrivate::unload_sys()
{
#if defined(QT_HPUX_LD)
    if (shl_unload((shl_t)pHnd)) {
#else
    if (dlclose(pHnd)) {
#endif
        errorString = QLibrary::tr("Cannot unload library %1: %2").arg(fileName).arg(qdlerror());
        return false;
    }
    errorString.clear();
    return true;
}

void* PatchedLibraryPrivate::resolve_sys(const char* symbol)
{
#if defined(QT_AOUT_UNDERSCORE)
    // older a.out systems add an underscore in front of symbols
    char* undrscr_symbol = new char[strlen(symbol)+2];
    undrscr_symbol[0] = '_';
    strcpy(undrscr_symbol+1, symbol);
    void* address = dlsym(pHnd, undrscr_symbol);
    delete [] undrscr_symbol;
#elif defined(QT_HPUX_LD)
    void* address = 0;
    if (shl_findsym((shl_t*)&pHnd, symbol, TYPE_UNDEFINED, &address) < 0)
        address = 0;
#else
    void* address = dlsym(pHnd, symbol);
#endif
    if (!address) {
        errorString = QLibrary::tr("Cannot resolve symbol \"%1\" in %2: %3").arg(
            QString::fromAscii(symbol)).arg(fileName).arg(qdlerror());
    } else {
        errorString.clear();
    }
    return address;
}

#endif // Q_OS_UNIX





#ifdef Q_OS_WIN

extern QString qt_error_string(int code);

bool PatchedLibraryPrivate::load_sys()
{
#ifdef Q_OS_WINCE
    QString attempt = QFileInfo(fileName).absoluteFilePath();
#else
    QString attempt = fileName;
#endif

    //avoid 'Bad Image' message box
    UINT oldmode = SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
    QT_WA({
        pHnd = LoadLibraryW((TCHAR*)QDir::toNativeSeparators(attempt).utf16());
    } , {
        pHnd = LoadLibraryA(QFile::encodeName(QDir::toNativeSeparators(attempt)).data());
    });
    
    if (pluginState != IsAPlugin) {
#if defined(Q_OS_WINCE)
        if (!pHnd && ::GetLastError() == ERROR_MOD_NOT_FOUND) {
            QString secondAttempt = fileName;
            QT_WA({
                pHnd = LoadLibraryW((TCHAR*)QDir::toNativeSeparators(secondAttempt).utf16());
            } , {
                pHnd = LoadLibraryA(QFile::encodeName(QDir::toNativeSeparators(secondAttempt)).data());
            });
        }
#endif
        if (!pHnd && ::GetLastError() == ERROR_MOD_NOT_FOUND) {
            attempt += QLatin1String(".dll");
            QT_WA({
                pHnd = LoadLibraryW((TCHAR*)QDir::toNativeSeparators(attempt).utf16());
            } , {
                pHnd = LoadLibraryA(QFile::encodeName(QDir::toNativeSeparators(attempt)).data());
            });
        }
    }

    SetErrorMode(oldmode);
    if (!pHnd) {
        errorString = QLibrary::tr("Cannot load library %1: %2").arg(fileName).arg(qt_error_string());
    }
    if (pHnd) {
        errorString.clear();
        QT_WA({
            TCHAR buffer[MAX_PATH + 1];
            ::GetModuleFileNameW(pHnd, buffer, MAX_PATH);
            attempt = QString::fromUtf16(reinterpret_cast<const ushort *>(&buffer));
        }, {
            char buffer[MAX_PATH + 1];
            ::GetModuleFileNameA(pHnd, buffer, MAX_PATH);
            attempt = QString::fromLocal8Bit(buffer);
        });
        const QDir dir =  QFileInfo(fileName).dir();
        const QString realfilename = attempt.mid(attempt.lastIndexOf(QLatin1Char('\\')) + 1);
        if (dir.path() == QLatin1String("."))
            qualifiedFileName = realfilename;
        else
            qualifiedFileName = dir.filePath(realfilename);
    }
    return (pHnd != 0);
}

bool PatchedLibraryPrivate::unload_sys()
{
    if (!FreeLibrary(pHnd)) {
        errorString = QLibrary::tr("Cannot unload library %1: %2").arg(fileName).arg(qt_error_string());
        return false;
    }
    errorString.clear();
    return true;
}

void* PatchedLibraryPrivate::resolve_sys(const char* symbol)
{
#ifdef Q_OS_WINCE
    void* address = (void*)GetProcAddress(pHnd, (const wchar_t*)QString::fromLatin1(symbol).utf16());
#else
    void* address = (void*)GetProcAddress(pHnd, symbol);
#endif
    if (!address) {
        errorString = QLibrary::tr("Cannot resolve symbol \"%1\" in %2: %3").arg(
            QString::fromAscii(symbol)).arg(fileName).arg(qt_error_string());
    } else {
        errorString.clear();
    }
    return address;
}

#endif // Q_OS_WIN

QT_END_NAMESPACE
