]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: when copying files into vfat or similar, do not set ownership 42345/head
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Wed, 27 May 2026 16:28:21 +0000 (18:28 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Wed, 3 Jun 2026 14:02:58 +0000 (16:02 +0200)
$ 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.

src/basic/mountpoint-util.c
src/basic/mountpoint-util.h
src/repart/repart.c

index 7c043bb54a4b9b4b0a2866e4f6e4565f6e0ea649..72d2a01af55553c8f983746756c52dd9a80b7ce1 100644 (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
index 8cc966751c9c63a70600bf7270ea95644bcce0d8..dd27f36c2c3e7f93e387dced1abe18c827c54fc9 100644 (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);
 
index 6b0422dd6ccaaa4bdc7f3b05d307c89b6b725f1e..879a90e1836f268b83bac9f4bdde783c57db77da 100644 (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) {