gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/fsimpl/iouringfs/buffer.go (about)

     1  // Copyright 2022 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package iouringfs
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"gvisor.dev/gvisor/pkg/safemem"
    21  )
    22  
    23  // sharedBuffer represents a memory buffer shared between the sentry and
    24  // userspace. In many cases, this is simply an internal mmap on the underlying
    25  // memory (aka fast mode). However in some cases the mapped region may lie
    26  // across multiple blocks and we need to copy the region into a contiguous
    27  // buffer (aka slow mode). The goal in either case is to present a contiguous
    28  // slice for easy access.
    29  //
    30  // sharedBuffer must be initialized with init before first use.
    31  //
    32  // Example
    33  // =======
    34  /*
    35  var sb sharedBuffer
    36  bs := MapInternal(...)
    37  sb.init(bs)
    38  
    39  fetch := true
    40  
    41  for !done {
    42  	var err error
    43  
    44  	// (Re-)Fetch the view.
    45  	var view []byte
    46  	if fetch {
    47  		view, err = sb.view(128)
    48  	}
    49  
    50  	// Use the view slice to access the region, both for read or write.
    51  	someState := dosomething(view[10])
    52  	view[20] = someState & mask
    53  
    54  	// Write back the changes.
    55  	fetch, err = sb.writeback(128)
    56  }
    57  */
    58  // In the above example, in fast mode view returns a slice that points directly
    59  // to the underlying memory and requires no copying. Writeback is a no-op, and
    60  // the view can be reused on subsequent loop iterations (writeback will return
    61  // refetch == false).
    62  //
    63  // In slow mode, view will copy disjoint parts of the region from different
    64  // blocks to a single contiguous slice. Writeback will also required a copy, and
    65  // a new view will have to be fetched on every loop iteration (writeback will
    66  // return refetch == true).
    67  //
    68  // sharedBuffer is *not* thread safe.
    69  type sharedBuffer struct {
    70  	bs safemem.BlockSeq
    71  
    72  	// copy is allocated once and reused on subsequent calls to view. We don't
    73  	// use the Task's copy scratch buffer because these buffers may be accessed
    74  	// from a background context.
    75  	copy []byte
    76  
    77  	// needsWriteback indicates whether we need to copy out back data from the
    78  	// slice returned by the last view() call.
    79  	needsWriteback bool
    80  }
    81  
    82  // init initializes the sharedBuffer, and must be called before first use.
    83  func (b *sharedBuffer) init(bs safemem.BlockSeq) {
    84  	b.bs = bs
    85  }
    86  
    87  func (b *sharedBuffer) valid() bool {
    88  	return !b.bs.IsEmpty()
    89  }
    90  
    91  // view returns a slice representing the shared buffer. When done, view must be
    92  // released with either writeback{,Window} or drop.
    93  func (b *sharedBuffer) view(n int) ([]byte, error) {
    94  	if uint64(n) > b.bs.NumBytes() {
    95  		// Mapping too short? This is a bug.
    96  		panic(fmt.Sprintf("iouringfs: mapping too short for requested len: mapping length %v, requested %d", b.bs.NumBytes(), n))
    97  	}
    98  
    99  	// Fast path: use mapping directly, no copies required.
   100  	h := b.bs.Head()
   101  	if h.Len() <= n && !h.NeedSafecopy() {
   102  		b.needsWriteback = false
   103  		return h.ToSlice()[:n], nil
   104  	}
   105  
   106  	// Buffer mapped across multiple blocks, or requires safe copy.
   107  	if len(b.copy) < n {
   108  		b.copy = make([]byte, n)
   109  	}
   110  	dst := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(b.copy[:n]))
   111  	copyN, err := safemem.CopySeq(dst, b.bs)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	if copyN != uint64(n) {
   116  		// Short copy risks exposing stale data from view buffer. This should never happen.
   117  		panic(fmt.Sprintf("iouringfs: short copy for shared buffer view: want %d, got %d", n, copyN))
   118  	}
   119  	b.needsWriteback = true
   120  	return b.copy, nil
   121  }
   122  
   123  // writeback writes back the changes to the slice returned by the previous view
   124  // call. On return, writeback indicates if the previous view may be reused, or
   125  // needs to be refetched with a new call to view.
   126  //
   127  // Precondition: Must follow a call to view. n must match the value passed to
   128  // view.
   129  //
   130  // Postcondition: Previous view is invalidated whether writeback is successful
   131  // or not. To attempt another modification, a new view may need to be obtained,
   132  // according to refetch.
   133  func (b *sharedBuffer) writeback(n int) (refetch bool, err error) {
   134  	return b.writebackWindow(0, n)
   135  }
   136  
   137  // writebackWindow is like writeback, but only writes back a subregion. Useful
   138  // if the caller knows only a small region has been updated, as it reduces how
   139  // much data need to be copied. writebackWindow still potentially invalidates
   140  // the entire view, caller must check refetch to determine if the view needs to
   141  // be refreshed.
   142  func (b *sharedBuffer) writebackWindow(off, len int) (refetch bool, err error) {
   143  	if uint64(off+len) > b.bs.NumBytes() {
   144  		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()))
   145  	}
   146  
   147  	if !b.needsWriteback {
   148  		return false, nil
   149  	}
   150  
   151  	// Existing view invalid after this point.
   152  	b.needsWriteback = false
   153  
   154  	src := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(b.copy[off : off+len]))
   155  	dst := b.bs.DropFirst(off)
   156  	copyN, err := safemem.CopySeq(dst, src)
   157  	if err != nil {
   158  		return true, err
   159  	}
   160  	if copyN != uint64(len) {
   161  		panic(fmt.Sprintf("iouringfs: short copy for shared buffer writeback: want %d, got %d", len, copyN))
   162  	}
   163  	return true, nil
   164  }
   165  
   166  // drop releases a view without writeback. Returns whether any existing views
   167  // need to be refetched. Useful when caller is done with a view that doesn't
   168  // need to be modified.
   169  func (b *sharedBuffer) drop() bool {
   170  	wb := b.needsWriteback
   171  	b.needsWriteback = false
   172  	return wb
   173  }