serve nfs: fix EIO when creating symlinks with --vfs-links

The macOS NFS client sends SETATTR after SYMLINK, which arrives as
Chmod/Chown on the link path. These opened the target with vfs.Open,
which follows symlinks - a freshly created symlink usually dangles, so
the open failed with ENOENT, surfaced to the client as NFS3ERR_IO even
though the link was created.

Add path-based VFS.Chmod and VFS.Chown mirroring VFS.Chtimes. They do
not follow symlinks (lstat semantics, matching VFS.Stat) and return
ENOSYS when the node exists, since the VFS stores neither permissions
nor ownership; serve nfs calls them and masks ENOSYS as before.

Fixes #9627
This commit is contained in:
SillyZir
2026-08-01 12:17:20 +01:00
committed by Nick Craig-Wood
parent 060b997595
commit 7804c1b315
3 changed files with 79 additions and 19 deletions
+32
View File
@@ -745,6 +745,38 @@ func (vfs *VFS) Chtimes(name string, atime time.Time, mtime time.Time) error {
return nil
}
// Chmod changes the mode of the named file.
//
// If name is a symlink the mode of the link itself is changed, not
// its target (like lchmod). It does not follow the link, so it works
// on symlinks whose target doesn't exist.
//
// The VFS doesn't store file permissions so currently this returns
// ENOSYS if the file exists and ENOENT if it doesn't.
func (vfs *VFS) Chmod(name string, mode os.FileMode) error {
_, err := vfs.Stat(name)
if err != nil {
return err
}
return ENOSYS
}
// Chown changes the uid and gid of the named file.
//
// If name is a symlink the ownership of the link itself is changed,
// not its target (like lchown). It does not follow the link, so it
// works on symlinks whose target doesn't exist.
//
// The VFS doesn't store file ownership so currently this returns
// ENOSYS if the file exists and ENOENT if it doesn't.
func (vfs *VFS) Chown(name string, uid, gid int) error {
_, err := vfs.Stat(name)
if err != nil {
return err
}
return ENOSYS
}
// mkdir creates a new directory with the specified name and permission bits
// (before umask) returning the new directory node.
func (vfs *VFS) mkdir(name string, perm os.FileMode) (*Dir, error) {