/* Chrysalide - Outil d'analyse de fichiers binaires
 * pychrysa.c - plugin permettant des extensions en Python
 *
 * Copyright (C) 2018-2019 Cyrille Bagard
 *
 *  This file is part of Chrysalide.
 *
 *  Chrysalide is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Chrysalide 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Chrysalide.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "pychrysa.h"


#include <assert.h>
#include <errno.h>
#include <malloc.h>
#include <pygobject.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>


#include <config.h>
#include <i18n.h>
#include <gleak.h>
#include <common/cpp.h>
#include <common/environment.h>
#include <common/extstr.h>
#include <core/core.h>
#include <plugins/pglist.h>
#include <plugins/self.h>


#include "access.h"
#include "constval.h"
#include "helpers.h"
#include "plugin.h"
#include "star.h"
#include "struct.h"
#include "analysis/module.h"
#include "arch/module.h"
#include "common/module.h"
#include "core/module.h"
#include "debug/module.h"
#include "format/module.h"
#include "glibext/module.h"
#include "gtkext/module.h"
#include "gui/module.h"
#include "mangling/module.h"



DEFINE_CHRYSALIDE_CONTAINER_PLUGIN("PyChrysalide", "Chrysalide bindings to Python",
                                   PACKAGE_VERSION, CHRYSALIDE_WEBSITE("api/python/pychrysalide"),
                                   NO_REQ, AL(PGA_PLUGIN_INIT, PGA_PLUGIN_EXIT, PGA_NATIVE_LOADED));


/* Note la nature du chargement */
static bool _standalone = true;

/* Réceptacle pour le chargement forcé */
static PyObject *_chrysalide_module = NULL;

/* Conservation des informations du thread principal */
static PyThreadState *_main_tstate = NULL;


/* Fournit la révision du programme global. */
static PyObject *py_chrysalide_revision(PyObject *, PyObject *);

/* Fournit la version du programme global. */
static PyObject *py_chrysalide_version(PyObject *, PyObject *);

/* Fournit la version du greffon pour Python. */
static PyObject *py_chrysalide_mod_version(PyObject *, PyObject *);

/* Détermine si l'interpréteur lancé est celui pris en compte. */
static bool is_current_abi_suitable(void);

/* Définit la version attendue de GTK à charger dans Python. */
static bool set_version_for_gtk_namespace(const char *);

/* Point de sortie pour l'initialisation de Python. */
static void PyExit_pychrysalide(void);

/* Ajoute le module 'plugins' au module Python. */
static bool add_plugin_module_to_python_module(PyObject *);

/* Complète les chemins de recherches de Python. */
static void extend_python_path(const char *);

/* Charge autant de greffons composés en Python que possible. */
static void load_python_plugins(GPluginModule *);



/******************************************************************************
*                                                                             *
*  Paramètres  : self = NULL car méthode statique.                            *
*                args = non utilisé ici.                                      *
*                                                                             *
*  Description : Fournit la révision du programme global.                     *
*                                                                             *
*  Retour      : Numéro de révision.                                          *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static PyObject *py_chrysalide_revision(PyObject *self, PyObject *args)
{
    PyObject *result;                       /* Valeur à retourner          */

#define PY_CHRYSALIDE_REVISION_METHOD PYTHON_METHOD_DEF                     \
(                                                                           \
     revision, "/",                                                         \
     METH_NOARGS, py_chrysalide,                                            \
     "Provide the revision number of Chrysalide.\n"                         \
     "\n"                                                                   \
     "The returned value is provided as a string, for instance: 'r1665'."   \
)

    result = PyUnicode_FromString("r" XSTR(REVISION));

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : self = NULL car méthode statique.                            *
*                args = non utilisé ici.                                      *
*                                                                             *
*  Description : Fournit la version du programme global.                      *
*                                                                             *
*  Retour      : Numéro de version.                                           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static PyObject *py_chrysalide_version(PyObject *self, PyObject *args)
{
    PyObject *result;                       /* Valeur à retourner          */
    int major;                              /* Numéro de version majeur    */
    int minor;                              /* Numéro de version mineur    */
    int revision;                           /* Numéro de révision          */
    char version[16];                       /* Conservation temporaire     */

#define PY_CHRYSALIDE_VERSION_METHOD PYTHON_METHOD_DEF                      \
(                                                                           \
     version, "/",                                                          \
     METH_NOARGS, py_chrysalide,                                            \
     "Provide the version number of Chrysalide.\n"                          \
     "\n"                                                                   \
     "The returned value is provided as a string, for instance: '1.6.65'."  \
)

    major = REVISION / 1000;
    minor = (REVISION - (major * 1000)) / 100;
    revision = REVISION % 100;

    snprintf(version, sizeof(version), "%d.%d.%d", major, minor, revision);

    result = PyUnicode_FromString(version);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : self = NULL car méthode statique.                            *
*                args = non utilisé ici.                                      *
*                                                                             *
*  Description : Fournit la version du greffon pour Python.                   *
*                                                                             *
*  Retour      : Numéro de version.                                           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static PyObject *py_chrysalide_mod_version(PyObject *self, PyObject *args)
{
    PyObject *result;                       /* Valeur à retourner          */
    char version[16];                       /* Conservation temporaire     */

#define PY_CHRYSALIDE_MOD_VERSION_METHOD PYTHON_METHOD_DEF                  \
(                                                                           \
     mod_version, "/",                                                      \
     METH_NOARGS, py_chrysalide,                                            \
     "Provide the version number of Chrysalide module for Python.\n"        \
     "\n"                                                                   \
     "The returned value is provided as a string, for instance: '0.1.0'."   \
)

    snprintf(version, sizeof(version), "%s", _chrysalide_plugin.version);

    result = PyUnicode_FromString(version);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Détermine si l'interpréteur lancé est celui pris en compte.  *
*                                                                             *
*  Retour      : true si l'exécution peut se poursuivre, false sinon.         *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool is_current_abi_suitable(void)
{
    bool result;
    int fds[2];
    int ret;
    char cmds[128];
    char content[64];
    ssize_t got;

#define GRAB_ABI_FLAGS_IN_PYTHON                        \
    "import sys" "\n"                                   \
    "import os" "\n"                                    \
    "os.write(%d, bytes(sys.abiflags, 'UTF-8'))" "\n"

    result = false;

    ret = pipe(fds);
    if (ret == -1)
    {
        perror("pipe()");
        return false;
    }

    snprintf(cmds, sizeof(cmds), GRAB_ABI_FLAGS_IN_PYTHON, fds[1]);

    ret = PyRun_SimpleString(cmds);
    if (ret != 0) goto icas_exit;

    got = read(fds[0], content, sizeof(content));
    if (got < 0)
    {
        perror("read()");
        goto icas_exit;
    }

    content[got] = '\0';

    result = (strcmp(content, LIBPYTHON_ABI_FLAGS) == 0);

 icas_exit:

    if (!result)
        PyErr_SetString(PyExc_SystemError, "the ABI flags of the current interpreter do not match " \
                        "the ones of the Python library used during the module compilation.");

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : version = idenfiant de la version de GTK à stipuler.         *
*                                                                             *
*  Description : Définit la version attendue de GTK à charger dans Python.    *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool set_version_for_gtk_namespace(const char *version)
{
    bool result;                            /* Bilan à retourner           */
    PyObject *gi_mod;                       /* Module Python-GObject       */
    PyObject *args;                         /* Arguments à fournir         */

    result = false;

    /**
     * On cherche ici à éviter le message suivant si on charge 'gi.repository.Gtk' directement :
     *
     *
     *   PyGIWarning: Gtk was imported without specifying a version first. \
     *   Use gi.require_version('Gtk', '3.0') before import to ensure that the right version gets loaded.
     *
     */

    gi_mod = PyImport_ImportModule("gi");

    if (gi_mod != NULL)
    {
        args = Py_BuildValue("ss", "Gtk", version);

        run_python_method(gi_mod, "require_version", args);

        result = (PyErr_Occurred() == NULL);

        Py_DECREF(args);
        Py_DECREF(gi_mod);

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Point de sortie pour l'initialisation de Python.             *
*                                                                             *
*  Retour      : ?                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void PyExit_pychrysalide(void)
{
    assert(_standalone);

    extern void set_current_project(void *project);

    set_current_project(NULL);

#ifdef TRACK_GOBJECT_LEAKS
    remember_gtypes_for_leaks();
#endif

    exit_all_plugins();

    unload_all_basic_components();

#ifdef TRACK_GOBJECT_LEAKS
    dump_remaining_gtypes();
#endif

}


/******************************************************************************
*                                                                             *
*  Paramètres  : super = module dont la définition est à compléter.           *
*                                                                             *
*  Description : Ajoute le module 'plugins' au module Python.                 *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool add_plugin_module_to_python_module(PyObject *super)
{
    bool result;                            /* Bilan à retourner           */
    PyObject *module;                       /* Sous-module mis en place    */

#define PYCHRYSALIDE_PLUGINS_DOC                    \
    "Home for all plugins without another home."

    static PyModuleDef py_chrysalide_deguard_module = {

        .m_base = PyModuleDef_HEAD_INIT,

        .m_name = "pychrysalide.plugins",
        .m_doc = PYCHRYSALIDE_PLUGINS_DOC,

        .m_size = -1,

    };

    result = false;

    module = build_python_module(super, &py_chrysalide_deguard_module);

    result = (module != NULL);

    assert(result);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Point d'entrée pour l'initialisation de Python.              *
*                                                                             *
*  Retour      : ?                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

#define PYCHRYSALIDE_DOC                                                                                        \
    "PyChrysalide is a module containing Chrysalide's features and designed for Python users.\n"                \
    "\n"                                                                                                        \
    "The whole API is defined in a single library named 'pychrysalide.so' and can be used in two ways:\n"       \
    "* either from the Chrysalide's GUI, by registering hooks or GLib signals,\n"                               \
    "* or from a shell command line, by setting PYTHONPATH to point to the directory containing the library.\n" \
    "\n"                                                                                                        \
    "In both cases, this is a good start point to have a look at already existing plugins to quickly learn "    \
    "how the API works.\n"                                                                                      \
    "\n"                                                                                                        \
    "These plugins are located in the 'plugins/python' directory."

PyMODINIT_FUNC PyInit_pychrysalide(void)
{
    PyObject *result;                       /* Module Python à retourner   */
    bool status;                            /* Bilan des inclusions        */
    int ret;                                /* Bilan de préparatifs        */
    GPluginModule *self;                    /* Représentation interne      */
    PluginStatusFlags self_flags;           /* Fanions à mettre à jour     */

    static PyMethodDef py_chrysalide_methods[] = {
        PY_CHRYSALIDE_REVISION_METHOD,
        PY_CHRYSALIDE_VERSION_METHOD,
        PY_CHRYSALIDE_MOD_VERSION_METHOD,
        { NULL }
    };

    static PyModuleDef py_chrysalide_module = {

        .m_base = PyModuleDef_HEAD_INIT,

        .m_name = "pychrysalide",
        .m_doc = PYCHRYSALIDE_DOC,

        .m_size = -1,

        .m_methods = py_chrysalide_methods

    };

    /**
     * Vérification préalable : dans le cas où on est embarqué directement dans
     * un interpréteur Python, le module se charge et termine par charger à leur
     * tour les différentes extensions trouvées, via load_remaning_plugins() puis
     * chrysalide_plugin_on_native_loaded().
     *
     * Lesquelles vont très probablement charger le module pychrysalide.
     *
     * Comme le chargement de ce dernier n'est alors pas encore terminé,
     * Python va relancer cette procédure, et register_access_to_python_module()
     * va détecter un doublon.
     */

    result = get_access_to_python_module(py_chrysalide_module.m_name);

    if (result != NULL)
    {
        Py_INCREF(result);
        return result;
    }

    if (!is_current_abi_suitable())
        return NULL;

    if (pygobject_init(-1, -1, -1) == NULL)
    {
        PyErr_SetString(PyExc_SystemError, "unable to init GObject in Python.");
        return NULL;
    }

    if (!set_version_for_gtk_namespace("3.0"))
        return NULL;

    if (!load_all_basic_components())
    {
        PyErr_SetString(PyExc_SystemError, "unable to load all basic components.");
        return NULL;
    }

    /* Mise en place des fonctionnalités offertes */

    result = PyModule_Create(&py_chrysalide_module);

    register_access_to_python_module(py_chrysalide_module.m_name, result);

    status = true;

    if (status) status = add_features_module(result);

    if (status) add_plugin_module_to_python_module(result);

    if (status) status = add_analysis_module(result);
    if (status) status = add_arch_module(result);
    if (status) status = add_common_module(result);
    if (status) status = add_core_module(result);
    if (status) status = add_debug_module(result);
    if (status) status = add_format_module(result);
    if (status) status = add_glibext_module(result);
    if (status) status = add_gtkext_module(result);
    if (status) status = add_gui_module(result);
    if (status) status = add_mangling_module(result);

    if (status) status = ensure_python_plugin_module_is_registered();
    if (status) status = ensure_python_py_constval_is_registered();
    if (status) status = ensure_python_py_struct_is_registered();

    if (status) status = populate_analysis_module();
    if (status) status = populate_arch_module();
    if (status) status = populate_common_module();
    if (status) status = populate_core_module();
    if (status) status = populate_debug_module();
    if (status) status = populate_format_module();
    if (status) status = populate_glibext_module();
    if (status) status = populate_gtkext_module();
    if (status) status = populate_gui_module();
    if (status) status = populate_mangling_module();

    if (!status)
    {
        PyErr_SetString(PyExc_SystemError, "failed to load all PyChrysalide components.");
        return NULL;
    }

    if (_standalone)
    {
        ret = Py_AtExit(PyExit_pychrysalide);

        if (ret == -1)
        {
            PyErr_SetString(PyExc_SystemError, "failed to register a cleanup function.");
            return NULL;
        }

        /**
         * Comme les sources locales sont prioritaires, le fichier "core/global.h"
         * du greffon masque la fonction suivante, issue du corps principal du
         * programme.
         *
         * On la déclare donc à la main.
         */
        extern void set_batch_mode(void);

        set_batch_mode();

        init_all_plugins(false);

        lock_plugin_list_for_reading();

        self = get_plugin_by_name("PyChrysalide", NULL);
        assert(self != NULL);

        self_flags = g_plugin_module_get_flags(self);
        self_flags &= ~(PSF_FAILURE | PSF_LOADED);
        self_flags |= (status ? PSF_LOADED : PSF_FAILURE);

        g_plugin_module_override_flags(self, self_flags);

        unlock_plugin_list_for_reading();

        load_remaning_plugins();

        /**
         * On laisse fuir ici la référence sur self afin d'avoir
         * l'assurance que le greffon se déchargera toujours en dernier.
         *
         * La fuite mémoire est au final évitée dans PyExit_pychrysalide().
         */

    }

    return result;

}

/******************************************************************************
*                                                                             *
*  Paramètres  : path = chemin supplémentaire pour l'espace de recherche.     *
*                                                                             *
*  Description : Complète les chemins de recherches de Python.                *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void extend_python_path(const char *path)
{
    PyObject *list;                         /* Liste de chemins à compléter*/
    PyObject *new;                          /* Nouveau chemin à intégrer   */

    list = PySys_GetObject("path");
    assert(list != NULL);

    new = PyUnicode_FromString(path);
    assert(new != NULL);

    PyList_Append(list, new);

    Py_DECREF(new);

    add_to_env_var("PYTHONPATH", path, ":");

}


/******************************************************************************
*                                                                             *
*  Paramètres  : plugin = instance représentant le greffon Python d'origine.  *
*                                                                             *
*  Description : Charge autant de greffons composés en Python que possible.   *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void load_python_plugins(GPluginModule *plugin)
{
    DIR *dir;                               /* Répertoire à parcourir      */
    char *paths;                            /* Emplacements de greffons    */
    char *save;                             /* Sauvegarde pour ré-entrance */
    char *path;                             /* Chemin à fouiller           */
    struct dirent *entry;                   /* Elément trouvé              */
    char *modname;                          /* Nom du module pour Python   */
    char *filename;                         /* Chemin d'accès reconstruit  */
    GPluginModule *pyplugin;                /* Lien vers un grffon Python  */

    /* Définition des zones d'influence */

#ifndef DISCARD_LOCAL

    extend_python_path(PACKAGE_SOURCE_DIR G_DIR_SEPARATOR_S "plugins" G_DIR_SEPARATOR_S "python");

#else

    dir = opendir(PLUGINS_DATA_DIR G_DIR_SEPARATOR_S "python");

    if (dir != NULL)
    {
         closedir(dir);
         extend_python_path(PLUGINS_DATA_DIR G_DIR_SEPARATOR_S "python");
    }

#endif

    g_plugin_module_log_variadic_message(plugin, LMT_INFO,
                                         _("PYTHONPATH environment variable set to '%s'"),
                                         getenv("PYTHONPATH"));

    /* Chargements des extensions Python */

    paths = get_env_var("PYTHONPATH");

    save = NULL;   /* gcc... */

    for (path = strtok_r(paths, ":", &save);
         path != NULL; 
         path = strtok_r(NULL, ":", &save))
    {
        dir = opendir(path);
        if (dir == NULL)
        {
            perror("opendir");
            continue;
        }

        g_plugin_module_log_variadic_message(plugin, LMT_INFO, 
                                             _("Looking for Python plugins in '%s'..."),
                                             path);

        while (1)
        {
            errno = 0;

            entry = readdir(dir);

            if (entry == NULL)
            {
                if (errno != 0)
                    perror("readdir");

                break;

            }

            if (entry->d_type != DT_DIR) continue;
            if (entry->d_name[0] == '.') continue;

            modname = strdup(entry->d_name);
            modname = stradd(modname, ".");
            modname = stradd(modname, "__init__");

            filename = strdup(path);
            filename = stradd(filename, G_DIR_SEPARATOR_S);
            filename = stradd(filename, entry->d_name);

            pyplugin = g_python_plugin_new(modname, filename);

            if (pyplugin == NULL)
                g_plugin_module_log_variadic_message(plugin, LMT_ERROR, 
                                                     _("No suitable Python plugin found in '%s'"),
                                                     filename);
            else
            {
                g_plugin_module_log_variadic_message(plugin, LMT_PROCESS, 
                                                     _("Loaded the Python plugin found in the '<b>%s</b>' directory"),
                                                     filename);

                /**
                 * Comme le greffon n'est pas passé par la résolution des dépendances,
                 * on simule l'effet attendu.
                 */
                g_object_ref(G_OBJECT(plugin));

                _register_plugin(pyplugin);

            }

            free(filename);
            free(modname);

        }

         closedir(dir);

    }

    free(paths);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : plugin = greffon à manipuler.                                *
*                                                                             *
*  Description : Prend acte du chargement du greffon.                         *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

G_MODULE_EXPORT bool chrysalide_plugin_init(GPluginModule *plugin)
{
    bool result;                            /* Bilan à retourner           */
    int ret;                                /* Bilan de préparatifs        */

    _standalone = false;

    /* Chargement du module pour Python */

    ret = PyImport_AppendInittab("pychrysalide", &PyInit_pychrysalide);

    if (ret == -1)
    {
        log_plugin_simple_message(LMT_ERROR, _("Can not extend the existing table of Python built-in modules."));
        result = false;
        goto cpi_done;
    }

    Py_Initialize();

    PyEval_InitThreads();

    PySys_SetArgv(0, (wchar_t *[]) { NULL });

    _chrysalide_module = PyImport_ImportModule("pychrysalide");

    /**
     * Pour mémoire, une situation concrête conduisant à un échec :
     * le paquet python3-gi-dbg n'est pas installé alors que le
     * programme est compilé en mode débogage.
     *
     * Dans ce cas, pygobject_init(-1, -1, -1) échoue, et Py_Initialize()
     * le laisse rien filtrer...
     *
     * En mode autonome, le shell Python remonte bien l'erreur par contre.
     */

    result = (_chrysalide_module != NULL);

    _main_tstate = PyThreadState_Get();

    PyEval_ReleaseLock();

 cpi_done:

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : plugin = greffon à manipuler.                                *
*                                                                             *
*  Description : Prend acte du déchargement du greffon.                       *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

G_MODULE_EXPORT void chrysalide_plugin_exit(GPluginModule *plugin)
{
    clear_all_accesses_to_python_modules();

    Py_XDECREF(_chrysalide_module);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : plugin = greffon à manipuler.                                *
*                action = type d'action attendue.                             *
*                                                                             *
*  Description : Accompagne la fin du chargement des modules natifs.          *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

G_MODULE_EXPORT void chrysalide_plugin_on_native_loaded(GPluginModule *plugin, PluginAction action)
{
    PyThreadState *tstate;                  /* Contexte d'environnement    */

    if (!_standalone)
    {
        tstate = get_pychrysalide_main_tstate();
        PyEval_RestoreThread(tstate);
    }

    load_python_plugins(plugin);

    if (!_standalone)
        PyEval_SaveThread();

}


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Fournit les informations du thread principal.                *
*                                                                             *
*  Retour      : Indications utiles à Python.                                 *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

PyThreadState *get_pychrysalide_main_tstate(void)
{
    PyThreadState *result;                  /* Indications à retourner     */

    result = _main_tstate;

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : prefix = message d'introduction à faire apparaître à l'écran.*
*                                                                             *
*  Description : Présente dans le journal une exception survenue.             *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void log_pychrysalide_exception(const char *prefix, ...)
{
    va_list ap;                             /* Compléments argumentaires   */
    char *msg;                              /* Message complet à imprimer  */
    PyObject *err_type;                     /* Type d'erreur Python        */
    PyObject *err_value;                    /* Instance Python d'erreur    */
    PyObject *err_traceback;                /* Trace Python associée       */
    PyObject *err_string;                   /* Description Python d'erreur */
    const char *err_msg;                    /* Représentation humaine      */

    if (PyErr_Occurred())
    {
        /* Base de la communication */

        va_start(ap, prefix);

        vasprintf(&msg, prefix, ap);

        va_end(ap);

        /* Détails complémentaires */

        PyErr_Fetch(&err_type, &err_value, &err_traceback);

        if (err_value == NULL)
            msg = stradd(msg, _("; no extra information is provided..."));

        else
        {
            err_string = PyObject_Str(err_value);
            err_msg = PyUnicode_AsUTF8(err_string);

            msg = stradd(msg, ": ");
            msg = stradd(msg, err_msg);

            Py_DECREF(err_string);
            Py_DECREF(err_value);

        }

        Py_XDECREF(err_traceback);
        Py_XDECREF(err_type);

        log_plugin_simple_message(LMT_ERROR, msg);

        free(msg);

    }

}