Implementation of agent protocol and tool
authorJose Bollo <jose.bollo@iot.bzh>
Mon, 14 Oct 2019 16:02:38 +0000 (18:02 +0200)
committerJose Bollo <jose.bollo@iot.bzh>
Wed, 16 Oct 2019 09:05:46 +0000 (11:05 +0200)
Signed-off-by: Jose Bollo <jose.bollo@iot.bzh>
CMakeLists.txt
src/CMakeLists.txt
src/cyn-protocol.c
src/cyn-protocol.h
src/cyn-server.c
src/cyn.c
src/cyn.h
src/cynagora-protocol.txt
src/cynagora.c
src/cynagora.h
src/main-cynagora-agent.c [new file with mode: 0644]

index 2995a07..d150697 100644 (file)
@@ -61,7 +61,7 @@ add_compile_options(-Wno-unused-parameter) # frankly not using a parameter does
 add_compile_options(-Werror=maybe-uninitialized)
 add_compile_options(-Werror=implicit-function-declaration)
 add_compile_options(-ffunction-sections -fdata-sections)
-add_compile_options(-ffile-prefix-map=${CMAKE_SOURCE_DIR}=.)
+#add_compile_options(-ffile-prefix-map=${CMAKE_SOURCE_DIR}=.)
 
 ###########################################################################
 
index 300fd3d..0c1417d 100644 (file)
@@ -109,3 +109,11 @@ target_link_libraries(cynagoracli cynagora)
 install(TARGETS cynagoracli
         RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})
 
+###########################################
+# build and install cynagora-agent
+###########################################
+add_executable(cynagora-agent main-cynagora-agent.c expire.c)
+target_link_libraries(cynagora-agent cynagora)
+install(TARGETS cynagora-agent
+        RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})
+
index b7f4c01..c443996 100644 (file)
@@ -26,6 +26,7 @@
 
 const char
        _agent_[] = "agent",
+       _ask_[] = "ask",
        _check_[] = "check",
        _clear_[] = "clear",
        _commit_[] = "commit",
@@ -41,6 +42,7 @@ const char
        _no_[] = "no",
        _off_[] = "off",
        _on_[] = "on",
+       _reply_[] = "reply",
        _rollback_[] = "rollback",
        _set_[] = "set",
        _test_[] = "test",
index d8bcca0..e68d70c 100644 (file)
@@ -24,6 +24,7 @@
 /* predefined protocol strings */
 extern const char
        _agent_[],
+       _ask_[],
        _check_[],
        _clear_[],
        _commit_[],
@@ -39,6 +40,7 @@ extern const char
        _no_[],
        _off_[],
        _on_[],
+       _reply_[],
        _rollback_[],
        _set_[],
        _test_[],
index be72e92..97a44d1 100644 (file)
 #include "socket.h"
 #include "pollitem.h"
 #include "expire.h"
+#include "cynagora.h"
+
+typedef struct client client_t;
+typedef struct agent agent_t;
+typedef struct ask ask_t;
 
 #define MAX_PUTX_ITEMS 15
 
@@ -87,8 +92,26 @@ struct client
 
        /** polling callback */
        pollitem_t pollitem;
+
+       /** list of pending ask */
+       ask_t *asks;
+
+       /** last askid */
+       uint32_t askid;
+};
+
+/** structure for pending asks */
+struct ask
+{
+       /** chained list */
+       ask_t *next;
+
+       /** query */
+       cynagora_query_t *query;
+
+       /** id of the ask */
+       uint32_t id;
 };
-typedef struct client client_t;
 
 /** structure for servers */
 struct cyn_server
@@ -265,8 +288,8 @@ entercb(
 ) {
        client_t *cli = closure;
 
-       cli->entered = true;
-       cli->entering = false;
+       cli->entered = 1;
+       cli->entering = 0;
        send_done(cli);
 }
 
@@ -371,6 +394,96 @@ getcb(
                value->value, exp2get(value->expire, text, sizeof text), NULL);
 }
 
+/** search the request of askid */
+static
+ask_t*
+searchask(
+       client_t *cli,
+       uint32_t askid,
+       bool unlink
+) {
+       ask_t *r, **prv;
+
+       prv = &cli->asks;
+       while ((r = *prv) && r->id != askid)
+               prv = &r->next;
+       if (r && unlink)
+               *prv = r->next;
+       return r;
+}
+
+/** callback of agents */
+static
+int
+agentcb(
+       const char *name,
+       void *closure,
+       const data_key_t *key,
+       const char *value,
+       cynagora_query_t *query
+) {
+       client_t *cli = closure;
+       uint32_t askid;
+       ask_t *ask;
+       char buffer[30];
+       int rc;
+
+       /* search a valid id */
+       do {
+               askid = ++cli->askid;
+       } while (!askid && searchask(cli, askid, false));
+       rc = snprintf(buffer, sizeof buffer, "%lu", (long unsigned)askid);
+       if (rc < 0)
+               return -errno;
+       if (rc >= (int)sizeof buffer)
+               return -ECANCELED;
+
+       /* allocate the ask structure */
+       ask = malloc(sizeof *ask);
+       if (!ask)
+               return -ENOMEM;
+       ask->id = askid;
+       ask->query = query;
+
+       /* link the ask */
+       ask->next = cli->asks;
+       cli->asks = ask;
+
+       /* make the query */
+       putx(cli, _ask_, buffer, name, value,
+                       key->client, key->session, key->user, key->permission,
+                       NULL);
+       flushw(cli);
+       return 0;
+}
+
+static
+void
+replycb(
+       client_t *cli,
+       const char *askid,
+       const char *yesno,
+       const char *expire
+) {
+       ask_t *ask;
+       unsigned long int ul;
+       data_value_t value;
+
+       ul = strtoul(askid, NULL, 10);
+       if (ul <= UINT32_MAX) {
+               ask = searchask(cli, (uint32_t)ul, true);
+               if (ask) {
+                       value.value = yesno;
+                       if (!expire)
+                               value.expire = 0;
+                       else if (!txt2exp(expire, &value.expire, true))
+                               value.expire = -1;
+                       cyn_query_reply(ask->query, &value);
+                       free(ask);
+               }
+       }
+}
+
 /** handle a request */
 static
 void
@@ -408,10 +521,11 @@ onrequest(
 
        switch(args[0][0]) {
        case 'a': /* agent */
-               if (ckarg(args[0], _agent_, 1) && count == 5) {
+               if (ckarg(args[0], _agent_, 1) && count == 2) {
                        if (cli->type != server_Agent)
                                break;
-                       /* TODO */ break;
+                       rc = cyn_agent_add(args[1], agentcb, cli);
+                       send_done_or_error(cli, rc);
                        return;
                }
                break;
@@ -447,7 +561,7 @@ onrequest(
                                break;
                        if (cli->entered || cli->entering)
                                break;
-                       cli->entering = true;
+                       cli->entering = 1;
                        /* TODO: remove from polling until entered? */
                        cyn_enter_async(entercb, cli);
                        return;
@@ -475,7 +589,7 @@ onrequest(
                        if (!cli->entered)
                                break;
                        rc = cyn_leave(cli, count == 2 && ckarg(args[1], _commit_, 0));
-                       cli->entered = false;
+                       cli->entered = 0;
                        send_done_or_error(cli, rc);
                        return;
                }
@@ -494,6 +608,14 @@ onrequest(
                        return;
                }
                break;
+       case 'r': /* reply */
+               if (ckarg(args[0], _reply_, 1) && (count == 3 || count == 4)) {
+                       if (cli->type != server_Agent)
+                               break;
+                       replycb(cli, args[1], args[2], count == 4 ? args[3] : NULL);
+                       return;
+               }
+               break;
        case 's': /* set */
                if (ckarg(args[0], _set_, 1) && (count == 6 || count == 7)) {
                        if (cli->type != server_Admin)
@@ -541,7 +663,7 @@ onchange(
 ) {
        client_t *cli = closure;
        if (cli->checked) {
-               cli->checked = false;
+               cli->checked = 0;
                putx(cli, _clear_, cyn_changeid_string(), NULL);
                flushw(cli);
        }
@@ -554,13 +676,35 @@ destroy_client(
        client_t *cli,
        bool closefds
 ) {
+       ask_t *ask;
+       data_value_t value;
+
+       /* remove observers */
+       cyn_on_change_remove(onchange, cli);
+
+       /* close protocol */
        if (closefds)
                close(cli->pollitem.fd);
        if (cli->entering)
                cyn_enter_async_cancel(entercb, cli);
        if (cli->entered)
                cyn_leave(cli, false);
-       cyn_on_change_remove(onchange, cli);
+
+       /* clean of asks */
+       ask = cli->asks;
+       if (ask) {
+               value.value = _no_;
+               value.expire = -1;
+               do {
+                       cyn_query_reply(ask->query, &value);
+                       cli->asks = ask->next;
+                       free(ask);
+                       ask = cli->asks;
+               } while (ask);
+       }
+
+       /* clean of agents */
+       cyn_agent_remove_by_cc(agentcb, cli);
        prot_destroy(cli->prot);
        free(cli);
 }
@@ -634,14 +778,16 @@ create_client(
        /* records the file descriptor */
        cli->type = type;
        cli->version = 0; /* version not set */
-       cli->relax = true; /* relax on error */
-       cli->invalid = false; /* not invalid */
-       cli->entered = false; /* not entered */
-       cli->entering = false; /* not entering */
-       cli->checked = false; /* no check made */
+       cli->relax = 0; /* relax on error */
+       cli->invalid = 0; /* not invalid */
+       cli->entered = 0; /* not entered */
+       cli->entering = 0; /* not entering */
+       cli->checked = 0; /* no check made */
        cli->pollitem.handler = on_client_event;
        cli->pollitem.closure = cli;
        cli->pollitem.fd = fd;
+       cli->asks = NULL;
+       cli->askid = 0;
        return 0;
 error3:
        prot_destroy(cli->prot);
index fba8d57..7255821 100644 (file)
--- a/src/cyn.c
+++ b/src/cyn.c
@@ -27,6 +27,7 @@
 #include <stdbool.h>
 #include <string.h>
 #include <errno.h>
+#include <ctype.h>
 
 #include "data.h"
 #include "db.h"
@@ -563,13 +564,15 @@ cyn_query_reply(
  */
 static
 size_t
-check_agent_name(
+agent_check_name(
        const char *name
 ) {
+       char c;
        size_t length = 0;
        if (name) {
-               while (name[length]) {
-                       if (length > UINT8_MAX || name[length] == AGENT_SEPARATOR_CHARACTER) {
+               while ((c = name[length])) {
+                       if (length > UINT8_MAX
+                        || (!isalnum(c) && !strchr("@_-$", c))) {
                                length = 0;
                                break;
                        }
@@ -589,8 +592,8 @@ cyn_agent_add(
        struct agent *agent, **pprev;
        size_t length;
 
-       /* compute and check name length */
-       length = check_agent_name(name);
+       /* compute and check name */
+       length = agent_check_name(name);
        if (!length)
                return -EINVAL;
 
@@ -617,14 +620,14 @@ cyn_agent_add(
 
 /* see cyn.h */
 int
-cyn_agent_remove(
+cyn_agent_remove_by_name(
        const char *name
 ) {
        struct agent *agent, **pprev;
        size_t length;
 
        /* compute and check name length */
-       length = check_agent_name(name);
+       length = agent_check_name(name);
        if (!length)
                return -EINVAL;
 
@@ -639,6 +642,24 @@ cyn_agent_remove(
        return 0;
 }
 
+/* see cyn.h */
+void
+cyn_agent_remove_by_cc(
+       agent_cb_t *agent_cb,
+       void *closure
+) {
+       struct agent *it, **pprev;
+
+       pprev = &agents;
+       while((it = *pprev))
+               if (it->agent_cb != agent_cb || it->closure != closure)
+                       pprev = &it->next;
+               else {
+                       *pprev = it->next;
+                       free(it);
+               }
+}
+
 /* see cyn.h */
 void
 cyn_changeid_reset(
index d583674..64366f0 100644 (file)
--- a/src/cyn.h
+++ b/src/cyn.h
@@ -336,11 +336,14 @@ cyn_query_reply(
 /**
  * Add the agent of name
  *
+ * Valid names have at least one character and at most 256 and contains
+ * only characters of a-zA-Z0-9@$_-
+ *
  * @param name name of the agent to add
  * @param agent_cb callback of the agent
  * @param closure closure of the callback of the agent
  * @return 0 in case of success
- *         -EINVAL if the name is too long
+ *         -EINVAL if the name is invalid
  *         -EEXIST if an agent of the same name is already recorded
  *         -ENOMEM if out of memory
  */
@@ -362,10 +365,26 @@ cyn_agent_add(
  */
 extern
 int
-cyn_agent_remove(
+cyn_agent_remove_by_name(
        const char *name
 );
 
+/**
+ * Remove the agents of callback and closure
+ *
+ * @param agent_cb callback of the agent to remove
+ * @param closure closure of the callback of the agent to remove
+ * @return 0 in case of successful removal
+ *         -EINVAL if the name is too long
+ *         -ENOENT if the agent isn't recorded
+ */
+extern
+void
+cyn_agent_remove_by_cc(
+       agent_cb_t *agent_cb,
+       void *closure
+);
+
 /**
  * Reset the changeid
  *
index 29e5b3a..ebebd4e 100644 (file)
@@ -54,8 +54,8 @@ logging set/get (admin)
 
 register agent (agent):
 
-  c->s agent NAME [ARGS...]
-  s->c done|error ...
+  s->c ask ASKID NAME VALUE CLIENT SESSION USER PERMISSION
+  c->s reply ASKID (yes|no|error) [EXPIRE]
 
 ask agent (agent):
 
index 96c6466..b4bd7e9 100644 (file)
@@ -31,6 +31,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <poll.h>
+#include <ctype.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
 #define MIN_CACHE_SIZE 400
 #define CACHESIZE(x)  ((x) >= MIN_CACHE_SIZE ? (x) : (x) ? MIN_CACHE_SIZE : 0)
 
+typedef struct asreq asreq_t;
+typedef struct agent agent_t;
+typedef struct query query_t;
+
 /** recording of asynchronous requests */
 struct asreq
 {
        /** link to the next pending request */
-       struct asreq *next;
+       asreq_t *next;
 
        /** callback function */
        cynagora_async_check_cb_t *callback;
@@ -58,7 +63,22 @@ struct asreq
        /** closure of the callback */
        void *closure;
 };
-typedef struct asreq asreq_t;
+
+/** structure to handle agents */
+struct agent
+{
+       /* Link to the next agent */
+       agent_t *next;
+
+       /* recorded callback of the agent */
+       cynagora_agent_cb_t *agentcb;
+
+       /* closure of the callback */
+       void *closure;
+
+       /* name of the agent */
+       char name[];
+};
 
 /**
  * structure recording a client
@@ -101,10 +121,34 @@ struct cynagora
                asreq_t *requests;
        } async;
 
+       /** the declared agents */
+       agent_t *agents;
+
+       /** the pending queries */
+       query_t *queries;
+
        /** spec of the socket */
        char socketspec[];
 };
 
+/** structure of queries for agents */
+struct query
+{
+       /** public query */
+       cynagora_query_t query;
+
+       /** link to the next */
+       query_t *next;
+
+       /** the client of the query */
+       cynagora_t *cynagora;
+
+       /** the askid */
+       char *askid;
+};
+
+static void agent_ask(cynagora_t *cynagora, int count, const char **fields);
+
 /**
  * Flush the write buffer of the client
  *
@@ -139,6 +183,59 @@ flushw(
        return rc;
 }
 
+/**
+ * Send a reply
+ *
+ * @param cynagora the client
+ * @param fields the fields to send
+ * @param count the count of fields
+ * @return 0 on success or a negative error code
+ */
+static
+int
+send_reply(
+       cynagora_t *cynagora,
+       const char **fields,
+       int count
+) {
+       int rc, trial, i;
+       prot_t *prot;
+
+       /* retrieves the protocol handler */
+       prot = cynagora->prot;
+       trial = 0;
+       for(;;) {
+
+               /* fill the fields */
+               for (i = rc = 0 ; i < count && rc == 0 ; i++)
+                       rc = prot_put_field(prot, fields[i]);
+
+               /* send if done */
+               if (rc == 0) {
+                       rc = prot_put_end(prot);
+                       if (rc == 0) {
+                               rc = flushw(cynagora);
+                               break;
+                       }
+               }
+
+               /* failed to fill protocol, cancel current composition  */
+               prot_put_cancel(prot);
+
+               /* fail if was last trial */
+               if (trial)
+                       break;
+
+               /* try to flush the output buffer */
+               rc = flushw(cynagora);
+               if (rc)
+                       break;
+
+               trial = 1;
+       }
+       return rc;
+}
+
 /**
  * Put the command made of arguments ...
  * Increment the count of pending requests.
@@ -160,57 +257,33 @@ putxkv(
        const cynagora_key_t *optkey,
        const cynagora_value_t *optval
 ) {
-       int rc, trial;
-       prot_t *prot;
+       int nf, rc;
        char text[30];
-
-       /* retrieves the protocol handler */
-       prot = cynagora->prot;
-       for(trial = 0 ; ; trial++) {
-               /* fill the protocol handler with command and its arguments */
-               rc = prot_put_field(prot, command);
-               if (!rc && optarg)
-                       rc = prot_put_field(prot, optarg);
-               if (!rc && optkey) {
-                       rc = prot_put_field(prot, optkey->client);
-                       if (!rc)
-                               rc = prot_put_field(prot, optkey->session);
-                       if (!rc)
-                               rc = prot_put_field(prot, optkey->user);
-                       if (!rc)
-                               rc = prot_put_field(prot, optkey->permission);
-               }
-               if (!rc && optval) {
-                       rc = prot_put_field(prot, optval->value);
-                       if (!rc) {
-                               if (!optval->expire)
-                                       text[0] = 0;
-                               else
-                                       exp2txt(optval->expire, true, text, sizeof text);
-                               rc = prot_put_field(prot, text);
-                       }
-               }
-               if (!rc)
-                       rc = prot_put_end(prot);
-               if (!rc) {
-                       /* success ! */
-                       /* client always flushes */
-                       cynagora->pending++;
-                       return flushw(cynagora);
+       const char *fields[8];
+
+       /* prepare fields */
+       fields[0] = command;
+       nf = 1;
+       if (optarg)
+               fields[nf++] = optarg;
+       if (optkey) {
+               fields[nf++] = optkey->client;
+               fields[nf++] = optkey->session;
+               fields[nf++] = optkey->user;
+               fields[nf++] = optkey->permission;
+       }
+       if (optval) {
+               fields[nf++] = optval->value;
+               if (optval->expire) {
+                       exp2txt(optval->expire, true, text, sizeof text);
+                       fields[nf++] = text;
                }
-
-               /* failed to fill protocol, cancel current composition  */
-               prot_put_cancel(prot);
-
-               /* fail if was last trial */
-               if (trial >= 1)
-                       return rc;
-
-               /* try to flush the output buffer */
-               rc = flushw(cynagora);
-               if (rc)
-                       return rc;
        }
+
+       /* send now */
+       rc = send_reply(cynagora, fields, nf);
+       cynagora->pending += !rc; /* one more pending if no error */
+       return rc;
 }
 
 /**
@@ -257,6 +330,10 @@ get_reply(
                        cacheid = rc > 1 ? (uint32_t)atol(cynagora->reply.fields[1]) : 0;
                        cache_clear(cynagora->cache, cacheid);
                        rc = 0;
+               } else if (0 == strcmp(cynagora->reply.fields[0], _ask_)) {
+                       /* on asking agent */
+                       agent_ask(cynagora, rc - 1, &cynagora->reply.fields[1]);
+                       rc = 0;
                } else {
                        if (0 != strcmp(cynagora->reply.fields[0], _item_))
                                cynagora->pending--;
@@ -446,7 +523,17 @@ void
 disconnection(
        cynagora_t *cynagora
 ) {
+       query_t *query;
+
        if (cynagora->fd >= 0) {
+               /* forget queries */
+               query = cynagora->queries;
+               cynagora->queries = 0;
+               while(query) {
+                       query->cynagora = 0;
+                       query = query->next;
+               }
+               /* drop connection */
                async(cynagora, EPOLL_CTL_DEL, 0);
                close(cynagora->fd);
                cynagora->fd = -1;
@@ -466,6 +553,7 @@ connection(
        cynagora_t *cynagora
 ) {
        int rc;
+       agent_t *agent;
 
        /* init the client */
        cynagora->pending = 0;
@@ -487,8 +575,17 @@ connection(
                                cache_clear(cynagora->cache,
                                        cynagora->reply.count > 2 ? (uint32_t)atol(cynagora->reply.fields[2]) : 0);
                                rc = async(cynagora, EPOLL_CTL_ADD, EPOLLIN);
-                               if (rc >= 0)
+                               /* reconnect agent */
+                               agent = cynagora->agents;
+                               while (agent && rc >= 0) {
+                                       rc = putxkv(cynagora, _agent_, agent->name, 0, 0);
+                                       if (rc >= 0)
+                                               rc = wait_done(cynagora);
+                                       agent = agent->next;
+                               }
+                               if (rc >= 0) {
                                        return 0;
+                               }
                        }
                }
        }
@@ -564,7 +661,7 @@ check_or_test(
 }
 
 /******************************************************************************/
-/*** PUBLIC METHODS                                                         ***/
+/*** PUBLIC COMMON METHODS                                                  ***/
 /******************************************************************************/
 
 /* see cynagora.h */
@@ -615,6 +712,7 @@ cynagora_create(
        cynagora->async.controlcb = NULL;
        cynagora->async.closure = 0;
        cynagora->async.requests = NULL;
+       cynagora->agents = NULL;
 
        /* lazy connection */
        cynagora->fd = -1;
@@ -649,6 +747,80 @@ cynagora_destroy(
        free(cynagora);
 }
 
+/* see cynagora.h */
+int
+cynagora_async_setup(
+       cynagora_t *cynagora,
+       cynagora_async_ctl_cb_t *controlcb,
+       void *closure
+) {
+       asreq_t *ar;
+
+       /* cancel pending requests */
+       while((ar = cynagora->async.requests) != NULL) {
+               cynagora->async.requests = ar->next;
+               ar->callback(ar->closure, -ECANCELED);
+               free(ar);
+       }
+
+       /* remove existing polling */
+       async(cynagora, EPOLL_CTL_DEL, 0);
+
+       /* records new data */
+       cynagora->async.controlcb = controlcb;
+       cynagora->async.closure = closure;
+
+       /* record to polling */
+       return async(cynagora, EPOLL_CTL_ADD, EPOLLIN);
+}
+
+/* see cynagora.h */
+int
+cynagora_async_process(
+       cynagora_t *cynagora
+) {
+       int rc;
+       const char *first;
+       asreq_t *ar;
+       time_t expire;
+       cynagora_key_t key;
+
+       for (;;) {
+               /* non blocking wait for a reply */
+               rc = wait_reply(cynagora, false);
+               if (rc < 0)
+                       return rc == -EAGAIN ? 0 : rc;
+
+               /* skip empty replies */
+               if (rc == 0)
+                       continue;
+
+               /* skip done/error replies */
+               first = cynagora->reply.fields[0];
+               if (!strcmp(first, _done_)
+                || !strcmp(first, _error_))
+                       continue;
+
+               /* ignore unexpected answers */
+               ar = cynagora->async.requests;
+               if (ar == NULL)
+                       continue;
+
+               /* emit the asynchronous answer */
+               cynagora->async.requests = ar->next;
+               rc = status_check(cynagora, &expire);
+               if (rc >= 0) {
+                       key.client = (const char*)(ar + 1);
+                       key.session = &key.client[1 + strlen(key.client)];
+                       key.user = &key.session[1 + strlen(key.session)];
+                       key.permission = &key.user[1 + strlen(key.user)];
+                       cache_put(cynagora->cache, &key, rc, expire, true);
+               }
+               ar->callback(ar->closure, rc);
+               free(ar);
+       }
+}
+
 /* see cynagora.h */
 int
 cynagora_cache_resize(
@@ -693,6 +865,55 @@ cynagora_test(
        return check_or_test(cynagora, key, _test_);
 }
 
+/* see cynagora.h */
+int
+cynagora_async_check(
+       cynagora_t *cynagora,
+       const cynagora_key_t *key,
+       int simple,
+       cynagora_async_check_cb_t *callback,
+       void *closure
+) {
+       int rc;
+       asreq_t **pr, *ar;
+
+       /* ensure connection */
+       rc = ensure_opened(cynagora);
+       if (rc < 0)
+               return rc;
+
+       /* allocate */
+       ar = malloc(sizeof *ar + strlen(key->client) + strlen(key->session) + strlen(key->user) + strlen(key->permission) + 4);
+       if (ar == NULL)
+               return -ENOMEM;
+
+       /* init */
+       ar->next = NULL;
+       ar->callback = callback;
+       ar->closure = closure;
+       stpcpy(1 + stpcpy(1 + stpcpy(1 + stpcpy((char*)(ar + 1), key->client), key->session), key->user), key->permission);
+
+       /* send the request */
+       rc = putxkv(cynagora, simple ? _test_ : _check_, 0, key, 0);
+       if (rc >= 0)
+               rc = flushw(cynagora);
+       if (rc < 0) {
+               free(ar);
+               return rc;
+       }
+
+       /* record the request */
+       pr = &cynagora->async.requests;
+       while(*pr != NULL)
+               pr = &(*pr)->next;
+       *pr = ar;
+       return 0;
+}
+
+/******************************************************************************/
+/*** PUBLIC ADMIN METHODS                                                   ***/
+/******************************************************************************/
+
 /* see cynagora.h */
 int
 cynagora_get(
@@ -847,121 +1068,266 @@ cynagora_drop(
        return rc;
 }
 
-/* see cynagora.h */
-int
-cynagora_async_setup(
-       cynagora_t *cynagora,
-       cynagora_async_ctl_cb_t *controlcb,
-       void *closure
-) {
-       asreq_t *ar;
+/******************************************************************************/
+/*** PRIVATE AGENT METHODS                                                  ***/
+/******************************************************************************/
 
-       /* cancel pending requests */
-       while((ar = cynagora->async.requests) != NULL) {
-               cynagora->async.requests = ar->next;
-               ar->callback(ar->closure, -ECANCELED);
-               free(ar);
+/**
+ * Check the name and compute its length. Returns 0 in case of invalid name
+ * @param name the name to check
+ * @return the length of the name or zero if invalid
+ */
+static
+size_t
+agent_check_name(
+       const char *name
+) {
+       char c;
+       size_t length = 0;
+       if (name) {
+               while ((c = name[length])) {
+                       if (length > UINT8_MAX
+                        || (!isalnum(c) && !strchr("@_-$", c))) {
+                               length = 0;
+                               break;
+                       }
+                       length++;
+               }
        }
+       return length;
+}
 
-       /* remove existing polling */
-       async(cynagora, EPOLL_CTL_DEL, 0);
-
-       /* records new data */
-       cynagora->async.controlcb = controlcb;
-       cynagora->async.closure = closure;
-
-       /* record to polling */
-       return async(cynagora, EPOLL_CTL_ADD, EPOLLIN);
+/**
+ * Search the recorded agent of name
+ *
+ * @param cynagora the client cynagora
+ * @param name the name of the agent
+ * @param unlink should unlink from the link
+ * @return the found agent or NULL
+ */
+static
+agent_t*
+agent_search(
+       cynagora_t *cynagora,
+       const char *name,
+       bool unlink
+) {
+       agent_t *r, **pr;
+
+       pr = &cynagora->agents;
+       while((r = *pr) && strcmp(name, r->name))
+               pr = &r->next;
+       if (r && unlink)
+               *pr = r->next;
+       return r;
 }
 
-/* see cynagora.h */
+/**
+ * Send an agent reply
+ *
+ * @param cynagora the client
+ * @param askid the ask identifier
+ * @param value the value to return
+ * @param expire the expiration of the value
+ * @return 0 on success or a negative error code
+ */
+static
 int
-cynagora_async_process(
-       cynagora_t *cynagora
+agent_send_reply(
+       cynagora_t *cynagora,
+       const char *askid,
+       const char *value,
+       time_t expire
+) {
+       int nf;
+       char text[30];
+       const char *fields[4];
+
+       fields[0] = _reply_;
+       fields[1] = askid;
+       fields[2] = value;
+
+       /* format expiration */
+       if (!expire)
+               nf = 3;
+       else {
+               exp2txt(expire, true, text, sizeof text);
+               fields[3] = text;
+               nf = 4;
+       }
+       return send_reply(cynagora, fields, nf);
+}
+
+/**
+ * Dispatch a received agent request
+ *
+ * The received fields should be:
+ *
+ *    ASKID NAME VALUE CLIENT SESSION USER PERMISSION
+ *
+ * @param cynagora the handler
+ * @param count the count of fields
+ * @param fields the fields
+ */
+static
+void
+agent_ask(
+       cynagora_t *cynagora,
+       int count,
+       const char **fields
 ) {
        int rc;
-       const char *first;
-       asreq_t *ar;
-       time_t expire;
-       cynagora_key_t key;
+       size_t length;
+       agent_t *agent;
+       query_t *query;
+       char *p;
 
-       for (;;) {
-               /* non blocking wait for a reply */
-               rc = wait_reply(cynagora, false);
-               if (rc < 0)
-                       return rc == -EAGAIN ? 0 : rc;
+       if (count != 7)
+               goto error;
 
-               /* skip empty replies */
-               if (rc == 0)
-                       continue;
+       /* search the agent */
+       agent = agent_search(cynagora, fields[1], false);
+       if (!agent)
+               goto error;
 
-               /* skip done/error replies */
-               first = cynagora->reply.fields[0];
-               if (!strcmp(first, _done_)
-                || !strcmp(first, _error_))
-                       continue;
+       length = strlen(fields[0]);
+       length += strlen(fields[1]);
+       length += strlen(fields[2]);
+       length += strlen(fields[3]);
+       length += strlen(fields[4]);
+       length += strlen(fields[5]);
+       length += strlen(fields[6]);
 
-               /* ignore unexpected answers */
-               ar = cynagora->async.requests;
-               if (ar == NULL)
-                       continue;
+       query = malloc(length + 7 + sizeof *query);
+       if (!query)
+               goto error;
+       p = (char *)&query[1];
 
-               /* emit the asynchronous answer */
-               cynagora->async.requests = ar->next;
-               rc = status_check(cynagora, &expire);
-               if (rc >= 0) {
-                       key.client = (const char*)(ar + 1);
-                       key.session = &key.client[1 + strlen(key.client)];
-                       key.user = &key.session[1 + strlen(key.session)];
-                       key.permission = &key.user[1 + strlen(key.user)];
-                       cache_put(cynagora->cache, &key, rc, expire, true);
-               }
-               ar->callback(ar->closure, rc);
-               free(ar);
-       }
+       query->askid = p;
+       p = 1 + stpcpy(p, fields[0]);
+
+       query->query.name = p;
+       p = 1 + stpcpy(p, fields[1]);
+
+       query->query.value = p;
+       p = 1 + stpcpy(p, fields[2]);
+
+       query->query.key.client = p;
+       p = 1 + stpcpy(p, fields[3]);
+
+       query->query.key.session = p;
+       p = 1 + stpcpy(p, fields[4]);
+
+       query->query.key.user = p;
+       p = 1 + stpcpy(p, fields[5]);
+
+       query->query.key.permission = p;
+       p = 1 + stpcpy(p, fields[6]);
+
+       query->cynagora = cynagora;
+       query->next = cynagora->queries;
+       cynagora->queries = query;
+
+       rc = agent->agentcb(agent->closure, &query->query);
+       if (rc < 0)
+               cynagora_agent_reply(&query->query, NULL);
+       return;
+error:
+       agent_send_reply(cynagora, count ? fields[0] : "0", _error_, -1);
+}
+
+/******************************************************************************/
+/*** PUBLIC AGENT METHODS                                                   ***/
+/******************************************************************************/
+
+int
+cynagora_agent_is_valid_name(
+       const char *name
+) {
+       return agent_check_name(name) != 0;
 }
 
 /* see cynagora.h */
 int
-cynagora_async_check(
+cynagora_agent_create(
        cynagora_t *cynagora,
-       const cynagora_key_t *key,
-       int simple,
-       cynagora_async_check_cb_t *callback,
+       const char *name,
+       cynagora_agent_cb_t *agentcb,
        void *closure
 ) {
        int rc;
-       asreq_t **pr, *ar;
+       size_t length;
+       agent_t *agent;
+
+       /* check validity */
+       if (cynagora->type != cynagora_Agent)
+               return -EPERM;
 
+       /* check name */
+       length = agent_check_name(name);
+       if (!length)
+               return -EINVAL;
+
+       /* ensure connection */
        rc = ensure_opened(cynagora);
        if (rc < 0)
                return rc;
 
-       /* allocate */
-       ar = malloc(sizeof *ar + strlen(key->client) + strlen(key->session) + strlen(key->user) + strlen(key->permission) + 4);
-       if (ar == NULL)
+       /* allocate agent */
+       agent = malloc(length + 1 + sizeof *agent);
+       if (!agent)
                return -ENOMEM;
 
-       /* init */
-       ar->next = NULL;
-       ar->callback = callback;
-       ar->closure = closure;
-       stpcpy(1 + stpcpy(1 + stpcpy(1 + stpcpy((char*)(ar + 1), key->client), key->session), key->user), key->permission);
+       /* init the structure */
+       agent->agentcb = agentcb;
+       agent->closure = closure;
+       memcpy(agent->name, name, 1 + length);
+       agent->next = cynagora->agents;
+       cynagora->agents = agent;
 
-       /* send the request */
-       rc = putxkv(cynagora, simple ? _test_ : _check_, 0, key, 0);
+       /* send the command */
+       rc = putxkv(cynagora, _agent_, name, 0, 0);
        if (rc >= 0)
-               rc = flushw(cynagora);
+               rc = wait_done(cynagora);
        if (rc < 0) {
-               free(ar);
-               return rc;
+               /* clean on error */
+               agent_search(cynagora, name, true);
+               free(agent);
        }
 
-       /* record the request */
-       pr = &cynagora->async.requests;
-       while(*pr != NULL)
-               pr = &(*pr)->next;
-       *pr = ar;
-       return 0;
+       return rc;
 }
 
+/* see cynagora.h */
+int
+cynagora_agent_reply(
+       cynagora_query_t *_query,
+       cynagora_value_t *value
+) {
+       int rc;
+       query_t *query = (query_t*)_query;
+       query_t **p;
+       cynagora_t *cynagora;
+
+       cynagora = query->cynagora;
+       if (!cynagora)
+               rc = -ECANCELED;
+       else {
+               /* unlink the query */
+               p = &cynagora->queries;
+               while (*p)
+                       if (*p != query)
+                               p = &(*p)->next;
+                       else {
+                               *p = query->next;
+                               break;
+                       }
+
+               /* send the reply */
+               rc = agent_send_reply(cynagora, query->askid,
+                       value ? value->value : _error_,
+                       value ? value->expire : -1);
+       }
+       free(query);
+       return rc;
+}
index b77c0f4..aff1720 100644 (file)
 /******************************************************************************/
 /******************************************************************************/
 
+/******************************************************************************/
+/* COMMON PART - types and functions common to check/admin/agent clients      */
+/******************************************************************************/
 typedef struct cynagora cynagora_t;
 typedef enum cynagora_type cynagora_type_t;
 typedef struct cynagora_key cynagora_key_t;
-typedef struct cynagora_value cynagora_value_t;
 
 /**
  * type of the client interface
  */
-enum cynagora_type {
+enum cynagora_type
+{
        /** type for checking permissions */
        cynagora_Check,
+
        /** type for adminstration */
        cynagora_Admin,
+
        /** type for handling agents */
        cynagora_Agent
 };
@@ -41,7 +46,8 @@ enum cynagora_type {
 /**
  * Describes a query key
  */
-struct cynagora_key {
+struct cynagora_key
+{
        /** client item of the key */
        const char *client;
        /** session item of the key */
@@ -53,34 +59,12 @@ struct cynagora_key {
 };
 
 /**
- * Describes the value associated to a key
- */
-struct cynagora_value {
-       /** the associated value */
-       const char *value;
-       /** the expiration in seconds since epoch, negative to avoid cache */
-       time_t expire;
-};
-
-/**
- * Callback for enumeration of items (admin)
- * The function is called for each entry matching the selection key 
- * with the key and the associated value for that entry
- * 
- * @see cynagora_get
- */
-typedef void cynagora_get_cb_t(
-                       void *closure,
-                       const cynagora_key_t *key,
-                       const cynagora_value_t *value);
-
-/**
- * Callback for receiving asynchronousely the replies to the queries
+ * Callback for receiving asynchronously the replies to the queries
  * Receives:
  *  closure: the closure given to cynagora_async_check
  *  status: 0 if forbidden
  *          1 if granted
- *          -ECANCELED if cancelled
+ *          -ECANCELED if canceled
  */
 typedef void cynagora_async_check_cb_t(
                void *closure,
@@ -88,10 +72,10 @@ typedef void cynagora_async_check_cb_t(
 
 /**
  * Callback for managing the connection in an external loop
- * 
+ *
  * That callback receives epoll_ctl operations, a file descriptor number and
  * a mask of expected events.
- * 
+ *
  * @see epoll_ctl
  */
 typedef int cynagora_async_ctl_cb_t(
@@ -103,16 +87,16 @@ typedef int cynagora_async_ctl_cb_t(
 /**
  * Create a client to the permission server cynagora
  * The client is created but not connected. The connection is made on need.
- * 
+ *
  * @param cynagora   pointer to the handle of the opened client
  * @param type       type of the client to open
- * @param cache_size requested cache size
+ * @param cache_size requested cache size (no cache if 0)
  * @param socketspec specification of the socket to connect to or NULL for
  *                   using the default
- * 
+ *
  * @return 0 in case of success and in that case *cynagora is filled
  *         a negative -errno value and *cynara is set to NULL
- * 
+ *
  * @see cynagora_destroy, cynagora_cache_resize
  */
 extern
@@ -126,9 +110,9 @@ cynagora_create(
 
 /**
  * Destroy the client handler and release its memory
- * 
+ *
  * @param cynagora the client handler to close
- * 
+ *
  * @see cynagora_create
  */
 extern
@@ -140,7 +124,7 @@ cynagora_destroy(
 /**
  * Ask the client to disconnect from the server.
  * The client will reconnect if needed.
- * 
+ *
  * @param cynagora the client handler
  */
 extern
@@ -149,11 +133,41 @@ cynagora_disconnect(
        cynagora_t *cynagora
 );
 
+/**
+ * Set the asynchronous control function
+ *
+ * @param cynagora  the handler of the client
+ * @param controlcb
+ * @param closure
+ *
+ * @return  0 in case of success or a negative -errno value
+ */
+extern
+int
+cynagora_async_setup(
+       cynagora_t *cynagora,
+       cynagora_async_ctl_cb_t *controlcb,
+       void *closure
+);
+
+/**
+ * Process the inputs of the client
+ *
+ * @param cynagora  the handler of the client
+ *
+ * @return  0 in case of success or a negative -errno value
+ */
+extern
+int
+cynagora_async_process(
+       cynagora_t *cynagora
+);
+
 /**
  * Clear the cache
- * 
+ *
  * @param cynagora the client handler
- * 
+ *
  * @see cynagora_cache_resize
  */
 extern
@@ -164,12 +178,12 @@ cynagora_cache_clear(
 
 /**
  * Resize the cache
- * 
+ *
  * @param cynagora the client handler
  * @param size     new expected cache
- * 
+ *
  * @return 0 on success or -ENOMEM if out of memory
- * 
+ *
  * @see cynagora_cache_clear, cynagora_create
  */
 extern
@@ -181,12 +195,12 @@ cynagora_cache_resize(
 
 /**
  * Check a key against the cache
- * 
+ *
  * @param cynagora the client handler
  * @param key the key to check
- * 
+ *
  * @return 0 if forbidden, 1 if authorize, -ENOENT if cache miss
- * 
+ *
  * @see cynagora_check
  */
 extern
@@ -199,13 +213,13 @@ cynagora_cache_check(
 /**
  * Query the permission database for the key (synchronous)
  * Allows agent resolution.
- * 
+ *
  * @param cynagora the client handler
  * @param key      the key to check
- * 
+ *
  * @return 0 if permission forbidden, 1 if permission granted
  *         or if error a negative -errno value
- * 
+ *
  * @see cynagora_test, cynagora_cache_check
  */
 extern
@@ -218,11 +232,11 @@ cynagora_check(
 /**
  * Query the permission database for the key (synchronous)
  * Avoids agent resolution.
- * 
+ *
  * @param cynagora the client handler
  * @param key
- * @return 
- * 
+ * @return
+ *
  * @see cynagora_check
  */
 extern
@@ -232,14 +246,64 @@ cynagora_test(
        const cynagora_key_t *key
 );
 
+/**
+ * Check the key asynchronousely (async)
+ *
+ * @param cynagora  the handler of the client
+ * @param key       the key to query
+ * @param simple    if zero allows agent process else if not 0 forbids it
+ * @param callback  the callback to call on reply
+ * @param closure   a closure for the callback
+ *
+ * @return  0 in case of success or a negative -errno value
+ */
+extern
+int
+cynagora_async_check(
+       cynagora_t *cynagora,
+       const cynagora_key_t *key,
+       int simple,
+       cynagora_async_check_cb_t *callback,
+       void *closure
+);
+
+/******************************************************************************/
+/* ADMIN PART - types and functions specific to admin clients                 */
+/******************************************************************************/
+
+/**
+ * Describes the value associated to a key
+ */
+struct cynagora_value
+{
+       /** the associated value */
+       const char *value;
+       /** the expiration in seconds since epoch, negative to avoid cache */
+       time_t expire;
+};
+typedef struct cynagora_value cynagora_value_t;
+
+/**
+ * Callback for enumeration of items (admin)
+ * The function is called for each entry matching the selection key
+ * with the key and the associated value for that entry
+ *
+ * @see cynagora_get
+ */
+typedef void cynagora_get_cb_t(
+                       void *closure,
+                       const cynagora_key_t *key,
+                       const cynagora_value_t *value);
+
+
 /**
  * List any value of the permission database that matches the key (admin, synchronous)
- * 
+ *
  * @param cynagora the client handler
  * @param key      the selection key
  * @param callback the callback for receiving items
  * @param closure  closure of the callback
- * 
+ *
  * @return 0 in case of success or a negative -errno value
  */
 extern
@@ -253,11 +317,11 @@ cynagora_get(
 
 /**
  * Query or set the logging of requests (admin, synchronous)
- * 
+ *
  * @param cynagora the client handler
  * @param on       should set on
  * @param off      should set off
- * 
+ *
  * @return 0 if not logging, 1 if logging or a negative -errno value
  */
 extern
@@ -270,13 +334,13 @@ cynagora_log(
 
 /**
  * Enter cancelable section for modifying database (admin, synchronous)
- * 
+ *
  * @param cynagora the handler of the client
- * 
+ *
  * @return 0 in case of success or a negative -errno value
  *         -EPERM if not a admin client
  *         -EINPROGRESS if already entered
- * 
+ *
  * @see cynagora_leave, cynagora_set, cynagora_drop
  */
 extern
@@ -287,15 +351,15 @@ cynagora_enter(
 
 /**
  * Leave cancelable section for modifying database (admin, synchronous)
- * 
+ *
  * @param cynagora  the handler of the client
  * @param commit    if zero, cancel the modifications in progress otherwise if
  *                  not zero, commit the changes
- * 
+ *
  * @return 0 in case of success or a negative -errno value
  *         -EPERM if not a admin client
  *         -ECANCELED if not entered
- * 
+ *
  * @see cynagora_enter, cynagora_set, cynagora_drop
  */
 extern
@@ -308,15 +372,15 @@ cynagora_leave(
 /**
  * Set a rule (either create or change it) (admin, synchronous)
  * This call requires to have entered the cancelable section.
- * 
+ *
  * @param cynagora  the handler of the client
  * @param key       the key to set
  * @param value     the value to set to the key
- * 
+ *
  * @return 0 in case of success or a negative -errno value
  *         -EPERM if not a admin client
  *         -ECANCELED if not entered
- * 
+ *
  * @see cynagora_enter, cynagora_leave, cynagora_drop
  */
 extern
@@ -330,14 +394,14 @@ cynagora_set(
 /**
  * Drop items matching the key selector (admin, synchronous)
  * This call requires to have entered the cancelable section.
- * 
+ *
  * @param cynagora  the handler of the client
  * @param key       Filter of the keys to drop
- * 
+ *
  * @return  0 in case of success or a negative -errno value
  *         -EPERM if not a admin client
  *         -ECANCELED if not entered
- * 
+ *
  * @see cynagora_enter, cynagora_leave, cynagora_set
  */
 extern
@@ -347,53 +411,71 @@ cynagora_drop(
        const cynagora_key_t *key
 );
 
+
+/******************************************************************************/
+/* AGENT PART - types and functions specific to agent clients                 */
+/******************************************************************************/
+
+/** structure representing an agent query from cynagora server */
+struct cynagora_query
+{
+       /** name of the queried agent */
+       const char *name;
+
+       /** value associated to the matching rule */
+       const char *value;
+
+       /** key of the query */
+       cynagora_key_t key;
+};
+typedef struct cynagora_query cynagora_query_t;
+
+/** callback receiving agent queries */
+typedef int (cynagora_agent_cb_t)(
+               void *closure,
+               cynagora_query_t *query);
+
 /**
- * Set the asynchronous control function
- * 
- * @param cynagora  the handler of the client
- * @param controlcb
- * @param closure
- * 
- * @return  0 in case of success or a negative -errno value
+ * Check if the given name is a valid agent name
+ *
+ * @param name name to check
+ * @return 0 when invalid or 1 if valid
  */
 extern
 int
-cynagora_async_setup(
-       cynagora_t *cynagora,
-       cynagora_async_ctl_cb_t *controlcb,
-       void *closure
+cynagora_agent_is_valid_name(
+       const char *name
 );
 
 /**
- * Process the inputs of the client
- * 
- * @param cynagora  the handler of the client
- * 
- * @return  0 in case of success or a negative -errno value
+ * Create an agent of a given name
+ *
+ * @param cynagora the client
+ * @param name name of the agent to create
+ * @param agentcb callback that will treat queries for the agent
+ * @param closure closure for the callback
+ * @return 0 on success
  */
 extern
 int
-cynagora_async_process(
-       cynagora_t *cynagora
+cynagora_agent_create(
+       cynagora_t *cynagora,
+       const char *name,
+       cynagora_agent_cb_t *agentcb,
+       void *closure
 );
 
 /**
- * Check the key asynchronousely (async)
- * 
- * @param cynagora  the handler of the client
- * @param key       the key to query
- * @param simple    if zero allows agent process else if not 0 forbids it
- * @param callback  the callback to call on reply
- * @param closure   a closure for the callback
- * 
- * @return  0 in case of success or a negative -errno value
+ * Reply to the query. After calling that function, the query is not more
+ * valid (removed from memory).
+ *
+ * @param query the query to reply
+ * @param value the value that the agent wants to reply to the query
+ * @return 0 on success or a negative error code
  */
 extern
 int
-cynagora_async_check(
-       cynagora_t *cynagora,
-       const cynagora_key_t *key,
-       int simple,
-       cynagora_async_check_cb_t *callback,
-       void *closure
+cynagora_agent_reply(
+       cynagora_query_t *query,
+       cynagora_value_t *value
 );
diff --git a/src/main-cynagora-agent.c b/src/main-cynagora-agent.c
new file mode 100644 (file)
index 0000000..88ed995
--- /dev/null
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author José Bollo <jose.bollo@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/******************************************************************************/
+/******************************************************************************/
+/* IMPLEMENTATION OF CYNAGORA ADMINISTRATION TOOL                             */
+/******************************************************************************/
+/******************************************************************************/
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <signal.h>
+#include <poll.h>
+#include <sys/epoll.h>
+#include <sys/wait.h>
+
+#include "cynagora.h"
+#include "expire.h"
+
+#define _HELP_        'h'
+#define _SOCKET_      's'
+#define _VERSION_     'v'
+#define _PIPED_       'p'
+
+static
+const char
+shortopts[] = "hps:v";
+
+static
+const struct option
+longopts[] = {
+       { "help", 0, NULL, _HELP_ },
+       { "piped", 0, NULL, _PIPED_ },
+       { "socket", 1, NULL, _SOCKET_ },
+       { "version", 0, NULL, _VERSION_ },
+       { NULL, 0, NULL, 0 }
+};
+
+static
+const char
+helptxt[] =
+       "\n"
+       "usage: cynagora-agent [options]... name [program]\n"
+       "\n"
+       "otpions:\n"
+       "       -s, --socket xxx      set the base xxx for sockets\n"
+       "       -p, --piped           replace stdin/out by out/in of program"
+       "       -h, --help            print this help and exit\n"
+       "       -v, --version         print the version and exit\n"
+       "\n"
+       "When program is given, cynagora-agent performs invoke it with\n"
+       "arguments VALUE CLIENT SESSION USER PERMISSION and expect it\n"
+       "to echo the result with optional expiration\n"
+       "Otherwise cynagora-agent echo its requests on one line:\n"
+       "ID VALUE CLIENT SESSION USER PERMISSION and expect to read the\n"
+       "replies: ID RESULT [EXPIRE]\n"
+       "\n"
+;
+
+static
+const char
+versiontxt[] =
+       "cynagora-agent version 1.99.99\n"
+;
+
+typedef struct {
+       int filled;
+       char buffer[4000];
+} buf_t;
+
+typedef struct {
+       int count;
+       char *args[20];
+} args_t;
+
+typedef struct {
+       int id;
+       cynagora_query_t *query;
+} query_t;
+
+typedef struct {
+       pid_t pid;
+       int id;
+       int io[2];
+} proc_t;
+
+static char *name;
+static char **prog;
+static int piped;
+static cynagora_t *cynagora;
+static int nexid;
+
+static buf_t buffer;
+static query_t queries[200];
+static proc_t procs[200];
+
+int qidx(int id)
+{
+       int r = sizeof queries / sizeof *queries;
+       while (r-- && queries[r].id != id);
+       return r;
+}
+
+int pidx(pid_t pid)
+{
+       int r = sizeof procs / sizeof *procs;
+       while (r-- && procs[r].pid != pid);
+       return r;
+}
+
+int buf_parse(buf_t *buf, args_t *args)
+{
+       char *p, *x;
+       size_t s;
+       int r;
+       static const char seps[] = " \t";
+
+       p = memchr(buf->buffer, '\n', (size_t)buf->filled);
+       if (!p)
+               r = 0;
+       else {
+               /* process one line: split args */
+               *p++ = 0;
+               r = (int)(p - buf->buffer);
+
+               args->count = 0;
+               x = buf->buffer;
+               s = strspn(x, seps);
+               x = &x[s];
+               while (*x) {
+                       if (args->count < (int)(sizeof args->args / sizeof *args->args))
+                               args->args[args->count++] = x;
+                       s = strcspn(x, seps);
+                       x = &x[s];
+                       if (!*x)
+                               break;
+                       *x++ = 0;
+                       s = strspn(x, seps);
+                       x = &x[s];
+               }
+       }
+       return r;
+}
+
+void buf_unprefix(buf_t *buf, int count)
+{
+       int remain;
+       if (count > 0) {
+               remain = buf->filled - count;
+               if (remain >= 0) {
+                       if (remain)
+                               memmove(buf->buffer, &buf->buffer[count], (size_t)remain);
+                       buf->filled = remain;
+               }
+       }
+}
+
+void read_and_dispatch(int fd, buf_t *buf, void (*fun)(int,char**))
+{
+       int n;
+       ssize_t sz;
+       args_t args;
+
+       sz = read(fd, &buf->buffer[buf->filled], sizeof buf->buffer - (size_t)buf->filled);
+       if (sz > 0) {
+               buf->filled += (int)sz;
+
+               n = buf_parse(buf, &args);
+               while (n) {
+                       if (args.count)
+                               fun(args.count, args.args);
+                       buf_unprefix(buf, n);
+                       n = buf_parse(buf, &args);
+               }
+       }
+}
+
+pid_t split(int io[2])
+{
+       int rc;
+       int parent2child[2], child2parent[2];
+       pid_t pid = -1;
+
+       /* create pipes */
+       rc = pipe(parent2child);
+       if (rc == 0) {
+               rc = pipe(child2parent);
+               if (rc == 0) {
+                       pid = fork();
+                       if (pid >= 0) {
+                               if (pid == 0) {
+                                       /* in child */
+                                       close(0);
+                                       dup(parent2child[0]);
+                                       close(1);
+                                       dup(child2parent[1]);
+                               } else {
+                                       /* in parent */
+                                       io[0] = dup(child2parent[0]);
+                                       io[1] = dup(parent2child[1]);
+                               }
+                       }
+                       close(child2parent[0]);
+                       close(child2parent[1]);
+               }
+               close(parent2child[0]);
+               close(parent2child[1]);
+       }
+       return pid;
+}
+
+void deadchild(int sig, siginfo_t *info, void *item)
+{
+       int i;
+       pid_t pid;
+       buf_t buf;
+       args_t args;
+       ssize_t sz;
+
+       pid = info->si_pid;
+       i = pidx(pid);
+       if (i >= 0) {
+               args.count = 0;
+               sz = read(procs[i].io[0], buf.buffer, sizeof buf.buffer);
+               if (sz > 0) {
+                       buf.filled = (int)sz;
+                       buf_parse(&buf, &args);
+               }
+               if (!args.count) {
+                       args.args[0] = "no";
+                       args.args[1] = "-";
+                       args.count = 2;
+               }
+               if (args.count == 1)
+                       printf("%d %s\n", procs[i].id, args.args[0]);
+               else
+                       printf("%d %s %s\n", procs[i].id, args.args[0], args.args[1]);
+               fflush(stdout);
+               close(procs[i].io[0]);
+               close(procs[i].io[1]);
+               procs[i].pid = 0;
+       }
+       waitpid(pid, NULL, 0);
+}
+
+void onquery(int ac, char **av)
+{
+       int i;
+       pid_t pid;
+
+       i = pidx(0);
+       if (ac == 6 && i >= 0) {
+               procs[i].pid = pid = split(procs[i].io);
+               if (pid >= 0) {
+                       procs[i].id = atoi(av[0]);
+                       if (!pid) {
+                               setenv("CYAG_VALUE", av[1], 1);
+                               setenv("CYAG_CLIENT", av[2], 1);
+                               setenv("CYAG_SESSION", av[3], 1);
+                               setenv("CYAG_USER", av[4], 1);
+                               setenv("CYAG_PERMISSION", av[5], 1);
+                               execvp(prog[0], prog);
+                               fprintf(stderr, "error: can't exec %s: %s\n", prog[0], strerror(errno));
+                               exit(1);
+                       }
+                       return;
+                       close(procs[i].io[0]);
+                       close(procs[i].io[1]);
+               }
+               procs[i].pid = 0;
+       }
+       fprintf(stdout, "%s no -\n", av[0]);
+}
+
+int runloop()
+{
+       struct pollfd pfd;
+       struct sigaction sigact;
+
+       /* set the signal handler */
+       sigact.sa_sigaction = deadchild;
+       sigemptyset(&sigact.sa_mask);
+       sigact.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
+       sigaction(SIGCHLD, &sigact, NULL);
+
+       pfd.fd = 0;
+       pfd.events = POLLIN;
+       for(;;) {
+               pfd.revents = 0;
+               poll(&pfd, 1, -1);
+               if (pfd.revents & POLLIN)
+                       read_and_dispatch(0, &buffer, onquery);
+               if (pfd.revents & POLLHUP)
+                       break;
+       }
+
+       return 0;
+}
+
+int setup_child()
+{
+       int rc, io[2];
+       pid_t pid;
+
+       /* fork the child */
+       pid = split(io);
+       if (pid < 0)
+               return -1;
+       if (pid) {
+               close(0);
+               dup(io[0]);
+               close(io[0]);
+               close(1);
+               dup(io[1]);
+               close(io[1]);
+               return 0;
+       }
+
+       /* run piped if required */
+       if (piped) {
+               rc = execvp(prog[0], prog);
+               fprintf(stderr, "error: can't exec %s: %s\n", prog[0], strerror(errno));
+       } else {
+               rc = runloop();
+               if (rc)
+                       fprintf(stderr, "error: can't loop: %s\n", strerror(errno));
+       }
+       exit(!!rc);
+}
+
+int agentcb(void *closure, cynagora_query_t *query)
+{
+       int i, id, rc;
+
+       /* get an id */
+       do {
+               id = ++nexid;
+               if (id < 0)
+                       id = nexid = 1;
+       } while (qidx(id) >= 0);
+
+       /* get an index */
+       i = qidx(0);
+       if (i < 0)
+               return -ECANCELED;
+
+       queries[i].id = id;
+       queries[i].query = query;
+
+       /* compose the value */
+       rc = fprintf(stdout, "%d %s %s %s %s %s\n",
+               id, query->value, query->key.client, query->key.session,
+               query->key.user, query->key.permission);
+       if (rc < 0) {
+               queries[i].query = NULL;
+               queries[i].id = 0;
+               return -ECANCELED;
+       }
+
+       return 0;
+}
+
+void onreply(int ac, char **av)
+{
+       int i, id;
+       cynagora_value_t value;
+
+       id = atoi(av[0]);
+       i = qidx(id);
+       if (i >= 0) {
+               value.value = "no";
+               value.expire = 0;
+               if (ac > 1)
+                       value.value = av[1];
+               if (ac > 2)
+                       txt2exp(av[2], &value.expire, true);
+               cynagora_agent_reply(queries[i].query, &value);
+               queries[i].query = NULL;
+               queries[i].id = 0;
+       }
+}
+
+int async_ctl(void *closure, int op, int fd, uint32_t events)
+{
+       int *pfd = closure;
+
+       switch(op) {
+       case EPOLL_CTL_ADD:
+       case EPOLL_CTL_MOD:
+               *pfd = fd;
+               break;
+       case EPOLL_CTL_DEL:
+               *pfd = -1;
+               break;
+       }
+       return 0;
+}
+
+int main(int ac, char **av)
+{
+       int opt;
+       int rc;
+       int help = 0;
+       int version = 0;
+       int error = 0;
+       char *socket = NULL;
+       struct pollfd fds[2];
+
+       /* scan arguments */
+       for (;;) {
+               opt = getopt_long(ac, av, shortopts, longopts, NULL);
+               if (opt == -1)
+                       break;
+
+               switch(opt) {
+               case _HELP_:
+                       help = 1;
+                       break;
+               case _PIPED_:
+                       piped = 1;
+                       break;
+               case _SOCKET_:
+                       socket = optarg;
+                       break;
+               case _VERSION_:
+                       version = 1;
+                       break;
+               default:
+                       error = 1;
+                       break;
+               }
+       }
+
+       /* handles help, version, error */
+       if (help) {
+               fprintf(stdout, helptxt);
+               return 0;
+       }
+       if (version) {
+               fprintf(stdout, versiontxt);
+               return 0;
+       }
+
+       /* check agent name */
+       if (optind == ac) {
+               fprintf(stderr, "error: name missing\n");
+               error = 1;
+       } else {
+               name = av[optind++];
+               if (!cynagora_agent_is_valid_name(name)) {
+                       fprintf(stderr, "error: invalid agent name %s\n", name);
+                       error = 1;
+               } else if (optind == ac) {
+                       prog = NULL;
+               } else {
+                       prog = &av[optind++];
+               }
+       }
+       if (error)
+               return 1;
+
+       /* initialize server */
+       signal(SIGPIPE, SIG_IGN); /* avoid SIGPIPE! */
+       rc = cynagora_create(&cynagora, cynagora_Agent, 0, socket);
+       if (rc < 0) {
+               fprintf(stderr, "error: initialization failed, %s\n", strerror(-rc));
+               return 1;
+       }
+       fds[1].fd = -1;
+       rc = cynagora_async_setup(cynagora, async_ctl, &fds[1].fd);
+       if (rc < 0) {
+               fprintf(stderr, "error: asynchronous setup failed, %s\n", strerror(-rc));
+               return 1;
+       }
+
+       /* create the agent */
+       rc = cynagora_agent_create(cynagora, name, agentcb, NULL);
+       if (rc < 0) {
+               fprintf(stderr, "error: creation of agent failed, %s\n", strerror(-rc));
+               return 1;
+       }
+
+       /* setup piped */
+       if (prog) {
+               rc = setup_child();
+               if (rc < 0) {
+                       fprintf(stderr, "error: can't setup child, %s\n", strerror(errno));
+                       return 1;
+               }
+       }
+
+       /* setup output */
+       setlinebuf(stdout);
+
+       fcntl(0, F_SETFL, O_NONBLOCK);
+       fds[0].fd = 0;
+       fds[0].events = fds[1].events = POLLIN;
+       for(;;) {
+               rc = poll(fds, 2, -1);
+               if (fds[0].revents & POLLIN)
+                       read_and_dispatch(0, &buffer, onreply);
+               if (fds[1].revents & POLLIN) {
+                       rc = cynagora_async_process(cynagora);
+                       if (rc < 0)
+                               fprintf(stderr, "asynchronous processing failed: %s\n", strerror(-rc));
+               }
+               if (fds[0].revents & POLLHUP)
+                       break;
+               if (fds[1].revents & POLLHUP)
+                       break;
+       }
+       return 0;
+}