github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/fsimpl/gofer/dentry_impl.go (about) 1 // Copyright 2022 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 "golang.org/x/sys/unix" 19 "github.com/nicocha30/gvisor-ligolo/pkg/abi/linux" 20 "github.com/nicocha30/gvisor-ligolo/pkg/context" 21 "github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr" 22 "github.com/nicocha30/gvisor-ligolo/pkg/fsutil" 23 "github.com/nicocha30/gvisor-ligolo/pkg/log" 24 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel/auth" 25 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/vfs" 26 ) 27 28 // We do *not* define an interface for dentry.impl because making interface 29 // method calls is almost 2.5x slower than calling the same method on a 30 // concrete type. Instead, we use type assertions in switch statements. The 31 // asserted type is a concrete dentry implementation and methods are called 32 // directly on the concrete type. This helps in the following ways: 33 // 34 // 1. This is faster because concrete type assertion just needs to compare the 35 // itab pointer in the interface value to a constant which is relatively 36 // cheap. Benchmarking showed that such type switches don't add almost any 37 // overhead. 38 // 2. Passing any pointer to an interface method immediately causes the pointed 39 // object to escape to heap. Making concrete method calls allows escape 40 // analysis to proceed as usual and avoids heap allocations. 41 // 42 // Also note that the default case in these type switch statements panics. We 43 // do not do panic(fmt.Sprintf("... %T", d.impl)) because somehow it adds a lot 44 // of overhead to the type switch. So instead we panic with a constant string. 45 46 // Precondition: d.handleMu must be locked. 47 func (d *dentry) isReadHandleOk() bool { 48 switch dt := d.impl.(type) { 49 case *lisafsDentry: 50 return dt.readFDLisa.Ok() 51 case *directfsDentry: 52 return d.readFD.RacyLoad() >= 0 53 case nil: // synthetic dentry 54 return false 55 default: 56 panic("unknown dentry implementation") 57 } 58 } 59 60 // Precondition: d.handleMu must be locked. 61 func (d *dentry) isWriteHandleOk() bool { 62 switch dt := d.impl.(type) { 63 case *lisafsDentry: 64 return dt.writeFDLisa.Ok() 65 case *directfsDentry: 66 return d.writeFD.RacyLoad() >= 0 67 case nil: // synthetic dentry 68 return false 69 default: 70 panic("unknown dentry implementation") 71 } 72 } 73 74 // Precondition: d.handleMu must be locked. 75 func (d *dentry) readHandle() handle { 76 switch dt := d.impl.(type) { 77 case *lisafsDentry: 78 return handle{ 79 fdLisa: dt.readFDLisa, 80 fd: d.readFD.RacyLoad(), 81 } 82 case *directfsDentry: 83 return handle{fd: d.readFD.RacyLoad()} 84 case nil: // synthetic dentry 85 return noHandle 86 default: 87 panic("unknown dentry implementation") 88 } 89 } 90 91 // Precondition: d.handleMu must be locked. 92 func (d *dentry) writeHandle() handle { 93 switch dt := d.impl.(type) { 94 case *lisafsDentry: 95 return handle{ 96 fdLisa: dt.writeFDLisa, 97 fd: d.writeFD.RacyLoad(), 98 } 99 case *directfsDentry: 100 return handle{fd: d.writeFD.RacyLoad()} 101 case nil: // synthetic dentry 102 return noHandle 103 default: 104 panic("unknown dentry implementation") 105 } 106 } 107 108 // Preconditions: 109 // - !d.isSynthetic(). 110 // - fs.renameMu is locked. 111 func (d *dentry) openHandle(ctx context.Context, read, write, trunc bool) (handle, error) { 112 flags := uint32(unix.O_RDONLY) 113 switch { 114 case read && write: 115 flags = unix.O_RDWR 116 case read: 117 flags = unix.O_RDONLY 118 case write: 119 flags = unix.O_WRONLY 120 default: 121 log.Debugf("openHandle called with read = write = false. Falling back to read only FD.") 122 } 123 if trunc { 124 flags |= unix.O_TRUNC 125 } 126 switch dt := d.impl.(type) { 127 case *lisafsDentry: 128 return dt.openHandle(ctx, flags) 129 case *directfsDentry: 130 return dt.openHandle(ctx, flags) 131 default: 132 panic("unknown dentry implementation") 133 } 134 } 135 136 // Preconditions: 137 // - d.handleMu must be locked. 138 // - !d.isSynthetic(). 139 func (d *dentry) updateHandles(ctx context.Context, h handle, readable, writable bool) { 140 switch dt := d.impl.(type) { 141 case *lisafsDentry: 142 dt.updateHandles(ctx, h, readable, writable) 143 case *directfsDentry: 144 // No update needed. 145 default: 146 panic("unknown dentry implementation") 147 } 148 } 149 150 // updateMetadataLocked updates the dentry's metadata fields. The h parameter 151 // is optional. If it is not provided, an appropriate FD should be chosen to 152 // stat the remote file. 153 // 154 // Preconditions: 155 // - !d.isSynthetic(). 156 // - d.metadataMu is locked. 157 // 158 // +checklocks:d.metadataMu 159 func (d *dentry) updateMetadataLocked(ctx context.Context, h handle) error { 160 // Need checklocksforce below because checklocks has no way of knowing that 161 // d.impl.(*dentryImpl).dentry == d. It can't know that the right metadataMu 162 // is already locked. 163 switch dt := d.impl.(type) { 164 case *lisafsDentry: 165 return dt.updateMetadataLocked(ctx, h) // +checklocksforce: acquired by precondition. 166 case *directfsDentry: 167 return dt.updateMetadataLocked(h) // +checklocksforce: acquired by precondition. 168 default: 169 panic("unknown dentry implementation") 170 } 171 } 172 173 // Preconditions: 174 // - !d.isSynthetic(). 175 // - fs.renameMu is locked. 176 func (d *dentry) prepareSetStat(ctx context.Context, stat *linux.Statx) error { 177 switch dt := d.impl.(type) { 178 case *lisafsDentry: 179 // Nothing to be done. 180 return nil 181 case *directfsDentry: 182 return dt.prepareSetStat(ctx, stat) 183 default: 184 panic("unknown dentry implementation") 185 } 186 } 187 188 // Precondition: fs.renameMu is locked if d is a socket. 189 func (d *dentry) chmod(ctx context.Context, mode uint16) error { 190 switch dt := d.impl.(type) { 191 case *lisafsDentry: 192 return chmod(ctx, dt.controlFD, mode) 193 case *directfsDentry: 194 return dt.chmod(ctx, mode) 195 default: 196 panic("unknown dentry implementation") 197 } 198 } 199 200 // Preconditions: 201 // - !d.isSynthetic(). 202 // - d.handleMu is locked. 203 // - fs.renameMu is locked. 204 func (d *dentry) setStatLocked(ctx context.Context, stat *linux.Statx) (uint32, error, error) { 205 switch dt := d.impl.(type) { 206 case *lisafsDentry: 207 return dt.controlFD.SetStat(ctx, stat) 208 case *directfsDentry: 209 failureMask, failureErr := dt.setStatLocked(ctx, stat) 210 return failureMask, failureErr, nil 211 default: 212 panic("unknown dentry implementation") 213 } 214 } 215 216 func (d *dentry) destroyImpl(ctx context.Context) { 217 switch dt := d.impl.(type) { 218 case *lisafsDentry: 219 dt.destroy(ctx) 220 case *directfsDentry: 221 dt.destroy(ctx) 222 case nil: // synthetic dentry 223 default: 224 panic("unknown dentry implementation") 225 } 226 } 227 228 // Postcondition: Caller must do dentry caching appropriately. 229 // 230 // +checklocksread:d.opMu 231 func (d *dentry) getRemoteChild(ctx context.Context, name string) (*dentry, error) { 232 switch dt := d.impl.(type) { 233 case *lisafsDentry: 234 return dt.getRemoteChild(ctx, name) 235 case *directfsDentry: 236 return dt.getHostChild(name) 237 default: 238 panic("unknown dentry implementation") 239 } 240 } 241 242 // Preconditions: 243 // - fs.renameMu must be locked. 244 // - parent.opMu must be locked for reading. 245 // - parent.isDir(). 246 // - !rp.Done() && rp.Component() is not "." or "..". 247 // 248 // Postcondition: The returned dentry is already cached appropriately. 249 // 250 // +checklocksread:d.opMu 251 func (d *dentry) getRemoteChildAndWalkPathLocked(ctx context.Context, rp resolvingPath, ds **[]*dentry) (*dentry, error) { 252 switch dt := d.impl.(type) { 253 case *lisafsDentry: 254 return dt.getRemoteChildAndWalkPathLocked(ctx, rp, ds) 255 case *directfsDentry: 256 // We need to check for races because opMu is read locked which allows 257 // concurrent walks to occur. 258 return d.fs.getRemoteChildLocked(ctx, d, rp.Component(), true /* checkForRace */, ds) 259 default: 260 panic("unknown dentry implementation") 261 } 262 } 263 264 // Precondition: !d.isSynthetic(). 265 func (d *dentry) listXattrImpl(ctx context.Context, size uint64) ([]string, error) { 266 switch dt := d.impl.(type) { 267 case *lisafsDentry: 268 return dt.controlFD.ListXattr(ctx, size) 269 case *directfsDentry: 270 // Consistent with runsc/fsgofer. 271 return nil, linuxerr.EOPNOTSUPP 272 default: 273 panic("unknown dentry implementation") 274 } 275 } 276 277 // Precondition: !d.isSynthetic(). 278 func (d *dentry) getXattrImpl(ctx context.Context, opts *vfs.GetXattrOptions) (string, error) { 279 switch dt := d.impl.(type) { 280 case *lisafsDentry: 281 return dt.controlFD.GetXattr(ctx, opts.Name, opts.Size) 282 case *directfsDentry: 283 // Consistent with runsc/fsgofer. 284 return "", linuxerr.EOPNOTSUPP 285 default: 286 panic("unknown dentry implementation") 287 } 288 } 289 290 // Precondition: !d.isSynthetic(). 291 func (d *dentry) setXattrImpl(ctx context.Context, opts *vfs.SetXattrOptions) error { 292 switch dt := d.impl.(type) { 293 case *lisafsDentry: 294 return dt.controlFD.SetXattr(ctx, opts.Name, opts.Value, opts.Flags) 295 case *directfsDentry: 296 // Consistent with runsc/fsgofer. 297 return linuxerr.EOPNOTSUPP 298 default: 299 panic("unknown dentry implementation") 300 } 301 } 302 303 // Precondition: !d.isSynthetic(). 304 func (d *dentry) removeXattrImpl(ctx context.Context, name string) error { 305 switch dt := d.impl.(type) { 306 case *lisafsDentry: 307 return dt.controlFD.RemoveXattr(ctx, name) 308 case *directfsDentry: 309 // Consistent with runsc/fsgofer. 310 return linuxerr.EOPNOTSUPP 311 default: 312 panic("unknown dentry implementation") 313 } 314 } 315 316 // Precondition: !d.isSynthetic(). 317 func (d *dentry) mknod(ctx context.Context, name string, creds *auth.Credentials, opts *vfs.MknodOptions) (*dentry, error) { 318 switch dt := d.impl.(type) { 319 case *lisafsDentry: 320 return dt.mknod(ctx, name, creds, opts) 321 case *directfsDentry: 322 return dt.mknod(ctx, name, creds, opts) 323 default: 324 panic("unknown dentry implementation") 325 } 326 } 327 328 // Preconditions: 329 // - !d.isSynthetic(). 330 // - d.fs.renameMu must be locked. 331 func (d *dentry) link(ctx context.Context, target *dentry, name string) (*dentry, error) { 332 switch dt := d.impl.(type) { 333 case *lisafsDentry: 334 return dt.link(ctx, target.impl.(*lisafsDentry), name) 335 case *directfsDentry: 336 return dt.link(target.impl.(*directfsDentry), name) 337 default: 338 panic("unknown dentry implementation") 339 } 340 } 341 342 // Precondition: !d.isSynthetic(). 343 func (d *dentry) mkdir(ctx context.Context, name string, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, error) { 344 switch dt := d.impl.(type) { 345 case *lisafsDentry: 346 return dt.mkdir(ctx, name, mode, uid, gid) 347 case *directfsDentry: 348 return dt.mkdir(name, mode, uid, gid) 349 default: 350 panic("unknown dentry implementation") 351 } 352 } 353 354 // Precondition: !d.isSynthetic(). 355 func (d *dentry) symlink(ctx context.Context, name, target string, creds *auth.Credentials) (*dentry, error) { 356 switch dt := d.impl.(type) { 357 case *lisafsDentry: 358 return dt.symlink(ctx, name, target, creds) 359 case *directfsDentry: 360 return dt.symlink(name, target, creds) 361 default: 362 panic("unknown dentry implementation") 363 } 364 } 365 366 // Precondition: !d.isSynthetic(). 367 func (d *dentry) openCreate(ctx context.Context, name string, accessFlags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, handle, error) { 368 switch dt := d.impl.(type) { 369 case *lisafsDentry: 370 return dt.openCreate(ctx, name, accessFlags, mode, uid, gid) 371 case *directfsDentry: 372 return dt.openCreate(name, accessFlags, mode, uid, gid) 373 default: 374 panic("unknown dentry implementation") 375 } 376 } 377 378 // Preconditions: 379 // - d.isDir(). 380 // - d.handleMu must be locked. 381 // - !d.isSynthetic(). 382 func (d *dentry) getDirentsLocked(ctx context.Context, recordDirent func(name string, key inoKey, dType uint8)) error { 383 switch dt := d.impl.(type) { 384 case *lisafsDentry: 385 return dt.getDirentsLocked(ctx, recordDirent) 386 case *directfsDentry: 387 return dt.getDirentsLocked(recordDirent) 388 default: 389 panic("unknown dentry implementation") 390 } 391 } 392 393 // Precondition: !d.isSynthetic(). 394 func (d *dentry) flush(ctx context.Context) error { 395 d.handleMu.RLock() 396 defer d.handleMu.RUnlock() 397 switch dt := d.impl.(type) { 398 case *lisafsDentry: 399 return flush(ctx, dt.writeFDLisa) 400 case *directfsDentry: 401 // Nothing to do here. 402 return nil 403 default: 404 panic("unknown dentry implementation") 405 } 406 } 407 408 // Precondition: !d.isSynthetic(). 409 func (d *dentry) allocate(ctx context.Context, mode, offset, length uint64) error { 410 d.handleMu.RLock() 411 defer d.handleMu.RUnlock() 412 switch dt := d.impl.(type) { 413 case *lisafsDentry: 414 return dt.writeFDLisa.Allocate(ctx, mode, offset, length) 415 case *directfsDentry: 416 return unix.Fallocate(int(d.writeFD.RacyLoad()), uint32(mode), int64(offset), int64(length)) 417 default: 418 panic("unknown dentry implementation") 419 } 420 } 421 422 // Preconditions: 423 // - !d.isSynthetic(). 424 // - fs.renameMu is locked. 425 func (d *dentry) connect(ctx context.Context, sockType linux.SockType) (int, error) { 426 switch dt := d.impl.(type) { 427 case *lisafsDentry: 428 return dt.controlFD.Connect(ctx, sockType) 429 case *directfsDentry: 430 return dt.connect(ctx, sockType) 431 default: 432 panic("unknown dentry implementation") 433 } 434 } 435 436 // Precondition: !d.isSynthetic(). 437 func (d *dentry) readlinkImpl(ctx context.Context) (string, error) { 438 switch dt := d.impl.(type) { 439 case *lisafsDentry: 440 return dt.controlFD.ReadLinkAt(ctx) 441 case *directfsDentry: 442 return dt.readlink() 443 default: 444 panic("unknown dentry implementation") 445 } 446 } 447 448 // Precondition: !d.isSynthetic(). 449 func (d *dentry) unlink(ctx context.Context, name string, flags uint32) error { 450 switch dt := d.impl.(type) { 451 case *lisafsDentry: 452 return dt.controlFD.UnlinkAt(ctx, name, flags) 453 case *directfsDentry: 454 return unix.Unlinkat(dt.controlFD, name, int(flags)) 455 default: 456 panic("unknown dentry implementation") 457 } 458 } 459 460 // Precondition: !d.isSynthetic(). 461 func (d *dentry) rename(ctx context.Context, oldName string, newParent *dentry, newName string) error { 462 switch dt := d.impl.(type) { 463 case *lisafsDentry: 464 return dt.controlFD.RenameAt(ctx, oldName, newParent.impl.(*lisafsDentry).controlFD.ID(), newName) 465 case *directfsDentry: 466 return fsutil.RenameAt(dt.controlFD, oldName, newParent.impl.(*directfsDentry).controlFD, newName) 467 default: 468 panic("unknown dentry implementation") 469 } 470 } 471 472 // Precondition: !d.isSynthetic(). 473 func (d *dentry) statfs(ctx context.Context) (linux.Statfs, error) { 474 switch dt := d.impl.(type) { 475 case *lisafsDentry: 476 return dt.statfs(ctx) 477 case *directfsDentry: 478 return dt.statfs() 479 default: 480 panic("unknown dentry implementation") 481 } 482 } 483 484 func (fs *filesystem) restoreRoot(ctx context.Context, opts *vfs.CompleteRestoreOptions) error { 485 rootInode, rootHostFD, err := fs.initClientAndGetRoot(ctx) 486 if err != nil { 487 return err 488 } 489 490 // The root is always non-synthetic. 491 switch dt := fs.root.impl.(type) { 492 case *lisafsDentry: 493 return dt.restoreFile(ctx, &rootInode, opts) 494 case *directfsDentry: 495 dt.controlFDLisa = fs.client.NewFD(rootInode.ControlFD) 496 return dt.restoreFile(ctx, rootHostFD, opts) 497 default: 498 panic("unknown dentry implementation") 499 } 500 } 501 502 // Preconditions: 503 // - !d.isSynthetic(). 504 // - d.parent != nil and has been restored. 505 func (d *dentry) restoreFile(ctx context.Context, opts *vfs.CompleteRestoreOptions) error { 506 switch dt := d.impl.(type) { 507 case *lisafsDentry: 508 inode, err := d.parent.impl.(*lisafsDentry).controlFD.Walk(ctx, d.name) 509 if err != nil { 510 return err 511 } 512 return dt.restoreFile(ctx, &inode, opts) 513 case *directfsDentry: 514 childFD, err := tryOpen(func(flags int) (int, error) { 515 return unix.Openat(d.parent.impl.(*directfsDentry).controlFD, d.name, flags, 0) 516 }) 517 if err != nil { 518 return err 519 } 520 return dt.restoreFile(ctx, childFD, opts) 521 default: 522 panic("unknown dentry implementation") 523 } 524 } 525 526 // doRevalidation calls into r.start's dentry implementation to perform 527 // revalidation on all the dentries contained in r. 528 // 529 // Preconditions: 530 // - fs.renameMu must be locked. 531 // - InteropModeShared is in effect. 532 func (r *revalidateState) doRevalidation(ctx context.Context, vfsObj *vfs.VirtualFilesystem, ds **[]*dentry) error { 533 // Skip synthetic dentries because there is no actual implementation that can 534 // be used to walk the remote filesystem. A start dentry cannot be replaced. 535 if r.start.isSynthetic() { 536 return nil 537 } 538 switch r.start.impl.(type) { 539 case *lisafsDentry: 540 return doRevalidationLisafs(ctx, vfsObj, r, ds) 541 case *directfsDentry: 542 return doRevalidationDirectfs(ctx, vfsObj, r, ds) 543 default: 544 panic("unknown dentry implementation") 545 } 546 }