summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJose Bollo <jose.bollo@iot.bzh>2019-10-14 18:02:38 +0200
committerJose Bollo <jose.bollo@iot.bzh>2019-10-16 11:05:46 +0200
commitd927b8c4d931b3fa4c5744778976081e9218a838 (patch)
tree41a8403daf9d90caa46429d625da4ff92b51261c
parentf53a76ce91ab83c7345a104b57f148738101c58d (diff)
Implementation of agent protocol and tool
Signed-off-by: Jose Bollo <jose.bollo@iot.bzh>
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/CMakeLists.txt8
-rw-r--r--src/cyn-protocol.c2
-rw-r--r--src/cyn-protocol.h2
-rw-r--r--src/cyn-server.c174
-rw-r--r--src/cyn.c35
-rw-r--r--src/cyn.h23
-rw-r--r--src/cynagora-protocol.txt4
-rw-r--r--src/cynagora.c636
-rw-r--r--src/cynagora.h280
-rw-r--r--src/main-cynagora-agent.c534
11 files changed, 1440 insertions, 260 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2995a07..d150697 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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}=.)
###########################################################################
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 300fd3d..0c1417d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -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})
+
diff --git a/src/cyn-protocol.c b/src/cyn-protocol.c
index b7f4c01..c443996 100644
--- a/src/cyn-protocol.c
+++ b/src/cyn-protocol.c
@@ -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",
diff --git a/src/cyn-protocol.h b/src/cyn-protocol.h
index d8bcca0..e68d70c 100644
--- a/src/cyn-protocol.h
+++ b/src/cyn-protocol.h
@@ -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_[],
diff --git a/src/cyn-server.c b/src/cyn-server.c
index be72e92..97a44d1 100644
--- a/src/cyn-server.c
+++ b/src/cyn-server.c
@@ -44,6 +44,11 @@
#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);
diff --git a/src/cyn.c b/src/cyn.c
index fba8d57..7255821 100644
--- 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;
@@ -641,6 +644,24 @@ cyn_agent_remove(
/* 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(
) {
changeid.current = 1;
diff --git a/src/cyn.h b/src/cyn.h
index d583674..64366f0 100644
--- 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,11 +365,27 @@ 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
*
* @see cyn_changeid, cyn_changeid_string
diff --git a/src/cynagora-protocol.txt b/src/cynagora-protocol.txt
index 29e5b3a..ebebd4e 100644
--- a/src/cynagora-protocol.txt
+++ b/src/cynagora-protocol.txt
@@ -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):
diff --git a/src/cynagora.c b/src/cynagora.c
index 96c6466..b4bd7e9 100644
--- a/src/cynagora.c
+++ b/src/cynagora.c
@@ -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>
@@ -46,11 +47,15 @@
#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
*
@@ -140,6 +184,59 @@ flushw(
}
/**
+ * 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;
@@ -651,6 +749,80 @@ cynagora_destroy(
/* 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(
cynagora_t *cynagora,
uint32_t size
@@ -695,6 +867,55 @@ cynagora_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(
cynagora_t *cynagora,
const cynagora_key_t *key,
@@ -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;
+}
diff --git a/src/cynagora.h b/src/cynagora.h
index b77c0f4..aff1720 100644
--- a/src/cynagora.h
+++ b/src/cynagora.h
@@ -21,19 +21,24 @@
/******************************************************************************/
/******************************************************************************/
+/******************************************************************************/
+/* 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
@@ -150,10 +134,40 @@ cynagora_disconnect(
);
/**
+ * 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
@@ -233,13 +247,63 @@ cynagora_test(
);
/**
+ * 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
index 0000000..88ed995
--- /dev/null
+++ b/src/main-cynagora-agent.c
@@ -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;
+}