/*
 * Copyright (C) 2011, 2013 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 */

/*
 * When libvirt initializes, it creates a thread local for storing
 * the last virErrorPtr instance. It also registers a cleanup
 * callback for the thread local that will be invoked whenever
 * a thread exits.
 *
 * If the libvirt.so library was dlopen()'d and is dlclose()'d
 * while there is still a thread present, then when that thread
 * later exits, the libvirt cleanup callback will be invoked.
 * Unfortunately libvirt.so will no longer be in memory so the
 * callback SEGVs (if you're lucky), or invokes unlreated
 * code at the same address as the old callback (if you're
 * unlucky).
 *
 * To fix the problem libvirt is linked '-z nodelete' which
 * prevents the code being removed from memory at dlclose().
 *
 * This test case demonstrates this SEGV scenario. If this
 * test does not SEGV, then the '-z nodelete' fix is working
 */

#include <config.h>

#define VIR_NO_GLIB_STDIO /* This file intentionally does not link to libvirt/glib */
#include "testutils.h"

#ifdef linux

# include <dlfcn.h>
# include <pthread.h>
# include <unistd.h>
# include <signal.h>

# include "internal.h"

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
bool running = false;
bool failstart = false;
bool quit = false;

static void *threadMain(void *arg)
{
    int (*startup)(void) = arg;

    if (startup() < 0) {
        pthread_mutex_lock(&lock);
        failstart = true;
        pthread_cond_signal(&cond);
    } else {
        pthread_mutex_lock(&lock);
        running = true;
        pthread_cond_signal(&cond);
    }

    while (!quit)
        pthread_cond_wait(&cond, &lock);
    pthread_mutex_unlock(&lock);

    return NULL;
}

static void sigHandler(int sig)
{
    ignore_value(write(STDERR_FILENO, "FAIL\n", 5)); /* sc_avoid_write */
    signal(sig, SIG_DFL);
    raise(sig);
}

/* We're not using the testutils.c main() wrapper because
 * we don't want  'shunloadtest' itself to link against
 * libvirt.so. We need to test dlopen()'ing of libvirt.so
 */
int main(int argc G_GNUC_UNUSED, char **argv)
{
    void (*startup)(void);
    pthread_t t;
    void *lib;
    char *theprogname;

    theprogname = argv[0];
    if (STRPREFIX(theprogname, "./"))
        theprogname += 2;

    fprintf(stderr, "TEST: %s\n", theprogname);
    fprintf(stderr, "      .%*s 1   ", 39, "");
    signal(SIGSEGV, sigHandler);

    if (!(lib = dlopen(abs_builddir "/libshunload.so", RTLD_LAZY))) {
        fprintf(stderr, "Cannot load ./libshunload.so %s\n", dlerror());
        return 1;
    }
    if (!(startup = dlsym(lib, "shunloadStart"))) {
        fprintf(stderr, "Cannot find shunloadStart %s\n", dlerror());
        dlclose(lib);
        return 1;
    }

    /*
     * Create a thread which is going to initialize libvirt
     * and raise an error
     */
    pthread_create(&t, NULL, threadMain, startup);

    /* Wait for the thread to start and call libvirt */
    pthread_mutex_lock(&lock);
    while (!running && !failstart)
        pthread_cond_wait(&cond, &lock);

    /* Close the shared library (and thus make libvirt.so
     * non-resident */
    dlclose(lib);

    /* Tell the thread to quit */
    quit = true;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&lock);

    pthread_join(t, NULL);

    /* If we got to here the thread successfully exited without
     * causing a SEGV !
     */

    if (failstart)
        fprintf(stderr, "FAIL to initialize libvirt\n");
    else
        fprintf(stderr, "OK\n");

    return 0;
}

#else

int main(void)
{
    return EXIT_AM_SKIP;
}

#endif
