summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--discover/grub2/builtins.c8
-rw-r--r--discover/grub2/env.c163
-rw-r--r--test/parser/Makefile.am1
-rw-r--r--test/parser/test-grub2-save-env.c109
4 files changed, 276 insertions, 5 deletions
diff --git a/discover/grub2/builtins.c b/discover/grub2/builtins.c
index 71ae61a..c218bc7 100644
--- a/discover/grub2/builtins.c
+++ b/discover/grub2/builtins.c
@@ -259,6 +259,10 @@ static int builtin_nop(struct grub2_script *script __attribute__((unused)),
extern int builtin_load_env(struct grub2_script *script,
void *data __attribute__((unused)),
int argc, char *argv[]);
+int builtin_save_env(struct grub2_script *script,
+ void *data __attribute__((unused)),
+ int argc, char *argv[]);
+
static struct {
const char *name;
@@ -304,6 +308,10 @@ static struct {
.name = "load_env",
.fn = builtin_load_env,
},
+ {
+ .name = "save_env",
+ .fn = builtin_save_env,
+ },
};
static const char *nops[] = {
diff --git a/discover/grub2/env.c b/discover/grub2/env.c
index e28c9fe..90e9c69 100644
--- a/discover/grub2/env.c
+++ b/discover/grub2/env.c
@@ -92,16 +92,169 @@ int builtin_load_env(struct grub2_script *script,
return 0;
}
+static int update_env(char *buf, int buflen, const char *name,
+ const char *value)
+{
+ /* head and tail define the pointers within the existing data that we
+ * can insert our env entry, end is the last byte of valid environment
+ * data. if tail - head != entrylen, when we'll memmove to create (or
+ * remove) space */
+ int i, j, linelen, namelen, valuelen, entrylen, delta, space;
+ char *head, *tail, *end;
+ bool replace;
+
+ namelen = strlen(name);
+ valuelen = strlen(value);
+ entrylen = namelen + strlen("=") + valuelen + strlen("\n");
+
+ head = tail = end = NULL;
+ replace = false;
+
+ /* For each line (where linelen includes the trailing \n). Find
+ * head, tail and end.
+ */
+ for (i = 0; i < buflen; i += linelen) {
+ char *eol = NULL, *sep = NULL, *line = buf + i;
+
+ /* find eol and sep */
+ for (j = 0; !eol && i + j < buflen; j++) {
+ switch (line[j]) {
+ case '\n':
+ eol = line + j;
+ break;
+ case '=':
+ if (!sep)
+ sep = line + j;
+ break;
+ }
+ }
+
+ /* no eol, put the new value at the start of this line,
+ * no need to shift data. This will also match the
+ * padding '#' chars at the end of the entries. */
+ if (!eol) {
+ if (!head) {
+ head = line;
+ tail = head + entrylen;
+ }
+ end = line;
+ break;
+ }
+
+ linelen = (eol - line) + 1;
+ end = line + linelen;
+
+ /* invalid line? may as well overwrite it with valid data */
+ if (!sep && !replace) {
+ head = line;
+ tail = line + linelen;
+ }
+
+ /* an existing entry for this var? We set replace, as we prefer
+ * to overwrite an existing entry (the first one, in
+ * particular) rather than use an invalid line.
+ */
+ if (sep && !replace && sep - line == namelen &&
+ !strncmp(name, line, namelen)) {
+ head = line;
+ tail = line + linelen;
+ replace = true;
+ }
+ }
+
+ if (!head || !tail || !end) {
+ pb_log("grub save_env: can't parse buffer space\n");
+ return -1;
+ }
+
+ /* how much extra space do we need? */
+ delta = entrylen - (tail - head);
+ /* how much space do we have? */
+ space = (buf + buflen) - end;
+
+ /* check we have enough space. the tail > buf-end check is required
+ * for the case where there was no eol, and we set
+ * tail = head + entrylen
+ */
+ if (delta > space || tail > buf + buflen) {
+ pb_log("grub save_env: not enough buffer space\n");
+ return -1;
+ }
+
+ /* create space between head & tail */
+ if (delta) {
+ /* for positive delta, we need to reduce the copied data size */
+ int shiftlen = delta > 0 ? delta : 0;
+ memmove(tail + delta, tail, (buf + buflen) - tail - shiftlen);
+ }
+
+ /* if we've shifted data down towards head, we'll need to append
+ * padding */
+ if (delta < 0)
+ memset(buf + buflen + delta, '#', 0 - delta);
+
+ /* set the entry data */
+ memcpy(head, name, namelen);
+ memcpy(head + namelen, "=", 1);
+ memcpy(head + namelen + 1, value, valuelen);
+ memcpy(head + namelen + 1 + valuelen, "\n", 1);
+
+ return 0;
+}
+
int builtin_save_env(struct grub2_script *script,
void *data __attribute__((unused)),
int argc, char *argv[]);
-int builtin_save_env(struct grub2_script *script __attribute__((unused)),
+int builtin_save_env(struct grub2_script *script,
void *data __attribute__((unused)),
- int argc __attribute__((unused)),
- char *argv[] __attribute__((unused)))
+ int argc, char *argv[])
{
- /* todo: save */
- return 0;
+ struct discover_device *dev = script->ctx->device;
+ int i, rc, len, siglen;
+ char *buf, *envpath;
+ const char *envfile;
+
+ /* we only support local filesystems */
+ if (!dev->mounted) {
+ pb_log("save_env: can't save to a non-mounted device (%s)\n",
+ dev->device->id);
+ return -1;
+ }
+
+ if (argc == 3 && !strcmp(argv[1], "-f"))
+ envfile = argv[2];
+ else
+ envfile = default_envfile;
+
+ envpath = talloc_asprintf(script, "%s/%s",
+ script_env_get(script, "prefix") ? : "",
+ envfile);
+ buf = NULL;
+
+ rc = parser_request_file(script->ctx, dev, envpath, &buf, &len);
+
+ siglen = strlen(signature);
+
+ /* we require the environment to be pre-allocated, so abort if
+ * the file isn't present and valid */
+ if (rc || len < siglen || memcmp(buf, signature, siglen))
+ goto err;
+
+ for (i = 1; i < argc; i++) {
+ const char *name, *value;
+
+ name = argv[i];
+ value = script_env_get(script, name);
+
+ update_env(buf + siglen, len - siglen, name, value);
+ }
+
+ rc = parser_replace_file(script->ctx, dev, envpath, buf, len);
+
+err:
+ talloc_free(buf);
+ talloc_free(envpath);
+ return rc;
}
diff --git a/test/parser/Makefile.am b/test/parser/Makefile.am
index 165b9ae..65dd7fc 100644
--- a/test/parser/Makefile.am
+++ b/test/parser/Makefile.am
@@ -34,6 +34,7 @@ TESTS = \
test-grub2-multiple-resolve \
test-grub2-single-line-if \
test-grub2-load-env \
+ test-grub2-save-env \
test-grub2-f18-ppc64 \
test-grub2-ubuntu-13_04-x86 \
test-grub2-lexer-error \
diff --git a/test/parser/test-grub2-save-env.c b/test/parser/test-grub2-save-env.c
new file mode 100644
index 0000000..7a2938f
--- /dev/null
+++ b/test/parser/test-grub2-save-env.c
@@ -0,0 +1,109 @@
+
+#include <string.h>
+
+#include <array-size/array-size.h>
+#include <talloc/talloc.h>
+
+#include "parser-test.h"
+
+static const char *envsig = "# GRUB Environment Block\n";
+
+struct env_test {
+ const char *name;
+ const char *env_before;
+ const char *script;
+ const char *env_after;
+} tests[] = {
+ {
+ "init",
+ "######",
+ "a=xxx\nsave_env a\n",
+ "a=xxx\n"
+ },
+ {
+ "append",
+ "q=q\nr=r\n######",
+ "a=xxx\nsave_env a\n",
+ "q=q\nr=r\na=xxx\n"
+ },
+ {
+ "expand",
+ "q=q\na=x\nr=r\n##",
+ "a=xxx\nsave_env a\n",
+ "q=q\na=xxx\nr=r\n",
+ },
+ {
+ "reduce",
+ "q=q\na=xxx\nr=r\n",
+ "a=x\nsave_env a\n",
+ "q=q\na=x\nr=r\n##",
+ },
+ {
+ "invalid-insert",
+ "q=q\n---\nr=r\n",
+ "a=x\nsave_env a\n",
+ "q=q\na=x\nr=r\n",
+ },
+ {
+ "invalid-shift",
+ "q=q\n--\nr=r\n#",
+ "a=x\nsave_env a\n",
+ "q=q\na=x\nr=r\n",
+ },
+ {
+ "invalid-reduce",
+ "q=q\n----\nr=r\n",
+ "a=x\nsave_env a\n",
+ "q=q\na=x\nr=r\n#",
+ },
+ {
+ "dup-replace-first",
+ "q=q\na=y\nr=r\na=z",
+ "a=x\nsave_env a\n",
+ "q=q\na=x\nr=r\na=z",
+ },
+ {
+ "nospace-add",
+ "q=q\nr=r\n###",
+ "a=x\nsave_env a\n",
+ "q=q\nr=r\n###",
+ },
+ {
+ "nospace-replace",
+ "q=q\na=x\nr=r\n#",
+ "a=xxx\nsave_env a\n",
+ "q=q\na=x\nr=r\n#",
+ },
+};
+
+static void run_env_test(struct parser_test *test, struct env_test *envtest)
+{
+ const char *env_before, *env_after;
+
+ env_before = talloc_asprintf(test, "%s%s", envsig, envtest->env_before);
+ env_after = talloc_asprintf(test, "%s%s", envsig, envtest->env_after);
+
+ test_add_file_data(test, test->ctx->device, "/boot/grub/grubenv",
+ env_before, strlen(env_before));
+
+ __test_read_conf_data(test, envtest->script, strlen(envtest->script));
+ test_run_parser(test, "grub2");
+
+ check_file_contents(test, test->ctx->device, "/boot/grub/grubenv",
+ env_after, strlen(env_after));
+}
+
+void run_test(struct parser_test *test)
+{
+ struct env_test *env_test;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ env_test = &tests[i];
+ printf("test %s: ", env_test->name);
+ fflush(stdout);
+ run_env_test(test, env_test);
+ printf("OK\n");
+ fflush(stdout);
+ }
+}
OpenPOWER on IntegriCloud