github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/fsimpl/gofer/directory.go (about) 1 // Copyright 2019 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 gofer 16 17 import ( 18 "fmt" 19 20 "github.com/nicocha30/gvisor-ligolo/pkg/abi/linux" 21 "github.com/nicocha30/gvisor-ligolo/pkg/atomicbitops" 22 "github.com/nicocha30/gvisor-ligolo/pkg/context" 23 "github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr" 24 "github.com/nicocha30/gvisor-ligolo/pkg/hostarch" 25 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel/auth" 26 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel/pipe" 27 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/socket/unix/transport" 28 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/vfs" 29 "github.com/nicocha30/gvisor-ligolo/pkg/sync" 30 ) 31 32 func (d *dentry) isDir() bool { 33 return d.fileType() == linux.S_IFDIR 34 } 35 36 // cacheNewChildLocked will cache the new child dentry, and will panic if a 37 // non-negative child is already cached. It is the caller's responsibility to 38 // check that the child does not exist before calling this method. 39 // 40 // Preconditions: 41 // - filesystem.renameMu must be locked. 42 // - If the addition to the dentry tree is due to a read-only operation (like 43 // Walk), then d.opMu must be held for reading. Otherwise d.opMu must be 44 // held for writing. 45 // - d.childrenMu must be locked. 46 // - d.isDir(). 47 // - child must be a newly-created dentry that has never had a parent. 48 // - d.children[name] must be unset or nil (a "negative child") 49 // 50 // +checklocksread:d.opMu 51 // +checklocks:d.childrenMu 52 func (d *dentry) cacheNewChildLocked(child *dentry, name string) { 53 d.IncRef() // reference held by child on its parent 54 child.parent = d 55 child.name = name 56 if d.children == nil { 57 d.children = make(map[string]*dentry) 58 } else if c, ok := d.children[name]; ok { 59 if c != nil { 60 panic(fmt.Sprintf("cacheNewChildLocked collision; child with name=%q already cached", name)) 61 } 62 63 // Cached child is negative. OK to cache over, but we must 64 // update the count of negative children. 65 d.negativeChildren-- 66 } 67 d.children[name] = child 68 } 69 70 // Preconditions: 71 // - d.childrenMu must be locked. 72 // - d.isDir(). 73 // - name is not already a negative entry. 74 // 75 // +checklocks:d.childrenMu 76 func (d *dentry) cacheNegativeLookupLocked(name string) { 77 // Don't cache negative lookups if InteropModeShared is in effect (since 78 // this makes remote lookup unavoidable), or if d.isSynthetic() (in which 79 // case the only files in the directory are those for which a dentry exists 80 // in d.children). Instead, just delete any previously-cached dentry. 81 if d.fs.opts.interop == InteropModeShared || d.isSynthetic() { 82 delete(d.children, name) 83 return 84 } 85 if d.children == nil { 86 d.children = make(map[string]*dentry) 87 } 88 d.children[name] = nil 89 d.negativeChildren++ 90 91 if !d.negativeChildrenCache.isInited() { 92 // Initializing cache with all negative children name at the first time 93 // that negativeChildren increase upto max. 94 if d.negativeChildren >= maxCachedNegativeChildren { 95 d.negativeChildrenCache.init(maxCachedNegativeChildren) 96 for childName, child := range d.children { 97 if child == nil { 98 d.negativeChildrenCache.add(childName) 99 } 100 } 101 } 102 } else if victim := d.negativeChildrenCache.add(name); victim != "" { 103 // If victim is a negative entry in d.children, delete it. 104 if child, ok := d.children[victim]; ok && child == nil { 105 delete(d.children, victim) 106 d.negativeChildren-- 107 } 108 } 109 } 110 111 type createSyntheticOpts struct { 112 name string 113 mode linux.FileMode 114 kuid auth.KUID 115 kgid auth.KGID 116 117 // The endpoint for a synthetic socket. endpoint should be nil if the file 118 // being created is not a socket. 119 endpoint transport.BoundEndpoint 120 121 // pipe should be nil if the file being created is not a pipe. 122 pipe *pipe.VFSPipe 123 } 124 125 // newSyntheticDentry creates a synthetic file with the given name. 126 func (fs *filesystem) newSyntheticDentry(opts *createSyntheticOpts) *dentry { 127 now := fs.clock.Now().Nanoseconds() 128 child := &dentry{ 129 refs: atomicbitops.FromInt64(1), // held by parent. 130 fs: fs, 131 ino: fs.nextIno(), 132 mode: atomicbitops.FromUint32(uint32(opts.mode)), 133 uid: atomicbitops.FromUint32(uint32(opts.kuid)), 134 gid: atomicbitops.FromUint32(uint32(opts.kgid)), 135 blockSize: atomicbitops.FromUint32(hostarch.PageSize), // arbitrary 136 atime: atomicbitops.FromInt64(now), 137 mtime: atomicbitops.FromInt64(now), 138 ctime: atomicbitops.FromInt64(now), 139 btime: atomicbitops.FromInt64(now), 140 readFD: atomicbitops.FromInt32(-1), 141 writeFD: atomicbitops.FromInt32(-1), 142 mmapFD: atomicbitops.FromInt32(-1), 143 nlink: atomicbitops.FromUint32(2), 144 } 145 switch opts.mode.FileType() { 146 case linux.S_IFDIR: 147 // Nothing else needs to be done. 148 case linux.S_IFSOCK: 149 child.endpoint = opts.endpoint 150 case linux.S_IFIFO: 151 child.pipe = opts.pipe 152 default: 153 panic(fmt.Sprintf("failed to create synthetic file of unrecognized type: %v", opts.mode.FileType())) 154 } 155 child.init(nil /* impl */) 156 return child 157 } 158 159 // Preconditions: 160 // - d.childrenMu must be locked. 161 // 162 // +checklocks:d.childrenMu 163 func (d *dentry) clearDirentsLocked() { 164 d.dirents = nil 165 d.childrenSet = nil 166 } 167 168 // +stateify savable 169 type directoryFD struct { 170 fileDescription 171 vfs.DirectoryFileDescriptionDefaultImpl 172 173 mu sync.Mutex `state:"nosave"` 174 off int64 175 dirents []vfs.Dirent 176 } 177 178 // Release implements vfs.FileDescriptionImpl.Release. 179 func (fd *directoryFD) Release(context.Context) { 180 } 181 182 // IterDirents implements vfs.FileDescriptionImpl.IterDirents. 183 func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error { 184 fd.mu.Lock() 185 defer fd.mu.Unlock() 186 187 d := fd.dentry() 188 if fd.dirents == nil { 189 ds, err := d.getDirents(ctx) 190 if err != nil { 191 return err 192 } 193 fd.dirents = ds 194 } 195 196 if d.cachedMetadataAuthoritative() { 197 d.touchAtime(fd.vfsfd.Mount()) 198 } 199 200 for fd.off < int64(len(fd.dirents)) { 201 if err := cb.Handle(fd.dirents[fd.off]); err != nil { 202 return err 203 } 204 fd.off++ 205 } 206 return nil 207 } 208 209 // Preconditions: 210 // - d.isDir(). 211 // - There exists at least one directoryFD representing d. 212 func (d *dentry) getDirents(ctx context.Context) ([]vfs.Dirent, error) { 213 // NOTE(b/135560623): 9P2000.L's readdir does not specify behavior in the 214 // presence of concurrent mutation of an iterated directory, so 215 // implementations may duplicate or omit entries in this case, which 216 // violates POSIX semantics. Thus we read all directory entries while 217 // holding d.opMu to exclude directory mutations. (Note that it is 218 // impossible for the client to exclude concurrent mutation from other 219 // remote filesystem users. Since there is no way to detect if the server 220 // has incorrectly omitted directory entries, we simply assume that the 221 // server is well-behaved under InteropModeShared.) This is inconsistent 222 // with Linux (which appears to assume that directory fids have the correct 223 // semantics, and translates struct file_operations::readdir calls directly 224 // to readdir RPCs), but is consistent with VFS1. 225 226 // filesystem.renameMu is needed for d.parent, and must be locked before 227 // d.opMu. 228 d.fs.renameMu.RLock() 229 defer d.fs.renameMu.RUnlock() 230 d.opMu.RLock() 231 defer d.opMu.RUnlock() 232 233 // d.childrenMu must be locked after d.opMu and held for the entire 234 // function. This synchronizes concurrent getDirents() attempts. 235 // getdents(2) advances the file offset. To get complete results from 236 // multiple getdents(2) calls, the directory FD's offset needs to be 237 // protected. 238 d.childrenMu.Lock() 239 defer d.childrenMu.Unlock() 240 241 if d.dirents != nil { 242 return d.dirents, nil 243 } 244 245 // It's not clear if 9P2000.L's readdir is expected to return "." and "..", 246 // so we generate them here. 247 parent := genericParentOrSelf(d) 248 dirents := []vfs.Dirent{ 249 { 250 Name: ".", 251 Type: linux.DT_DIR, 252 Ino: uint64(d.ino), 253 NextOff: 1, 254 }, 255 { 256 Name: "..", 257 Type: uint8(parent.mode.Load() >> 12), 258 Ino: uint64(parent.ino), 259 NextOff: 2, 260 }, 261 } 262 var realChildren map[string]struct{} 263 if !d.isSynthetic() { 264 if d.syntheticChildren != 0 && d.fs.opts.interop == InteropModeShared { 265 // Record the set of children d actually has so that we don't emit 266 // duplicate entries for synthetic children. 267 realChildren = make(map[string]struct{}) 268 } 269 d.handleMu.RLock() 270 if !d.isReadHandleOk() { 271 // This should not be possible because a readable handle should 272 // have been opened when the calling directoryFD was opened. 273 panic("gofer.dentry.getDirents called without a readable handle") 274 } 275 err := d.getDirentsLocked(ctx, func(name string, key inoKey, dType uint8) { 276 dirent := vfs.Dirent{ 277 Name: name, 278 Ino: d.fs.inoFromKey(key), 279 NextOff: int64(len(dirents) + 1), 280 Type: dType, 281 } 282 dirents = append(dirents, dirent) 283 if realChildren != nil { 284 realChildren[name] = struct{}{} 285 } 286 }) 287 d.handleMu.RUnlock() 288 if err != nil { 289 return nil, err 290 } 291 } 292 293 // Emit entries for synthetic children. 294 if d.syntheticChildren != 0 { 295 for _, child := range d.children { 296 if child == nil || !child.isSynthetic() { 297 continue 298 } 299 if _, ok := realChildren[child.name]; ok { 300 continue 301 } 302 dirents = append(dirents, vfs.Dirent{ 303 Name: child.name, 304 Type: uint8(child.mode.Load() >> 12), 305 Ino: uint64(child.ino), 306 NextOff: int64(len(dirents) + 1), 307 }) 308 } 309 } 310 // Cache dirents for future directoryFDs if permitted. 311 if d.cachedMetadataAuthoritative() { 312 d.dirents = dirents 313 d.childrenSet = make(map[string]struct{}, len(dirents)) 314 for _, dirent := range d.dirents { 315 d.childrenSet[dirent.Name] = struct{}{} 316 } 317 } 318 return dirents, nil 319 } 320 321 // Seek implements vfs.FileDescriptionImpl.Seek. 322 func (fd *directoryFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) { 323 fd.mu.Lock() 324 defer fd.mu.Unlock() 325 326 switch whence { 327 case linux.SEEK_SET: 328 if offset < 0 { 329 return 0, linuxerr.EINVAL 330 } 331 if offset == 0 { 332 // Ensure that the next call to fd.IterDirents() calls 333 // fd.dentry().getDirents(). 334 fd.dirents = nil 335 } 336 fd.off = offset 337 return fd.off, nil 338 case linux.SEEK_CUR: 339 offset += fd.off 340 if offset < 0 { 341 return 0, linuxerr.EINVAL 342 } 343 // Don't clear fd.dirents in this case, even if offset == 0. 344 fd.off = offset 345 return fd.off, nil 346 default: 347 return 0, linuxerr.EINVAL 348 } 349 } 350 351 // Sync implements vfs.FileDescriptionImpl.Sync. 352 func (fd *directoryFD) Sync(ctx context.Context) error { 353 return fd.dentry().syncRemoteFile(ctx) 354 }