aboutsummaryrefslogtreecommitdiff
/**
 * 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 catalogue_script(const char *path, cJSON *index_json,
			    struct scriptbase *base)
{
	const cJSON *name, *sha256, *location;
	const char *bad = NULL;
	struct script *script;
	bool filling_existing = false;

	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;
	}

	filling_existing = !ht_get(&base->scripts, name->valuestring,
				   NULL, (void**) &script);

	if (filling_existing) {
		if (script->filled) {
			fprintf(stderr, "Multiple occurences of script %s.\n",
				name->valuestring);
			errno = 0;
			return -1;
		}
	} else {
		script = script_create(name->valuestring);
		if (!script)
			return -1;
	}

	if (script_fill(script, path, location->valuestring,
			sha256->valuestring))
		goto free_script;

	if (!filling_existing && ht_add(&base->scripts, script->name, script)) {
		errno = ENOMEM;
		goto free_script;
	}

	return 0;

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;
	bool filling_existing = false;

	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;
	}

	filling_existing = !ht_get(&base->bags, name->valuestring,
				   NULL, (void**) &bag);
	if (filling_existing) {
		if (bag->filled) {
			fprintf(stderr, "Multiple occurences of bag %s.\n",
				name->valuestring);
			errno = 0;
			return -1;
		}
	} else {
		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 (!filling_existing && ht_add(&base->bags, bag->name, bag)) {
		errno = ENOMEM;
		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;

	switch (ht_add(&base->pages, page->pattern, page)) {
	case 0:
		return 0;
	case HT_NO_MEM:
		errno = ENOMEM;
		break;
	case HT_KEY_PRESENT:
		fprintf(stderr, "Multiple occurences of pattern %s.\n",
			page->pattern);
		errno = 0;
	}

free_page:
	page_free(page);
	return -1;

invalid_payload:
	fprintf(stderr, "Invalid field \"payload\"");
	errno = 0;

	return 0;
}

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;
}