From f822c5d59e20c0f3b3f715ae16d2dd26003ef97e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 2 Jun 2019 00:44:02 +0900 Subject: [PATCH 1/7] util: make argument "subsystem" in device_wait_for_initialization() optional --- src/shared/udev-util.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/shared/udev-util.c b/src/shared/udev-util.c index 054feeda76..1ca5080d1c 100644 --- a/src/shared/udev-util.c +++ b/src/shared/udev-util.c @@ -3,6 +3,7 @@ #include #include "alloc-util.h" +#include "device-util.h" #include "env-file.h" #include "log.h" #include "parse-util.h" @@ -119,7 +120,6 @@ int device_wait_for_initialization(sd_device *device, const char *subsystem, sd_ int r; assert(device); - assert(subsystem); if (sd_device_get_is_initialized(device) > 0) { if (ret) @@ -139,9 +139,17 @@ int device_wait_for_initialization(sd_device *device, const char *subsystem, sd_ if (r < 0) return log_error_errno(r, "Failed to acquire monitor: %m"); - r = sd_device_monitor_filter_add_match_subsystem_devtype(monitor, subsystem, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add %s subsystem match to monitor: %m", subsystem); + if (!subsystem) { + r = sd_device_get_subsystem(device, &subsystem); + if (r < 0 && r != -ENOENT) + return log_device_error_errno(device, r, "Failed to get subsystem: %m"); + } + + if (subsystem) { + r = sd_device_monitor_filter_add_match_subsystem_devtype(monitor, subsystem, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add %s subsystem match to monitor: %m", subsystem); + } r = sd_device_monitor_attach_event(monitor, event); if (r < 0) From 1b47436e0ecb67dd6d0f2ddc85d0738f0b92b4c0 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 2 Jun 2019 01:01:31 +0900 Subject: [PATCH 2/7] util: make device_wait_for_initialization() optionally takes timeout value --- src/rfkill/rfkill.c | 2 +- src/shared/dissect-image.c | 4 ++-- src/shared/udev-util.c | 17 +++++++++++++++-- src/shared/udev-util.h | 2 +- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/rfkill/rfkill.c b/src/rfkill/rfkill.c index fe26566fa3..f9d309c909 100644 --- a/src/rfkill/rfkill.c +++ b/src/rfkill/rfkill.c @@ -109,7 +109,7 @@ static int determine_state_file( if (r < 0) return r; - r = device_wait_for_initialization(d, "rfkill", &device); + r = device_wait_for_initialization(d, "rfkill", USEC_INFINITY, &device); if (r < 0) return r; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 168fa9cda5..95ee7efa92 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -181,7 +181,7 @@ static int wait_for_partitions_to_appear( continue; if (!FLAGS_SET(flags, DISSECT_IMAGE_NO_UDEV)) { - r = device_wait_for_initialization(q, "block", NULL); + r = device_wait_for_initialization(q, "block", USEC_INFINITY, NULL); if (r < 0) return r; } @@ -251,7 +251,7 @@ static int loop_wait_for_partitions_to_appear( log_debug("Waiting for device (parent + %d partitions) to appear...", num_partitions); if (!FLAGS_SET(flags, DISSECT_IMAGE_NO_UDEV)) { - r = device_wait_for_initialization(d, "block", &device); + r = device_wait_for_initialization(d, "block", USEC_INFINITY, &device); if (r < 0) return r; } else diff --git a/src/shared/udev-util.c b/src/shared/udev-util.c index 1ca5080d1c..482d914d07 100644 --- a/src/shared/udev-util.c +++ b/src/shared/udev-util.c @@ -113,8 +113,13 @@ static int device_monitor_handler(sd_device_monitor *monitor, sd_device *device, return 0; } -int device_wait_for_initialization(sd_device *device, const char *subsystem, sd_device **ret) { +static int device_timeout_handler(sd_event_source *s, uint64_t usec, void *userdata) { + return sd_event_exit(sd_event_source_get_event(s), -ETIMEDOUT); +} + +int device_wait_for_initialization(sd_device *device, const char *subsystem, usec_t timeout, sd_device **ret) { _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *timeout_source = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; struct DeviceMonitorData data = {}; int r; @@ -159,6 +164,14 @@ int device_wait_for_initialization(sd_device *device, const char *subsystem, sd_ if (r < 0) return log_error_errno(r, "Failed to start device monitor: %m"); + if (timeout != USEC_INFINITY) { + r = sd_event_add_time(event, &timeout_source, + CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + timeout, 0, + device_timeout_handler, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add timeout event source: %m"); + } + /* Check again, maybe things changed. Udev will re-read the db if the device wasn't initialized * yet. */ if (sd_device_get_is_initialized(device) > 0) { @@ -169,7 +182,7 @@ int device_wait_for_initialization(sd_device *device, const char *subsystem, sd_ r = sd_event_loop(event); if (r < 0) - return log_error_errno(r, "Event loop failed: %m"); + return log_error_errno(r, "Failed to wait for device to be initialized: %m"); if (ret) *ret = TAKE_PTR(data.device); diff --git a/src/shared/udev-util.h b/src/shared/udev-util.h index 3c45447515..f62c4c56e3 100644 --- a/src/shared/udev-util.h +++ b/src/shared/udev-util.h @@ -27,6 +27,6 @@ static inline int udev_parse_config(void) { return udev_parse_config_full(NULL, NULL, NULL, NULL); } -int device_wait_for_initialization(sd_device *device, const char *subsystem, sd_device **ret); +int device_wait_for_initialization(sd_device *device, const char *subsystem, usec_t timeout, sd_device **ret); int device_is_renaming(sd_device *dev); bool device_for_action(sd_device *dev, DeviceAction action); From ae760f4b165353ef4f0e84179131791c7a619550 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 2 Jun 2019 01:02:09 +0900 Subject: [PATCH 3/7] udevadm: add --wait-for-initialization option to "udevadm info" --- man/udevadm.xml | 8 +++++++ shell-completion/bash/udevadm | 3 ++- src/udev/udevadm-info.c | 45 +++++++++++++++++++++++++---------- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/man/udevadm.xml b/man/udevadm.xml index a591ab8c34..396d418f83 100644 --- a/man/udevadm.xml +++ b/man/udevadm.xml @@ -175,6 +175,14 @@ Cleanup the udev database. + + + + + Wait for device to be initialized. If argument SECONDS + is not specified, the default is to wait forever. + + diff --git a/shell-completion/bash/udevadm b/shell-completion/bash/udevadm index 0e9dbb0c4e..c151a41978 100644 --- a/shell-completion/bash/udevadm +++ b/shell-completion/bash/udevadm @@ -48,7 +48,8 @@ _udevadm() { local -A OPTS=( [COMMON]='-h --help -V --version' [DEBUG]='-d --debug' - [INFO_STANDALONE]='-r --root -a --attribute-walk -x --export -e --export-db -c --cleanup-db' + [INFO_STANDALONE]='-r --root -a --attribute-walk -x --export -e --export-db -c --cleanup-db + -w --wait-for-initialization' [INFO_ARG]='-q --query -p --path -n --name -P --export-prefix -d --device-id-of-file' [TRIGGER_STANDALONE]='-v --verbose -n --dry-run -w --settle --wait-daemon' [TRIGGER_ARG]='-t --type -c --action -s --subsystem-match -S --subsystem-nomatch diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index 6742cda8f6..9078a3c2ce 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -20,6 +20,7 @@ #include "fd-util.h" #include "string-table.h" #include "string-util.h" +#include "udev-util.h" #include "udevadm-util.h" #include "udevadm.h" @@ -40,6 +41,7 @@ typedef enum QueryType { static bool arg_root = false; static bool arg_export = false; static const char *arg_export_prefix = NULL; +static usec_t arg_wait_for_initialization_timeout = 0; static bool skip_attribute(const char *name) { static const char* const skip[] = { @@ -329,6 +331,8 @@ static int help(void) { " -P --export-prefix Export the key name with a prefix\n" " -e --export-db Export the content of the udev database\n" " -c --cleanup-db Clean up the udev database\n" + " -w --wait-for-initialization[=SECONDS]\n" + " Wait for device to be initialized\n" , program_invocation_short_name); return 0; @@ -340,25 +344,26 @@ int info_main(int argc, char *argv[], void *userdata) { int c, r; static const struct option options[] = { - { "name", required_argument, NULL, 'n' }, - { "path", required_argument, NULL, 'p' }, - { "query", required_argument, NULL, 'q' }, - { "attribute-walk", no_argument, NULL, 'a' }, - { "cleanup-db", no_argument, NULL, 'c' }, - { "export-db", no_argument, NULL, 'e' }, - { "root", no_argument, NULL, 'r' }, - { "device-id-of-file", required_argument, NULL, 'd' }, - { "export", no_argument, NULL, 'x' }, - { "export-prefix", required_argument, NULL, 'P' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, + { "name", required_argument, NULL, 'n' }, + { "path", required_argument, NULL, 'p' }, + { "query", required_argument, NULL, 'q' }, + { "attribute-walk", no_argument, NULL, 'a' }, + { "cleanup-db", no_argument, NULL, 'c' }, + { "export-db", no_argument, NULL, 'e' }, + { "root", no_argument, NULL, 'r' }, + { "device-id-of-file", required_argument, NULL, 'd' }, + { "export", no_argument, NULL, 'x' }, + { "export-prefix", required_argument, NULL, 'P' }, + { "wait-for-initialization", optional_argument, NULL, 'w' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, {} }; ActionType action = ACTION_QUERY; QueryType query = QUERY_ALL; - while ((c = getopt_long(argc, argv, "aced:n:p:q:rxP:Vh", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "aced:n:p:q:rxP:w::Vh", options, NULL)) >= 0) switch (c) { case 'n': case 'p': { @@ -414,6 +419,14 @@ int info_main(int argc, char *argv[], void *userdata) { arg_export = true; arg_export_prefix = optarg; break; + case 'w': + if (optarg) { + r = parse_sec(optarg, &arg_wait_for_initialization_timeout); + if (r < 0) + return log_error_errno(r, "Failed to parse timeout value: %m"); + } else + arg_wait_for_initialization_timeout = USEC_INFINITY; + break; case 'V': return print_version(); case 'h': @@ -453,6 +466,12 @@ int info_main(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Unknown device \"%s\": %m", *p); + if (arg_wait_for_initialization_timeout > 0) { + r = device_wait_for_initialization(device, NULL, arg_wait_for_initialization_timeout, NULL); + if (r < 0) + return r; + } + if (action == ACTION_QUERY) r = query_device(query, device); else if (action == ACTION_ATTRIBUTE_WALK) From 1ca44d7dfdbeb484f041de67071abfa83f65b50f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 1 Jun 2019 10:29:08 +0900 Subject: [PATCH 4/7] test-network: move networkctl related tests to NetworkctlTests class --- test/test-network/systemd-networkd-tests.py | 107 +++++++++++++++----- 1 file changed, 81 insertions(+), 26 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 335e3215f2..ad63cce702 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -79,6 +79,23 @@ def expectedFailureIfRoutingPolicyIPProtoIsNotAvailable(): return f +def expectedFailureIfEthtoolDoesNotSupportDriver(): + def f(func): + support = False + rc = subprocess.call(['ip', 'link', 'add', 'name', 'dummy99', 'type', 'dummy']) + if rc == 0: + ret = subprocess.run(['udevadm', 'info', '-w10s', '/sys/class/net/dummy99'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) + if ret.returncode == 0 and 'E: ID_NET_DRIVER=dummy' in ret.stdout.rstrip(): + support = True + subprocess.call(['ip', 'link', 'del', 'dummy99']) + + if support: + return func + else: + return unittest.expectedFailure(func) + + return f + def setUpModule(): os.makedirs(network_unit_file_path, exist_ok=True) os.makedirs(networkd_ci_path, exist_ok=True) @@ -283,6 +300,69 @@ class Utilities(): else: self.assertRegex(output, address_regex) +class NetworkctlTests(unittest.TestCase, Utilities): + + links = [ + 'test1', + 'veth99', + ] + + units = [ + '11-dummy.netdev', + '11-dummy.network', + '25-veth.netdev', + 'netdev-link-local-addressing-yes.network', + ] + + def setUp(self): + self.remove_links(self.links) + + def tearDown(self): + self.remove_links(self.links) + self.remove_unit_from_networkd_path(self.units) + + def test_glob(self): + self.copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network') + self.start_networkd(0) + + self.wait_online(['test1:degraded']) + + output = subprocess.check_output(networkctl_cmd + ['list'], universal_newlines=True, env=env).rstrip() + self.assertRegex(output, '1 lo ') + self.assertRegex(output, 'test1') + + output = subprocess.check_output(networkctl_cmd + ['list', 'test1'], universal_newlines=True, env=env).rstrip() + self.assertNotRegex(output, '1 lo ') + self.assertRegex(output, 'test1') + + output = subprocess.check_output(networkctl_cmd + ['list', 'te*'], universal_newlines=True, env=env).rstrip() + self.assertNotRegex(output, '1 lo ') + self.assertRegex(output, 'test1') + + output = subprocess.check_output(networkctl_cmd + ['status', 'te*'], universal_newlines=True, env=env).rstrip() + self.assertNotRegex(output, '1: lo ') + self.assertRegex(output, 'test1') + + output = subprocess.check_output(networkctl_cmd + ['status', 'tes[a-z][0-9]'], universal_newlines=True, env=env).rstrip() + self.assertNotRegex(output, '1: lo ') + self.assertRegex(output, 'test1') + + @expectedFailureIfEthtoolDoesNotSupportDriver() + def test_udev_driver(self): + self.copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network', + '25-veth.netdev', 'netdev-link-local-addressing-yes.network') + self.start_networkd(0) + + self.wait_online(['test1:degraded', 'veth99:degraded', 'veth-peer:degraded']) + + output = subprocess.check_output(networkctl_cmd + ['status', 'test1'], universal_newlines=True, env=env).rstrip() + self.assertRegex(output, 'Driver: dummy') + + output = subprocess.check_output(networkctl_cmd + ['status', 'veth99'], universal_newlines=True, env=env).rstrip() + self.assertRegex(output, 'Driver: veth') + + output = subprocess.check_output(networkctl_cmd + ['status', 'veth-peer'], universal_newlines=True, env=env).rstrip() + self.assertRegex(output, 'Driver: veth') class NetworkdNetDevTests(unittest.TestCase, Utilities): @@ -451,41 +531,16 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities): self.remove_links(self.links) self.remove_unit_from_networkd_path(self.units) - def test_dropin_and_networkctl_glob(self): + def test_dropin_and_name_conflict(self): self.copy_unit_to_networkd_unit_path('10-dropin-test.netdev', '15-name-conflict-test.netdev') self.start_networkd(0) self.wait_online(['dropin-test:off']) - # This also tests NetDev.Name= conflict and basic networkctl functionalities - output = subprocess.check_output(['ip', 'link', 'show', 'dropin-test'], universal_newlines=True).rstrip() print(output) self.assertRegex(output, '00:50:56:c0:00:28') - output = subprocess.check_output(networkctl_cmd + ['list'], universal_newlines=True, env=env).rstrip() - self.assertRegex(output, '1 lo ') - self.assertRegex(output, 'dropin-test') - - output = subprocess.check_output(networkctl_cmd + ['list', 'dropin-test'], universal_newlines=True, env=env).rstrip() - self.assertNotRegex(output, '1 lo ') - self.assertRegex(output, 'dropin-test') - - output = subprocess.check_output(networkctl_cmd + ['list', 'dropin-*'], universal_newlines=True, env=env).rstrip() - self.assertNotRegex(output, '1 lo ') - self.assertRegex(output, 'dropin-test') - - output = subprocess.check_output(networkctl_cmd + ['status', 'dropin-*'], universal_newlines=True, env=env).rstrip() - self.assertNotRegex(output, '1: lo ') - self.assertRegex(output, 'dropin-test') - - ret = subprocess.run(['ethtool', '--driver', 'dropin-test'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - print(ret.stdout.rstrip()) - if ret.returncode == 0 and re.search('driver: dummy', ret.stdout.rstrip()) != None: - self.assertRegex(output, 'Driver: dummy') - else: - print('ethtool does not support driver field at least for dummy interfaces, skipping test for Driver field of networkctl.') - def test_wait_online_any(self): self.copy_unit_to_networkd_unit_path('25-bridge.netdev', '25-bridge.network', '11-dummy.netdev', '11-dummy.network') self.start_networkd(0) From bee692fd38b33e92f1e448dde0eef873ff4f2862 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 31 May 2019 16:11:53 +0900 Subject: [PATCH 5/7] test-network: add tests for "networkctl delete" --- test/test-network/systemd-networkd-tests.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index ad63cce702..0eb3492b7d 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -364,6 +364,18 @@ class NetworkctlTests(unittest.TestCase, Utilities): output = subprocess.check_output(networkctl_cmd + ['status', 'veth-peer'], universal_newlines=True, env=env).rstrip() self.assertRegex(output, 'Driver: veth') + def test_delete_links(self): + self.copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network', + '25-veth.netdev', 'netdev-link-local-addressing-yes.network') + self.start_networkd(0) + + self.wait_online(['test1:degraded', 'veth99:degraded', 'veth-peer:degraded']) + + subprocess.check_call(networkctl_cmd + ['delete', 'test1', 'veth99']) + self.assertFalse(self.link_exists('test1')) + self.assertFalse(self.link_exists('veth99')) + self.assertFalse(self.link_exists('veth-peer')) + class NetworkdNetDevTests(unittest.TestCase, Utilities): links =[ From 6d5b4efed23a0371275337038e2c4ea5e8e91ef6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 1 Jun 2019 15:44:04 +0900 Subject: [PATCH 6/7] test-network: add test for MTU field of 'networkctl status' --- test/test-network/conf/11-dummy-mtu.netdev | 4 ++++ test/test-network/systemd-networkd-tests.py | 10 ++++++++++ 2 files changed, 14 insertions(+) create mode 100644 test/test-network/conf/11-dummy-mtu.netdev diff --git a/test/test-network/conf/11-dummy-mtu.netdev b/test/test-network/conf/11-dummy-mtu.netdev new file mode 100644 index 0000000000..aeecc1dc01 --- /dev/null +++ b/test/test-network/conf/11-dummy-mtu.netdev @@ -0,0 +1,4 @@ +[NetDev] +Name=test1 +Kind=dummy +MTUBytes=1600 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 0eb3492b7d..803b7122e8 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -309,6 +309,7 @@ class NetworkctlTests(unittest.TestCase, Utilities): units = [ '11-dummy.netdev', + '11-dummy-mtu.netdev', '11-dummy.network', '25-veth.netdev', 'netdev-link-local-addressing-yes.network', @@ -347,6 +348,15 @@ class NetworkctlTests(unittest.TestCase, Utilities): self.assertNotRegex(output, '1: lo ') self.assertRegex(output, 'test1') + def test_mtu(self): + self.copy_unit_to_networkd_unit_path('11-dummy-mtu.netdev', '11-dummy.network') + self.start_networkd(0) + + self.wait_online(['test1:degraded']) + + output = subprocess.check_output(networkctl_cmd + ['status', 'test1'], universal_newlines=True, env=env).rstrip() + self.assertRegex(output, 'MTU: 1600') + @expectedFailureIfEthtoolDoesNotSupportDriver() def test_udev_driver(self): self.copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network', From 78690bb5d090563cf836de7c5145850d8f26eaab Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 1 Jun 2019 15:55:16 +0900 Subject: [PATCH 7/7] test-network: disable SystemCallFilter= when *SAN is enabled --- test/test-network/systemd-networkd-tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 803b7122e8..d9242cb116 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -126,6 +126,8 @@ def setUpModule(): drop_in += ['Environment=LSAN_OPTIONS="' + lsan_options + '"'] if ubsan_options: drop_in += ['Environment=UBSAN_OPTIONS="' + ubsan_options + '"'] + if asan_options or lsan_options or ubsan_options: + drop_in += ['SystemCallFilter='] if use_valgrind or asan_options or lsan_options or ubsan_options: drop_in += ['MemoryDenyWriteExecute=no']