| /* |
| * 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-2007 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); |
| } |
| |
| 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 protocol Netlink Protocol this manager is used for |
| * @arg flags Flags |
| * |
| * @return Newly allocated cache manager or NULL on failure. |
| */ |
| struct nl_cache_mngr *nl_cache_mngr_alloc(struct nl_handle *handle, |
| int protocol, int flags) |
| { |
| struct nl_cache_mngr *mngr; |
| |
| if (handle == NULL) |
| BUG(); |
| |
| mngr = calloc(1, sizeof(*mngr)); |
| if (!mngr) |
| goto enomem; |
| |
| mngr->cm_handle = handle; |
| 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 enomem; |
| |
| |
| nl_socket_modify_cb(mngr->cm_handle, NL_CB_VALID, NL_CB_CUSTOM, |
| event_input, mngr); |
| |
| /* Required to receive async event notifications */ |
| nl_disable_sequence_check(mngr->cm_handle); |
| |
| if (nl_connect(mngr->cm_handle, protocol) < 0) |
| goto errout; |
| |
| if (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); |
| |
| return mngr; |
| |
| enomem: |
| nl_errno(ENOMEM); |
| errout: |
| nl_cache_mngr_free(mngr); |
| return NULL; |
| } |
| |
| /** |
| * Add cache responsibility to cache manager |
| * @arg mngr Cache manager. |
| * @arg name Name of cache to keep track of |
| * |
| * 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 The newly allocated cache or NULL on failure. |
| */ |
| struct nl_cache *nl_cache_mngr_add(struct nl_cache_mngr *mngr, const char *name, |
| change_func_t cb) |
| { |
| 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) { |
| nl_error(ENOENT, "Unknown cache type"); |
| return NULL; |
| } |
| |
| if (ops->co_protocol != mngr->cm_protocol) { |
| nl_error(EINVAL, "Netlink protocol mismatch"); |
| return NULL; |
| } |
| |
| if (ops->co_groups == NULL) { |
| nl_error(EOPNOTSUPP, NULL); |
| return NULL; |
| } |
| |
| for (i = 0; i < mngr->cm_nassocs; i++) { |
| if (mngr->cm_assocs[i].ca_cache && |
| mngr->cm_assocs[i].ca_cache->c_ops == ops) { |
| nl_error(EEXIST, "Cache of this type already managed"); |
| return NULL; |
| } |
| } |
| |
| 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) { |
| nl_errno(ENOMEM); |
| return NULL; |
| } 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) { |
| nl_errno(ENOMEM); |
| return NULL; |
| } |
| |
| 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; |
| |
| 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); |
| |
| return cache; |
| |
| 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 NULL; |
| } |
| |
| /** |
| * 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_errno(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 |
| * @arg mngr Cache manager |
| * |
| * Release all resources after usage of a cache manager. |
| */ |
| void nl_cache_mngr_free(struct nl_cache_mngr *mngr) |
| { |
| if (!mngr) |
| return; |
| |
| if (mngr->cm_handle) { |
| nl_close(mngr->cm_handle); |
| nl_handle_destroy(mngr->cm_handle); |
| } |
| |
| free(mngr->cm_assocs); |
| free(mngr); |
| |
| NL_DBG(1, "Cache manager %p freed\n", mngr); |
| } |
| |
| /** @} */ |