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 | |
Import project
Diffstat (limited to 'src')
| -rw-r--r-- | src/base.h | 118 | ||||
| -rw-r--r-- | src/base/main.c | 797 | ||||
| -rw-r--r-- | src/base/state.c | 380 | ||||
| -rw-r--r-- | src/base/state.h | 72 | ||||
| -rw-r--r-- | src/jprint.c | 176 | ||||
| -rw-r--r-- | src/jprint.h | 26 | ||||
| -rw-r--r-- | src/lamina/main.c | 583 | ||||
| -rw-r--r-- | src/module/module.c | 126 | ||||
| -rw-r--r-- | src/module/module.h | 14 | ||||
| -rw-r--r-- | src/sf.bridge/main.c | 166 | ||||
| -rw-r--r-- | src/sf.bridge/state.c | 58 | ||||
| -rw-r--r-- | src/sf.bridge/state.h | 20 | ||||
| -rw-r--r-- | src/sf.eiface/main.c | 155 | ||||
| -rw-r--r-- | src/sf.eiface/state.c | 63 | ||||
| -rw-r--r-- | src/sf.eiface/state.h | 21 | ||||
| -rw-r--r-- | src/sf.jail/main.c | 525 | ||||
| -rw-r--r-- | src/sf.jail/state.c | 396 | ||||
| -rw-r--r-- | src/sf.jail/state.h | 93 | ||||
| -rw-r--r-- | src/sf.skel/main.c | 116 | ||||
| -rw-r--r-- | src/sf.skel/state.c | 49 | ||||
| -rw-r--r-- | src/sf.skel/state.h | 19 | ||||
| -rw-r--r-- | src/util.c | 396 | ||||
| -rw-r--r-- | src/util.h | 28 |
23 files changed, 4397 insertions, 0 deletions
diff --git a/src/base.h b/src/base.h new file mode 100644 index 0000000..83d086c --- /dev/null +++ b/src/base.h @@ -0,0 +1,118 @@ +#pragma once + +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <sysexits.h> +#include <errno.h> +#include <string.h> +#include <err.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <ctype.h> + +typedef uint64_t u64; +typedef uint32_t u32; +typedef uint16_t u16; +typedef uint8_t u8; +typedef int64_t s64; +typedef int32_t s32; +typedef int16_t s16; +typedef int8_t s8; +typedef float f32; +typedef double f64; +typedef int fd; +typedef int jid_t; + +#define ArrayCount(x) (sizeof(x) / sizeof(*(x))) + +#define PARENS () + +#define EVAL3(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__)))) +#define EVAL2(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__)))) +#define EVAL1(...) EVAL0(EVAL0(EVAL0(EVAL0(__VA_ARGS__)))) +#define EVAL0(...) __VA_ARGS__ + +#define FOR_EACH(f, ...) __VA_OPT__(EVAL3(FOR_EACH_STEP(f, __VA_ARGS__))) +#define FOR_EACH_STEP(f, x, ...) f(x) __VA_OPT__(FOR_EACH_AGAIN PARENS(f, __VA_ARGS__)) +#define FOR_EACH_AGAIN() FOR_EACH_STEP + +#define FOR_EACH_1(f, s, ...) __VA_OPT__(EVAL3(FOR_EACH_1_STEP(f, s __VA_ARGS__))) +#define FOR_EACH_1_STEP(f, s, x, ...) f(s, x) __VA_OPT__(FOR_EACH_1_AGAIN PARENS(f, s, __VA_ARGS__)) +#define FOR_EACH_1_AGAIN() FOR_EACH_1_STEP + +#define DO_PRAGMA(x) _Pragma(#x) +#define WARNINGS_RESTORE DO_PRAGMA(clang diagnostic pop) +#define WARNINGS_IGNORE_STEP(x) DO_PRAGMA(clang diagnostic ignored x) +#define WARNINGS_IGNORE(...) \ + DO_PRAGMA(clang diagnostic push) \ + FOR_EACH(WARNINGS_IGNORE_STEP, __VA_ARGS__) + +#define UNUSED(...) (void)sizeof(__VA_ARGS__ __VA_OPT__(, ) 0) // NOLINT(bugprone-sizeof-expression) + +#define OffsetOf(x, y) __builtin_offsetof(x, y) +#define ContainerOf(Member, type, member) (type *)(void *)((u8 *)(Member)-OffsetOf(type, member)) + +WARNINGS_IGNORE("-Wdocumentation") +#include <ucl.h> +WARNINGS_RESTORE + +#define UCL_CHECK_HELPER_INT "an integer" +#define UCL_CHECK_HELPER_STRING "a string" +#define UCL_CHECK_HELPER_OBJECT "an object" +#define UCL_CHECK_HELPER_ARRAY "an array" +#define UCL_CHECK_HELPER_BOOLEAN "a boolean" +#define UCL_CHECK_HELPER_(x) UCL_CHECK_HELPER_##x +#define UCL_CHECK_HELPER(x) UCL_CHECK_HELPER_(x) +#define UCL_CHECK_ROOT(type_) \ + do { \ + if(root == NULL || root->type != UCL_##type_) \ + { \ + if(*Position == '\0') Position = "."; \ + asprintf(&Error, "%s is not " UCL_CHECK_HELPER(type_), Position); \ + goto error; \ + } \ + } while(0) +#define UCL_CHECK(x, type_) \ + do { \ + if(x == NULL || x->type != UCL_##type_) \ + { \ + asprintf(&Error, "%s." #x " is not " UCL_CHECK_HELPER(type_), Position); \ + goto error; \ + } \ + } while(0) +#define UCL_CHECK_OPTIONAL(x, type_) \ + do { \ + if(x != NULL && x->type != UCL_##type_) \ + { \ + asprintf(&Error, "%s." #x " is not " UCL_CHECK_HELPER(type_), Position); \ + goto error; \ + } \ + } while(0) + +#define CONCAT_ENUM_NAMES(a, b) a##_##b, +#define STRING_(...) #__VA_ARGS__ +#define STRING(...) STRING_(__VA_ARGS__) +#define LISTIFY(...) __VA_ARGS__, +#define STRING_LISTIFY(...) LISTIFY(STRING(__VA_ARGS__)) +#define REQUIRE_SEMICOLON_(x) struct dummy_struct_##x +#define REQUIRE_SEMICOLON(x) REQUIRE_SEMICOLON_(x) +#define NAMED_ENUM(x, ...) \ + enum \ + { \ + FOR_EACH_1(CONCAT_ENUM_NAMES, x, __VA_OPT__(, ) __VA_ARGS__) x##_COUNT \ + } typedef x; \ + WARNINGS_IGNORE("-Wunused-variable") \ + static char const *x##_Names[] = { FOR_EACH(STRING_LISTIFY __VA_OPT__(, ) __VA_ARGS__) }; \ + WARNINGS_RESTORE \ + REQUIRE_SEMICOLON(x) + +static inline bool +isprints(char const *String) +{ + for(char const *At = String; *At; ++At) + if(isprint(*At) == false) return false; + return true; +} 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 *); diff --git a/src/jprint.c b/src/jprint.c new file mode 100644 index 0000000..e058ea3 --- /dev/null +++ b/src/jprint.c @@ -0,0 +1,176 @@ +#include "jprint.h" + +void +JPrintElement(jprint_state *State) +{ + if(State->NeedsComma == true) + { + fprintf(State->F, ","); + } else + { + State->NeedsComma = true; + } +} + +void +JPrint_null(jprint_state *State) +{ + JPrintElement(State); + fprintf(State->F, "null"); +} + +void +JPrint_bool(jprint_state *State, bool E) +{ + JPrintElement(State); + fprintf(State->F, "%s", E ? "true" : "false"); +} + +void +JPrint_ssize_t(jprint_state *State, ssize_t E) +{ + JPrintElement(State); + fprintf(State->F, "%zd", E); +} + +void +JPrint_double(jprint_state *State, double E) +{ + JPrintElement(State); + fprintf(State->F, "%f", E); +} + +void +JPrint_string(jprint_state *State, char const E[]) +{ + JPrintElement(State); + fprintf(State->F, "\""); + for(size_t i = 0; E[i] != '\0'; ++i) + { + if(__builtin_expect(E[i] == '\\', false)) + { + fputc('\\', State->F); + fputc('\\', State->F); + } else if(__builtin_expect(E[i] == '"', false)) + { + fputc('\\', State->F); + fputc('"', State->F); + } else + { + fputc(E[i], State->F); + } + } + fprintf(State->F, "\""); +} + +void +JPrint_string_n(jprint_state *State, char const E[], size_t Length) +{ + JPrintElement(State); + fprintf(State->F, "\""); + for(size_t i = 0; i < Length && E[i] != '\0'; ++i) + { + if(__builtin_expect(E[i] == '\\', false)) + { + fputc('\\', State->F); + fputc('\\', State->F); + } else if(__builtin_expect(E[i] == '"', false)) + { + fputc('\\', State->F); + fputc('"', State->F); + } else + { + fputc(E[i], State->F); + } + } + fprintf(State->F, "\""); +} + +void +JPrintArrayBegin(jprint_state *State) +{ + JPrintElement(State); + State->NeedsComma = false; + fprintf(State->F, "["); +} + +void +JPrintArrayEnd(jprint_state *State) +{ + fprintf(State->F, "]"); + State->NeedsComma = true; +} + +void +JPrintObjectBegin(jprint_state *State) +{ + JPrintElement(State); + State->NeedsComma = false; + fprintf(State->F, "{"); +} + +void +JPrintObjectEnd(jprint_state *State) +{ + fprintf(State->F, "}"); + State->NeedsComma = true; +} + +void +JPrintMember(jprint_state *State, char const T[]) +{ + JPrintElement(State); + State->NeedsComma = false; + fprintf(State->F, "\"%s\":", T); +} + +void +JPrintMember_n(jprint_state *State, char const T[], size_t Length) +{ + JPrintElement(State); + State->NeedsComma = false; + fprintf(State->F, "\"%.*s\":", (int)Length, T); +} + +void +JPrint_ucl_object_t(jprint_state *State, ucl_object_t const *E) +{ + if(E == NULL) + { + JPrint_null(State); + return; + } + switch(E->type) + { + case UCL_OBJECT: + { + JPrintObjectBegin(State); + ucl_object_t const *v; + ucl_object_iter_t it = NULL; + while((v = ucl_iterate_object(E, &it, true))) + { + char const *k = ucl_object_key(v); + if(k == NULL) continue; + JPrintMember(State, k); + JPrint_ucl_object_t(State, v); + } + JPrintObjectEnd(State); + break; + } + case UCL_ARRAY: + { + JPrintArrayBegin(State); + ucl_object_t const *v; + ucl_object_iter_t it = NULL; + while((v = ucl_iterate_object(E, &it, true))) JPrint_ucl_object_t(State, v); + JPrintArrayEnd(State); + break; + } + case UCL_STRING: JPrint_string(State, ucl_object_tostring(E)); break; + case UCL_INT: JPrint_ssize_t(State, ucl_object_toint(E)); break; + case UCL_FLOAT: JPrint_double(State, ucl_object_todouble(E)); break; + case UCL_BOOLEAN: JPrint_bool(State, ucl_object_toboolean(E)); break; + case UCL_NULL: JPrint_null(State); break; + default: __builtin_unreachable(); + } +} diff --git a/src/jprint.h b/src/jprint.h new file mode 100644 index 0000000..d9e4b95 --- /dev/null +++ b/src/jprint.h @@ -0,0 +1,26 @@ +#pragma once + +#include "base.h" + +struct +{ + FILE *F; + bool NeedsComma; +} typedef jprint_state; + +void JPrint_null(jprint_state *); +void JPrint_bool(jprint_state *, bool); +void JPrint_ssize_t(jprint_state *, ssize_t); +void JPrint_double(jprint_state *, double); +void JPrint_string(jprint_state *, char const[]); +void JPrint_string_n(jprint_state *, char const[], size_t); +void JPrint_ucl_object_t(jprint_state *, ucl_object_t const *); + +void JPrintArrayBegin(jprint_state *); +void JPrintElement(jprint_state *); +void JPrintArrayEnd(jprint_state *); + +void JPrintObjectBegin(jprint_state *); +void JPrintMember(jprint_state *, char const[]); +void JPrintMember_n(jprint_state *, char const[], size_t); +void JPrintObjectEnd(jprint_state *); diff --git a/src/lamina/main.c b/src/lamina/main.c new file mode 100644 index 0000000..a17462e --- /dev/null +++ b/src/lamina/main.c @@ -0,0 +1,583 @@ +#include "../util.h" + +#include <sys/param.h> +#include <sys/mount.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <signal.h> +#include <pwd.h> + +static char *Root; + +NAMED_ENUM(mount_type, layer, devfs, ); + +struct mount +{ + mount_type Type; + union + { + struct + { + char *Source; + } layer; + struct + { + char *Target; + } devfs; + }; +}; + +static struct mount *Mounts; +static size_t MountsCount; +static bool MountsCleanup; +static pid_t SlavePID; +static fd SlaveSocket; + +static char const *Arg0; + +static void __attribute__((noreturn)) usage(void) +{ + fprintf(stderr, "usage: %1$s [recipe-file] [name]\n", Arg0); + exit(EX_USAGE); +} + +NAMED_ENUM(message_type, run, chdir, user, ); + +#define MAX_MESSAGE_SIZE 8192 +#define MAX_SLAVE_COMMAND_ARGUMENT_COUNT 32 + +struct +{ + message_type Type; + union + { + struct + { + size_t ArgCount; + size_t Arg[MAX_SLAVE_COMMAND_ARGUMENT_COUNT]; + char ArgData[0]; + } run; + struct + { + char Path[0]; + } chdir; + struct + { + char Username[0]; + } user; + }; +} typedef message; + +static string_array +Split(char const *Line, size_t LineLen) +{ + string_array E; + E._ = NULL; + E.Count = 0; + bool PartStart = true; + size_t Mark = 0; + for(size_t i = 0; i < LineLen; ++i) + { + if(Line[i] == ' ') + { + if(PartStart == false) + { + PartStart = true; + E._[E.Count - 1] = strndup(Line + Mark, i - Mark); + } + } else if(PartStart == true) + { + PartStart = false; + ++E.Count; + Mark = i; + E._ = reallocarray(E._, E.Count, sizeof(*E._)); + if(E._ == NULL) + { + fprintf(stderr, "fatal: reallocarray failed\n"); + exit(-1); + } + } + } + if(PartStart == false) + { + PartStart = true; + E._[E.Count - 1] = strndup(Line + Mark, LineLen - Mark); + } + E._ = reallocarray(E._, E.Count + 1, sizeof(*E._)); + E._[E.Count] = NULL; + return E; +} + +static void +ExpandMounts(void) +{ + struct mount *NewMounts = reallocarray(Mounts, MountsCount + 1, sizeof(*NewMounts)); + if(NewMounts == NULL) + { + fprintf(stderr, "reallocarray failed: %s\n", strerror(errno)); + exit(-1); + } + Mounts = NewMounts; +} + +static int +command_layer(string_array E) +{ + if(E.Count != 1) + { + fprintf(stderr, "command expects 1 argument\n"); + return -1; + } + char const *Source = E._[0]; + ExpandMounts(); + char Target[MAXPATHLEN]; + snprintf(Target, sizeof(Target), "%s", Root); + if(mkdir(Target, 755) == -1) + { + if(errno != EEXIST) + { + fprintf(stderr, "mkdir %s: %s\n", Target, strerror(errno)); + return -1; + } + } + int Result = mount_unionfs(Target, Source, true); + if(Result == -1) return -1; + Mounts[MountsCount++] = (struct mount){ + .Type = mount_type_layer, + .layer = { strdup(Source) }, + }; + return 0; +} + +static int +command_devfs(string_array E) +{ + if(E.Count != 1) + { + fprintf(stderr, "command expects 1 argument\n"); + return -1; + } + char const *TargetRelative = E._[0]; + ExpandMounts(); + char Target[MAXPATHLEN]; + snprintf(Target, sizeof(Target), "%s/%s", Root, E._[0]); + int Result = mount_devfs(Target); + if(Result == -1) return -1; + Mounts[MountsCount++] = (struct mount){ + .Type = mount_type_devfs, + .devfs = { strdup(TargetRelative) }, + }; + return 0; +} + +static int +command_copy(string_array E) +{ + if(E.Count != 2) + { + fprintf(stderr, "command expects 2 arguments\n"); + return -1; + } + char const *Source = E._[0]; + char Target[MAXPATHLEN]; + snprintf(Target, sizeof(Target), "%s/%s", Root, E._[1]); + pid_t PID = fork(); + if(PID == -1) return -1; + if(PID == 0) + { + MountsCleanup = false; + close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + close_all(); + fprintf(stderr, "rsync --archive --xattrs %s %s\n", Source, Target); + execlp("rsync", "rsync", "--archive", "--xattrs", Source, Target, NULL); + exit(-1); + } + int Exit; + waitpid(PID, &Exit, 0); + if(!WIFEXITED(Exit) || (WEXITSTATUS(Exit) != 0)) return -1; + return 0; +} + +static int +command_run(string_array E) +{ + if(E.Count > MAX_SLAVE_COMMAND_ARGUMENT_COUNT) + { + fprintf(stderr, "too many arguments\n"); + return -1; + } + char Buffer[MAX_MESSAGE_SIZE]; + message *M = (message *)Buffer; + M->Type = message_type_run; + M->run.ArgCount = E.Count; + size_t ArgAt = 0; + size_t At = 0; + for(size_t i = 0; i < E.Count; ++i) + { + M->run.Arg[ArgAt] = At; + size_t Length = strlen(E._[i]); + // XXX: check that we do not go outside + memcpy(M->run.ArgData + At, E._[i], Length); + M->run.ArgData[At + Length] = 0; + At += Length + 1; + ++ArgAt; + } + send(SlaveSocket, M, OffsetOf(message, run.ArgData) + At, 0); + int Status; + if(recv(SlaveSocket, &Status, sizeof(Status), 0) == -1) + { + fprintf(stderr, "recv: %s\n", strerror(errno)); + return -1; + } + return Status; +} + +static int +command_chdir(string_array E) +{ + if(E.Count != 1) + { + fprintf(stderr, "requires 1 argument\n"); + return -1; + } + char Buffer[MAX_MESSAGE_SIZE]; + message *M = (message *)Buffer; + M->Type = message_type_chdir; + size_t Length = strlen(E._[0]); + // XXX: check bounds + memcpy(M->chdir.Path, E._[0], Length); + M->chdir.Path[Length] = 0; + send(SlaveSocket, M, OffsetOf(message, chdir.Path) + Length + 1, 0); + int Status; + if(recv(SlaveSocket, &Status, sizeof(Status), 0) == -1) + { + fprintf(stderr, "recv: %s\n", strerror(errno)); + return -1; + } + return Status; +} + +static int +command_user(string_array E) +{ + if(E.Count != 1) + { + fprintf(stderr, "requires 1 argument\n"); + return -1; + } + char Buffer[MAX_MESSAGE_SIZE]; + message *M = (message *)Buffer; + M->Type = message_type_user; + size_t Length = strlen(E._[0]); + // XXX: check bounds + memcpy(M->user.Username, E._[0], Length); + M->user.Username[Length] = 0; + send(SlaveSocket, M, OffsetOf(message, user.Username) + Length + 1, 0); + int Status; + if(recv(SlaveSocket, &Status, sizeof(Status), 0) == -1) + { + fprintf(stderr, "recv: %s\n", strerror(errno)); + return -1; + } + return Status; +} + +static int +Execute(char const *Line, size_t LineLen) +{ + string_array Args = Split(Line, LineLen); + if(Args.Count == 0) + { + Free_string_array(&Args); + return 0; + } + int Result; + char *Command = Args._[0]; + string_array Rest = (string_array){ ._ = Args._ + 1, .Count = Args.Count - 1 }; + if(strcmp(Command, "RUN") == 0) + { + Result = command_run(Rest); + } else if(strcmp(Command, "LAYER") == 0) + { + Result = command_layer(Rest); + } else if(strcmp(Command, "DEVFS") == 0) + { + Result = command_devfs(Rest); + } else if(strcmp(Command, "CHDIR") == 0) + { + Result = command_chdir(Rest); + } else if(strcmp(Command, "COPY") == 0) + { + Result = command_copy(Rest); + } else if(strcmp(Command, "USER") == 0) + { + Result = command_user(Rest); + } else + { + fprintf(stderr, "unrecognized command: %s\n", Command); + Result = -1; + } + Free_string_array(&Args); + return Result; +} + +static int +unmount_single(struct mount *E) +{ + char const *Target; + switch(E->Type) + { + case mount_type_layer: Target = Root; break; + case mount_type_devfs: + { + char TargetBuffer[MAXPATHLEN]; + snprintf(TargetBuffer, sizeof(TargetBuffer), "%s/%s", Root, E->devfs.Target); + Target = TargetBuffer; + break; + } + case mount_type_COUNT: + default: __builtin_unreachable(); + } + while(unmount(Target, MNT_FORCE) == -1) + { + fprintf(stderr, "unmount %s failed: %s\n", Target, strerror(errno)); + if(errno != EBUSY) return -1; + sleep(1); + } + return 0; +} + +static void +unmount_all(void) +{ + for(size_t i = MountsCount - 1; i < MountsCount; --i) + { + unmount_single(Mounts + i); + switch(Mounts[i].Type) + { + case mount_type_layer: free(Mounts[i].layer.Source); break; + case mount_type_devfs: free(Mounts[i].devfs.Target); break; + case mount_type_COUNT: + default: __builtin_unreachable(); + } + } + if(Mounts != NULL) free(Mounts); +} + +static int +slave_command_run(string_array E) +{ + if(E.Count == 0) + { + fprintf(stderr, "run what?\n"); + return -1; + } + pid_t PID = fork(); + if(PID == -1) return -1; + if(PID == 0) + { + MountsCleanup = false; + close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + close_all(); + execvp(E._[0], E._); + fprintf(stderr, "failed to exec '%s': %s\n", E._[0], strerror(errno)); + exit(-1); + } + int Exit; + waitpid(PID, &Exit, 0); + if(!WIFEXITED(Exit) || (WEXITSTATUS(Exit) != 0)) return -1; + return 0; +} + +static int +chuser(char *Username) +{ + errno = 0; + struct passwd *E = getpwnam(Username); + if(E == NULL) + { + if(errno != 0) fprintf(stderr, "getpwnam(%s): %s\n", Username, strerror(errno)); + else + fprintf(stderr, "user %s does not exist\n", Username); + return -1; + } + uid_t UID = E->pw_uid; + gid_t GID = E->pw_gid; + if(setgid(GID) == -1) + { + fprintf(stderr, "setgid(%u): %s\n", GID, strerror(errno)); + return -1; + } + if(setuid(UID) == -1) + { + fprintf(stderr, "setuid(%u): %s\n", UID, strerror(errno)); + return -1; + } + return 0; +} + +static int +slave(fd FD) +{ + char Buffer[MAX_MESSAGE_SIZE]; + message *M = (message *)Buffer; + ssize_t Size; + while((Size = recv(FD, Buffer, sizeof(Buffer), 0))) + { + if(Size == -1) + { + if(errno == EINTR) continue; + fprintf(stderr, "recv: %s\n", strerror(errno)); + break; + } + if(Size == 0) break; + int Result; + switch(M->Type) + { + case message_type_run: + { + string_array E; + E._ = NULL; + E.Count = M->run.ArgCount; + E._ = reallocarray(E._, E.Count + 1, sizeof(*E._)); + E._[E.Count] = NULL; + // XXX: add checking + for(size_t i = 0; i < M->run.ArgCount; ++i) E._[i] = M->run.ArgData + M->run.Arg[i]; + Result = slave_command_run(E); + free(E._); + break; + } + case message_type_chdir: + { + Result = chdir(M->chdir.Path); + break; + } + case message_type_user: + { + Result = chuser(M->user.Username); + break; + } + case message_type_COUNT: + default: __builtin_unreachable(); + } + send(FD, &Result, sizeof(Result), 0); + } + close(FD); + return 0; +} + +static void +StopSlave(void) +{ + kill(SlavePID, SIGINT); + int Exit; + waitpid(SlavePID, &Exit, 0); +} + +static int +SpawnSlave(void) +{ + fd FD[2]; + if(socketpair(PF_UNIX, SOCK_DGRAM, 0, FD) == -1) + { + fprintf(stderr, "socketpair %s\n", strerror(errno)); + return -1; + } + pid_t PID = fork(); + if(PID == -1) return -1; + if(PID == 0) + { + close(FD[1]); + close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + if(chroot(Root) == -1) + { + fprintf(stderr, "chroot %s: %s\n", Root, strerror(errno)); + exit(-1); + } + if(chdir("/") == -1) + { + fprintf(stderr, "chdir /: %s\n", strerror(errno)); + exit(-1); + } + exit(slave(FD[0])); + } + close(FD[0]); + SlaveSocket = FD[1]; + SlavePID = PID; + return 0; +} + +static void +Cleanup(void) +{ + if(SlavePID > 0) StopSlave(); + if(MountsCleanup == true) unmount_all(); +} + +int +main(int ArgCount, char **Arg) +{ + Arg0 = Arg[0]; + --ArgCount; + ++Arg; + if(ArgCount != 2) usage(); + char const *Path = Arg[0]; + Root = Arg[1]; + ArgCount -= 2; + Arg += 2; + if(mkdir(Root, 0755) == -1) + { + fprintf(stderr, "mkdir %s: %s\n", Root, strerror(errno)); + exit(-1); + } + atexit(Cleanup); + if(SpawnSlave() == -1) exit(-1); + FILE *F = fopen(Path, "r"); + if(F == NULL) + { + fprintf(stderr, "fopen(\"%s\", \"r\")\n", Path); + exit(-1); + } + MountsCleanup = true; + char *Line = NULL; + size_t LineSize = 0; + ssize_t LineLength; + size_t LineAt = 0; + while((LineLength = getline(&Line, &LineSize, F))) + { + if(LineLength <= 0) break; + ++LineAt; + if(Line[LineLength - 1] == '\n') Line[--LineLength] = '\0'; + if(LineLength == 0) continue; + if(Line[0] == '#') continue; + if(Execute(Line, (size_t)LineLength) == -1) + { + fprintf(stderr, "(line %zu)\n", LineAt); + break; + } + } + printf("LAYER %s\n", Root); + for(size_t i = 0; i < MountsCount; ++i) + { + struct mount *E = Mounts + i; + switch(Mounts[i].Type) + { + case mount_type_layer: + { + printf("LAYER %s\n", E->layer.Source); + break; + } + case mount_type_devfs: + { + printf("DEVFS %s\n", E->devfs.Target); + break; + } + case mount_type_COUNT: + default: __builtin_unreachable(); + } + } + return 0; +} diff --git a/src/module/module.c b/src/module/module.c new file mode 100644 index 0000000..22f6142 --- /dev/null +++ b/src/module/module.c @@ -0,0 +1,126 @@ +#include "module.h" + +char const *Arg0; + +void __attribute__((noreturn)) usage(void) +{ + WARNINGS_IGNORE("-Wformat-nonliteral") + fprintf(stderr, Usage, Arg0); + WARNINGS_RESTORE + exit(EX_USAGE); +} + +static ucl_object_t * +Parse(char const *Arg) +{ + struct ucl_parser *Parser = ucl_parser_new(0); + if(ucl_parser_add_string(Parser, Arg, 0) == 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); + ucl_parser_free(Parser); + return root; +} + +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 *Command = Arg[0]; + --ArgCount; + ++Arg; + int Result; + if(strcmp(Command, "start") == 0) + { + if(ArgCount != 2) usage(); + char const *Name = Arg[0]; + if(isprints(Name) == false) + { + fprintf(stderr, "Name is non-printable\n"); + return -1; + } + ucl_object_t *Configuration = Parse(Arg[1]); + Result = start(Name, Configuration); + ucl_object_unref(Configuration); + } else if(strcmp(Command, "stop") == 0) + { + if(ArgCount != 3) usage(); + char const *Name = Arg[0]; + if(isprints(Name) == false) + { + fprintf(stderr, "Name is non-printable\n"); + return -1; + } + ucl_object_t *Configuration = Parse(Arg[1]); + ucl_object_t *Data = Parse(Arg[2]); + Result = stop(Name, Configuration, Data); + ucl_object_unref(Configuration); + ucl_object_unref(Data); + } else if(strcmp(Command, "get-endpoint") == 0) + { + if(ArgCount != 4) usage(); + char const *Name = Arg[0]; + if(isprints(Name) == false) + { + fprintf(stderr, "Name is non-printable\n"); + return -1; + } + ucl_object_t *Configuration = Parse(Arg[1]); + ucl_object_t *Data = Parse(Arg[2]); + char const *Interface = Arg[3]; + Result = get_endpoint(Name, Configuration, Data, Interface); + ucl_object_unref(Configuration); + ucl_object_unref(Data); + } else if(strcmp(Command, "cmd") == 0) + { + if(ArgCount < 3) usage(); + char const *Name = Arg[0]; + if(isprints(Name) == false) + { + fprintf(stderr, "Name is non-printable\n"); + return -1; + } + ucl_object_t *Configuration = Parse(Arg[1]); + ucl_object_t *Data = Parse(Arg[2]); + Arg += 3; + ArgCount -= 3; + Result = cmd(Name, Configuration, Data, (size_t)ArgCount, Arg); + ucl_object_unref(Configuration); + ucl_object_unref(Data); + } else if(strcmp(Command, "mod") == 0) + { + if(ArgCount < 3) usage(); + char const *Name = Arg[0]; + if(isprints(Name) == false) + { + fprintf(stderr, "Name is non-printable\n"); + return -1; + } + ucl_object_t *Configuration = Parse(Arg[1]); + ucl_object_t *Data = Parse(Arg[2]); + Arg += 3; + ArgCount -= 3; + Result = mod(Name, Configuration, Data, (size_t)ArgCount, Arg); + ucl_object_unref(Configuration); + ucl_object_unref(Data); + } else + { + usage(); + } + return Result; +} diff --git a/src/module/module.h b/src/module/module.h new file mode 100644 index 0000000..c357faa --- /dev/null +++ b/src/module/module.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../base.h" + +extern char const *Usage; +extern char const *Arg0; + +void __attribute__((noreturn)) usage(void); + +int start(char const *Name, ucl_object_t *Configuration); +int stop(char const *Name, ucl_object_t *Configuration, ucl_object_t *Data); +int get_endpoint(char const *Name, ucl_object_t *Configuration, ucl_object_t *Data, char const *Interface); +int cmd(char const *Name, ucl_object_t *Configuration, ucl_object_t *Data, size_t ArgCount, char **Arg); +int mod(char const *Name, ucl_object_t *Configuration, ucl_object_t *Data, size_t ArgCount, char **Arg); diff --git a/src/sf.bridge/main.c b/src/sf.bridge/main.c new file mode 100644 index 0000000..fbfb500 --- /dev/null +++ b/src/sf.bridge/main.c @@ -0,0 +1,166 @@ +#include "../module/module.h" +#include "../util.h" +#include "state.h" + +#include <netgraph.h> + +char const *Usage = "usage: %1$s start [name] [configuration]\n" + " %1$s stop [name] [configuration] [data]\n" + " %1$s get-endpoint [name] [configuration] [data] [interface]\n" + "unsupported commands: cmd mod\n"; + +static configuration Configuration; +static data Data; +static bool ConfigurationLoaded; +static bool DataLoaded; + +// HELPERS + +static void +Free_all(void) +{ + if(ConfigurationLoaded == true) Free_configuration(&Configuration); + if(DataLoaded == true) Free_data(&Data); +} + +static void +Load_configuration(ucl_object_t *root) +{ + char *Error = Parse_configuration(&Configuration, root, ""); + if(Error != NULL) + { + fprintf(stderr, "%s: configuration error: %s\n", Arg0, Error); + free(Error); + Free_all(); + exit(-1); + } + ConfigurationLoaded = true; +} + +static void +Load_data(ucl_object_t *root) +{ + char *Error = Parse_data(&Data, root, ""); + if(Error != NULL) + { + fprintf(stderr, "%s: data error: %s\n", Arg0, Error); + free(Error); + Free_all(); + exit(-1); + } + DataLoaded = true; + // TODO: check that data matches configuration +} + +// COMMANDS + +int +start(char const *Name, ucl_object_t *configuration) +{ + UNUSED(Name); + Load_configuration(configuration); + // Implement + Data.ID = 0; + fd Control = -1; + if(NgMkSockNode(NULL, &Control, NULL) == -1) + { + fprintf(stderr, "Failed to create netgraph socket: %s\n", strerror(errno)); + goto error; + } + if(Create_bridge(&Data.ID, Control) == -1) goto error; + close(Control); + jprint_state S; + bzero(&S, sizeof(S)); + S.F = stdout; + Save_data(&S, &Data); + Free_all(); + return 0; +error: + Free_all(); + return -1; +} + +int +stop(char const *Name, ucl_object_t *configuration, ucl_object_t *data) +{ + UNUSED(Name); + Load_configuration(configuration); + Load_data(data); + fd Control = -1; + if(NgMkSockNode(NULL, &Control, NULL) == -1) + { + fprintf(stderr, "Failed to create netgraph socket: %s\n", strerror(errno)); + goto error; + } + if(DestroyNetgraphNode(Data.ID, Control) == -1) goto error; + Free_all(); + return 0; +error: + Free_all(); + return -1; +} + +int +get_endpoint(char const *Name, ucl_object_t *configuration, ucl_object_t *data, char const *Interface) +{ + UNUSED(Name); + Load_configuration(configuration); + Load_data(data); + size_t linkNum; + if(strncmp(Interface, "link", __builtin_strlen("link")) == 0) + { + char const *N = Interface + __builtin_strlen("link"); + linkNum = strtoul(N, NULL, 10); + if(linkNum == 0 && errno == EINVAL) + { + fprintf(stderr, "invalid interface %s\n", Interface); + goto error; + } + } else if(strncmp(Interface, "uplink", __builtin_strlen("uplink")) == 0) + { + char const *N = Interface + __builtin_strlen("uplink"); + linkNum = strtoul(N, NULL, 10); + if(linkNum == 0) + { + fprintf(stderr, "unknown interface %s\n", Interface); + goto error; + } + } else + { + fprintf(stderr, "unknown interface %s\n", Interface); + } + jprint_state S; + bzero(&S, sizeof(S)); + S.F = stdout; + JPrintObjectBegin(&S); + JPrintMember(&S, "address"); + char Address[NG_PATHSIZ]; + snprintf(Address, sizeof(Address), "[%x]:", Data.ID); + JPrint_string(&S, Address); + JPrintMember(&S, "hook"); + JPrint_string(&S, Interface); + JPrintObjectEnd(&S); + Free_all(); + return 0; +error: + Free_all(); + return -1; +} + +int +cmd(char const *Name, ucl_object_t *configuration, ucl_object_t *data, size_t ArgCount, char **Arg) +{ + UNUSED(Name, ArgCount, Arg); + Load_configuration(configuration); + Load_data(data); + usage(); +} + +int +mod(char const *Name, ucl_object_t *configuration, ucl_object_t *data, size_t ArgCount, char **Arg) +{ + UNUSED(Name, ArgCount, Arg); + Load_configuration(configuration); + Load_data(data); + usage(); +} diff --git a/src/sf.bridge/state.c b/src/sf.bridge/state.c new file mode 100644 index 0000000..c7780f3 --- /dev/null +++ b/src/sf.bridge/state.c @@ -0,0 +1,58 @@ +#include "state.h" + +// FREE + +void +Free_configuration(configuration *E) +{ + UNUSED(E); +} + +void +Free_data(data *E) +{ + UNUSED(E); +} + +// PARSE + +char * +Parse_configuration(configuration *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(OBJECT); + UNUSED(E); + return NULL; +error: + return Error; +} + +char * +Parse_data(data *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(OBJECT); + ucl_object_t const *id = ucl_object_lookup(root, "id"); + UCL_CHECK(id, INT); + s64 ID = ucl_object_toint(id); + if(ID < 0 || ID > UINT32_MAX) + { + asprintf(&Error, "%s id invalid (%ld)", Position, ID); + goto error; + } + E->ID = (u32)ID; + return NULL; +error: + return Error; +} + +// SAVE + +void +Save_data(jprint_state *S, data const *E) +{ + JPrintObjectBegin(S); + JPrintMember(S, "id"); + JPrint_ssize_t(S, E->ID); + JPrintObjectEnd(S); +} diff --git a/src/sf.bridge/state.h b/src/sf.bridge/state.h new file mode 100644 index 0000000..7ef6243 --- /dev/null +++ b/src/sf.bridge/state.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../jprint.h" + +struct +{ +} typedef configuration; + +struct +{ + u32 ID; +} typedef data; + +void Free_configuration(configuration *); +void Free_data(data *); + +char *Parse_configuration(configuration *, ucl_object_t const *, char const *Position); +char *Parse_data(data *, ucl_object_t const *, char const *Position); + +void Save_data(jprint_state *, data const *); diff --git a/src/sf.eiface/main.c b/src/sf.eiface/main.c new file mode 100644 index 0000000..4fafa81 --- /dev/null +++ b/src/sf.eiface/main.c @@ -0,0 +1,155 @@ +#include "../module/module.h" +#include "../util.h" +#include "state.h" + +#include <netgraph.h> + +char const *Usage = "usage: %1$s start [name] [configuration]\n" + " %1$s stop [name] [configuration] [data]\n" + " %1$s get-endpoint [name] [configuration] [data] [interface]\n" + "unsupported commands: cmd mod\n"; + +static configuration Configuration; +static data Data; +static bool ConfigurationLoaded; +static bool DataLoaded; + +// HELPERS + +static void +Free_all(void) +{ + if(ConfigurationLoaded == true) Free_configuration(&Configuration); + if(DataLoaded == true) Free_data(&Data); +} + +static void +Load_configuration(ucl_object_t *root) +{ + char *Error = Parse_configuration(&Configuration, root, ""); + if(Error != NULL) + { + fprintf(stderr, "%s: configuration error: %s\n", Arg0, Error); + free(Error); + Free_all(); + exit(-1); + } + ConfigurationLoaded = true; +} + +static void +Load_data(ucl_object_t *root) +{ + char *Error = Parse_data(&Data, root, ""); + if(Error != NULL) + { + fprintf(stderr, "%s: data error: %s\n", Arg0, Error); + free(Error); + Free_all(); + exit(-1); + } + DataLoaded = true; + // TODO: check that data matches configuration +} + +// COMMANDS + +int +start(char const *Name, ucl_object_t *configuration) +{ + UNUSED(Name); + Load_configuration(configuration); + Data.ID = 0; + fd Control = -1; + if(NgMkSockNode(NULL, &Control, NULL) == -1) + { + fprintf(stderr, "Failed to create netgraph socket: %s\n", strerror(errno)); + goto error; + } + if(Create_eiface(&Data.ID, Control) == -1) goto error; + char *InterfaceName = getifname(Data.ID, Control); + if(InterfaceName == NULL) goto error; + int Result = command("ifconfig", "ifconfig", InterfaceName, "name", Name, NULL); + free(InterfaceName); + if(Result == -1) goto error; + close(Control); + if(Configuration.inet != NULL) command("ifconfig", "ifconfig", Name, "inet", Configuration.inet, NULL); + jprint_state S; + bzero(&S, sizeof(S)); + S.F = stdout; + Save_data(&S, &Data); + Free_all(); + return 0; +error: + if(Data.ID != 0) DestroyNetgraphNode(Data.ID, Control); + if(Control != -1) close(Control); + Free_all(); + return -1; +} + +int +stop(char const *Name, ucl_object_t *configuration, ucl_object_t *data) +{ + UNUSED(Name); + Load_configuration(configuration); + Load_data(data); + fd Control = -1; + if(NgMkSockNode(NULL, &Control, NULL) == -1) + { + fprintf(stderr, "Failed to create netgraph socket: %s\n", strerror(errno)); + goto error; + } + if(DestroyNetgraphNode(Data.ID, Control) == -1) goto error; + Free_all(); + return 0; +error: + Free_all(); + return -1; +} + +int +get_endpoint(char const *Name, ucl_object_t *configuration, ucl_object_t *data, char const *Interface) +{ + UNUSED(Name); + Load_configuration(configuration); + Load_data(data); + if(*Interface != '\0') + { + fprintf(stderr, "%s provides only empty interface name\n", Arg0); + goto error; + } + jprint_state S; + bzero(&S, sizeof(S)); + S.F = stdout; + JPrintObjectBegin(&S); + JPrintMember(&S, "address"); + char Address[NG_PATHSIZ]; + snprintf(Address, sizeof(Address), "[%x]:", Data.ID); + JPrint_string(&S, Address); + JPrintMember(&S, "hook"); + JPrint_string(&S, "ether"); + JPrintObjectEnd(&S); + Free_all(); + return 0; +error: + Free_all(); + return -1; +} + +int +cmd(char const *Name, ucl_object_t *configuration, ucl_object_t *data, size_t ArgCount, char **Arg) +{ + UNUSED(Name, ArgCount, Arg); + Load_configuration(configuration); + Load_data(data); + usage(); +} + +int +mod(char const *Name, ucl_object_t *configuration, ucl_object_t *data, size_t ArgCount, char **Arg) +{ + UNUSED(Name, ArgCount, Arg); + Load_configuration(configuration); + Load_data(data); + usage(); +} diff --git a/src/sf.eiface/state.c b/src/sf.eiface/state.c new file mode 100644 index 0000000..7d85056 --- /dev/null +++ b/src/sf.eiface/state.c @@ -0,0 +1,63 @@ +#include "state.h" + +// FREE + +void +Free_configuration(configuration *E) +{ + if(E->inet != NULL) free(E->inet); +} + +void +Free_data(data *E) +{ + UNUSED(E); +} + +// PARSE + +char * +Parse_configuration(configuration *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(OBJECT); + ucl_object_t const *inet = ucl_object_lookup(root, "inet"); + if(inet != NULL) + { + UCL_CHECK(inet, STRING); + E->inet = strdup(ucl_object_tostring(inet)); + } + return NULL; +error: + return Error; +} + +char * +Parse_data(data *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(OBJECT); + ucl_object_t const *id = ucl_object_lookup(root, "id"); + UCL_CHECK(id, INT); + s64 ID = ucl_object_toint(id); + if(ID < 0 || ID > UINT32_MAX) + { + asprintf(&Error, "%s id invalid (%ld)", Position, ID); + goto error; + } + E->ID = (u32)ID; + return NULL; +error: + return Error; +} + +// SAVE + +void +Save_data(jprint_state *S, data const *E) +{ + JPrintObjectBegin(S); + JPrintMember(S, "id"); + JPrint_ssize_t(S, E->ID); + JPrintObjectEnd(S); +} diff --git a/src/sf.eiface/state.h b/src/sf.eiface/state.h new file mode 100644 index 0000000..0431fdf --- /dev/null +++ b/src/sf.eiface/state.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../jprint.h" + +struct +{ + char *inet; +} typedef configuration; + +struct +{ + u32 ID; +} typedef data; + +void Free_configuration(configuration *); +void Free_data(data *); + +char *Parse_configuration(configuration *, ucl_object_t const *, char const *Position); +char *Parse_data(data *, ucl_object_t const *, char const *Position); + +void Save_data(jprint_state *, data const *); diff --git a/src/sf.jail/main.c b/src/sf.jail/main.c new file mode 100644 index 0000000..d71511b --- /dev/null +++ b/src/sf.jail/main.c @@ -0,0 +1,525 @@ +#include "../module/module.h" +#include "../util.h" +#include "state.h" + +#include <jail.h> +#include <sys/param.h> +#include <sys/jail.h> + +#include <netgraph.h> +#include <netgraph/ng_message.h> +#include <netgraph/ng_eiface.h> + +#include <sys/mount.h> +#include <sys/ioctl.h> +#include <fs/devfs/devfs.h> + +char const *Usage = "usage: %1$s start [name] [configuration]\n" + " %1$s stop [name] [configuration] [data]\n" + " %1$s get-endpoint [name] [configuration] [data] [interface]\n" + " %1$s cmd [name] [configuration] [data] sh\n" + " %1$s cmd [name] [configuration] [data] wireshark [interface]\n" + "unsupported commands: mod\n"; + +static configuration Configuration; +static data Data; +static bool ConfigurationLoaded; +static bool DataLoaded; + +// HELPERS + +static void +Free_all(void) +{ + if(ConfigurationLoaded == true) Free_configuration(&Configuration); + if(DataLoaded == true) Free_data(&Data); +} + +static void +Load_configuration(ucl_object_t *root) +{ + char *Error = Parse_configuration(&Configuration, root, ""); + if(Error != NULL) + { + fprintf(stderr, "%s: configuration error: %s\n", Arg0, Error); + free(Error); + Free_all(); + exit(-1); + } + ConfigurationLoaded = true; +} + +static void +Load_data(ucl_object_t *root) +{ + char *Error = Parse_data(&Data, root, ""); + if(Error != NULL) + { + fprintf(stderr, "%s: data error: %s\n", Arg0, Error); + free(Error); + Free_all(); + exit(-1); + } + DataLoaded = true; + // TODO: check that data matches configuration +} + +static int +start_interface(interface *E, interface_configuration *C, fd Control, char const *JailName) +{ + E->Type = C->Type; + switch(C->Type) + { + case interface_type_eiface: + { + if(Create_eiface(&E->ID, Control) == -1) return -1; + char *InterfaceName = getifname(E->ID, Control); + if(InterfaceName == NULL) + { + DestroyNetgraphNode(E->ID, Control); + return -1; + } + // NOTE: This first renames the interface and only then vnets it. + int Result = + command("ifconfig", "ifconfig", InterfaceName, "name", C->Name, "vnet", JailName, NULL); + free(InterfaceName); + return Result; + } + case interface_type_steal: + { + return command("ifconfig", "ifconfig", C->steal.Interface, "name", C->Name, "vnet", JailName, NULL); + } + case interface_type_COUNT: + default: __builtin_unreachable(); + } +} + +static int +stop_interface(interface *E, interface_configuration *C, fd Control, char const *JailName) +{ + switch(C->Type) + { + case interface_type_eiface: return DestroyNetgraphNode(E->ID, Control); + case interface_type_steal: + { + return command("ifconfig", + "ifconfig", + C->Name, + "-vnet", + JailName, + "name", + C->steal.Interface, + NULL); + } + case interface_type_COUNT: + default: __builtin_unreachable(); + } +} + +static int +stop_interfaces(interfaces *E, interfaces_configuration *C, fd Control, char const *JailName) +{ + for(size_t i = 0; i < E->Count; ++i) + { + if(stop_interface(E->_ + i, C->_ + i, Control, JailName) == -1) + { + fprintf(stderr, "failed to stop interface '%s'\n", C->_[i].Name); + return -1; + } + } + return 0; +} + +static int +start_interfaces(interfaces *E, interfaces_configuration *C, fd Control, char const *Name) +{ + Data.Interfaces._ = calloc(sizeof(interface), Configuration.Interfaces.Count); + for(E->Count = 0; E->Count < C->Count; ++E->Count) + { + interface *E_ = E->_ + E->Count; + interface_configuration *C_ = C->_ + E->Count; + if(start_interface(E_, C_, Control, Name) == -1) goto error; + } + return 0; +error: + stop_interfaces(E, C, Control, Name); + Free_interfaces(E); + return -1; +} + +static int +start_jail(jid_t *JID, char const *Name, char const *Path) +{ + *JID = jail_setv(JAIL_CREATE, "name", Name, "vnet", "new", "persist", "true", "path", Path, NULL); + if(*JID == -1) + { + fprintf(stderr, "jail creation failed: %s\n", strerror(errno)); + return -1; + } + return 0; +} + +static int +stop_jail(jid_t JID) +{ + if(jail_remove(JID) == -1) + { + fprintf(stderr, "jail removal failed: %s\n", strerror(errno)); + Free_all(); + return -1; + } + return 0; +} + +NAMED_ENUM(mapping_type, unionfs_below, unionfs, devfs, nullfs); + +static int +mapping_mount(mapping *M, char const *TargetPrefix, mapping_type Type) +{ + char Target[MAXPATHLEN]; + snprintf(Target, sizeof(Target), "%s/%s", TargetPrefix, M->Target); + if(mkdir(Target, 755) == -1) + { + if(errno != EEXIST) + { + fprintf(stderr, "mkdir %s: %s\n", Target, strerror(errno)); + return -1; + } + } + switch(Type) + { + case mapping_type_unionfs_below: return mount_unionfs(Target, M->Source, true); + case mapping_type_unionfs: return mount_unionfs(Target, M->Source, false); + case mapping_type_devfs: return mount_devfs(Target); + case mapping_type_nullfs: return mount_nullfs(Target, M->Source); + case mapping_type_COUNT: + default: __builtin_unreachable(); + } +} + +static int +mapping_umount(char const *TargetPrefix, char const *Target_) +{ + char Target[MAXPATHLEN]; + snprintf(Target, sizeof(Target), "%s/%s", TargetPrefix, Target_); + // NOTE: unmount fails because some processes somehow still hold a reference to the jail FS + while(unmount(Target, MNT_FORCE) == -1) + { + fprintf(stderr, "unmount %s failed: %s\n", Target, strerror(errno)); + if(errno != EBUSY) return -1; + sleep(1); + } + return 0; +} + +static int +rm(char const *Path) +{ + pid_t PID = fork(); + if(PID == -1) return -1; + if(PID == 0) + { + close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + close_all(); + fprintf(stderr, "rm -r %s\n", Path); + execlp("rm", "rm", "-r", Path, NULL); + exit(-1); + } + int Exit; + waitpid(PID, &Exit, 0); + if(!WIFEXITED(Exit) || (WEXITSTATUS(Exit) != 0)) return -1; + return 0; +} + +static int +ApplyDevfsRuleset(char const *Prefix, s64 Ruleset) +{ + if(Ruleset == 0) return 0; + devfs_rsnum RS = (devfs_rsnum)Ruleset; + if(RS != Ruleset) + { + fprintf(stderr, "Ruleset invalid: %ld\n", Ruleset); + return -1; + } + char Path[MAXPATHLEN]; + snprintf(Path, sizeof(Path), "%s/dev", Prefix); + fd DevFD = open(Path, O_RDONLY); + if(DevFD == -1) + { + fprintf(stderr, "open '%s' failed: %s\n", Path, strerror(errno)); + return -1; + } + if(ioctl(DevFD, DEVFSIO_SAPPLY, &RS) == -1) + { + fprintf(stderr, "ruleset %hu apply failed: %s\n", RS, strerror(errno)); + close(DevFD); + return -1; + } + close(DevFD); + return 0; +} + +static int +start_fs(filesystem_configuration *E, char const *Path) +{ + bool CreatedPath = false; + size_t LayerAt = 0; + size_t VolumeAt = 0; + bool Mounted_devfs = false; + bool Mounted_temporary = false; + if(mkdir(Path, 0755) == -1) + { + fprintf(stderr, "mkdir %s: %s\n", Path, strerror(errno)); + goto error; + } + CreatedPath = true; + for(LayerAt = 0; LayerAt < E->LayerCount; ++LayerAt) + { + if(mapping_mount(&(mapping){ .Source = E->Layer[LayerAt], .Target = "/" }, + Path, + mapping_type_unionfs_below) == -1) + goto error; + } + if(E->Temporary != NULL) + { + if(mkdir(E->Temporary, 755) == -1) + { + if(errno != EEXIST) + { + fprintf(stderr, "mkdir %s: %s\n", E->Temporary, strerror(errno)); + goto error; + } + } + if(mapping_mount(&(mapping){ .Source = E->Temporary, .Target = "/" }, Path, mapping_type_unionfs) == -1) + goto error; + } + Mounted_temporary = true; + if(mapping_mount(&(mapping){ .Target = "/dev" }, Path, mapping_type_devfs) == -1) goto error; + if(ApplyDevfsRuleset(Path, E->DevfsRuleset) == -1) goto error; + for(VolumeAt = 0; VolumeAt < E->VolumeCount; ++VolumeAt) + { + if(mapping_mount(E->Volume + VolumeAt, Path, mapping_type_nullfs) == -1) goto error; + } + return 0; +error: + for(size_t i = 0; i < VolumeAt; ++i) mapping_umount(Path, E->Volume[E->VolumeCount - 1 - i].Target); + if(Mounted_devfs) mapping_umount(Path, "/dev"); + if(Mounted_temporary) mapping_umount(Path, "/"); + for(size_t i = 0; i < LayerAt; ++i) mapping_umount(Path, "/"); + if(CreatedPath) rm(Path); + return -1; +} + +static int +stop_fs(filesystem_configuration *E, char const *Path) +{ + for(size_t i = 0; i < E->VolumeCount; ++i) mapping_umount(Path, E->Volume[E->VolumeCount - 1 - i].Target); + mapping_umount(Path, "/dev"); + if(E->Temporary != NULL) mapping_umount(Path, "/"); + for(size_t i = 0; i < E->LayerCount; ++i) mapping_umount(Path, "/"); + if(rm(Path) == -1) + { + fprintf(stderr, "rm -r %s: %s\n", Path, strerror(errno)); + goto error; + } + return 0; +error: + return -1; +} + +static bool +ValidJailName(char const *Name) +{ + for(char const *At = Name; *At; ++At) + { + if(isnumber(*At) == false) return true; + } + return true; +} + +// COMMANDS + +static char const *FilesystemPrefix = "/tmp/sf"; + +int +start(char const *Name, ucl_object_t *configuration) +{ + if(ValidJailName(Name) == false) return -1; + Load_configuration(configuration); + fd Control = -1; + Data.JID = -1; + char Path[PATH_MAX]; + snprintf(Path, sizeof(Path), "%s/%s", FilesystemPrefix, Name); + if(mkdir(FilesystemPrefix, 0755) == -1) + { + if(errno != EEXIST) + { + fprintf(stderr, "mkdir %s: %s\n", FilesystemPrefix, strerror(errno)); + goto error; + } + } + if(start_fs(&Configuration.Filesystem, Path) == -1) goto error; + if(start_jail(&Data.JID, Name, Path) == -1) goto error; + if(NgMkSockNode(NULL, &Control, NULL) == -1) + { + fprintf(stderr, "Failed to create netgraph socket: %s\n", strerror(errno)); + goto error; + } + if(start_interfaces(&Data.Interfaces, &Configuration.Interfaces, Control, Name) == -1) + { + fprintf(stderr, "failed to start interfaces\n"); + goto error; + } + DataLoaded = true; + close(Control); + exec_string_array(&Configuration.Init, false, Data.JID); + jprint_state S; + bzero(&S, sizeof(S)); + S.F = stdout; + Save_data(&S, &Data); + Free_all(); + return 0; +error: + if(Control != -1) close(Control); + if(Data.JID != -1) stop_jail(Data.JID); + Free_all(); + return -1; +} + +int +stop(char const *Name, ucl_object_t *configuration, ucl_object_t *data) +{ + if(ValidJailName(Name) == false) return -1; + Load_configuration(configuration); + Load_data(data); + exec_string_array(&Configuration.Shutdown, true, Data.JID); + fd Control = -1; + if(NgMkSockNode(NULL, &Control, NULL) == -1) + { + fprintf(stderr, "Failed to create netgraph socket: %s\n", strerror(errno)); + goto error; + } + if(stop_interfaces(&Data.Interfaces, &Configuration.Interfaces, Control, Name) == -1) goto error; + if(stop_jail(Data.JID) == -1) goto error; + char Path[PATH_MAX]; + snprintf(Path, sizeof(Path), "%s/%s", FilesystemPrefix, Name); + if(stop_fs(&Configuration.Filesystem, Path) == -1) goto error; + Free_all(); + return 0; +error: + Free_all(); + return -1; +} + +int +get_endpoint(char const *Name, ucl_object_t *configuration, ucl_object_t *data, char const *Interface) +{ + if(ValidJailName(Name) == false) return -1; + Load_configuration(configuration); + Load_data(data); + for(size_t i = 0; i < Configuration.Interfaces.Count; ++i) + { + interface_configuration *C = Configuration.Interfaces._ + i; + if(strcmp(C->Name, Interface) == 0) + { + interface *E = Data.Interfaces._ + i; + jprint_state S; + bzero(&S, sizeof(S)); + S.F = stdout; + JPrintObjectBegin(&S); + JPrintMember(&S, "address"); + char Address[NG_PATHSIZ]; + snprintf(Address, sizeof(Address), "[%x]:", E->ID); + JPrint_string(&S, Address); + JPrintMember(&S, "hook"); + JPrint_string(&S, "ether"); + JPrintObjectEnd(&S); + Free_all(); + return 0; + } + } + Free_all(); + return -1; +} + +int +cmd(char const *Name, ucl_object_t *configuration, ucl_object_t *data, size_t ArgCount, char **Arg) +{ + if(ValidJailName(Name) == false) return -1; + Load_configuration(configuration); + Load_data(data); + if(ArgCount < 1) usage(); + char const *Command = Arg[0]; + --ArgCount; + ++Arg; + if(strcmp(Command, "sh") == 0) + { + size_t ExtraArguments = 2; + char **Args = alloca(sizeof(char *) * (ArgCount + ExtraArguments + 1)); + Args[0] = "jexec"; + Args[1] = strdup(Name); + memcpy(Args + ExtraArguments, Arg, sizeof(char *) * ArgCount); + Args[ArgCount + ExtraArguments] = NULL; + execvp("jexec", Args); + fprintf(stderr, "execvp failed: %s\n", strerror(errno)); + exit(-1); + } else if(strcmp(Command, "wireshark") == 0) + { + if(ArgCount < 1) usage(); + char const *Interface = Arg[0]; + --ArgCount; + ++Arg; + fd Pipe[2]; + if(pipe(Pipe) == -1) + { + fprintf(stderr, "pipe: %s\n", strerror(errno)); + return -1; + } + pid_t PID = fork(); + if(PID == -1) + { + fprintf(stderr, "fork: %s\n", strerror(errno)); + return -1; + } else if(PID == 0) + { + dup2(Pipe[1], STDOUT_FILENO); + close(Pipe[0]); + close(Pipe[1]); + close_all(); + if(jail_attach(Data.JID)) + { + fprintf(stderr, "jail_attach(%d): %s\n", Data.JID, strerror(errno)); + exit(-1); + } + execlp("tcpdump", "tcpdump", "-s", "-0", "-U", "-w", "-", "-i", Interface, NULL); + fprintf(stderr, "execlp failed: %s\n", strerror(errno)); + exit(-1); + } else + { + dup2(Pipe[0], STDIN_FILENO); + close(Pipe[0]); + close(Pipe[1]); + close_all(); + char Title[64]; + snprintf(Title, sizeof(Title), "gui.window_title:%s:%s", Name, Interface); + execlp("wireshark", "wireshark", "-o", Title, "-k", "-i", "-", NULL); + fprintf(stderr, "execlp failed: %s\n", strerror(errno)); + exit(-1); + } + } else + { + fprintf(stderr, "unknown command %s\n", Command); + usage(); + } +} + +int +mod(char const *Name, ucl_object_t *configuration, ucl_object_t *data, size_t ArgCount, char **Arg) +{ + if(ValidJailName(Name) == false) return -1; + Load_configuration(configuration); + Load_data(data); + UNUSED(ArgCount, Arg); + usage(); +} diff --git a/src/sf.jail/state.c b/src/sf.jail/state.c new file mode 100644 index 0000000..893961e --- /dev/null +++ b/src/sf.jail/state.c @@ -0,0 +1,396 @@ +#include "state.h" + +// FREE + +void +Free_interface_configuration(interface_configuration *E) +{ + free(E->Name); + switch(E->Type) + { + case interface_type_eiface: break; + case interface_type_steal: free(E->steal.Interface); break; + case interface_type_COUNT: + default: __builtin_unreachable(); + } +} + +void +Free_interfaces_configuration(interfaces_configuration *E) +{ + for(size_t i = 0; i < E->Count; ++i) Free_interface_configuration(E->_ + i); + free(E->_); +} + +void +Free_mapping(mapping *E) +{ + free(E->Source); + free(E->Target); +} + +void +Free_filesystem_configuration(filesystem_configuration *E) +{ + for(size_t i = 0; i < E->LayerCount; ++i) free(E->Layer[i]); + if(E->Layer != NULL) free(E->Layer); + if(E->Temporary != NULL) free(E->Temporary); + for(size_t i = 0; i < E->VolumeCount; ++i) Free_mapping(E->Volume + i); + if(E->Volume != NULL) free(E->Volume); +} + +void +Free_configuration(configuration *E) +{ + Free_filesystem_configuration(&E->Filesystem); + Free_interfaces_configuration(&E->Interfaces); + Free_string_array(&E->Init); + Free_string_array(&E->Shutdown); +} + +void +Free_interface(interface *E) +{ + UNUSED(E); +} + +void +Free_interfaces(interfaces *E) +{ + for(size_t i = 0; i < E->Count; ++i) Free_interface(E->_ + i); + free(E->_); +} + +void +Free_data(data *E) +{ + Free_interfaces(&E->Interfaces); +} + +// PARSE + +char * +Parse_interface_type(interface_type *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(STRING); + char const *Type = ucl_object_tostring(root); + for(interface_type i = 0; i < interface_type_COUNT; ++i) + { + if(strcmp(interface_type_Names[i], Type) == 0) + { + *E = i; + return NULL; + } + } + asprintf(&Error, "%s invalid interface type '%s'", Position, Type); + goto error; +error: + return Error; +} + +char * +Parse_interface_configuration(interface_configuration *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(OBJECT); + { + char *NewPosition; + asprintf(&NewPosition, "%s.type", Position); + Error = Parse_interface_type(&E->Type, ucl_object_lookup(root, "type"), NewPosition); + free(NewPosition); + if(Error != NULL) goto error; + } + switch(E->Type) + { + case interface_type_eiface: break; + case interface_type_steal: + { + ucl_object_t const *interface = ucl_object_lookup(root, "interface"); + UCL_CHECK(interface, STRING); + E->steal.Interface = strdup(ucl_object_tostring(interface)); + break; + } + case interface_type_COUNT: + default: __builtin_unreachable(); + } + return NULL; +error: + return Error; +} + +char * +Parse_interfaces_configuration(interfaces_configuration *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(OBJECT); + E->Count = root->len; + E->_ = calloc(sizeof(interface_configuration), E->Count); + { + ucl_object_iter_t it = NULL; + for(E->Count = 0; E->Count < root->len; ++E->Count) + { + ucl_object_t const *v = ucl_iterate_object(root, &it, true); + if(v == NULL) break; + char const *k = ucl_object_key(v); + if(k == NULL) continue; + interface_configuration *I = E->_ + E->Count; + char *NewPosition; + asprintf(&NewPosition, "%s['%s']", Position, k); + Error = Parse_interface_configuration(I, v, NewPosition); + free(NewPosition); + if(Error != NULL) goto error; + I->Name = strdup(k); + } + } + return NULL; +error: + Free_interfaces_configuration(E); + return Error; +} + +char * +Parse_mapping(mapping *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(OBJECT); + ucl_object_t const *source = ucl_object_lookup(root, "source"); + UCL_CHECK(source, STRING); + ucl_object_t const *target = ucl_object_lookup(root, "target"); + UCL_CHECK(target, STRING); + E->Source = strdup(ucl_object_tostring(source)); + E->Target = strdup(ucl_object_tostring(target)); + return NULL; +error: + return Error; +} + +char * +Parse_filesystem_configuration(filesystem_configuration *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(OBJECT); + ucl_object_t const *layers = ucl_object_lookup(root, "layers"); + UCL_CHECK_OPTIONAL(layers, ARRAY); + ucl_object_t const *temporary = ucl_object_lookup(root, "temporary"); + UCL_CHECK_OPTIONAL(temporary, STRING); + ucl_object_t const *volumes = ucl_object_lookup(root, "volumes"); + UCL_CHECK_OPTIONAL(volumes, ARRAY); + ucl_object_t const *devfs = ucl_object_lookup(root, "devfs"); + UCL_CHECK_OPTIONAL(devfs, INT); + if(layers != NULL) + { + E->Layer = calloc(sizeof(char *), layers->len); + ucl_object_iter_t it = NULL; + for(E->LayerCount = 0; E->LayerCount < layers->len; ++E->LayerCount) + { + ucl_object_t const *v = ucl_iterate_object(layers, &it, true); + if(v == NULL) break; + char const *k = ucl_object_key(v); + if(k != NULL) continue; + if(v->type != UCL_STRING) + { + asprintf(&Error, "%s.layers[%zu] is not " UCL_CHECK_HELPER(STRING), Position, E->LayerCount); + goto error; + } + E->Layer[E->LayerCount] = strdup(ucl_object_tostring(v)); + } + } + if(temporary != NULL) E->Temporary = strdup(ucl_object_tostring(temporary)); + if(volumes != NULL) + { + E->Volume = calloc(sizeof(mapping), volumes->len); + ucl_object_iter_t it = NULL; + for(E->VolumeCount = 0; E->VolumeCount < volumes->len; ++E->VolumeCount) + { + ucl_object_t const *v = ucl_iterate_object(volumes, &it, true); + if(v == NULL) break; + char const *k = ucl_object_key(v); + if(k != NULL) continue; + mapping *I = E->Volume + E->VolumeCount; + char *NewPosition; + asprintf(&NewPosition, "%s.volumes[%zu]", Position, E->VolumeCount); + Error = Parse_mapping(I, v, NewPosition); + free(NewPosition); + if(Error != NULL) goto error; + } + } + E->DevfsRuleset = ucl_object_toint(devfs); + return NULL; +error: + Free_filesystem_configuration(E); + return Error; +} + +char * +Parse_configuration(configuration *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(OBJECT); + { + char *NewPosition; + asprintf(&NewPosition, "%s.interfaces", Position); + Error = + Parse_interfaces_configuration(&E->Interfaces, ucl_object_lookup(root, "interfaces"), NewPosition); + free(NewPosition); + if(Error != NULL) goto error; + } + { + char *NewPosition; + asprintf(&NewPosition, "%s.filesystem", Position); + Error = + Parse_filesystem_configuration(&E->Filesystem, ucl_object_lookup(root, "filesystem"), NewPosition); + free(NewPosition); + if(Error != NULL) goto error; + } + { + char *NewPosition; + asprintf(&NewPosition, "%s.init", Position); + Error = Parse_string_array(&E->Init, ucl_object_lookup(root, "init"), NewPosition); + free(NewPosition); + if(Error != NULL) goto error; + } + { + char *NewPosition; + asprintf(&NewPosition, "%s.shutdown", Position); + Error = Parse_string_array(&E->Shutdown, ucl_object_lookup(root, "shutdown"), NewPosition); + free(NewPosition); + if(Error != NULL) goto error; + } + return NULL; +error: + return Error; +} + +char * +Parse_interface(interface *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(OBJECT); + { + char *NewPosition; + asprintf(&NewPosition, "%s.type", Position); + Error = Parse_interface_type(&E->Type, ucl_object_lookup(root, "type"), NewPosition); + free(NewPosition); + if(Error != NULL) goto error; + } + switch(E->Type) + { + case interface_type_eiface: + { + ucl_object_t const *id = ucl_object_lookup(root, "id"); + UCL_CHECK(id, INT); + s64 ID = ucl_object_toint(id); + if(ID < 0 || ID > INT32_MAX) + { + asprintf(&Error, "%s id invalid (%ld)", Position, ID); + goto error; + } + E->ID = (u32)ID; + break; + } + case interface_type_steal: break; + case interface_type_COUNT: + default: __builtin_unreachable(); + } + return NULL; +error: + return Error; +} + +char * +Parse_interfaces(interfaces *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(ARRAY); + E->Count = root->len; + E->_ = calloc(sizeof(interface_configuration), E->Count); + { + ucl_object_iter_t it = NULL; + for(E->Count = 0; E->Count < root->len; ++E->Count) + { + ucl_object_t const *v = ucl_iterate_object(root, &it, true); + if(v == NULL) break; + char const *k = ucl_object_key(v); + if(k != NULL) continue; + interface *I = E->_ + E->Count; + char *NewPosition; + asprintf(&NewPosition, "%s['%zu']", Position, E->Count); + Error = Parse_interface(I, v, NewPosition); + free(NewPosition); + if(Error != NULL) goto error; + } + } + return NULL; +error: + Free_interfaces(E); + return Error; +} + +char * +Parse_data(data *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(OBJECT); + ucl_object_t const *jid = ucl_object_lookup(root, "jid"); + UCL_CHECK(jid, INT); + int64_t JID = ucl_object_toint(jid); + if(JID < 0 || JID > INT32_MAX) + { + asprintf(&Error, "%s jid invalid (%ld)", Position, JID); + goto error; + } + E->JID = (jid_t)JID; + { + char *NewPosition; + asprintf(&NewPosition, "%s.interfaces", Position); + Error = Parse_interfaces(&E->Interfaces, ucl_object_lookup(root, "interfaces"), NewPosition); + free(NewPosition); + if(Error != NULL) goto error; + } + return NULL; +error: + return Error; +} + +// SAVE + +void +Save_interface(jprint_state *S, interface const *E) +{ + JPrintObjectBegin(S); + JPrintMember(S, "type"); + JPrint_string(S, interface_type_Names[E->Type]); + switch(E->Type) + { + case interface_type_eiface: + { + JPrintMember(S, "id"); + JPrint_ssize_t(S, E->ID); + break; + } + case interface_type_steal: break; + case interface_type_COUNT: + default: __builtin_unreachable(); + } + JPrintObjectEnd(S); +} + +void +Save_interfaces(jprint_state *S, interfaces const *E) +{ + JPrintArrayBegin(S); + for(size_t i = 0; i < E->Count; ++i) Save_interface(S, E->_ + i); + JPrintArrayEnd(S); +} + +void +Save_data(jprint_state *S, data const *E) +{ + JPrintObjectBegin(S); + JPrintMember(S, "jid"); + JPrint_ssize_t(S, E->JID); + JPrintMember(S, "interfaces"); + Save_interfaces(S, &E->Interfaces); + JPrintObjectEnd(S); +} diff --git a/src/sf.jail/state.h b/src/sf.jail/state.h new file mode 100644 index 0000000..d22d374 --- /dev/null +++ b/src/sf.jail/state.h @@ -0,0 +1,93 @@ +#pragma once + +#include "../util.h" + +NAMED_ENUM(interface_type, eiface, steal); + +struct +{ + char *Name; + interface_type Type; + union + { + struct + { + } eiface; + struct + { + char *Interface; + } steal; + }; +} typedef interface_configuration; + +struct +{ + size_t Count; + interface_configuration *_; +} typedef interfaces_configuration; + +struct +{ + char *Source; + char *Target; +} typedef mapping; + +struct +{ + size_t LayerCount; + char **Layer; + char *Temporary; + size_t VolumeCount; + mapping *Volume; + s64 DevfsRuleset; +} typedef filesystem_configuration; + +struct +{ + interfaces_configuration Interfaces; + filesystem_configuration Filesystem; + string_array Init; + string_array Shutdown; +} typedef configuration; + +struct +{ + u32 ID; + interface_type Type; +} typedef interface; + +struct +{ + size_t Count; + interface *_; +} typedef interfaces; + +struct +{ + interfaces Interfaces; + jid_t JID; +} typedef data; + +void Free_interface_configuration(interface_configuration *); +void Free_interfaces_configuration(interfaces_configuration *); +void Free_mapping(mapping *); +void Free_filesystem_configuration(filesystem_configuration *); +void Free_configuration(configuration *); +void Free_interface(interface *); +void Free_interfaces(interfaces *); +void Free_data(data *); + +char *Parse_interface_type(interface_type *, ucl_object_t const *, char const *Position); +char *Parse_interface_configuration(interface_configuration *, ucl_object_t const *, char const *Position); +char *Parse_interfaces_configuration(interfaces_configuration *, ucl_object_t const *, char const *Position); +char *Parse_mapping(mapping *, ucl_object_t const *, char const *Position); +char *Parse_filesystem_configuration(filesystem_configuration *, ucl_object_t const *, char const *Position); +char *Parse_configuration(configuration *, ucl_object_t const *, char const *Position); +char *Parse_interface(interface *, ucl_object_t const *, char const *Position); +char *Parse_interfaces(interfaces *, ucl_object_t const *, char const *Position); +char *Parse_data(data *, ucl_object_t const *, char const *Position); + +void Save_interface_type(jprint_state *, interface_type const *); +void Save_interface(jprint_state *, interface const *); +void Save_interfaces(jprint_state *, interfaces const *); +void Save_data(jprint_state *, data const *); diff --git a/src/sf.skel/main.c b/src/sf.skel/main.c new file mode 100644 index 0000000..5460454 --- /dev/null +++ b/src/sf.skel/main.c @@ -0,0 +1,116 @@ +#include "../module/module.h" +#include "state.h" + +char const *Usage = "usage: %1$s start [name] [configuration]\n" + " %1$s stop [name] [configuration] [data]\n" + " %1$s get-endpoint [name] [configuration] [data] [interface]\n" + "unsupported commands: cmd mod\n"; + +static configuration Configuration; +static data Data; +static bool ConfigurationLoaded; +static bool DataLoaded; + +// HELPERS + +static void +Free_all(void) +{ + if(ConfigurationLoaded == true) Free_configuration(&Configuration); + if(DataLoaded == true) Free_data(&Data); +} + +static void +Load_configuration(ucl_object_t *root) +{ + char *Error = Parse_configuration(&Configuration, root, ""); + if(Error != NULL) + { + fprintf(stderr, "%s: configuration error: %s\n", Arg0, Error); + free(Error); + Free_all(); + exit(-1); + } + ConfigurationLoaded = true; +} + +static void +Load_data(ucl_object_t *root) +{ + char *Error = Parse_data(&Data, root, ""); + if(Error != NULL) + { + fprintf(stderr, "%s: data error: %s\n", Arg0, Error); + free(Error); + Free_all(); + exit(-1); + } + DataLoaded = true; + // TODO: check that data matches configuration +} + +// COMMANDS + +int +start(char const *Name, ucl_object_t *configuration) +{ + UNUSED(Name); + Load_configuration(configuration); + // Implement + if((true)) goto error; + jprint_state S; + bzero(&S, sizeof(S)); + S.F = stdout; + Save_data(&S, &Data); + Free_all(); + return 0; +error: + Free_all(); + return -1; +} + +int +stop(char const *Name, ucl_object_t *configuration, ucl_object_t *data) +{ + UNUSED(Name); + Load_configuration(configuration); + Load_data(data); + // Implement + if((true)) goto error; + Free_all(); + return 0; +error: + Free_all(); + return -1; +} + +int +get_endpoint(char const *Name, ucl_object_t *configuration, ucl_object_t *data, char const *Interface) +{ + UNUSED(Name, Interface); + Load_configuration(configuration); + Load_data(data); + // Implement + Free_all(); + return -1; +} + +int +cmd(char const *Name, ucl_object_t *configuration, ucl_object_t *data, size_t ArgCount, char **Arg) +{ + UNUSED(Name, ArgCount, Arg); + Load_configuration(configuration); + Load_data(data); + // Implement + usage(); +} + +int +mod(char const *Name, ucl_object_t *configuration, ucl_object_t *data, size_t ArgCount, char **Arg) +{ + UNUSED(Name, ArgCount, Arg); + Load_configuration(configuration); + Load_data(data); + // Implement + usage(); +} diff --git a/src/sf.skel/state.c b/src/sf.skel/state.c new file mode 100644 index 0000000..3a1d297 --- /dev/null +++ b/src/sf.skel/state.c @@ -0,0 +1,49 @@ +#include "state.h" + +// FREE + +void +Free_configuration(configuration *E) +{ + UNUSED(E); +} + +void +Free_data(data *E) +{ + UNUSED(E); +} + +// PARSE + +char * +Parse_configuration(configuration *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(OBJECT); + UNUSED(E); + return NULL; +error: + return Error; +} + +char * +Parse_data(data *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(OBJECT); + UNUSED(E); + return NULL; +error: + return Error; +} + +// SAVE + +void +Save_data(jprint_state *S, data const *E) +{ + JPrintObjectBegin(S); + UNUSED(E); + JPrintObjectEnd(S); +} diff --git a/src/sf.skel/state.h b/src/sf.skel/state.h new file mode 100644 index 0000000..9f52a2b --- /dev/null +++ b/src/sf.skel/state.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../jprint.h" + +struct +{ +} typedef configuration; + +struct +{ +} typedef data; + +void Free_configuration(configuration *); +void Free_data(data *); + +char *Parse_configuration(configuration *, ucl_object_t const *, char const *Position); +char *Parse_data(data *, ucl_object_t const *, char const *Position); + +void Save_data(jprint_state *, data const *); diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..f7d0aef --- /dev/null +++ b/src/util.c @@ -0,0 +1,396 @@ +#include "util.h" + +#include <net/ethernet.h> +#include <netgraph.h> +#include <netgraph/ng_message.h> +#include <netgraph/ng_eiface.h> +#include <netgraph/ng_bridge.h> +#include <sys/user.h> +#include <libutil.h> + +#include <sys/jail.h> + +#include "/usr/src/sbin/mount/mntopts.h" +#include <sys/mount.h> + +int +DestroyNetgraphNode(u32 ID, fd Control) +{ + char Path[NG_PATHSIZ]; + snprintf(Path, sizeof(Path), "[%x]:", ID); + if(NgSendMsg(Control, Path, NGM_GENERIC_COOKIE, NGM_SHUTDOWN, NULL, 0) == -1) + { + fprintf(stderr, "ngctl shutdown %s: %s\n", Path, strerror(errno)); + return -1; + } + return 0; +} + +int +Create_eiface(u32 *ID, fd Control) +{ + *ID = 0; + { + struct ngm_mkpeer D; + strcpy(D.type, "eiface"); + strcpy(D.ourhook, "_"); + strcpy(D.peerhook, "ether"); + if(NgSendMsg(Control, ".:", NGM_GENERIC_COOKIE, NGM_MKPEER, &D, sizeof(D)) == -1) + { + fprintf(stderr, "ngctl mkpeer .: eiface _ ether: %s\n", strerror(errno)); + goto error; + } + } + { + if(NgSendMsg(Control, ".:_", NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0) == -1) + { + fprintf(stderr, "ngctl info .:_ : %s\n", strerror(errno)); + goto error; + } + struct ng_mesg *Response; + if(NgAllocRecvMsg(Control, &Response, NULL) == -1) + { + fprintf(stderr, "NgAllocRecvMsg: %s\n", strerror(errno)); + return -1; + } + struct nodeinfo *Info = (void *)Response->data; + *ID = Info->id; + free(Response); + } + { + struct ngm_rmhook D; + strcpy(D.ourhook, "_"); + if(NgSendMsg(Control, ".:", NGM_GENERIC_COOKIE, NGM_RMHOOK, &D, sizeof(D)) == -1) + { + fprintf(stderr, "ngctl rmhook .: _: %s\n", strerror(errno)); + goto error; + } + } + return 0; +error: + if(*ID != 0) DestroyNetgraphNode(*ID, Control); + return -1; +} + +int +Create_bridge(u32 *ID, fd Control) +{ + *ID = 0; + { + struct ngm_mkpeer D; + strcpy(D.type, "bridge"); + strcpy(D.ourhook, "_"); + strcpy(D.peerhook, "link0"); + if(NgSendMsg(Control, ".:", NGM_GENERIC_COOKIE, NGM_MKPEER, &D, sizeof(D)) == -1) + { + fprintf(stderr, "ngctl mkpeer .: eiface _ ether: %s\n", strerror(errno)); + goto error; + } + } + { + if(NgSendMsg(Control, ".:_", NGM_BRIDGE_COOKIE, NGM_BRIDGE_SET_PERSISTENT, NULL, 0) == -1) + { + fprintf(stderr, "ngctl msg .:_ setpersistent: %s\n", strerror(errno)); + goto error; + } + } + { + if(NgSendMsg(Control, ".:_", NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0) == -1) + { + fprintf(stderr, "ngctl info .:_ : %s\n", strerror(errno)); + goto error; + } + struct ng_mesg *Response; + if(NgAllocRecvMsg(Control, &Response, NULL) == -1) + { + fprintf(stderr, "NgAllocRecvMsg: %s\n", strerror(errno)); + return -1; + } + struct nodeinfo *Info = (void *)Response->data; + *ID = Info->id; + free(Response); + } + { + struct ngm_rmhook D; + strcpy(D.ourhook, "_"); + if(NgSendMsg(Control, ".:", NGM_GENERIC_COOKIE, NGM_RMHOOK, &D, sizeof(D)) == -1) + { + fprintf(stderr, "ngctl rmhook .: _: %s\n", strerror(errno)); + goto error; + } + } + return 0; +error: + if(*ID != 0) DestroyNetgraphNode(*ID, Control); + return -1; +} + +char * +getifname(u32 ID, fd Control) +{ + struct ng_mesg *Response; + char Path[NG_PATHSIZ]; + snprintf(Path, sizeof(Path), "[%x]:", ID); + if(NgSendMsg(Control, Path, NGM_EIFACE_COOKIE, NGM_EIFACE_GET_IFNAME, NULL, 0) == -1) + { + fprintf(stderr, "ngctl msg %s getifname: %s\n", Path, strerror(errno)); + return NULL; + } + if(NgAllocRecvMsg(Control, &Response, NULL) == -1) + { + fprintf(stderr, "NgAllocRecvMsg: %s\n", strerror(errno)); + return NULL; + } + char *Result = strdup((void *)Response->data); + free(Response); + return Result; +} + +void +close_all(void) +{ + int Count; + struct kinfo_file *Result = kinfo_getfile(getpid(), &Count); + if(Result == NULL) return; + for(int i = 0; i < Count; ++i) + { + if(Result[i].kf_fd > STDERR_FILENO) close(Result[i].kf_fd); + } + free(Result); +} + +int +command_a(char const *Program, char **Arg) +{ + pid_t PID = -1; + PID = fork(); + if(PID == -1) + { + fprintf(stderr, "fork failed: %s\n", strerror(errno)); + goto error; + } + if(PID == 0) + { + close(STDIN_FILENO); + close(STDOUT_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + close_all(); + execvp(Program, Arg); + fprintf(stderr, "exec failed: %s\n", strerror(errno)); + exit(-1); + } + int Status; + waitpid(PID, &Status, 0); + if(!WIFEXITED(Status) || (WEXITSTATUS(Status) != 0)) return -1; + return 0; +error: + return -1; +} + +int +command_v(char const *Program, 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 command_a(Program, Args); +} + +int +command(char const *Program, ...) +{ + va_list Arg; + va_start(Arg, Program); + int Result = command_v(Program, Arg); + va_end(Arg); + return Result; +} + +void +Free_string_array(string_array *E) +{ + for(size_t i = 0; i < E->Count; ++i) free(E->_[i]); + if(E->_ != NULL) free(E->_); +} + +char * +Parse_string_array(string_array *E, ucl_object_t const *root, char const *Position) +{ + char *Error; + UCL_CHECK_ROOT(ARRAY); + E->Count = root->len; + E->_ = calloc(sizeof(char *), E->Count + 1); // NOTE: we null-terminate it + ucl_object_iter_t it = NULL; + for(E->Count = 0; E->Count < root->len; ++E->Count) + { + ucl_object_t const *v = ucl_iterate_object(root, &it, true); + if(v == NULL) break; + char const *k = ucl_object_key(v); + if(k != NULL) continue; + if(v->type != UCL_STRING) + { + asprintf(&Error, "%s[%zu] is not a string", Position, E->Count); + goto error; + } + E->_[E->Count] = strdup(ucl_object_tostring(v)); + } + return NULL; +error: + Free_string_array(E); + return Error; +} + +void +Save_string_array(jprint_state *S, string_array const *E) +{ + JPrintArrayBegin(S); + for(size_t i = 0; i < E->Count; ++i) JPrint_string(S, E->_[i]); + JPrintArrayEnd(S); +} + +int +exec_string_array(string_array *E, bool Wait, jid_t JID) +{ + if(E->Count == 0) return 0; + pid_t PID = fork(); + if(PID == -1) return -1; + if(PID == 0) + { + close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + close_all(); + if(JID != 0) + { + if(jail_attach(JID) == -1) + { + fprintf(stderr, "failed to attach to jail %d: %s\n", JID, strerror(errno)); + exit(-1); + } + } + execvp(E->_[0], E->_); + fprintf(stderr, "failed exec '%s': %s\n", E->_[0], strerror(errno)); + exit(-1); + } + if(Wait == true) + { + int Exit; + waitpid(PID, &Exit, 0); + if(!WIFEXITED(Exit) || (WEXITSTATUS(Exit) != 0)) return -1; + } + return 0; +} + +static int +subdir(char const *p, char const *dir) +{ + size_t l = strlen(dir); + if(l <= 1) return 1; + if((strncmp(p, dir, l) == 0) && (p[l] == '/' || p[l] == '\0')) return 1; + return 0; +} + +int +mount_unionfs(char const *Target, char const *Source, bool Below) +{ + char TargetR[MAXPATHLEN]; + char SourceR[MAXPATHLEN]; + if(checkpath(Target, TargetR) != 0) + { + fprintf(stderr, "checkpath %s\n", TargetR); + return -1; + } + if(checkpath(Source, SourceR) != 0) + { + fprintf(stderr, "checkpath %s\n", SourceR); + return -1; + } + if(subdir(TargetR, SourceR) || subdir(SourceR, TargetR)) + { + fprintf(stderr, "%s (%s) and %s (%s) are not distinct paths\n", Target, TargetR, Source, SourceR); + return -1; + } + char errmsg[255]; + bzero(errmsg, sizeof(errmsg)); + struct iovec *iov = NULL; + int iovlen = 0; + build_iovec(&iov, &iovlen, "fstype", "unionfs", (size_t)-1); + if(Below == true) build_iovec(&iov, &iovlen, "below", NULL, 0); + build_iovec(&iov, &iovlen, "fspath", TargetR, (size_t)-1); + build_iovec(&iov, &iovlen, "from", SourceR, (size_t)-1); + build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); + if(nmount(iov, (u32)iovlen, 0)) + { + fprintf(stderr, "nmount unionfs %s: %s\n", SourceR, errmsg); + return -1; + } + return 0; +} + +int +mount_devfs(char const *Target) +{ + char TargetR[MAXPATHLEN]; + if(checkpath(Target, TargetR) != 0) + { + fprintf(stderr, "checkpath %s\n", TargetR); + return -1; + } + char errmsg[255]; + bzero(errmsg, sizeof(errmsg)); + struct iovec *iov = NULL; + int iovlen = 0; + build_iovec(&iov, &iovlen, "fstype", "devfs", (size_t)-1); + build_iovec(&iov, &iovlen, "fspath", TargetR, (size_t)-1); + build_iovec(&iov, &iovlen, "from", "devfs", (size_t)-1); + build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); + if(nmount(iov, (u32)iovlen, 0)) + { + fprintf(stderr, "nmount devfs: %s\n", errmsg); + return -1; + } + return 0; +} + +int +mount_nullfs(char const *Target, char const *Source) +{ + char TargetR[MAXPATHLEN]; + char SourceR[MAXPATHLEN]; + if(checkpath(Target, TargetR) != 0) + { + fprintf(stderr, "checkpath %s\n", TargetR); + return -1; + } + if(checkpath(Source, SourceR) != 0) + { + fprintf(stderr, "checkpath %s\n", SourceR); + return -1; + } + if(subdir(TargetR, SourceR) || subdir(SourceR, TargetR)) + { + fprintf(stderr, "%s (%s) and %s (%s) are not distinct paths\n", Target, TargetR, Source, SourceR); + return -1; + } + char errmsg[255]; + bzero(errmsg, sizeof(errmsg)); + struct iovec *iov = NULL; + int iovlen = 0; + build_iovec(&iov, &iovlen, "fstype", "nullfs", (size_t)-1); + build_iovec(&iov, &iovlen, "fspath", TargetR, (size_t)-1); + build_iovec(&iov, &iovlen, "from", SourceR, (size_t)-1); + build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); + if(nmount(iov, (u32)iovlen, 0)) + { + fprintf(stderr, "nmount nullfs %s: %s\n", SourceR, errmsg); + return -1; + } + return 0; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..02ed6ad --- /dev/null +++ b/src/util.h @@ -0,0 +1,28 @@ +#pragma once + +#include "base.h" +#include "jprint.h" + +int Create_eiface(u32 *ID, fd Control); +int Create_bridge(u32 *ID, fd Control); +int DestroyNetgraphNode(u32 ID, fd Control); +char *getifname(u32 ID, fd Control); +void close_all(void); +int command_a(char const *Program, char **Arg); +int command_v(char const *Program, va_list Arg); +int command(char const *Program, ...); + +struct +{ + size_t Count; + char **_; +} typedef string_array; + +void Free_string_array(string_array *); +char *Parse_string_array(string_array *, ucl_object_t const *root, char const *Position); +void Save_string_array(jprint_state *, string_array const *); +int exec_string_array(string_array *, bool Wait, jid_t JID); + +int mount_unionfs(char const *Target, char const *Source, bool Below); +int mount_devfs(char const *Target); +int mount_nullfs(char const *Target, char const *Source); |
