diff options
author | Ian Lance Taylor <iant@golang.org> | 2020-05-19 20:23:58 -0700 |
---|---|---|
committer | Ian Lance Taylor <iant@golang.org> | 2020-05-23 21:08:37 +0000 |
commit | f758dabf52d38333985e24762f9b53a29e2e7da0 (patch) | |
tree | e22ee99372c1c6022a36290e73b42ff3daf34daf | |
parent | f296b7a6f045325a230f77e9bda1470b1270f817 (diff) | |
download | go-f758dabf52d38333985e24762f9b53a29e2e7da0.tar.gz go-f758dabf52d38333985e24762f9b53a29e2e7da0.zip |
[release-branch.go1.14] 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.
For #38225
Fixes #39158
Change-Id: I70097fca8863df427cc8a97b9376a9ffc69c6318
Reviewed-on: https://go-review.googlesource.com/c/go/+/234534
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
(cherry picked from commit 567556d78657326c99b8fa84ec2a5ee511a0941b)
Reviewed-on: https://go-review.googlesource.com/c/go/+/234686
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
-rw-r--r-- | src/os/os_test.go | 35 | ||||
-rw-r--r-- | src/syscall/syscall_windows.go | 20 |
2 files changed, 55 insertions, 0 deletions
diff --git a/src/os/os_test.go b/src/os/os_test.go index 278c19e44b..31b3e67319 100644 --- a/src/os/os_test.go +++ b/src/os/os_test.go @@ -2448,3 +2448,38 @@ func TestDirSeek(t *testing.T) { } } } + +// Test that opening a file does not change its permissions. Issue 38225. +func TestOpenFileKeepsPermissions(t *testing.T) { + t.Parallel() + dir, err := ioutil.TempDir("", "TestOpenFileKeepsPermissions") + if err != nil { + t.Fatal(err) + } + defer RemoveAll(dir) + 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 950c281e4d..41bc42a44e 100644 --- a/src/syscall/syscall_windows.go +++ b/src/syscall/syscall_windows.go @@ -334,6 +334,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 |