From 567556d78657326c99b8fa84ec2a5ee511a0941b Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Tue, 19 May 2020 20:23:58 -0700 Subject: [PATCH] syscall: preserve Windows file permissions for O_CREAT|O_TRUNC On Windows, calling syscall.Open(file, O_CREAT|O_TRUNC, 0) for a file that already exists would change the file to be read-only. That is not how the Unix syscall.Open behaves, so avoid it on Windows by calling CreateFile twice if necessary. Fixes #38225 Change-Id: I70097fca8863df427cc8a97b9376a9ffc69c6318 Reviewed-on: https://go-review.googlesource.com/c/go/+/234534 Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot Reviewed-by: Alex Brainman --- src/os/os_test.go | 31 +++++++++++++++++++++++++++++++ src/syscall/syscall_windows.go | 20 ++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/os/os_test.go b/src/os/os_test.go index f86428b7b9..e8c64510f5 100644 --- a/src/os/os_test.go +++ b/src/os/os_test.go @@ -2539,3 +2539,34 @@ func isDeadlineExceeded(err error) bool { } return true } + +// Test that opening a file does not change its permissions. Issue 38225. +func TestOpenFileKeepsPermissions(t *testing.T) { + t.Parallel() + dir := t.TempDir() + name := filepath.Join(dir, "x") + f, err := Create(name) + if err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Error(err) + } + f, err = OpenFile(name, O_WRONLY|O_CREATE|O_TRUNC, 0) + if err != nil { + t.Fatal(err) + } + if fi, err := f.Stat(); err != nil { + t.Error(err) + } else if fi.Mode()&0222 == 0 { + t.Errorf("f.Stat.Mode after OpenFile is %v, should be writable", fi.Mode()) + } + if err := f.Close(); err != nil { + t.Error(err) + } + if fi, err := Stat(name); err != nil { + t.Error(err) + } else if fi.Mode()&0222 == 0 { + t.Errorf("Stat after OpenFile is %v, should be writable", fi.Mode()) + } +} diff --git a/src/syscall/syscall_windows.go b/src/syscall/syscall_windows.go index 89c0a930cb..f62c00d72f 100644 --- a/src/syscall/syscall_windows.go +++ b/src/syscall/syscall_windows.go @@ -339,6 +339,26 @@ func Open(path string, mode int, perm uint32) (fd Handle, err error) { var attrs uint32 = FILE_ATTRIBUTE_NORMAL if perm&S_IWRITE == 0 { attrs = FILE_ATTRIBUTE_READONLY + if createmode == CREATE_ALWAYS { + // We have been asked to create a read-only file. + // If the file already exists, the semantics of + // the Unix open system call is to preserve the + // existing permissions. If we pass CREATE_ALWAYS + // and FILE_ATTRIBUTE_READONLY to CreateFile, + // and the file already exists, CreateFile will + // change the file permissions. + // Avoid that to preserve the Unix semantics. + h, e := CreateFile(pathp, access, sharemode, sa, TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) + switch e { + case ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, ERROR_PATH_NOT_FOUND: + // File does not exist. These are the same + // errors as Errno.Is checks for ErrNotExist. + // Carry on to create the file. + default: + // Success or some different error. + return h, e + } + } } h, e := CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0) return h, e -- 2.48.1