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/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 054feeda76..482d914d07 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"
@@ -112,14 +113,18 @@ 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;
assert(device);
- assert(subsystem);
if (sd_device_get_is_initialized(device) > 0) {
if (ret)
@@ -139,9 +144,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)
@@ -151,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) {
@@ -161,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);
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)
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 335e3215f2..d9242cb116 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)
@@ -109,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']
@@ -283,6 +302,91 @@ class Utilities():
else:
self.assertRegex(output, address_regex)
+class NetworkctlTests(unittest.TestCase, Utilities):
+
+ links = [
+ 'test1',
+ 'veth99',
+ ]
+
+ units = [
+ '11-dummy.netdev',
+ '11-dummy-mtu.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')
+
+ 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',
+ '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')
+
+ 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):
@@ -451,41 +555,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)