summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordimitri staessens <dimitri.staessens@ugent.be>2017-09-18 08:03:31 +0200
committerdimitri staessens <dimitri.staessens@ugent.be>2017-09-18 09:27:53 +0200
commitf86502516b2a069954d0529d8b43593ece7360eb (patch)
tree73bc28244ef03c23e2ba5e79ae843de3df3fe727 /src
parent60ffacc12ab73752cb6ef736f2b41022c984785c (diff)
downloadouroboros-f86502516b2a069954d0529d8b43593ece7360eb.tar.gz
ouroboros-f86502516b2a069954d0529d8b43593ece7360eb.zip
lib: Provide RIB API to export internals via fuse
This adds a virtual RIB that is accessible as a filesystem that is accessed through a fuse mountpoint (configurable , default is /tmp/ouroboros). Currently, each IPCP will export its link state database.
Diffstat (limited to 'src')
-rw-r--r--src/ipcpd/normal/main.c21
-rw-r--r--src/ipcpd/normal/pol/link_state.c124
-rw-r--r--src/irmd/config.h.in5
-rw-r--r--src/irmd/main.c12
-rw-r--r--src/lib/CMakeLists.txt25
-rw-r--r--src/lib/config.h.in7
-rw-r--r--src/lib/rib.c389
7 files changed, 565 insertions, 18 deletions
diff --git a/src/ipcpd/normal/main.c b/src/ipcpd/normal/main.c
index 2b35a04a..e6dd6717 100644
--- a/src/ipcpd/normal/main.c
+++ b/src/ipcpd/normal/main.c
@@ -26,21 +26,21 @@
#define OUROBOROS_PREFIX "normal-ipcp"
-#include <ouroboros/endian.h>
-#include <ouroboros/logs.h>
+#include <ouroboros/errno.h>
+#include <ouroboros/hash.h>
#include <ouroboros/ipcp-dev.h>
-#include <ouroboros/time_utils.h>
#include <ouroboros/irm.h>
-#include <ouroboros/hash.h>
-#include <ouroboros/errno.h>
+#include <ouroboros/logs.h>
#include <ouroboros/notifier.h>
+#include <ouroboros/rib.h>
+#include <ouroboros/time_utils.h>
#include "addr_auth.h"
#include "connmgr.h"
#include "dir.h"
+#include "dt.h"
#include "enroll.h"
#include "fa.h"
-#include "dt.h"
#include "ipcp.h"
#include <stdbool.h>
@@ -338,6 +338,11 @@ int main(int argc,
}
/* These components must be init at creation. */
+ if (rib_init("ipcpd-normal")) {
+ log_err("Failed to initialize RIB.");
+ goto fail_rib_init;
+ }
+
if (connmgr_init()) {
log_err("Failed to initialize connection manager.");
goto fail_connmgr_init;
@@ -378,6 +383,8 @@ int main(int argc,
connmgr_fini();
+ rib_fini();
+
irm_unbind_api(getpid(), ipcpi.name);
ipcp_fini();
@@ -393,6 +400,8 @@ int main(int argc,
fail_enroll_init:
connmgr_fini();
fail_connmgr_init:
+ rib_fini();
+ fail_rib_init:
irm_unbind_api(getpid(), ipcpi.name);
fail_bind_api:
ipcp_fini();
diff --git a/src/ipcpd/normal/pol/link_state.c b/src/ipcpd/normal/pol/link_state.c
index 7df09bce..3d9e8246 100644
--- a/src/ipcpd/normal/pol/link_state.c
+++ b/src/ipcpd/normal/pol/link_state.c
@@ -22,15 +22,18 @@
#define _POSIX_C_SOURCE 200112L
+#include "config.h"
+
#define OUROBOROS_PREFIX "link-state-routing"
+#include <ouroboros/dev.h>
#include <ouroboros/errno.h>
+#include <ouroboros/fqueue.h>
#include <ouroboros/list.h>
#include <ouroboros/logs.h>
-#include <ouroboros/utils.h>
#include <ouroboros/notifier.h>
-#include <ouroboros/dev.h>
-#include <ouroboros/fqueue.h>
+#include <ouroboros/rib.h>
+#include <ouroboros/utils.h>
#include "ae.h"
#include "connmgr.h"
@@ -52,6 +55,7 @@ typedef LinkStateMsg link_state_msg_t;
#define LS_UPDATE_TIME 15
#define LS_TIMEO 60
#define LSA_MAX_LEN 128
+#define LSDB "lsdb"
#ifndef CLOCK_REALTIME_COARSE
#define CLOCK_REALTIME_COARSE CLOCK_REALTIME
@@ -90,6 +94,7 @@ struct {
fset_t * mgmt_set;
struct list_head db;
+ size_t db_len;
pthread_rwlock_t db_lock;
@@ -107,6 +112,106 @@ struct pol_routing_ops link_state_ops = {
.routing_i_destroy = link_state_routing_i_destroy
};
+static int str_adj(struct adjacency * adj,
+ char * buf,
+ size_t len)
+{
+ char tmbuf[64];
+ struct tm * tm;
+
+ if (len < 256)
+ return -1;
+
+ tm = localtime(&adj->stamp);
+ strftime(tmbuf, sizeof(tmbuf), "%Y-%m-%d %H:%M:%S", tm);
+
+ sprintf(buf,
+ "src: %" PRIu64 "\n"
+ "dst: %" PRIu64 "\n"
+ "upd: %s\n",
+ adj->src,
+ adj->dst,
+ tmbuf);
+
+ return strlen(buf);
+}
+
+static int lsdb_read(const char * path,
+ char * buf,
+ size_t len)
+{
+ struct list_head * p;
+ char entry[RIB_PATH_LEN + 1];
+
+ pthread_rwlock_rdlock(&ls.db_lock);
+
+ if (ls.db_len == 0) {
+ pthread_rwlock_unlock(&ls.db_lock);
+ return -EPERM;
+ }
+
+ list_for_each(p, &ls.db) {
+ struct adjacency * a = list_entry(p, struct adjacency, next);
+ sprintf(entry, "%" PRIu64 ".%" PRIu64, a->src, a->dst);
+ if (strcmp(entry, path) == 0) {
+ len = str_adj(a, buf, len);
+ pthread_rwlock_unlock(&ls.db_lock);
+ return len;
+ }
+ }
+
+ pthread_rwlock_unlock(&ls.db_lock);
+
+ return -1;
+}
+
+static int lsdb_readdir(char *** buf)
+{
+ struct list_head * p;
+ char entry[RIB_PATH_LEN + 1];
+ ssize_t idx = 0;
+
+ pthread_rwlock_rdlock(&ls.db_lock);
+
+ if (ls.db_len == 0) {
+ pthread_rwlock_unlock(&ls.db_lock);
+ return 0;
+ }
+
+ *buf = malloc(sizeof(**buf) * ls.db_len);
+ if (*buf == NULL) {
+ pthread_rwlock_unlock(&ls.db_lock);
+ return -ENOMEM;
+ }
+
+ list_for_each(p, &ls.db) {
+ struct adjacency * a = list_entry(p, struct adjacency, next);
+ sprintf(entry, "%" PRIu64 ".%" PRIu64, a->src, a->dst);
+ (*buf)[idx] = malloc(strlen(entry) + 1);
+ if ((*buf)[idx] == NULL) {
+ ssize_t j;
+ for (j = 0; j < idx; ++j)
+ free(*buf[j]);
+ free(buf);
+ pthread_rwlock_unlock(&ls.db_lock);
+ return -ENOMEM;
+ }
+
+ strcpy((*buf)[idx], entry);
+
+ idx++;
+ }
+
+ pthread_rwlock_unlock(&ls.db_lock);
+
+ return idx;
+}
+
+static struct rib_ops r_ops = {
+ .read = lsdb_read,
+ .readdir = lsdb_readdir
+};
+
static int lsdb_add_nb(uint64_t addr,
int fd,
enum nb_type type)
@@ -214,11 +319,11 @@ static int lsdb_add_link(uint64_t src,
list_add_tail(&adj->next, p);
+ ls.db_len++;
+
if (graph_update_edge(ls.graph, src, dst, *qs))
log_warn("Failed to add edge to graph.");
- log_dbg("Added %" PRIu64 " - %" PRIu64" to lsdb.", adj->src, adj->dst);
-
pthread_rwlock_unlock(&ls.db_lock);
return 0;
@@ -239,8 +344,7 @@ static int lsdb_del_link(uint64_t src,
if (graph_del_edge(ls.graph, src, dst))
log_warn("Failed to delete edge from graph.");
- log_dbg("Removed %" PRIu64 " - %" PRIu64" from lsdb.",
- a->src, a->dst);
+ ls.db_len--;
pthread_rwlock_unlock(&ls.db_lock);
free(a);
@@ -587,6 +691,10 @@ int link_state_init(void)
if (pthread_create(&ls.listener, NULL, ls_conn_handle, NULL))
goto fail_pthread_create_listener;
+ ls.db_len = 0;
+
+ rib_reg(LSDB, &r_ops);
+
return 0;
fail_pthread_create_listener:
@@ -614,6 +722,8 @@ void link_state_fini(void)
struct list_head * p;
struct list_head * h;
+ rib_unreg(LSDB);
+
pthread_cancel(ls.listener);
pthread_join(ls.listener, NULL);
diff --git a/src/irmd/config.h.in b/src/irmd/config.h.in
index c217fe93..d253a1ac 100644
--- a/src/irmd/config.h.in
+++ b/src/irmd/config.h.in
@@ -45,3 +45,8 @@
#define IRMD_MIN_THREADS @IRMD_MIN_THREADS@
#define IRMD_ADD_THREADS @IRMD_ADD_THREADS@
+
+#cmakedefine HAVE_FUSE
+#ifdef HAVE_FUSE
+#define FUSE_PREFIX "@FUSE_PREFIX@"
+#endif
diff --git a/src/irmd/main.c b/src/irmd/main.c
index c5caff6c..df903f40 100644
--- a/src/irmd/main.c
+++ b/src/irmd/main.c
@@ -1631,6 +1631,11 @@ static void irm_fini(void)
pthread_cond_destroy(&irmd.cmd_cond);
pthread_rwlock_destroy(&irmd.reg_lock);
pthread_rwlock_destroy(&irmd.state_lock);
+
+#ifdef HAVE_FUSE
+ if (rmdir(FUSE_PREFIX))
+ log_dbg("Failed to remove " FUSE_PREFIX);
+#endif
}
void irmd_sig_handler(int sig,
@@ -2249,7 +2254,12 @@ static int irm_init(void)
log_err("Failed to create rdrbuff.");
goto fail_rdrbuff;
}
-
+#ifdef HAVE_FUSE
+ if (stat(FUSE_PREFIX, &st) != -1)
+ log_warn(FUSE_PREFIX " already exists...");
+ else
+ mkdir(FUSE_PREFIX, 0777);
+#endif
irmd.csockfd = -1;
irmd.state = IRMD_RUNNING;
diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt
index fd7ece83..26cecb44 100644
--- a/src/lib/CMakeLists.txt
+++ b/src/lib/CMakeLists.txt
@@ -37,10 +37,30 @@ if (HAVE_ROBUST_MUTEX)
set(HAVE_ROBUST_MUTEX TRUE)
else ()
message(STATUS "Robust mutex support disabled by user")
- set(HAVE_ROBUST_MUTEX FALSE)
endif ()
endif ()
+find_library(FUSE_LIBRARIES fuse QUIET)
+if (FUSE_LIBRARIES)
+ #FIXME: Check for version >= 2.6
+ set(DISABLE_FUSE FALSE CACHE BOOL "Disable FUSE support")
+ if (NOT DISABLE_FUSE)
+ message(STATUS "FUSE support enabled")
+ set(FUSE_PREFIX "/tmp/ouroboros" CACHE STRING
+ "Mountpoint for RIB filesystem")
+ set(HAVE_FUSE TRUE CACHE INTERNAL "")
+ else ()
+ message(STATUS "FUSE support disabled by user")
+ endif ()
+endif ()
+
+if (NOT HAVE_FUSE)
+ set(FUSE_LIBRARIES "")
+ set(FUSE_INCLUDE_DIR "")
+endif ()
+
+mark_as_advanced(FUSE_LIBRARIES)
+
find_library(LIBGCRYPT_LIBRARIES gcrypt QUIET)
if (LIBGCRYPT_LIBRARIES)
find_path(LIBGCRYPT_INCLUDE_DIR gcrypt.h
@@ -163,6 +183,7 @@ set(SOURCE_FILES
qos.c
qoscube.c
random.c
+ rib.c
rq.c
sha3.c
shm_flow_set.c
@@ -188,7 +209,7 @@ endif (CMAKE_BUILD_TYPE MATCHES Debug)
target_link_libraries(ouroboros ${LIBRT_LIBRARIES}
${LIBPTHREAD_LIBRARIES} ${PROTOBUF_C_LIBRARY} ${OPENSSL_LIBRARIES}
- ${LIBGCRYPT_LIBRARIES})
+ ${LIBGCRYPT_LIBRARIES} ${FUSE_LIBRARIES})
install(TARGETS ouroboros LIBRARY DESTINATION usr/lib)
diff --git a/src/lib/config.h.in b/src/lib/config.h.in
index e9c43389..a85ce7b4 100644
--- a/src/lib/config.h.in
+++ b/src/lib/config.h.in
@@ -45,6 +45,11 @@
#cmakedefine HAVE_ROBUST_MUTEX
#endif
+#cmakedefine HAVE_FUSE
+#ifdef HAVE_FUSE
+#define FUSE_PREFIX "@FUSE_PREFIX@"
+#endif
+
#define PTHREAD_COND_CLOCK @PTHREAD_COND_CLOCK@
#define AP_MAX_FLOWS @AP_MAX_FLOWS@
@@ -53,5 +58,3 @@
#define DU_BUFF_HEADSPACE @DU_BUFF_HEADSPACE@
#define DU_BUFF_TAILSPACE @DU_BUFF_TAILSPACE@
-
-#define CDAP_REPLY_TIMEOUT @CDAP_REPLY_TIMEOUT@
diff --git a/src/lib/rib.c b/src/lib/rib.c
new file mode 100644
index 00000000..b6c8e140
--- /dev/null
+++ b/src/lib/rib.c
@@ -0,0 +1,389 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2017
+ *
+ * RIB export using FUSE
+ *
+ * Dimitri Staessens <dimitri.staessens@ugent.be>
+ * Sander Vrijders <sander.vrijders@ugent.be>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#define _POSIX_C_SOURCE 200112L
+
+#include "config.h"
+
+#include <ouroboros/errno.h>
+#include <ouroboros/list.h>
+#include <ouroboros/rib.h>
+#include <ouroboros/utils.h>
+
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_FUSE
+#define _FILE_OFFSET_BITS 64
+#define FUSE_USE_VERSION 26
+#include <fuse.h>
+#endif
+
+#ifdef HAVE_FUSE
+
+#ifndef CLOCK_REALTIME_COARSE
+#define CLOCK_REALTIME_COARSE CLOCK_REALTIME
+#endif
+
+#define RT "/"
+
+struct reg_comp {
+ struct list_head next;
+
+ char path[RIB_PATH_LEN + 1];
+ struct rib_ops * ops;
+};
+
+struct {
+ struct list_head reg_comps;
+
+ char mnt[RIB_PATH_LEN + 1];
+
+ struct fuse * fuse;
+ struct fuse_chan * ch;
+
+ pthread_rwlock_t lock;
+
+ pthread_t fuse_thr;
+} rib;
+
+static int rib_open(const char * path,
+ struct fuse_file_info * info)
+{
+ (void) path;
+
+ info->nonseekable = 1;
+
+ return 0;
+}
+
+static int rib_opendir(const char * path,
+ struct fuse_file_info * info)
+{
+ (void) path;
+ (void) info;
+
+ return 0;
+}
+
+static int rib_read(const char * path,
+ char * buf,
+ size_t size,
+ off_t offset,
+ struct fuse_file_info * info)
+{
+ struct list_head * p;
+ char comp[RIB_PATH_LEN + 1];
+ char * c;
+
+ strcpy(comp, path + 1);
+
+ c = strstr(comp, "/");
+
+ if (c != NULL)
+ *c = '\0';
+
+ (void) info;
+ (void) offset;
+
+ pthread_rwlock_wrlock(&rib.lock);
+
+ list_for_each(p, &rib.reg_comps) {
+ struct reg_comp * r = list_entry(p, struct reg_comp, next);
+ if (strcmp(comp, r->path) == 0) {
+ int ret = r->ops->read(c + 1, buf, size);
+ pthread_rwlock_unlock(&rib.lock);
+ return ret;
+ }
+ }
+
+ pthread_rwlock_unlock(&rib.lock);
+
+ return -1;
+}
+
+static int rib_readdir(const char * path,
+ void * buf,
+ fuse_fill_dir_t filler,
+ off_t offset,
+ struct fuse_file_info * info)
+{
+ struct list_head * p;
+
+ (void) offset;
+ (void) info;
+
+ filler(buf, ".", NULL, 0);
+ filler(buf, "..", NULL, 0);
+
+ pthread_rwlock_rdlock(&rib.lock);
+
+ if (strcmp(path, RT) == 0) {
+ list_for_each(p, &rib.reg_comps) {
+ struct reg_comp * c;
+ c = list_entry(p, struct reg_comp, next);
+ filler(buf, c->path, NULL, 0);
+ }
+ } else {
+ list_for_each(p, &rib.reg_comps) {
+ char ** dir_entries;
+ ssize_t len;
+ ssize_t i;
+ struct reg_comp * c;
+ c = list_entry(p, struct reg_comp, next);
+ if (strcmp(path + 1, c->path) == 0)
+ if (c->ops->readdir == NULL)
+ break;
+
+ len = c->ops->readdir(&dir_entries);
+ if (len < 0)
+ break;
+ for (i = 0; i < len; ++i)
+ filler(buf, dir_entries[i], NULL, 0);
+ freepp(char, dir_entries, len);
+ }
+ }
+
+ pthread_rwlock_unlock(&rib.lock);
+
+ return 0;
+}
+
+static int rib_getattr(const char * path,
+ struct stat * st)
+{
+ struct list_head * p;
+ struct timespec now;
+
+ clock_gettime(CLOCK_REALTIME_COARSE, &now);
+
+ memset(st, 0, sizeof(*st));
+
+ if (strcmp(path, RT) == 0) {
+ st->st_mode = __S_IFDIR | 0755;
+ st->st_nlink = 2;
+ st->st_uid = getuid();
+ st->st_gid = getgid();
+ st->st_mtime = now.tv_sec;
+ return 0;
+ }
+
+ pthread_rwlock_rdlock(&rib.lock);
+
+ list_for_each(p, &rib.reg_comps) {
+ struct reg_comp * rc = list_entry(p, struct reg_comp, next);
+ if (strcmp(path + 1, rc->path) == 0) {
+ st->st_mode = __S_IFDIR | 0755;
+ st->st_nlink = 2;
+ break;
+ }
+ }
+
+ pthread_rwlock_unlock(&rib.lock);
+
+ if (st->st_mode == 0) {
+ char buf[4096];
+ st->st_nlink = 2;
+ st->st_mode = __S_IFREG | 0755;
+ st->st_size = rib_read(path, buf, 4096, 0, NULL);
+ }
+
+ st->st_uid = getuid();
+ st->st_gid = getgid();
+ st->st_mtime = now.tv_sec;
+
+ return 0;
+}
+
+static struct fuse_operations r_ops = {
+ .getattr = rib_getattr,
+ .open = rib_open,
+ .opendir = rib_opendir,
+ .read = rib_read,
+ .readdir = rib_readdir
+};
+
+static void * fuse_thr(void * o)
+{
+ (void) o;
+
+ if (fuse_loop(rib.fuse) < 0)
+ return (void *) -1;
+
+ return (void *) 0;
+}
+#endif /* HAVE_FUSE */
+
+int rib_init(const char * prefix)
+{
+#ifdef HAVE_FUSE
+ struct stat st;
+ char * argv[] = {"ignored",
+ NULL,
+ "-f",
+ "-o",
+ "ro,",
+ "allow_other,",
+ "default_permissions,",
+ "fsname=rib",
+ NULL};
+ struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+
+ if (stat(FUSE_PREFIX, &st) == -1)
+ return -1;
+
+ sprintf(rib.mnt, FUSE_PREFIX "/%s.%d", prefix, getpid());
+
+ if (stat(rib.mnt, &st) == -1)
+ mkdir(rib.mnt, 0777);
+
+ argv[1] = rib.mnt;
+
+ fuse_opt_parse(&args, argv, NULL, NULL);
+
+ list_head_init(&rib.reg_comps);
+
+ rib.ch = fuse_mount(rib.mnt, &args);
+ if (rib.ch == NULL)
+ goto fail_mount;
+
+ rib.fuse = fuse_new(rib.ch, &args, &r_ops, sizeof(r_ops), NULL);
+ if (rib.fuse == NULL)
+ goto fail_fuse;
+
+ if (pthread_rwlock_init(&rib.lock, NULL))
+ goto fail_rwlock_init;
+
+ if (pthread_create(&rib.fuse_thr, NULL, fuse_thr, NULL))
+ goto fail_fuse_thr;
+
+ fuse_opt_free_args(&args);
+
+ return 0;
+
+ fail_fuse_thr:
+ pthread_rwlock_destroy(&rib.lock);
+ fail_rwlock_init:
+ fuse_destroy(rib.fuse);
+ fail_fuse:
+ fuse_unmount(rib.mnt, rib.ch);
+ fail_mount:
+ fuse_opt_free_args(&args);
+ rmdir(rib.mnt);
+ return -1;
+#else
+ (void) prefix;
+ return 0;
+#endif
+}
+
+void rib_fini(void)
+{
+#ifdef HAVE_FUSE
+ struct list_head * p;
+ struct list_head * h;
+
+ fuse_unmount(rib.mnt, rib.ch);
+ pthread_join(rib.fuse_thr, NULL);
+
+ fuse_destroy(rib.fuse);
+
+ rmdir(rib.mnt);
+
+ pthread_rwlock_wrlock(&rib.lock);
+
+ list_for_each_safe(p, h, &rib.reg_comps) {
+ struct reg_comp * c = list_entry(p, struct reg_comp, next);
+ list_del(&c->next);
+ free(c);
+ }
+
+ pthread_rwlock_unlock(&rib.lock);
+
+ pthread_rwlock_destroy(&rib.lock);
+#endif
+}
+
+int rib_reg(const char * path,
+ struct rib_ops * ops)
+{
+#ifdef HAVE_FUSE
+ struct reg_comp * rc;
+ struct list_head * p;
+
+ pthread_rwlock_wrlock(&rib.lock);
+
+ list_for_each(p, &rib.reg_comps) {
+ struct reg_comp * r = list_entry(p, struct reg_comp, next);
+ if (strcmp(r->path, path) == 0) {
+ pthread_rwlock_unlock(&rib.lock);
+ return -EPERM;
+ }
+
+ if (strcmp(r->path, path) > 0)
+ break;
+ }
+
+ rc = malloc(sizeof(*rc));
+ if (rc == NULL) {
+ pthread_rwlock_unlock(&rib.lock);
+ return -ENOMEM;
+ }
+
+ strcpy(rc->path, path);
+ rc->ops = ops;
+
+ list_add_tail(&rc->next, p);
+
+ pthread_rwlock_unlock(&rib.lock);
+#else
+ (void) path;
+ (void) ops;
+#endif
+ return 0;
+}
+
+void rib_unreg(const char * path)
+{
+#ifdef HAVE_FUSE
+ struct list_head * p;
+ struct list_head * h;
+
+ pthread_rwlock_wrlock(&rib.lock);
+
+ list_for_each_safe(p, h, &rib.reg_comps) {
+ struct reg_comp * r = list_entry(p, struct reg_comp, next);
+ if (strcmp(r->path, path) == 0) {
+ list_del(&r->next);
+ free(r);
+ break;
+ }
+ }
+
+ pthread_rwlock_unlock(&rib.lock);
+#else
+ (void) path;
+#endif
+}