diff --git a/man/sysupdate.d.xml b/man/sysupdate.d.xml
index 5e8e9d6fe4c..b8c403aa7de 100644
--- a/man/sysupdate.d.xml
+++ b/man/sysupdate.d.xml
@@ -758,6 +758,12 @@
resources, as well as inline data that is encoded, must consist of the file and nothing else, other
such sources are ignored.
+ The extended attributes in the user namespace listed in the
+ extendedAttributes field of a file object of a UAPI.16 manifest are applied to the
+ file or directory created when installing it. This is done on a best-effort basis: if the file system
+ does not support extended attributes, or they cannot be set for other reasons, the instance is
+ installed without them.
+
diff --git a/src/shared/forward.h b/src/shared/forward.h
index d8c2c183125..3d9a1c03d6c 100644
--- a/src/shared/forward.h
+++ b/src/shared/forward.h
@@ -97,6 +97,7 @@ typedef struct Tpm2Handle Tpm2Handle;
typedef struct Tpm2PCRValue Tpm2PCRValue;
typedef struct Uapi16Contents Uapi16Contents;
typedef struct Uapi16Object Uapi16Object;
+typedef struct Uapi16Xattr Uapi16Xattr;
typedef struct UnitInfo UnitInfo;
typedef struct UnitResult UnitResult;
typedef struct UserRecord UserRecord;
diff --git a/src/sysupdate/sysupdate-instance.c b/src/sysupdate/sysupdate-instance.c
index 7406f47f6d5..ef769bac103 100644
--- a/src/sysupdate/sysupdate-instance.c
+++ b/src/sysupdate/sysupdate-instance.c
@@ -63,5 +63,7 @@ Instance *instance_free(Instance *i) {
uapi16_contents_done(s);
free(i->sources);
+ uapi16_xattr_array_free(i->user_xattrs, i->n_user_xattrs);
+
return mfree(i);
}
diff --git a/src/sysupdate/sysupdate-instance.h b/src/sysupdate/sysupdate-instance.h
index d64ed897959..584937159e4 100644
--- a/src/sysupdate/sysupdate-instance.h
+++ b/src/sysupdate/sysupdate-instance.h
@@ -53,6 +53,11 @@ struct Instance {
Uapi16Contents *sources;
size_t n_sources;
+ /* The "user" extended attributes listed in the manifest (for web backed sources), which we apply
+ * to the inode we create when installing the instance */
+ Uapi16Xattr *user_xattrs;
+ size_t n_user_xattrs;
+
/* The current state of this instance */
bool is_partial;
bool is_pending;
diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c
index b076ec35132..4514982c040 100644
--- a/src/sysupdate/sysupdate-resource.c
+++ b/src/sysupdate/sysupdate-resource.c
@@ -899,6 +899,11 @@ static int uapi16_process_object(Uapi16Object *object, size_t index, void *userd
instance->n_sources = object->n_contents;
object->n_contents = 0;
+ /* Similar, for the extended attributes to apply when installing */
+ instance->user_xattrs = TAKE_PTR(object->user_xattrs);
+ instance->n_user_xattrs = object->n_user_xattrs;
+ object->n_user_xattrs = 0;
+
return 0;
}
diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c
index 1b5c2f67801..faa7676f80d 100644
--- a/src/sysupdate/sysupdate-transfer.c
+++ b/src/sysupdate/sysupdate-transfer.c
@@ -51,6 +51,7 @@
#include "sysupdate-transfer.h"
#include "time-util.h"
#include "web-util.h"
+#include "xattr-util.h"
/* Default value for InstancesMax= for fs object targets */
#define DEFAULT_FILE_INSTANCES_MAX 3
@@ -1835,6 +1836,54 @@ static int transfer_acquire_source(
t, i, cb, userdata);
}
+static int instance_apply_user_xattrs(Instance *i, const char *path) {
+ int r;
+
+ assert(i);
+ assert(path);
+
+ /* Applies the user extended attributes listed in the manifest to the installed inode, so that they
+ * are retained on the target. Failing to do so is not fatal, the instance is fully usable without
+ * them. Returns > 0 if any attribute was applied. */
+
+ if (i->n_user_xattrs == 0)
+ return 0;
+
+ _cleanup_close_ int fd = xopenat(AT_FDCWD, path, O_PATH|O_NOFOLLOW);
+ if (fd < 0) {
+ log_warning_errno(fd, "Failed to open '%s' to apply extended attributes, ignoring: %m", path);
+ return 0;
+ }
+
+ bool applied = false;
+ FOREACH_ARRAY(x, i->user_xattrs, i->n_user_xattrs) {
+ _cleanup_free_ char *n = strjoin("user.", x->name);
+ if (!n)
+ return log_oom();
+
+ r = xsetxattr_full(
+ fd,
+ /* path= */ NULL,
+ AT_EMPTY_PATH,
+ n,
+ x->value.iov_base,
+ x->value.iov_len,
+ /* xattr_flags= */ 0);
+ if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) {
+ log_notice_errno(r, "File system backing '%s' does not support extended attributes, not applying them: %m", path);
+ break;
+ }
+ if (r < 0) {
+ log_warning_errno(r, "Failed to set extended attribute '%s' on '%s', ignoring: %m", n, path);
+ continue;
+ }
+
+ applied = true;
+ }
+
+ return applied;
+}
+
int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, TransferProgress cb, void *userdata) {
char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
const char *where = NULL;
@@ -2074,6 +2123,12 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra
need_sync = true;
}
+ r = instance_apply_user_xattrs(i, t->temporary_partial_path);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ need_sync = true;
+
/* Synchronize */
if (t->context->sync && need_sync) {
if (t->target.type == RESOURCE_REGULAR_FILE)