mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Wasm: implement file IO in WASI
This commit is contained in:
parent
3eb4b742ea
commit
e428e2ac7a
|
@ -15,7 +15,9 @@
|
|||
*/
|
||||
package org.teavm.backend.c.intrinsic;
|
||||
|
||||
import org.teavm.ast.Expr;
|
||||
import org.teavm.ast.InvocationExpr;
|
||||
import org.teavm.ast.VariableExpr;
|
||||
import org.teavm.backend.c.generate.CodeGeneratorUtil;
|
||||
import org.teavm.backend.c.util.ConstantUtil;
|
||||
import org.teavm.interop.Address;
|
||||
|
@ -62,6 +64,8 @@ public class AddressIntrinsic implements Intrinsic {
|
|||
case "sizeOf":
|
||||
|
||||
case "ofData":
|
||||
|
||||
case "pin":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -214,6 +218,16 @@ public class AddressIntrinsic implements Intrinsic {
|
|||
+ sizeOf(type.getItemType()) + "))");
|
||||
break;
|
||||
}
|
||||
|
||||
case "pin": {
|
||||
context.writer().print("/* PIN ");
|
||||
Expr arg = invocation.getArguments().get(0);
|
||||
if (arg instanceof VariableExpr) {
|
||||
context.writer().print(((VariableExpr) arg).getIndex() + " ");
|
||||
}
|
||||
context.writer().print("*/");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.teavm.backend.wasm.generate.WasmClassGenerator;
|
|||
import org.teavm.backend.wasm.model.WasmType;
|
||||
import org.teavm.backend.wasm.model.expression.WasmCall;
|
||||
import org.teavm.backend.wasm.model.expression.WasmConversion;
|
||||
import org.teavm.backend.wasm.model.expression.WasmDrop;
|
||||
import org.teavm.backend.wasm.model.expression.WasmExpression;
|
||||
import org.teavm.backend.wasm.model.expression.WasmInt32Constant;
|
||||
import org.teavm.backend.wasm.model.expression.WasmInt32Subtype;
|
||||
|
@ -172,6 +173,8 @@ public class AddressIntrinsic implements WasmIntrinsic {
|
|||
return new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD,
|
||||
manager.generate(invocation.getArguments().get(0)), new WasmInt32Constant(start));
|
||||
}
|
||||
case "pin":
|
||||
return new WasmDrop(new WasmInt32Constant(0));
|
||||
default:
|
||||
throw new IllegalArgumentException(invocation.getMethod().toString());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright 2022 TeaVM Contributors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.backend.wasm.runtime.fs;
|
||||
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.ERRNO_BADF;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.ERRNO_SUCCESS;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.PRESTAT_DIR;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.teavm.backend.wasm.wasi.Prestat;
|
||||
import org.teavm.backend.wasm.wasi.PrestatDir;
|
||||
import org.teavm.backend.wasm.wasi.Wasi;
|
||||
import org.teavm.interop.Address;
|
||||
import org.teavm.runtime.fs.VirtualFile;
|
||||
import org.teavm.runtime.fs.VirtualFileSystem;
|
||||
|
||||
public class WasiFileSystem implements VirtualFileSystem {
|
||||
private List<Preopened> preopenedList;
|
||||
final byte[] buffer = new byte[256];
|
||||
short errno;
|
||||
String bestPreopenedPath;
|
||||
int bestPreopenedId;
|
||||
|
||||
@Override
|
||||
public String getUserDir() {
|
||||
return "/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public VirtualFile getFile(String path) {
|
||||
return new WasiVirtualFile(this, path);
|
||||
}
|
||||
|
||||
void findBestPreopened(String path) {
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
|
||||
if (path.endsWith("/")) {
|
||||
path = path.substring(0, path.length() - 1);
|
||||
}
|
||||
|
||||
List<Preopened> list = getPreopenedList();
|
||||
int bestFd = -1;
|
||||
int bestNameLength = -1;
|
||||
for (Preopened preopened : list) {
|
||||
if (path.equals(preopened.name)) {
|
||||
bestFd = preopened.fd;
|
||||
path = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (preopened.name.length() > bestNameLength) {
|
||||
int prefixLen = getPrefixLength(path, preopened.name);
|
||||
if (prefixLen >= 0) {
|
||||
bestFd = preopened.fd;
|
||||
bestNameLength = prefixLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestFd == -1) {
|
||||
path = null;
|
||||
} else if (path != null) {
|
||||
path = path.substring(bestNameLength);
|
||||
}
|
||||
|
||||
bestPreopenedPath = path;
|
||||
bestPreopenedId = bestFd;
|
||||
}
|
||||
|
||||
private static int getPrefixLength(String name, String dir) {
|
||||
if (dir.equals("/")) {
|
||||
return 0;
|
||||
} else {
|
||||
return name.startsWith(dir) && name.length() > dir.length() && name.charAt(dir.length()) == '/'
|
||||
? dir.length() + 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWindows() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String canonicalize(String path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
private List<Preopened> getPreopenedList() {
|
||||
if (preopenedList == null) {
|
||||
preopenedList = createPreopenedList();
|
||||
}
|
||||
return preopenedList;
|
||||
}
|
||||
|
||||
private List<Preopened> createPreopenedList() {
|
||||
byte[] buffer = this.buffer;
|
||||
Prestat prestat = Address.align(Address.ofData(buffer), 4).toStructure();
|
||||
List<Preopened> list = new ArrayList<>();
|
||||
int fd = 3; // skip stdin, stdout, and stderr
|
||||
while (true) {
|
||||
short errno = Wasi.fdPrestatGet(fd, prestat);
|
||||
if (errno == ERRNO_BADF) {
|
||||
break;
|
||||
}
|
||||
if (errno != ERRNO_BADF) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (prestat.kind == PRESTAT_DIR) {
|
||||
PrestatDir prestatDir = (PrestatDir) prestat;
|
||||
int length = prestatDir.nameLength;
|
||||
byte[] bytes = length <= buffer.length ? buffer : new byte[length];
|
||||
errno = Wasi.fdPrestatDirName(fd, Address.ofData(bytes), length);
|
||||
|
||||
if (errno == ERRNO_SUCCESS && length > 0) {
|
||||
if (bytes[length - 1] == 0) {
|
||||
length -= 1;
|
||||
}
|
||||
|
||||
list.add(new Preopened(fd, new String(bytes, 0, length, StandardCharsets.UTF_8)));
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
fd += 1;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static class Preopened {
|
||||
final int fd;
|
||||
final String name;
|
||||
|
||||
Preopened(int fd, String name) {
|
||||
this.fd = fd;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,368 @@
|
|||
/*
|
||||
* Copyright 2022 TeaVM Contributors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.backend.wasm.runtime.fs;
|
||||
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.DIRFLAGS_FOLLOW_SYMLINKS;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.ERRNO_EXIST;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.ERRNO_SUCCESS;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.FDFLAGS_APPEND;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.FILETYPE_DIRECTORY;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.FILETYPE_REGULAR_FILE;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.FSTFLAGS_MTIME;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.OFLAGS_CREATE;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.OFLAGS_DIRECTORY;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.OFLAGS_EXCLUSIVE;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.RIGHTS_FD_FILESTAT_GET;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.RIGHTS_FD_FILESTAT_SET_SIZE;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.RIGHTS_READ;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.RIGHTS_SEEK;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.RIGHTS_SYNC;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.RIGHTS_TELL;
|
||||
import static org.teavm.backend.wasm.wasi.Wasi.RIGHTS_WRITE;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import org.teavm.backend.wasm.runtime.WasiBuffer;
|
||||
import org.teavm.backend.wasm.wasi.Dirent;
|
||||
import org.teavm.backend.wasm.wasi.FdResult;
|
||||
import org.teavm.backend.wasm.wasi.Filestat;
|
||||
import org.teavm.backend.wasm.wasi.SizeResult;
|
||||
import org.teavm.backend.wasm.wasi.Wasi;
|
||||
import org.teavm.interop.Address;
|
||||
import org.teavm.interop.Structure;
|
||||
import org.teavm.runtime.fs.VirtualFile;
|
||||
import org.teavm.runtime.fs.VirtualFileAccessor;
|
||||
|
||||
public class WasiVirtualFile implements VirtualFile {
|
||||
private final WasiFileSystem fs;
|
||||
private final String fullPath;
|
||||
private boolean initialized;
|
||||
private int baseFd;
|
||||
private String path;
|
||||
|
||||
WasiVirtualFile(WasiFileSystem fs, String path) {
|
||||
this.fs = fs;
|
||||
fullPath = path;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
|
||||
fs.findBestPreopened(path);
|
||||
path = fs.bestPreopenedPath;
|
||||
baseFd = fs.bestPreopenedId;
|
||||
fs.bestPreopenedPath = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return path.substring(path.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
Stat stat() {
|
||||
init();
|
||||
if (baseFd < 0) {
|
||||
return null;
|
||||
}
|
||||
Filestat filestat = Address.align(WasiBuffer.getBuffer(), 8).toStructure();
|
||||
int errno;
|
||||
if (path != null) {
|
||||
byte[] bytes = path.getBytes(StandardCharsets.UTF_8);
|
||||
errno = Wasi.pathFilestatGet(baseFd, DIRFLAGS_FOLLOW_SYMLINKS, Address.ofData(bytes), bytes.length,
|
||||
filestat);
|
||||
} else {
|
||||
errno = Wasi.fdFilestatGet(baseFd, filestat);
|
||||
}
|
||||
if (errno == ERRNO_SUCCESS) {
|
||||
return new Stat((byte) filestat.fileType, filestat.lastModified, filestat.size);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
Stat stat = stat();
|
||||
return stat != null && stat.filetype == FILETYPE_DIRECTORY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
Stat stat = stat();
|
||||
return stat != null && stat.filetype == FILETYPE_REGULAR_FILE;
|
||||
}
|
||||
|
||||
private String[] listFiles(int fd) {
|
||||
SizeResult sizePtr = WasiBuffer.getBuffer().toStructure();
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
final int direntSize = Structure.sizeOf(Dirent.class);
|
||||
byte[] direntArray = fs.buffer;
|
||||
Address direntBuffer = Address.align(Address.ofData(direntArray), 8);
|
||||
int direntArrayOffset = (int) direntBuffer.diff(Address.ofData(direntArray).add(8));
|
||||
int bufferSize = direntArray.length - direntArrayOffset;
|
||||
long cookie = 0;
|
||||
|
||||
outer:
|
||||
while (true) {
|
||||
short errno = Wasi.fdReaddir(fd, direntBuffer, bufferSize, cookie, sizePtr);
|
||||
if (errno != ERRNO_SUCCESS) {
|
||||
return null;
|
||||
}
|
||||
int remainingSize = bufferSize;
|
||||
while (true) {
|
||||
if (remainingSize < direntSize) {
|
||||
break;
|
||||
}
|
||||
Address direntPtr = direntBuffer.add(direntArrayOffset);
|
||||
Dirent dirent = direntPtr.toStructure();
|
||||
int entryLength = direntSize + (int) dirent.nameLength;
|
||||
if (entryLength > bufferSize) {
|
||||
direntArray = new byte[entryLength * 3 / 2 + 8];
|
||||
direntBuffer = Address.align(Address.ofData(direntArray), 8);
|
||||
direntArrayOffset = (int) direntBuffer.diff(Address.ofData(direntArray));
|
||||
bufferSize = direntArray.length - direntArrayOffset;
|
||||
break;
|
||||
}
|
||||
cookie = dirent.next;
|
||||
list.add(new String(direntArray, direntArrayOffset + direntSize, (int) dirent.nameLength,
|
||||
StandardCharsets.UTF_8));
|
||||
remainingSize -= entryLength;
|
||||
direntArrayOffset += entryLength;
|
||||
if (direntArrayOffset == sizePtr.value) {
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] listFiles() {
|
||||
init();
|
||||
if (baseFd < 0) {
|
||||
return null;
|
||||
}
|
||||
int fd = path != null ? open(OFLAGS_DIRECTORY, RIGHTS_READ, (short) 0) : baseFd;
|
||||
if (fd < 0) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return listFiles(fd);
|
||||
} finally {
|
||||
if (path != null) {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
private int open(int fd, String path, short oflags, long rights, short fdflags) {
|
||||
byte[] bytes = path.getBytes(StandardCharsets.UTF_8);
|
||||
FdResult fdPtr = WasiBuffer.getBuffer().toStructure();
|
||||
fs.errno = Wasi.pathOpen(fd, DIRFLAGS_FOLLOW_SYMLINKS, Address.ofData(bytes), bytes.length, oflags,
|
||||
rights, rights, fdflags, fdPtr);
|
||||
return fs.errno == ERRNO_SUCCESS ? fdPtr.value : -1;
|
||||
}
|
||||
|
||||
|
||||
private int open(short oflags, long rights, short fdflags) {
|
||||
return open(baseFd, path, oflags, rights, fdflags);
|
||||
}
|
||||
|
||||
private static void close(int fd) {
|
||||
// Ignore errno since this is only called in contexts where there's no need to handle the error.
|
||||
Wasi.fdClose(fd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VirtualFileAccessor createAccessor(boolean readable, boolean writable, boolean append) {
|
||||
init();
|
||||
if (baseFd < 0) {
|
||||
return null;
|
||||
}
|
||||
long rights = 0;
|
||||
if (readable) {
|
||||
rights |= RIGHTS_FD_FILESTAT_GET | RIGHTS_SEEK | RIGHTS_TELL | RIGHTS_READ;
|
||||
}
|
||||
if (writable) {
|
||||
rights |= RIGHTS_FD_FILESTAT_SET_SIZE | RIGHTS_WRITE | RIGHTS_SYNC;
|
||||
}
|
||||
short fdflags = (short) 0;
|
||||
if (append) {
|
||||
fdflags |= FDFLAGS_APPEND;
|
||||
}
|
||||
int fd = open((short) 0, rights, fdflags);
|
||||
return fd >= 0 ? new WasiVirtualFileAccessor(fs, fd) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createFile(String fileName) throws IOException {
|
||||
init();
|
||||
if (baseFd < 0) {
|
||||
return false;
|
||||
}
|
||||
int fd = open(baseFd, constructPath(path, fileName), (short) (OFLAGS_CREATE | OFLAGS_EXCLUSIVE), 0, (short) 0);
|
||||
if (fs.errno == ERRNO_EXIST) {
|
||||
return false;
|
||||
}
|
||||
if (fs.errno != ERRNO_SUCCESS) {
|
||||
throw new IOException("fd_open: " + fs.errno);
|
||||
}
|
||||
close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createDirectory(String fileName) {
|
||||
init();
|
||||
if (baseFd < 0) {
|
||||
return false;
|
||||
}
|
||||
String filePath = constructPath(path, fileName);
|
||||
byte[] bytes = filePath.getBytes(StandardCharsets.UTF_8);
|
||||
return Wasi.pathCreateDirectory(baseFd, Address.ofData(bytes), bytes.length) == ERRNO_SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
init();
|
||||
if (fullPath == null || baseFd < 0) {
|
||||
return false;
|
||||
}
|
||||
byte[] bytes = path.getBytes(StandardCharsets.UTF_8);
|
||||
short errno = Wasi.pathUnlinkFile(baseFd, Address.ofData(bytes), bytes.length);
|
||||
return errno == ERRNO_SUCCESS;
|
||||
}
|
||||
|
||||
private static boolean adopt(int fd, WasiVirtualFile file, String fileName) {
|
||||
byte[] newBytes = fileName.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] oldBytes = file.path.getBytes(StandardCharsets.UTF_8);
|
||||
short errno = Wasi.pathRename(file.baseFd, Address.ofData(oldBytes), oldBytes.length, fd,
|
||||
Address.ofData(newBytes), newBytes.length);
|
||||
return errno == ERRNO_SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean adopt(VirtualFile file, String fileName) {
|
||||
if (!(file instanceof WasiVirtualFile)) {
|
||||
return false;
|
||||
}
|
||||
WasiVirtualFile wasiFile = (WasiVirtualFile) file;
|
||||
wasiFile.init();
|
||||
|
||||
if (wasiFile.path == null || fileName == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
init();
|
||||
if (baseFd < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path == null) {
|
||||
return adopt(baseFd, wasiFile, fileName);
|
||||
} else {
|
||||
int fd = open(OFLAGS_DIRECTORY, RIGHTS_READ | RIGHTS_WRITE, (short) 0);
|
||||
try {
|
||||
return adopt(fd, wasiFile, fileName);
|
||||
} finally {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead() {
|
||||
init();
|
||||
int fd = open((short) 0, RIGHTS_READ, (short) 0);
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite() {
|
||||
init();
|
||||
int fd = open((short) 0, RIGHTS_WRITE, (short) 0);
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long lastModified() {
|
||||
Stat stat = stat();
|
||||
return stat == null ? 0 : stat.mtime / 1000000L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setLastModified(long lastModified) {
|
||||
init();
|
||||
if (baseFd < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
short errno;
|
||||
if (path == null) {
|
||||
errno = Wasi.fdFilestatSetTimes(baseFd, 0, lastModified * 1000000L, FSTFLAGS_MTIME);
|
||||
} else {
|
||||
byte[] bytes = path.getBytes(StandardCharsets.UTF_8);
|
||||
errno = Wasi.pathFilestatSetTimes(baseFd, DIRFLAGS_FOLLOW_SYMLINKS, Address.ofData(bytes), bytes.length,
|
||||
0, lastModified * 1000000L, FSTFLAGS_MTIME);
|
||||
}
|
||||
|
||||
return errno == ERRNO_SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setReadOnly(boolean readOnly) {
|
||||
// TODO: is this possible on WASI?
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
Stat stat = stat();
|
||||
return stat == null ? 0 : (int) stat.filesize;
|
||||
}
|
||||
|
||||
private String constructPath(String parent, String child) {
|
||||
if (parent == null) {
|
||||
return child;
|
||||
}
|
||||
return !parent.isEmpty() && parent.charAt(parent.length() - 1) == '/'
|
||||
? parent + child
|
||||
: parent + '/' + child;
|
||||
}
|
||||
|
||||
static class Stat {
|
||||
final byte filetype;
|
||||
final long mtime;
|
||||
final long filesize;
|
||||
|
||||
Stat(byte filetype, long mtime, long filesize) {
|
||||
this.filetype = filetype;
|
||||
this.mtime = mtime;
|
||||
this.filesize = filesize;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright 2022 TeaVM Contributors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.backend.wasm.runtime.fs;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.teavm.backend.wasm.runtime.WasiBuffer;
|
||||
import org.teavm.interop.Address;
|
||||
import org.teavm.runtime.fs.VirtualFileAccessor;
|
||||
|
||||
public class WasiVirtualFileAccessor implements VirtualFileAccessor {
|
||||
// Enough room for an I32 plus padding for alignment:
|
||||
private static final byte[] EIGHT_BYTE_BUFFER = new byte[8];
|
||||
// Enough room for an I64 plus padding for alignment:
|
||||
private static final byte[] SIXTEEN_BYTE_BUFFER = new byte[16];
|
||||
|
||||
private WasiFileSystem fs;
|
||||
private int fd;
|
||||
|
||||
public WasiVirtualFileAccessor(WasiFileSystem fs, int fd) {
|
||||
this.fs = fs;
|
||||
this.fd = fd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws IOException {
|
||||
byte[] vecBuffer = SIXTEEN_BYTE_BUFFER;
|
||||
Address vec = WasiBuffer.getBuffer();
|
||||
vec.putInt(Address.ofData(buffer).add(offset).toInt());
|
||||
vec.add(4).putInt(length);
|
||||
byte[] sizeBuffer = EIGHT_BYTE_BUFFER;
|
||||
Address size = Address.align(Address.ofData(sizeBuffer), 4);
|
||||
short errno = Wasi.fdRead(fd, vec, 1, size);
|
||||
|
||||
if (errno == ERRNO_SUCCESS) {
|
||||
return size.getInt();
|
||||
} else {
|
||||
throw new IOException(errnoMessage("fd_read", errno));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer, int offset, int length) throws IOException {
|
||||
byte[] vecBuffer = SIXTEEN_BYTE_BUFFER;
|
||||
Address vec = Address.align(Address.ofData(vecBuffer), 4);
|
||||
byte[] sizeBuffer = EIGHT_BYTE_BUFFER;
|
||||
Address size = Address.align(Address.ofData(sizeBuffer), 4);
|
||||
|
||||
int index = 0;
|
||||
while (true) {
|
||||
vec.putInt(Address.ofData(buffer).add(offset + index).toInt());
|
||||
vec.add(4).putInt(length - index);
|
||||
short errno = Wasi.fdWrite(fd, vec, 1, size);
|
||||
|
||||
if (errno == ERRNO_SUCCESS) {
|
||||
index += size.getInt();
|
||||
if (index >= length) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
throw new IOException(errnoMessage("fd_write", errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int tell() throws IOException {
|
||||
byte[] filesizeBuffer = SIXTEEN_BYTE_BUFFER;
|
||||
Address filesize = Address.align(Address.ofData(filesizeBuffer), 8);
|
||||
short errno = Wasi.fdTell(fd, filesize);
|
||||
|
||||
if (errno == ERRNO_SUCCESS) {
|
||||
return (int) filesize.getLong();
|
||||
} else {
|
||||
throw new IOException(errnoMessage("fd_tell", errno));
|
||||
}
|
||||
}
|
||||
|
||||
private long seek(long offset, byte whence) throws IOException {
|
||||
byte[] filesizeBuffer = SIXTEEN_BYTE_BUFFER;
|
||||
Address filesize = Address.align(Address.ofData(filesizeBuffer), 8);
|
||||
short errno = Wasi.fdSeek(fd, offset, whence, filesize);
|
||||
|
||||
if (errno == ERRNO_SUCCESS) {
|
||||
return filesize.getLong();
|
||||
} else {
|
||||
throw new IOException(errnoMessage("fd_seek", errno));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skip(int amount) throws IOException {
|
||||
seek(amount, WHENCE_CURRENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(int target) throws IOException {
|
||||
seek(target, WHENCE_START);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() throws IOException {
|
||||
return (int) new WasiVirtualFile(fd, null).stat().filesize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(int size) throws IOException {
|
||||
short errno = Wasi.fdFilestatSetSize(fd, size);
|
||||
|
||||
if (errno != ERRNO_SUCCESS) {
|
||||
throw new IOException(errnoMessage("fd_filestat_set_size", errno));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (this.fd >= 0) {
|
||||
int fd = this.fd;
|
||||
this.fd = -1;
|
||||
|
||||
short errno = Wasi.fdClose(fd);
|
||||
|
||||
if (errno != ERRNO_SUCCESS) {
|
||||
throw new IOException(errnoMessage("fd_close", errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
short errno = Wasi.fdSync(fd);
|
||||
|
||||
if (errno != ERRNO_SUCCESS) {
|
||||
throw new IOException(errnoMessage("fd_sync", errno));
|
||||
}
|
||||
}
|
||||
}
|
25
core/src/main/java/org/teavm/backend/wasm/wasi/Dirent.java
Normal file
25
core/src/main/java/org/teavm/backend/wasm/wasi/Dirent.java
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 2022 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.backend.wasm.wasi;
|
||||
|
||||
import org.teavm.interop.Structure;
|
||||
|
||||
public class Dirent extends Structure {
|
||||
public long next;
|
||||
public long inode;
|
||||
public long nameLength;
|
||||
public long fileType;
|
||||
}
|
22
core/src/main/java/org/teavm/backend/wasm/wasi/FdResult.java
Normal file
22
core/src/main/java/org/teavm/backend/wasm/wasi/FdResult.java
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2022 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.backend.wasm.wasi;
|
||||
|
||||
import org.teavm.interop.Structure;
|
||||
|
||||
public class FdResult extends Structure {
|
||||
public int value;
|
||||
}
|
29
core/src/main/java/org/teavm/backend/wasm/wasi/Filestat.java
Normal file
29
core/src/main/java/org/teavm/backend/wasm/wasi/Filestat.java
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2022 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.backend.wasm.wasi;
|
||||
|
||||
import org.teavm.interop.Structure;
|
||||
|
||||
public class Filestat extends Structure {
|
||||
public long deviceId;
|
||||
public long inode;
|
||||
public long fileType;
|
||||
public long linkCount;
|
||||
public long size;
|
||||
public long lastAccess;
|
||||
public long lastModified;
|
||||
public long lastStatusChange;
|
||||
}
|
22
core/src/main/java/org/teavm/backend/wasm/wasi/Prestat.java
Normal file
22
core/src/main/java/org/teavm/backend/wasm/wasi/Prestat.java
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2022 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.backend.wasm.wasi;
|
||||
|
||||
import org.teavm.interop.Structure;
|
||||
|
||||
public class Prestat extends Structure {
|
||||
public int kind;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2022 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.backend.wasm.wasi;
|
||||
|
||||
public class PrestatDir extends Prestat {
|
||||
public int nameLength;
|
||||
}
|
|
@ -20,14 +20,44 @@ import org.teavm.interop.Import;
|
|||
|
||||
public final class Wasi {
|
||||
public static final int CLOCKID_REALTIME = 0;
|
||||
|
||||
public static final byte PRESTAT_DIR = 0;
|
||||
|
||||
public static final short ERRNO_SUCCESS = 0;
|
||||
public static final short ERRNO_BADF = 8;
|
||||
public static final short ERRNO_EXIST = 20;
|
||||
public static final short ERRNO_NOENT = 44;
|
||||
|
||||
public static final byte FILETYPE_DIRECTORY = 3;
|
||||
public static final byte FILETYPE_REGULAR_FILE = 4;
|
||||
|
||||
public static final int DIRFLAGS_FOLLOW_SYMLINKS = 1;
|
||||
|
||||
public static final short OFLAGS_CREATE = 1 << 0;
|
||||
public static final short OFLAGS_DIRECTORY = 1 << 1;
|
||||
public static final short OFLAGS_EXCLUSIVE = 1 << 2;
|
||||
|
||||
public static final long RIGHTS_READ = 1L << 1;
|
||||
public static final long RIGHTS_SEEK = 1L << 2;
|
||||
public static final long RIGHTS_TELL = 1L << 5;
|
||||
public static final long RIGHTS_WRITE = 1L << 6;
|
||||
public static final long RIGHTS_SYNC = 1L << 4;
|
||||
public static final long RIGHTS_CREATE_DIRECTORY = 1L << 9;
|
||||
public static final long RIGHTS_CREATE_FILE = 1L << 10;
|
||||
public static final long RIGHTS_FD_FILESTAT_GET = 1L << 21;
|
||||
public static final long RIGHTS_FD_FILESTAT_SET_SIZE = 1L << 22;
|
||||
|
||||
public static final short FDFLAGS_APPEND = 1 << 0;
|
||||
|
||||
public static final short FSTFLAGS_MTIME = 1 << 2;
|
||||
|
||||
public static final byte WHENCE_START = 0;
|
||||
public static final byte WHENCE_CURRENT = 1;
|
||||
public static final byte WHENCE_END = 2;
|
||||
|
||||
private Wasi() {
|
||||
}
|
||||
|
||||
@Import(name = "fd_write", module = "wasi_snapshot_preview1")
|
||||
public static native short fdWrite(int fd, IOVec vectors, int vectorsCont, SizeResult result);
|
||||
|
||||
@Import(name = "clock_time_get", module = "wasi_snapshot_preview1")
|
||||
public static native short clockTimeGet(int clockId, long precision, LongResult result);
|
||||
|
||||
|
@ -36,4 +66,47 @@ public final class Wasi {
|
|||
|
||||
@Import(name = "args_get", module = "wasi_snapshot_preview1")
|
||||
public static native short argsGet(Address argv, Address argvBuf);
|
||||
|
||||
@Import(name = "fd_write", module = "wasi_snapshot_preview1")
|
||||
public static native short fdWrite(int fd, IOVec vectors, int vectorsCont, SizeResult result);
|
||||
|
||||
@Import(name = "fd_prestat_get", module = "wasi_snapshot_preview1")
|
||||
public static native short fdPrestatGet(int fd, Prestat prestat);
|
||||
|
||||
@Import(name = "fd_prestat_dir_name", module = "wasi_snapshot_preview1")
|
||||
public static native short fdPrestatDirName(int fd, Address buffer, int bufferLength);
|
||||
|
||||
@Import(name = "fd_filestat_get", module = "wasi_snapshot_preview1")
|
||||
public static native short fdFilestatGet(int fd, Filestat filestat);
|
||||
|
||||
@Import(name = "path_filestat_get", module = "wasi_snapshot_preview1")
|
||||
public static native short pathFilestatGet(int fd, int lookupFlags, Address path, int pathLength,
|
||||
Filestat filestat);
|
||||
|
||||
@Import(name = "fd_readdir", module = "wasi_snapshot_preview1")
|
||||
public static native short fdReaddir(int fd, Address dirent, int direntSize, long cookie, SizeResult size);
|
||||
|
||||
@Import(name = "path_open", module = "wasi_snapshot_preview1")
|
||||
public static native short pathOpen(int dirFd, int lookupFlags, Address path, int pathLength, short oflags,
|
||||
long baseRights, long inheritingRights, short fdflags, FdResult fd);
|
||||
|
||||
@Import(name = "fd_close", module = "wasi_snapshot_preview1")
|
||||
public static native short fdClose(int fd);
|
||||
|
||||
@Import(name = "path_create_directory", module = "wasi_snapshot_preview1")
|
||||
public static native short pathCreateDirectory(int fd, Address path, int pathLength);
|
||||
|
||||
@Import(name = "path_unlink_file", module = "wasi_snapshot_preview1")
|
||||
public static native short pathUnlinkFile(int fd, Address path, int pathLength);
|
||||
|
||||
@Import(name = "path_rename", module = "wasi_snapshot_preview1")
|
||||
public static native short pathRename(int oldFd, Address oldPath, int oldPathLength, int newFd, Address newPath,
|
||||
int newPathLength);
|
||||
|
||||
@Import(name = "fd_filestat_set_times", module = "wasi_snapshot_preview1")
|
||||
public static native short fdFilestatSetTimes(int fd, long atime, long mtime, short fstflags);
|
||||
|
||||
@Import(name = "path_filestat_set_times", module = "wasi_snapshot_preview1")
|
||||
public static native short pathFilestatSetTimes(int fd, int lookupFlags, Address path, int pathLength,
|
||||
long atime, long mtime, short fstflags);
|
||||
}
|
||||
|
|
|
@ -93,4 +93,6 @@ public final class Address {
|
|||
public long diff(Address that) {
|
||||
return toLong() - that.toLong();
|
||||
}
|
||||
|
||||
public static native void pin(Object obj);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user