Skip to content

Commit

Permalink
WasiFileSystem.openReadOnly and openReadWrite (#1315)
Browse files Browse the repository at this point in the history
This is the FileHandle API.
  • Loading branch information
squarejesse authored Jul 28, 2023
1 parent 32dc86b commit 0709c1f
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 4 deletions.
4 changes: 3 additions & 1 deletion okio-wasifilesystem/src/wasmMain/kotlin/okio/FileSink.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import kotlin.wasm.unsafe.withScopedMemoryAllocator
import okio.internal.ErrnoException
import okio.internal.fdClose
import okio.internal.preview1.fd
import okio.internal.preview1.fd_sync
import okio.internal.preview1.fd_write
import okio.internal.preview1.size
import okio.internal.write
Expand Down Expand Up @@ -74,7 +75,8 @@ internal class FileSink(
}

override fun flush() {
// TODO
val errno = fd_sync(fd)
if (errno != 0) throw ErrnoException(errno.toShort())
}

override fun timeout() = Timeout.NONE
Expand Down
121 changes: 121 additions & 0 deletions okio-wasifilesystem/src/wasmMain/kotlin/okio/WasiFileHandle.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright (C) 2023 Square, Inc.
*
* 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 okio

import kotlin.wasm.unsafe.Pointer
import kotlin.wasm.unsafe.withScopedMemoryAllocator
import okio.internal.ErrnoException
import okio.internal.fdClose
import okio.internal.preview1.fd
import okio.internal.preview1.fd_filestat_get
import okio.internal.preview1.fd_filestat_set_size
import okio.internal.preview1.fd_pread
import okio.internal.preview1.fd_pwrite
import okio.internal.preview1.fd_sync
import okio.internal.read
import okio.internal.write

internal class WasiFileHandle(
private val fd: fd,
readWrite: Boolean,
) : FileHandle(readWrite) {
override fun protectedSize(): Long {
withScopedMemoryAllocator { allocator ->
val returnPointer: Pointer = allocator.allocate(64) // filestat is 64 bytes.
val errno = fd_filestat_get(fd, returnPointer.address.toInt())
if (errno != 0) throw ErrnoException(errno.toShort())

return (returnPointer + 32).loadLong()
}
}

override fun protectedRead(
fileOffset: Long,
array: ByteArray,
arrayOffset: Int,
byteCount: Int,
): Int {
withScopedMemoryAllocator { allocator ->
val dataPointer = allocator.allocate(byteCount)

val iovec = allocator.allocate(8)
iovec.storeInt(dataPointer.address.toInt())
(iovec + 4).storeInt(byteCount)

val returnPointer = allocator.allocate(4) // `size` is u32, 4 bytes.
val errno = fd_pread(
fd = fd,
iovs = iovec.address.toInt(),
iovsSize = 1,
offset = fileOffset,
returnPointer = returnPointer.address.toInt(),
)
if (errno != 0) throw ErrnoException(errno.toShort())

val readByteCount = returnPointer.loadInt()
if (byteCount != -1) {
dataPointer.read(array, arrayOffset, readByteCount)
}

if (readByteCount == 0) return -1
return readByteCount
}
}

override fun protectedWrite(
fileOffset: Long,
array: ByteArray,
arrayOffset: Int,
byteCount: Int,
) {
withScopedMemoryAllocator { allocator ->
val dataPointer = allocator.write(array, arrayOffset, byteCount)

val iovec = allocator.allocate(8)
iovec.storeInt(dataPointer.address.toInt())
(iovec + 4).storeInt(byteCount)

val returnPointer = allocator.allocate(4) // `size` is u32, 4 bytes.
val errno = fd_pwrite(
fd = fd,
iovs = iovec.address.toInt(),
iovsSize = 1,
offset = fileOffset,
returnPointer = returnPointer.address.toInt(),
)
if (errno != 0) throw ErrnoException(errno.toShort())

val writtenByteCount = returnPointer.loadInt()
if (writtenByteCount != byteCount) {
throw IOException("expected $byteCount but was $writtenByteCount")
}
}
}

override fun protectedFlush() {
val errno = fd_sync(fd)
if (errno != 0) throw ErrnoException(errno.toShort())
}

override fun protectedResize(size: Long) {
val errno = fd_filestat_set_size(fd, size)
if (errno != 0) throw ErrnoException(errno.toShort())
}

override fun protectedClose() {
fdClose(fd)
}
}
38 changes: 35 additions & 3 deletions okio-wasifilesystem/src/wasmMain/kotlin/okio/WasiFileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ import okio.internal.preview1.path_remove_directory
import okio.internal.preview1.path_rename
import okio.internal.preview1.path_symlink
import okio.internal.preview1.path_unlink_file
import okio.internal.preview1.right_fd_filestat_get
import okio.internal.preview1.right_fd_filestat_set_size
import okio.internal.preview1.right_fd_read
import okio.internal.preview1.right_fd_readdir
import okio.internal.preview1.right_fd_seek
import okio.internal.preview1.right_fd_sync
import okio.internal.preview1.right_fd_write
import okio.internal.preview1.rights
import okio.internal.readString
Expand Down Expand Up @@ -228,11 +232,39 @@ object WasiFileSystem : FileSystem() {
}

override fun openReadOnly(file: Path): FileHandle {
TODO("Not yet implemented")
val rightsBase = right_fd_filestat_get or
right_fd_read or
right_fd_seek or
right_fd_sync
val fd = pathOpen(
path = file.toString(),
oflags = 0,
rightsBase = rightsBase,
)
return WasiFileHandle(fd, readWrite = false)
}

override fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean): FileHandle {
TODO("Not yet implemented")
val oflags = when {
mustCreate && mustExist -> {
throw IllegalArgumentException("Cannot require mustCreate and mustExist at the same time.")
}
mustCreate -> oflag_creat or oflag_excl
mustExist -> 0
else -> oflag_creat
}
val rightsBase = right_fd_filestat_get or
right_fd_filestat_set_size or
right_fd_read or
right_fd_seek or
right_fd_sync or
right_fd_write
val fd = pathOpen(
path = file.toString(),
oflags = oflags,
rightsBase = rightsBase,
)
return WasiFileHandle(fd, readWrite = true)
}

override fun source(file: Path): Source {
Expand All @@ -255,7 +287,7 @@ object WasiFileSystem : FileSystem() {
fd = pathOpen(
path = file.toString(),
oflags = oflags,
rightsBase = right_fd_write,
rightsBase = right_fd_write or right_fd_sync,
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,51 @@ internal external fun fd_close(
fd: fd,
): Int // should be Short??

/**
* fd_filestat_get(fd: fd) -> Result<filestat, errno>
*
* Return the attributes of an open file.
*/
@WasmImport("wasi_snapshot_preview1", "fd_filestat_get")
internal external fun fd_filestat_get(
fd: fd,
returnPointer: PointerU8,
): Int // should be Short??

/**
* fd_pread(fd: fd, iovs: iovec_array, offset: filesize) -> Result<size, errno>
*
* Read from a file descriptor.
* Note: This is similar to `readv` in POSIX.
*/
@WasmImport("wasi_snapshot_preview1", "fd_pread")
internal external fun fd_pread(
fd: fd,
iovs: PointerU8,
iovsSize: size,
offset: Long,
returnPointer: PointerU8,
): Int // should be Short??

/**
* fd_pwrite(fd: fd, iovs: ciovec_array, offset: filesize) -> Result<size, errno>`
*
* Write to a file descriptor, without using and updating the file descriptor's offset.
* Note: This is similar to `pwritev` in Linux (and other Unix-es).
*
* Like Linux (and other Unix-es), any calls of `pwrite` (and other
* functions to read or write) for a regular file by other threads in the
* WASI process should not be interleaved while `pwrite` is executed.
*/
@WasmImport("wasi_snapshot_preview1", "fd_pwrite")
internal external fun fd_pwrite(
fd: fd,
iovs: PointerU8,
iovsSize: size,
offset: Long,
returnPointer: PointerU8,
): Int // should be Short??

/**
* fd_read(fd: fd, iovs: iovec_array) -> Result<size, errno>
*
Expand Down Expand Up @@ -237,6 +282,29 @@ internal external fun fd_readdir(
returnPointer: PointerU8,
): Int // should be Short??

/**
* fd_filestat_set_size(fd: fd, size: filesize) -> Result<(), errno>
*
* Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros.
* Note: This is similar to `ftruncate` in POSIX.
*/
@WasmImport("wasi_snapshot_preview1", "fd_filestat_set_size")
internal external fun fd_filestat_set_size(
fd: fd,
size: Long,
): Int // should be Short??

/**
* fd_sync(fd: fd) -> Result<(), errno>
*
* Synchronize the data and metadata of a file to disk.
* Note: This is similar to `fsync` in POSIX.
*/
@WasmImport("wasi_snapshot_preview1", "fd_sync")
internal external fun fd_sync(
fd: fd,
): Int // should be Short??

/**
* fd_write(fd: fd, iovs: ciovec_array) -> Result<size, errno>
*
Expand Down
102 changes: 102 additions & 0 deletions okio-wasifilesystem/src/wasmTest/kotlin/okio/WasiTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -272,4 +272,106 @@ class WasiTest {
assertEquals("regularFile".toPath(), symlinkMetadata.symlinkTarget)
assertEquals("regularFile".length.toLong(), symlinkMetadata.size)
}

@Test
fun fileHandleRead() {
val path = base / "file.txt"
fileSystem.write(path) {
writeUtf8("this is a file about dogs and cats")
}
fileSystem.openReadOnly(path).use { handle ->
val sink = Buffer()
handle.read(21L, sink, 4L)

assertEquals(
"dogs",
sink.readUtf8(),
)
}
}

@Test
fun fileHandleWrite() {
val path = base / "file.txt"
fileSystem.write(path) {
writeUtf8("this is a file about cats and cats")
}
fileSystem.openReadWrite(path).use { handle ->
val source = Buffer().writeUtf8("dogs")
handle.write(21L, source, 4L)

assertEquals(
"this is a file about dogs and cats",
fileSystem.read(path) {
readUtf8()
},
)
}
}

@Test
fun fileHandleGetSize() {
val path = base / "file.txt"
fileSystem.write(path) {
writeUtf8("this is a file about dogs and cats")
}
fileSystem.openReadOnly(path).use { handle ->
assertEquals(
34L,
handle.size(),
)
}
}

@Test
fun fileHandleResize() {
val path = base / "file.txt"
fileSystem.write(path) {
writeUtf8("this is a file about dogs and cats")
}
fileSystem.openReadWrite(path).use { handle ->
handle.resize(25L)

assertEquals(
"this is a file about dogs",
fileSystem.read(path) {
readUtf8()
},
)
}
}

@Test
fun fileHandleFlush() {
val path = base / "file.txt"
fileSystem.openReadWrite(path).use { handle ->
handle.sink().buffer().use {
it.writeUtf8("hello")
}
handle.flush()

assertEquals(
"hello",
fileSystem.read(path) {
readUtf8()
},
)
}
}

@Test
fun fileSinkFlush() {
val path = base / "file.txt"
fileSystem.write(path) {
writeUtf8("hello")
flush()

assertEquals(
"hello",
fileSystem.read(path) {
readUtf8()
},
)
}
}
}

0 comments on commit 0709c1f

Please sign in to comment.