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.

(cherry picked from commit fe2b35bd58)
(cherry picked from commit 3a5c0df5c9)
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-05-27 18:28:21 +02:00
committed by Luca Boccassi
parent 5e17a74fcf
commit cea45d7789
3 changed files with 28 additions and 4 deletions

View File

@@ -548,6 +548,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

View File

@@ -58,6 +58,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);

View File

@@ -6216,6 +6216,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
@@ -6237,12 +6239,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;
@@ -6299,14 +6305,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)
@@ -6353,7 +6359,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) {