@@ -15,96 +15,105 @@ func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
15
15
if name == "" {
16
16
return nil , & os.PathError {Op : "open" , Path : name , Err : ENOENT }
17
17
}
18
- r , e := syscallOpen (name , flag | O_CLOEXEC , uint32 (perm .Perm ()))
19
- if e != nil {
20
- return nil , & os.PathError {Op : "open" , Path : name , Err : e }
18
+ r , err := syscallOpen (name , flag | O_CLOEXEC , uint32 (perm .Perm ()))
19
+ if err != nil {
20
+ return nil , & os.PathError {Op : "open" , Path : name , Err : err }
21
21
}
22
22
return os .NewFile (uintptr (r ), name ), nil
23
23
}
24
24
25
25
// syscallOpen is a copy of [syscall.Open]
26
- // that uses [syscall.FILE_SHARE_DELETE].
26
+ // that uses [syscall.FILE_SHARE_DELETE],
27
+ // and supports [syscall.FILE_FLAG_OVERLAPPED].
27
28
//
28
29
// https://go.dev/src/syscall/syscall_windows.go
29
- func syscallOpen (path string , mode int , perm uint32 ) (fd Handle , err error ) {
30
- if len (path ) == 0 {
30
+ func syscallOpen (name string , flag int , perm uint32 ) (fd Handle , err error ) {
31
+ if len (name ) == 0 {
31
32
return InvalidHandle , ERROR_FILE_NOT_FOUND
32
33
}
33
- pathp , err := UTF16PtrFromString (path )
34
+ namep , err := UTF16PtrFromString (name )
34
35
if err != nil {
35
36
return InvalidHandle , err
36
37
}
37
38
var access uint32
38
- switch mode & (O_RDONLY | O_WRONLY | O_RDWR ) {
39
+ switch flag & (O_RDONLY | O_WRONLY | O_RDWR ) {
39
40
case O_RDONLY :
40
41
access = GENERIC_READ
41
42
case O_WRONLY :
42
43
access = GENERIC_WRITE
43
44
case O_RDWR :
44
45
access = GENERIC_READ | GENERIC_WRITE
45
46
}
46
- if mode & O_CREAT != 0 {
47
+ if flag & O_CREAT != 0 {
47
48
access |= GENERIC_WRITE
48
49
}
49
- if mode & O_APPEND != 0 {
50
- access &^= GENERIC_WRITE
51
- access |= FILE_APPEND_DATA
50
+ if flag & O_APPEND != 0 {
51
+ // Remove GENERIC_WRITE unless O_TRUNC is set, in which case we need it to truncate the file.
52
+ // We can't just remove FILE_WRITE_DATA because GENERIC_WRITE without FILE_WRITE_DATA
53
+ // starts appending at the beginning of the file rather than at the end.
54
+ if flag & O_TRUNC == 0 {
55
+ access &^= GENERIC_WRITE
56
+ }
57
+ // Set all access rights granted by GENERIC_WRITE except for FILE_WRITE_DATA.
58
+ access |= FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | _FILE_WRITE_EA | STANDARD_RIGHTS_WRITE | SYNCHRONIZE
52
59
}
53
60
sharemode := uint32 (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE )
54
61
var sa * SecurityAttributes
55
- if mode & O_CLOEXEC == 0 {
62
+ if flag & O_CLOEXEC == 0 {
56
63
sa = makeInheritSa ()
57
64
}
65
+ // We don't use CREATE_ALWAYS, because when opening a file with
66
+ // FILE_ATTRIBUTE_READONLY these will replace an existing file
67
+ // with a new, read-only one. See https://go.dev/issue/38225.
68
+ //
69
+ // Instead, we ftruncate the file after opening when O_TRUNC is set.
58
70
var createmode uint32
59
71
switch {
60
- case mode & (O_CREAT | O_EXCL ) == (O_CREAT | O_EXCL ):
72
+ case flag & (O_CREAT | O_EXCL ) == (O_CREAT | O_EXCL ):
61
73
createmode = CREATE_NEW
62
- case mode & (O_CREAT | O_TRUNC ) == (O_CREAT | O_TRUNC ):
63
- createmode = CREATE_ALWAYS
64
- case mode & O_CREAT == O_CREAT :
74
+ case flag & O_CREAT == O_CREAT :
65
75
createmode = OPEN_ALWAYS
66
- case mode & O_TRUNC == O_TRUNC :
67
- createmode = TRUNCATE_EXISTING
68
76
default :
69
77
createmode = OPEN_EXISTING
70
78
}
71
79
var attrs uint32 = FILE_ATTRIBUTE_NORMAL
72
80
if perm & S_IWRITE == 0 {
73
81
attrs = FILE_ATTRIBUTE_READONLY
74
- if createmode == CREATE_ALWAYS {
75
- const _ERROR_BAD_NETPATH = Errno (53 )
76
- // We have been asked to create a read-only file.
77
- // If the file already exists, the semantics of
78
- // the Unix open system call is to preserve the
79
- // existing permissions. If we pass CREATE_ALWAYS
80
- // and FILE_ATTRIBUTE_READONLY to CreateFile,
81
- // and the file already exists, CreateFile will
82
- // change the file permissions.
83
- // Avoid that to preserve the Unix semantics.
84
- h , e := CreateFile (pathp , access , sharemode , sa , TRUNCATE_EXISTING , FILE_ATTRIBUTE_NORMAL , 0 )
85
- switch e {
86
- case ERROR_FILE_NOT_FOUND , _ERROR_BAD_NETPATH , ERROR_PATH_NOT_FOUND :
87
- // File does not exist. These are the same
88
- // errors as Errno.Is checks for ErrNotExist.
89
- // Carry on to create the file.
90
- default :
91
- // Success or some different error.
92
- return h , e
93
- }
94
- }
95
82
}
96
- if createmode == OPEN_EXISTING && access == GENERIC_READ {
97
- // Necessary for opening directory handles.
83
+ if flag & O_WRONLY == 0 && flag & O_RDWR == 0 {
84
+ // We might be opening or creating a directory.
85
+ // CreateFile requires FILE_FLAG_BACKUP_SEMANTICS
86
+ // to work with directories.
98
87
attrs |= FILE_FLAG_BACKUP_SEMANTICS
99
88
}
100
- if mode & O_SYNC != 0 {
89
+ if flag & O_SYNC != 0 {
101
90
const _FILE_FLAG_WRITE_THROUGH = 0x80000000
102
91
attrs |= _FILE_FLAG_WRITE_THROUGH
103
92
}
104
- if mode & O_NONBLOCK != 0 {
93
+ if flag & O_NONBLOCK != 0 {
105
94
attrs |= FILE_FLAG_OVERLAPPED
106
95
}
107
- return CreateFile (pathp , access , sharemode , sa , createmode , attrs , 0 )
96
+ h , err := CreateFile (namep , access , sharemode , sa , createmode , attrs , 0 )
97
+ if h == InvalidHandle {
98
+ if err == ERROR_ACCESS_DENIED && (flag & O_WRONLY != 0 || flag & O_RDWR != 0 ) {
99
+ // We should return EISDIR when we are trying to open a directory with write access.
100
+ fa , e1 := GetFileAttributes (namep )
101
+ if e1 == nil && fa & FILE_ATTRIBUTE_DIRECTORY != 0 {
102
+ err = EISDIR
103
+ }
104
+ }
105
+ return h , err
106
+ }
107
+ // Ignore O_TRUNC if the file has just been created.
108
+ if flag & O_TRUNC == O_TRUNC &&
109
+ (createmode == OPEN_EXISTING || (createmode == OPEN_ALWAYS /*&& err == ERROR_ALREADY_EXISTS*/ )) {
110
+ err = Ftruncate (h , 0 )
111
+ if err != nil {
112
+ CloseHandle (h )
113
+ return InvalidHandle , err
114
+ }
115
+ }
116
+ return h , nil
108
117
}
109
118
110
119
func makeInheritSa () * SecurityAttributes {
@@ -113,3 +122,5 @@ func makeInheritSa() *SecurityAttributes {
113
122
sa .InheritHandle = 1
114
123
return & sa
115
124
}
125
+
126
+ const _FILE_WRITE_EA = 0x00000010
0 commit comments