diff options
author | Jose Bollo <jose.bollo@iot.bzh> | 2019-10-14 18:02:38 +0200 |
---|---|---|
committer | Jose Bollo <jose.bollo@iot.bzh> | 2019-10-16 11:05:46 +0200 |
commit | d927b8c4d931b3fa4c5744778976081e9218a838 (patch) | |
tree | 41a8403daf9d90caa46429d625da4ff92b51261c | |
parent | f53a76ce91ab83c7345a104b57f148738101c58d (diff) |
Implementation of agent protocol and tool
Signed-off-by: Jose Bollo <jose.bollo@iot.bzh>
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/CMakeLists.txt | 8 | ||||
-rw-r--r-- | src/cyn-protocol.c | 2 | ||||
-rw-r--r-- | src/cyn-protocol.h | 2 | ||||
-rw-r--r-- | src/cyn-server.c | 174 | ||||
-rw-r--r-- | src/cyn.c | 35 | ||||
-rw-r--r-- | src/cyn.h | 23 | ||||
-rw-r--r-- | src/cynagora-protocol.txt | 4 | ||||
-rw-r--r-- | src/cynagora.c | 636 | ||||
-rw-r--r-- | src/cynagora.h | 280 | ||||
-rw-r--r-- | src/main-cynagora-agent.c | 534 |
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); @@ -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; @@ -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; +} |