github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/vfs/filesystem.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 vfs
    16  
    17  import (
    18  	"github.com/ttpreport/gvisor-ligolo/pkg/abi/linux"
    19  	"github.com/ttpreport/gvisor-ligolo/pkg/context"
    20  	"github.com/ttpreport/gvisor-ligolo/pkg/fspath"
    21  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel/auth"
    22  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/socket/unix/transport"
    23  )
    24  
    25  // A Filesystem is a tree of nodes represented by Dentries, which forms part of
    26  // a VirtualFilesystem.
    27  //
    28  // Filesystems are reference-counted. Unless otherwise specified, all
    29  // Filesystem methods require that a reference is held.
    30  //
    31  // Filesystem is analogous to Linux's struct super_block.
    32  //
    33  // +stateify savable
    34  type Filesystem struct {
    35  	FilesystemRefs
    36  
    37  	// vfs is the VirtualFilesystem that uses this Filesystem. vfs is
    38  	// immutable.
    39  	vfs *VirtualFilesystem
    40  
    41  	// fsType is the FilesystemType of this Filesystem.
    42  	fsType FilesystemType
    43  
    44  	// impl is the FilesystemImpl associated with this Filesystem. impl is
    45  	// immutable. This should be the last field in Dentry.
    46  	impl FilesystemImpl
    47  }
    48  
    49  // Init must be called before first use of fs.
    50  func (fs *Filesystem) Init(vfsObj *VirtualFilesystem, fsType FilesystemType, impl FilesystemImpl) {
    51  	fs.InitRefs()
    52  	fs.vfs = vfsObj
    53  	fs.fsType = fsType
    54  	fs.impl = impl
    55  	vfsObj.filesystemsMu.Lock()
    56  	vfsObj.filesystems[fs] = struct{}{}
    57  	vfsObj.filesystemsMu.Unlock()
    58  }
    59  
    60  // FilesystemType returns the FilesystemType for this Filesystem.
    61  func (fs *Filesystem) FilesystemType() FilesystemType {
    62  	return fs.fsType
    63  }
    64  
    65  // VirtualFilesystem returns the containing VirtualFilesystem.
    66  func (fs *Filesystem) VirtualFilesystem() *VirtualFilesystem {
    67  	return fs.vfs
    68  }
    69  
    70  // Impl returns the FilesystemImpl associated with fs.
    71  func (fs *Filesystem) Impl() FilesystemImpl {
    72  	return fs.impl
    73  }
    74  
    75  // DecRef decrements fs' reference count.
    76  func (fs *Filesystem) DecRef(ctx context.Context) {
    77  	fs.FilesystemRefs.DecRef(func() {
    78  		fs.vfs.filesystemsMu.Lock()
    79  		delete(fs.vfs.filesystems, fs)
    80  		fs.vfs.filesystemsMu.Unlock()
    81  		fs.impl.Release(ctx)
    82  	})
    83  }
    84  
    85  // FilesystemImpl contains implementation details for a Filesystem.
    86  // Implementations of FilesystemImpl should contain their associated Filesystem
    87  // by value as their first field.
    88  //
    89  // All methods that take a ResolvingPath must resolve the path before
    90  // performing any other checks, including rejection of the operation if not
    91  // supported by the FilesystemImpl. This is because the final FilesystemImpl
    92  // (responsible for actually implementing the operation) isn't known until path
    93  // resolution is complete.
    94  //
    95  // Unless otherwise specified, FilesystemImpl methods are responsible for
    96  // performing permission checks. In many cases, vfs package functions in
    97  // permissions.go may be used to help perform these checks.
    98  //
    99  // When multiple specified error conditions apply to a given method call, the
   100  // implementation may return any applicable errno unless otherwise specified,
   101  // but returning the earliest error specified is preferable to maximize
   102  // compatibility with Linux.
   103  //
   104  // All methods may return errors not specified, notably including:
   105  //
   106  //   - ENOENT if a required path component does not exist.
   107  //
   108  //   - ENOTDIR if an intermediate path component is not a directory.
   109  //
   110  //   - Errors from vfs-package functions (ResolvingPath.Resolve*(),
   111  //     Mount.CheckBeginWrite(), permission-checking functions, etc.)
   112  //
   113  // For all methods that take or return linux.Statx, Statx.Uid and Statx.Gid
   114  // should be interpreted as IDs in the root UserNamespace (i.e. as auth.KUID
   115  // and auth.KGID respectively).
   116  //
   117  // FilesystemImpl combines elements of Linux's struct super_operations and
   118  // struct inode_operations, for reasons described in the documentation for
   119  // Dentry.
   120  type FilesystemImpl interface {
   121  	// Release is called when the associated Filesystem reaches zero
   122  	// references.
   123  	Release(ctx context.Context)
   124  
   125  	// Sync "causes all pending modifications to filesystem metadata and cached
   126  	// file data to be written to the underlying [filesystem]", as by syncfs(2).
   127  	Sync(ctx context.Context) error
   128  
   129  	// AccessAt checks whether a user with creds can access the file at rp.
   130  	AccessAt(ctx context.Context, rp *ResolvingPath, creds *auth.Credentials, ats AccessTypes) error
   131  
   132  	// GetDentryAt returns a Dentry representing the file at rp. A reference is
   133  	// taken on the returned Dentry.
   134  	//
   135  	// GetDentryAt does not correspond directly to a Linux syscall; it is used
   136  	// in the implementation of:
   137  	//
   138  	//	- Syscalls that need to resolve two paths: link(), linkat().
   139  	//
   140  	//	- Syscalls that need to refer to a filesystem position outside the
   141  	//		context of a file description: chdir(), fchdir(), chroot(), mount(),
   142  	//		umount().
   143  	GetDentryAt(ctx context.Context, rp *ResolvingPath, opts GetDentryOptions) (*Dentry, error)
   144  
   145  	// GetParentDentryAt returns a Dentry representing the directory at the
   146  	// second-to-last path component in rp. (Note that, despite the name, this
   147  	// is not necessarily the parent directory of the file at rp, since the
   148  	// last path component in rp may be "." or "..".) A reference is taken on
   149  	// the returned Dentry.
   150  	//
   151  	// GetParentDentryAt does not correspond directly to a Linux syscall; it is
   152  	// used in the implementation of the rename() family of syscalls, which
   153  	// must resolve the parent directories of two paths.
   154  	//
   155  	// Preconditions: !rp.Done().
   156  	//
   157  	// Postconditions: If GetParentDentryAt returns a nil error, then
   158  	// rp.Final(). If GetParentDentryAt returns an error returned by
   159  	// ResolvingPath.Resolve*(), then !rp.Done().
   160  	GetParentDentryAt(ctx context.Context, rp *ResolvingPath) (*Dentry, error)
   161  
   162  	// LinkAt creates a hard link at rp representing the same file as vd. It
   163  	// does not take ownership of references on vd.
   164  	//
   165  	// Errors:
   166  	//
   167  	//	- If the last path component in rp is "." or "..", LinkAt returns
   168  	//		EEXIST.
   169  	//
   170  	//	- If a file already exists at rp, LinkAt returns EEXIST.
   171  	//
   172  	//	- If rp.MustBeDir(), LinkAt returns ENOENT.
   173  	//
   174  	//	- If the directory in which the link would be created has been removed
   175  	//		by RmdirAt or RenameAt, LinkAt returns ENOENT.
   176  	//
   177  	//	- If rp.Mount != vd.Mount(), LinkAt returns EXDEV.
   178  	//
   179  	//	- If vd represents a directory, LinkAt returns EPERM.
   180  	//
   181  	//	- If vd represents a file for which all existing links have been
   182  	//		removed, or a file created by open(O_TMPFILE|O_EXCL), LinkAt returns
   183  	//		ENOENT. Equivalently, if vd represents a file with a link count of 0 not
   184  	//		created by open(O_TMPFILE) without O_EXCL, LinkAt returns ENOENT.
   185  	//
   186  	// Preconditions:
   187  	//	* !rp.Done().
   188  	//	* For the final path component in rp, !rp.ShouldFollowSymlink().
   189  	//
   190  	// Postconditions: If LinkAt returns an error returned by
   191  	// ResolvingPath.Resolve*(), then !rp.Done().
   192  	LinkAt(ctx context.Context, rp *ResolvingPath, vd VirtualDentry) error
   193  
   194  	// MkdirAt creates a directory at rp.
   195  	//
   196  	// Errors:
   197  	//
   198  	//	- If the last path component in rp is "." or "..", MkdirAt returns
   199  	//		EEXIST.
   200  	//
   201  	//	- If a file already exists at rp, MkdirAt returns EEXIST.
   202  	//
   203  	//	- If the directory in which the new directory would be created has been
   204  	//		removed by RmdirAt or RenameAt, MkdirAt returns ENOENT.
   205  	//
   206  	// Preconditions:
   207  	//	* !rp.Done().
   208  	//	* For the final path component in rp, !rp.ShouldFollowSymlink().
   209  	//
   210  	// Postconditions: If MkdirAt returns an error returned by
   211  	// ResolvingPath.Resolve*(), then !rp.Done().
   212  	MkdirAt(ctx context.Context, rp *ResolvingPath, opts MkdirOptions) error
   213  
   214  	// MknodAt creates a regular file, device special file, or named pipe at
   215  	// rp.
   216  	//
   217  	// Errors:
   218  	//
   219  	//	- If the last path component in rp is "." or "..", MknodAt returns
   220  	//		EEXIST.
   221  	//
   222  	//	- If a file already exists at rp, MknodAt returns EEXIST.
   223  	//
   224  	//	- If rp.MustBeDir(), MknodAt returns ENOENT.
   225  	//
   226  	//	- If the directory in which the file would be created has been removed
   227  	//		by RmdirAt or RenameAt, MknodAt returns ENOENT.
   228  	//
   229  	// Preconditions:
   230  	//	* !rp.Done().
   231  	//	* For the final path component in rp, !rp.ShouldFollowSymlink().
   232  	//
   233  	// Postconditions: If MknodAt returns an error returned by
   234  	// ResolvingPath.Resolve*(), then !rp.Done().
   235  	MknodAt(ctx context.Context, rp *ResolvingPath, opts MknodOptions) error
   236  
   237  	// OpenAt returns an FileDescription providing access to the file at rp. A
   238  	// reference is taken on the returned FileDescription.
   239  	//
   240  	// Errors:
   241  	//
   242  	//	- If opts.Flags specifies O_TMPFILE and this feature is unsupported by
   243  	//		the implementation, OpenAt returns EOPNOTSUPP. (All other unsupported
   244  	//		features are silently ignored, consistently with Linux's open*(2).)
   245  	OpenAt(ctx context.Context, rp *ResolvingPath, opts OpenOptions) (*FileDescription, error)
   246  
   247  	// ReadlinkAt returns the target of the symbolic link at rp.
   248  	//
   249  	// Errors:
   250  	//
   251  	//	- If the file at rp is not a symbolic link, ReadlinkAt returns EINVAL.
   252  	ReadlinkAt(ctx context.Context, rp *ResolvingPath) (string, error)
   253  
   254  	// RenameAt renames the file named oldName in directory oldParentVD to rp.
   255  	// It does not take ownership of references on oldParentVD.
   256  	//
   257  	// Errors [1]:
   258  	//
   259  	//	- If opts.Flags specifies unsupported options, RenameAt returns EINVAL.
   260  	//
   261  	//	- If the last path component in rp is "." or "..", and opts.Flags
   262  	//		contains RENAME_NOREPLACE, RenameAt returns EEXIST.
   263  	//
   264  	//	- If the last path component in rp is "." or "..", and opts.Flags does
   265  	//		not contain RENAME_NOREPLACE, RenameAt returns EBUSY.
   266  	//
   267  	//	- If rp.Mount != oldParentVD.Mount(), RenameAt returns EXDEV.
   268  	//
   269  	//	- If the renamed file is not a directory, and opts.MustBeDir is true,
   270  	//		RenameAt returns ENOTDIR.
   271  	//
   272  	//	- If renaming would replace an existing file and opts.Flags contains
   273  	//		RENAME_NOREPLACE, RenameAt returns EEXIST.
   274  	//
   275  	//	- If there is no existing file at rp and opts.Flags contains
   276  	//		RENAME_EXCHANGE, RenameAt returns ENOENT.
   277  	//
   278  	//	- If there is an existing non-directory file at rp, and rp.MustBeDir()
   279  	//		is true, RenameAt returns ENOTDIR.
   280  	//
   281  	//	- If the renamed file is not a directory, opts.Flags does not contain
   282  	//		RENAME_EXCHANGE, and rp.MustBeDir() is true, RenameAt returns ENOTDIR.
   283  	//		(This check is not subsumed by the check for directory replacement below
   284  	//		since it applies even if there is no file to replace.)
   285  	//
   286  	//	- If the renamed file is a directory, and the new parent directory of
   287  	//		the renamed file is either the renamed directory or a descendant
   288  	//		subdirectory of the renamed directory, RenameAt returns EINVAL.
   289  	//
   290  	//	- If renaming would exchange the renamed file with an ancestor directory
   291  	//		of the renamed file, RenameAt returns EINVAL.
   292  	//
   293  	//	- If renaming would replace an ancestor directory of the renamed file,
   294  	//		RenameAt returns ENOTEMPTY. (This check would be subsumed by the
   295  	//		non-empty directory check below; however, this check takes place before
   296  	//		the self-rename check.)
   297  	//
   298  	//	- If the renamed file would replace or exchange with itself (i.e. the
   299  	//		source and destination paths resolve to the same file), RenameAt returns
   300  	//		nil, skipping the checks described below.
   301  	//
   302  	//	- If the source or destination directory is not writable by the provider
   303  	//		of rp.Credentials(), RenameAt returns EACCES.
   304  	//
   305  	//	- If the renamed file is a directory, and renaming would replace a
   306  	//		non-directory file, RenameAt returns ENOTDIR.
   307  	//
   308  	//	- If the renamed file is not a directory, and renaming would replace a
   309  	//		directory, RenameAt returns EISDIR.
   310  	//
   311  	//	- If the new parent directory of the renamed file has been removed by
   312  	//		RmdirAt or a preceding call to RenameAt, RenameAt returns ENOENT.
   313  	//
   314  	//	- If the renamed file is a directory, it is not writable by the
   315  	//		provider of rp.Credentials(), and the source and destination parent
   316  	//		directories are different, RenameAt returns EACCES. (This is nominally
   317  	//		required to change the ".." entry in the renamed directory.)
   318  	//
   319  	//	- If renaming would replace a non-empty directory, RenameAt returns
   320  	//		ENOTEMPTY.
   321  	//
   322  	// Preconditions:
   323  	//	* !rp.Done().
   324  	//	* For the final path component in rp, !rp.ShouldFollowSymlink().
   325  	//	* oldParentVD.Dentry() was obtained from a previous call to
   326  	//		oldParentVD.Mount().Filesystem().Impl().GetParentDentryAt().
   327  	//	* oldName is not "." or "..".
   328  	//
   329  	// Postconditions: If RenameAt returns an error returned by
   330  	// ResolvingPath.Resolve*(), then !rp.Done().
   331  	//
   332  	// [1] "The worst of all namespace operations - renaming directory.
   333  	// "Perverted" doesn't even start to describe it. Somebody in UCB had a
   334  	// heck of a trip..." - fs/namei.c:vfs_rename()
   335  	RenameAt(ctx context.Context, rp *ResolvingPath, oldParentVD VirtualDentry, oldName string, opts RenameOptions) error
   336  
   337  	// RmdirAt removes the directory at rp.
   338  	//
   339  	// Errors:
   340  	//
   341  	//	- If the last path component in rp is ".", RmdirAt returns EINVAL.
   342  	//
   343  	//	- If the last path component in rp is "..", RmdirAt returns ENOTEMPTY.
   344  	//
   345  	//	- If no file exists at rp, RmdirAt returns ENOENT.
   346  	//
   347  	//	- If the file at rp exists but is not a directory, RmdirAt returns
   348  	//		ENOTDIR.
   349  	//
   350  	// Preconditions:
   351  	//	* !rp.Done().
   352  	//	* For the final path component in rp, !rp.ShouldFollowSymlink().
   353  	//
   354  	// Postconditions: If RmdirAt returns an error returned by
   355  	// ResolvingPath.Resolve*(), then !rp.Done().
   356  	RmdirAt(ctx context.Context, rp *ResolvingPath) error
   357  
   358  	// SetStatAt updates metadata for the file at the given path. Implementations
   359  	// are responsible for checking if the operation can be performed
   360  	// (see vfs.CheckSetStat() for common checks).
   361  	//
   362  	// Errors:
   363  	//
   364  	//	- If opts specifies unsupported options, SetStatAt returns EINVAL.
   365  	SetStatAt(ctx context.Context, rp *ResolvingPath, opts SetStatOptions) error
   366  
   367  	// StatAt returns metadata for the file at rp.
   368  	StatAt(ctx context.Context, rp *ResolvingPath, opts StatOptions) (linux.Statx, error)
   369  
   370  	// StatFSAt returns metadata for the filesystem containing the file at rp.
   371  	// (This method takes a path because a FilesystemImpl may consist of any
   372  	// number of constituent filesystems.)
   373  	StatFSAt(ctx context.Context, rp *ResolvingPath) (linux.Statfs, error)
   374  
   375  	// SymlinkAt creates a symbolic link at rp referring to the given target.
   376  	//
   377  	// Errors:
   378  	//
   379  	//	- If the last path component in rp is "." or "..", SymlinkAt returns
   380  	//		EEXIST.
   381  	//
   382  	//	- If a file already exists at rp, SymlinkAt returns EEXIST.
   383  	//
   384  	//	- If rp.MustBeDir(), SymlinkAt returns ENOENT.
   385  	//
   386  	//	- If the directory in which the symbolic link would be created has been
   387  	//		removed by RmdirAt or RenameAt, SymlinkAt returns ENOENT.
   388  	//
   389  	// Preconditions:
   390  	//	* !rp.Done().
   391  	//	* For the final path component in rp, !rp.ShouldFollowSymlink().
   392  	//
   393  	// Postconditions: If SymlinkAt returns an error returned by
   394  	// ResolvingPath.Resolve*(), then !rp.Done().
   395  	SymlinkAt(ctx context.Context, rp *ResolvingPath, target string) error
   396  
   397  	// UnlinkAt removes the file at rp.
   398  	//
   399  	// Errors:
   400  	//
   401  	//	- If the last path component in rp is "." or "..", UnlinkAt returns
   402  	//		EISDIR.
   403  	//
   404  	//	- If no file exists at rp, UnlinkAt returns ENOENT.
   405  	//
   406  	//	- If rp.MustBeDir(), and the file at rp exists and is not a directory,
   407  	//		UnlinkAt returns ENOTDIR.
   408  	//
   409  	//	- If the file at rp exists but is a directory, UnlinkAt returns EISDIR.
   410  	//
   411  	// Preconditions:
   412  	//	* !rp.Done().
   413  	//	* For the final path component in rp, !rp.ShouldFollowSymlink().
   414  	//
   415  	// Postconditions: If UnlinkAt returns an error returned by
   416  	// ResolvingPath.Resolve*(), then !rp.Done().
   417  	UnlinkAt(ctx context.Context, rp *ResolvingPath) error
   418  
   419  	// ListXattrAt returns all extended attribute names for the file at rp.
   420  	//
   421  	// Errors:
   422  	//
   423  	//	- If extended attributes are not supported by the filesystem,
   424  	//		ListXattrAt returns ENOTSUP.
   425  	//
   426  	//	- If the size of the list (including a NUL terminating byte after every
   427  	//		entry) would exceed size, ERANGE may be returned. Note that
   428  	//		implementations are free to ignore size entirely and return without
   429  	//		error). In all cases, if size is 0, the list should be returned without
   430  	//		error, regardless of size.
   431  	ListXattrAt(ctx context.Context, rp *ResolvingPath, size uint64) ([]string, error)
   432  
   433  	// GetXattrAt returns the value associated with the given extended
   434  	// attribute for the file at rp.
   435  	//
   436  	// Errors:
   437  	//
   438  	//	- If extended attributes are not supported by the filesystem, GetXattrAt
   439  	//		returns ENOTSUP.
   440  	//
   441  	//	- If an extended attribute named opts.Name does not exist, ENODATA is
   442  	//		returned.
   443  	//
   444  	//	- If the size of the return value exceeds opts.Size, ERANGE may be
   445  	//		returned (note that implementations are free to ignore opts.Size entirely
   446  	//		and return without error). In all cases, if opts.Size is 0, the value
   447  	//		should be returned without error, regardless of size.
   448  	GetXattrAt(ctx context.Context, rp *ResolvingPath, opts GetXattrOptions) (string, error)
   449  
   450  	// SetXattrAt changes the value associated with the given extended
   451  	// attribute for the file at rp.
   452  	//
   453  	// Errors:
   454  	//
   455  	//	- If extended attributes are not supported by the filesystem, SetXattrAt
   456  	//		returns ENOTSUP.
   457  	//
   458  	//	- If XATTR_CREATE is set in opts.Flag and opts.Name already exists,
   459  	//		EEXIST is returned. If XATTR_REPLACE is set and opts.Name does not exist,
   460  	//		ENODATA is returned.
   461  	SetXattrAt(ctx context.Context, rp *ResolvingPath, opts SetXattrOptions) error
   462  
   463  	// RemoveXattrAt removes the given extended attribute from the file at rp.
   464  	//
   465  	// Errors:
   466  	//
   467  	//	- If extended attributes are not supported by the filesystem,
   468  	//		RemoveXattrAt returns ENOTSUP.
   469  	//
   470  	//	- If name does not exist, ENODATA is returned.
   471  	RemoveXattrAt(ctx context.Context, rp *ResolvingPath, name string) error
   472  
   473  	// BoundEndpointAt returns the Unix socket endpoint bound at the path rp.
   474  	//
   475  	// Errors:
   476  	//
   477  	//	- If the file does not have write permissions, then BoundEndpointAt
   478  	//		returns EACCES.
   479  	//
   480  	//	- If a non-socket file exists at rp, then BoundEndpointAt returns
   481  	//		ECONNREFUSED.
   482  	BoundEndpointAt(ctx context.Context, rp *ResolvingPath, opts BoundEndpointOptions) (transport.BoundEndpoint, error)
   483  
   484  	// PrependPath prepends a path from vd to vd.Mount().Root() to b.
   485  	//
   486  	// If vfsroot.Ok(), it is the contextual VFS root; if it is encountered
   487  	// before vd.Mount().Root(), PrependPath should stop prepending path
   488  	// components and return a PrependPathAtVFSRootError.
   489  	//
   490  	// If traversal of vd.Dentry()'s ancestors encounters an independent
   491  	// ("root") Dentry that is not vd.Mount().Root() (i.e. vd.Dentry() is not a
   492  	// descendant of vd.Mount().Root()), PrependPath should stop prepending
   493  	// path components and return a PrependPathAtNonMountRootError.
   494  	//
   495  	// Filesystems for which Dentries do not have meaningful paths may prepend
   496  	// an arbitrary descriptive string to b and then return a
   497  	// PrependPathSyntheticError.
   498  	//
   499  	// Most implementations can acquire the appropriate locks to ensure that
   500  	// Dentry.Name() and Dentry.Parent() are fixed for vd.Dentry() and all of
   501  	// its ancestors, then call GenericPrependPath.
   502  	//
   503  	// Preconditions: vd.Mount().Filesystem().Impl() == this FilesystemImpl.
   504  	PrependPath(ctx context.Context, vfsroot, vd VirtualDentry, b *fspath.Builder) error
   505  
   506  	// MountOptions returns mount options for the current filesystem. This
   507  	// should only return options specific to the filesystem (i.e. don't return
   508  	// "ro", "rw", etc). Options should be returned as a comma-separated string,
   509  	// similar to the input to the 5th argument to mount.
   510  	//
   511  	// If the implementation has no filesystem-specific options, it should
   512  	// return the empty string.
   513  	MountOptions() string
   514  }
   515  
   516  // PrependPathAtVFSRootError is returned by implementations of
   517  // FilesystemImpl.PrependPath() when they encounter the contextual VFS root.
   518  //
   519  // +stateify savable
   520  type PrependPathAtVFSRootError struct{}
   521  
   522  // Error implements error.Error.
   523  func (PrependPathAtVFSRootError) Error() string {
   524  	return "vfs.FilesystemImpl.PrependPath() reached VFS root"
   525  }
   526  
   527  // PrependPathAtNonMountRootError is returned by implementations of
   528  // FilesystemImpl.PrependPath() when they encounter an independent ancestor
   529  // Dentry that is not the Mount root.
   530  //
   531  // +stateify savable
   532  type PrependPathAtNonMountRootError struct{}
   533  
   534  // Error implements error.Error.
   535  func (PrependPathAtNonMountRootError) Error() string {
   536  	return "vfs.FilesystemImpl.PrependPath() reached root other than Mount root"
   537  }
   538  
   539  // PrependPathSyntheticError is returned by implementations of
   540  // FilesystemImpl.PrependPath() for which prepended names do not represent real
   541  // paths.
   542  //
   543  // +stateify savable
   544  type PrependPathSyntheticError struct{}
   545  
   546  // Error implements error.Error.
   547  func (PrependPathSyntheticError) Error() string {
   548  	return "vfs.FilesystemImpl.PrependPath() prepended synthetic name"
   549  }