mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
It is an idea of running codespell as part of our presubmit checks. Before enabling it for new changes, let's fix what it has found. Signed-off-by: Andrei Vagin <avagin@gmail.com>
174 lines
5.6 KiB
Go
174 lines
5.6 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 iouringfs
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"gvisor.dev/gvisor/pkg/safemem"
|
|
)
|
|
|
|
// sharedBuffer represents a memory buffer shared between the sentry and
|
|
// userspace. In many cases, this is simply an internal mmap on the underlying
|
|
// memory (aka fast mode). However in some cases the mapped region may lie
|
|
// across multiple blocks and we need to copy the region into a contiguous
|
|
// buffer (aka slow mode). The goal in either case is to present a contiguous
|
|
// slice for easy access.
|
|
//
|
|
// sharedBuffer must be initialized with init before first use.
|
|
//
|
|
// Example
|
|
// =======
|
|
/*
|
|
var sb sharedBuffer
|
|
bs := MapInternal(...)
|
|
sb.init(bs)
|
|
|
|
fetch := true
|
|
|
|
for !done {
|
|
var err error
|
|
|
|
// (Re-)Fetch the view.
|
|
var view []byte
|
|
if fetch {
|
|
view, err = sb.view(128)
|
|
}
|
|
|
|
// Use the view slice to access the region, both for read or write.
|
|
someState := dosomething(view[10])
|
|
view[20] = someState & mask
|
|
|
|
// Write back the changes.
|
|
fetch, err = sb.writeback(128)
|
|
}
|
|
*/
|
|
// In the above example, in fast mode view returns a slice that points directly
|
|
// to the underlying memory and requires no copying. Writeback is a no-op, and
|
|
// the view can be reused on subsequent loop iterations (writeback will return
|
|
// refetch == false).
|
|
//
|
|
// In slow mode, view will copy disjoint parts of the region from different
|
|
// blocks to a single contiguous slice. Writeback will also required a copy, and
|
|
// a new view will have to be fetched on every loop iteration (writeback will
|
|
// return refetch == true).
|
|
//
|
|
// sharedBuffer is *not* thread safe.
|
|
type sharedBuffer struct {
|
|
bs safemem.BlockSeq
|
|
|
|
// copy is allocated once and reused on subsequent calls to view. We don't
|
|
// use the Task's copy scratch buffer because these buffers may be accessed
|
|
// from a background context.
|
|
copy []byte
|
|
|
|
// needsWriteback indicates whether we need to copy out back data from the
|
|
// slice returned by the last view() call.
|
|
needsWriteback bool
|
|
}
|
|
|
|
// init initializes the sharedBuffer, and must be called before first use.
|
|
func (b *sharedBuffer) init(bs safemem.BlockSeq) {
|
|
b.bs = bs
|
|
}
|
|
|
|
func (b *sharedBuffer) valid() bool {
|
|
return !b.bs.IsEmpty()
|
|
}
|
|
|
|
// view returns a slice representing the shared buffer. When done, view must be
|
|
// released with either writeback{,Window} or drop.
|
|
func (b *sharedBuffer) view(n int) ([]byte, error) {
|
|
if uint64(n) > b.bs.NumBytes() {
|
|
// Mapping too short? This is a bug.
|
|
panic(fmt.Sprintf("iouringfs: mapping too short for requested len: mapping length %v, requested %d", b.bs.NumBytes(), n))
|
|
}
|
|
|
|
// Fast path: use mapping directly, no copies required.
|
|
h := b.bs.Head()
|
|
if h.Len() <= n && !h.NeedSafecopy() {
|
|
b.needsWriteback = false
|
|
return h.ToSlice()[:n], nil
|
|
}
|
|
|
|
// Buffer mapped across multiple blocks, or requires safe copy.
|
|
if len(b.copy) < n {
|
|
b.copy = make([]byte, n)
|
|
}
|
|
dst := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(b.copy[:n]))
|
|
copyN, err := safemem.CopySeq(dst, b.bs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if copyN != uint64(n) {
|
|
// Short copy risks exposing stale data from view buffer. This should never happen.
|
|
panic(fmt.Sprintf("iouringfs: short copy for shared buffer view: want %d, got %d", n, copyN))
|
|
}
|
|
b.needsWriteback = true
|
|
return b.copy, nil
|
|
}
|
|
|
|
// writeback writes back the changes to the slice returned by the previous view
|
|
// call. On return, writeback indicates if the previous view may be reused, or
|
|
// needs to be refetched with a new call to view.
|
|
//
|
|
// Precondition: Must follow a call to view. n must match the value passed to
|
|
// view.
|
|
//
|
|
// Postcondition: Previous view is invalidated whether writeback is successful
|
|
// or not. To attempt another modification, a new view may need to be obtained,
|
|
// according to refetch.
|
|
func (b *sharedBuffer) writeback(n int) (refetch bool, err error) {
|
|
return b.writebackWindow(0, n)
|
|
}
|
|
|
|
// writebackWindow is like writeback, but only writes back a subregion. Useful
|
|
// if the caller knows only a small region has been updated, as it reduces how
|
|
// much data need to be copied. writebackWindow still potentially invalidates
|
|
// the entire view, caller must check refetch to determine if the view needs to
|
|
// be refreshed.
|
|
func (b *sharedBuffer) writebackWindow(off, len int) (refetch bool, err error) {
|
|
if uint64(off+len) > b.bs.NumBytes() {
|
|
panic(fmt.Sprintf("iouringfs: requested writeback to shared buffer from offset %d for %d bytes would overflow underlying region of size %d", off, len, b.bs.NumBytes()))
|
|
}
|
|
|
|
if !b.needsWriteback {
|
|
return false, nil
|
|
}
|
|
|
|
// Existing view invalid after this point.
|
|
b.needsWriteback = false
|
|
|
|
src := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(b.copy[off : off+len]))
|
|
dst := b.bs.DropFirst(off)
|
|
copyN, err := safemem.CopySeq(dst, src)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
if copyN != uint64(len) {
|
|
panic(fmt.Sprintf("iouringfs: short copy for shared buffer writeback: want %d, got %d", len, copyN))
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// drop releases a view without writeback. Returns whether any existing views
|
|
// need to be refetched. Useful when caller is done with a view that doesn't
|
|
// need to be modified.
|
|
func (b *sharedBuffer) drop() bool {
|
|
wb := b.needsWriteback
|
|
b.needsWriteback = false
|
|
return wb
|
|
}
|