logo

live-bootstrap

Mirror of <https://github.com/fosslinux/live-bootstrap>

script-generator.c (14803B)


  1. /*
  2. * SPDX-FileCopyrightText: 2023 fosslinux <fosslinux@aussies.space>
  3. *
  4. * SPDX-License-Identifier: GPL-3.0-or-later
  5. */
  6. #define MAX_TOKEN 64
  7. #define MAX_STRING 2048
  8. #include <bootstrappable.h>
  9. #include <stdio.h>
  10. #include <stdlib.h>
  11. #include <string.h>
  12. struct Token {
  13. char *val;
  14. struct Token *next;
  15. };
  16. typedef struct Token Token;
  17. #define TYPE_BUILD 1
  18. #define TYPE_IMPROVE 2
  19. #define TYPE_DEFINE 3
  20. #define TYPE_JUMP 4
  21. #define TYPE_UNINSTALL 5
  22. struct Directive {
  23. Token *tok;
  24. struct Directive *next;
  25. int type;
  26. char *arg; /* The primary argument */
  27. };
  28. typedef struct Directive Directive;
  29. /* Tokenizer. */
  30. /* Skip over a comment. */
  31. char consume_comment(FILE *in) {
  32. /* Discard the rest of the line. */
  33. char c = fgetc(in);
  34. while (c != -1 && c != '\n')
  35. c = fgetc(in);
  36. return c;
  37. }
  38. char consume_line(FILE *in, Directive *directive) {
  39. char c = fgetc(in);
  40. /* Short-circuit if whole line is comment or blank line. */
  41. if (c == '#') {
  42. c = consume_comment(in);
  43. return c;
  44. } else if (c == '\n' || c == -1) {
  45. return c;
  46. }
  47. /* Ok, we will have something to put here. */
  48. directive->next = calloc(1, sizeof(Directive));
  49. directive = directive->next;
  50. Token *head = calloc(1, sizeof(Token));
  51. Token *cur = head;
  52. char *out;
  53. int i = 0;
  54. while (c != -1 && c != '\n') {
  55. /* Initialize next token. */
  56. cur->next = calloc(1, sizeof(Token));
  57. cur = cur->next;
  58. cur->val = calloc(MAX_TOKEN, sizeof(char));
  59. out = cur->val;
  60. /* Copy line to token until a space (or EOL/EOF) or comment is found. */
  61. while (c != -1 && c != '\n' && c != ' ' && c != '#') {
  62. out[0] = c;
  63. out += 1;
  64. c = fgetc(in);
  65. }
  66. /* Go to start of next token. */
  67. if (c == ' ') {
  68. c = fgetc(in);
  69. }
  70. /* Handle comment. */
  71. if (c == '#') {
  72. c = consume_comment(in);
  73. }
  74. }
  75. /* Add information to directive. */
  76. directive->tok = head->next;
  77. return c;
  78. }
  79. Directive *tokenizer(FILE *in) {
  80. Directive *head = calloc(1, sizeof(Directive));
  81. Directive *cur = head;
  82. char c;
  83. while (c != -1) {
  84. /*
  85. * Note that consume_line fills cur->next, not cur.
  86. * This avoids having an empty last Directive.
  87. */
  88. c = consume_line(in, cur);
  89. if (cur->next != NULL) {
  90. cur = cur->next;
  91. }
  92. }
  93. return head->next;
  94. }
  95. /* Config variables. */
  96. struct Variable {
  97. char *name;
  98. char *val;
  99. struct Variable *next;
  100. };
  101. typedef struct Variable Variable;
  102. Variable *variables;
  103. Variable *load_config() {
  104. FILE *config = fopen("/steps/bootstrap.cfg", "r");
  105. /* File does not exist check. */
  106. if (config == NULL) {
  107. return NULL;
  108. }
  109. char *line = calloc(MAX_STRING, sizeof(char));
  110. Variable *head = calloc(1, sizeof(Variable));
  111. Variable *cur = head;
  112. /* For each line... */
  113. char *equals;
  114. while (fgets(line, MAX_STRING, config) != 0) {
  115. /* Weird M2-Planet behaviour. */
  116. if (*line == 0) {
  117. break;
  118. }
  119. cur->next = calloc(1, sizeof(Variable));
  120. cur = cur->next;
  121. /* Split on the equals. First half is name, second half is value. */
  122. equals = strchr(line, '=');
  123. if (equals == 0) {
  124. fputs("bootstrap.cfg should have the format var=val on each line.", stderr);
  125. exit(1);
  126. }
  127. cur->name = calloc(equals - line + 1, sizeof(char));
  128. strncpy(cur->name, line, equals - line);
  129. equals += 1;
  130. cur->val = calloc(strlen(equals), sizeof(char));
  131. strncpy(cur->val, equals, strlen(equals) - 1);
  132. line = calloc(MAX_STRING, sizeof(char));
  133. }
  134. variables = head->next;
  135. fclose(config);
  136. }
  137. void output_config(FILE *out) {
  138. Variable *variable;
  139. for (variable = variables; variable != NULL; variable = variable->next) {
  140. fputs(variable->name, out);
  141. fputs("=", out);
  142. fputs(variable->val, out);
  143. fputs("\n", out);
  144. }
  145. }
  146. char *get_var(char *name) {
  147. /* Search through existing variables. */
  148. Variable *var;
  149. Variable *last = NULL;
  150. for (var = variables; var != NULL; var = var->next) {
  151. if (strcmp(name, var->name) == 0) {
  152. return var->val;
  153. }
  154. last = var;
  155. }
  156. /* If the variable is unset, take it to be the empty string. */
  157. return "";
  158. }
  159. /* Recursive descent interpreter. */
  160. Token *fill(Token *tok, Directive *directive, int type) {
  161. directive->type = type;
  162. directive->arg = tok->val;
  163. return tok->next;
  164. }
  165. Token *logic(Token *tok, char **val) {
  166. /* logic = "("
  167. * (name |
  168. * (name "==" value) |
  169. * (name "!=" value) |
  170. * (logic "||" logic) |
  171. * (logic "&&" logic))
  172. * ")"
  173. */
  174. char *lhs = tok->val;
  175. char *rhs;
  176. tok = tok->next;
  177. if (strcmp(tok->val, ")") == 0) {
  178. /* Case where it's just a constant. */
  179. *val = lhs;
  180. return tok;
  181. } else if (strcmp(tok->val, "==") == 0) {
  182. /* Case for equality. */
  183. rhs = tok->next->val;
  184. tok = tok->next->next;
  185. if (strcmp(get_var(lhs), rhs) == 0) {
  186. lhs = "True";
  187. } else {
  188. lhs = "False";
  189. }
  190. } else if (strcmp(tok->val, "!=") == 0) {
  191. /* Case for inequality. */
  192. rhs = tok->next->val;
  193. tok = tok->next->next;
  194. if (strcmp(get_var(lhs), rhs) == 0) {
  195. lhs = "False";
  196. } else {
  197. lhs = "True";
  198. }
  199. } else {
  200. fputs("Expected == or != after ", stderr);
  201. fputs(lhs, stderr);
  202. fputs(" in logic\n", stderr);
  203. exit(1);
  204. }
  205. if (strcmp(tok->val, ")") == 0) {
  206. *val = lhs;
  207. return tok;
  208. } else if (strcmp(tok->val, "||") == 0) {
  209. /* OR */
  210. tok = logic(tok->next, &rhs);
  211. if (strcmp(lhs, "True") == 0 || strcmp(rhs, "True") == 0) {
  212. lhs = "True";
  213. } else {
  214. lhs = "False";
  215. }
  216. } else if (strcmp(tok->val, "&&") == 0) {
  217. /* AND */
  218. tok = logic(tok->next, &rhs);
  219. if (strcmp(lhs, "True") == 0 && strcmp(rhs, "True") == 0) {
  220. lhs = "True";
  221. } else {
  222. lhs = "False";
  223. }
  224. } else {
  225. fputs("Expected || or && in logic\n", stderr);
  226. exit(1);
  227. }
  228. *val = lhs;
  229. return tok;
  230. }
  231. Token *primary_logic(Token *tok, char **val) {
  232. /* Starting ( */
  233. if (strcmp(tok->val, "(") != 0) {
  234. fputs("Expected logic to begin with (\n", stderr);
  235. exit(1);
  236. }
  237. tok = tok->next;
  238. tok = logic(tok, val);
  239. if (strcmp(tok->val, ")") != 0) {
  240. fputs("Expected logic to end with )\n", stderr);
  241. exit(1);
  242. }
  243. return tok;
  244. }
  245. int eval_predicate(Token *tok) {
  246. char *result;
  247. tok = primary_logic(tok, &result);
  248. return strcmp(result, "True") == 0;
  249. }
  250. Token *define(Token *tok, Directive *directive) {
  251. /* define = name "=" (logic | constant) */
  252. char *name = tok->val;
  253. tok = tok->next;
  254. if (strcmp(tok->val, "=") != 0) {
  255. fputs("define of ", stderr);
  256. fputs(name, stderr);
  257. fputs(" has a missing equals\n", stderr);
  258. exit(1);
  259. }
  260. tok = tok->next;
  261. char *val = calloc(MAX_STRING, sizeof(char));
  262. if (strcmp(tok->val, "(") == 0) {
  263. /* It is a logic. */
  264. tok = primary_logic(tok, &val);
  265. } else {
  266. /* It is a constant. */
  267. strcpy(val, tok->val);
  268. }
  269. /* Check for predicate. */
  270. tok = tok->next;
  271. if (tok != NULL) {
  272. if (!eval_predicate(tok)) {
  273. /* Nothing more to do. */
  274. return tok;
  275. }
  276. }
  277. /* Update existing variable, or else, add to the end of variables. */
  278. /* Special case: empty variables. */
  279. if (variables == NULL) {
  280. variables = calloc(1, sizeof(Variable));
  281. variables->name = name;
  282. variables->val = val;
  283. }
  284. Variable *var;
  285. for (var = variables; var->next != NULL; var = var->next) {
  286. if (strcmp(var->next->name, name) == 0) {
  287. var->next->val = val;
  288. break;
  289. }
  290. }
  291. if (var->next == NULL) {
  292. /* We did not update an existing variable. */
  293. var->next = calloc(1, sizeof(Variable));
  294. var->next->name = name;
  295. var->next->val = val;
  296. }
  297. return tok;
  298. }
  299. int interpret(Directive *directive) {
  300. /* directive = (build | improve | define | jump | uninstall) predicate? */
  301. Token *tok = directive->tok;
  302. if (strcmp(tok->val, "build:") == 0) {
  303. tok = fill(tok->next, directive, TYPE_BUILD);
  304. } else if (strcmp(tok->val, "improve:") == 0) {
  305. tok = fill(tok->next, directive, TYPE_IMPROVE);
  306. } else if (strcmp(tok->val, "jump:") == 0) {
  307. tok = fill(tok->next, directive, TYPE_JUMP);
  308. } else if (strcmp(tok->val, "define:") == 0) {
  309. tok = define(tok->next, directive);
  310. return 1; /* There is no codegen for a define. */
  311. } else if (strcmp(tok->val, "uninstall:") == 0) {
  312. tok = fill(tok->next, directive, TYPE_UNINSTALL);
  313. while (tok != NULL) {
  314. if (strcmp(tok->val, "(") == 0) {
  315. break;
  316. }
  317. if (strlen(directive->arg) + strlen(tok->val) + 1 > MAX_STRING) {
  318. fputs("somehow you have managed to have too many uninstall arguments.\n", stderr);
  319. exit(1);
  320. }
  321. directive->arg = strcat(directive->arg, " ");
  322. directive->arg = strcat(directive->arg, tok->val);
  323. tok = tok->next;
  324. }
  325. }
  326. if (tok != NULL) {
  327. return !eval_predicate(tok);
  328. }
  329. return 0;
  330. }
  331. Directive *interpreter(Directive *directives) {
  332. Directive *directive;
  333. Directive *last = NULL;
  334. for (directive = directives; directive != NULL; directive = directive->next) {
  335. if (interpret(directive)) {
  336. /* This means this directive needs to be removed from the linked list. */
  337. if (last == NULL) {
  338. /* First directive. */
  339. directives = directive->next;
  340. } else {
  341. last->next = directive->next;
  342. }
  343. } else {
  344. last = directive;
  345. }
  346. }
  347. return directives;
  348. }
  349. /* Script generator. */
  350. FILE *start_script(int id, int bash_build) {
  351. /* Create the file /steps/$id.sh */
  352. char *filename = calloc(MAX_STRING, sizeof(char));
  353. strcpy(filename, "/steps/");
  354. strcat(filename, int2str(id, 10, 0));
  355. strcat(filename, ".sh");
  356. FILE *out = fopen(filename, "w");
  357. if (out == NULL) {
  358. fputs("Error opening output file ", stderr);
  359. fputs(filename, stderr);
  360. fputs("\n", stderr);
  361. exit(1);
  362. }
  363. if (bash_build) {
  364. fputs("#!/bin/bash\n", out);
  365. if (strcmp(get_var("INTERACTIVE"), "True") == 0) {
  366. if (bash_build != 1) {
  367. fputs("set -E\ntrap 'env PS1=\"[TRAP] \\w # \" bash -i' ERR\n", out);
  368. } else {
  369. /* FIXME early bash has buggy ERR trap handling */
  370. fputs("set -e\ntrap 'bash -c '\"'\"'while true; do printf \""
  371. "[TRAP - use Ctrl+D] $(pwd) # \"; eval \"$(cat)\"; done'\"'\"'' EXIT\n",
  372. out);
  373. }
  374. } else {
  375. fputs("set -e\n", out);
  376. }
  377. fputs("cd /steps\n", out);
  378. fputs(". ./bootstrap.cfg\n", out);
  379. fputs(". ./env\n", out);
  380. fputs(". ./helpers.sh\n", out);
  381. } else {
  382. fputs("set -ex\n", out);
  383. fputs("cd /steps\n", out);
  384. output_config(out);
  385. FILE *env = fopen("/steps/env", "r");
  386. char *line = calloc(MAX_STRING, sizeof(char));
  387. while (fgets(line, MAX_STRING, env) != 0) {
  388. /* Weird M2-Planet behaviour. */
  389. if (*line == 0) {
  390. break;
  391. }
  392. fputs(line, out);
  393. line = calloc(MAX_STRING, sizeof(char));
  394. }
  395. fclose(env);
  396. }
  397. return out;
  398. }
  399. void output_call_script(FILE *out, char *type, char *name, int bash_build, int source) {
  400. if (bash_build) {
  401. if (source) {
  402. fputs(". ", out);
  403. } else {
  404. fputs("bash ", out);
  405. }
  406. } else {
  407. fputs("kaem --file ", out);
  408. }
  409. fputs("/steps/", out);
  410. if (strlen(type) != 0) {
  411. fputs(type, out);
  412. fputs("/", out);
  413. }
  414. fputs(name, out);
  415. fputs(".sh\n", out);
  416. }
  417. void output_build(FILE *out, Directive *directive, int pass_no, int bash_build) {
  418. if (bash_build) {
  419. fputs("build ", out);
  420. fputs(directive->arg, out);
  421. fputs(" pass", out);
  422. fputs(int2str(pass_no, 10, 0), out);
  423. fputs(".sh\n", out);
  424. } else {
  425. fputs("pkg=", out);
  426. fputs(directive->arg, out);
  427. fputs("\n", out);
  428. fputs("cd ${pkg}\n", out);
  429. fputs("kaem --file pass", out);
  430. fputs(int2str(pass_no, 10, 0), out);
  431. fputs(".kaem\n", out);
  432. fputs("cd ..\n", out);
  433. }
  434. }
  435. void generate_preseed_jump(int id) {
  436. FILE *out = fopen("/preseed-jump.kaem", "w");
  437. fputs("set -ex\n", out);
  438. fputs("PATH=/usr/bin\n", out);
  439. fputs("bash /steps/", out);
  440. fputs(int2str(id, 10, 0), out);
  441. fputs(".sh\n", out);
  442. fclose(out);
  443. }
  444. void generate(Directive *directives) {
  445. /*
  446. * We are separating the stages given in the mainfest into a bunch of
  447. * smaller scripts. The following conditions call for the creation of
  448. * a new script:
  449. * - a jump
  450. * - build of bash
  451. */
  452. int counter = 0;
  453. /* Initially, we use kaem, not bash. */
  454. int bash_build = 0;
  455. FILE *out = start_script(counter, bash_build);
  456. counter += 1;
  457. Directive *directive;
  458. Directive *past;
  459. char *filename;
  460. int pass_no;
  461. for (directive = directives; directive != NULL; directive = directive->next) {
  462. if (directive->type == TYPE_BUILD) {
  463. /* Get what pass number this is. */
  464. pass_no = 1;
  465. for (past = directives; past != directive; past = past->next) {
  466. if (strcmp(past->arg, directive->arg) == 0) {
  467. pass_no += 1;
  468. }
  469. }
  470. output_build(out, directive, pass_no, bash_build);
  471. if (strncmp(directive->arg, "bash-", 5) == 0) {
  472. if (!bash_build) {
  473. /*
  474. * We are transitioning from bash to kaem, the point at which "early
  475. * preseed" occurs. So generate the preseed jump script at this point.
  476. */
  477. generate_preseed_jump(counter);
  478. }
  479. bash_build += 1;
  480. /* Create call to new script. */
  481. output_call_script(out, "", int2str(counter, 10, 0), bash_build, 0);
  482. fclose(out);
  483. out = start_script(counter, bash_build);
  484. counter += 1;
  485. }
  486. } else if (directive->type == TYPE_IMPROVE) {
  487. output_call_script(out, "improve", directive->arg, bash_build, 1);
  488. } else if (directive->type == TYPE_JUMP) {
  489. /*
  490. * Create /init to call new script.
  491. * We actually do this by creating /init.X for some number X, and then
  492. * moving that to /init at the appropriate time.
  493. */
  494. filename = calloc(MAX_STRING, sizeof(char));
  495. if (bash_build) {
  496. fputs("mv /init /init.bak\n", out);
  497. /* Move new init to /init. */
  498. strcpy(filename, "/init.");
  499. strcat(filename, int2str(counter, 10, 0));
  500. fputs("cp ", out);
  501. fputs(filename, out);
  502. fputs(" /init\n", out);
  503. fputs("chmod 755 /init\n", out);
  504. } else {
  505. strcpy(filename, "/kaem.run.");
  506. strcat(filename, int2str(counter, 10, 0));
  507. fputs("cp ", out);
  508. fputs(filename, out);
  509. fputs(" /kaem.run\n", out);
  510. fputs("cp /usr/bin/kaem /init\n", out);
  511. fputs("chmod 755 /init\n", out);
  512. }
  513. output_call_script(out, "jump", directive->arg, bash_build, 1);
  514. fclose(out);
  515. if (bash_build) {
  516. out = fopen(filename, "w");
  517. if (out == NULL) {
  518. fputs("Error opening /init\n", stderr);
  519. exit(1);
  520. }
  521. fputs("#!/bin/bash\n", out);
  522. } else {
  523. out = fopen(filename, "w");
  524. if (out == NULL) {
  525. fputs("Error opening /kaem.run\n", stderr);
  526. exit(1);
  527. }
  528. fputs("set -ex\n", out);
  529. }
  530. output_call_script(out, "", int2str(counter, 10, 0), bash_build, 0);
  531. fclose(out);
  532. out = start_script(counter, bash_build);
  533. counter += 1;
  534. } else if (directive->type == TYPE_UNINSTALL) {
  535. fputs("uninstall ", out);
  536. fputs(directive->arg, out);
  537. fputs("\n", out);
  538. }
  539. }
  540. fclose(out);
  541. }
  542. void main(int argc, char **argv) {
  543. if (argc != 2) {
  544. fputs("Usage: script-generator <script>\n", stderr);
  545. exit(1);
  546. }
  547. FILE *in = fopen(argv[1], "r");
  548. if (in == NULL) {
  549. fputs("Error opening input file\n", stderr);
  550. exit(1);
  551. }
  552. Directive *directives = tokenizer(in);
  553. fclose(in);
  554. load_config();
  555. directives = interpreter(directives);
  556. generate(directives);
  557. FILE *config = fopen("/steps/bootstrap.cfg", "w");
  558. output_config(config);
  559. fclose(config);
  560. }