@@ -15,96 +15,105 @@ func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
1515 if name == "" {
1616 return nil , & os.PathError {Op : "open" , Path : name , Err : ENOENT }
1717 }
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 }
2121 }
2222 return os .NewFile (uintptr (r ), name ), nil
2323}
2424
2525// 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].
2728//
2829// 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 {
3132 return InvalidHandle , ERROR_FILE_NOT_FOUND
3233 }
33- pathp , err := UTF16PtrFromString (path )
34+ namep , err := UTF16PtrFromString (name )
3435 if err != nil {
3536 return InvalidHandle , err
3637 }
3738 var access uint32
38- switch mode & (O_RDONLY | O_WRONLY | O_RDWR ) {
39+ switch flag & (O_RDONLY | O_WRONLY | O_RDWR ) {
3940 case O_RDONLY :
4041 access = GENERIC_READ
4142 case O_WRONLY :
4243 access = GENERIC_WRITE
4344 case O_RDWR :
4445 access = GENERIC_READ | GENERIC_WRITE
4546 }
46- if mode & O_CREAT != 0 {
47+ if flag & O_CREAT != 0 {
4748 access |= GENERIC_WRITE
4849 }
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
5259 }
5360 sharemode := uint32 (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE )
5461 var sa * SecurityAttributes
55- if mode & O_CLOEXEC == 0 {
62+ if flag & O_CLOEXEC == 0 {
5663 sa = makeInheritSa ()
5764 }
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.
5870 var createmode uint32
5971 switch {
60- case mode & (O_CREAT | O_EXCL ) == (O_CREAT | O_EXCL ):
72+ case flag & (O_CREAT | O_EXCL ) == (O_CREAT | O_EXCL ):
6173 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 :
6575 createmode = OPEN_ALWAYS
66- case mode & O_TRUNC == O_TRUNC :
67- createmode = TRUNCATE_EXISTING
6876 default :
6977 createmode = OPEN_EXISTING
7078 }
7179 var attrs uint32 = FILE_ATTRIBUTE_NORMAL
7280 if perm & S_IWRITE == 0 {
7381 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- }
9582 }
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.
9887 attrs |= FILE_FLAG_BACKUP_SEMANTICS
9988 }
100- if mode & O_SYNC != 0 {
89+ if flag & O_SYNC != 0 {
10190 const _FILE_FLAG_WRITE_THROUGH = 0x80000000
10291 attrs |= _FILE_FLAG_WRITE_THROUGH
10392 }
104- if mode & O_NONBLOCK != 0 {
93+ if flag & O_NONBLOCK != 0 {
10594 attrs |= FILE_FLAG_OVERLAPPED
10695 }
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
108117}
109118
110119func makeInheritSa () * SecurityAttributes {
@@ -113,3 +122,5 @@ func makeInheritSa() *SecurityAttributes {
113122 sa .InheritHandle = 1
114123 return & sa
115124}
125+
126+ const _FILE_WRITE_EA = 0x00000010
0 commit comments