summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-06-17 14:54:10 +0200
committerMichael Biebl <biebl@debian.org>2018-06-17 14:54:10 +0200
commit069cb5c3a525ebcc19cc2927964258acaca87b13 (patch)
tree66ac6b21a44d630c0fc281c0df327239d491226a /src
parent04bc9e1cd3544445d883ad29ea108c1645c8e7b7 (diff)
New upstream version 1.11.90 upstream/1.11.90
Diffstat (limited to 'src')
-rw-r--r--src/devices/bluetooth/nm-device-bt.c7
-rw-r--r--src/devices/nm-device-ethernet.c36
-rw-r--r--src/devices/nm-device-macsec.c1
-rw-r--r--src/devices/nm-device-private.h2
-rw-r--r--src/devices/nm-device.c427
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c2
-rw-r--r--src/devices/wifi/nm-device-iwd.c15
-rw-r--r--src/devices/wifi/nm-device-iwd.h1
-rw-r--r--src/devices/wifi/nm-device-wifi.c91
-rw-r--r--src/devices/wifi/nm-device-wifi.h1
-rw-r--r--src/devices/wifi/nm-wifi-common.c1
-rw-r--r--src/devices/wwan/nm-device-modem.c2
-rw-r--r--src/devices/wwan/nm-modem.c13
-rw-r--r--src/devices/wwan/nm-modem.h2
-rw-r--r--src/dhcp/nm-dhcp-client.c76
-rw-r--r--src/dhcp/nm-dhcp-client.h7
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c41
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c7
-rw-r--r--src/dhcp/nm-dhcp-manager.c11
-rw-r--r--src/dhcp/nm-dhcp-manager.h2
-rw-r--r--src/dhcp/nm-dhcp-utils.h6
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c47
-rw-r--r--src/dns/nm-dns-manager.c19
-rw-r--r--src/nm-core-utils.c22
-rw-r--r--src/nm-core-utils.h1
-rw-r--r--src/nm-manager.c33
-rw-r--r--src/nm-policy.c6
-rw-r--r--src/platform/nm-linux-platform.c9
-rw-r--r--src/platform/nm-netlink.h3
-rw-r--r--src/platform/nm-platform-utils.c62
-rw-r--r--src/platform/nm-platform.c17
-rw-r--r--src/platform/nm-platform.h4
-rw-r--r--src/platform/wifi/wifi-utils-nl80211.c42
-rw-r--r--src/platform/wifi/wifi-utils-private.h3
-rw-r--r--src/platform/wifi/wifi-utils.c9
-rw-r--r--src/platform/wifi/wifi-utils.h3
-rw-r--r--src/settings/nm-settings-connection.c5
-rw-r--r--src/settings/nm-settings-connection.h4
-rw-r--r--src/settings/nm-settings.c17
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-ifdown3
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-ifup3
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c151
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c25
-rw-r--r--src/supplicant/nm-supplicant-interface.c16
-rw-r--r--src/supplicant/nm-supplicant-interface.h2
-rw-r--r--src/systemd/sd-adapt/copy.h3
-rw-r--r--src/systemd/src/basic/alloc-util.c5
-rw-r--r--src/systemd/src/basic/alloc-util.h6
-rw-r--r--src/systemd/src/basic/async.h6
-rw-r--r--src/systemd/src/basic/escape.c11
-rw-r--r--src/systemd/src/basic/escape.h8
-rw-r--r--src/systemd/src/basic/ether-addr-util.c4
-rw-r--r--src/systemd/src/basic/ether-addr-util.h4
-rw-r--r--src/systemd/src/basic/extract-word.c5
-rw-r--r--src/systemd/src/basic/extract-word.h6
-rw-r--r--src/systemd/src/basic/fd-util.c227
-rw-r--r--src/systemd/src/basic/fd-util.h10
-rw-r--r--src/systemd/src/basic/fileio.c114
-rw-r--r--src/systemd/src/basic/fileio.h23
-rw-r--r--src/systemd/src/basic/fs-util.c44
-rw-r--r--src/systemd/src/basic/fs-util.h22
-rw-r--r--src/systemd/src/basic/hash-funcs.c5
-rw-r--r--src/systemd/src/basic/hash-funcs.h5
-rw-r--r--src/systemd/src/basic/hashmap.c5
-rw-r--r--src/systemd/src/basic/hashmap.h5
-rw-r--r--src/systemd/src/basic/hexdecoct.c5
-rw-r--r--src/systemd/src/basic/hexdecoct.h6
-rw-r--r--src/systemd/src/basic/hostname-util.c5
-rw-r--r--src/systemd/src/basic/hostname-util.h6
-rw-r--r--src/systemd/src/basic/in-addr-util.c5
-rw-r--r--src/systemd/src/basic/in-addr-util.h6
-rw-r--r--src/systemd/src/basic/io-util.c5
-rw-r--r--src/systemd/src/basic/io-util.h6
-rw-r--r--src/systemd/src/basic/list.h6
-rw-r--r--src/systemd/src/basic/log.h10
-rw-r--r--src/systemd/src/basic/macro.h8
-rw-r--r--src/systemd/src/basic/mempool.c5
-rw-r--r--src/systemd/src/basic/mempool.h6
-rw-r--r--src/systemd/src/basic/parse-util.c57
-rw-r--r--src/systemd/src/basic/parse-util.h9
-rw-r--r--src/systemd/src/basic/path-util.c212
-rw-r--r--src/systemd/src/basic/path-util.h24
-rw-r--r--src/systemd/src/basic/prioq.c5
-rw-r--r--src/systemd/src/basic/prioq.h6
-rw-r--r--src/systemd/src/basic/process-util.c59
-rw-r--r--src/systemd/src/basic/process-util.h27
-rw-r--r--src/systemd/src/basic/random-util.c9
-rw-r--r--src/systemd/src/basic/random-util.h6
-rw-r--r--src/systemd/src/basic/refcnt.h6
-rw-r--r--src/systemd/src/basic/set.h6
-rw-r--r--src/systemd/src/basic/signal-util.h6
-rw-r--r--src/systemd/src/basic/socket-util.c7
-rw-r--r--src/systemd/src/basic/socket-util.h8
-rw-r--r--src/systemd/src/basic/stat-util.c31
-rw-r--r--src/systemd/src/basic/stat-util.h7
-rw-r--r--src/systemd/src/basic/stdio-util.h6
-rw-r--r--src/systemd/src/basic/string-table.c5
-rw-r--r--src/systemd/src/basic/string-table.h22
-rw-r--r--src/systemd/src/basic/string-util.c183
-rw-r--r--src/systemd/src/basic/string-util.h33
-rw-r--r--src/systemd/src/basic/strv.c5
-rw-r--r--src/systemd/src/basic/strv.h6
-rw-r--r--src/systemd/src/basic/time-util.c41
-rw-r--r--src/systemd/src/basic/time-util.h10
-rw-r--r--src/systemd/src/basic/umask-util.h6
-rw-r--r--src/systemd/src/basic/utf8.c23
-rw-r--r--src/systemd/src/basic/utf8.h7
-rw-r--r--src/systemd/src/basic/util.c61
-rw-r--r--src/systemd/src/basic/util.h6
-rw-r--r--src/systemd/src/libsystemd-network/arp-util.c6
-rw-r--r--src/systemd/src/libsystemd-network/arp-util.h4
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.c4
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.h4
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-internal.h6
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-lease-internal.h6
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-network.c4
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-option.c4
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-packet.c6
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-protocol.h4
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-internal.h4
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-lease-internal.h6
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-network.c4
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-option.c6
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-protocol.h4
-rw-r--r--src/systemd/src/libsystemd-network/lldp-internal.h6
-rw-r--r--src/systemd/src/libsystemd-network/lldp-neighbor.c5
-rw-r--r--src/systemd/src/libsystemd-network/lldp-neighbor.h6
-rw-r--r--src/systemd/src/libsystemd-network/lldp-network.c6
-rw-r--r--src/systemd/src/libsystemd-network/lldp-network.h6
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.c4
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.h41
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-client.c4
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-lease.c8
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-client.c4
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-lease.c6
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4acd.c6
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4ll.c6
-rw-r--r--src/systemd/src/libsystemd-network/sd-lldp.c6
-rw-r--r--src/systemd/src/libsystemd/sd-event/sd-event.c970
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.c3
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.h3
-rw-r--r--src/systemd/src/libsystemd/sd-id128/sd-id128.c5
-rw-r--r--src/systemd/src/shared/dns-domain.c8
-rw-r--r--src/systemd/src/shared/dns-domain.h5
-rw-r--r--src/systemd/src/systemd/_sd-common.h3
-rw-r--r--src/systemd/src/systemd/sd-dhcp-client.h4
-rw-r--r--src/systemd/src/systemd/sd-dhcp-lease.h6
-rw-r--r--src/systemd/src/systemd/sd-dhcp6-client.h4
-rw-r--r--src/systemd/src/systemd/sd-dhcp6-lease.h6
-rw-r--r--src/systemd/src/systemd/sd-event.h10
-rw-r--r--src/systemd/src/systemd/sd-id128.h3
-rw-r--r--src/systemd/src/systemd/sd-ipv4acd.h6
-rw-r--r--src/systemd/src/systemd/sd-ipv4ll.h4
-rw-r--r--src/systemd/src/systemd/sd-lldp.h6
-rw-r--r--src/systemd/src/systemd/sd-ndisc.h4
155 files changed, 2851 insertions, 1138 deletions
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index 1d237d92..92ad0b31 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -903,11 +903,8 @@ act_stage3_ip6_config_start (NMDevice *device,
 {
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
 
-	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
-		return nm_modem_stage3_ip6_config_start (priv->modem,
-		                                         nm_device_get_act_request (device),
-		                                         out_failure_reason);
-	}
+	if (priv->bt_type == NM_BT_CAPABILITY_DUN)
+		return nm_modem_stage3_ip6_config_start (priv->modem, device, out_failure_reason);
 
 	return NM_DEVICE_CLASS (nm_device_bt_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
 }
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index 6c5e33e4..87d16dbe 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -753,13 +753,6 @@ supplicant_interface_init (NMDeviceEthernet *self)
 	return TRUE;
 }
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (link_duplex_to_string, NMPlatformLinkDuplexType,
-	NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_LINK_DUPLEX_UNKNOWN, "unknown"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_LINK_DUPLEX_FULL,     "full"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_LINK_DUPLEX_HALF,     "half"),
-);
-
 static NMPlatformLinkDuplexType
 link_duplex_to_platform (const char *duplex)
 {
@@ -787,13 +780,11 @@ link_negotiation_set (NMDevice *device)
 	s_wired = (NMSettingWired *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
 	if (s_wired) {
 		autoneg = nm_setting_wired_get_auto_negotiate (s_wired);
-		if (!autoneg) {
-			speed = nm_setting_wired_get_speed (s_wired);
-			duplex = link_duplex_to_platform (nm_setting_wired_get_duplex (s_wired));
-			if (!speed && !duplex) {
-				_LOGD (LOGD_DEVICE, "set-link: ignore link negotiation");
-				return;
-			}
+		speed = nm_setting_wired_get_speed (s_wired);
+		duplex = link_duplex_to_platform (nm_setting_wired_get_duplex (s_wired));
+		if (!autoneg && !speed && !duplex) {
+			_LOGD (LOGD_DEVICE, "set-link: ignore link negotiation");
+			return;
 		}
 	}
 
@@ -804,20 +795,23 @@ link_negotiation_set (NMDevice *device)
 	}
 
 	/* If link negotiation setting are already in place do nothing and return with success */
-	if (   (!!autoneg == !!link_autoneg)
-	    && (!speed || (speed == link_speed))
-	    && (!duplex || (duplex == link_duplex))) {
+	if (   !!autoneg == !!link_autoneg
+	    && speed == link_speed
+	    && duplex == link_duplex) {
 		_LOGD (LOGD_DEVICE, "set-link: link negotiation is already configured");
 		return;
 	}
 
-	if (autoneg)
-		_LOGD (LOGD_DEVICE, "set-link: configure autonegotiation");
+	if (autoneg && !speed && !duplex)
+		_LOGD (LOGD_DEVICE, "set-link: configure auto-negotiation");
 	else {
-		_LOGD (LOGD_DEVICE, "set-link: configure static negotiation (%u Mbit%s - %s duplex%s)",
+		_LOGD (LOGD_DEVICE, "set-link: configure %snegotiation (%u Mbit%s - %s duplex%s)",
+		       autoneg ? "auto-" : "static ",
 		       speed ?: link_speed,
 		       speed ? "" : "*",
-		       duplex ? link_duplex_to_string (duplex) : link_duplex_to_string (link_duplex),
+		       duplex
+		         ? nm_platform_link_duplex_type_to_string (duplex)
+		         : nm_platform_link_duplex_type_to_string (link_duplex),
 		       duplex ? "" : "*");
 	}
 
diff --git a/src/devices/nm-device-macsec.c b/src/devices/nm-device-macsec.c
index 4eaf5469..166bfb57 100644
--- a/src/devices/nm-device-macsec.c
+++ b/src/devices/nm-device-macsec.c
@@ -704,6 +704,7 @@ create_and_realize (NMDevice *device,
 	sci.s.port = htons (nm_setting_macsec_get_port (s_macsec));
 	lnk.sci = be64toh (sci.u);
 	lnk.validation = nm_setting_macsec_get_validation (s_macsec);
+	lnk.include_sci = nm_setting_macsec_get_send_sci (s_macsec);
 
 	parent_ifindex = nm_device_get_ifindex (parent);
 	g_warn_if_fail (parent_ifindex > 0);
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index 0b844411..c76debff 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -134,7 +134,7 @@ void nm_device_commit_mtu (NMDevice *self);
 	}
 
 gboolean _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
-                                             GError **error, const char **argv);
+                                             GError **error, const char **whitelist);
 #define nm_device_hash_check_invalid_keys(hash, setting_name, error, ...) \
 	_nm_device_hash_check_invalid_keys (hash, setting_name, error, ((const char *[]) { __VA_ARGS__, NULL }))
 
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 9a40a0dd..9df41410 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -37,8 +37,11 @@
 #include <linux/if_arp.h>
 #include <linux/rtnetlink.h>
 #include <linux/pkt_sched.h>
+#include <uuid/uuid.h>
 
 #include "nm-utils/nm-dedup-multi.h"
+#include "nm-utils/nm-random-utils.h"
+#include "nm-utils/unaligned.h"
 
 #include "nm-common-macros.h"
 #include "nm-device-private.h"
@@ -6830,11 +6833,12 @@ dhcp4_grace_period_expired (gpointer user_data)
 }
 
 static void
-dhcp4_fail (NMDevice *self, gboolean timeout)
+dhcp4_fail (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	_LOGD (LOGD_DHCP4, "DHCPv4 failed%s", timeout ? " (timeout)" : "");
+	_LOGD (LOGD_DHCP4, "DHCPv4 failed (ip_state %s)",
+	       _ip_state_to_string (priv->ip4_state));
 
 	/* Keep client running if there are static addresses configured
 	 * on the interface.
@@ -6848,7 +6852,7 @@ dhcp4_fail (NMDevice *self, gboolean timeout)
 	 * configuration.
 	 */
 	if (   !priv->dhcp4.was_active
-	    && (timeout || priv->ip4_state == IP_CONF)) {
+	    && priv->ip4_state == IP_CONF) {
 		dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
 		nm_device_activate_schedule_ip4_config_timeout (self);
 		return;
@@ -6911,7 +6915,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 	case NM_DHCP_STATE_BOUND:
 		if (!ip4_config) {
 			_LOGW (LOGD_DHCP4, "failed to get IPv4 config in response to DHCP event.");
-			dhcp4_fail (self, FALSE);
+			dhcp4_fail (self);
 			break;
 		}
 
@@ -6950,11 +6954,11 @@ dhcp4_state_changed (NMDhcpClient *client,
 			if (dhcp4_lease_change (self, ip4_config))
 				nm_device_update_metered (self);
 			else
-				dhcp4_fail (self, FALSE);
+				dhcp4_fail (self);
 		}
 		break;
 	case NM_DHCP_STATE_TIMEOUT:
-		dhcp4_fail (self, TRUE);
+		dhcp4_fail (self);
 		break;
 	case NM_DHCP_STATE_EXPIRE:
 		/* Ignore expiry before we even have a lease (NAK, old lease, etc) */
@@ -6963,7 +6967,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 		/* fall through */
 	case NM_DHCP_STATE_DONE:
 	case NM_DHCP_STATE_FAIL:
-		dhcp4_fail (self, FALSE);
+		dhcp4_fail (self);
 		break;
 	default:
 		break;
@@ -7014,13 +7018,32 @@ get_dhcp_timeout (NMDevice *self, int addr_family)
 }
 
 static GBytes *
-dhcp4_get_client_id (NMDevice *self, NMConnection *connection)
+dhcp4_get_client_id_mac (const guint8 *hwaddr /* ETH_ALEN bytes */)
+{
+	guint8 *client_id_buf;
+	guint8 hwaddr_type = ARPHRD_ETHER;
+
+	client_id_buf = g_malloc (ETH_ALEN + 1);
+	client_id_buf[0] = hwaddr_type;
+	memcpy (&client_id_buf[1], hwaddr, ETH_ALEN);
+	return g_bytes_new_take (client_id_buf, ETH_ALEN + 1);
+}
+
+static GBytes *
+dhcp4_get_client_id (NMDevice *self,
+                     NMConnection *connection,
+                     GBytes *hwaddr)
 {
 	NMSettingIPConfig *s_ip4;
 	const char *client_id;
 	gs_free char *client_id_default = NULL;
 	guint8 *client_id_buf;
-	gboolean is_mac;
+	const char *fail_reason;
+	guint8 hwaddr_bin_buf[NM_UTILS_HWADDR_LEN_MAX];
+	const guint8 *hwaddr_bin;
+	gsize hwaddr_len;
+	GBytes *result;
+	gs_free char *logstr1 = NULL;
 
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 	client_id = nm_setting_ip4_config_get_dhcp_client_id (NM_SETTING_IP4_CONFIG (s_ip4));
@@ -7028,42 +7051,54 @@ dhcp4_get_client_id (NMDevice *self, NMConnection *connection)
 	if (!client_id) {
 		client_id_default = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
 		                                                           "ipv4.dhcp-client-id", self);
-		if (client_id_default && client_id_default[0])
+		if (client_id_default && client_id_default[0]) {
+			/* a non-empty client-id is always valid, see nm_dhcp_utils_client_id_string_to_bytes().  */
 			client_id = client_id_default;
+		}
 	}
 
-	if (!client_id)
+	if (!client_id) {
+		_LOGD (LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4,
+		       "ipv4.dhcp-client-id: no explicity client-id configured");
 		return NULL;
+	}
 
-	if (   (is_mac = nm_streq (client_id, "mac"))
-	    || nm_streq (client_id, "perm-mac")) {
-		const char *hwaddr;
-		char addr_buf[NM_UTILS_HWADDR_LEN_MAX];
-		gsize addr_len;
-		guint8 addr_type;
-
-		hwaddr = is_mac
-		         ? nm_device_get_hw_address (self)
-		         : nm_device_get_permanent_hw_address (self);
-		if (!hwaddr)
-			return NULL;
+	if (nm_streq (client_id, "mac")) {
+		if (!hwaddr) {
+			fail_reason = "failed to get current MAC address";
+			goto out_fail;
+		}
+
+		hwaddr_bin = g_bytes_get_data (hwaddr, &hwaddr_len);
+		if (hwaddr_len != ETH_ALEN) {
+			fail_reason = "MAC address is not ethernet";
+			goto out_fail;
+		}
+
+		result = dhcp4_get_client_id_mac (hwaddr_bin);
+		goto out_good;
+	}
+
+	if (nm_streq (client_id, "perm-mac")) {
+		const char *hwaddr_str;
+
+		hwaddr_str = nm_device_get_permanent_hw_address (self);
+		if (!hwaddr_str) {
+			fail_reason = "failed to get permanent MAC address";
+			goto out_fail;
+		}
 
-		if (!_nm_utils_hwaddr_aton (hwaddr, addr_buf, sizeof (addr_buf), &addr_len))
+		if (!_nm_utils_hwaddr_aton (hwaddr_str, hwaddr_bin_buf, sizeof (hwaddr_bin_buf), &hwaddr_len))
 			g_return_val_if_reached (NULL);
 
-		switch (addr_len) {
-		case ETH_ALEN:
-			addr_type = ARPHRD_ETHER;
-			break;
-		default:
+		if (hwaddr_len != ETH_ALEN) {
 			/* unsupported type. */
-			return NULL;
+			fail_reason = "MAC address is not ethernet";
+			goto out_fail;
 		}
 
-		client_id_buf = g_malloc (addr_len + 1);
-		client_id_buf[0] = addr_type;
-		memcpy (&client_id_buf[1], addr_buf, addr_len);
-		return g_bytes_new_take (client_id_buf, addr_len + 1);
+		result = dhcp4_get_client_id_mac (hwaddr_bin_buf);
+		goto out_good;
 	}
 
 	if (nm_streq (client_id, "stable")) {
@@ -7099,10 +7134,30 @@ dhcp4_get_client_id (NMDevice *self, NMConnection *connection)
 		client_id_buf = g_malloc (1 + 15);
 		client_id_buf[0] = 0;
 		memcpy (&client_id_buf[1], buf, 15);
-		return g_bytes_new_take (client_id_buf, 1 + 15);
+		result = g_bytes_new_take (client_id_buf, 1 + 15);
+		goto out_good;
 	}
 
-	return nm_dhcp_utils_client_id_string_to_bytes (client_id);
+	result = nm_dhcp_utils_client_id_string_to_bytes (client_id);
+	goto out_good;
+
+out_fail:
+	nm_assert (fail_reason);
+	_LOGW (LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4,
+	       "ipv4.dhcp-client-id: failure to generate client id (%s). Use random client id",
+	       fail_reason);
+	client_id_buf = g_malloc (1 + 15);
+	client_id_buf[0] = 0;
+	nm_utils_random_bytes (&client_id_buf[1], 15);
+	result = g_bytes_new_take (client_id_buf, 1 + 15);
+
+out_good:
+	nm_assert (result);
+	_LOGD (LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4,
+	       "ipv4.dhcp-client-id: use \"%s\" client ID: %s",
+	       client_id,
+	       (logstr1 = nm_dhcp_utils_duid_to_string (result)));
+	return result;
 }
 
 static NMActStageReturn
@@ -7126,7 +7181,7 @@ dhcp4_start (NMDevice *self)
 	hwaddr = nm_platform_link_get_address_as_bytes (nm_device_get_platform (self),
 	                                                nm_device_get_ip_ifindex (self));
 
-	client_id = dhcp4_get_client_id (self, connection);
+	client_id = dhcp4_get_client_id (self, connection, hwaddr);
 
 	g_warn_if_fail (priv->dhcp4.client == NULL);
 	priv->dhcp4.client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (),
@@ -7678,12 +7733,260 @@ dhcp6_prefix_delegated (NMDhcpClient *client,
 	g_signal_emit (self, signals[IP6_PREFIX_DELEGATED], 0, prefix);
 }
 
+/* RFC 3315 defines the epoch for the DUID-LLT time field on Jan 1st 2000. */
+#define EPOCH_DATETIME_200001010000  946684800
+
+static GBytes *
+generate_duid_llt (const guint8 *hwaddr /* ETH_ALEN bytes */,
+                   gint64 time)
+{
+	GByteArray *duid_arr;
+	const guint16 duid_type = htons (1);
+	const guint16 hw_type = htons (ARPHRD_ETHER);
+	const guint32 duid_time = htonl (NM_MAX (0, time - EPOCH_DATETIME_200001010000));
+
+	duid_arr = g_byte_array_sized_new (2 + 4 + 2 + ETH_ALEN);
+
+	g_byte_array_append (duid_arr, (const guint8 *) &duid_type, 2);
+	g_byte_array_append (duid_arr, (const guint8 *) &hw_type, 2);
+	g_byte_array_append (duid_arr, (const guint8 *) &duid_time, 4);
+	g_byte_array_append (duid_arr, hwaddr, ETH_ALEN);
+
+	return g_byte_array_free_to_bytes (duid_arr);
+}
+
+static GBytes *
+generate_duid_ll (const guint8 *hwaddr /* ETH_ALEN bytes */)
+{
+	GByteArray *duid_arr;
+	const guint16 duid_type = htons (3);
+	const guint16 hw_type = htons (ARPHRD_ETHER);
+
+	duid_arr = g_byte_array_sized_new (2 + 2 + ETH_ALEN);
+
+	g_byte_array_append (duid_arr, (const guint8 *) &duid_type, 2);
+	g_byte_array_append (duid_arr, (const guint8 *) &hw_type, 2);
+	g_byte_array_append (duid_arr, hwaddr, ETH_ALEN);
+
+	return g_byte_array_free_to_bytes (duid_arr);
+}
+
+static GBytes *
+generate_duid_uuid (guint8 *data, gsize data_len)
+{
+	const guint16 duid_type = g_htons (4);
+	const int DUID_SIZE = 18;
+	guint8 *duid_buffer;
+
+	nm_assert (data);
+	nm_assert (data_len >= 16);
+
+	/* Generate a DHCP Unique Identifier for DHCPv6 using the
+	 * DUID-UUID method (see RFC 6355 section 4).  Format is:
+	 *
+	 * u16: type (DUID-UUID = 4)
+	 * u8[16]: UUID bytes
+	 */
+	duid_buffer = g_malloc (DUID_SIZE);
+
+	G_STATIC_ASSERT_EXPR (sizeof (duid_type) == 2);
+	memcpy (&duid_buffer[0], &duid_type, 2);
+
+	/* UUID is 128 bits, we just take the first 128 bits
+	 * (regardless of data size) as the DUID-UUID.
+	 */
+	memcpy (&duid_buffer[2], data, 16);
+
+	return g_bytes_new_take (duid_buffer, DUID_SIZE);
+}
+
+static GBytes *
+generate_duid_from_machine_id (void)
+{
+	gs_free const char *machine_id_s = NULL;
+	uuid_t uuid;
+	GChecksum *sum;
+	guint8 sha256_digest[32];
+	gsize len = sizeof (sha256_digest);
+	static GBytes *global_duid = NULL;
+
+	if (global_duid)
+		return g_bytes_ref (global_duid);
+
+	machine_id_s = nm_utils_machine_id_read ();
+	if (!nm_utils_machine_id_parse (machine_id_s, uuid))
+		return NULL;
+
+	/* Hash the machine ID so it's not leaked to the network */
+	sum = g_checksum_new (G_CHECKSUM_SHA256);
+	g_checksum_update (sum, (const guchar *) &uuid, sizeof (uuid));
+	g_checksum_get_digest (sum, sha256_digest, &len);
+	g_checksum_free (sum);
+
+	global_duid = generate_duid_uuid (sha256_digest, len);
+	return g_bytes_ref (global_duid);
+}
+
+static GBytes *
+dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, NMDhcpDuidEnforce *out_enforce)
+{
+	NMSettingIPConfig *s_ip6;
+	const char *duid;
+	gs_free char *duid_default = NULL;
+	const char *duid_error;
+	GBytes *duid_out;
+	guint8 sha256_digest[32];
+	gsize len = sizeof (sha256_digest);
+	NMDhcpDuidEnforce duid_enforce = NM_DHCP_DUID_ENFORCE_ALWAYS;
+	gs_free char *logstr1 = NULL;
+
+	s_ip6 = nm_connection_get_setting_ip6_config (connection);
+	duid = nm_setting_ip6_config_get_dhcp_duid (NM_SETTING_IP6_CONFIG (s_ip6));
+
+	if (!duid) {
+		duid_default = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+		                                                      "ipv6.dhcp-duid", self);
+		duid = duid_default;
+		if (!duid)
+			duid = "lease";
+	}
+
+	if (nm_streq (duid, "lease")) {
+		duid_enforce = NM_DHCP_DUID_ENFORCE_NEVER;
+		duid_out = generate_duid_from_machine_id ();
+		if (!duid_out) {
+			duid_error = "failure to read machine-id";
+			goto out_fail;
+		}
+		goto out_good;
+	}
+
+	if (!_nm_utils_dhcp_duid_valid (duid, &duid_out)) {
+		duid_error = "invalid duid";
+		goto out_fail;
+	}
+
+	if (duid_out)
+		goto out_good;
+
+	if (NM_IN_STRSET (duid, "ll", "llt")) {
+		if (!hwaddr) {
+			duid_error = "missing link-layer address";
+			goto out_fail;
+		}
+		if (g_bytes_get_size (hwaddr) != ETH_ALEN) {
+			duid_error = "unsupported link-layer address";
+			goto out_fail;
+		}
+
+		if (nm_streq (duid, "ll")) {
+			duid_out = generate_duid_ll (g_bytes_get_data (hwaddr, NULL));
+		} else {
+			gint64 time;
+
+			time = nm_utils_secret_key_get_timestamp ();
+			if (!time) {
+				duid_error = "cannot retrieve the secret key timestamp";
+				goto out_fail;
+			}
+
+			duid_out = generate_duid_llt (g_bytes_get_data (hwaddr, NULL), time);
+		}
+
+		goto out_good;
+	}
+
+	if (NM_IN_STRSET (duid, "stable-ll", "stable-llt", "stable-uuid")) {
+		NMUtilsStableType stable_type;
+		const char *stable_id = NULL;
+		guint32 salted_header;
+		GChecksum *sum;
+		const guint8 *secret_key;
+		gsize secret_key_len;
+
+		stable_id = _get_stable_id (self, connection, &stable_type);
+		if (!stable_id)
+			g_return_val_if_reached (NULL);
+
+		salted_header = htonl (670531087 + stable_type);
+
+		nm_utils_secret_key_get (&secret_key, &secret_key_len);
+
+		sum = g_checksum_new (G_CHECKSUM_SHA256);
+
+		g_checksum_update (sum, (const guchar *) &salted_header, sizeof (salted_header));
+		g_checksum_update (sum, (const guchar *) stable_id, -1);
+		g_checksum_update (sum, (const guchar *) secret_key, secret_key_len);
+
+		g_checksum_get_digest (sum, sha256_digest, &len);
+		g_checksum_free (sum);
+
+		if (nm_streq (duid, "stable-ll")) {
+			duid_out = generate_duid_ll (sha256_digest);
+		} else if (nm_streq (duid, "stable-llt")) {
+			gint64 time;
+
+#define EPOCH_DATETIME_THREE_YEARS  (356 * 24 * 3600 * 3)
+
+			/* We want a variable time between the secret_key timestamp and three years
+			 * before. Let's compute the time (in seconds) from 0 to 3 years; then we'll
+			 * subtract it from the secret_key timestamp.
+			 */
+			time = nm_utils_secret_key_get_timestamp ();
+			if (!time) {
+				duid_error = "cannot retrieve the secret key timestamp";
+				goto out_fail;
+			}
+			/* don't use too old timestamps. They cannot be expressed in DUID-LLT and
+			 * would all be truncated to zero. */
+			time = NM_MAX (time, EPOCH_DATETIME_200001010000 + EPOCH_DATETIME_THREE_YEARS);
+			time -= (unaligned_read_be32 (&sha256_digest[ETH_ALEN]) % EPOCH_DATETIME_THREE_YEARS);
+
+			duid_out = generate_duid_llt (sha256_digest, time);
+		} else {
+			nm_assert (nm_streq (duid, "stable-uuid"));
+			duid_out = generate_duid_uuid (sha256_digest, len);
+		}
+
+		goto out_good;
+	}
+
+	g_return_val_if_reached (NULL);
+
+out_fail:
+	nm_assert (!duid_out && duid_error);
+	{
+		guint8 uuid[16];
+
+		_LOGW (LOGD_IP6 | LOGD_DHCP6,
+		       "ipv6.dhcp-duid: failure to generate %s DUID: %s. Fallback to random DUID-UUID.",
+		       duid, duid_error);
+
+		nm_utils_random_bytes (uuid, sizeof (uuid));
+		duid_out = generate_duid_uuid (uuid, sizeof (uuid));
+	}
+
+out_good:
+	nm_assert (duid_out);
+	_LOGD (LOGD_IP6 | LOGD_DHCP6,
+	       "ipv6.dhcp-duid: generate %s DUID '%s' (%s)",
+	       duid,
+	       (logstr1 = nm_dhcp_utils_duid_to_string (duid_out)),
+	       (duid_enforce == NM_DHCP_DUID_ENFORCE_ALWAYS) ? "enforcing" : "fallback");
+
+	NM_SET_OUT (out_enforce, duid_enforce);
+	return duid_out;
+}
+
 static gboolean
 dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMSettingIPConfig *s_ip6;
 	gs_unref_bytes GBytes *hwaddr = NULL;
+	gs_unref_bytes GBytes *duid = NULL;
+	NMDhcpDuidEnforce enforce_duid = NM_DHCP_DUID_ENFORCE_NEVER;
+
 	const NMPlatformIP6Address *ll_addr = NULL;
 
 	g_assert (connection);
@@ -7704,6 +8007,7 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	hwaddr = nm_platform_link_get_address_as_bytes (nm_device_get_platform (self),
 	                                                nm_device_get_ip_ifindex (self));
 
+	duid = dhcp6_get_duid (self, connection, hwaddr, &enforce_duid);
 	priv->dhcp6.client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (),
 	                                                nm_device_get_multi_index (self),
 	                                                nm_device_get_ip_iface (self),
@@ -7715,6 +8019,8 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	                                                nm_device_get_route_metric (self, AF_INET6),
 	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip6),
 	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip6),
+	                                                duid,
+	                                                enforce_duid,
 	                                                get_dhcp_timeout (self, AF_INET6),
 	                                                priv->dhcp_anycast_address,
 	                                                (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE,
@@ -8139,8 +8445,10 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 	if (ifindex <= 0)
 		return;
 
-	if (nm_device_sys_iface_state_is_external_or_assume (self)) {
-		/* for assumed connections we don't tamper with the MTU. */
+	if (   !nm_device_get_applied_connection (self)
+	    || nm_device_sys_iface_state_is_external_or_assume (self)) {
+		/* we don't tamper with the MTU of disconnected and
+		 * external/assumed devices. */
 		return;
 	}
 
@@ -9886,43 +10194,45 @@ _cleanup_ip_pre (NMDevice *self, int addr_family, CleanupType cleanup_type)
 
 gboolean
 _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
-                                    GError **error, const char **argv)
+                                    GError **error, const char **whitelist)
 {
-	guint found_keys = 0;
+	guint found_whitelisted_keys = 0;
 	guint i;
 
 	nm_assert (hash && g_hash_table_size (hash) > 0);
-	nm_assert (argv && argv[0]);
+	nm_assert (whitelist && whitelist[0]);
 
 #if NM_MORE_ASSERTS > 10
-	/* Assert that the keys are unique. */
+	/* Require whitelist to only contain unique keys. */
 	{
 		gs_unref_hashtable GHashTable *check_dups = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, NULL);
 
-		for (i = 0; argv[i]; i++) {
-			if (!g_hash_table_add (check_dups, (char *) argv[i]))
+		for (i = 0; whitelist[i]; i++) {
+			if (!g_hash_table_add (check_dups, (char *) whitelist[i]))
 				nm_assert (FALSE);
 		}
 		nm_assert (g_hash_table_size (check_dups) > 0);
 	}
 #endif
 
-	for (i = 0; argv[i]; i++) {
-		if (g_hash_table_contains (hash, argv[i]))
-			found_keys++;
+	for (i = 0; whitelist[i]; i++) {
+		if (g_hash_table_contains (hash, whitelist[i]))
+			found_whitelisted_keys++;
 	}
 
-	if (found_keys != g_hash_table_size (hash)) {
+	if (found_whitelisted_keys == g_hash_table_size (hash)) {
+		/* Good, there are only whitelisted keys in the hash. */
+		return TRUE;
+	}
+
+	if (error) {
 		GHashTableIter iter;
 		const char *k = NULL;
 		const char *first_invalid_key = NULL;
 
-		if (!error)
-			return FALSE;
-
 		g_hash_table_iter_init (&iter, hash);
 		while (g_hash_table_iter_next (&iter, (gpointer *) &k, NULL)) {
-			if (nm_utils_strv_find_first ((char **) argv, -1, k) < 0) {
+			if (nm_utils_strv_find_first ((char **) whitelist, -1, k) < 0) {
 				first_invalid_key = k;
 				break;
 			}
@@ -9942,10 +10252,9 @@ _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
 			             first_invalid_key);
 		}
 		g_return_val_if_fail (first_invalid_key, FALSE);
-		return FALSE;
 	}
 
-	return TRUE;
+	return FALSE;
 }
 
 void
@@ -10088,9 +10397,11 @@ can_reapply_change (NMDevice *self, const char *setting_name,
 		                                          NM_SETTING_CONNECTION_METERED,
 		                                          NM_SETTING_CONNECTION_LLDP);
 	} else if (NM_IN_STRSET (setting_name,
-	                         NM_SETTING_IP4_CONFIG_SETTING_NAME,
-	                         NM_SETTING_IP6_CONFIG_SETTING_NAME,
 	                         NM_SETTING_PROXY_SETTING_NAME)) {
+		return TRUE;
+	} else if (NM_IN_STRSET (setting_name,
+	                         NM_SETTING_IP4_CONFIG_SETTING_NAME,
+	                         NM_SETTING_IP6_CONFIG_SETTING_NAME)) {
 		if (g_hash_table_contains (diffs, NM_SETTING_IP_CONFIG_ROUTE_TABLE)) {
 			/* changing the route-table setting is complicated, because it affects
 			 * how we sync the routes. Don't support changing it without full
@@ -10221,7 +10532,7 @@ check_and_reapply_connection (NMDevice *self,
 
 	if (diffs) {
 		NMConnection *connection_clean = connection;
-		gs_free NMConnection *connection_clean_free = NULL;
+		gs_unref_object NMConnection *connection_clean_free = NULL;
 
 		{
 			NMSettingConnection *s_con_a, *s_con_n;
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
index eefe38c8..c59af7a6 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -116,7 +116,7 @@ link_changed (NMDevice *device,
 {
 	NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE (device);
 
-	if (priv->waiting_for_interface) {
+	if (pllink && priv->waiting_for_interface) {
 		priv->waiting_for_interface = FALSE;
 		nm_device_bring_up (device, TRUE, NULL);
 		nm_device_activate_schedule_stage3_ip_config_start (device);
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index d3c5ae9a..d28fcdbd 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -54,6 +54,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceIwd,
 	PROP_ACTIVE_ACCESS_POINT,
 	PROP_CAPABILITIES,
 	PROP_SCANNING,
+	PROP_LAST_SCAN,
 );
 
 enum {
@@ -79,6 +80,7 @@ typedef struct {
 	bool            can_connect:1;
 	bool            scanning:1;
 	bool            scan_requested:1;
+	gint64          last_scan;
 } NMDeviceIwdPrivate;
 
 struct _NMDeviceIwd {
@@ -863,6 +865,8 @@ scan_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 
 	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	priv->scan_requested = FALSE;
+	priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
+	_notify (self, PROP_LAST_SCAN);
 
 	/* On success, priv->scanning becomes true right before or right
 	 * after this callback, so the next automatic scan will be
@@ -1530,6 +1534,12 @@ get_property (GObject *object, guint prop_id,
 	case PROP_SCANNING:
 		g_value_set_boolean (value, priv->scanning);
 		break;
+	case PROP_LAST_SCAN:
+		g_value_set_int64 (value,
+		                   priv->last_scan > 0
+		                       ? nm_utils_monotonic_timestamp_as_boottime (priv->last_scan, NM_UTILS_NS_PER_MSEC)
+		                       : (gint64) -1);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -1909,6 +1919,11 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 	                          G_PARAM_READABLE |
 	                          G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_LAST_SCAN] =
+	    g_param_spec_int64 (NM_DEVICE_IWD_LAST_SCAN, "", "",
+	                        -1, G_MAXINT64, -1,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	signals[SCANNING_PROHIBITED] =
diff --git a/src/devices/wifi/nm-device-iwd.h b/src/devices/wifi/nm-device-iwd.h
index 699ba1bb..a4253f52 100644
--- a/src/devices/wifi/nm-device-iwd.h
+++ b/src/devices/wifi/nm-device-iwd.h
@@ -38,6 +38,7 @@
 #define NM_DEVICE_IWD_ACTIVE_ACCESS_POINT NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT
 #define NM_DEVICE_IWD_CAPABILITIES        NM_DEVICE_WIFI_CAPABILITIES
 #define NM_DEVICE_IWD_SCANNING            NM_DEVICE_WIFI_SCANNING
+#define NM_DEVICE_IWD_LAST_SCAN           NM_DEVICE_WIFI_LAST_SCAN
 
 #define NM_DEVICE_IWD_SCANNING_PROHIBITED  NM_DEVICE_WIFI_SCANNING_PROHIBITED
 
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 90f94557..1a86e13b 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -74,6 +74,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
 	PROP_ACTIVE_ACCESS_POINT,
 	PROP_CAPABILITIES,
 	PROP_SCANNING,
+	PROP_LAST_SCAN,
 );
 
 enum {
@@ -96,8 +97,8 @@ typedef struct {
 	bool              ssid_found:1;
 	bool              is_scanning:1;
 
-	gint32            last_scan;
-	gint32            scheduled_scan_time;
+	gint64            last_scan; /* milliseconds */
+	gint32            scheduled_scan_time; /* seconds */
 	guint8            scan_interval; /* seconds */
 	guint             pending_scan_id;
 	guint             ap_dump_id;
@@ -1126,7 +1127,7 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
-	gint32 last_scan;
+	gint64 last_scan;
 
 	if (   !priv->enabled
 	    || !priv->sup_iface
@@ -1147,8 +1148,8 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
 		return;
 	}
 
-	last_scan = nm_supplicant_interface_get_last_scan_time (priv->sup_iface);
-	if (last_scan && (nm_utils_get_monotonic_timestamp_s () - last_scan) < 10) {
+	last_scan = nm_supplicant_interface_get_last_scan (priv->sup_iface);
+	if (last_scan && (nm_utils_get_monotonic_timestamp_ms () - last_scan) < 10 * NM_UTILS_MSEC_PER_SECOND) {
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
@@ -1422,7 +1423,8 @@ supplicant_iface_scan_done_cb (NMSupplicantInterface *iface,
 
 	_LOGD (LOGD_WIFI, "wifi-scan: scan-done callback: %s", success ? "successful" : "failed");
 
-	priv->last_scan = nm_utils_get_monotonic_timestamp_s ();
+	priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
+	_notify (self, PROP_LAST_SCAN);
 	schedule_scan (self, success);
 
 	_requested_scan_set (self, FALSE);
@@ -1445,9 +1447,9 @@ ap_list_dump (gpointer user_data)
 		NMWifiAP *ap;
 		gint32 now_s = nm_utils_get_monotonic_timestamp_s ();
 
-		_LOGD (LOGD_WIFI_SCAN, "APs: [now:%u last:%u next:%u]",
+		_LOGD (LOGD_WIFI_SCAN, "APs: [now:%u last:%" G_GINT64_FORMAT " next:%u]",
 		       now_s,
-		       priv->last_scan,
+		       priv->last_scan / NM_UTILS_MSEC_PER_SECOND,
 		       priv->scheduled_scan_time);
 		c_list_for_each_entry (ap, &priv->aps_lst_head, aps_lst)
 			_ap_dump (self, LOGL_DEBUG, ap, "dump", now_s);
@@ -2421,6 +2423,48 @@ error:
 
 /*****************************************************************************/
 
+static gboolean
+wake_on_wlan_enable (NMDeviceWifi *self)
+{
+	NMSettingWirelessWakeOnWLan wowl;
+	NMSettingWireless *s_wireless;
+	gs_free char *value = NULL;
+
+	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_WIRELESS);
+	if (s_wireless) {
+		wowl = nm_setting_wireless_get_wake_on_wlan (s_wireless);
+		if (wowl != NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT)
+			goto found;
+	}
+
+	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+	                                               "wifi.wake-on-wlan",
+	                                               NM_DEVICE (self));
+
+	if (value) {
+		wowl = _nm_utils_ascii_str_to_int64 (value, 10,
+		                                     NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE,
+		                                     G_MAXINT32,
+		                                     NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT);
+
+		if (NM_FLAGS_ANY (wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_EXCLUSIVE_FLAGS)) {
+			if (!nm_utils_is_power_of_two (wowl)) {
+				_LOGD (LOGD_WIFI, "invalid default value %u for wake-on-wlan: "
+				       "'default' and 'ignore' are exclusive flags", (guint) wowl);
+				wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT;
+			}
+		} else if (NM_FLAGS_ANY (wowl, ~NM_SETTING_WIRELESS_WAKE_ON_WLAN_ALL)) {
+			_LOGD (LOGD_WIFI, "invalid default value %u for wake-on-wlan", (guint) wowl);
+			wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT;
+		}
+		if (wowl != NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT)
+			goto found;
+	}
+	wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
+found:
+	return nm_platform_wifi_set_wake_on_wlan (NM_PLATFORM_GET, nm_device_get_ifindex (NM_DEVICE (self)), wowl);
+}
+
 static NMActStageReturn
 act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
@@ -2614,6 +2658,8 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_assert (s_wireless);
 
+	wake_on_wlan_enable (self);
+
 	/* If we need secrets, get them */
 	setting_name = nm_connection_need_secrets (connection, NULL);
 	if (setting_name) {
@@ -3081,7 +3127,8 @@ can_reapply_change (NMDevice *device,
 		return nm_device_hash_check_invalid_keys (diffs,
 		                                          NM_SETTING_WIRELESS_SETTING_NAME,
 		                                          error,
-		                                          NM_SETTING_WIRELESS_MTU); /* reapplied with IP config */
+		                                          NM_SETTING_WIRELESS_MTU, /* reapplied with IP config */
+		                                          NM_SETTING_WIRELESS_WAKE_ON_WLAN);
 	}
 
 	device_class = NM_DEVICE_CLASS (nm_device_wifi_parent_class);
@@ -3093,6 +3140,20 @@ can_reapply_change (NMDevice *device,
 	                                         error);
 }
 
+static void
+reapply_connection (NMDevice *device, NMConnection *con_old, NMConnection *con_new)
+{
+	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+
+	NM_DEVICE_CLASS (nm_device_wifi_parent_class)->reapply_connection (device,
+	                                                                   con_old,
+	                                                                   con_new);
+
+	_LOGD (LOGD_DEVICE, "reapplying wireless settings");
+
+	wake_on_wlan_enable (self);
+}
+
 /*****************************************************************************/
 
 static void
@@ -3123,6 +3184,12 @@ get_property (GObject *object, guint prop_id,
 	case PROP_SCANNING:
 		g_value_set_boolean (value, priv->is_scanning);
 		break;
+	case PROP_LAST_SCAN:
+		g_value_set_int64 (value,
+		                   priv->last_scan > 0
+		                       ? nm_utils_monotonic_timestamp_as_boottime (priv->last_scan, NM_UTILS_NS_PER_MSEC)
+		                       : (gint64) -1);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -3256,6 +3323,7 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	parent_class->deactivate_reset_hw_addr = deactivate_reset_hw_addr;
 	parent_class->unmanaged_on_quit = unmanaged_on_quit;
 	parent_class->can_reapply_change = can_reapply_change;
+	parent_class->reapply_connection = reapply_connection;
 
 	parent_class->state_changed = device_state_changed;
 
@@ -3300,6 +3368,11 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	                          G_PARAM_READABLE |
 	                          G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_LAST_SCAN] =
+	    g_param_spec_int64 (NM_DEVICE_WIFI_LAST_SCAN, "", "",
+	                        -1, G_MAXINT64, -1,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	signals[SCANNING_PROHIBITED] =
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index c13b00de..1c555f5f 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -37,6 +37,7 @@
 #define NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT "active-access-point"
 #define NM_DEVICE_WIFI_CAPABILITIES        "wireless-capabilities"
 #define NM_DEVICE_WIFI_SCANNING            "scanning"
+#define NM_DEVICE_WIFI_LAST_SCAN           "last-scan"
 
 #define NM_DEVICE_WIFI_SCANNING_PROHIBITED    "scanning-prohibited"
 
diff --git a/src/devices/wifi/nm-wifi-common.c b/src/devices/wifi/nm-wifi-common.c
index e5e16f03..8e079d11 100644
--- a/src/devices/wifi/nm-wifi-common.c
+++ b/src/devices/wifi/nm-wifi-common.c
@@ -200,6 +200,7 @@ const NMDBusInterfaceInfoExtended nm_interface_info_device_wireless = {
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("AccessPoints",         "ao", NM_DEVICE_WIFI_ACCESS_POINTS),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ActiveAccessPoint",    "o",  NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WirelessCapabilities", "u",  NM_DEVICE_WIFI_CAPABILITIES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE   ("LastScan",             "x",  NM_DEVICE_WIFI_LAST_SCAN),
 		),
 	),
 	.legacy_property_changed = TRUE,
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index 2a3e9ebe..352b1c3e 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -542,7 +542,7 @@ act_stage3_ip6_config_start (NMDevice *device,
                              NMDeviceStateReason *out_failure_reason)
 {
 	return nm_modem_stage3_ip6_config_start (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem,
-	                                         nm_device_get_act_request (device),
+	                                         device,
 	                                         out_failure_reason);
 }
 
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index fbe99cc3..ff1579d1 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -704,6 +704,8 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 	connection = nm_act_request_get_applied_connection (req);
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
+	nm_modem_set_route_parameters_from_device (self, device);
+
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
 
 	/* Only Disabled and Auto methods make sense for WWAN */
@@ -745,8 +747,6 @@ nm_modem_ip4_pre_commit (NMModem *modem,
 {
 	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (modem);
 
-	nm_modem_set_route_parameters_from_device (modem, device);
-
 	/* If the modem has an ethernet-type data interface (ie, not PPP and thus
 	 * not point-to-point) and IP config has a /32 prefix, then we assume that
 	 * ARP will be pointless and we turn it off.
@@ -804,20 +804,25 @@ stage3_ip6_config_request (NMModem *self, NMDeviceStateReason *out_failure_reaso
 
 NMActStageReturn
 nm_modem_stage3_ip6_config_start (NMModem *self,
-                                  NMActRequest *req,
+                                  NMDevice *device,
                                   NMDeviceStateReason *out_failure_reason)
 {
 	NMModemPrivate *priv;
+	NMActRequest *req;
 	NMActStageReturn ret;
 	NMConnection *connection;
 	const char *method;
 
 	g_return_val_if_fail (NM_IS_MODEM (self), NM_ACT_STAGE_RETURN_FAILURE);
-	g_return_val_if_fail (NM_IS_ACT_REQUEST (req), NM_ACT_STAGE_RETURN_FAILURE);
+
+	req = nm_device_get_act_request (device);
+	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
 
 	connection = nm_act_request_get_applied_connection (req);
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
+	nm_modem_set_route_parameters_from_device (self, device);
+
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
 
 	/* Only Ignore and Auto methods make sense for WWAN */
diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h
index 3e281c0c..6e78d2d5 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/devices/wwan/nm-modem.h
@@ -221,7 +221,7 @@ NMActStageReturn nm_modem_stage3_ip4_config_start (NMModem *modem,
                                                    NMDeviceStateReason *out_failure_reason);
 
 NMActStageReturn nm_modem_stage3_ip6_config_start (NMModem *modem,
-                                                   NMActRequest *req,
+                                                   NMDevice *device,
                                                    NMDeviceStateReason *out_failure_reason);
 
 void nm_modem_ip4_pre_commit (NMModem *modem, NMDevice *device, NMIP4Config *config);
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index ba517606..390b7054 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -513,69 +513,15 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
 }
 
 static GBytes *
-generate_duid_from_machine_id (void)
+get_duid (NMDhcpClient *self, gboolean global)
 {
-	const int DUID_SIZE = 18;
-	guint8 *duid_buffer;
-	GChecksum *sum;
-	guint8 buffer[32]; /* SHA256 digest size */
-	gsize sumlen = sizeof (buffer);
-	const guint16 duid_type = g_htons (4);
-	uuid_t uuid;
-	gs_free char *machine_id_s = NULL;
-	gs_free char *str = NULL;
-	GBytes *duid;
-
-	machine_id_s = nm_utils_machine_id_read ();
-	if (nm_utils_machine_id_parse (machine_id_s, uuid)) {
-		/* Hash the machine ID so it's not leaked to the network */
-		sum = g_checksum_new (G_CHECKSUM_SHA256);
-		g_checksum_update (sum, (const guchar *) &uuid, sizeof (uuid));
-		g_checksum_get_digest (sum, buffer, &sumlen);
-		g_checksum_free (sum);
-	} else {
-		nm_log_warn (LOGD_DHCP, "dhcp: failed to read " SYSCONFDIR "/machine-id "
-		             "or " LOCALSTATEDIR "/lib/dbus/machine-id to generate "
-		             "DHCPv6 DUID; creating non-persistent random DUID.");
-
-		nm_utils_random_bytes (buffer, sizeof (buffer));
-	}
-
-	/* Generate a DHCP Unique Identifier for DHCPv6 using the
-	 * DUID-UUID method (see RFC 6355 section 4).  Format is:
-	 *
-	 * u16: type (DUID-UUID = 4)
-	 * u8[16]: UUID bytes
-	 */
-	duid_buffer = g_malloc (DUID_SIZE);
-
-	G_STATIC_ASSERT_EXPR (sizeof (duid_type) == 2);
-	memcpy (&duid_buffer[0], &duid_type, 2);
-
-	/* Since SHA256 is 256 bits, but UUID is 128 bits, we just take the first
-	 * 128 bits of the SHA256 as the DUID-UUID.
-	 */
-	memcpy (&duid_buffer[2], buffer, 16);
-
-	duid = g_bytes_new_take (duid_buffer, DUID_SIZE);
-	nm_log_dbg (LOGD_DHCP, "dhcp: generated DUID %s",
-	            (str = nm_dhcp_utils_duid_to_string (duid)));
-	return duid;
-}
-
-static GBytes *
-get_duid (NMDhcpClient *self)
-{
-	static GBytes *duid = NULL;
-
-	if (G_UNLIKELY (!duid))
-		duid = generate_duid_from_machine_id ();
-
-	return g_bytes_ref (duid);
+	return NULL;
 }
 
 gboolean
 nm_dhcp_client_start_ip6 (NMDhcpClient *self,
+                          GBytes *client_id,
+                          NMDhcpDuidEnforce enforce_duid,
                           const char *dhcp_anycast_addr,
                           const struct in6_addr *ll_addr,
                           const char *hostname,
@@ -592,11 +538,17 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
 	g_return_val_if_fail (priv->addr_family == AF_INET6, FALSE);
 	g_return_val_if_fail (priv->uuid != NULL, FALSE);
 
-	/* If we don't have one yet, read the default DUID for this DHCPv6 client
-	 * from the client-specific persistent configuration.
-	 */
+	nm_assert (!priv->duid);
+	nm_assert (client_id);
+
+	if (enforce_duid == NM_DHCP_DUID_ENFORCE_NEVER)
+		priv->duid = NM_DHCP_CLIENT_GET_CLASS (self)->get_duid (self, TRUE);
+	else if (enforce_duid == NM_DHCP_DUID_ENFORCE_LEASE_FALLBACK)
+		priv->duid = NM_DHCP_CLIENT_GET_CLASS (self)->get_duid (self, FALSE);
+
+	/* NM_DHCP_DUID_ENFORCE_ALWAYS and fallback */
 	if (!priv->duid)
-		priv->duid = NM_DHCP_CLIENT_GET_CLASS (self)->get_duid (self);
+		priv->duid = g_bytes_ref (client_id);
 
 	_LOGD ("DUID is '%s'", (str = nm_dhcp_utils_duid_to_string (priv->duid)));
 
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index 111b063b..98c3ed26 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -23,6 +23,7 @@
 #include "nm-setting-ip6-config.h"
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
+#include "nm-dhcp-utils.h"
 
 #define NM_DHCP_TIMEOUT_DEFAULT ((guint32) 45) /* default DHCP timeout, in seconds */
 #define NM_DHCP_TIMEOUT_INFINITY G_MAXINT32
@@ -95,13 +96,15 @@ typedef struct {
 	/**
 	 * get_duid:
 	 * @self: the #NMDhcpClient
+	 * @global: if set to #true, the duid should be searched also in the
+	 *          DHCP client's system-wide persistent configuration.
 	 *
 	 * Attempts to find an existing DHCPv6 DUID for this client in the DHCP
 	 * client's persistent configuration.  Returned DUID should be the binary
 	 * representation of the DUID.  If no DUID is found, %NULL should be
 	 * returned.
 	 */
-	GBytes *(*get_duid) (NMDhcpClient *self);
+	GBytes *(*get_duid) (NMDhcpClient *self, gboolean global);
 
 	/* Signals */
 	void (*state_changed) (NMDhcpClient *self,
@@ -149,6 +152,8 @@ gboolean nm_dhcp_client_start_ip4 (NMDhcpClient *self,
                                    const char *last_ip4_address);
 
 gboolean nm_dhcp_client_start_ip6 (NMDhcpClient *self,
+                                   GBytes *client_id,
+                                   NMDhcpDuidEnforce enforce_duid,
                                    const char *dhcp_anycast_addr,
                                    const struct in6_addr *ll_addr,
                                    const char *hostname,
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index 16a76d23..3290dd65 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -594,7 +594,8 @@ nm_dhcp_dhclient_save_duid (const char *leasefile,
                             const char *escaped_duid,
                             GError **error)
 {
-	char **lines = NULL, **iter, *l;
+	gs_strfreev char **lines = NULL;
+	char **iter, *l;
 	GString *s;
 	gboolean success;
 	gsize len = 0;
@@ -610,19 +611,9 @@ nm_dhcp_dhclient_save_duid (const char *leasefile,
 			return FALSE;
 		}
 
-		/* If the file already contains an uncommented DUID, leave it */
 		g_assert (contents);
 		lines = g_strsplit_set (contents, "\n\r", -1);
 		g_free (contents);
-		for (iter = lines; iter && *iter; iter++) {
-			l = *iter;
-			while (g_ascii_isspace (*l))
-				l++;
-			if (g_str_has_prefix (l, DUID_PREFIX)) {
-				g_strfreev (lines);
-				return TRUE;
-			}
-		}
 	}
 
 	s = g_string_sized_new (len + 50);
@@ -630,9 +621,31 @@ nm_dhcp_dhclient_save_duid (const char *leasefile,
 
 	/* Preserve existing leasefile contents */
 	if (lines) {
-		for (iter = lines; iter && *iter; iter++)
-			g_string_append (s, *iter[0] ? *iter : "\n");
-		g_strfreev (lines);
+		for (iter = lines; iter && *iter; iter++) {
+			l = *iter;
+			while (g_ascii_isspace (*l))
+				l++;
+			/* If we find an uncommented DUID in the file, check if
+			 * equal to the one we are going to write: if so, no need
+			 * to update the lease file, otherwise skip the old DUID.
+			 */
+			if (g_str_has_prefix (l, DUID_PREFIX)) {
+				gs_strfreev char **split = NULL;
+
+				split = g_strsplit (l, "\"", -1);
+				if (nm_streq0 (split[1], escaped_duid)) {
+					g_string_free (s, TRUE);
+					return TRUE;
+				}
+				continue;
+			}
+
+			if (*iter[0])
+				g_string_append (s, *iter);
+			/* avoid to add an extra '\n' at the end of file */
+			if ((iter[1]) != NULL)
+				g_string_append_c (s, '\n');
+		}
 	}
 
 	success = g_file_set_contents (leasefile, s->str, -1, error);
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index 93306ddd..43746dd3 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -582,7 +582,7 @@ state_changed (NMDhcpClient *client,
 }
 
 static GBytes *
-get_duid (NMDhcpClient *client)
+get_duid (NMDhcpClient *client, gboolean global)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
@@ -607,7 +607,7 @@ get_duid (NMDhcpClient *client)
 		g_free (leasefile);
 	}
 
-	if (!duid) {
+	if (!duid && global) {
 		/* Otherwise read the default machine-wide DUID */
 		_LOGD ("looking for default DUID in '%s'", priv->def_leasefile);
 		duid = nm_dhcp_dhclient_read_duid (priv->def_leasefile, &error);
@@ -619,8 +619,7 @@ get_duid (NMDhcpClient *client)
 		}
 	}
 
-	/* return our DUID, otherwise let the parent class make a default DUID */
-	return duid ?: NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->get_duid (client);
+	return duid;
 }
 
 /*****************************************************************************/
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index aa40e803..2d85c73a 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -164,6 +164,7 @@ client_start (NMDhcpManager *self,
               guint32 route_metric,
               const struct in6_addr *ipv6_ll_addr,
               GBytes *dhcp_client_id,
+              NMDhcpDuidEnforce enforce_duid,
               guint32 timeout,
               const char *dhcp_anycast_addr,
               const char *hostname,
@@ -218,7 +219,7 @@ client_start (NMDhcpManager *self,
 	if (addr_family == AF_INET)
 		success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname, last_ip4_address);
 	else
-		success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, privacy, needed_prefixes);
+		success = nm_dhcp_client_start_ip6 (client, dhcp_client_id, enforce_duid, dhcp_anycast_addr, ipv6_ll_addr, hostname, privacy, needed_prefixes);
 
 	if (!success) {
 		remove_client_unref (self, client);
@@ -280,7 +281,7 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
 
 	return client_start (self, AF_INET, multi_idx, iface, ifindex, hwaddr, uuid,
 	                     route_table, route_metric, NULL,
-	                     dhcp_client_id, timeout, dhcp_anycast_addr, hostname,
+	                     dhcp_client_id, 0, timeout, dhcp_anycast_addr, hostname,
 	                     use_fqdn, FALSE, 0, last_ip_address, 0);
 }
 
@@ -297,6 +298,8 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
                            guint32 route_metric,
                            gboolean send_hostname,
                            const char *dhcp_hostname,
+                           GBytes *duid,
+                           NMDhcpDuidEnforce enforce_duid,
                            guint32 timeout,
                            const char *dhcp_anycast_addr,
                            gboolean info_only,
@@ -314,8 +317,8 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
 		hostname = dhcp_hostname ?: priv->default_hostname;
 	}
 	return client_start (self, AF_INET6, multi_idx, iface, ifindex, hwaddr, uuid,
-	                     route_table, route_metric, ll_addr,
-	                     NULL, timeout, dhcp_anycast_addr, hostname, TRUE, info_only,
+	                     route_table, route_metric, ll_addr, duid, enforce_duid,
+	                     timeout, dhcp_anycast_addr, hostname, TRUE, info_only,
 	                     privacy, NULL, needed_prefixes);
 }
 
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/dhcp/nm-dhcp-manager.h
index f8a7e31d..ed8ee742 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -72,6 +72,8 @@ NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
                                               guint32 route_metric,
                                               gboolean send_hostname,
                                               const char *dhcp_hostname,
+                                              GBytes *duid,
+                                              NMDhcpDuidEnforce enforce_duid,
                                               guint32 timeout,
                                               const char *dhcp_anycast_addr,
                                               gboolean info_only,
diff --git a/src/dhcp/nm-dhcp-utils.h b/src/dhcp/nm-dhcp-utils.h
index 5c127bd1..afb87c1a 100644
--- a/src/dhcp/nm-dhcp-utils.h
+++ b/src/dhcp/nm-dhcp-utils.h
@@ -24,6 +24,12 @@
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
 
+typedef enum {
+	NM_DHCP_DUID_ENFORCE_NEVER = 0,
+	NM_DHCP_DUID_ENFORCE_LEASE_FALLBACK,
+	NM_DHCP_DUID_ENFORCE_ALWAYS,
+} NMDhcpDuidEnforce;
+
 NMIP4Config *nm_dhcp_utils_ip4_config_from_options (struct _NMDedupMultiIndex *multi_idx,
                                                     int ifindex,
                                                     const char *iface,
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index a8284b23..2f369aac 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -785,17 +785,18 @@ static void
 test_write_existing_duid (void)
 {
 	const char *duid = "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302";
-	const char *expected_contents = "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n";
+	const char *original_contents = "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n";
+	const char *expected_contents = "default-duid \"\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302\";\n";
 	GError *error = NULL;
 	char *contents = NULL;
 	gboolean success;
 	const char *path = "test-dhclient-write-existing-duid.leases";
 
-	success = g_file_set_contents (path, expected_contents, -1, &error);
+	success = g_file_set_contents (path, original_contents, -1, &error);
 	g_assert_no_error (error);
 	g_assert (success);
 
-	/* Save other DUID; should be a no-op */
+	/* Save other DUID; should be overwritten */
 	success = nm_dhcp_dhclient_save_duid (path, duid, &error);
 	g_assert_no_error (error);
 	g_assert (success);
@@ -811,14 +812,14 @@ test_write_existing_duid (void)
 	g_free (contents);
 }
 
+#define DUID "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302"
 static void
 test_write_existing_commented_duid (void)
 {
-	#define DUID "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302"
-	#define ORIG_CONTENTS "#default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n"
-	const char *expected_contents = \
-		"default-duid \"" DUID "\";\n"
-		ORIG_CONTENTS;
+#define ORIG_CONTENTS "#default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n"
+	const char *expected_contents =
+	    "default-duid \"" DUID "\";\n"
+	    ORIG_CONTENTS;
 	GError *error = NULL;
 	char *contents = NULL;
 	gboolean success;
@@ -828,7 +829,7 @@ test_write_existing_commented_duid (void)
 	g_assert_no_error (error);
 	g_assert (success);
 
-	/* Save other DUID; should be a no-op */
+	/* Save other DUID; should be saved on top */
 	success = nm_dhcp_dhclient_save_duid (path, DUID, &error);
 	g_assert_no_error (error);
 	g_assert (success);
@@ -842,6 +843,33 @@ test_write_existing_commented_duid (void)
 	g_assert_cmpstr (expected_contents, ==, contents);
 
 	g_free (contents);
+#undef ORIG_CONTENTS
+}
+
+static void
+test_write_existing_multiline_duid (void)
+{
+#define ORIG_CONTENTS "### Commented old DUID ###\n" \
+                      "#default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n"
+	const char *expected_contents = \
+	    "default-duid \"" DUID "\";\n"
+	    ORIG_CONTENTS;
+	GError *error = NULL;
+	gs_free char *contents = NULL;
+	gboolean success;
+	nmtst_auto_unlinkfile char *path = g_strdup ("test-dhclient-write-existing-multiline-duid.leases");
+
+	success = g_file_set_contents (path, ORIG_CONTENTS, -1, &error);
+	nmtst_assert_success (success, error);
+
+	success = nm_dhcp_dhclient_save_duid (path, DUID, &error);
+	nmtst_assert_success (success, error);
+
+	success = g_file_get_contents (path, &contents, NULL, &error);
+	nmtst_assert_success (success, error);
+
+	g_assert_cmpstr (expected_contents, ==, contents);
+#undef ORIG_CONTENTS
 }
 
 /*****************************************************************************/
@@ -1025,6 +1053,7 @@ main (int argc, char **argv)
 	g_test_add_func ("/dhcp/dhclient/write_duid", test_write_duid);
 	g_test_add_func ("/dhcp/dhclient/write_existing_duid", test_write_existing_duid);
 	g_test_add_func ("/dhcp/dhclient/write_existing_commented_duid", test_write_existing_commented_duid);
+	g_test_add_func ("/dhcp/dhclient/write_existing_multiline_duid", test_write_existing_multiline_duid);
 
 	return g_test_run ();
 }
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index f5500ee7..a29fd81f 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -768,8 +768,6 @@ update_resolv_conf (NMDnsManager *self,
 	gs_free char *content = NULL;
 	SpawnResult write_file_result = SR_SUCCESS;
 	int errsv;
-	const char *rc_path = _PATH_RESCONF;
-	nm_auto_free char *rc_path_real = NULL;
 	gboolean resconf_link_cached = FALSE;
 	gs_free char *resconf_link = NULL;
 
@@ -794,12 +792,27 @@ update_resolv_conf (NMDnsManager *self,
 	if (   rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE
 	    || (   rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK
 	        && !_read_link_cached (_PATH_RESCONF, &resconf_link_cached, &resconf_link))) {
+		gs_free char *rc_path_syml = NULL;
+		nm_auto_free char *rc_path_real = NULL;
+		const char *rc_path = _PATH_RESCONF;
 		GError *local = NULL;
 
 		if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE) {
-			rc_path_real = realpath (rc_path, NULL);
+			rc_path_real = realpath (_PATH_RESCONF, NULL);
 			if (rc_path_real)
 				rc_path = rc_path_real;
+			else {
+				/* realpath did not resolve a path-name. That either means,
+				 * _PATH_RESCONF:
+				 *   - does not exist
+				 *   - is a plain file
+				 *   - is a dangling symlink
+				 *
+				 * Handle the case, where it is a dangling symlink... */
+				rc_path_syml = nm_utils_read_link_absolute (_PATH_RESCONF, NULL);
+				if (rc_path_syml)
+					rc_path = rc_path_syml;
+			}
 		}
 
 		/* we first write to /etc/resolv.conf directly. If that fails,
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index b1a4cc25..54ccdb83 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -2521,20 +2521,20 @@ nm_utils_machine_id_read (void)
 	 */
 	if (   !g_file_get_contents ("/etc/machine-id", &contents, NULL, NULL)
 	    && !g_file_get_contents (LOCALSTATEDIR "/lib/dbus/machine-id", &contents, NULL, NULL))
-		return FALSE;
+		return NULL;
 
 	contents = g_strstrip (contents);
 
 	for (i = 0; i < 32; i++) {
 		if (!g_ascii_isxdigit (contents[i]))
-			return FALSE;
+			return NULL;
 		if (contents[i] >= 'A' && contents[i] <= 'F') {
 			/* canonicalize to lower-case */
 			contents[i] = 'a' + (contents[i] - 'A');
 		}
 	}
 	if (contents[i] != '\0')
-		return FALSE;
+		return NULL;
 
 	return g_steal_pointer (&contents);
 }
@@ -2896,6 +2896,22 @@ nm_utils_secret_key_get (const guint8 **out_secret_key,
 	return secret_key->is_good;
 }
 
+gint64
+nm_utils_secret_key_get_timestamp (void)
+{
+	struct stat stat_buf;
+	const guint8 *key;
+	gsize key_len;
+
+	if (!nm_utils_secret_key_get (&key, &key_len))
+		return 0;
+
+	if (stat (NMSTATEDIR "/secret_key", &stat_buf) != 0)
+		return 0;
+
+	return stat_buf.st_mtim.tv_sec;
+}
+
 /*****************************************************************************/
 
 const char *
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index 7f406d21..08530400 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -285,6 +285,7 @@ gboolean nm_utils_machine_id_parse (const char *id_str, /*uuid_t*/ guchar *out_u
 
 gboolean nm_utils_secret_key_get (const guint8 **out_secret_key,
                                   gsize *out_key_len);
+gint64 nm_utils_secret_key_get_timestamp (void);
 
 const char *nm_utils_get_boot_id (void);
 
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 9f0891a7..c6d82c9d 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -1558,6 +1558,12 @@ again:
 	}
 }
 
+static gboolean
+device_is_wake_on_lan (NMPlatform *platform, NMDevice *device)
+{
+	return nm_platform_link_get_wake_on_lan (platform, nm_device_get_ip_ifindex (device));
+}
+
 static void
 remove_device (NMManager *self,
                NMDevice *device,
@@ -1567,14 +1573,19 @@ remove_device (NMManager *self,
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	gboolean unmanage = FALSE;
 
-	_LOG2D (LOGD_DEVICE, device, "removing device (allow_unmanage %d, managed %d)",
-	        allow_unmanage, nm_device_get_managed (device, FALSE));
+	_LOG2D (LOGD_DEVICE, device, "removing device (allow_unmanage %d, managed %d, wol %d)",
+	        allow_unmanage, nm_device_get_managed (device, FALSE),
+	        device_is_wake_on_lan (priv->platform, device));
 
 	if (allow_unmanage && nm_device_get_managed (device, FALSE)) {
 
-		if (quitting)
-			unmanage = nm_device_unmanage_on_quit (device);
-		else {
+		if (quitting) {
+			/* Leave configured if wo(w)lan and quitting */
+			if (device_is_wake_on_lan (priv->platform, device))
+				unmanage = FALSE;
+			else
+				unmanage = nm_device_unmanage_on_quit (device);
+		} else {
 			/* the device is already gone. Unmanage it. */
 			unmanage = TRUE;
 		}
@@ -4747,12 +4758,16 @@ impl_manager_activate_connection (NMDBusObject *obj,
 	if (!active)
 		goto error;
 
-	nm_active_connection_authorize (g_steal_pointer (&active),
+	nm_active_connection_authorize (active,
 	                                NULL,
 	                                _async_op_complete_ac_auth_cb,
 	                                _async_op_data_new_ac_auth_activate_user (self,
 	                                                                          active,
 	                                                                          invocation));
+
+	/* we passed the pointer on to _async_op_data_new_ac_auth_activate_user() */
+	g_steal_pointer (&active);
+
 	return;
 
 error:
@@ -5135,12 +5150,6 @@ done:
 }
 
 static gboolean
-device_is_wake_on_lan (NMPlatform *platform, NMDevice *device)
-{
-	return nm_platform_link_get_wake_on_lan (platform, nm_device_get_ip_ifindex (device));
-}
-
-static gboolean
 sleep_devices_add (NMManager *self, NMDevice *device, gboolean suspending)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
diff --git a/src/nm-policy.c b/src/nm-policy.c
index fe16cda5..125f2704 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -1244,8 +1244,10 @@ auto_activate_device (NMPolicy *self,
 	if (!best_connection)
 		return;
 
-	_LOGI (LOGD_DEVICE, "auto-activating connection '%s'",
-	       nm_settings_connection_get_id (best_connection));
+	_LOGI (LOGD_DEVICE, "auto-activating connection '%s' (%s)",
+	       nm_settings_connection_get_id (best_connection),
+	       nm_settings_connection_get_uuid (best_connection));
+
 	subject = nm_auth_subject_new_internal ();
 	ac = nm_manager_activate_connection (priv->manager,
 	                                     best_connection,
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 3a671acf..9bd662a1 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -6092,6 +6092,14 @@ wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean ru
 	wifi_utils_indicate_addressing_running (wifi_data, running);
 }
 
+static gboolean
+wifi_set_wake_on_wlan (NMPlatform *platform, int ifindex,
+                       NMSettingWirelessWakeOnWLan wowl)
+{
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
+	return wifi_utils_set_wake_on_wlan (wifi_data, wowl);
+}
+
 /*****************************************************************************/
 
 static gboolean
@@ -7230,6 +7238,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->wifi_set_powersave = wifi_set_powersave;
 	platform_class->wifi_find_frequency = wifi_find_frequency;
 	platform_class->wifi_indicate_addressing_running = wifi_indicate_addressing_running;
+	platform_class->wifi_set_wake_on_wlan = wifi_set_wake_on_wlan;
 
 	platform_class->mesh_get_channel = mesh_get_channel;
 	platform_class->mesh_set_channel = mesh_set_channel;
diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h
index c0dc09c4..b938fa4a 100644
--- a/src/platform/nm-netlink.h
+++ b/src/platform/nm-netlink.h
@@ -244,6 +244,9 @@ nla_put_string (struct nl_msg *msg, int attrtype, const char *str)
 #define NLA_PUT_STRING(msg, attrtype, value) \
 	NLA_PUT(msg, attrtype, (int) strlen(value) + 1, value)
 
+#define NLA_PUT_FLAG(msg, attrtype) \
+	NLA_PUT(msg, attrtype, 0, NULL)
+
 struct nlattr *nla_find (const struct nlattr *head, int len, int attrtype);
 
 static inline int
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index 114bf4b3..0d224443 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -384,6 +384,40 @@ nmp_utils_ethtool_get_link_settings (int ifindex,
 	return TRUE;
 }
 
+
+#define ADVERTISED_INVALID 0
+#define BASET_ALL_MODES (  ADVERTISED_10baseT_Half \
+                         | ADVERTISED_10baseT_Full \
+                         | ADVERTISED_100baseT_Half \
+                         | ADVERTISED_100baseT_Full \
+                         | ADVERTISED_1000baseT_Half \
+                         | ADVERTISED_1000baseT_Full \
+                         | ADVERTISED_10000baseT_Full )
+
+static inline guint32
+get_baset_mode (guint32 speed, NMPlatformLinkDuplexType duplex)
+{
+	if (duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN)
+		return ADVERTISED_INVALID;
+
+	if (duplex == NM_PLATFORM_LINK_DUPLEX_HALF) {
+		switch (speed) {
+		case 10: return ADVERTISED_10baseT_Half;
+		case 100: return ADVERTISED_100baseT_Half;
+		case 1000: return ADVERTISED_1000baseT_Half;
+		default: return ADVERTISED_INVALID;
+		}
+	} else {
+		switch (speed) {
+		case 10: return ADVERTISED_10baseT_Full;
+		case 100: return ADVERTISED_100baseT_Full;
+		case 1000: return ADVERTISED_1000baseT_Full;
+		case 10000: return ADVERTISED_10000baseT_Full;
+		default: return ADVERTISED_INVALID;
+		}
+	}
+}
+
 gboolean
 nmp_utils_ethtool_set_link_settings (int ifindex,
                                      gboolean autoneg,
@@ -395,6 +429,8 @@ nmp_utils_ethtool_set_link_settings (int ifindex,
 	};
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (   (speed && duplex != NM_PLATFORM_LINK_DUPLEX_UNKNOWN)
+	                      || (!speed && duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN), FALSE);
 
 	/* retrieve first current settings */
 	if (!ethtool_get (ifindex, &edata))
@@ -404,7 +440,31 @@ nmp_utils_ethtool_set_link_settings (int ifindex,
 	edata.cmd = ETHTOOL_SSET;
 	if (autoneg) {
 		edata.autoneg = AUTONEG_ENABLE;
-		edata.advertising = edata.supported;
+		if (!speed)
+			edata.advertising = edata.supported;
+		else {
+			guint32 mode;
+
+			mode = get_baset_mode (speed, duplex);
+
+			if (!mode) {
+				nm_log_trace (LOGD_PLATFORM,
+				              "ethtool[%d]: %uBASE-T %s duplex mode cannot be advertised",
+				              ifindex,
+				              speed,
+				              nm_platform_link_duplex_type_to_string (duplex));
+				return FALSE;
+			}
+			if (!(edata.supported & mode)) {
+				nm_log_trace (LOGD_PLATFORM,
+				              "ethtool[%d]: device does not support %uBASE-T %s duplex mode",
+				              ifindex,
+				              speed,
+				              nm_platform_link_duplex_type_to_string (duplex));
+				return FALSE;
+			}
+			edata.advertising = (edata.supported & ~BASET_ALL_MODES) | mode;
+		}
 	} else {
 		edata.autoneg = AUTONEG_DISABLE;
 
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 1a94ce28..f75019e8 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -2888,6 +2888,16 @@ nm_platform_wifi_indicate_addressing_running (NMPlatform *self, int ifindex, gbo
 	klass->wifi_indicate_addressing_running (self, ifindex, running);
 }
 
+gboolean
+nm_platform_wifi_set_wake_on_wlan (NMPlatform *self, int ifindex, NMSettingWirelessWakeOnWLan wowl)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wifi_set_wake_on_wlan (self, ifindex, wowl);
+}
+
 guint32
 nm_platform_mesh_get_channel (NMPlatform *self, int ifindex)
 {
@@ -2995,6 +3005,13 @@ nm_platform_ethtool_get_link_settings (NMPlatform *self, int ifindex, gboolean *
 	return nmp_utils_ethtool_get_link_settings (ifindex, out_autoneg, out_speed, out_duplex);
 }
 
+NM_UTILS_LOOKUP_STR_DEFINE (nm_platform_link_duplex_type_to_string, NMPlatformLinkDuplexType,
+	NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_LINK_DUPLEX_UNKNOWN, "unknown"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_LINK_DUPLEX_FULL,    "full"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_LINK_DUPLEX_HALF,    "half"),
+);
+
 /*****************************************************************************/
 
 const NMDedupMultiHeadEntry *
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 2d2bfaab..866df736 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -33,6 +33,7 @@
 #include "nm-core-utils.h"
 #include "nm-setting-vlan.h"
 #include "nm-setting-wired.h"
+#include "nm-setting-wireless.h"
 #include "nm-setting-ip-tunnel.h"
 
 #define NM_TYPE_PLATFORM            (nm_platform_get_type ())
@@ -867,6 +868,7 @@ typedef struct {
 	void        (*wifi_set_powersave)    (NMPlatform *, int ifindex, guint32 powersave);
 	guint32     (*wifi_find_frequency)   (NMPlatform *, int ifindex, const guint32 *freqs);
 	void        (*wifi_indicate_addressing_running) (NMPlatform *, int ifindex, gboolean running);
+	gboolean    (*wifi_set_wake_on_wlan) (NMPlatform *, int ifindex, NMSettingWirelessWakeOnWLan wowl);
 
 	guint32     (*mesh_get_channel)      (NMPlatform *, int ifindex);
 	gboolean    (*mesh_set_channel)      (NMPlatform *, int ifindex, guint32 channel);
@@ -1245,6 +1247,7 @@ void        nm_platform_wifi_set_mode         (NMPlatform *self, int ifindex, NM
 void        nm_platform_wifi_set_powersave    (NMPlatform *self, int ifindex, guint32 powersave);
 guint32     nm_platform_wifi_find_frequency   (NMPlatform *self, int ifindex, const guint32 *freqs);
 void        nm_platform_wifi_indicate_addressing_running (NMPlatform *self, int ifindex, gboolean running);
+gboolean    nm_platform_wifi_set_wake_on_wlan (NMPlatform *self, int ifindex, NMSettingWirelessWakeOnWLan wowl);
 
 guint32     nm_platform_mesh_get_channel      (NMPlatform *self, int ifindex);
 gboolean    nm_platform_mesh_set_channel      (NMPlatform *self, int ifindex, guint32 channel);
@@ -1449,6 +1452,7 @@ int nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPla
 gboolean nm_platform_ethtool_set_wake_on_lan (NMPlatform *self, int ifindex, NMSettingWiredWakeOnLan wol, const char *wol_password);
 gboolean nm_platform_ethtool_set_link_settings (NMPlatform *self, int ifindex, gboolean autoneg, guint32 speed, NMPlatformLinkDuplexType duplex);
 gboolean nm_platform_ethtool_get_link_settings (NMPlatform *self, int ifindex, gboolean *out_autoneg, guint32 *out_speed, NMPlatformLinkDuplexType *out_duplex);
+const char * nm_platform_link_duplex_type_to_string (NMPlatformLinkDuplexType duplex);
 
 void nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
                                               int ifindex,
diff --git a/src/platform/wifi/wifi-utils-nl80211.c b/src/platform/wifi/wifi-utils-nl80211.c
index 767e23a9..6c2ff3f5 100644
--- a/src/platform/wifi/wifi-utils-nl80211.c
+++ b/src/platform/wifi/wifi-utils-nl80211.c
@@ -271,6 +271,47 @@ nla_put_failure:
 	return FALSE;
 }
 
+static gboolean
+wifi_nl80211_set_wake_on_wlan (WifiData *data, NMSettingWirelessWakeOnWLan wowl)
+{
+	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	struct nlattr *triggers;
+	int err;
+
+	if (wowl == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE)
+		return TRUE;
+
+	msg = nl80211_alloc_msg(nl80211, NL80211_CMD_SET_WOWLAN, 0);
+	if (!msg)
+		return FALSE;
+
+	triggers = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS);
+
+	if (NM_FLAGS_HAS (wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY))
+		NLA_PUT_FLAG (msg, NL80211_WOWLAN_TRIG_ANY);
+	if (NM_FLAGS_HAS (wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT))
+		NLA_PUT_FLAG (msg, NL80211_WOWLAN_TRIG_DISCONNECT);
+	if (NM_FLAGS_HAS (wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC))
+		NLA_PUT_FLAG (msg, NL80211_WOWLAN_TRIG_MAGIC_PKT);
+	if (NM_FLAGS_HAS (wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE))
+		NLA_PUT_FLAG (msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE);
+	if (NM_FLAGS_HAS (wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST))
+		NLA_PUT_FLAG (msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST);
+	if (NM_FLAGS_HAS (wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE))
+		NLA_PUT_FLAG (msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE);
+	if (NM_FLAGS_HAS (wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE))
+		NLA_PUT_FLAG (msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE);
+
+	nla_nest_end(msg, triggers);
+
+	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
+	return err ? FALSE : TRUE;
+
+nla_put_failure:
+	return FALSE;
+}
+
 /* @divisor: pass what value @xbm should be divided by to get dBm */
 static guint32
 nl80211_xbm_to_percent (gint32 xbm, guint32 divisor)
@@ -846,6 +887,7 @@ wifi_nl80211_init (int ifindex)
 		.get_mode = wifi_nl80211_get_mode,
 		.set_mode = wifi_nl80211_set_mode,
 		.set_powersave = wifi_nl80211_set_powersave,
+		.set_wake_on_wlan = wifi_nl80211_set_wake_on_wlan,
 		.get_freq = wifi_nl80211_get_freq,
 		.find_freq = wifi_nl80211_find_freq,
 		.get_bssid = wifi_nl80211_get_bssid,
diff --git a/src/platform/wifi/wifi-utils-private.h b/src/platform/wifi/wifi-utils-private.h
index 59386514..627108cb 100644
--- a/src/platform/wifi/wifi-utils-private.h
+++ b/src/platform/wifi/wifi-utils-private.h
@@ -34,6 +34,9 @@ typedef struct {
 	/* Set power saving mode on an interface */
 	gboolean (*set_powersave) (WifiData *data, guint32 powersave);
 
+	/* Set WakeOnWLAN mode on an interface */
+	gboolean (*set_wake_on_wlan) (WifiData *data, NMSettingWirelessWakeOnWLan wowl);
+
 	/* Return current frequency in MHz (really associated BSS frequency) */
 	guint32 (*get_freq) (WifiData *data);
 
diff --git a/src/platform/wifi/wifi-utils.c b/src/platform/wifi/wifi-utils.c
index 8818dc9d..b3dd92ba 100644
--- a/src/platform/wifi/wifi-utils.c
+++ b/src/platform/wifi/wifi-utils.c
@@ -112,6 +112,15 @@ wifi_utils_set_powersave (WifiData *data, guint32 powersave)
 	return data->klass->set_powersave ? data->klass->set_powersave (data, powersave) : TRUE;
 }
 
+gboolean
+wifi_utils_set_wake_on_wlan (WifiData *data, NMSettingWirelessWakeOnWLan wowl)
+{
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	return data->klass->set_wake_on_wlan ?
+	       data->klass->set_wake_on_wlan (data, wowl) : FALSE;
+}
+
 guint32
 wifi_utils_get_freq (WifiData *data)
 {
diff --git a/src/platform/wifi/wifi-utils.h b/src/platform/wifi/wifi-utils.h
index 8acc35fc..c69561de 100644
--- a/src/platform/wifi/wifi-utils.h
+++ b/src/platform/wifi/wifi-utils.h
@@ -25,6 +25,7 @@
 #include <net/ethernet.h>
 
 #include "nm-dbus-interface.h"
+#include "nm-setting-wireless.h"
 
 typedef struct WifiData WifiData;
 
@@ -66,6 +67,8 @@ gboolean wifi_utils_get_wowlan (WifiData *data);
 
 gboolean wifi_utils_set_powersave (WifiData *data, guint32 powersave);
 
+gboolean wifi_utils_set_wake_on_wlan (WifiData *data, NMSettingWirelessWakeOnWLan wowl);
+
 /* OLPC Mesh-only functions */
 guint32 wifi_utils_get_mesh_channel (WifiData *data);
 
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 6526abc7..50cc1427 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -3189,8 +3189,9 @@ static const NMDBusInterfaceInfoExtended interface_info_settings_connection = {
 			&signal_info_removed,
 		),
 		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Unsaved", "b",  NM_SETTINGS_CONNECTION_UNSAVED),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE   ("Flags",   "u",  NM_SETTINGS_CONNECTION_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Unsaved",  "b",  NM_SETTINGS_CONNECTION_UNSAVED),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE   ("Flags",    "u",  NM_SETTINGS_CONNECTION_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE   ("Filename", "s",  NM_SETTINGS_CONNECTION_FILENAME),
 		),
 	),
 	.legacy_property_changed = TRUE,
diff --git a/src/settings/nm-settings-connection.h b/src/settings/nm-settings-connection.h
index 70518687..cd64f976 100644
--- a/src/settings/nm-settings-connection.h
+++ b/src/settings/nm-settings-connection.h
@@ -42,11 +42,11 @@
 
 /* Properties */
 #define NM_SETTINGS_CONNECTION_UNSAVED  "unsaved"
+#define NM_SETTINGS_CONNECTION_FLAGS    "flags"
+#define NM_SETTINGS_CONNECTION_FILENAME "filename"
 
 /* Internal properties */
 #define NM_SETTINGS_CONNECTION_READY    "ready"
-#define NM_SETTINGS_CONNECTION_FLAGS    "flags"
-#define NM_SETTINGS_CONNECTION_FILENAME "filename"
 
 /**
  * NMSettingsConnectionIntFlags:
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 94018734..5bb629dc 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -653,7 +653,7 @@ plugin_loaded (GSList *list, const char *path)
 }
 
 static gboolean
-load_plugin (NMSettings *self, GSList *list, const char *pname, GError **error)
+load_plugin (NMSettings *self, GSList **list, const char *pname, GError **error)
 {
 	gs_free char *full_name = NULL;
 	gs_free char *path = NULL;
@@ -666,7 +666,7 @@ load_plugin (NMSettings *self, GSList *list, const char *pname, GError **error)
 	full_name = g_strdup_printf ("nm-settings-plugin-%s", pname);
 	path = g_module_build_path (NMPLUGINDIR, full_name);
 
-	if (plugin_loaded (list, path))
+	if (plugin_loaded (*list, path))
 		return TRUE;
 
 	if (stat (path, &st) != 0) {
@@ -719,7 +719,7 @@ load_plugin (NMSettings *self, GSList *list, const char *pname, GError **error)
 	g_object_set_qdata_full (obj, plugin_module_path_quark (), path, g_free);
 	path = NULL;
 	if (add_plugin (self, NM_SETTINGS_PLUGIN (obj)))
-		list = g_slist_append (list, g_steal_pointer (&obj));
+		*list = g_slist_append (*list, g_steal_pointer (&obj));
 
 	return TRUE;
 }
@@ -787,7 +787,7 @@ load_plugins (NMSettings *self, const char **plugins, GError **error)
 			continue;
 		}
 
-		success = load_plugin (self, list, pname, error);
+		success = load_plugin (self, &list, pname, error);
 		if (!success)
 			break;
 
@@ -797,7 +797,7 @@ load_plugins (NMSettings *self, const char **plugins, GError **error)
 			pname = "ibft";
 			add_ibft = FALSE;
 
-			success = load_plugin (self, list, "ibft", error);
+			success = load_plugin (self, &list, "ibft", error);
 			if (!success)
 				break;
 		}
@@ -886,6 +886,8 @@ connection_removed (NMSettingsConnection *connection, gpointer user_data)
 	g_object_unref (connection);
 
 	check_startup_complete (self);
+
+	g_object_unref (self);       /* Balanced by a ref in claim_connection() */
 }
 
 #define NM_DBUS_SERVICE_OPENCONNECT    "org.freedesktop.NetworkManager.openconnect"
@@ -998,6 +1000,11 @@ claim_connection (NMSettings *self, NMSettingsConnection *connection)
 	_clear_connections_cached_list (priv);
 
 	g_object_ref (connection);
+	/* FIXME(shutdown): The NMSettings instance can't be disposed
+	 * while there is any exported connection. Ideally we should
+	 * unexport all connections on NMSettings' disposal, but for now
+	 * leak @self on termination when there are connections alive. */
+	g_object_ref (self);
 	priv->connections_len++;
 	c_list_link_tail (&priv->connections_lst_head, &connection->_connections_lst);
 
diff --git a/src/settings/plugins/ifcfg-rh/nm-ifdown b/src/settings/plugins/ifcfg-rh/nm-ifdown
new file mode 100644
index 00000000..e7bd73ae
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/nm-ifdown
@@ -0,0 +1,3 @@
+#!/bin/sh
+nmcli connection load "/etc/sysconfig/network-scripts/ifcfg-$1" &&
+exec nmcli connection down filename "/etc/sysconfig/network-scripts/ifcfg-$1"
diff --git a/src/settings/plugins/ifcfg-rh/nm-ifup b/src/settings/plugins/ifcfg-rh/nm-ifup
new file mode 100644
index 00000000..96637a92
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/nm-ifup
@@ -0,0 +1,3 @@
+#!/bin/sh
+nmcli connection load "/etc/sysconfig/network-scripts/ifcfg-$1" &&
+exec nmcli connection up filename "/etc/sysconfig/network-scripts/ifcfg-$1"
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 722d4e36..c1960846 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -1685,12 +1685,13 @@ make_ip6_setting (shvarFile *ifcfg,
 {
 	NMSettingIPConfig *s_ip6 = NULL;
 	const char *v;
-	char *value = NULL;
-	char *str_value;
+	gs_free char *value = NULL;
 	char *route6_path = NULL;
 	gboolean ipv6init, ipv6forwarding, dhcp6 = FALSE;
 	char *method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
-	char *ipv6addr, *ipv6addr_secondaries;
+	const char *ipv6addr, *ipv6addr_secondaries;
+	gs_free char *ipv6addr_to_free = NULL;
+	gs_free char *ipv6addr_secondaries_to_free = NULL;
 	gs_free const char **list = NULL;
 	const char *const *iter;
 	guint32 i;
@@ -1716,13 +1717,16 @@ make_ip6_setting (shvarFile *ifcfg,
 	 * When both are set, the device specified in IPV6_DEFAULTGW takes preference.
 	 */
 	if (network_ifcfg) {
-		char *ipv6_defaultgw, *ipv6_defaultdev;
-		char *default_dev = NULL;
+		const char *ipv6_defaultgw, *ipv6_defaultdev;
+		gs_free char *ipv6_defaultgw_to_free = NULL;
+		gs_free char *ipv6_defaultdev_to_free = NULL;
+		const char *default_dev = NULL;
 
 		/* Get the connection ifcfg device name and the global default route device */
-		value = svGetValueStr_cp (ifcfg, "DEVICE");
-		ipv6_defaultgw = svGetValueStr_cp (network_ifcfg, "IPV6_DEFAULTGW");
-		ipv6_defaultdev = svGetValueStr_cp (network_ifcfg, "IPV6_DEFAULTDEV");
+		nm_clear_g_free (&value);
+		v = svGetValueStr (ifcfg, "DEVICE", &value);
+		ipv6_defaultgw = svGetValueStr (network_ifcfg, "IPV6_DEFAULTGW", &ipv6_defaultgw_to_free);
+		ipv6_defaultdev = svGetValueStr (network_ifcfg, "IPV6_DEFAULTDEV", &ipv6_defaultdev_to_free);
 
 		if (ipv6_defaultgw) {
 			default_dev = strchr (ipv6_defaultgw, '%');
@@ -1735,66 +1739,64 @@ make_ip6_setting (shvarFile *ifcfg,
 		/* If there was a global default route device specified, then only connections
 		 * for that device can be the default connection.
 		 */
-		if (default_dev && value)
-			never_default = !!strcmp (value, default_dev);
-
-		g_free (ipv6_defaultgw);
-		g_free (ipv6_defaultdev);
-		g_free (value);
+		if (default_dev && v)
+			never_default = !!strcmp (v, default_dev);
 	}
 
 	/* Find out method property */
 	/* Is IPV6 enabled? Set method to "ignored", when not enabled */
-	str_value = svGetValueStr_cp (ifcfg, "IPV6INIT");
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IPV6INIT", &value);
 	ipv6init = svGetValueBoolean (ifcfg, "IPV6INIT", FALSE);
-	if (!str_value) {
+	if (!v) {
 		if (network_ifcfg)
 			ipv6init = svGetValueBoolean (network_ifcfg, "IPV6INIT", FALSE);
 	}
-	g_free (str_value);
 
 	if (!ipv6init)
 		method = NM_SETTING_IP6_CONFIG_METHOD_IGNORE;  /* IPv6 is disabled */
 	else {
 		ipv6forwarding = svGetValueBoolean (ifcfg, "IPV6FORWARDING", FALSE);
-		str_value = svGetValueStr_cp (ifcfg, "IPV6_AUTOCONF");
+		nm_clear_g_free (&value);
+		v = svGetValueStr (ifcfg, "IPV6_AUTOCONF", &value);
 		dhcp6 = svGetValueBoolean (ifcfg, "DHCPV6C", FALSE);
 
-		if (!g_strcmp0 (str_value, "shared"))
+		if (!g_strcmp0 (v, "shared"))
 			method = NM_SETTING_IP6_CONFIG_METHOD_SHARED;
-		else if (svParseBoolean (str_value, !ipv6forwarding))
+		else if (svParseBoolean (v, !ipv6forwarding))
 			method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
 		else if (dhcp6)
 			method = NM_SETTING_IP6_CONFIG_METHOD_DHCP;
 		else {
 			/* IPV6_AUTOCONF=no and no IPv6 address -> method 'link-local' */
-			g_free (str_value);
-			str_value = svGetValueStr_cp (ifcfg, "IPV6ADDR");
-			if (!str_value)
-				str_value = svGetValueStr_cp (ifcfg, "IPV6ADDR_SECONDARIES");
+			nm_clear_g_free (&value);
+			v = svGetValueStr (ifcfg, "IPV6ADDR", &value);
+			if (!v) {
+				nm_clear_g_free (&value);
+				v = svGetValueStr (ifcfg, "IPV6ADDR_SECONDARIES", &value);
+			}
 
-			if (!str_value)
+			if (!v)
 				method = NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL;
 		}
-		g_free (str_value);
 	}
 	/* TODO - handle other methods */
 
 	/* Read IPv6 Privacy Extensions configuration */
-	str_value = svGetValueStr_cp (ifcfg, "IPV6_PRIVACY");
-	if (str_value) {
-		ip6_privacy = svParseBoolean (str_value, FALSE);
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IPV6_PRIVACY", &value);
+	if (v) {
+		ip6_privacy = svParseBoolean (v, FALSE);
 		if (!ip6_privacy)
-			ip6_privacy = (g_strcmp0 (str_value, "rfc4941") == 0) ||
-			              (g_strcmp0 (str_value, "rfc3041") == 0);
+			ip6_privacy = (g_strcmp0 (v, "rfc4941") == 0) ||
+			              (g_strcmp0 (v, "rfc3041") == 0);
 	}
 	ip6_privacy_prefer_public_ip = svGetValueBoolean (ifcfg, "IPV6_PRIVACY_PREFER_PUBLIC_IP", FALSE);
-	ip6_privacy_val = str_value ?
+	ip6_privacy_val = v ?
 	                      (ip6_privacy ?
 	                          (ip6_privacy_prefer_public_ip ? NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR : NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR) :
 	                          NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED) :
 	                      NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
-	g_free (str_value);
 
 	/* the route table (policy routing) is ignored if we don't handle routes. */
 	route_table = svGetValueInt64 (ifcfg, "IPV6_ROUTE_TABLE", 10,
@@ -1821,19 +1823,25 @@ make_ip6_setting (shvarFile *ifcfg,
 	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0)
 		return NM_SETTING (s_ip6);
 
-	value = svGetValueStr_cp (ifcfg, "DHCPV6_HOSTNAME");
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "DHCPV6_DUID", &value);
+	if (v)
+		g_object_set (s_ip6, NM_SETTING_IP6_CONFIG_DHCP_DUID, v, NULL);
+
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "DHCPV6_HOSTNAME", &value);
 	/* Use DHCP_HOSTNAME as fallback if it is in FQDN format and ipv6.method is
 	 * auto or dhcp: this is required to support old ifcfg files
 	 */
-	if (!value && (   !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)
+	if (!v && (   !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)
 		       || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP))) {
-		value = svGetValueStr_cp (ifcfg, "DHCP_HOSTNAME");
-		if (value && !strchr (value, '.'))
-			g_clear_pointer (&value, g_free);
+		nm_clear_g_free (&value);
+		v = svGetValueStr (ifcfg, "DHCP_HOSTNAME", &value);
+		if (v && !strchr (v, '.'))
+			v = NULL;
 	}
-	if (value)
-		g_object_set (s_ip6, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, value, NULL);
-	g_free (value);
+	if (v)
+		g_object_set (s_ip6, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, v, NULL);
 
 	g_object_set (s_ip6, NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME,
 		      svGetValueBoolean (ifcfg, "DHCPV6_SEND_HOSTNAME", TRUE), NULL);
@@ -1843,18 +1851,16 @@ make_ip6_setting (shvarFile *ifcfg,
 	 * added to the automatic ones. Note that this is not currently supported by
 	 * the legacy 'network' service (ifup-eth).
 	 */
-	ipv6addr = svGetValueStr_cp (ifcfg, "IPV6ADDR");
-	ipv6addr_secondaries = svGetValueStr_cp (ifcfg, "IPV6ADDR_SECONDARIES");
+	ipv6addr = svGetValueStr (ifcfg, "IPV6ADDR", &ipv6addr_to_free);
+	ipv6addr_secondaries = svGetValueStr (ifcfg, "IPV6ADDR_SECONDARIES", &ipv6addr_secondaries_to_free);
 
+	nm_clear_g_free (&value);
 	value = g_strjoin (ipv6addr && ipv6addr_secondaries ? " " : NULL,
 	                   ipv6addr ?: "",
 	                   ipv6addr_secondaries ?: "",
 	                   NULL);
-	g_free (ipv6addr);
-	g_free (ipv6addr_secondaries);
 
 	list = nm_utils_strsplit_set (value, " ");
-	g_free (value);
 	for (iter = list, i = 0; iter && *iter; iter++, i++) {
 		NMIPAddress *addr = NULL;
 
@@ -1868,25 +1874,26 @@ make_ip6_setting (shvarFile *ifcfg,
 
 	/* Gateway */
 	if (nm_setting_ip_config_get_num_addresses (s_ip6)) {
-		value = svGetValueStr_cp (ifcfg, "IPV6_DEFAULTGW");
-		if (!value) {
+		nm_clear_g_free (&value);
+		v = svGetValueStr (ifcfg, "IPV6_DEFAULTGW", &value);
+		if (!v) {
 			/* If no gateway in the ifcfg, try global /etc/sysconfig/network instead */
-			if (network_ifcfg)
-				value = svGetValueStr_cp (network_ifcfg, "IPV6_DEFAULTGW");
+			if (network_ifcfg) {
+				nm_clear_g_free (&value);
+				v = svGetValueStr (network_ifcfg, "IPV6_DEFAULTGW", &value);
+			}
 		}
-		if (value) {
+		if (v) {
 			char *ptr;
-			if ((ptr = strchr (value, '%')) != NULL)
+			if ((ptr = strchr (v, '%')) != NULL)
 				*ptr = '\0';  /* remove %interface prefix if present */
-			if (!nm_utils_ipaddr_valid (AF_INET6, value)) {
+			if (!nm_utils_ipaddr_valid (AF_INET6, v)) {
 				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				             "Invalid IP6 address '%s'", value);
-				g_free (value);
+				             "Invalid IP6 address '%s'", v);
 				goto error;
 			}
 
-			g_object_set (s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, value, NULL);
-			g_free (value);
+			g_object_set (s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, v, NULL);
 		}
 	}
 
@@ -1900,11 +1907,10 @@ make_ip6_setting (shvarFile *ifcfg,
 	g_object_set (s_ip6, NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, i_val, NULL);
 
 	/* IPv6 tokenized interface identifier */
-	str_value = svGetValueStr_cp (ifcfg, "IPV6_TOKEN");
-	if (str_value) {
-		g_object_set (s_ip6, NM_SETTING_IP6_CONFIG_TOKEN, str_value, NULL);
-		g_free (str_value);
-	}
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IPV6_TOKEN", &value);
+	if (v)
+		g_object_set (s_ip6, NM_SETTING_IP6_CONFIG_TOKEN, v, NULL);
 
 	/* DNS servers
 	 * Pick up just IPv6 addresses (IPv4 addresses are taken by make_ip4_setting())
@@ -1913,24 +1919,22 @@ make_ip6_setting (shvarFile *ifcfg,
 		char tag[256];
 
 		numbered_tag (tag, "DNS", i);
-		value = svGetValueStr_cp (ifcfg, tag);
-		if (!value) {
+		nm_clear_g_free (&value);
+		v = svGetValueStr (ifcfg, tag, &value);
+		if (!v) {
 			/* all done */
 			break;
 		}
 
-		if (nm_utils_ipaddr_valid (AF_INET6, value)) {
-			if (!nm_setting_ip_config_add_dns (s_ip6, value))
+		if (nm_utils_ipaddr_valid (AF_INET6, v)) {
+			if (!nm_setting_ip_config_add_dns (s_ip6, v))
 				PARSE_WARNING ("duplicate DNS server %s", tag);
-		} else if (nm_utils_ipaddr_valid (AF_INET, value)) {
+		} else if (nm_utils_ipaddr_valid (AF_INET, v)) {
 			/* Ignore IPv4 addresses */
 		} else {
-			PARSE_WARNING ("invalid DNS server address %s", value);
-			g_free (value);
+			PARSE_WARNING ("invalid DNS server address %s", v);
 			goto error;
 		}
-
-		g_free (value);
 	}
 
 	if (!routes_read) {
@@ -1961,7 +1965,6 @@ make_ip6_setting (shvarFile *ifcfg,
 	/* DNS options */
 	nm_clear_g_free (&value);
 	parse_dns_options (s_ip6, svGetValue (ifcfg, "IPV6_RES_OPTIONS", &value));
-	g_free (value);
 
 	/* DNS priority */
 	priority = svGetValueInt64 (ifcfg, "IPV6_DNS_PRIORITY", 10, G_MININT32, G_MAXINT32, 0);
@@ -4171,8 +4174,8 @@ parse_ethtool_options (shvarFile *ifcfg, NMSettingWired *s_wired, const char *va
 	              NM_SETTING_WIRED_WAKE_ON_LAN, wol_flags,
 	              NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD, ignore_wol_password ? NULL : wol_password,
 	              NM_SETTING_WIRED_AUTO_NEGOTIATE, autoneg,
-	              NM_SETTING_WIRED_SPEED, autoneg ? 0 : speed,
-	              NM_SETTING_WIRED_DUPLEX, autoneg ? NULL : duplex,
+	              NM_SETTING_WIRED_SPEED, speed,
+	              NM_SETTING_WIRED_DUPLEX, duplex,
 	              NULL);
 }
 
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index bdad7389..fdb2e10a 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -1135,6 +1135,9 @@ write_wired_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	/* Stuff ETHTOOL_OPT with required options */
 	str = NULL;
 	auto_negotiate = nm_setting_wired_get_auto_negotiate (s_wired);
+	speed = nm_setting_wired_get_speed (s_wired);
+	duplex = nm_setting_wired_get_duplex (s_wired);
+
 	/* autoneg off + speed 0 + duplex NULL, means we want NM
 	 * to skip link configuration which is default. So write
 	 * down link config only if we have auto-negotiate true or
@@ -1143,18 +1146,14 @@ write_wired_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	if (auto_negotiate) {
 		str = g_string_sized_new (64);
 		g_string_printf (str, "autoneg on");
-	} else {
-		speed = nm_setting_wired_get_speed (s_wired);
-		duplex = nm_setting_wired_get_duplex (s_wired);
-		if (speed || duplex) {
-			str = g_string_sized_new (64);
-			g_string_printf (str, "autoneg off");
-			if (speed)
-				g_string_append_printf (str, " speed %u", speed);
-			if (duplex)
-				g_string_append_printf (str, " duplex %s", duplex);
-		}
+	} else if (speed || duplex) {
+		str = g_string_sized_new (64);
+		g_string_printf (str, "autoneg off");
 	}
+	if (speed)
+		g_string_append_printf (str, " speed %u", speed);
+	if (duplex)
+		g_string_append_printf (str, " duplex %s", duplex);
 
 	wol = nm_setting_wired_get_wake_on_lan (s_wired);
 	wol_password = nm_setting_wired_get_wake_on_lan_password (s_wired);
@@ -2568,6 +2567,7 @@ write_ip6_setting (NMConnection *connection,
 		svUnsetValue (ifcfg, "IPV6INIT");
 		svUnsetValue (ifcfg, "IPV6_AUTOCONF");
 		svUnsetValue (ifcfg, "DHCPV6C");
+		svUnsetValue (ifcfg, "DHCPv6_DUID");
 		svUnsetValue (ifcfg, "DHCPV6_HOSTNAME");
 		svUnsetValue (ifcfg, "DHCPV6_SEND_HOSTNAME");
 		svUnsetValue (ifcfg, "IPV6_DEFROUTE");
@@ -2608,6 +2608,9 @@ write_ip6_setting (NMConnection *connection,
 		svUnsetValue (ifcfg, "DHCPV6C");
 	}
 
+	svSetValueStr (ifcfg, "DHCPV6_DUID",
+	               nm_setting_ip6_config_get_dhcp_duid (NM_SETTING_IP6_CONFIG (s_ip6)));
+
 	write_ip6_setting_dhcp_hostname (s_ip6, ifcfg);
 
 	/* Write out IP addresses */
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 3511b151..71e6a35a 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -129,7 +129,7 @@ typedef struct {
 	GHashTable *   bss_proxies;
 	char *         current_bss;
 
-	gint32         last_scan; /* timestamp as returned by nm_utils_get_monotonic_timestamp_s() */
+	gint64         last_scan; /* timestamp as returned by nm_utils_get_monotonic_timestamp_ms() */
 
 } NMSupplicantInterfacePrivate;
 
@@ -209,7 +209,7 @@ bss_proxy_properties_changed_cb (GDBusProxy *proxy,
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
 	if (priv->scanning)
-		priv->last_scan = nm_utils_get_monotonic_timestamp_s ();
+		priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
 
 	g_signal_emit (self, signals[BSS_UPDATED], 0,
 	               g_dbus_proxy_get_object_path (proxy),
@@ -344,7 +344,7 @@ set_state (NMSupplicantInterface *self, NMSupplicantInterfaceState new_state)
 
 	if (   priv->state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING
 	    || old_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
-		priv->last_scan = nm_utils_get_monotonic_timestamp_s ();
+		priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
 
 	/* Disconnect reason is no longer relevant when not in the DISCONNECTED state */
 	if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED)
@@ -406,7 +406,7 @@ set_scanning (NMSupplicantInterface *self, gboolean new_scanning)
 
 		/* Cache time of last scan completion */
 		if (priv->scanning == FALSE)
-			priv->last_scan = nm_utils_get_monotonic_timestamp_s ();
+			priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
 
 		_notify (self, PROP_SCANNING);
 	}
@@ -438,8 +438,8 @@ nm_supplicant_interface_get_current_bss (NMSupplicantInterface *self)
 	return priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY ? priv->current_bss : NULL;
 }
 
-gint32
-nm_supplicant_interface_get_last_scan_time (NMSupplicantInterface *self)
+gint64
+nm_supplicant_interface_get_last_scan (NMSupplicantInterface *self)
 {
 	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->last_scan;
 }
@@ -987,7 +987,7 @@ wpas_iface_scan_done (GDBusProxy *proxy,
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
 	/* Cache last scan completed time */
-	priv->last_scan = nm_utils_get_monotonic_timestamp_s ();
+	priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
 	priv->scan_done_success |= success;
 	scan_done_emit_signal (self);
 }
@@ -1002,7 +1002,7 @@ wpas_iface_bss_added (GDBusProxy *proxy,
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
 	if (priv->scanning)
-		priv->last_scan = nm_utils_get_monotonic_timestamp_s ();
+		priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
 
 	bss_add_new (self, path);
 }
diff --git a/src/supplicant/nm-supplicant-interface.h b/src/supplicant/nm-supplicant-interface.h
index f32ad8dd..31272b3c 100644
--- a/src/supplicant/nm-supplicant-interface.h
+++ b/src/supplicant/nm-supplicant-interface.h
@@ -110,7 +110,7 @@ gboolean nm_supplicant_interface_get_scanning (NMSupplicantInterface *self);
 
 const char *nm_supplicant_interface_get_current_bss (NMSupplicantInterface *self);
 
-gint32 nm_supplicant_interface_get_last_scan_time (NMSupplicantInterface *self);
+gint64 nm_supplicant_interface_get_last_scan (NMSupplicantInterface *self);
 
 const char *nm_supplicant_interface_get_ifname (NMSupplicantInterface *self);
 
diff --git a/src/systemd/sd-adapt/copy.h b/src/systemd/sd-adapt/copy.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/systemd/sd-adapt/copy.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/systemd/src/basic/alloc-util.c b/src/systemd/src/basic/alloc-util.c
index 79b823de..ef405098 100644
--- a/src/systemd/src/basic/alloc-util.c
+++ b/src/systemd/src/basic/alloc-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/basic/alloc-util.h b/src/systemd/src/basic/alloc-util.h
index bae6a284..ebe42889 100644
--- a/src/systemd/src/basic/alloc-util.h
+++ b/src/systemd/src/basic/alloc-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <alloca.h>
 #include <stddef.h>
 #include <stdlib.h>
diff --git a/src/systemd/src/basic/async.h b/src/systemd/src/basic/async.h
index 36f33377..31606131 100644
--- a/src/systemd/src/basic/async.h
+++ b/src/systemd/src/basic/async.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2013 Lennart Poettering
-***/
-
 int asynchronous_job(void* (*func)(void *p), void *arg);
 
 int asynchronous_sync(pid_t *ret_pid);
diff --git a/src/systemd/src/basic/escape.c b/src/systemd/src/basic/escape.c
index f51de788..5c82a545 100644
--- a/src/systemd/src/basic/escape.c
+++ b/src/systemd/src/basic/escape.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -17,8 +12,10 @@
 #include "macro.h"
 #include "utf8.h"
 
-size_t cescape_char(char c, char *buf) {
-        char * buf_old = buf;
+int cescape_char(char c, char *buf) {
+        char *buf_old = buf;
+
+        /* Needs space for 4 characters in the buffer */
 
         switch (c) {
 
diff --git a/src/systemd/src/basic/escape.h b/src/systemd/src/basic/escape.h
index 80b0946e..c612a7c0 100644
--- a/src/systemd/src/basic/escape.h
+++ b/src/systemd/src/basic/escape.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <inttypes.h>
 #include <stddef.h>
 #include <stdint.h>
@@ -47,7 +41,7 @@ typedef enum EscapeStyle {
 
 char *cescape(const char *s);
 char *cescape_length(const char *s, size_t n);
-size_t cescape_char(char c, char *buf);
+int cescape_char(char c, char *buf);
 
 int cunescape(const char *s, UnescapeFlags flags, char **ret);
 int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret);
diff --git a/src/systemd/src/basic/ether-addr-util.c b/src/systemd/src/basic/ether-addr-util.c
index 75de9a19..6f946c3e 100644
--- a/src/systemd/src/basic/ether-addr-util.c
+++ b/src/systemd/src/basic/ether-addr-util.c
@@ -1,8 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright 2014 Tom Gundersen
+  Copyright © 2014 Tom Gundersen
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/basic/ether-addr-util.h b/src/systemd/src/basic/ether-addr-util.h
index f7e0de54..f02cefea 100644
--- a/src/systemd/src/basic/ether-addr-util.h
+++ b/src/systemd/src/basic/ether-addr-util.h
@@ -2,9 +2,7 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright 2014 Tom Gundersen
+  Copyright © 2014 Tom Gundersen
 ***/
 
 #include <net/ethernet.h>
diff --git a/src/systemd/src/basic/extract-word.c b/src/systemd/src/basic/extract-word.c
index 2adb8966..404fe615 100644
--- a/src/systemd/src/basic/extract-word.c
+++ b/src/systemd/src/basic/extract-word.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/basic/extract-word.h b/src/systemd/src/basic/extract-word.h
index d2113948..8c63b7c3 100644
--- a/src/systemd/src/basic/extract-word.h
+++ b/src/systemd/src/basic/extract-word.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include "macro.h"
 
 typedef enum ExtractFlags {
diff --git a/src/systemd/src/basic/fd-util.c b/src/systemd/src/basic/fd-util.c
index 6a753195..71babe2f 100644
--- a/src/systemd/src/basic/fd-util.c
+++ b/src/systemd/src/basic/fd-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -14,10 +9,13 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include "alloc-util.h"
+#include "copy.h"
 #include "dirent-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
+#include "io-util.h"
 #include "macro.h"
 #include "memfd-util.h"
 #include "missing.h"
@@ -566,6 +564,201 @@ try_dev_shm_without_o_tmpfile:
 
         return -EOPNOTSUPP;
 }
+
+/* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */
+#define DATA_FD_MEMORY_LIMIT (64U*1024U)
+
+/* If memfd/pipe didn't work out, then let's use a file in /tmp up to a size of 1M. If it's large than that use /var/tmp instead. */
+#define DATA_FD_TMP_LIMIT (1024U*1024U)
+
+int fd_duplicate_data_fd(int fd) {
+
+        _cleanup_close_ int copy_fd = -1, tmp_fd = -1;
+        _cleanup_free_ void *remains = NULL;
+        size_t remains_size = 0;
+        const char *td;
+        struct stat st;
+        int r;
+
+        /* Creates a 'data' fd from the specified source fd, containing all the same data in a read-only fashion, but
+         * independent of it (i.e. the source fd can be closed and unmounted after this call succeeded). Tries to be
+         * somewhat smart about where to place the data. In the best case uses a memfd(). If memfd() are not supported
+         * uses a pipe instead. For larger data will use an unlinked file in /tmp, and for even larger data one in
+         * /var/tmp. */
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        /* For now, let's only accept regular files, sockets, pipes and char devices */
+        if (S_ISDIR(st.st_mode))
+                return -EISDIR;
+        if (S_ISLNK(st.st_mode))
+                return -ELOOP;
+        if (!S_ISREG(st.st_mode) && !S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode) && !S_ISCHR(st.st_mode))
+                return -EBADFD;
+
+        /* If we have reason to believe the data is bounded in size, then let's use memfds or pipes as backing fd. Note
+         * that we use the reported regular file size only as a hint, given that there are plenty special files in
+         * /proc and /sys which report a zero file size but can be read from. */
+
+        if (!S_ISREG(st.st_mode) || st.st_size < DATA_FD_MEMORY_LIMIT) {
+
+                /* Try a memfd first */
+                copy_fd = memfd_new("data-fd");
+                if (copy_fd >= 0) {
+                        off_t f;
+
+                        r = copy_bytes(fd, copy_fd, DATA_FD_MEMORY_LIMIT, 0);
+                        if (r < 0)
+                                return r;
+
+                        f = lseek(copy_fd, 0, SEEK_SET);
+                        if (f != 0)
+                                return -errno;
+
+                        if (r == 0) {
+                                /* Did it fit into the limit? If so, we are done. */
+                                r = memfd_set_sealed(copy_fd);
+                                if (r < 0)
+                                        return r;
+
+                                return TAKE_FD(copy_fd);
+                        }
+
+                        /* Hmm, pity, this didn't fit. Let's fall back to /tmp then, see below */
+
+                } else {
+                        _cleanup_(close_pairp) int pipefds[2] = { -1, -1 };
+                        int isz;
+
+                        /* If memfds aren't available, use a pipe. Set O_NONBLOCK so that we will get EAGAIN rather
+                         * then block indefinitely when we hit the pipe size limit */
+
+                        if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0)
+                                return -errno;
+
+                        isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
+                        if (isz < 0)
+                                return -errno;
+
+                        /* Try to enlarge the pipe size if necessary */
+                        if ((size_t) isz < DATA_FD_MEMORY_LIMIT) {
+
+                                (void) fcntl(pipefds[1], F_SETPIPE_SZ, DATA_FD_MEMORY_LIMIT);
+
+                                isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
+                                if (isz < 0)
+                                        return -errno;
+                        }
+
+                        if ((size_t) isz >= DATA_FD_MEMORY_LIMIT) {
+
+                                r = copy_bytes_full(fd, pipefds[1], DATA_FD_MEMORY_LIMIT, 0, &remains, &remains_size);
+                                if (r < 0 && r != -EAGAIN)
+                                        return r; /* If we get EAGAIN it could be because of the source or because of
+                                                   * the destination fd, we can't know, as sendfile() and friends won't
+                                                   * tell us. Hence, treat this as reason to fall back, just to be
+                                                   * sure. */
+                                if (r == 0) {
+                                        /* Everything fit in, yay! */
+                                        (void) fd_nonblock(pipefds[0], false);
+
+                                        return TAKE_FD(pipefds[0]);
+                                }
+
+                                /* Things didn't fit in. But we read data into the pipe, let's remember that, so that
+                                 * when writing the new file we incorporate this first. */
+                                copy_fd = TAKE_FD(pipefds[0]);
+                        }
+                }
+        }
+
+        /* If we have reason to believe this will fit fine in /tmp, then use that as first fallback. */
+        if ((!S_ISREG(st.st_mode) || st.st_size < DATA_FD_TMP_LIMIT) &&
+            (DATA_FD_MEMORY_LIMIT + remains_size) < DATA_FD_TMP_LIMIT) {
+                off_t f;
+
+                tmp_fd = open_tmpfile_unlinkable(NULL /* NULL as directory means /tmp */, O_RDWR|O_CLOEXEC);
+                if (tmp_fd < 0)
+                        return tmp_fd;
+
+                if (copy_fd >= 0) {
+                        /* If we tried a memfd/pipe first and it ended up being too large, then copy this into the
+                         * temporary file first. */
+
+                        r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, 0);
+                        if (r < 0)
+                                return r;
+
+                        assert(r == 0);
+                }
+
+                if (remains_size > 0) {
+                        /* If there were remaining bytes (i.e. read into memory, but not written out yet) from the
+                         * failed copy operation, let's flush them out next. */
+
+                        r = loop_write(tmp_fd, remains, remains_size, false);
+                        if (r < 0)
+                                return r;
+                }
+
+                r = copy_bytes(fd, tmp_fd, DATA_FD_TMP_LIMIT - DATA_FD_MEMORY_LIMIT - remains_size, COPY_REFLINK);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        goto finish;  /* Yay, it fit in */
+
+                /* It didn't fit in. Let's not forget to use what we already used */
+                f = lseek(tmp_fd, 0, SEEK_SET);
+                if (f != 0)
+                        return -errno;
+
+                safe_close(copy_fd);
+                copy_fd = TAKE_FD(tmp_fd);
+
+                remains = mfree(remains);
+                remains_size = 0;
+        }
+
+        /* As last fallback use /var/tmp */
+        r = var_tmp_dir(&td);
+        if (r < 0)
+                return r;
+
+        tmp_fd = open_tmpfile_unlinkable(td, O_RDWR|O_CLOEXEC);
+        if (tmp_fd < 0)
+                return tmp_fd;
+
+        if (copy_fd >= 0) {
+                /* If we tried a memfd/pipe first, or a file in /tmp, and it ended up being too large, than copy this
+                 * into the temporary file first. */
+                r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, COPY_REFLINK);
+                if (r < 0)
+                        return r;
+
+                assert(r == 0);
+        }
+
+        if (remains_size > 0) {
+                /* Then, copy in any read but not yet written bytes. */
+                r = loop_write(tmp_fd, remains, remains_size, false);
+                if (r < 0)
+                        return r;
+        }
+
+        /* Copy in the rest */
+        r = copy_bytes(fd, tmp_fd, UINT64_MAX, COPY_REFLINK);
+        if (r < 0)
+                return r;
+
+        assert(r == 0);
+
+finish:
+        /* Now convert the O_RDWR file descriptor into an O_RDONLY one (and as side effect seek to the beginning of the
+         * file again */
+
+        return fd_reopen(tmp_fd, O_RDONLY|O_CLOEXEC);
+}
 #endif /* NM_IGNORED */
 
 int fd_move_above_stdio(int fd) {
@@ -739,4 +932,28 @@ int fd_reopen(int fd, int flags) {
 
         return new_fd;
 }
+
+int read_nr_open(void) {
+        _cleanup_free_ char *nr_open = NULL;
+        int r;
+
+        /* Returns the kernel's current fd limit, either by reading it of /proc/sys if that works, or using the
+         * hard-coded default compiled-in value of current kernels (1M) if not. This call will never fail. */
+
+        r = read_one_line_file("/proc/sys/fs/nr_open", &nr_open);
+        if (r < 0)
+                log_debug_errno(r, "Failed to read /proc/sys/fs/nr_open, ignoring: %m");
+        else {
+                int v;
+
+                r = safe_atoi(nr_open, &v);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse /proc/sys/fs/nr_open value '%s', ignoring: %m", nr_open);
+                else
+                        return v;
+        }
+
+        /* If we fail, fallback to the hard-coded kernel limit of 1024 * 1024. */
+        return 1024 * 1024;
+}
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/fd-util.h b/src/systemd/src/basic/fd-util.h
index 89c3f34c..8adc959d 100644
--- a/src/systemd/src/basic/fd-util.h
+++ b/src/systemd/src/basic/fd-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <dirent.h>
 #include <stdbool.h>
 #include <stdio.h>
@@ -81,6 +75,8 @@ enum {
 
 int acquire_data_fd(const void *data, size_t size, unsigned flags);
 
+int fd_duplicate_data_fd(int fd);
+
 /* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */
 #define ERRNO_IS_DISCONNECT(r) \
         IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH)
@@ -106,3 +102,5 @@ static inline int make_null_stdio(void) {
         })
 
 int fd_reopen(int fd, int flags);
+
+int read_nr_open(void);
diff --git a/src/systemd/src/basic/fileio.c b/src/systemd/src/basic/fileio.c
index 4f71384e..2d3aef2c 100644
--- a/src/systemd/src/basic/fileio.c
+++ b/src/systemd/src/basic/fileio.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -209,6 +204,25 @@ fail:
         return 0;
 }
 
+int write_string_filef(
+                const char *fn,
+                WriteStringFileFlags flags,
+                const char *format, ...) {
+
+        _cleanup_free_ char *p = NULL;
+        va_list ap;
+        int r;
+
+        va_start(ap, format);
+        r = vasprintf(&p, format, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return -ENOMEM;
+
+        return write_string_file(fn, p, flags);
+}
+
 int read_one_line_file(const char *fn, char **line) {
         _cleanup_fclose_ FILE *f = NULL;
         int r;
@@ -267,29 +281,35 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
 #endif /* NM_IGNORED */
 
 int read_full_stream(FILE *f, char **contents, size_t *size) {
-        size_t n, l;
         _cleanup_free_ char *buf = NULL;
         struct stat st;
+        size_t n, l;
+        int fd;
 
         assert(f);
         assert(contents);
 
-        if (fstat(fileno(f), &st) < 0)
-                return -errno;
-
         n = LINE_MAX;
 
-        if (S_ISREG(st.st_mode)) {
+        fd = fileno(f);
+        if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see fmemopen(), let's
+                        * optimize our buffering) */
 
-                /* Safety check */
-                if (st.st_size > READ_FULL_BYTES_MAX)
-                        return -E2BIG;
+                if (fstat(fileno(f), &st) < 0)
+                        return -errno;
+
+                if (S_ISREG(st.st_mode)) {
+
+                        /* Safety check */
+                        if (st.st_size > READ_FULL_BYTES_MAX)
+                                return -E2BIG;
 
-                /* Start with the right file size, but be prepared for files from /proc which generally report a file
-                 * size of 0. Note that we increase the size to read here by one, so that the first read attempt
-                 * already makes us notice the EOF. */
-                if (st.st_size > 0)
-                        n = st.st_size + 1;
+                        /* Start with the right file size, but be prepared for files from /proc which generally report a file
+                         * size of 0. Note that we increase the size to read here by one, so that the first read attempt
+                         * already makes us notice the EOF. */
+                        if (st.st_size > 0)
+                                n = st.st_size + 1;
+                }
         }
 
         l = 0;
@@ -680,21 +700,41 @@ static int parse_env_file_push(
         return 0;
 }
 
-int parse_env_file(
+int parse_env_filev(
+                FILE *f,
                 const char *fname,
-                const char *newline, ...) {
+                const char *newline,
+                va_list ap) {
 
-        va_list ap;
         int r, n_pushed = 0;
+        va_list aq;
 
         if (!newline)
                 newline = NEWLINE;
 
+        va_copy(aq, ap);
+        r = parse_env_file_internal(f, fname, newline, parse_env_file_push, &aq, &n_pushed);
+        va_end(aq);
+        if (r < 0)
+                return r;
+
+        return n_pushed;
+}
+
+int parse_env_file(
+                FILE *f,
+                const char *fname,
+                const char *newline,
+                ...) {
+
+        va_list ap;
+        int r;
+
         va_start(ap, newline);
-        r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap, &n_pushed);
+        r = parse_env_filev(f, fname, newline, ap);
         va_end(ap);
 
-        return r < 0 ? r : n_pushed;
+        return r;
 }
 
 #if 0 /* NM_IGNORED */
@@ -1218,7 +1258,7 @@ int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
 
         strcpy(stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn), "XXXXXX");
 
-        *ret = path_kill_slashes(t);
+        *ret = path_simplify(t, false);
         return 0;
 }
 
@@ -1260,7 +1300,7 @@ int tempfn_random(const char *p, const char *extra, char **ret) {
 
         *x = 0;
 
-        *ret = path_kill_slashes(t);
+        *ret = path_simplify(t, false);
         return 0;
 }
 
@@ -1300,7 +1340,7 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) {
 
         *x = 0;
 
-        *ret = path_kill_slashes(t);
+        *ret = path_simplify(t, false);
         return 0;
 }
 
@@ -1528,21 +1568,29 @@ int read_nul_string(FILE *f, char **ret) {
 }
 
 int mkdtemp_malloc(const char *template, char **ret) {
-        char *p;
+        _cleanup_free_ char *p = NULL;
+        int r;
 
-        assert(template);
         assert(ret);
 
-        p = strdup(template);
+        if (template)
+                p = strdup(template);
+        else {
+                const char *tmp;
+
+                r = tmp_dir(&tmp);
+                if (r < 0)
+                        return r;
+
+                p = strjoin(tmp, "/XXXXXX");
+        }
         if (!p)
                 return -ENOMEM;
 
-        if (!mkdtemp(p)) {
-                free(p);
+        if (!mkdtemp(p))
                 return -errno;
-        }
 
-        *ret = p;
+        *ret = TAKE_PTR(p);
         return 0;
 }
 
diff --git a/src/systemd/src/basic/fileio.h b/src/systemd/src/basic/fileio.h
index bed7c8fb..77e6206e 100644
--- a/src/systemd/src/basic/fileio.h
+++ b/src/systemd/src/basic/fileio.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <dirent.h>
 #include <stdbool.h>
 #include <stddef.h>
@@ -17,12 +11,12 @@
 #include "time-util.h"
 
 typedef enum {
-        WRITE_STRING_FILE_CREATE            = 1<<0,
-        WRITE_STRING_FILE_ATOMIC            = 1<<1,
-        WRITE_STRING_FILE_AVOID_NEWLINE     = 1<<2,
-        WRITE_STRING_FILE_VERIFY_ON_FAILURE = 1<<3,
-        WRITE_STRING_FILE_SYNC              = 1<<4,
-        WRITE_STRING_FILE_DISABLE_BUFFER    = 1<<5,
+        WRITE_STRING_FILE_CREATE            = 1 << 0,
+        WRITE_STRING_FILE_ATOMIC            = 1 << 1,
+        WRITE_STRING_FILE_AVOID_NEWLINE     = 1 << 2,
+        WRITE_STRING_FILE_VERIFY_ON_FAILURE = 1 << 3,
+        WRITE_STRING_FILE_SYNC              = 1 << 4,
+        WRITE_STRING_FILE_DISABLE_BUFFER    = 1 << 5,
 
         /* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one
            more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file()
@@ -39,13 +33,16 @@ static inline int write_string_file(const char *fn, const char *line, WriteStrin
         return write_string_file_ts(fn, line, flags, NULL);
 }
 
+int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4);
+
 int read_one_line_file(const char *fn, char **line);
 int read_full_file(const char *fn, char **contents, size_t *size);
 int read_full_stream(FILE *f, char **contents, size_t *size);
 
 int verify_file(const char *fn, const char *blob, bool accept_extra_nl);
 
-int parse_env_file(const char *fname, const char *separator, ...) _sentinel_;
+int parse_env_filev(FILE *f, const char *fname, const char *separator, va_list ap);
+int parse_env_file(FILE *f, const char *fname, const char *separator, ...) _sentinel_;
 int load_env_file(FILE *f, const char *fname, const char *separator, char ***l);
 int load_env_file_pairs(FILE *f, const char *fname, const char *separator, char ***l);
 
diff --git a/src/systemd/src/basic/fs-util.c b/src/systemd/src/basic/fs-util.c
index 049b238e..e3ae9916 100644
--- a/src/systemd/src/basic/fs-util.c
+++ b/src/systemd/src/basic/fs-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -234,6 +229,22 @@ int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
 
         return 0;
 }
+
+int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) {
+        /* Under the assumption that we are running privileged we
+         * first change the access mode and only then hand out
+         * ownership to avoid a window where access is too open. */
+
+        if (mode != MODE_INVALID)
+                if (fchmod(fd, mode) < 0)
+                        return -errno;
+
+        if (uid != UID_INVALID || gid != GID_INVALID)
+                if (fchown(fd, uid, gid) < 0)
+                        return -errno;
+
+        return 0;
+}
 #endif /* NM_IGNORED */
 
 int fchmod_umask(int fd, mode_t m) {
@@ -249,7 +260,7 @@ int fchmod_umask(int fd, mode_t m) {
 
 #if 0 /* NM_IGNORED */
 int fchmod_opath(int fd, mode_t m) {
-        char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+        char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
 
         /* This function operates also on fd that might have been opened with
          * O_PATH. Indeed fchmodat() doesn't have the AT_EMPTY_PATH flag like
@@ -571,6 +582,7 @@ int unlink_or_warn(const char *filename) {
 
         return 0;
 }
+#endif /* NM_IGNORED */
 
 int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
         char path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
@@ -586,6 +598,7 @@ int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
         return r;
 }
 
+#if 0 /* NM_IGNORED */
 static bool safe_transition(const struct stat *a, const struct stat *b) {
         /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to
          * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files
@@ -609,10 +622,10 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
         assert(path);
 
         /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
-        if ((flags & (CHASE_NONEXISTENT|CHASE_OPEN)) == (CHASE_NONEXISTENT|CHASE_OPEN))
+        if (FLAGS_SET(flags, CHASE_NONEXISTENT | CHASE_OPEN))
                 return -EINVAL;
 
-        if ((flags & (CHASE_STEP|CHASE_OPEN)) == (CHASE_STEP|CHASE_OPEN))
+        if (FLAGS_SET(flags, CHASE_STEP | CHASE_OPEN))
                 return -EINVAL;
 
         if (isempty(path))
@@ -1073,6 +1086,15 @@ int access_fd(int fd, int mode) {
         return r;
 }
 
+void unlink_tempfilep(char (*p)[]) {
+        /* If the file is created with mkstemp(), it will (almost always)
+         * change the suffix. Treat this as a sign that the file was
+         * successfully created. We ignore both the rare case where the
+         * original suffix is used and unlink failures. */
+        if (!endswith(*p, ".XXXXXX"))
+                (void) unlink_noerrno(*p);
+}
+
 int unlinkat_deallocate(int fd, const char *name, int flags) {
         _cleanup_close_ int truncate_fd = -1;
         struct stat st;
@@ -1153,9 +1175,9 @@ int fsync_directory_of_file(int fd) {
 
         r = fd_get_path(fd, &path);
         if (r < 0) {
-                log_debug("Failed to query /proc/self/fd/%d%s: %m",
-                          fd,
-                          r == -EOPNOTSUPP ? ", ignoring" : "");
+                log_debug_errno(r, "Failed to query /proc/self/fd/%d%s: %m",
+                                fd,
+                                r == -EOPNOTSUPP ? ", ignoring" : "");
 
                 if (r == -EOPNOTSUPP)
                         /* If /proc is not available, we're most likely running in some
diff --git a/src/systemd/src/basic/fs-util.h b/src/systemd/src/basic/fs-util.h
index 6157fe81..28566773 100644
--- a/src/systemd/src/basic/fs-util.h
+++ b/src/systemd/src/basic/fs-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <dirent.h>
 #include <fcntl.h>
 #include <limits.h>
@@ -31,6 +25,7 @@ int readlink_value(const char *p, char **ret);
 int readlink_and_make_absolute(const char *p, char **r);
 
 int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid);
+int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid);
 
 int fchmod_umask(int fd, mode_t mode);
 int fchmod_opath(int fd, mode_t m);
@@ -70,13 +65,13 @@ union inotify_event_buffer {
 int inotify_add_watch_fd(int fd, int what, uint32_t mask);
 
 enum {
-        CHASE_PREFIX_ROOT = 1U << 0,   /* If set, the specified path will be prefixed by the specified root before beginning the iteration */
-        CHASE_NONEXISTENT = 1U << 1,   /* If set, it's OK if the path doesn't actually exist. */
-        CHASE_NO_AUTOFS   = 1U << 2,   /* If set, return -EREMOTE if autofs mount point found */
-        CHASE_SAFE        = 1U << 3,   /* If set, return EPERM if we ever traverse from unprivileged to privileged files or directories */
-        CHASE_OPEN        = 1U << 4,   /* If set, return an O_PATH object to the final component */
-        CHASE_TRAIL_SLASH = 1U << 5,   /* If set, any trailing slash will be preserved */
-        CHASE_STEP        = 1U << 6,   /* If set, just execute a single step of the normalization */
+        CHASE_PREFIX_ROOT = 1 << 0, /* If set, the specified path will be prefixed by the specified root before beginning the iteration */
+        CHASE_NONEXISTENT = 1 << 1, /* If set, it's OK if the path doesn't actually exist. */
+        CHASE_NO_AUTOFS   = 1 << 2, /* If set, return -EREMOTE if autofs mount point found */
+        CHASE_SAFE        = 1 << 3, /* If set, return EPERM if we ever traverse from unprivileged to privileged files or directories */
+        CHASE_OPEN        = 1 << 4, /* If set, return an O_PATH object to the final component */
+        CHASE_TRAIL_SLASH = 1 << 5, /* If set, any trailing slash will be preserved */
+        CHASE_STEP        = 1 << 6, /* If set, just execute a single step of the normalization */
 };
 
 /* How many iterations to execute before returning -ELOOP */
@@ -104,6 +99,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free);
 
 int access_fd(int fd, int mode);
 
+void unlink_tempfilep(char (*p)[]);
 int unlinkat_deallocate(int fd, const char *name, int flags);
 
 int fsync_directory_of_file(int fd);
diff --git a/src/systemd/src/basic/hash-funcs.c b/src/systemd/src/basic/hash-funcs.c
index cb763f56..7ed35cb1 100644
--- a/src/systemd/src/basic/hash-funcs.c
+++ b/src/systemd/src/basic/hash-funcs.c
@@ -1,9 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-  Copyright 2014 Michal Schmidt
+  Copyright © 2014 Michal Schmidt
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/basic/hash-funcs.h b/src/systemd/src/basic/hash-funcs.h
index 0c963af8..3715c56e 100644
--- a/src/systemd/src/basic/hash-funcs.h
+++ b/src/systemd/src/basic/hash-funcs.h
@@ -2,10 +2,7 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-  Copyright 2014 Michal Schmidt
+  Copyright © 2014 Michal Schmidt
 ***/
 
 #include "macro.h"
diff --git a/src/systemd/src/basic/hashmap.c b/src/systemd/src/basic/hashmap.c
index b8a8e37b..c91f8bd2 100644
--- a/src/systemd/src/basic/hashmap.c
+++ b/src/systemd/src/basic/hashmap.c
@@ -1,9 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-  Copyright 2014 Michal Schmidt
+  Copyright © 2014 Michal Schmidt
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/basic/hashmap.h b/src/systemd/src/basic/hashmap.h
index 701eecbd..d1d1b9c8 100644
--- a/src/systemd/src/basic/hashmap.h
+++ b/src/systemd/src/basic/hashmap.h
@@ -2,10 +2,7 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-  Copyright 2014 Michal Schmidt
+  Copyright © 2014 Michal Schmidt
 ***/
 
 #include <limits.h>
diff --git a/src/systemd/src/basic/hexdecoct.c b/src/systemd/src/basic/hexdecoct.c
index 1fe410f5..09f2a9e7 100644
--- a/src/systemd/src/basic/hexdecoct.c
+++ b/src/systemd/src/basic/hexdecoct.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/basic/hexdecoct.h b/src/systemd/src/basic/hexdecoct.h
index c2f11013..9477d16e 100644
--- a/src/systemd/src/basic/hexdecoct.h
+++ b/src/systemd/src/basic/hexdecoct.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdio.h>
diff --git a/src/systemd/src/basic/hostname-util.c b/src/systemd/src/basic/hostname-util.c
index 0feeaa5d..85708394 100644
--- a/src/systemd/src/basic/hostname-util.c
+++ b/src/systemd/src/basic/hostname-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2015 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/basic/hostname-util.h b/src/systemd/src/basic/hostname-util.h
index 8e29e9e1..74948172 100644
--- a/src/systemd/src/basic/hostname-util.h
+++ b/src/systemd/src/basic/hostname-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010-2015 Lennart Poettering
-***/
-
 #include <stdbool.h>
 #include <stdio.h>
 
diff --git a/src/systemd/src/basic/in-addr-util.c b/src/systemd/src/basic/in-addr-util.c
index 65748615..d945567a 100644
--- a/src/systemd/src/basic/in-addr-util.c
+++ b/src/systemd/src/basic/in-addr-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2014 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/basic/in-addr-util.h b/src/systemd/src/basic/in-addr-util.h
index acd567c9..956c00a8 100644
--- a/src/systemd/src/basic/in-addr-util.h
+++ b/src/systemd/src/basic/in-addr-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2014 Lennart Poettering
-***/
-
 #include <netinet/in.h>
 #include <stddef.h>
 #include <sys/socket.h>
diff --git a/src/systemd/src/basic/io-util.c b/src/systemd/src/basic/io-util.c
index 1bc5c345..6f46c944 100644
--- a/src/systemd/src/basic/io-util.c
+++ b/src/systemd/src/basic/io-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/basic/io-util.h b/src/systemd/src/basic/io-util.h
index e4717b6f..ed189b58 100644
--- a/src/systemd/src/basic/io-util.h
+++ b/src/systemd/src/basic/io-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
diff --git a/src/systemd/src/basic/list.h b/src/systemd/src/basic/list.h
index 3ec7e73f..643e0bea 100644
--- a/src/systemd/src/basic/list.h
+++ b/src/systemd/src/basic/list.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 /* The head of the linked list. Use this in the structure that shall
  * contain the head of the linked list */
 #define LIST_HEAD(t,name)                                               \
diff --git a/src/systemd/src/basic/log.h b/src/systemd/src/basic/log.h
index e7d7c8b1..28edd572 100644
--- a/src/systemd/src/basic/log.h
+++ b/src/systemd/src/basic/log.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <stdarg.h>
 #include <stdbool.h>
 #include <stdlib.h>
@@ -251,7 +245,7 @@ int log_emergency_level(void);
 /* Structured logging */
 #define log_struct_errno(level, error, ...) \
         log_struct_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
-                            error, __FILE__, __LINE__, __func__, __VA_ARGS__)
+                            error, __FILE__, __LINE__, __func__, __VA_ARGS__, NULL)
 #define log_struct(level, ...) log_struct_errno(level, 0, __VA_ARGS__)
 
 #define log_struct_iovec_errno(level, error, iovec, n_iovec)            \
@@ -290,6 +284,8 @@ void log_set_open_when_needed(bool b);
  * stderr, the console or kmsg */
 void log_set_prohibit_ipc(bool b);
 
+int log_dup_console(void);
+
 int log_syntax_internal(
                 const char *unit,
                 int level,
diff --git a/src/systemd/src/basic/macro.h b/src/systemd/src/basic/macro.h
index c89fb84e..cdb54f82 100644
--- a/src/systemd/src/basic/macro.h
+++ b/src/systemd/src/basic/macro.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <inttypes.h>
 #include <stdbool.h>
 #include <sys/param.h>
@@ -366,6 +360,8 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
 
 #define SET_FLAG(v, flag, b) \
         (v) = (b) ? ((v) | (flag)) : ((v) & ~(flag))
+#define FLAGS_SET(v, flags) \
+        (((v) & (flags)) == (flags))
 
 #define CASE_F(X) case X:
 #define CASE_F_1(CASE, X) CASE_F(X)
diff --git a/src/systemd/src/basic/mempool.c b/src/systemd/src/basic/mempool.c
index 07096766..a0154263 100644
--- a/src/systemd/src/basic/mempool.c
+++ b/src/systemd/src/basic/mempool.c
@@ -1,9 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright 2010-2014 Lennart Poettering
-  Copyright 2014 Michal Schmidt
+  Copyright © 2014 Michal Schmidt
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/basic/mempool.h b/src/systemd/src/basic/mempool.h
index 68249cd8..291415de 100644
--- a/src/systemd/src/basic/mempool.h
+++ b/src/systemd/src/basic/mempool.h
@@ -2,10 +2,7 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright 2011-2014 Lennart Poettering
-  Copyright 2014 Michal Schmidt
+  Copyright © 2014 Michal Schmidt
 ***/
 
 #include <stddef.h>
@@ -29,7 +26,6 @@ static struct mempool pool_name = { \
         .at_least = alloc_at_least, \
 }
 
-
 #if VALGRIND
 void mempool_drop(struct mempool *mp);
 #endif
diff --git a/src/systemd/src/basic/parse-util.c b/src/systemd/src/basic/parse-util.c
index ae43a041..7d6bcb5a 100644
--- a/src/systemd/src/basic/parse-util.c
+++ b/src/systemd/src/basic/parse-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -637,6 +632,58 @@ int parse_percent(const char *p) {
         return v;
 }
 
+int parse_permille_unbounded(const char *p) {
+        const char *pc, *pm, *dot, *n;
+        int r, q, v;
+
+        pm = endswith(p, "‰");
+        if (pm) {
+                n = strndupa(p, pm - p);
+                r = safe_atoi(n, &v);
+                if (r < 0)
+                        return r;
+        } else {
+                pc = endswith(p, "%");
+                if (!pc)
+                        return -EINVAL;
+
+                dot = memchr(p, '.', pc - p);
+                if (dot) {
+                        if (dot + 2 != pc)
+                                return -EINVAL;
+                        if (dot[1] < '0' || dot[1] > '9')
+                                return -EINVAL;
+                        q = dot[1] - '0';
+                        n = strndupa(p, dot - p);
+                } else {
+                        q = 0;
+                        n = strndupa(p, pc - p);
+                }
+                r = safe_atoi(n, &v);
+                if (r < 0)
+                        return r;
+                if (v > (INT_MAX - q) / 10)
+                        return -ERANGE;
+
+                v = v * 10 + q;
+        }
+
+        if (v < 0)
+                return -ERANGE;
+
+        return v;
+}
+
+int parse_permille(const char *p) {
+        int v;
+
+        v = parse_permille_unbounded(p);
+        if (v > 1000)
+                return -ERANGE;
+
+        return v;
+}
+
 int parse_nice(const char *p, int *ret) {
         int n, r;
 
diff --git a/src/systemd/src/basic/parse-util.h b/src/systemd/src/basic/parse-util.h
index 2b75b938..f3267f4c 100644
--- a/src/systemd/src/basic/parse-util.h
+++ b/src/systemd/src/basic/parse-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <inttypes.h>
 #include <limits.h>
 #include <stddef.h>
@@ -115,6 +109,9 @@ int parse_fractional_part_u(const char **s, size_t digits, unsigned *res);
 int parse_percent_unbounded(const char *p);
 int parse_percent(const char *p);
 
+int parse_permille_unbounded(const char *p);
+int parse_permille(const char *p);
+
 int parse_nice(const char *p, int *ret);
 
 int parse_ip_port(const char *s, uint16_t *ret);
diff --git a/src/systemd/src/basic/path-util.c b/src/systemd/src/basic/path-util.c
index 567fc61c..69bdb894 100644
--- a/src/systemd/src/basic/path-util.c
+++ b/src/systemd/src/basic/path-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010-2012 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -34,12 +29,13 @@
 #include "string-util.h"
 #include "strv.h"
 #include "time-util.h"
+#include "utf8.h"
 
-#if 0 /* NM_IGNORED */
 bool path_is_absolute(const char *p) {
         return p[0] == '/';
 }
 
+#if 0 /* NM_IGNORED */
 bool is_path(const char *p) {
         return !!strchr(p, '/');
 }
@@ -130,8 +126,8 @@ int path_make_absolute_cwd(const char *p, char **ret) {
 }
 
 int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
-        char *r, *p;
-        unsigned n_parents;
+        char *f, *t, *r, *p;
+        unsigned n_parents = 0;
 
         assert(from_dir);
         assert(to_path);
@@ -139,85 +135,81 @@ int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
 
         /* Strips the common part, and adds ".." elements as necessary. */
 
-        if (!path_is_absolute(from_dir))
+        if (!path_is_absolute(from_dir) || !path_is_absolute(to_path))
                 return -EINVAL;
 
-        if (!path_is_absolute(to_path))
-                return -EINVAL;
+        f = strdupa(from_dir);
+        t = strdupa(to_path);
+
+        path_simplify(f, true);
+        path_simplify(t, true);
 
         /* Skip the common part. */
         for (;;) {
                 size_t a, b;
 
-                from_dir += strspn(from_dir, "/");
-                to_path += strspn(to_path, "/");
+                f += *f == '/';
+                t += *t == '/';
 
-                if (!*from_dir) {
-                        if (!*to_path)
+                if (!*f) {
+                        if (!*t)
                                 /* from_dir equals to_path. */
                                 r = strdup(".");
                         else
                                 /* from_dir is a parent directory of to_path. */
-                                r = strdup(to_path);
+                                r = strdup(t);
                         if (!r)
                                 return -ENOMEM;
 
-                        path_kill_slashes(r);
-
                         *_r = r;
                         return 0;
                 }
 
-                if (!*to_path)
+                if (!*t)
                         break;
 
-                a = strcspn(from_dir, "/");
-                b = strcspn(to_path, "/");
+                a = strcspn(f, "/");
+                b = strcspn(t, "/");
 
-                if (a != b)
+                if (a != b || memcmp(f, t, a) != 0)
                         break;
 
-                if (memcmp(from_dir, to_path, a) != 0)
-                        break;
-
-                from_dir += a;
-                to_path += b;
+                f += a;
+                t += b;
         }
 
         /* If we're here, then "from_dir" has one or more elements that need to
          * be replaced with "..". */
 
         /* Count the number of necessary ".." elements. */
-        for (n_parents = 0;;) {
+        for (; *f;) {
                 size_t w;
 
-                from_dir += strspn(from_dir, "/");
-
-                if (!*from_dir)
-                        break;
-
-                w = strcspn(from_dir, "/");
+                w = strcspn(f, "/");
 
                 /* If this includes ".." we can't do a simple series of "..", refuse */
-                if (w == 2 && from_dir[0] == '.' && from_dir[1] == '.')
+                if (w == 2 && f[0] == '.' && f[1] == '.')
                         return -EINVAL;
 
-                /* Count number of elements, except if they are "." */
-                if (w != 1 || from_dir[0] != '.')
-                        n_parents++;
+                /* Count number of elements */
+                n_parents++;
 
-                from_dir += w;
+                f += w;
+                f += *f == '/';
         }
 
-        r = new(char, n_parents * 3 + strlen(to_path) + 1);
+        r = new(char, n_parents * 3 + strlen(t) + 1);
         if (!r)
                 return -ENOMEM;
 
         for (p = r; n_parents > 0; n_parents--)
                 p = mempcpy(p, "../", 3);
 
-        strcpy(p, to_path);
-        path_kill_slashes(r);
+        if (*t)
+                strcpy(p, t);
+        else
+                /* Remove trailing slash */
+                *(--p) = 0;
 
         *_r = r;
         return 0;
@@ -238,7 +230,7 @@ int path_strv_make_absolute_cwd(char **l) {
                 if (r < 0)
                         return r;
 
-                path_kill_slashes(t);
+                path_simplify(t, false);
                 free_and_replace(*s, t);
         }
 
@@ -339,17 +331,30 @@ char **path_strv_resolve_uniq(char **l, const char *root) {
 }
 #endif /* NM_IGNORED */
 
-char *path_kill_slashes(char *path) {
+char *path_simplify(char *path, bool kill_dots) {
         char *f, *t;
-        bool slash = false;
+        bool slash = false, ignore_slash = false, absolute;
+
+        assert(path);
 
-        /* Removes redundant inner and trailing slashes. Modifies the
-         * passed string in-place.
+        /* Removes redundant inner and trailing slashes. Also removes unnecessary dots
+         * if kill_dots is true. Modifies the passed string in-place.
          *
-         * ///foo///bar/ becomes /foo/bar
+         * ///foo//./bar/.   becomes /foo/./bar/.  (if kill_dots is false)
+         * ///foo//./bar/.   becomes /foo/bar      (if kill_dots is true)
+         * .//./foo//./bar/. becomes ./foo/bar     (if kill_dots is false)
+         * .//./foo//./bar/. becomes foo/bar       (if kill_dots is true)
          */
 
-        for (f = path, t = path; *f; f++) {
+        absolute = path_is_absolute(path);
+
+        f = path;
+        if (kill_dots && *f == '.' && IN_SET(f[1], 0, '/')) {
+                ignore_slash = true;
+                f++;
+        }
+
+        for (t = path; *f; f++) {
 
                 if (*f == '/') {
                         slash = true;
@@ -357,17 +362,21 @@ char *path_kill_slashes(char *path) {
                 }
 
                 if (slash) {
+                        if (kill_dots && *f == '.' && IN_SET(f[1], 0, '/'))
+                                continue;
+
                         slash = false;
-                        *(t++) = '/';
+                        if (ignore_slash)
+                                ignore_slash = false;
+                        else
+                                *(t++) = '/';
                 }
 
                 *(t++) = *f;
         }
 
-        /* Special rule, if we are talking of the root directory, a
-        trailing slash is good */
-
-        if (t == path && slash)
+        /* Special rule, if we are talking of the root directory, a trailing slash is good */
+        if (absolute && t == path)
                 *(t++) = '/';
 
         *t = 0;
@@ -535,7 +544,7 @@ int find_binary(const char *name, char **ret) {
                         /* Found it! */
 
                         if (ret) {
-                                *ret = path_kill_slashes(j);
+                                *ret = path_simplify(j, false);
                                 j = NULL;
                         }
 
@@ -689,12 +698,11 @@ int parse_path_argument_and_warn(const char *path, bool suppress_root, char **ar
         if (r < 0)
                 return log_error_errno(r, "Failed to parse path \"%s\" and make it absolute: %m", path);
 
-        path_kill_slashes(p);
+        path_simplify(p, false);
         if (suppress_root && empty_or_root(p))
                 p = mfree(p);
 
-        free(*arg);
-        *arg = p;
+        free_and_replace(*arg, p);
 
         return 0;
 }
@@ -778,7 +786,6 @@ bool filename_is_valid(const char *p) {
         return true;
 }
 
-#if 0 /* NM_IGNORED */
 bool path_is_normalized(const char *p) {
 
         if (isempty(p))
@@ -802,6 +809,7 @@ bool path_is_normalized(const char *p) {
         return true;
 }
 
+#if 0 /* NM_IGNORED */
 char *file_in_same_dir(const char *path, const char *filename) {
         char *e, *ret;
         size_t k;
@@ -880,17 +888,36 @@ bool hidden_or_backup_file(const char *filename) {
 
 bool is_device_path(const char *path) {
 
-        /* Returns true on paths that refer to a device, either in
-         * sysfs or in /dev */
+        /* Returns true on paths that likely refer to a device, either by path in sysfs or to something in /dev */
+
+        return PATH_STARTSWITH_SET(path, "/dev/", "/sys/");
+}
+
+bool valid_device_node_path(const char *path) {
+
+        /* Some superficial checks whether the specified path is a valid device node path, all without looking at the
+         * actual device node. */
+
+        if (!PATH_STARTSWITH_SET(path, "/dev/", "/run/systemd/inaccessible/"))
+                return false;
+
+        if (endswith(path, "/")) /* can't be a device node if it ends in a slash */
+                return false;
 
-        return path_startswith(path, "/dev/") ||
-               path_startswith(path, "/sys/");
+        return path_is_normalized(path);
 }
 
-bool is_deviceallow_pattern(const char *path) {
-        return path_startswith(path, "/dev/") ||
-               startswith(path, "block-") ||
-               startswith(path, "char-");
+bool valid_device_allow_pattern(const char *path) {
+        assert(path);
+
+        /* Like valid_device_node_path(), but also allows full-subsystem expressions, like DeviceAllow= and DeviceDeny=
+         * accept it */
+
+        if (startswith(path, "block-") ||
+            startswith(path, "char-"))
+                return true;
+
+        return valid_device_node_path(path);
 }
 
 int systemd_installation_has_version(const char *root, unsigned minimal_version) {
@@ -926,7 +953,7 @@ int systemd_installation_has_version(const char *root, unsigned minimal_version)
                 if (r < 0)
                         return r;
 
-                assert_se((c = endswith(path, "*.so")));
+                assert_se(c = endswith(path, "*.so"));
                 *c = '\0'; /* truncate the glob part */
 
                 STRV_FOREACH(name, names) {
@@ -975,6 +1002,7 @@ bool dot_or_dot_dot(const char *path) {
         return path[2] == 0;
 }
 
+#if 0 /* NM_IGNORED */
 bool empty_or_root(const char *root) {
 
         /* For operations relative to some root directory, returns true if the specified root directory is redundant,
@@ -985,3 +1013,51 @@ bool empty_or_root(const char *root) {
 
         return root[strspn(root, "/")] == 0;
 }
+
+int path_simplify_and_warn(
+                char *path,
+                unsigned flag,
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *lvalue) {
+
+        bool absolute, fatal = flag & PATH_CHECK_FATAL;
+
+        assert(!FLAGS_SET(flag, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE));
+
+        if (!utf8_is_valid(path)) {
+                log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, path);
+                return -EINVAL;
+        }
+
+        if (flag & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) {
+                absolute = path_is_absolute(path);
+
+                if (!absolute && (flag & PATH_CHECK_ABSOLUTE)) {
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "%s= path is not absolute%s: %s",
+                                   lvalue, fatal ? "" : ", ignoring", path);
+                        return -EINVAL;
+                }
+
+                if (absolute && (flag & PATH_CHECK_RELATIVE)) {
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "%s= path is absolute%s: %s",
+                                   lvalue, fatal ? "" : ", ignoring", path);
+                        return -EINVAL;
+                }
+        }
+
+        path_simplify(path, true);
+
+        if (!path_is_normalized(path)) {
+                log_syntax(unit, LOG_ERR, filename, line, 0,
+                           "%s= path is not normalized%s: %s",
+                           lvalue, fatal ? "" : ", ignoring", path);
+                return -EINVAL;
+        }
+
+        return 0;
+}
+#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/path-util.h b/src/systemd/src/basic/path-util.h
index 79db819e..519060cf 100644
--- a/src/systemd/src/basic/path-util.h
+++ b/src/systemd/src/basic/path-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010-2012 Lennart Poettering
-***/
-
 #include <alloca.h>
 #include <stdbool.h>
 #include <stddef.h>
@@ -52,12 +46,12 @@ char* path_make_absolute(const char *p, const char *prefix);
 int safe_getcwd(char **ret);
 int path_make_absolute_cwd(const char *p, char **ret);
 int path_make_relative(const char *from_dir, const char *to_path, char **_r);
-char* path_kill_slashes(char *path);
 char* path_startswith(const char *path, const char *prefix) _pure_;
 int path_compare(const char *a, const char *b) _pure_;
 bool path_equal(const char *a, const char *b) _pure_;
 bool path_equal_or_files_same(const char *a, const char *b, int flags);
 char* path_join(const char *root, const char *path, const char *rest);
+char* path_simplify(char *path, bool kill_dots);
 
 static inline bool path_equal_ptr(const char *a, const char *b) {
         return !!a == !!b && (!a || path_equal(a, b));
@@ -103,11 +97,11 @@ int mkfs_exists(const char *fstype);
  * the tree, to root. Also returns "" (and not "/"!) for the root
  * directory. Excludes the specified directory itself */
 #define PATH_FOREACH_PREFIX(prefix, path) \
-        for (char *_slash = ({ path_kill_slashes(strcpy(prefix, path)); streq(prefix, "/") ? NULL : strrchr(prefix, '/'); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/'))
+        for (char *_slash = ({ path_simplify(strcpy(prefix, path), false); streq(prefix, "/") ? NULL : strrchr(prefix, '/'); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/'))
 
 /* Same as PATH_FOREACH_PREFIX but also includes the specified path itself */
 #define PATH_FOREACH_PREFIX_MORE(prefix, path) \
-        for (char *_slash = ({ path_kill_slashes(strcpy(prefix, path)); if (streq(prefix, "/")) prefix[0] = 0; strrchr(prefix, 0); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/'))
+        for (char *_slash = ({ path_simplify(strcpy(prefix, path), false); if (streq(prefix, "/")) prefix[0] = 0; strrchr(prefix, 0); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/'))
 
 char *prefix_root(const char *root, const char *path);
 
@@ -149,7 +143,9 @@ char *file_in_same_dir(const char *path, const char *filename);
 bool hidden_or_backup_file(const char *filename) _pure_;
 
 bool is_device_path(const char *path);
-bool is_deviceallow_pattern(const char *path);
+
+bool valid_device_node_path(const char *path);
+bool valid_device_allow_pattern(const char *path);
 
 int systemd_installation_has_version(const char *root, unsigned minimal_version);
 
@@ -169,3 +165,11 @@ bool empty_or_root(const char *root);
 static inline const char *empty_to_root(const char *path) {
         return isempty(path) ? "/" : path;
 }
+
+enum {
+        PATH_CHECK_FATAL    = 1 << 0,  /* If not set, then error message is appended with 'ignoring'. */
+        PATH_CHECK_ABSOLUTE = 1 << 1,
+        PATH_CHECK_RELATIVE = 1 << 2,
+};
+
+int path_simplify_and_warn(char *path, unsigned flag, const char *unit, const char *filename, unsigned line, const char *lvalue);
diff --git a/src/systemd/src/basic/prioq.c b/src/systemd/src/basic/prioq.c
index 805a6174..79900df3 100644
--- a/src/systemd/src/basic/prioq.c
+++ b/src/systemd/src/basic/prioq.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2013 Lennart Poettering
-***/
 
 /*
  * Priority Queue
diff --git a/src/systemd/src/basic/prioq.h b/src/systemd/src/basic/prioq.h
index e348c646..e0361752 100644
--- a/src/systemd/src/basic/prioq.h
+++ b/src/systemd/src/basic/prioq.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2013 Lennart Poettering
-***/
-
 #include <stdbool.h>
 
 #include "hashmap.h"
diff --git a/src/systemd/src/basic/process-util.c b/src/systemd/src/basic/process-util.c
index 4af99957..10aa0e56 100644
--- a/src/systemd/src/basic/process-util.c
+++ b/src/systemd/src/basic/process-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -19,6 +14,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/mman.h>
+#include <sys/mount.h>
 #include <sys/personality.h>
 #include <sys/prctl.h>
 #include <sys/types.h>
@@ -80,20 +76,31 @@ int get_process_state(pid_t pid) {
         return (unsigned char) state;
 }
 
-int get_process_comm(pid_t pid, char **name) {
+int get_process_comm(pid_t pid, char **ret) {
+        _cleanup_free_ char *escaped = NULL, *comm = NULL;
         const char *p;
         int r;
 
-        assert(name);
+        assert(ret);
         assert(pid >= 0);
 
+        escaped = new(char, TASK_COMM_LEN);
+        if (!escaped)
+                return -ENOMEM;
+
         p = procfs_file_alloca(pid, "comm");
 
-        r = read_one_line_file(p, name);
+        r = read_one_line_file(p, &comm);
         if (r == -ENOENT)
                 return -ESRCH;
+        if (r < 0)
+                return r;
 
-        return r;
+        /* Escape unprintable characters, just in case, but don't grow the string beyond the underlying size */
+        cellescape(escaped, TASK_COMM_LEN, comm);
+
+        *ret = TAKE_PTR(escaped);
+        return 0;
 }
 
 int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) {
@@ -251,15 +258,10 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
                                 memcpy(ans, "[...]", max_length-1);
                                 ans[max_length-1] = 0;
                         } else {
-                                char *e;
-
                                 t[max_length - 6] = 0;
 
                                 /* Chop off final spaces */
-                                e = strchr(t, 0);
-                                while (e > t && isspace(e[-1]))
-                                        e--;
-                                *e = 0;
+                                delete_trailing_chars(t, WHITESPACE);
 
                                 ans = strjoin("[", t, "...]");
                         }
@@ -300,7 +302,7 @@ int rename_process(const char name[]) {
          * can use PR_SET_NAME, which sets the thread name for the calling thread. */
         if (prctl(PR_SET_NAME, name) < 0)
                 log_debug_errno(errno, "PR_SET_NAME failed: %m");
-        if (l > 15) /* Linux process names can be 15 chars at max */
+        if (l >= TASK_COMM_LEN) /* Linux process names can be 15 chars at max */
                 truncated = true;
 
         /* Second step, change glibc's ID of the process name. */
@@ -744,14 +746,17 @@ int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags) {
 
 /*
  * Return values:
- * < 0 : wait_for_terminate_with_timeout() failed to get the state of the
- *       process, the process timed out, the process was terminated by a
- *       signal, or failed for an unknown reason.
+ *
+ * < 0 : wait_for_terminate_with_timeout() failed to get the state of the process, the process timed out, the process
+ *       was terminated by a signal, or failed for an unknown reason.
+ *
  * >=0 : The process terminated normally with no failures.
  *
- * Success is indicated by a return value of zero, a timeout is indicated
- * by ETIMEDOUT, and all other child failure states are indicated by error
- * is indicated by a non-zero value.
+ * Success is indicated by a return value of zero, a timeout is indicated by ETIMEDOUT, and all other child failure
+ * states are indicated by error is indicated by a non-zero value.
+ *
+ * This call assumes SIGCHLD has been blocked already, in particular before the child to wait for has been forked off
+ * to remain entirely race-free.
  */
 int wait_for_terminate_with_timeout(pid_t pid, usec_t timeout) {
         sigset_t mask;
@@ -1353,6 +1358,16 @@ int safe_fork_full(
                 }
         }
 
+        if (FLAGS_SET(flags, FORK_NEW_MOUNTNS | FORK_MOUNTNS_SLAVE)) {
+
+                /* Optionally, make sure we never propagate mounts to the host. */
+
+                if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) {
+                        log_full_errno(prio, errno, "Failed to remount root directory as MS_SLAVE: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
         if (flags & FORK_CLOSE_ALL_FDS) {
                 /* Close the logs here in case it got reopened above, as close_all_fds() would close them for us */
                 log_close();
diff --git a/src/systemd/src/basic/process-util.h b/src/systemd/src/basic/process-util.h
index e3bf36bf..e11164bd 100644
--- a/src/systemd/src/basic/process-util.h
+++ b/src/systemd/src/basic/process-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <alloca.h>
 #include <errno.h>
 #include <sched.h>
@@ -51,8 +45,8 @@ int get_process_ppid(pid_t pid, pid_t *ppid);
 int wait_for_terminate(pid_t pid, siginfo_t *status);
 
 typedef enum WaitFlags {
-        WAIT_LOG_ABNORMAL             = 1U << 0,
-        WAIT_LOG_NON_ZERO_EXIT_STATUS = 1U << 1,
+        WAIT_LOG_ABNORMAL             = 1 << 0,
+        WAIT_LOG_NON_ZERO_EXIT_STATUS = 1 << 1,
 
         /* A shortcut for requesting the most complete logging */
         WAIT_LOG = WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS,
@@ -157,14 +151,15 @@ void reset_cached_pid(void);
 int must_be_root(void);
 
 typedef enum ForkFlags {
-        FORK_RESET_SIGNALS = 1U << 0,
-        FORK_CLOSE_ALL_FDS = 1U << 1,
-        FORK_DEATHSIG      = 1U << 2,
-        FORK_NULL_STDIO    = 1U << 3,
-        FORK_REOPEN_LOG    = 1U << 4,
-        FORK_LOG           = 1U << 5,
-        FORK_WAIT          = 1U << 6,
-        FORK_NEW_MOUNTNS   = 1U << 7,
+        FORK_RESET_SIGNALS = 1 << 0,
+        FORK_CLOSE_ALL_FDS = 1 << 1,
+        FORK_DEATHSIG      = 1 << 2,
+        FORK_NULL_STDIO    = 1 << 3,
+        FORK_REOPEN_LOG    = 1 << 4,
+        FORK_LOG           = 1 << 5,
+        FORK_WAIT          = 1 << 6,
+        FORK_NEW_MOUNTNS   = 1 << 7,
+        FORK_MOUNTNS_SLAVE = 1 << 8,
 } ForkFlags;
 
 int safe_fork_full(const char *name, const int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid);
diff --git a/src/systemd/src/basic/random-util.c b/src/systemd/src/basic/random-util.c
index d80751cf..01a8488f 100644
--- a/src/systemd/src/basic/random-util.c
+++ b/src/systemd/src/basic/random-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -48,9 +43,9 @@ int acquire_random_bytes(void *p, size_t n, bool high_quality_required) {
          * for us. */
 
         /* Use the getrandom() syscall unless we know we don't have it. */
-        if (have_syscall != 0) {
+        if (have_syscall != 0 && !HAS_FEATURE_MEMORY_SANITIZER) {
 #if !HAVE_GETRANDOM
-                /* XXX: NM: systemd calls the syscall directly in this case. Don't add that workaround.
+                /* NetworkManager Note: systemd calls the syscall directly in this case. Don't add that workaround.
                  * If you don't compile against a libc that provides getrandom(), you don't get it. */
                 r = -1;
                 errno = ENOSYS;
diff --git a/src/systemd/src/basic/random-util.h b/src/systemd/src/basic/random-util.h
index 72391705..9a103f0e 100644
--- a/src/systemd/src/basic/random-util.h
+++ b/src/systemd/src/basic/random-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
diff --git a/src/systemd/src/basic/refcnt.h b/src/systemd/src/basic/refcnt.h
index 0d5b3f4e..d2be6086 100644
--- a/src/systemd/src/basic/refcnt.h
+++ b/src/systemd/src/basic/refcnt.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2013 Lennart Poettering
-***/
-
 /* A type-safe atomic refcounter.
  *
  * DO NOT USE THIS UNLESS YOU ACTUALLY CARE ABOUT THREAD SAFETY! */
diff --git a/src/systemd/src/basic/set.h b/src/systemd/src/basic/set.h
index dc0f1e17..66471381 100644
--- a/src/systemd/src/basic/set.h
+++ b/src/systemd/src/basic/set.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include "extract-word.h"
 #include "hashmap.h"
 #include "macro.h"
diff --git a/src/systemd/src/basic/signal-util.h b/src/systemd/src/basic/signal-util.h
index 0c43467b..92f2804c 100644
--- a/src/systemd/src/basic/signal-util.h
+++ b/src/systemd/src/basic/signal-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010-2015 Lennart Poettering
-***/
-
 #include <signal.h>
 
 #include "macro.h"
diff --git a/src/systemd/src/basic/socket-util.c b/src/systemd/src/basic/socket-util.c
index aa06070e..a71db5a7 100644
--- a/src/systemd/src/basic/socket-util.c
+++ b/src/systemd/src/basic/socket-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -793,7 +788,7 @@ static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIN
 
 DEFINE_STRING_TABLE_LOOKUP(socket_address_bind_ipv6_only, SocketAddressBindIPv6Only);
 
-SocketAddressBindIPv6Only parse_socket_address_bind_ipv6_only_or_bool(const char *n) {
+SocketAddressBindIPv6Only socket_address_bind_ipv6_only_or_bool_from_string(const char *n) {
         int r;
 
         r = parse_boolean(n);
diff --git a/src/systemd/src/basic/socket-util.h b/src/systemd/src/basic/socket-util.h
index c2d21b45..e2db795a 100644
--- a/src/systemd/src/basic/socket-util.h
+++ b/src/systemd/src/basic/socket-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <netinet/ether.h>
 #include <netinet/in.h>
 #include <stdbool.h>
@@ -118,7 +112,7 @@ int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret);
 
 const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_;
 SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_;
-SocketAddressBindIPv6Only parse_socket_address_bind_ipv6_only_or_bool(const char *s);
+SocketAddressBindIPv6Only socket_address_bind_ipv6_only_or_bool_from_string(const char *s);
 
 int netlink_family_to_string_alloc(int b, char **s);
 int netlink_family_from_string(const char *s) _pure_;
diff --git a/src/systemd/src/basic/stat-util.c b/src/systemd/src/basic/stat-util.c
index 28e75618..9892d8bc 100644
--- a/src/systemd/src/basic/stat-util.c
+++ b/src/systemd/src/basic/stat-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010-2012 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -135,32 +130,6 @@ int path_is_read_only_fs(const char *path) {
         return false;
 }
 
-int path_is_os_tree(const char *path) {
-        int r;
-
-        assert(path);
-
-        /* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir
-         * always results in -ENOENT, and we can properly distuingish the case where the whole root doesn't exist from
-         * the case where just the os-release file is missing. */
-        if (laccess(path, F_OK) < 0)
-                return -errno;
-
-        /* We use /usr/lib/os-release as flag file if something is an OS */
-        r = chase_symlinks("/usr/lib/os-release", path, CHASE_PREFIX_ROOT, NULL);
-        if (r == -ENOENT) {
-
-                /* Also check for the old location in /etc, just in case. */
-                r = chase_symlinks("/etc/os-release", path, CHASE_PREFIX_ROOT, NULL);
-                if (r == -ENOENT)
-                        return 0; /* We got nothing */
-        }
-        if (r < 0)
-                return r;
-
-        return 1;
-}
-
 int files_same(const char *filea, const char *fileb, int flags) {
         struct stat a, b;
 
diff --git a/src/systemd/src/basic/stat-util.h b/src/systemd/src/basic/stat-util.h
index d1e8d330..f8014ed3 100644
--- a/src/systemd/src/basic/stat-util.h
+++ b/src/systemd/src/basic/stat-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010-2012 Lennart Poettering
-***/
-
 #include <stdbool.h>
 #include <stddef.h>
 #include <sys/stat.h>
@@ -35,7 +29,6 @@ int null_or_empty_path(const char *fn);
 int null_or_empty_fd(int fd);
 
 int path_is_read_only_fs(const char *path);
-int path_is_os_tree(const char *path);
 
 int files_same(const char *filea, const char *fileb, int flags);
 
diff --git a/src/systemd/src/basic/stdio-util.h b/src/systemd/src/basic/stdio-util.h
index 5330789a..73c03274 100644
--- a/src/systemd/src/basic/stdio-util.h
+++ b/src/systemd/src/basic/stdio-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <printf.h>
 #include <stdarg.h>
 #include <stdio.h>
diff --git a/src/systemd/src/basic/string-table.c b/src/systemd/src/basic/string-table.c
index 34723b4c..94412ed2 100644
--- a/src/systemd/src/basic/string-table.c
+++ b/src/systemd/src/basic/string-table.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/basic/string-table.h b/src/systemd/src/basic/string-table.h
index a39206fb..9bd78793 100644
--- a/src/systemd/src/basic/string-table.h
+++ b/src/systemd/src/basic/string-table.h
@@ -2,12 +2,6 @@
 
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <errno.h>
 #include <stddef.h>
 #include <stdio.h>
@@ -77,7 +71,6 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
                 return (type) -1;                                       \
         }                                                               \
 
-
 #define _DEFINE_STRING_TABLE_LOOKUP(name,type,scope)                    \
         _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope)          \
         _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope)
@@ -102,3 +95,18 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
         _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,static)
 #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max) \
         _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,static)
+
+#define DUMP_STRING_TABLE(name,type,max)                                \
+        do {                                                            \
+                type _k;                                                \
+                flockfile(stdout);                                      \
+                for (_k = 0; _k < (max); _k++) {                        \
+                        const char *_t;                                 \
+                        _t = name##_to_string(_k);                      \
+                        if (!_t)                                        \
+                                continue;                               \
+                        fputs_unlocked(_t, stdout);                     \
+                        fputc_unlocked('\n', stdout);                   \
+                }                                                       \
+                funlockfile(stdout);                                    \
+        } while(false)
diff --git a/src/systemd/src/basic/string-util.c b/src/systemd/src/basic/string-util.c
index a60c7dcc..1747f35b 100644
--- a/src/systemd/src/basic/string-util.c
+++ b/src/systemd/src/basic/string-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -16,6 +11,7 @@
 #include <string.h>
 
 #include "alloc-util.h"
+#include "escape.h"
 #include "gunicode.h"
 #include "locale-util.h"
 #include "macro.h"
@@ -271,23 +267,12 @@ char *strjoin_real(const char *x, ...) {
 }
 
 char *strstrip(char *s) {
-        char *e;
-
         if (!s)
                 return NULL;
 
-        /* Drops trailing whitespace. Modifies the string in
-         * place. Returns pointer to first non-space character */
-
-        s += strspn(s, WHITESPACE);
-
-        for (e = strchr(s, 0); e > s; e --)
-                if (!strchr(WHITESPACE, e[-1]))
-                        break;
-
-        *e = 0;
+        /* Drops trailing whitespace. Modifies the string in place. Returns pointer to first non-space character */
 
-        return s;
+        return delete_trailing_chars(skip_leading_chars(s, WHITESPACE), WHITESPACE);
 }
 
 char *delete_chars(char *s, const char *bad) {
@@ -458,9 +443,23 @@ bool string_has_cc(const char *p, const char *ok) {
 }
 
 #if 0 /* NM_IGNORED */
+static int write_ellipsis(char *buf, bool unicode) {
+        if (unicode || is_locale_utf8()) {
+                buf[0] = 0xe2; /* tri-dot ellipsis: … */
+                buf[1] = 0x80;
+                buf[2] = 0xa6;
+        } else {
+                buf[0] = '.';
+                buf[1] = '.';
+                buf[2] = '.';
+        }
+
+        return 3;
+}
+
 static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) {
-        size_t x, need_space;
-        char *r;
+        size_t x, need_space, suffix_len;
+        char *t;
 
         assert(s);
         assert(percent <= 100);
@@ -496,8 +495,8 @@ static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_le
          * either for the UTF-8 encoded character or for three ASCII characters. */
         need_space = is_locale_utf8() ? 1 : 3;
 
-        r = new(char, new_length+3);
-        if (!r)
+        t = new(char, new_length+3);
+        if (!t)
                 return NULL;
 
         assert(new_length >= need_space);
@@ -505,23 +504,13 @@ static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_le
         x = ((new_length - need_space) * percent + 50) / 100;
         assert(x <= new_length - need_space);
 
-        memcpy(r, s, x);
-
-        if (is_locale_utf8()) {
-                r[x+0] = 0xe2; /* tri-dot ellipsis: … */
-                r[x+1] = 0x80;
-                r[x+2] = 0xa6;
-        } else {
-                r[x+0] = '.';
-                r[x+1] = '.';
-                r[x+2] = '.';
-        }
-
-        memcpy(r + x + 3,
-               s + old_length - (new_length - x - need_space),
-               new_length - x - need_space + 1);
+        memcpy(t, s, x);
+        write_ellipsis(t + x, false);
+        suffix_len = new_length - x - need_space;
+        memcpy(t + x + 3, s + old_length - suffix_len, suffix_len);
+        *(t + x + 3 + suffix_len) = '\0';
 
-        return r;
+        return t;
 }
 
 char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) {
@@ -552,42 +541,56 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne
                 return strdup("");
 
         /* If no multibyte characters use ascii_ellipsize_mem for speed */
-        if (ascii_is_valid(s))
+        if (ascii_is_valid_n(s, old_length))
                 return ascii_ellipsize_mem(s, old_length, new_length, percent);
 
         x = ((new_length - 1) * percent) / 100;
         assert(x <= new_length - 1);
 
         k = 0;
-        for (i = s; k < x && i < s + old_length; i = utf8_next_char(i)) {
+        for (i = s; i < s + old_length; i = utf8_next_char(i)) {
                 char32_t c;
+                int w;
 
                 r = utf8_encoded_to_unichar(i, &c);
                 if (r < 0)
                         return NULL;
-                k += unichar_iswide(c) ? 2 : 1;
-        }
 
-        if (k > x) /* last character was wide and went over quota */
-                x++;
+                w = unichar_iswide(c) ? 2 : 1;
+                if (k + w <= x)
+                        k += w;
+                else
+                        break;
+        }
 
-        for (j = s + old_length; k < new_length && j > i; ) {
+        for (j = s + old_length; j > i; ) {
                 char32_t c;
+                int w;
+                const char *jj;
 
-                j = utf8_prev_char(j);
-                r = utf8_encoded_to_unichar(j, &c);
+                jj = utf8_prev_char(j);
+                r = utf8_encoded_to_unichar(jj, &c);
                 if (r < 0)
                         return NULL;
-                k += unichar_iswide(c) ? 2 : 1;
+
+                w = unichar_iswide(c) ? 2 : 1;
+                if (k + w <= new_length) {
+                        k += w;
+                        j = jj;
+                } else
+                        break;
         }
         assert(i <= j);
 
         /* we don't actually need to ellipsize */
         if (i == j)
-                return memdup(s, old_length + 1);
+                return memdup_suffix0(s, old_length);
 
-        /* make space for ellipsis */
-        j = utf8_next_char(j);
+        /* make space for ellipsis, if possible */
+        if (j < s + old_length)
+                j = utf8_next_char(j);
+        else if (i > s)
+                i = utf8_prev_char(i);
 
         len = i - s;
         len2 = s + old_length - j;
@@ -601,21 +604,81 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne
         */
 
         memcpy(e, s, len);
-        e[len + 0] = 0xe2; /* tri-dot ellipsis: … */
-        e[len + 1] = 0x80;
-        e[len + 2] = 0xa6;
-
-        memcpy(e + len + 3, j, len2 + 1);
+        write_ellipsis(e + len, true);
+        memcpy(e + len + 3, j, len2);
+        *(e + len + 3 + len2) = '\0';
 
         return e;
 }
 
-char *ellipsize(const char *s, size_t length, unsigned percent) {
+char *cellescape(char *buf, size_t len, const char *s) {
+        /* Escape and ellipsize s into buffer buf of size len. Only non-control ASCII
+         * characters are copied as they are, everything else is escaped. The result
+         * is different then if escaping and ellipsization was performed in two
+         * separate steps, because each sequence is either stored in full or skipped.
+         *
+         * This function should be used for logging about strings which expected to
+         * be plain ASCII in a safe way.
+         *
+         * An ellipsis will be used if s is too long. It was always placed at the
+         * very end.
+         */
 
-        if (length == (size_t) -1)
-                return strdup(s);
+        size_t i = 0, last_char_width[4] = {}, k = 0, j;
+
+        assert(len > 0); /* at least a terminating NUL */
+
+        for (;;) {
+                char four[4];
+                int w;
+
+                if (*s == 0) /* terminating NUL detected? then we are done! */
+                        goto done;
+
+                w = cescape_char(*s, four);
+                if (i + w + 1 > len) /* This character doesn't fit into the buffer anymore? In that case let's
+                                      * ellipsize at the previous location */
+                        break;
+
+                /* OK, there was space, let's add this escaped character to the buffer */
+                memcpy(buf + i, four, w);
+                i += w;
+
+                /* And remember its width in the ring buffer */
+                last_char_width[k] = w;
+                k = (k + 1) % 4;
+
+                s++;
+        }
+
+        /* Ellipsation is necessary. This means we might need to truncate the string again to make space for 4
+         * characters ideally, but the buffer is shorter than that in the first place take what we can get */
+        for (j = 0; j < ELEMENTSOF(last_char_width); j++) {
+
+                if (i + 4 <= len) /* nice, we reached our space goal */
+                        break;
+
+                k = k == 0 ? 3 : k - 1;
+                if (last_char_width[k] == 0) /* bummer, we reached the beginning of the strings */
+                        break;
+
+                assert(i >= last_char_width[k]);
+                i -= last_char_width[k];
+        }
+
+        if (i + 4 <= len) /* yay, enough space */
+                i += write_ellipsis(buf + i, false);
+        else if (i + 3 <= len) { /* only space for ".." */
+                buf[i++] = '.';
+                buf[i++] = '.';
+        } else if (i + 2 <= len) /* only space for a single "." */
+                buf[i++] = '.';
+        else
+                assert(i + 1 <= len);
 
-        return ellipsize_mem(s, strlen(s), length, percent);
+ done:
+        buf[i] = '\0';
+        return buf;
 }
 #endif /* NM_IGNORED */
 
diff --git a/src/systemd/src/basic/string-util.h b/src/systemd/src/basic/string-util.h
index 5a10eeab..c0cc4e78 100644
--- a/src/systemd/src/basic/string-util.h
+++ b/src/systemd/src/basic/string-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <alloca.h>
 #include <stdbool.h>
 #include <stddef.h>
@@ -156,7 +150,14 @@ static inline bool _pure_ in_charset(const char *s, const char* charset) {
 bool string_has_cc(const char *p, const char *ok) _pure_;
 
 char *ellipsize_mem(const char *s, size_t old_length_bytes, size_t new_length_columns, unsigned percent);
-char *ellipsize(const char *s, size_t length, unsigned percent);
+static inline char *ellipsize(const char *s, size_t length, unsigned percent) {
+        return ellipsize_mem(s, strlen(s), length, percent);
+}
+
+char *cellescape(char *buf, size_t len, const char *s);
+
+/* This limit is arbitrary, enough to give some idea what the string contains */
+#define CELLESCAPE_DEFAULT_LENGTH 64
 
 bool nulstr_contains(const char *nulstr, const char *needle);
 
@@ -209,3 +210,21 @@ static inline size_t strlen_ptr(const char *s) {
 
         return strlen(s);
 }
+
+/* Like startswith(), but operates on arbitrary memory blocks */
+static inline void *memory_startswith(const void *p, size_t sz, const char *token) {
+        size_t n;
+
+        assert(token);
+
+        n = strlen(token);
+        if (sz < n)
+                return NULL;
+
+        assert(p);
+
+        if (memcmp(p, token, n) != 0)
+                return NULL;
+
+        return (uint8_t*) p + n;
+}
diff --git a/src/systemd/src/basic/strv.c b/src/systemd/src/basic/strv.c
index 9d5000aa..9da8cece 100644
--- a/src/systemd/src/basic/strv.c
+++ b/src/systemd/src/basic/strv.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/basic/strv.h b/src/systemd/src/basic/strv.h
index 958c5f3a..51d03db9 100644
--- a/src/systemd/src/basic/strv.h
+++ b/src/systemd/src/basic/strv.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <fnmatch.h>
 #include <stdarg.h>
 #include <stdbool.h>
diff --git a/src/systemd/src/basic/time-util.c b/src/systemd/src/basic/time-util.c
index fa9f66f2..aafa0375 100644
--- a/src/systemd/src/basic/time-util.c
+++ b/src/systemd/src/basic/time-util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -285,8 +280,11 @@ static char *format_timestamp_internal(
                 return NULL; /* Timestamp is unset */
 
         /* Let's not format times with years > 9999 */
-        if (t > USEC_TIMESTAMP_FORMATTABLE_MAX)
-                return NULL;
+        if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) {
+                assert(l >= strlen("--- XXXX-XX-XX XX:XX:XX") + 1);
+                strcpy(buf, "--- XXXX-XX-XX XX:XX:XX");
+                return buf;
+        }
 
         sec = (time_t) (t / USEC_PER_SEC); /* Round down */
 
@@ -485,7 +483,7 @@ char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
                 /* Let's see if we should shows this in dot notation */
                 if (t < USEC_PER_MINUTE && b > 0) {
                         usec_t cc;
-                        int j;
+                        signed char j;
 
                         j = 0;
                         for (cc = table[i].usec; cc > 1; cc /= 10)
@@ -1314,6 +1312,9 @@ bool timezone_is_valid(const char *name, int log_level) {
         if (slash)
                 return false;
 
+        if (p - name >= PATH_MAX)
+                return false;
+
         t = strjoina("/usr/share/zoneinfo/", name);
 
         fd = open(t, O_RDONLY|O_CLOEXEC);
@@ -1467,4 +1468,28 @@ bool in_utc_timezone(void) {
 
         return timezone == 0 && daylight == 0;
 }
+
+int time_change_fd(void) {
+
+        /* We only care for the cancellation event, hence we set the timeout to the latest possible value. */
+        static const struct itimerspec its = {
+                .it_value.tv_sec = TIME_T_MAX,
+        };
+
+        _cleanup_close_ int fd;
+
+        assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX));
+
+        /* Uses TFD_TIMER_CANCEL_ON_SET to get notifications whenever CLOCK_REALTIME makes a jump relative to
+         * CLOCK_MONOTONIC. */
+
+        fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0)
+                return -errno;
+
+        return TAKE_FD(fd);
+}
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/time-util.h b/src/systemd/src/basic/time-util.h
index e720688c..344f2dc5 100644
--- a/src/systemd/src/basic/time-util.h
+++ b/src/systemd/src/basic/time-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <inttypes.h>
 #include <stdbool.h>
 #include <stddef.h>
@@ -183,5 +177,7 @@ static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) {
 /* With a 32bit time_t we can't go beyond 2038... */
 #define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000)
 #else
-#error "Yuck, time_t is neither 4 not 8 bytes wide?"
+#error "Yuck, time_t is neither 4 nor 8 bytes wide?"
 #endif
+
+int time_change_fd(void);
diff --git a/src/systemd/src/basic/umask-util.h b/src/systemd/src/basic/umask-util.h
index ce705247..e964292e 100644
--- a/src/systemd/src/basic/umask-util.h
+++ b/src/systemd/src/basic/umask-util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <stdbool.h>
 #include <sys/stat.h>
 #include <sys/types.h>
diff --git a/src/systemd/src/basic/utf8.c b/src/systemd/src/basic/utf8.c
index 7a382204..80de43c5 100644
--- a/src/systemd/src/basic/utf8.c
+++ b/src/systemd/src/basic/utf8.c
@@ -1,9 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright 2008-2011 Kay Sievers
-  Copyright 2012 Lennart Poettering
+  Copyright © 2008-2011 Kay Sievers
 ***/
 
 /* Parts of this file are based on the GLIB utf8 validation functions. The
@@ -249,6 +246,9 @@ char *utf8_escape_non_printable(const char *str) {
 char *ascii_is_valid(const char *str) {
         const char *p;
 
+        /* Check whether the string consists of valid ASCII bytes,
+         * i.e values between 0 and 127, inclusive. */
+
         assert(str);
 
         for (p = str; *p; p++)
@@ -258,6 +258,21 @@ char *ascii_is_valid(const char *str) {
         return (char*) str;
 }
 
+char *ascii_is_valid_n(const char *str, size_t len) {
+        size_t i;
+
+        /* Very similar to ascii_is_valid(), but checks exactly len
+         * bytes and rejects any NULs in that range. */
+
+        assert(str);
+
+        for (i = 0; i < len; i++)
+                if ((unsigned char) str[i] >= 128 || str[i] == 0)
+                        return NULL;
+
+        return (char*) str;
+}
+
 /**
  * utf8_encode_unichar() - Encode single UCS-4 character as UTF-8
  * @out_utf8: output buffer of at least 4 bytes or NULL
diff --git a/src/systemd/src/basic/utf8.h b/src/systemd/src/basic/utf8.h
index 0ebe36d9..f37f87ff 100644
--- a/src/systemd/src/basic/utf8.h
+++ b/src/systemd/src/basic/utf8.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2012 Lennart Poettering
-***/
-
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
@@ -24,6 +18,7 @@ bool unichar_is_valid(char32_t c);
 
 const char *utf8_is_valid(const char *s) _pure_;
 char *ascii_is_valid(const char *s) _pure_;
+char *ascii_is_valid_n(const char *str, size_t len);
 
 bool utf8_is_printable_newline(const char* str, size_t length, bool newline) _pure_;
 #define utf8_is_printable(str, length) utf8_is_printable_newline(str, length, true)
diff --git a/src/systemd/src/basic/util.c b/src/systemd/src/basic/util.c
index 12e2f451..ba7474f0 100644
--- a/src/systemd/src/basic/util.c
+++ b/src/systemd/src/basic/util.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -269,7 +264,7 @@ int container_get_leader(const char *machine, pid_t *pid) {
                 return -EINVAL;
 
         p = strjoina("/run/systemd/machines/", machine);
-        r = parse_env_file(p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL);
+        r = parse_env_file(NULL, p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL);
         if (r == -ENOENT)
                 return -EHOSTDOWN;
         if (r < 0)
@@ -410,6 +405,7 @@ uint64_t physical_memory(void) {
         uint64_t mem, lim;
         size_t ps;
         long sc;
+        int r;
 
         /* We return this as uint64_t in case we are running as 32bit process on a 64bit kernel with huge amounts of
          * memory.
@@ -423,13 +419,40 @@ uint64_t physical_memory(void) {
         ps = page_size();
         mem = (uint64_t) sc * (uint64_t) ps;
 
-        if (cg_get_root_path(&root) < 0)
+        r = cg_get_root_path(&root);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to determine root cgroup, ignoring cgroup memory limit: %m");
                 return mem;
+        }
 
-        if (cg_get_attribute("memory", root, "memory.limit_in_bytes", &value))
+        r = cg_all_unified();
+        if (r < 0) {
+                log_debug_errno(r, "Failed to determine root unified mode, ignoring cgroup memory limit: %m");
                 return mem;
+        }
+        if (r > 0) {
+                r = cg_get_attribute("memory", root, "memory.max", &value);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to read memory.max cgroup attribute, ignoring cgroup memory limit: %m");
+                        return mem;
+                }
+
+                if (streq(value, "max"))
+                        return mem;
+        } else {
+                r = cg_get_attribute("memory", root, "memory.limit_in_bytes", &value);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to read memory.limit_in_bytes cgroup attribute, ignoring cgroup memory limit: %m");
+                        return mem;
+                }
+        }
 
-        if (safe_atou64(value, &lim) < 0)
+        r = safe_atou64(value, &lim);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to parse cgroup memory limit '%s', ignoring: %m", value);
+                return mem;
+        }
+        if (lim == UINT64_MAX)
                 return mem;
 
         /* Make sure the limit is a multiple of our own page size */
@@ -470,6 +493,7 @@ uint64_t system_tasks_max(void) {
 
         uint64_t a = TASKS_MAX, b = TASKS_MAX;
         _cleanup_free_ char *root = NULL;
+        int r;
 
         /* Determine the maximum number of tasks that may run on this system. We check three sources to determine this
          * limit:
@@ -480,13 +504,24 @@ uint64_t system_tasks_max(void) {
          *
          * And then pick the smallest of the three */
 
-        (void) procfs_tasks_get_limit(&a);
+        r = procfs_tasks_get_limit(&a);
+        if (r < 0)
+                log_debug_errno(r, "Failed to read maximum number of tasks from /proc, ignoring: %m");
 
-        if (cg_get_root_path(&root) >= 0) {
+        r = cg_get_root_path(&root);
+        if (r < 0)
+                log_debug_errno(r, "Failed to determine cgroup root path, ignoring: %m");
+        else {
                 _cleanup_free_ char *value = NULL;
 
-                if (cg_get_attribute("pids", root, "pids.max", &value) >= 0)
-                        (void) safe_atou64(value, &b);
+                r = cg_get_attribute("pids", root, "pids.max", &value);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to read pids.max attribute of cgroup root, ignoring: %m");
+                else if (!streq(value, "max")) {
+                        r = safe_atou64(value, &b);
+                        if (r < 0)
+                                log_debug_errno(r, "Failed to parse pids.max attribute of cgroup root, ignoring: %m");
+                }
         }
 
         return MIN3(TASKS_MAX,
diff --git a/src/systemd/src/basic/util.h b/src/systemd/src/basic/util.h
index ad1931f7..9699d228 100644
--- a/src/systemd/src/basic/util.h
+++ b/src/systemd/src/basic/util.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
-
 #include <alloca.h>
 #include <errno.h>
 #include <fcntl.h>
diff --git a/src/systemd/src/libsystemd-network/arp-util.c b/src/systemd/src/libsystemd-network/arp-util.c
index 26db7ea8..1b711c95 100644
--- a/src/systemd/src/libsystemd-network/arp-util.c
+++ b/src/systemd/src/libsystemd-network/arp-util.c
@@ -1,9 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Axis Communications AB. All rights reserved.
-  Copyright (C) 2015 Tom Gundersen
+  Copyright © 2014 Axis Communications AB. All rights reserved.
+  Copyright © 2015 Tom Gundersen
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/arp-util.h b/src/systemd/src/libsystemd-network/arp-util.h
index 86864e9f..10c68486 100644
--- a/src/systemd/src/libsystemd-network/arp-util.h
+++ b/src/systemd/src/libsystemd-network/arp-util.h
@@ -2,9 +2,7 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Axis Communications AB. All rights reserved.
+  Copyright © 2014 Axis Communications AB. All rights reserved.
 ***/
 
 #include <netinet/if_ether.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.c b/src/systemd/src/libsystemd-network/dhcp-identifier.c
index 6f8adfa6..75357367 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.c
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.c
@@ -1,8 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2015 Tom Gundersen <teg@jklmen>
+  Copyright © 2015 Tom Gundersen <teg@jklmen>
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.h b/src/systemd/src/libsystemd-network/dhcp-identifier.h
index c0a10b6c..42d4956d 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.h
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.h
@@ -2,9 +2,7 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2015 Tom Gundersen <teg@jklmen>
+  Copyright © 2015 Tom Gundersen <teg@jklmen>
 ***/
 
 #include "sd-id128.h"
diff --git a/src/systemd/src/libsystemd-network/dhcp-internal.h b/src/systemd/src/libsystemd-network/dhcp-internal.h
index f8276222..257a3c2e 100644
--- a/src/systemd/src/libsystemd-network/dhcp-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp-internal.h
@@ -2,10 +2,8 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2013 Intel Corporation. All rights reserved.
-  Copyright (C) 2014 Tom Gundersen
+  Copyright © 2013 Intel Corporation. All rights reserved.
+  Copyright © 2014 Tom Gundersen
 ***/
 
 #include <linux/if_packet.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
index 66c213e2..fabac183 100644
--- a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
@@ -2,10 +2,8 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2013 Intel Corporation. All rights reserved.
-  Copyright (C) 2014 Tom Gundersen
+  Copyright © 2013 Intel Corporation. All rights reserved.
+  Copyright © 2014 Tom Gundersen
 ***/
 
 #include <stdint.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp-network.c b/src/systemd/src/libsystemd-network/dhcp-network.c
index 37c1ba19..90fe29d0 100644
--- a/src/systemd/src/libsystemd-network/dhcp-network.c
+++ b/src/systemd/src/libsystemd-network/dhcp-network.c
@@ -1,8 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2013 Intel Corporation. All rights reserved.
+  Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/dhcp-option.c b/src/systemd/src/libsystemd-network/dhcp-option.c
index 86e67358..4a4f7de0 100644
--- a/src/systemd/src/libsystemd-network/dhcp-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp-option.c
@@ -1,8 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2013 Intel Corporation. All rights reserved.
+  Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/dhcp-packet.c b/src/systemd/src/libsystemd-network/dhcp-packet.c
index 136bab9d..214aed8e 100644
--- a/src/systemd/src/libsystemd-network/dhcp-packet.c
+++ b/src/systemd/src/libsystemd-network/dhcp-packet.c
@@ -1,9 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2013 Intel Corporation. All rights reserved.
-  Copyright (C) 2014 Tom Gundersen
+  Copyright © 2013 Intel Corporation. All rights reserved.
+  Copyright © 2014 Tom Gundersen
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/dhcp-protocol.h b/src/systemd/src/libsystemd-network/dhcp-protocol.h
index 2230e094..f0366324 100644
--- a/src/systemd/src/libsystemd-network/dhcp-protocol.h
+++ b/src/systemd/src/libsystemd-network/dhcp-protocol.h
@@ -2,9 +2,7 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2013 Intel Corporation. All rights reserved.
+  Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
 
 #include <netinet/ip.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp6-internal.h b/src/systemd/src/libsystemd-network/dhcp6-internal.h
index 33c43320..f1cbd6a4 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp6-internal.h
@@ -2,9 +2,7 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
+  Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
 
 #include <net/ethernet.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h b/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h
index 2bf54997..dc85c437 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h
@@ -2,10 +2,8 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Tom Gundersen
-  Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
+  Copyright © 2014 Tom Gundersen
+  Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
 
 #include <stdint.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp6-network.c b/src/systemd/src/libsystemd-network/dhcp6-network.c
index 2c6dc713..98aa6261 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-network.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-network.c
@@ -1,8 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Intel Corporation. All rights reserved.
+  Copyright © 2014 Intel Corporation. All rights reserved.
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/dhcp6-option.c b/src/systemd/src/libsystemd-network/dhcp6-option.c
index 87ae96a2..a8a56463 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-option.c
@@ -1,8 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
+  Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
 
 #include "nm-sd-adapt.h"
@@ -143,7 +141,7 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
 }
 
 int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
-        uint8_t buffer[1 + DNS_WIRE_FOMAT_HOSTNAME_MAX];
+        uint8_t buffer[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX];
         int r;
 
         assert_return(buf && *buf && buflen && fqdn, -EINVAL);
diff --git a/src/systemd/src/libsystemd-network/dhcp6-protocol.h b/src/systemd/src/libsystemd-network/dhcp6-protocol.h
index 18417062..ffae4453 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/systemd/src/libsystemd-network/dhcp6-protocol.h
@@ -2,9 +2,7 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Intel Corporation. All rights reserved.
+  Copyright © 2014 Intel Corporation. All rights reserved.
 ***/
 
 #include <netinet/ip6.h>
diff --git a/src/systemd/src/libsystemd-network/lldp-internal.h b/src/systemd/src/libsystemd-network/lldp-internal.h
index 06cf7e8d..e5650988 100644
--- a/src/systemd/src/libsystemd-network/lldp-internal.h
+++ b/src/systemd/src/libsystemd-network/lldp-internal.h
@@ -2,10 +2,8 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Tom Gundersen
-  Copyright (C) 2014 Susant Sahani
+  Copyright © 2014 Tom Gundersen
+  Copyright © 2014 Susant Sahani
 ***/
 
 #include "sd-event.h"
diff --git a/src/systemd/src/libsystemd-network/lldp-neighbor.c b/src/systemd/src/libsystemd-network/lldp-neighbor.c
index 373e34c8..c28c9956 100644
--- a/src/systemd/src/libsystemd-network/lldp-neighbor.c
+++ b/src/systemd/src/libsystemd-network/lldp-neighbor.c
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2016 Lennart Poettering
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/libsystemd-network/lldp-neighbor.h b/src/systemd/src/libsystemd-network/lldp-neighbor.h
index fae49c59..494bc517 100644
--- a/src/systemd/src/libsystemd-network/lldp-neighbor.h
+++ b/src/systemd/src/libsystemd-network/lldp-neighbor.h
@@ -1,12 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  This file is part of systemd.
-
-  Copyright 2016 Lennart Poettering
-***/
-
 #include <inttypes.h>
 #include <stdbool.h>
 #include <sys/types.h>
diff --git a/src/systemd/src/libsystemd-network/lldp-network.c b/src/systemd/src/libsystemd-network/lldp-network.c
index 307cf6c3..bc6c20d1 100644
--- a/src/systemd/src/libsystemd-network/lldp-network.c
+++ b/src/systemd/src/libsystemd-network/lldp-network.c
@@ -1,9 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Tom Gundersen
-  Copyright (C) 2014 Susant Sahani
+  Copyright © 2014 Tom Gundersen
+  Copyright © 2014 Susant Sahani
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/lldp-network.h b/src/systemd/src/libsystemd-network/lldp-network.h
index 7a1e8b10..1d773acc 100644
--- a/src/systemd/src/libsystemd-network/lldp-network.h
+++ b/src/systemd/src/libsystemd-network/lldp-network.h
@@ -2,10 +2,8 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Tom Gundersen
-  Copyright (C) 2014 Susant Sahani
+  Copyright © 2014 Tom Gundersen
+  Copyright © 2014 Susant Sahani
 ***/
 
 #include "sd-event.h"
diff --git a/src/systemd/src/libsystemd-network/network-internal.c b/src/systemd/src/libsystemd-network/network-internal.c
index e05f52d4..308762ee 100644
--- a/src/systemd/src/libsystemd-network/network-internal.c
+++ b/src/systemd/src/libsystemd-network/network-internal.c
@@ -1,8 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
- This file is part of systemd.
-
- Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
+ Copyright © 2013 Tom Gundersen <teg@jklm.no>
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/network-internal.h b/src/systemd/src/libsystemd-network/network-internal.h
index d0076f45..ef69e701 100644
--- a/src/systemd/src/libsystemd-network/network-internal.h
+++ b/src/systemd/src/libsystemd-network/network-internal.h
@@ -2,9 +2,7 @@
 #pragma once
 
 /***
- This file is part of systemd.
-
- Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
+ Copyright © 2013 Tom Gundersen <teg@jklm.no>
 ***/
 
 #include <stdbool.h>
@@ -12,6 +10,7 @@
 #include "sd-dhcp-lease.h"
 
 #include "condition.h"
+#include "conf-parser.h"
 #include "set.h"
 #include "udev.h"
 
@@ -35,33 +34,15 @@ bool net_match_config(Set *match_mac,
                       const char *dev_type,
                       const char *dev_name);
 
-int config_parse_net_condition(const char *unit, const char *filename, unsigned line,
-                               const char *section, unsigned section_line, const char *lvalue,
-                               int ltype, const char *rvalue, void *data, void *userdata);
-
-int config_parse_hwaddr(const char *unit, const char *filename, unsigned line,
-                        const char *section, unsigned section_line, const char *lvalue,
-                        int ltype, const char *rvalue, void *data, void *userdata);
-
-int config_parse_hwaddrs(const char *unit, const char *filename, unsigned line,
-                         const char *section, unsigned section_line, const char *lvalue,
-                         int ltype, const char *rvalue, void *data, void *userdata);
-
-int config_parse_ifnames(const char *unit, const char *filename, unsigned line,
-                         const char *section, unsigned section_line, const char *lvalue,
-                         int ltype, const char *rvalue, void *data, void *userdata);
-
-int config_parse_ifalias(const char *unit, const char *filename, unsigned line,
-                         const char *section, unsigned section_line, const char *lvalue,
-                         int ltype, const char *rvalue, void *data, void *userdata);
-
-int config_parse_iaid(const char *unit, const char *filename, unsigned line,
-                      const char *section, unsigned section_line, const char *lvalue,
-                      int ltype, const char *rvalue, void *data, void *userdata);
-
-int config_parse_bridge_port_priority(const char *unit, const char *filename, unsigned line,
-                      const char *section, unsigned section_line, const char *lvalue,
-                      int ltype, const char *rvalue, void *data, void *userdata);
+#if 0 /* NM_IGNORED */
+CONFIG_PARSER_PROTOTYPE(config_parse_net_condition);
+CONFIG_PARSER_PROTOTYPE(config_parse_hwaddr);
+CONFIG_PARSER_PROTOTYPE(config_parse_hwaddrs);
+CONFIG_PARSER_PROTOTYPE(config_parse_ifnames);
+CONFIG_PARSER_PROTOTYPE(config_parse_ifalias);
+CONFIG_PARSER_PROTOTYPE(config_parse_iaid);
+CONFIG_PARSER_PROTOTYPE(config_parse_bridge_port_priority);
+#endif /* NM_IGNORED */
 
 int net_get_unique_predictable_data(struct udev_device *device, uint64_t *result);
 const char *net_get_name(struct udev_device *device);
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
index ef2620f4..c2f81e1c 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -1,8 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2013 Intel Corporation. All rights reserved.
+  Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
index d92c5126..33a0796a 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -1,9 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2013 Intel Corporation. All rights reserved.
-  Copyright (C) 2014 Tom Gundersen
+  Copyright © 2013 Intel Corporation. All rights reserved.
+  Copyright © 2014 Tom Gundersen
 ***/
 
 #include "nm-sd-adapt.h"
@@ -1036,7 +1034,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
         if (r < 0)
                 return r;
 
-        r = parse_env_file(lease_file, NEWLINE,
+        r = parse_env_file(NULL, lease_file, NEWLINE,
                            "ADDRESS", &address,
                            "ROUTER", &router,
                            "NETMASK", &netmask,
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
index 1cec6022..ca03f580 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
@@ -1,8 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
+  Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
index 3952cd99..1c8346d4 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
@@ -1,9 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Tom Gundersen
-  Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
+  Copyright © 2014 Tom Gundersen
+  Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4acd.c b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
index ed98c0b6..3766c1f3 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4acd.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
@@ -1,9 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Axis Communications AB. All rights reserved.
-  Copyright (C) 2015 Tom Gundersen
+  Copyright © 2014 Axis Communications AB. All rights reserved.
+  Copyright © 2015 Tom Gundersen
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4ll.c b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
index 001ebbd8..ca1e0bdb 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4ll.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
@@ -1,9 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Axis Communications AB. All rights reserved.
-  Copyright (C) 2015 Tom Gundersen
+  Copyright © 2014 Axis Communications AB. All rights reserved.
+  Copyright © 2015 Tom Gundersen
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/sd-lldp.c b/src/systemd/src/libsystemd-network/sd-lldp.c
index d88a0c6f..ea1dda4d 100644
--- a/src/systemd/src/libsystemd-network/sd-lldp.c
+++ b/src/systemd/src/libsystemd-network/sd-lldp.c
@@ -1,9 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Tom Gundersen
-  Copyright (C) 2014 Susant Sahani
+  Copyright © 2014 Tom Gundersen
+  Copyright © 2014 Susant Sahani
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd/sd-event/sd-event.c b/src/systemd/src/libsystemd/sd-event/sd-event.c
index 8a9b8055..f4d5dbe4 100644
--- a/src/systemd/src/libsystemd/sd-event/sd-event.c
+++ b/src/systemd/src/libsystemd/sd-event/sd-event.c
@@ -1,8 +1,5 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright 2013 Lennart Poettering
 ***/
 
 #include "nm-sd-adapt.h"
@@ -17,6 +14,7 @@
 
 #include "alloc-util.h"
 #include "fd-util.h"
+#include "fs-util.h"
 #include "hashmap.h"
 #include "list.h"
 #include "macro.h"
@@ -45,6 +43,7 @@ typedef enum EventSourceType {
         SOURCE_POST,
         SOURCE_EXIT,
         SOURCE_WATCHDOG,
+        SOURCE_INOTIFY,
         _SOURCE_EVENT_SOURCE_TYPE_MAX,
         _SOURCE_EVENT_SOURCE_TYPE_INVALID = -1
 } EventSourceType;
@@ -62,6 +61,7 @@ static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX]
         [SOURCE_POST] = "post",
         [SOURCE_EXIT] = "exit",
         [SOURCE_WATCHDOG] = "watchdog",
+        [SOURCE_INOTIFY] = "inotify",
 };
 
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int);
@@ -73,12 +73,15 @@ typedef enum WakeupType {
         WAKEUP_EVENT_SOURCE,
         WAKEUP_CLOCK_DATA,
         WAKEUP_SIGNAL_DATA,
+        WAKEUP_INOTIFY_DATA,
         _WAKEUP_TYPE_MAX,
         _WAKEUP_TYPE_INVALID = -1,
 } WakeupType;
 
 #define EVENT_SOURCE_IS_TIME(t) IN_SET((t), SOURCE_TIME_REALTIME, SOURCE_TIME_BOOTTIME, SOURCE_TIME_MONOTONIC, SOURCE_TIME_REALTIME_ALARM, SOURCE_TIME_BOOTTIME_ALARM)
 
+struct inode_data;
+
 struct sd_event_source {
         WakeupType wakeup;
 
@@ -102,6 +105,8 @@ struct sd_event_source {
         uint64_t pending_iteration;
         uint64_t prepare_iteration;
 
+        sd_event_destroy_t destroy_callback;
+
         LIST_FIELDS(sd_event_source, sources);
 
         union {
@@ -140,6 +145,12 @@ struct sd_event_source {
                         sd_event_handler_t callback;
                         unsigned prioq_index;
                 } exit;
+                struct {
+                        sd_event_inotify_handler_t callback;
+                        uint32_t mask;
+                        struct inode_data *inode_data;
+                        LIST_FIELDS(sd_event_source, by_inode_data);
+                } inotify;
         };
 };
 
@@ -174,6 +185,64 @@ struct signal_data {
         sd_event_source *current;
 };
 
+/* A structure listing all event sources currently watching a specific inode */
+struct inode_data {
+        /* The identifier for the inode, the combination of the .st_dev + .st_ino fields of the file */
+        ino_t ino;
+        dev_t dev;
+
+        /* An fd of the inode to watch. The fd is kept open until the next iteration of the loop, so that we can
+         * rearrange the priority still until then, as we need the original inode to change the priority as we need to
+         * add a watch descriptor to the right inotify for the priority which we can only do if we have a handle to the
+         * original inode. We keep a list of all inode_data objects with an open fd in the to_close list (see below) of
+         * the sd-event object, so that it is efficient to close everything, before entering the next event loop
+         * iteration. */
+        int fd;
+
+        /* The inotify "watch descriptor" */
+        int wd;
+
+        /* The combination of the mask of all inotify watches on this inode we manage. This is also the mask that has
+         * most recently been set on the watch descriptor. */
+        uint32_t combined_mask;
+
+        /* All event sources subscribed to this inode */
+        LIST_HEAD(sd_event_source, event_sources);
+
+        /* The inotify object we watch this inode with */
+        struct inotify_data *inotify_data;
+
+        /* A linked list of all inode data objects with fds to close (see above) */
+        LIST_FIELDS(struct inode_data, to_close);
+};
+
+/* A structure encapsulating an inotify fd */
+struct inotify_data {
+        WakeupType wakeup;
+
+        /* For each priority we maintain one inotify fd, so that we only have to dequeue a single event per priority at
+         * a time */
+
+        int fd;
+        int64_t priority;
+
+        Hashmap *inodes; /* The inode_data structures keyed by dev+ino */
+        Hashmap *wd;     /* The inode_data structures keyed by the watch descriptor for each */
+
+        /* The buffer we read inotify events into */
+        union inotify_event_buffer buffer;
+        size_t buffer_filled; /* fill level of the buffer */
+
+        /* How many event sources are currently marked pending for this inotify. We won't read new events off the
+         * inotify fd as long as there are still pending events on the inotify (because we have no strategy of queuing
+         * the events locally if they can't be coalesced). */
+        unsigned n_pending;
+
+        /* A linked list of all inotify objects with data already read, that still need processing. We keep this list
+         * to make it efficient to figure out what inotify objects to process data on next. */
+        LIST_FIELDS(struct inotify_data, buffered);
+};
+
 struct sd_event {
         unsigned n_ref;
 
@@ -204,6 +273,14 @@ struct sd_event {
 
         Prioq *exit;
 
+        Hashmap *inotify_data; /* indexed by priority */
+
+        /* A list of inode structures that still have an fd open, that we need to close before the next loop iteration */
+        LIST_HEAD(struct inode_data, inode_data_to_close);
+
+        /* A list of inotify objects that already have events buffered which aren't processed yet */
+        LIST_HEAD(struct inotify_data, inotify_data_buffered);
+
         pid_t original_pid;
 
         uint64_t iteration;
@@ -233,6 +310,7 @@ struct sd_event {
 static thread_local sd_event *default_event = NULL;
 
 static void source_disconnect(sd_event_source *s);
+static void event_gc_inode_data(sd_event *e, struct inode_data *d);
 
 static sd_event *event_resolve(sd_event *e) {
         return e == SD_EVENT_DEFAULT ? default_event : e;
@@ -414,6 +492,8 @@ static void event_free(sd_event *e) {
         free(e->signal_sources);
         hashmap_free(e->signal_data);
 
+        hashmap_free(e->inotify_data);
+
         hashmap_free(e->child_sources);
         set_free(e->post_sources);
         free(e);
@@ -425,16 +505,32 @@ _public_ int sd_event_new(sd_event** ret) {
 
         assert_return(ret, -EINVAL);
 
-        e = new0(sd_event, 1);
+        e = new(sd_event, 1);
         if (!e)
                 return -ENOMEM;
 
-        e->n_ref = 1;
-        e->watchdog_fd = e->epoll_fd = e->realtime.fd = e->boottime.fd = e->monotonic.fd = e->realtime_alarm.fd = e->boottime_alarm.fd = -1;
-        e->realtime.next = e->boottime.next = e->monotonic.next = e->realtime_alarm.next = e->boottime_alarm.next = USEC_INFINITY;
-        e->realtime.wakeup = e->boottime.wakeup = e->monotonic.wakeup = e->realtime_alarm.wakeup = e->boottime_alarm.wakeup = WAKEUP_CLOCK_DATA;
-        e->original_pid = getpid_cached();
-        e->perturb = USEC_INFINITY;
+        *e = (sd_event) {
+                .n_ref = 1,
+                .epoll_fd = -1,
+                .watchdog_fd = -1,
+                .realtime.wakeup = WAKEUP_CLOCK_DATA,
+                .realtime.fd = -1,
+                .realtime.next = USEC_INFINITY,
+                .boottime.wakeup = WAKEUP_CLOCK_DATA,
+                .boottime.fd = -1,
+                .boottime.next = USEC_INFINITY,
+                .monotonic.wakeup = WAKEUP_CLOCK_DATA,
+                .monotonic.fd = -1,
+                .monotonic.next = USEC_INFINITY,
+                .realtime_alarm.wakeup = WAKEUP_CLOCK_DATA,
+                .realtime_alarm.fd = -1,
+                .realtime_alarm.next = USEC_INFINITY,
+                .boottime_alarm.wakeup = WAKEUP_CLOCK_DATA,
+                .boottime_alarm.fd = -1,
+                .boottime_alarm.next = USEC_INFINITY,
+                .perturb = USEC_INFINITY,
+                .original_pid = getpid_cached(),
+        };
 
         r = prioq_ensure_allocated(&e->pending, pending_prioq_compare);
         if (r < 0)
@@ -520,18 +616,17 @@ static int source_io_register(
                 int enabled,
                 uint32_t events) {
 
-        struct epoll_event ev = {};
+        struct epoll_event ev;
         int r;
 
         assert(s);
         assert(s->type == SOURCE_IO);
         assert(enabled != SD_EVENT_OFF);
 
-        ev.events = events;
-        ev.data.ptr = s;
-
-        if (enabled == SD_EVENT_ONESHOT)
-                ev.events |= EPOLLONESHOT;
+        ev = (struct epoll_event) {
+                .events = events | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0),
+                .data.ptr = s,
+        };
 
         if (s->io.registered)
                 r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_MOD, s->io.fd, &ev);
@@ -623,7 +718,7 @@ static int event_make_signal_data(
                 int sig,
                 struct signal_data **ret) {
 
-        struct epoll_event ev = {};
+        struct epoll_event ev;
         struct signal_data *d;
         bool added = false;
         sigset_t ss_copy;
@@ -638,7 +733,7 @@ static int event_make_signal_data(
         if (e->signal_sources && e->signal_sources[sig])
                 priority = e->signal_sources[sig]->priority;
         else
-                priority = 0;
+                priority = SD_EVENT_PRIORITY_NORMAL;
 
         d = hashmap_get(e->signal_data, &priority);
         if (d) {
@@ -652,13 +747,15 @@ static int event_make_signal_data(
                 if (r < 0)
                         return r;
 
-                d = new0(struct signal_data, 1);
+                d = new(struct signal_data, 1);
                 if (!d)
                         return -ENOMEM;
 
-                d->wakeup = WAKEUP_SIGNAL_DATA;
-                d->fd  = -1;
-                d->priority = priority;
+                *d = (struct signal_data) {
+                        .wakeup = WAKEUP_SIGNAL_DATA,
+                        .fd = -1,
+                        .priority = priority,
+                };
 
                 r = hashmap_put(e->signal_data, &d->priority, d);
                 if (r < 0) {
@@ -688,8 +785,10 @@ static int event_make_signal_data(
 
         d->fd = fd_move_above_stdio(r);
 
-        ev.events = EPOLLIN;
-        ev.data.ptr = d;
+        ev = (struct epoll_event) {
+                .events = EPOLLIN,
+                .data.ptr = d,
+        };
 
         r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev);
         if (r < 0)  {
@@ -856,6 +955,41 @@ static void source_disconnect(sd_event_source *s) {
                 prioq_remove(s->event->exit, s, &s->exit.prioq_index);
                 break;
 
+        case SOURCE_INOTIFY: {
+                struct inode_data *inode_data;
+
+                inode_data = s->inotify.inode_data;
+                if (inode_data) {
+                        struct inotify_data *inotify_data;
+                        assert_se(inotify_data = inode_data->inotify_data);
+
+                        /* Detach this event source from the inode object */
+                        LIST_REMOVE(inotify.by_inode_data, inode_data->event_sources, s);
+                        s->inotify.inode_data = NULL;
+
+                        if (s->pending) {
+                                assert(inotify_data->n_pending > 0);
+                                inotify_data->n_pending--;
+                        }
+
+                        /* Note that we don't reduce the inotify mask for the watch descriptor here if the inode is
+                         * continued to being watched. That's because inotify doesn't really have an API for that: we
+                         * can only change watch masks with access to the original inode either by fd or by path. But
+                         * paths aren't stable, and keeping an O_PATH fd open all the time would mean wasting an fd
+                         * continously and keeping the mount busy which we can't really do. We could reconstruct the
+                         * original inode from /proc/self/fdinfo/$INOTIFY_FD (as all watch descriptors are listed
+                         * there), but given the need for open_by_handle_at() which is privileged and not universally
+                         * available this would be quite an incomplete solution. Hence we go the other way, leave the
+                         * mask set, even if it is not minimized now, and ignore all events we aren't interested in
+                         * anymore after reception. Yes, this sucks, but … Linux … */
+
+                        /* Maybe release the inode data (and its inotify) */
+                        event_gc_inode_data(s->event, inode_data);
+                }
+
+                break;
+        }
+
         default:
                 assert_not_reached("Wut? I shouldn't exist.");
         }
@@ -883,7 +1017,10 @@ static void source_free(sd_event_source *s) {
         source_disconnect(s);
 
         if (s->type == SOURCE_IO && s->io.owned)
-                safe_close(s->io.fd);
+                s->io.fd = safe_close(s->io.fd);
+
+        if (s->destroy_callback)
+                s->destroy_callback(s->userdata);
 
         free(s->description);
         free(s);
@@ -930,6 +1067,19 @@ static int source_set_pending(sd_event_source *s, bool b) {
                         d->current = NULL;
         }
 
+        if (s->type == SOURCE_INOTIFY) {
+
+                assert(s->inotify.inode_data);
+                assert(s->inotify.inode_data->inotify_data);
+
+                if (b)
+                        s->inotify.inode_data->inotify_data->n_pending ++;
+                else {
+                        assert(s->inotify.inode_data->inotify_data->n_pending > 0);
+                        s->inotify.inode_data->inotify_data->n_pending --;
+                }
+        }
+
         return 0;
 }
 
@@ -938,15 +1088,18 @@ static sd_event_source *source_new(sd_event *e, bool floating, EventSourceType t
 
         assert(e);
 
-        s = new0(sd_event_source, 1);
+        s = new(sd_event_source, 1);
         if (!s)
                 return NULL;
 
-        s->n_ref = 1;
-        s->event = e;
-        s->floating = floating;
-        s->type = type;
-        s->pending_index = s->prepare_index = PRIOQ_IDX_NULL;
+        *s = (struct sd_event_source) {
+                .n_ref = 1,
+                .event = e,
+                .floating = floating,
+                .type = type,
+                .pending_index = PRIOQ_IDX_NULL,
+                .prepare_index = PRIOQ_IDX_NULL,
+        };
 
         if (!floating)
                 sd_event_ref(e);
@@ -1023,7 +1176,7 @@ static int event_setup_timer_fd(
                 struct clock_data *d,
                 clockid_t clock) {
 
-        struct epoll_event ev = {};
+        struct epoll_event ev;
         int r, fd;
 
         assert(e);
@@ -1038,8 +1191,10 @@ static int event_setup_timer_fd(
 
         fd = fd_move_above_stdio(fd);
 
-        ev.events = EPOLLIN;
-        ev.data.ptr = d;
+        ev = (struct epoll_event) {
+                .events = EPOLLIN,
+                .data.ptr = d,
+        };
 
         r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, fd, &ev);
         if (r < 0) {
@@ -1378,6 +1533,405 @@ _public_ int sd_event_add_exit(
         return 0;
 }
 
+static void event_free_inotify_data(sd_event *e, struct inotify_data *d) {
+        assert(e);
+
+        if (!d)
+                return;
+
+        assert(hashmap_isempty(d->inodes));
+        assert(hashmap_isempty(d->wd));
+
+        if (d->buffer_filled > 0)
+                LIST_REMOVE(buffered, e->inotify_data_buffered, d);
+
+        hashmap_free(d->inodes);
+        hashmap_free(d->wd);
+
+        assert_se(hashmap_remove(e->inotify_data, &d->priority) == d);
+
+        if (d->fd >= 0) {
+                if (epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, d->fd, NULL) < 0)
+                        log_debug_errno(errno, "Failed to remove inotify fd from epoll, ignoring: %m");
+
+                safe_close(d->fd);
+        }
+        free(d);
+}
+
+static int event_make_inotify_data(
+                sd_event *e,
+                int64_t priority,
+                struct inotify_data **ret) {
+
+        _cleanup_close_ int fd = -1;
+        struct inotify_data *d;
+        struct epoll_event ev;
+        int r;
+
+        assert(e);
+
+        d = hashmap_get(e->inotify_data, &priority);
+        if (d) {
+                if (ret)
+                        *ret = d;
+                return 0;
+        }
+
+        fd = inotify_init1(IN_NONBLOCK|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        fd = fd_move_above_stdio(fd);
+
+        r = hashmap_ensure_allocated(&e->inotify_data, &uint64_hash_ops);
+        if (r < 0)
+                return r;
+
+        d = new(struct inotify_data, 1);
+        if (!d)
+                return -ENOMEM;
+
+        *d = (struct inotify_data) {
+                .wakeup = WAKEUP_INOTIFY_DATA,
+                .fd = TAKE_FD(fd),
+                .priority = priority,
+        };
+
+        r = hashmap_put(e->inotify_data, &d->priority, d);
+        if (r < 0) {
+                d->fd = safe_close(d->fd);
+                free(d);
+                return r;
+        }
+
+        ev = (struct epoll_event) {
+                .events = EPOLLIN,
+                .data.ptr = d,
+        };
+
+        if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev) < 0) {
+                r = -errno;
+                d->fd = safe_close(d->fd); /* let's close this ourselves, as event_free_inotify_data() would otherwise
+                                            * remove the fd from the epoll first, which we don't want as we couldn't
+                                            * add it in the first place. */
+                event_free_inotify_data(e, d);
+                return r;
+        }
+
+        if (ret)
+                *ret = d;
+
+        return 1;
+}
+
+static int inode_data_compare(const void *a, const void *b) {
+        const struct inode_data *x = a, *y = b;
+
+        assert(x);
+        assert(y);
+
+        if (x->dev < y->dev)
+                return -1;
+        if (x->dev > y->dev)
+                return 1;
+
+        if (x->ino < y->ino)
+                return -1;
+        if (x->ino > y->ino)
+                return 1;
+
+        return 0;
+}
+
+static void inode_data_hash_func(const void *p, struct siphash *state) {
+        const struct inode_data *d = p;
+
+        assert(p);
+
+        siphash24_compress(&d->dev, sizeof(d->dev), state);
+        siphash24_compress(&d->ino, sizeof(d->ino), state);
+}
+
+const struct hash_ops inode_data_hash_ops = {
+        .hash = inode_data_hash_func,
+        .compare = inode_data_compare
+};
+
+static void event_free_inode_data(
+                sd_event *e,
+                struct inode_data *d) {
+
+        assert(e);
+
+        if (!d)
+                return;
+
+        assert(!d->event_sources);
+
+        if (d->fd >= 0) {
+                LIST_REMOVE(to_close, e->inode_data_to_close, d);
+                safe_close(d->fd);
+        }
+
+        if (d->inotify_data) {
+
+                if (d->wd >= 0) {
+                        if (d->inotify_data->fd >= 0) {
+                                /* So here's a problem. At the time this runs the watch descriptor might already be
+                                 * invalidated, because an IN_IGNORED event might be queued right the moment we enter
+                                 * the syscall. Hence, whenever we get EINVAL, ignore it entirely, since it's a very
+                                 * likely case to happen. */
+
+                                if (inotify_rm_watch(d->inotify_data->fd, d->wd) < 0 && errno != EINVAL)
+                                        log_debug_errno(errno, "Failed to remove watch descriptor %i from inotify, ignoring: %m", d->wd);
+                        }
+
+                        assert_se(hashmap_remove(d->inotify_data->wd, INT_TO_PTR(d->wd)) == d);
+                }
+
+                assert_se(hashmap_remove(d->inotify_data->inodes, d) == d);
+        }
+
+        free(d);
+}
+
+static void event_gc_inode_data(
+                sd_event *e,
+                struct inode_data *d) {
+
+        struct inotify_data *inotify_data;
+
+        assert(e);
+
+        if (!d)
+                return;
+
+        if (d->event_sources)
+                return;
+
+        inotify_data = d->inotify_data;
+        event_free_inode_data(e, d);
+
+        if (inotify_data && hashmap_isempty(inotify_data->inodes))
+                event_free_inotify_data(e, inotify_data);
+}
+
+static int event_make_inode_data(
+                sd_event *e,
+                struct inotify_data *inotify_data,
+                dev_t dev,
+                ino_t ino,
+                struct inode_data **ret) {
+
+        struct inode_data *d, key;
+        int r;
+
+        assert(e);
+        assert(inotify_data);
+
+        key = (struct inode_data) {
+                .ino = ino,
+                .dev = dev,
+        };
+
+        d = hashmap_get(inotify_data->inodes, &key);
+        if (d) {
+                if (ret)
+                        *ret = d;
+
+                return 0;
+        }
+
+        r = hashmap_ensure_allocated(&inotify_data->inodes, &inode_data_hash_ops);
+        if (r < 0)
+                return r;
+
+        d = new(struct inode_data, 1);
+        if (!d)
+                return -ENOMEM;
+
+        *d = (struct inode_data) {
+                .dev = dev,
+                .ino = ino,
+                .wd = -1,
+                .fd = -1,
+                .inotify_data = inotify_data,
+        };
+
+        r = hashmap_put(inotify_data->inodes, d, d);
+        if (r < 0) {
+                free(d);
+                return r;
+        }
+
+        if (ret)
+                *ret = d;
+
+        return 1;
+}
+
+static uint32_t inode_data_determine_mask(struct inode_data *d) {
+        bool excl_unlink = true;
+        uint32_t combined = 0;
+        sd_event_source *s;
+
+        assert(d);
+
+        /* Combines the watch masks of all event sources watching this inode. We generally just OR them together, but
+         * the IN_EXCL_UNLINK flag is ANDed instead.
+         *
+         * Note that we add all sources to the mask here, regardless whether enabled, disabled or oneshot. That's
+         * because we cannot change the mask anymore after the event source was created once, since the kernel has no
+         * API for that. Hence we need to subscribe to the maximum mask we ever might be interested in, and supress
+         * events we don't care for client-side. */
+
+        LIST_FOREACH(inotify.by_inode_data, s, d->event_sources) {
+
+                if ((s->inotify.mask & IN_EXCL_UNLINK) == 0)
+                        excl_unlink = false;
+
+                combined |= s->inotify.mask;
+        }
+
+        return (combined & ~(IN_ONESHOT|IN_DONT_FOLLOW|IN_ONLYDIR|IN_EXCL_UNLINK)) | (excl_unlink ? IN_EXCL_UNLINK : 0);
+}
+
+static int inode_data_realize_watch(sd_event *e, struct inode_data *d) {
+        uint32_t combined_mask;
+        int wd, r;
+
+        assert(d);
+        assert(d->fd >= 0);
+
+        combined_mask = inode_data_determine_mask(d);
+
+        if (d->wd >= 0 && combined_mask == d->combined_mask)
+                return 0;
+
+        r = hashmap_ensure_allocated(&d->inotify_data->wd, NULL);
+        if (r < 0)
+                return r;
+
+        wd = inotify_add_watch_fd(d->inotify_data->fd, d->fd, combined_mask);
+        if (wd < 0)
+                return -errno;
+
+        if (d->wd < 0) {
+                r = hashmap_put(d->inotify_data->wd, INT_TO_PTR(wd), d);
+                if (r < 0) {
+                        (void) inotify_rm_watch(d->inotify_data->fd, wd);
+                        return r;
+                }
+
+                d->wd = wd;
+
+        } else if (d->wd != wd) {
+
+                log_debug("Weird, the watch descriptor we already knew for this inode changed?");
+                (void) inotify_rm_watch(d->fd, wd);
+                return -EINVAL;
+        }
+
+        d->combined_mask = combined_mask;
+        return 1;
+}
+
+_public_ int sd_event_add_inotify(
+                sd_event *e,
+                sd_event_source **ret,
+                const char *path,
+                uint32_t mask,
+                sd_event_inotify_handler_t callback,
+                void *userdata) {
+
+        bool rm_inotify = false, rm_inode = false;
+        struct inotify_data *inotify_data = NULL;
+        struct inode_data *inode_data = NULL;
+        _cleanup_close_ int fd = -1;
+        sd_event_source *s;
+        struct stat st;
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(path, -EINVAL);
+        assert_return(callback, -EINVAL);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        /* Refuse IN_MASK_ADD since we coalesce watches on the same inode, and hence really don't want to merge
+         * masks. Or in other words, this whole code exists only to manage IN_MASK_ADD type operations for you, hence
+         * the user can't use them for us. */
+        if (mask & IN_MASK_ADD)
+                return -EINVAL;
+
+        fd = open(path, O_PATH|O_CLOEXEC|
+                  (mask & IN_ONLYDIR ? O_DIRECTORY : 0)|
+                  (mask & IN_DONT_FOLLOW ? O_NOFOLLOW : 0));
+        if (fd < 0)
+                return -errno;
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        s = source_new(e, !ret, SOURCE_INOTIFY);
+        if (!s)
+                return -ENOMEM;
+
+        s->enabled = mask & IN_ONESHOT ? SD_EVENT_ONESHOT : SD_EVENT_ON;
+        s->inotify.mask = mask;
+        s->inotify.callback = callback;
+        s->userdata = userdata;
+
+        /* Allocate an inotify object for this priority, and an inode object within it */
+        r = event_make_inotify_data(e, SD_EVENT_PRIORITY_NORMAL, &inotify_data);
+        if (r < 0)
+                goto fail;
+        rm_inotify = r > 0;
+
+        r = event_make_inode_data(e, inotify_data, st.st_dev, st.st_ino, &inode_data);
+        if (r < 0)
+                goto fail;
+        rm_inode = r > 0;
+
+        /* Keep the O_PATH fd around until the first iteration of the loop, so that we can still change the priority of
+         * the event source, until then, for which we need the original inode. */
+        if (inode_data->fd < 0) {
+                inode_data->fd = TAKE_FD(fd);
+                LIST_PREPEND(to_close, e->inode_data_to_close, inode_data);
+        }
+
+        /* Link our event source to the inode data object */
+        LIST_PREPEND(inotify.by_inode_data, inode_data->event_sources, s);
+        s->inotify.inode_data = inode_data;
+
+        rm_inode = rm_inotify = false;
+
+        /* Actually realize the watch now */
+        r = inode_data_realize_watch(e, inode_data);
+        if (r < 0)
+                goto fail;
+
+        (void) sd_event_source_set_description(s, path);
+
+        if (ret)
+                *ret = s;
+
+        return 0;
+
+fail:
+        source_free(s);
+
+        if (rm_inode)
+                event_free_inode_data(e, inode_data);
+
+        if (rm_inotify)
+                event_free_inotify_data(e, inotify_data);
+
+        return r;
+}
+
 _public_ sd_event_source* sd_event_source_ref(sd_event_source *s) {
 
         if (!s)
@@ -1532,6 +2086,10 @@ _public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events)
         if (s->io.events == events && !(events & EPOLLET))
                 return 0;
 
+        r = source_set_pending(s, false);
+        if (r < 0)
+                return r;
+
         if (s->enabled != SD_EVENT_OFF) {
                 r = source_io_register(s, s->enabled, events);
                 if (r < 0)
@@ -1539,7 +2097,6 @@ _public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events)
         }
 
         s->io.events = events;
-        source_set_pending(s, false);
 
         return 0;
 }
@@ -1572,6 +2129,9 @@ _public_ int sd_event_source_get_priority(sd_event_source *s, int64_t *priority)
 }
 
 _public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority) {
+        bool rm_inotify = false, rm_inode = false;
+        struct inotify_data *new_inotify_data = NULL;
+        struct inode_data *new_inode_data = NULL;
         int r;
 
         assert_return(s, -EINVAL);
@@ -1581,7 +2141,59 @@ _public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority)
         if (s->priority == priority)
                 return 0;
 
-        if (s->type == SOURCE_SIGNAL && s->enabled != SD_EVENT_OFF) {
+        if (s->type == SOURCE_INOTIFY) {
+                struct inode_data *old_inode_data;
+
+                assert(s->inotify.inode_data);
+                old_inode_data = s->inotify.inode_data;
+
+                /* We need the original fd to change the priority. If we don't have it we can't change the priority,
+                 * anymore. Note that we close any fds when entering the next event loop iteration, i.e. for inotify
+                 * events we allow priority changes only until the first following iteration. */
+                if (old_inode_data->fd < 0)
+                        return -EOPNOTSUPP;
+
+                r = event_make_inotify_data(s->event, priority, &new_inotify_data);
+                if (r < 0)
+                        return r;
+                rm_inotify = r > 0;
+
+                r = event_make_inode_data(s->event, new_inotify_data, old_inode_data->dev, old_inode_data->ino, &new_inode_data);
+                if (r < 0)
+                        goto fail;
+                rm_inode = r > 0;
+
+                if (new_inode_data->fd < 0) {
+                        /* Duplicate the fd for the new inode object if we don't have any yet */
+                        new_inode_data->fd = fcntl(old_inode_data->fd, F_DUPFD_CLOEXEC, 3);
+                        if (new_inode_data->fd < 0) {
+                                r = -errno;
+                                goto fail;
+                        }
+
+                        LIST_PREPEND(to_close, s->event->inode_data_to_close, new_inode_data);
+                }
+
+                /* Move the event source to the new inode data structure */
+                LIST_REMOVE(inotify.by_inode_data, old_inode_data->event_sources, s);
+                LIST_PREPEND(inotify.by_inode_data, new_inode_data->event_sources, s);
+                s->inotify.inode_data = new_inode_data;
+
+                /* Now create the new watch */
+                r = inode_data_realize_watch(s->event, new_inode_data);
+                if (r < 0) {
+                        /* Move it back */
+                        LIST_REMOVE(inotify.by_inode_data, new_inode_data->event_sources, s);
+                        LIST_PREPEND(inotify.by_inode_data, old_inode_data->event_sources, s);
+                        s->inotify.inode_data = old_inode_data;
+                        goto fail;
+                }
+
+                s->priority = priority;
+
+                event_gc_inode_data(s->event, old_inode_data);
+
+        } else if (s->type == SOURCE_SIGNAL && s->enabled != SD_EVENT_OFF) {
                 struct signal_data *old, *d;
 
                 /* Move us from the signalfd belonging to the old
@@ -1611,6 +2223,15 @@ _public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority)
                 prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index);
 
         return 0;
+
+fail:
+        if (rm_inode)
+                event_free_inode_data(s->event, new_inode_data);
+
+        if (rm_inotify)
+                event_free_inotify_data(s->event, new_inotify_data);
+
+        return r;
 }
 
 _public_ int sd_event_source_get_enabled(sd_event_source *s, int *m) {
@@ -1639,6 +2260,13 @@ _public_ int sd_event_source_set_enabled(sd_event_source *s, int m) {
 
         if (m == SD_EVENT_OFF) {
 
+                /* Unset the pending flag when this event source is disabled */
+                if (!IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) {
+                        r = source_set_pending(s, false);
+                        if (r < 0)
+                                return r;
+                }
+
                 switch (s->type) {
 
                 case SOURCE_IO:
@@ -1685,6 +2313,7 @@ _public_ int sd_event_source_set_enabled(sd_event_source *s, int m) {
 
                 case SOURCE_DEFER:
                 case SOURCE_POST:
+                case SOURCE_INOTIFY:
                         s->enabled = m;
                         break;
 
@@ -1693,6 +2322,14 @@ _public_ int sd_event_source_set_enabled(sd_event_source *s, int m) {
                 }
 
         } else {
+
+                /* Unset the pending flag when this event source is enabled */
+                if (s->enabled == SD_EVENT_OFF && !IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) {
+                        r = source_set_pending(s, false);
+                        if (r < 0)
+                                return r;
+                }
+
                 switch (s->type) {
 
                 case SOURCE_IO:
@@ -1757,6 +2394,7 @@ _public_ int sd_event_source_set_enabled(sd_event_source *s, int m) {
 
                 case SOURCE_DEFER:
                 case SOURCE_POST:
+                case SOURCE_INOTIFY:
                         s->enabled = m;
                         break;
 
@@ -1786,15 +2424,18 @@ _public_ int sd_event_source_get_time(sd_event_source *s, uint64_t *usec) {
 
 _public_ int sd_event_source_set_time(sd_event_source *s, uint64_t usec) {
         struct clock_data *d;
+        int r;
 
         assert_return(s, -EINVAL);
         assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(s->event), -ECHILD);
 
-        s->time.next = usec;
+        r = source_set_pending(s, false);
+        if (r < 0)
+                return r;
 
-        source_set_pending(s, false);
+        s->time.next = usec;
 
         d = event_get_clock_data(s->event, s->type);
         assert(d);
@@ -1818,6 +2459,7 @@ _public_ int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *use
 
 _public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec) {
         struct clock_data *d;
+        int r;
 
         assert_return(s, -EINVAL);
         assert_return(usec != (uint64_t) -1, -EINVAL);
@@ -1825,13 +2467,15 @@ _public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(s->event), -ECHILD);
 
+        r = source_set_pending(s, false);
+        if (r < 0)
+                return r;
+
         if (usec == 0)
                 usec = DEFAULT_ACCURACY_USEC;
 
         s->time.accuracy = usec;
 
-        source_set_pending(s, false);
-
         d = event_get_clock_data(s->event, s->type);
         assert(d);
 
@@ -1861,6 +2505,16 @@ _public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) {
         return 0;
 }
 
+_public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *mask) {
+        assert_return(s, -EINVAL);
+        assert_return(mask, -EINVAL);
+        assert_return(s->type == SOURCE_INOTIFY, -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        *mask = s->inotify.mask;
+        return 0;
+}
+
 _public_ int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback) {
         int r;
 
@@ -2194,6 +2848,7 @@ static int process_signal(sd_event *e, struct signal_data *d, uint32_t events) {
         int r;
 
         assert(e);
+        assert(d);
         assert_return(events == EPOLLIN, -EIO);
 
         /* If there's a signal queued on this priority and SIGCHLD is
@@ -2250,6 +2905,160 @@ static int process_signal(sd_event *e, struct signal_data *d, uint32_t events) {
         }
 }
 
+static int event_inotify_data_read(sd_event *e, struct inotify_data *d, uint32_t revents) {
+        ssize_t n;
+
+        assert(e);
+        assert(d);
+
+        assert_return(revents == EPOLLIN, -EIO);
+
+        /* If there's already an event source pending for this priority, don't read another */
+        if (d->n_pending > 0)
+                return 0;
+
+        /* Is the read buffer non-empty? If so, let's not read more */
+        if (d->buffer_filled > 0)
+                return 0;
+
+        n = read(d->fd, &d->buffer, sizeof(d->buffer));
+        if (n < 0) {
+                if (IN_SET(errno, EAGAIN, EINTR))
+                        return 0;
+
+                return -errno;
+        }
+
+        assert(n > 0);
+        d->buffer_filled = (size_t) n;
+        LIST_PREPEND(buffered, e->inotify_data_buffered, d);
+
+        return 1;
+}
+
+static void event_inotify_data_drop(sd_event *e, struct inotify_data *d, size_t sz) {
+        assert(e);
+        assert(d);
+        assert(sz <= d->buffer_filled);
+
+        if (sz == 0)
+                return;
+
+        /* Move the rest to the buffer to the front, in order to get things properly aligned again */
+        memmove(d->buffer.raw, d->buffer.raw + sz, d->buffer_filled - sz);
+        d->buffer_filled -= sz;
+
+        if (d->buffer_filled == 0)
+                LIST_REMOVE(buffered, e->inotify_data_buffered, d);
+}
+
+static int event_inotify_data_process(sd_event *e, struct inotify_data *d) {
+        int r;
+
+        assert(e);
+        assert(d);
+
+        /* If there's already an event source pending for this priority, don't read another */
+        if (d->n_pending > 0)
+                return 0;
+
+        while (d->buffer_filled > 0) {
+                size_t sz;
+
+                /* Let's validate that the event structures are complete */
+                if (d->buffer_filled < offsetof(struct inotify_event, name))
+                        return -EIO;
+
+                sz = offsetof(struct inotify_event, name) + d->buffer.ev.len;
+                if (d->buffer_filled < sz)
+                        return -EIO;
+
+                if (d->buffer.ev.mask & IN_Q_OVERFLOW) {
+                        struct inode_data *inode_data;
+                        Iterator i;
+
+                        /* The queue overran, let's pass this event to all event sources connected to this inotify
+                         * object */
+
+                        HASHMAP_FOREACH(inode_data, d->inodes, i) {
+                                sd_event_source *s;
+
+                                LIST_FOREACH(inotify.by_inode_data, s, inode_data->event_sources) {
+
+                                        if (s->enabled == SD_EVENT_OFF)
+                                                continue;
+
+                                        r = source_set_pending(s, true);
+                                        if (r < 0)
+                                                return r;
+                                }
+                        }
+                } else {
+                        struct inode_data *inode_data;
+                        sd_event_source *s;
+
+                        /* Find the inode object for this watch descriptor. If IN_IGNORED is set we also remove it from
+                         * our watch descriptor table. */
+                        if (d->buffer.ev.mask & IN_IGNORED) {
+
+                                inode_data = hashmap_remove(d->wd, INT_TO_PTR(d->buffer.ev.wd));
+                                if (!inode_data) {
+                                        event_inotify_data_drop(e, d, sz);
+                                        continue;
+                                }
+
+                                /* The watch descriptor was removed by the kernel, let's drop it here too */
+                                inode_data->wd = -1;
+                        } else {
+                                inode_data = hashmap_get(d->wd, INT_TO_PTR(d->buffer.ev.wd));
+                                if (!inode_data) {
+                                        event_inotify_data_drop(e, d, sz);
+                                        continue;
+                                }
+                        }
+
+                        /* Trigger all event sources that are interested in these events. Also trigger all event
+                         * sources if IN_IGNORED or IN_UNMOUNT is set. */
+                        LIST_FOREACH(inotify.by_inode_data, s, inode_data->event_sources) {
+
+                                if (s->enabled == SD_EVENT_OFF)
+                                        continue;
+
+                                if ((d->buffer.ev.mask & (IN_IGNORED|IN_UNMOUNT)) == 0 &&
+                                    (s->inotify.mask & d->buffer.ev.mask & IN_ALL_EVENTS) == 0)
+                                        continue;
+
+                                r = source_set_pending(s, true);
+                                if (r < 0)
+                                        return r;
+                        }
+                }
+
+                /* Something pending now? If so, let's finish, otherwise let's read more. */
+                if (d->n_pending > 0)
+                        return 1;
+        }
+
+        return 0;
+}
+
+static int process_inotify(sd_event *e) {
+        struct inotify_data *d;
+        int r, done = 0;
+
+        assert(e);
+
+        LIST_FOREACH(buffered, d, e->inotify_data_buffered) {
+                r = event_inotify_data_process(e, d);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        done ++;
+        }
+
+        return done;
+}
+
 static int source_dispatch(sd_event_source *s) {
         EventSourceType saved_type;
         int r = 0;
@@ -2319,7 +3128,7 @@ static int source_dispatch(sd_event_source *s) {
 
                 /* Now, reap the PID for good. */
                 if (zombie)
-                        waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|WEXITED);
+                        (void) waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|WEXITED);
 
                 break;
         }
@@ -2336,6 +3145,28 @@ static int source_dispatch(sd_event_source *s) {
                 r = s->exit.callback(s, s->userdata);
                 break;
 
+        case SOURCE_INOTIFY: {
+                struct sd_event *e = s->event;
+                struct inotify_data *d;
+                size_t sz;
+
+                assert(s->inotify.inode_data);
+                assert_se(d = s->inotify.inode_data->inotify_data);
+
+                assert(d->buffer_filled >= offsetof(struct inotify_event, name));
+                sz = offsetof(struct inotify_event, name) + d->buffer.ev.len;
+                assert(d->buffer_filled >= sz);
+
+                r = s->inotify.callback(s, &d->buffer.ev, s->userdata);
+
+                /* When no event is pending anymore on this inotify object, then let's drop the event from the
+                 * buffer. */
+                if (d->n_pending == 0)
+                        event_inotify_data_drop(e, d, sz);
+
+                break;
+        }
+
         case SOURCE_WATCHDOG:
         case _SOURCE_EVENT_SOURCE_TYPE_MAX:
         case _SOURCE_EVENT_SOURCE_TYPE_INVALID:
@@ -2470,6 +3301,25 @@ static int process_watchdog(sd_event *e) {
         return arm_watchdog(e);
 }
 
+static void event_close_inode_data_fds(sd_event *e) {
+        struct inode_data *d;
+
+        assert(e);
+
+        /* Close the fds pointing to the inodes to watch now. We need to close them as they might otherwise pin
+         * filesystems. But we can't close them right-away as we need them as long as the user still wants to make
+         * adjustments to the even source, such as changing the priority (which requires us to remove and readd a watch
+         * for the inode). Hence, let's close them when entering the first iteration after they were added, as a
+         * compromise. */
+
+        while ((d = e->inode_data_to_close)) {
+                assert(d->fd >= 0);
+                d->fd = safe_close(d->fd);
+
+                LIST_REMOVE(to_close, e->inode_data_to_close, d);
+        }
+}
+
 _public_ int sd_event_prepare(sd_event *e) {
         int r;
 
@@ -2510,6 +3360,8 @@ _public_ int sd_event_prepare(sd_event *e) {
         if (r < 0)
                 return r;
 
+        event_close_inode_data_fds(e);
+
         if (event_next_pending(e) || e->need_process_child)
                 goto pending;
 
@@ -2545,6 +3397,10 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
         ev_queue_max = MAX(e->n_sources, 1u);
         ev_queue = newa(struct epoll_event, ev_queue_max);
 
+        /* If we still have inotify data buffered, then query the other fds, but don't wait on it */
+        if (e->inotify_data_buffered)
+                timeout = 0;
+
         m = epoll_wait(e->epoll_fd, ev_queue, ev_queue_max,
                        timeout == (uint64_t) -1 ? -1 : (int) ((timeout + USEC_PER_MSEC - 1) / USEC_PER_MSEC));
         if (m < 0) {
@@ -2582,6 +3438,10 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
                                 r = process_signal(e, ev_queue[i].data.ptr, ev_queue[i].events);
                                 break;
 
+                        case WAKEUP_INOTIFY_DATA:
+                                r = event_inotify_data_read(e, ev_queue[i].data.ptr, ev_queue[i].events);
+                                break;
+
                         default:
                                 assert_not_reached("Invalid wake-up pointer");
                         }
@@ -2620,6 +3480,10 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
                         goto finish;
         }
 
+        r = process_inotify(e);
+        if (r < 0)
+                goto finish;
+
         if (event_next_pending(e)) {
                 e->state = SD_EVENT_PENDING;
 
@@ -2858,7 +3722,7 @@ _public_ int sd_event_set_watchdog(sd_event *e, int b) {
                 return e->watchdog;
 
         if (b) {
-                struct epoll_event ev = {};
+                struct epoll_event ev;
 
                 r = sd_watchdog_enabled(false, &e->watchdog_period);
                 if (r <= 0)
@@ -2876,8 +3740,10 @@ _public_ int sd_event_set_watchdog(sd_event *e, int b) {
                 if (r < 0)
                         goto fail;
 
-                ev.events = EPOLLIN;
-                ev.data.ptr = INT_TO_PTR(SOURCE_WATCHDOG);
+                ev = (struct epoll_event) {
+                        .events = EPOLLIN,
+                        .data.ptr = INT_TO_PTR(SOURCE_WATCHDOG),
+                };
 
                 r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, e->watchdog_fd, &ev);
                 if (r < 0) {
@@ -2916,4 +3782,20 @@ _public_ int sd_event_get_iteration(sd_event *e, uint64_t *ret) {
         *ret = e->iteration;
         return 0;
 }
+
+_public_ int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback) {
+        assert_return(s, -EINVAL);
+
+        s->destroy_callback = callback;
+        return 0;
+}
+
+_public_ int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret) {
+        assert_return(s, -EINVAL);
+
+        if (ret)
+                *ret = s->destroy_callback;
+
+        return !!s->destroy_callback;
+}
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.c b/src/systemd/src/libsystemd/sd-id128/id128-util.c
index 26eef0fd..03a38699 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.c
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.c
@@ -1,8 +1,5 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright 2016 Lennart Poettering
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.h b/src/systemd/src/libsystemd/sd-id128/id128-util.h
index 9aeb27a2..f0b4eca5 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.h
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.h
@@ -2,9 +2,6 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright 2016 Lennart Poettering
 ***/
 
 #include <stdbool.h>
diff --git a/src/systemd/src/libsystemd/sd-id128/sd-id128.c b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
index de0ae600..8fe8ff43 100644
--- a/src/systemd/src/libsystemd/sd-id128/sd-id128.c
+++ b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
@@ -1,8 +1,5 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright 2011 Lennart Poettering
 ***/
 
 #include "nm-sd-adapt.h"
@@ -102,7 +99,7 @@ _public_ int sd_id128_get_machine(sd_id128_t *ret) {
                         return r;
 
                 if (sd_id128_is_null(saved_machine_id))
-                        return -EINVAL;
+                        return -ENOMEDIUM;
         }
 
         *ret = saved_machine_id;
diff --git a/src/systemd/src/shared/dns-domain.c b/src/systemd/src/shared/dns-domain.c
index 3f41be62..ab034043 100644
--- a/src/systemd/src/shared/dns-domain.c
+++ b/src/systemd/src/shared/dns-domain.c
@@ -1,8 +1,5 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright 2014 Lennart Poettering
  ***/
 
 #include "nm-sd-adapt.h"
@@ -360,10 +357,7 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
         if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX)
                 return -EINVAL;
 
-        if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1)
-                return 0;
-
-        if (memcmp(encoded, IDNA_ACE_PREFIX, sizeof(IDNA_ACE_PREFIX) -1) != 0)
+        if (!memory_startswith(encoded, encoded_size, IDNA_ACE_PREFIX))
                 return 0;
 
         input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
diff --git a/src/systemd/src/shared/dns-domain.h b/src/systemd/src/shared/dns-domain.h
index 47bd5357..8781eec6 100644
--- a/src/systemd/src/shared/dns-domain.h
+++ b/src/systemd/src/shared/dns-domain.h
@@ -2,9 +2,6 @@
 #pragma once
 
 /***
-  This file is part of systemd.
-
-  Copyright 2014 Lennart Poettering
  ***/
 
 #include <errno.h>
@@ -25,7 +22,7 @@
 #define DNS_HOSTNAME_MAX 253
 
 /* Maximum length of a full hostname, on the wire, including the final NUL byte */
-#define DNS_WIRE_FOMAT_HOSTNAME_MAX 255
+#define DNS_WIRE_FORMAT_HOSTNAME_MAX 255
 
 /* Maximum number of labels per valid hostname */
 #define DNS_N_LABELS_MAX 127
diff --git a/src/systemd/src/systemd/_sd-common.h b/src/systemd/src/systemd/_sd-common.h
index b4400e7b..7b54d179 100644
--- a/src/systemd/src/systemd/_sd-common.h
+++ b/src/systemd/src/systemd/_sd-common.h
@@ -3,9 +3,6 @@
 #define foosdcommonhfoo
 
 /***
-  This file is part of systemd.
-
-  Copyright 2013 Lennart Poettering
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
diff --git a/src/systemd/src/systemd/sd-dhcp-client.h b/src/systemd/src/systemd/sd-dhcp-client.h
index fd0a5693..931b0e89 100644
--- a/src/systemd/src/systemd/sd-dhcp-client.h
+++ b/src/systemd/src/systemd/sd-dhcp-client.h
@@ -3,9 +3,7 @@
 #define foosddhcpclienthfoo
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2013 Intel Corporation. All rights reserved.
+  Copyright © 2013 Intel Corporation. All rights reserved.
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
diff --git a/src/systemd/src/systemd/sd-dhcp-lease.h b/src/systemd/src/systemd/sd-dhcp-lease.h
index 3cc7fcab..16c05661 100644
--- a/src/systemd/src/systemd/sd-dhcp-lease.h
+++ b/src/systemd/src/systemd/sd-dhcp-lease.h
@@ -3,10 +3,8 @@
 #define foosddhcpleasehfoo
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2013 Intel Corporation. All rights reserved.
-  Copyright (C) 2014 Tom Gundersen
+  Copyright © 2013 Intel Corporation. All rights reserved.
+  Copyright © 2014 Tom Gundersen
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
diff --git a/src/systemd/src/systemd/sd-dhcp6-client.h b/src/systemd/src/systemd/sd-dhcp6-client.h
index cadb32a0..4f3b2d9e 100644
--- a/src/systemd/src/systemd/sd-dhcp6-client.h
+++ b/src/systemd/src/systemd/sd-dhcp6-client.h
@@ -3,9 +3,7 @@
 #define foosddhcp6clienthfoo
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Intel Corporation. All rights reserved.
+  Copyright © 2014 Intel Corporation. All rights reserved.
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
diff --git a/src/systemd/src/systemd/sd-dhcp6-lease.h b/src/systemd/src/systemd/sd-dhcp6-lease.h
index 22a5f8ce..a673de5e 100644
--- a/src/systemd/src/systemd/sd-dhcp6-lease.h
+++ b/src/systemd/src/systemd/sd-dhcp6-lease.h
@@ -3,10 +3,8 @@
 #define foosddhcp6leasehfoo
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Tom Gundersen
-  Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
+  Copyright © 2014 Tom Gundersen
+  Copyright © 2014-2015 Intel Corporation. All rights reserved.
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
diff --git a/src/systemd/src/systemd/sd-event.h b/src/systemd/src/systemd/sd-event.h
index ec4b7bcf..7fcae4ac 100644
--- a/src/systemd/src/systemd/sd-event.h
+++ b/src/systemd/src/systemd/sd-event.h
@@ -3,9 +3,6 @@
 #define foosdeventhfoo
 
 /***
-  This file is part of systemd.
-
-  Copyright 2013 Lennart Poettering
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
@@ -24,6 +21,7 @@
 #include <inttypes.h>
 #include <signal.h>
 #include <sys/epoll.h>
+#include <sys/inotify.h>
 #include <sys/signalfd.h>
 #include <sys/types.h>
 #include <time.h>
@@ -78,6 +76,8 @@ typedef int (*sd_event_child_handler_t)(sd_event_source *s, const siginfo_t *si,
 #else
 typedef void* sd_event_child_handler_t;
 #endif
+typedef int (*sd_event_inotify_handler_t)(sd_event_source *s, const struct inotify_event *event, void *userdata);
+typedef void (*sd_event_destroy_t)(void *userdata);
 
 int sd_event_default(sd_event **e);
 
@@ -89,6 +89,7 @@ int sd_event_add_io(sd_event *e, sd_event_source **s, int fd, uint32_t events, s
 int sd_event_add_time(sd_event *e, sd_event_source **s, clockid_t clock, uint64_t usec, uint64_t accuracy, sd_event_time_handler_t callback, void *userdata);
 int sd_event_add_signal(sd_event *e, sd_event_source **s, int sig, sd_event_signal_handler_t callback, void *userdata);
 int sd_event_add_child(sd_event *e, sd_event_source **s, pid_t pid, int options, sd_event_child_handler_t callback, void *userdata);
+int sd_event_add_inotify(sd_event *e, sd_event_source **s, const char *path, uint32_t mask, sd_event_inotify_handler_t callback, void *userdata);
 int sd_event_add_defer(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
 int sd_event_add_post(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
 int sd_event_add_exit(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
@@ -139,6 +140,9 @@ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec);
 int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock);
 int sd_event_source_get_signal(sd_event_source *s);
 int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid);
+int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret);
+int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback);
+int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret);
 
 /* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref);
diff --git a/src/systemd/src/systemd/sd-id128.h b/src/systemd/src/systemd/sd-id128.h
index 67fc5956..b24fd06f 100644
--- a/src/systemd/src/systemd/sd-id128.h
+++ b/src/systemd/src/systemd/sd-id128.h
@@ -3,9 +3,6 @@
 #define foosdid128hfoo
 
 /***
-  This file is part of systemd.
-
-  Copyright 2011 Lennart Poettering
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
diff --git a/src/systemd/src/systemd/sd-ipv4acd.h b/src/systemd/src/systemd/sd-ipv4acd.h
index 677ae3b2..259db263 100644
--- a/src/systemd/src/systemd/sd-ipv4acd.h
+++ b/src/systemd/src/systemd/sd-ipv4acd.h
@@ -3,10 +3,8 @@
 #define foosdipv4acdfoo
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Axis Communications AB. All rights reserved.
-  Copyright (C) 2015 Tom Gundersen
+  Copyright © 2014 Axis Communications AB. All rights reserved.
+  Copyright © 2015 Tom Gundersen
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
diff --git a/src/systemd/src/systemd/sd-ipv4ll.h b/src/systemd/src/systemd/sd-ipv4ll.h
index c330b0ae..71bd4cfe 100644
--- a/src/systemd/src/systemd/sd-ipv4ll.h
+++ b/src/systemd/src/systemd/sd-ipv4ll.h
@@ -3,9 +3,7 @@
 #define foosdipv4llfoo
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Axis Communications AB. All rights reserved.
+  Copyright © 2014 Axis Communications AB. All rights reserved.
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
diff --git a/src/systemd/src/systemd/sd-lldp.h b/src/systemd/src/systemd/sd-lldp.h
index 0a76fa63..11a2119a 100644
--- a/src/systemd/src/systemd/sd-lldp.h
+++ b/src/systemd/src/systemd/sd-lldp.h
@@ -3,10 +3,8 @@
 #define foosdlldphfoo
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Tom Gundersen
-  Copyright (C) 2014 Susant Sahani
+  Copyright © 2014 Tom Gundersen
+  Copyright © 2014 Susant Sahani
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
diff --git a/src/systemd/src/systemd/sd-ndisc.h b/src/systemd/src/systemd/sd-ndisc.h
index 15211450..6b6249ca 100644
--- a/src/systemd/src/systemd/sd-ndisc.h
+++ b/src/systemd/src/systemd/sd-ndisc.h
@@ -3,9 +3,7 @@
 #define foosdndiscfoo
 
 /***
-  This file is part of systemd.
-
-  Copyright (C) 2014 Intel Corporation. All rights reserved.
+  Copyright © 2014 Intel Corporation. All rights reserved.
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by