From ccacb83a030da564c59c8e0f081fe967fba5a3eb Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Fri, 4 Oct 2013 11:10:18 +0800 Subject: [PATCH] discover/grub2: Implement save_env command Signed-off-by: Jeremy Kerr --- discover/grub2/builtins.c | 8 ++ discover/grub2/env.c | 163 +++++++++++++++++++++++++++++- test/parser/Makefile.am | 1 + test/parser/test-grub2-save-env.c | 109 ++++++++++++++++++++ 4 files changed, 276 insertions(+), 5 deletions(-) create mode 100644 test/parser/test-grub2-save-env.c 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 + +#include +#include + +#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); + } +} -- 2.39.2