1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
package sysfs
import (
"io/fs"
"os"
"strings"
"syscall"
"unsafe"
"github.com/tetratelabs/wazero/experimental/sys"
)
func openFile(path string, oflag sys.Oflag, perm fs.FileMode) (*os.File, sys.Errno) {
isDir := oflag&sys.O_DIRECTORY > 0
flag := toOsOpenFlag(oflag)
// TODO: document why we are opening twice
fd, err := open(path, flag|syscall.O_CLOEXEC, uint32(perm))
if err == nil {
return os.NewFile(uintptr(fd), path), 0
}
// TODO: Set FILE_SHARE_DELETE for directory as well.
f, err := os.OpenFile(path, flag, perm)
errno := sys.UnwrapOSError(err)
if errno == 0 {
return f, 0
}
switch errno {
case sys.EINVAL:
// WASI expects ENOTDIR for a file path with a trailing slash.
if strings.HasSuffix(path, "/") {
errno = sys.ENOTDIR
}
// To match expectations of WASI, e.g. TinyGo TestStatBadDir, return
// ENOENT, not ENOTDIR.
case sys.ENOTDIR:
errno = sys.ENOENT
case sys.ENOENT:
if isSymlink(path) {
// Either symlink or hard link not found. We change the returned
// errno depending on if it is symlink or not to have consistent
// behavior across OSes.
if isDir {
// Dangling symlink dir must raise ENOTDIR.
errno = sys.ENOTDIR
} else {
errno = sys.ELOOP
}
}
}
return f, errno
}
const supportedSyscallOflag = sys.O_NONBLOCK
// Map to synthetic values here https://github.com/golang/go/blob/go1.20/src/syscall/types_windows.go#L34-L48
func withSyscallOflag(oflag sys.Oflag, flag int) int {
// O_DIRECTORY not defined in windows
// O_DSYNC not defined in windows
// O_NOFOLLOW not defined in windows
if oflag&sys.O_NONBLOCK != 0 {
flag |= syscall.O_NONBLOCK
}
// O_RSYNC not defined in windows
return flag
}
func isSymlink(path string) bool {
if st, e := os.Lstat(path); e == nil && st.Mode()&os.ModeSymlink != 0 {
return true
}
return false
}
// # Differences from syscall.Open
//
// This code is based on syscall.Open from the below link with some differences
// https://github.com/golang/go/blame/go1.20/src/syscall/syscall_windows.go#L308-L379
//
// - syscall.O_CREAT doesn't imply syscall.GENERIC_WRITE as that breaks
// flag expectations in wasi.
// - add support for setting FILE_SHARE_DELETE.
func open(path string, mode int, perm uint32) (fd syscall.Handle, err error) {
if len(path) == 0 {
return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
}
pathp, err := syscall.UTF16PtrFromString(path)
if err != nil {
return syscall.InvalidHandle, err
}
var access uint32
switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
case syscall.O_RDONLY:
access = syscall.GENERIC_READ
case syscall.O_WRONLY:
access = syscall.GENERIC_WRITE
case syscall.O_RDWR:
access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
}
if mode&syscall.O_APPEND != 0 {
access &^= syscall.GENERIC_WRITE
access |= syscall.FILE_APPEND_DATA
}
sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE)
var sa *syscall.SecurityAttributes
if mode&syscall.O_CLOEXEC == 0 {
var _sa syscall.SecurityAttributes
_sa.Length = uint32(unsafe.Sizeof(sa))
_sa.InheritHandle = 1
sa = &_sa
}
var createmode uint32
switch {
case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
createmode = syscall.CREATE_NEW
case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC):
createmode = syscall.CREATE_ALWAYS
case mode&syscall.O_CREAT == syscall.O_CREAT:
createmode = syscall.OPEN_ALWAYS
case mode&syscall.O_TRUNC == syscall.O_TRUNC:
createmode = syscall.TRUNCATE_EXISTING
default:
createmode = syscall.OPEN_EXISTING
}
var attrs uint32 = syscall.FILE_ATTRIBUTE_NORMAL
if perm&syscall.S_IWRITE == 0 {
attrs = syscall.FILE_ATTRIBUTE_READONLY
if createmode == syscall.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 := syscall.CreateFile(pathp, access, sharemode, sa, syscall.TRUNCATE_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0)
switch e {
case syscall.ERROR_FILE_NOT_FOUND, syscall.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
}
}
}
// This shouldn't be included before 1.20 to have consistent behavior.
// https://github.com/golang/go/commit/0f0aa5d8a6a0253627d58b3aa083b24a1091933f
if createmode == syscall.OPEN_EXISTING && access == syscall.GENERIC_READ {
// Necessary for opening directory handles.
attrs |= syscall.FILE_FLAG_BACKUP_SEMANTICS
}
h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
return h, e
}
|