Discussion:
[linux-lvm] [PATCH v2 3/3] dmsetup: add support to create devices when bootformat is used
Enric Balletbo i Serra
2017-05-16 15:20:40 UTC
Permalink
Add a new switch to supply a bootformated string to dmsetup, we
support create one or multiple devices in one line provided to
the create command. E.g.

dmsetup create --bootformat <multi_dev_info>|<multi_dev_info_file>

Signed-off-by: Enric Balletbo i Serra <***@collabora.com>
---
man/dmsetup.8_main | 77 ++++++++++++++++++++++---
test/shell/dmsetup-bootformat.sh | 77 +++++++++++++++++++++++++
tools/dmsetup.c | 118 ++++++++++++++++++++++++++++++++++++++-
3 files changed, 264 insertions(+), 8 deletions(-)
create mode 100644 test/shell/dmsetup-bootformat.sh

diff --git a/man/dmsetup.8_main b/man/dmsetup.8_main
index 4421882..dad58b1 100644
--- a/man/dmsetup.8_main
+++ b/man/dmsetup.8_main
@@ -22,14 +22,17 @@ dmsetup \(em low level logical volume management
.de CMD_CREATE
. ad l
. BR create
-. IR device_name
+. IR [[device_name]
. RB [ -u | --uuid
. IR uuid ]
. RB \%[ --addnodeoncreate | --addnodeonresume ]
. RB \%[ -n | --notable | --table
. IR \%table | table_file ]
. RB [ --readahead
-. RB \%[ + ] \fIsectors | auto | none ]
+. RB \%[ + ] \fIsectors | auto | none ]]
+. RB |
+. RB \%[ --bootformat
+. IR \%multi_dev_info | multi_dev_info_file]
. ad b
..
.CMD_CREATE
@@ -269,9 +272,11 @@ dmsetup \(em low level logical volume management
.de CMD_TABLE
. ad l
. BR table
-. RB [ --target
+. RB [[ --target
. IR target_type ]
-. RB [ --showkeys ]
+. RB [ --showkeys ]]
+. RB |
+. RB [ --bootformat ]
. RI [ device_name ...]
. ad b
..
@@ -400,6 +405,12 @@ Ensure \fI/dev/mapper\fP node exists after \fBdmsetup create\fP.
Ensure \fI/dev/mapper\fP node exists after \fBdmsetup resume\fP (default with udev).
.
.HP
+.BR --bootformat
+.br
+Specify a one-line multi device setup information directly on the command line.
+See below for more information on the boot format.
+.
+.HP
.BR --checks
.br
Perform additional checks on the operations requested and report
@@ -610,9 +621,12 @@ Destroys the table in the inactive table slot for device_name.
.HP
.CMD_CREATE
.br
-Creates a device with the given name.
-If \fItable\fP or \fItable_file\fP is supplied, the table is loaded and made live.
-Otherwise a table is read from standard input unless \fB--notable\fP is used.
+Creates a device with the given name or multiple devices if boot format is used.
+If boot format is used we must supply a multiple device setup information
+from cmdline or standard input. See below for more information on the boot format.
+Otherwise, if \fItable\fP or \fItable_file\fP is supplied, the table is loaded
+and made live. Otherwise a table is read from standard input unless \fB--notable\fP
+is used.
The optional \fIuuid\fP can be used in place of
device_name in subsequent dmsetup commands.
If successful the device will appear in table and for live
@@ -824,6 +838,8 @@ Real encryption keys are suppressed in the table output for the crypt
target unless the \fB--showkeys\fP parameter is supplied. Kernel key
references prefixed with \fB:\fP are not affected by the parameter and get
displayed always.
+With \fB--bootformat\fP, the information relating to the specified targets
+is displayed in boot format style.
.
.HP
.CMD_TARGETS
@@ -996,6 +1012,53 @@ documentation directory for the device-mapper package.)
.br
2056320 2875602 linear /dev/hdb 1028160
.
+.SH BOOT FORMAT
+.
+Simple string of data separated by commas and optionally semi-colons, where:
+.br
+- A comma is used to separate the fields for one device.
+.br
+- A semi-colon is used to separate devices.
+.TP
+The string is of the form:
+.sp
+<name>,<uuid>,<flags>,<table>[,<table>+][;<dev_name>,<uuid>,<flags>,<table>[,<table>+]]
+.sp
+.TP
+One device args include:
+.
+.TP
+.B name
+The name of the device.
+.TP
+.B uuid
+The UUID of the device or empty
+.TP
+.B flags
+Supported flags are:
+.sp
+.B ro
+Sets the table being loaded for the device read-only (default)
+.br
+.B rw
+Sets the table being loaded for the device read-write
+.sp
+.TP
+.B table
+See table format above.
+.TP
+.
+.SH EXAMPLES
+.
+# A simple linear device
+.br
+test-linear-small,,ro,0 2097152 linear /dev/loop0 0, 2097152 2097152 linear /dev/loop1 0'
+.br
+# Two linear devices
+.br
+test-linear-small,,ro,0 2097152 linear /dev/loop0 0;test-linear-large,,rw, 0 2097152 linear /dev/loop1 0, 2097152 2097152 linear /dev/loop2 0
+.br
+.
.SH ENVIRONMENT VARIABLES
.
.TP
diff --git a/test/shell/dmsetup-bootformat.sh b/test/shell/dmsetup-bootformat.sh
new file mode 100644
index 0000000..8174ef4
--- /dev/null
+++ b/test/shell/dmsetup-bootformat.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+# Copyright (C) 2017 Red Hat, Inc. All rights reserved.
+#
+# 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
+
+# unrelated to lvm2 daemons
+SKIP_WITH_LVMLOCKD=1
+SKIP_WITH_LVMPOLLD=1
+SKIP_WITH_CLVMD=1
+SKIP_WITH_LVMETAD=1
+
+. lib/inittest
+
+LOOP="/dev/loop"
+
+for i in 0 1 2 3; do
+ [ -b /dev/loop${i} ] || skip "test requires /dev/loop${i}"
+done;
+
+# some constants
+UUID_A="457f0e3b-dc4b-4d39-81d4-2423a8ec0fa3"
+UUID_B="34e08a5b-8108-4939-b7e8-ba90b0b7d767"
+TABLES_A="0 2097152 linear ${LOOP}0 0, 2097152 2097152 linear ${LOOP}1 0"
+TABLES_B="0 2097152 linear ${LOOP}2 0, 2097152 2097152 linear ${LOOP}3 0"
+
+# test create/remove a single device
+dmsetup create --bootformat "test-linear-small,${UUID_A},rw,${TABLES_A}"
+dmsetup remove test-linear-small
+
+# test create/remove multiple devices
+dmsetup create --bootformat "test-linear-small,${UUID_A},rw,${TABLES_A};test-linear-large,${UUID_B},rw,${TABLES_B}"
+dmsetup remove test-linear-small test-linear-large
+
+# test empty fields
+dmsetup create --bootformat "test-linear-small,,,${TABLES_A}"
+dmsetup remove test-linear-small
+
+# test flags options (rw | ro | <empty>)
+# flag : rw
+dmsetup create --bootformat "test-linear-small,${UUID_A},rw,${TABLES_A}"
+# check that READ-ONLY string is not in State line
+str=`dmsetup info test-linear-small | grep 'State:'`
+test "${str#*READ-ONLY}" == "$str"
+dmsetup remove test-linear-small
+# flag : ro
+dmsetup create --bootformat "test-linear-small,${UUID_A},ro,${TABLES_A}"
+# check that READ-ONLY string is in State line
+str=`dmsetup info test-linear-small | grep 'State:'`
+test "${str#*READ-ONLY}" != "$str"
+dmsetup remove test-linear-small
+# flag : <emtpy>
+dmsetup create --bootformat "test-linear-small,${UUID_A},,${TABLES_A}"
+# check that READ-ONLY string is in State line
+str=`dmsetup info test-linear-small | grep 'State:'`
+test "${str#*READ-ONLY}" != "$str"
+dmsetup remove test-linear-small
+
+# let's try some scaped names
+declare -A escaped_names=(
+ ["test,linear,small"]="test\,linear\,small"
+ [",test,,linear,"]="\,test\,\,linear\,"
+ ["test;linear;small"]="test\;linear\;small"
+ [";test;;linear;"]="\;test\;\;linear\;"
+ [";test,linear;small,"]="\;test\,linear\;small\,"
+)
+
+for name in "${!escaped_names[@]}"
+do
+ dmsetup create --bootformat "${escaped_names[$name]},,ro,${TABLES_A}"
+ dmsetup remove "${name}"
+done
diff --git a/tools/dmsetup.c b/tools/dmsetup.c
index fc99145..dec3c84 100644
--- a/tools/dmsetup.c
+++ b/tools/dmsetup.c
@@ -1097,6 +1097,110 @@ out:
return r;
}

+static int _parse_device(char *dev_info)
+{
+ int r = 0;
+ struct dm_task *dmt;
+ uint32_t cookie = 0;
+ uint16_t udev_flags = 0;
+ int line = 0, field = 0;
+ char *str = dev_info, *ptr = dev_info;
+
+ if (!(dmt = dm_task_create(DM_DEVICE_CREATE)))
+ return_0;
+
+ while ((str = dm_find_unescaped_char(&ptr, ',')) != NULL ) {
+ str = dm_unescape_colons(str);
+ switch (field) {
+ case 0: /* set device name */
+ if (!dm_task_set_name(dmt, str))
+ goto_out;
+ break;
+ case 1: /* set uuid if any */
+ if (strlen(str) && !dm_task_set_uuid(dmt, str))
+ goto_out;
+ break;
+ case 2:
+ /* set as read-only if flags = "ro" | "" */
+ if (!strncmp(str, "ro", strlen(str)) || !strlen(str)) {
+ if (!dm_task_set_ro(dmt))
+ goto_out;
+ } else if (!strncmp(str, "rw", strlen(str))) {
+ break;
+ } else
+ goto_out;
+ break;
+ default:
+ if (!_parse_line(dmt, str, "", line++))
+ goto_out;
+ break;
+ }
+ field++;
+ }
+
+ if (field < 4)
+ goto_out;
+
+ if (!_set_task_add_node(dmt))
+ goto_out;
+
+ if (_udev_cookie)
+ cookie = _udev_cookie;
+
+ if (_udev_only)
+ udev_flags |= DM_UDEV_DISABLE_LIBRARY_FALLBACK;
+
+ if (!dm_task_set_cookie(dmt, &cookie, udev_flags) ||
+ !_task_run(dmt))
+ goto_out;
+
+ r = 1;
+
+out:
+ if (!_udev_cookie)
+ (void) dm_udev_wait(cookie);
+
+ if (r && _switches[VERBOSE_ARG])
+ r = _display_info(dmt);
+
+ dm_task_destroy(dmt);
+
+ return r;
+}
+
+static int _create_multi_devices(const char *multi_dev_info)
+{
+ char *buffer = NULL, *dev_info, *ptr;
+ size_t buffer_size = LINE_SIZE;
+ int r = 0;
+
+ if (!(buffer = dm_malloc(buffer_size))) {
+ err("Failed to malloc line buffer.");
+ return 0;
+ }
+
+ if (!multi_dev_info) { /* get info from stdin */
+ if (!(getline(&buffer, &buffer_size, stdin) > 0))
+ goto_out;
+ } else if (!dm_strncpy(buffer, multi_dev_info, LINE_SIZE))
+ goto_out;
+
+ dev_info = ptr = buffer;
+
+ while ((dev_info = dm_find_unescaped_char(&ptr, ';')) != NULL ) {
+ dev_info = dm_unescape_semicolons(dev_info);
+ if (!_parse_device(dev_info))
+ goto_out;
+ }
+
+ r = 1;
+out:
+ memset(buffer, 0, buffer_size);
+ dm_free(buffer);
+
+ return r;
+}
+
static int _create(CMD_ARGS)
{
int r = 0;
@@ -1105,6 +1209,16 @@ static int _create(CMD_ARGS)
uint32_t cookie = 0;
uint16_t udev_flags = 0;

+ /* arguments are in bootformat style */
+ if (_switches[BOOTFORMAT_ARG]) {
+ if (argc > 1)
+ return 0;
+ if (argc == 1)
+ file = argv[0];
+ return _create_multi_devices(file);
+ }
+
+ /* otherwise */
if (argc == 2)
file = argv[1];

@@ -5909,7 +6023,8 @@ static struct command _dmsetup_commands[] = {
"\t [-U|--uid <uid>] [-G|--gid <gid>] [-M|--mode <octal_mode>]\n"
"\t [-u|uuid <uuid>] [--addnodeonresume|--addnodeoncreate]\n"
"\t [--readahead {[+]<sectors>|auto|none}]\n"
- "\t [-n|--notable|--table {<table>|<table_file>}]", 1, 2, 0, 0, _create},
+ "\t [-n|--notable|--table {<table>|<table_file>}]\n"
+ "\t [--bootformat {<multi_dev_info>|<multi_dev_info_file>}]", 0, 2, 0, 0, _create},
{"remove", "[--deferred] [-f|--force] [--retry] <device>...", 0, -1, 1, 0, _remove},
{"remove_all", "[-f|--force]", 0, 0, 0, 0, _remove_all},
{"suspend", "[--noflush] [--nolockfs] <device>...", 0, -1, 1, 0, _suspend},
@@ -6004,6 +6119,7 @@ static void _dmsetup_usage(FILE *out)
fprintf(out, "<mangling_mode> is one of 'none', 'auto' and 'hex'.\n");
fprintf(out, "<fields> are comma-separated. Use 'help -c' for list.\n");
fprintf(out, "Table_file contents may be supplied on stdin.\n");
+ fprintf(out, "Multi_dev_info_file contents may be supplied on stdin.\n");
fprintf(out, "Options are: devno, devname, blkdevname.\n");
fprintf(out, "Tree specific options are: ascii, utf, vt100; compact, inverted, notrunc;\n"
" blkdevname, [no]device, active, open, rw and uuid.\n");
--
2.9.3
Enric Balletbo i Serra
2017-05-16 15:20:39 UTC
Permalink
Add a new parameter to be able to output the bootformat of one or more
devices. The bootformat for a device can be obtained by doing a:

dmsetup table <device...> --bootformat

where device can be a specific device, a list of the devices or empty
for all devices. The output will look like this:

<dev_name>,<uuid>,<flags>,<table>[,<table>+][;<dev_name>,<uuid>,<flags>,<table>[,<table>+]]+

Signed-off-by: Enric Balletbo i Serra <***@collabora.com>
---
tools/dmsetup.c | 31 ++++++++++++++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)

diff --git a/tools/dmsetup.c b/tools/dmsetup.c
index 5c5c14c..fc99145 100644
--- a/tools/dmsetup.c
+++ b/tools/dmsetup.c
@@ -163,6 +163,7 @@ enum {
AREA_ARG,
AREAS_ARG,
AREA_SIZE_ARG,
+ BOOTFORMAT_ARG,
BOUNDS_ARG,
CHECKS_ARG,
CLEAR_ARG,
@@ -2127,6 +2128,9 @@ static int _status(CMD_ARGS)
int matched = 0;
int ls_only = 0;
struct dm_info info;
+ const char *uuid = NULL;
+ char bootstr[LINE_SIZE] = {0};
+ int len;

if (names)
name = names->name;
@@ -2171,6 +2175,9 @@ static int _status(CMD_ARGS)
if (!name)
name = dm_task_get_name(dmt);

+ if (!uuid)
+ uuid = dm_task_get_uuid(dmt);
+
/* Fetch targets and print 'em */
do {
next = dm_get_next_target(dmt, next, &start, &length,
@@ -2184,6 +2191,20 @@ static int _status(CMD_ARGS)
_switches[VERBOSE_ARG])
_display_dev(dmt, name);
next = NULL;
+ } else if (_switches[BOOTFORMAT_ARG]) {
+ if (!dm_snprintf(bootstr, sizeof(bootstr), "%s,%s,%s",
+ dm_task_get_name(dmt), uuid,
+ info.read_only ? "ro" : "rw"))
+ goto_out;
+ if (target_type) {
+ len = strlen(bootstr);
+ if (!dm_snprintf(bootstr + len,
+ sizeof(bootstr) - len,
+ "," FMTu64 " " FMTu64 " %s %s",
+ start, length, target_type,
+ params))
+ goto_out;
+ }
} else if (!_switches[EXEC_ARG] || !_command_to_exec ||
_switches[VERBOSE_ARG]) {
if (!matched && _switches[VERBOSE_ARG])
@@ -2218,6 +2239,9 @@ static int _status(CMD_ARGS)
matched = 1;
} while (next);

+ if (matched && _switches[BOOTFORMAT_ARG])
+ printf("%s;", bootstr);
+
if (multiple_devices && _switches[VERBOSE_ARG] && matched && !ls_only)
putchar('\n');

@@ -5903,7 +5927,9 @@ static struct command _dmsetup_commands[] = {
{"deps", "[-o <options>] [<device>...]", 0, -1, 1, 0, _deps},
{"stats", "<command> [<options>] [<device>...]", 1, -1, 1, 1, _stats},
{"status", "[<device>...] [--noflush] [--target <target_type>]", 0, -1, 1, 0, _status},
- {"table", "[<device>...] [--target <target_type>] [--showkeys]", 0, -1, 1, 0, _status},
+ {"table", "[<device>...]\n"
+ "\t [--target <target_type>] [--showkeys]\n"
+ "\t [--bootformat]", 0, -1, 1, 0, _status},
{"wait", "<device> [<event_nr>] [--noflush]", 0, 2, 0, 0, _wait},
{"mknodes", "[<device>...]", 0, -1, 1, 0, _mknodes},
{"mangle", "[<device>...]", 0, -1, 1, 0, _mangle},
@@ -6468,6 +6494,7 @@ static int _process_switches(int *argcp, char ***argvp, const char *dev_dir)
{"area", 0, &ind, AREA_ARG},
{"areas", 1, &ind, AREAS_ARG},
{"areasize", 1, &ind, AREA_SIZE_ARG},
+ {"bootformat", 0, &ind, BOOTFORMAT_ARG},
{"bounds", 1, &ind, BOUNDS_ARG},
{"checks", 0, &ind, CHECKS_ARG},
{"clear", 0, &ind, CLEAR_ARG},
@@ -6636,6 +6663,8 @@ static int _process_switches(int *argcp, char ***argvp, const char *dev_dir)
return_0;
if (c == 'h' || ind == HELP_ARG)
_switches[HELP_ARG]++;
+ if (ind == BOOTFORMAT_ARG)
+ _switches[BOOTFORMAT_ARG]++;
if (ind == BOUNDS_ARG) {
_switches[BOUNDS_ARG]++;
_string_args[BOUNDS_ARG] = optarg;
--
2.9.3
Enric Balletbo i Serra
2017-05-16 15:20:38 UTC
Permalink
Add helper functions to split a string into tokens ignoring escaped
chars. The function finds the first token in the string that is delimited
by one non escaped char. In case it founds an escaped token finds next
token.

When a token is found the string is terminated by overwriting the
delimiter with a null byte ('\0') and the pointer to the search string
is updated to point past the token ready for the next call.

In case no delimiter was found, the token is taken to be the entire
string.

Aditionally there is dm_unescape_colons and dm_unescape_semicolons
helpers to unescape these characters in situ, it replaces all occurrences
of "\," or "\;" with ',' or ';'. This is normally used to unescape colons
and semi-colons used in boot format.

Signed-off-by: Enric Balletbo i Serra <***@collabora.com>
---
libdm/.exported_symbols.Base | 3 ++
libdm/libdevmapper.h | 17 +++++++++
libdm/libdm-string.c | 82 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 102 insertions(+)

diff --git a/libdm/.exported_symbols.Base b/libdm/.exported_symbols.Base
index 4dc5c93..51c21de 100644
--- a/libdm/.exported_symbols.Base
+++ b/libdm/.exported_symbols.Base
@@ -66,6 +66,7 @@ dm_dump_memory_debug
dm_escaped_len
dm_escape_double_quotes
dm_fclose
+dm_find_unescaped_char
dm_format_dev
dm_free_aux
dm_get_library_version
@@ -279,8 +280,10 @@ dm_udev_get_sync_support
dm_udev_set_checking
dm_udev_set_sync_support
dm_udev_wait
+dm_unescape_colons
dm_unescape_colons_and_at_signs
dm_unescape_double_quotes
+dm_unescape_semicolons
dm_units_to_factor
dm_uuid_prefix
dm_vasprintf
diff --git a/libdm/libdevmapper.h b/libdm/libdevmapper.h
index 4bd32b4..7b6c1c0 100644
--- a/libdm/libdevmapper.h
+++ b/libdm/libdevmapper.h
@@ -2662,6 +2662,23 @@ void dm_unescape_colons_and_at_signs(char *src,
*/
int dm_strncpy(char *dest, const char *src, size_t n);

+char *dm_unescape_colons(char *str);
+
+char *dm_unescape_semicolons(char *str);
+
+/*
+ * Splits a string into tokens ignoring escaped chars
+ *
+ * Updates @s to point after the token, ready for the next call.
+ *
+ * @s: The string to be searched
+ * @c: The character to search for
+ *
+ * Returns:
+ * The string found or NULL.
+ */
+char *dm_find_unescaped_char(char **str, const char c);
+
/*
* Recognize unit specifier in the 'units' arg and return a factor
* representing that unit. If the 'units' contains a prefix with digits,
diff --git a/libdm/libdm-string.c b/libdm/libdm-string.c
index 2085aa8..667f8dd 100644
--- a/libdm/libdm-string.c
+++ b/libdm/libdm-string.c
@@ -444,6 +444,88 @@ int dm_strncpy(char *dest, const char *src, size_t n)
return 0;
}

+/*
+ * Unescape characters in situ, it replaces all occurrences of "\c"
+ * with 'c'. This is normally used to unescape colons and semi-colons used
+ * in boot format.
+ */
+static char *_unescape_char(char *str, const char c)
+{
+ int i = 0, j = 0;
+ int len = strlen(str);
+
+ if (len < 2)
+ return str;
+
+ while (j < len - 1) {
+ if (str[j] == '\\' && str[j + 1] == c) {
+ j = j + 2;
+ str[i++] = c;
+ continue;
+ }
+ str[i++] = str[j++];
+ }
+
+ if (j == len - 1)
+ str[i++] = str[j];
+
+ str[i] = '\0';
+
+ return str;
+}
+
+char *dm_unescape_colons(char *str)
+{
+ return _unescape_char(str, ',');
+}
+
+char *dm_unescape_semicolons(char *str)
+{
+ return _unescape_char(str, ';');
+}
+
+#define is_even(a) (((a) & 1) == 0)
+
+/*
+ * Splits a string into tokens ignoring escaped chars
+ *
+ * Updates @s to point after the token, ready for the next call.
+ *
+ * @str: The string to be searched
+ * @c: The character to search for
+ *
+ * Returns:
+ * The string found or NULL.
+ */
+char *dm_find_unescaped_char(char **str, const char c)
+{
+ char *s = *str;
+ char *p = strchr(*str, c);
+
+ /* loop through all the characters */
+ while (p != NULL) {
+ /* scan backwards through preceding escapes */
+ char* q = p;
+ while (q > s && *(q - 1) == '\\')
+ --q;
+ /* even number of escapes so c is a token */
+ if (is_even( p - q )) {
+ *p = '\0';
+ *str = p + 1;
+ return s;
+ }
+ /* else odd escapes so c is escaped, keep going */
+ p = strchr(p + 1, c);
+ }
+
+ if (strlen(*str)) {
+ *str += strlen(*str);
+ return s;
+ }
+
+ return NULL;
+}
+
/* Test if the doubles are close enough to be considered equal */
static int _close_enough(double d1, double d2)
{
--
2.9.3
Loading...