weechat/src/core/wee-hook.c

821 lines
22 KiB
C

/*
* wee-hook.c - WeeChat hooks management
*
* Copyright (C) 2003-2020 Sébastien Helleu <flashcode@flashtux.org>
*
* 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/>.
*/
#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 <errno.h>
#include "weechat.h"
#include "wee-hook.h"
#include "wee-hashtable.h"
#include "wee-infolist.h"
#include "wee-log.h"
#include "wee-string.h"
#include "wee-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" };
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 };
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 };
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 };
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 };
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 };
/*
* Initializes hooks.
*/
void
hook_init ()
{
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
}
/*
* Searches for a hook type.
*
* Returns 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;
}
/*
* Searches 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_strcasecmp (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;
}
/*
* Adds 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);
}
/*
* Removes 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);
}
/*
* Removes hooks marked as "deleted" from list.
*/
void
hook_remove_deleted ()
{
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;
}
}
/*
* Extracts priority and name from a string.
*
* String can be:
* - a simple name like "test":
* => priority = 1000 (default), name = "test"
* - a priority + "|" + name, like "500|test":
* => priority = 500, name = "test"
*/
void
hook_get_priority_and_name (const char *string,
int *priority, const char **name)
{
char *pos, *str_priority, *error;
long number;
if (priority)
*priority = HOOK_PRIORITY_DEFAULT;
if (name)
*name = string;
pos = strchr (string, '|');
if (pos)
{
str_priority = string_strndup (string, pos - string);
if (str_priority)
{
error = NULL;
number = strtol (str_priority, &error, 10);
if (error && !error[0])
{
if (priority)
*priority = number;
if (name)
*name = pos + 1;
}
free (str_priority);
}
}
}
/*
* Initializes 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);
}
}
/*
* Checks if a hook pointer is valid.
*
* Returns:
* 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;
}
/*
* Starts a hook exec.
*/
void
hook_exec_start ()
{
hook_exec_recursion++;
}
/*
* Ends a hook_exec.
*/
void
hook_exec_end ()
{
if (hook_exec_recursion > 0)
hook_exec_recursion--;
if (hook_exec_recursion == 0)
hook_remove_deleted ();
}
/*
* Sets 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 (string_strcasecmp (property, "subplugin") == 0)
{
if (hook->subplugin)
free (hook->subplugin);
hook->subplugin = strdup (value);
}
else if (string_strcasecmp (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 (string_strcasecmp (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 (string_strcasecmp (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 = util_signal_search (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));
}
}
}
}
}
/*
* Unhooks 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;
}
}
/*
* Unhooks 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;
}
}
}
/*
* Unhooks everything.
*/
void
unhook_all ()
{
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;
}
}
}
/*
* Adds a hook in an infolist.
*
* Returns:
* 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;
}
/*
* Adds hooks of a type in an infolist.
*
* Returns:
* 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, 0);
break;
default:
break;
}
}
if (!match)
continue;
hook_add_to_infolist_pointer (infolist, ptr_hook);
}
return 1;
}
/*
* Adds hooks in an infolist.
*
* Argument "arguments" can be a hook type with optional comma + name after.
*
* Returns:
* 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);
}
if (type)
free (type);
return 1;
}
/*
* Prints hooks in WeeChat log file (usually for crash dump).
*/
void
hook_print_log ()
{
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:0x%lx)]", ptr_hook);
log_printf (" plugin. . . . . . . . . : 0x%lx ('%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. . . . : 0x%lx", ptr_hook->callback_pointer);
log_printf (" callback_data . . . . . : 0x%lx", ptr_hook->callback_data);
if (ptr_hook->deleted)
continue;
(hook_callback_print_log[ptr_hook->type]) (ptr_hook);
log_printf (" prev_hook . . . . . . . : 0x%lx", ptr_hook->prev_hook);
log_printf (" next_hook . . . . . . . : 0x%lx", ptr_hook->next_hook);
}
}
}