diff --git a/.systemd.metadata b/.systemd.metadata
index 3d124b3..f971042 100644
--- a/.systemd.metadata
+++ b/.systemd.metadata
@@ -1 +1 @@
-5b3ef156651d2879a54b7e1635792fad038f637f SOURCES/systemd-248.5.tar.gz
+9fdcf03e0c88b3aa98c95626a588311084ef9047 SOURCES/systemd-249.2.tar.gz
diff --git a/SOURCES/13496-fb.patch b/SOURCES/13496-fb.patch
deleted file mode 100644
index 2f16d58..0000000
--- a/SOURCES/13496-fb.patch
+++ /dev/null
@@ -1,1738 +0,0 @@
-From 6999572c06303057dbdc16c04c523f407aec08bd Mon Sep 17 00:00:00 2001
-From: Julia Kartseva <hex@fb.com>
-Date: Wed, 16 Sep 2020 15:58:04 -0700
-Subject: [PATCH 1/9] shared: add bpf-program helpers
-
-Add helpers to:
-- Create new BPFProgram instance from a path in bpf
-filesystem and bpf attach type;
-- Pin a program to bpf fs;
-- Get BPF program ID by BPF program FD.
----
- src/shared/bpf-program.c | 80 ++++++++++++++++++++++++++++++++++++++++
- src/shared/bpf-program.h |  5 ++-
- 2 files changed, 84 insertions(+), 1 deletion(-)
-
-diff --git a/src/shared/bpf-program.c b/src/shared/bpf-program.c
-index 10239142af..d67ada23b0 100644
---- a/src/shared/bpf-program.c
-+++ b/src/shared/bpf-program.c
-@@ -12,6 +12,26 @@
- #include "missing_syscall.h"
- #include "path-util.h"
- 
-+ /* struct bpf_prog_info info must be initialized since its value is both input and output
-+  * for BPF_OBJ_GET_INFO_BY_FD syscall. */
-+static int bpf_program_get_info_by_fd(int prog_fd, struct bpf_prog_info *info, uint32_t info_len) {
-+        union bpf_attr attr;
-+
-+        /* Explicitly memset to zero since some compilers may produce non-zero-initialized padding when
-+         * structured initialization is used.
-+         * Refer to https://github.com/systemd/systemd/issues/18164
-+         */
-+        zero(attr);
-+        attr.info.bpf_fd = prog_fd;
-+        attr.info.info_len = info_len;
-+        attr.info.info = PTR_TO_UINT64(info);
-+
-+        if (bpf(BPF_OBJ_GET_INFO_BY_FD, &attr, sizeof(attr)) < 0)
-+                return -errno;
-+
-+        return 0;
-+}
-+
- int bpf_program_new(uint32_t prog_type, BPFProgram **ret) {
-         _cleanup_(bpf_program_unrefp) BPFProgram *p = NULL;
- 
-@@ -28,6 +48,38 @@ int bpf_program_new(uint32_t prog_type, BPFProgram **ret) {
-         return 0;
- }
- 
-+int bpf_program_new_from_bpffs_path(const char *path, BPFProgram **ret) {
-+        _cleanup_(bpf_program_unrefp) BPFProgram *p = NULL;
-+        struct bpf_prog_info info = {};
-+        int r;
-+
-+        assert(path);
-+        assert(ret);
-+
-+        p = new(BPFProgram, 1);
-+        if (!p)
-+                return -ENOMEM;
-+
-+        *p = (BPFProgram) {
-+                .prog_type = BPF_PROG_TYPE_UNSPEC,
-+                .n_ref = 1,
-+                .kernel_fd = -1,
-+        };
-+
-+        r = bpf_program_load_from_bpf_fs(p, path);
-+        if (r < 0)
-+                return r;
-+
-+        r = bpf_program_get_info_by_fd(p->kernel_fd, &info, sizeof(info));
-+        if (r < 0)
-+                return r;
-+
-+        p->prog_type = info.type;
-+        *ret = TAKE_PTR(p);
-+
-+        return 0;
-+}
-+
- static BPFProgram *bpf_program_free(BPFProgram *p) {
-         assert(p);
- 
-@@ -254,3 +306,31 @@ int bpf_map_lookup_element(int fd, const void *key, void *value) {
- 
-         return 0;
- }
-+
-+int bpf_program_pin(int prog_fd, const char *bpffs_path) {
-+        union bpf_attr attr;
-+
-+        zero(attr);
-+        attr.pathname = PTR_TO_UINT64((void *) bpffs_path);
-+        attr.bpf_fd = prog_fd;
-+
-+        if (bpf(BPF_OBJ_PIN, &attr, sizeof(attr)) < 0)
-+                return -errno;
-+
-+        return 0;
-+}
-+
-+int bpf_program_get_id_by_fd(int prog_fd, uint32_t *ret_id) {
-+        struct bpf_prog_info info = {};
-+        int r;
-+
-+        assert(ret_id);
-+
-+        r = bpf_program_get_info_by_fd(prog_fd, &info, sizeof(info));
-+        if (r < 0)
-+                return r;
-+
-+        *ret_id = info.id;
-+
-+        return 0;
-+};
-diff --git a/src/shared/bpf-program.h b/src/shared/bpf-program.h
-index eef77f9d8e..243cef923f 100644
---- a/src/shared/bpf-program.h
-+++ b/src/shared/bpf-program.h
-@@ -26,8 +26,9 @@ struct BPFProgram {
- };
- 
- int bpf_program_new(uint32_t prog_type, BPFProgram **ret);
--BPFProgram *bpf_program_unref(BPFProgram *p);
-+int bpf_program_new_from_bpffs_path(const char *path, BPFProgram **ret);
- BPFProgram *bpf_program_ref(BPFProgram *p);
-+BPFProgram *bpf_program_unref(BPFProgram *p);
- 
- int bpf_program_add_instructions(BPFProgram *p, const struct bpf_insn *insn, size_t count);
- int bpf_program_load_kernel(BPFProgram *p, char *log_buf, size_t log_size);
-@@ -35,6 +36,8 @@ int bpf_program_load_from_bpf_fs(BPFProgram *p, const char *path);
- 
- int bpf_program_cgroup_attach(BPFProgram *p, int type, const char *path, uint32_t flags);
- int bpf_program_cgroup_detach(BPFProgram *p);
-+int bpf_program_pin(int prog_fd, const char *bpffs_path);
-+int bpf_program_get_id_by_fd(int prog_fd, uint32_t *ret_id);
- 
- int bpf_map_new(enum bpf_map_type type, size_t key_size, size_t value_size, size_t max_entries, uint32_t flags);
- int bpf_map_update_element(int fd, const void *key, void *value);
--- 
-2.30.2
-
-
-From b04b09640c624df8f64944dead8d59faa0521d2e Mon Sep 17 00:00:00 2001
-From: Julia Kartseva <hex@fb.com>
-Date: Thu, 4 Feb 2021 00:02:07 -0800
-Subject: [PATCH 2/9] shared: bpf_attach_type {from,to} string
-
-Introduce bpf_cgroup_attach_type_table with accustomed attached type
-names also used in bpftool.
-Add bpf_cgroup_attach_type_{from|to}_string helpers to convert from|to
-string representation of pinned bpf program, e.g.
-"egress:/sys/fs/bpf/egress-hook" for
-/sys/fs/bpf/egress-hook path and BPF_CGROUP_INET_EGRESS attach type.
----
- src/shared/bpf-program.c | 24 ++++++++++++++++++++++++
- src/shared/bpf-program.h |  3 +++
- 2 files changed, 27 insertions(+)
-
-diff --git a/src/shared/bpf-program.c b/src/shared/bpf-program.c
-index d67ada23b0..a8a34521fd 100644
---- a/src/shared/bpf-program.c
-+++ b/src/shared/bpf-program.c
-@@ -11,6 +11,30 @@
- #include "memory-util.h"
- #include "missing_syscall.h"
- #include "path-util.h"
-+#include "string-table.h"
-+
-+static const char *const bpf_cgroup_attach_type_table[__MAX_BPF_ATTACH_TYPE] = {
-+        [BPF_CGROUP_INET_INGRESS] =     "ingress",
-+        [BPF_CGROUP_INET_EGRESS] =      "egress",
-+        [BPF_CGROUP_INET_SOCK_CREATE] = "sock_create",
-+        [BPF_CGROUP_SOCK_OPS] =         "sock_ops",
-+        [BPF_CGROUP_DEVICE] =           "device",
-+        [BPF_CGROUP_INET4_BIND] =       "bind4",
-+        [BPF_CGROUP_INET6_BIND] =       "bind6",
-+        [BPF_CGROUP_INET4_CONNECT] =    "connect4",
-+        [BPF_CGROUP_INET6_CONNECT] =    "connect6",
-+        [BPF_CGROUP_INET4_POST_BIND] =  "post_bind4",
-+        [BPF_CGROUP_INET6_POST_BIND] =  "post_bind6",
-+        [BPF_CGROUP_UDP4_SENDMSG] =     "sendmsg4",
-+        [BPF_CGROUP_UDP6_SENDMSG] =     "sendmsg6",
-+        [BPF_CGROUP_SYSCTL] =           "sysctl",
-+        [BPF_CGROUP_UDP4_RECVMSG] =     "recvmsg4",
-+        [BPF_CGROUP_UDP6_RECVMSG] =     "recvmsg6",
-+        [BPF_CGROUP_GETSOCKOPT] =       "getsockopt",
-+        [BPF_CGROUP_SETSOCKOPT] =       "setsockopt",
-+};
-+
-+DEFINE_STRING_TABLE_LOOKUP(bpf_cgroup_attach_type, int);
- 
-  /* struct bpf_prog_info info must be initialized since its value is both input and output
-   * for BPF_OBJ_GET_INFO_BY_FD syscall. */
-diff --git a/src/shared/bpf-program.h b/src/shared/bpf-program.h
-index 243cef923f..86fd338c93 100644
---- a/src/shared/bpf-program.h
-+++ b/src/shared/bpf-program.h
-@@ -43,4 +43,7 @@ int bpf_map_new(enum bpf_map_type type, size_t key_size, size_t value_size, size
- int bpf_map_update_element(int fd, const void *key, void *value);
- int bpf_map_lookup_element(int fd, const void *key, void *value);
- 
-+int bpf_cgroup_attach_type_from_string(const char *str) _pure_;
-+const char *bpf_cgroup_attach_type_to_string(int attach_type) _const_;
-+
- DEFINE_TRIVIAL_CLEANUP_FUNC(BPFProgram*, bpf_program_unref);
--- 
-2.30.2
-
-
-From 2ab40fe0dc0f5ec83fe73bc750e9b3f5aabf1fb9 Mon Sep 17 00:00:00 2001
-From: Julia Kartseva <hex@fb.com>
-Date: Mon, 1 Mar 2021 16:56:04 -0800
-Subject: [PATCH 3/9] cgroup: add foreign program to cgroup context
-
-- Store foreign bpf programs in cgroup context. A program is considered
-foreign if it was loaded to a kernel by an entity external to systemd,
-so systemd is responsible only for attach and detach paths.
-- Support the case of pinned bpf programs: pinning to bpffs so a program
-is kept loaded to the kernel even when program fd is closed by a user
-application is a common way to extend program's lifetime.
-- Aadd linked list node struct with attach type and bpffs path
-fields.
----
- src/core/cgroup.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
- src/core/cgroup.h | 10 ++++++++++
- 2 files changed, 55 insertions(+)
-
-diff --git a/src/core/cgroup.c b/src/core/cgroup.c
-index 3ed6ac09ff..a0af50518d 100644
---- a/src/core/cgroup.c
-+++ b/src/core/cgroup.c
-@@ -190,6 +190,15 @@ void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockI
-         free(b);
- }
- 
-+void cgroup_context_remove_bpf_foreign_program(CGroupContext *c, CGroupBPFForeignProgram *p) {
-+        assert(c);
-+        assert(p);
-+
-+        LIST_REMOVE(programs, c->bpf_foreign_programs, p);
-+        free(p->bpffs_path);
-+        free(p);
-+}
-+
- void cgroup_context_done(CGroupContext *c) {
-         assert(c);
- 
-@@ -217,6 +226,9 @@ void cgroup_context_done(CGroupContext *c) {
-         c->ip_filters_ingress = strv_free(c->ip_filters_ingress);
-         c->ip_filters_egress = strv_free(c->ip_filters_egress);
- 
-+        while (c->bpf_foreign_programs)
-+                cgroup_context_remove_bpf_foreign_program(c, c->bpf_foreign_programs);
-+
-         cpu_set_reset(&c->cpuset_cpus);
-         cpu_set_reset(&c->cpuset_mems);
- }
-@@ -360,6 +372,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) {
-         CGroupIODeviceLatency *l;
-         CGroupBlockIODeviceBandwidth *b;
-         CGroupBlockIODeviceWeight *w;
-+        CGroupBPFForeignProgram *p;
-         CGroupDeviceAllow *a;
-         CGroupContext *c;
-         IPAddressAccessItem *iaai;
-@@ -544,6 +557,10 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) {
- 
-         STRV_FOREACH(path, c->ip_filters_egress)
-                 fprintf(f, "%sIPEgressFilterPath: %s\n", prefix, *path);
-+
-+        LIST_FOREACH(programs, p, c->bpf_foreign_programs)
-+                fprintf(f, "%sBPFProgram: %s:%s",
-+                        prefix, bpf_cgroup_attach_type_to_string(p->attach_type), p->bpffs_path);
- }
- 
- int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode) {
-@@ -575,6 +592,34 @@ int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode)
-         return 0;
- }
- 
-+int cgroup_add_bpf_foreign_program(CGroupContext *c, uint32_t attach_type, const char *bpffs_path) {
-+        CGroupBPFForeignProgram *p;
-+        _cleanup_free_ char *d = NULL;
-+
-+        assert(c);
-+        assert(bpffs_path);
-+
-+        if (!path_is_normalized(bpffs_path) || !path_is_absolute(bpffs_path))
-+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not normalized: %m");
-+
-+        d = strdup(bpffs_path);
-+        if (!d)
-+                return log_oom();
-+
-+        p = new(CGroupBPFForeignProgram, 1);
-+        if (!p)
-+                return log_oom();
-+
-+        *p = (CGroupBPFForeignProgram) {
-+                .attach_type = attach_type,
-+                .bpffs_path = TAKE_PTR(d),
-+        };
-+
-+        LIST_PREPEND(programs, c->bpf_foreign_programs, TAKE_PTR(p));
-+
-+        return 0;
-+}
-+
- #define UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP(entry)                       \
-         uint64_t unit_get_ancestor_##entry(Unit *u) {                   \
-                 CGroupContext *c;                                       \
-diff --git a/src/core/cgroup.h b/src/core/cgroup.h
-index fa79ba1523..be3060eba7 100644
---- a/src/core/cgroup.h
-+++ b/src/core/cgroup.h
-@@ -31,6 +31,7 @@ typedef struct CGroupIODeviceLimit CGroupIODeviceLimit;
- typedef struct CGroupIODeviceLatency CGroupIODeviceLatency;
- typedef struct CGroupBlockIODeviceWeight CGroupBlockIODeviceWeight;
- typedef struct CGroupBlockIODeviceBandwidth CGroupBlockIODeviceBandwidth;
-+typedef struct CGroupBPFForeignProgram CGroupBPFForeignProgram;
- 
- typedef enum CGroupDevicePolicy {
-         /* When devices listed, will allow those, plus built-in ones, if none are listed will allow
-@@ -94,6 +95,12 @@ struct CGroupBlockIODeviceBandwidth {
-         uint64_t wbps;
- };
- 
-+struct CGroupBPFForeignProgram {
-+        LIST_FIELDS(CGroupBPFForeignProgram, programs);
-+        uint32_t attach_type;
-+        char *bpffs_path;
-+};
-+
- struct CGroupContext {
-         bool cpu_accounting;
-         bool io_accounting;
-@@ -142,6 +149,7 @@ struct CGroupContext {
- 
-         char **ip_filters_ingress;
-         char **ip_filters_egress;
-+        LIST_HEAD(CGroupBPFForeignProgram, bpf_foreign_programs);
- 
-         /* For legacy hierarchies */
-         uint64_t cpu_shares;
-@@ -202,8 +210,10 @@ void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *
- void cgroup_context_free_io_device_latency(CGroupContext *c, CGroupIODeviceLatency *l);
- void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w);
- void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b);
-+void cgroup_context_remove_bpf_foreign_program(CGroupContext *c, CGroupBPFForeignProgram *p);
- 
- int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode);
-+int cgroup_add_bpf_foreign_program(CGroupContext *c, uint32_t attach_type, const char *path);
- 
- void cgroup_oomd_xattr_apply(Unit *u, const char *cgroup_path);
- 
--- 
-2.30.2
-
-
-From 42b3c33e312d70fec24b5daa455ab4abdcab81b4 Mon Sep 17 00:00:00 2001
-From: Julia Kartseva <hex@fb.com>
-Date: Wed, 16 Sep 2020 15:58:04 -0700
-Subject: [PATCH 4/9] core: add bpf-foreign unit helpers
-
-- Introduce support of cgroup-bpf programs managed (i.e. compiled,
-loaded to and unloaded from kernel) externally. Systemd is only
-responsible for attaching programs to unit cgroup hence the name
-'foreign'.
-
-Foreign BPF programs are identified by bpf program ID and attach type.
-
-systemd:
-- Gets kernel FD of BPF program;
-- Makes a unique identifier of BPF program from BPF attach type and
-program ID. Same program IDs mean the same program, i.e the same
-chunk of kernel memory. Even if the same program is passed multiple
-times, identical (program_id, attach_type) instances are collapsed
-into one;
-- Attaches programs to unit cgroup.
----
- src/core/bpf-foreign.c | 151 +++++++++++++++++++++++++++++++++++++++++
- src/core/bpf-foreign.h |  12 ++++
- src/core/meson.build   |   2 +
- src/core/unit.c        |   3 +
- src/core/unit.h        |   4 ++
- 5 files changed, 172 insertions(+)
- create mode 100644 src/core/bpf-foreign.c
- create mode 100644 src/core/bpf-foreign.h
-
-diff --git a/src/core/bpf-foreign.c b/src/core/bpf-foreign.c
-new file mode 100644
-index 0000000000..98655bda3c
---- /dev/null
-+++ b/src/core/bpf-foreign.c
-@@ -0,0 +1,151 @@
-+/* SPDX-License-Identifier: LGPL-2.1+ */
-+
-+#include "bpf-foreign.h"
-+#include "bpf-program.h"
-+#include "cgroup.h"
-+#include "memory-util.h"
-+#include "mountpoint-util.h"
-+#include "set.h"
-+
-+typedef struct BPFForeignKey BPFForeignKey;
-+struct BPFForeignKey {
-+        uint32_t prog_id;
-+        uint32_t attach_type;
-+};
-+
-+static int bpf_foreign_key_new(uint32_t prog_id,
-+                enum bpf_attach_type attach_type,
-+                BPFForeignKey **ret) {
-+        _cleanup_free_ BPFForeignKey *p = NULL;
-+
-+        assert(ret);
-+
-+        p = new(BPFForeignKey, 1);
-+        if (!p)
-+                return log_oom();
-+
-+        *p = (BPFForeignKey) {
-+                .prog_id = prog_id,
-+                .attach_type = attach_type,
-+        };
-+
-+        *ret = TAKE_PTR(p);
-+
-+        return 0;
-+}
-+
-+static int bpf_foreign_key_compare_func(const BPFForeignKey *a, const BPFForeignKey *b) {
-+        int r = CMP(a->prog_id, b->prog_id);
-+        if (r != 0)
-+                return r;
-+
-+        return CMP(a->attach_type, b->attach_type);
-+}
-+
-+static void bpf_foreign_key_hash_func(const BPFForeignKey *p, struct siphash *h) {
-+        siphash24_compress(&p->prog_id, sizeof(p->prog_id), h);
-+        siphash24_compress(&p->attach_type, sizeof(p->attach_type), h);
-+}
-+
-+DEFINE_PRIVATE_HASH_OPS_FULL(bpf_foreign_by_key_hash_ops,
-+                BPFForeignKey, bpf_foreign_key_hash_func, bpf_foreign_key_compare_func, free,
-+                BPFProgram, bpf_program_unref);
-+
-+static int attach_programs(Unit *u, const char *path, Hashmap* foreign_by_key, uint32_t attach_flags) {
-+        const BPFForeignKey *key;
-+        BPFProgram *prog;
-+        int r;
-+
-+        assert(u);
-+
-+        HASHMAP_FOREACH_KEY(prog, key, foreign_by_key) {
-+                r = bpf_program_cgroup_attach(prog, key->attach_type, path, attach_flags);
-+                if (r < 0)
-+                        return log_unit_error_errno(u, r, "Attaching foreign BPF program to cgroup %s failed: %m", path);
-+        }
-+
-+        return 0;
-+}
-+
-+/*
-+ * Prepare foreign BPF program for installation:
-+ * - Load the program from BPF filesystem to the kernel;
-+ * - Store program FD identified by program ID and attach type in the unit.
-+ */
-+static int bpf_foreign_prepare(
-+                Unit *u,
-+                enum bpf_attach_type attach_type,
-+                const char *bpffs_path) {
-+        _cleanup_(bpf_program_unrefp) BPFProgram *prog = NULL;
-+        _cleanup_free_ BPFForeignKey *key = NULL;
-+        uint32_t prog_id;
-+        int r;
-+
-+        assert(u);
-+        assert(bpffs_path);
-+
-+        r = bpf_program_new_from_bpffs_path(bpffs_path, &prog);
-+        if (r < 0)
-+                return log_unit_error_errno(u, r, "Failed to create foreign BPFProgram: %m");
-+
-+        r = bpf_program_get_id_by_fd(prog->kernel_fd, &prog_id);
-+        if (r < 0)
-+                return log_unit_error_errno(u, r, "Failed to get BPF program id by fd: %m");
-+
-+        r = bpf_foreign_key_new(prog_id, attach_type, &key);
-+        if (r < 0)
-+                return log_unit_error_errno(u, r,
-+                                "Failed to create foreign BPF program key from path '%s': %m", bpffs_path);
-+
-+        r = hashmap_ensure_put(&u->bpf_foreign_by_key, &bpf_foreign_by_key_hash_ops, key, prog);
-+        if (r == -EEXIST) {
-+                log_unit_warning_errno(u, r, "Foreign BPF program already exists, ignoring: %m");
-+                return 0;
-+        }
-+        if (r < 0)
-+                return log_unit_error_errno(u, r, "Failed to put foreign BPFProgram into map: %m");
-+
-+        TAKE_PTR(key);
-+        TAKE_PTR(prog);
-+
-+        return 0;
-+}
-+
-+int bpf_foreign_supported(void) {
-+        int r;
-+
-+        r = cg_all_unified();
-+        if (r <= 0)
-+                return r;
-+
-+        return path_is_mount_point("/sys/fs/bpf", NULL, 0);
-+}
-+
-+int bpf_foreign_install(Unit *u) {
-+        _cleanup_free_ char *cgroup_path = NULL;
-+        CGroupBPFForeignProgram *p;
-+        CGroupContext *cc;
-+        int r;
-+
-+        assert(u);
-+
-+        cc = unit_get_cgroup_context(u);
-+        if (!cc)
-+                return 0;
-+
-+        r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, NULL, &cgroup_path);
-+        if (r < 0)
-+                return log_unit_error_errno(u, r, "Failed to get cgroup path: %m");
-+
-+        LIST_FOREACH(programs, p, cc->bpf_foreign_programs) {
-+                r = bpf_foreign_prepare(u, p->attach_type, p->bpffs_path);
-+                if (r < 0)
-+                        return log_unit_error_errno(u, r, "Failed to prepare foreign BPF hashmap: %m");
-+        }
-+
-+        r = attach_programs(u, cgroup_path, u->bpf_foreign_by_key, BPF_F_ALLOW_MULTI);
-+        if (r < 0)
-+                  return log_unit_error_errno(u, r, "Failed to install foreign BPF programs: %m");
-+
-+        return 0;
-+}
-diff --git a/src/core/bpf-foreign.h b/src/core/bpf-foreign.h
-new file mode 100644
-index 0000000000..7704986e3e
---- /dev/null
-+++ b/src/core/bpf-foreign.h
-@@ -0,0 +1,12 @@
-+/* SPDX-License-Identifier: LGPL-2.1+ */
-+
-+#pragma once
-+
-+#include "unit.h"
-+
-+int bpf_foreign_supported(void);
-+/*
-+ * Attach cgroup-bpf programs foreign to systemd, i.e. loaded to the kernel by an entity
-+ * external to systemd.
-+ */
-+int bpf_foreign_install(Unit *u);
-diff --git a/src/core/meson.build b/src/core/meson.build
-index a389c906b3..a1294f3a72 100644
---- a/src/core/meson.build
-+++ b/src/core/meson.build
-@@ -11,6 +11,8 @@ libcore_sources = '''
-         bpf-devices.h
-         bpf-firewall.c
-         bpf-firewall.h
-+        bpf-foreign.c
-+        bpf-foreign.h
-         cgroup.c
-         cgroup.h
-         core-varlink.c
-diff --git a/src/core/unit.c b/src/core/unit.c
-index c212f1043d..b7141b29af 100644
---- a/src/core/unit.c
-+++ b/src/core/unit.c
-@@ -11,6 +11,7 @@
- #include "all-units.h"
- #include "alloc-util.h"
- #include "bpf-firewall.h"
-+#include "bpf-foreign.h"
- #include "bus-common-errors.h"
- #include "bus-util.h"
- #include "cgroup-setup.h"
-@@ -723,6 +724,8 @@ Unit* unit_free(Unit *u) {
-         set_free(u->ip_bpf_custom_ingress_installed);
-         set_free(u->ip_bpf_custom_egress_installed);
- 
-+        hashmap_free(u->bpf_foreign_by_key);
-+
-         bpf_program_unref(u->bpf_device_control_installed);
- 
-         condition_free_list(u->conditions);
-diff --git a/src/core/unit.h b/src/core/unit.h
-index 264431d04d..6de529af92 100644
---- a/src/core/unit.h
-+++ b/src/core/unit.h
-@@ -305,6 +305,10 @@ typedef struct Unit {
-         Set *ip_bpf_custom_egress;
-         Set *ip_bpf_custom_egress_installed;
- 
-+        /* BPF programs managed (e.g. loaded to kernel) by an entity external to systemd,
-+         * attached to unit cgroup by provided program fd and attach type. */
-+        Hashmap *bpf_foreign_by_key;
-+
-         uint64_t ip_accounting_extra[_CGROUP_IP_ACCOUNTING_METRIC_MAX];
- 
-         /* Low-priority event source which is used to remove watched PIDs that have gone away, and subscribe to any new
--- 
-2.30.2
-
-
-From 4d84c1af52683f7272e021fb4c6e68148d64e33b Mon Sep 17 00:00:00 2001
-From: Julia Kartseva <hex@fb.com>
-Date: Thu, 4 Feb 2021 00:03:08 -0800
-Subject: [PATCH 5/9] core: add bpf-foreign cgroup mask and harness
-
-Add CGROUP_MASK_BPF_FOREIGN to CGROUP_MASK_BPF and standard cgroup
-context harness.
----
- src/basic/cgroup-util.c     |  1 +
- src/basic/cgroup-util.h     |  4 +++-
- src/core/cgroup.c           | 29 +++++++++++++++++++++++++++++
- src/test/test-cgroup-mask.c |  2 +-
- 4 files changed, 34 insertions(+), 2 deletions(-)
-
-diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c
-index 8dd3f8cd95..743fb0afe1 100644
---- a/src/basic/cgroup-util.c
-+++ b/src/basic/cgroup-util.c
-@@ -2162,6 +2162,7 @@ static const char *const cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = {
-         [CGROUP_CONTROLLER_PIDS] = "pids",
-         [CGROUP_CONTROLLER_BPF_FIREWALL] = "bpf-firewall",
-         [CGROUP_CONTROLLER_BPF_DEVICES] = "bpf-devices",
-+        [CGROUP_CONTROLLER_BPF_FOREIGN] = "bpf-foreign",
- };
- 
- DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController);
-diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h
-index f79e384147..8894fd9b0a 100644
---- a/src/basic/cgroup-util.h
-+++ b/src/basic/cgroup-util.h
-@@ -30,6 +30,7 @@ typedef enum CGroupController {
-         /* BPF-based pseudo-controllers, v2 only */
-         CGROUP_CONTROLLER_BPF_FIREWALL,
-         CGROUP_CONTROLLER_BPF_DEVICES,
-+        CGROUP_CONTROLLER_BPF_FOREIGN,
- 
-         _CGROUP_CONTROLLER_MAX,
-         _CGROUP_CONTROLLER_INVALID = -EINVAL,
-@@ -49,6 +50,7 @@ typedef enum CGroupMask {
-         CGROUP_MASK_PIDS = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_PIDS),
-         CGROUP_MASK_BPF_FIREWALL = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_FIREWALL),
-         CGROUP_MASK_BPF_DEVICES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_DEVICES),
-+        CGROUP_MASK_BPF_FOREIGN = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_FOREIGN),
- 
-         /* All real cgroup v1 controllers */
-         CGROUP_MASK_V1 = CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT|CGROUP_MASK_BLKIO|CGROUP_MASK_MEMORY|CGROUP_MASK_DEVICES|CGROUP_MASK_PIDS,
-@@ -57,7 +59,7 @@ typedef enum CGroupMask {
-         CGROUP_MASK_V2 = CGROUP_MASK_CPU|CGROUP_MASK_CPUSET|CGROUP_MASK_IO|CGROUP_MASK_MEMORY|CGROUP_MASK_PIDS,
- 
-         /* All cgroup v2 BPF pseudo-controllers */
--        CGROUP_MASK_BPF = CGROUP_MASK_BPF_FIREWALL|CGROUP_MASK_BPF_DEVICES,
-+        CGROUP_MASK_BPF = CGROUP_MASK_BPF_FIREWALL|CGROUP_MASK_BPF_DEVICES|CGROUP_MASK_BPF_FOREIGN,
- 
-         _CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1
- } CGroupMask;
-diff --git a/src/core/cgroup.c b/src/core/cgroup.c
-index a0af50518d..f3d5d89339 100644
---- a/src/core/cgroup.c
-+++ b/src/core/cgroup.c
-@@ -8,6 +8,7 @@
- #include "blockdev-util.h"
- #include "bpf-devices.h"
- #include "bpf-firewall.h"
-+#include "bpf-foreign.h"
- #include "btrfs-util.h"
- #include "bus-error.h"
- #include "cgroup-setup.h"
-@@ -1160,6 +1161,12 @@ static void set_io_weight(Unit *u, const char *controller, uint64_t weight) {
-         (void) set_attribute_and_warn(u, controller, p, buf);
- }
- 
-+static void cgroup_apply_bpf_foreign_program(Unit *u) {
-+        assert(u);
-+
-+        (void) bpf_foreign_install(u);
-+}
-+
- static void cgroup_context_apply(
-                 Unit *u,
-                 CGroupMask apply_mask,
-@@ -1473,6 +1480,9 @@ static void cgroup_context_apply(
- 
-         if (apply_mask & CGROUP_MASK_BPF_FIREWALL)
-                 cgroup_apply_firewall(u);
-+
-+        if (apply_mask & CGROUP_MASK_BPF_FOREIGN)
-+                cgroup_apply_bpf_foreign_program(u);
- }
- 
- static bool unit_get_needs_bpf_firewall(Unit *u) {
-@@ -1505,6 +1515,17 @@ static bool unit_get_needs_bpf_firewall(Unit *u) {
-         return false;
- }
- 
-+static bool unit_get_needs_bpf_foreign_program(Unit *u) {
-+        CGroupContext *c;
-+        assert(u);
-+
-+        c = unit_get_cgroup_context(u);
-+        if (!c)
-+                return false;
-+
-+        return !LIST_IS_EMPTY(c->bpf_foreign_programs);
-+}
-+
- static CGroupMask unit_get_cgroup_mask(Unit *u) {
-         CGroupMask mask = 0;
-         CGroupContext *c;
-@@ -1556,6 +1577,9 @@ static CGroupMask unit_get_bpf_mask(Unit *u) {
-         if (unit_get_needs_bpf_firewall(u))
-                 mask |= CGROUP_MASK_BPF_FIREWALL;
- 
-+        if (unit_get_needs_bpf_foreign_program(u))
-+                mask |= CGROUP_MASK_BPF_FOREIGN;
-+
-         return mask;
- }
- 
-@@ -3032,6 +3056,11 @@ static int cg_bpf_mask_supported(CGroupMask *ret) {
-         if (r > 0)
-                 mask |= CGROUP_MASK_BPF_DEVICES;
- 
-+        /* BPF pinned prog */
-+        r = bpf_foreign_supported();
-+        if (r > 0)
-+                mask |= CGROUP_MASK_BPF_FOREIGN;
-+
-         *ret = mask;
-         return 0;
- }
-diff --git a/src/test/test-cgroup-mask.c b/src/test/test-cgroup-mask.c
-index b53e327c63..d721946f71 100644
---- a/src/test/test-cgroup-mask.c
-+++ b/src/test/test-cgroup-mask.c
-@@ -140,7 +140,7 @@ static void test_cg_mask_to_string_one(CGroupMask mask, const char *t) {
- 
- static void test_cg_mask_to_string(void) {
-         test_cg_mask_to_string_one(0, NULL);
--        test_cg_mask_to_string_one(_CGROUP_MASK_ALL, "cpu cpuacct cpuset io blkio memory devices pids bpf-firewall bpf-devices");
-+        test_cg_mask_to_string_one(_CGROUP_MASK_ALL, "cpu cpuacct cpuset io blkio memory devices pids bpf-firewall bpf-devices bpf-foreign");
-         test_cg_mask_to_string_one(CGROUP_MASK_CPU, "cpu");
-         test_cg_mask_to_string_one(CGROUP_MASK_CPUACCT, "cpuacct");
-         test_cg_mask_to_string_one(CGROUP_MASK_CPUSET, "cpuset");
--- 
-2.30.2
-
-
-From d1f0204f1ca7bf7b562a2fe1c90decbb5e04cf31 Mon Sep 17 00:00:00 2001
-From: Julia Kartseva <hex@fb.com>
-Date: Thu, 4 Feb 2021 00:04:19 -0800
-Subject: [PATCH 6/9] core: add bpf-foreign to fragment parser
-
-- Parse a string for bpf attach type
-- Simplify bpffs path
-- Add foreign bpf program to cgroup context
----
- src/core/load-fragment-gperf.gperf.m4 |  3 +-
- src/core/load-fragment.c              | 59 +++++++++++++++++++++++++++
- src/core/load-fragment.h              |  1 +
- 3 files changed, 62 insertions(+), 1 deletion(-)
-
-diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
-index 21bbcffe41..bbb79a12ca 100644
---- a/src/core/load-fragment-gperf.gperf.m4
-+++ b/src/core/load-fragment-gperf.gperf.m4
-@@ -234,7 +234,8 @@ $1.ManagedOOMSwap,                       config_parse_managed_oom_mode,
- $1.ManagedOOMMemoryPressure,             config_parse_managed_oom_mode,               0,                                  offsetof($1, cgroup_context.moom_mem_pressure)
- $1.ManagedOOMMemoryPressureLimit,        config_parse_managed_oom_mem_pressure_limit, 0,                                  offsetof($1, cgroup_context.moom_mem_pressure_limit)
- $1.ManagedOOMPreference,                 config_parse_managed_oom_preference,         0,                                  offsetof($1, cgroup_context.moom_preference)
--$1.NetClass,                             config_parse_warn_compat,                    DISABLED_LEGACY,                    0'
-+$1.NetClass,                             config_parse_warn_compat,                    DISABLED_LEGACY,                    0
-+$1.BPFProgram,                           config_parse_bpf_foreign_program,            0,                                  offsetof($1, cgroup_context)'
- )m4_dnl
- Unit.Description,                        config_parse_unit_string_printf,             0,                                  offsetof(Unit, description)
- Unit.Documentation,                      config_parse_documentation,                  0,                                  offsetof(Unit, documentation)
-diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
-index 6da623dbda..fb80acbc02 100644
---- a/src/core/load-fragment.c
-+++ b/src/core/load-fragment.c
-@@ -19,6 +19,7 @@
- #include "alloc-util.h"
- #include "all-units.h"
- #include "bpf-firewall.h"
-+#include "bpf-program.h"
- #include "bus-error.h"
- #include "bus-internal.h"
- #include "bus-util.h"
-@@ -5580,6 +5581,64 @@ int config_parse_ip_filter_bpf_progs(
-         return 0;
- }
- 
-+int config_parse_bpf_foreign_program(
-+                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_free_ char *resolved = NULL, *word = NULL;
-+        CGroupContext *c = data;
-+        Unit *u = userdata;
-+        int attach_type, r;
-+
-+        assert(filename);
-+        assert(lvalue);
-+        assert(rvalue);
-+
-+        if (isempty(rvalue)) {
-+                while (c->bpf_foreign_programs)
-+                        cgroup_context_remove_bpf_foreign_program(c, c->bpf_foreign_programs);
-+
-+                return 0;
-+        }
-+
-+        r = extract_first_word(&rvalue, &word, ":", 0);
-+        if (r == -ENOMEM)
-+                return log_oom();
-+        if (r < 0) {
-+                log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse foreign BPF program, ignoring: %s", rvalue);
-+                return 0;
-+        }
-+
-+        attach_type = bpf_cgroup_attach_type_from_string(word);
-+        if (attach_type < 0) {
-+                log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown BPF attach type=%s, ignoring: %s", word, rvalue);
-+                return 0;
-+        }
-+
-+        r = unit_full_printf(u, rvalue, &resolved);
-+        if (r < 0) {
-+                log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", rvalue);
-+                return 0;
-+        }
-+
-+        r = path_simplify_and_warn(resolved, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue);
-+        if (r < 0)
-+                return 0;
-+
-+        r = cgroup_add_bpf_foreign_program(c, attach_type, resolved);
-+        if (r < 0)
-+                return log_error_errno(r, "Failed to add foreign BPF program to cgroup context: %m");
-+
-+        return 0;
-+}
-+
- static int merge_by_names(Unit **u, Set *names, const char *id) {
-         char *k;
-         int r;
-diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
-index b8a6d5fead..eebeda5747 100644
---- a/src/core/load-fragment.h
-+++ b/src/core/load-fragment.h
-@@ -139,6 +139,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_swap_priority);
- CONFIG_PARSER_PROTOTYPE(config_parse_mount_images);
- CONFIG_PARSER_PROTOTYPE(config_parse_socket_timestamping);
- CONFIG_PARSER_PROTOTYPE(config_parse_extension_images);
-+CONFIG_PARSER_PROTOTYPE(config_parse_bpf_foreign_program);
- 
- /* gperf prototypes */
- const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
--- 
-2.30.2
-
-
-From 75d16dfd0b2fc813534081d3bdf1a957360078e5 Mon Sep 17 00:00:00 2001
-From: Julia Kartseva <hex@fb.com>
-Date: Tue, 3 Sep 2019 19:08:13 -0700
-Subject: [PATCH 7/9] tests: add unit file tests for BPFProgram=
-
-- Pin trivial bpf programs to bpf filesystem, compose BPFProgram= option
-string and pass it to a unit. Programs store `0` in r0 BPF register for
-denying action, e.g. drop a packet.
-- Load trivial BPF programs
-- Test is skipped if not run under root or if can not lock enough
-memory.
-- For egress and ingress hooks, test BPFProgram= option along with
-with IP{Egress|Ingress}FilterPath=, expected result should not depend on
-which rule is executed first.
-Expected results for BPF_CGROUP_INET_INGRESS:
-5 packets transmitted, 0 received, 100% packet loss, time 89ms
-
-For BPF_CGROUP_INET_SOCK_CREATE:
-ping: socket: Operation not permitted
----
- src/test/meson.build                 |   6 +
- src/test/test-bpf-foreign-programs.c | 332 +++++++++++++++++++++++++++
- 2 files changed, 338 insertions(+)
- create mode 100644 src/test/test-bpf-foreign-programs.c
-
-diff --git a/src/test/meson.build b/src/test/meson.build
-index c752e995f6..6349034aeb 100644
---- a/src/test/meson.build
-+++ b/src/test/meson.build
-@@ -335,6 +335,12 @@ tests += [
-           libblkid],
-          core_includes],
- 
-+        [['src/test/test-bpf-foreign-programs.c'],
-+         [libcore,
-+          libshared],
-+         [],
-+         core_includes],
-+
-         [['src/test/test-watch-pid.c'],
-          [libcore,
-           libshared],
-diff --git a/src/test/test-bpf-foreign-programs.c b/src/test/test-bpf-foreign-programs.c
-new file mode 100644
-index 0000000000..e703924077
---- /dev/null
-+++ b/src/test/test-bpf-foreign-programs.c
-@@ -0,0 +1,332 @@
-+/* SPDX-License-Identifier: LGPL-2.1+ */
-+
-+#include <fcntl.h>
-+#include <linux/bpf_insn.h>
-+#include <string.h>
-+#include <sys/mman.h>
-+#include <unistd.h>
-+
-+#include "bpf-foreign.h"
-+#include "load-fragment.h"
-+#include "manager.h"
-+#include "process-util.h"
-+#include "rlimit-util.h"
-+#include "rm-rf.h"
-+#include "service.h"
-+#include "tests.h"
-+#include "unit.h"
-+#include "virt.h"
-+
-+struct Test {
-+        const char *option_name;
-+        enum bpf_prog_type prog_type;
-+        enum bpf_attach_type attach_type;
-+        const char *bpffs_path;
-+};
-+
-+typedef struct Test Test;
-+
-+#define BPFFS_PATH(prog_suffix) ("/sys/fs/bpf/test-bpf-foreing-" # prog_suffix)
-+static const Test single_prog[] = {
-+        {
-+                .option_name = "BPFProgram",
-+                .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
-+                .attach_type = BPF_CGROUP_INET_INGRESS,
-+                .bpffs_path = BPFFS_PATH("trivial-skb"),
-+        },
-+};
-+static const Test path_split_test[] = {
-+        {
-+                .option_name = "BPFProgram",
-+                .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
-+                .attach_type = BPF_CGROUP_INET_INGRESS,
-+                .bpffs_path = BPFFS_PATH("path:split:test"),
-+        },
-+};
-+
-+static const Test same_prog_same_hook[] = {
-+        {
-+                .option_name = "BPFProgram",
-+                .prog_type = BPF_PROG_TYPE_CGROUP_SOCK,
-+                .attach_type = BPF_CGROUP_INET_SOCK_CREATE,
-+                .bpffs_path = BPFFS_PATH("trivial-sock"),
-+        },
-+        {
-+                .option_name = "BPFProgram",
-+                .prog_type = BPF_PROG_TYPE_CGROUP_SOCK,
-+                .attach_type = BPF_CGROUP_INET_SOCK_CREATE,
-+                .bpffs_path = BPFFS_PATH("trivial-sock"),
-+        }
-+};
-+
-+static const Test multi_prog_same_hook[] = {
-+        {
-+                .option_name = "BPFProgram",
-+                .prog_type = BPF_PROG_TYPE_CGROUP_SOCK,
-+                .attach_type = BPF_CGROUP_INET_SOCK_CREATE,
-+                .bpffs_path = BPFFS_PATH("trivial-sock-0"),
-+        },
-+        {
-+                .option_name = "BPFProgram",
-+                .prog_type = BPF_PROG_TYPE_CGROUP_SOCK,
-+                .attach_type = BPF_CGROUP_INET_SOCK_CREATE,
-+                .bpffs_path = BPFFS_PATH("trivial-sock-1"),
-+        }
-+};
-+
-+static const Test same_prog_multi_hook[] = {
-+        {
-+                .option_name = "BPFProgram",
-+                .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
-+                .attach_type = BPF_CGROUP_INET_INGRESS,
-+                .bpffs_path = BPFFS_PATH("trivial-skb"),
-+        },
-+        {
-+                .option_name = "BPFProgram",
-+                .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
-+                .attach_type = BPF_CGROUP_INET_EGRESS,
-+                .bpffs_path = BPFFS_PATH("trivial-skb"),
-+        }
-+};
-+
-+static const Test same_prog_multi_option_0[] = {
-+        {
-+                .option_name = "BPFProgram",
-+                .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
-+                .attach_type = BPF_CGROUP_INET_INGRESS,
-+                .bpffs_path = BPFFS_PATH("trivial-skb"),
-+        },
-+        {
-+                .option_name = "IPIngressFilterPath",
-+                .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
-+                .attach_type = BPF_CGROUP_INET_INGRESS,
-+                .bpffs_path = BPFFS_PATH("trivial-skb"),
-+        }
-+};
-+
-+static const Test same_prog_multi_option_1[] = {
-+        {
-+                .option_name = "IPEgressFilterPath",
-+                .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
-+                .attach_type = BPF_CGROUP_INET_EGRESS,
-+                .bpffs_path = BPFFS_PATH("trivial-skb"),
-+        },
-+        {
-+                .option_name = "BPFProgram",
-+                .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
-+                .attach_type = BPF_CGROUP_INET_EGRESS,
-+                .bpffs_path = BPFFS_PATH("trivial-skb"),
-+        }
-+};
-+#undef BPFFS_PATH
-+
-+static int bpf_foreign_test_to_string(enum bpf_attach_type attach_type, const char *bpffs_path, char **ret_str) {
-+        const char *s = NULL;
-+
-+        assert_se(bpffs_path);
-+        assert_se(ret_str);
-+
-+        assert_se(s = bpf_cgroup_attach_type_to_string(attach_type));
-+        assert_se(*ret_str = strjoin(s, ":", bpffs_path));
-+
-+        return 0;
-+}
-+
-+static char **unlink_paths_and_free(char **paths) {
-+        char **i;
-+
-+        STRV_FOREACH(i, paths)
-+                (void) unlink(*i);
-+
-+        return strv_free(paths);
-+}
-+
-+DEFINE_TRIVIAL_CLEANUP_FUNC(char **, unlink_paths_and_free);
-+
-+static int pin_programs(Unit *u, CGroupContext *cc, const Test *test_suite, size_t test_suite_size, char ***paths_ret) {
-+        _cleanup_(unlink_paths_and_freep) char **bpffs_paths = NULL;
-+        static const struct bpf_insn trivial[] = {
-+                BPF_MOV64_IMM(BPF_REG_0, 0),
-+                BPF_EXIT_INSN()
-+        };
-+        char log_buf[0xffff];
-+        int r;
-+
-+        assert_se(paths_ret);
-+
-+        for (size_t i = 0; i < test_suite_size; i++) {
-+                _cleanup_(bpf_program_unrefp) BPFProgram *prog = NULL;
-+                _cleanup_free_ char *str = NULL;
-+
-+                r = bpf_foreign_test_to_string(test_suite[i].attach_type, test_suite[i].bpffs_path, &str);
-+                if (r < 0)
-+                        return log_error_errno(r, "Failed to convert program to string");
-+
-+                r = bpf_program_new(test_suite[i].prog_type, &prog);
-+                if (r < 0)
-+                        return log_error_errno(r, "Failed to create program '%s'", str);
-+
-+                r = bpf_program_add_instructions(prog, trivial, ELEMENTSOF(trivial));
-+                if (r < 0)
-+                        return log_error_errno(r, "Failed to add trivial instructions for '%s'", str);
-+
-+                r = bpf_program_load_kernel(prog, log_buf, ELEMENTSOF(log_buf));
-+                if (r < 0)
-+                        return log_error_errno(r, "Failed to load BPF program '%s'", str);
-+
-+                if (strv_contains(bpffs_paths, test_suite[i].bpffs_path))
-+                        continue;
-+
-+                r = strv_extend(&bpffs_paths, test_suite[i].bpffs_path);
-+                if (r < 0)
-+                        return log_error_errno(r, "Failed to put path into a vector: %m");
-+
-+                r = bpf_program_pin(prog->kernel_fd, test_suite[i].bpffs_path);
-+                if (r < 0)
-+                        return log_error_errno(r, "Failed to pin BPF program '%s'", str);
-+        }
-+
-+        *paths_ret = TAKE_PTR(bpffs_paths);
-+        return 0;
-+}
-+
-+static int test_bpf_cgroup_programs(Manager *m, const char *unit_name, const Test *test_suite, size_t test_suite_size) {
-+        _cleanup_(unlink_paths_and_freep) char **bpffs_paths = NULL;
-+        _cleanup_(unit_freep) Unit *u = NULL;
-+        CGroupContext *cc = NULL;
-+        int cld_code, r;
-+
-+        assert_se(u = unit_new(m, sizeof(Service)));
-+        assert_se(unit_add_name(u, unit_name) == 0);
-+        assert_se(cc = unit_get_cgroup_context(u));
-+
-+        r = pin_programs(u, cc, test_suite, test_suite_size, &bpffs_paths);
-+        if (r < 0)
-+                return log_error_errno(r, "Failed to pin programs: %m");
-+
-+        for (size_t i = 0; i < test_suite_size; i++) {
-+                if (streq(test_suite[i].option_name, "BPFProgram")) {
-+                        _cleanup_free_ char *option = NULL;
-+                        r = bpf_foreign_test_to_string(test_suite[i].attach_type, test_suite[i].bpffs_path, &option);
-+                        if (r < 0)
-+                                return log_error_errno(r, "Failed to compose option string: %m");
-+                        r = config_parse_bpf_foreign_program(
-+                                        u->id, "filename", 1, "Service", 1, test_suite[i].option_name, 0, option, cc, u);
-+
-+                        if (r < 0)
-+                                return log_error_errno(r, "Failed to parse option string '%s': %m", option);
-+                } else if (STR_IN_SET(test_suite[i].option_name, "IPIngressFilterPath", "IPEgressFilterPath")) {
-+                        const char *option = test_suite[i].bpffs_path;
-+                        void *paths = NULL;
-+
-+                        if (streq(test_suite[i].option_name, "IPIngressFilterPath"))
-+                                paths = &cc->ip_filters_ingress;
-+                        else
-+                                paths = &cc->ip_filters_egress;
-+
-+                        r = config_parse_ip_filter_bpf_progs(
-+                                        u->id, "filename", 1, "Service", 1, test_suite[i].option_name, 0, option, paths, u);
-+                        if (r < 0)
-+                                return log_error_errno(r, "Failed to parse option string '%s': %m", option);
-+                }
-+        }
-+
-+        r = config_parse_exec(
-+                        u->id,
-+                        "filename",
-+                        1,
-+                        "Service",
-+                        1,
-+                        "ExecStart",
-+                        SERVICE_EXEC_START,
-+                        "-/bin/ping -c 5 127.0.0.1 -W 1",
-+                        SERVICE(u)->exec_command,
-+                        u);
-+        if (r < 0)
-+                return log_error_errno(r, "Failed to parse ExecStart");
-+
-+        SERVICE(u)->type = SERVICE_ONESHOT;
-+        u->load_state = UNIT_LOADED;
-+
-+        r = unit_start(u);
-+        if (r < 0)
-+                return log_error_errno(r, "Unit start failed %m");
-+
-+        while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) {
-+                r = sd_event_run(m->event, UINT64_MAX);
-+                if (r < 0)
-+                        return log_error_errno(errno, "Event run failed %m");
-+        }
-+
-+        cld_code = SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code;
-+        if (cld_code != CLD_EXITED)
-+                return log_error_errno(SYNTHETIC_ERRNO(EBUSY),
-+                                "ExecStart didn't exited, code='%s'", sigchld_code_to_string(cld_code));
-+
-+        if (SERVICE(u)->state != SERVICE_DEAD)
-+                return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Service is not dead");
-+
-+        return r;
-+}
-+
-+int main(int argc, char *argv[]) {
-+        _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
-+        _cleanup_(manager_freep) Manager *m = NULL;
-+        _cleanup_free_ char *unit_dir = NULL;
-+        struct rlimit rl;
-+        int r;
-+
-+        test_setup_logging(LOG_DEBUG);
-+
-+        if (detect_container() > 0)
-+                return log_tests_skipped("test-bpf fails inside LXC and Docker containers: https://github.com/systemd/systemd/issues/9666");
-+
-+        if (getuid() != 0)
-+                return log_tests_skipped("not running as root");
-+
-+        assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0);
-+        rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE);
-+        (void) setrlimit_closest(RLIMIT_MEMLOCK, &rl);
-+
-+        if (!can_memlock())
-+                return log_tests_skipped("Can't use mlock(), skipping.");
-+
-+        r = cg_all_unified();
-+        if (r <= 0)
-+                return log_tests_skipped_errno(r, "Unified hierarchy is required, skipping.");
-+
-+        r = enter_cgroup_subroot(NULL);
-+        if (r == -ENOMEDIUM)
-+                return log_tests_skipped("cgroupfs not available");
-+
-+        assert_se(get_testdata_dir("units", &unit_dir) >= 0);
-+        assert_se(set_unit_path(unit_dir) >= 0);
-+        assert_se(runtime_dir = setup_fake_runtime_dir());
-+
-+        assert_se(manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0);
-+        assert_se(manager_startup(m, NULL, NULL) >= 0);
-+
-+        assert_se(test_bpf_cgroup_programs(m,
-+                                "single_prog.service", single_prog, ELEMENTSOF(single_prog)) >= 0);
-+        assert_se(test_bpf_cgroup_programs(m,
-+                                "multi_prog_same_hook.service",
-+                                multi_prog_same_hook, ELEMENTSOF(multi_prog_same_hook)) >= 0);
-+        assert_se(test_bpf_cgroup_programs(m,
-+                                "same_prog_multi_hook.service",
-+                                same_prog_multi_hook, ELEMENTSOF(same_prog_multi_hook)) >= 0);
-+        assert_se(test_bpf_cgroup_programs(m,
-+                                "same_prog_multi_option_0.service",
-+                                same_prog_multi_option_0, ELEMENTSOF(same_prog_multi_option_0)) >= 0);
-+        assert_se(test_bpf_cgroup_programs(m,
-+                                "same_prog_multi_option_1.service",
-+                                same_prog_multi_option_1, ELEMENTSOF(same_prog_multi_option_1)) >= 0);
-+        assert_se(test_bpf_cgroup_programs(m,
-+                                "same_prog_same_hook.service",
-+                                same_prog_same_hook,
-+                                ELEMENTSOF(same_prog_same_hook)) >= 0);
-+        assert_se(test_bpf_cgroup_programs(m,
-+                                "path_split_test.service",
-+                                path_split_test,
-+                                ELEMENTSOF(path_split_test)) >= 0);
-+        return 0;
-+}
--- 
-2.30.2
-
-
-From faebec942b34c62d8dce512839c3a43be6844394 Mon Sep 17 00:00:00 2001
-From: Julia Kartseva <hex@fb.com>
-Date: Tue, 8 Dec 2020 22:06:56 -0800
-Subject: [PATCH 8/9] man: add BPFProgram= documentation
-
----
- man/systemd.resource-control.xml | 52 ++++++++++++++++++++++++++++++++
- 1 file changed, 52 insertions(+)
-
-diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml
-index 1bc45a9f00..a2d01f7afb 100644
---- a/man/systemd.resource-control.xml
-+++ b/man/systemd.resource-control.xml
-@@ -696,6 +696,12 @@
-           <para>If these settings are used multiple times in the same unit all the specified programs are attached. If an
-           empty string is assigned to these settings the program list is reset and all previous specified programs ignored.</para>
- 
-+          <para>If the path <replaceable>BPF_FS_PROGRAM_PATH</replaceable> in <varname>IPIngressFilterPath=</varname> assignment
-+          is already being handled by <varname>BPFProgram=</varname> ingress hook, e.g.
-+          <varname>BPFProgram=</varname><constant>ingress</constant>:<replaceable>BPF_FS_PROGRAM_PATH</replaceable>,
-+          the assignment will be still considered valid and the program will be attached to a cgroup. Same for
-+          <varname>IPEgressFilterPath=</varname> path and <constant>egress</constant> hook.</para>
-+
-           <para>Note that for socket-activated services, the IP filter programs configured on the socket unit apply to
-           all sockets associated with it directly, but not to any sockets created by the ultimately activated services
-           for it. Conversely, the IP filter programs configured for the service are not applied to any sockets passed into
-@@ -710,6 +716,52 @@
-         </listitem>
-       </varlistentry>
- 
-+      <varlistentry>
-+        <term><varname>BPFProgram=<replaceable>type</replaceable><constant>:</constant><replaceable>program-path</replaceable></varname></term>
-+        <listitem>
-+          <para>Add a custom cgroup BPF program.</para>
-+
-+          <para><varname>BPFProgram=</varname> allows attaching BPF hooks to the cgroup of a systemd unit.
-+          (This generalizes the functionality exposed via <varname>IPEgressFilterPath=</varname> for egress and
-+          <varname>IPIngressFilterPath=</varname> for ingress.)
-+          Cgroup-bpf hooks in the form of BPF programs loaded to the BPF filesystem are attached with cgroup-bpf attach
-+          flags determined by the unit. For details about attachment types and flags see <ulink
-+          url="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/include/uapi/linux/bpf.h"/>.
-+          For general BPF documentation please refer to <ulink url="https://www.kernel.org/doc/html/latest/bpf/index.html"/>.</para>
-+
-+          <para>The specification of BPF program consists of a <replaceable>type</replaceable> followed by a
-+          <replaceable>program-path</replaceable> with <literal>:</literal> as the separator:
-+          <replaceable>type</replaceable><constant>:</constant><replaceable>program-path</replaceable>.</para>
-+
-+          <para><replaceable>type</replaceable> is the string name of BPF attach type also used in
-+          <command>bpftool</command>. <replaceable>type</replaceable> can be one of <constant>egress</constant>,
-+          <constant>ingress</constant>, <constant>sock_create</constant>, <constant>sock_ops</constant>,
-+          <constant>device</constant>, <constant>bind4</constant>, <constant>bind6</constant>,
-+          <constant>connect4</constant>, <constant>connect6</constant>, <constant>post_bind4</constant>,
-+          <constant>post_bind6</constant>, <constant>sendmsg4</constant>, <constant>sendmsg6</constant>,
-+          <constant>sysctl</constant>, <constant>recvmsg4</constant>, <constant>recvmsg6</constant>,
-+          <constant>getsockopt</constant>, <constant>setsockopt</constant>.</para>
-+
-+          <para>Setting <varname>BPFProgram=</varname> to an empty value makes previous assignments ineffective.</para>
-+          <para>Multiple assignments of the same <replaceable>type</replaceable>:<replaceable>program-path</replaceable>
-+          value have the same effect as a single assignment: the program with the path <replaceable>program-path</replaceable>
-+          will be attached to cgroup hook <replaceable>type</replaceable> just once.</para>
-+          <para>If BPF <constant>egress</constant> pinned to <replaceable>program-path</replaceable> path is already being
-+          handled by <varname>IPEgressFilterPath=</varname>, <varname>BPFProgram=</varname>
-+          assignment will be considered valid and <varname>BPFProgram=</varname> will be attached to a cgroup.
-+          Similarly for <constant>ingress</constant> hook and <varname>IPIngressFilterPath=</varname> assignment.</para>
-+
-+          <para>BPF programs passed with <varname>BPFProgram=</varname> are attached to the cgroup of a unit with BPF
-+          attach flag <constant>multi</constant>, that allows further attachments of the same
-+          <replaceable>type</replaceable> within cgroup hierarchy topped by the unit cgroup.</para>
-+
-+          <para>Examples:<programlisting>
-+BPFProgram=egress:/sys/fs/bpf/egress-hook
-+BPFProgram=bind6:/sys/fs/bpf/sock-addr-hook
-+</programlisting></para>
-+        </listitem>
-+      </varlistentry>
-+
-       <varlistentry>
-         <term><varname>DeviceAllow=</varname></term>
- 
--- 
-2.30.2
-
-
-From 3e1bfa33f198feb47431d19f4d48383e070dbdca Mon Sep 17 00:00:00 2001
-From: Julia Kartseva <hex@fb.com>
-Date: Tue, 8 Dec 2020 22:07:30 -0800
-Subject: [PATCH 9/9] dbus-cgroup: add BPFProgram= dbus support
-
-- Handle BPFProgram= property in string format
-"<bpf_attach_type>:<bpffs_path>", e.g. egress:/sys/fs/bpf/egress-hook.
-- Add dbus getter to list foreign bpf programs attached to a cgroup.
----
- man/org.freedesktop.systemd1.xml |  36 +++++++++++
- src/core/dbus-cgroup.c           | 108 +++++++++++++++++++++++++++++++
- src/shared/bus-unit-util.c       |  20 ++++++
- src/systemctl/systemctl-show.c   |  17 +++++
- 4 files changed, 181 insertions(+)
-
-diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
-index aff43217e1..d233d96d24 100644
---- a/man/org.freedesktop.systemd1.xml
-+++ b/man/org.freedesktop.systemd1.xml
-@@ -2472,6 +2472,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
-       readonly u ManagedOOMMemoryPressureLimit = ...;
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
-       readonly s ManagedOOMPreference = '...';
-+      @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
-+      readonly a(ss) BPFProgram = [...];
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
-       readonly as Environment = ['...', ...];
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
-@@ -3004,6 +3006,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
- 
-     <!--property ManagedOOMPreference is not documented!-->
- 
-+    <!--property BPFProgram is not documented!-->
-+
-     <!--property EnvironmentFiles is not documented!-->
- 
-     <!--property PassEnvironment is not documented!-->
-@@ -3560,6 +3564,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
- 
-     <variablelist class="dbus-property" generated="True" extra-ref="ManagedOOMPreference"/>
- 
-+    <variablelist class="dbus-property" generated="True" extra-ref="BPFProgram"/>
-+
-     <variablelist class="dbus-property" generated="True" extra-ref="Environment"/>
- 
-     <variablelist class="dbus-property" generated="True" extra-ref="EnvironmentFiles"/>
-@@ -4245,6 +4251,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
-       readonly u ManagedOOMMemoryPressureLimit = ...;
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
-       readonly s ManagedOOMPreference = '...';
-+      @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
-+      readonly a(ss) BPFProgram = [...];
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
-       readonly as Environment = ['...', ...];
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
-@@ -4805,6 +4813,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
- 
-     <!--property ManagedOOMPreference is not documented!-->
- 
-+    <!--property BPFProgram is not documented!-->
-+
-     <!--property EnvironmentFiles is not documented!-->
- 
-     <!--property PassEnvironment is not documented!-->
-@@ -5359,6 +5369,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
- 
-     <variablelist class="dbus-property" generated="True" extra-ref="ManagedOOMPreference"/>
- 
-+    <variablelist class="dbus-property" generated="True" extra-ref="BPFProgram"/>
-+
-     <variablelist class="dbus-property" generated="True" extra-ref="Environment"/>
- 
-     <variablelist class="dbus-property" generated="True" extra-ref="EnvironmentFiles"/>
-@@ -5946,6 +5958,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
-       readonly u ManagedOOMMemoryPressureLimit = ...;
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
-       readonly s ManagedOOMPreference = '...';
-+      @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
-+      readonly a(ss) BPFProgram = [...];
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
-       readonly as Environment = ['...', ...];
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
-@@ -6434,6 +6448,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
- 
-     <!--property ManagedOOMPreference is not documented!-->
- 
-+    <!--property BPFProgram is not documented!-->
-+
-     <!--property EnvironmentFiles is not documented!-->
- 
-     <!--property PassEnvironment is not documented!-->
-@@ -6906,6 +6922,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
- 
-     <variablelist class="dbus-property" generated="True" extra-ref="ManagedOOMPreference"/>
- 
-+    <variablelist class="dbus-property" generated="True" extra-ref="BPFProgram"/>
-+
-     <variablelist class="dbus-property" generated="True" extra-ref="Environment"/>
- 
-     <variablelist class="dbus-property" generated="True" extra-ref="EnvironmentFiles"/>
-@@ -7614,6 +7632,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
-       readonly u ManagedOOMMemoryPressureLimit = ...;
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
-       readonly s ManagedOOMPreference = '...';
-+      @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
-+      readonly a(ss) BPFProgram = [...];
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
-       readonly as Environment = ['...', ...];
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
-@@ -8088,6 +8108,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
- 
-     <!--property ManagedOOMPreference is not documented!-->
- 
-+    <!--property BPFProgram is not documented!-->
-+
-     <!--property EnvironmentFiles is not documented!-->
- 
-     <!--property PassEnvironment is not documented!-->
-@@ -8546,6 +8568,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
- 
-     <variablelist class="dbus-property" generated="True" extra-ref="ManagedOOMPreference"/>
- 
-+    <variablelist class="dbus-property" generated="True" extra-ref="BPFProgram"/>
-+
-     <variablelist class="dbus-property" generated="True" extra-ref="Environment"/>
- 
-     <variablelist class="dbus-property" generated="True" extra-ref="EnvironmentFiles"/>
-@@ -9107,6 +9131,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
-       readonly u ManagedOOMMemoryPressureLimit = ...;
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
-       readonly s ManagedOOMPreference = '...';
-+      @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
-+      readonly a(ss) BPFProgram = [...];
-   };
-   interface org.freedesktop.DBus.Peer { ... };
-   interface org.freedesktop.DBus.Introspectable { ... };
-@@ -9245,6 +9271,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
- 
-     <!--property ManagedOOMPreference is not documented!-->
- 
-+    <!--property BPFProgram is not documented!-->
-+
-     <!--Autogenerated cross-references for systemd.directives, do not edit-->
- 
-     <variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.systemd1.Unit"/>
-@@ -9387,6 +9415,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
- 
-     <variablelist class="dbus-property" generated="True" extra-ref="ManagedOOMPreference"/>
- 
-+    <variablelist class="dbus-property" generated="True" extra-ref="BPFProgram"/>
-+
-     <!--End of Autogenerated section-->
- 
-     <refsect2>
-@@ -9548,6 +9578,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
-       readonly u ManagedOOMMemoryPressureLimit = ...;
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
-       readonly s ManagedOOMPreference = '...';
-+      @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
-+      readonly a(ss) BPFProgram = [...];
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
-       readonly s KillMode = '...';
-       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
-@@ -9702,6 +9734,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
- 
-     <!--property ManagedOOMPreference is not documented!-->
- 
-+    <!--property BPFProgram is not documented!-->
-+
-     <!--property KillMode is not documented!-->
- 
-     <!--property KillSignal is not documented!-->
-@@ -9870,6 +9904,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
- 
-     <variablelist class="dbus-property" generated="True" extra-ref="ManagedOOMPreference"/>
- 
-+    <variablelist class="dbus-property" generated="True" extra-ref="BPFProgram"/>
-+
-     <variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
- 
-     <variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
-diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c
-index 04d2ba34f3..604cecf84c 100644
---- a/src/core/dbus-cgroup.c
-+++ b/src/core/dbus-cgroup.c
-@@ -5,6 +5,7 @@
- #include "af-list.h"
- #include "alloc-util.h"
- #include "bpf-firewall.h"
-+#include "bpf-foreign.h"
- #include "bus-get-properties.h"
- #include "cgroup-util.h"
- #include "cgroup.h"
-@@ -347,6 +348,33 @@ static int property_get_ip_address_access(
-         return sd_bus_message_close_container(reply);
- }
- 
-+static int property_get_bpf_foreign_program(
-+                sd_bus *bus,
-+                const char *path,
-+                const char *interface,
-+                const char *property,
-+                sd_bus_message *reply,
-+                void *userdata,
-+                sd_bus_error *error) {
-+        CGroupContext *c = userdata;
-+        CGroupBPFForeignProgram *p;
-+        int r;
-+
-+        r = sd_bus_message_open_container(reply, 'a', "(ss)");
-+        if (r < 0)
-+                return r;
-+
-+        LIST_FOREACH(programs, p, c->bpf_foreign_programs) {
-+                const char *attach_type = bpf_cgroup_attach_type_to_string(p->attach_type);
-+
-+                r = sd_bus_message_append(reply, "(ss)", attach_type, p->bpffs_path);
-+                if (r < 0)
-+                        return r;
-+        }
-+
-+        return sd_bus_message_close_container(reply);
-+}
-+
- const sd_bus_vtable bus_cgroup_vtable[] = {
-         SD_BUS_VTABLE_START(0),
-         SD_BUS_PROPERTY("Delegate", "b", bus_property_get_bool, offsetof(CGroupContext, delegate), 0),
-@@ -398,6 +426,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
-         SD_BUS_PROPERTY("ManagedOOMMemoryPressure", "s", property_get_managed_oom_mode, offsetof(CGroupContext, moom_mem_pressure), 0),
-         SD_BUS_PROPERTY("ManagedOOMMemoryPressureLimit", "u", NULL, offsetof(CGroupContext, moom_mem_pressure_limit), 0),
-         SD_BUS_PROPERTY("ManagedOOMPreference", "s", property_get_managed_oom_preference, offsetof(CGroupContext, moom_preference), 0),
-+        SD_BUS_PROPERTY("BPFProgram", "a(ss)", property_get_bpf_foreign_program, 0, 0),
-         SD_BUS_VTABLE_END
- };
- 
-@@ -570,6 +599,85 @@ static int bus_cgroup_set_transient_property(
-                         }
-                 }
- 
-+                return 1;
-+        } else if (streq(name, "BPFProgram")) {
-+                const char *a, *p;
-+                size_t n = 0;
-+
-+                r = sd_bus_message_enter_container(message, 'a', "(ss)");
-+                if (r < 0)
-+                        return r;
-+
-+                while ((r = sd_bus_message_read(message, "(ss)", &a, &p)) > 0) {
-+                        int attach_type = bpf_cgroup_attach_type_from_string(a);
-+                        if (attach_type < 0)
-+                                return sd_bus_error_setf(
-+                                                error,
-+                                                SD_BUS_ERROR_INVALID_ARGS,
-+                                                "%s expects a valid BPF attach type, got '%s'.",
-+                                                name, a);
-+
-+                        if (!path_is_normalized(p) || !path_is_absolute(p))
-+                                return sd_bus_error_setf(
-+                                                error,
-+                                                SD_BUS_ERROR_INVALID_ARGS,
-+                                                "%s= expects a normalized absolute path.",
-+                                                name);
-+
-+                        if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
-+                                r = cgroup_add_bpf_foreign_program(c, attach_type, p);
-+                                if (r < 0)
-+                                        return r;
-+                        }
-+                        n++;
-+                }
-+                if (r < 0)
-+                        return r;
-+
-+                r = sd_bus_message_exit_container(message);
-+                if (r < 0)
-+                        return r;
-+
-+                if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
-+                        _cleanup_free_ char *buf = NULL;
-+                        _cleanup_fclose_ FILE *f = NULL;
-+                        CGroupBPFForeignProgram *fp;
-+                        size_t size = 0;
-+
-+                        if (n == 0)
-+                                while (c->bpf_foreign_programs)
-+                                        cgroup_context_remove_bpf_foreign_program(c, c->bpf_foreign_programs);
-+
-+                        f = open_memstream_unlocked(&buf, &size);
-+                        if (!f)
-+                                return -ENOMEM;
-+
-+                        fputs(name, f);
-+                        fputs("=\n", f);
-+
-+                        LIST_FOREACH(programs, fp, c->bpf_foreign_programs)
-+                                fprintf(f, "%s=%s:%s\n", name,
-+                                                bpf_cgroup_attach_type_to_string(fp->attach_type),
-+                                                fp->bpffs_path);
-+
-+                        r = fflush_and_check(f);
-+                        if (r < 0)
-+                                return r;
-+
-+                        unit_write_setting(u, flags, name, buf);
-+
-+                        if (!LIST_IS_EMPTY(c->bpf_foreign_programs)) {
-+                                r = bpf_foreign_supported();
-+                                if (r < 0)
-+                                        return r;
-+                                if (r == 0)
-+                                        log_full(LOG_DEBUG,
-+                                                 "Transient unit %s configures a BPF program pinned to BPF "
-+                                                 "filesystem, but the local system does not support that.\n"
-+                                                 "Starting this unit will fail!", u->id);
-+                        }
-+                }
-+
-                 return 1;
-         }
- 
-diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
-index a75178068b..bcb4087e6d 100644
---- a/src/shared/bus-unit-util.c
-+++ b/src/shared/bus-unit-util.c
-@@ -842,6 +842,26 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
-                 return 1;
-         }
- 
-+        if (streq(field, "BPFProgram")) {
-+                if (isempty(eq))
-+                        r = sd_bus_message_append(m, "(sv)", field, "a(ss)", 0);
-+                else {
-+                        _cleanup_free_ char *word = NULL;
-+
-+                        r = extract_first_word(&eq, &word, ":", 0);
-+                        if (r == -ENOMEM)
-+                                return log_oom();
-+                        if (r < 0)
-+                                return log_error_errno(r, "Failed to parse %s: %m", field);
-+
-+                        r = sd_bus_message_append(m, "(sv)", field, "a(ss)", 1, word, eq);
-+                }
-+                if (r < 0)
-+                        return bus_log_create_error(r);
-+
-+                return 1;
-+        }
-+
-         return 0;
- }
- 
-diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c
-index 2402a59d31..dc45de4b3d 100644
---- a/src/systemctl/systemctl-show.c
-+++ b/src/systemctl/systemctl-show.c
-@@ -1696,6 +1696,23 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
- 
-                         return 1;
- 
-+                } else if (streq(name, "BPFProgram")) {
-+                        const char *a, *p;
-+
-+                        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)");
-+                        if (r < 0)
-+                                return bus_log_parse_error(r);
-+
-+                        while ((r = sd_bus_message_read(m, "(ss)", &a, &p)) > 0)
-+                                bus_print_property_valuef(name, expected_value, value, "%s:%s", a, p);
-+                        if (r < 0)
-+                                return bus_log_parse_error(r);
-+
-+                        r = sd_bus_message_exit_container(m);
-+                        if (r < 0)
-+                                return bus_log_parse_error(r);
-+
-+                        return 1;
-                 }
- 
-                 break;
--- 
-2.30.2
-
diff --git a/SOURCES/17495-rebased.patch b/SOURCES/17495-rebased.patch
deleted file mode 100644
index 0a94fe5..0000000
--- a/SOURCES/17495-rebased.patch
+++ /dev/null
@@ -1,410 +0,0 @@
-From 945960a5e4fa339e3d747d695c0e0996c2336e02 Mon Sep 17 00:00:00 2001
-From: Chris Down <chris@chrisdown.name>
-Date: Thu, 29 Oct 2020 12:03:52 +0000
-Subject: [PATCH] bpf: pid1: Pin reference to BPF programs for post-coldplug
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-During `daemon-reload` and `daemon-reexec`, we detach and reattach all
-BPF programs attached to cgroups. This, however, poses a real practical
-problem for DevicePolicy (and some other settings using BPF): it
-presents a period of time where the old device filtering BPF program has
-been unloaded, but the new one has not been loaded yet.
-
-Since the filtering is at open() time, it has become apparent that that
-there's a non-trivial period where applications inside that ostensibly
-filtered cgroup can grab any device -- and often do so -- and then
-retain access to that device even after the reload is over. Due to the
-file continuing to be available after the initial open(), this issue is
-particularly visible for DevicePolicy={strict,closed}, however it also
-applies to other BPF programs we install.
-
-In particular, for BPF ingress/egress filtering this may have more
-concerning implications: network traffic which is supposed to be
-filtered will -- for a very brief period of time -- not be filtered or
-subject to any restrictions imposed by BPF.
-
-These BPF programs are fundamentally attached to a cgroup lifetime, not
-our unit lifetime, so it's enough to pin these programs by taking a
-reference to affected BPF programs before reload/reexec. We can then
-serialise the program's kernel-facing FD and cgroup attachment FD for
-the new daemon, and have the daemon on the other side unpin the programs
-after it's finished with coldplug.
-
-That means that, for example, the BPF program lifecycle during
-daemon-reload or daemon-reexec changes from this:
-
-    manager_clear_jobs_and_units
-                 │
-          ╔══════╪═════════╤═══════╗
-          ║ prog │ no prog │ prog' ║
-          ╚══════╧═════════╪═══════╝
-                           │
-                    manager_coldplug
-
-to this:
-
-    manager_clear_jobs_and_units         manager_dispatch_cgroup_realize_queue
-                 │                                       │
-          ╔══════╪═══════════════╤═══════════════════════╪═══════╗
-          ║ prog │ prog (orphan) │ prog (orphan) + prog' │ prog' ║
-          ╚══════╧═══════════════╪═══════════════════════╧═══════╝
-                                 │
-                          manager_coldplug
-
-For daemon-reexec the semantics are mostly the same, but the point at
-which the program becomes orphan is tied to the process lifecycle
-instead.
-
-None of the BPF programs we install require exclusive access, so having
-multiple instances of them running at the same time is fine. Custom
-programs, of course, are unknown, but it's hard to imagine legitimate
-cases which should be affected, whereas the benefits of this "overlap"
-approach with reference pinning is immediately tangible.
-
-[keszybz: use _cleanup_ for unpin, use FOREACH_POINTER]
----
- src/core/main.c          |   9 +++
- src/core/manager.c       | 163 ++++++++++++++++++++++++++++++++++++++-
- src/core/manager.h       |   5 ++
- src/core/unit.h          |   8 +-
- src/shared/bpf-program.c |  10 +++
- src/shared/bpf-program.h |   1 +
- 6 files changed, 191 insertions(+), 5 deletions(-)
-
-diff --git a/src/core/main.c b/src/core/main.c
-index 3ee8d0a869..cb065dbc6e 100644
---- a/src/core/main.c
-+++ b/src/core/main.c
-@@ -1164,6 +1164,14 @@ static int prepare_reexecute(
-         if (!fds)
-                 return log_oom();
- 
-+        /* We need existing BPF programs to survive reload, otherwise there will be a period where no BPF
-+         * program is active during task execution within a cgroup. This would be bad since this may have
-+         * security or reliability implications: devices we should filter won't be filtered, network activity
-+         * we should filter won't be filtered, etc. We pin all the existing devices by bumping their
-+         * refcount, and then storing them to later have it decremented. */
-+        _cleanup_(manager_unpin_all_cgroup_bpf_programs) Manager *m_unpin =
-+                manager_pin_all_cgroup_bpf_programs(m);
-+
-         r = manager_serialize(m, f, fds, switching_root);
-         if (r < 0)
-                 return r;
-@@ -1179,6 +1187,7 @@ static int prepare_reexecute(
-         if (r < 0)
-                 return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization fds: %m");
- 
-+        TAKE_PTR(m_unpin);
-         *ret_f = TAKE_PTR(f);
-         *ret_fds = TAKE_PTR(fds);
- 
-diff --git a/src/core/manager.c b/src/core/manager.c
-index 688e6881c3..44a92e7577 100644
---- a/src/core/manager.c
-+++ b/src/core/manager.c
-@@ -65,6 +65,7 @@
- #include "rm-rf.h"
- #include "selinux-util.h"
- #include "serialize.h"
-+#include "set.h"
- #include "signal-util.h"
- #include "socket-util.h"
- #include "special.h"
-@@ -3203,6 +3204,79 @@ static void manager_serialize_gid_refs(Manager *m, FILE *f) {
-         manager_serialize_uid_refs_internal(f, m->gid_refs, "destroy-ipc-gid");
- }
- 
-+static int serialize_limbo_bpf_program(FILE *f, FDSet *fds, BPFProgram *p) {
-+        int copy;
-+        _cleanup_free_ char *ap = NULL;
-+
-+        /* We don't actually need the instructions or other data, since this is only used on the other side
-+         * for BPF limbo, which just requires the program type, cgroup path, and kernel-facing BPF file
-+         * descriptor. We don't even need to know what unit or directive it's attached to, since we're just
-+         * going to expire it after coldplug. */
-+
-+        assert(f);
-+        assert(p);
-+
-+        /* If the program isn't attached to the kernel yet, there's no reason to serialise it for limbo. Just
-+         * let it be skeletonized and then coldplug can do the work on the other side if it's still
-+         * necessary. */
-+        if (p->kernel_fd < 0 || !p->attached_path)
-+                return -ENOTCONN;
-+
-+        copy = fdset_put_dup(fds, p->kernel_fd);
-+        if (copy < 0)
-+                return log_error_errno(copy, "Failed to add file descriptor to serialization set: %m");
-+
-+        /* Otherwise, on daemon-reload, we'd remain pinned. */
-+        safe_close(p->kernel_fd);
-+
-+        ap = cescape(p->attached_path);
-+        if (!ap)
-+                return log_oom();
-+
-+        return serialize_item_format(f, "bpf-limbo", "%i %i %i \"%s\"",
-+                                     copy, p->prog_type, p->attached_type, ap);
-+}
-+
-+static void deserialize_limbo_bpf_program(Manager *m, FDSet *fds, const char *value) {
-+        _cleanup_free_ char *raw_fd = NULL, *raw_pt = NULL, *raw_at = NULL, *cgpath = NULL;
-+        int fd, r, prog_type, attached_type;
-+
-+        assert(m);
-+        assert(value);
-+
-+        r = extract_first_word(&value, &raw_fd, NULL, 0);
-+        if (r <= 0 || safe_atoi(raw_fd, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd))
-+                return (void) log_error("Failed to parse bpf-limbo FD: %s", value);
-+
-+        r = extract_first_word(&value, &raw_pt, NULL, 0);
-+        if (r <= 0 || safe_atoi(raw_pt, &prog_type) < 0)
-+                return (void) log_error("Failed to parse bpf-limbo program type: %s", value);
-+
-+        r = extract_first_word(&value, &raw_at, NULL, 0);
-+        if (r <= 0 || safe_atoi(raw_at, &attached_type) < 0)
-+                return (void) log_error("Failed to parse bpf-limbo attached type: %s", value);
-+
-+        r = extract_first_word(&value, &cgpath, NULL, EXTRACT_CUNESCAPE | EXTRACT_UNQUOTE);
-+        if (r <= 0)
-+                return (void) log_error("Failed to parse attached path for BPF limbo FD %s", value);
-+
-+        _cleanup_(bpf_program_unrefp) BPFProgram *p = NULL;
-+        r = bpf_program_new(prog_type, &p);
-+        if (r < 0)
-+                return (void) log_error_errno(r, "Failed to create BPF limbo program: %m");
-+
-+        /* Just enough to free it when the time is right, this does not have enough information be used as a
-+         * real BPFProgram. */
-+        p->attached_type = attached_type;
-+        p->kernel_fd = fdset_remove(fds, fd);
-+        p->attached_path = TAKE_PTR(cgpath);
-+
-+        r = set_ensure_put(&m->bpf_limbo_progs, NULL, p);
-+        if (r < 0)
-+                return (void) log_error_errno(r, "Failed to register BPF limbo program for FD %s: %m", value);
-+        TAKE_PTR(p);
-+}
-+
- int manager_serialize(
-                 Manager *m,
-                 FILE *f,
-@@ -3212,6 +3286,7 @@ int manager_serialize(
-         const char *t;
-         Unit *u;
-         int r;
-+        BPFProgram *p;
- 
-         assert(m);
-         assert(f);
-@@ -3256,6 +3331,9 @@ int manager_serialize(
-                 (void) serialize_dual_timestamp(f, joined, m->timestamps + q);
-         }
- 
-+        SET_FOREACH(p, m->bpf_limbo_progs)
-+                (void) serialize_limbo_bpf_program(f, fds, p);
-+
-         if (!switching_root)
-                 (void) serialize_strv(f, "env", m->client_environment);
- 
-@@ -3571,7 +3649,10 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
-                         else
-                                 m->n_failed_jobs += n;
- 
--                } else if ((val = startswith(l, "taint-usr="))) {
-+                } else if ((val = startswith(l, "bpf-limbo=")))
-+                        deserialize_limbo_bpf_program(m, fds, val);
-+
-+                else if ((val = startswith(l, "taint-usr="))) {
-                         int b;
- 
-                         b = parse_boolean(val);
-@@ -3747,6 +3828,67 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
-         return manager_deserialize_units(m, f, fds);
- }
- 
-+Manager* manager_pin_all_cgroup_bpf_programs(Manager *m) {
-+        int r;
-+        Unit *u;
-+
-+        assert(m);
-+
-+        HASHMAP_FOREACH(u, m->units) {
-+                BPFProgram *p;
-+
-+                FOREACH_POINTER(p,
-+                                u->bpf_device_control_installed,
-+                                u->ip_bpf_ingress,
-+                                u->ip_bpf_ingress_installed,
-+                                u->ip_bpf_egress,
-+                                u->ip_bpf_egress_installed)
-+                        if (p) {
-+                                r = set_ensure_put(&m->bpf_limbo_progs, NULL, p);
-+                                if (r < 0) {
-+                                        log_unit_error_errno(u, r, "Cannot store BPF program for reload, ignoring: %m");
-+                                        continue;
-+                                }
-+
-+                                bpf_program_ref(p);
-+                        }
-+
-+                Set *s;
-+                FOREACH_POINTER(s,
-+                                u->ip_bpf_custom_ingress,
-+                                u->ip_bpf_custom_ingress_installed,
-+                                u->ip_bpf_custom_egress,
-+                                u->ip_bpf_custom_egress_installed)
-+                        SET_FOREACH(p, s) {
-+                                r = set_ensure_put(&m->bpf_limbo_progs, NULL, p);
-+                                if (r < 0) {
-+                                        log_unit_error_errno(u, r, "Cannot store BPF program for reload, ignoring: %m");
-+                                        continue;
-+                                }
-+
-+                                bpf_program_ref(p);
-+                        }
-+        }
-+
-+        log_debug("Pinned %d BPF programs", set_size(m->bpf_limbo_progs));
-+
-+        return m;
-+}
-+
-+static void manager_skeletonize_all_cgroup_bpf_programs(Manager *m) {
-+        BPFProgram *p;
-+
-+        SET_FOREACH(p, m->bpf_limbo_progs)
-+                bpf_program_skeletonize(p);
-+}
-+
-+void manager_unpin_all_cgroup_bpf_programs(Manager **m) {
-+        if (m && *m) {
-+                log_debug("Unpinning %d BPF programs", set_size((*m)->bpf_limbo_progs));
-+                set_clear_with_destructor((*m)->bpf_limbo_progs, bpf_program_unref);
-+        }
-+}
-+
- int manager_reload(Manager *m) {
-         _cleanup_(manager_reloading_stopp) Manager *reloading = NULL;
-         _cleanup_fdset_free_ FDSet *fds = NULL;
-@@ -3766,6 +3908,13 @@ int manager_reload(Manager *m) {
-         /* We are officially in reload mode from here on. */
-         reloading = manager_reloading_start(m);
- 
-+        /* We need existing BPF programs to survive reload, otherwise there will be a period where no BPF
-+         * program is active during task execution within a cgroup. This would be bad since this may have
-+         * security or reliability implications: devices we should filter won't be filtered, network activity
-+         * we should filter won't be filtered, etc. We pin all the existing devices by bumping their
-+         * refcount, and then storing them to later have it decremented. */
-+        (void) manager_pin_all_cgroup_bpf_programs(m);
-+
-         r = manager_serialize(m, f, fds, false);
-         if (r < 0)
-                 return r;
-@@ -3790,6 +3939,12 @@ int manager_reload(Manager *m) {
-         m->uid_refs = hashmap_free(m->uid_refs);
-         m->gid_refs = hashmap_free(m->gid_refs);
- 
-+        /* The only canonical reference left to the dynamically allocated parts of these BPF programs is
-+         * going to be on the other side of manager_deserialize, so the freeable parts can now be freed. The
-+         * program itself will be detached as part of manager_vacuum. */
-+        manager_skeletonize_all_cgroup_bpf_programs(m);
-+        m->bpf_limbo_progs = set_free(m->bpf_limbo_progs);
-+
-         r = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL);
-         if (r < 0)
-                 log_warning_errno(r, "Failed to initialize path lookup table, ignoring: %m");
-@@ -4721,6 +4876,12 @@ static void manager_vacuum(Manager *m) {
- 
-         /* Release any runtimes no longer referenced */
-         exec_runtime_vacuum(m);
-+
-+        /* Release any outmoded BPF programs that were deserialized from the previous manager, since new ones
-+         * should be in action now. We first need to make sure all entries in the cgroup realize queue are
-+         * complete, otherwise BPF firewalls/etc may not have been set up yet. */
-+        (void) manager_dispatch_cgroup_realize_queue(m);
-+        manager_unpin_all_cgroup_bpf_programs(&m);
- }
- 
- int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
-diff --git a/src/core/manager.h b/src/core/manager.h
-index f58982a364..16bcdc1277 100644
---- a/src/core/manager.h
-+++ b/src/core/manager.h
-@@ -438,6 +438,8 @@ struct Manager {
-         VarlinkServer *varlink_server;
-         /* Only systemd-oomd should be using this to subscribe to changes in ManagedOOM settings */
-         Varlink *managed_oom_varlink_request;
-+
-+        Set *bpf_limbo_progs;
- };
- 
- static inline usec_t manager_default_timeout_abort_usec(Manager *m) {
-@@ -479,6 +481,9 @@ int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode
- int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Set *affected_jobs,  Job **ret);
- int manager_propagate_reload(Manager *m, Unit *unit, JobMode mode, sd_bus_error *e);
- 
-+Manager* manager_pin_all_cgroup_bpf_programs(Manager *m);
-+void manager_unpin_all_cgroup_bpf_programs(Manager **m);
-+
- void manager_dump_units(Manager *s, FILE *f, const char *prefix);
- void manager_dump_jobs(Manager *s, FILE *f, const char *prefix);
- void manager_dump(Manager *s, FILE *f, const char *prefix);
-diff --git a/src/core/unit.h b/src/core/unit.h
-index 6de529af92..7c9cc47f92 100644
---- a/src/core/unit.h
-+++ b/src/core/unit.h
-@@ -927,10 +927,10 @@ int unit_thaw_vtable_common(Unit *u);
- #define log_unit_full_errno(unit, level, error, ...)                    \
-         ({                                                              \
-                 const Unit *_u = (unit);                                \
--                const int _l = (level);                                 \
--                (log_get_max_level() < LOG_PRI(_l) || (_u && !unit_log_level_test(_u, _l))) ? -ERRNO_VALUE(error) : \
--                        _u ? log_object_internal(_l, error, PROJECT_FILE, __LINE__, __func__, _u->manager->unit_log_field, _u->id, _u->manager->invocation_log_field, _u->invocation_id_string, ##__VA_ARGS__) : \
--                                log_internal(_l, error, PROJECT_FILE, __LINE__, __func__, ##__VA_ARGS__); \
-+                const int _v = (level);                                 \
-+                (log_get_max_level() < LOG_PRI(_v) || (_u && !unit_log_level_test(_u, _v))) ? -ERRNO_VALUE(error) : \
-+                        _u ? log_object_internal(_v, error, PROJECT_FILE, __LINE__, __func__, _u->manager->unit_log_field, _u->id, _u->manager->invocation_log_field, _u->invocation_id_string, ##__VA_ARGS__) : \
-+                                log_internal(_v, error, PROJECT_FILE, __LINE__, __func__, ##__VA_ARGS__); \
-         })
- 
- #define log_unit_full(unit, level, ...) (void) log_unit_full_errno(unit, level, 0, __VA_ARGS__)
-diff --git a/src/shared/bpf-program.c b/src/shared/bpf-program.c
-index a8a34521fd..314b48b229 100644
---- a/src/shared/bpf-program.c
-+++ b/src/shared/bpf-program.c
-@@ -104,6 +104,16 @@ int bpf_program_new_from_bpffs_path(const char *path, BPFProgram **ret) {
-         return 0;
- }
- 
-+void bpf_program_skeletonize(BPFProgram *p) {
-+        assert(p);
-+
-+        /* Called shortly after serialization. From this point on, we are frozen for serialization and entry
-+         * into BPF limbo, so we should proactively free our instructions and attached path. However, we
-+         * shouldn't detach the program or close the kernel FD -- we need those on the other side. */
-+        free(p->instructions);
-+        free(p->attached_path);
-+}
-+
- static BPFProgram *bpf_program_free(BPFProgram *p) {
-         assert(p);
- 
-diff --git a/src/shared/bpf-program.h b/src/shared/bpf-program.h
-index 86fd338c93..dcaeb6cde7 100644
---- a/src/shared/bpf-program.h
-+++ b/src/shared/bpf-program.h
-@@ -29,6 +29,7 @@ int bpf_program_new(uint32_t prog_type, BPFProgram **ret);
- int bpf_program_new_from_bpffs_path(const char *path, BPFProgram **ret);
- BPFProgram *bpf_program_ref(BPFProgram *p);
- BPFProgram *bpf_program_unref(BPFProgram *p);
-+void bpf_program_skeletonize(BPFProgram *p);
- 
- int bpf_program_add_instructions(BPFProgram *p, const struct bpf_insn *insn, size_t count);
- int bpf_program_load_kernel(BPFProgram *p, char *log_buf, size_t log_size);
--- 
-2.30.2
-
diff --git a/SOURCES/README.build-in-place b/SOURCES/README.build-in-place
new file mode 100644
index 0000000..8b66077
--- /dev/null
+++ b/SOURCES/README.build-in-place
@@ -0,0 +1,14 @@
+== Building systemd rpms for local development using rpmbuild --build-in-place ==
+
+This approach is based on https://github.com/filbranden/git-rpmbuild
+and filbranden's talk during ASG2019 [https://cfp.all-systems-go.io/ASG2019/talk/JM7GDN/].
+
+```
+git clone https://github.com/systemd/systemd
+fedpkg clone systemd fedora-systemd
+cd systemd
+rpmbuild -bb --build-in-place --noprep --define "_sourcedir $PWD/../fedora-systemd" --define "_rpmdir $PWD/rpms" --with inplace ../systemd.spec
+sudo dnf upgrade --setopt install_weak_deps=False rpms/*/*.rpm
+```
+
+`--without lto` and `--without tests` may be useful to speed up the build.
diff --git a/SOURCES/libsystemd-shared.abignore b/SOURCES/libsystemd-shared.abignore
new file mode 100644
index 0000000..e412d8b
--- /dev/null
+++ b/SOURCES/libsystemd-shared.abignore
@@ -0,0 +1,3 @@
+[suppress_file]
+# This shared object is private to systemd
+file_name_regexp=libsystemd-shared-.*.so
diff --git a/SOURCES/revert-d586f642fd90e3bb378f7b6d3e3a64a753e51756.patch b/SOURCES/revert-d586f642fd90e3bb378f7b6d3e3a64a753e51756.patch
deleted file mode 100644
index 76b1d48..0000000
--- a/SOURCES/revert-d586f642fd90e3bb378f7b6d3e3a64a753e51756.patch
+++ /dev/null
@@ -1,34 +0,0 @@
-From c6b36901b8a3771b8c3762faa67f08fc5611de66 Mon Sep 17 00:00:00 2001
-From: Anita Zhang <the.anitazha@gmail.com>
-Date: Mon, 14 Jun 2021 10:48:01 -0700
-Subject: [PATCH] Revert "core: prevent excessive /proc/self/mountinfo parsing"
-
-This reverts commit d586f642fd90e3bb378f7b6d3e3a64a753e51756.
-
-sd-event rate limiting has a few issues in 248 that need to be worked
-out. See: https://github.com/systemd/systemd/pull/19811 and
-https://github.com/systemd/systemd/pull/19924. Let's try again in 249.
----
- src/core/mount.c | 6 ------
- 1 file changed, 6 deletions(-)
-
-diff --git a/src/core/mount.c b/src/core/mount.c
-index ca5d0939a1..2939062161 100644
---- a/src/core/mount.c
-+++ b/src/core/mount.c
-@@ -1859,12 +1859,6 @@ static void mount_enumerate(Manager *m) {
-                         goto fail;
-                 }
- 
--                r = sd_event_source_set_ratelimit(m->mount_event_source, 1 * USEC_PER_SEC, 5);
--                if (r < 0) {
--                        log_error_errno(r, "Failed to enable rate limit for mount events: %m");
--                        goto fail;
--                }
--
-                 (void) sd_event_source_set_description(m->mount_event_source, "mount-monitor-dispatch");
-         }
- 
--- 
-2.31.1
-
diff --git a/SOURCES/split-files.py b/SOURCES/split-files.py
index 46c4982..f883f73 100644
--- a/SOURCES/split-files.py
+++ b/SOURCES/split-files.py
@@ -21,8 +21,10 @@ o_pam = open('.file-list-pam', 'w')
 o_rpm_macros = open('.file-list-rpm-macros', 'w')
 o_devel = open('.file-list-devel', 'w')
 o_container = open('.file-list-container', 'w')
+o_networkd = open('.file-list-networkd', 'w')
 o_oomd_defaults = open('.file-list-oomd-defaults', 'w')
 o_remote = open('.file-list-remote', 'w')
+o_resolve = open('.file-list-resolve', 'w')
 o_tests = open('.file-list-tests', 'w')
 o_standalone_tmpfiles = open('.file-list-standalone-tmpfiles', 'w')
 o_standalone_sysusers = open('.file-list-standalone-sysusers', 'w')
@@ -75,6 +77,12 @@ for file in files(buildroot):
                        org.freedesktop.(import|machine)1
     ''', n, re.X):
         o = o_container
+    elif re.search(r'''/usr/lib/systemd/network/80-|
+                       networkd|
+                       networkctl|
+                       org.freedesktop.network1
+    ''', n, re.X):
+        o = o_networkd
     elif '.so.' in n:
         o = o_libs
     elif re.search(r'''udev(?!\.pc)|
@@ -111,6 +119,14 @@ for file in files(buildroot):
                        /modprobe.d
     ''', n, re.X):
         o = o_udev
+    elif re.search(r'''resolvectl|
+                       resolved|
+                       systemd-resolve|
+                       resolvconf|
+                       resolve1\.
+    ''', n, re.X):
+        # keep only nss-resolve in systemd
+        o = o_resolve
     elif re.search(r'10-oomd-.*defaults.conf|lib/systemd/oomd.conf.d', n, re.X):
         o = o_oomd_defaults
     elif n.endswith('.standalone'):
diff --git a/SOURCES/sysusers.generate-pre.sh b/SOURCES/sysusers.generate-pre.sh
index 6c481c3..fd9938d 100755
--- a/SOURCES/sysusers.generate-pre.sh
+++ b/SOURCES/sysusers.generate-pre.sh
@@ -12,17 +12,17 @@ user() {
     home="$5"
     shell="$6"
 
-[ "$desc" = '-' ] && desc=
-[ "$home" = '-' -o "$home" = '' ] && home=/
-[ "$shell" = '-' -o "$shell" = '' ] && shell=/sbin/nologin
+    [ "$desc" = '-' ] && desc=
+    { [ "$home" = '-' ] || [ "$home" = '' ]; } && home=/
+    { [ "$shell" = '-' ] || [ "$shell" = '' ]; } && shell=/sbin/nologin
 
-if [ "$uid" = '-' -o "$uid" = '' ]; then
-    cat <<EOF
+    if [ "$uid" = '-' ] || [ "$uid" = '' ]; then
+        cat <<EOF
 getent passwd '$user' >/dev/null || \\
     useradd -r -g '$group' -d '$home' -s '$shell' -c '$desc' '$user'
 EOF
-else
-    cat <<EOF
+    else
+        cat <<EOF
 if ! getent passwd '$user' >/dev/null ; then
     if ! getent passwd '$uid' >/dev/null ; then
         useradd -r -u '$uid' -g '$group' -d '$home' -s /sbin/nologin -c '$desc' '$user'
@@ -32,29 +32,29 @@ if ! getent passwd '$user' >/dev/null ; then
 fi
 
 EOF
-fi
+    fi
 }
 
 group() {
     group="$1"
     gid="$2"
-if [ "$gid" = '-' ]; then
-    cat <<EOF
-getent group '$group' >/dev/null || groupadd -r '$group'
-EOF
-else
-    cat <<EOF
-getent group '$group' >/dev/null || groupadd -f -g '$gid' -r '$group'
-EOF
-fi
+    if [ "$gid" = '-' ]; then
+        cat <<-EOF
+	getent group '$group' >/dev/null || groupadd -r '$group'
+	EOF
+    else
+        cat <<-EOF
+	getent group '$group' >/dev/null || groupadd -f -g '$gid' -r '$group'
+	EOF
+    fi
 }
 
 parse() {
-    while read line || [ "$line" ]; do
-        [ "${line:0:1}" = '#' -o "${line:0:1}" = ';' ] && continue
+    while read -r line || [ -n "$line" ] ; do
+        { [ "${line:0:1}" = '#' ] || [ "${line:0:1}" = ';' ]; } && continue
         line="${line## *}"
         [ -z "$line" ] && continue
-        eval arr=( $line )
+        eval "arr=( $line )"
         case "${arr[0]}" in
             ('u')
                 group "${arr[1]}" "${arr[2]}"
@@ -74,6 +74,6 @@ parse() {
 
 for fn in "$@"; do
     [ -e "$fn" ] || continue
-    echo "# generated from $(basename $fn)"
-    parse < "$fn"
+    echo "# generated from $(basename "$fn")"
+    parse <"$fn"
 done
diff --git a/SPECS/systemd.spec b/SPECS/systemd.spec
index d991a61..afe032e 100644
--- a/SPECS/systemd.spec
+++ b/SPECS/systemd.spec
@@ -12,35 +12,52 @@
 %global system_unit_dir %{pkgdir}/system
 %global user_unit_dir %{pkgdir}/user
 
+%if 0%{?__isa_bits} == 64
+%global elf_bits (64bit)
+%global elf_suffix ()%{elf_bits}
+%endif
+
 # Bootstrap may be needed to break intercircular dependencies with
 # cryptsetup, e.g. when re-building cryptsetup on a json-c SONAME-bump.
 %bcond_with    bootstrap
 %bcond_without tests
 %bcond_without lto
+
+# Support for quick builds with rpmbuild --build-in-place.
+# See README.build-in-place.
+%bcond_with    inplace
+
 %if 0%{?facebook}
 %bcond_with selinux
 %else
 %bcond_without selinux
 %endif
 
+# Remove this when the macro exists in CentOS
+%global version_no_tilde %(c=%{version}; echo ${c}|tr '~' '-')
+
 Name:           systemd
 Url:            https://www.freedesktop.org/wiki/Software/systemd
-Version:        248.5
-Release:        1.3%{?dist}
+%if %{without inplace}
+Version:        249.2
+Release:        1.1%{?dist}
+%else
+# determine the build information from local checkout
+Version:        %(tools/meson-vcs-tag.sh . error | sed -r 's/-([0-9])/.^\1/; s/-g/_g/')
+Release:        0
+%endif
 # For a breakdown of the licensing, see README
 License:        LGPLv2+ and MIT and GPLv2+
 Summary:        System and Service Manager
 
-%global github_version %(c=%{version}; echo ${c}|tr '~' '-')
-
 # download tarballs with "spectool -g systemd.spec"
 %if %{defined commit}
 Source0:        https://github.com/systemd/systemd%{?stable:-stable}/archive/%{commit}/%{name}-%{shortcommit}.tar.gz
 %else
 %if 0%{?stable}
-Source0:        https://github.com/systemd/systemd-stable/archive/v%{github_version}/%{name}-%{github_version}.tar.gz
+Source0:        https://github.com/systemd/systemd-stable/archive/v%{version_no_tilde}/%{name}-%{version_no_tilde}.tar.gz
 %else
-Source0:        https://github.com/systemd/systemd/archive/v%{github_version}/%{name}-%{github_version}.tar.gz
+Source0:        https://github.com/systemd/systemd/archive/v%{version_no_tilde}/%{name}-%{version_no_tilde}.tar.gz
 %endif
 %endif
 # This file must be available before %%prep.
@@ -56,6 +73,7 @@ Source9:        20-yama-ptrace.conf
 Source10:       systemd-udev-trigger-no-reload.conf
 Source11:       20-grubby.install
 Source12:       systemd-user
+Source13:       libsystemd-shared.abignore
 
 Source14:       10-oomd-defaults.conf
 Source15:       10-oomd-root-slice-defaults.conf
@@ -84,15 +102,11 @@ GIT_DIR=../../src/systemd/.git git diffab -M v233..master@{2017-06-15} -- hwdb/[
 # patches in this range before applying upstream pull requests.
 
 %if 0%{?facebook}
-# PR 13496: Extend bpf cgroup program support
-Patch0100:      13496-fb.patch
 # PR 18621: FB variant of quieting "proc: Bad value for 'hidepid'" messages
-Patch0101:      18621-fb.patch
-# PR 17495: Fixes BPF pinning post-coldplug
-Patch0102:      17495-rebased.patch
+Patch0001:      18621-fb.patch
 %else
 # PR 18621: Quiet "proc: Bad value for 'hidepid'" messages
-Patch0101:      https://github.com/systemd/systemd/pull/18621.patch
+Patch0001:      https://github.com/systemd/systemd/pull/18621.patch
 %endif
 
 # Downstream-only patches (0500–9999)
@@ -101,8 +115,6 @@ Patch0101:      https://github.com/systemd/systemd/pull/18621.patch
 Patch0501:      https://github.com/systemd/systemd/pull/17050/commits/f58b96d3e8d1cb0dd3666bc74fa673918b586612.patch
 # Downgrade sysv-generator messages from warning to debug
 Patch0502:      0001-sysv-generator-downgrade-log-warning-about-autogener.patch
-# Revert ratelimiting added to mount processing events
-Patch0503:      revert-d586f642fd90e3bb378f7b6d3e3a64a753e51756.patch
 
 %ifarch %{ix86} x86_64 aarch64
 %global have_gnu_efi 1
@@ -149,6 +161,7 @@ BuildRequires:  iptables-devel
 BuildRequires:  pkgconfig(tss2-esys)
 BuildRequires:  pkgconfig(tss2-rc)
 BuildRequires:  pkgconfig(tss2-mu)
+BuildRequires:  systemtap-sdt-devel
 BuildRequires:  libxslt
 BuildRequires:  docbook-style-xsl
 BuildRequires:  pkgconfig
@@ -156,9 +169,8 @@ BuildRequires:  gperf
 BuildRequires:  gawk
 BuildRequires:  tree
 BuildRequires:  hostname
-BuildRequires:  python3-devel
-BuildRequires:  python3-lxml
-BuildRequires:  python3-jinja2
+BuildRequires:  python3dist(lxml)
+BuildRequires:  python3dist(jinja2)
 %if 0%{?have_gnu_efi}
 BuildRequires:  gnu-efi gnu-efi-devel
 %endif
@@ -178,15 +190,14 @@ Requires(post): grep
 # systemd-machine-id-setup requires libssl
 Requires(post): openssl-libs
 Requires(pre):  coreutils
-Requires(pre):  /usr/bin/getent
-Requires(pre):  /usr/sbin/groupadd
 Requires:       dbus >= 1.9.18
 Requires:       %{name}-pam = %{version}-%{release}
-Requires:       %{name}-rpm-macros = %{version}-%{release}
+Requires:       (%{name}-rpm-macros = %{version}-%{release} if rpm-build)
 Requires:       %{name}-libs = %{version}-%{release}
 %{?fedora:Recommends:     %{name}-networkd = %{version}-%{release}}
+%{?fedora:Recommends:     %{name}-resolved = %{version}-%{release}}
 Recommends:     diffutils
-Requires:       util-linux
+Requires:       (util-linux-core or util-linux)
 Recommends:     libxkbcommon%{?_isa}
 Provides:       /bin/systemctl
 Provides:       /sbin/shutdown
@@ -198,7 +209,7 @@ Provides:       system-setup-keyboard = 0.9
 Obsoletes:      systemd-sysv < 206
 %if 0%{?facebook} == 0
 # self-obsoletes so that dnf will install new subpackages on upgrade (#1260394)
-Obsoletes:      %{name} < 246.6-2
+Obsoletes:      %{name} < 249~~
 Conflicts:      initscripts < 9.56.1
 %endif
 Provides:       systemd-sysv = 206
@@ -213,14 +224,12 @@ Conflicts:      %{name}-standalone-sysusers < %{version}-%{release}
 Obsoletes:      %{name}-standalone-sysusers < %{version}-%{release}
 
 # Recommends to replace normal Requires deps for stuff that is dlopen()ed
-Recommends:     libcryptsetup.so.12()(64bit)
-Recommends:     libcryptsetup.so.12(CRYPTSETUP_2.0)(64bit)
-Recommends:     libidn2.so.0()(64bit)
-Recommends:     libidn2.so.0(IDN2_0.0.0)(64bit)
-Recommends:     libpcre2-8.so.0()(64bit)
-Recommends:     libpwquality.so.1()(64bit)
-Recommends:     libpwquality.so.1(LIBPWQUALITY_1.0)(64bit)
-Recommends:     libqrencode.so.4()(64bit)
+Recommends:     libidn2.so.0%{?elf_suffix}
+Recommends:     libidn2.so.0(IDN2_0.0.0)%{?elf_bits}
+Recommends:     libpcre2-8.so.0%{?elf_suffix}
+Recommends:     libpwquality.so.1%{?elf_suffix}
+Recommends:     libpwquality.so.1(LIBPWQUALITY_1.0)%{?elf_bits}
+Recommends:     libqrencode.so.4%{?elf_suffix}
 
 %if %{with selinux}
 # Force the SELinux module to be installed
@@ -322,10 +331,6 @@ Requires:       kbd
 Provides:       u2f-hidraw-policy = 1.0.2-40
 Obsoletes:      u2f-hidraw-policy < 1.0.2-40
 
-# Recommends to replace normal Requires deps for stuff that is dlopen()ed
-Recommends:     libcryptsetup.so.12()(64bit)
-Recommends:     libcryptsetup.so.12(CRYPTSETUP_2.0)(64bit)
-
 %description udev
 This package contains systemd-udev and the rules and hardware database
 needed to manage device nodes. This package is necessary on physical
@@ -368,10 +373,33 @@ and to write journal files from serialized journal contents.
 This package contains systemd-journal-gatewayd,
 systemd-journal-remote, and systemd-journal-upload.
 
+%package networkd
+Summary:        System daemon that manages network configurations
+Requires:       %{name}%{?_isa} = %{version}-%{release}
+License:        LGPLv2+
+# https://src.fedoraproject.org/rpms/systemd/pull-request/34
+Obsoletes:      systemd < 246.6-2
+
+%description networkd
+systemd-networkd is a system service that manages networks. It detects
+and configures network devices as they appear, as well as creating virtual
+network devices.
+
+%package resolved
+Summary:        Network Name Resolution manager
+Requires:       %{name}%{?_isa} = %{version}-%{release}
+Obsoletes:      %{name} < 249~~
+
+%description resolved
+systemd-resolved is a system service that provides network name resolution
+to local applications. It implements a caching and validating DNS/DNSSEC
+stub resolver, as well as an LLMNR and MulticastDNS resolver and responder.
+
 %package oomd-defaults
 Summary:        Configuration files for systemd-oomd
-Requires:       %{name}%{?_isa} = %{version}-%{release}
+Requires:       %{name} = %{version}-%{release}
 License:        LGPLv2+
+BuildArch:      noarch
 
 %description oomd-defaults
 A set of drop-in files for systemd units to enable action from systemd-oomd,
@@ -406,7 +434,7 @@ runs properly under an environment with SELinux enabled.
 %endif
 
 %prep
-%autosetup -n %{?commit:%{name}%{?stable:-stable}-%{commit}}%{!?commit:%{name}%{?stable:-stable}-%{github_version}} -p1
+%autosetup -n %{?commit:%{name}%{?stable:-stable}-%{commit}}%{!?commit:%{name}%{?stable:-stable}-%{version_no_tilde}} -p1
 
 %if %{with selinux}
 mkdir selinux
@@ -487,10 +515,32 @@ CONFIGURE_OPTS=(
         -Dfallback-hostname=localhost
 %endif
         -Ddefault-dnssec=no
+        -Ddefault-dns-over-tls=opportunistic
         # https://bugzilla.redhat.com/show_bug.cgi?id=1867830
         -Ddefault-mdns=no
         -Ddefault-llmnr=resolve
         -Doomd=true
+        -Dadm-gid=4
+        -Daudio-gid=63
+        -Dcdrom-gid=11
+        -Ddialout-gid=18
+        -Ddisk-gid=6
+        -Dinput-gid=104   # https://pagure.io/setup/pull-request/27
+        -Dkmem-gid=9
+        -Dkvm-gid=36
+        -Dlp-gid=7
+        -Drender-gid=105  # https://pagure.io/setup/pull-request/27
+        -Dsgx-gid=106     # https://pagure.io/setup/pull-request/27
+        -Dtape-gid=33
+        -Dtty-gid=5
+        -Dusers-gid=100
+        -Dutmp-gid=22
+        -Dvideo-gid=39
+        -Dwheel-gid=10
+        -Dsystemd-journal-gid=190
+        -Dsystemd-network-uid=192
+        -Dsystemd-resolve-uid=193
+        # -Dsystemd-timesync-uid=, not set yet
         # Need to set this for CentOS build
         -Ddocdir=%{_pkgdocdir}
         # CentOS is missing newer deps required to include these
@@ -525,7 +575,17 @@ CONFIGURE_OPTS+=(
 
 export LANG=en_US.UTF-8
 export LC_ALL=en_US.UTF-8
-%meson "${CONFIGURE_OPTS[@]}"
+# Do configuration. If doing an inplace build, try to do
+# reconfiguration to pick up new options.
+%if %{with inplace}
+  command -v ccache 2>/dev/null && { CC="${CC:-ccache %__cc}"; CXX="${CXX:-ccache %__cxx}"; }
+
+  [ -e %{_vpath_builddir}/build.ninja ] &&
+  %__meson configure %{_vpath_builddir} "${CONFIGURE_OPTS[@]}" ||
+%endif
+{ %meson "${CONFIGURE_OPTS[@]}"; }
+
+%meson_build
 
 new_triggers=%{_vpath_builddir}/src/rpm/triggers.systemd.sh
 if ! diff -u %{SOURCE1} ${new_triggers}; then
@@ -534,8 +594,6 @@ if ! diff -u %{SOURCE1} ${new_triggers}; then
    sleep 5
 fi
 
-%meson_build
-
 %if %{with selinux}
 cd selinux
 %{__make} -f Makefile.selinux SHARE="%{_datadir}" TARGETS="systemd_hs"
@@ -627,6 +685,8 @@ EOF
 
 install -Dm0755 -t %{buildroot}%{_prefix}/lib/kernel/install.d/ %{SOURCE11}
 
+install -Dm0644 -t %{buildroot}%{_prefix}/lib/systemd/ %{SOURCE13}
+
 install -D -t %{buildroot}/usr/lib/systemd/ %{SOURCE3}
 
 # systemd-oomd default configuration
@@ -699,31 +759,18 @@ meson test -C %{_vpath_builddir} -t 6 --print-errorlogs
 
 %include %{SOURCE1}
 
-%pre
-getent group cdrom &>/dev/null || groupadd -r -g 11 cdrom &>/dev/null || :
-getent group utmp &>/dev/null || groupadd -r -g 22 utmp &>/dev/null || :
-getent group tape &>/dev/null || groupadd -r -g 33 tape &>/dev/null || :
-getent group dialout &>/dev/null || groupadd -r -g 18 dialout &>/dev/null || :
-getent group input &>/dev/null || groupadd -r input &>/dev/null || :
-getent group kvm &>/dev/null || groupadd -r -g 36 kvm &>/dev/null || :
-getent group render &>/dev/null || groupadd -r render &>/dev/null || :
-getent group systemd-journal &>/dev/null || groupadd -r -g 190 systemd-journal 2>&1 || :
-
-getent group systemd-coredump &>/dev/null || groupadd -r systemd-coredump 2>&1 || :
-getent passwd systemd-coredump &>/dev/null || useradd -r -l -g systemd-coredump -d / -s /sbin/nologin -c "systemd Core Dumper" systemd-coredump &>/dev/null || :
-
-getent group systemd-network &>/dev/null || groupadd -r -g 192 systemd-network 2>&1 || :
-getent passwd systemd-network &>/dev/null || useradd -r -u 192 -l -g systemd-network -d / -s /sbin/nologin -c "systemd Network Management" systemd-network &>/dev/null || :
-
-getent group systemd-resolve &>/dev/null || groupadd -r -g 193 systemd-resolve 2>&1 || :
-getent passwd systemd-resolve &>/dev/null || useradd -r -u 193 -l -g systemd-resolve -d / -s /sbin/nologin -c "systemd Resolver" systemd-resolve &>/dev/null || :
-
-getent group systemd-oom &>/dev/null || groupadd -r systemd-oom 2>&1 || :
-getent passwd systemd-oom &>/dev/null || useradd -r -l -g systemd-oom -d / -s /sbin/nologin -c "systemd Userspace OOM Killer" systemd-oom &>/dev/null || :
-
 %post
 systemd-machine-id-setup &>/dev/null || :
 
+# FIXME: move to %postun. We want to restart systemd *after* removing
+# files from the old rpm. Right now we may still have bits the old
+# setup if the files are not present in the new version. But before
+# implement restarting of *other* services after the transaction, moving
+# this would make things worse, increasing the number of warnings we get
+# about needed daemon-reload.
+
+oomd_state=$(systemctl is-active systemd-oomd 2>/dev/null || :)
+
 systemctl daemon-reexec &>/dev/null || {
   # systemd v239 had bug #9553 in D-Bus authentication of the private socket,
   # which was later fixed in v240 by #9625.
@@ -744,24 +791,19 @@ systemctl daemon-reexec &>/dev/null || {
   fi
 }
 
-if [ $1 -eq 1 ]; then
-   # create /var/log/journal only on initial installation,
-   # and only if it's writable (it won't be in rpm-ostree).
-   [ -w %{_localstatedir} ] && mkdir -p %{_localstatedir}/log/journal
-
-   [ -w %{_localstatedir} ] && journalctl --update-catalog || :
-   systemd-tmpfiles --create &>/dev/null || :
+if [ "$oomd_state" == "active" ]; then
+   systemctl start -q systemd-oomd 2>/dev/null || :
 fi
 
-# Make sure new journal files will be owned by the "systemd-journal" group
-machine_id=$(cat /etc/machine-id 2>/dev/null)
-chgrp systemd-journal /{run,var}/log/journal/{,${machine_id}} &>/dev/null || :
-chmod g+s /{run,var}/log/journal/{,${machine_id}} &>/dev/null || :
+[ $1 -eq 1 ] || exit 0
 
-# Apply ACL to the journal directory
-setfacl -Rnm g:wheel:rx,d:g:wheel:rx,g:adm:rx,d:g:adm:rx /var/log/journal/ &>/dev/null || :
+# create /var/log/journal only on initial installation,
+# and only if it's writable (it won't be in rpm-ostree).
+[ -w %{_localstatedir} ] && mkdir -p %{_localstatedir}/log/journal
 
-[ $1 -eq 1 ] || exit 0
+[ -w %{_localstatedir} ] && journalctl --update-catalog || :
+systemd-sysusers || :
+systemd-tmpfiles --create &>/dev/null || :
 
 # We reset the enablement of all services upon initial installation
 # https://bugzilla.redhat.com/show_bug.cgi?id=1118740#c23
@@ -773,25 +815,23 @@ setfacl -Rnm g:wheel:rx,d:g:wheel:rx,g:adm:rx,d:g:adm:rx /var/log/journal/ &>/de
 systemctl preset-all &>/dev/null || :
 systemctl --global preset-all &>/dev/null || :
 
-# Create /etc/resolv.conf symlink.
-# We would also create it using tmpfiles, but let's do this here
-# too before NetworkManager gets a chance. (systemd-tmpfiles invocation above
-# does not do this, because it's marked with ! and we don't specify --boot.)
-# https://bugzilla.redhat.com/show_bug.cgi?id=1873856
-#
-# If systemd is not running, don't overwrite the symlink because that
-# will immediately break DNS resolution, since systemd-resolved is
-# also not running (https://bugzilla.redhat.com/show_bug.cgi?id=1891847).
-#
-# Also don't creat the symlink to the stub when the stub is disabled (#1891847 again).
-if test -d /run/systemd/system/ &&
-   systemctl -q is-enabled systemd-resolved.service &>/dev/null &&
-   ! mountpoint /etc/resolv.conf &>/dev/null &&
-   ! systemd-analyze cat-config systemd/resolved.conf 2>/dev/null | \
-        grep -qE '^DNSStubListener\s*=\s*([nN][oO]?|[fF]|[fF][aA][lL][sS][eE]|0|[oO][fF][fF])$'; then
-  ln -fsv ../run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
+%postun
+if [ $1 -eq 1 ]; then
+   [ -w %{_localstatedir} ] && journalctl --update-catalog || :
+   systemd-tmpfiles --create &>/dev/null || :
 fi
 
+%systemd_postun_with_restart systemd-timedated.service systemd-portabled.service systemd-homed.service systemd-hostnamed.service systemd-journald.service systemd-localed.service systemd-userdbd.service systemd-oomd.service
+
+# FIXME: systemd-logind.service is excluded (https://github.com/systemd/systemd/pull/17558)
+# FIXME: user@*.service needs to be restarted, but using systemctl --user daemon-reexec
+
+%triggerpostun -- systemd < 247.3-2
+# This is for upgrades from previous versions before oomd-defaults is available.
+# We use %%triggerpostun here because rpm doesn't allow a second %%triggerun with
+# a different package version.
+systemctl --no-reload preset systemd-oomd.service &>/dev/null || :
+
 %post libs
 %{?ldconfig}
 
@@ -836,10 +876,6 @@ fi
 
 %global udev_services systemd-udev{d,-settle,-trigger}.service systemd-udevd-{control,kernel}.socket systemd-timesyncd.service
 
-%pre udev
-getent group systemd-timesync &>/dev/null || groupadd -r systemd-timesync 2>&1 || :
-getent passwd systemd-timesync &>/dev/null || useradd -r -l -g systemd-timesync -d / -s /sbin/nologin -c "systemd Time Synchronization" systemd-timesync &>/dev/null || :
-
 %post udev
 # Move old stuff around in /var/lib
 mv %{_localstatedir}/lib/random-seed %{_localstatedir}/lib/systemd/random-seed &>/dev/null
@@ -874,15 +910,13 @@ grep -q -E '^KEYMAP="?fi-latin[19]"?' /etc/vconsole.conf 2>/dev/null &&
 # Others are either oneshot services, or sockets, and restarting them causes issues (#1378974)
 %systemd_postun_with_restart systemd-udevd.service systemd-timesyncd.service
 
-%pre journal-remote
-getent group systemd-journal-remote &>/dev/null || groupadd -r systemd-journal-remote 2>&1 || :
-getent passwd systemd-journal-remote &>/dev/null || useradd -r -l -g systemd-journal-remote -d %{_localstatedir}/log/journal/remote -s /sbin/nologin -c "Journal Remote" systemd-journal-remote &>/dev/null || :
-
+%global journal_remote_units_restart systemd-journal-gatewayd.service systemd-journal-remote.service systemd-journal-upload.service
+%global journal_remote_units_norestart systemd-journal-gatewayd.socket systemd-journal-remote.socket
 %post journal-remote
-%systemd_post systemd-journal-gatewayd.socket systemd-journal-gatewayd.service systemd-journal-remote.socket systemd-journal-remote.service systemd-journal-upload.service
+%systemd_post %journal_remote_units_restart %journal_remote_units_norestart
 
 %preun journal-remote
-%systemd_preun systemd-journal-gatewayd.socket systemd-journal-gatewayd.service systemd-journal-remote.socket systemd-journal-remote.service systemd-journal-upload.service
+%systemd_preun %journal_remote_units_restart %journal_remote_units_norestart
 if [ $1 -eq 1 ] ; then
     if [ -f %{_localstatedir}/lib/systemd/journal-upload/state -a ! -L %{_localstatedir}/lib/systemd/journal-upload ] ; then
         mkdir -p %{_localstatedir}/lib/private/systemd/journal-upload
@@ -892,7 +926,63 @@ if [ $1 -eq 1 ] ; then
 fi
 
 %postun journal-remote
-%systemd_postun_with_restart systemd-journal-gatewayd.service systemd-journal-remote.service systemd-journal-upload.service
+%systemd_postun_with_restart %journal_remote_units_restart
+
+%post networkd
+# systemd-networkd was split out in systemd-246.6-2.
+# Ideally, we would have a trigger scriptlet to record enablement
+# state when upgrading from systemd <= systemd-246.6-1. But, AFAICS,
+# rpm doesn't allow us to trigger on another package, short of
+# querying the rpm database ourselves, which seems risky. For rpm,
+# systemd and systemd-networkd are completely unrelated.  So let's use
+# a hack to detect if an old systemd version is currently present in
+# the file system.
+# https://bugzilla.redhat.com/show_bug.cgi?id=1943263
+if [ $1 -eq 1 ] && ls /usr/lib/systemd/libsystemd-shared-24[0-6].so &>/dev/null; then
+    echo "Skipping presets for systemd-networkd.service, seems we are upgrading from old systemd."
+else
+    %systemd_post systemd-networkd.service systemd-networkd-wait-online.service
+fi
+
+%preun networkd
+%systemd_preun systemd-networkd.service systemd-networkd-wait-online.service
+
+%preun resolved
+if [ $1 -eq 0 ] ; then
+        systemctl disable --quiet \
+                systemd-resolved.service \
+                >/dev/null || :
+fi
+
+%post resolved
+[ $1 -gt 1 ] && exit 0
+
+# Related to https://bugzilla.redhat.com/show_bug.cgi?id=1943263
+if ls /usr/lib/systemd/libsystemd-shared-24[0-8].so &>/dev/null; then
+    echo "Skipping presets for systemd-resolved.service, seems we are upgrading from old systemd."
+    exit 0
+fi
+
+%systemd_post systemd-resolved.service
+
+# Create /etc/resolv.conf symlink.
+# We would also create it using tmpfiles, but let's do this here
+# too before NetworkManager gets a chance. (systemd-tmpfiles invocation above
+# does not do this, because it's marked with ! and we don't specify --boot.)
+# https://bugzilla.redhat.com/show_bug.cgi?id=1873856
+#
+# If systemd is not running, don't overwrite the symlink because that
+# will immediately break DNS resolution, since systemd-resolved is
+# also not running (https://bugzilla.redhat.com/show_bug.cgi?id=1891847).
+#
+# Also don't create the symlink to the stub when the stub is disabled (#1891847 again).
+if test -d /run/systemd/system/ &&
+   systemctl -q is-enabled systemd-resolved.service &>/dev/null &&
+   ! mountpoint /etc/resolv.conf &>/dev/null &&
+   ! systemd-analyze cat-config systemd/resolved.conf 2>/dev/null | \
+        grep -qE '^DNSStubListener\s*=\s*([nN][oO]?|[fF]|[fF][aA][lL][sS][eE]|0|[oO][fF][fF])$'; then
+  ln -fsv ../run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
+fi
 
 %if %{with selinux}
 %pre selinux
@@ -943,6 +1033,8 @@ fi
 
 %files rpm-macros -f .file-list-rpm-macros
 
+%files resolved -f .file-list-resolve
+
 %files devel -f .file-list-devel
 
 %files udev -f .file-list-udev
@@ -951,6 +1043,8 @@ fi
 
 %files journal-remote -f .file-list-remote
 
+%files networkd -f .file-list-networkd
+
 %files oomd-defaults -f .file-list-oomd-defaults
 
 %files tests -f .file-list-tests
@@ -962,10 +1056,24 @@ fi
 %endif
 
 %changelog
+* Wed Jul 28 2021 Anita Zhang <the.anitazha@gmail.com> - 249.2-1.1
+- New release for 249
+- Drop merged patches
+- Split networkd and resolved into their own subpackages. However we don't
+  create the /etc/resolv.conf stub.
+
 * Tue Jul 27 2021 Davide Cavalca <dcavalca@centosproject.org> - 248.5-1.3
 - Add missing SELinux rules for the GNOME and KDE LiveDVD spins
   (https://pagure.io/centos-sig-hyperscale/package-bugs/issue/7)
 
+* Fri Jul 23 2021 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> - 249.2-1
+- Latest bugfix release (a minor hwdb regression bugfix, and correction
+  to kernel commandline handling when reexecuting PID 1 in a container)
+
+* Fri Jul 23 2021 Michael Catanzaro <mcatanzaro@redhat.com> - 249.2-1
+- Build with -Ddefault-dns-over-tls=opportunistic
+  (https://fedoraproject.org/wiki/Changes/DNS_Over_TLS, #1889901)
+
 * Wed Jul 21 2021 Davide Cavalca <dcavalca@centosproject.org> - 248.5-1.2
 - Add missing SELinux rules for 248
   (https://pagure.io/centos-sig-hyperscale/package-bugs/issue/1)
@@ -985,6 +1093,34 @@ fi
 - systemd-networkd workaround for TALOS-2020-1142, CVE-2020-13529.
 - A big update of hardware descriptions.
 
+* Wed Jul  7 2021 Neal Gompa <ngompa13@gmail.com> - 249-2
+- Use correct NEWS URLs for systemd 249 releases in changelog entries
+
+* Wed Jul  7 2021 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> - 249-1
+- Latest upstream release with minor bugfixes, see
+  https://github.com/systemd/systemd/blob/v249/NEWS.
+- systemd-oomd cpu usage is reduced (#1944646)
+
+* Thu Jul  1 2021 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> - 249~rc3-1
+- Latest upstream prerelease with various bugfixes, see
+  https://github.com/systemd/systemd/blob/v249-rc3/NEWS.
+
+* Fri Jun 25 2021 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> - 249~rc2-1
+- Latest upstream prerelease with various bugfixes, see
+  https://github.com/systemd/systemd/blob/v249-rc2/NEWS.
+- Ignore FORCERENEW DHCP packets (TALOS-2020-1142, CVE-2020-13529, #1959398)
+
+* Thu Jun 17 2021 Adam Williamson <awilliam@redhat.com> - 249~rc1-2
+- Stop systemd providing systemd-resolved, now the subpackage exists (#1973462)
+
+* Wed Jun 16 2021 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> - 249~rc1-1
+- Latest upstream prerelease, see
+  https://github.com/systemd/systemd/blob/v249-rc1/NEWS.
+  Fixes #1963428.
+- Use systemd-sysusers to create users (#1965815)
+- Move systemd-resolved into systemd-resolved subpackage (#1923727)
+  [patch from Petr Menšík]
+
 * Mon Jun 14 2021 Anita Zhang <anitazha@fb.com> - 248.2-1.5
 - Remove backport PR #19811 since it's still buggy
 - Remove d586f642fd90e3bb378f7b6d3e3a64a753e51756 to fix rate limiting instead