]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
image-policy: add image_policy_union helper
authorLuca Boccassi <luca.boccassi@gmail.com>
Fri, 19 Dec 2025 16:53:18 +0000 (16:53 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Mon, 19 Jan 2026 14:47:15 +0000 (15:47 +0100)
Similar to image_policy_intersect but is the union of all used policies
instead

src/shared/image-policy.c
src/shared/image-policy.h
src/test/test-image-policy.c

index b5f9e699d6dddc504778e1647e7f16f64777c03c..813dc04f9595cce4fb446cec2d3ae264ebc628a4 100644 (file)
@@ -785,21 +785,35 @@ static bool partition_policy_flags_has_unspecified(PartitionPolicyFlags flags) {
         return false;
 }
 
-int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret) {
+static PartitionPolicyFlags policy_flags_or(PartitionPolicyFlags a, PartitionPolicyFlags b) {
+        return a | b;
+}
+
+static PartitionPolicyFlags policy_flags_and(PartitionPolicyFlags a, PartitionPolicyFlags b) {
+        return a & b;
+}
+
+static int policy_intersect_or_union(
+                const ImagePolicy *a,
+                const ImagePolicy *b,
+                PartitionPolicyFlags (*op)(PartitionPolicyFlags a, PartitionPolicyFlags b),
+                ImagePolicy **ret) {
+
         _cleanup_(image_policy_freep) ImagePolicy *p = NULL;
 
-        /* Calculates the intersection of the specified policies, i.e. only what is permitted in both. This
-         * might fail with -ENAVAIL if the intersection is an "impossible policy". For example, if a root
-         * partition my neither be used, nor be absent, nor be unused then this is considered
-         * "impossible".  */
+        assert(op);
+
+        /* Calculates the intersection or union of the specified policies, i.e. only what is permitted in
+         * both or either. This might fail with -ENAVAIL if the intersection is an "impossible policy". For
+         * example, if a root partition my neither be used, nor be absent, nor be unused then this is
+         * considered "impossible". */
 
         p = image_policy_new(_PARTITION_DESIGNATOR_MAX);
         if (!p)
                 return -ENOMEM;
 
-        p->default_flags =
-                partition_policy_flags_extend(image_policy_default(a)) &
-                partition_policy_flags_extend(image_policy_default(b));
+        p->default_flags = op(partition_policy_flags_extend(image_policy_default(a)),
+                              partition_policy_flags_extend(image_policy_default(b)));
 
         if (partition_policy_flags_has_unspecified(p->default_flags)) /* Intersection empty? */
                 return -ENAVAIL;
@@ -824,10 +838,12 @@ int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePoli
                         return y;
 
                 /* Mask it */
-                z = x & y;
+                z = op(x, y);
 
-                /* Check if the intersection is empty for this partition. If so, generate a clear error */
-                if (partition_policy_flags_has_unspecified(z))
+                /* Check if the intersection is empty for this partition. If so, generate a clear error.
+                 * If the partition has to be absent, then it won't have read-only/growfs flags, as
+                 * image_policy_get_exhaustively() intentionally strips them, so skip the check. */
+                if (z != PARTITION_POLICY_ABSENT && partition_policy_flags_has_unspecified(z))
                         return -ENAVAIL;
 
                 df = partition_policy_normalized_flags(
@@ -857,6 +873,14 @@ int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePoli
         return 0;
 }
 
+int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret) {
+        return policy_intersect_or_union(a, b, policy_flags_and, ret);
+}
+
+int image_policy_union(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret) {
+        return policy_intersect_or_union(a, b, policy_flags_or, ret);
+}
+
 ImagePolicy* image_policy_free(ImagePolicy *p) {
         return mfree(p);
 }
index 218d2c5beb63a864fdc79a6d8d26ad191fbd4a2a..c40192931b76896970a0f81f525e2fc97b74be53 100644 (file)
@@ -111,6 +111,7 @@ bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b);       /* ch
 int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b);   /* checks if the outcome is the same, i.e. for all partitions results in the same decisions. */
 
 int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret);
+int image_policy_union(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret);
 
 ImagePolicy* image_policy_free(ImagePolicy *p);
 
index f285d31d9e1056f0a7a35aaf5a3ad93b1c84f7f4..12f7fc51e06943445def8108ed754ba3c0fdb0dc 100644 (file)
@@ -142,14 +142,17 @@ TEST(extend) {
         assert_se(partition_policy_flags_extend(PARTITION_POLICY_GROWFS_ON) == (PARTITION_POLICY_GROWFS_ON|_PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK));
 }
 
-static void test_policy_intersect_one(const char *a, const char *b, const char *c) {
+static void test_policy_intersect_one(const char *a, const char *b, const char *c, bool intersect) {
         _cleanup_(image_policy_freep) ImagePolicy *x = NULL, *y = NULL, *z = NULL, *t = NULL;
 
         assert_se(image_policy_from_string(a, /* graceful= */ false, &x) >= 0);
         assert_se(image_policy_from_string(b, /* graceful= */ false, &y) >= 0);
         assert_se(image_policy_from_string(c, /* graceful= */ false, &z) >= 0);
 
-        assert_se(image_policy_intersect(x, y, &t) >= 0);
+        if (intersect)
+                assert_se(image_policy_intersect(x, y, &t) >= 0);
+        else
+                assert_se(image_policy_union(x, y, &t) >= 0);
 
         _cleanup_free_ char *s1 = NULL, *s2 = NULL, *s3 = NULL, *s4 = NULL;
         assert_se(image_policy_to_string(x, false, &s1) >= 0);
@@ -157,21 +160,33 @@ static void test_policy_intersect_one(const char *a, const char *b, const char *
         assert_se(image_policy_to_string(z, false, &s3) >= 0);
         assert_se(image_policy_to_string(t, false, &s4) >= 0);
 
-        log_info("%s ^ %s → %s vs. %s", s1, s2, s3, s4);
+        log_info("%s %s %s → %s vs. %s", s1, intersect ? "^" : "U", s2, s3, s4);
 
         assert_se(image_policy_equivalent(z, t) > 0);
 }
 
 TEST(image_policy_intersect) {
-        test_policy_intersect_one("", "", "");
-        test_policy_intersect_one("-", "-", "-");
-        test_policy_intersect_one("*", "*", "*");
-        test_policy_intersect_one("~", "~", "~");
-        test_policy_intersect_one("root=verity+signed", "root=signed+verity", "root=verity+signed");
-        test_policy_intersect_one("root=verity+signed", "root=signed", "root=signed");
-        test_policy_intersect_one("root=verity+signed", "root=verity", "root=verity");
-        test_policy_intersect_one("root=open", "root=verity", "root=verity");
-        test_policy_intersect_one("root=open", "=verity+ignore", "root=verity+ignore:=ignore");
+        test_policy_intersect_one("", "", "", /* intersect= */ true);
+        test_policy_intersect_one("-", "-", "-", /* intersect= */ true);
+        test_policy_intersect_one("*", "*", "*", /* intersect= */ true);
+        test_policy_intersect_one("~", "~", "~", /* intersect= */ true);
+        test_policy_intersect_one("root=verity+signed", "root=signed+verity", "root=verity+signed", /* intersect= */ true);
+        test_policy_intersect_one("root=verity+signed", "root=signed", "root=signed", /* intersect= */ true);
+        test_policy_intersect_one("root=verity+signed", "root=verity", "root=verity", /* intersect= */ true);
+        test_policy_intersect_one("root=open", "root=verity", "root=verity", /* intersect= */ true);
+        test_policy_intersect_one("root=open", "=verity+ignore", "root=verity+ignore:=ignore", /* intersect= */ true);
+}
+
+TEST(image_policy_union) {
+        test_policy_intersect_one("root=verity+signed", "root=signed+verity", "root=verity+signed", /* intersect= */ false);
+        test_policy_intersect_one("root=verity+signed", "root=signed", "root=verity+signed", /* intersect= */ false);
+        test_policy_intersect_one("root=verity+signed", "root=verity", "root=verity+signed", /* intersect= */ false);
+        test_policy_intersect_one("root=signed", "root=verity", "root=verity+signed", /* intersect= */ false);
+        test_policy_intersect_one("root=signed:=absent", "root=verity:=unused", "root=verity+signed", /* intersect= */ false);
+        test_policy_intersect_one("root=open", "root=verity", "root=open", /* intersect= */ false);
+        test_policy_intersect_one("root=open", "=verity+ignore", "root=open:=verity+ignore", /* intersect= */ false);
+        test_policy_intersect_one("root=open:usr=absent", "root=open:usr=absent", "root=open:usr=absent", /* intersect= */ false);
+
 }
 
 static void test_policy_ignore_designators_one(const char *a, const PartitionDesignator array[], size_t n, const char *b) {