diff --git a/src/internal/syscall/windows/at_windows.go b/src/internal/syscall/windows/at_windows.go index 064564c5e0..ad4a0ab25b 100644 --- a/src/internal/syscall/windows/at_windows.go +++ b/src/internal/syscall/windows/at_windows.go @@ -41,8 +41,12 @@ func Openat(dirfd syscall.Handle, name string, flag int, perm uint32) (_ syscall access |= FILE_GENERIC_WRITE } if flag&syscall.O_APPEND != 0 { - access &^= FILE_WRITE_DATA access |= FILE_APPEND_DATA + // Remove GENERIC_WRITE access unless O_TRUNC is set, + // in which case we need it to truncate the file. + if flag&syscall.O_TRUNC == 0 { + access &^= FILE_WRITE_DATA + } } if flag&O_DIRECTORY != 0 { options |= FILE_DIRECTORY_FILE diff --git a/src/internal/syscall/windows/at_windows_test.go b/src/internal/syscall/windows/at_windows_test.go new file mode 100644 index 0000000000..7da9ecf07a --- /dev/null +++ b/src/internal/syscall/windows/at_windows_test.go @@ -0,0 +1,58 @@ +// Copyright 2024 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. + +package windows_test + +import ( + "internal/syscall/windows" + "os" + "path/filepath" + "syscall" + "testing" +) + +func TestOpen(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + file := filepath.Join(dir, "a") + f, err := os.Create(file) + if err != nil { + t.Fatal(err) + } + f.Close() + + tests := []struct { + path string + flag int + err error + }{ + {dir, syscall.O_RDONLY, nil}, + {dir, syscall.O_CREAT, nil}, + {dir, syscall.O_RDONLY | syscall.O_CREAT, nil}, + {file, syscall.O_APPEND | syscall.O_WRONLY | os.O_CREATE, nil}, + {file, syscall.O_APPEND | syscall.O_WRONLY | os.O_CREATE | os.O_TRUNC, nil}, + {dir, syscall.O_RDONLY | syscall.O_TRUNC, syscall.ERROR_ACCESS_DENIED}, + {dir, syscall.O_WRONLY | syscall.O_RDWR, nil}, // TODO: syscall.Open returns EISDIR here, we should reconcile this + {dir, syscall.O_WRONLY, syscall.EISDIR}, + {dir, syscall.O_RDWR, syscall.EISDIR}, + } + for i, tt := range tests { + dir := filepath.Dir(tt.path) + dirfd, err := syscall.Open(dir, syscall.O_RDONLY, 0) + if err != nil { + t.Error(err) + continue + } + base := filepath.Base(tt.path) + h, err := windows.Openat(dirfd, base, tt.flag, 0o660) + syscall.CloseHandle(dirfd) + if err == nil { + syscall.CloseHandle(h) + } + if err != tt.err { + t.Errorf("%d: Open got %q, want %q", i, err, tt.err) + } + } +}