diff options
Diffstat (limited to 'src/main-cynagora-agent.c')
-rw-r--r-- | src/main-cynagora-agent.c | 534 |
1 files changed, 534 insertions, 0 deletions
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; +} |