nexmon – Rev 1

Subversion Repositories:
Rev:
/*
 * 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);
}

/** @} */