NetworkManager/libnm/nm-libnm-utils.h

238 lines
7.3 KiB
C
Raw Normal View History

// SPDX-License-Identifier: LGPL-2.1+
/*
* Copyright (C) 2017, 2018 Red Hat, Inc.
*/
#ifndef __NM_LIBNM_UTILS_H__
#define __NM_LIBNM_UTILS_H__
#include "nm-types.h"
#include "nm-glib-aux/nm-ref-string.h"
/*****************************************************************************/
libnm: deprecate synchronous/blocking API in libnm Note that D-Bus is fundamentally asynchronous. Doing blocking calls on top of D-Bus is odd, especially for libnm's NMClient. That is because NMClient essentially is a client-side cache of the objects from the D-Bus interface. This cache should be filled exclusively by (asynchronous) D-Bus events (PropertiesChanged). So, making a blocking D-Bus call means to wait for a response and return it, while queuing all messages that are received in the meantime. Basically there are three ways how a synchronous API on NMClient could behave: 1) the call just calls g_dbus_connection_call_sync(). This means that libnm sends a D-Bus request via GDBusConnection, and blockingly waits for the response. All D-Bus messages that get received in the meantime are queued in the GMainContext that belongs to NMClient. That means, none of these D-Bus events are processed until we iterate the GMainContext after the call returns. The effect is, that NMClient (and all cached objects in there) are unaffected by the D-Bus request. Most of the synchronous API calls in libnm are of this kind. The problem is that the strict ordering of D-Bus events gets violated. For some API this is not an immediate problem. Take for example nm_device_wifi_request_scan(). The call merely blockingly tells NetworkManager to start scanning, but since NetworkManager's D-Bus API does not directly expose any state that tells whether we are currently scanning, this out of order processing of the D-Bus request is a small issue. The problem is more obvious for nm_client_networking_set_enabled(). After calling it, NM_CLIENT_NETWORKING_ENABLED is still unaffected and unchanged, because the PropertiesChanged signal from D-Bus is not yet processed. This means, while you make such a blocking call, NMClient's state does not change. But usually you perform the synchronous call to change some state. In this form, the blocking call is not useful, because NMClient only changes the state after iterating the GMainContext, and not after the blocking call returns. 2) like 1), but after making the blocking g_dbus_connection_call_sync(), update the NMClient cache artificially. This is what nm_manager_check_connectivity() does, to "fix" bgo#784629. This also has the problem of out-of-order events, but it kinda solves the problem of not changing the state during the blocking call. But it does so by hacking the state of the cache. I think this is really wrong because the state should only be updated from the ordered stream of D-Bus messages (PropertiesChanged signal and similar). When libnm decides to modify the state, there may be already D-Bus messages queued that affect this very state. 3) instead of calling g_dbus_connection_call_sync(), use the asynchronous g_dbus_connection_call(). If we would use a sepaate GMainContext for all D-Bus related calls, we could ensure that while we block for the response, we iterate that internal main context. This might be nice, because all events are processed in order and after the blocking call returns, the NMClient state is up to date. The are problems however: current blocking API does not do this, so it's a significant change in behavior. Also, it might be unexpected to the user that during the blocking call the entire content of NMClient's cache might change and all pointers to the cache might be invalidated. Also, of course NMClient would invoke signals for all the changes that happen. Another problem is that this would be more effort to implement and it involves a small performance overhead for all D-Bus related calls (because we have to serialize all events in an internal GMainContext first and then invoke them on the caller's context). Also, if the users wants this behavior, they could implement it themself by running libnm in their own GMainContext. Note that libnm might have bugs to make that really working, but that should be fixed instead of adding such synchrnous API behavior. Read also [1], for why blocking calls are wrong. [1] https://smcv.pseudorandom.co.uk/2008/11/nonblocking/ So, all possible behaviors for synchronous API have severe behavioural issues. Mark all this API as deprecated. Also, this serves the purpose of identifying blocking D-Bus calls in libnm. Note that "deprecated" here does not really mean that the API is going to be removed. We don't break API. The user may: - continue to use this API. It's deprecated, awkward and discouraged, but if it works, by all means use it. - use asynchronous API. That's the only sensible way to use D-Bus. If libnm lacks a certain asynchronous counterpart, it should be added. - use GDBusConnection directly. There really isn't anything wrong with D-Bus or GDBusConnection. This deprecated API is just a wrapper around g_dbus_connection_call_sync(). You may call it directly without feeling dirty. --- The only other remainging API is the synchronous GInitable call for NMClient. That is an entirely separate beast and not particularly wrong (from an API point of view). Note that synchronous API in NMSecretAgentOld, NMVpnPluginOld and NMVpnServicePlugin as not deprecated here. These types are not part of the D-Bus cache and while they have similar issues, it's less severe because they have less state.
2019-09-04 13:58:43 +02:00
/* Markers for deprecated sync code in internal API. */
#define _NM_DEPRECATED_SYNC_METHOD_INTERNAL NM_DEPRECATED_IN_1_22
#define _NM_DEPRECATED_SYNC_WRITABLE_PROPERTY_INTERNAL NM_DEPRECATED_IN_1_22
libnm: add logging NML_DBUS_LOG*() for debugging D-Bus for NMClient Commonly, a library (like libnm) is not supposed to log anything. Logging is not a suitable way to notify the calling application about anything. When something of importance happens, then the application must be notified via the library's API. However, logging can be very useful for debugging to see what is going on. Add a logging macro that by default does nothing, but can be turned on via an environment variable "LIBNM_CLIENT_DEBUG=debug". Another point is that libnm relies on the server side NetworkManager D-Bus interface to be in an expected manner. For example, we require a D-Bus object "org.freedesktop.NetworkManager" to be present and certain D-Bus interfaces implemented. However libnm should treat NetworkManager as external and untrusted component. That means, we cannot assert against the expectations we have. There are two reasons for this: - a bug in NetworkManager, dbus-daemon or else may cause such errors. This must not trigger an assertion failure in the client application, at least not unless requested. - libnm must be forward and backward compatible against a different NetworkManager server version. That is only possibly by ignoring anything that is unexpected. Asserting by default might prevent to implement API changes, both on libnm and server side. Note that we also don't notify the calling application via dedicated API. On the one hand, these things *can* happen. On the other hand, what would the calling appication do about it anyway? libnm by default must just behave gracefully and pretend all is good. For testing, development and debugging that is however not useful. We want the user to opt in to strict API validation. The user will be able to do that by setting "LIBNM_CLIENT_DEBUG=warning", which causes API violations being logged with g_warning(). These are assertions when running with G_DEBUG=fatal-warnings. This is inspired by GDBus' G_DBUS_DEBUG variable. Note that LIBNM_CLIENT_DEBUG environment variables is undocumented, unstable API. It's used for debugging and testing of the current libnm version at hand. There is no guaranteed stable behavior how a different libnm version might behave.
2019-10-14 08:06:33 +02:00
/*****************************************************************************/
typedef enum {
_NML_DBUS_LOG_LEVEL_INITIALIZED = 0x01,
_NML_DBUS_LOG_LEVEL_TRACE = 0x02,
_NML_DBUS_LOG_LEVEL_DEBUG = 0x04,
/* the difference between a warning and a critical is that it results in
* g_warning() vs. g_critical() messages. Note that we want to use "warnings"
* for unknown D-Bus API that could just result because we run against a
* newer NetworkManager version (such warnings are more graceful, because
* we want that libnm can be forward compatible against newer servers).
* Critial warnings should be emitted when NetworkManager exposes something
* on D-Bus that breaks the current expectations. Usually NetworkManager
* should not break API, hence such issues are more severe. */
_NML_DBUS_LOG_LEVEL_WARN = 0x08,
_NML_DBUS_LOG_LEVEL_ERROR = 0x10,
/* ANY is only relevant for nml_dbus_log_enabled() to check whether any of the
* options is on. */
NML_DBUS_LOG_LEVEL_ANY = _NML_DBUS_LOG_LEVEL_INITIALIZED,
NML_DBUS_LOG_LEVEL_TRACE = _NML_DBUS_LOG_LEVEL_TRACE,
NML_DBUS_LOG_LEVEL_DEBUG = _NML_DBUS_LOG_LEVEL_DEBUG
| NML_DBUS_LOG_LEVEL_TRACE,
NML_DBUS_LOG_LEVEL_WARN = _NML_DBUS_LOG_LEVEL_WARN
| NML_DBUS_LOG_LEVEL_DEBUG,
NML_DBUS_LOG_LEVEL_ERROR = _NML_DBUS_LOG_LEVEL_ERROR
| NML_DBUS_LOG_LEVEL_WARN,
} NMLDBusLogLevel;
extern volatile int _nml_dbus_log_level;
int _nml_dbus_log_level_init (void);
static inline gboolean
nml_dbus_log_enabled (NMLDBusLogLevel level)
{
int l;
nm_assert (NM_IN_SET (level, NML_DBUS_LOG_LEVEL_ANY,
NML_DBUS_LOG_LEVEL_TRACE,
NML_DBUS_LOG_LEVEL_DEBUG,
NML_DBUS_LOG_LEVEL_WARN,
NML_DBUS_LOG_LEVEL_ERROR));
l = g_atomic_int_get (&_nml_dbus_log_level);
if (G_UNLIKELY (l == 0))
l = _nml_dbus_log_level_init ();
nm_assert (l & _NML_DBUS_LOG_LEVEL_INITIALIZED);
if (level == NML_DBUS_LOG_LEVEL_ANY)
return l != _NML_DBUS_LOG_LEVEL_INITIALIZED;
return !!(((NMLDBusLogLevel) l) & level);
}
void _nml_dbus_log (NMLDBusLogLevel level,
const char *fmt,
...) _nm_printf (2, 3);
#define NML_DBUS_LOG(level, ...) \
G_STMT_START { \
G_STATIC_ASSERT ( (level) == NML_DBUS_LOG_LEVEL_TRACE \
|| (level) == NML_DBUS_LOG_LEVEL_DEBUG \
|| (level) == NML_DBUS_LOG_LEVEL_WARN \
|| (level) == NML_DBUS_LOG_LEVEL_ERROR); \
\
if (nml_dbus_log_enabled (level)) { \
_nml_dbus_log ((level), __VA_ARGS__); \
} \
} G_STMT_END
#define NML_DBUS_LOG_T(...) NML_DBUS_LOG (NML_DBUS_LOG_LEVEL_TRACE, __VA_ARGS__)
#define NML_DBUS_LOG_D(...) NML_DBUS_LOG (NML_DBUS_LOG_LEVEL_DEBUG, __VA_ARGS__)
#define NML_DBUS_LOG_W(...) NML_DBUS_LOG (NML_DBUS_LOG_LEVEL_WARN, __VA_ARGS__)
#define NML_DBUS_LOG_E(...) NML_DBUS_LOG (NML_DBUS_LOG_LEVEL_ERROR, __VA_ARGS__)
#define NML_NMCLIENT_LOG(level, self, ...) \
NML_DBUS_LOG ((level), \
"nmclient["NM_HASH_OBFUSCATE_PTR_FMT"]: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
NM_HASH_OBFUSCATE_PTR (self) \
_NM_UTILS_MACRO_REST (__VA_ARGS__))
#define NML_NMCLIENT_LOG_T(self, ...) NML_NMCLIENT_LOG (NML_DBUS_LOG_LEVEL_TRACE, self, __VA_ARGS__)
#define NML_NMCLIENT_LOG_D(self, ...) NML_NMCLIENT_LOG (NML_DBUS_LOG_LEVEL_DEBUG, self, __VA_ARGS__)
#define NML_NMCLIENT_LOG_W(self, ...) NML_NMCLIENT_LOG (NML_DBUS_LOG_LEVEL_WARN, self, __VA_ARGS__)
#define NML_NMCLIENT_LOG_E(self, ...) NML_NMCLIENT_LOG (NML_DBUS_LOG_LEVEL_ERROR, self, __VA_ARGS__)
/*****************************************************************************/
static inline const char *
_nml_coerce_property_str_not_null (const char *str)
{
return str ?: "";
}
static inline const char *
_nml_coerce_property_str_not_empty (const char *str)
{
return str && str[0] ? str : NULL;
}
static inline const char *
_nml_coerce_property_object_path (NMRefString *path)
{
if (!path)
return NULL;
2019-10-24 12:29:21 +02:00
return nm_dbus_path_not_empty (path->str);
}
static inline const char *const*
_nml_coerce_property_strv_not_null (char **strv)
{
return ((const char *const*) strv) ?: NM_PTRARRAY_EMPTY (const char *);
}
/*****************************************************************************/
char *nm_utils_wincaps_to_dash (const char *caps);
/*****************************************************************************/
char *nm_utils_fixup_vendor_string (const char *desc);
char *nm_utils_fixup_product_string (const char *desc);
/*****************************************************************************/
struct _NMObjectPrivate;
struct _NMObject {
GObject parent;
struct _NMObjectPrivate *_priv;
};
struct _NMObjectClass {
GObjectClass parent;
void (*init_dbus) (struct _NMObject *object);
/* The "object-creation-failed" method is PRIVATE for libnm and
* is not meant for any external usage. It indicates that an error
* occurred during creation of an object.
*/
void (*object_creation_failed) (struct _NMObject *master_object,
const char *failed_path);
};
/*****************************************************************************/
struct _NMDevicePrivate;
struct _NMDevice {
NMObject parent;
struct _NMDevicePrivate *_priv;
};
struct _NMDeviceClass {
struct _NMObjectClass parent;
/* Signals */
void (*state_changed) (NMDevice *device,
NMDeviceState new_state,
NMDeviceState old_state,
NMDeviceStateReason reason);
/* Methods */
gboolean (*connection_compatible) (NMDevice *device,
NMConnection *connection,
GError **error);
const char * (*get_type_description) (NMDevice *device);
const char * (*get_hw_address) (NMDevice *device);
GType (*get_setting_type) (NMDevice *device);
};
/*****************************************************************************/
struct _NMActiveConnectionPrivate;
struct _NMActiveConnection {
NMObject parent;
struct _NMActiveConnectionPrivate *_priv;
};
struct _NMActiveConnectionClass {
struct _NMObjectClass parent;
};
/*****************************************************************************/
struct _NMDhcpConfigPrivate;
struct _NMDhcpConfig {
NMObject parent;
struct _NMDhcpConfigPrivate *_priv;
};
struct _NMDhcpConfigClass {
struct _NMObjectClass parent;
};
/*****************************************************************************/
struct _NMIPConfigPrivate;
struct _NMIPConfig {
NMObject parent;
struct _NMIPConfigPrivate *_priv;
};
struct _NMIPConfigClass {
struct _NMObjectClass parent;
};
/*****************************************************************************/
#endif /* __NM_LIBNM_UTILS_H__ */