github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/gofer/fs.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 gofer implements a remote 9p filesystem.
    16  package gofer
    17  
    18  import (
    19  	"errors"
    20  	"fmt"
    21  	"strconv"
    22  
    23  	"github.com/SagerNet/gvisor/pkg/context"
    24  	"github.com/SagerNet/gvisor/pkg/p9"
    25  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    26  )
    27  
    28  // The following are options defined by the Linux 9p client that we support,
    29  // see Documentation/filesystems/9p.txt.
    30  const (
    31  	// The transport method.
    32  	transportKey = "trans"
    33  
    34  	// The file tree to access when the file server
    35  	// is exporting several file systems. Stands for "attach name".
    36  	anameKey = "aname"
    37  
    38  	// The caching policy.
    39  	cacheKey = "cache"
    40  
    41  	// The file descriptor for reading with trans=fd.
    42  	readFDKey = "rfdno"
    43  
    44  	// The file descriptor for writing with trans=fd.
    45  	writeFDKey = "wfdno"
    46  
    47  	// The number of bytes to use for a 9p packet payload.
    48  	msizeKey = "msize"
    49  
    50  	// The 9p protocol version.
    51  	versionKey = "version"
    52  
    53  	// If set to true allows the creation of unix domain sockets inside the
    54  	// sandbox using files backed by the gofer. If set to false, unix sockets
    55  	// cannot be bound to gofer files without an overlay on top.
    56  	privateUnixSocketKey = "privateunixsocket"
    57  
    58  	// If present, sets CachingInodeOperationsOptions.LimitHostFDTranslation to
    59  	// true.
    60  	limitHostFDTranslationKey = "limit_host_fd_translation"
    61  
    62  	// overlayfsStaleRead if present closes cached readonly file after the first
    63  	// write. This is done to workaround a limitation of Linux overlayfs.
    64  	overlayfsStaleRead = "overlayfs_stale_read"
    65  )
    66  
    67  // defaultAname is the default attach name.
    68  const defaultAname = "/"
    69  
    70  // defaultMSize is the message size used for chunking large read and write requests.
    71  // This has been tested to give good enough performance up to 64M.
    72  const defaultMSize = 1024 * 1024 // 1M
    73  
    74  // defaultVersion is the default 9p protocol version. Will negotiate downwards with
    75  // file server if needed.
    76  var defaultVersion = p9.HighestVersionString()
    77  
    78  // Number of names of non-children to cache, preventing unneeded walks.  64 is
    79  // plenty for nodejs, which seems to stat about 4 children on every require().
    80  const nonChildrenCacheSize = 64
    81  
    82  var (
    83  	// ErrNoTransport is returned when there is no 'trans' option.
    84  	ErrNoTransport = errors.New("missing required option: 'trans='")
    85  
    86  	// ErrFileNoReadFD is returned when there is no 'rfdno' option.
    87  	ErrFileNoReadFD = errors.New("missing required option: 'rfdno='")
    88  
    89  	// ErrFileNoWriteFD is returned when there is no 'wfdno' option.
    90  	ErrFileNoWriteFD = errors.New("missing required option: 'wfdno='")
    91  )
    92  
    93  // filesystem is a 9p client.
    94  //
    95  // +stateify savable
    96  type filesystem struct{}
    97  
    98  var _ fs.Filesystem = (*filesystem)(nil)
    99  
   100  func init() {
   101  	fs.RegisterFilesystem(&filesystem{})
   102  }
   103  
   104  // FilesystemName is the name under which the filesystem is registered.
   105  // The name matches fs/9p/vfs_super.c:v9fs_fs_type.name.
   106  const FilesystemName = "9p"
   107  
   108  // Name is the name of the filesystem.
   109  func (*filesystem) Name() string {
   110  	return FilesystemName
   111  }
   112  
   113  // AllowUserMount prohibits users from using mount(2) with this file system.
   114  func (*filesystem) AllowUserMount() bool {
   115  	return false
   116  }
   117  
   118  // AllowUserList allows this filesystem to be listed in /proc/filesystems.
   119  func (*filesystem) AllowUserList() bool {
   120  	return true
   121  }
   122  
   123  // Flags returns that there is nothing special about this file system.
   124  //
   125  // The 9p Linux client returns FS_RENAME_DOES_D_MOVE, see fs/9p/vfs_super.c.
   126  func (*filesystem) Flags() fs.FilesystemFlags {
   127  	return 0
   128  }
   129  
   130  // Mount returns an attached 9p client that can be positioned in the vfs.
   131  func (f *filesystem) Mount(ctx context.Context, device string, flags fs.MountSourceFlags, data string, _ interface{}) (*fs.Inode, error) {
   132  	// Parse and validate the mount options.
   133  	o, err := options(data)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	// Construct the 9p root to mount. We intentionally diverge from Linux in that
   139  	// the first Tversion and Tattach requests are done lazily.
   140  	return Root(ctx, device, f, flags, o)
   141  }
   142  
   143  // opts are parsed 9p mount options.
   144  type opts struct {
   145  	fd                     int
   146  	aname                  string
   147  	policy                 cachePolicy
   148  	msize                  uint32
   149  	version                string
   150  	privateunixsocket      bool
   151  	limitHostFDTranslation bool
   152  	overlayfsStaleRead     bool
   153  }
   154  
   155  // options parses mount(2) data into structured options.
   156  func options(data string) (opts, error) {
   157  	var o opts
   158  
   159  	// Parse generic comma-separated key=value options, this file system expects them.
   160  	options := fs.GenericMountSourceOptions(data)
   161  
   162  	// Check for the required 'trans=fd' option.
   163  	trans, ok := options[transportKey]
   164  	if !ok {
   165  		return o, ErrNoTransport
   166  	}
   167  	if trans != "fd" {
   168  		return o, fmt.Errorf("unsupported transport: 'trans=%s'", trans)
   169  	}
   170  	delete(options, transportKey)
   171  
   172  	// Check for the required 'rfdno=' option.
   173  	srfd, ok := options[readFDKey]
   174  	if !ok {
   175  		return o, ErrFileNoReadFD
   176  	}
   177  	delete(options, readFDKey)
   178  
   179  	// Check for the required 'wfdno=' option.
   180  	swfd, ok := options[writeFDKey]
   181  	if !ok {
   182  		return o, ErrFileNoWriteFD
   183  	}
   184  	delete(options, writeFDKey)
   185  
   186  	// Parse the read fd.
   187  	rfd, err := strconv.Atoi(srfd)
   188  	if err != nil {
   189  		return o, fmt.Errorf("invalid fd for 'rfdno=%s': %v", srfd, err)
   190  	}
   191  
   192  	// Parse the write fd.
   193  	wfd, err := strconv.Atoi(swfd)
   194  	if err != nil {
   195  		return o, fmt.Errorf("invalid fd for 'wfdno=%s': %v", swfd, err)
   196  	}
   197  
   198  	// Require that the read and write fd are the same.
   199  	if rfd != wfd {
   200  		return o, fmt.Errorf("fd in 'rfdno=%d' and 'wfdno=%d' must match", rfd, wfd)
   201  	}
   202  	o.fd = rfd
   203  
   204  	// Parse the attach name.
   205  	o.aname = defaultAname
   206  	if an, ok := options[anameKey]; ok {
   207  		o.aname = an
   208  		delete(options, anameKey)
   209  	}
   210  
   211  	// Parse the cache policy. Reject unsupported policies.
   212  	o.policy = cacheAll
   213  	if policy, ok := options[cacheKey]; ok {
   214  		cp, err := parseCachePolicy(policy)
   215  		if err != nil {
   216  			return o, err
   217  		}
   218  		o.policy = cp
   219  		delete(options, cacheKey)
   220  	}
   221  
   222  	// Parse the message size. Reject malformed options.
   223  	o.msize = uint32(defaultMSize)
   224  	if m, ok := options[msizeKey]; ok {
   225  		i, err := strconv.ParseUint(m, 10, 32)
   226  		if err != nil {
   227  			return o, fmt.Errorf("invalid message size for 'msize=%s': %v", m, err)
   228  		}
   229  		o.msize = uint32(i)
   230  		delete(options, msizeKey)
   231  	}
   232  
   233  	// Parse the protocol version.
   234  	o.version = defaultVersion
   235  	if v, ok := options[versionKey]; ok {
   236  		o.version = v
   237  		delete(options, versionKey)
   238  	}
   239  
   240  	// Parse the unix socket policy. Reject non-booleans.
   241  	if v, ok := options[privateUnixSocketKey]; ok {
   242  		b, err := strconv.ParseBool(v)
   243  		if err != nil {
   244  			return o, fmt.Errorf("invalid boolean value for '%s=%s': %v", privateUnixSocketKey, v, err)
   245  		}
   246  		o.privateunixsocket = b
   247  		delete(options, privateUnixSocketKey)
   248  	}
   249  
   250  	if _, ok := options[limitHostFDTranslationKey]; ok {
   251  		o.limitHostFDTranslation = true
   252  		delete(options, limitHostFDTranslationKey)
   253  	}
   254  
   255  	if _, ok := options[overlayfsStaleRead]; ok {
   256  		o.overlayfsStaleRead = true
   257  		delete(options, overlayfsStaleRead)
   258  	}
   259  
   260  	// Fail to attach if the caller wanted us to do something that we
   261  	// don't support.
   262  	if len(options) > 0 {
   263  		return o, fmt.Errorf("unsupported mount options: %v", options)
   264  	}
   265  
   266  	return o, nil
   267  }