#include "state.h" #include #include #include "../util.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; } int Result = ng_connect(Control, R->Peer[0].Address, R->Peer[1].Address, R->Peer[0].Hook, R->Peer[1].Hook); close(Control); return Result; } 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; } if(R->PipeID) { int Result = DestroyNetgraphNode(R->PipeID, Control); close(Control); return Result; } else { 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) { // NOTE: I couldn't find a way to make string precision work with '$'. // We could skip all this buffer nonsense that way. size_t Length = strlen(Arg0); char Buffer[PATH_MAX]; if(Length > sizeof(Buffer)) Length = sizeof(Buffer) - 1; memset(Buffer, ' ', Length); Buffer[Length] = 0; fprintf( stderr, "usage: %1$s show\n" " %1$s node start [node-name] [configuration]\n" " %2$s stop [node-name]\n" " %2$s cmd [node-name] [command...]\n" " %2$s mod [node-name] [command...]\n" " %1$s link start [link-name] [node-A-name] [interface-A-name] [node-B-name] [interface-B-name]\n" " %2$s stop [link-name]\n" " %2$s setcfg [link-name] [configuration]\n" " %2$s getcfg [link-name]\n" " %2$s clrcfg [link-name]\n" " %2$s getstats [link-name]\n" " %2$s clrstats [link-name]\n" " %2$s getclrstats [link-name]\n", Arg0, Buffer); 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 link_unpipe(link_ *E, fd Control) { if(E->PipeID == 0) return 0; if(DestroyNetgraphNode(E->PipeID, Control) == -1) return -1; E->PipeID = 0; if(ng_connect(Control, E->Peer[0].Address, E->Peer[1].Address, E->Peer[0].Hook, E->Peer[1].Hook) == -1) return -1; return 0; } static int link_pipe(link_ *E, fd Control) { if(E->PipeID != 0) return 0; struct ngm_rmhook D; char Path0[NG_PATHSIZ]; strncpy(Path0, E->Peer[0].Address, sizeof(Path0)); strncpy(D.ourhook, E->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)); return -1; } return Create_pipe(&E->PipeID, Control, E->Peer[0].Address, E->Peer[1].Address, E->Peer[0].Hook, E->Peer[1].Hook); } static int link_setcfg(link_ *E, fd Control, char const *Config) { if(link_pipe(E, Control) == -1) goto error; if(*Config == '\0') return 0; char Path[NG_PATHSIZ]; snprintf(Path, sizeof(Path), "[%x]:", E->PipeID); if(NgSendAsciiMsg(Control, Path, "setcfg {%s}", Config) < 0) { // NOTE: We do not save if return code is not 0. // We should probably always save configuration. // We do not want to create a node and not save it. fprintf(stderr, "NgSendAsciiMsg failed: %s\n", strerror(errno)); } return 0; error: return -1; } static int link_get(link_ *E, fd Control, char const *What) { if(E->PipeID == 0) { fprintf(stderr, "link is not a pipe\n"); return -1; } char Path[NG_PATHSIZ]; snprintf(Path, sizeof(Path), "[%x]:", E->PipeID); if(NgSendAsciiMsg(Control, Path, "%s", What) < 0) { fprintf(stderr, "NgSendAsciiMsg failed: %s\n", strerror(errno)); return -1; } struct ng_mesg *Message; if(NgAllocRecvAsciiMsg(Control, &Message, Path) < 0) { fprintf(stderr, "NgAllocRecvAsciiMsg failed: %s\n", strerror(errno)); return -1; } printf("%s", Message->data); free(Message); return 0; } static int link_clrstats(link_ *E, fd Control) { if(E->PipeID == 0) { fprintf(stderr, "link is not a pipe\n"); return -1; } char Path[NG_PATHSIZ]; snprintf(Path, sizeof(Path), "[%x]:", E->PipeID); if(NgSendAsciiMsg(Control, Path, "clrstats") < 0) { fprintf(stderr, "NgSendAsciiMsg failed: %s\n", strerror(errno)); return -1; } return 0; } 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 { size_t LinkIndex = FindLinkIndex(E, LinkName); if(LinkIndex == SIZE_MAX) { fprintf(stderr, "Link %s does not exists\n", LinkName); return -1; } link_ *R = E->Link + LinkIndex; fd Control; if(NgMkSockNode(NULL, &Control, NULL) == -1) { fprintf(stderr, "Failed to create netgraph socket: %s\n", strerror(errno)); return -1; } if(strcmp(Command, "setcfg") == 0) { if(ArgCount != 1) usage(); Result = link_setcfg(R, Control, Arg[0]); } else if(strcmp(Command, "getcfg") == 0) { if(ArgCount != 0) usage(); Result = link_get(R, Control, "getcfg"); } else if(strcmp(Command, "clrcfg") == 0) { if(ArgCount != 0) usage(); Result = link_unpipe(R, Control); } else if(strcmp(Command, "getstats") == 0) { if(ArgCount != 0) usage(); Result = link_get(R, Control, "getstats"); } else if(strcmp(Command, "clrstats") == 0) { if(ArgCount != 0) usage(); Result = link_clrstats(R, Control); } else if(strcmp(Command, "getclrstats") == 0) { if(ArgCount != 0) usage(); Result = link_get(R, Control, "getclrstats"); } else { fprintf(stderr, "unknown argument: %s\n", Command); usage(); } close(Control); } 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; }