
the patch below adds the possibility to pass user data to callbacks of type change_func_t when using the nl_cache_mngr_* family of functions. If there is any better way to do this, without duplicating the code in cache_mngr.c please let me know.
383 lines
10 KiB
C
383 lines
10 KiB
C
/*
|
|
* lib/cache_mngr.c Cache Manager
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation version 2.1
|
|
* of the License.
|
|
*
|
|
* Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
|
|
*/
|
|
|
|
/**
|
|
* @ingroup cache_mngt
|
|
* @defgroup cache_mngr Manager
|
|
* @brief Helps keeping caches up to date.
|
|
*
|
|
* The purpose of a cache manager is to keep track of caches and
|
|
* automatically receive event notifications to keep the caches
|
|
* up to date with the kernel state. Each manager has exactly one
|
|
* netlink socket assigned which limits the scope of each manager
|
|
* to exactly one netlink family. Therefore all caches committed
|
|
* to a manager must be part of the same netlink family. Due to the
|
|
* nature of a manager, it is not possible to have a cache maintain
|
|
* two instances of the same cache type. The socket is subscribed
|
|
* to the event notification group of each cache and also put into
|
|
* non-blocking mode. Functions exist to poll() on the socket to
|
|
* wait for new events to be received.
|
|
*
|
|
* @code
|
|
* App libnl Kernel
|
|
* | |
|
|
* +-----------------+ [ notification, link change ]
|
|
* | | Cache Manager | | [ (IFF_UP | IFF_RUNNING) ]
|
|
* | | |
|
|
* | | +------------+| | | [ notification, new addr ]
|
|
* <-------|---| route/link |<-------(async)--+ [ 10.0.1.1/32 dev eth1 ]
|
|
* | | +------------+| | |
|
|
* | +------------+| |
|
|
* <---|---|---| route/addr |<------|-(async)--------------+
|
|
* | +------------+|
|
|
* | | +------------+| |
|
|
* <-------|---| ... ||
|
|
* | | +------------+| |
|
|
* +-----------------+
|
|
* | |
|
|
* @endcode
|
|
*
|
|
* @par 1) Creating a new cache manager
|
|
* @code
|
|
* struct nl_cache_mngr *mngr;
|
|
*
|
|
* // Allocate a new cache manager for RTNETLINK and automatically
|
|
* // provide the caches added to the manager.
|
|
* mngr = nl_cache_mngr_alloc(NETLINK_ROUTE, NL_AUTO_PROVIDE);
|
|
* @endcode
|
|
*
|
|
* @par 2) Keep track of a cache
|
|
* @code
|
|
* struct nl_cache *cache;
|
|
*
|
|
* // Create a new cache for links/interfaces and ask the manager to
|
|
* // keep it up to date for us. This will trigger a full dump request
|
|
* // to initially fill the cache.
|
|
* cache = nl_cache_mngr_add(mngr, "route/link");
|
|
* @endcode
|
|
*
|
|
* @par 3) Make the manager receive updates
|
|
* @code
|
|
* // Give the manager the ability to receive updates, will call poll()
|
|
* // with a timeout of 5 seconds.
|
|
* if (nl_cache_mngr_poll(mngr, 5000) > 0) {
|
|
* // Manager received at least one update, dump cache?
|
|
* nl_cache_dump(cache, ...);
|
|
* }
|
|
* @endcode
|
|
*
|
|
* @par 4) Release cache manager
|
|
* @code
|
|
* nl_cache_mngr_free(mngr);
|
|
* @endcode
|
|
* @{
|
|
*/
|
|
|
|
#include <netlink-local.h>
|
|
#include <netlink/netlink.h>
|
|
#include <netlink/cache.h>
|
|
#include <netlink/utils.h>
|
|
|
|
static int include_cb(struct nl_object *obj, struct nl_parser_param *p)
|
|
{
|
|
struct nl_cache_assoc *ca = p->pp_arg;
|
|
|
|
NL_DBG(2, "Including object %p into cache %p\n", obj, ca->ca_cache);
|
|
#ifdef NL_DEBUG
|
|
if (nl_debug >= 4)
|
|
nl_object_dump(obj, &nl_debug_dp);
|
|
#endif
|
|
return nl_cache_include(ca->ca_cache, obj, ca->ca_change, ca->ca_change_data);
|
|
}
|
|
|
|
static int event_input(struct nl_msg *msg, void *arg)
|
|
{
|
|
struct nl_cache_mngr *mngr = arg;
|
|
int protocol = nlmsg_get_proto(msg);
|
|
int type = nlmsg_hdr(msg)->nlmsg_type;
|
|
struct nl_cache_ops *ops;
|
|
int i, n;
|
|
struct nl_parser_param p = {
|
|
.pp_cb = include_cb,
|
|
};
|
|
|
|
NL_DBG(2, "Cache manager %p, handling new message %p as event\n",
|
|
mngr, msg);
|
|
#ifdef NL_DEBUG
|
|
if (nl_debug >= 4)
|
|
nl_msg_dump(msg, stderr);
|
|
#endif
|
|
|
|
if (mngr->cm_protocol != protocol)
|
|
BUG();
|
|
|
|
for (i = 0; i < mngr->cm_nassocs; i++) {
|
|
if (mngr->cm_assocs[i].ca_cache) {
|
|
ops = mngr->cm_assocs[i].ca_cache->c_ops;
|
|
for (n = 0; ops->co_msgtypes[n].mt_id >= 0; n++)
|
|
if (ops->co_msgtypes[n].mt_id == type)
|
|
goto found;
|
|
}
|
|
}
|
|
|
|
return NL_SKIP;
|
|
|
|
found:
|
|
NL_DBG(2, "Associated message %p to cache %p\n",
|
|
msg, mngr->cm_assocs[i].ca_cache);
|
|
p.pp_arg = &mngr->cm_assocs[i];
|
|
|
|
return nl_cache_parse(ops, NULL, nlmsg_hdr(msg), &p);
|
|
}
|
|
|
|
/**
|
|
* Allocate new cache manager
|
|
* @arg sk Netlink socket.
|
|
* @arg protocol Netlink Protocol this manager is used for
|
|
* @arg flags Flags
|
|
*
|
|
* @return Newly allocated cache manager or NULL on failure.
|
|
*/
|
|
int nl_cache_mngr_alloc(struct nl_sock *sk, int protocol, int flags,
|
|
struct nl_cache_mngr **result)
|
|
{
|
|
struct nl_cache_mngr *mngr;
|
|
int err = -NLE_NOMEM;
|
|
|
|
if (sk == NULL)
|
|
BUG();
|
|
|
|
mngr = calloc(1, sizeof(*mngr));
|
|
if (!mngr)
|
|
goto errout;
|
|
|
|
mngr->cm_handle = sk;
|
|
mngr->cm_nassocs = 32;
|
|
mngr->cm_protocol = protocol;
|
|
mngr->cm_flags = flags;
|
|
mngr->cm_assocs = calloc(mngr->cm_nassocs,
|
|
sizeof(struct nl_cache_assoc));
|
|
if (!mngr->cm_assocs)
|
|
goto errout;
|
|
|
|
nl_socket_modify_cb(mngr->cm_handle, NL_CB_VALID, NL_CB_CUSTOM,
|
|
event_input, mngr);
|
|
|
|
/* Required to receive async event notifications */
|
|
nl_socket_disable_seq_check(mngr->cm_handle);
|
|
|
|
if ((err = nl_connect(mngr->cm_handle, protocol) < 0))
|
|
goto errout;
|
|
|
|
if ((err = nl_socket_set_nonblocking(mngr->cm_handle) < 0))
|
|
goto errout;
|
|
|
|
NL_DBG(1, "Allocated cache manager %p, protocol %d, %d caches\n",
|
|
mngr, protocol, mngr->cm_nassocs);
|
|
|
|
*result = mngr;
|
|
return 0;
|
|
|
|
errout:
|
|
nl_cache_mngr_free(mngr);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Add cache responsibility to cache manager
|
|
* @arg mngr Cache manager.
|
|
* @arg name Name of cache to keep track of
|
|
* @arg cb Function to be called upon changes.
|
|
* @arg result Pointer to store added cache.
|
|
*
|
|
* Allocates a new cache of the specified type and adds it to the manager.
|
|
* The operation will trigger a full dump request from the kernel to
|
|
* initially fill the contents of the cache. The manager will subscribe
|
|
* to the notification group of the cache to keep track of any further
|
|
* changes.
|
|
*
|
|
* @return 0 on success or a negative error code.
|
|
*/
|
|
int nl_cache_mngr_add(struct nl_cache_mngr *mngr, const char *name,
|
|
change_func_t cb, void *data, struct nl_cache **result)
|
|
{
|
|
struct nl_cache_ops *ops;
|
|
struct nl_cache *cache;
|
|
struct nl_af_group *grp;
|
|
int err, i;
|
|
|
|
ops = nl_cache_ops_lookup(name);
|
|
if (!ops)
|
|
return -NLE_NOCACHE;
|
|
|
|
if (ops->co_protocol != mngr->cm_protocol)
|
|
return -NLE_PROTO_MISMATCH;
|
|
|
|
if (ops->co_groups == NULL)
|
|
return -NLE_OPNOTSUPP;
|
|
|
|
for (i = 0; i < mngr->cm_nassocs; i++)
|
|
if (mngr->cm_assocs[i].ca_cache &&
|
|
mngr->cm_assocs[i].ca_cache->c_ops == ops)
|
|
return -NLE_EXIST;
|
|
|
|
retry:
|
|
for (i = 0; i < mngr->cm_nassocs; i++)
|
|
if (!mngr->cm_assocs[i].ca_cache)
|
|
break;
|
|
|
|
if (i >= mngr->cm_nassocs) {
|
|
mngr->cm_nassocs += 16;
|
|
mngr->cm_assocs = realloc(mngr->cm_assocs,
|
|
mngr->cm_nassocs *
|
|
sizeof(struct nl_cache_assoc));
|
|
if (mngr->cm_assocs == NULL)
|
|
return -NLE_NOMEM;
|
|
else {
|
|
NL_DBG(1, "Increased capacity of cache manager %p " \
|
|
"to %d\n", mngr, mngr->cm_nassocs);
|
|
goto retry;
|
|
}
|
|
}
|
|
|
|
cache = nl_cache_alloc(ops);
|
|
if (!cache)
|
|
return -NLE_NOMEM;
|
|
|
|
for (grp = ops->co_groups; grp->ag_group; grp++) {
|
|
err = nl_socket_add_membership(mngr->cm_handle, grp->ag_group);
|
|
if (err < 0)
|
|
goto errout_free_cache;
|
|
}
|
|
|
|
err = nl_cache_refill(mngr->cm_handle, cache);
|
|
if (err < 0)
|
|
goto errout_drop_membership;
|
|
|
|
mngr->cm_assocs[i].ca_cache = cache;
|
|
mngr->cm_assocs[i].ca_change = cb;
|
|
mngr->cm_assocs[i].ca_change_data = data;
|
|
|
|
if (mngr->cm_flags & NL_AUTO_PROVIDE)
|
|
nl_cache_mngt_provide(cache);
|
|
|
|
NL_DBG(1, "Added cache %p <%s> to cache manager %p\n",
|
|
cache, nl_cache_name(cache), mngr);
|
|
|
|
*result = cache;
|
|
return 0;
|
|
|
|
errout_drop_membership:
|
|
for (grp = ops->co_groups; grp->ag_group; grp++)
|
|
nl_socket_drop_membership(mngr->cm_handle, grp->ag_group);
|
|
errout_free_cache:
|
|
nl_cache_free(cache);
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Get file descriptor
|
|
* @arg mngr Cache Manager
|
|
*
|
|
* Get the file descriptor of the socket associated to the manager.
|
|
* This can be used to change socket options or monitor activity
|
|
* using poll()/select().
|
|
*/
|
|
int nl_cache_mngr_get_fd(struct nl_cache_mngr *mngr)
|
|
{
|
|
return nl_socket_get_fd(mngr->cm_handle);
|
|
}
|
|
|
|
/**
|
|
* Check for event notifications
|
|
* @arg mngr Cache Manager
|
|
* @arg timeout Upper limit poll() will block, in milliseconds.
|
|
*
|
|
* Causes poll() to be called to check for new event notifications
|
|
* being available. Automatically receives and handles available
|
|
* notifications.
|
|
*
|
|
* This functionally is ideally called regularly during an idle
|
|
* period.
|
|
*
|
|
* @return A positive value if at least one update was handled, 0
|
|
* for none, or a negative error code.
|
|
*/
|
|
int nl_cache_mngr_poll(struct nl_cache_mngr *mngr, int timeout)
|
|
{
|
|
int ret;
|
|
struct pollfd fds = {
|
|
.fd = nl_socket_get_fd(mngr->cm_handle),
|
|
.events = POLLIN,
|
|
};
|
|
|
|
NL_DBG(3, "Cache manager %p, poll() fd %d\n", mngr, fds.fd);
|
|
ret = poll(&fds, 1, timeout);
|
|
NL_DBG(3, "Cache manager %p, poll() returned %d\n", mngr, ret);
|
|
if (ret < 0)
|
|
return -nl_syserr2nlerr(errno);
|
|
|
|
if (ret == 0)
|
|
return 0;
|
|
|
|
return nl_cache_mngr_data_ready(mngr);
|
|
}
|
|
|
|
/**
|
|
* Receive available event notifications
|
|
* @arg mngr Cache manager
|
|
*
|
|
* This function can be called if the socket associated to the manager
|
|
* contains updates to be received. This function should not be used
|
|
* if nl_cache_mngr_poll() is used.
|
|
*
|
|
* @return A positive value if at least one update was handled, 0
|
|
* for none, or a negative error code.
|
|
*/
|
|
int nl_cache_mngr_data_ready(struct nl_cache_mngr *mngr)
|
|
{
|
|
int err;
|
|
|
|
err = nl_recvmsgs_default(mngr->cm_handle);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Free cache manager and all caches.
|
|
* @arg mngr Cache manager.
|
|
*
|
|
* Release all resources after usage of a cache manager.
|
|
*/
|
|
void nl_cache_mngr_free(struct nl_cache_mngr *mngr)
|
|
{
|
|
int i;
|
|
|
|
if (!mngr)
|
|
return;
|
|
|
|
if (mngr->cm_handle)
|
|
nl_close(mngr->cm_handle);
|
|
|
|
for (i = 0; i < mngr->cm_nassocs; i++)
|
|
if (mngr->cm_assocs[i].ca_cache)
|
|
nl_cache_free(mngr->cm_assocs[i].ca_cache);
|
|
|
|
free(mngr->cm_assocs);
|
|
free(mngr);
|
|
|
|
NL_DBG(1, "Cache manager %p freed\n", mngr);
|
|
}
|
|
|
|
/** @} */
|