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 508782a913 shell: add pre-cmd log report object type and enable lastlog for it
If we failed or logged anything before we actually execute given command
in lvm shell, we couldn't report the log using lastlog command after.
This patch adds specific 'pre-cmd' log report object type to identify
such log messages and enables lastlog to report even this log.
2022-08-22 14:06:34 +02:00

371 lines
9.1 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_PRE_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_object_type(LOG_REPORT_OBJECT_TYPE_PRE_CMD);
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;
}
log_set_report_object_name_and_id(input, NULL);
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.");
goto report_log;
}
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_type(LOG_REPORT_OBJECT_TYPE_CMD);
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);
report_log:
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 */