diff options
Diffstat (limited to 'src')
153 files changed, 21556 insertions, 141 deletions
diff --git a/src/core/NetworkManagerUtils.c b/src/core/NetworkManagerUtils.c index 1b44c68134..bb72aaf8d7 100644 --- a/src/core/NetworkManagerUtils.c +++ b/src/core/NetworkManagerUtils.c @@ -12,7 +12,7 @@ #include <linux/pkt_sched.h> #include <linux/if_ether.h> -#include "nm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-c-list.h" #include "libnm-core-aux-intern/nm-common-macros.h" #include "nm-utils.h" diff --git a/src/core/NetworkManagerUtils.h b/src/core/NetworkManagerUtils.h index 2afb5a3ecc..28b0531ac0 100644 --- a/src/core/NetworkManagerUtils.h +++ b/src/core/NetworkManagerUtils.h @@ -8,7 +8,7 @@ #define __NETWORKMANAGER_UTILS_H__ #include "nm-core-utils.h" -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "nm-setting-ip-config.h" #include "nm-setting-ip6-config.h" #include "platform/nm-platform.h" diff --git a/src/core/devices/bluetooth/nm-bluez-manager.c b/src/core/devices/bluetooth/nm-bluez-manager.c index fe0350c7f9..3f10d80bba 100644 --- a/src/core/devices/bluetooth/nm-bluez-manager.c +++ b/src/core/devices/bluetooth/nm-bluez-manager.c @@ -12,8 +12,8 @@ #include <gmodule.h> #include <linux/if_ether.h> -#include "nm-glib-aux/nm-dbus-aux.h" -#include "nm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-dbus-aux.h" +#include "libnm-glib-aux/nm-c-list.h" #include "nm-dbus-manager.h" #include "devices/nm-device-factory.h" #include "devices/nm-device-bridge.h" diff --git a/src/core/devices/nm-device-wireguard.c b/src/core/devices/nm-device-wireguard.c index f9bd8ad8f8..4ad75d1db5 100644 --- a/src/core/devices/nm-device-wireguard.c +++ b/src/core/devices/nm-device-wireguard.c @@ -12,7 +12,7 @@ #include "nm-setting-wireguard.h" #include "libnm-core-intern/nm-core-internal.h" -#include "nm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" #include "nm-device-private.h" #include "platform/nm-platform.h" #include "platform/nmp-object.h" diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index b1418e24ac..3d4e58a958 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -24,8 +24,8 @@ #include <linux/if_infiniband.h> #include "nm-std-aux/unaligned.h" -#include "nm-glib-aux/nm-dedup-multi.h" -#include "nm-glib-aux/nm-random-utils.h" +#include "libnm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-random-utils.h" #include "libnm-systemd-shared/nm-sd-utils-shared.h" #include "libnm-base/nm-ethtool-base.h" diff --git a/src/core/devices/nm-lldp-listener.c b/src/core/devices/nm-lldp-listener.c index c60fb3ada6..9ce1781a9c 100644 --- a/src/core/devices/nm-lldp-listener.c +++ b/src/core/devices/nm-lldp-listener.c @@ -11,7 +11,7 @@ #include "nm-std-aux/unaligned.h" #include "platform/nm-platform.h" -#include "nm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-c-list.h" #include "nm-utils.h" #include "systemd/nm-sd.h" diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c index 0aa613bd75..b8d5311a7a 100644 --- a/src/core/devices/ovs/nm-ovsdb.c +++ b/src/core/devices/ovs/nm-ovsdb.c @@ -10,8 +10,8 @@ #include <gmodule.h> #include <gio/gunixsocketaddress.h> -#include "nm-glib-aux/nm-jansson.h" -#include "nm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-jansson.h" +#include "libnm-glib-aux/nm-str-buf.h" #include "nm-core-utils.h" #include "libnm-core-intern/nm-core-internal.h" #include "devices/nm-device.h" diff --git a/src/core/devices/team/nm-device-team.c b/src/core/devices/team/nm-device-team.c index e6e230eed1..5559bd1ef4 100644 --- a/src/core/devices/team/nm-device-team.c +++ b/src/core/devices/team/nm-device-team.c @@ -15,7 +15,7 @@ #include <teamdctl.h> #include <stdlib.h> -#include "nm-glib-aux/nm-jansson.h" +#include "libnm-glib-aux/nm-jansson.h" #include "NetworkManagerUtils.h" #include "devices/nm-device-private.h" #include "platform/nm-platform.h" diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c index dc5bc54a10..d9c4f797b9 100644 --- a/src/core/devices/wifi/nm-device-iwd.c +++ b/src/core/devices/wifi/nm-device-iwd.c @@ -15,7 +15,7 @@ #include "nm-config.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-dbus-manager.h" -#include "nm-glib-aux/nm-ref-string.h" +#include "libnm-glib-aux/nm-ref-string.h" #include "nm-iwd-manager.h" #include "libnm-core-aux-intern/nm-common-macros.h" #include "nm-setting-8021x.h" diff --git a/src/core/devices/wifi/nm-device-wifi-p2p.c b/src/core/devices/wifi/nm-device-wifi-p2p.c index 74eba64c39..0a0955deda 100644 --- a/src/core/devices/wifi/nm-device-wifi-p2p.c +++ b/src/core/devices/wifi/nm-device-wifi-p2p.c @@ -16,7 +16,7 @@ #include "devices/nm-device-private.h" #include "nm-act-request.h" #include "libnm-core-intern/nm-core-internal.h" -#include "nm-glib-aux/nm-ref-string.h" +#include "libnm-glib-aux/nm-ref-string.h" #include "nm-ip4-config.h" #include "nm-manager.h" #include "nm-manager.h" diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c index 3f55d9f15f..19d340a3af 100644 --- a/src/core/devices/wifi/nm-device-wifi.c +++ b/src/core/devices/wifi/nm-device-wifi.c @@ -12,8 +12,8 @@ #include <unistd.h> #include <linux/if_ether.h> -#include "nm-glib-aux/nm-ref-string.h" -#include "nm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-ref-string.h" +#include "libnm-glib-aux/nm-c-list.h" #include "nm-device-wifi-p2p.h" #include "nm-wifi-ap.h" #include "libnm-core-aux-intern/nm-common-macros.h" diff --git a/src/core/devices/wifi/nm-iwd-manager.c b/src/core/devices/wifi/nm-iwd-manager.c index b0b208ac52..71da3bdfd2 100644 --- a/src/core/devices/wifi/nm-iwd-manager.c +++ b/src/core/devices/wifi/nm-iwd-manager.c @@ -13,7 +13,7 @@ #include "nm-manager.h" #include "nm-device-iwd.h" #include "nm-wifi-utils.h" -#include "nm-glib-aux/nm-random-utils.h" +#include "libnm-glib-aux/nm-random-utils.h" #include "settings/nm-settings.h" #include "nm-std-aux/nm-dbus-compat.h" diff --git a/src/core/devices/wifi/nm-wifi-ap.c b/src/core/devices/wifi/nm-wifi-ap.c index 7d39e913d5..e07ea0afe4 100644 --- a/src/core/devices/wifi/nm-wifi-ap.c +++ b/src/core/devices/wifi/nm-wifi-ap.c @@ -15,7 +15,7 @@ #include "devices/nm-device.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-dbus-manager.h" -#include "nm-glib-aux/nm-ref-string.h" +#include "libnm-glib-aux/nm-ref-string.h" #include "nm-setting-wireless.h" #include "nm-utils.h" #include "nm-wifi-utils.h" diff --git a/src/core/devices/wifi/nm-wifi-p2p-peer.c b/src/core/devices/wifi/nm-wifi-p2p-peer.c index 40532bb142..6b04204cb1 100644 --- a/src/core/devices/wifi/nm-wifi-p2p-peer.c +++ b/src/core/devices/wifi/nm-wifi-p2p-peer.c @@ -14,7 +14,7 @@ #include "devices/nm-device.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-dbus-manager.h" -#include "nm-glib-aux/nm-ref-string.h" +#include "libnm-glib-aux/nm-ref-string.h" #include "nm-setting-wireless.h" #include "nm-utils.h" #include "nm-wifi-utils.h" diff --git a/src/core/dhcp/meson.build b/src/core/dhcp/meson.build index 6743568ea6..1788f864dc 100644 --- a/src/core/dhcp/meson.build +++ b/src/core/dhcp/meson.build @@ -3,6 +3,10 @@ executable( 'nm-dhcp-helper', 'nm-dhcp-helper.c', + include_directories: [ + src_inc, + top_inc, + ], dependencies: glib_nm_default_dep, link_args: ldflags_linker_script_binary, link_depends: linker_script_binary, diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c index c38c814ead..1b5aa1060e 100644 --- a/src/core/dhcp/nm-dhcp-client.c +++ b/src/core/dhcp/nm-dhcp-client.c @@ -14,8 +14,8 @@ #include <stdlib.h> #include <linux/rtnetlink.h> -#include "nm-glib-aux/nm-dedup-multi.h" -#include "nm-glib-aux/nm-random-utils.h" +#include "libnm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-random-utils.h" #include "NetworkManagerUtils.h" #include "nm-utils.h" diff --git a/src/core/dhcp/nm-dhcp-dhclient-utils.c b/src/core/dhcp/nm-dhcp-dhclient-utils.c index ad1e097f39..59682fb5ae 100644 --- a/src/core/dhcp/nm-dhcp-dhclient-utils.c +++ b/src/core/dhcp/nm-dhcp-dhclient-utils.c @@ -12,7 +12,7 @@ #include <net/if.h> #include <linux/if_ether.h> -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "nm-dhcp-utils.h" #include "nm-ip4-config.h" diff --git a/src/core/dhcp/nm-dhcp-dhclient.c b/src/core/dhcp/nm-dhcp-dhclient.c index c42a0ba53e..6384f3920c 100644 --- a/src/core/dhcp/nm-dhcp-dhclient.c +++ b/src/core/dhcp/nm-dhcp-dhclient.c @@ -21,7 +21,7 @@ #include <arpa/inet.h> #include <ctype.h> - #include "nm-glib-aux/nm-dedup-multi.h" + #include "libnm-glib-aux/nm-dedup-multi.h" #include "nm-utils.h" #include "nm-dhcp-dhclient-utils.h" diff --git a/src/core/dhcp/nm-dhcp-helper.c b/src/core/dhcp/nm-dhcp-helper.c index 0f98add127..c81d1a4815 100644 --- a/src/core/dhcp/nm-dhcp-helper.c +++ b/src/core/dhcp/nm-dhcp-helper.c @@ -3,7 +3,7 @@ * Copyright (C) 2007 - 2013 Red Hat, Inc. */ -#include "nm-glib-aux/nm-default-glib.h" +#include "libnm-glib-aux/nm-default-glib.h" #include <unistd.h> #include <stdlib.h> diff --git a/src/core/dhcp/nm-dhcp-manager.c b/src/core/dhcp/nm-dhcp-manager.c index aeaac63571..9be32e014c 100644 --- a/src/core/dhcp/nm-dhcp-manager.c +++ b/src/core/dhcp/nm-dhcp-manager.c @@ -16,7 +16,7 @@ #include <fcntl.h> #include <stdio.h> -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "libnm-systemd-shared/nm-sd-utils-shared.h" #include "nm-config.h" diff --git a/src/core/dhcp/nm-dhcp-nettools.c b/src/core/dhcp/nm-dhcp-nettools.c index 783d0be2cd..044223fb96 100644 --- a/src/core/dhcp/nm-dhcp-nettools.c +++ b/src/core/dhcp/nm-dhcp-nettools.c @@ -13,9 +13,9 @@ #include <ctype.h> #include <net/if_arp.h> -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "nm-std-aux/unaligned.h" -#include "nm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-str-buf.h" #include "nm-utils.h" #include "nm-config.h" diff --git a/src/core/dhcp/nm-dhcp-options.c b/src/core/dhcp/nm-dhcp-options.c index 3537cd147e..8f0d7408a9 100644 --- a/src/core/dhcp/nm-dhcp-options.c +++ b/src/core/dhcp/nm-dhcp-options.c @@ -7,7 +7,7 @@ #include "nm-dhcp-options.h" -#include "nm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-str-buf.h" /*****************************************************************************/ diff --git a/src/core/dhcp/nm-dhcp-systemd.c b/src/core/dhcp/nm-dhcp-systemd.c index b92a9073fa..2d0af1ca3a 100644 --- a/src/core/dhcp/nm-dhcp-systemd.c +++ b/src/core/dhcp/nm-dhcp-systemd.c @@ -13,7 +13,7 @@ #include <ctype.h> #include <net/if_arp.h> -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "nm-std-aux/unaligned.h" #include "nm-utils.h" diff --git a/src/core/dhcp/nm-dhcp-utils.c b/src/core/dhcp/nm-dhcp-utils.c index 26de6d6276..9b72998b6a 100644 --- a/src/core/dhcp/nm-dhcp-utils.c +++ b/src/core/dhcp/nm-dhcp-utils.c @@ -9,8 +9,8 @@ #include <arpa/inet.h> #include "nm-std-aux/unaligned.h" -#include "nm-glib-aux/nm-dedup-multi.h" -#include "nm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-str-buf.h" #include "libnm-systemd-shared/nm-sd-utils-shared.h" #include "nm-dhcp-utils.h" diff --git a/src/core/dhcp/tests/test-dhcp-dhclient.c b/src/core/dhcp/tests/test-dhcp-dhclient.c index 77626f6962..24ea9a448f 100644 --- a/src/core/dhcp/tests/test-dhcp-dhclient.c +++ b/src/core/dhcp/tests/test-dhcp-dhclient.c @@ -9,7 +9,7 @@ #include <arpa/inet.h> #include <linux/rtnetlink.h> -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "NetworkManagerUtils.h" #include "dhcp/nm-dhcp-dhclient-utils.h" diff --git a/src/core/dhcp/tests/test-dhcp-utils.c b/src/core/dhcp/tests/test-dhcp-utils.c index 9b54e2cd02..9fd224e339 100644 --- a/src/core/dhcp/tests/test-dhcp-utils.c +++ b/src/core/dhcp/tests/test-dhcp-utils.c @@ -9,7 +9,7 @@ #include <arpa/inet.h> #include <linux/rtnetlink.h> -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "nm-utils.h" #include "dhcp/nm-dhcp-utils.h" diff --git a/src/core/dns/nm-dns-dnsmasq.c b/src/core/dns/nm-dns-dnsmasq.c index 7ecd785f5f..6ef9f924ee 100644 --- a/src/core/dns/nm-dns-dnsmasq.c +++ b/src/core/dns/nm-dns-dnsmasq.c @@ -15,7 +15,7 @@ #include <sys/stat.h> #include <linux/if.h> -#include "nm-glib-aux/nm-dbus-aux.h" +#include "libnm-glib-aux/nm-dbus-aux.h" #include "libnm-core-intern/nm-core-internal.h" #include "platform/nm-platform.h" #include "nm-utils.h" diff --git a/src/core/dns/nm-dns-systemd-resolved.c b/src/core/dns/nm-dns-systemd-resolved.c index bbf212b135..4be77c8c92 100644 --- a/src/core/dns/nm-dns-systemd-resolved.c +++ b/src/core/dns/nm-dns-systemd-resolved.c @@ -16,8 +16,8 @@ #include <sys/stat.h> #include <linux/if.h> -#include "nm-glib-aux/nm-c-list.h" -#include "nm-glib-aux/nm-dbus-aux.h" +#include "libnm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-dbus-aux.h" #include "libnm-core-intern/nm-core-internal.h" #include "platform/nm-platform.h" #include "nm-utils.h" diff --git a/src/core/initrd/nm-initrd-generator.c b/src/core/initrd/nm-initrd-generator.c index f9756df29b..9d4e3656e2 100644 --- a/src/core/initrd/nm-initrd-generator.c +++ b/src/core/initrd/nm-initrd-generator.c @@ -8,7 +8,7 @@ #include "libnm-core-intern/nm-core-internal.h" #include "libnm-core-intern/nm-keyfile-internal.h" #include "nm-initrd-generator.h" -#include "nm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-io-utils.h" #include "nm-config.h" /*****************************************************************************/ diff --git a/src/core/meson.build b/src/core/meson.build index b466771eb0..2208209c58 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -20,6 +20,7 @@ core_default_dep = declare_dependency( include_directories: [ src_core_inc, shared_inc, + src_inc, top_inc, ], dependencies: [ diff --git a/src/core/ndisc/nm-lndp-ndisc.c b/src/core/ndisc/nm-lndp-ndisc.c index 00f666c12b..8b63f92c91 100644 --- a/src/core/ndisc/nm-lndp-ndisc.c +++ b/src/core/ndisc/nm-lndp-ndisc.c @@ -13,7 +13,7 @@ #include <stdarg.h> #include <ndp.h> -#include "nm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-str-buf.h" #include "libnm-systemd-shared/nm-sd-utils-shared.h" #include "nm-ndisc-private.h" #include "NetworkManagerUtils.h" diff --git a/src/core/nm-auth-manager.c b/src/core/nm-auth-manager.c index 4a7542ccfe..5c0e465aff 100644 --- a/src/core/nm-auth-manager.c +++ b/src/core/nm-auth-manager.c @@ -8,7 +8,7 @@ #include "nm-auth-manager.h" #include "c-list/src/c-list.h" -#include "nm-glib-aux/nm-dbus-aux.h" +#include "libnm-glib-aux/nm-dbus-aux.h" #include "nm-errors.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-dbus-manager.h" diff --git a/src/core/nm-auth-utils.c b/src/core/nm-auth-utils.c index 96c48cc336..006264dd28 100644 --- a/src/core/nm-auth-utils.c +++ b/src/core/nm-auth-utils.c @@ -7,7 +7,7 @@ #include "nm-auth-utils.h" -#include "nm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-c-list.h" #include "nm-setting-connection.h" #include "libnm-core-aux-intern/nm-auth-subject.h" #include "nm-auth-manager.h" diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c index 49dd1af64b..13c095d98b 100644 --- a/src/core/nm-core-utils.c +++ b/src/core/nm-core-utils.c @@ -23,10 +23,10 @@ #include <net/ethernet.h> #include "nm-std-aux/unaligned.h" -#include "nm-glib-aux/nm-random-utils.h" -#include "nm-glib-aux/nm-io-utils.h" -#include "nm-glib-aux/nm-secret-utils.h" -#include "nm-glib-aux/nm-time-utils.h" +#include "libnm-glib-aux/nm-random-utils.h" +#include "libnm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-time-utils.h" #include "nm-utils.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-setting-connection.h" diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h index 5dc4d43202..01d8322568 100644 --- a/src/core/nm-core-utils.h +++ b/src/core/nm-core-utils.h @@ -12,7 +12,7 @@ #include "nm-connection.h" -#include "nm-glib-aux/nm-time-utils.h" +#include "libnm-glib-aux/nm-time-utils.h" /*****************************************************************************/ diff --git a/src/core/nm-dbus-manager.c b/src/core/nm-dbus-manager.c index cae2d71344..d52023fb15 100644 --- a/src/core/nm-dbus-manager.c +++ b/src/core/nm-dbus-manager.c @@ -13,7 +13,7 @@ #include <sys/types.h> #include "c-list/src/c-list.h" -#include "nm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-c-list.h" #include "nm-dbus-interface.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-std-aux/nm-dbus-compat.h" diff --git a/src/core/nm-default-daemon.h b/src/core/nm-default-daemon.h index 65ca4b5428..57d4c6afd8 100644 --- a/src/core/nm-default-daemon.h +++ b/src/core/nm-default-daemon.h @@ -8,7 +8,7 @@ /*****************************************************************************/ -#include "nm-glib-aux/nm-default-glib-i18n-prog.h" +#include "libnm-glib-aux/nm-default-glib-i18n-prog.h" #undef NETWORKMANAGER_COMPILATION #define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_DAEMON @@ -19,7 +19,7 @@ #include "nm-core-types.h" #include "nm-types.h" -#include "nm-log-core/nm-logging.h" +#include "libnm-log-core/nm-logging.h" /*****************************************************************************/ diff --git a/src/core/nm-firewall-manager.c b/src/core/nm-firewall-manager.c index 8f476e511a..8094261f19 100644 --- a/src/core/nm-firewall-manager.c +++ b/src/core/nm-firewall-manager.c @@ -7,7 +7,7 @@ #include "nm-firewall-manager.h" -#include "nm-glib-aux/nm-dbus-aux.h" +#include "libnm-glib-aux/nm-dbus-aux.h" #include "c-list/src/c-list.h" #include "NetworkManagerUtils.h" diff --git a/src/core/nm-iface-helper.c b/src/core/nm-iface-helper.c index 8b3fb959f6..9dfee8d9e7 100644 --- a/src/core/nm-iface-helper.c +++ b/src/core/nm-iface-helper.c @@ -16,7 +16,7 @@ #include <signal.h> #include <linux/rtnetlink.h> -#include "nm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-c-list.h" #include "main-utils.h" #include "NetworkManagerUtils.h" diff --git a/src/core/nm-ip4-config.c b/src/core/nm-ip4-config.c index 23cb2b06b9..575d2876f3 100644 --- a/src/core/nm-ip4-config.c +++ b/src/core/nm-ip4-config.c @@ -12,7 +12,7 @@ #include <resolv.h> #include <linux/rtnetlink.h> -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "nm-utils.h" #include "platform/nmp-object.h" diff --git a/src/core/nm-ip4-config.h b/src/core/nm-ip4-config.h index a795e152ca..1196a724cd 100644 --- a/src/core/nm-ip4-config.h +++ b/src/core/nm-ip4-config.h @@ -10,7 +10,7 @@ #include "nm-setting-ip4-config.h" -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "platform/nmp-object.h" #include "nm-ip-config.h" diff --git a/src/core/nm-ip6-config.c b/src/core/nm-ip6-config.c index e085edb68e..5501e02f5f 100644 --- a/src/core/nm-ip6-config.c +++ b/src/core/nm-ip6-config.c @@ -13,7 +13,7 @@ #include <linux/rtnetlink.h> #include <linux/if.h> -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "nm-utils.h" #include "platform/nmp-object.h" diff --git a/src/core/nm-ip6-config.h b/src/core/nm-ip6-config.h index 398186da79..7cc00a83f3 100644 --- a/src/core/nm-ip6-config.h +++ b/src/core/nm-ip6-config.h @@ -10,7 +10,7 @@ #include "nm-setting-ip6-config.h" -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "platform/nmp-object.h" #include "nm-ip-config.h" diff --git a/src/core/nm-keep-alive.c b/src/core/nm-keep-alive.c index 197e30eaf4..6275544ca7 100644 --- a/src/core/nm-keep-alive.c +++ b/src/core/nm-keep-alive.c @@ -8,7 +8,7 @@ #include "nm-keep-alive.h" #include "settings/nm-settings-connection.h" -#include "nm-glib-aux/nm-dbus-aux.h" +#include "libnm-glib-aux/nm-dbus-aux.h" /*****************************************************************************/ diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c index 9ce20d2377..cce7364cd4 100644 --- a/src/core/nm-l3-config-data.c +++ b/src/core/nm-l3-config-data.c @@ -8,7 +8,7 @@ #include <linux/if_addr.h> #include <linux/rtnetlink.h> -#include "nm-glib-aux/nm-enum-utils.h" +#include "libnm-glib-aux/nm-enum-utils.h" #include "libnm-core-intern/nm-core-internal.h" #include "platform/nm-platform.h" #include "libnm-platform/nm-platform-utils.h" diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h index b3d9cd32ec..f89095f9b9 100644 --- a/src/core/nm-l3-config-data.h +++ b/src/core/nm-l3-config-data.h @@ -3,7 +3,7 @@ #ifndef __NM_L3_CONFIG_DATA_H__ #define __NM_L3_CONFIG_DATA_H__ -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "nm-setting-connection.h" #include "nm-setting-ip6-config.h" #include "platform/nm-platform.h" diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c index 8d2e8a8fe1..c9c040d668 100644 --- a/src/core/nm-manager.c +++ b/src/core/nm-manager.c @@ -16,7 +16,7 @@ #include <sys/sendfile.h> #include <limits.h> -#include "nm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-c-list.h" #include "libnm-core-aux-intern/nm-common-macros.h" #include "nm-dbus-manager.h" diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c index 8e3a984a25..17ba5d6453 100644 --- a/src/core/nm-netns.c +++ b/src/core/nm-netns.c @@ -7,8 +7,8 @@ #include "nm-netns.h" -#include "nm-glib-aux/nm-dedup-multi.h" -#include "nm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-c-list.h" #include "NetworkManagerUtils.h" #include "libnm-core-intern/nm-core-internal.h" diff --git a/src/core/nm-pacrunner-manager.c b/src/core/nm-pacrunner-manager.c index b58aef0649..492580a071 100644 --- a/src/core/nm-pacrunner-manager.c +++ b/src/core/nm-pacrunner-manager.c @@ -15,7 +15,7 @@ #include "nm-ip4-config.h" #include "nm-ip6-config.h" #include "c-list/src/c-list.h" -#include "nm-glib-aux/nm-dbus-aux.h" +#include "libnm-glib-aux/nm-dbus-aux.h" #define PACRUNNER_DBUS_SERVICE "org.pacrunner" #define PACRUNNER_DBUS_INTERFACE "org.pacrunner.Manager" diff --git a/src/core/nm-test-utils-core.h b/src/core/nm-test-utils-core.h index a765e1a3c2..f145904ef7 100644 --- a/src/core/nm-test-utils-core.h +++ b/src/core/nm-test-utils-core.h @@ -347,7 +347,7 @@ nmtst_platform_ip6_routes_equal_aptr(const NMPObject *const * a, #ifdef __NETWORKMANAGER_IP4_CONFIG_H__ - #include "nm-glib-aux/nm-dedup-multi.h" + #include "libnm-glib-aux/nm-dedup-multi.h" static inline NMIP4Config * nmtst_ip4_config_new(int ifindex) @@ -361,7 +361,7 @@ nmtst_ip4_config_new(int ifindex) #ifdef __NETWORKMANAGER_IP6_CONFIG_H__ - #include "nm-glib-aux/nm-dedup-multi.h" + #include "libnm-glib-aux/nm-dedup-multi.h" static inline NMIP6Config * nmtst_ip6_config_new(int ifindex) diff --git a/src/core/platform/nm-linux-platform.c b/src/core/platform/nm-linux-platform.c index ef5a54a16a..cae13d60de 100644 --- a/src/core/platform/nm-linux-platform.c +++ b/src/core/platform/nm-linux-platform.c @@ -38,8 +38,8 @@ #include "libnm-core-intern/nm-core-internal.h" #include "nm-setting-vlan.h" -#include "nm-glib-aux/nm-secret-utils.h" -#include "nm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-c-list.h" #include "libnm-platform/nm-netlink.h" #include "nm-core-utils.h" #include "nmp-object.h" @@ -49,7 +49,7 @@ #include "wifi/nm-wifi-utils.h" #include "wifi/nm-wifi-utils-wext.h" #include "wpan/nm-wpan-utils.h" -#include "nm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-io-utils.h" #include "libnm-udev-aux/nm-udev-utils.h" /*****************************************************************************/ diff --git a/src/core/platform/nm-platform.c b/src/core/platform/nm-platform.c index 68c5db149e..3a76a1e5a6 100644 --- a/src/core/platform/nm-platform.c +++ b/src/core/platform/nm-platform.c @@ -24,9 +24,9 @@ #include "nm-utils.h" #include "libnm-core-intern/nm-core-internal.h" -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "libnm-udev-aux/nm-udev-utils.h" -#include "nm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" #include "nm-core-utils.h" #include "libnm-platform/nm-platform-utils.h" diff --git a/src/core/platform/nmp-object.c b/src/core/platform/nmp-object.c index 174e016824..49a8f5987b 100644 --- a/src/core/platform/nmp-object.c +++ b/src/core/platform/nmp-object.c @@ -13,7 +13,7 @@ #include <libudev.h> #include "nm-utils.h" -#include "nm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" #include "nm-core-utils.h" #include "libnm-platform/nm-platform-utils.h" diff --git a/src/core/platform/nmp-object.h b/src/core/platform/nmp-object.h index 19f6bcd7ba..14cf1e3f09 100644 --- a/src/core/platform/nmp-object.h +++ b/src/core/platform/nmp-object.h @@ -8,8 +8,8 @@ #include <netinet/in.h> -#include "nm-glib-aux/nm-obj.h" -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-obj.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "nm-platform.h" struct udev_device; diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c index 79437a09c6..d1ca405f97 100644 --- a/src/core/platform/tests/test-link.c +++ b/src/core/platform/tests/test-link.c @@ -11,7 +11,7 @@ #include <sys/types.h> #include <linux/if_tun.h> -#include "nm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-io-utils.h" #include "libnm-base/nm-ethtool-base.h" #include "platform/nmp-object.h" #include "libnm-platform/nmp-netns.h" diff --git a/src/core/ppp/nm-pppd-plugin.c b/src/core/ppp/nm-pppd-plugin.c index c9016dac54..13c4e512df 100644 --- a/src/core/ppp/nm-pppd-plugin.c +++ b/src/core/ppp/nm-pppd-plugin.c @@ -20,7 +20,7 @@ #include <pppd/eui64.h> #include <pppd/ipv6cp.h> -#include "nm-glib-aux/nm-default-glib.h" +#include "libnm-glib-aux/nm-default-glib.h" #include "nm-dbus-interface.h" diff --git a/src/core/settings/nm-secret-agent.c b/src/core/settings/nm-secret-agent.c index b0d71886b4..5493984b7b 100644 --- a/src/core/settings/nm-secret-agent.c +++ b/src/core/settings/nm-secret-agent.c @@ -10,8 +10,8 @@ #include <sys/types.h> #include <pwd.h> -#include "nm-glib-aux/nm-c-list.h" -#include "nm-glib-aux/nm-dbus-aux.h" +#include "libnm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-dbus-aux.h" #include "nm-dbus-interface.h" #include "libnm-core-intern/nm-core-internal.h" #include "libnm-core-aux-intern/nm-auth-subject.h" diff --git a/src/core/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c index 0f0beef068..6f400f7e85 100644 --- a/src/core/settings/nm-settings-connection.c +++ b/src/core/settings/nm-settings-connection.c @@ -10,7 +10,7 @@ #include "c-list/src/c-list.h" -#include "nm-glib-aux/nm-keyfile-aux.h" +#include "libnm-glib-aux/nm-keyfile-aux.h" #include "libnm-core-aux-intern/nm-common-macros.h" #include "nm-config.h" #include "nm-config-data.h" diff --git a/src/core/settings/nm-settings.c b/src/core/settings/nm-settings.c index 858df29883..6d7ea6a65a 100644 --- a/src/core/settings/nm-settings.c +++ b/src/core/settings/nm-settings.c @@ -21,7 +21,7 @@ #endif #include "libnm-core-aux-intern/nm-common-macros.h" -#include "nm-glib-aux/nm-keyfile-aux.h" +#include "libnm-glib-aux/nm-keyfile-aux.h" #include "libnm-core-intern/nm-keyfile-internal.h" #include "nm-dbus-interface.h" #include "nm-connection.h" @@ -47,7 +47,7 @@ #include "libnm-core-intern/nm-core-internal.h" #include "nm-std-aux/c-list-util.h" -#include "nm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-c-list.h" #include "nm-dbus-object.h" #include "devices/nm-device-ethernet.h" #include "nm-settings-connection.h" diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c index 2cc1d373de..bfe2f40380 100644 --- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c @@ -14,8 +14,8 @@ #include <unistd.h> #include "nm-std-aux/c-list-util.h" -#include "nm-glib-aux/nm-c-list.h" -#include "nm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-io-utils.h" #include "nm-std-aux/nm-dbus-compat.h" #include "nm-utils.h" #include "libnm-core-intern/nm-core-internal.h" diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c index f02f33d3f2..cbe363de02 100644 --- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c @@ -18,7 +18,7 @@ #include <linux/rtnetlink.h> #include <linux/if_ether.h> -#include "nm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" #include "nm-connection.h" #include "nm-dbus-interface.h" #include "nm-setting-connection.h" diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c index 70147a9cd7..b513c98388 100644 --- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c @@ -15,8 +15,8 @@ #include <unistd.h> #include <stdio.h> -#include "nm-glib-aux/nm-enum-utils.h" -#include "nm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-enum-utils.h" +#include "libnm-glib-aux/nm-io-utils.h" #include "nm-manager.h" #include "nm-setting-connection.h" #include "nm-setting-wired.h" diff --git a/src/core/settings/plugins/ifcfg-rh/shvar.c b/src/core/settings/plugins/ifcfg-rh/shvar.c index dc26da759c..c6099dd173 100644 --- a/src/core/settings/plugins/ifcfg-rh/shvar.c +++ b/src/core/settings/plugins/ifcfg-rh/shvar.c @@ -16,8 +16,8 @@ #include "libnm-core-intern/nm-core-internal.h" #include "nm-core-utils.h" -#include "nm-glib-aux/nm-enum-utils.h" -#include "nm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-enum-utils.h" +#include "libnm-glib-aux/nm-io-utils.h" #include "c-list/src/c-list.h" #include "nms-ifcfg-rh-utils.h" diff --git a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c index c6ad8874d0..ad96d0c4be 100644 --- a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c +++ b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c @@ -17,7 +17,7 @@ #include <linux/if_ether.h> #include <linux/if_infiniband.h> -#include "nm-glib-aux/nm-json-aux.h" +#include "libnm-glib-aux/nm-json-aux.h" #include "nm-utils.h" #include "nm-setting-connection.h" #include "nm-setting-wired.h" diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c index 902270158a..27a219d8e8 100644 --- a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c +++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c @@ -14,8 +14,8 @@ #include <sys/time.h> #include "nm-std-aux/c-list-util.h" -#include "nm-glib-aux/nm-c-list.h" -#include "nm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-io-utils.h" #include "nm-connection.h" #include "nm-setting.h" diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-utils.c b/src/core/settings/plugins/keyfile/nms-keyfile-utils.c index 4b69dc79aa..c26b8676f1 100644 --- a/src/core/settings/plugins/keyfile/nms-keyfile-utils.c +++ b/src/core/settings/plugins/keyfile/nms-keyfile-utils.c @@ -10,7 +10,7 @@ #include <stdlib.h> #include <sys/stat.h> -#include "nm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-io-utils.h" #include "libnm-core-intern/nm-keyfile-internal.h" #include "nm-utils.h" #include "nm-setting-wired.h" diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.c b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c index 0dae91ae76..661a828058 100644 --- a/src/core/settings/plugins/keyfile/nms-keyfile-writer.c +++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c @@ -17,7 +17,7 @@ #include "nms-keyfile-utils.h" #include "nms-keyfile-reader.h" -#include "nm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-io-utils.h" /*****************************************************************************/ diff --git a/src/core/supplicant/nm-supplicant-config.c b/src/core/supplicant/nm-supplicant-config.c index 1f27ab80dd..4dc029aa3f 100644 --- a/src/core/supplicant/nm-supplicant-config.c +++ b/src/core/supplicant/nm-supplicant-config.c @@ -10,7 +10,7 @@ #include <stdlib.h> -#include "nm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-str-buf.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-supplicant-settings-verify.h" #include "nm-setting.h" diff --git a/src/core/supplicant/nm-supplicant-interface.c b/src/core/supplicant/nm-supplicant-interface.c index 176e4d2f61..8e284f1774 100644 --- a/src/core/supplicant/nm-supplicant-interface.c +++ b/src/core/supplicant/nm-supplicant-interface.c @@ -13,12 +13,12 @@ #include "NetworkManagerUtils.h" #include "libnm-core-intern/nm-core-internal.h" -#include "nm-glib-aux/nm-c-list.h" -#include "nm-glib-aux/nm-ref-string.h" +#include "libnm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-ref-string.h" +#include "libnm-glib-aux/nm-dbus-aux.h" #include "nm-std-aux/nm-dbus-compat.h" #include "nm-supplicant-config.h" #include "nm-supplicant-manager.h" -#include "shared/nm-glib-aux/nm-dbus-aux.h" #define DBUS_TIMEOUT_MSEC 20000 diff --git a/src/core/supplicant/nm-supplicant-manager.c b/src/core/supplicant/nm-supplicant-manager.c index d4806161e8..c7401ff07c 100644 --- a/src/core/supplicant/nm-supplicant-manager.c +++ b/src/core/supplicant/nm-supplicant-manager.c @@ -10,8 +10,8 @@ #include "libnm-core-intern/nm-core-internal.h" #include "nm-dbus-manager.h" -#include "nm-glib-aux/nm-dbus-aux.h" -#include "nm-glib-aux/nm-ref-string.h" +#include "libnm-glib-aux/nm-dbus-aux.h" +#include "libnm-glib-aux/nm-ref-string.h" #include "nm-supplicant-interface.h" #include "nm-supplicant-types.h" #include "platform/nm-platform.h" diff --git a/src/libnm-base/nm-ethtool-base.c b/src/libnm-base/nm-ethtool-base.c index 52ff28714a..03cbf56007 100644 --- a/src/libnm-base/nm-ethtool-base.c +++ b/src/libnm-base/nm-ethtool-base.c @@ -3,7 +3,7 @@ * Copyright (C) 2018 Red Hat, Inc. */ -#include "nm-glib-aux/nm-default-glib-i18n-lib.h" +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-ethtool-base.h" diff --git a/src/libnm-core-aux-extern/nm-libnm-core-aux.c b/src/libnm-core-aux-extern/nm-libnm-core-aux.c index 8f3bbef0b9..6c45ecf728 100644 --- a/src/libnm-core-aux-extern/nm-libnm-core-aux.c +++ b/src/libnm-core-aux-extern/nm-libnm-core-aux.c @@ -3,7 +3,7 @@ * Copyright (C) 2019 Red Hat, Inc. */ -#include "nm-glib-aux/nm-default-glib-i18n-lib.h" +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-libnm-core-aux.h" diff --git a/src/libnm-core-aux-intern/nm-auth-subject.c b/src/libnm-core-aux-intern/nm-auth-subject.c index 2862356052..536b4e474d 100644 --- a/src/libnm-core-aux-intern/nm-auth-subject.c +++ b/src/libnm-core-aux-intern/nm-auth-subject.c @@ -11,7 +11,7 @@ * makes requests, like process identifier and user UID. */ -#include "nm-glib-aux/nm-default-glib-i18n-lib.h" +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-auth-subject.h" diff --git a/src/libnm-core-aux-intern/nm-libnm-core-utils.c b/src/libnm-core-aux-intern/nm-libnm-core-utils.c index e3d7b7b183..87dc1e9ebd 100644 --- a/src/libnm-core-aux-intern/nm-libnm-core-utils.c +++ b/src/libnm-core-aux-intern/nm-libnm-core-utils.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "nm-glib-aux/nm-default-glib-i18n-lib.h" +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-libnm-core-utils.h" diff --git a/src/libnm-core-impl/nm-crypto-gnutls.c b/src/libnm-core-impl/nm-crypto-gnutls.c index 0f6da6175e..db4be8a721 100644 --- a/src/libnm-core-impl/nm-crypto-gnutls.c +++ b/src/libnm-core-impl/nm-crypto-gnutls.c @@ -4,7 +4,7 @@ * Copyright (C) 2007 - 2015 Red Hat, Inc. */ -#include "nm-glib-aux/nm-default-glib-i18n-lib.h" +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-crypto-impl.h" @@ -13,7 +13,7 @@ #include <gnutls/x509.h> #include <gnutls/pkcs12.h> -#include "nm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" #include "nm-errors.h" /*****************************************************************************/ diff --git a/src/libnm-core-impl/nm-crypto-nss.c b/src/libnm-core-impl/nm-crypto-nss.c index 24a53f5dfe..6f4322813a 100644 --- a/src/libnm-core-impl/nm-crypto-nss.c +++ b/src/libnm-core-impl/nm-crypto-nss.c @@ -4,7 +4,7 @@ * Copyright (C) 2007 - 2009 Red Hat, Inc. */ -#include "nm-glib-aux/nm-default-glib-i18n-lib.h" +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-crypto-impl.h" @@ -20,7 +20,7 @@ NM_PRAGMA_WARNING_DISABLE("-Wstrict-prototypes") #include <p12plcy.h> NM_PRAGMA_WARNING_REENABLE -#include "nm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" #include "nm-errors.h" /*****************************************************************************/ diff --git a/src/libnm-core-impl/nm-crypto.c b/src/libnm-core-impl/nm-crypto.c index 46888d45b8..d84a2b100d 100644 --- a/src/libnm-core-impl/nm-crypto.c +++ b/src/libnm-core-impl/nm-crypto.c @@ -12,8 +12,8 @@ #include <unistd.h> #include <stdlib.h> -#include "nm-glib-aux/nm-secret-utils.h" -#include "nm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-io-utils.h" #include "nm-crypto-impl.h" #include "nm-utils.h" diff --git a/src/libnm-core-impl/nm-default-libnm-core.h b/src/libnm-core-impl/nm-default-libnm-core.h index 6eb84b947a..0f27f82d52 100644 --- a/src/libnm-core-impl/nm-default-libnm-core.h +++ b/src/libnm-core-impl/nm-default-libnm-core.h @@ -8,7 +8,7 @@ /*****************************************************************************/ -#include "nm-glib-aux/nm-default-glib-i18n-lib.h" +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #undef NETWORKMANAGER_COMPILATION #define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_LIBNM_CORE diff --git a/src/libnm-core-impl/nm-keyfile-utils.c b/src/libnm-core-impl/nm-keyfile-utils.c index 6e1a255314..9c4a981ae5 100644 --- a/src/libnm-core-impl/nm-keyfile-utils.c +++ b/src/libnm-core-impl/nm-keyfile-utils.c @@ -9,7 +9,7 @@ #include <stdlib.h> -#include "nm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-str-buf.h" #include "nm-keyfile.h" #include "nm-setting-wired.h" diff --git a/src/libnm-core-impl/nm-keyfile.c b/src/libnm-core-impl/nm-keyfile.c index f15e004f61..1de18180a3 100644 --- a/src/libnm-core-impl/nm-keyfile.c +++ b/src/libnm-core-impl/nm-keyfile.c @@ -18,8 +18,8 @@ #include <linux/if_ether.h> #include <linux/if_infiniband.h> -#include "nm-glib-aux/nm-str-buf.h" -#include "nm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-secret-utils.h" #include "libnm-systemd-shared/nm-sd-utils-shared.h" #include "libnm-core-aux-intern/nm-common-macros.h" diff --git a/src/libnm-core-impl/nm-meta-setting-base-impl.c b/src/libnm-core-impl/nm-meta-setting-base-impl.c index 523c0d5db3..da85e5683b 100644 --- a/src/libnm-core-impl/nm-meta-setting-base-impl.c +++ b/src/libnm-core-impl/nm-meta-setting-base-impl.c @@ -3,7 +3,7 @@ * Copyright (C) 2017 - 2018 Red Hat, Inc. */ -#include "nm-glib-aux/nm-default-glib-i18n-lib.h" +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-meta-setting-base.h" diff --git a/src/libnm-core-impl/nm-setting-8021x.c b/src/libnm-core-impl/nm-setting-8021x.c index 96935b5f08..39afa3aaeb 100644 --- a/src/libnm-core-impl/nm-setting-8021x.c +++ b/src/libnm-core-impl/nm-setting-8021x.c @@ -8,7 +8,7 @@ #include "nm-setting-8021x.h" -#include "nm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" #include "nm-utils.h" #include "nm-crypto.h" #include "nm-utils-private.h" diff --git a/src/libnm-core-impl/nm-setting-bridge.c b/src/libnm-core-impl/nm-setting-bridge.c index 95ae5bfbcc..202a8791ed 100644 --- a/src/libnm-core-impl/nm-setting-bridge.c +++ b/src/libnm-core-impl/nm-setting-bridge.c @@ -11,7 +11,7 @@ #include <stdlib.h> #include <linux/if_ether.h> -#include "nm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-str-buf.h" #include "nm-connection-private.h" #include "nm-utils.h" #include "nm-utils-private.h" diff --git a/src/libnm-core-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c index d55ebbf78f..0c3d75d0bc 100644 --- a/src/libnm-core-impl/nm-setting-ip-config.c +++ b/src/libnm-core-impl/nm-setting-ip-config.c @@ -11,7 +11,7 @@ #include <arpa/inet.h> #include <linux/fib_rules.h> -#include "nm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-str-buf.h" #include "nm-setting-ip4-config.h" #include "nm-setting-ip6-config.h" #include "nm-utils.h" diff --git a/src/libnm-core-impl/nm-setting-macsec.c b/src/libnm-core-impl/nm-setting-macsec.c index e2a87ffc18..f9a3a7818c 100644 --- a/src/libnm-core-impl/nm-setting-macsec.c +++ b/src/libnm-core-impl/nm-setting-macsec.c @@ -9,7 +9,7 @@ #include <stdlib.h> -#include "nm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" #include "nm-utils.h" #include "libnm-core-intern/nm-core-types-internal.h" diff --git a/src/libnm-core-impl/nm-setting-vpn.c b/src/libnm-core-impl/nm-setting-vpn.c index 19aafd4f19..ce6e73f5b4 100644 --- a/src/libnm-core-impl/nm-setting-vpn.c +++ b/src/libnm-core-impl/nm-setting-vpn.c @@ -10,7 +10,7 @@ #include <stdlib.h> -#include "nm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" #include "nm-utils.h" #include "nm-utils-private.h" #include "nm-setting-private.h" diff --git a/src/libnm-core-impl/nm-setting-wireguard.c b/src/libnm-core-impl/nm-setting-wireguard.c index ea723a5b2e..644b36d698 100644 --- a/src/libnm-core-impl/nm-setting-wireguard.c +++ b/src/libnm-core-impl/nm-setting-wireguard.c @@ -10,7 +10,7 @@ #include "nm-setting-private.h" #include "nm-utils-private.h" #include "nm-connection-private.h" -#include "nm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" /*****************************************************************************/ diff --git a/src/libnm-core-impl/nm-setting-wireless-security.c b/src/libnm-core-impl/nm-setting-wireless-security.c index f5bbe185f3..6104aea575 100644 --- a/src/libnm-core-impl/nm-setting-wireless-security.c +++ b/src/libnm-core-impl/nm-setting-wireless-security.c @@ -13,7 +13,7 @@ #include "nm-utils-private.h" #include "nm-setting-private.h" #include "nm-setting-wireless.h" -#include "nm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" /** * SECTION:nm-setting-wireless-security diff --git a/src/libnm-core-impl/nm-team-utils.c b/src/libnm-core-impl/nm-team-utils.c index 9546ababfd..88983d0b64 100644 --- a/src/libnm-core-impl/nm-team-utils.c +++ b/src/libnm-core-impl/nm-team-utils.c @@ -11,7 +11,7 @@ #include "nm-errors.h" #include "nm-utils-private.h" -#include "nm-glib-aux/nm-json-aux.h" +#include "libnm-glib-aux/nm-json-aux.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-setting-team.h" #include "nm-setting-team-port.h" diff --git a/src/libnm-core-impl/nm-team-utils.h b/src/libnm-core-impl/nm-team-utils.h index 1488eb9da5..2039c16ec7 100644 --- a/src/libnm-core-impl/nm-team-utils.h +++ b/src/libnm-core-impl/nm-team-utils.h @@ -10,7 +10,7 @@ #error Cannot use this header. #endif -#include "nm-glib-aux/nm-value-type.h" +#include "libnm-glib-aux/nm-value-type.h" #include "libnm-core-intern/nm-core-internal.h" struct _NMSetting; diff --git a/src/libnm-core-impl/nm-utils.c b/src/libnm-core-impl/nm-utils.c index 7a1789caef..b493b06f50 100644 --- a/src/libnm-core-impl/nm-utils.c +++ b/src/libnm-core-impl/nm-utils.c @@ -18,11 +18,11 @@ #include <linux/pkt_sched.h> #include <linux/if_infiniband.h> -#include "nm-glib-aux/nm-json-aux.h" -#include "nm-glib-aux/nm-str-buf.h" -#include "nm-glib-aux/nm-enum-utils.h" -#include "nm-glib-aux/nm-time-utils.h" -#include "nm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-json-aux.h" +#include "libnm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-enum-utils.h" +#include "libnm-glib-aux/nm-time-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" #include "libnm-systemd-shared/nm-sd-utils-shared.h" #include "libnm-core-aux-intern/nm-common-macros.h" #include "nm-utils-private.h" diff --git a/src/libnm-core-impl/tests/test-general.c b/src/libnm-core-impl/tests/test-general.c index dc9945f8a3..5f43695eee 100644 --- a/src/libnm-core-impl/tests/test-general.c +++ b/src/libnm-core-impl/tests/test-general.c @@ -14,9 +14,9 @@ #include <linux/if_infiniband.h> #include "nm-std-aux/c-list-util.h" -#include "nm-glib-aux/nm-enum-utils.h" -#include "nm-glib-aux/nm-str-buf.h" -#include "nm-glib-aux/nm-json-aux.h" +#include "libnm-glib-aux/nm-enum-utils.h" +#include "libnm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-json-aux.h" #include "libnm-base/nm-base.h" #include "libnm-systemd-shared/nm-sd-utils-shared.h" @@ -58,7 +58,7 @@ #include "nm-setting-wpan.h" #include "nm-simple-connection.h" #include "libnm-core-intern/nm-keyfile-internal.h" -#include "nm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-dedup-multi.h" #include "libnm-base/nm-ethtool-base.h" #include "libnm-base/nm-ethtool-utils-base.h" diff --git a/src/libnm-core-impl/tests/test-keyfile.c b/src/libnm-core-impl/tests/test-keyfile.c index 755a40ae29..3b1ee09155 100644 --- a/src/libnm-core-impl/tests/test-keyfile.c +++ b/src/libnm-core-impl/tests/test-keyfile.c @@ -5,7 +5,7 @@ #include "libnm-core-impl/nm-default-libnm-core.h" -#include "nm-glib-aux/nm-json-aux.h" +#include "libnm-glib-aux/nm-json-aux.h" #include "libnm-core-intern/nm-keyfile-utils.h" #include "libnm-core-intern/nm-keyfile-internal.h" #include "nm-simple-connection.h" diff --git a/src/libnm-core-impl/tests/test-setting.c b/src/libnm-core-impl/tests/test-setting.c index 6188a2d4d2..0b622ec877 100644 --- a/src/libnm-core-impl/tests/test-setting.c +++ b/src/libnm-core-impl/tests/test-setting.c @@ -8,7 +8,7 @@ #include <linux/pkt_sched.h> #include <net/if.h> -#include "nm-glib-aux/nm-json-aux.h" +#include "libnm-glib-aux/nm-json-aux.h" #include "libnm-base/nm-ethtool-utils-base.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-utils.h" diff --git a/src/libnm-core-intern/nm-keyfile-utils.h b/src/libnm-core-intern/nm-keyfile-utils.h index 1555c11244..450cc81cd9 100644 --- a/src/libnm-core-intern/nm-keyfile-utils.h +++ b/src/libnm-core-intern/nm-keyfile-utils.h @@ -12,7 +12,7 @@ /*****************************************************************************/ -#include "nm-glib-aux/nm-shared-utils.h" +#include "libnm-glib-aux/nm-shared-utils.h" /*****************************************************************************/ diff --git a/src/libnm-glib-aux/meson.build b/src/libnm-glib-aux/meson.build new file mode 100644 index 0000000000..3a601dcb57 --- /dev/null +++ b/src/libnm-glib-aux/meson.build @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libnm_glib_aux = static_library( + 'nm-glib-aux', + sources: files( + 'nm-dbus-aux.c', + 'nm-dedup-multi.c', + 'nm-enum-utils.c', + 'nm-errno.c', + 'nm-hash-utils.c', + 'nm-io-utils.c', + 'nm-json-aux.c', + 'nm-keyfile-aux.c', + 'nm-logging-base.c', + 'nm-random-utils.c', + 'nm-ref-string.c', + 'nm-secret-utils.c', + 'nm-shared-utils.c', + 'nm-time-utils.c', + ), + include_directories: [ + shared_inc, + src_inc, + top_inc, + ], + dependencies: glib_nm_default_dep, + link_with: [ + libc_siphash, + libnm_std_aux, + ], +) + +libnm_glib_aux_dep = declare_dependency( + include_directories: [ + shared_inc, + src_inc, + top_inc, + ], + dependencies: [ + glib_nm_default_dep, + ], +) + +libnm_glib_aux_dep_link = declare_dependency( + dependencies: libnm_glib_aux_dep, + link_with: libnm_glib_aux, +) diff --git a/src/libnm-glib-aux/nm-c-list.h b/src/libnm-glib-aux/nm-c-list.h new file mode 100644 index 0000000000..6dd3ac720f --- /dev/null +++ b/src/libnm-glib-aux/nm-c-list.h @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#ifndef __NM_C_LIST_H__ +#define __NM_C_LIST_H__ + +#include "c-list/src/c-list.h" + +/*****************************************************************************/ + +#define nm_c_list_contains_entry(list, what, member) \ + ({ \ + typeof(what) _what = (what); \ + \ + _what &&c_list_contains(list, &_what->member); \ + }) + +/*****************************************************************************/ + +typedef struct { + CList lst; + void *data; +} NMCListElem; + +static inline NMCListElem * +nm_c_list_elem_new_stale(void *data) +{ + NMCListElem *elem; + + elem = g_slice_new(NMCListElem); + elem->data = data; + return elem; +} + +static inline gboolean +nm_c_list_elem_free_full(NMCListElem *elem, GDestroyNotify free_fcn) +{ + if (!elem) + return FALSE; + c_list_unlink_stale(&elem->lst); + if (free_fcn) + free_fcn(elem->data); + g_slice_free(NMCListElem, elem); + return TRUE; +} + +static inline gboolean +nm_c_list_elem_free(NMCListElem *elem) +{ + return nm_c_list_elem_free_full(elem, NULL); +} + +static inline void * +nm_c_list_elem_free_steal(NMCListElem *elem) +{ + gpointer data; + + if (!elem) + return NULL; + data = elem->data; + nm_c_list_elem_free_full(elem, NULL); + return data; +} + +static inline void +nm_c_list_elem_free_all(CList *head, GDestroyNotify free_fcn) +{ + NMCListElem *elem; + + while ((elem = c_list_first_entry(head, NMCListElem, lst))) + nm_c_list_elem_free_full(elem, free_fcn); +} + +#define nm_c_list_elem_find_first(head, arg, predicate) \ + ({ \ + CList *const _head = (head); \ + NMCListElem *_result = NULL; \ + NMCListElem *_elem; \ + \ + c_list_for_each_entry (_elem, _head, lst) { \ + void *const arg = _elem->data; \ + \ + if (predicate) { \ + _result = _elem; \ + break; \ + } \ + } \ + _result; \ + }) + +/** + * nm_c_list_elem_find_first_ptr: + * @head: the @CList head of a list containing #NMCListElem elements. + * Note that the head is not itself part of the list. + * @needle: the needle pointer. + * + * Iterates the list and returns the first #NMCListElem with the matching @needle, + * using pointer equality. + * + * Returns: the found list element or %NULL if not found. + */ +static inline NMCListElem * +nm_c_list_elem_find_first_ptr(CList *head, gconstpointer needle) +{ + return nm_c_list_elem_find_first(head, x, x == needle); +} + +/*****************************************************************************/ + +/** + * nm_c_list_move_before: + * @lst: the list element to which @elem will be prepended. + * @elem: the list element to move. + * + * This unlinks @elem from the current list and linkes it before + * @lst. This is like c_list_link_before(), except that @elem must + * be initialized and linked. Note that @elem may be linked in @lst + * or in another list. In both cases it gets moved. + * + * Returns: %TRUE if there were any changes. %FALSE if elem was already + * linked at the right place. + */ +static inline gboolean +nm_c_list_move_before(CList *lst, CList *elem) +{ + nm_assert(lst); + nm_assert(elem); + + if (lst != elem && lst->prev != elem) { + c_list_unlink_stale(elem); + c_list_link_before(lst, elem); + return TRUE; + } + return FALSE; +} +#define nm_c_list_move_tail(lst, elem) nm_c_list_move_before(lst, elem) + +/** + * nm_c_list_move_after: + * @lst: the list element to which @elem will be prepended. + * @elem: the list element to move. + * + * This unlinks @elem from the current list and linkes it after + * @lst. This is like c_list_link_after(), except that @elem must + * be initialized and linked. Note that @elem may be linked in @lst + * or in another list. In both cases it gets moved. + * + * Returns: %TRUE if there were any changes. %FALSE if elem was already + * linked at the right place. + */ +static inline gboolean +nm_c_list_move_after(CList *lst, CList *elem) +{ + nm_assert(lst); + nm_assert(elem); + + if (lst != elem && lst->next != elem) { + c_list_unlink_stale(elem); + c_list_link_after(lst, elem); + return TRUE; + } + return FALSE; +} +#define nm_c_list_move_front(lst, elem) nm_c_list_move_after(lst, elem) + +#define nm_c_list_free_all(lst, type, member, destroy_fcn) \ + G_STMT_START \ + { \ + CList *const _lst = (lst); \ + type * _elem; \ + \ + while ((_elem = c_list_first_entry(_lst, type, member))) { \ + destroy_fcn(_elem); \ + } \ + } \ + G_STMT_END + +#endif /* __NM_C_LIST_H__ */ diff --git a/src/libnm-glib-aux/nm-dbus-aux.c b/src/libnm-glib-aux/nm-dbus-aux.c new file mode 100644 index 0000000000..48864a0c41 --- /dev/null +++ b/src/libnm-glib-aux/nm-dbus-aux.c @@ -0,0 +1,292 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-dbus-aux.h" + +/*****************************************************************************/ + +static void +_nm_dbus_connection_call_get_name_owner_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError * error = NULL; + const char * owner = NULL; + gpointer orig_user_data; + NMDBusConnectionCallGetNameOwnerCb callback; + + nm_utils_user_data_unpack(user_data, &orig_user_data, &callback); + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); + if (ret) + g_variant_get(ret, "(&s)", &owner); + + callback(owner, error, orig_user_data); +} + +void +nm_dbus_connection_call_get_name_owner(GDBusConnection * dbus_connection, + const char * service_name, + int timeout_msec, + GCancellable * cancellable, + NMDBusConnectionCallGetNameOwnerCb callback, + gpointer user_data) +{ + nm_assert(callback); + + g_dbus_connection_call(dbus_connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetNameOwner", + g_variant_new("(s)", service_name), + G_VARIANT_TYPE("(s)"), + G_DBUS_CALL_FLAGS_NONE, + timeout_msec, + cancellable, + _nm_dbus_connection_call_get_name_owner_cb, + nm_utils_user_data_pack(user_data, callback)); +} + +/*****************************************************************************/ + +static void +_nm_dbus_connection_call_default_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError * error = NULL; + gpointer orig_user_data; + NMDBusConnectionCallDefaultCb callback; + + nm_utils_user_data_unpack(user_data, &orig_user_data, &callback); + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); + + nm_assert((!!ret) != (!!error)); + + callback(ret, error, orig_user_data); +} + +void +nm_dbus_connection_call_get_all(GDBusConnection * dbus_connection, + const char * bus_name, + const char * object_path, + const char * interface_name, + int timeout_msec, + GCancellable * cancellable, + NMDBusConnectionCallDefaultCb callback, + gpointer user_data) +{ + nm_assert(callback); + + g_dbus_connection_call(dbus_connection, + bus_name, + object_path, + DBUS_INTERFACE_PROPERTIES, + "GetAll", + g_variant_new("(s)", interface_name), + G_VARIANT_TYPE("(a{sv})"), + G_DBUS_CALL_FLAGS_NONE, + timeout_msec, + cancellable, + _nm_dbus_connection_call_default_cb, + nm_utils_user_data_pack(user_data, callback)); +} + +void +nm_dbus_connection_call_set(GDBusConnection * dbus_connection, + const char * bus_name, + const char * object_path, + const char * interface_name, + const char * property_name, + GVariant * value, + int timeout_msec, + GCancellable * cancellable, + NMDBusConnectionCallDefaultCb callback, + gpointer user_data) +{ + g_dbus_connection_call(dbus_connection, + bus_name, + object_path, + DBUS_INTERFACE_PROPERTIES, + "Set", + g_variant_new("(ssv)", interface_name, property_name, value), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + timeout_msec, + cancellable, + callback ? _nm_dbus_connection_call_default_cb : NULL, + callback ? nm_utils_user_data_pack(user_data, callback) : NULL); +} + +/*****************************************************************************/ + +static void +_nm_dbus_connection_call_get_managed_objects_cb(GObject * source, + GAsyncResult *res, + gpointer user_data) +{ + gs_unref_variant GVariant *ret = NULL; + gs_unref_variant GVariant *arg = NULL; + gs_free_error GError * error = NULL; + gpointer orig_user_data; + NMDBusConnectionCallDefaultCb callback; + + nm_utils_user_data_unpack(user_data, &orig_user_data, &callback); + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); + + nm_assert((!!ret) != (!!error)); + + if (ret) { + nm_assert(g_variant_is_of_type(ret, G_VARIANT_TYPE("(a{oa{sa{sv}}})"))); + arg = g_variant_get_child_value(ret, 0); + } + + callback(arg, error, orig_user_data); +} + +void +nm_dbus_connection_call_get_managed_objects(GDBusConnection * dbus_connection, + const char * bus_name, + const char * object_path, + GDBusCallFlags flags, + int timeout_msec, + GCancellable * cancellable, + NMDBusConnectionCallDefaultCb callback, + gpointer user_data) +{ + nm_assert(callback); + + g_dbus_connection_call(dbus_connection, + bus_name, + object_path, + DBUS_INTERFACE_OBJECT_MANAGER, + "GetManagedObjects", + NULL, + G_VARIANT_TYPE("(a{oa{sa{sv}}})"), + flags, + timeout_msec, + cancellable, + _nm_dbus_connection_call_get_managed_objects_cb, + nm_utils_user_data_pack(user_data, callback)); +} + +/*****************************************************************************/ + +static void +_call_finish_cb(GObject * source, + GAsyncResult *result, + gpointer user_data, + gboolean return_void, + gboolean strip_dbus_error) +{ + gs_unref_object GTask *task = user_data; + gs_unref_variant GVariant *ret = NULL; + GError * error = NULL; + + nm_assert(G_IS_DBUS_CONNECTION(source)); + nm_assert(G_IS_TASK(user_data)); + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (!ret) { + if (strip_dbus_error) + g_dbus_error_strip_remote_error(error); + g_task_return_error(task, error); + return; + } + + if (!return_void) + g_task_return_pointer(task, g_steal_pointer(&ret), (GDestroyNotify) g_variant_unref); + else { + nm_assert(g_variant_is_of_type(ret, G_VARIANT_TYPE("()"))); + g_task_return_boolean(task, TRUE); + } +} + +/** + * nm_dbus_connection_call_finish_void_cb: + * + * A default callback to pass as callback to g_dbus_connection_call(). + * + * - user_data must be a GTask, whose reference will be consumed by the + * callback. + * - the return GVariant must be a empty tuple "()". + * - the GTask is returned either with error or TRUE boolean. + */ +void +nm_dbus_connection_call_finish_void_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + _call_finish_cb(source, result, user_data, TRUE, FALSE); +} + +/** + * nm_dbus_connection_call_finish_void_strip_dbus_error_cb: + * + * Like nm_dbus_connection_call_finish_void_cb(). The difference + * is that on error this will first call g_dbus_error_strip_remote_error() on the error. + */ +void +nm_dbus_connection_call_finish_void_strip_dbus_error_cb(GObject * source, + GAsyncResult *result, + gpointer user_data) +{ + _call_finish_cb(source, result, user_data, TRUE, TRUE); +} + +/** + * nm_dbus_connection_call_finish_variant_cb: + * + * A default callback to pass as callback to g_dbus_connection_call(). + * + * - user_data must be a GTask, whose reference will be consumed by the + * callback. + * - the GTask is returned either with error or with a pointer containing the GVariant. + */ +void +nm_dbus_connection_call_finish_variant_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + _call_finish_cb(source, result, user_data, FALSE, FALSE); +} + +/** + * nm_dbus_connection_call_finish_variant_strip_dbus_error_cb: + * + * Like nm_dbus_connection_call_finish_variant_strip_dbus_error_cb(). The difference + * is that on error this will first call g_dbus_error_strip_remote_error() on the error. + */ +void +nm_dbus_connection_call_finish_variant_strip_dbus_error_cb(GObject * source, + GAsyncResult *result, + gpointer user_data) +{ + _call_finish_cb(source, result, user_data, FALSE, TRUE); +} + +/*****************************************************************************/ + +gboolean +_nm_dbus_error_is(GError *error, ...) +{ + gs_free char *dbus_error = NULL; + const char * name; + va_list ap; + + dbus_error = g_dbus_error_get_remote_error(error); + if (!dbus_error) + return FALSE; + + va_start(ap, error); + while ((name = va_arg(ap, const char *))) { + if (nm_streq(dbus_error, name)) { + va_end(ap); + return TRUE; + } + } + va_end(ap); + + return FALSE; +} diff --git a/src/libnm-glib-aux/nm-dbus-aux.h b/src/libnm-glib-aux/nm-dbus-aux.h new file mode 100644 index 0000000000..4e3ae22d82 --- /dev/null +++ b/src/libnm-glib-aux/nm-dbus-aux.h @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#ifndef __NM_DBUS_AUX_H__ +#define __NM_DBUS_AUX_H__ + +#include "nm-std-aux/nm-dbus-compat.h" + +/*****************************************************************************/ + +static inline gboolean +nm_clear_g_dbus_connection_signal(GDBusConnection *dbus_connection, guint *id) +{ + guint v; + + if (id && (v = *id)) { + *id = 0; + g_dbus_connection_signal_unsubscribe(dbus_connection, v); + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ + +typedef void (*NMDBusConnectionCallDefaultCb)(GVariant *result, GError *error, gpointer user_data); + +/*****************************************************************************/ + +static inline void +nm_dbus_connection_call_start_service_by_name(GDBusConnection * dbus_connection, + const char * name, + int timeout_msec, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_connection_call(dbus_connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "StartServiceByName", + g_variant_new("(su)", name, 0u), + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + timeout_msec, + cancellable, + callback, + user_data); +} + +/*****************************************************************************/ + +static inline guint +nm_dbus_connection_signal_subscribe_name_owner_changed(GDBusConnection * dbus_connection, + const char * service_name, + GDBusSignalCallback callback, + gpointer user_data, + GDestroyNotify user_data_free_func) + +{ + return g_dbus_connection_signal_subscribe(dbus_connection, + DBUS_SERVICE_DBUS, + DBUS_INTERFACE_DBUS, + "NameOwnerChanged", + DBUS_PATH_DBUS, + service_name, + G_DBUS_SIGNAL_FLAGS_NONE, + callback, + user_data, + user_data_free_func); +} + +typedef void (*NMDBusConnectionCallGetNameOwnerCb)(const char *name_owner, + GError * error, + gpointer user_data); + +void nm_dbus_connection_call_get_name_owner(GDBusConnection * dbus_connection, + const char * service_name, + int timeout_msec, + GCancellable * cancellable, + NMDBusConnectionCallGetNameOwnerCb callback, + gpointer user_data); + +static inline guint +nm_dbus_connection_signal_subscribe_properties_changed(GDBusConnection * dbus_connection, + const char * bus_name, + const char * object_path, + const char * interface_name, + GDBusSignalCallback callback, + gpointer user_data, + GDestroyNotify user_data_free_func) + +{ + nm_assert(bus_name); + + /* it seems that using a non-unique name causes problems that we get signals + * also from unrelated senders. Usually, you are anyway monitoring the name-owner, + * so you should have the unique name at hand. + * + * If not, investigate this, ensure that it works, and lift this restriction. */ + nm_assert(g_dbus_is_unique_name(bus_name)); + + return g_dbus_connection_signal_subscribe(dbus_connection, + bus_name, + DBUS_INTERFACE_PROPERTIES, + "PropertiesChanged", + object_path, + interface_name, + G_DBUS_SIGNAL_FLAGS_NONE, + callback, + user_data, + user_data_free_func); +} + +void nm_dbus_connection_call_get_all(GDBusConnection * dbus_connection, + const char * bus_name, + const char * object_path, + const char * interface_name, + int timeout_msec, + GCancellable * cancellable, + NMDBusConnectionCallDefaultCb callback, + gpointer user_data); + +void nm_dbus_connection_call_set(GDBusConnection * dbus_connection, + const char * bus_name, + const char * object_path, + const char * interface_name, + const char * property_name, + GVariant * value, + int timeout_msec, + GCancellable * cancellable, + NMDBusConnectionCallDefaultCb callback, + gpointer user_data); + +/*****************************************************************************/ + +static inline guint +nm_dbus_connection_signal_subscribe_object_manager(GDBusConnection * dbus_connection, + const char * service_name, + const char * object_path, + const char * signal_name, + GDBusSignalCallback callback, + gpointer user_data, + GDestroyNotify user_data_free_func) +{ + return g_dbus_connection_signal_subscribe(dbus_connection, + service_name, + DBUS_INTERFACE_OBJECT_MANAGER, + signal_name, + object_path, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + callback, + user_data, + user_data_free_func); +} + +void nm_dbus_connection_call_get_managed_objects(GDBusConnection * dbus_connection, + const char * bus_name, + const char * object_path, + GDBusCallFlags flags, + int timeout_msec, + GCancellable * cancellable, + NMDBusConnectionCallDefaultCb callback, + gpointer user_data); + +/*****************************************************************************/ + +void +nm_dbus_connection_call_finish_void_cb(GObject *source, GAsyncResult *result, gpointer user_data); + +void nm_dbus_connection_call_finish_void_strip_dbus_error_cb(GObject * source, + GAsyncResult *result, + gpointer user_data); + +void nm_dbus_connection_call_finish_variant_cb(GObject * source, + GAsyncResult *result, + gpointer user_data); + +void nm_dbus_connection_call_finish_variant_strip_dbus_error_cb(GObject * source, + GAsyncResult *result, + gpointer user_data); + +/*****************************************************************************/ + +gboolean _nm_dbus_error_is(GError *error, ...) G_GNUC_NULL_TERMINATED; + +#define nm_dbus_error_is(error, ...) \ + ({ \ + GError *const _error = (error); \ + \ + _error &&_nm_dbus_error_is(_error, __VA_ARGS__, NULL); \ + }) + +#define NM_DBUS_ERROR_NAME_UNKNOWN_METHOD "org.freedesktop.DBus.Error.UnknownMethod" + +/*****************************************************************************/ + +#endif /* __NM_DBUS_AUX_H__ */ diff --git a/src/libnm-glib-aux/nm-dedup-multi.c b/src/libnm-glib-aux/nm-dedup-multi.c new file mode 100644 index 0000000000..f77bb3cd7f --- /dev/null +++ b/src/libnm-glib-aux/nm-dedup-multi.c @@ -0,0 +1,1065 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-dedup-multi.h" + +#include "nm-hash-utils.h" +#include "nm-c-list.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); + + *idx_type = (NMDedupMultiIdxType){ + .klass = klass, + .lst_idx_head = 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 partition 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); + nm_assert(!entry_order || entry_order->head == entry->head); + nm_assert(!entry_order || c_list_contains(&entry->lst_entries, &entry_order->lst_entries)); + nm_assert(!entry_order || c_list_contains(&entry_order->lst_entries, &entry->lst_entries)); + + switch (mode) { + case NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE: + if (entry_order) { + if (nm_c_list_move_before((CList *) &entry_order->lst_entries, &entry->lst_entries)) + changed = TRUE; + } else { + if (nm_c_list_move_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 (nm_c_list_move_after((CList *) &entry_order->lst_entries, &entry->lst_entries)) + changed = TRUE; + } else { + if (nm_c_list_move_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 && !g_hash_table_add(self->idx_entries, head_entry)) + nm_assert_not_reached(); + + if (!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 peculiarity 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 peculiarity + * 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_stale(&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_stale(&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 (!g_hash_table_add(self->idx_objs, (gpointer) obj_new)) + nm_assert_not_reached(); + + ((NMDedupMultiObj *) obj_new)->_multi_idx = self; + return obj_new; +} + +void +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); + } + } +} + +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; + + if (order_after) { + if (nm_c_list_move_tail((CList *) &head_entry->lst_entries_head, + (CList *) &entry->lst_entries)) + return TRUE; + } else { + if (nm_c_list_move_front((CList *) &head_entry->lst_entries_head, + (CList *) &entry->lst_entries)) + return TRUE; + } + } else { + if (order_after) { + if (nm_c_list_move_after((CList *) &entry_order->lst_entries, + (CList *) &entry->lst_entries)) + return TRUE; + } else { + if (nm_c_list_move_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/src/libnm-glib-aux/nm-dedup-multi.h b/src/libnm-glib-aux/nm-dedup-multi.h new file mode 100644 index 0000000000..1c0761bf1e --- /dev/null +++ b/src/libnm-glib-aux/nm-dedup-multi.h @@ -0,0 +1,407 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NM_DEDUP_MULTI_H__ +#define __NM_DEDUP_MULTI_H__ + +#include "nm-obj.h" +#include "nm-std-aux/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 the 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/return 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; +} + +void 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 its 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 account 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 partition 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/src/libnm-glib-aux/nm-default-glib-i18n-lib.h b/src/libnm-glib-aux/nm-default-glib-i18n-lib.h new file mode 100644 index 0000000000..4b04da9e46 --- /dev/null +++ b/src/libnm-glib-aux/nm-default-glib-i18n-lib.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_DEFAULT_GLIB_I18N_LIB_H__ +#define __NM_DEFAULT_GLIB_I18N_LIB_H__ + +/*****************************************************************************/ + +#define _NETWORKMANAGER_COMPILATION_GLIB_I18N_LIB + +#include "libnm-glib-aux/nm-default-glib.h" + +#undef NETWORKMANAGER_COMPILATION +#define NETWORKMANAGER_COMPILATION \ + (NM_NETWORKMANAGER_COMPILATION_GLIB | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB) + +/*****************************************************************************/ + +#endif /* __NM_DEFAULT_GLIB_I18N_LIB_H__ */ diff --git a/src/libnm-glib-aux/nm-default-glib-i18n-prog.h b/src/libnm-glib-aux/nm-default-glib-i18n-prog.h new file mode 100644 index 0000000000..ebd31f966e --- /dev/null +++ b/src/libnm-glib-aux/nm-default-glib-i18n-prog.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_DEFAULT_GLIB_I18N_PROG_H__ +#define __NM_DEFAULT_GLIB_I18N_PROG_H__ + +/*****************************************************************************/ + +#define _NETWORKMANAGER_COMPILATION_GLIB_I18N_PROG + +#include "libnm-glib-aux/nm-default-glib.h" + +#undef NETWORKMANAGER_COMPILATION +#define NETWORKMANAGER_COMPILATION \ + (NM_NETWORKMANAGER_COMPILATION_GLIB | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG) + +/*****************************************************************************/ + +#endif /* __NM_DEFAULT_GLIB_I18N_PROG_H__ */ diff --git a/src/libnm-glib-aux/nm-default-glib.h b/src/libnm-glib-aux/nm-default-glib.h new file mode 100644 index 0000000000..001be170fb --- /dev/null +++ b/src/libnm-glib-aux/nm-default-glib.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_DEFAULT_GLIB_H__ +#define __NM_DEFAULT_GLIB_H__ + +/*****************************************************************************/ + +#include "nm-std-aux/nm-default-std.h" + +#undef NETWORKMANAGER_COMPILATION +#define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_WITH_GLIB + +/*****************************************************************************/ + +#include <glib.h> + +#if defined(_NETWORKMANAGER_COMPILATION_GLIB_I18N_PROG) + #if defined(_NETWORKMANAGER_COMPILATION_GLIB_I18N_LIB) + #error Cannot define _NETWORKMANAGER_COMPILATION_GLIB_I18N_LIB and _NETWORKMANAGER_COMPILATION_GLIB_I18N_PROG together + #endif + #undef _NETWORKMANAGER_COMPILATION_GLIB_I18N_PROG + #include <glib/gi18n.h> +#elif defined(_NETWORKMANAGER_COMPILATION_GLIB_I18N_LIB) + #undef _NETWORKMANAGER_COMPILATION_GLIB_I18N_LIB + #include <glib/gi18n-lib.h> +#endif + +/*****************************************************************************/ + +#if NM_MORE_ASSERTS == 0 + #ifndef G_DISABLE_CAST_CHECKS + /* Unless compiling with G_DISABLE_CAST_CHECKS, glib performs type checking + * during G_VARIANT_TYPE() via g_variant_type_checked_(). This is not necessary + * because commonly this cast is needed during something like + * + * g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}")); + * + * Note that in if the variant type would be invalid, the check still + * wouldn't make the buggy code magically work. Instead of passing a + * bogus type string (bad), it would pass %NULL to g_variant_builder_init() + * (also bad). + * + * Also, a function like g_variant_builder_init() already validates + * the input type via something like + * + * g_return_if_fail (g_variant_type_is_container (type)); + * + * So, by having G_VARIANT_TYPE() also validate the type, we validate + * twice, whereas the first validation is rather pointless because it + * doesn't prevent the function to be called with invalid arguments. + * + * Just patch G_VARIANT_TYPE() to perform no check. + */ + #undef G_VARIANT_TYPE + #define G_VARIANT_TYPE(type_string) ((const GVariantType *) (type_string)) + #endif +#endif + +/*****************************************************************************/ + +#include "nm-gassert-patch.h" + +#include "nm-std-aux/nm-std-aux.h" +#include "nm-std-aux/nm-std-utils.h" +#include "libnm-glib-aux/nm-macros-internal.h" +#include "libnm-glib-aux/nm-shared-utils.h" +#include "libnm-glib-aux/nm-errno.h" +#include "libnm-glib-aux/nm-hash-utils.h" + +/*****************************************************************************/ + +#endif /* __NM_DEFAULT_GLIB_H__ */ diff --git a/src/libnm-glib-aux/nm-enum-utils.c b/src/libnm-glib-aux/nm-enum-utils.c new file mode 100644 index 0000000000..f97cdfcbb5 --- /dev/null +++ b/src/libnm-glib-aux/nm-enum-utils.c @@ -0,0 +1,368 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-enum-utils.h" +#include "nm-str-buf.h" + +/*****************************************************************************/ + +#define IS_FLAGS_SEPARATOR(ch) (NM_IN_SET((ch), ' ', '\t', ',', '\n', '\r')) + +static void +_ASSERT_enum_values_info(GType type, const NMUtilsEnumValueInfo *value_infos) +{ +#if NM_MORE_ASSERTS > 5 + nm_auto_unref_gtypeclass GTypeClass *klass = NULL; + gs_unref_hashtable GHashTable *ht = NULL; + + klass = g_type_class_ref(type); + + g_assert(G_IS_ENUM_CLASS(klass) || G_IS_FLAGS_CLASS(klass)); + + if (!value_infos) + return; + + ht = g_hash_table_new(g_str_hash, g_str_equal); + + for (; value_infos->nick; value_infos++) { + g_assert(value_infos->nick[0]); + + /* duplicate nicks make no sense!! */ + g_assert(!g_hash_table_contains(ht, value_infos->nick)); + g_hash_table_add(ht, (gpointer) value_infos->nick); + + if (G_IS_ENUM_CLASS(klass)) { + GEnumValue *enum_value; + + enum_value = g_enum_get_value_by_nick(G_ENUM_CLASS(klass), value_infos->nick); + if (enum_value) { + /* we do allow specifying the same name via @value_infos and @type. + * That might make sense, if @type comes from a library where older versions + * of the library don't yet support the value. In this case, the caller can + * provide the nick via @value_infos, to support the older library version. + * And then, when actually running against a newer library version where + * @type knows the nick, we have this situation. + * + * Another reason for specifying a nick both in @value_infos and @type, + * is to specify an alias which is not used with highest preference. For + * example, if you add an alias "disabled" for "none" (both numerically + * equal), then the first alias in @value_infos will be preferred over + * the name from @type. So, to still use "none" as preferred name, you may + * explicitly specify the "none" alias in @value_infos before "disabled". + * + * However, what never is allowed, is to use a name (nick) to re-number + * the value. That is, if both @value_infos and @type contain a particular + * nick, their numeric values must agree as well. + * Allowing this, would be very confusing, because the name would have a different + * value from the regular GLib GEnum API. + */ + g_assert(enum_value->value == value_infos->value); + } + } else { + GFlagsValue *flags_value; + + flags_value = g_flags_get_value_by_nick(G_FLAGS_CLASS(klass), value_infos->nick); + if (flags_value) { + /* see ENUM case above. */ + g_assert(flags_value->value == (guint) value_infos->value); + } + } + } +#endif +} + +static gboolean +_is_hex_string(const char *str, gboolean allow_sign) +{ + if (allow_sign && str[0] == '-') + 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, gboolean allow_sign) +{ + if (allow_sign && str[0] == '-') + 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, TRUE) + && !_is_hex_string(str, TRUE); +} + +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, FALSE) + && !_is_hex_string(str, FALSE); +} + +char * +_nm_utils_enum_to_str_full(GType type, + int value, + const char * flags_separator, + const NMUtilsEnumValueInfo *value_infos) +{ + nm_auto_unref_gtypeclass GTypeClass *klass = NULL; + + _ASSERT_enum_values_info(type, value_infos); + + if (flags_separator + && (!flags_separator[0] || NM_STRCHAR_ANY(flags_separator, ch, !IS_FLAGS_SEPARATOR(ch)))) + g_return_val_if_reached(NULL); + + klass = g_type_class_ref(type); + + if (G_IS_ENUM_CLASS(klass)) { + GEnumValue *enum_value; + + for (; value_infos && value_infos->nick; value_infos++) { + if (value_infos->value == value) + return g_strdup(value_infos->nick); + } + + enum_value = g_enum_get_value(G_ENUM_CLASS(klass), value); + if (!enum_value || !_enum_is_valid_enum_nick(enum_value->value_nick)) + return g_strdup_printf("%d", value); + else + return g_strdup(enum_value->value_nick); + } else if (G_IS_FLAGS_CLASS(klass)) { + unsigned uvalue = (unsigned) value; + GFlagsValue *flags_value; + NMStrBuf strbuf; + + flags_separator = flags_separator ?: " "; + + nm_str_buf_init(&strbuf, 16, FALSE); + + for (; value_infos && value_infos->nick; value_infos++) { + nm_assert(_enum_is_valid_flags_nick(value_infos->nick)); + + if (uvalue == 0) { + if (value_infos->value != 0) + continue; + } else { + if (!NM_FLAGS_ALL(uvalue, (unsigned) value_infos->value)) + continue; + } + + if (strbuf.len) + nm_str_buf_append(&strbuf, flags_separator); + nm_str_buf_append(&strbuf, value_infos->nick); + uvalue &= ~((unsigned) value_infos->value); + if (uvalue == 0) { + /* we printed all flags. Done. */ + goto flags_done; + } + } + + do { + flags_value = g_flags_get_first_value(G_FLAGS_CLASS(klass), uvalue); + if (strbuf.len) + nm_str_buf_append(&strbuf, flags_separator); + if (!flags_value || !_enum_is_valid_flags_nick(flags_value->value_nick)) { + if (uvalue) + nm_str_buf_append_printf(&strbuf, "0x%x", uvalue); + break; + } + nm_str_buf_append(&strbuf, flags_value->value_nick); + uvalue &= ~flags_value->value; + } while (uvalue); + +flags_done: + return nm_str_buf_finalize(&strbuf, NULL); + } + + g_return_val_if_reached(NULL); +} + +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) +{ + nm_auto_unref_gtypeclass GTypeClass *klass = NULL; + 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); + + _ASSERT_enum_values_info(type, value_infos); + + s = nm_strdup_maybe_a(300, nm_str_skip_leading_spaces(str), &str_clone); + g_strchomp(s); + + klass = g_type_class_ref(type); + + if (G_IS_ENUM_CLASS(klass)) { + GEnumValue *enum_value; + + G_STATIC_ASSERT(G_MAXINT < G_MAXINT64); + G_STATIC_ASSERT(G_MININT > G_MININT64); + + if (s[0]) { + if (_is_hex_string(s, TRUE)) { + if (s[0] == '-') { + v64 = _nm_utils_ascii_str_to_int64(&s[3], + 16, + -((gint64) G_MAXINT), + -((gint64) G_MININT), + G_MAXINT64); + if (v64 != G_MAXINT64) { + value = (int) (-v64); + ret = TRUE; + } + } else { + v64 = _nm_utils_ascii_str_to_int64(&s[2], 16, G_MININT, G_MAXINT, G_MAXINT64); + if (v64 != G_MAXINT64) { + value = (int) v64; + ret = TRUE; + } + } + } else if (_is_dec_string(s, TRUE)) { + v64 = _nm_utils_ascii_str_to_int64(s, 10, G_MININT, G_MAXINT, G_MAXINT64); + if (v64 != G_MAXINT64) { + value = (int) v64; + ret = TRUE; + } + } else if ((nick = _find_value_info(value_infos, s))) { + value = nick->value; + ret = TRUE; + } else if ((enum_value = g_enum_get_value_by_nick(G_ENUM_CLASS(klass), s))) { + value = enum_value->value; + ret = TRUE; + } + } + } else if (G_IS_FLAGS_CLASS(klass)) { + 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, FALSE)) { + 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, FALSE)) { + v64 = _nm_utils_ascii_str_to_int64(s, 10, 0, G_MAXUINT, -1); + if (v64 == -1) { + ret = FALSE; + break; + } + uvalue |= (unsigned) v64; + } else if ((nick = _find_value_info(value_infos, s))) + uvalue |= (unsigned) nick->value; + else if ((flags_value = g_flags_get_value_by_nick(G_FLAGS_CLASS(klass), s))) + uvalue |= flags_value->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); + return ret; +} + +const char ** +_nm_utils_enum_get_values(GType type, int from, int to) +{ + GTypeClass *klass; + GPtrArray * array; + int i; + char sbuf[64]; + + klass = g_type_class_ref(type); + array = g_ptr_array_new(); + + if (G_IS_ENUM_CLASS(klass)) { + GEnumClass *enum_class = G_ENUM_CLASS(klass); + 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(klass)) { + GFlagsClass *flags_class = G_FLAGS_CLASS(klass); + 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(klass); + g_ptr_array_free(array, TRUE); + g_return_val_if_reached(NULL); + } + + g_type_class_unref(klass); + g_ptr_array_add(array, NULL); + + return (const char **) g_ptr_array_free(array, FALSE); +} diff --git a/src/libnm-glib-aux/nm-enum-utils.h b/src/libnm-glib-aux/nm-enum-utils.h new file mode 100644 index 0000000000..89be54e77f --- /dev/null +++ b/src/libnm-glib-aux/nm-enum-utils.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 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, + const NMUtilsEnumValueInfo *value_infos); +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, int from, int to); + +/*****************************************************************************/ + +#endif /* __NM_ENUM_UTILS_H__ */ diff --git a/src/libnm-glib-aux/nm-errno.c b/src/libnm-glib-aux/nm-errno.c new file mode 100644 index 0000000000..283173e328 --- /dev/null +++ b/src/libnm-glib-aux/nm-errno.c @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-errno.h" + +#include <pthread.h> + +/*****************************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE( + _geterror, +#if 0 + enum _NMErrno, +#else + int, +#endif + NM_UTILS_LOOKUP_DEFAULT(NULL), + + NM_UTILS_LOOKUP_STR_ITEM(NME_ERRNO_SUCCESS, "NME_ERRNO_SUCCESS"), + NM_UTILS_LOOKUP_STR_ITEM(NME_ERRNO_OUT_OF_RANGE, "NME_ERRNO_OUT_OF_RANGE"), + + NM_UTILS_LOOKUP_STR_ITEM(NME_UNSPEC, "NME_UNSPEC"), + NM_UTILS_LOOKUP_STR_ITEM(NME_BUG, "NME_BUG"), + NM_UTILS_LOOKUP_STR_ITEM(NME_NATIVE_ERRNO, "NME_NATIVE_ERRNO"), + + NM_UTILS_LOOKUP_STR_ITEM(NME_NL_ATTRSIZE, "NME_NL_ATTRSIZE"), + NM_UTILS_LOOKUP_STR_ITEM(NME_NL_BAD_SOCK, "NME_NL_BAD_SOCK"), + NM_UTILS_LOOKUP_STR_ITEM(NME_NL_DUMP_INTR, "NME_NL_DUMP_INTR"), + NM_UTILS_LOOKUP_STR_ITEM(NME_NL_MSG_OVERFLOW, "NME_NL_MSG_OVERFLOW"), + NM_UTILS_LOOKUP_STR_ITEM(NME_NL_MSG_TOOSHORT, "NME_NL_MSG_TOOSHORT"), + NM_UTILS_LOOKUP_STR_ITEM(NME_NL_MSG_TRUNC, "NME_NL_MSG_TRUNC"), + NM_UTILS_LOOKUP_STR_ITEM(NME_NL_SEQ_MISMATCH, "NME_NL_SEQ_MISMATCH"), + NM_UTILS_LOOKUP_STR_ITEM(NME_NL_NOADDR, "NME_NL_NOADDR"), + + NM_UTILS_LOOKUP_STR_ITEM(NME_PL_NOT_FOUND, "not-found"), + NM_UTILS_LOOKUP_STR_ITEM(NME_PL_EXISTS, "exists"), + NM_UTILS_LOOKUP_STR_ITEM(NME_PL_WRONG_TYPE, "wrong-type"), + NM_UTILS_LOOKUP_STR_ITEM(NME_PL_NOT_SLAVE, "not-slave"), + NM_UTILS_LOOKUP_STR_ITEM(NME_PL_NO_FIRMWARE, "no-firmware"), + NM_UTILS_LOOKUP_STR_ITEM(NME_PL_OPNOTSUPP, "not-supported"), + NM_UTILS_LOOKUP_STR_ITEM(NME_PL_NETLINK, "netlink"), + NM_UTILS_LOOKUP_STR_ITEM(NME_PL_CANT_SET_MTU, "cant-set-mtu"), + + NM_UTILS_LOOKUP_ITEM_IGNORE(_NM_ERRNO_MININT), + NM_UTILS_LOOKUP_ITEM_IGNORE(_NM_ERRNO_RESERVED_LAST_PLUS_1), ); + +/** + * nm_strerror(): + * @nmerr: the NetworkManager specific errno to be converted + * to string. + * + * NetworkManager specific error numbers reserve a range in "errno.h" with + * our own defines. For numbers that don't fall into this range, the numbers + * are identical to the common error numbers. + * + * Identical to strerror(), g_strerror(), nm_strerror_native() for error numbers + * that are not in the reserved range of NetworkManager specific errors. + * + * Returns: (transfer none): the string representation of the error number. + */ +const char * +nm_strerror(int nmerr) +{ + const char *s; + + nmerr = nm_errno(nmerr); + + if (nmerr >= _NM_ERRNO_RESERVED_FIRST) { + s = _geterror(nmerr); + if (s) + return s; + } + return nm_strerror_native(nmerr); +} + +/*****************************************************************************/ + +/** + * nm_strerror_native_r: + * @errsv: the errno to convert to string. + * @buf: the output buffer where to write the string to. + * @buf_size: the length of buffer. + * + * This is like strerror_r(), with one difference: depending on the + * locale, the returned string is guaranteed to be valid UTF-8. + * Also, there is some confusion as to whether to use glibc's + * strerror_r() or the POXIX/XSI variant. This is abstracted + * by the function. + * + * Note that the returned buffer may also be a statically allocated + * buffer, and not the input buffer @buf. Consequently, the returned + * string may be longer than @buf_size. + * + * Returns: (transfer none): a NUL terminated error message. This is either a static + * string (that is never freed), or the provided @buf argument. + */ +const char * +nm_strerror_native_r(int errsv, char *buf, gsize buf_size) +{ + char *buf2; + + nm_assert(buf); + nm_assert(buf_size > 0); + +#if (!defined(__GLIBC__) && !defined(__UCLIBC__)) || ((_POSIX_C_SOURCE >= 200112L) && !_GNU_SOURCE) + /* XSI-compliant */ + { + int errno_saved = errno; + + if (strerror_r(errsv, buf, buf_size) != 0) { + g_snprintf(buf, buf_size, "Unspecified errno %d", errsv); + errno = errno_saved; + } + buf2 = buf; + } +#else + /* GNU-specific */ + buf2 = strerror_r(errsv, buf, buf_size); +#endif + + /* like g_strerror(), ensure that the error message is UTF-8. */ + if (!g_get_charset(NULL) && !g_utf8_validate(buf2, -1, NULL)) { + gs_free char *msg = NULL; + + msg = g_locale_to_utf8(buf2, -1, NULL, NULL, NULL); + if (msg) { + g_strlcpy(buf, msg, buf_size); + buf2 = buf; + } + } + + return buf2; +} + +/** + * nm_strerror_native: + * @errsv: the errno integer from <errno.h> + * + * Like strerror(), but strerror() is not thread-safe and not guaranteed + * to be UTF-8. + * + * g_strerror() is a thread-safe variant of strerror(), however it caches + * all returned strings in a dictionary. That means, using this on untrusted + * error numbers can result in this cache to grow without limits. + * + * Instead, return a tread-local buffer. This way, it's thread-safe. + * + * There is a downside to this: subsequent calls of nm_strerror_native() + * overwrite the error message. + * + * Returns: (transfer none): the text representation of the error number. + */ +const char * +nm_strerror_native(int errsv) +{ + static _nm_thread_local char *buf_static = NULL; + char * buf; + + buf = buf_static; + if (G_UNLIKELY(!buf)) { + int errno_saved = errno; + pthread_key_t key; + + buf = g_malloc(NM_STRERROR_BUFSIZE); + buf_static = buf; + + if (pthread_key_create(&key, g_free) != 0 || pthread_setspecific(key, buf) != 0) { + /* Failure. We will leak the buffer when the thread exits. + * + * Nothing we can do about it really. For Debug builds we fail with an assertion. */ + nm_assert_not_reached(); + } + errno = errno_saved; + } + + return nm_strerror_native_r(errsv, buf, NM_STRERROR_BUFSIZE); +} diff --git a/src/libnm-glib-aux/nm-errno.h b/src/libnm-glib-aux/nm-errno.h new file mode 100644 index 0000000000..62c8379f83 --- /dev/null +++ b/src/libnm-glib-aux/nm-errno.h @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_ERRNO_H__ +#define __NM_ERRNO_H__ + +#include <errno.h> + +/*****************************************************************************/ + +enum _NMErrno { + _NM_ERRNO_MININT = G_MININT, + _NM_ERRNO_MAXINT = G_MAXINT, + _NM_ERRNO_RESERVED_FIRST = 100000, + + /* when we cannot represent a number as positive number, we resort to this + * number. Basically, the values G_MININT, -NME_ERRNO_SUCCESS, NME_ERRNO_SUCCESS + * and G_MAXINT all map to the same value. */ + NME_ERRNO_OUT_OF_RANGE = G_MAXINT, + + /* Indicate that the original errno was zero. Zero denotes *no error*, but we know something + * went wrong and we want to report some error. This is a placeholder to mean, something + * was wrong, but errno was zero. */ + NME_ERRNO_SUCCESS = G_MAXINT - 1, + + /* an unspecified error. */ + NME_UNSPEC = _NM_ERRNO_RESERVED_FIRST, + + /* A bug, for example when an assertion failed. + * Should never happen. */ + NME_BUG, + + /* a native error number (from <errno.h>) cannot be mapped as + * an nm-error, because it is in the range [_NM_ERRNO_RESERVED_FIRST, + * _NM_ERRNO_RESERVED_LAST]. */ + NME_NATIVE_ERRNO, + + /* netlink errors. */ + NME_NL_SEQ_MISMATCH, + NME_NL_MSG_TRUNC, + NME_NL_MSG_TOOSHORT, + NME_NL_DUMP_INTR, + NME_NL_ATTRSIZE, + NME_NL_BAD_SOCK, + NME_NL_NOADDR, + NME_NL_MSG_OVERFLOW, + + /* platform errors. */ + NME_PL_NOT_FOUND, + NME_PL_EXISTS, + NME_PL_WRONG_TYPE, + NME_PL_NOT_SLAVE, + NME_PL_NO_FIRMWARE, + NME_PL_OPNOTSUPP, + NME_PL_NETLINK, + NME_PL_CANT_SET_MTU, + + _NM_ERRNO_RESERVED_LAST_PLUS_1, + _NM_ERRNO_RESERVED_LAST = _NM_ERRNO_RESERVED_LAST_PLUS_1 - 1, +}; + +/*****************************************************************************/ + +/* When we receive an errno from a system function, we can safely assume + * that the error number is not negative. We rely on that, and possibly just + * "return -errsv;" to signal an error. We also rely on that, because libc + * is our trusted base: meaning, if it cannot even succeed at setting errno + * according to specification, all bets are off. + * + * This macro returns the input argument, and asserts that the error variable + * is positive. + * + * In a sense, the macro is related to nm_errno_native() function, but the difference + * is that this macro asserts that @errsv is positive, while nm_errno_native() coerces + * negative values to be non-negative. */ +#define NM_ERRNO_NATIVE(errsv) \ + ({ \ + const int _errsv_x = (errsv); \ + \ + nm_assert(_errsv_x > 0); \ + _errsv_x; \ + }) + +/* Normalize native errno. + * + * Our API may return native error codes (<errno.h>) as negative values. This function + * takes such an errno, and normalizes it to their positive value. + * + * The special values G_MININT and zero are coerced to NME_ERRNO_OUT_OF_RANGE and NME_ERRNO_SUCCESS + * respectively. + * Other values are coerced to their inverse. + * Other positive values are returned unchanged. + * + * Basically, this normalizes errsv to be positive (taking care of two pathological cases). + */ +static inline int +nm_errno_native(int errsv) +{ + switch (errsv) { + case 0: + return NME_ERRNO_SUCCESS; + case G_MININT: + return NME_ERRNO_OUT_OF_RANGE; + default: + return errsv >= 0 ? errsv : -errsv; + } +} + +/* Normalizes an nm-error to be positive. + * + * Various API returns negative error codes, and this function converts the negative + * value to its positive. + * + * Note that @nmerr is on the domain of NetworkManager specific error numbers, + * which is not the same as the native error numbers (errsv from <errno.h>). But + * as far as normalizing goes, nm_errno() does exactly the same remapping as + * nm_errno_native(). */ +static inline int +nm_errno(int nmerr) +{ + return nm_errno_native(nmerr); +} + +/* this maps a native errno to a (always non-negative) nm-error number. + * + * Note that nm-error numbers are embedded into the range of regular + * errno. The only difference is, that nm-error numbers reserve a + * range (_NM_ERRNO_RESERVED_FIRST, _NM_ERRNO_RESERVED_LAST) for their + * own purpose. + * + * That means, converting an errno to nm-error number means in + * most cases just returning itself. + * Only pathological cases need special handling: + * + * - 0 is mapped to NME_ERRNO_SUCCESS; + * - G_MININT is mapped to NME_ERRNO_OUT_OF_RANGE; + * - values in the range of (+/-) [_NM_ERRNO_RESERVED_FIRST, _NM_ERRNO_RESERVED_LAST] + * are mapped to NME_NATIVE_ERRNO + * - all other values are their (positive) absolute value. + */ +static inline int +nm_errno_from_native(int errsv) +{ + switch (errsv) { + case 0: + return NME_ERRNO_SUCCESS; + case G_MININT: + return NME_ERRNO_OUT_OF_RANGE; + default: + if (errsv < 0) + errsv = -errsv; + return G_UNLIKELY(errsv >= _NM_ERRNO_RESERVED_FIRST && errsv <= _NM_ERRNO_RESERVED_LAST) + ? NME_NATIVE_ERRNO + : errsv; + } +} + +const char *nm_strerror(int nmerr); + +/*****************************************************************************/ + +#define NM_STRERROR_BUFSIZE 1024 + +const char *nm_strerror_native_r(int errsv, char *buf, gsize buf_size); +const char *nm_strerror_native(int errsv); + +/*****************************************************************************/ + +#endif /* __NM_ERRNO_H__ */ diff --git a/src/libnm-glib-aux/nm-gassert-patch.h b/src/libnm-glib-aux/nm-gassert-patch.h new file mode 100644 index 0000000000..bac8697c0e --- /dev/null +++ b/src/libnm-glib-aux/nm-gassert-patch.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_GASSERT_PATCH_H__ +#define __NM_GASSERT_PATCH_H__ + +/*****************************************************************************/ + +#if NM_MORE_ASSERTS == 0 + +/* glib assertions (g_return_*(), g_assert*()) contain a textual representation + * of the checked statement. This part of the assertion blows up the size of the + * binary. Unless we compile a debug-build with NM_MORE_ASSERTS, drop these + * parts. Note that the failed assertion still prints the file and line where the + * assertion fails. That shall suffice. */ + +static inline void +_nm_g_return_if_fail_warning(const char *log_domain, const char *file, int line) +{ + char file_buf[256 + 15]; + + g_snprintf(file_buf, sizeof(file_buf), "((%s:%d))", file, line); + g_return_if_fail_warning(log_domain, file_buf, "<dropped>"); +} + + #define g_return_if_fail_warning(log_domain, pretty_function, expression) \ + _nm_g_return_if_fail_warning(log_domain, __FILE__, __LINE__) + + #define g_assertion_message_expr(domain, file, line, func, expr) \ + g_assertion_message_expr(domain, file, line, "<unknown-fcn>", (expr) ? "<dropped>" : NULL) + + #undef g_return_val_if_reached + #define g_return_val_if_reached(val) \ + G_STMT_START \ + { \ + g_log(G_LOG_DOMAIN, \ + G_LOG_LEVEL_CRITICAL, \ + "file %s: line %d (%s): should not be reached", \ + __FILE__, \ + __LINE__, \ + "<dropped>"); \ + return (val); \ + } \ + G_STMT_END + + #undef g_return_if_reached + #define g_return_if_reached() \ + G_STMT_START \ + { \ + g_log(G_LOG_DOMAIN, \ + G_LOG_LEVEL_CRITICAL, \ + "file %s: line %d (%s): should not be reached", \ + __FILE__, \ + __LINE__, \ + "<dropped>"); \ + return; \ + } \ + G_STMT_END +#endif + +/*****************************************************************************/ + +#if NM_MORE_ASSERTS == 0 + #define NM_ASSERT_G_RETURN_EXPR(expr) "<dropped>" + #define NM_ASSERT_NO_MSG 1 + +#else + #define NM_ASSERT_G_RETURN_EXPR(expr) "" expr "" + #define NM_ASSERT_NO_MSG 0 +#endif + +/*****************************************************************************/ + +#endif /* __NM_GASSERT_PATCH_H__ */ diff --git a/src/libnm-glib-aux/nm-glib.h b/src/libnm-glib-aux/nm-glib.h new file mode 100644 index 0000000000..befb8d9013 --- /dev/null +++ b/src/libnm-glib-aux/nm-glib.h @@ -0,0 +1,707 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2008 - 2018 Red Hat, Inc. + */ + +#ifndef __NM_GLIB_H__ +#define __NM_GLIB_H__ + +/*****************************************************************************/ + +#ifndef __NM_MACROS_INTERNAL_H__ + #error "nm-glib.h requires nm-macros-internal.h. Do not include this directly" +#endif + +/*****************************************************************************/ + +#ifdef __clang__ + + #undef G_GNUC_BEGIN_IGNORE_DEPRECATIONS + #undef G_GNUC_END_IGNORE_DEPRECATIONS + + #define G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") + + #define G_GNUC_END_IGNORE_DEPRECATIONS _Pragma("clang diagnostic pop") + +#endif + +/*****************************************************************************/ + +static inline void +__g_type_ensure(GType type) +{ +#if !GLIB_CHECK_VERSION(2, 34, 0) + if (G_UNLIKELY(type == (GType) -1)) + g_error("can't happen"); +#else + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + g_type_ensure(type); + G_GNUC_END_IGNORE_DEPRECATIONS; +#endif +} +#define g_type_ensure __g_type_ensure + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2, 34, 0) + + #define g_clear_pointer(pp, destroy) \ + G_STMT_START \ + { \ + G_STATIC_ASSERT(sizeof *(pp) == sizeof(gpointer)); \ + /* Only one access, please */ \ + gpointer *_pp = (gpointer *) (pp); \ + gpointer _p; \ + /* This assignment is needed to avoid a gcc warning */ \ + GDestroyNotify _destroy = (GDestroyNotify)(destroy); \ + \ + _p = *_pp; \ + if (_p) { \ + *_pp = NULL; \ + _destroy(_p); \ + } \ + } \ + G_STMT_END + +#endif + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2, 34, 0) + + /* These are used to clean up the output of test programs; we can just let + * them no-op in older glib. + */ + #define g_test_expect_message(log_domain, log_level, pattern) + #define g_test_assert_expected_messages() + +#else + + /* We build with -DGLIB_MAX_ALLOWED_VERSION set to 2.32 to make sure we don't + * accidentally use new API that we shouldn't. But we don't want warnings for + * the APIs that we emulate above. + */ + + #define g_test_expect_message(domain, level, format...) \ + G_STMT_START \ + { \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + g_test_expect_message(domain, level, format); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + } \ + G_STMT_END + + #define g_test_assert_expected_messages_internal(domain, file, line, func) \ + G_STMT_START \ + { \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + g_test_assert_expected_messages_internal(domain, file, line, func); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + } \ + G_STMT_END + +#endif + +/*****************************************************************************/ + +#if GLIB_CHECK_VERSION(2, 35, 0) + /* For glib >= 2.36, g_type_init() is deprecated. + * But since 2.35.1 (7c42ab23b55c43ab96d0ac2124b550bf1f49c1ec) this function + * does nothing. Replace the call with empty statement. */ + #define nm_g_type_init() \ + G_STMT_START \ + { \ + (void) 0; \ + } \ + G_STMT_END +#else + #define nm_g_type_init() \ + G_STMT_START \ + { \ + g_type_init(); \ + } \ + G_STMT_END +#endif + +/*****************************************************************************/ + +/* g_test_initialized() is only available since glib 2.36. */ +#if !GLIB_CHECK_VERSION(2, 36, 0) + #define g_test_initialized() (g_test_config_vars->test_initialized) +#endif + +/*****************************************************************************/ + +/* g_assert_cmpmem() is only available since glib 2.46. */ +#if !GLIB_CHECK_VERSION(2, 45, 7) + #define g_assert_cmpmem(m1, l1, m2, l2) \ + G_STMT_START \ + { \ + gconstpointer __m1 = m1, __m2 = m2; \ + int __l1 = l1, __l2 = l2; \ + if (__l1 != __l2) \ + g_assertion_message_cmpnum(G_LOG_DOMAIN, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + #l1 " (len(" #m1 ")) == " #l2 " (len(" #m2 "))", \ + __l1, \ + "==", \ + __l2, \ + 'i'); \ + else if (memcmp(__m1, __m2, __l1) != 0) \ + g_assertion_message(G_LOG_DOMAIN, \ + __FILE__, \ + __LINE__, \ + G_STRFUNC, \ + "assertion failed (" #m1 " == " #m2 ")"); \ + } \ + G_STMT_END +#endif + +/*****************************************************************************/ + +/* Rumtime check for glib version. First do a compile time check which + * (if satisfied) shortcuts the runtime check. */ +static inline gboolean +nm_glib_check_version(guint major, guint minor, guint micro) +{ + return GLIB_CHECK_VERSION(major, minor, micro) + || ((glib_major_version > major) + || (glib_major_version == major && glib_minor_version > minor) + || (glib_major_version == major && glib_minor_version == minor + && glib_micro_version < micro)); +} + +/*****************************************************************************/ + +/* g_test_skip() is only available since glib 2.38. Add a compatibility wrapper. */ +static inline void +__nmtst_g_test_skip(const char *msg) +{ +#if GLIB_CHECK_VERSION(2, 38, 0) + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + g_test_skip(msg); + G_GNUC_END_IGNORE_DEPRECATIONS +#else + g_debug("%s", msg); +#endif +} +#define g_test_skip __nmtst_g_test_skip + +/*****************************************************************************/ + +/* g_test_add_data_func_full() is only available since glib 2.34. Add a compatibility wrapper. */ +static inline void +__g_test_add_data_func_full(const char * testpath, + gpointer test_data, + GTestDataFunc test_func, + GDestroyNotify data_free_func) +{ +#if GLIB_CHECK_VERSION(2, 34, 0) + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + g_test_add_data_func_full(testpath, test_data, test_func, data_free_func); + G_GNUC_END_IGNORE_DEPRECATIONS +#else + g_return_if_fail(testpath != NULL); + g_return_if_fail(testpath[0] == '/'); + g_return_if_fail(test_func != NULL); + + g_test_add_vtable(testpath, + 0, + test_data, + NULL, + (GTestFixtureFunc) test_func, + (GTestFixtureFunc) data_free_func); +#endif +} +#define g_test_add_data_func_full __g_test_add_data_func_full + +/*****************************************************************************/ + +static inline gboolean +nm_g_hash_table_replace(GHashTable *hash, gpointer key, gpointer value) +{ + /* glib 2.40 added a return value indicating whether the key already existed + * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */ +#if GLIB_CHECK_VERSION(2, 40, 0) + return g_hash_table_replace(hash, key, value); +#else + gboolean contained = g_hash_table_contains(hash, key); + + g_hash_table_replace(hash, key, value); + return !contained; +#endif +} + +static inline gboolean +nm_g_hash_table_insert(GHashTable *hash, gpointer key, gpointer value) +{ + /* glib 2.40 added a return value indicating whether the key already existed + * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */ +#if GLIB_CHECK_VERSION(2, 40, 0) + return g_hash_table_insert(hash, key, value); +#else + gboolean contained = g_hash_table_contains(hash, key); + + g_hash_table_insert(hash, key, value); + return !contained; +#endif +} + +static inline gboolean +nm_g_hash_table_add(GHashTable *hash, gpointer key) +{ + /* glib 2.40 added a return value indicating whether the key already existed + * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */ +#if GLIB_CHECK_VERSION(2, 40, 0) + return g_hash_table_add(hash, key); +#else + gboolean contained = g_hash_table_contains(hash, key); + + g_hash_table_add(hash, key); + return !contained; +#endif +} + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2, 40, 0) || defined(NM_GLIB_COMPAT_H_TEST) +static inline void +_nm_g_ptr_array_insert(GPtrArray *array, int index_, gpointer data) +{ + g_return_if_fail(array); + g_return_if_fail(index_ >= -1); + g_return_if_fail(index_ <= (int) array->len); + + g_ptr_array_add(array, data); + + if (index_ != -1 && index_ != (int) (array->len - 1)) { + memmove(&(array->pdata[index_ + 1]), + &(array->pdata[index_]), + (array->len - index_ - 1) * sizeof(gpointer)); + array->pdata[index_] = data; + } +} +#endif + +#if !GLIB_CHECK_VERSION(2, 40, 0) + #define g_ptr_array_insert(array, index, data) \ + G_STMT_START \ + { \ + _nm_g_ptr_array_insert(array, index, data); \ + } \ + G_STMT_END +#else + #define g_ptr_array_insert(array, index, data) \ + G_STMT_START \ + { \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + g_ptr_array_insert(array, index, data); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + } \ + G_STMT_END +#endif + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2, 40, 0) +static inline gboolean +_g_key_file_save_to_file(GKeyFile *key_file, const char *filename, GError **error) +{ + char * contents; + gboolean success; + gsize length; + + g_return_val_if_fail(key_file != NULL, FALSE); + g_return_val_if_fail(filename != NULL, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + contents = g_key_file_to_data(key_file, &length, NULL); + g_assert(contents != NULL); + + success = g_file_set_contents(filename, contents, length, error); + g_free(contents); + + return success; +} + #define g_key_file_save_to_file(key_file, filename, error) \ + _g_key_file_save_to_file(key_file, filename, error) +#else + #define g_key_file_save_to_file(key_file, filename, error) \ + ({ \ + gboolean _success; \ + \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + _success = g_key_file_save_to_file(key_file, filename, error); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + _success; \ + }) +#endif + +/*****************************************************************************/ + +#if GLIB_CHECK_VERSION(2, 36, 0) + #define g_credentials_get_unix_pid(creds, error) \ + ({ \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS(g_credentials_get_unix_pid)((creds), (error)); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + }) +#else + #define g_credentials_get_unix_pid(creds, error) \ + ({ \ + struct ucred *native_creds; \ + \ + native_creds = g_credentials_get_native((creds), G_CREDENTIALS_TYPE_LINUX_UCRED); \ + g_assert(native_creds); \ + native_creds->pid; \ + }) +#endif + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2, 40, 0) || defined(NM_GLIB_COMPAT_H_TEST) +static inline gpointer * +_nm_g_hash_table_get_keys_as_array(GHashTable *hash_table, guint *length) +{ + GHashTableIter iter; + gpointer key, *ret; + guint i = 0; + + g_return_val_if_fail(hash_table, NULL); + + ret = g_new0(gpointer, g_hash_table_size(hash_table) + 1); + g_hash_table_iter_init(&iter, hash_table); + + while (g_hash_table_iter_next(&iter, &key, NULL)) + ret[i++] = key; + + ret[i] = NULL; + + if (length) + *length = i; + + return ret; +} +#endif +#if !GLIB_CHECK_VERSION(2, 40, 0) + #define g_hash_table_get_keys_as_array(hash_table, length) \ + ({ _nm_g_hash_table_get_keys_as_array(hash_table, length); }) +#else + #define g_hash_table_get_keys_as_array(hash_table, length) \ + ({ \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS(g_hash_table_get_keys_as_array) \ + ((hash_table), (length)); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + }) +#endif + +/*****************************************************************************/ + +#ifndef g_info + /* g_info was only added with 2.39.2 */ + #define g_info(...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__) +#endif + +/*****************************************************************************/ + +static inline gpointer +_nm_g_steal_pointer(gpointer pp) +{ + gpointer *ptr = (gpointer *) pp; + gpointer ref; + + ref = *ptr; + *ptr = NULL; + + return ref; +} + +#if !GLIB_CHECK_VERSION(2, 44, 0) +static inline gpointer +g_steal_pointer(gpointer pp) +{ + return _nm_g_steal_pointer(pp); +} +#endif + +#ifdef g_steal_pointer + #undef g_steal_pointer +#endif +#define g_steal_pointer(pp) ((typeof(*(pp))) _nm_g_steal_pointer(pp)) + +/*****************************************************************************/ + +static inline gboolean +_nm_g_strv_contains(const char *const *strv, const char *str) +{ +#if !GLIB_CHECK_VERSION(2, 44, 0) + g_return_val_if_fail(strv != NULL, FALSE); + g_return_val_if_fail(str != NULL, FALSE); + + for (; *strv != NULL; strv++) { + if (g_str_equal(str, *strv)) + return TRUE; + } + + return FALSE; +#else + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + return g_strv_contains(strv, str); + G_GNUC_END_IGNORE_DEPRECATIONS +#endif +} +#define g_strv_contains _nm_g_strv_contains + +/*****************************************************************************/ + +static inline GVariant * +_nm_g_variant_new_take_string(char *string) +{ +#if !GLIB_CHECK_VERSION(2, 36, 0) + GVariant *value; + + g_return_val_if_fail(string != NULL, NULL); + g_return_val_if_fail(g_utf8_validate(string, -1, NULL), NULL); + + value = g_variant_new_string(string); + g_free(string); + return value; +#elif !GLIB_CHECK_VERSION(2, 38, 0) + GVariant *value; + GBytes * bytes; + + g_return_val_if_fail(string != NULL, NULL); + g_return_val_if_fail(g_utf8_validate(string, -1, NULL), NULL); + + bytes = g_bytes_new_take(string, strlen(string) + 1); + value = g_variant_new_from_bytes(G_VARIANT_TYPE_STRING, bytes, TRUE); + g_bytes_unref(bytes); + + return value; +#else + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + return g_variant_new_take_string(string); + G_GNUC_END_IGNORE_DEPRECATIONS +#endif +} +#define g_variant_new_take_string _nm_g_variant_new_take_string + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2, 38, 0) +_nm_printf(1, 2) static inline GVariant *_nm_g_variant_new_printf(const char *format_string, ...) +{ + char * string; + va_list ap; + + g_return_val_if_fail(format_string, NULL); + + va_start(ap, format_string); + string = g_strdup_vprintf(format_string, ap); + va_end(ap); + + return g_variant_new_take_string(string); +} + #define g_variant_new_printf(...) _nm_g_variant_new_printf(__VA_ARGS__) +#else + #define g_variant_new_printf(...) \ + ({ \ + GVariant *_v; \ + \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + _v = g_variant_new_printf(__VA_ARGS__); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + _v; \ + }) +#endif + +/*****************************************************************************/ + +/* Recent glib also casts the results to typeof(Obj), but only if + * + * ( defined(g_has_typeof) && GLIB_VERSION_MAX_ALLOWED >= GLIB_VERSION_2_56 ) + * + * Since we build NetworkManager with older GLIB_VERSION_MAX_ALLOWED, it's + * not taking effect. + * + * Override this. */ +#undef g_object_ref +#undef g_object_ref_sink +#define g_object_ref(Obj) ((typeof(Obj)) g_object_ref(Obj)) +#define g_object_ref_sink(Obj) ((typeof(Obj)) g_object_ref_sink(Obj)) + +/*****************************************************************************/ + +#ifndef g_autofree + /* we still don't rely on recent glib to provide g_autofree. Hence, we continue + * to use our gs_* free macros that we took from libgsystem. + * + * To ease migration towards g_auto*, add a compat define for g_autofree. */ + #define g_autofree gs_free +#endif + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2, 47, 1) +/* Older versions of g_value_unset() only allowed to unset a GValue which + * was initialized previously. This was relaxed ([1], [2], [3]). + * + * Our nm_auto_unset_gvalue macro requires to be able to call g_value_unset(). + * Also, it is our general practice to allow for that. Add a compat implementation. + * + * [1] https://gitlab.gnome.org/GNOME/glib/commit/4b2d92a864f1505f1b08eb639d74293fa32681da + * [2] commit "Allow passing unset GValues to g_value_unset()" + * [3] https://bugzilla.gnome.org/show_bug.cgi?id=755766 + */ +static inline void +_nm_g_value_unset(GValue *value) +{ + g_return_if_fail(value); + + if (value->g_type != 0) + g_value_unset(value); +} + #define g_value_unset _nm_g_value_unset +#endif + +/* G_PID_FORMAT was added only in 2.53.5. Define it ourself. + * + * If this was about "pid_t", we would check SIZEOF_PID_T, and set + * PRIi32/PRIi16, like systemd does. But it's actually about + * GPid, which glib typedefs as an "int". + * + * There is a test_gpid() that check that GPid is really a typedef + * for int. */ +#undef G_PID_FORMAT +#define G_PID_FORMAT "i" + +/*****************************************************************************/ + +/* G_SOURCE_FUNC was added in 2.57.2. */ +#undef G_SOURCE_FUNC +#define G_SOURCE_FUNC(f) ((GSourceFunc)(void (*)(void))(f)) + +/*****************************************************************************/ + +/* g_atomic_pointer_get() is implemented as a macro, and it is also used for + * (gsize *) arguments. However, that leads to compiler warnings in certain + * configurations. Work around it, by redefining the macro. */ +static inline gpointer +_g_atomic_pointer_get(void **atomic) +{ + return g_atomic_pointer_get(atomic); +} +#undef g_atomic_pointer_get +#define g_atomic_pointer_get(atomic) \ + ({ \ + typeof(*atomic) *const _atomic = (atomic); \ + \ + /* g_atomic_pointer_get() is used by glib also for (gsize *) pointers, + * not only pointers to pointers. We thus don't enforce that (*atomic) + * is a pointer, but of suitable size/alignment. */ \ + \ + G_STATIC_ASSERT(sizeof(*_atomic) == sizeof(gpointer)); \ + G_STATIC_ASSERT(_nm_alignof(*_atomic) == _nm_alignof(gpointer)); \ + (void) (0 ? (gpointer) * (_atomic) : NULL); \ + \ + (typeof(*_atomic)) _g_atomic_pointer_get((void **) _atomic); \ + }) + +/* Reimplement g_atomic_pointer_set() macro too. Our variant does more type + * checks. */ +static inline void +_g_atomic_pointer_set(void **atomic, void *newval) +{ + return g_atomic_pointer_set(atomic, newval); +} +#undef g_atomic_pointer_set +#define g_atomic_pointer_set(atomic, newval) \ + ({ \ + typeof(*atomic) *const _atomic = (atomic); \ + typeof(*_atomic) const _newval = (newval); \ + _nm_unused gconstpointer const _val_type_check = _newval; \ + \ + (void) (0 ? (gpointer) * (_atomic) : NULL); \ + \ + _g_atomic_pointer_set((void **) _atomic, (void *) _newval); \ + }) + +/* Glib implements g_atomic_pointer_compare_and_exchange() as a macro. + * For one, to inline the atomic operation and also to perform some type checks + * on the arguments. + * Depending on compiler and glib version, glib passes the arguments as they + * are to __atomic_compare_exchange_n(). Some clang version don't accept const + * pointers there. Reimplement the macro to get that right, but with stronger + * type checks (as we use typeof()). Had one job. */ +static inline gboolean +_g_atomic_pointer_compare_and_exchange(void **atomic, void *oldval, void *newval) +{ + return g_atomic_pointer_compare_and_exchange(atomic, oldval, newval); +} +#undef g_atomic_pointer_compare_and_exchange +#define g_atomic_pointer_compare_and_exchange(atomic, oldval, newval) \ + ({ \ + typeof(*atomic) *const _atomic = (atomic); \ + typeof(*_atomic) const _oldval = (oldval); \ + typeof(*_atomic) const _newval = (newval); \ + _nm_unused gconstpointer const _val_type_check = _oldval; \ + \ + (void) (0 ? (gpointer) * (_atomic) : NULL); \ + \ + _g_atomic_pointer_compare_and_exchange((void **) _atomic, \ + (void *) _oldval, \ + (void *) _newval); \ + }) + +/*****************************************************************************/ + +#if !GLIB_CHECK_VERSION(2, 58, 0) +static inline gboolean +g_hash_table_steal_extended(GHashTable * hash_table, + gconstpointer lookup_key, + gpointer * stolen_key, + gpointer * stolen_value) +{ + g_assert(stolen_key); + g_assert(stolen_value); + + if (g_hash_table_lookup_extended(hash_table, lookup_key, stolen_key, stolen_value)) { + g_hash_table_steal(hash_table, lookup_key); + return TRUE; + } + *stolen_key = NULL; + *stolen_value = NULL; + return FALSE; +} +#else + #define g_hash_table_steal_extended(hash_table, lookup_key, stolen_key, stolen_value) \ + ({ \ + gpointer *_stolen_key = (stolen_key); \ + gpointer *_stolen_value = (stolen_value); \ + \ + /* we cannot allow NULL arguments, because then we would leak the values in + * the compat implementation. */ \ + g_assert(_stolen_key); \ + g_assert(_stolen_value); \ + \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + g_hash_table_steal_extended(hash_table, lookup_key, _stolen_key, _stolen_value); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + }) +#endif + +/*****************************************************************************/ + +__attribute__(( + __deprecated__("Don't use g_cancellable_reset(). Create a new cancellable instead."))) void +_nm_g_cancellable_reset(GCancellable *cancellable); + +#undef g_cancellable_reset +#define g_cancellable_reset(cancellable) _nm_g_cancellable_reset(cancellable) + +/*****************************************************************************/ + +#endif /* __NM_GLIB_H__ */ diff --git a/src/libnm-glib-aux/nm-hash-utils.c b/src/libnm-glib-aux/nm-hash-utils.c new file mode 100644 index 0000000000..9e168dccc7 --- /dev/null +++ b/src/libnm-glib-aux/nm-hash-utils.c @@ -0,0 +1,286 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.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 *volatile global_seed = NULL; + +static const guint8 * +_get_hash_key_init(void) +{ + /* the returned hash is aligned to guin64, hence, it is safe + * to use it as guint* or guint64* pointer. */ + static union { + guint8 v8[HASH_KEY_SIZE]; + guint _align_as_uint; + guint32 _align_as_uint32; + guint64 _align_as_uint64; + } g_arr; + const guint8 *g; + +again: + g = g_atomic_pointer_get(&global_seed); + if (!G_UNLIKELY(g)) { + static gsize g_lock; + uint64_t h; + union { + guint vuint; + guint8 v8[HASH_KEY_SIZE]; + guint8 _extra_entropy[3 * HASH_KEY_SIZE]; + } t_arr; + + nm_utils_random_bytes(&t_arr, sizeof(t_arr)); + + /* We only initialize one random hash key. So we can spend some effort + * of getting this right. For one, we collect more random bytes than + * necessary. + * + * Then, the first guint of the seed should have all the entropy that we could + * obtain in sizeof(t_arr). For that, siphash(t_arr) and xor the first guint + * with hash. + * The first guint is especially interesting for nm_hash_static() below that + * doesn't use siphash itself. */ + h = c_siphash_hash(t_arr.v8, (const guint8 *) &t_arr, sizeof(t_arr)); + if (sizeof(h) > sizeof(guint)) + t_arr.vuint = t_arr.vuint ^ ((guint)(h & G_MAXUINT)) ^ ((guint)(h >> 32)); + else + t_arr.vuint = t_arr.vuint ^ ((guint)(h & G_MAXUINT)); + + if (!g_once_init_enter(&g_lock)) { + /* lost a race. The random key is already initialized. */ + goto again; + } + + memcpy(g_arr.v8, t_arr.v8, HASH_KEY_SIZE); + g = g_arr.v8; + g_atomic_pointer_set(&global_seed, g); + g_once_init_leave(&g_lock, 1); + } + + nm_assert(g == g_arr.v8); + return g; +} + +#define _get_hash_key() \ + ({ \ + const guint8 *_g; \ + \ + _g = g_atomic_pointer_get(&global_seed); \ + if (G_UNLIKELY(!_g)) \ + _g = _get_hash_key_init(); \ + _g; \ + }) + +guint +nm_hash_static(guint static_seed) +{ + /* Note that we only xor the static_seed with the first guint of the key. + * + * We don't use siphash, which would mix the bits better with _get_hash_key(). + * Note that nm_hash_static() isn't used to hash the static_seed. Instead, it + * is used to get a unique hash value in a static context. That means, every + * caller is responsible to choose a static_seed that is sufficiently + * distinct from all other callers. In other words, static_seed should be a + * unique constant with good entropy. + * + * Note that _get_hash_key_init() already xored the first guint of the + * key with the siphash of the entire static key. That means, even if + * we got bad randomness for the first guint, the first guint is also + * mixed with the randomness of the entire random key. + * + * Also, ensure that we don't return zero (like for nm_hash_complete()). + */ + return ((*((const guint *) _get_hash_key())) ^ static_seed) ?: 3679500967u; +} + +void +nm_hash_siphash42_init(CSipHash *h, guint static_seed) +{ + const guint8 *g; + union { + guint64 _align_as_uint64; + guint arr[HASH_KEY_SIZE_GUINT]; + } seed; + + nm_assert(h); + + g = _get_hash_key(); + memcpy(&seed, g, HASH_KEY_SIZE); + seed.arr[0] ^= static_seed; + c_siphash_init(h, (const guint8 *) &seed); +} + +guint +nm_hash_str(const char *str) +{ + NMHashState h; + + if (!str) + return nm_hash_static(1867854211u); + nm_hash_init(&h, 1867854211u); + nm_hash_update_str(&h, str); + return nm_hash_complete(&h); +} + +guint +nm_str_hash(gconstpointer str) +{ + return nm_hash_str(str); +} + +guint +nm_hash_ptr(gconstpointer ptr) +{ + NMHashState h; + + if (!ptr) + return nm_hash_static(2907677551u); + nm_hash_init(&h, 2907677551u); + nm_hash_update(&h, &ptr, sizeof(ptr)); + return nm_hash_complete(&h); +} + +guint +nm_direct_hash(gconstpointer ptr) +{ + return nm_hash_ptr(ptr); +} + +/*****************************************************************************/ + +guint +nm_pstr_hash(gconstpointer p) +{ + const char *const *s = p; + + if (!s) + return nm_hash_static(101061439u); + return nm_hash_str(*s); +} + +gboolean +nm_pstr_equal(gconstpointer a, gconstpointer b) +{ + const char *const *s1 = a; + const char *const *s2 = b; + + return (s1 == s2) || (s1 && s2 && nm_streq0(*s1, *s2)); +} + +guint +nm_pint_hash(gconstpointer p) +{ + const int *s = p; + + if (!s) + return nm_hash_static(298377461u); + return nm_hash_val(1208815757u, *s); +} + +gboolean +nm_pint_equals(gconstpointer a, gconstpointer b) +{ + const int *s1 = a; + const int *s2 = a; + + return s1 == s2 || (s1 && s2 && *s1 == *s2); +} + +guint +nm_pdirect_hash(gconstpointer p) +{ + const void *const *s = p; + + if (!s) + return nm_hash_static(1852748873u); + return nm_direct_hash(*s); +} + +gboolean +nm_pdirect_equal(gconstpointer a, gconstpointer b) +{ + const void *const *s1 = a; + const void *const *s2 = b; + + return (s1 == s2) || (s1 && s2 && *s1 == *s2); +} + +guint +nm_ppdirect_hash(gconstpointer p) +{ + const void *const *const *s = p; + + if (!s) + return nm_hash_static(396534869u); + if (!*s) + return nm_hash_static(1476102263u); + return nm_direct_hash(**s); +} + +gboolean +nm_ppdirect_equal(gconstpointer a, gconstpointer b) +{ + const void *const *const *s1 = a; + const void *const *const *s2 = b; + + if (s1 == s2) + return TRUE; + if (!s1 || !s2) + return FALSE; + + if (*s1 == *s2) + return TRUE; + if (!*s1 || !*s2) + return FALSE; + + return **s1 == **s2; +} + +/*****************************************************************************/ + +guint +nm_gbytes_hash(gconstpointer p) +{ + GBytes * ptr = (GBytes *) p; + gconstpointer arr; + gsize len; + + arr = g_bytes_get_data(ptr, &len); + return nm_hash_mem(792701303u, arr, len); +} + +guint +nm_pgbytes_hash(gconstpointer p) +{ + GBytes *const *ptr = p; + gconstpointer arr; + gsize len; + + arr = g_bytes_get_data(*ptr, &len); + return nm_hash_mem(1470631313u, arr, len); +} + +gboolean +nm_pgbytes_equal(gconstpointer a, gconstpointer b) +{ + GBytes *const *ptr_a = a; + GBytes *const *ptr_b = b; + + return g_bytes_equal(*ptr_a, *ptr_b); +} diff --git a/src/libnm-glib-aux/nm-hash-utils.h b/src/libnm-glib-aux/nm-hash-utils.h new file mode 100644 index 0000000000..a7b8677bf5 --- /dev/null +++ b/src/libnm-glib-aux/nm-hash-utils.h @@ -0,0 +1,442 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NM_HASH_UTILS_H__ +#define __NM_HASH_UTILS_H__ + +#include "c-siphash/src/c-siphash.h" +#include "nm-macros-internal.h" + +/*****************************************************************************/ + +#define NM_HASH_SEED_16(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af) \ + ((const guint8[16]){a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af}) + +void nm_hash_siphash42_init(CSipHash *h, guint static_seed); + +/* Siphash24 of binary buffer @arr and @len, using the randomized seed from + * other NMHash functions. + * + * Note, that this is guaranteed to use siphash42 under the hood (contrary to + * all other NMHash API, which leave this undefined). That matters at the point, + * where the caller needs to be sure that a reasonably strong hashing algorithm + * is used. (Yes, NMHash is all about siphash24, but otherwise that is not promised + * anywhere). + * + * Another difference is, that this returns guint64 (not guint like other NMHash functions). + * + * Another difference is, that this may also return zero (not like nm_hash_complete()). + * + * Then, why not use c_siphash_hash() directly? Because this also uses the randomized, + * per-run hash-seed like nm_hash_init(). So, you get siphash24 with a random + * seed (which is cached for the current run of the program). + */ +static inline guint64 +nm_hash_siphash42(guint static_seed, const void *ptr, gsize n) +{ + CSipHash h; + + nm_hash_siphash42_init(&h, static_seed); + c_siphash_append(&h, ptr, n); + return c_siphash_finalize(&h); +} + +/*****************************************************************************/ + +struct _NMHashState { + CSipHash _state; +}; + +typedef struct _NMHashState NMHashState; + +guint nm_hash_static(guint static_seed); + +static inline void +nm_hash_init(NMHashState *state, guint static_seed) +{ + nm_assert(state); + + nm_hash_siphash42_init(&state->_state, static_seed); +} + +static inline guint64 +nm_hash_complete_u64(NMHashState *state) +{ + nm_assert(state); + + /* this returns the native u64 hash value. Note that this differs + * from nm_hash_complete() in two ways: + * + * - the type, guint64 vs. guint. + * - nm_hash_complete() never returns zero. + * + * In practice, nm_hash*() API is implemented via siphash24, so this returns + * the siphash24 value. But that is not guaranteed by the API, and if you need + * siphash24 directly, use c_siphash_*() and nm_hash_siphash42*() API. */ + return c_siphash_finalize(&state->_state); +} + +static inline guint +nm_hash_complete(NMHashState *state) +{ + guint64 h; + + h = nm_hash_complete_u64(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(n == 0 || ptr); + + /* Note: the data passed in here might be sensitive data (secrets), + * that we should nm_explicit_bzero() afterwards. However, since + * we are using siphash24 with a random key, that is not really + * necessary. Something to keep in mind, if we ever move away from + * this hash implementation. */ + c_siphash_append(&state->_state, ptr, n); +} + +#define nm_hash_update_val(state, val) \ + G_STMT_START \ + { \ + typeof(val) _val = (val); \ + \ + nm_hash_update((state), &_val, sizeof(_val)); \ + } \ + G_STMT_END + +#define nm_hash_update_valp(state, val) nm_hash_update((state), (val), sizeof(*(val))) + +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) + nm_hash_update(state, ptr, n); +} + +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); + +#define nm_hash_val(static_seed, val) \ + ({ \ + NMHashState _h; \ + \ + nm_hash_init(&_h, (static_seed)); \ + nm_hash_update_val(&_h, (val)); \ + nm_hash_complete(&_h); \ + }) + +static inline guint +nm_hash_mem(guint static_seed, const void *ptr, gsize n) +{ + NMHashState h; + + if (n == 0) + return nm_hash_static(static_seed); + nm_hash_init(&h, static_seed); + nm_hash_update(&h, ptr, n); + return nm_hash_complete(&h); +} + +/*****************************************************************************/ + +/* nm_pstr_*() are for hashing keys that are pointers to strings, + * that is, "const char *const*" types, using strcmp(). */ + +guint nm_pstr_hash(gconstpointer p); + +gboolean nm_pstr_equal(gconstpointer a, gconstpointer b); + +/*****************************************************************************/ + +/* nm_pint_*() are for hashing keys that are pointers to int values, + * that is, "const int *" types. */ + +guint nm_pint_hash(gconstpointer p); +gboolean nm_pint_equals(gconstpointer a, gconstpointer b); + +G_STATIC_ASSERT(sizeof(int) == sizeof(guint32)); +#define nm_puint32_hash nm_pint_hash +#define nm_puint32_equals nm_pint_equals + +/*****************************************************************************/ + +/* this hashes/compares the pointer value that we point to. Basically, + * (*((const void *const*) a) == *((const void *const*) b)). */ + +guint nm_pdirect_hash(gconstpointer p); + +gboolean nm_pdirect_equal(gconstpointer a, gconstpointer b); + +/* this hashes/compares the direct pointer value by following pointers to + * pointers 2 times. + * (**((const void *const*const*) a) == **((const void *const*const*) b)). */ + +guint nm_ppdirect_hash(gconstpointer p); + +gboolean nm_ppdirect_equal(gconstpointer a, gconstpointer b); + +/*****************************************************************************/ + +guint nm_gbytes_hash(gconstpointer p); +#define nm_gbytes_equal g_bytes_equal + +guint nm_pgbytes_hash(gconstpointer p); +gboolean nm_pgbytes_equal(gconstpointer a, gconstpointer b); + +/*****************************************************************************/ + +#define NM_HASH_OBFUSCATE_PTR_FMT "%016" G_GINT64_MODIFIER "x" + +/* sometimes we want to log a pointer directly, for providing context/information about + * the message that get logged. Logging pointer values directly defeats ASLR, so we should + * not do that. This returns a "unsigned long long" value that can be used + * instead. + * + * Note that there is a chance that two different pointer values hash to the same obfuscated + * value. So beware of that when reviewing logs. However, such a collision is very unlikely. */ +static inline guint64 +nm_hash_obfuscate_ptr(guint static_seed, gconstpointer val) +{ + NMHashState h; + + nm_hash_init(&h, static_seed); + nm_hash_update_val(&h, val); + return nm_hash_complete_u64(&h); +} + +/* if you want to log obfuscated pointer for a certain context (like, NMPRuleManager + * logging user-tags), then you are advised to use nm_hash_obfuscate_ptr() with your + * own, unique static-seed. + * + * However, for example the singleton constructors log the obfuscated pointer values + * for all singletons, so they must all be obfuscated with the same seed. So, this + * macro uses a particular static seed that should be used by when comparing pointer + * values in a global context. */ +#define NM_HASH_OBFUSCATE_PTR(ptr) (nm_hash_obfuscate_ptr(1678382159u, ptr)) + +#define NM_HASH_OBFUSCATE_PTR_STR(ptr, buf) \ + ({ \ + gconstpointer _ptr = (ptr); \ + \ + _ptr ? nm_sprintf_buf(buf, "[" NM_HASH_OBFUSCATE_PTR_FMT "]", NM_HASH_OBFUSCATE_PTR(_ptr)) \ + : "(null)"; \ + }) + +static inline const char * +nm_hash_obfuscated_ptr_str(gconstpointer ptr, char buf[static 17]) +{ + int l; + + nm_assert(buf); + l = g_snprintf(buf, 17, NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(ptr)); + nm_assert(l < 17); + return buf; +} + +#define nm_hash_obfuscated_ptr_str_a(ptr) (nm_hash_obfuscated_ptr_str((ptr), g_alloca(17))) + +/*****************************************************************************/ + +#endif /* __NM_HASH_UTILS_H__ */ diff --git a/src/libnm-glib-aux/nm-io-utils.c b/src/libnm-glib-aux/nm-io-utils.c new file mode 100644 index 0000000000..e02049af1a --- /dev/null +++ b/src/libnm-glib-aux/nm-io-utils.c @@ -0,0 +1,480 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-io-utils.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "nm-str-buf.h" +#include "nm-shared-utils.h" +#include "nm-secret-utils.h" +#include "nm-errno.h" + +/*****************************************************************************/ + +_nm_printf(4, 5) static int _get_contents_error(GError ** error, + int errsv, + int * out_errsv, + const char *format, + ...) +{ + nm_assert(NM_ERRNO_NATIVE(errsv)); + + if (error) { + gs_free char *msg = NULL; + va_list args; + char bstrerr[NM_STRERROR_BUFSIZE]; + + va_start(args, format); + msg = g_strdup_vprintf(format, args); + va_end(args); + g_set_error(error, + G_FILE_ERROR, + g_file_error_from_errno(errsv), + "%s: %s", + msg, + nm_strerror_native_r(errsv, bstrerr, sizeof(bstrerr))); + } + + nm_assert(errsv > 0); + NM_SET_OUT(out_errsv, errsv); + + return FALSE; +} +#define _get_contents_error_errno(error, out_errsv, ...) \ + ({ \ + int _errsv = (errno); \ + \ + _get_contents_error(error, _errsv, out_errsv, __VA_ARGS__); \ + }) + +/** + * nm_utils_fd_get_contents: + * @fd: open file descriptor to read. The fd will not be closed, + * but don't rely on its state afterwards. + * @close_fd: if %TRUE, @fd will be closed by the function. + * Passing %TRUE here might safe a syscall for dup(). + * @max_length: allocate at most @max_length bytes. If the + * file is larger, reading will fail. Set to zero to use + * a very large default. + * WARNING: @max_length is here to avoid a crash for huge/unlimited files. + * For example, stat(/sys/class/net/enp0s25/ifindex) gives a filesize of + * 4K, although the actual real is small. @max_length is the memory + * allocated in the process of reading the file, thus it must be at least + * the size reported by fstat. + * If you set it to 1K, read will fail because fstat() claims the + * file is larger. + * @flags: %NMUtilsFileGetContentsFlags for reading the file. + * @contents: the output buffer with the file read. It is always + * NUL terminated. The buffer is at most @max_length long, including + * the NUL byte. That is, it reads only files up to a length of + * @max_length - 1 bytes. + * @length: optional output argument of the read file size. + * @out_errsv: (allow-none) (out): on error, a positive errno. or zero. + * @error: + * + * + * A reimplementation of g_file_get_contents() with a few differences: + * - accepts an open fd, instead of a path name. This allows you to + * use openat(). + * - limits the maximum filesize to max_length. + * + * Returns: TRUE on success. + */ +gboolean +nm_utils_fd_get_contents(int fd, + gboolean close_fd, + gsize max_length, + NMUtilsFileGetContentsFlags flags, + char ** contents, + gsize * length, + int * out_errsv, + GError ** error) +{ + nm_auto_close int fd_keeper = close_fd ? fd : -1; + struct stat stat_buf; + gs_free char * str = NULL; + const bool do_bzero_mem = NM_FLAGS_HAS(flags, NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET); + int errsv; + + g_return_val_if_fail(fd >= 0, FALSE); + g_return_val_if_fail(contents && !*contents, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + NM_SET_OUT(length, 0); + + if (fstat(fd, &stat_buf) < 0) + return _get_contents_error_errno(error, out_errsv, "failure during fstat"); + + if (!max_length) { + /* default to a very large size, but not extreme */ + max_length = 2 * 1024 * 1024; + } + + if (stat_buf.st_size > 0 && S_ISREG(stat_buf.st_mode)) { + const gsize n_stat = stat_buf.st_size; + ssize_t n_read; + + if (n_stat > max_length - 1) + return _get_contents_error(error, + EMSGSIZE, + out_errsv, + "file too large (%zu+1 bytes with maximum %zu bytes)", + n_stat, + max_length); + + str = g_try_malloc(n_stat + 1); + if (!str) + return _get_contents_error(error, + ENOMEM, + out_errsv, + "failure to allocate buffer of %zu+1 bytes", + n_stat); + + n_read = nm_utils_fd_read_loop(fd, str, n_stat, TRUE); + if (n_read < 0) { + if (do_bzero_mem) + nm_explicit_bzero(str, n_stat); + return _get_contents_error(error, + -n_read, + out_errsv, + "error reading %zu bytes from file descriptor", + n_stat); + } + str[n_read] = '\0'; + + if (n_read < n_stat) { + if (!(str = nm_secret_mem_try_realloc_take(str, do_bzero_mem, n_stat + 1, n_read + 1))) + return _get_contents_error(error, + ENOMEM, + out_errsv, + "failure to reallocate buffer with %zu bytes", + n_read + 1); + } + NM_SET_OUT(length, n_read); + } else { + nm_auto_fclose FILE *f = NULL; + char buf[4096]; + gsize n_have, n_alloc; + int fd2; + + if (fd_keeper >= 0) + fd2 = nm_steal_fd(&fd_keeper); + else { + fd2 = fcntl(fd, F_DUPFD_CLOEXEC, 0); + if (fd2 < 0) + return _get_contents_error_errno(error, out_errsv, "error during dup"); + } + + if (!(f = fdopen(fd2, "r"))) { + errsv = errno; + nm_close(fd2); + return _get_contents_error(error, errsv, out_errsv, "failure during fdopen"); + } + + n_have = 0; + n_alloc = 0; + + while (!feof(f)) { + gsize n_read; + + n_read = fread(buf, 1, sizeof(buf), f); + errsv = errno; + if (ferror(f)) { + if (do_bzero_mem) + nm_explicit_bzero(buf, sizeof(buf)); + return _get_contents_error(error, errsv, out_errsv, "error during fread"); + } + + if (n_have > G_MAXSIZE - 1 - n_read || n_have + n_read + 1 > max_length) { + if (do_bzero_mem) + nm_explicit_bzero(buf, sizeof(buf)); + return _get_contents_error( + error, + EMSGSIZE, + out_errsv, + "file stream too large (%zu+1 bytes with maximum %zu bytes)", + (n_have > G_MAXSIZE - 1 - n_read) ? G_MAXSIZE : n_have + n_read, + max_length); + } + + if (n_have + n_read + 1 >= n_alloc) { + gsize old_n_alloc = n_alloc; + + if (n_alloc != 0) { + nm_assert(str); + if (n_alloc >= max_length / 2) + n_alloc = max_length; + else + n_alloc *= 2; + } else { + nm_assert(!str); + n_alloc = NM_MIN(n_read + 1, sizeof(buf)); + } + + if (!(str = nm_secret_mem_try_realloc_take(str, + do_bzero_mem, + old_n_alloc, + n_alloc))) { + if (do_bzero_mem) + nm_explicit_bzero(buf, sizeof(buf)); + return _get_contents_error(error, + ENOMEM, + out_errsv, + "failure to allocate buffer of %zu bytes", + n_alloc); + } + } + + memcpy(str + n_have, buf, n_read); + n_have += n_read; + } + + if (do_bzero_mem) + nm_explicit_bzero(buf, sizeof(buf)); + + if (n_alloc == 0) + str = g_new0(char, 1); + else { + str[n_have] = '\0'; + if (n_have + 1 < n_alloc) { + if (!(str = nm_secret_mem_try_realloc_take(str, do_bzero_mem, n_alloc, n_have + 1))) + return _get_contents_error(error, + ENOMEM, + out_errsv, + "failure to truncate buffer to %zu bytes", + n_have + 1); + } + } + + NM_SET_OUT(length, n_have); + } + + *contents = g_steal_pointer(&str); + NM_SET_OUT(out_errsv, 0); + return TRUE; +} + +/** + * nm_utils_file_get_contents: + * @dirfd: optional file descriptor to use openat(). If negative, use plain open(). + * @filename: the filename to open. Possibly relative to @dirfd. + * @max_length: allocate at most @max_length bytes. + * WARNING: see nm_utils_fd_get_contents() hint about @max_length. + * @flags: %NMUtilsFileGetContentsFlags for reading the file. + * @contents: the output buffer with the file read. It is always + * NUL terminated. The buffer is at most @max_length long, including + * the NUL byte. That is, it reads only files up to a length of + * @max_length - 1 bytes. + * @length: optional output argument of the read file size. + * @out_errsv: (allow-none) (out): on error, a positive errno. or zero. + * @error: + * + * A reimplementation of g_file_get_contents() with a few differences: + * - accepts an @dirfd to open @filename relative to that path via openat(). + * - limits the maximum filesize to max_length. + * - uses O_CLOEXEC on internal file descriptor + * - optionally returns the native errno on failure. + * + * Returns: TRUE on success. + */ +gboolean +nm_utils_file_get_contents(int dirfd, + const char * filename, + gsize max_length, + NMUtilsFileGetContentsFlags flags, + char ** contents, + gsize * length, + int * out_errsv, + GError ** error) +{ + int fd; + + g_return_val_if_fail(filename && filename[0], FALSE); + g_return_val_if_fail(contents && !*contents, FALSE); + + NM_SET_OUT(length, 0); + + if (dirfd >= 0) { + fd = openat(dirfd, filename, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + return _get_contents_error_errno(error, + out_errsv, + "Failed to open file \"%s\" with openat", + filename); + } + } else { + fd = open(filename, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + return _get_contents_error_errno(error, + out_errsv, + "Failed to open file \"%s\"", + filename); + } + } + return nm_utils_fd_get_contents(fd, + TRUE, + max_length, + flags, + contents, + length, + out_errsv, + error); +} + +/*****************************************************************************/ + +/* + * Copied from GLib's g_file_set_contents() et al., but allows + * specifying a mode for the new file. + */ +gboolean +nm_utils_file_set_contents(const char *filename, + const char *contents, + gssize length, + mode_t mode, + int * out_errsv, + GError ** error) +{ + gs_free char *tmp_name = NULL; + struct stat statbuf; + int errsv; + gssize s; + int fd; + + g_return_val_if_fail(filename, FALSE); + g_return_val_if_fail(contents || !length, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + g_return_val_if_fail(length >= -1, FALSE); + + if (length == -1) + length = strlen(contents); + + tmp_name = g_strdup_printf("%s.XXXXXX", filename); + fd = g_mkstemp_full(tmp_name, O_RDWR | O_CLOEXEC, mode); + if (fd < 0) { + return _get_contents_error_errno(error, out_errsv, "failed to create file %s", tmp_name); + } + + while (length > 0) { + s = write(fd, contents, length); + if (s < 0) { + errsv = NM_ERRNO_NATIVE(errno); + if (errsv == EINTR) + continue; + + nm_close(fd); + unlink(tmp_name); + return _get_contents_error(error, + errsv, + out_errsv, + "failed to write to file %s", + tmp_name); + } + + g_assert(s <= length); + + contents += s; + length -= s; + } + + /* If the final destination exists and is > 0 bytes, we want to sync the + * newly written file to ensure the data is on disk when we rename over + * the destination. Otherwise, if we get a system crash we can lose both + * the new and the old file on some filesystems. (I.E. those that don't + * guarantee the data is written to the disk before the metadata.) + */ + if (lstat(filename, &statbuf) == 0 && statbuf.st_size > 0) { + if (fsync(fd) != 0) { + errsv = NM_ERRNO_NATIVE(errno); + nm_close(fd); + unlink(tmp_name); + return _get_contents_error(error, errsv, out_errsv, "failed to fsync %s", tmp_name); + } + } + + nm_close(fd); + + if (rename(tmp_name, filename)) { + errsv = NM_ERRNO_NATIVE(errno); + unlink(tmp_name); + return _get_contents_error(error, + errsv, + out_errsv, + "failed rename %s to %s", + tmp_name, + filename); + } + + return TRUE; +} + +/** + * nm_utils_file_stat: + * @filename: the filename to stat. + * @out_st: (allow-none) (out): if given, this will be passed to stat(). + * + * Just wraps stat() and gives the errno number as function result instead + * of setting the errno (though, errno is also set). It's only for convenience + * with + * + * if (nm_utils_file_stat (filename, NULL) == -ENOENT) { + * } + * + * Returns: 0 on success a negative errno on failure. */ +int +nm_utils_file_stat(const char *filename, struct stat *out_st) +{ + struct stat st; + + if (stat(filename, out_st ?: &st) != 0) + return -NM_ERRNO_NATIVE(errno); + return 0; +} + +/** + * nm_utils_fd_read: + * @fd: the fd to read from. + * @out_string: (out): output string where read bytes will be stored. + * + * Returns: <0 on failure, which is -(errno). + * 0 on EOF. + * >0 on success, which is the number of bytes read. */ +gssize +nm_utils_fd_read(int fd, NMStrBuf *out_string) +{ + gsize buf_available; + gssize n_read; + int errsv; + + g_return_val_if_fail(fd >= 0, -1); + g_return_val_if_fail(out_string, -1); + + /* If the buffer size is 0, we allocate NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 (1000 bytes) + * the first time. Afterwards, the buffer grows exponentially. + * + * Note that with @buf_available, we always would read as much buffer as we actually + * have reserved. */ + nm_str_buf_maybe_expand(out_string, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE); + + buf_available = out_string->allocated - out_string->len; + + n_read = read(fd, &((nm_str_buf_get_str_unsafe(out_string))[out_string->len]), buf_available); + if (n_read < 0) { + errsv = errno; + return -NM_ERRNO_NATIVE(errsv); + } + + if (n_read > 0) { + nm_assert((gsize) n_read <= buf_available); + nm_str_buf_set_size(out_string, out_string->len + (gsize) n_read, TRUE, FALSE); + } + + return n_read; +} diff --git a/src/libnm-glib-aux/nm-io-utils.h b/src/libnm-glib-aux/nm-io-utils.h new file mode 100644 index 0000000000..8182f5cf60 --- /dev/null +++ b/src/libnm-glib-aux/nm-io-utils.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_IO_UTILS_H__ +#define __NM_IO_UTILS_H__ + +#include "nm-macros-internal.h" + +/*****************************************************************************/ + +/** + * NMUtilsFileGetContentsFlags: + * @NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE: no flag + * @NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET: if present, ensure that no + * data is left in memory. Essentially, it means to call nm_explicit_bzero() + * to not leave key material on the heap (when reading secrets). + */ +typedef enum { + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE = 0, + NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET = (1 << 0), +} NMUtilsFileGetContentsFlags; + +gboolean nm_utils_fd_get_contents(int fd, + gboolean close_fd, + gsize max_length, + NMUtilsFileGetContentsFlags flags, + char ** contents, + gsize * length, + int * out_errsv, + GError ** error); + +gboolean nm_utils_file_get_contents(int dirfd, + const char * filename, + gsize max_length, + NMUtilsFileGetContentsFlags flags, + char ** contents, + gsize * length, + int * out_errsv, + GError ** error); + +gboolean nm_utils_file_set_contents(const char *filename, + const char *contents, + gssize length, + mode_t mode, + int * out_errsv, + GError ** error); + +struct _NMStrBuf; + +gssize nm_utils_fd_read(int fd, struct _NMStrBuf *out_string); + +struct stat; + +int nm_utils_file_stat(const char *filename, struct stat *out_st); + +#endif /* __NM_IO_UTILS_H__ */ diff --git a/src/libnm-glib-aux/nm-jansson.h b/src/libnm-glib-aux/nm-jansson.h new file mode 100644 index 0000000000..6173a7ac61 --- /dev/null +++ b/src/libnm-glib-aux/nm-jansson.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_JANSSON_H__ +#define __NM_JANSSON_H__ + +/* you need to include at least "config.h" first, possibly "nm-default.h". */ + +#if WITH_JANSSON + + #include <jansson.h> + + /* Added in Jansson v2.8 */ + #ifndef json_object_foreach_safe + #define json_object_foreach_safe(object, n, key, value) \ + for (key = json_object_iter_key(json_object_iter(object)), \ + n = json_object_iter_next(object, json_object_key_to_iter(key)); \ + key && (value = json_object_iter_value(json_object_key_to_iter(key))); \ + key = json_object_iter_key(n), \ + n = json_object_iter_next(object, json_object_key_to_iter(key))) + #endif + +NM_AUTO_DEFINE_FCN0(json_t *, _nm_auto_decref_json, json_decref); + #define nm_auto_decref_json nm_auto(_nm_auto_decref_json) + +#endif /* WITH_JANSON */ + +#endif /* __NM_JANSSON_H__ */ diff --git a/src/libnm-glib-aux/nm-json-aux.c b/src/libnm-glib-aux/nm-json-aux.c new file mode 100644 index 0000000000..dc67d6d593 --- /dev/null +++ b/src/libnm-glib-aux/nm-json-aux.c @@ -0,0 +1,286 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 - 2019 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-json-aux.h" + +#include <dlfcn.h> + +/*****************************************************************************/ + +/* If RTLD_DEEPBIND isn't available just ignore it. This can cause problems + * with jansson, json-glib, and cjson symbols clashing (and as such crashing the + * program). But that needs to be fixed by the json libraries, and it is by adding + * symbol versioning in recent versions. */ +#ifndef RTLD_DEEPBIND + #define RTLD_DEEPBIND 0 +#endif + +/*****************************************************************************/ + +static void +_gstr_append_string_len(GString *gstr, const char *str, gsize len) +{ + g_string_append_c(gstr, '\"'); + + while (len > 0) { + gsize n; + const char *end; + gboolean valid; + + nm_assert(len > 0); + + valid = g_utf8_validate(str, len, &end); + + nm_assert(end && end >= str && end <= &str[len]); + + if (end > str) { + const char *s; + + for (s = str; s < end; s++) { + nm_assert(s[0] != '\0'); + + if (s[0] < 0x20) { + const char *text; + + switch (s[0]) { + case '\\': + text = "\\\\"; + break; + case '\"': + text = "\\\""; + break; + case '\b': + text = "\\b"; + break; + case '\f': + text = "\\f"; + break; + case '\n': + text = "\\n"; + break; + case '\r': + text = "\\r"; + break; + case '\t': + text = "\\t"; + break; + default: + g_string_append_printf(gstr, "\\u%04X", (guint) s[0]); + continue; + } + g_string_append(gstr, text); + continue; + } + + if (NM_IN_SET(s[0], '\\', '\"')) + g_string_append_c(gstr, '\\'); + g_string_append_c(gstr, s[0]); + } + } else + nm_assert(!valid); + + if (valid) { + nm_assert(end == &str[len]); + break; + } + + nm_assert(end < &str[len]); + + if (end[0] == '\0') { + /* there is a NUL byte in the string. Technically this is valid UTF-8, so we + * encode it there. However, this will likely result in a truncated string when + * parsing. */ + g_string_append(gstr, "\\u0000"); + } else { + /* the character is not valid UTF-8. There is nothing we can do about it, because + * JSON can only contain UTF-8 and even the escape sequences can only escape Unicode + * codepoints (but not binary). + * + * The argument is not a string (in any known encoding), hence we cannot represent + * it as a JSON string (which are unicode strings). + * + * Print an underscore instead of the invalid char :) */ + g_string_append_c(gstr, '_'); + } + + n = str - end; + nm_assert(n < len); + n++; + str += n; + len -= n; + } + + g_string_append_c(gstr, '\"'); +} + +void +nm_json_gstr_append_string_len(GString *gstr, const char *str, gsize n) +{ + g_return_if_fail(gstr); + + _gstr_append_string_len(gstr, str, n); +} + +void +nm_json_gstr_append_string(GString *gstr, const char *str) +{ + g_return_if_fail(gstr); + + if (!str) + g_string_append(gstr, "null"); + else + _gstr_append_string_len(gstr, str, strlen(str)); +} + +void +nm_json_gstr_append_obj_name(GString *gstr, const char *key, char start_container) +{ + g_return_if_fail(gstr); + g_return_if_fail(key); + + nm_json_gstr_append_string(gstr, key); + + if (start_container != '\0') { + nm_assert(NM_IN_SET(start_container, '[', '{')); + g_string_append_printf(gstr, ": %c ", start_container); + } else + g_string_append(gstr, ": "); +} + +/*****************************************************************************/ + +typedef struct { + NMJsonVt vt; + void * dl_handle; +} NMJsonVtInternal; + +static NMJsonVtInternal * +_nm_json_vt_internal_load(void) +{ + NMJsonVtInternal *v; + const char * soname; + void * handle; + + v = g_new0(NMJsonVtInternal, 1); + +#if WITH_JANSSON && defined(JANSSON_SONAME) + G_STATIC_ASSERT_EXPR(NM_STRLEN(JANSSON_SONAME) > 0); + nm_assert(strlen(JANSSON_SONAME) > 0); + soname = JANSSON_SONAME; +#elif !WITH_JANSSON && !defined(JANSSON_SONAME) + soname = NULL; +#else + #error "WITH_JANSON and JANSSON_SONAME are defined inconsistently." +#endif + + if (!soname) + return v; + + handle = dlopen(soname, + RTLD_LAZY | RTLD_LOCAL | RTLD_NODELETE +#if !defined(ASAN_BUILD) + | RTLD_DEEPBIND +#endif + | 0); + if (!handle) + return v; + +#define TRY_BIND_SYMBOL(symbol) \ + G_STMT_START \ + { \ + void *_sym = dlsym(handle, #symbol); \ + \ + if (!_sym) \ + goto fail_symbol; \ + v->vt.nm_##symbol = _sym; \ + } \ + G_STMT_END + + TRY_BIND_SYMBOL(json_array); + TRY_BIND_SYMBOL(json_array_append_new); + TRY_BIND_SYMBOL(json_array_get); + TRY_BIND_SYMBOL(json_array_size); + TRY_BIND_SYMBOL(json_delete); + TRY_BIND_SYMBOL(json_dumps); + TRY_BIND_SYMBOL(json_false); + TRY_BIND_SYMBOL(json_integer); + TRY_BIND_SYMBOL(json_integer_value); + TRY_BIND_SYMBOL(json_loads); + TRY_BIND_SYMBOL(json_object); + TRY_BIND_SYMBOL(json_object_del); + TRY_BIND_SYMBOL(json_object_get); + TRY_BIND_SYMBOL(json_object_iter); + TRY_BIND_SYMBOL(json_object_iter_key); + TRY_BIND_SYMBOL(json_object_iter_next); + TRY_BIND_SYMBOL(json_object_iter_value); + TRY_BIND_SYMBOL(json_object_key_to_iter); + TRY_BIND_SYMBOL(json_object_set_new); + TRY_BIND_SYMBOL(json_object_size); + TRY_BIND_SYMBOL(json_string); + TRY_BIND_SYMBOL(json_string_value); + TRY_BIND_SYMBOL(json_true); + + v->vt.loaded = TRUE; + v->dl_handle = handle; + return v; + +fail_symbol: + dlclose(&handle); + *v = (NMJsonVtInternal){}; + return v; +} + +const NMJsonVt *_nm_json_vt_ptr = NULL; + +const NMJsonVt * +_nm_json_vt_init(void) +{ + NMJsonVtInternal *v; + +again: + v = g_atomic_pointer_get((gpointer *) &_nm_json_vt_ptr); + if (G_UNLIKELY(!v)) { + v = _nm_json_vt_internal_load(); + if (!g_atomic_pointer_compare_and_exchange((gpointer *) &_nm_json_vt_ptr, NULL, v)) { + if (v->dl_handle) + dlclose(v->dl_handle); + g_free(v); + goto again; + } + + /* we transfer ownership. */ + } + + nm_assert(v && v == g_atomic_pointer_get((gpointer *) &_nm_json_vt_ptr)); + return &v->vt; +} + +const NMJsonVt * +nmtst_json_vt_reset(gboolean loaded) +{ + NMJsonVtInternal *v_old; + NMJsonVtInternal *v; + + v_old = g_atomic_pointer_get((gpointer *) &_nm_json_vt_ptr); + + if (!loaded) { + /* load a fake instance for testing. */ + v = g_new0(NMJsonVtInternal, 1); + } else + v = _nm_json_vt_internal_load(); + + if (!g_atomic_pointer_compare_and_exchange((gpointer *) &_nm_json_vt_ptr, v_old, v)) + g_assert_not_reached(); + + if (v_old) { + if (v_old->dl_handle) + dlclose(v_old->dl_handle); + g_free((gpointer *) v_old); + } + + return v->vt.loaded ? &v->vt : NULL; +} diff --git a/src/libnm-glib-aux/nm-json-aux.h b/src/libnm-glib-aux/nm-json-aux.h new file mode 100644 index 0000000000..99759a8d27 --- /dev/null +++ b/src/libnm-glib-aux/nm-json-aux.h @@ -0,0 +1,312 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 - 2019 Red Hat, Inc. + */ + +#ifndef __NM_JSON_AUX_H__ +#define __NM_JSON_AUX_H__ + +#include "nm-value-type.h" + +/*****************************************************************************/ + +static inline GString * +nm_json_gstr_append_delimiter(GString *gstr) +{ + g_string_append(gstr, ", "); + return gstr; +} + +void nm_json_gstr_append_string_len(GString *gstr, const char *str, gsize n); + +void nm_json_gstr_append_string(GString *gstr, const char *str); + +static inline void +nm_json_gstr_append_bool(GString *gstr, gboolean v) +{ + g_string_append(gstr, v ? "true" : "false"); +} + +static inline void +nm_json_gstr_append_int64(GString *gstr, gint64 v) +{ + g_string_append_printf(gstr, "%" G_GINT64_FORMAT, v); +} + +void nm_json_gstr_append_obj_name(GString *gstr, const char *key, char start_container); + +/*****************************************************************************/ + +#define NM_JSON_REJECT_DUPLICATES 0x1 + +typedef enum { + NM_JSON_OBJECT, + NM_JSON_ARRAY, + NM_JSON_STRING, + NM_JSON_INTEGER, + NM_JSON_REAL, + NM_JSON_TRUE, + NM_JSON_FALSE, + NM_JSON_NULL, +} nm_json_type; + +typedef struct nm_json_t { + nm_json_type type; + volatile size_t refcount; +} nm_json_t; + +typedef long long nm_json_int_t; + +#define NM_JSON_ERROR_TEXT_LENGTH 160 +#define NM_JSON_ERROR_SOURCE_LENGTH 80 + +typedef struct nm_json_error_t { + int line; + int column; + int position; + char source[NM_JSON_ERROR_SOURCE_LENGTH]; + char text[NM_JSON_ERROR_TEXT_LENGTH]; +} nm_json_error_t; + +typedef struct { + gboolean loaded; + char *(*nm_json_dumps)(const nm_json_t *json, size_t flags); + const char *(*nm_json_object_iter_key)(void *iter); + const char *(*nm_json_string_value)(const nm_json_t *json); + int (*nm_json_array_append_new)(nm_json_t *json, nm_json_t *value); + int (*nm_json_object_del)(nm_json_t *json, const char *key); + int (*nm_json_object_set_new)(nm_json_t *json, const char *key, nm_json_t *value); + nm_json_int_t (*nm_json_integer_value)(const nm_json_t *json); + nm_json_t *(*nm_json_array)(void); + nm_json_t *(*nm_json_array_get)(const nm_json_t *json, size_t index); + nm_json_t *(*nm_json_false)(void); + nm_json_t *(*nm_json_integer)(nm_json_int_t value); + nm_json_t *(*nm_json_loads)(const char *string, size_t flags, nm_json_error_t *error); + nm_json_t *(*nm_json_object)(void); + nm_json_t *(*nm_json_object_get)(const nm_json_t *json, const char *key); + nm_json_t *(*nm_json_object_iter_value)(void *); + nm_json_t *(*nm_json_string)(const char *value); + nm_json_t *(*nm_json_true)(void); + size_t (*nm_json_array_size)(const nm_json_t *json); + size_t (*nm_json_object_size)(const nm_json_t *json); + void (*nm_json_delete)(nm_json_t *json); + void *(*nm_json_object_iter)(nm_json_t *json); + void *(*nm_json_object_iter_next)(nm_json_t *json, void *iter); + void *(*nm_json_object_key_to_iter)(const char *key); +} NMJsonVt; + +extern const NMJsonVt *_nm_json_vt_ptr; + +const NMJsonVt *_nm_json_vt_init(void); + +static inline const NMJsonVt * +_nm_json_vt(void) +{ + const NMJsonVt *vt; + + vt = g_atomic_pointer_get((gpointer *) &_nm_json_vt_ptr); + if (G_UNLIKELY(!vt)) { + vt = _nm_json_vt_init(); + nm_assert(vt); + } + return vt; +} + +static inline const NMJsonVt * +nm_json_vt(void) +{ + const NMJsonVt *vt; + + vt = _nm_json_vt(); + return vt->loaded ? vt : NULL; +} + +static inline const NMJsonVt * +nm_json_vt_assert(void) +{ + const NMJsonVt *vt; + + vt = _nm_json_vt(); + nm_assert(vt->loaded); + return vt; +} + +const NMJsonVt *nmtst_json_vt_reset(gboolean loaded); + +/*****************************************************************************/ + +#define nm_json_boolean(vt, val) ((val) ? (vt)->nm_json_true() : (vt)->nm_json_false()) + +static inline void +nm_json_decref(const NMJsonVt *vt, nm_json_t *json) +{ + /* Our ref-counting is not threadsafe, unlike libjansson's. But we never + * share one json_t instance between threads, and if we would, we would very likely + * wrap a mutex around it. */ + if (json && json->refcount != (size_t) -1 && --json->refcount == 0) + vt->nm_json_delete(json); +} + +static inline void +_nm_auto_decref_json(nm_json_t **p_json) +{ + if (*p_json && (*p_json)->refcount != (size_t) -1 && --(*p_json)->refcount == 0) + nm_json_vt()->nm_json_delete(*p_json); +} + +#define nm_auto_decref_json nm_auto(_nm_auto_decref_json) + +/*****************************************************************************/ + +/* the following are implemented as pure macros in jansson.h. + * They can be used directly, however, add a nm_json* variant, + * to make it explict we don't accidentally use jansson ABI. */ + +#define nm_json_typeof(json) ((json)->type) +#define nm_json_is_object(json) ((json) && nm_json_typeof(json) == NM_JSON_OBJECT) +#define nm_json_is_array(json) ((json) && nm_json_typeof(json) == NM_JSON_ARRAY) +#define nm_json_is_string(json) ((json) && nm_json_typeof(json) == NM_JSON_STRING) +#define nm_json_is_integer(json) ((json) && nm_json_typeof(json) == NM_JSON_INTEGER) +#define nm_json_is_real(json) ((json) && nm_json_typeof(json) == NM_JSON_REAL) +#define nm_json_is_number(json) (nm_json_is_integer(json) || nm_json_is_real(json)) +#define nm_json_is_true(json) ((json) && nm_json_typeof(json) == NM_JSON_TRUE) +#define nm_json_is_false(json) ((json) && nm_json_typeof(json) == NM_JSON_FALSE) +#define nm_json_boolean_value nm_json_is_true +#define nm_json_is_boolean(json) (nm_json_is_true(json) || nm_json_is_false(json)) +#define nm_json_is_null(json) ((json) && nm_json_typeof(json) == NM_JSON_NULL) + +#define nm_json_array_foreach(vt, array, index, value) \ + for (index = 0; \ + index < vt->nm_json_array_size(array) && (value = vt->nm_json_array_get(array, index)); \ + index++) + +#define nm_json_object_foreach(vt, object, key, value) \ + for (key = vt->nm_json_object_iter_key(vt->nm_json_object_iter(object)); \ + key && (value = vt->nm_json_object_iter_value(vt->nm_json_object_key_to_iter(key))); \ + key = vt->nm_json_object_iter_key( \ + vt->nm_json_object_iter_next(object, vt->nm_json_object_key_to_iter(key)))) + +/*****************************************************************************/ + +static inline int +nm_jansson_json_as_bool(const nm_json_t *elem, bool *out_val) +{ + if (!elem) + return 0; + + if (!nm_json_is_boolean(elem)) + return -EINVAL; + + NM_SET_OUT(out_val, nm_json_boolean_value(elem)); + return 1; +} + +static inline int +nm_jansson_json_as_int32(const NMJsonVt *vt, const nm_json_t *elem, gint32 *out_val) +{ + nm_json_int_t v; + + if (!elem) + return 0; + + if (!nm_json_is_integer(elem)) + return -EINVAL; + + v = vt->nm_json_integer_value(elem); + if (v < (gint64) G_MININT32 || v > (gint64) G_MAXINT32) + return -ERANGE; + + NM_SET_OUT(out_val, v); + return 1; +} + +static inline int +nm_jansson_json_as_int(const NMJsonVt *vt, const nm_json_t *elem, int *out_val) +{ + nm_json_int_t v; + + if (!elem) + return 0; + + if (!nm_json_is_integer(elem)) + return -EINVAL; + + v = vt->nm_json_integer_value(elem); + if (v < (gint64) G_MININT || v > (gint64) G_MAXINT) + return -ERANGE; + + NM_SET_OUT(out_val, v); + return 1; +} + +static inline int +nm_jansson_json_as_string(const NMJsonVt *vt, const nm_json_t *elem, const char **out_val) +{ + if (!elem) + return 0; + + if (!nm_json_is_string(elem)) + return -EINVAL; + + NM_SET_OUT(out_val, vt->nm_json_string_value(elem)); + return 1; +} + +/*****************************************************************************/ + +#ifdef NM_VALUE_TYPE_DEFINE_FUNCTIONS + +static inline void +nm_value_type_to_json(NMValueType value_type, GString *gstr, gconstpointer p_field) +{ + nm_assert(p_field); + nm_assert(gstr); + + switch (value_type) { + case NM_VALUE_TYPE_BOOL: + nm_json_gstr_append_bool(gstr, *((const bool *) p_field)); + return; + case NM_VALUE_TYPE_INT32: + nm_json_gstr_append_int64(gstr, *((const gint32 *) p_field)); + return; + case NM_VALUE_TYPE_INT: + nm_json_gstr_append_int64(gstr, *((const int *) p_field)); + return; + case NM_VALUE_TYPE_STRING: + nm_json_gstr_append_string(gstr, *((const char *const *) p_field)); + return; + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached(); +} + +static inline gboolean +nm_value_type_from_json(const NMJsonVt * vt, + NMValueType value_type, + const nm_json_t *elem, + gpointer out_val) +{ + switch (value_type) { + case NM_VALUE_TYPE_BOOL: + return (nm_jansson_json_as_bool(elem, out_val) > 0); + case NM_VALUE_TYPE_INT32: + return (nm_jansson_json_as_int32(vt, elem, out_val) > 0); + case NM_VALUE_TYPE_INT: + return (nm_jansson_json_as_int(vt, elem, out_val) > 0); + + /* warning: this overwrites/leaks the previous value. You better have *out_val + * point to uninitialized memory or NULL. */ + case NM_VALUE_TYPE_STRING: + return (nm_jansson_json_as_string(vt, elem, out_val) > 0); + + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached(); + return FALSE; +} + +#endif /* NM_VALUE_TYPE_DEFINE_FUNCTIONS */ + +#endif /* __NM_JSON_AUX_H__ */ diff --git a/src/libnm-glib-aux/nm-keyfile-aux.c b/src/libnm-glib-aux/nm-keyfile-aux.c new file mode 100644 index 0000000000..75abe53848 --- /dev/null +++ b/src/libnm-glib-aux/nm-keyfile-aux.c @@ -0,0 +1,373 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-keyfile-aux.h" + +#include <syslog.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "nm-io-utils.h" + +/*****************************************************************************/ + +struct _NMKeyFileDB { + NMKeyFileDBLogFcn log_fcn; + NMKeyFileDBGotDirtyFcn got_dirty_fcn; + gpointer user_data; + const char * group_name; + GKeyFile * kf; + guint ref_count; + + bool is_started : 1; + bool dirty : 1; + bool destroyed : 1; + + char filename[]; +}; + +#define _NMLOG(self, syslog_level, fmt, ...) \ + G_STMT_START \ + { \ + NMKeyFileDB *_self = (self); \ + \ + nm_assert(_self); \ + nm_assert(!_self->destroyed); \ + \ + if (_self->log_fcn) { \ + _self->log_fcn(_self, (syslog_level), _self->user_data, "" fmt "", ##__VA_ARGS__); \ + }; \ + } \ + G_STMT_END + +#define _LOGD(...) _NMLOG(self, LOG_DEBUG, __VA_ARGS__) + +static gboolean +_IS_KEY_FILE_DB(NMKeyFileDB *self, gboolean require_is_started, gboolean allow_destroyed) +{ + if (self == NULL) + return FALSE; + if (self->ref_count <= 0) { + nm_assert_not_reached(); + return FALSE; + } + if (require_is_started && !self->is_started) + return FALSE; + if (!allow_destroyed && self->destroyed) + return FALSE; + return TRUE; +} + +/*****************************************************************************/ + +NMKeyFileDB * +nm_key_file_db_new(const char * filename, + const char * group_name, + NMKeyFileDBLogFcn log_fcn, + NMKeyFileDBGotDirtyFcn got_dirty_fcn, + gpointer user_data) +{ + NMKeyFileDB *self; + gsize l_filename; + gsize l_group; + + g_return_val_if_fail(filename && filename[0], NULL); + g_return_val_if_fail(group_name && group_name[0], NULL); + + l_filename = strlen(filename); + l_group = strlen(group_name); + + self = g_malloc0(sizeof(NMKeyFileDB) + l_filename + 1 + l_group + 1); + self->ref_count = 1; + self->log_fcn = log_fcn; + self->got_dirty_fcn = got_dirty_fcn; + self->user_data = user_data; + self->kf = g_key_file_new(); + g_key_file_set_list_separator(self->kf, ','); + memcpy(self->filename, filename, l_filename + 1); + self->group_name = &self->filename[l_filename + 1]; + memcpy((char *) self->group_name, group_name, l_group + 1); + + return self; +} + +NMKeyFileDB * +nm_key_file_db_ref(NMKeyFileDB *self) +{ + if (!self) + return NULL; + + g_return_val_if_fail(_IS_KEY_FILE_DB(self, FALSE, TRUE), NULL); + + nm_assert(self->ref_count < G_MAXUINT); + self->ref_count++; + return self; +} + +void +nm_key_file_db_unref(NMKeyFileDB *self) +{ + if (!self) + return; + + g_return_if_fail(_IS_KEY_FILE_DB(self, FALSE, TRUE)); + + if (--self->ref_count > 0) + return; + + g_key_file_unref(self->kf); + + g_free(self); +} + +/* destroy() is like unref, but it also makes the instance unusable. + * All changes afterwards fail with an assertion. + * + * The point is that NMKeyFileDB is ref-counted in principle. But there + * is a primary owner who also provides the log_fcn(). + * + * When the primary owner goes out of scope and gives up the reference, it does + * not want to receive any log notifications anymore. + * + * The way NMKeyFileDB is intended to be used is in a very strict context: + * NMSettings owns the NMKeyFileDB instance and receives logging notifications. + * It's also the last one to persist the data to disk. Afterwards, no other user + * is supposed to be around and do anything with NMKeyFileDB. But since NMKeyFileDB + * is ref-counted it's hard to ensure that this is truly honored. So we start + * asserting at that point. + */ +void +nm_key_file_db_destroy(NMKeyFileDB *self) +{ + if (!self) + return; + + g_return_if_fail(_IS_KEY_FILE_DB(self, FALSE, FALSE)); + g_return_if_fail(!self->destroyed); + + self->destroyed = TRUE; + nm_key_file_db_unref(self); +} + +/*****************************************************************************/ + +/* nm_key_file_db_start() is supposed to be called right away, after creating the + * instance. + * + * It's not done as separate step after nm_key_file_db_new(), because we want to log, + * and the log_fcn returns the self pointer (which we should not expose before + * nm_key_file_db_new() returns. */ +void +nm_key_file_db_start(NMKeyFileDB *self) +{ + gs_free char *contents = NULL; + gsize contents_len; + gs_free_error GError *error = NULL; + + g_return_if_fail(_IS_KEY_FILE_DB(self, FALSE, FALSE)); + g_return_if_fail(!self->is_started); + + self->is_started = TRUE; + + if (!nm_utils_file_get_contents(-1, + self->filename, + 20 * 1024 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &contents, + &contents_len, + NULL, + &error)) { + _LOGD("failed to read \"%s\": %s", self->filename, error->message); + return; + } + + if (!g_key_file_load_from_data(self->kf, + contents, + contents_len, + G_KEY_FILE_KEEP_COMMENTS, + &error)) { + _LOGD("failed to load keyfile \"%s\": %s", self->filename, error->message); + return; + } + + _LOGD("loaded keyfile-db for \"%s\"", self->filename); +} + +/*****************************************************************************/ + +const char * +nm_key_file_db_get_filename(NMKeyFileDB *self) +{ + g_return_val_if_fail(_IS_KEY_FILE_DB(self, FALSE, TRUE), NULL); + + return self->filename; +} + +gboolean +nm_key_file_db_is_dirty(NMKeyFileDB *self) +{ + g_return_val_if_fail(_IS_KEY_FILE_DB(self, FALSE, TRUE), FALSE); + + return self->dirty; +} + +/*****************************************************************************/ + +char * +nm_key_file_db_get_value(NMKeyFileDB *self, const char *key) +{ + g_return_val_if_fail(_IS_KEY_FILE_DB(self, TRUE, TRUE), NULL); + + return g_key_file_get_value(self->kf, self->group_name, key, NULL); +} + +char ** +nm_key_file_db_get_string_list(NMKeyFileDB *self, const char *key, gsize *out_len) +{ + g_return_val_if_fail(_IS_KEY_FILE_DB(self, TRUE, TRUE), NULL); + + return g_key_file_get_string_list(self->kf, self->group_name, key, out_len, NULL); +} + +/*****************************************************************************/ + +static void +_got_dirty(NMKeyFileDB *self, const char *key) +{ + nm_assert(_IS_KEY_FILE_DB(self, TRUE, FALSE)); + nm_assert(!self->dirty); + + _LOGD("updated entry for %s.%s", self->group_name, key); + + self->dirty = TRUE; + if (self->got_dirty_fcn) + self->got_dirty_fcn(self, self->user_data); +} + +/*****************************************************************************/ + +void +nm_key_file_db_remove_key(NMKeyFileDB *self, const char *key) +{ + gboolean got_dirty = FALSE; + + g_return_if_fail(_IS_KEY_FILE_DB(self, TRUE, FALSE)); + + if (!key) + return; + + if (!self->dirty) { + gs_free_error GError *error = NULL; + + g_key_file_has_key(self->kf, self->group_name, key, &error); + got_dirty = (error != NULL); + } + g_key_file_remove_key(self->kf, self->group_name, key, NULL); + + if (got_dirty) + _got_dirty(self, key); +} + +void +nm_key_file_db_set_value(NMKeyFileDB *self, const char *key, const char *value) +{ + gs_free char *old_value = NULL; + gboolean got_dirty = FALSE; + + g_return_if_fail(_IS_KEY_FILE_DB(self, TRUE, FALSE)); + g_return_if_fail(key); + + if (!value) { + nm_key_file_db_remove_key(self, key); + return; + } + + if (!self->dirty) { + gs_free_error GError *error = NULL; + + old_value = g_key_file_get_value(self->kf, self->group_name, key, &error); + if (error) + got_dirty = TRUE; + } + + g_key_file_set_value(self->kf, self->group_name, key, value); + + if (!self->dirty && !got_dirty) { + gs_free_error GError *error = NULL; + gs_free char * new_value = NULL; + + new_value = g_key_file_get_value(self->kf, self->group_name, key, &error); + if (error || !new_value || !nm_streq0(old_value, new_value)) + got_dirty = TRUE; + } + + if (got_dirty) + _got_dirty(self, key); +} + +void +nm_key_file_db_set_string_list(NMKeyFileDB * self, + const char * key, + const char *const *value, + gssize len) +{ + gs_free char *old_value = NULL; + gboolean got_dirty = FALSE; + + g_return_if_fail(_IS_KEY_FILE_DB(self, TRUE, FALSE)); + g_return_if_fail(key); + + if (!value) { + nm_key_file_db_remove_key(self, key); + return; + } + + if (!self->dirty) { + gs_free_error GError *error = NULL; + + old_value = g_key_file_get_value(self->kf, self->group_name, key, &error); + if (error) + got_dirty = TRUE; + } + + if (len < 0) + len = NM_PTRARRAY_LEN(value); + + g_key_file_set_string_list(self->kf, self->group_name, key, value, len); + + if (!self->dirty && !got_dirty) { + gs_free_error GError *error = NULL; + gs_free char * new_value = NULL; + + new_value = g_key_file_get_value(self->kf, self->group_name, key, &error); + if (error || !new_value || !nm_streq0(old_value, new_value)) + got_dirty = TRUE; + } + + if (got_dirty) + _got_dirty(self, key); +} + +/*****************************************************************************/ + +void +nm_key_file_db_to_file(NMKeyFileDB *self, gboolean force) +{ + gs_free_error GError *error = NULL; + + g_return_if_fail(_IS_KEY_FILE_DB(self, TRUE, FALSE)); + + if (!force && !self->dirty) + return; + + self->dirty = FALSE; + + if (!g_key_file_save_to_file(self->kf, self->filename, &error)) { + _LOGD("failure to write keyfile \"%s\": %s", self->filename, error->message); + } else + _LOGD("write keyfile: \"%s\"", self->filename); +} diff --git a/src/libnm-glib-aux/nm-keyfile-aux.h b/src/libnm-glib-aux/nm-keyfile-aux.h new file mode 100644 index 0000000000..72d2f418f9 --- /dev/null +++ b/src/libnm-glib-aux/nm-keyfile-aux.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#ifndef __NM_KEYFILE_AUX_H__ +#define __NM_KEYFILE_AUX_H__ + +/*****************************************************************************/ + +typedef struct _NMKeyFileDB NMKeyFileDB; + +typedef void (*NMKeyFileDBLogFcn)(NMKeyFileDB *self, + int syslog_level, + gpointer user_data, + const char * fmt, + ...) G_GNUC_PRINTF(4, 5); + +typedef void (*NMKeyFileDBGotDirtyFcn)(NMKeyFileDB *self, gpointer user_data); + +NMKeyFileDB *nm_key_file_db_new(const char * filename, + const char * group, + NMKeyFileDBLogFcn log_fcn, + NMKeyFileDBGotDirtyFcn got_dirty_fcn, + gpointer user_data); + +void nm_key_file_db_start(NMKeyFileDB *self); + +NMKeyFileDB *nm_key_file_db_ref(NMKeyFileDB *self); +void nm_key_file_db_unref(NMKeyFileDB *self); + +void nm_key_file_db_destroy(NMKeyFileDB *self); + +const char *nm_key_file_db_get_filename(NMKeyFileDB *self); + +gboolean nm_key_file_db_is_dirty(NMKeyFileDB *self); + +char *nm_key_file_db_get_value(NMKeyFileDB *self, const char *key); + +char **nm_key_file_db_get_string_list(NMKeyFileDB *self, const char *key, gsize *out_len); + +void nm_key_file_db_remove_key(NMKeyFileDB *self, const char *key); + +void nm_key_file_db_set_value(NMKeyFileDB *self, const char *key, const char *value); + +void nm_key_file_db_set_string_list(NMKeyFileDB * self, + const char * key, + const char *const *value, + gssize len); + +void nm_key_file_db_to_file(NMKeyFileDB *self, gboolean force); + +/*****************************************************************************/ + +#endif /* __NM_KEYFILE_AUX_H__ */ diff --git a/src/libnm-glib-aux/nm-logging-base.c b/src/libnm-glib-aux/nm-logging-base.c new file mode 100644 index 0000000000..24d97adc8d --- /dev/null +++ b/src/libnm-glib-aux/nm-logging-base.c @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-logging-base.h" + +#include <syslog.h> + +/*****************************************************************************/ + +const LogLevelDesc level_desc[_LOGL_N] = { + [LOGL_TRACE] = + { + "TRACE", + "<trace>", + LOG_DEBUG, + G_LOG_LEVEL_DEBUG, + }, + [LOGL_DEBUG] = + { + "DEBUG", + "<debug>", + LOG_DEBUG, + G_LOG_LEVEL_DEBUG, + }, + [LOGL_INFO] = + { + "INFO", + "<info>", + LOG_INFO, + G_LOG_LEVEL_INFO, + }, + [LOGL_WARN] = + { + "WARN", + "<warn>", + LOG_WARNING, + G_LOG_LEVEL_MESSAGE, + }, + [LOGL_ERR] = + { + "ERR", + "<error>", + LOG_ERR, + G_LOG_LEVEL_MESSAGE, + }, + [_LOGL_OFF] = + { + "OFF", + NULL, + 0, + 0, + }, + [_LOGL_KEEP] = + { + "KEEP", + NULL, + 0, + 0, + }, +}; + +gboolean +_nm_log_parse_level(const char *level, NMLogLevel *out_level) +{ + int i; + + if (!level) + return FALSE; + + for (i = 0; i < (int) G_N_ELEMENTS(level_desc); i++) { + if (!g_ascii_strcasecmp(level_desc[i].name, level)) { + NM_SET_OUT(out_level, i); + return TRUE; + } + } + + return FALSE; +} diff --git a/src/libnm-glib-aux/nm-logging-base.h b/src/libnm-glib-aux/nm-logging-base.h new file mode 100644 index 0000000000..d9ac03c796 --- /dev/null +++ b/src/libnm-glib-aux/nm-logging-base.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NM_LOGGING_BASE_H__ +#define __NM_LOGGING_BASE_H__ + +#include "nm-logging-fwd.h" + +typedef struct { + const char *name; + const char *level_str; + + /* nm-logging uses syslog internally. Note that the three most-verbose syslog levels + * are LOG_DEBUG, LOG_INFO and LOG_NOTICE. Journal already highlights LOG_NOTICE + * as special. + * + * On the other hand, we have three levels LOGL_TRACE, LOGL_DEBUG and LOGL_INFO, + * which are regular messages not to be highlighted. For that reason, we must map + * LOGL_TRACE and LOGL_DEBUG both to syslog level LOG_DEBUG. */ + int syslog_level; + + GLogLevelFlags g_log_level; +} LogLevelDesc; + +extern const LogLevelDesc level_desc[_LOGL_N]; + +gboolean _nm_log_parse_level(const char *level, NMLogLevel *out_level); + +#endif /* __NM_LOGGING_BASE_H__ */ diff --git a/src/libnm-glib-aux/nm-logging-fwd.h b/src/libnm-glib-aux/nm-logging-fwd.h new file mode 100644 index 0000000000..df0bb161e1 --- /dev/null +++ b/src/libnm-glib-aux/nm-logging-fwd.h @@ -0,0 +1,308 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2006 - 2018 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef __NM_LOGGING_FWD_H__ +#define __NM_LOGGING_FWD_H__ + +/* Log domains */ + +typedef enum { /*< skip >*/ + LOGD_NONE = 0LL, + LOGD_PLATFORM = (1LL << 0), /* Platform services */ + LOGD_RFKILL = (1LL << 1), + LOGD_ETHER = (1LL << 2), + LOGD_WIFI = (1LL << 3), + LOGD_BT = (1LL << 4), + LOGD_MB = (1LL << 5), /* mobile broadband */ + LOGD_DHCP4 = (1LL << 6), + LOGD_DHCP6 = (1LL << 7), + LOGD_PPP = (1LL << 8), + LOGD_WIFI_SCAN = (1LL << 9), + LOGD_IP4 = (1LL << 10), + LOGD_IP6 = (1LL << 11), + LOGD_AUTOIP4 = (1LL << 12), + LOGD_DNS = (1LL << 13), + LOGD_VPN = (1LL << 14), + LOGD_SHARING = (1LL << 15), /* Connection sharing/dnsmasq */ + LOGD_SUPPLICANT = (1LL << 16), /* Wi-Fi and 802.1x */ + LOGD_AGENTS = (1LL << 17), /* Secret agents */ + LOGD_SETTINGS = (1LL << 18), /* Settings */ + LOGD_SUSPEND = (1LL << 19), /* Suspend/Resume */ + LOGD_CORE = (1LL << 20), /* Core daemon and policy stuff */ + LOGD_DEVICE = (1LL << 21), /* Device state and activation */ + LOGD_OLPC = (1LL << 22), + LOGD_INFINIBAND = (1LL << 23), + LOGD_FIREWALL = (1LL << 24), + LOGD_ADSL = (1LL << 25), + LOGD_BOND = (1LL << 26), + LOGD_VLAN = (1LL << 27), + LOGD_BRIDGE = (1LL << 28), + LOGD_DBUS_PROPS = (1LL << 29), + LOGD_TEAM = (1LL << 30), + LOGD_CONCHECK = (1LL << 31), + LOGD_DCB = (1LL << 32), /* Data Center Bridging */ + LOGD_DISPATCH = (1LL << 33), + LOGD_AUDIT = (1LL << 34), + LOGD_SYSTEMD = (1LL << 35), + LOGD_VPN_PLUGIN = (1LL << 36), + LOGD_PROXY = (1LL << 37), + + __LOGD_MAX, + LOGD_ALL = (((__LOGD_MAX - 1LL) << 1) - 1LL), + LOGD_DEFAULT = LOGD_ALL & ~(LOGD_DBUS_PROPS | LOGD_WIFI_SCAN | LOGD_VPN_PLUGIN | 0), + + /* aliases: */ + LOGD_DHCP = LOGD_DHCP4 | LOGD_DHCP6, + LOGD_IP = LOGD_IP4 | LOGD_IP6, + +#define LOGD_DHCPX(is_ipv4) ((is_ipv4) ? LOGD_DHCP4 : LOGD_DHCP6) +#define LOGD_IPX(is_ipv4) ((is_ipv4) ? LOGD_IP4 : LOGD_IP6) + +} NMLogDomain; + +/* Log levels */ +typedef enum { /*< skip >*/ + LOGL_TRACE, + LOGL_DEBUG, + LOGL_INFO, + LOGL_WARN, + LOGL_ERR, + + _LOGL_N_REAL, /* the number of actual logging levels */ + + _LOGL_OFF = _LOGL_N_REAL, /* special logging level that is always disabled. */ + _LOGL_KEEP, /* special logging level to indicate that the logging level should not be changed. */ + + _LOGL_N, /* the number of logging levels including "OFF" */ +} NMLogLevel; + +gboolean _nm_log_enabled_impl(gboolean mt_require_locking, NMLogLevel level, NMLogDomain domain); + +void _nm_log_impl(const char *file, + guint line, + const char *func, + gboolean mt_require_locking, + NMLogLevel level, + NMLogDomain domain, + int error, + const char *ifname, + const char *con_uuid, + const char *fmt, + ...) _nm_printf(10, 11); + +static inline NMLogLevel +nm_log_level_from_syslog(int syslog_level) +{ + switch (syslog_level) { + case 0 /* LOG_EMERG */: + return LOGL_ERR; + case 1 /* LOG_ALERT */: + return LOGL_ERR; + case 2 /* LOG_CRIT */: + return LOGL_ERR; + case 3 /* LOG_ERR */: + return LOGL_ERR; + case 4 /* LOG_WARNING */: + return LOGL_WARN; + case 5 /* LOG_NOTICE */: + return LOGL_INFO; + case 6 /* LOG_INFO */: + return LOGL_DEBUG; + case 7 /* LOG_DEBUG */: + return LOGL_TRACE; + default: + return syslog_level >= 0 ? LOGL_TRACE : LOGL_ERR; + } +} + +static inline int +nm_log_level_to_syslog(NMLogLevel nm_level) +{ + switch (nm_level) { + case LOGL_ERR: + return 3; /* LOG_ERR */ + case LOGL_WARN: + return 4; /* LOG_WARN */ + case LOGL_INFO: + return 5; /* LOG_NOTICE */ + case LOGL_DEBUG: + return 6; /* LOG_INFO */ + case LOGL_TRACE: + return 7; /* LOG_DEBUG */ + default: + return 0; /* LOG_EMERG */ + } +} + +/*****************************************************************************/ + +struct timespec; + +/* this function must be implemented to handle the notification when + * the first monotonic-timestamp is fetched. */ +extern void _nm_utils_monotonic_timestamp_initialized(const struct timespec *tp, + gint64 offset_sec, + gboolean is_boottime); + +/*****************************************************************************/ + +#define _LOGL_TRACE LOGL_TRACE +#define _LOGL_DEBUG LOGL_DEBUG +#define _LOGL_INFO LOGL_INFO +#define _LOGL_WARN LOGL_WARN +#define _LOGL_ERR LOGL_ERR + +/* This is the default definition of _NMLOG_ENABLED(). Special implementations + * might want to undef this and redefine it. */ +#define _NMLOG_ENABLED(level) (nm_logging_enabled((level), (_NMLOG_DOMAIN))) + +#define _LOGT(...) _NMLOG(_LOGL_TRACE, __VA_ARGS__) +#define _LOGD(...) _NMLOG(_LOGL_DEBUG, __VA_ARGS__) +#define _LOGI(...) _NMLOG(_LOGL_INFO, __VA_ARGS__) +#define _LOGW(...) _NMLOG(_LOGL_WARN, __VA_ARGS__) +#define _LOGE(...) _NMLOG(_LOGL_ERR, __VA_ARGS__) + +#define _LOGT_ENABLED(...) _NMLOG_ENABLED(_LOGL_TRACE, ##__VA_ARGS__) +#define _LOGD_ENABLED(...) _NMLOG_ENABLED(_LOGL_DEBUG, ##__VA_ARGS__) +#define _LOGI_ENABLED(...) _NMLOG_ENABLED(_LOGL_INFO, ##__VA_ARGS__) +#define _LOGW_ENABLED(...) _NMLOG_ENABLED(_LOGL_WARN, ##__VA_ARGS__) +#define _LOGE_ENABLED(...) _NMLOG_ENABLED(_LOGL_ERR, ##__VA_ARGS__) + +#define _LOGT_err(errsv, ...) _NMLOG_err(errsv, _LOGL_TRACE, __VA_ARGS__) +#define _LOGD_err(errsv, ...) _NMLOG_err(errsv, _LOGL_DEBUG, __VA_ARGS__) +#define _LOGI_err(errsv, ...) _NMLOG_err(errsv, _LOGL_INFO, __VA_ARGS__) +#define _LOGW_err(errsv, ...) _NMLOG_err(errsv, _LOGL_WARN, __VA_ARGS__) +#define _LOGE_err(errsv, ...) _NMLOG_err(errsv, _LOGL_ERR, __VA_ARGS__) + +/* _LOGT() and _LOGt() both log with level TRACE, but the latter is disabled by default, + * unless building with --with-more-logging. */ +#if NM_MORE_LOGGING + #define _LOGt_ENABLED(...) _NMLOG_ENABLED(_LOGL_TRACE, ##__VA_ARGS__) + #define _LOGt(...) _NMLOG(_LOGL_TRACE, __VA_ARGS__) + #define _LOGt_err(errsv, ...) _NMLOG_err(errsv, _LOGL_TRACE, __VA_ARGS__) +#else + /* still call the logging macros to get compile time checks, but they will be optimized out. */ + #define _LOGt_ENABLED(...) (FALSE && (_NMLOG_ENABLED(_LOGL_TRACE, ##__VA_ARGS__))) + #define _LOGt(...) \ + G_STMT_START \ + { \ + if (FALSE) { \ + _NMLOG(_LOGL_TRACE, __VA_ARGS__); \ + } \ + } \ + G_STMT_END + #define _LOGt_err(errsv, ...) \ + G_STMT_START \ + { \ + if (FALSE) { \ + _NMLOG_err(errsv, _LOGL_TRACE, __VA_ARGS__); \ + } \ + } \ + G_STMT_END +#endif + +/*****************************************************************************/ + +/* Some implementation define a second set of logging macros, for a separate + * use. As with the _LOGD() macro family above, the exact implementation + * depends on the file that uses them. + * Still, it encourages a common pattern to have the common set of macros + * like _LOG2D(), _LOG2I(), etc. and have _LOG2t() which by default + * is disabled at compile time. */ + +#define _NMLOG2_ENABLED(level) (nm_logging_enabled((level), (_NMLOG2_DOMAIN))) + +#define _LOG2T(...) _NMLOG2(_LOGL_TRACE, __VA_ARGS__) +#define _LOG2D(...) _NMLOG2(_LOGL_DEBUG, __VA_ARGS__) +#define _LOG2I(...) _NMLOG2(_LOGL_INFO, __VA_ARGS__) +#define _LOG2W(...) _NMLOG2(_LOGL_WARN, __VA_ARGS__) +#define _LOG2E(...) _NMLOG2(_LOGL_ERR, __VA_ARGS__) + +#define _LOG2T_ENABLED(...) _NMLOG2_ENABLED(_LOGL_TRACE, ##__VA_ARGS__) +#define _LOG2D_ENABLED(...) _NMLOG2_ENABLED(_LOGL_DEBUG, ##__VA_ARGS__) +#define _LOG2I_ENABLED(...) _NMLOG2_ENABLED(_LOGL_INFO, ##__VA_ARGS__) +#define _LOG2W_ENABLED(...) _NMLOG2_ENABLED(_LOGL_WARN, ##__VA_ARGS__) +#define _LOG2E_ENABLED(...) _NMLOG2_ENABLED(_LOGL_ERR, ##__VA_ARGS__) + +#define _LOG2T_err(errsv, ...) _NMLOG2_err(errsv, _LOGL_TRACE, __VA_ARGS__) +#define _LOG2D_err(errsv, ...) _NMLOG2_err(errsv, _LOGL_DEBUG, __VA_ARGS__) +#define _LOG2I_err(errsv, ...) _NMLOG2_err(errsv, _LOGL_INFO, __VA_ARGS__) +#define _LOG2W_err(errsv, ...) _NMLOG2_err(errsv, _LOGL_WARN, __VA_ARGS__) +#define _LOG2E_err(errsv, ...) _NMLOG2_err(errsv, _LOGL_ERR, __VA_ARGS__) + +#if NM_MORE_LOGGING + #define _LOG2t_ENABLED(...) _NMLOG2_ENABLED(_LOGL_TRACE, ##__VA_ARGS__) + #define _LOG2t(...) _NMLOG2(_LOGL_TRACE, __VA_ARGS__) + #define _LOG2t_err(errsv, ...) _NMLOG2_err(errsv, _LOGL_TRACE, __VA_ARGS__) +#else + /* still call the logging macros to get compile time checks, but they will be optimized out. */ + #define _LOG2t_ENABLED(...) (FALSE && (_NMLOG2_ENABLED(_LOGL_TRACE, ##__VA_ARGS__))) + #define _LOG2t(...) \ + G_STMT_START \ + { \ + if (FALSE) { \ + _NMLOG2(_LOGL_TRACE, __VA_ARGS__); \ + } \ + } \ + G_STMT_END + #define _LOG2t_err(errsv, ...) \ + G_STMT_START \ + { \ + if (FALSE) { \ + _NMLOG2_err(errsv, _LOGL_TRACE, __VA_ARGS__); \ + } \ + } \ + G_STMT_END +#endif + +#define _NMLOG3_ENABLED(level) (nm_logging_enabled((level), (_NMLOG3_DOMAIN))) + +#define _LOG3T(...) _NMLOG3(_LOGL_TRACE, __VA_ARGS__) +#define _LOG3D(...) _NMLOG3(_LOGL_DEBUG, __VA_ARGS__) +#define _LOG3I(...) _NMLOG3(_LOGL_INFO, __VA_ARGS__) +#define _LOG3W(...) _NMLOG3(_LOGL_WARN, __VA_ARGS__) +#define _LOG3E(...) _NMLOG3(_LOGL_ERR, __VA_ARGS__) + +#define _LOG3T_ENABLED(...) _NMLOG3_ENABLED(_LOGL_TRACE, ##__VA_ARGS__) +#define _LOG3D_ENABLED(...) _NMLOG3_ENABLED(_LOGL_DEBUG, ##__VA_ARGS__) +#define _LOG3I_ENABLED(...) _NMLOG3_ENABLED(_LOGL_INFO, ##__VA_ARGS__) +#define _LOG3W_ENABLED(...) _NMLOG3_ENABLED(_LOGL_WARN, ##__VA_ARGS__) +#define _LOG3E_ENABLED(...) _NMLOG3_ENABLED(_LOGL_ERR, ##__VA_ARGS__) + +#define _LOG3T_err(errsv, ...) _NMLOG3_err(errsv, _LOGL_TRACE, __VA_ARGS__) +#define _LOG3D_err(errsv, ...) _NMLOG3_err(errsv, _LOGL_DEBUG, __VA_ARGS__) +#define _LOG3I_err(errsv, ...) _NMLOG3_err(errsv, _LOGL_INFO, __VA_ARGS__) +#define _LOG3W_err(errsv, ...) _NMLOG3_err(errsv, _LOGL_WARN, __VA_ARGS__) +#define _LOG3E_err(errsv, ...) _NMLOG3_err(errsv, _LOGL_ERR, __VA_ARGS__) + +#if NM_MORE_LOGGING + #define _LOG3t_ENABLED(...) _NMLOG3_ENABLED(_LOGL_TRACE, ##__VA_ARGS__) + #define _LOG3t(...) _NMLOG3(_LOGL_TRACE, __VA_ARGS__) + #define _LOG3t_err(errsv, ...) _NMLOG3_err(errsv, _LOGL_TRACE, __VA_ARGS__) +#else + /* still call the logging macros to get compile time checks, but they will be optimized out. */ + #define _LOG3t_ENABLED(...) (FALSE && (_NMLOG3_ENABLED(_LOGL_TRACE, ##__VA_ARGS__))) + #define _LOG3t(...) \ + G_STMT_START \ + { \ + if (FALSE) { \ + _NMLOG3(_LOGL_TRACE, __VA_ARGS__); \ + } \ + } \ + G_STMT_END + #define _LOG3t_err(errsv, ...) \ + G_STMT_START \ + { \ + if (FALSE) { \ + _NMLOG3_err(errsv, _LOGL_TRACE, __VA_ARGS__); \ + } \ + } \ + G_STMT_END +#endif + +/*****************************************************************************/ + +#endif /* __NM_LOGGING_FWD_H__ */ diff --git a/src/libnm-glib-aux/nm-macros-internal.h b/src/libnm-glib-aux/nm-macros-internal.h new file mode 100644 index 0000000000..113a67a0d2 --- /dev/null +++ b/src/libnm-glib-aux/nm-macros-internal.h @@ -0,0 +1,1815 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2012 Colin Walters <walters@verbum.org>. + * Copyright (C) 2014 Red Hat, Inc. + */ + +#ifndef __NM_MACROS_INTERNAL_H__ +#define __NM_MACROS_INTERNAL_H__ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#include <gio/gio.h> + +/*****************************************************************************/ + +/* most of our code is single-threaded with a mainloop. Hence, we usually don't need + * any thread-safety. Sometimes, we do need thread-safety (nm-logging), but we can + * avoid locking if we are on the main-thread by: + * + * - modifications of shared data is done infrequently and only from the + * main-thread (nm_logging_setup()) + * - read-only access is done frequently (nm_logging_enabled()) + * - from the main-thread, we can do that without locking (because + * all modifications are also done on the main thread. + * - from other threads, we need locking. But this is expected to be + * done infrequently too. Important is the lock-free fast-path on the + * main-thread. + * + * By defining NM_THREAD_SAFE_ON_MAIN_THREAD you indicate that this code runs + * on the main-thread. It is by default defined to "1". If you have code that + * is also used on another thread, redefine the define to 0 (to opt in into + * the slow-path). + */ +#define NM_THREAD_SAFE_ON_MAIN_THREAD 1 + +/*****************************************************************************/ + +#include "nm-glib.h" + +/*****************************************************************************/ + +#define nm_offsetofend(t, m) (G_STRUCT_OFFSET(t, m) + sizeof(((t *) NULL)->m)) + +/*****************************************************************************/ + +#define gs_free nm_auto_g_free +#define gs_unref_object nm_auto_unref_object +#define gs_unref_variant nm_auto_unref_variant +#define gs_unref_array nm_auto_unref_array +#define gs_unref_ptrarray nm_auto_unref_ptrarray +#define gs_unref_hashtable nm_auto_unref_hashtable +#define gs_unref_bytes nm_auto_unref_bytes +#define gs_strfreev nm_auto_strfreev +#define gs_free_error nm_auto_free_error + +/*****************************************************************************/ + +NM_AUTO_DEFINE_FCN_VOID0(void *, _nm_auto_g_free, g_free); +#define nm_auto_g_free nm_auto(_nm_auto_g_free) + +NM_AUTO_DEFINE_FCN_VOID0(GObject *, _nm_auto_unref_object, g_object_unref); +#define nm_auto_unref_object nm_auto(_nm_auto_unref_object) + +NM_AUTO_DEFINE_FCN0(GVariant *, _nm_auto_unref_variant, g_variant_unref); +#define nm_auto_unref_variant nm_auto(_nm_auto_unref_variant) + +NM_AUTO_DEFINE_FCN0(GArray *, _nm_auto_unref_array, g_array_unref); +#define nm_auto_unref_array nm_auto(_nm_auto_unref_array) + +NM_AUTO_DEFINE_FCN0(GPtrArray *, _nm_auto_unref_ptrarray, g_ptr_array_unref); +#define nm_auto_unref_ptrarray nm_auto(_nm_auto_unref_ptrarray) + +NM_AUTO_DEFINE_FCN0(GHashTable *, _nm_auto_unref_hashtable, g_hash_table_unref); +#define nm_auto_unref_hashtable nm_auto(_nm_auto_unref_hashtable) + +NM_AUTO_DEFINE_FCN0(GSList *, _nm_auto_free_slist, g_slist_free); +#define nm_auto_free_slist nm_auto(_nm_auto_free_slist) + +NM_AUTO_DEFINE_FCN0(GBytes *, _nm_auto_unref_bytes, g_bytes_unref); +#define nm_auto_unref_bytes nm_auto(_nm_auto_unref_bytes) + +NM_AUTO_DEFINE_FCN0(char **, _nm_auto_strfreev, g_strfreev); +#define nm_auto_strfreev nm_auto(_nm_auto_strfreev) + +NM_AUTO_DEFINE_FCN0(GError *, _nm_auto_free_error, g_error_free); +#define nm_auto_free_error nm_auto(_nm_auto_free_error) + +NM_AUTO_DEFINE_FCN0(GKeyFile *, _nm_auto_unref_keyfile, g_key_file_unref); +#define nm_auto_unref_keyfile nm_auto(_nm_auto_unref_keyfile) + +NM_AUTO_DEFINE_FCN0(GVariantIter *, _nm_auto_free_variant_iter, g_variant_iter_free); +#define nm_auto_free_variant_iter nm_auto(_nm_auto_free_variant_iter) + +NM_AUTO_DEFINE_FCN0(GVariantBuilder *, _nm_auto_unref_variant_builder, g_variant_builder_unref); +#define nm_auto_unref_variant_builder nm_auto(_nm_auto_unref_variant_builder) + +#define nm_auto_clear_variant_builder nm_auto(g_variant_builder_clear) + +NM_AUTO_DEFINE_FCN0(GList *, _nm_auto_free_list, g_list_free); +#define nm_auto_free_list nm_auto(_nm_auto_free_list) + +NM_AUTO_DEFINE_FCN0(GChecksum *, _nm_auto_checksum_free, g_checksum_free); +#define nm_auto_free_checksum nm_auto(_nm_auto_checksum_free) + +#define nm_auto_unset_gvalue nm_auto(g_value_unset) + +NM_AUTO_DEFINE_FCN_VOID0(void *, _nm_auto_unref_gtypeclass, g_type_class_unref); +#define nm_auto_unref_gtypeclass nm_auto(_nm_auto_unref_gtypeclass) + +NM_AUTO_DEFINE_FCN0(GByteArray *, _nm_auto_unref_bytearray, g_byte_array_unref); +#define nm_auto_unref_bytearray nm_auto(_nm_auto_unref_bytearray) + +static inline void +_nm_auto_free_gstring(GString **str) +{ + if (*str) + g_string_free(*str, TRUE); +} +#define nm_auto_free_gstring nm_auto(_nm_auto_free_gstring) + +static inline void +_nm_auto_protect_errno(int *p_saved_errno) +{ + errno = *p_saved_errno; +} +#define NM_AUTO_PROTECT_ERRNO(errsv_saved) \ + nm_auto(_nm_auto_protect_errno) _nm_unused const int errsv_saved = (errno) + +NM_AUTO_DEFINE_FCN0(GSource *, _nm_auto_unref_gsource, g_source_unref); +#define nm_auto_unref_gsource nm_auto(_nm_auto_unref_gsource) + +NM_AUTO_DEFINE_FCN0(guint, _nm_auto_remove_source, g_source_remove); +#define nm_auto_remove_source nm_auto(_nm_auto_remove_source) + +NM_AUTO_DEFINE_FCN0(GIOChannel *, _nm_auto_unref_io_channel, g_io_channel_unref); +#define nm_auto_unref_io_channel nm_auto(_nm_auto_unref_io_channel) + +NM_AUTO_DEFINE_FCN0(GMainLoop *, _nm_auto_unref_gmainloop, g_main_loop_unref); +#define nm_auto_unref_gmainloop nm_auto(_nm_auto_unref_gmainloop) + +NM_AUTO_DEFINE_FCN0(GOptionContext *, _nm_auto_free_option_context, g_option_context_free); +#define nm_auto_free_option_context nm_auto(_nm_auto_free_option_context) + +static inline void +_nm_auto_freev(gpointer ptr) +{ + gpointer **p = ptr; + gpointer * _ptr; + + if (*p) { + for (_ptr = *p; *_ptr; _ptr++) + g_free(*_ptr); + g_free(*p); + } +} +/* g_free a NULL terminated array of pointers, with also freeing each + * pointer with g_free(). It essentially does the same as + * gs_strfreev / g_strfreev(), but not restricted to strv arrays. */ +#define nm_auto_freev nm_auto(_nm_auto_freev) + +/*****************************************************************************/ + +#define _NM_MACRO_SELECT_ARG_64(_1, \ + _2, \ + _3, \ + _4, \ + _5, \ + _6, \ + _7, \ + _8, \ + _9, \ + _10, \ + _11, \ + _12, \ + _13, \ + _14, \ + _15, \ + _16, \ + _17, \ + _18, \ + _19, \ + _20, \ + _21, \ + _22, \ + _23, \ + _24, \ + _25, \ + _26, \ + _27, \ + _28, \ + _29, \ + _30, \ + _31, \ + _32, \ + _33, \ + _34, \ + _35, \ + _36, \ + _37, \ + _38, \ + _39, \ + _40, \ + _41, \ + _42, \ + _43, \ + _44, \ + _45, \ + _46, \ + _47, \ + _48, \ + _49, \ + _50, \ + _51, \ + _52, \ + _53, \ + _54, \ + _55, \ + _56, \ + _57, \ + _58, \ + _59, \ + _60, \ + _61, \ + _62, \ + _63, \ + N, \ + ...) \ + N + +/* http://stackoverflow.com/a/2124385/354393 + * https://stackoverflow.com/questions/11317474/macro-to-count-number-of-arguments + */ + +#define NM_NARG(...) \ + _NM_MACRO_SELECT_ARG_64(, \ + ##__VA_ARGS__, \ + 62, \ + 61, \ + 60, \ + 59, \ + 58, \ + 57, \ + 56, \ + 55, \ + 54, \ + 53, \ + 52, \ + 51, \ + 50, \ + 49, \ + 48, \ + 47, \ + 46, \ + 45, \ + 44, \ + 43, \ + 42, \ + 41, \ + 40, \ + 39, \ + 38, \ + 37, \ + 36, \ + 35, \ + 34, \ + 33, \ + 32, \ + 31, \ + 30, \ + 29, \ + 28, \ + 27, \ + 26, \ + 25, \ + 24, \ + 23, \ + 22, \ + 21, \ + 20, \ + 19, \ + 18, \ + 17, \ + 16, \ + 15, \ + 14, \ + 13, \ + 12, \ + 11, \ + 10, \ + 9, \ + 8, \ + 7, \ + 6, \ + 5, \ + 4, \ + 3, \ + 2, \ + 1, \ + 0) +#define NM_NARG_MAX1(...) \ + _NM_MACRO_SELECT_ARG_64(, \ + ##__VA_ARGS__, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 1, \ + 0) +#define NM_NARG_MAX2(...) \ + _NM_MACRO_SELECT_ARG_64(, \ + ##__VA_ARGS__, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 2, \ + 1, \ + 0) + +#define _NM_MACRO_CALL(macro, ...) macro(__VA_ARGS__) + +/*****************************************************************************/ + +#define _NM_MACRO_COMMA_IF_ARGS(...) \ + _NM_MACRO_CALL(G_PASTE(__NM_MACRO_COMMA_IF_ARGS_, NM_NARG_MAX1(__VA_ARGS__)), __VA_ARGS__) +#define __NM_MACRO_COMMA_IF_ARGS_0() +#define __NM_MACRO_COMMA_IF_ARGS_1(...) , + +/*****************************************************************************/ + +/* http://stackoverflow.com/a/11172679 */ +#define _NM_UTILS_MACRO_FIRST(...) __NM_UTILS_MACRO_FIRST_HELPER(__VA_ARGS__, throwaway) +#define __NM_UTILS_MACRO_FIRST_HELPER(first, ...) first + +#define _NM_UTILS_MACRO_REST(...) \ + _NM_MACRO_CALL(G_PASTE(__NM_UTILS_MACRO_REST_, NM_NARG_MAX2(__VA_ARGS__)), __VA_ARGS__) +#define __NM_UTILS_MACRO_REST_0() +#define __NM_UTILS_MACRO_REST_1(first) +#define __NM_UTILS_MACRO_REST_2(first, ...) , __VA_ARGS__ + +/*****************************************************************************/ + +#if defined(__GNUC__) + #define _NM_PRAGMA_WARNING_DO(warning) G_STRINGIFY(GCC diagnostic ignored warning) +#elif defined(__clang__) + #define _NM_PRAGMA_WARNING_DO(warning) G_STRINGIFY(clang diagnostic ignored warning) +#endif + +/* you can only suppress a specific warning that the compiler + * understands. Otherwise you will get another compiler warning + * about invalid pragma option. + * It's not that bad however, because gcc and clang often have the + * same name for the same warning. */ + +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) + #define NM_PRAGMA_WARNING_DISABLE(warning) \ + _Pragma("GCC diagnostic push") _Pragma(_NM_PRAGMA_WARNING_DO(warning)) +#elif defined(__clang__) + #define NM_PRAGMA_WARNING_DISABLE(warning) \ + _Pragma("clang diagnostic push") \ + _Pragma(_NM_PRAGMA_WARNING_DO("-Wunknown-warning-option")) \ + _Pragma(_NM_PRAGMA_WARNING_DO(warning)) +#else + #define NM_PRAGMA_WARNING_DISABLE(warning) +#endif + +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) + #define NM_PRAGMA_WARNING_REENABLE _Pragma("GCC diagnostic pop") +#elif defined(__clang__) + #define NM_PRAGMA_WARNING_REENABLE _Pragma("clang diagnostic pop") +#else + #define NM_PRAGMA_WARNING_REENABLE +#endif + +/*****************************************************************************/ + +/** + * NM_G_ERROR_MSG: + * @error: (allow-none): the #GError instance + * + * All functions must follow the convention that when they + * return a failure, they must also set the GError to a valid + * message. For external API however, we want to be extra + * careful before accessing the error instance. Use NM_G_ERROR_MSG() + * which is safe to use on NULL. + * + * Returns: the error message. + **/ +static inline const char * +NM_G_ERROR_MSG(GError *error) +{ + return error ? (error->message ?: "(null)") : "(no-error)"; +} + +/*****************************************************************************/ + +#ifndef _NM_CC_SUPPORT_AUTO_TYPE + #if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))) + #define _NM_CC_SUPPORT_AUTO_TYPE 1 + #else + #define _NM_CC_SUPPORT_AUTO_TYPE 0 + #endif +#endif + +#ifndef _NM_CC_SUPPORT_GENERIC + /* In the meantime, NetworkManager requires C11 and _Generic() should always be available. + * However, shared/nm-utils may also be used in VPN/applet, which possibly did not yet + * bump the C standard requirement. Leave this for the moment, but eventually we can + * drop it. */ + #if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))) \ + || (defined(__clang__)) + #define _NM_CC_SUPPORT_GENERIC 1 + #else + #define _NM_CC_SUPPORT_GENERIC 0 + #endif +#endif + +#if _NM_CC_SUPPORT_AUTO_TYPE + #define _nm_auto_type __auto_type +#endif + +#if _NM_CC_SUPPORT_GENERIC + #define _NM_CONSTCAST_FULL_1(type, obj_expr, obj) \ + (_Generic ((obj_expr), \ + const void *const: ((const type *) (obj)), \ + const void * : ((const type *) (obj)), \ + void *const: (( type *) (obj)), \ + void * : (( type *) (obj)), \ + const type *const: ((const type *) (obj)), \ + const type * : ((const type *) (obj)), \ + type *const: (( type *) (obj)), \ + type * : (( type *) (obj)))) + #define _NM_CONSTCAST_FULL_2(type, obj_expr, obj, alias_type2) \ + (_Generic ((obj_expr), \ + const void *const: ((const type *) (obj)), \ + const void * : ((const type *) (obj)), \ + void *const: (( type *) (obj)), \ + void * : (( type *) (obj)), \ + const alias_type2 *const: ((const type *) (obj)), \ + const alias_type2 * : ((const type *) (obj)), \ + alias_type2 *const: (( type *) (obj)), \ + alias_type2 * : (( type *) (obj)), \ + const type *const: ((const type *) (obj)), \ + const type * : ((const type *) (obj)), \ + type *const: (( type *) (obj)), \ + type * : (( type *) (obj)))) + #define _NM_CONSTCAST_FULL_3(type, obj_expr, obj, alias_type2, alias_type3) \ + (_Generic ((obj_expr), \ + const void *const: ((const type *) (obj)), \ + const void * : ((const type *) (obj)), \ + void *const: (( type *) (obj)), \ + void * : (( type *) (obj)), \ + const alias_type2 *const: ((const type *) (obj)), \ + const alias_type2 * : ((const type *) (obj)), \ + alias_type2 *const: (( type *) (obj)), \ + alias_type2 * : (( type *) (obj)), \ + const alias_type3 *const: ((const type *) (obj)), \ + const alias_type3 * : ((const type *) (obj)), \ + alias_type3 *const: (( type *) (obj)), \ + alias_type3 * : (( type *) (obj)), \ + const type *const: ((const type *) (obj)), \ + const type * : ((const type *) (obj)), \ + type *const: (( type *) (obj)), \ + type * : (( type *) (obj)))) + #define _NM_CONSTCAST_FULL_4(type, obj_expr, obj, alias_type2, alias_type3, alias_type4) \ + (_Generic ((obj_expr), \ + const void *const: ((const type *) (obj)), \ + const void * : ((const type *) (obj)), \ + void *const: (( type *) (obj)), \ + void * : (( type *) (obj)), \ + const alias_type2 *const: ((const type *) (obj)), \ + const alias_type2 * : ((const type *) (obj)), \ + alias_type2 *const: (( type *) (obj)), \ + alias_type2 * : (( type *) (obj)), \ + const alias_type3 *const: ((const type *) (obj)), \ + const alias_type3 * : ((const type *) (obj)), \ + alias_type3 *const: (( type *) (obj)), \ + alias_type3 * : (( type *) (obj)), \ + const alias_type4 *const: ((const type *) (obj)), \ + const alias_type4 * : ((const type *) (obj)), \ + alias_type4 *const: (( type *) (obj)), \ + alias_type4 * : (( type *) (obj)), \ + const type *const: ((const type *) (obj)), \ + const type * : ((const type *) (obj)), \ + type *const: (( type *) (obj)), \ + type * : (( type *) (obj)))) + #define _NM_CONSTCAST_FULL_x(type, obj_expr, obj, n, ...) \ + (_NM_CONSTCAST_FULL_##n(type, obj_expr, obj, ##__VA_ARGS__)) + #define _NM_CONSTCAST_FULL_y(type, obj_expr, obj, n, ...) \ + (_NM_CONSTCAST_FULL_x(type, obj_expr, obj, n, ##__VA_ARGS__)) + #define NM_CONSTCAST_FULL(type, obj_expr, obj, ...) \ + (_NM_CONSTCAST_FULL_y(type, obj_expr, obj, NM_NARG(dummy, ##__VA_ARGS__), ##__VA_ARGS__)) +#else + #define NM_CONSTCAST_FULL(type, obj_expr, obj, ...) ((type *) (obj)) +#endif + +#define NM_CONSTCAST(type, obj, ...) NM_CONSTCAST_FULL(type, (obj), (obj), ##__VA_ARGS__) + +#if _NM_CC_SUPPORT_GENERIC + #define NM_UNCONST_PTR(type, arg) \ + _Generic((arg), const type * : ((type *) (arg)), type * : ((type *) (arg))) +#else + #define NM_UNCONST_PTR(type, arg) ((type *) (arg)) +#endif + +#if _NM_CC_SUPPORT_GENERIC + #define NM_UNCONST_PPTR(type, arg) \ + _Generic ((arg), \ + const type * *: ((type **) (arg)), \ + type * *: ((type **) (arg)), \ + const type *const*: ((type **) (arg)), \ + type *const*: ((type **) (arg))) +#else + #define NM_UNCONST_PPTR(type, arg) ((type **) (arg)) +#endif + +#define NM_GOBJECT_CAST(type, obj, is_check, ...) \ + ({ \ + const void *_obj = (obj); \ + \ + nm_assert(_obj || (is_check(_obj))); \ + NM_CONSTCAST_FULL(type, (obj), _obj, GObject, ##__VA_ARGS__); \ + }) + +#define NM_GOBJECT_CAST_NON_NULL(type, obj, is_check, ...) \ + ({ \ + const void *_obj = (obj); \ + \ + nm_assert(is_check(_obj)); \ + NM_CONSTCAST_FULL(type, (obj), _obj, GObject, ##__VA_ARGS__); \ + }) + +#define NM_ENSURE_NOT_NULL(ptr) \ + ({ \ + typeof(ptr) _ptr = (ptr); \ + \ + nm_assert(_ptr != NULL); \ + _ptr; \ + }) + +#if _NM_CC_SUPPORT_GENERIC + /* returns @value, if the type of @value matches @type. + * This requires support for C11 _Generic(). If no support is + * 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))) + #define _NM_ENSURE_TYPE_CONST(type, value) \ + (_Generic((value), const type \ + : ((const type)(value)), const type const \ + : ((const type)(value)), type \ + : ((const type)(value)), type const \ + : ((const type)(value)))) +#else + #define _NM_ENSURE_TYPE(type, value) (value) + #define _NM_ENSURE_TYPE_CONST(type, value) ((const type)(value)) +#endif + +#if _NM_CC_SUPPORT_GENERIC && (!defined(__clang__) || __clang_major__ > 3) + #define NM_STRUCT_OFFSET_ENSURE_TYPE(type, container, field) \ + (_Generic((&(((container *) NULL)->field))[0], type : G_STRUCT_OFFSET(container, field))) +#else + #define NM_STRUCT_OFFSET_ENSURE_TYPE(type, container, field) G_STRUCT_OFFSET(container, field) +#endif + +#if _NM_CC_SUPPORT_GENERIC + /* these macros cast (value) to + * - "const char **" (for "MC", mutable-const) + * - "const char *const*" (for "CC", const-const) + * The point is to do this cast, but only accepting pointers + * that are compatible already. + * + * The problem is, if you add a function like g_strdupv(), the input + * argument is not modified (CC), but you want to make it work also + * for "char **". C doesn't allow this form of casting (for good reasons), + * so the function makes a choice like g_strdupv(char**). That means, + * every time you want to call it with a const argument, you need to + * explicitly cast it. + * + * These macros do the cast, but they only accept a compatible input + * type, otherwise they will fail compilation. + */ + #define NM_CAST_STRV_MC(value) \ + (_Generic ((value), \ + const char * *: (const char * *) (value), \ + char * *: (const char * *) (value), \ + void *: (const char * *) (value))) + #define NM_CAST_STRV_CC(value) \ + (_Generic ((value), \ + const char *const*: (const char *const*) (value), \ + const char * *: (const char *const*) (value), \ + char *const*: (const char *const*) (value), \ + char * *: (const char *const*) (value), \ + const void *: (const char *const*) (value), \ + void *: (const char *const*) (value), \ + const char *const*const: (const char *const*) (value), \ + const char * *const: (const char *const*) (value), \ + char *const*const: (const char *const*) (value), \ + char * *const: (const char *const*) (value), \ + const void *const: (const char *const*) (value), \ + void *const: (const char *const*) (value))) +#else + #define NM_CAST_STRV_MC(value) ((const char **) (value)) + #define NM_CAST_STRV_CC(value) ((const char *const *) (value)) +#endif + +#if _NM_CC_SUPPORT_GENERIC + #define NM_PROPAGATE_CONST(test_expr, ptr) \ + (_Generic ((test_expr), \ + const typeof (*(test_expr)) *: ((const typeof (*(ptr)) *) (ptr)), \ + default: (_Generic ((test_expr), \ + typeof (*(test_expr)) *: (ptr))))) +#else + #define NM_PROPAGATE_CONST(test_expr, ptr) (ptr) +#endif + +/* with the way it is implemented, the caller may or may not pass a trailing + * ',' and it will work. However, this makes the macro unsuitable for initializing + * an array. */ +#define NM_MAKE_STRV(...) \ + ((const char *const[(sizeof(((const char *const[]){__VA_ARGS__})) / sizeof(const char *)) \ + + 1]){__VA_ARGS__}) + +/*****************************************************************************/ + +/* NM_CACHED_QUARK() returns the GQuark for @string, but caches + * it in a static variable to speed up future lookups. + * + * @string must be a string literal. + */ +#define NM_CACHED_QUARK(string) \ + ({ \ + static GQuark _nm_cached_quark = 0; \ + \ + (G_LIKELY(_nm_cached_quark != 0) \ + ? _nm_cached_quark \ + : (_nm_cached_quark = g_quark_from_static_string("" string ""))); \ + }) + +/* NM_CACHED_QUARK_FCN() is essentially the same as G_DEFINE_QUARK + * with two differences: + * - @string must be a quoted string-literal + * - @fcn must be the full function name, while G_DEFINE_QUARK() appends + * "_quark" to the function name. + * Both properties of G_DEFINE_QUARK() are non favorable, because you can no + * longer grep for string/fcn -- unless you are aware that you are searching + * for G_DEFINE_QUARK() and omit quotes / append _quark(). With NM_CACHED_QUARK_FCN(), + * ctags/cscope can locate the use of @fcn (though it doesn't recognize that + * NM_CACHED_QUARK_FCN() defines it). + */ +#define NM_CACHED_QUARK_FCN(string, fcn) \ + GQuark fcn(void) \ + { \ + return NM_CACHED_QUARK(string); \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +/*****************************************************************************/ + +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 GString * +nm_gstring_add_space_delimiter(GString *str) +{ + if (str->len > 0) + g_string_append_c(str, ' '); + return str; +} + +static inline gboolean +nm_str_is_empty(const char *str) +{ + /* %NULL is also accepted, and also "empty". */ + return !str || !str[0]; +} + +static inline const char * +nm_str_not_empty(const char *str) +{ + return !nm_str_is_empty(str) ? str : NULL; +} + +static inline char * +nm_strdup_not_empty(const char *str) +{ + return !nm_str_is_empty(str) ? g_strdup(str) : NULL; +} + +static inline char * +nm_str_realloc(char *str) +{ + gs_free char *s = str; + + /* Returns a new clone of @str and frees @str. The point is that @str + * possibly points to a larger chunck of memory. We want to freshly allocate + * a buffer. + * + * We could use realloc(), but that might not do anything or leave + * @str in its memory pool for chunks of a different size (bad for + * fragmentation). + * + * This is only useful when we want to keep the buffer around for a long + * time and want to re-allocate a more optimal buffer. */ + + return g_strdup(s); +} + +/*****************************************************************************/ + +#define NM_PRINT_FMT_QUOTED2(cond, prefix, str, str_else) \ + (cond) ? (prefix) : "", (cond) ? (str) : (str_else) +#define NM_PRINT_FMT_QUOTED(cond, prefix, str, suffix, str_else) \ + (cond) ? (prefix) : "", (cond) ? (str) : (str_else), (cond) ? (suffix) : "" +#define NM_PRINT_FMT_QUOTE_STRING(arg) NM_PRINT_FMT_QUOTED((arg), "\"", (arg), "\"", "(null)") +#define NM_PRINT_FMT_QUOTE_REF_STRING(arg) \ + NM_PRINT_FMT_QUOTED((arg), "\"", (arg)->str, "\"", "(null)") + +/*****************************************************************************/ + +/* redefine assertions to use g_assert*() */ +#undef _nm_assert_call +#undef _nm_assert_call_not_reached +#define _nm_assert_call(cond) g_assert(cond) +#define _nm_assert_call_not_reached() g_assert_not_reached() + +/* Usage: + * + * if (NM_MORE_ASSERT_ONCE (5)) { extra_check (); } + * + * This will only run the check once, and only if NM_MORE_ASSERT is >= than + * more_assert_level. + */ +#define NM_MORE_ASSERT_ONCE(more_assert_level) \ + ((NM_MORE_ASSERTS >= (more_assert_level)) && ({ \ + static volatile int _assert_once = 0; \ + \ + G_STATIC_ASSERT_EXPR((more_assert_level) > 0); \ + \ + G_UNLIKELY(_assert_once == 0 && g_atomic_int_compare_and_exchange(&_assert_once, 0, 1)); \ + })) + +/*****************************************************************************/ + +#define NM_GOBJECT_PROPERTIES_DEFINE_BASE_FULL(suffix, ...) \ + typedef enum { \ + PROP_0##suffix, \ + __VA_ARGS__ _PROPERTY_ENUMS_LAST##suffix, \ + } _PropertyEnums##suffix; \ + static GParamSpec *obj_properties##suffix[_PROPERTY_ENUMS_LAST##suffix] = { \ + NULL, \ + } + +#define NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY(suffix, obj_type) \ + static inline void _nm_gobject_notify_together_impl##suffix( \ + obj_type * obj, \ + guint n, \ + const _PropertyEnums##suffix *props) \ + { \ + GObject *const gobj = (GObject *) obj; \ + GParamSpec * pspec_first = NULL; \ + gboolean frozen = FALSE; \ + \ + nm_assert(G_IS_OBJECT(obj)); \ + nm_assert(n > 0); \ + \ + while (n-- > 0) { \ + const _PropertyEnums##suffix prop = *props++; \ + GParamSpec * pspec; \ + \ + if (prop == PROP_0##suffix) \ + continue; \ + \ + nm_assert((gsize) prop < G_N_ELEMENTS(obj_properties##suffix)); \ + pspec = obj_properties##suffix[prop]; \ + nm_assert(pspec); \ + \ + if (!frozen) { \ + if (!pspec_first) { \ + pspec_first = pspec; \ + continue; \ + } \ + frozen = TRUE; \ + g_object_freeze_notify(gobj); \ + g_object_notify_by_pspec(gobj, pspec_first); \ + } \ + g_object_notify_by_pspec(gobj, pspec); \ + } \ + \ + if (frozen) \ + g_object_thaw_notify(gobj); \ + else if (pspec_first) \ + g_object_notify_by_pspec(gobj, pspec_first); \ + } \ + \ + _nm_unused static inline void _notify##suffix(obj_type *obj, _PropertyEnums##suffix prop) \ + { \ + _nm_gobject_notify_together_impl##suffix(obj, 1, &prop); \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +#define NM_GOBJECT_PROPERTIES_DEFINE_BASE(...) \ + NM_GOBJECT_PROPERTIES_DEFINE_BASE_FULL(, __VA_ARGS__); + +#define NM_GOBJECT_PROPERTIES_DEFINE_FULL(suffix, obj_type, ...) \ + NM_GOBJECT_PROPERTIES_DEFINE_BASE_FULL(suffix, __VA_ARGS__); \ + NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY(suffix, obj_type) + +#define NM_GOBJECT_PROPERTIES_DEFINE(obj_type, ...) \ + NM_GOBJECT_PROPERTIES_DEFINE_FULL(, obj_type, __VA_ARGS__) + +/* invokes _notify() for all arguments (of type _PropertyEnums). Note, that if + * there are more than one prop arguments, this will involve a freeze/thaw + * of GObject property notifications. */ +#define nm_gobject_notify_together_full(suffix, obj, ...) \ + _nm_gobject_notify_together_impl##suffix(obj, \ + NM_NARG(__VA_ARGS__), \ + (const _PropertyEnums##suffix[]){__VA_ARGS__}) + +#define nm_gobject_notify_together(obj, ...) nm_gobject_notify_together_full(, obj, __VA_ARGS__) + +/*****************************************************************************/ + +#define _NM_GET_PRIVATE(self, type, is_check, ...) \ + (&(NM_GOBJECT_CAST_NON_NULL(type, (self), is_check, ##__VA_ARGS__)->_priv)) +#if _NM_CC_SUPPORT_AUTO_TYPE + #define _NM_GET_PRIVATE_PTR(self, type, is_check, ...) \ + ({ \ + _nm_auto_type _self_get_private = \ + NM_GOBJECT_CAST_NON_NULL(type, (self), is_check, ##__VA_ARGS__); \ + \ + NM_PROPAGATE_CONST(_self_get_private, _self_get_private->_priv); \ + }) +#else + #define _NM_GET_PRIVATE_PTR(self, type, is_check, ...) \ + (NM_GOBJECT_CAST_NON_NULL(type, (self), is_check, ##__VA_ARGS__)->_priv) +#endif + +/*****************************************************************************/ + +static inline gpointer +nm_g_object_ref(gpointer obj) +{ + /* g_object_ref() doesn't accept NULL. */ + if (obj) + g_object_ref(obj); + return obj; +} +#define nm_g_object_ref(obj) ((typeof(obj)) nm_g_object_ref(obj)) + +static inline void +nm_g_object_unref(gpointer obj) +{ + /* g_object_unref() doesn't accept NULL. Usually, we workaround that + * by using g_clear_object(), but sometimes that is not convenient + * (for example as destroy function for a hash table that can contain + * NULL values). */ + if (obj) + g_object_unref(obj); +} + +/* Assigns GObject @obj to destination @pp, and takes an additional ref. + * The previous value of @pp 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; \ + \ + nm_assert(!_pp || !*_pp || G_IS_OBJECT(*_pp)); \ + nm_assert(!_obj || G_IS_OBJECT(_obj)); \ + \ + if (_pp && ((_p = *_pp) != _obj)) { \ + nm_g_object_ref(_obj); \ + *_pp = _obj; \ + nm_g_object_unref(_p); \ + _changed = TRUE; \ + } \ + _changed; \ + }) + +#define nm_g_object_ref_set_take(pp, obj) \ + ({ \ + typeof(*(pp)) *const _pp = (pp); \ + typeof(*_pp) const _obj = (obj); \ + typeof(*_pp) _p; \ + gboolean _changed = FALSE; \ + \ + nm_assert(!_pp || !*_pp || G_IS_OBJECT(*_pp)); \ + nm_assert(!_obj || G_IS_OBJECT(_obj)); \ + \ + if (_pp && ((_p = *_pp) != _obj)) { \ + *_pp = _obj; \ + nm_g_object_unref(_p); \ + _changed = TRUE; \ + } else \ + nm_g_object_unref(_obj); \ + _changed; \ + }) + +/* basically, replaces + * g_clear_pointer (&location, g_free) + * with + * nm_clear_g_free (&location) + * + * Another advantage is that by using a macro and typeof(), it is more + * typesafe and gives you for example a compiler warning when pp is a const + * pointer or points to a const-pointer. + */ +#define nm_clear_g_free(pp) nm_clear_pointer(pp, g_free) + +/* Our nm_clear_pointer() is more typesafe than g_clear_pointer() and + * should be preferred. + * + * For g_clear_object() that is not the case (because g_object_unref() + * anyway takes a void pointer). So using g_clear_object() is fine. + * + * Still have a nm_clear_g_object() because that returns a boolean + * indication whether anything was cleared. */ +#define nm_clear_g_object(pp) nm_clear_pointer(pp, g_object_unref) + +/** + * nm_clear_error: + * @err: a pointer to pointer to a #GError. + * + * This is like g_clear_error(). The only difference is + * that this is an inline function. + */ +static inline void +nm_clear_error(GError **err) +{ + if (err && *err) { + g_error_free(*err); + *err = NULL; + } +} + +/* Patch g_clear_error() to use nm_clear_error(), which is inlineable + * and visible to the compiler. For example gs_free_error attribute only + * frees the error after checking that it's not %NULL. So, in many cases + * the compiler knows that gs_free_error has no effect and can optimize + * the call away. By making g_clear_error() inlineable, we give the compiler + * more chance to detect that the function actually has no effect. */ +#define g_clear_error(ptr) nm_clear_error(ptr) + +static inline gboolean +nm_clear_g_source(guint *id) +{ + guint v; + + if (id && (v = *id)) { + *id = 0; + g_source_remove(v); + return TRUE; + } + return FALSE; +} + +static inline gboolean +nm_clear_g_signal_handler(gpointer self, gulong *id) +{ + gulong v; + + if (id && (v = *id)) { + *id = 0; + g_signal_handler_disconnect(self, v); + return TRUE; + } + return FALSE; +} + +static inline gboolean +nm_clear_g_variant(GVariant **variant) +{ + GVariant *v; + + if (variant && (v = *variant)) { + *variant = NULL; + g_variant_unref(v); + return TRUE; + } + return FALSE; +} + +static inline gboolean +nm_clear_g_cancellable(GCancellable **cancellable) +{ + GCancellable *v; + + if (cancellable && (v = *cancellable)) { + *cancellable = NULL; + g_cancellable_cancel(v); + g_object_unref(v); + return TRUE; + } + return FALSE; +} + +/* If @cancellable_id is not 0, clear it and call g_cancellable_disconnect(). + * @cancellable may be %NULL, if there is nothing to disconnect. + * + * It's like nm_clear_g_signal_handler(), except that it uses g_cancellable_disconnect() + * instead of g_signal_handler_disconnect(). + * + * Note the warning in glib documentation about dead-lock and what g_cancellable_disconnect() + * actually does. */ +static inline gboolean +nm_clear_g_cancellable_disconnect(GCancellable *cancellable, gulong *cancellable_id) +{ + gulong id; + + if (cancellable_id && (id = *cancellable_id) != 0) { + *cancellable_id = 0; + g_cancellable_disconnect(cancellable, id); + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ + +static inline const char * +nm_dbus_path_not_empty(const char *str) +{ + nm_assert(!str || str[0] == '/'); + return !str || (str[0] == '/' && str[1] == '\0') ? NULL : str; +} + +/*****************************************************************************/ + +/* GVariantType is basically a C string. But G_VARIANT_TYPE() is not suitable + * to initialize a static variable (because it evaluates a function check that + * the string is valid). Add an alternative macro that does the plain cast. + * + * Here you loose the assertion check that G_VARIANT_TYPE() to ensure the + * string is valid. */ +#define NM_G_VARIANT_TYPE(fmt) ((const GVariantType *) ("" fmt "")) + +static inline GVariant * +nm_g_variant_ref(GVariant *v) +{ + if (v) + g_variant_ref(v); + return v; +} + +static inline GVariant * +nm_g_variant_ref_sink(GVariant *v) +{ + if (v) + g_variant_ref_sink(v); + return v; +} + +static inline void +nm_g_variant_unref(GVariant *v) +{ + if (v) + g_variant_unref(v); +} + +static inline GVariant * +nm_g_variant_take_ref(GVariant *v) +{ + if (v) + g_variant_take_ref(v); + return v; +} + +/*****************************************************************************/ + +#define NM_DIV_ROUND_UP(x, y) \ + ({ \ + const typeof(x) _x = (x); \ + const typeof(y) _y = (y); \ + \ + (_x / _y + !!(_x % _y)); \ + }) + +/*****************************************************************************/ + +#define NM_UTILS_LOOKUP_DEFAULT(v) return (v) +#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(fcn_name, lookup_type, result_type, unknown_val, ...) \ + result_type fcn_name(lookup_type val) \ + { \ + switch (val) { \ + (void) 0, __VA_ARGS__(void) 0; \ + }; \ + { \ + unknown_val; \ + } \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +#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__) + +/* 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_A(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) \ + (G_STATIC_ASSERT_EXPR((check) > 0 && ((check) & ((check) -1)) == 0), \ + NM_FLAGS_ANY((flags), (check))) + +#define NM_FLAGS_ANY(flags, check) ((((flags) & (check)) != 0) ? TRUE : FALSE) +#define NM_FLAGS_ALL(flags, check) ((((flags) & (check)) == (check)) ? TRUE : FALSE) + +#define NM_FLAGS_SET(flags, val) \ + ({ \ + const typeof(flags) _flags = (flags); \ + const typeof(flags) _val = (val); \ + \ + _flags | _val; \ + }) + +#define NM_FLAGS_UNSET(flags, val) \ + ({ \ + const typeof(flags) _flags = (flags); \ + const typeof(flags) _val = (val); \ + \ + _flags &(~_val); \ + }) + +#define NM_FLAGS_ASSIGN(flags, val, assign) \ + ({ \ + const typeof(flags) _flags = (flags); \ + const typeof(flags) _val = (val); \ + \ + (assign) ? _flags | (_val) : _flags &(~_val); \ + }) + +#define NM_FLAGS_ASSIGN_MASK(flags, mask, val) \ + ({ \ + const typeof(flags) _flags = (flags); \ + const typeof(flags) _mask = (mask); \ + const typeof(flags) _val = (val); \ + \ + ((_flags & ~_mask) | (_mask & _val)); \ + }) + +/*****************************************************************************/ + +#define _NM_BACKPORT_SYMBOL_IMPL(version, \ + return_type, \ + orig_func, \ + versioned_func, \ + args_typed, \ + args) \ + return_type versioned_func args_typed; \ + _nm_externally_visible return_type versioned_func args_typed \ + { \ + return orig_func args; \ + } \ + return_type orig_func args_typed; \ + __asm__(".symver " G_STRINGIFY(versioned_func) ", " G_STRINGIFY(orig_func) "@" G_STRINGIFY( \ + version)) + +#define NM_BACKPORT_SYMBOL(version, return_type, func, args_typed, args) \ + _NM_BACKPORT_SYMBOL_IMPL(version, return_type, func, _##func##_##version, args_typed, args) + +/*****************************************************************************/ + +/* mirrors g_ascii_isspace() and what we consider spaces in general. */ +#define NM_ASCII_SPACES " \n\t\r\f" + +/* Like NM_ASCII_SPACES, but without "\f" (0x0c, Formfeed Page Break). + * This is what for example systemd calls WHITESPACE and what it uses to tokenize + * the kernel command line. */ +#define NM_ASCII_WHITESPACES " \n\t\r" + +#define nm_str_skip_leading_spaces(str) \ + ({ \ + typeof(*(str)) * _str_sls = (str); \ + _nm_unused const char *const _str_type_check = _str_sls; \ + \ + if (_str_sls) { \ + while (g_ascii_isspace(_str_sls[0])) \ + _str_sls++; \ + } \ + _str_sls; \ + }) + +static inline char * +nm_strstrip(char *str) +{ + /* g_strstrip doesn't like NULL. */ + 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; +} + +#define nm_strstrip_avoid_copy_a(alloca_maxlen, str, out_str_free) \ + ({ \ + const char *_str_ssac = (str); \ + char ** _out_str_free_ssac = (out_str_free); \ + \ + G_STATIC_ASSERT_EXPR((alloca_maxlen) > 0); \ + \ + nm_assert(_out_str_free_ssac || ((alloca_maxlen) > (str ? strlen(str) : 0u))); \ + nm_assert(!_out_str_free_ssac || !*_out_str_free_ssac); \ + \ + if (_str_ssac) { \ + _str_ssac = nm_str_skip_leading_spaces(_str_ssac); \ + if (_str_ssac[0] != '\0') { \ + gsize _l = strlen(_str_ssac); \ + \ + if (g_ascii_isspace(_str_ssac[--_l])) { \ + while (_l > 0 && g_ascii_isspace(_str_ssac[_l - 1])) { \ + _l--; \ + } \ + _str_ssac = nm_strndup_a((alloca_maxlen), _str_ssac, _l, _out_str_free_ssac); \ + } \ + } \ + } \ + \ + _str_ssac; \ + }) + +static inline gboolean +nm_str_is_stripped(const char *str) +{ + if (str && str[0]) { + if (g_ascii_isspace(str[0]) || g_ascii_isspace(str[strlen(str) - 1])) + return FALSE; + } + return TRUE; +} + +/* g_ptr_array_sort()'s compare function takes pointers to the + * value. Thus, you cannot use strcmp directly. You can use + * nm_strcmp_p(). + * + * Like strcmp(), this function is not forgiving to accept %NULL. */ +static inline int +nm_strcmp_p(gconstpointer a, gconstpointer b) +{ + const char *s1 = *((const char **) a); + const char *s2 = *((const char **) b); + + return strcmp(s1, s2); +} + +/*****************************************************************************/ + +static inline int +_NM_IN_STRSET_ASCII_CASE_op_streq(const char *x, const char *s) +{ + return s && g_ascii_strcasecmp(x, s) == 0; +} + +#define NM_IN_STRSET_ASCII_CASE(x, ...) \ + _NM_IN_STRSET_EVAL_N(||, \ + _NM_IN_STRSET_ASCII_CASE_op_streq, \ + x, \ + NM_NARG(__VA_ARGS__), \ + __VA_ARGS__) + +#define NM_STR_HAS_SUFFIX_ASCII_CASE(str, suffix) \ + ({ \ + const char *const _str_has_suffix = (str); \ + size_t _l; \ + \ + nm_assert(strlen(suffix) == NM_STRLEN(suffix)); \ + \ + (_str_has_suffix && ((_l = strlen(_str_has_suffix)) >= NM_STRLEN(suffix)) \ + && (g_ascii_strcasecmp(&_str_has_suffix[_l - NM_STRLEN(suffix)], "" suffix "") == 0)); \ + }) + +#define NM_STR_HAS_SUFFIX_ASCII_CASE_WITH_MORE(str, suffix) \ + ({ \ + const char *const _str_has_suffix = (str); \ + size_t _l; \ + \ + nm_assert(strlen(suffix) == NM_STRLEN(suffix)); \ + \ + (_str_has_suffix && ((_l = strlen(_str_has_suffix)) > NM_STRLEN(suffix)) \ + && (g_ascii_strcasecmp(&_str_has_suffix[_l - NM_STRLEN(suffix)], "" suffix "") == 0)); \ + }) + +/*****************************************************************************/ + +#define nm_g_slice_free(ptr) g_slice_free(typeof(*(ptr)), ptr) + +/*****************************************************************************/ + +/* like g_memdup(). The difference is that the @size argument is of type + * gsize, while g_memdup() has type guint. Since, the size of container types + * like GArray is guint as well, this means trying to g_memdup() an + * array, + * g_memdup (array->data, array->len * sizeof (ElementType)) + * will lead to integer overflow, if there are more than G_MAXUINT/sizeof(ElementType) + * bytes. That seems unnecessarily dangerous to me. + * nm_memdup() avoids that, because its size argument is always large enough + * to contain all data that a GArray can hold. + * + * Another minor difference to g_memdup() is that the glib version also + * returns %NULL if @data is %NULL. E.g. g_memdup(NULL, 1) + * gives %NULL, but nm_memdup(NULL, 1) crashes. I think that + * is desirable, because @size MUST be correct at all times. @size + * may be zero, but one must not claim to have non-zero bytes when + * passing a %NULL @data pointer. + */ +static inline gpointer +nm_memdup(gconstpointer data, gsize size) +{ + gpointer p; + + if (size == 0) + return NULL; + p = g_malloc(size); + memcpy(p, data, size); + return p; +} + +#define nm_malloc_maybe_a(alloca_maxlen, bytes, to_free) \ + ({ \ + const gsize _bytes = (bytes); \ + typeof(to_free) _to_free = (to_free); \ + typeof(*_to_free) _ptr; \ + \ + G_STATIC_ASSERT_EXPR((alloca_maxlen) <= 500u); \ + G_STATIC_ASSERT_EXPR((alloca_maxlen) > 0u); \ + nm_assert(_to_free && !*_to_free); \ + \ + if (G_LIKELY(_bytes <= (alloca_maxlen))) { \ + _ptr = _bytes > 0u ? g_alloca(_bytes) : NULL; \ + } else { \ + _ptr = g_malloc(_bytes); \ + *_to_free = _ptr; \ + }; \ + \ + _ptr; \ + }) + +#define nm_malloc0_maybe_a(alloca_maxlen, bytes, to_free) \ + ({ \ + const gsize _bytes = (bytes); \ + typeof(to_free) _to_free = (to_free); \ + typeof(*_to_free) _ptr; \ + \ + G_STATIC_ASSERT_EXPR((alloca_maxlen) <= 500u); \ + G_STATIC_ASSERT_EXPR((alloca_maxlen) > 0u); \ + nm_assert(_to_free && !*_to_free); \ + \ + if (G_LIKELY(_bytes <= (alloca_maxlen))) { \ + if (_bytes > 0u) { \ + _ptr = g_alloca(_bytes); \ + memset(_ptr, 0, _bytes); \ + } else \ + _ptr = NULL; \ + } else { \ + _ptr = g_malloc0(_bytes); \ + *_to_free = _ptr; \ + }; \ + \ + _ptr; \ + }) + +#define nm_memdup_maybe_a(alloca_maxlen, data, size, to_free) \ + ({ \ + const gsize _size = (size); \ + typeof(to_free) _to_free_md = (to_free); \ + typeof(*_to_free_md) _ptr_md = NULL; \ + \ + nm_assert(_to_free_md && !*_to_free_md); \ + \ + if (_size > 0u) { \ + _ptr_md = nm_malloc_maybe_a((alloca_maxlen), _size, _to_free_md); \ + memcpy(_ptr_md, (data), _size); \ + } \ + \ + _ptr_md; \ + }) + +static inline char * +_nm_strndup_a_step(char *s, const char *str, gsize len) +{ + NM_PRAGMA_WARNING_DISABLE("-Wstringop-truncation"); + NM_PRAGMA_WARNING_DISABLE("-Wstringop-overflow"); + if (len > 0) + strncpy(s, str, len); + s[len] = '\0'; + return s; + NM_PRAGMA_WARNING_REENABLE; + NM_PRAGMA_WARNING_REENABLE; +} + +/* Similar to g_strndup(), however, if the string (including the terminating + * NUL char) fits into alloca_maxlen, this will alloca() the memory. + * + * It's a mix of strndup() and strndupa(), but deciding based on @alloca_maxlen + * which one to use. + * + * In case malloc() is necessary, @out_str_free will be set (this string + * must be freed afterwards). It is permissible to pass %NULL as @out_str_free, + * if you ensure that len < alloca_maxlen. + * + * Note that just like g_strndup(), this always returns a buffer with @len + 1 + * bytes, even if strlen(@str) is shorter than that (NUL terminated early). We fill + * the buffer with strncpy(), which means, that @str is copied up to the first + * NUL character and then filled with NUL characters. */ +#define nm_strndup_a(alloca_maxlen, str, len, out_str_free) \ + ({ \ + const gsize _alloca_maxlen_snd = (alloca_maxlen); \ + const char *const _str_snd = (str); \ + const gsize _len_snd = (len); \ + char **const _out_str_free_snd = (out_str_free); \ + char * _s_snd; \ + \ + G_STATIC_ASSERT_EXPR((alloca_maxlen) <= 300); \ + \ + if (_out_str_free_snd && _len_snd >= _alloca_maxlen_snd) { \ + _s_snd = g_malloc(_len_snd + 1); \ + *_out_str_free_snd = _s_snd; \ + } else { \ + g_assert(_len_snd < _alloca_maxlen_snd); \ + _s_snd = g_alloca(_len_snd + 1); \ + } \ + _nm_strndup_a_step(_s_snd, _str_snd, _len_snd); \ + }) + +#define nm_strdup_maybe_a(alloca_maxlen, str, out_str_free) \ + ({ \ + const char *const _str_snd = (str); \ + \ + (char *) nm_memdup_maybe_a(alloca_maxlen, \ + _str_snd, \ + _str_snd ? strlen(_str_snd) + 1u : 0u, \ + out_str_free); \ + }) + +/*****************************************************************************/ + +/* generic macro to convert an int to a (heap allocated) string. + * + * Usually, an inline function nm_strdup_int64() would be enough. However, + * that cannot be used for guint64. So, we would also need nm_strdup_uint64(). + * This causes subtle error potential, because the caller needs to ensure to + * use the right one (and compiler isn't going to help as it silently casts). + * + * Instead, this generic macro is supposed to handle all integers correctly. */ +#if _NM_CC_SUPPORT_GENERIC + #define nm_strdup_int(val) \ + _Generic((val), char \ + : g_strdup_printf("%d", (int) (val)), \ + \ + signed char \ + : g_strdup_printf("%d", (signed) (val)), signed short \ + : g_strdup_printf("%d", (signed) (val)), signed \ + : g_strdup_printf("%d", (signed) (val)), signed long \ + : g_strdup_printf("%ld", (signed long) (val)), signed long long \ + : g_strdup_printf("%lld", (signed long long) (val)), \ + \ + unsigned char \ + : g_strdup_printf("%u", (unsigned) (val)), unsigned short \ + : g_strdup_printf("%u", (unsigned) (val)), unsigned \ + : g_strdup_printf("%u", (unsigned) (val)), unsigned long \ + : g_strdup_printf("%lu", (unsigned long) (val)), unsigned long long \ + : g_strdup_printf("%llu", (unsigned long long) (val))) +#else + #define nm_strdup_int(val) \ + ((sizeof(val) == sizeof(guint64) && ((typeof(val)) - 1) > 0) \ + ? g_strdup_printf("%" G_GUINT64_FORMAT, (guint64)(val)) \ + : g_strdup_printf("%" G_GINT64_FORMAT, (gint64)(val))) +#endif + +/*****************************************************************************/ + +static inline guint +nm_encode_version(guint major, guint minor, guint micro) +{ + /* analog to the preprocessor macro NM_ENCODE_VERSION(). */ + return (major << 16) | (minor << 8) | micro; +} + +static inline void +nm_decode_version(guint version, guint *major, guint *minor, guint *micro) +{ + *major = (version & 0xFFFF0000u) >> 16; + *minor = (version & 0x0000FF00u) >> 8; + *micro = (version & 0x000000FFu); +} + +/*****************************************************************************/ + +/* taken from systemd's DECIMAL_STR_MAX() + * + * Returns the number of chars needed to format variables of the + * specified type as a decimal string. Adds in extra space for a + * negative '-' prefix (hence works correctly on signed + * types). Includes space for the trailing NUL. */ +#define NM_DECIMAL_STR_MAX(type) \ + (2 \ + + (sizeof(type) <= 1 ? 3 \ + : sizeof(type) <= 2 ? 5 \ + : sizeof(type) <= 4 ? 10 \ + : sizeof(type) <= 8 ? 20 \ + : sizeof(int[-2 * (sizeof(type) > 8)]))) + +/*****************************************************************************/ + +/* if @str is NULL, return "(null)". Otherwise, allocate a buffer using + * alloca() of and fill it with @str. @str will be quoted with double quote. + * If @str is longer then @trunc_at, the string is truncated and the closing + * quote is instead '^' to indicate truncation. + * + * Thus, the maximum stack allocated buffer will be @trunc_at+3. The maximum + * buffer size must be a constant and not larger than 300. */ +#define nm_strquote_a(trunc_at, str) \ + ({ \ + const char *const _str = (str); \ + \ + (_str ? ({ \ + const gsize _trunc_at = (trunc_at); \ + const gsize _strlen_trunc = NM_MIN(strlen(_str), _trunc_at); \ + char * _buf; \ + \ + G_STATIC_ASSERT_EXPR((trunc_at) <= 300); \ + \ + _buf = g_alloca(_strlen_trunc + 3); \ + _buf[0] = '"'; \ + memcpy(&_buf[1], _str, _strlen_trunc); \ + _buf[_strlen_trunc + 1] = _str[_strlen_trunc] ? '^' : '"'; \ + _buf[_strlen_trunc + 2] = '\0'; \ + _buf; \ + }) \ + : "(null)"); \ + }) + +#define nm_sprintf_buf(buf, format, ...) \ + ({ \ + char *_buf = (buf); \ + int _buf_len; \ + \ + /* some static assert trying to ensure that the buffer is statically allocated. + * It disallows a buffer size of sizeof(gpointer) to catch that. */ \ + G_STATIC_ASSERT(G_N_ELEMENTS(buf) == sizeof(buf) && sizeof(buf) != sizeof(char *)); \ + _buf_len = g_snprintf(_buf, sizeof(buf), "" format "", ##__VA_ARGS__); \ + nm_assert(_buf_len < sizeof(buf)); \ + _buf; \ + }) + +/* it is "unsafe" because @bufsize must not be a constant expression and + * there is no check at compiletime. Regardless of that, the buffer size + * must not be larger than 300 bytes, as this gets stack allocated. */ +#define nm_sprintf_buf_unsafe_a(bufsize, format, ...) \ + ({ \ + char *_buf; \ + int _buf_len; \ + typeof(bufsize) _bufsize = (bufsize); \ + \ + nm_assert(_bufsize <= 300); \ + \ + _buf = g_alloca(_bufsize); \ + _buf_len = g_snprintf(_buf, _bufsize, "" format "", ##__VA_ARGS__); \ + nm_assert(_buf_len >= 0 && _buf_len < _bufsize); \ + _buf; \ + }) + +#define nm_sprintf_bufa(bufsize, format, ...) \ + ({ \ + G_STATIC_ASSERT_EXPR((bufsize) <= 300); \ + nm_sprintf_buf_unsafe_a((bufsize), format, ##__VA_ARGS__); \ + }) + +/* aims to alloca() a buffer and fill it with printf(format, name). + * Note that format must not contain any format specifier except + * "%s". + * If the resulting string would be too large for stack allocation, + * it allocates a buffer with g_malloc() and assigns it to *p_val_to_free. */ +#define nm_construct_name_a(format, name, p_val_to_free) \ + ({ \ + const char *const _name = (name); \ + char **const _p_val_to_free = (p_val_to_free); \ + const gsize _name_len = strlen(_name); \ + char * _buf2; \ + \ + nm_assert(_p_val_to_free && !*_p_val_to_free); \ + if (NM_STRLEN(format) <= 290 && _name_len < (gsize)(290 - NM_STRLEN(format))) \ + _buf2 = nm_sprintf_buf_unsafe_a(NM_STRLEN(format) + _name_len, format, _name); \ + else { \ + _buf2 = g_strdup_printf(format, _name); \ + *_p_val_to_free = _buf2; \ + } \ + (const char *) _buf2; \ + }) + +/*****************************************************************************/ + +#ifdef _G_BOOLEAN_EXPR + /* g_assert() uses G_LIKELY(), which in turn uses _G_BOOLEAN_EXPR(). + * As glib's implementation uses a local variable _g_boolean_var_, + * we cannot do + * g_assert (some_macro ()); + * where some_macro() itself expands to ({g_assert(); ...}). + * In other words, you cannot have a g_assert() inside a g_assert() + * without getting a -Werror=shadow failure. + * + * Workaround that by re-defining _G_BOOLEAN_EXPR() + **/ + #undef _G_BOOLEAN_EXPR + #define _G_BOOLEAN_EXPR(expr) NM_BOOLEAN_EXPR(expr) +#endif + +/*****************************************************************************/ + +#define NM_PID_T_INVAL ((pid_t) -1) + +/*****************************************************************************/ + +NM_AUTO_DEFINE_FCN_VOID0(GMutex *, _nm_auto_unlock_g_mutex, g_mutex_unlock); + +#define nm_auto_unlock_g_mutex nm_auto(_nm_auto_unlock_g_mutex) + +#define _NM_G_MUTEX_LOCKED(lock, uniq) \ + nm_auto_unlock_g_mutex GMutex *NM_UNIQ_T(nm_lock, uniq) = (lock) + +#define NM_G_MUTEX_LOCKED(lock) _NM_G_MUTEX_LOCKED(lock, NM_UNIQ) + +/*****************************************************************************/ + +#endif /* __NM_MACROS_INTERNAL_H__ */ diff --git a/src/libnm-glib-aux/nm-obj.h b/src/libnm-glib-aux/nm-obj.h new file mode 100644 index 0000000000..2062f66180 --- /dev/null +++ b/src/libnm-glib-aux/nm-obj.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 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 its + * 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/src/libnm-glib-aux/nm-random-utils.c b/src/libnm-glib-aux/nm-random-utils.c new file mode 100644 index 0000000000..56b99d5e3c --- /dev/null +++ b/src/libnm-glib-aux/nm-random-utils.c @@ -0,0 +1,148 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.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 + * entropy (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 high 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); + nm_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/src/libnm-glib-aux/nm-random-utils.h b/src/libnm-glib-aux/nm-random-utils.h new file mode 100644 index 0000000000..d0eae1033b --- /dev/null +++ b/src/libnm-glib-aux/nm-random-utils.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 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/src/libnm-glib-aux/nm-ref-string.c b/src/libnm-glib-aux/nm-ref-string.c new file mode 100644 index 0000000000..0804a05782 --- /dev/null +++ b/src/libnm-glib-aux/nm-ref-string.c @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-ref-string.h" + +/*****************************************************************************/ + +typedef struct { + NMRefString r; + volatile int ref_count; + char str_data[]; +} RefString; + +G_LOCK_DEFINE_STATIC(gl_lock); +static GHashTable *gl_hash; + +/* the first field of NMRefString is a pointer to the NUL terminated string. + * This also allows to compare strings with nm_pstr_equal(), although, pointer + * equality might be better. */ +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMRefString, str) == 0); +G_STATIC_ASSERT(G_STRUCT_OFFSET(RefString, r) == 0); +G_STATIC_ASSERT(G_STRUCT_OFFSET(RefString, r.str) == 0); + +/*****************************************************************************/ + +static guint +_ref_string_hash(gconstpointer ptr) +{ + const RefString *a = ptr; + NMHashState h; + + nm_hash_init(&h, 1463435489u); + nm_hash_update(&h, a->r.str, a->r.len); + return nm_hash_complete(&h); +} + +static gboolean +_ref_string_equal(gconstpointer pa, gconstpointer pb) +{ + const RefString *a = pa; + const RefString *b = pb; + + return a->r.len == b->r.len && memcmp(a->r.str, b->r.str, a->r.len) == 0; +} + +/*****************************************************************************/ + +static void +_ASSERT(const RefString *rstr0) +{ + int r; + + nm_assert(rstr0); + + if (NM_MORE_ASSERTS > 0) { + r = g_atomic_int_get(&rstr0->ref_count); + nm_assert(r > 0); + nm_assert(r < G_MAXINT); + } + + nm_assert(rstr0->r.str == rstr0->str_data); + nm_assert(rstr0->r.str[rstr0->r.len] == '\0'); + + if (NM_MORE_ASSERTS > 10) { + G_LOCK(gl_lock); + r = g_atomic_int_get(&rstr0->ref_count); + nm_assert(r > 0); + nm_assert(r < G_MAXINT); + + nm_assert(rstr0 == g_hash_table_lookup(gl_hash, rstr0)); + G_UNLOCK(gl_lock); + } +} + +/** + * nm_ref_string_new_len: + * @cstr: the string to intern. Must contain @len bytes. + * If @len is zero, @cstr may be %NULL. Note that it is + * acceptable that the string contains a NUL character + * within the first @len bytes. That is, the string is + * not treated as a NUL terminated string, but as binary. + * Also, contrary to strncpy(), this will read all the + * first @len bytes. It won't stop at the first NUL. + * @len: the length of the string (usually there is no NUL character + * within the first @len bytes, but that would be acceptable as well + * to add binary data). + * + * Note that the resulting NMRefString instance will always be NUL terminated + * (at position @len). + * + * Note that NMRefString are always interned/deduplicated. If such a string + * already exists, the existing instance will be referred and returned. + * + * + * Since all NMRefString are shared and interned, you may use + * pointer equality to compare them. Note that if a NMRefString contains + * a NUL character (meaning, if + * + * strlen (nm_ref_string_get_str (str)) != nm_ref_string_get_len (str) + * + * ), then pointer in-equality does not mean that the NUL terminated strings + * are also unequal. In other words, for strings that contain NUL characters, + * + * if (str1 != str2) + * assert (!nm_streq0 (nm_ref_string_get_str (str1), nm_ref_string_get_str (str2))); + * + * might not hold! + * + * + * NMRefString is thread-safe. + * + * Returns: (transfer full): the interned string. This is + * never %NULL, but note that %NULL is also a valid NMRefString. + * The result must be unrefed with nm_ref_string_unref(). + */ +NMRefString * +nm_ref_string_new_len(const char *cstr, gsize len) +{ + RefString *rstr0; + + G_LOCK(gl_lock); + + if (G_UNLIKELY(!gl_hash)) { + gl_hash = g_hash_table_new_full(_ref_string_hash, _ref_string_equal, g_free, NULL); + rstr0 = NULL; + } else { + NMRefString rr_lookup = { + .len = len, + .str = cstr, + }; + + rstr0 = g_hash_table_lookup(gl_hash, &rr_lookup); + } + + if (rstr0) { + nm_assert(({ + int r = g_atomic_int_get(&rstr0->ref_count); + + (r >= 0 && r < G_MAXINT); + })); + g_atomic_int_inc(&rstr0->ref_count); + } else { + rstr0 = g_malloc(sizeof(RefString) + 1 + len); + rstr0->ref_count = 1; + *((gsize *) &rstr0->r.len) = len; + *((const char **) &rstr0->r.str) = rstr0->str_data; + if (len > 0) + memcpy(rstr0->str_data, cstr, len); + rstr0->str_data[len] = '\0'; + + if (!g_hash_table_add(gl_hash, rstr0)) + nm_assert_not_reached(); + } + + G_UNLOCK(gl_lock); + + return &rstr0->r; +} + +NMRefString * +nm_ref_string_ref(NMRefString *rstr) +{ + RefString *const rstr0 = (RefString *) rstr; + + if (!rstr) + return NULL; + + _ASSERT(rstr0); + + g_atomic_int_inc(&rstr0->ref_count); + return &rstr0->r; +} + +void +_nm_ref_string_unref_non_null(NMRefString *rstr) +{ + RefString *const rstr0 = (RefString *) rstr; + int r; + + _ASSERT(rstr0); + + /* fast-path: first try to decrement the ref-count without bringing it + * to zero. */ + r = rstr0->ref_count; + if (G_LIKELY(r > 1 && g_atomic_int_compare_and_exchange(&rstr0->ref_count, r, r - 1))) + return; + + /* We apparently are about to return the last reference. Take a lock. */ + + G_LOCK(gl_lock); + + nm_assert(g_hash_table_lookup(gl_hash, rstr0) == rstr0); + + if (G_LIKELY(g_atomic_int_dec_and_test(&rstr0->ref_count))) { + if (!g_hash_table_remove(gl_hash, rstr0)) + nm_assert_not_reached(); + } + + G_UNLOCK(gl_lock); +} + +/*****************************************************************************/ diff --git a/src/libnm-glib-aux/nm-ref-string.h b/src/libnm-glib-aux/nm-ref-string.h new file mode 100644 index 0000000000..97c263b08f --- /dev/null +++ b/src/libnm-glib-aux/nm-ref-string.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NM_REF_STRING_H__ +#define __NM_REF_STRING_H__ + +/*****************************************************************************/ + +typedef struct _NMRefString { + const char *const str; + const gsize len; +} NMRefString; + +/*****************************************************************************/ + +NMRefString *nm_ref_string_new_len(const char *cstr, gsize len); + +static inline NMRefString * +nm_ref_string_new(const char *cstr) +{ + return cstr ? nm_ref_string_new_len(cstr, strlen(cstr)) : NULL; +} + +NMRefString *nm_ref_string_ref(NMRefString *rstr); +void _nm_ref_string_unref_non_null(NMRefString *rstr); + +static inline void +nm_ref_string_unref(NMRefString *rstr) +{ + if (rstr) + _nm_ref_string_unref_non_null(rstr); +} + +NM_AUTO_DEFINE_FCN_VOID0(NMRefString *, _nm_auto_ref_string, _nm_ref_string_unref_non_null); +#define nm_auto_ref_string nm_auto(_nm_auto_ref_string) + +/*****************************************************************************/ + +static inline const char * +nm_ref_string_get_str(NMRefString *rstr) +{ + return rstr ? rstr->str : NULL; +} + +static inline gsize +nm_ref_string_get_len(NMRefString *rstr) +{ + return rstr ? rstr->len : 0u; +} + +static inline gboolean +nm_ref_string_equals_str(NMRefString *rstr, const char *s) +{ + /* Note that rstr->len might be greater than strlen(rstr->str). This function does + * not cover that and would ignore everything after the first NUL byte. If you need + * that distinction, this function is not for you. */ + + return rstr ? (s && nm_streq(rstr->str, s)) : (s == NULL); +} + +static inline gboolean +NM_IS_REF_STRING(const NMRefString *rstr) +{ +#if NM_MORE_ASSERTS > 10 + if (rstr) { + nm_auto_ref_string NMRefString *r2 = NULL; + + r2 = nm_ref_string_new_len(rstr->str, rstr->len); + nm_assert(rstr == r2); + } +#endif + + /* Technically, %NULL is also a valid NMRefString (according to nm_ref_string_new(), + * nm_ref_string_get_str() and nm_ref_string_unref()). However, NM_IS_REF_STRING() + * does not think so. If callers want to allow %NULL, they need to check + * separately. */ + return !!rstr; +} + +#endif /* __NM_REF_STRING_H__ */ diff --git a/src/libnm-glib-aux/nm-secret-utils.c b/src/libnm-glib-aux/nm-secret-utils.c new file mode 100644 index 0000000000..c764b6e575 --- /dev/null +++ b/src/libnm-glib-aux/nm-secret-utils.c @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + * Copyright (C) 2015 - 2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-secret-utils.h" + +#include <malloc.h> + +/*****************************************************************************/ + +void +nm_explicit_bzero(void *s, gsize n) +{ + /* gracefully handle n == 0. This is important, callers rely on it. */ + if (G_UNLIKELY(n == 0)) + return; + + nm_assert(s); + +#if defined(HAVE_DECL_EXPLICIT_BZERO) && HAVE_DECL_EXPLICIT_BZERO + explicit_bzero(s, n); +#else + { + volatile guint8 *p = s; + + memset(s, '\0', n); + while (n-- > 0) + *(p++) = '\0'; + } +#endif +} + +void +nm_free_secret(char *secret) +{ + gsize len; + + if (!secret) + return; + +#if GLIB_CHECK_VERSION(2, 44, 0) + /* Here we mix malloc() and g_malloc() API. Usually we avoid this, + * however since glib 2.44.0 we are in fact guaranteed that g_malloc()/g_free() + * just wraps malloc()/free(), so this is actually fine. + * + * See https://gitlab.gnome.org/GNOME/glib/commit/3be6ed60aa58095691bd697344765e715a327fc1 + */ + len = malloc_usable_size(secret); +#else + len = strlen(secret); +#endif + + nm_explicit_bzero(secret, len); + g_free(secret); +} + +/*****************************************************************************/ + +char * +nm_secret_strchomp(char *secret) +{ + gsize len; + + g_return_val_if_fail(secret, NULL); + + /* it's actually identical to g_strchomp(). However, + * the glib function does not document, that it clears the + * memory. For @secret, we don't only want to truncate trailing + * spaces, we want to overwrite them with NUL. */ + + len = strlen(secret); + while (len--) { + if (g_ascii_isspace((guchar) secret[len])) + secret[len] = '\0'; + else + break; + } + + return secret; +} + +/*****************************************************************************/ + +GBytes * +nm_secret_copy_to_gbytes(gconstpointer mem, gsize mem_len) +{ + NMSecretBuf *b; + + if (mem_len == 0) + return g_bytes_new_static("", 0); + + nm_assert(mem); + + /* NUL terminate the buffer. + * + * The entire buffer is already malloc'ed and likely has some room for padding. + * Thus, in many situations, this additional byte will cause no overhead in + * practice. + * + * Even if it causes an overhead, do it just for safety. Yes, the returned + * bytes is not a NUL terminated string and no user must rely on this. Do + * not treat binary data as NUL terminated strings, unless you know what + * you are doing. Anyway, defensive FTW. + */ + + b = nm_secret_buf_new(mem_len + 1); + memcpy(b->bin, mem, mem_len); + b->bin[mem_len] = 0; + return nm_secret_buf_to_gbytes_take(b, mem_len); +} + +/*****************************************************************************/ + +NMSecretBuf * +nm_secret_buf_new(gsize len) +{ + NMSecretBuf *secret; + + nm_assert(len > 0); + + secret = g_malloc(sizeof(NMSecretBuf) + len); + *((gsize *) &(secret->len)) = len; + return secret; +} + +static void +_secret_buf_free(gpointer user_data) +{ + NMSecretBuf *secret = user_data; + + nm_assert(secret); + nm_assert(secret->len > 0); + + nm_explicit_bzero(secret->bin, secret->len); + g_free(user_data); +} + +GBytes * +nm_secret_buf_to_gbytes_take(NMSecretBuf *secret, gssize actual_len) +{ + nm_assert(secret); + nm_assert(secret->len > 0); + nm_assert(actual_len == -1 || (actual_len >= 0 && actual_len <= secret->len)); + return g_bytes_new_with_free_func(secret->bin, + actual_len >= 0 ? (gsize) actual_len : secret->len, + _secret_buf_free, + secret); +} + +/*****************************************************************************/ + +/** + * nm_utils_memeqzero_secret: + * @data: the data pointer to check (may be %NULL if @length is zero). + * @length: the number of bytes to check. + * + * Checks that all bytes are zero. This always takes the same amount + * of time to prevent timing attacks. + * + * Returns: whether all bytes are zero. + */ +gboolean +nm_utils_memeqzero_secret(gconstpointer data, gsize length) +{ + const guint8 *const key = data; + volatile guint8 acc = 0; + gsize i; + + for (i = 0; i < length; i++) { + acc |= key[i]; + asm volatile("" : "=r"(acc) : "0"(acc)); + } + return 1 & ((acc - 1) >> 8); +} diff --git a/src/libnm-glib-aux/nm-secret-utils.h b/src/libnm-glib-aux/nm-secret-utils.h new file mode 100644 index 0000000000..ac27963571 --- /dev/null +++ b/src/libnm-glib-aux/nm-secret-utils.h @@ -0,0 +1,273 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_SECRET_UTILS_H__ +#define __NM_SECRET_UTILS_H__ + +#include "nm-macros-internal.h" + +/*****************************************************************************/ + +void nm_explicit_bzero(void *s, gsize n); + +/*****************************************************************************/ + +char *nm_secret_strchomp(char *secret); + +/*****************************************************************************/ + +void nm_free_secret(char *secret); + +NM_AUTO_DEFINE_FCN0(char *, _nm_auto_free_secret, nm_free_secret); +/** + * nm_auto_free_secret: + * + * Call g_free() on a variable location when it goes out of scope. + * Also, previously, calls memset(loc, 0, strlen(loc)) to clear out + * the secret. + */ +#define nm_auto_free_secret nm_auto(_nm_auto_free_secret) + +/*****************************************************************************/ + +GBytes *nm_secret_copy_to_gbytes(gconstpointer mem, gsize mem_len); + +/*****************************************************************************/ + +/* NMSecretPtr is a pair of malloc'ed data pointer and the length of the + * data. The purpose is to use it in combination with nm_auto_clear_secret_ptr + * which ensures that the data pointer (with all len bytes) is cleared upon + * cleanup. */ +typedef struct { + gsize len; + + /* the data pointer. This pointer must be allocated with malloc (at least + * when used with nm_secret_ptr_clear()). */ + union { + char * str; + void * ptr; + guint8 *bin; + }; +} NMSecretPtr; + +static inline void +nm_secret_ptr_bzero(NMSecretPtr *secret) +{ + if (secret) { + if (secret->len > 0) { + if (secret->ptr) + nm_explicit_bzero(secret->ptr, secret->len); + } + } +} + +#define nm_auto_bzero_secret_ptr nm_auto(nm_secret_ptr_bzero) + +static inline void +nm_secret_ptr_clear(NMSecretPtr *secret) +{ + if (secret) { + if (secret->len > 0) { + if (secret->ptr) + nm_explicit_bzero(secret->ptr, secret->len); + secret->len = 0; + } + nm_clear_g_free(&secret->ptr); + } +} + +#define nm_auto_clear_secret_ptr nm_auto(nm_secret_ptr_clear) + +#define NM_SECRET_PTR_INIT() \ + ((const NMSecretPtr){ \ + .len = 0, \ + .ptr = NULL, \ + }) + +#define NM_SECRET_PTR_STATIC(_len) \ + ((const NMSecretPtr){ \ + .len = _len, \ + .ptr = ((guint8[_len]){}), \ + }) + +#define NM_SECRET_PTR_ARRAY(_arr) \ + ((const NMSecretPtr){ \ + .len = G_N_ELEMENTS(_arr) * sizeof((_arr)[0]), \ + .ptr = &((_arr)[0]), \ + }) + +static inline void +nm_secret_ptr_clear_static(const NMSecretPtr *secret) +{ + if (secret) { + if (secret->len > 0) { + nm_assert(secret->ptr); + nm_explicit_bzero(secret->ptr, secret->len); + } + } +} + +#define nm_auto_clear_static_secret_ptr nm_auto(nm_secret_ptr_clear_static) + +static inline void +nm_secret_ptr_move(NMSecretPtr *dst, NMSecretPtr *src) +{ + if (dst && dst != src) { + *dst = *src; + src->len = 0; + src->ptr = NULL; + } +} + +/*****************************************************************************/ + +typedef struct { + const gsize len; + union { + char str[0]; + guint8 bin[0]; + }; +} NMSecretBuf; + +static inline void +_nm_auto_free_secret_buf(NMSecretBuf **ptr) +{ + NMSecretBuf *b = *ptr; + + if (b) { + nm_assert(b->len > 0); + nm_explicit_bzero(b->bin, b->len); + g_free(b); + } +} +#define nm_auto_free_secret_buf nm_auto(_nm_auto_free_secret_buf) + +NMSecretBuf *nm_secret_buf_new(gsize len); + +GBytes *nm_secret_buf_to_gbytes_take(NMSecretBuf *secret, gssize actual_len); + +/*****************************************************************************/ + +gboolean nm_utils_memeqzero_secret(gconstpointer data, gsize length); + +/*****************************************************************************/ + +/** + * nm_secret_mem_realloc: + * @m_old: the current buffer of length @cur_len. + * @do_bzero_mem: if %TRUE, bzero the old buffer + * @cur_len: the current buffer length of @m_old. It is necessary for bzero. + * @new_len: the desired new length + * + * If @do_bzero_mem is false, this is like g_realloc(). + * Otherwise, this will allocate a new buffer of the desired size, copy over the + * old data, and bzero the old buffer before freeing it. As such, it also behaves + * similar to g_realloc(), with the overhead of nm_explicit_bzero() and using + * malloc/free instead of realloc(). + * + * Returns: the new allocated buffer. Think of it behaving like g_realloc(). + */ +static inline gpointer +nm_secret_mem_realloc(gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) +{ + gpointer m_new; + + nm_assert(m_old || cur_len == 0); + + if (do_bzero_mem && G_LIKELY(cur_len > 0)) { + m_new = g_malloc(new_len); + if (G_LIKELY(new_len > 0)) + memcpy(m_new, m_old, NM_MIN(cur_len, new_len)); + nm_explicit_bzero(m_old, cur_len); + g_free(m_old); + } else + m_new = g_realloc(m_old, new_len); + + return m_new; +} + +/** + * nm_secret_mem_try_realloc: + * @m_old: the current buffer of length @cur_len. + * @do_bzero_mem: if %TRUE, bzero the old buffer + * @cur_len: the current buffer length of @m_old. It is necessary for bzero. + * @new_len: the desired new length + * + * If @do_bzero_mem is false, this is like g_try_realloc(). + * Otherwise, this will try to allocate a new buffer of the desired size, copy over the + * old data, and bzero the old buffer before freeing it. As such, it also behaves + * similar to g_try_realloc(), with the overhead of nm_explicit_bzero() and using + * malloc/free instead of realloc(). + * + * Returns: the new allocated buffer or NULL. Think of it behaving like g_try_realloc(). + */ +static inline gpointer +nm_secret_mem_try_realloc(gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) +{ + gpointer m_new; + + nm_assert(m_old || cur_len == 0); + + if (do_bzero_mem && G_LIKELY(cur_len > 0)) { + if (G_UNLIKELY(new_len == 0)) + m_new = NULL; + else { + m_new = g_try_malloc(new_len); + if (!m_new) + return NULL; + memcpy(m_new, m_old, NM_MIN(cur_len, new_len)); + } + nm_explicit_bzero(m_old, cur_len); + g_free(m_old); + return m_new; + } + + return g_try_realloc(m_old, new_len); +} + +/** + * nm_secret_mem_try_realloc_take: + * @m_old: the current buffer of length @cur_len. + * @do_bzero_mem: if %TRUE, bzero the old buffer + * @cur_len: the current buffer length of @m_old. It is necessary for bzero. + * @new_len: the desired new length + * + * This works like nm_secret_mem_try_realloc(), which is not unlike g_try_realloc(). + * The difference is, if we fail to allocate a new buffer, then @m_old will be + * freed (and possibly cleared). This differs from plain realloc(), where the + * old buffer is unchanged if the operation fails. + * + * Returns: the new allocated buffer or NULL. Think of it behaving like g_try_realloc() + * but it will always free @m_old. + */ +static inline gpointer +nm_secret_mem_try_realloc_take(gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) +{ + gpointer m_new; + + nm_assert(m_old || cur_len == 0); + + if (do_bzero_mem && G_LIKELY(cur_len > 0)) { + if (G_UNLIKELY(new_len == 0)) + m_new = NULL; + else { + m_new = g_try_malloc(new_len); + if (G_LIKELY(m_new)) + memcpy(m_new, m_old, NM_MIN(cur_len, new_len)); + } + nm_explicit_bzero(m_old, cur_len); + g_free(m_old); + return m_new; + } + + m_new = g_try_realloc(m_old, new_len); + if (G_UNLIKELY(!m_new && new_len > 0)) + g_free(m_old); + return m_new; +} + +/*****************************************************************************/ + +#endif /* __NM_SECRET_UTILS_H__ */ diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c new file mode 100644 index 0000000000..9477cc3e74 --- /dev/null +++ b/src/libnm-glib-aux/nm-shared-utils.c @@ -0,0 +1,5710 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-shared-utils.h" + +#include <pwd.h> +#include <arpa/inet.h> +#include <poll.h> +#include <fcntl.h> +#include <sys/syscall.h> +#include <glib-unix.h> +#include <net/if.h> +#include <net/ethernet.h> + +#include "nm-errno.h" +#include "nm-str-buf.h" + +G_STATIC_ASSERT(sizeof(NMEtherAddr) == 6); +G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) == 1); + +G_STATIC_ASSERT(sizeof(NMUtilsNamedEntry) == sizeof(const char *)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMUtilsNamedValue, value_ptr) == sizeof(const char *)); + +/*****************************************************************************/ + +const char _nm_hexchar_table_lower[16] = "0123456789abcdef"; +const char _nm_hexchar_table_upper[16] = "0123456789ABCDEF"; + +const void *const _NM_PTRARRAY_EMPTY[1] = {NULL}; + +/*****************************************************************************/ + +const NMIPAddr nm_ip_addr_zero = {}; + +/* this initializes a struct in_addr/in6_addr and allows for untrusted + * arguments (like unsuitable @addr_family or @src_len). It's almost safe + * in the sense that it verifies input arguments strictly. Also, it + * uses memcpy() to access @src, so alignment is not an issue. + * + * Only potential pitfalls: + * + * - it allows for @addr_family to be AF_UNSPEC. If that is the case (and the + * caller allows for that), the caller MUST provide @out_addr_family. + * - when setting @dst to an IPv4 address, the trailing bytes are not touched. + * Meaning, if @dst is an NMIPAddr union, only the first bytes will be set. + * If that matter to you, clear @dst before. */ +gboolean +nm_ip_addr_set_from_untrusted(int addr_family, + gpointer dst, + gconstpointer src, + gsize src_len, + int * out_addr_family) +{ + nm_assert(dst); + + switch (addr_family) { + case AF_UNSPEC: + if (!out_addr_family) { + /* when the callers allow undefined @addr_family, they must provide + * an @out_addr_family argument. */ + nm_assert_not_reached(); + return FALSE; + } + switch (src_len) { + case sizeof(struct in_addr): + addr_family = AF_INET; + break; + case sizeof(struct in6_addr): + addr_family = AF_INET6; + break; + default: + return FALSE; + } + break; + case AF_INET: + if (src_len != sizeof(struct in_addr)) + return FALSE; + break; + case AF_INET6: + if (src_len != sizeof(struct in6_addr)) + return FALSE; + break; + default: + /* when the callers allow undefined @addr_family, they must provide + * an @out_addr_family argument. */ + nm_assert(out_addr_family); + return FALSE; + } + + nm_assert(src); + + memcpy(dst, src, src_len); + NM_SET_OUT(out_addr_family, addr_family); + return TRUE; +} + +/*****************************************************************************/ + +G_STATIC_ASSERT(ETH_ALEN == sizeof(struct ether_addr)); +G_STATIC_ASSERT(ETH_ALEN == 6); + +/*****************************************************************************/ + +pid_t +nm_utils_gettid(void) +{ + return (pid_t) syscall(SYS_gettid); +} + +/* Used for asserting that this function is called on the main-thread. + * The main-thread is determined by remembering the thread-id + * of when the function was called the first time. + * + * When forking, the thread-id is again reset upon first call. */ +gboolean +_nm_assert_on_main_thread(void) +{ + G_LOCK_DEFINE_STATIC(lock); + static pid_t seen_tid; + static pid_t seen_pid; + pid_t tid; + pid_t pid; + gboolean success = FALSE; + + tid = nm_utils_gettid(); + nm_assert(tid != 0); + + G_LOCK(lock); + + if (G_LIKELY(tid == seen_tid)) { + /* we don't care about false positives (when the process forked, and the thread-id + * is accidentally re-used) . It's for assertions only. */ + success = TRUE; + } else { + pid = getpid(); + nm_assert(pid != 0); + + if (seen_tid == 0 || seen_pid != pid) { + /* either this is the first time we call the function, or the process + * forked. In both cases, remember the thread-id. */ + seen_tid = tid; + seen_pid = pid; + success = TRUE; + } + } + + G_UNLOCK(lock); + + return success; +} + +/*****************************************************************************/ + +void +nm_utils_strbuf_append_c(char **buf, gsize *len, char c) +{ + switch (*len) { + case 0: + return; + case 1: + (*buf)[0] = '\0'; + *len = 0; + (*buf)++; + return; + default: + (*buf)[0] = c; + (*buf)[1] = '\0'; + (*len)--; + (*buf)++; + return; + } +} + +void +nm_utils_strbuf_append_bin(char **buf, gsize *len, gconstpointer str, gsize str_len) +{ + switch (*len) { + case 0: + return; + case 1: + if (str_len == 0) { + (*buf)[0] = '\0'; + return; + } + (*buf)[0] = '\0'; + *len = 0; + (*buf)++; + return; + default: + if (str_len == 0) { + (*buf)[0] = '\0'; + return; + } + if (str_len >= *len) { + memcpy(*buf, str, *len - 1); + (*buf)[*len - 1] = '\0'; + *buf = &(*buf)[*len]; + *len = 0; + } else { + memcpy(*buf, str, str_len); + *buf = &(*buf)[str_len]; + (*buf)[0] = '\0'; + *len -= str_len; + } + return; + } +} + +void +nm_utils_strbuf_append_str(char **buf, gsize *len, const char *str) +{ + gsize src_len; + + switch (*len) { + case 0: + return; + case 1: + if (!str || !*str) { + (*buf)[0] = '\0'; + return; + } + (*buf)[0] = '\0'; + *len = 0; + (*buf)++; + return; + default: + if (!str || !*str) { + (*buf)[0] = '\0'; + return; + } + src_len = g_strlcpy(*buf, str, *len); + if (src_len >= *len) { + *buf = &(*buf)[*len]; + *len = 0; + } else { + *buf = &(*buf)[src_len]; + *len -= src_len; + } + return; + } +} + +void +nm_utils_strbuf_append(char **buf, gsize *len, const char *format, ...) +{ + char * p = *buf; + va_list args; + int retval; + + if (*len == 0) + return; + + va_start(args, format); + retval = g_vsnprintf(p, *len, format, args); + va_end(args); + + if ((gsize) retval >= *len) { + *buf = &p[*len]; + *len = 0; + } else { + *buf = &p[retval]; + *len -= retval; + } +} + +/** + * nm_utils_strbuf_seek_end: + * @buf: the input/output buffer + * @len: the input/output length of the buffer. + * + * Commonly, one uses nm_utils_strbuf_append*(), to incrementally + * append strings to the buffer. However, sometimes we need to use + * existing API to write to the buffer. + * After doing so, we want to adjust the buffer counter. + * Essentially, + * + * g_snprintf (buf, len, ...); + * nm_utils_strbuf_seek_end (&buf, &len); + * + * is almost the same as + * + * nm_utils_strbuf_append (&buf, &len, ...); + * + * The only difference is the behavior when the string got truncated: + * nm_utils_strbuf_append() will recognize that and set the remaining + * length to zero. + * + * In general, the behavior is: + * + * - if *len is zero, do nothing + * - if the buffer contains a NUL byte within the first *len characters, + * the buffer is pointed to the NUL byte and len is adjusted. In this + * case, the remaining *len is always >= 1. + * In particular, that is also the case if the NUL byte is at the very last + * position ((*buf)[*len -1]). That happens, when the previous operation + * either fit the string exactly into the buffer or the string was truncated + * by g_snprintf(). The difference cannot be determined. + * - if the buffer contains no NUL bytes within the first *len characters, + * write NUL at the last position, set *len to zero, and point *buf past + * the NUL byte. This would happen with + * + * strncpy (buf, long_str, len); + * nm_utils_strbuf_seek_end (&buf, &len). + * + * where strncpy() does truncate the string and not NUL terminate it. + * nm_utils_strbuf_seek_end() would then NUL terminate it. + */ +void +nm_utils_strbuf_seek_end(char **buf, gsize *len) +{ + gsize l; + char *end; + + nm_assert(len); + nm_assert(buf && *buf); + + if (*len <= 1) { + if (*len == 1 && (*buf)[0]) + goto truncate; + return; + } + + end = memchr(*buf, 0, *len); + if (end) { + l = end - *buf; + nm_assert(l < *len); + + *buf = end; + *len -= l; + return; + } + +truncate: + /* hm, no NUL character within len bytes. + * Just NUL terminate the array and consume them + * all. */ + *buf += *len; + (*buf)[-1] = '\0'; + *len = 0; + return; +} + +/*****************************************************************************/ + +GBytes * +nm_gbytes_get_empty(void) +{ + static GBytes *bytes = NULL; + GBytes * b; + +again: + b = g_atomic_pointer_get(&bytes); + if (G_UNLIKELY(!b)) { + b = g_bytes_new_static("", 0); + if (!g_atomic_pointer_compare_and_exchange(&bytes, NULL, b)) { + g_bytes_unref(b); + goto again; + } + } + return b; +} + +GBytes * +nm_g_bytes_new_from_str(const char *str) +{ + gsize l; + + if (!str) + return NULL; + + /* the returned array is guaranteed to have a trailing '\0' + * character *after* the length. */ + + l = strlen(str); + return g_bytes_new_take(nm_memdup(str, l + 1u), l); +} + +/** + * nm_utils_gbytes_equals: + * @bytes: (allow-none): a #GBytes array to compare. Note that + * %NULL is treated like an #GBytes array of length zero. + * @mem_data: the data pointer with @mem_len bytes + * @mem_len: the length of the data pointer + * + * Returns: %TRUE if @bytes contains the same data as @mem_data. As a + * special case, a %NULL @bytes is treated like an empty array. + */ +gboolean +nm_utils_gbytes_equal_mem(GBytes *bytes, gconstpointer mem_data, gsize mem_len) +{ + gconstpointer p; + gsize l; + + if (!bytes) { + /* as a special case, let %NULL GBytes compare identical + * to an empty array. */ + return (mem_len == 0); + } + + p = g_bytes_get_data(bytes, &l); + return l == mem_len + && (mem_len == 0 /* allow @mem_data to be %NULL */ + || memcmp(p, mem_data, mem_len) == 0); +} + +GVariant * +nm_utils_gbytes_to_variant_ay(GBytes *bytes) +{ + const guint8 *p; + gsize l; + + if (!bytes) { + /* for convenience, accept NULL to return an empty variant */ + return g_variant_new_array(G_VARIANT_TYPE_BYTE, NULL, 0); + } + + p = g_bytes_get_data(bytes, &l); + return g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, p, l, 1); +} + +/*****************************************************************************/ + +#define _variant_singleton_get(create_variant) \ + ({ \ + static GVariant *_singleton = NULL; \ + GVariant * _v; \ + \ +again: \ + _v = g_atomic_pointer_get(&_singleton); \ + if (G_UNLIKELY(!_v)) { \ + _v = (create_variant); \ + nm_assert(_v); \ + nm_assert(g_variant_is_floating(_v)); \ + g_variant_ref_sink(_v); \ + if (!g_atomic_pointer_compare_and_exchange(&_singleton, NULL, _v)) { \ + g_variant_unref(_v); \ + goto again; \ + } \ + } \ + _v; \ + }) + +GVariant * +nm_g_variant_singleton_u_0(void) +{ + return _variant_singleton_get(g_variant_new_uint32(0)); +} + +/*****************************************************************************/ + +GHashTable * +nm_utils_strdict_clone(GHashTable *src) +{ + GHashTable * dst; + GHashTableIter iter; + const char * key; + const char * val; + + if (!src) + return NULL; + + dst = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); + g_hash_table_iter_init(&iter, src); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) + g_hash_table_insert(dst, g_strdup(key), g_strdup(val)); + return dst; +} + +/* Convert a hash table with "char *" keys and values to an "a{ss}" GVariant. + * The keys will be sorted asciibetically. + * Returns a floating reference. + */ +GVariant * +nm_utils_strdict_to_variant_ass(GHashTable *strdict) +{ + gs_free NMUtilsNamedValue *values_free = NULL; + NMUtilsNamedValue values_prepared[20]; + const NMUtilsNamedValue * values; + GVariantBuilder builder; + guint i; + guint n; + + values = nm_utils_named_values_from_strdict(strdict, &n, values_prepared, &values_free); + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}")); + for (i = 0; i < n; i++) { + g_variant_builder_add(&builder, "{ss}", values[i].name, values[i].value_str); + } + return g_variant_builder_end(&builder); +} + +/*****************************************************************************/ + +GVariant * +nm_utils_strdict_to_variant_asv(GHashTable *strdict) +{ + gs_free NMUtilsNamedValue *values_free = NULL; + NMUtilsNamedValue values_prepared[20]; + const NMUtilsNamedValue * values; + GVariantBuilder builder; + guint i; + guint n; + + values = nm_utils_named_values_from_strdict(strdict, &n, values_prepared, &values_free); + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + for (i = 0; i < n; i++) { + g_variant_builder_add(&builder, + "{sv}", + values[i].name, + g_variant_new_string(values[i].value_str)); + } + return g_variant_builder_end(&builder); +} + +/*****************************************************************************/ + +/** + * 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(0xFFFFFFFFu >> prefix) : 0xFFFFFFFFu; +} + +gconstpointer +nm_utils_ipx_address_clear_host_address(int family, gpointer dst, gconstpointer src, guint8 plen) +{ + g_return_val_if_fail(dst, NULL); + + switch (family) { + case AF_INET: + g_return_val_if_fail(plen <= 32, NULL); + + if (!src) { + /* allow "self-assignment", by specifying %NULL as source. */ + src = dst; + } + + *((guint32 *) dst) = nm_utils_ip4_address_clear_host_address(*((guint32 *) src), plen); + break; + case AF_INET6: + nm_utils_ip6_address_clear_host_address(dst, src, plen); + break; + default: + g_return_val_if_reached(NULL); + } + return dst; +} + +/* nm_utils_ip4_address_clear_host_address: + * @addr: source ip6 address + * @plen: prefix length of network + * + * returns: the input address, with the host address set to 0. + */ +in_addr_t +nm_utils_ip4_address_clear_host_address(in_addr_t addr, guint8 plen) +{ + return addr & _nm_utils_ip4_prefix_to_netmask(plen); +} + +/* nm_utils_ip6_address_clear_host_address: + * @dst: destination output buffer, will contain the network part of the @src address + * @src: source ip6 address + * @plen: prefix length of network + * + * Note: this function is self assignment safe, to update @src inplace, set both + * @dst and @src to the same destination or set @src NULL. + */ +const struct in6_addr * +nm_utils_ip6_address_clear_host_address(struct in6_addr * dst, + const struct in6_addr *src, + guint8 plen) +{ + g_return_val_if_fail(plen <= 128, NULL); + g_return_val_if_fail(dst, NULL); + + if (!src) + src = dst; + + if (plen < 128) { + guint nbytes = plen / 8; + guint nbits = plen % 8; + + if (nbytes && dst != src) + memcpy(dst, src, nbytes); + if (nbits) { + dst->s6_addr[nbytes] = (src->s6_addr[nbytes] & (0xFF << (8 - nbits))); + nbytes++; + } + if (nbytes <= 15) + memset(&dst->s6_addr[nbytes], 0, 16 - nbytes); + } else if (src != dst) + *dst = *src; + + return dst; +} + +int +nm_utils_ip6_address_same_prefix_cmp(const struct in6_addr *addr_a, + const struct in6_addr *addr_b, + guint8 plen) +{ + int nbytes; + guint8 va, vb, m; + + if (plen >= 128) + NM_CMP_DIRECT_MEMCMP(addr_a, addr_b, sizeof(struct in6_addr)); + else { + nbytes = plen / 8; + if (nbytes) + NM_CMP_DIRECT_MEMCMP(addr_a, addr_b, nbytes); + + plen = plen % 8; + if (plen != 0) { + m = ~((1 << (8 - plen)) - 1); + va = ((((const guint8 *) addr_a))[nbytes]) & m; + vb = ((((const guint8 *) addr_b))[nbytes]) & m; + NM_CMP_DIRECT(va, vb); + } + } + return 0; +} + +/*****************************************************************************/ + +guint32 +_nm_utils_ip4_get_default_prefix0(in_addr_t ip) +{ + /* The function is originally from ipcalc.c of Red Hat's initscripts. */ + switch (ntohl(ip) >> 24) { + case 0 ... 127: + return 8; /* Class A */ + case 128 ... 191: + return 16; /* Class B */ + case 192 ... 223: + return 24; /* Class C */ + } + return 0; +} + +guint32 +_nm_utils_ip4_get_default_prefix(in_addr_t ip) +{ + return _nm_utils_ip4_get_default_prefix0(ip) ?: 24; +} + +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); + } +} + +/*****************************************************************************/ + +static gboolean +_parse_legacy_addr4(const char *text, in_addr_t *out_addr, GError **error) +{ + gs_free char * s_free = NULL; + struct in_addr a1; + guint8 bin[sizeof(a1)]; + char * s; + int i; + + if (inet_aton(text, &a1) != 1) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "address invalid according to inet_aton()"); + return FALSE; + } + + /* OK, inet_aton() accepted the format. That's good, because we want + * to accept IPv4 addresses in octal format, like 255.255.000.000. + * That's what "legacy" means here. inet_pton() doesn't accept those. + * + * But inet_aton() also ignores trailing garbage and formats with fewer than + * 4 digits. That is just too crazy and we don't do that. Perform additional checks + * and reject some forms that inet_aton() accepted. + * + * Note that we still should (of course) accept everything that inet_pton() + * accepts. However this code never gets called if inet_pton() succeeds + * (see below, aside the assertion code). */ + + if (NM_STRCHAR_ANY(text, ch, (!(ch >= '0' && ch <= '9') && !NM_IN_SET(ch, '.', 'x')))) { + /* We only accepts '.', digits, and 'x' for "0x". */ + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "contains an invalid character"); + return FALSE; + } + + s = nm_memdup_maybe_a(300, text, strlen(text) + 1, &s_free); + + for (i = 0; i < G_N_ELEMENTS(bin); i++) { + char * current_token = s; + gint32 v; + + s = strchr(s, '.'); + if (s) { + s[0] = '\0'; + s++; + } + + if ((i == G_N_ELEMENTS(bin) - 1) != (s == NULL)) { + /* Exactly for the last digit, we expect to have no more following token. + * But this isn't the case. Abort. */ + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "wrong number of tokens (index %d, token '%s')", + i, + s); + return FALSE; + } + + v = _nm_utils_ascii_str_to_int64(current_token, 0, 0, 0xFF, -1); + if (v == -1) { + int errsv = errno; + + /* we do accept octal and hex (even with leading "0x"). But something + * about this token is wrong. */ + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "invalid token '%s': %s (%d)", + current_token, + nm_strerror_native(errsv), + errsv); + return FALSE; + } + + bin[i] = v; + } + + if (memcmp(bin, &a1, sizeof(bin)) != 0) { + /* our parsing did not agree with what inet_aton() gave. Something + * is wrong. Abort. */ + g_set_error( + error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "inet_aton() result 0x%08x differs from computed value 0x%02hhx%02hhx%02hhx%02hhx", + a1.s_addr, + bin[0], + bin[1], + bin[2], + bin[3]); + return FALSE; + } + + *out_addr = a1.s_addr; + return TRUE; +} + +gboolean +nm_utils_parse_inaddr_bin_full(int addr_family, + gboolean accept_legacy, + const char *text, + int * out_addr_family, + gpointer out_addr) +{ + NMIPAddr addrbin; + + g_return_val_if_fail(text, FALSE); + + if (addr_family == AF_UNSPEC) { + g_return_val_if_fail(!out_addr || out_addr_family, FALSE); + addr_family = strchr(text, ':') ? AF_INET6 : AF_INET; + } else + g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE); + + if (inet_pton(addr_family, text, &addrbin) != 1) { + if (accept_legacy && addr_family == AF_INET + && _parse_legacy_addr4(text, &addrbin.addr4, NULL)) { + /* The address is in some legacy format which inet_aton() accepts, but not inet_pton(). + * Most likely octal digits (leading zeros). We accept the address. */ + } else + return FALSE; + } + +#if NM_MORE_ASSERTS > 10 + if (addr_family == AF_INET) { + gs_free_error GError *error = NULL; + in_addr_t a; + + /* The legacy parser should accept everything that inet_pton() accepts too. Meaning, + * it should strictly parse *more* formats. And of course, parse it the same way. */ + if (!_parse_legacy_addr4(text, &a, &error)) { + char buf[INET_ADDRSTRLEN]; + + g_error("unexpected assertion failure: could parse \"%s\" as %s, but not accepted by " + "legacy parser: %s", + text, + _nm_utils_inet4_ntop(addrbin.addr4, buf), + error->message); + } + nm_assert(addrbin.addr4 == a); + } +#endif + + NM_SET_OUT(out_addr_family, addr_family); + if (out_addr) + nm_ip_addr_set(addr_family, out_addr, &addrbin); + 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)]; + + 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); + + if (inet_pton(addr_family, text, &addrbin) != 1) + 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, + int * out_addr_family, + gpointer out_addr, + int * out_prefix) +{ + gs_free char *addrstr_free = NULL; + int prefix = -1; + const char * slash; + const char * addrstr; + NMIPAddr addrbin; + + g_return_val_if_fail(text, FALSE); + + if (addr_family == AF_UNSPEC) { + g_return_val_if_fail(!out_addr || out_addr_family, FALSE); + addr_family = strchr(text, ':') ? AF_INET6 : AF_INET; + } else + g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE); + + slash = strchr(text, '/'); + if (slash) + addrstr = nm_strndup_a(300, text, slash - text, &addrstr_free); + else + addrstr = text; + + if (inet_pton(addr_family, addrstr, &addrbin) != 1) + return FALSE; + + if (slash) { + /* For IPv4, `ip addr add` supports the prefix-length as a netmask. We don't + * do that. */ + prefix = + _nm_utils_ascii_str_to_int64(&slash[1], 10, 0, addr_family == AF_INET ? 32 : 128, -1); + if (prefix == -1) + return FALSE; + } + + NM_SET_OUT(out_addr_family, addr_family); + if (out_addr) + nm_ip_addr_set(addr_family, out_addr, &addrbin); + 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, &addr_family, &addrbin, out_prefix)) + 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_next_line(const char **inout_ptr, + gsize * inout_len, + const char **out_line, + gsize * out_line_len) +{ + gboolean eol_is_carriage_return; + const char *line_start; + gsize line_len; + + nm_assert(inout_ptr); + nm_assert(inout_len); + nm_assert(*inout_len == 0 || *inout_ptr); + nm_assert(out_line); + nm_assert(out_line_len); + + if (G_UNLIKELY(*inout_len == 0)) + return FALSE; + + line_start = *inout_ptr; + + eol_is_carriage_return = FALSE; + for (line_len = 0;; line_len++) { + if (line_len >= *inout_len) { + /* if we consumed the entire line, we place the pointer at + * one character after the end. */ + *inout_ptr = &line_start[line_len]; + *inout_len = 0; + goto done; + } + switch (line_start[line_len]) { + case '\r': + eol_is_carriage_return = TRUE; + /* fall-through*/ + case '\0': + case '\n': + *inout_ptr = &line_start[line_len + 1]; + *inout_len = *inout_len - line_len - 1u; + if (eol_is_carriage_return && *inout_len > 0 && (*inout_ptr)[0] == '\n') { + /* also consume "\r\n" as one. */ + (*inout_len)--; + (*inout_ptr)++; + } + goto done; + } + } + +done: + *out_line = line_start; + *out_line_len = line_len; + return TRUE; +} + +/*****************************************************************************/ + +gboolean +nm_utils_ipaddr_is_valid(int addr_family, const char *str_addr) +{ + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + return str_addr && nm_utils_parse_inaddr_bin(addr_family, str_addr, NULL, NULL); +} + +gboolean +nm_utils_ipaddr_is_normalized(int addr_family, const char *str_addr) +{ + NMIPAddr addr; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + if (!str_addr) + return FALSE; + + if (!nm_utils_parse_inaddr_bin(addr_family, str_addr, &addr_family, &addr)) + return FALSE; + + nm_utils_inet_ntop(addr_family, &addr, sbuf); + return nm_streq(sbuf, str_addr); +} + +/*****************************************************************************/ + +/** + * nm_g_ascii_strtoll() + * @nptr: the string to parse + * @endptr: the pointer on the first invalid chars + * @base: the base. + * + * This wraps g_ascii_strtoll() and should in almost all cases behave identical + * to it. + * + * However, it seems there are situations where g_ascii_strtoll() might set + * errno to some unexpected value EAGAIN. Possibly this is related to creating + * the C locale during + * + * #ifdef USE_XLOCALE + * return strtoll_l (nptr, endptr, base, get_C_locale ()); + * + * This wrapper tries to workaround that condition. + */ +gint64 +nm_g_ascii_strtoll(const char *nptr, char **endptr, guint base) +{ + int try_count = 2; + gint64 v; + const int errsv_orig = errno; + int errsv; + + nm_assert(nptr); + nm_assert(base == 0u || (base >= 2u && base <= 36u)); + +again: + errno = 0; + v = g_ascii_strtoll(nptr, endptr, base); + errsv = errno; + + if (errsv == 0) { + if (errsv_orig != 0) + errno = errsv_orig; + return v; + } + + if (errsv == ERANGE && NM_IN_SET(v, G_MININT64, G_MAXINT64)) + return v; + + if (errsv == EINVAL && v == 0 && nptr && nptr[0] == '\0') + return v; + + if (try_count-- > 0) + goto again; + +#if NM_MORE_ASSERTS + g_critical("g_ascii_strtoll() for \"%s\" failed with errno=%d (%s) and v=%" G_GINT64_FORMAT, + nptr, + errsv, + nm_strerror_native(errsv), + v); +#endif + + return v; +} + +/* See nm_g_ascii_strtoll() */ +guint64 +nm_g_ascii_strtoull(const char *nptr, char **endptr, guint base) +{ + int try_count = 2; + guint64 v; + const int errsv_orig = errno; + int errsv; + + nm_assert(nptr); + nm_assert(base == 0u || (base >= 2u && base <= 36u)); + +again: + errno = 0; + v = g_ascii_strtoull(nptr, endptr, base); + errsv = errno; + + if (errsv == 0) { + if (errsv_orig != 0) + errno = errsv_orig; + return v; + } + + if (errsv == ERANGE && NM_IN_SET(v, G_MAXUINT64)) + return v; + + if (errsv == EINVAL && v == 0 && nptr && nptr[0] == '\0') + return v; + + if (try_count-- > 0) + goto again; + +#if NM_MORE_ASSERTS + g_critical("g_ascii_strtoull() for \"%s\" failed with errno=%d (%s) and v=%" G_GUINT64_FORMAT, + nptr, + errsv, + nm_strerror_native(errsv), + v); +#endif + + return v; +} + +/* see nm_g_ascii_strtoll(). */ +double +nm_g_ascii_strtod(const char *nptr, char **endptr) +{ + int try_count = 2; + double v; + int errsv; + + nm_assert(nptr); + +again: + v = g_ascii_strtod(nptr, endptr); + errsv = errno; + + if (errsv == 0) + return v; + + if (errsv == ERANGE) + return v; + + if (try_count-- > 0) + goto again; + +#if NM_MORE_ASSERTS + g_critical("g_ascii_strtod() for \"%s\" failed with errno=%d (%s) and v=%f", + nptr, + errsv, + nm_strerror_native(errsv), + v); +#endif + + /* Not really much else to do. Return the parsed value and leave errno set + * to the unexpected value. */ + return v; +} + +/* _nm_utils_ascii_str_to_int64: + * + * A wrapper for g_ascii_strtoll, that checks whether the whole string + * can be successfully converted to a number and is within a given + * range. On any error, @fallback will be returned and %errno will be set + * to a non-zero value. On success, %errno will be set to zero, check %errno + * for errors. Any trailing or leading (ascii) white space is ignored and the + * functions is locale independent. + * + * The function is guaranteed to return a value between @min and @max + * (inclusive) or @fallback. Also, the parsing is rather strict, it does + * not allow for any unrecognized characters, except leading and trailing + * white space. + **/ +gint64 +_nm_utils_ascii_str_to_int64(const char *str, guint base, gint64 min, gint64 max, gint64 fallback) +{ + gint64 v; + const char *s = NULL; + + str = nm_str_skip_leading_spaces(str); + if (!str || !str[0]) { + errno = EINVAL; + return fallback; + } + + errno = 0; + v = nm_g_ascii_strtoll(str, (char **) &s, base); + + if (errno != 0) + return fallback; + + if (s[0] != '\0') { + s = nm_str_skip_leading_spaces(s); + if (s[0] != '\0') { + errno = EINVAL; + return fallback; + } + } + if (v > max || v < min) { + errno = ERANGE; + return fallback; + } + + return v; +} + +guint64 +_nm_utils_ascii_str_to_uint64(const char *str, + guint base, + guint64 min, + guint64 max, + guint64 fallback) +{ + guint64 v; + const char *s = NULL; + + if (str) { + while (g_ascii_isspace(str[0])) + str++; + } + if (!str || !str[0]) { + errno = EINVAL; + return fallback; + } + + errno = 0; + v = nm_g_ascii_strtoull(str, (char **) &s, base); + + if (errno != 0) + return fallback; + if (s[0] != '\0') { + while (g_ascii_isspace(s[0])) + s++; + if (s[0] != '\0') { + errno = EINVAL; + return fallback; + } + } + if (v > max || v < min) { + errno = ERANGE; + return fallback; + } + + if (v != 0 && str[0] == '-') { + /* As documented, g_ascii_strtoull() accepts negative values, and returns their + * absolute value. We don't. */ + errno = ERANGE; + return fallback; + } + + return v; +} + +/*****************************************************************************/ + +int +nm_strcmp_with_data(gconstpointer a, gconstpointer b, gpointer user_data) +{ + const char *s1 = a; + const char *s2 = b; + + return strcmp(s1, s2); +} + +/* like nm_strcmp_p(), suitable for g_ptr_array_sort_with_data(). + * g_ptr_array_sort() just casts nm_strcmp_p() to a function of different + * signature. I guess, in glib there are knowledgeable people that ensure + * that this additional argument doesn't cause problems due to different ABI + * for every architecture that glib supports. + * For NetworkManager, we'd rather avoid such stunts. + **/ +int +nm_strcmp_p_with_data(gconstpointer a, gconstpointer b, gpointer user_data) +{ + const char *s1 = *((const char **) a); + const char *s2 = *((const char **) b); + + return strcmp(s1, s2); +} + +int +nm_strcmp0_p_with_data(gconstpointer a, gconstpointer b, gpointer user_data) +{ + const char *s1 = *((const char **) a); + const char *s2 = *((const char **) b); + + return nm_strcmp0(s1, s2); +} + +int +nm_strcmp_ascii_case_with_data(gconstpointer a, gconstpointer b, gpointer user_data) +{ + const char *s1 = a; + const char *s2 = b; + + return g_ascii_strcasecmp(s1, s2); +} + +int +nm_cmp_uint32_p_with_data(gconstpointer p_a, gconstpointer p_b, gpointer user_data) +{ + const guint32 a = *((const guint32 *) p_a); + const guint32 b = *((const guint32 *) p_b); + + if (a < b) + return -1; + if (a > b) + return 1; + return 0; +} + +int +nm_cmp_int2ptr_p_with_data(gconstpointer p_a, gconstpointer p_b, gpointer user_data) +{ + /* p_a and p_b are two pointers to a pointer, where the pointer is + * interpreted as a integer using GPOINTER_TO_INT(). + * + * That is the case of a hash-table that uses GINT_TO_POINTER() to + * convert integers as pointers, and the resulting keys-as-array + * array. */ + const int a = GPOINTER_TO_INT(*((gconstpointer *) p_a)); + const int b = GPOINTER_TO_INT(*((gconstpointer *) p_b)); + + if (a < b) + return -1; + if (a > b) + return 1; + return 0; +} + +/*****************************************************************************/ + +const char * +nm_utils_dbus_path_get_last_component(const char *dbus_path) +{ + if (dbus_path) { + dbus_path = strrchr(dbus_path, '/'); + if (dbus_path) + return dbus_path + 1; + } + return NULL; +} + +static gint64 +_dbus_path_component_as_num(const char *p) +{ + gint64 n; + + /* no odd stuff. No leading zeros, only a non-negative, decimal integer. + * + * Otherwise, there would be multiple ways to encode the same number "10" + * and "010". That is just confusing. A number has no leading zeros, + * if it has, it's not a number (as far as we are concerned here). */ + if (p[0] == '0') { + if (p[1] != '\0') + return -1; + else + return 0; + } + if (!(p[0] >= '1' && p[0] <= '9')) + return -1; + if (!NM_STRCHAR_ALL(&p[1], ch, (ch >= '0' && ch <= '9'))) + return -1; + n = _nm_utils_ascii_str_to_int64(p, 10, 0, G_MAXINT64, -1); + nm_assert(n == -1 || nm_streq0(p, nm_sprintf_bufa(100, "%" G_GINT64_FORMAT, n))); + return n; +} + +int +nm_utils_dbus_path_cmp(const char *dbus_path_a, const char *dbus_path_b) +{ + const char *l_a, *l_b; + gsize plen; + gint64 n_a, n_b; + + /* compare function for two D-Bus paths. It behaves like + * strcmp(), except, if both paths have the same prefix, + * and both end in a (positive) number, then the paths + * will be sorted by number. */ + + NM_CMP_SELF(dbus_path_a, dbus_path_b); + + /* if one or both paths have no slash (and no last component) + * compare the full paths directly. */ + if (!(l_a = nm_utils_dbus_path_get_last_component(dbus_path_a)) + || !(l_b = nm_utils_dbus_path_get_last_component(dbus_path_b))) + goto comp_full; + + /* check if both paths have the same prefix (up to the last-component). */ + plen = l_a - dbus_path_a; + if (plen != (l_b - dbus_path_b)) + goto comp_full; + NM_CMP_RETURN(strncmp(dbus_path_a, dbus_path_b, plen)); + + n_a = _dbus_path_component_as_num(l_a); + n_b = _dbus_path_component_as_num(l_b); + if (n_a == -1 && n_b == -1) + goto comp_l; + + /* both components must be convertible to a number. If they are not, + * (and only one of them is), then we must always strictly sort numeric parts + * after non-numeric components. If we wouldn't, we wouldn't have + * a total order. + * + * An example of a not total ordering would be: + * "8" < "010" (numeric) + * "0x" < "8" (lexical) + * "0x" > "010" (lexical) + * We avoid this, by forcing that a non-numeric entry "0x" always sorts + * before numeric entries. + * + * Additionally, _dbus_path_component_as_num() would also reject "010" as + * not a valid number. + */ + if (n_a == -1) + return -1; + if (n_b == -1) + return 1; + + NM_CMP_DIRECT(n_a, n_b); + nm_assert(nm_streq(dbus_path_a, dbus_path_b)); + return 0; + +comp_full: + NM_CMP_DIRECT_STRCMP0(dbus_path_a, dbus_path_b); + return 0; +comp_l: + NM_CMP_DIRECT_STRCMP0(l_a, l_b); + nm_assert(nm_streq(dbus_path_a, dbus_path_b)); + return 0; +} + +/*****************************************************************************/ + +typedef struct { + union { + guint8 table[256]; + guint64 _dummy_for_alignment; + }; +} CharLookupTable; + +static void +_char_lookup_table_set_one(CharLookupTable *lookup, char ch) +{ + lookup->table[(guint8) ch] = 1; +} + +static void +_char_lookup_table_set_all(CharLookupTable *lookup, const char *candidates) +{ + while (candidates[0] != '\0') + _char_lookup_table_set_one(lookup, (candidates++)[0]); +} + +static void +_char_lookup_table_init(CharLookupTable *lookup, const char *candidates) +{ + *lookup = (CharLookupTable){ + .table = {0}, + }; + if (candidates) + _char_lookup_table_set_all(lookup, candidates); +} + +static gboolean +_char_lookup_has(const CharLookupTable *lookup, char ch) +{ + /* with some optimization levels, the compiler thinks this code + * might access uninitialized @lookup. It is not -- when you look at the + * callers of this function. */ + NM_PRAGMA_WARNING_DISABLE("-Wmaybe-uninitialized") + nm_assert(lookup->table[(guint8) '\0'] == 0); + return lookup->table[(guint8) ch] != 0; + NM_PRAGMA_WARNING_REENABLE +} + +static gboolean +_char_lookup_has_all(const CharLookupTable *lookup, const char *candidates) +{ + if (candidates) { + while (candidates[0] != '\0') { + if (!_char_lookup_has(lookup, (candidates++)[0])) + return FALSE; + } + } + return TRUE; +} + +/** + * nm_utils_strsplit_set_full: + * @str: the string to split. + * @delimiters: the set of delimiters. + * @flags: additional flags for controlling the operation. + * + * 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. + * + * Note that for @str %NULL and "", this always returns %NULL too. That differs + * from g_strsplit_set(), which would return an empty strv array for "". + * This never returns an empty array. + * + * Returns: %NULL if @str is %NULL or "". + * If @str only contains delimiters and %NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY + * is not set, it also returns %NULL. + * Otherwise, a %NULL terminated strv array containing the split words. + * (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(). + * It is however safe and allowed to modify the individual strings in-place, + * like "g_strstrip((char *) iter[0])". + */ +const char ** +nm_utils_strsplit_set_full(const char *str, const char *delimiters, NMUtilsStrsplitSetFlags flags) +{ + const char ** ptr; + gsize num_tokens; + gsize i_token; + gsize str_len_p1; + const char * c_str; + char * s; + CharLookupTable ch_lookup; + const gboolean f_escaped = NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED); + const gboolean f_allow_escaping = + f_escaped || NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING); + const gboolean f_preserve_empty = + NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY); + const gboolean f_strstrip = NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP); + + if (!str) + return NULL; + + if (!delimiters) { + nm_assert_not_reached(); + delimiters = " \t\n"; + } + _char_lookup_table_init(&ch_lookup, delimiters); + + nm_assert(!f_allow_escaping || !_char_lookup_has(&ch_lookup, '\\')); + + if (!f_preserve_empty) { + while (_char_lookup_has(&ch_lookup, str[0])) + str++; + } + + if (!str[0]) { + /* We return %NULL here, also with NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY. + * That makes nm_utils_strsplit_set_full() with NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY + * different from g_strsplit_set(), which would in this case return an empty array. + * If you need to handle %NULL, and "" specially, then check the input string first. */ + return NULL; + } + +#define _char_is_escaped(str_start, str_cur) \ + ({ \ + const char *const _str_start = (str_start); \ + const char *const _str_cur = (str_cur); \ + const char * _str_i = (_str_cur); \ + \ + while (_str_i > _str_start && _str_i[-1] == '\\') \ + _str_i--; \ + (((_str_cur - _str_i) % 2) != 0); \ + }) + + num_tokens = 1; + c_str = str; + while (TRUE) { + while (G_LIKELY(!_char_lookup_has(&ch_lookup, c_str[0]))) { + if (c_str[0] == '\0') + goto done1; + c_str++; + } + + /* we assume escapings are not frequent. After we found + * this delimiter, check whether it was escaped by counting + * the backslashed before. */ + if (f_allow_escaping && _char_is_escaped(str, c_str)) { + /* the delimiter is escaped. This was not an accepted delimiter. */ + c_str++; + continue; + } + + c_str++; + + /* if we drop empty tokens, then we now skip over all consecutive delimiters. */ + if (!f_preserve_empty) { + while (_char_lookup_has(&ch_lookup, c_str[0])) + c_str++; + if (c_str[0] == '\0') + break; + } + + num_tokens++; + } + +done1: + + nm_assert(c_str[0] == '\0'); + + str_len_p1 = (c_str - str) + 1; + + nm_assert(str[str_len_p1 - 1] == '\0'); + + ptr = g_malloc((sizeof(const char *) * (num_tokens + 1)) + str_len_p1); + s = (char *) &ptr[num_tokens + 1]; + memcpy(s, str, str_len_p1); + + i_token = 0; + + while (TRUE) { + nm_assert(i_token < num_tokens); + ptr[i_token++] = s; + + if (s[0] == '\0') { + nm_assert(f_preserve_empty); + goto done2; + } + nm_assert(f_preserve_empty || !_char_lookup_has(&ch_lookup, s[0])); + + while (!_char_lookup_has(&ch_lookup, s[0])) { + if (G_UNLIKELY(s[0] == '\\' && f_allow_escaping)) { + s++; + if (s[0] == '\0') + goto done2; + s++; + } else if (s[0] == '\0') + goto done2; + else + s++; + } + + nm_assert(_char_lookup_has(&ch_lookup, s[0])); + s[0] = '\0'; + s++; + + if (!f_preserve_empty) { + while (_char_lookup_has(&ch_lookup, s[0])) + s++; + if (s[0] == '\0') + goto done2; + } + } + +done2: + nm_assert(i_token == num_tokens); + ptr[i_token] = NULL; + + if (f_strstrip) { + gsize i; + + i_token = 0; + for (i = 0; ptr[i]; i++) { + s = (char *) nm_str_skip_leading_spaces(ptr[i]); + if (s[0] != '\0') { + char *s_last; + + s_last = &s[strlen(s) - 1]; + while (s_last > s && g_ascii_isspace(s_last[0]) + && (!f_allow_escaping || !_char_is_escaped(s, s_last))) + (s_last--)[0] = '\0'; + } + + if (!f_preserve_empty && s[0] == '\0') + continue; + + ptr[i_token++] = s; + } + + if (i_token == 0) { + g_free(ptr); + return NULL; + } + ptr[i_token] = NULL; + } + + if (f_escaped) { + gsize i, j; + + /* We no longer need ch_lookup for its original purpose. Modify it, so it + * can detect the delimiters, '\\', and (optionally) whitespaces. */ + _char_lookup_table_set_one(&ch_lookup, '\\'); + if (f_strstrip) + _char_lookup_table_set_all(&ch_lookup, NM_ASCII_SPACES); + + for (i_token = 0; ptr[i_token]; i_token++) { + s = (char *) ptr[i_token]; + j = 0; + for (i = 0; s[i] != '\0';) { + if (s[i] == '\\' && _char_lookup_has(&ch_lookup, s[i + 1])) + i++; + s[j++] = s[i++]; + } + s[j] = '\0'; + } + } + + nm_assert(ptr && ptr[0]); + return ptr; +} + +/*****************************************************************************/ + +const char * +nm_utils_escaped_tokens_escape_full(const char * str, + const char * delimiters, + const char * delimiters_as_needed, + NMUtilsEscapedTokensEscapeFlags flags, + char ** out_to_free) +{ + CharLookupTable ch_lookup; + CharLookupTable ch_lookup_as_needed; + gboolean has_ch_lookup_as_needed = FALSE; + char * ret; + gsize str_len; + gsize alloc_len; + gsize n_escapes; + gsize i, j; + gboolean escape_leading_space; + gboolean escape_trailing_space; + gboolean escape_backslash_as_needed; + + nm_assert( + !delimiters_as_needed + || (delimiters_as_needed[0] + && NM_FLAGS_HAS(flags, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED))); + + if (!str || str[0] == '\0') { + *out_to_free = NULL; + return str; + } + + str_len = strlen(str); + + _char_lookup_table_init(&ch_lookup, delimiters); + if (!delimiters || NM_FLAGS_HAS(flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_SPACES)) { + flags &= ~(NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE); + _char_lookup_table_set_all(&ch_lookup, NM_ASCII_SPACES); + } + + if (NM_FLAGS_HAS(flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS)) { + _char_lookup_table_set_one(&ch_lookup, '\\'); + escape_backslash_as_needed = FALSE; + } else if (_char_lookup_has(&ch_lookup, '\\')) + escape_backslash_as_needed = FALSE; + else { + escape_backslash_as_needed = + NM_FLAGS_HAS(flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED); + if (escape_backslash_as_needed) { + if (NM_FLAGS_ANY(flags, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE) + && !_char_lookup_has_all(&ch_lookup, NM_ASCII_SPACES)) { + /* ESCAPE_LEADING_SPACE and ESCAPE_TRAILING_SPACE implies that we escape backslash + * before whitespaces. */ + if (!has_ch_lookup_as_needed) { + has_ch_lookup_as_needed = TRUE; + _char_lookup_table_init(&ch_lookup_as_needed, NULL); + } + _char_lookup_table_set_all(&ch_lookup_as_needed, NM_ASCII_SPACES); + } + if (delimiters_as_needed && !_char_lookup_has_all(&ch_lookup, delimiters_as_needed)) { + if (!has_ch_lookup_as_needed) { + has_ch_lookup_as_needed = TRUE; + _char_lookup_table_init(&ch_lookup_as_needed, NULL); + } + _char_lookup_table_set_all(&ch_lookup_as_needed, delimiters_as_needed); + } + } + } + + escape_leading_space = + NM_FLAGS_HAS(flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE) + && g_ascii_isspace(str[0]) && !_char_lookup_has(&ch_lookup, str[0]); + if (str_len == 1) + escape_trailing_space = FALSE; + else { + escape_trailing_space = + NM_FLAGS_HAS(flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE) + && g_ascii_isspace(str[str_len - 1]) && !_char_lookup_has(&ch_lookup, str[str_len - 1]); + } + + n_escapes = 0; + for (i = 0; str[i] != '\0'; i++) { + if (_char_lookup_has(&ch_lookup, str[i])) + n_escapes++; + else if (str[i] == '\\' && escape_backslash_as_needed + && (_char_lookup_has(&ch_lookup, str[i + 1]) || NM_IN_SET(str[i + 1], '\0', '\\') + || (has_ch_lookup_as_needed + && _char_lookup_has(&ch_lookup_as_needed, str[i + 1])))) + n_escapes++; + } + if (escape_leading_space) + n_escapes++; + if (escape_trailing_space) + n_escapes++; + + if (n_escapes == 0u) { + *out_to_free = NULL; + return str; + } + + alloc_len = str_len + n_escapes + 1u; + ret = g_new(char, alloc_len); + + j = 0; + i = 0; + + if (escape_leading_space) { + ret[j++] = '\\'; + ret[j++] = str[i++]; + } + for (; str[i] != '\0'; i++) { + if (_char_lookup_has(&ch_lookup, str[i])) + ret[j++] = '\\'; + else if (str[i] == '\\' && escape_backslash_as_needed + && (_char_lookup_has(&ch_lookup, str[i + 1]) || NM_IN_SET(str[i + 1], '\0', '\\') + || (has_ch_lookup_as_needed + && _char_lookup_has(&ch_lookup_as_needed, str[i + 1])))) + ret[j++] = '\\'; + ret[j++] = str[i]; + } + if (escape_trailing_space) { + nm_assert(!_char_lookup_has(&ch_lookup, ret[j - 1]) && g_ascii_isspace(ret[j - 1])); + ret[j] = ret[j - 1]; + ret[j - 1] = '\\'; + j++; + } + + nm_assert(j == alloc_len - 1); + ret[j] = '\0'; + nm_assert(strlen(ret) == j); + + *out_to_free = ret; + return ret; +} + +/** + * nm_utils_escaped_tokens_options_split: + * @str: the src string. This string will be modified in-place. + * The output values will point into @str. + * @out_key: (allow-none): the returned output key. This will always be set to @str + * itself. @str will be modified to contain only the unescaped, truncated + * key name. + * @out_val: returns the parsed (and unescaped) value or %NULL, if @str contains + * no '=' delimiter. + * + * Honors backslash escaping to parse @str as "key=value" pairs. Optionally, if no '=' + * is present, @out_val will be returned as %NULL. Backslash can be used to escape + * '=', ',', '\\', and ascii whitespace. Other backslash sequences are taken verbatim. + * + * For keys, '=' obviously must be escaped. For values, that is optional because an + * unescaped '=' is just taken verbatim. For example, in a key, the sequence "\\=" + * must be escaped as "\\\\\\=". For the value, that works too, but "\\\\=" is also + * accepted. + * + * Unescaped Space around the key and value are also removed. Space in general must + * not be escaped, unless they are at the beginning or the end of key/value. + */ +void +nm_utils_escaped_tokens_options_split(char *str, const char **out_key, const char **out_val) +{ + const char *val = NULL; + gsize i; + gsize j; + gsize last_space_idx; + gboolean last_space_has; + + nm_assert(str); + + i = 0; + while (g_ascii_isspace(str[i])) + i++; + + j = 0; + last_space_idx = 0; + last_space_has = FALSE; + while (str[i] != '\0') { + if (g_ascii_isspace(str[i])) { + if (!last_space_has) { + last_space_has = TRUE; + last_space_idx = j; + } + } else { + if (str[i] == '\\') { + if (NM_IN_SET(str[i + 1u], '\\', ',', '=') || g_ascii_isspace(str[i + 1u])) + i++; + } else if (str[i] == '=') { + /* Encounter an unescaped '=' character. When we still parse the key, this + * is the separator we were waiting for. If we are parsing the value, + * we take the character verbatim. */ + if (!val) { + if (last_space_has) { + str[last_space_idx] = '\0'; + j = last_space_idx + 1; + last_space_has = FALSE; + } else + str[j++] = '\0'; + val = &str[j]; + i++; + while (g_ascii_isspace(str[i])) + i++; + continue; + } + } + last_space_has = FALSE; + } + str[j++] = str[i++]; + } + + if (last_space_has) + str[last_space_idx] = '\0'; + else + str[j] = '\0'; + + *out_key = str; + *out_val = val; +} + +/*****************************************************************************/ + +/** + * nm_utils_strsplit_quoted: + * @str: the string to split (e.g. from /proc/cmdline). + * + * This basically does that systemd's extract_first_word() does + * with the flags "EXTRACT_UNQUOTE | EXTRACT_RELAX". This is what + * systemd uses to parse /proc/cmdline, and we do too. + * + * Splits the string. We have nm_utils_strsplit_set() which + * supports a variety of flags. However, extending that already + * complex code to also support quotation and escaping is hard. + * Instead, add a naive implementation. + * + * Returns: (transfer full): the split string. + */ +char ** +nm_utils_strsplit_quoted(const char *str) +{ + gs_unref_ptrarray GPtrArray *arr = NULL; + gs_free char * str_out = NULL; + CharLookupTable ch_lookup; + + nm_assert(str); + + _char_lookup_table_init(&ch_lookup, NM_ASCII_WHITESPACES); + + for (;;) { + char quote; + gsize j; + + while (_char_lookup_has(&ch_lookup, str[0])) + str++; + + if (str[0] == '\0') + break; + + if (!str_out) + str_out = g_new(char, strlen(str) + 1); + + quote = '\0'; + j = 0; + for (;;) { + if (str[0] == '\\') { + str++; + if (str[0] == '\0') + break; + str_out[j++] = str[0]; + str++; + continue; + } + if (quote) { + if (str[0] == '\0') + break; + if (str[0] == quote) { + quote = '\0'; + str++; + continue; + } + str_out[j++] = str[0]; + str++; + continue; + } + if (str[0] == '\0') + break; + if (NM_IN_SET(str[0], '\'', '"')) { + quote = str[0]; + str++; + continue; + } + if (_char_lookup_has(&ch_lookup, str[0])) { + str++; + break; + } + str_out[j++] = str[0]; + str++; + } + + if (!arr) + arr = g_ptr_array_new(); + g_ptr_array_add(arr, g_strndup(str_out, j)); + } + + if (!arr) + return g_new0(char *, 1); + + g_ptr_array_add(arr, NULL); + + /* We want to return an optimally sized strv array, with no excess + * memory allocated. Hence, clone once more. */ + return nm_memdup(arr->pdata, sizeof(char *) * arr->len); +} + +/*****************************************************************************/ + +/** + * 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. + * @needle: the value to search for. The search is done using strcmp(). + * + * Searches @list for @needle and returns the index of the first match (based + * on strcmp()). + * + * For convenience, @list has type 'char**' instead of 'const char **'. + * + * Returns: index of first occurrence or -1 if @needle is not found in @list. + */ +gssize +nm_utils_strv_find_first(char **list, gssize len, const char *needle) +{ + gssize i; + + if (len > 0) { + g_return_val_if_fail(list, -1); + + if (!needle) { + /* if we search a list with known length, %NULL is a valid @needle. */ + for (i = 0; i < len; i++) { + if (!list[i]) + return i; + } + } else { + for (i = 0; i < len; i++) { + if (list[i] && !strcmp(needle, list[i])) + return i; + } + } + } else if (len < 0) { + g_return_val_if_fail(needle, -1); + + if (list) { + for (i = 0; list[i]; i++) { + if (strcmp(needle, list[i]) == 0) + return i; + } + } + } + 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) { + /* we only modify the strings pointed to by @strv if @strip_whitespace is + * requested. Otherwise, the strings themselves are untouched. */ + 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; +} + +/*****************************************************************************/ + +GPtrArray * +_nm_g_ptr_array_copy(GPtrArray * array, + GCopyFunc func, + gpointer user_data, + GDestroyNotify element_free_func) +{ + GPtrArray *new_array; + guint i; + + g_return_val_if_fail(array, NULL); + + new_array = g_ptr_array_new_full(array->len, element_free_func); + for (i = 0; i < array->len; i++) { + g_ptr_array_add(new_array, func ? func(array->pdata[i], user_data) : array->pdata[i]); + } + return new_array; +} + +/*****************************************************************************/ + +int +_nm_utils_ascii_str_to_bool(const char *str, int default_value) +{ + gs_free char *str_free = NULL; + + if (!str) + return default_value; + + str = nm_strstrip_avoid_copy_a(300, str, &str_free); + if (str[0] == '\0') + return default_value; + + if (!g_ascii_strcasecmp(str, "true") || !g_ascii_strcasecmp(str, "yes") + || !g_ascii_strcasecmp(str, "on") || !g_ascii_strcasecmp(str, "1")) + return TRUE; + + if (!g_ascii_strcasecmp(str, "false") || !g_ascii_strcasecmp(str, "no") + || !g_ascii_strcasecmp(str, "off") || !g_ascii_strcasecmp(str, "0")) + return FALSE; + + return default_value; +} + +/*****************************************************************************/ + +NM_CACHED_QUARK_FCN("nm-manager-error-quark", nm_manager_error_quark); + +NM_CACHED_QUARK_FCN("nm-utils-error-quark", nm_utils_error_quark); + +void +nm_utils_error_set_cancelled(GError **error, gboolean is_disposing, const char *instance_name) +{ + if (is_disposing) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_CANCELLED_DISPOSING, + "Disposing %s instance", + instance_name && *instance_name ? instance_name : "source"); + } else { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Request cancelled"); + } +} + +gboolean +nm_utils_error_is_cancelled_or_disposing(GError *error) +{ + if (error) { + if (error->domain == G_IO_ERROR) + return NM_IN_SET(error->code, G_IO_ERROR_CANCELLED); + if (error->domain == NM_UTILS_ERROR) + return NM_IN_SET(error->code, NM_UTILS_ERROR_CANCELLED_DISPOSING); + } + return FALSE; +} + +gboolean +nm_utils_error_is_notfound(GError *error) +{ + if (error) { + if (error->domain == G_IO_ERROR) + return NM_IN_SET(error->code, G_IO_ERROR_NOT_FOUND); + if (error->domain == G_FILE_ERROR) + return NM_IN_SET(error->code, G_FILE_ERROR_NOENT); + } + return FALSE; +} + +/*****************************************************************************/ + +/** + * nm_g_object_set_property: + * @object: the target object + * @property_name: the property name + * @value: the #GValue to set + * @error: (allow-none): optional error argument + * + * A reimplementation of g_object_set_property(), but instead + * returning an error instead of logging a warning. All g_object_set*() + * versions in glib require you to not pass invalid types or they will + * log a g_warning() -- without reporting an error. We don't want that, + * so we need to hack error checking around it. + * + * Returns: whether the value was successfully set. + */ +gboolean +nm_g_object_set_property(GObject * object, + const char * property_name, + const GValue *value, + GError ** error) +{ + GParamSpec * pspec; + nm_auto_unset_gvalue GValue tmp_value = G_VALUE_INIT; + GObjectClass * klass; + + g_return_val_if_fail(G_IS_OBJECT(object), FALSE); + g_return_val_if_fail(property_name != NULL, FALSE); + g_return_val_if_fail(G_IS_VALUE(value), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + /* g_object_class_find_property() does g_param_spec_get_redirect_target(), + * where we differ from a plain g_object_set_property(). */ + pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(object), property_name); + + if (!pspec) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("object class '%s' has no property named '%s'"), + G_OBJECT_TYPE_NAME(object), + property_name); + return FALSE; + } + if (!(pspec->flags & G_PARAM_WRITABLE)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("property '%s' of object class '%s' is not writable"), + pspec->name, + G_OBJECT_TYPE_NAME(object)); + return FALSE; + } + if ((pspec->flags & G_PARAM_CONSTRUCT_ONLY)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("construct property \"%s\" for object '%s' can't be set after construction"), + pspec->name, + G_OBJECT_TYPE_NAME(object)); + return FALSE; + } + + klass = g_type_class_peek(pspec->owner_type); + if (klass == NULL) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("'%s::%s' is not a valid property name; '%s' is not a GObject subtype"), + g_type_name(pspec->owner_type), + pspec->name, + g_type_name(pspec->owner_type)); + return FALSE; + } + + /* provide a copy to work from, convert (if necessary) and validate */ + g_value_init(&tmp_value, pspec->value_type); + if (!g_value_transform(value, &tmp_value)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("unable to set property '%s' of type '%s' from value of type '%s'"), + pspec->name, + g_type_name(pspec->value_type), + G_VALUE_TYPE_NAME(value)); + return FALSE; + } + if (g_param_value_validate(pspec, &tmp_value) && !(pspec->flags & G_PARAM_LAX_VALIDATION)) { + gs_free char *contents = g_strdup_value_contents(value); + + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("value \"%s\" of type '%s' is invalid or out of range for property '%s' of " + "type '%s'"), + contents, + G_VALUE_TYPE_NAME(value), + pspec->name, + g_type_name(pspec->value_type)); + return FALSE; + } + + g_object_set_property(object, property_name, &tmp_value); + return TRUE; +} + +#define _set_property(object, property_name, gtype, gtype_set, value, error) \ + G_STMT_START \ + { \ + nm_auto_unset_gvalue GValue gvalue = {0}; \ + \ + g_value_init(&gvalue, gtype); \ + gtype_set(&gvalue, (value)); \ + return nm_g_object_set_property((object), (property_name), &gvalue, (error)); \ + } \ + G_STMT_END + +gboolean +nm_g_object_set_property_string(GObject * object, + const char *property_name, + const char *value, + GError ** error) +{ + _set_property(object, property_name, G_TYPE_STRING, g_value_set_string, value, error); +} + +gboolean +nm_g_object_set_property_string_static(GObject * object, + const char *property_name, + const char *value, + GError ** error) +{ + _set_property(object, property_name, G_TYPE_STRING, g_value_set_static_string, value, error); +} + +gboolean +nm_g_object_set_property_string_take(GObject * object, + const char *property_name, + char * value, + GError ** error) +{ + _set_property(object, property_name, G_TYPE_STRING, g_value_take_string, value, error); +} + +gboolean +nm_g_object_set_property_boolean(GObject * object, + const char *property_name, + gboolean value, + GError ** error) +{ + _set_property(object, property_name, G_TYPE_BOOLEAN, g_value_set_boolean, !!value, error); +} + +gboolean +nm_g_object_set_property_char(GObject * object, + const char *property_name, + gint8 value, + GError ** error) +{ + /* glib says about G_TYPE_CHAR: + * + * The type designated by G_TYPE_CHAR is unconditionally an 8-bit signed integer. + * + * This is always a (signed!) char. */ + _set_property(object, property_name, G_TYPE_CHAR, g_value_set_schar, value, error); +} + +gboolean +nm_g_object_set_property_uchar(GObject * object, + const char *property_name, + guint8 value, + GError ** error) +{ + _set_property(object, property_name, G_TYPE_UCHAR, g_value_set_uchar, value, error); +} + +gboolean +nm_g_object_set_property_int(GObject *object, const char *property_name, int value, GError **error) +{ + _set_property(object, property_name, G_TYPE_INT, g_value_set_int, value, error); +} + +gboolean +nm_g_object_set_property_int64(GObject * object, + const char *property_name, + gint64 value, + GError ** error) +{ + _set_property(object, property_name, G_TYPE_INT64, g_value_set_int64, value, error); +} + +gboolean +nm_g_object_set_property_uint(GObject * object, + const char *property_name, + guint value, + GError ** error) +{ + _set_property(object, property_name, G_TYPE_UINT, g_value_set_uint, value, error); +} + +gboolean +nm_g_object_set_property_uint64(GObject * object, + const char *property_name, + guint64 value, + GError ** error) +{ + _set_property(object, property_name, G_TYPE_UINT64, g_value_set_uint64, value, error); +} + +gboolean +nm_g_object_set_property_flags(GObject * object, + const char *property_name, + GType gtype, + guint value, + GError ** error) +{ + nm_assert(({ + nm_auto_unref_gtypeclass GTypeClass *gtypeclass = g_type_class_ref(gtype); + G_IS_FLAGS_CLASS(gtypeclass); + })); + _set_property(object, property_name, gtype, g_value_set_flags, value, error); +} + +gboolean +nm_g_object_set_property_enum(GObject * object, + const char *property_name, + GType gtype, + int value, + GError ** error) +{ + nm_assert(({ + nm_auto_unref_gtypeclass GTypeClass *gtypeclass = g_type_class_ref(gtype); + G_IS_ENUM_CLASS(gtypeclass); + })); + _set_property(object, property_name, gtype, g_value_set_enum, value, error); +} + +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); +} + +/*****************************************************************************/ + +/** + * nm_g_type_find_implementing_class_for_property: + * @gtype: the GObject type which has a property @pname + * @pname: the name of the property to look up + * + * This is only a helper function for printf debugging. It's not + * used in actual code. Hence, the function just asserts that + * @pname and @gtype arguments are suitable. It cannot fail. + * + * Returns: the most ancestor type of @gtype, that + * implements the property @pname. It means, it + * searches the type hierarchy to find the type + * that added @pname. + */ +GType +nm_g_type_find_implementing_class_for_property(GType gtype, const char *pname) +{ + nm_auto_unref_gtypeclass GObjectClass *klass = NULL; + GParamSpec * pspec; + + g_return_val_if_fail(pname, G_TYPE_INVALID); + + klass = g_type_class_ref(gtype); + g_return_val_if_fail(G_IS_OBJECT_CLASS(klass), G_TYPE_INVALID); + + pspec = g_object_class_find_property(klass, pname); + g_return_val_if_fail(pspec, G_TYPE_INVALID); + + gtype = G_TYPE_FROM_CLASS(klass); + + while (TRUE) { + nm_auto_unref_gtypeclass GObjectClass *k = NULL; + + k = g_type_class_ref(g_type_parent(gtype)); + + g_return_val_if_fail(G_IS_OBJECT_CLASS(k), G_TYPE_INVALID); + + if (g_object_class_find_property(k, pname) != pspec) + return gtype; + + gtype = G_TYPE_FROM_CLASS(k); + } +} + +/*****************************************************************************/ + +static void +_str_buf_append_c_escape_octal(NMStrBuf *strbuf, char ch) +{ + nm_str_buf_append_c4(strbuf, + '\\', + '0' + ((char) ((((guchar) ch) >> 6) & 07)), + '0' + ((char) ((((guchar) ch) >> 3) & 07)), + '0' + ((char) ((((guchar) ch)) & 07))); +} + +/** + * nm_utils_buf_utf8safe_unescape: + * @str: (allow-none): the string to unescape. The string itself is a NUL terminated + * ASCII string, that can have C-style backslash escape sequences (which + * are to be unescaped). Non-ASCII characters (e.g. UTF-8) are taken verbatim, so + * it doesn't care that this string is UTF-8. However, usually this is a UTF-8 encoded + * string. + * @flags: flags for unescaping. The following flags are supported. + * %NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES performs a g_strstrip() on the input string, + * but preserving escaped spaces. For example, "a\\t " gives "a\t" (that is, the escaped space does + * not get stripped). Likewise, the invalid escape sequence "a\\ " results in "a " (stripping + * the unescaped space, but preserving the escaped one). + * @out_len: (out): the length of the parsed string. + * @to_free: (out): if @str requires unescaping, the function will clone the string. In + * that case, the allocated buffer will be returned here. + * + * See C-style escapes at https://en.wikipedia.org/wiki/Escape_sequences_in_C#Table_of_escape_sequences. + * Note that hex escapes ("\\xhh") and unicode escapes ("\\uhhhh", "\\Uhhhhhhhh") are not supported. + * + * Also, this function is very similar to g_strcompress() but without issuing g_warning() + * assertions and proper handling of "\\000" escape sequences. + * + * Invalid escape sequences (or non-UTF-8 input) are gracefully accepted. For example "\\ " + * is an invalid escape sequence, in this case the backslash is removed and " " gets returned. + * + * The function never leaks secrets in memory. + * + * Returns: the unescaped buffer of length @out_len. If @str is %NULL, this returns %NULL + * and sets @out_len to 0. Otherwise, a non-%NULL binary buffer is returned with + * @out_len bytes. Note that the binary buffer is guaranteed to be NUL terminated. That + * is @result[@out_len] is NUL. + * Note that the result is binary, and may have embedded NUL characters and non-UTF-8. + * If the function can avoid cloning the input string, it will return a pointer inside + * the input @str. For example, if there is no backslash, no cloning is necessary. In that + * case, @to_free will be %NULL. Otherwise, @to_free is set to a newly allocated buffer + * containing the unescaped string and returned. + */ +gconstpointer +nm_utils_buf_utf8safe_unescape(const char * str, + NMUtilsStrUtf8SafeFlags flags, + gsize * out_len, + gpointer * to_free) +{ + gboolean strip_spaces = NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES); + NMStrBuf strbuf; + const char *s; + gsize len; + + g_return_val_if_fail(to_free, NULL); + g_return_val_if_fail(out_len, NULL); + + if (!str) { + *out_len = 0; + *to_free = NULL; + return NULL; + } + + if (strip_spaces) + str = nm_str_skip_leading_spaces(str); + + len = strlen(str); + + s = memchr(str, '\\', len); + if (!s) { + if (strip_spaces && len > 0 && g_ascii_isspace(str[len - 1])) { + len--; + while (len > 0 && g_ascii_isspace(str[len - 1])) + len--; + *out_len = len; + return (*to_free = g_strndup(str, len)); + } + *out_len = len; + *to_free = NULL; + return str; + } + + nm_str_buf_init(&strbuf, len + 1u, FALSE); + + nm_str_buf_append_len(&strbuf, str, s - str); + str = s; + + for (;;) { + char ch; + guint v; + + nm_assert(str[0] == '\\'); + + ch = (++str)[0]; + + if (ch == '\0') { + /* error. Trailing '\\' */ + break; + } + + if (ch >= '0' && ch <= '9') { + v = ch - '0'; + ch = (++str)[0]; + if (ch >= '0' && ch <= '7') { + v = v * 8 + (ch - '0'); + ch = (++str)[0]; + if (ch >= '0' && ch <= '7') { + /* technically, escape sequences larger than \3FF are out of range + * and invalid. We don't check for that, and do the same as + * g_strcompress(): silently clip the value with & 0xFF. */ + v = v * 8 + (ch - '0'); + ++str; + } + } + ch = v; + } else { + switch (ch) { + case 'b': + ch = '\b'; + break; + case 'f': + ch = '\f'; + break; + case 'n': + ch = '\n'; + break; + case 'r': + ch = '\r'; + break; + case 't': + ch = '\t'; + break; + case 'v': + ch = '\v'; + break; + default: + /* Here we handle "\\\\", but all other unexpected escape sequences are really a bug. + * Take them literally, after removing the escape character */ + break; + } + str++; + } + + nm_str_buf_append_c(&strbuf, ch); + + s = strchr(str, '\\'); + if (!s) { + gsize l = strlen(str); + + if (strip_spaces) { + while (l > 0 && g_ascii_isspace(str[l - 1])) + l--; + } + nm_str_buf_append_len(&strbuf, str, l); + break; + } + + nm_str_buf_append_len(&strbuf, str, s - str); + str = s; + } + + /* assert that no reallocation was necessary. For one, unescaping should + * never result in a longer string than the input. Also, when unescaping + * secrets, we want to ensure that we don't leak secrets in memory. */ + nm_assert(strbuf.allocated == len + 1u); + + return (*to_free = nm_str_buf_finalize(&strbuf, out_len)); +} + +/** + * nm_utils_buf_utf8safe_escape: + * @buf: byte array, possibly in utf-8 encoding, may have NUL characters. + * @buflen: the length of @buf in bytes, or -1 if @buf is a NUL terminated + * string. + * @flags: #NMUtilsStrUtf8SafeFlags flags + * @to_free: (out): return the pointer location of the string + * if a copying was necessary. + * + * Based on the assumption, that @buf contains UTF-8 encoded bytes, + * this will return valid UTF-8 sequence, and invalid sequences + * will be escaped with backslash (C escaping, like g_strescape()). + * This is sanitize non UTF-8 characters. The result is valid + * UTF-8. + * + * The operation can be reverted with nm_utils_buf_utf8safe_unescape(). + * Note that if, and only if @buf contains no NUL bytes, the operation + * can also be reverted with g_strcompress(). + * + * Depending on @flags, valid UTF-8 characters are not escaped at all + * (except the escape character '\\'). This is the difference to g_strescape(), + * which escapes all non-ASCII characters. This allows to pass on + * valid UTF-8 characters as-is and can be directly shown to the user + * as UTF-8 -- with exception of the backslash escape character, + * invalid UTF-8 sequences, and other (depending on @flags). + * + * Returns: the escaped input buffer, as valid UTF-8. If no escaping + * is necessary, it returns the input @buf. Otherwise, an allocated + * string @to_free is returned which must be freed by the caller + * with g_free. The escaping can be reverted by g_strcompress(). + **/ +const char * +nm_utils_buf_utf8safe_escape(gconstpointer buf, + gssize buflen, + NMUtilsStrUtf8SafeFlags flags, + char ** to_free) +{ + const char *const str = buf; + const char * p = NULL; + const char * s; + gboolean nul_terminated = FALSE; + NMStrBuf strbuf; + + g_return_val_if_fail(to_free, NULL); + + *to_free = NULL; + + if (buflen == 0) + return NULL; + + if (buflen < 0) { + if (!str) + return NULL; + buflen = strlen(str); + if (buflen == 0) + return str; + nul_terminated = TRUE; + } + + if (g_utf8_validate(str, buflen, &p) && nul_terminated) { + /* note that g_utf8_validate() does not allow NUL character inside @str. Good. + * We can treat @str like a NUL terminated string. */ + if (!NM_STRCHAR_ANY( + str, + ch, + (ch == '\\' + || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) && ch < ' ') + || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) + && ((guchar) ch) >= 127)))) + return str; + } + + nm_str_buf_init(&strbuf, buflen + 5, NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET)); + + s = str; + do { + buflen -= p - s; + nm_assert(buflen >= 0); + + for (; s < p; s++) { + char ch = s[0]; + + nm_assert(ch); + if (ch == '\\') + nm_str_buf_append_c2(&strbuf, '\\', '\\'); + else if ((NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) && ch < ' ') + || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) + && ((guchar) ch) >= 127)) + _str_buf_append_c_escape_octal(&strbuf, ch); + else + nm_str_buf_append_c(&strbuf, ch); + } + + if (buflen <= 0) + break; + + _str_buf_append_c_escape_octal(&strbuf, p[0]); + + buflen--; + if (buflen == 0) + break; + + s = &p[1]; + (void) g_utf8_validate(s, buflen, &p); + } while (TRUE); + + return (*to_free = nm_str_buf_finalize(&strbuf, NULL)); +} + +const char * +nm_utils_buf_utf8safe_escape_bytes(GBytes *bytes, NMUtilsStrUtf8SafeFlags flags, char **to_free) +{ + gconstpointer p; + gsize l; + + if (bytes) + p = g_bytes_get_data(bytes, &l); + else { + p = NULL; + l = 0; + } + + return nm_utils_buf_utf8safe_escape(p, l, flags, to_free); +} + +char * +nm_utils_buf_utf8safe_escape_cp(gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags) +{ + const char *s_const; + char * s; + + s_const = nm_utils_buf_utf8safe_escape(buf, buflen, flags, &s); + nm_assert(!s || s == s_const); + return s ?: g_strdup(s_const); +} + +/*****************************************************************************/ + +const char * +nm_utils_str_utf8safe_unescape(const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free) +{ + const char *res; + gsize len; + + g_return_val_if_fail(to_free, NULL); + + res = nm_utils_buf_utf8safe_unescape(str, flags, &len, (gpointer *) to_free); + + nm_assert((!res && len == 0) || (strlen(res) <= len)); + + return res; +} + +/** + * nm_utils_str_utf8safe_escape: + * @str: NUL terminated input string, possibly in utf-8 encoding + * @flags: #NMUtilsStrUtf8SafeFlags flags + * @to_free: (out): return the pointer location of the string + * if a copying was necessary. + * + * Returns the possible non-UTF-8 NUL terminated string @str + * and uses backslash escaping (C escaping, like g_strescape()) + * to sanitize non UTF-8 characters. The result is valid + * UTF-8. + * + * The operation can be reverted with g_strcompress() or + * nm_utils_str_utf8safe_unescape(). + * + * Depending on @flags, valid UTF-8 characters are not escaped at all + * (except the escape character '\\'). This is the difference to g_strescape(), + * which escapes all non-ASCII characters. This allows to pass on + * valid UTF-8 characters as-is and can be directly shown to the user + * as UTF-8 -- with exception of the backslash escape character, + * invalid UTF-8 sequences, and other (depending on @flags). + * + * Returns: the escaped input string, as valid UTF-8. If no escaping + * is necessary, it returns the input @str. Otherwise, an allocated + * string @to_free is returned which must be freed by the caller + * with g_free. The escaping can be reverted by g_strcompress(). + **/ +const char * +nm_utils_str_utf8safe_escape(const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free) +{ + return nm_utils_buf_utf8safe_escape(str, -1, flags, to_free); +} + +/** + * nm_utils_str_utf8safe_escape_cp: + * @str: NUL terminated input string, possibly in utf-8 encoding + * @flags: #NMUtilsStrUtf8SafeFlags flags + * + * Like nm_utils_str_utf8safe_escape(), except the returned value + * is always a copy of the input and must be freed by the caller. + * + * Returns: the escaped input string in UTF-8 encoding. The returned + * value should be freed with g_free(). + * The escaping can be reverted by g_strcompress(). + **/ +char * +nm_utils_str_utf8safe_escape_cp(const char *str, NMUtilsStrUtf8SafeFlags flags) +{ + char *s; + + nm_utils_str_utf8safe_escape(str, flags, &s); + return s ?: g_strdup(str); +} + +char * +nm_utils_str_utf8safe_unescape_cp(const char *str, NMUtilsStrUtf8SafeFlags flags) +{ + char *s; + + str = nm_utils_str_utf8safe_unescape(str, flags, &s); + return s ?: g_strdup(str); +} + +char * +nm_utils_str_utf8safe_escape_take(char *str, NMUtilsStrUtf8SafeFlags flags) +{ + char *str_to_free; + + nm_utils_str_utf8safe_escape(str, flags, &str_to_free); + if (str_to_free) { + g_free(str); + return str_to_free; + } + 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_nsec) +{ + struct pollfd pollfd = { + .fd = fd, + .events = event, + }; + struct timespec ts, *pts; + int r; + + if (timeout_nsec < 0) + pts = NULL; + else { + ts.tv_sec = (time_t)(timeout_nsec / NM_UTILS_NSEC_PER_SEC); + ts.tv_nsec = (long int) (timeout_nsec % NM_UTILS_NSEC_PER_SEC); + pts = &ts; + } + + r = ppoll(&pollfd, 1, pts, NULL); + if (r < 0) + return -NM_ERRNO_NATIVE(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) { + int errsv = errno; + + if (errsv == EINTR) + continue; + + if (errsv == 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 : -NM_ERRNO_NATIVE(errsv); + } + + 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; +} + +/*****************************************************************************/ + +void +nm_utils_named_value_clear_with_g_free(NMUtilsNamedValue *val) +{ + if (val) { + gs_free gpointer x_name = NULL; + gs_free gpointer x_value = NULL; + + x_name = (gpointer) g_steal_pointer(&val->name); + x_value = g_steal_pointer(&val->value_ptr); + } +} + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMUtilsNamedValue, name) == 0); + +NMUtilsNamedValue * +nm_utils_named_values_from_strdict_full(GHashTable * hash, + guint * out_len, + GCompareDataFunc compare_func, + gpointer user_data, + NMUtilsNamedValue * provided_buffer, + guint provided_buffer_len, + NMUtilsNamedValue **out_allocated_buffer) +{ + GHashTableIter iter; + NMUtilsNamedValue *values; + guint i, len; + + nm_assert(provided_buffer_len == 0 || provided_buffer); + nm_assert(!out_allocated_buffer || !*out_allocated_buffer); + + if (!hash || !(len = g_hash_table_size(hash))) { + NM_SET_OUT(out_len, 0); + return NULL; + } + + if (provided_buffer_len >= len + 1) { + /* the buffer provided by the caller is large enough. Use it. */ + values = provided_buffer; + } else { + /* allocate a new buffer. */ + values = g_new(NMUtilsNamedValue, len + 1); + NM_SET_OUT(out_allocated_buffer, values); + } + + i = 0; + g_hash_table_iter_init(&iter, hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &values[i].name, &values[i].value_ptr)) + i++; + nm_assert(i == len); + values[i].name = NULL; + values[i].value_ptr = NULL; + + if (compare_func) + nm_utils_named_value_list_sort(values, len, compare_func, user_data); + + NM_SET_OUT(out_len, len); + return values; +} + +gssize +nm_utils_named_value_list_find(const NMUtilsNamedValue *arr, + gsize len, + const char * name, + gboolean sorted) +{ + gsize i; + + nm_assert(name); + +#if NM_MORE_ASSERTS > 5 + { + for (i = 0; i < len; i++) { + const NMUtilsNamedValue *v = &arr[i]; + + nm_assert(v->name); + if (sorted && i > 0) + nm_assert(strcmp(arr[i - 1].name, v->name) < 0); + } + } + + nm_assert(!sorted || nm_utils_named_value_list_is_sorted(arr, len, FALSE, NULL, NULL)); +#endif + + if (sorted) { + return nm_utils_array_find_binary_search(arr, + sizeof(NMUtilsNamedValue), + len, + &name, + nm_strcmp_p_with_data, + NULL); + } + for (i = 0; i < len; i++) { + if (nm_streq(arr[i].name, name)) + return i; + } + return ~((gssize) len); +} + +gboolean +nm_utils_named_value_list_is_sorted(const NMUtilsNamedValue *arr, + gsize len, + gboolean accept_duplicates, + GCompareDataFunc compare_func, + gpointer user_data) +{ + gsize i; + int c_limit; + + if (len == 0) + return TRUE; + + g_return_val_if_fail(arr, FALSE); + + if (!compare_func) + compare_func = nm_strcmp_p_with_data; + + c_limit = accept_duplicates ? 0 : -1; + + for (i = 1; i < len; i++) { + int c; + + c = compare_func(&arr[i - 1], &arr[i], user_data); + if (c > c_limit) + return FALSE; + } + return TRUE; +} + +void +nm_utils_named_value_list_sort(NMUtilsNamedValue *arr, + gsize len, + GCompareDataFunc compare_func, + gpointer user_data) +{ + if (len == 0) + return; + + g_return_if_fail(arr); + + if (len == 1) + return; + + g_qsort_with_data(arr, + len, + sizeof(NMUtilsNamedValue), + compare_func ?: nm_strcmp_p_with_data, + user_data); +} + +/*****************************************************************************/ + +gpointer * +nm_utils_hash_keys_to_array(GHashTable * hash, + GCompareDataFunc compare_func, + gpointer user_data, + guint * out_len) +{ + guint len; + gpointer *keys; + + /* by convention, we never return an empty array. In that + * case, always %NULL. */ + if (!hash || g_hash_table_size(hash) == 0) { + NM_SET_OUT(out_len, 0); + return NULL; + } + + keys = g_hash_table_get_keys_as_array(hash, &len); + if (len > 1 && compare_func) { + g_qsort_with_data(keys, len, sizeof(gpointer), compare_func, user_data); + } + NM_SET_OUT(out_len, len); + return keys; +} + +gpointer * +nm_utils_hash_values_to_array(GHashTable * hash, + GCompareDataFunc compare_func, + gpointer user_data, + guint * out_len) +{ + GHashTableIter iter; + gpointer value; + gpointer * arr; + guint i, len; + + if (!hash || (len = g_hash_table_size(hash)) == 0u) { + NM_SET_OUT(out_len, 0); + return NULL; + } + + arr = g_new(gpointer, ((gsize) len) + 1); + i = 0; + g_hash_table_iter_init(&iter, hash); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &value)) + arr[i++] = value; + + nm_assert(i == len); + arr[len] = NULL; + + if (len > 1 && compare_func) { + g_qsort_with_data(arr, len, sizeof(gpointer), compare_func, user_data); + } + + NM_SET_OUT(out_len, len); + return arr; +} + +/*****************************************************************************/ + +/** + * nm_utils_hashtable_equal: + * @a: one #GHashTable + * @b: other #GHashTable + * @treat_null_as_empty: if %TRUE, when either @a or @b is %NULL, it is + * treated like an empty hash. It means, a %NULL hash will compare equal + * to an empty hash. + * @equal_func: the equality function, for comparing the values. + * If %NULL, the values are not compared. In that case, the function + * only checks, if both dictionaries have the same keys -- according + * to @b's key equality function. + * Note that the values of @a will be passed as first argument + * to @equal_func. + * + * Compares two hash tables, whether they have equal content. + * This only makes sense, if @a and @b have the same key types and + * the same key compare-function. + * + * Returns: %TRUE, if both dictionaries have the same content. + */ +gboolean +nm_utils_hashtable_equal(const GHashTable *a, + const GHashTable *b, + gboolean treat_null_as_empty, + GEqualFunc equal_func) +{ + guint n; + GHashTableIter iter; + gconstpointer key, v_a, v_b; + + if (a == b) + return TRUE; + if (!treat_null_as_empty) { + if (!a || !b) + return FALSE; + } + + n = a ? g_hash_table_size((GHashTable *) a) : 0; + if (n != (b ? g_hash_table_size((GHashTable *) b) : 0)) + return FALSE; + + if (n > 0) { + g_hash_table_iter_init(&iter, (GHashTable *) a); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &v_a)) { + if (!g_hash_table_lookup_extended((GHashTable *) b, key, NULL, (gpointer *) &v_b)) + return FALSE; + if (equal_func && !equal_func(v_a, v_b)) + return FALSE; + } + } + + return TRUE; +} + +static gboolean +_utils_hashtable_equal(GHashTable * hash_a, + GHashTable * hash_b, + GCompareDataFunc cmp_values, + gpointer user_data) +{ + GHashTableIter h; + gpointer a_key; + gpointer a_val; + gpointer b_val; + + nm_assert(hash_a); + nm_assert(hash_b); + nm_assert(hash_a != hash_b); + nm_assert(g_hash_table_size(hash_a) == g_hash_table_size(hash_b)); + + /* We rely on both hashes to have the same hash/equal function. Otherwise, we would have to iterate + * both hashes and check whether all keys/values are present in the respective other hash (which + * would be O(n^2), since we couldn't use the plain lookup function. That is not a useful thing + * for this function. */ + + g_hash_table_iter_init(&h, hash_a); + while (g_hash_table_iter_next(&h, &a_key, &a_val)) { + if (!g_hash_table_lookup_extended(hash_b, a_key, NULL, &b_val)) + return FALSE; + + if (!cmp_values) { + /* we accept %NULL compare function to indicate that we don't care about the key. */ + continue; + } + + if (cmp_values(a_val, b_val, user_data) != 0) + return FALSE; + } + + return TRUE; +} + +/** + * nm_utils_hashtable_cmp_equal: + * @a: (allow-none): the hash table or %NULL + * @b: (allow-none): the other hash table or %NULL + * @cmp_values: (allow-none): if %NULL, only the keys + * will be compared. Otherwise, this function is used to + * check whether all keys are equal. + * @user_data: the argument for @cmp_values. + * + * It is required that both @a and @b have the same hash and equals + * function. + * + * Returns: %TRUE, if both keys have the same keys and (if + * @cmp_values is given) all values are the same. + */ +gboolean +nm_utils_hashtable_cmp_equal(const GHashTable *a, + const GHashTable *b, + GCompareDataFunc cmp_values, + gpointer user_data) +{ + GHashTable *hash_a = (GHashTable *) a; + GHashTable *hash_b = (GHashTable *) b; + gboolean same; + guint size; + + if (hash_a == hash_b) + return TRUE; + + if (!hash_a || !hash_b) + return FALSE; + + size = g_hash_table_size(hash_a); + if (size != g_hash_table_size(hash_b)) + return FALSE; + + if (size == 0) + return TRUE; + + same = _utils_hashtable_equal(hash_a, hash_b, cmp_values, user_data); + +#if NM_MORE_ASSERTS > 5 + nm_assert(same == _utils_hashtable_equal(hash_b, hash_a, cmp_values, user_data)); +#endif + + return same; +} + +typedef struct { + gpointer key; + gpointer val; +} HashTableCmpData; + +typedef struct { + GCompareDataFunc cmp_keys; + gpointer user_data; +} HashTableUserData; + +static int +_hashtable_cmp_func(gconstpointer a, gconstpointer b, gpointer user_data) +{ + const HashTableUserData *d = user_data; + const HashTableCmpData * d_a = *((const HashTableCmpData *const *) a); + const HashTableCmpData * d_b = *((const HashTableCmpData *const *) b); + + NM_CMP_RETURN(d->cmp_keys(d_a, d_b, d->user_data)); + return 0; +} + +/** + * nm_utils_hashtable_cmp: + * @a: (allow-none): the hash to compare. May be %NULL. + * @b: (allow-none): the other hash to compare. May be %NULL. + * @do_fast_precheck: if %TRUE, assume that the hashes are equal + * and that it is worth calling nm_utils_hashtable_cmp_equal() first. + * That requires, that both hashes have the same equals function + * which is compatible with the @cmp_keys function. + * @cmp_keys: the compare function for keys. Usually, the hash/equal function + * of both hashes corresponds to this function. If you set @do_fast_precheck + * to false, then this is not a requirement. + * @cmp_values: (allow-none): if %NULL, only the keys are compared. + * Otherwise, the values must are also compared with this function. + * + * Both hashes must have keys/values of the same domain, so that + * they can be effectively compared with @cmp_keys and @cmp_values. + * + * %NULL hashes compare equal to %NULL, but not to empty hashes. + * + * Returns: 0 if both hashes are equal, or -1 or 1 if one of the hashes + * sorts before/after. + */ +int +nm_utils_hashtable_cmp(const GHashTable *a, + const GHashTable *b, + gboolean do_fast_precheck, + GCompareDataFunc cmp_keys, + GCompareDataFunc cmp_values, + gpointer user_data) +{ + GHashTable *hash_a = (GHashTable *) a; + GHashTable *hash_b = (GHashTable *) b; + gs_free HashTableCmpData *cmp_array_free = NULL; + HashTableCmpData * cmp_array_a; + HashTableCmpData * cmp_array_b; + GHashTableIter h; + gpointer i_key; + gpointer i_val; + gsize size2; + guint size; + guint i; + + nm_assert(cmp_keys); + + NM_CMP_SELF(hash_a, hash_b); + + size = g_hash_table_size(hash_a); + + NM_CMP_DIRECT(size, g_hash_table_size(hash_b)); + + if (size == 0) + return 0; + + if (do_fast_precheck) { + gboolean same; + + /* we expect that the hashes are equal and the caller ensures us that they + * use the same hash/equal functions. Do a fast path check first... + * + * It's unclear whether this is worth it. The full comparison is O(n*ln(n)), + * while the fast check (using the hash lookup) is O(n). But then, the pre-check + * makes additional requirements on the hash's hash/equal functions -- the + * full comparison does not make such requirements. */ + same = _utils_hashtable_equal(hash_a, hash_b, cmp_values, user_data); +#if NM_MORE_ASSERTS > 5 + nm_assert(same == _utils_hashtable_equal(hash_b, hash_a, cmp_values, user_data)); +#endif + if (same) + return 0; + } + + size2 = ((gsize) size) * 2u; + if (size2 > 600u / sizeof(HashTableCmpData)) { + cmp_array_free = g_new(HashTableCmpData, size2); + cmp_array_a = cmp_array_free; + } else + cmp_array_a = g_newa(HashTableCmpData, size2); + cmp_array_b = &cmp_array_a[size]; + + i = 0; + g_hash_table_iter_init(&h, hash_a); + while (g_hash_table_iter_next(&h, &i_key, &i_val)) { + nm_assert(i < size); + cmp_array_a[i++] = (HashTableCmpData){ + .key = i_key, + .val = i_val, + }; + } + nm_assert(i == size); + + i = 0; + g_hash_table_iter_init(&h, hash_b); + while (g_hash_table_iter_next(&h, &i_key, &i_val)) { + nm_assert(i < size); + cmp_array_b[i++] = (HashTableCmpData){ + .key = i_key, + .val = i_val, + }; + } + nm_assert(i == size); + + g_qsort_with_data(cmp_array_a, + size, + sizeof(HashTableCmpData), + _hashtable_cmp_func, + &((HashTableUserData){ + .cmp_keys = cmp_keys, + .user_data = user_data, + })); + + g_qsort_with_data(cmp_array_b, + size, + sizeof(HashTableCmpData), + _hashtable_cmp_func, + &((HashTableUserData){ + .cmp_keys = cmp_keys, + .user_data = user_data, + })); + + for (i = 0; i < size; i++) { + NM_CMP_RETURN(cmp_keys(cmp_array_a[i].key, cmp_array_b[i].key, user_data)); + } + + if (cmp_values) { + for (i = 0; i < size; i++) { + NM_CMP_RETURN(cmp_values(cmp_array_a[i].val, cmp_array_b[i].val, user_data)); + } + } + + /* the fast-precheck should have already told that the arrays are equal. */ + nm_assert(!do_fast_precheck); + + return 0; +} + +char ** +nm_utils_strv_make_deep_copied(const char **strv) +{ + gsize i; + + /* it takes a strv list, and copies each + * strings. Note that this updates @strv *in-place* + * and returns it. */ + + if (!strv) + return NULL; + for (i = 0; strv[i]; i++) + strv[i] = g_strdup(strv[i]); + + return (char **) strv; +} + +char ** +nm_utils_strv_make_deep_copied_n(const char **strv, gsize len) +{ + gsize i; + + /* it takes a strv array with len elements, and copies each + * strings. Note that this updates @strv *in-place* + * and returns it. */ + + if (!strv) + return NULL; + for (i = 0; i < len; i++) + strv[i] = g_strdup(strv[i]); + + return (char **) strv; +} + +/** + * @strv: the strv array to copy. It may be %NULL if @len + * is negative or zero (in which case %NULL will be returned). + * @len: the length of strings in @str. If negative, strv is assumed + * to be a NULL terminated array. + * @deep_copied: if %TRUE, clones the individual strings. In that case, + * the returned array must be freed with g_strfreev(). Otherwise, the + * strings themself are not copied. You must take care of who owns the + * strings yourself. + * + * Like g_strdupv(), with two differences: + * + * - accepts a @len parameter for non-null terminated strv array. + * + * - this never returns an empty strv array, but always %NULL if + * there are no strings. + * + * Note that if @len is non-negative, then it still must not + * contain any %NULL pointers within the first @len elements. + * Otherwise, you would leak elements if you try to free the + * array with g_strfreev(). Allowing that would be error prone. + * + * Returns: (transfer full): a clone of the strv array. Always + * %NULL terminated. Depending on @deep_copied, the strings are + * cloned or not. + */ +char ** +_nm_utils_strv_dup(const char *const *strv, gssize len, gboolean deep_copied) +{ + gsize i, l; + char **v; + + if (len < 0) + l = NM_PTRARRAY_LEN(strv); + else + l = len; + if (l == 0) { + /* this function never returns an empty strv array. If you + * need that, handle it yourself. */ + return NULL; + } + + v = g_new(char *, l + 1); + for (i = 0; i < l; i++) { + if (G_UNLIKELY(!strv[i])) { + /* NULL strings are not allowed. Clear the remainder of the array + * and return it (with assertion failure). */ + l++; + for (; i < l; i++) + v[i] = NULL; + g_return_val_if_reached(v); + } + + if (deep_copied) + v[i] = g_strdup(strv[i]); + else + v[i] = (char *) strv[i]; + } + v[l] = NULL; + return v; +} + +const char ** +_nm_utils_strv_dup_packed(const char *const *strv, gssize len) + +{ + gs_free gsize *str_len_free = NULL; + gsize * str_len; + const char ** result; + gsize mem_len; + gsize pre_len; + gsize len2; + char * sbuf; + gsize i; + + nm_assert(len >= -1); + + if (G_LIKELY(len < 0)) { + if (!strv || !strv[0]) { + /* This function never returns an empty strv array. If you need that, handle it + * yourself. */ + return NULL; + } + len2 = NM_PTRARRAY_LEN(strv); + } else { + if (len == 0) + return NULL; + len2 = len; + } + + if (len2 > 300u / sizeof(gsize)) { + str_len_free = g_new(gsize, len2); + str_len = str_len_free; + } else + str_len = g_newa(gsize, len2); + + mem_len = 0; + for (i = 0; i < len2; i++) { + gsize l; + + if (G_LIKELY(strv[i])) + l = strlen(strv[i]) + 1u; + else + l = 0; + str_len[i] = l; + mem_len += l; + } + + pre_len = sizeof(const char *) * (len2 + 1u); + + result = g_malloc(pre_len + mem_len); + sbuf = &(((char *) result)[pre_len]); + for (i = 0; i < len2; i++) { + gsize l; + + if (G_UNLIKELY(!strv[i])) { + /* Technically there is no problem with accepting NULL strings. But that + * does not really result in a strv array, and likely this only happens due + * to a bug. We want to catch such bugs by asserting. + * + * We clear the remainder of the buffer and fail with an assertion. */ + len2++; + for (; i < len2; i++) + result[i] = NULL; + g_return_val_if_reached(result); + } + + result[i] = sbuf; + + l = str_len[i]; + memcpy(sbuf, strv[i], l); + sbuf += l; + } + result[i] = NULL; + nm_assert(i == len2); + nm_assert(sbuf == (&((const char *) result)[pre_len]) + mem_len); + + return result; +} + +/*****************************************************************************/ + +gssize +nm_utils_ptrarray_find_binary_search(gconstpointer * list, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data, + gssize * out_idx_first, + gssize * out_idx_last) +{ + gssize imin, imax, imid, i2min, i2max, i2mid; + int cmp; + + g_return_val_if_fail(list || !len, ~((gssize) 0)); + g_return_val_if_fail(cmpfcn, ~((gssize) 0)); + + imin = 0; + if (len > 0) { + imax = len - 1; + + while (imin <= imax) { + imid = imin + (imax - imin) / 2; + + cmp = cmpfcn(list[imid], needle, user_data); + if (cmp == 0) { + /* we found a matching entry at index imid. + * + * Does the caller request the first/last index as well (in case that + * there are multiple entries which compare equal). */ + + if (out_idx_first) { + i2min = imin; + i2max = imid + 1; + while (i2min <= i2max) { + i2mid = i2min + (i2max - i2min) / 2; + + cmp = cmpfcn(list[i2mid], needle, user_data); + if (cmp == 0) + i2max = i2mid - 1; + else { + nm_assert(cmp < 0); + i2min = i2mid + 1; + } + } + *out_idx_first = i2min; + } + if (out_idx_last) { + i2min = imid + 1; + i2max = imax; + while (i2min <= i2max) { + i2mid = i2min + (i2max - i2min) / 2; + + cmp = cmpfcn(list[i2mid], needle, user_data); + if (cmp == 0) + i2min = i2mid + 1; + else { + nm_assert(cmp > 0); + i2max = i2mid - 1; + } + } + *out_idx_last = i2min - 1; + } + return imid; + } + + if (cmp < 0) + imin = imid + 1; + else + imax = imid - 1; + } + } + + /* return the inverse of @imin. This is a negative number, but + * also is ~imin the position where the value should be inserted. */ + imin = ~imin; + NM_SET_OUT(out_idx_first, imin); + NM_SET_OUT(out_idx_last, imin); + return imin; +} + +/*****************************************************************************/ + +/** + * nm_utils_array_find_binary_search: + * @list: the list to search. It must be sorted according to @cmpfcn ordering. + * @elem_size: the size in bytes of each element in the list + * @len: the number of elements in @list + * @needle: the value that is searched + * @cmpfcn: the compare function. The elements @list are passed as first + * argument to @cmpfcn, while @needle is passed as second. Usually, the + * needle is the same data type as inside the list, however, that is + * not necessary, as long as @cmpfcn takes care to cast the two arguments + * accordingly. + * @user_data: optional argument passed to @cmpfcn + * + * Performs binary search for @needle in @list. On success, returns the + * (non-negative) index where the compare function found the searched element. + * On success, it returns a negative value. Note that the return negative value + * is the bitwise inverse of the position where the element should be inserted. + * + * If the list contains multiple matching elements, an arbitrary index is + * returned. + * + * Returns: the index to the element in the list, or the (negative, bitwise inverted) + * position where it should be. + */ +gssize +nm_utils_array_find_binary_search(gconstpointer list, + gsize elem_size, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data) +{ + gssize imin, imax, imid; + int cmp; + + g_return_val_if_fail(list || !len, ~((gssize) 0)); + g_return_val_if_fail(cmpfcn, ~((gssize) 0)); + g_return_val_if_fail(elem_size > 0, ~((gssize) 0)); + + imin = 0; + if (len == 0) + return ~imin; + + imax = len - 1; + + while (imin <= imax) { + imid = imin + (imax - imin) / 2; + + cmp = cmpfcn(&((const char *) list)[elem_size * imid], needle, user_data); + if (cmp == 0) + return imid; + + if (cmp < 0) + imin = imid + 1; + else + imax = imid - 1; + } + + /* return the inverse of @imin. This is a negative number, but + * also is ~imin the position where the value should be inserted. */ + return ~imin; +} + +/*****************************************************************************/ + +/** + * nm_utils_get_start_time_for_pid: + * @pid: the process identifier + * @out_state: return the state character, like R, S, Z. See `man 5 proc`. + * @out_ppid: parent process id + * + * Originally copied from polkit source (src/polkit/polkitunixprocess.c) + * and adjusted. + * + * Returns: the timestamp when the process started (by parsing /proc/$PID/stat). + * If an error occurs (e.g. the process does not exist), 0 is returned. + * + * The returned start time counts since boot, in the unit HZ (with HZ usually being (1/100) seconds) + **/ +guint64 +nm_utils_get_start_time_for_pid(pid_t pid, char *out_state, pid_t *out_ppid) +{ + guint64 start_time; + char filename[256]; + gs_free char * contents = NULL; + size_t length; + gs_free const char **tokens = NULL; + char * p; + char state = ' '; + gint64 ppid = 0; + + start_time = 0; + contents = NULL; + + g_return_val_if_fail(pid > 0, 0); + + G_STATIC_ASSERT_EXPR(sizeof(GPid) >= sizeof(pid_t)); + + nm_sprintf_buf(filename, "/proc/%" G_PID_FORMAT "/stat", (GPid) pid); + + if (!g_file_get_contents(filename, &contents, &length, NULL)) + goto fail; + + /* start time is the token at index 19 after the '(process name)' entry - since only this + * field can contain the ')' character, search backwards for this to avoid malicious + * processes trying to fool us + */ + p = strrchr(contents, ')'); + if (!p) + goto fail; + p += 2; /* skip ') ' */ + if (p - contents >= (int) length) + goto fail; + + state = p[0]; + + tokens = nm_utils_strsplit_set(p, " "); + + if (NM_PTRARRAY_LEN(tokens) < 20) + goto fail; + + if (out_ppid) { + ppid = _nm_utils_ascii_str_to_int64(tokens[1], 10, 1, G_MAXINT, 0); + if (ppid == 0) + goto fail; + } + + start_time = _nm_utils_ascii_str_to_int64(tokens[19], 10, 1, G_MAXINT64, 0); + if (start_time == 0) + goto fail; + + NM_SET_OUT(out_state, state); + NM_SET_OUT(out_ppid, ppid); + return start_time; + +fail: + NM_SET_OUT(out_state, ' '); + NM_SET_OUT(out_ppid, 0); + return 0; +} + +/*****************************************************************************/ + +/** + * _nm_utils_strv_sort: + * @strv: pointer containing strings that will be sorted + * in-place, %NULL is allowed, unless @len indicates + * that there are more elements. + * @len: the number of elements in strv. If negative, + * strv must be a NULL terminated array and the length + * will be calculated first. If @len is a positive + * number, @strv is allowed to contain %NULL strings + * too. + * + * Ascending sort of the array @strv inplace, using plain strcmp() string + * comparison. + */ +void +_nm_utils_strv_sort(const char **strv, gssize len) +{ + GCompareDataFunc cmp; + gsize l; + + if (len < 0) { + l = NM_PTRARRAY_LEN(strv); + cmp = nm_strcmp_p_with_data; + } else { + l = len; + cmp = nm_strcmp0_p_with_data; + } + + if (l <= 1) + return; + + nm_assert(l <= (gsize) G_MAXINT); + + g_qsort_with_data(strv, l, sizeof(const char *), cmp, NULL); +} + +/** + * _nm_utils_strv_cmp_n: + * @strv1: a string array + * @len1: the length of @strv1, or -1 for NULL terminated array. + * @strv2: a string array + * @len2: the length of @strv2, or -1 for NULL terminated array. + * + * Note that + * - len == -1 && strv == NULL + * is treated like a %NULL argument and compares differently from + * other arrays. + * + * Note that an empty array can be represented as + * - len == -1 && strv && !strv[0] + * - len == 0 && !strv + * - len == 0 && strv + * These 3 forms all compare equal. + * It also means, if length is 0, then it is permissible for strv to be %NULL. + * + * The strv arrays may contain %NULL strings (if len is positive). + * + * Returns: 0 if the arrays are equal (using strcmp). + **/ +int +_nm_utils_strv_cmp_n(const char *const *strv1, gssize len1, const char *const *strv2, gssize len2) +{ + gsize n, n2; + + if (len1 < 0) { + if (!strv1) + return (len2 < 0 && !strv2) ? 0 : -1; + n = NM_PTRARRAY_LEN(strv1); + } else + n = len1; + + if (len2 < 0) { + if (!strv2) + return 1; + n2 = NM_PTRARRAY_LEN(strv2); + } else + n2 = len2; + + NM_CMP_DIRECT(n, n2); + for (; n > 0; n--, strv1++, strv2++) + NM_CMP_DIRECT_STRCMP0(*strv1, *strv2); + return 0; +} + +/*****************************************************************************/ + +/** + * nm_utils_g_slist_find_str: + * @list: the #GSList with NUL terminated strings to search + * @needle: the needle string to look for. + * + * Search the list for @needle and return the first found match + * (or %NULL if not found). Uses strcmp() for finding the first matching + * element. + * + * Returns: the #GSList element with @needle as string value or + * %NULL if not found. + */ +GSList * +nm_utils_g_slist_find_str(const GSList *list, const char *needle) +{ + nm_assert(needle); + + for (; list; list = list->next) { + nm_assert(list->data); + if (nm_streq(list->data, needle)) + return (GSList *) list; + } + return NULL; +} + +/** + * nm_utils_g_slist_strlist_cmp: + * @a: the left #GSList of strings + * @b: the right #GSList of strings to compare. + * + * Compares two string lists. The data elements are compared with + * strcmp(), allowing %NULL elements. + * + * Returns: 0, 1, or -1, depending on how the lists compare. + */ +int +nm_utils_g_slist_strlist_cmp(const GSList *a, const GSList *b) +{ + while (TRUE) { + if (!a) + return !b ? 0 : -1; + if (!b) + return 1; + NM_CMP_DIRECT_STRCMP0(a->data, b->data); + a = a->next; + b = b->next; + } +} + +char * +nm_utils_g_slist_strlist_join(const GSList *a, const char *separator) +{ + GString *str = NULL; + + if (!a) + return NULL; + + for (; a; a = a->next) { + if (!str) + str = g_string_new(NULL); + else + g_string_append(str, separator); + g_string_append(str, a->data); + } + return g_string_free(str, FALSE); +} + +/*****************************************************************************/ + +NMUtilsUserData * +_nm_utils_user_data_pack(int nargs, gconstpointer *args) +{ + int i; + gpointer *data; + + nm_assert(nargs > 0); + nm_assert(args); + + data = g_slice_alloc(((gsize) nargs) * sizeof(gconstpointer)); + for (i = 0; i < nargs; i++) + data[i] = (gpointer) args[i]; + return (NMUtilsUserData *) data; +} + +void +_nm_utils_user_data_unpack(NMUtilsUserData *user_data, int nargs, ...) +{ + gpointer *data = (gpointer *) user_data; + va_list ap; + int i; + + nm_assert(data); + nm_assert(nargs > 0); + + va_start(ap, nargs); + for (i = 0; i < nargs; i++) { + gpointer *dst; + + dst = va_arg(ap, gpointer *); + nm_assert(dst); + + *dst = data[i]; + } + va_end(ap); + + g_slice_free1(((gsize) nargs) * sizeof(gconstpointer), data); +} + +/*****************************************************************************/ + +typedef struct { + gpointer callback_user_data; + GCancellable * cancellable; + GSource * source; + NMUtilsInvokeOnIdleCallback callback; + gulong cancelled_id; +} InvokeOnIdleData; + +static gboolean +_nm_utils_invoke_on_idle_cb_idle(gpointer user_data) +{ + InvokeOnIdleData *data = user_data; + + nm_clear_g_signal_handler(data->cancellable, &data->cancelled_id); + + data->callback(data->callback_user_data, data->cancellable); + + nm_g_object_unref(data->cancellable); + g_source_destroy(data->source); + nm_g_slice_free(data); + return G_SOURCE_REMOVE; +} + +static void +_nm_utils_invoke_on_idle_cb_cancelled(GCancellable *cancellable, InvokeOnIdleData *data) +{ + /* on cancellation, we invoke the callback synchronously. */ + nm_clear_g_signal_handler(data->cancellable, &data->cancelled_id); + nm_clear_g_source_inst(&data->source); + data->callback(data->callback_user_data, data->cancellable); + nm_g_object_unref(data->cancellable); + nm_g_slice_free(data); +} + +static void +_nm_utils_invoke_on_idle_start(gboolean use_timeout, + guint timeout_msec, + GCancellable * cancellable, + NMUtilsInvokeOnIdleCallback callback, + gpointer callback_user_data) +{ + InvokeOnIdleData *data; + GSource * source; + + g_return_if_fail(callback); + + data = g_slice_new(InvokeOnIdleData); + *data = (InvokeOnIdleData){ + .callback = callback, + .callback_user_data = callback_user_data, + .cancellable = nm_g_object_ref(cancellable), + .cancelled_id = 0, + }; + + if (cancellable) { + if (g_cancellable_is_cancelled(cancellable)) { + /* the cancellable is already cancelled. We ignore the timeout + * and always schedule an idle action. */ + use_timeout = FALSE; + } else { + /* if we are passed a non-cancelled cancellable, we register to the "cancelled" + * signal an invoke the callback synchronously (from the signal handler). + * + * We don't do that, + * - if the cancellable is already cancelled (because we don't want to invoke + * the callback synchronously from the caller). + * - if we have no cancellable at hand. */ + data->cancelled_id = g_signal_connect(cancellable, + "cancelled", + G_CALLBACK(_nm_utils_invoke_on_idle_cb_cancelled), + data); + } + } + + if (use_timeout) { + source = nm_g_timeout_source_new(timeout_msec, + G_PRIORITY_DEFAULT, + _nm_utils_invoke_on_idle_cb_idle, + data, + NULL); + } else { + source = + nm_g_idle_source_new(G_PRIORITY_DEFAULT, _nm_utils_invoke_on_idle_cb_idle, data, NULL); + } + + /* use the current thread default context. */ + g_source_attach(source, g_main_context_get_thread_default()); + + data->source = source; +} + +void +nm_utils_invoke_on_idle(GCancellable * cancellable, + NMUtilsInvokeOnIdleCallback callback, + gpointer callback_user_data) +{ + _nm_utils_invoke_on_idle_start(FALSE, 0, cancellable, callback, callback_user_data); +} + +void +nm_utils_invoke_on_timeout(guint timeout_msec, + GCancellable * cancellable, + NMUtilsInvokeOnIdleCallback callback, + gpointer callback_user_data) +{ + _nm_utils_invoke_on_idle_start(TRUE, timeout_msec, cancellable, callback, callback_user_data); +} + +/*****************************************************************************/ + +int +nm_utils_getpagesize(void) +{ + static volatile int val = 0; + long l; + int v; + + v = g_atomic_int_get(&val); + + if (G_UNLIKELY(v == 0)) { + l = sysconf(_SC_PAGESIZE); + + g_return_val_if_fail(l > 0 && l < G_MAXINT, 4 * 1024); + + v = (int) l; + if (!g_atomic_int_compare_and_exchange(&val, 0, v)) { + v = g_atomic_int_get(&val); + g_return_val_if_fail(v > 0, 4 * 1024); + } + } + + nm_assert(v > 0); +#if NM_MORE_ASSERTS > 5 + nm_assert(v == getpagesize()); + nm_assert(v == sysconf(_SC_PAGESIZE)); +#endif + + return v; +} + +gboolean +nm_utils_memeqzero(gconstpointer data, gsize length) +{ + const unsigned char *p = data; + int len; + + /* Taken from https://github.com/rustyrussell/ccan/blob/9d2d2c49f053018724bcc6e37029da10b7c3d60d/ccan/mem/mem.c#L92, + * CC-0 licensed. */ + + /* Check first 16 bytes manually */ + for (len = 0; len < 16; len++) { + if (!length) + return TRUE; + if (*p) + return FALSE; + p++; + length--; + } + + /* Now we know that's zero, memcmp with self. */ + return memcmp(data, p, length) == 0; +} + +/** + * nm_utils_bin2hexstr_full: + * @addr: pointer of @length bytes. If @length is zero, this may + * also be %NULL. + * @length: number of bytes in @addr. May also be zero, in which + * case this will return an empty string. + * @delimiter: either '\0', otherwise the output string will have the + * given delimiter character between each two hex numbers. + * @upper_case: if TRUE, use upper case ASCII characters for hex. + * @out: if %NULL, the function will allocate a new buffer of + * either (@length*2+1) or (@length*3) bytes, depending on whether + * a @delimiter is specified. In that case, the allocated buffer will + * be returned and must be freed by the caller. + * If not %NULL, the buffer must already be preallocated and contain + * at least (@length*2+1) or (@length*3) bytes, depending on the delimiter. + * If @length is zero, then of course at least one byte will be allocated + * or @out (if given) must contain at least room for the trailing NUL byte. + * + * Returns: the binary value converted to a hex string. If @out is given, + * this always returns @out. If @out is %NULL, a newly allocated string + * is returned. This never returns %NULL, for buffers of length zero + * an empty string is returned. + */ +char * +nm_utils_bin2hexstr_full(gconstpointer addr, + gsize length, + char delimiter, + gboolean upper_case, + char * out) +{ + const guint8 *in = addr; + const char * LOOKUP = upper_case ? "0123456789ABCDEF" : "0123456789abcdef"; + char * out0; + + if (out) + out0 = out; + else { + out0 = out = + g_new(char, length == 0 ? 1u : (delimiter == '\0' ? length * 2u + 1u : length * 3u)); + } + + /* @out must contain at least @length*3 bytes if @delimiter is set, + * otherwise, @length*2+1. */ + + if (length > 0) { + nm_assert(in); + for (;;) { + const guint8 v = *in++; + + *out++ = LOOKUP[v >> 4]; + *out++ = LOOKUP[v & 0x0F]; + length--; + if (!length) + break; + if (delimiter) + *out++ = delimiter; + } + } + + *out = '\0'; + return out0; +} + +guint8 * +nm_utils_hexstr2bin_full(const char *hexstr, + gboolean allow_0x_prefix, + gboolean delimiter_required, + gboolean hexdigit_pairs_required, + const char *delimiter_candidates, + gsize required_len, + guint8 * buffer, + gsize buffer_len, + gsize * out_len) +{ + const char *in = hexstr; + guint8 * out = buffer; + gboolean delimiter_has = TRUE; + guint8 delimiter = '\0'; + gsize len; + + nm_assert(hexstr); + nm_assert(buffer); + nm_assert(required_len > 0 || out_len); + + if (allow_0x_prefix && in[0] == '0' && in[1] == 'x') + in += 2; + + while (TRUE) { + const guint8 d1 = in[0]; + guint8 d2; + int i1, i2; + + i1 = nm_utils_hexchar_to_int(d1); + if (i1 < 0) + goto fail; + + /* If there's no leading zero (ie "aa:b:cc") then fake it */ + d2 = in[1]; + if (d2 && (i2 = nm_utils_hexchar_to_int(d2)) >= 0) { + *out++ = (i1 << 4) + i2; + d2 = in[2]; + if (!d2) + break; + in += 2; + } else { + /* Fake leading zero */ + *out++ = i1; + if (!d2) { + if (!delimiter_has || hexdigit_pairs_required) { + /* when using no delimiter, there must be pairs of hex chars */ + goto fail; + } + break; + } else if (hexdigit_pairs_required) + goto fail; + in += 1; + } + + if (--buffer_len == 0) + goto fail; + + if (delimiter_has) { + if (d2 != delimiter) { + if (delimiter) + goto fail; + if (delimiter_candidates) { + while (delimiter_candidates[0]) { + if (delimiter_candidates++[0] == d2) + delimiter = d2; + } + } + if (!delimiter) { + if (delimiter_required) + goto fail; + delimiter_has = FALSE; + continue; + } + } + in++; + } + } + + len = out - buffer; + if (required_len == 0 || len == required_len) { + NM_SET_OUT(out_len, len); + return buffer; + } + +fail: + NM_SET_OUT(out_len, 0); + return NULL; +} + +guint8 * +nm_utils_hexstr2bin_alloc(const char *hexstr, + gboolean allow_0x_prefix, + gboolean delimiter_required, + const char *delimiter_candidates, + gsize required_len, + gsize * out_len) +{ + guint8 *buffer; + gsize buffer_len, len; + + if (G_UNLIKELY(!hexstr)) { + NM_SET_OUT(out_len, 0); + g_return_val_if_fail(hexstr, NULL); + } + + nm_assert(required_len > 0 || out_len); + + if (allow_0x_prefix && hexstr[0] == '0' && hexstr[1] == 'x') + hexstr += 2; + + if (!hexstr[0]) + goto fail; + + if (required_len > 0) + buffer_len = required_len; + else + buffer_len = strlen(hexstr) / 2 + 3; + + buffer = g_malloc(buffer_len); + + if (nm_utils_hexstr2bin_full(hexstr, + FALSE, + delimiter_required, + FALSE, + delimiter_candidates, + required_len, + buffer, + buffer_len, + &len)) { + NM_SET_OUT(out_len, len); + return buffer; + } + + g_free(buffer); + +fail: + NM_SET_OUT(out_len, 0); + return NULL; +} + +/*****************************************************************************/ + +GVariant * +nm_utils_gvariant_vardict_filter(GVariant *src, + gboolean (*filter_fcn)(const char *key, + GVariant * val, + char ** out_key, + GVariant ** out_val, + gpointer user_data), + gpointer user_data) +{ + GVariantIter iter; + GVariantBuilder builder; + const char * key; + GVariant * val; + + g_return_val_if_fail(src && g_variant_is_of_type(src, G_VARIANT_TYPE_VARDICT), NULL); + g_return_val_if_fail(filter_fcn, NULL); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + g_variant_iter_init(&iter, src); + while (g_variant_iter_next(&iter, "{&sv}", &key, &val)) { + _nm_unused gs_unref_variant GVariant *val_free = val; + gs_free char * key2 = NULL; + gs_unref_variant GVariant *val2 = NULL; + + if (filter_fcn(key, val, &key2, &val2, user_data)) { + g_variant_builder_add(&builder, "{sv}", key2 ?: key, val2 ?: val); + } + } + + return g_variant_builder_end(&builder); +} + +static gboolean +_gvariant_vardict_filter_drop_one(const char *key, + GVariant * val, + char ** out_key, + GVariant ** out_val, + gpointer user_data) +{ + return !nm_streq(key, user_data); +} + +GVariant * +nm_utils_gvariant_vardict_filter_drop_one(GVariant *src, const char *key) +{ + return nm_utils_gvariant_vardict_filter(src, _gvariant_vardict_filter_drop_one, (gpointer) key); +} + +/*****************************************************************************/ + +static gboolean +debug_key_matches(const char *key, const char *token, guint length) +{ + /* may not call GLib functions: see note in g_parse_debug_string() */ + for (; length; length--, key++, token++) { + char k = (*key == '_') ? '-' : g_ascii_tolower(*key); + char t = (*token == '_') ? '-' : g_ascii_tolower(*token); + + if (k != t) + return FALSE; + } + + return *key == '\0'; +} + +/** + * nm_utils_parse_debug_string: + * @string: the string to parse + * @keys: the debug keys + * @nkeys: number of entries in @keys + * + * Similar to g_parse_debug_string(), but does not special + * case "help" or "all". + * + * Returns: the flags + */ +guint +nm_utils_parse_debug_string(const char *string, const GDebugKey *keys, guint nkeys) +{ + guint i; + guint result = 0; + const char *q; + + if (string == NULL) + return 0; + + while (*string) { + q = strpbrk(string, ":;, \t"); + if (!q) + q = string + strlen(string); + + for (i = 0; i < nkeys; i++) { + if (debug_key_matches(keys[i].key, string, q - string)) + result |= keys[i].value; + } + + string = q; + if (*string) + string++; + } + + return result; +} + +/*****************************************************************************/ + +GSource * +nm_g_idle_source_new(int priority, + GSourceFunc func, + gpointer user_data, + GDestroyNotify destroy_notify) +{ + GSource *source; + + source = g_idle_source_new(); + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority(source, priority); + g_source_set_callback(source, func, user_data, destroy_notify); + return source; +} + +GSource * +nm_g_timeout_source_new(guint timeout_msec, + int priority, + GSourceFunc func, + gpointer user_data, + GDestroyNotify destroy_notify) +{ + GSource *source; + + source = g_timeout_source_new(timeout_msec); + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority(source, priority); + g_source_set_callback(source, func, user_data, destroy_notify); + return source; +} + +GSource * +nm_g_timeout_source_new_seconds(guint timeout_sec, + int priority, + GSourceFunc func, + gpointer user_data, + GDestroyNotify destroy_notify) +{ + GSource *source; + + source = g_timeout_source_new_seconds(timeout_sec); + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority(source, priority); + g_source_set_callback(source, func, user_data, destroy_notify); + return source; +} + +GSource * +nm_g_unix_signal_source_new(int signum, + int priority, + GSourceFunc handler, + gpointer user_data, + GDestroyNotify notify) +{ + GSource *source; + + source = g_unix_signal_source_new(signum); + + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority(source, priority); + g_source_set_callback(source, handler, user_data, notify); + return source; +} + +GSource * +nm_g_unix_fd_source_new(int fd, + GIOCondition io_condition, + int priority, + gboolean (*source_func)(int fd, GIOCondition condition, gpointer user_data), + gpointer user_data, + GDestroyNotify destroy_notify) +{ + GSource *source; + + source = g_unix_fd_source_new(fd, io_condition); + + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority(source, priority); + g_source_set_callback(source, G_SOURCE_FUNC(source_func), user_data, destroy_notify); + return source; +} + +/*****************************************************************************/ + +#define _CTX_LOG(fmt, ...) \ + G_STMT_START \ + { \ + if (FALSE) { \ + gint64 _ts = g_get_monotonic_time() / 100; \ + \ + g_printerr(">>>> [%" G_GINT64_FORMAT ".%05" G_GINT64_FORMAT "] [src:%p]: " fmt "\n", \ + _ts / 10000, \ + _ts % 10000, \ + (ctx_src), \ + ##__VA_ARGS__); \ + } \ + } \ + G_STMT_END + +typedef struct { + int fd; + guint events; + guint registered_events; + union { + int one; + int *many; + } idx; + gpointer tag; + bool stale : 1; + bool has_many_idx : 1; +} PollData; + +typedef struct { + GSource source; + GMainContext *context; + GHashTable * fds; + GPollFD * fds_arr; + guint fds_len; + int max_priority; + bool acquired : 1; +} CtxIntegSource; + +static void +_poll_data_free(gpointer user_data) +{ + PollData *poll_data = user_data; + + if (poll_data->has_many_idx) + g_free(poll_data->idx.many); + nm_g_slice_free(poll_data); +} + +static void +_ctx_integ_source_reacquire(CtxIntegSource *ctx_src) +{ + if (G_LIKELY(ctx_src->acquired && g_main_context_is_owner(ctx_src->context))) + return; + + /* the parent context now iterates on a different thread. + * We need to release and reacquire the inner context. */ + + if (ctx_src->acquired) + g_main_context_release(ctx_src->context); + + if (G_UNLIKELY(!g_main_context_acquire(ctx_src->context))) { + /* Nobody is supposed to reacquire the context while we use it. This is a bug + * of the user. */ + ctx_src->acquired = FALSE; + g_return_if_reached(); + } + ctx_src->acquired = TRUE; +} + +static gboolean +_ctx_integ_source_prepare(GSource *source, int *out_timeout) +{ + CtxIntegSource *ctx_src = ((CtxIntegSource *) source); + int max_priority; + int timeout = -1; + gboolean any_ready; + GHashTableIter h_iter; + PollData * poll_data; + gboolean fds_changed; + GPollFD new_fds_stack[300u / sizeof(GPollFD)]; + gs_free GPollFD *new_fds_heap = NULL; + GPollFD * new_fds; + guint new_fds_len; + guint new_fds_alloc; + guint i; + + _CTX_LOG("prepare..."); + + _ctx_integ_source_reacquire(ctx_src); + + any_ready = g_main_context_prepare(ctx_src->context, &max_priority); + + new_fds_alloc = NM_MAX(G_N_ELEMENTS(new_fds_stack), ctx_src->fds_len); + + if (new_fds_alloc > G_N_ELEMENTS(new_fds_stack)) { + new_fds_heap = g_new(GPollFD, new_fds_alloc); + new_fds = new_fds_heap; + } else + new_fds = new_fds_stack; + + for (;;) { + int l; + + nm_assert(new_fds_alloc <= (guint) G_MAXINT); + + l = g_main_context_query(ctx_src->context, + max_priority, + &timeout, + new_fds, + (int) new_fds_alloc); + nm_assert(l >= 0); + + new_fds_len = (guint) l; + + if (G_LIKELY(new_fds_len <= new_fds_alloc)) + break; + + new_fds_alloc = new_fds_len; + g_free(new_fds_heap); + new_fds_heap = g_new(GPollFD, new_fds_alloc); + new_fds = new_fds_heap; + } + + fds_changed = FALSE; + if (new_fds_len != ctx_src->fds_len) + fds_changed = TRUE; + else { + for (i = 0; i < new_fds_len; i++) { + if (new_fds[i].fd != ctx_src->fds_arr[i].fd + || new_fds[i].events != ctx_src->fds_arr[i].events) { + fds_changed = TRUE; + break; + } + } + } + + if (G_UNLIKELY(fds_changed)) { + g_free(ctx_src->fds_arr); + ctx_src->fds_len = new_fds_len; + if (G_LIKELY(new_fds == new_fds_stack) || new_fds_alloc != new_fds_len) + ctx_src->fds_arr = nm_memdup(new_fds, sizeof(*new_fds) * new_fds_len); + else + ctx_src->fds_arr = g_steal_pointer(&new_fds_heap); + + g_hash_table_iter_init(&h_iter, ctx_src->fds); + while (g_hash_table_iter_next(&h_iter, (gpointer *) &poll_data, NULL)) + poll_data->stale = TRUE; + + for (i = 0; i < ctx_src->fds_len; i++) { + const GPollFD *fd = &ctx_src->fds_arr[i]; + + poll_data = g_hash_table_lookup(ctx_src->fds, &fd->fd); + + if (G_UNLIKELY(!poll_data)) { + poll_data = g_slice_new(PollData); + *poll_data = (PollData){ + .fd = fd->fd, + .idx.one = i, + .has_many_idx = FALSE, + .events = fd->events, + .registered_events = 0, + .tag = NULL, + .stale = FALSE, + }; + g_hash_table_add(ctx_src->fds, poll_data); + nm_assert(poll_data == g_hash_table_lookup(ctx_src->fds, &fd->fd)); + continue; + } + + if (G_LIKELY(poll_data->stale)) { + if (poll_data->has_many_idx) { + g_free(poll_data->idx.many); + poll_data->has_many_idx = FALSE; + } + poll_data->events = fd->events; + poll_data->idx.one = i; + poll_data->stale = FALSE; + continue; + } + + /* How odd. We have duplicate FDs. In fact, currently g_main_context_query() always + * coalesces the FDs and this cannot happen. However, that is not documented behavior, + * so we should not rely on that. So we need to keep a list of indexes... */ + poll_data->events |= fd->events; + if (!poll_data->has_many_idx) { + int idx0; + + idx0 = poll_data->idx.one; + poll_data->has_many_idx = TRUE; + poll_data->idx.many = g_new(int, 4); + poll_data->idx.many[0] = 2; /* number allocated */ + poll_data->idx.many[1] = 2; /* number used */ + poll_data->idx.many[2] = idx0; + poll_data->idx.many[3] = i; + } else { + if (poll_data->idx.many[0] == poll_data->idx.many[1]) { + poll_data->idx.many[0] *= 2; + poll_data->idx.many = + g_realloc(poll_data->idx.many, sizeof(int) * (2 + poll_data->idx.many[0])); + } + poll_data->idx.many[2 + poll_data->idx.many[1]] = i; + poll_data->idx.many[1]++; + } + } + + g_hash_table_iter_init(&h_iter, ctx_src->fds); + while (g_hash_table_iter_next(&h_iter, (gpointer *) &poll_data, NULL)) { + if (poll_data->stale) { + nm_assert(poll_data->tag); + nm_assert(poll_data->events == poll_data->registered_events); + _CTX_LOG("prepare: remove poll fd=%d, events=0x%x", + poll_data->fd, + poll_data->events); + g_source_remove_unix_fd(&ctx_src->source, poll_data->tag); + g_hash_table_iter_remove(&h_iter); + continue; + } + if (!poll_data->tag) { + _CTX_LOG("prepare: add poll fd=%d, events=0x%x", poll_data->fd, poll_data->events); + poll_data->registered_events = poll_data->events; + poll_data->tag = g_source_add_unix_fd(&ctx_src->source, + poll_data->fd, + poll_data->registered_events); + continue; + } + if (poll_data->registered_events != poll_data->events) { + _CTX_LOG("prepare: update poll fd=%d, events=0x%x", + poll_data->fd, + poll_data->events); + poll_data->registered_events = poll_data->events; + g_source_modify_unix_fd(&ctx_src->source, + poll_data->tag, + poll_data->registered_events); + } + } + } + + NM_SET_OUT(out_timeout, timeout); + ctx_src->max_priority = max_priority; + + _CTX_LOG("prepare: done, any-ready=%d, timeout=%d, max-priority=%d", + any_ready, + timeout, + max_priority); + + /* we always need to poll, because we have some file descriptors. */ + return FALSE; +} + +static gboolean +_ctx_integ_source_check(GSource *source) +{ + CtxIntegSource *ctx_src = ((CtxIntegSource *) source); + GHashTableIter h_iter; + gboolean some_ready; + PollData * poll_data; + + nm_assert(ctx_src->context); + + _CTX_LOG("check"); + + _ctx_integ_source_reacquire(ctx_src); + + g_hash_table_iter_init(&h_iter, ctx_src->fds); + while (g_hash_table_iter_next(&h_iter, (gpointer *) &poll_data, NULL)) { + guint revents; + + revents = g_source_query_unix_fd(&ctx_src->source, poll_data->tag); + if (G_UNLIKELY(poll_data->has_many_idx)) { + int num = poll_data->idx.many[1]; + int *p_idx = &poll_data->idx.many[2]; + + for (; num > 0; num--, p_idx++) + ctx_src->fds_arr[*p_idx].revents = revents; + } else + ctx_src->fds_arr[poll_data->idx.one].revents = revents; + } + + nm_assert(ctx_src->fds_len <= (guint) G_MAXINT); + + some_ready = g_main_context_check(ctx_src->context, + ctx_src->max_priority, + ctx_src->fds_arr, + (int) ctx_src->fds_len); + + _CTX_LOG("check (some-ready=%d)...", some_ready); + + return some_ready; +} + +static gboolean +_ctx_integ_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) +{ + CtxIntegSource *ctx_src = ((CtxIntegSource *) source); + + nm_assert(ctx_src->context); + + _ctx_integ_source_reacquire(ctx_src); + + _CTX_LOG("dispatch"); + + g_main_context_dispatch(ctx_src->context); + + return G_SOURCE_CONTINUE; +} + +static void +_ctx_integ_source_finalize(GSource *source) +{ + CtxIntegSource *ctx_src = ((CtxIntegSource *) source); + GHashTableIter h_iter; + PollData * poll_data; + + g_return_if_fail(ctx_src->context); + + _CTX_LOG("finalize..."); + + g_hash_table_iter_init(&h_iter, ctx_src->fds); + while (g_hash_table_iter_next(&h_iter, (gpointer *) &poll_data, NULL)) { + nm_assert(poll_data->tag); + _CTX_LOG("prepare: remove poll fd=%d, events=0x%x", poll_data->fd, poll_data->events); + g_source_remove_unix_fd(&ctx_src->source, poll_data->tag); + g_hash_table_iter_remove(&h_iter); + } + + nm_clear_pointer(&ctx_src->fds, g_hash_table_unref); + nm_clear_g_free(&ctx_src->fds_arr); + ctx_src->fds_len = 0; + + if (ctx_src->acquired) { + ctx_src->acquired = FALSE; + g_main_context_release(ctx_src->context); + } + + nm_clear_pointer(&ctx_src->context, g_main_context_unref); +} + +static GSourceFuncs ctx_integ_source_funcs = { + .prepare = _ctx_integ_source_prepare, + .check = _ctx_integ_source_check, + .dispatch = _ctx_integ_source_dispatch, + .finalize = _ctx_integ_source_finalize, +}; + +/** + * nm_utils_g_main_context_create_integrate_source: + * @inner_context: the inner context that will be integrated to an + * outer #GMainContext. + * + * By integrating the inner context with an outer context, when iterating the outer + * context sources on the inner context will be dispatched. Note that while the + * created source exists, the @inner_context will be acquired. The user gets restricted + * what to do with the inner context. In particular while the inner context is integrated, + * the user should not acquire the inner context again or explicitly iterate it. What + * the user of course still can (and wants to) do is attaching new sources to the inner + * context. + * + * Note that GSource has a priority. While each context dispatches events based on + * their source's priorities, the outer context dispatches to the inner context + * only with one priority (the priority of the created source). That is, the sources + * from the two contexts are kept separate and are not sorted by their priorities. + * + * Returns: a newly created GSource that should be attached to the + * outer context. + */ +GSource * +nm_utils_g_main_context_create_integrate_source(GMainContext *inner_context) +{ + CtxIntegSource *ctx_src; + + g_return_val_if_fail(inner_context, NULL); + + if (!g_main_context_acquire(inner_context)) { + /* We require to acquire the context while it's integrated. We need to keep it acquired + * for the entire duration. + * + * This is also necessary because g_source_attach() only wakes up the context, if + * the context is currently acquired. */ + g_return_val_if_reached(NULL); + } + + ctx_src = (CtxIntegSource *) g_source_new(&ctx_integ_source_funcs, sizeof(CtxIntegSource)); + + g_source_set_name(&ctx_src->source, "ContextIntegrateSource"); + + ctx_src->context = g_main_context_ref(inner_context); + ctx_src->fds = g_hash_table_new_full(nm_pint_hash, nm_pint_equals, _poll_data_free, NULL); + ctx_src->fds_len = 0; + ctx_src->fds_arr = NULL; + ctx_src->acquired = TRUE; + ctx_src->max_priority = G_MAXINT; + + _CTX_LOG("create new integ-source for %p", inner_context); + + return &ctx_src->source; +} + +/*****************************************************************************/ + +void +nm_utils_ifname_cpy(char *dst, const char *name) +{ + int i; + + g_return_if_fail(dst); + g_return_if_fail(name && name[0]); + + nm_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + /* ensures NUL padding of the entire IFNAMSIZ buffer. */ + + for (i = 0; i < (int) IFNAMSIZ && name[i] != '\0'; i++) + dst[i] = name[i]; + + nm_assert(name[i] == '\0'); + + for (; i < (int) IFNAMSIZ; i++) + dst[i] = '\0'; +} + +/*****************************************************************************/ + +gboolean +nm_utils_ifname_valid_kernel(const char *name, GError **error) +{ + int i; + + /* This function follows kernel's interface validation + * function dev_valid_name() in net/core/dev.c. + */ + + if (!name) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("interface name is missing")); + return FALSE; + } + + if (name[0] == '\0') { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("interface name is too short")); + return FALSE; + } + + if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("interface name is reserved")); + return FALSE; + } + + for (i = 0; i < IFNAMSIZ; i++) { + char ch = name[i]; + + if (ch == '\0') + return TRUE; + if (NM_IN_SET(ch, '/', ':') || g_ascii_isspace(ch)) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("interface name contains an invalid character")); + return FALSE; + } + } + + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("interface name is longer than 15 characters")); + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +_nm_utils_ifname_valid_kernel(const char *name, GError **error) +{ + if (!nm_utils_ifname_valid_kernel(name, error)) + return FALSE; + + if (strchr(name, '%')) { + /* Kernel's dev_valid_name() accepts (almost) any binary up to 15 chars. + * However, '%' is treated special as a format specifier. Try + * + * ip link add 'dummy%dx' type dummy + * + * Don't allow that for "connection.interface-name", which either + * matches an existing netdev name (thus, it cannot have a '%') or + * is used to configure a name (in which case we don't want kernel + * to replace the format specifier). */ + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("'%%' is not allowed in interface names")); + return FALSE; + } + + if (NM_IN_STRSET(name, "all", "default", "bonding_masters")) { + /* Certain names are not allowed. The "all" and "default" names are reserved + * due to their directories in "/proc/sys/net/ipv4/conf/" and "/proc/sys/net/ipv6/conf/". + * + * Also, there is "/sys/class/net/bonding_masters" file. + */ + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + _("'%s' is not allowed as interface name"), + name); + return FALSE; + } + + return TRUE; +} + +static gboolean +_nm_utils_ifname_valid_ovs(const char *name, GError **error) +{ + const char *ch; + + /* OVS actually accepts a wider range of chars (all printable UTF-8 chars), + * NetworkManager restricts this to ASCII char as it's a safer option for + * now since OVS is not well documented on this matter. + **/ + for (ch = name; *ch; ++ch) { + if (*ch == '\\' || *ch == '/' || !g_ascii_isgraph(*ch)) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("interface name must be alphanumerical with " + "no forward or backward slashes")); + return FALSE; + } + }; + return TRUE; +} + +gboolean +nm_utils_ifname_valid(const char *name, NMUtilsIfaceType type, GError **error) +{ + g_return_val_if_fail(!error || !(*error), FALSE); + + if (!name || !(name[0])) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("interface name must not be empty")); + return FALSE; + } + + if (!g_utf8_validate(name, -1, NULL)) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("interface name must be UTF-8 encoded")); + return FALSE; + } + + switch (type) { + case NMU_IFACE_KERNEL: + return _nm_utils_ifname_valid_kernel(name, error); + case NMU_IFACE_OVS: + return _nm_utils_ifname_valid_ovs(name, error); + case NMU_IFACE_OVS_AND_KERNEL: + return _nm_utils_ifname_valid_kernel(name, error) + && _nm_utils_ifname_valid_ovs(name, error); + case NMU_IFACE_ANY: + { + gs_free_error GError *local = NULL; + + if (_nm_utils_ifname_valid_kernel(name, error ? &local : NULL)) + return TRUE; + if (_nm_utils_ifname_valid_ovs(name, NULL)) + return TRUE; + if (error) + g_propagate_error(error, g_steal_pointer(&local)); + return FALSE; + } + } + + g_return_val_if_reached(FALSE); +} + +/*****************************************************************************/ + +void +_nm_str_buf_ensure_size(NMStrBuf *strbuf, gsize new_size, gboolean reserve_exact) +{ + _nm_str_buf_assert(strbuf); + + /* Currently, this only supports strictly growing the buffer. */ + nm_assert(new_size > strbuf->_priv_allocated); + + if (!reserve_exact) { + new_size = nm_utils_get_next_realloc_size(!strbuf->_priv_do_bzero_mem, new_size); + } + + strbuf->_priv_str = nm_secret_mem_realloc(strbuf->_priv_str, + strbuf->_priv_do_bzero_mem, + strbuf->_priv_allocated, + new_size); + strbuf->_priv_allocated = new_size; +} + +void +nm_str_buf_append_printf(NMStrBuf *strbuf, const char *format, ...) +{ + va_list args; + gsize available; + int l; + + _nm_str_buf_assert(strbuf); + + available = strbuf->_priv_allocated - strbuf->_priv_len; + + nm_assert(available < G_MAXULONG); + + va_start(args, format); + l = g_vsnprintf(&strbuf->_priv_str[strbuf->_priv_len], available, format, args); + va_end(args); + + nm_assert(l >= 0); + nm_assert(l < G_MAXINT); + + if ((gsize) l >= available) { + gsize l2; + + if (l == 0) + return; + + l2 = ((gsize) l) + 1u; + + nm_str_buf_maybe_expand(strbuf, l2, FALSE); + + va_start(args, format); + l = g_vsnprintf(&strbuf->_priv_str[strbuf->_priv_len], l2, format, args); + va_end(args); + + nm_assert(l >= 0); + nm_assert((gsize) l == l2 - 1u); + } + + strbuf->_priv_len += (gsize) l; +} + +/*****************************************************************************/ + +/** + * nm_indirect_g_free: + * @arg: a pointer to a pointer that is to be freed. + * + * This does the same as nm_clear_g_free(arg) (g_clear_pointer (arg, g_free)). + * This is for example useful when you have a GArray with pointers and a + * clear function to free them. g_array_set_clear_func()'s destroy notify + * function gets a pointer to the array location, so we have to follow + * the first pointer. + */ +void +nm_indirect_g_free(gpointer arg) +{ + gpointer *p = arg; + + nm_clear_g_free(p); +} + +/*****************************************************************************/ + +static char * +attribute_escape(const char *src, char c1, char c2) +{ + char *ret, *dest; + + dest = ret = g_malloc(strlen(src) * 2 + 1); + + while (*src) { + if (*src == c1 || *src == c2 || *src == '\\') + *dest++ = '\\'; + *dest++ = *src++; + } + *dest++ = '\0'; + + return ret; +} + +void +_nm_utils_format_variant_attributes_full(GString * str, + const NMUtilsNamedValue * values, + guint num_values, + const NMVariantAttributeSpec *const *spec, + char attr_separator, + char key_value_separator) +{ + const NMVariantAttributeSpec *const *s; + const char * name, *value; + GVariant * variant; + char * escaped; + char buf[64]; + char sep = 0; + guint i; + + for (i = 0; i < num_values; i++) { + name = values[i].name; + variant = values[i].value_ptr; + value = NULL; + s = NULL; + + if (spec) { + for (s = spec; *s; s++) { + if (nm_streq0((*s)->name, name)) + break; + } + + if (!*s) + continue; + } + + if (g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32)) + value = nm_sprintf_buf(buf, "%u", g_variant_get_uint32(variant)); + else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_INT32)) + value = nm_sprintf_buf(buf, "%d", (int) g_variant_get_int32(variant)); + else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT64)) + value = nm_sprintf_buf(buf, "%" G_GUINT64_FORMAT, g_variant_get_uint64(variant)); + else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BYTE)) + value = nm_sprintf_buf(buf, "%hhu", g_variant_get_byte(variant)); + else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) + value = g_variant_get_boolean(variant) ? "true" : "false"; + else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING)) + value = g_variant_get_string(variant, NULL); + else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BYTESTRING)) { + /* FIXME: there is no guarantee that the byte array + * is valid UTF-8.*/ + value = g_variant_get_bytestring(variant); + } else + continue; + + if (sep) + g_string_append_c(str, sep); + + escaped = attribute_escape(name, attr_separator, key_value_separator); + g_string_append(str, escaped); + g_free(escaped); + + if (!s || !*s || !(*s)->no_value) { + g_string_append_c(str, key_value_separator); + + escaped = attribute_escape(value, attr_separator, key_value_separator); + g_string_append(str, escaped); + g_free(escaped); + } + + sep = attr_separator; + } +} + +char * +_nm_utils_format_variant_attributes(GHashTable * attributes, + const NMVariantAttributeSpec *const *spec, + char attr_separator, + char key_value_separator) +{ + gs_free NMUtilsNamedValue *values_free = NULL; + NMUtilsNamedValue values_prepared[20]; + const NMUtilsNamedValue * values; + GString * str = NULL; + guint len; + + g_return_val_if_fail(attr_separator, NULL); + g_return_val_if_fail(key_value_separator, NULL); + + if (!attributes) + return NULL; + + values = nm_utils_named_values_from_strdict(attributes, &len, values_prepared, &values_free); + if (len == 0) + return NULL; + + str = g_string_new(""); + _nm_utils_format_variant_attributes_full(str, + values, + len, + spec, + attr_separator, + key_value_separator); + return g_string_free(str, FALSE); +} + +/*****************************************************************************/ + +gboolean +nm_utils_is_localhost(const char *name) +{ + static const char *const NAMES[] = { + "localhost", + "localhost4", + "localhost6", + "localhost.localdomain", + "localhost4.localdomain4", + "localhost6.localdomain6", + }; + gsize name_len; + int i; + + if (!name) + return FALSE; + + /* This tries to identify local host and domain names + * described in RFC6761 plus the redhatism of localdomain. + * + * Similar to systemd's is_localhost(). */ + + name_len = strlen(name); + + if (name_len == 0) + return FALSE; + + if (name[name_len - 1] == '.') { + /* one trailing dot is fine. Hide it. */ + name_len--; + } + + for (i = 0; i < (int) G_N_ELEMENTS(NAMES); i++) { + const char *n = NAMES[i]; + gsize l = strlen(n); + gsize s; + + if (name_len < l) + continue; + + s = name_len - l; + + if (g_ascii_strncasecmp(&name[s], n, l) != 0) + continue; + + /* we accept the name if it is equal to one of the well-known names, + * or if it is some prefix, a '.' and the well-known name. */ + if (s == 0) + return TRUE; + if (name[s - 1] == '.') + return TRUE; + } + + return FALSE; +} + +gboolean +nm_utils_is_specific_hostname(const char *name) +{ + if (nm_str_is_empty(name)) + return FALSE; + + if (nm_streq(name, "(none)")) { + /* This is not a special hostname. Probably an artefact by somebody wrongly + * printing NULL. */ + return FALSE; + } + + if (nm_utils_is_localhost(name)) + return FALSE; + + /* FIXME: properly validate the hostname, like systemd's hostname_is_valid() */ + + return TRUE; +} + +/*****************************************************************************/ + +/* taken from systemd's uid_to_name(). */ +char * +nm_utils_uid_to_name(uid_t uid) +{ + gs_free char *buf_heap = NULL; + char buf_stack[4096]; + gsize bufsize; + char * buf; + + bufsize = sizeof(buf_stack); + buf = buf_stack; + + for (;;) { + struct passwd pwbuf; + struct passwd *pw = NULL; + int r; + + r = getpwuid_r(uid, &pwbuf, buf, bufsize, &pw); + if (r == 0 && pw) + return nm_strdup_not_empty(pw->pw_name); + + if (r != ERANGE) + return NULL; + + if (bufsize > G_MAXSIZE / 2u) + return NULL; + + bufsize *= 2u; + g_free(buf_heap); + buf_heap = g_malloc(bufsize); + buf = buf_heap; + } +} + +/* taken from systemd's nss_user_record_by_name() */ +gboolean +nm_utils_name_to_uid(const char *name, uid_t *out_uid) +{ + gs_free char *buf_heap = NULL; + char buf_stack[4096]; + gsize bufsize; + char * buf; + + if (!name) + return nm_assert_unreachable_val(FALSE); + + bufsize = sizeof(buf_stack); + buf = buf_stack; + + for (;;) { + struct passwd *result; + struct passwd pwd; + int r; + + r = getpwnam_r(name, &pwd, buf, bufsize, &result); + if (r == 0) { + if (!result) + return FALSE; + NM_SET_OUT(out_uid, pwd.pw_uid); + return TRUE; + } + + if (r != ERANGE) + return FALSE; + + if (bufsize > G_MAXSIZE / 2u) + return FALSE; + + bufsize *= 2u; + g_free(buf_heap); + buf_heap = g_malloc(bufsize); + buf = buf_heap; + } +} diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h new file mode 100644 index 0000000000..7d330458c1 --- /dev/null +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -0,0 +1,2480 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#ifndef __NM_SHARED_UTILS_H__ +#define __NM_SHARED_UTILS_H__ + +#include <netinet/in.h> + +/*****************************************************************************/ + +/* An optional boolean (like NMTernary, with identical numerical + * enum values). Note that this enum type is _nm_packed! */ +typedef enum _nm_packed { + NM_OPTION_BOOL_DEFAULT = -1, + NM_OPTION_BOOL_FALSE = 0, + NM_OPTION_BOOL_TRUE = 1, +} NMOptionBool; + +/*****************************************************************************/ + +static inline gboolean +nm_is_ascii(char ch) +{ + return ((uint8_t) ch) < 128; +} + +/*****************************************************************************/ + +pid_t nm_utils_gettid(void); + +gboolean _nm_assert_on_main_thread(void); + +#if NM_MORE_ASSERTS > 5 + #define NM_ASSERT_ON_MAIN_THREAD() \ + G_STMT_START \ + { \ + nm_assert(_nm_assert_on_main_thread()); \ + } \ + G_STMT_END +#else + #define NM_ASSERT_ON_MAIN_THREAD() \ + G_STMT_START \ + { \ + ; \ + } \ + G_STMT_END +#endif + +/*****************************************************************************/ + +static inline gboolean +_NM_INT_NOT_NEGATIVE(gssize val) +{ + /* whether an enum (without negative values) is a signed int, depends on compiler options + * and compiler implementation. + * + * When using such an enum for accessing an array, one naturally wants to check + * that the enum is not negative. However, the compiler doesn't like a plain + * comparison "enum_val >= 0", because (if the enum is unsigned), it will warn + * that the expression is always true *duh*. Not even a cast to a signed + * type helps to avoid the compiler warning in any case. + * + * The sole purpose of this function is to avoid a compiler warning, when checking + * that an enum is not negative. */ + return val >= 0; +} + +/* check whether the integer value is smaller than G_MAXINT32. This macro exists + * for the sole purpose, that a plain "((int) value <= G_MAXINT32)" comparison + * may cause the compiler or coverity that this check is always TRUE. But the + * check depends on compile time and the size of C type "int". Of course, most + * of the time in is gint32 and an int value is always <= G_MAXINT32. The check + * exists to catch cases where that is not true. + * + * Together with the G_STATIC_ASSERT(), we make sure that this is always satisfied. */ +G_STATIC_ASSERT(sizeof(int) == sizeof(gint32)); +#if _NM_CC_SUPPORT_GENERIC + #define _NM_INT_LE_MAXINT32(value) \ + ({ \ + _nm_unused typeof(value) _value = (value); \ + \ + _Generic((value), int : TRUE); \ + }) +#else + #define _NM_INT_LE_MAXINT32(value) \ + ({ \ + _nm_unused typeof(value) _value = (value); \ + _nm_unused const int *_p_value = &_value; \ + \ + TRUE; \ + }) +#endif + +/*****************************************************************************/ + +typedef struct { + guint8 ether_addr_octet[6 /*ETH_ALEN*/]; +} NMEtherAddr; + +#define NM_ETHER_ADDR_FORMAT_STR "%02X:%02X:%02X:%02X:%02X:%02X" + +#define NM_ETHER_ADDR_FORMAT_VAL(x) \ + (x)->ether_addr_octet[0], (x)->ether_addr_octet[1], (x)->ether_addr_octet[2], \ + (x)->ether_addr_octet[3], (x)->ether_addr_octet[4], (x)->ether_addr_octet[5] + +#define _NM_ETHER_ADDR_INIT(a0, a1, a2, a3, a4, a5) \ + { \ + .ether_addr_octet = { \ + (a0), \ + (a1), \ + (a2), \ + (a3), \ + (a4), \ + (a5), \ + }, \ + } + +#define NM_ETHER_ADDR_INIT(...) ((NMEtherAddr) _NM_ETHER_ADDR_INIT(__VA_ARGS__)) + +static inline int +nm_ether_addr_cmp(const NMEtherAddr *a, const NMEtherAddr *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_DIRECT_MEMCMP(a, b, sizeof(NMEtherAddr)); + return 0; +} + +static inline gboolean +nm_ether_addr_equal(const NMEtherAddr *a, const NMEtherAddr *b) +{ + return nm_ether_addr_cmp(a, b) == 0; +} + +/*****************************************************************************/ + +typedef struct { + union { + guint8 addr_ptr[1]; + in_addr_t addr4; + struct in_addr addr4_struct; + 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 ether_addr_octet[6 /*ETH_ALEN*/]; + NMEtherAddr ether_addr; + + guint8 array[sizeof(struct in6_addr)]; + }; +} NMIPAddr; + +#define NM_IP_ADDR_INIT \ + { \ + .array = { 0 } \ + } + +extern const NMIPAddr nm_ip_addr_zero; + +#define nm_ether_addr_zero (nm_ip_addr_zero.ether_addr) + +static inline int +nm_ip_addr_cmp(int addr_family, gconstpointer a, gconstpointer b) +{ + nm_assert_addr_family(addr_family); + nm_assert(a); + nm_assert(b); + + return memcmp(a, b, nm_utils_addr_family_to_size(addr_family)); +} + +static inline gboolean +nm_ip_addr_equal(int addr_family, gconstpointer a, gconstpointer b) +{ + return nm_ip_addr_cmp(addr_family, a, b) == 0; +} + +static inline gboolean +nm_ip_addr_is_null(int addr_family, gconstpointer addr) +{ + nm_assert(addr); + if (addr_family == AF_INET6) + return IN6_IS_ADDR_UNSPECIFIED((const struct in6_addr *) addr); + nm_assert(addr_family == AF_INET); + return ((const struct in_addr *) addr)->s_addr == 0; +} + +static inline void +nm_ip_addr_set(int addr_family, gpointer dst, gconstpointer src) +{ + nm_assert_addr_family(addr_family); + nm_assert(dst); + nm_assert(src); + + memcpy(dst, src, (addr_family != AF_INET6) ? sizeof(in_addr_t) : sizeof(struct in6_addr)); +} + +gboolean nm_ip_addr_set_from_untrusted(int addr_family, + gpointer dst, + gconstpointer src, + gsize src_len, + int * out_addr_family); + +static inline gboolean +nm_ip4_addr_is_localhost(in_addr_t addr4) +{ + return (addr4 & htonl(0xFF000000u)) == htonl(0x7F000000u); +} + +/*****************************************************************************/ + +struct ether_addr; + +static inline int +nm_utils_ether_addr_cmp(const struct ether_addr *a1, const struct ether_addr *a2) +{ + nm_assert(a1); + nm_assert(a2); + return memcmp(a1, a2, 6 /*ETH_ALEN*/); +} + +static inline gboolean +nm_utils_ether_addr_equal(const struct ether_addr *a1, const struct ether_addr *a2) +{ + return nm_utils_ether_addr_cmp(a1, a2) == 0; +} + +/*****************************************************************************/ + +#define NM_UTILS_INET_ADDRSTRLEN INET6_ADDRSTRLEN + +static inline const char * +nm_utils_inet_ntop(int addr_family, gconstpointer addr, char *dst) +{ + const char *s; + + const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); + + nm_assert_addr_family(addr_family); + nm_assert(addr); + nm_assert(dst); + + s = inet_ntop(addr_family, + addr, + dst, + addr_family == AF_INET6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN); + nm_assert(s); + return s; +} + +static inline const char * +_nm_utils_inet4_ntop(in_addr_t addr, char dst[static INET_ADDRSTRLEN]) +{ + return nm_utils_inet_ntop(AF_INET, &addr, dst); +} + +static inline const char * +_nm_utils_inet6_ntop(const struct in6_addr *addr, char dst[static INET6_ADDRSTRLEN]) +{ + return nm_utils_inet_ntop(AF_INET6, addr, dst); +} + +static inline char * +nm_utils_inet_ntop_dup(int addr_family, gconstpointer addr) +{ + char buf[NM_UTILS_INET_ADDRSTRLEN]; + + return g_strdup(nm_utils_inet_ntop(addr_family, addr, buf)); +} + +static inline char * +nm_utils_inet4_ntop_dup(in_addr_t addr) +{ + return nm_utils_inet_ntop_dup(AF_INET, &addr); +} + +static inline char * +nm_utils_inet6_ntop_dup(const struct in6_addr *addr) +{ + return nm_utils_inet_ntop_dup(AF_INET6, addr); +} + +/*****************************************************************************/ + +gboolean nm_utils_ipaddr_is_valid(int addr_family, const char *str_addr); + +gboolean nm_utils_ipaddr_is_normalized(int addr_family, const char *str_addr); + +/*****************************************************************************/ + +gboolean nm_utils_memeqzero(gconstpointer data, gsize length); + +/*****************************************************************************/ + +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) +{ + NM_SET_OUT(p_buf_len, len); + NM_SET_OUT(p_buf_ptr, buf); + buf[0] = '\0'; +} + +#define nm_utils_strbuf_init(buf, p_buf_ptr, p_buf_len) \ + G_STMT_START \ + { \ + G_STATIC_ASSERT(G_N_ELEMENTS(buf) == sizeof(buf) && sizeof(buf) > sizeof(char *)); \ + _nm_utils_strbuf_init((buf), sizeof(buf), (p_buf_ptr), (p_buf_len)); \ + } \ + G_STMT_END +void nm_utils_strbuf_append(char **buf, gsize *len, const char *format, ...) _nm_printf(3, 4); +void nm_utils_strbuf_append_c(char **buf, gsize *len, char c); +void nm_utils_strbuf_append_str(char **buf, gsize *len, const char *str); +void nm_utils_strbuf_append_bin(char **buf, gsize *len, gconstpointer str, gsize str_len); +void nm_utils_strbuf_seek_end(char **buf, gsize *len); + +const char *nm_strquote(char *buf, gsize buf_len, const char *str); + +static inline gboolean +nm_utils_is_separator(const char c) +{ + return NM_IN_SET(c, ' ', '\t'); +} + +/*****************************************************************************/ + +GBytes *nm_gbytes_get_empty(void); + +GBytes *nm_g_bytes_new_from_str(const char *str); + +static inline gboolean +nm_gbytes_equal0(GBytes *a, GBytes *b) +{ + return a == b || (a && b && g_bytes_equal(a, b)); +} + +gboolean nm_utils_gbytes_equal_mem(GBytes *bytes, gconstpointer mem_data, gsize mem_len); + +GVariant *nm_utils_gbytes_to_variant_ay(GBytes *bytes); + +GHashTable *nm_utils_strdict_clone(GHashTable *src); + +GVariant *nm_utils_strdict_to_variant_ass(GHashTable *strdict); +GVariant *nm_utils_strdict_to_variant_asv(GHashTable *strdict); + +/*****************************************************************************/ + +GVariant *nm_utils_gvariant_vardict_filter(GVariant *src, + gboolean (*filter_fcn)(const char *key, + GVariant * val, + char ** out_key, + GVariant ** out_val, + gpointer user_data), + gpointer user_data); + +GVariant *nm_utils_gvariant_vardict_filter_drop_one(GVariant *src, const char *key); + +/*****************************************************************************/ + +static inline int +nm_utils_hexchar_to_int(char ch) +{ + G_STATIC_ASSERT_EXPR('0' < 'A'); + G_STATIC_ASSERT_EXPR('A' < 'a'); + + if (ch >= '0') { + if (ch <= '9') + return ch - '0'; + if (ch >= 'A') { + if (ch <= 'F') + return ((int) ch) + (10 - (int) 'A'); + if (ch >= 'a' && ch <= 'f') + return ((int) ch) + (10 - (int) 'a'); + } + } + return -1; +} + +/*****************************************************************************/ + +const char *nm_utils_dbus_path_get_last_component(const char *dbus_path); + +int nm_utils_dbus_path_cmp(const char *dbus_path_a, const char *dbus_path_b); + +/*****************************************************************************/ + +typedef enum { + NM_UTILS_STRSPLIT_SET_FLAGS_NONE = 0, + + /* by default, strsplit will coalesce consecutive delimiters and remove + * them from the result. If this flag is present, empty values are preserved + * and returned. + * + * When combined with %NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, if a value gets + * empty after strstrip(), it also gets removed. */ + NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY = (1u << 0), + + /* %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING means that delimiters prefixed + * by a backslash are not treated as a separator. Such delimiters and their escape + * character are copied to the current word without unescaping them. In general, + * nm_utils_strsplit_set_full() does not remove any backslash escape characters + * and does no unescaping. It only considers them for skipping to split at + * an escaped delimiter. + * + * If this is combined with (or implied by %NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED), then + * the backslash escapes are removed from the result. + */ + NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING = (1u << 1), + + /* If flag is set, does the same as g_strstrip() on the returned tokens. + * This will remove leading and trailing ascii whitespaces (g_ascii_isspace() + * and NM_ASCII_SPACES). + * + * - when combined with !%NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, + * empty tokens will be removed (and %NULL will be returned if that + * results in an empty string array). + * - when combined with %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING, + * trailing whitespace escaped by backslash are not stripped. */ + NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP = (1u << 2), + + /* This implies %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING. + * + * This will do a final run over all tokens and remove all backslash + * escape characters that + * - precede a delimiter. + * - precede a backslash. + * - preceed a whitespace (with %NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP). + * + * Note that with %NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, it is only + * necessary to escape the very last whitespace (if the delimiters + * are not whitespace themself). So, technically, it would be sufficient + * to only unescape a backslash before the last whitespace and the user + * still could express everything. However, such a rule would be complicated + * to understand, so when using backslash escaping with nm_utils_strsplit_set_full(), + * then all characters (including backslash) are treated verbatim, except: + * + * - "\\$DELIMITER" (escaped delimiter) + * - "\\\\" (escaped backslash) + * - "\\$SPACE" (escaped space) (with %NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP). + * + * Note that all other escapes like "\\n" or "\\001" are left alone. + * That makes the escaping/unescaping rules simple. Also, for the most part + * a text is just taken as-is, with little additional rules. Only backslashes + * need extra care, and then only if they proceed one of the relevant characters. + */ + NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED = (1u << 3), + +} NMUtilsStrsplitSetFlags; + +const char ** +nm_utils_strsplit_set_full(const char *str, const char *delimiter, NMUtilsStrsplitSetFlags flags); + +static inline const char ** +nm_utils_strsplit_set_with_empty(const char *str, const char *delimiters) +{ + /* this returns the same result as g_strsplit_set(str, delimiters, -1), except + * it does not deep-clone the strv array. + * Also, for @str == "", this returns %NULL while g_strsplit_set() would return + * an empty strv array. */ + return nm_utils_strsplit_set_full(str, delimiters, NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY); +} + +static inline const char ** +nm_utils_strsplit_set(const char *str, const char *delimiters) +{ + return nm_utils_strsplit_set_full(str, delimiters, NM_UTILS_STRSPLIT_SET_FLAGS_NONE); +} + +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); + +/*****************************************************************************/ + +static inline gpointer +nm_copy_func_g_strdup(gconstpointer arg, gpointer user_data) +{ + return g_strdup(arg); +} + +/*****************************************************************************/ + +static inline const char ** +nm_utils_escaped_tokens_split(const char *str, const char *delimiters) +{ + return nm_utils_strsplit_set_full(str, + delimiters, + NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED + | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP); +} + +typedef enum { + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_NONE = 0, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_SPACES = (1ull << 0), + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE = (1ull << 1), + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE = (1ull << 2), + + /* Backslash characters will be escaped as "\\\\" if they precede another + * character that makes it necessary. Such characters are: + * + * 1) before another '\\' backslash. + * 2) before any delimiter in @delimiters. + * 3) before any delimiter in @delimiters_as_needed. + * 4) before a white space, if ESCAPE_LEADING_SPACE or ESCAPE_TRAILING_SPACE is set. + * 5) before the end of the word + * + * Rule 4) is an extension. It's not immediately clear why with ESCAPE_LEADING_SPACE + * and ESCAPE_TRAILING_SPACE we want *all* backslashes before a white space escaped. + * The reason is, that we obviously want to use ESCAPE_LEADING_SPACE and ESCAPE_TRAILING_SPACE + * in cases, where we later parse the backslash escaped strings back, but allowing to strip + * unescaped white spaces. That means, we want that " a " gets escaped as "\\ a\\ ". + * On the other hand, we also want that " a\\ b " gets escaped as "\\ a\\\\ b\\ ", + * and not "\\ a\\ b\\ ". Because otherwise, the parser would need to treat "\\ " + * differently depending on whether the sequence is at the beginning, end or middle + * of the word. + * + * Rule 5) is also not immediately obvious. When used with ESCAPE_TRAILING_SPACE, + * we clearly want to allow that an escaped word can have arbitrary + * whitespace suffixes. That's why this mode exists. So we must escape "a\\" as + * "a\\\\", so that appending " " does not change the meaning. + * Also without ESCAPE_TRAILING_SPACE, we want in general that we can concatenate + * two escaped words without changing their meaning. If the words would be "a\\" + * and "," (with ',' being a delimiter), then the result must be "a\\\\" and "\\," + * so that the concatenated word ("a\\\\\\,") is still the same. If we would escape + * them instead as "a\\" + "\\,", then the concatenated word would be "a\\\\," and + * different. + * */ + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED = (1ull << 3), + + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS = (1ull << 4), +} NMUtilsEscapedTokensEscapeFlags; + +const char *nm_utils_escaped_tokens_escape_full(const char *str, + const char *delimiters, + const char *delimiters_as_needed, + NMUtilsEscapedTokensEscapeFlags flags, + char ** out_to_free); + +static inline const char * +nm_utils_escaped_tokens_escape(const char *str, const char *delimiters, char **out_to_free) +{ + return nm_utils_escaped_tokens_escape_full( + str, + delimiters, + NULL, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE, + out_to_free); +} + +/** + * nm_utils_escaped_tokens_escape_unnecessary: + * @str: the string to check for "escape" + * @delimiters: the delimiters + * + * This asserts that calling nm_utils_escaped_tokens_escape() + * on @str has no effect and returns @str directly. This is only + * for asserting that @str is safe to not require any escaping. + * + * Returns: @str + */ +static inline const char * +nm_utils_escaped_tokens_escape_unnecessary(const char *str, const char *delimiters) +{ +#if NM_MORE_ASSERTS > 0 + + nm_assert(str); + nm_assert(delimiters); + + { + gs_free char *str_to_free = NULL; + const char * str0; + + str0 = nm_utils_escaped_tokens_escape(str, delimiters, &str_to_free); + nm_assert(str0 == str); + nm_assert(!str_to_free); + } +#endif + + return str; +} + +static inline void +nm_utils_escaped_tokens_escape_gstr_assert(const char *str, + const char *delimiters, + GString * gstring) +{ + g_string_append(gstring, nm_utils_escaped_tokens_escape_unnecessary(str, delimiters)); +} + +static inline GString * +nm_utils_escaped_tokens_escape_gstr(const char *str, const char *delimiters, GString *gstring) +{ + gs_free char *str_to_free = NULL; + + nm_assert(str); + nm_assert(gstring); + + g_string_append(gstring, nm_utils_escaped_tokens_escape(str, delimiters, &str_to_free)); + return gstring; +} + +/*****************************************************************************/ + +char **nm_utils_strsplit_quoted(const char *str); + +/*****************************************************************************/ + +static inline const char ** +nm_utils_escaped_tokens_options_split_list(const char *str) +{ + return nm_utils_strsplit_set_full(str, + ",", + NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP + | NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING); +} + +void nm_utils_escaped_tokens_options_split(char *str, const char **out_key, const char **out_val); + +static inline const char * +nm_utils_escaped_tokens_options_escape_key(const char *key, char **out_to_free) +{ + return nm_utils_escaped_tokens_escape_full( + key, + ",=", + NULL, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE, + out_to_free); +} + +static inline const char * +nm_utils_escaped_tokens_options_escape_val(const char *val, char **out_to_free) +{ + return nm_utils_escaped_tokens_escape_full( + val, + ",", + "=", + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE, + out_to_free); +} + +/*****************************************************************************/ + +#define NM_UTILS_CHECKSUM_LENGTH_MD5 16 +#define NM_UTILS_CHECKSUM_LENGTH_SHA1 20 +#define NM_UTILS_CHECKSUM_LENGTH_SHA256 32 + +#define nm_utils_checksum_get_digest(sum, arr) \ + G_STMT_START \ + { \ + GChecksum *const _sum = (sum); \ + gsize _len; \ + \ + G_STATIC_ASSERT_EXPR(sizeof(arr) == NM_UTILS_CHECKSUM_LENGTH_MD5 \ + || sizeof(arr) == NM_UTILS_CHECKSUM_LENGTH_SHA1 \ + || sizeof(arr) == NM_UTILS_CHECKSUM_LENGTH_SHA256); \ + G_STATIC_ASSERT_EXPR(sizeof(arr) == G_N_ELEMENTS(arr)); \ + \ + nm_assert(_sum); \ + \ + _len = G_N_ELEMENTS(arr); \ + \ + g_checksum_get_digest(_sum, (arr), &_len); \ + nm_assert(_len == G_N_ELEMENTS(arr)); \ + } \ + G_STMT_END + +#define nm_utils_checksum_get_digest_len(sum, buf, len) \ + G_STMT_START \ + { \ + GChecksum *const _sum = (sum); \ + const gsize _len0 = (len); \ + gsize _len; \ + \ + nm_assert(NM_IN_SET(_len0, \ + NM_UTILS_CHECKSUM_LENGTH_MD5, \ + NM_UTILS_CHECKSUM_LENGTH_SHA1, \ + NM_UTILS_CHECKSUM_LENGTH_SHA256)); \ + nm_assert(_sum); \ + \ + _len = _len0; \ + g_checksum_get_digest(_sum, (buf), &_len); \ + nm_assert(_len == _len0); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +guint32 _nm_utils_ip4_prefix_to_netmask(guint32 prefix); +guint32 _nm_utils_ip4_get_default_prefix0(in_addr_t ip); +guint32 _nm_utils_ip4_get_default_prefix(in_addr_t ip); + +gconstpointer +nm_utils_ipx_address_clear_host_address(int family, gpointer dst, gconstpointer src, guint8 plen); +in_addr_t nm_utils_ip4_address_clear_host_address(in_addr_t addr, guint8 plen); +const struct in6_addr *nm_utils_ip6_address_clear_host_address(struct in6_addr * dst, + const struct in6_addr *src, + guint8 plen); +int nm_utils_ip6_address_same_prefix_cmp(const struct in6_addr *addr_a, + const struct in6_addr *addr_b, + guint8 plen); + +gboolean nm_utils_ip_is_site_local(int addr_family, const void *address); + +/*****************************************************************************/ + +gboolean nm_utils_parse_inaddr_bin_full(int addr_family, + gboolean accept_legacy, + const char *text, + int * out_addr_family, + gpointer out_addr); +static inline gboolean +nm_utils_parse_inaddr_bin(int addr_family, + const char *text, + int * out_addr_family, + gpointer out_addr) +{ + return nm_utils_parse_inaddr_bin_full(addr_family, FALSE, text, out_addr_family, 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, + int * out_addr_family, + gpointer out_addr, + int * out_prefix); + +gboolean +nm_utils_parse_inaddr_prefix(int addr_family, const char *text, char **out_addr, int *out_prefix); + +gboolean nm_utils_parse_next_line(const char **inout_ptr, + gsize * inout_len, + const char **out_line, + gsize * out_line_len); + +gint64 nm_g_ascii_strtoll(const char *nptr, char **endptr, guint base); + +guint64 nm_g_ascii_strtoull(const char *nptr, char **endptr, guint base); + +double nm_g_ascii_strtod(const char *nptr, char **endptr); + +gint64 +_nm_utils_ascii_str_to_int64(const char *str, guint base, gint64 min, gint64 max, gint64 fallback); +guint64 _nm_utils_ascii_str_to_uint64(const char *str, + guint base, + guint64 min, + guint64 max, + guint64 fallback); + +int _nm_utils_ascii_str_to_bool(const char *str, int default_value); + +/*****************************************************************************/ + +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(fcn_name, flags_type, ...) \ + 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); \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +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_FULL(fcn_name, lookup_type, int_fmt, ...) \ + 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; \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +#define NM_UTILS_ENUM2STR_DEFINE(fcn_name, lookup_type, ...) \ + NM_UTILS_ENUM2STR_DEFINE_FULL(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(10) _nm_g_slice_free_fcn_define(12) + _nm_g_slice_free_fcn_define(16) _nm_g_slice_free_fcn_define(32) + +#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. */ \ + G_STATIC_ASSERT_EXPR(((mem_size) == 1) || ((mem_size) == 2) || ((mem_size) == 4) \ + || ((mem_size) == 8) || ((mem_size) == 10) || ((mem_size) == 12) \ + || ((mem_size) == 16) || ((mem_size) == 32)); \ + 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 10: \ + _fcn = _nm_g_slice_free_fcn_10; \ + break; \ + case 12: \ + _fcn = _nm_g_slice_free_fcn_12; \ + break; \ + case 16: \ + _fcn = _nm_g_slice_free_fcn_16; \ + break; \ + case 32: \ + _fcn = _nm_g_slice_free_fcn_32; \ + 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 a compile time + * error for the wrong types. */ +#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)) + +/*****************************************************************************/ + +/* Like g_error_matches() however: + * - as macro it is always inlined. + * - the @domain is usually a error quark getter function that cannot + * be inlined. This macro calls the getter only if there is an error (lazy). + * - accept a list of allowed codes, instead of only one. + */ +#define nm_g_error_matches(error, err_domain, ...) \ + ({ \ + const GError *const _error = (error); \ + \ + _error && _error->domain == (err_domain) && NM_IN_SET(_error->code, __VA_ARGS__); \ + }) + + static inline void nm_g_set_error_take(GError **error, GError *error_take) +{ + if (!error_take) + g_return_if_reached(); + if (!error) { + g_error_free(error_take); + return; + } + if (*error) { + g_error_free(error_take); + g_return_if_reached(); + } + *error = error_take; +} + +#define nm_g_set_error_take_lazy(error, error_take_lazy) \ + G_STMT_START \ + { \ + GError **_error = (error); \ + \ + if (_error) \ + nm_g_set_error_take(_error, (error_take_lazy)); \ + } \ + G_STMT_END + +/** + * NMUtilsError: + * @NM_UTILS_ERROR_UNKNOWN: unknown or unclassified error + * @NM_UTILS_ERROR_CANCELLED_DISPOSING: when disposing an object that has + * pending asynchronous operations, the operation is cancelled with this + * 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_NOT_READY: the failure is related to being currently + * not ready to perform the operation. + * + * @NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE: used for a very particular + * purpose during nm_device_check_connection_compatible() to indicate that + * the profile does not match the device already because their type differs. + * That is, there is a fundamental reason of trying to check a profile that + * cannot possibly match on this device. + * @NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE: used for a very particular + * purpose during nm_device_check_connection_available(), to indicate that the + * device is not available because it is unmanaged. + * @NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY: the profile is currently not + * available/compatible with the device, but this may be only temporary. + * + * @NM_UTILS_ERROR_SETTING_MISSING: the setting is missing + * + * @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 >*/ + NM_UTILS_ERROR_NOT_READY, /*< nick=NotReady >*/ + + /* the following codes have a special meaning and are exactly used for + * nm_device_check_connection_compatible() and nm_device_check_connection_available(). + * + * Actually, their meaning is not very important (so, don't think too + * hard about the name of these error codes). What is important, is their + * relative order (i.e. the integer value of the codes). When manager + * searches for a suitable device, it will check all devices whether + * a profile can be activated. If they all fail, it will pick the error + * message from the device that returned the *highest* error code, + * in the hope that this message makes the most sense for the caller. + * */ + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + + NM_UTILS_ERROR_SETTING_MISSING, + +} NMUtilsError; + +#define NM_UTILS_ERROR (nm_utils_error_quark()) +GQuark nm_utils_error_quark(void); + +GQuark nm_manager_error_quark(void); +#define _NM_MANAGER_ERROR (nm_manager_error_quark()) + +#define _NM_MANAGER_ERROR_UNKNOWN_LOG_LEVEL 10 +#define _NM_MANAGER_ERROR_UNKNOWN_LOG_DOMAIN 11 + +void nm_utils_error_set_cancelled(GError **error, gboolean is_disposing, const char *instance_name); + +static inline GError * +nm_utils_error_new_cancelled(gboolean is_disposing, const char *instance_name) +{ + GError *error = NULL; + + nm_utils_error_set_cancelled(&error, is_disposing, instance_name); + return error; +} + +gboolean nm_utils_error_is_cancelled_or_disposing(GError *error); + +static inline gboolean +nm_utils_error_is_cancelled(GError *error) +{ + return error && error->code == G_IO_ERROR_CANCELLED && error->domain == G_IO_ERROR; +} + +gboolean nm_utils_error_is_notfound(GError *error); + +static inline void +nm_utils_error_set_literal(GError **error, int error_code, const char *literal) +{ + g_set_error_literal(error, NM_UTILS_ERROR, error_code, literal); +} + +#define nm_utils_error_set(error, error_code, ...) \ + G_STMT_START \ + { \ + if (NM_NARG(__VA_ARGS__) == 1) { \ + g_set_error_literal((error), \ + NM_UTILS_ERROR, \ + (error_code), \ + _NM_UTILS_MACRO_FIRST(__VA_ARGS__)); \ + } else { \ + g_set_error((error), NM_UTILS_ERROR, (error_code), __VA_ARGS__); \ + } \ + } \ + G_STMT_END + +#define nm_utils_error_set_errno(error, errsv, fmt, ...) \ + G_STMT_START \ + { \ + char _bstrerr[NM_STRERROR_BUFSIZE]; \ + \ + g_set_error((error), \ + NM_UTILS_ERROR, \ + NM_UTILS_ERROR_UNKNOWN, \ + fmt, \ + ##__VA_ARGS__, \ + nm_strerror_native_r( \ + ({ \ + const int _errsv = (errsv); \ + \ + (_errsv >= 0 ? _errsv \ + : (G_UNLIKELY(_errsv == G_MININT) ? G_MAXINT : -errsv)); \ + }), \ + _bstrerr, \ + sizeof(_bstrerr))); \ + } \ + G_STMT_END + +#define nm_utils_error_new(error_code, ...) \ + ((NM_NARG(__VA_ARGS__) == 1) \ + ? g_error_new_literal(NM_UTILS_ERROR, (error_code), _NM_UTILS_MACRO_FIRST(__VA_ARGS__)) \ + : g_error_new(NM_UTILS_ERROR, (error_code), __VA_ARGS__)) + +/*****************************************************************************/ + +gboolean nm_g_object_set_property(GObject * object, + const char * property_name, + const GValue *value, + GError ** error); + +gboolean nm_g_object_set_property_string(GObject * object, + const char *property_name, + const char *value, + GError ** error); + +gboolean nm_g_object_set_property_string_static(GObject * object, + const char *property_name, + const char *value, + GError ** error); + +gboolean nm_g_object_set_property_string_take(GObject * object, + const char *property_name, + char * value, + GError ** error); + +gboolean nm_g_object_set_property_boolean(GObject * object, + const char *property_name, + gboolean value, + GError ** error); + +gboolean nm_g_object_set_property_char(GObject * object, + const char *property_name, + gint8 value, + GError ** error); + +gboolean nm_g_object_set_property_uchar(GObject * object, + const char *property_name, + guint8 value, + GError ** error); + +gboolean +nm_g_object_set_property_int(GObject *object, const char *property_name, int value, GError **error); + +gboolean nm_g_object_set_property_int64(GObject * object, + const char *property_name, + gint64 value, + GError ** error); + +gboolean nm_g_object_set_property_uint(GObject * object, + const char *property_name, + guint value, + GError ** error); + +gboolean nm_g_object_set_property_uint64(GObject * object, + const char *property_name, + guint64 value, + GError ** error); + +gboolean nm_g_object_set_property_flags(GObject * object, + const char *property_name, + GType gtype, + guint value, + GError ** error); + +gboolean nm_g_object_set_property_enum(GObject * object, + const char *property_name, + GType gtype, + int value, + GError ** error); + +GParamSpec *nm_g_object_class_find_property_from_gtype(GType gtype, const char *property_name); + +/*****************************************************************************/ + +#define _NM_G_PARAM_SPEC_CAST(param_spec, _value_type, _c_type) \ + ({ \ + const GParamSpec *const _param_spec = (param_spec); \ + \ + nm_assert(!_param_spec || _param_spec->value_type == (_value_type)); \ + ((const _c_type *) _param_spec); \ + }) + +#define NM_G_PARAM_SPEC_CAST_BOOLEAN(param_spec) \ + _NM_G_PARAM_SPEC_CAST(param_spec, G_TYPE_BOOLEAN, GParamSpecBoolean) +#define NM_G_PARAM_SPEC_CAST_UINT(param_spec) \ + _NM_G_PARAM_SPEC_CAST(param_spec, G_TYPE_UINT, GParamSpecUInt) +#define NM_G_PARAM_SPEC_CAST_UINT64(param_spec) \ + _NM_G_PARAM_SPEC_CAST(param_spec, G_TYPE_UINT64, GParamSpecUInt64) + +#define NM_G_PARAM_SPEC_GET_DEFAULT_BOOLEAN(param_spec) \ + (NM_G_PARAM_SPEC_CAST_BOOLEAN(NM_ENSURE_NOT_NULL(param_spec))->default_value) +#define NM_G_PARAM_SPEC_GET_DEFAULT_UINT(param_spec) \ + (NM_G_PARAM_SPEC_CAST_UINT(NM_ENSURE_NOT_NULL(param_spec))->default_value) +#define NM_G_PARAM_SPEC_GET_DEFAULT_UINT64(param_spec) \ + (NM_G_PARAM_SPEC_CAST_UINT64(NM_ENSURE_NOT_NULL(param_spec))->default_value) + +/*****************************************************************************/ + +GType nm_g_type_find_implementing_class_for_property(GType gtype, const char *pname); + +/*****************************************************************************/ + +typedef enum { + NM_UTILS_STR_UTF8_SAFE_FLAG_NONE = 0, + + /* This flag only has an effect during escaping. */ + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL = 0x0001, + + /* This flag only has an effect during escaping. */ + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII = 0x0002, + + /* This flag only has an effect during escaping to ensure we + * don't leak secrets in memory. Note that during unescape we + * know the maximum result size from the beginning, and no + * reallocation happens. Thus, unescape always avoids leaking + * secrets already. */ + NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET = 0x0004, + + /* This flag only has an effect during unescaping. It means + * that non-escaped whitespaces (g_ascii_isspace()) will be + * stripped from the front and end of the string. Note that + * this flag is only useful for gracefully accepting user input + * with spaces. With this flag, escape and unescape may no longer + * yield the original input. */ + NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES = 0x0008, +} NMUtilsStrUtf8SafeFlags; + +const char *nm_utils_buf_utf8safe_escape(gconstpointer buf, + gssize buflen, + NMUtilsStrUtf8SafeFlags flags, + char ** to_free); +char * +nm_utils_buf_utf8safe_escape_cp(gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags); +const char * +nm_utils_buf_utf8safe_escape_bytes(GBytes *bytes, NMUtilsStrUtf8SafeFlags flags, char **to_free); +gconstpointer nm_utils_buf_utf8safe_unescape(const char * str, + NMUtilsStrUtf8SafeFlags flags, + gsize * out_len, + gpointer * to_free); + +const char * +nm_utils_str_utf8safe_escape(const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free); +const char * +nm_utils_str_utf8safe_unescape(const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free); + +char *nm_utils_str_utf8safe_escape_cp(const char *str, NMUtilsStrUtf8SafeFlags flags); +char *nm_utils_str_utf8safe_unescape_cp(const char *str, NMUtilsStrUtf8SafeFlags flags); + +char *nm_utils_str_utf8safe_escape_take(char *str, NMUtilsStrUtf8SafeFlags flags); + +GVariant *nm_g_variant_singleton_u_0(void); + +static inline void +nm_g_variant_unref_floating(GVariant *var) +{ + /* often a function wants to keep a reference to an input variant. + * It uses g_variant_ref_sink() to either increase the ref-count, + * or take ownership of a possibly floating reference. + * + * If the function doesn't actually want to do anything with the + * input variant, it still must make sure that a passed in floating + * reference is consumed. Hence, this helper which: + * + * - does nothing if @var is not floating + * - unrefs (consumes) @var if it is floating. */ + if (g_variant_is_floating(var)) + g_variant_unref(var); +} + +#define nm_g_variant_lookup(dictionary, ...) \ + ({ \ + GVariant *const _dictionary = (dictionary); \ + \ + (_dictionary && g_variant_lookup(_dictionary, __VA_ARGS__)); \ + }) + +static inline GVariant * +nm_g_variant_lookup_value(GVariant *dictionary, const char *key, const GVariantType *expected_type) +{ + return dictionary ? g_variant_lookup_value(dictionary, key, expected_type) : NULL; +} + +static inline gboolean +nm_g_variant_is_of_type(GVariant *value, const GVariantType *type) +{ + return value && g_variant_is_of_type(value, type); +} + +static inline GVariant * +nm_g_variant_new_ay_inaddr(int addr_family, gconstpointer addr) +{ + return g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, + addr ?: &nm_ip_addr_zero, + nm_utils_addr_family_to_size(addr_family), + 1); +} + +static inline GVariant * +nm_g_variant_new_ay_in4addr(in_addr_t addr) +{ + return nm_g_variant_new_ay_inaddr(AF_INET, &addr); +} + +static inline GVariant * +nm_g_variant_new_ay_in6addr(const struct in6_addr *addr) +{ + return nm_g_variant_new_ay_inaddr(AF_INET6, addr); +} + +static inline void +nm_g_variant_builder_add_sv(GVariantBuilder *builder, const char *key, GVariant *val) +{ + g_variant_builder_add(builder, "{sv}", key, val); +} + +static inline void +nm_g_variant_builder_add_sv_bytearray(GVariantBuilder *builder, + const char * key, + const guint8 * arr, + gsize len) +{ + g_variant_builder_add(builder, + "{sv}", + key, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, arr, len, 1)); +} + +static inline void +nm_g_variant_builder_add_sv_uint32(GVariantBuilder *builder, const char *key, guint32 val) +{ + nm_g_variant_builder_add_sv(builder, key, g_variant_new_uint32(val)); +} + +static inline void +nm_g_variant_builder_add_sv_str(GVariantBuilder *builder, const char *key, const char *str) +{ + nm_g_variant_builder_add_sv(builder, key, g_variant_new_string(str)); +} + +static inline void +nm_g_source_destroy_and_unref(GSource *source) +{ + g_source_destroy(source); + g_source_unref(source); +} + +#define nm_clear_g_source_inst(ptr) (nm_clear_pointer((ptr), nm_g_source_destroy_and_unref)) + +NM_AUTO_DEFINE_FCN0(GSource *, _nm_auto_destroy_and_unref_gsource, nm_g_source_destroy_and_unref); +#define nm_auto_destroy_and_unref_gsource nm_auto(_nm_auto_destroy_and_unref_gsource) + +NM_AUTO_DEFINE_FCN0(GMainContext *, _nm_auto_pop_gmaincontext, g_main_context_pop_thread_default); +#define nm_auto_pop_gmaincontext nm_auto(_nm_auto_pop_gmaincontext) + +static inline gboolean +nm_source_func_unref_gobject(gpointer user_data) +{ + nm_assert(G_IS_OBJECT(user_data)); + g_object_unref(user_data); + return G_SOURCE_REMOVE; +} + +GSource *nm_g_idle_source_new(int priority, + GSourceFunc func, + gpointer user_data, + GDestroyNotify destroy_notify); + +GSource *nm_g_timeout_source_new(guint timeout_msec, + int priority, + GSourceFunc func, + gpointer user_data, + GDestroyNotify destroy_notify); + +GSource *nm_g_timeout_source_new_seconds(guint timeout_sec, + int priority, + GSourceFunc func, + gpointer user_data, + GDestroyNotify destroy_notify); + +GSource * + nm_g_unix_fd_source_new(int fd, + GIOCondition io_condition, + int priority, + gboolean (*source_func)(int fd, GIOCondition condition, gpointer user_data), + gpointer user_data, + GDestroyNotify destroy_notify); +GSource *nm_g_unix_signal_source_new(int signum, + int priority, + GSourceFunc handler, + gpointer user_data, + GDestroyNotify notify); + +static inline GSource * +nm_g_source_attach(GSource *source, GMainContext *context) +{ + g_source_attach(source, context); + return source; +} + +static inline GSource * +nm_g_idle_add_source(GSourceFunc func, gpointer user_data) +{ + /* G convenience function to attach a new timeout source to the default GMainContext. + * In that sense it's very similar to g_idle_add() except that it returns a + * reference to the new source. */ + return nm_g_source_attach(nm_g_idle_source_new(G_PRIORITY_DEFAULT, func, user_data, NULL), + NULL); +} + +static inline GSource * +nm_g_timeout_add_source(guint timeout_msec, GSourceFunc func, gpointer user_data) +{ + /* G convenience function to attach a new timeout source to the default GMainContext. + * In that sense it's very similar to g_timeout_add() except that it returns a + * reference to the new source. */ + return nm_g_source_attach( + nm_g_timeout_source_new(timeout_msec, G_PRIORITY_DEFAULT, func, user_data, NULL), + NULL); +} + +static inline GSource * +nm_g_timeout_add_source_seconds(guint timeout_sec, GSourceFunc func, gpointer user_data) +{ + /* G convenience function to attach a new timeout source to the default GMainContext. + * In that sense it's very similar to g_timeout_add_seconds() except that it returns a + * reference to the new source. */ + return nm_g_source_attach( + nm_g_timeout_source_new_seconds(timeout_sec, G_PRIORITY_DEFAULT, func, user_data, NULL), + NULL); +} + +static inline GSource * +nm_g_timeout_add_source_approx(guint timeout_msec, + guint timeout_sec_threshold, + GSourceFunc func, + gpointer user_data) +{ + GSource *source; + + /* If timeout_msec is larger or equal than a threshold, then we use g_timeout_source_new_seconds() + * instead. */ + if (timeout_msec / 1000u >= timeout_sec_threshold) + source = nm_g_timeout_source_new_seconds(timeout_msec / 1000u, + G_PRIORITY_DEFAULT, + func, + user_data, + NULL); + else + source = nm_g_timeout_source_new(timeout_msec, G_PRIORITY_DEFAULT, func, user_data, NULL); + return nm_g_source_attach(source, NULL); +} + +NM_AUTO_DEFINE_FCN0(GMainContext *, _nm_auto_unref_gmaincontext, g_main_context_unref); +#define nm_auto_unref_gmaincontext nm_auto(_nm_auto_unref_gmaincontext) + +static inline GMainContext * +nm_g_main_context_push_thread_default(GMainContext *context) +{ + /* This function is to work together with nm_auto_pop_gmaincontext. */ + if (G_UNLIKELY(!context)) + context = g_main_context_default(); + g_main_context_push_thread_default(context); + return context; +} + +static inline gboolean +nm_g_main_context_is_thread_default(GMainContext *context) +{ + GMainContext *cur_context; + + cur_context = g_main_context_get_thread_default(); + if (cur_context == context) + return TRUE; + + if (G_UNLIKELY(!cur_context)) + cur_context = g_main_context_default(); + else if (G_UNLIKELY(!context)) + context = g_main_context_default(); + else + return FALSE; + + return (cur_context == context); +} + +static inline GMainContext * +nm_g_main_context_push_thread_default_if_necessary(GMainContext *context) +{ + GMainContext *cur_context; + + cur_context = g_main_context_get_thread_default(); + if (cur_context == context) + return NULL; + + if (G_UNLIKELY(!cur_context)) { + cur_context = g_main_context_default(); + if (cur_context == context) + return NULL; + } else if (G_UNLIKELY(!context)) { + context = g_main_context_default(); + if (cur_context == context) + return NULL; + } + + g_main_context_push_thread_default(context); + return context; +} + +/*****************************************************************************/ + +static inline int +nm_utf8_collate0(const char *a, const char *b) +{ + if (!a) + return !b ? 0 : -1; + if (!b) + return 1; + return g_utf8_collate(a, b); +} + +int nm_strcmp_with_data(gconstpointer a, gconstpointer b, gpointer user_data); +int nm_strcmp_p_with_data(gconstpointer a, gconstpointer b, gpointer user_data); +int nm_strcmp0_p_with_data(gconstpointer a, gconstpointer b, gpointer user_data); +int nm_strcmp_ascii_case_with_data(gconstpointer a, gconstpointer b, gpointer user_data); +int nm_cmp_uint32_p_with_data(gconstpointer p_a, gconstpointer p_b, gpointer user_data); +int nm_cmp_int2ptr_p_with_data(gconstpointer p_a, gconstpointer p_b, gpointer user_data); + +/*****************************************************************************/ + +typedef struct { + const char *name; +} NMUtilsNamedEntry; + +typedef struct { + union { + NMUtilsNamedEntry named_entry; + const char * name; + }; + union { + const char *value_str; + gpointer value_ptr; + }; +} NMUtilsNamedValue; + +#define NM_UTILS_NAMED_VALUE_INIT(n, v) \ + { \ + .name = (n), .value_ptr = (v) \ + } + +NMUtilsNamedValue * +nm_utils_named_values_from_strdict_full(GHashTable * hash, + guint * out_len, + GCompareDataFunc compare_func, + gpointer user_data, + NMUtilsNamedValue * provided_buffer, + guint provided_buffer_len, + NMUtilsNamedValue **out_allocated_buffer); + +#define nm_utils_named_values_from_strdict(hash, out_len, array, out_allocated_buffer) \ + nm_utils_named_values_from_strdict_full((hash), \ + (out_len), \ + nm_strcmp_p_with_data, \ + NULL, \ + (array), \ + G_N_ELEMENTS(array), \ + (out_allocated_buffer)) + +gssize nm_utils_named_value_list_find(const NMUtilsNamedValue *arr, + gsize len, + const char * name, + gboolean sorted); + +gboolean nm_utils_named_value_list_is_sorted(const NMUtilsNamedValue *arr, + gsize len, + gboolean accept_duplicates, + GCompareDataFunc compare_func, + gpointer user_data); + +void nm_utils_named_value_list_sort(NMUtilsNamedValue *arr, + gsize len, + GCompareDataFunc compare_func, + gpointer user_data); + +void nm_utils_named_value_clear_with_g_free(NMUtilsNamedValue *val); + +/*****************************************************************************/ + +gpointer *nm_utils_hash_keys_to_array(GHashTable * hash, + GCompareDataFunc compare_func, + gpointer user_data, + guint * out_len); + +gpointer *nm_utils_hash_values_to_array(GHashTable * hash, + GCompareDataFunc compare_func, + gpointer user_data, + guint * out_len); + +static inline const char ** +nm_utils_strdict_get_keys(const GHashTable *hash, gboolean sorted, guint *out_length) +{ + return (const char **) nm_utils_hash_keys_to_array((GHashTable *) hash, + sorted ? nm_strcmp_p_with_data : NULL, + NULL, + out_length); +} + +gboolean nm_utils_hashtable_equal(const GHashTable *a, + const GHashTable *b, + gboolean treat_null_as_empty, + GEqualFunc equal_func); + +gboolean nm_utils_hashtable_cmp_equal(const GHashTable *a, + const GHashTable *b, + GCompareDataFunc cmp_values, + gpointer user_data); + +static inline gboolean +nm_utils_hashtable_same_keys(const GHashTable *a, const GHashTable *b) +{ + return nm_utils_hashtable_cmp_equal(a, b, NULL, NULL); +} + +int nm_utils_hashtable_cmp(const GHashTable *a, + const GHashTable *b, + gboolean do_fast_precheck, + GCompareDataFunc cmp_keys, + GCompareDataFunc cmp_values, + gpointer user_data); + +char **nm_utils_strv_make_deep_copied(const char **strv); + +char **nm_utils_strv_make_deep_copied_n(const char **strv, gsize len); + +static inline char ** +nm_utils_strv_make_deep_copied_nonnull(const char **strv) +{ + return nm_utils_strv_make_deep_copied(strv) ?: g_new0(char *, 1); +} + +char **_nm_utils_strv_dup(const char *const *strv, gssize len, gboolean deep_copied); + +#define nm_utils_strv_dup(strv, len, deep_copied) \ + _nm_utils_strv_dup(NM_CAST_STRV_CC(strv), (len), (deep_copied)) + +const char **_nm_utils_strv_dup_packed(const char *const *strv, gssize len); + +#define nm_utils_strv_dup_packed(strv, len) _nm_utils_strv_dup_packed(NM_CAST_STRV_CC(strv), (len)) + +/*****************************************************************************/ + +GSList *nm_utils_g_slist_find_str(const GSList *list, const char *needle); + +int nm_utils_g_slist_strlist_cmp(const GSList *a, const GSList *b); + +char *nm_utils_g_slist_strlist_join(const GSList *a, const char *separator); + +/*****************************************************************************/ + +static inline guint +nm_g_array_len(const GArray *arr) +{ + return arr ? arr->len : 0u; +} + +static inline void +nm_g_array_unref(GArray *arr) +{ + if (arr) + g_array_unref(arr); +} + +#define nm_g_array_append_new(arr, type) \ + ({ \ + GArray *const _arr = (arr); \ + guint _len; \ + \ + nm_assert(_arr); \ + _len = _arr->len; \ + nm_assert(_len < G_MAXUINT); \ + g_array_set_size(_arr, _len + 1u); \ + &g_array_index(arr, type, _len); \ + }) + +/*****************************************************************************/ + +static inline GPtrArray * +nm_g_ptr_array_ref(GPtrArray *arr) +{ + return arr ? g_ptr_array_ref(arr) : NULL; +} + +static inline void +nm_g_ptr_array_unref(GPtrArray *arr) +{ + if (arr) + g_ptr_array_unref(arr); +} + +#define nm_g_ptr_array_set(pdst, val) \ + ({ \ + GPtrArray **_pdst = (pdst); \ + GPtrArray * _val = (val); \ + gboolean _changed = FALSE; \ + \ + nm_assert(_pdst); \ + \ + if (*_pdst != _val) { \ + _nm_unused gs_unref_ptrarray GPtrArray *_old = *_pdst; \ + \ + *_pdst = nm_g_ptr_array_ref(_val); \ + _changed = TRUE; \ + } \ + _changed; \ + }) + +#define nm_g_ptr_array_set_take(pdst, val) \ + ({ \ + GPtrArray **_pdst = (pdst); \ + GPtrArray * _val = (val); \ + gboolean _changed = FALSE; \ + \ + nm_assert(_pdst); \ + \ + if (*_pdst != _val) { \ + _nm_unused gs_unref_ptrarray GPtrArray *_old = *_pdst; \ + \ + *_pdst = _val; \ + _changed = TRUE; \ + } else { \ + nm_g_ptr_array_unref(_val); \ + } \ + _changed; \ + }) + +static inline guint +nm_g_ptr_array_len(const GPtrArray *arr) +{ + return arr ? arr->len : 0u; +} + +static inline gpointer * +nm_g_ptr_array_pdata(const GPtrArray *arr) +{ + return arr ? arr->pdata : NULL; +} + +GPtrArray *_nm_g_ptr_array_copy(GPtrArray * array, + GCopyFunc func, + gpointer user_data, + GDestroyNotify element_free_func); + +/** + * nm_g_ptr_array_copy: + * @array: the #GPtrArray to clone. + * @func: the copy function. + * @user_data: the user data for the copy function + * @element_free_func: the free function of the elements. @array MUST have + * the same element_free_func. This argument is only used on older + * glib, that doesn't support g_ptr_array_copy(). + * + * This is a replacement for g_ptr_array_copy(), which is not available + * before glib 2.62. Since GPtrArray does not allow to access the internal + * element_free_func, we cannot add a compatibility implementation of g_ptr_array_copy() + * and the user must provide a suitable destroy function. + * + * Note that the @element_free_func MUST correspond to free function set in @array. + */ +#if GLIB_CHECK_VERSION(2, 62, 0) + #define nm_g_ptr_array_copy(array, func, user_data, element_free_func) \ + ({ \ + _nm_unused GDestroyNotify const _element_free_func = (element_free_func); \ + \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; \ + g_ptr_array_copy((array), (func), (user_data)); \ + G_GNUC_END_IGNORE_DEPRECATIONS; \ + }) +#else + #define nm_g_ptr_array_copy(array, func, user_data, element_free_func) \ + _nm_g_ptr_array_copy((array), (func), (user_data), (element_free_func)) +#endif + +/*****************************************************************************/ + +static inline GHashTable * +nm_g_hash_table_ref(GHashTable *hash) +{ + return hash ? g_hash_table_ref(hash) : NULL; +} + +static inline void +nm_g_hash_table_unref(GHashTable *hash) +{ + if (hash) + g_hash_table_unref(hash); +} + +static inline guint +nm_g_hash_table_size(GHashTable *hash) +{ + return hash ? g_hash_table_size(hash) : 0u; +} + +static inline gpointer +nm_g_hash_table_lookup(GHashTable *hash, gconstpointer key) +{ + return hash ? g_hash_table_lookup(hash, key) : NULL; +} + +static inline gboolean +nm_g_hash_table_contains(GHashTable *hash, gconstpointer key) +{ + return hash ? g_hash_table_contains(hash, key) : FALSE; +} + +static inline gboolean +nm_g_hash_table_remove(GHashTable *hash, gconstpointer key) +{ + return hash ? g_hash_table_remove(hash, key) : FALSE; +} + +/*****************************************************************************/ + +gssize nm_utils_ptrarray_find_binary_search(gconstpointer * list, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data, + gssize * out_idx_first, + gssize * out_idx_last); + +gssize nm_utils_array_find_binary_search(gconstpointer list, + gsize elem_size, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data); + +/*****************************************************************************/ + +void _nm_utils_strv_sort(const char **strv, gssize len); +#define nm_utils_strv_sort(strv, len) _nm_utils_strv_sort(NM_CAST_STRV_MC(strv), len) + +int +_nm_utils_strv_cmp_n(const char *const *strv1, gssize len1, const char *const *strv2, gssize len2); + +#define nm_utils_strv_cmp_n(strv1, len1, strv2, len2) \ + _nm_utils_strv_cmp_n(NM_CAST_STRV_CC(strv1), (len1), NM_CAST_STRV_CC(strv2), (len2)) + +#define nm_utils_strv_equal(strv1, strv2) (nm_utils_strv_cmp_n((strv1), -1, (strv2), -1) == 0) + +/*****************************************************************************/ + +#define NM_UTILS_NSEC_PER_SEC ((gint64) 1000000000) +#define NM_UTILS_USEC_PER_SEC ((gint64) 1000000) +#define NM_UTILS_MSEC_PER_SEC ((gint64) 1000) +#define NM_UTILS_NSEC_PER_MSEC ((gint64) 1000000) + +static inline gint64 +NM_UTILS_NSEC_TO_MSEC_CEIL(gint64 nsec) +{ + return (nsec + (NM_UTILS_NSEC_PER_MSEC - 1)) / NM_UTILS_NSEC_PER_MSEC; +} + +/*****************************************************************************/ + +int nm_utils_fd_wait_for_event(int fd, int event, gint64 timeout_nsec); +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); + +/*****************************************************************************/ + +#define NM_DEFINE_GDBUS_ARG_INFO_FULL(name_, ...) \ + ((GDBusArgInfo *) (&((const GDBusArgInfo){.ref_count = -1, .name = name_, __VA_ARGS__}))) + +#define NM_DEFINE_GDBUS_ARG_INFO(name_, a_signature) \ + NM_DEFINE_GDBUS_ARG_INFO_FULL(name_, .signature = a_signature, ) + +#define NM_DEFINE_GDBUS_ARG_INFOS(...) \ + ((GDBusArgInfo **) ((const GDBusArgInfo *[]){ \ + __VA_ARGS__ NULL, \ + })) + +#define NM_DEFINE_GDBUS_PROPERTY_INFO(name_, ...) \ + ((GDBusPropertyInfo *) (&( \ + (const GDBusPropertyInfo){.ref_count = -1, .name = name_, __VA_ARGS__}))) + +#define NM_DEFINE_GDBUS_PROPERTY_INFO_READABLE(name_, m_signature) \ + NM_DEFINE_GDBUS_PROPERTY_INFO(name_, \ + .signature = m_signature, \ + .flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE, ) + +#define NM_DEFINE_GDBUS_PROPERTY_INFOS(...) \ + ((GDBusPropertyInfo **) ((const GDBusPropertyInfo *[]){ \ + __VA_ARGS__ NULL, \ + })) + +#define NM_DEFINE_GDBUS_SIGNAL_INFO_INIT(name_, ...) \ + { \ + .ref_count = -1, .name = name_, __VA_ARGS__ \ + } + +#define NM_DEFINE_GDBUS_SIGNAL_INFO(name_, ...) \ + ((GDBusSignalInfo *) (&( \ + (const GDBusSignalInfo) NM_DEFINE_GDBUS_SIGNAL_INFO_INIT(name_, __VA_ARGS__)))) + +#define NM_DEFINE_GDBUS_SIGNAL_INFOS(...) \ + ((GDBusSignalInfo **) ((const GDBusSignalInfo *[]){ \ + __VA_ARGS__ NULL, \ + })) + +#define NM_DEFINE_GDBUS_METHOD_INFO_INIT(name_, ...) \ + { \ + .ref_count = -1, .name = name_, __VA_ARGS__ \ + } + +#define NM_DEFINE_GDBUS_METHOD_INFO(name_, ...) \ + ((GDBusMethodInfo *) (&( \ + (const GDBusMethodInfo) NM_DEFINE_GDBUS_METHOD_INFO_INIT(name_, __VA_ARGS__)))) + +#define NM_DEFINE_GDBUS_METHOD_INFOS(...) \ + ((GDBusMethodInfo **) ((const GDBusMethodInfo *[]){ \ + __VA_ARGS__ NULL, \ + })) + +#define NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(name_, ...) \ + { \ + .ref_count = -1, .name = name_, __VA_ARGS__ \ + } + +#define NM_DEFINE_GDBUS_INTERFACE_INFO(name_, ...) \ + ((GDBusInterfaceInfo *) (&( \ + (const GDBusInterfaceInfo) NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(name_, __VA_ARGS__)))) + +#define NM_DEFINE_GDBUS_INTERFACE_VTABLE(...) \ + ((GDBusInterfaceVTable *) (&((const GDBusInterfaceVTable){__VA_ARGS__}))) + +/*****************************************************************************/ + +guint64 nm_utils_get_start_time_for_pid(pid_t pid, char *out_state, pid_t *out_ppid); + +static inline gboolean +nm_utils_process_state_is_dead(char pstate) +{ + /* "/proc/[pid]/stat" returns a state as the 3rd fields (see `man 5 proc`). + * Some of these states indicate the process is effectively dead (or a zombie). + */ + return NM_IN_SET(pstate, 'Z', 'x', 'X'); +} + +/*****************************************************************************/ + +typedef struct _NMUtilsUserData NMUtilsUserData; + +NMUtilsUserData *_nm_utils_user_data_pack(int nargs, gconstpointer *args); + +#define nm_utils_user_data_pack(...) \ + _nm_utils_user_data_pack(NM_NARG(__VA_ARGS__), (gconstpointer[]){__VA_ARGS__}) + +void _nm_utils_user_data_unpack(NMUtilsUserData *user_data, int nargs, ...); + +#define nm_utils_user_data_unpack(user_data, ...) \ + _nm_utils_user_data_unpack(user_data, NM_NARG(__VA_ARGS__), __VA_ARGS__) + +/*****************************************************************************/ + +typedef void (*NMUtilsInvokeOnIdleCallback)(gpointer user_data, GCancellable *cancellable); + +void nm_utils_invoke_on_idle(GCancellable * cancellable, + NMUtilsInvokeOnIdleCallback callback, + gpointer callback_user_data); + +void nm_utils_invoke_on_timeout(guint timeout_msec, + GCancellable * cancellable, + NMUtilsInvokeOnIdleCallback callback, + gpointer callback_user_data); + +/*****************************************************************************/ + +GSource *nm_utils_g_main_context_create_integrate_source(GMainContext *internal); + +/*****************************************************************************/ + +static inline GPtrArray * +nm_strv_ptrarray_ensure(GPtrArray **p_arr) +{ + nm_assert(p_arr); + + if (G_UNLIKELY(!*p_arr)) + *p_arr = g_ptr_array_new_with_free_func(g_free); + + return *p_arr; +} + +static inline const char *const * +nm_strv_ptrarray_get_unsafe(GPtrArray *arr, guint *out_len) +{ + /* warning: the GPtrArray is not NULL terminated. So, it + * isn't really a strv array (sorry the misnomer). That's why + * the function is potentially "unsafe" and you must provide a + * out_len parameter. */ + if (!arr || arr->len == 0) { + *out_len = 0; + return NULL; + } + *out_len = arr->len; + return (const char *const *) arr->pdata; +} + +static inline GPtrArray * +nm_strv_ptrarray_clone(const GPtrArray *src, gboolean null_if_empty) +{ + if (!src || (null_if_empty && src->len == 0)) + return NULL; + return nm_g_ptr_array_copy((GPtrArray *) src, nm_copy_func_g_strdup, NULL, g_free); +} + +static inline void +nm_strv_ptrarray_add_string_take(GPtrArray *cmd, char *str) +{ + nm_assert(cmd); + nm_assert(str); + + g_ptr_array_add(cmd, str); +} + +static inline void +nm_strv_ptrarray_add_string_dup(GPtrArray *cmd, const char *str) +{ + nm_strv_ptrarray_add_string_take(cmd, g_strdup(str)); +} + +#define nm_strv_ptrarray_add_string_concat(cmd, ...) \ + nm_strv_ptrarray_add_string_take((cmd), g_strconcat(__VA_ARGS__, NULL)) + +#define nm_strv_ptrarray_add_string_printf(cmd, ...) \ + nm_strv_ptrarray_add_string_take((cmd), g_strdup_printf(__VA_ARGS__)) + +#define nm_strv_ptrarray_add_int(cmd, val) \ + nm_strv_ptrarray_add_string_take((cmd), nm_strdup_int(val)) + +static inline void +nm_strv_ptrarray_take_gstring(GPtrArray *cmd, GString **gstr) +{ + nm_assert(gstr && *gstr); + + nm_strv_ptrarray_add_string_take(cmd, g_string_free(g_steal_pointer(gstr), FALSE)); +} + +static inline gssize +nm_strv_ptrarray_find_first(const GPtrArray *strv, const char *str) +{ + if (!strv) + return -1; + return nm_utils_strv_find_first((char **) strv->pdata, strv->len, str); +} + +static inline gboolean +nm_strv_ptrarray_contains(const GPtrArray *strv, const char *str) +{ + return nm_strv_ptrarray_find_first(strv, str) >= 0; +} + +static inline int +nm_strv_ptrarray_cmp(const GPtrArray *a, const GPtrArray *b) +{ + /* nm_utils_strv_cmp_n() will treat NULL and empty arrays the same. + * That means, an empty strv array can both be represented by NULL + * and an array of length zero. + * If you need to distinguish between these case, do that yourself. */ + return nm_utils_strv_cmp_n((const char *const *) nm_g_ptr_array_pdata(a), + nm_g_ptr_array_len(a), + (const char *const *) nm_g_ptr_array_pdata(b), + nm_g_ptr_array_len(b)); +} + +/*****************************************************************************/ + +int nm_utils_getpagesize(void); + +/*****************************************************************************/ + +extern const char _nm_hexchar_table_lower[16]; +extern const char _nm_hexchar_table_upper[16]; + +static inline char +nm_hexchar(int x, gboolean upper_case) +{ + return upper_case ? _nm_hexchar_table_upper[x & 15] : _nm_hexchar_table_lower[x & 15]; +} + +char *nm_utils_bin2hexstr_full(gconstpointer addr, + gsize length, + char delimiter, + gboolean upper_case, + char * out); + +#define nm_utils_bin2hexstr_a(addr, length, delimiter, upper_case, str_to_free) \ + ({ \ + gconstpointer _addr = (addr); \ + gsize _length = (length); \ + char _delimiter = (delimiter); \ + char ** _str_to_free = (str_to_free); \ + char * _s; \ + gsize _s_len; \ + \ + nm_assert(_str_to_free); \ + \ + _s_len = _length == 0 ? 1u : (_delimiter == '\0' ? _length * 2u + 1u : _length * 3u); \ + if (_s_len < 100) \ + _s = g_alloca(_s_len); \ + else { \ + _s = g_malloc(_s_len); \ + *_str_to_free = _s; \ + } \ + nm_utils_bin2hexstr_full(_addr, _length, _delimiter, (upper_case), _s); \ + }) + +static inline const char * +nm_ether_addr_to_string(const NMEtherAddr *ether_addr, char sbuf[static(sizeof(NMEtherAddr) * 3)]) +{ + nm_assert(ether_addr); + nm_assert(sbuf); + + return nm_utils_bin2hexstr_full(ether_addr, sizeof(NMEtherAddr), ':', TRUE, sbuf); +} + +#define nm_ether_addr_to_string_a(ether_addr) \ + nm_ether_addr_to_string((ether_addr), g_alloca(sizeof(NMEtherAddr) * 3)) + +guint8 *nm_utils_hexstr2bin_full(const char *hexstr, + gboolean allow_0x_prefix, + gboolean delimiter_required, + gboolean hexdigit_pairs_required, + const char *delimiter_candidates, + gsize required_len, + guint8 * buffer, + gsize buffer_len, + gsize * out_len); + +#define nm_utils_hexstr2bin_buf(hexstr, \ + allow_0x_prefix, \ + delimiter_required, \ + delimiter_candidates, \ + buffer) \ + nm_utils_hexstr2bin_full((hexstr), \ + (allow_0x_prefix), \ + (delimiter_required), \ + FALSE, \ + (delimiter_candidates), \ + G_N_ELEMENTS(buffer), \ + (buffer), \ + G_N_ELEMENTS(buffer), \ + NULL) + +guint8 *nm_utils_hexstr2bin_alloc(const char *hexstr, + gboolean allow_0x_prefix, + gboolean delimiter_required, + const char *delimiter_candidates, + gsize required_len, + gsize * out_len); + +/** + * _nm_utils_hwaddr_aton: + * @asc: the ASCII representation of a hardware address + * @buffer: buffer to store the result into. Must have + * at least a size of @buffer_length. + * @buffer_length: the length of the input buffer @buffer. + * The result must fit into that buffer, otherwise + * the function fails and returns %NULL. + * @out_length: the output length in case of success. + * + * Parses @asc and converts it to binary form in @buffer. + * Bytes in @asc can be separated by colons (:), or hyphens (-), but not mixed. + * + * It is like nm_utils_hwaddr_aton(), but contrary to that it + * can parse addresses of any length. That is, you don't need + * to know the length before-hand. + * + * Return value: @buffer, or %NULL if @asc couldn't be parsed. + */ +static inline guint8 * +_nm_utils_hwaddr_aton(const char *asc, gpointer buffer, gsize buffer_length, gsize *out_length) +{ + g_return_val_if_fail(asc, NULL); + g_return_val_if_fail(buffer, NULL); + g_return_val_if_fail(buffer_length > 0, NULL); + g_return_val_if_fail(out_length, NULL); + + return nm_utils_hexstr2bin_full(asc, + FALSE, + TRUE, + FALSE, + ":-", + 0, + buffer, + buffer_length, + out_length); +} + +static inline guint8 * +_nm_utils_hwaddr_aton_exact(const char *asc, gpointer buffer, gsize buffer_length) +{ + g_return_val_if_fail(asc, NULL); + g_return_val_if_fail(buffer, NULL); + g_return_val_if_fail(buffer_length > 0, NULL); + + return nm_utils_hexstr2bin_full(asc, + FALSE, + TRUE, + FALSE, + ":-", + buffer_length, + buffer, + buffer_length, + NULL); +} + +static inline const char * +_nm_utils_hwaddr_ntoa(gconstpointer addr, + gsize addr_len, + gboolean upper_case, + char * buf, + gsize buf_len) +{ + g_return_val_if_fail(addr, NULL); + g_return_val_if_fail(addr_len > 0, NULL); + g_return_val_if_fail(buf, NULL); + if (buf_len < addr_len * 3) + g_return_val_if_reached(NULL); + + return nm_utils_bin2hexstr_full(addr, addr_len, ':', upper_case, buf); +} + +/*****************************************************************************/ + +#define _NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \ + value_type, \ + value_type_result, \ + entry_cmd, \ + unknown_val_cmd, \ + get_operator, \ + ...) \ + value_type_result fcn_name(const char *name) \ + { \ + static const struct { \ + const char *name; \ + value_type value; \ + } LIST[] = {__VA_ARGS__}; \ + \ + if (NM_MORE_ASSERT_ONCE(5)) { \ + int i; \ + \ + for (i = 0; i < G_N_ELEMENTS(LIST); i++) { \ + nm_assert(LIST[i].name); \ + if (i > 0) \ + nm_assert(strcmp(LIST[i - 1].name, LIST[i].name) < 0); \ + } \ + } \ + \ + { \ + entry_cmd; \ + } \ + \ + if (G_LIKELY(name)) { \ + G_STATIC_ASSERT(G_N_ELEMENTS(LIST) > 1); \ + G_STATIC_ASSERT(G_N_ELEMENTS(LIST) < G_MAXINT / 2 - 10); \ + int imin = 0; \ + int imax = (G_N_ELEMENTS(LIST) - 1); \ + int imid = (G_N_ELEMENTS(LIST) - 1) / 2; \ + \ + for (;;) { \ + const int cmp = strcmp(LIST[imid].name, name); \ + \ + if (G_UNLIKELY(cmp == 0)) \ + return get_operator(LIST[imid].value); \ + \ + if (cmp < 0) \ + imin = imid + 1; \ + else \ + imax = imid - 1; \ + \ + if (G_UNLIKELY(imin > imax)) \ + break; \ + \ + /* integer overflow cannot happen, because LIST is shorter than G_MAXINT/2. */ \ + imid = (imin + imax) / 2; \ + } \ + } \ + \ + { \ + unknown_val_cmd; \ + } \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +#define NM_UTILS_STRING_TABLE_LOOKUP_STRUCT_DEFINE(fcn_name, \ + result_type, \ + entry_cmd, \ + unknown_val_cmd, \ + ...) \ + _NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \ + result_type, \ + const result_type *, \ + entry_cmd, \ + unknown_val_cmd, \ + &, \ + __VA_ARGS__) + +#define NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \ + result_type, \ + entry_cmd, \ + unknown_val_cmd, \ + ...) \ + _NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \ + result_type, \ + result_type, \ + entry_cmd, \ + unknown_val_cmd, \ + , \ + __VA_ARGS__) + +/*****************************************************************************/ + +static inline GTask * +nm_g_task_new(gpointer source_object, + GCancellable * cancellable, + gpointer source_tag, + GAsyncReadyCallback callback, + gpointer callback_data) +{ + GTask *task; + + task = g_task_new(source_object, cancellable, callback, callback_data); + if (source_tag) + g_task_set_source_tag(task, source_tag); + return task; +} + +static inline gboolean +nm_g_task_is_valid(gpointer task, gpointer source_object, gpointer source_tag) +{ + return g_task_is_valid(task, source_object) && g_task_get_source_tag(task) == source_tag; +} + +guint nm_utils_parse_debug_string(const char *string, const GDebugKey *keys, guint nkeys); + +/*****************************************************************************/ + +static inline gboolean +nm_utils_strdup_reset(char **dst, const char *src) +{ + char *old; + + nm_assert(dst); + + if (nm_streq0(*dst, src)) + return FALSE; + old = *dst; + *dst = g_strdup(src); + g_free(old); + return TRUE; +} + +static inline gboolean +nm_utils_strdup_reset_take(char **dst, char *src) +{ + char *old; + + nm_assert(dst); + nm_assert(src != *dst); + + if (nm_streq0(*dst, src)) { + if (src) + g_free(src); + return FALSE; + } + old = *dst; + *dst = src; + g_free(old); + return TRUE; +} + +void nm_indirect_g_free(gpointer arg); + +/*****************************************************************************/ + +void nm_utils_ifname_cpy(char *dst, const char *name); + +typedef enum { + NMU_IFACE_ANY, + NMU_IFACE_KERNEL, + NMU_IFACE_OVS, + NMU_IFACE_OVS_AND_KERNEL, +} NMUtilsIfaceType; + +gboolean nm_utils_ifname_valid_kernel(const char *name, GError **error); + +gboolean nm_utils_ifname_valid(const char *name, NMUtilsIfaceType type, GError **error); + +/*****************************************************************************/ + +static inline GArray * +nm_strvarray_ensure(GArray **p) +{ + if (!*p) { + *p = g_array_new(TRUE, FALSE, sizeof(char *)); + g_array_set_clear_func(*p, nm_indirect_g_free); + } + return *p; +} + +static inline void +nm_strvarray_add(GArray *array, const char *str) +{ + char *s; + + s = g_strdup(str); + g_array_append_val(array, s); +} + +static inline const char *const * +nm_strvarray_get_strv_non_empty(GArray *arr, guint *length) +{ + if (!arr || arr->len == 0) { + NM_SET_OUT(length, 0); + return NULL; + } + + NM_SET_OUT(length, arr->len); + return &g_array_index(arr, const char *, 0); +} + +static inline const char *const * +nm_strvarray_get_strv(GArray **arr, guint *length) +{ + if (!*arr) { + NM_SET_OUT(length, 0); + return (const char *const *) arr; + } + + NM_SET_OUT(length, (*arr)->len); + return &g_array_index(*arr, const char *, 0); +} + +static inline void +nm_strvarray_set_strv(GArray **array, const char *const *strv) +{ + gs_unref_array GArray *array_old = NULL; + + array_old = g_steal_pointer(array); + + if (!strv || !strv[0]) + return; + + nm_strvarray_ensure(array); + for (; strv[0]; strv++) + nm_strvarray_add(*array, strv[0]); +} + +static inline gboolean +nm_strvarray_remove_first(GArray *strv, const char *needle) +{ + guint i; + + nm_assert(needle); + + if (strv) { + for (i = 0; i < strv->len; i++) { + if (nm_streq(needle, g_array_index(strv, const char *, i))) { + g_array_remove_index(strv, i); + return TRUE; + } + } + } + return FALSE; +} + +/*****************************************************************************/ + +struct _NMVariantAttributeSpec { + char * name; + const GVariantType *type; + bool v4 : 1; + bool v6 : 1; + bool no_value : 1; + bool consumes_rest : 1; + char str_type; +}; + +typedef struct _NMVariantAttributeSpec NMVariantAttributeSpec; + +void _nm_utils_format_variant_attributes_full(GString * str, + const NMUtilsNamedValue * values, + guint num_values, + const NMVariantAttributeSpec *const *spec, + char attr_separator, + char key_value_separator); + +char *_nm_utils_format_variant_attributes(GHashTable * attributes, + const NMVariantAttributeSpec *const *spec, + char attr_separator, + char key_value_separator); + +/*****************************************************************************/ + +gboolean nm_utils_is_localhost(const char *name); + +gboolean nm_utils_is_specific_hostname(const char *name); + +char * nm_utils_uid_to_name(uid_t uid); +gboolean nm_utils_name_to_uid(const char *name, uid_t *out_uid); + +#endif /* __NM_SHARED_UTILS_H__ */ diff --git a/src/libnm-glib-aux/nm-str-buf.h b/src/libnm-glib-aux/nm-str-buf.h new file mode 100644 index 0000000000..cb0d3fb189 --- /dev/null +++ b/src/libnm-glib-aux/nm-str-buf.h @@ -0,0 +1,488 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NM_STR_BUF_H__ +#define __NM_STR_BUF_H__ + +#include "nm-shared-utils.h" +#include "nm-secret-utils.h" + +/*****************************************************************************/ + +/* NMStrBuf is not unlike GString. The main difference is that it can use + * nm_explicit_bzero() when growing the buffer. */ +typedef struct _NMStrBuf { + char *_priv_str; + + /* The unions only exist because we allow/encourage read-only access + * to the "len" and "allocated" fields, but modifying the fields is + * only allowed to the NMStrBuf implementation itself. */ + union { + /*const*/ gsize len; + gsize _priv_len; + }; + union { + /*const*/ gsize allocated; + gsize _priv_allocated; + }; + + bool _priv_do_bzero_mem; +} NMStrBuf; + +/*****************************************************************************/ + +static inline void +_nm_str_buf_assert(const NMStrBuf *strbuf) +{ + nm_assert(strbuf); + nm_assert((!!strbuf->_priv_str) == (strbuf->_priv_allocated > 0)); + nm_assert(strbuf->_priv_len <= strbuf->_priv_allocated); +} + +static inline NMStrBuf +NM_STR_BUF_INIT(gsize allocated, gboolean do_bzero_mem) +{ + NMStrBuf strbuf = { + ._priv_str = allocated ? g_malloc(allocated) : NULL, + ._priv_allocated = allocated, + ._priv_len = 0, + ._priv_do_bzero_mem = do_bzero_mem, + }; + + return strbuf; +} + +static inline void +nm_str_buf_init(NMStrBuf *strbuf, gsize len, bool do_bzero_mem) +{ + nm_assert(strbuf); + *strbuf = NM_STR_BUF_INIT(len, do_bzero_mem); + _nm_str_buf_assert(strbuf); +} + +void _nm_str_buf_ensure_size(NMStrBuf *strbuf, gsize new_size, gboolean reserve_exact); + +static inline void +nm_str_buf_maybe_expand(NMStrBuf *strbuf, gsize reserve, gboolean reserve_exact) +{ + _nm_str_buf_assert(strbuf); + nm_assert(strbuf->_priv_len < G_MAXSIZE - reserve); + + /* @reserve is the extra space that we require. */ + if (G_UNLIKELY(reserve > strbuf->_priv_allocated - strbuf->_priv_len)) + _nm_str_buf_ensure_size(strbuf, strbuf->_priv_len + reserve, reserve_exact); +} + +/*****************************************************************************/ + +/** + * nm_str_buf_set_size: + * @strbuf: the initialized #NMStrBuf + * @new_len: the new length + * @honor_do_bzero_mem: if %TRUE, the shrunk memory will be cleared, if + * do_bzero_mem is set. This should be usually set to %TRUE, unless + * you know that the shrunk memory does not contain data that requires to be + * cleared. When growing the size, this value has no effect. + * @reserve_exact: when growing the buffer, reserve the exact amount of bytes. + * If %FALSE, the buffer may allocate more memory than requested to grow + * exponentially. + * + * This is like g_string_set_size(). If new_len is smaller than the + * current length, the string gets truncated (excess memory will be cleared). + * + * When extending the length, the added bytes are undefined (like with + * g_string_set_size(). Likewise, if you first pre-allocate a buffer with + * nm_str_buf_maybe_expand(), then write to the bytes, and finally set + * the appropriate size, then that works as expected (by not clearing the + * pre-existing, grown buffer). + */ +static inline void +nm_str_buf_set_size(NMStrBuf *strbuf, + gsize new_len, + gboolean honor_do_bzero_mem, + gboolean reserve_exact) +{ + _nm_str_buf_assert(strbuf); + + if (new_len < strbuf->_priv_len) { + if (honor_do_bzero_mem && strbuf->_priv_do_bzero_mem) { + /* we only clear the memory that we wrote to. */ + nm_explicit_bzero(&strbuf->_priv_str[new_len], strbuf->_priv_len - new_len); + } + } else if (new_len > strbuf->_priv_len) { + nm_str_buf_maybe_expand(strbuf, + new_len - strbuf->_priv_len + (reserve_exact ? 0u : 1u), + reserve_exact); + } else + return; + + strbuf->_priv_len = new_len; +} + +/*****************************************************************************/ + +static inline void +nm_str_buf_erase(NMStrBuf *strbuf, gsize pos, gssize len, gboolean honor_do_bzero_mem) +{ + gsize new_len; + + _nm_str_buf_assert(strbuf); + + nm_assert(pos <= strbuf->_priv_len); + + if (len == 0) + return; + + if (len < 0) { + /* truncate the string before pos */ + nm_assert(len == -1); + new_len = pos; + } else { + gsize l = len; + + nm_assert(l <= strbuf->_priv_len - pos); + + new_len = strbuf->_priv_len - l; + if (pos + l < strbuf->_priv_len) { + memmove(&strbuf->_priv_str[pos], + &strbuf->_priv_str[pos + l], + strbuf->_priv_len - (pos + l)); + } + } + + nm_assert(new_len <= strbuf->_priv_len); + nm_str_buf_set_size(strbuf, new_len, honor_do_bzero_mem, TRUE); +} + +/*****************************************************************************/ + +static inline void +nm_str_buf_append_c_repeated(NMStrBuf *strbuf, char ch, guint len) +{ + if (len > 0) { + nm_str_buf_maybe_expand(strbuf, len + 1, FALSE); + do { + strbuf->_priv_str[strbuf->_priv_len++] = ch; + } while (--len > 0); + } +} + +static inline void +nm_str_buf_append_c(NMStrBuf *strbuf, char ch) +{ + nm_str_buf_maybe_expand(strbuf, 2, FALSE); + strbuf->_priv_str[strbuf->_priv_len++] = ch; +} + +static inline void +nm_str_buf_append_c2(NMStrBuf *strbuf, char ch0, char ch1) +{ + nm_str_buf_maybe_expand(strbuf, 3, FALSE); + strbuf->_priv_str[strbuf->_priv_len++] = ch0; + strbuf->_priv_str[strbuf->_priv_len++] = ch1; +} + +static inline void +nm_str_buf_append_c4(NMStrBuf *strbuf, char ch0, char ch1, char ch2, char ch3) +{ + nm_str_buf_maybe_expand(strbuf, 5, FALSE); + strbuf->_priv_str[strbuf->_priv_len++] = ch0; + strbuf->_priv_str[strbuf->_priv_len++] = ch1; + strbuf->_priv_str[strbuf->_priv_len++] = ch2; + strbuf->_priv_str[strbuf->_priv_len++] = ch3; +} + +static inline void +nm_str_buf_append_c_hex(NMStrBuf *strbuf, char ch, gboolean upper_case) +{ + nm_str_buf_maybe_expand(strbuf, 3, FALSE); + strbuf->_priv_str[strbuf->_priv_len++] = nm_hexchar(((guchar) ch) >> 4, upper_case); + strbuf->_priv_str[strbuf->_priv_len++] = nm_hexchar((guchar) ch, upper_case); +} + +static inline void +nm_str_buf_append_len(NMStrBuf *strbuf, const char *str, gsize len) +{ + _nm_str_buf_assert(strbuf); + + if (len > 0) { + nm_str_buf_maybe_expand(strbuf, len + 1, FALSE); + memcpy(&strbuf->_priv_str[strbuf->_priv_len], str, len); + strbuf->_priv_len += len; + } +} + +static inline char * +nm_str_buf_append_len0(NMStrBuf *strbuf, const char *str, gsize len) +{ + _nm_str_buf_assert(strbuf); + + /* this is basically like nm_str_buf_append_len() and + * nm_str_buf_get_str() in one. */ + + nm_str_buf_maybe_expand(strbuf, len + 1u, FALSE); + if (len > 0) { + memcpy(&strbuf->_priv_str[strbuf->_priv_len], str, len); + strbuf->_priv_len += len; + } + strbuf->_priv_str[strbuf->_priv_len] = '\0'; + return strbuf->_priv_str; +} + +static inline void +nm_str_buf_append(NMStrBuf *strbuf, const char *str) +{ + nm_assert(str); + + nm_str_buf_append_len(strbuf, str, strlen(str)); +} + +static inline char * +nm_str_buf_append0(NMStrBuf *strbuf, const char *str) +{ + nm_assert(str); + + return nm_str_buf_append_len0(strbuf, str, strlen(str)); +} + +void nm_str_buf_append_printf(NMStrBuf *strbuf, const char *format, ...) _nm_printf(2, 3); + +static inline void +nm_str_buf_ensure_trailing_c(NMStrBuf *strbuf, char ch) +{ + _nm_str_buf_assert(strbuf); + + if (strbuf->_priv_len == 0 || strbuf->_priv_str[strbuf->_priv_len - 1] != ch) + nm_str_buf_append_c(strbuf, ch); +} + +static inline NMStrBuf * +nm_str_buf_append_required_delimiter(NMStrBuf *strbuf, char delimiter) +{ + _nm_str_buf_assert(strbuf); + + /* appends the @delimiter if it is required (that is, if the + * string is not empty). */ + if (strbuf->len > 0) + nm_str_buf_append_c(strbuf, delimiter); + return strbuf; +} + +static inline void +nm_str_buf_append_dirty(NMStrBuf *strbuf, gsize len) +{ + _nm_str_buf_assert(strbuf); + + /* this append @len bytes to the buffer, but it does not + * initialize them! */ + if (len > 0) { + nm_str_buf_maybe_expand(strbuf, len, FALSE); + strbuf->_priv_len += len; + } +} + +static inline void +nm_str_buf_append_c_len(NMStrBuf *strbuf, char ch, gsize len) +{ + _nm_str_buf_assert(strbuf); + + if (len > 0) { + nm_str_buf_maybe_expand(strbuf, len, FALSE); + memset(&strbuf->_priv_str[strbuf->_priv_len], ch, len); + strbuf->_priv_len += len; + } +} + +/*****************************************************************************/ + +static inline NMStrBuf * +nm_str_buf_reset(NMStrBuf *strbuf) +{ + _nm_str_buf_assert(strbuf); + + if (strbuf->_priv_len > 0) { + if (strbuf->_priv_do_bzero_mem) { + /* we only clear the memory that we wrote to. */ + nm_explicit_bzero(strbuf->_priv_str, strbuf->_priv_len); + } + strbuf->_priv_len = 0; + } + + return strbuf; +} + +/*****************************************************************************/ + +/* Calls nm_utils_escaped_tokens_escape() on @str and appends the + * result to @strbuf. */ +static inline void +nm_utils_escaped_tokens_escape_strbuf(const char *str, const char *delimiters, NMStrBuf *strbuf) +{ + gs_free char *str_to_free = NULL; + + nm_assert(str); + + nm_str_buf_append(strbuf, nm_utils_escaped_tokens_escape(str, delimiters, &str_to_free)); +} + +/* Calls nm_utils_escaped_tokens_escape_unnecessary() on @str and appends the + * string to @strbuf. */ +static inline void +nm_utils_escaped_tokens_escape_strbuf_assert(const char *str, + const char *delimiters, + NMStrBuf * strbuf) +{ + nm_str_buf_append(strbuf, nm_utils_escaped_tokens_escape_unnecessary(str, delimiters)); +} + +/*****************************************************************************/ + +static inline gboolean +nm_str_buf_is_initalized(NMStrBuf *strbuf) +{ + nm_assert(strbuf); +#if NM_MORE_ASSERTS + if (strbuf->_priv_str) + _nm_str_buf_assert(strbuf); +#endif + return !!strbuf->_priv_str; +} + +/** + * nm_str_buf_get_str: + * @strbuf: the #NMStrBuf instance + * + * Returns the NUL terminated internal string. + * + * While constructing the string, the intermediate buffer + * is not NUL terminated (this makes it different from GString). + * Usually, one would build the string and retrieve it at the + * end with nm_str_buf_finalize(). This returns the NUL terminated + * buffer that was appended so far. Contrary to nm_str_buf_finalize(), you + * can still append more data to the buffer and this does not transfer ownership + * of the string. + * + * Returns: (transfer none): the internal string. The string + * is of length "strbuf->len", which may be larger if the + * returned string contains NUL characters (binary). The terminating + * NUL character is always present after "strbuf->len" characters. + * If currently no buffer is allocated, this will return %NULL. + */ +static inline char * +nm_str_buf_get_str(NMStrBuf *strbuf) +{ + _nm_str_buf_assert(strbuf); + + if (!strbuf->_priv_str) + return NULL; + + nm_str_buf_maybe_expand(strbuf, 1, FALSE); + strbuf->_priv_str[strbuf->_priv_len] = '\0'; + return strbuf->_priv_str; +} + +static inline char * +nm_str_buf_get_str_unsafe(NMStrBuf *strbuf) +{ + _nm_str_buf_assert(strbuf); + return strbuf->_priv_str; +} + +static inline char * +nm_str_buf_get_str_at_unsafe(NMStrBuf *strbuf, gsize index) +{ + _nm_str_buf_assert(strbuf); + + /* it is acceptable to ask for a pointer at the end of the buffer -- even + * if there is no data there. The caller is anyway required to take care + * of the length (that's the "unsafe" part), and in that case, the length + * is merely zero. */ + nm_assert(index <= strbuf->allocated); + + if (!strbuf->_priv_str) + return NULL; + + return &strbuf->_priv_str[index]; +} + +static inline char +nm_str_buf_get_char(const NMStrBuf *strbuf, gsize index) +{ + _nm_str_buf_assert(strbuf); + nm_assert(index < strbuf->allocated); + return strbuf->_priv_str[index]; +} + +/** + * nm_str_buf_finalize: + * @strbuf: an initilized #NMStrBuf + * @out_len: (out): (allow-none): optional output + * argument with the length of the returned string. + * + * Returns: (transfer full): the string of the buffer + * which must be freed by the caller. The @strbuf + * is afterwards in undefined state, though it can be + * reused after nm_str_buf_init(). + * Note that if no string is allocated yet (after nm_str_buf_init() with + * length zero), this will return %NULL. */ +static inline char * +nm_str_buf_finalize(NMStrBuf *strbuf, gsize *out_len) +{ + _nm_str_buf_assert(strbuf); + + NM_SET_OUT(out_len, strbuf->_priv_len); + + if (!strbuf->_priv_str) + return NULL; + + nm_str_buf_maybe_expand(strbuf, 1, TRUE); + strbuf->_priv_str[strbuf->_priv_len] = '\0'; + + /* the buffer is in invalid state afterwards, however, we clear it + * so far, that nm_auto_str_buf and nm_str_buf_destroy() is happy. */ + return g_steal_pointer(&strbuf->_priv_str); +} + +static inline GBytes * +nm_str_buf_finalize_to_gbytes(NMStrBuf *strbuf) +{ + char *s; + gsize l; + + /* this always returns a non-NULL, newly allocated GBytes instance. + * The data buffer always has an additional NUL character after + * the data, and the data is allocated with malloc. + * + * That means, the caller who takes ownership of the GBytes can + * safely modify the content of the buffer (including the additional + * NUL sentinel). */ + s = nm_str_buf_finalize(strbuf, &l); + return g_bytes_new_take(s ?: g_new0(char, 1), l); +} + +/** + * nm_str_buf_destroy: + * @strbuf: an initialized #NMStrBuf + * + * Frees the associated memory of @strbuf. The buffer + * afterwards is in undefined state, but can be re-initialized + * with nm_str_buf_init(). + */ +static inline void +nm_str_buf_destroy(NMStrBuf *strbuf) +{ + if (!strbuf->_priv_str) + return; + _nm_str_buf_assert(strbuf); + if (strbuf->_priv_do_bzero_mem) + nm_explicit_bzero(strbuf->_priv_str, strbuf->_priv_len); + g_free(strbuf->_priv_str); + + /* the buffer is in invalid state afterwards, however, we clear it + * so far, that nm_auto_str_buf is happy when calling + * nm_str_buf_destroy() again. */ + strbuf->_priv_str = NULL; +} + +#define nm_auto_str_buf nm_auto(nm_str_buf_destroy) + +#endif /* __NM_STR_BUF_H__ */ diff --git a/src/libnm-glib-aux/nm-time-utils.c b/src/libnm-glib-aux/nm-time-utils.c new file mode 100644 index 0000000000..f30e6a1994 --- /dev/null +++ b/src/libnm-glib-aux/nm-time-utils.c @@ -0,0 +1,328 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-time-utils.h" + +#include "nm-logging-fwd.h" + +/*****************************************************************************/ + +typedef struct { + /* the offset to the native clock, in seconds. */ + gint64 offset_sec; + clockid_t clk_id; +} GlobalState; + +static const GlobalState *volatile p_global_state; + +static const GlobalState * +_t_init_global_state(void) +{ + static GlobalState global_state = {}; + static gsize init_once = 0; + const GlobalState *p; + clockid_t clk_id; + struct timespec tp; + gint64 offset_sec; + int r; + + clk_id = CLOCK_BOOTTIME; + r = clock_gettime(clk_id, &tp); + if (r == -1 && errno == EINVAL) { + clk_id = CLOCK_MONOTONIC; + r = clock_gettime(clk_id, &tp); + } + + /* The only failure we tolerate is that CLOCK_BOOTTIME is not supported. + * Other than that, we rely on kernel to not fail on this. */ + g_assert(r == 0); + g_assert(tp.tv_nsec >= 0 && tp.tv_nsec < NM_UTILS_NSEC_PER_SEC); + + /* Calculate an offset for the time stamp. + * + * We always want positive values, because then we can initialize + * a timestamp with 0 and be sure, that it will be less then any + * value nm_utils_get_monotonic_timestamp_*() might return. + * For this to be true also for nm_utils_get_monotonic_timestamp_sec() at + * early boot, we have to shift the timestamp to start counting at + * least from 1 second onward. + * + * Another advantage of shifting is, that this way we make use of the whole 31 bit + * range of signed int, before the time stamp for nm_utils_get_monotonic_timestamp_sec() + * wraps (~68 years). + **/ + offset_sec = (-((gint64) tp.tv_sec)) + 1; + + if (!g_once_init_enter(&init_once)) { + /* there was a race. We expect the pointer to be fully initialized now. */ + p = g_atomic_pointer_get(&p_global_state); + g_assert(p); + return p; + } + + global_state.offset_sec = offset_sec; + global_state.clk_id = clk_id; + p = &global_state; + g_atomic_pointer_set(&p_global_state, p); + g_once_init_leave(&init_once, 1); + + _nm_utils_monotonic_timestamp_initialized(&tp, p->offset_sec, p->clk_id == CLOCK_BOOTTIME); + + return p; +} + +#define _t_get_global_state() \ + ({ \ + const GlobalState *_p; \ + \ + _p = g_atomic_pointer_get(&p_global_state); \ + (G_LIKELY(_p) ? _p : _t_init_global_state()); \ + }) + +#define _t_clock_gettime_eval(p, tp) \ + ({ \ + struct timespec *const _tp = (tp); \ + const GlobalState *const _p2 = (p); \ + int _r; \ + \ + nm_assert(_tp); \ + \ + _r = clock_gettime(_p2->clk_id, _tp); \ + \ + nm_assert(_r == 0); \ + nm_assert(_tp->tv_nsec >= 0 && _tp->tv_nsec < NM_UTILS_NSEC_PER_SEC); \ + \ + _p2; \ + }) + +#define _t_clock_gettime(tp) _t_clock_gettime_eval(_t_get_global_state(), tp); + +/*****************************************************************************/ + +/** + * nm_utils_get_monotonic_timestamp_nsec: + * + * Returns: a monotonically increasing time stamp in nanoseconds, + * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME. + * + * The returned value will start counting at an undefined point + * in the past and will always be positive. + * + * All the nm_utils_get_monotonic_timestamp_*sec functions return the same + * timestamp but in different scales (nsec, usec, msec, sec). + **/ +gint64 +nm_utils_get_monotonic_timestamp_nsec(void) +{ + const GlobalState *p; + struct timespec tp; + + p = _t_clock_gettime(&tp); + + /* Although the result will always be positive, we return a signed + * integer, which makes it easier to calculate time differences (when + * you want to subtract signed values). + **/ + return (((gint64) tp.tv_sec) + p->offset_sec) * NM_UTILS_NSEC_PER_SEC + tp.tv_nsec; +} + +/** + * nm_utils_get_monotonic_timestamp_usec: + * + * Returns: a monotonically increasing time stamp in microseconds, + * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME. + * + * The returned value will start counting at an undefined point + * in the past and will always be positive. + * + * All the nm_utils_get_monotonic_timestamp_*sec functions return the same + * timestamp but in different scales (nsec, usec, msec, sec). + **/ +gint64 +nm_utils_get_monotonic_timestamp_usec(void) +{ + const GlobalState *p; + struct timespec tp; + + p = _t_clock_gettime(&tp); + + /* Although the result will always be positive, we return a signed + * integer, which makes it easier to calculate time differences (when + * you want to subtract signed values). + **/ + return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) G_USEC_PER_SEC) + + (tp.tv_nsec / (NM_UTILS_NSEC_PER_SEC / G_USEC_PER_SEC)); +} + +/** + * nm_utils_get_monotonic_timestamp_msec: + * + * Returns: a monotonically increasing time stamp in milliseconds, + * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME. + * + * The returned value will start counting at an undefined point + * in the past and will always be positive. + * + * All the nm_utils_get_monotonic_timestamp_*sec functions return the same + * timestamp but in different scales (nsec, usec, msec, sec). + **/ +gint64 +nm_utils_get_monotonic_timestamp_msec(void) +{ + const GlobalState *p; + struct timespec tp; + + p = _t_clock_gettime(&tp); + + /* Although the result will always be positive, we return a signed + * integer, which makes it easier to calculate time differences (when + * you want to subtract signed values). + **/ + return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) 1000) + + (tp.tv_nsec / (NM_UTILS_NSEC_PER_SEC / 1000)); +} + +/** + * nm_utils_get_monotonic_timestamp_sec: + * + * Returns: nm_utils_get_monotonic_timestamp_msec() in seconds (throwing + * away sub second parts). The returned value will always be positive. + * + * This value wraps after roughly 68 years which should be fine for any + * practical purpose. + * + * All the nm_utils_get_monotonic_timestamp_*sec functions return the same + * timestamp but in different scales (nsec, usec, msec, sec). + **/ +gint32 +nm_utils_get_monotonic_timestamp_sec(void) +{ + const GlobalState *p; + struct timespec tp; + + p = _t_clock_gettime(&tp); + + return (((gint64) tp.tv_sec) + p->offset_sec); +} + +/** + * nm_utils_monotonic_timestamp_as_boottime: + * @timestamp: the monotonic-timestamp that should be converted into CLOCK_BOOTTIME. + * @timestamp_nsec_per_tick: How many nanoseconds make one unit of @timestamp? E.g. if + * @timestamp is in unit seconds, pass %NM_UTILS_NSEC_PER_SEC; if @timestamp is + * in nanoseconds, pass 1; if @timestamp is in milliseconds, pass %NM_UTILS_NSEC_PER_SEC/1000. + * This must be a multiple of 10, and between 1 and %NM_UTILS_NSEC_PER_SEC. + * + * Returns: the monotonic-timestamp as CLOCK_BOOTTIME, as returned by clock_gettime(). + * The unit is the same as the passed in @timestamp based on @timestamp_nsec_per_tick. + * E.g. if you passed @timestamp in as seconds, it will return boottime in seconds. + * + * Note that valid monotonic-timestamps are always positive numbers (counting roughly since + * the application is running). However, it might make sense to calculate a timestamp from + * before the application was running, hence negative @timestamp is allowed. The result + * in that case might also be a negative timestamp (in CLOCK_BOOTTIME), which would indicate + * that the timestamp lies in the past before the machine was booted. + * + * On older kernels that don't support CLOCK_BOOTTIME, the returned time is instead CLOCK_MONOTONIC. + **/ +gint64 +nm_utils_monotonic_timestamp_as_boottime(gint64 timestamp, gint64 timestamp_nsec_per_tick) +{ + const GlobalState *p; + gint64 offset; + + /* only support nsec-per-tick being a multiple of 10. */ + g_return_val_if_fail(timestamp_nsec_per_tick == 1 + || (timestamp_nsec_per_tick > 0 + && timestamp_nsec_per_tick <= NM_UTILS_NSEC_PER_SEC + && timestamp_nsec_per_tick % 10 == 0), + -1); + + /* if the caller didn't yet ever fetch a monotonic-timestamp, he cannot pass any meaningful + * value (because he has no idea what these timestamps would be). That would be a bug. */ + nm_assert(g_atomic_pointer_get(&p_global_state)); + + p = _t_get_global_state(); + + nm_assert(p->offset_sec <= 0); + + /* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */ + offset = p->offset_sec * (NM_UTILS_NSEC_PER_SEC / timestamp_nsec_per_tick); + + nm_assert(offset <= 0 && offset > G_MININT64); + + /* check for overflow (note that offset is non-positive). */ + g_return_val_if_fail(timestamp < G_MAXINT64 + offset, G_MAXINT64); + + return timestamp - offset; +} + +/** + * nm_utils_monotonic_timestamp_from_boottime: + * @boottime: the timestamp from CLOCK_BOOTTIME (or CLOCK_MONOTONIC, if + * kernel does not support CLOCK_BOOTTIME and monotonic timestamps are based + * on CLOCK_MONOTONIC). + * @timestamp_nsec_per_tick: the scale in which @boottime is. If @boottime is in + * nano seconds, this should be 1. If it is in milli seconds, this should be + * %NM_UTILS_NSEC_PER_SEC/1000, etc. + * + * Returns: the same timestamp in monotonic timestamp scale. + * + * Note that commonly monotonic timestamps are positive. But they may not + * be positive in this case. That's when boottime is taken from a time before + * the monotonic timestamps started counting. So, that means a zero or negative + * value is still a valid timestamp. + * + * This is the inverse of nm_utils_monotonic_timestamp_as_boottime(). + */ +gint64 +nm_utils_monotonic_timestamp_from_boottime(guint64 boottime, gint64 timestamp_nsec_per_tick) +{ + const GlobalState *p; + gint64 offset; + + /* only support nsec-per-tick being a multiple of 10. */ + g_return_val_if_fail(timestamp_nsec_per_tick == 1 + || (timestamp_nsec_per_tick > 0 + && timestamp_nsec_per_tick <= NM_UTILS_NSEC_PER_SEC + && timestamp_nsec_per_tick % 10 == 0), + -1); + + p = _t_get_global_state(); + + nm_assert(p->offset_sec <= 0); + + /* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */ + offset = p->offset_sec * (NM_UTILS_NSEC_PER_SEC / timestamp_nsec_per_tick); + + nm_assert(offset <= 0 && offset > G_MININT64); + + /* check for overflow (note that offset is non-positive). */ + g_return_val_if_fail(boottime < G_MAXINT64, G_MAXINT64); + + return (gint64) boottime + offset; +} + +gint64 +nm_utils_clock_gettime_nsec(clockid_t clockid) +{ + struct timespec tp; + + if (clock_gettime(clockid, &tp) != 0) + return -NM_ERRNO_NATIVE(errno); + return nm_utils_timespec_to_nsec(&tp); +} + +gint64 +nm_utils_clock_gettime_msec(clockid_t clockid) +{ + struct timespec tp; + + if (clock_gettime(clockid, &tp) != 0) + return -NM_ERRNO_NATIVE(errno); + return nm_utils_timespec_to_msec(&tp); +} diff --git a/src/libnm-glib-aux/nm-time-utils.h b/src/libnm-glib-aux/nm-time-utils.h new file mode 100644 index 0000000000..3c3e935f8d --- /dev/null +++ b/src/libnm-glib-aux/nm-time-utils.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_TIME_UTILS_H__ +#define __NM_TIME_UTILS_H__ + +#include <time.h> + +static inline gint64 +nm_utils_timespec_to_nsec(const struct timespec *ts) +{ + return (((gint64) ts->tv_sec) * ((gint64) NM_UTILS_NSEC_PER_SEC)) + ((gint64) ts->tv_nsec); +} + +static inline gint64 +nm_utils_timespec_to_msec(const struct timespec *ts) +{ + return (((gint64) ts->tv_sec) * ((gint64) 1000)) + + (((gint64) ts->tv_nsec) / ((gint64) NM_UTILS_NSEC_PER_SEC / 1000)); +} + +gint64 nm_utils_get_monotonic_timestamp_nsec(void); +gint64 nm_utils_get_monotonic_timestamp_usec(void); +gint64 nm_utils_get_monotonic_timestamp_msec(void); +gint32 nm_utils_get_monotonic_timestamp_sec(void); + +gint64 nm_utils_monotonic_timestamp_as_boottime(gint64 timestamp, gint64 timestamp_ticks_per_nsec); +gint64 nm_utils_monotonic_timestamp_from_boottime(guint64 boottime, gint64 timestamp_nsec_per_tick); + +static inline gint64 +nm_utils_get_monotonic_timestamp_nsec_cached(gint64 *cache_now) +{ + return (*cache_now) ?: (*cache_now = nm_utils_get_monotonic_timestamp_nsec()); +} + +static inline gint64 +nm_utils_get_monotonic_timestamp_msec_cached(gint64 *cache_now) +{ + return (*cache_now) ?: (*cache_now = nm_utils_get_monotonic_timestamp_msec()); +} + +gint64 nm_utils_clock_gettime_nsec(clockid_t clockid); +gint64 nm_utils_clock_gettime_msec(clockid_t clockid); + +#endif /* __NM_TIME_UTILS_H__ */ diff --git a/src/libnm-glib-aux/nm-value-type.h b/src/libnm-glib-aux/nm-value-type.h new file mode 100644 index 0000000000..f9edebdb6c --- /dev/null +++ b/src/libnm-glib-aux/nm-value-type.h @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#ifndef __NM_VALUE_TYPE_H__ +#define __NM_VALUE_TYPE_H__ + +typedef enum { + NM_VALUE_TYPE_UNSPEC = 1, + NM_VALUE_TYPE_BOOL = 2, + NM_VALUE_TYPE_INT32 = 3, + NM_VALUE_TYPE_INT = 4, + NM_VALUE_TYPE_STRING = 5, +} NMValueType; + +/*****************************************************************************/ + +#ifdef NM_VALUE_TYPE_DEFINE_FUNCTIONS + +typedef union { + bool v_bool; + gint32 v_int32; + int v_int; + const char *v_string; + + /* for convenience, also let the union contain other pointer types. These are + * for NM_VALUE_TYPE_UNSPEC. */ + gconstpointer * v_ptr; + const GPtrArray *v_ptrarray; + +} NMValueTypUnion; + + /* Set the NMValueTypUnion. You can also assign the member directly. + * The only purpose of this is that it also returns a pointer to the + * union. So, you can do + * + * ptr = NM_VALUE_TYP_UNION_SET (&value_typ_union_storage, v_bool, TRUE); + */ + #define NM_VALUE_TYP_UNION_SET(_arg, _type, _val) \ + ({ \ + NMValueTypUnion *const _arg2 = (_arg); \ + \ + *_arg2 = (NMValueTypUnion){ \ + ._type = (_val), \ + }; \ + _arg2; \ + }) + +typedef struct { + bool has; + NMValueTypUnion val; +} NMValueTypUnioMaybe; + + #define NM_VALUE_TYP_UNIO_MAYBE_SET(_arg, _type, _val) \ + ({ \ + NMValueTypUnioMaybe *const _arg2 = (_arg); \ + \ + *_arg2 = (NMValueTypUnioMaybe){ \ + .has = TRUE, \ + .val._type = (_val), \ + }; \ + _arg2; \ + }) + +/*****************************************************************************/ + +static inline int +nm_value_type_cmp(NMValueType value_type, gconstpointer p_a, gconstpointer p_b) +{ + switch (value_type) { + case NM_VALUE_TYPE_BOOL: + NM_CMP_DIRECT(*((const bool *) p_a), *((const bool *) p_b)); + return 0; + case NM_VALUE_TYPE_INT32: + NM_CMP_DIRECT(*((const gint32 *) p_a), *((const gint32 *) p_b)); + return 0; + case NM_VALUE_TYPE_INT: + NM_CMP_DIRECT(*((const int *) p_a), *((const int *) p_b)); + return 0; + case NM_VALUE_TYPE_STRING: + return nm_strcmp0(*((const char *const *) p_a), *((const char *const *) p_b)); + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached(); + return 0; +} + +static inline gboolean +nm_value_type_equal(NMValueType value_type, gconstpointer p_a, gconstpointer p_b) +{ + return nm_value_type_cmp(value_type, p_a, p_b) == 0; +} + +static inline void +nm_value_type_copy(NMValueType value_type, gpointer dst, gconstpointer src) +{ + switch (value_type) { + case NM_VALUE_TYPE_BOOL: + (*((bool *) dst) = *((const bool *) src)); + return; + case NM_VALUE_TYPE_INT32: + (*((gint32 *) dst) = *((const gint32 *) src)); + return; + case NM_VALUE_TYPE_INT: + (*((int *) dst) = *((const int *) src)); + return; + case NM_VALUE_TYPE_STRING: + /* self assignment safe! */ + if (*((char **) dst) != *((const char *const *) src)) { + g_free(*((char **) dst)); + *((char **) dst) = g_strdup(*((const char *const *) src)); + } + return; + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached(); +} + +static inline void +nm_value_type_get_from_variant(NMValueType value_type, + gpointer dst, + GVariant * variant, + gboolean clone) +{ + switch (value_type) { + case NM_VALUE_TYPE_BOOL: + *((bool *) dst) = g_variant_get_boolean(variant); + return; + case NM_VALUE_TYPE_INT32: + *((gint32 *) dst) = g_variant_get_int32(variant); + return; + case NM_VALUE_TYPE_STRING: + if (clone) { + g_free(*((char **) dst)); + *((char **) dst) = g_variant_dup_string(variant, NULL); + } else { + /* we don't clone the string, nor free the previous value. */ + *((const char **) dst) = g_variant_get_string(variant, NULL); + } + return; + + case NM_VALUE_TYPE_INT: + /* "int" also does not have a define variant type, because it's not + * clear how many bits we would need. */ + + /* fall-through */ + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached(); +} + +static inline GVariant * +nm_value_type_to_variant(NMValueType value_type, gconstpointer src) +{ + const char *v_string; + + switch (value_type) { + case NM_VALUE_TYPE_BOOL: + return g_variant_new_boolean(*((const bool *) src)); + case NM_VALUE_TYPE_INT32: + return g_variant_new_int32(*((const gint32 *) src)); + case NM_VALUE_TYPE_STRING: + v_string = *((const char *const *) src); + return v_string ? g_variant_new_string(v_string) : NULL; + + case NM_VALUE_TYPE_INT: + /* "int" also does not have a define variant type, because it's not + * clear how many bits we would need. */ + + /* fall-through */ + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached(); + return NULL; +} + +static inline const GVariantType * +nm_value_type_get_variant_type(NMValueType value_type) +{ + switch (value_type) { + case NM_VALUE_TYPE_BOOL: + return G_VARIANT_TYPE_BOOLEAN; + case NM_VALUE_TYPE_INT32: + return G_VARIANT_TYPE_INT32; + case NM_VALUE_TYPE_STRING: + return G_VARIANT_TYPE_STRING; + + case NM_VALUE_TYPE_INT: + /* "int" also does not have a define variant type, because it's not + * clear how many bits we would need. */ + + /* fall-through */ + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached(); + return NULL; +} + + /*****************************************************************************/ + +#endif /* NM_VALUE_TYPE_DEFINE_FUNCTIONS */ + +#endif /* __NM_VALUE_TYPE_H__ */ diff --git a/src/libnm-glib-aux/tests/meson.build b/src/libnm-glib-aux/tests/meson.build new file mode 100644 index 0000000000..38dfff0c6c --- /dev/null +++ b/src/libnm-glib-aux/tests/meson.build @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +exe = executable( + 'test-shared-general', + 'test-shared-general.c', + dependencies: libnm_glib_aux_dep_link, + link_with: libnm_log_null, +) + +test( + 'shared/nm-glib-aux/test-shared-general', + test_script, + args: test_args + [exe.full_path()], + timeout: default_test_timeout, +) + +if jansson_dep.found() + exe = executable( + 'test-json-aux', + 'test-json-aux.c', + dependencies: [ + libnm_glib_aux_dep_link, + jansson_dep, + dl_dep, + ], + link_with: libnm_log_null, + ) + + test( + 'shared/nm-glib-aux/test-json-aux', + test_script, + args: test_args + [exe.full_path()], + timeout: default_test_timeout, + ) +endif diff --git a/src/libnm-glib-aux/tests/test-json-aux.c b/src/libnm-glib-aux/tests/test-json-aux.c new file mode 100644 index 0000000000..b27504482d --- /dev/null +++ b/src/libnm-glib-aux/tests/test-json-aux.c @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-glib-aux/nm-default-glib-i18n-prog.h" + +#include <jansson.h> + +#include "libnm-glib-aux/nm-json-aux.h" + +#include "nm-utils/nm-test-utils.h" + +/*****************************************************************************/ + +static void +test_jansson(void) +{ + const NMJsonVt * vt; + nm_auto_decref_json nm_json_t *js1 = NULL; + nm_auto_decref_json nm_json_t *js2 = NULL; + +#define _ASSERT_FIELD(type1, type2, field) \ + G_STMT_START \ + { \ + G_STATIC_ASSERT_EXPR(sizeof(((type1 *) NULL)->field) == sizeof(((type2 *) NULL)->field)); \ + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(type1, field) == G_STRUCT_OFFSET(type2, field)); \ + } \ + G_STMT_END + + G_STATIC_ASSERT_EXPR(NM_JSON_REJECT_DUPLICATES == JSON_REJECT_DUPLICATES); + + G_STATIC_ASSERT_EXPR(sizeof(nm_json_type) == sizeof(json_type)); + + G_STATIC_ASSERT_EXPR((int) NM_JSON_OBJECT == JSON_OBJECT); + G_STATIC_ASSERT_EXPR((int) NM_JSON_ARRAY == JSON_ARRAY); + G_STATIC_ASSERT_EXPR((int) NM_JSON_STRING == JSON_STRING); + G_STATIC_ASSERT_EXPR((int) NM_JSON_INTEGER == JSON_INTEGER); + G_STATIC_ASSERT_EXPR((int) NM_JSON_REAL == JSON_REAL); + G_STATIC_ASSERT_EXPR((int) NM_JSON_TRUE == JSON_TRUE); + G_STATIC_ASSERT_EXPR((int) NM_JSON_FALSE == JSON_FALSE); + G_STATIC_ASSERT_EXPR((int) NM_JSON_NULL == JSON_NULL); + + G_STATIC_ASSERT_EXPR(sizeof(nm_json_int_t) == sizeof(json_int_t)); + + G_STATIC_ASSERT_EXPR(sizeof(nm_json_t) == sizeof(json_t)); + _ASSERT_FIELD(nm_json_t, json_t, refcount); + _ASSERT_FIELD(nm_json_t, json_t, type); + + G_STATIC_ASSERT_EXPR(NM_JSON_ERROR_TEXT_LENGTH == JSON_ERROR_TEXT_LENGTH); + G_STATIC_ASSERT_EXPR(NM_JSON_ERROR_SOURCE_LENGTH == JSON_ERROR_SOURCE_LENGTH); + + G_STATIC_ASSERT_EXPR(sizeof(nm_json_error_t) == sizeof(json_error_t)); + _ASSERT_FIELD(nm_json_error_t, json_error_t, line); + _ASSERT_FIELD(nm_json_error_t, json_error_t, column); + _ASSERT_FIELD(nm_json_error_t, json_error_t, position); + _ASSERT_FIELD(nm_json_error_t, json_error_t, source); + _ASSERT_FIELD(nm_json_error_t, json_error_t, text); + + vt = nm_json_vt(); + + g_assert(vt); + g_assert(vt->loaded); + + js1 = vt->nm_json_loads("{ \"a\": 5 }", 0, NULL); + g_assert(js1); + nm_json_decref(vt, g_steal_pointer(&js1)); + + js2 = vt->nm_json_loads("{ \"a\": 6 }", 0, NULL); + g_assert(js2); + +#define CHECK_FCN(vt, fcn, nm_type, js_type) \ + G_STMT_START \ + { \ + const NMJsonVt *const _vt = (vt); \ + _nm_unused nm_type = (_vt->nm_##fcn); \ + _nm_unused js_type = (fcn); \ + \ + g_assert(_vt->nm_##fcn); \ + g_assert(_f_nm); \ + g_assert(_f_js); \ + g_assert(_f_nm == _vt->nm_##fcn); \ + } \ + G_STMT_END + + CHECK_FCN(vt, json_array, nm_json_t * (*_f_nm)(void), json_t * (*_f_js)(void) ); + CHECK_FCN(vt, + json_array_append_new, + int (*_f_nm)(nm_json_t *, nm_json_t *), + int (*_f_js)(json_t *, json_t *)); + CHECK_FCN(vt, + json_array_get, + nm_json_t * (*_f_nm)(const nm_json_t *, gsize), + json_t * (*_f_js)(const json_t *, size_t)); + CHECK_FCN(vt, + json_array_size, + gsize(*_f_nm)(const nm_json_t *), + size_t(*_f_js)(const json_t *)); + CHECK_FCN(vt, json_delete, void (*_f_nm)(nm_json_t *), void (*_f_js)(json_t *)); + CHECK_FCN(vt, + json_dumps, + char *(*_f_nm)(const nm_json_t *, gsize), + char *(*_f_js)(const json_t *, size_t)); + CHECK_FCN(vt, json_false, nm_json_t * (*_f_nm)(void), json_t * (*_f_js)(void) ); + CHECK_FCN(vt, json_integer, nm_json_t * (*_f_nm)(nm_json_int_t), json_t * (*_f_js)(json_int_t)); + CHECK_FCN(vt, + json_integer_value, + nm_json_int_t(*_f_nm)(const nm_json_t *), + json_int_t(*_f_js)(const json_t *)); + CHECK_FCN(vt, + json_loads, + nm_json_t * (*_f_nm)(const char *, gsize, nm_json_error_t *), + json_t * (*_f_js)(const char *, size_t, json_error_t *) ); + CHECK_FCN(vt, json_object, nm_json_t * (*_f_nm)(void), json_t * (*_f_js)(void) ); + CHECK_FCN(vt, + json_object_del, + int (*_f_nm)(nm_json_t *, const char *), + int (*_f_js)(json_t *, const char *)); + CHECK_FCN(vt, + json_object_get, + nm_json_t * (*_f_nm)(const nm_json_t *, const char *), + json_t * (*_f_js)(const json_t *, const char *) ); + CHECK_FCN(vt, json_object_iter, void *(*_f_nm)(nm_json_t *), void *(*_f_js)(json_t *) ); + CHECK_FCN(vt, + json_object_iter_key, + const char *(*_f_nm)(void *), + const char *(*_f_js)(void *) ); + CHECK_FCN(vt, + json_object_iter_next, + void *(*_f_nm)(nm_json_t *, void *), + void *(*_f_js)(json_t *, void *) ); + CHECK_FCN(vt, json_object_iter_value, nm_json_t * (*_f_nm)(void *), json_t * (*_f_js)(void *) ); + CHECK_FCN(vt, + json_object_key_to_iter, + void *(*_f_nm)(const char *), + void *(*_f_js)(const char *) ); + CHECK_FCN(vt, + json_object_set_new, + int (*_f_nm)(nm_json_t *, const char *, nm_json_t *), + int (*_f_js)(json_t *, const char *, json_t *)); + CHECK_FCN(vt, + json_object_size, + gsize(*_f_nm)(const nm_json_t *), + size_t(*_f_js)(const json_t *)); + CHECK_FCN(vt, + json_string, + nm_json_t * (*_f_nm)(const char *), + json_t * (*_f_js)(const char *) ); + CHECK_FCN(vt, + json_string_value, + const char *(*_f_nm)(const nm_json_t *), + const char *(*_f_js)(const json_t *) ); + CHECK_FCN(vt, json_true, nm_json_t * (*_f_nm)(void), json_t * (*_f_js)(void) ); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init(&argc, &argv, TRUE); + + g_test_add_func("/general/test_jansson", test_jansson); + + return g_test_run(); +} diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c new file mode 100644 index 0000000000..c98cce1595 --- /dev/null +++ b/src/libnm-glib-aux/tests/test-shared-general.c @@ -0,0 +1,1289 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-prog.h" + +#include "nm-std-aux/unaligned.h" +#include "libnm-glib-aux/nm-random-utils.h" +#include "libnm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-time-utils.h" +#include "libnm-glib-aux/nm-ref-string.h" + +#include "nm-utils/nm-test-utils.h" + +/*****************************************************************************/ + +G_STATIC_ASSERT(NM_AF_UNSPEC == AF_UNSPEC); +G_STATIC_ASSERT(NM_AF_INET == AF_INET); +G_STATIC_ASSERT(NM_AF_INET6 == AF_INET6); + +G_STATIC_ASSERT(NM_AF_INET_SIZE == sizeof(in_addr_t)); +G_STATIC_ASSERT(NM_AF_INET_SIZE == sizeof(struct in_addr)); +G_STATIC_ASSERT(NM_AF_INET6_SIZE == sizeof(struct in6_addr)); + +G_STATIC_ASSERT(4 == _nm_alignof(in_addr_t)); +G_STATIC_ASSERT(4 == _nm_alignof(struct in_addr)); +G_STATIC_ASSERT(4 == _nm_alignof(struct in6_addr)); +G_STATIC_ASSERT(4 == _nm_alignof(NMIPAddr)); + +/*****************************************************************************/ + +static void +test_gpid(void) +{ + const int *int_ptr; + GPid pid = 42; + + /* We redefine G_PID_FORMAT, because it's only available since glib 2.53.5. + * + * Also, this is the format for GPid, which for glib is always a typedef + * for "int". Add a check for that here. + * + * G_PID_FORMAT is not about pid_t, which might be a smaller int, and which we would + * check with SIZEOF_PID_T. */ + G_STATIC_ASSERT(sizeof(GPid) == sizeof(int)); + + g_assert_cmpstr("" G_PID_FORMAT, ==, "i"); + + /* check that it's really "int". We will get a compiler warning, if that's not + * the case. */ + int_ptr = &pid; + g_assert_cmpint(*int_ptr, ==, 42); +} + +/*****************************************************************************/ + +static void +test_monotonic_timestamp(void) +{ + g_assert(nm_utils_get_monotonic_timestamp_sec() > 0); +} + +/*****************************************************************************/ + +static void +test_nmhash(void) +{ + int rnd; + + nm_utils_random_bytes(&rnd, sizeof(rnd)); + + g_assert(nm_hash_val(555, 4) != 0); +} + +/*****************************************************************************/ + +static const char * +_make_strv_foo(void) +{ + return "foo"; +} + +static const char *const *const _tst_make_strv_1 = NM_MAKE_STRV("1", "2"); + +static void +test_make_strv(void) +{ + const char *const *v1a = NM_MAKE_STRV("a"); + const char *const *v1b = NM_MAKE_STRV("a", ); + const char *const *v2a = NM_MAKE_STRV("a", "b"); + const char *const *v2b = NM_MAKE_STRV("a", "b", ); + const char *const v3[] = { + "a", + "b", + }; + const char *const *v4b = NM_MAKE_STRV("a", _make_strv_foo(), ); + + g_assert(NM_PTRARRAY_LEN(v1a) == 1); + g_assert(NM_PTRARRAY_LEN(v1b) == 1); + g_assert(NM_PTRARRAY_LEN(v2a) == 2); + g_assert(NM_PTRARRAY_LEN(v2b) == 2); + + g_assert(NM_PTRARRAY_LEN(_tst_make_strv_1) == 2); + g_assert_cmpstr(_tst_make_strv_1[0], ==, "1"); + g_assert_cmpstr(_tst_make_strv_1[1], ==, "2"); + /* writing the static read-only variable leads to crash .*/ + //((char **) _tst_make_strv_1)[0] = NULL; + //((char **) _tst_make_strv_1)[2] = "c"; + + G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(v3) == 2); + + g_assert(NM_PTRARRAY_LEN(v4b) == 2); + + G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(NM_MAKE_STRV("a", "b")) == 3); + G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(NM_MAKE_STRV("a", "b", )) == 3); + + nm_strquote_a(300, ""); +} + +/*****************************************************************************/ + +typedef enum { + TEST_NM_STRDUP_ENUM_m1 = -1, + TEST_NM_STRDUP_ENUM_3 = 3, +} TestNMStrdupIntEnum; + +static void +test_nm_strdup_int(void) +{ +#define _NM_STRDUP_INT_TEST(num, str) \ + G_STMT_START \ + { \ + gs_free char *_s1 = NULL; \ + \ + _s1 = nm_strdup_int((num)); \ + \ + g_assert(_s1); \ + g_assert_cmpstr(_s1, ==, str); \ + } \ + G_STMT_END + +#define _NM_STRDUP_INT_TEST_TYPED(type, num) \ + G_STMT_START \ + { \ + type _num = ((type) num); \ + \ + _NM_STRDUP_INT_TEST(_num, G_STRINGIFY(num)); \ + } \ + G_STMT_END + + _NM_STRDUP_INT_TEST_TYPED(char, 0); + _NM_STRDUP_INT_TEST_TYPED(char, 1); + _NM_STRDUP_INT_TEST_TYPED(guint8, 0); + _NM_STRDUP_INT_TEST_TYPED(gint8, 25); + _NM_STRDUP_INT_TEST_TYPED(char, 47); + _NM_STRDUP_INT_TEST_TYPED(short, 47); + _NM_STRDUP_INT_TEST_TYPED(int, 47); + _NM_STRDUP_INT_TEST_TYPED(long, 47); + _NM_STRDUP_INT_TEST_TYPED(unsigned char, 47); + _NM_STRDUP_INT_TEST_TYPED(unsigned short, 47); + _NM_STRDUP_INT_TEST_TYPED(unsigned, 47); + _NM_STRDUP_INT_TEST_TYPED(unsigned long, 47); + _NM_STRDUP_INT_TEST_TYPED(gint64, 9223372036854775807); + _NM_STRDUP_INT_TEST_TYPED(gint64, -9223372036854775807); + _NM_STRDUP_INT_TEST_TYPED(guint64, 0); + _NM_STRDUP_INT_TEST_TYPED(guint64, 9223372036854775807); + + _NM_STRDUP_INT_TEST(TEST_NM_STRDUP_ENUM_m1, "-1"); + _NM_STRDUP_INT_TEST(TEST_NM_STRDUP_ENUM_3, "3"); +} + +/*****************************************************************************/ + +static void +test_nm_strndup_a(void) +{ + int run; + + for (run = 0; run < 20; run++) { + gs_free char *input = NULL; + char ch; + gsize i, l; + + input = g_strnfill(nmtst_get_rand_uint32() % 20, 'x'); + + for (i = 0; input[i]; i++) { + while ((ch = ((char) nmtst_get_rand_uint32())) == '\0') { + /* repeat. */ + } + input[i] = ch; + } + + { + gs_free char *dup_free = NULL; + const char * dup; + + l = strlen(input) + 1; + dup = nm_strndup_a(10, input, l - 1, &dup_free); + g_assert_cmpstr(dup, ==, input); + if (strlen(dup) < 10) + g_assert(!dup_free); + else + g_assert(dup == dup_free); + } + + { + gs_free char *dup_free = NULL; + const char * dup; + + l = nmtst_get_rand_uint32() % 23; + dup = nm_strndup_a(10, input, l, &dup_free); + g_assert(strncmp(dup, input, l) == 0); + g_assert(strlen(dup) <= l); + if (l < 10) + g_assert(!dup_free); + else + g_assert(dup == dup_free); + if (strlen(input) < l) + g_assert(nm_utils_memeqzero(&dup[strlen(input)], l - strlen(input))); + } + } +} + +/*****************************************************************************/ + +static void +test_nm_ip4_addr_is_localhost(void) +{ + g_assert(nm_ip4_addr_is_localhost(nmtst_inet4_from_string("127.0.0.0"))); + g_assert(nm_ip4_addr_is_localhost(nmtst_inet4_from_string("127.0.0.1"))); + g_assert(nm_ip4_addr_is_localhost(nmtst_inet4_from_string("127.5.0.1"))); + g_assert(!nm_ip4_addr_is_localhost(nmtst_inet4_from_string("126.5.0.1"))); + g_assert(!nm_ip4_addr_is_localhost(nmtst_inet4_from_string("128.5.0.1"))); + g_assert(!nm_ip4_addr_is_localhost(nmtst_inet4_from_string("129.5.0.1"))); +} + +/*****************************************************************************/ + +static void +test_unaligned(void) +{ + int shift; + + for (shift = 0; shift <= 32; shift++) { + guint8 buf[100] = {}; + guint8 val = 0; + + while (val == 0) + val = nmtst_get_rand_uint32() % 256; + + buf[shift] = val; + + g_assert_cmpint(unaligned_read_le64(&buf[shift]), ==, (guint64) val); + g_assert_cmpint(unaligned_read_be64(&buf[shift]), ==, ((guint64) val) << 56); + g_assert_cmpint(unaligned_read_ne64(&buf[shift]), !=, 0); + + g_assert_cmpint(unaligned_read_le32(&buf[shift]), ==, (guint32) val); + g_assert_cmpint(unaligned_read_be32(&buf[shift]), ==, ((guint32) val) << 24); + g_assert_cmpint(unaligned_read_ne32(&buf[shift]), !=, 0); + + g_assert_cmpint(unaligned_read_le16(&buf[shift]), ==, (guint16) val); + g_assert_cmpint(unaligned_read_be16(&buf[shift]), ==, ((guint16) val) << 8); + g_assert_cmpint(unaligned_read_ne16(&buf[shift]), !=, 0); + } +} + +/*****************************************************************************/ + +static void +_strv_cmp_fuzz_input(const char *const * in, + gssize l, + const char *** out_strv_free_shallow, + char *** out_strv_free_deep, + const char *const **out_s1, + const char *const **out_s2) +{ + const char **strv; + gsize i; + + /* Fuzz the input argument. It will return two output arrays that are semantically + * equal the input. */ + + if (nmtst_get_rand_bool()) { + char **ss; + + if (l < 0) + ss = g_strdupv((char **) in); + else if (l == 0) { + ss = nmtst_get_rand_bool() ? NULL : g_new0(char *, 1); + } else { + ss = nm_memdup(in, sizeof(const char *) * l); + for (i = 0; i < (gsize) l; i++) + ss[i] = g_strdup(ss[i]); + } + strv = (const char **) ss; + *out_strv_free_deep = ss; + } else { + if (l < 0) { + strv = in ? nm_memdup(in, sizeof(const char *) * (NM_PTRARRAY_LEN(in) + 1)) : NULL; + } else if (l == 0) { + strv = nmtst_get_rand_bool() ? NULL : g_new0(const char *, 1); + } else + strv = nm_memdup(in, sizeof(const char *) * l); + *out_strv_free_shallow = strv; + } + + *out_s1 = in; + *out_s2 = strv; + + if (nmtst_get_rand_bool()) { + /* randomly swap the original and the clone. That means, out_s1 is either + * the input argument (as-is) or the sementically equal clone. */ + NM_SWAP(out_s1, out_s2); + } + if (nmtst_get_rand_bool()) { + /* randomly make s1 and s2 the same. This is for testing that + * comparing two identical pointers yields the same result. */ + *out_s2 = *out_s1; + } +} + +static void +_strv_cmp_free_deep(char **strv, gssize len) +{ + gssize i; + + if (strv) { + if (len < 0) + g_strfreev(strv); + else { + for (i = 0; i < len; i++) + g_free(strv[i]); + g_free(strv); + } + } +} + +static void +test_strv_cmp(void) +{ + const char *const strv0[1] = {}; + const char *const strv1[2] = { + "", + }; + +#define _STRV_CMP(a1, l1, a2, l2, equal) \ + G_STMT_START \ + { \ + gssize _l1 = (l1); \ + gssize _l2 = (l2); \ + const char *const * _a1; \ + const char *const * _a2; \ + const char *const * _a1x; \ + const char *const * _a2x; \ + char ** _a1_free_deep = NULL; \ + char ** _a2_free_deep = NULL; \ + gs_free const char **_a1_free_shallow = NULL; \ + gs_free const char **_a2_free_shallow = NULL; \ + int _c1, _c2; \ + \ + _strv_cmp_fuzz_input((a1), _l1, &_a1_free_shallow, &_a1_free_deep, &_a1, &_a1x); \ + _strv_cmp_fuzz_input((a2), _l2, &_a2_free_shallow, &_a2_free_deep, &_a2, &_a2x); \ + \ + _c1 = nm_utils_strv_cmp_n(_a1, _l1, _a2, _l2); \ + _c2 = nm_utils_strv_cmp_n(_a2, _l2, _a1, _l1); \ + if (equal) { \ + g_assert_cmpint(_c1, ==, 0); \ + g_assert_cmpint(_c2, ==, 0); \ + } else { \ + g_assert_cmpint(_c1, ==, -1); \ + g_assert_cmpint(_c2, ==, 1); \ + } \ + \ + /* Compare with self. _strv_cmp_fuzz_input() randomly swapped the arguments (_a1 and _a1x). + * Either way, the arrays must compare equal to their semantically equal alternative. */ \ + g_assert_cmpint(nm_utils_strv_cmp_n(_a1, _l1, _a1x, _l1), ==, 0); \ + g_assert_cmpint(nm_utils_strv_cmp_n(_a2, _l2, _a2x, _l2), ==, 0); \ + \ + _strv_cmp_free_deep(_a1_free_deep, _l1); \ + _strv_cmp_free_deep(_a2_free_deep, _l2); \ + } \ + G_STMT_END + + _STRV_CMP(NULL, -1, NULL, -1, TRUE); + + _STRV_CMP(NULL, -1, NULL, 0, FALSE); + _STRV_CMP(NULL, -1, strv0, 0, FALSE); + _STRV_CMP(NULL, -1, strv0, -1, FALSE); + + _STRV_CMP(NULL, 0, NULL, 0, TRUE); + _STRV_CMP(NULL, 0, strv0, 0, TRUE); + _STRV_CMP(NULL, 0, strv0, -1, TRUE); + _STRV_CMP(strv0, 0, strv0, 0, TRUE); + _STRV_CMP(strv0, 0, strv0, -1, TRUE); + _STRV_CMP(strv0, -1, strv0, -1, TRUE); + + _STRV_CMP(NULL, 0, strv1, -1, FALSE); + _STRV_CMP(NULL, 0, strv1, 1, FALSE); + _STRV_CMP(strv0, 0, strv1, -1, FALSE); + _STRV_CMP(strv0, 0, strv1, 1, FALSE); + _STRV_CMP(strv0, -1, strv1, -1, FALSE); + _STRV_CMP(strv0, -1, strv1, 1, FALSE); + + _STRV_CMP(strv1, -1, strv1, 1, TRUE); + _STRV_CMP(strv1, 1, strv1, 1, TRUE); +} + +/*****************************************************************************/ + +static void +_do_strstrip_avoid_copy(const char *str) +{ + gs_free char *str1 = g_strdup(str); + gs_free char *str2 = g_strdup(str); + gs_free char *str3 = NULL; + gs_free char *str4 = NULL; + const char * s3; + const char * s4; + + if (str1) + g_strstrip(str1); + + nm_strstrip(str2); + + g_assert_cmpstr(str1, ==, str2); + + s3 = nm_strstrip_avoid_copy(str, &str3); + g_assert_cmpstr(str1, ==, s3); + + s4 = nm_strstrip_avoid_copy_a(10, str, &str4); + g_assert_cmpstr(str1, ==, s4); + g_assert(!str == !s4); + g_assert(!s4 || strlen(s4) <= strlen(str)); + if (s4 && s4 == &str[strlen(str) - strlen(s4)]) { + g_assert(!str4); + g_assert(s3 == s4); + } else if (s4 && strlen(s4) >= 10) { + g_assert(str4); + g_assert(s4 == str4); + } else + g_assert(!str4); + + if (!nm_streq0(str1, str)) + _do_strstrip_avoid_copy(str1); +} + +static void +test_strstrip_avoid_copy(void) +{ + _do_strstrip_avoid_copy(NULL); + _do_strstrip_avoid_copy(""); + _do_strstrip_avoid_copy(" "); + _do_strstrip_avoid_copy(" a "); + _do_strstrip_avoid_copy(" 012345678 "); + _do_strstrip_avoid_copy(" 0123456789 "); + _do_strstrip_avoid_copy(" 01234567890 "); + _do_strstrip_avoid_copy(" 012345678901 "); +} + +/*****************************************************************************/ + +static void +test_nm_utils_bin2hexstr(void) +{ + int n_run; + + for (n_run = 0; n_run < 500; n_run++) { + guint8 buf[100]; + guint8 buf2[G_N_ELEMENTS(buf) + 1]; + gsize len = nmtst_get_rand_uint32() % (G_N_ELEMENTS(buf) + 1); + char strbuf1[G_N_ELEMENTS(buf) * 3]; + gboolean allocate = nmtst_get_rand_bool(); + char delimiter = nmtst_get_rand_bool() ? ':' : '\0'; + gboolean upper_case = nmtst_get_rand_bool(); + gboolean hexdigit_pairs_mangled; + gsize expected_strlen; + char * str_hex; + gsize required_len; + gboolean outlen_set; + gsize outlen; + guint8 * bin2; + guint i, j; + + nmtst_rand_buf(NULL, buf, len); + + if (len == 0) + expected_strlen = 0; + else if (delimiter != '\0') + expected_strlen = (len * 3u) - 1; + else + expected_strlen = len * 2u; + + g_assert_cmpint(expected_strlen, <, G_N_ELEMENTS(strbuf1)); + + str_hex = + nm_utils_bin2hexstr_full(buf, len, delimiter, upper_case, !allocate ? strbuf1 : NULL); + + g_assert(str_hex); + if (!allocate) + g_assert(str_hex == strbuf1); + g_assert_cmpint(strlen(str_hex), ==, expected_strlen); + + g_assert(NM_STRCHAR_ALL( + str_hex, + ch, + (ch >= '0' && ch <= '9') || ch == delimiter + || (upper_case ? (ch >= 'A' && ch <= 'F') : (ch >= 'a' && ch <= 'f')))); + + hexdigit_pairs_mangled = FALSE; + if (delimiter && len > 1 && nmtst_get_rand_bool()) { + /* randomly convert "0?" sequences to single digits, so we can get hexdigit_pairs_required + * parameter. */ + g_assert(strlen(str_hex) >= 5); + g_assert(str_hex[2] == delimiter); + i = 0; + j = 0; + for (;;) { + g_assert(g_ascii_isxdigit(str_hex[i])); + g_assert(g_ascii_isxdigit(str_hex[i + 1])); + g_assert(NM_IN_SET(str_hex[i + 2], delimiter, '\0')); + if (str_hex[i] == '0' && nmtst_get_rand_bool()) { + i++; + str_hex[j++] = str_hex[i++]; + hexdigit_pairs_mangled = TRUE; + } else { + str_hex[j++] = str_hex[i++]; + str_hex[j++] = str_hex[i++]; + } + if (str_hex[i] == '\0') { + str_hex[j] = '\0'; + break; + } + g_assert(str_hex[i] == delimiter); + str_hex[j++] = str_hex[i++]; + } + } + + required_len = nmtst_get_rand_bool() ? len : 0u; + + outlen_set = required_len == 0 || nmtst_get_rand_bool(); + + memset(buf2, 0, sizeof(buf2)); + + bin2 = nm_utils_hexstr2bin_full(str_hex, + nmtst_get_rand_bool(), + delimiter != '\0' && nmtst_get_rand_bool(), + !hexdigit_pairs_mangled && nmtst_get_rand_bool(), + delimiter != '\0' + ? nmtst_rand_select((const char *) ":", ":-") + : nmtst_rand_select((const char *) ":", ":-", "", NULL), + required_len, + buf2, + len, + outlen_set ? &outlen : NULL); + if (len > 0) { + g_assert(bin2); + g_assert(bin2 == buf2); + } else + g_assert(!bin2); + + if (outlen_set) + g_assert_cmpint(outlen, ==, len); + + g_assert_cmpmem(buf, len, buf2, len); + + g_assert(buf2[len] == '\0'); + + if (hexdigit_pairs_mangled) { + /* we mangled the hexstr to contain single digits. Trying to parse with + * hexdigit_pairs_required must now fail. */ + bin2 = nm_utils_hexstr2bin_full( + str_hex, + nmtst_get_rand_bool(), + delimiter != '\0' && nmtst_get_rand_bool(), + TRUE, + delimiter != '\0' ? nmtst_rand_select((const char *) ":", ":-") + : nmtst_rand_select((const char *) ":", ":-", "", NULL), + required_len, + buf2, + len, + outlen_set ? &outlen : NULL); + g_assert(!bin2); + } + + if (allocate) + g_free(str_hex); + } +} + +/*****************************************************************************/ + +static void +test_nm_ref_string(void) +{ + nm_auto_ref_string NMRefString *s1 = NULL; + NMRefString * s2; + + s1 = nm_ref_string_new("hallo"); + g_assert(s1); + g_assert_cmpstr(s1->str, ==, "hallo"); + g_assert_cmpint(s1->len, ==, strlen("hallo")); + + s2 = nm_ref_string_new("hallo"); + g_assert(s2 == s1); + nm_ref_string_unref(s2); + + s2 = nm_ref_string_new(NULL); + g_assert(!s2); + nm_ref_string_unref(s2); + +#define STR_WITH_NUL "hallo\0test\0" + s2 = nm_ref_string_new_len(STR_WITH_NUL, NM_STRLEN(STR_WITH_NUL)); + g_assert(s2); + g_assert_cmpstr(s2->str, ==, "hallo"); + g_assert_cmpint(s2->len, ==, NM_STRLEN(STR_WITH_NUL)); + g_assert_cmpint(s2->len, >, strlen(s2->str)); + g_assert_cmpmem(s2->str, s2->len, STR_WITH_NUL, NM_STRLEN(STR_WITH_NUL)); + g_assert(s2->str[s2->len] == '\0'); + nm_ref_string_unref(s2); +} + +/*****************************************************************************/ + +static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE( + _do_string_table_lookup, + int, + { ; }, + { return -1; }, + {"0", 0}, + {"1", 1}, + {"2", 2}, + {"3", 3}, ); + +static void +test_string_table_lookup(void) +{ + const char *const args[] = { + NULL, + "0", + "1", + "2", + "3", + "x", + }; + int i; + + for (i = 0; i < G_N_ELEMENTS(args); i++) { + const char *needle = args[i]; + const int val2 = _nm_utils_ascii_str_to_int64(needle, 10, 0, 100, -1); + int val; + + val = _do_string_table_lookup(needle); + g_assert_cmpint(val, ==, val2); + } +} + +/*****************************************************************************/ + +static void +test_nm_utils_get_next_realloc_size(void) +{ + static const struct { + gsize requested; + gsize reserved_true; + gsize reserved_false; + } test_data[] = { + {0, 8, 8}, + {1, 8, 8}, + {8, 8, 8}, + {9, 16, 16}, + {16, 16, 16}, + {17, 32, 32}, + {32, 32, 32}, + {33, 40, 40}, + {40, 40, 40}, + {41, 104, 104}, + {104, 104, 104}, + {105, 232, 232}, + {232, 232, 232}, + {233, 488, 488}, + {488, 488, 488}, + {489, 1000, 1000}, + {1000, 1000, 1000}, + {1001, 2024, 2024}, + {2024, 2024, 2024}, + {2025, 4072, 4072}, + {4072, 4072, 4072}, + {4073, 8168, 8168}, + {8168, 8168, 8168}, + {8169, 12264, 16360}, + {12263, 12264, 16360}, + {12264, 12264, 16360}, + {12265, 16360, 16360}, + {16360, 16360, 16360}, + {16361, 20456, 32744}, + {20456, 20456, 32744}, + {20457, 24552, 32744}, + {24552, 24552, 32744}, + {24553, 28648, 32744}, + {28648, 28648, 32744}, + {28649, 32744, 32744}, + {32744, 32744, 32744}, + {32745, 36840, 65512}, + {36840, 36840, 65512}, + {G_MAXSIZE - 0x1000u, G_MAXSIZE, G_MAXSIZE}, + {G_MAXSIZE - 25u, G_MAXSIZE, G_MAXSIZE}, + {G_MAXSIZE - 24u, G_MAXSIZE, G_MAXSIZE}, + {G_MAXSIZE - 1u, G_MAXSIZE, G_MAXSIZE}, + {G_MAXSIZE, G_MAXSIZE, G_MAXSIZE}, + {NM_UTILS_GET_NEXT_REALLOC_SIZE_32, + NM_UTILS_GET_NEXT_REALLOC_SIZE_32, + NM_UTILS_GET_NEXT_REALLOC_SIZE_32}, + {NM_UTILS_GET_NEXT_REALLOC_SIZE_40, + NM_UTILS_GET_NEXT_REALLOC_SIZE_40, + NM_UTILS_GET_NEXT_REALLOC_SIZE_40}, + {NM_UTILS_GET_NEXT_REALLOC_SIZE_104, + NM_UTILS_GET_NEXT_REALLOC_SIZE_104, + NM_UTILS_GET_NEXT_REALLOC_SIZE_104}, + {NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, + NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, + NM_UTILS_GET_NEXT_REALLOC_SIZE_1000}, + }; + guint i; + + G_STATIC_ASSERT_EXPR(NM_UTILS_GET_NEXT_REALLOC_SIZE_104 == 104u); + G_STATIC_ASSERT_EXPR(NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 == 1000u); + + for (i = 0; i < G_N_ELEMENTS(test_data) + 5000u; i++) { + gsize requested0; + + if (i < G_N_ELEMENTS(test_data)) + requested0 = test_data[i].requested; + else { + /* find some interesting random values for testing. */ + switch (nmtst_get_rand_uint32() % 5) { + case 0: + requested0 = nmtst_get_rand_size(); + break; + case 1: + /* values close to G_MAXSIZE. */ + requested0 = G_MAXSIZE - (nmtst_get_rand_uint32() % 12000u); + break; + case 2: + /* values around G_MAXSIZE/2. */ + requested0 = (G_MAXSIZE / 2u) + 6000u - (nmtst_get_rand_uint32() % 12000u); + break; + case 3: + /* values around powers of 2. */ + requested0 = (((gsize) 1) << (nmtst_get_rand_uint32() % (sizeof(gsize) * 8u))) + + 6000u - (nmtst_get_rand_uint32() % 12000u); + break; + case 4: + /* values around 4k borders. */ + requested0 = (nmtst_get_rand_size() & ~((gsize) 0xFFFu)) + 30u + - (nmtst_get_rand_uint32() % 60u); + break; + default: + g_assert_not_reached(); + } + } + + { + const gsize requested = requested0; + gsize reserved_true; + gsize reserved_false; + bool truncated_true = FALSE; + bool truncated_false = FALSE; + + if (sizeof(gsize) > 4 && requested > SIZE_MAX / 2u - 24u) { + reserved_false = G_MAXSSIZE; + truncated_false = TRUE; + } else + reserved_false = nm_utils_get_next_realloc_size(FALSE, requested); + + if (sizeof(gsize) > 4 && requested > SIZE_MAX - 0x1000u - 24u) { + reserved_true = G_MAXSSIZE; + truncated_true = TRUE; + } else + reserved_true = nm_utils_get_next_realloc_size(TRUE, requested); + + g_assert_cmpuint(reserved_true, >, 0); + g_assert_cmpuint(reserved_false, >, 0); + if (!truncated_true) + g_assert_cmpuint(reserved_true, >=, requested); + if (!truncated_false) + g_assert_cmpuint(reserved_false, >=, requested); + if (!truncated_true && !truncated_false) + g_assert_cmpuint(reserved_false, >=, reserved_true); + + if (i < G_N_ELEMENTS(test_data)) { + if (!truncated_true) + g_assert_cmpuint(reserved_true, ==, test_data[i].reserved_true); + if (!truncated_false) + g_assert_cmpuint(reserved_false, ==, test_data[i].reserved_false); + } + + /* reserved_false is generally the next power of two - 24. */ + if (reserved_false == G_MAXSIZE) + g_assert_cmpuint(requested, >, G_MAXSIZE / 2u - 24u); + else if (!reserved_false) { + g_assert_cmpuint(reserved_false, <=, G_MAXSIZE - 24u); + if (reserved_false >= 40) { + const gsize _pow2 = reserved_false + 24u; + + /* reserved_false must always be a power of two minus 24. */ + g_assert_cmpuint(_pow2, >=, 64u); + g_assert_cmpuint(_pow2, >, requested); + g_assert(nm_utils_is_power_of_two(_pow2)); + + /* but _pow2/2 must also be smaller than what we requested. */ + g_assert_cmpuint(_pow2 / 2u - 24u, <, requested); + } else { + /* smaller values are hard-coded. */ + } + } + + /* reserved_true is generally the next 4k border - 24. */ + if (reserved_true == G_MAXSIZE) + g_assert_cmpuint(requested, >, G_MAXSIZE - 0x1000u - 24u); + else if (!truncated_true) { + g_assert_cmpuint(reserved_true, <=, G_MAXSIZE - 24u); + if (reserved_true > 8168u) { + const gsize page_border = reserved_true + 24u; + + /* reserved_true must always be aligned to 4k (minus 24). */ + g_assert_cmpuint(page_border % 0x1000u, ==, 0); + if (requested > 0x1000u - 24u) { + /* page_border not be more than 4k above requested. */ + g_assert_cmpuint(page_border, >=, 0x1000u - 24u); + g_assert_cmpuint(page_border - 0x1000u - 24u, <, requested); + } + } else { + /* for smaller sizes, reserved_true and reserved_false are the same. */ + g_assert_cmpuint(reserved_true, ==, reserved_false); + } + } + } + } +} + +/*****************************************************************************/ + +static void +test_nm_str_buf(void) +{ + guint i_run; + + for (i_run = 0; TRUE; i_run++) { + nm_auto_str_buf NMStrBuf strbuf = {}; + nm_auto_free_gstring GString *gstr = NULL; + int i, j, k; + int c; + + nm_str_buf_init(&strbuf, nmtst_get_rand_uint32() % 200u + 1u, nmtst_get_rand_bool()); + + if (i_run < 1000) { + c = nmtst_get_rand_word_length(NULL); + for (i = 0; i < c; i++) + nm_str_buf_append_c(&strbuf, '0' + (i % 10)); + gstr = g_string_new(nm_str_buf_get_str(&strbuf)); + j = nmtst_get_rand_uint32() % (strbuf.len + 1); + k = nmtst_get_rand_uint32() % (strbuf.len - j + 2) - 1; + + nm_str_buf_erase(&strbuf, j, k, nmtst_get_rand_bool()); + g_string_erase(gstr, j, k); + g_assert_cmpstr(gstr->str, ==, nm_str_buf_get_str(&strbuf)); + } else + return; + } +} + +/*****************************************************************************/ + +static void +test_nm_utils_parse_next_line(void) +{ + const char *data; + const char *data0; + gsize data_len; + const char *line_start; + gsize line_len; + int i_run; + gsize j, k; + + data = NULL; + data_len = 0; + g_assert(!nm_utils_parse_next_line(&data, &data_len, &line_start, &line_len)); + + for (i_run = 0; i_run < 1000; i_run++) { + gs_unref_ptrarray GPtrArray *strv = g_ptr_array_new_with_free_func(g_free); + gs_unref_ptrarray GPtrArray *strv2 = g_ptr_array_new_with_free_func(g_free); + gsize strv_len = nmtst_get_rand_word_length(NULL); + nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT(0, nmtst_get_rand_bool()); + + /* create a list of random words. */ + for (j = 0; j < strv_len; j++) { + gsize w_len = nmtst_get_rand_word_length(NULL); + NMStrBuf w_buf = + NM_STR_BUF_INIT(nmtst_get_rand_uint32() % (w_len + 1), nmtst_get_rand_bool()); + + for (k = 0; k < w_len; k++) + nm_str_buf_append_c(&w_buf, '0' + (k % 10)); + nm_str_buf_maybe_expand(&w_buf, 1, TRUE); + g_ptr_array_add(strv, nm_str_buf_finalize(&w_buf, NULL)); + } + + /* join the list of random words with (random) line delimiters + * ("\0", "\n", "\r" or EOF). */ + for (j = 0; j < strv_len; j++) { + nm_str_buf_append(&strbuf, strv->pdata[j]); +again: + switch (nmtst_get_rand_uint32() % 5) { + case 0: + nm_str_buf_append_c(&strbuf, '\0'); + break; + case 1: + if (strbuf.len > 0 + && (nm_str_buf_get_str_unsafe(&strbuf))[strbuf.len - 1] == '\r') { + /* the previous line was empty and terminated by "\r". We + * must not join with "\n". Retry. */ + goto again; + } + nm_str_buf_append_c(&strbuf, '\n'); + break; + case 2: + nm_str_buf_append_c(&strbuf, '\r'); + break; + case 3: + nm_str_buf_append(&strbuf, "\r\n"); + break; + case 4: + /* the last word randomly is delimited or not, but not if the last + * word is "". */ + if (j + 1 < strv_len) { + /* it's not the last word. Retry. */ + goto again; + } + g_assert(j == strv_len - 1); + if (((const char *) strv->pdata[j])[0] == '\0') { + /* if the last word was "", we need a delimiter (to parse it back). + * Retry. */ + goto again; + } + /* The final delimiter gets omitted. It's EOF. */ + break; + } + } + + data0 = nm_str_buf_get_str_unsafe(&strbuf); + if (!data0 && nmtst_get_rand_bool()) { + nm_str_buf_maybe_expand(&strbuf, 1, TRUE); + data0 = nm_str_buf_get_str_unsafe(&strbuf); + g_assert(data0); + } + data_len = strbuf.len; + g_assert((data_len > 0 && data0) || data_len == 0); + data = data0; + while (nm_utils_parse_next_line(&data, &data_len, &line_start, &line_len)) { + g_assert(line_start); + g_assert(line_start >= data0); + g_assert(line_start < &data0[strbuf.len]); + g_assert(!memchr(line_start, '\0', line_len)); + g_ptr_array_add(strv2, g_strndup(line_start, line_len)); + } + g_assert(data_len == 0); + if (data0) + g_assert(data == &data0[strbuf.len]); + else + g_assert(!data); + + g_assert(nm_utils_strv_cmp_n((const char *const *) strv->pdata, + strv->len, + (const char *const *) strv2->pdata, + strv2->len) + == 0); + } +} + +/*****************************************************************************/ + +static void +test_in_strset_ascii_case(void) +{ + const char *x; + + x = NULL; + g_assert(NM_IN_STRSET_ASCII_CASE(x, NULL)); + g_assert(NM_IN_STRSET_ASCII_CASE(x, NULL, "b")); + g_assert(!NM_IN_STRSET_ASCII_CASE(x, "b")); + + x = "b"; + g_assert(NM_IN_STRSET(x, "b")); + g_assert(NM_IN_STRSET_ASCII_CASE(x, "b")); + g_assert(!NM_IN_STRSET(x, "B")); + g_assert(NM_IN_STRSET_ASCII_CASE(x, "B")); +} + +/*****************************************************************************/ + +static void +test_is_specific_hostname(void) +{ + g_assert(!nm_utils_is_specific_hostname(NULL)); + g_assert(!nm_utils_is_specific_hostname("")); + g_assert(!nm_utils_is_specific_hostname("(none)")); + g_assert(nm_utils_is_specific_hostname("(NONE)")); + + g_assert(!nm_utils_is_specific_hostname("localhost")); + g_assert(!nm_utils_is_specific_hostname("lOcalHost")); + g_assert(!nm_utils_is_specific_hostname("LOCALHOST")); + + g_assert(!nm_utils_is_specific_hostname("LOCALHOST.localdomain")); + + g_assert(nm_utils_is_specific_hostname("xlocalhost")); + g_assert(nm_utils_is_specific_hostname("lOcalHxost")); + g_assert(nm_utils_is_specific_hostname("LOCALxHOST")); + + g_assert(!nm_utils_is_specific_hostname("foo.LOCALHOST")); + g_assert(!nm_utils_is_specific_hostname("foo.LOCALHOsT6.")); + g_assert(!nm_utils_is_specific_hostname("foo.LOCALHOsT6.localdomain6")); + g_assert(!nm_utils_is_specific_hostname(".LOCALHOsT6.localdomain6")); + g_assert(!nm_utils_is_specific_hostname("LOCALHOsT6.localdomain6")); + g_assert(!nm_utils_is_specific_hostname("LOCALHOsT6.localdomain6.")); + g_assert(nm_utils_is_specific_hostname("LOCALHOsT6.localdomain.")); + + g_assert(nm_utils_is_specific_hostname(" ")); +} + +/*****************************************************************************/ + +static void +test_strv_dup_packed(void) +{ + gs_unref_ptrarray GPtrArray *src = NULL; + int i_run; + + src = g_ptr_array_new_with_free_func(g_free); + + for (i_run = 0; i_run < 500; i_run++) { + const int strv_len = nmtst_get_rand_word_length(NULL); + gs_free const char **strv_cpy = NULL; + const char *const * strv_src; + int i, j; + + g_ptr_array_set_size(src, 0); + for (i = 0; i < strv_len; i++) { + const int word_len = nmtst_get_rand_word_length(NULL); + NMStrBuf sbuf = NM_STR_BUF_INIT(0, nmtst_get_rand_bool()); + + for (j = 0; j < word_len; j++) + nm_str_buf_append_c(&sbuf, 'a' + (nmtst_get_rand_uint32() % 20)); + + g_ptr_array_add(src, nm_str_buf_finalize(&sbuf, NULL) ?: g_new0(char, 1)); + } + g_ptr_array_add(src, NULL); + + strv_src = (const char *const *) src->pdata; + g_assert(strv_src); + g_assert(NM_PTRARRAY_LEN(strv_src) == strv_len); + + strv_cpy = + nm_utils_strv_dup_packed(strv_src, + nmtst_get_rand_bool() ? (gssize) strv_len : (gssize) -1); + if (strv_len == 0) + g_assert(!strv_cpy); + else + g_assert(strv_cpy); + g_assert(NM_PTRARRAY_LEN(strv_cpy) == strv_len); + if (strv_cpy) + g_assert(nm_utils_strv_equal(strv_cpy, strv_src)); + } +} + +/*****************************************************************************/ + +static int +_hash_func_cmp_direct(gconstpointer a, gconstpointer b, gpointer user_data) +{ + NM_CMP_DIRECT(GPOINTER_TO_INT(a), GPOINTER_TO_INT(b)); + return 0; +} + +static void +test_utils_hashtable_cmp(void) +{ + static struct { + int val_i; + const char *val_s; + } vals[] = { + { + 0, + "0", + }, + { + 1, + "1", + }, + { + 2, + "2", + }, + { + 3, + "3", + }, + { + 4, + "4", + }, + { + 5, + "5", + }, + { + 6, + "6", + }, + { + 7, + "7", + }, + { + 8, + "8", + }, + { + 9, + "9", + }, + { + 0, + "a", + }, + { + 1, + "a", + }, + { + 2, + "a", + }, + { + 3, + "a", + }, + { + 4, + "a", + }, + { + 5, + "a", + }, + { + 0, + "0", + }, + { + 0, + "1", + }, + { + 0, + "2", + }, + { + 0, + "3", + }, + { + 0, + "4", + }, + { + 0, + "5", + }, + }; + guint test_run; + int is_num_key; + + for (test_run = 0; test_run < 30; test_run++) { + for (is_num_key = 0; is_num_key < 2; is_num_key++) { + GHashFunc func_key_hash = is_num_key ? nm_direct_hash : nm_str_hash; + GEqualFunc func_key_equal = is_num_key ? g_direct_equal : g_str_equal; + GCompareDataFunc func_key_cmp = + is_num_key ? _hash_func_cmp_direct : (GCompareDataFunc) nm_strcmp_with_data; + GCompareDataFunc func_val_cmp = + !is_num_key ? _hash_func_cmp_direct : (GCompareDataFunc) nm_strcmp_with_data; + gs_unref_hashtable GHashTable *h1 = NULL; + gs_unref_hashtable GHashTable *h2 = NULL; + gboolean has_same_keys; + guint i, n; + + h1 = g_hash_table_new(func_key_hash, func_key_equal); + h2 = g_hash_table_new(func_key_hash, func_key_equal); + + n = nmtst_get_rand_word_length(NULL); + for (i = 0; i < n; i++) { + typeof(vals[0]) *v = &vals[nmtst_get_rand_uint32() % G_N_ELEMENTS(vals)]; + gconstpointer v_key = is_num_key ? GINT_TO_POINTER(v->val_i) : v->val_s; + gconstpointer v_val = !is_num_key ? GINT_TO_POINTER(v->val_i) : v->val_s; + + g_hash_table_insert(h1, (gpointer) v_key, (gpointer) v_val); + g_hash_table_insert(h2, (gpointer) v_key, (gpointer) v_val); + } + + g_assert(nm_utils_hashtable_same_keys(h1, h2)); + g_assert(nm_utils_hashtable_cmp_equal(h1, h2, NULL, NULL)); + g_assert(nm_utils_hashtable_cmp_equal(h1, h2, func_val_cmp, NULL)); + g_assert(nm_utils_hashtable_cmp(h1, h2, FALSE, func_key_cmp, NULL, NULL) == 0); + g_assert(nm_utils_hashtable_cmp(h1, h2, TRUE, func_key_cmp, NULL, NULL) == 0); + g_assert(nm_utils_hashtable_cmp(h1, h2, FALSE, func_key_cmp, func_val_cmp, NULL) == 0); + g_assert(nm_utils_hashtable_cmp(h1, h2, TRUE, func_key_cmp, func_val_cmp, NULL) == 0); + + n = nmtst_get_rand_word_length(NULL) + 1; + has_same_keys = TRUE; + for (i = 0; i < n; i++) { +again: +{ + typeof(vals[0]) *v = &vals[nmtst_get_rand_uint32() % G_N_ELEMENTS(vals)]; + gconstpointer v_key = is_num_key ? GINT_TO_POINTER(v->val_i) : v->val_s; + gconstpointer v_val = !is_num_key ? GINT_TO_POINTER(v->val_i) : v->val_s; + gpointer v_key2; + gpointer v_val2; + + if (g_hash_table_lookup_extended(h1, v_key, &v_key2, &v_val2)) { + g_assert(func_key_cmp(v_key, v_key2, NULL) == 0); + if (func_val_cmp(v_val, v_val2, NULL) == 0) + goto again; + } else + has_same_keys = FALSE; + + g_hash_table_insert(h2, (gpointer) v_key, (gpointer) v_val); +} + } + + if (has_same_keys) { + g_assert(nm_utils_hashtable_same_keys(h1, h2)); + g_assert(nm_utils_hashtable_cmp_equal(h1, h2, NULL, NULL)); + g_assert(nm_utils_hashtable_cmp(h1, h2, FALSE, func_key_cmp, NULL, NULL) == 0); + g_assert(nm_utils_hashtable_cmp(h1, h2, TRUE, func_key_cmp, NULL, NULL) == 0); + } else { + g_assert(!nm_utils_hashtable_same_keys(h1, h2)); + g_assert(!nm_utils_hashtable_cmp_equal(h1, h2, NULL, NULL)); + g_assert(nm_utils_hashtable_cmp(h1, h2, FALSE, func_key_cmp, NULL, NULL) != 0); + g_assert(nm_utils_hashtable_cmp(h1, h2, TRUE, func_key_cmp, NULL, NULL) != 0); + } + g_assert(!nm_utils_hashtable_cmp_equal(h1, h2, func_val_cmp, NULL)); + g_assert(nm_utils_hashtable_cmp(h1, h2, FALSE, func_key_cmp, func_val_cmp, NULL) != 0); + g_assert(nm_utils_hashtable_cmp(h1, h2, TRUE, func_key_cmp, func_val_cmp, NULL) != 0); + } + } +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init(&argc, &argv, TRUE); + + g_test_add_func("/general/test_gpid", test_gpid); + g_test_add_func("/general/test_monotonic_timestamp", test_monotonic_timestamp); + g_test_add_func("/general/test_nmhash", test_nmhash); + g_test_add_func("/general/test_nm_make_strv", test_make_strv); + g_test_add_func("/general/test_nm_strdup_int", test_nm_strdup_int); + g_test_add_func("/general/test_nm_strndup_a", test_nm_strndup_a); + g_test_add_func("/general/test_nm_ip4_addr_is_localhost", test_nm_ip4_addr_is_localhost); + g_test_add_func("/general/test_unaligned", test_unaligned); + g_test_add_func("/general/test_strv_cmp", test_strv_cmp); + g_test_add_func("/general/test_strstrip_avoid_copy", test_strstrip_avoid_copy); + g_test_add_func("/general/test_nm_utils_bin2hexstr", test_nm_utils_bin2hexstr); + g_test_add_func("/general/test_nm_ref_string", test_nm_ref_string); + g_test_add_func("/general/test_string_table_lookup", test_string_table_lookup); + g_test_add_func("/general/test_nm_utils_get_next_realloc_size", + test_nm_utils_get_next_realloc_size); + g_test_add_func("/general/test_nm_str_buf", test_nm_str_buf); + g_test_add_func("/general/test_nm_utils_parse_next_line", test_nm_utils_parse_next_line); + g_test_add_func("/general/test_in_strset_ascii_case", test_in_strset_ascii_case); + g_test_add_func("/general/test_is_specific_hostname", test_is_specific_hostname); + g_test_add_func("/general/test_strv_dup_packed", test_strv_dup_packed); + g_test_add_func("/general/test_utils_hashtable_cmp", test_utils_hashtable_cmp); + + return g_test_run(); +} diff --git a/src/libnm-log-core/meson.build b/src/libnm-log-core/meson.build new file mode 100644 index 0000000000..2cbf294312 --- /dev/null +++ b/src/libnm-log-core/meson.build @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libnm_log_core = static_library( + 'nm-log-core', + sources: 'nm-logging.c', + include_directories: [ + shared_inc, + src_inc, + top_inc, + ], + dependencies: [ + glib_nm_default_dep, + libsystemd_dep, + ], +) + +libnm_log_core_dep = declare_dependency( + include_directories: shared_inc, + dependencies: [ + libnm_glib_aux_dep_link, + ], + link_with: libnm_log_core, +) diff --git a/src/libnm-log-core/nm-logging.c b/src/libnm-log-core/nm-logging.c new file mode 100644 index 0000000000..7a23344b94 --- /dev/null +++ b/src/libnm-log-core/nm-logging.c @@ -0,0 +1,1035 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2012 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-logging.h" + +#include <dlfcn.h> +#include <syslog.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <strings.h> + +#if SYSTEMD_JOURNAL + #define SD_JOURNAL_SUPPRESS_LOCATION + #include <systemd/sd-journal.h> +#endif + +#include "libnm-glib-aux/nm-logging-base.h" +#include "libnm-glib-aux/nm-time-utils.h" + +/*****************************************************************************/ + +/* Notes about thread-safety: + * + * NetworkManager generally is single-threaded and uses a (GLib) mainloop. + * However, nm-logging is in parts thread-safe. That means: + * + * - functions that configure logging (nm_logging_init(), nm_logging_setup()) and + * most other functions MUST be called only from the main-thread. These functions + * are expected to be called infrequently, so they may or may not use a mutex + * (but the overhead is negligible here). + * + * - functions that do the actual logging logging (nm_log(), nm_logging_enabled()) are + * thread-safe and may be used from multiple threads. + * - When called from the not-main-thread, @mt_require_locking must be set to %TRUE. + * In this case, a Mutex will be used for accessing the global state. + * - When called from the main-thread, they may optionally pass @mt_require_locking %FALSE. + * This avoids extra locking and is in particular interesting for nm_logging_enabled(), + * which is expected to be called frequently and from the main-thread. + * + * Note that the logging macros honor %NM_THREAD_SAFE_ON_MAIN_THREAD define, to automatically + * set @mt_require_locking. That means, by default %NM_THREAD_SAFE_ON_MAIN_THREAD is "1", + * and code that only runs on the main-thread (which is the majority), can get away + * without locking. + */ + +/*****************************************************************************/ + +G_STATIC_ASSERT(LOG_EMERG == 0); +G_STATIC_ASSERT(LOG_ALERT == 1); +G_STATIC_ASSERT(LOG_CRIT == 2); +G_STATIC_ASSERT(LOG_ERR == 3); +G_STATIC_ASSERT(LOG_WARNING == 4); +G_STATIC_ASSERT(LOG_NOTICE == 5); +G_STATIC_ASSERT(LOG_INFO == 6); +G_STATIC_ASSERT(LOG_DEBUG == 7); + +/* We have more then 32 logging domains. Assert that it compiles to a 64 bit sized enum */ +G_STATIC_ASSERT(sizeof(NMLogDomain) >= sizeof(guint64)); + +/* Combined domains */ +#define LOGD_ALL_STRING "ALL" +#define LOGD_DEFAULT_STRING "DEFAULT" +#define LOGD_DHCP_STRING "DHCP" +#define LOGD_IP_STRING "IP" + +/*****************************************************************************/ + +typedef enum { + LOG_BACKEND_GLIB, + LOG_BACKEND_SYSLOG, + LOG_BACKEND_JOURNAL, +} LogBackend; + +typedef struct { + NMLogDomain num; + const char *name; +} LogDesc; + +typedef struct { + char *logging_domains_to_string; +} GlobalMain; + +typedef struct { + NMLogLevel log_level; + bool uses_syslog : 1; + bool init_pre_done : 1; + bool init_done : 1; + bool debug_stderr : 1; + const char *prefix; + const char *syslog_identifier; + + /* before we setup syslog (during start), the backend defaults to GLIB, meaning: + * we use g_log() for all logging. At that point, the application is not yet supposed + * to do any logging and doing so indicates a bug. + * + * Afterwards, the backend is either SYSLOG or JOURNAL. From that point, also + * g_log() is redirected to this backend via a logging handler. */ + LogBackend log_backend; +} Global; + +/*****************************************************************************/ + +G_LOCK_DEFINE_STATIC(log); + +/* This data must only be accessed from the main-thread (and as + * such does not need any lock). */ +static GlobalMain gl_main = {}; + +static union { + /* a union with an immutable and a mutable alias for the Global. + * Since nm-logging must be thread-safe, we must take care at which + * places we only read value ("imm") and where we modify them ("mut"). */ + Global mut; + const Global imm; +} gl = { + .imm = + { + /* nm_logging_setup ("INFO", LOGD_DEFAULT_STRING, NULL, NULL); */ + .log_level = LOGL_INFO, + .log_backend = LOG_BACKEND_GLIB, + .syslog_identifier = "SYSLOG_IDENTIFIER=" G_LOG_DOMAIN, + .prefix = "", + }, +}; + +NMLogDomain _nm_logging_enabled_state[_LOGL_N_REAL] = { + /* nm_logging_setup ("INFO", LOGD_DEFAULT_STRING, NULL, NULL); + * + * Note: LOGD_VPN_PLUGIN is special and must be disabled for + * DEBUG and TRACE levels. */ + [LOGL_INFO] = LOGD_DEFAULT, + [LOGL_WARN] = LOGD_DEFAULT, + [LOGL_ERR] = LOGD_DEFAULT, +}; + +/*****************************************************************************/ + +static const LogDesc domain_desc[] = { + {LOGD_PLATFORM, "PLATFORM"}, + {LOGD_RFKILL, "RFKILL"}, + {LOGD_ETHER, "ETHER"}, + {LOGD_WIFI, "WIFI"}, + {LOGD_BT, "BT"}, + {LOGD_MB, "MB"}, + {LOGD_DHCP4, "DHCP4"}, + {LOGD_DHCP6, "DHCP6"}, + {LOGD_PPP, "PPP"}, + {LOGD_WIFI_SCAN, "WIFI_SCAN"}, + {LOGD_IP4, "IP4"}, + {LOGD_IP6, "IP6"}, + {LOGD_AUTOIP4, "AUTOIP4"}, + {LOGD_DNS, "DNS"}, + {LOGD_VPN, "VPN"}, + {LOGD_SHARING, "SHARING"}, + {LOGD_SUPPLICANT, "SUPPLICANT"}, + {LOGD_AGENTS, "AGENTS"}, + {LOGD_SETTINGS, "SETTINGS"}, + {LOGD_SUSPEND, "SUSPEND"}, + {LOGD_CORE, "CORE"}, + {LOGD_DEVICE, "DEVICE"}, + {LOGD_OLPC, "OLPC"}, + {LOGD_INFINIBAND, "INFINIBAND"}, + {LOGD_FIREWALL, "FIREWALL"}, + {LOGD_ADSL, "ADSL"}, + {LOGD_BOND, "BOND"}, + {LOGD_VLAN, "VLAN"}, + {LOGD_BRIDGE, "BRIDGE"}, + {LOGD_DBUS_PROPS, "DBUS_PROPS"}, + {LOGD_TEAM, "TEAM"}, + {LOGD_CONCHECK, "CONCHECK"}, + {LOGD_DCB, "DCB"}, + {LOGD_DISPATCH, "DISPATCH"}, + {LOGD_AUDIT, "AUDIT"}, + {LOGD_SYSTEMD, "SYSTEMD"}, + {LOGD_VPN_PLUGIN, "VPN_PLUGIN"}, + {LOGD_PROXY, "PROXY"}, + {0}, +}; + +/*****************************************************************************/ + +static char *_domains_to_string(gboolean include_level_override, + NMLogLevel log_level, + const NMLogDomain log_state[static _LOGL_N_REAL]); + +/*****************************************************************************/ + +static gboolean +_syslog_identifier_valid_domain(const char *domain) +{ + char c; + + if (!domain || !domain[0]) + return FALSE; + + /* we pass the syslog identifier as format string. No funny stuff. */ + + for (; (c = domain[0]); domain++) { + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') + || NM_IN_SET(c, '-', '_')) + continue; + return FALSE; + } + return TRUE; +} + +static gboolean +_syslog_identifier_assert(const char *syslog_identifier) +{ + g_assert(syslog_identifier); + g_assert(g_str_has_prefix(syslog_identifier, "SYSLOG_IDENTIFIER=")); + g_assert(_syslog_identifier_valid_domain(&syslog_identifier[NM_STRLEN("SYSLOG_IDENTIFIER=")])); + return TRUE; +} + +static const char * +syslog_identifier_domain(const char *syslog_identifier) +{ + nm_assert(_syslog_identifier_assert(syslog_identifier)); + return &syslog_identifier[NM_STRLEN("SYSLOG_IDENTIFIER=")]; +} + +#if SYSTEMD_JOURNAL +static const char * +syslog_identifier_full(const char *syslog_identifier) +{ + nm_assert(_syslog_identifier_assert(syslog_identifier)); + return &syslog_identifier[0]; +} +#endif + +/*****************************************************************************/ + +static gboolean +match_log_level(const char *level, NMLogLevel *out_level, GError **error) +{ + if (_nm_log_parse_level(level, out_level)) + return TRUE; + + g_set_error(error, + _NM_MANAGER_ERROR, + _NM_MANAGER_ERROR_UNKNOWN_LOG_LEVEL, + _("Unknown log level '%s'"), + level); + return FALSE; +} + +gboolean +nm_logging_setup(const char *level, const char *domains, char **bad_domains, GError **error) +{ + GString * unrecognized = NULL; + NMLogDomain cur_log_state[_LOGL_N_REAL]; + NMLogDomain new_log_state[_LOGL_N_REAL]; + NMLogLevel cur_log_level; + NMLogLevel new_log_level; + gs_free const char **domains_v = NULL; + gsize i_d; + int i; + gboolean had_platform_debug; + gs_free char * domains_free = NULL; + + NM_ASSERT_ON_MAIN_THREAD(); + + g_return_val_if_fail(!bad_domains || !*bad_domains, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + cur_log_level = gl.imm.log_level; + memcpy(cur_log_state, _nm_logging_enabled_state, sizeof(cur_log_state)); + + new_log_level = cur_log_level; + + if (!domains || !*domains) { + domains_free = _domains_to_string(FALSE, cur_log_level, cur_log_state); + domains = domains_free; + } + + for (i = 0; i < G_N_ELEMENTS(new_log_state); i++) + new_log_state[i] = 0; + + if (level && *level) { + if (!match_log_level(level, &new_log_level, error)) + return FALSE; + if (new_log_level == _LOGL_KEEP) { + new_log_level = cur_log_level; + for (i = 0; i < G_N_ELEMENTS(new_log_state); i++) + new_log_state[i] = cur_log_state[i]; + } + } + + domains_v = nm_utils_strsplit_set(domains, ", "); + for (i_d = 0; domains_v && domains_v[i_d]; i_d++) { + const char * s = domains_v[i_d]; + const char * p; + const LogDesc *diter; + NMLogLevel domain_log_level; + NMLogDomain bits; + + /* LOGD_VPN_PLUGIN is protected, that is, when setting ALL or DEFAULT, + * it does not enable the verbose levels DEBUG and TRACE, because that + * may expose sensitive data. */ + NMLogDomain protect = LOGD_NONE; + + p = strchr(s, ':'); + if (p) { + *((char *) p) = '\0'; + if (!match_log_level(p + 1, &domain_log_level, error)) + return FALSE; + } else + domain_log_level = new_log_level; + + bits = 0; + + if (domains_free) { + /* The caller didn't provide any domains to set (`nmcli general logging level DEBUG`). + * We reset all domains that were previously set, but we still want to protect + * VPN_PLUGIN domain. */ + protect = LOGD_VPN_PLUGIN; + } + + /* Check for combined domains */ + if (!g_ascii_strcasecmp(s, LOGD_ALL_STRING)) { + bits = LOGD_ALL; + protect = LOGD_VPN_PLUGIN; + } else if (!g_ascii_strcasecmp(s, LOGD_DEFAULT_STRING)) { + bits = LOGD_DEFAULT; + protect = LOGD_VPN_PLUGIN; + } else if (!g_ascii_strcasecmp(s, LOGD_DHCP_STRING)) + bits = LOGD_DHCP; + else if (!g_ascii_strcasecmp(s, LOGD_IP_STRING)) + bits = LOGD_IP; + + /* Check for compatibility domains */ + else if (!g_ascii_strcasecmp(s, "HW")) + bits = LOGD_PLATFORM; + else if (!g_ascii_strcasecmp(s, "WIMAX")) + continue; + + else { + for (diter = &domain_desc[0]; diter->name; diter++) { + if (!g_ascii_strcasecmp(diter->name, s)) { + bits = diter->num; + break; + } + } + + if (!bits) { + if (!bad_domains) { + g_set_error(error, + _NM_MANAGER_ERROR, + _NM_MANAGER_ERROR_UNKNOWN_LOG_DOMAIN, + _("Unknown log domain '%s'"), + s); + return FALSE; + } + + if (unrecognized) + g_string_append(unrecognized, ", "); + else + unrecognized = g_string_new(NULL); + g_string_append(unrecognized, s); + continue; + } + } + + if (domain_log_level == _LOGL_KEEP) { + for (i = 0; i < G_N_ELEMENTS(new_log_state); i++) + new_log_state[i] = (new_log_state[i] & ~bits) | (cur_log_state[i] & bits); + } else { + for (i = 0; i < G_N_ELEMENTS(new_log_state); i++) { + if (i < domain_log_level) + new_log_state[i] &= ~bits; + else { + new_log_state[i] |= bits; + if ((protect & bits) && i < LOGL_INFO) + new_log_state[i] &= ~protect; + } + } + } + } + + nm_clear_g_free(&gl_main.logging_domains_to_string); + + had_platform_debug = _nm_logging_enabled_lockfree(LOGL_DEBUG, LOGD_PLATFORM); + + G_LOCK(log); + + gl.mut.log_level = new_log_level; + for (i = 0; i < G_N_ELEMENTS(new_log_state); i++) + _nm_logging_enabled_state[i] = new_log_state[i]; + + G_UNLOCK(log); + + if (had_platform_debug && !_nm_logging_enabled_lockfree(LOGL_DEBUG, LOGD_PLATFORM)) { + /* when debug logging is enabled, platform will cache all access to + * sysctl. When the user disables debug-logging, we want to clear that + * cache right away. */ + _nm_logging_clear_platform_logging_cache(); + } + + if (unrecognized) + *bad_domains = g_string_free(unrecognized, FALSE); + + return TRUE; +} + +const char * +nm_logging_level_to_string(void) +{ + NM_ASSERT_ON_MAIN_THREAD(); + + return level_desc[gl.imm.log_level].name; +} + +const char * +nm_logging_all_levels_to_string(void) +{ + static GString *str; + + if (G_UNLIKELY(!str)) { + int i; + + str = g_string_new(NULL); + for (i = 0; i < G_N_ELEMENTS(level_desc); i++) { + if (str->len) + g_string_append_c(str, ','); + g_string_append(str, level_desc[i].name); + } + } + + return str->str; +} + +const char * +nm_logging_domains_to_string(void) +{ + NM_ASSERT_ON_MAIN_THREAD(); + + if (G_UNLIKELY(!gl_main.logging_domains_to_string)) { + gl_main.logging_domains_to_string = + _domains_to_string(TRUE, gl.imm.log_level, _nm_logging_enabled_state); + } + + return gl_main.logging_domains_to_string; +} + +static char * +_domains_to_string(gboolean include_level_override, + NMLogLevel log_level, + const NMLogDomain log_state[static _LOGL_N_REAL]) +{ + const LogDesc *diter; + GString * str; + int i; + + /* We don't just return g_strdup() the logging domains that were set during + * nm_logging_setup(), because we want to expand "DEFAULT" and "ALL". + */ + + str = g_string_sized_new(75); + for (diter = &domain_desc[0]; diter->name; diter++) { + /* If it's set for any lower level, it will also be set for LOGL_ERR */ + if (!(diter->num & log_state[LOGL_ERR])) + continue; + + if (str->len) + g_string_append_c(str, ','); + g_string_append(str, diter->name); + + if (!include_level_override) + continue; + + /* Check if it's logging at a lower level than the default. */ + for (i = 0; i < log_level; i++) { + if (diter->num & log_state[i]) { + g_string_append_printf(str, ":%s", level_desc[i].name); + break; + } + } + /* Check if it's logging at a higher level than the default. */ + if (!(diter->num & log_state[log_level])) { + for (i = log_level + 1; i < _LOGL_N_REAL; i++) { + if (diter->num & log_state[i]) { + g_string_append_printf(str, ":%s", level_desc[i].name); + break; + } + } + } + } + return g_string_free(str, FALSE); +} + +static char _all_logging_domains_to_str[273]; + +const char * +nm_logging_all_domains_to_string(void) +{ + static const char *volatile str = NULL; + const char *s; + +again: + s = g_atomic_pointer_get(&str); + if (G_UNLIKELY(!s)) { + static gsize once = 0; + const LogDesc *diter; + gsize buf_l; + char * buf_p; + + if (!g_once_init_enter(&once)) + goto again; + + buf_p = _all_logging_domains_to_str; + buf_l = sizeof(_all_logging_domains_to_str); + + nm_utils_strbuf_append_str(&buf_p, &buf_l, LOGD_DEFAULT_STRING); + for (diter = &domain_desc[0]; diter->name; diter++) { + nm_utils_strbuf_append_c(&buf_p, &buf_l, ','); + nm_utils_strbuf_append_str(&buf_p, &buf_l, diter->name); + if (diter->num == LOGD_DHCP6) + nm_utils_strbuf_append_str(&buf_p, &buf_l, "," LOGD_DHCP_STRING); + else if (diter->num == LOGD_IP6) + nm_utils_strbuf_append_str(&buf_p, &buf_l, "," LOGD_IP_STRING); + } + nm_utils_strbuf_append_str(&buf_p, &buf_l, LOGD_ALL_STRING); + + /* Did you modify the logging domains (or their names)? Adjust the size of + * _all_logging_domains_to_str buffer above to have the exact size. */ + nm_assert(strlen(_all_logging_domains_to_str) == sizeof(_all_logging_domains_to_str) - 1); + nm_assert(buf_l == 1); + + s = _all_logging_domains_to_str; + g_atomic_pointer_set(&str, s); + g_once_init_leave(&once, 1); + } + + return s; +} + +/** + * nm_logging_get_level: + * @domain: find the lowest enabled logging level for the + * given domain. If this is a set of multiple + * domains, the most verbose level will be returned. + * + * Returns: the lowest (most verbose) logging level for the + * give @domain, or %_LOGL_OFF if it is disabled. + **/ +NMLogLevel +nm_logging_get_level(NMLogDomain domain) +{ + NMLogLevel sl = _LOGL_OFF; + + G_STATIC_ASSERT(LOGL_TRACE == 0); + while (sl > LOGL_TRACE && _nm_logging_enabled_lockfree(sl - 1, domain)) + sl--; + return sl; +} + +gboolean +_nm_logging_enabled_locking(NMLogLevel level, NMLogDomain domain) +{ + gboolean v; + + G_LOCK(log); + v = _nm_logging_enabled_lockfree(level, domain); + G_UNLOCK(log); + return v; +} + +gboolean +_nm_log_enabled_impl(gboolean mt_require_locking, NMLogLevel level, NMLogDomain domain) +{ + return nm_logging_enabled_mt(mt_require_locking, level, domain); +} + +#if SYSTEMD_JOURNAL +static void +_iovec_set(struct iovec *iov, const void *str, gsize len) +{ + iov->iov_base = (void *) str; + iov->iov_len = len; +} + +static void +_iovec_set_string(struct iovec *iov, const char *str) +{ + _iovec_set(iov, str, strlen(str)); +} + + #define _iovec_set_string_literal(iov, str) _iovec_set((iov), "" str "", NM_STRLEN(str)) + +_nm_printf(3, 4) static void _iovec_set_format(struct iovec *iov, + char ** iov_free, + const char * format, + ...) +{ + va_list ap; + char * str; + + va_start(ap, format); + str = g_strdup_vprintf(format, ap); + va_end(ap); + + _iovec_set_string(iov, str); + *iov_free = str; +} + + #define _iovec_set_format_a(iov, reserve_extra, format, ...) \ + G_STMT_START \ + { \ + const gsize _size = (reserve_extra) + (NM_STRLEN(format) + 3); \ + char *const _buf = g_alloca(_size); \ + int _len; \ + \ + G_STATIC_ASSERT_EXPR((reserve_extra) + (NM_STRLEN(format) + 3) <= 96); \ + \ + _len = g_snprintf(_buf, _size, "" format "", ##__VA_ARGS__); \ + \ + nm_assert(_len >= 0); \ + nm_assert(_len < _size); \ + nm_assert(_len == strlen(_buf)); \ + \ + _iovec_set((iov), _buf, _len); \ + } \ + G_STMT_END + + #define _iovec_set_format_str_a(iov, max_str_len, format, str_arg) \ + G_STMT_START \ + { \ + const char *_str_arg = (str_arg); \ + \ + nm_assert(_str_arg &&strlen(_str_arg) < (max_str_len)); \ + _iovec_set_format_a((iov), (max_str_len), format, str_arg); \ + } \ + G_STMT_END + +#endif + +void +_nm_log_impl(const char *file, + guint line, + const char *func, + gboolean mt_require_locking, + NMLogLevel level, + NMLogDomain domain, + int error, + const char *ifname, + const char *conn_uuid, + const char *fmt, + ...) +{ + va_list args; + char * msg; + GTimeVal tv; + int errsv; + const NMLogDomain *cur_log_state; + NMLogDomain cur_log_state_copy[_LOGL_N_REAL]; + Global g_copy; + const Global * g; + + if (G_UNLIKELY(mt_require_locking)) { + G_LOCK(log); + /* we evaluate logging-enabled under lock. There is still a race that + * we might log the message below *after* logging was disabled. That means, + * when disabling logging, we might still log messages. */ + if (!_nm_logging_enabled_lockfree(level, domain)) { + G_UNLOCK(log); + return; + } + g_copy = gl.imm; + memcpy(cur_log_state_copy, _nm_logging_enabled_state, sizeof(cur_log_state_copy)); + G_UNLOCK(log); + g = &g_copy; + cur_log_state = cur_log_state_copy; + } else { + NM_ASSERT_ON_MAIN_THREAD(); + if (!_nm_logging_enabled_lockfree(level, domain)) + return; + g = &gl.imm; + cur_log_state = _nm_logging_enabled_state; + } + + (void) cur_log_state; + + errsv = errno; + + /* Make sure that %m maps to the specified error */ + if (error != 0) { + if (error < 0) + error = -error; + errno = error; + } + + va_start(args, fmt); + msg = g_strdup_vprintf(fmt, args); + va_end(args); + +#define MESSAGE_FMT "%s%-7s [%ld.%04ld] %s" +#define MESSAGE_ARG(prefix, tv, msg) \ + prefix, level_desc[level].level_str, (tv).tv_sec, ((tv).tv_usec / 100), (msg) + + g_get_current_time(&tv); + + if (g->debug_stderr) + g_printerr(MESSAGE_FMT "\n", MESSAGE_ARG(g->prefix, tv, msg)); + + switch (g->log_backend) { +#if SYSTEMD_JOURNAL + case LOG_BACKEND_JOURNAL: + { + gint64 now, boottime; + struct iovec iov_data[15]; + struct iovec * iov = iov_data; + char * iov_free_data[5]; + char ** iov_free = iov_free_data; + const LogDesc *diter; + NMLogDomain dom_all; + char s_log_domains_buf[NM_STRLEN("NM_LOG_DOMAINS=") + sizeof(_all_logging_domains_to_str)]; + char *s_log_domains; + gsize l_log_domains; + + now = nm_utils_get_monotonic_timestamp_nsec(); + boottime = nm_utils_monotonic_timestamp_as_boottime(now, 1); + + _iovec_set_format_a(iov++, 30, "PRIORITY=%d", level_desc[level].syslog_level); + _iovec_set_format(iov++, + iov_free++, + "MESSAGE=" MESSAGE_FMT, + MESSAGE_ARG(g->prefix, tv, msg)); + _iovec_set_string(iov++, syslog_identifier_full(g->syslog_identifier)); + _iovec_set_format_a(iov++, 30, "SYSLOG_PID=%ld", (long) getpid()); + + dom_all = domain; + s_log_domains = s_log_domains_buf; + l_log_domains = sizeof(s_log_domains_buf); + + nm_utils_strbuf_append_str(&s_log_domains, &l_log_domains, "NM_LOG_DOMAINS="); + for (diter = &domain_desc[0]; dom_all != 0 && diter->name; diter++) { + if (!NM_FLAGS_ANY(dom_all, diter->num)) + continue; + if (dom_all != domain) + nm_utils_strbuf_append_c(&s_log_domains, &l_log_domains, ','); + nm_utils_strbuf_append_str(&s_log_domains, &l_log_domains, diter->name); + dom_all &= ~diter->num; + } + nm_assert(l_log_domains > 0); + _iovec_set(iov++, s_log_domains_buf, s_log_domains - s_log_domains_buf); + + G_STATIC_ASSERT_EXPR(LOG_FAC(LOG_DAEMON) == 3); + _iovec_set_string_literal(iov++, "SYSLOG_FACILITY=3"); + _iovec_set_format_str_a(iov++, 15, "NM_LOG_LEVEL=%s", level_desc[level].name); + if (func) + _iovec_set_format(iov++, iov_free++, "CODE_FUNC=%s", func); + _iovec_set_format(iov++, iov_free++, "CODE_FILE=%s", file ?: ""); + _iovec_set_format_a(iov++, 20, "CODE_LINE=%u", line); + _iovec_set_format_a(iov++, + 60, + "TIMESTAMP_MONOTONIC=%lld.%06lld", + (long long) (now / NM_UTILS_NSEC_PER_SEC), + (long long) ((now % NM_UTILS_NSEC_PER_SEC) / 1000)); + _iovec_set_format_a(iov++, + 60, + "TIMESTAMP_BOOTTIME=%lld.%06lld", + (long long) (boottime / NM_UTILS_NSEC_PER_SEC), + (long long) ((boottime % NM_UTILS_NSEC_PER_SEC) / 1000)); + if (error != 0) + _iovec_set_format_a(iov++, 30, "ERRNO=%d", error); + if (ifname) + _iovec_set_format(iov++, iov_free++, "NM_DEVICE=%s", ifname); + if (conn_uuid) + _iovec_set_format(iov++, iov_free++, "NM_CONNECTION=%s", conn_uuid); + + nm_assert(iov <= &iov_data[G_N_ELEMENTS(iov_data)]); + nm_assert(iov_free <= &iov_free_data[G_N_ELEMENTS(iov_free_data)]); + + sd_journal_sendv(iov_data, iov - iov_data); + + for (; --iov_free >= iov_free_data;) + g_free(*iov_free); + } break; +#endif + case LOG_BACKEND_SYSLOG: + syslog(level_desc[level].syslog_level, MESSAGE_FMT, MESSAGE_ARG(g->prefix, tv, msg)); + break; + default: + g_log(syslog_identifier_domain(g->syslog_identifier), + level_desc[level].g_log_level, + MESSAGE_FMT, + MESSAGE_ARG(g->prefix, tv, msg)); + break; + } + + g_free(msg); + + errno = errsv; +} + +/*****************************************************************************/ + +void +_nm_utils_monotonic_timestamp_initialized(const struct timespec *tp, + gint64 offset_sec, + gboolean is_boottime) +{ + NM_ASSERT_ON_MAIN_THREAD(); + + if (_nm_logging_enabled_lockfree(LOGL_DEBUG, LOGD_CORE)) { + time_t now = time(NULL); + struct tm tm; + char s[255]; + + strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", localtime_r(&now, &tm)); + nm_log_dbg(LOGD_CORE, + "monotonic timestamp started counting 1.%09ld seconds ago with " + "an offset of %lld.0 seconds to %s (local time is %s)", + tp->tv_nsec, + (long long) -offset_sec, + is_boottime ? "CLOCK_BOOTTIME" : "CLOCK_MONOTONIC", + s); + } +} + +/*****************************************************************************/ + +static void +nm_log_handler(const char *log_domain, GLogLevelFlags level, const char *message, gpointer ignored) +{ + int syslog_priority; + + switch (level & G_LOG_LEVEL_MASK) { + case G_LOG_LEVEL_ERROR: + syslog_priority = LOG_CRIT; + break; + case G_LOG_LEVEL_CRITICAL: + syslog_priority = LOG_ERR; + break; + case G_LOG_LEVEL_WARNING: + syslog_priority = LOG_WARNING; + break; + case G_LOG_LEVEL_MESSAGE: + syslog_priority = LOG_NOTICE; + break; + case G_LOG_LEVEL_DEBUG: + syslog_priority = LOG_DEBUG; + break; + case G_LOG_LEVEL_INFO: + default: + syslog_priority = LOG_INFO; + break; + } + + /* we don't need any locking here. The glib log handler gets only registered + * once during nm_logging_init() and the global data is not modified afterwards. */ + nm_assert(gl.imm.init_done); + + if (gl.imm.debug_stderr) + g_printerr("%s%s\n", gl.imm.prefix, message ?: ""); + + switch (gl.imm.log_backend) { +#if SYSTEMD_JOURNAL + case LOG_BACKEND_JOURNAL: + { + gint64 now, boottime; + + now = nm_utils_get_monotonic_timestamp_nsec(); + boottime = nm_utils_monotonic_timestamp_as_boottime(now, 1); + + sd_journal_send("PRIORITY=%d", + syslog_priority, + "MESSAGE=%s%s", + gl.imm.prefix, + message ?: "", + syslog_identifier_full(gl.imm.syslog_identifier), + "SYSLOG_PID=%ld", + (long) getpid(), + "SYSLOG_FACILITY=3", + "GLIB_DOMAIN=%s", + log_domain ?: "", + "GLIB_LEVEL=%d", + (int) (level & G_LOG_LEVEL_MASK), + "TIMESTAMP_MONOTONIC=%lld.%06lld", + (long long) (now / NM_UTILS_NSEC_PER_SEC), + (long long) ((now % NM_UTILS_NSEC_PER_SEC) / 1000), + "TIMESTAMP_BOOTTIME=%lld.%06lld", + (long long) (boottime / NM_UTILS_NSEC_PER_SEC), + (long long) ((boottime % NM_UTILS_NSEC_PER_SEC) / 1000), + NULL); + } break; +#endif + default: + syslog(syslog_priority, "%s%s", gl.imm.prefix, message ?: ""); + break; + } +} + +gboolean +nm_logging_syslog_enabled(void) +{ + NM_ASSERT_ON_MAIN_THREAD(); + + return gl.imm.uses_syslog; +} + +void +nm_logging_init_pre(const char *syslog_identifier, char *prefix_take) +{ + /* this function may be called zero or one times, and only + * - on the main thread + * - not after nm_logging_init(). */ + + NM_ASSERT_ON_MAIN_THREAD(); + + if (gl.imm.init_pre_done) + g_return_if_reached(); + + if (gl.imm.init_done) + g_return_if_reached(); + + if (!_syslog_identifier_valid_domain(syslog_identifier)) + g_return_if_reached(); + + if (!prefix_take || !prefix_take[0]) + g_return_if_reached(); + + G_LOCK(log); + + gl.mut.init_pre_done = TRUE; + + gl.mut.syslog_identifier = g_strdup_printf("SYSLOG_IDENTIFIER=%s", syslog_identifier); + nm_assert(_syslog_identifier_assert(gl.imm.syslog_identifier)); + + /* we pass the allocated string on and never free it. */ + gl.mut.prefix = prefix_take; + + G_UNLOCK(log); +} + +void +nm_logging_init(const char *logging_backend, gboolean debug) +{ + gboolean fetch_monotonic_timestamp = FALSE; + gboolean obsolete_debug_backend = FALSE; + LogBackend x_log_backend; + + /* this function may be called zero or one times, and only on the + * main thread. */ + + NM_ASSERT_ON_MAIN_THREAD(); + + nm_assert(NM_IN_STRSET("" NM_CONFIG_DEFAULT_LOGGING_BACKEND, + NM_LOG_CONFIG_BACKEND_JOURNAL, + NM_LOG_CONFIG_BACKEND_SYSLOG)); + + if (gl.imm.init_done) + g_return_if_reached(); + + if (!logging_backend) + logging_backend = "" NM_CONFIG_DEFAULT_LOGGING_BACKEND; + + if (nm_streq(logging_backend, NM_LOG_CONFIG_BACKEND_DEBUG)) { + /* "debug" was wrongly documented as a valid logging backend. It makes no sense however, + * because printing to stderr only makes sense when not demonizing. Whether to daemonize + * is only controlled via command line arguments (--no-daemon, --debug) and not via the + * logging backend from configuration. + * + * Fall back to the default. */ + logging_backend = "" NM_CONFIG_DEFAULT_LOGGING_BACKEND; + obsolete_debug_backend = TRUE; + } + + G_LOCK(log); + +#if SYSTEMD_JOURNAL + if (!nm_streq(logging_backend, NM_LOG_CONFIG_BACKEND_SYSLOG)) { + x_log_backend = LOG_BACKEND_JOURNAL; + + /* We only log the monotonic-timestamp with structured logging (journal). + * Only in this case, fetch the timestamp. */ + fetch_monotonic_timestamp = TRUE; + } else +#endif + { + x_log_backend = LOG_BACKEND_SYSLOG; + openlog(syslog_identifier_domain(gl.imm.syslog_identifier), LOG_PID, LOG_DAEMON); + } + + gl.mut.init_done = TRUE; + gl.mut.log_backend = x_log_backend; + gl.mut.uses_syslog = TRUE; + gl.mut.debug_stderr = debug; + + g_log_set_handler(syslog_identifier_domain(gl.imm.syslog_identifier), + G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, + nm_log_handler, + NULL); + + G_UNLOCK(log); + + if (fetch_monotonic_timestamp) { + /* ensure we read a monotonic timestamp. Reading the timestamp the first + * time causes a logging message. We don't want to do that during _nm_log_impl. */ + nm_utils_get_monotonic_timestamp_nsec(); + } + + if (obsolete_debug_backend) + nm_log_dbg(LOGD_CORE, + "config: ignore deprecated logging backend 'debug', fallback to '%s'", + logging_backend); + + if (nm_streq(logging_backend, NM_LOG_CONFIG_BACKEND_SYSLOG)) { + /* good */ + } else if (nm_streq(logging_backend, NM_LOG_CONFIG_BACKEND_JOURNAL)) { +#if !SYSTEMD_JOURNAL + nm_log_warn(LOGD_CORE, + "config: logging backend 'journal' is not available, fallback to 'syslog'"); +#endif + } else { + nm_log_warn(LOGD_CORE, + "config: invalid logging backend '%s', fallback to '%s'", + logging_backend, +#if SYSTEMD_JOURNAL + NM_LOG_CONFIG_BACKEND_JOURNAL +#else + NM_LOG_CONFIG_BACKEND_SYSLOG +#endif + ); + } +} diff --git a/src/libnm-log-core/nm-logging.h b/src/libnm-log-core/nm-logging.h new file mode 100644 index 0000000000..574c225c2e --- /dev/null +++ b/src/libnm-log-core/nm-logging.h @@ -0,0 +1,189 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2012 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_LOGGING_H__ +#define __NETWORKMANAGER_LOGGING_H__ + +#ifdef __NM_TEST_UTILS_H__ + #error nm-test-utils.h must be included as last header +#endif + +#include "libnm-glib-aux/nm-logging-fwd.h" + +#define NM_LOG_CONFIG_BACKEND_DEBUG "debug" +#define NM_LOG_CONFIG_BACKEND_SYSLOG "syslog" +#define NM_LOG_CONFIG_BACKEND_JOURNAL "journal" + +#define nm_log_err(domain, ...) nm_log(LOGL_ERR, (domain), NULL, NULL, __VA_ARGS__) +#define nm_log_warn(domain, ...) nm_log(LOGL_WARN, (domain), NULL, NULL, __VA_ARGS__) +#define nm_log_info(domain, ...) nm_log(LOGL_INFO, (domain), NULL, NULL, __VA_ARGS__) +#define nm_log_dbg(domain, ...) nm_log(LOGL_DEBUG, (domain), NULL, NULL, __VA_ARGS__) +#define nm_log_trace(domain, ...) nm_log(LOGL_TRACE, (domain), NULL, NULL, __VA_ARGS__) + +//#define _NM_LOG_FUNC G_STRFUNC +#define _NM_LOG_FUNC NULL + +/* A wrapper for the _nm_log_impl() function that adds call site information. + * Contrary to nm_log(), it unconditionally calls the function without + * checking whether logging for the given level and domain is enabled. */ +#define _nm_log_mt(mt_require_locking, level, domain, error, ifname, con_uuid, ...) \ + G_STMT_START \ + { \ + _nm_log_impl(__FILE__, \ + __LINE__, \ + _NM_LOG_FUNC, \ + (mt_require_locking), \ + (level), \ + (domain), \ + (error), \ + (ifname), \ + (con_uuid), \ + ""__VA_ARGS__); \ + } \ + G_STMT_END + +#define _nm_log(level, domain, error, ifname, con_uuid, ...) \ + _nm_log_mt(!(NM_THREAD_SAFE_ON_MAIN_THREAD), \ + level, \ + domain, \ + error, \ + ifname, \ + con_uuid, \ + __VA_ARGS__) + +/* nm_log() only evaluates its argument list after checking + * whether logging for the given level/domain is enabled. */ +#define nm_log(level, domain, ifname, con_uuid, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled((level), (domain))) { \ + _nm_log(level, domain, 0, ifname, con_uuid, __VA_ARGS__); \ + } \ + } \ + G_STMT_END + +#define _nm_log_ptr(level, domain, ifname, con_uuid, self, prefix, ...) \ + nm_log((level), \ + (domain), \ + (ifname), \ + (con_uuid), \ + "%s[" NM_HASH_OBFUSCATE_PTR_FMT "] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + (prefix) ?: "", \ + NM_HASH_OBFUSCATE_PTR(self) _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +static inline gboolean +_nm_log_ptr_is_debug(NMLogLevel level) +{ + return level <= LOGL_DEBUG; +} + +/* log a message for an object (with providing a generic @self pointer) */ +#define nm_log_ptr(level, domain, ifname, con_uuid, self, prefix, ...) \ + G_STMT_START \ + { \ + if (_nm_log_ptr_is_debug(level)) { \ + _nm_log_ptr((level), (domain), (ifname), (con_uuid), (self), (prefix), __VA_ARGS__); \ + } else { \ + const char *__prefix = (prefix); \ + \ + nm_log((level), \ + (domain), \ + (ifname), \ + (con_uuid), \ + "%s%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __prefix ?: "", \ + __prefix ? " " : "" _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +#define _nm_log_obj(level, domain, ifname, con_uuid, self, prefix, ...) \ + _nm_log_ptr((level), (domain), (ifname), (con_uuid), (self), prefix, __VA_ARGS__) + +/* log a message for an object (with providing a @self pointer to a GObject). + * Contrary to nm_log_ptr(), @self must be a GObject type (or %NULL). + * As of now, nm_log_obj() is identical to nm_log_ptr(), but we might change that */ +#define nm_log_obj(level, domain, ifname, con_uuid, self, prefix, ...) \ + nm_log_ptr((level), (domain), (ifname), (con_uuid), (self), prefix, __VA_ARGS__) + +const char *nm_logging_level_to_string(void); +const char *nm_logging_domains_to_string(void); + +/*****************************************************************************/ + +extern NMLogDomain _nm_logging_enabled_state[_LOGL_N_REAL]; + +static inline gboolean +_nm_logging_enabled_lockfree(NMLogLevel level, NMLogDomain domain) +{ + nm_assert(((guint) level) < G_N_ELEMENTS(_nm_logging_enabled_state)); + return (((guint) level) < G_N_ELEMENTS(_nm_logging_enabled_state)) + && !!(_nm_logging_enabled_state[level] & domain); +} + +gboolean _nm_logging_enabled_locking(NMLogLevel level, NMLogDomain domain); + +static inline gboolean +nm_logging_enabled_mt(gboolean mt_require_locking, NMLogLevel level, NMLogDomain domain) +{ + if (mt_require_locking) + return _nm_logging_enabled_locking(level, domain); + + NM_ASSERT_ON_MAIN_THREAD(); + return _nm_logging_enabled_lockfree(level, domain); +} + +#define nm_logging_enabled(level, domain) \ + nm_logging_enabled_mt(!(NM_THREAD_SAFE_ON_MAIN_THREAD), level, domain) + +/*****************************************************************************/ + +NMLogLevel nm_logging_get_level(NMLogDomain domain); + +const char *nm_logging_all_levels_to_string(void); +const char *nm_logging_all_domains_to_string(void); + +gboolean +nm_logging_setup(const char *level, const char *domains, char **bad_domains, GError **error); + +void nm_logging_init_pre(const char *syslog_identifier, char *prefix_take); + +void nm_logging_init(const char *logging_backend, gboolean debug); + +gboolean nm_logging_syslog_enabled(void); + +/*****************************************************************************/ + +#define __NMLOG_DEFAULT(level, domain, prefix, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + (domain), \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + (prefix) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +#define __NMLOG_DEFAULT_WITH_ADDR(level, domain, prefix, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + (domain), \ + NULL, \ + NULL, \ + "%s[" NM_HASH_OBFUSCATE_PTR_FMT "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + (prefix), \ + NM_HASH_OBFUSCATE_PTR(self) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +extern void _nm_logging_clear_platform_logging_cache(void); + +#endif /* __NETWORKMANAGER_LOGGING_H__ */ diff --git a/src/libnm-log-null/meson.build b/src/libnm-log-null/meson.build new file mode 100644 index 0000000000..6208708260 --- /dev/null +++ b/src/libnm-log-null/meson.build @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libnm_log_null = static_library( + 'nm-log-null', + sources: 'nm-logging-null.c', + include_directories: [ + shared_inc, + src_inc, + top_inc, + ], + dependencies: glib_nm_default_dep, +) diff --git a/src/libnm-log-null/nm-logging-null.c b/src/libnm-log-null/nm-logging-null.c new file mode 100644 index 0000000000..a454c5bc6a --- /dev/null +++ b/src/libnm-log-null/nm-logging-null.c @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "libnm-glib-aux/nm-logging-fwd.h" + +/*****************************************************************************/ + +gboolean +_nm_log_enabled_impl(gboolean mt_require_locking, NMLogLevel level, NMLogDomain domain) +{ + return FALSE; +} + +void +_nm_log_impl(const char *file, + guint line, + const char *func, + gboolean mt_require_locking, + NMLogLevel level, + NMLogDomain domain, + int error, + const char *ifname, + const char *con_uuid, + const char *fmt, + ...) +{} + +void +_nm_utils_monotonic_timestamp_initialized(const struct timespec *tp, + gint64 offset_sec, + gboolean is_boottime) +{} diff --git a/src/libnm-platform/nm-netlink.c b/src/libnm-platform/nm-netlink.c index 7a6d7e045b..95010c0257 100644 --- a/src/libnm-platform/nm-netlink.c +++ b/src/libnm-platform/nm-netlink.c @@ -3,7 +3,7 @@ * Copyright (C) 2018 Red Hat, Inc. */ -#include "nm-glib-aux/nm-default-glib-i18n-lib.h" +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-netlink.h" diff --git a/src/libnm-platform/nm-platform-utils.c b/src/libnm-platform/nm-platform-utils.c index dbb4864f1d..7c101213d9 100644 --- a/src/libnm-platform/nm-platform-utils.c +++ b/src/libnm-platform/nm-platform-utils.c @@ -3,7 +3,7 @@ * Copyright (C) 2015 Red Hat, Inc. */ -#include "nm-glib-aux/nm-default-glib-i18n-lib.h" +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-platform-utils.h" @@ -19,7 +19,7 @@ #include <libudev.h> #include "libnm-base/nm-ethtool-base.h" -#include "nm-log-core/nm-logging.h" +#include "libnm-log-core/nm-logging.h" /*****************************************************************************/ diff --git a/src/libnm-platform/nmp-netns.c b/src/libnm-platform/nmp-netns.c index f97339a756..aea5b3b6dc 100644 --- a/src/libnm-platform/nmp-netns.c +++ b/src/libnm-platform/nmp-netns.c @@ -3,7 +3,7 @@ * Copyright (C) 2016 Red Hat, Inc. */ -#include "nm-glib-aux/nm-default-glib-i18n-lib.h" +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nmp-netns.h" @@ -13,7 +13,7 @@ #include <sys/types.h> #include <pthread.h> -#include "nm-log-core/nm-logging.h" +#include "libnm-log-core/nm-logging.h" /*****************************************************************************/ diff --git a/src/libnm-platform/tests/test-nm-platform.c b/src/libnm-platform/tests/test-nm-platform.c index d68dc772eb..f4b32d4693 100644 --- a/src/libnm-platform/tests/test-nm-platform.c +++ b/src/libnm-platform/tests/test-nm-platform.c @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "nm-glib-aux/nm-default-glib-i18n-prog.h" +#include "libnm-glib-aux/nm-default-glib-i18n-prog.h" -#include "nm-log-core/nm-logging.h" +#include "libnm-log-core/nm-logging.h" #include "libnm-platform/nm-netlink.h" #include "libnm-platform/nmp-netns.h" diff --git a/src/libnm-systemd-shared/nm-default-systemd-shared.h b/src/libnm-systemd-shared/nm-default-systemd-shared.h index bc0e6c4ca0..8b312f9aa7 100644 --- a/src/libnm-systemd-shared/nm-default-systemd-shared.h +++ b/src/libnm-systemd-shared/nm-default-systemd-shared.h @@ -8,7 +8,7 @@ /*****************************************************************************/ -#include "nm-glib-aux/nm-default-glib.h" +#include "libnm-glib-aux/nm-default-glib.h" #undef NETWORKMANAGER_COMPILATION #define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED diff --git a/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h b/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h index 83531e230e..2fc8b83176 100644 --- a/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h +++ b/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h @@ -8,7 +8,7 @@ #include "libnm-systemd-shared/nm-default-systemd-shared.h" -#include "nm-glib-aux/nm-logging-fwd.h" +#include "libnm-glib-aux/nm-logging-fwd.h" /*****************************************************************************/ diff --git a/src/libnm-udev-aux/meson.build b/src/libnm-udev-aux/meson.build index 4577a83a93..55acbaab06 100644 --- a/src/libnm-udev-aux/meson.build +++ b/src/libnm-udev-aux/meson.build @@ -3,6 +3,11 @@ libnm_udev_aux = static_library( 'nm-udev-aux', sources: 'nm-udev-utils.c', + include_directories: [ + shared_inc, + src_inc, + top_inc, + ], dependencies: [ glib_nm_default_dep, libudev_dep, diff --git a/src/libnm-udev-aux/nm-udev-utils.c b/src/libnm-udev-aux/nm-udev-utils.c index 0b941dff53..ef1cf26b2d 100644 --- a/src/libnm-udev-aux/nm-udev-utils.c +++ b/src/libnm-udev-aux/nm-udev-utils.c @@ -3,7 +3,7 @@ * Copyright (C) 2017 Red Hat, Inc. */ -#include "nm-glib-aux/nm-default-glib-i18n-lib.h" +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-udev-utils.h" diff --git a/src/meson.build b/src/meson.build index 32925d5b77..fbe9765756 100644 --- a/src/meson.build +++ b/src/meson.build @@ -2,6 +2,9 @@ src_inc = include_directories('.') +subdir('libnm-glib-aux') +subdir('libnm-log-null') +subdir('libnm-log-core') subdir('libnm-systemd-shared') subdir('libnm-udev-aux') subdir('libnm-base') @@ -14,6 +17,7 @@ subdir('libnm-core-aux-extern') subdir('core') if enable_tests + subdir('libnm-glib-aux/tests') subdir('libnm-platform/tests') subdir('libnm-core-impl/tests') endif |