--- Begin Message ---
Package: systemd
Version: 232-8
Severity: normal
Tags: patch
User: ubuntu-de...@lists.ubuntu.com
Usertags: origin-ubuntu zesty ubuntu-patch
Dear Maintainer,
udev creates symlinks for all NVMe drives at /dev/disk/by-id/nvme-MODEL_SERIAL.
The MODEL and SERIAL strings come directly from the NVMe device, and may
contain whitespace; if they do, this breaks the creation of the symlink
because udev treats it as multiple symlinks, separated by the whitespace.
These patches, which are merged upstream, change udev default behavior to
replace all whitespace, when added to a SYMLINK value as a result of variable
substitution, with underscores. This results in a single symlink for each
NVMe drive, which is the correct behavior. Note that this also fixes any
broken symlinks for other devices/buses that are a result of unexpected
whitespace included in variables.
This is tracked upstream in github bug 4833:
https://github.com/systemd/systemd/issues/4833
and in Ubuntu in launchpad bug 1647485:
https://bugs.launchpad.net/ubuntu/+source/systemd/+bug/1647485
diff -Nru systemd-232/debian/patches/0001-libudev-util-change-util_replace_whitespace-to-retur.patch systemd-232/debian/patches/0001-libudev-util-change-util_replace_whitespace-to-retur.patch
--- systemd-232/debian/patches/0001-libudev-util-change-util_replace_whitespace-to-retur.patch 1969-12-31 19:00:00.000000000 -0500
+++ systemd-232/debian/patches/0001-libudev-util-change-util_replace_whitespace-to-retur.patch 2017-01-12 09:55:40.000000000 -0500
@@ -0,0 +1,32 @@
+From a9d99b32a34589777e95898dac0597dbfbede4ea Mon Sep 17 00:00:00 2001
+From: Dan Streetman <ddstr...@ieee.org>
+Date: Tue, 3 Jan 2017 14:31:45 -0500
+Subject: [PATCH 1/3] libudev-util: change util_replace_whitespace to return
+ number of chars in dest
+
+Instead of returning 0, which is unhelpful, return the number of chars
+copied into the dest string. This allows callers that care about that
+to easily use it, instead of having to calculate the strlen.
+
+No current users of the function check the return value, so this does not
+break any existing code; it is used in the following patch.
+---
+ src/libudev/libudev-util.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/libudev/libudev-util.c b/src/libudev/libudev-util.c
+index 574cfea..a9819b9 100644
+--- a/src/libudev/libudev-util.c
++++ b/src/libudev/libudev-util.c
+@@ -186,7 +186,7 @@ int util_replace_whitespace(const char *str, char *to, size_t len)
+ to[j++] = str[i++];
+ }
+ to[j] = '\0';
+- return 0;
++ return j;
+ }
+
+ /* allow chars in whitelist, plain ascii, hex-escaping and valid utf8 */
+--
+2.9.3
+
diff -Nru systemd-232/debian/patches/0002-udev-event-add-replace_whitespace-param-to-udev_even.patch systemd-232/debian/patches/0002-udev-event-add-replace_whitespace-param-to-udev_even.patch
--- systemd-232/debian/patches/0002-udev-event-add-replace_whitespace-param-to-udev_even.patch 1969-12-31 19:00:00.000000000 -0500
+++ systemd-232/debian/patches/0002-udev-event-add-replace_whitespace-param-to-udev_even.patch 2017-01-12 09:55:40.000000000 -0500
@@ -0,0 +1,309 @@
+From e20a917105b9c41e7e552ca5f11f9077897db505 Mon Sep 17 00:00:00 2001
+From: Dan Streetman <ddstr...@ieee.org>
+Date: Tue, 3 Jan 2017 14:37:59 -0500
+Subject: [PATCH 2/3] udev-event: add replace_whitespace param to
+ udev_event_apply_format
+
+If replace_whitespace is true, each substitution value has all its
+whitespace removed/replaced by util_replace_whitespace (except the
+SUBST_RESULT substitution - $result{} or %c{} - which handles spaces
+itself as field separators). All existing callers are updated to
+pass false, so no functional change is made by this patch.
+
+This is needed so the SYMLINK assignment can replace any spaces
+introduced through variable substitution, becuase the SYMLINK value is
+a space-separated list of symlinks to create. Any variables that
+contain spaces will thus unexpectedly change the symlink value from
+a single symlink to multiple incorrectly-named symlinks.
+
+This is used in the next patch, which enables the whitespace
+replacement for SYMLINK variable substitution.
+---
+ src/udev/udev-event.c | 39 +++++++++++++++++++++++++++++++++++----
+ src/udev/udev-rules.c | 40 ++++++++++++++++++++--------------------
+ src/udev/udev.h | 4 +++-
+ src/udev/udevadm-test.c | 2 +-
+ 4 files changed, 59 insertions(+), 26 deletions(-)
+
+diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c
+index 304a287..be7c736 100644
+--- a/src/udev/udev-event.c
++++ b/src/udev/udev-event.c
+@@ -73,7 +73,9 @@ void udev_event_unref(struct udev_event *event) {
+ free(event);
+ }
+
+-size_t udev_event_apply_format(struct udev_event *event, const char *src, char *dest, size_t size) {
++size_t udev_event_apply_format(struct udev_event *event,
++ const char *src, char *dest, size_t size,
++ bool replace_whitespace) {
+ struct udev_device *dev = event->dev;
+ enum subst_type {
+ SUBST_UNKNOWN,
+@@ -130,8 +132,10 @@ size_t udev_event_apply_format(struct udev_event *event, const char *src, char *
+
+ for (;;) {
+ enum subst_type type = SUBST_UNKNOWN;
+- char attrbuf[UTIL_PATH_SIZE];
+- char *attr = NULL;
++ char attrbuf[UTIL_PATH_SIZE], sbuf[UTIL_PATH_SIZE];
++ char *attr = NULL, *_s;
++ size_t _l;
++ bool replws = replace_whitespace;
+
+ while (from[0] != '\0') {
+ if (from[0] == '$') {
+@@ -200,6 +204,19 @@ subst:
+ attr = NULL;
+ }
+
++ /* result subst handles space as field separator */
++ if (type == SUBST_RESULT)
++ replws = false;
++
++ if (replws) {
++ /* store dest string ptr and remaining len */
++ _s = s;
++ _l = l;
++ /* temporarily use sbuf */
++ s = &sbuf;
++ l = UTIL_PATH_SIZE;
++ }
++
+ switch (type) {
+ case SUBST_DEVPATH:
+ l = strpcpy(&s, l, udev_device_get_devpath(dev));
+@@ -380,6 +397,20 @@ subst:
+ log_error("unknown substitution type=%i", type);
+ break;
+ }
++
++ /* replace whitespace in sbuf and copy to dest */
++ if (replws) {
++ size_t tmplen = UTIL_PATH_SIZE - l;
++
++ /* restore s and l to dest string values */
++ s = _s;
++ l = _l;
++
++ /* copy ws-replaced value to s */
++ tmplen = util_replace_whitespace(sbuf, s, MIN(tmplen, l));
++ l -= tmplen;
++ s += tmplen;
++ }
+ }
+
+ out:
+@@ -927,7 +958,7 @@ void udev_event_execute_run(struct udev_event *event, usec_t timeout_usec, usec_
+ const char *cmd = udev_list_entry_get_name(list_entry);
+ enum udev_builtin_cmd builtin_cmd = udev_list_entry_get_num(list_entry);
+
+- udev_event_apply_format(event, cmd, command, sizeof(command));
++ udev_event_apply_format(event, cmd, command, sizeof(command), false);
+
+ if (builtin_cmd < UDEV_BUILTIN_MAX)
+ udev_builtin_run(event->dev, builtin_cmd, command, false);
+diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c
+index b023822..c688120 100644
+--- a/src/udev/udev-rules.c
++++ b/src/udev/udev-rules.c
+@@ -1676,7 +1676,7 @@ static int match_attr(struct udev_rules *rules, struct udev_device *dev, struct
+ name = rules_str(rules, cur->key.attr_off);
+ switch (cur->key.attrsubst) {
+ case SB_FORMAT:
+- udev_event_apply_format(event, name, nbuf, sizeof(nbuf));
++ udev_event_apply_format(event, name, nbuf, sizeof(nbuf), false);
+ name = nbuf;
+ /* fall through */
+ case SB_NONE:
+@@ -1838,7 +1838,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ _cleanup_free_ char *value = NULL;
+ size_t len;
+
+- udev_event_apply_format(event, rules_str(rules, cur->key.attr_off), filename, sizeof(filename));
++ udev_event_apply_format(event, rules_str(rules, cur->key.attr_off), filename, sizeof(filename), false);
+ sysctl_normalize(filename);
+ if (sysctl_read(filename, &value) < 0)
+ goto nomatch;
+@@ -1916,7 +1916,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ struct stat statbuf;
+ int match;
+
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), filename, sizeof(filename));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), filename, sizeof(filename), false);
+ if (util_resolve_subsys_kernel(event->udev, filename, filename, sizeof(filename), 0) != 0) {
+ if (filename[0] != '/') {
+ char tmp[UTIL_PATH_SIZE];
+@@ -1942,7 +1942,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ char result[UTIL_LINE_SIZE];
+
+ event->program_result = mfree(event->program_result);
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), program, sizeof(program));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), program, sizeof(program), false);
+ log_debug("PROGRAM '%s' %s:%u",
+ program,
+ rules_str(rules, rule->rule.filename_off),
+@@ -1969,7 +1969,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ case TK_M_IMPORT_FILE: {
+ char import[UTIL_PATH_SIZE];
+
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import), false);
+ if (import_file_into_properties(event->dev, import) != 0)
+ if (cur->key.op != OP_NOMATCH)
+ goto nomatch;
+@@ -1978,7 +1978,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ case TK_M_IMPORT_PROG: {
+ char import[UTIL_PATH_SIZE];
+
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import), false);
+ log_debug("IMPORT '%s' %s:%u",
+ import,
+ rules_str(rules, rule->rule.filename_off),
+@@ -2009,7 +2009,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ event->builtin_run |= (1 << cur->key.builtin_cmd);
+ }
+
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), command, sizeof(command));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), command, sizeof(command), false);
+ log_debug("IMPORT builtin '%s' %s:%u",
+ udev_builtin_name(cur->key.builtin_cmd),
+ rules_str(rules, rule->rule.filename_off),
+@@ -2077,7 +2077,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ case TK_M_IMPORT_PARENT: {
+ char import[UTIL_PATH_SIZE];
+
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import), false);
+ if (import_parent_into_properties(event->dev, import) != 0)
+ if (cur->key.op != OP_NOMATCH)
+ goto nomatch;
+@@ -2115,7 +2115,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ break;
+ if (cur->key.op == OP_ASSIGN_FINAL)
+ event->owner_final = true;
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), owner, sizeof(owner));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), owner, sizeof(owner), false);
+ event->owner_set = true;
+ r = get_user_creds(&ow, &event->uid, NULL, NULL, NULL);
+ if (r < 0) {
+@@ -2141,7 +2141,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ break;
+ if (cur->key.op == OP_ASSIGN_FINAL)
+ event->group_final = true;
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), group, sizeof(group));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), group, sizeof(group), false);
+ event->group_set = true;
+ r = get_group_creds(&gr, &event->gid);
+ if (r < 0) {
+@@ -2165,7 +2165,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+
+ if (event->mode_final)
+ break;
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), mode_str, sizeof(mode_str));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), mode_str, sizeof(mode_str), false);
+ mode = strtol(mode_str, &endptr, 8);
+ if (endptr[0] != '\0') {
+ log_error("ignoring invalid mode '%s'", mode_str);
+@@ -2222,7 +2222,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ const char *name, *label;
+
+ name = rules_str(rules, cur->key.attr_off);
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), label_str, sizeof(label_str));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), label_str, sizeof(label_str), false);
+ if (label_str[0] != '\0')
+ label = label_str;
+ else
+@@ -2256,10 +2256,10 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ char temp[UTIL_NAME_SIZE];
+
+ /* append value separated by space */
+- udev_event_apply_format(event, value, temp, sizeof(temp));
++ udev_event_apply_format(event, value, temp, sizeof(temp), false);
+ strscpyl(value_new, sizeof(value_new), value_old, " ", temp, NULL);
+ } else
+- udev_event_apply_format(event, value, value_new, sizeof(value_new));
++ udev_event_apply_format(event, value, value_new, sizeof(value_new), false);
+
+ udev_device_add_property(event->dev, name, value_new);
+ break;
+@@ -2268,7 +2268,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ char tag[UTIL_PATH_SIZE];
+ const char *p;
+
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), tag, sizeof(tag));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), tag, sizeof(tag), false);
+ if (cur->key.op == OP_ASSIGN || cur->key.op == OP_ASSIGN_FINAL)
+ udev_device_cleanup_tags_list(event->dev);
+ for (p = tag; *p != '\0'; p++) {
+@@ -2296,7 +2296,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ break;
+ if (cur->key.op == OP_ASSIGN_FINAL)
+ event->name_final = true;
+- udev_event_apply_format(event, name, name_str, sizeof(name_str));
++ udev_event_apply_format(event, name, name_str, sizeof(name_str), false);
+ if (esc == ESCAPE_UNSET || esc == ESCAPE_REPLACE) {
+ count = util_replace_chars(name_str, "/");
+ if (count > 0)
+@@ -2336,7 +2336,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ udev_device_cleanup_devlinks_list(event->dev);
+
+ /* allow multiple symlinks separated by spaces */
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), temp, sizeof(temp));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), temp, sizeof(temp), false);
+ if (esc == ESCAPE_UNSET)
+ count = util_replace_chars(temp, "/ ");
+ else if (esc == ESCAPE_REPLACE)
+@@ -2376,7 +2376,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ strscpyl(attr, sizeof(attr), udev_device_get_syspath(event->dev), "/", key_name, NULL);
+ attr_subst_subdir(attr, sizeof(attr));
+
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), value, sizeof(value));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), value, sizeof(value), false);
+ log_debug("ATTR '%s' writing '%s' %s:%u", attr, value,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
+@@ -2392,9 +2392,9 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ char value[UTIL_NAME_SIZE];
+ int r;
+
+- udev_event_apply_format(event, rules_str(rules, cur->key.attr_off), filename, sizeof(filename));
++ udev_event_apply_format(event, rules_str(rules, cur->key.attr_off), filename, sizeof(filename), false);
+ sysctl_normalize(filename);
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), value, sizeof(value));
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), value, sizeof(value), false);
+ log_debug("SYSCTL '%s' writing '%s' %s:%u", filename, value,
+ rules_str(rules, rule->rule.filename_off), rule->rule.filename_line);
+ r = sysctl_write(filename, value);
+diff --git a/src/udev/udev.h b/src/udev/udev.h
+index 8433e8d..c0cb7ea 100644
+--- a/src/udev/udev.h
++++ b/src/udev/udev.h
+@@ -80,7 +80,9 @@ int udev_rules_apply_static_dev_perms(struct udev_rules *rules);
+ /* udev-event.c */
+ struct udev_event *udev_event_new(struct udev_device *dev);
+ void udev_event_unref(struct udev_event *event);
+-size_t udev_event_apply_format(struct udev_event *event, const char *src, char *dest, size_t size);
++size_t udev_event_apply_format(struct udev_event *event,
++ const char *src, char *dest, size_t size,
++ bool replace_whitespace);
+ int udev_event_apply_subsys_kernel(struct udev_event *event, const char *string,
+ char *result, size_t maxsize, int read_value);
+ int udev_event_spawn(struct udev_event *event,
+diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c
+index 702dbe5..07b667f 100644
+--- a/src/udev/udevadm-test.c
++++ b/src/udev/udevadm-test.c
+@@ -144,7 +144,7 @@ static int adm_test(struct udev *udev, int argc, char *argv[]) {
+ udev_list_entry_foreach(entry, udev_list_get_entry(&event->run_list)) {
+ char program[UTIL_PATH_SIZE];
+
+- udev_event_apply_format(event, udev_list_entry_get_name(entry), program, sizeof(program));
++ udev_event_apply_format(event, udev_list_entry_get_name(entry), program, sizeof(program), false);
+ printf("run: '%s'\n", program);
+ }
+ out:
+--
+2.9.3
+
diff -Nru systemd-232/debian/patches/0003-udev-rules-perform-whitespace-replacement-for-symlin.patch systemd-232/debian/patches/0003-udev-rules-perform-whitespace-replacement-for-symlin.patch
--- systemd-232/debian/patches/0003-udev-rules-perform-whitespace-replacement-for-symlin.patch 1969-12-31 19:00:00.000000000 -0500
+++ systemd-232/debian/patches/0003-udev-rules-perform-whitespace-replacement-for-symlin.patch 2017-01-12 09:55:40.000000000 -0500
@@ -0,0 +1,35 @@
+From 0a10235ed4538a80f5fc7dbbf79e3a1c178f5179 Mon Sep 17 00:00:00 2001
+From: Dan Streetman <ddstr...@ieee.org>
+Date: Tue, 3 Jan 2017 14:39:50 -0500
+Subject: [PATCH 3/3] udev-rules: perform whitespace replacement for symlink
+ subst values
+
+If the string_escape option is either unset or 'replace' (i.e. if it is
+not 'none'), then enable whitespace replacement in SYMLINK variable
+substitution values, as added in the last patch.
+
+This will keep any whitespace that is directly contained in a SYMLINK
+value, but will replace any whitespace that is added to the SYMLINK
+value as a result of variable substitution (except $result/%c).
+
+This fixes bug 4833.
+---
+ src/udev/udev-rules.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c
+index c688120..4d07b8f 100644
+--- a/src/udev/udev-rules.c
++++ b/src/udev/udev-rules.c
+@@ -2336,7 +2336,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
+ udev_device_cleanup_devlinks_list(event->dev);
+
+ /* allow multiple symlinks separated by spaces */
+- udev_event_apply_format(event, rules_str(rules, cur->key.value_off), temp, sizeof(temp), false);
++ udev_event_apply_format(event, rules_str(rules, cur->key.value_off), temp, sizeof(temp), esc != ESCAPE_NONE);
+ if (esc == ESCAPE_UNSET)
+ count = util_replace_chars(temp, "/ ");
+ else if (esc == ESCAPE_REPLACE)
+--
+2.9.3
+
diff -Nru systemd-232/debian/patches/series systemd-232/debian/patches/series
--- systemd-232/debian/patches/series 2016-12-16 19:54:18.000000000 -0500
+++ systemd-232/debian/patches/series 2017-01-12 09:55:40.000000000 -0500
@@ -33,3 +33,6 @@
debian/Revert-core-set-RLIMIT_CORE-to-unlimited-by-default.patch
debian/Revert-core-enable-TasksMax-for-all-services-by-default-a.patch
debian/Let-graphical-session-pre.target-be-manually-started.patch
+0001-libudev-util-change-util_replace_whitespace-to-retur.patch
+0002-udev-event-add-replace_whitespace-param-to-udev_even.patch
+0003-udev-rules-perform-whitespace-replacement-for-symlin.patch
--- End Message ---