repart: when copying files into vfat or similar, do not set ownership

$ mkdir /var/tmp/files
$ touch /var/tmp/files/a
$ mkdir /var/tmp/conf
$ cat >>/var/tmp/conf/esp.conf
[Partition]
Type=esp
Format=vfat
CopyFiles=/var/tmp/files:/
$ truncate /var/tmp/disk -s 300M
$ sudo systemd-repart --dry-run=no --empty=require --definitions=/var/tmp/conf /var/tmp/disk
...
Populating vfat filesystem.
Failed to copy '...' to '/run/systemd/mount-root/': Operation not permitted
(sd-copy) failed with exit status 1.

The issue is that if there's a file owned by non-root and we try to copy
it into a newly-created DOS partition, fchown fails:
  fchown(11</run/systemd/mount-root/...>, 1000, 1000) = -1 EPERM (Operation not permitted)
We want to ignore file ownership in such cases, so pass our own UID/GID
to copy_tree_at(), which turns the fchown into a noop and let's the
operation pass through.

Fixes #38863.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-06-03 16:02:58 +02:00
parent 230a38ea52
commit fe2b35bd58
3 changed files with 28 additions and 4 deletions
+16
View File
@@ -485,6 +485,22 @@ bool fstype_can_fmask_dmask(const char *fstype) {
return streq(fstype, "vfat") || (mount_option_supported(fstype, "fmask", "0177") > 0 && mount_option_supported(fstype, "dmask", "0077") > 0);
}
bool fstype_can_ownership(const char *fstype) {
/* File systems which are not known to not support uid/gid ownership.
* For some types, this can be a bit murky. So just exclude the ones that for sure
* don't support with the current implementations in Linux. */
return !STR_IN_SET(ASSERT_PTR(fstype),
"adfs",
"exfat",
"fat",
"hfs",
"hpfs",
"msdos",
"ntfs",
"vfat");
}
bool fstype_can_uid_gid(const char *fstype) {
/* All file systems that have a uid=/gid= mount option that fixates the owners of all files and
* directories, current and future. Note that this does *not* ask the kernel via
+1
View File
@@ -66,6 +66,7 @@ bool fstype_is_api_vfs(const char *fstype);
bool fstype_is_blockdev_backed(const char *fstype);
bool fstype_is_ro(const char *fsype);
bool fstype_can_discard(const char *fstype);
bool fstype_can_ownership(const char *fstype);
bool fstype_can_uid_gid(const char *fstype);
bool fstype_can_fmask_dmask(const char *fstype);
+11 -4
View File
@@ -6712,6 +6712,8 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
return log_oom();
}
bool copy_ownership = fstype_can_ownership(p->format);
/* copy_tree_at() automatically copies the permissions of source directories to target directories if
* it created them. However, the root directory is created by us, so we have to manually take care
* that it is initialized. We use the first source directory targeting "/" as the metadata source for
@@ -6733,12 +6735,16 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_copy_source), line->source);
(void) copy_xattr(sfd, NULL, rfd, NULL, COPY_ALL_XATTRS);
(void) copy_access(sfd, rfd);
if (copy_ownership)
(void) copy_access(sfd, rfd);
(void) copy_times(sfd, rfd, 0);
break;
}
uid_t uid = copy_ownership ? UID_INVALID : getuid();
gid_t gid = copy_ownership ? GID_INVALID : getgid();
FOREACH_ARRAY(line, copy_files, n_copy_files) {
_cleanup_hashmap_free_ Hashmap *denylist = NULL;
_cleanup_hashmap_free_ Hashmap *subvolumes_by_source_inode = NULL;
@@ -6795,14 +6801,14 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
r = copy_tree_at(
sfd, ".",
pfd, fn,
UID_INVALID, GID_INVALID,
uid, gid,
line->flags,
denylist, subvolumes_by_source_inode);
} else
r = copy_tree_at(
sfd, ".",
tfd, ".",
UID_INVALID, GID_INVALID,
uid, gid,
line->flags,
denylist, subvolumes_by_source_inode);
if (r < 0)
@@ -6849,7 +6855,8 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
return log_error_errno(r, "Failed to copy '%s' to '%s%s': %m", line->source, strempty(arg_copy_source), line->target);
(void) copy_xattr(sfd, NULL, tfd, NULL, COPY_ALL_XATTRS);
(void) copy_access(sfd, tfd);
if (copy_ownership)
(void) copy_access(sfd, tfd);
(void) copy_times(sfd, tfd, 0);
if (ts != USEC_INFINITY) {