diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c index 018129b..fd2309e 100644 --- a/libnm-core/nm-setting-connection.c +++ b/libnm-core/nm-setting-connection.c @@ -1218,20 +1218,6 @@ after_interface_name: return FALSE; } - if ( priv->wait_device_timeout != -1 - && !priv->interface_name) { - /* currently, only waiting by interface-name is implemented. Hence reject - * configurations that are not implemented (yet). */ - g_set_error (error, - NM_CONNECTION_ERROR, - NM_CONNECTION_ERROR_INVALID_PROPERTY, - _("wait-device-timeout requires %s"), - NM_SETTING_CONNECTION_INTERFACE_NAME); - g_prefix_error (error, "%s.%s: ", NM_SETTING_CONNECTION_SETTING_NAME, - NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT); - return FALSE; - } - if (priv->mud_url) { if (!priv->mud_url[0]) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, diff --git a/src/initrd/nm-initrd-generator.h b/src/initrd/nm-initrd-generator.h index 9719992..8e17f04 100644 --- a/src/initrd/nm-initrd-generator.h +++ b/src/initrd/nm-initrd-generator.h @@ -9,6 +9,8 @@ #include "nm-connection.h" #include "nm-utils.h" +#define NMI_WAIT_DEVICE_TIMEOUT_MS 60000 + static inline gboolean guess_ip_address_family (const char *str) { diff --git a/src/initrd/nmi-cmdline-reader.c b/src/initrd/nmi-cmdline-reader.c index 17f9e1d..be39ef8 100644 --- a/src/initrd/nmi-cmdline-reader.c +++ b/src/initrd/nmi-cmdline-reader.c @@ -584,7 +584,6 @@ reader_parse_master (Reader *reader, { NMConnection *connection; NMSettingConnection *s_con; - NMSettingBond *s_bond; gs_free char *master_to_free = NULL; const char *master; char *slaves; @@ -603,8 +602,15 @@ reader_parse_master (Reader *reader, s_con = nm_connection_get_setting_connection (connection); master = nm_setting_connection_get_uuid (s_con); - if (nm_streq (type_name, NM_SETTING_BOND_SETTING_NAME)) { - s_bond = (NMSettingBond *)nm_connection_get_setting_by_name (connection, type_name); + if (nm_streq (type_name, NM_SETTING_BRIDGE_SETTING_NAME)) { + NMSettingBridge *s_bridge = nm_connection_get_setting_bridge (connection); + + /* Avoid the forwarding delay */ + g_object_set (s_bridge, + NM_SETTING_BRIDGE_STP, FALSE, + NULL); + } else if (nm_streq (type_name, NM_SETTING_BOND_SETTING_NAME)) { + NMSettingBond *s_bond = nm_connection_get_setting_bond (connection); opts = get_word (&argument, ':'); while (opts && *opts) { @@ -867,6 +873,27 @@ reader_add_nameservers (Reader *reader, GPtrArray *nameservers) } } +static void +connection_set_needed (NMConnection *connection) +{ + NMSettingConnection *s_con; + + s_con = nm_connection_get_setting_connection (connection); + if (!nm_streq0 (nm_setting_connection_get_connection_type (s_con), + NM_SETTING_WIRED_SETTING_NAME)) + return; + + g_object_set (s_con, + NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT, (int) NMI_WAIT_DEVICE_TIMEOUT_MS, + NULL); +} + +static void +connection_set_needed_cb (gpointer key, gpointer value, gpointer user_data) +{ + connection_set_needed (value); +} + GHashTable * nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv, char **hostname) { @@ -1001,11 +1028,16 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv, char ** connection = reader_get_connection (reader, bootdev, NULL, TRUE); reader->bootdev_connection = connection; + connection_set_needed (connection); } - if (neednet && g_hash_table_size (reader->hash) == 0) { - /* Make sure there's some connection. */ - reader_get_default_connection (reader); + if (neednet) { + if (g_hash_table_size (reader->hash) == 0) { + /* Make sure there's some connection. */ + reader_get_default_connection (reader); + } + + g_hash_table_foreach (reader->hash, connection_set_needed_cb, NULL); } if (routes) diff --git a/src/initrd/tests/test-cmdline-reader.c b/src/initrd/tests/test-cmdline-reader.c index 04594c4..7787cf5 100644 --- a/src/initrd/tests/test-cmdline-reader.c +++ b/src/initrd/tests/test-cmdline-reader.c @@ -49,6 +49,7 @@ test_auto (void) g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "Wired Connection"); g_assert_cmpint (nm_setting_connection_get_timestamp (s_con), ==, 0); g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_MULTIPLE); + g_assert_cmpint (nm_setting_connection_get_wait_device_timeout (s_con), ==, -1); g_assert (nm_setting_connection_get_autoconnect (s_con)); @@ -190,6 +191,7 @@ test_if_ip4_manual (void) "ip=203.0.113.2::203.0.113.1:26:" "hostname1.example.com:eth4"); NMConnection *connection; + NMSettingConnection *s_con; NMSettingIPConfig *s_ip4; NMSettingIPConfig *s_ip6; NMIPAddress *ip_addr; @@ -205,6 +207,10 @@ test_if_ip4_manual (void) nmtst_assert_connection_verifies_without_normalization (connection); g_assert_cmpstr (nm_connection_get_id (connection), ==, "eth3"); + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + g_assert_cmpint (nm_setting_connection_get_wait_device_timeout (s_con), ==, -1); + s_ip4 = nm_connection_get_setting_ip4_config (connection); g_assert (s_ip4); g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_MANUAL); @@ -294,6 +300,7 @@ test_multiple_merge (void) const char *const*ARGV = NM_MAKE_STRV ("ip=192.0.2.2:::::eth0", "ip=[2001:db8::2]:::::eth0"); NMConnection *connection; + NMSettingConnection *s_con; NMSettingWired *s_wired; NMSettingIPConfig *s_ip4; NMSettingIPConfig *s_ip6; @@ -310,6 +317,10 @@ test_multiple_merge (void) nmtst_assert_connection_verifies_without_normalization (connection); g_assert_cmpstr (nm_connection_get_id (connection), ==, "eth0"); + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + g_assert_cmpint (nm_setting_connection_get_wait_device_timeout (s_con), ==, -1); + s_wired = nm_connection_get_setting_wired (connection); g_assert (s_wired); @@ -341,6 +352,7 @@ test_multiple_bootdev (void) "ip=eth4:dhcp", "bootdev=eth4"); NMConnection *connection; + NMSettingConnection *s_con; NMSettingIPConfig *s_ip4; NMSettingIPConfig *s_ip6; gs_free char *hostname = NULL; @@ -352,12 +364,18 @@ test_multiple_bootdev (void) connection = g_hash_table_lookup (connections, "eth3"); g_assert (connection); + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + g_assert_cmpint (nm_setting_connection_get_wait_device_timeout (s_con), ==, -1); s_ip6 = nm_connection_get_setting_ip6_config (connection); g_assert (s_ip6); g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO); connection = g_hash_table_lookup (connections, "eth4"); g_assert (connection); + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + g_assert_cmpint (nm_setting_connection_get_wait_device_timeout (s_con), ==, NMI_WAIT_DEVICE_TIMEOUT_MS); s_ip4 = nm_connection_get_setting_ip4_config (connection); g_assert (s_ip4); g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO); @@ -388,6 +406,7 @@ test_bootdev (void) g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME); g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "ens3"); g_assert_cmpstr (nm_setting_connection_get_interface_name (s_con), ==, "ens3"); + g_assert_cmpint (nm_setting_connection_get_wait_device_timeout(s_con), ==, NMI_WAIT_DEVICE_TIMEOUT_MS); connection = g_hash_table_lookup (connections, "vlan2"); g_assert (connection); @@ -774,6 +793,7 @@ test_bridge (void) s_bridge = nm_connection_get_setting_bridge (connection); g_assert (s_bridge); + g_assert_cmpint (nm_setting_bridge_get_stp (s_bridge), ==, FALSE); connection = g_hash_table_lookup (connections, "eth0"); g_assert (connection); @@ -1255,6 +1275,56 @@ test_bootif_ip (void) } static void +test_neednet (void) +{ + gs_unref_hashtable GHashTable *connections = NULL; + const char *const*ARGV = NM_MAKE_STRV ("rd.neednet", + "ip=eno1:dhcp", + "ip=172.25.1.100::172.25.1.1:24::eno2", + "bridge=br0:eno3"); + NMConnection *connection; + NMSettingConnection *s_con; + gs_free char *hostname = NULL; + + connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname); + g_assert (connections); + g_assert_cmpint (g_hash_table_size (connections), ==, 4); + g_assert_cmpstr (hostname, ==, NULL); + + connection = g_hash_table_lookup (connections, "eno1"); + g_assert (connection); + nmtst_assert_connection_verifies_without_normalization (connection); + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + g_assert_cmpstr (nm_setting_connection_get_interface_name (s_con), ==, "eno1"); + g_assert_cmpint (nm_setting_connection_get_wait_device_timeout (s_con), ==, NMI_WAIT_DEVICE_TIMEOUT_MS); + + connection = g_hash_table_lookup (connections, "eno2"); + g_assert (connection); + nmtst_assert_connection_verifies_without_normalization (connection); + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + g_assert_cmpstr (nm_setting_connection_get_interface_name (s_con), ==, "eno2"); + g_assert_cmpint (nm_setting_connection_get_wait_device_timeout (s_con), ==, NMI_WAIT_DEVICE_TIMEOUT_MS); + + connection = g_hash_table_lookup (connections, "eno3"); + g_assert (connection); + nmtst_assert_connection_verifies_without_normalization (connection); + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + g_assert_cmpstr (nm_setting_connection_get_interface_name (s_con), ==, "eno3"); + g_assert_cmpint (nm_setting_connection_get_wait_device_timeout (s_con), ==, NMI_WAIT_DEVICE_TIMEOUT_MS); + + connection = g_hash_table_lookup (connections, "br0"); + g_assert (connection); + nmtst_assert_connection_verifies_without_normalization (connection); + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + g_assert_cmpstr (nm_setting_connection_get_interface_name (s_con), ==, "br0"); + g_assert_cmpint (nm_setting_connection_get_wait_device_timeout (s_con), ==, -1); +} + +static void test_bootif_no_ip (void) { gs_unref_hashtable GHashTable *connections = NULL; @@ -1450,6 +1520,7 @@ int main (int argc, char **argv) g_test_add_func ("/initrd/cmdline/bootif/no_ip", test_bootif_no_ip); g_test_add_func ("/initrd/cmdline/bootif/hwtype", test_bootif_hwtype); g_test_add_func ("/initrd/cmdline/bootif/off", test_bootif_off); + g_test_add_func ("/initrd/cmdline/neednet", test_neednet); return g_test_run (); } diff --git a/src/nm-manager.c b/src/nm-manager.c index d687fcd..778e3b9 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -1561,13 +1561,6 @@ check_if_startup_complete (NMManager *self) if (!priv->devices_inited) return; - reason = nm_settings_get_startup_complete_blocked_reason (priv->settings); - if (reason) { - _LOGD (LOGD_CORE, "startup complete is waiting for connection (%s)", - reason); - return; - } - c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { reason = nm_device_has_pending_action_reason (device); if (reason) { @@ -1578,6 +1571,31 @@ check_if_startup_complete (NMManager *self) } } + /* All NMDevice must be ready. But also NMSettings tracks profiles that wait for + * ready devices via "connection.wait-device-timeout". + * + * Note that we only re-check nm_settings_get_startup_complete_blocked_reason() when + * all of the devices become ready (again). + * + * For example, assume we have device "eth1" and "profile-eth2" which waits for "eth2". + * If "eth1" is ready (no pending action), we only need to re-evaluate "profile-eth2" + * if we have another device ("eth2"), that becomes non-ready (had pending actions) + * and again become ready. We don't need to check "profile-eth2" until "eth2" becomes + * non-ready. + * That is why nm_settings_get_startup_complete_blocked_reason() only has any significance + * if all devices are ready too. It allows us to cut down the number of checks whether + * NMSettings is ready. That's because we don't need to re-evaluate on minor changes of + * a device, only when all devices become managed and ready. */ + + g_signal_handlers_block_by_func (priv->settings, settings_startup_complete_changed, self); + reason = nm_settings_get_startup_complete_blocked_reason (priv->settings, TRUE); + g_signal_handlers_unblock_by_func (priv->settings, settings_startup_complete_changed, self); + if (reason) { + _LOGD (LOGD_CORE, "startup complete is waiting for connection (%s)", + reason); + return; + } + _LOGI (LOGD_CORE, "startup complete"); priv->startup = FALSE; @@ -7455,7 +7473,7 @@ constructed (GObject *object) G_OBJECT_CLASS (nm_manager_parent_class)->constructed (object); - priv->settings = nm_settings_new (); + priv->settings = nm_settings_new (self); nm_dbus_object_export (NM_DBUS_OBJECT (priv->settings)); diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c index 0a1e7b4..4573d5f 100644 --- a/src/settings/nm-settings.c +++ b/src/settings/nm-settings.c @@ -60,6 +60,7 @@ #include "plugins/keyfile/nms-keyfile-storage.h" #include "nm-agent-manager.h" #include "nm-config.h" +#include "nm-manager.h" #include "nm-audit-manager.h" #include "NetworkManagerUtils.h" #include "nm-dispatcher.h" @@ -324,6 +325,7 @@ _sett_conn_entry_find_shadowed_storage (SettConnEntry *sett_conn_entry, /*****************************************************************************/ NM_GOBJECT_PROPERTIES_DEFINE (NMSettings, + PROP_MANAGER, PROP_UNMANAGED_SPECS, PROP_HOSTNAME, PROP_CAN_MODIFY, @@ -348,6 +350,8 @@ typedef struct { NMPlatform *platform; + NMManager *manager; + NMHostnameManager *hostname_manager; NMSessionMonitor *session_monitor; @@ -372,9 +376,9 @@ typedef struct { GSList *unmanaged_specs; GSList *unrecognized_specs; + gint64 startup_complete_start_timestamp_msec; GHashTable *startup_complete_idx; - NMSettingsConnection *startup_complete_blocked_by; - gulong startup_complete_platform_change_id; + CList startup_complete_scd_lst_head; guint startup_complete_timeout_id; guint connections_len; @@ -423,7 +427,7 @@ static void default_wired_clear_tag (NMSettings *self, static void _clear_connections_cached_list (NMSettingsPrivate *priv); static void _startup_complete_check (NMSettings *self, - gint64 now_us); + gint64 now_msec); /*****************************************************************************/ @@ -461,34 +465,53 @@ _emit_connection_flags_changed (NMSettings *self, typedef struct { NMSettingsConnection *sett_conn; - gint64 start_at; - gint64 timeout; + CList scd_lst; + gint64 timeout_msec; } StartupCompleteData; static void _startup_complete_data_destroy (StartupCompleteData *scd) { + c_list_unlink_stale (&scd->scd_lst); g_object_unref (scd->sett_conn); - g_slice_free (StartupCompleteData, scd); + nm_g_slice_free (scd); } static gboolean -_startup_complete_check_is_ready (NMPlatform *platform, - NMSettingsConnection *sett_conn) +_startup_complete_check_is_ready (NMSettings *self, + NMSettingsConnection *sett_conn, + gboolean ignore_pending_actions) { - const NMPlatformLink *plink; - const char *ifname; + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self); + NMConnection *conn; + const CList *tmp_lst; + NMDevice *device; - /* FIXME: instead of just looking for the interface name, it would be better - * to wait for a device that is compatible with the profile. */ + if (!priv->manager) + return TRUE; - ifname = nm_connection_get_interface_name (nm_settings_connection_get_connection (sett_conn)); + conn = nm_settings_connection_get_connection (sett_conn); + + nm_manager_for_each_device (priv->manager, device, tmp_lst) { + + if (!nm_device_is_real (device)) + continue; + + if ( nm_device_get_state (device) < NM_DEVICE_STATE_UNAVAILABLE + || ( !ignore_pending_actions + && nm_device_has_pending_action (device))) { + /* while a device is not yet available and still has a pending + * action itself, it's not a suitable candidate. */ + continue; + } + + if (!nm_device_check_connection_compatible (device, conn, NULL)) + continue; - if (!ifname) return TRUE; + } - plink = nm_platform_link_get_by_ifname (platform, ifname); - return plink && plink->initialized; + return FALSE; } static gboolean @@ -503,116 +526,97 @@ _startup_complete_timeout_cb (gpointer user_data) } static void -_startup_complete_platform_change_cb (NMPlatform *platform, - int obj_type_i, - int ifindex, - const NMPlatformLink *link, - int change_type_i, - NMSettings *self) -{ - const NMPlatformSignalChangeType change_type = change_type_i; - NMSettingsPrivate *priv; - const char *ifname; - - if (change_type == NM_PLATFORM_SIGNAL_REMOVED) - return; - - if (!link->initialized) - return; - - priv = NM_SETTINGS_GET_PRIVATE (self); - - ifname = nm_connection_get_interface_name (nm_settings_connection_get_connection (priv->startup_complete_blocked_by)); - if ( ifname - && !nm_streq (ifname, link->name)) - return; - - nm_assert (priv->startup_complete_timeout_id > 0); - - nm_clear_g_source (&priv->startup_complete_timeout_id); - priv->startup_complete_timeout_id = g_idle_add (_startup_complete_timeout_cb, self); -} - -static void _startup_complete_check (NMSettings *self, - gint64 now_us) + gint64 now_msec) { NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self); - gint64 next_expiry; + StartupCompleteData *scd_not_ready; + StartupCompleteData *scd_safe; StartupCompleteData *scd; - NMSettingsConnection *next_sett_conn = NULL; - GHashTableIter iter; + gint64 elapsed_msec; + CList ready_lst; + + if (priv->startup_complete_start_timestamp_msec == 0) { + /* we are already done for good or didn't start yet. */ + return; + } if (!priv->started) { - /* before we are started, we don't setup the timers... */ + /* before we are started there is no need to evaluate our list because + * we are anyway blocking startup-complete. */ return; } - if (!priv->startup_complete_idx) + if (c_list_is_empty (&priv->startup_complete_scd_lst_head)) goto ready; - if (!now_us) - now_us = nm_utils_get_monotonic_timestamp_usec (); + nm_utils_get_monotonic_timestamp_msec_cached (&now_msec); - next_expiry = 0; + elapsed_msec = now_msec - priv->startup_complete_start_timestamp_msec; - g_hash_table_iter_init (&iter, priv->startup_complete_idx); - while (g_hash_table_iter_next (&iter, (gpointer *) &scd, NULL)) { - gint64 expiry; - - if (scd->start_at == 0) { - /* once ready, the decision is remembered and there is nothing - * left to check. */ - continue; - } + /* We search the entire list whether they all timed-out or found a compatible device. + * We do that by appending elements that are ready to the end of the list, so that + * we hopefully keep testing the elements that are ready already (and can shortcut + * the test in common cases). + * + * Note that all profiles that we wait for need to have their dependencies satisfied + * at the same time. For example, consider connection A is waiting for device A' which is ready. + * Connection B waits for device B', which isn't ready. Once B'/B becomes ready, A/A' must + * still be ready. Otherwise, we would wait for A/A' to become ready again. */ + scd_not_ready = NULL; + c_list_init (&ready_lst); + c_list_for_each_entry_safe (scd, scd_safe, &priv->startup_complete_scd_lst_head, scd_lst) { - expiry = scd->start_at + scd->timeout; - if (expiry <= now_us) { - scd->start_at = 0; - continue; - } + if (scd->timeout_msec <= elapsed_msec) + goto next_with_ready; - if (_startup_complete_check_is_ready (priv->platform, scd->sett_conn)) { - scd->start_at = 0; - continue; - } + if (_startup_complete_check_is_ready (self, scd->sett_conn, FALSE)) + goto next_with_ready; - next_expiry = expiry; - next_sett_conn = scd->sett_conn; - /* we found one timeout for which to wait. that's good enough. */ + scd_not_ready = scd; break; + +next_with_ready: + /* this element is ready. We move it to a temporary list, so that we + * can reorder the list (to next time evaluate the non-ready element first). */ + nm_c_list_move_tail (&ready_lst, &scd->scd_lst); } + c_list_splice (&priv->startup_complete_scd_lst_head, &ready_lst); nm_clear_g_source (&priv->startup_complete_timeout_id); - nm_g_object_ref_set (&priv->startup_complete_blocked_by, next_sett_conn); - if (next_expiry > 0) { - nm_assert (priv->startup_complete_blocked_by); - if (priv->startup_complete_platform_change_id == 0) { - priv->startup_complete_platform_change_id = g_signal_connect (priv->platform, - NM_PLATFORM_SIGNAL_LINK_CHANGED, - G_CALLBACK (_startup_complete_platform_change_cb), - self); - } - priv->startup_complete_timeout_id = g_timeout_add (NM_MIN (3600u*1000u, (next_expiry - now_us) / 1000u), + + if (scd_not_ready) { + gint64 timeout_msec; + + timeout_msec = priv->startup_complete_start_timestamp_msec + scd_not_ready->timeout_msec - nm_utils_get_monotonic_timestamp_msec (); + priv->startup_complete_timeout_id = g_timeout_add (NM_CLAMP (0, timeout_msec, 60000), _startup_complete_timeout_cb, self); - _LOGT ("startup-complete: wait for device \"%s\" due to connection %s (%s)", - nm_connection_get_interface_name (nm_settings_connection_get_connection (priv->startup_complete_blocked_by)), - nm_settings_connection_get_uuid (priv->startup_complete_blocked_by), - nm_settings_connection_get_id (priv->startup_complete_blocked_by)); + _LOGT ("startup-complete: wait for suitable device for connection \"%s\" (%s) which has \"connection.wait-device-timeout\" set", + nm_settings_connection_get_id (scd_not_ready->sett_conn), + nm_settings_connection_get_uuid (scd_not_ready->sett_conn)); return; } - nm_clear_pointer (&priv->startup_complete_idx, g_hash_table_destroy); - nm_clear_g_signal_handler (priv->platform, &priv->startup_complete_platform_change_id); + if (_LOGW_ENABLED ()) { + c_list_for_each_entry (scd, &priv->startup_complete_scd_lst_head, scd_lst) { + if (!_startup_complete_check_is_ready (self, scd->sett_conn, TRUE)) { + _LOGW ("startup-complete: profile \"%s\" (%s) was waiting for non-existing device (with timeout \"connection.wait-device-timeout=%"G_GINT64_FORMAT"\")", + nm_settings_connection_get_id (scd->sett_conn), + nm_settings_connection_get_uuid (scd->sett_conn), + scd->timeout_msec); + } + } + } ready: - _LOGT ("startup-complete: ready, no profiles to wait for"); + nm_clear_pointer (&priv->startup_complete_idx, g_hash_table_destroy); + nm_assert (c_list_is_empty (&priv->startup_complete_scd_lst_head)); nm_assert (priv->started); - nm_assert (!priv->startup_complete_blocked_by); + _LOGT ("startup-complete: ready, no more profiles to wait for"); + priv->startup_complete_start_timestamp_msec = 0; nm_assert (!priv->startup_complete_idx); nm_assert (priv->startup_complete_timeout_id == 0); - nm_assert (priv->startup_complete_platform_change_id == 0); _notify (self, PROP_STARTUP_COMPLETE); } @@ -622,75 +626,95 @@ _startup_complete_notify_connection (NMSettings *self, gboolean forget) { NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self); - gint64 timeout; - gint64 now_us = 0; - - nm_assert ( !priv->started - || priv->startup_complete_idx); - - timeout = 0; - if (!forget) { - NMSettingConnection *s_con; - gint32 v; - - s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (sett_conn)); - v = nm_setting_connection_get_wait_device_timeout (s_con); - if (v > 0) { - nm_assert (nm_setting_connection_get_interface_name (s_con)); - timeout = ((gint64) v) * 1000; - } + StartupCompleteData *scd; + gint64 timeout_msec; + gint64 now_msec = 0; + NMSettingConnection *s_con; + gint32 v; + + nm_assert (priv->startup_complete_start_timestamp_msec != 0); + + if (forget) { + if (!priv->startup_complete_idx) + return; + if (!g_hash_table_remove (priv->startup_complete_idx, &sett_conn)) + return; + goto check; } - if (timeout == 0) { - if ( !priv->startup_complete_idx - || !g_hash_table_remove (priv->startup_complete_idx, &sett_conn)) + s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (sett_conn)); + v = nm_setting_connection_get_wait_device_timeout (s_con); + if (v > 0) + timeout_msec = v; + else + timeout_msec = 0; + + if (!priv->startup_complete_idx) { + nm_assert (!priv->started); + + if (timeout_msec == 0) + return; + + priv->startup_complete_idx = g_hash_table_new_full (nm_pdirect_hash, + nm_pdirect_equal, + NULL, + (GDestroyNotify) _startup_complete_data_destroy); + scd = NULL; + } else + scd = g_hash_table_lookup (priv->startup_complete_idx, &sett_conn); + + if (!scd) { + if (timeout_msec == 0) return; + scd = g_slice_new (StartupCompleteData); + *scd = (StartupCompleteData) { + .sett_conn = g_object_ref (sett_conn), + .timeout_msec = timeout_msec, + }; + g_hash_table_add (priv->startup_complete_idx, scd); + c_list_link_tail (&priv->startup_complete_scd_lst_head, &scd->scd_lst); } else { - StartupCompleteData *scd; - - if (!priv->startup_complete_idx) { - nm_assert (!priv->started); - priv->startup_complete_idx = g_hash_table_new_full (nm_pdirect_hash, - nm_pdirect_equal, - NULL, - (GDestroyNotify) _startup_complete_data_destroy); - scd = NULL; - } else - scd = g_hash_table_lookup (priv->startup_complete_idx, &sett_conn); - if (!scd) { - now_us = nm_utils_get_monotonic_timestamp_usec (); - scd = g_slice_new (StartupCompleteData); - *scd = (StartupCompleteData) { - .sett_conn = g_object_ref (sett_conn), - .start_at = now_us, - .timeout = timeout, - }; - g_hash_table_add (priv->startup_complete_idx, scd); - } else { - if (scd->start_at == 0) { - /* the entry already is ready and no longer relevant. Ignore it. */ - return; - } - scd->timeout = timeout; - } + scd->timeout_msec = timeout_msec; + nm_c_list_move_front (&priv->startup_complete_scd_lst_head, &scd->scd_lst); } - _startup_complete_check (self, now_us); +check: + _startup_complete_check (self, now_msec); } const char * -nm_settings_get_startup_complete_blocked_reason (NMSettings *self) +nm_settings_get_startup_complete_blocked_reason (NMSettings *self, + gboolean force_reload) { NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self); - const char *uuid = NULL; + StartupCompleteData *scd; + const char *uuid; - if (priv->started) { - if (!priv->startup_complete_idx) - return NULL; - if (priv->startup_complete_blocked_by) - uuid = nm_settings_connection_get_uuid (priv->startup_complete_blocked_by); - } - return uuid ?: "unknown"; + if (priv->startup_complete_start_timestamp_msec == 0) + goto out_done; + + if (force_reload) + _startup_complete_check (self, 0); + + if (c_list_is_empty (&priv->startup_complete_scd_lst_head)) + goto out_done; + + scd = c_list_first_entry (&priv->startup_complete_scd_lst_head, StartupCompleteData, scd_lst); + + nm_assert (scd); + nm_assert (NM_IS_SETTINGS_CONNECTION (scd->sett_conn)); + nm_assert (scd == nm_g_hash_table_lookup (priv->startup_complete_idx, &scd->sett_conn)); + + uuid = nm_settings_connection_get_uuid (scd->sett_conn); + if (uuid) + return uuid; + + g_return_val_if_reached ("settings-starting"); + +out_done: + if (!priv->started) + return "settings-starting"; + return NULL; } /*****************************************************************************/ @@ -1138,8 +1162,7 @@ _connection_changed_update (NMSettings *self, _emit_connection_updated (self, sett_conn, update_reason); } - if ( !priv->started - || priv->startup_complete_idx) { + if (priv->startup_complete_start_timestamp_msec != 0) { if (nm_settings_has_connection (self, sett_conn)) _startup_complete_notify_connection (self, sett_conn, FALSE); } @@ -1213,8 +1236,7 @@ _connection_changed_delete (NMSettings *self, nm_key_file_db_remove_key (priv->kf_db_timestamps, uuid); nm_key_file_db_remove_key (priv->kf_db_seen_bssids, uuid); - if ( !priv->started - || priv->startup_complete_idx) + if (priv->startup_complete_start_timestamp_msec != 0) _startup_complete_notify_connection (self, sett_conn, TRUE); } @@ -3711,6 +3733,8 @@ nm_settings_start (NMSettings *self, GError **error) nm_assert (!priv->started); + priv->startup_complete_start_timestamp_msec = nm_utils_get_monotonic_timestamp_msec (); + priv->hostname_manager = g_object_ref (nm_hostname_manager_get ()); priv->kf_db_timestamps = nm_key_file_db_new (NMSTATEDIR "/timestamps", @@ -3797,7 +3821,27 @@ get_property (GObject *object, guint prop_id, g_value_take_boxed (value, nm_utils_strv_make_deep_copied (strv)); break; case PROP_STARTUP_COMPLETE: - g_value_set_boolean (value, !nm_settings_get_startup_complete_blocked_reason (self)); + g_value_set_boolean (value, !nm_settings_get_startup_complete_blocked_reason (self, FALSE)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMSettings *self = NM_SETTINGS (object); + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self); + + switch (prop_id) { + case PROP_MANAGER: + /* construct-only */ + priv->manager = g_value_get_pointer (value); + nm_assert (NM_IS_MANAGER (priv->manager)); + g_object_add_weak_pointer (G_OBJECT (priv->manager), (gpointer *) &priv->manager); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -3814,6 +3858,7 @@ nm_settings_init (NMSettings *self) c_list_init (&priv->auth_lst_head); c_list_init (&priv->connections_lst_head); + c_list_init (&priv->startup_complete_scd_lst_head); c_list_init (&priv->sce_dirty_lst_head); priv->sce_idx = g_hash_table_new_full (nm_pstr_hash, nm_pstr_equal, @@ -3833,9 +3878,13 @@ nm_settings_init (NMSettings *self) } NMSettings * -nm_settings_new (void) +nm_settings_new (NMManager *manager) { - return g_object_new (NM_TYPE_SETTINGS, NULL); + nm_assert (NM_IS_MANAGER (manager)); + + return g_object_new (NM_TYPE_SETTINGS, + NM_SETTINGS_MANAGER, manager, + NULL); } static void @@ -3849,9 +3898,8 @@ dispose (GObject *object) nm_assert (g_hash_table_size (priv->sce_idx) == 0); nm_clear_g_source (&priv->startup_complete_timeout_id); - nm_clear_g_signal_handler (priv->platform, &priv->startup_complete_platform_change_id); nm_clear_pointer (&priv->startup_complete_idx, g_hash_table_destroy); - g_clear_object (&priv->startup_complete_blocked_by); + nm_assert (c_list_is_empty (&priv->startup_complete_scd_lst_head)); while ((iter = c_list_first (&priv->auth_lst_head))) nm_auth_chain_destroy (nm_auth_chain_parent_lst_entry (iter)); @@ -3915,6 +3963,11 @@ finalize (GObject *object) g_clear_object (&priv->config); g_clear_object (&priv->platform); + + if (priv->manager) { + g_object_remove_weak_pointer (G_OBJECT (priv->manager), (gpointer *) &priv->manager); + priv->manager = NULL; + } } static const GDBusSignalInfo signal_info_new_connection = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT ( @@ -4051,9 +4104,16 @@ nm_settings_class_init (NMSettingsClass *class) dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_settings); object_class->get_property = get_property; + object_class->set_property = set_property; object_class->dispose = dispose; object_class->finalize = finalize; + obj_properties[PROP_MANAGER] = + g_param_spec_pointer (NM_SETTINGS_MANAGER, "", "", + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS); + obj_properties[PROP_UNMANAGED_SPECS] = g_param_spec_boxed (NM_SETTINGS_UNMANAGED_SPECS, "", "", G_TYPE_STRV, diff --git a/src/settings/nm-settings.h b/src/settings/nm-settings.h index aa7e36e..35c62ff 100644 --- a/src/settings/nm-settings.h +++ b/src/settings/nm-settings.h @@ -26,6 +26,7 @@ #define NM_SETTINGS_CAN_MODIFY "can-modify" #define NM_SETTINGS_CONNECTIONS "connections" #define NM_SETTINGS_STARTUP_COMPLETE "startup-complete" +#define NM_SETTINGS_MANAGER "manager" #define NM_SETTINGS_SIGNAL_CONNECTION_ADDED "connection-added" #define NM_SETTINGS_SIGNAL_CONNECTION_UPDATED "connection-updated" @@ -53,7 +54,7 @@ GType nm_settings_get_type (void); NMSettings *nm_settings_get (void); #define NM_SETTINGS_GET (nm_settings_get ()) -NMSettings *nm_settings_new (void); +NMSettings *nm_settings_new (NMManager *manager); gboolean nm_settings_start (NMSettings *self, GError **error); @@ -122,7 +123,8 @@ void nm_settings_device_added (NMSettings *self, NMDevice *device); void nm_settings_device_removed (NMSettings *self, NMDevice *device, gboolean quitting); -const char *nm_settings_get_startup_complete_blocked_reason (NMSettings *self); +const char *nm_settings_get_startup_complete_blocked_reason (NMSettings *self, + gboolean force_reload); void nm_settings_kf_db_write (NMSettings *settings);