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