Subject: mv cannot move a file from a Linux "portal" file system

From: Bruno Haible <[email-masked]>
Date: Tue, 10 Dec 2024 14:59:22 +0100

Hi,

On Linux (Linux 5.15.0) I have a file /run/user/1000/doc/1316905f/jas-key.gpg
(created by visiting https://savannah.gnu.org/users/jas in Firefox and clicking
the "Download GPG Key" in that page).

It appears to be a regular file:

$ ls -l /run/user/1000/doc/1316905f/jas-key.gpg
-rw-rw-r-- 1 bruno bruno 7335 Dec 10 14:41 /run/user/1000/doc/1316905f/jas-key.gpg

It sits on a file system of type "portal":

$ (cd /run/user/1000/doc/1316905f/ && df -m .)
Filesystem     1M-blocks    Used Available Use% Mounted on
portal           1290923 1218257      7019 100% /run/user/1000/doc

I'm using 'mv' from GNU coreutils:

$ LC_ALL=C mv --version
mv (GNU coreutils) 9.4
...

Moving the file to an ext4 file system fails:

$ mv /run/user/1000/doc/1316905f/jas-key.gpg .
mv: cannot open '/run/user/1000/doc/1316905f/jas-key.gpg' for reading: Too many levels of symbolic links

Look at the system calls:

$ strace mv /run/user/1000/doc/1316905f/jas-key.gpg .
execve("/arch/local/x86_64-linux/bin/mv", ["mv", "/run/user/1000/doc/1316905f/jas-"..., "."], 0x7ffd1f57d410 /* 89 vars */) = 0
brk(NULL)                               = 0x14a8000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffcdeb8fe90) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0e9e8f3000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=134075, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 134075, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f0e9e8d2000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=166280, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 177672, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0e9e8a6000
mprotect(0x7f0e9e8ac000, 139264, PROT_NONE) = 0
mmap(0x7f0e9e8ac000, 106496, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6000) = 0x7f0e9e8ac000
mmap(0x7f0e9e8c6000, 28672, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x20000) = 0x7f0e9e8c6000
mmap(0x7f0e9e8ce000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x27000) = 0x7f0e9e8ce000
mmap(0x7f0e9e8d0000, 5640, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f0e9e8d0000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libacl.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=34888, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 36896, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0e9e89c000
mprotect(0x7f0e9e89e000, 24576, PROT_NONE) = 0
mmap(0x7f0e9e89e000, 16384, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f0e9e89e000
mmap(0x7f0e9e8a2000, 4096, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6000) = 0x7f0e9e8a2000
mmap(0x7f0e9e8a4000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x7000) = 0x7f0e9e8a4000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=26696, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 28696, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0e9e894000
mmap(0x7f0e9e896000, 12288, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f0e9e896000
mmap(0x7f0e9e899000, 4096, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x5000) = 0x7f0e9e899000
mmap(0x7f0e9e89a000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x5000) = 0x7f0e9e89a000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0I\17\357\204\3$\f\221\2039x\324\224\323\236S"..., 68, 896) = 68
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=2220400, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2264656, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0e9e66b000
mprotect(0x7f0e9e693000, 2023424, PROT_NONE) = 0
mmap(0x7f0e9e693000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7f0e9e693000
mmap(0x7f0e9e828000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7f0e9e828000
mmap(0x7f0e9e881000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x215000) = 0x7f0e9e881000
mmap(0x7f0e9e887000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f0e9e887000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpcre2-8.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=613064, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 615184, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0e9e5d4000
mmap(0x7f0e9e5d6000, 438272, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f0e9e5d6000
mmap(0x7f0e9e641000, 163840, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6d000) = 0x7f0e9e641000
mmap(0x7f0e9e669000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x94000) = 0x7f0e9e669000
close(3)                                = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0e9e5d2000
arch_prctl(ARCH_SET_FS, 0x7f0e9e5d3340) = 0
set_tid_address(0x7f0e9e5d3610)         = 3802617
set_robust_list(0x7f0e9e5d3620, 24)     = 0
rseq(0x7f0e9e5d3ce0, 0x20, 0, 0x53053053) = 0
mprotect(0x7f0e9e881000, 16384, PROT_READ) = 0
mprotect(0x7f0e9e669000, 4096, PROT_READ) = 0
mprotect(0x7f0e9e89a000, 4096, PROT_READ) = 0
mprotect(0x7f0e9e8a4000, 4096, PROT_READ) = 0
mprotect(0x7f0e9e8ce000, 4096, PROT_READ) = 0
mprotect(0x423000, 4096, PROT_READ)     = 0
mprotect(0x7f0e9e92d000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7f0e9e8d2000, 134075)          = 0
statfs("/sys/fs/selinux", 0x7ffcdeb8fed0) = -1 ENOENT (No such file or directory)
statfs("/selinux", 0x7ffcdeb8fed0)      = -1 ENOENT (No such file or directory)
getrandom("\xc1\x7e\x88\x97\xcb\xb4\x9e\x85", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x14a8000
brk(0x14c9000)                          = 0x14c9000
openat(AT_FDCWD, "/proc/filesystems", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0444, st_size=0, ...}, AT_EMPTY_PATH) = 0
read(3, "nodev\tsysfs\nnodev\ttmpfs\nnodev\tbd"..., 1024) = 505
read(3, "", 1024)                       = 0
close(3)                                = 0
access("/etc/selinux/config", F_OK)     = -1 ENOENT (No such file or directory)
geteuid()                               = 1000
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
renameat2(AT_FDCWD, "/run/user/1000/doc/1316905f/jas-key.gpg", AT_FDCWD, ".", RENAME_NOREPLACE) = -1 EXDEV (Invalid cross-device link)
renameat2(AT_FDCWD, "/run/user/1000/doc/1316905f/jas-key.gpg", AT_FDCWD, "jas-key.gpg", RENAME_NOREPLACE) = -1 EXDEV (Invalid cross-device link)
newfstatat(AT_FDCWD, "/run/user/1000/doc/1316905f/jas-key.gpg", {st_mode=S_IFREG|0664, st_size=7335, ...}, AT_SYMLINK_NOFOLLOW) = 0
newfstatat(AT_FDCWD, "jas-key.gpg", 0x7ffcdeb8fb30, AT_SYMLINK_NOFOLLOW) = -1 ENOENT (No such file or directory)
unlinkat(AT_FDCWD, "jas-key.gpg", 0)    = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/run/user/1000/doc/1316905f/jas-key.gpg", O_RDONLY|O_NOFOLLOW) = -1 ELOOP (Too many levels of symbolic links)
write(2, "mv: ", 4mv: )                     = 4
write(2, "cannot open '/run/user/1000/doc/"..., 65cannot open '/run/user/1000/doc/1316905f/jas-key.gpg' for reading) = 65
write(2, ": Too many levels of symbolic li"..., 35: Too many levels of symbolic links) = 35
write(2, "\n", 1
)                       = 1
lseek(0, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
close(0)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(1)                           = ?
+++ exited with 1 +++

I don't understand the result of the openat() system call.

$ cp /run/user/1000/doc/1316905f/jas-key.gpg .
works fine.

Bruno