From 9c6e5cd813d0c064e2805cdb4c6726d32b49d3e1 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Fri, 2 Mar 2018 08:40:18 -0500 Subject: [PATCH 50/55] Add btrfs subvolume support for grub2 In order to find the subvolume prefix from a given path, we parse /proc/mounts. In cases where /proc/mounts doesn't contain the filesystem, the caller can use the --mounts option to specify his own mounts file. Btrfs subvolumes are already supported by grub2 and by grub2-mkconfig. Fixes #22 --- grubby.c | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 142 insertions(+), 4 deletions(-) diff --git a/grubby.c b/grubby.c index 1fe850a3ddf..396041a1187 100644 --- a/grubby.c +++ b/grubby.c @@ -68,6 +68,8 @@ int isEfi = 0; char *saved_command_line = NULL; +const char *mounts = "/proc/mounts"; + /* comments get lumped in with indention */ struct lineElement { char *item; @@ -2115,6 +2117,129 @@ static int endswith(const char *s, char c) return s[slen] == c; } +typedef struct { + const char *start; + size_t chars; +} field; + +static int iscomma(int c) +{ + return c == ','; +} + +static int isequal(int c) +{ + return c == '='; +} + +static field findField(const field *in, typeof(isspace) *isdelim, field *out) +{ + field nxt = {}; + size_t off = 0; + + while (off < in->chars && isdelim(in->start[off])) + off++; + + if (off == in->chars) + return nxt; + + out->start = &in->start[off]; + out->chars = 0; + + while (off + out->chars < in->chars && !isdelim(out->start[out->chars])) + out->chars++; + + nxt.start = out->start + out->chars; + nxt.chars = in->chars - off - out->chars; + return nxt; +} + +static int fieldEquals(const field *in, const char *str) +{ + return in->chars == strlen(str) && + strncmp(in->start, str, in->chars) == 0; +} + +/* Parse /proc/mounts to determine the subvolume prefix. */ +static size_t subvolPrefix(const char *str) +{ + FILE *file = NULL; + char *line = NULL; + size_t prfx = 0; + size_t size = 0; + + file = fopen(mounts, "r"); + if (!file) + return 0; + + for (ssize_t s; (s = getline(&line, &size, file)) >= 0; ) { + field nxt = { line, s }; + field dev = {}; + field path = {}; + field type = {}; + field opts = {}; + field opt = {}; + + nxt = findField(&nxt, isspace, &dev); + if (!nxt.start) + continue; + + nxt = findField(&nxt, isspace, &path); + if (!nxt.start) + continue; + + nxt = findField(&nxt, isspace, &type); + if (!nxt.start) + continue; + + nxt = findField(&nxt, isspace, &opts); + if (!nxt.start) + continue; + + if (!fieldEquals(&type, "btrfs")) + continue; + + /* We have found a btrfs mount point. */ + + nxt = opts; + while ((nxt = findField(&nxt, iscomma, &opt)).start) { + field key = {}; + field val = {}; + + opt = findField(&opt, isequal, &key); + if (!opt.start) + continue; + + opt = findField(&opt, isequal, &val); + if (!opt.start) + continue; + + if (!fieldEquals(&key, "subvol")) + continue; + + /* We have found a btrfs subvolume mount point. */ + + if (strncmp(val.start, str, val.chars)) + continue; + + if (val.start[val.chars - 1] != '/' && + str[val.chars] != '/') + continue; + + /* The subvolume mount point matches our input. */ + + if (prfx < val.chars) + prfx = val.chars; + } + } + + dbgPrintf("%s(): str: '%s', prfx: '%s'\n", __FUNCTION__, str, prfx); + + fclose(file); + free(line); + return prfx; +} + int suitableImage(struct singleEntry *entry, const char *bootPrefix, int skipRemoved, int flags) { @@ -3262,12 +3387,22 @@ struct singleLine *addLineTmpl(struct singleEntry *entry, type & (LT_HYPER | LT_KERNEL | LT_MBMODULE | LT_INITRD | LT_KERNEL_EFI | LT_INITRD_EFI | LT_KERNEL_16 | LT_INITRD_16)) { - size_t rs = getRootSpecifier(tmplLine->elements[1].item); + const char *prfx = tmplLine->elements[1].item; + size_t rs = getRootSpecifier(prfx); + if (isinitrd(tmplLine->type)) { + for (struct singleLine *l = entry->lines; + rs == 0 && l; l = l->next) { + if (iskernel(l->type)) { + prfx = l->elements[1].item; + rs = getRootSpecifier(prfx); + break; + } + } + } if (rs > 0) { free(newLine->elements[1].item); newLine->elements[1].item = sdupprintf( - "%.*s%s", (int) rs, - tmplLine->elements[1].item, val); + "%.*s%s", (int) rs, prfx, val); } } } @@ -4331,7 +4466,7 @@ static size_t getRootSpecifier(const char *str) rs++; } - return rs; + return rs + subvolPrefix(str + rs); } static char *getInitrdVal(struct grubConfig *config, @@ -4963,6 +5098,9 @@ int main(int argc, const char **argv) {"mbargs", 0, POPT_ARG_STRING, &newMBKernelArgs, 0, _("default arguments for the new multiboot kernel or " "new arguments for multiboot kernel being updated"), NULL}, + {"mounts", 0, POPT_ARG_STRING, &mounts, 0, + _("path to fake /proc/mounts file (for testing only)"), + _("mounts")}, {"bad-image-okay", 0, 0, &badImageOkay, 0, _ ("don't sanity check images in boot entries (for testing only)"), -- 2.17.1