bug#13601: mv should not silently lose file extended attributes

Collin Funk <[email protected]>
Newsgroups gmane.comp.gnu.core-utils.bugs
Message-ID <[email protected]>
Jean-Francois Dockes via GNU coreutils Bug Reports
<[email protected]> writes:

> Paul Eggert writes:
>  > On 12/11/23 12:03, Abraham S.A.H. via GNU coreutils Bug Reports wrote:
>  > > a sane default behaviour regarding extended attributes in mv and others?
>  > 
>  > What's wrong with the default behavior in current GNU mv? Please give a 
>  > specific example (specify platform, filesystems, mv version, etc.).
>
> mv still silently discards the extended attributes when asked to move a file from
> e.g. an ext4 fs to e.g., a VFAT one.
>
> Again: the data is gone. mv lost the data.
>
> For example (on Ubuntu 26.04, but this is probably irrelevant):
>
> hm3$ mv --version
> mv (GNU coreutils) 9.7
> Packaged by Ubuntu (9.7-3ubuntu2)
> Copyright (C) 2025 Free Software Foundation, Inc.
> License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
> This is free software: you are free to change and redistribute it.
> There is NO WARRANTY, to the extent permitted by law.
>
> Written by Mike Parker, David MacKenzie, and Jim Meyering.
>
> hm3$ getfattr myfile.txt 
> # file: myfile.txt
> user.myattr
>
> hm3$ mv myfile.txt /run/media/dockes/C052-0E64/
>
> hm3$ getfattr /run/media/dockes/C052-0E64/myfile.txt 
>
> hm3$ mv /run/media/dockes/C052-0E64/myfile.txt .
>
> hm3$ getfattr myfile.txt 
>
>
> mv just casually discarding data...
>
> You are asking what's wrong. IMO this is wrong.
>
> If the target file system is full ("No space left on device"), mv keeps the
> original file. It should do the same for an extended attribute problem.

Just to avoid confusing any other readers, I want to clarify that the
"data loss" mentioned here is the extended attributes of the given file,
not the file contents. E.g., see the result of the following commands:

    $ echo a > a && setfattr --name=user.charset --value=utf-8 a \
        && mv a /mnt/vfat-mount/ \
        && getfattr --encoding=text --dump /mnt/vfat-mount/a \
        && cat /mnt/vfat-mount/a
    a

This is because VFAT does not support extended attributes. You can see
the behavior by adding 'strace' before the 'mv' invocation:

    renameat2(AT_FDCWD, "a", AT_FDCWD, "/mnt/vfat-mount/", RENAME_NOREPLACE) = -1 EEXIST (File exists)
    openat(AT_FDCWD, "/mnt/vfat-mount/", O_RDONLY|O_PATH|O_DIRECTORY) = 3
    renameat2(AT_FDCWD, "a", 3, "a", RENAME_NOREPLACE) = -1 EXDEV (Invalid cross-device link)
    newfstatat(AT_FDCWD, "a", {st_mode=S_IFREG|0644, st_size=2, ...}, AT_SYMLINK_NOFOLLOW) = 0
    newfstatat(3, "a", {st_mode=S_IFREG|0755, st_size=2, ...}, AT_SYMLINK_NOFOLLOW) = 0
    geteuid()                               = 0
    renameat(AT_FDCWD, "a", 3, "a")         = -1 EXDEV (Invalid cross-device link)
    unlinkat(3, "a", 0)                     = 0
    lgetxattr("a", "security.selinux", "unconfined_u:object_r:admin_home"..., 255) = 38
    futex(0x7f275cc00580, FUTEX_WAKE_PRIVATE, 2147483647) = 0
    openat(AT_FDCWD, "/proc/thread-self/attr/fscreate", O_RDWR|O_CLOEXEC) = 4
    write(4, "unconfined_u:object_r:admin_home"..., 38) = 38
    close(4)                                = 0
    openat(AT_FDCWD, "a", O_RDONLY|O_NOFOLLOW) = 4
    fstat(4, {st_mode=S_IFREG|0644, st_size=2, ...}) = 0
    openat(3, "a", O_WRONLY|O_CREAT|O_EXCL, 0600) = 5
    ioctl(5, BTRFS_IOC_CLONE or FICLONE, 4) = -1 EXDEV (Invalid cross-device link)
    fstat(5, {st_mode=S_IFREG|0755, st_size=0, ...}) = 0
    copy_file_range(4, NULL, 5, NULL, 9223372035781033984, 0) = -1 EXDEV (Invalid cross-device link)
    mmap(NULL, 270336, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f275c89d000
    read(4, "a\n", 262144)                  = 2
    write(5, "a\n", 2)                      = 2
    read(4, "", 262144)                     = 0
    munmap(0x7f275c89d000, 270336)          = 0
    utimensat(5, NULL, [{tv_sec=1786821834, tv_nsec=183963469} /* 2026-08-15T12:23:54.183963469-0700 */, {tv_sec=1786821834, tv_nsec=183963469} /* 2026-08-15T12:23:54.183963469-0700 */], 0) = 0
    flistxattr(4, "user.charset\0security.selinux\0", 511) = 30
    openat(AT_FDCWD, "/etc/xattr.conf", O_RDONLY) = 6
    fstat(6, {st_mode=S_IFREG|0644, st_size=817, ...}) = 0
    read(6, "# /etc/xattr.conf\n#\n# Format:\n# "..., 4096) = 817
    read(6, "", 4096)                       = 0
    close(6)                                = 0
    openat(AT_FDCWD, "/usr/lib64/gconv/gconv-modules.cache", O_RDONLY|O_CLOEXEC) = 6
    fstat(6, {st_mode=S_IFREG|0644, st_size=27012, ...}) = 0
    mmap(NULL, 27012, PROT_READ, MAP_SHARED, 6, 0) = 0x7f275cc37000
    close(6)                                = 0
    futex(0x7f275cbb26ec, FUTEX_WAKE_PRIVATE, 2147483647) = 0
    fgetxattr(4, "user.charset", "utf-8", 512) = 5
    fsetxattr(5, "user.charset", "utf-8", 5, 0) = -1 EOPNOTSUPP (Operation not supported)

Are you suggesting we remove the moved file after fsetxattr fails and
preserve a copy to restore the orignal file that was renamed with its
extended attributes intact? That behavior seems a bit strange to me...
Why not just move the file as the user requested? It seems strange to me
that a user would depend on extended attributes, but use a file system
that does not support them...

Collin
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.