From 70e37dfed9225c1bf7bde377297084f151f93909 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sat, 12 Nov 2022 21:52:29 +0100 Subject: [PATCH] Wasi: fix bugs in IO implementation --- .../org/teavm/classlib/java/io/TFile.java | 32 ++++++++------ .../classlib/java/io/TFileInputStream.java | 2 +- .../classlib/java/io/TFileOutputStream.java | 5 ++- .../classlib/java/io/TRandomAccessFile.java | 2 +- .../org/teavm/classlib/java/util/TArrays.java | 1 + .../wasm/runtime/fs/WasiFileSystem.java | 11 +++-- .../wasm/runtime/fs/WasiVirtualFile.java | 43 ++++++++++++------- .../org/teavm/backend/wasm/wasi/Dirent.java | 4 +- .../org/teavm/backend/wasm/wasi/Wasi.java | 6 ++- tests/run-wasi.sh | 2 +- 10 files changed, 68 insertions(+), 40 deletions(-) diff --git a/classlib/src/main/java/org/teavm/classlib/java/io/TFile.java b/classlib/src/main/java/org/teavm/classlib/java/io/TFile.java index a17cfb8e2..e9ba6f913 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/io/TFile.java +++ b/classlib/src/main/java/org/teavm/classlib/java/io/TFile.java @@ -396,7 +396,7 @@ public class TFile implements Serializable, Comparable { public boolean createNewFile() throws IOException { VirtualFile parentVirtualFile = findParentFile(); - if (parentVirtualFile == null) { + if (parentVirtualFile == null || !parentVirtualFile.isDirectory()) { throw new IOException("Can't create file " + getPath() + " since parent directory does not exist"); } @@ -405,7 +405,7 @@ public class TFile implements Serializable, Comparable { public boolean mkdir() { VirtualFile virtualFile = findParentFile(); - return virtualFile != null && virtualFile.createDirectory(getName()); + return virtualFile != null && virtualFile.isDirectory() && virtualFile.createDirectory(getName()); } public boolean mkdirs() { @@ -416,7 +416,7 @@ public class TFile implements Serializable, Comparable { } int i = path.length(); - do { + while (true) { VirtualFile file = fs().getFile(path.substring(0, i)); if (file.isDirectory()) { break; @@ -424,13 +424,18 @@ public class TFile implements Serializable, Comparable { return false; } - i = path.lastIndexOf(separatorChar, i - 1); - } while (i >= 0); - - i++; + int next = path.lastIndexOf(separatorChar, i - 1); + if (next < 0) { + break; + } + i = next; + if (i == 0) { + break; + } + } while (i < path.length()) { - int next = path.indexOf(separatorChar, i); + int next = path.indexOf(separatorChar, i + 1); if (next < 0) { next = path.length(); } @@ -438,11 +443,12 @@ public class TFile implements Serializable, Comparable { break; } - VirtualFile file = fs().getFile(path.substring(0, i)); - if (!file.createDirectory(path.substring(i, next))) { + String dirPath = i == 0 && path.startsWith("/") ? "/" : path.substring(0, i); + VirtualFile file = fs().getFile(dirPath); + if (!file.createDirectory(path.substring(i + 1, next))) { return false; } - i = next + 1; + i = next; } return true; @@ -515,7 +521,9 @@ public class TFile implements Serializable, Comparable { if (directory == null) { String tmpDir = System.getProperty("java.io.tmpdir", "."); tmpDirFile = new TFile(tmpDir); - tmpDirFile.mkdirs(); + if (!tmpDirFile.mkdirs()) { + throw new IOException("Could not access temp dir"); + } } else { tmpDirFile = directory; } diff --git a/classlib/src/main/java/org/teavm/classlib/java/io/TFileInputStream.java b/classlib/src/main/java/org/teavm/classlib/java/io/TFileInputStream.java index e2b4793ec..a1e4ccbf9 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/io/TFileInputStream.java +++ b/classlib/src/main/java/org/teavm/classlib/java/io/TFileInputStream.java @@ -28,7 +28,7 @@ public class TFileInputStream extends InputStream { public TFileInputStream(TFile file) throws FileNotFoundException { VirtualFile virtualFile = file.findVirtualFile(); - if (virtualFile == null || virtualFile.isDirectory()) { + if (virtualFile == null || !virtualFile.isFile()) { throw new FileNotFoundException(); } diff --git a/classlib/src/main/java/org/teavm/classlib/java/io/TFileOutputStream.java b/classlib/src/main/java/org/teavm/classlib/java/io/TFileOutputStream.java index fea2f998a..ec02b8295 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/io/TFileOutputStream.java +++ b/classlib/src/main/java/org/teavm/classlib/java/io/TFileOutputStream.java @@ -44,7 +44,7 @@ public class TFileOutputStream extends OutputStream { throw new FileNotFoundException("Invalid file name"); } VirtualFile parentVirtualFile = file.findParentFile(); - if (parentVirtualFile != null) { + if (parentVirtualFile != null && parentVirtualFile.isDirectory()) { try { parentVirtualFile.createFile(file.getName()); } catch (IOException e) { @@ -53,6 +53,9 @@ public class TFileOutputStream extends OutputStream { } VirtualFile virtualFile = file.findVirtualFile(); + if (virtualFile == null || !virtualFile.isFile()) { + throw new FileNotFoundException("Could not create file"); + } accessor = virtualFile.createAccessor(false, true, append); if (accessor == null) { throw new FileNotFoundException(); diff --git a/classlib/src/main/java/org/teavm/classlib/java/io/TRandomAccessFile.java b/classlib/src/main/java/org/teavm/classlib/java/io/TRandomAccessFile.java index 2dc44cc35..ab4b0a649 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/io/TRandomAccessFile.java +++ b/classlib/src/main/java/org/teavm/classlib/java/io/TRandomAccessFile.java @@ -55,7 +55,7 @@ public class TRandomAccessFile implements DataInput, DataOutput, Closeable { } VirtualFile virtualFile = file.findVirtualFile(); - if (virtualFile == null || virtualFile.isDirectory()) { + if (virtualFile == null || !virtualFile.isFile()) { throw new FileNotFoundException(); } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TArrays.java b/classlib/src/main/java/org/teavm/classlib/java/util/TArrays.java index 43dba5512..75cfa4b5f 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TArrays.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TArrays.java @@ -1527,6 +1527,7 @@ public class TArrays extends TObject { @SafeVarargs public static TList asList(final T... a) { + Objects.requireNonNull(a); return new ArrayAsList<>(a); } diff --git a/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiFileSystem.java b/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiFileSystem.java index 7e1e5b6ea..45a8c96da 100644 --- a/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiFileSystem.java +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiFileSystem.java @@ -47,7 +47,7 @@ public class WasiFileSystem implements VirtualFileSystem { } void findBestPreopened(String path) { - if (path.endsWith("/")) { + if (path.endsWith("/") && path.length() > 1) { path = path.substring(0, path.length() - 1); } @@ -81,12 +81,11 @@ public class WasiFileSystem implements VirtualFileSystem { } 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; + if (dir.equals("/") && name.startsWith("/")) { + return 1; } + return name.startsWith(dir) && name.length() > dir.length() && name.charAt(dir.length()) == '/' + ? dir.length() + 1 : -1; } @Override diff --git a/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiVirtualFile.java b/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiVirtualFile.java index 686bf42cc..e71ec2f99 100644 --- a/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiVirtualFile.java +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/fs/WasiVirtualFile.java @@ -27,6 +27,7 @@ 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_FD_READDIR; 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; @@ -39,7 +40,7 @@ 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.IntResult; import org.teavm.backend.wasm.wasi.Wasi; import org.teavm.interop.Address; import org.teavm.interop.Structure; @@ -109,12 +110,12 @@ public class WasiVirtualFile implements VirtualFile { } private String[] listFiles(int fd) { - SizeResult sizePtr = WasiBuffer.getBuffer().toStructure(); + IntResult sizePtr = WasiBuffer.getBuffer().toStructure(); ArrayList 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 direntArrayOffset = (int) direntBuffer.diff(Address.ofData(direntArray)); int bufferSize = direntArray.length - direntArrayOffset; long cookie = 0; @@ -124,27 +125,34 @@ public class WasiVirtualFile implements VirtualFile { if (errno != ERRNO_SUCCESS) { return null; } + int size = sizePtr.value; int remainingSize = bufferSize; + int entryOffset = 0; while (true) { if (remainingSize < direntSize) { break; } - Address direntPtr = direntBuffer.add(direntArrayOffset); + Address direntPtr = direntBuffer.add(entryOffset); Dirent dirent = direntPtr.toStructure(); - int entryLength = direntSize + (int) dirent.nameLength; + int entryLength = direntSize + 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; + } else if (entryOffset + entryLength > bufferSize) { + break; } cookie = dirent.next; - list.add(new String(direntArray, direntArrayOffset + direntSize, (int) dirent.nameLength, - StandardCharsets.UTF_8)); + String name = new String(direntArray, direntArrayOffset + entryOffset + direntSize, dirent.nameLength, + StandardCharsets.UTF_8); + if (!name.equals(".") && !name.equals("..")) { + list.add(name); + } remainingSize -= entryLength; - direntArrayOffset += entryLength; - if (direntArrayOffset == sizePtr.value) { + entryOffset += entryLength; + if (entryOffset == size) { break outer; } } @@ -159,7 +167,7 @@ public class WasiVirtualFile implements VirtualFile { if (baseFd < 0) { return null; } - int fd = path != null ? open(OFLAGS_DIRECTORY, RIGHTS_READ, (short) 0) : baseFd; + int fd = path != null ? open(OFLAGS_DIRECTORY, RIGHTS_READ | RIGHTS_FD_READDIR, (short) 0) : baseFd; if (fd < 0) { return null; } @@ -214,7 +222,7 @@ public class WasiVirtualFile implements VirtualFile { public boolean createFile(String fileName) throws IOException { init(); if (baseFd < 0) { - return false; + throw new IOException("Can't create file: access to directory not granted by WASI runtime"); } int fd = open(baseFd, constructPath(path, fileName), (short) (OFLAGS_CREATE | OFLAGS_EXCLUSIVE), 0, (short) 0); if (fs.errno == ERRNO_EXIST) { @@ -241,12 +249,17 @@ public class WasiVirtualFile implements VirtualFile { @Override public boolean delete() { init(); - if (fullPath == null || baseFd < 0) { + if (path == 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; + if (isFile()) { + byte[] bytes = path.getBytes(StandardCharsets.UTF_8); + return Wasi.pathUnlinkFile(baseFd, Address.ofData(bytes), bytes.length) == ERRNO_SUCCESS; + } else if (isDirectory()) { + byte[] bytes = path.getBytes(StandardCharsets.UTF_8); + return Wasi.pathRemoveDirectory(baseFd, Address.ofData(bytes), bytes.length) == ERRNO_SUCCESS; + } + return false; } private static boolean adopt(int fd, WasiVirtualFile file, String fileName) { diff --git a/core/src/main/java/org/teavm/backend/wasm/wasi/Dirent.java b/core/src/main/java/org/teavm/backend/wasm/wasi/Dirent.java index 44776945b..16a22ecc2 100644 --- a/core/src/main/java/org/teavm/backend/wasm/wasi/Dirent.java +++ b/core/src/main/java/org/teavm/backend/wasm/wasi/Dirent.java @@ -20,6 +20,6 @@ import org.teavm.interop.Structure; public class Dirent extends Structure { public long next; public long inode; - public long nameLength; - public long fileType; + public int nameLength; + public int fileType; } diff --git a/core/src/main/java/org/teavm/backend/wasm/wasi/Wasi.java b/core/src/main/java/org/teavm/backend/wasm/wasi/Wasi.java index e413e90d9..0899955ff 100644 --- a/core/src/main/java/org/teavm/backend/wasm/wasi/Wasi.java +++ b/core/src/main/java/org/teavm/backend/wasm/wasi/Wasi.java @@ -44,6 +44,7 @@ public final class Wasi { 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_READDIR = 1L << 14; public static final long RIGHTS_FD_FILESTAT_GET = 1L << 21; public static final long RIGHTS_FD_FILESTAT_SET_SIZE = 1L << 22; @@ -93,7 +94,7 @@ public final class Wasi { 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); + public static native short fdReaddir(int fd, Address dirent, int direntSize, long cookie, IntResult size); @Import(name = "path_open", module = "wasi_snapshot_preview1") public static native short pathOpen(int dirFd, int lookupFlags, Address path, int pathLength, short oflags, @@ -108,6 +109,9 @@ public final class Wasi { @Import(name = "path_unlink_file", module = "wasi_snapshot_preview1") public static native short pathUnlinkFile(int fd, Address path, int pathLength); + @Import(name = "path_remove_directory", module = "wasi_snapshot_preview1") + public static native short pathRemoveDirectory(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); diff --git a/tests/run-wasi.sh b/tests/run-wasi.sh index dc97b3d1a..63146a532 100755 --- a/tests/run-wasi.sh +++ b/tests/run-wasi.sh @@ -1 +1 @@ -~/.wasmtime/bin/wasmtime run --dir target/wasi-testdir --mapdir /::target/wasi-testdir $1 $2 \ No newline at end of file +~/.wasmtime/bin/wasmtime run --mapdir /::target/wasi-testdir $1 $2 \ No newline at end of file