/** * 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 #include #include #include #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; }