Skip to content

Commit 567556d

Browse files
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Alex Brainman <[email protected]>
1 parent aeab403 commit 567556d

File tree

2 files changed

+51
-0
lines changed

2 files changed

+51
-0
lines changed

src/os/os_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2539,3 +2539,34 @@ func isDeadlineExceeded(err error) bool {
25392539
}
25402540
return true
25412541
}
2542+
2543+
// Test that opening a file does not change its permissions. Issue 38225.
2544+
func TestOpenFileKeepsPermissions(t *testing.T) {
2545+
t.Parallel()
2546+
dir := t.TempDir()
2547+
name := filepath.Join(dir, "x")
2548+
f, err := Create(name)
2549+
if err != nil {
2550+
t.Fatal(err)
2551+
}
2552+
if err := f.Close(); err != nil {
2553+
t.Error(err)
2554+
}
2555+
f, err = OpenFile(name, O_WRONLY|O_CREATE|O_TRUNC, 0)
2556+
if err != nil {
2557+
t.Fatal(err)
2558+
}
2559+
if fi, err := f.Stat(); err != nil {
2560+
t.Error(err)
2561+
} else if fi.Mode()&0222 == 0 {
2562+
t.Errorf("f.Stat.Mode after OpenFile is %v, should be writable", fi.Mode())
2563+
}
2564+
if err := f.Close(); err != nil {
2565+
t.Error(err)
2566+
}
2567+
if fi, err := Stat(name); err != nil {
2568+
t.Error(err)
2569+
} else if fi.Mode()&0222 == 0 {
2570+
t.Errorf("Stat after OpenFile is %v, should be writable", fi.Mode())
2571+
}
2572+
}

src/syscall/syscall_windows.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,26 @@ func Open(path string, mode int, perm uint32) (fd Handle, err error) {
339339
var attrs uint32 = FILE_ATTRIBUTE_NORMAL
340340
if perm&S_IWRITE == 0 {
341341
attrs = FILE_ATTRIBUTE_READONLY
342+
if createmode == CREATE_ALWAYS {
343+
// We have been asked to create a read-only file.
344+
// If the file already exists, the semantics of
345+
// the Unix open system call is to preserve the
346+
// existing permissions. If we pass CREATE_ALWAYS
347+
// and FILE_ATTRIBUTE_READONLY to CreateFile,
348+
// and the file already exists, CreateFile will
349+
// change the file permissions.
350+
// Avoid that to preserve the Unix semantics.
351+
h, e := CreateFile(pathp, access, sharemode, sa, TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
352+
switch e {
353+
case ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, ERROR_PATH_NOT_FOUND:
354+
// File does not exist. These are the same
355+
// errors as Errno.Is checks for ErrNotExist.
356+
// Carry on to create the file.
357+
default:
358+
// Success or some different error.
359+
return h, e
360+
}
361+
}
342362
}
343363
h, e := CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
344364
return h, e

0 commit comments

Comments
 (0)