/*
 * SPDX-FileCopyrightText: 2014-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/>.
 */

/* Exec command */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include "../weechat-plugin.h"
#include "exec.h"
#include "exec-buffer.h"
#include "exec-command.h"
#include "exec-config.h"


/*
 * Display a list of executed commands.
 */

void
exec_command_list (void)
{
    struct t_exec_cmd *ptr_exec_cmd;
    char str_elapsed[32], str_time1[256], str_time2[256];
    time_t elapsed_time;
    struct tm *local_time;

    weechat_printf (NULL, "");

    if (!exec_cmds)
    {
        weechat_printf (NULL, _("No command is running"));
        return;
    }

    weechat_printf (NULL, _("Commands:"));

    for (ptr_exec_cmd = exec_cmds; ptr_exec_cmd;
         ptr_exec_cmd = ptr_exec_cmd->next_cmd)
    {
        elapsed_time = (ptr_exec_cmd->end_time == 0) ?
            time (NULL) - ptr_exec_cmd->start_time :
            ptr_exec_cmd->end_time - ptr_exec_cmd->start_time;
        if (elapsed_time >= 3600)
        {
            snprintf (str_elapsed, sizeof (str_elapsed),
                      /* TRANSLATORS: format: hours + minutes, for example: 3h59 */
                      _("%dh%02d"),
                      elapsed_time / 3600,
                      elapsed_time % 3600);
        }
        else if (elapsed_time >= 60)
        {
            snprintf (str_elapsed, sizeof (str_elapsed),
                      /* TRANSLATORS: format: minutes + seconds, for example: 3m59 */
                      _("%dm%02d"),
                      elapsed_time / 60,
                      elapsed_time % 60);
        }
        else
        {
            snprintf (str_elapsed, sizeof (str_elapsed),
                      /* TRANSLATORS: format: seconds, for example: 59s */
                      _("%ds"),
                      elapsed_time);
        }
        if (ptr_exec_cmd->end_time == 0)
        {
            /* running command */
            weechat_printf (NULL,
                            /* TRANSLATORS: %s before "ago" is elapsed time, for example: "3m59" */
                            _("  %s%s%s %ld%s%s%s: %s\"%s%s%s\"%s (pid: %d, "
                              "started %s ago)"),
                            weechat_color (weechat_config_string (exec_config_color_flag_running)),
                            ">>",
                            weechat_color ("reset"),
                            ptr_exec_cmd->number,
                            (ptr_exec_cmd->name) ? " (" : "",
                            (ptr_exec_cmd->name) ? ptr_exec_cmd->name : "",
                            (ptr_exec_cmd->name) ? ")" : "",
                            weechat_color ("chat_delimiters"),
                            weechat_color ("reset"),
                            ptr_exec_cmd->command,
                            weechat_color ("chat_delimiters"),
                            weechat_color ("reset"),
                            ptr_exec_cmd->pid,
                            str_elapsed);
        }
        else
        {
            /* process has ended */
            local_time = localtime (&ptr_exec_cmd->start_time);
            if (strftime (str_time1, sizeof (str_time1),
                          "%Y-%m-%d %H:%M:%S", local_time) == 0)
                str_time1[0] = '\0';
            local_time = localtime (&ptr_exec_cmd->end_time);
            if (strftime (str_time2, sizeof (str_time2),
                          "%Y-%m-%d %H:%M:%S", local_time) == 0)
                str_time2[0] = '\0';
            weechat_printf (NULL,
                            "  %s%s%s %ld%s%s%s: %s\"%s%s%s\"%s (%s -> %s, %s)",
                            weechat_color (weechat_config_string (exec_config_color_flag_finished)),
                            "[]",
                            weechat_color ("reset"),
                            ptr_exec_cmd->number,
                            (ptr_exec_cmd->name) ? " (" : "",
                            (ptr_exec_cmd->name) ? ptr_exec_cmd->name : "",
                            (ptr_exec_cmd->name) ? ")" : "",
                            weechat_color ("chat_delimiters"),
                            weechat_color ("reset"),
                            ptr_exec_cmd->command,
                            weechat_color ("chat_delimiters"),
                            weechat_color ("reset"),
                            str_time1,
                            str_time2,
                            str_elapsed);
        }
    }
}

/*
 * Search a running command by id, and displays an error if command is not
 * found or not running anymore.
 *
 * Return the command found, or NULL if not found or not running.
 */

struct t_exec_cmd *
exec_command_search_running_id (const char *id)
{
    struct t_exec_cmd *ptr_exec_cmd;

    if (!id)
        return NULL;

    ptr_exec_cmd = exec_search_by_id (id);
    if (!ptr_exec_cmd)
    {
        weechat_printf (NULL, _("%s%s: command id \"%s\" not found"),
                        weechat_prefix ("error"), EXEC_PLUGIN_NAME, id);
        return NULL;
    }

    if (!ptr_exec_cmd->hook)
    {
        weechat_printf (NULL,
                        _("%s%s: command with id \"%s\" is not running anymore"),
                        weechat_prefix ("error"), EXEC_PLUGIN_NAME, id);
        return NULL;
    }

    return ptr_exec_cmd;
}

/*
 * Parse command options.
 *
 * Return:
 *   1: options parsed successfully
 *   0: error parsing options
 */

int
exec_command_parse_options (struct t_exec_cmd_options *cmd_options,
                            int argc, char **argv, int start_arg,
                            int set_command_index)
{
    int i, j, end, length, length_total, timeout;

    for (i = start_arg; i < argc; i++)
    {
        if (weechat_strcmp (argv[i], "-sh") == 0)
        {
            cmd_options->use_shell = 1;
        }
        else if (weechat_strcmp (argv[i], "-nosh") == 0)
        {
            cmd_options->use_shell = 0;
        }
        else if (weechat_strcmp (argv[i], "-bg") == 0)
        {
            cmd_options->detached = 1;
        }
        else if (weechat_strcmp (argv[i], "-nobg") == 0)
        {
            cmd_options->detached = 0;
        }
        else if (weechat_strcmp (argv[i], "-stdin") == 0)
        {
            cmd_options->pipe_stdin = 1;
        }
        else if (weechat_strcmp (argv[i], "-nostdin") == 0)
        {
            cmd_options->pipe_stdin = 0;
        }
        else if (weechat_strcmp (argv[i], "-buffer") == 0)
        {
            if (i + 1 >= argc)
                return 0;
            i++;
            cmd_options->ptr_buffer_name = argv[i];
            cmd_options->ptr_buffer = weechat_buffer_search ("==", argv[i]);
            if (cmd_options->ptr_buffer
                && (weechat_buffer_get_integer (cmd_options->ptr_buffer, "type") != 0))
            {
                /* only a buffer with formatted content is allowed */
                return 0;
            }
            if (!cmd_options->ptr_buffer)
                cmd_options->new_buffer = 1;
        }
        else if (weechat_strcmp (argv[i], "-l") == 0)
        {
            cmd_options->output_to_buffer = 0;
            cmd_options->output_to_buffer_exec_cmd = 0;
            cmd_options->new_buffer = 0;
        }
        else if (weechat_strcmp (argv[i], "-o") == 0)
        {
            cmd_options->output_to_buffer = 1;
            cmd_options->output_to_buffer_exec_cmd = 0;
            cmd_options->new_buffer = 0;
        }
        else if (weechat_strcmp (argv[i], "-oc") == 0)
        {
            cmd_options->output_to_buffer = 1;
            cmd_options->output_to_buffer_exec_cmd = 1;
            cmd_options->new_buffer = 0;
        }
        else if (weechat_strcmp (argv[i], "-oerr") == 0)
        {
            cmd_options->output_to_buffer_stderr = 1;
        }
        else if (weechat_strcmp (argv[i], "-n") == 0)
        {
            cmd_options->output_to_buffer = 0;
            cmd_options->output_to_buffer_exec_cmd = 0;
            cmd_options->new_buffer = 1;
        }
        else if (weechat_strcmp (argv[i], "-nf") == 0)
        {
            cmd_options->output_to_buffer = 0;
            cmd_options->output_to_buffer_exec_cmd = 0;
            cmd_options->new_buffer = 2;
        }
        else if (weechat_strcmp (argv[i], "-cl") == 0)
        {
            cmd_options->new_buffer_clear = 1;
        }
        else if (weechat_strcmp (argv[i], "-nocl") == 0)
        {
            cmd_options->new_buffer_clear = 0;
        }
        else if (weechat_strcmp (argv[i], "-sw") == 0)
        {
            cmd_options->switch_to_buffer = 1;
        }
        else if (weechat_strcmp (argv[i], "-nosw") == 0)
        {
            cmd_options->switch_to_buffer = 0;
        }
        else if (weechat_strcmp (argv[i], "-ln") == 0)
        {
            cmd_options->line_numbers = 1;
        }
        else if (weechat_strcmp (argv[i], "-noln") == 0)
        {
            cmd_options->line_numbers = 0;
        }
        else if (weechat_strcmp (argv[i], "-flush") == 0)
        {
            cmd_options->flush = 1;
        }
        else if (weechat_strcmp (argv[i], "-noflush") == 0)
        {
            cmd_options->flush = 0;
        }
        else if (weechat_strcmp (argv[i], "-color") == 0)
        {
            if (i + 1 >= argc)
                return 0;
            i++;
            cmd_options->color = exec_search_color (argv[i]);
            if (cmd_options->color < 0)
                return 0;
        }
        else if (weechat_strcmp (argv[i], "-rc") == 0)
        {
            cmd_options->display_rc = 1;
        }
        else if (weechat_strcmp (argv[i], "-norc") == 0)
        {
            cmd_options->display_rc = 0;
        }
        else if (weechat_strcmp (argv[i], "-timeout") == 0)
        {
            if (i + 1 >= argc)
                return 0;
            i++;
            if (!weechat_util_parse_int (argv[i], 10, &timeout))
                return 0;
            cmd_options->timeout = timeout;
        }
        else if (weechat_strcmp (argv[i], "-name") == 0)
        {
            if (i + 1 >= argc)
                return 0;
            i++;
            cmd_options->ptr_command_name = argv[i];
        }
        else if (weechat_strcmp (argv[i], "-pipe") == 0)
        {
            if (i + 1 >= argc)
                return 0;
            i++;
            free (cmd_options->pipe_command);
            cmd_options->pipe_command = NULL;
            if (argv[i][0] == '"')
            {
                /* search the ending double quote */
                length_total = 2;
                end = i;
                while (end < argc)
                {
                    length = strlen (argv[end]);
                    length_total += length + 1;
                    if ((length > 0) && (argv[end][length - 1] == '"'))
                        break;
                    end++;
                }
                if (end == argc)
                    return 0;
                cmd_options->pipe_command = malloc (length_total);
                if (!cmd_options->pipe_command)
                    return 0;
                cmd_options->pipe_command[0] = '\0';
                for (j = i; j <= end; j++)
                {
                    if (cmd_options->pipe_command[0])
                        strcat (cmd_options->pipe_command, " ");
                    strcat (cmd_options->pipe_command,
                            (j == i) ? argv[j] + 1 : argv[j]);
                }
                length = strlen (cmd_options->pipe_command);
                if (length > 0)
                    cmd_options->pipe_command[length - 1] = '\0';
                i = end;
            }
            else
                cmd_options->pipe_command = strdup (argv[i]);
        }
        else if (weechat_strcmp (argv[i], "-hsignal") == 0)
        {
            if (i + 1 >= argc)
                return 0;
            i++;
            free (cmd_options->hsignal);
            cmd_options->hsignal = NULL;
            cmd_options->hsignal = strdup (argv[i]);
        }
        else
        {
            if (set_command_index)
            {
                cmd_options->command_index = i;
                break;
            }
            else
                return 0;
        }
    }

    return 1;
}

/*
 * Run a command.
 *
 * Return:
 *   WEECHAT_RC_OK: command run successfully
 *   WEECHAT_RC_ERROR: error running command
 */

int
exec_command_run (struct t_gui_buffer *buffer,
                  int argc, char **argv, char **argv_eol, int start_arg)
{
    char str_buffer[512], *shell, *default_shell = "sh";
    struct t_exec_cmd *new_exec_cmd;
    struct t_exec_cmd_options cmd_options;
    struct t_hashtable *process_options;
    struct t_infolist *ptr_infolist;
    struct t_gui_buffer *ptr_new_buffer;

    shell = NULL;
    new_exec_cmd = NULL;
    process_options = NULL;

    /* parse command options */
    cmd_options.command_index = -1;
    cmd_options.use_shell = 0;
    cmd_options.detached = 0;
    cmd_options.pipe_stdin = 0;
    cmd_options.timeout = 0;
    cmd_options.ptr_buffer_name = NULL;
    cmd_options.ptr_buffer = buffer;
    cmd_options.output_to_buffer = 0;
    cmd_options.output_to_buffer_exec_cmd = 0;
    cmd_options.output_to_buffer_stderr = 0;
    cmd_options.new_buffer = 0;
    cmd_options.new_buffer_clear = 0;
    cmd_options.switch_to_buffer = 1;
    cmd_options.line_numbers = -1;
    cmd_options.flush = 1;
    cmd_options.color = EXEC_COLOR_AUTO;
    cmd_options.display_rc = 1;
    cmd_options.ptr_command_name = NULL;
    cmd_options.pipe_command = NULL;
    cmd_options.hsignal = NULL;

    /* parse default options */
    if (!exec_command_parse_options (&cmd_options,
                                     exec_config_cmd_num_options,
                                     exec_config_cmd_options,
                                     0, 0))
    {
        weechat_printf (NULL,
                        _("%s%s: invalid options in option "
                          "exec.command.default_options"),
                        weechat_prefix ("error"), EXEC_PLUGIN_NAME);
        goto error;
    }
    if (!exec_command_parse_options (&cmd_options, argc, argv, start_arg, 1))
        goto error;

    /* options "-bg" and "-o"/"-oc"/"-n" are incompatible */
    if (cmd_options.detached
        && (cmd_options.output_to_buffer || cmd_options.new_buffer))
    {
        goto error;
    }

    /* options "-pipe" and "-bg"/"-o"/"-oc"/"-n" are incompatible */
    if (cmd_options.pipe_command
        && (cmd_options.detached || cmd_options.output_to_buffer
            || cmd_options.new_buffer))
    {
        goto error;
    }

    /* command not found? */
    if (cmd_options.command_index < 0)
        goto error;

    new_exec_cmd = exec_add ();
    if (!new_exec_cmd)
        goto error;

    /* create hashtable for weechat_hook_process_hashtable() */
    process_options = weechat_hashtable_new (32,
                                             WEECHAT_HASHTABLE_STRING,
                                             WEECHAT_HASHTABLE_STRING,
                                             NULL, NULL);
    if (!process_options)
        goto error;

    /* automatically disable shell if we are downloading an URL */
    if (strncmp (argv_eol[cmd_options.command_index], "url:", 4) == 0)
        cmd_options.use_shell = 0;

    /* get default shell */
    if (cmd_options.use_shell)
    {
        shell = weechat_string_eval_expression (
            weechat_config_string (exec_config_command_shell),
            NULL, NULL, NULL);
        if (!shell || !shell[0])
        {
            free (shell);
            shell = strdup (default_shell);
        }
    }

    if (cmd_options.use_shell)
    {
        /* command will be: sh -c "command arguments..." */
        weechat_hashtable_set (process_options, "arg1", "-c");
        weechat_hashtable_set (process_options, "arg2",
                               argv_eol[cmd_options.command_index]);
    }
    if (cmd_options.pipe_stdin)
        weechat_hashtable_set (process_options, "stdin", "1");
    if (cmd_options.detached)
        weechat_hashtable_set (process_options, "detached", "1");
    if (cmd_options.flush)
        weechat_hashtable_set (process_options, "buffer_flush", "1");

    /* set variables in new command (before running the command) */
    new_exec_cmd->name = (cmd_options.ptr_command_name) ?
        strdup (cmd_options.ptr_command_name) : NULL;
    new_exec_cmd->command = strdup (argv_eol[cmd_options.command_index]);
    new_exec_cmd->detached = cmd_options.detached;

    if (!cmd_options.detached && !cmd_options.pipe_command
        && !cmd_options.hsignal)
    {
        if (cmd_options.ptr_buffer_name && !cmd_options.ptr_buffer)
        {
            /* output in a new buffer using given name */
            new_exec_cmd->output_to_buffer = 0;
            new_exec_cmd->output_to_buffer_exec_cmd = 0;
            snprintf (str_buffer, sizeof (str_buffer),
                      "exec.%s", cmd_options.ptr_buffer_name);
            ptr_new_buffer = exec_buffer_new (str_buffer,
                                              (cmd_options.new_buffer == 2),
                                              cmd_options.new_buffer_clear,
                                              cmd_options.switch_to_buffer);
            if (ptr_new_buffer)
            {
                new_exec_cmd->buffer_full_name =
                    strdup (weechat_buffer_get_string (ptr_new_buffer,
                                                       "full_name"));
            }
        }
        else if (cmd_options.new_buffer)
        {
            /* output in a new buffer using automatic name */
            if (new_exec_cmd->name)
            {
                snprintf (str_buffer, sizeof (str_buffer),
                          "exec.%s", new_exec_cmd->name);
            }
            else
            {
                snprintf (str_buffer, sizeof (str_buffer),
                          "exec.%ld", new_exec_cmd->number);
            }
            ptr_new_buffer = exec_buffer_new (str_buffer,
                                              (cmd_options.new_buffer == 2),
                                              cmd_options.new_buffer_clear,
                                              cmd_options.switch_to_buffer);
            if (ptr_new_buffer)
            {
                new_exec_cmd->buffer_full_name =
                    strdup (weechat_buffer_get_string (ptr_new_buffer,
                                                       "full_name"));
            }
        }
        else if (cmd_options.ptr_buffer)
        {
            new_exec_cmd->buffer_full_name =
                strdup (weechat_buffer_get_string (cmd_options.ptr_buffer,
                                                   "full_name"));
            if (cmd_options.switch_to_buffer)
                weechat_buffer_set (cmd_options.ptr_buffer, "display", "1");
        }
        if (cmd_options.ptr_buffer
            && (strcmp (weechat_buffer_get_string (cmd_options.ptr_buffer, "plugin"),
                        EXEC_PLUGIN_NAME) == 0))
        {
            cmd_options.output_to_buffer = 0;
            cmd_options.output_to_buffer_exec_cmd = 0;
            cmd_options.new_buffer = 1;
        }
    }
    new_exec_cmd->output_to_buffer = cmd_options.output_to_buffer;
    new_exec_cmd->output_to_buffer_exec_cmd = cmd_options.output_to_buffer_exec_cmd;
    new_exec_cmd->output_to_buffer_stderr = cmd_options.output_to_buffer_stderr;
    new_exec_cmd->line_numbers = (cmd_options.line_numbers < 0) ?
        cmd_options.new_buffer : cmd_options.line_numbers;
    new_exec_cmd->color = cmd_options.color;
    new_exec_cmd->display_rc = cmd_options.display_rc;
    new_exec_cmd->pipe_command = cmd_options.pipe_command;
    new_exec_cmd->hsignal = cmd_options.hsignal;

    /* execute the command */
    if (weechat_exec_plugin->debug >= 1)
    {
        weechat_printf (NULL, "%s: executing command: \"%s%s%s%s\"",
                        EXEC_PLUGIN_NAME,
                        (cmd_options.use_shell) ? shell : "",
                        (cmd_options.use_shell) ? " -c '" : "",
                        argv_eol[cmd_options.command_index],
                        (cmd_options.use_shell) ? "'" : "");
    }
    new_exec_cmd->hook = weechat_hook_process_hashtable (
        (cmd_options.use_shell) ? shell : argv_eol[cmd_options.command_index],
        process_options,
        cmd_options.timeout * 1000,
        &exec_process_cb,
        new_exec_cmd,
        NULL);

    if (new_exec_cmd->hook)
    {
        /* get PID of command */
        ptr_infolist = weechat_infolist_get ("hook", new_exec_cmd->hook, NULL);
        if (ptr_infolist)
        {
            if (weechat_infolist_next (ptr_infolist))
            {
                new_exec_cmd->pid = weechat_infolist_integer (ptr_infolist,
                                                              "child_pid");
            }
            weechat_infolist_free (ptr_infolist);
        }
    }
    else
    {
        exec_free (new_exec_cmd);
        weechat_printf (NULL,
                        _("%s%s: failed to run command \"%s\""),
                        weechat_prefix ("error"), EXEC_PLUGIN_NAME,
                        argv_eol[cmd_options.command_index]);
    }

    free (shell);
    weechat_hashtable_free (process_options);

    return WEECHAT_RC_OK;

error:
    free (shell);
    exec_free (new_exec_cmd);
    weechat_hashtable_free (process_options);

    return WEECHAT_RC_ERROR;
}

/*
 * Callback for command "/exec": manage executed commands.
 */

int
exec_command_exec (const void *pointer, void *data,
                   struct t_gui_buffer *buffer, int argc,
                   char **argv, char **argv_eol)
{
    int i, count;
    char *text;
    struct t_exec_cmd *ptr_exec_cmd, *ptr_next_exec_cmd;

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

    /* list running commands */
    if ((argc == 1)
        || ((argc == 2) && (weechat_strcmp (argv[1], "-list") == 0)))
    {
        exec_command_list ();
        return WEECHAT_RC_OK;
    }

    /* send text to a running process */
    if (weechat_strcmp (argv[1], "-in") == 0)
    {
        WEECHAT_COMMAND_MIN_ARGS(4, argv[1]);
        ptr_exec_cmd = exec_command_search_running_id (argv[2]);
        if (ptr_exec_cmd && ptr_exec_cmd->hook)
        {
            if (weechat_asprintf (&text, "%s\n", argv_eol[3]) >= 0)
            {
                weechat_hook_set (ptr_exec_cmd->hook, "stdin", text);
                free (text);
            }
        }
        return WEECHAT_RC_OK;
    }

    /* send text to a running process (if given), then close stdin */
    if (weechat_strcmp (argv[1], "-inclose") == 0)
    {
        WEECHAT_COMMAND_MIN_ARGS(3, argv[1]);
        ptr_exec_cmd = exec_command_search_running_id (argv[2]);
        if (ptr_exec_cmd && ptr_exec_cmd->hook)
        {
            if (argc > 3)
            {
                if (weechat_asprintf (&text, "%s\n", argv_eol[3]) >= 0)
                {
                    weechat_hook_set (ptr_exec_cmd->hook, "stdin", text);
                    free (text);
                }
            }
            weechat_hook_set (ptr_exec_cmd->hook, "stdin_close", "1");
        }
        return WEECHAT_RC_OK;
    }

    /* send a signal to a running process */
    if (weechat_strcmp (argv[1], "-signal") == 0)
    {
        WEECHAT_COMMAND_MIN_ARGS(4, argv[1]);
        ptr_exec_cmd = exec_command_search_running_id (argv[2]);
        if (ptr_exec_cmd)
            weechat_hook_set (ptr_exec_cmd->hook, "signal", argv[3]);
        return WEECHAT_RC_OK;
    }

    /* send a KILL signal to a running process */
    if (weechat_strcmp (argv[1], "-kill") == 0)
    {
        WEECHAT_COMMAND_MIN_ARGS(3, argv[1]);
        ptr_exec_cmd = exec_command_search_running_id (argv[2]);
        if (ptr_exec_cmd)
            weechat_hook_set (ptr_exec_cmd->hook, "signal", "kill");
        return WEECHAT_RC_OK;
    }

    /* send a KILL signal to all running processes */
    if (weechat_strcmp (argv[1], "-killall") == 0)
    {
        for (ptr_exec_cmd = exec_cmds; ptr_exec_cmd;
             ptr_exec_cmd = ptr_exec_cmd->next_cmd)
        {
            if (ptr_exec_cmd->hook)
            {
                weechat_hook_set (ptr_exec_cmd->hook, "signal", "kill");
            }
        }
        return WEECHAT_RC_OK;
    }

    /* set a hook property */
    if (weechat_strcmp (argv[1], "-set") == 0)
    {
        WEECHAT_COMMAND_MIN_ARGS(5, argv[1]);
        ptr_exec_cmd = exec_command_search_running_id (argv[2]);
        if (ptr_exec_cmd)
            weechat_hook_set (ptr_exec_cmd->hook, argv[3], argv_eol[4]);
        return WEECHAT_RC_OK;
    }

    /* delete terminated command(s) */
    if (weechat_strcmp (argv[1], "-del") == 0)
    {
        WEECHAT_COMMAND_MIN_ARGS(3, argv[1]);
        if (weechat_strcmp (argv[2], "-all") == 0)
        {
            count = 0;
            ptr_exec_cmd = exec_cmds;
            while (ptr_exec_cmd)
            {
                ptr_next_exec_cmd = ptr_exec_cmd->next_cmd;
                if (!ptr_exec_cmd->hook)
                {
                    exec_free (ptr_exec_cmd);
                    count++;
                }
                ptr_exec_cmd = ptr_next_exec_cmd;
            }
            weechat_printf (NULL, _("%d commands removed"), count);
        }
        else
        {
            for (i = 2; i < argc; i++)
            {
                ptr_exec_cmd = exec_search_by_id (argv[i]);
                if (ptr_exec_cmd)
                {
                    if (ptr_exec_cmd->hook)
                    {
                        weechat_printf (NULL,
                                        _("%s%s: command with id \"%s\" is still "
                                          "running"),
                                        weechat_prefix ("error"), EXEC_PLUGIN_NAME,
                                        argv[i]);
                    }
                    else
                    {
                        exec_free (ptr_exec_cmd);
                        weechat_printf (NULL,
                                        _("Command \"%s\" removed"), argv[i]);
                    }
                }
                else
                {
                    weechat_printf (NULL,
                                    _("%s%s: command id \"%s\" not found"),
                                    weechat_prefix ("error"), EXEC_PLUGIN_NAME,
                                    argv[i]);
                }
            }
        }
        return WEECHAT_RC_OK;
    }

    return exec_command_run (buffer, argc, argv, argv_eol, 1);
}

/*
 * Hook exec commands.
 */

void
exec_command_init (void)
{
    weechat_hook_command (
        "exec",
        N_("execute external commands"),
        /* TRANSLATORS: only text between angle brackets (eg: "<name>") may be translated */
        N_("-list"
           " || [-sh|-nosh] [-bg|-nobg] [-stdin|-nostdin] [-buffer <name>] "
           "[-l|-o|-oc|-n|-nf] [-oerr] [-cl|-nocl] [-sw|-nosw] [-ln|-noln] "
           "[-flush|-noflush] [-color ansi|auto|irc|weechat|strip] [-rc|-norc] "
           "[-timeout <timeout>] [-name <name>] [-pipe <command>] "
           "[-hsignal <name>] <command>"
           " || -in <id> <text>"
           " || -inclose <id> [<text>]"
           " || -signal <id> <signal>"
           " || -kill <id>"
           " || -killall"
           " || -set <id> <property> <value>"
           " || -del <id>|-all [<id>...]"),
        WEECHAT_CMD_ARGS_DESC(
            N_("raw[-list]: list commands"),
            N_("raw[-sh]: use the shell to execute the command, many commands can "
               "be piped (WARNING: use this option ONLY if all arguments are "
               "safe, see option -nosh)"),
            N_("raw[-nosh]: do not use the shell to execute the command (required if "
               "the command has some unsafe data, for example the content of a "
               "message from another user) (default)"),
            N_("raw[-bg]: run process in background: do not display process output "
               "neither return code (not compatible with options "
               "-o/-oc/-n/-nf/-pipe/-hsignal)"),
            N_("raw[-nobg]: catch process output and display return code (default)"),
            N_("raw[-stdin]: create a pipe for sending data to the process (with "
               "/exec -in/-inclose)"),
            N_("raw[-nostdin]: do not create a pipe for stdin (default)"),
            N_("raw[-buffer]: display/send output of command on this buffer (if the "
               "buffer is not found, a new buffer with name \"exec.exec.xxx\" is "
               "created)"),
            N_("raw[-l]: display locally output of command on buffer (default)"),
            N_("raw[-o]: send output of command to the buffer without executing "
               "commands (not compatible with options -bg/-pipe/-hsignal)"),
            N_("raw[-oc]: send output of command to the buffer and execute commands "
               "(lines starting with \"/\" or another custom command char) "
               "(not compatible with options -bg/-pipe/-hsignal)"),
            N_("raw[-n]: display output of command in a new buffer (not compatible "
               "with options -bg/-pipe/-hsignal)"),
            N_("raw[-nf]: display output of command in a new buffer with free "
               "content (no word-wrap, no limit on number of lines) (not compatible "
               "with options -bg/-pipe/-hsignal)"),
            N_("raw[-oerr]: also send stderr (error output) to the buffer (can be "
               "used only with options -o and -oc)"),
            N_("raw[-cl]: clear the new buffer before displaying output"),
            N_("raw[-nocl]: append to the new buffer without clear (default)"),
            N_("raw[-sw]: switch to the output buffer (default)"),
            N_("raw[-nosw]: don't switch to the output buffer"),
            N_("raw[-ln]: display line numbers (default in new buffer only)"),
            N_("raw[-noln]: don't display line numbers"),
            N_("raw[-flush]: display output of command in real time (default)"),
            N_("raw[-noflush]: display output of command after its end"),
            N_("raw[-color]: action on ANSI colors in output:"),
            N_("> raw[ansi]: keep ANSI codes as-is"),
            N_("> raw[auto]: convert ANSI colors to WeeChat/IRC (default)"),
            N_("> raw[irc]: convert ANSI colors to IRC colors"),
            N_("> raw[weechat]: convert ANSI colors to WeeChat colors"),
            N_("> raw[strip]: remove ANSI colors"),
            N_("raw[-rc]: display return code (default)"),
            N_("raw[-norc]: don't display return code"),
            N_("raw[-timeout]: set a timeout for the command (in seconds)"),
            N_("raw[-name]: set a name for the command (to name it later with /exec)"),
            N_("raw[-pipe]: send the output to a WeeChat/plugin command (line by "
               "line); if there are spaces in command/arguments, enclose them with "
               "double quotes; variable $line is replaced by the line (by default "
               "the line is added after the command, separated by a space) "
               "(not compatible with options -bg/-o/-oc/-n/-nf)"),
            N_("raw[-hsignal]: send the output as a hsignal (to be used for example in "
               "a trigger) (not compatible with options -bg/-o/-oc/-n/-nf)"),
            N_("command: the command to execute; if beginning with \"url:\", the "
               "shell is disabled and the content of URL is downloaded and sent as "
               "output"),
            N_("id: command identifier: either its number or name (if set "
               "with \"-name xxx\")"),
            N_("raw[-in]: send text on standard input of process"),
            N_("raw[-inclose]: same as -in, but stdin is closed after (and text is "
               "optional: without text, the stdin is just closed)"),
            N_("raw[-signal]: send a signal to the process; the signal can be an integer "
               "or one of these names: hup, int, quit, kill, term, usr1, usr2"),
            N_("raw[-kill]: alias of \"-signal <id> kill\""),
            N_("raw[-killall]: kill all running processes"),
            N_("raw[-set]: set a hook property (see function hook_set in plugin API "
               "reference)"),
            N_("property: hook property"),
            N_("value: new value for hook property"),
            N_("raw[-del]: delete a terminated command"),
            N_("raw[-all]: delete all terminated commands"),
            "",
            N_("Default options can be set in the option "
               "exec.command.default_options."),
            "",
            N_("Examples:"),
            AI("  /exec -n ls -l /tmp"),
            AI("  /exec -sh -n ps xu | grep weechat"),
            AI("  /exec -n -norc url:https://pastebin.com/raw.php?i=xxxxxxxx"),
            AI("  /exec -nf -noln links -dump "
               "https://weechat.org/files/doc/weechat/devel/weechat_user.en.html"),
            AI("  /exec -o uptime"),
            AI("  /exec -pipe \"/print Machine uptime:\" uptime"),
            AI("  /exec -n tail -f /var/log/messages"),
            AI("  /exec -kill 0")),
        "-list"
        " || -sh|-nosh|-bg|-nobg|-stdin|-nostdin|-buffer|-l|-o|-n|-nf|"
        "-cl|-nocl|-sw|-nosw|-ln|-noln|-flush|-noflush|-color|-timeout|-name|"
        "-pipe|-hsignal|%*"
        " || -in|-inclose|-signal|-kill %(exec_commands_ids)"
        " || -killall"
        " || -set %(exec_commands_ids) stdin|stdin_close|signal"
        " || -del %(exec_commands_ids)|-all %(exec_commands_ids)|%*",
        &exec_command_exec, NULL, NULL);
}
