Wasi: fix bugs in IO implementation

This commit is contained in:
Alexey Andreev 2022-11-12 21:52:29 +01:00
parent d9fb2bc159
commit 70e37dfed9
10 changed files with 68 additions and 40 deletions

View File

@ -396,7 +396,7 @@ public class TFile implements Serializable, Comparable<TFile> {
public boolean createNewFile() throws IOException { public boolean createNewFile() throws IOException {
VirtualFile parentVirtualFile = findParentFile(); 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"); throw new IOException("Can't create file " + getPath() + " since parent directory does not exist");
} }
@ -405,7 +405,7 @@ public class TFile implements Serializable, Comparable<TFile> {
public boolean mkdir() { public boolean mkdir() {
VirtualFile virtualFile = findParentFile(); VirtualFile virtualFile = findParentFile();
return virtualFile != null && virtualFile.createDirectory(getName()); return virtualFile != null && virtualFile.isDirectory() && virtualFile.createDirectory(getName());
} }
public boolean mkdirs() { public boolean mkdirs() {
@ -416,7 +416,7 @@ public class TFile implements Serializable, Comparable<TFile> {
} }
int i = path.length(); int i = path.length();
do { while (true) {
VirtualFile file = fs().getFile(path.substring(0, i)); VirtualFile file = fs().getFile(path.substring(0, i));
if (file.isDirectory()) { if (file.isDirectory()) {
break; break;
@ -424,13 +424,18 @@ public class TFile implements Serializable, Comparable<TFile> {
return false; return false;
} }
i = path.lastIndexOf(separatorChar, i - 1); int next = path.lastIndexOf(separatorChar, i - 1);
} while (i >= 0); if (next < 0) {
break;
i++; }
i = next;
if (i == 0) {
break;
}
}
while (i < path.length()) { while (i < path.length()) {
int next = path.indexOf(separatorChar, i); int next = path.indexOf(separatorChar, i + 1);
if (next < 0) { if (next < 0) {
next = path.length(); next = path.length();
} }
@ -438,11 +443,12 @@ public class TFile implements Serializable, Comparable<TFile> {
break; break;
} }
VirtualFile file = fs().getFile(path.substring(0, i)); String dirPath = i == 0 && path.startsWith("/") ? "/" : path.substring(0, i);
if (!file.createDirectory(path.substring(i, next))) { VirtualFile file = fs().getFile(dirPath);
if (!file.createDirectory(path.substring(i + 1, next))) {
return false; return false;
} }
i = next + 1; i = next;
} }
return true; return true;
@ -515,7 +521,9 @@ public class TFile implements Serializable, Comparable<TFile> {
if (directory == null) { if (directory == null) {
String tmpDir = System.getProperty("java.io.tmpdir", "."); String tmpDir = System.getProperty("java.io.tmpdir", ".");
tmpDirFile = new TFile(tmpDir); tmpDirFile = new TFile(tmpDir);
tmpDirFile.mkdirs(); if (!tmpDirFile.mkdirs()) {
throw new IOException("Could not access temp dir");
}
} else { } else {
tmpDirFile = directory; tmpDirFile = directory;
} }

View File

@ -28,7 +28,7 @@ public class TFileInputStream extends InputStream {
public TFileInputStream(TFile file) throws FileNotFoundException { public TFileInputStream(TFile file) throws FileNotFoundException {
VirtualFile virtualFile = file.findVirtualFile(); VirtualFile virtualFile = file.findVirtualFile();
if (virtualFile == null || virtualFile.isDirectory()) { if (virtualFile == null || !virtualFile.isFile()) {
throw new FileNotFoundException(); throw new FileNotFoundException();
} }

View File

@ -44,7 +44,7 @@ public class TFileOutputStream extends OutputStream {
throw new FileNotFoundException("Invalid file name"); throw new FileNotFoundException("Invalid file name");
} }
VirtualFile parentVirtualFile = file.findParentFile(); VirtualFile parentVirtualFile = file.findParentFile();
if (parentVirtualFile != null) { if (parentVirtualFile != null && parentVirtualFile.isDirectory()) {
try { try {
parentVirtualFile.createFile(file.getName()); parentVirtualFile.createFile(file.getName());
} catch (IOException e) { } catch (IOException e) {
@ -53,6 +53,9 @@ public class TFileOutputStream extends OutputStream {
} }
VirtualFile virtualFile = file.findVirtualFile(); VirtualFile virtualFile = file.findVirtualFile();
if (virtualFile == null || !virtualFile.isFile()) {
throw new FileNotFoundException("Could not create file");
}
accessor = virtualFile.createAccessor(false, true, append); accessor = virtualFile.createAccessor(false, true, append);
if (accessor == null) { if (accessor == null) {
throw new FileNotFoundException(); throw new FileNotFoundException();

View File

@ -55,7 +55,7 @@ public class TRandomAccessFile implements DataInput, DataOutput, Closeable {
} }
VirtualFile virtualFile = file.findVirtualFile(); VirtualFile virtualFile = file.findVirtualFile();
if (virtualFile == null || virtualFile.isDirectory()) { if (virtualFile == null || !virtualFile.isFile()) {
throw new FileNotFoundException(); throw new FileNotFoundException();
} }

View File

@ -1527,6 +1527,7 @@ public class TArrays extends TObject {
@SafeVarargs @SafeVarargs
public static <T> TList<T> asList(final T... a) { public static <T> TList<T> asList(final T... a) {
Objects.requireNonNull(a);
return new ArrayAsList<>(a); return new ArrayAsList<>(a);
} }

View File

@ -47,7 +47,7 @@ public class WasiFileSystem implements VirtualFileSystem {
} }
void findBestPreopened(String path) { void findBestPreopened(String path) {
if (path.endsWith("/")) { if (path.endsWith("/") && path.length() > 1) {
path = path.substring(0, 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) { private static int getPrefixLength(String name, String dir) {
if (dir.equals("/")) { if (dir.equals("/") && name.startsWith("/")) {
return 0; return 1;
} else {
return name.startsWith(dir) && name.length() > dir.length() && name.charAt(dir.length()) == '/'
? dir.length() + 1 : -1;
} }
return name.startsWith(dir) && name.length() > dir.length() && name.charAt(dir.length()) == '/'
? dir.length() + 1 : -1;
} }
@Override @Override

View File

@ -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.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_GET;
import static org.teavm.backend.wasm.wasi.Wasi.RIGHTS_FD_FILESTAT_SET_SIZE; 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_READ;
import static org.teavm.backend.wasm.wasi.Wasi.RIGHTS_SEEK; 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_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.Dirent;
import org.teavm.backend.wasm.wasi.FdResult; import org.teavm.backend.wasm.wasi.FdResult;
import org.teavm.backend.wasm.wasi.Filestat; 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.backend.wasm.wasi.Wasi;
import org.teavm.interop.Address; import org.teavm.interop.Address;
import org.teavm.interop.Structure; import org.teavm.interop.Structure;
@ -109,12 +110,12 @@ public class WasiVirtualFile implements VirtualFile {
} }
private String[] listFiles(int fd) { private String[] listFiles(int fd) {
SizeResult sizePtr = WasiBuffer.getBuffer().toStructure(); IntResult sizePtr = WasiBuffer.getBuffer().toStructure();
ArrayList<String> list = new ArrayList<>(); ArrayList<String> list = new ArrayList<>();
final int direntSize = Structure.sizeOf(Dirent.class); final int direntSize = Structure.sizeOf(Dirent.class);
byte[] direntArray = fs.buffer; byte[] direntArray = fs.buffer;
Address direntBuffer = Address.align(Address.ofData(direntArray), 8); 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; int bufferSize = direntArray.length - direntArrayOffset;
long cookie = 0; long cookie = 0;
@ -124,27 +125,34 @@ public class WasiVirtualFile implements VirtualFile {
if (errno != ERRNO_SUCCESS) { if (errno != ERRNO_SUCCESS) {
return null; return null;
} }
int size = sizePtr.value;
int remainingSize = bufferSize; int remainingSize = bufferSize;
int entryOffset = 0;
while (true) { while (true) {
if (remainingSize < direntSize) { if (remainingSize < direntSize) {
break; break;
} }
Address direntPtr = direntBuffer.add(direntArrayOffset); Address direntPtr = direntBuffer.add(entryOffset);
Dirent dirent = direntPtr.toStructure(); Dirent dirent = direntPtr.toStructure();
int entryLength = direntSize + (int) dirent.nameLength; int entryLength = direntSize + dirent.nameLength;
if (entryLength > bufferSize) { if (entryLength > bufferSize) {
direntArray = new byte[entryLength * 3 / 2 + 8]; direntArray = new byte[entryLength * 3 / 2 + 8];
direntBuffer = Address.align(Address.ofData(direntArray), 8); direntBuffer = Address.align(Address.ofData(direntArray), 8);
direntArrayOffset = (int) direntBuffer.diff(Address.ofData(direntArray)); direntArrayOffset = (int) direntBuffer.diff(Address.ofData(direntArray));
bufferSize = direntArray.length - direntArrayOffset; bufferSize = direntArray.length - direntArrayOffset;
break; break;
} else if (entryOffset + entryLength > bufferSize) {
break;
} }
cookie = dirent.next; cookie = dirent.next;
list.add(new String(direntArray, direntArrayOffset + direntSize, (int) dirent.nameLength, String name = new String(direntArray, direntArrayOffset + entryOffset + direntSize, dirent.nameLength,
StandardCharsets.UTF_8)); StandardCharsets.UTF_8);
if (!name.equals(".") && !name.equals("..")) {
list.add(name);
}
remainingSize -= entryLength; remainingSize -= entryLength;
direntArrayOffset += entryLength; entryOffset += entryLength;
if (direntArrayOffset == sizePtr.value) { if (entryOffset == size) {
break outer; break outer;
} }
} }
@ -159,7 +167,7 @@ public class WasiVirtualFile implements VirtualFile {
if (baseFd < 0) { if (baseFd < 0) {
return null; 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) { if (fd < 0) {
return null; return null;
} }
@ -214,7 +222,7 @@ public class WasiVirtualFile implements VirtualFile {
public boolean createFile(String fileName) throws IOException { public boolean createFile(String fileName) throws IOException {
init(); init();
if (baseFd < 0) { 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); int fd = open(baseFd, constructPath(path, fileName), (short) (OFLAGS_CREATE | OFLAGS_EXCLUSIVE), 0, (short) 0);
if (fs.errno == ERRNO_EXIST) { if (fs.errno == ERRNO_EXIST) {
@ -241,12 +249,17 @@ public class WasiVirtualFile implements VirtualFile {
@Override @Override
public boolean delete() { public boolean delete() {
init(); init();
if (fullPath == null || baseFd < 0) { if (path == null || baseFd < 0) {
return false; return false;
} }
byte[] bytes = path.getBytes(StandardCharsets.UTF_8); if (isFile()) {
short errno = Wasi.pathUnlinkFile(baseFd, Address.ofData(bytes), bytes.length); byte[] bytes = path.getBytes(StandardCharsets.UTF_8);
return errno == ERRNO_SUCCESS; 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) { private static boolean adopt(int fd, WasiVirtualFile file, String fileName) {

View File

@ -20,6 +20,6 @@ import org.teavm.interop.Structure;
public class Dirent extends Structure { public class Dirent extends Structure {
public long next; public long next;
public long inode; public long inode;
public long nameLength; public int nameLength;
public long fileType; public int fileType;
} }

View File

@ -44,6 +44,7 @@ public final class Wasi {
public static final long RIGHTS_SYNC = 1L << 4; public static final long RIGHTS_SYNC = 1L << 4;
public static final long RIGHTS_CREATE_DIRECTORY = 1L << 9; public static final long RIGHTS_CREATE_DIRECTORY = 1L << 9;
public static final long RIGHTS_CREATE_FILE = 1L << 10; 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_GET = 1L << 21;
public static final long RIGHTS_FD_FILESTAT_SET_SIZE = 1L << 22; public static final long RIGHTS_FD_FILESTAT_SET_SIZE = 1L << 22;
@ -93,7 +94,7 @@ public final class Wasi {
Filestat filestat); Filestat filestat);
@Import(name = "fd_readdir", module = "wasi_snapshot_preview1") @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") @Import(name = "path_open", module = "wasi_snapshot_preview1")
public static native short pathOpen(int dirFd, int lookupFlags, Address path, int pathLength, short oflags, 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") @Import(name = "path_unlink_file", module = "wasi_snapshot_preview1")
public static native short pathUnlinkFile(int fd, Address path, int pathLength); 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") @Import(name = "path_rename", module = "wasi_snapshot_preview1")
public static native short pathRename(int oldFd, Address oldPath, int oldPathLength, int newFd, Address newPath, public static native short pathRename(int oldFd, Address oldPath, int oldPathLength, int newFd, Address newPath,
int newPathLength); int newPathLength);

View File

@ -1 +1 @@
~/.wasmtime/bin/wasmtime run --dir target/wasi-testdir --mapdir /::target/wasi-testdir $1 $2 ~/.wasmtime/bin/wasmtime run --mapdir /::target/wasi-testdir $1 $2