summaryrefslogtreecommitdiff
path: root/qcom/qrtr/src/ns.c
diff options
context:
space:
mode:
Diffstat (limited to 'qcom/qrtr/src/ns.c')
-rw-r--r--qcom/qrtr/src/ns.c808
1 files changed, 808 insertions, 0 deletions
diff --git a/qcom/qrtr/src/ns.c b/qcom/qrtr/src/ns.c
new file mode 100644
index 0000000..393cc68
--- /dev/null
+++ b/qcom/qrtr/src/ns.c
@@ -0,0 +1,808 @@
+#include <err.h>
+#include <errno.h>
+#include <libgen.h>
+#include <limits.h>
+#include <linux/qrtr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "addr.h"
+#include "hash.h"
+#include "list.h"
+#include "map.h"
+#include "ns.h"
+#include "util.h"
+#include "waiter.h"
+
+#include "libqrtr.h"
+#include "logging.h"
+
+static const char *ctrl_pkt_strings[] = {
+ [QRTR_TYPE_HELLO] = "hello",
+ [QRTR_TYPE_BYE] = "bye",
+ [QRTR_TYPE_NEW_SERVER] = "new-server",
+ [QRTR_TYPE_DEL_SERVER] = "del-server",
+ [QRTR_TYPE_DEL_CLIENT] = "del-client",
+ [QRTR_TYPE_RESUME_TX] = "resume-tx",
+ [QRTR_TYPE_EXIT] = "exit",
+ [QRTR_TYPE_PING] = "ping",
+ [QRTR_TYPE_NEW_LOOKUP] = "new-lookup",
+ [QRTR_TYPE_DEL_LOOKUP] = "del-lookup",
+};
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
+
+struct context {
+ int sock;
+
+ int local_node;
+
+ struct sockaddr_qrtr bcast_sq;
+
+ struct list lookups;
+};
+
+struct server_filter {
+ unsigned int service;
+ unsigned int instance;
+ unsigned int ifilter;
+};
+
+struct lookup {
+ unsigned int service;
+ unsigned int instance;
+
+ struct sockaddr_qrtr sq;
+ struct list_item li;
+};
+
+struct server {
+ unsigned int service;
+ unsigned int instance;
+
+ unsigned int node;
+ unsigned int port;
+ struct map_item mi;
+ struct list_item qli;
+};
+
+struct node {
+ unsigned int id;
+
+ struct map_item mi;
+ struct map services;
+};
+
+static struct map nodes;
+
+static void server_mi_free(struct map_item *mi);
+
+static struct node *node_get(unsigned int node_id)
+{
+ struct map_item *mi;
+ struct node *node;
+ int rc;
+
+ mi = map_get(&nodes, hash_u32(node_id));
+ if (mi)
+ return container_of(mi, struct node, mi);
+
+ node = calloc(1, sizeof(*node));
+ if (!node)
+ return NULL;
+
+ node->id = node_id;
+
+ rc = map_create(&node->services);
+ if (rc)
+ LOGE_AND_EXIT("unable to create map");
+
+ rc = map_put(&nodes, hash_u32(node_id), &node->mi);
+ if (rc) {
+ map_destroy(&node->services);
+ free(node);
+ return NULL;
+ }
+
+ return node;
+}
+
+static int server_match(const struct server *srv, const struct server_filter *f)
+{
+ unsigned int ifilter = f->ifilter;
+
+ if (f->service != 0 && srv->service != f->service)
+ return 0;
+ if (!ifilter && f->instance)
+ ifilter = ~0;
+ return (srv->instance & ifilter) == f->instance;
+}
+
+static int server_query(const struct server_filter *f, struct list *list)
+{
+ struct map_entry *node_me;
+ struct map_entry *me;
+ struct node *node;
+ int count = 0;
+
+ list_init(list);
+ map_for_each(&nodes, node_me) {
+ node = map_iter_data(node_me, struct node, mi);
+
+ map_for_each(&node->services, me) {
+ struct server *srv;
+
+ srv = map_iter_data(me, struct server, mi);
+ if (!server_match(srv, f))
+ continue;
+
+ list_append(list, &srv->qli);
+ ++count;
+ }
+ }
+
+ return count;
+}
+
+static int service_announce_new(struct context *ctx,
+ struct sockaddr_qrtr *dest,
+ struct server *srv)
+{
+ struct qrtr_ctrl_pkt cmsg;
+ int rc;
+
+ LOGD("advertising new server [%d:%x]@[%d:%d]\n",
+ srv->service, srv->instance, srv->node, srv->port);
+
+ cmsg.cmd = cpu_to_le32(QRTR_TYPE_NEW_SERVER);
+ cmsg.server.service = cpu_to_le32(srv->service);
+ cmsg.server.instance = cpu_to_le32(srv->instance);
+ cmsg.server.node = cpu_to_le32(srv->node);
+ cmsg.server.port = cpu_to_le32(srv->port);
+
+ rc = sendto(ctx->sock, &cmsg, sizeof(cmsg), 0,
+ (struct sockaddr *)dest, sizeof(*dest));
+ if (rc < 0)
+ PLOGW("sendto()");
+
+ return rc;
+}
+
+static int service_announce_del(struct context *ctx,
+ struct sockaddr_qrtr *dest,
+ struct server *srv)
+{
+ struct qrtr_ctrl_pkt cmsg;
+ int rc;
+
+ LOGD("advertising removal of server [%d:%x]@[%d:%d]\n",
+ srv->service, srv->instance, srv->node, srv->port);
+
+ cmsg.cmd = cpu_to_le32(QRTR_TYPE_DEL_SERVER);
+ cmsg.server.service = cpu_to_le32(srv->service);
+ cmsg.server.instance = cpu_to_le32(srv->instance);
+ cmsg.server.node = cpu_to_le32(srv->node);
+ cmsg.server.port = cpu_to_le32(srv->port);
+
+ rc = sendto(ctx->sock, &cmsg, sizeof(cmsg), 0,
+ (struct sockaddr *)dest, sizeof(*dest));
+ if (rc < 0)
+ PLOGW("sendto()");
+
+ return rc;
+}
+
+static int lookup_notify(struct context *ctx, struct sockaddr_qrtr *to,
+ struct server *srv, bool new)
+{
+ struct qrtr_ctrl_pkt pkt = {};
+ int rc;
+
+ pkt.cmd = new ? QRTR_TYPE_NEW_SERVER : QRTR_TYPE_DEL_SERVER;
+ if (srv) {
+ pkt.server.service = cpu_to_le32(srv->service);
+ pkt.server.instance = cpu_to_le32(srv->instance);
+ pkt.server.node = cpu_to_le32(srv->node);
+ pkt.server.port = cpu_to_le32(srv->port);
+ }
+
+ rc = sendto(ctx->sock, &pkt, sizeof(pkt), 0,
+ (struct sockaddr *)to, sizeof(*to));
+ if (rc < 0)
+ PLOGW("send lookup result failed");
+ return rc;
+}
+
+static int annouce_servers(struct context *ctx, struct sockaddr_qrtr *sq)
+{
+ struct map_entry *me;
+ struct server *srv;
+ struct node *node;
+ int rc;
+
+ node = node_get(ctx->local_node);
+ if (!node)
+ return 0;
+
+ map_for_each(&node->services, me) {
+ srv = map_iter_data(me, struct server, mi);
+
+ rc = service_announce_new(ctx, sq, srv);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static struct server *server_add(unsigned int service, unsigned int instance,
+ unsigned int node_id, unsigned int port)
+{
+ struct map_item *mi;
+ struct server *srv;
+ struct node *node;
+ int rc;
+
+ if (!service || !port)
+ return NULL;
+
+ srv = calloc(1, sizeof(*srv));
+ if (srv == NULL)
+ return NULL;
+
+ srv->service = service;
+ srv->instance = instance;
+ srv->node = node_id;
+ srv->port = port;
+
+ node = node_get(node_id);
+ if (!node)
+ goto err;
+
+ rc = map_reput(&node->services, hash_u32(port), &srv->mi, &mi);
+ if (rc)
+ goto err;
+
+ LOGD("add server [%d:%x]@[%d:%d]\n", srv->service, srv->instance,
+ srv->node, srv->port);
+
+ if (mi) { /* we replaced someone */
+ struct server *old = container_of(mi, struct server, mi);
+ free(old);
+ }
+
+ return srv;
+
+err:
+ free(srv);
+ return NULL;
+}
+
+static int server_del(struct context *ctx, struct node *node, unsigned int port)
+{
+ struct lookup *lookup;
+ struct list_item *li;
+ struct map_item *mi;
+ struct server *srv;
+
+ mi = map_get(&node->services, hash_u32(port));
+ if (!mi)
+ return -ENOENT;
+
+ srv = container_of(mi, struct server, mi);
+ map_remove(&node->services, srv->mi.key);
+
+ /* Broadcast the removal of local services */
+ if (srv->node == ctx->local_node)
+ service_announce_del(ctx, &ctx->bcast_sq, srv);
+
+ /* Announce the service's disappearance to observers */
+ list_for_each(&ctx->lookups, li) {
+ lookup = container_of(li, struct lookup, li);
+ if (lookup->service && lookup->service != srv->service)
+ continue;
+ if (lookup->instance && lookup->instance != srv->instance)
+ continue;
+
+ lookup_notify(ctx, &lookup->sq, srv, false);
+ }
+
+ free(srv);
+
+ return 0;
+}
+
+static int ctrl_cmd_hello(struct context *ctx, struct sockaddr_qrtr *sq,
+ const void *buf, size_t len)
+{
+ int rc;
+
+ rc = sendto(ctx->sock, buf, len, 0, (void *)sq, sizeof(*sq));
+ if (rc > 0)
+ rc = annouce_servers(ctx, sq);
+
+ return rc;
+}
+
+static int ctrl_cmd_bye(struct context *ctx, struct sockaddr_qrtr *from)
+{
+ struct qrtr_ctrl_pkt pkt;
+ struct sockaddr_qrtr sq;
+ struct node *local_node;
+ struct map_entry *me;
+ struct server *srv;
+ struct node *node;
+ int rc;
+
+ node = node_get(from->sq_node);
+ if (!node)
+ return 0;
+
+ map_for_each(&node->services, me) {
+ srv = map_iter_data(me, struct server, mi);
+
+ server_del(ctx, node, srv->port);
+ }
+
+ /* Advertise the removal of this client to all local services */
+ local_node = node_get(ctx->local_node);
+ if (!local_node)
+ return 0;
+
+ memset(&pkt, 0, sizeof(pkt));
+ pkt.cmd = QRTR_TYPE_BYE;
+ pkt.client.node = from->sq_node;
+
+ map_for_each(&local_node->services, me) {
+ srv = map_iter_data(me, struct server, mi);
+
+ sq.sq_family = AF_QIPCRTR;
+ sq.sq_node = srv->node;
+ sq.sq_port = srv->port;
+
+ rc = sendto(ctx->sock, &pkt, sizeof(pkt), 0,
+ (struct sockaddr *)&sq, sizeof(sq));
+ if (rc < 0)
+ PLOGW("bye propagation failed");
+ }
+
+ return 0;
+}
+
+static int ctrl_cmd_del_client(struct context *ctx, struct sockaddr_qrtr *from,
+ unsigned node_id, unsigned port)
+{
+ struct qrtr_ctrl_pkt pkt;
+ struct sockaddr_qrtr sq;
+ struct node *local_node;
+ struct list_item *tmp;
+ struct lookup *lookup;
+ struct list_item *li;
+ struct map_entry *me;
+ struct server *srv;
+ struct node *node;
+ int rc;
+
+ /* Don't accept spoofed messages */
+ if (from->sq_node != node_id)
+ return -EINVAL;
+
+ /* Local DEL_CLIENT messages comes from the port being closed */
+ if (from->sq_node == ctx->local_node && from->sq_port != port)
+ return -EINVAL;
+
+ /* Remove any lookups by this client */
+ list_for_each_safe(&ctx->lookups, li, tmp) {
+ lookup = container_of(li, struct lookup, li);
+ if (lookup->sq.sq_node != node_id)
+ continue;
+ if (lookup->sq.sq_port != port)
+ continue;
+
+ list_remove(&ctx->lookups, &lookup->li);
+ free(lookup);
+ }
+
+ /* Remove the server belonging to this port*/
+ node = node_get(node_id);
+ if (node)
+ server_del(ctx, node, port);
+
+ /* Advertise the removal of this client to all local services */
+ local_node = node_get(ctx->local_node);
+ if (!local_node)
+ return 0;
+
+ pkt.cmd = QRTR_TYPE_DEL_CLIENT;
+ pkt.client.node = node_id;
+ pkt.client.port = port;
+
+ map_for_each(&local_node->services, me) {
+ srv = map_iter_data(me, struct server, mi);
+
+ sq.sq_family = AF_QIPCRTR;
+ sq.sq_node = srv->node;
+ sq.sq_port = srv->port;
+
+ rc = sendto(ctx->sock, &pkt, sizeof(pkt), 0,
+ (struct sockaddr *)&sq, sizeof(sq));
+ if (rc < 0)
+ PLOGW("del_client propagation failed");
+ }
+
+ return 0;
+}
+
+static int ctrl_cmd_new_server(struct context *ctx, struct sockaddr_qrtr *from,
+ unsigned int service, unsigned int instance,
+ unsigned int node_id, unsigned int port)
+{
+ struct lookup *lookup;
+ struct list_item *li;
+ struct server *srv;
+ int rc = 0;
+
+ /* Ignore specified node and port for local servers*/
+ if (from->sq_node == ctx->local_node) {
+ node_id = from->sq_node;
+ port = from->sq_port;
+ }
+
+ /* Don't accept spoofed messages */
+ if (from->sq_node != node_id)
+ return -EINVAL;
+
+ srv = server_add(service, instance, node_id, port);
+ if (!srv)
+ return -EINVAL;
+
+ if (srv->node == ctx->local_node)
+ rc = service_announce_new(ctx, &ctx->bcast_sq, srv);
+
+ list_for_each(&ctx->lookups, li) {
+ lookup = container_of(li, struct lookup, li);
+ if (lookup->service && lookup->service != service)
+ continue;
+ if (lookup->instance && lookup->instance != instance)
+ continue;
+
+ lookup_notify(ctx, &lookup->sq, srv, true);
+ }
+
+ return rc;
+}
+
+static int ctrl_cmd_del_server(struct context *ctx, struct sockaddr_qrtr *from,
+ unsigned int service __unused,
+ unsigned int instance __unused,
+ unsigned int node_id, unsigned int port)
+{
+ struct node *node;
+
+ /* Ignore specified node and port for local servers*/
+ if (from->sq_node == ctx->local_node) {
+ node_id = from->sq_node;
+ port = from->sq_port;
+ }
+
+ /* Don't accept spoofed messages */
+ if (from->sq_node != node_id)
+ return -EINVAL;
+
+ /* Local servers may only unregister themselves */
+ if (from->sq_node == ctx->local_node && from->sq_port != port)
+ return -EINVAL;
+
+ node = node_get(node_id);
+ if (!node)
+ return -ENOENT;
+
+ return server_del(ctx, node, port);
+}
+
+static int ctrl_cmd_new_lookup(struct context *ctx, struct sockaddr_qrtr *from,
+ unsigned int service, unsigned int instance)
+{
+ struct server_filter filter;
+ struct list reply_list;
+ struct lookup *lookup;
+ struct list_item *li;
+ struct server *srv;
+
+ /* Accept only local observers */
+ if (from->sq_node != ctx->local_node)
+ return -EINVAL;
+
+ lookup = calloc(1, sizeof(*lookup));
+ if (!lookup)
+ return -EINVAL;
+
+ lookup->sq = *from;
+ lookup->service = service;
+ lookup->instance = instance;
+ list_append(&ctx->lookups, &lookup->li);
+
+ memset(&filter, 0, sizeof(filter));
+ filter.service = service;
+ filter.instance = instance;
+
+ server_query(&filter, &reply_list);
+ list_for_each(&reply_list, li) {
+ srv = container_of(li, struct server, qli);
+
+ lookup_notify(ctx, from, srv, true);
+ }
+
+ lookup_notify(ctx, from, NULL, true);
+
+ return 0;
+}
+
+static int ctrl_cmd_del_lookup(struct context *ctx, struct sockaddr_qrtr *from,
+ unsigned int service, unsigned int instance)
+{
+ struct lookup *lookup;
+ struct list_item *tmp;
+ struct list_item *li;
+
+ list_for_each_safe(&ctx->lookups, li, tmp) {
+ lookup = container_of(li, struct lookup, li);
+ if (lookup->sq.sq_node != from->sq_node)
+ continue;
+ if (lookup->sq.sq_port != from->sq_port)
+ continue;
+ if (lookup->service != service)
+ continue;
+ if (lookup->instance && lookup->instance != instance)
+ continue;
+
+ list_remove(&ctx->lookups, &lookup->li);
+ free(lookup);
+ }
+
+ return 0;
+}
+
+static void ctrl_port_fn(void *vcontext, struct waiter_ticket *tkt)
+{
+ struct context *ctx = vcontext;
+ struct sockaddr_qrtr sq;
+ int sock = ctx->sock;
+ struct qrtr_ctrl_pkt *msg;
+ unsigned int cmd;
+ char buf[4096];
+ socklen_t sl;
+ ssize_t len;
+ int rc;
+
+ sl = sizeof(sq);
+ len = recvfrom(sock, buf, sizeof(buf), 0, (void *)&sq, &sl);
+ if (len <= 0) {
+ PLOGW("recvfrom()");
+ close(sock);
+ ctx->sock = -1;
+ goto out;
+ }
+ msg = (void *)buf;
+
+ if (len < 4) {
+ LOGW("short packet from %d:%d", sq.sq_node, sq.sq_port);
+ goto out;
+ }
+
+ cmd = le32_to_cpu(msg->cmd);
+ if (cmd < ARRAY_SIZE(ctrl_pkt_strings) && ctrl_pkt_strings[cmd])
+ LOGD("%s from %d:%d\n", ctrl_pkt_strings[cmd], sq.sq_node, sq.sq_port);
+ else
+ LOGD("UNK (%08x) from %d:%d\n", cmd, sq.sq_node, sq.sq_port);
+
+ rc = 0;
+ switch (cmd) {
+ case QRTR_TYPE_HELLO:
+ rc = ctrl_cmd_hello(ctx, &sq, buf, len);
+ break;
+ case QRTR_TYPE_BYE:
+ rc = ctrl_cmd_bye(ctx, &sq);
+ break;
+ case QRTR_TYPE_DEL_CLIENT:
+ rc = ctrl_cmd_del_client(ctx, &sq,
+ le32_to_cpu(msg->client.node),
+ le32_to_cpu(msg->client.port));
+ break;
+ case QRTR_TYPE_NEW_SERVER:
+ rc = ctrl_cmd_new_server(ctx, &sq,
+ le32_to_cpu(msg->server.service),
+ le32_to_cpu(msg->server.instance),
+ le32_to_cpu(msg->server.node),
+ le32_to_cpu(msg->server.port));
+ break;
+ case QRTR_TYPE_DEL_SERVER:
+ rc = ctrl_cmd_del_server(ctx, &sq,
+ le32_to_cpu(msg->server.service),
+ le32_to_cpu(msg->server.instance),
+ le32_to_cpu(msg->server.node),
+ le32_to_cpu(msg->server.port));
+ break;
+ case QRTR_TYPE_EXIT:
+ case QRTR_TYPE_PING:
+ case QRTR_TYPE_RESUME_TX:
+ break;
+ case QRTR_TYPE_NEW_LOOKUP:
+ rc = ctrl_cmd_new_lookup(ctx, &sq,
+ le32_to_cpu(msg->server.service),
+ le32_to_cpu(msg->server.instance));
+ break;
+ case QRTR_TYPE_DEL_LOOKUP:
+ rc = ctrl_cmd_del_lookup(ctx, &sq,
+ le32_to_cpu(msg->server.service),
+ le32_to_cpu(msg->server.instance));
+ break;
+ }
+
+ if (rc < 0)
+ LOGW("failed while handling packet from %d:%d",
+ sq.sq_node, sq.sq_port);
+out:
+ waiter_ticket_clear(tkt);
+}
+
+static int say_hello(struct context *ctx)
+{
+ struct qrtr_ctrl_pkt pkt;
+ int rc;
+
+ memset(&pkt, 0, sizeof(pkt));
+ pkt.cmd = cpu_to_le32(QRTR_TYPE_HELLO);
+
+ rc = sendto(ctx->sock, &pkt, sizeof(pkt), 0,
+ (struct sockaddr *)&ctx->bcast_sq, sizeof(ctx->bcast_sq));
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+static void server_mi_free(struct map_item *mi)
+{
+ free(container_of(mi, struct server, mi));
+}
+
+static void node_mi_free(struct map_item *mi)
+{
+ struct node *node = container_of(mi, struct node, mi);
+
+ map_clear(&node->services, server_mi_free);
+ map_destroy(&node->services);
+
+ free(node);
+}
+
+static void go_dormant(int sock)
+{
+ close(sock);
+
+ for (;;)
+ sleep(UINT_MAX);
+}
+
+static void usage(const char *progname)
+{
+ fprintf(stderr, "%s [-f] [-s] [<node-id>]\n", progname);
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ struct waiter_ticket *tkt;
+ struct sockaddr_qrtr sq;
+ struct context ctx;
+ unsigned long addr = (unsigned long)-1;
+ struct waiter *w;
+ socklen_t sl = sizeof(sq);
+ bool foreground = false;
+ bool use_syslog = false;
+ bool verbose_log = false;
+ char *ep;
+ int opt;
+ int rc;
+ const char *progname = basename(argv[0]);
+
+ while ((opt = getopt(argc, argv, "fsv")) != -1) {
+ switch (opt) {
+ case 'f':
+ foreground = true;
+ break;
+ case 's':
+ use_syslog = true;
+ break;
+ case 'v':
+ verbose_log = true;
+ break;
+ default:
+ usage(progname);
+ }
+ }
+
+ qlog_setup(progname, use_syslog);
+ if (verbose_log)
+ qlog_set_min_priority(LOG_DEBUG);
+
+ if (optind < argc) {
+ addr = strtoul(argv[optind], &ep, 10);
+ if (argv[1][0] == '\0' || *ep != '\0' || addr >= UINT_MAX)
+ usage(progname);
+
+ qrtr_set_address(addr);
+ optind++;
+ }
+
+ if (optind != argc)
+ usage(progname);
+
+ w = waiter_create();
+ if (w == NULL)
+ LOGE_AND_EXIT("unable to create waiter");
+
+ list_init(&ctx.lookups);
+
+ rc = map_create(&nodes);
+ if (rc)
+ LOGE_AND_EXIT("unable to create node map");
+
+ ctx.sock = socket(AF_QIPCRTR, SOCK_DGRAM, 0);
+ if (ctx.sock < 0)
+ PLOGE_AND_EXIT("unable to create control socket");
+
+ rc = getsockname(ctx.sock, (void*)&sq, &sl);
+ if (rc < 0)
+ PLOGE_AND_EXIT("getsockname()");
+ sq.sq_port = QRTR_PORT_CTRL;
+ ctx.local_node = sq.sq_node;
+
+ rc = bind(ctx.sock, (void *)&sq, sizeof(sq));
+ if (rc < 0) {
+ if (errno == EADDRINUSE) {
+ PLOGE("nameserver already running, going dormant");
+ go_dormant(ctx.sock);
+ }
+
+ PLOGE_AND_EXIT("bind control socket");
+ }
+
+ ctx.bcast_sq.sq_family = AF_QIPCRTR;
+ ctx.bcast_sq.sq_node = QRTR_NODE_BCAST;
+ ctx.bcast_sq.sq_port = QRTR_PORT_CTRL;
+
+ rc = say_hello(&ctx);
+ if (rc)
+ PLOGE_AND_EXIT("unable to say hello");
+
+ /* If we're going to background, fork and exit parent */
+ if (!foreground && fork() != 0) {
+ close(ctx.sock);
+ exit(0);
+ }
+
+ tkt = waiter_add_fd(w, ctx.sock);
+ waiter_ticket_callback(tkt, ctrl_port_fn, &ctx);
+
+ while (ctx.sock >= 0)
+ waiter_wait(w);
+
+ puts("exiting cleanly");
+
+ waiter_destroy(w);
+
+ map_clear(&nodes, node_mi_free);
+ map_destroy(&nodes);
+
+ return 0;
+}