github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/fsutil/host_file_mapper.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  
    20  	"golang.org/x/sys/unix"
    21  	"github.com/SagerNet/gvisor/pkg/hostarch"
    22  	"github.com/SagerNet/gvisor/pkg/log"
    23  	"github.com/SagerNet/gvisor/pkg/safemem"
    24  	"github.com/SagerNet/gvisor/pkg/sentry/memmap"
    25  	"github.com/SagerNet/gvisor/pkg/sync"
    26  )
    27  
    28  // HostFileMapper caches mappings of an arbitrary host file descriptor. It is
    29  // used by implementations of memmap.Mappable that represent a host file
    30  // descriptor.
    31  //
    32  // +stateify savable
    33  type HostFileMapper struct {
    34  	// HostFile conceptually breaks the file into pieces called chunks, of
    35  	// size and alignment chunkSize, and caches mappings of the file on a chunk
    36  	// granularity.
    37  
    38  	refsMu sync.Mutex `state:"nosave"`
    39  
    40  	// refs maps chunk start offsets to the sum of reference counts for all
    41  	// pages in that chunk. refs is protected by refsMu.
    42  	refs map[uint64]int32
    43  
    44  	mapsMu sync.Mutex `state:"nosave"`
    45  
    46  	// mappings maps chunk start offsets to mappings of those chunks,
    47  	// obtained by calling unix.Mmap. mappings is protected by
    48  	// mapsMu.
    49  	mappings map[uint64]mapping `state:"nosave"`
    50  }
    51  
    52  const (
    53  	chunkShift = hostarch.HugePageShift
    54  	chunkSize  = 1 << chunkShift
    55  	chunkMask  = chunkSize - 1
    56  )
    57  
    58  func pagesInChunk(mr memmap.MappableRange, chunkStart uint64) int32 {
    59  	return int32(mr.Intersect(memmap.MappableRange{chunkStart, chunkStart + chunkSize}).Length() / hostarch.PageSize)
    60  }
    61  
    62  type mapping struct {
    63  	addr     uintptr
    64  	writable bool
    65  }
    66  
    67  // Init must be called on zero-value HostFileMappers before first use.
    68  func (f *HostFileMapper) Init() {
    69  	f.refs = make(map[uint64]int32)
    70  	f.mappings = make(map[uint64]mapping)
    71  }
    72  
    73  // IsInited returns true if f.Init() has been called. This is used when
    74  // restoring a checkpoint that contains a HostFileMapper that may or may not
    75  // have been initialized.
    76  func (f *HostFileMapper) IsInited() bool {
    77  	return f.refs != nil
    78  }
    79  
    80  // NewHostFileMapper returns an initialized HostFileMapper allocated on the
    81  // heap with no references or cached mappings.
    82  func NewHostFileMapper() *HostFileMapper {
    83  	f := &HostFileMapper{}
    84  	f.Init()
    85  	return f
    86  }
    87  
    88  // IncRefOn increments the reference count on all offsets in mr.
    89  //
    90  // Preconditions:
    91  // * mr.Length() != 0.
    92  // * mr.Start and mr.End must be page-aligned.
    93  func (f *HostFileMapper) IncRefOn(mr memmap.MappableRange) {
    94  	f.refsMu.Lock()
    95  	defer f.refsMu.Unlock()
    96  	for chunkStart := mr.Start &^ chunkMask; chunkStart < mr.End; chunkStart += chunkSize {
    97  		refs := f.refs[chunkStart]
    98  		pgs := pagesInChunk(mr, chunkStart)
    99  		if refs+pgs < refs {
   100  			// Would overflow.
   101  			panic(fmt.Sprintf("HostFileMapper.IncRefOn(%v): adding %d page references to chunk %#x, which has %d page references", mr, pgs, chunkStart, refs))
   102  		}
   103  		f.refs[chunkStart] = refs + pgs
   104  	}
   105  }
   106  
   107  // DecRefOn decrements the reference count on all offsets in mr.
   108  //
   109  // Preconditions:
   110  // * mr.Length() != 0.
   111  // * mr.Start and mr.End must be page-aligned.
   112  func (f *HostFileMapper) DecRefOn(mr memmap.MappableRange) {
   113  	f.refsMu.Lock()
   114  	defer f.refsMu.Unlock()
   115  	for chunkStart := mr.Start &^ chunkMask; chunkStart < mr.End; chunkStart += chunkSize {
   116  		refs := f.refs[chunkStart]
   117  		pgs := pagesInChunk(mr, chunkStart)
   118  		switch {
   119  		case refs > pgs:
   120  			f.refs[chunkStart] = refs - pgs
   121  		case refs == pgs:
   122  			f.mapsMu.Lock()
   123  			delete(f.refs, chunkStart)
   124  			if m, ok := f.mappings[chunkStart]; ok {
   125  				f.unmapAndRemoveLocked(chunkStart, m)
   126  			}
   127  			f.mapsMu.Unlock()
   128  		case refs < pgs:
   129  			panic(fmt.Sprintf("HostFileMapper.DecRefOn(%v): removing %d page references from chunk %#x, which has %d page references", mr, pgs, chunkStart, refs))
   130  		}
   131  	}
   132  }
   133  
   134  // MapInternal returns a mapping of offsets in fr from fd. The returned
   135  // safemem.BlockSeq is valid as long as at least one reference is held on all
   136  // offsets in fr or until the next call to UnmapAll.
   137  //
   138  // Preconditions: The caller must hold a reference on all offsets in fr.
   139  func (f *HostFileMapper) MapInternal(fr memmap.FileRange, fd int, write bool) (safemem.BlockSeq, error) {
   140  	chunks := ((fr.End + chunkMask) >> chunkShift) - (fr.Start >> chunkShift)
   141  	f.mapsMu.Lock()
   142  	defer f.mapsMu.Unlock()
   143  	if chunks == 1 {
   144  		// Avoid an unnecessary slice allocation.
   145  		var seq safemem.BlockSeq
   146  		err := f.forEachMappingBlockLocked(fr, fd, write, func(b safemem.Block) {
   147  			seq = safemem.BlockSeqOf(b)
   148  		})
   149  		return seq, err
   150  	}
   151  	blocks := make([]safemem.Block, 0, chunks)
   152  	err := f.forEachMappingBlockLocked(fr, fd, write, func(b safemem.Block) {
   153  		blocks = append(blocks, b)
   154  	})
   155  	return safemem.BlockSeqFromSlice(blocks), err
   156  }
   157  
   158  // Preconditions: f.mapsMu must be locked.
   159  func (f *HostFileMapper) forEachMappingBlockLocked(fr memmap.FileRange, fd int, write bool, fn func(safemem.Block)) error {
   160  	prot := unix.PROT_READ
   161  	if write {
   162  		prot |= unix.PROT_WRITE
   163  	}
   164  	for chunkStart := fr.Start &^ chunkMask; chunkStart < fr.End; chunkStart += chunkSize {
   165  		m, ok := f.mappings[chunkStart]
   166  		if !ok {
   167  			addr, _, errno := unix.Syscall6(
   168  				unix.SYS_MMAP,
   169  				0,
   170  				chunkSize,
   171  				uintptr(prot),
   172  				unix.MAP_SHARED,
   173  				uintptr(fd),
   174  				uintptr(chunkStart))
   175  			if errno != 0 {
   176  				return errno
   177  			}
   178  			m = mapping{addr, write}
   179  			f.mappings[chunkStart] = m
   180  		} else if write && !m.writable {
   181  			addr, _, errno := unix.Syscall6(
   182  				unix.SYS_MMAP,
   183  				m.addr,
   184  				chunkSize,
   185  				uintptr(prot),
   186  				unix.MAP_SHARED|unix.MAP_FIXED,
   187  				uintptr(fd),
   188  				uintptr(chunkStart))
   189  			if errno != 0 {
   190  				return errno
   191  			}
   192  			m = mapping{addr, write}
   193  			f.mappings[chunkStart] = m
   194  		}
   195  		var startOff uint64
   196  		if chunkStart < fr.Start {
   197  			startOff = fr.Start - chunkStart
   198  		}
   199  		endOff := uint64(chunkSize)
   200  		if chunkStart+chunkSize > fr.End {
   201  			endOff = fr.End - chunkStart
   202  		}
   203  		fn(f.unsafeBlockFromChunkMapping(m.addr).TakeFirst64(endOff).DropFirst64(startOff))
   204  	}
   205  	return nil
   206  }
   207  
   208  // UnmapAll unmaps all cached mappings. Callers are responsible for
   209  // synchronization with mappings returned by previous calls to MapInternal.
   210  func (f *HostFileMapper) UnmapAll() {
   211  	f.mapsMu.Lock()
   212  	defer f.mapsMu.Unlock()
   213  	for chunkStart, m := range f.mappings {
   214  		f.unmapAndRemoveLocked(chunkStart, m)
   215  	}
   216  }
   217  
   218  // Preconditions:
   219  // * f.mapsMu must be locked.
   220  // * f.mappings[chunkStart] == m.
   221  func (f *HostFileMapper) unmapAndRemoveLocked(chunkStart uint64, m mapping) {
   222  	if _, _, errno := unix.Syscall(unix.SYS_MUNMAP, m.addr, chunkSize, 0); errno != 0 {
   223  		// This leaks address space and is unexpected, but is otherwise
   224  		// harmless, so complain but don't panic.
   225  		log.Warningf("HostFileMapper: failed to unmap mapping %#x for chunk %#x: %v", m.addr, chunkStart, errno)
   226  	}
   227  	delete(f.mappings, chunkStart)
   228  }
   229  
   230  // RegenerateMappings must be called when the file description mapped by f
   231  // changes, to replace existing mappings of the previous file description.
   232  func (f *HostFileMapper) RegenerateMappings(fd int) error {
   233  	f.mapsMu.Lock()
   234  	defer f.mapsMu.Unlock()
   235  
   236  	for chunkStart, m := range f.mappings {
   237  		prot := unix.PROT_READ
   238  		if m.writable {
   239  			prot |= unix.PROT_WRITE
   240  		}
   241  		_, _, errno := unix.Syscall6(
   242  			unix.SYS_MMAP,
   243  			m.addr,
   244  			chunkSize,
   245  			uintptr(prot),
   246  			unix.MAP_SHARED|unix.MAP_FIXED,
   247  			uintptr(fd),
   248  			uintptr(chunkStart))
   249  		if errno != 0 {
   250  			return errno
   251  		}
   252  	}
   253  	return nil
   254  }