From 0907cfd07329805f68ae5c7976c021b95dce4f93 Mon Sep 17 00:00:00 2001 From: fosslinux Date: Sat, 4 Nov 2023 10:38:35 +1100 Subject: [PATCH 1/8] Add script-generator --- sysa/script-generator.c | 627 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 627 insertions(+) create mode 100644 sysa/script-generator.c diff --git a/sysa/script-generator.c b/sysa/script-generator.c new file mode 100644 index 0000000..f500e56 --- /dev/null +++ b/sysa/script-generator.c @@ -0,0 +1,627 @@ +/* + * SPDX-FileCopyrightText: 2023 fosslinux + * + * SPDX-License-Idnetifier: GPL-3.0-or-later + */ + +#define MAX_TOKEN 64 +#define MAX_STRING 2048 + +#include +#include +#include +#include + +struct Token { + char *val; + struct Token *next; +}; +typedef struct Token Token; + +#define TYPE_BUILD 1 +#define TYPE_IMPROVE 2 +#define TYPE_DEFINE 3 +#define TYPE_JUMP 4 +#define TYPE_MAINT 5 + +struct Directive { + Token *tok; + struct Directive *next; + int type; + char *arg; /* The primary argument */ +}; +typedef struct Directive Directive; + +/* Tokenizer. */ + +/* Skip over a comment. */ +char consume_comment(FILE *in) { + /* Discard the rest of the line. */ + char c = fgetc(in); + while (c != -1 && c != '\n') + c = fgetc(in); + return c; +} + +char consume_line(FILE *in, Directive *directive) { + char c = fgetc(in); + + /* Short-circuit if whole line is comment or blank line. */ + if (c == '#') { + c = consume_comment(in); + return c; + } else if (c == '\n' || c == -1) { + return c; + } + + /* Ok, we will have something to put here. */ + directive->next = calloc(1, sizeof(Directive)); + directive = directive->next; + + Token *head = calloc(1, sizeof(Token)); + Token *cur = head; + char *out; + int i = 0; + while (c != -1 && c != '\n') { + /* Initialize next token. */ + cur->next = calloc(1, sizeof(Token)); + cur = cur->next; + cur->val = calloc(MAX_TOKEN, sizeof(char)); + out = cur->val; + /* Copy line to token until a space (or EOL/EOF) or comment is found. */ + while (c != -1 && c != '\n' && c != ' ' && c != '#') { + out[0] = c; + out += 1; + c = fgetc(in); + } + /* Go to start of next token. */ + if (c == ' ') { + c = fgetc(in); + } + /* Handle comment. */ + if (c == '#') { + c = consume_comment(in); + } + } + + /* Add information to directive. */ + directive->tok = head->next; + + return c; +} + +Directive *tokenizer(FILE *in) { + Directive *head = calloc(1, sizeof(Directive)); + Directive *cur = head; + + char c; + while (c != -1) { + /* + * Note that consume_line fills cur->next, not cur. + * This avoids having an empty last Directive. + */ + c = consume_line(in, cur); + if (cur->next != NULL) { + cur = cur->next; + } + } + return head->next; +} + +/* Config variables. */ + +struct Variable { + char *name; + char *val; + struct Variable *next; +}; +typedef struct Variable Variable; + +Variable *variables; + +Variable *load_config() { + FILE *config = fopen("/steps/bootstrap.cfg", "r"); + /* File does not exist check. */ + if (config == NULL) { + return NULL; + } + + char *line = calloc(MAX_STRING, sizeof(char)); + Variable *head = calloc(1, sizeof(Variable)); + Variable *cur = head; + /* For each line... */ + char *equals; + while (fgets(line, MAX_STRING, config) != 0) { + /* Weird M2-Planet behaviour. */ + if (*line == 0) { + break; + } + cur->next = calloc(1, sizeof(Variable)); + cur = cur->next; + /* Split on the equals. First half is name, second half is value. */ + equals = strchr(line, '='); + if (equals == 0) { + fputs("bootstrap.cfg should have the format var=val on each line.", stderr); + exit(1); + } + cur->name = calloc(equals - line + 1, sizeof(char)); + strncpy(cur->name, line, equals - line); + equals += 1; + cur->val = calloc(strlen(equals), sizeof(char)); + strncpy(cur->val, equals, strlen(equals) - 1); + line = calloc(MAX_STRING, sizeof(char)); + } + variables = head->next; + fclose(config); +} + +void output_config(FILE *out) { + Variable *variable; + for (variable = variables; variable != NULL; variable = variable->next) { + fputs(variable->name, out); + fputs("=", out); + fputs(variable->val, out); + fputs("\n", out); + } +} + +char *get_var(char *name) { + /* Search through existing variables. */ + Variable *var; + Variable *last; + for (var = variables; var != NULL; var = var->next) { + if (strcmp(name, var->name) == 0) { + return var->val; + } + last = var; + } + + /* If the variable is unset, prompt the user. */ + if (variables == NULL) { + variables = calloc(1, sizeof(Variable)); + var = variables; + } else { + last->next = calloc(1, sizeof(Variable)); + var = last->next; + } + var->name = calloc(strlen(name) + 1, sizeof(char)); + strcpy(var->name, name); + var->val = calloc(MAX_STRING, sizeof(char)); + fputs("You have not set a value for ", stdout); + fputs(name, stdout); + fputs(" in bootstrap.cfg. Please set it now:\n", stdout); + while (fgets(var->val, MAX_STRING, stdin) == 0 || var->val[0] == '\n') { + fputs("Error inputting, try again:\n", stdout); + } + if (var->val[0] == 0) { + fputs("You put in an EOF!\n", stderr); + exit(1); + } + /* Trim the newline. */ + var->val[strlen(var->val)] = 0; + return var->val; +} + +/* Recursive descent interpreter. */ + +Token *fill(Token *tok, Directive *directive, int type) { + directive->type = type; + directive->arg = tok->val; + return tok->next; +} + +Token *logic(Token *tok, char **val) { + /* logic = "(" + * (name | + * (name "==" value) | + * (logic "||" logic) | + * (logic "&&" logic)) + * ")" + */ + + char *lhs = tok->val; + char *rhs; + tok = tok->next; + if (strcmp(tok->val, ")") == 0) { + /* Case where it's just a constant. */ + *val = lhs; + return tok; + } else if (strcmp(tok->val, "==") == 0) { + /* Case for equality. */ + rhs = tok->next->val; + tok = tok->next->next; + if (strcmp(get_var(lhs), rhs) == 0) { + lhs = "True"; + } else { + lhs = "False"; + } + } else { + fputs("Expected == after ", stderr); + fputs(lhs, stderr); + fputs(" in logic\n", stderr); + exit(1); + } + + if (strcmp(tok->val, ")") == 0) { + *val = lhs; + return tok; + } else if (strcmp(tok->val, "||") == 0) { + /* OR */ + tok = logic(tok->next, &rhs); + if (strcmp(lhs, "True") == 0 || strcmp(rhs, "True") == 0) { + lhs = "True"; + } else { + lhs = "False"; + } + } else if (strcmp(tok->val, "&&") == 0) { + /* AND */ + tok = logic(tok->next, &rhs); + if (strcmp(lhs, "True") == 0 && strcmp(rhs, "True") == 0) { + lhs = "True"; + } else { + lhs = "False"; + } + } else { + fputs("Expected || or && in logic\n", stderr); + exit(1); + } + + *val = lhs; + return tok; +} + +Token *primary_logic(Token *tok, char **val) { + /* Starting ( */ + if (strcmp(tok->val, "(") != 0) { + fputs("Expected logic to begin with (\n", stderr); + exit(1); + } + tok = tok->next; + + tok = logic(tok, val); + + if (strcmp(tok->val, ")") != 0) { + fputs("Expected logic to end with )\n", stderr); + exit(1); + } + + return tok; +} + +int eval_predicate(Token *tok) { + char *result; + tok = primary_logic(tok, &result); + return strcmp(result, "True") == 0; +} + +Token *define(Token *tok, Directive *directive) { + /* define = name "=" (logic | constant) */ + char *name = tok->val; + tok = tok->next; + if (strcmp(tok->val, "=") != 0) { + fputs("define of ", stderr); + fputs(name, stderr); + fputs(" has a missing equals\n", stderr); + exit(1); + } + tok = tok->next; + + char *val = calloc(MAX_STRING, sizeof(char)); + if (strcmp(tok->val, "(") == 0) { + /* It is a logic. */ + tok = primary_logic(tok, &val); + } else { + /* It is a constant. */ + strcpy(val, tok->val); + } + + /* Check for predicate. */ + tok = tok->next; + if (tok != NULL) { + if (!eval_predicate(tok)) { + /* Nothing more to do. */ + return tok; + } + } + + /* Update existing variable, or else, add to the end of variables. */ + /* Special case: empty variables. */ + if (variables == NULL) { + variables = calloc(1, sizeof(Variable)); + variables->name = name; + variables->val = val; + } + + Variable *var; + for (var = variables; var->next != NULL; var = var->next) { + if (strcmp(var->next->name, name) == 0) { + var->next->val = val; + break; + } + } + if (var->next == NULL) { + /* We did not update an existing variable. */ + var->next = calloc(1, sizeof(Variable)); + var->next->name = name; + var->next->val = val; + } + + return tok; +} + +int interpret(Directive *directive) { + /* directive = (build | improve | define | jump | maint) predicate? */ + Token *tok = directive->tok; + if (strcmp(tok->val, "build:") == 0) { + tok = fill(tok->next, directive, TYPE_BUILD); + } else if (strcmp(tok->val, "improve:") == 0) { + tok = fill(tok->next, directive, TYPE_IMPROVE); + } else if (strcmp(tok->val, "jump:") == 0) { + tok = fill(tok->next, directive, TYPE_JUMP); + } else if (strcmp(tok->val, "maint:") == 0) { + tok = fill(tok->next, directive, TYPE_MAINT); + } else if (strcmp(tok->val, "define:") == 0) { + tok = define(tok->next, directive); + return 1; /* There is no codegen for a define. */ + } + + if (tok != NULL) { + return !eval_predicate(tok); + } + return 0; +} + +Directive *interpreter(Directive *directives) { + Directive *directive; + Directive *last; + for (directive = directives; directive != NULL; directive = directive->next) { + if (interpret(directive)) { + /* This means this directive needs to be removed from the linked list. */ + if (last == NULL) { + /* First directive. */ + directives = directive->next; + } else { + last->next = directive->next; + } + } else { + last = directive; + } + } + return directives; +} + +void add_to_fiwix_filelist(char *filename) { + /* Add the filename to fiwix-file-list.txt */ + FILE *fiwix_list = fopen("/steps/lwext4-1.0.0-lb1/files/fiwix-file-list.txt", "r"); + fseek(fiwix_list, 0, SEEK_END); + long size = ftell(fiwix_list); + char *contents = calloc(size, sizeof(char)); + fseek(fiwix_list, 0, SEEK_SET); + fread(contents, 1, size, fiwix_list); + fclose(fiwix_list); + fiwix_list = fopen("/steps/lwext4-1.0.0-lb1/files/fiwix-file-list.txt", "w"); + fwrite(contents, 1, size, fiwix_list); + fputs(filename, fiwix_list); + fputc('\n', fiwix_list); + fclose(fiwix_list); +} + +/* Script generator. */ +FILE *start_script(int id, int using_bash) { + /* Create the file /steps/$id.sh */ + char *filename = calloc(MAX_STRING, sizeof(char)); + strcpy(filename, "/steps/"); + strcat(filename, int2str(id, 10, 0)); + strcat(filename, ".sh"); + add_to_fiwix_filelist(filename); + + FILE *out = fopen(filename, "w"); + if (out == NULL) { + fputs("Error opening output file ", stderr); + fputs(filename, stderr); + fputs("\n", stderr); + exit(1); + } + + if (using_bash) { + fputs("#!/bin/bash\n", out); + fputs("set -e\n", out); + fputs("cd /steps\n", out); + fputs(". ./bootstrap.cfg\n", out); + fputs(". ./env\n", out); + fputs(". ./helpers.sh\n", out); + } else { + fputs("set -ex\n", out); + fputs("cd /steps\n", out); + output_config(out); + FILE *env = fopen("/steps/env", "r"); + char *line = calloc(MAX_STRING, sizeof(char)); + while (fgets(line, MAX_STRING, env) != 0) { + /* Weird M2-Planet behaviour. */ + if (*line == 0) { + break; + } + fputs(line, out); + line = calloc(MAX_STRING, sizeof(char)); + } + fclose(env); + } + + return out; +} + +void output_call_script(FILE *out, char *type, char *name, int using_bash, int source) { + if (using_bash) { + if (source) { + fputs(". ", out); + } else { + fputs("bash ", out); + } + } else { + fputs("kaem --file ", out); + } + fputs("/steps/", out); + fputs(type, out); + fputs("/", out); + fputs(name, out); + fputs(".sh\n", out); +} + +void output_build(FILE *out, Directive *directive, int pass_no, int using_bash) { + if (using_bash) { + fputs("build ", out); + fputs(directive->arg, out); + fputs(" pass", out); + fputs(int2str(pass_no, 10, 0), out); + fputs(".sh\n", out); + } else { + fputs("pkg=", out); + fputs(directive->arg, out); + fputs("\n", out); + fputs("cd ${pkg}\n", out); + fputs("kaem --file pass", out); + fputs(int2str(pass_no, 10, 0), out); + fputs(".kaem\n", out); + fputs("cd ..\n", out); + } +} + +void generate_preseed_jump(int id) { + FILE *out = fopen("/preseed-jump.kaem", "w"); + fputs("set -ex\n", out); + fputs("PATH=/usr/bin\n", out); + fputs("bash /steps/", out); + fputs(int2str(id, 10, 0), out); + fputs(".sh\n", out); + fclose(out); +} + +void generate(Directive *directives) { + /* + * We are separating the stages given in the mainfest into a bunch of + * smaller scripts. The following conditions call for the creation of + * a new script: + * - a jump + * - build of bash + */ + + int counter = 0; + + /* Initially, we use kaem, not bash. */ + int using_bash = 0; + + FILE *out = start_script(counter, using_bash); + counter += 1; + + Directive *directive; + Directive *past; + char *filename; + int pass_no; + for (directive = directives; directive != NULL; directive = directive->next) { + if (directive->type == TYPE_BUILD) { + /* Get what pass number this is. */ + pass_no = 1; + for (past = directives; past != directive; past = past->next) { + if (strcmp(past->arg, directive->arg) == 0) { + pass_no += 1; + } + } + output_build(out, directive, pass_no, using_bash); + if (strncmp(directive->arg, "bash-", 5) == 0) { + if (!using_bash) { + /* + * We are transitioning from bash to kaem, the point at which "early + * preseed" occurs. So generate the preseed jump script at this point. + */ + generate_preseed_jump(counter); + } + using_bash = 1; + /* Create call to new script. */ + output_call_script(out, "", int2str(counter, 10, 0), using_bash, 0); + fclose(out); + out = start_script(counter, using_bash); + counter += 1; + } + } else if (directive->type == TYPE_IMPROVE) { + output_call_script(out, "improve", directive->arg, using_bash, 1); + } else if (directive->type == TYPE_JUMP) { + /* + * Create /init to call new script. + * We actually do this by creating /init.X for some number X, and then + * moving that to /init at the appropriate time. + */ + filename = calloc(MAX_STRING, sizeof(char)); + if (using_bash) { + fputs("mv /init /init.bak\n", out); + /* Move new init to /init. */ + strcpy(filename, "/init."); + strcat(filename, int2str(counter, 10, 0)); + fputs("cp ", out); + fputs(filename, out); + fputs(" /init\n", out); + fputs("chmod 755 /init\n", out); + } else { + strcpy(filename, "/kaem.run."); + strcat(filename, int2str(counter, 10, 0)); + fputs("cp ", out); + fputs(filename, out); + fputs(" /kaem.run\n", out); + fputs("cp /usr/bin/kaem /init\n", out); + fputs("chmod 755 /init\n", out); + } + + output_call_script(out, "jump", directive->arg, using_bash, 1); + fclose(out); + + /* + * This cannot go before here as builder-hex0 does not like having + * multiple files open at once! + */ + add_to_fiwix_filelist(filename); + + if (using_bash) { + out = fopen(filename, "w"); + if (out == NULL) { + fputs("Error opening /init\n", stderr); + exit(1); + } + fputs("#!/bin/bash\n", out); + } else { + out = fopen(filename, "w"); + if (out == NULL) { + fputs("Error opening /kaem.run\n", stderr); + exit(1); + } + fputs("set -ex\n", out); + } + output_call_script(out, "", int2str(counter, 10, 0), using_bash, 0); + fclose(out); + out = start_script(counter, using_bash); + counter += 1; + } else if (directive->type == TYPE_MAINT) { + output_call_script(out, "maint", directive->arg, using_bash, 1); + } + } + fclose(out); +} + +void main(int argc, char **argv) { + if (argc != 2) { + fputs("Usage: script-generator