| // Copyright 2026 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build unix || wasip1 |
| |
| package unix_test |
| |
| import ( |
| "internal/syscall/unix" |
| "os" |
| "runtime" |
| "testing" |
| ) |
| |
| // TestFchmodAtSymlinkNofollow verifies that Fchmodat honors the AT_SYMLINK_NOFOLLOW flag. |
| func TestFchmodatSymlinkNofollow(t *testing.T) { |
| if runtime.GOOS == "wasip1" { |
| t.Skip("wasip1 doesn't support chmod") |
| } |
| |
| dir := t.TempDir() |
| filename := dir + "/file" |
| linkname := dir + "/symlink" |
| if err := os.WriteFile(filename, nil, 0o100); err != nil { |
| t.Fatal(err) |
| } |
| if err := os.Symlink(filename, linkname); err != nil { |
| t.Fatal(err) |
| } |
| |
| parent, err := os.Open(dir) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer parent.Close() |
| |
| lstatMode := func(path string) os.FileMode { |
| st, err := os.Lstat(path) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return st.Mode() |
| } |
| |
| // Fchmodat with no flags follows symlinks. |
| const mode1 = 0o200 |
| if err := unix.Fchmodat(int(parent.Fd()), "symlink", mode1, 0); err != nil { |
| t.Fatal(err) |
| } |
| if got, want := lstatMode(filename), os.FileMode(mode1); got != want { |
| t.Errorf("after Fchmodat(parent, symlink, %v, 0); mode = %v, want %v", mode1, got, want) |
| } |
| |
| // Fchmodat with AT_SYMLINK_NOFOLLOW does not follow symlinks. |
| // The Fchmodat call may fail or chmod the symlink itself, depending on the kernel version. |
| const mode2 = 0o400 |
| unix.Fchmodat(int(parent.Fd()), "symlink", mode2, unix.AT_SYMLINK_NOFOLLOW) |
| if got, want := lstatMode(filename), os.FileMode(mode1); got != want { |
| t.Errorf("after Fchmodat(parent, symlink, %v, AT_SYMLINK_NOFOLLOW); mode = %v, want %v", mode1, got, want) |
| } |
| } |