Skip to content

Commit 16f0f9c

Browse files
committed
syscall: respect permission bits on file opening on Windows
On Windows, os.Chmod and syscall.Chmod toggle the FILE_ATTRIBUTES_ READONLY flag depending on the permission bits. That's a bit odd but I guess some compromises were made at some point and this is what was chosen to map to a Unix concept that Windows doesn't really have in the same way. That's fine. However, the logic used in Chmod was forgotten from os.Open and syscall.Open, which then manifested itself in various places, most recently, go modules' read-only behavior. This makes syscall.Open consistent with syscall.Chmod and adds a test for the permission _behavior_ using ioutil. By testing the behavior instead of explicitly testing for the attribute bits we care about, we make sure this doesn't regress in unforeseen ways in the future, as well as ensuring the test works on platforms other than Windows. In the process, we fix some tests that never worked and relied on broken behavior, as well as tests that were disabled on Windows due to the broken behavior and had TODO notes. Fixes #35033 Change-Id: I6f7cf54517cbe5f6b1678d1c24f2ab337edcc7f7 Reviewed-on: https://go-review.googlesource.com/c/go/+/202439 Run-TryBot: Jason A. Donenfeld <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]> Reviewed-by: Alex Brainman <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 7f98c0e commit 16f0f9c

File tree

4 files changed

+43
-7
lines changed

4 files changed

+43
-7
lines changed

Diff for: src/cmd/cover/cover_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ func TestHtmlUnformatted(t *testing.T) {
457457
t.Fatal(err)
458458
}
459459

460-
if err := ioutil.WriteFile(filepath.Join(htmlUDir, "go.mod"), []byte("module htmlunformatted\n"), 0444); err != nil {
460+
if err := ioutil.WriteFile(filepath.Join(htmlUDir, "go.mod"), []byte("module htmlunformatted\n"), 0666); err != nil {
461461
t.Fatal(err)
462462
}
463463

@@ -540,7 +540,7 @@ func TestFuncWithDuplicateLines(t *testing.T) {
540540
t.Fatal(err)
541541
}
542542

543-
if err := ioutil.WriteFile(filepath.Join(lineDupDir, "go.mod"), []byte("module linedup\n"), 0444); err != nil {
543+
if err := ioutil.WriteFile(filepath.Join(lineDupDir, "go.mod"), []byte("module linedup\n"), 0666); err != nil {
544544
t.Fatal(err)
545545
}
546546
if err := ioutil.WriteFile(lineDupGo, []byte(lineDupContents), 0444); err != nil {

Diff for: src/cmd/go/testdata/script/mod_cache_rw.txt

+5-4
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ cp $WORK/extraneous.txt $GOPATH/pkg/mod/rsc.io/[email protected]/extraneous_file.go
1313

1414
# However, files within those directories should still be read-only to avoid
1515
# accidental mutations.
16-
# TODO: Today, this does not seem to be effective on Windows.
17-
# (https://golang.org/issue/35033)
18-
[!windows] [!root] ! cp $WORK/extraneous.txt $GOPATH/pkg/mod/rsc.io/[email protected]/go.mod
16+
[!root] ! cp $WORK/extraneous.txt $GOPATH/pkg/mod/rsc.io/[email protected]/go.mod
1917

2018
# If all 'go' commands ran with the flag, the system's 'rm' binary
2119
# should be able to remove the module cache if the '-rf' flags are set.
@@ -27,8 +25,11 @@ cp $WORK/extraneous.txt $GOPATH/pkg/mod/rsc.io/[email protected]/extraneous_file.go
2725

2826
# The directories in the module cache should by default be unwritable,
2927
# so that tests and tools will not accidentally add extraneous files to them.
28+
# Windows does not respect FILE_ATTRIBUTE_READONLY on directories, according
29+
# to MSDN, so there we disable testing whether the directory itself is
30+
# unwritable.
3031
go get -d rsc.io/quote@latest
31-
[!windows] [!root] ! cp $WORK/extraneous.txt $GOPATH/pkg/mod/rsc.io/[email protected]/go.mod
32+
[!root] ! cp $WORK/extraneous.txt $GOPATH/pkg/mod/rsc.io/[email protected]/go.mod
3233
[!windows] [!root] ! cp $WORK/extraneous.txt $GOPATH/pkg/mod/rsc.io/[email protected]/extraneous_file.go
3334
! exists $GOPATH/pkg/mod/rsc.io/[email protected]/extraneous_file.go
3435

Diff for: src/io/ioutil/ioutil_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
package ioutil
66

77
import (
8+
"bytes"
89
"os"
10+
"path/filepath"
911
"testing"
1012
)
1113

@@ -63,6 +65,35 @@ func TestWriteFile(t *testing.T) {
6365
os.Remove(filename) // ignore error
6466
}
6567

68+
func TestReadOnlyWriteFile(t *testing.T) {
69+
if os.Getuid() == 0 {
70+
t.Skipf("Root can write to read-only files anyway, so skip the read-only test.")
71+
}
72+
73+
// We don't want to use TempFile directly, since that opens a file for us as 0600.
74+
tempDir, err := TempDir("", t.Name())
75+
defer os.RemoveAll(tempDir)
76+
filename := filepath.Join(tempDir, "blurp.txt")
77+
78+
shmorp := []byte("shmorp")
79+
florp := []byte("florp")
80+
err = WriteFile(filename, shmorp, 0444)
81+
if err != nil {
82+
t.Fatalf("WriteFile %s: %v", filename, err)
83+
}
84+
err = WriteFile(filename, florp, 0444)
85+
if err == nil {
86+
t.Fatalf("Expected an error when writing to read-only file %s", filename)
87+
}
88+
got, err := ReadFile(filename)
89+
if err != nil {
90+
t.Fatalf("ReadFile %s: %v", filename, err)
91+
}
92+
if !bytes.Equal(got, shmorp) {
93+
t.Fatalf("want %s, got %s", shmorp, got)
94+
}
95+
}
96+
6697
func TestReadDir(t *testing.T) {
6798
dirname := "rumpelstilzchen"
6899
_, err := ReadDir(dirname)

Diff for: src/syscall/syscall_windows.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,11 @@ func Open(path string, mode int, perm uint32) (fd Handle, err error) {
312312
default:
313313
createmode = OPEN_EXISTING
314314
}
315-
h, e := CreateFile(pathp, access, sharemode, sa, createmode, FILE_ATTRIBUTE_NORMAL, 0)
315+
var attrs uint32 = FILE_ATTRIBUTE_NORMAL
316+
if perm&S_IWRITE == 0 {
317+
attrs = FILE_ATTRIBUTE_READONLY
318+
}
319+
h, e := CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
316320
return h, e
317321
}
318322

0 commit comments

Comments
 (0)