/*
 * SPDX-FileCopyrightText: 2003-2026 Sébastien Helleu <flashcode@flashtux.org>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 * This file is part of WeeChat, the extensible chat client.
 *
 * WeeChat 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.
 *
 * WeeChat 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 WeeChat.  If not, see <https://www.gnu.org/licenses/>.
 */

/* WeeChat hooks management */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <errno.h>

#include "weechat.h"
#include "core-debug.h"
#include "core-hook.h"
#include "core-hashtable.h"
#include "core-hdata.h"
#include "core-infolist.h"
#include "core-log.h"
#include "core-signal.h"
#include "core-string.h"
#include "core-util.h"
#include "../gui/gui-chat.h"
#include "../plugins/plugin.h"


char *hook_type_string[HOOK_NUM_TYPES] =
{ "command", "command_run", "timer", "fd", "process", "connect", "line",
  "print", "signal", "hsignal", "config", "completion", "modifier",
  "info", "info_hashtable", "infolist", "hdata", "focus", "url" };
struct t_hook *weechat_hooks[HOOK_NUM_TYPES];     /* list of hooks          */
struct t_hook *last_weechat_hook[HOOK_NUM_TYPES]; /* last hook              */
int hooks_count[HOOK_NUM_TYPES];                  /* number of hooks        */
int hooks_count_total = 0;                        /* total number of hooks  */
int hook_exec_recursion = 0;           /* 1 when a hook is executed         */
int real_delete_pending = 0;           /* 1 if some hooks must be deleted   */

int hook_socketpair_ok = 0;            /* 1 if socketpair() is OK           */

/* hook callbacks */
t_callback_hook *hook_callback_add[HOOK_NUM_TYPES] =
{ NULL, NULL, NULL, &hook_fd_add_cb, NULL, NULL, NULL, NULL, NULL, NULL,
  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
t_callback_hook *hook_callback_remove[HOOK_NUM_TYPES] =
{ NULL, NULL, NULL, &hook_fd_remove_cb, NULL, NULL, NULL, NULL, NULL, NULL,
  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
t_callback_hook *hook_callback_free_data[HOOK_NUM_TYPES] =
{
    &hook_command_free_data,
    &hook_command_run_free_data,
    &hook_timer_free_data,
    &hook_fd_free_data,
    &hook_process_free_data,
    &hook_connect_free_data,
    &hook_line_free_data,
    &hook_print_free_data,
    &hook_signal_free_data,
    &hook_hsignal_free_data,
    &hook_config_free_data,
    &hook_completion_free_data,
    &hook_modifier_free_data,
    &hook_info_free_data,
    &hook_info_hashtable_free_data,
    &hook_infolist_free_data,
    &hook_hdata_free_data,
    &hook_focus_free_data,
    &hook_url_free_data,
};
t_callback_hook_get_desc *hook_callback_get_desc[HOOK_NUM_TYPES] =
{
    &hook_command_get_description,
    &hook_command_run_get_description,
    &hook_timer_get_description,
    &hook_fd_get_description,
    &hook_process_get_description,
    &hook_connect_get_description,
    &hook_line_get_description,
    &hook_print_get_description,
    &hook_signal_get_description,
    &hook_hsignal_get_description,
    &hook_config_get_description,
    &hook_completion_get_description,
    &hook_modifier_get_description,
    &hook_info_get_description,
    &hook_info_hashtable_get_description,
    &hook_infolist_get_description,
    &hook_hdata_get_description,
    &hook_focus_get_description,
    &hook_url_get_description,
};
t_callback_hook_infolist *hook_callback_add_to_infolist[HOOK_NUM_TYPES] =
{
    &hook_command_add_to_infolist,
    &hook_command_run_add_to_infolist,
    &hook_timer_add_to_infolist,
    &hook_fd_add_to_infolist,
    &hook_process_add_to_infolist,
    &hook_connect_add_to_infolist,
    &hook_line_add_to_infolist,
    &hook_print_add_to_infolist,
    &hook_signal_add_to_infolist,
    &hook_hsignal_add_to_infolist,
    &hook_config_add_to_infolist,
    &hook_completion_add_to_infolist,
    &hook_modifier_add_to_infolist,
    &hook_info_add_to_infolist,
    &hook_info_hashtable_add_to_infolist,
    &hook_infolist_add_to_infolist,
    &hook_hdata_add_to_infolist,
    &hook_focus_add_to_infolist,
    &hook_url_add_to_infolist,
};
t_callback_hook *hook_callback_print_log[HOOK_NUM_TYPES] =
{
    &hook_command_print_log,
    &hook_command_run_print_log,
    &hook_timer_print_log,
    &hook_fd_print_log,
    &hook_process_print_log,
    &hook_connect_print_log,
    &hook_line_print_log,
    &hook_print_print_log,
    &hook_signal_print_log,
    &hook_hsignal_print_log,
    &hook_config_print_log,
    &hook_completion_print_log,
    &hook_modifier_print_log,
    &hook_info_print_log,
    &hook_info_hashtable_print_log,
    &hook_infolist_print_log,
    &hook_hdata_print_log,
    &hook_focus_print_log,
    &hook_url_print_log,
};


/*
 * Initialize hooks.
 */

void
hook_init (void)
{
    int type, sock[2], rc;

    /* initialize list of hooks and callbacks */
    for (type = 0; type < HOOK_NUM_TYPES; type++)
    {
        weechat_hooks[type] = NULL;
        last_weechat_hook[type] = NULL;
        hooks_count[type] = 0;
    }
    hooks_count_total = 0;
    hook_last_system_time = time (NULL);

    /*
     * Set a flag to 0 if socketpair() function is not available.
     *
     * For the connect hook, when this is defined an array of sockets will
     * be passed from the parent process to the child process instead of using
     * SCM_RIGHTS to pass a socket back from the child process to parent
     * process.
     *
     * This allows connections to work on Windows but it limits the number of
     * IPs that can be attempted each time.
     */
    hook_socketpair_ok = 1;

#if defined(__CYGWIN__) || defined(__APPLE__) || defined(__MACH__)
    hook_socketpair_ok = 0;
    (void) sock;
    (void) rc;
#else
    /*
     * Test if socketpair() function is working fine: this is NOT the case
     * on Windows with Ubuntu bash
     * (errno == 94: ESOCKTNOSUPPORT: socket type not supported)
     */
    rc = socketpair (AF_LOCAL, SOCK_DGRAM, 0, sock);
    if (rc < 0)
    {
        /* Windows/Ubuntu */
        hook_socketpair_ok = 0;
    }
    else
    {
        close (sock[0]);
        close (sock[1]);
    }
#endif
}

/*
 * Search for a hook type.
 *
 * Return index of type in enum t_hook_type, -1 if type is not found.
 */

int
hook_search_type (const char *type)
{
    int i;

    if (!type)
        return -1;

    for (i = 0; i < HOOK_NUM_TYPES; i++)
    {
        if (strcmp (hook_type_string[i], type) == 0)
            return i;
    }

    /* type not found */
    return -1;
}

/*
 * Search for position of hook in list (to keep hooks sorted).
 *
 * Hooks are sorted by priority, except commands which are sorted by command
 * name, and then priority.
 */

struct t_hook *
hook_find_pos (struct t_hook *hook)
{
    struct t_hook *ptr_hook;
    int rc_cmp;

    if (hook->type == HOOK_TYPE_COMMAND)
    {
        /* for command hook, sort on command name + priority */
        for (ptr_hook = weechat_hooks[hook->type]; ptr_hook;
             ptr_hook = ptr_hook->next_hook)
        {
            if (!ptr_hook->deleted)
            {
                rc_cmp = string_strcmp (HOOK_COMMAND(hook, command),
                                        HOOK_COMMAND(ptr_hook, command));
                if (rc_cmp < 0)
                    return ptr_hook;
                if ((rc_cmp == 0) && (hook->priority > ptr_hook->priority))
                    return ptr_hook;
            }
        }
    }
    else
    {
        /* for other types, sort on priority */
        for (ptr_hook = weechat_hooks[hook->type]; ptr_hook;
             ptr_hook = ptr_hook->next_hook)
        {
            if (!ptr_hook->deleted && (hook->priority > ptr_hook->priority))
                return ptr_hook;
        }
    }

    /* position not found, add at the end */
    return NULL;
}

/*
 * Add a hook to list.
 */

void
hook_add_to_list (struct t_hook *new_hook)
{
    struct t_hook *pos_hook;

    if (weechat_hooks[new_hook->type])
    {
        pos_hook = hook_find_pos (new_hook);
        if (pos_hook)
        {
            /* add hook before "pos_hook" */
            new_hook->prev_hook = pos_hook->prev_hook;
            new_hook->next_hook = pos_hook;
            if (pos_hook->prev_hook)
                (pos_hook->prev_hook)->next_hook = new_hook;
            else
                weechat_hooks[new_hook->type] = new_hook;
            pos_hook->prev_hook = new_hook;
        }
        else
        {
            /* add hook to end of list */
            new_hook->prev_hook = last_weechat_hook[new_hook->type];
            new_hook->next_hook = NULL;
            last_weechat_hook[new_hook->type]->next_hook = new_hook;
            last_weechat_hook[new_hook->type] = new_hook;
        }
    }
    else
    {
        new_hook->prev_hook = NULL;
        new_hook->next_hook = NULL;
        weechat_hooks[new_hook->type] = new_hook;
        last_weechat_hook[new_hook->type] = new_hook;
    }

    hooks_count[new_hook->type]++;
    hooks_count_total++;

    if (hook_callback_add[new_hook->type])
        (hook_callback_add[new_hook->type]) (new_hook);
}

/*
 * Remove a hook from list.
 */

void
hook_remove_from_list (struct t_hook *hook)
{
    struct t_hook *new_hooks;
    int type;

    type = hook->type;

    if (last_weechat_hook[hook->type] == hook)
        last_weechat_hook[hook->type] = hook->prev_hook;
    if (hook->prev_hook)
    {
        (hook->prev_hook)->next_hook = hook->next_hook;
        new_hooks = weechat_hooks[hook->type];
    }
    else
        new_hooks = hook->next_hook;

    if (hook->next_hook)
        (hook->next_hook)->prev_hook = hook->prev_hook;

    weechat_hooks[type] = new_hooks;

    hooks_count[type]--;
    hooks_count_total--;

    if (hook_callback_remove[hook->type])
        (hook_callback_remove[hook->type]) (hook);

    free (hook);
}

/*
 * Remove hooks marked as "deleted" from list.
 */

void
hook_remove_deleted (void)
{
    int type;
    struct t_hook *ptr_hook, *next_hook;

    if (real_delete_pending)
    {
        for (type = 0; type < HOOK_NUM_TYPES; type++)
        {
            ptr_hook = weechat_hooks[type];
            while (ptr_hook)
            {
                next_hook = ptr_hook->next_hook;

                if (ptr_hook->deleted)
                    hook_remove_from_list (ptr_hook);

                ptr_hook = next_hook;
            }
        }
        real_delete_pending = 0;
    }
}

/*
 * Initialize a new hook with default values.
 */

void
hook_init_data (struct t_hook *hook, struct t_weechat_plugin *plugin,
                int type, int priority,
                const void *callback_pointer, void *callback_data)
{
    hook->plugin = plugin;
    hook->subplugin = NULL;
    hook->type = type;
    hook->deleted = 0;
    hook->running = 0;
    hook->priority = priority;
    hook->callback_pointer = callback_pointer;
    hook->callback_data = callback_data;
    hook->hook_data = NULL;

    if (weechat_debug_core >= 2)
    {
        gui_chat_printf (NULL,
                         "debug: adding hook: type=%d (%s), plugin=\"%s\", "
                         "priority=%d",
                         hook->type,
                         hook_type_string[hook->type],
                         plugin_get_name (hook->plugin),
                         hook->priority);
    }
}

/*
 * Check if a hook pointer is valid.
 *
 * Return:
 *   1: hook exists
 *   0: hook does not exist
 */

int
hook_valid (struct t_hook *hook)
{
    int type;
    struct t_hook *ptr_hook;

    if (!hook)
        return 0;

    for (type = 0; type < HOOK_NUM_TYPES; type++)
    {
        for (ptr_hook = weechat_hooks[type]; ptr_hook;
             ptr_hook = ptr_hook->next_hook)
        {
            if (!ptr_hook->deleted && (ptr_hook == hook))
                return 1;
        }
    }

    /* hook not found */
    return 0;
}

/*
 * Start a hook exec.
 */

void
hook_exec_start (void)
{
    hook_exec_recursion++;
}

/*
 * End a hook_exec.
 */

void
hook_exec_end (void)
{
    if (hook_exec_recursion > 0)
        hook_exec_recursion--;

    if (hook_exec_recursion == 0)
        hook_remove_deleted ();
}

/*
 * Start execution of a hook callback.
 */

void
hook_callback_start (struct t_hook *hook, struct t_hook_exec_cb *hook_exec_cb)
{
    if (hook->type == HOOK_TYPE_COMMAND)
        hook->running++;
    else
        hook->running = 1;

    if (debug_long_callbacks > 0)
    {
        gettimeofday (&hook_exec_cb->start_time, NULL);
    }
    else
    {
        hook_exec_cb->start_time.tv_sec = 0;
        hook_exec_cb->start_time.tv_usec = 0;
    }
}

/*
 * End execution of a hook callback.
 */

void
hook_callback_end (struct t_hook *hook, struct t_hook_exec_cb *hook_exec_cb)
{
    struct timeval end_time;
    long long time_diff;
    char *str_diff;

    if (hook->type == HOOK_TYPE_COMMAND)
        hook->running--;
    else
        hook->running = 0;

    if ((debug_long_callbacks > 0)
        && (hook_exec_cb->start_time.tv_sec > 0))
    {
        gettimeofday (&end_time, NULL);
        time_diff = util_timeval_diff (&hook_exec_cb->start_time, &end_time);
        if (time_diff >= debug_long_callbacks)
        {
            str_diff = util_get_microseconds_string ((unsigned long long)time_diff);
            log_printf (
                _("debug: long callback: hook %s (%s), plugin: %s, "
                  "subplugin: %s, time elapsed: %s"),
                hook_type_string[hook->type],
                hook_get_description (hook),
                plugin_get_name (hook->plugin),
                (hook->subplugin) ? hook->subplugin : "-",
                str_diff);
            free (str_diff);
        }
    }
}

/*
 * Return description of hook.
 *
 * Note: result must be freed after use.
 */

char *
hook_get_description (struct t_hook *hook)
{
    return (hook_callback_get_desc[hook->type]) (hook);
}

/*
 * Set a hook property (string).
 */

void
hook_set (struct t_hook *hook, const char *property, const char *value)
{
    ssize_t num_written;
    char *error;
    long number;
    int rc;

    /* invalid hook? */
    if (!hook_valid (hook))
        return;

    if (!property)
        return;

    if (strcmp (property, "subplugin") == 0)
    {
        free (hook->subplugin);
        hook->subplugin = strdup (value);
    }
    else if (strcmp (property, "stdin") == 0)
    {
        if (!hook->deleted
            && (hook->type == HOOK_TYPE_PROCESS)
            && (HOOK_PROCESS(hook, child_write[HOOK_PROCESS_STDIN]) >= 0))
        {
            /* send data on child's stdin */
            num_written = write (HOOK_PROCESS(hook, child_write[HOOK_PROCESS_STDIN]),
                                 value, strlen (value));
            (void) num_written;
        }
    }
    else if (strcmp (property, "stdin_close") == 0)
    {
        if (!hook->deleted
            && (hook->type == HOOK_TYPE_PROCESS)
            && (HOOK_PROCESS(hook, child_write[HOOK_PROCESS_STDIN]) >= 0))
        {
            /* close stdin pipe */
            close (HOOK_PROCESS(hook, child_write[HOOK_PROCESS_STDIN]));
            HOOK_PROCESS(hook, child_write[HOOK_PROCESS_STDIN]) = -1;
        }
    }
    else if (strcmp (property, "signal") == 0)
    {
        if (!hook->deleted
            && (hook->type == HOOK_TYPE_PROCESS)
            && (HOOK_PROCESS(hook, child_pid) > 0))
        {
            error = NULL;
            number = strtol (value, &error, 10);
            if (!error || error[0])
            {
                /* not a number? look for signal by name */
                number = signal_search_name (value);
            }
            if (number >= 0)
            {
                rc = kill (HOOK_PROCESS(hook, child_pid), (int)number);
                if (rc < 0)
                {
                    gui_chat_printf (NULL,
                                     _("%sError sending signal %d to pid %d: %s"),
                                     gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
                                     (int)number,
                                     HOOK_PROCESS(hook, child_pid),
                                     strerror (errno));
                }
            }
        }
    }
    else if (strcmp (property, "keep_spaces_right") == 0)
    {
        if (!hook->deleted
            && ((hook->type == HOOK_TYPE_COMMAND)
                || (hook->type == HOOK_TYPE_COMMAND_RUN)))
        {
            error = NULL;
            number = strtol (value, &error, 10);
            if (error && !error[0])
            {
                switch (hook->type)
                {
                    case HOOK_TYPE_COMMAND:
                        HOOK_COMMAND(hook, keep_spaces_right) = (number) ? 1 : 0;
                        break;
                    case HOOK_TYPE_COMMAND_RUN:
                        HOOK_COMMAND_RUN(hook, keep_spaces_right) = (number) ? 1 : 0;
                        break;
                    default:
                        break;
                }
            }
        }
    }
}

/*
 * Callback used to clean a process (forked processes) by acknowledging its end.
 */

int
hook_timer_clean_process_cb (const void *pointer, void *data,
                             int remaining_calls)
{
    /* make C compiler happy */
    (void) pointer;
    (void) data;
    (void) remaining_calls;

    waitpid (*((pid_t *)data), NULL, WNOHANG);

    return WEECHAT_RC_OK;
}

/*
 * Schedule a cleanup timer to clean children (forked processes).
 */

void
hook_schedule_clean_process (pid_t pid)
{
    pid_t *temp_pid;

    /* temp_pid will be freed when the timer is removed */
    temp_pid = malloc (sizeof (*temp_pid));
    if (temp_pid)
    {
        *temp_pid = pid;
        hook_timer (NULL, 100, 0, 1,
                    &hook_timer_clean_process_cb, NULL, temp_pid);
    }
}

/*
 * Unhook something.
 */

void
unhook (struct t_hook *hook)
{
    /* invalid hook? */
    if (!hook_valid (hook))
        return;

    /* hook already deleted? */
    if (hook->deleted)
        return;

    if (weechat_debug_core >= 2)
    {
        gui_chat_printf (NULL,
                         "debug: removing hook: type=%d (%s), plugin=\"%s\"",
                         hook->type,
                         hook_type_string[hook->type],
                         plugin_get_name (hook->plugin));
    }

    /* free data specific to the hook */
    (hook_callback_free_data[hook->type]) (hook);

    /* free data common to all hooks */
    if (hook->subplugin)
    {
        free (hook->subplugin);
        hook->subplugin = NULL;
    }
    if (hook->callback_data)
    {
        free (hook->callback_data);
        hook->callback_data = NULL;
    }

    /* remove hook from list (if there's no hook exec pending) */
    if (hook_exec_recursion == 0)
    {
        hook_remove_from_list (hook);
    }
    else
    {
        /* there is one or more hook exec, then delete later */
        hook->deleted = 1;
        real_delete_pending = 1;
    }
}

/*
 * Unhook everything for a plugin/subplugin.
 */

void
unhook_all_plugin (struct t_weechat_plugin *plugin, const char *subplugin)
{
    int type;
    struct t_hook *ptr_hook, *next_hook;

    for (type = 0; type < HOOK_NUM_TYPES; type++)
    {
        ptr_hook = weechat_hooks[type];
        while (ptr_hook)
        {
            next_hook = ptr_hook->next_hook;
            if (ptr_hook->plugin == plugin)
            {
                if (!subplugin
                    || (ptr_hook->subplugin &&
                        strcmp (ptr_hook->subplugin, subplugin) == 0))
                {
                    unhook (ptr_hook);
                }
            }
            ptr_hook = next_hook;
        }
    }
}

/*
 * Unhook everything.
 */

void
unhook_all (void)
{
    int type;
    struct t_hook *ptr_hook, *next_hook;

    for (type = 0; type < HOOK_NUM_TYPES; type++)
    {
        ptr_hook = weechat_hooks[type];
        while (ptr_hook)
        {
            next_hook = ptr_hook->next_hook;
            unhook (ptr_hook);
            ptr_hook = next_hook;
        }
    }
}

/*
 * Return hdata for hook.
 */

struct t_hdata *
hook_hdata_hook_cb (const void *pointer, void *data, const char *hdata_name)
{
    struct t_hdata *hdata;
    int hook_type;
    char str_list[128];

    /* make C compiler happy */
    (void) pointer;
    (void) data;

    hdata = hdata_new (NULL, hdata_name, "prev_hook", "next_hook",
                       0, 0, NULL, NULL);
    if (hdata)
    {
        HDATA_VAR(struct t_hook, plugin, POINTER, 0, NULL, "plugin");
        HDATA_VAR(struct t_hook, subplugin, STRING, 0, NULL, NULL);
        HDATA_VAR(struct t_hook, type, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_hook, deleted, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_hook, running, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_hook, priority, INTEGER, 0, NULL, NULL);
        HDATA_VAR(struct t_hook, callback_pointer, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_hook, callback_data, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_hook, hook_data, POINTER, 0, NULL, NULL);
        HDATA_VAR(struct t_hook, prev_hook, POINTER, 0, NULL, hdata_name);
        HDATA_VAR(struct t_hook, next_hook, POINTER, 0, NULL, hdata_name);
        for (hook_type = 0; hook_type < HOOK_NUM_TYPES; hook_type++)
        {
            snprintf (str_list, sizeof (str_list),
                      "weechat_hooks_%s", hook_type_string[hook_type]);
            hdata_new_list (hdata, str_list, &weechat_hooks[hook_type],
                            WEECHAT_HDATA_LIST_CHECK_POINTERS);
            snprintf (str_list, sizeof (str_list),
                      "last_weechat_hook_%s", hook_type_string[hook_type]);
            hdata_new_list (hdata, str_list, &last_weechat_hook[hook_type], 0);
        }
    }
    return hdata;
}

/*
 * Add a hook in an infolist.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
hook_add_to_infolist_pointer (struct t_infolist *infolist, struct t_hook *hook)
{
    struct t_infolist_item *ptr_item;

    ptr_item = infolist_new_item (infolist);
    if (!ptr_item)
        return 0;

    if (!infolist_new_var_pointer (ptr_item, "pointer", hook))
        return 0;
    if (!infolist_new_var_pointer (ptr_item, "plugin", hook->plugin))
        return 0;
    if (!infolist_new_var_string (ptr_item, "plugin_name",
                                  (hook->plugin) ?
                                  hook->plugin->name : NULL))
        return 0;
    if (!infolist_new_var_string (ptr_item, "subplugin", hook->subplugin))
        return 0;
    if (!infolist_new_var_string (ptr_item, "type", hook_type_string[hook->type]))
        return 0;
    if (!infolist_new_var_integer (ptr_item, "deleted", hook->deleted))
        return 0;
    if (!infolist_new_var_integer (ptr_item, "running", hook->running))
        return 0;
    if (!infolist_new_var_integer (ptr_item, "priority", hook->priority))
        return 0;
    if (!infolist_new_var_pointer (ptr_item, "callback_pointer", (void *)hook->callback_pointer))
        return 0;
    if (!infolist_new_var_pointer (ptr_item, "callback_data", (void *)hook->callback_data))
        return 0;

    /* hook deleted? return only hook info above */
    if (hook->deleted)
        return 1;

    /* hook not deleted: add extra hook info */
    if (!(hook_callback_add_to_infolist[hook->type]) (ptr_item, hook))
        return 0;

    return 1;
}

/*
 * Add hooks of a type in an infolist.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
hook_add_to_infolist_type (struct t_infolist *infolist, int type,
                           const char *arguments)
{
    struct t_hook *ptr_hook;
    int match;

    for (ptr_hook = weechat_hooks[type]; ptr_hook;
         ptr_hook = ptr_hook->next_hook)
    {
        match = 1;
        if (arguments && !ptr_hook->deleted)
        {
            switch (ptr_hook->type)
            {
                case HOOK_TYPE_COMMAND:
                    match = string_match (HOOK_COMMAND(ptr_hook, command), arguments, 1);
                    break;
                default:
                    break;
            }
        }

        if (!match)
            continue;

        hook_add_to_infolist_pointer (infolist, ptr_hook);
    }

    return 1;
}

/*
 * Add hooks in an infolist.
 *
 * Argument "arguments" can be a hook type with optional comma + name after.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
hook_add_to_infolist (struct t_infolist *infolist, struct t_hook *pointer,
                      const char *arguments)
{
    const char *pos_arguments;
    char *type;
    int i, type_int;

    if (!infolist)
        return 0;

    if (pointer)
        return hook_add_to_infolist_pointer (infolist, pointer);

    type = NULL;
    pos_arguments = NULL;

    if (arguments && arguments[0])
    {
        pos_arguments = strchr (arguments, ',');
        if (pos_arguments)
        {
            type = string_strndup (arguments, pos_arguments - arguments);
            pos_arguments++;
        }
        else
            type = strdup (arguments);
    }

    type_int = (type) ? hook_search_type (type) : -1;

    for (i = 0; i < HOOK_NUM_TYPES; i++)
    {
        if ((type_int < 0) || (type_int == i))
            hook_add_to_infolist_type (infolist, i, pos_arguments);
    }

    free (type);

    return 1;
}

/*
 * Print hooks in WeeChat log file (usually for crash dump).
 */

void
hook_print_log (void)
{
    int type;
    struct t_hook *ptr_hook;

    for (type = 0; type < HOOK_NUM_TYPES; type++)
    {
        for (ptr_hook = weechat_hooks[type]; ptr_hook;
             ptr_hook = ptr_hook->next_hook)
        {
            log_printf ("");
            log_printf ("[hook (addr:%p)]", ptr_hook);
            log_printf ("  plugin. . . . . . . . . : %p ('%s')",
                        ptr_hook->plugin, plugin_get_name (ptr_hook->plugin));
            log_printf ("  subplugin . . . . . . . : '%s'", ptr_hook->subplugin);
            log_printf ("  type. . . . . . . . . . : %d (%s)",
                        ptr_hook->type, hook_type_string[ptr_hook->type]);
            log_printf ("  deleted . . . . . . . . : %d", ptr_hook->deleted);
            log_printf ("  running . . . . . . . . : %d", ptr_hook->running);
            log_printf ("  priority. . . . . . . . : %d", ptr_hook->priority);
            log_printf ("  callback_pointer. . . . : %p", ptr_hook->callback_pointer);
            log_printf ("  callback_data . . . . . : %p", ptr_hook->callback_data);
            if (ptr_hook->deleted)
                continue;

            (hook_callback_print_log[ptr_hook->type]) (ptr_hook);

            log_printf ("  prev_hook . . . . . . . : %p", ptr_hook->prev_hook);
            log_printf ("  next_hook . . . . . . . : %p", ptr_hook->next_hook);
        }
    }
}
