1
0
mirror of https://github.com/weechat/weechat.git synced 2026-06-26 12:56:37 +02:00
Files
weechat/src/core/wee-hook.c
T
Sébastien Helleu 0dc7fbcb0c core: add options to customize commands on system signals, quit by default on SIGHUP when not running headless (closes #1595)
New options to customize behavior on signals received, with the default
behavior:

- weechat.signal.sighup: quit in normal mode, reload config in headless
- weechat.signal.sigquit: quit
- weechat.signal.sigterm: quit
- weechat.signal.sigusr1: no command executed by default
- weechat.signal.sigusr2: no command executed by default

The signals SIGUSR1 and SIGUSR2 are introduced by this commit, so it's now
possible to run commands when they are received.

The SIGHUP signal makes now WeeChat quit, it was the behavior before version
2.9 of WeeChat (see commit de1e61f7cd).
2021-03-16 18:47:31 +01:00

821 lines
22 KiB
C

/*
* wee-hook.c - WeeChat hooks management
*
* Copyright (C) 2003-2021 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-signal.h"
#include "wee-string.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 = 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));
}
}
}
}
}
/*
* 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);
}
}
}