diff options
| author | dautor <karlo98.m@gmail.com> | 2024-11-16 13:22:54 +0100 |
|---|---|---|
| committer | dautor <karlo98.m@gmail.com> | 2024-11-16 17:54:38 +0100 |
| commit | 47778ccd67cbb3fb70dda706911d3166038ca010 (patch) | |
| tree | 906bf0537d14f5ce8e2528736fb89a3499ada214 /src/base/main.c | |
Import project
Diffstat (limited to 'src/base/main.c')
| -rw-r--r-- | src/base/main.c | 797 |
1 files changed, 797 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; +} |
