/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include "alloc-util.h"
#include "dissect-image.h"
#include "extract-word.h"
#include "image-policy.h"
#include "log.h"
#include "logarithm.h"
#include "sort-util.h"
#include "string-util.h"
#include "strv.h"

/* Rationale for the chosen syntax:
 *
 * → one line, so that it can be reasonably added to a shell command line, for example via `systemd-dissect
 *   --image-policy=…` or to the kernel command line via `systemd.image_policy=`.
 *
 * → no use of "," or ";" as separators, so that it can be included in mount/fstab-style option strings and
 *   doesn't require escaping. Instead, separators are ":", "=", "+" which should be fine both in shell
 *   command lines and in mount/fstab style option strings.
 */

static int partition_policy_compare(const PartitionPolicy *a, const PartitionPolicy *b) {
        return CMP(ASSERT_PTR(a)->designator, ASSERT_PTR(b)->designator);
}

static const PartitionPolicy* image_policy_bsearch(const ImagePolicy *policy, PartitionDesignator designator) {
        if (!policy)
                return NULL;

        return typesafe_bsearch(
                        &(const PartitionPolicy) { .designator = designator },
                        ASSERT_PTR(policy)->policies,
                        ASSERT_PTR(policy)->n_policies,
                        partition_policy_compare);
}

PartitionPolicyFlags partition_policy_flags_extend(PartitionPolicyFlags flags) {
        /* If some parts of a flags field are left unspecified, let's fill in all options. */

        /* If no protection flag is set, then this means all are set */
        if ((flags & _PARTITION_POLICY_USE_MASK) == 0)
                flags |= PARTITION_POLICY_OPEN;

        /* If the gpt flags bits are not specified, set both options for each */
        if ((flags & _PARTITION_POLICY_READ_ONLY_MASK) == 0)
                flags |= PARTITION_POLICY_READ_ONLY_ON|PARTITION_POLICY_READ_ONLY_OFF;

        if ((flags & _PARTITION_POLICY_GROWFS_MASK) == 0)
                flags |= PARTITION_POLICY_GROWFS_ON|PARTITION_POLICY_GROWFS_OFF;

        return flags;
}

PartitionPolicyFlags partition_policy_flags_reduce(PartitionPolicyFlags flags) {
        /* The reverse of partition_policy_flags_extend(): if some parts of the flags field allow all
         * possible options, let's remove it from the flags to make them shorter */

        if (FLAGS_SET(flags, _PARTITION_POLICY_USE_MASK))
                flags &= ~_PARTITION_POLICY_USE_MASK;
        if (FLAGS_SET(flags, _PARTITION_POLICY_READ_ONLY_MASK))
                flags &= ~_PARTITION_POLICY_READ_ONLY_MASK;
        if (FLAGS_SET(flags, _PARTITION_POLICY_GROWFS_MASK))
                flags &= ~_PARTITION_POLICY_GROWFS_MASK;

        return flags;
}

static PartitionPolicyFlags partition_policy_normalized_flags(const PartitionPolicy *policy) {
        PartitionPolicyFlags flags = ASSERT_PTR(policy)->flags;

        /* This normalizes the per-partition policy flags. This means if the user left some things
         * unspecified, we'll fill in the appropriate "dontcare" policy instead. We'll also mask out bits
         * that do not make any sense for specific partition types. */

        flags = partition_policy_flags_extend(flags);

        /* If this is a verity or verity signature designator, then mask off all protection bits, this after
         * all needs no protection, because it *is* the protection */
        if (partition_verity_hash_to_data(policy->designator) >= 0 ||
            partition_verity_sig_to_data(policy->designator) >= 0)
                flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY);

        /* if this designator has no verity concept, then mask off verity protection flags */
        if (partition_verity_hash_of(policy->designator) < 0)
                flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED);

        /* If the partition must be absent, then the gpt flags don't matter */
        if ((flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT)
                flags &= ~(_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK);

        return flags;
}

PartitionPolicyFlags image_policy_get(const ImagePolicy *policy, PartitionDesignator designator) {
        PartitionDesignator data_designator = _PARTITION_DESIGNATOR_INVALID;
        const PartitionPolicy *pp;

        /* No policy means: everything may be used in any mode */
        if (!policy)
                return partition_policy_normalized_flags(
                                &(const PartitionPolicy) {
                                        .flags = PARTITION_POLICY_OPEN,
                                        .designator = designator,
                                });

        pp = image_policy_bsearch(policy, designator);
        if (pp)
                return partition_policy_normalized_flags(pp);

        /* Hmm, so this didn't work, then let's see if we can derive some policy from the underlying data
         * partition in case of verity/signature partitions */

        data_designator = partition_verity_hash_to_data(designator);
        if (data_designator >= 0) {
                PartitionPolicyFlags data_flags;

                /* So we are asked for the policy for a verity partition, and there's no explicit policy for
                 * that case. Let's synthesize a policy from the protection setting for the underlying data
                 * partition. */

                data_flags = image_policy_get(policy, data_designator);
                if (data_flags < 0)
                        return data_flags;

                /* We need verity if verity or verity with sig is requested */
                if (!(data_flags & (PARTITION_POLICY_SIGNED|PARTITION_POLICY_VERITY)))
                        return _PARTITION_POLICY_FLAGS_INVALID;

                /* If the data partition may be unused or absent, then the verity partition may too. Also, inherit the partition flags policy */
                return partition_policy_normalized_flags(
                                &(const PartitionPolicy) {
                                        .flags = PARTITION_POLICY_UNPROTECTED | (data_flags & (PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT)) |
                                                 (data_flags & _PARTITION_POLICY_PFLAGS_MASK),
                                        .designator = designator,
                                });
        }

        data_designator = partition_verity_sig_to_data(designator);
        if (data_designator >= 0) {
                PartitionPolicyFlags data_flags;

                /* Similar case as for verity partitions, but slightly more strict rules */

                data_flags = image_policy_get(policy, data_designator);
                if (data_flags < 0)
                        return data_flags;

                if (!(data_flags & PARTITION_POLICY_SIGNED))
                        return _PARTITION_POLICY_FLAGS_INVALID;

                return partition_policy_normalized_flags(
                                &(const PartitionPolicy) {
                                        .flags = PARTITION_POLICY_UNPROTECTED | (data_flags & (PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT)) |
                                                 (data_flags & _PARTITION_POLICY_PFLAGS_MASK),
                                        .designator = designator,
                                });
        }

        return _PARTITION_POLICY_FLAGS_INVALID; /* got nothing */
}

PartitionPolicyFlags image_policy_get_exhaustively(const ImagePolicy *policy, PartitionDesignator designator) {
        PartitionPolicyFlags flags;

        /* This is just like image_policy_get() but whenever there is no policy for a specific designator, we
         * return the default policy. */

        flags = image_policy_get(policy, designator);
        if (flags < 0)
                return partition_policy_normalized_flags(
                                &(const PartitionPolicy) {
                                        .flags = image_policy_default(policy),
                                        .designator = designator,
                                });

        return flags;
}

static PartitionPolicyFlags policy_flag_from_fstype(const char *s) {
        if (!s)
                return _PARTITION_POLICY_FLAGS_INVALID;

        if (streq(s, "btrfs"))
                return PARTITION_POLICY_BTRFS;
        if (streq(s, "erofs"))
                return PARTITION_POLICY_EROFS;
        if (streq(s, "ext4"))
                return PARTITION_POLICY_EXT4;
        if (streq(s, "f2fs"))
                return PARTITION_POLICY_F2FS;
        if (streq(s, "squashfs"))
                return PARTITION_POLICY_SQUASHFS;
        if (streq(s, "vfat"))
                return PARTITION_POLICY_VFAT;
        if (streq(s, "xfs"))
                return PARTITION_POLICY_XFS;

        return _PARTITION_POLICY_FLAGS_INVALID;
}

static PartitionPolicyFlags policy_flag_from_string_one(const char *s) {
        assert(s);

        /* This is a bitmask (i.e. not dense), hence we don't use the "string-table.h" stuff here. */

        if (streq(s, "verity"))
                return PARTITION_POLICY_VERITY;
        if (streq(s, "signed"))
                return PARTITION_POLICY_SIGNED;
        if (streq(s, "encrypted"))
                return PARTITION_POLICY_ENCRYPTED;
        if (streq(s, "encryptedwithintegrity"))
                return PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY;
        if (streq(s, "unprotected"))
                return PARTITION_POLICY_UNPROTECTED;
        if (streq(s, "unused"))
                return PARTITION_POLICY_UNUSED;
        if (streq(s, "absent"))
                return PARTITION_POLICY_ABSENT;
        if (streq(s, "open")) /* shortcut alias */
                return PARTITION_POLICY_OPEN;
        if (streq(s, "ignore")) /* ditto */
                return PARTITION_POLICY_IGNORE;
        if (streq(s, "read-only-on"))
                return PARTITION_POLICY_READ_ONLY_ON;
        if (streq(s, "read-only-off"))
                return PARTITION_POLICY_READ_ONLY_OFF;
        if (streq(s, "growfs-on"))
                return PARTITION_POLICY_GROWFS_ON;
        if (streq(s, "growfs-off"))
                return PARTITION_POLICY_GROWFS_OFF;

        return policy_flag_from_fstype(s);
}

PartitionPolicyFlags partition_policy_flags_from_string(const char *s, bool graceful) {
        PartitionPolicyFlags flags = 0;
        int r;

        assert(s);

        if (empty_or_dash(s))
                return 0;

        for (;;) {
                _cleanup_free_ char *f = NULL;
                PartitionPolicyFlags ff;

                r = extract_first_word(&s, &f, "+", EXTRACT_DONT_COALESCE_SEPARATORS);
                if (r < 0)
                        return r;
                if (r == 0)
                        break;

                ff = policy_flag_from_string_one(strstrip(f));
                if (ff < 0) {
                        if (graceful) {
                                log_debug("Unknown partition policy flag, ignoring: %s", f);
                                continue;
                        }
                        return -EBADRQC; /* recognizable error */
                }

                flags |= ff;
        }

        return flags;
}

static ImagePolicy* image_policy_new(size_t n_policies) {
        ImagePolicy *p;

        if (n_policies > (SIZE_MAX - offsetof(ImagePolicy, policies)) / sizeof(PartitionPolicy)) /* overflow check */
                return NULL;

        p = malloc(offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * n_policies);
        if (!p)
                return NULL;

        *p = (ImagePolicy) {
                .default_flags = PARTITION_POLICY_IGNORE,
        };
        return p;
}

ImagePolicy* image_policy_new_from_dissected(const DissectedImage *image, const VeritySettings *verity) {
        assert(image);

        ImagePolicy *image_policy = image_policy_new(_PARTITION_DESIGNATOR_MAX);
        if (!image_policy)
                return NULL;

        /* Default to 'absent', only what we find is allowed to be used */
        image_policy->default_flags = PARTITION_POLICY_ABSENT;

        for (PartitionDesignator pd = 0; pd < _PARTITION_DESIGNATOR_MAX; pd++) {
                PartitionPolicyFlags f = 0;

                if (!image->partitions[pd].found)
                        f |= PARTITION_POLICY_ABSENT;
                else {
                        if (streq_ptr(image->partitions[pd].fstype, "crypto_LUKS"))
                                f |= PARTITION_POLICY_ENCRYPTED;
                        else {
                                PartitionPolicyFlags fstype_flag = policy_flag_from_fstype(image->partitions[pd].fstype);
                                if (fstype_flag >= 0)
                                        f |= fstype_flag;
                        }

                        if (!verity || verity->designator < 0 || verity->designator == pd) {
                                if (image->verity_sig_ready || (verity && iovec_is_set(&verity->root_hash_sig)))
                                        f |= PARTITION_POLICY_SIGNED;
                                else if (image->verity_ready || (verity && iovec_is_set(&verity->root_hash)))
                                        f |= PARTITION_POLICY_VERITY;
                        }

                        if (!image->single_file_system) {
                                if (image->partitions[pd].growfs)
                                        f |= PARTITION_POLICY_GROWFS_ON;
                                else
                                        f |= PARTITION_POLICY_GROWFS_OFF;

                                if (image->partitions[pd].rw)
                                        f |= PARTITION_POLICY_READ_ONLY_OFF;
                                else
                                        f |= PARTITION_POLICY_READ_ONLY_ON;
                        }
                }

                image_policy->policies[pd] = (PartitionPolicy) {
                        .designator = pd,
                        .flags = f,
                };
                image_policy->n_policies++;
        }

        return image_policy;
}

int image_policy_from_string(const char *s, bool graceful, ImagePolicy **ret) {
        _cleanup_free_ ImagePolicy *p = NULL;
        uint64_t dmask = 0;
        ImagePolicy *t;
        PartitionPolicyFlags symbolic_policy;
        int r;

        assert(s);
        assert_cc(sizeof(dmask) * 8 >= _PARTITION_DESIGNATOR_MAX);

        /* Recognizable errors:
         *
         *     ENOTUNIQ → Two or more rules for the same partition
         *     EBADSLT  → Unknown partition designator
         *     EBADRQC  → Unknown policy flags
         */

        /* First, let's handle "symbolic" policies, i.e. "-", "*", "~" */
        if (empty_or_dash(s))
                /* ignore policy: everything may exist, but nothing used */
                symbolic_policy = PARTITION_POLICY_IGNORE;
        else if (streq(s, "*"))
                /* allow policy: everything is allowed */
                symbolic_policy = PARTITION_POLICY_OPEN;
        else if (streq(s, "~"))
                /* deny policy: nothing may exist */
                symbolic_policy = PARTITION_POLICY_ABSENT;
        else
                symbolic_policy = _PARTITION_POLICY_FLAGS_INVALID;

        if (symbolic_policy >= 0) {
                if (!ret)
                        return 0;

                p = image_policy_new(0);
                if (!p)
                        return -ENOMEM;

                p->default_flags = symbolic_policy;
                *ret = TAKE_PTR(p);
                return 0;
        }

        /* Allocate the policy at maximum size, i.e. for all designators. We might overshoot a bit, but the
         * items are cheap, and we can return unused space to libc once we know we don't need it */
        p = image_policy_new(_PARTITION_DESIGNATOR_MAX);
        if (!p)
                return -ENOMEM;

        const char *q = s;
        bool default_specified = false;
        for (;;) {
                _cleanup_free_ char *e = NULL, *d = NULL;
                PartitionDesignator designator;
                PartitionPolicyFlags flags;
                char *f, *ds, *fs;

                r = extract_first_word(&q, &e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
                if (r < 0)
                        return r;
                if (r == 0)
                        break;

                f = e;
                r = extract_first_word((const char**) &f, &d, "=", EXTRACT_DONT_COALESCE_SEPARATORS);
                if (r < 0)
                        return r;
                if (r == 0)
                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Expected designator name followed by '='; got instead: %s", e);
                if (!f) /* no separator? */
                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing '=' in policy expression: %s", e);

                ds = strstrip(d);
                if (isempty(ds)) {
                        /* Not partition name? then it's the default policy */
                        if (default_specified)
                                return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Default partition policy flags specified more than once.");

                        designator = _PARTITION_DESIGNATOR_INVALID;
                        default_specified = true;
                } else {
                        designator = partition_designator_from_string(ds);
                        if (designator < 0) {
                                if (!graceful)
                                        return log_debug_errno(SYNTHETIC_ERRNO(EBADSLT), "Unknown partition designator: %s", ds); /* recognizable error */

                                log_debug("Unknown partition designator, ignoring: %s", ds);
                                continue;
                        }
                        if (dmask & (UINT64_C(1) << designator))
                                return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Partition designator specified more than once: %s", ds);
                        dmask |= UINT64_C(1) << designator;
                }

                fs = strstrip(f);
                flags = partition_policy_flags_from_string(fs, graceful);
                if (flags == -EBADRQC)
                        return log_debug_errno(flags, "Unknown partition policy flag: %s", fs);
                if (flags < 0)
                        return log_debug_errno(flags, "Failed to parse partition policy flags '%s': %m", fs);

                if (designator < 0)
                        p->default_flags = flags;
                else {
                        p->policies[p->n_policies++] = (PartitionPolicy) {
                                .designator = designator,
                                .flags = flags,
                        };
                }
        };

        assert(p->n_policies <= _PARTITION_DESIGNATOR_MAX);

        /* Return unused space to libc */
        t = realloc(p, offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * p->n_policies);
        if (t)
                p = t;

        typesafe_qsort(p->policies, p->n_policies, partition_policy_compare);

        if (ret)
                *ret = TAKE_PTR(p);

        return 0;
}

int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret) {
        _cleanup_free_ char *buf = NULL;
        const char *l[CONST_LOG2U(_PARTITION_POLICY_MASK + _PARTITION_POLICY_FSTYPE_MASK + 1) + 1]; /* one string per known flag at most */
        size_t m = 0;

        assert(ret);

        if (flags < 0)
                return -EINVAL;

        /* If 'simplify' is false we'll output the precise value of every single flag.
         *
         * If 'simplify' is true we'll try to make the output shorter, by doing the following:
         *
         *     → we'll spell the long form "verity+signed+encrypted+unprotected+unused+absent" via its
         *       equivalent shortcut form "open" (which we happily parse btw, see above)
         *
         *     → we'll spell the long form "unused+absent" via its shortcut "ignore" (which we are also happy
         *       to parse)
         *
         *     → if the read-only/growfs policy flags are both set, we suppress them. this thus removes the
         *       distinction between "user explicitly declared don't care" and "we implied don't care because
         *       user didn't say anything".
         *
         * net result: the resulting string is shorter, but the effective policy declared that way will have
         * the same results as the long form. */

        if (simplify && (flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_OPEN)
                l[m++] = "open";
        else if (simplify && (flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_IGNORE)
                l[m++] = "ignore";
        else {
                if (flags & PARTITION_POLICY_VERITY)
                        l[m++] = "verity";
                if (flags & PARTITION_POLICY_SIGNED)
                        l[m++] = "signed";
                if (flags & PARTITION_POLICY_ENCRYPTED)
                        l[m++] = "encrypted";
                if (flags & PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY)
                        l[m++] = "encryptedwithintegrity";
                if (flags & PARTITION_POLICY_UNPROTECTED)
                        l[m++] = "unprotected";
                if (flags & PARTITION_POLICY_UNUSED)
                        l[m++] = "unused";
                if (flags & PARTITION_POLICY_ABSENT)
                        l[m++] = "absent";
        }

        if (!simplify || (!(flags & PARTITION_POLICY_READ_ONLY_ON) != !(flags & PARTITION_POLICY_READ_ONLY_OFF))) {
                if (flags & PARTITION_POLICY_READ_ONLY_ON)
                        l[m++] = "read-only-on";
                if (flags & PARTITION_POLICY_READ_ONLY_OFF)
                        l[m++] = "read-only-off";
        }

        if (!simplify || (!(flags & PARTITION_POLICY_GROWFS_ON) != !(flags & PARTITION_POLICY_GROWFS_OFF))) {
                if (flags & PARTITION_POLICY_GROWFS_OFF)
                        l[m++] = "growfs-off";
                if (flags & PARTITION_POLICY_GROWFS_ON)
                        l[m++] = "growfs-on";
        }

        /* These flags must translate to the literal fstype name of each filesystem, as accepted by
         * `mount -t`. */
        if (flags & PARTITION_POLICY_BTRFS)
                l[m++] = "btrfs";
        if (flags & PARTITION_POLICY_EROFS)
                l[m++] = "erofs";
        if (flags & PARTITION_POLICY_EXT4)
                l[m++] = "ext4";
        if (flags & PARTITION_POLICY_F2FS)
                l[m++] = "f2fs";
        if (flags & PARTITION_POLICY_SQUASHFS)
                l[m++] = "squashfs";
        if (flags & PARTITION_POLICY_VFAT)
                l[m++] = "vfat";
        if (flags & PARTITION_POLICY_XFS)
                l[m++] = "xfs";

        if (m == 0)
                buf = strdup("-");
        else {
                assert(m < ELEMENTSOF(l));
                l[m] = NULL;

                buf = strv_join((char**) l, "+");
        }
        if (!buf)
                return -ENOMEM;

        *ret = TAKE_PTR(buf);
        return (int) m;
}

int partition_policy_determine_fstype(
                const ImagePolicy *policy,
                PartitionDesignator designator,
                bool *ret_encrypted,
                char **ret_fstype) {

        _cleanup_free_ char *fstype = NULL;
        PartitionPolicyFlags policy_flags;
        int r;

        assert(designator >= 0 && designator < _PARTITION_DESIGNATOR_MAX);
        assert(ret_fstype);

        policy_flags = image_policy_get_exhaustively(policy, designator);
        if (policy_flags < 0)
                return policy_flags;

        /* The policy fstype flags translate to the literal fstype name of each filesystem. */
        r = partition_policy_flags_to_string(policy_flags & _PARTITION_POLICY_FSTYPE_MASK, /* simplify= */ true, &fstype);
        if (r < 0)
                return r;
        /* Input must be a single filesystem type, if the policy specifies more than one, return NULL */
        if (r != 1) {
                if (ret_encrypted)
                        *ret_encrypted = false;
                *ret_fstype = NULL;
                return 0;
        }

        /* If the policy also allows unprotected or verity filesystems, don't set the 'encrypted' flag */
        if (ret_encrypted)
                *ret_encrypted = (policy_flags & (PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY)) &&
                                 !(policy_flags & (PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_UNPROTECTED));
        *ret_fstype = TAKE_PTR(fstype);
        return 1;
}

static bool partition_policy_flags_extended_equal(PartitionPolicyFlags a, PartitionPolicyFlags b) {
        return partition_policy_flags_extend(a) == partition_policy_flags_extend(b);
}

static int image_policy_flags_all_match(const ImagePolicy *policy, PartitionPolicyFlags expected) {

        if (expected < 0)
                return -EINVAL;

        if (!partition_policy_flags_extended_equal(image_policy_default(policy), expected))
                return false;

        for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
                PartitionPolicyFlags f, w;

                f = image_policy_get_exhaustively(policy, d);
                if (f < 0)
                        return f;

                w = partition_policy_normalized_flags(
                                &(const PartitionPolicy) {
                                        .flags = expected,
                                        .designator = d,
                                });
                if (w < 0)
                        return w;
                if (f != w)
                        return false;
        }

        return true;
}

bool image_policy_equiv_ignore(const ImagePolicy *policy) {
        /* Checks if this is the ignore policy (or equivalent to it), i.e. everything is ignored, aka '-', aka '' */
        return image_policy_flags_all_match(policy, PARTITION_POLICY_IGNORE);
}

bool image_policy_equiv_allow(const ImagePolicy *policy) {
        /* Checks if this is the allow policy (or equivalent to it), i.e. everything is allowed, aka '*' */
        return image_policy_flags_all_match(policy, PARTITION_POLICY_OPEN);
}

bool image_policy_equiv_deny(const ImagePolicy *policy) {
        /* Checks if this is the deny policy (or equivalent to it), i.e. everything must be absent, aka '~' */
        return image_policy_flags_all_match(policy, PARTITION_POLICY_ABSENT);
}

int image_policy_to_string(const ImagePolicy *policy, bool simplify, char **ret) {
        _cleanup_free_ char *s = NULL;
        int r;

        assert(ret);

        if (simplify) {
                const char *fixed;

                if (image_policy_equiv_allow(policy))
                        fixed = "*";
                else if (image_policy_equiv_ignore(policy))
                        fixed = "-";
                else if (image_policy_equiv_deny(policy))
                        fixed = "~";
                else
                        fixed = NULL;

                if (fixed) {
                        s = strdup(fixed);
                        if (!s)
                                return -ENOMEM;

                        *ret = TAKE_PTR(s);
                        return 0;
                }
        }

        for (size_t i = 0; i < image_policy_n_entries(policy); i++) {
                const PartitionPolicy *p = policy->policies + i;
                _cleanup_free_ char *f = NULL;
                const char *t;

                assert(i == 0 || p->designator > policy->policies[i-1].designator); /* Validate perfect ordering */

                assert_se(t = partition_designator_to_string(p->designator));

                if (simplify) {
                        /* Skip policy entries that match the default anyway */
                        PartitionPolicyFlags df;

                        df = partition_policy_normalized_flags(
                                        &(const PartitionPolicy) {
                                                .flags = image_policy_default(policy),
                                                .designator = p->designator,
                                        });
                        if (df < 0)
                                return df;

                        if (df == p->flags)
                                continue;
                }

                r = partition_policy_flags_to_string(p->flags, simplify, &f);
                if (r < 0)
                        return r;

                if (!strextend(&s, isempty(s) ? "" : ":", t, "=", f))
                        return -ENOMEM;
        }

        if (!simplify || !partition_policy_flags_extended_equal(image_policy_default(policy), PARTITION_POLICY_IGNORE)) {
                _cleanup_free_ char *df = NULL;

                r = partition_policy_flags_to_string(image_policy_default(policy), simplify, &df);
                if (r < 0)
                        return r;

                if (!strextend(&s, isempty(s) ? "" : ":", "=", df))
                        return -ENOMEM;
        }

        if (isempty(s)) { /* no rule and default policy? then let's return "-" */
                s = strdup("-");
                if (!s)
                        return -ENOMEM;
        }

        *ret = TAKE_PTR(s);
        return 0;
}

bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b) {
        if (a == b)
                return true;
        if (image_policy_n_entries(a) != image_policy_n_entries(b))
                return false;
        if (image_policy_default(a) != image_policy_default(b))
                return false;
        for (size_t i = 0; i < image_policy_n_entries(a); i++) {
                if (a->policies[i].designator != b->policies[i].designator)
                        return false;
                if (a->policies[i].flags != b->policies[i].flags)
                        return false;
        }

        return true;
}

int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b) {

        /* The image_policy_equal() function checks if the policy is defined the exact same way. This
         * function here instead looks at the outcome of the two policies instead. Where does this come to
         * different results you ask? We imply some logic regarding Verity/Encryption: when no rule is
         * defined for a verity partition we can synthesize it from the protection level of the data
         * partition it protects. Or: any per-partition rule that is identical to the default rule is
         * redundant, and will be recognized as such by image_policy_equivalent() but not by
         * image_policy_equal()- */

        if (!partition_policy_flags_extended_equal(image_policy_default(a), image_policy_default(b)))
                return false;

        for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
                PartitionPolicyFlags f, w;

                f = image_policy_get_exhaustively(a, d);
                if (f < 0)
                        return f;

                w = image_policy_get_exhaustively(b, d);
                if (w < 0)
                        return w;

                if (f != w)
                        return false;
        }

        return true;
}

int config_parse_image_policy(
                const char *unit,
                const char *filename,
                unsigned line,
                const char *section,
                unsigned section_line,
                const char *lvalue,
                int ltype,
                const char *rvalue,
                void *data,
                void *userdata) {

        _cleanup_(image_policy_freep) ImagePolicy *np = NULL;
        ImagePolicy **p = ASSERT_PTR(data);
        int r;

        assert(rvalue);

        if (isempty(rvalue)) {
                *p = image_policy_free(*p);
                return 0;
        }

        r = image_policy_from_string(rvalue, /* graceful */ true, &np);
        if (r == -ENOTUNIQ)
                return log_syntax(unit, LOG_ERR, filename, line, r, "Duplicate rule in image policy, refusing: %s", rvalue);
        if (r == -EBADSLT)
                return log_syntax(unit, LOG_ERR, filename, line, r, "Unknown partition type in image policy, refusing: %s", rvalue);
        if (r == -EBADRQC)
                return log_syntax(unit, LOG_ERR, filename, line, r, "Unknown partition policy flag in image policy, refusing: %s", rvalue);
        if (r < 0)
                return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse image policy, refusing: %s", rvalue);

        return free_and_replace_full(*p, np, image_policy_free);
}

int parse_image_policy_argument(const char *s, ImagePolicy **policy) {
        _cleanup_(image_policy_freep) ImagePolicy *np = NULL;
        int r;

        assert(s);
        assert(policy);

        /*
         * This function is intended to be used in command line parsers.
         *
         * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON SUCCESS!
         * Hence, do not pass in uninitialized pointers.
         */

        r = image_policy_from_string(s, /* graceful= */ false, &np);
        if (r == -ENOTUNIQ)
                return log_error_errno(r, "Duplicate rule in image policy: %s", s);
        if (r == -EBADSLT)
                return log_error_errno(r, "Unknown partition type in image policy: %s", s);
        if (r == -EBADRQC)
                return log_error_errno(r, "Unknown partition policy flag in image policy: %s", s);
        if (r < 0)
                return log_error_errno(r, "Failed to parse image policy: %s", s);

        return free_and_replace_full(*policy, np, image_policy_free);
}

static bool partition_policy_flags_has_unspecified(PartitionPolicyFlags flags) {

        if ((flags & _PARTITION_POLICY_USE_MASK) == 0)
                return true;
        if ((flags & _PARTITION_POLICY_READ_ONLY_MASK) == 0)
                return true;
        if ((flags & _PARTITION_POLICY_GROWFS_MASK) == 0)
                return true;

        return false;
}

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;

        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 = 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;

        p->default_flags = partition_policy_flags_reduce(p->default_flags);

        for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
                PartitionPolicyFlags x, y, z, df;

                /* If this designator has no entry in either policy we don't need to include it in the intersection either. */
                if (!image_policy_bsearch(a, d) && !image_policy_bsearch(b, d))
                        continue;

                /* Expand this policy flags field to the "long" form, i.e. for each part of the flags that
                 * are left unspcified add in all possible options */
                x = image_policy_get_exhaustively(a, d);
                if (x < 0)
                        return x;

                y = image_policy_get_exhaustively(b, d);
                if (y < 0)
                        return y;

                /* Mask it */
                z = op(x, y);

                /* 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(
                                &(const PartitionPolicy) {
                                        .flags = image_policy_default(p),
                                        .designator = d,
                                });
                if (df < 0)
                        return df;
                if (df == z) /* Same as default? then let's skip this */
                        continue;

                /* image_policy_get_exhaustively() may have extended the flags mask to include all
                 * read-only/growfs flags if not set. Let's remove them again, if they are both set to
                 * minimize the policy again. */
                z = partition_policy_flags_reduce(z);

                p->policies[p->n_policies++] = (struct PartitionPolicy) {
                        .designator = d,
                        .flags = z,
                };
        }

        if (ret)
                *ret = TAKE_PTR(p);

        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);
}

int image_policy_ignore_designators(const ImagePolicy *p, const PartitionDesignator table[], size_t n_table, ImagePolicy **ret) {
        assert(p);
        assert(table || n_table == 0);
        assert(ret);

        /* Patches the specified image policy, replacing the policy for the specified designators by an
         * "ignore" policy. Returns a patched copy. This is useful in context where only some of the
         * available partitions shall be mounted, and hence the policy for the others really doesn't
         * matter. */

        _cleanup_(image_policy_freep) ImagePolicy *np = image_policy_new(_PARTITION_DESIGNATOR_MAX);
        if (!np)
                return -ENOMEM;

        FOREACH_ARRAY(t, table, n_table) {
                assert(*t >= 0);
                assert(*t < _PARTITION_DESIGNATOR_MAX);

                if (image_policy_bsearch(np, *t))
                        continue;

                /* Insert an ignore policy for this entry, and sort it to the right place, so that image_policy_bsearch() can work */
                np->policies[np->n_policies++] = (PartitionPolicy) {
                        .designator = *t,
                        .flags = PARTITION_POLICY_IGNORE,
                };
                typesafe_qsort(np->policies, np->n_policies, partition_policy_compare);
        }

        FOREACH_ARRAY(i, p->policies, p->n_policies) {

                if (image_policy_bsearch(np, i->designator))
                        continue;

                /* Copy the policy entry from the old image policy, and sort it to the right place, so that image_policy_bsearch() can work */
                np->policies[np->n_policies++] = *i;
                typesafe_qsort(np->policies, np->n_policies, partition_policy_compare);
        }

        np->default_flags = p->default_flags;

        /* Return unused space to libc */
        ImagePolicy *t = realloc(np, offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * np->n_policies);
        if (t)
                np = t;

        *ret = TAKE_PTR(np);
        return 0;
}

const ImagePolicy image_policy_allow = {
        /* Allow policy */
        .n_policies = 0,
        .default_flags = PARTITION_POLICY_OPEN,
};

const ImagePolicy image_policy_deny = {
        /* Deny policy */
        .n_policies = 0,
        .default_flags = PARTITION_POLICY_ABSENT,
};

const ImagePolicy image_policy_ignore = {
        /* Ignore policy */
        .n_policies = 0,
        .default_flags = PARTITION_POLICY_IGNORE,
};

const ImagePolicy image_policy_sysext = {
        /* For system extensions, honour root file system, and /usr/ and ignore everything else. After all,
         * we are only interested in /usr/ + /opt/ trees anyway, and that's really the only place they can
         * be. */
        .n_policies = 2,
        .policies = {
                { PARTITION_ROOT,     PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_USR,      PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
        },
        .default_flags = PARTITION_POLICY_IGNORE,
};

const ImagePolicy image_policy_sysext_strict = {
        /* For system extensions, requiring signing */
        .n_policies = 2,
        .policies = {
                { PARTITION_ROOT,     PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
                { PARTITION_USR,      PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
        },
        .default_flags = PARTITION_POLICY_IGNORE,
};

const ImagePolicy image_policy_confext = {
        /* For configuration extensions, honour root file system, and ignore everything else. After all, we
         * are only interested in the /etc/ tree anyway, and that's really the only place it can be. */
        .n_policies = 1,
        .policies = {
                { PARTITION_ROOT,     PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
        },
        .default_flags = PARTITION_POLICY_IGNORE,
};

const ImagePolicy image_policy_confext_strict = {
        .n_policies = 1,
        .policies = {
                { PARTITION_ROOT,     PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
        },
        .default_flags = PARTITION_POLICY_IGNORE,
};

const ImagePolicy image_policy_container = {
        /* For systemd-nspawn containers we use all partitions, with the exception of swap */
        .n_policies = 8,
        .policies = {
                { PARTITION_ROOT,     PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_USR,      PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_HOME,     PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_SRV,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_ESP,      PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_TMP,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_VAR,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
        },
        .default_flags = PARTITION_POLICY_IGNORE,
};

const ImagePolicy image_policy_host = {
        /* For the host policy we basically use everything */
        .n_policies = 9,
        .policies = {
                { PARTITION_ROOT,     PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_USR,      PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_HOME,     PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_SRV,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_ESP,      PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_SWAP,     PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_TMP,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_VAR,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
        },
        .default_flags = PARTITION_POLICY_IGNORE,
};

const ImagePolicy image_policy_service = {
        /* For RootImage= in services we skip ESP/XBOOTLDR and swap */
        .n_policies = 6,
        .policies = {
                { PARTITION_ROOT,     PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_USR,      PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_HOME,     PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_SRV,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_TMP,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
                { PARTITION_VAR,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
        },
        .default_flags = PARTITION_POLICY_IGNORE,
};
