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  }