1
0
mirror of git://sourceware.org/git/lvm2.git synced 2025-09-23 17:44:22 +03:00

Compare commits

..

1 Commits

Author SHA1 Message Date
David Teigland
94ea57779a commands: new method for defining commands
. Define a prototype for every lvm command.
. Verify every user command matches one.
. Generate help text and man pages from them.

The new file command-lines.in defines a prototype for every
unique lvm command.  A unique lvm command is a unique
combination of: command name + required option args +
required positional args.  Each of these prototypes also
includes the optional option args and optional positional
args that the command will accept, a description, and a
unique string ID for the definition.  Any valid command
will match one of the prototypes.

Here's an example of the lvresize command definitions from
command-lines.in, there are three unique lvresize commands:

lvresize --size SizeMB LV
OO: --alloc Alloc, --autobackup Bool, --force,
--nofsck, --nosync, --noudevsync, --reportformat String, --resizefs,
--stripes Number, --stripesize SizeKB, --test, --poolmetadatasize SizeMB
OP: PV ...
ID: lvresize_by_size
DESC: Resize an LV by a specified size.

lvresize LV PV ...
OO: --alloc Alloc, --autobackup Bool, --force,
--nofsck, --nosync, --noudevsync,
--reportformat String, --resizefs, --stripes Number, --stripesize SizeKB,
--test
ID: lvresize_by_pv
DESC: Resize an LV by a specified PV.

lvresize --poolmetadatasize SizeMB LV_thinpool
OO: --alloc Alloc, --autobackup Bool, --force,
--nofsck, --nosync, --noudevsync,
--reportformat String, --stripes Number, --stripesize SizeKB,
--test
OP: PV ...
ID: lvresize_pool_metadata_by_size
DESC: Resize the metadata SubLV of a pool LV.

The three commands have separate definitions because they have
different required parameters.  Required parameters are specified
on the first line of the definition.  Optional options are
listed after OO, and optional positional args are listed after OP.

This data is used to generate corresponding command definition
structures for lvm in command-lines.h.  "usage" text is also
generated, so it is always in sync with the definitions.

Example of the corresponding generated structure in
command-lines.h for the first lvresize prototype
(these structures are never edited directly):

commands[78].name = "lvresize";
commands[78].command_line_id = "lvresize_by_size";
commands[78].command_line_enum = lvresize_by_size_CMD;
commands[78].fn = lvresize;
commands[78].ro_count = 1;
commands[78].rp_count = 1;
commands[78].oo_count = 22;
commands[78].op_count = 1;
commands[78].desc = "DESC: Resize an LV by a specified size.";
commands[78].usage = "lvresize --size Number[m|unit] LV"
" [ --alloc contiguous|cling|normal|anywhere|inherit,
   --autobackup y|n, --nofsck, --nosync, --reportformat String,
   --resizefs, --stripes Number, --stripesize Number[k|unit],
   --poolmetadatasize Number[m|unit] ]"
" [ PV ... ]";
commands[78].usage_common =
" [ --commandprofile String, --config String, --debug,
    --driverloaded y|n, --help, --profile String, --quiet,
    --verbose, --version, --yes, --force, --test, --noudevsync ]";
commands[78].required_opt_args[0].opt = size_ARG;
commands[78].required_opt_args[0].def.val_bits = val_enum_to_bit(sizemb_VAL);
commands[78].required_pos_args[0].pos = 1;
commands[78].required_pos_args[0].def.val_bits = val_enum_to_bit(lv_VAL);
commands[78].optional_opt_args[0].opt = commandprofile_ARG;
commands[78].optional_opt_args[0].def.val_bits = val_enum_to_bit(string_VAL);
commands[78].optional_opt_args[1].opt = config_ARG;
commands[78].optional_opt_args[1].def.val_bits = val_enum_to_bit(string_VAL);
commands[78].optional_opt_args[2].opt = debug_ARG;
commands[78].optional_opt_args[3].opt = driverloaded_ARG;
commands[78].optional_opt_args[3].def.val_bits = val_enum_to_bit(bool_VAL);
commands[78].optional_opt_args[4].opt = help_ARG;
commands[78].optional_opt_args[5].opt = profile_ARG;
commands[78].optional_opt_args[5].def.val_bits = val_enum_to_bit(string_VAL);
commands[78].optional_opt_args[6].opt = quiet_ARG;
commands[78].optional_opt_args[7].opt = verbose_ARG;
commands[78].optional_opt_args[8].opt = version_ARG;
commands[78].optional_opt_args[9].opt = yes_ARG;
commands[78].optional_opt_args[10].opt = alloc_ARG;
commands[78].optional_opt_args[10].def.val_bits = val_enum_to_bit(alloc_VAL);
commands[78].optional_opt_args[11].opt = autobackup_ARG;
commands[78].optional_opt_args[11].def.val_bits = val_enum_to_bit(bool_VAL);
commands[78].optional_opt_args[12].opt = force_ARG;
commands[78].optional_opt_args[13].opt = nofsck_ARG;
commands[78].optional_opt_args[14].opt = nosync_ARG;
commands[78].optional_opt_args[15].opt = noudevsync_ARG;
commands[78].optional_opt_args[16].opt = reportformat_ARG;
commands[78].optional_opt_args[16].def.val_bits = val_enum_to_bit(string_VAL);
commands[78].optional_opt_args[17].opt = resizefs_ARG;
commands[78].optional_opt_args[18].opt = stripes_ARG;
commands[78].optional_opt_args[18].def.val_bits = val_enum_to_bit(number_VAL);
commands[78].optional_opt_args[19].opt = stripesize_ARG;
commands[78].optional_opt_args[19].def.val_bits = val_enum_to_bit(sizekb_VAL);
commands[78].optional_opt_args[20].opt = test_ARG;
commands[78].optional_opt_args[21].opt = poolmetadatasize_ARG;
commands[78].optional_opt_args[21].def.val_bits = val_enum_to_bit(sizemb_VAL);
commands[78].optional_pos_args[0].pos = 2;
commands[78].optional_pos_args[0].def.val_bits = val_enum_to_bit(pv_VAL);
commands[78].optional_pos_args[0].def.flags = ARG_DEF_FLAG_MAY_REPEAT;

Every user-entered command is compared against the set of
command structures, and matched with one.  An error is
reported if an entered command does not have the required
parameters for any definition.  The closest match is printed
as a suggestion, and running lvresize --help will display
the usage for each possible lvresize command, e.g.:

$ lvresize --help
  lvresize - Resize a logical volume

  Resize an LV by a specified size.
  lvresize --size Number[m|unit] LV
  	[ --alloc contiguous|cling|normal|anywhere|inherit,
	  --autobackup y|n,
	  --nofsck,
	  --nosync,
	  --reportformat String,
	  --resizefs,
	  --stripes Number,
	  --stripesize Number[k|unit],
	  --poolmetadatasize Number[m|unit] ]
  	[ PV ... ]

  Resize an LV by a specified PV.
  lvresize LV PV ...
  	[ --alloc contiguous|cling|normal|anywhere|inherit,
	  --autobackup y|n,
	  --nofsck,
	  --nosync,
	  --reportformat String,
	  --resizefs,
	  --stripes Number,
	  --stripesize Number[k|unit] ]

  Resize the metadata SubLV of a pool LV.
  lvresize --poolmetadatasize Number[m|unit] LV_thinpool
  	[ --alloc contiguous|cling|normal|anywhere|inherit,
	  --autobackup y|n,
	  --nofsck,
	  --nosync,
	  --reportformat String,
	  --stripes Number,
	  --stripesize Number[k|unit] ]
  	[ PV ... ]

  Common options:
  	[ --commandprofile String,
	  --config String,
	  --debug,
	  --driverloaded y|n,
	  --help,
	  --profile String,
	  --quiet,
	  --verbose,
	  --version,
	  --yes,
	  --force,
	  --test,
	  --noudevsync ]

  (Use --help --help for usage notes.)

$ lvresize --poolmetadatasize 4
  Failed to find a matching command definition.
  Closest command usage is:
  lvresize --poolmetadatasize Number[m|unit] LV_thinpool

Man page prototypes are also generated from the same original
command definitions, and are always in sync with the code
and help text.

Very early in command execution, a matching command definition
is found.  lvm then knows the operation being done, and that
the provided args conform to the definition.  This will allow
lots of ad hoc checking/validation to be removed throughout
the code.

Each command definition can also be routed to a specific
function to implement it.  The function is associated with
an enum value for the command definition (generated from
the ID string.)  These per-command-definition implementation
functions have not yet been created, so all commands
currently fall back to the existing implementation.

Using per-command-definition functions will allow lots of
code to be removed which tries to figure out what the
command is meant to do.  This is currently based on ad hoc
and complicated option analysis.  When using the new
functions, what the command is doing is already known
from the associated command definition.

So, this first phase validates every user-entered command
against the set of command prototypes, then calls the existing
implementation.  The second phase can associate an implementation
function with each definition, and take further advantage of the
known operation to avoid the complicated option analysis.
2016-10-13 13:54:39 -05:00
6 changed files with 2585 additions and 2744 deletions

View File

@@ -88,7 +88,6 @@ struct cmd_context {
* Command line and arguments.
*/
const char *cmd_line;
const char *name; /* needed before cmd->command is set */
struct command *command;
char **argv;
struct arg_values *opt_arg_values;

View File

@@ -90,13 +90,7 @@
#
# "---" is like a comment line, used to separate text for readability
#
# ID: A unique string identifying the command. Two commands that do
# the same thing, but are alternate syntaxes can share the same ID,
# in which case the implementation would have to sort out which
# args to look at for the required parameters. Or, the two commands
# could use differnet IDs, in which case the implementation would
# know where to look for each parameter.
#
# ID: A unique string identifying the command.
# DESC: A description of the command.
#
@@ -292,20 +286,14 @@ DESC: (variant, infers --type thin).
---
# FIXME: I don't think --zero applies when creating cache LV,
# but it's used in a test. Should the test be fixed and
# --zero removed here?
lvconvert --type cache --cachepool LV LV_linear_striped_raid_thinpool
OO: --cache, --cachemode CacheMode, --cachepolicy String,
--cachesettings String, --zero Bool, OO_LVCONVERT_POOL, OO_LVCONVERT
OO: --cache, --cachepolicy String, --cachesettings String, OO_LVCONVERT_POOL, OO_LVCONVERT
ID: lvconvert_to_cache_vol
DESC: Convert LV to type cache.
# alternate form of lvconvert --type cache
lvconvert --cache --cachepool LV LV_linear_striped_raid_thinpool
OO: --type cache, --cachemode CacheMode, --cachepolicy String,
--cachesettings String, --zero Bool, OO_LVCONVERT_POOL, OO_LVCONVERT
OO: --type cache, --cachepolicy String, --cachesettings String, OO_LVCONVERT_POOL, OO_LVCONVERT
ID: lvconvert_to_cache_vol
DESC: Convert LV to type cache (variant, infers --type cache).
@@ -325,21 +313,15 @@ DESC: Convert LV to type thin-pool (variant, use --type thin-pool).
---
# FIXME: I don't think that --cachemode, --cachepolicy, --cachesettings
# are meant to be used when creating a cache pool, but they are used
# in one test. Should that test be fixed and these options removed?
lvconvert --type cache-pool LV_linear_striped_raid
OO: OO_LVCONVERT_POOL, OO_LVCONVERT,
--cachemode CacheMode, --cachepolicy String, --cachesettings String
OO: OO_LVCONVERT_POOL, OO_LVCONVERT
ID: lvconvert_to_cachepool
DESC: Convert LV to type cache-pool.
# alternate form of lvconvert --type cache-pool
# deprecated because of non-standard syntax (missing positional arg)
lvconvert --cachepool LV_linear_striped_raid
OO: --type cache-pool, OO_LVCONVERT_POOL, OO_LVCONVERT,
--cachemode CacheMode, --cachepolicy String, --cachesettings String
lvconvert --cachepool LV
OO: OO_LVCONVERT_POOL, OO_LVCONVERT
ID: lvconvert_to_cachepool
DESC: Convert LV to type cache-pool (variant, use --type cache-pool).
@@ -608,7 +590,7 @@ DESC: (infers --type snapshot).
---
lvcreate --type snapshot --size SizeMB --virtualsize SizeMB VG
OO: --snapshot, --virtualoriginsize SizeMB, OO_LVCREATE
OO: --virtualoriginsize SizeMB, OO_LVCREATE
OP: PV ...
ID: lvcreate_cow_snapshot_with_virtual_origin
DESC: Create a sparse COW snapshot LV of a virtual origin LV.
@@ -646,23 +628,16 @@ DESC: Create a cache pool (variant, infers --type cache-pool).
---
lvcreate --type thin --virtualsize SizeMB --thinpool LV_thinpool
OO: --thin, OO_LVCREATE_POOL, OO_LVCREATE_THIN, OO_LVCREATE
OO: OO_LVCREATE_POOL, OO_LVCREATE_THIN, OO_LVCREATE
ID: lvcreate_thin_vol
DESC: Create a thin LV in a thin pool.
# alternate form of lvcreate --type thin
lvcreate --virtualsize SizeMB --thinpool LV_thinpool
OO: --type thin, --thin, OO_LVCREATE_THIN, OO_LVCREATE
OO: --type thin, OO_LVCREATE_THIN, OO_LVCREATE
ID: lvcreate_thin_vol
DESC: Create a thin LV in a thin pool (variant, infers --type thin).
# alternate form of lvcreate --type thin
lvcreate --virtualsize SizeMB LV_thinpool
OO: --type thin, --thin, OO_LVCREATE_THIN, OO_LVCREATE
ID: lvcreate_thin_vol
DESC: Create a thin LV in the thin pool named in arg pos 1
DESC: (variant, infers --type thin).
---
lvcreate --type thin LV_thin
@@ -756,14 +731,14 @@ DESC: cache pool LV to use.
---
lvcreate --type cache --size SizeMB --cachepool LV_cachepool
OO: --cache, OO_LVCREATE_POOL, OO_LVCREATE_CACHE, OO_LVCREATE
OO: OO_LVCREATE_POOL, OO_LVCREATE_CACHE, OO_LVCREATE
OP: PV ...
ID: lvcreate_cache_vol_with_new_origin
DESC: Create a cache LV, first creating a new origin LV,
DESC: then combining it with the existing cache pool in arg pos 1.
lvcreate --size SizeMB --cachepool LV_cachepool
OO: --type cache, --cache, OO_LVCREATE_CACHE, OO_LVCREATE
OO: --type cache, OO_LVCREATE_CACHE, OO_LVCREATE
OP: PV ...
ID: lvcreate_cache_vol_with_new_origin
DESC: Create a cache LV, first creating a new origin LV,
@@ -924,8 +899,8 @@ ID: pvck_general
pvcreate PV ...
OO: --dataalignment SizeKB, --dataalignmentoffset SizeKB, --bootloaderareasize SizeMB,
--force, --test, --labelsector Number, --metadatatype MetadataType,
--pvmetadatacopies Number, --metadatacopies MetadataCopies, --metadatasize SizeMB,
--metadataignore Bool, --norestorefile, --setphysicalvolumesize SizeMB,
--pvmetadatacopies Number, --metadatasize SizeMB, --metadataignore Bool,
--norestorefile, --setphysicalvolumesize SizeMB,
--reportformat String, --restorefile String, --uuidstr String, --zero Bool
ID: pvcreate_general
@@ -983,7 +958,6 @@ ID: pvscan_cache
vgcfgbackup
OO: --file String, --foreign, --ignorelockingfailure, --partial, --readonly,
--reportformat String
OP: VG ...
ID: vgcfgbackup_general
---

View File

@@ -1150,8 +1150,6 @@ void print_command_count(void)
printf("#define COMMAND_COUNT %d\n", cmd_count);
printf("enum {\n");
printf("\tno_CMD,\n"); /* enum value 0 is not used */
for (i = 0; i < cmd_count; i++) {
cmd = &cmd_array[i];

View File

@@ -1,8 +1,7 @@
/* Do not edit. This file is generated by scripts/create-commands */
/* using command definitions from scripts/command-lines.in */
#define COMMAND_COUNT 147
#define COMMAND_COUNT 146
enum {
no_CMD,
lvchange_properties_CMD,
lvchange_resync_CMD,
lvchange_syncaction_CMD,

File diff suppressed because it is too large Load Diff

View File

@@ -735,7 +735,7 @@ int readahead_arg(struct cmd_context *cmd __attribute__((unused)), struct arg_va
*/
int metadatacopies_arg(struct cmd_context *cmd, struct arg_values *av)
{
if (!strncmp(cmd->name, "vg", 2)) {
if (!strncmp(cmd->command->name, "vg", 2)) {
if (!strcasecmp(av->value, "all")) {
av->ui_value = VGMETADATACOPIES_ALL;
return 1;
@@ -785,12 +785,6 @@ static void _set_valid_args_for_command_name(int ci)
if (all_args[i]) {
command_names[ci].valid_args[num_args] = _cmdline.arg_props[i].arg_enum;
num_args++;
/* Automatically recognize --extents in addition to --size. */
if (_cmdline.arg_props[i].arg_enum == size_ARG) {
command_names[ci].valid_args[num_args] = extents_ARG;
num_args++;
}
}
}
command_names[ci].num_args = num_args;
@@ -813,9 +807,6 @@ static struct command_function *_find_command_function(int command_line_enum)
{
int i;
if (!command_line_enum)
return NULL;
for (i = 0; i < COMMAND_ID_COUNT; i++) {
if (command_functions[i].command_line_enum == command_line_enum)
return &command_functions[i];
@@ -861,6 +852,28 @@ void lvm_register_commands(void)
_set_valid_args_for_command_name(i);
}
/*
* Match what the user typed with a one specific command definition/prototype
* from commands[]. If nothing matches, it's not a valid command. The match
* is based on command name, required opt args and required pos args.
*
* Find an entry in the commands array that matches based the arg values.
*
* If the cmd has opt or pos args set that are not accepted by command,
* we can: silently ignore them, warn they are not being used, or fail.
* Default should probably be to warn and continue.
*
* For each command[i], check how many required opt/pos args cmd matches.
* Save the command[i] that matches the most.
*
* commands[i].cmd_flags & CMD_FLAG_ONE_REQUIRED_OPT means
* any one item from commands[i].required_opt_args needs to be
* set to match.
*
* required_pos_args[0].types & select_VAL means
* cmd->argv[] in that pos can be NULL if arg_is_set(select_ARG)
*/
static int _opt_equivalent_is_set(struct cmd_context *cmd, int opt)
{
if ((opt == mirrorlog_ARG) && arg_is_set(cmd, corelog_ARG))
@@ -1164,28 +1177,6 @@ static void _print_description(int ci)
log_print("%s", buf);
}
/*
* Match what the user typed with a one specific command definition/prototype
* from commands[]. If nothing matches, it's not a valid command. The match
* is based on command name, required opt args and required pos args.
*
* Find an entry in the commands array that matches based the arg values.
*
* If the cmd has opt or pos args set that are not accepted by command,
* we can: silently ignore them, warn they are not being used, or fail.
* Default should probably be to warn and continue.
*
* For each command[i], check how many required opt/pos args cmd matches.
* Save the command[i] that matches the most.
*
* commands[i].cmd_flags & CMD_FLAG_ONE_REQUIRED_OPT means
* any one item from commands[i].required_opt_args needs to be
* set to match.
*
* required_pos_args[0].types & select_VAL means
* cmd->argv[] in that pos can be NULL if arg_is_set(select_ARG)
*/
static struct command *_find_command(struct cmd_context *cmd, const char *path, int *argc, char **argv)
{
const char *name;
@@ -1353,19 +1344,12 @@ static struct command *_find_command(struct cmd_context *cmd, const char *path,
return NULL;
}
/* --extents is a special case which is accepted in place of --size */
if (!accepted && (i != extents_ARG)) {
log_error("Invalid option for the specified command (%s %d): %s.",
commands[best_i].command_line_id, best_i, arg_long_option_name(i));
return NULL;
#if 0
if (!accepted) {
log_warn("Ignoring option which is not used by the specified command: %s.",
arg_long_option_name(i));
/* clear so it can't be used when processing. */
cmd->opt_arg_values[i].count = 0;
cmd->opt_arg_values[i].value = NULL;
#endif
}
}
@@ -1399,7 +1383,7 @@ static struct command *_find_command(struct cmd_context *cmd, const char *path,
}
}
out:
log_debug("command line id: %s %d", commands[best_i].command_line_id, best_i);
log_debug("command line id: %s (%d)", commands[best_i].command_line_id, best_i);
return &commands[best_i];
}
@@ -1574,7 +1558,8 @@ static int _find_arg(const char *cmd_name, int goval)
return -1;
}
static int _process_command_line(struct cmd_context *cmd, int *argc, char ***argv)
static int _process_command_line(struct cmd_context *cmd, const char *cmd_name,
int *argc, char ***argv)
{
char str[((ARG_COUNT + 1) * 2) + 1], *ptr = str;
struct option opts[ARG_COUNT + 1], *o = opts;
@@ -1586,7 +1571,7 @@ static int _process_command_line(struct cmd_context *cmd, int *argc, char ***arg
int goval; /* the number returned from getopt_long identifying what it found */
int i;
if (!(cname = _find_command_name(cmd->name)))
if (!(cname = _find_command_name(cmd_name)))
return_0;
if (!(cmd->opt_arg_values = dm_pool_zalloc(cmd->mem, sizeof(*cmd->opt_arg_values) * ARG_COUNT))) {
@@ -1616,7 +1601,7 @@ static int _process_command_line(struct cmd_context *cmd, int *argc, char ***arg
* translate the option value used by getopt into the enum
* value (e.g. foo_ARG) from the args array.
*/
if ((arg_enum = _find_arg(cmd->name, goval)) < 0) {
if ((arg_enum = _find_arg(cmd_name, goval)) < 0) {
log_fatal("Unrecognised option.");
return 0;
}
@@ -1889,9 +1874,9 @@ static int _get_settings(struct cmd_context *cmd)
!_merge_synonym(cmd, raidwritebehind_ARG, writebehind_ARG))
return EINVALID_CMD_LINE;
if ((!strncmp(cmd->name, "pv", 2) &&
if ((!strncmp(cmd->command->name, "pv", 2) &&
!_merge_synonym(cmd, metadatacopies_ARG, pvmetadatacopies_ARG)) ||
(!strncmp(cmd->name, "vg", 2) &&
(!strncmp(cmd->command->name, "vg", 2) &&
!_merge_synonym(cmd, metadatacopies_ARG, vgmetadatacopies_ARG)))
return EINVALID_CMD_LINE;
@@ -1902,7 +1887,7 @@ static int _get_settings(struct cmd_context *cmd)
static int _process_common_commands(struct cmd_context *cmd)
{
if (arg_is_set(cmd, help_ARG) || arg_is_set(cmd, help2_ARG)) {
_usage(cmd->name, arg_count(cmd, help_ARG));
_usage(cmd->command->name, arg_count(cmd, help_ARG));
if (arg_count(cmd, help_ARG) < 2)
log_print("(Use --help --help for usage notes.)");
@@ -2215,6 +2200,7 @@ int lvm_run_command(struct cmd_context *cmd, int argc, char **argv)
{
struct dm_config_tree *config_string_cft, *config_profile_command_cft, *config_profile_metadata_cft;
const char *reason = NULL;
const char *cmd_name;
int ret = 0;
int locking_type;
int monitoring;
@@ -2228,7 +2214,7 @@ int lvm_run_command(struct cmd_context *cmd, int argc, char **argv)
/* each command should start out with sigint flag cleared */
sigint_clear();
cmd->name = strdup(argv[0]);
cmd_name = strdup(argv[0]);
/* eliminate '-' from all options starting with -- */
for (i = 1; i < argc; i++) {
@@ -2267,7 +2253,7 @@ int lvm_run_command(struct cmd_context *cmd, int argc, char **argv)
if (!(cmd->cmd_line = _copy_command_line(cmd, argc, argv)))
return_ECMD_FAILED;
if (!_process_command_line(cmd, &argc, &argv)) {
if (!_process_command_line(cmd, cmd_name, &argc, &argv)) {
log_error("Error during parsing of command line.");
return EINVALID_CMD_LINE;
}
@@ -2283,10 +2269,10 @@ int lvm_run_command(struct cmd_context *cmd, int argc, char **argv)
log_debug("Parsing: %s", cmd->cmd_line);
if (!(cmd->command = _find_command(cmd, cmd->name, &argc, argv)))
return EINVALID_CMD_LINE;
if (!(cmd->command = _find_command(cmd, cmd_name, &argc, argv)))
return_ECMD_FAILED;
set_cmd_name(cmd->name);
set_cmd_name(cmd_name);
if (arg_is_set(cmd, backgroundfork_ARG)) {
if (!become_daemon(cmd, 1)) {