discover/grub2: Implement save_env command
authorJeremy Kerr <jk@ozlabs.org>
Fri, 4 Oct 2013 03:10:18 +0000 (11:10 +0800)
committerJeremy Kerr <jk@ozlabs.org>
Fri, 4 Oct 2013 04:21:02 +0000 (12:21 +0800)
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
discover/grub2/builtins.c
discover/grub2/env.c
test/parser/Makefile.am
test/parser/test-grub2-save-env.c [new file with mode: 0644]

index 71ae61a3ae97118ed63e7f30303e2c4d4c09dd7c..c218bc7d07155bd6426d630f26bf623f0bcb4bb6 100644 (file)
@@ -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[] = {
index e28c9feffd4ebcdf42c46f8fdb63915e3f65e812..90e9c69667abe47db82ee324c3f52052bba99648 100644 (file)
@@ -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;
 }
 
index 165b9ae3c37e30420ddeca718af127ac4be32882..65dd7fcf491d13688e174229686f1efc67c073ec 100644 (file)
@@ -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 (file)
index 0000000..7a2938f
--- /dev/null
@@ -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);
+       }
+}