diff options
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/nm-types.h | 2 | ||||
-rw-r--r-- | src/platform/nm-linux-platform.c | 162 | ||||
-rw-r--r-- | src/platform/nm-platform.c | 69 | ||||
-rw-r--r-- | src/platform/nm-platform.h | 6 | ||||
-rw-r--r-- | src/platform/nmp-netns.c | 495 | ||||
-rw-r--r-- | src/platform/nmp-netns.h | 70 | ||||
-rw-r--r-- | src/platform/tests/test-link.c | 136 | ||||
-rwxr-xr-x | tools/run-test-valgrind.sh | 8 |
9 files changed, 889 insertions, 63 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 0d6457a43b..ff3d0ba873 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -311,6 +311,8 @@ libNetworkManager_la_SOURCES = \ dnsmasq-manager/nm-dnsmasq-utils.c \ dnsmasq-manager/nm-dnsmasq-utils.h \ \ + platform/nmp-netns.c \ + platform/nmp-netns.h \ platform/nm-fake-platform.c \ platform/nm-fake-platform.h \ platform/nm-linux-platform.c \ @@ -541,6 +543,8 @@ libnm_iface_helper_la_SOURCES = \ platform/nm-platform.h \ platform/nm-platform-utils.c \ platform/nm-platform-utils.h \ + platform/nmp-netns.c \ + platform/nmp-netns.h \ platform/nmp-object.c \ platform/nmp-object.h \ platform/wifi/wifi-utils-nl80211.c \ diff --git a/src/nm-types.h b/src/nm-types.h index 0fda9216a7..6722e39474 100644 --- a/src/nm-types.h +++ b/src/nm-types.h @@ -79,7 +79,7 @@ typedef struct _NMPlatformIP4Route NMPlatformIP4Route; typedef struct _NMPlatformIP6Address NMPlatformIP6Address; typedef struct _NMPlatformIP6Route NMPlatformIP6Route; typedef struct _NMPlatformLink NMPlatformLink; - +typedef struct _NMPNetns NMPNetns; typedef struct _NMPObject NMPObject; typedef enum { diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c index 2bdcb9bcd8..a089edef09 100644 --- a/src/platform/nm-linux-platform.c +++ b/src/platform/nm-linux-platform.c @@ -50,6 +50,7 @@ #include "nm-core-utils.h" #include "nmp-object.h" +#include "nmp-netns.h" #include "nm-platform-utils.h" #include "wifi/wifi-utils.h" #include "wifi/wifi-utils-wext.h" @@ -221,6 +222,7 @@ static void do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActi static void cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data); static void cache_prune_candidates_prune (NMPlatform *platform); static gboolean event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks); +static void _assert_netns_current (NMPlatform *platform); /*****************************************************************************/ @@ -647,6 +649,8 @@ _linktype_get_type (NMPlatform *platform, { guint i; + _assert_netns_current (platform); + if (completed_from_cache) { const NMPObject *obj; @@ -2410,6 +2414,7 @@ void nm_linux_platform_setup (void) { g_object_new (NM_TYPE_LINUX_PLATFORM, + NM_PLATFORM_NETNS_SUPPORT, FALSE, NM_PLATFORM_REGISTER_SINGLETON, TRUE, NULL); } @@ -2417,6 +2422,16 @@ nm_linux_platform_setup (void) /******************************************************************/ static void +_assert_netns_current (NMPlatform *platform) +{ +#if NM_MORE_ASSERTS + nm_assert (NM_IS_LINUX_PLATFORM (platform)); + + nm_assert (NM_IN_SET (nm_platform_netns_get (platform), NULL, nmp_netns_get_current ())); +#endif +} + +static void _log_dbg_sysctl_set_impl (NMPlatform *platform, const char *path, const char *value) { GError *error = NULL; @@ -2449,6 +2464,7 @@ _log_dbg_sysctl_set_impl (NMPlatform *platform, const char *path, const char *va static gboolean sysctl_set (NMPlatform *platform, const char *path, const char *value) { + nm_auto_pop_netns NMPNetns *netns = NULL; int fd, tries; gssize nwrote; gsize len; @@ -2464,6 +2480,9 @@ sysctl_set (NMPlatform *platform, const char *path, const char *value) /* Don't write to suspicious locations */ g_assert (!strstr (path, "/../")); + if (!nm_platform_netns_push (platform, &netns)) + return FALSE; + fd = open (path, O_WRONLY | O_TRUNC); if (fd == -1) { if (errno == ENOENT) { @@ -2578,6 +2597,7 @@ _log_dbg_sysctl_get_impl (NMPlatform *platform, const char *path, const char *co static char * sysctl_get (NMPlatform *platform, const char *path) { + nm_auto_pop_netns NMPNetns *netns = NULL; GError *error = NULL; char *contents; @@ -2587,6 +2607,9 @@ sysctl_get (NMPlatform *platform, const char *path) /* Don't write to suspicious locations */ g_assert (!strstr (path, "/../")); + if (!nm_platform_netns_push (platform, &netns)) + return NULL; + if (!g_file_get_contents (path, &contents, NULL, &error)) { /* We assume FAILED means EOPNOTSUP */ if ( g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) @@ -2651,6 +2674,9 @@ do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cach nm_assert (!obj || cache_op == NMP_CACHE_OPS_REMOVED || obj == nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj)); nm_assert (!obj || cache_op != NMP_CACHE_OPS_REMOVED || obj != nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj)); + /* we raise the signals inside the namespace of the NMPlatform instance. */ + _assert_netns_current (platform); + switch (cache_op) { case NMP_CACHE_OPS_ADDED: if (!nmp_object_is_visible (obj)) @@ -4135,11 +4161,15 @@ link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enable static gboolean link_supports_carrier_detect (NMPlatform *platform, int ifindex) { + nm_auto_pop_netns NMPNetns *netns = NULL; const char *name = nm_platform_link_get_name (platform, ifindex); if (!name) return FALSE; + if (!nm_platform_netns_push (platform, &netns)) + return FALSE; + /* We use netlink for the actual carrier detection, but netlink can't tell * us whether the device actually supports carrier detection in the first * place. We assume any device that does implements one of these two APIs. @@ -4150,6 +4180,7 @@ link_supports_carrier_detect (NMPlatform *platform, int ifindex) static gboolean link_supports_vlans (NMPlatform *platform, int ifindex) { + nm_auto_pop_netns NMPNetns *netns = NULL; const NMPObject *obj; obj = cache_lookup_link (platform, ifindex); @@ -4158,6 +4189,9 @@ link_supports_vlans (NMPlatform *platform, int ifindex) if (!obj || obj->link.arptype != ARPHRD_ETHER) return FALSE; + if (!nm_platform_netns_push (platform, &netns)) + return FALSE; + return nmp_utils_ethtool_supports_vlans (obj->link.name); } @@ -4196,6 +4230,11 @@ link_get_permanent_address (NMPlatform *platform, guint8 *buf, size_t *length) { + nm_auto_pop_netns NMPNetns *netns = NULL; + + if (!nm_platform_netns_push (platform, &netns)) + return FALSE; + return nmp_utils_ethtool_get_permanent_address (nm_platform_link_get_name (platform, ifindex), buf, length); } @@ -4975,15 +5014,19 @@ wifi_get_wifi_data (NMPlatform *platform, int ifindex) return wifi_data; } +#define WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, retval) \ + nm_auto_pop_netns NMPNetns *netns = NULL; \ + WifiData *wifi_data; \ + if (!nm_platform_netns_push (platform, &netns)) \ + return retval; \ + wifi_data = wifi_get_wifi_data (platform, ifindex); \ + if (!wifi_data) \ + return retval; static gboolean wifi_get_capabilities (NMPlatform *platform, int ifindex, NMDeviceWifiCapabilities *caps) { - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (!wifi_data) - return FALSE; - + WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE); if (caps) *caps = wifi_utils_get_caps (wifi_data); return TRUE; @@ -4992,90 +5035,64 @@ wifi_get_capabilities (NMPlatform *platform, int ifindex, NMDeviceWifiCapabiliti static gboolean wifi_get_bssid (NMPlatform *platform, int ifindex, guint8 *bssid) { - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (!wifi_data) - return FALSE; + WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE); return wifi_utils_get_bssid (wifi_data, bssid); } static guint32 wifi_get_frequency (NMPlatform *platform, int ifindex) { - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (!wifi_data) - return 0; + WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, 0); return wifi_utils_get_freq (wifi_data); } static gboolean wifi_get_quality (NMPlatform *platform, int ifindex) { - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (!wifi_data) - return FALSE; + WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE); return wifi_utils_get_qual (wifi_data); } static guint32 wifi_get_rate (NMPlatform *platform, int ifindex) { - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (!wifi_data) - return FALSE; + WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE); return wifi_utils_get_rate (wifi_data); } static NM80211Mode wifi_get_mode (NMPlatform *platform, int ifindex) { - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (!wifi_data) - return NM_802_11_MODE_UNKNOWN; - + WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, NM_802_11_MODE_UNKNOWN); return wifi_utils_get_mode (wifi_data); } static void wifi_set_mode (NMPlatform *platform, int ifindex, NM80211Mode mode) { - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (wifi_data) - wifi_utils_set_mode (wifi_data, mode); + WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, ); + wifi_utils_set_mode (wifi_data, mode); } static void wifi_set_powersave (NMPlatform *platform, int ifindex, guint32 powersave) { - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (wifi_data) - wifi_utils_set_powersave (wifi_data, powersave); + WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, ); + wifi_utils_set_powersave (wifi_data, powersave); } static guint32 wifi_find_frequency (NMPlatform *platform, int ifindex, const guint32 *freqs) { - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (!wifi_data) - return 0; - + WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, 0); return wifi_utils_find_freq (wifi_data, freqs); } static void wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean running) { - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (wifi_data) - wifi_utils_indicate_addressing_running (wifi_data, running); + WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, ); + wifi_utils_indicate_addressing_running (wifi_data, running); } /******************************************************************/ @@ -5083,33 +5100,21 @@ wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean ru static guint32 mesh_get_channel (NMPlatform *platform, int ifindex) { - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (!wifi_data) - return 0; - + WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, 0); return wifi_utils_get_mesh_channel (wifi_data); } static gboolean mesh_set_channel (NMPlatform *platform, int ifindex, guint32 channel) { - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (!wifi_data) - return FALSE; - + WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE); return wifi_utils_set_mesh_channel (wifi_data, channel); } static gboolean mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len) { - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (!wifi_data) - return FALSE; - + WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE); return wifi_utils_set_mesh_ssid (wifi_data, ssid, len); } @@ -5118,8 +5123,12 @@ mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len) static gboolean link_get_wake_on_lan (NMPlatform *platform, int ifindex) { + nm_auto_pop_netns NMPNetns *netns = NULL; NMLinkType type = nm_platform_link_get_type (platform, ifindex); + if (!nm_platform_netns_push (platform, &netns)) + return FALSE; + if (type == NM_LINK_TYPE_ETHERNET) return nmp_utils_ethtool_get_wake_on_lan (nm_platform_link_get_name (platform, ifindex)); else if (type == NM_LINK_TYPE_WIFI) { @@ -5140,6 +5149,11 @@ link_get_driver_info (NMPlatform *platform, char **out_driver_version, char **out_fw_version) { + nm_auto_pop_netns NMPNetns *netns = NULL; + + if (!nm_platform_netns_push (platform, &netns)) + return FALSE; + return nmp_utils_ethtool_get_driver_info (nm_platform_link_get_name (platform, ifindex), out_driver_name, out_driver_version, @@ -5721,6 +5735,7 @@ out: static gboolean event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks) { + nm_auto_pop_netns NMPNetns *netns = NULL; NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); int r, nle; struct pollfd pfd; @@ -5733,6 +5748,9 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks) gint64 timeout_abs_ns; } data_next; + if (!nm_platform_netns_push (platform, &netns)) + return FALSE; + while (TRUE) { while (TRUE) { @@ -5837,7 +5855,14 @@ cache_update_link_udev (NMPlatform *platform, int ifindex, GUdevDevice *udev_dev NMPCacheOpsType cache_op; cache_op = nmp_cache_update_link_udev (priv->cache, ifindex, udev_device, &obj_cache, &was_visible, cache_pre_hook, platform); - do_emit_signal (platform, obj_cache, cache_op, was_visible); + + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + nm_auto_pop_netns NMPNetns *netns = NULL; + + if (!nm_platform_netns_push (platform, &netns)) + return; + do_emit_signal (platform, obj_cache, cache_op, was_visible); + } } static void @@ -5908,6 +5933,7 @@ handle_udev_event (GUdevClient *client, GUdevDevice *udev_device, gpointer user_data) { + nm_auto_pop_netns NMPNetns *netns = NULL; NMPlatform *platform = NM_PLATFORM (user_data); const char *subsys; const char *ifindex; @@ -5915,6 +5941,9 @@ handle_udev_event (GUdevClient *client, g_return_if_fail (action != NULL); + if (!nm_platform_netns_push (platform, &netns)) + return; + /* A bit paranoid */ subsys = g_udev_device_get_subsystem (udev_device); g_return_if_fail (!g_strcmp0 (subsys, "net")); @@ -5939,7 +5968,8 @@ nm_linux_platform_init (NMLinuxPlatform *self) NMLinuxPlatformPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformPrivate); gboolean use_udev; - use_udev = access ("/sys", W_OK) == 0; + use_udev = nmp_netns_is_initial () + && access ("/sys", W_OK) == 0; self->priv = priv; @@ -5963,7 +5993,17 @@ constructed (GObject *_object) gboolean status; int nle; - _LOGD ("create (%s udev)", + nm_assert (!platform->_netns || platform->_netns == nmp_netns_get_current ()); + + _LOGD ("create (%s netns, %s, %s udev)", + !platform->_netns ? "ignore" : "use", + !platform->_netns && nmp_netns_is_initial () + ? "initial netns" + : (!nmp_netns_get_current () + ? "no netns support" + : nm_sprintf_bufa (100, "in netns[%p]%s", + nmp_netns_get_current (), + nmp_netns_get_current () == nmp_netns_get_initial () ? "/main" : "")), nmp_cache_use_udev_get (priv->cache) ? "use" : "no"); priv->nlh = nl_socket_alloc (); diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c index e31196b5ee..f22078a5cb 100644 --- a/src/platform/nm-platform.c +++ b/src/platform/nm-platform.c @@ -41,6 +41,7 @@ #include "nm-enum-types.h" #include "nm-platform-utils.h" #include "nmp-object.h" +#include "nmp-netns.h" /*****************************************************************************/ @@ -89,6 +90,7 @@ static guint signals[_NM_PLATFORM_SIGNAL_ID_LAST] = { 0 }; enum { PROP_0, + PROP_NETNS_SUPPORT, PROP_REGISTER_SINGLETON, LAST_PROP, }; @@ -2128,6 +2130,10 @@ nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_pe /* Pre-4.1 kernel did not expose the peer_ifindex as IFA_LINK. Lookup via ethtool. */ if (out_peer_ifindex) { + nm_auto_pop_netns NMPNetns *netns = NULL; + + if (!nm_platform_netns_push (self, &netns)) + return FALSE; peer_ifindex = nmp_utils_ethtool_get_peer_ifindex (plink->name); if (peer_ifindex <= 0) return FALSE; @@ -2393,16 +2399,24 @@ _to_string_ifa_flags (guint32 ifa_flags, char *buf, gsize size) gboolean nm_platform_ethtool_set_wake_on_lan (NMPlatform *self, const char *ifname, NMSettingWiredWakeOnLan wol, const char *wol_password) { + nm_auto_pop_netns NMPNetns *netns = NULL; _CHECK_SELF (self, klass, FALSE); + if (!nm_platform_netns_push (self, &netns)) + return FALSE; + return nmp_utils_ethtool_set_wake_on_lan (ifname, wol, wol_password); } gboolean nm_platform_ethtool_get_link_speed (NMPlatform *self, const char *ifname, guint32 *out_speed) { + nm_auto_pop_netns NMPNetns *netns = NULL; _CHECK_SELF (self, klass, FALSE); + if (!nm_platform_netns_push (self, &netns)) + return FALSE; + return nmp_utils_ethtool_get_link_speed (ifname, out_speed); } @@ -4018,6 +4032,31 @@ log_ip6_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatform /******************************************************************/ +NMPNetns * +nm_platform_netns_get (NMPlatform *self) +{ + _CHECK_SELF (self, klass, NULL); + + return self->_netns; +} + +gboolean +nm_platform_netns_push (NMPlatform *platform, NMPNetns **netns) +{ + g_return_val_if_fail (NM_IS_PLATFORM (platform), FALSE); + + if ( platform->_netns + && !nmp_netns_push (platform->_netns)) { + NM_SET_OUT (netns, NULL); + return FALSE; + } + + NM_SET_OUT (netns, platform->_netns); + return TRUE; +} + +/******************************************************************/ + static gboolean _vtr_v4_route_add (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route, gint64 metric) { @@ -4117,9 +4156,20 @@ static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { - NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE (object); + NMPlatform *self = NM_PLATFORM (object); + NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE (self); switch (prop_id) { + case PROP_NETNS_SUPPORT: + /* construct-only */ + if (g_value_get_boolean (value)) { + NMPNetns *netns; + + netns = nmp_netns_get_current (); + if (netns) + self->_netns = g_object_ref (netns); + } + break; case PROP_REGISTER_SINGLETON: /* construct-only */ priv->register_singleton = g_value_get_boolean (value); @@ -4148,6 +4198,14 @@ nm_platform_init (NMPlatform *object) } static void +finalize (GObject *object) +{ + NMPlatform *self = NM_PLATFORM (object); + + g_clear_object (&self->_netns); +} + +static void nm_platform_class_init (NMPlatformClass *platform_class) { GObjectClass *object_class = G_OBJECT_CLASS (platform_class); @@ -4156,10 +4214,19 @@ nm_platform_class_init (NMPlatformClass *platform_class) object_class->set_property = set_property; object_class->constructed = constructed; + object_class->finalize = finalize; platform_class->wifi_set_powersave = wifi_set_powersave; g_object_class_install_property + (object_class, PROP_NETNS_SUPPORT, + g_param_spec_boolean (NM_PLATFORM_NETNS_SUPPORT, "", "", + FALSE, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_REGISTER_SINGLETON, g_param_spec_boolean (NM_PLATFORM_REGISTER_SINGLETON, "", "", FALSE, diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h index eadc378658..56a0110b5d 100644 --- a/src/platform/nm-platform.h +++ b/src/platform/nm-platform.h @@ -42,6 +42,7 @@ /******************************************************************/ +#define NM_PLATFORM_NETNS_SUPPORT "netns-support" #define NM_PLATFORM_REGISTER_SINGLETON "register-singleton" /******************************************************************/ @@ -464,6 +465,8 @@ typedef struct { struct _NMPlatform { GObject parent; + + NMPNetns *_netns; }; typedef struct { @@ -671,6 +674,9 @@ _nm_platform_uint8_inv (guint8 scope) return (guint8) ~scope; } +NMPNetns *nm_platform_netns_get (NMPlatform *self); +gboolean nm_platform_netns_push (NMPlatform *platform, NMPNetns **netns); + const char *nm_link_type_to_string (NMLinkType link_type); const char *_nm_platform_error_to_string (NMPlatformError error); diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c new file mode 100644 index 0000000000..f153e9e62f --- /dev/null +++ b/src/platform/nmp-netns.c @@ -0,0 +1,495 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* nm-platform.c - Handle runtime kernel networking configuration + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "nm-default.h" +#include "nmp-netns.h" + +#include <fcntl.h> +#include <errno.h> +#include <sys/mount.h> + +#include "NetworkManagerUtils.h" + +/*********************************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG_PREFIX_NAME "netns" +#define _NMLOG(level, netns, ...) \ + G_STMT_START { \ + NMLogLevel _level = (level); \ + \ + if (nm_logging_enabled (_level, _NMLOG_DOMAIN)) { \ + NMPNetns *_netns = (netns); \ + char _sbuf[20]; \ + \ + _nm_log (_level, _NMLOG_DOMAIN, 0, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (_netns ? nm_sprintf_buf (_sbuf, "[%p]", _netns) : "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } G_STMT_END + +/*********************************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE ( + PROP_FD_NET, + PROP_FD_MNT, +); + +typedef struct _NMPNetnsPrivate NMPNetnsPrivate; + +struct _NMPNetnsPrivate { + int fd_net; + int fd_mnt; +}; + +typedef struct { + NMPNetns *netns; + int count; +} NetnsInfo; + +static void _stack_push (NMPNetns *netns); +static NMPNetns *_netns_new (GError **error); + +/*********************************************************************************************/ + +static GArray *netns_stack = NULL; + +static void +_stack_ensure_init_impl (void) +{ + NMPNetns *netns; + GError *error = NULL; + + nm_assert (!netns_stack); + + netns_stack = g_array_new (FALSE, FALSE, sizeof (NetnsInfo)); + + /* at the bottom of the stack we must try to create a netns instance + * that we never pop. It's the base to which we need to return. */ + + netns = _netns_new (&error); + + if (!netns) { + /* don't know how to recover from this error. Netns are not supported. */ + _LOGE (NULL, "failed to create initial netns: %s", error->message); + g_clear_error (&error); + return; + } + + _stack_push (netns); + + /* we leak this instance inside netns_stack. It cannot be popped. */ + g_object_unref (netns); +} +#define _stack_ensure_init() \ + G_STMT_START { \ + if (G_UNLIKELY (!netns_stack)) { \ + _stack_ensure_init_impl (); \ + } \ + } G_STMT_END + +static NetnsInfo * +_stack_peek (void) +{ + nm_assert (netns_stack); + + if (netns_stack->len > 0) + return &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1)); + return NULL; +} + +static NetnsInfo * +_stack_peek2 (void) +{ + nm_assert (netns_stack); + + if (netns_stack->len > 1) + return &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 2)); + return NULL; +} + +static NetnsInfo * +_stack_bottom (void) +{ + nm_assert (netns_stack); + + if (netns_stack->len > 0) + return &g_array_index (netns_stack, NetnsInfo, 0); + return NULL; +} + +static void +_stack_push (NMPNetns *netns) +{ + NetnsInfo *info; + + nm_assert (netns_stack); + nm_assert (NMP_IS_NETNS (netns)); + + g_array_set_size (netns_stack, netns_stack->len + 1); + + info = &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1)); + info->netns = g_object_ref (netns); + info->count = 1; +} + +static void +_stack_pop (void) +{ + NetnsInfo *info; + + nm_assert (netns_stack); + nm_assert (netns_stack->len > 1); + + info = &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1)); + + nm_assert (NMP_IS_NETNS (info->netns)); + nm_assert (info->count == 1); + + g_object_unref (info->netns); + + g_array_set_size (netns_stack, netns_stack->len - 1); +} + +static guint +_stack_size (void) +{ + nm_assert (netns_stack); + + return netns_stack->len; +} + +/*********************************************************************************************/ + +G_DEFINE_TYPE (NMPNetns, nmp_netns, G_TYPE_OBJECT); + +#define NMP_NETNS_GET_PRIVATE(o) ((o)->priv) + +/*********************************************************************************************/ + +static NMPNetns * +_netns_new (GError **error) +{ + NMPNetns *self; + int fd_net, fd_mnt; + int errsv; + + fd_net = open ("/proc/self/ns/net", O_RDONLY); + if (fd_net == -1) { + errsv = errno; + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + "Failed opening netns: %s", + g_strerror (errsv)); + return NULL; + } + + fd_mnt = open ("/proc/self/ns/mnt", O_RDONLY); + if (fd_mnt == -1) { + errsv = errno; + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + "Failed opening mntns: %s", + g_strerror (errsv)); + close (fd_net); + return NULL; + } + + self = g_object_new (NMP_TYPE_NETNS, + NMP_NETNS_FD_NET, fd_net, + NMP_NETNS_FD_MNT, fd_mnt, + NULL); + + _LOGD (self, "new netns (net:%d, mnt:%d)", fd_net, fd_mnt); + + return self; +} + +static gboolean +_netns_switch (NMPNetns *self, NMPNetns *netns_fail) +{ + int errsv; + + if (setns (self->priv->fd_net, CLONE_NEWNET) != 0) { + errsv = errno; + _LOGE (self, "failed to switch netns: %s", g_strerror (errsv)); + return FALSE; + } + if (setns (self->priv->fd_mnt, CLONE_NEWNS) != 0) { + errsv = errno; + _LOGE (self, "failed to switch mntns: %s", g_strerror (errsv)); + + /* try to fix the mess by returning to the previous netns. */ + if (netns_fail) { + if (setns (netns_fail->priv->fd_net, CLONE_NEWNET) != 0) { + errsv = errno; + _LOGE (netns_fail, "failed to restore netns: %s", g_strerror (errsv)); + } + } + return FALSE; + } + + return TRUE; +} + +/*********************************************************************************************/ + +int +nmp_netns_get_fd_net (NMPNetns *self) +{ + g_return_val_if_fail (NMP_IS_NETNS (self), 0); + + return self->priv->fd_net; +} + +int +nmp_netns_get_fd_mnt (NMPNetns *self) +{ + g_return_val_if_fail (NMP_IS_NETNS (self), 0); + + return self->priv->fd_mnt; +} + +/*********************************************************************************************/ + +gboolean +nmp_netns_push (NMPNetns *self) +{ + NetnsInfo *info; + + g_return_val_if_fail (NMP_IS_NETNS (self), FALSE); + + _stack_ensure_init (); + + info = _stack_peek (); + g_return_val_if_fail (info, FALSE); + + if (info->netns == self) { + info->count++; + _LOGt (self, "push (increase count to %d)", info->count); + return TRUE; + } + + _LOGD (self, "push (was %p)", info->netns); + + if (!_netns_switch (self, info->netns)) + return FALSE; + + _stack_push (self); + return TRUE; +} + +NMPNetns * +nmp_netns_new (void) +{ + NetnsInfo *info; + NMPNetns *self; + int errsv; + GError *error = NULL; + + _stack_ensure_init (); + + if (!_stack_peek ()) { + /* there are no netns instances. We cannot create a new one + * (because after unshare we couldn't return to the original one). */ + return NULL; + } + + if (unshare (CLONE_NEWNET) != 0) { + errsv = errno; + _LOGE (NULL, "failed to create netns: %s", g_strerror (errsv)); + return NULL; + } + + if (unshare (CLONE_NEWNS) != 0) { + errsv = errno; + _LOGE (NULL, "failed to create ns: %s", g_strerror (errsv)); + goto err_out; + } + + if (mount ("", "/", "none", MS_SLAVE | MS_REC, NULL)) { + _LOGE (NULL, "failed mount --make-rslave: %s", error->message); + goto err_out; + } + + if (umount2 ("/sys", MNT_DETACH) < 0) { + _LOGE (NULL, "failed umount /sys: %s", error->message); + goto err_out; + } + + if (mount ("sysfs", "/sys", "sysfs", 0, NULL) < 0) { + _LOGE (NULL, "failed mount /sys: %s", error->message); + goto err_out; + } + + self = _netns_new (&error); + if (!self) { + _LOGE (NULL, "failed to create netns after unshare: %s", error->message); + g_clear_error (&error); + goto err_out; + } + + _stack_push (self); + + return self; +err_out: + info = _stack_peek (); + _netns_switch (info->netns, NULL); + return NULL; +} + +gboolean +nmp_netns_pop (NMPNetns *self) +{ + NetnsInfo *info; + + g_return_val_if_fail (NMP_IS_NETNS (self), FALSE); + + _stack_ensure_init (); + + info = _stack_peek (); + + g_return_val_if_fail (info, FALSE); + g_return_val_if_fail (info->netns == self, FALSE); + + if (info->count > 1) { + info->count--; + _LOGt (self, "pop (decrease count to %d)", info->count); + return TRUE; + } + g_return_val_if_fail (info->count == 1, FALSE); + + /* cannot pop the original netns. */ + g_return_val_if_fail (_stack_size () > 1, FALSE); + + _LOGD (self, "pop (restore %p)", _stack_peek2 ()); + + _stack_pop (); + info = _stack_peek (); + + nm_assert (info); + + return _netns_switch (info->netns, NULL); +} + +NMPNetns * +nmp_netns_get_current (void) +{ + NetnsInfo *info; + + _stack_ensure_init (); + + info = _stack_peek (); + return info ? info->netns : NULL; +} + +NMPNetns * +nmp_netns_get_initial (void) +{ + NetnsInfo *info; + + _stack_ensure_init (); + + info = _stack_bottom (); + return info ? info->netns : NULL; +} + +gboolean +nmp_netns_is_initial (void) +{ + if (G_UNLIKELY (!netns_stack)) + return TRUE; + + return nmp_netns_get_current () == nmp_netns_get_initial (); +} + +/*********************************************************************************************/ + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMPNetns *self = NMP_NETNS (object); + + switch (prop_id) { + case PROP_FD_NET: + /* construct only */ + self->priv->fd_net = g_value_get_int (value); + g_return_if_fail (self->priv->fd_net > 0); + break; + case PROP_FD_MNT: + /* construct only */ + self->priv->fd_mnt = g_value_get_int (value); + g_return_if_fail (self->priv->fd_mnt > 0); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nmp_netns_init (NMPNetns *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NMP_TYPE_NETNS, NMPNetnsPrivate); +} + +static void +dispose (GObject *object) +{ + NMPNetns *self = NMP_NETNS (object); + + if (self->priv->fd_net > 0) { + close (self->priv->fd_net); + self->priv->fd_net = 0; + } + + if (self->priv->fd_mnt > 0) { + close (self->priv->fd_mnt); + self->priv->fd_mnt = 0; + } + + G_OBJECT_CLASS (nmp_netns_parent_class)->dispose (object); +} + +static void +nmp_netns_class_init (NMPNetnsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMPNetnsPrivate)); + + object_class->set_property = set_property; + object_class->dispose = dispose; + + obj_properties[PROP_FD_NET] + = g_param_spec_int (NMP_NETNS_FD_NET, "", "", + 0, G_MAXINT, 0, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + obj_properties[PROP_FD_MNT] + = g_param_spec_int (NMP_NETNS_FD_MNT, "", "", + 0, G_MAXINT, 0, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/platform/nmp-netns.h b/src/platform/nmp-netns.h new file mode 100644 index 0000000000..bdd797daca --- /dev/null +++ b/src/platform/nmp-netns.h @@ -0,0 +1,70 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* nm-platform.c - Handle runtime kernel networking configuration + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2016 Red Hat, Inc. + */ + +#ifndef __NMP_NETNS_UTILS_H__ +#define __NMP_NETNS_UTILS_H__ + +/*****************************************************************************/ + +#define NMP_TYPE_NETNS (nmp_netns_get_type ()) +#define NMP_NETNS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMP_TYPE_NETNS, NMPNetns)) +#define NMP_NETNS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NMP_TYPE_NETNS, NMPNetnsClass)) +#define NMP_IS_NETNS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMP_TYPE_NETNS)) +#define NMP_IS_NETNS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMP_TYPE_NETNS)) +#define NMP_NETNS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NMP_TYPE_NETNS, NMPNetnsClass)) + +#define NMP_NETNS_FD_NET "fd-net" +#define NMP_NETNS_FD_MNT "fd-mnt" + +struct _NMPNetnsPrivate; + +struct _NMPNetns { + GObject parent; + struct _NMPNetnsPrivate *priv; +}; + +typedef struct { + GObjectClass parent; +} NMPNetnsClass; + +GType nmp_netns_get_type (void); + +NMPNetns *nmp_netns_new (void); + +gboolean nmp_netns_push (NMPNetns *self); +gboolean nmp_netns_pop (NMPNetns *self); + +NMPNetns *nmp_netns_get_current (void); +NMPNetns *nmp_netns_get_initial (void); +gboolean nmp_netns_is_initial (void); + +int nmp_netns_get_fd_net (NMPNetns *self); +int nmp_netns_get_fd_mnt (NMPNetns *self); + +static inline void +_nm_auto_pop_netns (NMPNetns **p) +{ + if (*p) + nmp_netns_pop (*p); +} + +#define nm_auto_pop_netns __attribute__((cleanup(_nm_auto_pop_netns))) + +#endif /* __NMP_NETNS_UTILS_H__ */ diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c index 3fa5ad5001..c136bbc640 100644 --- a/src/platform/tests/test-link.c +++ b/src/platform/tests/test-link.c @@ -23,6 +23,8 @@ #include <sched.h> #include "nmp-object.h" +#include "nmp-netns.h" +#include "nm-platform-utils.h" #include "test-common.h" #include "nm-test-utils.h" @@ -1846,6 +1848,138 @@ again: nmtstp_link_del (-1, ifindex_dummy0, IFACE_DUMMY0); } +/******************************************************************/ + +static void +test_netns_general_setup (gpointer fixture, gconstpointer test_data) +{ + /* the singleton platform instance has netns support disabled. + * Destroy the instance before the test and re-create it afterwards. */ + g_object_unref (nm_platform_get ()); +} + +static void +test_netns_general_teardown (gpointer fixture, gconstpointer test_data) +{ + /* re-create platform instance */ + SETUP (); +} + +static void +test_netns_general (gpointer fixture, gconstpointer test_data) +{ + gs_unref_object NMPlatform *platform_1 = NULL; + gs_unref_object NMPlatform *platform_2 = NULL; + gs_unref_object NMPNetns *netns_2 = NULL; + NMPNetns *netns_tmp; + char sbuf[100]; + int i, j, k, errsv; + gboolean ethtool_support; + + netns_tmp = nmp_netns_get_current (); + if (!netns_tmp) { + g_test_skip ("No netns support"); + return; + } + + g_assert (nmp_netns_get_fd_net (netns_tmp) > 0); + if (setns (nmp_netns_get_fd_net (netns_tmp), CLONE_NEWNET) != 0) { + errsv = errno; + _LOGD ("setns() failed with \"%s\". This indicates missing support (valgrind?)", g_strerror (errsv)); + g_test_skip ("No netns support (setns failed)"); + return; + } + + platform_1 = g_object_new (NM_TYPE_LINUX_PLATFORM, NM_PLATFORM_NETNS_SUPPORT, TRUE, NULL); + + netns_2 = nmp_netns_new (); + platform_2 = g_object_new (NM_TYPE_LINUX_PLATFORM, NM_PLATFORM_NETNS_SUPPORT, TRUE, NULL); + nmp_netns_pop (netns_2); + + /* add some dummy devices. The "other-*" devices are there to bump the ifindex */ + for (k = 0; k < 2; k++) { + NMPlatform *p = (k == 0 ? platform_1 : platform_2); + const char *id = (k == 0 ? "a" : "b"); + +#define _ADD_DUMMY(platform, name) \ + g_assert_cmpint (nm_platform_link_dummy_add ((platform), (name), NULL), ==, NM_PLATFORM_ERROR_SUCCESS) + + for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++) + _ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-a-%s-%02d", id, i)); + + _ADD_DUMMY (p, "dummy1_"); + + for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++) + _ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-b-%s-%02d", id, i)); + + _ADD_DUMMY (p, nm_sprintf_buf (sbuf, "dummy2%s", id)); + + for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++) + _ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-c-%s-%02d", id, i)); + +#undef _ADD_DUMMY + } + + g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy1_/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nm_platform_link_get_by_ifname (platform_1, "dummy1_")->ifindex)); + g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy2a/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nm_platform_link_get_by_ifname (platform_1, "dummy2a")->ifindex)); + g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy2b/ifindex"), ==, NULL); + + g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy1_/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nm_platform_link_get_by_ifname (platform_2, "dummy1_")->ifindex)); + g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy2a/ifindex"), ==, NULL); + g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy2b/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nm_platform_link_get_by_ifname (platform_2, "dummy2b")->ifindex)); + + for (i = 0; i < 10; i++) { + NMPlatform *pl; + const char *path; + + j = nmtst_get_rand_int () % 2; + + if (nmtst_get_rand_int () % 2) { + pl = platform_1; + if (nmtst_get_rand_int () % 2) + path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6"; + else + path = "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6"; + } else { + pl = platform_2; + if (nmtst_get_rand_int () % 2) + path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6"; + else + path = "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6"; + } + g_assert (nm_platform_sysctl_set (pl, path, nm_sprintf_buf (sbuf, "%d", j))); + g_assert_cmpstr (nm_platform_sysctl_get (pl, path), ==, nm_sprintf_buf (sbuf, "%d", j)); + } + g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6"), ==, NULL); + g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6"), ==, NULL); + + /* older kernels (Ubuntu 12.04) don't support ethtool -i for dummy devices. Work around that and + * skip asserts that are known to fail. */ + ethtool_support = nmtstp_run_command ("ethtool -i dummy1_ > /dev/null") == 0; + if (ethtool_support) { + g_assert ( nmp_utils_ethtool_get_driver_info ("dummy1_", NULL, NULL, NULL)); + g_assert ( nmp_utils_ethtool_get_driver_info ("dummy2a", NULL, NULL, NULL)); + g_assert (!nmp_utils_ethtool_get_driver_info ("dummy2b", NULL, NULL, NULL)); + g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy1_ > /dev/null"), ==, 0); + g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy2a > /dev/null"), ==, 0); + g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy2b 2> /dev/null"), !=, 0); + } + + g_assert (nm_platform_netns_push (platform_2, &netns_tmp)); + g_assert (netns_tmp == netns_2); + + if (ethtool_support) { + g_assert ( nmp_utils_ethtool_get_driver_info ("dummy1_", NULL, NULL, NULL)); + g_assert (!nmp_utils_ethtool_get_driver_info ("dummy2a", NULL, NULL, NULL)); + g_assert ( nmp_utils_ethtool_get_driver_info ("dummy2b", NULL, NULL, NULL)); + g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy1_ > /dev/null"), ==, 0); + g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy2a 2> /dev/null"), !=, 0); + g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy2b > /dev/null"), ==, 0); + } + + nmp_netns_pop (netns_tmp); +} + /*****************************************************************************/ void @@ -1894,5 +2028,7 @@ setup_tests (void) g_test_add_func ("/link/nl-bugs/veth", test_nl_bugs_veth); g_test_add_func ("/link/nl-bugs/spurious-newlink", test_nl_bugs_spuroius_newlink); g_test_add_func ("/link/nl-bugs/spurious-dellink", test_nl_bugs_spuroius_dellink); + + g_test_add_vtable ("/general/netns/general", 0, NULL, test_netns_general_setup, test_netns_general, test_netns_general_teardown); } } diff --git a/tools/run-test-valgrind.sh b/tools/run-test-valgrind.sh index f2eeefcd02..d3dd915bf0 100755 --- a/tools/run-test-valgrind.sh +++ b/tools/run-test-valgrind.sh @@ -140,6 +140,14 @@ if [ $RESULT -ne 0 -a $RESULT -ne 77 ]; then fi if [ $HAS_ERRORS -eq 0 ]; then + # valgrind doesn't support setns syscall and spams the logfile. + # hack around it... + if [ "$(basename "$TEST")" = 'test-link-linux' -a -z "$(sed -e '/^--[0-9]\+-- WARNING: unhandled .* syscall: /,/^--[0-9]\+-- it at http.*\.$/d' "$LOGFILE")" ]; then + HAS_ERRORS=1 + fi +fi + +if [ $HAS_ERRORS -eq 0 ]; then # shouldn't actually happen... echo "valgrind succeeded, but log is not empty: '`realpath "$LOGFILE"`'" >&2 exit 1 |