summaryrefslogtreecommitdiff
path: root/src/base
diff options
context:
space:
mode:
Diffstat (limited to 'src/base')
-rw-r--r--src/base/main.c797
-rw-r--r--src/base/state.c380
-rw-r--r--src/base/state.h72
3 files changed, 1249 insertions, 0 deletions
diff --git a/src/base/main.c b/src/base/main.c
new file mode 100644
index 0000000..27ef1c2
--- /dev/null
+++ b/src/base/main.c
@@ -0,0 +1,797 @@
+#include "state.h"
+#include <netgraph.h>
+
+static char const *Arg0;
+
+static size_t
+FindNodeIndex(experiment *E, char const *Name)
+{
+ for(size_t i = 0; i < E->NodeCount; ++i)
+ if(strcmp(E->Node[i].Name, Name) == 0) return i;
+ return SIZE_MAX;
+}
+
+static size_t
+FindLinkIndex(experiment *E, char const *Name)
+{
+ for(size_t i = 0; i < E->LinkCount; ++i)
+ if(strcmp(E->Link[i].Name, Name) == 0) return i;
+ return SIZE_MAX;
+}
+
+struct
+{
+ bool Called;
+ int Status;
+ ucl_object_t *Data;
+} typedef call_module_result;
+
+static call_module_result
+CallModule_a(char const *Module, char **Arg)
+{
+ call_module_result Result;
+ bzero(&Result, sizeof(Result));
+ Result.Called = false;
+#define RD 0
+#define WR 1
+ fd PipeI[2];
+ PipeI[RD] = -1;
+ PipeI[WR] = -1;
+
+ pid_t PID = -1;
+ struct ucl_parser *Parser = NULL;
+ if(pipe(PipeI) == -1)
+ {
+ fprintf(stderr, "pipe failed: %s\n", strerror(errno));
+ goto error;
+ }
+
+ PID = fork();
+ if(PID == -1)
+ {
+ fprintf(stderr, "fork failed: %s\n", strerror(errno));
+ goto error;
+ }
+ if(PID == 0)
+ {
+ close(STDIN_FILENO);
+ dup2(PipeI[WR], STDOUT_FILENO);
+ close(PipeI[RD]);
+ close(PipeI[WR]);
+ execvp(Module, Arg);
+ fprintf(stderr, "exec failed: %s\n", strerror(errno));
+ exit(-1);
+ }
+ close(PipeI[WR]);
+
+ // Read
+ Parser = ucl_parser_new(0);
+ unsigned char Buffer[8192]; // XXX: libucl currently does not handle partial objects
+ while(true)
+ {
+ ssize_t Count = read(PipeI[RD], Buffer, sizeof(Buffer));
+ if(Count == -1) break;
+ if(Count == 0) break;
+ if(ucl_parser_add_chunk(Parser, Buffer, (size_t)Count) == false)
+ {
+ fprintf(stderr, "Unable to parse configuration file: %s\n", ucl_parser_get_error(Parser));
+ goto error;
+ }
+ }
+ close(PipeI[RD]);
+ Result.Called = true;
+ Result.Data = ucl_parser_get_object(Parser);
+ ucl_parser_free(Parser);
+ waitpid(PID, &Result.Status, 0);
+ return Result;
+error:
+ if(Parser != NULL) ucl_parser_free(Parser);
+ if(Result.Data != NULL) ucl_object_unref(Result.Data);
+ if(Result.Called == true) waitpid(PID, &Result.Status, 0);
+ if(PipeI[0] != -1) close(PipeI[0]);
+ if(PipeI[1] != -1) close(PipeI[1]);
+ return Result;
+#undef RD
+#undef WR
+}
+
+static call_module_result
+CallModule_v(char const *Module, va_list Arg)
+{
+ va_list Arg_;
+ va_copy(Arg_, Arg);
+ size_t ArgCount = 0;
+ do {
+ char const *_ = va_arg(Arg_, char const *);
+ ++ArgCount;
+ if(_ == NULL) break;
+ } while(true);
+ va_end(Arg_);
+ char **Args = alloca(sizeof(char *) * ArgCount);
+ for(size_t i = 0; i < ArgCount; ++i) Args[i] = va_arg(Arg, char *);
+ return CallModule_a(Module, Args);
+}
+
+static call_module_result
+CallModule(char const *Module, ...)
+{
+ va_list Arg;
+ va_start(Arg, Module);
+ call_module_result Result = CallModule_v(Module, Arg);
+ va_end(Arg);
+ return Result;
+}
+
+static char *
+CallModuleBuffer(ucl_object_t *object)
+{
+ size_t const BufferSize = 1024 * 1024;
+ char *Buffer = calloc(BufferSize, 1);
+ if(Buffer == NULL)
+ {
+ fprintf(stderr, "calloc failed: %s\n", strerror(errno));
+ return NULL;
+ }
+ jprint_state S;
+ bzero(&S, sizeof(S));
+ S.F = fmemopen(Buffer, BufferSize, "w");
+ if(S.F == NULL)
+ {
+ fprintf(stderr, "fmemopen failed: %s\n", strerror(errno));
+ free(Buffer);
+ return NULL;
+ }
+ JPrint_ucl_object_t(&S, object);
+ fclose(S.F);
+ return Buffer;
+}
+
+static endpoint
+GetEndpointAddress(experiment *E, endpoint_configuration *Endpoint)
+{
+ endpoint R;
+ bzero(&R, sizeof(R));
+ size_t NodeIndex = FindNodeIndex(E, Endpoint->Node);
+ if(NodeIndex == SIZE_MAX)
+ {
+ fprintf(stderr, "node %s does not exist\n", Endpoint->Node);
+ return R;
+ }
+ node *Node = E->Node + NodeIndex;
+ char const *Module = Node->Configuration.Module;
+ char *Configuration = CallModuleBuffer(Node->Configuration.Configuration);
+ if(Configuration == NULL) return R;
+ char *Data = CallModuleBuffer(Node->Data);
+ if(Data == NULL)
+ {
+ free(Configuration);
+ return R;
+ }
+ call_module_result Result =
+ CallModule(Module, Module, "get-endpoint", Node->Name, Configuration, Data, Endpoint->Interface, NULL);
+ free(Configuration);
+ free(Data);
+ if(Result.Called == false)
+ {
+ fprintf(stderr, "failed to call module %s\n", Module);
+ return R;
+ }
+ if(!WIFEXITED(Result.Status) || (WEXITSTATUS(Result.Status) != 0))
+ {
+ fprintf(stderr, "%s exited with status 0x%x\n", Module, Result.Status);
+ if(Result.Data != NULL)
+ {
+ jprint_state S;
+ bzero(&S, sizeof(S));
+ S.F = stdout;
+ JPrint_ucl_object_t(&S, Result.Data);
+ ucl_object_unref(Result.Data);
+ }
+ return R;
+ }
+ if(Result.Data == NULL)
+ {
+ fprintf(stderr, "%s returned no data\n", Module);
+ return R;
+ }
+ char *Error = Parse_endpoint(&R, Result.Data, "");
+ ucl_object_unref(Result.Data);
+ if(Error != NULL)
+ {
+ fprintf(stderr, "link format error: %s\n", Error);
+ free(Error);
+ return R;
+ }
+ return R;
+}
+
+static int
+start_link_(link_ *R)
+{
+ fd Control;
+ if(NgMkSockNode(NULL, &Control, NULL) == -1)
+ {
+ fprintf(stderr, "Failed to create netgraph socket: %s\n", strerror(errno));
+ return -1;
+ }
+ struct ngm_connect D;
+ char Path0[NG_PATHSIZ];
+ strncpy(Path0, R->Peer[0].Address, sizeof(Path0));
+ strncpy(D.path, R->Peer[1].Address, sizeof(D.path));
+ strncpy(D.ourhook, R->Peer[0].Hook, sizeof(D.ourhook));
+ strncpy(D.peerhook, R->Peer[1].Hook, sizeof(D.peerhook));
+ if(NgSendMsg(Control, Path0, NGM_GENERIC_COOKIE, NGM_CONNECT, &D, sizeof(D)) == -1)
+ {
+ fprintf(stderr,
+ "ngctl connect %s %s %s %s: %s\n",
+ Path0,
+ D.path,
+ D.ourhook,
+ D.peerhook,
+ strerror(errno));
+ close(Control);
+ return -1;
+ }
+ close(Control);
+ return 0;
+}
+
+static int
+stop_link_(link_ *R)
+{
+ fd Control;
+ if(NgMkSockNode(NULL, &Control, NULL) == -1)
+ {
+ fprintf(stderr, "Failed to create netgraph socket: %s\n", strerror(errno));
+ return -1;
+ }
+ struct ngm_rmhook D;
+ char Path0[NG_PATHSIZ];
+ strncpy(Path0, R->Peer[0].Address, sizeof(Path0));
+ strncpy(D.ourhook, R->Peer[0].Hook, sizeof(D.ourhook));
+ if(NgSendMsg(Control, Path0, NGM_GENERIC_COOKIE, NGM_RMHOOK, &D, sizeof(D)) == -1)
+ {
+ fprintf(stderr, "ngctl rmhook %s %s: %s\n", Path0, D.ourhook, strerror(errno));
+ close(Control);
+ return -1;
+ }
+ close(Control);
+ return 0;
+}
+
+static int
+start_link(experiment *E,
+ char const *Name,
+ char const *NodeA,
+ char const *InterfaceA,
+ char const *NodeB,
+ char const *InterfaceB)
+{
+ if(FindLinkIndex(E, Name) != SIZE_MAX)
+ {
+ fprintf(stderr, "Link %s already exists\n", Name);
+ return -1;
+ }
+ link_configuration C;
+ {
+ C.Peer[0].Node = strdup(NodeA);
+ C.Peer[0].Interface = strdup(InterfaceA);
+ C.Peer[1].Node = strdup(NodeB);
+ C.Peer[1].Interface = strdup(InterfaceB);
+ }
+ link_ R;
+ { // Start
+ endpoint E0 = GetEndpointAddress(E, C.Peer + 0);
+ if(E0.Address == NULL || E0.Hook == NULL)
+ {
+ fprintf(stderr, "peer0 endpoint is invalid (%s, %s)\n", C.Peer[0].Node, C.Peer[0].Interface);
+ Free_link_configuration(&C);
+ return -1;
+ }
+ endpoint E1 = GetEndpointAddress(E, C.Peer + 1);
+ if(E1.Address == NULL || E1.Hook == NULL)
+ {
+ fprintf(stderr, "peer1 endpoint is invalid (%s, %s)\n", C.Peer[1].Node, C.Peer[1].Interface);
+ Free_link_configuration(&C);
+ Free_endpoint(&E0);
+ return -1;
+ }
+ bzero(&R, sizeof(R));
+ R.Peer[0] = E0;
+ R.Peer[1] = E1;
+ R.Configuration = C;
+ R.Name = strdup(Name);
+ if(start_link_(&R) == -1)
+ {
+ Free_link(&R);
+ return -1;
+ }
+ }
+ { // Insert
+ size_t LinkCount = E->LinkCount + 1;
+ link_ *Link = reallocarray(E->Link, LinkCount, sizeof(*Link));
+ if(Link == NULL)
+ {
+ fprintf(stderr, "Failed to reallcoate links: %s\n", strerror(errno));
+ fprintf(stderr, "Link is running, but its state will be lost... Dumping it to stderr:\n");
+ jprint_state S;
+ bzero(&S, sizeof(S));
+ S.F = stderr;
+ Save_link(&S, &R);
+ Free_link(&R);
+ return -1;
+ }
+ E->Link = Link;
+ E->LinkCount = LinkCount;
+ Link[LinkCount - 1] = R;
+ }
+ return 0;
+}
+
+static int
+stop_link(experiment *E, char const *Name)
+{
+ size_t LinkIndex = FindLinkIndex(E, Name);
+ if(LinkIndex == SIZE_MAX)
+ {
+ fprintf(stderr, "Link %s does not exists\n", Name);
+ return -1;
+ }
+ link_ *R = E->Link + LinkIndex;
+ if(stop_link_(R) == -1)
+ {
+ fprintf(stderr, "Failed to stop link %s\n", Name);
+ return -1;
+ }
+ Free_link(R);
+ memmove(E->Link + LinkIndex, E->Link + LinkIndex + 1, (E->LinkCount - LinkIndex - 1) * sizeof(link_));
+ --E->LinkCount;
+ return 0;
+}
+
+static void __attribute__((noreturn)) cmd(node *N, size_t ArgCount, char **Arg)
+{
+ char *Configuration = CallModuleBuffer(N->Configuration.Configuration);
+ if(Configuration == NULL) exit(-1);
+ char *Data = CallModuleBuffer(N->Data);
+ if(Data == NULL)
+ {
+ free(Configuration);
+ exit(-1);
+ }
+ size_t ExtraArguments = 5;
+ char **Args = alloca(sizeof(char *) * (ArgCount + ExtraArguments + 1));
+ Args[0] = N->Configuration.Module;
+ Args[1] = "cmd";
+ Args[2] = N->Name;
+ Args[3] = Configuration;
+ Args[4] = Data;
+ memcpy(Args + ExtraArguments, Arg, sizeof(char *) * ArgCount);
+ Args[ArgCount + ExtraArguments] = NULL;
+ execvp(N->Configuration.Module, Args);
+ fprintf(stderr, "execvp failed: %s\n", strerror(errno));
+ exit(-1);
+}
+
+static int
+start_node(experiment *E, char const *Name, ucl_object_t *root)
+{
+ if(FindNodeIndex(E, Name) != SIZE_MAX)
+ {
+ fprintf(stderr, "Node %s already exists\n", Name);
+ return -1;
+ }
+ node_configuration C;
+ { // Load
+ char *Error = Parse_node_configuration(&C, root, "");
+ if(Error != NULL)
+ {
+ fprintf(stderr, "Node configuration error: %s\n", Error);
+ free(Error);
+ return -1;
+ }
+ }
+ node R;
+ { // Start
+ bzero(&R, sizeof(R));
+ char *Configuration = CallModuleBuffer(C.Configuration);
+ if(Configuration == NULL)
+ {
+ Free_node_configuration(&C);
+ return -1;
+ }
+ call_module_result Result = CallModule(C.Module, C.Module, "start", Name, Configuration, NULL);
+ free(Configuration);
+ if(Result.Called == false)
+ {
+ fprintf(stderr, "failed to call module %s\n", C.Module);
+ Free_node_configuration(&C);
+ return -1;
+ }
+ if(!WIFEXITED(Result.Status) || (WEXITSTATUS(Result.Status) != 0))
+ {
+ fprintf(stderr, "%s exited with status 0x%x\n", C.Module, Result.Status);
+ if(Result.Data != NULL)
+ {
+ jprint_state S;
+ bzero(&S, sizeof(S));
+ S.F = stdout;
+ JPrint_ucl_object_t(&S, Result.Data);
+ ucl_object_unref(Result.Data);
+ }
+ Free_node_configuration(&C);
+ return -1;
+ }
+ if(Result.Data == NULL)
+ {
+ fprintf(stderr, "%s returned no data\n", C.Module);
+ Free_node_configuration(&C);
+ return -1;
+ }
+ R.Data = Result.Data;
+ R.Configuration = C;
+ R.Name = strdup(Name);
+ }
+ { // Insert
+ size_t NodeCount = E->NodeCount + 1;
+ node *Node = reallocarray(E->Node, NodeCount, sizeof(*Node));
+ if(Node == NULL)
+ {
+ fprintf(stderr, "Failed to reallcoate nodes: %s\n", strerror(errno));
+ fprintf(stderr, "Node is running, but its state will be lost... Dumping it to stderr:\n");
+ jprint_state S;
+ bzero(&S, sizeof(S));
+ S.F = stderr;
+ Save_node(&S, &R);
+ Free_node(&R);
+ return -1;
+ }
+ E->Node = Node;
+ E->NodeCount = NodeCount;
+ Node[NodeCount - 1] = R;
+ }
+ return 0;
+}
+
+static int
+stop_node(experiment *E, char const *Name)
+{
+ size_t NodeIndex = FindNodeIndex(E, Name);
+ if(NodeIndex == SIZE_MAX)
+ {
+ fprintf(stderr, "Node %s does not exists\n", Name);
+ return -1;
+ }
+ node *R = E->Node + NodeIndex;
+ { // Stop all links
+ for(size_t i = 0; i < E->LinkCount;)
+ {
+ link_ *L = E->Link + i;
+ if(strcmp(Name, L->Configuration.Peer[0].Node) == 0 ||
+ strcmp(Name, L->Configuration.Peer[1].Node) == 0)
+ {
+ fprintf(stderr, "destroying link '%s'\n", L->Name);
+ if(stop_link(E, L->Name) == -1)
+ {
+ fprintf(stderr, "failed to destroy link '%s'\n", L->Name);
+ return -1;
+ }
+ } else
+ {
+ ++i;
+ }
+ }
+ }
+ { // Stop
+ char *Configuration = CallModuleBuffer(R->Configuration.Configuration);
+ if(Configuration == NULL) return -1;
+ char *Data = CallModuleBuffer(R->Data);
+ if(Data == NULL)
+ {
+ free(Configuration);
+ return -1;
+ }
+ call_module_result Result =
+ CallModule(R->Configuration.Module, R->Configuration.Module, "stop", Name, Configuration, Data, NULL);
+ free(Configuration);
+ free(Data);
+ if(Result.Data != NULL)
+ {
+ jprint_state S;
+ bzero(&S, sizeof(S));
+ S.F = stdout;
+ JPrint_ucl_object_t(&S, Result.Data);
+ ucl_object_unref(Result.Data);
+ }
+ if(Result.Called == false)
+ {
+ fprintf(stderr, "failed to call module %s\n", R->Configuration.Module);
+ return -1;
+ }
+ if(!WIFEXITED(Result.Status) || (WEXITSTATUS(Result.Status) != 0))
+ {
+ fprintf(stderr, "%s exited with status 0x%x\n", R->Configuration.Module, Result.Status);
+ return -1;
+ }
+ }
+ { // Remove
+ Free_node(R);
+ memmove(E->Node + NodeIndex, E->Node + NodeIndex + 1, (E->NodeCount - NodeIndex - 1) * sizeof(node));
+ --E->NodeCount;
+ }
+ return 0;
+}
+
+static int
+mod(node *N, size_t ArgCount, char **Arg)
+{
+ char *Configuration = CallModuleBuffer(N->Configuration.Configuration);
+ if(Configuration == NULL) return -1;
+ char *Data = CallModuleBuffer(N->Data);
+ if(Data == NULL)
+ {
+ free(Configuration);
+ return -1;
+ }
+ size_t ExtraArguments = 5;
+ char **Args = alloca(sizeof(char *) * (ArgCount + ExtraArguments + 1));
+ Args[0] = N->Configuration.Module;
+ Args[1] = "mod";
+ Args[2] = N->Name;
+ Args[3] = Configuration;
+ Args[4] = Data;
+ memcpy(Args + ExtraArguments, Arg, sizeof(char *) * ArgCount);
+ Args[ArgCount + ExtraArguments] = NULL;
+ char const *Module = N->Configuration.Module;
+ call_module_result Result = CallModule_a(Module, Args);
+ free(Configuration);
+ free(Data);
+ if(Result.Called == false)
+ {
+ fprintf(stderr, "failed to call module %s\n", Module);
+ return -1;
+ }
+ if(!WIFEXITED(Result.Status) || (WEXITSTATUS(Result.Status) != 0))
+ {
+ fprintf(stderr, "%s exited with status 0x%x\n", Module, Result.Status);
+ if(Result.Data != NULL)
+ {
+ jprint_state S;
+ bzero(&S, sizeof(S));
+ S.F = stdout;
+ JPrint_ucl_object_t(&S, Result.Data);
+ ucl_object_unref(Result.Data);
+ }
+ return -1;
+ }
+ if(Result.Data == NULL)
+ {
+ fprintf(stderr, "%s returned no data\n", Module);
+ return -1;
+ }
+ ucl_object_unref(N->Data);
+ N->Data = Result.Data;
+ return 0;
+}
+
+static int
+LoadState(experiment *E, fd FD)
+{
+ struct ucl_parser *Parser = ucl_parser_new(0);
+ if(ucl_parser_add_fd(Parser, FD) == false)
+ {
+ fprintf(stderr, "Unable to parse state file: %s\n", ucl_parser_get_error(Parser));
+ ucl_parser_free(Parser);
+ exit(EX_CONFIG);
+ }
+ ucl_object_t *root = ucl_parser_get_object(Parser);
+ char *Error = Parse_experiment(E, root, "");
+ if(Error != NULL)
+ {
+ fprintf(stderr, "Config error: %s\n", Error);
+ free(Error);
+ ucl_object_unref(root);
+ ucl_parser_free(Parser);
+ exit(EX_DATAERR);
+ }
+ ucl_object_unref(root);
+ ucl_parser_free(Parser);
+ return 0;
+}
+
+static void __attribute__((noreturn)) usage(void)
+{
+ fprintf(
+ stderr,
+ "usage: %1$s show\n"
+ " %1$s node start [node-name] [configuration]\n"
+ " %1$s node stop [node-name]\n"
+ " %1$s link start [link-name] [node-A-name] [interface-A-name] [node-B-name] [interface-B-name]\n"
+ " %1$s link stop [link-name]\n"
+ " %1$s node cmd [node-name] [command...]\n"
+ " %1$s node mod [node-name] [command...]\n",
+ Arg0);
+ exit(EX_USAGE);
+}
+
+static int
+SaveState(experiment *E, fd FD)
+{
+ ftruncate(FD, 0);
+ FILE *F = fdopen(FD, "w");
+ if(F == NULL)
+ {
+ fprintf(stderr, "failed to save state (fdopen: %s)\n", strerror(errno));
+ return -1;
+ }
+ jprint_state S;
+ bzero(&S, sizeof(S));
+ S.F = F;
+ Save_experiment(&S, E);
+ fclose(F);
+ return 0;
+}
+
+static int
+Command_node(experiment *E, fd StateFD, int ArgCount, char **Arg)
+{
+ if(ArgCount < 2) usage();
+ char const *Command = Arg[0];
+ char const *NodeName = Arg[1];
+ if(isprints(NodeName) == false)
+ {
+ fprintf(stderr, "Node name is non-printable\n");
+ return -1;
+ }
+ ArgCount -= 2;
+ Arg += 2;
+ if(strcmp(Command, "start") == 0)
+ {
+ if(ArgCount != 1) usage();
+ char const *Configuration = Arg[0];
+ struct ucl_parser *Parser = ucl_parser_new(0);
+ if(ucl_parser_add_string(Parser, Configuration, 0) == false)
+ {
+ fprintf(stderr, "Unable to parse configuration file: %s\n", ucl_parser_get_error(Parser));
+ ucl_parser_free(Parser);
+ return -1;
+ }
+ ucl_object_t *root = ucl_parser_get_object(Parser);
+ if(root == NULL)
+ {
+ fprintf(stderr, "configuration error: %s\n", ucl_parser_get_error(Parser));
+ ucl_parser_free(Parser);
+ return -1;
+ }
+ ucl_parser_free(Parser);
+ int Result = start_node(E, NodeName, root);
+ ucl_object_unref(root);
+ if(Result == 0) SaveState(E, StateFD);
+ return Result;
+ } else if(strcmp(Command, "stop") == 0)
+ {
+ if(ArgCount != 0) usage();
+ stop_node(E, NodeName);
+ return SaveState(E, StateFD);
+ } else if(strcmp(Command, "cmd") == 0)
+ {
+ size_t NodeIndex = FindNodeIndex(E, NodeName);
+ if(NodeIndex == SIZE_MAX)
+ {
+ fprintf(stderr, "Node %s does not exists\n", NodeName);
+ return -1;
+ }
+ node *N = E->Node + NodeIndex;
+ cmd(N, (size_t)ArgCount, Arg);
+ } else if(strcmp(Command, "mod") == 0)
+ {
+ size_t NodeIndex = FindNodeIndex(E, NodeName);
+ if(NodeIndex == SIZE_MAX)
+ {
+ fprintf(stderr, "Node %s does not exists\n", NodeName);
+ return -1;
+ }
+ node *N = E->Node + NodeIndex;
+ if(mod(N, (size_t)ArgCount, Arg) == -1) return -1;
+ return SaveState(E, StateFD);
+ } else
+ {
+ fprintf(stderr, "unknown argument: %s\n", Command);
+ usage();
+ }
+}
+
+static int
+Command_link(experiment *E, fd StateFD, int ArgCount, char **Arg)
+{
+ if(ArgCount < 2) usage();
+ char const *Command = Arg[0];
+ char const *LinkName = Arg[1];
+ if(isprints(LinkName) == false)
+ {
+ fprintf(stderr, "link name is non-printable\n");
+ return -1;
+ }
+ ArgCount -= 2;
+ Arg += 2;
+ int Result;
+ if(strcmp(Command, "start") == 0)
+ {
+ if(ArgCount != 4) usage();
+ Result = start_link(E, LinkName, Arg[0], Arg[1], Arg[2], Arg[3]);
+ } else if(strcmp(Command, "stop") == 0)
+ {
+ if(ArgCount != 0) usage();
+ Result = stop_link(E, LinkName);
+ } else
+ {
+ fprintf(stderr, "unknown argument: %s\n", Command);
+ usage();
+ }
+ if(Result == 0) SaveState(E, StateFD);
+ return Result;
+}
+
+static int
+main_(int ArgCount, char **Arg, experiment *E, fd StateFD)
+{
+ char const *Command = Arg[0];
+ --ArgCount;
+ ++Arg;
+ if(strcmp(Command, "show") == 0)
+ {
+ if(ArgCount != 0) usage();
+ return SaveState(E, STDOUT_FILENO);
+ } else if(strcmp(Command, "node") == 0)
+ {
+ return Command_node(E, StateFD, ArgCount, Arg);
+ } else if(strcmp(Command, "link") == 0)
+ {
+ return Command_link(E, StateFD, ArgCount, Arg);
+ } else
+ {
+ fprintf(stderr, "unknown argument: %s\n", Command);
+ usage();
+ }
+}
+
+int
+main(int ArgCount, char **Arg)
+{
+ Arg0 = Arg[0];
+ int C;
+ while((C = getopt(ArgCount, Arg, "")) != -1)
+ {
+ switch(C)
+ {
+ case '?':
+ default: usage();
+ }
+ }
+ ArgCount -= optind;
+ Arg += optind;
+ if(ArgCount == 0) usage();
+ char const *StatePath = getenv("SF_PATH");
+ if(StatePath == NULL)
+ {
+ fprintf(stderr, "SF_PATH environment variable is unset\n");
+ exit(-1);
+ }
+ fd StateFD = open(StatePath, O_RDWR | O_CREAT | O_EXLOCK | O_CLOEXEC | O_NONBLOCK, 0644);
+ if(StateFD == -1)
+ {
+ fprintf(stderr, "failed to acquire state: %s\n", strerror(errno));
+ exit(-1);
+ }
+ static experiment E_;
+ experiment *E = &E_;
+ if(LoadState(E, StateFD) == -1)
+ {
+ close(StateFD);
+ exit(-1);
+ }
+ int Result = main_(ArgCount, Arg, E, StateFD);
+ Free_experiment(E);
+ close(StateFD);
+ return Result;
+}
diff --git a/src/base/state.c b/src/base/state.c
new file mode 100644
index 0000000..50ebe4e
--- /dev/null
+++ b/src/base/state.c
@@ -0,0 +1,380 @@
+#include "state.h"
+
+// FREE
+
+void
+Free_endpoint(endpoint *E)
+{
+ free(E->Address);
+ free(E->Hook);
+}
+
+void
+Free_endpoint_configuration(endpoint_configuration *E)
+{
+ free(E->Node);
+ free(E->Interface);
+}
+
+void
+Free_link_configuration(link_configuration *E)
+{
+ Free_endpoint_configuration(E->Peer + 0);
+ Free_endpoint_configuration(E->Peer + 1);
+}
+
+void
+Free_link(link_ *E)
+{
+ free(E->Name);
+ Free_link_configuration(&E->Configuration);
+ Free_endpoint(E->Peer + 0);
+ Free_endpoint(E->Peer + 1);
+}
+
+void
+Free_node_configuration(node_configuration *E)
+{
+ free(E->Module);
+ ucl_object_unref(E->Configuration);
+}
+
+void
+Free_node(node *E)
+{
+ free(E->Name);
+ Free_node_configuration(&E->Configuration);
+ ucl_object_unref(E->Data);
+}
+
+void
+Free_experiment(experiment *E)
+{
+ for(size_t i = 0; i < E->NodeCount; ++i) Free_node(E->Node + i);
+ free(E->Node);
+ for(size_t i = 0; i < E->LinkCount; ++i) Free_link(E->Link + i);
+ free(E->Link);
+}
+
+// PARSE
+
+char *
+Parse_endpoint(endpoint *E, ucl_object_t const *root, char const *Position)
+{
+ char *Error;
+ if(root == NULL || root->type != UCL_OBJECT)
+ {
+ if(*Position == '\0') Position = ".";
+ asprintf(&Error, "%s is not an object", Position);
+ goto error;
+ }
+ ucl_object_t const *address = ucl_object_lookup(root, "address");
+ if(address == NULL || address->type != UCL_STRING)
+ {
+ asprintf(&Error, "%s.address is not a string", Position);
+ goto error;
+ }
+ ucl_object_t const *hook = ucl_object_lookup(root, "hook");
+ if(hook == NULL || hook->type != UCL_STRING)
+ {
+ asprintf(&Error, "%s.hook is not a string", Position);
+ goto error;
+ }
+ E->Address = strdup(ucl_object_tostring(address));
+ E->Hook = strdup(ucl_object_tostring(hook));
+ return NULL;
+error:
+ return Error;
+}
+
+char *
+Parse_endpoint_configuration(endpoint_configuration *E, ucl_object_t const *root, char const *Position)
+{
+ char *Error;
+ UCL_CHECK_ROOT(OBJECT);
+ ucl_object_t const *node = ucl_object_lookup(root, "node");
+ UCL_CHECK(node, STRING);
+ ucl_object_t const *interface = ucl_object_lookup(root, "interface");
+ UCL_CHECK(interface, STRING);
+ E->Node = strdup(ucl_object_tostring(node));
+ E->Interface = strdup(ucl_object_tostring(interface));
+ return NULL;
+error:
+ return Error;
+}
+
+char *
+Parse_link_configuration(link_configuration *E, ucl_object_t const *root, char const *Position)
+{
+ char *Error;
+ size_t EndpointAt = 0;
+ UCL_CHECK_ROOT(OBJECT);
+ ucl_object_t const *peer0 = ucl_object_lookup(root, "peer0");
+ {
+ char *NewPosition;
+ asprintf(&NewPosition, "%s.peer0", Position);
+ Error = Parse_endpoint_configuration(&E->Peer[EndpointAt], peer0, NewPosition);
+ free(NewPosition);
+ if(Error != NULL) goto error;
+ ++EndpointAt;
+ }
+ ucl_object_t const *peer1 = ucl_object_lookup(root, "peer1");
+ {
+ char *NewPosition;
+ asprintf(&NewPosition, "%s.peer1", Position);
+ Error = Parse_endpoint_configuration(&E->Peer[EndpointAt], peer1, NewPosition);
+ free(NewPosition);
+ if(Error != NULL) goto error;
+ ++EndpointAt;
+ }
+ return NULL;
+error:
+ for(size_t i = 0; i < EndpointAt; ++i) Free_endpoint_configuration(E->Peer + i);
+ return Error;
+}
+
+char *
+Parse_link(link_ *E, ucl_object_t const *root, char const *Position)
+{
+ char *Error;
+ size_t EndpointAt = 0;
+ bool Configuration = false;
+ UCL_CHECK_ROOT(OBJECT);
+ ucl_object_t const *configuration = ucl_object_lookup(root, "configuration");
+ {
+ char *NewPosition;
+ asprintf(&NewPosition, "%s.configuration", Position);
+ Error = Parse_link_configuration(&E->Configuration, configuration, NewPosition);
+ free(NewPosition);
+ if(Error != NULL) goto error;
+ Configuration = true;
+ }
+ ucl_object_t const *peer0 = ucl_object_lookup(root, "peer0");
+ {
+ char *NewPosition;
+ asprintf(&NewPosition, "%s.peer0", Position);
+ Error = Parse_endpoint(E->Peer + EndpointAt, peer0, NewPosition);
+ free(NewPosition);
+ if(Error != NULL) goto error;
+ ++EndpointAt;
+ }
+ ucl_object_t const *peer1 = ucl_object_lookup(root, "peer1");
+ {
+ char *NewPosition;
+ asprintf(&NewPosition, "%s.peer1", Position);
+ Error = Parse_endpoint(E->Peer + EndpointAt, peer1, NewPosition);
+ free(NewPosition);
+ if(Error != NULL) goto error;
+ ++EndpointAt;
+ }
+ return NULL;
+error:
+ for(size_t i = 0; i < EndpointAt; ++i) Free_endpoint(E->Peer + i);
+ if(Configuration == true) Free_link_configuration(&E->Configuration);
+ return Error;
+}
+
+char *
+Parse_node_configuration(node_configuration *E, ucl_object_t const *root, char const *Position)
+{
+ char *Error;
+ UCL_CHECK_ROOT(OBJECT);
+ ucl_object_t const *module = ucl_object_lookup(root, "module");
+ UCL_CHECK(module, STRING);
+ ucl_object_t const *configuration = ucl_object_lookup(root, "configuration");
+ if(configuration == NULL)
+ {
+ asprintf(&Error, "%s.configuration does not exist", Position);
+ goto error;
+ }
+ E->Module = strdup(ucl_object_tostring(module));
+ E->Configuration = ucl_object_copy(configuration);
+ return NULL;
+error:
+ return Error;
+}
+
+char *
+Parse_node(node *E, ucl_object_t const *root, char const *Position)
+{
+ char *Error;
+ bool Configuration = false;
+ UCL_CHECK_ROOT(OBJECT);
+ ucl_object_t const *configuration = ucl_object_lookup(root, "configuration");
+ {
+ char *NewPosition;
+ asprintf(&NewPosition, "%s.configuration", Position);
+ Error = Parse_node_configuration(&E->Configuration, configuration, NewPosition);
+ free(NewPosition);
+ if(Error != NULL) goto error;
+ Configuration = true;
+ }
+ ucl_object_t const *data = ucl_object_lookup(root, "data");
+ if(data == NULL)
+ {
+ asprintf(&Error, "%s.does not exist", Position);
+ goto error;
+ }
+ E->Data = ucl_object_copy(data);
+ return NULL;
+error:
+ if(Configuration == true) Free_node_configuration(&E->Configuration);
+ return Error;
+}
+
+char *
+Parse_experiment(experiment *E, ucl_object_t const *root, char const *Position)
+{
+ char *Error;
+ size_t NodeAt = 0;
+ size_t LinkAt = 0;
+ if(root == NULL) return NULL;
+ UCL_CHECK_ROOT(OBJECT);
+ {
+ ucl_object_t const *nodes = ucl_object_lookup(root, "nodes");
+ UCL_CHECK(nodes, OBJECT);
+ E->NodeCount = nodes->len;
+ E->Node = calloc(sizeof(node), E->NodeCount);
+ ucl_object_t const *v;
+ ucl_object_iter_t it = NULL;
+ while((v = ucl_iterate_object(nodes, &it, true)))
+ {
+ char const *k = ucl_object_key(v);
+ if(k == NULL) continue;
+ node *I = E->Node + NodeAt;
+ char *NewPosition;
+ asprintf(&NewPosition, "%s.nodes['%s']", Position, k);
+ Error = Parse_node(I, v, NewPosition);
+ free(NewPosition);
+ if(Error != NULL) goto error;
+ I->Name = strdup(k);
+ NodeAt++;
+ }
+ }
+ {
+ ucl_object_t const *links = ucl_object_lookup(root, "links");
+ UCL_CHECK(links, OBJECT);
+ E->LinkCount = links->len;
+ E->Link = calloc(sizeof(link_), E->LinkCount);
+ ucl_object_t const *v;
+ ucl_object_iter_t it = NULL;
+ while((v = ucl_iterate_object(links, &it, true)))
+ {
+ char const *k = ucl_object_key(v);
+ if(k == NULL) continue;
+ link_ *I = E->Link + LinkAt;
+ char *NewPosition;
+ asprintf(&NewPosition, "%s.links['%s']", Position, k);
+ Error = Parse_link(I, v, NewPosition);
+ free(NewPosition);
+ if(Error != NULL) goto error;
+ I->Name = strdup(k);
+ LinkAt++;
+ }
+ }
+ return NULL;
+error:
+ E->NodeCount = NodeAt;
+ E->LinkCount = LinkAt;
+ Free_experiment(E);
+ return Error;
+}
+
+// SAVE
+
+void
+Save_endpoint(jprint_state *S, endpoint const *E)
+{
+ JPrintObjectBegin(S);
+ JPrintMember(S, "address");
+ JPrint_string(S, E->Address);
+ JPrintMember(S, "hook");
+ JPrint_string(S, E->Hook);
+ JPrintObjectEnd(S);
+}
+
+void
+Save_endpoint_configuration(jprint_state *S, endpoint_configuration const *E)
+{
+ JPrintObjectBegin(S);
+ JPrintMember(S, "node");
+ JPrint_string(S, E->Node);
+ JPrintMember(S, "interface");
+ JPrint_string(S, E->Interface);
+ JPrintObjectEnd(S);
+}
+
+void
+Save_link_configuration(jprint_state *S, link_configuration const *E)
+{
+ JPrintObjectBegin(S);
+ JPrintMember(S, "peer0");
+ Save_endpoint_configuration(S, E->Peer + 0);
+ JPrintMember(S, "peer1");
+ Save_endpoint_configuration(S, E->Peer + 1);
+ JPrintObjectEnd(S);
+}
+
+void
+Save_link(jprint_state *S, link_ const *E)
+{
+ JPrintObjectBegin(S);
+ JPrintMember(S, "configuration");
+ Save_link_configuration(S, &E->Configuration);
+ JPrintMember(S, "peer0");
+ Save_endpoint(S, E->Peer + 0);
+ JPrintMember(S, "peer1");
+ Save_endpoint(S, E->Peer + 1);
+ JPrintObjectEnd(S);
+}
+
+void
+Save_node_configuration(jprint_state *S, node_configuration const *E)
+{
+ JPrintObjectBegin(S);
+ JPrintMember(S, "module");
+ JPrint_string(S, E->Module);
+ JPrintMember(S, "configuration");
+ JPrint_ucl_object_t(S, E->Configuration);
+ JPrintObjectEnd(S);
+}
+
+void
+Save_node(jprint_state *S, node const *E)
+{
+ JPrintObjectBegin(S);
+ JPrintMember(S, "configuration");
+ Save_node_configuration(S, &E->Configuration);
+ JPrintMember(S, "data");
+ JPrint_ucl_object_t(S, E->Data);
+ JPrintObjectEnd(S);
+}
+
+void
+Save_experiment(jprint_state *S, experiment const *E)
+{
+ JPrintObjectBegin(S);
+ {
+ JPrintMember(S, "nodes");
+ JPrintObjectBegin(S);
+ for(size_t i = 0; i < E->NodeCount; ++i)
+ {
+ node *I = E->Node + i;
+ JPrintMember(S, I->Name);
+ Save_node(S, I);
+ }
+ JPrintObjectEnd(S);
+ }
+ {
+ JPrintMember(S, "links");
+ JPrintObjectBegin(S);
+ for(size_t i = 0; i < E->LinkCount; ++i)
+ {
+ link_ *I = E->Link + i;
+ JPrintMember(S, I->Name);
+ Save_link(S, E->Link + i);
+ }
+ JPrintObjectEnd(S);
+ }
+ JPrintObjectEnd(S);
+}
diff --git a/src/base/state.h b/src/base/state.h
new file mode 100644
index 0000000..3bbccb5
--- /dev/null
+++ b/src/base/state.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#include "../jprint.h"
+
+struct
+{
+ char *Address;
+ char *Hook;
+} typedef endpoint;
+
+struct
+{
+ char *Node;
+ char *Interface;
+} typedef endpoint_configuration;
+
+struct
+{
+ endpoint_configuration Peer[2];
+} typedef link_configuration;
+
+struct
+{
+ char *Name;
+ link_configuration Configuration;
+ endpoint Peer[2];
+} typedef link_;
+
+struct
+{
+ char *Module;
+ ucl_object_t *Configuration;
+} typedef node_configuration;
+
+struct
+{
+ char *Name;
+ node_configuration Configuration;
+ ucl_object_t *Data;
+} typedef node;
+
+struct
+{
+ size_t NodeCount;
+ node *Node;
+ size_t LinkCount;
+ link_ *Link;
+} typedef experiment;
+
+void Free_endpoint(endpoint *);
+void Free_endpoint_configuration(endpoint_configuration *);
+void Free_link_configuration(link_configuration *);
+void Free_link(link_ *);
+void Free_node_configuration(node_configuration *);
+void Free_node(node *);
+void Free_experiment(experiment *);
+
+char *Parse_endpoint(endpoint *, ucl_object_t const *, char const *Position);
+char *Parse_endpoint_configuration(endpoint_configuration *, ucl_object_t const *, char const *Position);
+char *Parse_link_configuration(link_configuration *, ucl_object_t const *, char const *Position);
+char *Parse_link(link_ *, ucl_object_t const *, char const *Position);
+char *Parse_node_configuration(node_configuration *, ucl_object_t const *, char const *Position);
+char *Parse_node(node *, ucl_object_t const *, char const *Position);
+char *Parse_experiment(experiment *, ucl_object_t const *, char const *Position);
+
+void Save_endpoint(jprint_state *, endpoint const *);
+void Save_endpoint_configuration(jprint_state *, endpoint_configuration const *);
+void Save_link_configuration(jprint_state *, link_configuration const *);
+void Save_link(jprint_state *, link_ const *);
+void Save_node_configuration(jprint_state *, node_configuration const *);
+void Save_node(jprint_state *, node const *);
+void Save_experiment(jprint_state *, experiment const *);