aboutsummaryrefslogtreecommitdiff
path: root/scriptbase_build.c
diff options
context:
space:
mode:
Diffstat (limited to 'scriptbase_build.c')
-rw-r--r--scriptbase_build.c534
1 files changed, 534 insertions, 0 deletions
diff --git a/scriptbase_build.c b/scriptbase_build.c
new file mode 100644
index 0000000..2bb9e80
--- /dev/null
+++ b/scriptbase_build.c
@@ -0,0 +1,534 @@
+/**
+ * part of Hydrilla
+ * Routines for building scriptbase from resources and configuratons (stored in
+ * `/var/lib/hydrilla/content/' or another, user-specified location.
+ *
+ * Copyright (C) 2021 Wojtek Kosior
+ * Redistribution terms are gathered in the `copyright' file.
+ */
+
+#define _POSIX_C_SOURCE 200809L /* strdup() */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "hashtable.h"
+#include "string_buf.h"
+
+#include "scriptbase.h"
+
+static const char scriptstr[] = "script", bagstr[] = "bag", pagestr[] = "page";
+
+
+static void *add_or_get(const char *component_name, char component_type,
+ struct scriptbase *base);
+
+static struct script *script_create(const char *name)
+{
+ struct script *script;
+
+ script = malloc(sizeof(struct script));
+ if (!script)
+ return NULL;
+
+ script->name = strdup(name);
+
+ if (!script->name)
+ goto free_script;
+
+ script->location = NULL;
+ script->sha256 = NULL;
+ script->filled = false;
+
+ return script;
+
+free_script:
+ free(script);
+
+ return NULL;
+}
+
+static int script_fill(struct script *script, const char *dir,
+ const char *filename, const char *sha256)
+{
+ char *location = NULL;
+ size_t location_len = 0, location_filled = 0;
+
+ if (sb_raw_sprintf(&location, &location_len, &location_filled,
+ "%s/%s", dir, filename))
+ goto free_location;
+ script->location = location;
+
+ script->sha256 = strdup(sha256);
+ if (!script->sha256)
+ goto free_location;
+
+ script->filled = true;
+
+ return 0;
+
+free_location:
+ free(location);
+ script->location = NULL;
+
+ return -1;
+}
+
+static void script_free(struct script *script)
+{
+ if (!script)
+ return;
+
+ free(script->name);
+ free(script->location);
+ free(script->sha256);
+ free(script);
+}
+
+static struct bag *bag_create(const char *name)
+{
+ struct bag *bag;
+
+ bag = malloc(sizeof(struct bag));
+ if (!bag)
+ return NULL;
+
+ bag->name = strdup(name);
+
+ if (!bag->name)
+ goto free_bag;
+
+ bag->components = NULL;
+ bag->last_component = NULL;
+ bag->filled = false;
+
+ return bag;
+
+free_bag:
+ free(bag);
+
+ return NULL;
+}
+
+static int bag_add(struct bag *bag, const char *component_name,
+ const char *component_type, struct scriptbase *base)
+{
+ struct component_ref *new_ref;
+
+ new_ref = malloc(sizeof(struct component_ref));
+ if (!new_ref)
+ return -1;
+
+ new_ref->component.any =
+ add_or_get(component_name, *component_type, base);
+
+ if (!new_ref->component.any)
+ goto free_ref;
+
+ new_ref->component_type = component_type;
+ new_ref->next = NULL;
+
+ *(bag->components ? &bag->last_component->next : &bag->components) =
+ new_ref;
+ bag->last_component = new_ref;
+ bag->filled = true;
+
+ return 0;
+
+free_ref:
+ free(new_ref);
+
+ return -1;
+}
+
+static void bag_free(struct bag *bag)
+{
+ struct component_ref *tmp1, *tmp2;
+
+ if (!bag)
+ return;
+
+ tmp1 = bag->components;
+
+ while (tmp1) {
+ tmp2 = tmp1->next;
+ free(tmp1);
+ tmp1 = tmp2;
+ }
+
+ free(bag->name);
+ free(bag);
+}
+
+static struct page *page_create(const char *pattern, const char *payload_name,
+ const char *payload_type,
+ struct scriptbase *base)
+{
+ struct page *page;
+
+ page = malloc(sizeof(struct page));
+ if (!page)
+ return NULL;
+
+ page->pattern = strdup(pattern);
+ if (!page->pattern)
+ goto free_page;
+
+ if (payload_name) {
+ page->payload.any =
+ add_or_get(payload_name, *payload_type, base);
+ if (!page->payload.any)
+ goto free_pattern;
+ } else {
+ page->payload.any = NULL;
+ }
+
+ page->payload_type = payload_type;
+
+ return page;
+
+free_pattern:
+ free(page->pattern);
+
+free_page:
+ free(page);
+
+ return NULL;
+}
+
+static void page_free(struct page *page)
+{
+ if (!page)
+ return;
+
+ free(page->pattern);
+ free(page);
+}
+
+static void *add_or_get(const char *component_name, char component_type,
+ struct scriptbase *base)
+{
+ void *component;
+ hashtable_t *relevant_ht;
+ bool found = true;
+
+ relevant_ht = component_type == *bagstr ? &base->bags : &base->scripts;
+ if (ht_get(relevant_ht, component_name, NULL, &component)) {
+ if (component_type == *bagstr)
+ component = bag_create(component_name);
+ else
+ component = script_create(component_name);
+
+ found = false;
+ }
+
+ if (!component)
+ return NULL;
+
+ /* Name is at the same position in both struct bag and struct script. */
+ if (!found) {
+ switch (ht_add(relevant_ht, ((struct bag*) component)->name,
+ component)) {
+ case HT_NO_MEM:
+ errno = ENOMEM;
+ case HT_KEY_PRESENT:
+ goto free_component;
+ }
+ }
+
+ return component;
+
+free_component:
+ if (component_type == *bagstr)
+ bag_free(component);
+ else
+ script_free(component);
+
+ return NULL;
+}
+
+int scriptbase_init(struct scriptbase *base, const char *repo_url)
+{
+ base->repo_url = strdup(repo_url);
+ if (!base->repo_url)
+ goto end;
+
+ if (ht_string_init(&base->scripts))
+ goto free_url;
+
+ if (ht_string_init(&base->bags))
+ goto free_scripts;
+
+ if (ht_string_init(&base->pages))
+ goto free_bags;
+
+ return 0;
+
+free_bags:
+ ht_destroy(&base->bags);
+
+free_scripts:
+ ht_destroy(&base->scripts);
+
+free_url:
+ free(base->repo_url);
+
+end:
+ errno = ENOMEM;
+ return -1;
+}
+
+static void destroy_cb(void *key, void *val, void *arg)
+{
+ char type = *((const char*) arg);
+
+ if (type == 's')
+ script_free(val);
+ else if (type == 'b')
+ bag_free(val);
+ else
+ page_free(val);
+}
+
+void scriptbase_destroy(struct scriptbase *base)
+{
+ char keys[] = {'s', 'b', 'p'};
+
+ ht_map_destroy(&base->scripts, keys, destroy_cb);
+ ht_map_destroy(&base->bags, keys + 1, destroy_cb);
+ ht_map_destroy(&base->pages, keys + 2, destroy_cb);
+
+ free(base->repo_url);
+}
+
+static int add_component_to_base(const char *name, const void *component,
+ const char *type, struct scriptbase *base)
+{
+ hashtable_t *ht = *type == 's' ? &base->scripts :
+ *type == 'b' ? &base->bags : &base->pages;
+ bool name_collision = false;
+ void *old_val = NULL;
+
+ if (ht_set(ht, name, component, NULL, &old_val)) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (old_val) {
+ if (*type == 's') {
+ name_collision = ((struct script*) old_val)->filled;
+ script_free(old_val);
+ } else if (*type == 'b') {
+ name_collision = ((struct bag*) old_val)->filled;
+ bag_free(old_val);
+ } else {
+ name_collision = true;
+ page_free(old_val);
+ }
+ }
+
+ if (name_collision) {
+ fprintf(stderr, "Multiple occurences of %s %s.\n", type, name);
+ errno = 0;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int catalogue_script(const char *path, cJSON *index_json,
+ struct scriptbase *base)
+{
+ const cJSON *name, *sha256, *location;
+ const char *bad = NULL;
+ struct script *script;
+
+ name = cJSON_GetObjectItemCaseSensitive(index_json, "name");
+ sha256 = cJSON_GetObjectItemCaseSensitive(index_json, "sha256");
+ location = cJSON_GetObjectItemCaseSensitive(index_json, "location");
+
+ if (!cJSON_IsString(name) || name->valuestring == NULL)
+ bad = "name";
+ else if (!cJSON_IsString(sha256) || sha256->valuestring == NULL)
+ bad = "sha256";
+ else if (!cJSON_IsString(location) || location->valuestring == NULL)
+ bad = "location";
+
+ if (bad) {
+ fprintf(stderr, "Missing or invalid field \"%s\".\n", bad);
+ errno = 0;
+ return -1;
+ }
+
+ script = script_create(name->valuestring);
+ if (!script)
+ return -1;
+
+ if (script_fill(script, path, location->valuestring,
+ sha256->valuestring))
+ goto free_script;
+
+ return add_component_to_base(script->name, script, scriptstr, base);
+
+free_script:
+ script_free(script);
+ return -1;
+}
+
+static int component_ref_from_json(const cJSON *component_cJSON,
+ const char **component_type,
+ const char **component_name)
+{
+ const cJSON *component_type_cJSON, *component_name_cJSON;
+
+ if (!cJSON_IsArray(component_cJSON))
+ return -1;
+
+ component_type_cJSON = component_cJSON->child;
+ if (!component_type_cJSON)
+ return -1;
+ component_name_cJSON = component_type_cJSON->next;
+ if (!component_name_cJSON || component_name_cJSON->next)
+ return -1;
+
+ if (!cJSON_IsString(component_type_cJSON) ||
+ !cJSON_IsString(component_name_cJSON))
+ return -1;
+
+ if (!strcmp(scriptstr, component_type_cJSON->valuestring))
+ *component_type = scriptstr;
+ else if (!strcmp(bagstr, component_type_cJSON->valuestring))
+ *component_type = bagstr;
+ else
+ return -1;
+
+ *component_name = component_name_cJSON->valuestring;
+
+ return 0;
+}
+
+static int catalogue_bag(const char *path, cJSON *index_json,
+ struct scriptbase *base)
+{
+ const cJSON *name, *components, *component;
+ const char *component_type, *component_name;
+ struct bag *bag;
+
+ name = cJSON_GetObjectItemCaseSensitive(index_json, "name");
+
+ if (!cJSON_IsString(name) || name->valuestring == NULL) {
+ fprintf(stderr, "Missing or invalid field \"name\".\n");
+ errno = 0;
+ return -1;
+ }
+
+ bag = bag_create(name->valuestring);
+ if (!bag)
+ return -1;
+
+ bag->filled = true;
+
+ components = cJSON_GetObjectItemCaseSensitive(index_json, "components");
+ if (!components)
+ return 0;
+ if (!cJSON_IsArray(components))
+ goto invalid_components;
+
+ cJSON_ArrayForEach(component, components) {
+ if (component_ref_from_json(component,
+ &component_type, &component_name))
+ goto invalid_components;
+
+ /*
+ * component_type now points to a static buffer and
+ * component_name to cJSON-owned memory
+ */
+
+ if (bag_add(bag, component_name, component_type, base))
+ goto free_bag;
+ }
+
+ if (add_component_to_base(bag->name, bag, bagstr, base))
+ goto free_bag;
+
+ return 0;
+
+invalid_components:
+ fprintf(stderr, "Invalid field \"components\"");
+ errno = 0;
+
+free_bag:
+ bag_free(bag);
+ return -1;
+}
+
+static int catalogue_page(const char *path, cJSON *index_json,
+ struct scriptbase *base)
+{
+ const cJSON *pattern, *payload;
+ const char *payload_type = "", *payload_name = NULL;
+ struct page *page;
+
+ pattern = cJSON_GetObjectItemCaseSensitive(index_json, "pattern");
+ payload = cJSON_GetObjectItemCaseSensitive(index_json, "payload");
+
+ if (!cJSON_IsString(pattern) || pattern->valuestring == NULL) {
+ fprintf(stderr, "Missing or invalid field \"pattern\".\n");
+ errno = 0;
+ return -1;
+ }
+
+ if (!payload)
+ goto skip_payload;
+
+ if (component_ref_from_json(payload, &payload_type, &payload_name))
+ goto invalid_payload;
+
+skip_payload:
+ page = page_create(pattern->valuestring, payload_name, payload_type,
+ base);
+ if (!page)
+ goto free_page;
+
+ if (add_component_to_base(page->pattern, page, pagestr, base))
+ goto free_page;
+
+ return 0;
+
+invalid_payload:
+ fprintf(stderr, "Invalid field \"payload\"");
+ errno = 0;
+
+free_page:
+ page_free(page);
+ return -1;
+}
+
+int catalogue_component(const char *path, cJSON *index_json,
+ struct scriptbase *base)
+{
+ const cJSON *type;
+
+ type = cJSON_GetObjectItemCaseSensitive(index_json, "type");
+ if (!cJSON_IsString(type) || type->valuestring == NULL)
+ goto bad_type;
+
+ if (!strcmp(type->valuestring, scriptstr))
+ return catalogue_script(path, index_json, base);
+ else if (!strcmp(type->valuestring, bagstr))
+ return catalogue_bag(path, index_json, base);
+ else if (!strcmp(type->valuestring, pagestr))
+ return catalogue_page(path, index_json, base);
+ else
+ goto bad_type;
+
+ return 0;
+
+bad_type:
+ fprintf(stderr, "Missing or invalid type.");
+
+ errno = 0;
+ return -1;
+}