blob: e9935afb932a069a93bbc8bb5a141fa4f9ae1ed9 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package vmobuffer
import (
"io"
"os"
"syscall/zx"
"go.fuchsia.dev/fuchsia/src/lib/component"
)
var _ io.WriteCloser = (*VMOBuffer)(nil)
// VMOBuffer implements a self-resizing io.WriteCloser backed by a VMO.
type VMOBuffer struct {
vmo zx.VMO
size uint64
offset uint64
}
func NewVMOBuffer(size uint64, name string) (*VMOBuffer, error) {
vmo, err := zx.NewVMO(size, zx.VMOOptionResizable)
if err != nil {
return nil, err
}
if err := vmo.Handle().SetProperty(zx.PropName, []byte(name)); err != nil {
vmo.Close()
return nil, err
}
return &VMOBuffer{vmo: vmo, size: size, offset: 0}, nil
}
func (b *VMOBuffer) grow() error {
newSize := b.size * 2
if err := b.vmo.SetSize(newSize); err != nil {
return err
}
b.size = newSize
return nil
}
func (b *VMOBuffer) Write(p []byte) (int, error) {
if b.vmo == zx.VMO(zx.HandleInvalid) {
return 0, os.ErrClosed
}
if len(p) > int(b.size-b.offset) {
if err := b.grow(); err != nil {
return 0, err
}
}
if err := b.vmo.Write(p, b.offset); err != nil {
return 0, err
}
b.offset += uint64(len(p))
return len(p), nil
}
func (b *VMOBuffer) Close() error {
return b.vmo.Close()
}
func (b *VMOBuffer) GetVMO() *zx.VMO {
return &b.vmo
}
// ToVMOReader converts the VMOBuffer into a VMOReader that outputs the data
// that has been written into the VMOBuffer. The VMOBuffer should be considered
// to be consumed after calling ToVMOReader(), and it is not valid to perform
// operations on it afterward.
func (b *VMOBuffer) ToVMOReader() (*VMOReader, error) {
if b.vmo == zx.VMO(zx.HandleInvalid) {
return nil, os.ErrClosed
}
if err := b.vmo.SetSize(b.offset); err != nil {
return nil, err
}
vmo := b.vmo
b.vmo = zx.VMO(zx.HandleInvalid)
return &VMOReader{
vmo: vmo,
size: b.offset,
offset: 0,
}, nil
}
// VMOReader implements a component.Reader backed by a VMO.
type VMOReader struct {
vmo zx.VMO
size uint64
offset uint64
}
var _ component.Reader = (*VMOReader)(nil)
func NewVMOReader(vmo zx.VMO, size uint64) *VMOReader {
return &VMOReader{vmo: vmo, size: size, offset: 0}
}
func (r *VMOReader) Read(p []byte) (int, error) {
n, err := r.ReadAt(p, int64(r.offset))
r.offset += uint64(n)
return n, err
}
func (r *VMOReader) ReadAt(p []byte, off int64) (int, error) {
if r.vmo == zx.VMO(zx.HandleInvalid) {
return 0, os.ErrClosed
}
if off >= int64(r.size) {
return 0, io.EOF
}
numBytesToRead := len(p)
if bytesRemaining := int(r.size - uint64(off)); bytesRemaining < numBytesToRead {
numBytesToRead = bytesRemaining
}
if err := r.vmo.Read(p[:numBytesToRead], uint64(off)); err != nil {
return 0, err
}
return numBytesToRead, nil
}
func (r *VMOReader) Seek(offset int64, whence int) (int64, error) {
offset, err := func() (int64, error) {
switch whence {
case io.SeekCurrent:
return offset + int64(r.offset), nil
case io.SeekStart:
return offset, nil
case io.SeekEnd:
return offset + int64(r.size), nil
default:
return 0, os.ErrInvalid
}
}()
if err != nil {
return 0, err
}
if offset < 0 {
return 0, os.ErrInvalid
}
r.offset = uint64(offset)
return offset, nil
}
func (r *VMOReader) Close() error {
return r.vmo.Close()
}
func (r *VMOReader) Size() uint64 {
return r.size
}
func (r *VMOReader) GetVMO() *zx.VMO {
return &r.vmo
}