367 lines
8.8 KiB
Go
367 lines
8.8 KiB
Go
// Copyright 2022 The gVisor Authors.
|
|
//
|
|
// 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 buffer
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"gvisor.dev/gvisor/pkg/sync"
|
|
)
|
|
|
|
// ReadSize is the default amount that a View's size is increased by when an
|
|
// io.Reader has more data than a View can hold during calls to ReadFrom.
|
|
const ReadSize = 512
|
|
|
|
var viewPool = sync.Pool{
|
|
New: func() any {
|
|
return &View{}
|
|
},
|
|
}
|
|
|
|
// View is a window into a shared chunk. Views are held by Buffers in
|
|
// viewLists to represent contiguous memory.
|
|
//
|
|
// A View must be created with NewView, NewViewWithData, or Clone. Owners are
|
|
// responsible for maintaining ownership over their views. When Views need to be
|
|
// shared or copied, the owner should create a new View with Clone. Clone must
|
|
// only ever be called on a owned View, not a borrowed one.
|
|
//
|
|
// Users are responsible for calling Release when finished with their View so
|
|
// that its resources can be returned to the pool.
|
|
//
|
|
// Users must not write directly to slices returned by AsSlice. Instead, they
|
|
// must use Write/WriteAt/CopyIn to modify the underlying View. This preserves
|
|
// the safety guarantees of copy-on-write.
|
|
//
|
|
// +stateify savable
|
|
type View struct {
|
|
ViewEntry `state:"nosave"`
|
|
read int
|
|
write int
|
|
chunk *chunk
|
|
}
|
|
|
|
// NewView creates a new view with capacity at least as big as cap. It is
|
|
// analogous to make([]byte, 0, cap).
|
|
func NewView(cap int) *View {
|
|
c := newChunk(cap)
|
|
v := viewPool.Get().(*View)
|
|
*v = View{chunk: c}
|
|
return v
|
|
}
|
|
|
|
// NewViewSize creates a new view with capacity at least as big as size and
|
|
// length that is exactly size. It is analogous to make([]byte, size).
|
|
func NewViewSize(size int) *View {
|
|
v := NewView(size)
|
|
v.Grow(size)
|
|
return v
|
|
}
|
|
|
|
// NewViewWithData creates a new view and initializes it with data. This
|
|
// function should be used with caution to avoid unnecessary []byte allocations.
|
|
// When in doubt use NewWithView to maximize chunk reuse in production
|
|
// environments.
|
|
func NewViewWithData(data []byte) *View {
|
|
c := newChunk(len(data))
|
|
v := viewPool.Get().(*View)
|
|
*v = View{chunk: c}
|
|
v.Write(data)
|
|
return v
|
|
}
|
|
|
|
// Clone creates a shallow clone of v where the underlying chunk is shared.
|
|
//
|
|
// The caller must own the View to call Clone. It is not safe to call Clone
|
|
// on a borrowed or shared View because it can race with other View methods.
|
|
func (v *View) Clone() *View {
|
|
if v == nil {
|
|
panic("cannot clone a nil view")
|
|
}
|
|
v.chunk.IncRef()
|
|
newV := viewPool.Get().(*View)
|
|
newV.chunk = v.chunk
|
|
newV.read = v.read
|
|
newV.write = v.write
|
|
return newV
|
|
}
|
|
|
|
// Release releases the chunk held by v and returns v to the pool.
|
|
func (v *View) Release() {
|
|
if v == nil {
|
|
panic("cannot release a nil view")
|
|
}
|
|
v.chunk.DecRef()
|
|
*v = View{}
|
|
viewPool.Put(v)
|
|
}
|
|
|
|
// Reset sets the view's read and write indices back to zero.
|
|
func (v *View) Reset() {
|
|
if v == nil {
|
|
panic("cannot reset a nil view")
|
|
}
|
|
v.read = 0
|
|
v.write = 0
|
|
}
|
|
|
|
func (v *View) sharesChunk() bool {
|
|
return v.chunk.refCount.Load() > 1
|
|
}
|
|
|
|
// Full indicates the chunk is full.
|
|
//
|
|
// This indicates there is no capacity left to write.
|
|
func (v *View) Full() bool {
|
|
return v == nil || v.write == len(v.chunk.data)
|
|
}
|
|
|
|
// Capacity returns the total size of this view's chunk.
|
|
func (v *View) Capacity() int {
|
|
if v == nil {
|
|
return 0
|
|
}
|
|
return len(v.chunk.data)
|
|
}
|
|
|
|
// Size returns the size of data written to the view.
|
|
func (v *View) Size() int {
|
|
if v == nil {
|
|
return 0
|
|
}
|
|
return v.write - v.read
|
|
}
|
|
|
|
// TrimFront advances the read index by the given amount.
|
|
func (v *View) TrimFront(n int) {
|
|
if v.read+n > v.write {
|
|
panic("cannot trim past the end of a view")
|
|
}
|
|
v.read += n
|
|
}
|
|
|
|
// AsSlice returns a slice of the data written to this view.
|
|
func (v *View) AsSlice() []byte {
|
|
if v.Size() == 0 {
|
|
return nil
|
|
}
|
|
return v.chunk.data[v.read:v.write]
|
|
}
|
|
|
|
// ToSlice returns an owned copy of the data in this view.
|
|
func (v *View) ToSlice() []byte {
|
|
if v.Size() == 0 {
|
|
return nil
|
|
}
|
|
s := make([]byte, v.Size())
|
|
copy(s, v.AsSlice())
|
|
return s
|
|
}
|
|
|
|
// AvailableSize returns the number of bytes available for writing.
|
|
func (v *View) AvailableSize() int {
|
|
if v == nil {
|
|
return 0
|
|
}
|
|
return len(v.chunk.data) - v.write
|
|
}
|
|
|
|
// Read reads v's data into p.
|
|
//
|
|
// Implements the io.Reader interface.
|
|
func (v *View) Read(p []byte) (int, error) {
|
|
if len(p) == 0 {
|
|
return 0, nil
|
|
}
|
|
if v.Size() == 0 {
|
|
return 0, io.EOF
|
|
}
|
|
n := copy(p, v.AsSlice())
|
|
v.TrimFront(n)
|
|
return n, nil
|
|
}
|
|
|
|
// ReadByte implements the io.ByteReader interface.
|
|
func (v *View) ReadByte() (byte, error) {
|
|
if v.Size() == 0 {
|
|
return 0, io.EOF
|
|
}
|
|
b := v.AsSlice()[0]
|
|
v.read++
|
|
return b, nil
|
|
}
|
|
|
|
// WriteTo writes data to w until the view is empty or an error occurs. The
|
|
// return value n is the number of bytes written.
|
|
//
|
|
// WriteTo implements the io.WriterTo interface.
|
|
func (v *View) WriteTo(w io.Writer) (n int64, err error) {
|
|
if v.Size() > 0 {
|
|
sz := v.Size()
|
|
m, e := w.Write(v.AsSlice())
|
|
v.TrimFront(m)
|
|
n = int64(m)
|
|
if e != nil {
|
|
return n, e
|
|
}
|
|
if m != sz {
|
|
return n, io.ErrShortWrite
|
|
}
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
// ReadAt reads data to the p starting at offset.
|
|
//
|
|
// Implements the io.ReaderAt interface.
|
|
func (v *View) ReadAt(p []byte, off int) (int, error) {
|
|
if off < 0 || off > v.Size() {
|
|
return 0, fmt.Errorf("ReadAt(): offset out of bounds: want 0 < off < %d, got off=%d", v.Size(), off)
|
|
}
|
|
n := copy(p, v.AsSlice()[off:])
|
|
return n, nil
|
|
}
|
|
|
|
// Write writes data to the view's chunk starting at the v.write index. If the
|
|
// view's chunk has a reference count greater than 1, the chunk is copied first
|
|
// and then written to.
|
|
//
|
|
// Implements the io.Writer interface.
|
|
func (v *View) Write(p []byte) (int, error) {
|
|
if v == nil {
|
|
panic("cannot write to a nil view")
|
|
}
|
|
if v.AvailableSize() < len(p) {
|
|
v.growCap(len(p) - v.AvailableSize())
|
|
} else if v.sharesChunk() {
|
|
defer v.chunk.DecRef()
|
|
v.chunk = v.chunk.Clone()
|
|
}
|
|
n := copy(v.chunk.data[v.write:], p)
|
|
v.write += n
|
|
if n < len(p) {
|
|
return n, io.ErrShortWrite
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
// ReadFrom reads data from r until EOF and appends it to the buffer, growing
|
|
// the buffer as needed. The return value n is the number of bytes read. Any
|
|
// error except io.EOF encountered during the read is also returned.
|
|
//
|
|
// ReadFrom implements the io.ReaderFrom interface.
|
|
func (v *View) ReadFrom(r io.Reader) (n int64, err error) {
|
|
if v == nil {
|
|
panic("cannot write to a nil view")
|
|
}
|
|
if v.sharesChunk() {
|
|
defer v.chunk.DecRef()
|
|
v.chunk = v.chunk.Clone()
|
|
}
|
|
for {
|
|
// Check for EOF to avoid an unnnecesary allocation.
|
|
if _, e := r.Read(nil); e == io.EOF {
|
|
return n, nil
|
|
}
|
|
if v.AvailableSize() == 0 {
|
|
v.growCap(ReadSize)
|
|
}
|
|
m, e := r.Read(v.availableSlice())
|
|
v.write += m
|
|
n += int64(m)
|
|
|
|
if e == io.EOF {
|
|
return n, nil
|
|
}
|
|
if e != nil {
|
|
return n, e
|
|
}
|
|
}
|
|
}
|
|
|
|
// WriteAt writes data to the views's chunk starting at start. If the
|
|
// view's chunk has a reference count greater than 1, the chunk is copied first
|
|
// and then written to.
|
|
//
|
|
// Implements the io.WriterAt interface.
|
|
func (v *View) WriteAt(p []byte, off int) (int, error) {
|
|
if v == nil {
|
|
panic("cannot write to a nil view")
|
|
}
|
|
if off < 0 || off > v.Size() {
|
|
return 0, fmt.Errorf("write offset out of bounds: want 0 < off < %d, got off=%d", v.Size(), off)
|
|
}
|
|
if v.sharesChunk() {
|
|
defer v.chunk.DecRef()
|
|
v.chunk = v.chunk.Clone()
|
|
}
|
|
n := copy(v.AsSlice()[off:], p)
|
|
if n < len(p) {
|
|
return n, io.ErrShortWrite
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
// Grow increases the size of the view. If the new size is greater than the
|
|
// view's current capacity, Grow will reallocate the view with an increased
|
|
// capacity.
|
|
func (v *View) Grow(n int) {
|
|
if v == nil {
|
|
panic("cannot grow a nil view")
|
|
}
|
|
if v.write+n > v.Capacity() {
|
|
v.growCap(n)
|
|
}
|
|
v.write += n
|
|
}
|
|
|
|
// growCap increases the capacity of the view by at least n.
|
|
func (v *View) growCap(n int) {
|
|
if v == nil {
|
|
panic("cannot grow a nil view")
|
|
}
|
|
defer v.chunk.DecRef()
|
|
old := v.AsSlice()
|
|
v.chunk = newChunk(v.Capacity() + n)
|
|
copy(v.chunk.data, old)
|
|
v.read = 0
|
|
v.write = len(old)
|
|
}
|
|
|
|
// CapLength caps the length of the view's read slice to n. If n > v.Size(),
|
|
// the function is a no-op.
|
|
func (v *View) CapLength(n int) {
|
|
if v == nil {
|
|
panic("cannot resize a nil view")
|
|
}
|
|
if n < 0 {
|
|
panic("n must be >= 0")
|
|
}
|
|
if n > v.Size() {
|
|
n = v.Size()
|
|
}
|
|
v.write = v.read + n
|
|
}
|
|
|
|
func (v *View) availableSlice() []byte {
|
|
if v.sharesChunk() {
|
|
defer v.chunk.DecRef()
|
|
c := v.chunk.Clone()
|
|
v.chunk = c
|
|
}
|
|
return v.chunk.data[v.write:]
|
|
}
|