From c7abf1c87c44c54c16758e079dc3528a13d8e177 Mon Sep 17 00:00:00 2001
From: Karel Zak <kzak@redhat.com>
Date: Wed, 29 Nov 2023 12:19:55 +0100
Subject: libmount: add private mnt_optstr_get_missing()

The function compares two options strings and returns options which
are missing.

Addresses: https://issues.redhat.com/browse/RHEL-14612
Upstream: http://github.com/util-linux/util-linux/commit/722e4d47a9a21f09a6a719a0f510f29f12497ce5
Signed-off-by: Karel Zak <kzak@redhat.com>
---
 libmount/src/mountP.h |   1 +
 libmount/src/optstr.c | 104 ++++++++++++++++++++++++++++++++++++------
 2 files changed, 92 insertions(+), 13 deletions(-)

diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h
index 22442ec55..30fac593a 100644
--- a/libmount/src/mountP.h
+++ b/libmount/src/mountP.h
@@ -401,6 +401,7 @@ extern const struct libmnt_optmap *mnt_optmap_get_entry(
 /* optstr.c */
 extern int mnt_optstr_get_uid(const char *optstr, const char *name, uid_t *uid);
 extern int mnt_optstr_remove_option_at(char **optstr, char *begin, char *end);
+extern int mnt_optstr_get_missing(const char *optstr, const char *wanted, char **missing);
 extern int mnt_optstr_fix_gid(char **optstr, char *value, size_t valsz, char **next);
 extern int mnt_optstr_fix_uid(char **optstr, char *value, size_t valsz, char **next);
 extern int mnt_optstr_fix_secontext(char **optstr, char *value, size_t valsz, char **next);
diff --git a/libmount/src/optstr.c b/libmount/src/optstr.c
index 16800f571..a274e39b3 100644
--- a/libmount/src/optstr.c
+++ b/libmount/src/optstr.c
@@ -121,13 +121,16 @@ error:
  * Locates the first option that matches @name. The @end is set to the
  * char behind the option (it means ',' or \0).
  *
+ * @ol is optional.
+ *
  * Returns negative number on parse error, 1 when not found and 0 on success.
  */
-static int mnt_optstr_locate_option(char *optstr, const char *name,
+static int mnt_optstr_locate_option(char *optstr,
+					const char *name, size_t namesz,
 					struct libmnt_optloc *ol)
 {
 	char *n;
-	size_t namesz, nsz;
+	size_t nsz;
 	int rc;
 
 	if (!optstr)
@@ -135,25 +138,30 @@ static int mnt_optstr_locate_option(char *optstr, const char *name,
 
 	assert(name);
 
-	namesz = strlen(name);
+	if (!namesz)
+		namesz = strlen(name);
+	if (!namesz)
+		return 1;
 
 	do {
 		rc = mnt_optstr_parse_next(&optstr, &n, &nsz,
-					&ol->value, &ol->valsz);
+					ol ? &ol->value : NULL,
+					ol ? &ol->valsz : NULL);
 		if (rc)
 			break;
 
 		if (namesz == nsz && strncmp(n, name, nsz) == 0) {
-			ol->begin = n;
-			ol->end = *(optstr - 1) == ',' ? optstr - 1 : optstr;
-			ol->namesz = nsz;
+			if (ol) {
+				ol->begin = n;
+				ol->end = *(optstr - 1) == ',' ? optstr - 1 : optstr;
+				ol->namesz = nsz;
+			}
 			return 0;
 		}
 	} while(1);
 
 	return rc;
 }
-
 /**
  * mnt_optstr_next_option:
  * @optstr: option string, returns the position of the next option
@@ -284,7 +292,7 @@ int mnt_optstr_get_option(const char *optstr, const char *name,
 	if (!optstr || !name)
 		return -EINVAL;
 
-	rc = mnt_optstr_locate_option((char *) optstr, name, &ol);
+	rc = mnt_optstr_locate_option((char *) optstr, name, 0, &ol);
 	if (!rc) {
 		if (value)
 			*value = ol.value;
@@ -316,7 +324,7 @@ int mnt_optstr_deduplicate_option(char **optstr, const char *name)
 	do {
 		struct libmnt_optloc ol = MNT_INIT_OPTLOC;
 
-		rc = mnt_optstr_locate_option(opt, name, &ol);
+		rc = mnt_optstr_locate_option(opt, name, 0, &ol);
 		if (!rc) {
 			if (begin) {
 				/* remove the previous instance */
@@ -429,7 +437,7 @@ int mnt_optstr_set_option(char **optstr, const char *name, const char *value)
 		return -EINVAL;
 
 	if (*optstr)
-		rc = mnt_optstr_locate_option(*optstr, name, &ol);
+		rc = mnt_optstr_locate_option(*optstr, name, 0, &ol);
 	if (rc < 0)
 		return rc;			/* parse error */
 	if (rc == 1)
@@ -472,7 +480,7 @@ int mnt_optstr_remove_option(char **optstr, const char *name)
 	if (!optstr || !name)
 		return -EINVAL;
 
-	rc = mnt_optstr_locate_option(*optstr, name, &ol);
+	rc = mnt_optstr_locate_option(*optstr, name, 0, &ol);
 	if (rc != 0)
 		return rc;
 
@@ -632,6 +640,52 @@ int mnt_optstr_get_options(const char *optstr, char **subset,
 	return rc;
 }
 
+/*
+ * @optstr: string with comma separated list of options
+ * @wanted: options expected in @optstr
+ * @missing: returns options from @wanted which missing in @optstr (optional)
+ *
+ * Retursn: <0 on error, 0 on missing options, 1 if nothing is missing
+ */
+int mnt_optstr_get_missing(const char *optstr, const char *wanted, char **missing)
+{
+	char *name, *val, *str = (char *) wanted;
+	size_t namesz = 0, valsz = 0;
+	struct ul_buffer buf = UL_INIT_BUFFER;
+	int rc = 0;
+
+	if (!wanted)
+		return 1;
+	if (missing) {
+		/* caller wants data, prepare buffer */
+		ul_buffer_set_chunksize(&buf, strlen(wanted) + 3);	/* to call realloc() only once */
+		*missing = NULL;
+	}
+
+	while (!mnt_optstr_next_option(&str, &name, &namesz, &val, &valsz)) {
+
+		rc = mnt_optstr_locate_option((char *) optstr, name, namesz, NULL);
+		if (rc == 1) {			/* not found */
+			if (!missing)
+				return 0;
+			rc = __buffer_append_option(&buf, name, namesz, val, valsz);
+		}
+
+		if (rc < 0)
+			break;
+		rc = 0;
+	}
+
+	if (!rc && missing) {
+		if (ul_buffer_is_empty(&buf))
+			rc = 1;
+		else
+			*missing = ul_buffer_get_data(&buf);
+	} else
+		ul_buffer_free_data(&buf);
+
+	return rc;
+}
 
 /**
  * mnt_optstr_get_flags:
@@ -1055,7 +1109,7 @@ int mnt_optstr_fix_user(char **optstr)
 
 	DBG(CXT, ul_debug("fixing user"));
 
-	rc = mnt_optstr_locate_option(*optstr, "user", &ol);
+	rc = mnt_optstr_locate_option(*optstr, "user", 0, &ol);
 	if (rc)
 		return rc == 1 ? 0 : rc;	/* 1: user= not found */
 
@@ -1378,6 +1432,29 @@ static int test_get(struct libmnt_test *ts, int argc, char *argv[])
 	return rc;
 }
 
+static int test_missing(struct libmnt_test *ts, int argc, char *argv[])
+{
+	const char *optstr;
+	const char *wanted;
+	char *missing = NULL;
+	int rc;
+
+	if (argc < 2)
+		return -EINVAL;
+	optstr = argv[1];
+	wanted = argv[2];
+
+	rc = mnt_optstr_get_missing(optstr, wanted, &missing);
+	if (rc == 0)
+		printf("missing: %s\n", missing);
+	else if (rc == 1) {
+		printf("nothing\n");
+		rc = 0;
+	} else
+		printf("parse error: %s\n", optstr);
+	return rc;
+}
+
 static int test_remove(struct libmnt_test *ts, int argc, char *argv[])
 {
 	const char *name;
@@ -1456,6 +1533,7 @@ int main(int argc, char *argv[])
 		{ "--prepend",test_prepend,"<optstr> <name> [<value>]  prepend value to optstr" },
 		{ "--set",    test_set,    "<optstr> <name> [<value>]  (un)set value" },
 		{ "--get",    test_get,    "<optstr> <name>            search name in optstr" },
+		{ "--missing",test_missing,"<optstr> <wanted>          what from wanted is missing" },
 		{ "--remove", test_remove, "<optstr> <name>            remove name in optstr" },
 		{ "--dedup",  test_dedup,  "<optstr> <name>            deduplicate name in optstr" },
 		{ "--split",  test_split,  "<optstr>                   split into FS, VFS and userspace" },
-- 
2.43.0