about summary refs log tree commit diff
path: root/shared
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-06-28 18:58:14 +0200
committerMichael Biebl <biebl@debian.org>2020-06-28 18:58:14 +0200
commita54ac63bbf9b2c71026ac9028a8ffaf186cf3c82 (patch)
tree6a32883bd916c4096357b35298beff6db8bcd0b5 /shared
parent45e8e1149027529194982212c804c0468aa01d98 (diff)
New upstream version 1.25.90 upstream/1.25.90
Diffstat (limited to 'shared')
-rw-r--r--shared/meson.build15
-rw-r--r--shared/n-acd/src/n-acd.c21
-rw-r--r--shared/n-dhcp4/src/n-dhcp4-c-connection.c67
-rw-r--r--shared/n-dhcp4/src/n-dhcp4-c-probe.c6
-rw-r--r--shared/n-dhcp4/src/n-dhcp4-client.c150
-rw-r--r--shared/n-dhcp4/src/n-dhcp4-private.h77
-rw-r--r--shared/n-dhcp4/src/n-dhcp4.h15
-rw-r--r--shared/nm-glib-aux/nm-glib.h12
-rw-r--r--shared/nm-glib-aux/nm-macros-internal.h46
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.c190
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.h181
-rw-r--r--shared/nm-keyfile/nm-keyfile-internal.h180
-rw-r--r--shared/nm-keyfile/nm-keyfile-utils.c635
-rw-r--r--shared/nm-keyfile/nm-keyfile-utils.h85
-rw-r--r--shared/nm-keyfile/nm-keyfile.c4068
-rw-r--r--shared/nm-libnm-aux/nm-libnm-aux.c146
-rw-r--r--shared/nm-libnm-aux/nm-libnm-aux.h24
-rw-r--r--shared/nm-libnm-core-aux/nm-dispatcher-api.h45
-rw-r--r--shared/nm-libnm-core-aux/nm-libnm-core-aux.c358
-rw-r--r--shared/nm-libnm-core-aux/nm-libnm-core-aux.h39
-rw-r--r--shared/nm-libnm-core-intern/nm-auth-subject.c446
-rw-r--r--shared/nm-libnm-core-intern/nm-auth-subject.h56
-rw-r--r--shared/nm-libnm-core-intern/nm-common-macros.h59
-rw-r--r--shared/nm-libnm-core-intern/nm-ethtool-utils.c209
-rw-r--r--shared/nm-libnm-core-intern/nm-ethtool-utils.h105
-rw-r--r--shared/nm-libnm-core-intern/nm-libnm-core-utils.c180
-rw-r--r--shared/nm-libnm-core-intern/nm-libnm-core-utils.h115
-rw-r--r--shared/nm-test-libnm-utils.h5
-rw-r--r--shared/nm-utils/nm-test-utils.h43
-rw-r--r--shared/nm-version-macros.h5
-rw-r--r--shared/nm-version-macros.h.in1
-rw-r--r--shared/systemd/nm-sd-utils-shared.c43
-rw-r--r--shared/systemd/nm-sd-utils-shared.h4
-rw-r--r--shared/systemd/sd-adapt-shared/blockdev-util.h3
-rw-r--r--shared/systemd/src/basic/cgroup-util.h25
-rw-r--r--shared/systemd/src/basic/errno-util.h8
-rw-r--r--shared/systemd/src/basic/escape.c18
-rw-r--r--shared/systemd/src/basic/escape.h9
-rw-r--r--shared/systemd/src/basic/fd-util.c31
-rw-r--r--shared/systemd/src/basic/fileio.c83
-rw-r--r--shared/systemd/src/basic/fileio.h8
-rw-r--r--shared/systemd/src/basic/fs-util.c189
-rw-r--r--shared/systemd/src/basic/fs-util.h13
-rw-r--r--shared/systemd/src/basic/hash-funcs.c6
-rw-r--r--shared/systemd/src/basic/hash-funcs.h2
-rw-r--r--shared/systemd/src/basic/hashmap.c105
-rw-r--r--shared/systemd/src/basic/hashmap.h179
-rw-r--r--shared/systemd/src/basic/hexdecoct.c21
-rw-r--r--shared/systemd/src/basic/hostname-util.c48
-rw-r--r--shared/systemd/src/basic/hostname-util.h1
-rw-r--r--shared/systemd/src/basic/in-addr-util.c74
-rw-r--r--shared/systemd/src/basic/in-addr-util.h1
-rw-r--r--shared/systemd/src/basic/io-util.c30
-rw-r--r--shared/systemd/src/basic/macro.h35
-rw-r--r--shared/systemd/src/basic/memory-util.h2
-rw-r--r--shared/systemd/src/basic/missing_random.h4
-rw-r--r--shared/systemd/src/basic/missing_socket.h5
-rw-r--r--shared/systemd/src/basic/parse-util.c181
-rw-r--r--shared/systemd/src/basic/parse-util.h18
-rw-r--r--shared/systemd/src/basic/path-util.c2
-rw-r--r--shared/systemd/src/basic/process-util.c151
-rw-r--r--shared/systemd/src/basic/process-util.h20
-rw-r--r--shared/systemd/src/basic/random-util.c25
-rw-r--r--shared/systemd/src/basic/random-util.h1
-rw-r--r--shared/systemd/src/basic/set.h52
-rw-r--r--shared/systemd/src/basic/socket-util.c94
-rw-r--r--shared/systemd/src/basic/socket-util.h23
-rw-r--r--shared/systemd/src/basic/sort-util.h4
-rw-r--r--shared/systemd/src/basic/stat-util.c50
-rw-r--r--shared/systemd/src/basic/stat-util.h4
-rw-r--r--shared/systemd/src/basic/string-util.c17
-rw-r--r--shared/systemd/src/basic/string-util.h1
-rw-r--r--shared/systemd/src/basic/strv.c20
-rw-r--r--shared/systemd/src/basic/strv.h23
-rw-r--r--shared/systemd/src/basic/utf8.c4
-rw-r--r--shared/systemd/src/shared/web-util.c34
76 files changed, 1903 insertions, 7352 deletions
diff --git a/shared/meson.build b/shared/meson.build
index 9a8768f5..48880ec4 100644
--- a/shared/meson.build
+++ b/shared/meson.build
@@ -107,27 +107,12 @@ nm_version_macro_header = configure_file(
   configuration: data_conf,
 )
 
-nm_ethtool_utils_source = files('nm-libnm-core-intern/nm-ethtool-utils.c')
-
-nm_libnm_core_aux_source = files('nm-libnm-core-aux/nm-libnm-core-aux.c')
-
-nm_libnm_core_utils_source = files('nm-libnm-core-intern/nm-libnm-core-utils.c')
-
-nm_keyfile_source = files(
-  'nm-keyfile/nm-keyfile-utils.c',
-  'nm-keyfile/nm-keyfile.c',
-)
-
-nm_libnm_aux_source = files('nm-libnm-aux/nm-libnm-aux.c')
-
 nm_meta_setting_source = files('nm-meta-setting.c')
 
 nm_test_utils_impl_source = files('nm-test-utils-impl.c')
 
 nm_vpn_plugin_utils_source = files('nm-utils/nm-vpn-plugin-utils.c')
 
-nm_auth_subject_source = files('nm-libnm-core-intern/nm-auth-subject.c')
-
 c_flags = [
   '-DG_LOG_DOMAIN="@0@"'.format(libnm_name),
   '-DNETWORKMANAGER_COMPILATION=0',
diff --git a/shared/n-acd/src/n-acd.c b/shared/n-acd/src/n-acd.c
index d434d1d1..af3328c1 100644
--- a/shared/n-acd/src/n-acd.c
+++ b/shared/n-acd/src/n-acd.c
@@ -328,6 +328,7 @@ int n_acd_ensure_bpf_map_space(NAcd *acd) {
 _c_public_ int n_acd_new(NAcd **acdp, NAcdConfig *config) {
         _c_cleanup_(n_acd_unrefp) NAcd *acd = NULL;
         _c_cleanup_(c_closep) int fd_bpf_prog = -1;
+        struct epoll_event eevent;
         int r;
 
         if (config->ifindex <= 0 ||
@@ -370,19 +371,19 @@ _c_public_ int n_acd_new(NAcd **acdp, NAcdConfig *config) {
         if (r)
                 return r;
 
-        r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, acd->timer.fd,
-                      &(struct epoll_event){
-                              .events = EPOLLIN,
-                              .data.u32 = N_ACD_EPOLL_TIMER,
-                      });
+        eevent = (struct epoll_event){
+                .events = EPOLLIN,
+                .data.u32 = N_ACD_EPOLL_TIMER,
+        };
+        r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, acd->timer.fd, &eevent);
         if (r < 0)
                 return -c_errno();
 
-        r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, acd->fd_socket,
-                      &(struct epoll_event){
-                              .events = EPOLLIN,
-                              .data.u32 = N_ACD_EPOLL_SOCKET,
-                      });
+        eevent = (struct epoll_event){
+                .events = EPOLLIN,
+                .data.u32 = N_ACD_EPOLL_SOCKET,
+        };
+        r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, acd->fd_socket, &eevent);
         if (r < 0)
                 return -c_errno();
 
diff --git a/shared/n-dhcp4/src/n-dhcp4-c-connection.c b/shared/n-dhcp4/src/n-dhcp4-c-connection.c
index 30514e28..8c32a984 100644
--- a/shared/n-dhcp4/src/n-dhcp4-c-connection.c
+++ b/shared/n-dhcp4/src/n-dhcp4-c-connection.c
@@ -23,6 +23,7 @@
  * @connection:                 connection to operate on
  * @client_config:              client configuration to use
  * @probe_config:               client probe configuration to use
+ * @log_queue:                  the log queue for logging events
  * @fd_epoll:                   epoll context to attach to, or -1
  *
  * This initializes a new client connection using the configuration given in
@@ -47,11 +48,13 @@
 int n_dhcp4_c_connection_init(NDhcp4CConnection *connection,
                               NDhcp4ClientConfig *client_config,
                               NDhcp4ClientProbeConfig *probe_config,
+                              NDhcp4LogQueue *log_queue,
                               int fd_epoll) {
         *connection = (NDhcp4CConnection)N_DHCP4_C_CONNECTION_NULL(*connection);
         connection->client_config = client_config;
         connection->probe_config = probe_config;
         connection->fd_epoll = fd_epoll;
+        connection->log_queue = log_queue;
 
         /*
          * We explicitly allow initializing connections with an invalid
@@ -1066,25 +1069,27 @@ static int n_dhcp4_c_connection_send_request(NDhcp4CConnection *connection,
         }
 
         if (request->userdata.client_addr == INADDR_ANY) {
-                n_dhcp4_c_log(connection->client_config, LOG_INFO,
-                              "send %s to %s%s",
-                              message_type_to_str(request->userdata.message_type),
-                              broadcast ?
-                              "255.255.255.255" :
-                              inet_ntop(AF_INET, &connection->server_ip,
-                                        server_addr, sizeof(server_addr)),
-                              error_msg);
+                n_dhcp4_log(connection->log_queue,
+                            LOG_INFO,
+                            "send %s to %s%s",
+                            message_type_to_str(request->userdata.message_type),
+                            broadcast ?
+                            "255.255.255.255" :
+                            inet_ntop(AF_INET, &connection->server_ip,
+                                      server_addr, sizeof(server_addr)),
+                            error_msg);
         } else {
-                n_dhcp4_c_log(connection->client_config, LOG_INFO,
-                              "send %s of %s to %s%s",
-                              message_type_to_str(request->userdata.message_type),
-                              inet_ntop(AF_INET, &request->userdata.client_addr,
-                                        client_addr, sizeof(client_addr)),
-                              broadcast ?
-                              "255.255.255.255" :
-                              inet_ntop(AF_INET, &connection->server_ip,
-                                        server_addr, sizeof(server_addr)),
-                              error_msg);
+                n_dhcp4_log(connection->log_queue,
+                            LOG_INFO,
+                            "send %s of %s to %s%s",
+                            message_type_to_str(request->userdata.message_type),
+                            inet_ntop(AF_INET, &request->userdata.client_addr,
+                                      client_addr, sizeof(client_addr)),
+                            broadcast ?
+                            "255.255.255.255" :
+                            inet_ntop(AF_INET, &connection->server_ip,
+                                      server_addr, sizeof(server_addr)),
+                            error_msg);
         }
 
         ++request->userdata.n_send;
@@ -1207,19 +1212,21 @@ int n_dhcp4_c_connection_dispatch_io(NDhcp4CConnection *connection,
                 return N_DHCP4_E_AGAIN;
 
         if (type == N_DHCP4_MESSAGE_OFFER || type == N_DHCP4_MESSAGE_ACK) {
-                n_dhcp4_c_log(connection->client_config, LOG_INFO,
-                              "received %s of %s from %s",
-                              message_type_to_str(type),
-                              inet_ntop(AF_INET, &message->message.header.yiaddr,
-                                        client_addr, sizeof(client_addr)),
-                              inet_ntop(AF_INET, &message->message.header.siaddr,
-                                        serv_addr, sizeof(serv_addr)));
+                n_dhcp4_log(connection->log_queue,
+                            LOG_INFO,
+                            "received %s of %s from %s",
+                            message_type_to_str(type),
+                            inet_ntop(AF_INET, &message->message.header.yiaddr,
+                                      client_addr, sizeof(client_addr)),
+                            inet_ntop(AF_INET, &message->message.header.siaddr,
+                                      serv_addr, sizeof(serv_addr)));
         } else {
-                n_dhcp4_c_log(connection->client_config, LOG_INFO,
-                              "received %s from %s",
-                              message_type_to_str(type),
-                              inet_ntop(AF_INET, &message->message.header.siaddr,
-                                        serv_addr, sizeof(serv_addr)));
+                n_dhcp4_log(connection->log_queue,
+                            LOG_INFO,
+                            "received %s from %s",
+                            message_type_to_str(type),
+                            inet_ntop(AF_INET, &message->message.header.siaddr,
+                                      serv_addr, sizeof(serv_addr)));
         }
 
         switch (type) {
diff --git a/shared/n-dhcp4/src/n-dhcp4-c-probe.c b/shared/n-dhcp4/src/n-dhcp4-c-probe.c
index 5e971298..f3d4f265 100644
--- a/shared/n-dhcp4/src/n-dhcp4-c-probe.c
+++ b/shared/n-dhcp4/src/n-dhcp4-c-probe.c
@@ -429,9 +429,13 @@ int n_dhcp4_client_probe_new(NDhcp4ClientProbe **probep,
          */
         n_dhcp4_client_probe_config_initialize_random_seed(probe->config);
 
+        /* The new probe keeps a reference on @client. So we are sure that &client->log_queue
+         * stays alive as long as we need it. */
+
         r = n_dhcp4_c_connection_init(&probe->connection,
                                       client->config,
                                       probe->config,
+                                      &client->log_queue,
                                       active ? client->fd_epoll : -1);
         if (r)
                 return r;
@@ -1091,7 +1095,7 @@ int n_dhcp4_client_probe_transition_accept(NDhcp4ClientProbe *probe, NDhcp4Incom
 
                 probe->state = N_DHCP4_CLIENT_PROBE_STATE_BOUND;
 
-                n_dhcp4_client_arm_timer (probe->client);
+                n_dhcp4_client_arm_timer(probe->client);
 
                 break;
 
diff --git a/shared/n-dhcp4/src/n-dhcp4-client.c b/shared/n-dhcp4/src/n-dhcp4-client.c
index 6b015e81..403a6932 100644
--- a/shared/n-dhcp4/src/n-dhcp4-client.c
+++ b/shared/n-dhcp4/src/n-dhcp4-client.c
@@ -94,9 +94,6 @@ int n_dhcp4_client_config_dup(NDhcp4ClientConfig *config, NDhcp4ClientConfig **d
         dup->n_mac = config->n_mac;
         memcpy(dup->broadcast_mac, config->broadcast_mac, sizeof(dup->broadcast_mac));
         dup->n_broadcast_mac = config->n_broadcast_mac;
-        dup->log.level = config->log.level;
-        dup->log.func = config->log.func;
-        dup->log.data = config->log.data;
 
         r = n_dhcp4_client_config_set_client_id(dup,
                                                 config->client_id,
@@ -227,37 +224,64 @@ _c_public_ void n_dhcp4_client_config_set_broadcast_mac(NDhcp4ClientConfig *conf
  * n_dhcp4_client_config_set_client_id() - set client-id property
  * @config:                     client configuration to operate on
  * @id:                         client id
- * @n_id:                       length of the client id in bytes
+ * @n_id:                       length of the client id in bytes. The length
+ *                              must be from 2 up to 255 bytes. Set it to 0
+ *                              to unset the client-id.
  *
  * This sets the client-id property of @config. It copies the entire client-id
  * buffer into the configuration.
+ * See RFC 2132 (section 9.14) for the format of the Client Identifier.
  *
  * Return: 0 on success, negative error code on failure.
  */
 _c_public_ int n_dhcp4_client_config_set_client_id(NDhcp4ClientConfig *config, const uint8_t *id, size_t n_id) {
         uint8_t *t;
 
+        if (n_id == 0) {
+                config->client_id = c_free(config->client_id);
+                config->n_client_id = 0;
+                return 0;
+        }
+
+        if (n_id < 2 || n_id > 255)
+                return -EINVAL;
+
         t = malloc(n_id + 1);
         if (!t)
                 return -ENOMEM;
 
+        memcpy(t, id, n_id);
+        t[n_id] = 0; /* safety 0 for debugging */
+
         free(config->client_id);
         config->client_id = t;
         config->n_client_id = n_id;
-
-        memcpy(config->client_id, id, n_id);
-        config->client_id[n_id] = 0; /* safety 0 for debugging */
-
         return 0;
 }
 
-_c_public_ void n_dhcp4_client_config_set_log_level(NDhcp4ClientConfig *config, int level) {
-        config->log.level = level;
-}
-
-_c_public_ void n_dhcp4_client_config_set_log_func(NDhcp4ClientConfig *config, NDhcp4LogFunc func, void *data) {
-        config->log.func = func;
-        config->log.data = data;
+/**
+ * n_dhcp4_client_set_log_level() - set the logging level of the client
+ * @client:                         the client to operate on
+ * @level:                          the minimum syslog logging level that is
+ *                                  still logged. For example, set to LOG_NOTICE
+ *                                  to receive logging events with level LOG_NOTICE
+ *                                  and higher. Set to -1 to disable generating
+ *                                  logging events (which is also the default).
+ *
+ * By enabling logging, you can get N_DHCP4_CLIENT_EVENT_LOG events.
+ *
+ * From the logging event you may steal the message if (and only if) "allow_steal_message"
+ * is true. In that case, clear the message field and free the message yourself.
+ *
+ * If a logging event cannot be logged due to out of memory, one message
+ * gets logged that messages are missing. Until the event with that message
+ * gets dropped, no further logging events will be queued.
+ *
+ * You may change the logging level at any time, but it does not affect
+ * logging events that are already queued.
+  */
+_c_public_ void n_dhcp4_client_set_log_level(NDhcp4Client *client, int level) {
+        client->log_queue.log_level = level;
 }
 
 /**
@@ -313,6 +337,16 @@ NDhcp4CEventNode *n_dhcp4_c_event_node_free(NDhcp4CEventNode *node) {
         case N_DHCP4_CLIENT_EVENT_EXTENDED:
                 node->event.extended.lease = n_dhcp4_client_lease_unref(node->event.extended.lease);
                 break;
+        case N_DHCP4_CLIENT_EVENT_LOG:
+                if (_c_unlikely_(!node->event.log.allow_steal_message)) {
+                        /* @node is the static node "nomem_node". It must not be
+                         * freed. */
+                        c_list_unlink(&node->client_link);
+                        node->is_public = false;
+                        return NULL;
+                }
+                node->event.log.message = c_free((char *)node->event.log.message);
+                break;
         default:
                 break;
         }
@@ -395,8 +429,11 @@ _c_public_ int n_dhcp4_client_new(NDhcp4Client **clientp, NDhcp4ClientConfig *co
 
         ev.data.u32 = N_DHCP4_CLIENT_EPOLL_TIMER;
         r = epoll_ctl(client->fd_epoll, EPOLL_CTL_ADD, client->fd_timer, &ev);
-        if (r < 0)
+        if (r < 0) {
+                close(client->fd_timer);
+                client->fd_timer = -1;
                 return -errno;
+        }
 
         *clientp = client;
         client = NULL;
@@ -487,6 +524,78 @@ int n_dhcp4_client_raise(NDhcp4Client *client, NDhcp4CEventNode **nodep, unsigne
 }
 
 /**
+ * n_dhcp4_log_queue_fmt() - add a logging event.
+ * @client:                  the NDhcp4LogQueue to operate on
+ * @level:                   the syslog logging level
+ * @fmt:                     the format string for the message
+ * @...                      printf arguments for logging
+ *
+ * Appends a logging event to the event queue if logging is
+ * enabled and the logging level sufficiently high.
+ *
+ * Queuing a logging event might fail with out of memory.
+ * In that case, a static event will be queued that informs
+ * about lost messages.
+ */
+void n_dhcp4_log_queue_fmt(NDhcp4LogQueue *log_queue,
+                           int level,
+                           const char *fmt,
+                           ...) {
+        NDhcp4CEventNode *node;
+        char *message;
+        va_list ap;
+        int r;
+
+        if (level > log_queue->log_level)
+                return;
+
+        /* Currently the logging queue is only implemented for
+         * the client. Nobody would enable logging except a
+         * client instance. */
+        c_assert(log_queue->is_client);
+
+        if (!c_list_is_empty (&log_queue->nomem_node.client_link)) {
+                /* we have the nomem_node queued after a recent out
+                 * of memory. This disables all logging messages until
+                 * the event gets popped.
+                 *
+                 * The reason is that we can only queue the nomem_node once,
+                 * so if we now try to append another event and succeed, the
+                 * user wouldn't know which messages got dropped. Instead,
+                 * just drop them all!! */
+                return;
+        }
+
+        r = n_dhcp4_c_event_node_new(&node);
+        if (r < 0)
+                goto handle_nomem;
+
+        va_start(ap, fmt);
+        r = vasprintf(&message, fmt, ap);
+        va_end(ap);
+
+        if (r < 0) {
+                n_dhcp4_c_event_node_free(node);
+                goto handle_nomem;
+        }
+
+        node->event = (NDhcp4ClientEvent) {
+                .event = N_DHCP4_CLIENT_EVENT_LOG,
+                .log = {
+                        .level = level,
+                        .message = message,
+                        .allow_steal_message = true,
+                },
+        };
+
+        c_list_link_tail(log_queue->event_list, &node->client_link);
+        return;
+
+handle_nomem:
+        c_list_link_tail(log_queue->event_list, &log_queue->nomem_node.client_link);
+}
+
+/**
  * n_dhcp4_client_arm_timer() - update timer
  * @client:                     client to operate on
  *
@@ -682,9 +791,10 @@ _c_public_ int n_dhcp4_client_dispatch(NDhcp4Client *client) {
                                 /* continue normally */
                         } else if (r) {
                                 if (r >= _N_DHCP4_E_INTERNAL) {
-                                        n_dhcp4_c_log(client->config, LOG_ERR,
-                                                      "invalid internal error code %d after dispatch",
-                                                      r);
+                                        n_dhcp4_log(&client->log_queue,
+                                                    LOG_ERR,
+                                                    "invalid internal error code %d after dispatch",
+                                                    r);
                                         return N_DHCP4_E_INTERNAL;
                                 }
                                 return r;
@@ -753,6 +863,8 @@ _c_public_ int n_dhcp4_client_dispatch(NDhcp4Client *client) {
  *                                   the client attempted several incompatible
  *                                   probes in parallel, then the most recent
  *                                   ones will be cancelled asynchronously.
+ * * N_DHCP4_CLIENT_EVENT_LOG:       A logging event if n_dhcp4_client_set_log_level()
+ *                                   is enabled.
  *
  * Return: 0 on success, negative error code on failure.
  */
diff --git a/shared/n-dhcp4/src/n-dhcp4-private.h b/shared/n-dhcp4/src/n-dhcp4-private.h
index e285d95d..90f8f0c3 100644
--- a/shared/n-dhcp4/src/n-dhcp4-private.h
+++ b/shared/n-dhcp4/src/n-dhcp4-private.h
@@ -24,6 +24,7 @@ typedef struct NDhcp4Outgoing NDhcp4Outgoing;
 typedef struct NDhcp4SConnection NDhcp4SConnection;
 typedef struct NDhcp4SConnectionIp NDhcp4SConnectionIp;
 typedef struct NDhcp4SEventNode NDhcp4SEventNode;
+typedef struct NDhcp4LogQueue NDhcp4LogQueue;
 
 /* specs */
 
@@ -242,11 +243,6 @@ struct NDhcp4ClientConfig {
         size_t n_broadcast_mac;
         uint8_t *client_id;
         size_t n_client_id;
-        struct {
-                int level;
-                NDhcp4LogFunc func;
-                void *data;
-        } log;
 };
 
 #define N_DHCP4_CLIENT_CONFIG_NULL(_x) {                                        \
@@ -290,9 +286,42 @@ struct NDhcp4CEventNode {
                 .probe_link = C_LIST_INIT((_x).probe_link),                     \
         }
 
+struct NDhcp4LogQueue {
+        CList *event_list;
+        NDhcp4CEventNode nomem_node;
+        int log_level;
+        bool is_client : 1;
+};
+
+#define N_DHCP4_LOG_QUEUE_NULL_DEFUNCT() {                                      \
+                .log_level = -1,                                                \
+                .is_client = false,                                             \
+        }
+
+#define N_DHCP4_LOG_QUEUE_NULL_CLIENT(client) {                                 \
+                .event_list = &((client).event_list),                           \
+                .log_level = -1,                                                \
+                .is_client = true,                                              \
+                .nomem_node = {                                                 \
+                        .client_link = C_LIST_INIT((client).log_queue.nomem_node.client_link), \
+                        .probe_link = C_LIST_INIT((client).log_queue.nomem_node.probe_link), \
+                        .event = {                                              \
+                                .event = N_DHCP4_CLIENT_EVENT_LOG,              \
+                                .log = {                                        \
+                                        .level = LOG_CRIT,                      \
+                                        .message = "one or more logging messages dropped due to out of memory", \
+                                        .allow_steal_message = false,           \
+                                },                                              \
+                        },                                                      \
+                        .is_public = false,                                     \
+                },                                                              \
+        }
+
 struct NDhcp4CConnection {
         NDhcp4ClientConfig *client_config;
         NDhcp4ClientProbeConfig *probe_config;
+        NDhcp4LogQueue *log_queue;
+
         int fd_epoll;
 
         unsigned int state;             /* current connection state */
@@ -324,6 +353,9 @@ struct NDhcp4Client {
         unsigned long n_refs;
         NDhcp4ClientConfig *config;
         CList event_list;
+
+        NDhcp4LogQueue log_queue;
+
         int fd_epoll;
         int fd_timer;
 
@@ -339,6 +371,7 @@ struct NDhcp4Client {
                 .event_list = C_LIST_INIT((_x).event_list),                     \
                 .fd_epoll = -1,                                                 \
                 .fd_timer = -1,                                                 \
+                .log_queue = N_DHCP4_LOG_QUEUE_NULL_CLIENT(_x),                 \
         }
 
 struct NDhcp4ClientProbe {
@@ -570,6 +603,7 @@ NDhcp4CEventNode *n_dhcp4_c_event_node_free(NDhcp4CEventNode *node);
 int n_dhcp4_c_connection_init(NDhcp4CConnection *connection,
                               NDhcp4ClientConfig *client_config,
                               NDhcp4ClientProbeConfig *probe_config,
+                              NDhcp4LogQueue *log_queue,
                               int fd_epoll);
 void n_dhcp4_c_connection_deinit(NDhcp4CConnection *connection);
 
@@ -698,19 +732,28 @@ static inline uint64_t n_dhcp4_gettime(clockid_t clock) {
         return ts.tv_sec * 1000ULL * 1000ULL * 1000ULL + ts.tv_nsec;
 }
 
-#define n_dhcp4_c_log(_config, _level, ...)                                    \
+void n_dhcp4_log_queue_fmt(NDhcp4LogQueue *log_queue,
+                           int level,
+                           const char *fmt,
+                           ...) _c_printf_(3, 4);
+
+/**
+ * n_dhcp4_log() - append a logging event
+ * @x_log_queue:   the logging event queue
+ * @x_level:       the syslog logging level for the message.
+ * @...:           the format string and arguments.
+ *
+ * Warning: this macro only evaluates the format arguments if the logging
+ * level is enabled.
+ */
+#define n_dhcp4_log(x_log_queue, x_level, ...)                                 \
         do {                                                                   \
-                const NDhcp4ClientConfig *__config = _config;                  \
-                int __level = _level;                                          \
+                NDhcp4LogQueue *const _log_queue = (x_log_queue);              \
+                const int _level = (x_level);                                  \
                                                                                \
-                if (__level <= __config->log.level && __config->log.func) {    \
-                        if (1) {                                               \
-                                _config->log.func(__level,                     \
-                                                  __config->log.data,          \
-                                                  __VA_ARGS__);                \
-                        } else {                                               \
-                                /* To have the compiler check arguments */     \
-                                printf(__VA_ARGS__);                           \
-                        }                                                      \
+                if (_level <= _log_queue->log_level) {                         \
+                        n_dhcp4_log_queue_fmt(_log_queue,                      \
+                                              _level,                          \
+                                              __VA_ARGS__);                    \
                 }                                                              \
         } while (0)
diff --git a/shared/n-dhcp4/src/n-dhcp4.h b/shared/n-dhcp4/src/n-dhcp4.h
index d3747662..81452848 100644
--- a/shared/n-dhcp4/src/n-dhcp4.h
+++ b/shared/n-dhcp4/src/n-dhcp4.h
@@ -29,8 +29,6 @@ typedef struct NDhcp4ServerEvent NDhcp4ServerEvent;
 typedef struct NDhcp4ServerIp NDhcp4ServerIp;
 typedef struct NDhcp4ServerLease NDhcp4ServerLease;
 
-typedef void (*NDhcp4LogFunc)(int level, void *data, const char *fmt, ...);
-
 #define N_DHCP4_CLIENT_START_DELAY_RFC2131 (UINT64_C(9000))
 
 enum {
@@ -63,6 +61,7 @@ enum {
         N_DHCP4_CLIENT_EVENT_EXTENDED,
         N_DHCP4_CLIENT_EVENT_EXPIRED,
         N_DHCP4_CLIENT_EVENT_CANCELLED,
+        N_DHCP4_CLIENT_EVENT_LOG,
         _N_DHCP4_CLIENT_EVENT_N,
 };
 
@@ -88,6 +87,14 @@ struct NDhcp4ClientEvent {
                 struct {
                         NDhcp4ClientProbe *probe;
                 } retracted, expired, cancelled;
+                struct {
+                        /* If allow_steal_message is true, then the user may steal the message when handling
+                         * the event. In that case, set the message field to %NULL and free it yourself
+                         * with free(). */
+                        const char *message;
+                        int level;
+                        bool allow_steal_message;
+                } log;
         };
 };
 
@@ -113,8 +120,6 @@ void n_dhcp4_client_config_set_request_broadcast(NDhcp4ClientConfig *config, boo
 void n_dhcp4_client_config_set_mac(NDhcp4ClientConfig *config, const uint8_t *mac, size_t n_mac);
 void n_dhcp4_client_config_set_broadcast_mac(NDhcp4ClientConfig *config, const uint8_t *mac, size_t n_mac);
 int n_dhcp4_client_config_set_client_id(NDhcp4ClientConfig *config, const uint8_t *id, size_t n_id);
-void n_dhcp4_client_config_set_log_level(NDhcp4ClientConfig *config, int level);
-void n_dhcp4_client_config_set_log_func(NDhcp4ClientConfig *config, NDhcp4LogFunc func, void *data);
 
 /* client-probe configs */
 
@@ -141,6 +146,8 @@ void n_dhcp4_client_get_fd(NDhcp4Client *client, int *fdp);
 int n_dhcp4_client_dispatch(NDhcp4Client *client);
 int n_dhcp4_client_pop_event(NDhcp4Client *client, NDhcp4ClientEvent **eventp);
 
+void n_dhcp4_client_set_log_level(NDhcp4Client *client, int level);
+
 int n_dhcp4_client_update_mtu(NDhcp4Client *client, uint16_t mtu);
 
 int n_dhcp4_client_probe(NDhcp4Client *client,
diff --git a/shared/nm-glib-aux/nm-glib.h b/shared/nm-glib-aux/nm-glib.h
index 7a5b8edd..2df51795 100644
--- a/shared/nm-glib-aux/nm-glib.h
+++ b/shared/nm-glib-aux/nm-glib.h
@@ -382,9 +382,8 @@ _nm_g_hash_table_get_keys_as_array (GHashTable *hash_table,
 
 /*****************************************************************************/
 
-#if !GLIB_CHECK_VERSION(2, 44, 0)
 static inline gpointer
-g_steal_pointer (gpointer pp)
+_nm_g_steal_pointer (gpointer pp)
 {
 	gpointer *ptr = (gpointer *) pp;
 	gpointer ref;
@@ -394,13 +393,20 @@ g_steal_pointer (gpointer pp)
 
 	return ref;
 }
+
+#if !GLIB_CHECK_VERSION(2, 44, 0)
+static inline gpointer
+g_steal_pointer (gpointer pp)
+{
+	return _nm_g_steal_pointer (pp);
+}
 #endif
 
 #ifdef g_steal_pointer
 #undef g_steal_pointer
 #endif
 #define g_steal_pointer(pp) \
-	((typeof (*(pp))) g_steal_pointer (pp))
+	((typeof (*(pp))) _nm_g_steal_pointer (pp))
 
 /*****************************************************************************/
 
diff --git a/shared/nm-glib-aux/nm-macros-internal.h b/shared/nm-glib-aux/nm-macros-internal.h
index d5a8513e..f56ed856 100644
--- a/shared/nm-glib-aux/nm-macros-internal.h
+++ b/shared/nm-glib-aux/nm-macros-internal.h
@@ -630,6 +630,14 @@ NM_G_ERROR_MSG (GError *error)
 		NM_CONSTCAST_FULL (type, (obj), _obj, GObject, ##__VA_ARGS__); \
 	})
 
+#define NM_ENSURE_NOT_NULL(ptr) \
+	({ \
+		typeof (ptr) _ptr = (ptr); \
+		\
+		nm_assert (_ptr != NULL); \
+		_ptr; \
+	})
+
 #if _NM_CC_SUPPORT_GENERIC
 /* returns @value, if the type of @value matches @type.
  * This requires support for C11 _Generic(). If no support is
@@ -929,23 +937,38 @@ nm_streq0 (const char *s1, const char *s2)
 
 #define NM_STR_HAS_PREFIX(str, prefix) \
 	({ \
-		const char *const _str = (str); \
+		const char *const _str_has_prefix = (str); \
 		\
-		_str && (strncmp ((str), ""prefix"", NM_STRLEN (prefix)) == 0); \
+		nm_assert (strlen (prefix) == NM_STRLEN (prefix)); \
+		\
+		   _str_has_prefix \
+		&& (strncmp (_str_has_prefix, ""prefix"", NM_STRLEN (prefix)) == 0); \
 	})
 
 #define NM_STR_HAS_SUFFIX(str, suffix) \
 	({ \
-		const char *_str; \
+		const char *const _str_has_suffix = (str); \
 		gsize _l; \
 		\
-		(   (_str = (str)) \
-		 && ((_l = strlen (_str)) >= NM_STRLEN (suffix)) \
-		 && (memcmp (&_str[_l - NM_STRLEN (suffix)], \
+		nm_assert (strlen (suffix) == NM_STRLEN (suffix)); \
+		\
+		(   _str_has_suffix \
+		 && ((_l = strlen (_str_has_suffix)) >= NM_STRLEN (suffix)) \
+		 && (memcmp (&_str_has_suffix[_l - NM_STRLEN (suffix)], \
 		             ""suffix"", \
 		             NM_STRLEN (suffix)) == 0)); \
 	})
 
+/* whether @str starts with the string literal @prefix and is followed by
+ * some other text. It is like NM_STR_HAS_PREFIX() && !nm_streq() together. */
+#define NM_STR_HAS_PREFIX_WITH_MORE(str, prefix) \
+	({ \
+		const char *const _str_has_prefix_with_more = (str); \
+		\
+		   NM_STR_HAS_PREFIX (_str_has_prefix_with_more, ""prefix"") \
+		&& _str_has_prefix_with_more[NM_STRLEN (prefix)] != '\0'; \
+	})
+
 /*****************************************************************************/
 
 static inline GString *
@@ -966,16 +989,23 @@ nm_gstring_add_space_delimiter (GString *str)
 	return str;
 }
 
+static inline gboolean
+nm_str_is_empty (const char *str)
+{
+	/* %NULL is also accepted, and also "empty". */
+	return !str || !str[0];
+}
+
 static inline const char *
 nm_str_not_empty (const char *str)
 {
-	return str && str[0] ? str : NULL;
+	return !nm_str_is_empty (str) ? str : NULL;
 }
 
 static inline char *
 nm_strdup_not_empty (const char *str)
 {
-	return str && str[0] ? g_strdup (str) : NULL;
+	return !nm_str_is_empty (str) ? g_strdup (str) : NULL;
 }
 
 static inline char *
diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c
index 5dd099a7..39e98a31 100644
--- a/shared/nm-glib-aux/nm-shared-utils.c
+++ b/shared/nm-glib-aux/nm-shared-utils.c
@@ -13,10 +13,14 @@
 #include <sys/syscall.h>
 #include <glib-unix.h>
 #include <net/if.h>
+#include <net/ethernet.h>
 
 #include "nm-errno.h"
 #include "nm-str-buf.h"
 
+G_STATIC_ASSERT (sizeof (NMUtilsNamedEntry) == sizeof (const char *));
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMUtilsNamedValue, value_ptr) == sizeof (const char *));
+
 /*****************************************************************************/
 
 const void *const _NM_PTRARRAY_EMPTY[1] = { NULL };
@@ -85,6 +89,11 @@ nm_ip_addr_set_from_untrusted (int addr_family,
 
 /*****************************************************************************/
 
+G_STATIC_ASSERT (ETH_ALEN == sizeof (struct ether_addr));
+G_STATIC_ASSERT (ETH_ALEN == 6);
+
+/*****************************************************************************/
+
 gsize
 nm_utils_get_next_realloc_size (gboolean true_realloc, gsize requested)
 {
@@ -2456,11 +2465,12 @@ _str_buf_append_c_escape_octal (NMStrBuf *strbuf,
 }
 
 gconstpointer
-nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_free)
+nm_utils_buf_utf8safe_unescape (const char *str, NMUtilsStrUtf8SafeFlags flags, gsize *out_len, gpointer *to_free)
 {
+	gboolean strip_spaces = NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES);
 	NMStrBuf strbuf;
-	gsize len;
 	const char *s;
+	gsize len;
 
 	g_return_val_if_fail (to_free, NULL);
 	g_return_val_if_fail (out_len, NULL);
@@ -2471,16 +2481,29 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
 		return NULL;
 	}
 
+	if (strip_spaces)
+		str = nm_str_skip_leading_spaces (str);
+
 	len = strlen (str);
 
 	s = memchr (str, '\\', len);
 	if (!s) {
+		if (   strip_spaces
+		    && len > 0
+		    && g_ascii_isspace (str[len - 1])) {
+			len--;
+			while (   len > 0
+			       && g_ascii_isspace (str[len - 1]))
+				len--;
+			*out_len = len;
+			return (*to_free = g_strndup (str, len));
+		}
 		*out_len = len;
 		*to_free = NULL;
 		return str;
 	}
 
-	nm_str_buf_init (&strbuf, len, FALSE);
+	nm_str_buf_init (&strbuf, len + 1u, FALSE);
 
 	nm_str_buf_append_len (&strbuf, str, s - str);
 	str = s;
@@ -2494,7 +2517,7 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
 		ch = (++str)[0];
 
 		if (ch == '\0') {
-			// error. Trailing '\\'
+			/* error. Trailing '\\' */
 			break;
 		}
 
@@ -2533,7 +2556,14 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
 
 		s = strchr (str, '\\');
 		if (!s) {
-			nm_str_buf_append (&strbuf, str);
+			gsize l = strlen (str);
+
+			if (strip_spaces) {
+				while (   l > 0
+				       && g_ascii_isspace (str[l - 1]))
+					l--;
+			}
+			nm_str_buf_append_len (&strbuf, str, l);
 			break;
 		}
 
@@ -2541,6 +2571,11 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
 		str = s;
 	}
 
+	/* assert that no reallocation was necessary. For one, unescaping should
+	 * never result in a longer string than the input. Also, when unescaping
+	 * secrets, we want to ensure that we don't leak secrets in memory. */
+	nm_assert (strbuf.allocated == len + 1u);
+
 	return (*to_free = nm_str_buf_finalize (&strbuf,
 	                                        out_len));
 }
@@ -2670,16 +2705,33 @@ nm_utils_buf_utf8safe_escape_bytes (GBytes *bytes, NMUtilsStrUtf8SafeFlags flags
 	return nm_utils_buf_utf8safe_escape (p, l, flags, to_free);
 }
 
+char *
+nm_utils_buf_utf8safe_escape_cp (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags)
+{
+	const char *s_const;
+	char *s;
+
+	s_const = nm_utils_buf_utf8safe_escape (buf, buflen, flags, &s);
+	nm_assert (!s || s == s_const);
+	return s ?: g_strdup (s_const);
+}
+
 /*****************************************************************************/
 
 const char *
-nm_utils_str_utf8safe_unescape (const char *str, char **to_free)
+nm_utils_str_utf8safe_unescape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free)
 {
+	const char *res;
 	gsize len;
 
 	g_return_val_if_fail (to_free, NULL);
 
-	return nm_utils_buf_utf8safe_unescape (str, &len, (gpointer *) to_free);
+	res = nm_utils_buf_utf8safe_unescape (str, flags, &len, (gpointer *) to_free);
+
+	nm_assert (   (!res && len == 0)
+	           || (strlen (res) <= len));
+
+	return res;
 }
 
 /**
@@ -2737,11 +2789,11 @@ nm_utils_str_utf8safe_escape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags)
 }
 
 char *
-nm_utils_str_utf8safe_unescape_cp (const char *str)
+nm_utils_str_utf8safe_unescape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags)
 {
 	char *s;
 
-	str = nm_utils_str_utf8safe_unescape (str, &s);
+	str = nm_utils_str_utf8safe_unescape (str, flags, &s);
 	return s ?: g_strdup (str);
 }
 
@@ -4817,3 +4869,123 @@ nm_str_buf_append_printf (NMStrBuf *strbuf,
 
 	strbuf->_priv_len += (gsize) l;
 }
+
+/*****************************************************************************/
+
+/**
+ * nm_indirect_g_free:
+ * @arg: a pointer to a pointer that is to be freed.
+ *
+ * This does the same as nm_clear_g_free(arg) (g_clear_pointer (arg, g_free)).
+ * This is for example useful when you have a GArray with pointers and a
+ * clear function to free them. g_array_set_clear_func()'s destroy notify
+ * function gets a pointer to the array location, so we have to follow
+ * the first pointer.
+ */
+void
+nm_indirect_g_free (gpointer arg)
+{
+	gpointer *p = arg;
+
+	nm_clear_g_free (p);
+}
+
+/*****************************************************************************/
+
+static char *
+attribute_escape (const char *src, char c1, char c2)
+{
+	char *ret, *dest;
+
+	dest = ret = g_malloc (strlen (src) * 2 + 1);
+
+	while (*src) {
+		if (*src == c1 || *src == c2 || *src == '\\')
+			*dest++ = '\\';
+		*dest++ = *src++;
+	}
+	*dest++ = '\0';
+
+	return ret;
+}
+
+void
+_nm_utils_format_variant_attributes_full (GString *str,
+                                          const NMUtilsNamedValue *values,
+                                          guint num_values,
+                                          char attr_separator,
+                                          char key_value_separator)
+{
+	const char *name, *value;
+	GVariant *variant;
+	char *escaped;
+	char buf[64];
+	char sep = 0;
+	guint i;
+
+	for (i = 0; i < num_values; i++) {
+		name = values[i].name;
+		variant = (GVariant *) values[i].value_ptr;
+		value = NULL;
+
+		if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32))
+			value = nm_sprintf_buf (buf, "%u", g_variant_get_uint32 (variant));
+		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_INT32))
+			value = nm_sprintf_buf (buf, "%d", (int) g_variant_get_int32 (variant));
+		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT64))
+			value = nm_sprintf_buf (buf, "%"G_GUINT64_FORMAT, g_variant_get_uint64 (variant));
+		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BYTE))
+			value = nm_sprintf_buf (buf, "%hhu", g_variant_get_byte (variant));
+		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BOOLEAN))
+			value = g_variant_get_boolean (variant) ? "true" : "false";
+		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING))
+			value = g_variant_get_string (variant, NULL);
+		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BYTESTRING)) {
+			/* FIXME: there is no guarantee that the byte array
+			 * is valid UTF-8.*/
+			value = g_variant_get_bytestring (variant);
+		} else
+			continue;
+
+		if (sep)
+			g_string_append_c (str, sep);
+
+		escaped = attribute_escape (name, attr_separator, key_value_separator);
+		g_string_append (str, escaped);
+		g_free (escaped);
+
+		g_string_append_c (str, key_value_separator);
+
+		escaped = attribute_escape (value, attr_separator, key_value_separator);
+		g_string_append (str, escaped);
+		g_free (escaped);
+
+		sep = attr_separator;
+	}
+}
+
+char *
+_nm_utils_format_variant_attributes (GHashTable *attributes,
+                                     char attr_separator,
+                                     char key_value_separator)
+{
+	GString *str = NULL;
+	gs_free NMUtilsNamedValue *values = NULL;
+	guint len;
+
+	g_return_val_if_fail (attr_separator, NULL);
+	g_return_val_if_fail (key_value_separator, NULL);
+
+	if (!attributes || !g_hash_table_size (attributes))
+		return NULL;
+
+	values = nm_utils_named_values_from_str_dict (attributes, &len);
+
+	str = g_string_new ("");
+	_nm_utils_format_variant_attributes_full (str,
+	                                          values,
+	                                          len,
+	                                          attr_separator,
+	                                          key_value_separator);
+	return g_string_free (str, FALSE);
+}
diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h
index d5990c2d..eeb8be5b 100644
--- a/shared/nm-glib-aux/nm-shared-utils.h
+++ b/shared/nm-glib-aux/nm-shared-utils.h
@@ -178,6 +178,24 @@ nm_ip4_addr_is_localhost (in_addr_t addr4)
 
 /*****************************************************************************/
 
+struct ether_addr;
+
+static inline int
+nm_utils_ether_addr_cmp (const struct ether_addr *a1, const struct ether_addr *a2)
+{
+	nm_assert (a1);
+	nm_assert (a2);
+	return memcmp (a1, a2, 6 /*ETH_ALEN*/);
+}
+
+static inline gboolean
+nm_utils_ether_addr_equal (const struct ether_addr *a1, const struct ether_addr *a2)
+{
+	return nm_utils_ether_addr_cmp (a1, a2) == 0;
+}
+
+/*****************************************************************************/
+
 #define NM_UTILS_INET_ADDRSTRLEN INET6_ADDRSTRLEN
 
 static inline const char *
@@ -926,6 +944,21 @@ _nm_g_slice_free_fcn_define (32)
 
 /*****************************************************************************/
 
+/* Like g_error_matches() however:
+ * - as macro it is always inlined.
+ * - the @domain is usually a error quark getter function that cannot
+ *   be inlined. This macro calls the getter only if there is an error (lazy).
+ * - accept a list of allowed codes, instead of only one.
+ */
+#define nm_g_error_matches(error, err_domain, ...) \
+	({ \
+		const GError *const _error = (error); \
+		\
+		   _error \
+		&& _error->domain == (err_domain) \
+		&& NM_IN_SET (_error->code, __VA_ARGS__); \
+	})
+
 static inline void
 nm_g_set_error_take (GError **error, GError *error_take)
 {
@@ -1143,6 +1176,25 @@ GParamSpec *nm_g_object_class_find_property_from_gtype (GType gtype,
 
 /*****************************************************************************/
 
+#define _NM_G_PARAM_SPEC_CAST(param_spec, _value_type, _c_type) \
+	({ \
+		const GParamSpec *const _param_spec = (param_spec); \
+		\
+		nm_assert (   !_param_spec \
+		           || _param_spec->value_type == (_value_type)); \
+		((const _c_type *) _param_spec); \
+	})
+
+#define NM_G_PARAM_SPEC_CAST_BOOLEAN(param_spec) _NM_G_PARAM_SPEC_CAST (param_spec, G_TYPE_BOOLEAN, GParamSpecBoolean)
+#define NM_G_PARAM_SPEC_CAST_UINT(param_spec)    _NM_G_PARAM_SPEC_CAST (param_spec, G_TYPE_UINT,    GParamSpecUInt)
+#define NM_G_PARAM_SPEC_CAST_UINT64(param_spec)  _NM_G_PARAM_SPEC_CAST (param_spec, G_TYPE_UINT64,  GParamSpecUInt64)
+
+#define NM_G_PARAM_SPEC_GET_DEFAULT_BOOLEAN(param_spec) (NM_G_PARAM_SPEC_CAST_BOOLEAN (NM_ENSURE_NOT_NULL (param_spec))->default_value)
+#define NM_G_PARAM_SPEC_GET_DEFAULT_UINT(param_spec)    (NM_G_PARAM_SPEC_CAST_UINT    (NM_ENSURE_NOT_NULL (param_spec))->default_value)
+#define NM_G_PARAM_SPEC_GET_DEFAULT_UINT64(param_spec)  (NM_G_PARAM_SPEC_CAST_UINT64  (NM_ENSURE_NOT_NULL (param_spec))->default_value)
+
+/*****************************************************************************/
+
 GType nm_g_type_find_implementing_class_for_property (GType gtype,
                                                       const char *pname);
 
@@ -1150,20 +1202,39 @@ GType nm_g_type_find_implementing_class_for_property (GType gtype,
 
 typedef enum {
 	NM_UTILS_STR_UTF8_SAFE_FLAG_NONE                = 0,
+
+	/* This flag only has an effect during escaping. */
 	NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL         = 0x0001,
+
+	/* This flag only has an effect during escaping. */
 	NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII    = 0x0002,
+
+	/* This flag only has an effect during escaping to ensure we
+	 * don't leak secrets in memory. Note that during unescape we
+	 * know the maximum result size from the beginning, and no
+	 * reallocation happens. Thus, unescape always avoids leaking
+	 * secrets already. */
 	NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET              = 0x0004,
+
+	/* This flag only has an effect during unescaping. It means
+	 * that non-escaped whitespaces (g_ascii_isspace()) will be
+	 * stripped from the front and end of the string. Note that
+	 * this flag is only useful for gracefully accepting user input
+	 * with spaces. With this flag, escape and unescape may no longer
+	 * yield the original input. */
+	NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES    = 0x0008,
 } NMUtilsStrUtf8SafeFlags;
 
 const char *nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags, char **to_free);
+char *nm_utils_buf_utf8safe_escape_cp (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags);
 const char *nm_utils_buf_utf8safe_escape_bytes (GBytes *bytes, NMUtilsStrUtf8SafeFlags flags, char **to_free);
-gconstpointer nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_free);
+gconstpointer nm_utils_buf_utf8safe_unescape (const char *str, NMUtilsStrUtf8SafeFlags flags, gsize *out_len, gpointer *to_free);
 
 const char *nm_utils_str_utf8safe_escape   (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free);
-const char *nm_utils_str_utf8safe_unescape (const char *str, char **to_free);
+const char *nm_utils_str_utf8safe_unescape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free);
 
 char *nm_utils_str_utf8safe_escape_cp   (const char *str, NMUtilsStrUtf8SafeFlags flags);
-char *nm_utils_str_utf8safe_unescape_cp (const char *str);
+char *nm_utils_str_utf8safe_unescape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags);
 
 char *nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags);
 
@@ -1211,6 +1282,30 @@ nm_g_variant_is_of_type (GVariant *value,
 }
 
 static inline void
+nm_g_variant_builder_add_sv (GVariantBuilder *builder, const char *key, GVariant *val)
+{
+	g_variant_builder_add (builder, "{sv}", key, val);
+}
+
+static inline void
+nm_g_variant_builder_add_sv_bytearray (GVariantBuilder *builder, const char *key, const guint8 *arr, gsize len)
+{
+	g_variant_builder_add (builder, "{sv}", key, g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, arr, len, 1));
+}
+
+static inline void
+nm_g_variant_builder_add_sv_uint32 (GVariantBuilder *builder, const char *key, guint32 val)
+{
+	nm_g_variant_builder_add_sv (builder, key, g_variant_new_uint32 (val));
+}
+
+static inline void
+nm_g_variant_builder_add_sv_str (GVariantBuilder *builder, const char *key, const char *str)
+{
+	nm_g_variant_builder_add_sv (builder, key, g_variant_new_string (str));
+}
+
+static inline void
 nm_g_source_destroy_and_unref (GSource *source)
 {
 	g_source_destroy (source);
@@ -1355,6 +1450,8 @@ typedef struct {
 	};
 } NMUtilsNamedValue;
 
+#define NM_UTILS_NAMED_VALUE_INIT(n, v)     { .name = (n), .value_ptr = (v) }
+
 NMUtilsNamedValue *nm_utils_named_values_from_str_dict_with_sort (GHashTable *hash,
                                                                   guint *out_len,
                                                                   GCompareDataFunc compare_func,
@@ -1436,6 +1533,22 @@ char *nm_utils_g_slist_strlist_join (const GSList *a, const char *separator);
 /*****************************************************************************/
 
 static inline guint
+nm_g_array_len (const GArray *arr)
+{
+	return arr ? arr->len : 0u;
+}
+
+/*****************************************************************************/
+
+static inline guint
+nm_g_ptr_array_len (const GPtrArray *arr)
+{
+	return arr ? arr->len : 0u;
+}
+
+/*****************************************************************************/
+
+static inline guint
 nm_g_hash_table_size (GHashTable *hash)
 {
 	return hash ? g_hash_table_size (hash) : 0u;
@@ -1848,6 +1961,8 @@ nm_utils_strdup_reset (char **dst, const char *src)
 	return TRUE;
 }
 
+void nm_indirect_g_free (gpointer arg);
+
 /*****************************************************************************/
 
 /* nm_utils_get_next_realloc_size() is used to grow buffers exponentially, when
@@ -1887,4 +2002,64 @@ gboolean nm_utils_ifname_valid (const char* name,
                                 NMUtilsIfaceType type,
                                 GError **error);
 
+/*****************************************************************************/
+
+static inline GArray *
+nm_strvarray_ensure (GArray **p)
+{
+	if (!*p) {
+		*p = g_array_new (TRUE, FALSE, sizeof (char *));
+		g_array_set_clear_func (*p, nm_indirect_g_free);
+	}
+	return *p;
+}
+
+static inline void
+nm_strvarray_add (GArray *array, const char *str)
+{
+	char *s;
+
+	s = g_strdup (str);
+	g_array_append_val (array, s);
+}
+
+static inline const char *const*
+nm_strvarray_get_strv (GArray **arr, guint *length)
+{
+	if (!*arr) {
+		NM_SET_OUT (length, 0);
+		return (const char *const*) arr;
+	}
+
+	NM_SET_OUT (length, (*arr)->len);
+	return &g_array_index (*arr, const char *, 0);
+}
+
+static inline void
+nm_strvarray_set_strv (GArray **array, const char *const*strv)
+{
+	gs_unref_array GArray *array_old = NULL;
+
+	array_old = g_steal_pointer (array);
+
+	if (!strv || !strv[0])
+		return;
+
+	nm_strvarray_ensure (array);
+	for (; strv[0]; strv++)
+		nm_strvarray_add (*array, strv[0]);
+}
+
+/*****************************************************************************/
+
+void _nm_utils_format_variant_attributes_full (GString *str,
+                                               const NMUtilsNamedValue *values,
+                                               guint num_values,
+                                               char attr_separator,
+                                               char key_value_separator);
+
+char *_nm_utils_format_variant_attributes (GHashTable *attributes,
+                                           char attr_separator,
+                                           char key_value_separator);
+
 #endif /* __NM_SHARED_UTILS_H__ */
diff --git a/shared/nm-keyfile/nm-keyfile-internal.h b/shared/nm-keyfile/nm-keyfile-internal.h
deleted file mode 100644
index 42003a5f..00000000
--- a/shared/nm-keyfile/nm-keyfile-internal.h
+++ /dev/null
@@ -1,180 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2008 Novell, Inc.
- * Copyright (C) 2015 Red Hat, Inc.
- */
-
-#ifndef __NM_KEYFILE_INTERNAL_H__
-#define __NM_KEYFILE_INTERNAL_H__
-
-#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL)
-#error Cannot use this header.
-#endif
-
-#include <sys/types.h>
-
-#include "nm-connection.h"
-#include "nm-setting-8021x.h"
-
-#include "nm-core-internal.h"
-#include "nm-meta-setting.h"
-
-/*****************************************************************************/
-
-#define NM_KEYFILE_CERT_SCHEME_PREFIX_PATH "file://"
-#define NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11 "pkcs11:"
-#define NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB "data:;base64,"
-
-char *nm_keyfile_detect_unqualified_path_scheme (const char *base_dir,
-                                                 gconstpointer pdata,
-                                                 gsize data_len,
-                                                 gboolean consider_exists,
-                                                 gboolean *out_exists);
-
-typedef enum {
-	NM_KEYFILE_READ_TYPE_WARN               = 1,
-} NMKeyfileReadType;
-
-/**
- * NMKeyfileReadHandler:
- *
- * Hook to nm_keyfile_read(). The user might fail the reading by setting
- * @error.
- *
- * Returns: should return TRUE, if the reading was handled. Otherwise,
- * a default action will be performed that depends on the @type.
- * For %NM_KEYFILE_READ_TYPE_WARN type, the default action is doing nothing.
- */
-typedef gboolean (*NMKeyfileReadHandler) (GKeyFile *keyfile,
-                                          NMConnection *connection,
-                                          NMKeyfileReadType type,
-                                          void *type_data,
-                                          void *user_data,
-                                          GError **error);
-
-typedef enum {
-	NM_KEYFILE_WARN_SEVERITY_DEBUG                  = 1000,
-	NM_KEYFILE_WARN_SEVERITY_INFO                   = 2000,
-	NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE      = 2901,
-	NM_KEYFILE_WARN_SEVERITY_WARN                   = 3000,
-} NMKeyfileWarnSeverity;
-
-/**
- * NMKeyfileReadTypeDataWarn:
- *
- * this struct is passed as @type_data for the @NMKeyfileReadHandler of
- * type %NM_KEYFILE_READ_TYPE_WARN.
- */
-typedef struct {
-	/* might be %NULL, if the warning is not about a group. */
-	const char *group;
-
-	/* might be %NULL, if the warning is not about a setting. */
-	NMSetting *setting;
-
-	/* might be %NULL, if the warning is not about a property. */
-	const char *property_name;
-
-	NMKeyfileWarnSeverity severity;
-	const char *message;
-} NMKeyfileReadTypeDataWarn;
-
-NMConnection *nm_keyfile_read (GKeyFile *keyfile,
-                               const char *base_dir,
-                               NMKeyfileReadHandler handler,
-                               void *user_data,
-                               GError **error);
-
-gboolean nm_keyfile_read_ensure_id (NMConnection *connection,
-                                    const char *fallback_id);
-
-gboolean nm_keyfile_read_ensure_uuid (NMConnection *connection,
-                                      const char *fallback_uuid_seed);
-
-/*****************************************************************************/
-
-typedef enum {
-	NM_KEYFILE_WRITE_TYPE_CERT              = 1,
-} NMKeyfileWriteType;
-
-/**
- * NMKeyfileWriteHandler:
- *
- * This is a hook to tweak the serialization.
- *
- * Handler for certain properties or events that are not entirely contained
- * within the keyfile or that might be serialized differently. The @type and
- * @type_data arguments tell which kind of argument we have at hand.
- *
- * Currently only the type %NM_KEYFILE_WRITE_TYPE_CERT is supported, which provides
- * @type_data as %NMKeyfileWriteTypeDataCert. However, this handler should be generic enough
- * to support other types as well.
- *
- * This don't have to be only "properties". For example, nm_keyfile_read() uses
- * a similar handler to push warnings to the caller.
- *
- * If the handler raises an error, it should set the @error value. This causes
- * the an overall failure.
- *
- * Returns: whether the issue was handled. If the type was unhandled,
- * a default action will be performed. This might be raise an error,
- * do some fallback parsing, or do nothing.
- */
-typedef gboolean (*NMKeyfileWriteHandler) (NMConnection *connection,
-                                           GKeyFile *keyfile,
-                                           NMKeyfileWriteType type,
-                                           void *type_data,
-                                           void *user_data,
-                                           GError **error);
-
-/**
- * NMKeyfileWriteTypeDataCert:
- *
- * this struct is passed as @type_data for the @NMKeyfileWriteHandler of
- * type %NM_KEYFILE_WRITE_TYPE_CERT.
- */
-typedef struct {
-	const NMSetting8021xSchemeVtable *vtable;
-	NMSetting8021x *setting;
-} NMKeyfileWriteTypeDataCert;
-
-GKeyFile *nm_keyfile_write (NMConnection *connection,
-                            NMKeyfileWriteHandler handler,
-                            void *user_data,
-                            GError **error);
-
-/*****************************************************************************/
-
-char *nm_keyfile_plugin_kf_get_string (GKeyFile *kf, const char *group, const char *key, GError **error);
-void nm_keyfile_plugin_kf_set_string (GKeyFile *kf, const char *group, const char *key, const char *value);
-
-int nm_key_file_get_boolean (GKeyFile *kf, const char *group, const char *key, int default_value);
-
-void _nm_keyfile_copy (GKeyFile *dst, GKeyFile *src);
-gboolean _nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b);
-gboolean _nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order);
-gboolean _nm_keyfile_has_values (GKeyFile *keyfile);
-
-/*****************************************************************************/
-
-#define NM_KEYFILE_GROUP_NMMETA                 ".nmmeta"
-#define NM_KEYFILE_KEY_NMMETA_NM_GENERATED      "nm-generated"
-#define NM_KEYFILE_KEY_NMMETA_VOLATILE          "volatile"
-#define NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE  "shadowed-storage"
-#define NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED    "shadowed-owned"
-
-#define NM_KEYFILE_PATH_NAME_LIB                 NMLIBDIR  "/system-connections"
-#define NM_KEYFILE_PATH_NAME_ETC_DEFAULT         NMCONFDIR "/system-connections"
-#define NM_KEYFILE_PATH_NAME_RUN                 NMRUNDIR  "/system-connections"
-
-#define NM_KEYFILE_PATH_SUFFIX_NMCONNECTION      ".nmconnection"
-
-#define NM_KEYFILE_PATH_SUFFIX_NMMETA            ".nmmeta"
-
-#define NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL      "/dev/null"
-
-gboolean nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extension);
-
-char *nm_keyfile_utils_create_filename (const char *filename, gboolean with_extension);
-
-#endif /* __NM_KEYFILE_INTERNAL_H__ */
diff --git a/shared/nm-keyfile/nm-keyfile-utils.c b/shared/nm-keyfile/nm-keyfile-utils.c
deleted file mode 100644
index 041b5eab..00000000
--- a/shared/nm-keyfile/nm-keyfile-utils.c
+++ /dev/null
@@ -1,635 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2010 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-keyfile-utils.h"
-
-#include <stdlib.h>
-
-#include "nm-keyfile-internal.h"
-#include "nm-setting-wired.h"
-#include "nm-setting-wireless.h"
-#include "nm-setting-wireless-security.h"
-
-/*****************************************************************************/
-
-/**
- * nm_key_file_get_boolean:
- * @kf: the #GKeyFile
- * @group: the group
- * @key: the key
- * @default_value: the default value if the value is set or not parsable as a boolean.
- *
- * Replacement for g_key_file_get_boolean() (which uses g_key_file_parse_value_as_boolean()).
- * g_key_file_get_boolean() seems odd to me, because it accepts trailing ASCII whitespace,
- * but not leading.
- * This uses _nm_utils_ascii_str_to_bool(), which accepts trailing and leading whitespace,
- * case-insensitive words, and also strings like "on" and "off".
- * _nm_utils_ascii_str_to_bool() is our way to parse booleans from string, and we should
- * use that one consistently.
- *
- * Also, it doesn't have g_key_file_get_boolean()'s odd API to require an error argument
- * to detect parsing failures.
- *
- * Returns: either %TRUE or %FALSE if the key exists and is parsable as a boolean.
- *   Otherwise, @default_value. Sets errno to ENODATA, EINVAL or 0, depending on whether
- *   the key exists, whether the value is invalid, or success.
- */
-int
-nm_key_file_get_boolean (GKeyFile *kf, const char *group, const char *key, int default_value)
-{
-	int v;
-	gs_free char *value = NULL;
-
-	value = g_key_file_get_value (kf, group, key, NULL);
-
-	if (!value) {
-		errno = ENODATA;
-		return default_value;
-	}
-	v = _nm_utils_ascii_str_to_bool (value, -1);
-	if (v != -1) {
-		errno = 0;
-		return v;
-	}
-	errno = EINVAL;
-	return default_value;
-}
-
-/*****************************************************************************/
-
-typedef struct {
-	const char *setting;
-	const char *alias;
-} SettingAlias;
-
-static const SettingAlias alias_list[] = {
-	{ NM_SETTING_WIRED_SETTING_NAME, "ethernet" },
-	{ NM_SETTING_WIRELESS_SETTING_NAME, "wifi" },
-	{ NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, "wifi-security" },
-};
-
-const char *
-nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name)
-{
-	guint i;
-
-	g_return_val_if_fail (setting_name != NULL, NULL);
-
-	for (i = 0; i < G_N_ELEMENTS (alias_list); i++) {
-		if (nm_streq (setting_name, alias_list[i].setting))
-			return alias_list[i].alias;
-	}
-	return NULL;
-}
-
-const char *
-nm_keyfile_plugin_get_setting_name_for_alias (const char *alias)
-{
-	guint i;
-
-	g_return_val_if_fail (alias != NULL, NULL);
-
-	for (i = 0; i < G_N_ELEMENTS (alias_list); i++) {
-		if (nm_streq (alias, alias_list[i].alias))
-			return alias_list[i].setting;
-	}
-	return NULL;
-}
-
-/*****************************************************************************/
-
-/* List helpers */
-#define DEFINE_KF_LIST_WRAPPER(stype, get_ctype, set_ctype) \
-get_ctype \
-nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \
-                                         const char *group, \
-                                         const char *key, \
-                                         gsize *out_length, \
-                                         GError **error) \
-{ \
-	get_ctype list; \
-	const char *alias; \
-	GError *local = NULL; \
-	gsize l; \
- \
-	list = g_key_file_get_##stype##_list (kf, group, key, &l, &local); \
-	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \
-		alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
-		if (alias) { \
-			g_clear_error (&local); \
-			list = g_key_file_get_##stype##_list (kf, alias, key, &l, &local); \
-		} \
-	} \
-	if (local) \
-		g_propagate_error (error, local); \
-	if (!list) \
-		l = 0; \
-	NM_SET_OUT (out_length, l); \
-	return list; \
-} \
- \
-void \
-nm_keyfile_plugin_kf_set_##stype##_list (GKeyFile *kf, \
-                                         const char *group, \
-                                         const char *key, \
-                                         set_ctype list[], \
-                                         gsize length) \
-{ \
-	const char *alias; \
- \
-	alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
-	g_key_file_set_##stype##_list (kf, alias ?: group, key, list, length); \
-}
-
-DEFINE_KF_LIST_WRAPPER(integer, int*, int);
-DEFINE_KF_LIST_WRAPPER(string, char **, const char* const);
-
-void
-nm_keyfile_plugin_kf_set_integer_list_uint8 (GKeyFile *kf,
-                                             const char *group,
-                                             const char *key,
-                                             const guint8 *data,
-                                             gsize length)
-{
-	gsize i;
-	gsize l = length * 4 + 2;
-	gs_free char *value = g_malloc (l);
-	char *s = value;
-
-	g_return_if_fail (kf);
-	g_return_if_fail (!length || data);
-	g_return_if_fail (group && group[0]);
-	g_return_if_fail (key && key[0]);
-
-	value[0] = '\0';
-	for (i = 0; i < length; i++)
-		nm_utils_strbuf_append (&s, &l, "%d;", (int) data[i]);
-	nm_assert (l > 0);
-	nm_keyfile_plugin_kf_set_value (kf, group, key, value);
-}
-
-/* Single value helpers */
-#define DEFINE_KF_WRAPPER(stype, get_ctype, set_ctype) \
-get_ctype \
-nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \
-                                  const char *group, \
-                                  const char *key, \
-                                  GError **error) \
-{ \
-	get_ctype val; \
-	const char *alias; \
-	GError *local = NULL; \
- \
-	val = g_key_file_get_##stype (kf, group, key, &local); \
-	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \
-		alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
-		if (alias) { \
-			g_clear_error (&local); \
-			val = g_key_file_get_##stype (kf, alias, key, &local); \
-		} \
-	} \
-	if (local) \
-		g_propagate_error (error, local); \
-	return val; \
-} \
- \
-void \
-nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \
-                                  const char *group, \
-                                  const char *key, \
-                                  set_ctype value) \
-{ \
-	const char *alias; \
- \
-	alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
-	g_key_file_set_##stype (kf, alias ?: group, key, value); \
-}
-
-DEFINE_KF_WRAPPER(string, char*, const char*);
-DEFINE_KF_WRAPPER(boolean, gboolean, gboolean);
-DEFINE_KF_WRAPPER(value, char*, const char*);
-
-gint64
-nm_keyfile_plugin_kf_get_int64 (GKeyFile *kf,
-                                const char *group,
-                                const char *key,
-                                guint base,
-                                gint64 min,
-                                gint64 max,
-                                gint64 fallback,
-                                GError **error)
-{
-	gs_free char *s = NULL;
-	int errsv;
-	gint64 v;
-
-	s = nm_keyfile_plugin_kf_get_value (kf, group, key, error);
-	if (!s) {
-		errno = ENODATA;
-		return fallback;
-	}
-
-	v = _nm_utils_ascii_str_to_int64 (s, base, min, max, fallback);
-	errsv = errno;
-	if (   errsv != 0
-	    && error) {
-		g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-		             _("value is not an integer in range [%lld, %lld]"),
-		             (long long) min, (long long) max);
-		errno = errsv;
-	}
-	return v;
-}
-
-char **
-nm_keyfile_plugin_kf_get_keys (GKeyFile *kf,
-                               const char *group,
-                               gsize *out_length,
-                               GError **error)
-{
-	char **keys;
-	const char *alias;
-	GError *local = NULL;
-	gsize l;
-
-	keys = g_key_file_get_keys (kf, group, &l, &local);
-	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
-		alias = nm_keyfile_plugin_get_alias_for_setting_name (group);
-		if (alias) {
-			g_clear_error (&local);
-			keys = g_key_file_get_keys (kf, alias, &l, error ? &local : NULL);
-		}
-	}
-	nm_assert ((!local) != (!keys));
-	if (!keys)
-		l = 0;
-	nm_assert (l == NM_PTRARRAY_LEN (keys));
-	NM_SET_OUT (out_length, l);
-	if (local)
-		g_propagate_error (error, local);
-	return keys;
-}
-
-gboolean
-nm_keyfile_plugin_kf_has_key (GKeyFile *kf,
-                              const char *group,
-                              const char *key,
-                              GError **error)
-{
-	gboolean has;
-	const char *alias;
-	GError *local = NULL;
-
-	has = g_key_file_has_key (kf, group, key, &local);
-	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
-		alias = nm_keyfile_plugin_get_alias_for_setting_name (group);
-		if (alias) {
-			g_clear_error (&local);
-			has = g_key_file_has_key (kf, alias, key, &local);
-		}
-	}
-	if (local)
-		g_propagate_error (error, local);
-	return has;
-}
-
-/*****************************************************************************/
-
-void
-_nm_keyfile_copy (GKeyFile *dst, GKeyFile *src)
-{
-	gs_strfreev char **groups = NULL;
-	guint g, k;
-
-	groups = g_key_file_get_groups (src, NULL);
-	for (g = 0; groups && groups[g]; g++) {
-		const char *group = groups[g];
-		gs_strfreev char **keys = NULL;
-
-		keys = g_key_file_get_keys (src, group, NULL, NULL);
-		if (!keys)
-			continue;
-
-		for (k = 0; keys[k]; k++) {
-			const char *key = keys[k];
-			gs_free char *value = NULL;
-
-			value = g_key_file_get_value (src, group, key, NULL);
-			if (value)
-				g_key_file_set_value (dst, group, key, value);
-			else
-				g_key_file_remove_key (dst, group, key, NULL);
-		}
-	}
-}
-
-/*****************************************************************************/
-
-gboolean
-_nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b)
-{
-	gs_strfreev char **groups = NULL;
-	guint i, j;
-
-	if (kf_a == kf_b)
-		return TRUE;
-	if (!kf_a || !kf_b)
-		return FALSE;
-
-	groups = g_key_file_get_groups (kf_a, NULL);
-	for (i = 0; groups && groups[i]; i++) {
-		gs_strfreev char **keys = NULL;
-
-		keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL);
-		if (!keys)
-			continue;
-
-		for (j = 0; keys[j]; j++) {
-			gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL);
-			gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL);
-
-			if (g_strcmp0 (key_a, key_b) != 0)
-				return FALSE;
-		}
-	}
-	return TRUE;
-}
-
-static gboolean
-_nm_keyfile_equals_ordered (GKeyFile *kf_a, GKeyFile *kf_b)
-{
-	gs_strfreev char **groups = NULL;
-	gs_strfreev char **groups_b = NULL;
-	guint i, j;
-
-	if (kf_a == kf_b)
-		return TRUE;
-	if (!kf_a || !kf_b)
-		return FALSE;
-
-	groups = g_key_file_get_groups (kf_a, NULL);
-	groups_b = g_key_file_get_groups (kf_b, NULL);
-	if (!groups && !groups_b)
-		return TRUE;
-	if (!groups || !groups_b)
-		return FALSE;
-	for (i = 0; groups[i] && groups_b[i] && !strcmp (groups[i], groups_b[i]); i++)
-		;
-	if (groups[i] || groups_b[i])
-		return FALSE;
-
-	for (i = 0; groups[i]; i++) {
-		gs_strfreev char **keys = NULL;
-		gs_strfreev char **keys_b = NULL;
-
-		keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL);
-		keys_b = g_key_file_get_keys (kf_b, groups[i], NULL, NULL);
-
-		if ((!keys) != (!keys_b))
-			return FALSE;
-		if (!keys)
-			continue;
-
-		for (j = 0; keys[j] && keys_b[j] && !strcmp (keys[j], keys_b[j]); j++)
-			;
-		if (keys[j] || keys_b[j])
-			return FALSE;
-
-		for (j = 0; keys[j]; j++) {
-			gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL);
-			gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL);
-
-			if (g_strcmp0 (key_a, key_b) != 0)
-				return FALSE;
-		}
-	}
-	return TRUE;
-}
-
-gboolean
-_nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order)
-{
-	if (!consider_order) {
-		return    _nm_keyfile_a_contains_all_in_b (kf_a, kf_b)
-		       && _nm_keyfile_a_contains_all_in_b (kf_b, kf_a);
-	} else {
-		return _nm_keyfile_equals_ordered (kf_a, kf_b);
-	}
-}
-
-gboolean
-_nm_keyfile_has_values (GKeyFile *keyfile)
-{
-	gs_strfreev char **groups = NULL;
-
-	g_return_val_if_fail (keyfile, FALSE);
-
-	groups = g_key_file_get_groups (keyfile, NULL);
-	return groups && groups[0];
-}
-
-/*****************************************************************************/
-
-static const char *
-_keyfile_key_encode (const char *name,
-                     char **out_to_free)
-{
-	gsize len, i;
-	GString *str;
-
-	nm_assert (name);
-	nm_assert (out_to_free && !*out_to_free);
-
-	/* See g_key_file_is_key_name().
-	 *
-	 * GKeyFile allows all UTF-8 characters (even non-well formed sequences),
-	 * except:
-	 *  - no empty keys
-	 *  - no leading/trailing ' '
-	 *  - no '=', '[', ']'
-	 *
-	 * We do something more strict here. All non-ASCII characters, all non-printable
-	 * characters, and all invalid characters are escaped with "\\XX".
-	 *
-	 * We don't escape \\, unless it is followed by two hex digits.
-	 */
-
-	if (!name[0]) {
-		/* empty keys are are backslash encoded. Note that usually
-		 * \\00 is not a valid encode, the only exception is the empty
-		 * word. */
-		return "\\00";
-	}
-
-	/* find the first character that needs escaping. */
-	i = 0;
-	if (name[0] != ' ') {
-		for (;; i++) {
-			const guchar ch = (guchar) name[i];
-
-			if (ch == '\0')
-				return name;
-
-			if (   ch < 0x20
-			    || ch >= 127
-			    || NM_IN_SET (ch, '=', '[', ']')
-			    || (   ch == '\\'
-			        && g_ascii_isxdigit (name[i + 1])
-			        && g_ascii_isxdigit (name[i + 2]))
-			    || (   ch == ' '
-			        && name[i + 1] == '\0'))
-				break;
-		}
-	} else if (name[1] == '\0')
-		return "\\20";
-
-	len = i + strlen (&name[i]);
-	nm_assert (len == strlen (name));
-	str = g_string_sized_new (len + 15);
-
-	if (name[0] == ' ') {
-		nm_assert (i == 0);
-		g_string_append (str, "\\20");
-		i = 1;
-	} else
-		g_string_append_len (str, name, i);
-
-	for (;; i++) {
-		const guchar ch = (guchar) name[i];
-
-		if (ch == '\0')
-			break;
-
-		if (   ch < 0x20
-		    || ch >= 127
-		    || NM_IN_SET (ch, '=', '[', ']')
-		    || (   ch == '\\'
-		        && g_ascii_isxdigit (name[i + 1])
-		        && g_ascii_isxdigit (name[i + 2]))
-		    || (   ch == ' '
-		        && name[i + 1] == '\0'))
-			g_string_append_printf (str, "\\%02X", ch);
-		else
-			g_string_append_c (str, (char) ch);
-	}
-
-	return (*out_to_free = g_string_free (str, FALSE));
-}
-
-static const char *
-_keyfile_key_decode (const char *key,
-                     char **out_to_free)
-{
-	gsize i, len;
-	GString *str;
-
-	nm_assert (key);
-	nm_assert (out_to_free && !*out_to_free);
-
-	if (!key[0])
-		return "";
-
-	for (i = 0; TRUE; i++) {
-		const char ch = key[i];
-
-		if (ch == '\0')
-			return key;
-		if (   ch == '\\'
-		    && g_ascii_isxdigit (key[i + 1])
-		    && g_ascii_isxdigit (key[i + 2]))
-			break;
-	}
-
-	len = i + strlen (&key[i]);
-
-	if (   len == 3
-	    && nm_streq (key, "\\00"))
-		return "";
-
-	nm_assert (len == strlen (key));
-	str = g_string_sized_new (len + 3);
-
-	g_string_append_len (str, key, i);
-	for (;;) {
-		const char ch = key[i];
-		char ch1, ch2;
-		unsigned v;
-
-		if (ch == '\0')
-			break;
-
-		if (   ch == '\\'
-		    && g_ascii_isxdigit ((ch1 = key[i + 1]))
-		    && g_ascii_isxdigit ((ch2 = key[i + 2]))) {
-			v = (g_ascii_xdigit_value (ch1) << 4) + g_ascii_xdigit_value (ch2);
-			if (v != 0) {
-				g_string_append_c (str, (char) v);
-				i += 3;
-				continue;
-			}
-		}
-		g_string_append_c (str, ch);
-		i++;
-	}
-
-	return (*out_to_free = g_string_free (str, FALSE));
-}
-
-/*****************************************************************************/
-
-const char *
-nm_keyfile_key_encode (const char *name,
-                       char **out_to_free)
-{
-	const char *key;
-
-	key = _keyfile_key_encode (name, out_to_free);
-#if NM_MORE_ASSERTS > 5
-	nm_assert (key);
-	nm_assert (!*out_to_free || key == *out_to_free);
-	nm_assert (!*out_to_free || !nm_streq0 (name, key));
-	{
-		gs_free char *to_free2 = NULL;
-		const char *name2;
-
-		name2 = _keyfile_key_decode (key, &to_free2);
-		/* name2, the result of encode()+decode() is identical to name.
-		 * That is because
-		 *   - encode() is a injective function.
-		 *   - decode() is a surjective function, however for output
-		 *     values of encode() is behaves injective too. */
-		nm_assert (nm_streq0 (name2, name));
-	}
-#endif
-	return key;
-}
-
-const char *
-nm_keyfile_key_decode (const char *key,
-                       char **out_to_free)
-{
-	const char *name;
-
-	name = _keyfile_key_decode (key, out_to_free);
-#if NM_MORE_ASSERTS > 5
-	nm_assert (name);
-	nm_assert (!*out_to_free || name == *out_to_free);
-	{
-		gs_free char *to_free2 = NULL;
-		const char *key2;
-
-		key2 = _keyfile_key_encode (name, &to_free2);
-		/* key2, the result of decode+encode may not be idential
-		 * to the original key. That is, decode() is a surjective
-		 * function mapping different keys to the same name.
-		 * However, decode() behaves injective for input that
-		 * are valid output of encode(). */
-		nm_assert (key2);
-	}
-#endif
-	return name;
-}
diff --git a/shared/nm-keyfile/nm-keyfile-utils.h b/shared/nm-keyfile/nm-keyfile-utils.h
deleted file mode 100644
index a351262e..00000000
--- a/shared/nm-keyfile/nm-keyfile-utils.h
+++ /dev/null
@@ -1,85 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2010 - 2015 Red Hat, Inc.
- */
-
-#ifndef __NM_KEYFILE_UTILS_H__
-#define __NM_KEYFILE_UTILS_H__
-
-#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL)
-#error Cannot use this header.
-#endif
-
-#define NM_KEYFILE_GROUP_VPN_SECRETS          "vpn-secrets"
-#define NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER "wireguard-peer."
-
-const char *nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name);
-
-const char *nm_keyfile_plugin_get_setting_name_for_alias (const char *alias);
-
-/*****************************************************************************/
-
-/* List helpers */
-#define DEFINE_KF_LIST_WRAPPER_PROTO(stype, get_ctype, set_ctype) \
-get_ctype nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \
-                                                   const char *group, \
-                                                   const char *key, \
-                                                   gsize *out_length, \
-                                                   GError **error); \
-\
-void nm_keyfile_plugin_kf_set_##stype##_list  (GKeyFile *kf, \
-                                               const char *group, \
-                                               const char *key, \
-                                               set_ctype list[], \
-                                               gsize length);
-DEFINE_KF_LIST_WRAPPER_PROTO(integer, int*, int)
-DEFINE_KF_LIST_WRAPPER_PROTO(string, char**, const char* const)
-
-void nm_keyfile_plugin_kf_set_integer_list_uint8 (GKeyFile *kf,
-                                                  const char *group,
-                                                  const char *key,
-                                                  const guint8 *list,
-                                                  gsize length);
-
-/* Single-value helpers */
-#define DEFINE_KF_WRAPPER_PROTO(stype, get_ctype, set_ctype) \
-get_ctype nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \
-                                            const char *group, \
-                                            const char *key, \
-                                            GError **error); \
-\
-void nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \
-                                       const char *group, \
-                                       const char *key, \
-                                       set_ctype value);
-DEFINE_KF_WRAPPER_PROTO(string, char*, const char*)
-DEFINE_KF_WRAPPER_PROTO(boolean, gboolean, gboolean)
-DEFINE_KF_WRAPPER_PROTO(value, char*, const char*)
-
-/* Misc */
-gint64 nm_keyfile_plugin_kf_get_int64 (GKeyFile *kf,
-                                       const char *group,
-                                       const char *key,
-                                       guint base,
-                                       gint64 min,
-                                       gint64 max,
-                                       gint64 fallback,
-                                       GError **error);
-
-char ** nm_keyfile_plugin_kf_get_keys    (GKeyFile *kf,
-                                           const char *group,
-                                           gsize *out_length,
-                                           GError **error);
-
-gboolean nm_keyfile_plugin_kf_has_key     (GKeyFile *kf,
-                                           const char *group,
-                                           const char *key,
-                                           GError **error);
-
-const char *nm_keyfile_key_encode (const char *name,
-                                   char **out_to_free);
-
-const char *nm_keyfile_key_decode (const char *key,
-                                   char **out_to_free);
-
-#endif  /* __NM_KEYFILE_UTILS_H__ */
diff --git a/shared/nm-keyfile/nm-keyfile.c b/shared/nm-keyfile/nm-keyfile.c
deleted file mode 100644
index 00a61d03..00000000
--- a/shared/nm-keyfile/nm-keyfile.c
+++ /dev/null
@@ -1,4068 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2008 - 2009 Novell, Inc.
- * Copyright (C) 2008 - 2017 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-keyfile-internal.h"
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <arpa/inet.h>
-#include <linux/pkt_sched.h>
-
-#include "nm-glib-aux/nm-secret-utils.h"
-#include "systemd/nm-sd-utils-shared.h"
-#include "nm-libnm-core-intern/nm-common-macros.h"
-#include "nm-core-internal.h"
-#include "nm-keyfile-utils.h"
-
-#include "nm-setting-user.h"
-
-/*****************************************************************************/
-
-typedef struct _ParseInfoProperty ParseInfoProperty;
-
-typedef struct {
-	NMConnection *connection;
-	GKeyFile *keyfile;
-	const char *base_dir;
-	NMKeyfileReadHandler handler;
-	void *user_data;
-	GError *error;
-	const char *group;
-	NMSetting *setting;
-} KeyfileReaderInfo;
-
-typedef struct {
-	NMConnection *connection;
-	GKeyFile *keyfile;
-	GError *error;
-	NMKeyfileWriteHandler handler;
-	void *user_data;
-} KeyfileWriterInfo;
-
-/*****************************************************************************/
-
-static void
-_handle_warn (KeyfileReaderInfo *info,
-              const char *property_name,
-              NMKeyfileWarnSeverity severity,
-              char *message)
-{
-	NMKeyfileReadTypeDataWarn type_data = {
-		.group = info->group,
-		.setting = info->setting,
-		.property_name = property_name,
-		.severity = severity,
-		.message = message,
-	};
-
-	info->handler (info->keyfile,
-	               info->connection,
-	               NM_KEYFILE_READ_TYPE_WARN,
-	               &type_data,
-	               info->user_data,
-	               &info->error);
-	g_free (message);
-}
-#define handle_warn(arg_info, arg_property_name, arg_severity, ...) \
-	({ \
-		KeyfileReaderInfo *_info = (arg_info); \
-		\
-		if (_info->handler) { \
-			_handle_warn (_info, (arg_property_name), (arg_severity), \
-			              g_strdup_printf (__VA_ARGS__)); \
-		} \
-		_info->error == NULL; \
-	})
-
-/*****************************************************************************/
-
-static gboolean
-_secret_flags_persist_secret (NMSettingSecretFlags flags)
-{
-	return flags == NM_SETTING_SECRET_FLAG_NONE;
-}
-
-/*****************************************************************************/
-/* Some setting properties also contain setting names, such as
- * NMSettingConnection's 'type' property (which specifies the base type of the
- * connection, e.g. ethernet or wifi) or 'slave-type' (specifies type of slave
- * connection, e.g. bond or bridge). This function handles translating those
- * properties' values to the real setting name if they are an alias.
- */
-static void
-setting_alias_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	const char *key_setting_name;
-	gs_free char *s = NULL;
-
-	s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
-	if (!s)
-		return;
-
-	key_setting_name = nm_keyfile_plugin_get_setting_name_for_alias (s);
-	g_object_set (G_OBJECT (setting),
-	              key,
-	              key_setting_name ?: s,
-	              NULL);
-}
-
-static void
-sriov_vfs_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_unref_ptrarray GPtrArray *vfs = NULL;
-	gs_strfreev char **keys = NULL;
-	gsize n_keys = 0;
-	int i;
-
-	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
-	if (n_keys == 0)
-		return;
-
-	vfs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_sriov_vf_unref);
-
-	for (i = 0; i < n_keys; i++) {
-		gs_free char *value = NULL;
-		NMSriovVF *vf;
-		const char *rest;
-
-		if (!g_str_has_prefix (keys[i], "vf."))
-			continue;
-
-		rest = &keys[i][3];
-
-		if (!NM_STRCHAR_ALL (rest, ch, g_ascii_isdigit (ch)))
-			continue;
-
-		value = nm_keyfile_plugin_kf_get_string (info->keyfile,
-		                                         setting_name,
-		                                         keys[i],
-		                                         NULL);
-
-		vf = _nm_utils_sriov_vf_from_strparts (rest, value, TRUE, NULL);
-		if (vf)
-			g_ptr_array_add (vfs, vf);
-	}
-
-	g_object_set (G_OBJECT (setting),
-	              key, vfs,
-	              NULL);
-}
-
-static void
-read_array_of_uint (GKeyFile *file,
-                    NMSetting *setting,
-                    const char *key)
-{
-	gs_unref_array GArray *array = NULL;
-	gsize length;
-	gsize i;
-	gs_free int *tmp = NULL;
-	gs_free_error GError *error = NULL;
-
-	tmp = nm_keyfile_plugin_kf_get_integer_list (file, nm_setting_get_name (setting), key, &length, &error);
-	if (error)
-		return;
-
-	array = g_array_sized_new (FALSE, FALSE, sizeof (guint), length);
-
-	for (i = 0; i < length; i++) {
-		if (tmp[i] < 0)
-			return;
-		g_array_append_val (array, tmp[i]);
-	}
-
-	g_object_set (setting, key, array, NULL);
-}
-
-static gboolean
-get_one_int (KeyfileReaderInfo *info, const char *property_name, const char *str, guint32 max_val, guint32 *out)
-{
-	gint64 tmp;
-
-	g_return_val_if_fail (!info == !property_name, FALSE);
-
-	if (!str || !str[0]) {
-		if (property_name)
-			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("ignoring missing number"));
-		return FALSE;
-	}
-
-	tmp = _nm_utils_ascii_str_to_int64 (str, 10, 0, max_val, -1);
-	if (tmp == -1) {
-		if (property_name) {
-			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("ignoring invalid number '%s'"),
-			            str);
-		}
-		return FALSE;
-	}
-
-	*out = (guint32) tmp;
-	return TRUE;
-}
-
-static gpointer
-build_address (KeyfileReaderInfo *info, int family, const char *address_str, guint32 plen, const char *property_name)
-{
-	NMIPAddress *addr;
-	GError *error = NULL;
-
-	g_return_val_if_fail (address_str, NULL);
-
-	addr = nm_ip_address_new (family, address_str, plen, &error);
-	if (!addr) {
-		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("ignoring invalid %s address: %s"),
-		            family == AF_INET ? "IPv4" : "IPv6", error->message);
-		g_error_free (error);
-	}
-
-	return addr;
-}
-
-static gpointer
-build_route (KeyfileReaderInfo *info,
-             const char *property_name,
-             int family,
-             const char *dest_str,
-             guint32 plen,
-             const char *gateway_str,
-             const char *metric_str)
-{
-	NMIPRoute *route;
-	guint32 u32;
-	gint64 metric = -1;
-	GError *error = NULL;
-
-	g_return_val_if_fail (dest_str, NULL);
-
-	/* Next hop */
-	if (gateway_str && gateway_str[0]) {
-		if (!nm_utils_ipaddr_is_valid (family, gateway_str)) {
-			/* Try workaround for routes written by broken keyfile writer.
-			 * Due to bug bgo#719851, an older version of writer would have
-			 * written "a:b:c:d::/plen,metric" if the gateway was ::, instead
-			 * of "a:b:c:d::/plen,,metric" or "a:b:c:d::/plen,::,metric"
-			 * Try workaround by interpreting gateway_str as metric to accept such
-			 * invalid routes. This broken syntax should not be not officially
-			 * supported.
-			 **/
-			if (   family == AF_INET6
-			    && !metric_str
-			    && get_one_int (NULL, NULL, gateway_str, G_MAXUINT32, &u32)) {
-				metric = u32;
-				gateway_str = NULL;
-			} else {
-				if (!info->error) {
-					handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-					             _("ignoring invalid gateway '%s' for %s route"),
-					             gateway_str, family == AF_INET ? "IPv4" : "IPv6");
-				}
-				return NULL;
-			}
-		}
-	} else
-		gateway_str = NULL;
-
-	/* parse metric, default to -1 */
-	if (metric_str) {
-		if (!get_one_int (info, property_name, metric_str, G_MAXUINT32, &u32))
-			return NULL;
-		metric = u32;
-	}
-
-	route = nm_ip_route_new (family,
-	                         dest_str,
-	                         plen,
-	                         gateway_str,
-	                         metric,
-	                         &error);
-	if (!route) {
-		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("ignoring invalid %s route: %s"),
-		             family == AF_INET ? "IPv4" : "IPv6",
-		             error->message);
-		g_error_free (error);
-	}
-
-	return route;
-}
-
-/* On success, returns pointer to the zero-terminated field (original @current).
- * The @current * pointer target is set to point to the rest of the input
- * or %NULL if there is no more input. Sets error to %NULL for convenience.
- *
- * On failure, returns %NULL (unspecified). The @current pointer target is
- * resets to its original value to allow skipping fields. The @error target
- * is set to the character that breaks the parsing or %NULL if @current was %NULL.
- *
- * When @current target is %NULL, gracefully fail returning %NULL while
- * leaving the @current target %NULL end setting @error to %NULL;
- */
-static const char *
-read_field (char **current, const char **out_err_str, const char *characters, const char *delimiters)
-{
-	const char *start;
-
-	nm_assert (current);
-	nm_assert (out_err_str);
-	nm_assert (characters);
-	nm_assert (delimiters);
-
-	*out_err_str = NULL;
-
-	if (!*current) {
-		/* graceful failure, leave '*current' NULL */
-		return NULL;
-	}
-
-	/* fail on empty input */
-	if (!**current)
-		return NULL;
-
-	/* remember beginning of input */
-	start = *current;
-
-	while (**current && strchr (characters, **current))
-		(*current)++;
-	if (**current)
-		if (strchr (delimiters, **current)) {
-			/* success, more data available */
-			*(*current)++ = '\0';
-			return start;
-		} else {
-			/* error, bad character */
-			*out_err_str = *current;
-			*current = (char *) start;
-			return NULL;
-		}
-	else {
-		/* success, end of input */
-		*current = NULL;
-		return start;
-	}
-}
-
-/*****************************************************************************/
-
-#define NM_DBUS_SERVICE_OPENCONNECT    "org.freedesktop.NetworkManager.openconnect"
-#define NM_OPENCONNECT_KEY_GATEWAY     "gateway"
-#define NM_OPENCONNECT_KEY_COOKIE      "cookie"
-#define NM_OPENCONNECT_KEY_GWCERT      "gwcert"
-#define NM_OPENCONNECT_KEY_XMLCONFIG   "xmlconfig"
-#define NM_OPENCONNECT_KEY_LASTHOST    "lasthost"
-#define NM_OPENCONNECT_KEY_AUTOCONNECT "autoconnect"
-#define NM_OPENCONNECT_KEY_CERTSIGS    "certsigs"
-
-static void
-openconnect_fix_secret_flags (NMSetting *setting)
-{
-	NMSettingVpn *s_vpn;
-	NMSettingSecretFlags flags;
-
-	/* Huge hack.  There were some openconnect changes that needed to happen
-	 * pretty late, too late to get into distros.  Migration has already
-	 * happened for many people, and their secret flags are wrong.  But we
-	 * don't want to requrie re-migration, so we have to fix it up here. Ugh.
-	 */
-
-	if (!NM_IS_SETTING_VPN (setting))
-		return;
-
-	s_vpn = NM_SETTING_VPN (setting);
-
-	if (!nm_streq0 (nm_setting_vpn_get_service_type (s_vpn), NM_DBUS_SERVICE_OPENCONNECT))
-		return;
-
-	/* These are different for every login session, and should not be stored */
-	flags = NM_SETTING_SECRET_FLAG_NOT_SAVED;
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GATEWAY, flags, NULL);
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_COOKIE, flags, NULL);
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GWCERT, flags, NULL);
-
-	/* These are purely internal data for the auth-dialog, and should be stored */
-	flags = 0;
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_XMLCONFIG, flags, NULL);
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_LASTHOST, flags, NULL);
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_AUTOCONNECT, flags, NULL);
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_CERTSIGS, flags, NULL);
-}
-
-/*****************************************************************************/
-
-#define IP_ADDRESS_CHARS "0123456789abcdefABCDEF:.%"
-#define DIGITS "0123456789"
-#define DELIMITERS "/;,"
-
-/* The following IPv4 and IPv6 address formats are supported:
- *
- * address (DEPRECATED)
- * address/plen
- * address/gateway (DEPRECATED)
- * address/plen,gateway
- *
- * The following IPv4 and IPv6 route formats are supported:
- *
- * address/plen (NETWORK dev DEVICE)
- * address/plen,gateway (NETWORK via GATEWAY dev DEVICE)
- * address/plen,,metric (NETWORK dev DEVICE metric METRIC)
- * address/plen,gateway,metric (NETWORK via GATEWAY dev DEVICE metric METRIC)
- *
- * For backward, forward and sideward compatibility, slash (/),
- * semicolon (;) and comma (,) are interchangeable. The choice of
- * separator in the above examples is therefore not significant.
- *
- * Leaving out the prefix length is discouraged and DEPRECATED. The
- * default value of IPv6 prefix length was 64 and has not been
- * changed. The default for IPv4 is now 24, which is the closest
- * IPv4 equivalent. These defaults may just as well be changed to
- * match the iproute2 defaults (32 for IPv4 and 128 for IPv6).
- */
-static gpointer
-read_one_ip_address_or_route (KeyfileReaderInfo *info,
-                              const char *property_name,
-                              const char *setting_name,
-                              const char *key_name,
-                              gboolean ipv6,
-                              gboolean route,
-                              char **out_gateway,
-                              NMSetting *setting)
-{
-	guint plen;
-	gpointer result;
-	const char *address_str;
-	const char *plen_str;
-	const char *gateway_str;
-	const char *metric_str;
-	const char *err_str = NULL;
-	char *current;
-	gs_free char *value = NULL;
-	gs_free char *value_orig = NULL;
-
-#define VALUE_ORIG()   (value_orig ?: (value_orig = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL)))
-
-	value = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL);
-	if (!value)
-		return NULL;
-
-	current = value;
-
-	/* get address field */
-	address_str = read_field (&current, &err_str, IP_ADDRESS_CHARS, DELIMITERS);
-	if (err_str) {
-		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("unexpected character '%c' for address %s: '%s' (position %td)"),
-		             *err_str, key_name, VALUE_ORIG (), err_str - current);
-		return NULL;
-	}
-	/* get prefix length field (skippable) */
-	plen_str = read_field (&current, &err_str, DIGITS, DELIMITERS);
-	/* get gateway field */
-	gateway_str = read_field (&current, &err_str, IP_ADDRESS_CHARS, DELIMITERS);
-	if (err_str) {
-		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("unexpected character '%c' for %s: '%s' (position %td)"),
-		             *err_str, key_name, VALUE_ORIG (), err_str - current);
-		return NULL;
-	}
-	/* for routes, get metric */
-	if (route) {
-		metric_str = read_field (&current, &err_str, DIGITS, DELIMITERS);
-		if (err_str) {
-			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("unexpected character '%c' in prefix length for %s: '%s' (position %td)"),
-			             *err_str, key_name, VALUE_ORIG (), err_str - current);
-			return NULL;
-		}
-	} else
-		metric_str = NULL;
-	if (current) {
-		/* there is still some data */
-		if (*current) {
-			/* another field follows */
-			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("garbage at the end of value %s: '%s'"),
-			             key_name, VALUE_ORIG ());
-			return NULL;
-		} else {
-			/* semicolon at the end of input */
-			if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_INFO,
-			                  _("deprecated semicolon at the end of value %s: '%s'"),
-			                  key_name, VALUE_ORIG ()))
-				return NULL;
-		}
-	}
-
-#define DEFAULT_PREFIX(for_route, for_ipv6) ( (for_route) ? ( (for_ipv6) ? 128 : 24 ) : ( (for_ipv6) ? 64 : 24 ) )
-
-	/* parse plen, fallback to defaults */
-	if (plen_str) {
-		if (!get_one_int (info, property_name, plen_str, ipv6 ? 128 : 32, &plen)) {
-			plen = DEFAULT_PREFIX (route, ipv6);
-			if (   info->error
-			    || !handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-			                     _("invalid prefix length for %s '%s', defaulting to %d"),
-			                     key_name, VALUE_ORIG (), plen))
-				return NULL;
-		}
-	} else {
-		plen = DEFAULT_PREFIX (route, ipv6);
-		if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-		                  _("missing prefix length for %s '%s', defaulting to %d"),
-		                  key_name, VALUE_ORIG (), plen))
-			return NULL;
-	}
-
-	/* build the appropriate data structure for NetworkManager settings */
-	if (route) {
-		result = build_route (info,
-		                      property_name,
-		                      ipv6 ? AF_INET6 : AF_INET,
-		                      address_str,
-		                      plen,
-		                      gateway_str,
-		                      metric_str);
-	} else {
-		result = build_address (info,
-		                        ipv6 ? AF_INET6 : AF_INET,
-		                        address_str,
-		                        plen,
-		                        property_name);
-		if (!result)
-			return NULL;
-		if (gateway_str)
-			NM_SET_OUT (out_gateway, g_strdup (gateway_str));
-	}
-
-#undef VALUE_ORIG
-
-	return result;
-}
-
-static void
-fill_route_attributes (GKeyFile *kf, NMIPRoute *route, const char *setting, const char *key, int family)
-{
-	gs_free char *value = NULL;
-	gs_unref_hashtable GHashTable *hash = NULL;
-	GHashTableIter iter;
-	char *name;
-	GVariant *variant;
-
-	value = nm_keyfile_plugin_kf_get_string (kf, setting, key, NULL);
-	if (!value || !value[0])
-		return;
-
-	hash = nm_utils_parse_variant_attributes (value, ',', '=', TRUE,
-	                                          nm_ip_route_get_variant_attribute_spec (),
-	                                          NULL);
-	if (hash) {
-		g_hash_table_iter_init (&iter, hash);
-		while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &variant)) {
-			if (nm_ip_route_attribute_validate (name, variant, family, NULL, NULL))
-				nm_ip_route_set_attribute (route, name, g_variant_ref (variant));
-		}
-	}
-}
-
-typedef struct {
-	const char *s_key;
-	gint32 key_idx;
-	gint8 key_type;
-} BuildListData;
-
-typedef enum {
-	BUILD_LIST_TYPE_ADDRESSES,
-	BUILD_LIST_TYPE_ROUTES,
-	BUILD_LIST_TYPE_ROUTING_RULES,
-} BuildListType;
-
-static int
-_build_list_data_cmp (gconstpointer p_a, gconstpointer p_b, gpointer user_data)
-{
-	const BuildListData *a = p_a;
-	const BuildListData *b = p_b;
-
-	NM_CMP_FIELD (a, b, key_idx);
-	NM_CMP_FIELD (a, b, key_type);
-	NM_CMP_FIELD_STR (a, b, s_key);
-	return 0;
-}
-
-static gboolean
-_build_list_data_is_shadowed (const BuildListData *build_list,
-                              gsize build_list_len,
-                              gsize idx)
-{
-	/* the keyfile contains duplicate keys, which are both returned
-	 * by g_key_file_get_keys() (WHY??).
-	 *
-	 * Skip the earlier one. */
-	return    idx + 1 < build_list_len
-	       && build_list[idx].key_idx == build_list[idx + 1].key_idx
-	       && build_list[idx].key_type == build_list[idx + 1].key_type
-	       && nm_streq (build_list[idx].s_key, build_list[idx + 1].s_key);
-}
-
-static gboolean
-_build_list_match_key_w_name_impl (const char *key,
-                                   const char *base_name,
-                                   gsize base_name_l,
-                                   gint32 *out_key_idx)
-{
-	gint64 v;
-
-	/* some very strict parsing. */
-
-	/* the key must start with base_name. */
-	if (strncmp (key, base_name, base_name_l) != 0)
-		return FALSE;
-
-	key += base_name_l;
-	if (key[0] == '\0') {
-		/* if key is identical to base_name, that's good. */
-		NM_SET_OUT (out_key_idx, -1);
-		return TRUE;
-	}
-
-	/* if base_name is followed by a zero, then it must be
-	 * only a zero, nothing else. */
-	if (key[0] == '0') {
-		if (key[1] != '\0')
-			return FALSE;
-		NM_SET_OUT (out_key_idx, 0);
-		return TRUE;
-	}
-
-	/* otherwise, it can only be followed by a non-zero decimal. */
-	if (!(key[0] >= '1' && key[0] <= '9'))
-		return FALSE;
-	/* and all remaining chars must be decimals too. */
-	if (!NM_STRCHAR_ALL (&key[1], ch, g_ascii_isdigit (ch)))
-		return FALSE;
-
-	/* and it must be convertible to a (positive) int. */
-	v = _nm_utils_ascii_str_to_int64 (key, 10, 0, G_MAXINT32, -1);
-	if (v < 0)
-		return FALSE;
-
-	/* good */
-	NM_SET_OUT (out_key_idx, v);
-	return TRUE;
-}
-
-#define _build_list_match_key_w_name(key, base_name, out_key_idx) \
-	_build_list_match_key_w_name_impl (key, base_name, NM_STRLEN (base_name), out_key_idx)
-
-static BuildListData *
-_build_list_create (GKeyFile *keyfile,
-                    const char *group_name,
-                    BuildListType build_list_type,
-                    gsize *out_build_list_len,
-                    char ***out_keys_strv)
-{
-	gs_strfreev char **keys = NULL;
-	gsize i_keys, n_keys;
-	gs_free BuildListData *build_list = NULL;
-	gsize build_list_len = 0;
-
-	nm_assert (out_build_list_len && *out_build_list_len == 0);
-	nm_assert (out_keys_strv && !*out_keys_strv);
-
-	keys = nm_keyfile_plugin_kf_get_keys (keyfile, group_name, &n_keys, NULL);
-	if (n_keys == 0)
-		return NULL;
-
-	for (i_keys = 0; i_keys < n_keys; i_keys++) {
-		const char *s_key = keys[i_keys];
-		gint32 key_idx;
-		gint8 key_type = 0;
-
-		switch (build_list_type) {
-		case BUILD_LIST_TYPE_ROUTES:
-			if (_build_list_match_key_w_name (s_key, "route", &key_idx))
-				key_type = 0;
-			else if (_build_list_match_key_w_name (s_key, "routes", &key_idx))
-				key_type = 1;
-			else
-				continue;
-			break;
-		case BUILD_LIST_TYPE_ADDRESSES:
-			if (_build_list_match_key_w_name (s_key, "address", &key_idx))
-				key_type = 0;
-			else if (_build_list_match_key_w_name (s_key, "addresses", &key_idx))
-				key_type = 1;
-			else
-				continue;
-			break;
-		case BUILD_LIST_TYPE_ROUTING_RULES:
-			if (_build_list_match_key_w_name (s_key, "routing-rule", &key_idx))
-				key_type = 0;
-			else
-				continue;
-			break;
-		default:
-			nm_assert_not_reached ();
-			break;
-		}
-
-		if (G_UNLIKELY (!build_list))
-			build_list = g_new (BuildListData, n_keys - i_keys);
-
-		build_list[build_list_len++] = (BuildListData) {
-			.s_key    = s_key,
-			.key_idx  = key_idx,
-			.key_type = key_type,
-		};
-	}
-
-	if (build_list_len == 0)
-		return NULL;
-
-	if (build_list_len > 1) {
-		g_qsort_with_data (build_list,
-		                   build_list_len,
-		                   sizeof (BuildListData),
-		                   _build_list_data_cmp,
-		                   NULL);
-	}
-
-	*out_build_list_len = build_list_len;
-	*out_keys_strv = g_steal_pointer (&keys);
-	return g_steal_pointer (&build_list);
-}
-
-static void
-ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *setting_key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gboolean is_ipv6 = nm_streq (setting_name, "ipv6");
-	gboolean is_routes = nm_streq (setting_key, "routes");
-	gs_free char *gateway = NULL;
-	gs_unref_ptrarray GPtrArray *list = NULL;
-	gs_strfreev char **keys = NULL;
-	gs_free BuildListData *build_list = NULL;
-	gsize i_build_list, build_list_len = 0;
-
-	build_list = _build_list_create (info->keyfile,
-	                                 setting_name,
-	                                   is_routes
-	                                 ? BUILD_LIST_TYPE_ROUTES
-	                                 : BUILD_LIST_TYPE_ADDRESSES,
-	                                 &build_list_len,
-	                                 &keys);
-	if (!build_list)
-		return;
-
-	list = g_ptr_array_new_with_free_func (is_routes
-	                                       ? (GDestroyNotify) nm_ip_route_unref
-	                                       : (GDestroyNotify) nm_ip_address_unref);
-
-	for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) {
-		const char *s_key;
-		gpointer item;
-
-		if (_build_list_data_is_shadowed (build_list, build_list_len, i_build_list))
-			continue;
-
-		s_key = build_list[i_build_list].s_key;
-		item = read_one_ip_address_or_route (info,
-		                                     setting_key,
-		                                     setting_name,
-		                                     s_key,
-		                                     is_ipv6,
-		                                     is_routes,
-		                                     gateway ? NULL : &gateway,
-		                                     setting);
-		if (item && is_routes) {
-			char options_key[128];
-
-			nm_sprintf_buf (options_key, "%s_options", s_key);
-			fill_route_attributes (info->keyfile,
-			                       item,
-			                       setting_name,
-			                       options_key,
-			                       is_ipv6 ? AF_INET6 : AF_INET);
-		}
-
-		if (info->error)
-			return;
-
-		if (item)
-			g_ptr_array_add (list, item);
-	}
-
-	if (list->len >= 1)
-		g_object_set (setting, setting_key, list, NULL);
-
-	if (gateway)
-		g_object_set (setting, "gateway", gateway, NULL);
-}
-
-static void
-ip_routing_rule_parser_full (KeyfileReaderInfo *info,
-                             const NMMetaSettingInfo *setting_info,
-                             const NMSettInfoProperty *property_info,
-                             const ParseInfoProperty *pip,
-                             NMSetting *setting)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gboolean is_ipv6 = nm_streq (setting_name, "ipv6");
-	gs_strfreev char **keys = NULL;
-	gs_free BuildListData *build_list = NULL;
-	gsize i_build_list, build_list_len = 0;
-
-	build_list = _build_list_create (info->keyfile,
-	                                 setting_name,
-	                                 BUILD_LIST_TYPE_ROUTING_RULES,
-	                                 &build_list_len,
-	                                 &keys);
-	if (!build_list)
-		return;
-
-	for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) {
-		nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL;
-		gs_free char *value = NULL;
-		gs_free_error GError *local = NULL;
-
-		if (_build_list_data_is_shadowed (build_list, build_list_len, i_build_list))
-			continue;
-
-		value = nm_keyfile_plugin_kf_get_string (info->keyfile,
-		                                         setting_name,
-		                                         build_list[i_build_list].s_key,
-		                                         NULL);
-		if (!value)
-			continue;
-
-		rule = nm_ip_routing_rule_from_string (value,
-		                                       (  NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE
-		                                        | (  is_ipv6
-		                                           ? NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6
-		                                           : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET)),
-		                                       NULL,
-		                                       &local);
-		if (!rule) {
-			handle_warn (info, property_info->name, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid value for \"%s\": %s"),
-			             build_list[i_build_list].s_key,
-			             local->message);
-			if (info->error)
-				return;
-			continue;
-		}
-
-		nm_setting_ip_config_add_routing_rule (NM_SETTING_IP_CONFIG (setting), rule);
-	}
-}
-
-static void
-ip_dns_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	int addr_family;
-	gs_strfreev char **list = NULL;
-	gsize i, n, length;
-
-	nm_assert (NM_IS_SETTING_IP4_CONFIG (setting) || NM_IS_SETTING_IP6_CONFIG (setting));
-
-	list = nm_keyfile_plugin_kf_get_string_list (info->keyfile,
-	                                             nm_setting_get_name (setting),
-	                                             key,
-	                                             &length,
-	                                             NULL);
-	nm_assert (length == NM_PTRARRAY_LEN (list));
-	if (length == 0)
-		return;
-
-	addr_family = NM_IS_SETTING_IP4_CONFIG (setting) ? AF_INET : AF_INET6;
-
-	n = 0;
-	for (i = 0; i < length; i++) {
-		NMIPAddr addr;
-
-		if (inet_pton (addr_family, list[i], &addr) <= 0) {
-			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			                  _("ignoring invalid DNS server IPv%c address '%s'"),
-			                  nm_utils_addr_family_to_char (addr_family),
-			                  list[i])) {
-				do {
-					nm_clear_g_free (&list[i]);
-				} while (++i < length);
-				return;
-			}
-			nm_clear_g_free (&list[i]);
-			continue;
-		}
-
-		if (n != i)
-			list[n] = g_steal_pointer (&list[i]);
-		n++;
-	}
-
-	g_object_set (setting, key, list, NULL);
-}
-
-static void
-ip6_addr_gen_mode_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	NMSettingIP6ConfigAddrGenMode addr_gen_mode;
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_free char *s = NULL;
-
-	s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
-	if (s) {
-		if (!nm_utils_enum_from_str (nm_setting_ip6_config_addr_gen_mode_get_type (), s,
-		                             (int *) &addr_gen_mode, NULL)) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid option '%s', use one of [%s]"),
-			             s, "eui64,stable-privacy");
-			return;
-		}
-	} else
-		addr_gen_mode = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64;
-
-	g_object_set (G_OBJECT (setting), key, (int) addr_gen_mode, NULL);
-}
-
-static void
-mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key, gsize addr_len, gboolean cloned_mac_addr)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	char addr_str[NM_UTILS_HWADDR_LEN_MAX * 3];
-	guint8 addr_bin[NM_UTILS_HWADDR_LEN_MAX];
-	gs_free char *tmp_string = NULL;
-	gs_free int *int_list = NULL;
-	const char *mac_str;
-	gsize int_list_len;
-	gsize i;
-
-	nm_assert (addr_len > 0);
-	nm_assert (addr_len <= NM_UTILS_HWADDR_LEN_MAX);
-
-	tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
-
-	if (   cloned_mac_addr
-	    && NM_CLONED_MAC_IS_SPECIAL (tmp_string)) {
-		mac_str = tmp_string;
-		goto out;
-	}
-
-	if (   tmp_string
-	    && nm_utils_hwaddr_aton (tmp_string, addr_bin, addr_len))
-		goto good_addr_bin;
-
-	/* Old format; list of ints */
-	int_list = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &int_list_len, NULL);
-	if (int_list_len == addr_len) {
-		for (i = 0; i < addr_len; i++) {
-			const int val = int_list[i];
-
-			if (val < 0 || val > 255)
-				break;
-			addr_bin[i] = (guint8) val;
-		}
-		if (i == addr_len)
-			goto good_addr_bin;
-	}
-
-	handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-	             _("ignoring invalid MAC address"));
-	return;
-
-good_addr_bin:
-	nm_utils_bin2hexstr_full (addr_bin, addr_len, ':', TRUE, addr_str);
-	mac_str = addr_str;
-
-out:
-	g_object_set (setting, key, mac_str, NULL);
-}
-
-static void
-mac_address_parser_ETHER (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	mac_address_parser (info, setting, key, ETH_ALEN, FALSE);
-}
-
-static void
-mac_address_parser_ETHER_cloned (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	mac_address_parser (info, setting, key, ETH_ALEN, TRUE);
-}
-
-static void
-mac_address_parser_INFINIBAND (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	mac_address_parser (info, setting, key, INFINIBAND_ALEN, FALSE);
-}
-
-static void
-read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key)
-{
-	gs_strfreev char **keys = NULL;
-	const char *const*iter;
-	const char *setting_name = nm_setting_get_name (setting);
-	gboolean is_vpn;
-	gsize n_keys;
-
-	nm_assert (   (NM_IS_SETTING_VPN (setting)  && nm_streq (key, NM_SETTING_VPN_DATA))
-	           || (NM_IS_SETTING_VPN (setting)  && nm_streq (key, NM_SETTING_VPN_SECRETS))
-	           || (NM_IS_SETTING_BOND (setting) && nm_streq (key, NM_SETTING_BOND_OPTIONS))
-	           || (NM_IS_SETTING_USER (setting) && nm_streq (key, NM_SETTING_USER_DATA)));
-
-	keys = nm_keyfile_plugin_kf_get_keys (file, setting_name, &n_keys, NULL);
-	if (n_keys == 0)
-		return;
-
-	if (   (is_vpn = NM_IS_SETTING_VPN (setting))
-	    || NM_IS_SETTING_BOND (setting)) {
-		for (iter = (const char *const*) keys; *iter; iter++) {
-			gs_free char *to_free = NULL;
-			gs_free char *value = NULL;
-			const char *name;
-
-			value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL);
-			if (!value)
-				continue;
-
-			name = nm_keyfile_key_decode (*iter, &to_free);
-
-			if (is_vpn) {
-				/* Add any item that's not a class property to the data hash */
-				if (!g_object_class_find_property (G_OBJECT_GET_CLASS (setting), name))
-					nm_setting_vpn_add_data_item (NM_SETTING_VPN (setting), name, value);
-			} else {
-				if (!nm_streq (name, "interface-name"))
-					nm_setting_bond_add_option (NM_SETTING_BOND (setting), name, value);
-			}
-		}
-		openconnect_fix_secret_flags (setting);
-		return;
-	}
-
-	if (NM_IS_SETTING_USER (setting)) {
-		gs_unref_hashtable GHashTable *data = NULL;
-
-		data = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
-		for (iter = (const char *const*) keys; *iter; iter++) {
-			gs_free char *to_free = NULL;
-			char *value = NULL;
-			const char *name;
-
-			value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL);
-			if (!value)
-				continue;
-			name = nm_keyfile_key_decode (*iter, &to_free);
-			g_hash_table_insert (data,
-			                     g_steal_pointer (&to_free) ?: g_strdup (name),
-			                     value);
-		}
-		g_object_set (setting, NM_SETTING_USER_DATA, data, NULL);
-		return;
-	}
-
-	nm_assert_not_reached ();
-}
-
-static gsize
-unescape_semicolons (char *str)
-{
-	gsize i, j;
-
-	for (i = 0, j = 0; str[i]; ) {
-		if (str[i] == '\\' && str[i+1] == ';')
-			i++;
-		str[j++] = str[i++];;
-	}
-	nm_explicit_bzero (&str[j], i - j);
-	return j;
-}
-
-static GBytes *
-get_bytes (KeyfileReaderInfo *info,
-           const char *setting_name,
-           const char *key,
-           gboolean zero_terminate,
-           gboolean unescape_semicolon)
-{
-	nm_auto_free_secret char *tmp_string = NULL;
-	gboolean may_be_int_list = TRUE;
-	gsize length;
-	GBytes *result;
-
-	/* New format: just a string
-	 * Old format: integer list; e.g. 11;25;38;
-	 */
-	tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
-	if (!tmp_string)
-		return NULL;
-
-	/* if the string is empty, we return an empty GBytes array.
-	 * Note that for NM_SETTING_802_1X_PASSWORD_RAW both %NULL and
-	 * an empty GBytes are valid, and shall be destinguished. */
-	if (!tmp_string[0]) {
-		/* note that even if @zero_terminate is TRUE, we return an empty
-		 * byte-array. The reason is that zero_terminate is there to terminate
-		 * *valid* strings. It's not there to terminated invalid (empty) strings.
-		 */
-		return g_bytes_new_static ("", 0);
-	}
-
-	for (length = 0; tmp_string[length]; length++) {
-		const char ch = tmp_string[length];
-
-		if (   !g_ascii_isspace (ch)
-		    && !g_ascii_isdigit (ch)
-		    && ch != ';') {
-			may_be_int_list = FALSE;
-			length += strlen (&tmp_string[length]);
-			break;
-		}
-	}
-
-	/* Try to parse the string as a integer list. */
-	if (may_be_int_list && length > 0) {
-		nm_auto_free_secret_buf NMSecretBuf *bin = NULL;
-		const char *const s = tmp_string;
-		gsize i, d;
-
-		bin = nm_secret_buf_new (length / 2 + 3);
-
-#define DIGIT(c) ((c) - '0')
-		i = 0;
-		d = 0;
-		while (TRUE) {
-			int n;
-
-			/* leading whitespace */
-			while (g_ascii_isspace (s[i]))
-				i++;
-			if (s[i] == '\0')
-				break;
-			/* then expect 1 to 3 digits */
-			if (!g_ascii_isdigit (s[i])) {
-				d = 0;
-				break;
-			}
-			n = DIGIT (s[i]);
-			i++;
-			if (g_ascii_isdigit (s[i])) {
-				n = 10 * n + DIGIT (s[i]);
-				i++;
-				if (g_ascii_isdigit (s[i])) {
-					n = 10 * n + DIGIT (s[i]);
-					i++;
-				}
-			}
-			if (n > 255) {
-				d = 0;
-				break;
-			}
-
-			nm_assert (d < bin->len);
-			bin->bin[d++] = n;
-
-			/* allow whitespace after the digit. */
-			while (g_ascii_isspace (s[i]))
-				i++;
-			/* need a semicolon as separator. */
-			if (s[i] != ';') {
-				d = 0;
-				break;
-			}
-			i++;
-		}
-#undef DIGIT
-
-		/* Old format; list of ints. We already did a strict validation of the
-		 * string format before. We expect that this conversion cannot fail. */
-		if (d > 0) {
-			/* note that @zero_terminate does not add a terminating '\0' to
-			 * binary data as an integer list. If the bytes are expressed as
-			 * an integer list, all potential NUL characters are supposed to
-			 * be included there explicitly.
-			 *
-			 * However, in the spirit of defensive programming, we do append a
-			 * NUL character to the buffer, although this character is hidden
-			 * and only a mitigation for bugs. */
-
-			if (d + 10 < bin->len) {
-				/* hm, too much unused memory. Copy the memory to a suitable
-				 * sized buffer. */
-				return nm_secret_copy_to_gbytes (bin->bin, d);
-			}
-
-			nm_assert (d < bin->len);
-			bin->bin[d] = '\0';
-			return nm_secret_buf_to_gbytes_take (g_steal_pointer (&bin), d);
-		}
-	}
-
-	/* Handle as a simple string (ie, new format) */
-	if (unescape_semicolon)
-		length = unescape_semicolons (tmp_string);
-	if (zero_terminate)
-		length++;
-	if (length == 0)
-		return NULL;
-
-	result = g_bytes_new_with_free_func (tmp_string,
-	                                     length,
-	                                     (GDestroyNotify) nm_free_secret,
-	                                     tmp_string);
-	tmp_string = NULL;
-	return result;
-}
-
-static void
-ssid_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	GBytes *bytes;
-
-	bytes = get_bytes (info, setting_name, key, FALSE, TRUE);
-	if (bytes) {
-		g_object_set (setting, key, bytes, NULL);
-		g_bytes_unref (bytes);
-	} else if (!info->error) {
-		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("ignoring invalid SSID"));
-	}
-}
-
-static void
-password_raw_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	GBytes *bytes;
-
-	bytes = get_bytes (info, setting_name, key, FALSE, TRUE);
-	if (bytes) {
-		g_object_set (setting, key, bytes, NULL);
-		g_bytes_unref (bytes);
-	} else if (!info->error) {
-		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("ignoring invalid raw password"));
-	}
-}
-
-static char *
-get_cert_path (const char *base_dir, const guint8 *cert_path, gsize cert_path_len)
-{
-	const char *base;
-	char *p = NULL, *path, *tmp;
-
-	g_return_val_if_fail (base_dir != NULL, NULL);
-	g_return_val_if_fail (cert_path != NULL, NULL);
-
-	path = g_strndup ((char *) cert_path, cert_path_len);
-
-	if (path[0] == '/')
-		return path;
-
-	base = path;
-	p = strrchr (path, '/');
-	if (p)
-		base = p + 1;
-
-	tmp = g_build_path ("/", base_dir, base, NULL);
-	g_free (path);
-	return tmp;
-}
-
-static const char *certext[] = { ".pem", ".cert", ".crt", ".cer", ".p12", ".der", ".key" };
-
-static gboolean
-has_cert_ext (const char *path)
-{
-	int i;
-
-	for (i = 0; i < G_N_ELEMENTS (certext); i++) {
-		if (g_str_has_suffix (path, certext[i]))
-			return TRUE;
-	}
-	return FALSE;
-}
-
-char *
-nm_keyfile_detect_unqualified_path_scheme (const char *base_dir,
-                                           gconstpointer pdata,
-                                           gsize data_len,
-                                           gboolean consider_exists,
-                                           gboolean *out_exists)
-{
-	const char *data = pdata;
-	gboolean exists = FALSE;
-	gsize validate_len;
-	gsize path_len, pathuri_len;
-	gs_free char *path = NULL;
-	gs_free char *pathuri = NULL;
-
-	g_return_val_if_fail (base_dir && base_dir[0] == '/', NULL);
-
-	if (!pdata)
-		return NULL;
-	if (data_len == -1)
-		data_len = strlen (data);
-	if (data_len > 500 || data_len < 1)
-		return NULL;
-
-	/* If there's a trailing zero tell g_utf8_validate() to validate until the zero */
-	if (data[data_len - 1] == '\0') {
-		/* setting it to -1, would mean we accept data to contain NUL characters before the
-		 * end. Don't accept any NUL in [0 .. data_len-1[ . */
-		validate_len = data_len - 1;
-	} else
-		validate_len = data_len;
-	if (   validate_len == 0
-	    || g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE)
-		 return NULL;
-
-	/* Might be a bare path without the file:// prefix; in that case
-	 * if it's an absolute path, use that, otherwise treat it as a
-	 * relative path to the current directory.
-	 */
-
-	path = get_cert_path (base_dir, (const guint8 *) data, data_len);
-
-	/* FIXME(keyfile-parse-in-memory): it is wrong that keyfile reader makes decisions based on
-	 * the file systems content. The serialization/parsing should be entirely in-memory. */
-	if (   !memchr (data, '/', data_len)
-	    && !has_cert_ext (path)) {
-		if (!consider_exists)
-			return NULL;
-		exists = g_file_test (path, G_FILE_TEST_EXISTS);
-		if (!exists)
-			return NULL;
-	} else if (out_exists)
-		exists = g_file_test (path, G_FILE_TEST_EXISTS);
-
-	/* Construct the proper value as required for the PATH scheme.
-	 *
-	 * When returning TRUE, we must also be sure that @data_len does not look like
-	 * the deprecated format of list of integers. With this implementation that is the
-	 * case, as long as @consider_exists is FALSE. */
-	path_len = strlen (path);
-	pathuri_len = (NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) + 1) + path_len;
-	pathuri = g_new (char, pathuri_len);
-	memcpy (pathuri, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH));
-	memcpy (&pathuri[NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)], path, path_len + 1);
-	if (nm_setting_802_1x_check_cert_scheme (pathuri, pathuri_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PATH)
-		return NULL;
-
-	NM_SET_OUT (out_exists, exists);
-	return g_steal_pointer (&pathuri);
-}
-
-#define HAS_SCHEME_PREFIX(bin, bin_len, scheme) \
-	({ \
-		const char *const _bin = (bin); \
-		const gsize _bin_len = (bin_len); \
-		\
-		nm_assert (_bin && _bin_len > 0); \
-		\
-		(   _bin_len > NM_STRLEN (scheme) + 1 \
-		 && _bin[_bin_len - 1] == '\0' \
-		 && memcmp (_bin, scheme, NM_STRLEN (scheme)) == 0); \
-	})
-
-static void
-cert_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_unref_bytes GBytes *bytes = NULL;
-	const char *bin = NULL;
-	gsize bin_len = 0;
-	char *path;
-	gboolean path_exists;
-
-	bytes = get_bytes (info, setting_name, key, TRUE, FALSE);
-	if (bytes)
-		bin = g_bytes_get_data (bytes, &bin_len);
-	if (bin_len == 0) {
-		if (!info->error) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid key/cert value"));
-		}
-		return;
-	}
-
-	if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)) {
-		const char *path2 = &bin[NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)];
-		gs_free char *path2_free = NULL;
-
-		if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PATH) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid key/cert value path \"%s\""), bin);
-			return;
-		}
-
-		g_object_set (setting, key, bytes, NULL);
-
-		if (path2[0] != '/') {
-			/* we want to read absolute paths because we use keyfile as exchange
-			 * between different processes which might not have the same cwd. */
-			path2_free = get_cert_path (info->base_dir, (const guint8 *) path2,
-			                            bin_len - NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) - 1);
-			path2 = path2_free;
-		}
-
-		/* FIXME(keyfile-parse-in-memory): keyfile reader must not access the file system and
-		 * (in a first step) only operate in memory-only. If the presence of files should be checked,
-		 * then by invoking a callback (and possibly keyfile settings plugin would
-		 * collect the file names to be checked and check them later). */
-		if (!g_file_test (path2, G_FILE_TEST_EXISTS)) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
-			             _("certificate or key file '%s' does not exist"),
-			             path2);
-		}
-		return;
-	}
-
-	if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11)) {
-		if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid PKCS#11 URI \"%s\""), bin);
-			return;
-		}
-
-		g_object_set (setting, key, bytes, NULL);
-		return;
-	}
-
-	if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)) {
-		const char *cdata = bin + NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB);
-		gsize cdata_len = bin_len - NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB) - 1;
-		gs_free guchar *bin_decoded = NULL;
-		gsize bin_decoded_len = 0;
-		gsize i;
-		gboolean valid_base64;
-		gs_unref_bytes GBytes *val = NULL;
-
-		/* Let's be strict here. We expect valid base64, no funny stuff!!
-		 * We didn't write such invalid data ourselfes and refuse to read it as blob. */
-		if ((valid_base64 = (cdata_len % 4 == 0))) {
-			for (i = 0; i < cdata_len; i++) {
-				char c = cdata[i];
-
-				if (!(   (c >= 'a' && c <= 'z')
-				      || (c >= 'A' && c <= 'Z')
-				      || (c >= '0' && c <= '9')
-				      || (c == '+' || c == '/'))) {
-					if (c != '=' || i < cdata_len - 2)
-						valid_base64 = FALSE;
-					else {
-						for (; i < cdata_len; i++) {
-							if (cdata[i] != '=')
-								valid_base64 = FALSE;
-						}
-					}
-					break;
-				}
-			}
-		}
-		if (valid_base64)
-			bin_decoded = g_base64_decode (cdata, &bin_decoded_len);
-
-		if (bin_decoded_len == 0) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid key/cert value data:;base64, is not base64"));
-			return;
-		}
-
-		if (nm_setting_802_1x_check_cert_scheme (bin_decoded, bin_decoded_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) {
-			/* The blob probably starts with "file://". Setting the cert data will confuse NMSetting8021x.
-			 * In fact this is a limitation of NMSetting8021x which does not support setting blobs that start
-			 * with file://. Just warn and return TRUE to signal that we ~handled~ the setting. */
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid key/cert value data:;base64,file://"));
-			return;
-		}
-
-		val = g_bytes_new_take (g_steal_pointer (&bin_decoded), bin_decoded_len);
-		g_object_set (setting, key, val, NULL);
-		return;
-	}
-
-	/* If not, it might be a plain path */
-	path = nm_keyfile_detect_unqualified_path_scheme (info->base_dir, bin, bin_len, TRUE, &path_exists);
-	if (path) {
-		gs_unref_bytes GBytes *val = NULL;
-
-		/* Construct the proper value as required for the PATH scheme */
-		val = g_bytes_new_take (path, strlen (path) + 1);
-		g_object_set (setting, key, val, NULL);
-
-		/* Warn if the certificate didn't exist */
-		if (!path_exists) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
-			             _("certificate or key file '%s' does not exist"),
-			             path);
-		}
-		return;
-	}
-
-	if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) {
-		/* The blob probably starts with "file://" but contains invalid characters for a path.
-		 * Setting the cert data will confuse NMSetting8021x.
-		 * In fact, NMSetting8021x does not support setting such binary data, so just warn and
-		 * continue. */
-		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("invalid key/cert value is not a valid blob"));
-		return;
-	}
-
-	g_object_set (setting, key, bytes, NULL);
-}
-
-static int
-_parity_from_char (int ch)
-{
-#if NM_MORE_ASSERTS > 5
-	{
-		static char check = 0;
-
-		if (check == 0) {
-			nm_auto_unref_gtypeclass GEnumClass *klass = g_type_class_ref (NM_TYPE_SETTING_SERIAL_PARITY);
-			guint i;
-
-			check = 1;
-
-			/* In older versions, parity was G_TYPE_CHAR/gint8, and the character
-			 * value was stored as integer.
-			 * For example parity=69 equals parity=E, meaning NM_SETTING_SERIAL_PARITY_EVEN.
-			 *
-			 * That means, certain values are reserved. Assert that these numbers
-			 * are not reused when we extend NMSettingSerialParity enum.
-			 * Actually, since NM_SETTING_SERIAL_PARITY is g_param_spec_enum(),
-			 * we anyway cannot extend the enum without breaking API...
-			 *
-			 * [1] commit "a91e60902e libnm-core: make NMSettingSerial:parity an enum"
-			 * [2] https://cgit.freedesktop.org/NetworkManager/NetworkManager/commit/?id=a91e60902eabae1de93d61323dae6ac894b5d40f
-			 */
-			g_assert (G_IS_ENUM_CLASS (klass));
-			for (i = 0; i < klass->n_values; i++) {
-				const GEnumValue *v = &klass->values[i];
-				int num = v->value;
-
-				g_assert (_parity_from_char (num) == -1);
-				g_assert (!NM_IN_SET (num, 'e', 'E', 'o', 'O', 'n', 'N'));
-			}
-		}
-	}
-#endif
-
-	switch (ch) {
-	case 'E':
-	case 'e':
-		return NM_SETTING_SERIAL_PARITY_EVEN;
-	case 'O':
-	case 'o':
-		return NM_SETTING_SERIAL_PARITY_ODD;
-	case 'N':
-	case 'n':
-		return NM_SETTING_SERIAL_PARITY_NONE;
-	}
-
-	return -1;
-}
-
-static void
-parity_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_free_error GError *err = NULL;
-	int parity;
-	gs_free char *tmp_str = NULL;
-	gint64 i64;
-
-	/* Keyfile traditionally stored this as the ASCII value for 'E', 'o', or 'n'.
-	 * We now accept either that or the (case-insensitive) character itself (but
-	 * still always write it the old way, for backward compatibility).
-	 */
-	tmp_str = nm_keyfile_plugin_kf_get_value (info->keyfile, setting_name, key, &err);
-	if (err)
-		goto out_err;
-
-	if (   tmp_str
-	    && tmp_str[0] != '\0'
-	    && tmp_str[1] == '\0') {
-		/* the ASCII characters like 'E' are taken directly... */
-		parity = _parity_from_char (tmp_str[0]);
-		if (parity >= 0)
-			goto parity_good;
-	}
-
-	i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MININT64);
-	if (   i64 != G_MININT64
-	    && errno == 0) {
-
-		if ((parity = _parity_from_char (i64)) >= 0) {
-			/* another oddity: the string is a valid number. However, if the numeric values
-			 * is one of the supported ASCII codes, accept it (like 69 for 'E').
-			 */
-			goto parity_good;
-		}
-
-		/* Finally, take the numeric value as is. */
-		parity = i64;
-		goto parity_good;
-	}
-
-	handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-	             _("invalid parity value '%s'"),
-	             tmp_str ?: "");
-	return;
-
-parity_good:
-	nm_g_object_set_property_enum (G_OBJECT (setting), key, NM_TYPE_SETTING_SERIAL_PARITY, parity, &err);
-
-out_err:
-	if (!err)
-		return;
-	if (   err->domain == G_KEY_FILE_ERROR
-	    && NM_IN_SET (err->code, G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
-	                             G_KEY_FILE_ERROR_KEY_NOT_FOUND)) {
-		/* ignore such errors. The key is not present. */
-		return;
-	}
-	handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-	             _("invalid setting: %s"), err->message);
-}
-
-static void
-team_config_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_free char *conf = NULL;
-	gs_free_error GError *error = NULL;
-
-	conf = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
-
-	g_object_set (G_OBJECT (setting), key, conf, NULL);
-
-	if (   conf
-	    && !nm_setting_verify (setting, NULL, &error)) {
-		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("ignoring invalid team configuration: %s"),
-		             error->message);
-		g_object_set (G_OBJECT (setting), key, NULL, NULL);
-	}
-}
-
-static void
-bridge_vlan_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	gs_unref_ptrarray GPtrArray *vlans = NULL;
-	gs_free char *value = NULL;
-	gs_free const char **strv = NULL;
-	const char *const *iter;
-	GError *local = NULL;
-	NMBridgeVlan *vlan;
-
-	value = nm_keyfile_plugin_kf_get_string (info->keyfile,
-	                                         nm_setting_get_name (setting),
-	                                         key,
-	                                         NULL);
-	if (!value || !value[0])
-		return;
-
-	vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref);
-
-	strv = nm_utils_escaped_tokens_split (value, ",");
-	if (strv) {
-		for (iter = strv; *iter; iter++) {
-			vlan = nm_bridge_vlan_from_str (*iter, &local);
-			if (!vlan) {
-				handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-				             "invalid bridge VLAN: %s", local->message);
-				g_clear_error (&local);
-				continue;
-			}
-			g_ptr_array_add (vlans, vlan);
-		}
-	}
-
-	if (vlans->len > 0)
-		g_object_set (setting, key, vlans, NULL);
-}
-
-static void
-qdisc_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_unref_ptrarray GPtrArray *qdiscs = NULL;
-	gs_strfreev char **keys = NULL;
-	gsize n_keys = 0;
-	int i;
-
-	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
-	if (n_keys == 0)
-		return;
-
-	qdiscs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_qdisc_unref);
-
-	for (i = 0; i < n_keys; i++) {
-		NMTCQdisc *qdisc;
-		const char *qdisc_parent;
-		gs_free char *qdisc_rest = NULL;
-		gs_free char *qdisc_str = NULL;
-		gs_free_error GError *err = NULL;
-
-		if (!g_str_has_prefix (keys[i], "qdisc."))
-			continue;
-
-		qdisc_parent = keys[i] + sizeof ("qdisc.") - 1;
-		qdisc_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL);
-		qdisc_str = g_strdup_printf ("%s%s %s",
-		                             _nm_utils_parse_tc_handle (qdisc_parent, NULL) != TC_H_UNSPEC ? "parent " : "",
-		                             qdisc_parent,
-		                             qdisc_rest);
-
-		qdisc = nm_utils_tc_qdisc_from_str (qdisc_str, &err);
-		if (!qdisc) {
-			handle_warn (info, keys[i], NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid qdisc: %s"),
-			             err->message);
-		} else {
-			g_ptr_array_add (qdiscs, qdisc);
-		}
-	}
-
-	if (qdiscs->len >= 1)
-		g_object_set (setting, key, qdiscs, NULL);
-}
-
-static void
-tfilter_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_unref_ptrarray GPtrArray *tfilters = NULL;
-	gs_strfreev char **keys = NULL;
-	gsize n_keys = 0;
-	int i;
-
-	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
-	if (n_keys == 0)
-		return;
-
-	tfilters = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_tfilter_unref);
-
-	for (i = 0; i < n_keys; i++) {
-		NMTCTfilter *tfilter;
-		const char *tfilter_parent;
-		gs_free char *tfilter_rest = NULL;
-		gs_free char *tfilter_str = NULL;
-		gs_free_error GError *err = NULL;
-
-		if (!g_str_has_prefix (keys[i], "tfilter."))
-			continue;
-
-		tfilter_parent = keys[i] + sizeof ("tfilter.") - 1;
-		tfilter_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL);
-		tfilter_str = g_strdup_printf ("%s%s %s",
-		                             _nm_utils_parse_tc_handle (tfilter_parent, NULL) != TC_H_UNSPEC ? "parent " : "",
-		                             tfilter_parent,
-		                             tfilter_rest);
-
-		tfilter = nm_utils_tc_tfilter_from_str (tfilter_str, &err);
-		if (!tfilter) {
-			handle_warn (info, keys[i], NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid tfilter: %s"),
-			             err->message);
-		} else {
-			g_ptr_array_add (tfilters, tfilter);
-		}
-	}
-
-	if (tfilters->len >= 1)
-		g_object_set (setting, key, tfilters, NULL);
-}
-
-/*****************************************************************************/
-
-/* Some setting properties also contain setting names, such as
- * NMSettingConnection's 'type' property (which specifies the base type of the
- * connection, eg ethernet or wifi) or the 802-11-wireless setting's
- * 'security' property which specifies whether or not the AP requires
- * encryption.  This function handles translating those properties' values
- * from the real setting name to the more-readable alias.
- */
-static void
-setting_alias_writer (KeyfileWriterInfo *info,
-                      NMSetting *setting,
-                      const char *key,
-                      const GValue *value)
-{
-	const char *str, *alias;
-
-	str = g_value_get_string (value);
-	alias = nm_keyfile_plugin_get_alias_for_setting_name (str);
-	nm_keyfile_plugin_kf_set_string (info->keyfile,
-	                                 nm_setting_get_name (setting),
-	                                 key,
-	                                 alias ?: str);
-}
-
-static void
-sriov_vfs_writer (KeyfileWriterInfo *info,
-                  NMSetting *setting,
-                  const char *key,
-                  const GValue *value)
-{
-	GPtrArray *vfs;
-	guint i;
-
-	vfs = g_value_get_boxed (value);
-	if (!vfs)
-		return;
-
-	for (i = 0; i < vfs->len; i++) {
-		const NMSriovVF *vf = vfs->pdata[i];
-		gs_free char *kf_value = NULL;
-		char kf_key[32];
-
-		kf_value = nm_utils_sriov_vf_to_str (vf, TRUE, NULL);
-		if (!kf_value)
-			continue;
-
-		nm_sprintf_buf (kf_key, "vf.%u", nm_sriov_vf_get_index (vf));
-
-		nm_keyfile_plugin_kf_set_string (info->keyfile,
-		                                 nm_setting_get_name (setting),
-		                                 kf_key,
-		                                 kf_value);
-	}
-}
-
-static void
-write_array_of_uint (GKeyFile *file,
-                     NMSetting *setting,
-                     const char *key,
-                     const GValue *value)
-{
-	GArray *array;
-	guint i;
-	gs_free int *tmp_array = NULL;
-
-	array = (GArray *) g_value_get_boxed (value);
-	if (!array || !array->len)
-		return;
-
-	g_return_if_fail (g_array_get_element_size (array) == sizeof (guint));
-
-	tmp_array = g_new (int, array->len);
-	for (i = 0; i < array->len; i++) {
-		guint v = g_array_index (array, guint, i);
-
-		if (v > G_MAXINT)
-			g_return_if_reached ();
-		tmp_array[i] = (int) v;
-	}
-
-	nm_keyfile_plugin_kf_set_integer_list (file, nm_setting_get_name (setting), key, tmp_array, array->len);
-}
-
-static void
-dns_writer (KeyfileWriterInfo *info,
-            NMSetting *setting,
-            const char *key,
-            const GValue *value)
-{
-	char **list;
-
-	list = g_value_get_boxed (value);
-	if (list && list[0]) {
-		nm_keyfile_plugin_kf_set_string_list (info->keyfile, nm_setting_get_name (setting), key,
-		                                      (const char **) list, g_strv_length (list));
-	}
-}
-
-static void
-ip6_addr_gen_mode_writer (KeyfileWriterInfo *info,
-                          NMSetting *setting,
-                          const char *key,
-                          const GValue *value)
-{
-	NMSettingIP6ConfigAddrGenMode addr_gen_mode;
-	gs_free char *str = NULL;
-
-	addr_gen_mode = (NMSettingIP6ConfigAddrGenMode) g_value_get_int (value);
-	str = nm_utils_enum_to_str (nm_setting_ip6_config_addr_gen_mode_get_type (),
-	                            addr_gen_mode);
-	nm_keyfile_plugin_kf_set_string (info->keyfile,
-	                                 nm_setting_get_name (setting),
-	                                 key,
-	                                 str);
-}
-
-static void
-write_ip_values (GKeyFile *file,
-                 const char *setting_name,
-                 GPtrArray *array,
-                 const char *gateway,
-                 gboolean is_route)
-{
-	nm_auto_free_gstring GString *output = NULL;
-	int addr_family;
-	guint i;
-	const char *addr;
-	const char *gw;
-	guint32 plen;
-	char key_name[64];
-	char *key_name_idx;
-
-	if (!array->len)
-		return;
-
-	addr_family =   nm_streq (setting_name, NM_SETTING_IP4_CONFIG_SETTING_NAME)
-	              ? AF_INET
-	              : AF_INET6;
-
-	strcpy (key_name, is_route ? "route" : "address");
-	key_name_idx = key_name + strlen (key_name);
-
-	output = g_string_sized_new (2*INET_ADDRSTRLEN + 10);
-	for (i = 0; i < array->len; i++) {
-		gint64 metric = -1;
-
-		if (is_route) {
-			NMIPRoute *route = array->pdata[i];
-
-			addr = nm_ip_route_get_dest (route);
-			plen = nm_ip_route_get_prefix (route);
-			gw = nm_ip_route_get_next_hop (route);
-			metric = nm_ip_route_get_metric (route);
-		} else {
-			NMIPAddress *address = array->pdata[i];
-
-			addr = nm_ip_address_get_address (address);
-			plen = nm_ip_address_get_prefix (address);
-			gw =   (i == 0)
-			     ? gateway
-			     : NULL;
-		}
-
-		g_string_set_size (output, 0);
-		g_string_append_printf (output, "%s/%u", addr, plen);
-		if (   metric != -1
-		    || gw) {
-			/* Older versions of the plugin do not support the form
-			 * "a.b.c.d/plen,,metric", so, we always have to write the
-			 * gateway, even if there isn't one.
-			 * The current version supports reading of the above form.
-			 */
-			if (!gw) {
-				if (addr_family == AF_INET)
-					gw = "0.0.0.0";
-				else
-					gw = "::";
-			}
-
-			g_string_append_printf (output, ",%s", gw);
-			if (   is_route
-			    && metric != -1)
-				g_string_append_printf (output, ",%lu", (unsigned long) metric);
-		}
-
-		sprintf (key_name_idx, "%u", i + 1);
-		nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, output->str);
-
-		if (is_route) {
-			gs_free char *attributes = NULL;
-
-			attributes = nm_utils_format_variant_attributes (_nm_ip_route_get_attributes (array->pdata[i]),
-			                                                 ',', '=');
-			if (attributes) {
-				g_strlcat (key_name, "_options", sizeof (key_name));
-				nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, attributes);
-			}
-		}
-	}
-}
-
-static void
-addr_writer (KeyfileWriterInfo *info,
-             NMSetting *setting,
-             const char *key,
-             const GValue *value)
-{
-	GPtrArray *array;
-	const char *setting_name = nm_setting_get_name (setting);
-	const char *gateway = nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (setting));
-
-	array = (GPtrArray *) g_value_get_boxed (value);
-	if (array && array->len)
-		write_ip_values (info->keyfile, setting_name, array, gateway, FALSE);
-}
-
-static void
-route_writer (KeyfileWriterInfo *info,
-              NMSetting *setting,
-              const char *key,
-              const GValue *value)
-{
-	GPtrArray *array;
-	const char *setting_name = nm_setting_get_name (setting);
-
-	array = (GPtrArray *) g_value_get_boxed (value);
-	if (array && array->len)
-		write_ip_values (info->keyfile, setting_name, array, NULL, TRUE);
-}
-
-static void
-bridge_vlan_writer (KeyfileWriterInfo *info,
-                    NMSetting *setting,
-                    const char *key,
-                    const GValue *value)
-{
-	NMBridgeVlan *vlan;
-	GPtrArray *vlans;
-	GString *string;
-	guint i;
-
-	vlans = (GPtrArray *) g_value_get_boxed (value);
-	if (!vlans || !vlans->len)
-		return;
-
-	string = g_string_new ("");
-	for (i = 0; i < vlans->len; i++) {
-		gs_free char *vlan_str = NULL;
-
-		vlan = vlans->pdata[i];
-		vlan_str = nm_bridge_vlan_to_str (vlan, NULL);
-		if (!vlan_str)
-			continue;
-		if (string->len > 0)
-			g_string_append (string, ",");
-		nm_utils_escaped_tokens_escape_gstr_assert (vlan_str, ",", string);
-	}
-
-	nm_keyfile_plugin_kf_set_string (info->keyfile,
-	                                 nm_setting_get_name (setting),
-	                                 "vlans",
-	                                 string->str);
-
-	g_string_free (string, TRUE);
-}
-
-
-#define ETHERNET_S390_OPTIONS_GROUP_NAME "ethernet-s390-options"
-
-static void
-wired_s390_options_parser_full (KeyfileReaderInfo *info,
-                                const NMMetaSettingInfo *setting_info,
-                                const NMSettInfoProperty *property_info,
-                                const ParseInfoProperty *pip,
-                                NMSetting *setting)
-{
-	NMSettingWired *s_wired = NM_SETTING_WIRED (setting);
-	gs_strfreev char **keys = NULL;
-	gsize n_keys;
-	gsize i;
-
-	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, ETHERNET_S390_OPTIONS_GROUP_NAME, &n_keys, NULL);
-	for (i = 0; i < n_keys; i++) {
-		gs_free char *value = NULL;
-		gs_free char *key_to_free = NULL;
-
-		value = nm_keyfile_plugin_kf_get_string (info->keyfile,
-		                                         ETHERNET_S390_OPTIONS_GROUP_NAME,
-		                                         keys[i],
-		                                         NULL);
-		if (!value)
-			continue;
-
-		nm_setting_wired_add_s390_option (s_wired,
-		                                  nm_keyfile_key_decode (keys[i],
-		                                                         &key_to_free),
-		                                  value);
-	}
-}
-
-static void
-wired_s390_options_writer_full (KeyfileWriterInfo *info,
-                                const NMMetaSettingInfo *setting_info,
-                                const NMSettInfoProperty *property_info,
-                                const ParseInfoProperty *pip,
-                                NMSetting *setting)
-{
-	NMSettingWired *s_wired = NM_SETTING_WIRED (setting);
-	guint i, n;
-
-	n = nm_setting_wired_get_num_s390_options (s_wired);
-	for (i = 0; i < n; i++) {
-		const char *opt_key;
-		const char *opt_val;
-		gs_free char *key_to_free = NULL;
-
-		nm_setting_wired_get_s390_option (s_wired, i, &opt_key, &opt_val);
-		nm_keyfile_plugin_kf_set_string (info->keyfile,
-		                                 ETHERNET_S390_OPTIONS_GROUP_NAME,
-		                                 nm_keyfile_key_encode (opt_key, &key_to_free),
-		                                 opt_val);
-	}
-}
-
-static void
-ip_routing_rule_writer_full (KeyfileWriterInfo *info,
-                             const NMMetaSettingInfo *setting_info,
-                             const NMSettInfoProperty *property_info,
-                             const ParseInfoProperty *pip,
-                             NMSetting *setting)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	NMSettingIPConfig *s_ip = NM_SETTING_IP_CONFIG (setting);
-	guint i, j, n;
-	char key_name_full[100] = "routing-rule";
-	char *key_name_num = &key_name_full[NM_STRLEN ("routing-rule")];
-
-	n = nm_setting_ip_config_get_num_routing_rules (s_ip);
-	j = 0;
-	for (i = 0; i < n; i++) {
-		NMIPRoutingRule *rule = nm_setting_ip_config_get_routing_rule (s_ip, i);
-		gs_free char *str = NULL;
-
-		str = nm_ip_routing_rule_to_string (rule,
-		                                    NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE,
-		                                    NULL,
-		                                    NULL);
-		if (!str)
-			continue;
-
-		sprintf (key_name_num, "%u", ++j);
-		nm_keyfile_plugin_kf_set_string (info->keyfile,
-		                                 setting_name,
-		                                 key_name_full,
-		                                 str);
-	}
-}
-
-static void
-qdisc_writer (KeyfileWriterInfo *info,
-              NMSetting *setting,
-              const char *key,
-              const GValue *value)
-{
-	gsize i;
-	GPtrArray *array;
-
-	array = (GPtrArray *) g_value_get_boxed (value);
-	if (!array || !array->len)
-		return;
-
-	for (i = 0; i < array->len; i++) {
-		NMTCQdisc *qdisc = array->pdata[i];
-		GString *key_name = g_string_sized_new (16);
-		GString *value_str = g_string_sized_new (60);
-
-		g_string_append (key_name, "qdisc.");
-		_nm_utils_string_append_tc_parent (key_name, NULL,
-		                                   nm_tc_qdisc_get_parent (qdisc));
-		_nm_utils_string_append_tc_qdisc_rest (value_str, qdisc);
-
-		nm_keyfile_plugin_kf_set_string (info->keyfile,
-		                                 NM_SETTING_TC_CONFIG_SETTING_NAME,
-		                                 key_name->str,
-		                                 value_str->str);
-
-		g_string_free (key_name, TRUE);
-		g_string_free (value_str, TRUE);
-	}
-}
-
-static void
-tfilter_writer (KeyfileWriterInfo *info,
-              NMSetting *setting,
-              const char *key,
-              const GValue *value)
-{
-	gsize i;
-	GPtrArray *array;
-
-	array = (GPtrArray *) g_value_get_boxed (value);
-	if (!array || !array->len)
-		return;
-
-	for (i = 0; i < array->len; i++) {
-		NMTCTfilter *tfilter = array->pdata[i];
-		GString *key_name = g_string_sized_new (16);
-		GString *value_str = g_string_sized_new (60);
-
-		g_string_append (key_name, "tfilter.");
-		_nm_utils_string_append_tc_parent (key_name, NULL,
-		                                   nm_tc_tfilter_get_parent (tfilter));
-		_nm_utils_string_append_tc_tfilter_rest (value_str, tfilter, NULL);
-
-		nm_keyfile_plugin_kf_set_string (info->keyfile,
-		                                 NM_SETTING_TC_CONFIG_SETTING_NAME,
-		                                 key_name->str,
-		                                 value_str->str);
-
-		g_string_free (key_name, TRUE);
-		g_string_free (value_str, TRUE);
-	}
-}
-
-static void
-write_hash_of_string (GKeyFile *file,
-                      NMSetting *setting,
-                      const char *key,
-                      const GValue *value)
-{
-	GHashTable *hash;
-	const char *group_name = nm_setting_get_name (setting);
-	gboolean vpn_secrets = FALSE;
-	gs_free const char **keys = NULL;
-	guint i, l;
-
-	nm_assert (   (NM_IS_SETTING_VPN (setting)  && nm_streq (key, NM_SETTING_VPN_DATA))
-	           || (NM_IS_SETTING_VPN (setting)  && nm_streq (key, NM_SETTING_VPN_SECRETS))
-	           || (NM_IS_SETTING_BOND (setting) && nm_streq (key, NM_SETTING_BOND_OPTIONS))
-	           || (NM_IS_SETTING_USER (setting) && nm_streq (key, NM_SETTING_USER_DATA)));
-
-	/* Write VPN secrets out to a different group to keep them separate */
-	if (   NM_IS_SETTING_VPN (setting)
-	    && nm_streq (key, NM_SETTING_VPN_SECRETS)) {
-		group_name = NM_KEYFILE_GROUP_VPN_SECRETS;
-		vpn_secrets = TRUE;
-	}
-
-	hash = g_value_get_boxed (value);
-
-	keys = nm_utils_strdict_get_keys (hash, TRUE, &l);
-	for (i = 0; i < l; i++) {
-		gs_free char *to_free = NULL;
-		const char *property, *data;
-
-		property = keys[i];
-
-		/* Handle VPN secrets specially; they are nested in the property's hash;
-		 * we don't want to write them if the secret is not saved, not required,
-		 * or owned by a user's secret agent.
-		 */
-		if (vpn_secrets) {
-			NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
-
-			if (!nm_setting_get_secret_flags (setting, property, &secret_flags, NULL))
-				nm_assert_not_reached ();
-			if (!_secret_flags_persist_secret (secret_flags))
-				continue;
-		}
-
-		data = g_hash_table_lookup (hash, property);
-		nm_keyfile_plugin_kf_set_string (file, group_name,
-		                                 nm_keyfile_key_encode (property, &to_free),
-		                                 data);
-	}
-}
-
-static void
-ssid_writer (KeyfileWriterInfo *info,
-             NMSetting *setting,
-             const char *key,
-             const GValue *value)
-{
-	GBytes *bytes;
-	const guint8 *ssid_data;
-	gsize ssid_len;
-	const char *setting_name = nm_setting_get_name (setting);
-	gboolean new_format = TRUE;
-	gsize semicolons = 0;
-	gsize i;
-
-	g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES));
-
-	bytes = g_value_get_boxed (value);
-	if (!bytes)
-		return;
-	ssid_data = g_bytes_get_data (bytes, &ssid_len);
-	if (!ssid_data || !ssid_len) {
-		nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, "");
-		return;
-	}
-
-	/* Check whether each byte is printable.  If not, we have to use an
-	 * integer list, otherwise we can just use a string.
-	 */
-	for (i = 0; i < ssid_len; i++) {
-		const char c = ssid_data[i];
-
-		if (!g_ascii_isprint (c)) {
-			new_format = FALSE;
-			break;
-		}
-		if (c == ';')
-			semicolons++;
-	}
-
-	if (new_format) {
-		gs_free char *ssid = NULL;
-
-		if (semicolons == 0)
-			ssid = g_strndup ((char *) ssid_data, ssid_len);
-		else {
-			/* Escape semicolons with backslashes to make strings
-			 * containing ';', such as '16;17;' unambiguous */
-			gsize j = 0;
-
-			ssid = g_malloc (ssid_len + semicolons + 1);
-			for (i = 0; i < ssid_len; i++) {
-				if (ssid_data[i] == ';')
-					ssid[j++] = '\\';
-				ssid[j++] = ssid_data[i];
-			}
-			ssid[j] = '\0';
-		}
-		nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, ssid);
-	} else
-		nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_name, key, ssid_data, ssid_len);
-}
-
-static void
-password_raw_writer (KeyfileWriterInfo *info,
-                     NMSetting *setting,
-                     const char *key,
-                     const GValue *value)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	GBytes *array;
-	gsize len;
-	const guint8 *data;
-
-	g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES));
-
-	array = (GBytes *) g_value_get_boxed (value);
-	if (!array)
-		return;
-	data = g_bytes_get_data (array, &len);
-	if (!data)
-		len = 0;
-	nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_name, key, data, len);
-}
-
-/*****************************************************************************/
-
-static void
-cert_writer_default (NMConnection *connection,
-                     GKeyFile *file,
-                     NMKeyfileWriteTypeDataCert *cert_data)
-{
-	const char *setting_name = nm_setting_get_name (NM_SETTING (cert_data->setting));
-	NMSetting8021xCKScheme scheme;
-
-	scheme = cert_data->vtable->scheme_func (cert_data->setting);
-	if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
-		gs_free char *path_free = NULL;
-		gs_free char *base_dir = NULL;
-		gs_free char *tmp = NULL;
-		const char *path;
-
-		path = cert_data->vtable->path_func (cert_data->setting);
-		g_assert (path);
-
-		/* If the path is relative, make it an absolute path.
-		 * Relative paths make a keyfile not easily usable in another
-		 * context. */
-		if (path[0] && path[0] != '/') {
-			base_dir = g_get_current_dir ();
-			path_free = g_strconcat (base_dir, "/", path, NULL);
-			path = path_free;
-		} else
-			base_dir = g_path_get_dirname (path);
-
-		/* path cannot start with "file://" or "data:;base64,", because it is an absolute path.
-		 * Still, make sure that a prefix-less path will be recognized. This can happen
-		 * for example if the path is longer then 500 chars. */
-		tmp = nm_keyfile_detect_unqualified_path_scheme (base_dir, path, -1, FALSE, NULL);
-		if (tmp)
-			nm_clear_g_free (&tmp);
-		else {
-			tmp = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL);
-			path = tmp;
-		}
-
-		/* Path contains at least a '/', hence it cannot be recognized as the old
-		 * binary format consisting of a list of integers. */
-
-		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key, path);
-	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
-		GBytes *blob;
-		const guint8 *blob_data;
-		gsize blob_len;
-		gs_free char *blob_base64 = NULL;
-		gs_free char *val = NULL;
-
-		blob = cert_data->vtable->blob_func (cert_data->setting);
-		g_assert (blob);
-		blob_data = g_bytes_get_data (blob, &blob_len);
-
-		blob_base64 = g_base64_encode (blob_data, blob_len);
-		val = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB, blob_base64, NULL);
-
-		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key, val);
-	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
-		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key,
-		                                 cert_data->vtable->uri_func (cert_data->setting));
-	} else {
-		/* scheme_func() returns UNKNOWN in all other cases. The only valid case
-		 * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this
-		 * case, we don't expect the writer to be called, because the default value
-		 * will not be serialized.
-		 * The only other reason for the scheme to be UNKNOWN is an invalid cert.
-		 * But our connection verifies, so that cannot happen either. */
-		g_return_if_reached ();
-	}
-}
-
-static void
-cert_writer (KeyfileWriterInfo *info,
-             NMSetting *setting,
-             const char *key,
-             const GValue *value)
-{
-	const NMSetting8021xSchemeVtable *objtype = NULL;
-	guint i;
-	NMKeyfileWriteTypeDataCert type_data = { 0 };
-
-	for (i = 0; nm_setting_8021x_scheme_vtable[i].setting_key; i++) {
-		if (nm_streq0 (nm_setting_8021x_scheme_vtable[i].setting_key, key)) {
-			objtype = &nm_setting_8021x_scheme_vtable[i];
-			break;
-		}
-	}
-	if (!objtype)
-		g_return_if_reached ();
-
-	type_data.setting = NM_SETTING_802_1X (setting);
-	type_data.vtable = objtype;
-
-	if (info->handler) {
-		if (info->handler (info->connection,
-		                   info->keyfile,
-		                   NM_KEYFILE_WRITE_TYPE_CERT,
-		                   &type_data,
-		                   info->user_data,
-		                   &info->error))
-			return;
-		if (info->error)
-			return;
-	}
-
-	cert_writer_default (info->connection, info->keyfile, &type_data);
-}
-
-/*****************************************************************************/
-
-struct _ParseInfoProperty {
-	const char *property_name;
-	union {
-		void (*parser) (KeyfileReaderInfo *info,
-		                NMSetting *setting,
-		                const char *key);
-		void (*parser_full) (KeyfileReaderInfo *info,
-		                     const NMMetaSettingInfo *setting_info,
-		                     const NMSettInfoProperty *property_info,
-		                     const ParseInfoProperty *pip,
-		                     NMSetting *setting);
-	};
-	union {
-		void (*writer) (KeyfileWriterInfo *info,
-		                NMSetting *setting,
-		                const char *key,
-		                const GValue *value);
-		void (*writer_full) (KeyfileWriterInfo *info,
-		                     const NMMetaSettingInfo *setting_info,
-		                     const NMSettInfoProperty *property_info,
-		                     const ParseInfoProperty *pip,
-		                     NMSetting *setting);
-	};
-	bool parser_skip;
-	bool parser_no_check_key:1;
-	bool writer_skip:1;
-	bool has_writer_full:1;
-	bool has_parser_full:1;
-
-	/* usually, we skip to write values that have their
-	 * default value. By setting this flag to TRUE, also
-	 * default values are written. */
-	bool writer_persist_default:1;
-};
-
-#define PARSE_INFO_PROPERTY(_property_name, ...) \
-	(&((const ParseInfoProperty) { \
-		.property_name = _property_name, \
-		__VA_ARGS__ \
-	}))
-
-#define PARSE_INFO_PROPERTIES(...) \
-	.properties = ((const ParseInfoProperty*const[]) { \
-		__VA_ARGS__ \
-		NULL, \
-	})
-
-typedef struct {
-	const ParseInfoProperty*const*properties;
-} ParseInfoSetting;
-
-#define PARSE_INFO_SETTING(setting_type, ...) \
-	[setting_type] = (&((const ParseInfoSetting) { \
-		__VA_ARGS__ \
-	}))
-
-static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = {
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIRELESS,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_BSSID,
-				.parser        = mac_address_parser_ETHER,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
-				.parser        = mac_address_parser_ETHER_cloned,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_MAC_ADDRESS,
-				.parser        = mac_address_parser_ETHER,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_SSID,
-				.parser        = ssid_parser,
-				.writer        = ssid_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_802_1X,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_CA_CERT,
-				.parser        = cert_parser,
-				.writer        = cert_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_CLIENT_CERT,
-				.parser        = cert_parser,
-				.writer        = cert_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PASSWORD_RAW,
-				.parser        = password_raw_parser,
-				.writer        = password_raw_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_CA_CERT,
-				.parser        = cert_parser,
-				.writer        = cert_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
-				.parser        = cert_parser,
-				.writer        = cert_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
-				.parser        = cert_parser,
-				.writer        = cert_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PRIVATE_KEY,
-				.parser        = cert_parser,
-				.writer        = cert_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIRED,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
-				.parser        = mac_address_parser_ETHER_cloned,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRED_MAC_ADDRESS,
-				.parser        = mac_address_parser_ETHER,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRED_S390_OPTIONS,
-				.parser_no_check_key = TRUE,
-				.parser_full   = wired_s390_options_parser_full,
-				.writer_full   = wired_s390_options_writer_full,
-				.has_parser_full = TRUE,
-				.has_writer_full = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BLUETOOTH,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_BLUETOOTH_BDADDR,
-				.parser        = mac_address_parser_ETHER,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BOND,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_BOND_OPTIONS,
-				.parser_no_check_key = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BRIDGE,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_MAC_ADDRESS,
-				.parser        = mac_address_parser_ETHER,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_VLANS,
-				.parser_no_check_key = TRUE,
-				.parser        = bridge_vlan_parser,
-				.writer        = bridge_vlan_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BRIDGE_PORT,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_PORT_VLANS,
-				.parser_no_check_key = TRUE,
-				.parser        = bridge_vlan_parser,
-				.writer        = bridge_vlan_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_CONNECTION,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_CONNECTION_READ_ONLY,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_CONNECTION_TYPE,
-				.parser        = setting_alias_parser,
-				.writer        = setting_alias_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_INFINIBAND,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_INFINIBAND_MAC_ADDRESS,
-				.parser        = mac_address_parser_INFINIBAND,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_IP4_CONFIG,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ADDRESSES,
-				.parser_no_check_key = TRUE,
-				.parser        = ip_address_or_route_parser,
-				.writer        = addr_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_DNS,
-				.parser_no_check_key = TRUE,
-				.parser        = ip_dns_parser,
-				.writer        = dns_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_GATEWAY,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTES,
-				.parser_no_check_key = TRUE,
-				.parser        = ip_address_or_route_parser,
-				.writer        = route_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTING_RULES,
-				.parser_no_check_key = TRUE,
-				.parser_full   = ip_routing_rule_parser_full,
-				.writer_full   = ip_routing_rule_writer_full,
-				.has_parser_full = TRUE,
-				.has_writer_full = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_IP6_CONFIG,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
-				.parser_no_check_key = TRUE,
-				.parser        = ip6_addr_gen_mode_parser,
-				.writer        = ip6_addr_gen_mode_writer,
-				.writer_persist_default = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ADDRESSES,
-				.parser_no_check_key = TRUE,
-				.parser        = ip_address_or_route_parser,
-				.writer        = addr_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_DNS,
-				.parser_no_check_key = TRUE,
-				.parser        = ip_dns_parser,
-				.writer        = dns_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_GATEWAY,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTES,
-				.parser_no_check_key = TRUE,
-				.parser        = ip_address_or_route_parser,
-				.writer        = route_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTING_RULES,
-				.parser_no_check_key = TRUE,
-				.parser_full   = ip_routing_rule_parser_full,
-				.writer_full   = ip_routing_rule_writer_full,
-				.has_parser_full = TRUE,
-				.has_writer_full = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_SERIAL,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_SERIAL_PARITY,
-				.parser        = parity_parser,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_SRIOV,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_SRIOV_VFS,
-				.parser_no_check_key = TRUE,
-				.parser        = sriov_vfs_parser,
-				.writer        = sriov_vfs_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TC_CONFIG,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_TC_CONFIG_QDISCS,
-				.parser_no_check_key = TRUE,
-				.parser        = qdisc_parser,
-				.writer        = qdisc_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TC_CONFIG_TFILTERS,
-				.parser_no_check_key = TRUE,
-				.parser        = tfilter_parser,
-				.writer        = tfilter_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TEAM,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_CONFIG,
-				.parser        = team_config_parser,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_LINK_WATCHERS,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_MCAST_REJOIN_COUNT,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_NOTIFY_PEERS_COUNT,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_ACTIVE,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_FAST_RATE,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_HWADDR_POLICY,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_MIN_PORTS,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_SYS_PRIO,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_BALANCER,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_HASH,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TEAM_PORT,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_CONFIG,
-				.parser        = team_config_parser,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LACP_KEY,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LACP_PRIO,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LINK_WATCHERS,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_PRIO,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_QUEUE_ID,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_STICKY,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_USER,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_USER_DATA,
-				.parser_no_check_key = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_VLAN,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_VLAN_FLAGS,
-				.writer_persist_default = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_VPN,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_VPN_DATA,
-				.parser_no_check_key = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_VPN_PERSISTENT,
-				.parser_no_check_key = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_VPN_SECRETS,
-				.parser_no_check_key = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_VPN_SERVICE_TYPE,
-				.parser_no_check_key = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_VPN_TIMEOUT,
-				.parser_no_check_key = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_VPN_USER_NAME,
-				.parser_no_check_key = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIMAX,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_WIMAX_MAC_ADDRESS,
-				.parser        = mac_address_parser_ETHER,
-			),
-		),
-	),
-};
-
-static void
-_parse_info_find (NMSetting *setting,
-                  const char *property_name,
-                  const NMMetaSettingInfo **out_setting_info,
-                  const ParseInfoSetting **out_parse_info_setting,
-                  const ParseInfoProperty **out_parse_info_property)
-{
-	const NMMetaSettingInfo *setting_info;
-	const ParseInfoSetting *pis;
-	const ParseInfoProperty *pip;
-
-#if NM_MORE_ASSERTS > 10
-	{
-		guint i, j;
-		static int asserted = FALSE;
-
-		if (!asserted) {
-			for (i = 0; i < G_N_ELEMENTS (parse_infos); i++) {
-				pis = parse_infos[i];
-
-				if (!pis)
-					continue;
-				if (!pis->properties)
-					continue;
-
-				g_assert (pis->properties[0]);
-				for (j = 0; pis->properties[j]; j++) {
-					const ParseInfoProperty *pip0;
-					const ParseInfoProperty *pipj = pis->properties[j];
-
-					g_assert (pipj->property_name);
-					if (   j > 0
-					    && (pip0 = pis->properties[j - 1])
-					    && strcmp (pip0->property_name, pipj->property_name) >= 0) {
-						g_error ("Wrong order at index #%d.%d: \"%s.%s\" before \"%s.%s\"",
-						         i, j - 1,
-						         nm_meta_setting_infos[i].setting_name, pip0->property_name,
-						         nm_meta_setting_infos[i].setting_name, pipj->property_name);
-					}
-				}
-			}
-			asserted = TRUE;
-		}
-	}
-#endif
-
-	if (   !NM_IS_SETTING (setting)
-	    || !(setting_info = NM_SETTING_GET_CLASS (setting)->setting_info)) {
-		/* handle invalid setting objects gracefully. */
-		NM_SET_OUT (out_setting_info, NULL);
-		NM_SET_OUT (out_parse_info_setting, NULL);
-		NM_SET_OUT (out_parse_info_property, NULL);
-		return;
-	}
-
-	nm_assert (setting_info->setting_name);
-	nm_assert (_NM_INT_NOT_NEGATIVE (setting_info->meta_type));
-	nm_assert (setting_info->meta_type < G_N_ELEMENTS (parse_infos));
-
-	pis = parse_infos[setting_info->meta_type];
-
-	pip = NULL;
-	if (   pis
-	    && property_name) {
-		gssize idx;
-
-		G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (ParseInfoProperty, property_name) == 0);
-
-		idx = nm_utils_ptrarray_find_binary_search ((gconstpointer *) pis->properties,
-		                                            NM_PTRARRAY_LEN (pis->properties),
-		                                            &property_name,
-		                                            nm_strcmp_p_with_data,
-		                                            NULL,
-		                                            NULL,
-		                                            NULL);
-		if (idx >= 0)
-			pip = pis->properties[idx];
-	}
-
-	NM_SET_OUT (out_setting_info, setting_info);
-	NM_SET_OUT (out_parse_info_setting, pis);
-	NM_SET_OUT (out_parse_info_property, pip);
-}
-
-/*****************************************************************************/
-
-static void
-read_one_setting_value (KeyfileReaderInfo *info,
-                        NMSetting *setting,
-                        const NMSettInfoProperty *property_info)
-{
-	GKeyFile *keyfile = info->keyfile;
-	gs_free_error GError *err = NULL;
-	const NMMetaSettingInfo *setting_info;
-	const ParseInfoProperty *pip;
-	gs_free char *tmp_str = NULL;
-	const char *key;
-	GType type;
-	guint64 u64;
-	gint64 i64;
-
-	nm_assert (!info->error);
-	nm_assert (   !property_info->param_spec
-	           || nm_streq (property_info->param_spec->name, property_info->name));
-
-	key = property_info->name;
-
-	_parse_info_find (setting, key, &setting_info, NULL, &pip);
-
-	nm_assert (setting_info);
-
-	if (!pip) {
-		if (nm_streq (key, NM_SETTING_NAME))
-			return;
-		if (!property_info->param_spec)
-			return;
-		if ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE)
-			return;
-	} else {
-		if (pip->parser_skip)
-			return;
-		if (pip->has_parser_full) {
-			pip->parser_full (info, setting_info, property_info, pip, setting);
-			return;
-		}
-	}
-
-	nm_assert (property_info->param_spec);
-	nm_assert ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) == G_PARAM_WRITABLE);
-
-	/* Check for the exact key in the GKeyFile if required.  Most setting
-	 * properties map 1:1 to a key in the GKeyFile, but for those properties
-	 * like IP addresses and routes where more than one value is actually
-	 * encoded by the setting property, this won't be true.
-	 */
-	if (   (!pip || !pip->parser_no_check_key)
-	    && !nm_keyfile_plugin_kf_has_key (keyfile, setting_info->setting_name, key, &err)) {
-		/* Key doesn't exist or an error occurred, thus nothing to do. */
-		if (err) {
-			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			                  _("error loading setting value: %s"),
-			                  err->message))
-				return;
-		}
-		return;
-	}
-
-	if (   pip
-	    && pip->parser) {
-		pip->parser (info, setting, key);
-		return;
-	}
-
-	type = G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec);
-
-	if (type == G_TYPE_STRING) {
-		gs_free char *str_val = NULL;
-
-		str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_info->setting_name, key, &err);
-		if (!err)
-			nm_g_object_set_property_string_take (G_OBJECT (setting), key, g_steal_pointer (&str_val), &err);
-	} else if (type == G_TYPE_UINT) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64);
-			if (   u64 == G_MAXUINT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_uint (G_OBJECT (setting), key, u64, &err);
-		}
-	} else if (type == G_TYPE_INT) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MININT64);
-			if (   i64 == G_MININT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_int (G_OBJECT (setting), key, i64, &err);
-		}
-	} else if (type == G_TYPE_BOOLEAN) {
-		gboolean bool_val;
-
-		bool_val = nm_keyfile_plugin_kf_get_boolean (keyfile, setting_info->setting_name, key, &err);
-		if (!err)
-			nm_g_object_set_property_boolean (G_OBJECT (setting), key, bool_val, &err);
-	} else if (type == G_TYPE_CHAR) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			/* As documented by glib, G_TYPE_CHAR is really a (signed!) gint8. */
-			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT8, G_MAXINT8, G_MININT64);
-			if (   i64 == G_MININT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_char (G_OBJECT (setting), key, i64, &err);
-		}
-	} else if (type == G_TYPE_UINT64) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT64, G_MAXUINT64);
-			if (   u64 == G_MAXUINT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_uint64 (G_OBJECT (setting), key, u64, &err);
-		}
-	} else if (type == G_TYPE_INT64) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT64, G_MAXINT64, G_MAXINT64);
-			if (   i64 == G_MAXINT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_int64 (G_OBJECT (setting), key, i64, &err);
-		}
-	} else if (type == G_TYPE_BYTES) {
-		gs_free int *tmp = NULL;
-		GByteArray *array;
-		GBytes *bytes;
-		gsize length;
-		int i;
-		gboolean already_warned = FALSE;
-
-		tmp = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_info->setting_name, key, &length, NULL);
-
-		array = g_byte_array_sized_new (length);
-		for (i = 0; i < length; i++) {
-			const int val = tmp[i];
-			unsigned char v = (unsigned char) (val & 0xFF);
-
-			if (val < 0 || val > 255) {
-				if (   !already_warned
-				    && !handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-				                     _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"),
-				                     val)) {
-					g_byte_array_unref (array);
-					return;
-				}
-				already_warned = TRUE;
-			} else
-				g_byte_array_append (array, (const unsigned char *) &v, sizeof (v));
-		}
-
-		bytes = g_byte_array_free_to_bytes (array);
-		g_object_set (setting, key, bytes, NULL);
-		g_bytes_unref (bytes);
-	} else if (type == G_TYPE_STRV) {
-		gs_strfreev char **sa = NULL;
-		gsize length;
-
-		sa = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_info->setting_name, key, &length, NULL);
-		g_object_set (setting, key, sa, NULL);
-	} else if (type == G_TYPE_HASH_TABLE) {
-		read_hash_of_string (keyfile, setting, key);
-	} else if (type == G_TYPE_ARRAY) {
-		read_array_of_uint (keyfile, setting, key);
-	} else if (G_TYPE_IS_FLAGS (type)) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64);
-			if (   u64 == G_MAXUINT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_flags (G_OBJECT (setting), key, type, u64, &err);
-		}
-	} else if (G_TYPE_IS_ENUM (type)) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MAXINT64);
-			if (   i64 == G_MAXINT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_enum (G_OBJECT (setting), key, type, i64, &err);
-		}
-	} else
-		g_return_if_reached ();
-
-	if (err) {
-		if (   err->domain == G_KEY_FILE_ERROR
-		    && NM_IN_SET (err->code, G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
-		                             G_KEY_FILE_ERROR_KEY_NOT_FOUND)) {
-			/* ignore such errors. The key is not present. */
-		} else {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid setting: %s"), err->message);
-		}
-	}
-}
-
-static void
-_read_setting (KeyfileReaderInfo *info)
-{
-	const NMSettInfoSetting *sett_info;
-	gs_unref_object NMSetting *setting = NULL;
-	const char *alias;
-	GType type;
-	guint i;
-
-	alias = nm_keyfile_plugin_get_setting_name_for_alias (info->group);
-	if (!alias)
-		alias = info->group;
-
-	type = nm_setting_lookup_type (alias);
-	if (!type) {
-		handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("invalid setting name '%s'"), info->group);
-		return;
-	}
-
-	setting = g_object_new (type, NULL);
-
-	info->setting = setting;
-
-	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
-
-	if (sett_info->detail.gendata_info) {
-		gs_free char **keys = NULL;
-		gsize k, n_keys;
-
-		keys = g_key_file_get_keys (info->keyfile, info->group, &n_keys, NULL);
-		if (!keys)
-			n_keys = 0;
-		if (n_keys > 0) {
-			GHashTable *h = _nm_setting_gendata_hash (setting, TRUE);
-
-			nm_utils_strv_sort (keys, n_keys);
-			for (k = 0; k < n_keys; k++) {
-				gs_free char *key = keys[k];
-				gs_free_error GError *local = NULL;
-				const GVariantType *variant_type;
-				GVariant *variant;
-
-				/* a GKeyFile can return duplicate keys, there is just no API to make sense
-				 * of them. Skip them. */
-				if (   k + 1 < n_keys
-				    && nm_streq (key, keys[k + 1]))
-					continue;
-
-				/* currently, the API is very simple. The setting class just returns
-				 * the desired variant type, and keyfile reader will try to parse
-				 * it accordingly. Note, that this does currently not allow, that
-				 * a particular key can contain different variant types, nor is it
-				 * very flexible in general.
-				 *
-				 * We add flexibility when we need it. Keep it simple for now. */
-				variant_type = sett_info->detail.gendata_info->get_variant_type (sett_info,
-				                                                                 key,
-				                                                                 &local);
-				if (!variant_type) {
-					if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-					                  _("invalid key '%s.%s'"),
-					                  info->group, key))
-						break;
-					continue;
-				}
-
-				if (g_variant_type_equal (variant_type, G_VARIANT_TYPE_BOOLEAN)) {
-					gboolean v;
-
-					v = g_key_file_get_boolean (info->keyfile,
-					                            info->group,
-					                            key,
-					                            &local);
-					if (local) {
-						if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-						                  _("key '%s.%s' is not boolean"),
-						                  info->group, key))
-							break;
-						continue;
-					}
-					variant = g_variant_new_boolean (v);
-				} else {
-					nm_assert_not_reached ();
-					continue;
-				}
-
-				g_hash_table_insert (h,
-				                     g_steal_pointer (&key),
-				                     g_variant_take_ref (variant));
-			}
-			for (; k < n_keys; k++)
-				g_free (keys[k]);
-		}
-	}
-
-	for (i = 0; i < sett_info->property_infos_len; i++) {
-		read_one_setting_value (info,
-		                        setting,
-		                        &sett_info->property_infos[i]);
-		if (info->error)
-			goto out;
-	}
-
-out:
-	info->setting = NULL;
-	if (!info->error)
-		nm_connection_add_setting (info->connection, g_steal_pointer (&setting));
-}
-
-static void
-_read_setting_wireguard_peer (KeyfileReaderInfo *info)
-{
-	gs_unref_object NMSettingWireGuard *s_wg_new = NULL;
-	nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
-	gs_free_error GError *error = NULL;
-	NMSettingWireGuard *s_wg;
-	gs_free char *str = NULL;
-	const char *cstr = NULL;
-	const char *key;
-	gint64 i64;
-	gs_strfreev char **sa = NULL;
-	gsize n_sa;
-
-	peer = nm_wireguard_peer_new ();
-
-	nm_assert (g_str_has_prefix (info->group, NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER));
-	cstr = &info->group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)];
-	if (   !nm_utils_base64secret_normalize (cstr, NM_WIREGUARD_PUBLIC_KEY_LEN, &str)
-	    || !nm_streq0 (str, cstr)) {
-		/* the group name must be identical to the normalized(!) key, so that it
-		 * is uniquely identified. */
-		handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("invalid peer public key in section '%s'"),
-		             info->group);
-		return;
-	}
-	nm_wireguard_peer_set_public_key (peer, cstr, TRUE);
-	nm_clear_g_free (&str);
-
-	key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY;
-	str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL);
-	if (str) {
-		if (!nm_wireguard_peer_set_preshared_key (peer, str, FALSE)) {
-			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			                  _("key '%s.%s' is not a valid 256 bit key in base64 encoding"),
-			                  info->group, key))
-				return;
-		}
-		nm_clear_g_free (&str);
-	}
-
-	key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS;
-	i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, NM_SETTING_SECRET_FLAG_ALL, -1, NULL);
-	if (errno != ENODATA) {
-		if (   i64 == -1
-		    || !_nm_setting_secret_flags_valid (i64)) {
-			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			                  _("key '%s.%s' is not a valid secret flag"),
-			                  info->group, key))
-				return;
-		} else
-			nm_wireguard_peer_set_preshared_key_flags (peer, i64);
-	}
-
-	key = NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE;
-	i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, G_MAXUINT32, -1, NULL);
-	if (errno != ENODATA) {
-		if (i64 == -1) {
-			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			                  _("key '%s.%s' is not a integer in range 0 to 2^32"),
-			                  info->group, key))
-				return;
-		} else
-			nm_wireguard_peer_set_persistent_keepalive (peer, i64);
-	}
-
-	key = NM_WIREGUARD_PEER_ATTR_ENDPOINT;
-	str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL);
-	if (str && str[0]) {
-		if (!nm_wireguard_peer_set_endpoint (peer, str, FALSE)) {
-			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			                  _("key '%s.%s' is not a valid endpoint"),
-			                  info->group, key))
-				return;
-		}
-	}
-	nm_clear_g_free (&str);
-
-	key = NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS;
-	sa = nm_keyfile_plugin_kf_get_string_list (info->keyfile, info->group, key, &n_sa, NULL);
-	if (n_sa > 0) {
-		gboolean has_error = FALSE;
-		gsize i;
-
-		for (i = 0; i < n_sa; i++) {
-			if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC, sa[i], NULL, NULL, NULL)) {
-				has_error = TRUE;
-				continue;
-			}
-			nm_wireguard_peer_append_allowed_ip (peer, sa[i], TRUE);
-		}
-		if (has_error) {
-			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			                  _("key '%s.%s' has invalid allowed-ips"),
-			                  info->group, key))
-				return;
-		}
-	}
-	nm_clear_pointer (&sa, g_strfreev);
-
-	if (info->error)
-		return;
-
-	if (!nm_wireguard_peer_is_valid (peer, TRUE, TRUE, &error)) {
-		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("peer '%s' is invalid: %s"),
-		             info->group, error->message);
-		return;
-	}
-
-	s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (info->connection, NM_TYPE_SETTING_WIREGUARD));
-	if (!s_wg) {
-		s_wg_new = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ());
-		s_wg = s_wg_new;
-	}
-
-	nm_setting_wireguard_append_peer (s_wg, peer);
-
-	if (s_wg_new) {
-		nm_connection_add_setting (info->connection,
-		                           NM_SETTING (g_steal_pointer (&s_wg_new)));
-	}
-}
-
-static void
-_read_setting_vpn_secrets (KeyfileReaderInfo *info)
-{
-	gs_strfreev char **keys = NULL;
-	gsize i, n_keys;
-	NMSettingVpn *s_vpn;
-
-	s_vpn = nm_connection_get_setting_vpn (info->connection);
-	if (!s_vpn) {
-		/* if we don't also have a [vpn] section (which must be parsed earlier),
-		 * we don't do anything. */
-		nm_assert (!g_key_file_has_group (info->keyfile, "vpn"));
-		return;
-	}
-
-	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, &n_keys, NULL);
-	for (i = 0; i < n_keys; i++) {
-		gs_free char *secret = NULL;
-
-		secret = nm_keyfile_plugin_kf_get_string (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, keys[i], NULL);
-		if (secret)
-			nm_setting_vpn_add_secret (s_vpn, keys[i], secret);
-	}
-}
-
-gboolean
-nm_keyfile_read_ensure_id (NMConnection *connection,
-                           const char *fallback_id)
-{
-	NMSettingConnection *s_con;
-
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
-	g_return_val_if_fail (fallback_id, FALSE);
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_return_val_if_fail (NM_IS_SETTING_CONNECTION (s_con), FALSE);
-
-	if (nm_setting_connection_get_id (s_con))
-		return FALSE;
-
-	g_object_set (s_con, NM_SETTING_CONNECTION_ID, fallback_id, NULL);
-	return TRUE;
-}
-
-gboolean
-nm_keyfile_read_ensure_uuid (NMConnection *connection,
-                             const char *fallback_uuid_seed)
-{
-	NMSettingConnection *s_con;
-	gs_free char *hashed_uuid = NULL;
-
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
-	g_return_val_if_fail (fallback_uuid_seed, FALSE);
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_return_val_if_fail (NM_IS_SETTING_CONNECTION (s_con), FALSE);
-
-	if (nm_setting_connection_get_uuid (s_con))
-		return FALSE;
-
-	hashed_uuid = _nm_utils_uuid_generate_from_strings ("keyfile", fallback_uuid_seed, NULL);
-	g_object_set (s_con, NM_SETTING_CONNECTION_UUID, hashed_uuid, NULL);
-	return TRUE;
-}
-
-/**
- * nm_keyfile_read:
- * @keyfile: the keyfile from which to create the connection
- * @base_dir: when reading certificates from files with relative name,
- *   the relative path is made absolute using @base_dir. This must
- *   be an absolute path.
- * @handler: read handler
- * @user_data: user data for read handler
- * @error: error
- *
- * Tries to create a NMConnection from a keyfile. The resulting keyfile is
- * not normalized and might not even verify.
- *
- * Returns: (transfer full): on success, returns the created connection.
- */
-NMConnection *
-nm_keyfile_read (GKeyFile *keyfile,
-                 const char *base_dir,
-                 NMKeyfileReadHandler handler,
-                 void *user_data,
-                 GError **error)
-{
-	gs_unref_object NMConnection *connection = NULL;
-	NMSettingConnection *s_con;
-	gs_strfreev char **groups = NULL;
-	gsize n_groups;
-	gsize i;
-	gboolean vpn_secrets = FALSE;
-	KeyfileReaderInfo info;
-
-	g_return_val_if_fail (keyfile, NULL);
-	g_return_val_if_fail (!error || !*error, NULL);
-	g_return_val_if_fail (base_dir && base_dir[0] == '/', NULL);
-
-	connection = nm_simple_connection_new ();
-
-	info = (KeyfileReaderInfo) {
-		.connection = connection,
-		.keyfile    = keyfile,
-		.base_dir   = base_dir,
-		.handler    = handler,
-		.user_data  = user_data,
-	};
-
-	groups = g_key_file_get_groups (keyfile, &n_groups);
-	if (!groups)
-		n_groups = 0;
-
-	for (i = 0; i < n_groups; i++) {
-
-		info.group = groups[i];
-
-		if (nm_streq (groups[i], NM_KEYFILE_GROUP_VPN_SECRETS)) {
-			/* Only read out secrets when needed */
-			vpn_secrets = TRUE;
-		} else if (NM_STR_HAS_PREFIX (groups[i], NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER))
-			_read_setting_wireguard_peer (&info);
-		else if (NM_IN_STRSET (groups[i], NM_KEYFILE_GROUP_NMMETA,
-		                                  ETHERNET_S390_OPTIONS_GROUP_NAME)) {
-			/* pass */
-		} else
-			_read_setting (&info);
-
-		info.group = NULL;
-
-		if (info.error)
-			goto out_with_info_error;
-	}
-
-	s_con = nm_connection_get_setting_connection (connection);
-	if (!s_con) {
-		s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
-		nm_connection_add_setting (connection, NM_SETTING (s_con));
-	}
-
-	/* Make sure that we have 'interface-name' even if it was specified in the
-	 * "wrong" (ie, deprecated) group.
-	 */
-	if (   !nm_setting_connection_get_interface_name (s_con)
-	    && nm_setting_connection_get_connection_type (s_con)) {
-		gs_free char *interface_name = NULL;
-
-		interface_name = g_key_file_get_string (keyfile,
-		                                        nm_setting_connection_get_connection_type (s_con),
-		                                        "interface-name",
-		                                        NULL);
-		if (interface_name)
-			g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, interface_name, NULL);
-	}
-
-	if (vpn_secrets) {
-		info.group = NM_KEYFILE_GROUP_VPN_SECRETS;
-		_read_setting_vpn_secrets (&info);
-		info.group = NULL;;
-		if (info.error)
-			goto out_with_info_error;
-	}
-
-	return g_steal_pointer (&connection);
-
-out_with_info_error:
-	g_propagate_error (error, info.error);
-	return NULL;
-}
-
-/*****************************************************************************/
-
-static void
-write_setting_value (KeyfileWriterInfo *info,
-                     NMSetting *setting,
-                     const NMSettInfoProperty *property_info)
-{
-	const NMMetaSettingInfo *setting_info;
-	const ParseInfoProperty *pip;
-	const char *key;
-	char numstr[64];
-	GValue value;
-	GType type;
-
-	nm_assert (!info->error);
-	nm_assert (   !property_info->param_spec
-	           || nm_streq (property_info->param_spec->name, property_info->name));
-
-	key = property_info->name;
-
-	_parse_info_find (setting, key, &setting_info, NULL, &pip);
-
-	if (!pip) {
-		if (!setting_info) {
-			/* the setting type is unknown. That is highly unexpected
-			 * (and as this is currently only called from NetworkManager
-			 * daemon, not possible).
-			 *
-			 * Still, handle it gracefully, because later keyfile writer will become
-			 * public API of libnm, where @setting is (untrusted) user input.
-			 *
-			 * Gracefully here just means: ignore the setting. */
-			return;
-		}
-		if (!property_info->param_spec)
-			return;
-		if (nm_streq (key, NM_SETTING_NAME))
-			return;
-	} else {
-		if (pip->has_writer_full) {
-			pip->writer_full (info, setting_info, property_info, pip, setting);
-			return;
-		}
-		if (pip->writer_skip)
-			return;
-	}
-
-	nm_assert (property_info->param_spec);
-
-	/* Don't write secrets that are owned by user secret agents or aren't
-	 * supposed to be saved.  VPN secrets are handled specially though since
-	 * the secret flags there are in a third-level hash in the 'secrets'
-	 * property.
-	 */
-	if (   (property_info->param_spec->flags & NM_SETTING_PARAM_SECRET)
-	    && !NM_IS_SETTING_VPN (setting)) {
-		NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
-
-		if (!nm_setting_get_secret_flags (setting, key, &secret_flags, NULL))
-			g_return_if_reached ();
-		if (!_secret_flags_persist_secret (secret_flags))
-			return;
-	}
-
-	value = (GValue) { 0 };
-
-	g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec));
-	g_object_get_property (G_OBJECT (setting), property_info->param_spec->name, &value);
-
-	if (   (!pip || !pip->writer_persist_default)
-	    && g_param_value_defaults (property_info->param_spec, &value)) {
-		nm_assert (!g_key_file_has_key (info->keyfile, setting_info->setting_name, key, NULL));
-		goto out_unset_value;
-	}
-
-	if (   pip
-	    && pip->writer) {
-		pip->writer (info, setting, key, &value);
-		goto out_unset_value;
-	}
-
-	type = G_VALUE_TYPE (&value);
-	if (type == G_TYPE_STRING) {
-		const char *str;
-
-		str = g_value_get_string (&value);
-		if (str)
-			nm_keyfile_plugin_kf_set_string (info->keyfile, setting_info->setting_name, key, str);
-	} else if (type == G_TYPE_UINT) {
-		nm_sprintf_buf (numstr, "%u", g_value_get_uint (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else if (type == G_TYPE_INT) {
-		nm_sprintf_buf (numstr, "%d", g_value_get_int (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else if (type == G_TYPE_UINT64) {
-		nm_sprintf_buf (numstr, "%" G_GUINT64_FORMAT, g_value_get_uint64 (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else if (type == G_TYPE_INT64) {
-		nm_sprintf_buf (numstr, "%" G_GINT64_FORMAT, g_value_get_int64 (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else if (type == G_TYPE_BOOLEAN) {
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key,
-		                                  g_value_get_boolean (&value)
-		                                ? "true"
-		                                : "false");
-	} else if (type == G_TYPE_CHAR) {
-		nm_sprintf_buf (numstr, "%d", (int) g_value_get_schar (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else if (type == G_TYPE_BYTES) {
-		GBytes *bytes;
-		const guint8 *data;
-		gsize len = 0;
-
-		bytes = g_value_get_boxed (&value);
-		data = bytes ? g_bytes_get_data (bytes, &len) : NULL;
-
-		if (data != NULL && len > 0)
-			nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_info->setting_name, key, data, len);
-	} else if (type == G_TYPE_STRV) {
-		char **array;
-
-		array = (char **) g_value_get_boxed (&value);
-		nm_keyfile_plugin_kf_set_string_list (info->keyfile, setting_info->setting_name, key, (const char **const) array, g_strv_length (array));
-	} else if (type == G_TYPE_HASH_TABLE) {
-		write_hash_of_string (info->keyfile, setting, key, &value);
-	} else if (type == G_TYPE_ARRAY) {
-		write_array_of_uint (info->keyfile, setting, key, &value);
-	} else if (G_VALUE_HOLDS_FLAGS (&value)) {
-		nm_sprintf_buf (numstr, "%u", g_value_get_flags (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else if (G_VALUE_HOLDS_ENUM (&value)) {
-		nm_sprintf_buf (numstr, "%d", g_value_get_enum (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else
-		g_return_if_reached ();
-
-out_unset_value:
-	g_value_unset (&value);
-}
-
-static void
-_write_setting_wireguard (NMSetting *setting, KeyfileWriterInfo *info)
-{
-	NMSettingWireGuard *s_wg;
-	guint i_peer, n_peers;
-
-	s_wg = NM_SETTING_WIREGUARD (setting);
-
-	n_peers = nm_setting_wireguard_get_peers_len (s_wg);
-	for (i_peer = 0; i_peer < n_peers; i_peer++) {
-		NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i_peer);
-		const char *public_key;
-		char group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER) + 200];
-		NMSettingSecretFlags secret_flags;
-		gboolean any_key = FALSE;
-		guint i_aip, n_aip;
-		const char *cstr;
-		guint32 u32;
-
-		public_key = nm_wireguard_peer_get_public_key (peer);
-		if (   !public_key
-		    || !public_key[0]
-		    || !NM_STRCHAR_ALL (public_key, ch, nm_sd_utils_unbase64char (ch, TRUE) >= 0)) {
-			/* invalid peer. Skip it */
-			continue;
-		}
-
-		if (g_snprintf (group,
-		                sizeof (group),
-		                "%s%s",
-		                NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER,
-		                nm_wireguard_peer_get_public_key (peer)) >= sizeof (group)) {
-			/* Too long. Not a valid public key. Skip the peer. */
-			continue;
-		}
-
-		cstr = nm_wireguard_peer_get_endpoint (peer);
-		if (cstr) {
-			g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, cstr);
-			any_key = TRUE;
-		}
-
-		secret_flags = nm_wireguard_peer_get_preshared_key_flags (peer);
-		if (_secret_flags_persist_secret (secret_flags)) {
-			cstr = nm_wireguard_peer_get_preshared_key (peer);
-			if (cstr) {
-				g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, cstr);
-				any_key = TRUE;
-			}
-		}
-
-		/* usually, we don't persist the secret-flags 0 (because they are the default).
-		 * For WireGuard peers, the default secret-flags for preshared-key are 4 (not-required).
-		 * So, in this case behave differently: a missing preshared-key-flag setting means
-		 * "not-required". */
-		if (secret_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED) {
-			g_key_file_set_int64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, secret_flags);
-			any_key = TRUE;
-		}
-
-		u32 = nm_wireguard_peer_get_persistent_keepalive (peer);
-		if (u32) {
-			g_key_file_set_uint64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, u32);
-			any_key = TRUE;
-		}
-
-		n_aip = nm_wireguard_peer_get_allowed_ips_len (peer);
-		if (n_aip > 0) {
-			gs_free const char **strv = NULL;
-
-			strv = g_new (const char *, ((gsize) n_aip) + 1);
-			for (i_aip = 0; i_aip < n_aip; i_aip++)
-				strv[i_aip] = nm_wireguard_peer_get_allowed_ip (peer, i_aip, NULL);
-			strv[n_aip] = NULL;
-			g_key_file_set_string_list (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS,
-			                            strv, n_aip);
-			any_key = TRUE;
-		}
-
-		if (!any_key) {
-			/* we cannot omit all keys. At an empty endpoint. */
-			g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, "");
-		}
-	}
-}
-
-GKeyFile *
-nm_keyfile_write (NMConnection *connection,
-                  NMKeyfileWriteHandler handler,
-                  void *user_data,
-                  GError **error)
-{
-	gs_unref_keyfile GKeyFile *keyfile = NULL;
-	KeyfileWriterInfo info;
-	gs_free NMSetting **settings = NULL;
-	guint i, j, n_settings = 0;
-
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
-	g_return_val_if_fail (!error || !*error, NULL);
-
-	if (!nm_connection_verify (connection, error))
-		return NULL;
-
-	keyfile = g_key_file_new ();
-
-	info = (KeyfileWriterInfo) {
-		.connection = connection,
-		.keyfile    = keyfile,
-		.error      = NULL,
-		.handler    = handler,
-		.user_data  = user_data,
-	};
-
-	settings = nm_connection_get_settings (connection, &n_settings);
-	for (i = 0; i < n_settings; i++) {
-		const NMSettInfoSetting *sett_info;
-		NMSetting *setting = settings[i];
-		const char *setting_name;
-		const char *setting_alias;
-
-		sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
-
-		setting_name = sett_info->setting_class->setting_info->setting_name;
-
-		if (sett_info->detail.gendata_info) {
-			guint k, n_keys;
-			const char *const*keys;
-
-			nm_assert (!nm_keyfile_plugin_get_alias_for_setting_name (sett_info->setting_class->setting_info->setting_name));
-
-			n_keys = _nm_setting_gendata_get_all (setting, &keys, NULL);
-
-			if (n_keys > 0) {
-				GHashTable *h = _nm_setting_gendata_hash (setting, FALSE);
-
-				for (k = 0; k < n_keys; k++) {
-					const char *key = keys[k];
-					GVariant *v;
-
-					v = g_hash_table_lookup (h, key);
-
-					if (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)) {
-						g_key_file_set_boolean (info.keyfile,
-						                        setting_name,
-						                        key,
-						                        g_variant_get_boolean (v));
-					} else {
-						/* BUG: The variant type is not implemented. Since the connection
-						 * verifies, this can only mean we either wrongly didn't reject
-						 * the connection as invalid, or we didn't properly implement the
-						 * variant type. */
-						nm_assert_not_reached ();
-						continue;
-					}
-				}
-			}
-		}
-
-		for (j = 0; j < sett_info->property_infos_len; j++) {
-			const NMSettInfoProperty *property_info = _nm_sett_info_property_info_get_sorted (sett_info, j);
-
-			write_setting_value (&info, setting, property_info);
-			if (info.error)
-				goto out_with_info_error;
-		}
-
-		setting_alias = nm_keyfile_plugin_get_alias_for_setting_name (setting_name);
-		if (   (   setting_alias
-		        && g_key_file_has_group (info.keyfile, setting_alias))
-		    || g_key_file_has_group (info.keyfile, setting_name)) {
-			/* we have a section for the setting. Nothing to do. */
-		} else {
-			/* ensure the group is present. There is no API for that, so add and remove
-			 * a dummy key. */
-			g_key_file_set_value  (info.keyfile, setting_alias ?: setting_name, ".X", "1");
-			g_key_file_remove_key (info.keyfile, setting_alias ?: setting_name, ".X", NULL);
-		}
-
-		if (NM_IS_SETTING_WIREGUARD (setting)) {
-			_write_setting_wireguard (setting, &info);
-			if (info.error)
-				goto out_with_info_error;
-		}
-
-		nm_assert (!info.error);
-	}
-
-	nm_assert (!info.error);
-
-	return g_steal_pointer (&keyfile);
-
-out_with_info_error:
-	g_propagate_error (error, info.error);
-	return NULL;
-}
-
-/*****************************************************************************/
-
-static const char temp_letters[] =
-"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
-
-/*
- * Check '.[a-zA-Z0-9]{6}' file suffix used for temporary files by g_file_set_contents() (mkstemp()).
- */
-static gboolean
-check_mkstemp_suffix (const char *path)
-{
-	const char *ptr;
-
-	nm_assert (path);
-
-	/* Matches *.[a-zA-Z0-9]{6} suffix of mkstemp()'s temporary files */
-	ptr = strrchr (path, '.');
-	if (   ptr
-	    && strspn (&ptr[1], temp_letters) == 6
-	    && ptr[7] == '\0')
-		return TRUE;
-	return FALSE;
-}
-
-static gboolean
-_check_suffix_impl (const char *base, const char *tag, gsize tag_len)
-{
-	gsize len;
-
-	nm_assert (base);
-	nm_assert (tag);
-	nm_assert (strlen (tag) == tag_len);
-
-	len = strlen (base);
-	if (   len > tag_len
-	    && !g_ascii_strcasecmp (base + len - tag_len, tag))
-		return TRUE;
-	return FALSE;
-}
-#define check_suffix(base, tag) _check_suffix_impl ((base), ""tag"", NM_STRLEN (tag))
-
-#define SWP_TAG ".swp"
-#define SWPX_TAG ".swpx"
-#define PEM_TAG ".pem"
-#define DER_TAG ".der"
-
-gboolean
-nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extension)
-{
-	const char *base;
-	gsize l;
-
-	/* ignore_filename() must mirror nm_keyfile_utils_create_filename() */
-
-	g_return_val_if_fail (filename, TRUE);
-
-	base = strrchr (filename, '/');
-	if (base)
-		base++;
-	else
-		base = filename;
-
-	if (!base[0]) {
-		/* this check above with strrchr() also rejects "/some/path/with/trailing/slash/",
-		 * but that is fine, because such a path would name a directory, and we are not
-		 * interested in directories. */
-		return TRUE;
-	}
-
-	if (base[0] == '.') {
-		/* don't allow hidden files */
-		return TRUE;
-	}
-
-	l = strlen (base);
-
-	if (require_extension) {
-		if (   l <= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)
-		    || !NM_STR_HAS_SUFFIX (base, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION))
-			return TRUE;
-		return FALSE;
-	}
-
-	/* Ignore backup files */
-	if (base[l - 1] == '~')
-		return TRUE;
-
-	/* Ignore temporary files
-	 *
-	 * This check is also important to ignore .nmload files (see
-	 * %NM_KEYFILE_PATH_SUFFIX_NMMETA). */
-	if (check_mkstemp_suffix (base))
-		return TRUE;
-
-	/* Ignore 802.1x certificates and keys */
-	if (   check_suffix (base, PEM_TAG)
-	    || check_suffix (base, DER_TAG))
-		return TRUE;
-
-	return FALSE;
-}
-
-char *
-nm_keyfile_utils_create_filename (const char *name,
-                                  gboolean with_extension)
-{
-	GString *str;
-	const char *f = name;
-	/* keyfile used to escape with '*', do not change that behavior.
-	 *
-	 * But for newly added escapings, use '_' instead.
-	 * Also, @with_extension is new-style. */
-	const char ESCAPE_CHAR = with_extension ? '_' : '*';
-	const char ESCAPE_CHAR2 = '_';
-
-	g_return_val_if_fail (name && name[0], NULL);
-
-	str = g_string_sized_new (60);
-
-	/* Convert '/' to ESCAPE_CHAR */
-	for (f = name; f[0]; f++) {
-		if (f[0] == '/')
-			g_string_append_c (str, ESCAPE_CHAR);
-		else
-			g_string_append_c (str, f[0]);
-	}
-
-	/* nm_keyfile_utils_create_filename() must avoid anything that ignore_filename() would reject.
-	 * We can escape here more aggressivly then what we would read back. */
-	if (str->str[0] == '.')
-		str->str[0] = ESCAPE_CHAR2;
-	if (str->str[str->len - 1] == '~')
-		str->str[str->len - 1] = ESCAPE_CHAR2;
-	if (   check_mkstemp_suffix (str->str)
-	    || check_suffix (str->str, PEM_TAG)
-	    || check_suffix (str->str, DER_TAG))
-		g_string_append_c (str, ESCAPE_CHAR2);
-
-	if (with_extension)
-		g_string_append (str, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION);
-
-	/* nm_keyfile_utils_create_filename() must mirror ignore_filename() */
-	nm_assert (!strchr (str->str, '/'));
-	nm_assert (!nm_keyfile_utils_ignore_filename (str->str, with_extension));
-
-	return g_string_free (str, FALSE);;
-}
diff --git a/shared/nm-libnm-aux/nm-libnm-aux.c b/shared/nm-libnm-aux/nm-libnm-aux.c
deleted file mode 100644
index 169416b9..00000000
--- a/shared/nm-libnm-aux/nm-libnm-aux.c
+++ /dev/null
@@ -1,146 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1+
-
-#include "nm-default.h"
-
-#include "nm-libnm-aux.h"
-
-/*****************************************************************************/
-
-NMClient *
-nmc_client_new_async_valist (GCancellable *cancellable,
-                             GAsyncReadyCallback callback,
-                             gpointer user_data,
-                             const char *first_property_name,
-                             va_list ap)
-{
-	NMClient *nmc;
-
-	nmc = NM_CLIENT (g_object_new_valist (NM_TYPE_CLIENT, first_property_name, ap));
-	g_async_initable_init_async (G_ASYNC_INITABLE (nmc),
-	                             G_PRIORITY_DEFAULT,
-	                             cancellable,
-	                             callback,
-	                             user_data);
-	return nmc;
-}
-
-NMClient *
-nmc_client_new_async (GCancellable *cancellable,
-                      GAsyncReadyCallback callback,
-                      gpointer user_data,
-                      const char *first_property_name,
-                      ...)
-{
-	NMClient *nmc;
-	va_list ap;
-
-	va_start (ap, first_property_name);
-	nmc = nmc_client_new_async_valist (cancellable,
-	                                   callback,
-	                                   user_data,
-	                                   first_property_name,
-	                                   ap);
-	va_end (ap);
-	return nmc;
-}
-
-/*****************************************************************************/
-
-typedef struct {
-	GMainLoop *main_loop;
-	NMClient *nmc;
-	GError *error;
-} ClientCreateData;
-
-static void
-_nmc_client_new_waitsync_cb (GObject *source_object,
-                             GAsyncResult *result,
-                             gpointer user_data)
-{
-	ClientCreateData *data = user_data;
-
-	g_async_initable_init_finish (G_ASYNC_INITABLE (source_object),
-	                              result,
-	                              &data->error);
-	g_main_loop_quit (data->main_loop);
-}
-
-/**
- * nmc_client_new:
- * @cancellable: the cancellable to abort the creation.
- * @out_nmc: (out): (transfer full): if give, transfers a reference
- *   to the NMClient instance. Note that this never fails to create
- *   the NMClient GObject, but depending on the return value,
- *   the instance was successfully initialized or not.
- * @error: the error if creation fails.
- * @first_property_name: the name of the first property
- * @...: the value of the first property, followed optionally by more
- *  name/value pairs, followed by %NULL
- *
- * Returns: %TRUE, if the client was successfully initalized.
- *
- * This uses nm_client_new_async() to create a NMClient instance,
- * but it iterates the current GMainContext until the client is
- * ready. As such, it waits for the client creation to complete
- * (like sync nm_client_new()) but it iterates the caller's GMainContext
- * (unlike sync nm_client_new()). This is often preferable, because
- * sync nm_client_new() needs to create an additional internal GMainContext
- * that it can iterate instead. That has a performance overhead that
- * is often unnecessary.
- */
-gboolean
-nmc_client_new_waitsync (GCancellable *cancellable,
-                         NMClient **out_nmc,
-                         GError **error,
-                         const char *first_property_name,
-                         ...)
-{
-	gs_unref_object NMClient *nmc = NULL;
-	nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new (g_main_context_get_thread_default (), FALSE);
-	ClientCreateData data = {
-		.main_loop = main_loop,
-	};
-	va_list ap;
-
-#if NM_MORE_ASSERTS > 10
-	/* The sync initialization of NMClient is generally a bad idea, because it
-	 * brings the overhead of an additional GMainContext. Anyway, since our own
-	 * code no longer uses that, we hardly test those code paths. But they should
-	 * work just the same. Randomly use instead the sync initialization in a debug
-	 * build... */
-	if ((g_random_int () % 2) == 0) {
-		gboolean success;
-
-		va_start (ap, first_property_name);
-		nmc = NM_CLIENT (g_object_new_valist (NM_TYPE_CLIENT, first_property_name, ap));
-		va_end (ap);
-
-		/* iterate the context at least once, just so that the behavior from POV of the
-		 * caller is roughly the same. */
-		g_main_context_iteration (nm_client_get_main_context (nmc), FALSE);
-
-		success = g_initable_init (G_INITABLE (nmc),
-		                           cancellable,
-		                           error);
-		NM_SET_OUT (out_nmc, g_steal_pointer (&nmc));
-		return success;
-	}
-#endif
-
-	va_start (ap, first_property_name);
-	nmc = nmc_client_new_async_valist (cancellable,
-	                                   _nmc_client_new_waitsync_cb,
-	                                   &data,
-	                                   first_property_name,
-	                                   ap);
-	va_end (ap);
-
-	g_main_loop_run (main_loop);
-
-	NM_SET_OUT (out_nmc, g_steal_pointer (&nmc));
-	if (data.error) {
-		g_propagate_error (error, data.error);
-		return FALSE;
-	}
-	return TRUE;
-}
diff --git a/shared/nm-libnm-aux/nm-libnm-aux.h b/shared/nm-libnm-aux/nm-libnm-aux.h
deleted file mode 100644
index a0aff19f..00000000
--- a/shared/nm-libnm-aux/nm-libnm-aux.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1+
-
-#ifndef __NM_LIBNM_AUX_H__
-#define __NM_LIBNM_AUX_H__
-
-NMClient *nmc_client_new_async_valist (GCancellable *cancellable,
-                                       GAsyncReadyCallback callback,
-                                       gpointer user_data,
-                                       const char *first_property_name,
-                                       va_list ap);
-
-NMClient *nmc_client_new_async (GCancellable *cancellable,
-                                GAsyncReadyCallback callback,
-                                gpointer user_data,
-                                const char *first_property_name,
-                                ...);
-
-gboolean nmc_client_new_waitsync (GCancellable *cancellable,
-                                  NMClient **out_nmc,
-                                  GError **error,
-                                  const char *first_property_name,
-                                  ...);
-
-#endif /* __NM_LIBNM_AUX_H__ */
diff --git a/shared/nm-libnm-core-aux/nm-dispatcher-api.h b/shared/nm-libnm-core-aux/nm-dispatcher-api.h
deleted file mode 100644
index 96f512e0..00000000
--- a/shared/nm-libnm-core-aux/nm-dispatcher-api.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2008 - 2012 Red Hat, Inc.
- */
-
-#ifndef __NM_DISPACHER_API_H__
-#define __NM_DISPACHER_API_H__
-
-#define NM_DISPATCHER_DBUS_SERVICE   "org.freedesktop.nm_dispatcher"
-#define NM_DISPATCHER_DBUS_INTERFACE "org.freedesktop.nm_dispatcher"
-#define NM_DISPATCHER_DBUS_PATH      "/org/freedesktop/nm_dispatcher"
-
-#define NMD_CONNECTION_PROPS_PATH         "path"
-#define NMD_CONNECTION_PROPS_FILENAME     "filename"
-#define NMD_CONNECTION_PROPS_EXTERNAL     "external"
-
-#define NMD_DEVICE_PROPS_INTERFACE        "interface"
-#define NMD_DEVICE_PROPS_IP_INTERFACE     "ip-interface"
-#define NMD_DEVICE_PROPS_TYPE             "type"
-#define NMD_DEVICE_PROPS_STATE            "state"
-#define NMD_DEVICE_PROPS_PATH             "path"
-
-/* Actions */
-#define NMD_ACTION_HOSTNAME     "hostname"
-#define NMD_ACTION_PRE_UP       "pre-up"
-#define NMD_ACTION_UP           "up"
-#define NMD_ACTION_PRE_DOWN     "pre-down"
-#define NMD_ACTION_DOWN         "down"
-#define NMD_ACTION_VPN_PRE_UP   "vpn-pre-up"
-#define NMD_ACTION_VPN_UP       "vpn-up"
-#define NMD_ACTION_VPN_PRE_DOWN "vpn-pre-down"
-#define NMD_ACTION_VPN_DOWN     "vpn-down"
-#define NMD_ACTION_DHCP4_CHANGE "dhcp4-change"
-#define NMD_ACTION_DHCP6_CHANGE "dhcp6-change"
-#define NMD_ACTION_CONNECTIVITY_CHANGE "connectivity-change"
-
-typedef enum {
-	DISPATCH_RESULT_UNKNOWN = 0,
-	DISPATCH_RESULT_SUCCESS = 1,
-	DISPATCH_RESULT_EXEC_FAILED = 2,
-	DISPATCH_RESULT_FAILED = 3,
-	DISPATCH_RESULT_TIMEOUT = 4,
-} DispatchResult;
-
-#endif /* __NM_DISPACHER_API_H__ */
diff --git a/shared/nm-libnm-core-aux/nm-libnm-core-aux.c b/shared/nm-libnm-core-aux/nm-libnm-core-aux.c
deleted file mode 100644
index f827726f..00000000
--- a/shared/nm-libnm-core-aux/nm-libnm-core-aux.c
+++ /dev/null
@@ -1,358 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1+
-/*
- * Copyright (C) 2019 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-libnm-core-aux.h"
-
-#include "nm-libnm-core-intern/nm-libnm-core-utils.h"
-
-/*****************************************************************************/
-
-typedef enum {
-	KEY_TYPE_STRING,
-	KEY_TYPE_INT,
-	KEY_TYPE_BOOL,
-} KeyType;
-
-typedef struct {
-	const char *str_val;
-	union {
-		int vint;
-		bool vbool;
-	} typ_val;
-} ParseData;
-
-typedef struct {
-	const char *name;
-	NMTeamLinkWatcherType watcher_type;
-	KeyType key_type;
-	union {
-		int         (*fint)    (const NMTeamLinkWatcher *watcher);
-		gboolean    (*fbool)   (const NMTeamLinkWatcher *watcher);
-		const char *(*fstring) (const NMTeamLinkWatcher *watcher);
-	} get_fcn;
-	union {
-		int vint;
-		bool vbool;
-	} def_val;
-} TeamLinkWatcherKeyInfo;
-
-static gboolean
-_team_link_watcher_validate_active (const NMTeamLinkWatcher *watcher)
-{
-	return NM_FLAGS_HAS (nm_team_link_watcher_get_flags (watcher), NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE);
-}
-
-static gboolean
-_team_link_watcher_validate_inactive (const NMTeamLinkWatcher *watcher)
-{
-	return NM_FLAGS_HAS (nm_team_link_watcher_get_flags (watcher), NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE);
-}
-
-static gboolean
-_team_link_watcher_send_always (const NMTeamLinkWatcher *watcher)
-{
-	return NM_FLAGS_HAS (nm_team_link_watcher_get_flags (watcher), NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS);
-}
-
-static const TeamLinkWatcherKeyInfo _team_link_watcher_key_infos[_NM_TEAM_LINK_WATCHER_KEY_NUM] = {
-
-#define _KEY_INFO(key_id, _name, _watcher_type, _key_type, ...) \
-	[key_id] = { .name = ""_name"", .watcher_type = (_watcher_type), .key_type = _key_type, ##__VA_ARGS__ }
-
-	_KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_NAME,              "name",              NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL | NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_STRING, .get_fcn.fstring = nm_team_link_watcher_get_name,        ),
-	_KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_DELAY_UP,          "delay-up",          NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL,                                                                         KEY_TYPE_INT,    .get_fcn.fint    = nm_team_link_watcher_get_delay_up,    ),
-	_KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_DELAY_DOWN,        "delay-down",        NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL,                                                                         KEY_TYPE_INT,    .get_fcn.fint    = nm_team_link_watcher_get_delay_down,  ),
-	_KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT,         "init-wait",         NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING,                                     KEY_TYPE_INT,    .get_fcn.fint    = nm_team_link_watcher_get_init_wait,   ),
-	_KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_INTERVAL,          "interval",          NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING,                                     KEY_TYPE_INT,    .get_fcn.fint    = nm_team_link_watcher_get_interval,    ),
-	_KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX,        "missed-max",        NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING,                                     KEY_TYPE_INT,    .get_fcn.fint    = nm_team_link_watcher_get_missed_max,  .def_val.vint = 3, ),
-	_KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST,       "target-host",       NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING,                                     KEY_TYPE_STRING, .get_fcn.fstring = nm_team_link_watcher_get_target_host, ),
-	_KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_VLANID,            "vlanid",            NM_TEAM_LINK_WATCHER_TYPE_ARPING,                                                                          KEY_TYPE_INT,    .get_fcn.fint    = nm_team_link_watcher_get_vlanid,      .def_val.vint = -1, ),
-	_KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_SOURCE_HOST,       "source-host",       NM_TEAM_LINK_WATCHER_TYPE_ARPING,                                                                          KEY_TYPE_STRING, .get_fcn.fstring = nm_team_link_watcher_get_source_host, ),
-	_KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_VALIDATE_ACTIVE,   "validate-active",   NM_TEAM_LINK_WATCHER_TYPE_ARPING,                                                                          KEY_TYPE_BOOL,   .get_fcn.fbool   = _team_link_watcher_validate_active,   ),
-	_KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_VALIDATE_INACTIVE, "validate-inactive", NM_TEAM_LINK_WATCHER_TYPE_ARPING,                                                                          KEY_TYPE_BOOL,   .get_fcn.fbool   = _team_link_watcher_validate_inactive, ),
-	_KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_SEND_ALWAYS,       "send-always",       NM_TEAM_LINK_WATCHER_TYPE_ARPING,                                                                          KEY_TYPE_BOOL,   .get_fcn.fbool   = _team_link_watcher_send_always,       ),
-
-};
-
-static NMTeamLinkWatcherType
-_team_link_watcher_get_watcher_type_from_name (const char *name)
-{
-	if (name) {
-		if (nm_streq (name, NM_TEAM_LINK_WATCHER_ETHTOOL))
-			return NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL;
-		if (nm_streq (name, NM_TEAM_LINK_WATCHER_NSNA_PING))
-			return NM_TEAM_LINK_WATCHER_TYPE_NSNAPING;
-		if (nm_streq (name, NM_TEAM_LINK_WATCHER_ARP_PING))
-			return NM_TEAM_LINK_WATCHER_TYPE_ARPING;
-	}
-	return NM_TEAM_LINK_WATCHER_TYPE_NONE;
-}
-
-static const char *
-_parse_data_get_str (const ParseData parse_data[static _NM_TEAM_LINK_WATCHER_KEY_NUM],
-                     NMTeamLinkWatcherKeyId key_id)
-{
-	nm_assert (_NM_INT_NOT_NEGATIVE (key_id) && key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM);
-	nm_assert (_team_link_watcher_key_infos[key_id].key_type == KEY_TYPE_STRING);
-
-	return parse_data[key_id].str_val;
-}
-
-static int
-_parse_data_get_int (const ParseData parse_data[static _NM_TEAM_LINK_WATCHER_KEY_NUM],
-                     NMTeamLinkWatcherKeyId key_id)
-{
-	nm_assert (_NM_INT_NOT_NEGATIVE (key_id) && key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM);
-	nm_assert (_team_link_watcher_key_infos[key_id].key_type == KEY_TYPE_INT);
-
-	if (parse_data[key_id].str_val)
-		return parse_data[key_id].typ_val.vint;
-	return _team_link_watcher_key_infos[key_id].def_val.vint;
-}
-
-static int
-_parse_data_get_bool (const ParseData parse_data[static _NM_TEAM_LINK_WATCHER_KEY_NUM],
-                      NMTeamLinkWatcherKeyId key_id)
-{
-	nm_assert (_NM_INT_NOT_NEGATIVE (key_id) && key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM);
-	nm_assert (_team_link_watcher_key_infos[key_id].key_type == KEY_TYPE_BOOL);
-
-	if (parse_data[key_id].str_val)
-		return parse_data[key_id].typ_val.vbool;
-	return _team_link_watcher_key_infos[key_id].def_val.vbool;
-}
-
-char *
-nm_utils_team_link_watcher_to_string (const NMTeamLinkWatcher *watcher)
-{
-	nm_auto_free_gstring GString *str = NULL;
-	const char *name;
-	NMTeamLinkWatcherType watcher_type;
-	NMTeamLinkWatcherKeyId key_id;
-
-	if (!watcher)
-		return NULL;
-
-	str = g_string_new (NULL);
-
-	name = nm_team_link_watcher_get_name (watcher);
-	g_string_append_printf (str, "name=%s", name ?: "");
-
-	watcher_type = _team_link_watcher_get_watcher_type_from_name (name);
-
-	for (key_id = 0; key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM; key_id++) {
-		const TeamLinkWatcherKeyInfo *info = &_team_link_watcher_key_infos[key_id];
-		const char *vstr;
-		int vint;
-		bool vbool;
-
-		nm_assert (info->name && info->name && NM_STRCHAR_ALL (info->name, ch,((ch >= 'a' && ch <= 'z') || NM_IN_SET (ch, '-'))));
-		nm_assert (NM_IN_SET (info->key_type, KEY_TYPE_STRING,
-		                                      KEY_TYPE_INT,
-		                                      KEY_TYPE_BOOL));
-
-		if (key_id == NM_TEAM_LINK_WATCHER_KEY_NAME)
-			continue;
-
-		if (!NM_FLAGS_ALL (info->watcher_type, watcher_type))
-			continue;
-
-		switch (info->key_type) {
-		case KEY_TYPE_STRING:
-			vstr = info->get_fcn.fstring (watcher);
-			if (vstr) {
-				g_string_append_printf (nm_gstring_add_space_delimiter (str),
-				                        "%s=%s", info->name, vstr);
-			}
-			break;
-		case KEY_TYPE_INT:
-			vint = info->get_fcn.fint (watcher);
-			if (vint != info->def_val.vint) {
-				g_string_append_printf (nm_gstring_add_space_delimiter (str),
-				                        "%s=%d", info->name, vint);
-			}
-			break;
-		case KEY_TYPE_BOOL:
-			vbool = info->get_fcn.fbool (watcher);
-			if (vbool != info->def_val.vbool) {
-				g_string_append_printf (nm_gstring_add_space_delimiter (str),
-				                        "%s=%s", info->name, vbool ? "true" : "false");
-			}
-			break;
-		}
-	}
-
-	return g_string_free (g_steal_pointer (&str), FALSE);
-}
-
-NMTeamLinkWatcher *
-nm_utils_team_link_watcher_from_string (const char *str,
-                                        GError **error)
-{
-	gs_free const char **tokens = NULL;
-	ParseData parse_data[_NM_TEAM_LINK_WATCHER_KEY_NUM] = { };
-	NMTeamLinkWatcherType watcher_type;
-	NMTeamLinkWatcherKeyId key_id;
-	gsize i_token;
-	NMTeamLinkWatcher *watcher;
-	int errsv;
-
-	g_return_val_if_fail (str, NULL);
-	g_return_val_if_fail (!error || !*error, NULL);
-
-	tokens = nm_utils_escaped_tokens_split (str, NM_ASCII_SPACES);
-	if (!tokens) {
-		g_set_error (error, 1, 0, "'%s' is not valid", str);
-		return NULL;
-	}
-
-	for (i_token = 0; tokens[i_token]; i_token++) {
-		const TeamLinkWatcherKeyInfo *info;
-		const char *key = tokens[i_token];
-		const char *val;
-
-		val = strchr (key, '=');
-		if (!val) {
-			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
-			                    _("'%s' is not valid: properties should be specified as 'key=value'"),
-			                    key);
-			return NULL;
-		}
-		((char *) val)[0] = '\0';
-		val++;
-
-		for (key_id = 0; key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM; key_id++) {
-			info = &_team_link_watcher_key_infos[key_id];
-			if (nm_streq (key, info->name))
-				break;
-		}
-
-		if (key_id == _NM_TEAM_LINK_WATCHER_KEY_NUM) {
-			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
-			                    _("'%s' is not a valid key"), key);
-			return NULL;
-		}
-
-		if (parse_data[key_id].str_val) {
-			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
-			                    _("duplicate key '%s'"), key);
-			return NULL;
-		}
-
-		parse_data[key_id].str_val = val;
-
-		if (info->key_type == KEY_TYPE_INT) {
-			gint64 v64;
-
-			v64 = _nm_utils_ascii_str_to_int64 (val, 10, G_MININT, G_MAXINT, G_MAXINT64);
-			if (   v64 == G_MAXINT64
-			    && ((errsv = errno) != 0)) {
-				if (errsv == ERANGE) {
-					nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
-					                    _("number for '%s' is out of range"), key);
-				} else {
-					nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
-					                    _("value for '%s' must be a number"), key);
-				}
-				return NULL;
-			}
-			parse_data[key_id].typ_val.vint = v64;
-		} else if (info->key_type == KEY_TYPE_BOOL) {
-			int vbool;
-
-			vbool = _nm_utils_ascii_str_to_bool (val, -1);
-			if (vbool == -1) {
-				nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
-				                    _("value for '%s' must be a boolean"), key);
-				return NULL;
-			}
-			parse_data[key_id].typ_val.vbool = vbool;
-		}
-	}
-
-	if (!parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val) {
-		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
-		                    _("missing 'name' attribute"));
-		return NULL;
-	}
-
-	watcher_type = _team_link_watcher_get_watcher_type_from_name (parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val);
-	if (watcher_type == NM_TEAM_LINK_WATCHER_TYPE_NONE) {
-		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
-		                    _("invalid 'name' \"%s\""),
-		                    parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val);
-		return NULL;
-	}
-
-	for (key_id = 0; key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM; key_id++) {
-		const TeamLinkWatcherKeyInfo *info = &_team_link_watcher_key_infos[key_id];
-
-		if (!parse_data[key_id].str_val)
-			continue;
-		if (!NM_FLAGS_ALL (info->watcher_type, watcher_type)) {
-			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
-			                    _("attribute '%s' is invalid for \"%s\""),
-			                    info->name,
-			                    parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val);
-			return NULL;
-		}
-	}
-
-	switch (watcher_type) {
-	case NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL:
-		watcher = nm_team_link_watcher_new_ethtool (_parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_DELAY_UP),
-		                                            _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_DELAY_DOWN),
-		                                            error);
-		break;
-	case NM_TEAM_LINK_WATCHER_TYPE_NSNAPING:
-		watcher = nm_team_link_watcher_new_nsna_ping (_parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT),
-		                                              _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_INTERVAL),
-		                                              _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX),
-		                                              _parse_data_get_str (parse_data, NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST),
-		                                              error);
-		break;
-	default:
-		nm_assert (watcher_type == NM_TEAM_LINK_WATCHER_TYPE_ARPING);
-		watcher = nm_team_link_watcher_new_arp_ping2 (_parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT),
-		                                              _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_INTERVAL),
-		                                              _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX),
-		                                              _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_VLANID),
-		                                              _parse_data_get_str (parse_data, NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST),
-		                                              _parse_data_get_str (parse_data, NM_TEAM_LINK_WATCHER_KEY_SOURCE_HOST),
-		                                              (  NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE
-		                                               | (_parse_data_get_bool (parse_data, NM_TEAM_LINK_WATCHER_KEY_VALIDATE_ACTIVE)   ? NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE   : NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE)
-		                                               | (_parse_data_get_bool (parse_data, NM_TEAM_LINK_WATCHER_KEY_VALIDATE_INACTIVE) ? NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE : NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE)
-		                                               | (_parse_data_get_bool (parse_data, NM_TEAM_LINK_WATCHER_KEY_SEND_ALWAYS)       ? NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS       : NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE)
-		                                              ),
-		                                              error);
-		break;
-	}
-
-#if NM_MORE_ASSERTS > 5
-	if (watcher) {
-		gs_free char *str2 = NULL;
-		nm_auto_unref_team_link_watcher NMTeamLinkWatcher *watcher2 = NULL;
-		static _nm_thread_local int recursive;
-
-		nm_assert (!error || !*error);
-		if (recursive == 0) {
-			recursive = 1;
-			str2 = nm_utils_team_link_watcher_to_string (watcher);
-			nm_assert (str2);
-			watcher2 = nm_utils_team_link_watcher_from_string (str2, NULL);
-			nm_assert (watcher2);
-			nm_assert (nm_team_link_watcher_equal (watcher, watcher2));
-			nm_assert (nm_team_link_watcher_equal (watcher2, watcher));
-			nm_assert (recursive == 1);
-			recursive = 0;
-		}
-	} else
-		nm_assert (!error || *error);
-#endif
-
-	return watcher;
-}
diff --git a/shared/nm-libnm-core-aux/nm-libnm-core-aux.h b/shared/nm-libnm-core-aux/nm-libnm-core-aux.h
deleted file mode 100644
index 8a835205..00000000
--- a/shared/nm-libnm-core-aux/nm-libnm-core-aux.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1+
-/*
- * Copyright (C) 2019 Red Hat, Inc.
- */
-
-#ifndef __NM_LIBNM_CORE_AUX_H__
-#define __NM_LIBNM_CORE_AUX_H__
-
-#include "nm-setting-team.h"
-
-typedef enum {
-	NM_TEAM_LINK_WATCHER_TYPE_NONE     = 0,
-	NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL  = (1u << 0),
-	NM_TEAM_LINK_WATCHER_TYPE_NSNAPING = (1u << 1),
-	NM_TEAM_LINK_WATCHER_TYPE_ARPING   = (1u << 2),
-} NMTeamLinkWatcherType;
-
-typedef enum {
-	NM_TEAM_LINK_WATCHER_KEY_NAME,
-	NM_TEAM_LINK_WATCHER_KEY_DELAY_UP,
-	NM_TEAM_LINK_WATCHER_KEY_DELAY_DOWN,
-	NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT,
-	NM_TEAM_LINK_WATCHER_KEY_INTERVAL,
-	NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX,
-	NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST,
-	NM_TEAM_LINK_WATCHER_KEY_VLANID,
-	NM_TEAM_LINK_WATCHER_KEY_SOURCE_HOST,
-	NM_TEAM_LINK_WATCHER_KEY_VALIDATE_ACTIVE,
-	NM_TEAM_LINK_WATCHER_KEY_VALIDATE_INACTIVE,
-	NM_TEAM_LINK_WATCHER_KEY_SEND_ALWAYS,
-	_NM_TEAM_LINK_WATCHER_KEY_NUM,
-} NMTeamLinkWatcherKeyId;
-
-char *nm_utils_team_link_watcher_to_string (const NMTeamLinkWatcher *watcher);
-
-NMTeamLinkWatcher *nm_utils_team_link_watcher_from_string (const char *str,
-                                                           GError **error);
-
-#endif /* __NM_LIBNM_CORE_AUX_H__ */
diff --git a/shared/nm-libnm-core-intern/nm-auth-subject.c b/shared/nm-libnm-core-intern/nm-auth-subject.c
deleted file mode 100644
index 15c2d652..00000000
--- a/shared/nm-libnm-core-intern/nm-auth-subject.c
+++ /dev/null
@@ -1,446 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1+
-/*
- * Copyright (C) 2013 - 2014 Red Hat, Inc.
- */
-
-/**
- * SECTION:nm-auth-subject
- * @short_description: Encapsulates authentication information about a requestor
- *
- * #NMAuthSubject encpasulates identifying information about an entity that
- * makes requests, like process identifier and user UID.
- */
-
-#include "nm-default.h"
-
-#include "nm-auth-subject.h"
-
-#include <stdlib.h>
-
-enum {
-	PROP_0,
-	PROP_SUBJECT_TYPE,
-	PROP_UNIX_PROCESS_DBUS_SENDER,
-	PROP_UNIX_PROCESS_PID,
-	PROP_UNIX_PROCESS_UID,
-	PROP_UNIX_SESSION_ID,
-
-	PROP_LAST,
-};
-
-typedef struct {
-	NMAuthSubjectType subject_type;
-	struct {
-		gulong pid;
-		gulong uid;
-		guint64 start_time;
-		char *dbus_sender;
-	} unix_process;
-
-	struct {
-		char *id;
-	} unix_session;
-} NMAuthSubjectPrivate;
-
-struct _NMAuthSubject {
-	GObject parent;
-	NMAuthSubjectPrivate _priv;
-};
-
-struct _NMAuthSubjectClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMAuthSubject, nm_auth_subject, G_TYPE_OBJECT)
-
-#define NM_AUTH_SUBJECT_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMAuthSubject, NM_IS_AUTH_SUBJECT)
-
-/*****************************************************************************/
-
-#define CHECK_SUBJECT(self, error_value) \
-	NMAuthSubjectPrivate *priv; \
-	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (self), error_value); \
-	priv = NM_AUTH_SUBJECT_GET_PRIVATE (self); \
-
-#define CHECK_SUBJECT_TYPED(self, expected_subject_type, error_value) \
-	CHECK_SUBJECT (self, error_value); \
-	g_return_val_if_fail (priv->subject_type == (expected_subject_type), error_value);
-
-const char *
-nm_auth_subject_to_string (NMAuthSubject *self, char *buf, gsize buf_len)
-{
-	CHECK_SUBJECT (self, NULL);
-
-	switch (priv->subject_type) {
-	case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS:
-		g_snprintf (buf, buf_len, "unix-process[pid=%lu, uid=%lu, start=%llu]",
-		            (unsigned long) priv->unix_process.pid,
-		            (unsigned long) priv->unix_process.uid,
-		            (unsigned long long) priv->unix_process.start_time);
-		break;
-	case NM_AUTH_SUBJECT_TYPE_INTERNAL:
-		g_strlcpy (buf, "internal", buf_len);
-		break;
-	case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION:
-		g_snprintf (buf, buf_len, "unix-session[id=%s]",
-		            priv->unix_session.id);
-		break;
-	default:
-		g_strlcpy (buf, "invalid", buf_len);
-		break;
-	}
-	return buf;
-}
-
-/* returns a floating variant */
-GVariant *
-nm_auth_subject_unix_to_polkit_gvariant (NMAuthSubject *self)
-{
-	GVariantBuilder builder;
-	CHECK_SUBJECT (self, NULL);
-
-	switch (priv->subject_type) {
-
-	case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION:
-		g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
-		g_variant_builder_add (&builder, "{sv}", "session-id",
-		                       g_variant_new_string (priv->unix_session.id));
-		return g_variant_new ("(sa{sv})", "unix-session", &builder);
-
-	case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS:
-		g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
-		g_variant_builder_add (&builder, "{sv}", "pid",
-		                       g_variant_new_uint32 (priv->unix_process.pid));
-		g_variant_builder_add (&builder, "{sv}", "start-time",
-		                       g_variant_new_uint64 (priv->unix_process.start_time));
-		g_variant_builder_add (&builder, "{sv}", "uid",
-		                       g_variant_new_int32 (priv->unix_process.uid));
-		return g_variant_new ("(sa{sv})", "unix-process", &builder);
-
-	default:
-		g_return_val_if_reached (NULL);
-	}
-}
-
-NMAuthSubjectType
-nm_auth_subject_get_subject_type (NMAuthSubject *subject)
-{
-	CHECK_SUBJECT (subject, NM_AUTH_SUBJECT_TYPE_INVALID);
-
-	return priv->subject_type;
-}
-
-gulong
-nm_auth_subject_get_unix_process_pid (NMAuthSubject *subject)
-{
-	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, G_MAXULONG);
-
-	return priv->unix_process.pid;
-}
-
-gulong
-nm_auth_subject_get_unix_process_uid (NMAuthSubject *subject)
-{
-	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, G_MAXULONG);
-
-	return priv->unix_process.uid;
-}
-
-const char *
-nm_auth_subject_get_unix_process_dbus_sender (NMAuthSubject *subject)
-{
-	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, NULL);
-
-	return priv->unix_process.dbus_sender;
-}
-
-const char *
-nm_auth_subject_get_unix_session_id (NMAuthSubject *subject)
-{
-	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_SESSION, NULL);
-
-	return priv->unix_session.id;
-}
-
-/*****************************************************************************/
-
-/**
- * nm_auth_subject_new_internal():
- *
- * Creates a new auth subject representing the NetworkManager process itself.
- *
- * Returns: the new #NMAuthSubject
- */
-NMAuthSubject *
-nm_auth_subject_new_internal (void)
-{
-	return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT,
-                                          NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_INTERNAL,
-                                          NULL));
-}
-
-/**
- * nm_auth_subject_new_unix_session():
- *
- * Creates a new auth subject representing a given unix session.
- *
- * Returns: the new #NMAuthSubject
- */
-NMAuthSubject *
-nm_auth_subject_new_unix_session (const char *session_id)
-{
-	return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT,
-	                                      NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_UNIX_SESSION,
-	                                      NM_AUTH_SUBJECT_UNIX_SESSION_ID, session_id,
-	                                      NULL));
-}
-
-/**
- * nm_auth_subject_new_unix_process():
- *
- * Creates a new auth subject representing a given unix process.
- *
- * Returns: the new #NMAuthSubject
- */
-NMAuthSubject *
-nm_auth_subject_new_unix_process (const char *dbus_sender, gulong pid, gulong uid)
-{
-	return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT,
-	                                      NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS,
-	                                      NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER, dbus_sender,
-	                                      NM_AUTH_SUBJECT_UNIX_PROCESS_PID, pid,
-	                                      NM_AUTH_SUBJECT_UNIX_PROCESS_UID, uid,
-	                                      NULL));
-}
-
-/**
- * nm_auth_subject_new_unix_process_self():
- *
- * Creates a new auth subject representing the current executing process.
- *
- * Returns: the new #NMAuthSubject
- */
-NMAuthSubject *
-nm_auth_subject_new_unix_process_self (void)
-{
-	return nm_auth_subject_new_unix_process (NULL, getpid(), getuid());
-}
-
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
-{
-	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (object);
-
-	switch (prop_id) {
-	case PROP_SUBJECT_TYPE:
-		g_value_set_int (value, priv->subject_type);
-		break;
-	case PROP_UNIX_PROCESS_DBUS_SENDER:
-		g_value_set_string (value, priv->unix_process.dbus_sender);
-		break;
-	case PROP_UNIX_PROCESS_PID:
-		g_value_set_ulong (value, priv->unix_process.pid);
-		break;
-	case PROP_UNIX_PROCESS_UID:
-		g_value_set_ulong (value, priv->unix_process.uid);
-		break;
-	case PROP_UNIX_SESSION_ID:
-		g_value_set_string (value, priv->unix_session.id);
-		break;
-	default:
-		 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		 break;
-	}
-}
-
-static void
-set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
-{
-	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (object);
-	NMAuthSubjectType subject_type;
-	int i;
-	const char *str;
-	gulong id;
-
-	switch (prop_id) {
-	case PROP_SUBJECT_TYPE:
-		/* construct-only */
-		i = g_value_get_int (value);
-		g_return_if_fail (NM_IN_SET (i,
-		                             (int) NM_AUTH_SUBJECT_TYPE_INTERNAL,
-		                             (int) NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS,
-		                             (int) NM_AUTH_SUBJECT_TYPE_UNIX_SESSION));
-		subject_type = i;
-		priv->subject_type |= subject_type;
-		g_return_if_fail (priv->subject_type == subject_type);
-		break;
-	case PROP_UNIX_PROCESS_DBUS_SENDER:
-		/* construct-only */
-		if ((str = g_value_get_string (value))) {
-			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
-			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
-			priv->unix_process.dbus_sender = g_strdup (str);
-		}
-		break;
-	case PROP_UNIX_PROCESS_PID:
-		/* construct-only */
-		if ((id = g_value_get_ulong (value)) != G_MAXULONG) {
-			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
-			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
-			priv->unix_process.pid = id;
-		}
-		break;
-	case PROP_UNIX_PROCESS_UID:
-		/* construct-only */
-		if ((id = g_value_get_ulong (value)) != G_MAXULONG) {
-			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
-			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
-			priv->unix_process.uid = id;
-		}
-		break;
-	case PROP_UNIX_SESSION_ID:
-		/* construct-only */
-		if ((str = g_value_get_string (value))) {
-			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_SESSION;
-			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_SESSION);
-			priv->unix_session.id = g_strdup (str);
-		}
-		break;
-	default:
-		 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		 break;
-	}
-}
-
-static void
-_clear_private (NMAuthSubject *self)
-{
-	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (self);
-
-	priv->subject_type = NM_AUTH_SUBJECT_TYPE_INVALID;
-	priv->unix_process.pid = G_MAXULONG;
-	priv->unix_process.uid = G_MAXULONG;
-	nm_clear_g_free (&priv->unix_process.dbus_sender);
-
-	nm_clear_g_free (&priv->unix_session.id);
-}
-
-static void
-nm_auth_subject_init (NMAuthSubject *self)
-{
-	_clear_private (self);
-}
-
-static void
-constructed (GObject *object)
-{
-	NMAuthSubject *self = NM_AUTH_SUBJECT (object);
-	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (self);
-
-	/* validate that the created instance. */
-
-	switch (priv->subject_type) {
-	case NM_AUTH_SUBJECT_TYPE_INTERNAL:
-		priv->unix_process.pid = G_MAXULONG;
-		priv->unix_process.uid = 0;  /* internal uses 'root' user */
-		return;
-	case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS:
-		/* Ensure pid and uid to be representable as int32.
-		 * DBUS treats them as uint32, polkit library as int. */
-		if (priv->unix_process.pid > MIN (G_MAXINT, G_MAXINT32))
-			break;
-		if (priv->unix_process.uid > MIN (G_MAXINT, G_MAXINT32)) {
-			/* for uid==-1, libpolkit-gobject-1 detects the user based on the process id.
-			 * Don't bother and require the user id as parameter. */
-			break;
-		}
-
-		priv->unix_process.start_time = nm_utils_get_start_time_for_pid (priv->unix_process.pid, NULL, NULL);
-
-		if (!priv->unix_process.start_time) {
-			/* Is the process already gone? Then fail creation of the auth subject
-			 * by clearing the type. */
-			if (kill (priv->unix_process.pid, 0) != 0)
-				_clear_private (self);
-
-			/* Otherwise, although we didn't detect a start_time, the process is still around.
-			 * That could be due to procfs mounted with hidepid. So just accept the request.
-			 *
-			 * Polkit on the other side, will accept 0 and try to lookup /proc/$PID/stat
-			 * itself (and if it fails to do so, assume a start-time of 0 and proceed).
-			 * The only combination that would fail here, is when NM is able to read the
-			 * start-time, but polkit is not. */
-		}
-		return;
-	case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION:
-		return;
-	default:
-		break;
-	}
-
-	_clear_private (self);
-	g_return_if_reached ();
-}
-
-static void
-finalize (GObject *object)
-{
-	_clear_private ((NMAuthSubject *) object);
-
-	G_OBJECT_CLASS (nm_auth_subject_parent_class)->finalize (object);
-}
-
-static void
-nm_auth_subject_class_init (NMAuthSubjectClass *config_class)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
-
-	object_class->get_property = get_property;
-	object_class->set_property = set_property;
-	object_class->constructed = constructed;
-	object_class->finalize = finalize;
-
-	g_object_class_install_property
-	    (object_class, PROP_SUBJECT_TYPE,
-	     g_param_spec_int (NM_AUTH_SUBJECT_SUBJECT_TYPE, "", "",
-	                       NM_AUTH_SUBJECT_TYPE_INVALID,
-	                       NM_AUTH_SUBJECT_TYPE_UNIX_SESSION,
-	                       NM_AUTH_SUBJECT_TYPE_INVALID,
-	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT_ONLY |
-	                       G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	    (object_class, PROP_UNIX_PROCESS_DBUS_SENDER,
-	     g_param_spec_string (NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER, "", "",
-	                          NULL,
-	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	     (object_class, PROP_UNIX_PROCESS_PID,
-	      g_param_spec_ulong (NM_AUTH_SUBJECT_UNIX_PROCESS_PID, "", "",
-	                          0, G_MAXULONG, G_MAXULONG,
-	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	     (object_class, PROP_UNIX_PROCESS_UID,
-	      g_param_spec_ulong (NM_AUTH_SUBJECT_UNIX_PROCESS_UID, "", "",
-	                          0, G_MAXULONG, G_MAXULONG,
-	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	     (object_class, PROP_UNIX_SESSION_ID,
-	      g_param_spec_string (NM_AUTH_SUBJECT_UNIX_SESSION_ID, "", "",
-	                           NULL,
-	                           G_PARAM_READWRITE |
-	                           G_PARAM_CONSTRUCT_ONLY |
-	                           G_PARAM_STATIC_STRINGS));
-}
diff --git a/shared/nm-libnm-core-intern/nm-auth-subject.h b/shared/nm-libnm-core-intern/nm-auth-subject.h
deleted file mode 100644
index fc37b8e2..00000000
--- a/shared/nm-libnm-core-intern/nm-auth-subject.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1+
-/*
- * Copyright (C) 2013 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_AUTH_SUBJECT_H__
-#define __NETWORKMANAGER_AUTH_SUBJECT_H__
-
-#define NM_TYPE_AUTH_SUBJECT            (nm_auth_subject_get_type ())
-#define NM_AUTH_SUBJECT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_AUTH_SUBJECT, NMAuthSubject))
-#define NM_AUTH_SUBJECT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_AUTH_SUBJECT, NMAuthSubjectClass))
-#define NM_IS_AUTH_SUBJECT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_AUTH_SUBJECT))
-#define NM_IS_AUTH_SUBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_AUTH_SUBJECT))
-#define NM_AUTH_SUBJECT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_AUTH_SUBJECT, NMAuthSubjectClass))
-
-typedef enum {
-	NM_AUTH_SUBJECT_TYPE_INVALID      = 0,
-	NM_AUTH_SUBJECT_TYPE_INTERNAL     = 1,
-	NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS = 2,
-	NM_AUTH_SUBJECT_TYPE_UNIX_SESSION = 4,
-} NMAuthSubjectType;
-
-#define NM_AUTH_SUBJECT_SUBJECT_TYPE                           "subject-type"
-#define NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER               "unix-process-dbus-sender"
-#define NM_AUTH_SUBJECT_UNIX_PROCESS_PID                       "unix-process-pid"
-#define NM_AUTH_SUBJECT_UNIX_PROCESS_UID                       "unix-process-uid"
-#define NM_AUTH_SUBJECT_UNIX_SESSION_ID                        "unix-session-id"
-
-typedef struct _NMAuthSubjectClass NMAuthSubjectClass;
-typedef struct _NMAuthSubject NMAuthSubject;
-
-GType nm_auth_subject_get_type (void);
-
-NMAuthSubject *nm_auth_subject_new_internal (void);
-
-NMAuthSubject *nm_auth_subject_new_unix_session (const char *session_id);
-
-NMAuthSubject *nm_auth_subject_new_unix_process (const char *dbus_sender, gulong pid, gulong uid);
-
-NMAuthSubject *nm_auth_subject_new_unix_process_self (void);
-
-NMAuthSubjectType nm_auth_subject_get_subject_type (NMAuthSubject *subject);
-
-gulong nm_auth_subject_get_unix_process_pid (NMAuthSubject *subject);
-
-const char *nm_auth_subject_get_unix_process_dbus_sender (NMAuthSubject *subject);
-
-gulong nm_auth_subject_get_unix_process_uid (NMAuthSubject *subject);
-
-const char *nm_auth_subject_get_unix_session_id (NMAuthSubject *subject);
-
-const char *nm_auth_subject_to_string (NMAuthSubject *self, char *buf, gsize buf_len);
-
-GVariant *nm_auth_subject_unix_to_polkit_gvariant (NMAuthSubject *self);
-
-#endif /* __NETWORKMANAGER_AUTH_SUBJECT_H__ */
diff --git a/shared/nm-libnm-core-intern/nm-common-macros.h b/shared/nm-libnm-core-intern/nm-common-macros.h
deleted file mode 100644
index 5f011efd..00000000
--- a/shared/nm-libnm-core-intern/nm-common-macros.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1+
-/*
- * Copyright (C) 2016 Red Hat, Inc.
- */
-
-#ifndef __NM_COMMON_MACROS_H__
-#define __NM_COMMON_MACROS_H__
-
-/*****************************************************************************/
-
-#define NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK     "org.freedesktop.NetworkManager.enable-disable-network"
-#define NM_AUTH_PERMISSION_SLEEP_WAKE                 "org.freedesktop.NetworkManager.sleep-wake"
-#define NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI        "org.freedesktop.NetworkManager.enable-disable-wifi"
-#define NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN        "org.freedesktop.NetworkManager.enable-disable-wwan"
-#define NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX       "org.freedesktop.NetworkManager.enable-disable-wimax"
-#define NM_AUTH_PERMISSION_NETWORK_CONTROL            "org.freedesktop.NetworkManager.network-control"
-#define NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED       "org.freedesktop.NetworkManager.wifi.share.protected"
-#define NM_AUTH_PERMISSION_WIFI_SHARE_OPEN            "org.freedesktop.NetworkManager.wifi.share.open"
-#define NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM     "org.freedesktop.NetworkManager.settings.modify.system"
-#define NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN        "org.freedesktop.NetworkManager.settings.modify.own"
-#define NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME   "org.freedesktop.NetworkManager.settings.modify.hostname"
-#define NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS "org.freedesktop.NetworkManager.settings.modify.global-dns"
-#define NM_AUTH_PERMISSION_RELOAD                     "org.freedesktop.NetworkManager.reload"
-#define NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK        "org.freedesktop.NetworkManager.checkpoint-rollback"
-#define NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS  "org.freedesktop.NetworkManager.enable-disable-statistics"
-#define NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK "org.freedesktop.NetworkManager.enable-disable-connectivity-check"
-#define NM_AUTH_PERMISSION_WIFI_SCAN                  "org.freedesktop.NetworkManager.wifi.scan"
-
-#define NM_CLONED_MAC_PRESERVE                          "preserve"
-#define NM_CLONED_MAC_PERMANENT                         "permanent"
-#define NM_CLONED_MAC_RANDOM                            "random"
-#define NM_CLONED_MAC_STABLE                            "stable"
-
-static inline gboolean
-NM_CLONED_MAC_IS_SPECIAL (const char *str)
-{
-	return NM_IN_STRSET (str,
-	                     NM_CLONED_MAC_PRESERVE,
-	                     NM_CLONED_MAC_PERMANENT,
-	                     NM_CLONED_MAC_RANDOM,
-	                     NM_CLONED_MAC_STABLE);
-}
-
-#define NM_IAID_MAC                "mac"
-#define NM_IAID_PERM_MAC           "perm-mac"
-#define NM_IAID_IFNAME             "ifname"
-#define NM_IAID_STABLE             "stable"
-
-static inline gboolean
-NM_IAID_IS_SPECIAL (const char *str)
-{
-	return NM_IN_STRSET (str,
-	                     NM_IAID_MAC,
-	                     NM_IAID_PERM_MAC,
-	                     NM_IAID_IFNAME,
-	                     NM_IAID_STABLE);
-}
-
-#endif /* __NM_COMMON_MACROS_H__ */
diff --git a/shared/nm-libnm-core-intern/nm-ethtool-utils.c b/shared/nm-libnm-core-intern/nm-ethtool-utils.c
deleted file mode 100644
index 2bc182cf..00000000
--- a/shared/nm-libnm-core-intern/nm-ethtool-utils.c
+++ /dev/null
@@ -1,209 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1+
-/*
- * Copyright (C) 2018 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-ethtool-utils.h"
-
-#include "nm-setting-ethtool.h"
-
-/*****************************************************************************/
-
-#define ETHT_DATA(xname) \
-	[NM_ETHTOOL_ID_##xname] = (&((const NMEthtoolData) { \
-	   .optname = NM_ETHTOOL_OPTNAME_##xname, \
-	   .id = NM_ETHTOOL_ID_##xname, \
-	}))
-
-const NMEthtoolData *const nm_ethtool_data[_NM_ETHTOOL_ID_NUM + 1] = {
-	/* indexed by NMEthtoolID */
-	ETHT_DATA (FEATURE_ESP_HW_OFFLOAD),
-	ETHT_DATA (FEATURE_ESP_TX_CSUM_HW_OFFLOAD),
-	ETHT_DATA (FEATURE_FCOE_MTU),
-	ETHT_DATA (FEATURE_GRO),
-	ETHT_DATA (FEATURE_GSO),
-	ETHT_DATA (FEATURE_HIGHDMA),
-	ETHT_DATA (FEATURE_HW_TC_OFFLOAD),
-	ETHT_DATA (FEATURE_L2_FWD_OFFLOAD),
-	ETHT_DATA (FEATURE_LOOPBACK),
-	ETHT_DATA (FEATURE_LRO),
-	ETHT_DATA (FEATURE_NTUPLE),
-	ETHT_DATA (FEATURE_RX),
-	ETHT_DATA (FEATURE_RXHASH),
-	ETHT_DATA (FEATURE_RXVLAN),
-	ETHT_DATA (FEATURE_RX_ALL),
-	ETHT_DATA (FEATURE_RX_FCS),
-	ETHT_DATA (FEATURE_RX_GRO_HW),
-	ETHT_DATA (FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD),
-	ETHT_DATA (FEATURE_RX_VLAN_FILTER),
-	ETHT_DATA (FEATURE_RX_VLAN_STAG_FILTER),
-	ETHT_DATA (FEATURE_RX_VLAN_STAG_HW_PARSE),
-	ETHT_DATA (FEATURE_SG),
-	ETHT_DATA (FEATURE_TLS_HW_RECORD),
-	ETHT_DATA (FEATURE_TLS_HW_TX_OFFLOAD),
-	ETHT_DATA (FEATURE_TSO),
-	ETHT_DATA (FEATURE_TX),
-	ETHT_DATA (FEATURE_TXVLAN),
-	ETHT_DATA (FEATURE_TX_CHECKSUM_FCOE_CRC),
-	ETHT_DATA (FEATURE_TX_CHECKSUM_IPV4),
-	ETHT_DATA (FEATURE_TX_CHECKSUM_IPV6),
-	ETHT_DATA (FEATURE_TX_CHECKSUM_IP_GENERIC),
-	ETHT_DATA (FEATURE_TX_CHECKSUM_SCTP),
-	ETHT_DATA (FEATURE_TX_ESP_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_FCOE_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_GRE_CSUM_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_GRE_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_GSO_PARTIAL),
-	ETHT_DATA (FEATURE_TX_GSO_ROBUST),
-	ETHT_DATA (FEATURE_TX_IPXIP4_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_IPXIP6_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_NOCACHE_COPY),
-	ETHT_DATA (FEATURE_TX_SCATTER_GATHER),
-	ETHT_DATA (FEATURE_TX_SCATTER_GATHER_FRAGLIST),
-	ETHT_DATA (FEATURE_TX_SCTP_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_TCP6_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_TCP_ECN_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_TCP_MANGLEID_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_TCP_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_UDP_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_UDP_TNL_SEGMENTATION),
-	ETHT_DATA (FEATURE_TX_VLAN_STAG_HW_INSERT),
-	[_NM_ETHTOOL_ID_NUM] = NULL,
-};
-
-static const guint8 _by_name[_NM_ETHTOOL_ID_NUM] = {
-	/* sorted by optname. */
-	NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD,
-	NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD,
-	NM_ETHTOOL_ID_FEATURE_FCOE_MTU,
-	NM_ETHTOOL_ID_FEATURE_GRO,
-	NM_ETHTOOL_ID_FEATURE_GSO,
-	NM_ETHTOOL_ID_FEATURE_HIGHDMA,
-	NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD,
-	NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD,
-	NM_ETHTOOL_ID_FEATURE_LOOPBACK,
-	NM_ETHTOOL_ID_FEATURE_LRO,
-	NM_ETHTOOL_ID_FEATURE_NTUPLE,
-	NM_ETHTOOL_ID_FEATURE_RX,
-	NM_ETHTOOL_ID_FEATURE_RX_ALL,
-	NM_ETHTOOL_ID_FEATURE_RX_FCS,
-	NM_ETHTOOL_ID_FEATURE_RX_GRO_HW,
-	NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD,
-	NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER,
-	NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER,
-	NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE,
-	NM_ETHTOOL_ID_FEATURE_RXHASH,
-	NM_ETHTOOL_ID_FEATURE_RXVLAN,
-	NM_ETHTOOL_ID_FEATURE_SG,
-	NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD,
-	NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD,
-	NM_ETHTOOL_ID_FEATURE_TSO,
-	NM_ETHTOOL_ID_FEATURE_TX,
-	NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC,
-	NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC,
-	NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4,
-	NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6,
-	NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP,
-	NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL,
-	NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST,
-	NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY,
-	NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER,
-	NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST,
-	NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT,
-	NM_ETHTOOL_ID_FEATURE_TXVLAN,
-};
-
-/*****************************************************************************/
-
-static void
-_ASSERT_data (void)
-{
-#if NM_MORE_ASSERTS > 10
-	int i;
-
-	G_STATIC_ASSERT_EXPR (_NM_ETHTOOL_ID_FIRST == 0);
-	G_STATIC_ASSERT_EXPR (_NM_ETHTOOL_ID_LAST == _NM_ETHTOOL_ID_NUM - 1);
-	G_STATIC_ASSERT_EXPR (_NM_ETHTOOL_ID_NUM > 0);
-
-	nm_assert (NM_PTRARRAY_LEN (nm_ethtool_data) == _NM_ETHTOOL_ID_NUM);
-	nm_assert (G_N_ELEMENTS (_by_name)           == _NM_ETHTOOL_ID_NUM);
-	nm_assert (G_N_ELEMENTS (nm_ethtool_data)    == _NM_ETHTOOL_ID_NUM + 1);
-
-	for (i = 0; i < _NM_ETHTOOL_ID_NUM; i++) {
-		const NMEthtoolData *d = nm_ethtool_data[i];
-
-		nm_assert (d);
-		nm_assert (d->id == (NMEthtoolID) i);
-		nm_assert (d->optname && d->optname[0]);
-	}
-
-	for (i = 0; i < _NM_ETHTOOL_ID_NUM; i++) {
-		NMEthtoolID id = _by_name[i];
-		const NMEthtoolData *d;
-
-		nm_assert (id >= 0);
-		nm_assert (id < _NM_ETHTOOL_ID_NUM);
-
-		d = nm_ethtool_data[id];
-		if (i > 0) {
-			/* since we assert that all optnames are sorted strictly monotonically increasing,
-			 * it also follows that there are no duplicates in the _by_name.
-			 * It also follows, that all names in nm_ethtool_data are unique. */
-			if (strcmp (nm_ethtool_data[_by_name[i - 1]]->optname, d->optname) >= 0) {
-				g_error ("nm_ethtool_data is not sorted asciibetically: %u/%s should be after %u/%s",
-				         i - 1, nm_ethtool_data[_by_name[i - 1]]->optname,
-				         i, d->optname);
-			}
-		}
-	}
-#endif
-}
-
-static int
-_by_name_cmp (gconstpointer a,
-              gconstpointer b,
-              gpointer user_data)
-{
-	const guint8 *p_id = a;
-	const char *optname = b;
-
-	nm_assert (p_id && p_id >= _by_name && p_id <= &_by_name[_NM_ETHTOOL_ID_NUM]);
-	nm_assert (*p_id < _NM_ETHTOOL_ID_NUM);
-
-	return strcmp (nm_ethtool_data[*p_id]->optname, optname);
-}
-
-const NMEthtoolData *
-nm_ethtool_data_get_by_optname (const char *optname)
-{
-	gssize idx;
-
-	nm_assert (optname);
-
-	_ASSERT_data ();
-
-	idx = nm_utils_array_find_binary_search ((gconstpointer *) _by_name,
-	                                         sizeof (_by_name[0]),
-	                                         _NM_ETHTOOL_ID_NUM,
-	                                         optname,
-	                                         _by_name_cmp,
-	                                         NULL);
-	return (idx < 0) ? NULL : nm_ethtool_data[_by_name[idx]];
-}
diff --git a/shared/nm-libnm-core-intern/nm-ethtool-utils.h b/shared/nm-libnm-core-intern/nm-ethtool-utils.h
deleted file mode 100644
index b3413de2..00000000
--- a/shared/nm-libnm-core-intern/nm-ethtool-utils.h
+++ /dev/null
@@ -1,105 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1+
-/*
- * Copyright (C) 2018 Red Hat, Inc.
- */
-
-#ifndef __NM_ETHTOOL_UTILS_H__
-#define __NM_ETHTOOL_UTILS_H__
-
-/*****************************************************************************/
-
-typedef enum {
-	NM_ETHTOOL_ID_UNKNOWN = -1,
-
-	_NM_ETHTOOL_ID_FIRST = 0,
-
-	_NM_ETHTOOL_ID_FEATURE_FIRST = _NM_ETHTOOL_ID_FIRST,
-	NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD = _NM_ETHTOOL_ID_FEATURE_FIRST,
-	NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD,
-	NM_ETHTOOL_ID_FEATURE_FCOE_MTU,
-	NM_ETHTOOL_ID_FEATURE_GRO,
-	NM_ETHTOOL_ID_FEATURE_GSO,
-	NM_ETHTOOL_ID_FEATURE_HIGHDMA,
-	NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD,
-	NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD,
-	NM_ETHTOOL_ID_FEATURE_LOOPBACK,
-	NM_ETHTOOL_ID_FEATURE_LRO,
-	NM_ETHTOOL_ID_FEATURE_NTUPLE,
-	NM_ETHTOOL_ID_FEATURE_RX,
-	NM_ETHTOOL_ID_FEATURE_RXHASH,
-	NM_ETHTOOL_ID_FEATURE_RXVLAN,
-	NM_ETHTOOL_ID_FEATURE_RX_ALL,
-	NM_ETHTOOL_ID_FEATURE_RX_FCS,
-	NM_ETHTOOL_ID_FEATURE_RX_GRO_HW,
-	NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD,
-	NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER,
-	NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER,
-	NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE,
-	NM_ETHTOOL_ID_FEATURE_SG,
-	NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD,
-	NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD,
-	NM_ETHTOOL_ID_FEATURE_TSO,
-	NM_ETHTOOL_ID_FEATURE_TX,
-	NM_ETHTOOL_ID_FEATURE_TXVLAN,
-	NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC,
-	NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4,
-	NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6,
-	NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC,
-	NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP,
-	NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL,
-	NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST,
-	NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY,
-	NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER,
-	NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST,
-	NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION,
-	NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT,
-	_NM_ETHTOOL_ID_FEATURE_LAST = NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT,
-	_NM_ETHTOOL_ID_FEATURE_NUM = (_NM_ETHTOOL_ID_FEATURE_LAST - _NM_ETHTOOL_ID_FEATURE_FIRST + 1),
-
-	_NM_ETHTOOL_ID_LAST = _NM_ETHTOOL_ID_FEATURE_LAST,
-
-	_NM_ETHTOOL_ID_NUM = (_NM_ETHTOOL_ID_LAST - _NM_ETHTOOL_ID_FIRST + 1),
-} NMEthtoolID;
-
-typedef struct {
-	const char *optname;
-	NMEthtoolID id;
-} NMEthtoolData;
-
-extern const NMEthtoolData *const nm_ethtool_data[_NM_ETHTOOL_ID_NUM + 1];
-
-const NMEthtoolData *nm_ethtool_data_get_by_optname (const char *optname);
-
-/****************************************************************************/
-
-static inline NMEthtoolID
-nm_ethtool_id_get_by_name (const char *optname)
-{
-	const NMEthtoolData *d;
-
-	d = nm_ethtool_data_get_by_optname (optname);
-	return d ? d->id : NM_ETHTOOL_ID_UNKNOWN;
-}
-
-static inline gboolean
-nm_ethtool_id_is_feature (NMEthtoolID id)
-{
-	return id >= _NM_ETHTOOL_ID_FEATURE_FIRST && id <= _NM_ETHTOOL_ID_FEATURE_LAST;
-}
-
-/****************************************************************************/
-
-#endif /* __NM_ETHTOOL_UTILS_H__ */
diff --git a/shared/nm-libnm-core-intern/nm-libnm-core-utils.c b/shared/nm-libnm-core-intern/nm-libnm-core-utils.c
deleted file mode 100644
index df2f2e77..00000000
--- a/shared/nm-libnm-core-intern/nm-libnm-core-utils.c
+++ /dev/null
@@ -1,180 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1+
-
-#include "nm-default.h"
-
-#include "nm-libnm-core-utils.h"
-
-#include "nm-common-macros.h"
-
-/*****************************************************************************/
-
-gboolean
-nm_utils_vlan_priority_map_parse_str (NMVlanPriorityMap map_type,
-                                      const char *str,
-                                      gboolean allow_wildcard_to,
-                                      guint32 *out_from,
-                                      guint32 *out_to,
-                                      gboolean *out_has_wildcard_to)
-{
-	const char *s2;
-	gint64 v1, v2;
-
-	nm_assert (str);
-
-	s2 = strchr (str, ':');
-
-	if (!s2) {
-		if (!allow_wildcard_to)
-			return FALSE;
-		v1 = _nm_utils_ascii_str_to_int64 (str, 10, 0, G_MAXUINT32, -1);
-		v2 = -1;
-	} else {
-		gs_free char *s1_free = NULL;
-		gsize s1_len = (s2 - str);
-
-		s2 = nm_str_skip_leading_spaces (&s2[1]);
-		if (   s2[0] == '\0'
-		    || (   s2[0] == '*'
-		        && NM_STRCHAR_ALL (&s2[1], ch, g_ascii_isspace (ch)))) {
-			if (!allow_wildcard_to)
-				return FALSE;
-			v2 = -1;
-		} else {
-			v2 = _nm_utils_ascii_str_to_int64 (s2, 10, 0, G_MAXUINT32, -1);
-			if (   v2 < 0
-			    || (guint32) v2 > nm_utils_vlan_priority_map_get_max_prio (map_type, FALSE))
-				return FALSE;
-		}
-
-		v1 = _nm_utils_ascii_str_to_int64 (nm_strndup_a (100, str, s1_len, &s1_free),
-		                                   10, 0, G_MAXUINT32, -1);
-	}
-
-	if (   v1 < 0
-	    || (guint32) v1 > nm_utils_vlan_priority_map_get_max_prio (map_type, TRUE))
-		return FALSE;
-
-	NM_SET_OUT (out_from, v1);
-	NM_SET_OUT (out_to,   v2 < 0
-	                    ? 0u
-	                    : (guint) v2);
-	NM_SET_OUT (out_has_wildcard_to, v2 < 0);
-	return TRUE;
-}
-
-/*****************************************************************************/
-
-const char *const nm_auth_permission_names_by_idx[NM_CLIENT_PERMISSION_LAST] = {
-	[NM_CLIENT_PERMISSION_CHECKPOINT_ROLLBACK - 1]               = NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK,
-	[NM_CLIENT_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK,
-	[NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK - 1]            = NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK,
-	[NM_CLIENT_PERMISSION_ENABLE_DISABLE_STATISTICS - 1]         = NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS,
-	[NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI - 1]               = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI,
-	[NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX - 1]              = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX,
-	[NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN - 1]               = NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN,
-	[NM_CLIENT_PERMISSION_NETWORK_CONTROL - 1]                   = NM_AUTH_PERMISSION_NETWORK_CONTROL,
-	[NM_CLIENT_PERMISSION_RELOAD - 1]                            = NM_AUTH_PERMISSION_RELOAD,
-	[NM_CLIENT_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS - 1]        = NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS,
-	[NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME - 1]          = NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME,
-	[NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN - 1]               = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN,
-	[NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM - 1]            = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM,
-	[NM_CLIENT_PERMISSION_SLEEP_WAKE - 1]                        = NM_AUTH_PERMISSION_SLEEP_WAKE,
-	[NM_CLIENT_PERMISSION_WIFI_SCAN - 1]                         = NM_AUTH_PERMISSION_WIFI_SCAN,
-	[NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN - 1]                   = NM_AUTH_PERMISSION_WIFI_SHARE_OPEN,
-	[NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED - 1]              = NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED,
-};
-
-const NMClientPermission nm_auth_permission_sorted[NM_CLIENT_PERMISSION_LAST] = {
-	NM_CLIENT_PERMISSION_CHECKPOINT_ROLLBACK,
-	NM_CLIENT_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK,
-	NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK,
-	NM_CLIENT_PERMISSION_ENABLE_DISABLE_STATISTICS,
-	NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI,
-	NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX,
-	NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN,
-	NM_CLIENT_PERMISSION_NETWORK_CONTROL,
-	NM_CLIENT_PERMISSION_RELOAD,
-	NM_CLIENT_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS,
-	NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME,
-	NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN,
-	NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM,
-	NM_CLIENT_PERMISSION_SLEEP_WAKE,
-	NM_CLIENT_PERMISSION_WIFI_SCAN,
-	NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN,
-	NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED,
-};
-
-const char *
-nm_auth_permission_to_string (NMClientPermission permission)
-{
-	if (permission < 1)
-		return NULL;
-	if (permission > NM_CLIENT_PERMISSION_LAST)
-		return NULL;
-	return nm_auth_permission_names_by_idx[permission - 1];
-}
-
-#define AUTH_PERMISSION_PREFIX "org.freedesktop.NetworkManager."
-
-static int
-_nm_auth_permission_from_string_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
-{
-	const NMClientPermission *const p = a;
-	const char *const needle = b;
-	const char *ss = nm_auth_permission_names_by_idx[*p - 1];
-
-	nm_assert (NM_STR_HAS_PREFIX (ss, AUTH_PERMISSION_PREFIX));
-	nm_assert (ss[NM_STRLEN (AUTH_PERMISSION_PREFIX)] != '\0');
-
-	return strcmp (&ss[NM_STRLEN (AUTH_PERMISSION_PREFIX)], needle);
-}
-
-NMClientPermission
-nm_auth_permission_from_string (const char *str)
-{
-	gssize idx;
-
-	if (!str)
-		return NM_CLIENT_PERMISSION_NONE;
-
-	if (!NM_STR_HAS_PREFIX (str, AUTH_PERMISSION_PREFIX))
-		return NM_CLIENT_PERMISSION_NONE;
-	idx = nm_utils_array_find_binary_search (nm_auth_permission_sorted,
-	                                         sizeof (nm_auth_permission_sorted[0]),
-	                                         G_N_ELEMENTS (nm_auth_permission_sorted),
-	                                         &str[NM_STRLEN (AUTH_PERMISSION_PREFIX)],
-	                                         _nm_auth_permission_from_string_cmp,
-	                                         NULL);
-	if (idx < 0)
-		return NM_CLIENT_PERMISSION_NONE;
-	return nm_auth_permission_sorted[idx];
-}
-
-/*****************************************************************************/
-
-NMClientPermissionResult
-nm_client_permission_result_from_string (const char *nm)
-{
-	if (!nm)
-		return NM_CLIENT_PERMISSION_RESULT_UNKNOWN;
-	if (nm_streq (nm, "yes"))
-		return NM_CLIENT_PERMISSION_RESULT_YES;
-	if (nm_streq (nm, "no"))
-		return NM_CLIENT_PERMISSION_RESULT_NO;
-	if (nm_streq (nm, "auth"))
-		return NM_CLIENT_PERMISSION_RESULT_AUTH;
-	return NM_CLIENT_PERMISSION_RESULT_UNKNOWN;
-}
-
-const char *
-nm_client_permission_result_to_string (NMClientPermissionResult permission)
-{
-	switch (permission) {
-	case NM_CLIENT_PERMISSION_RESULT_YES:     return "yes";
-	case NM_CLIENT_PERMISSION_RESULT_NO:      return "no";
-	case NM_CLIENT_PERMISSION_RESULT_AUTH:    return "auth";
-	case NM_CLIENT_PERMISSION_RESULT_UNKNOWN: return "unknown";
-	}
-	nm_assert_not_reached ();
-	return NULL;
-}
diff --git a/shared/nm-libnm-core-intern/nm-libnm-core-utils.h b/shared/nm-libnm-core-intern/nm-libnm-core-utils.h
deleted file mode 100644
index a35a5e15..00000000
--- a/shared/nm-libnm-core-intern/nm-libnm-core-utils.h
+++ /dev/null
@@ -1,115 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1+
-
-#ifndef __NM_LIBNM_SHARED_UTILS_H__
-#define __NM_LIBNM_SHARED_UTILS_H__
-
-/****************************************************************************/
-
-#include "nm-setting-bridge.h"
-#include "nm-setting-connection.h"
-#include "nm-setting-ip-config.h"
-#include "nm-setting-ip4-config.h"
-#include "nm-setting-ip6-config.h"
-#include "nm-setting-sriov.h"
-#include "nm-setting-team.h"
-#include "nm-setting-vlan.h"
-#include "nm-setting-wireguard.h"
-
-/****************************************************************************/
-
-#define nm_auto_unref_ip_address nm_auto (_nm_ip_address_unref)
-NM_AUTO_DEFINE_FCN0 (NMIPAddress *, _nm_ip_address_unref, nm_ip_address_unref)
-
-#define nm_auto_unref_ip_route nm_auto (_nm_auto_unref_ip_route)
-NM_AUTO_DEFINE_FCN0 (NMIPRoute *, _nm_auto_unref_ip_route, nm_ip_route_unref)
-
-#define nm_auto_unref_ip_routing_rule nm_auto(_nm_auto_unref_ip_routing_rule)
-NM_AUTO_DEFINE_FCN0 (NMIPRoutingRule *, _nm_auto_unref_ip_routing_rule, nm_ip_routing_rule_unref)
-
-#define nm_auto_unref_sriov_vf nm_auto (_nm_auto_unref_sriov_vf)
-NM_AUTO_DEFINE_FCN0 (NMSriovVF *, _nm_auto_unref_sriov_vf, nm_sriov_vf_unref)
-
-#define nm_auto_unref_tc_qdisc nm_auto (_nm_auto_unref_tc_qdisc)
-NM_AUTO_DEFINE_FCN0 (NMTCQdisc *, _nm_auto_unref_tc_qdisc, nm_tc_qdisc_unref)
-
-#define nm_auto_unref_tc_tfilter nm_auto (_nm_auto_unref_tc_tfilter)
-NM_AUTO_DEFINE_FCN0 (NMTCTfilter *, _nm_auto_unref_tc_tfilter, nm_tc_tfilter_unref)
-
-#define nm_auto_unref_bridge_vlan nm_auto (_nm_auto_unref_bridge_vlan)
-NM_AUTO_DEFINE_FCN0 (NMBridgeVlan *, _nm_auto_unref_bridge_vlan, nm_bridge_vlan_unref)
-
-#define nm_auto_unref_team_link_watcher nm_auto (_nm_auto_unref_team_link_watcher)
-NM_AUTO_DEFINE_FCN0 (NMTeamLinkWatcher *, _nm_auto_unref_team_link_watcher, nm_team_link_watcher_unref)
-
-#define nm_auto_unref_wgpeer nm_auto (_nm_auto_unref_wgpeer)
-NM_AUTO_DEFINE_FCN0 (NMWireGuardPeer *, _nm_auto_unref_wgpeer, nm_wireguard_peer_unref)
-
-/****************************************************************************/
-
-static inline guint32
-nm_utils_vlan_priority_map_get_max_prio (NMVlanPriorityMap map, gboolean from)
-{
-	if (map == NM_VLAN_INGRESS_MAP) {
-		return   from
-		       ? 7u /* MAX_8021P_PRIO */
-		       : (guint32) G_MAXUINT32 /* MAX_SKB_PRIO */;
-	}
-	nm_assert (map == NM_VLAN_EGRESS_MAP);
-	return   from
-	       ? (guint32) G_MAXUINT32 /* MAX_SKB_PRIO */
-	       : 7u /* MAX_8021P_PRIO */;
-}
-
-gboolean nm_utils_vlan_priority_map_parse_str (NMVlanPriorityMap map_type,
-                                               const char *str,
-                                               gboolean allow_wildcard_to,
-                                               guint32 *out_from,
-                                               guint32 *out_to,
-                                               gboolean *out_has_wildcard_to);
-
-/*****************************************************************************/
-
-static inline int
-nm_setting_ip_config_get_addr_family (NMSettingIPConfig *s_ip)
-{
-	if (NM_IS_SETTING_IP4_CONFIG (s_ip))
-		return AF_INET;
-	if (NM_IS_SETTING_IP6_CONFIG (s_ip))
-		return AF_INET6;
-	g_return_val_if_reached (AF_UNSPEC);
-}
-
-/*****************************************************************************/
-
-/* The maximum MTU for infiniband.
- *
- * This is both in transport-mode "datagram" and "connected"
- * and they both have the same maximum define.
- *
- * Note that in the past, MTU in "datagram" mode was restricted
- * to 2044 bytes. That is no longer the case and we accept large
- * MTUs.
- *
- * This define is the maxiumum for the MTU in a connection profile (the
- * setting). Whether large MTUs can be configured later (at activation time)
- * depends on other factors. */
-#define NM_INFINIBAND_MAX_MTU ((guint) 65520)
-
-/*****************************************************************************/
-
-#define _NM_CAPABILITY_MAX NM_CAPABILITY_OVS
-
-/*****************************************************************************/
-
-extern const char *const nm_auth_permission_names_by_idx[NM_CLIENT_PERMISSION_LAST];
-extern const NMClientPermission nm_auth_permission_sorted[NM_CLIENT_PERMISSION_LAST];
-
-const char *nm_auth_permission_to_string (NMClientPermission permission);
-NMClientPermission nm_auth_permission_from_string (const char *str);
-
-/*****************************************************************************/
-
-NMClientPermissionResult nm_client_permission_result_from_string (const char *nm);
-const char *nm_client_permission_result_to_string (NMClientPermissionResult permission);
-
-#endif /* __NM_LIBNM_SHARED_UTILS_H__ */
diff --git a/shared/nm-test-libnm-utils.h b/shared/nm-test-libnm-utils.h
index 06dbc72d..04e8fb64 100644
--- a/shared/nm-test-libnm-utils.h
+++ b/shared/nm-test-libnm-utils.h
@@ -3,6 +3,9 @@
  * Copyright (C) 2014 - 2015 Red Hat, Inc.
  */
 
+#ifndef __NM_TEST_LIBNM_UTILS_H__
+#define __NM_TEST_LIBNM_UTILS_H__
+
 #include "NetworkManager.h"
 
 #include "nm-utils/nm-test-utils.h"
@@ -82,3 +85,5 @@ nmtstc_client_new (gboolean allow_iterate_main_context)
 {
 	return nmtstc_context_object_new (NM_TYPE_CLIENT, allow_iterate_main_context, NULL);
 }
+
+#endif /* __NM_TEST_LIBNM_UTILS_H__ */
diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h
index 96e36135..c270d2b9 100644
--- a/shared/nm-utils/nm-test-utils.h
+++ b/shared/nm-utils/nm-test-utils.h
@@ -1924,8 +1924,8 @@ nmtst_assert_connection_equals (NMConnection *a, gboolean normalize_a, NMConnect
 			gs_unref_keyfile GKeyFile *kf_a = NULL, *kf_b = NULL;
 			gs_free char *str_a = NULL, *str_b = NULL;
 
-			kf_a = nm_keyfile_write (a, NULL, NULL, NULL);
-			kf_b = nm_keyfile_write (b, NULL, NULL, NULL);
+			kf_a = nm_keyfile_write (a, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, NULL);
+			kf_b = nm_keyfile_write (b, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, NULL);
 
 			if (kf_a)
 				str_a = g_key_file_to_data (kf_a, NULL, NULL);
@@ -2262,7 +2262,7 @@ nmtst_create_connection_from_keyfile (const char *keyfile_str, const char *full_
 	success = g_key_file_load_from_data (keyfile, keyfile_str, strlen (keyfile_str), G_KEY_FILE_NONE, &error);
 	nmtst_assert_success (success, error);
 
-	con = nm_keyfile_read (keyfile, base_dir, NULL, NULL, &error);
+	con = nm_keyfile_read (keyfile, base_dir, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error);
 	nmtst_assert_success (NM_IS_CONNECTION (con), error);
 
 	nm_keyfile_read_ensure_id (con, filename);
@@ -2326,6 +2326,43 @@ _nmtst_variant_new_vardict (int dummy, ...)
 		g_assert_cmpint (_l, ==, strlen (_str)); \
 	} G_STMT_END
 
+#ifdef __NM_SHARED_UTILS_H__
+#define _nmtst_assert_variant_bytestring_cmp_str(_ptr, _ptr2, _len) \
+	G_STMT_START { \
+		if (memcmp (_ptr2, _ptr, _len) != 0) { \
+			gs_free char *_x1 = NULL; \
+			gs_free char *_x2 = NULL; \
+			const char *_xx1; \
+			const char *_xx2; \
+			\
+			_xx1 = nm_utils_buf_utf8safe_escape (_ptr, _len, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL, &_x1); \
+			_xx2 = nm_utils_buf_utf8safe_escape (_ptr2, _len, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL, &_x2); \
+			g_assert_cmpstr (_xx1, ==, _xx2); \
+			g_assert_not_reached (); \
+		} \
+	} G_STMT_END
+#else
+#define _nmtst_assert_variant_bytestring_cmp_str(_ptr, _ptr2, _len) G_STMT_START { } G_STMT_END
+#endif
+
+#define nmtst_assert_variant_bytestring(variant, ptr, len) \
+	G_STMT_START { \
+		GVariant *_variant = (variant); \
+		gconstpointer _ptr = (ptr); \
+		gconstpointer _ptr2; \
+		gsize _len = (len); \
+		gsize _len2; \
+		\
+		nmtst_assert_variant_is_of_type (_variant, G_VARIANT_TYPE_BYTESTRING); \
+		_ptr2 = g_variant_get_fixed_array (_variant, &_len2, 1); \
+		g_assert_cmpint (_len2, ==, _len); \
+		if (   _len != 0 \
+		    && _ptr) { \
+			_nmtst_assert_variant_bytestring_cmp_str(_ptr, _ptr2, _len); \
+			g_assert_cmpmem (_ptr2, _len2, _ptr, _len); \
+		} \
+	} G_STMT_END
+
 typedef enum {
 	NMTST_VARIANT_EDITOR_CONNECTION,
 	NMTST_VARIANT_EDITOR_SETTING,
diff --git a/shared/nm-version-macros.h b/shared/nm-version-macros.h
index 4858a066..40bfaaa9 100644
--- a/shared/nm-version-macros.h
+++ b/shared/nm-version-macros.h
@@ -22,7 +22,7 @@
  * Evaluates to the minor version number of NetworkManager which this source
  * is compiled against.
  */
-#define NM_MINOR_VERSION (24)
+#define NM_MINOR_VERSION (25)
 
 /**
  * NM_MICRO_VERSION:
@@ -30,7 +30,7 @@
  * Evaluates to the micro version number of NetworkManager which this source
  * compiled against.
  */
-#define NM_MICRO_VERSION (2)
+#define NM_MICRO_VERSION (90)
 
 /**
  * NM_CHECK_VERSION:
@@ -64,6 +64,7 @@
 #define NM_VERSION_1_20   (NM_ENCODE_VERSION (1, 20, 0))
 #define NM_VERSION_1_22   (NM_ENCODE_VERSION (1, 22, 0))
 #define NM_VERSION_1_24   (NM_ENCODE_VERSION (1, 24, 0))
+#define NM_VERSION_1_26   (NM_ENCODE_VERSION (1, 26, 0))
 
 /* For releases, NM_API_VERSION is equal to NM_VERSION.
  *
diff --git a/shared/nm-version-macros.h.in b/shared/nm-version-macros.h.in
index e97f506c..83da86ce 100644
--- a/shared/nm-version-macros.h.in
+++ b/shared/nm-version-macros.h.in
@@ -64,6 +64,7 @@
 #define NM_VERSION_1_20   (NM_ENCODE_VERSION (1, 20, 0))
 #define NM_VERSION_1_22   (NM_ENCODE_VERSION (1, 22, 0))
 #define NM_VERSION_1_24   (NM_ENCODE_VERSION (1, 24, 0))
+#define NM_VERSION_1_26   (NM_ENCODE_VERSION (1, 26, 0))
 
 /* For releases, NM_API_VERSION is equal to NM_VERSION.
  *
diff --git a/shared/systemd/nm-sd-utils-shared.c b/shared/systemd/nm-sd-utils-shared.c
index 85fd6a35..4444e6c7 100644
--- a/shared/systemd/nm-sd-utils-shared.c
+++ b/shared/systemd/nm-sd-utils-shared.c
@@ -13,6 +13,7 @@
 #include "hexdecoct.h"
 #include "hostname-util.h"
 #include "path-util.h"
+#include "web-util.h"
 
 /*****************************************************************************/
 
@@ -94,3 +95,45 @@ gboolean nm_sd_hostname_is_valid (const char *s, bool allow_trailing_dot)
 {
 	return hostname_is_valid (s, allow_trailing_dot);
 }
+
+/*****************************************************************************/
+
+static gboolean
+_http_url_is_valid (const char *url, gboolean only_https)
+{
+	if (   !url
+	    || !url[0])
+		return FALSE;
+
+	if (   !only_https
+	    && NM_STR_HAS_PREFIX (url, "http://"))
+		url += NM_STRLEN ("http://");
+	else if (NM_STR_HAS_PREFIX (url, "https://"))
+		url += NM_STRLEN ("https://");
+	else
+		return FALSE;
+
+	if (!url[0])
+		return FALSE;
+
+	return !NM_STRCHAR_ANY (url, ch, (guchar) ch >= 128u);
+}
+
+gboolean
+nm_sd_http_url_is_valid_https (const char *url)
+{
+	/* We use this function to verify connection:mud-url property, it must thus
+	 * not change behavior.
+	 *
+	 * Note that sd_dhcp_client_set_mud_url() and sd_dhcp6_client_set_request_mud_url()
+	 * assert with http_url_is_valid() that the argument is valid. We thus must make
+	 * sure to only pass URLs that are valid according to http_url_is_valid().
+	 *
+	 * This is given, because our nm_sd_http_url_is_valid_https() is more strict
+	 * than http_url_is_valid().
+	 *
+	 * We only must make sure that this is also correct in the future, when we
+	 * re-import systemd code. */
+	nm_assert (_http_url_is_valid (url, FALSE) == http_url_is_valid (url));
+	return _http_url_is_valid (url, TRUE);
+}
diff --git a/shared/systemd/nm-sd-utils-shared.h b/shared/systemd/nm-sd-utils-shared.h
index 3495a31d..a3ca1edc 100644
--- a/shared/systemd/nm-sd-utils-shared.h
+++ b/shared/systemd/nm-sd-utils-shared.h
@@ -34,4 +34,8 @@ int nm_sd_dns_name_to_wire_format (const char *domain,
 int nm_sd_dns_name_is_valid (const char *s);
 gboolean nm_sd_hostname_is_valid(const char *s, bool allow_trailing_dot);
 
+/*****************************************************************************/
+
+gboolean nm_sd_http_url_is_valid_https (const char *url);
+
 #endif /* __NM_SD_UTILS_SHARED_H__ */
diff --git a/shared/systemd/sd-adapt-shared/blockdev-util.h b/shared/systemd/sd-adapt-shared/blockdev-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/blockdev-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/src/basic/cgroup-util.h b/shared/systemd/src/basic/cgroup-util.h
index 237139fa..2b88571b 100644
--- a/shared/systemd/src/basic/cgroup-util.h
+++ b/shared/systemd/src/basic/cgroup-util.h
@@ -180,9 +180,31 @@ int cg_pid_get_path(const char *controller, pid_t pid, char **path);
 
 int cg_rmdir(const char *controller, const char *path);
 
+typedef enum  {
+        CG_KEY_MODE_GRACEFUL = 1 << 0,
+} CGroupKeyMode;
+
 int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value);
 int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret);
-int cg_get_keyed_attribute(const char *controller, const char *path, const char *attribute, char **keys, char **values);
+int cg_get_keyed_attribute_full(const char *controller, const char *path, const char *attribute, char **keys, char **values, CGroupKeyMode mode);
+
+static inline int cg_get_keyed_attribute(
+                const char *controller,
+                const char *path,
+                const char *attribute,
+                char **keys,
+                char **ret_values) {
+        return cg_get_keyed_attribute_full(controller, path, attribute, keys, ret_values, 0);
+}
+
+static inline int cg_get_keyed_attribute_graceful(
+                const char *controller,
+                const char *path,
+                const char *attribute,
+                char **keys,
+                char **ret_values) {
+        return cg_get_keyed_attribute_full(controller, path, attribute, keys, ret_values, CG_KEY_MODE_GRACEFUL);
+}
 
 int cg_get_attribute_as_uint64(const char *controller, const char *path, const char *attribute, uint64_t *ret);
 
@@ -238,6 +260,7 @@ int cg_mask_to_string(CGroupMask mask, char **ret);
 int cg_kernel_controllers(Set **controllers);
 
 bool cg_ns_supported(void);
+bool cg_freezer_supported(void);
 
 int cg_all_unified(void);
 int cg_hybrid_unified(void);
diff --git a/shared/systemd/src/basic/errno-util.h b/shared/systemd/src/basic/errno-util.h
index 65a6384e..0ca650f4 100644
--- a/shared/systemd/src/basic/errno-util.h
+++ b/shared/systemd/src/basic/errno-util.h
@@ -87,12 +87,16 @@ static inline bool ERRNO_IS_RESOURCE(int r) {
                       ENOMEM);
 }
 
-/* Three different errors for "operation/system call/ioctl not supported" */
+/* Seven different errors for "operation/system call/ioctl/socket feature not supported" */
 static inline bool ERRNO_IS_NOT_SUPPORTED(int r) {
         return IN_SET(abs(r),
                       EOPNOTSUPP,
                       ENOTTY,
-                      ENOSYS);
+                      ENOSYS,
+                      EAFNOSUPPORT,
+                      EPFNOSUPPORT,
+                      EPROTONOSUPPORT,
+                      ESOCKTNOSUPPORT);
 }
 
 /* Two different errors for access problems */
diff --git a/shared/systemd/src/basic/escape.c b/shared/systemd/src/basic/escape.c
index 2cc5be18..1ea0b6a6 100644
--- a/shared/systemd/src/basic/escape.c
+++ b/shared/systemd/src/basic/escape.c
@@ -522,22 +522,28 @@ char* shell_maybe_quote(const char *s, EscapeStyle style) {
                 return NULL;
 
         t = r;
-        if (style == ESCAPE_BACKSLASH)
+        switch (style) {
+        case ESCAPE_BACKSLASH:
+        case ESCAPE_BACKSLASH_ONELINE:
                 *(t++) = '"';
-        else if (style == ESCAPE_POSIX) {
+                break;
+        case ESCAPE_POSIX:
                 *(t++) = '$';
                 *(t++) = '\'';
-        } else
+                break;
+        default:
                 assert_not_reached("Bad EscapeStyle");
+        }
 
         t = mempcpy(t, s, p - s);
 
-        if (style == ESCAPE_BACKSLASH)
-                t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE, false);
+        if (IN_SET(style, ESCAPE_BACKSLASH, ESCAPE_BACKSLASH_ONELINE))
+                t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE,
+                                             style == ESCAPE_BACKSLASH_ONELINE);
         else
                 t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE_POSIX, true);
 
-        if (style == ESCAPE_BACKSLASH)
+        if (IN_SET(style, ESCAPE_BACKSLASH, ESCAPE_BACKSLASH_ONELINE))
                 *(t++) = '"';
         else
                 *(t++) = '\'';
diff --git a/shared/systemd/src/basic/escape.h b/shared/systemd/src/basic/escape.h
index b8eb137c..0b00b116 100644
--- a/shared/systemd/src/basic/escape.h
+++ b/shared/systemd/src/basic/escape.h
@@ -34,8 +34,13 @@ typedef enum UnescapeFlags {
 } UnescapeFlags;
 
 typedef enum EscapeStyle {
-        ESCAPE_BACKSLASH = 1,
-        ESCAPE_POSIX     = 2,
+        ESCAPE_BACKSLASH         = 1,  /* Add shell quotes ("") so the shell will consider this a single
+                                          argument, possibly multiline. Tabs and newlines are not escaped. */
+        ESCAPE_BACKSLASH_ONELINE = 2,  /* Similar to ESCAPE_BACKSLASH, but always produces a single-line
+                                          string instead. Shell escape sequences are produced for tabs and
+                                          newlines. */
+        ESCAPE_POSIX             = 3,  /* Similar to ESCAPE_BACKSLASH_ONELINE, but uses POSIX shell escape
+                                        * syntax (a string enclosed in $'') instead of plain quotes. */
 } EscapeStyle;
 
 char *cescape(const char *s);
diff --git a/shared/systemd/src/basic/fd-util.c b/shared/systemd/src/basic/fd-util.c
index ea2c1612..a401663e 100644
--- a/shared/systemd/src/basic/fd-util.c
+++ b/shared/systemd/src/basic/fd-util.c
@@ -23,9 +23,10 @@
 #include "path-util.h"
 #include "process-util.h"
 #include "socket-util.h"
+#include "stat-util.h"
 #include "stdio-util.h"
-#include "util.h"
 #include "tmpfile-util.h"
+#include "util.h"
 
 /* The maximum number of iterations in the loop to close descriptors in the fallback case
  * when /proc/self/fd/ is inaccessible. */
@@ -104,13 +105,16 @@ int fclose_nointr(FILE *f) {
 
         /* Same as close_nointr(), but for fclose() */
 
+        errno = 0; /* Extra safety: if the FILE* object is not encapsulating an fd, it might not set errno
+                    * correctly. Let's hence initialize it to zero first, so that we aren't confused by any
+                    * prior errno here */
         if (fclose(f) == 0)
                 return 0;
 
         if (errno == EINTR)
                 return 0;
 
-        return -errno;
+        return errno_or_else(EIO);
 }
 
 FILE* safe_fclose(FILE *f) {
@@ -146,11 +150,7 @@ int fd_nonblock(int fd, bool nonblock) {
         if (flags < 0)
                 return -errno;
 
-        if (nonblock)
-                nflags = flags | O_NONBLOCK;
-        else
-                nflags = flags & ~O_NONBLOCK;
-
+        nflags = UPDATE_FLAG(flags, O_NONBLOCK, nonblock);
         if (nflags == flags)
                 return 0;
 
@@ -169,11 +169,7 @@ int fd_cloexec(int fd, bool cloexec) {
         if (flags < 0)
                 return -errno;
 
-        if (cloexec)
-                nflags = flags | FD_CLOEXEC;
-        else
-                nflags = flags & ~FD_CLOEXEC;
-
+        nflags = UPDATE_FLAG(flags, FD_CLOEXEC, cloexec);
         if (nflags == flags)
                 return 0;
 
@@ -954,8 +950,15 @@ int fd_reopen(int fd, int flags) {
 
         xsprintf(procfs_path, "/proc/self/fd/%i", fd);
         new_fd = open(procfs_path, flags);
-        if (new_fd < 0)
-                return -errno;
+        if (new_fd < 0) {
+                if (errno != ENOENT)
+                        return -errno;
+
+                if (proc_mounted() == 0)
+                        return -ENOSYS; /* if we have no /proc/, the concept is not implementable */
+
+                return -ENOENT;
+        }
 
         return new_fd;
 }
diff --git a/shared/systemd/src/basic/fileio.c b/shared/systemd/src/basic/fileio.c
index 543dd4eb..30903263 100644
--- a/shared/systemd/src/basic/fileio.c
+++ b/shared/systemd/src/basic/fileio.c
@@ -122,7 +122,7 @@ int write_string_stream_ts(
                 struct timespec *ts) {
 
         bool needs_nl;
-        int r;
+        int r, fd;
 
         assert(f);
         assert(line);
@@ -130,6 +130,14 @@ int write_string_stream_ts(
         if (ferror(f))
                 return -EIO;
 
+        if (ts) {
+                /* If we shall set the timestamp we need the fd. But fmemopen() streams generally don't have
+                 * an fd. Let's fail early in that case. */
+                fd = fileno(f);
+                if (fd < 0)
+                        return -EBADF;
+        }
+
         needs_nl = !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) && !endswith(line, "\n");
 
         if (needs_nl && (flags & WRITE_STRING_FILE_DISABLE_BUFFER)) {
@@ -157,7 +165,7 @@ int write_string_stream_ts(
         if (ts) {
                 struct timespec twice[2] = {*ts, *ts};
 
-                if (futimens(fileno(f), twice) < 0)
+                if (futimens(fd, twice) < 0)
                         return -errno;
         }
 
@@ -197,6 +205,13 @@ static int write_string_file_atomic(
                 goto fail;
         }
 
+        if (FLAGS_SET(flags, WRITE_STRING_FILE_SYNC)) {
+                /* Sync the rename, too */
+                r = fsync_directory_of_file(fileno(f));
+                if (r < 0)
+                        return r;
+        }
+
         return 0;
 
 fail:
@@ -415,7 +430,7 @@ int read_full_virtual_file(const char *filename, char **ret_contents, size_t *re
                                 break;
                         }
 
-                        if (errno != -EINTR)
+                        if (errno != EINTR)
                                 return -errno;
                 }
 
@@ -895,7 +910,7 @@ int fflush_and_check(FILE *f) {
 
 #if 0 /* NM_IGNORED */
 int fflush_sync_and_check(FILE *f) {
-        int r;
+        int r, fd;
 
         assert(f);
 
@@ -903,10 +918,16 @@ int fflush_sync_and_check(FILE *f) {
         if (r < 0)
                 return r;
 
-        if (fsync(fileno(f)) < 0)
+        /* Not all file streams have an fd associated (think: fmemopen()), let's handle this gracefully and
+         * assume that in that case we need no explicit syncing */
+        fd = fileno(f);
+        if (fd < 0)
+                return 0;
+
+        if (fsync(fd) < 0)
                 return -errno;
 
-        r = fsync_directory_of_file(fileno(f));
+        r = fsync_directory_of_file(fd);
         if (r < 0)
                 return r;
 
@@ -1006,7 +1027,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, funlockfile);
 int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) {
         size_t n = 0, allocated = 0, count = 0;
         _cleanup_free_ char *buffer = NULL;
-        int r, tty = -1;
+        int r;
 
         assert(f);
 
@@ -1081,13 +1102,23 @@ int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) {
                         count++;
 
                         if (eol != EOL_NONE) {
-                                /* If we are on a tty, we can't wait for more input. But we expect only
-                                 * \n as the single EOL marker, so there is no need to wait. We check
-                                 * this condition last to avoid isatty() check if not necessary. */
-
-                                if (tty < 0)
-                                        tty = isatty(fileno(f));
-                                if (tty > 0)
+                                /* If we are on a tty, we can't shouldn't wait for more input, because that
+                                 * generally means waiting for the user, interactively. In the case of a TTY
+                                 * we expect only \n as the single EOL marker, so we are in the lucky
+                                 * position that there is no need to wait. We check this condition last, to
+                                 * avoid isatty() check if not necessary. */
+
+                                if ((flags & (READ_LINE_IS_A_TTY|READ_LINE_NOT_A_TTY)) == 0) {
+                                        int fd;
+
+                                        fd = fileno(f);
+                                        if (fd < 0) /* Maybe an fmemopen() stream? Handle this gracefully,
+                                                     * and don't call isatty() on an invalid fd */
+                                                flags |= READ_LINE_NOT_A_TTY;
+                                        else
+                                                flags |= isatty(fd) ? READ_LINE_IS_A_TTY : READ_LINE_NOT_A_TTY;
+                                }
+                                if (FLAGS_SET(flags, READ_LINE_IS_A_TTY))
                                         break;
                         }
 
@@ -1168,3 +1199,27 @@ int warn_file_is_world_accessible(const char *filename, struct stat *st, const c
                             filename, st->st_mode & 07777);
         return 0;
 }
+
+#if 0 /* NM_IGNORED */
+int sync_rights(int from, int to) {
+        struct stat st;
+
+        if (fstat(from, &st) < 0)
+                return -errno;
+
+        return fchmod_and_chown(to, st.st_mode & 07777, st.st_uid, st.st_gid);
+}
+
+int rename_and_apply_smack_floor_label(const char *from, const char *to) {
+        int r = 0;
+        if (rename(from, to) < 0)
+                return -errno;
+
+#ifdef SMACK_RUN_LABEL
+        r = mac_smack_apply(to, SMACK_ATTR_ACCESS, SMACK_FLOOR_LABEL);
+        if (r < 0)
+                return r;
+#endif
+        return r;
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/fileio.h b/shared/systemd/src/basic/fileio.h
index 58daabaa..e2830b79 100644
--- a/shared/systemd/src/basic/fileio.h
+++ b/shared/systemd/src/basic/fileio.h
@@ -88,7 +88,9 @@ int read_timestamp_file(const char *fn, usec_t *ret);
 int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space);
 
 typedef enum ReadLineFlags {
-        READ_LINE_ONLY_NUL = 1 << 0,
+        READ_LINE_ONLY_NUL  = 1 << 0,
+        READ_LINE_IS_A_TTY  = 1 << 1,
+        READ_LINE_NOT_A_TTY = 1 << 2,
 } ReadLineFlags;
 
 int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret);
@@ -104,3 +106,7 @@ static inline int read_nul_string(FILE *f, size_t limit, char **ret) {
 int safe_fgetc(FILE *f, char *ret);
 
 int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line);
+
+int sync_rights(int from, int to);
+
+int rename_and_apply_smack_floor_label(const char *temp_path, const char *dest_path);
diff --git a/shared/systemd/src/basic/fs-util.c b/shared/systemd/src/basic/fs-util.c
index 79966571..75df913a 100644
--- a/shared/systemd/src/basic/fs-util.c
+++ b/shared/systemd/src/basic/fs-util.c
@@ -10,8 +10,10 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
+#include "blockdev-util.h"
 #include "dirent-util.h"
 #include "fd-util.h"
+#include "fileio.h"
 #include "fs-util.h"
 #include "locale-util.h"
 #include "log.h"
@@ -23,6 +25,7 @@
 #include "parse-util.h"
 #include "path-util.h"
 #include "process-util.h"
+#include "random-util.h"
 #include "stat-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
@@ -342,34 +345,51 @@ int fchmod_opath(int fd, mode_t m) {
          * fchownat() does. */
 
         xsprintf(procfs_path, "/proc/self/fd/%i", fd);
-        if (chmod(procfs_path, m) < 0)
-                return -errno;
+        if (chmod(procfs_path, m) < 0) {
+                if (errno != ENOENT)
+                        return -errno;
+
+                if (proc_mounted() == 0)
+                        return -ENOSYS; /* if we have no /proc/, the concept is not implementable */
+
+                return -ENOENT;
+        }
 
         return 0;
 }
 
-int fd_warn_permissions(const char *path, int fd) {
-        struct stat st;
-
-        if (fstat(fd, &st) < 0)
-                return -errno;
+int stat_warn_permissions(const char *path, const struct stat *st) {
+        assert(path);
+        assert(st);
 
         /* Don't complain if we are reading something that is not a file, for example /dev/null */
-        if (!S_ISREG(st.st_mode))
+        if (!S_ISREG(st->st_mode))
                 return 0;
 
-        if (st.st_mode & 0111)
+        if (st->st_mode & 0111)
                 log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path);
 
-        if (st.st_mode & 0002)
+        if (st->st_mode & 0002)
                 log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path);
 
-        if (getpid_cached() == 1 && (st.st_mode & 0044) != 0044)
+        if (getpid_cached() == 1 && (st->st_mode & 0044) != 0044)
                 log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path);
 
         return 0;
 }
 
+int fd_warn_permissions(const char *path, int fd) {
+        struct stat st;
+
+        assert(path);
+        assert(fd >= 0);
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        return stat_warn_permissions(path, &st);
+}
+
 int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
         char fdpath[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
         _cleanup_close_ int fd = -1;
@@ -702,29 +722,31 @@ int unlink_or_warn(const char *filename) {
 
 int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
         char path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
-        int r;
+        int wd;
 
         /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */
         xsprintf(path, "/proc/self/fd/%i", what);
 
-        r = inotify_add_watch(fd, path, mask);
-        if (r < 0)
+        wd = inotify_add_watch(fd, path, mask);
+        if (wd < 0)
                 return -errno;
 
-        return r;
+        return wd;
 }
 
 #if 0 /* NM_IGNORED */
 int inotify_add_watch_and_warn(int fd, const char *pathname, uint32_t mask) {
+        int wd;
 
-        if (inotify_add_watch(fd, pathname, mask) < 0) {
+        wd = inotify_add_watch(fd, pathname, mask);
+        if (wd < 0) {
                 if (errno == ENOSPC)
                         return log_error_errno(errno, "Failed to add a watch for %s: inotify watch limit reached", pathname);
 
                 return log_error_errno(errno, "Failed to add a watch for %s: %m", pathname);
         }
 
-        return 0;
+        return wd;
 }
 
 static bool unsafe_transition(const struct stat *a, const struct stat *b) {
@@ -1303,11 +1325,13 @@ void unlink_tempfilep(char (*p)[]) {
                 (void) unlink_noerrno(*p);
 }
 
-int unlinkat_deallocate(int fd, const char *name, int flags) {
+int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
         _cleanup_close_ int truncate_fd = -1;
         struct stat st;
         off_t l, bs;
 
+        assert((flags & ~(UNLINK_REMOVEDIR|UNLINK_ERASE)) == 0);
+
         /* Operates like unlinkat() but also deallocates the file contents if it is a regular file and there's no other
          * link to it. This is useful to ensure that other processes that might have the file open for reading won't be
          * able to keep the data pinned on disk forever. This call is particular useful whenever we execute clean-up
@@ -1324,7 +1348,7 @@ int unlinkat_deallocate(int fd, const char *name, int flags) {
          * Note that we attempt deallocation, but failure to succeed with that is not considered fatal, as long as the
          * primary job – to delete the file – is accomplished. */
 
-        if ((flags & AT_REMOVEDIR) == 0) {
+        if (!FLAGS_SET(flags, UNLINK_REMOVEDIR)) {
                 truncate_fd = openat(fd, name, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
                 if (truncate_fd < 0) {
 
@@ -1340,7 +1364,7 @@ int unlinkat_deallocate(int fd, const char *name, int flags) {
                 }
         }
 
-        if (unlinkat(fd, name, flags) < 0)
+        if (unlinkat(fd, name, FLAGS_SET(flags, UNLINK_REMOVEDIR) ? AT_REMOVEDIR : 0) < 0)
                 return -errno;
 
         if (truncate_fd < 0) /* Don't have a file handle, can't do more ☹️ */
@@ -1351,7 +1375,45 @@ int unlinkat_deallocate(int fd, const char *name, int flags) {
                 return 0;
         }
 
-        if (!S_ISREG(st.st_mode) || st.st_blocks == 0 || st.st_nlink > 0)
+        if (!S_ISREG(st.st_mode))
+                return 0;
+
+        if (FLAGS_SET(flags, UNLINK_ERASE) && st.st_size > 0 && st.st_nlink == 0) {
+                uint64_t left = st.st_size;
+                char buffer[64 * 1024];
+
+                /* If erasing is requested, let's overwrite the file with random data once before deleting
+                 * it. This isn't going to give you shred(1) semantics, but hopefully should be good enough
+                 * for stuff backed by tmpfs at least.
+                 *
+                 * Note that we only erase like this if the link count of the file is zero. If it is higher it
+                 * is still linked by someone else and we'll leave it to them to remove it securely
+                 * eventually! */
+
+                random_bytes(buffer, sizeof(buffer));
+
+                while (left > 0) {
+                        ssize_t n;
+
+                        n = write(truncate_fd, buffer, MIN(sizeof(buffer), left));
+                        if (n < 0) {
+                                log_debug_errno(errno, "Failed to erase data in file '%s', ignoring.", name);
+                                break;
+                        }
+
+                        assert(left >= (size_t) n);
+                        left -= n;
+                }
+
+                /* Let's refresh metadata */
+                if (fstat(truncate_fd, &st) < 0) {
+                        log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name);
+                        return 0;
+                }
+        }
+
+        /* Don't dallocate if there's nothing to deallocate or if the file is linked elsewhere */
+        if (st.st_blocks == 0 || st.st_nlink > 0)
                 return 0;
 
         /* If this is a regular file, it actually took up space on disk and there are no other links it's time to
@@ -1490,4 +1552,89 @@ int open_parent(const char *path, int flags, mode_t mode) {
 
         return fd;
 }
+
+static int blockdev_is_encrypted(const char *sysfs_path, unsigned depth_left) {
+        _cleanup_free_ char *p = NULL, *uuids = NULL;
+        _cleanup_closedir_ DIR *d = NULL;
+        int r, found_encrypted = false;
+
+        assert(sysfs_path);
+
+        if (depth_left == 0)
+                return -EINVAL;
+
+        p = path_join(sysfs_path, "dm/uuid");
+        if (!p)
+                return -ENOMEM;
+
+        r = read_one_line_file(p, &uuids);
+        if (r != -ENOENT) {
+                if (r < 0)
+                        return r;
+
+                /* The DM device's uuid attribute is prefixed with "CRYPT-" if this is a dm-crypt device. */
+                if (startswith(uuids, "CRYPT-"))
+                        return true;
+        }
+
+        /* Not a dm-crypt device itself. But maybe it is on top of one? Follow the links in the "slaves/"
+         * subdir. */
+
+        p = mfree(p);
+        p = path_join(sysfs_path, "slaves");
+        if (!p)
+                return -ENOMEM;
+
+        d = opendir(p);
+        if (!d) {
+                if (errno == ENOENT) /* Doesn't have slaves */
+                        return false;
+
+                return -errno;
+        }
+
+        for (;;) {
+                _cleanup_free_ char *q = NULL;
+                struct dirent *de;
+
+                errno = 0;
+                de = readdir_no_dot(d);
+                if (!de) {
+                        if (errno != 0)
+                                return -errno;
+
+                        break; /* No more slaves */
+                }
+
+                q = path_join(p, de->d_name);
+                if (!q)
+                        return -ENOMEM;
+
+                r = blockdev_is_encrypted(q, depth_left - 1);
+                if (r < 0)
+                        return r;
+                if (r == 0) /* we found one that is not encrypted? then propagate that immediately */
+                        return false;
+
+                found_encrypted = true;
+        }
+
+        return found_encrypted;
+}
+
+int path_is_encrypted(const char *path) {
+        char p[SYS_BLOCK_PATH_MAX(NULL)];
+        dev_t devt;
+        int r;
+
+        r = get_block_device(path, &devt);
+        if (r < 0)
+                return r;
+        if (r == 0) /* doesn't have a block device */
+                return false;
+
+        xsprintf_sys_block_path(p, NULL, devt);
+
+        return blockdev_is_encrypted(p, 10 /* safety net: maximum recursion depth */);
+}
 #endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/fs-util.h b/shared/systemd/src/basic/fs-util.h
index 6b9ade2e..b184570f 100644
--- a/shared/systemd/src/basic/fs-util.h
+++ b/shared/systemd/src/basic/fs-util.h
@@ -40,6 +40,7 @@ int fchmod_umask(int fd, mode_t mode);
 int fchmod_opath(int fd, mode_t m);
 
 int fd_warn_permissions(const char *path, int fd);
+int stat_warn_permissions(const char *path, const struct stat *st);
 
 #define laccess(path, mode) faccessat(AT_FDCWD, (path), (mode), AT_SYMLINK_NOFOLLOW)
 
@@ -82,7 +83,7 @@ enum {
         CHASE_SAFE        = 1 << 3, /* Return EPERM if we ever traverse from unprivileged to privileged files or directories */
         CHASE_TRAIL_SLASH = 1 << 4, /* Any trailing slash will be preserved */
         CHASE_STEP        = 1 << 5, /* Just execute a single step of the normalization */
-        CHASE_NOFOLLOW    = 1 << 6, /* Do not follow the path's right-most compontent. With ret_fd, when the path's
+        CHASE_NOFOLLOW    = 1 << 6, /* Do not follow the path's right-most component. With ret_fd, when the path's
                                      * right-most component refers to symlink, return O_PATH fd of the symlink. */
         CHASE_WARN        = 1 << 7, /* Emit an appropriate warning when an error is encountered */
 };
@@ -113,7 +114,13 @@ 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);
+
+typedef enum UnlinkDeallocateFlags {
+        UNLINK_REMOVEDIR = 1 << 0,
+        UNLINK_ERASE     = 1 << 1,
+} UnlinkDeallocateFlags;
+
+int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags);
 
 int fsync_directory_of_file(int fd);
 int fsync_full(int fd);
@@ -122,3 +129,5 @@ int fsync_path_at(int at_fd, const char *path);
 int syncfs_path(int atfd, const char *path);
 
 int open_parent(const char *path, int flags, mode_t mode);
+
+int path_is_encrypted(const char *path);
diff --git a/shared/systemd/src/basic/hash-funcs.c b/shared/systemd/src/basic/hash-funcs.c
index 1b0d1292..b1c19c95 100644
--- a/shared/systemd/src/basic/hash-funcs.c
+++ b/shared/systemd/src/basic/hash-funcs.c
@@ -11,12 +11,14 @@ void string_hash_func(const char *p, struct siphash *state) {
         siphash24_compress(p, strlen(p) + 1, state);
 }
 
-#if 0 /* NM_IGNORED */
 DEFINE_HASH_OPS(string_hash_ops, char, string_hash_func, string_compare_func);
+DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(string_hash_ops_free,
+                                    char, string_hash_func, string_compare_func, free);
 DEFINE_HASH_OPS_FULL(string_hash_ops_free_free,
                      char, string_hash_func, string_compare_func, free,
                      char, free);
 
+#if 0 /* NM_IGNORED */
 void path_hash_func(const char *q, struct siphash *state) {
         size_t n;
 
@@ -56,6 +58,8 @@ void path_hash_func(const char *q, struct siphash *state) {
 }
 
 DEFINE_HASH_OPS(path_hash_ops, char, path_hash_func, path_compare);
+DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(path_hash_ops_free,
+                                    char, path_hash_func, path_compare, free);
 #endif /* NM_IGNORED */
 
 void trivial_hash_func(const void *p, struct siphash *state) {
diff --git a/shared/systemd/src/basic/hash-funcs.h b/shared/systemd/src/basic/hash-funcs.h
index 7bb5d1cd..005d1b21 100644
--- a/shared/systemd/src/basic/hash-funcs.h
+++ b/shared/systemd/src/basic/hash-funcs.h
@@ -76,10 +76,12 @@ struct hash_ops {
 void string_hash_func(const char *p, struct siphash *state);
 #define string_compare_func strcmp
 extern const struct hash_ops string_hash_ops;
+extern const struct hash_ops string_hash_ops_free;
 extern const struct hash_ops string_hash_ops_free_free;
 
 void path_hash_func(const char *p, struct siphash *state);
 extern const struct hash_ops path_hash_ops;
+extern const struct hash_ops path_hash_ops_free;
 
 /* This will compare the passed pointers directly, and will not dereference them. This is hence not useful for strings
  * or suchlike. */
diff --git a/shared/systemd/src/basic/hashmap.c b/shared/systemd/src/basic/hashmap.c
index 1aa00947..c0296bde 100644
--- a/shared/systemd/src/basic/hashmap.c
+++ b/shared/systemd/src/basic/hashmap.c
@@ -147,12 +147,7 @@ struct hashmap_debug_info {
 /* Tracks all existing hashmaps. Get at it from gdb. See sd_dump_hashmaps.py */
 static LIST_HEAD(struct hashmap_debug_info, hashmap_debug_list);
 static pthread_mutex_t hashmap_debug_list_mutex = PTHREAD_MUTEX_INITIALIZER;
-
-#define HASHMAP_DEBUG_FIELDS struct hashmap_debug_info debug;
-
-#else /* !ENABLE_DEBUG_HASHMAP */
-#define HASHMAP_DEBUG_FIELDS
-#endif /* ENABLE_DEBUG_HASHMAP */
+#endif
 
 enum HashmapType {
         HASHMAP_TYPE_PLAIN,
@@ -214,7 +209,10 @@ struct HashmapBase {
         bool from_pool:1;            /* whether was allocated from mempool */
         bool dirty:1;                /* whether dirtied since last iterated_cache_get() */
         bool cached:1;               /* whether this hashmap is being cached */
-        HASHMAP_DEBUG_FIELDS         /* optional hashmap_debug_info */
+
+#if ENABLE_DEBUG_HASHMAP
+        struct hashmap_debug_info debug;
+#endif
 };
 
 /* Specific hash types
@@ -256,7 +254,7 @@ struct hashmap_type_info {
         unsigned n_direct_buckets;
 };
 
-static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = {
+static _used_ const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = {
         [HASHMAP_TYPE_PLAIN] = {
                 .head_size        = sizeof(Hashmap),
                 .entry_size       = sizeof(struct plain_hashmap_entry),
@@ -709,7 +707,7 @@ static unsigned hashmap_iterate_entry(HashmapBase *h, Iterator *i) {
                                                : hashmap_iterate_in_internal_order(h, i);
 }
 
-bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key) {
+bool _hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key) {
         struct hashmap_base_entry *e;
         void *data;
         unsigned idx;
@@ -735,7 +733,7 @@ bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const v
 }
 
 bool set_iterate(const Set *s, Iterator *i, void **value) {
-        return internal_hashmap_iterate(HASHMAP_BASE((Set*) s), i, value, NULL);
+        return _hashmap_iterate(HASHMAP_BASE((Set*) s), i, value, NULL);
 }
 
 #define HASHMAP_FOREACH_IDX(idx, h, i) \
@@ -743,7 +741,7 @@ bool set_iterate(const Set *s, Iterator *i, void **value) {
              (idx != IDX_NIL); \
              (idx) = hashmap_iterate_entry((h), &(i)))
 
-IteratedCache *internal_hashmap_iterated_cache_new(HashmapBase *h) {
+IteratedCache *_hashmap_iterated_cache_new(HashmapBase *h) {
         IteratedCache *cache;
 
         assert(h);
@@ -811,15 +809,15 @@ static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enu
         return h;
 }
 
-Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
+Hashmap *_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
         return (Hashmap*)        hashmap_base_new(hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS);
 }
 
-OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
+OrderedHashmap *_ordered_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
         return (OrderedHashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS);
 }
 
-Set *internal_set_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
+Set *_set_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
         return (Set*)            hashmap_base_new(hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS);
 }
 
@@ -837,18 +835,18 @@ static int hashmap_base_ensure_allocated(HashmapBase **h, const struct hash_ops
                 return -ENOMEM;
 
         *h = q;
-        return 0;
+        return 1;
 }
 
-int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
+int _hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
         return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS);
 }
 
-int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
+int _ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
         return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS);
 }
 
-int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
+int _set_ensure_allocated(Set **s, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
         return hashmap_base_ensure_allocated((HashmapBase**)s, hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS);
 }
 
@@ -870,16 +868,16 @@ static void hashmap_free_no_clear(HashmapBase *h) {
                 free(h);
 }
 
-HashmapBase *internal_hashmap_free(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value) {
+HashmapBase *_hashmap_free(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value) {
         if (h) {
-                internal_hashmap_clear(h, default_free_key, default_free_value);
+                _hashmap_clear(h, default_free_key, default_free_value);
                 hashmap_free_no_clear(h);
         }
 
         return NULL;
 }
 
-void internal_hashmap_clear(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value) {
+void _hashmap_clear(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value) {
         free_func_t free_key, free_value;
         if (!h)
                 return;
@@ -893,11 +891,11 @@ void internal_hashmap_clear(HashmapBase *h, free_func_t default_free_key, free_f
                  * hash table, and only then call the destructor functions. If these destructors then try to unregister
                  * themselves from our hash table a second time, the entry is already gone. */
 
-                while (internal_hashmap_size(h) > 0) {
+                while (_hashmap_size(h) > 0) {
                         void *k = NULL;
                         void *v;
 
-                        v = internal_hashmap_first_key_and_value(h, true, &k);
+                        v = _hashmap_first_key_and_value(h, true, &k);
 
                         if (free_key)
                                 free_key(k);
@@ -1303,7 +1301,7 @@ int hashmap_update(Hashmap *h, const void *key, void *value) {
         return 0;
 }
 
-void *internal_hashmap_get(HashmapBase *h, const void *key) {
+void *_hashmap_get(HashmapBase *h, const void *key) {
         struct hashmap_base_entry *e;
         unsigned hash, idx;
 
@@ -1338,7 +1336,7 @@ void *hashmap_get2(Hashmap *h, const void *key, void **key2) {
         return e->value;
 }
 
-bool internal_hashmap_contains(HashmapBase *h, const void *key) {
+bool _hashmap_contains(HashmapBase *h, const void *key) {
         unsigned hash;
 
         if (!h)
@@ -1348,7 +1346,7 @@ bool internal_hashmap_contains(HashmapBase *h, const void *key) {
         return bucket_scan(h, hash, key) != IDX_NIL;
 }
 
-void *internal_hashmap_remove(HashmapBase *h, const void *key) {
+void *_hashmap_remove(HashmapBase *h, const void *key) {
         struct hashmap_base_entry *e;
         unsigned hash, idx;
         void *data;
@@ -1486,7 +1484,7 @@ int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_
         return 0;
 }
 
-void *internal_hashmap_remove_value(HashmapBase *h, const void *key, void *value) {
+void *_hashmap_remove_value(HashmapBase *h, const void *key, void *value) {
         struct hashmap_base_entry *e;
         unsigned hash, idx;
 
@@ -1516,7 +1514,7 @@ static unsigned find_first_entry(HashmapBase *h) {
         return hashmap_iterate_entry(h, &i);
 }
 
-void *internal_hashmap_first_key_and_value(HashmapBase *h, bool remove, void **ret_key) {
+void *_hashmap_first_key_and_value(HashmapBase *h, bool remove, void **ret_key) {
         struct hashmap_base_entry *e;
         void *key, *data;
         unsigned idx;
@@ -1541,21 +1539,21 @@ void *internal_hashmap_first_key_and_value(HashmapBase *h, bool remove, void **r
         return data;
 }
 
-unsigned internal_hashmap_size(HashmapBase *h) {
+unsigned _hashmap_size(HashmapBase *h) {
         if (!h)
                 return 0;
 
         return n_entries(h);
 }
 
-unsigned internal_hashmap_buckets(HashmapBase *h) {
+unsigned _hashmap_buckets(HashmapBase *h) {
         if (!h)
                 return 0;
 
         return n_buckets(h);
 }
 
-int internal_hashmap_merge(Hashmap *h, Hashmap *other) {
+int _hashmap_merge(Hashmap *h, Hashmap *other) {
         Iterator i;
         unsigned idx;
 
@@ -1591,7 +1589,7 @@ int set_merge(Set *s, Set *other) {
         return 0;
 }
 
-int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add) {
+int _hashmap_reserve(HashmapBase *h, unsigned entries_add) {
         int r;
 
         assert(h);
@@ -1609,7 +1607,7 @@ int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add) {
  * Returns: 0 on success.
  *          -ENOMEM on alloc failure, in which case no move has been done.
  */
-int internal_hashmap_move(HashmapBase *h, HashmapBase *other) {
+int _hashmap_move(HashmapBase *h, HashmapBase *other) {
         struct swap_entries swap;
         struct hashmap_base_entry *e, *n;
         Iterator i;
@@ -1654,7 +1652,7 @@ int internal_hashmap_move(HashmapBase *h, HashmapBase *other) {
         return 0;
 }
 
-int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key) {
+int _hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key) {
         struct swap_entries swap;
         unsigned h_hash, other_hash, idx;
         struct hashmap_base_entry *e, *n;
@@ -1691,7 +1689,7 @@ int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *ke
         return 0;
 }
 
-HashmapBase *internal_hashmap_copy(HashmapBase *h) {
+HashmapBase *_hashmap_copy(HashmapBase *h) {
         HashmapBase *copy;
         int r;
 
@@ -1714,14 +1712,14 @@ HashmapBase *internal_hashmap_copy(HashmapBase *h) {
         }
 
         if (r < 0) {
-                internal_hashmap_free(copy, false, false);
+                _hashmap_free(copy, false, false);
                 return NULL;
         }
 
         return copy;
 }
 
-char **internal_hashmap_get_strv(HashmapBase *h) {
+char **_hashmap_get_strv(HashmapBase *h) {
         char **sv;
         Iterator i;
         unsigned idx, n;
@@ -1778,42 +1776,55 @@ int hashmap_put_strdup(Hashmap **h, const char *k, const char *v) {
                 return r;
 
         _cleanup_free_ char *kdup = NULL, *vdup = NULL;
+
         kdup = strdup(k);
-        vdup = strdup(v);
-        if (!kdup || !vdup)
+        if (!kdup)
                 return -ENOMEM;
 
+        if (v) {
+                vdup = strdup(v);
+                if (!vdup)
+                        return -ENOMEM;
+        }
+
         r = hashmap_put(*h, kdup, vdup);
         if (r < 0) {
-                if (r == -EEXIST && streq(v, hashmap_get(*h, kdup)))
+                if (r == -EEXIST && streq_ptr(v, hashmap_get(*h, kdup)))
                         return 0;
                 return r;
         }
 
-        assert(r > 0); /* 0 would mean vdup is already in the hashmap, which cannot be */
-        kdup = vdup = NULL;
+        /* 0 with non-null vdup would mean vdup is already in the hashmap, which cannot be */
+        assert(vdup == NULL || r > 0);
+        if (r > 0)
+                kdup = vdup = NULL;
 
-        return 0;
+        return r;
 }
 #endif /* NM_IGNORED */
 
-int set_put_strdup(Set *s, const char *p) {
+int set_put_strdup(Set **s, const char *p) {
         char *c;
+        int r;
 
         assert(s);
         assert(p);
 
-        if (set_contains(s, (char*) p))
+        r = set_ensure_allocated(s, &string_hash_ops_free);
+        if (r < 0)
+                return r;
+
+        if (set_contains(*s, (char*) p))
                 return 0;
 
         c = strdup(p);
         if (!c)
                 return -ENOMEM;
 
-        return set_consume(s, c);
+        return set_consume(*s, c);
 }
 
-int set_put_strdupv(Set *s, char **l) {
+int set_put_strdupv(Set **s, char **l) {
         int n = 0, r;
         char **i;
 
diff --git a/shared/systemd/src/basic/hashmap.h b/shared/systemd/src/basic/hashmap.h
index 65adc925..230d3222 100644
--- a/shared/systemd/src/basic/hashmap.h
+++ b/shared/systemd/src/basic/hashmap.h
@@ -14,7 +14,7 @@
  * will be treated as empty hashmap for all read operations. That way it is not
  * necessary to instantiate an object for each Hashmap use.
  *
- * If ENABLE_DEBUG_HASHMAP is defined (by configuring with --enable-debug=hashmap),
+ * If ENABLE_DEBUG_HASHMAP is defined (by configuring with -Ddebug-extra=hashmap),
  * the implementation will:
  * - store extra data for debugging and statistics (see tools/gdb-sd_dump_hashmaps.py)
  * - perform extra checks for invalid use of iterators
@@ -24,10 +24,9 @@
 
 typedef void* (*hashmap_destroy_t)(void *p);
 
-/* The base type for all hashmap and set types. Many functions in the
- * implementation take (HashmapBase*) parameters and are run-time polymorphic,
- * though the API is not meant to be polymorphic (do not call functions
- * internal_*() directly). */
+/* The base type for all hashmap and set types. Many functions in the implementation take (HashmapBase*)
+ * parameters and are run-time polymorphic, though the API is not meant to be polymorphic (do not call
+ * underscore-prefixed functions directly). */
 typedef struct HashmapBase HashmapBase;
 
 /* Specific hashmap/set types */
@@ -84,62 +83,70 @@ typedef struct {
 # define HASHMAP_DEBUG_PASS_ARGS
 #endif
 
-Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
-OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
-#define hashmap_new(ops) internal_hashmap_new(ops  HASHMAP_DEBUG_SRC_ARGS)
-#define ordered_hashmap_new(ops) internal_ordered_hashmap_new(ops  HASHMAP_DEBUG_SRC_ARGS)
+Hashmap *_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
+OrderedHashmap *_ordered_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
+#define hashmap_new(ops) _hashmap_new(ops  HASHMAP_DEBUG_SRC_ARGS)
+#define ordered_hashmap_new(ops) _ordered_hashmap_new(ops  HASHMAP_DEBUG_SRC_ARGS)
+
+#define hashmap_free_and_replace(a, b)          \
+        ({                                      \
+                hashmap_free(a);                \
+                (a) = (b);                      \
+                (b) = NULL;                     \
+                0;                              \
+        })
 
-HashmapBase *internal_hashmap_free(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value);
+HashmapBase *_hashmap_free(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value);
 static inline Hashmap *hashmap_free(Hashmap *h) {
-        return (void*) internal_hashmap_free(HASHMAP_BASE(h), NULL, NULL);
+        return (void*) _hashmap_free(HASHMAP_BASE(h), NULL, NULL);
 }
 static inline OrderedHashmap *ordered_hashmap_free(OrderedHashmap *h) {
-        return (void*) internal_hashmap_free(HASHMAP_BASE(h), NULL, NULL);
+        return (void*) _hashmap_free(HASHMAP_BASE(h), NULL, NULL);
 }
 
 static inline Hashmap *hashmap_free_free(Hashmap *h) {
-        return (void*) internal_hashmap_free(HASHMAP_BASE(h), NULL, free);
+        return (void*) _hashmap_free(HASHMAP_BASE(h), NULL, free);
 }
 static inline OrderedHashmap *ordered_hashmap_free_free(OrderedHashmap *h) {
-        return (void*) internal_hashmap_free(HASHMAP_BASE(h), NULL, free);
+        return (void*) _hashmap_free(HASHMAP_BASE(h), NULL, free);
 }
 
 static inline Hashmap *hashmap_free_free_key(Hashmap *h) {
-        return (void*) internal_hashmap_free(HASHMAP_BASE(h), free, NULL);
+        return (void*) _hashmap_free(HASHMAP_BASE(h), free, NULL);
 }
 static inline OrderedHashmap *ordered_hashmap_free_free_key(OrderedHashmap *h) {
-        return (void*) internal_hashmap_free(HASHMAP_BASE(h), free, NULL);
+        return (void*) _hashmap_free(HASHMAP_BASE(h), free, NULL);
 }
 
 static inline Hashmap *hashmap_free_free_free(Hashmap *h) {
-        return (void*) internal_hashmap_free(HASHMAP_BASE(h), free, free);
+        return (void*) _hashmap_free(HASHMAP_BASE(h), free, free);
 }
 static inline OrderedHashmap *ordered_hashmap_free_free_free(OrderedHashmap *h) {
-        return (void*) internal_hashmap_free(HASHMAP_BASE(h), free, free);
+        return (void*) _hashmap_free(HASHMAP_BASE(h), free, free);
 }
 
 IteratedCache *iterated_cache_free(IteratedCache *cache);
 int iterated_cache_get(IteratedCache *cache, const void ***res_keys, const void ***res_values, unsigned *res_n_entries);
 
-HashmapBase *internal_hashmap_copy(HashmapBase *h);
+HashmapBase *_hashmap_copy(HashmapBase *h);
 static inline Hashmap *hashmap_copy(Hashmap *h) {
-        return (Hashmap*) internal_hashmap_copy(HASHMAP_BASE(h));
+        return (Hashmap*) _hashmap_copy(HASHMAP_BASE(h));
 }
 static inline OrderedHashmap *ordered_hashmap_copy(OrderedHashmap *h) {
-        return (OrderedHashmap*) internal_hashmap_copy(HASHMAP_BASE(h));
+        return (OrderedHashmap*) _hashmap_copy(HASHMAP_BASE(h));
 }
 
-int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
-int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
-#define hashmap_ensure_allocated(h, ops) internal_hashmap_ensure_allocated(h, ops  HASHMAP_DEBUG_SRC_ARGS)
-#define ordered_hashmap_ensure_allocated(h, ops) internal_ordered_hashmap_ensure_allocated(h, ops  HASHMAP_DEBUG_SRC_ARGS)
+int _hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
+int _ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
+#define hashmap_ensure_allocated(h, ops) _hashmap_ensure_allocated(h, ops  HASHMAP_DEBUG_SRC_ARGS)
+#define ordered_hashmap_ensure_allocated(h, ops) _ordered_hashmap_ensure_allocated(h, ops  HASHMAP_DEBUG_SRC_ARGS)
 
-IteratedCache *internal_hashmap_iterated_cache_new(HashmapBase *h);
+IteratedCache *_hashmap_iterated_cache_new(HashmapBase *h);
 static inline IteratedCache *hashmap_iterated_cache_new(Hashmap *h) {
-        return (IteratedCache*) internal_hashmap_iterated_cache_new(HASHMAP_BASE(h));
+        return (IteratedCache*) _hashmap_iterated_cache_new(HASHMAP_BASE(h));
 }
 static inline IteratedCache *ordered_hashmap_iterated_cache_new(OrderedHashmap *h) {
-        return (IteratedCache*) internal_hashmap_iterated_cache_new(HASHMAP_BASE(h));
+        return (IteratedCache*) _hashmap_iterated_cache_new(HASHMAP_BASE(h));
 }
 
 int hashmap_put(Hashmap *h, const void *key, void *value);
@@ -159,12 +166,12 @@ static inline int ordered_hashmap_replace(OrderedHashmap *h, const void *key, vo
         return hashmap_replace(PLAIN_HASHMAP(h), key, value);
 }
 
-void *internal_hashmap_get(HashmapBase *h, const void *key);
+void *_hashmap_get(HashmapBase *h, const void *key);
 static inline void *hashmap_get(Hashmap *h, const void *key) {
-        return internal_hashmap_get(HASHMAP_BASE(h), key);
+        return _hashmap_get(HASHMAP_BASE(h), key);
 }
 static inline void *ordered_hashmap_get(OrderedHashmap *h, const void *key) {
-        return internal_hashmap_get(HASHMAP_BASE(h), key);
+        return _hashmap_get(HASHMAP_BASE(h), key);
 }
 
 void *hashmap_get2(Hashmap *h, const void *key, void **rkey);
@@ -172,20 +179,20 @@ static inline void *ordered_hashmap_get2(OrderedHashmap *h, const void *key, voi
         return hashmap_get2(PLAIN_HASHMAP(h), key, rkey);
 }
 
-bool internal_hashmap_contains(HashmapBase *h, const void *key);
+bool _hashmap_contains(HashmapBase *h, const void *key);
 static inline bool hashmap_contains(Hashmap *h, const void *key) {
-        return internal_hashmap_contains(HASHMAP_BASE(h), key);
+        return _hashmap_contains(HASHMAP_BASE(h), key);
 }
 static inline bool ordered_hashmap_contains(OrderedHashmap *h, const void *key) {
-        return internal_hashmap_contains(HASHMAP_BASE(h), key);
+        return _hashmap_contains(HASHMAP_BASE(h), key);
 }
 
-void *internal_hashmap_remove(HashmapBase *h, const void *key);
+void *_hashmap_remove(HashmapBase *h, const void *key);
 static inline void *hashmap_remove(Hashmap *h, const void *key) {
-        return internal_hashmap_remove(HASHMAP_BASE(h), key);
+        return _hashmap_remove(HASHMAP_BASE(h), key);
 }
 static inline void *ordered_hashmap_remove(OrderedHashmap *h, const void *key) {
-        return internal_hashmap_remove(HASHMAP_BASE(h), key);
+        return _hashmap_remove(HASHMAP_BASE(h), key);
 }
 
 void *hashmap_remove2(Hashmap *h, const void *key, void **rkey);
@@ -193,9 +200,9 @@ static inline void *ordered_hashmap_remove2(OrderedHashmap *h, const void *key,
         return hashmap_remove2(PLAIN_HASHMAP(h), key, rkey);
 }
 
-void *internal_hashmap_remove_value(HashmapBase *h, const void *key, void *value);
+void *_hashmap_remove_value(HashmapBase *h, const void *key, void *value);
 static inline void *hashmap_remove_value(Hashmap *h, const void *key, void *value) {
-        return internal_hashmap_remove_value(HASHMAP_BASE(h), key, value);
+        return _hashmap_remove_value(HASHMAP_BASE(h), key, value);
 }
 
 static inline void *ordered_hashmap_remove_value(OrderedHashmap *h, const void *key, void *value) {
@@ -214,41 +221,41 @@ static inline int ordered_hashmap_remove_and_replace(OrderedHashmap *h, const vo
 
 /* Since merging data from a OrderedHashmap into a Hashmap or vice-versa
  * should just work, allow this by having looser type-checking here. */
-int internal_hashmap_merge(Hashmap *h, Hashmap *other);
-#define hashmap_merge(h, other) internal_hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other))
+int _hashmap_merge(Hashmap *h, Hashmap *other);
+#define hashmap_merge(h, other) _hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other))
 #define ordered_hashmap_merge(h, other) hashmap_merge(h, other)
 
-int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add);
+int _hashmap_reserve(HashmapBase *h, unsigned entries_add);
 static inline int hashmap_reserve(Hashmap *h, unsigned entries_add) {
-        return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add);
+        return _hashmap_reserve(HASHMAP_BASE(h), entries_add);
 }
 static inline int ordered_hashmap_reserve(OrderedHashmap *h, unsigned entries_add) {
-        return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add);
+        return _hashmap_reserve(HASHMAP_BASE(h), entries_add);
 }
 
-int internal_hashmap_move(HashmapBase *h, HashmapBase *other);
+int _hashmap_move(HashmapBase *h, HashmapBase *other);
 /* Unlike hashmap_merge, hashmap_move does not allow mixing the types. */
 static inline int hashmap_move(Hashmap *h, Hashmap *other) {
-        return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other));
+        return _hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other));
 }
 static inline int ordered_hashmap_move(OrderedHashmap *h, OrderedHashmap *other) {
-        return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other));
+        return _hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other));
 }
 
-int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key);
+int _hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key);
 static inline int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) {
-        return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key);
+        return _hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key);
 }
 static inline int ordered_hashmap_move_one(OrderedHashmap *h, OrderedHashmap *other, const void *key) {
-        return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key);
+        return _hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key);
 }
 
-unsigned internal_hashmap_size(HashmapBase *h) _pure_;
+unsigned _hashmap_size(HashmapBase *h) _pure_;
 static inline unsigned hashmap_size(Hashmap *h) {
-        return internal_hashmap_size(HASHMAP_BASE(h));
+        return _hashmap_size(HASHMAP_BASE(h));
 }
 static inline unsigned ordered_hashmap_size(OrderedHashmap *h) {
-        return internal_hashmap_size(HASHMAP_BASE(h));
+        return _hashmap_size(HASHMAP_BASE(h));
 }
 
 static inline bool hashmap_isempty(Hashmap *h) {
@@ -258,49 +265,49 @@ static inline bool ordered_hashmap_isempty(OrderedHashmap *h) {
         return ordered_hashmap_size(h) == 0;
 }
 
-unsigned internal_hashmap_buckets(HashmapBase *h) _pure_;
+unsigned _hashmap_buckets(HashmapBase *h) _pure_;
 static inline unsigned hashmap_buckets(Hashmap *h) {
-        return internal_hashmap_buckets(HASHMAP_BASE(h));
+        return _hashmap_buckets(HASHMAP_BASE(h));
 }
 static inline unsigned ordered_hashmap_buckets(OrderedHashmap *h) {
-        return internal_hashmap_buckets(HASHMAP_BASE(h));
+        return _hashmap_buckets(HASHMAP_BASE(h));
 }
 
-bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key);
+bool _hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key);
 static inline bool hashmap_iterate(Hashmap *h, Iterator *i, void **value, const void **key) {
-        return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key);
+        return _hashmap_iterate(HASHMAP_BASE(h), i, value, key);
 }
 static inline bool ordered_hashmap_iterate(OrderedHashmap *h, Iterator *i, void **value, const void **key) {
-        return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key);
+        return _hashmap_iterate(HASHMAP_BASE(h), i, value, key);
 }
 
-void internal_hashmap_clear(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value);
+void _hashmap_clear(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value);
 static inline void hashmap_clear(Hashmap *h) {
-        internal_hashmap_clear(HASHMAP_BASE(h), NULL, NULL);
+        _hashmap_clear(HASHMAP_BASE(h), NULL, NULL);
 }
 static inline void ordered_hashmap_clear(OrderedHashmap *h) {
-        internal_hashmap_clear(HASHMAP_BASE(h), NULL, NULL);
+        _hashmap_clear(HASHMAP_BASE(h), NULL, NULL);
 }
 
 static inline void hashmap_clear_free(Hashmap *h) {
-        internal_hashmap_clear(HASHMAP_BASE(h), NULL, free);
+        _hashmap_clear(HASHMAP_BASE(h), NULL, free);
 }
 static inline void ordered_hashmap_clear_free(OrderedHashmap *h) {
-        internal_hashmap_clear(HASHMAP_BASE(h), NULL, free);
+        _hashmap_clear(HASHMAP_BASE(h), NULL, free);
 }
 
 static inline void hashmap_clear_free_key(Hashmap *h) {
-        internal_hashmap_clear(HASHMAP_BASE(h), free, NULL);
+        _hashmap_clear(HASHMAP_BASE(h), free, NULL);
 }
 static inline void ordered_hashmap_clear_free_key(OrderedHashmap *h) {
-        internal_hashmap_clear(HASHMAP_BASE(h), free, NULL);
+        _hashmap_clear(HASHMAP_BASE(h), free, NULL);
 }
 
 static inline void hashmap_clear_free_free(Hashmap *h) {
-        internal_hashmap_clear(HASHMAP_BASE(h), free, free);
+        _hashmap_clear(HASHMAP_BASE(h), free, free);
 }
 static inline void ordered_hashmap_clear_free_free(OrderedHashmap *h) {
-        internal_hashmap_clear(HASHMAP_BASE(h), free, free);
+        _hashmap_clear(HASHMAP_BASE(h), free, free);
 }
 
 /*
@@ -314,50 +321,50 @@ static inline void ordered_hashmap_clear_free_free(OrderedHashmap *h) {
  * the first entry is O(1).
  */
 
-void *internal_hashmap_first_key_and_value(HashmapBase *h, bool remove, void **ret_key);
+void *_hashmap_first_key_and_value(HashmapBase *h, bool remove, void **ret_key);
 static inline void *hashmap_steal_first_key_and_value(Hashmap *h, void **ret) {
-        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), true, ret);
+        return _hashmap_first_key_and_value(HASHMAP_BASE(h), true, ret);
 }
 static inline void *ordered_hashmap_steal_first_key_and_value(OrderedHashmap *h, void **ret) {
-        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), true, ret);
+        return _hashmap_first_key_and_value(HASHMAP_BASE(h), true, ret);
 }
 static inline void *hashmap_first_key_and_value(Hashmap *h, void **ret) {
-        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), false, ret);
+        return _hashmap_first_key_and_value(HASHMAP_BASE(h), false, ret);
 }
 static inline void *ordered_hashmap_first_key_and_value(OrderedHashmap *h, void **ret) {
-        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), false, ret);
+        return _hashmap_first_key_and_value(HASHMAP_BASE(h), false, ret);
 }
 
 static inline void *hashmap_steal_first(Hashmap *h) {
-        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), true, NULL);
+        return _hashmap_first_key_and_value(HASHMAP_BASE(h), true, NULL);
 }
 static inline void *ordered_hashmap_steal_first(OrderedHashmap *h) {
-        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), true, NULL);
+        return _hashmap_first_key_and_value(HASHMAP_BASE(h), true, NULL);
 }
 static inline void *hashmap_first(Hashmap *h) {
-        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), false, NULL);
+        return _hashmap_first_key_and_value(HASHMAP_BASE(h), false, NULL);
 }
 static inline void *ordered_hashmap_first(OrderedHashmap *h) {
-        return internal_hashmap_first_key_and_value(HASHMAP_BASE(h), false, NULL);
+        return _hashmap_first_key_and_value(HASHMAP_BASE(h), false, NULL);
 }
 
-static inline void *internal_hashmap_first_key(HashmapBase *h, bool remove) {
+static inline void *_hashmap_first_key(HashmapBase *h, bool remove) {
         void *key = NULL;
 
-        (void) internal_hashmap_first_key_and_value(HASHMAP_BASE(h), remove, &key);
+        (void) _hashmap_first_key_and_value(HASHMAP_BASE(h), remove, &key);
         return key;
 }
 static inline void *hashmap_steal_first_key(Hashmap *h) {
-        return internal_hashmap_first_key(HASHMAP_BASE(h), true);
+        return _hashmap_first_key(HASHMAP_BASE(h), true);
 }
 static inline void *ordered_hashmap_steal_first_key(OrderedHashmap *h) {
-        return internal_hashmap_first_key(HASHMAP_BASE(h), true);
+        return _hashmap_first_key(HASHMAP_BASE(h), true);
 }
 static inline void *hashmap_first_key(Hashmap *h) {
-        return internal_hashmap_first_key(HASHMAP_BASE(h), false);
+        return _hashmap_first_key(HASHMAP_BASE(h), false);
 }
 static inline void *ordered_hashmap_first_key(OrderedHashmap *h) {
-        return internal_hashmap_first_key(HASHMAP_BASE(h), false);
+        return _hashmap_first_key(HASHMAP_BASE(h), false);
 }
 
 #define hashmap_clear_with_destructor(_s, _f)                   \
@@ -386,12 +393,12 @@ static inline void *ordered_hashmap_first_key(OrderedHashmap *h) {
 /* no hashmap_next */
 void *ordered_hashmap_next(OrderedHashmap *h, const void *key);
 
-char **internal_hashmap_get_strv(HashmapBase *h);
+char **_hashmap_get_strv(HashmapBase *h);
 static inline char **hashmap_get_strv(Hashmap *h) {
-        return internal_hashmap_get_strv(HASHMAP_BASE(h));
+        return _hashmap_get_strv(HASHMAP_BASE(h));
 }
 static inline char **ordered_hashmap_get_strv(OrderedHashmap *h) {
-        return internal_hashmap_get_strv(HASHMAP_BASE(h));
+        return _hashmap_get_strv(HASHMAP_BASE(h));
 }
 
 /*
diff --git a/shared/systemd/src/basic/hexdecoct.c b/shared/systemd/src/basic/hexdecoct.c
index 36174ec3..1fb52305 100644
--- a/shared/systemd/src/basic/hexdecoct.c
+++ b/shared/systemd/src/basic/hexdecoct.c
@@ -604,13 +604,13 @@ ssize_t base64mem(const void *p, size_t l, char **out) {
 
 static int base64_append_width(
                 char **prefix, int plen,
-                const char *sep, int indent,
+                char sep, int indent,
                 const void *p, size_t l,
                 int width) {
 
         _cleanup_free_ char *x = NULL;
         char *t, *s;
-        ssize_t len, slen, avail, line, lines;
+        ssize_t len, avail, line, lines;
 
         len = base64mem(p, l, &x);
         if (len <= 0)
@@ -618,21 +618,20 @@ static int base64_append_width(
 
         lines = DIV_ROUND_UP(len, width);
 
-        slen = strlen_ptr(sep);
-        if (plen >= SSIZE_MAX - 1 - slen ||
-            lines > (SSIZE_MAX - plen - 1 - slen) / (indent + width + 1))
+        if ((size_t) plen >= SSIZE_MAX - 1 - 1 ||
+            lines > (SSIZE_MAX - plen - 1 - 1) / (indent + width + 1))
                 return -ENOMEM;
 
-        t = realloc(*prefix, (ssize_t) plen + 1 + slen + (indent + width + 1) * lines);
+        t = realloc(*prefix, (ssize_t) plen + 1 + 1 + (indent + width + 1) * lines);
         if (!t)
                 return -ENOMEM;
 
-        memcpy_safe(t + plen, sep, slen);
+        t[plen] = sep;
 
-        for (line = 0, s = t + plen + slen, avail = len; line < lines; line++) {
+        for (line = 0, s = t + plen + 1, avail = len; line < lines; line++) {
                 int act = MIN(width, avail);
 
-                if (line > 0 || sep) {
+                if (line > 0 || sep == '\n') {
                         memset(s, ' ', indent);
                         s += indent;
                 }
@@ -655,10 +654,10 @@ int base64_append(
 
         if (plen > width / 2 || plen + indent > width)
                 /* leave indent on the left, keep last column free */
-                return base64_append_width(prefix, plen, "\n", indent, p, l, width - indent - 1);
+                return base64_append_width(prefix, plen, '\n', indent, p, l, width - indent - 1);
         else
                 /* leave plen on the left, keep last column free */
-                return base64_append_width(prefix, plen, " ", plen, p, l, width - plen - 1);
+                return base64_append_width(prefix, plen, ' ', plen + 1, p, l, width - plen - 1);
 }
 #endif /* NM_IGNORED */
 
diff --git a/shared/systemd/src/basic/hostname-util.c b/shared/systemd/src/basic/hostname-util.c
index 00a92cb7..82fc56db 100644
--- a/shared/systemd/src/basic/hostname-util.c
+++ b/shared/systemd/src/basic/hostname-util.c
@@ -14,6 +14,7 @@
 #include "hostname-util.h"
 #include "macro.h"
 #include "string-util.h"
+#include "strv.h"
 
 #if 0 /* NM_IGNORED */
 bool hostname_is_set(void) {
@@ -24,7 +25,7 @@ bool hostname_is_set(void) {
         if (isempty(u.nodename))
                 return false;
 
-        /* This is the built-in kernel default host name */
+        /* This is the built-in kernel default hostname */
         if (streq(u.nodename, "(none)"))
                 return false;
 
@@ -33,6 +34,7 @@ bool hostname_is_set(void) {
 
 char* gethostname_malloc(void) {
         struct utsname u;
+        const char *s;
 
         /* This call tries to return something useful, either the actual hostname
          * or it makes something up. The only reason it might fail is OOM.
@@ -40,10 +42,28 @@ char* gethostname_malloc(void) {
 
         assert_se(uname(&u) >= 0);
 
-        if (isempty(u.nodename) || streq(u.nodename, "(none)"))
-                return strdup(FALLBACK_HOSTNAME);
+        s = u.nodename;
+        if (isempty(s) || streq(s, "(none)"))
+                s = FALLBACK_HOSTNAME;
 
-        return strdup(u.nodename);
+        return strdup(s);
+}
+
+char* gethostname_short_malloc(void) {
+        struct utsname u;
+        const char *s;
+
+        /* Like above, but kills the FQDN part if present. */
+
+        assert_se(uname(&u) >= 0);
+
+        s = u.nodename;
+        if (isempty(s) || streq(s, "(none)") || s[0] == '.') {
+                s = FALLBACK_HOSTNAME;
+                assert(s[0] != '.');
+        }
+
+        return strndup(s, strcspn(s, "."));
 }
 #endif /* NM_IGNORED */
 
@@ -81,7 +101,7 @@ bool valid_ldh_char(char c) {
 }
 
 /**
- * Check if s looks like a valid host name or FQDN. This does not do
+ * Check if s looks like a valid hostname or FQDN. This does not do
  * full DNS validation, but only checks if the name is composed of
  * allowed characters and the length is not above the maximum allowed
  * by Linux (c.f. dns_name_is_valid()). Trailing dot is allowed if
@@ -184,14 +204,16 @@ bool is_localhost(const char *hostname) {
         /* This tries to identify local host and domain names
          * described in RFC6761 plus the redhatism of localdomain */
 
-        return strcaseeq(hostname, "localhost") ||
-               strcaseeq(hostname, "localhost.") ||
-               strcaseeq(hostname, "localhost.localdomain") ||
-               strcaseeq(hostname, "localhost.localdomain.") ||
-               endswith_no_case(hostname, ".localhost") ||
-               endswith_no_case(hostname, ".localhost.") ||
-               endswith_no_case(hostname, ".localhost.localdomain") ||
-               endswith_no_case(hostname, ".localhost.localdomain.");
+        return STRCASE_IN_SET(
+                        hostname,
+                        "localhost",
+                        "localhost.",
+                        "localhost.localdomain",
+                        "localhost.localdomain.") ||
+                endswith_no_case(hostname, ".localhost") ||
+                endswith_no_case(hostname, ".localhost.") ||
+                endswith_no_case(hostname, ".localhost.localdomain") ||
+                endswith_no_case(hostname, ".localhost.localdomain.");
 }
 
 #if 0 /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/hostname-util.h b/shared/systemd/src/basic/hostname-util.h
index 7ba386a0..cafd6f02 100644
--- a/shared/systemd/src/basic/hostname-util.h
+++ b/shared/systemd/src/basic/hostname-util.h
@@ -9,6 +9,7 @@
 bool hostname_is_set(void);
 
 char* gethostname_malloc(void);
+char* gethostname_short_malloc(void);
 int gethostname_strict(char **ret);
 
 bool valid_ldh_char(char c) _const_;
diff --git a/shared/systemd/src/basic/in-addr-util.c b/shared/systemd/src/basic/in-addr-util.c
index 0e770621..cddc269f 100644
--- a/shared/systemd/src/basic/in-addr-util.c
+++ b/shared/systemd/src/basic/in-addr-util.c
@@ -180,47 +180,89 @@ int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen)
         assert(u);
 
         /* Increases the network part of an address by one. Returns
-         * positive it that succeeds, or 0 if this overflows. */
+         * positive if that succeeds, or -ERANGE if this overflows. */
+
+        return in_addr_prefix_nth(family, u, prefixlen, 1);
+}
+
+/*
+ * Calculates the nth prefix of size prefixlen starting from the address denoted by u.
+ *
+ * On success 1 will be returned and the calculated prefix will be available in
+ * u. In the case nth == 0 the input will be left unchanged and 1 will be returned.
+ * In case the calculation cannot be performed (invalid prefix length,
+ * overflows would occur) -ERANGE is returned. If the address family given isn't
+ * supported -EAFNOSUPPORT will be returned.
+ *
+ *
+ * Examples:
+ *   - in_addr_prefix_nth(AF_INET, 192.168.0.0, 24, 2), returns 1, writes 192.168.2.0 to u
+ *   - in_addr_prefix_nth(AF_INET, 192.168.0.0, 24, 0), returns 1, no data written
+ *   - in_addr_prefix_nth(AF_INET, 255.255.255.0, 24, 1), returns -ERANGE, no data written
+ *   - in_addr_prefix_nth(AF_INET, 255.255.255.0, 0, 1), returns -ERANGE, no data written
+ *   - in_addr_prefix_nth(AF_INET6, 2001:db8, 64, 0xff00) returns 1, writes 2001:0db8:0000:ff00:: to u
+ */
+int in_addr_prefix_nth(int family, union in_addr_union *u, unsigned prefixlen, uint64_t nth) {
+        assert(u);
 
         if (prefixlen <= 0)
-                return 0;
+                return -ERANGE;
 
-        if (family == AF_INET) {
-                uint32_t c, n;
+        if (nth == 0)
+                return 1;
 
+        if (family == AF_INET) {
+                uint32_t c, n, t;
                 if (prefixlen > 32)
                         prefixlen = 32;
 
                 c = be32toh(u->in.s_addr);
-                n = c + (1UL << (32 - prefixlen));
-                if (n < c)
-                        return 0;
-                n &= 0xFFFFFFFFUL << (32 - prefixlen);
 
+                t = nth << (32 - prefixlen);
+
+                /* Check for wrap */
+                if (c > UINT32_MAX - t)
+                        return -ERANGE;
+
+                n = c + t;
+
+                n &= UINT32_C(0xFFFFFFFF) << (32 - prefixlen);
                 u->in.s_addr = htobe32(n);
                 return 1;
         }
 
         if (family == AF_INET6) {
-                struct in6_addr add = {}, result;
+                struct in6_addr result = {};
                 uint8_t overflow = 0;
-                unsigned i;
+                uint64_t delta;  /* this assumes that we only ever have to up to 1<<64 subnets */
+                unsigned start_byte = (prefixlen - 1) / 8;
 
                 if (prefixlen > 128)
                         prefixlen = 128;
 
                 /* First calculate what we have to add */
-                add.s6_addr[(prefixlen-1) / 8] = 1 << (7 - (prefixlen-1) % 8);
+                delta = nth << ((128 - prefixlen) % 8);
 
-                for (i = 16; i > 0; i--) {
+                for (unsigned i = 16; i > 0; i--) {
                         unsigned j = i - 1;
+                        unsigned d = 0;
 
-                        result.s6_addr[j] = u->in6.s6_addr[j] + add.s6_addr[j] + overflow;
-                        overflow = (result.s6_addr[j] < u->in6.s6_addr[j]);
+                        if (j <= start_byte) {
+                                int16_t t;
+
+                                d = delta & 0xFF;
+                                delta >>= 8;
+
+                                t = u->in6.s6_addr[j] + d + overflow;
+                                overflow = t > UINT8_MAX ? t - UINT8_MAX : 0;
+
+                                result.s6_addr[j] = (uint8_t)t;
+                        } else
+                                result.s6_addr[j] = u->in6.s6_addr[j];
                 }
 
-                if (overflow)
-                        return 0;
+                if (overflow || delta != 0)
+                        return -ERANGE;
 
                 u->in6 = result;
                 return 1;
diff --git a/shared/systemd/src/basic/in-addr-util.h b/shared/systemd/src/basic/in-addr-util.h
index ae2dad0b..90d79a5e 100644
--- a/shared/systemd/src/basic/in-addr-util.h
+++ b/shared/systemd/src/basic/in-addr-util.h
@@ -36,6 +36,7 @@ bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b);
 int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b);
 int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen);
 int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen);
+int in_addr_prefix_nth(int family, union in_addr_union *u, unsigned prefixlen, uint64_t nth);
 int in_addr_random_prefix(int family, union in_addr_union *u, unsigned prefixlen_fixed_part, unsigned prefixlen);
 int in_addr_to_string(int family, const union in_addr_union *u, char **ret);
 int in_addr_prefix_to_string(int family, const union in_addr_union *u, unsigned prefixlen, char **ret);
diff --git a/shared/systemd/src/basic/io-util.c b/shared/systemd/src/basic/io-util.c
index 4f57f044..f1df3fac 100644
--- a/shared/systemd/src/basic/io-util.c
+++ b/shared/systemd/src/basic/io-util.c
@@ -14,10 +14,6 @@
 
 #if 0 /* NM_IGNORED */
 int flush_fd(int fd) {
-        struct pollfd pollfd = {
-                .fd = fd,
-                .events = POLLIN,
-        };
         int count = 0;
 
         /* Read from the specified file descriptor, until POLLIN is not set anymore, throwing away everything
@@ -30,19 +26,18 @@ int flush_fd(int fd) {
                 ssize_t l;
                 int r;
 
-                r = poll(&pollfd, 1, 0);
+                r = fd_wait_for_event(fd, POLLIN, 0);
                 if (r < 0) {
-                        if (errno == EINTR)
+                        if (r == -EINTR)
                                 continue;
 
-                        return -errno;
-
-                } else if (r == 0)
+                        return r;
+                }
+                if (r == 0)
                         return count;
 
                 l = read(fd, buf, sizeof(buf));
                 if (l < 0) {
-
                         if (errno == EINTR)
                                 continue;
 
@@ -160,21 +155,15 @@ int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) {
 }
 
 int pipe_eof(int fd) {
-        struct pollfd pollfd = {
-                .fd = fd,
-                .events = POLLIN|POLLHUP,
-        };
-
         int r;
 
-        r = poll(&pollfd, 1, 0);
+        r = fd_wait_for_event(fd, POLLIN, 0);
         if (r < 0)
-                return -errno;
-
+                return r;
         if (r == 0)
                 return 0;
 
-        return pollfd.revents & POLLHUP;
+        return !!(r & POLLHUP);
 }
 #endif /* NM_IGNORED */
 
@@ -194,6 +183,9 @@ int fd_wait_for_event(int fd, int event, usec_t t) {
         if (r == 0)
                 return 0;
 
+        if (pollfd.revents & POLLNVAL)
+                return -EBADF;
+
         return pollfd.revents;
 }
 
diff --git a/shared/systemd/src/basic/macro.h b/shared/systemd/src/basic/macro.h
index 6cd9c516..0c2eb894 100644
--- a/shared/systemd/src/basic/macro.h
+++ b/shared/systemd/src/basic/macro.h
@@ -84,6 +84,14 @@
 #define _variable_no_sanitize_address_
 #endif
 
+/* Apparently there's no has_feature() call defined to check for ubsan, hence let's define this
+ * unconditionally on llvm */
+#if defined(__clang__)
+#define _function_no_sanitize_float_cast_overflow_ __attribute__((no_sanitize("float-cast-overflow")))
+#else
+#define _function_no_sanitize_float_cast_overflow_
+#endif
+
 #if (defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) || defined (__clang__)
 /* Temporarily disable some warnings */
 #define DISABLE_WARNING_FORMAT_NONLITERAL                               \
@@ -115,6 +123,14 @@
         _Pragma("GCC diagnostic push")
 #endif
 
+#define DISABLE_WARNING_FLOAT_EQUAL \
+        _Pragma("GCC diagnostic push");                                 \
+        _Pragma("GCC diagnostic ignored \"-Wfloat-equal\"")
+
+#define DISABLE_WARNING_TYPE_LIMITS \
+        _Pragma("GCC diagnostic push");                                 \
+        _Pragma("GCC diagnostic ignored \"-Wtype-limits\"")
+
 #define REENABLE_WARNING                                                \
         _Pragma("GCC diagnostic pop")
 #else
@@ -441,6 +457,8 @@ static inline int __coverity_check_and_return__(int condition) {
 
 #define char_array_0(x) x[sizeof(x)-1] = 0;
 
+#define sizeof_field(struct_type, member) sizeof(((struct_type *) 0)->member)
+
 /* Returns the number of chars needed to format variables of the
  * specified type as a decimal string. Adds in extra space for a
  * negative '-' prefix (hence works correctly on signed
@@ -460,8 +478,10 @@ static inline int __coverity_check_and_return__(int condition) {
                 ans;                                    \
         })
 
+#define UPDATE_FLAG(orig, flag, b)                      \
+        ((b) ? ((orig) | (flag)) : ((orig) & ~(flag)))
 #define SET_FLAG(v, flag, b) \
-        (v) = (b) ? ((v) | (flag)) : ((v) & ~(flag))
+        (v) = UPDATE_FLAG(v, flag, b)
 #define FLAGS_SET(v, flags) \
         ((~(v) & (flags)) == 0)
 
@@ -592,4 +612,17 @@ static inline int __coverity_check_and_return__(int condition) {
         DEFINE_PUBLIC_TRIVIAL_REF_FUNC(type, name);                    \
         DEFINE_PUBLIC_TRIVIAL_UNREF_FUNC(type, name, free_func);
 
+/* A macro to force copying of a variable from memory. This is useful whenever we want to read something from
+ * memory and want to make sure the compiler won't optimize away the destination variable for us. It's not
+ * supposed to be a full CPU memory barrier, i.e. CPU is still allowed to reorder the reads, but it is not
+ * allowed to remove our local copies of the variables. We want this to work for unaligned memory, hence
+ * memcpy() is great for our purposes. */
+#define READ_NOW(x)                                                     \
+        ({                                                              \
+                typeof(x) _copy;                                        \
+                memcpy(&_copy, &(x), sizeof(_copy));                    \
+                asm volatile ("" : : : "memory");                       \
+                _copy;                                                  \
+        })
+
 #include "log.h"
diff --git a/shared/systemd/src/basic/memory-util.h b/shared/systemd/src/basic/memory-util.h
index b7e2e67e..a6a2ccdb 100644
--- a/shared/systemd/src/basic/memory-util.h
+++ b/shared/systemd/src/basic/memory-util.h
@@ -12,7 +12,7 @@
 
 size_t page_size(void) _pure_;
 #define PAGE_ALIGN(l) ALIGN_TO((l), page_size())
-#define PAGE_ALIGN_DOWN(l) (l & ~(page_size() - 1))
+#define PAGE_ALIGN_DOWN(l) ((l) & ~(page_size() - 1))
 
 /* Normal memcpy requires src to be nonnull. We do nothing if n is 0. */
 static inline void memcpy_safe(void *dst, const void *src, size_t n) {
diff --git a/shared/systemd/src/basic/missing_random.h b/shared/systemd/src/basic/missing_random.h
index 2e76031b..17af87a3 100644
--- a/shared/systemd/src/basic/missing_random.h
+++ b/shared/systemd/src/basic/missing_random.h
@@ -14,3 +14,7 @@
 #ifndef GRND_RANDOM
 #define GRND_RANDOM 0x0002
 #endif
+
+#ifndef GRND_INSECURE
+#define GRND_INSECURE 0x0004
+#endif
diff --git a/shared/systemd/src/basic/missing_socket.h b/shared/systemd/src/basic/missing_socket.h
index 29828dba..647e56f1 100644
--- a/shared/systemd/src/basic/missing_socket.h
+++ b/shared/systemd/src/basic/missing_socket.h
@@ -64,3 +64,8 @@ struct sockaddr_vm {
 #ifndef IP_TRANSPARENT
 #define IP_TRANSPARENT 19
 #endif
+
+/* linux/sockios.h */
+#ifndef SIOCGSKNS
+#define SIOCGSKNS 0x894C
+#endif
diff --git a/shared/systemd/src/basic/parse-util.c b/shared/systemd/src/basic/parse-util.c
index 475a06cc..0b343b2a 100644
--- a/shared/systemd/src/basic/parse-util.c
+++ b/shared/systemd/src/basic/parse-util.c
@@ -20,14 +20,28 @@
 #include "process-util.h"
 #include "stat-util.h"
 #include "string-util.h"
+#include "strv.h"
 
 int parse_boolean(const char *v) {
         if (!v)
                 return -EINVAL;
 
-        if (streq(v, "1") || strcaseeq(v, "yes") || strcaseeq(v, "y") || strcaseeq(v, "true") || strcaseeq(v, "t") || strcaseeq(v, "on"))
+        if (STRCASE_IN_SET(v,
+                           "1",
+                           "yes",
+                           "y",
+                           "true",
+                           "t",
+                           "on"))
                 return 1;
-        else if (streq(v, "0") || strcaseeq(v, "no") || strcaseeq(v, "n") || strcaseeq(v, "false") || strcaseeq(v, "f") || strcaseeq(v, "off"))
+
+        if (STRCASE_IN_SET(v,
+                           "0",
+                           "no",
+                           "n",
+                           "false",
+                           "f",
+                           "off"))
                 return 0;
 
         return -EINVAL;
@@ -59,26 +73,24 @@ int parse_pid(const char *s, pid_t* ret_pid) {
 }
 
 int parse_mode(const char *s, mode_t *ret) {
-        char *x;
-        long l;
+        unsigned m;
+        int r;
 
         assert(s);
-        assert(ret);
 
-        s += strspn(s, WHITESPACE);
-        if (s[0] == '-')
-                return -ERANGE;
-
-        errno = 0;
-        l = strtol(s, &x, 8);
-        if (errno > 0)
-                return -errno;
-        if (!x || x == s || *x != 0)
-                return -EINVAL;
-        if (l < 0 || l  > 07777)
+        r = safe_atou_full(s, 8 |
+                           SAFE_ATO_REFUSE_PLUS_MINUS, /* Leading '+' or even '-' char? that's just weird,
+                                                        * refuse. User might have wanted to add mode flags or
+                                                        * so, but this parser doesn't allow that, so let's
+                                                        * better be safe. */
+                           &m);
+        if (r < 0)
+                return r;
+        if (m > 07777)
                 return -ERANGE;
 
-        *ret = (mode_t) l;
+        if (ret)
+                *ret = m;
         return 0;
 }
 
@@ -344,30 +356,73 @@ int parse_syscall_and_errno(const char *in, char **name, int *error) {
 }
 #endif /* NM_IGNORED */
 
+static const char *mangle_base(const char *s, unsigned *base) {
+        const char *k;
+
+        assert(s);
+        assert(base);
+
+        /* Base already explicitly specified, then don't do anything. */
+        if (SAFE_ATO_MASK_FLAGS(*base) != 0)
+                return s;
+
+        /* Support Python 3 style "0b" and 0x" prefixes, because they truly make sense, much more than C's "0" prefix for octal. */
+        k = STARTSWITH_SET(s, "0b", "0B");
+        if (k) {
+                *base = 2 | (*base & SAFE_ATO_ALL_FLAGS);
+                return k;
+        }
+
+        k = STARTSWITH_SET(s, "0o", "0O");
+        if (k) {
+                *base = 8 | (*base & SAFE_ATO_ALL_FLAGS);
+                return k;
+        }
+
+        return s;
+}
+
 int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
         char *x = NULL;
         unsigned long l;
 
         assert(s);
-        assert(base <= 16);
+        assert(SAFE_ATO_MASK_FLAGS(base) <= 16);
 
-        /* strtoul() is happy to parse negative values, and silently
-         * converts them to unsigned values without generating an
-         * error. We want a clean error, hence let's look for the "-"
-         * prefix on our own, and generate an error. But let's do so
-         * only after strtoul() validated that the string is clean
-         * otherwise, so that we return EINVAL preferably over
-         * ERANGE. */
+        /* strtoul() is happy to parse negative values, and silently converts them to unsigned values without
+         * generating an error. We want a clean error, hence let's look for the "-" prefix on our own, and
+         * generate an error. But let's do so only after strtoul() validated that the string is clean
+         * otherwise, so that we return EINVAL preferably over ERANGE. */
+
+        if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_WHITESPACE) &&
+            strchr(WHITESPACE, s[0]))
+                return -EINVAL;
 
         s += strspn(s, WHITESPACE);
 
+        if (FLAGS_SET(base, SAFE_ATO_REFUSE_PLUS_MINUS) &&
+            IN_SET(s[0], '+', '-'))
+                return -EINVAL; /* Note that we check the "-" prefix again a second time below, but return a
+                                 * different error. I.e. if the SAFE_ATO_REFUSE_PLUS_MINUS flag is set we
+                                 * blanket refuse +/- prefixed integers, while if it is missing we'll just
+                                 * return ERANGE, because the string actually parses correctly, but doesn't
+                                 * fit in the return type. */
+
+        if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_ZERO) &&
+            s[0] == '0' && !streq(s, "0"))
+                return -EINVAL; /* This is particularly useful to avoid ambiguities between C's octal
+                                 * notation and assumed-to-be-decimal integers with a leading zero. */
+
+        s = mangle_base(s, &base);
+
         errno = 0;
-        l = strtoul(s, &x, base);
+        l = strtoul(s, &x, SAFE_ATO_MASK_FLAGS(base) /* Let's mask off the flags bits so that only the actual
+                                                      * base is left */);
         if (errno > 0)
                 return -errno;
         if (!x || x == s || *x != 0)
                 return -EINVAL;
-        if (s[0] == '-')
+        if (l != 0 && s[0] == '-')
                 return -ERANGE;
         if ((unsigned long) (unsigned) l != l)
                 return -ERANGE;
@@ -379,13 +434,17 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
 }
 
 int safe_atoi(const char *s, int *ret_i) {
+        unsigned base = 0;
         char *x = NULL;
         long l;
 
         assert(s);
 
+        s += strspn(s, WHITESPACE);
+        s = mangle_base(s, &base);
+
         errno = 0;
-        l = strtol(s, &x, 0);
+        l = strtol(s, &x, base);
         if (errno > 0)
                 return -errno;
         if (!x || x == s || *x != 0)
@@ -399,21 +458,36 @@ int safe_atoi(const char *s, int *ret_i) {
         return 0;
 }
 
-int safe_atollu(const char *s, long long unsigned *ret_llu) {
+int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu) {
         char *x = NULL;
         unsigned long long l;
 
         assert(s);
+        assert(SAFE_ATO_MASK_FLAGS(base) <= 16);
+
+        if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_WHITESPACE) &&
+            strchr(WHITESPACE, s[0]))
+                return -EINVAL;
 
         s += strspn(s, WHITESPACE);
 
+        if (FLAGS_SET(base, SAFE_ATO_REFUSE_PLUS_MINUS) &&
+            IN_SET(s[0], '+', '-'))
+                return -EINVAL;
+
+        if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_ZERO) &&
+            s[0] == '0' && s[1] != 0)
+                return -EINVAL;
+
+        s = mangle_base(s, &base);
+
         errno = 0;
-        l = strtoull(s, &x, 0);
+        l = strtoull(s, &x, SAFE_ATO_MASK_FLAGS(base));
         if (errno > 0)
                 return -errno;
         if (!x || x == s || *x != 0)
                 return -EINVAL;
-        if (*s == '-')
+        if (l != 0 && s[0] == '-')
                 return -ERANGE;
 
         if (ret_llu)
@@ -423,13 +497,17 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) {
 }
 
 int safe_atolli(const char *s, long long int *ret_lli) {
+        unsigned base = 0;
         char *x = NULL;
         long long l;
 
         assert(s);
 
+        s += strspn(s, WHITESPACE);
+        s = mangle_base(s, &base);
+
         errno = 0;
-        l = strtoll(s, &x, 0);
+        l = strtoll(s, &x, base);
         if (errno > 0)
                 return -errno;
         if (!x || x == s || *x != 0)
@@ -442,20 +520,22 @@ int safe_atolli(const char *s, long long int *ret_lli) {
 }
 
 int safe_atou8(const char *s, uint8_t *ret) {
-        char *x = NULL;
+        unsigned base = 0;
         unsigned long l;
+        char *x = NULL;
 
         assert(s);
 
         s += strspn(s, WHITESPACE);
+        s = mangle_base(s, &base);
 
         errno = 0;
-        l = strtoul(s, &x, 0);
+        l = strtoul(s, &x, base);
         if (errno > 0)
                 return -errno;
         if (!x || x == s || *x != 0)
                 return -EINVAL;
-        if (s[0] == '-')
+        if (l != 0 && s[0] == '-')
                 return -ERANGE;
         if ((unsigned long) (uint8_t) l != l)
                 return -ERANGE;
@@ -470,34 +550,53 @@ int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) {
         unsigned long l;
 
         assert(s);
-        assert(ret);
-        assert(base <= 16);
+        assert(SAFE_ATO_MASK_FLAGS(base) <= 16);
+
+        if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_WHITESPACE) &&
+            strchr(WHITESPACE, s[0]))
+                return -EINVAL;
 
         s += strspn(s, WHITESPACE);
 
+        if (FLAGS_SET(base, SAFE_ATO_REFUSE_PLUS_MINUS) &&
+            IN_SET(s[0], '+', '-'))
+                return -EINVAL;
+
+        if (FLAGS_SET(base, SAFE_ATO_REFUSE_LEADING_ZERO) &&
+            s[0] == '0' && s[1] != 0)
+                return -EINVAL;
+
+        s = mangle_base(s, &base);
+
         errno = 0;
-        l = strtoul(s, &x, base);
+        l = strtoul(s, &x, SAFE_ATO_MASK_FLAGS(base));
         if (errno > 0)
                 return -errno;
         if (!x || x == s || *x != 0)
                 return -EINVAL;
-        if (s[0] == '-')
+        if (l != 0 && s[0] == '-')
                 return -ERANGE;
         if ((unsigned long) (uint16_t) l != l)
                 return -ERANGE;
 
-        *ret = (uint16_t) l;
+        if (ret)
+                *ret = (uint16_t) l;
+
         return 0;
 }
 
 int safe_atoi16(const char *s, int16_t *ret) {
+        unsigned base = 0;
         char *x = NULL;
         long l;
 
         assert(s);
 
+        s += strspn(s, WHITESPACE);
+        s = mangle_base(s, &base);
+
         errno = 0;
-        l = strtol(s, &x, 0);
+        l = strtol(s, &x, base);
         if (errno > 0)
                 return -errno;
         if (!x || x == s || *x != 0)
diff --git a/shared/systemd/src/basic/parse-util.h b/shared/systemd/src/basic/parse-util.h
index 36d76ba5..9a516ce5 100644
--- a/shared/systemd/src/basic/parse-util.h
+++ b/shared/systemd/src/basic/parse-util.h
@@ -21,6 +21,12 @@ int parse_range(const char *t, unsigned *lower, unsigned *upper);
 int parse_errno(const char *t);
 int parse_syscall_and_errno(const char *in, char **name, int *error);
 
+#define SAFE_ATO_REFUSE_PLUS_MINUS (1U << 30)
+#define SAFE_ATO_REFUSE_LEADING_ZERO (1U << 29)
+#define SAFE_ATO_REFUSE_LEADING_WHITESPACE (1U << 28)
+#define SAFE_ATO_ALL_FLAGS (SAFE_ATO_REFUSE_PLUS_MINUS|SAFE_ATO_REFUSE_LEADING_ZERO|SAFE_ATO_REFUSE_LEADING_WHITESPACE)
+#define SAFE_ATO_MASK_FLAGS(base) ((base) & ~SAFE_ATO_ALL_FLAGS)
+
 int safe_atou_full(const char *s, unsigned base, unsigned *ret_u);
 
 static inline int safe_atou(const char *s, unsigned *ret_u) {
@@ -28,7 +34,6 @@ static inline int safe_atou(const char *s, unsigned *ret_u) {
 }
 
 int safe_atoi(const char *s, int *ret_i);
-int safe_atollu(const char *s, unsigned long long *ret_u);
 int safe_atolli(const char *s, long long int *ret_i);
 
 int safe_atou8(const char *s, uint8_t *ret);
@@ -59,6 +64,12 @@ static inline int safe_atoi32(const char *s, int32_t *ret_i) {
         return safe_atoi(s, (int*) ret_i);
 }
 
+int safe_atollu_full(const char *s, unsigned base, long long unsigned *ret_llu);
+
+static inline int safe_atollu(const char *s, long long unsigned *ret_llu) {
+        return safe_atollu_full(s, 0, ret_llu);
+}
+
 static inline int safe_atou64(const char *s, uint64_t *ret_u) {
         assert_cc(sizeof(uint64_t) == sizeof(unsigned long long));
         return safe_atollu(s, (unsigned long long*) ret_u);
@@ -69,6 +80,11 @@ static inline int safe_atoi64(const char *s, int64_t *ret_i) {
         return safe_atolli(s, (long long int*) ret_i);
 }
 
+static inline int safe_atoux64(const char *s, uint64_t *ret) {
+        assert_cc(sizeof(int64_t) == sizeof(long long unsigned));
+        return safe_atollu_full(s, 16, (long long unsigned*) ret);
+}
+
 #if LONG_MAX == INT_MAX
 static inline int safe_atolu(const char *s, unsigned long *ret_u) {
         assert_cc(sizeof(unsigned long) == sizeof(unsigned));
diff --git a/shared/systemd/src/basic/path-util.c b/shared/systemd/src/basic/path-util.c
index 7baab4be..45477bc2 100644
--- a/shared/systemd/src/basic/path-util.c
+++ b/shared/systemd/src/basic/path-util.c
@@ -1062,7 +1062,7 @@ int systemd_installation_has_version(const char *root, unsigned minimal_version)
                 if (!path)
                         return -ENOMEM;
 
-                r = glob_extend(&names, path);
+                r = glob_extend(&names, path, 0);
                 if (r == -ENOENT)
                         continue;
                 if (r < 0)
diff --git a/shared/systemd/src/basic/process-util.c b/shared/systemd/src/basic/process-util.c
index 2de3541c..6dcb26fb 100644
--- a/shared/systemd/src/basic/process-util.c
+++ b/shared/systemd/src/basic/process-util.c
@@ -212,50 +212,12 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags
         return 0;
 }
 
-int rename_process(const char name[]) {
-        static size_t mm_size = 0;
-        static char *mm = NULL;
-        bool truncated = false;
-        size_t l;
-
-        /* This is a like a poor man's setproctitle(). It changes the comm field, argv[0], and also the glibc's
-         * internally used name of the process. For the first one a limit of 16 chars applies; to the second one in
-         * many cases one of 10 (i.e. length of "/sbin/init") — however if we have CAP_SYS_RESOURCES it is unbounded;
-         * to the third one 7 (i.e. the length of "systemd". If you pass a longer string it will likely be
-         * truncated.
-         *
-         * Returns 0 if a name was set but truncated, > 0 if it was set but not truncated. */
-
-        if (isempty(name))
-                return -EINVAL; /* let's not confuse users unnecessarily with an empty name */
-
-        if (!is_main_thread())
-                return -EPERM; /* Let's not allow setting the process name from other threads than the main one, as we
-                                * cache things without locking, and we make assumptions that PR_SET_NAME sets the
-                                * process name that isn't correct on any other threads */
-
-        l = strlen(name);
-
-        /* First step, change the comm field. The main thread's comm is identical to the process comm. This means we
-         * 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 >= TASK_COMM_LEN) /* Linux userspace process names can be 15 chars at max */
-                truncated = true;
+static int update_argv(const char name[], size_t l) {
+        static int can_do = -1;
 
-        /* Second step, change glibc's ID of the process name. */
-        if (program_invocation_name) {
-                size_t k;
-
-                k = strlen(program_invocation_name);
-                strncpy(program_invocation_name, name, k);
-                if (l > k)
-                        truncated = true;
-        }
-
-        /* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but
-         * has the advantage that the argv[] array is exactly what we want it to be, and not filled up with zeros at
-         * the end. This is the best option for changing /proc/self/cmdline. */
+        if (can_do == 0)
+                return 0;
+        can_do = false; /* We'll set it to true only if the whole process works */
 
         /* Let's not bother with this if we don't have euid == 0. Strictly speaking we should check for the
          * CAP_SYS_RESOURCE capability which is independent of the euid. In our own code the capability generally is
@@ -263,22 +225,29 @@ int rename_process(const char name[]) {
          * PR_SET_MM_ARG_{START,END} fails with EPERM later on anyway. After all geteuid() is dead cheap to call, but
          * mmap() is not. */
         if (geteuid() != 0)
-                log_debug("Skipping PR_SET_MM, as we don't have privileges.");
-        else if (mm_size < l+1) {
+                return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
+                                       "Skipping PR_SET_MM, as we don't have privileges.");
+
+        static size_t mm_size = 0;
+        static char *mm = NULL;
+        int r;
+
+        if (mm_size < l+1) {
                 size_t nn_size;
                 char *nn;
 
                 nn_size = PAGE_ALIGN(l+1);
                 nn = mmap(NULL, nn_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
-                if (nn == MAP_FAILED) {
-                        log_debug_errno(errno, "mmap() failed: %m");
-                        goto use_saved_argv;
-                }
+                if (nn == MAP_FAILED)
+                        return log_debug_errno(errno, "mmap() failed: %m");
 
                 strncpy(nn, name, nn_size);
 
                 /* Now, let's tell the kernel about this new memory */
                 if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
+                        if (ERRNO_IS_PRIVILEGE(errno))
+                                return log_debug_errno(errno, "PR_SET_MM_ARG_START failed: %m");
+
                         /* HACK: prctl() API is kind of dumb on this point.  The existing end address may already be
                          * below the desired start address, in which case the kernel may have kicked this back due
                          * to a range-check failure (see linux/kernel/sys.c:validate_prctl_map() to see this in
@@ -290,15 +259,13 @@ int rename_process(const char name[]) {
                         log_debug_errno(errno, "PR_SET_MM_ARG_START failed, attempting PR_SET_MM_ARG_END hack: %m");
 
                         if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0) {
-                                log_debug_errno(errno, "PR_SET_MM_ARG_END hack failed, proceeding without: %m");
+                                r = log_debug_errno(errno, "PR_SET_MM_ARG_END hack failed, proceeding without: %m");
                                 (void) munmap(nn, nn_size);
-                                goto use_saved_argv;
+                                return r;
                         }
 
-                        if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
-                                log_debug_errno(errno, "PR_SET_MM_ARG_START still failed, proceeding without: %m");
-                                goto use_saved_argv;
-                        }
+                        if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0)
+                                return log_debug_errno(errno, "PR_SET_MM_ARG_START still failed, proceeding without: %m");
                 } else {
                         /* And update the end pointer to the new end, too. If this fails, we don't really know what
                          * to do, it's pretty unlikely that we can rollback, hence we'll just accept the failure,
@@ -320,13 +287,56 @@ int rename_process(const char name[]) {
                         log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
         }
 
-use_saved_argv:
+        can_do = true;
+        return 0;
+}
+
+int rename_process(const char name[]) {
+        bool truncated = false;
+
+        /* This is a like a poor man's setproctitle(). It changes the comm field, argv[0], and also the glibc's
+         * internally used name of the process. For the first one a limit of 16 chars applies; to the second one in
+         * many cases one of 10 (i.e. length of "/sbin/init") — however if we have CAP_SYS_RESOURCES it is unbounded;
+         * to the third one 7 (i.e. the length of "systemd". If you pass a longer string it will likely be
+         * truncated.
+         *
+         * Returns 0 if a name was set but truncated, > 0 if it was set but not truncated. */
+
+        if (isempty(name))
+                return -EINVAL; /* let's not confuse users unnecessarily with an empty name */
+
+        if (!is_main_thread())
+                return -EPERM; /* Let's not allow setting the process name from other threads than the main one, as we
+                                * cache things without locking, and we make assumptions that PR_SET_NAME sets the
+                                * process name that isn't correct on any other threads */
+
+        size_t l = strlen(name);
+
+        /* First step, change the comm field. The main thread's comm is identical to the process comm. This means we
+         * 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 >= TASK_COMM_LEN) /* Linux userspace process names can be 15 chars at max */
+                truncated = true;
+
+        /* Second step, change glibc's ID of the process name. */
+        if (program_invocation_name) {
+                size_t k;
+
+                k = strlen(program_invocation_name);
+                strncpy(program_invocation_name, name, k);
+                if (l > k)
+                        truncated = true;
+        }
+
+        /* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but
+         * has the advantage that the argv[] array is exactly what we want it to be, and not filled up with zeros at
+         * the end. This is the best option for changing /proc/self/cmdline. */
+        (void) update_argv(name, l);
+
         /* Fourth step: in all cases we'll also update the original argv[], so that our own code gets it right too if
          * it still looks here */
-
         if (saved_argc > 0) {
-                int i;
-
                 if (saved_argv[0]) {
                         size_t k;
 
@@ -336,7 +346,7 @@ use_saved_argv:
                                 truncated = true;
                 }
 
-                for (i = 1; i < saved_argc; i++) {
+                for (int i = 1; i < saved_argc; i++) {
                         if (!saved_argv[i])
                                 break;
 
@@ -634,6 +644,23 @@ int get_process_ppid(pid_t pid, pid_t *_ppid) {
         return 0;
 }
 
+int get_process_umask(pid_t pid, mode_t *umask) {
+        _cleanup_free_ char *m = NULL;
+        const char *p;
+        int r;
+
+        assert(umask);
+        assert(pid >= 0);
+
+        p = procfs_file_alloca(pid, "status");
+
+        r = get_proc_field(p, "Umask", WHITESPACE, &m);
+        if (r == -ENOENT)
+                return -ESRCH;
+
+        return parse_mode(m, umask);
+}
+
 int wait_for_terminate(pid_t pid, siginfo_t *status) {
         siginfo_t dummy;
 
@@ -1284,8 +1311,8 @@ int safe_fork_full(
                                        r, "Failed to rename process, ignoring: %m");
         }
 
-        if (flags & FORK_DEATHSIG)
-                if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) {
+        if (flags & (FORK_DEATHSIG|FORK_DEATHSIG_SIGINT))
+                if (prctl(PR_SET_PDEATHSIG, (flags & FORK_DEATHSIG_SIGINT) ? SIGINT : SIGTERM) < 0) {
                         log_full_errno(prio, errno, "Failed to set death signal: %m");
                         _exit(EXIT_FAILURE);
                 }
diff --git a/shared/systemd/src/basic/process-util.h b/shared/systemd/src/basic/process-util.h
index 7b70c9f3..05012377 100644
--- a/shared/systemd/src/basic/process-util.h
+++ b/shared/systemd/src/basic/process-util.h
@@ -45,6 +45,7 @@ int get_process_cwd(pid_t pid, char **cwd);
 int get_process_root(pid_t pid, char **root);
 int get_process_environ(pid_t pid, char **environ);
 int get_process_ppid(pid_t pid, pid_t *ppid);
+int get_process_umask(pid_t pid, mode_t *umask);
 
 int wait_for_terminate(pid_t pid, siginfo_t *status);
 
@@ -151,15 +152,16 @@ int must_be_root(void);
 typedef enum ForkFlags {
         FORK_RESET_SIGNALS      = 1 <<  0, /* Reset all signal handlers and signal mask */
         FORK_CLOSE_ALL_FDS      = 1 <<  1, /* Close all open file descriptors in the child, except for 0,1,2 */
-        FORK_DEATHSIG           = 1 <<  2, /* Set PR_DEATHSIG in the child */
-        FORK_NULL_STDIO         = 1 <<  3, /* Connect 0,1,2 to /dev/null */
-        FORK_REOPEN_LOG         = 1 <<  4, /* Reopen log connection */
-        FORK_LOG                = 1 <<  5, /* Log above LOG_DEBUG log level about failures */
-        FORK_WAIT               = 1 <<  6, /* Wait until child exited */
-        FORK_NEW_MOUNTNS        = 1 <<  7, /* Run child in its own mount namespace */
-        FORK_MOUNTNS_SLAVE      = 1 <<  8, /* Make child's mount namespace MS_SLAVE */
-        FORK_RLIMIT_NOFILE_SAFE = 1 <<  9, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */
-        FORK_STDOUT_TO_STDERR   = 1 << 10, /* Make stdout a copy of stderr */
+        FORK_DEATHSIG           = 1 <<  2, /* Set PR_DEATHSIG in the child to SIGTERM */
+        FORK_DEATHSIG_SIGINT    = 1 <<  3, /* Set PR_DEATHSIG in the child to SIGINT */
+        FORK_NULL_STDIO         = 1 <<  4, /* Connect 0,1,2 to /dev/null */
+        FORK_REOPEN_LOG         = 1 <<  5, /* Reopen log connection */
+        FORK_LOG                = 1 <<  6, /* Log above LOG_DEBUG log level about failures */
+        FORK_WAIT               = 1 <<  7, /* Wait until child exited */
+        FORK_NEW_MOUNTNS        = 1 <<  8, /* Run child in its own mount namespace */
+        FORK_MOUNTNS_SLAVE      = 1 <<  9, /* Make child's mount namespace MS_SLAVE */
+        FORK_RLIMIT_NOFILE_SAFE = 1 << 10, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */
+        FORK_STDOUT_TO_STDERR   = 1 << 11, /* Make stdout a copy of stderr */
 } 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/shared/systemd/src/basic/random-util.c b/shared/systemd/src/basic/random-util.c
index 1a294944..512e7af9 100644
--- a/shared/systemd/src/basic/random-util.c
+++ b/shared/systemd/src/basic/random-util.c
@@ -21,6 +21,7 @@
 #endif
 
 #include "alloc-util.h"
+#include "errno-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "io-util.h"
@@ -215,7 +216,9 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) {
                         r = -1;
                         errno = ENOSYS;
 #else
-                        r = getrandom(p, n, FLAGS_SET(flags, RANDOM_BLOCK) ? 0 : GRND_NONBLOCK);
+                        r = getrandom(p, n,
+                                      (FLAGS_SET(flags, RANDOM_BLOCK) ? 0 : GRND_NONBLOCK) |
+                                      (FLAGS_SET(flags, RANDOM_ALLOW_INSECURE) ? GRND_INSECURE : 0));
 #endif
                         if (r > 0) {
                                 have_syscall = true;
@@ -246,7 +249,7 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) {
                                 have_syscall = true;
                                 return -EIO;
 
-                        } else if (errno == ENOSYS) {
+                        } else if (ERRNO_IS_NOT_SUPPORTED(errno)) {
                                 /* We lack the syscall, continue with reading from /dev/urandom. */
                                 have_syscall = false;
                                 break;
@@ -272,6 +275,18 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) {
 
                                 /* Use /dev/urandom instead */
                                 break;
+
+                        } else if (errno == EINVAL) {
+
+                                /* Most likely: unknown flag. We know that GRND_INSECURE might cause this,
+                                 * hence try without. */
+
+                                if (FLAGS_SET(flags, RANDOM_ALLOW_INSECURE)) {
+                                        flags = flags &~ RANDOM_ALLOW_INSECURE;
+                                        continue;
+                                }
+
+                                return -errno;
                         } else
                                 return -errno;
                 }
@@ -334,9 +349,11 @@ void initialize_srand(void) {
 
 /* INT_MAX gives us only 31 bits, so use 24 out of that. */
 #if RAND_MAX >= INT_MAX
+assert_cc(RAND_MAX >= 16777215);
 #  define RAND_STEP 3
 #else
-/* SHORT_INT_MAX or lower gives at most 15 bits, we just just 8 out of that. */
+/* SHORT_INT_MAX or lower gives at most 15 bits, we just use 8 out of that. */
+assert_cc(RAND_MAX >= 255);
 #  define RAND_STEP 1
 #endif
 
@@ -401,7 +418,7 @@ void random_bytes(void *p, size_t n) {
          * This function is hence not useful for generating UUIDs or cryptographic key material.
          */
 
-        if (genuine_random_bytes(p, n, RANDOM_EXTEND_WITH_PSEUDO|RANDOM_MAY_FAIL|RANDOM_ALLOW_RDRAND) >= 0)
+        if (genuine_random_bytes(p, n, RANDOM_EXTEND_WITH_PSEUDO|RANDOM_MAY_FAIL|RANDOM_ALLOW_RDRAND|RANDOM_ALLOW_INSECURE) >= 0)
                 return;
 
         /* If for some reason some user made /dev/urandom unavailable to us, or the kernel has no entropy, use a PRNG instead. */
diff --git a/shared/systemd/src/basic/random-util.h b/shared/systemd/src/basic/random-util.h
index facc11b9..d8e067d9 100644
--- a/shared/systemd/src/basic/random-util.h
+++ b/shared/systemd/src/basic/random-util.h
@@ -10,6 +10,7 @@ typedef enum RandomFlags {
         RANDOM_BLOCK              = 1 << 1, /* Rather block than return crap randomness (only if the kernel supports that) */
         RANDOM_MAY_FAIL           = 1 << 2, /* If we can't get any randomness at all, return early with -ENODATA */
         RANDOM_ALLOW_RDRAND       = 1 << 3, /* Allow usage of the CPU RNG */
+        RANDOM_ALLOW_INSECURE     = 1 << 4, /* Allow usage of GRND_INSECURE flag to kernel's getrandom() API */
 } RandomFlags;
 
 int genuine_random_bytes(void *p, size_t n, RandomFlags flags); /* returns "genuine" randomness, optionally filled up with pseudo random, if not enough is available */
diff --git a/shared/systemd/src/basic/set.h b/shared/systemd/src/basic/set.h
index 5f195617..621e83bf 100644
--- a/shared/systemd/src/basic/set.h
+++ b/shared/systemd/src/basic/set.h
@@ -5,40 +5,48 @@
 #include "hashmap.h"
 #include "macro.h"
 
-Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
-#define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS)
+#define set_free_and_replace(a, b)              \
+        ({                                      \
+                set_free(a);                    \
+                (a) = (b);                      \
+                (b) = NULL;                     \
+                0;                              \
+        })
+
+Set *_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
+#define set_new(ops) _set_new(ops HASHMAP_DEBUG_SRC_ARGS)
 
 static inline Set *set_free(Set *s) {
-        return (Set*) internal_hashmap_free(HASHMAP_BASE(s), NULL, NULL);
+        return (Set*) _hashmap_free(HASHMAP_BASE(s), NULL, NULL);
 }
 
 static inline Set *set_free_free(Set *s) {
-        return (Set*) internal_hashmap_free(HASHMAP_BASE(s), free, NULL);
+        return (Set*) _hashmap_free(HASHMAP_BASE(s), free, NULL);
 }
 
 /* no set_free_free_free */
 
 static inline Set *set_copy(Set *s) {
-        return (Set*) internal_hashmap_copy(HASHMAP_BASE(s));
+        return (Set*) _hashmap_copy(HASHMAP_BASE(s));
 }
 
-int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
-#define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS)
+int _set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
+#define set_ensure_allocated(h, ops) _set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS)
 
 int set_put(Set *s, const void *key);
 /* no set_update */
 /* no set_replace */
 static inline void *set_get(const Set *s, void *key) {
-        return internal_hashmap_get(HASHMAP_BASE((Set *) s), key);
+        return _hashmap_get(HASHMAP_BASE((Set *) s), key);
 }
 /* no set_get2 */
 
 static inline bool set_contains(const Set *s, const void *key) {
-        return internal_hashmap_contains(HASHMAP_BASE((Set *) s), key);
+        return _hashmap_contains(HASHMAP_BASE((Set *) s), key);
 }
 
 static inline void *set_remove(Set *s, const void *key) {
-        return internal_hashmap_remove(HASHMAP_BASE(s), key);
+        return _hashmap_remove(HASHMAP_BASE(s), key);
 }
 
 /* no set_remove2 */
@@ -48,19 +56,19 @@ int set_remove_and_put(Set *s, const void *old_key, const void *new_key);
 int set_merge(Set *s, Set *other);
 
 static inline int set_reserve(Set *h, unsigned entries_add) {
-        return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add);
+        return _hashmap_reserve(HASHMAP_BASE(h), entries_add);
 }
 
 static inline int set_move(Set *s, Set *other) {
-        return internal_hashmap_move(HASHMAP_BASE(s), HASHMAP_BASE(other));
+        return _hashmap_move(HASHMAP_BASE(s), HASHMAP_BASE(other));
 }
 
 static inline int set_move_one(Set *s, Set *other, const void *key) {
-        return internal_hashmap_move_one(HASHMAP_BASE(s), HASHMAP_BASE(other), key);
+        return _hashmap_move_one(HASHMAP_BASE(s), HASHMAP_BASE(other), key);
 }
 
 static inline unsigned set_size(const Set *s) {
-        return internal_hashmap_size(HASHMAP_BASE((Set *) s));
+        return _hashmap_size(HASHMAP_BASE((Set *) s));
 }
 
 static inline bool set_isempty(const Set *s) {
@@ -68,23 +76,23 @@ static inline bool set_isempty(const Set *s) {
 }
 
 static inline unsigned set_buckets(const Set *s) {
-        return internal_hashmap_buckets(HASHMAP_BASE((Set *) s));
+        return _hashmap_buckets(HASHMAP_BASE((Set *) s));
 }
 
 bool set_iterate(const Set *s, Iterator *i, void **value);
 
 static inline void set_clear(Set *s) {
-        internal_hashmap_clear(HASHMAP_BASE(s), NULL, NULL);
+        _hashmap_clear(HASHMAP_BASE(s), NULL, NULL);
 }
 
 static inline void set_clear_free(Set *s) {
-        internal_hashmap_clear(HASHMAP_BASE(s), free, NULL);
+        _hashmap_clear(HASHMAP_BASE(s), free, NULL);
 }
 
 /* no set_clear_free_free */
 
 static inline void *set_steal_first(Set *s) {
-        return internal_hashmap_first_key_and_value(HASHMAP_BASE(s), true, NULL);
+        return _hashmap_first_key_and_value(HASHMAP_BASE(s), true, NULL);
 }
 
 #define set_clear_with_destructor(_s, _f)               \
@@ -103,18 +111,18 @@ static inline void *set_steal_first(Set *s) {
 /* no set_first_key */
 
 static inline void *set_first(const Set *s) {
-        return internal_hashmap_first_key_and_value(HASHMAP_BASE((Set *) s), false, NULL);
+        return _hashmap_first_key_and_value(HASHMAP_BASE((Set *) s), false, NULL);
 }
 
 /* no set_next */
 
 static inline char **set_get_strv(Set *s) {
-        return internal_hashmap_get_strv(HASHMAP_BASE(s));
+        return _hashmap_get_strv(HASHMAP_BASE(s));
 }
 
 int set_consume(Set *s, void *value);
-int set_put_strdup(Set *s, const char *p);
-int set_put_strdupv(Set *s, char **l);
+int set_put_strdup(Set **s, const char *p);
+int set_put_strdupv(Set **s, char **l);
 int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags flags);
 
 #define SET_FOREACH(e, s, i) \
diff --git a/shared/systemd/src/basic/socket-util.c b/shared/systemd/src/basic/socket-util.c
index 452b9ced..9ced3a12 100644
--- a/shared/systemd/src/basic/socket-util.c
+++ b/shared/systemd/src/basic/socket-util.c
@@ -25,6 +25,7 @@
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-util.h"
+#include "io-util.h"
 #include "log.h"
 #include "macro.h"
 #include "memory-util.h"
@@ -823,10 +824,7 @@ ssize_t send_one_fd_iov_sa(
                 const struct sockaddr *sa, socklen_t len,
                 int flags) {
 
-        union {
-                struct cmsghdr cmsghdr;
-                uint8_t buf[CMSG_SPACE(sizeof(int))];
-        } control = {};
+        CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int))) control = {};
         struct msghdr mh = {
                 .msg_name = (struct sockaddr*) sa,
                 .msg_namelen = len,
@@ -855,8 +853,6 @@ ssize_t send_one_fd_iov_sa(
                 cmsg->cmsg_type = SCM_RIGHTS;
                 cmsg->cmsg_len = CMSG_LEN(sizeof(int));
                 memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
-
-                mh.msg_controllen = CMSG_SPACE(sizeof(int));
         }
         k = sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags);
         if (k < 0)
@@ -882,17 +878,14 @@ ssize_t receive_one_fd_iov(
                 int flags,
                 int *ret_fd) {
 
-        union {
-                struct cmsghdr cmsghdr;
-                uint8_t buf[CMSG_SPACE(sizeof(int))];
-        } control = {};
+        CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int))) control;
         struct msghdr mh = {
                 .msg_control = &control,
                 .msg_controllen = sizeof(control),
                 .msg_iov = iov,
                 .msg_iovlen = iovlen,
         };
-        struct cmsghdr *cmsg, *found = NULL;
+        struct cmsghdr *found;
         ssize_t k;
 
         assert(transport_fd >= 0);
@@ -906,26 +899,18 @@ ssize_t receive_one_fd_iov(
          * combination with send_one_fd().
          */
 
-        k = recvmsg(transport_fd, &mh, MSG_CMSG_CLOEXEC | flags);
+        k = recvmsg_safe(transport_fd, &mh, MSG_CMSG_CLOEXEC | flags);
         if (k < 0)
-                return (ssize_t) -errno;
+                return k;
 
-        CMSG_FOREACH(cmsg, &mh) {
-                if (cmsg->cmsg_level == SOL_SOCKET &&
-                    cmsg->cmsg_type == SCM_RIGHTS &&
-                    cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
-                        assert(!found);
-                        found = cmsg;
-                        break;
-                }
-        }
-
-        if (!found)
+        found = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, CMSG_LEN(sizeof(int)));
+        if (!found) {
                 cmsg_close_all(&mh);
 
-        /* If didn't receive an FD or any data, return an error. */
-        if (k == 0 && !found)
-                return -EIO;
+                /* If didn't receive an FD or any data, return an error. */
+                if (k == 0)
+                        return -EIO;
+        }
 
         if (found)
                 *ret_fd = *(int*) CMSG_DATA(found);
@@ -991,10 +976,6 @@ fallback:
 
 int flush_accept(int fd) {
 
-        struct pollfd pollfd = {
-                .fd = fd,
-                .events = POLLIN,
-        };
         int r, b;
         socklen_t l = sizeof(b);
 
@@ -1015,12 +996,12 @@ int flush_accept(int fd) {
         for (unsigned iteration = 0;; iteration++) {
                 int cfd;
 
-                r = poll(&pollfd, 1, 0);
+                r = fd_wait_for_event(fd, POLLIN, 0);
                 if (r < 0) {
-                        if (errno == EINTR)
+                        if (r == -EINTR)
                                 continue;
 
-                        return -errno;
+                        return r;
                 }
                 if (r == 0)
                         return 0;
@@ -1043,6 +1024,7 @@ int flush_accept(int fd) {
                 safe_close(cfd);
         }
 }
+#endif /* NM_IGNORED */
 
 struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length) {
         struct cmsghdr *cmsg;
@@ -1058,6 +1040,7 @@ struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t leng
         return NULL;
 }
 
+#if 0 /* NM_IGNORED */
 int socket_ioctl_fd(void) {
         int fd;
 
@@ -1179,3 +1162,46 @@ int socket_bind_to_ifindex(int fd, int ifindex) {
 
         return socket_bind_to_ifname(fd, ifname);
 }
+
+ssize_t recvmsg_safe(int sockfd, struct msghdr *msg, int flags) {
+        ssize_t n;
+
+        /* A wrapper around recvmsg() that checks for MSG_CTRUNC, and turns it into an error, in a reasonably
+         * safe way, closing any SCM_RIGHTS fds in the error path.
+         *
+         * Note that unlike our usual coding style this might modify *msg on failure. */
+
+        n = recvmsg(sockfd, msg, flags);
+        if (n < 0)
+                return -errno;
+
+        if (FLAGS_SET(msg->msg_flags, MSG_CTRUNC)) {
+                cmsg_close_all(msg);
+                return -EXFULL; /* a recognizable error code */
+        }
+
+        return n;
+}
+
+int socket_pass_pktinfo(int fd, bool b) {
+        int af;
+        socklen_t sl = sizeof(af);
+
+        if (getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &af, &sl) < 0)
+                return -errno;
+
+        switch (af) {
+
+        case AF_INET:
+                return setsockopt_int(fd, IPPROTO_IP, IP_PKTINFO, b);
+
+        case AF_INET6:
+                return setsockopt_int(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, b);
+
+        case AF_NETLINK:
+                return setsockopt_int(fd, SOL_NETLINK, NETLINK_PKTINFO, b);
+
+        default:
+                return -EAFNOSUPPORT;
+        }
+}
diff --git a/shared/systemd/src/basic/socket-util.h b/shared/systemd/src/basic/socket-util.h
index 2596c540..5e5cf731 100644
--- a/shared/systemd/src/basic/socket-util.h
+++ b/shared/systemd/src/basic/socket-util.h
@@ -160,6 +160,25 @@ int flush_accept(int fd);
 
 struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length);
 
+/* Type-safe, dereferencing version of cmsg_find() */
+#define CMSG_FIND_DATA(mh, level, type, ctype) \
+        ({                                                            \
+                struct cmsghdr *_found;                               \
+                _found = cmsg_find(mh, level, type, CMSG_LEN(sizeof(ctype))); \
+                (ctype*) (_found ? CMSG_DATA(_found) : NULL);         \
+        })
+
+/* Resolves to a type that can carry cmsghdr structures. Make sure things are properly aligned, i.e. the type
+ * itself is placed properly in memory and the size is also aligned to what's appropriate for "cmsghdr"
+ * structures. */
+#define CMSG_BUFFER_TYPE(size)                                          \
+        union {                                                         \
+                struct cmsghdr cmsghdr;                                 \
+                uint8_t buf[size];                                      \
+                uint8_t align_check[(size) >= CMSG_SPACE(0) &&          \
+                                    (size) == CMSG_ALIGN(size) ? 1 : -1]; \
+        }
+
 /*
  * Certain hardware address types (e.g Infiniband) do not fit into sll_addr
  * (8 bytes) and run over the structure. This macro returns the correct size that
@@ -201,3 +220,7 @@ static inline int setsockopt_int(int fd, int level, int optname, int value) {
 
 int socket_bind_to_ifname(int fd, const char *ifname);
 int socket_bind_to_ifindex(int fd, int ifindex);
+
+ssize_t recvmsg_safe(int sockfd, struct msghdr *msg, int flags);
+
+int socket_pass_pktinfo(int fd, bool b);
diff --git a/shared/systemd/src/basic/sort-util.h b/shared/systemd/src/basic/sort-util.h
index e029f864..a8dc3bb6 100644
--- a/shared/systemd/src/basic/sort-util.h
+++ b/shared/systemd/src/basic/sort-util.h
@@ -39,7 +39,7 @@ static inline void* bsearch_safe(const void *key, const void *base,
  * Normal qsort requires base to be nonnull. Here were require
  * that only if nmemb > 0.
  */
-static inline void qsort_safe(void *base, size_t nmemb, size_t size, __compar_fn_t compar) {
+static inline void _qsort_safe(void *base, size_t nmemb, size_t size, __compar_fn_t compar) {
         if (nmemb <= 1)
                 return;
 
@@ -52,7 +52,7 @@ static inline void qsort_safe(void *base, size_t nmemb, size_t size, __compar_fn
 #define typesafe_qsort(p, n, func)                                      \
         ({                                                              \
                 int (*_func_)(const typeof(p[0])*, const typeof(p[0])*) = func; \
-                qsort_safe((p), (n), sizeof((p)[0]), (__compar_fn_t) _func_); \
+                _qsort_safe((p), (n), sizeof((p)[0]), (__compar_fn_t) _func_); \
         })
 
 static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, __compar_d_fn_t compar, void *userdata) {
diff --git a/shared/systemd/src/basic/stat-util.c b/shared/systemd/src/basic/stat-util.c
index a895c1c3..5c51e13a 100644
--- a/shared/systemd/src/basic/stat-util.c
+++ b/shared/systemd/src/basic/stat-util.c
@@ -99,10 +99,10 @@ bool null_or_empty(struct stat *st) {
         if (S_ISREG(st->st_mode) && st->st_size <= 0)
                 return true;
 
-        /* We don't want to hardcode the major/minor of /dev/null,
-         * hence we do a simpler "is this a device node?" check. */
+        /* We don't want to hardcode the major/minor of /dev/null, hence we do a simpler "is this a character
+         * device node?" check. */
 
-        if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
+        if (S_ISCHR(st->st_mode))
                 return true;
 
         return false;
@@ -113,6 +113,10 @@ int null_or_empty_path(const char *fn) {
 
         assert(fn);
 
+        /* If we have the path, let's do an easy text comparison first. */
+        if (path_equal(fn, "/dev/null"))
+                return true;
+
         if (stat(fn, &st) < 0)
                 return -errno;
 
@@ -183,13 +187,12 @@ int fd_is_fs_type(int fd, statfs_f_type_t magic_value) {
 }
 
 int path_is_fs_type(const char *path, statfs_f_type_t magic_value) {
-        _cleanup_close_ int fd = -1;
+        struct statfs s;
 
-        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH);
-        if (fd < 0)
+        if (statfs(path, &s) < 0)
                 return -errno;
 
-        return fd_is_fs_type(fd, magic_value);
+        return is_fs_type(&s, magic_value);
 }
 
 bool is_temporary_fs(const struct statfs *s) {
@@ -384,4 +387,37 @@ int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret
 
         return 0;
 }
+
+int proc_mounted(void) {
+        int r;
+
+        /* A quick check of procfs is properly mounted */
+
+        r = path_is_fs_type("/proc/", PROC_SUPER_MAGIC);
+        if (r == -ENOENT) /* not mounted at all */
+                return false;
+
+        return r;
+}
+
+bool stat_inode_unmodified(const struct stat *a, const struct stat *b) {
+
+        /* Returns if the specified stat structures reference the same, unmodified inode. This check tries to
+         * be reasonably careful when detecting changes: we check both inode and mtime, to cater for file
+         * systems where mtimes are fixed to 0 (think: ostree/nixos type installations). We also check file
+         * size, backing device, inode type and if this refers to a device not the major/minor.
+         *
+         * Note that we don't care if file attributes such as ownership or access mode change, this here is
+         * about contents of the file. The purpose here is to detect file contents changes, and nothing
+         * else. */
+
+        return a && b &&
+                (a->st_mode & S_IFMT) != 0 && /* We use the check for .st_mode if the structure was ever initialized */
+                ((a->st_mode ^ b->st_mode) & S_IFMT) == 0 &&  /* same inode type */
+                a->st_mtime == b->st_mtime &&
+                (!S_ISREG(a->st_mode) || a->st_size == b->st_size) && /* if regular file, compare file size */
+                a->st_dev == b->st_dev &&
+                a->st_ino == b->st_ino &&
+                (!(S_ISCHR(a->st_mode) || S_ISBLK(a->st_mode)) || a->st_rdev == b->st_rdev); /* if device node, also compare major/minor, because we can */
+}
 #endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/stat-util.h b/shared/systemd/src/basic/stat-util.h
index 7824af35..59aedcb7 100644
--- a/shared/systemd/src/basic/stat-util.h
+++ b/shared/systemd/src/basic/stat-util.h
@@ -87,3 +87,7 @@ int fd_verify_directory(int fd);
 int device_path_make_major_minor(mode_t mode, dev_t devno, char **ret);
 int device_path_make_canonical(mode_t mode, dev_t devno, char **ret);
 int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devno);
+
+int proc_mounted(void);
+
+bool stat_inode_unmodified(const struct stat *a, const struct stat *b);
diff --git a/shared/systemd/src/basic/string-util.c b/shared/systemd/src/basic/string-util.c
index 9f15caca..c30e88c5 100644
--- a/shared/systemd/src/basic/string-util.c
+++ b/shared/systemd/src/basic/string-util.c
@@ -21,18 +21,19 @@
 #include "util.h"
 
 int strcmp_ptr(const char *a, const char *b) {
-
         /* Like strcmp(), but tries to make sense of NULL pointers */
+
         if (a && b)
                 return strcmp(a, b);
+        return CMP(a, b); /* Direct comparison of pointers, one of which is NULL */
+}
 
-        if (!a && b)
-                return -1;
-
-        if (a && !b)
-                return 1;
+int strcasecmp_ptr(const char *a, const char *b) {
+        /* Like strcasecmp(), but tries to make sense of NULL pointers */
 
-        return 0;
+        if (a && b)
+                return strcasecmp(a, b);
+        return CMP(a, b); /* Direct comparison of pointers, one of which is NULL */
 }
 
 char* endswith(const char *s, const char *postfix) {
@@ -219,7 +220,6 @@ char *strnappend(const char *s, const char *suffix, size_t b) {
         return r;
 }
 
-#if 0 /* NM_IGNORED */
 char *strjoin_real(const char *x, ...) {
         va_list ap;
         size_t l;
@@ -277,6 +277,7 @@ char *strjoin_real(const char *x, ...) {
         return r;
 }
 
+#if 0 /* NM_IGNORED */
 char *strstrip(char *s) {
         if (!s)
                 return NULL;
diff --git a/shared/systemd/src/basic/string-util.h b/shared/systemd/src/basic/string-util.h
index 2a344b99..09131455 100644
--- a/shared/systemd/src/basic/string-util.h
+++ b/shared/systemd/src/basic/string-util.h
@@ -27,6 +27,7 @@
 #define strncaseeq(a, b, n) (strncasecmp((a), (b), (n)) == 0)
 
 int strcmp_ptr(const char *a, const char *b) _pure_;
+int strcasecmp_ptr(const char *a, const char *b) _pure_;
 
 static inline bool streq_ptr(const char *a, const char *b) {
         return strcmp_ptr(a, b) == 0;
diff --git a/shared/systemd/src/basic/strv.c b/shared/systemd/src/basic/strv.c
index be1c8325..6f812139 100644
--- a/shared/systemd/src/basic/strv.c
+++ b/shared/systemd/src/basic/strv.c
@@ -30,6 +30,18 @@ char *strv_find(char * const *l, const char *name) {
         return NULL;
 }
 
+char *strv_find_case(char * const *l, const char *name) {
+        char * const *i;
+
+        assert(name);
+
+        STRV_FOREACH(i, l)
+                if (strcaseeq(*i, name))
+                        return *i;
+
+        return NULL;
+}
+
 char *strv_find_prefix(char * const *l, const char *name) {
         char * const *i;
 
@@ -944,20 +956,20 @@ static int string_strv_hashmap_put_internal(Hashmap *h, const char *key, const c
         return 1;
 }
 
-int string_strv_hashmap_put(Hashmap **h, const char *key, const char *value) {
+int _string_strv_hashmap_put(Hashmap **h, const char *key, const char *value  HASHMAP_DEBUG_PARAMS) {
         int r;
 
-        r = hashmap_ensure_allocated(h, &string_strv_hash_ops);
+        r = _hashmap_ensure_allocated(h, &string_strv_hash_ops  HASHMAP_DEBUG_PASS_ARGS);
         if (r < 0)
                 return r;
 
         return string_strv_hashmap_put_internal(*h, key, value);
 }
 
-int string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value) {
+int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value  HASHMAP_DEBUG_PARAMS) {
         int r;
 
-        r = ordered_hashmap_ensure_allocated(h, &string_strv_hash_ops);
+        r = _ordered_hashmap_ensure_allocated(h, &string_strv_hash_ops  HASHMAP_DEBUG_PASS_ARGS);
         if (r < 0)
                 return r;
 
diff --git a/shared/systemd/src/basic/strv.h b/shared/systemd/src/basic/strv.h
index dd3323c2..2ad927bc 100644
--- a/shared/systemd/src/basic/strv.h
+++ b/shared/systemd/src/basic/strv.h
@@ -14,9 +14,13 @@
 #include "string-util.h"
 
 char *strv_find(char * const *l, const char *name) _pure_;
+char *strv_find_case(char * const *l, const char *name) _pure_;
 char *strv_find_prefix(char * const *l, const char *name) _pure_;
 char *strv_find_startswith(char * const *l, const char *name) _pure_;
 
+#define strv_contains(l, s) (!!strv_find((l), (s)))
+#define strv_contains_case(l, s) (!!strv_find_case((l), (s)))
+
 char **strv_free(char **l);
 DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free);
 #define _cleanup_strv_free_ _cleanup_(strv_freep)
@@ -54,8 +58,6 @@ static inline bool strv_equal(char * const *a, char * const *b) {
         return strv_compare(a, b) == 0;
 }
 
-#define strv_contains(l, s) (!!strv_find((l), (s)))
-
 char **strv_new_internal(const char *x, ...) _sentinel_;
 char **strv_new_ap(const char *x, va_list ap);
 #define strv_new(...) strv_new_internal(__VA_ARGS__, NULL)
@@ -104,14 +106,14 @@ bool strv_overlap(char * const *a, char * const *b) _pure_;
 
 #define STRV_FOREACH_BACKWARDS(s, l)                                \
         for (s = ({                                                 \
-                        char **_l = l;                              \
+                        typeof(l) _l = l;                           \
                         _l ? _l + strv_length(_l) - 1U : NULL;      \
                         });                                         \
              (l) && ((s) >= (l));                                   \
              (s)--)
 
 #define STRV_FOREACH_PAIR(x, y, l)               \
-        for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1))
+        for ((x) = (l), (y) = (x) ? (x+1) : NULL; (x) && *(x) && *(y); (x) += 2, (y) = (x + 1))
 
 char **strv_sort(char **l);
 void strv_print(char * const *l);
@@ -156,6 +158,13 @@ void strv_print(char * const *l);
                 _x && strv_contains(STRV_MAKE(__VA_ARGS__), _x); \
         })
 
+#define STRCASE_IN_SET(x, ...) strv_contains_case(STRV_MAKE(__VA_ARGS__), x)
+#define STRCASEPTR_IN_SET(x, ...)                                    \
+        ({                                                       \
+                const char* _x = (x);                            \
+                _x && strv_contains_case(STRV_MAKE(__VA_ARGS__), _x); \
+        })
+
 #define STARTSWITH_SET(p, ...)                                  \
         ({                                                      \
                 const char *_p = (p);                           \
@@ -217,5 +226,7 @@ int fputstrv(FILE *f, char * const *l, const char *separator, bool *space);
         })
 
 extern const struct hash_ops string_strv_hash_ops;
-int string_strv_hashmap_put(Hashmap **h, const char *key, const char *value);
-int string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value);
+int _string_strv_hashmap_put(Hashmap **h, const char *key, const char *value  HASHMAP_DEBUG_PARAMS);
+int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value  HASHMAP_DEBUG_PARAMS);
+#define string_strv_hashmap_put(h, k, v) _string_strv_hashmap_put(h, k, v  HASHMAP_DEBUG_SRC_ARGS)
+#define string_strv_ordered_hashmap_put(h, k, v) _string_strv_ordered_hashmap_put(h, k, v  HASHMAP_DEBUG_SRC_ARGS)
diff --git a/shared/systemd/src/basic/utf8.c b/shared/systemd/src/basic/utf8.c
index ba28e129..f2e27b68 100644
--- a/shared/systemd/src/basic/utf8.c
+++ b/shared/systemd/src/basic/utf8.c
@@ -50,7 +50,6 @@ bool unichar_is_valid(char32_t ch) {
         return true;
 }
 
-#if 0 /* NM_IGNORED */
 static bool unichar_is_control(char32_t ch) {
 
         /*
@@ -62,7 +61,6 @@ static bool unichar_is_control(char32_t ch) {
         return (ch < ' ' && !IN_SET(ch, '\t', '\n')) ||
                 (0x7F <= ch && ch <= 0x9F);
 }
-#endif /* NM_IGNORED */
 
 /* count of characters used to encode one unicode char */
 static size_t utf8_encoded_expected_len(uint8_t c) {
@@ -127,7 +125,6 @@ int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar) {
         return 0;
 }
 
-#if 0 /* NM_IGNORED */
 bool utf8_is_printable_newline(const char* str, size_t length, bool newline) {
         const char *p;
 
@@ -154,7 +151,6 @@ bool utf8_is_printable_newline(const char* str, size_t length, bool newline) {
 
         return true;
 }
-#endif /* NM_IGNORED */
 
 char *utf8_is_valid(const char *str) {
         const char *p;
diff --git a/shared/systemd/src/shared/web-util.c b/shared/systemd/src/shared/web-util.c
index f5a5362b..4cff5e27 100644
--- a/shared/systemd/src/shared/web-util.c
+++ b/shared/systemd/src/shared/web-util.c
@@ -24,21 +24,25 @@ bool http_etag_is_valid(const char *etag) {
 }
 #endif /* NM_IGNORED */
 
-bool http_url_is_valid(const char *url) {
-        const char *p;
-
-        if (isempty(url))
-                return false;
-
-        p = STARTSWITH_SET(url, "http://", "https://");
-        if (!p)
-                return false;
-
-        if (isempty(p))
-                return false;
-
-        return ascii_is_valid(p);
-}
+/* NM: we use http_url_is_valid() for our own code, and it must not
+ * change behavior. If a re-import results in a merge-conflict, you must
+ * ensure that it does not change behavior, and possibly do something
+ * about that. */
+/**/   bool http_url_is_valid(const char *url) {
+/**/           const char *p;
+/**/
+/**/           if (isempty(url))
+/**/                   return false;
+/**/
+/**/           p = STARTSWITH_SET(url, "http://", "https://");
+/**/           if (!p)
+/**/                   return false;
+/**/
+/**/           if (isempty(p))
+/**/                   return false;
+/**/
+/**/           return ascii_is_valid(p);
+/**/   }
 
 #if 0 /* NM_IGNORED */
 bool documentation_url_is_valid(const char *url) {