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 }