1
0
mirror of git://sourceware.org/git/lvm2.git synced 2024-12-21 13:34:40 +03:00
lvm2/tools/lvm.c
Peter Rajnoha 2fa9916493 shell: also output error message about max number of args hit with JSON format
If using JSON format for lvm shell's output, the error message about
exceeding the maximum number of arguments was not reported on output if
this condition was ever hit.

This is because the JSON format (as well as any other future format)
requires extra formatting compared to "basic" format and so it also
requires extra calls when it comes to reporting. The report needs to
be added to a report group and then popped and put on output with
specialized "dm_report_group_output_and_pop_all".

This "output and pop" is normally executed after we execute the command
in the lvm shell. When we didn't get to the command exection at all because
some precondition was not met (like hitting the limit for the number of
arguments for the command here), we skipped this important call and
so there was no log report output.

Right now, it's only this exact error message for which we need to call
"output and pop" directly, all the other error messages are about
initializing and setting the log report itself which we can't report
obviously.

Before this patch:

  lvm> pvs 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
  lvm>

With this patch applied:

  lvm> pvs 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
  {
      "log": [
          {"log_seq_num":"1", "log_type":"error", "log_context":"shell", "log_object_type":"cmd", "log_object_name":"", "log_object_id":"", "log_object_group":"", "log_object_group_id":"", "log_message":"Too many arguments, sorry.", "log_errno":"-1", "log_ret_code":"0"}
      ]
  }

If there's any other error message in the future before we execute the
command itself, we also need to call the "output and pop" directly.
2022-08-08 15:46:52 +02:00

368 lines
9.0 KiB
C

/*
* Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
* Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved.
*
* This file is part of LVM2.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License v.2.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "tools.h"
#include "lvm2cmdline.h"
int main(int argc, char **argv)
{
/* coverity[os_cmd_sink] intentionally passing argv */
return lvm2_main(argc, argv);
}
#if defined(READLINE_SUPPORT) || defined(EDITLINE_SUPPORT)
# ifdef READLINE_SUPPORT
# include <readline/readline.h>
# include <readline/history.h>
# ifndef HAVE_RL_COMPLETION_MATCHES
# define rl_completion_matches(a, b) completion_matches((char *)a, b)
# define rl_completion_func_t CPPFunction
# endif
# elif defined(EDITLINE_SUPPORT)
# include <editline/readline.h>
# endif
static struct cmdline_context *_cmdline;
/* List matching commands */
static char *_list_cmds(const char *text, int state)
{
static int i = 0;
static size_t len = 0;
/* Initialise if this is a new completion attempt */
if (!state) {
i = 0;
len = strlen(text);
}
while (i < _cmdline->num_command_names)
if (!strncmp(text, _cmdline->command_names[i++].name, len))
return strdup(_cmdline->command_names[i - 1].name);
return NULL;
}
/* List matching arguments */
static char *_list_args(const char *text, int state)
{
static int match_no = 0;
static size_t len = 0;
static struct command_name *cname;
/* Initialise if this is a new completion attempt */
if (!state) {
char *s = rl_line_buffer;
int j;
match_no = 0;
cname = NULL;
len = strlen(text);
/* Find start of first word in line buffer */
while (isspace(*s))
s++;
/* Look for word in list of command names */
for (j = 0; j < _cmdline->num_command_names; j++) {
const char *p;
char *q = s;
p = _cmdline->command_names[j].name;
while (*p == *q) {
p++;
q++;
}
if ((!*p) && *q == ' ') {
cname = _cmdline->command_names + j;
break;
}
}
}
if (!cname)
return NULL;
/* Short form arguments */
if (len < 3) {
while (match_no < cname->num_args) {
char s[3];
char c;
if (!(c = (_cmdline->opt_names +
cname->valid_args[match_no++])->short_opt))
continue;
sprintf(s, "-%c", c);
if (!strncmp(text, s, len))
return strdup(s);
}
}
/* Long form arguments */
if (match_no < cname->num_args)
match_no = cname->num_args;
while (match_no - cname->num_args < cname->num_args) {
const char *l;
l = (_cmdline->opt_names +
cname->valid_args[match_no++ - cname->num_args])->long_opt;
if (*(l + 2) && !strncmp(text, l, len))
return strdup(l);
}
return NULL;
}
/* Custom completion function */
static char **_completion(const char *text, int start_pos,
int end_pos __attribute__((unused)))
{
char **match_list = NULL;
int p = 0;
while (isspace((int) *(rl_line_buffer + p)))
p++;
/* First word should be one of our commands */
if (start_pos == p)
match_list = rl_completion_matches(text, _list_cmds);
else if (*text == '-')
match_list = rl_completion_matches(text, _list_args);
/* else other args */
/* No further completion */
rl_attempted_completion_over = 1;
return match_list;
}
static int _hist_file(char *buffer, size_t size)
{
char *e = getenv("HOME");
if (dm_snprintf(buffer, size, "%s/.lvm_history", e) < 0) {
log_error("$HOME/.lvm_history: path too long");
return 0;
}
return 1;
}
static void _read_history(struct cmd_context *cmd)
{
char hist_file[PATH_MAX];
if (!_hist_file(hist_file, sizeof(hist_file)))
return;
if (read_history(hist_file))
log_very_verbose("Couldn't read history from %s.", hist_file);
stifle_history(find_config_tree_int(cmd, shell_history_size_CFG, NULL));
}
static void _write_history(void)
{
char hist_file[PATH_MAX];
if (!_hist_file(hist_file, sizeof(hist_file)))
return;
if (write_history(hist_file))
log_very_verbose("Couldn't write history to %s.", hist_file);
}
static int _log_shell_command_status(struct cmd_context *cmd, int ret_code)
{
log_report_t log_state;
if (!cmd->cmd_report.log_rh)
return 1;
log_state = log_get_report_state();
return report_cmdlog(cmd->cmd_report.log_rh, REPORT_OBJECT_CMDLOG_NAME,
log_get_report_context_name(log_state.context),
log_get_report_object_type_name(log_state.object_type),
log_state.object_name, log_state.object_id,
log_state.object_group, log_state.object_group_id,
ret_code == ECMD_PROCESSED ? REPORT_OBJECT_CMDLOG_SUCCESS
: REPORT_OBJECT_CMDLOG_FAILURE,
stored_errno(), ret_code);
}
static void _discard_log_report_content(struct cmd_context *cmd)
{
if (cmd->cmd_report.log_rh)
dm_report_destroy_rows(cmd->cmd_report.log_rh);
}
int lvm_shell(struct cmd_context *cmd, struct cmdline_context *cmdline)
{
log_report_t saved_log_report_state = log_get_report_state();
char *orig_command_log_selection = NULL;
int is_lastlog_cmd = 0, argc, ret, i;
char *input = NULL, *args[MAX_ARGS], **argv;
rl_readline_name = "lvm";
rl_attempted_completion_function = (rl_completion_func_t *) _completion;
_read_history(cmd);
_cmdline = cmdline;
cmd->is_interactive = 1;
if (!report_format_init(cmd))
return_ECMD_FAILED;
orig_command_log_selection = dm_pool_strdup(cmd->libmem, find_config_tree_str(cmd, log_command_log_selection_CFG, NULL));
log_set_report_context(LOG_REPORT_CONTEXT_SHELL);
log_set_report_object_type(LOG_REPORT_OBJECT_TYPE_CMD);
while (1) {
/*
* Note: If we need to output the log report before we get to the dm_report_group_output_and_pop_all
* at the end of this loop, like hitting a failure situation before we execute the command itself,
* don't forget to directly call dm_report_group_output_and_pop_all, otherwise no log meesage will
* appear on output (for output formats other than 'basic').
*
* Obviously, you can't output the 'log report' if the error is in initializing or setting
* the report itself. In this case, we can only return an error code, but no message.
*/
report_reset_cmdlog_seqnum();
if (cmd->cmd_report.log_rh) {
/*
* If previous command was lastlog, reset log report selection to
* its original value as set by log/command_log_selection config setting.
*/
if (is_lastlog_cmd &&
!dm_report_set_selection(cmd->cmd_report.log_rh, orig_command_log_selection))
log_error("Failed to reset log report selection.");
}
log_set_report(cmd->cmd_report.log_rh);
log_set_report_object_name_and_id(NULL, NULL);
free(input);
input = readline("lvm> ");
/* EOF */
if (!input) {
_discard_log_report_content(cmd);
/* readline sends prompt to stdout */
printf("\n");
break;
}
/* empty line */
if (!*input) {
_discard_log_report_content(cmd);
continue;
}
add_history(input);
for (i = 0; i < MAX_ARGS; i++)
args[i] = NULL;
argv = args;
if (lvm_split(input, &argc, argv, MAX_ARGS) == MAX_ARGS) {
_discard_log_report_content(cmd);
log_error("Too many arguments, sorry.");
dm_report_group_output_and_pop_all(cmd->cmd_report.report_group);
continue;
}
if (!argc) {
_discard_log_report_content(cmd);
continue;
}
if (!strcmp(argv[0], "lvm")) {
argv++;
argc--;
}
if (!argc) {
_discard_log_report_content(cmd);
continue;
}
log_set_report_object_name_and_id(argv[0], NULL);
is_lastlog_cmd = !strcmp(argv[0], "lastlog");
if (!is_lastlog_cmd)
_discard_log_report_content(cmd);
if (!strcmp(argv[0], "quit") || !strcmp(argv[0], "exit")) {
_discard_log_report_content(cmd);
remove_history(history_length - 1);
log_error("Exiting.");
break;
}
ret = lvm_run_command(cmd, argc, argv);
if (ret == ENO_SUCH_CMD)
log_error("No such command '%s'. Try 'help'.",
argv[0]);
if ((ret != ECMD_PROCESSED) && !error_message_produced()) {
log_debug(INTERNAL_ERROR "Failed command did not use log_error");
log_error("Command failed with status code %d.", ret);
}
_write_history();
if (!is_lastlog_cmd)
_log_shell_command_status(cmd, ret);
log_set_report(NULL);
dm_report_group_output_and_pop_all(cmd->cmd_report.report_group);
if (cmd->cmd_report.log_rh &&
!(dm_report_group_push(cmd->cmd_report.report_group,
cmd->cmd_report.log_rh,
(void *) cmd->cmd_report.log_name))) {
log_set_report(NULL);
log_error("Failed to add log report.");
break;
}
}
log_restore_report_state(saved_log_report_state);
cmd->is_interactive = 0;
free(input);
if (cmd->cmd_report.report_group) {
if (!dm_report_group_destroy(cmd->cmd_report.report_group))
stack;
cmd->cmd_report.report_group = NULL;
}
if (cmd->cmd_report.log_rh) {
dm_report_free(cmd->cmd_report.log_rh);
cmd->cmd_report.report_group = NULL;
}
return 0;
}
#endif /* READLINE_SUPPORT || EDITLINE_SUPPORT */