aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2020-05-19 20:23:58 -0700
committerIan Lance Taylor <iant@golang.org>2020-05-23 21:08:37 +0000
commitf758dabf52d38333985e24762f9b53a29e2e7da0 (patch)
treee22ee99372c1c6022a36290e73b42ff3daf34daf
parentf296b7a6f045325a230f77e9bda1470b1270f817 (diff)
downloadgo-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.go35
-rw-r--r--src/syscall/syscall_windows.go20
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