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 }