github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/fsutil/file_range_set.go (about) 1 // Copyright 2018 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 fsutil 16 17 import ( 18 "fmt" 19 "io" 20 "math" 21 22 "github.com/nicocha30/gvisor-ligolo/pkg/context" 23 "github.com/nicocha30/gvisor-ligolo/pkg/hostarch" 24 "github.com/nicocha30/gvisor-ligolo/pkg/safemem" 25 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/memmap" 26 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/pgalloc" 27 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/usage" 28 ) 29 30 // FileRangeSet maps offsets into a memmap.Mappable to offsets into a 31 // memmap.File. It is used to implement Mappables that store data in 32 // sparsely-allocated memory. 33 // 34 // type FileRangeSet <generated by go_generics> 35 36 // FileRangeSetFunctions implements segment.Functions for FileRangeSet. 37 type FileRangeSetFunctions struct{} 38 39 // MinKey implements segment.Functions.MinKey. 40 func (FileRangeSetFunctions) MinKey() uint64 { 41 return 0 42 } 43 44 // MaxKey implements segment.Functions.MaxKey. 45 func (FileRangeSetFunctions) MaxKey() uint64 { 46 return math.MaxUint64 47 } 48 49 // ClearValue implements segment.Functions.ClearValue. 50 func (FileRangeSetFunctions) ClearValue(_ *uint64) { 51 } 52 53 // Merge implements segment.Functions.Merge. 54 func (FileRangeSetFunctions) Merge(mr1 memmap.MappableRange, frstart1 uint64, _ memmap.MappableRange, frstart2 uint64) (uint64, bool) { 55 if frstart1+mr1.Length() != frstart2 { 56 return 0, false 57 } 58 return frstart1, true 59 } 60 61 // Split implements segment.Functions.Split. 62 func (FileRangeSetFunctions) Split(mr memmap.MappableRange, frstart uint64, split uint64) (uint64, uint64) { 63 return frstart, frstart + (split - mr.Start) 64 } 65 66 // FileRange returns the FileRange mapped by seg. 67 func (seg FileRangeIterator) FileRange() memmap.FileRange { 68 return seg.FileRangeOf(seg.Range()) 69 } 70 71 // FileRangeOf returns the FileRange mapped by mr. 72 // 73 // Preconditions: 74 // - seg.Range().IsSupersetOf(mr). 75 // - mr.Length() != 0. 76 func (seg FileRangeIterator) FileRangeOf(mr memmap.MappableRange) memmap.FileRange { 77 frstart := seg.Value() + (mr.Start - seg.Start()) 78 return memmap.FileRange{frstart, frstart + mr.Length()} 79 } 80 81 // PagesToFill returns the number of pages that that Fill() will allocate 82 // for the given required and optional parameters. 83 func (frs *FileRangeSet) PagesToFill(required, optional memmap.MappableRange) uint64 { 84 var numPages uint64 85 gap := frs.LowerBoundGap(required.Start) 86 for gap.Ok() && gap.Start() < required.End { 87 gr := gap.Range().Intersect(optional) 88 numPages += gr.Length() / hostarch.PageSize 89 gap = gap.NextGap() 90 } 91 return numPages 92 } 93 94 // Fill attempts to ensure that all memmap.Mappable offsets in required are 95 // mapped to a memmap.File offset, by allocating from mf with the given 96 // memory usage kind and invoking readAt to store data into memory. (If readAt 97 // returns a successful partial read, Fill will call it repeatedly until all 98 // bytes have been read.) EOF is handled consistently with the requirements of 99 // mmap(2): bytes after EOF on the same page are zeroed; pages after EOF are 100 // invalid. fileSize is an upper bound on the file's size; bytes after fileSize 101 // will be zeroed without calling readAt. populate has the same meaning as the 102 // pgalloc.MemoryFile.AllocateAndFill() argument of the same name. 103 // 104 // Fill may read offsets outside of required, but will never read offsets 105 // outside of optional. It returns a non-nil error if any error occurs, even 106 // if the error only affects offsets in optional, but not in required. 107 // 108 // Fill returns the number of pages that were allocated. 109 // 110 // Preconditions: 111 // - required.Length() > 0. 112 // - optional.IsSupersetOf(required). 113 // - required and optional must be page-aligned. 114 func (frs *FileRangeSet) Fill(ctx context.Context, required, optional memmap.MappableRange, fileSize uint64, mf *pgalloc.MemoryFile, kind usage.MemoryKind, allocMode pgalloc.AllocationMode, readAt func(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error)) (uint64, error) { 115 gap := frs.LowerBoundGap(required.Start) 116 var pagesAlloced uint64 117 memCgID := pgalloc.MemoryCgroupIDFromContext(ctx) 118 for gap.Ok() && gap.Start() < required.End { 119 if gap.Range().Length() == 0 { 120 gap = gap.NextGap() 121 continue 122 } 123 gr := gap.Range().Intersect(optional) 124 125 // Read data into the gap. 126 opts := pgalloc.AllocOpts{ 127 Kind: kind, 128 Mode: allocMode, 129 MemCgID: memCgID, 130 } 131 if readAt != nil { 132 opts.Reader = safemem.ReaderFunc(func(dsts safemem.BlockSeq) (uint64, error) { 133 var done uint64 134 for !dsts.IsEmpty() { 135 n, err := func() (uint64, error) { 136 off := gr.Start + done 137 if off >= fileSize { 138 return 0, io.EOF 139 } 140 if off+dsts.NumBytes() > fileSize { 141 rd := fileSize - off 142 n, err := readAt(ctx, dsts.TakeFirst64(rd), off) 143 if n == rd && err == nil { 144 return n, io.EOF 145 } 146 return n, err 147 } 148 return readAt(ctx, dsts, off) 149 }() 150 done += n 151 dsts = dsts.DropFirst64(n) 152 if err != nil { 153 if err == io.EOF { 154 // MemoryFile.AllocateAndFill truncates down to a page 155 // boundary, but FileRangeSet.Fill is supposed to 156 // zero-fill to the end of the page in this case. 157 donepgaddr, ok := hostarch.Addr(done).RoundUp() 158 if donepg := uint64(donepgaddr); ok && donepg != done { 159 dsts.DropFirst64(donepg - done) 160 done = donepg 161 if dsts.IsEmpty() { 162 return done, nil 163 } 164 } 165 } 166 return done, err 167 } 168 } 169 return done, nil 170 }) 171 } 172 fr, err := mf.Allocate(gr.Length(), opts) 173 174 // Store anything we managed to read into the cache. 175 if done := fr.Length(); done != 0 { 176 gr.End = gr.Start + done 177 pagesAlloced += gr.Length() / hostarch.PageSize 178 gap = frs.Insert(gap, gr, fr.Start).NextGap() 179 } 180 181 if err != nil { 182 return pagesAlloced, err 183 } 184 } 185 return pagesAlloced, nil 186 } 187 188 // Drop removes segments for memmap.Mappable offsets in mr, freeing the 189 // corresponding memmap.FileRanges. 190 // 191 // Preconditions: mr must be page-aligned. 192 func (frs *FileRangeSet) Drop(mr memmap.MappableRange, mf *pgalloc.MemoryFile) { 193 seg := frs.LowerBoundSegment(mr.Start) 194 for seg.Ok() && seg.Start() < mr.End { 195 seg = frs.Isolate(seg, mr) 196 mf.DecRef(seg.FileRange()) 197 seg = frs.Remove(seg).NextSegment() 198 } 199 } 200 201 // DropAll removes all segments in mr, freeing the corresponding 202 // memmap.FileRanges. It returns the number of pages freed. 203 func (frs *FileRangeSet) DropAll(mf *pgalloc.MemoryFile) uint64 { 204 var pagesFreed uint64 205 for seg := frs.FirstSegment(); seg.Ok(); seg = seg.NextSegment() { 206 mf.DecRef(seg.FileRange()) 207 pagesFreed += seg.Range().Length() / hostarch.PageSize 208 } 209 frs.RemoveAll() 210 return pagesFreed 211 } 212 213 // Truncate updates frs to reflect Mappable truncation to the given length: 214 // bytes after the new EOF on the same page are zeroed, and pages after the new 215 // EOF are freed. It returns the number of pages freed. 216 func (frs *FileRangeSet) Truncate(end uint64, mf *pgalloc.MemoryFile) uint64 { 217 var pagesFreed uint64 218 pgendaddr, ok := hostarch.Addr(end).RoundUp() 219 if ok { 220 pgend := uint64(pgendaddr) 221 222 // Free truncated pages. 223 frs.SplitAt(pgend) 224 seg := frs.LowerBoundSegment(pgend) 225 for seg.Ok() { 226 mf.DecRef(seg.FileRange()) 227 pagesFreed += seg.Range().Length() / hostarch.PageSize 228 seg = frs.Remove(seg).NextSegment() 229 } 230 231 if end == pgend { 232 return pagesFreed 233 } 234 } 235 236 // Here we know end < end.RoundUp(). If the new EOF lands in the 237 // middle of a page that we have, zero out its contents beyond the new 238 // length. 239 seg := frs.FindSegment(end) 240 if seg.Ok() { 241 fr := seg.FileRange() 242 fr.Start += end - seg.Start() 243 ims, err := mf.MapInternal(fr, hostarch.Write) 244 if err != nil { 245 // There's no good recourse from here. This means 246 // that we can't keep cached memory consistent with 247 // the new end of file. The caller may have already 248 // updated the file size on their backing file system. 249 // 250 // We don't want to risk blindly continuing onward, 251 // so in the extremely rare cases this does happen, 252 // we abandon ship. 253 panic(fmt.Sprintf("Failed to map %v: %v", fr, err)) 254 } 255 if _, err := safemem.ZeroSeq(ims); err != nil { 256 panic(fmt.Sprintf("Zeroing %v failed: %v", fr, err)) 257 } 258 } 259 return pagesFreed 260 }