diff options
| author | Michael Biebl <biebl@debian.org> | 2017-11-07 00:14:39 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2017-11-07 00:14:39 +0100 |
| commit | 90e8691111889a7b5f3c812f5a41f15a8a058913 (patch) | |
| tree | f101a879eca27c34a9bfa5f3da52266b22539a36 /shared | |
| parent | bdb6eeb0670658255c2a4c3c501c0a27fa8cfe55 (diff) | |
New upstream version 1.9.90 upstream/1.9.90
Diffstat (limited to 'shared')
26 files changed, 5120 insertions, 206 deletions
diff --git a/shared/nm-common-macros.h b/shared/nm-common-macros.h index 39acf56f..2edb9728 100644 --- a/shared/nm-common-macros.h +++ b/shared/nm-common-macros.h @@ -39,6 +39,7 @@ #define NM_AUTH_PERMISSION_RELOAD "org.freedesktop.NetworkManager.reload" #define NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK "org.freedesktop.NetworkManager.checkpoint-rollback" #define NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS "org.freedesktop.NetworkManager.enable-disable-statistics" +#define NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK "org.freedesktop.NetworkManager.enable-disable-connectivity-check" #define NM_CLONED_MAC_PRESERVE "preserve" #define NM_CLONED_MAC_PERMANENT "permanent" diff --git a/shared/nm-meta-setting.c b/shared/nm-meta-setting.c new file mode 100644 index 00000000..734b8242 --- /dev/null +++ b/shared/nm-meta-setting.c @@ -0,0 +1,369 @@ +/* -*- 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 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-meta-setting.h" + +#include "nm-setting-8021x.h" +#include "nm-setting-adsl.h" +#include "nm-setting-bluetooth.h" +#include "nm-setting-bond.h" +#include "nm-setting-bridge.h" +#include "nm-setting-bridge-port.h" +#include "nm-setting-cdma.h" +#include "nm-setting-connection.h" +#include "nm-setting-dcb.h" +#include "nm-setting-dummy.h" +#include "nm-setting-generic.h" +#include "nm-setting-gsm.h" +#include "nm-setting-infiniband.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-setting-ip-config.h" +#include "nm-setting-ip-tunnel.h" +#include "nm-setting-macsec.h" +#include "nm-setting-macvlan.h" +#include "nm-setting-olpc-mesh.h" +#include "nm-setting-ovs-bridge.h" +#include "nm-setting-ovs-interface.h" +#include "nm-setting-ovs-patch.h" +#include "nm-setting-ovs-port.h" +#include "nm-setting-ppp.h" +#include "nm-setting-pppoe.h" +#include "nm-setting-proxy.h" +#include "nm-setting-serial.h" +#include "nm-setting-team.h" +#include "nm-setting-team-port.h" +#include "nm-setting-tun.h" +#include "nm-setting-user.h" +#include "nm-setting-vlan.h" +#include "nm-setting-vpn.h" +#include "nm-setting-vxlan.h" +#include "nm-setting-wimax.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" + +/*****************************************************************************/ + +const NMSetting8021xSchemeVtable nm_setting_8021x_scheme_vtable[] = { + [NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT] = { + .setting_key = NM_SETTING_802_1X_CA_CERT, + .scheme_func = nm_setting_802_1x_get_ca_cert_scheme, + .format_func = NULL, + .path_func = nm_setting_802_1x_get_ca_cert_path, + .blob_func = nm_setting_802_1x_get_ca_cert_blob, + .uri_func = nm_setting_802_1x_get_ca_cert_uri, + .passwd_func = nm_setting_802_1x_get_ca_cert_password, + .pwflag_func = nm_setting_802_1x_get_ca_cert_password_flags, + .file_suffix = "ca-cert", + }, + + [NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT] = { + .setting_key = NM_SETTING_802_1X_PHASE2_CA_CERT, + .scheme_func = nm_setting_802_1x_get_phase2_ca_cert_scheme, + .format_func = NULL, + .path_func = nm_setting_802_1x_get_phase2_ca_cert_path, + .blob_func = nm_setting_802_1x_get_phase2_ca_cert_blob, + .uri_func = nm_setting_802_1x_get_phase2_ca_cert_uri, + .passwd_func = nm_setting_802_1x_get_phase2_ca_cert_password, + .pwflag_func = nm_setting_802_1x_get_phase2_ca_cert_password_flags, + .file_suffix = "inner-ca-cert", + }, + + [NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT] = { + .setting_key = NM_SETTING_802_1X_CLIENT_CERT, + .scheme_func = nm_setting_802_1x_get_client_cert_scheme, + .format_func = NULL, + .path_func = nm_setting_802_1x_get_client_cert_path, + .blob_func = nm_setting_802_1x_get_client_cert_blob, + .uri_func = nm_setting_802_1x_get_client_cert_uri, + .passwd_func = nm_setting_802_1x_get_client_cert_password, + .pwflag_func = nm_setting_802_1x_get_client_cert_password_flags, + .file_suffix = "client-cert", + }, + + [NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT] = { + .setting_key = NM_SETTING_802_1X_PHASE2_CLIENT_CERT, + .scheme_func = nm_setting_802_1x_get_phase2_client_cert_scheme, + .format_func = NULL, + .path_func = nm_setting_802_1x_get_phase2_client_cert_path, + .blob_func = nm_setting_802_1x_get_phase2_client_cert_blob, + .uri_func = nm_setting_802_1x_get_phase2_client_cert_uri, + .passwd_func = nm_setting_802_1x_get_phase2_client_cert_password, + .pwflag_func = nm_setting_802_1x_get_phase2_client_cert_password_flags, + .file_suffix = "inner-client-cert", + }, + + [NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY] = { + .setting_key = NM_SETTING_802_1X_PRIVATE_KEY, + .scheme_func = nm_setting_802_1x_get_private_key_scheme, + .format_func = nm_setting_802_1x_get_private_key_format, + .path_func = nm_setting_802_1x_get_private_key_path, + .blob_func = nm_setting_802_1x_get_private_key_blob, + .uri_func = nm_setting_802_1x_get_private_key_uri, + .passwd_func = nm_setting_802_1x_get_private_key_password, + .pwflag_func = nm_setting_802_1x_get_private_key_password_flags, + .file_suffix = "private-key", + }, + + [NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY] = { + .setting_key = NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + .scheme_func = nm_setting_802_1x_get_phase2_private_key_scheme, + .format_func = nm_setting_802_1x_get_phase2_private_key_format, + .path_func = nm_setting_802_1x_get_phase2_private_key_path, + .blob_func = nm_setting_802_1x_get_phase2_private_key_blob, + .uri_func = nm_setting_802_1x_get_phase2_private_key_uri, + .passwd_func = nm_setting_802_1x_get_phase2_private_key_password, + .pwflag_func = nm_setting_802_1x_get_phase2_private_key_password_flags, + .file_suffix = "inner-private-key", + }, + + [NM_SETTING_802_1X_SCHEME_TYPE_UNKNOWN] = { NULL }, +}; + +/*****************************************************************************/ + +const NMMetaSettingInfo nm_meta_setting_infos[] = { + [NM_META_SETTING_TYPE_802_1X] = { + .meta_type = NM_META_SETTING_TYPE_802_1X, + .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_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_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_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_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_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_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_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_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_name = NM_SETTING_DUMMY_SETTING_NAME, + .get_setting_gtype = nm_setting_dummy_get_type, + }, + [NM_META_SETTING_TYPE_GENERIC] = { + .meta_type = NM_META_SETTING_TYPE_GENERIC, + .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_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_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_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_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_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_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_name = NM_SETTING_MACVLAN_SETTING_NAME, + .get_setting_gtype = nm_setting_macvlan_get_type, + }, + [NM_META_SETTING_TYPE_OLPC_MESH] = { + .meta_type = NM_META_SETTING_TYPE_OLPC_MESH, + .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_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_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_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_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_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_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_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_name = NM_SETTING_SERIAL_SETTING_NAME, + .get_setting_gtype = nm_setting_serial_get_type, + }, + [NM_META_SETTING_TYPE_TEAM] = { + .meta_type = NM_META_SETTING_TYPE_TEAM, + .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_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_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_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_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_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_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_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_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_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_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + .get_setting_gtype = nm_setting_wireless_security_get_type, + }, + + [NM_META_SETTING_TYPE_UNKNOWN] = { + .meta_type = NM_META_SETTING_TYPE_UNKNOWN, + }, +}; + +const NMMetaSettingInfo * +nm_meta_setting_infos_by_name (const char *name) +{ + int i; + + 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]; + } + } + return NULL; +} + +const NMMetaSettingInfo * +nm_meta_setting_infos_by_gtype (GType gtype) +{ + int 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; +} + +/*****************************************************************************/ diff --git a/shared/nm-meta-setting.h b/shared/nm-meta-setting.h new file mode 100644 index 00000000..f36e322e --- /dev/null +++ b/shared/nm-meta-setting.h @@ -0,0 +1,116 @@ +/* -*- 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 2017 Red Hat, Inc. + */ + +#ifndef __NM_META_SETTING_H__ +#define __NM_META_SETTING_H__ + +#include "nm-setting-8021x.h" + +/*****************************************************************************/ + +typedef enum { + NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT, + NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT, + NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT, + NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT, + NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY, + NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY, + + NM_SETTING_802_1X_SCHEME_TYPE_UNKNOWN, + + _NM_SETTING_802_1X_SCHEME_TYPE_NUM = NM_SETTING_802_1X_SCHEME_TYPE_UNKNOWN, +} NMSetting8021xSchemeType; + +typedef struct { + const char *setting_key; + NMSetting8021xCKScheme (*scheme_func) (NMSetting8021x *setting); + NMSetting8021xCKFormat (*format_func) (NMSetting8021x *setting); + const char * (*path_func) (NMSetting8021x *setting); + GBytes * (*blob_func) (NMSetting8021x *setting); + const char * (*uri_func) (NMSetting8021x *setting); + const char * (*passwd_func) (NMSetting8021x *setting); + NMSettingSecretFlags (*pwflag_func) (NMSetting8021x *setting); + const char *file_suffix; +} NMSetting8021xSchemeVtable; + +extern const NMSetting8021xSchemeVtable nm_setting_8021x_scheme_vtable[_NM_SETTING_802_1X_SCHEME_TYPE_NUM + 1]; + +/*****************************************************************************/ + +typedef enum { + NM_META_SETTING_TYPE_802_1X, + NM_META_SETTING_TYPE_ADSL, + NM_META_SETTING_TYPE_BLUETOOTH, + NM_META_SETTING_TYPE_BOND, + NM_META_SETTING_TYPE_BRIDGE, + NM_META_SETTING_TYPE_BRIDGE_PORT, + NM_META_SETTING_TYPE_CDMA, + NM_META_SETTING_TYPE_CONNECTION, + NM_META_SETTING_TYPE_DCB, + NM_META_SETTING_TYPE_DUMMY, + NM_META_SETTING_TYPE_GENERIC, + NM_META_SETTING_TYPE_GSM, + NM_META_SETTING_TYPE_INFINIBAND, + 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_OVS_BRIDGE, + NM_META_SETTING_TYPE_OVS_INTERFACE, + NM_META_SETTING_TYPE_OVS_PATCH, + NM_META_SETTING_TYPE_OVS_PORT, + NM_META_SETTING_TYPE_PPP, + NM_META_SETTING_TYPE_PPPOE, + NM_META_SETTING_TYPE_PROXY, + NM_META_SETTING_TYPE_SERIAL, + NM_META_SETTING_TYPE_TEAM, + NM_META_SETTING_TYPE_TEAM_PORT, + NM_META_SETTING_TYPE_TUN, + NM_META_SETTING_TYPE_USER, + NM_META_SETTING_TYPE_VLAN, + 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_UNKNOWN, + + _NM_META_SETTING_TYPE_NUM = NM_META_SETTING_TYPE_UNKNOWN, +} NMMetaSettingType; + +typedef struct { + NMMetaSettingType meta_type; + const char *setting_name; + GType (*get_setting_gtype) (void); +} NMMetaSettingInfo; + +extern const NMMetaSettingInfo nm_meta_setting_infos[_NM_META_SETTING_TYPE_NUM + 1]; + +const NMMetaSettingInfo *nm_meta_setting_infos_by_name (const char *name); +const NMMetaSettingInfo *nm_meta_setting_infos_by_gtype (GType gtype); + +/*****************************************************************************/ + +#endif /* __NM_META_SETTING_H__ */ diff --git a/shared/nm-setting-metadata.c b/shared/nm-setting-metadata.c deleted file mode 100644 index a88fb69f..00000000 --- a/shared/nm-setting-metadata.c +++ /dev/null @@ -1,104 +0,0 @@ -/* -*- 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 2017 Red Hat, Inc. - */ - -#include "nm-default.h" - -#include "nm-setting-metadata.h" - -/*****************************************************************************/ - -const NMSetting8021xSchemeVtable nm_setting_8021x_scheme_vtable[] = { - [NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT] = { - .setting_key = NM_SETTING_802_1X_CA_CERT, - .scheme_func = nm_setting_802_1x_get_ca_cert_scheme, - .format_func = NULL, - .path_func = nm_setting_802_1x_get_ca_cert_path, - .blob_func = nm_setting_802_1x_get_ca_cert_blob, - .uri_func = nm_setting_802_1x_get_ca_cert_uri, - .passwd_func = nm_setting_802_1x_get_ca_cert_password, - .pwflag_func = nm_setting_802_1x_get_ca_cert_password_flags, - .file_suffix = "ca-cert", - }, - - [NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT] = { - .setting_key = NM_SETTING_802_1X_PHASE2_CA_CERT, - .scheme_func = nm_setting_802_1x_get_phase2_ca_cert_scheme, - .format_func = NULL, - .path_func = nm_setting_802_1x_get_phase2_ca_cert_path, - .blob_func = nm_setting_802_1x_get_phase2_ca_cert_blob, - .uri_func = nm_setting_802_1x_get_phase2_ca_cert_uri, - .passwd_func = nm_setting_802_1x_get_phase2_ca_cert_password, - .pwflag_func = nm_setting_802_1x_get_phase2_ca_cert_password_flags, - .file_suffix = "inner-ca-cert", - }, - - [NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT] = { - .setting_key = NM_SETTING_802_1X_CLIENT_CERT, - .scheme_func = nm_setting_802_1x_get_client_cert_scheme, - .format_func = NULL, - .path_func = nm_setting_802_1x_get_client_cert_path, - .blob_func = nm_setting_802_1x_get_client_cert_blob, - .uri_func = nm_setting_802_1x_get_client_cert_uri, - .passwd_func = nm_setting_802_1x_get_client_cert_password, - .pwflag_func = nm_setting_802_1x_get_client_cert_password_flags, - .file_suffix = "client-cert", - }, - - [NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT] = { - .setting_key = NM_SETTING_802_1X_PHASE2_CLIENT_CERT, - .scheme_func = nm_setting_802_1x_get_phase2_client_cert_scheme, - .format_func = NULL, - .path_func = nm_setting_802_1x_get_phase2_client_cert_path, - .blob_func = nm_setting_802_1x_get_phase2_client_cert_blob, - .uri_func = nm_setting_802_1x_get_phase2_client_cert_uri, - .passwd_func = nm_setting_802_1x_get_phase2_client_cert_password, - .pwflag_func = nm_setting_802_1x_get_phase2_client_cert_password_flags, - .file_suffix = "inner-client-cert", - }, - - [NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY] = { - .setting_key = NM_SETTING_802_1X_PRIVATE_KEY, - .scheme_func = nm_setting_802_1x_get_private_key_scheme, - .format_func = nm_setting_802_1x_get_private_key_format, - .path_func = nm_setting_802_1x_get_private_key_path, - .blob_func = nm_setting_802_1x_get_private_key_blob, - .uri_func = nm_setting_802_1x_get_private_key_uri, - .passwd_func = nm_setting_802_1x_get_private_key_password, - .pwflag_func = nm_setting_802_1x_get_private_key_password_flags, - .file_suffix = "private-key", - }, - - [NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY] = { - .setting_key = NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, - .scheme_func = nm_setting_802_1x_get_phase2_private_key_scheme, - .format_func = nm_setting_802_1x_get_phase2_private_key_format, - .path_func = nm_setting_802_1x_get_phase2_private_key_path, - .blob_func = nm_setting_802_1x_get_phase2_private_key_blob, - .uri_func = nm_setting_802_1x_get_phase2_private_key_uri, - .passwd_func = nm_setting_802_1x_get_phase2_private_key_password, - .pwflag_func = nm_setting_802_1x_get_phase2_private_key_password_flags, - .file_suffix = "inner-private-key", - }, - - [NM_SETTING_802_1X_SCHEME_TYPE_UNKNOWN] = { NULL }, -}; - -/*****************************************************************************/ diff --git a/shared/nm-setting-metadata.h b/shared/nm-setting-metadata.h deleted file mode 100644 index 47e6e655..00000000 --- a/shared/nm-setting-metadata.h +++ /dev/null @@ -1,56 +0,0 @@ -/* -*- 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 2017 Red Hat, Inc. - */ - -#ifndef __NM_SETTING_METADATA_H__ -#define __NM_SETTING_METADATA_H__ - -#include "nm-setting-8021x.h" - -/*****************************************************************************/ - -typedef struct { - const char *setting_key; - NMSetting8021xCKScheme (*scheme_func) (NMSetting8021x *setting); - NMSetting8021xCKFormat (*format_func) (NMSetting8021x *setting); - const char * (*path_func) (NMSetting8021x *setting); - GBytes * (*blob_func) (NMSetting8021x *setting); - const char * (*uri_func) (NMSetting8021x *setting); - const char * (*passwd_func) (NMSetting8021x *setting); - NMSettingSecretFlags (*pwflag_func) (NMSetting8021x *setting); - const char *file_suffix; -} NMSetting8021xSchemeVtable; - -enum { - NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT, - NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT, - NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT, - NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT, - NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY, - NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY, - - NM_SETTING_802_1X_SCHEME_TYPE_UNKNOWN, -}; - -extern const NMSetting8021xSchemeVtable nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_UNKNOWN + 1]; - -/*****************************************************************************/ - -#endif /* __NM_SETTING_METADATA_H__ */ diff --git a/shared/nm-utils/c-list-util.c b/shared/nm-utils/c-list-util.c new file mode 100644 index 00000000..070323c6 --- /dev/null +++ b/shared/nm-utils/c-list-util.c @@ -0,0 +1,165 @@ +/* -*- 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 2017 Red Hat, Inc. + */ + +#include "c-list-util.h" + +/*****************************************************************************/ + +/** + * c_list_relink: + * @lst: the head list entry + * + * Takes an invalid list, that has undefined prev pointers. + * Only the next pointers are valid, and the tail's next + * pointer points to %NULL instead of the head. + * + * c_list_relink() fixes the list by updating all prev pointers + * and close the circular linking by pointing the tails' next + * pointer to @lst. + * + * The use of this function is to do a bulk update, that lets the + * list degredate by not updating the prev pointers. At the end, + * the list can be fixed by c_list_relink(). + */ +void +c_list_relink (CList *lst) +{ + CList *ls, *ls_prev; + + ls_prev = lst; + ls = lst->next; + do { + ls->prev = ls_prev; + ls_prev = ls; + ls = ls->next; + } while (ls); + ls_prev->next = lst; + lst->prev = ls_prev; +} + +/*****************************************************************************/ + +static CList * +_c_list_sort (CList *ls, + CListSortCmp cmp, + const void *user_data) +{ + CList *ls1, *ls2; + CList head; + + if (!ls->next) + return ls; + + /* split list in two halfs @ls1 and @ls2. */ + ls1 = ls; + ls2 = ls; + ls = ls->next; + while (ls) { + ls = ls->next; + if (!ls) + break; + ls = ls->next; + ls2 = ls2->next; + } + ls = ls2; + ls2 = ls->next; + ls->next = NULL; + + /* recurse */ + ls1 = _c_list_sort (ls1, cmp, user_data); + if (!ls2) + return ls1; + + ls2 = _c_list_sort (ls2, cmp, user_data); + + /* merge */ + ls = &head; + for (;;) { + /* while invoking the @cmp function, the list + * elements are not properly linked. Don't try to access + * their next/prev pointers. */ + if (cmp (ls1, ls2, user_data) <= 0) { + ls->next = ls1; + ls = ls1; + ls1 = ls1->next; + if (!ls1) + break; + } else { + ls->next = ls2; + ls = ls2; + ls2 = ls2->next; + if (!ls2) + break; + } + } + ls->next = ls1 ?: ls2; + + return head.next; +} + +/** + * c_list_sort_headless: + * @lst: the list. + * @cmp: compare function for sorting. While comparing two + * CList elements, their next/prev pointers are in undefined + * state. + * @user_data: user data for @cmp. + * + * Sorts the list @lst according to @cmp. Contrary to + * c_list_sort(), @lst is not the list head but a + * valid entry as well. This function returns the new + * list head. + */ +CList * +c_list_sort_headless (CList *lst, + CListSortCmp cmp, + const void *user_data) +{ + if (!c_list_is_empty (lst)) { + lst->prev->next = NULL; + lst = _c_list_sort (lst, cmp, user_data); + c_list_relink (lst); + } + return lst; +} + +/** + * c_list_sort: + * @head: the list head. + * @cmp: compare function for sorting. While comparing two + * CList elements, their next/prev pointers are in undefined + * state. + * @user_data: user data for @cmp. + * + * Sorts the list @head according to @cmp. + */ +void +c_list_sort (CList *head, + CListSortCmp cmp, + const void *user_data) +{ + if ( !c_list_is_empty (head) + && head->next->next != head) { + head->prev->next = NULL; + head->next = _c_list_sort (head->next, cmp, user_data); + c_list_relink (head); + } +} diff --git a/shared/nm-utils/c-list-util.h b/shared/nm-utils/c-list-util.h new file mode 100644 index 00000000..199583cf --- /dev/null +++ b/shared/nm-utils/c-list-util.h @@ -0,0 +1,43 @@ +/* -*- 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 2017 Red Hat, Inc. + */ + +#ifndef __C_LIST_UTIL_H__ +#define __C_LIST_UTIL_H__ + +#include "c-list.h" + +/*****************************************************************************/ + +void c_list_relink (CList *lst); + +typedef int (*CListSortCmp) (const CList *a, + const CList *b, + const void *user_data); + +CList *c_list_sort_headless (CList *lst, + CListSortCmp cmp, + const void *user_data); + +void c_list_sort (CList *head, + CListSortCmp cmp, + const void *user_data); + +#endif /* __C_LIST_UTIL_H__ */ diff --git a/shared/nm-utils/c-list.h b/shared/nm-utils/c-list.h new file mode 100644 index 00000000..557862b6 --- /dev/null +++ b/shared/nm-utils/c-list.h @@ -0,0 +1,397 @@ +#pragma once + +/* + * Circular Double Linked List Implementation in Standard ISO-C11 + * + * This implements a generic circular double linked list. List entries must + * embed the CList object, which provides pointers to the next and previous + * element. Insertion and removal can be done in O(1) due to the double links. + * Furthermore, the list is circular, thus allows access to front/tail in O(1) + * as well, even if you only have a single head pointer (which is not how the + * list is usually operated, though). + * + * Note that you are free to use the list implementation without a head + * pointer. However, usual operation uses a single CList object as head, which + * is itself linked in the list and as such must be identified as list head. + * This allows very simply list operations and avoids a lot of special cases. + * Most importantly, you can unlink entries without requiring a head pointer. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stddef.h> + +typedef struct CList CList; + +/** + * struct CList - Entry of a circular double linked list + * @next: next entry + * @prev: previous entry + * + * Each entry in a list must embed a CList object. This object contains + * pointers to its next and previous elements, which can be freely accessed by + * the API user at any time. Note that the list is circular, and the list head + * is linked in the list as well. + * + * The list head must be initialized via C_LIST_INIT before use. There is no + * reason to initialize entry objects before linking them. However, if you need + * a boolean state that tells you whether the entry is linked or not, you should + * initialize the entry via C_LIST_INIT as well. + */ +struct CList { + CList *next; + CList *prev; +}; + +#define C_LIST_INIT(_var) { .next = &(_var), .prev = &(_var) } + +/** + * c_list_init() - initialize list entry + * @what: list entry to initialize + */ +static inline void c_list_init(CList *what) { + *what = (CList)C_LIST_INIT(*what); +} + +/** + * c_list_entry() - get parent container of list entry + * @_what: list entry, or NULL + * @_t: type of parent container + * @_m: member name of list entry in @_t + * + * If the list entry @_what is embedded into a surrounding structure, this will + * turn the list entry pointer @_what into a pointer to the parent container + * (using offsetof(3), or sometimes called container_of(3)). + * + * If @_what is NULL, this will also return NULL. + * + * Return: Pointer to parent container, or NULL. + */ +#define c_list_entry(_what, _t, _m) \ + ((_t *)(void *)(((unsigned long)(void *)(_what) ?: \ + offsetof(_t, _m)) - offsetof(_t, _m))) + +/** + * c_list_is_linked() - check whether an entry is linked + * @what: entry to check, or NULL + * + * Return: True if @what is linked in a list, false if not. + */ +static inline _Bool c_list_is_linked(const CList *what) { + return what && what->next != what; +} + +/** + * c_list_is_empty() - check whether a list is empty + * @list: list to check, or NULL + * + * Return: True if @list is empty, false if not. + */ +static inline _Bool c_list_is_empty(const CList *list) { + return !list || !c_list_is_linked(list); +} + +/** + * c_list_link_before() - link entry into list + * @where: linked list entry used as anchor + * @what: entry to link + * + * This links @what directly in front of @where. @where can either be a list + * head or any entry in the list. + * + * If @where points to the list head, this effectively links @what as new tail + * element. Hence, the macro c_list_link_tail() is an alias to this. + * + * @what is not inspected prior to being linked. Hence, it better not be linked + * into another list, or the other list will be corrupted. + */ +static inline void c_list_link_before(CList *where, CList *what) { + CList *prev = where->prev, *next = where; + + next->prev = what; + what->next = next; + what->prev = prev; + prev->next = what; +} +#define c_list_link_tail(_list, _what) c_list_link_before((_list), (_what)) + +/** + * c_list_link_after() - link entry into list + * @where: linked list entry used as anchor + * @what: entry to link + * + * This links @what directly after @where. @where can either be a list head or + * any entry in the list. + * + * If @where points to the list head, this effectively links @what as new front + * element. Hence, the macro c_list_link_front() is an alias to this. + * + * @what is not inspected prior to being linked. Hence, it better not be linked + * into another list, or the other list will be corrupted. + */ +static inline void c_list_link_after(CList *where, CList *what) { + CList *prev = where, *next = where->next; + + next->prev = what; + what->next = next; + what->prev = prev; + prev->next = what; +} +#define c_list_link_front(_list, _what) c_list_link_after((_list), (_what)) + +/** + * c_list_unlink() - unlink element from list + * @what: element to unlink + * + * This unlinks @what. If @what was initialized via C_LIST_INIT(), it has no + * effect. If @what was never linked, nor initialized, behavior is undefined. + * + * Note that this does not modify @what. It just modifies the previous and next + * elements in the list to no longer reference @what. If you want to make sure + * @what is re-initialized after removal, use c_list_unlink_init(). + */ +static inline void c_list_unlink(CList *what) { + CList *prev = what->prev, *next = what->next; + + next->prev = prev; + prev->next = next; +} + +/** + * c_list_unlink_init() - unlink element from list and re-initialize + * @what: element to unlink + * + * This is like c_list_unlink() but re-initializes @what after removal. + */ +static inline void c_list_unlink_init(CList *what) { + /* condition is not needed, but avoids STOREs in fast-path */ + if (c_list_is_linked(what)) { + c_list_unlink(what); + *what = (CList)C_LIST_INIT(*what); + } +} + +/** + * c_list_swap() - exchange the contents of two lists + * @list1: the list to operate on + * @list2: the list to operate on + * + * This replaces the contents of the list @list1 with the contents + * of @list2, and vice versa. + */ +static inline void c_list_swap(CList *list1, CList *list2) { + CList t; + + /* make neighbors of list1 point to list2, and vice versa */ + t = *list1; + t.next->prev = list2; + t.prev->next = list2; + t = *list2; + t.next->prev = list1; + t.prev->next = list1; + + /* swap list1 and list2 now that their neighbors were fixed up */ + t = *list1; + *list1 = *list2; + *list2 = t; +} + +/** + * c_list_splice() - splice one list into another + * @target: the list to splice into + * @source: the list to splice + * + * This removes all the entries from @source and splice them into @target. + * The order of the two lists is preserved and the source is appended + * to the end of target. + * + * On return, the source list will be empty. + */ +static inline void c_list_splice(CList *target, CList *source) { + if (!c_list_is_empty(source)) { + /* attach the front of @source to the tail of @target */ + source->next->prev = target->prev; + target->prev->next = source->next; + + /* attach the tail of @source to the front of @target */ + source->prev->next = target; + target->prev = source->prev; + + /* clear source */ + *source = (CList)C_LIST_INIT(*source); + } +} + +/** + * c_list_for_each() - loop over all list entries + * @_iter: iterator to use + * @_list: list to loop over + * + * This is a macro to use as for-loop to iterate an entire list. It is meant as + * convenience macro. Feel free to code your own loop iterator. + */ +#define c_list_for_each(_iter, _list) \ + for (_iter = (_list)->next; \ + (_iter) != (_list); \ + _iter = (_iter)->next) + + +/** + * c_list_for_each_safe() - loop over all list entries, safe for removal + * @_iter: iterator to use + * @_safe: used to store pointer to next element + * @_list: list to loop over + * + * This is a macro to use as for-loop to iterate an entire list, safe against + * removal of the current element. It is meant as convenience macro. Feel free + * to code your own loop iterator. + * + * Note that this fetches the next element prior to executing the loop body. + * This makes it safe against removal of the current entry, but it will go + * havoc if you remove other list entries. You better not modify anything but + * the current list entry. + */ +#define c_list_for_each_safe(_iter, _safe, _list) \ + for (_iter = (_list)->next, _safe = (_iter)->next; \ + (_iter) != (_list); \ + _iter = (_safe), _safe = (_safe)->next) + +/** + * c_list_for_each_entry() - loop over all list entries + * @_iter: iterator to use + * @_list: list to loop over + * @_m: member name of CList object in list type + * + * This combines c_list_for_each() with c_list_entry(), making it easy to + * iterate over a list of a specific type. + */ +#define c_list_for_each_entry(_iter, _list, _m) \ + for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m); \ + &(_iter)->_m != (_list); \ + _iter = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m)) + +/** + * c_list_for_each_entry_safe() - loop over all list entries, safe for removal + * @_iter: iterator to use + * @_safe: used to store pointer to next element + * @_list: list to loop over + * @_m: member name of CList object in list type + * + * This combines c_list_for_each_safe() with c_list_entry(), making it easy to + * iterate over a list of a specific type. + */ +#define c_list_for_each_entry_safe(_iter, _safe, _list, _m) \ + for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m), \ + _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \ + &(_iter)->_m != (_list); \ + _iter = (_safe), \ + _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) \ + +/** + * c_list_first() - return pointer to first element, or NULL if empty + * @list: list to operate on, or NULL + * + * This returns a pointer to the first element, or NULL if empty. This never + * returns a pointer to the list head. + * + * Return: Pointer to first list element, or NULL if empty. + */ +static inline CList *c_list_first(CList *list) { + return c_list_is_empty(list) ? NULL : list->next; +} + +/** + * c_list_last() - return pointer to last element, or NULL if empty + * @list: list to operate on, or NULL + * + * This returns a pointer to the last element, or NULL if empty. This never + * returns a pointer to the list head. + * + * Return: Pointer to last list element, or NULL if empty. + */ +static inline CList *c_list_last(CList *list) { + return c_list_is_empty(list) ? NULL : list->prev; +} + +/** + * c_list_first_entry() - return pointer to first entry, or NULL if empty + * @_list: list to operate on, or NULL + * @_t: type of list entries + * @_m: name of CList member in @_t + * + * This is like c_list_first(), but also applies c_list_entry() on the result. + * + * Return: Pointer to first list entry, or NULL if empty. + */ +#define c_list_first_entry(_list, _t, _m) \ + c_list_entry(c_list_first(_list), _t, _m) + +/** + * c_list_last_entry() - return pointer to last entry, or NULL if empty + * @_list: list to operate on, or NULL + * @_t: type of list entries + * @_m: name of CList member in @_t + * + * This is like c_list_last(), but also applies c_list_entry() on the result. + * + * Return: Pointer to last list entry, or NULL if empty. + */ +#define c_list_last_entry(_list, _t, _m) \ + c_list_entry(c_list_last(_list), _t, _m) + +/** + * c_list_length() - return number of linked entries, excluding the head + * @list: list to operate on + * + * Returns the number of entries in the list, excluding the list head @list. + * That is, for a list that is empty according to c_list_is_empty(), the + * returned length is 0. This requires to iterate the list and has thus O(n) + * runtime. + * + * Note that this function is meant for debugging purposes only. If you need + * the list size during normal operation, you should maintain a counter + * separately. + * + * Return: Number of items in @list. + */ +static inline unsigned long c_list_length(const CList *list) { + unsigned long n = 0; + const CList *iter; + + c_list_for_each(iter, list) + ++n; + + return n; +} + +/** + * c_list_contains() - check whether an entry is linked in a certain list + * @list: list to operate on + * @what: entry to look for + * + * This checks whether @what is linked into @list. This requires a linear + * search through the list, as such runs in O(n). Note that the list-head is + * considered part of the list, and hence this returns true if @what equals + * @list. + * + * Note that this function is meant for debugging purposes, and consistency + * checks. You should always be aware whether your objects are linked in a + * specific list. + * + * Return: True if @what is in @list, false otherwise. + */ +static inline _Bool c_list_contains(const CList *list, const CList *what) { + const CList *iter; + + c_list_for_each(iter, list) + if (what == iter) + return 1; + + return what == list; +} + +#ifdef __cplusplus +} +#endif diff --git a/shared/nm-utils/nm-dedup-multi.c b/shared/nm-utils/nm-dedup-multi.c new file mode 100644 index 00000000..8a59f1c4 --- /dev/null +++ b/shared/nm-utils/nm-dedup-multi.c @@ -0,0 +1,1123 @@ +/* -*- 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 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dedup-multi.h" + +#include "nm-hash-utils.h" + +/*****************************************************************************/ + +typedef struct { + /* the stack-allocated lookup entry. It has a compatible + * memory layout with NMDedupMultiEntry and NMDedupMultiHeadEntry. + * + * It is recognizable by having lst_entries_sentinel.next set to NULL. + * Contrary to the other entries, which have lst_entries.next + * always non-NULL. + * */ + CList lst_entries_sentinel; + const NMDedupMultiObj *obj; + const NMDedupMultiIdxType *idx_type; + bool lookup_head; +} LookupEntry; + +struct _NMDedupMultiIndex { + int ref_count; + GHashTable *idx_entries; + GHashTable *idx_objs; +}; + +/*****************************************************************************/ + +static void +ASSERT_idx_type (const NMDedupMultiIdxType *idx_type) +{ + nm_assert (idx_type); +#if NM_MORE_ASSERTS > 10 + nm_assert (idx_type->klass); + nm_assert (idx_type->klass->idx_obj_id_hash_update); + nm_assert (idx_type->klass->idx_obj_id_equal); + nm_assert (!!idx_type->klass->idx_obj_partition_hash_update == !!idx_type->klass->idx_obj_partition_equal); + nm_assert (idx_type->lst_idx_head.next); +#endif +} + +void +nm_dedup_multi_idx_type_init (NMDedupMultiIdxType *idx_type, + const NMDedupMultiIdxTypeClass *klass) +{ + nm_assert (idx_type); + nm_assert (klass); + + memset (idx_type, 0, sizeof (*idx_type)); + idx_type->klass = klass; + c_list_init (&idx_type->lst_idx_head); + + ASSERT_idx_type (idx_type); +} + +/*****************************************************************************/ + +static NMDedupMultiEntry * +_entry_lookup_obj (const NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj) +{ + const LookupEntry stack_entry = { + .obj = obj, + .idx_type = idx_type, + .lookup_head = FALSE, + }; + + ASSERT_idx_type (idx_type); + return g_hash_table_lookup (self->idx_entries, &stack_entry); +} + +static NMDedupMultiHeadEntry * +_entry_lookup_head (const NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj) +{ + NMDedupMultiHeadEntry *head_entry; + const LookupEntry stack_entry = { + .obj = obj, + .idx_type = idx_type, + .lookup_head = TRUE, + }; + + ASSERT_idx_type (idx_type); + + if (!idx_type->klass->idx_obj_partition_equal) { + if (c_list_is_empty (&idx_type->lst_idx_head)) + head_entry = NULL; + else { + nm_assert (c_list_length (&idx_type->lst_idx_head) == 1); + head_entry = c_list_entry (idx_type->lst_idx_head.next, NMDedupMultiHeadEntry, lst_idx); + } + nm_assert (head_entry == g_hash_table_lookup (self->idx_entries, &stack_entry)); + return head_entry; + } + + return g_hash_table_lookup (self->idx_entries, &stack_entry); +} + +static void +_entry_unpack (const NMDedupMultiEntry *entry, + const NMDedupMultiIdxType **out_idx_type, + const NMDedupMultiObj **out_obj, + gboolean *out_lookup_head) +{ + const NMDedupMultiHeadEntry *head_entry; + const LookupEntry *lookup_entry; + + nm_assert (entry); + + G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (LookupEntry, lst_entries_sentinel) == G_STRUCT_OFFSET (NMDedupMultiEntry, lst_entries)); + G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMDedupMultiEntry, lst_entries) == G_STRUCT_OFFSET (NMDedupMultiHeadEntry, lst_entries_head)); + G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMDedupMultiEntry, obj) == G_STRUCT_OFFSET (NMDedupMultiHeadEntry, idx_type)); + G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMDedupMultiEntry, is_head) == G_STRUCT_OFFSET (NMDedupMultiHeadEntry, is_head)); + + if (!entry->lst_entries.next) { + /* the entry is stack-allocated by _entry_lookup(). */ + lookup_entry = (LookupEntry *) entry; + *out_obj = lookup_entry->obj; + *out_idx_type = lookup_entry->idx_type; + *out_lookup_head = lookup_entry->lookup_head; + } else if (entry->is_head) { + head_entry = (NMDedupMultiHeadEntry *) entry; + nm_assert (!c_list_is_empty (&head_entry->lst_entries_head)); + *out_obj = c_list_entry (head_entry->lst_entries_head.next, NMDedupMultiEntry, lst_entries)->obj; + *out_idx_type = head_entry->idx_type; + *out_lookup_head = TRUE; + } else { + *out_obj = entry->obj; + *out_idx_type = entry->head->idx_type; + *out_lookup_head = FALSE; + } + + nm_assert (NM_IN_SET (*out_lookup_head, FALSE, TRUE)); + ASSERT_idx_type (*out_idx_type); + + /* for lookup of the head, we allow to omit object, but only + * if the idx_type does not parition the objects. Otherwise, we + * require a obj to compare. */ + nm_assert ( !*out_lookup_head + || ( *out_obj + || !(*out_idx_type)->klass->idx_obj_partition_equal)); + + /* lookup of the object requires always an object. */ + nm_assert ( *out_lookup_head + || *out_obj); +} + +static guint +_dict_idx_entries_hash (const NMDedupMultiEntry *entry) +{ + const NMDedupMultiIdxType *idx_type; + const NMDedupMultiObj *obj; + gboolean lookup_head; + NMHashState h; + + _entry_unpack (entry, &idx_type, &obj, &lookup_head); + + nm_hash_init (&h, 1914869417u); + if (idx_type->klass->idx_obj_partition_hash_update) { + nm_assert (obj); + idx_type->klass->idx_obj_partition_hash_update (idx_type, obj, &h); + } + + if (!lookup_head) + idx_type->klass->idx_obj_id_hash_update (idx_type, obj, &h); + + nm_hash_update_val (&h, idx_type); + return nm_hash_complete (&h); +} + +static gboolean +_dict_idx_entries_equal (const NMDedupMultiEntry *entry_a, + const NMDedupMultiEntry *entry_b) +{ + const NMDedupMultiIdxType *idx_type_a, *idx_type_b; + const NMDedupMultiObj *obj_a, *obj_b; + gboolean lookup_head_a, lookup_head_b; + + _entry_unpack (entry_a, &idx_type_a, &obj_a, &lookup_head_a); + _entry_unpack (entry_b, &idx_type_b, &obj_b, &lookup_head_b); + + if ( idx_type_a != idx_type_b + || lookup_head_a != lookup_head_b) + return FALSE; + if (!nm_dedup_multi_idx_type_partition_equal (idx_type_a, obj_a, obj_b)) + return FALSE; + if ( !lookup_head_a + && !nm_dedup_multi_idx_type_id_equal (idx_type_a, obj_a, obj_b)) + return FALSE; + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +_add (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj, + NMDedupMultiEntry *entry, + NMDedupMultiIdxMode mode, + const NMDedupMultiEntry *entry_order, + NMDedupMultiHeadEntry *head_existing, + const NMDedupMultiEntry **out_entry, + const NMDedupMultiObj **out_obj_old) +{ + NMDedupMultiHeadEntry *head_entry; + const NMDedupMultiObj *obj_new, *obj_old; + gboolean add_head_entry = FALSE; + + nm_assert (self); + ASSERT_idx_type (idx_type); + nm_assert (obj); + nm_assert (NM_IN_SET (mode, + NM_DEDUP_MULTI_IDX_MODE_PREPEND, + NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE, + NM_DEDUP_MULTI_IDX_MODE_APPEND, + NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE)); + nm_assert (!head_existing || head_existing->idx_type == idx_type); + nm_assert (({ + const NMDedupMultiHeadEntry *_h; + gboolean _ok = TRUE; + if (head_existing) { + _h = nm_dedup_multi_index_lookup_head (self, idx_type, obj); + if (head_existing == NM_DEDUP_MULTI_HEAD_ENTRY_MISSING) + _ok = (_h == NULL); + else + _ok = (_h == head_existing); + } + _ok; + })); + + if (entry) { + gboolean changed = FALSE; + + nm_dedup_multi_entry_set_dirty (entry, FALSE); + + nm_assert (!head_existing || entry->head == head_existing); + + if (entry_order) { + nm_assert (entry_order->head == entry->head); + nm_assert (c_list_contains (&entry->lst_entries, &entry_order->lst_entries)); + nm_assert (c_list_contains (&entry_order->lst_entries, &entry->lst_entries)); + } + + switch (mode) { + case NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE: + if (entry_order) { + if ( entry_order != entry + && entry->lst_entries.next != &entry_order->lst_entries) { + c_list_unlink (&entry->lst_entries); + c_list_link_before ((CList *) &entry_order->lst_entries, &entry->lst_entries); + changed = TRUE; + } + } else { + if (entry->lst_entries.prev != &entry->head->lst_entries_head) { + c_list_unlink (&entry->lst_entries); + c_list_link_front ((CList *) &entry->head->lst_entries_head, &entry->lst_entries); + changed = TRUE; + } + } + break; + case NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE: + if (entry_order) { + if ( entry_order != entry + && entry->lst_entries.prev != &entry_order->lst_entries) { + c_list_unlink (&entry->lst_entries); + c_list_link_after ((CList *) &entry_order->lst_entries, &entry->lst_entries); + changed = TRUE; + } + } else { + if (entry->lst_entries.next != &entry->head->lst_entries_head) { + c_list_unlink (&entry->lst_entries); + c_list_link_tail ((CList *) &entry->head->lst_entries_head, &entry->lst_entries); + changed = TRUE; + } + } + break; + case NM_DEDUP_MULTI_IDX_MODE_PREPEND: + case NM_DEDUP_MULTI_IDX_MODE_APPEND: + break; + }; + + nm_assert (obj->klass == ((const NMDedupMultiObj *) entry->obj)->klass); + if ( obj == entry->obj + || obj->klass->obj_full_equal (obj, + entry->obj)) { + NM_SET_OUT (out_entry, entry); + NM_SET_OUT (out_obj_old, nm_dedup_multi_obj_ref (entry->obj)); + return changed; + } + + obj_new = nm_dedup_multi_index_obj_intern (self, obj); + + obj_old = entry->obj; + entry->obj = obj_new; + + NM_SET_OUT (out_entry, entry); + if (out_obj_old) + *out_obj_old = obj_old; + else + nm_dedup_multi_obj_unref (obj_old); + return TRUE; + } + + if ( idx_type->klass->idx_obj_partitionable + && !idx_type->klass->idx_obj_partitionable (idx_type, obj)) { + /* this object cannot be partitioned by this idx_type. */ + nm_assert (!head_existing || head_existing == NM_DEDUP_MULTI_HEAD_ENTRY_MISSING); + NM_SET_OUT (out_entry, NULL); + NM_SET_OUT (out_obj_old, NULL); + return FALSE; + } + + obj_new = nm_dedup_multi_index_obj_intern (self, obj); + + if (!head_existing) + head_entry = _entry_lookup_head (self, idx_type, obj_new); + else if (head_existing == NM_DEDUP_MULTI_HEAD_ENTRY_MISSING) + head_entry = NULL; + else + head_entry = head_existing; + + if (!head_entry) { + head_entry = g_slice_new0 (NMDedupMultiHeadEntry); + head_entry->is_head = TRUE; + head_entry->idx_type = idx_type; + c_list_init (&head_entry->lst_entries_head); + c_list_link_tail (&idx_type->lst_idx_head, &head_entry->lst_idx); + add_head_entry = TRUE; + } else + nm_assert (c_list_contains (&idx_type->lst_idx_head, &head_entry->lst_idx)); + + if (entry_order) { + nm_assert (!add_head_entry); + nm_assert (entry_order->head == head_entry); + nm_assert (c_list_contains (&head_entry->lst_entries_head, &entry_order->lst_entries)); + nm_assert (c_list_contains (&entry_order->lst_entries, &head_entry->lst_entries_head)); + } + + entry = g_slice_new0 (NMDedupMultiEntry); + entry->obj = obj_new; + entry->head = head_entry; + + switch (mode) { + case NM_DEDUP_MULTI_IDX_MODE_PREPEND: + case NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE: + if (entry_order) + c_list_link_before ((CList *) &entry_order->lst_entries, &entry->lst_entries); + else + c_list_link_front (&head_entry->lst_entries_head, &entry->lst_entries); + break; + default: + if (entry_order) + c_list_link_after ((CList *) &entry_order->lst_entries, &entry->lst_entries); + else + c_list_link_tail (&head_entry->lst_entries_head, &entry->lst_entries); + break; + }; + + idx_type->len++; + head_entry->len++; + + if ( add_head_entry + && !nm_g_hash_table_add (self->idx_entries, head_entry)) + nm_assert_not_reached (); + + if (!nm_g_hash_table_add (self->idx_entries, entry)) + nm_assert_not_reached (); + + NM_SET_OUT (out_entry, entry); + NM_SET_OUT (out_obj_old, NULL); + return TRUE; +} + +gboolean +nm_dedup_multi_index_add (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj, + NMDedupMultiIdxMode mode, + const NMDedupMultiEntry **out_entry, + /* const NMDedupMultiObj ** */ gpointer out_obj_old) +{ + NMDedupMultiEntry *entry; + + g_return_val_if_fail (self, FALSE); + g_return_val_if_fail (idx_type, FALSE); + g_return_val_if_fail (obj, FALSE); + g_return_val_if_fail (NM_IN_SET (mode, + NM_DEDUP_MULTI_IDX_MODE_PREPEND, + NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE, + NM_DEDUP_MULTI_IDX_MODE_APPEND, + NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE), + FALSE); + + entry = _entry_lookup_obj (self, idx_type, obj); + return _add (self, idx_type, obj, + entry, mode, + NULL, NULL, + out_entry, out_obj_old); +} + +/* nm_dedup_multi_index_add_full: + * @self: the index instance. + * @idx_type: the index handle for storing @obj. + * @obj: the NMDedupMultiObj instance to add. + * @mode: whether to append or prepend the new item. If @entry_order is given, + * the entry will be sorted after/before, instead of appending/prepending to + * the entire list. If a comparable object is already tracked, then it may + * still be resorted by specifying one of the "FORCE" modes. + * @entry_order: if not NULL, the new entry will be sorted before or after @entry_order. + * If given, @entry_order MUST be tracked by @self, and the object it points to MUST + * be in the same partition tracked by @idx_type. That is, they must have the same + * head_entry and it means, you must ensure that @entry_order and the created/modified + * entry will share the same head. + * @entry_existing: if not NULL, it safes a hash lookup of the entry where the + * object will be placed in. You can omit this, and it will be automatically + * detected (at the expense of an additional hash lookup). + * Basically, this is the result of nm_dedup_multi_index_lookup_obj(), + * with the pecularity that if you know that @obj is not yet tracked, + * you may specify %NM_DEDUP_MULTI_ENTRY_MISSING. + * @head_existing: an optional argument to safe a lookup for the head. If specified, + * it must be identical to nm_dedup_multi_index_lookup_head(), with the pecularity + * that if the head is not yet tracked, you may specify %NM_DEDUP_MULTI_HEAD_ENTRY_MISSING + * @out_entry: if give, return the added entry. This entry may have already exists (update) + * or be newly created. If @obj is not partitionable according to @idx_type, @obj + * is not to be added and it returns %NULL. + * @out_obj_old: if given, return the previously contained object. It only + * returns a object, if a matching entry was tracked previously, not if a + * new entry was created. Note that when passing @out_obj_old you obtain a reference + * to the boxed object and MUST return it with nm_dedup_multi_obj_unref(). + * + * Adds and object to the index. + * + * Return: %TRUE if anything changed, %FALSE if nothing changed. + */ +gboolean +nm_dedup_multi_index_add_full (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj, + NMDedupMultiIdxMode mode, + const NMDedupMultiEntry *entry_order, + const NMDedupMultiEntry *entry_existing, + const NMDedupMultiHeadEntry *head_existing, + const NMDedupMultiEntry **out_entry, + /* const NMDedupMultiObj ** */ gpointer out_obj_old) +{ + NMDedupMultiEntry *entry; + + g_return_val_if_fail (self, FALSE); + g_return_val_if_fail (idx_type, FALSE); + g_return_val_if_fail (obj, FALSE); + g_return_val_if_fail (NM_IN_SET (mode, + NM_DEDUP_MULTI_IDX_MODE_PREPEND, + NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE, + NM_DEDUP_MULTI_IDX_MODE_APPEND, + NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE), + FALSE); + + if (entry_existing == NULL) + entry = _entry_lookup_obj (self, idx_type, obj); + else if (entry_existing == NM_DEDUP_MULTI_ENTRY_MISSING) { + nm_assert (!_entry_lookup_obj (self, idx_type, obj)); + entry = NULL; + } else { + nm_assert (entry_existing == _entry_lookup_obj (self, idx_type, obj)); + entry = (NMDedupMultiEntry *) entry_existing; + } + return _add (self, idx_type, obj, + entry, + mode, entry_order, + (NMDedupMultiHeadEntry *) head_existing, + out_entry, out_obj_old); +} + +/*****************************************************************************/ + +static void +_remove_entry (NMDedupMultiIndex *self, + NMDedupMultiEntry *entry, + gboolean *out_head_entry_removed) +{ + const NMDedupMultiObj *obj; + NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIdxType *idx_type; + + nm_assert (self); + nm_assert (entry); + nm_assert (entry->obj); + nm_assert (entry->head); + nm_assert (!c_list_is_empty (&entry->lst_entries)); + nm_assert (g_hash_table_lookup (self->idx_entries, entry) == entry); + + head_entry = (NMDedupMultiHeadEntry *) entry->head; + obj = entry->obj; + + nm_assert (head_entry); + nm_assert (head_entry->len > 0); + nm_assert (g_hash_table_lookup (self->idx_entries, head_entry) == head_entry); + + idx_type = (NMDedupMultiIdxType *) head_entry->idx_type; + ASSERT_idx_type (idx_type); + + nm_assert (idx_type->len >= head_entry->len); + if (--head_entry->len > 0) { + nm_assert (idx_type->len > 1); + idx_type->len--; + head_entry = NULL; + } + + NM_SET_OUT (out_head_entry_removed, head_entry != NULL); + + if (!g_hash_table_remove (self->idx_entries, entry)) + nm_assert_not_reached (); + + if ( head_entry + && !g_hash_table_remove (self->idx_entries, head_entry)) + nm_assert_not_reached (); + + c_list_unlink (&entry->lst_entries); + g_slice_free (NMDedupMultiEntry, entry); + + if (head_entry) { + nm_assert (c_list_is_empty (&head_entry->lst_entries_head)); + c_list_unlink (&head_entry->lst_idx); + g_slice_free (NMDedupMultiHeadEntry, head_entry); + } + + nm_dedup_multi_obj_unref (obj); +} + +static guint +_remove_head (NMDedupMultiIndex *self, + NMDedupMultiHeadEntry *head_entry, + gboolean remove_all /* otherwise just dirty ones */, + gboolean mark_survivors_dirty) +{ + guint n; + gboolean head_entry_removed; + CList *iter_entry, *iter_entry_safe; + + nm_assert (self); + nm_assert (head_entry); + nm_assert (head_entry->len > 0); + nm_assert (head_entry->len == c_list_length (&head_entry->lst_entries_head)); + nm_assert (g_hash_table_lookup (self->idx_entries, head_entry) == head_entry); + + n = 0; + c_list_for_each_safe (iter_entry, iter_entry_safe, &head_entry->lst_entries_head) { + NMDedupMultiEntry *entry; + + entry = c_list_entry (iter_entry, NMDedupMultiEntry, lst_entries); + if ( remove_all + || entry->dirty) { + _remove_entry (self, + entry, + &head_entry_removed); + n++; + if (head_entry_removed) + break; + } else if (mark_survivors_dirty) + nm_dedup_multi_entry_set_dirty (entry, TRUE); + } + + return n; +} + +static guint +_remove_idx_entry (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + gboolean remove_all /* otherwise just dirty ones */, + gboolean mark_survivors_dirty) +{ + guint n; + CList *iter_idx, *iter_idx_safe; + + nm_assert (self); + ASSERT_idx_type (idx_type); + + n = 0; + c_list_for_each_safe (iter_idx, iter_idx_safe, &idx_type->lst_idx_head) { + n += _remove_head (self, + c_list_entry (iter_idx, NMDedupMultiHeadEntry, lst_idx), + remove_all, mark_survivors_dirty); + } + return n; +} + +guint +nm_dedup_multi_index_remove_entry (NMDedupMultiIndex *self, + gconstpointer entry) +{ + g_return_val_if_fail (self, 0); + + nm_assert (entry); + + if (!((NMDedupMultiEntry *) entry)->is_head) { + _remove_entry (self, (NMDedupMultiEntry *) entry, NULL); + return 1; + } + return _remove_head (self, (NMDedupMultiHeadEntry *) entry, TRUE, FALSE); +} + +guint +nm_dedup_multi_index_remove_obj (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj, + /*const NMDedupMultiObj ** */ gconstpointer *out_obj) +{ + const NMDedupMultiEntry *entry; + + entry = nm_dedup_multi_index_lookup_obj (self, idx_type, obj); + if (!entry) { + NM_SET_OUT (out_obj, NULL); + return 0; + } + + /* since we are about to remove the object, we obviously pass + * a reference to @out_obj, the caller MUST unref the object, + * if he chooses to provide @out_obj. */ + NM_SET_OUT (out_obj, nm_dedup_multi_obj_ref (entry->obj)); + + _remove_entry (self, (NMDedupMultiEntry *) entry, NULL); + return 1; +} + +guint +nm_dedup_multi_index_remove_head (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj) +{ + const NMDedupMultiHeadEntry *entry; + + entry = nm_dedup_multi_index_lookup_head (self, idx_type, obj); + return entry + ? _remove_head (self, (NMDedupMultiHeadEntry *) entry, TRUE, FALSE) + : 0; +} + +guint +nm_dedup_multi_index_remove_idx (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type) +{ + g_return_val_if_fail (self, 0); + g_return_val_if_fail (idx_type, 0); + + return _remove_idx_entry (self, idx_type, TRUE, FALSE); +} + +/*****************************************************************************/ + +/** + * nm_dedup_multi_index_lookup_obj: + * @self: the index cache + * @idx_type: the lookup index type + * @obj: the object to lookup. This means the match is performed + * according to NMDedupMultiIdxTypeClass's idx_obj_id_equal() + * of @idx_type. + * + * Returns: the cache entry or %NULL if the entry wasn't found. + */ +const NMDedupMultiEntry * +nm_dedup_multi_index_lookup_obj (const NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj) +{ + g_return_val_if_fail (self, FALSE); + g_return_val_if_fail (idx_type, FALSE); + g_return_val_if_fail (obj, FALSE); + + nm_assert (idx_type && idx_type->klass); + return _entry_lookup_obj (self, idx_type, obj); +} + +/** + * nm_dedup_multi_index_lookup_head: + * @self: the index cache + * @idx_type: the lookup index type + * @obj: the object to lookup, of type "const NMDedupMultiObj *". + * Depending on the idx_type, you *must* also provide a selector + * object, even when looking up the list head. That is, because + * the idx_type implementation may choose to partition the objects + * in distinct list, so you need a selector object to know which + * list head to lookup. + * + * Returns: the cache entry or %NULL if the entry wasn't found. + */ +const NMDedupMultiHeadEntry * +nm_dedup_multi_index_lookup_head (const NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj) +{ + g_return_val_if_fail (self, FALSE); + g_return_val_if_fail (idx_type, FALSE); + + return _entry_lookup_head (self, idx_type, obj); +} + +/*****************************************************************************/ + +void +nm_dedup_multi_index_dirty_set_head (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj) +{ + NMDedupMultiHeadEntry *head_entry; + CList *iter_entry; + + g_return_if_fail (self); + g_return_if_fail (idx_type); + + head_entry = _entry_lookup_head (self, idx_type, obj); + if (!head_entry) + return; + + c_list_for_each (iter_entry, &head_entry->lst_entries_head) { + NMDedupMultiEntry *entry; + + entry = c_list_entry (iter_entry, NMDedupMultiEntry, lst_entries); + nm_dedup_multi_entry_set_dirty (entry, TRUE); + } +} + +void +nm_dedup_multi_index_dirty_set_idx (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type) +{ + CList *iter_idx, *iter_entry; + + g_return_if_fail (self); + g_return_if_fail (idx_type); + + c_list_for_each (iter_idx, &idx_type->lst_idx_head) { + NMDedupMultiHeadEntry *head_entry; + + head_entry = c_list_entry (iter_idx, NMDedupMultiHeadEntry, lst_idx); + c_list_for_each (iter_entry, &head_entry->lst_entries_head) { + NMDedupMultiEntry *entry; + + entry = c_list_entry (iter_entry, NMDedupMultiEntry, lst_entries); + nm_dedup_multi_entry_set_dirty (entry, TRUE); + } + } +} + +/** + * nm_dedup_multi_index_dirty_remove_idx: + * @self: the index instance + * @idx_type: the index-type to select the objects. + * @mark_survivors_dirty: while the function removes all entries that are + * marked as dirty, if @set_dirty is true, the surviving objects + * will be marked dirty right away. + * + * Deletes all entries for @idx_type that are marked dirty. Only + * non-dirty objects survive. If @mark_survivors_dirty is set to TRUE, the survivors + * are marked as dirty right away. + * + * Returns: number of deleted entries. + */ +guint +nm_dedup_multi_index_dirty_remove_idx (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + gboolean mark_survivors_dirty) +{ + g_return_val_if_fail (self, 0); + g_return_val_if_fail (idx_type, 0); + + return _remove_idx_entry (self, idx_type, FALSE, mark_survivors_dirty); +} + +/*****************************************************************************/ + +static guint +_dict_idx_objs_hash (const NMDedupMultiObj *obj) +{ + NMHashState h; + + nm_hash_init (&h, 1748638583u); + obj->klass->obj_full_hash_update (obj, &h); + return nm_hash_complete (&h); +} + +static gboolean +_dict_idx_objs_equal (const NMDedupMultiObj *obj_a, + const NMDedupMultiObj *obj_b) +{ + return obj_a == obj_b + || ( obj_a->klass == obj_b->klass + && obj_a->klass->obj_full_equal (obj_a, obj_b)); +} + +void +nm_dedup_multi_index_obj_release (NMDedupMultiIndex *self, + /* const NMDedupMultiObj * */ gconstpointer obj) +{ + nm_assert (self); + nm_assert (obj); + nm_assert (g_hash_table_lookup (self->idx_objs, obj) == obj); + nm_assert (((const NMDedupMultiObj *) obj)->_multi_idx == self); + + ((NMDedupMultiObj *) obj)->_multi_idx = NULL; + if (!g_hash_table_remove (self->idx_objs, obj)) + nm_assert_not_reached (); +} + +gconstpointer +nm_dedup_multi_index_obj_find (NMDedupMultiIndex *self, + /* const NMDedupMultiObj * */ gconstpointer obj) +{ + g_return_val_if_fail (self, NULL); + g_return_val_if_fail (obj, NULL); + + return g_hash_table_lookup (self->idx_objs, obj); +} + +gconstpointer +nm_dedup_multi_index_obj_intern (NMDedupMultiIndex *self, + /* const NMDedupMultiObj * */ gconstpointer obj) +{ + const NMDedupMultiObj *obj_new = obj; + const NMDedupMultiObj *obj_old; + + nm_assert (self); + nm_assert (obj_new); + + if (obj_new->_multi_idx == self) { + nm_assert (g_hash_table_lookup (self->idx_objs, obj_new) == obj_new); + nm_dedup_multi_obj_ref (obj_new); + return obj_new; + } + + obj_old = g_hash_table_lookup (self->idx_objs, obj_new); + nm_assert (obj_old != obj_new); + + if (obj_old) { + nm_assert (obj_old->_multi_idx == self); + nm_dedup_multi_obj_ref (obj_old); + return obj_old; + } + + if (nm_dedup_multi_obj_needs_clone (obj_new)) + obj_new = nm_dedup_multi_obj_clone (obj_new); + else + obj_new = nm_dedup_multi_obj_ref (obj_new); + + nm_assert (obj_new); + nm_assert (!obj_new->_multi_idx); + + if (!nm_g_hash_table_add (self->idx_objs, (gpointer) obj_new)) + nm_assert_not_reached (); + + ((NMDedupMultiObj *) obj_new)->_multi_idx = self; + return obj_new; +} + +const NMDedupMultiObj * +nm_dedup_multi_obj_unref (const NMDedupMultiObj *obj) +{ + if (obj) { + nm_assert (obj->_ref_count > 0); + nm_assert (obj->_ref_count != NM_OBJ_REF_COUNT_STACKINIT); + +again: + if (--(((NMDedupMultiObj *) obj)->_ref_count) <= 0) { + if (obj->_multi_idx) { + /* restore the ref-count to 1 and release the object first + * from the index. Then, retry again to unref. */ + ((NMDedupMultiObj *) obj)->_ref_count++; + nm_dedup_multi_index_obj_release (obj->_multi_idx, obj); + nm_assert (obj->_ref_count == 1); + nm_assert (!obj->_multi_idx); + goto again; + } + + obj->klass->obj_destroy ((NMDedupMultiObj *) obj); + } + } + + return NULL; +} + +gboolean +nm_dedup_multi_obj_needs_clone (const NMDedupMultiObj *obj) +{ + nm_assert (obj); + + if ( obj->_multi_idx + || obj->_ref_count == NM_OBJ_REF_COUNT_STACKINIT) + return TRUE; + + if ( obj->klass->obj_needs_clone + && obj->klass->obj_needs_clone (obj)) + return TRUE; + + return FALSE; +} + +const NMDedupMultiObj * +nm_dedup_multi_obj_clone (const NMDedupMultiObj *obj) +{ + const NMDedupMultiObj *o; + + nm_assert (obj); + + o = obj->klass->obj_clone (obj); + nm_assert (o); + nm_assert (o->_ref_count == 1); + return o; +} + +gconstpointer * +nm_dedup_multi_objs_to_array_head (const NMDedupMultiHeadEntry *head_entry, + NMDedupMultiFcnSelectPredicate predicate, + gpointer user_data, + guint *out_len) +{ + gconstpointer *result; + CList *iter; + guint i; + + if (!head_entry) { + NM_SET_OUT (out_len, 0); + return NULL; + } + + result = g_new (gconstpointer, head_entry->len + 1); + i = 0; + c_list_for_each (iter, &head_entry->lst_entries_head) { + const NMDedupMultiObj *obj = c_list_entry (iter, NMDedupMultiEntry, lst_entries)->obj; + + if ( !predicate + || predicate (obj, user_data)) { + nm_assert (i < head_entry->len); + result[i++] = obj; + } + } + + if (i == 0) { + g_free (result); + NM_SET_OUT (out_len, 0); + return NULL; + } + + nm_assert (i <= head_entry->len); + NM_SET_OUT (out_len, i); + result[i++] = NULL; + return result; +} + +GPtrArray * +nm_dedup_multi_objs_to_ptr_array_head (const NMDedupMultiHeadEntry *head_entry, + NMDedupMultiFcnSelectPredicate predicate, + gpointer user_data) +{ + GPtrArray *result; + CList *iter; + + if (!head_entry) + return NULL; + + result = g_ptr_array_new_full (head_entry->len, + (GDestroyNotify) nm_dedup_multi_obj_unref); + c_list_for_each (iter, &head_entry->lst_entries_head) { + const NMDedupMultiObj *obj = c_list_entry (iter, NMDedupMultiEntry, lst_entries)->obj; + + if ( !predicate + || predicate (obj, user_data)) + g_ptr_array_add (result, (gpointer) nm_dedup_multi_obj_ref (obj)); + } + + if (result->len == 0) { + g_ptr_array_unref (result); + return NULL; + } + return result; +} + +/** + * nm_dedup_multi_entry_reorder: + * @entry: the entry to reorder. It must not be NULL (and tracked in an index). + * @entry_order: (allow-none): an optional other entry. It MUST be in the same + * list as entry. If given, @entry will be ordered after/before @entry_order. + * If left at %NULL, @entry will be moved to the front/end of the list. + * @order_after: if @entry_order is given, %TRUE means to move @entry after + * @entry_order (otherwise before). + * If @entry_order is %NULL, %TRUE means to move @entry to the tail of the list + * (otherwise the beginning). Note that "tail of the list" here means that @entry + * will be linked before the head of the circular list. + * + * Returns: %TRUE, if anything was changed. Otherwise, @entry was already at the + * right place and nothing was done. + */ +gboolean +nm_dedup_multi_entry_reorder (const NMDedupMultiEntry *entry, + const NMDedupMultiEntry *entry_order, + gboolean order_after) +{ + nm_assert (entry); + + if (!entry_order) { + const NMDedupMultiHeadEntry *head_entry = entry->head; + + nm_assert (c_list_contains (&head_entry->lst_entries_head, &entry->lst_entries)); + if (order_after) { + if (head_entry->lst_entries_head.prev != &entry->lst_entries) { + c_list_unlink ((CList *) &entry->lst_entries); + c_list_link_tail ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries); + return TRUE; + } + } else { + if (head_entry->lst_entries_head.next != &entry->lst_entries) { + c_list_unlink ((CList *) &entry->lst_entries); + c_list_link_front ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries); + return TRUE; + } + } + } else if (entry != entry_order) { + if (order_after) { + if (entry_order->lst_entries.next != &entry->lst_entries) { + c_list_unlink ((CList *) &entry->lst_entries); + c_list_link_after ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries); + return TRUE; + } + } else { + if (entry_order->lst_entries.prev != &entry->lst_entries) { + c_list_unlink ((CList *) &entry->lst_entries); + c_list_link_before ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries); + return TRUE; + } + } + } + + return FALSE; +} + +/*****************************************************************************/ + +NMDedupMultiIndex * +nm_dedup_multi_index_new (void) +{ + NMDedupMultiIndex *self; + + self = g_slice_new0 (NMDedupMultiIndex); + self->ref_count = 1; + self->idx_entries = g_hash_table_new ((GHashFunc) _dict_idx_entries_hash, (GEqualFunc) _dict_idx_entries_equal); + self->idx_objs = g_hash_table_new ((GHashFunc) _dict_idx_objs_hash, (GEqualFunc) _dict_idx_objs_equal); + return self; +} + +NMDedupMultiIndex * +nm_dedup_multi_index_ref (NMDedupMultiIndex *self) +{ + g_return_val_if_fail (self, NULL); + g_return_val_if_fail (self->ref_count > 0, NULL); + + self->ref_count++; + return self; +} + +NMDedupMultiIndex * +nm_dedup_multi_index_unref (NMDedupMultiIndex *self) +{ + GHashTableIter iter; + const NMDedupMultiIdxType *idx_type; + NMDedupMultiEntry *entry; + const NMDedupMultiObj *obj; + + g_return_val_if_fail (self, NULL); + g_return_val_if_fail (self->ref_count > 0, NULL); + + if (--self->ref_count > 0) + return NULL; + +more: + g_hash_table_iter_init (&iter, self->idx_entries); + while (g_hash_table_iter_next (&iter, (gpointer *) &entry, NULL)) { + if (entry->is_head) + idx_type = ((NMDedupMultiHeadEntry *) entry)->idx_type; + else + idx_type = entry->head->idx_type; + _remove_idx_entry (self, (NMDedupMultiIdxType *) idx_type, TRUE, FALSE); + goto more; + } + + nm_assert (g_hash_table_size (self->idx_entries) == 0); + + g_hash_table_iter_init (&iter, self->idx_objs); + while (g_hash_table_iter_next (&iter, (gpointer *) &obj, NULL)) { + nm_assert (obj->_multi_idx == self); + ((NMDedupMultiObj * )obj)->_multi_idx = NULL; + } + g_hash_table_remove_all (self->idx_objs); + + g_hash_table_unref (self->idx_entries); + g_hash_table_unref (self->idx_objs); + + g_slice_free (NMDedupMultiIndex, self); + return NULL; +} diff --git a/shared/nm-utils/nm-dedup-multi.h b/shared/nm-utils/nm-dedup-multi.h new file mode 100644 index 00000000..bebfe43d --- /dev/null +++ b/shared/nm-utils/nm-dedup-multi.h @@ -0,0 +1,437 @@ +/* -*- 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 2017 Red Hat, Inc. + */ + +#ifndef __NM_DEDUP_MULTI_H__ +#define __NM_DEDUP_MULTI_H__ + +#include "nm-obj.h" +#include "c-list-util.h" + +/*****************************************************************************/ + +struct _NMHashState; + +typedef struct _NMDedupMultiObj NMDedupMultiObj; +typedef struct _NMDedupMultiObjClass NMDedupMultiObjClass; +typedef struct _NMDedupMultiIdxType NMDedupMultiIdxType; +typedef struct _NMDedupMultiIdxTypeClass NMDedupMultiIdxTypeClass; +typedef struct _NMDedupMultiEntry NMDedupMultiEntry; +typedef struct _NMDedupMultiHeadEntry NMDedupMultiHeadEntry; +typedef struct _NMDedupMultiIndex NMDedupMultiIndex; + +typedef enum _NMDedupMultiIdxMode { + NM_DEDUP_MULTI_IDX_MODE_PREPEND, + + NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE, + + /* append new objects to the end of the list. + * If the object is already in the cache, don't move it. */ + NM_DEDUP_MULTI_IDX_MODE_APPEND, + + /* like NM_DEDUP_MULTI_IDX_MODE_APPEND, but if the object + * is already in teh cache, move it to the end. */ + NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE, +} NMDedupMultiIdxMode; + +/*****************************************************************************/ + +struct _NMDedupMultiObj { + union { + NMObjBaseInst parent; + const NMDedupMultiObjClass *klass; + }; + NMDedupMultiIndex *_multi_idx; + guint _ref_count; +}; + +struct _NMDedupMultiObjClass { + NMObjBaseClass parent; + + const NMDedupMultiObj *(*obj_clone) (const NMDedupMultiObj *obj); + + gboolean (*obj_needs_clone) (const NMDedupMultiObj *obj); + + void (*obj_destroy) (NMDedupMultiObj *obj); + + /* the NMDedupMultiObj can be deduplicated. For that the obj_full_hash_update() + * and obj_full_equal() compare *all* fields of the object, even minor ones. */ + void (*obj_full_hash_update) (const NMDedupMultiObj *obj, + struct _NMHashState *h); + gboolean (*obj_full_equal) (const NMDedupMultiObj *obj_a, + const NMDedupMultiObj *obj_b); +}; + +/*****************************************************************************/ + +static inline const NMDedupMultiObj * +nm_dedup_multi_obj_ref (const NMDedupMultiObj *obj) +{ + /* ref and unref accept const pointers. Objects is supposed to be shared + * and kept immutable. Disallowing to take/retrun a reference to a const + * NMPObject is cumbersome, because callers are precisely expected to + * keep a ref on the otherwise immutable object. */ + + nm_assert (obj); + nm_assert (obj->_ref_count != NM_OBJ_REF_COUNT_STACKINIT); + nm_assert (obj->_ref_count > 0); + + ((NMDedupMultiObj *) obj)->_ref_count++; + return obj; +} + +const NMDedupMultiObj *nm_dedup_multi_obj_unref (const NMDedupMultiObj *obj); +const NMDedupMultiObj *nm_dedup_multi_obj_clone (const NMDedupMultiObj *obj); +gboolean nm_dedup_multi_obj_needs_clone (const NMDedupMultiObj *obj); + +gconstpointer nm_dedup_multi_index_obj_intern (NMDedupMultiIndex *self, + /* const NMDedupMultiObj * */ gconstpointer obj); + +void nm_dedup_multi_index_obj_release (NMDedupMultiIndex *self, + /* const NMDedupMultiObj * */ gconstpointer obj); + +/* const NMDedupMultiObj * */ gconstpointer nm_dedup_multi_index_obj_find (NMDedupMultiIndex *self, + /* const NMDedupMultiObj * */ gconstpointer obj); + +/*****************************************************************************/ + +/* the NMDedupMultiIdxType is an access handle under which you can store and + * retrieve NMDedupMultiObj instances in NMDedupMultiIndex. + * + * The NMDedupMultiIdxTypeClass determines it's behavior, but you can have + * multiple instances (of the same class). + * + * For example, NMIP4Config can have idx-type to put there all IPv4 Routes. + * This idx-type instance is private to the NMIP4Config instance. Basically, + * the NMIP4Config instance uses the idx-type to maintain an ordered list + * of routes in NMDedupMultiIndex. + * + * However, a NMDedupMultiIdxType may also partition the set of objects + * in multiple distinct lists. NMIP4Config doesn't do that (because instead + * of creating one idx-type for IPv4 and IPv6 routes, it just cretaes + * to distinct idx-types, one for each address family. + * This partitioning is used by NMPlatform to maintain a lookup index for + * routes by ifindex. As the ifindex is dynamic, it does not create an + * idx-type instance for each ifindex. Instead, it has one idx-type for + * all routes. But whenever accessing NMDedupMultiIndex with an NMDedupMultiObj, + * the partitioning NMDedupMultiIdxType takes into accound the NMDedupMultiObj + * instance to associate it with the right list. + * + * Hence, a NMDedupMultiIdxEntry has a list of possibly multiple NMDedupMultiHeadEntry + * instances, which each is the head for a list of NMDedupMultiEntry instances. + * In the platform example, the NMDedupMultiHeadEntry parition the indexed objects + * by their ifindex. */ +struct _NMDedupMultiIdxType { + union { + NMObjBaseInst parent; + const NMDedupMultiIdxTypeClass *klass; + }; + + CList lst_idx_head; + + guint len; +}; + +void nm_dedup_multi_idx_type_init (NMDedupMultiIdxType *idx_type, + const NMDedupMultiIdxTypeClass *klass); + +struct _NMDedupMultiIdxTypeClass { + NMObjBaseClass parent; + + void (*idx_obj_id_hash_update) (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj, + struct _NMHashState *h); + gboolean (*idx_obj_id_equal) (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj_a, + const NMDedupMultiObj *obj_b); + + /* an NMDedupMultiIdxTypeClass which implements partitioning of the + * tracked objects, must implement the idx_obj_partition*() functions. + * + * idx_obj_partitionable() may return NULL if the object cannot be tracked. + * For example, a index for routes by ifindex, may not want to track any + * routes that don't have a valid ifindex. If the idx-type says that the + * object is not partitionable, it is never added to the NMDedupMultiIndex. */ + gboolean (*idx_obj_partitionable) (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj); + void (*idx_obj_partition_hash_update) (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj, + struct _NMHashState *h); + gboolean (*idx_obj_partition_equal) (const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj *obj_a, + const NMDedupMultiObj *obj_b); +}; + +static inline gboolean +nm_dedup_multi_idx_type_id_equal (const NMDedupMultiIdxType *idx_type, + /* const NMDedupMultiObj * */ gconstpointer obj_a, + /* const NMDedupMultiObj * */ gconstpointer obj_b) +{ + nm_assert (idx_type); + return obj_a == obj_b + || idx_type->klass->idx_obj_id_equal (idx_type, + obj_a, + obj_b); +} + +static inline gboolean +nm_dedup_multi_idx_type_partition_equal (const NMDedupMultiIdxType *idx_type, + /* const NMDedupMultiObj * */ gconstpointer obj_a, + /* const NMDedupMultiObj * */ gconstpointer obj_b) +{ + nm_assert (idx_type); + if (idx_type->klass->idx_obj_partition_equal) { + nm_assert (obj_a); + nm_assert (obj_b); + return obj_a == obj_b + || idx_type->klass->idx_obj_partition_equal (idx_type, + obj_a, + obj_b); + } + return TRUE; +} + +/*****************************************************************************/ + +struct _NMDedupMultiEntry { + + /* this is the list of all entries that share the same head entry. + * All entries compare equal according to idx_obj_partition_equal(). */ + CList lst_entries; + + /* const NMDedupMultiObj * */ gconstpointer obj; + + bool is_head; + bool dirty; + + const NMDedupMultiHeadEntry *head; +}; + +struct _NMDedupMultiHeadEntry { + + /* this is the list of all entries that share the same head entry. + * All entries compare equal according to idx_obj_partition_equal(). */ + CList lst_entries_head; + + const NMDedupMultiIdxType *idx_type; + + bool is_head; + + guint len; + + CList lst_idx; +}; + +/*****************************************************************************/ + +static inline gconstpointer +nm_dedup_multi_entry_get_obj (const NMDedupMultiEntry *entry) +{ + /* convenience method that allows to skip the %NULL check on + * @entry. Think of the NULL-conditional operator ?. of C# */ + return entry ? entry->obj : NULL; +} + +/*****************************************************************************/ + +static inline void +nm_dedup_multi_entry_set_dirty (const NMDedupMultiEntry *entry, + gboolean dirty) +{ + /* NMDedupMultiEntry is always exposed as a const object, because it is not + * supposed to be modified outside NMDedupMultiIndex API. Except the "dirty" + * flag. In C++ speak, it is a mutable field. + * + * Add this inline function, to cast-away constness and set the dirty flag. */ + nm_assert (entry); + ((NMDedupMultiEntry *) entry)->dirty = dirty; +} + +/*****************************************************************************/ + +NMDedupMultiIndex *nm_dedup_multi_index_new (void); +NMDedupMultiIndex *nm_dedup_multi_index_ref (NMDedupMultiIndex *self); +NMDedupMultiIndex *nm_dedup_multi_index_unref (NMDedupMultiIndex *self); + +static inline void +_nm_auto_unref_dedup_multi_index (NMDedupMultiIndex **v) +{ + if (*v) + nm_dedup_multi_index_unref (*v); +} +#define nm_auto_unref_dedup_multi_index nm_auto(_nm_auto_unref_dedup_multi_index) + +#define NM_DEDUP_MULTI_ENTRY_MISSING ((const NMDedupMultiEntry *) GUINT_TO_POINTER (1)) +#define NM_DEDUP_MULTI_HEAD_ENTRY_MISSING ((const NMDedupMultiHeadEntry *) GUINT_TO_POINTER (1)) + +gboolean nm_dedup_multi_index_add_full (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj, + NMDedupMultiIdxMode mode, + const NMDedupMultiEntry *entry_order, + const NMDedupMultiEntry *entry_existing, + const NMDedupMultiHeadEntry *head_existing, + const NMDedupMultiEntry **out_entry, + /* const NMDedupMultiObj ** */ gpointer out_obj_old); + +gboolean nm_dedup_multi_index_add (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj, + NMDedupMultiIdxMode mode, + const NMDedupMultiEntry **out_entry, + /* const NMDedupMultiObj ** */ gpointer out_obj_old); + +const NMDedupMultiEntry *nm_dedup_multi_index_lookup_obj (const NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj); + +const NMDedupMultiHeadEntry *nm_dedup_multi_index_lookup_head (const NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj); + +guint nm_dedup_multi_index_remove_entry (NMDedupMultiIndex *self, + gconstpointer entry); + +guint nm_dedup_multi_index_remove_obj (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj, + /*const NMDedupMultiObj ** */ gconstpointer *out_obj); + +guint nm_dedup_multi_index_remove_head (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj); + +guint nm_dedup_multi_index_remove_idx (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type); + +void nm_dedup_multi_index_dirty_set_head (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type, + /*const NMDedupMultiObj * */ gconstpointer obj); + +void nm_dedup_multi_index_dirty_set_idx (NMDedupMultiIndex *self, + const NMDedupMultiIdxType *idx_type); + +guint nm_dedup_multi_index_dirty_remove_idx (NMDedupMultiIndex *self, + NMDedupMultiIdxType *idx_type, + gboolean mark_survivors_dirty); + +/*****************************************************************************/ + +typedef struct _NMDedupMultiIter { + const CList *_head; + const CList *_next; + const NMDedupMultiEntry *current; +} NMDedupMultiIter; + +static inline void +nm_dedup_multi_iter_init (NMDedupMultiIter *iter, const NMDedupMultiHeadEntry *head) +{ + g_return_if_fail (iter); + + if (head && !c_list_is_empty (&head->lst_entries_head)) { + iter->_head = &head->lst_entries_head; + iter->_next = head->lst_entries_head.next; + } else { + iter->_head = NULL; + iter->_next = NULL; + } + iter->current = NULL; +} + +static inline gboolean +nm_dedup_multi_iter_next (NMDedupMultiIter *iter) +{ + g_return_val_if_fail (iter, FALSE); + + if (!iter->_next) + return FALSE; + + /* we always look ahead for the next. This way, the user + * may delete the current entry (but no other entries). */ + iter->current = c_list_entry (iter->_next, NMDedupMultiEntry, lst_entries); + if (iter->_next->next == iter->_head) + iter->_next = NULL; + else + iter->_next = iter->_next->next; + return TRUE; +} + +#define nm_dedup_multi_iter_for_each(iter, head_entry) \ + for (nm_dedup_multi_iter_init ((iter), (head_entry)); \ + nm_dedup_multi_iter_next ((iter)); \ + ) + +/*****************************************************************************/ + +typedef gboolean (*NMDedupMultiFcnSelectPredicate) (/* const NMDedupMultiObj * */ gconstpointer obj, + gpointer user_data); + +gconstpointer *nm_dedup_multi_objs_to_array_head (const NMDedupMultiHeadEntry *head_entry, + NMDedupMultiFcnSelectPredicate predicate, + gpointer user_data, + guint *out_len); +GPtrArray *nm_dedup_multi_objs_to_ptr_array_head (const NMDedupMultiHeadEntry *head_entry, + NMDedupMultiFcnSelectPredicate predicate, + gpointer user_data); + +static inline const NMDedupMultiEntry * +nm_dedup_multi_head_entry_get_idx (const NMDedupMultiHeadEntry *head_entry, + int idx) +{ + CList *iter; + + if (head_entry) { + if (idx >= 0) { + c_list_for_each (iter, &head_entry->lst_entries_head) { + if (idx-- == 0) + return c_list_entry (iter, NMDedupMultiEntry, lst_entries); + } + } else { + for (iter = head_entry->lst_entries_head.prev; + iter != &head_entry->lst_entries_head; + iter = iter->prev) { + if (++idx == 0) + return c_list_entry (iter, NMDedupMultiEntry, lst_entries); + } + } + } + return NULL; +} + +static inline void +nm_dedup_multi_head_entry_sort (const NMDedupMultiHeadEntry *head_entry, + CListSortCmp cmp, + gconstpointer user_data) +{ + if (head_entry) { + /* the head entry can be sorted directly without messing up the + * index to which it belongs. Of course, this does mess up any + * NMDedupMultiIter instances. */ + c_list_sort ((CList *) &head_entry->lst_entries_head, cmp, user_data); + } +} + +gboolean nm_dedup_multi_entry_reorder (const NMDedupMultiEntry *entry, + const NMDedupMultiEntry *entry_order, + gboolean order_after); + +/*****************************************************************************/ + +#endif /* __NM_DEDUP_MULTI_H__ */ diff --git a/shared/nm-utils/nm-enum-utils.c b/shared/nm-utils/nm-enum-utils.c new file mode 100644 index 00000000..70a8b415 --- /dev/null +++ b/shared/nm-utils/nm-enum-utils.c @@ -0,0 +1,288 @@ +/* -*- 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 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-enum-utils.h" + +/*****************************************************************************/ + +#define IS_FLAGS_SEPARATOR(ch) (NM_IN_SET ((ch), ' ', '\t', ',', '\n', '\r')) + +static gboolean +_is_hex_string (const char *str) +{ + return str[0] == '0' + && str[1] == 'x' + && str[2] + && NM_STRCHAR_ALL (&str[2], ch, g_ascii_isxdigit (ch)); +} + +static gboolean +_is_dec_string (const char *str) +{ + return str[0] + && NM_STRCHAR_ALL (&str[0], ch, g_ascii_isdigit (ch)); +} + +static gboolean +_enum_is_valid_enum_nick (const char *str) +{ + return str[0] + && !NM_STRCHAR_ANY (str, ch, g_ascii_isspace (ch)) + && !_is_dec_string (str) + && !_is_hex_string (str); +} + +static gboolean +_enum_is_valid_flags_nick (const char *str) +{ + return str[0] + && !NM_STRCHAR_ANY (str, ch, IS_FLAGS_SEPARATOR (ch)) + && !_is_dec_string (str) + && !_is_hex_string (str); +} + +char * +_nm_utils_enum_to_str_full (GType type, + int value, + const char *flags_separator) +{ + GTypeClass *class; + char *ret; + + if ( flags_separator + && ( !flags_separator[0] + || NM_STRCHAR_ANY (flags_separator, ch, !IS_FLAGS_SEPARATOR (ch)))) + g_return_val_if_reached (NULL); + + class = g_type_class_ref (type); + + if (G_IS_ENUM_CLASS (class)) { + GEnumValue *enum_value; + + enum_value = g_enum_get_value (G_ENUM_CLASS (class), value); + if ( !enum_value + || !_enum_is_valid_enum_nick (enum_value->value_nick)) + ret = g_strdup_printf ("%d", value); + else + ret = strdup (enum_value->value_nick); + } else if (G_IS_FLAGS_CLASS (class)) { + GFlagsValue *flags_value; + GString *str = g_string_new (""); + unsigned uvalue = (unsigned) value; + + flags_separator = flags_separator ?: " "; + + do { + flags_value = g_flags_get_first_value (G_FLAGS_CLASS (class), uvalue); + if (str->len) + g_string_append (str, flags_separator); + if ( !flags_value + || !_enum_is_valid_flags_nick (flags_value->value_nick)) { + if (uvalue) + g_string_append_printf (str, "0x%x", uvalue); + break; + } + g_string_append (str, flags_value->value_nick); + uvalue &= ~flags_value->value; + } while (uvalue); + ret = g_string_free (str, FALSE); + } else + g_return_val_if_reached (NULL); + + g_type_class_unref (class); + return ret; +} + +static const NMUtilsEnumValueInfo * +_find_value_info (const NMUtilsEnumValueInfo *value_infos, const char *needle) +{ + if (value_infos) { + for (; value_infos->nick; value_infos++) { + if (nm_streq (needle, value_infos->nick)) + return value_infos; + } + } + return NULL; +} + +gboolean +_nm_utils_enum_from_str_full (GType type, + const char *str, + int *out_value, + char **err_token, + const NMUtilsEnumValueInfo *value_infos) +{ + GTypeClass *class; + gboolean ret = FALSE; + int value = 0; + gs_free char *str_clone = NULL; + char *s; + gint64 v64; + const NMUtilsEnumValueInfo *nick; + + g_return_val_if_fail (str, FALSE); + + str_clone = strdup (str); + s = nm_str_skip_leading_spaces (str_clone); + g_strchomp (s); + + class = g_type_class_ref (type); + + if (G_IS_ENUM_CLASS (class)) { + GEnumValue *enum_value; + + if (s[0]) { + if (_is_hex_string (s)) { + v64 = _nm_utils_ascii_str_to_int64 (s, 16, 0, G_MAXUINT, -1); + if (v64 != -1) { + value = (int) v64; + ret = TRUE; + } + } else if (_is_dec_string (s)) { + v64 = _nm_utils_ascii_str_to_int64 (s, 10, 0, G_MAXUINT, -1); + if (v64 != -1) { + value = (int) v64; + ret = TRUE; + } + } else { + enum_value = g_enum_get_value_by_nick (G_ENUM_CLASS (class), s); + if (enum_value) { + value = enum_value->value; + ret = TRUE; + } else { + nick = _find_value_info (value_infos, s); + if (nick) { + value = nick->value; + ret = TRUE; + } + } + } + } + } else if (G_IS_FLAGS_CLASS (class)) { + GFlagsValue *flags_value; + unsigned uvalue = 0; + + ret = TRUE; + while (s[0]) { + char *s_end; + + for (s_end = s; s_end[0]; s_end++) { + if (IS_FLAGS_SEPARATOR (s_end[0])) { + s_end[0] = '\0'; + s_end++; + break; + } + } + + if (s[0]) { + if (_is_hex_string (s)) { + v64 = _nm_utils_ascii_str_to_int64 (&s[2], 16, 0, G_MAXUINT, -1); + if (v64 == -1) { + ret = FALSE; + break; + } + uvalue |= (unsigned) v64; + } else if (_is_dec_string (s)) { + v64 = _nm_utils_ascii_str_to_int64 (s, 10, 0, G_MAXUINT, -1); + if (v64 == -1) { + ret = FALSE; + break; + } + uvalue |= (unsigned) v64; + } else { + flags_value = g_flags_get_value_by_nick (G_FLAGS_CLASS (class), s); + if (flags_value) + uvalue |= flags_value->value; + else { + nick = _find_value_info (value_infos, s); + if (nick) + uvalue = (unsigned) nick->value; + else { + ret = FALSE; + break; + } + } + } + } + + s = s_end; + } + + value = (int) uvalue; + } else + g_return_val_if_reached (FALSE); + + NM_SET_OUT (err_token, !ret && s[0] ? g_strdup (s) : NULL); + NM_SET_OUT (out_value, ret ? value : 0); + g_type_class_unref (class); + return ret; +} + +const char ** +_nm_utils_enum_get_values (GType type, gint from, gint to) +{ + GTypeClass *class; + GPtrArray *array; + gint i; + char sbuf[64]; + + class = g_type_class_ref (type); + array = g_ptr_array_new (); + + if (G_IS_ENUM_CLASS (class)) { + GEnumClass *enum_class = G_ENUM_CLASS (class); + GEnumValue *enum_value; + + for (i = 0; i < enum_class->n_values; i++) { + enum_value = &enum_class->values[i]; + if (enum_value->value >= from && enum_value->value <= to) { + if (_enum_is_valid_enum_nick (enum_value->value_nick)) + g_ptr_array_add (array, (gpointer) enum_value->value_nick); + else + g_ptr_array_add (array, (gpointer) g_intern_string (nm_sprintf_buf (sbuf, "%d", enum_value->value))); + } + } + } else if (G_IS_FLAGS_CLASS (class)) { + GFlagsClass *flags_class = G_FLAGS_CLASS (class); + GFlagsValue *flags_value; + + for (i = 0; i < flags_class->n_values; i++) { + flags_value = &flags_class->values[i]; + if (flags_value->value >= (guint) from && flags_value->value <= (guint) to) { + if (_enum_is_valid_flags_nick (flags_value->value_nick)) + g_ptr_array_add (array, (gpointer) flags_value->value_nick); + else + g_ptr_array_add (array, (gpointer) g_intern_string (nm_sprintf_buf (sbuf, "0x%x", (unsigned) flags_value->value))); + } + } + } else { + g_type_class_unref (class); + g_ptr_array_free (array, TRUE); + g_return_val_if_reached (NULL); + } + + g_type_class_unref (class); + g_ptr_array_add (array, NULL); + + return (const char **) g_ptr_array_free (array, FALSE); +} diff --git a/shared/nm-utils/nm-enum-utils.h b/shared/nm-utils/nm-enum-utils.h new file mode 100644 index 00000000..b78d9191 --- /dev/null +++ b/shared/nm-utils/nm-enum-utils.h @@ -0,0 +1,45 @@ +/* -*- 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 2017 Red Hat, Inc. + */ + +#ifndef __NM_ENUM_UTILS_H__ +#define __NM_ENUM_UTILS_H__ + +/*****************************************************************************/ + +typedef struct _NMUtilsEnumValueInfo { + /* currently, this is only used for _nm_utils_enum_from_str_full() to + * declare additional aliases for values. */ + const char *nick; + int value; +} NMUtilsEnumValueInfo; + +char *_nm_utils_enum_to_str_full (GType type, int value, const char *sep); +gboolean _nm_utils_enum_from_str_full (GType type, + const char *str, + int *out_value, + char **err_token, + const NMUtilsEnumValueInfo *value_infos); + +const char **_nm_utils_enum_get_values (GType type, gint from, gint to); + +/*****************************************************************************/ + +#endif /* __NM_ENUM_UTILS_H__ */ diff --git a/shared/nm-utils/nm-glib.h b/shared/nm-utils/nm-glib.h index dd18756a..4ef538e4 100644 --- a/shared/nm-utils/nm-glib.h +++ b/shared/nm-utils/nm-glib.h @@ -452,4 +452,33 @@ _nm_g_variant_new_take_string (gchar *string) } #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 + #endif /* __NM_GLIB_H__ */ diff --git a/shared/nm-utils/nm-hash-utils.c b/shared/nm-utils/nm-hash-utils.c new file mode 100644 index 00000000..c563140e --- /dev/null +++ b/shared/nm-utils/nm-hash-utils.c @@ -0,0 +1,119 @@ +/* -*- 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 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-hash-utils.h" + +#include <stdint.h> + +#include "nm-shared-utils.h" +#include "nm-random-utils.h" + +/*****************************************************************************/ + +#define HASH_KEY_SIZE 16u +#define HASH_KEY_SIZE_GUINT ((HASH_KEY_SIZE + sizeof (guint) - 1) / sizeof (guint)) + +G_STATIC_ASSERT (sizeof (guint) * HASH_KEY_SIZE_GUINT >= HASH_KEY_SIZE); + +static const guint8 * +_get_hash_key (void) +{ + static const guint8 *volatile global_seed = NULL; + const guint8 *g; + + g = global_seed; + if (G_UNLIKELY (g == NULL)) { + /* the returned hash is aligned to guin64, hence, it is save + * to use it as guint* or guint64* pointer. */ + static union { + guint8 v8[HASH_KEY_SIZE]; + } g_arr _nm_alignas (guint64); + static gsize g_lock; + + if (g_once_init_enter (&g_lock)) { + nm_utils_random_bytes (g_arr.v8, sizeof (g_arr.v8)); + g_atomic_pointer_compare_and_exchange (&global_seed, NULL, g_arr.v8); + g = g_arr.v8; + g_once_init_leave (&g_lock, 1); + } else { + g = global_seed; + nm_assert (g); + } + } + + return g; +} + +void +nm_hash_init (NMHashState *state, guint static_seed) +{ + const guint8 *g; + guint seed[HASH_KEY_SIZE_GUINT]; + + nm_assert (state); + + g = _get_hash_key (); + memcpy (seed, g, HASH_KEY_SIZE); + seed[0] ^= static_seed; + siphash24_init (&state->_state, (const guint8 *) seed); +} + +guint +nm_hash_str (const char *str) +{ + NMHashState h; + + if (str) { + nm_hash_init (&h, 1867854211u); + nm_hash_update_str (&h, str); + } else + nm_hash_init (&h, 842995561u); + return nm_hash_complete (&h); +} + +guint +nm_str_hash (gconstpointer str) +{ + return nm_hash_str (str); +} + +guint +nm_hash_ptr (gconstpointer ptr) +{ + guint h; + + h = ((const guint *) _get_hash_key ())[0]; + + if (sizeof (ptr) <= sizeof (guint)) + h = h ^ ((guint) ((uintptr_t) ptr)); + else + h = h ^ ((guint) (((guint64) (uintptr_t) ptr) >> 32)) ^ ((guint) ((uintptr_t) ptr)); + + return h ?: 2907677551u; +} + +guint +nm_direct_hash (gconstpointer ptr) +{ + return nm_hash_ptr (ptr); +} diff --git a/shared/nm-utils/nm-hash-utils.h b/shared/nm-utils/nm-hash-utils.h new file mode 100644 index 00000000..276e1ebe --- /dev/null +++ b/shared/nm-utils/nm-hash-utils.h @@ -0,0 +1,210 @@ +/* -*- 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 2017 Red Hat, Inc. + */ + +#ifndef __NM_HASH_UTILS_H__ +#define __NM_HASH_UTILS_H__ + +#include "siphash24.h" +#include "nm-macros-internal.h" + +struct _NMHashState { + struct siphash _state; +}; + +typedef struct _NMHashState NMHashState; + +void nm_hash_init (NMHashState *state, guint static_seed); + +static inline guint +nm_hash_complete (NMHashState *state) +{ + guint64 h; + + nm_assert (state); + + h = siphash24_finalize (&state->_state); + + /* we don't ever want to return a zero hash. + * + * NMPObject requires that in _idx_obj_part(), and it's just a good idea. */ + return (((guint) (h >> 32)) ^ ((guint) h)) ?: 1396707757u; +} + +static inline void +nm_hash_update (NMHashState *state, const void *ptr, gsize n) +{ + nm_assert (state); + nm_assert (ptr); + nm_assert (n > 0); + + siphash24_compress (ptr, n, &state->_state); +} + +#define nm_hash_update_val(state, val) \ + G_STMT_START { \ + typeof (val) _val = (val); \ + \ + nm_hash_update ((state), &_val, sizeof (_val)); \ + } G_STMT_END + +static inline void +nm_hash_update_bool (NMHashState *state, bool val) +{ + nm_hash_update (state, &val, sizeof (val)); +} + +#define _NM_HASH_COMBINE_BOOLS_x_1( t, y) ((y) ? ((t) (1ull << 0)) : ((t) 0ull)) +#define _NM_HASH_COMBINE_BOOLS_x_2( t, y, ...) ((y) ? ((t) (1ull << 1)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_1 (t, __VA_ARGS__) +#define _NM_HASH_COMBINE_BOOLS_x_3( t, y, ...) ((y) ? ((t) (1ull << 2)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_2 (t, __VA_ARGS__) +#define _NM_HASH_COMBINE_BOOLS_x_4( t, y, ...) ((y) ? ((t) (1ull << 3)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_3 (t, __VA_ARGS__) +#define _NM_HASH_COMBINE_BOOLS_x_5( t, y, ...) ((y) ? ((t) (1ull << 4)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_4 (t, __VA_ARGS__) +#define _NM_HASH_COMBINE_BOOLS_x_6( t, y, ...) ((y) ? ((t) (1ull << 5)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_5 (t, __VA_ARGS__) +#define _NM_HASH_COMBINE_BOOLS_x_7( t, y, ...) ((y) ? ((t) (1ull << 6)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_6 (t, __VA_ARGS__) +#define _NM_HASH_COMBINE_BOOLS_x_8( t, y, ...) ((y) ? ((t) (1ull << 7)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_7 (t, __VA_ARGS__) +#define _NM_HASH_COMBINE_BOOLS_x_9( t, y, ...) ((y) ? ((t) (1ull << 8)) : ((t) 0ull)) | (G_STATIC_ASSERT_EXPR (sizeof (t) >= 2), (_NM_HASH_COMBINE_BOOLS_x_8 (t, __VA_ARGS__))) +#define _NM_HASH_COMBINE_BOOLS_x_10(t, y, ...) ((y) ? ((t) (1ull << 9)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_9 (t, __VA_ARGS__) +#define _NM_HASH_COMBINE_BOOLS_x_11(t, y, ...) ((y) ? ((t) (1ull << 10)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_10 (t, __VA_ARGS__) +#define _NM_HASH_COMBINE_BOOLS_n2(t, n, ...) _NM_HASH_COMBINE_BOOLS_x_##n (t, __VA_ARGS__) +#define _NM_HASH_COMBINE_BOOLS_n(t, n, ...) _NM_HASH_COMBINE_BOOLS_n2(t, n, __VA_ARGS__) + +#define NM_HASH_COMBINE_BOOLS(type, ...) ((type) (_NM_HASH_COMBINE_BOOLS_n(type, NM_NARG (__VA_ARGS__), __VA_ARGS__))) + +#define nm_hash_update_bools(state, ...) \ + nm_hash_update_val (state, NM_HASH_COMBINE_BOOLS (guint8, __VA_ARGS__)) + +#define _NM_HASH_COMBINE_VALS_typ_x_1( y) typeof (y) _v1; +#define _NM_HASH_COMBINE_VALS_typ_x_2( y, ...) typeof (y) _v2; _NM_HASH_COMBINE_VALS_typ_x_1 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_3( y, ...) typeof (y) _v3; _NM_HASH_COMBINE_VALS_typ_x_2 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_4( y, ...) typeof (y) _v4; _NM_HASH_COMBINE_VALS_typ_x_3 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_5( y, ...) typeof (y) _v5; _NM_HASH_COMBINE_VALS_typ_x_4 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_6( y, ...) typeof (y) _v6; _NM_HASH_COMBINE_VALS_typ_x_5 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_7( y, ...) typeof (y) _v7; _NM_HASH_COMBINE_VALS_typ_x_6 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_8( y, ...) typeof (y) _v8; _NM_HASH_COMBINE_VALS_typ_x_7 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_9( y, ...) typeof (y) _v9; _NM_HASH_COMBINE_VALS_typ_x_8 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_10(y, ...) typeof (y) _v10; _NM_HASH_COMBINE_VALS_typ_x_9 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_11(y, ...) typeof (y) _v11; _NM_HASH_COMBINE_VALS_typ_x_10 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_12(y, ...) typeof (y) _v12; _NM_HASH_COMBINE_VALS_typ_x_11 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_13(y, ...) typeof (y) _v13; _NM_HASH_COMBINE_VALS_typ_x_12 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_14(y, ...) typeof (y) _v14; _NM_HASH_COMBINE_VALS_typ_x_13 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_15(y, ...) typeof (y) _v15; _NM_HASH_COMBINE_VALS_typ_x_14 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_16(y, ...) typeof (y) _v16; _NM_HASH_COMBINE_VALS_typ_x_15 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_17(y, ...) typeof (y) _v17; _NM_HASH_COMBINE_VALS_typ_x_16 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_18(y, ...) typeof (y) _v18; _NM_HASH_COMBINE_VALS_typ_x_17 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_19(y, ...) typeof (y) _v19; _NM_HASH_COMBINE_VALS_typ_x_18 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_x_20(y, ...) typeof (y) _v20; _NM_HASH_COMBINE_VALS_typ_x_19 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_n2(n, ...) _NM_HASH_COMBINE_VALS_typ_x_##n (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_typ_n(n, ...) _NM_HASH_COMBINE_VALS_typ_n2(n, __VA_ARGS__) + +#define _NM_HASH_COMBINE_VALS_val_x_1( y) ._v1 = (y), +#define _NM_HASH_COMBINE_VALS_val_x_2( y, ...) ._v2 = (y), _NM_HASH_COMBINE_VALS_val_x_1 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_3( y, ...) ._v3 = (y), _NM_HASH_COMBINE_VALS_val_x_2 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_4( y, ...) ._v4 = (y), _NM_HASH_COMBINE_VALS_val_x_3 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_5( y, ...) ._v5 = (y), _NM_HASH_COMBINE_VALS_val_x_4 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_6( y, ...) ._v6 = (y), _NM_HASH_COMBINE_VALS_val_x_5 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_7( y, ...) ._v7 = (y), _NM_HASH_COMBINE_VALS_val_x_6 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_8( y, ...) ._v8 = (y), _NM_HASH_COMBINE_VALS_val_x_7 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_9( y, ...) ._v9 = (y), _NM_HASH_COMBINE_VALS_val_x_8 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_10(y, ...) ._v10 = (y), _NM_HASH_COMBINE_VALS_val_x_9 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_11(y, ...) ._v11 = (y), _NM_HASH_COMBINE_VALS_val_x_10 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_12(y, ...) ._v12 = (y), _NM_HASH_COMBINE_VALS_val_x_11 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_13(y, ...) ._v13 = (y), _NM_HASH_COMBINE_VALS_val_x_12 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_14(y, ...) ._v14 = (y), _NM_HASH_COMBINE_VALS_val_x_13 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_15(y, ...) ._v15 = (y), _NM_HASH_COMBINE_VALS_val_x_14 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_16(y, ...) ._v16 = (y), _NM_HASH_COMBINE_VALS_val_x_15 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_17(y, ...) ._v17 = (y), _NM_HASH_COMBINE_VALS_val_x_16 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_18(y, ...) ._v18 = (y), _NM_HASH_COMBINE_VALS_val_x_17 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_19(y, ...) ._v19 = (y), _NM_HASH_COMBINE_VALS_val_x_18 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_x_20(y, ...) ._v20 = (y), _NM_HASH_COMBINE_VALS_val_x_19 (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_n2(n, ...) _NM_HASH_COMBINE_VALS_val_x_##n (__VA_ARGS__) +#define _NM_HASH_COMBINE_VALS_val_n(n, ...) _NM_HASH_COMBINE_VALS_val_n2(n, __VA_ARGS__) + +/* NM_HASH_COMBINE_VALS() is faster then nm_hash_update_val() as it combines multiple + * calls to nm_hash_update() using a packed structure. */ +#define NM_HASH_COMBINE_VALS(var, ...) \ + const struct _nm_packed { \ + _NM_HASH_COMBINE_VALS_typ_n (NM_NARG (__VA_ARGS__), __VA_ARGS__) \ + } var _nm_alignas (guint64) = { \ + _NM_HASH_COMBINE_VALS_val_n (NM_NARG (__VA_ARGS__), __VA_ARGS__) \ + } + +/* nm_hash_update_vals() is faster then nm_hash_update_val() as it combines multiple + * calls to nm_hash_update() using a packed structure. */ +#define nm_hash_update_vals(state, ...) \ + G_STMT_START { \ + NM_HASH_COMBINE_VALS (_val, __VA_ARGS__); \ + \ + nm_hash_update ((state), &_val, sizeof (_val)); \ + } G_STMT_END + +static inline void +nm_hash_update_mem (NMHashState *state, const void *ptr, gsize n) +{ + /* This also hashes the length of the data. That means, + * hashing two consecutive binary fields (of arbitrary + * length), will hash differently. That is, + * [[1,1], []] differs from [[1],[1]]. + * + * If you have a constant length (sizeof), use nm_hash_update() + * instead. */ + nm_hash_update (state, &n, sizeof (n)); + if (n > 0) + siphash24_compress (ptr, n, &state->_state); +} + +static inline void +nm_hash_update_str0 (NMHashState *state, const char *str) +{ + if (str) + nm_hash_update_mem (state, str, strlen (str)); + else { + gsize n = G_MAXSIZE; + + nm_hash_update (state, &n, sizeof (n)); + } +} + +static inline void +nm_hash_update_str (NMHashState *state, const char *str) +{ + nm_assert (str); + nm_hash_update (state, str, strlen (str) + 1); +} + +#if _NM_CC_SUPPORT_GENERIC +/* Like nm_hash_update_str(), but restricted to arrays only. nm_hash_update_str() only works + * with a @str argument that cannot be NULL. If you have a string pointer, that is never NULL, use + * nm_hash_update() instead. */ +#define nm_hash_update_strarr(state, str) \ + (_Generic (&(str), \ + const char (*) [sizeof (str)]: nm_hash_update_str ((state), (str)), \ + char (*) [sizeof (str)]: nm_hash_update_str ((state), (str))) \ + ) +#else +#define nm_hash_update_strarr(state, str) nm_hash_update_str ((state), (str)) +#endif + +guint nm_hash_ptr (gconstpointer ptr); +guint nm_direct_hash (gconstpointer str); + +guint nm_hash_str (const char *str); +guint nm_str_hash (gconstpointer str); + +#endif /* __NM_HASH_UTILS_H__ */ diff --git a/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h index 5fe4bc50..d9dab83d 100644 --- a/shared/nm-utils/nm-macros-internal.h +++ b/shared/nm-utils/nm-macros-internal.h @@ -26,20 +26,41 @@ #include <stdlib.h> #include <errno.h> -#include "nm-glib.h" +#define _nm_packed __attribute__ ((packed)) +#define _nm_unused __attribute__ ((unused)) +#define _nm_pure __attribute__ ((pure)) +#define _nm_const __attribute__ ((const)) +#define _nm_printf(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#define _nm_align(s) __attribute__ ((aligned (s))) +#define _nm_alignof(type) __alignof (type) +#define _nm_alignas(type) _nm_align (_nm_alignof (type)) + +/*****************************************************************************/ + +#ifdef thread_local +#define _nm_thread_local thread_local +/* + * Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__ + * see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769 + */ +#elif __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16)) +#define _nm_thread_local _Thread_local +#else +#define _nm_thread_local __thread +#endif /*****************************************************************************/ -#define _nm_packed __attribute__ ((packed)) -#define _nm_unused __attribute__ ((unused)) -#define _nm_pure __attribute__ ((pure)) -#define _nm_const __attribute__ ((const)) -#define _nm_printf(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#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); + /** * nm_auto_free: * @@ -56,6 +77,14 @@ _nm_auto_unset_gvalue_impl (GValue *v) #define nm_auto_unset_gvalue nm_auto(_nm_auto_unset_gvalue_impl) static inline void +_nm_auto_unref_gtypeclass (gpointer v) +{ + if (v && *((gpointer *) v)) + g_type_class_unref (*((gpointer *) v)); +} +#define nm_auto_unref_gtypeclass nm_auto(_nm_auto_unref_gtypeclass) + +static inline void _nm_auto_free_gstring_impl (GString **str) { if (*str) @@ -69,7 +98,7 @@ _nm_auto_close_impl (int *pfd) if (*pfd >= 0) { int errsv = errno; - (void) close (*pfd); + (void) nm_close (*pfd); errno = errsv; } } @@ -201,6 +230,23 @@ NM_G_ERROR_MSG (GError *error) /* macro to return strlen() of a compile time string. */ #define NM_STRLEN(str) ( sizeof ("" str) - 1 ) +/* returns the length of a NULL terminated array of pointers, + * like g_strv_length() does. The difference is: + * - it operats on arrays of pointers (of any kind, requiring no cast). + * - it accepts NULL to return zero. */ +#define NM_PTRARRAY_LEN(array) \ + ({ \ + typeof (*(array)) *const _array = (array); \ + gsize _n = 0; \ + \ + if (_array) { \ + _nm_unused typeof (*(_array[0])) *_array_check = _array[0]; \ + while (_array[_n]) \ + _n++; \ + } \ + _n; \ + }) + /* Note: @value is only evaluated when *out_val is present. * Thus, * NM_SET_OUT (out_str, g_strdup ("hallo")); @@ -217,6 +263,47 @@ NM_G_ERROR_MSG (GError *error) /*****************************************************************************/ +#if (defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 9 ))) || (defined (__clang__)) +#define _NM_CC_SUPPORT_GENERIC 1 +#else +#define _NM_CC_SUPPORT_GENERIC 0 +#endif + +#if _NM_CC_SUPPORT_GENERIC +#define _NM_CONSTCAST(type, obj) \ + (_Generic ((obj), \ + void * : ((type *) (obj)), \ + void *const : ((type *) (obj)), \ + const void * : ((const type *) (obj)), \ + const void *const: ((const type *) (obj)), \ + const type * : (obj), \ + const type *const: (obj), \ + type * : (obj), \ + type *const : (obj))) +#else +/* _NM_CONSTCAST() is there to preserve constness of a pointer. + * It uses C11's _Generic(). If that is not supported, we fall back + * to casting away constness. So, with _Generic, we get some additional + * static type checking by preserving constness, without, we cast it + * to a non-const pointer. */ +#define _NM_CONSTCAST(type, obj) \ + ((type *) (obj)) +#endif + +#if _NM_CC_SUPPORT_GENERIC +/* returns @value, if the type of @value matches @type. + * This requires support for C11 _Generic(). If no support is + * present, this returns @value directly. + * + * It's useful to check the let the compiler ensure that @value is + * of a certain type. */ +#define _NM_ENSURE_TYPE(type, value) (_Generic ((value), type: (value))) +#else +#define _NM_ENSURE_TYPE(type, value) (value) +#endif + +/*****************************************************************************/ + #define _NM_IN_SET_EVAL_1( op, _x, y) (_x == (y)) #define _NM_IN_SET_EVAL_2( op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_1 (op, _x, __VA_ARGS__) #define _NM_IN_SET_EVAL_3( op, _x, y, ...) (_x == (y)) op _NM_IN_SET_EVAL_2 (op, _x, __VA_ARGS__) @@ -395,6 +482,16 @@ fcn (void) \ /*****************************************************************************/ +static inline GString * +nm_gstring_prepare (GString **l) +{ + if (*l) + g_string_set_size (*l, 0); + else + *l = g_string_sized_new (30); + return *l; +} + static inline const char * nm_str_not_empty (const char *str) { @@ -487,7 +584,7 @@ _notify (obj_type *obj, _PropertyEnums prop) \ /* these are implemented as a macro, because they accept self * as both (type*) and (const type*), and return a const * private pointer accordingly. */ -#define __NM_GET_PRIVATE(self, type, is_check, result_cmd) \ +#define __NM_GET_PRIVATE(self, type, is_check, addrop) \ ({ \ /* preserve the const-ness of self. Unfortunately, that * way, @self cannot be a void pointer */ \ @@ -497,11 +594,11 @@ _notify (obj_type *obj, _PropertyEnums prop) \ _nm_unused const type *const _self2 = (_self); \ \ nm_assert (is_check (_self)); \ - ( result_cmd ); \ + ( addrop ( _NM_CONSTCAST (type, _self)->_priv) ); \ }) -#define _NM_GET_PRIVATE(self, type, is_check) __NM_GET_PRIVATE(self, type, is_check, &_self->_priv) -#define _NM_GET_PRIVATE_PTR(self, type, is_check) __NM_GET_PRIVATE(self, type, is_check, _self->_priv) +#define _NM_GET_PRIVATE(self, type, is_check) __NM_GET_PRIVATE(self, type, is_check, &) +#define _NM_GET_PRIVATE_PTR(self, type, is_check) __NM_GET_PRIVATE(self, type, is_check, ) #define __NM_GET_PRIVATE_VOID(self, type, is_check, result_cmd) \ ({ \ @@ -540,6 +637,36 @@ nm_g_object_unref (gpointer obj) g_object_unref (obj); } +/* Assigns GObject @obj to destination @pdst, and takes an additional ref. + * The previous value of @pdst is unrefed. + * + * It makes sure to first increase the ref-count of @obj, and handles %NULL + * @obj correctly. + * */ +#define nm_g_object_ref_set(pp, obj) \ + ({ \ + typeof (*(pp)) *const _pp = (pp); \ + typeof (**_pp) *const _obj = (obj); \ + typeof (**_pp) *_p; \ + gboolean _changed = FALSE; \ + \ + if ( _pp \ + && ((_p = *_pp) != _obj)) { \ + if (_obj) { \ + nm_assert (G_IS_OBJECT (_obj)); \ + g_object_ref (_obj); \ + } \ + if (_p) { \ + nm_assert (G_IS_OBJECT (_p)); \ + *_pp = NULL; \ + g_object_unref (_p); \ + } \ + *_pp = _obj; \ + _changed = TRUE; \ + } \ + _changed; \ + }) + /* basically, replaces * g_clear_pointer (&location, g_free) * with @@ -552,13 +679,32 @@ nm_g_object_unref (gpointer obj) #define nm_clear_g_free(pp) \ ({ \ typeof (*(pp)) *_pp = (pp); \ - typeof (**_pp) *_p = *_pp; \ + typeof (**_pp) *_p; \ + gboolean _changed = FALSE; \ \ - if (_p) { \ + if ( _pp \ + && (_p = *_pp)) { \ *_pp = NULL; \ g_free (_p); \ + _changed = TRUE; \ + } \ + _changed; \ + }) + +#define nm_clear_g_object(pp) \ + ({ \ + typeof (*(pp)) *_pp = (pp); \ + typeof (**_pp) *_p; \ + gboolean _changed = FALSE; \ + \ + if ( _pp \ + && (_p = *_pp)) { \ + nm_assert (G_IS_OBJECT (_p)); \ + *_pp = NULL; \ + g_object_unref (_p); \ + _changed = TRUE; \ } \ - !!_p; \ + _changed; \ }) static inline gboolean @@ -620,6 +766,50 @@ nm_clear_g_cancellable (GCancellable **cancellable) /*****************************************************************************/ +#define NM_UTILS_LOOKUP_DEFAULT(v) return (v) +#define NM_UTILS_LOOKUP_DEFAULT_WARN(v) g_return_val_if_reached (v) +#define NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(v) { nm_assert_not_reached (); return (v); } +#define NM_UTILS_LOOKUP_ITEM(v, n) (void) 0; case v: return (n); (void) 0 +#define NM_UTILS_LOOKUP_STR_ITEM(v, n) NM_UTILS_LOOKUP_ITEM(v, ""n"") +#define NM_UTILS_LOOKUP_ITEM_IGNORE(v) (void) 0; case v: break; (void) 0 +#define NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER() (void) 0; default: break; (void) 0 + +#define _NM_UTILS_LOOKUP_DEFINE(scope, fcn_name, lookup_type, result_type, unknown_val, ...) \ +scope result_type \ +fcn_name (lookup_type val) \ +{ \ + switch (val) { \ + (void) 0, \ + __VA_ARGS__ \ + (void) 0; \ + }; \ + { unknown_val; } \ +} + +#define NM_UTILS_LOOKUP_STR_DEFINE(fcn_name, lookup_type, unknown_val, ...) \ + _NM_UTILS_LOOKUP_DEFINE (, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__) +#define NM_UTILS_LOOKUP_STR_DEFINE_STATIC(fcn_name, lookup_type, unknown_val, ...) \ + _NM_UTILS_LOOKUP_DEFINE (static, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__) + +/* Call the string-lookup-table function @fcn_name. If the function returns + * %NULL, the numeric index is converted to string using a alloca() buffer. + * Beware: this macro uses alloca(). */ +#define NM_UTILS_LOOKUP_STR(fcn_name, idx) \ + ({ \ + typeof (idx) _idx = (idx); \ + const char *_s; \ + \ + _s = fcn_name (_idx); \ + if (!_s) { \ + _s = g_alloca (30); \ + \ + g_snprintf ((char *) _s, 30, "(%lld)", (long long) _idx); \ + } \ + _s; \ + }) + +/*****************************************************************************/ + /* check if @flags has exactly one flag (@check) set. You should call this * only with @check being a compile time constant and a power of two. */ #define NM_FLAGS_HAS(flags, check) \ @@ -686,6 +876,33 @@ nm_strstrip (char *str) return str ? g_strstrip (str) : NULL; } +static inline const char * +nm_strstrip_avoid_copy (const char *str, char **str_free) +{ + gsize l; + char *s; + + nm_assert (str_free && !*str_free); + + if (!str) + return NULL; + + str = nm_str_skip_leading_spaces (str); + l = strlen (str); + if ( l == 0 + || !g_ascii_isspace (str[l - 1])) + return str; + while ( l > 0 + && g_ascii_isspace (str[l - 1])) + l--; + + s = g_new (char, l + 1); + memcpy (s, str, l); + s[l] = '\0'; + *str_free = s; + return s; +} + /* g_ptr_array_sort()'s compare function takes pointers to the * value. Thus, you cannot use strcmp directly. You can use * nm_strcmp_p(). @@ -934,4 +1151,35 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro) /*****************************************************************************/ +static inline int +nm_steal_fd (int *p_fd) +{ + int fd; + + if ( p_fd + && ((fd = *p_fd) >= 0)) { + *p_fd = -1; + return fd; + } + return -1; +} + +/** + * nm_close: + * + * Like close() but throws an assertion if the input fd is + * invalid. Closing an invalid fd is a programming error, so + * it's better to catch it early. + */ +static inline int +nm_close (int fd) +{ + if (fd >= 0) { + if (close (fd) == 0) + return 0; + nm_assert (errno != EBADF); + } + return -1; +} + #endif /* __NM_MACROS_INTERNAL_H__ */ diff --git a/shared/nm-utils/nm-obj.h b/shared/nm-utils/nm-obj.h new file mode 100644 index 00000000..1a9d4868 --- /dev/null +++ b/shared/nm-utils/nm-obj.h @@ -0,0 +1,82 @@ +/* -*- 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 2017 Red Hat, Inc. + */ + +#ifndef __NM_OBJ_H__ +#define __NM_OBJ_H__ + +/*****************************************************************************/ + +#define NM_OBJ_REF_COUNT_STACKINIT (G_MAXINT) + +typedef struct _NMObjBaseInst NMObjBaseInst; +typedef struct _NMObjBaseClass NMObjBaseClass; + +struct _NMObjBaseInst { + /* The first field of NMObjBaseInst is compatible with GObject. + * Basically, NMObjBaseInst is an abstract base type of GTypeInstance. + * + * If you do it right, you may derive a type of NMObjBaseInst as a proper GTypeInstance. + * That involves allocating a GType for it, which can be inconvenient because + * a GType is dynamically created (and the class can no longer be immutable + * memory). + * + * Even if your implementation of NMObjBaseInst is not a full fledged GType(Instance), + * you still can use GTypeInstances in the same context as you can decide based on the + * NMObjBaseClass with what kind of object you are dealing with. + * + * Basically, the only thing NMObjBaseInst gives you is access to an + * NMObjBaseClass instance. + */ + union { + const NMObjBaseClass *klass; + GTypeInstance g_type_instance; + }; +}; + +struct _NMObjBaseClass { + /* NMObjBaseClass is the base class of all NMObjBaseInst implementations. + * Note that it is also an abstract super class of GTypeInstance, that means + * you may implement a NMObjBaseClass as a subtype of GTypeClass. + * + * For that to work, you must properly set the GTypeClass instance (and it's + * GType). + * + * Note that to implement a NMObjBaseClass that is *not* a GTypeClass, you wouldn't + * set the GType. Hence, this field is only useful for type implementations that actually + * extend GTypeClass. + * + * In a way it is wrong that NMObjBaseClass has the GType member, because it is + * a base class of GTypeClass and doesn't necessarily use the GType. However, + * it is here so that G_TYPE_CHECK_INSTANCE_TYPE() and friends work correctly + * on any NMObjectClass. That means, while not necessary, it is convenient that + * a NMObjBaseClass has all members of GTypeClass. + * Also note that usually you have only one instance of a certain type, so this + * wastes just a few bytes for the unneeded GType. + */ + union { + GType g_type; + GTypeClass g_type_class; + }; +}; + +/*****************************************************************************/ + +#endif /* __NM_OBJ_H__ */ diff --git a/shared/nm-utils/nm-random-utils.c b/shared/nm-utils/nm-random-utils.c new file mode 100644 index 00000000..5d9e29da --- /dev/null +++ b/shared/nm-utils/nm-random-utils.c @@ -0,0 +1,165 @@ +/* -*- 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 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-random-utils.h" + +#include <fcntl.h> + +#if USE_SYS_RANDOM_H +#include <sys/random.h> +#else +#include <linux/random.h> +#endif + +#include "nm-shared-utils.h" + +/*****************************************************************************/ + +/** + * nm_utils_random_bytes: + * @p: the buffer to fill + * @n: the number of bytes to write to @p. + * + * Uses getrandom() or reads /dev/urandom to fill the buffer + * with random data. If all fails, as last fallback it uses + * GRand to fill the buffer with pseudo random numbers. + * The function always succeeds in writing some random numbers + * to the buffer. The return value of FALSE indicates that the + * obtained bytes are probably not of good randomness. + * + * Returns: whether the written bytes are good. If you + * don't require good randomness, you can ignore the return + * value. + * + * Note that if calling getrandom() fails because there is not enough + * entroy (at early boot), the function will read /dev/urandom. + * Which of course, still has low entropy, and cause kernel to log + * a warning. + */ +gboolean +nm_utils_random_bytes (void *p, size_t n) +{ + int fd; + int r; + gboolean has_high_quality = TRUE; + gboolean urandom_success; + guint8 *buf = p; + gboolean avoid_urandom = FALSE; + + g_return_val_if_fail (p, FALSE); + g_return_val_if_fail (n > 0, FALSE); + +#if HAVE_GETRANDOM + { + static gboolean have_syscall = TRUE; + + if (have_syscall) { + r = getrandom (buf, n, GRND_NONBLOCK); + if (r > 0) { + if ((size_t) r == n) + return TRUE; + + /* no or partial read. There is not enough entropy. + * Fill the rest reading from urandom, and remember that + * some bits are not hight quality. */ + nm_assert (r < n); + buf += r; + n -= r; + has_high_quality = FALSE; + + /* At this point, we don't want to read /dev/urandom, because + * the entropy pool is low (early boot?), and asking for more + * entropy causes kernel messages to be logged. + * + * We use our fallback via GRand. Note that g_rand_new() also + * tries to seed itself with data from /dev/urandom, but since + * we reuse the instance, it shouldn't matter. */ + avoid_urandom = TRUE; + } else { + if (errno == ENOSYS) { + /* no support for getrandom(). We don't know whether + * we urandom will give us good quality. Assume yes. */ + have_syscall = FALSE; + } else { + /* unknown error. We'll read urandom below, but we don't have + * high-quality randomness. */ + has_high_quality = FALSE; + } + } + } + } +#endif + + urandom_success = FALSE; + if (!avoid_urandom) { +fd_open: + fd = open ("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (fd < 0) { + r = errno; + if (r == EINTR) + goto fd_open; + } else { + r = nm_utils_fd_read_loop_exact (fd, buf, n, TRUE); + close (fd); + if (r >= 0) + urandom_success = TRUE; + } + } + + if (!urandom_success) { + static _nm_thread_local GRand *rand = NULL; + gsize i; + int j; + + /* we failed to fill the bytes reading from urandom. + * Fill the bits using GRand pseudo random numbers. + * + * We don't have good quality. + */ + has_high_quality = FALSE; + + if (G_UNLIKELY (!rand)) + rand = g_rand_new (); + + nm_assert (n > 0); + i = 0; + for (;;) { + const union { + guint32 v32; + guint8 v8[4]; + } v = { + .v32 = g_rand_int (rand), + }; + + for (j = 0; j < 4; ) { + buf[i++] = v.v8[j++]; + if (i >= n) + goto done; + } + } +done: + ; + } + + return has_high_quality; +} diff --git a/shared/nm-utils/nm-random-utils.h b/shared/nm-utils/nm-random-utils.h new file mode 100644 index 00000000..15a118d3 --- /dev/null +++ b/shared/nm-utils/nm-random-utils.h @@ -0,0 +1,27 @@ +/* -*- 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 2017 Red Hat, Inc. + */ + +#ifndef __NM_RANDOM_UTILS_H__ +#define __NM_RANDOM_UTILS_H__ + +gboolean nm_utils_random_bytes (void *p, size_t n); + +#endif /* __NM_RANDOM_UTILS_H__ */ diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c index e7f31cbc..cea60edf 100644 --- a/shared/nm-utils/nm-shared-utils.c +++ b/shared/nm-utils/nm-shared-utils.c @@ -24,6 +24,17 @@ #include "nm-shared-utils.h" #include <errno.h> +#include <arpa/inet.h> +#include <poll.h> +#include <fcntl.h> + +/*****************************************************************************/ + +const void *const _NM_PTRARRAY_EMPTY[1] = { NULL }; + +/*****************************************************************************/ + +const NMIPAddr nm_ip_addr_zero = { 0 }; /*****************************************************************************/ @@ -106,6 +117,336 @@ nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...) /*****************************************************************************/ +/** + * nm_strquote: + * @buf: the output buffer of where to write the quoted @str argument. + * @buf_len: the size of @buf. + * @str: (allow-none): the string to quote. + * + * Writes @str to @buf with quoting. The resulting buffer + * is always NUL terminated, unless @buf_len is zero. + * If @str is %NULL, it writes "(null)". + * + * If @str needs to be truncated, the closing quote is '^' instead + * of '"'. + * + * This is similar to nm_strquote_a(), which however uses alloca() + * to allocate a new buffer. Also, here @buf_len is the size of @buf, + * while nm_strquote_a() has the number of characters to print. The latter + * doesn't include the quoting. + * + * Returns: the input buffer with the quoted string. + */ +const char * +nm_strquote (char *buf, gsize buf_len, const char *str) +{ + const char *const buf0 = buf; + + if (!str) { + nm_utils_strbuf_append_str (&buf, &buf_len, "(null)"); + goto out; + } + + if (G_UNLIKELY (buf_len <= 2)) { + switch (buf_len) { + case 2: + *(buf++) = '^'; + /* fall-through*/ + case 1: + *(buf++) = '\0'; + break; + } + goto out; + } + + *(buf++) = '"'; + buf_len--; + + nm_utils_strbuf_append_str (&buf, &buf_len, str); + + /* if the string was too long we indicate truncation with a + * '^' instead of a closing quote. */ + if (G_UNLIKELY (buf_len <= 1)) { + switch (buf_len) { + case 1: + buf[-1] = '^'; + break; + case 0: + buf[-2] = '^'; + break; + default: + nm_assert_not_reached (); + break; + } + } else { + nm_assert (buf_len >= 2); + *(buf++) = '"'; + *(buf++) = '\0'; + } + +out: + return buf0; +} + +/*****************************************************************************/ + +char _nm_utils_to_string_buffer[]; + +void +nm_utils_to_string_buffer_init (char **buf, gsize *len) +{ + if (!*buf) { + *buf = _nm_utils_to_string_buffer; + *len = sizeof (_nm_utils_to_string_buffer); + } +} + +gboolean +nm_utils_to_string_buffer_init_null (gconstpointer obj, char **buf, gsize *len) +{ + nm_utils_to_string_buffer_init (buf, len); + if (!obj) { + g_strlcpy (*buf, "(null)", *len); + return FALSE; + } + return TRUE; +} + +/*****************************************************************************/ + +const char * +nm_utils_flags2str (const NMUtilsFlags2StrDesc *descs, + gsize n_descs, + unsigned flags, + char *buf, + gsize len) +{ + gsize i; + char *p; + +#if NM_MORE_ASSERTS > 10 + nm_assert (descs); + nm_assert (n_descs > 0); + for (i = 0; i < n_descs; i++) { + gsize j; + + nm_assert (descs[i].name && descs[i].name[0]); + for (j = 0; j < i; j++) + nm_assert (descs[j].flag != descs[i].flag); + } +#endif + + nm_utils_to_string_buffer_init (&buf, &len); + + if (!len) + return buf; + + buf[0] = '\0'; + p = buf; + if (!flags) { + for (i = 0; i < n_descs; i++) { + if (!descs[i].flag) { + nm_utils_strbuf_append_str (&p, &len, descs[i].name); + break; + } + } + return buf; + } + + for (i = 0; flags && i < n_descs; i++) { + if ( descs[i].flag + && NM_FLAGS_ALL (flags, descs[i].flag)) { + flags &= ~descs[i].flag; + + if (buf[0] != '\0') + nm_utils_strbuf_append_c (&p, &len, ','); + nm_utils_strbuf_append_str (&p, &len, descs[i].name); + } + } + if (flags) { + if (buf[0] != '\0') + nm_utils_strbuf_append_c (&p, &len, ','); + nm_utils_strbuf_append (&p, &len, "0x%x", flags); + } + return buf; +}; + +/*****************************************************************************/ + +/** + * _nm_utils_ip4_prefix_to_netmask: + * @prefix: a CIDR prefix + * + * Returns: the netmask represented by the prefix, in network byte order + **/ +guint32 +_nm_utils_ip4_prefix_to_netmask (guint32 prefix) +{ + return prefix < 32 ? ~htonl(0xFFFFFFFF >> prefix) : 0xFFFFFFFF; +} + +/** + * _nm_utils_ip4_get_default_prefix: + * @ip: an IPv4 address (in network byte order) + * + * When the Internet was originally set up, various ranges of IP addresses were + * segmented into three network classes: A, B, and C. This function will return + * a prefix that is associated with the IP address specified defining where it + * falls in the predefined classes. + * + * Returns: the default class prefix for the given IP + **/ +/* The function is originally from ipcalc.c of Red Hat's initscripts. */ +guint32 +_nm_utils_ip4_get_default_prefix (guint32 ip) +{ + if (((ntohl (ip) & 0xFF000000) >> 24) <= 127) + return 8; /* Class A - 255.0.0.0 */ + else if (((ntohl (ip) & 0xFF000000) >> 24) <= 191) + return 16; /* Class B - 255.255.0.0 */ + + return 24; /* Class C - 255.255.255.0 */ +} + +gboolean +nm_utils_ip_is_site_local (int addr_family, + const void *address) +{ + in_addr_t addr4; + + switch (addr_family) { + case AF_INET: + /* RFC1918 private addresses + * 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 */ + addr4 = ntohl (*((const in_addr_t *) address)); + return (addr4 & 0xff000000) == 0x0a000000 + || (addr4 & 0xfff00000) == 0xac100000 + || (addr4 & 0xffff0000) == 0xc0a80000; + case AF_INET6: + return IN6_IS_ADDR_SITELOCAL (address); + default: + g_return_val_if_reached (FALSE); + } +} + +/*****************************************************************************/ + +gboolean +nm_utils_parse_inaddr_bin (int addr_family, + const char *text, + gpointer out_addr) +{ + NMIPAddr addrbin; + + g_return_val_if_fail (text, FALSE); + + if (addr_family == AF_UNSPEC) + addr_family = strchr (text, ':') ? AF_INET6 : AF_INET; + else + g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), FALSE); + + /* use a temporary variable @addrbin, to guarantee that @out_addr + * is only modified on success. */ + if (inet_pton (addr_family, text, &addrbin) != 1) + return FALSE; + + if (out_addr) { + switch (addr_family) { + case AF_INET: + *((in_addr_t *) out_addr) = addrbin.addr4; + break; + case AF_INET6: + *((struct in6_addr *) out_addr) = addrbin.addr6; + break; + default: + nm_assert_not_reached (); + } + } + return TRUE; +} + +gboolean +nm_utils_parse_inaddr (int addr_family, + const char *text, + char **out_addr) +{ + NMIPAddr addrbin; + char addrstr_buf[MAX (INET_ADDRSTRLEN, INET6_ADDRSTRLEN)]; + + nm_assert (!out_addr || !*out_addr); + + if (!nm_utils_parse_inaddr_bin (addr_family, text, &addrbin)) + return FALSE; + NM_SET_OUT (out_addr, g_strdup (inet_ntop (addr_family, &addrbin, addrstr_buf, sizeof (addrstr_buf)))); + return TRUE; +} + +gboolean +nm_utils_parse_inaddr_prefix_bin (int addr_family, + const char *text, + gpointer out_addr, + int *out_prefix) +{ + gs_free char *addrstr_free = NULL; + int prefix = -1; + const char *slash; + const char *addrstr; + NMIPAddr addrbin; + int addr_len; + + g_return_val_if_fail (text, FALSE); + + if (addr_family == AF_UNSPEC) + addr_family = strchr (text, ':') ? AF_INET6 : AF_INET; + + if (addr_family == AF_INET) + addr_len = sizeof (in_addr_t); + else if (addr_family == AF_INET6) + addr_len = sizeof (struct in6_addr); + else + g_return_val_if_reached (FALSE); + + slash = strchr (text, '/'); + if (slash) + addrstr = addrstr_free = g_strndup (text, slash - text); + else + addrstr = text; + + if (inet_pton (addr_family, addrstr, &addrbin) != 1) + return FALSE; + + if (slash) { + prefix = _nm_utils_ascii_str_to_int64 (slash + 1, 10, + 0, + addr_family == AF_INET ? 32 : 128, + -1); + if (prefix == -1) + return FALSE; + } + + if (out_addr) + memcpy (out_addr, &addrbin, addr_len); + NM_SET_OUT (out_prefix, prefix); + return TRUE; +} + +gboolean +nm_utils_parse_inaddr_prefix (int addr_family, + const char *text, + char **out_addr, + int *out_prefix) +{ + NMIPAddr addrbin; + char addrstr_buf[MAX (INET_ADDRSTRLEN, INET6_ADDRSTRLEN)]; + + if (!nm_utils_parse_inaddr_prefix_bin (addr_family, text, &addrbin, out_prefix)) + return FALSE; + NM_SET_OUT (out_addr, g_strdup (inet_ntop (addr_family, &addrbin, addrstr_buf, sizeof (addrstr_buf)))); + return TRUE; +} + +/*****************************************************************************/ + /* _nm_utils_ascii_str_to_int64: * * A wrapper for g_ascii_strtoll, that checks whether the whole string @@ -159,6 +500,118 @@ _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 ma /*****************************************************************************/ /** + * nm_utils_strsplit_set: + * @str: the string to split. + * @delimiters: the set of delimiters. If %NULL, defaults to " \t\n", + * like bash's $IFS. + * + * This is a replacement for g_strsplit_set() which avoids copying + * each word once (the entire strv array), but instead copies it once + * and all words point into that internal copy. + * + * Another difference from g_strsplit_set() is that this never returns + * empty words. Multiple delimiters are combined and treated as one. + * + * 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 + * are removed). + * The strings to which the result strv array points to are allocated + * after the returned result itself. Don't free the strings themself, + * but free everything with g_free(). + */ +const char ** +nm_utils_strsplit_set (const char *str, const char *delimiters) +{ + const char **ptr, **ptr0; + gsize alloc_size, plen, i; + gsize str_len; + char *s0; + char *s; + guint8 delimiters_table[256]; + + if (!str) + return NULL; + + /* initialize lookup table for delimiter */ + if (!delimiters) + delimiters = " \t\n"; + memset (delimiters_table, 0, sizeof (delimiters_table)); + for (i = 0; delimiters[i]; i++) + delimiters_table[(guint8) delimiters[i]] = 1; + +#define _is_delimiter(ch, delimiters_table) \ + ((delimiters_table)[(guint8) (ch)] != 0) + + /* skip initial delimiters, and return of the remaining string is + * empty. */ + while (_is_delimiter (str[0], delimiters_table)) + str++; + if (!str[0]) + return NULL; + + str_len = strlen (str) + 1; + alloc_size = 8; + + /* we allocate the buffer larger, so to copy @str at the + * end of it as @s0. */ + ptr0 = g_malloc ((sizeof (const char *) * (alloc_size + 1)) + str_len); + s0 = (char *) &ptr0[alloc_size + 1]; + memcpy (s0, str, str_len); + + plen = 0; + s = s0; + ptr = ptr0; + + while (TRUE) { + if (plen >= alloc_size) { + const char **ptr_old = ptr; + + /* reallocate the buffer. Note that for now the string + * continues to be in ptr0/s0. We fix that at the end. */ + alloc_size += 2; + ptr = g_malloc ((sizeof (const char *) * (alloc_size + 1)) + str_len); + memcpy (ptr, ptr_old, sizeof (const char *) * plen); + if (ptr_old != ptr0) + g_free (ptr_old); + } + + ptr[plen++] = s; + + nm_assert (s[0] && !_is_delimiter (s[0], delimiters_table)); + + while (TRUE) { + s++; + if (_is_delimiter (s[0], delimiters_table)) + break; + if (s[0] == '\0') + goto done; + } + + s[0] = '\0'; + s++; + while (_is_delimiter (s[0], delimiters_table)) + s++; + if (s[0] == '\0') + break; + } +done: + ptr[plen] = NULL; + + if (ptr != ptr0) { + /* we reallocated the buffer. We must copy over the + * string @s0 and adjust the pointers. */ + s = (char *) &ptr[alloc_size + 1]; + memcpy (s, s0, str_len); + for (i = 0; i < plen; i++) + ptr[i] = &s[ptr[i] - s0]; + g_free (ptr0); + } + + return ptr; +} + +/** * nm_utils_strv_find_first: * @list: the strv list to search * @len: the length of the list, or a negative value if @list is %NULL terminated. @@ -204,6 +657,35 @@ nm_utils_strv_find_first (char **list, gssize len, const char *needle) return -1; } +char ** +_nm_utils_strv_cleanup (char **strv, + gboolean strip_whitespace, + gboolean skip_empty, + gboolean skip_repeated) +{ + guint i, j; + + if (!strv || !*strv) + return strv; + + if (strip_whitespace) { + for (i = 0; strv[i]; i++) + g_strstrip (strv[i]); + } + if (!skip_empty && !skip_repeated) + return strv; + j = 0; + for (i = 0; strv[i]; i++) { + if ( (skip_empty && !*strv[i]) + || (skip_repeated && nm_utils_strv_find_first (strv, j, strv[i]) >= 0)) + g_free (strv[i]); + else + strv[j++] = strv[i]; + } + strv[j] = NULL; + return strv; +} + /*****************************************************************************/ gint @@ -363,6 +845,16 @@ nm_g_object_set_property (GObject *object, return TRUE; } +GParamSpec * +nm_g_object_class_find_property_from_gtype (GType gtype, + const char *property_name) +{ + nm_auto_unref_gtypeclass GObjectClass *gclass = NULL; + + gclass = g_type_class_ref (gtype); + return g_object_class_find_property (gclass, property_name); +} + /*****************************************************************************/ static void @@ -502,3 +994,98 @@ nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags) } return str; } + +/*****************************************************************************/ + +/* taken from systemd's fd_wait_for_event(). Note that the timeout + * is here in nano-seconds, not micro-seconds. */ +int +nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns) +{ + struct pollfd pollfd = { + .fd = fd, + .events = event, + }; + struct timespec ts, *pts; + int r; + + if (timeout_ns < 0) + pts = NULL; + else { + ts.tv_sec = (time_t) (timeout_ns / NM_UTILS_NS_PER_SECOND); + ts.tv_nsec = (long int) (timeout_ns % NM_UTILS_NS_PER_SECOND); + pts = &ts; + } + + r = ppoll (&pollfd, 1, pts, NULL); + if (r < 0) + return -errno; + if (r == 0) + return 0; + return pollfd.revents; +} + +/* taken from systemd's loop_read() */ +ssize_t +nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll) +{ + uint8_t *p = buf; + ssize_t n = 0; + + g_return_val_if_fail (fd >= 0, -EINVAL); + g_return_val_if_fail (buf, -EINVAL); + + /* If called with nbytes == 0, let's call read() at least + * once, to validate the operation */ + + if (nbytes > (size_t) SSIZE_MAX) + return -EINVAL; + + do { + ssize_t k; + + k = read (fd, p, nbytes); + if (k < 0) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN && do_poll) { + + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via read() */ + + (void) nm_utils_fd_wait_for_event (fd, POLLIN, -1); + continue; + } + + return n > 0 ? n : -errno; + } + + if (k == 0) + return n; + + g_assert ((size_t) k <= nbytes); + + p += k; + nbytes -= k; + n += k; + } while (nbytes > 0); + + return n; +} + +/* taken from systemd's loop_read_exact() */ +int +nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll) +{ + ssize_t n; + + n = nm_utils_fd_read_loop (fd, buf, nbytes, do_poll); + if (n < 0) + return (int) n; + if ((size_t) n != nbytes) + return -EIO; + + return 0; +} diff --git a/shared/nm-utils/nm-shared-utils.h b/shared/nm-utils/nm-shared-utils.h index 69f9533d..96aab534 100644 --- a/shared/nm-utils/nm-shared-utils.h +++ b/shared/nm-utils/nm-shared-utils.h @@ -22,8 +22,143 @@ #ifndef __NM_SHARED_UTILS_H__ #define __NM_SHARED_UTILS_H__ +#include <netinet/in.h> + +/*****************************************************************************/ + +typedef struct { + union { + guint8 addr_ptr[1]; + in_addr_t addr4; + struct in6_addr addr6; + + /* NMIPAddr is really a union for IP addresses. + * However, as ethernet addresses fit in here nicely, use + * it also for an ethernet MAC address. */ + guint8 addr_eth[6 /*ETH_ALEN*/]; + }; +} NMIPAddr; + +extern const NMIPAddr nm_ip_addr_zero; + +/*****************************************************************************/ + +static inline char +nm_utils_addr_family_to_char (int addr_family) +{ + switch (addr_family) { + case AF_INET: return '4'; + case AF_INET6: return '6'; + } + g_return_val_if_reached ('?'); +} + +static inline gsize +nm_utils_addr_family_to_size (int addr_family) +{ + switch (addr_family) { + case AF_INET: return sizeof (in_addr_t); + case AF_INET6: return sizeof (struct in6_addr); + } + g_return_val_if_reached (0); +} + +#define nm_assert_addr_family(addr_family) \ + nm_assert (NM_IN_SET ((addr_family), AF_INET, AF_INET6)) + +/*****************************************************************************/ + +#define NM_CMP_RETURN(c) \ + G_STMT_START { \ + const int _cc = (c); \ + if (_cc) \ + return _cc < 0 ? -1 : 1; \ + } G_STMT_END + +#define NM_CMP_SELF(a, b) \ + G_STMT_START { \ + typeof (a) _a = (a); \ + typeof (b) _b = (b); \ + \ + if (_a == _b) \ + return 0; \ + if (!_a) \ + return -1; \ + if (!_b) \ + return 1; \ + } G_STMT_END + +#define NM_CMP_DIRECT(a, b) \ + G_STMT_START { \ + typeof (a) _a = (a); \ + typeof (b) _b = (b); \ + \ + if (_a != _b) \ + return (_a < _b) ? -1 : 1; \ + } G_STMT_END + +#define NM_CMP_DIRECT_MEMCMP(a, b, size) \ + NM_CMP_RETURN (memcmp ((a), (b), (size))) + +#define NM_CMP_DIRECT_IN6ADDR(a, b) \ + G_STMT_START { \ + const struct in6_addr *const _a = (a); \ + const struct in6_addr *const _b = (b); \ + NM_CMP_RETURN (memcmp (_a, _b, sizeof (struct in6_addr))); \ + } G_STMT_END + +#define NM_CMP_FIELD(a, b, field) \ + NM_CMP_DIRECT (((a)->field), ((b)->field)) + +#define NM_CMP_FIELD_UNSAFE(a, b, field) \ + G_STMT_START { \ + /* it's unsafe, because it evaluates the arguments more then once. + * This is necessary for bitfields, for which typeof() doesn't work. */ \ + if (((a)->field) != ((b)->field)) \ + return ((a)->field < ((b)->field)) ? -1 : 1; \ + } G_STMT_END + +#define NM_CMP_FIELD_BOOL(a, b, field) \ + NM_CMP_DIRECT (!!((a)->field), !!((b)->field)) + +#define NM_CMP_FIELD_STR(a, b, field) \ + NM_CMP_RETURN (strcmp (((a)->field), ((b)->field))) + +#define NM_CMP_FIELD_STR_INTERNED(a, b, field) \ + G_STMT_START { \ + const char *_a = ((a)->field); \ + const char *_b = ((b)->field); \ + \ + if (_a != _b) { \ + NM_CMP_RETURN (g_strcmp0 (_a, _b)); \ + } \ + } G_STMT_END + +#define NM_CMP_FIELD_STR0(a, b, field) \ + NM_CMP_RETURN (g_strcmp0 (((a)->field), ((b)->field))) + +#define NM_CMP_FIELD_MEMCMP_LEN(a, b, field, len) \ + NM_CMP_RETURN (memcmp (&((a)->field), &((b)->field), \ + MIN (len, sizeof ((a)->field)))) + +#define NM_CMP_FIELD_MEMCMP(a, b, field) \ + NM_CMP_RETURN (memcmp (&((a)->field), \ + &((b)->field), \ + sizeof ((a)->field))) + +#define NM_CMP_FIELD_IN6ADDR(a, b, field) \ + G_STMT_START { \ + const struct in6_addr *const _a = &((a)->field); \ + const struct in6_addr *const _b = &((b)->field); \ + NM_CMP_RETURN (memcmp (_a, _b, sizeof (struct in6_addr))); \ + } G_STMT_END + /*****************************************************************************/ +extern const void *const _NM_PTRARRAY_EMPTY[1]; + +#define NM_PTRARRAY_EMPTY(type) ((type const*) _NM_PTRARRAY_EMPTY) + static inline void _nm_utils_strbuf_init (char *buf, gsize len, char **p_buf_ptr, gsize *p_buf_len) { @@ -41,12 +176,47 @@ void nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...) _n 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); +const char *nm_strquote (char *buf, gsize buf_len, const char *str); + /*****************************************************************************/ +const char **nm_utils_strsplit_set (const char *str, const char *delimiters); + gssize nm_utils_strv_find_first (char **list, gssize len, const char *needle); +char **_nm_utils_strv_cleanup (char **strv, + gboolean strip_whitespace, + gboolean skip_empty, + gboolean skip_repeated); + +/*****************************************************************************/ + +guint32 _nm_utils_ip4_prefix_to_netmask (guint32 prefix); +guint32 _nm_utils_ip4_get_default_prefix (guint32 ip); + +gboolean nm_utils_ip_is_site_local (int addr_family, + const void *address); + /*****************************************************************************/ +gboolean nm_utils_parse_inaddr_bin (int addr_family, + const char *text, + gpointer out_addr); + +gboolean nm_utils_parse_inaddr (int addr_family, + const char *text, + char **out_addr); + +gboolean nm_utils_parse_inaddr_prefix_bin (int addr_family, + const char *text, + gpointer out_addr, + int *out_prefix); + +gboolean nm_utils_parse_inaddr_prefix (int addr_family, + const char *text, + char **out_addr, + int *out_prefix); + 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, @@ -54,6 +224,123 @@ gint _nm_utils_ascii_str_to_bool (const char *str, /*****************************************************************************/ +extern char _nm_utils_to_string_buffer[2096]; + +void nm_utils_to_string_buffer_init (char **buf, gsize *len); +gboolean nm_utils_to_string_buffer_init_null (gconstpointer obj, char **buf, gsize *len); + +/*****************************************************************************/ + +typedef struct { + unsigned flag; + const char *name; +} NMUtilsFlags2StrDesc; + +#define NM_UTILS_FLAGS2STR(f, n) { .flag = f, .name = ""n, } + +#define _NM_UTILS_FLAGS2STR_DEFINE(scope, fcn_name, flags_type, ...) \ +scope const char * \ +fcn_name (flags_type flags, char *buf, gsize len) \ +{ \ + static const NMUtilsFlags2StrDesc descs[] = { \ + __VA_ARGS__ \ + }; \ + G_STATIC_ASSERT (sizeof (flags_type) <= sizeof (unsigned)); \ + return nm_utils_flags2str (descs, G_N_ELEMENTS (descs), flags, buf, len); \ +}; + +#define NM_UTILS_FLAGS2STR_DEFINE(fcn_name, flags_type, ...) \ + _NM_UTILS_FLAGS2STR_DEFINE (, fcn_name, flags_type, __VA_ARGS__) +#define NM_UTILS_FLAGS2STR_DEFINE_STATIC(fcn_name, flags_type, ...) \ + _NM_UTILS_FLAGS2STR_DEFINE (static, fcn_name, flags_type, __VA_ARGS__) + +const char *nm_utils_flags2str (const NMUtilsFlags2StrDesc *descs, + gsize n_descs, + unsigned flags, + char *buf, + gsize len); + +/*****************************************************************************/ + +#define NM_UTILS_ENUM2STR(v, n) (void) 0; case v: s = ""n""; break; (void) 0 +#define NM_UTILS_ENUM2STR_IGNORE(v) (void) 0; case v: break; (void) 0 + +#define _NM_UTILS_ENUM2STR_DEFINE(scope, fcn_name, lookup_type, int_fmt, ...) \ +scope const char * \ +fcn_name (lookup_type val, char *buf, gsize len) \ +{ \ + nm_utils_to_string_buffer_init (&buf, &len); \ + if (len) { \ + const char *s = NULL; \ + switch (val) { \ + (void) 0, \ + __VA_ARGS__ \ + (void) 0; \ + }; \ + if (s) \ + g_strlcpy (buf, s, len); \ + else \ + g_snprintf (buf, len, "(%"int_fmt")", val); \ + } \ + return buf; \ +} + +#define NM_UTILS_ENUM2STR_DEFINE(fcn_name, lookup_type, ...) \ + _NM_UTILS_ENUM2STR_DEFINE (, fcn_name, lookup_type, "d", __VA_ARGS__) +#define NM_UTILS_ENUM2STR_DEFINE_STATIC(fcn_name, lookup_type, ...) \ + _NM_UTILS_ENUM2STR_DEFINE (static, fcn_name, lookup_type, "d", __VA_ARGS__) + +/*****************************************************************************/ + +#define _nm_g_slice_free_fcn_define(mem_size) \ +static inline void \ +_nm_g_slice_free_fcn_##mem_size (gpointer mem_block) \ +{ \ + g_slice_free1 (mem_size, mem_block); \ +} + +_nm_g_slice_free_fcn_define (1) +_nm_g_slice_free_fcn_define (2) +_nm_g_slice_free_fcn_define (4) +_nm_g_slice_free_fcn_define (8) +_nm_g_slice_free_fcn_define (12) +_nm_g_slice_free_fcn_define (16) + +#define _nm_g_slice_free_fcn1(mem_size) \ + ({ \ + void (*_fcn) (gpointer); \ + \ + /* If mem_size is a compile time constant, the compiler + * will be able to optimize this. Hence, you don't want + * to call this with a non-constant size argument. */ \ + switch (mem_size) { \ + case 1: _fcn = _nm_g_slice_free_fcn_1; break; \ + case 2: _fcn = _nm_g_slice_free_fcn_2; break; \ + case 4: _fcn = _nm_g_slice_free_fcn_4; break; \ + case 8: _fcn = _nm_g_slice_free_fcn_8; break; \ + case 12: _fcn = _nm_g_slice_free_fcn_12; break; \ + case 16: _fcn = _nm_g_slice_free_fcn_16; break; \ + default: g_assert_not_reached (); _fcn = NULL; break; \ + } \ + _fcn; \ + }) + +/** + * nm_g_slice_free_fcn: + * @type: type argument for sizeof() operator that you would + * pass to g_slice_new(). + * + * Returns: a function pointer with GDestroyNotify signature + * for g_slice_free(type,*). + * + * Only certain types are implemented. You'll get an assertion + * using the wrong type. */ +#define nm_g_slice_free_fcn(type) (_nm_g_slice_free_fcn1 (sizeof (type))) + +#define nm_g_slice_free_fcn_gint64 (nm_g_slice_free_fcn (gint64)) + +/*****************************************************************************/ + /** * NMUtilsError: * @NM_UTILS_ERROR_UNKNOWN: unknown or unclassified error @@ -62,10 +349,12 @@ gint _nm_utils_ascii_str_to_bool (const char *str, * 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_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 >*/ } NMUtilsError; #define NM_UTILS_ERROR (nm_utils_error_quark ()) @@ -84,6 +373,9 @@ gboolean nm_g_object_set_property (GObject *object, const GValue *value, GError **error); +GParamSpec *nm_g_object_class_find_property_from_gtype (GType gtype, + const char *property_name); + /*****************************************************************************/ typedef enum { @@ -102,4 +394,16 @@ char *nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flag /*****************************************************************************/ +#define NM_UTILS_NS_PER_SECOND ((gint64) 1000000000) +#define NM_UTILS_NS_PER_MSEC ((gint64) 1000000) +#define NM_UTILS_NS_TO_MSEC_CEIL(nsec) (((nsec) + (NM_UTILS_NS_PER_MSEC - 1)) / NM_UTILS_NS_PER_MSEC) + +/*****************************************************************************/ + +int nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns); +ssize_t nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll); +int nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll); + +/*****************************************************************************/ + #endif /* __NM_SHARED_UTILS_H__ */ diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h index bc521131..126546ec 100644 --- a/shared/nm-utils/nm-test-utils.h +++ b/shared/nm-utils/nm-test-utils.h @@ -41,7 +41,7 @@ * NMTST_SEED_RAND environment variable: * Tests that use random numbers from nmtst_get_rand() get seeded randomly at each start. * You can specify the seed by setting NMTST_SEED_RAND. Also, tests will print the seed - * to stdout, so that you know the choosen seed. + * to stdout, so that you know the chosen seed. * * * NMTST_DEBUG environment variable: @@ -82,7 +82,8 @@ * Whether long-running tests are enabled is determined as follows (highest priority first): * - specifying the value in NMTST_DEBUG has highest priority * - respect g_test_quick(), if the command line contains '-mslow', '-mquick', '-mthorough'. - * - use compile time default + * - use compile time default (CFLAGS=-DNMTST_TEST_QUICK=TRUE) + * - enable slow tests by default * * "p=PATH"|"s=PATH": passes the path to g_test_init() as "-p" and "-s", respectively. * Unfortunately, these options conflict with "--tap" which our makefile passes to the @@ -137,12 +138,13 @@ #define NMTST_WAIT(max_wait_ms, wait) \ ({ \ gboolean _not_expired = TRUE; \ - gint64 _nmtst_end, _nmtst_max_wait_us = (max_wait_ms) * 1000L; \ + const gint64 nmtst_wait_start_us = g_get_monotonic_time (); \ + const gint64 nmtst_wait_duration_us = (max_wait_ms) * 1000L; \ + const gint64 nmtst_wait_end_us = nmtst_wait_start_us + nmtst_wait_duration_us; \ \ - _nmtst_end = g_get_monotonic_time () + _nmtst_max_wait_us; \ while (TRUE) { \ { wait }; \ - if (g_get_monotonic_time () > _nmtst_end) { \ + if (g_get_monotonic_time () > nmtst_wait_end_us) { \ _not_expired = FALSE; \ break; \ } \ @@ -281,6 +283,15 @@ nmtst_free (void) } static inline void +_nmtst_log_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + g_print ("%s\n", message); +} + +static inline void __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_level, const char *log_domains, gboolean *out_set_logging) { const char *nmtst_debug; @@ -589,6 +600,11 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ g_assert_no_error (error); } #endif + + g_log_set_handler (G_LOG_DOMAIN, + G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, + _nmtst_log_handler, + NULL); } #ifndef _NMTST_INSIDE_CORE @@ -647,13 +663,18 @@ nmtst_test_quick (void) typedef struct _NmtstTestData NmtstTestData; -typedef void (*NmtstTestDataRelease) (const NmtstTestData *test_data); +typedef void (*NmtstTestHandler) (const NmtstTestData *test_data); struct _NmtstTestData { - const char *testpath; - NmtstTestDataRelease fcn_release; + union { + const char *testpath; + char *_testpath; + }; gsize n_args; - gpointer args[1]; + gpointer *args; + NmtstTestHandler _func_setup; + GTestDataFunc _func_test; + NmtstTestHandler _func_teardown; }; static inline void @@ -670,8 +691,8 @@ _nmtst_test_data_unpack (const NmtstTestData *test_data, gsize n_args, ...) for (i = 0; i < n_args; i++) { p = va_arg (ap, gpointer *); - g_assert (p); - *p = test_data->args[i]; + if (p) + *p = test_data->args[i]; } va_end (ap); } @@ -684,25 +705,42 @@ _nmtst_test_data_free (gpointer data) g_assert (test_data); - if (test_data->fcn_release) - test_data->fcn_release (test_data); - - g_free ((gpointer) test_data->testpath); + g_free (test_data->_testpath); g_free (test_data); } static inline void -_nmtst_add_test_func_full (const char *testpath, GTestDataFunc test_func, NmtstTestDataRelease fcn_release, gsize n_args, ...) +_nmtst_test_run (gconstpointer data) +{ + const NmtstTestData *test_data = data; + + if (test_data->_func_setup) + test_data->_func_setup (test_data); + + test_data->_func_test (test_data); + + if (test_data->_func_teardown) + test_data->_func_teardown (test_data); +} + +static inline void +_nmtst_add_test_func_full (const char *testpath, GTestDataFunc func_test, NmtstTestHandler func_setup, NmtstTestHandler func_teardown, gsize n_args, ...) { gsize i; NmtstTestData *data; va_list ap; - data = g_malloc (G_STRUCT_OFFSET (NmtstTestData, args) + sizeof (gpointer) * (n_args + 1)); + g_assert (testpath && testpath[0]); + g_assert (func_test); + + data = g_malloc0 (sizeof (NmtstTestData) + (sizeof (gpointer) * (n_args + 1))); - data->testpath = g_strdup (testpath); - data->fcn_release = fcn_release; + data->_testpath = g_strdup (testpath); + data->_func_test = func_test; + data->_func_setup = func_setup; + data->_func_teardown = func_teardown; data->n_args = n_args; + data->args = (gpointer) &data[1]; va_start (ap, n_args); for (i = 0; i < n_args; i++) data->args[i] = va_arg (ap, gpointer); @@ -711,11 +749,11 @@ _nmtst_add_test_func_full (const char *testpath, GTestDataFunc test_func, NmtstT g_test_add_data_func_full (testpath, data, - test_func, + _nmtst_test_run, _nmtst_test_data_free); } -#define nmtst_add_test_func_full(testpath, test_func, fcn_release, ...) _nmtst_add_test_func_full(testpath, test_func, fcn_release, NM_NARG (__VA_ARGS__), ##__VA_ARGS__) -#define nmtst_add_test_func(testpath, test_func, ...) nmtst_add_test_func_full(testpath, test_func, NULL, ##__VA_ARGS__) +#define nmtst_add_test_func_full(testpath, func_test, func_setup, func_teardown, ...) _nmtst_add_test_func_full(testpath, func_test, func_setup, func_teardown, NM_NARG (__VA_ARGS__), ##__VA_ARGS__) +#define nmtst_add_test_func(testpath, func_test, ...) nmtst_add_test_func_full(testpath, func_test, NULL, NULL, ##__VA_ARGS__) /*****************************************************************************/ @@ -1659,6 +1697,63 @@ nmtst_assert_setting_verifies (NMSetting *setting) g_assert (success); } +#if defined(__NM_SIMPLE_CONNECTION_H__) +static inline void +_nmtst_assert_connection_has_settings (NMConnection *connection, gboolean has_at_least, gboolean has_at_most, ...) +{ + gs_unref_hashtable GHashTable *names = NULL; + gs_free NMSetting **settings = NULL; + va_list ap; + const char *name; + guint i, len; + gs_unref_ptrarray GPtrArray *names_arr = NULL; + + g_assert (NM_IS_CONNECTION (connection)); + + names = g_hash_table_new (g_str_hash, g_str_equal); + names_arr = g_ptr_array_new (); + + va_start (ap, has_at_most); + while ((name = va_arg (ap, const char *))) { + if (!nm_g_hash_table_add (names, (gpointer) name)) + g_assert_not_reached (); + g_ptr_array_add (names_arr, (gpointer) name); + } + va_end (ap); + + g_ptr_array_add (names_arr, NULL); + + settings = nm_connection_get_settings (connection, &len); + for (i = 0; i < len; i++) { + if ( !g_hash_table_remove (names, nm_setting_get_name (settings[i])) + && has_at_most) { + g_error ("nmtst_assert_connection_has_settings(): has setting \"%s\" which is not expected", + nm_setting_get_name (settings[i])); + } + } + if ( g_hash_table_size (names) > 0 + && has_at_least) { + gs_free char *expected_str = g_strjoinv (" ", (char **) names_arr->pdata); + gs_free const char **settings_names = NULL; + gs_free char *has_str = NULL; + + settings_names = g_new0 (const char *, len + 1); + for (i = 0; i < len; i++) + settings_names[i] = nm_setting_get_name (settings[i]); + has_str = g_strjoinv (" ", (char **) settings_names); + + g_error ("nmtst_assert_connection_has_settings(): the setting lacks %u expected settings (expected: [%s] vs. has: [%s])", + g_hash_table_size (names), + expected_str, + has_str); + } +} +#define nmtst_assert_connection_has_settings(connection, ...) _nmtst_assert_connection_has_settings ((connection), TRUE, TRUE, __VA_ARGS__, NULL) +#define nmtst_assert_connection_has_settings_at_least(connection, ...) _nmtst_assert_connection_has_settings ((connection), TRUE, FALSE, __VA_ARGS__, NULL) +#define nmtst_assert_connection_has_settings_at_most(connection, ...) _nmtst_assert_connection_has_settings ((connection), FALSE, TRUE, __VA_ARGS__, NULL) + +#endif /* __NM_SIMPLE_CONNECTION_H__ */ + static inline void nmtst_assert_setting_verify_fails (NMSetting *setting, GQuark expect_error_domain, diff --git a/shared/nm-utils/siphash24.c b/shared/nm-utils/siphash24.c new file mode 100644 index 00000000..3a5a635d --- /dev/null +++ b/shared/nm-utils/siphash24.c @@ -0,0 +1,203 @@ +/* + SipHash reference C implementation + + Written in 2012 by + Jean-Philippe Aumasson <jeanphilippe.aumasson@gmail.com> + Daniel J. Bernstein <djb@cr.yp.to> + + To the extent possible under law, the author(s) have dedicated all copyright + and related and neighboring rights to this software to the public domain + worldwide. This software is distributed without any warranty. + + You should have received a copy of the CC0 Public Domain Dedication along with + this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>. + + (Minimal changes made by Lennart Poettering, to make clean for inclusion in systemd) + (Refactored by Tom Gundersen to split up in several functions and follow systemd + coding style) +*/ + +#include "nm-default.h" + +#define assert(cond) nm_assert (cond) + +#include <stdio.h> + +#include "siphash24.h" +#include "unaligned.h" + +static inline uint64_t rotate_left(uint64_t x, uint8_t b) { + assert(b < 64); + + return (x << b) | (x >> (64 - b)); +} + +static inline void sipround(struct siphash *state) { + assert(state); + + state->v0 += state->v1; + state->v1 = rotate_left(state->v1, 13); + state->v1 ^= state->v0; + state->v0 = rotate_left(state->v0, 32); + state->v2 += state->v3; + state->v3 = rotate_left(state->v3, 16); + state->v3 ^= state->v2; + state->v0 += state->v3; + state->v3 = rotate_left(state->v3, 21); + state->v3 ^= state->v0; + state->v2 += state->v1; + state->v1 = rotate_left(state->v1, 17); + state->v1 ^= state->v2; + state->v2 = rotate_left(state->v2, 32); +} + +void siphash24_init(struct siphash *state, const uint8_t k[16]) { + uint64_t k0, k1; + + assert(state); + assert(k); + + k0 = unaligned_read_le64(k); + k1 = unaligned_read_le64(k + 8); + + *state = (struct siphash) { + /* "somepseudorandomlygeneratedbytes" */ + .v0 = 0x736f6d6570736575ULL ^ k0, + .v1 = 0x646f72616e646f6dULL ^ k1, + .v2 = 0x6c7967656e657261ULL ^ k0, + .v3 = 0x7465646279746573ULL ^ k1, + .padding = 0, + .inlen = 0, + }; +} + +void siphash24_compress(const void *_in, size_t inlen, struct siphash *state) { + + const uint8_t *in = _in; + const uint8_t *end = in + inlen; + size_t left = state->inlen & 7; + uint64_t m; + + assert(in); + assert(state); + + /* Update total length */ + state->inlen += inlen; + + /* If padding exists, fill it out */ + if (left > 0) { + for ( ; in < end && left < 8; in ++, left ++) + state->padding |= ((uint64_t) *in) << (left * 8); + + if (in == end && left < 8) + /* We did not have enough input to fill out the padding completely */ + return; + +#ifdef DEBUG + printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); + printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); + printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); + printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); + printf("(%3zu) compress padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t)state->padding); +#endif + + state->v3 ^= state->padding; + sipround(state); + sipround(state); + state->v0 ^= state->padding; + + state->padding = 0; + } + + end -= (state->inlen % sizeof(uint64_t)); + + for ( ; in < end; in += 8) { + m = unaligned_read_le64(in); +#ifdef DEBUG + printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); + printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); + printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); + printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); + printf("(%3zu) compress %08x %08x\n", state->inlen, (uint32_t) (m >> 32), (uint32_t) m); +#endif + state->v3 ^= m; + sipround(state); + sipround(state); + state->v0 ^= m; + } + + left = state->inlen & 7; + switch (left) { + case 7: + state->padding |= ((uint64_t) in[6]) << 48; + /* fall through */ + case 6: + state->padding |= ((uint64_t) in[5]) << 40; + /* fall through */ + case 5: + state->padding |= ((uint64_t) in[4]) << 32; + /* fall through */ + case 4: + state->padding |= ((uint64_t) in[3]) << 24; + /* fall through */ + case 3: + state->padding |= ((uint64_t) in[2]) << 16; + /* fall through */ + case 2: + state->padding |= ((uint64_t) in[1]) << 8; + /* fall through */ + case 1: + state->padding |= ((uint64_t) in[0]); + /* fall through */ + case 0: + break; + } +} + +uint64_t siphash24_finalize(struct siphash *state) { + uint64_t b; + + assert(state); + + b = state->padding | (((uint64_t) state->inlen) << 56); + +#ifdef DEBUG + printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); + printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); + printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); + printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); + printf("(%3zu) padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t) state->padding); +#endif + + state->v3 ^= b; + sipround(state); + sipround(state); + state->v0 ^= b; + +#ifdef DEBUG + printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); + printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); + printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); + printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); +#endif + state->v2 ^= 0xff; + + sipround(state); + sipround(state); + sipround(state); + sipround(state); + + return state->v0 ^ state->v1 ^ state->v2 ^ state->v3; +} + +uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]) { + struct siphash state; + + assert(in); + assert(k); + + siphash24_init(&state, k); + siphash24_compress(in, inlen, &state); + + return siphash24_finalize(&state); +} diff --git a/shared/nm-utils/siphash24.h b/shared/nm-utils/siphash24.h new file mode 100644 index 00000000..54e2420c --- /dev/null +++ b/shared/nm-utils/siphash24.h @@ -0,0 +1,23 @@ +#pragma once + +#include <inttypes.h> +#include <stddef.h> +#include <stdint.h> +#include <sys/types.h> + +struct siphash { + uint64_t v0; + uint64_t v1; + uint64_t v2; + uint64_t v3; + uint64_t padding; + size_t inlen; +}; + +void siphash24_init(struct siphash *state, const uint8_t k[16]); +void siphash24_compress(const void *in, size_t inlen, struct siphash *state); +#define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state)) + +uint64_t siphash24_finalize(struct siphash *state); + +uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]); diff --git a/shared/nm-version-macros.h b/shared/nm-version-macros.h index 4ba9596e..34d919f5 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 (8) +#define NM_MINOR_VERSION (9) /** * NM_MICRO_VERSION: @@ -45,7 +45,7 @@ * Evaluates to the micro version number of NetworkManager which this source * compiled against. */ -#define NM_MICRO_VERSION (4) +#define NM_MICRO_VERSION (90) /** * NM_CHECK_VERSION: @@ -71,11 +71,10 @@ #define NM_VERSION_1_4 (NM_ENCODE_VERSION (1, 4, 0)) #define NM_VERSION_1_6 (NM_ENCODE_VERSION (1, 6, 0)) #define NM_VERSION_1_8 (NM_ENCODE_VERSION (1, 8, 0)) -#define NM_VERSION_1_8_2 (NM_ENCODE_VERSION (1, 8, 2)) -#define NM_VERSION_1_8_4 (NM_ENCODE_VERSION (1, 8, 4)) +#define NM_VERSION_1_10 (NM_ENCODE_VERSION (1, 10, 0)) -#define NM_VERSION_CUR_STABLE NM_VERSION_1_8_2 -#define NM_VERSION_NEXT_STABLE NM_VERSION_1_8_4 +#define NM_VERSION_CUR_STABLE NM_VERSION_1_8 +#define NM_VERSION_NEXT_STABLE NM_VERSION_1_10 #define NM_VERSION NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION, NM_MICRO_VERSION) diff --git a/shared/nm-version-macros.h.in b/shared/nm-version-macros.h.in index c4694ae2..c39f2604 100644 --- a/shared/nm-version-macros.h.in +++ b/shared/nm-version-macros.h.in @@ -71,11 +71,10 @@ #define NM_VERSION_1_4 (NM_ENCODE_VERSION (1, 4, 0)) #define NM_VERSION_1_6 (NM_ENCODE_VERSION (1, 6, 0)) #define NM_VERSION_1_8 (NM_ENCODE_VERSION (1, 8, 0)) -#define NM_VERSION_1_8_2 (NM_ENCODE_VERSION (1, 8, 2)) -#define NM_VERSION_1_8_4 (NM_ENCODE_VERSION (1, 8, 4)) +#define NM_VERSION_1_10 (NM_ENCODE_VERSION (1, 10, 0)) -#define NM_VERSION_CUR_STABLE NM_VERSION_1_8_2 -#define NM_VERSION_NEXT_STABLE NM_VERSION_1_8_4 +#define NM_VERSION_CUR_STABLE NM_VERSION_1_8 +#define NM_VERSION_NEXT_STABLE NM_VERSION_1_10 #define NM_VERSION NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION, NM_MICRO_VERSION) |