github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/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/metacubex/gvisor/pkg/context"
    23  	"github.com/metacubex/gvisor/pkg/hostarch"
    24  	"github.com/metacubex/gvisor/pkg/safemem"
    25  	"github.com/metacubex/gvisor/pkg/sentry/memmap"
    26  	"github.com/metacubex/gvisor/pkg/sentry/pgalloc"
    27  	"github.com/metacubex/gvisor/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 (s *FileRangeSet) PagesToFill(required, optional memmap.MappableRange) uint64 {
    84  	var numPages uint64
    85  	gap := s.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 (s *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 := s.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.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 = s.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 (s *FileRangeSet) Drop(mr memmap.MappableRange, mf *pgalloc.MemoryFile) {
   193  	seg := s.LowerBoundSegment(mr.Start)
   194  	for seg.Ok() && seg.Start() < mr.End {
   195  		seg = s.Isolate(seg, mr)
   196  		mf.DecRef(seg.FileRange())
   197  		seg = s.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 (s *FileRangeSet) DropAll(mf *pgalloc.MemoryFile) uint64 {
   204  	var pagesFreed uint64
   205  	for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
   206  		mf.DecRef(seg.FileRange())
   207  		pagesFreed += seg.Range().Length() / hostarch.PageSize
   208  	}
   209  	s.RemoveAll()
   210  	return pagesFreed
   211  }
   212  
   213  // Truncate updates s 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 (s *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  		seg := s.LowerBoundSegmentSplitBefore(pgend)
   224  		for seg.Ok() {
   225  			mf.DecRef(seg.FileRange())
   226  			pagesFreed += seg.Range().Length() / hostarch.PageSize
   227  			seg = s.Remove(seg).NextSegment()
   228  		}
   229  
   230  		if end == pgend {
   231  			return pagesFreed
   232  		}
   233  	}
   234  
   235  	// Here we know end < end.RoundUp(). If the new EOF lands in the
   236  	// middle of a page that we have, zero out its contents beyond the new
   237  	// length.
   238  	seg := s.FindSegment(end)
   239  	if seg.Ok() {
   240  		fr := seg.FileRange()
   241  		fr.Start += end - seg.Start()
   242  		ims, err := mf.MapInternal(fr, hostarch.Write)
   243  		if err != nil {
   244  			// There's no good recourse from here. This means
   245  			// that we can't keep cached memory consistent with
   246  			// the new end of file. The caller may have already
   247  			// updated the file size on their backing file system.
   248  			//
   249  			// We don't want to risk blindly continuing onward,
   250  			// so in the extremely rare cases this does happen,
   251  			// we abandon ship.
   252  			panic(fmt.Sprintf("Failed to map %v: %v", fr, err))
   253  		}
   254  		if _, err := safemem.ZeroSeq(ims); err != nil {
   255  			panic(fmt.Sprintf("Zeroing %v failed: %v", fr, err))
   256  		}
   257  	}
   258  	return pagesFreed
   259  }