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 }