diff options
| author | Michael Biebl <biebl@debian.org> | 2018-09-08 17:44:06 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2018-09-08 17:44:06 +0200 |
| commit | 8f7a3cbbdd0c0a48277c341dd3a8ec8743ae9735 (patch) | |
| tree | 4353551fcb59cc822c3cadf2f4888f70601e8fbf /shared | |
| parent | caf1db9d6fbc056cc6c76a24574890f6c7895f3d (diff) | |
New upstream version 1.13.90 upstream/1.13.90
Diffstat (limited to 'shared')
26 files changed, 2921 insertions, 370 deletions
diff --git a/shared/c-list/src/c-list.h b/shared/c-list/src/c-list.h index ff434d8d..3d44e330 100644 --- a/shared/c-list/src/c-list.h +++ b/shared/c-list/src/c-list.h @@ -367,6 +367,25 @@ static inline CList *c_list_last(CList *list) { _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) \ /** + * c_list_flush() - flush all entries from a list + * @list: list to flush + * + * This unlinks all entries from the given list @list and reinitializes their + * link-nodes via C_LIST_INIT(). + * + * Note that the entries are not modified in any other way, nor is their memory + * released. This function just unlinks them and resets all the list nodes. It + * is particularly useful with temporary lists on the stack in combination with + * the GCC-extension __attribute__((__cleanup__(arg))). + */ +static inline void c_list_flush(CList *list) { + CList *iter, *safe; + + c_list_for_each_safe_unlink(iter, safe, list) + /* empty */ ; +} + +/** * c_list_length() - return number of linked entries, excluding the head * @list: list to operate on * diff --git a/shared/meson.build b/shared/meson.build index db6b8a40..e1cf620b 100644 --- a/shared/meson.build +++ b/shared/meson.build @@ -36,7 +36,9 @@ version_header = configure_file( configuration: version_conf, ) -shared_nm_utils_nm_meta_setting_c = files('nm-meta-setting.c') +shared_nm_ethtool_utils_c = files('nm-ethtool-utils.c') + +shared_nm_meta_setting_c = files('nm-meta-setting.c') shared_nm_test_utils_impl_c = files('nm-test-utils-impl.c') @@ -48,7 +50,9 @@ shared_files_libnm_core = files(''' nm-utils/nm-dedup-multi.c nm-utils/nm-enum-utils.c nm-utils/nm-hash-utils.c + nm-utils/nm-io-utils.c nm-utils/nm-random-utils.c + nm-utils/nm-secret-utils.c nm-utils/nm-shared-utils.c nm-utils/nm-udev-utils.c '''.split()) diff --git a/shared/nm-default.h b/shared/nm-default.h index b9be4768..8bcc5c70 100644 --- a/shared/nm-default.h +++ b/shared/nm-default.h @@ -112,6 +112,8 @@ #include <config.h> #endif +#include "config-extra.h" + /* for internal compilation we don't want the deprecation macros * to be in effect. Define the widest range of versions to effectively * disable deprecation checks */ diff --git a/shared/nm-ethtool-utils.c b/shared/nm-ethtool-utils.c new file mode 100644 index 00000000..d50695ae --- /dev/null +++ b/shared/nm-ethtool-utils.c @@ -0,0 +1,225 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ + +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * Copyright 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, +}; + +const guint8 const _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-ethtool-utils.h b/shared/nm-ethtool-utils.h new file mode 100644 index 00000000..5f22a9a0 --- /dev/null +++ b/shared/nm-ethtool-utils.h @@ -0,0 +1,120 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * Copyright 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 + NULL-terminated*/]; + +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-meta-setting.c b/shared/nm-meta-setting.c index 88412739..3e79747f 100644 --- a/shared/nm-meta-setting.c +++ b/shared/nm-meta-setting.c @@ -16,13 +16,14 @@ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * - * Copyright 2017 Red Hat, Inc. + * Copyright 2017 - 2018 Red Hat, Inc. */ #include "nm-default.h" #include "nm-meta-setting.h" +#include "nm-setting-6lowpan.h" #include "nm-setting-8021x.h" #include "nm-setting-adsl.h" #include "nm-setting-bluetooth.h" @@ -33,6 +34,7 @@ #include "nm-setting-connection.h" #include "nm-setting-dcb.h" #include "nm-setting-dummy.h" +#include "nm-setting-ethtool.h" #include "nm-setting-generic.h" #include "nm-setting-gsm.h" #include "nm-setting-infiniband.h" @@ -42,6 +44,7 @@ #include "nm-setting-ip-tunnel.h" #include "nm-setting-macsec.h" #include "nm-setting-macvlan.h" +#include "nm-setting-match.h" #include "nm-setting-olpc-mesh.h" #include "nm-setting-ovs-bridge.h" #include "nm-setting-ovs-interface.h" @@ -63,6 +66,7 @@ #include "nm-setting-wired.h" #include "nm-setting-wireless.h" #include "nm-setting-wireless-security.h" +#include "nm-setting-wpan.h" /*****************************************************************************/ @@ -145,201 +149,270 @@ const NMSetting8021xSchemeVtable nm_setting_8021x_scheme_vtable[] = { /*****************************************************************************/ const NMMetaSettingInfo nm_meta_setting_infos[] = { + [NM_META_SETTING_TYPE_6LOWPAN] = { + .meta_type = NM_META_SETTING_TYPE_6LOWPAN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_6LOWPAN_SETTING_NAME, + .get_setting_gtype = nm_setting_6lowpan_get_type, + }, [NM_META_SETTING_TYPE_802_1X] = { .meta_type = NM_META_SETTING_TYPE_802_1X, + .setting_priority = NM_SETTING_PRIORITY_HW_AUX, .setting_name = NM_SETTING_802_1X_SETTING_NAME, .get_setting_gtype = nm_setting_802_1x_get_type, }, [NM_META_SETTING_TYPE_ADSL] = { .meta_type = NM_META_SETTING_TYPE_ADSL, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_ADSL_SETTING_NAME, .get_setting_gtype = nm_setting_adsl_get_type, }, [NM_META_SETTING_TYPE_BLUETOOTH] = { .meta_type = NM_META_SETTING_TYPE_BLUETOOTH, + .setting_priority = NM_SETTING_PRIORITY_HW_NON_BASE, .setting_name = NM_SETTING_BLUETOOTH_SETTING_NAME, .get_setting_gtype = nm_setting_bluetooth_get_type, }, [NM_META_SETTING_TYPE_BOND] = { .meta_type = NM_META_SETTING_TYPE_BOND, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_BOND_SETTING_NAME, .get_setting_gtype = nm_setting_bond_get_type, }, [NM_META_SETTING_TYPE_BRIDGE] = { .meta_type = NM_META_SETTING_TYPE_BRIDGE, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_BRIDGE_SETTING_NAME, .get_setting_gtype = nm_setting_bridge_get_type, }, [NM_META_SETTING_TYPE_BRIDGE_PORT] = { .meta_type = NM_META_SETTING_TYPE_BRIDGE_PORT, + .setting_priority = NM_SETTING_PRIORITY_AUX, .setting_name = NM_SETTING_BRIDGE_PORT_SETTING_NAME, .get_setting_gtype = nm_setting_bridge_port_get_type, }, [NM_META_SETTING_TYPE_CDMA] = { .meta_type = NM_META_SETTING_TYPE_CDMA, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_CDMA_SETTING_NAME, .get_setting_gtype = nm_setting_cdma_get_type, }, [NM_META_SETTING_TYPE_CONNECTION] = { .meta_type = NM_META_SETTING_TYPE_CONNECTION, + .setting_priority = NM_SETTING_PRIORITY_CONNECTION, .setting_name = NM_SETTING_CONNECTION_SETTING_NAME, .get_setting_gtype = nm_setting_connection_get_type, }, [NM_META_SETTING_TYPE_DCB] = { .meta_type = NM_META_SETTING_TYPE_DCB, + .setting_priority = NM_SETTING_PRIORITY_HW_AUX, .setting_name = NM_SETTING_DCB_SETTING_NAME, .get_setting_gtype = nm_setting_dcb_get_type, }, [NM_META_SETTING_TYPE_DUMMY] = { .meta_type = NM_META_SETTING_TYPE_DUMMY, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_DUMMY_SETTING_NAME, .get_setting_gtype = nm_setting_dummy_get_type, }, + [NM_META_SETTING_TYPE_ETHTOOL] = { + .meta_type = NM_META_SETTING_TYPE_ETHTOOL, + .setting_priority = NM_SETTING_PRIORITY_AUX, + .setting_name = NM_SETTING_ETHTOOL_SETTING_NAME, + .get_setting_gtype = nm_setting_ethtool_get_type, + }, [NM_META_SETTING_TYPE_GENERIC] = { .meta_type = NM_META_SETTING_TYPE_GENERIC, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_GENERIC_SETTING_NAME, .get_setting_gtype = nm_setting_generic_get_type, }, [NM_META_SETTING_TYPE_GSM] = { .meta_type = NM_META_SETTING_TYPE_GSM, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_GSM_SETTING_NAME, .get_setting_gtype = nm_setting_gsm_get_type, }, [NM_META_SETTING_TYPE_INFINIBAND] = { .meta_type = NM_META_SETTING_TYPE_INFINIBAND, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_INFINIBAND_SETTING_NAME, .get_setting_gtype = nm_setting_infiniband_get_type, }, [NM_META_SETTING_TYPE_IP4_CONFIG] = { .meta_type = NM_META_SETTING_TYPE_IP4_CONFIG, + .setting_priority = NM_SETTING_PRIORITY_IP, .setting_name = NM_SETTING_IP4_CONFIG_SETTING_NAME, .get_setting_gtype = nm_setting_ip4_config_get_type, }, [NM_META_SETTING_TYPE_IP6_CONFIG] = { .meta_type = NM_META_SETTING_TYPE_IP6_CONFIG, + .setting_priority = NM_SETTING_PRIORITY_IP, .setting_name = NM_SETTING_IP6_CONFIG_SETTING_NAME, .get_setting_gtype = nm_setting_ip6_config_get_type, }, [NM_META_SETTING_TYPE_IP_TUNNEL] = { .meta_type = NM_META_SETTING_TYPE_IP_TUNNEL, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_IP_TUNNEL_SETTING_NAME, .get_setting_gtype = nm_setting_ip_tunnel_get_type, }, [NM_META_SETTING_TYPE_MACSEC] = { .meta_type = NM_META_SETTING_TYPE_MACSEC, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_MACSEC_SETTING_NAME, .get_setting_gtype = nm_setting_macsec_get_type, }, [NM_META_SETTING_TYPE_MACVLAN] = { .meta_type = NM_META_SETTING_TYPE_MACVLAN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_MACVLAN_SETTING_NAME, .get_setting_gtype = nm_setting_macvlan_get_type, }, + [NM_META_SETTING_TYPE_MATCH] = { + .meta_type = NM_META_SETTING_TYPE_MATCH, + .setting_priority = NM_SETTING_PRIORITY_AUX, + .setting_name = NM_SETTING_MATCH_SETTING_NAME, + .get_setting_gtype = nm_setting_match_get_type, + }, [NM_META_SETTING_TYPE_OLPC_MESH] = { .meta_type = NM_META_SETTING_TYPE_OLPC_MESH, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_OLPC_MESH_SETTING_NAME, .get_setting_gtype = nm_setting_olpc_mesh_get_type, }, [NM_META_SETTING_TYPE_OVS_BRIDGE] = { .meta_type = NM_META_SETTING_TYPE_OVS_BRIDGE, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_OVS_BRIDGE_SETTING_NAME, .get_setting_gtype = nm_setting_ovs_bridge_get_type, }, [NM_META_SETTING_TYPE_OVS_INTERFACE] = { .meta_type = NM_META_SETTING_TYPE_OVS_INTERFACE, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_OVS_INTERFACE_SETTING_NAME, .get_setting_gtype = nm_setting_ovs_interface_get_type, }, [NM_META_SETTING_TYPE_OVS_PATCH] = { .meta_type = NM_META_SETTING_TYPE_OVS_PATCH, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_OVS_PATCH_SETTING_NAME, .get_setting_gtype = nm_setting_ovs_patch_get_type, }, [NM_META_SETTING_TYPE_OVS_PORT] = { .meta_type = NM_META_SETTING_TYPE_OVS_PORT, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_OVS_PORT_SETTING_NAME, .get_setting_gtype = nm_setting_ovs_port_get_type, }, [NM_META_SETTING_TYPE_PPPOE] = { .meta_type = NM_META_SETTING_TYPE_PPPOE, + .setting_priority = NM_SETTING_PRIORITY_AUX, .setting_name = NM_SETTING_PPPOE_SETTING_NAME, .get_setting_gtype = nm_setting_pppoe_get_type, }, [NM_META_SETTING_TYPE_PPP] = { .meta_type = NM_META_SETTING_TYPE_PPP, + .setting_priority = NM_SETTING_PRIORITY_AUX, .setting_name = NM_SETTING_PPP_SETTING_NAME, .get_setting_gtype = nm_setting_ppp_get_type, }, [NM_META_SETTING_TYPE_PROXY] = { .meta_type = NM_META_SETTING_TYPE_PROXY, + .setting_priority = NM_SETTING_PRIORITY_IP, .setting_name = NM_SETTING_PROXY_SETTING_NAME, .get_setting_gtype = nm_setting_proxy_get_type, }, [NM_META_SETTING_TYPE_SERIAL] = { .meta_type = NM_META_SETTING_TYPE_SERIAL, + .setting_priority = NM_SETTING_PRIORITY_HW_AUX, .setting_name = NM_SETTING_SERIAL_SETTING_NAME, .get_setting_gtype = nm_setting_serial_get_type, }, + [NM_META_SETTING_TYPE_SRIOV] = { + .meta_type = NM_META_SETTING_TYPE_SRIOV, + .setting_priority = NM_SETTING_PRIORITY_HW_AUX, + .setting_name = NM_SETTING_SRIOV_SETTING_NAME, + .get_setting_gtype = nm_setting_sriov_get_type, + }, [NM_META_SETTING_TYPE_TC_CONFIG] = { .meta_type = NM_META_SETTING_TYPE_TC_CONFIG, + .setting_priority = NM_SETTING_PRIORITY_IP, .setting_name = NM_SETTING_TC_CONFIG_SETTING_NAME, .get_setting_gtype = nm_setting_tc_config_get_type, }, [NM_META_SETTING_TYPE_TEAM] = { .meta_type = NM_META_SETTING_TYPE_TEAM, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_TEAM_SETTING_NAME, .get_setting_gtype = nm_setting_team_get_type, }, [NM_META_SETTING_TYPE_TEAM_PORT] = { .meta_type = NM_META_SETTING_TYPE_TEAM_PORT, + .setting_priority = NM_SETTING_PRIORITY_AUX, .setting_name = NM_SETTING_TEAM_PORT_SETTING_NAME, .get_setting_gtype = nm_setting_team_port_get_type, }, [NM_META_SETTING_TYPE_TUN] = { .meta_type = NM_META_SETTING_TYPE_TUN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_TUN_SETTING_NAME, .get_setting_gtype = nm_setting_tun_get_type, }, [NM_META_SETTING_TYPE_USER] = { .meta_type = NM_META_SETTING_TYPE_USER, + .setting_priority = NM_SETTING_PRIORITY_USER, .setting_name = NM_SETTING_USER_SETTING_NAME, .get_setting_gtype = nm_setting_user_get_type, }, [NM_META_SETTING_TYPE_VLAN] = { .meta_type = NM_META_SETTING_TYPE_VLAN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_VLAN_SETTING_NAME, .get_setting_gtype = nm_setting_vlan_get_type, }, [NM_META_SETTING_TYPE_VPN] = { .meta_type = NM_META_SETTING_TYPE_VPN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_VPN_SETTING_NAME, .get_setting_gtype = nm_setting_vpn_get_type, }, [NM_META_SETTING_TYPE_VXLAN] = { .meta_type = NM_META_SETTING_TYPE_VXLAN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_VXLAN_SETTING_NAME, .get_setting_gtype = nm_setting_vxlan_get_type, }, [NM_META_SETTING_TYPE_WIMAX] = { .meta_type = NM_META_SETTING_TYPE_WIMAX, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_WIMAX_SETTING_NAME, .get_setting_gtype = nm_setting_wimax_get_type, }, [NM_META_SETTING_TYPE_WIRED] = { .meta_type = NM_META_SETTING_TYPE_WIRED, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_WIRED_SETTING_NAME, .get_setting_gtype = nm_setting_wired_get_type, }, [NM_META_SETTING_TYPE_WIRELESS] = { .meta_type = NM_META_SETTING_TYPE_WIRELESS, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, .setting_name = NM_SETTING_WIRELESS_SETTING_NAME, .get_setting_gtype = nm_setting_wireless_get_type, }, [NM_META_SETTING_TYPE_WIRELESS_SECURITY] = { .meta_type = NM_META_SETTING_TYPE_WIRELESS_SECURITY, + .setting_priority = NM_SETTING_PRIORITY_HW_AUX, .setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, .get_setting_gtype = nm_setting_wireless_security_get_type, }, + [NM_META_SETTING_TYPE_WPAN] = { + .meta_type = NM_META_SETTING_TYPE_WPAN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_WPAN_SETTING_NAME, + .get_setting_gtype = nm_setting_wpan_get_type, + }, [NM_META_SETTING_TYPE_UNKNOWN] = { .meta_type = NM_META_SETTING_TYPE_UNKNOWN, @@ -349,27 +422,99 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = { const NMMetaSettingInfo * nm_meta_setting_infos_by_name (const char *name) { - int i; + gssize idx; + +#if NM_MORE_ASSERTS > 10 + { + guint i, j; - if (name) { for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { - if (nm_streq (nm_meta_setting_infos[i].setting_name, name)) - return &nm_meta_setting_infos[i]; + const NMMetaSettingInfo *setting_info = &nm_meta_setting_infos[i]; + + nm_assert (setting_info->meta_type == (NMMetaSettingType) i); + nm_assert (setting_info->setting_name); + nm_assert (setting_info->setting_name[0]); + nm_assert (setting_info->get_setting_gtype); + nm_assert (setting_info->setting_priority != NM_SETTING_PRIORITY_INVALID); + if ( i > 0 + && strcmp (nm_meta_setting_infos[i - 1].setting_name, setting_info->setting_name) >= 0) { + g_error ("nm_meta_setting_infos[%u, \"%s\"] is wrongly sorted before nm_meta_setting_infos[%u, \"%s\"]. Rearange NMMetaSettingType enum", + i - 1, nm_meta_setting_infos[i - 1].setting_name, + i, setting_info->setting_name); + } + for (j = 0; j < i; j++) { + const NMMetaSettingInfo *s = &nm_meta_setting_infos[j]; + + nm_assert (setting_info->get_setting_gtype != s->get_setting_gtype); + } } } - return NULL; +#endif + + G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMMetaSettingInfo, setting_name) == 0); + idx = nm_utils_array_find_binary_search (nm_meta_setting_infos, + sizeof (NMMetaSettingInfo), + _NM_META_SETTING_TYPE_NUM, + &name, + nm_strcmp_p_with_data, + NULL); + + return idx >= 0 ? &nm_meta_setting_infos[idx] : NULL; } const NMMetaSettingInfo * nm_meta_setting_infos_by_gtype (GType gtype) { - int i; +#if ((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL) + nm_auto_unref_gtypeclass GTypeClass *gtypeclass_unref = NULL; + GTypeClass *gtypeclass; + NMSettingClass *klass; + + if (!g_type_is_a (gtype, NM_TYPE_SETTING)) + goto out_none; + + gtypeclass = g_type_class_peek (gtype); + if (!gtypeclass) + gtypeclass = gtypeclass_unref = g_type_class_ref (gtype); + + nm_assert (NM_IS_SETTING_CLASS (gtypeclass)); + + klass = (NMSettingClass *) gtypeclass; + + if (!klass->setting_info) + goto out_none; + + nm_assert (klass->setting_info->get_setting_gtype); + nm_assert (klass->setting_info->get_setting_gtype () == gtype); + + return klass->setting_info; + +out_none: + +#if NM_MORE_ASSERTS > 10 + { + int i; + + /* this might hint to a bug, but it would be expected for NM_TYPE_SETTING + * and NM_TYPE_SETTING_IP_CONFIG. + * + * Assert that we didn't lookup for a gtype, which we would expect to find. + * An assertion failure here, hints to a bug in nm_setting_*_class_init(). + */ + for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) + nm_assert (nm_meta_setting_infos[i].get_setting_gtype () != gtype); + } +#endif + return NULL; +#else + guint i; for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { if (nm_meta_setting_infos[i].get_setting_gtype () == gtype) return &nm_meta_setting_infos[i]; } return NULL; +#endif } /*****************************************************************************/ diff --git a/shared/nm-meta-setting.h b/shared/nm-meta-setting.h index 4333001b..26c29bea 100644 --- a/shared/nm-meta-setting.h +++ b/shared/nm-meta-setting.h @@ -16,7 +16,7 @@ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * - * Copyright 2017 Red Hat, Inc. + * Copyright 2017 - 2018 Red Hat, Inc. */ #ifndef __NM_META_SETTING_H__ @@ -26,6 +26,47 @@ /*****************************************************************************/ +/* + * A setting's priority should roughly follow the OSI layer model, but it also + * controls which settings get asked for secrets first. Thus settings which + * relate to things that must be working first, like hardware, should get a + * higher priority than things which layer on top of the hardware. For example, + * the GSM/CDMA settings should provide secrets before the PPP setting does, + * because a PIN is required to unlock the device before PPP can even start. + * Even settings without secrets should be assigned the right priority. + * + * 0: reserved for invalid + * + * 1: reserved for the Connection setting + * + * 2,3: hardware-related settings like Ethernet, Wi-Fi, InfiniBand, Bridge, etc. + * These priority 1 settings are also "base types", which means that at least + * one of them is required for the connection to be valid, and their name is + * valid in the 'type' property of the Connection setting. + * + * 4: hardware-related auxiliary settings that require a base setting to be + * successful first, like Wi-Fi security, 802.1x, etc. + * + * 5: hardware-independent settings that are required before IP connectivity + * can be established, like PPP, PPPoE, etc. + * + * 6: IP-level stuff + * + * 10: NMSettingUser + */ +typedef enum { /*< skip >*/ + NM_SETTING_PRIORITY_INVALID = 0, + NM_SETTING_PRIORITY_CONNECTION = 1, + NM_SETTING_PRIORITY_HW_BASE = 2, + NM_SETTING_PRIORITY_HW_NON_BASE = 3, + NM_SETTING_PRIORITY_HW_AUX = 4, + NM_SETTING_PRIORITY_AUX = 5, + NM_SETTING_PRIORITY_IP = 6, + NM_SETTING_PRIORITY_USER = 10, +} NMSettingPriority; + +/*****************************************************************************/ + typedef enum { NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT, NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT, @@ -56,7 +97,18 @@ extern const NMSetting8021xSchemeVtable nm_setting_8021x_scheme_vtable[_NM_SETTI /*****************************************************************************/ typedef enum { + /* the enum (and their numeric values) are internal API. Do not assign + * any meaning the numeric values, because they already have one: + * + * they are sorted in a way, that corresponds to the asciibetical sort + * order of the corresponding setting-name. */ + + NM_META_SETTING_TYPE_6LOWPAN, + NM_META_SETTING_TYPE_OLPC_MESH, + NM_META_SETTING_TYPE_WIRELESS, + NM_META_SETTING_TYPE_WIRELESS_SECURITY, NM_META_SETTING_TYPE_802_1X, + NM_META_SETTING_TYPE_WIRED, NM_META_SETTING_TYPE_ADSL, NM_META_SETTING_TYPE_BLUETOOTH, NM_META_SETTING_TYPE_BOND, @@ -66,15 +118,16 @@ typedef enum { NM_META_SETTING_TYPE_CONNECTION, NM_META_SETTING_TYPE_DCB, NM_META_SETTING_TYPE_DUMMY, + NM_META_SETTING_TYPE_ETHTOOL, NM_META_SETTING_TYPE_GENERIC, NM_META_SETTING_TYPE_GSM, NM_META_SETTING_TYPE_INFINIBAND, + NM_META_SETTING_TYPE_IP_TUNNEL, NM_META_SETTING_TYPE_IP4_CONFIG, NM_META_SETTING_TYPE_IP6_CONFIG, - NM_META_SETTING_TYPE_IP_TUNNEL, NM_META_SETTING_TYPE_MACSEC, NM_META_SETTING_TYPE_MACVLAN, - NM_META_SETTING_TYPE_OLPC_MESH, + NM_META_SETTING_TYPE_MATCH, NM_META_SETTING_TYPE_OVS_BRIDGE, NM_META_SETTING_TYPE_OVS_INTERFACE, NM_META_SETTING_TYPE_OVS_PATCH, @@ -83,6 +136,7 @@ typedef enum { NM_META_SETTING_TYPE_PPPOE, NM_META_SETTING_TYPE_PROXY, NM_META_SETTING_TYPE_SERIAL, + NM_META_SETTING_TYPE_SRIOV, NM_META_SETTING_TYPE_TC_CONFIG, NM_META_SETTING_TYPE_TEAM, NM_META_SETTING_TYPE_TEAM_PORT, @@ -92,21 +146,57 @@ typedef enum { NM_META_SETTING_TYPE_VPN, NM_META_SETTING_TYPE_VXLAN, NM_META_SETTING_TYPE_WIMAX, - NM_META_SETTING_TYPE_WIRED, - NM_META_SETTING_TYPE_WIRELESS, - NM_META_SETTING_TYPE_WIRELESS_SECURITY, + NM_META_SETTING_TYPE_WPAN, NM_META_SETTING_TYPE_UNKNOWN, _NM_META_SETTING_TYPE_NUM = NM_META_SETTING_TYPE_UNKNOWN, } NMMetaSettingType; -typedef struct { - NMMetaSettingType meta_type; +/* this header is statically linked with both libnm-core.la and libnmc.la. + * Though, there is no stable API/ABI, so whenever on of these components + * accesses NMMetaSettingInfo or NMMetaSettingType, it only has meaning + * inside the same component. + * + * Note how NMSettingClass has field of type "struct _NMMetaSettingInfo". + * It would be a serious bug, if libnmc tries to interpret this pointer + * with the meaning of NMMetaSettingInfo. They might be different, because + * libnm.so (libnm-core.la) might be a newer version than nmcli (libnmc.la). + * + * This define helps to ensure that we don't accidentally use the pointer + * in different contexts. */ +#if ((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL) +#define _NMMetaSettingInfoXX _NMMetaSettingInfo +#else +#define _NMMetaSettingInfoXX _NMMetaSettingInfoCli +#endif +struct _NMMetaSettingInfoXX { const char *setting_name; GType (*get_setting_gtype) (void); -} NMMetaSettingInfo; + NMMetaSettingType meta_type; + NMSettingPriority setting_priority; +}; +typedef struct _NMMetaSettingInfoXX NMMetaSettingInfo; + +/* note that we statically link nm-meta-setting.h both to libnm-core.la and + * libnmc.la. That means, there are two versions of nm_meta_setting_infos + * in nmcli. That is not easily avoidable, because at this point, we don't + * want yet to making it public API. + * + * Eventually, this should become public API of libnm, and nmcli/libnmc.la + * should use that version. + * + * Downsides of the current solution: + * + * - duplication of the array in nmcli. + * + * - there is no stable API/ABI. That means, when you have a NMMetaSettingInfo + * pointer, or a NMMetaSettingType value, the value can only be used within + * the current context (libnm-core.la or libnmc.la). In other words, libnmc.la + * (and nmcli) must never access a NMMetaSettingInfo/NMMetaSettingType value, + * that comes from libnm-core.la. + */ extern const NMMetaSettingInfo nm_meta_setting_infos[_NM_META_SETTING_TYPE_NUM + 1]; const NMMetaSettingInfo *nm_meta_setting_infos_by_name (const char *name); diff --git a/shared/nm-test-libnm-utils.h b/shared/nm-test-libnm-utils.h index 20a15e5f..2b4fa600 100644 --- a/shared/nm-test-libnm-utils.h +++ b/shared/nm-test-libnm-utils.h @@ -48,9 +48,11 @@ static inline void _nmtstc_auto_service_cleanup (NMTstcServiceInfo **info) { nmtstc_service_cleanup (g_steal_pointer (info)); } +#define nmtstc_auto_service_cleanup nm_auto(_nmtstc_auto_service_cleanup) + #define NMTSTC_SERVICE_INFO_SETUP(sinfo) \ NM_PRAGMA_WARNING_DISABLE ("-Wunused-variable") \ - __attribute__ ((cleanup(_nmtstc_auto_service_cleanup))) NMTstcServiceInfo *sinfo = ({ \ + nmtstc_auto_service_cleanup NMTstcServiceInfo *sinfo = ({ \ NMTstcServiceInfo *_sinfo; \ \ _sinfo = nmtstc_service_init (); \ diff --git a/shared/nm-test-utils-impl.c b/shared/nm-test-utils-impl.c index 9e7312ca..1da9014e 100644 --- a/shared/nm-test-utils-impl.c +++ b/shared/nm-test-utils-impl.c @@ -98,7 +98,7 @@ _service_init_wait_probe_name (gpointer user_data) static void _service_init_wait_child_wait (GPid pid, - gint status, + int status, gpointer user_data) { ServiceInitWaitData *data = user_data; diff --git a/shared/nm-utils/gsystem-local-alloc.h b/shared/nm-utils/gsystem-local-alloc.h deleted file mode 100644 index 51b62519..00000000 --- a/shared/nm-utils/gsystem-local-alloc.h +++ /dev/null @@ -1,208 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012 Colin Walters <walters@verbum.org>. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#ifndef __GSYSTEM_LOCAL_ALLOC_H__ -#define __GSYSTEM_LOCAL_ALLOC_H__ - -#include <gio/gio.h> - -G_BEGIN_DECLS - -#define GS_DEFINE_CLEANUP_FUNCTION(Type, name, func) \ - static inline void name (void *v) \ - { \ - func (*(Type*)v); \ - } - -#define GS_DEFINE_CLEANUP_FUNCTION0(Type, name, func) \ - static inline void name (void *v) \ - { \ - if (*(Type*)v) \ - func (*(Type*)v); \ - } - -/* These functions shouldn't be invoked directly; - * they are stubs that: - * 1) Take a pointer to the location (typically itself a pointer). - * 2) Provide %NULL-safety where it doesn't exist already (e.g. g_object_unref) - */ - -/** - * gs_free: - * - * Call g_free() on a variable location when it goes out of scope. - */ -#define gs_free __attribute__ ((cleanup(gs_local_free))) -GS_DEFINE_CLEANUP_FUNCTION(void*, gs_local_free, g_free) - -/** - * gs_unref_object: - * - * Call g_object_unref() on a variable location when it goes out of - * scope. Note that unlike g_object_unref(), the variable may be - * %NULL. - */ -#define gs_unref_object __attribute__ ((cleanup(gs_local_obj_unref))) -GS_DEFINE_CLEANUP_FUNCTION0(GObject*, gs_local_obj_unref, g_object_unref) - -/** - * gs_unref_variant: - * - * Call g_variant_unref() on a variable location when it goes out of - * scope. Note that unlike g_variant_unref(), the variable may be - * %NULL. - */ -#define gs_unref_variant __attribute__ ((cleanup(gs_local_variant_unref))) -GS_DEFINE_CLEANUP_FUNCTION0(GVariant*, gs_local_variant_unref, g_variant_unref) - -/** - * gs_free_variant_iter: - * - * Call g_variant_iter_free() on a variable location when it goes out of - * scope. - */ -#define gs_free_variant_iter __attribute__ ((cleanup(gs_local_variant_iter_free))) -GS_DEFINE_CLEANUP_FUNCTION0(GVariantIter*, gs_local_variant_iter_free, g_variant_iter_free) - -/** - * gs_free_variant_builder: - * - * Call g_variant_builder_unref() on a variable location when it goes out of - * scope. - */ -#define gs_unref_variant_builder __attribute__ ((cleanup(gs_local_variant_builder_unref))) -GS_DEFINE_CLEANUP_FUNCTION0(GVariantBuilder*, gs_local_variant_builder_unref, g_variant_builder_unref) - -/** - * gs_unref_array: - * - * Call g_array_unref() on a variable location when it goes out of - * scope. Note that unlike g_array_unref(), the variable may be - * %NULL. - - */ -#define gs_unref_array __attribute__ ((cleanup(gs_local_array_unref))) -GS_DEFINE_CLEANUP_FUNCTION0(GArray*, gs_local_array_unref, g_array_unref) - -/** - * gs_unref_ptrarray: - * - * Call g_ptr_array_unref() on a variable location when it goes out of - * scope. Note that unlike g_ptr_array_unref(), the variable may be - * %NULL. - - */ -#define gs_unref_ptrarray __attribute__ ((cleanup(gs_local_ptrarray_unref))) -GS_DEFINE_CLEANUP_FUNCTION0(GPtrArray*, gs_local_ptrarray_unref, g_ptr_array_unref) - -/** - * gs_unref_hashtable: - * - * Call g_hash_table_unref() on a variable location when it goes out - * of scope. Note that unlike g_hash_table_unref(), the variable may - * be %NULL. - */ -#define gs_unref_hashtable __attribute__ ((cleanup(gs_local_hashtable_unref))) -GS_DEFINE_CLEANUP_FUNCTION0(GHashTable*, gs_local_hashtable_unref, g_hash_table_unref) - -/** - * gs_free_list: - * - * Call g_list_free() on a variable location when it goes out - * of scope. - */ -#define gs_free_list __attribute__ ((cleanup(gs_local_free_list))) -GS_DEFINE_CLEANUP_FUNCTION(GList*, gs_local_free_list, g_list_free) - -/** - * gs_free_slist: - * - * Call g_slist_free() on a variable location when it goes out - * of scope. - */ -#define gs_free_slist __attribute__ ((cleanup(gs_local_free_slist))) -GS_DEFINE_CLEANUP_FUNCTION(GSList*, gs_local_free_slist, g_slist_free) - -/** - * gs_free_checksum: - * - * Call g_checksum_free() on a variable location when it goes out - * of scope. Note that unlike g_checksum_free(), the variable may - * be %NULL. - */ -#define gs_free_checksum __attribute__ ((cleanup(gs_local_checksum_free))) -GS_DEFINE_CLEANUP_FUNCTION0(GChecksum*, gs_local_checksum_free, g_checksum_free) - -/** - * gs_unref_bytes: - * - * Call g_bytes_unref() on a variable location when it goes out - * of scope. Note that unlike g_bytes_unref(), the variable may - * be %NULL. - */ -#define gs_unref_bytes __attribute__ ((cleanup(gs_local_bytes_unref))) -GS_DEFINE_CLEANUP_FUNCTION0(GBytes*, gs_local_bytes_unref, g_bytes_unref) - -/** - * gs_strfreev: - * - * Call g_strfreev() on a variable location when it goes out of scope. - */ -#define gs_strfreev __attribute__ ((cleanup(gs_local_strfreev))) -GS_DEFINE_CLEANUP_FUNCTION(char**, gs_local_strfreev, g_strfreev) - -/** - * gs_free_error: - * - * Call g_error_free() on a variable location when it goes out of scope. - */ -#define gs_free_error __attribute__ ((cleanup(gs_local_free_error))) -GS_DEFINE_CLEANUP_FUNCTION0(GError*, gs_local_free_error, g_error_free) - -/** - * gs_unref_keyfile: - * - * Call g_key_file_unref() on a variable location when it goes out of scope. - */ -#define gs_unref_keyfile __attribute__ ((cleanup(gs_local_keyfile_unref))) -GS_DEFINE_CLEANUP_FUNCTION0(GKeyFile*, gs_local_keyfile_unref, g_key_file_unref) - -static inline void -gs_cleanup_close_fdp (int *fdp) -{ - int fd; - - g_assert (fdp); - - fd = *fdp; - if (fd != -1) - (void) close (fd); -} - -/** - * gs_fd_close: - * - * Call close() on a variable location when it goes out of scope. - */ -#define gs_fd_close __attribute__((cleanup(gs_cleanup_close_fdp))) - -G_END_DECLS - -#endif diff --git a/shared/nm-utils/nm-compat.c b/shared/nm-utils/nm-compat.c index 90328c06..aa7c42f1 100644 --- a/shared/nm-utils/nm-compat.c +++ b/shared/nm-utils/nm-compat.c @@ -60,7 +60,8 @@ _get_keys (NMSettingVpn *setting, if (len) { g_ptr_array_sort (a, nm_strcmp_p); g_ptr_array_add (a, NULL); - keys = g_memdup (a->pdata, a->len * sizeof (gpointer)); + keys = g_malloc (a->len * sizeof (gpointer)); + memcpy (keys, a->pdata, a->len * sizeof (gpointer)); /* we need to cache the keys *somewhere*. */ g_object_set_qdata_full (G_OBJECT (setting), diff --git a/shared/nm-utils/nm-enum-utils.c b/shared/nm-utils/nm-enum-utils.c index b83c4207..a4f6e809 100644 --- a/shared/nm-utils/nm-enum-utils.c +++ b/shared/nm-utils/nm-enum-utils.c @@ -323,11 +323,11 @@ _nm_utils_enum_from_str_full (GType type, } const char ** -_nm_utils_enum_get_values (GType type, gint from, gint to) +_nm_utils_enum_get_values (GType type, int from, int to) { GTypeClass *klass; GPtrArray *array; - gint i; + int i; char sbuf[64]; klass = g_type_class_ref (type); diff --git a/shared/nm-utils/nm-enum-utils.h b/shared/nm-utils/nm-enum-utils.h index d6dae859..1827fdf4 100644 --- a/shared/nm-utils/nm-enum-utils.h +++ b/shared/nm-utils/nm-enum-utils.h @@ -41,7 +41,7 @@ gboolean _nm_utils_enum_from_str_full (GType type, char **err_token, const NMUtilsEnumValueInfo *value_infos); -const char **_nm_utils_enum_get_values (GType type, gint from, gint to); +const char **_nm_utils_enum_get_values (GType type, int from, int to); /*****************************************************************************/ diff --git a/shared/nm-utils/nm-glib.h b/shared/nm-utils/nm-glib.h index 010f1820..770cf0fe 100644 --- a/shared/nm-utils/nm-glib.h +++ b/shared/nm-utils/nm-glib.h @@ -20,10 +20,13 @@ #ifndef __NM_GLIB_H__ #define __NM_GLIB_H__ -#include <gio/gio.h> -#include <string.h> +/*****************************************************************************/ -#include "gsystem-local-alloc.h" +#ifndef __NM_MACROS_INTERNAL_H__ +#error "nm-glib.h requires nm-macros-internal.h. Do not include this directly" +#endif + +/*****************************************************************************/ #ifdef __clang__ @@ -39,6 +42,98 @@ #endif +/*****************************************************************************/ + +static inline void +__g_type_ensure (GType type) +{ +#if !GLIB_CHECK_VERSION(2,34,0) + if (G_UNLIKELY (type == (GType)-1)) + g_error ("can't happen"); +#else + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + g_type_ensure (type); + G_GNUC_END_IGNORE_DEPRECATIONS; +#endif +} +#define g_type_ensure __g_type_ensure + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2,34,0) + +#define g_clear_pointer(pp, destroy) \ + G_STMT_START { \ + G_STATIC_ASSERT (sizeof *(pp) == sizeof (gpointer)); \ + /* Only one access, please */ \ + gpointer *_pp = (gpointer *) (pp); \ + gpointer _p; \ + /* This assignment is needed to avoid a gcc warning */ \ + GDestroyNotify _destroy = (GDestroyNotify) (destroy); \ + \ + _p = *_pp; \ + if (_p) \ + { \ + *_pp = NULL; \ + _destroy (_p); \ + } \ + } G_STMT_END + +#endif + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2,34,0) + +/* These are used to clean up the output of test programs; we can just let + * them no-op in older glib. + */ +#define g_test_expect_message(log_domain, log_level, pattern) +#define g_test_assert_expected_messages() + +#else + +/* We build with -DGLIB_MAX_ALLOWED_VERSION set to 2.32 to make sure we don't + * accidentally use new API that we shouldn't. But we don't want warnings for + * the APIs that we emulate above. + */ + +#define g_test_expect_message(domain, level, format...) \ + G_STMT_START { \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + g_test_expect_message (domain, level, format); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + } G_STMT_END + +#define g_test_assert_expected_messages_internal(domain, file, line, func) \ + G_STMT_START { \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + g_test_assert_expected_messages_internal (domain, file, line, func); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + } G_STMT_END + +#endif + +/*****************************************************************************/ + +#if GLIB_CHECK_VERSION (2, 35, 0) +/* For glib >= 2.36, g_type_init() is deprecated. + * But since 2.35.1 (7c42ab23b55c43ab96d0ac2124b550bf1f49c1ec) this function + * does nothing. Replace the call with empty statement. */ +#define nm_g_type_init() G_STMT_START { (void) 0; } G_STMT_END +#else +#define nm_g_type_init() G_STMT_START { g_type_init (); } G_STMT_END +#endif + +/*****************************************************************************/ + +/* g_test_initialized() is only available since glib 2.36. */ +#if !GLIB_CHECK_VERSION (2, 36, 0) +#define g_test_initialized() (g_test_config_vars->test_initialized) +#endif + +/*****************************************************************************/ + /* g_assert_cmpmem() is only available since glib 2.46. */ #if !GLIB_CHECK_VERSION (2, 45, 7) #define g_assert_cmpmem(m1, l1, m2, l2) G_STMT_START {\ @@ -53,6 +148,8 @@ } G_STMT_END #endif +/*****************************************************************************/ + /* Rumtime check for glib version. First do a compile time check which * (if satisfied) shortcuts the runtime check. */ static inline gboolean @@ -67,6 +164,254 @@ nm_glib_check_version (guint major, guint minor, guint micro) && glib_micro_version < micro)); } +/*****************************************************************************/ + +/* g_test_skip() is only available since glib 2.38. Add a compatibility wrapper. */ +static inline void +__nmtst_g_test_skip (const char *msg) +{ +#if GLIB_CHECK_VERSION (2, 38, 0) + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + g_test_skip (msg); + G_GNUC_END_IGNORE_DEPRECATIONS +#else + g_debug ("%s", msg); +#endif +} +#define g_test_skip __nmtst_g_test_skip + +/*****************************************************************************/ + +/* g_test_add_data_func_full() is only available since glib 2.34. Add a compatibility wrapper. */ +static inline void +__g_test_add_data_func_full (const char *testpath, + gpointer test_data, + GTestDataFunc test_func, + GDestroyNotify data_free_func) +{ +#if GLIB_CHECK_VERSION (2, 34, 0) + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + g_test_add_data_func_full (testpath, test_data, test_func, data_free_func); + G_GNUC_END_IGNORE_DEPRECATIONS +#else + g_return_if_fail (testpath != NULL); + g_return_if_fail (testpath[0] == '/'); + g_return_if_fail (test_func != NULL); + + g_test_add_vtable (testpath, 0, test_data, NULL, + (GTestFixtureFunc) test_func, + (GTestFixtureFunc) data_free_func); +#endif +} +#define g_test_add_data_func_full __g_test_add_data_func_full + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION (2, 34, 0) +#define G_DEFINE_QUARK(QN, q_n) \ +GQuark \ +q_n##_quark (void) \ +{ \ + static GQuark q; \ + \ + if G_UNLIKELY (q == 0) \ + q = g_quark_from_static_string (#QN); \ + \ + return q; \ +} +#endif + +/*****************************************************************************/ + +static inline gboolean +nm_g_hash_table_replace (GHashTable *hash, gpointer key, gpointer value) +{ + /* glib 2.40 added a return value indicating whether the key already existed + * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */ +#if GLIB_CHECK_VERSION(2, 40, 0) + return g_hash_table_replace (hash, key, value); +#else + gboolean contained = g_hash_table_contains (hash, key); + + g_hash_table_replace (hash, key, value); + return !contained; +#endif +} + +static inline gboolean +nm_g_hash_table_insert (GHashTable *hash, gpointer key, gpointer value) +{ + /* glib 2.40 added a return value indicating whether the key already existed + * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */ +#if GLIB_CHECK_VERSION(2, 40, 0) + return g_hash_table_insert (hash, key, value); +#else + gboolean contained = g_hash_table_contains (hash, key); + + g_hash_table_insert (hash, key, value); + return !contained; +#endif +} + +static inline gboolean +nm_g_hash_table_add (GHashTable *hash, gpointer key) +{ + /* glib 2.40 added a return value indicating whether the key already existed + * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */ +#if GLIB_CHECK_VERSION(2, 40, 0) + return g_hash_table_add (hash, key); +#else + gboolean contained = g_hash_table_contains (hash, key); + + g_hash_table_add (hash, key); + return !contained; +#endif +} + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2, 40, 0) || defined (NM_GLIB_COMPAT_H_TEST) +static inline void +_nm_g_ptr_array_insert (GPtrArray *array, + int index_, + gpointer data) +{ + g_return_if_fail (array); + g_return_if_fail (index_ >= -1); + g_return_if_fail (index_ <= (int) array->len); + + g_ptr_array_add (array, data); + + if (index_ != -1 && index_ != (int) (array->len - 1)) { + memmove (&(array->pdata[index_ + 1]), + &(array->pdata[index_]), + (array->len - index_ - 1) * sizeof (gpointer)); + array->pdata[index_] = data; + } +} +#endif + +#if !GLIB_CHECK_VERSION(2, 40, 0) +#define g_ptr_array_insert(array, index, data) G_STMT_START { _nm_g_ptr_array_insert (array, index, data); } G_STMT_END +#else +#define g_ptr_array_insert(array, index, data) \ + G_STMT_START { \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + g_ptr_array_insert (array, index, data); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + } G_STMT_END +#endif + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION (2, 40, 0) +static inline gboolean +_g_key_file_save_to_file (GKeyFile *key_file, + const char *filename, + GError **error) +{ + char *contents; + gboolean success; + gsize length; + + g_return_val_if_fail (key_file != NULL, FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + contents = g_key_file_to_data (key_file, &length, NULL); + g_assert (contents != NULL); + + success = g_file_set_contents (filename, contents, length, error); + g_free (contents); + + return success; +} +#define g_key_file_save_to_file(key_file, filename, error) \ + _g_key_file_save_to_file (key_file, filename, error) +#else +#define g_key_file_save_to_file(key_file, filename, error) \ + ({ \ + gboolean _success; \ + \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + _success = g_key_file_save_to_file (key_file, filename, error); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + _success; \ + }) +#endif + +/*****************************************************************************/ + +#if GLIB_CHECK_VERSION (2, 36, 0) +#define g_credentials_get_unix_pid(creds, error) \ + ({ \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + (g_credentials_get_unix_pid) ((creds), (error)); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + }) +#else +#define g_credentials_get_unix_pid(creds, error) \ + ({ \ + struct ucred *native_creds; \ + \ + native_creds = g_credentials_get_native ((creds), G_CREDENTIALS_TYPE_LINUX_UCRED); \ + g_assert (native_creds); \ + native_creds->pid; \ + }) +#endif + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2, 40, 0) || defined (NM_GLIB_COMPAT_H_TEST) +static inline gpointer * +_nm_g_hash_table_get_keys_as_array (GHashTable *hash_table, + guint *length) +{ + GHashTableIter iter; + gpointer key, *ret; + guint i = 0; + + g_return_val_if_fail (hash_table, NULL); + + ret = g_new0 (gpointer, g_hash_table_size (hash_table) + 1); + g_hash_table_iter_init (&iter, hash_table); + + while (g_hash_table_iter_next (&iter, &key, NULL)) + ret[i++] = key; + + ret[i] = NULL; + + if (length) + *length = i; + + return ret; +} +#endif +#if !GLIB_CHECK_VERSION(2, 40, 0) +#define g_hash_table_get_keys_as_array(hash_table, length) \ + ({ \ + _nm_g_hash_table_get_keys_as_array (hash_table, length); \ + }) +#else +#define g_hash_table_get_keys_as_array(hash_table, length) \ + ({ \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + (g_hash_table_get_keys_as_array) ((hash_table), (length)); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + }) +#endif + +/*****************************************************************************/ + +#ifndef g_info +/* g_info was only added with 2.39.2 */ +#define g_info(...) g_log (G_LOG_DOMAIN, \ + G_LOG_LEVEL_INFO, \ + __VA_ARGS__) +#endif + +/*****************************************************************************/ + #if !GLIB_CHECK_VERSION(2, 44, 0) static inline gpointer g_steal_pointer (gpointer pp) @@ -85,9 +430,11 @@ g_steal_pointer (gpointer pp) (0 ? (*(pp)) : (g_steal_pointer) (pp)) #endif +/*****************************************************************************/ + static inline gboolean -_nm_g_strv_contains (const gchar * const *strv, - const gchar *str) +_nm_g_strv_contains (const char * const *strv, + const char *str) { #if !GLIB_CHECK_VERSION(2, 44, 0) g_return_val_if_fail (strv != NULL, FALSE); @@ -107,11 +454,80 @@ _nm_g_strv_contains (const gchar * const *strv, } #define g_strv_contains _nm_g_strv_contains +/*****************************************************************************/ + +static inline GVariant * +_nm_g_variant_new_take_string (char *string) +{ +#if !GLIB_CHECK_VERSION(2, 36, 0) + GVariant *value; + + g_return_val_if_fail (string != NULL, NULL); + g_return_val_if_fail (g_utf8_validate (string, -1, NULL), NULL); + + value = g_variant_new_string (string); + g_free (string); + return value; +#elif !GLIB_CHECK_VERSION(2, 38, 0) + GVariant *value; + GBytes *bytes; + + g_return_val_if_fail (string != NULL, NULL); + g_return_val_if_fail (g_utf8_validate (string, -1, NULL), NULL); + + bytes = g_bytes_new_take (string, strlen (string) + 1); + value = g_variant_new_from_bytes (G_VARIANT_TYPE_STRING, bytes, TRUE); + g_bytes_unref (bytes); + + return value; +#else + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + return g_variant_new_take_string (string); + G_GNUC_END_IGNORE_DEPRECATIONS +#endif +} +#define g_variant_new_take_string _nm_g_variant_new_take_string + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2, 38, 0) +_nm_printf (1, 2) +static inline GVariant * +_nm_g_variant_new_printf (const char *format_string, ...) +{ + char *string; + va_list ap; + + g_return_val_if_fail (format_string, NULL); + + va_start (ap, format_string); + string = g_strdup_vprintf (format_string, ap); + va_end (ap); + + return g_variant_new_take_string (string); +} +#define g_variant_new_printf(...) _nm_g_variant_new_printf(__VA_ARGS__) +#else +#define g_variant_new_printf(...) \ + ({ \ + GVariant *_v; \ + \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + _v = g_variant_new_printf (__VA_ARGS__); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + _v; \ + }) +#endif + +/*****************************************************************************/ + #if !GLIB_CHECK_VERSION (2, 56, 0) #define g_object_ref(Obj) ((typeof(Obj)) g_object_ref (Obj)) #define g_object_ref_sink(Obj) ((typeof(Obj)) g_object_ref_sink (Obj)) #endif +/*****************************************************************************/ + #ifndef g_autofree /* we still don't rely on recent glib to provide g_autofree. Hence, we continue * to use our gs_* free macros that we took from libgsystem. @@ -120,4 +536,6 @@ _nm_g_strv_contains (const gchar * const *strv, #define g_autofree gs_free #endif +/*****************************************************************************/ + #endif /* __NM_GLIB_H__ */ diff --git a/shared/nm-utils/nm-hash-utils.h b/shared/nm-utils/nm-hash-utils.h index b7742e0f..b797fb75 100644 --- a/shared/nm-utils/nm-hash-utils.h +++ b/shared/nm-utils/nm-hash-utils.h @@ -57,6 +57,11 @@ nm_hash_update (NMHashState *state, const void *ptr, gsize n) nm_assert (ptr); nm_assert (n > 0); + /* Note: the data passed in here might be sensitive data (secrets), + * that we should nm_explicty_zero() afterwards. However, since + * we are using siphash24 with a random key, that is not really + * necessary. Something to keep in mind, if we ever move away from + * this hash implementation. */ c_siphash_append (&state->_state, ptr, n); } @@ -168,7 +173,7 @@ nm_hash_update_mem (NMHashState *state, const void *ptr, gsize n) * instead. */ nm_hash_update (state, &n, sizeof (n)); if (n > 0) - c_siphash_append (&state->_state, ptr, n); + nm_hash_update (state, ptr, n); } static inline void @@ -209,6 +214,15 @@ guint nm_direct_hash (gconstpointer str); guint nm_hash_str (const char *str); guint nm_str_hash (gconstpointer str); +#define nm_hash_val(static_seed, val) \ + ({ \ + NMHashState _h; \ + \ + nm_hash_init (&_h, static_seed); \ + nm_hash_update_val (&_h, val); \ + nm_hash_complete (&_h); \ + }) + /*****************************************************************************/ /* nm_pstr_*() are for hashing keys that are pointers to strings, diff --git a/shared/nm-utils/nm-io-utils.c b/shared/nm-utils/nm-io-utils.c new file mode 100644 index 00000000..88cb13ff --- /dev/null +++ b/shared/nm-utils/nm-io-utils.c @@ -0,0 +1,430 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-io-utils.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "nm-shared-utils.h" +#include "nm-secret-utils.h" + +/*****************************************************************************/ + +_nm_printf (3, 4) +static int +_get_contents_error (GError **error, int errsv, const char *format, ...) +{ + if (errsv < 0) + errsv = -errsv; + else if (!errsv) + errsv = errno; + + if (error) { + char *msg; + va_list args; + + va_start (args, format); + msg = g_strdup_vprintf (format, args); + va_end (args); + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errsv), + "%s: %s", + msg, g_strerror (errsv)); + g_free (msg); + } + return -errsv; +} + +static char * +_mem_realloc (char *old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) +{ + char *new; + + /* re-allocating to zero bytes is an odd case. We don't need it + * and it's not supported. */ + nm_assert (new_len > 0); + + /* regardless of success/failure, @old will always be freed/consumed. */ + + if (do_bzero_mem && cur_len > 0) { + new = g_try_malloc (new_len); + if (new) + memcpy (new, old, NM_MIN (cur_len, new_len)); + nm_explicit_bzero (old, cur_len); + g_free (old); + } else { + new = g_try_realloc (old, new_len); + if (!new) + g_free (old); + } + + return new; +} + +/** + * nm_utils_fd_get_contents: + * @fd: open file descriptor to read. The fd will not be closed, + * but don't rely on its state afterwards. + * @close_fd: if %TRUE, @fd will be closed by the function. + * Passing %TRUE here might safe a syscall for dup(). + * @max_length: allocate at most @max_length bytes. If the + * file is larger, reading will fail. Set to zero to use + * a very large default. + * WARNING: @max_length is here to avoid a crash for huge/unlimited files. + * For example, stat(/sys/class/net/enp0s25/ifindex) gives a filesize of + * 4K, although the actual real is small. @max_length is the memory + * allocated in the process of reading the file, thus it must be at least + * the size reported by fstat. + * If you set it to 1K, read will fail because fstat() claims the + * file is larger. + * @flags: %NMUtilsFileGetContentsFlags for reading the file. + * @contents: the output buffer with the file read. It is always + * NUL terminated. The buffer is at most @max_length long, including + * the NUL byte. That is, it reads only files up to a length of + * @max_length - 1 bytes. + * @length: optional output argument of the read file size. + * + * A reimplementation of g_file_get_contents() with a few differences: + * - accepts an open fd, instead of a path name. This allows you to + * use openat(). + * - limits the maxium filesize to max_length. + * + * Returns: a negative error code on failure. + */ +int +nm_utils_fd_get_contents (int fd, + gboolean close_fd, + gsize max_length, + NMUtilsFileGetContentsFlags flags, + char **contents, + gsize *length, + GError **error) +{ + nm_auto_close int fd_keeper = close_fd ? fd : -1; + struct stat stat_buf; + gs_free char *str = NULL; + const bool do_bzero_mem = NM_FLAGS_HAS (flags, NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET); + + g_return_val_if_fail (fd >= 0, -EINVAL); + g_return_val_if_fail (contents, -EINVAL); + g_return_val_if_fail (!error || !*error, -EINVAL); + + if (fstat (fd, &stat_buf) < 0) + return _get_contents_error (error, 0, "failure during fstat"); + + if (!max_length) { + /* default to a very large size, but not extreme */ + max_length = 2 * 1024 * 1024; + } + + if ( stat_buf.st_size > 0 + && S_ISREG (stat_buf.st_mode)) { + const gsize n_stat = stat_buf.st_size; + ssize_t n_read; + + if (n_stat > max_length - 1) + return _get_contents_error (error, EMSGSIZE, "file too large (%zu+1 bytes with maximum %zu bytes)", n_stat, max_length); + + str = g_try_malloc (n_stat + 1); + if (!str) + return _get_contents_error (error, ENOMEM, "failure to allocate buffer of %zu+1 bytes", n_stat); + + n_read = nm_utils_fd_read_loop (fd, str, n_stat, TRUE); + if (n_read < 0) { + if (do_bzero_mem) + nm_explicit_bzero (str, n_stat); + return _get_contents_error (error, n_read, "error reading %zu bytes from file descriptor", n_stat); + } + str[n_read] = '\0'; + + if (n_read < n_stat) { + if (!(str = _mem_realloc (str, do_bzero_mem, n_stat + 1, n_read + 1))) + return _get_contents_error (error, ENOMEM, "failure to reallocate buffer with %zu bytes", n_read + 1); + } + NM_SET_OUT (length, n_read); + } else { + nm_auto_fclose FILE *f = NULL; + char buf[4096]; + gsize n_have, n_alloc; + int fd2; + + if (fd_keeper >= 0) + fd2 = nm_steal_fd (&fd_keeper); + else { + fd2 = fcntl (fd, F_DUPFD_CLOEXEC, 0); + if (fd2 < 0) + return _get_contents_error (error, 0, "error during dup"); + } + + if (!(f = fdopen (fd2, "r"))) { + nm_close (fd2); + return _get_contents_error (error, 0, "failure during fdopen"); + } + + n_have = 0; + n_alloc = 0; + + while (!feof (f)) { + int errsv; + gsize n_read; + + n_read = fread (buf, 1, sizeof (buf), f); + errsv = errno; + if (ferror (f)) { + if (do_bzero_mem) + nm_explicit_bzero (buf, sizeof (buf)); + return _get_contents_error (error, errsv, "error during fread"); + } + + if ( n_have > G_MAXSIZE - 1 - n_read + || n_have + n_read + 1 > max_length) { + if (do_bzero_mem) + nm_explicit_bzero (buf, sizeof (buf)); + return _get_contents_error (error, EMSGSIZE, "file stream too large (%zu+1 bytes with maximum %zu bytes)", + (n_have > G_MAXSIZE - 1 - n_read) ? G_MAXSIZE : n_have + n_read, + max_length); + } + + if (n_have + n_read + 1 >= n_alloc) { + gsize old_n_alloc = n_alloc; + + if (n_alloc != 0) { + nm_assert (str); + if (n_alloc >= max_length / 2) + n_alloc = max_length; + else + n_alloc *= 2; + } else { + nm_assert (!str); + n_alloc = NM_MIN (n_read + 1, sizeof (buf)); + } + + if (!(str = _mem_realloc (str, do_bzero_mem, old_n_alloc, n_alloc))) { + if (do_bzero_mem) + nm_explicit_bzero (buf, sizeof (buf)); + return _get_contents_error (error, ENOMEM, "failure to allocate buffer of %zu bytes", n_alloc); + } + } + + memcpy (str + n_have, buf, n_read); + n_have += n_read; + } + + if (do_bzero_mem) + nm_explicit_bzero (buf, sizeof (buf)); + + if (n_alloc == 0) + str = g_new0 (char, 1); + else { + str[n_have] = '\0'; + if (n_have + 1 < n_alloc) { + if (!(str = _mem_realloc (str, do_bzero_mem, n_alloc, n_have + 1))) + return _get_contents_error (error, ENOMEM, "failure to truncate buffer to %zu bytes", n_have + 1); + } + } + + NM_SET_OUT (length, n_have); + } + + *contents = g_steal_pointer (&str); + return 0; +} + +/** + * nm_utils_file_get_contents: + * @dirfd: optional file descriptor to use openat(). If negative, use plain open(). + * @filename: the filename to open. Possibly relative to @dirfd. + * @max_length: allocate at most @max_length bytes. + * WARNING: see nm_utils_fd_get_contents() hint about @max_length. + * @flags: %NMUtilsFileGetContentsFlags for reading the file. + * @contents: the output buffer with the file read. It is always + * NUL terminated. The buffer is at most @max_length long, including + * the NUL byte. That is, it reads only files up to a length of + * @max_length - 1 bytes. + * @length: optional output argument of the read file size. + * + * A reimplementation of g_file_get_contents() with a few differences: + * - accepts an @dirfd to open @filename relative to that path via openat(). + * - limits the maxium filesize to max_length. + * - uses O_CLOEXEC on internal file descriptor + * + * Returns: a negative error code on failure. + */ +int +nm_utils_file_get_contents (int dirfd, + const char *filename, + gsize max_length, + NMUtilsFileGetContentsFlags flags, + char **contents, + gsize *length, + GError **error) +{ + int fd; + int errsv; + + g_return_val_if_fail (filename && filename[0], -EINVAL); + + if (dirfd >= 0) { + fd = openat (dirfd, filename, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + errsv = errno; + + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errsv), + "Failed to open file \"%s\" with openat: %s", + filename, + g_strerror (errsv)); + return -errsv; + } + } else { + fd = open (filename, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + errsv = errno; + + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errsv), + "Failed to open file \"%s\": %s", + filename, + g_strerror (errsv)); + return -errsv; + } + } + return nm_utils_fd_get_contents (fd, + TRUE, + max_length, + flags, + contents, + length, + error); +} + +/*****************************************************************************/ + +/* + * Copied from GLib's g_file_set_contents() et al., but allows + * specifying a mode for the new file. + */ +gboolean +nm_utils_file_set_contents (const char *filename, + const char *contents, + gssize length, + mode_t mode, + GError **error) +{ + gs_free char *tmp_name = NULL; + struct stat statbuf; + int errsv; + gssize s; + int fd; + + g_return_val_if_fail (filename, FALSE); + g_return_val_if_fail (contents || !length, FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + g_return_val_if_fail (length >= -1, FALSE); + + if (length == -1) + length = strlen (contents); + + tmp_name = g_strdup_printf ("%s.XXXXXX", filename); + fd = g_mkstemp_full (tmp_name, O_RDWR, mode); + if (fd < 0) { + errsv = errno; + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errsv), + "failed to create file %s: %s", + tmp_name, + g_strerror (errsv)); + return FALSE; + } + + while (length > 0) { + s = write (fd, contents, length); + if (s < 0) { + errsv = errno; + if (errsv == EINTR) + continue; + + nm_close (fd); + unlink (tmp_name); + + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errsv), + "failed to write to file %s: %s", + tmp_name, + g_strerror (errsv)); + return FALSE; + } + + g_assert (s <= length); + + contents += s; + length -= s; + } + + /* If the final destination exists and is > 0 bytes, we want to sync the + * newly written file to ensure the data is on disk when we rename over + * the destination. Otherwise if we get a system crash we can lose both + * the new and the old file on some filesystems. (I.E. those that don't + * guarantee the data is written to the disk before the metadata.) + */ + if ( lstat (filename, &statbuf) == 0 + && statbuf.st_size > 0 + && fsync (fd) != 0) { + errsv = errno; + + nm_close (fd); + unlink (tmp_name); + + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errsv), + "failed to fsync %s: %s", + tmp_name, + g_strerror (errsv)); + return FALSE; + } + + nm_close (fd); + + if (rename (tmp_name, filename)) { + errsv = errno; + unlink (tmp_name); + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errsv), + "failed to rename %s to %s: %s", + tmp_name, + filename, + g_strerror (errsv)); + return FALSE; + } + + return TRUE; +} diff --git a/shared/nm-utils/nm-io-utils.h b/shared/nm-utils/nm-io-utils.h new file mode 100644 index 00000000..dc72a2a6 --- /dev/null +++ b/shared/nm-utils/nm-io-utils.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2018 Red Hat, Inc. + */ + +#ifndef __NM_IO_UTILS_H__ +#define __NM_IO_UTILS_H__ + +#include "nm-macros-internal.h" + +/*****************************************************************************/ + +/** + * NMUtilsFileGetContentsFlags: + * @NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE: no flag + * @NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET: if present, ensure that no + * data is left in memory. Essentially, it means to call explicity_bzero() + * to not leave key material on the heap (when reading secrets). + */ +typedef enum { + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE = 0, + NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET = (1 << 0), +} NMUtilsFileGetContentsFlags; + +int nm_utils_fd_get_contents (int fd, + gboolean close_fd, + gsize max_length, + NMUtilsFileGetContentsFlags flags, + char **contents, + gsize *length, + GError **error); + +int nm_utils_file_get_contents (int dirfd, + const char *filename, + gsize max_length, + NMUtilsFileGetContentsFlags flags, + char **contents, + gsize *length, + GError **error); + +gboolean nm_utils_file_set_contents (const char *filename, + const char *contents, + gssize length, + mode_t mode, + GError **error); + +#endif /* __NM_IO_UTILS_H__ */ diff --git a/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h index ef3039e6..084219b4 100644 --- a/shared/nm-utils/nm-macros-internal.h +++ b/shared/nm-utils/nm-macros-internal.h @@ -16,6 +16,7 @@ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * + * (C) Copyright 2012 Colin Walters <walters@verbum.org>. * (C) Copyright 2014 Red Hat, Inc. */ @@ -25,6 +26,11 @@ #include <stdio.h> #include <stdlib.h> #include <errno.h> +#include <string.h> + +#include <gio/gio.h> + +/*****************************************************************************/ #define _nm_packed __attribute__ ((packed)) #define _nm_unused __attribute__ ((unused)) @@ -34,6 +40,8 @@ #define _nm_align(s) __attribute__ ((aligned (s))) #define _nm_alignof(type) __alignof (type) #define _nm_alignas(type) _nm_align (_nm_alignof (type)) +#define nm_auto(fcn) __attribute__ ((cleanup(fcn))) + #if __GNUC__ >= 7 #define _nm_fallthrough __attribute__ ((fallthrough)) @@ -57,13 +65,146 @@ /*****************************************************************************/ +#define NM_AUTO_DEFINE_FCN_VOID(CastType, name, func) \ +static inline void name (void *v) \ +{ \ + func (*((CastType *) v)); \ +} + +#define NM_AUTO_DEFINE_FCN_VOID0(CastType, name, func) \ +static inline void name (void *v) \ +{ \ + if (*((CastType *) v)) \ + func (*((CastType *) v)); \ +} + +#define NM_AUTO_DEFINE_FCN(Type, name, func) \ +static inline void name (Type *v) \ +{ \ + func (*v); \ +} + +#define NM_AUTO_DEFINE_FCN0(Type, name, func) \ +static inline void name (Type *v) \ +{ \ + if (*v) \ + func (*v); \ +} + +/*****************************************************************************/ + +/** + * gs_free: + * + * Call g_free() on a variable location when it goes out of scope. + */ +#define gs_free nm_auto(gs_local_free) +NM_AUTO_DEFINE_FCN_VOID (void *, gs_local_free, g_free) + +/** + * gs_unref_object: + * + * Call g_object_unref() on a variable location when it goes out of + * scope. Note that unlike g_object_unref(), the variable may be + * %NULL. + */ +#define gs_unref_object nm_auto(gs_local_obj_unref) +NM_AUTO_DEFINE_FCN_VOID0 (GObject *, gs_local_obj_unref, g_object_unref) + +/** + * gs_unref_variant: + * + * Call g_variant_unref() on a variable location when it goes out of + * scope. Note that unlike g_variant_unref(), the variable may be + * %NULL. + */ +#define gs_unref_variant nm_auto(gs_local_variant_unref) +NM_AUTO_DEFINE_FCN0 (GVariant *, gs_local_variant_unref, g_variant_unref) + +/** + * gs_unref_array: + * + * Call g_array_unref() on a variable location when it goes out of + * scope. Note that unlike g_array_unref(), the variable may be + * %NULL. + + */ +#define gs_unref_array nm_auto(gs_local_array_unref) +NM_AUTO_DEFINE_FCN0 (GArray *, gs_local_array_unref, g_array_unref) + +/** + * gs_unref_ptrarray: + * + * Call g_ptr_array_unref() on a variable location when it goes out of + * scope. Note that unlike g_ptr_array_unref(), the variable may be + * %NULL. + + */ +#define gs_unref_ptrarray nm_auto(gs_local_ptrarray_unref) +NM_AUTO_DEFINE_FCN0 (GPtrArray *, gs_local_ptrarray_unref, g_ptr_array_unref) + +/** + * gs_unref_hashtable: + * + * Call g_hash_table_unref() on a variable location when it goes out + * of scope. Note that unlike g_hash_table_unref(), the variable may + * be %NULL. + */ +#define gs_unref_hashtable nm_auto(gs_local_hashtable_unref) +NM_AUTO_DEFINE_FCN0 (GHashTable *, gs_local_hashtable_unref, g_hash_table_unref) + +/** + * gs_free_slist: + * + * Call g_slist_free() on a variable location when it goes out + * of scope. + */ +#define gs_free_slist nm_auto(gs_local_free_slist) +NM_AUTO_DEFINE_FCN (GSList *, gs_local_free_slist, g_slist_free) + +/** + * gs_unref_bytes: + * + * Call g_bytes_unref() on a variable location when it goes out + * of scope. Note that unlike g_bytes_unref(), the variable may + * be %NULL. + */ +#define gs_unref_bytes nm_auto(gs_local_bytes_unref) +NM_AUTO_DEFINE_FCN0 (GBytes *, gs_local_bytes_unref, g_bytes_unref) + +/** + * gs_strfreev: + * + * Call g_strfreev() on a variable location when it goes out of scope. + */ +#define gs_strfreev nm_auto(gs_local_strfreev) +NM_AUTO_DEFINE_FCN (char **, gs_local_strfreev, g_strfreev) + +/** + * gs_free_error: + * + * Call g_error_free() on a variable location when it goes out of scope. + */ +#define gs_free_error nm_auto(gs_local_free_error) +NM_AUTO_DEFINE_FCN0 (GError *, gs_local_free_error, g_error_free) + +/** + * gs_unref_keyfile: + * + * Call g_key_file_unref() on a variable location when it goes out of scope. + */ +#define gs_unref_keyfile nm_auto(gs_local_keyfile_unref) +NM_AUTO_DEFINE_FCN0 (GKeyFile *, gs_local_keyfile_unref, g_key_file_unref) + +/*****************************************************************************/ + #include "nm-glib.h" /*****************************************************************************/ #define nm_offsetofend(t,m) (G_STRUCT_OFFSET (t,m) + sizeof (((t *) NULL)->m)) -#define nm_auto(fcn) __attribute__ ((cleanup(fcn))) +/*****************************************************************************/ static inline int nm_close (int fd); @@ -71,59 +212,49 @@ static inline int nm_close (int fd); * nm_auto_free: * * Call free() on a variable location when it goes out of scope. + * This is for pointers that are allocated with malloc() instead of + * g_malloc(). + * + * In practice, since glib 2.45, g_malloc()/g_free() always wraps malloc()/free(). + * See bgo#751592. In that case, it would be safe to free pointers allocated with + * malloc() with gs_free or g_free(). + * + * However, let's never mix them. To free malloc'ed memory, always use + * free() or nm_auto_free. */ +NM_AUTO_DEFINE_FCN_VOID (void *, _nm_auto_free_impl, free) #define nm_auto_free nm_auto(_nm_auto_free_impl) -GS_DEFINE_CLEANUP_FUNCTION(void*, _nm_auto_free_impl, free) -static inline void -nm_free_secret (char *secret) -{ - if (secret) { - memset (secret, 0, strlen (secret)); - g_free (secret); - } -} +NM_AUTO_DEFINE_FCN0 (GVariantIter *, _nm_auto_free_variant_iter, g_variant_iter_free) +#define nm_auto_free_variant_iter nm_auto(_nm_auto_free_variant_iter) -static inline void -_nm_auto_free_secret_impl (char **v) -{ - nm_free_secret (*v); -} +NM_AUTO_DEFINE_FCN0 (GVariantBuilder *, _nm_auto_unref_variant_builder, g_variant_builder_unref) +#define nm_auto_unref_variant_builder nm_auto(_nm_auto_unref_variant_builder) -/** - * nm_auto_free_secret: - * - * Call g_free() on a variable location when it goes out of scope. - * Also, previously, calls memset(loc, 0, strlen(loc)) to clear out - * the secret. - */ -#define nm_auto_free_secret nm_auto(_nm_auto_free_secret_impl) +NM_AUTO_DEFINE_FCN (GList *, _nm_auto_free_list, g_list_free) +#define nm_auto_free_list nm_auto(_nm_auto_free_list) -static inline void -_nm_auto_unset_gvalue_impl (GValue *v) -{ - g_value_unset (v); -} -#define nm_auto_unset_gvalue nm_auto(_nm_auto_unset_gvalue_impl) +NM_AUTO_DEFINE_FCN0 (GChecksum *, _nm_auto_checksum_free, g_checksum_free) +#define nm_auto_free_checksum nm_auto(_nm_auto_checksum_free) -static inline void -_nm_auto_unref_gtypeclass (gpointer v) -{ - if (v && *((gpointer *) v)) - g_type_class_unref (*((gpointer *) v)); -} +#define nm_auto_unset_gvalue nm_auto(g_value_unset) + +NM_AUTO_DEFINE_FCN_VOID0 (void *, _nm_auto_unref_gtypeclass, g_type_class_unref) #define nm_auto_unref_gtypeclass nm_auto(_nm_auto_unref_gtypeclass) +NM_AUTO_DEFINE_FCN0 (GByteArray *, _nm_auto_unref_bytearray, g_byte_array_unref) +#define nm_auto_unref_bytearray nm_auto(_nm_auto_unref_bytearray) + static inline void -_nm_auto_free_gstring_impl (GString **str) +_nm_auto_free_gstring (GString **str) { if (*str) g_string_free (*str, TRUE); } -#define nm_auto_free_gstring nm_auto(_nm_auto_free_gstring_impl) +#define nm_auto_free_gstring nm_auto(_nm_auto_free_gstring) static inline void -_nm_auto_close_impl (int *pfd) +_nm_auto_close (int *pfd) { if (*pfd >= 0) { int errsv = errno; @@ -132,10 +263,10 @@ _nm_auto_close_impl (int *pfd) errno = errsv; } } -#define nm_auto_close nm_auto(_nm_auto_close_impl) +#define nm_auto_close nm_auto(_nm_auto_close) static inline void -_nm_auto_fclose_impl (FILE **pfd) +_nm_auto_fclose (FILE **pfd) { if (*pfd) { int errsv = errno; @@ -144,7 +275,7 @@ _nm_auto_fclose_impl (FILE **pfd) errno = errsv; } } -#define nm_auto_fclose nm_auto(_nm_auto_fclose_impl) +#define nm_auto_fclose nm_auto(_nm_auto_fclose) static inline void _nm_auto_protect_errno (int *p_saved_errno) @@ -153,13 +284,25 @@ _nm_auto_protect_errno (int *p_saved_errno) } #define NM_AUTO_PROTECT_ERRNO(errsv_saved) nm_auto(_nm_auto_protect_errno) _nm_unused const int errsv_saved = (errno) +NM_AUTO_DEFINE_FCN0 (GSource *, _nm_auto_unref_gsource, g_source_unref); +#define nm_auto_unref_gsource nm_auto(_nm_auto_unref_gsource) + static inline void -_nm_auto_unref_gsource (GSource **ptr) +_nm_auto_freev (gpointer ptr) { - if (*ptr) - g_source_unref (g_steal_pointer (ptr)); + gpointer **p = ptr; + gpointer *_ptr; + + if (*p) { + for (_ptr = *p; *_ptr; _ptr++) + g_free (*_ptr); + g_free (*p); + } } -#define nm_auto_unref_gsource nm_auto(_nm_auto_unref_gsource) +/* g_free a NULL terminated array of pointers, with also freeing each + * pointer with g_free(). It essentially does the same as + * gs_strfreev / g_strfreev(), but not restricted to strv arrays. */ +#define nm_auto_freev nm_auto(_nm_auto_freev) /*****************************************************************************/ @@ -184,13 +327,16 @@ _nm_auto_unref_gsource (GSource **ptr) /*****************************************************************************/ -/* http://stackoverflow.com/a/2124385/354393 */ +/* http://stackoverflow.com/a/2124385/354393 + * https://stackoverflow.com/questions/11317474/macro-to-count-number-of-arguments + */ #define NM_NARG(...) \ - _NM_NARG(__VA_ARGS__,_NM_NARG_RSEQ_N()) + _NM_NARG(, ##__VA_ARGS__, _NM_NARG_RSEQ_N()) #define _NM_NARG(...) \ _NM_NARG_ARG_N(__VA_ARGS__) #define _NM_NARG_ARG_N( \ + _0, \ _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ @@ -261,7 +407,7 @@ _nm_auto_unref_gsource (GSource **ptr) static inline const char * NM_G_ERROR_MSG (GError *error) { - return error ? (error->message ? : "(null)") : "(no-error)"; \ + return error ? (error->message ?: "(null)") : "(no-error)"; \ } /*****************************************************************************/ @@ -754,7 +900,7 @@ nm_str_realloc (char *str) #define NM_GOBJECT_PROPERTIES_DEFINE_BASE(...) \ typedef enum { \ - _PROPERTY_ENUMS_0, \ + PROP_0, \ __VA_ARGS__ \ _PROPERTY_ENUMS_LAST, \ } _PropertyEnums; \ @@ -763,12 +909,39 @@ static GParamSpec *obj_properties[_PROPERTY_ENUMS_LAST] = { NULL, } #define NM_GOBJECT_PROPERTIES_DEFINE(obj_type, ...) \ NM_GOBJECT_PROPERTIES_DEFINE_BASE (__VA_ARGS__); \ static inline void \ -_notify (obj_type *obj, _PropertyEnums prop) \ +_nm_gobject_notify_together_impl (obj_type *obj, guint n, const _PropertyEnums *props) \ { \ + const gboolean freeze_thaw = (n > 1); \ + \ nm_assert (G_IS_OBJECT (obj)); \ - nm_assert ((gsize) prop < G_N_ELEMENTS (obj_properties)); \ - g_object_notify_by_pspec ((GObject *) obj, obj_properties[prop]); \ -} + nm_assert (n > 0); \ + \ + if (freeze_thaw) \ + g_object_freeze_notify ((GObject *) obj); \ + while (n-- > 0) { \ + const _PropertyEnums prop = *props++; \ + \ + if (prop != PROP_0) { \ + nm_assert ((gsize) prop < G_N_ELEMENTS (obj_properties)); \ + nm_assert (obj_properties[prop]); \ + g_object_notify_by_pspec ((GObject *) obj, obj_properties[prop]); \ + } \ + } \ + if (freeze_thaw) \ + g_object_thaw_notify ((GObject *) obj); \ +} \ +\ +static inline void \ +_notify (obj_type *obj, _PropertyEnums prop) \ +{ \ + _nm_gobject_notify_together_impl (obj, 1, &prop); \ +} \ + +/* invokes _notify() for all arguments (of type _PropertyEnums). Note, that if + * there are more than one prop arguments, this will involve a freeze/thaw + * of GObject property notifications. */ +#define nm_gobject_notify_together(obj, ...) \ + _nm_gobject_notify_together_impl (obj, NM_NARG (__VA_ARGS__), (const _PropertyEnums[]) { __VA_ARGS__ }) /*****************************************************************************/ @@ -943,6 +1116,14 @@ nm_clear_g_cancellable (GCancellable **cancellable) && ((__x & (__x - (((typeof(__x)) 1)))) == ((typeof(__x)) 0))); \ }) +#define NM_DIV_ROUND_UP(x, y) \ + ({ \ + const typeof(x) _x = (x); \ + const typeof(y) _y = (y); \ + \ + (_x / _y + !!(_x % _y)); \ + }) + /*****************************************************************************/ #define NM_UTILS_LOOKUP_DEFAULT(v) return (v) @@ -1261,7 +1442,7 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro) * Using _Bool has advantages over gboolean: * * - commonly _Bool is one byte large, instead of gboolean's 4 bytes (because gboolean - * is a typedef for gint). Especially when having boolean fields in a struct, we can + * is a typedef for int). Especially when having boolean fields in a struct, we can * thereby easily save some space. * * - _Bool type guarantees that two "true" expressions compare equal. E.g. the follwing diff --git a/shared/nm-utils/nm-secret-utils.c b/shared/nm-utils/nm-secret-utils.c new file mode 100644 index 00000000..65f99c65 --- /dev/null +++ b/shared/nm-utils/nm-secret-utils.c @@ -0,0 +1,134 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-secret-utils.h" + +/*****************************************************************************/ + +void +nm_explicit_bzero (void *s, gsize n) +{ + /* gracefully handle n == 0. This is important, callers rely on it. */ + if (n > 0) { + nm_assert (s); +#if defined (HAVE_DECL_EXPLICIT_BZERO) && HAVE_DECL_EXPLICIT_BZERO + explicit_bzero (s, n); +#else + /* don't bother with a workaround. Use a reasonable glibc. */ + memset (s, 0, n); +#endif + } +} + +/*****************************************************************************/ + +char * +nm_secret_strchomp (char *secret) +{ + gsize len; + + g_return_val_if_fail (secret, NULL); + + /* it's actually identical to g_strchomp(). However, + * the glib function does not document, that it clears the + * memory. For @secret, we don't only want to truncate trailing + * spaces, we want to overwrite them with NUL. */ + + len = strlen (secret); + while (len--) { + if (g_ascii_isspace ((guchar) secret[len])) + secret[len] = '\0'; + else + break; + } + + return secret; +} + +/*****************************************************************************/ + +GBytes * +nm_secret_copy_to_gbytes (gconstpointer mem, gsize mem_len) +{ + NMSecretBuf *b; + + if (mem_len == 0) + return g_bytes_new_static ("", 0); + + nm_assert (mem); + + /* NUL terminate the buffer. + * + * The entire buffer is already malloc'ed and likely has some room for padding. + * Thus, in many situations, this additional byte will cause no overhead in + * practice. + * + * Even if it causes an overhead, do it just for safety. Yes, the returned + * bytes is not a NUL terminated string and no user must rely on this. Do + * not treat binary data as NUL terminated strings, unless you know what + * you are doing. Anyway, defensive FTW. + */ + + b = nm_secret_buf_new (mem_len + 1); + memcpy (b->bin, mem, mem_len); + b->bin[mem_len] = 0; + return nm_secret_buf_to_gbytes_take (b, mem_len); +} + +/*****************************************************************************/ + +NMSecretBuf * +nm_secret_buf_new (gsize len) +{ + NMSecretBuf *secret; + + nm_assert (len > 0); + + secret = g_malloc (sizeof (NMSecretBuf) + len); + *((gsize *) &(secret->len)) = len; + return secret; +} + +static void +_secret_buf_free (gpointer user_data) +{ + NMSecretBuf *secret = user_data; + + nm_assert (secret); + nm_assert (secret->len > 0); + + nm_explicit_bzero (secret->bin, secret->len); + g_free (user_data); +} + +GBytes * +nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len) +{ + nm_assert (secret); + nm_assert (secret->len > 0); + nm_assert (actual_len == -1 || (actual_len >= 0 && actual_len <= secret->len)); + return g_bytes_new_with_free_func (secret->bin, + actual_len >= 0 ? (gsize) actual_len : secret->len, + _secret_buf_free, + secret); +} diff --git a/shared/nm-utils/nm-secret-utils.h b/shared/nm-utils/nm-secret-utils.h new file mode 100644 index 00000000..21a3c1ba --- /dev/null +++ b/shared/nm-utils/nm-secret-utils.h @@ -0,0 +1,151 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2018 Red Hat, Inc. + */ + +#ifndef __NM_SECRET_UTILS_H__ +#define __NM_SECRET_UTILS_H__ + +#include "nm-macros-internal.h" + +/*****************************************************************************/ + +void nm_explicit_bzero (void *s, gsize n); + +/*****************************************************************************/ + +char *nm_secret_strchomp (char *secret); + +/*****************************************************************************/ + +static inline void +nm_free_secret (char *secret) +{ + if (secret) { + nm_explicit_bzero (secret, strlen (secret)); + g_free (secret); + } +} + +NM_AUTO_DEFINE_FCN (char *, _nm_auto_free_secret, nm_free_secret) +/** + * nm_auto_free_secret: + * + * Call g_free() on a variable location when it goes out of scope. + * Also, previously, calls memset(loc, 0, strlen(loc)) to clear out + * the secret. + */ +#define nm_auto_free_secret nm_auto(_nm_auto_free_secret) + +/*****************************************************************************/ + +GBytes *nm_secret_copy_to_gbytes (gconstpointer mem, gsize mem_len); + +/*****************************************************************************/ + +/* NMSecretPtr is a pair of malloc'ed data pointer and the length of the + * data. The purpose is to use it in combination with nm_auto_clear_secret_ptr + * which ensures that the data pointer (with all len bytes) is cleared upon + * cleanup. */ +typedef struct { + gsize len; + + /* the data pointer. This pointer must be allocated with malloc (at least + * when used with nm_secret_ptr_clear()). */ + union { + char *str; + void *ptr; + guint8 *bin; + }; +} NMSecretPtr; + +static inline void +nm_secret_ptr_clear (NMSecretPtr *secret) +{ + if (secret) { + if (secret->len > 0) { + if (secret->ptr) + nm_explicit_bzero (secret->ptr, secret->len); + secret->len = 0; + } + nm_clear_g_free (&secret->ptr); + } +} + +#define nm_auto_clear_secret_ptr nm_auto(nm_secret_ptr_clear) + +#define NM_SECRET_PTR_STATIC(_len) \ + ((const NMSecretPtr) { \ + .len = _len, \ + .ptr = ((guint8 [_len]) { }), \ + }) + +static inline void +nm_secret_ptr_clear_static (const NMSecretPtr *secret) +{ + if (secret) { + if (secret->len > 0) { + nm_assert (secret->ptr); + nm_explicit_bzero (secret->ptr, secret->len); + } + } +} + +#define nm_auto_clear_static_secret_ptr nm_auto(nm_secret_ptr_clear_static) + +static inline void +nm_secret_ptr_move (NMSecretPtr *dst, NMSecretPtr *src) +{ + if (dst && dst != src) { + *dst = *src; + src->len = 0; + src->ptr = NULL; + } +} + +/*****************************************************************************/ + +typedef struct { + const gsize len; + union { + char str[0]; + guint8 bin[0]; + }; +} NMSecretBuf; + +static inline void +_nm_auto_free_secret_buf (NMSecretBuf **ptr) +{ + NMSecretBuf *b = *ptr; + + if (b) { + nm_assert (b->len > 0); + nm_explicit_bzero (b->bin, b->len); + g_free (b); + } +} +#define nm_auto_free_secret_buf nm_auto(_nm_auto_free_secret_buf) + +NMSecretBuf *nm_secret_buf_new (gsize len); + +GBytes *nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len); + +/*****************************************************************************/ + +#endif /* __NM_SECRET_UTILS_H__ */ diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c index d0019c11..022c0652 100644 --- a/shared/nm-utils/nm-shared-utils.c +++ b/shared/nm-utils/nm-shared-utils.c @@ -97,7 +97,7 @@ nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...) { char *p = *buf; va_list args; - gint retval; + int retval; if (*len == 0) return; @@ -106,7 +106,7 @@ nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...) retval = g_vsnprintf (p, *len, format, args); va_end (args); - if (retval >= *len) { + if ((gsize) retval >= *len) { *buf = &p[*len]; *len = 0; } else { @@ -115,6 +115,131 @@ nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...) } } +/** + * nm_utils_strbuf_seek_end: + * @buf: the input/output buffer + * @len: the input/output lenght of the buffer. + * + * Commonly, one uses nm_utils_strbuf_append*(), to incrementally + * append strings to the buffer. However, sometimes we need to use + * existing API to write to the buffer. + * After doing so, we want to adjust the buffer counter. + * Essentially, + * + * g_snprintf (buf, len, ...); + * nm_utils_strbuf_seek_end (&buf, &len); + * + * is almost the same as + * + * nm_utils_strbuf_append (&buf, &len, ...); + * + * The only difference is the behavior when the string got truncated: + * nm_utils_strbuf_append() will recognize that and set the remaining + * length to zero. + * + * In general, the behavior is: + * + * - if *len is zero, do nothing + * - if the buffer contains a NUL byte within the first *len characters, + * the buffer is pointed to the NUL byte and len is adjusted. In this + * case, the remaining *len is always >= 1. + * In particular, that is also the case if the NUL byte is at the very last + * position ((*buf)[*len -1]). That happens, when the previous operation + * either fit the string exactly into the buffer or the string was truncated + * by g_snprintf(). The difference cannot be determined. + * - if the buffer contains no NUL bytes within the first *len characters, + * write NUL at the last position, set *len to zero, and point *buf past + * the NUL byte. This would happen with + * + * strncpy (buf, long_str, len); + * nm_utils_strbuf_seek_end (&buf, &len). + * + * where strncpy() does truncate the string and not NUL terminate it. + * nm_utils_strbuf_seek_end() would then NUL terminate it. + */ +void +nm_utils_strbuf_seek_end (char **buf, gsize *len) +{ + gsize l; + char *end; + + nm_assert (len); + nm_assert (buf && *buf); + + if (*len <= 1) { + if ( *len == 1 + && (*buf)[0]) + goto truncate; + return; + } + + end = memchr (*buf, 0, *len); + if (end) { + l = end - *buf; + nm_assert (l < *len); + + *buf = end; + *len -= l; + return; + } + +truncate: + /* hm, no NUL character within len bytes. + * Just NUL terminate the array and consume them + * all. */ + *buf += *len; + (*buf)[-1] = '\0'; + *len = 0; + return; +} + +/*****************************************************************************/ + +/** + * nm_utils_gbytes_equals: + * @bytes: (allow-none): a #GBytes array to compare. Note that + * %NULL is treated like an #GBytes array of length zero. + * @mem_data: the data pointer with @mem_len bytes + * @mem_len: the length of the data pointer + * + * Returns: %TRUE if @bytes contains the same data as @mem_data. As a + * special case, a %NULL @bytes is treated like an empty array. + */ +gboolean +nm_utils_gbytes_equal_mem (GBytes *bytes, + gconstpointer mem_data, + gsize mem_len) +{ + gconstpointer p; + gsize l; + + if (!bytes) { + /* as a special case, let %NULL GBytes compare idential + * to an empty array. */ + return (mem_len == 0); + } + + p = g_bytes_get_data (bytes, &l); + return l == mem_len + && ( mem_len == 0 /* allow @mem_data to be %NULL */ + || memcmp (p, mem_data, mem_len) == 0); +} + +GVariant * +nm_utils_gbytes_to_variant_ay (GBytes *bytes) +{ + const guint8 *p; + gsize l; + + if (!bytes) { + /* for convenience, accept NULL to return an empty variant */ + return g_variant_new_array (G_VARIANT_TYPE_BYTE, NULL, 0); + } + + p = g_bytes_get_data (bytes, &l); + return g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, p, l, 1); +} + /*****************************************************************************/ /** @@ -656,6 +781,7 @@ comp_l: * @str: the string to split. * @delimiters: the set of delimiters. If %NULL, defaults to " \t\n", * like bash's $IFS. + * @allow_escaping: whether delimiters can be escaped by a backslash * * This is a replacement for g_strsplit_set() which avoids copying * each word once (the entire strv array), but instead copies it once @@ -664,6 +790,10 @@ comp_l: * Another difference from g_strsplit_set() is that this never returns * empty words. Multiple delimiters are combined and treated as one. * + * If @allow_escaping is %TRUE, delimiters prefixed by a backslash are + * not treated as a separator. Such delimiters and their escape + * character are copied to the current word without unescaping them. + * * Returns: %NULL if @str is %NULL or contains only delimiters. * Otherwise, a %NULL terminated strv array containing non-empty * words, split at the delimiter characters (delimiter characters @@ -673,7 +803,7 @@ comp_l: * but free everything with g_free(). */ const char ** -nm_utils_strsplit_set (const char *str, const char *delimiters) +nm_utils_strsplit_set (const char *str, const char *delimiters, gboolean allow_escaping) { const char **ptr, **ptr0; gsize alloc_size, plen, i; @@ -681,6 +811,7 @@ nm_utils_strsplit_set (const char *str, const char *delimiters) char *s0; char *s; guint8 delimiters_table[256]; + gboolean escaped = FALSE; if (!str) return NULL; @@ -692,13 +823,23 @@ nm_utils_strsplit_set (const char *str, const char *delimiters) for (i = 0; delimiters[i]; i++) delimiters_table[(guint8) delimiters[i]] = 1; -#define _is_delimiter(ch, delimiters_table) \ - ((delimiters_table)[(guint8) (ch)] != 0) +#define _is_delimiter(ch, delimiters_table, allow_esc, esc) \ + ((delimiters_table)[(guint8) (ch)] != 0 && (!allow_esc || !esc)) + +#define next_char(p, esc) \ + G_STMT_START { \ + if (esc) \ + esc = FALSE; \ + else \ + esc = p[0] == '\\'; \ + p++; \ + } G_STMT_END /* skip initial delimiters, and return of the remaining string is * empty. */ - while (_is_delimiter (str[0], delimiters_table)) - str++; + while (_is_delimiter (str[0], delimiters_table, allow_escaping, escaped)) + next_char (str, escaped); + if (!str[0]) return NULL; @@ -730,20 +871,20 @@ nm_utils_strsplit_set (const char *str, const char *delimiters) ptr[plen++] = s; - nm_assert (s[0] && !_is_delimiter (s[0], delimiters_table)); + nm_assert (s[0] && !_is_delimiter (s[0], delimiters_table, allow_escaping, escaped)); while (TRUE) { - s++; - if (_is_delimiter (s[0], delimiters_table)) + next_char (s, escaped); + if (_is_delimiter (s[0], delimiters_table, allow_escaping, escaped)) break; if (s[0] == '\0') goto done; } s[0] = '\0'; - s++; - while (_is_delimiter (s[0], delimiters_table)) - s++; + next_char (s, escaped); + while (_is_delimiter (s[0], delimiters_table, allow_escaping, escaped)) + next_char (s, escaped); if (s[0] == '\0') break; } @@ -840,9 +981,9 @@ _nm_utils_strv_cleanup (char **strv, /*****************************************************************************/ -gint +int _nm_utils_ascii_str_to_bool (const char *str, - gint default_value) + int default_value) { gsize len; char *s = NULL; @@ -924,7 +1065,7 @@ nm_utils_error_is_cancelled (GError *error, */ gboolean nm_g_object_set_property (GObject *object, - const gchar *property_name, + const char *property_name, const GValue *value, GError **error) { @@ -999,7 +1140,7 @@ nm_g_object_set_property (GObject *object, gboolean nm_g_object_set_property_boolean (GObject *object, - const gchar *property_name, + const char *property_name, gboolean value, GError **error) { @@ -1012,7 +1153,7 @@ nm_g_object_set_property_boolean (GObject *object, gboolean nm_g_object_set_property_uint (GObject *object, - const gchar *property_name, + const char *property_name, guint value, GError **error) { @@ -1044,20 +1185,112 @@ _str_append_escape (GString *s, char ch) g_string_append_c (s, '0' + ( ((guchar) ch) & 07)); } +gconstpointer +nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_free) +{ + GString *gstr; + gsize len; + const char *s; + + g_return_val_if_fail (to_free, NULL); + g_return_val_if_fail (out_len, NULL); + + if (!str) { + *out_len = 0; + *to_free = NULL; + return NULL; + } + + len = strlen (str); + + s = memchr (str, '\\', len); + if (!s) { + *out_len = len; + *to_free = NULL; + return str; + } + + gstr = g_string_new_len (NULL, len); + + g_string_append_len (gstr, str, s - str); + str = s; + + for (;;) { + char ch; + guint v; + + nm_assert (str[0] == '\\'); + + ch = (++str)[0]; + + if (ch == '\0') { + // error. Trailing '\\' + break; + } + + if (ch >= '0' && ch <= '9') { + v = ch - '0'; + ch = (++str)[0]; + if (ch >= '0' && ch <= '7') { + v = v * 8 + (ch - '0'); + ch = (++str)[0]; + if (ch >= '0' && ch <= '7') { + v = v * 8 + (ch - '0'); + ch = (++str)[0]; + } + } + ch = v; + } else { + switch (ch) { + case 'b': ch = '\b'; break; + case 'f': ch = '\f'; break; + case 'n': ch = '\n'; break; + case 'r': ch = '\r'; break; + case 't': ch = '\t'; break; + case 'v': ch = '\v'; break; + default: + /* Here we handle "\\\\", but all other unexpected escape sequences are really a bug. + * Take them literally, after removing the escape character */ + break; + } + str++; + } + + g_string_append_c (gstr, ch); + + s = strchr (str, '\\'); + if (!s) { + g_string_append (gstr, str); + break; + } + + g_string_append_len (gstr, str, s - str); + str = s; + } + + *out_len = gstr->len; + *to_free = gstr->str; + return g_string_free (gstr, FALSE); +} + /** - * nm_utils_str_utf8safe_escape: - * @str: NUL terminated input string, possibly in utf-8 encoding + * nm_utils_buf_utf8safe_escape: + * @buf: byte array, possibly in utf-8 encoding, may have NUL characters. + * @buflen: the length of @buf in bytes, or -1 if @buf is a NUL terminated + * string. * @flags: #NMUtilsStrUtf8SafeFlags flags * @to_free: (out): return the pointer location of the string * if a copying was necessary. * - * Returns the possible non-UTF-8 NUL terminated string @str - * and uses backslash escaping (C escaping, like g_strescape()) - * to sanitize non UTF-8 characters. The result is valid + * Based on the assumption, that @buf contains UTF-8 encoded bytes, + * this will return valid UTF-8 sequence, and invalid sequences + * will be escaped with backslash (C escaping, like g_strescape()). + * This is sanitize non UTF-8 characters. The result is valid * UTF-8. * - * The operation can be reverted with g_strcompress() or - * nm_utils_str_utf8safe_unescape(). + * The operation can be reverted with nm_utils_buf_utf8safe_unescape(). + * Note that if, and only if @buf contains no NUL bytes, the operation + * can also be reverted with g_strcompress(). * * Depending on @flags, valid UTF-8 characters are not escaped at all * (except the escape character '\\'). This is the difference to g_strescape(), @@ -1066,62 +1299,106 @@ _str_append_escape (GString *s, char ch) * as UTF-8 -- with exception of the backslash escape character, * invalid UTF-8 sequences, and other (depending on @flags). * - * Returns: the escaped input string, as valid UTF-8. If no escaping - * is necessary, it returns the input @str. Otherwise, an allocated + * Returns: the escaped input buffer, as valid UTF-8. If no escaping + * is necessary, it returns the input @buf. Otherwise, an allocated * string @to_free is returned which must be freed by the caller * with g_free. The escaping can be reverted by g_strcompress(). **/ const char * -nm_utils_str_utf8safe_escape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free) +nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags, char **to_free) { + const char *const str = buf; const char *p = NULL; - GString *s; + const char *s; + gboolean nul_terminated = FALSE; + GString *gstr; g_return_val_if_fail (to_free, NULL); *to_free = NULL; - if (!str || !str[0]) - return str; - if ( g_utf8_validate (str, -1, &p) - && !NM_STRCHAR_ANY (str, ch, - ( ch == '\\' \ - || ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \ - && ch < ' ') \ - || ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \ - && ((guchar) ch) >= 127)))) - return str; + if (buflen == 0) + return NULL; + + if (buflen < 0) { + if (!str) + return NULL; + buflen = strlen (str); + if (buflen == 0) + return str; + nul_terminated = TRUE; + } - s = g_string_sized_new ((p - str) + strlen (p) + 5); + if ( g_utf8_validate (str, buflen, &p) + && nul_terminated) { + /* note that g_utf8_validate() does not allow NUL character inside @str. Good. + * We can treat @str like a NUL terminated string. */ + if (!NM_STRCHAR_ANY (str, ch, + ( ch == '\\' \ + || ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \ + && ch < ' ') \ + || ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \ + && ((guchar) ch) >= 127)))) + return str; + } + + gstr = g_string_sized_new (buflen + 5); + s = str; do { - for (; str < p; str++) { - char ch = str[0]; + buflen -= p - s; + nm_assert (buflen >= 0); + + for (; s < p; s++) { + char ch = s[0]; if (ch == '\\') - g_string_append (s, "\\\\"); + g_string_append (gstr, "\\\\"); else if ( ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \ && ch < ' ') \ || ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \ && ((guchar) ch) >= 127)) - _str_append_escape (s, ch); + _str_append_escape (gstr, ch); else - g_string_append_c (s, ch); + g_string_append_c (gstr, ch); } - if (p[0] == '\0') + if (buflen <= 0) + break; + + _str_append_escape (gstr, p[0]); + + buflen--; + if (buflen == 0) break; - _str_append_escape (s, p[0]); - str = &p[1]; - g_utf8_validate (str, -1, &p); + s = &p[1]; + g_utf8_validate (s, buflen, &p); } while (TRUE); - *to_free = g_string_free (s, FALSE); + *to_free = g_string_free (gstr, FALSE); return *to_free; } const char * +nm_utils_buf_utf8safe_escape_bytes (GBytes *bytes, NMUtilsStrUtf8SafeFlags flags, char **to_free) +{ + gconstpointer p; + gsize l; + + if (bytes) + p = g_bytes_get_data (bytes, &l); + else { + p = NULL; + l = 0; + } + + return nm_utils_buf_utf8safe_escape (p, l, flags, to_free); +} + +/*****************************************************************************/ + +const char * nm_utils_str_utf8safe_unescape (const char *str, char **to_free) { g_return_val_if_fail (to_free, NULL); @@ -1134,6 +1411,39 @@ nm_utils_str_utf8safe_unescape (const char *str, char **to_free) } /** + * nm_utils_str_utf8safe_escape: + * @str: NUL terminated input string, possibly in utf-8 encoding + * @flags: #NMUtilsStrUtf8SafeFlags flags + * @to_free: (out): return the pointer location of the string + * if a copying was necessary. + * + * Returns the possible non-UTF-8 NUL terminated string @str + * and uses backslash escaping (C escaping, like g_strescape()) + * to sanitize non UTF-8 characters. The result is valid + * UTF-8. + * + * The operation can be reverted with g_strcompress() or + * nm_utils_str_utf8safe_unescape(). + * + * Depending on @flags, valid UTF-8 characters are not escaped at all + * (except the escape character '\\'). This is the difference to g_strescape(), + * which escapes all non-ASCII characters. This allows to pass on + * valid UTF-8 characters as-is and can be directly shown to the user + * as UTF-8 -- with exception of the backslash escape character, + * invalid UTF-8 sequences, and other (depending on @flags). + * + * Returns: the escaped input string, as valid UTF-8. If no escaping + * is necessary, it returns the input @str. Otherwise, an allocated + * string @to_free is returned which must be freed by the caller + * with g_free. The escaping can be reverted by g_strcompress(). + **/ +const char * +nm_utils_str_utf8safe_escape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free) +{ + return nm_utils_buf_utf8safe_escape (str, -1, flags, to_free); +} + +/** * nm_utils_str_utf8safe_escape_cp: * @str: NUL terminated input string, possibly in utf-8 encoding * @flags: #NMUtilsStrUtf8SafeFlags flags @@ -1350,6 +1660,209 @@ nm_utils_strv_make_deep_copied (const char **strv) /*****************************************************************************/ +gssize +nm_utils_ptrarray_find_binary_search (gconstpointer *list, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data, + gssize *out_idx_first, + gssize *out_idx_last) +{ + gssize imin, imax, imid, i2min, i2max, i2mid; + int cmp; + + g_return_val_if_fail (list || !len, ~((gssize) 0)); + g_return_val_if_fail (cmpfcn, ~((gssize) 0)); + + imin = 0; + if (len > 0) { + imax = len - 1; + + while (imin <= imax) { + imid = imin + (imax - imin) / 2; + + cmp = cmpfcn (list[imid], needle, user_data); + if (cmp == 0) { + /* we found a matching entry at index imid. + * + * Does the caller request the first/last index as well (in case that + * there are multiple entries which compare equal). */ + + if (out_idx_first) { + i2min = imin; + i2max = imid + 1; + while (i2min <= i2max) { + i2mid = i2min + (i2max - i2min) / 2; + + cmp = cmpfcn (list[i2mid], needle, user_data); + if (cmp == 0) + i2max = i2mid -1; + else { + nm_assert (cmp < 0); + i2min = i2mid + 1; + } + } + *out_idx_first = i2min; + } + if (out_idx_last) { + i2min = imid + 1; + i2max = imax; + while (i2min <= i2max) { + i2mid = i2min + (i2max - i2min) / 2; + + cmp = cmpfcn (list[i2mid], needle, user_data); + if (cmp == 0) + i2min = i2mid + 1; + else { + nm_assert (cmp > 0); + i2max = i2mid - 1; + } + } + *out_idx_last = i2min - 1; + } + return imid; + } + + if (cmp < 0) + imin = imid + 1; + else + imax = imid - 1; + } + } + + /* return the inverse of @imin. This is a negative number, but + * also is ~imin the position where the value should be inserted. */ + imin = ~imin; + NM_SET_OUT (out_idx_first, imin); + NM_SET_OUT (out_idx_last, imin); + return imin; +} + +/*****************************************************************************/ + +/** + * nm_utils_array_find_binary_search: + * @list: the list to search. It must be sorted according to @cmpfcn ordering. + * @elem_size: the size in bytes of each element in the list + * @len: the number of elements in @list + * @needle: the value that is searched + * @cmpfcn: the compare function. The elements @list are passed as first + * argument to @cmpfcn, while @needle is passed as second. Usually, the + * needle is the same data type as inside the list, however, that is + * not necessary, as long as @cmpfcn takes care to cast the two arguments + * accordingly. + * @user_data: optional argument passed to @cmpfcn + * + * Performs binary search for @needle in @list. On success, returns the + * (non-negative) index where the compare function found the searched element. + * On success, it returns a negative value. Note that the return negative value + * is the bitwise inverse of the position where the element should be inserted. + * + * If the list contains multiple matching elements, an arbitrary index is + * returned. + * + * Returns: the index to the element in the list, or the (negative, bitwise inverted) + * position where it should be. + */ +gssize +nm_utils_array_find_binary_search (gconstpointer list, + gsize elem_size, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data) +{ + gssize imin, imax, imid; + int cmp; + + g_return_val_if_fail (list || !len, ~((gssize) 0)); + g_return_val_if_fail (cmpfcn, ~((gssize) 0)); + g_return_val_if_fail (elem_size > 0, ~((gssize) 0)); + + imin = 0; + if (len == 0) + return ~imin; + + imax = len - 1; + + while (imin <= imax) { + imid = imin + (imax - imin) / 2; + + cmp = cmpfcn (&((const char *) list)[elem_size * imid], needle, user_data); + if (cmp == 0) + return imid; + + if (cmp < 0) + imin = imid + 1; + else + imax = imid - 1; + } + + /* return the inverse of @imin. This is a negative number, but + * also is ~imin the position where the value should be inserted. */ + return ~imin; +} + +/*****************************************************************************/ + +/** + * nm_utils_hash_table_equal: + * @a: one #GHashTable + * @b: other #GHashTable + * @treat_null_as_empty: if %TRUE, when either @a or @b is %NULL, it is + * treated like an empty hash. It means, a %NULL hash will compare equal + * to an empty hash. + * @equal_func: the equality function, for comparing the values. + * If %NULL, the values are not compared. In that case, the function + * only checks, if both dictionaries have the same keys -- according + * to @b's key equality function. + * Note that the values of @a will be passed as first argument + * to @equal_func. + * + * Compares two hash tables, whether they have equal content. + * This only makes sense, if @a and @b have the same key types and + * the same key compare-function. + * + * Returns: %TRUE, if both dictionaries have the same content. + */ +gboolean +nm_utils_hash_table_equal (const GHashTable *a, + const GHashTable *b, + gboolean treat_null_as_empty, + NMUtilsHashTableEqualFunc equal_func) +{ + guint n; + GHashTableIter iter; + gconstpointer key, v_a, v_b; + + if (a == b) + return TRUE; + if (!treat_null_as_empty) { + if (!a || !b) + return FALSE; + } + + n = a ? g_hash_table_size ((GHashTable *) a) : 0; + if (n != (b ? g_hash_table_size ((GHashTable *) b) : 0)) + return FALSE; + + if (n > 0) { + g_hash_table_iter_init (&iter, (GHashTable *) a); + while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &v_a)) { + if (!g_hash_table_lookup_extended ((GHashTable *) b, key, NULL, (gpointer *) &v_b)) + return FALSE; + if ( equal_func + && !equal_func (v_a, v_b)) + return FALSE; + } + } + + return TRUE; +} + +/*****************************************************************************/ + /** * nm_utils_get_start_time_for_pid: * @pid: the process identifier @@ -1369,10 +1882,10 @@ nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid) { guint64 start_time; char filename[256]; - gs_free gchar *contents = NULL; + gs_free char *contents = NULL; size_t length; gs_free const char **tokens = NULL; - gchar *p; + char *p; char state = ' '; gint64 ppid = 0; @@ -1399,7 +1912,7 @@ nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid) state = p[0]; - tokens = nm_utils_strsplit_set (p, " "); + tokens = nm_utils_strsplit_set (p, " ", FALSE); if (NM_PTRARRAY_LEN (tokens) < 20) goto fail; @@ -1499,3 +2012,60 @@ _nm_utils_user_data_unpack (gpointer user_data, int nargs, ...) g_slice_free1 (((gsize) nargs) * sizeof (gconstpointer), user_data); } + +/*****************************************************************************/ + +#define IS_SPACE(c) NM_IN_SET ((c), ' ', '\t') + +const char * +_nm_utils_escape_spaces (const char *str, char **to_free) +{ + const char *ptr = str; + char *ret, *r; + + *to_free = NULL; + + if (!str) + return NULL; + + while (TRUE) { + if (!*ptr) + return str; + if (IS_SPACE (*ptr)) + break; + ptr++; + } + + ptr = str; + ret = g_new (char, strlen (str) * 2 + 1); + r = ret; + *to_free = ret; + while (*ptr) { + if (IS_SPACE (*ptr)) + *r++ = '\\'; + *r++ = *ptr++; + } + *r = '\0'; + + return ret; +} + +char * +_nm_utils_unescape_spaces (char *str) +{ + guint i, j = 0; + + if (!str) + return NULL; + + for (i = 0; str[i]; i++) { + if (str[i] == '\\' && IS_SPACE (str[i+1])) + i++; + str[j++] = str[i]; + } + str[j] = '\0'; + + return str; +} + +#undef IS_SPACE diff --git a/shared/nm-utils/nm-shared-utils.h b/shared/nm-utils/nm-shared-utils.h index d983cfcd..b3099738 100644 --- a/shared/nm-utils/nm-shared-utils.h +++ b/shared/nm-utils/nm-shared-utils.h @@ -26,6 +26,25 @@ /*****************************************************************************/ +static inline gboolean +_NM_INT_NOT_NEGATIVE (gssize val) +{ + /* whether an enum (without negative values) is a signed int, depends on compiler options + * and compiler implementation. + * + * When using such an enum for accessing an array, one naturally wants to check + * that the enum is not negative. However, the compiler doesn't like a plain + * comparisong "enum_val >= 0", because (if the enum is unsigned), it will warn + * that the expression is always true *duh*. Not even a cast to a signed + * type helps to avoid the compiler warning in any case. + * + * The sole purpose of this function is to avoid a compiler warning, when checking + * that an enum is not negative. */ + return val >= 0; +} + +/*****************************************************************************/ + static inline char nm_utils_addr_family_to_char (int addr_family) { @@ -171,6 +190,53 @@ nm_ip_addr_set (int addr_family, gpointer dst, const NMIPAddr *src) /*****************************************************************************/ +static inline gboolean +nm_utils_mem_all_zero (gconstpointer mem, gsize len) +{ + const guint8 *p; + + for (p = mem; len-- > 0; p++) { + if (*p != 0) + return FALSE; + } + + /* incidentally, a buffer with len==0, is also *all-zero*. */ + return TRUE; +} + +/*****************************************************************************/ + +/* like g_memdup(). The difference is that the @size argument is of type + * gsize, while g_memdup() has type guint. Since, the size of container types + * like GArray is guint as well, this means trying to g_memdup() an + * array, + * g_memdup (array->data, array->len * sizeof (ElementType)) + * will lead to integer overflow, if there are more than G_MAXUINT/sizeof(ElementType) + * bytes. That seems unnecessarily dangerous to me. + * nm_memdup() avoids that, because its size argument is always large enough + * to contain all data that a GArray can hold. + * + * Another minor difference to g_memdup() is that the glib version also + * returns %NULL if @data is %NULL. E.g. g_memdup(NULL, 1) + * gives %NULL, but nm_memdup(NULL, 1) crashes. I think that + * is desirable, because @size MUST be correct at all times. @size + * may be zero, but one must not claim to have non-zero bytes when + * passing a %NULL @data pointer. + */ +static inline gpointer +nm_memdup (gconstpointer data, gsize size) +{ + gpointer p; + + if (size == 0) + return NULL; + p = g_malloc (size); + memcpy (p, data, size); + return p; +} + +/*****************************************************************************/ + extern const void *const _NM_PTRARRAY_EMPTY[1]; #define NM_PTRARRAY_EMPTY(type) ((type const*) _NM_PTRARRAY_EMPTY) @@ -191,6 +257,7 @@ _nm_utils_strbuf_init (char *buf, gsize len, char **p_buf_ptr, gsize *p_buf_len) void nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...) _nm_printf (3, 4); void nm_utils_strbuf_append_c (char **buf, gsize *len, char c); void nm_utils_strbuf_append_str (char **buf, gsize *len, const char *str); +void nm_utils_strbuf_seek_end (char **buf, gsize *len); const char *nm_strquote (char *buf, gsize buf_len, const char *str); @@ -202,13 +269,48 @@ nm_utils_is_separator (const char c) /*****************************************************************************/ +static inline gboolean +nm_gbytes_equal0 (GBytes *a, GBytes *b) +{ + return a == b || (a && b && g_bytes_equal (a, b)); +} + +gboolean nm_utils_gbytes_equal_mem (GBytes *bytes, + gconstpointer mem_data, + gsize mem_len); + +GVariant *nm_utils_gbytes_to_variant_ay (GBytes *bytes); + +/*****************************************************************************/ + +static inline int +nm_utils_hexchar_to_int (char ch) +{ + G_STATIC_ASSERT_EXPR ('0' < 'A'); + G_STATIC_ASSERT_EXPR ('A' < 'a'); + + if (ch >= '0') { + if (ch <= '9') + return ch - '0'; + if (ch >= 'A') { + if (ch <= 'F') + return ((int) ch) + (10 - (int) 'A'); + if (ch >= 'a' && ch <= 'f') + return ((int) ch) + (10 - (int) 'a'); + } + } + return -1; +} + +/*****************************************************************************/ + const char *nm_utils_dbus_path_get_last_component (const char *dbus_path); int nm_utils_dbus_path_cmp (const char *dbus_path_a, const char *dbus_path_b); /*****************************************************************************/ -const char **nm_utils_strsplit_set (const char *str, const char *delimiters); +const char **nm_utils_strsplit_set (const char *str, const char *delimiters, gboolean allow_escaping); gssize nm_utils_strv_find_first (char **list, gssize len, const char *needle); @@ -247,8 +349,8 @@ gboolean nm_utils_parse_inaddr_prefix (int addr_family, gint64 _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback); -gint _nm_utils_ascii_str_to_bool (const char *str, - gint default_value); +int _nm_utils_ascii_str_to_bool (const char *str, + int default_value); /*****************************************************************************/ @@ -386,12 +488,40 @@ _nm_g_slice_free_fcn_define (16) * error reason. Depending on the usage, this might indicate a bug because * usually the target object should stay alive as long as there are pending * operations. + * + * @NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE: used for a very particular + * purpose during nm_device_check_connection_compatible() to indicate that + * the profile does not match the device already because their type differs. + * That is, there is a fundamental reason of trying to check a profile that + * cannot possibly match on this device. + * @NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE: used for a very particular + * purpose during nm_device_check_connection_available(), to indicate that the + * device is not available because it is unmanaged. + * @NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY: the profile is currently not + * available/compatible with the device, but this may be only temporary. + * * @NM_UTILS_ERROR_INVALID_ARGUMENT: invalid argument. */ typedef enum { NM_UTILS_ERROR_UNKNOWN = 0, /*< nick=Unknown >*/ NM_UTILS_ERROR_CANCELLED_DISPOSING, /*< nick=CancelledDisposing >*/ NM_UTILS_ERROR_INVALID_ARGUMENT, /*< nick=InvalidArgument >*/ + + /* the following codes have a special meaning and are exactly used for + * nm_device_check_connection_compatible() and nm_device_check_connection_available(). + * + * Actually, their meaning is not very important (so, don't think too + * hard about the name of these error codes). What is important, is their + * relative order (i.e. the integer value of the codes). When manager + * searches for a suitable device, it will check all devices whether + * a profile can be activated. If they all fail, it will pick the error + * message from the device that returned the *highest* error code, + * in the hope that this message makes the most sense for the caller. + * */ + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + } NMUtilsError; #define NM_UTILS_ERROR (nm_utils_error_quark ()) @@ -403,20 +533,29 @@ void nm_utils_error_set_cancelled (GError **error, gboolean nm_utils_error_is_cancelled (GError *error, gboolean consider_is_disposing); +static inline void +nm_utils_error_set_literal (GError **error, int error_code, const char *literal) +{ + g_set_error_literal (error, NM_UTILS_ERROR, error_code, literal); +} + +#define nm_utils_error_set(error, error_code, ...) \ + g_set_error ((error), NM_UTILS_ERROR, error_code, __VA_ARGS__) + /*****************************************************************************/ gboolean nm_g_object_set_property (GObject *object, - const gchar *property_name, + const char *property_name, const GValue *value, GError **error); gboolean nm_g_object_set_property_boolean (GObject *object, - const gchar *property_name, + const char *property_name, gboolean value, GError **error); gboolean nm_g_object_set_property_uint (GObject *object, - const gchar *property_name, + const char *property_name, guint value, GError **error); @@ -431,6 +570,10 @@ typedef enum { NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII = 0x0002, } NMUtilsStrUtf8SafeFlags; +const char *nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags, char **to_free); +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); + 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); @@ -518,6 +661,35 @@ nm_utils_strv_make_deep_copied_nonnull (const char **strv) return nm_utils_strv_make_deep_copied (strv) ?: g_new0 (char *, 1); } +/*****************************************************************************/ + +gssize nm_utils_ptrarray_find_binary_search (gconstpointer *list, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data, + gssize *out_idx_first, + gssize *out_idx_last); + +gssize nm_utils_array_find_binary_search (gconstpointer list, + gsize elem_size, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data); + +/*****************************************************************************/ + +typedef gboolean (*NMUtilsHashTableEqualFunc) (gconstpointer a, + gconstpointer b); + +gboolean nm_utils_hash_table_equal (const GHashTable *a, + const GHashTable *b, + gboolean treat_null_as_empty, + NMUtilsHashTableEqualFunc equal_func); + +/*****************************************************************************/ + void _nm_utils_strv_sort (const char **strv, gssize len); #define nm_utils_strv_sort(strv, len) _nm_utils_strv_sort (NM_CAST_STRV_MC (strv), len) @@ -652,4 +824,7 @@ void _nm_utils_user_data_unpack (gpointer user_data, int nargs, ...); /*****************************************************************************/ +const char *_nm_utils_escape_spaces (const char *str, char **to_free); +char *_nm_utils_unescape_spaces (char *str); + #endif /* __NM_SHARED_UTILS_H__ */ diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h index 743893f0..b575382e 100644 --- a/shared/nm-utils/nm-test-utils.h +++ b/shared/nm-utils/nm-test-utils.h @@ -300,9 +300,9 @@ nmtst_free (void) } static inline void -_nmtst_log_handler (const gchar *log_domain, +_nmtst_log_handler (const char *log_domain, GLogLevelFlags log_level, - const gchar *message, + const char *message, gpointer user_data) { g_print ("%s\n", message); @@ -345,6 +345,8 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ __nmtst_internal.assert_logging = !!assert_logging; + nm_g_type_init (); + is_debug = g_test_verbose (); nmtst_debug = g_getenv ("NMTST_DEBUG"); @@ -551,8 +553,13 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ *out_set_logging = TRUE; #endif g_assert (success); +#if GLIB_CHECK_VERSION(2,34,0) if (__nmtst_internal.no_expect_message) g_log_set_always_fatal (G_LOG_FATAL_MASK); +#else + /* g_test_expect_message() is a NOP, so allow any messages */ + g_log_set_always_fatal (G_LOG_FATAL_MASK); +#endif } else if (__nmtst_internal.no_expect_message) { /* We have a test that would be assert_logging, but the user specified no_expect_message. * This transforms g_test_expect_message() into a NOP, but we also have to relax @@ -572,9 +579,15 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ } #endif } else { +#if GLIB_CHECK_VERSION(2,34,0) /* We were called not to set logging levels. This means, that the user - * expects to assert against (all) messages. Any uncought message is fatal. */ - g_log_set_always_fatal (G_LOG_LEVEL_MASK); + * expects to assert against (all) messages. + * Any uncaught message on >debug level is fatal. */ + g_log_set_always_fatal (G_LOG_LEVEL_MASK & ~G_LOG_LEVEL_DEBUG); +#else + /* g_test_expect_message() is a NOP, so allow any messages */ + g_log_set_always_fatal (G_LOG_FATAL_MASK); +#endif } if ((!__nmtst_internal.assert_logging || (__nmtst_internal.assert_logging && __nmtst_internal.no_expect_message)) && @@ -641,6 +654,7 @@ nmtst_test_quick (void) return __nmtst_internal.test_quick; } +#if GLIB_CHECK_VERSION(2,34,0) #undef g_test_expect_message #define g_test_expect_message(...) \ G_STMT_START { \ @@ -648,7 +662,9 @@ nmtst_test_quick (void) if (__nmtst_internal.assert_logging && __nmtst_internal.no_expect_message) { \ g_debug ("nmtst: assert-logging: g_test_expect_message %s", G_STRINGIFY ((__VA_ARGS__))); \ } else { \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ g_test_expect_message (__VA_ARGS__); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ } \ } G_STMT_END #undef g_test_assert_expected_messages_internal @@ -662,8 +678,11 @@ nmtst_test_quick (void) if (__nmtst_internal.assert_logging && __nmtst_internal.no_expect_message) \ g_debug ("nmtst: assert-logging: g_test_assert_expected_messages(%s, %s:%d, %s)", _domain?:"", _file?:"", _line, _func?:""); \ \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ g_test_assert_expected_messages_internal (_domain, _file, _line, _func); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ } G_STMT_END +#endif #define NMTST_EXPECT(domain, level, msg) g_test_expect_message (domain, level, msg) @@ -805,7 +824,7 @@ nmtst_get_rand (void) g_rand_set_seed (__nmtst_internal.rand, seed); } else { /* NMTST_SEED_RAND is set. Use it as a seed. */ - gchar *s; + char *s; gint64 i; i = g_ascii_strtoll (str, &s, 0); @@ -1170,12 +1189,12 @@ _nmtst_assert_ip6_address (const char *file, int line, const struct in6_addr *ad #define nmtst_spawn_sync(working_directory, standard_out, standard_err, assert_exit_status, ...) \ __nmtst_spawn_sync (working_directory, standard_out, standard_err, assert_exit_status, ##__VA_ARGS__, NULL) -static inline gint +static inline int __nmtst_spawn_sync (const char *working_directory, char **standard_out, char **standard_err, int assert_exit_status, ...) G_GNUC_NULL_TERMINATED; -static inline gint +static inline int __nmtst_spawn_sync (const char *working_directory, char **standard_out, char **standard_err, int assert_exit_status, ...) { - gint exit_status = 0; + int exit_status = 0; GError *error = NULL; char *arg; va_list va_args; @@ -1658,7 +1677,7 @@ nmtst_assert_connection_verifies_and_normalizable (NMConnection *con) static inline void nmtst_assert_connection_verifies_after_normalization (NMConnection *con, GQuark expect_error_domain, - gint expect_error_code) + int expect_error_code) { /* assert that the connection does not verify, but normalization does fix it */ GError *error = NULL; @@ -1685,7 +1704,7 @@ nmtst_assert_connection_verifies_after_normalization (NMConnection *con, static inline void nmtst_assert_connection_unnormalizable (NMConnection *con, GQuark expect_error_domain, - gint expect_error_code) + int expect_error_code) { /* assert that the connection does not verify, and it cannot be fixed by normalization */ @@ -1742,7 +1761,7 @@ _nmtst_assert_connection_has_settings (NMConnection *connection, gboolean has_at va_start (ap, has_at_most); while ((name = va_arg (ap, const char *))) { - if (!g_hash_table_add (names, (gpointer) name)) + if (!nm_g_hash_table_add (names, (gpointer) name)) g_assert_not_reached (); g_ptr_array_add (names_arr, (gpointer) name); } @@ -1783,7 +1802,7 @@ _nmtst_assert_connection_has_settings (NMConnection *connection, gboolean has_at static inline void nmtst_assert_setting_verify_fails (NMSetting *setting, GQuark expect_error_domain, - gint expect_error_code) + int expect_error_code) { /* assert that the setting verification fails */ diff --git a/shared/nm-utils/unaligned.h b/shared/nm-utils/unaligned.h index 965a5fe9..e62188d1 100644 --- a/shared/nm-utils/unaligned.h +++ b/shared/nm-utils/unaligned.h @@ -1,10 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ #pragma once -/*** - Copyright © 2014 Tom Gundersen -***/ - #include <endian.h> #include <stdint.h> diff --git a/shared/nm-version-macros.h b/shared/nm-version-macros.h index 350858e2..d53e59f2 100644 --- a/shared/nm-version-macros.h +++ b/shared/nm-version-macros.h @@ -37,7 +37,7 @@ * Evaluates to the minor version number of NetworkManager which this source * is compiled against. */ -#define NM_MINOR_VERSION (12) +#define NM_MINOR_VERSION (13) /** * NM_MICRO_VERSION: @@ -45,7 +45,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: @@ -73,7 +73,7 @@ #define NM_VERSION_1_8 (NM_ENCODE_VERSION (1, 8, 0)) #define NM_VERSION_1_10 (NM_ENCODE_VERSION (1, 10, 0)) #define NM_VERSION_1_12 (NM_ENCODE_VERSION (1, 12, 0)) -#define NM_VERSION_1_12_2 (NM_ENCODE_VERSION (1, 12, 2)) +#define NM_VERSION_1_14 (NM_ENCODE_VERSION (1, 14, 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 0d8fa87a..6f2a1867 100644 --- a/shared/nm-version-macros.h.in +++ b/shared/nm-version-macros.h.in @@ -73,7 +73,7 @@ #define NM_VERSION_1_8 (NM_ENCODE_VERSION (1, 8, 0)) #define NM_VERSION_1_10 (NM_ENCODE_VERSION (1, 10, 0)) #define NM_VERSION_1_12 (NM_ENCODE_VERSION (1, 12, 0)) -#define NM_VERSION_1_12_2 (NM_ENCODE_VERSION (1, 12, 2)) +#define NM_VERSION_1_14 (NM_ENCODE_VERSION (1, 14, 0)) /* For releases, NM_API_VERSION is equal to NM_VERSION. * |