summaryrefslogtreecommitdiff
path: root/include/linux/timerqueue.h
diff options
context:
space:
mode:
authorMichael Nemanov <michael.nemanov@vastdata.com>2026-08-06 13:13:58 +0000
committerTrond Myklebust <trond.myklebust@hammerspace.com>2026-08-17 09:02:08 -0700
commit10f307e525a1783570a39eb9ac146d45f4f16b3e (patch)
tree266a324f5a9f62d1ef8b14bbab656b02e0222218 /include/linux/timerqueue.h
parenta89dd597458848b463d284b15e42a8078beeb046 (diff)
nfs: fix ENXIO on O_CREAT open of existing symlink over NFSv3
When open(2) is called with O_CREAT on a path that already exists as a symlink, over an NFSv3 mount with a cold dcache, the kernel returns ENXIO instead of following the symlink to its target. Reproducer script (MNT is an NFSv3 mount, kernel is 7.1-rc6): MNT=/mnt/export ln -sf /tmp/target $MNT/mylink echo 3 | sudo tee /proc/sys/vm/drop_caches # cold dcache python3 - <<'EOF' import os fd = os.open('/mnt/export/mylink', os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o666) os.close(fd) EOF Expected: success (follow symlink, open target) Actual: OSError: [Errno 6] No such device or address The bug does not trigger when the dcache is warm (e.g. after a prior stat(2)), because lookup_open() then finds a positive dentry and skips atomic_open entirely, leaving symlink resolution to the VFS. Root cause: nfs_atomic_open_v23(), registered as inode->i_op->atomic_open for NFSv3, handles O_CREAT by sending a CREATE UNCHECKED RPC. As implemented in nfsd3_create_file() (fs/nfsd/nfs3proc.c) and as required by RFC 1813 (3.3.8), when the name already exists as a non-regular file the server returns NFS3_OK with the existing object's file handle rather than NFS3ERR_EXIST causing nfs_do_create() to return 0 with the dentry now pointing to a symlink. The code then unconditionally calls finish_open(), which dispatches through inode->i_fop->open(). Symlink inodes never have i_fop set — the VFS initialises it to &no_open_fops because POSIX requires open(2) to follow symlinks, never open them directly. no_open() returns -ENXIO. Fix: After nfs_do_create() succeeds, verify the returned inode is a regular file before calling finish_open(). If the object is not regular, return finish_no_open(file, NULL) so the VFS follows the symlink through the normal open path. NULL is passed because nfs_do_create() instantiates the inode on the dentry already owned by the caller; passing dentry back would cause atomic_open() to dput() it a second time. !S_ISREG() is used rather than S_ISLNK() to cover any other non-regular types a server might return. Changes in v2: - Pass NULL to finish_no_open() per Trond's feedback. Fixes: 7c6c5249f061 ("NFS: add atomic_open for NFSv3 to handle O_TRUNC correctly.") Link: https://lore.kernel.org/linux-nfs/20260614122911.3485467-1-michael.nemanov@vastdata.com/ (v1) Signed-off-by: Michael Nemanov <michael.nemanov@vastdata.com> Tested-by: Michael Nemanov <michael.nemanov@vastdata.com> [trond.myklebust@hammerspace.com: use d_is_reg() to catch negative dentries] Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Diffstat (limited to 'include/linux/timerqueue.h')
0 files changed, 0 insertions, 0 deletions