summaryrefslogtreecommitdiffhomepage
path: root/pkg/buffer
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/buffer')
-rw-r--r--pkg/buffer/BUILD50
-rw-r--r--pkg/buffer/buffer.go77
-rw-r--r--pkg/buffer/pool.go83
-rw-r--r--pkg/buffer/pool_test.go51
-rw-r--r--pkg/buffer/safemem.go133
-rw-r--r--pkg/buffer/safemem_test.go172
-rw-r--r--pkg/buffer/view.go391
-rw-r--r--pkg/buffer/view_test.go544
-rw-r--r--pkg/buffer/view_unsafe.go25
9 files changed, 0 insertions, 1526 deletions
diff --git a/pkg/buffer/BUILD b/pkg/buffer/BUILD
deleted file mode 100644
index 1186f788e..000000000
--- a/pkg/buffer/BUILD
+++ /dev/null
@@ -1,50 +0,0 @@
-load("//tools:defs.bzl", "go_library", "go_test")
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-package(licenses = ["notice"])
-
-go_template_instance(
- name = "buffer_list",
- out = "buffer_list.go",
- package = "buffer",
- prefix = "buffer",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*buffer",
- "Linker": "*buffer",
- },
-)
-
-go_library(
- name = "buffer",
- srcs = [
- "buffer.go",
- "buffer_list.go",
- "pool.go",
- "safemem.go",
- "view.go",
- "view_unsafe.go",
- ],
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/context",
- "//pkg/log",
- "//pkg/safemem",
- "//pkg/usermem",
- ],
-)
-
-go_test(
- name = "buffer_test",
- size = "small",
- srcs = [
- "pool_test.go",
- "safemem_test.go",
- "view_test.go",
- ],
- library = ":buffer",
- deps = [
- "//pkg/safemem",
- "//pkg/state",
- ],
-)
diff --git a/pkg/buffer/buffer.go b/pkg/buffer/buffer.go
deleted file mode 100644
index 311808ae9..000000000
--- a/pkg/buffer/buffer.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2020 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 provides the implementation of a buffer view.
-//
-// A view is an flexible buffer, supporting the safecopy operations natively as
-// well as the ability to grow via either prepend or append, as well as shrink.
-package buffer
-
-// buffer encapsulates a queueable byte buffer.
-//
-// +stateify savable
-type buffer struct {
- data []byte
- read int
- write int
- bufferEntry
-}
-
-// init performs in-place initialization for zero value.
-func (b *buffer) init(size int) {
- b.data = make([]byte, size)
-}
-
-// Reset resets read and write locations, effectively emptying the buffer.
-func (b *buffer) Reset() {
- b.read = 0
- b.write = 0
-}
-
-// Full indicates the buffer is full.
-//
-// This indicates there is no capacity left to write.
-func (b *buffer) Full() bool {
- return b.write == len(b.data)
-}
-
-// ReadSize returns the number of bytes available for reading.
-func (b *buffer) ReadSize() int {
- return b.write - b.read
-}
-
-// ReadMove advances the read index by the given amount.
-func (b *buffer) ReadMove(n int) {
- b.read += n
-}
-
-// ReadSlice returns the read slice for this buffer.
-func (b *buffer) ReadSlice() []byte {
- return b.data[b.read:b.write]
-}
-
-// WriteSize returns the number of bytes available for writing.
-func (b *buffer) WriteSize() int {
- return len(b.data) - b.write
-}
-
-// WriteMove advances the write index by the given amount.
-func (b *buffer) WriteMove(n int) {
- b.write += n
-}
-
-// WriteSlice returns the write slice for this buffer.
-func (b *buffer) WriteSlice() []byte {
- return b.data[b.write:]
-}
diff --git a/pkg/buffer/pool.go b/pkg/buffer/pool.go
deleted file mode 100644
index 7ad6132ab..000000000
--- a/pkg/buffer/pool.go
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2020 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
-
-const (
- // embeddedCount is the number of buffer structures embedded in the pool. It
- // is also the number for overflow allocations.
- embeddedCount = 8
-
- // defaultBufferSize is the default size for each underlying storage buffer.
- //
- // It is slightly less than two pages. This is done intentionally to ensure
- // that the buffer object aligns with runtime internals. This two page size
- // will effectively minimize internal fragmentation, but still have a large
- // enough chunk to limit excessive segmentation.
- defaultBufferSize = 8144
-)
-
-// pool allocates buffer.
-//
-// It contains an embedded buffer storage for fast path when the number of
-// buffers needed is small.
-//
-// +stateify savable
-type pool struct {
- bufferSize int
- avail []buffer `state:"nosave"`
- embeddedStorage [embeddedCount]buffer `state:"wait"`
-}
-
-// get gets a new buffer from p.
-func (p *pool) get() *buffer {
- if p.avail == nil {
- p.avail = p.embeddedStorage[:]
- }
- if len(p.avail) == 0 {
- p.avail = make([]buffer, embeddedCount)
- }
- if p.bufferSize <= 0 {
- p.bufferSize = defaultBufferSize
- }
- buf := &p.avail[0]
- buf.init(p.bufferSize)
- p.avail = p.avail[1:]
- return buf
-}
-
-// put releases buf.
-func (p *pool) put(buf *buffer) {
- // Remove reference to the underlying storage, allowing it to be garbage
- // collected.
- buf.data = nil
-}
-
-// setBufferSize sets the size of underlying storage buffer for future
-// allocations. It can be called at any time.
-func (p *pool) setBufferSize(size int) {
- p.bufferSize = size
-}
-
-// afterLoad is invoked by stateify.
-func (p *pool) afterLoad() {
- // S/R does not save subslice into embeddedStorage correctly. Restore
- // available portion of embeddedStorage manually. Restore as nil if none used.
- for i := len(p.embeddedStorage); i > 0; i-- {
- if p.embeddedStorage[i-1].data != nil {
- p.avail = p.embeddedStorage[i:]
- break
- }
- }
-}
diff --git a/pkg/buffer/pool_test.go b/pkg/buffer/pool_test.go
deleted file mode 100644
index 8584bac89..000000000
--- a/pkg/buffer/pool_test.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 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 (
- "testing"
-)
-
-func TestGetDefaultBufferSize(t *testing.T) {
- var p pool
- for i := 0; i < embeddedCount*2; i++ {
- buf := p.get()
- if got, want := len(buf.data), defaultBufferSize; got != want {
- t.Errorf("#%d len(buf.data) = %d, want %d", i, got, want)
- }
- }
-}
-
-func TestGetCustomBufferSize(t *testing.T) {
- const size = 100
-
- var p pool
- p.setBufferSize(size)
- for i := 0; i < embeddedCount*2; i++ {
- buf := p.get()
- if got, want := len(buf.data), size; got != want {
- t.Errorf("#%d len(buf.data) = %d, want %d", i, got, want)
- }
- }
-}
-
-func TestPut(t *testing.T) {
- var p pool
- buf := p.get()
- p.put(buf)
- if buf.data != nil {
- t.Errorf("buf.data = %x, want nil", buf.data)
- }
-}
diff --git a/pkg/buffer/safemem.go b/pkg/buffer/safemem.go
deleted file mode 100644
index 8b42575b4..000000000
--- a/pkg/buffer/safemem.go
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright 2020 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 (
- "gvisor.dev/gvisor/pkg/safemem"
-)
-
-// WriteBlock returns this buffer as a write Block.
-func (b *buffer) WriteBlock() safemem.Block {
- return safemem.BlockFromSafeSlice(b.WriteSlice())
-}
-
-// ReadBlock returns this buffer as a read Block.
-func (b *buffer) ReadBlock() safemem.Block {
- return safemem.BlockFromSafeSlice(b.ReadSlice())
-}
-
-// WriteFromSafememReader writes up to count bytes from r to v and advances the
-// write index by the number of bytes written. It calls r.ReadToBlocks() at
-// most once.
-func (v *View) WriteFromSafememReader(r safemem.Reader, count uint64) (uint64, error) {
- if count == 0 {
- return 0, nil
- }
-
- var (
- dst safemem.BlockSeq
- blocks []safemem.Block
- )
-
- // Need at least one buffer.
- firstBuf := v.data.Back()
- if firstBuf == nil {
- firstBuf = v.pool.get()
- v.data.PushBack(firstBuf)
- }
-
- // Does the last block have sufficient capacity alone?
- if l := uint64(firstBuf.WriteSize()); l >= count {
- dst = safemem.BlockSeqOf(firstBuf.WriteBlock().TakeFirst64(count))
- } else {
- // Append blocks until sufficient.
- count -= l
- blocks = append(blocks, firstBuf.WriteBlock())
- for count > 0 {
- emptyBuf := v.pool.get()
- v.data.PushBack(emptyBuf)
- block := emptyBuf.WriteBlock().TakeFirst64(count)
- count -= uint64(block.Len())
- blocks = append(blocks, block)
- }
- dst = safemem.BlockSeqFromSlice(blocks)
- }
-
- // Perform I/O.
- n, err := r.ReadToBlocks(dst)
- v.size += int64(n)
-
- // Update all indices.
- for left := n; left > 0; firstBuf = firstBuf.Next() {
- if l := firstBuf.WriteSize(); left >= uint64(l) {
- firstBuf.WriteMove(l) // Whole block.
- left -= uint64(l)
- } else {
- firstBuf.WriteMove(int(left)) // Partial block.
- left = 0
- }
- }
-
- return n, err
-}
-
-// WriteFromBlocks implements safemem.Writer.WriteFromBlocks. It advances the
-// write index by the number of bytes written.
-func (v *View) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error) {
- return v.WriteFromSafememReader(&safemem.BlockSeqReader{srcs}, srcs.NumBytes())
-}
-
-// ReadToSafememWriter reads up to count bytes from v to w. It does not advance
-// the read index. It calls w.WriteFromBlocks() at most once.
-func (v *View) ReadToSafememWriter(w safemem.Writer, count uint64) (uint64, error) {
- if count == 0 {
- return 0, nil
- }
-
- var (
- src safemem.BlockSeq
- blocks []safemem.Block
- )
-
- firstBuf := v.data.Front()
- if firstBuf == nil {
- return 0, nil // No EOF.
- }
-
- // Is all the data in a single block?
- if l := uint64(firstBuf.ReadSize()); l >= count {
- src = safemem.BlockSeqOf(firstBuf.ReadBlock().TakeFirst64(count))
- } else {
- // Build a list of all the buffers.
- count -= l
- blocks = append(blocks, firstBuf.ReadBlock())
- for buf := firstBuf.Next(); buf != nil && count > 0; buf = buf.Next() {
- block := buf.ReadBlock().TakeFirst64(count)
- count -= uint64(block.Len())
- blocks = append(blocks, block)
- }
- src = safemem.BlockSeqFromSlice(blocks)
- }
-
- // Perform I/O. As documented, we don't advance the read index.
- return w.WriteFromBlocks(src)
-}
-
-// ReadToBlocks implements safemem.Reader.ReadToBlocks. It does not advance the
-// read index by the number of bytes read, such that it's only safe to call if
-// the caller guarantees that ReadToBlocks will only be called once.
-func (v *View) ReadToBlocks(dsts safemem.BlockSeq) (uint64, error) {
- return v.ReadToSafememWriter(&safemem.BlockSeqWriter{dsts}, dsts.NumBytes())
-}
diff --git a/pkg/buffer/safemem_test.go b/pkg/buffer/safemem_test.go
deleted file mode 100644
index 721cc5934..000000000
--- a/pkg/buffer/safemem_test.go
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2020 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 (
- "bytes"
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/pkg/safemem"
-)
-
-func TestSafemem(t *testing.T) {
- const bufferSize = defaultBufferSize
-
- testCases := []struct {
- name string
- input string
- output string
- readLen int
- op func(*View)
- }{
- // Basic coverage.
- {
- name: "short",
- input: "010",
- output: "010",
- },
- {
- name: "long",
- input: "0" + strings.Repeat("1", bufferSize) + "0",
- output: "0" + strings.Repeat("1", bufferSize) + "0",
- },
- {
- name: "short-read",
- input: "0",
- readLen: 100, // > size.
- output: "0",
- },
- {
- name: "zero-read",
- input: "0",
- output: "",
- },
- {
- name: "read-empty",
- input: "",
- readLen: 1, // > size.
- output: "",
- },
-
- // Ensure offsets work.
- {
- name: "offsets-short",
- input: "012",
- output: "2",
- op: func(v *View) {
- v.TrimFront(2)
- },
- },
- {
- name: "offsets-long0",
- input: "0" + strings.Repeat("1", bufferSize) + "0",
- output: strings.Repeat("1", bufferSize) + "0",
- op: func(v *View) {
- v.TrimFront(1)
- },
- },
- {
- name: "offsets-long1",
- input: "0" + strings.Repeat("1", bufferSize) + "0",
- output: strings.Repeat("1", bufferSize-1) + "0",
- op: func(v *View) {
- v.TrimFront(2)
- },
- },
- {
- name: "offsets-long2",
- input: "0" + strings.Repeat("1", bufferSize) + "0",
- output: "10",
- op: func(v *View) {
- v.TrimFront(bufferSize)
- },
- },
-
- // Ensure truncation works.
- {
- name: "truncate-short",
- input: "012",
- output: "01",
- op: func(v *View) {
- v.Truncate(2)
- },
- },
- {
- name: "truncate-long0",
- input: "0" + strings.Repeat("1", bufferSize) + "0",
- output: "0" + strings.Repeat("1", bufferSize),
- op: func(v *View) {
- v.Truncate(bufferSize + 1)
- },
- },
- {
- name: "truncate-long1",
- input: "0" + strings.Repeat("1", bufferSize) + "0",
- output: "0" + strings.Repeat("1", bufferSize-1),
- op: func(v *View) {
- v.Truncate(bufferSize)
- },
- },
- {
- name: "truncate-long2",
- input: "0" + strings.Repeat("1", bufferSize) + "0",
- output: "01",
- op: func(v *View) {
- v.Truncate(2)
- },
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- // Construct the new view.
- var view View
- bs := safemem.BlockSeqOf(safemem.BlockFromSafeSlice([]byte(tc.input)))
- n, err := view.WriteFromBlocks(bs)
- if err != nil {
- t.Errorf("expected err nil, got %v", err)
- }
- if n != uint64(len(tc.input)) {
- t.Errorf("expected %d bytes, got %d", len(tc.input), n)
- }
-
- // Run the operation.
- if tc.op != nil {
- tc.op(&view)
- }
-
- // Read and validate.
- readLen := tc.readLen
- if readLen == 0 {
- readLen = len(tc.output) // Default.
- }
- out := make([]byte, readLen)
- bs = safemem.BlockSeqOf(safemem.BlockFromSafeSlice(out))
- n, err = view.ReadToBlocks(bs)
- if err != nil {
- t.Errorf("expected nil, got %v", err)
- }
- if n != uint64(len(tc.output)) {
- t.Errorf("expected %d bytes, got %d", len(tc.output), n)
- }
-
- // Ensure the contents are correct.
- if !bytes.Equal(out[:n], []byte(tc.output[:n])) {
- t.Errorf("contents are wrong: expected %q, got %q", tc.output, string(out))
- }
- })
- }
-}
diff --git a/pkg/buffer/view.go b/pkg/buffer/view.go
deleted file mode 100644
index 00652d675..000000000
--- a/pkg/buffer/view.go
+++ /dev/null
@@ -1,391 +0,0 @@
-// Copyright 2020 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"
-)
-
-// View is a non-linear buffer.
-//
-// All methods are thread compatible.
-//
-// +stateify savable
-type View struct {
- data bufferList
- size int64
- pool pool
-}
-
-// TrimFront removes the first count bytes from the buffer.
-func (v *View) TrimFront(count int64) {
- if count >= v.size {
- v.advanceRead(v.size)
- } else {
- v.advanceRead(count)
- }
-}
-
-// ReadAt implements io.ReaderAt.ReadAt.
-func (v *View) ReadAt(p []byte, offset int64) (int, error) {
- var (
- skipped int64
- done int64
- )
- for buf := v.data.Front(); buf != nil && done < int64(len(p)); buf = buf.Next() {
- needToSkip := int(offset - skipped)
- if sz := buf.ReadSize(); sz <= needToSkip {
- skipped += int64(sz)
- continue
- }
-
- // Actually read data.
- n := copy(p[done:], buf.ReadSlice()[needToSkip:])
- skipped += int64(needToSkip)
- done += int64(n)
- }
- if int(done) < len(p) || offset+done == v.size {
- return int(done), io.EOF
- }
- return int(done), nil
-}
-
-// advanceRead advances the view's read index.
-//
-// Precondition: there must be sufficient bytes in the buffer.
-func (v *View) advanceRead(count int64) {
- for buf := v.data.Front(); buf != nil && count > 0; {
- sz := int64(buf.ReadSize())
- if sz > count {
- // There is still data for reading.
- buf.ReadMove(int(count))
- v.size -= count
- count = 0
- break
- }
-
- // Consume the whole buffer.
- oldBuf := buf
- buf = buf.Next() // Iterate.
- v.data.Remove(oldBuf)
- oldBuf.Reset()
- v.pool.put(oldBuf)
-
- // Update counts.
- count -= sz
- v.size -= sz
- }
- if count > 0 {
- panic(fmt.Sprintf("advanceRead still has %d bytes remaining", count))
- }
-}
-
-// Truncate truncates the view to the given bytes.
-//
-// This will not grow the view, only shrink it. If a length is passed that is
-// greater than the current size of the view, then nothing will happen.
-//
-// Precondition: length must be >= 0.
-func (v *View) Truncate(length int64) {
- if length < 0 {
- panic("negative length provided")
- }
- if length >= v.size {
- return // Nothing to do.
- }
- for buf := v.data.Back(); buf != nil && v.size > length; buf = v.data.Back() {
- sz := int64(buf.ReadSize())
- if after := v.size - sz; after < length {
- // Truncate the buffer locally.
- left := (length - after)
- buf.write = buf.read + int(left)
- v.size = length
- break
- }
-
- // Drop the buffer completely; see above.
- v.data.Remove(buf)
- buf.Reset()
- v.pool.put(buf)
- v.size -= sz
- }
-}
-
-// Grow grows the given view to the number of bytes, which will be appended. If
-// zero is true, all these bytes will be zero. If zero is false, then this is
-// the caller's responsibility.
-//
-// Precondition: length must be >= 0.
-func (v *View) Grow(length int64, zero bool) {
- if length < 0 {
- panic("negative length provided")
- }
- for v.size < length {
- buf := v.data.Back()
-
- // Is there some space in the last buffer?
- if buf == nil || buf.Full() {
- buf = v.pool.get()
- v.data.PushBack(buf)
- }
-
- // Write up to length bytes.
- sz := buf.WriteSize()
- if int64(sz) > length-v.size {
- sz = int(length - v.size)
- }
-
- // Zero the written section; note that this pattern is
- // specifically recognized and optimized by the compiler.
- if zero {
- for i := buf.write; i < buf.write+sz; i++ {
- buf.data[i] = 0
- }
- }
-
- // Advance the index.
- buf.WriteMove(sz)
- v.size += int64(sz)
- }
-}
-
-// Prepend prepends the given data.
-func (v *View) Prepend(data []byte) {
- // Is there any space in the first buffer?
- if buf := v.data.Front(); buf != nil && buf.read > 0 {
- // Fill up before the first write.
- avail := buf.read
- bStart := 0
- dStart := len(data) - avail
- if avail > len(data) {
- bStart = avail - len(data)
- dStart = 0
- }
- n := copy(buf.data[bStart:], data[dStart:])
- data = data[:dStart]
- v.size += int64(n)
- buf.read -= n
- }
-
- for len(data) > 0 {
- // Do we need an empty buffer?
- buf := v.pool.get()
- v.data.PushFront(buf)
-
- // The buffer is empty; copy last chunk.
- avail := len(buf.data)
- bStart := 0
- dStart := len(data) - avail
- if avail > len(data) {
- bStart = avail - len(data)
- dStart = 0
- }
-
- // We have to put the data at the end of the current
- // buffer in order to ensure that the next prepend will
- // correctly fill up the beginning of this buffer.
- n := copy(buf.data[bStart:], data[dStart:])
- data = data[:dStart]
- v.size += int64(n)
- buf.read = len(buf.data) - n
- buf.write = len(buf.data)
- }
-}
-
-// Append appends the given data.
-func (v *View) Append(data []byte) {
- for done := 0; done < len(data); {
- buf := v.data.Back()
-
- // Ensure there's a buffer with space.
- if buf == nil || buf.Full() {
- buf = v.pool.get()
- v.data.PushBack(buf)
- }
-
- // Copy in to the given buffer.
- n := copy(buf.WriteSlice(), data[done:])
- done += n
- buf.WriteMove(n)
- v.size += int64(n)
- }
-}
-
-// Flatten returns a flattened copy of this data.
-//
-// This method should not be used in any performance-sensitive paths. It may
-// allocate a fresh byte slice sufficiently large to contain all the data in
-// the buffer. This is principally for debugging.
-//
-// N.B. Tee data still belongs to this view, as if there is a single buffer
-// present, then it will be returned directly. This should be used for
-// temporary use only, and a reference to the given slice should not be held.
-func (v *View) Flatten() []byte {
- if buf := v.data.Front(); buf == nil {
- return nil // No data at all.
- } else if buf.Next() == nil {
- return buf.ReadSlice() // Only one buffer.
- }
- data := make([]byte, 0, v.size) // Need to flatten.
- for buf := v.data.Front(); buf != nil; buf = buf.Next() {
- // Copy to the allocated slice.
- data = append(data, buf.ReadSlice()...)
- }
- return data
-}
-
-// Size indicates the total amount of data available in this view.
-func (v *View) Size() int64 {
- return v.size
-}
-
-// Copy makes a strict copy of this view.
-func (v *View) Copy() (other View) {
- for buf := v.data.Front(); buf != nil; buf = buf.Next() {
- other.Append(buf.ReadSlice())
- }
- return
-}
-
-// Apply applies the given function across all valid data.
-func (v *View) Apply(fn func([]byte)) {
- for buf := v.data.Front(); buf != nil; buf = buf.Next() {
- fn(buf.ReadSlice())
- }
-}
-
-// Merge merges the provided View with this one.
-//
-// The other view will be appended to v, and other will be empty after this
-// operation completes.
-func (v *View) Merge(other *View) {
- // Copy over all buffers.
- for buf := other.data.Front(); buf != nil; buf = other.data.Front() {
- other.data.Remove(buf)
- v.data.PushBack(buf)
- }
-
- // Adjust sizes.
- v.size += other.size
- other.size = 0
-}
-
-// WriteFromReader writes to the buffer from an io.Reader.
-//
-// A minimum read size equal to unsafe.Sizeof(unintptr) is enforced,
-// provided that count is greater than or equal to unsafe.Sizeof(uintptr).
-func (v *View) WriteFromReader(r io.Reader, count int64) (int64, error) {
- var (
- done int64
- n int
- err error
- )
- for done < count {
- buf := v.data.Back()
-
- // Ensure we have an empty buffer.
- if buf == nil || buf.Full() {
- buf = v.pool.get()
- v.data.PushBack(buf)
- }
-
- // Is this less than the minimum batch?
- if buf.WriteSize() < minBatch && (count-done) >= int64(minBatch) {
- tmp := make([]byte, minBatch)
- n, err = r.Read(tmp)
- v.Append(tmp[:n])
- done += int64(n)
- if err != nil {
- break
- }
- continue
- }
-
- // Limit the read, if necessary.
- sz := buf.WriteSize()
- if left := count - done; int64(sz) > left {
- sz = int(left)
- }
-
- // Pass the relevant portion of the buffer.
- n, err = r.Read(buf.WriteSlice()[:sz])
- buf.WriteMove(n)
- done += int64(n)
- v.size += int64(n)
- if err == io.EOF {
- err = nil // Short write allowed.
- break
- } else if err != nil {
- break
- }
- }
- return done, err
-}
-
-// ReadToWriter reads from the buffer into an io.Writer.
-//
-// N.B. This does not consume the bytes read. TrimFront should
-// be called appropriately after this call in order to do so.
-//
-// A minimum write size equal to unsafe.Sizeof(unintptr) is enforced,
-// provided that count is greater than or equal to unsafe.Sizeof(uintptr).
-func (v *View) ReadToWriter(w io.Writer, count int64) (int64, error) {
- var (
- done int64
- n int
- err error
- )
- offset := 0 // Spill-over for batching.
- for buf := v.data.Front(); buf != nil && done < count; buf = buf.Next() {
- // Has this been consumed? Skip it.
- sz := buf.ReadSize()
- if sz <= offset {
- offset -= sz
- continue
- }
- sz -= offset
-
- // Is this less than the minimum batch?
- left := count - done
- if sz < minBatch && left >= int64(minBatch) && (v.size-done) >= int64(minBatch) {
- tmp := make([]byte, minBatch)
- n, err = v.ReadAt(tmp, done)
- w.Write(tmp[:n])
- done += int64(n)
- offset = n - sz // Reset below.
- if err != nil {
- break
- }
- continue
- }
-
- // Limit the write if necessary.
- if int64(sz) >= left {
- sz = int(left)
- }
-
- // Perform the actual write.
- n, err = w.Write(buf.ReadSlice()[offset : offset+sz])
- done += int64(n)
- if err != nil {
- break
- }
-
- // Reset spill-over.
- offset = 0
- }
- return done, err
-}
diff --git a/pkg/buffer/view_test.go b/pkg/buffer/view_test.go
deleted file mode 100644
index 839af0223..000000000
--- a/pkg/buffer/view_test.go
+++ /dev/null
@@ -1,544 +0,0 @@
-// Copyright 2020 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 (
- "bytes"
- "context"
- "io"
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/pkg/state"
-)
-
-const bufferSize = defaultBufferSize
-
-func fillAppend(v *View, data []byte) {
- v.Append(data)
-}
-
-func fillAppendEnd(v *View, data []byte) {
- v.Grow(bufferSize-1, false)
- v.Append(data)
- v.TrimFront(bufferSize - 1)
-}
-
-func fillWriteFromReader(v *View, data []byte) {
- b := bytes.NewBuffer(data)
- v.WriteFromReader(b, int64(len(data)))
-}
-
-func fillWriteFromReaderEnd(v *View, data []byte) {
- v.Grow(bufferSize-1, false)
- b := bytes.NewBuffer(data)
- v.WriteFromReader(b, int64(len(data)))
- v.TrimFront(bufferSize - 1)
-}
-
-var fillFuncs = map[string]func(*View, []byte){
- "append": fillAppend,
- "appendEnd": fillAppendEnd,
- "writeFromReader": fillWriteFromReader,
- "writeFromReaderEnd": fillWriteFromReaderEnd,
-}
-
-func BenchmarkReadAt(b *testing.B) {
- b.ReportAllocs()
- var v View
- v.Append(make([]byte, 100))
-
- buf := make([]byte, 10)
- for i := 0; i < b.N; i++ {
- v.ReadAt(buf, 0)
- }
-}
-
-func BenchmarkWriteRead(b *testing.B) {
- b.ReportAllocs()
- var v View
- sz := 1000
- wbuf := make([]byte, sz)
- rbuf := bytes.NewBuffer(make([]byte, sz))
- for i := 0; i < b.N; i++ {
- v.Append(wbuf)
- rbuf.Reset()
- v.ReadToWriter(rbuf, int64(sz))
- }
-}
-
-func testReadAt(t *testing.T, v *View, offset int64, n int, wantStr string, wantErr error) {
- t.Helper()
- d := make([]byte, n)
- n, err := v.ReadAt(d, offset)
- if n != len(wantStr) {
- t.Errorf("got %d, want %d", n, len(wantStr))
- }
- if err != wantErr {
- t.Errorf("got err %v, want %v", err, wantErr)
- }
- if !bytes.Equal(d[:n], []byte(wantStr)) {
- t.Errorf("got %q, want %q", string(d[:n]), wantStr)
- }
-}
-
-func TestView(t *testing.T) {
- testCases := []struct {
- name string
- input string
- output string
- op func(*testing.T, *View)
- }{
- // Preconditions.
- {
- name: "truncate-check",
- input: "hello",
- output: "hello", // Not touched.
- op: func(t *testing.T, v *View) {
- defer func() {
- if r := recover(); r == nil {
- t.Errorf("Truncate(-1) did not panic")
- }
- }()
- v.Truncate(-1)
- },
- },
- {
- name: "grow-check",
- input: "hello",
- output: "hello", // Not touched.
- op: func(t *testing.T, v *View) {
- defer func() {
- if r := recover(); r == nil {
- t.Errorf("Grow(-1) did not panic")
- }
- }()
- v.Grow(-1, false)
- },
- },
- {
- name: "advance-check",
- input: "hello",
- output: "", // Consumed.
- op: func(t *testing.T, v *View) {
- defer func() {
- if r := recover(); r == nil {
- t.Errorf("advanceRead(Size()+1) did not panic")
- }
- }()
- v.advanceRead(v.Size() + 1)
- },
- },
-
- // Prepend.
- {
- name: "prepend",
- input: "world",
- output: "hello world",
- op: func(t *testing.T, v *View) {
- v.Prepend([]byte("hello "))
- },
- },
- {
- name: "prepend-backfill-full",
- input: "hello world",
- output: "jello world",
- op: func(t *testing.T, v *View) {
- v.TrimFront(1)
- v.Prepend([]byte("j"))
- },
- },
- {
- name: "prepend-backfill-under",
- input: "hello world",
- output: "hola world",
- op: func(t *testing.T, v *View) {
- v.TrimFront(5)
- v.Prepend([]byte("hola"))
- },
- },
- {
- name: "prepend-backfill-over",
- input: "hello world",
- output: "smello world",
- op: func(t *testing.T, v *View) {
- v.TrimFront(1)
- v.Prepend([]byte("sm"))
- },
- },
- {
- name: "prepend-fill",
- input: strings.Repeat("1", bufferSize-1),
- output: "0" + strings.Repeat("1", bufferSize-1),
- op: func(t *testing.T, v *View) {
- v.Prepend([]byte("0"))
- },
- },
- {
- name: "prepend-overflow",
- input: strings.Repeat("1", bufferSize),
- output: "0" + strings.Repeat("1", bufferSize),
- op: func(t *testing.T, v *View) {
- v.Prepend([]byte("0"))
- },
- },
- {
- name: "prepend-multiple-buffers",
- input: strings.Repeat("1", bufferSize-1),
- output: strings.Repeat("0", bufferSize*3) + strings.Repeat("1", bufferSize-1),
- op: func(t *testing.T, v *View) {
- v.Prepend([]byte(strings.Repeat("0", bufferSize*3)))
- },
- },
-
- // Append and write.
- {
- name: "append",
- input: "hello",
- output: "hello world",
- op: func(t *testing.T, v *View) {
- v.Append([]byte(" world"))
- },
- },
- {
- name: "append-fill",
- input: strings.Repeat("1", bufferSize-1),
- output: strings.Repeat("1", bufferSize-1) + "0",
- op: func(t *testing.T, v *View) {
- v.Append([]byte("0"))
- },
- },
- {
- name: "append-overflow",
- input: strings.Repeat("1", bufferSize),
- output: strings.Repeat("1", bufferSize) + "0",
- op: func(t *testing.T, v *View) {
- v.Append([]byte("0"))
- },
- },
- {
- name: "append-multiple-buffers",
- input: strings.Repeat("1", bufferSize-1),
- output: strings.Repeat("1", bufferSize-1) + strings.Repeat("0", bufferSize*3),
- op: func(t *testing.T, v *View) {
- v.Append([]byte(strings.Repeat("0", bufferSize*3)))
- },
- },
-
- // Truncate.
- {
- name: "truncate",
- input: "hello world",
- output: "hello",
- op: func(t *testing.T, v *View) {
- v.Truncate(5)
- },
- },
- {
- name: "truncate-noop",
- input: "hello world",
- output: "hello world",
- op: func(t *testing.T, v *View) {
- v.Truncate(v.Size() + 1)
- },
- },
- {
- name: "truncate-multiple-buffers",
- input: strings.Repeat("1", bufferSize*2),
- output: strings.Repeat("1", bufferSize*2-1),
- op: func(t *testing.T, v *View) {
- v.Truncate(bufferSize*2 - 1)
- },
- },
- {
- name: "truncate-multiple-buffers-to-one",
- input: strings.Repeat("1", bufferSize*2),
- output: "11111",
- op: func(t *testing.T, v *View) {
- v.Truncate(5)
- },
- },
-
- // TrimFront.
- {
- name: "trim",
- input: "hello world",
- output: "world",
- op: func(t *testing.T, v *View) {
- v.TrimFront(6)
- },
- },
- {
- name: "trim-too-large",
- input: "hello world",
- output: "",
- op: func(t *testing.T, v *View) {
- v.TrimFront(v.Size() + 1)
- },
- },
- {
- name: "trim-multiple-buffers",
- input: strings.Repeat("1", bufferSize*2),
- output: strings.Repeat("1", bufferSize*2-1),
- op: func(t *testing.T, v *View) {
- v.TrimFront(1)
- },
- },
- {
- name: "trim-multiple-buffers-to-one-buffer",
- input: strings.Repeat("1", bufferSize*2),
- output: "1",
- op: func(t *testing.T, v *View) {
- v.TrimFront(bufferSize*2 - 1)
- },
- },
-
- // Grow.
- {
- name: "grow",
- input: "hello world",
- output: "hello world",
- op: func(t *testing.T, v *View) {
- v.Grow(1, true)
- },
- },
- {
- name: "grow-from-zero",
- output: strings.Repeat("\x00", 1024),
- op: func(t *testing.T, v *View) {
- v.Grow(1024, true)
- },
- },
- {
- name: "grow-from-non-zero",
- input: strings.Repeat("1", bufferSize),
- output: strings.Repeat("1", bufferSize) + strings.Repeat("\x00", bufferSize),
- op: func(t *testing.T, v *View) {
- v.Grow(bufferSize*2, true)
- },
- },
-
- // Copy.
- {
- name: "copy",
- input: "hello",
- output: "hello",
- op: func(t *testing.T, v *View) {
- other := v.Copy()
- bs := other.Flatten()
- want := []byte("hello")
- if !bytes.Equal(bs, want) {
- t.Errorf("expected %v, got %v", want, bs)
- }
- },
- },
- {
- name: "copy-large",
- input: strings.Repeat("1", bufferSize+1),
- output: strings.Repeat("1", bufferSize+1),
- op: func(t *testing.T, v *View) {
- other := v.Copy()
- bs := other.Flatten()
- want := []byte(strings.Repeat("1", bufferSize+1))
- if !bytes.Equal(bs, want) {
- t.Errorf("expected %v, got %v", want, bs)
- }
- },
- },
-
- // Merge.
- {
- name: "merge",
- input: "hello",
- output: "hello world",
- op: func(t *testing.T, v *View) {
- var other View
- other.Append([]byte(" world"))
- v.Merge(&other)
- if sz := other.Size(); sz != 0 {
- t.Errorf("expected 0, got %d", sz)
- }
- },
- },
- {
- name: "merge-large",
- input: strings.Repeat("1", bufferSize+1),
- output: strings.Repeat("1", bufferSize+1) + strings.Repeat("0", bufferSize+1),
- op: func(t *testing.T, v *View) {
- var other View
- other.Append([]byte(strings.Repeat("0", bufferSize+1)))
- v.Merge(&other)
- if sz := other.Size(); sz != 0 {
- t.Errorf("expected 0, got %d", sz)
- }
- },
- },
-
- // ReadAt.
- {
- name: "readat",
- input: "hello",
- output: "hello",
- op: func(t *testing.T, v *View) { testReadAt(t, v, 0, 6, "hello", io.EOF) },
- },
- {
- name: "readat-long",
- input: "hello",
- output: "hello",
- op: func(t *testing.T, v *View) { testReadAt(t, v, 0, 8, "hello", io.EOF) },
- },
- {
- name: "readat-short",
- input: "hello",
- output: "hello",
- op: func(t *testing.T, v *View) { testReadAt(t, v, 0, 3, "hel", nil) },
- },
- {
- name: "readat-offset",
- input: "hello",
- output: "hello",
- op: func(t *testing.T, v *View) { testReadAt(t, v, 2, 3, "llo", io.EOF) },
- },
- {
- name: "readat-long-offset",
- input: "hello",
- output: "hello",
- op: func(t *testing.T, v *View) { testReadAt(t, v, 2, 8, "llo", io.EOF) },
- },
- {
- name: "readat-short-offset",
- input: "hello",
- output: "hello",
- op: func(t *testing.T, v *View) { testReadAt(t, v, 2, 2, "ll", nil) },
- },
- {
- name: "readat-skip-all",
- input: "hello",
- output: "hello",
- op: func(t *testing.T, v *View) { testReadAt(t, v, bufferSize+1, 1, "", io.EOF) },
- },
- {
- name: "readat-second-buffer",
- input: strings.Repeat("0", bufferSize+1) + "12",
- output: strings.Repeat("0", bufferSize+1) + "12",
- op: func(t *testing.T, v *View) { testReadAt(t, v, bufferSize+1, 1, "1", nil) },
- },
- {
- name: "readat-second-buffer-end",
- input: strings.Repeat("0", bufferSize+1) + "12",
- output: strings.Repeat("0", bufferSize+1) + "12",
- op: func(t *testing.T, v *View) { testReadAt(t, v, bufferSize+1, 2, "12", io.EOF) },
- },
- }
-
- for _, tc := range testCases {
- for fillName, fn := range fillFuncs {
- t.Run(fillName+"/"+tc.name, func(t *testing.T) {
- // Construct & fill the view.
- var view View
- fn(&view, []byte(tc.input))
-
- // Run the operation.
- if tc.op != nil {
- tc.op(t, &view)
- }
-
- // Flatten and validate.
- out := view.Flatten()
- if !bytes.Equal([]byte(tc.output), out) {
- t.Errorf("expected %q, got %q", tc.output, string(out))
- }
-
- // Ensure the size is correct.
- if len(out) != int(view.Size()) {
- t.Errorf("size is wrong: expected %d, got %d", len(out), view.Size())
- }
-
- // Calculate contents via apply.
- var appliedOut []byte
- view.Apply(func(b []byte) {
- appliedOut = append(appliedOut, b...)
- })
- if len(appliedOut) != len(out) {
- t.Errorf("expected %d, got %d", len(out), len(appliedOut))
- }
- if !bytes.Equal(appliedOut, out) {
- t.Errorf("expected %v, got %v", out, appliedOut)
- }
-
- // Calculate contents via ReadToWriter.
- var b bytes.Buffer
- n, err := view.ReadToWriter(&b, int64(len(out)))
- if n != int64(len(out)) {
- t.Errorf("expected %d, got %d", len(out), n)
- }
- if err != nil {
- t.Errorf("expected nil, got %v", err)
- }
- if !bytes.Equal(b.Bytes(), out) {
- t.Errorf("expected %v, got %v", out, b.Bytes())
- }
- })
- }
- }
-}
-
-func doSaveAndLoad(t *testing.T, toSave, toLoad *View) {
- t.Helper()
- var buf bytes.Buffer
- ctx := context.Background()
- if _, err := state.Save(ctx, &buf, toSave); err != nil {
- t.Fatal("state.Save:", err)
- }
- if _, err := state.Load(ctx, bytes.NewReader(buf.Bytes()), toLoad); err != nil {
- t.Fatal("state.Load:", err)
- }
-}
-
-func TestSaveRestoreViewEmpty(t *testing.T) {
- var toSave View
- var v View
- doSaveAndLoad(t, &toSave, &v)
-
- if got := v.pool.avail; got != nil {
- t.Errorf("pool is not in zero state: v.pool.avail = %v, want nil", got)
- }
- if got := v.Flatten(); len(got) != 0 {
- t.Errorf("v.Flatten() = %x, want []", got)
- }
-}
-
-func TestSaveRestoreView(t *testing.T) {
- // Create data that fits 2.5 slots.
- data := bytes.Join([][]byte{
- bytes.Repeat([]byte{1, 2}, defaultBufferSize),
- bytes.Repeat([]byte{3}, defaultBufferSize/2),
- }, nil)
-
- var toSave View
- toSave.Append(data)
-
- var v View
- doSaveAndLoad(t, &toSave, &v)
-
- // Next available slot at index 3; 0-2 slot are used.
- i := 3
- if got, want := &v.pool.avail[0], &v.pool.embeddedStorage[i]; got != want {
- t.Errorf("next available buffer points to %p, want %p (&v.pool.embeddedStorage[%d])", got, want, i)
- }
- if got := v.Flatten(); !bytes.Equal(got, data) {
- t.Errorf("v.Flatten() = %x, want %x", got, data)
- }
-}
diff --git a/pkg/buffer/view_unsafe.go b/pkg/buffer/view_unsafe.go
deleted file mode 100644
index d1ef39b26..000000000
--- a/pkg/buffer/view_unsafe.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2020 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 (
- "unsafe"
-)
-
-// minBatch is the smallest Read or Write operation that the
-// WriteFromReader and ReadToWriter functions will use.
-//
-// This is defined as the size of a native pointer.
-const minBatch = int(unsafe.Sizeof(uintptr(0)))