open-cas-linux/casadm/argp.c
Michal Mielewczyk 46775aa884 casadm: Exit status 0 for blocked commands
Signed-off-by: Michal Mielewczyk <michal.mielewczyk@huawei.com>
2024-09-26 17:02:44 +02:00

820 lines
19 KiB
C

/*
* Copyright(c) 2012-2021 Intel Corporation
* Copyright(c) 2024 Huawei Technologies
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdbool.h>
#include <string.h>
#include "argp.h"
#include "cas_lib.h"
#include "cas_lib_utils.h"
#include <sys/time.h>
#include <sys/stat.h>
#define PADDING " "
#define MAX_OPT_HELP_LEN 30
extern cas_printf_t cas_printf;
static int is_su_requied(const cli_command* commands, int cmd)
{
return commands[cmd].flags & CLI_SU_REQUIRED;
}
static int is_command_hidden(const cli_command* commands, int cmd)
{
return commands[cmd].flags & CLI_COMMAND_HIDDEN;
}
static int is_command_blocked(const cli_command* commands, int cmd)
{
return commands[cmd].flags & CLI_COMMAND_BLOCKED;
}
static void print_short_usage(const app *app_values)
{
cas_printf(LOG_INFO, "Usage: %s %s\n", app_values->name, app_values->info);
}
static void print_info(const app *app_values)
{
cas_printf(LOG_INFO, "Try `%s --help | -H' for more information.\n", app_values->name);
}
char *get_short_name_string(const char short_name, char *buf)
{
if (short_name) {
snprintf(buf, 3, "-%c", short_name);
} else {
buf[0] = 0;
}
return buf;
}
char *command_name_with_slash(char *buf, size_t buf_size, char short_name, char *long_name) {
if (short_name) {
snprintf(buf, buf_size, "-%c/--%s", short_name, long_name);
} else {
snprintf(buf, buf_size, "--%s", long_name);
}
return buf;
}
char *command_name_in_brackets(char *buf, size_t buf_size, char short_name, char *long_name) {
if (short_name) {
snprintf(buf, buf_size, "--%s (-%c)", long_name, short_name);
} else {
snprintf(buf, buf_size, "--%s", long_name);
}
return buf;
}
void print_options_usage(int log_level, cli_option* options,
const char *separator, int (*view)(cli_option* options, int flag),
int flag)
{
int print_separator = 0;
int i;
if (NULL == options) {
return;
}
for (i = 0; options[i].long_name != NULL; ++i) {
if (0 == view(&options[i], flag)) {
continue;
}
if (print_separator) {
/* Separator */
cas_printf(log_level, "%s", separator);
}
print_separator = 1;
/* Long option name */
cas_printf(log_level, "--%s", options[i].long_name);
/* Parameter */
if (options[i].arg != NULL) {
cas_printf(log_level, " <%s>",
options[i].arg);
}
}
}
void print_command_header(const app *app_values, const cli_command *cmd)
{
cas_printf(LOG_INFO, "%s%s\n\n", PADDING,
cmd->long_desc != NULL ? cmd->long_desc : cmd->desc);
}
void print_list_options(cli_option* options, int flag,
int (*view)(cli_option* options, int flag))
{
char buffer[2048];
for (; options->long_name != NULL; options++) {
char *desc = options->desc;
char short_name[3];
if (0 == view(options, flag)) {
continue;
}
if ((options->flags & CLI_OPTION_RANGE_INT)
|| (options->flags & CLI_OPTION_DEFAULT_INT)) {
desc = buffer;
if ((options->flags & CLI_OPTION_RANGE_INT)
&& (options->flags
& CLI_OPTION_DEFAULT_INT)) {
snprintf(buffer, sizeof(buffer), options->desc,
options->min_value,
options->max_value,
options->default_value);
} else if (options->flags & CLI_OPTION_DEFAULT_INT) {
snprintf(buffer, sizeof(buffer), options->desc,
options->default_value);
} else if (options->flags & CLI_OPTION_RANGE_INT) {
snprintf(buffer, sizeof(buffer), options->desc,
options->min_value,
options->max_value);
}
}
get_short_name_string(options->short_name, short_name);
if (options->arg != NULL) {
char buf[MAX_OPT_HELP_LEN];
if (options->flags & CLI_OPTION_OPTIONAL_ARG) {
snprintf(buf, MAX_OPT_HELP_LEN, "--%s [<%s>]",
options->long_name, options->arg);
} else {
snprintf(buf, MAX_OPT_HELP_LEN, "--%s <%s>",
options->long_name, options->arg);
}
cas_printf(LOG_INFO, "%s%-4s%-32s%s\n", PADDING,
short_name, buf, desc);
} else {
cas_printf(LOG_INFO, "%s%-4s--%-30s%s\n", PADDING,
short_name, options->long_name, desc);
}
}
}
static void print_options_help(cli_option *options)
{
char buffer[2048];
int i;
for (i = 0; options[i].long_name != NULL; ++i) {
char *desc = options[i].desc;
char short_name[3];
if (options[i].flags & CLI_OPTION_HIDDEN) {
continue;
}
if ((options[i].flags & CLI_OPTION_RANGE_INT)
|| (options[i].flags & CLI_OPTION_DEFAULT_INT) ) {
desc = buffer;
if ((options[i].flags & CLI_OPTION_RANGE_INT)
&& (options[i].flags & CLI_OPTION_DEFAULT_INT) ) {
snprintf(buffer, sizeof(buffer), options[i].desc,
options[i].min_value,
options[i].max_value,
options[i].default_value);
} else if (options[i].flags & CLI_OPTION_DEFAULT_INT) {
snprintf(buffer, sizeof(buffer), options[i].desc,
options[i].default_value);
} else if (options[i].flags & CLI_OPTION_RANGE_INT) {
snprintf(buffer, sizeof(buffer), options[i].desc,
options[i].min_value,
options[i].max_value);
}
}
get_short_name_string(options[i].short_name, short_name);
if (options[i].arg != NULL) {
char buf[MAX_OPT_HELP_LEN];
if (options[i].flags & CLI_OPTION_OPTIONAL_ARG) {
snprintf(buf, MAX_OPT_HELP_LEN, "--%s [<%s>]",
options[i].long_name,
options[i].arg);
} else {
snprintf(buf, MAX_OPT_HELP_LEN, "--%s <%s>",
options[i].long_name,
options[i].arg);
}
cas_printf(LOG_INFO, "%s%-4s%-32s%s\n", PADDING,
short_name, buf, desc);
} else {
cas_printf(LOG_INFO, "%s%-4s--%-30s%s\n", PADDING,
short_name, options[i].long_name,
desc);
}
}
}
static void print_namespace_help(app *app_values, cli_command *cmd)
{
char command_name[MAX_STR_LEN];
char option_name[MAX_STR_LEN];
cli_namespace *ns = cmd->namespace;
int i;
cas_printf(LOG_INFO, "Usage: %s --%s --%s <NAME>\n\n", app_values->name,
cmd->name, ns->long_name);
print_command_header(app_values, cmd);
command_name_in_brackets(command_name, MAX_STR_LEN, cmd->short_name, cmd->name);
command_name_in_brackets(option_name, MAX_STR_LEN, ns->short_name, ns->long_name);
cas_printf(LOG_INFO, "Valid values of NAME are:\n");
for (i = 0; ns->entries[i].name; ++i)
cas_printf(LOG_INFO, "%s%s - %s\n", PADDING, ns->entries[i].name, ns->entries[i].desc);
cas_printf(LOG_INFO, "\n");
for (i = 0; ns->entries[i].name; ++i) {
cas_printf(LOG_INFO, "Options that are valid with %s %s %s are:\n",
command_name, option_name, ns->entries[i].name);
print_options_help(ns->entries[i].options);
if (ns->entries[i + 1].name)
cas_printf(LOG_INFO, "\n");
}
}
static void print_command_help(app *app_values, cli_command *cmd)
{
int all_mandatory = 1;
int all_hidden = 1;
int i;
if (cmd->help) {
(cmd->help)(app_values, cmd);
return;
}
if (cmd->namespace) {
print_namespace_help(app_values, cmd);
return;
}
cas_printf(LOG_INFO, "Usage: %s --%s", app_values->name, cmd->name);
if (cmd->options != NULL) {
for (i = 0; cmd->options[i].long_name != NULL; ++i) {
if (cmd->options[i].flags & CLI_OPTION_HIDDEN) {
continue;
}
all_hidden = 0;
if (cmd->options[i].flags & CLI_OPTION_REQUIRED) {
cas_printf(LOG_INFO, " --%s", cmd->options[i].long_name);
if (cmd->options[i].arg != NULL) {
if (cmd->options[i].flags & CLI_OPTION_OPTIONAL_ARG) {
cas_printf(LOG_INFO, " [<%s>]", cmd->options[i].arg);
} else {
cas_printf(LOG_INFO, " <%s>", cmd->options[i].arg);
}
}
} else {
all_mandatory = 0;
}
}
if (!all_mandatory) {
cas_printf(LOG_INFO, " [option...]");
}
}
cas_printf(LOG_INFO, "\n\n");
print_command_header(app_values, cmd);
if (cmd->options && !all_hidden) {
char option_name[MAX_STR_LEN];
command_name_in_brackets(option_name, MAX_STR_LEN, cmd->short_name, cmd->name);
cas_printf(LOG_INFO, "Options that are valid with %s are:\n", option_name);
print_options_help(cmd->options);
}
}
void print_help(const app *app_values, const cli_command *commands)
{
int i;
cas_printf(LOG_INFO, "%s\n\n", app_values->title);
print_short_usage(app_values);
cas_printf(LOG_INFO, "\nAvailable commands:\n");
for (i = 0;; ++i) {
char short_name[3];
if (commands[i].name == NULL) {
break;
}
if (is_command_hidden(commands, i) ||
is_command_blocked(commands, i)) {
continue;
}
get_short_name_string(commands[i].short_name, short_name);
cas_printf(LOG_INFO, "%s%-4s--%-25s%s\n", PADDING, short_name,
commands[i].name, commands[i].desc);
}
cas_printf(LOG_INFO, "\nFor detailed help on the above commands use --help after the command.\n"
"e.g.\n%s%s --%s --help\n",
PADDING, app_values->name, commands[0].name);
if (app_values->man != NULL) {
cas_printf(LOG_INFO,
"For more information, please refer to manual, Admin Guide (man %s)\n"
"or go to support page <https://open-cas.github.io>.\n",
app_values->man);
} else {
cas_printf(LOG_INFO,
"For more information, please refer to manual, Admin Guide\n"
"or go to support page <https://open-cas.github.io>.\n");
}
}
static int args_is_unrecognized(const char *cmd)
{
if (strempty(cmd)) {
return 1;
}
if ('-' == cmd[0]) {
char c = cmd[1];
/* Check if short option (command) is proper */
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
if ('\0' == cmd[2]) {
return 0;
} else {
return 1;
}
}
if ('-' == cmd[1]) {
char c = cmd[2];
/* Long option (command), check if it is valid */
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
return 0;
}
}
}
return 1;
}
static int args_is(const char *in, const char *arg, const char c)
{
if (strempty(in)) {
return 0;
}
if ('-' == in[0]) {
if (0 != c && c == in[1]) {
if ('\0' == in[2]) {
return 1;
}
}
if ('-' == in[1]) {
/* Long option */
if (0 == strncmp(&(in[2]), arg, MAX_STR_LEN)) {
return 1;
}
}
}
return 0;
}
static int is_help(const char* cmd)
{
return args_is(cmd, "help", 'H');
}
static int get_help_position(int argc, const char **argv)
{
int i;
for (i = 2; i < argc; i++) {
if (is_help(argv[i])) {
return i;
}
}
return -1;
}
static int get_option(const cli_option *options, const char* opt)
{
int i;
for (i = 0; options[i].long_name; ++i) {
if (args_is(opt, options[i].long_name, options[i].short_name)) {
return i;
}
}
return -1;
}
/**
* log command as it was entered in CLI
*/
void log_command(int argc, const char **argv, int result, long long int timespan)
{
const int def_cmd_buf_len = 100;
int cmd_buf_len = def_cmd_buf_len;
int cmd_len = 0;
int i = 0;
char *command = malloc(cmd_buf_len);
if (!command) {
cas_printf(LOG_ERR, "Memory allocation failed for logging.");
return;
}
for (i = 0 ; i != argc ; ++i) {
int tok_len = strnlen_s(argv[i], MAX_STR_LEN);
/* if reconstructed command width is longer than current
* cmd_buf_len (length of command buffer), than resize it
* to make it twice as big.
*/
if (tok_len + 1 + cmd_len > cmd_buf_len) {
cmd_buf_len = (tok_len + 1 + cmd_buf_len) * 2;
char *tmp = realloc(command, cmd_buf_len);
/* if reallocation failed, cancel logging */
if (!tmp) {
cas_printf(LOG_ERR,
"Memory allocation failed for logging.");
free(command);
return;
}
command = tmp;
}
/* append additional token to a string */
memcpy_s(command + cmd_len, cmd_buf_len - cmd_len,
argv[i], tok_len);
cmd_len += tok_len;
/* either a space or a null terminator */
command[cmd_len] = (i == argc - 1) ? 0 : ' ';
cmd_len++;
}
caslog(LOG_DEBUG, "Casadm invoked with: \"%s\". "
"Exit status is %d (%s). Command took %lld.%02lld s.",
command, result, result? "failure" : "success",
timespan / 1000, (timespan % 1000) / 10);
free(command);
}
static inline long long int timeval_to_msec(struct timeval t)
{
return 1000 * t.tv_sec + t.tv_usec / 1000;
}
/**
* run command. Additionally log its execution and report any errors if
* they've happened
*/
int run_command(cli_command *commands, int cmd, int argc, const char **argv)
{
int result;
const char *syslog_path = "/var/log/messages";
struct timeval t0, t1;
FILE *messages_f;
long long int timespan;
/* collect time */
gettimeofday(&t0, NULL);
/* collect stat buffer for syslog */
messages_f = fopen(syslog_path, "r");
if (messages_f) {
fseek(messages_f, 0, SEEK_END);
/* if opening file failed, don't stop command execution.
* - just omit checking for /var/log/messages at the end
*/
} else {
/* ubuntu case*/
syslog_path = "/var/log/syslog";
messages_f = fopen(syslog_path, "r");
if (messages_f) {
fseek(messages_f, 0, SEEK_END);
}
}
/* execute CAS command */
result = commands[cmd].handle();
gettimeofday(&t1, NULL);
timespan = timeval_to_msec(t1) - timeval_to_msec(t0);
if (commands[cmd].short_name != 'V') {
log_command(argc, argv, result, timespan);
}
/* print extra warning message IF command ended with failure and
* syslog contains something */
if (FAILURE == result && messages_f) {
char line_buf[MAX_STR_LEN];
bool kernel_said = false; /* set to true if CAS kernel module
* said anything during command
* execution */
while (fgets(line_buf, MAX_STR_LEN - 1, messages_f)) {
line_buf[MAX_STR_LEN - 1] = 0;
if (strstr(line_buf, "CAS") &&
strstr(line_buf, "kernel")) {
kernel_said = true;
}
}
if (kernel_said) {
fprintf(stderr, "Error occurred, "
"please see syslog (%s) for details. \n",
syslog_path);
}
}
if (messages_f) {
fclose(messages_f);
}
return result;
}
static int count_arg_params(const char **argv, int argc)
{
int i = 0;
for (i = 0; i < argc; i++) {
if ('-' == argv[i][0] && 0 != argv[i][1]) {
break;
}
}
return i;
}
void configure_cli_commands(cli_command *commands)
{
cli_command *cmd = commands;
int ret;
while(cmd->name) {
if (cmd->configure) {
ret = cmd->configure(cmd);
if (ret < 0) {
cmd->flags |= CLI_COMMAND_HIDDEN;
}
}
cmd++;
}
}
int args_parse(app *app_values, cli_command *commands, int argc, const char **argv)
{
int i, j, k, status = SUCCESS;
int args_count, args_offset;
const char **args_list = NULL;
const char* cmd_name = argv[1];
cli_ns_entry *entry = NULL;
cli_option *options;
int cmd, first_opt;
if (argc < 2) {
cas_printf(LOG_ERR, "No command given.\n");
print_info(app_values);
return FAILURE;
}
if (args_is_unrecognized(cmd_name)) {
cas_printf(LOG_ERR, "Unrecognized command %s\n", cmd_name);
print_info(app_values);
return FAILURE;
}
for (i = 0;; ++i) {
if (commands[i].name == NULL) {
if (is_help(cmd_name)) {
print_help(app_values, commands);
return SUCCESS;
} else {
cas_printf(LOG_ERR, "Unrecognized command %s\n",
cmd_name);
print_info(app_values);
}
return FAILURE;
} else if (args_is(cmd_name, commands[i].name, commands[i].short_name)) {
cmd = i;
break;
}
}
if (is_command_blocked(commands, i)) {
cas_printf(LOG_INFO, "The command is not supported\n");
return SUCCESS;
}
configure_cli_commands(commands);
if (argc >= 3 && get_help_position(argc, argv) != -1) {
if (!is_command_hidden(commands, i)) {
print_command_help(app_values, &commands[i]);
}
return SUCCESS;
}
if (is_su_requied(commands, cmd)) {
if (getuid() != 0) {
cas_printf(LOG_ERR, "Must be run as root.\n");
return FAILURE;
}
}
if (commands[cmd].options) {
options = commands[cmd].options;
first_opt = 2;
} else if (commands[cmd].namespace) {
if (argc < 3) {
cas_printf(LOG_ERR, "Missing namespace option.\n");
print_info(app_values);
return FAILURE;
}
if (argc < 4) {
cas_printf(LOG_ERR, "Missing namespace name.\n");
print_info(app_values);
return FAILURE;
}
if (!args_is(argv[2], commands[cmd].namespace->long_name,
commands[cmd].namespace->short_name)) {
cas_printf(LOG_ERR, "Unrecognized option.\n");
print_info(app_values);
return FAILURE;
}
entry = commands[cmd].namespace->entries;
while (true) {
if (!strcmp(argv[3], entry->name))
break;
if (!(++entry)->name) {
cas_printf(LOG_ERR, "Unrecognized namespace entry.\n");
print_info(app_values);
return FAILURE;
}
}
options = entry->options;
first_opt = 4;
} else {
return run_command(commands, cmd, argc, argv);
}
/* for each possible option:
* - if it is required, check if it is supplied exactly once
* - if it is not required, check if it is supplied at most once
*/
for (i = 0; options[i].long_name; ++i) {
char option_name[MAX_STR_LEN];
/* count occurrences of an option (k as counter) */
k = 0;
for (j = first_opt; j < argc; ++j) {
if (args_is(argv[j], options[i].long_name,
options[i].short_name)) {
k++;
}
}
command_name_with_slash(option_name, MAX_STR_LEN,
options[i].short_name, options[i].long_name);
if (options[i].flags & CLI_OPTION_REQUIRED) {
if (!k) {
cas_printf(LOG_ERR, "Missing required option %s\n", option_name);
print_info(app_values);
return FAILURE;
}
}
if (k > 1) {
cas_printf(LOG_ERR, "Option supplied more than once %s\n", option_name);
print_info(app_values);
return FAILURE;
}
}
/* Store parameters for arguments. Terminate each list with NULL element.
* Accomodate for max no of parameters */
args_list = malloc(sizeof(*args_list) * (argc + 1));
if (args_list == NULL) {
return FAILURE;
}
/* iterate over all arguments that were actually passed to the CLI */
args_count = args_offset = 0;
for (i = first_opt; i < argc; ++i) {
int opt;
if (args_is_unrecognized(argv[i])) {
cas_printf(LOG_ERR, "Invalid format %s\n",
argv[i]);
print_info(app_values);
status = FAILURE;
goto free_args;
}
opt = get_option(options, argv[i]);
if (opt == -1) {
cas_printf(LOG_ERR, "Unrecognized option %s\n",
argv[i]);
print_info(app_values);
status = FAILURE;
goto free_args;
}
if (options[opt].arg != NULL) {
/* Count params for current argument. */
args_count = count_arg_params(&argv[i + 1], argc - i - 1);
if (args_count == 0 && !(options[opt].flags & CLI_OPTION_OPTIONAL_ARG)) {
cas_printf(LOG_ERR, "Missing required argument in %s\n",
argv[i]);
print_info(app_values);
status = FAILURE;
goto free_args;
}
if (-1 != options[opt].args_count &&
args_count != options[opt].args_count &&
(0 != args_count || !(options[opt].flags & CLI_OPTION_OPTIONAL_ARG))) {
cas_printf(LOG_ERR, "Invalid number of arguments for %s\n",
argv[i]);
print_info(app_values);
status = FAILURE;
goto free_args;
}
/* Add params for current argument.
* Terminate list with NULL element.*/
for (k = args_offset, j = 0; j < args_count; j++) {
args_list[k++] = argv[j + i + 1];
}
args_list[args_offset + args_count] = NULL;
i += args_count;
}
if (commands[cmd].command_handle_opts) {
status = commands[cmd].command_handle_opts(
options[opt].long_name,
&args_list[args_offset]);
} else if (commands[cmd].namespace_handle_opts) {
status = commands[cmd].namespace_handle_opts(
entry->name,
options[opt].long_name,
&args_list[args_offset]);
} else {
cas_printf(LOG_ERR, "Internal error\n");
status = FAILURE;
goto free_args;
}
args_offset += args_count;
if (0 != status) {
cas_printf(LOG_ERR, "Error during options handling\n");
print_info(app_values);
status = FAILURE;
goto free_args;
}
}
status = run_command(commands, cmd, argc, argv);
free_args:
if (NULL != args_list) {
free(args_list);
args_list = NULL;
}
return status;
}