summaryrefslogtreecommitdiff
path: root/vendor/github.com/tetratelabs/wazero/internal/sysfs/open_file_windows.go
blob: 717f8598afbe9ceea4bf24b5a13d521d112a8d9c (plain)
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
}