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
--- /dev/null
+// 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)
+ }
+ }
+}