gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/fsgofer/lisafs.go (about) 1 // Copyright 2021 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 fsgofer provides a lisafs server implementation which gives access 16 // to local files. 17 package fsgofer 18 19 import ( 20 "fmt" 21 "io" 22 "math" 23 "os" 24 "path" 25 "path/filepath" 26 "strconv" 27 "sync" 28 29 "golang.org/x/sys/unix" 30 "gvisor.dev/gvisor/pkg/abi/linux" 31 "gvisor.dev/gvisor/pkg/atomicbitops" 32 "gvisor.dev/gvisor/pkg/cleanup" 33 rwfd "gvisor.dev/gvisor/pkg/fd" 34 "gvisor.dev/gvisor/pkg/fsutil" 35 "gvisor.dev/gvisor/pkg/lisafs" 36 "gvisor.dev/gvisor/pkg/log" 37 "gvisor.dev/gvisor/pkg/marshal/primitive" 38 "gvisor.dev/gvisor/runsc/config" 39 ) 40 41 // LINT.IfChange 42 43 const ( 44 openFlags = unix.O_NOFOLLOW | unix.O_CLOEXEC 45 46 // UNIX_PATH_MAX as defined in include/uapi/linux/un.h. 47 unixPathMax = 108 48 ) 49 50 // Config sets configuration options for each attach point. 51 type Config struct { 52 // ROMount is set to true if this is a readonly mount. 53 ROMount bool 54 55 // PanicOnWrite panics on attempts to write to RO mounts. 56 PanicOnWrite bool 57 58 // HostUDS signals whether the gofer can connect to host unix domain sockets. 59 HostUDS config.HostUDS 60 61 // HostFifo signals whether the gofer can connect to host FIFOs. 62 HostFifo config.HostFifo 63 64 // DonateMountPointFD indicates whether a host FD to the mount point should 65 // be donated to the client on Mount RPC. 66 DonateMountPointFD bool 67 } 68 69 var procSelfFD *rwfd.FD 70 71 // OpenProcSelfFD opens the /proc/self/fd directory, which will be used to 72 // reopen file descriptors. 73 func OpenProcSelfFD(path string) error { 74 d, err := unix.Open(path, unix.O_RDONLY|unix.O_DIRECTORY, 0) 75 if err != nil { 76 return fmt.Errorf("error opening /proc/self/fd: %v", err) 77 } 78 procSelfFD = rwfd.New(d) 79 return nil 80 } 81 82 // LisafsServer implements lisafs.ServerImpl for fsgofer. 83 type LisafsServer struct { 84 lisafs.Server 85 config Config 86 } 87 88 var _ lisafs.ServerImpl = (*LisafsServer)(nil) 89 90 // NewLisafsServer initializes a new lisafs server for fsgofer. 91 func NewLisafsServer(config Config) *LisafsServer { 92 s := &LisafsServer{config: config} 93 s.Server.Init(s, lisafs.ServerOpts{ 94 WalkStatSupported: true, 95 SetAttrOnDeleted: true, 96 AllocateOnDeleted: true, 97 }) 98 return s 99 } 100 101 // Mount implements lisafs.ServerImpl.Mount. 102 func (s *LisafsServer) Mount(c *lisafs.Connection, mountNode *lisafs.Node) (*lisafs.ControlFD, linux.Statx, int, error) { 103 mountPath := mountNode.FilePath() 104 rootHostFD, err := tryOpen(func(flags int) (int, error) { 105 return unix.Open(mountPath, flags, 0) 106 }) 107 if err != nil { 108 return nil, linux.Statx{}, -1, err 109 } 110 cu := cleanup.Make(func() { 111 _ = unix.Close(rootHostFD) 112 }) 113 defer cu.Clean() 114 115 stat, err := fstatTo(rootHostFD) 116 if err != nil { 117 return nil, linux.Statx{}, -1, err 118 } 119 120 if err := checkSupportedFileType(uint32(stat.Mode)); err != nil { 121 log.Warningf("Mount: checkSupportedFileType() failed for file %q with mode %o: %v", mountPath, stat.Mode, err) 122 return nil, linux.Statx{}, -1, err 123 } 124 125 clientHostFD := -1 126 if s.config.DonateMountPointFD { 127 clientHostFD, err = unix.Dup(rootHostFD) 128 if err != nil { 129 return nil, linux.Statx{}, -1, err 130 } 131 } 132 cu.Release() 133 134 rootFD := &controlFDLisa{ 135 hostFD: rootHostFD, 136 writableHostFD: atomicbitops.FromInt32(-1), 137 isMountPoint: true, 138 } 139 mountNode.IncRef() // Ref is transferred to ControlFD. 140 rootFD.ControlFD.Init(c, mountNode, linux.FileMode(stat.Mode), rootFD) 141 return rootFD.FD(), stat, clientHostFD, nil 142 } 143 144 // MaxMessageSize implements lisafs.ServerImpl.MaxMessageSize. 145 func (s *LisafsServer) MaxMessageSize() uint32 { 146 return lisafs.MaxMessageSize() 147 } 148 149 // SupportedMessages implements lisafs.ServerImpl.SupportedMessages. 150 func (s *LisafsServer) SupportedMessages() []lisafs.MID { 151 // Note that Flush, FListXattr and FRemoveXattr are not supported. 152 return []lisafs.MID{ 153 lisafs.Mount, 154 lisafs.Channel, 155 lisafs.FStat, 156 lisafs.SetStat, 157 lisafs.Walk, 158 lisafs.WalkStat, 159 lisafs.OpenAt, 160 lisafs.OpenCreateAt, 161 lisafs.Close, 162 lisafs.FSync, 163 lisafs.PWrite, 164 lisafs.PRead, 165 lisafs.MkdirAt, 166 lisafs.MknodAt, 167 lisafs.SymlinkAt, 168 lisafs.LinkAt, 169 lisafs.FStatFS, 170 lisafs.FAllocate, 171 lisafs.ReadLinkAt, 172 lisafs.Connect, 173 lisafs.UnlinkAt, 174 lisafs.RenameAt, 175 lisafs.Getdents64, 176 lisafs.FGetXattr, 177 lisafs.FSetXattr, 178 lisafs.BindAt, 179 lisafs.Listen, 180 lisafs.Accept, 181 } 182 } 183 184 // controlFDLisa implements lisafs.ControlFDImpl. 185 type controlFDLisa struct { 186 lisafs.ControlFD 187 188 // hostFD is the file descriptor which can be used to make host syscalls. 189 hostFD int 190 191 // writableHostFD is the file descriptor number for a writable FD opened on 192 // the same FD as `hostFD`. It is initialized to -1, and can change in value 193 // exactly once. 194 writableHostFD atomicbitops.Int32 195 196 // isMountpoint indicates whether this FD represents the mount point for its 197 // owning connection. isMountPoint is immutable. 198 isMountPoint bool 199 } 200 201 var _ lisafs.ControlFDImpl = (*controlFDLisa)(nil) 202 203 func newControlFDLisa(hostFD int, parent *controlFDLisa, name string, mode linux.FileMode) *controlFDLisa { 204 var ( 205 childFD *controlFDLisa 206 childNode *lisafs.Node 207 parentNode = parent.Node() 208 ) 209 parentNode.WithChildrenMu(func() { 210 childNode = parentNode.LookupChildLocked(name) 211 if childNode == nil { 212 // Common case. Performance hack which is used to allocate the node and 213 // its control FD together in the heap. For a well-behaving client, there 214 // will be a 1:1 mapping between control FD and node and their lifecycle 215 // will be similar too. This will help reduce allocations and memory 216 // fragmentation. This is more cache friendly too. 217 temp := struct { 218 node lisafs.Node 219 fd controlFDLisa 220 }{} 221 childFD = &temp.fd 222 childNode = &temp.node 223 childNode.InitLocked(name, parentNode) 224 } else { 225 childNode.IncRef() 226 childFD = &controlFDLisa{} 227 } 228 }) 229 childFD.hostFD = hostFD 230 childFD.writableHostFD = atomicbitops.FromInt32(-1) 231 childFD.ControlFD.Init(parent.Conn(), childNode, mode, childFD) 232 return childFD 233 } 234 235 func (fd *controlFDLisa) getWritableFD() (int, error) { 236 if writableFD := fd.writableHostFD.Load(); writableFD != -1 { 237 return int(writableFD), nil 238 } 239 240 writableFD, err := unix.Openat(int(procSelfFD.FD()), strconv.Itoa(fd.hostFD), (unix.O_WRONLY|openFlags)&^unix.O_NOFOLLOW, 0) 241 if err != nil { 242 return -1, err 243 } 244 if !fd.writableHostFD.CompareAndSwap(-1, int32(writableFD)) { 245 // Race detected, use the new value and clean this up. 246 unix.Close(writableFD) 247 return int(fd.writableHostFD.Load()), nil 248 } 249 return writableFD, nil 250 } 251 252 func (fd *controlFDLisa) getParentFD() (int, string, error) { 253 filePath := fd.Node().FilePath() 254 if filePath == "/" { 255 log.Warningf("getParentFD() call on the root") 256 return -1, "", unix.EINVAL 257 } 258 parent, err := unix.Open(path.Dir(filePath), openFlags|unix.O_PATH, 0) 259 return parent, path.Base(filePath), err 260 } 261 262 // FD implements lisafs.ControlFDImpl.FD. 263 func (fd *controlFDLisa) FD() *lisafs.ControlFD { 264 if fd == nil { 265 return nil 266 } 267 return &fd.ControlFD 268 } 269 270 // Close implements lisafs.ControlFDImpl.Close. 271 func (fd *controlFDLisa) Close() { 272 if fd.hostFD >= 0 { 273 _ = unix.Close(fd.hostFD) 274 fd.hostFD = -1 275 } 276 // No concurrent access is possible so no need to use atomics. 277 if fd.writableHostFD.RacyLoad() >= 0 { 278 _ = unix.Close(int(fd.writableHostFD.RacyLoad())) 279 fd.writableHostFD = atomicbitops.FromInt32(-1) 280 } 281 } 282 283 // Stat implements lisafs.ControlFDImpl.Stat. 284 func (fd *controlFDLisa) Stat() (linux.Statx, error) { 285 return fstatTo(fd.hostFD) 286 } 287 288 // SetStat implements lisafs.ControlFDImpl.SetStat. 289 func (fd *controlFDLisa) SetStat(stat lisafs.SetStatReq) (failureMask uint32, failureErr error) { 290 if stat.Mask&unix.STATX_MODE != 0 { 291 if fd.IsSocket() { 292 // fchmod(2) on socket files created via bind(2) fails. We need to 293 // fchmodat(2) it from its parent. 294 parent, sockName, err := fd.getParentFD() 295 if err == nil { 296 // Note that AT_SYMLINK_NOFOLLOW flag is not currently supported. 297 err = unix.Fchmodat(parent, sockName, stat.Mode&^unix.S_IFMT, 0 /* flags */) 298 unix.Close(parent) 299 } 300 if err != nil { 301 log.Warningf("SetStat fchmod failed on socket %q, err: %v", fd.Node().FilePath(), err) 302 failureMask |= unix.STATX_MODE 303 failureErr = err 304 } 305 } else { 306 if err := unix.Fchmod(fd.hostFD, stat.Mode&^unix.S_IFMT); err != nil { 307 log.Warningf("SetStat fchmod failed %q, err: %v", fd.Node().FilePath(), err) 308 failureMask |= unix.STATX_MODE 309 failureErr = err 310 } 311 } 312 } 313 314 if stat.Mask&unix.STATX_SIZE != 0 { 315 // ftruncate(2) requires the FD to be open for writing. 316 writableFD, err := fd.getWritableFD() 317 if err == nil { 318 err = unix.Ftruncate(writableFD, int64(stat.Size)) 319 } 320 if err != nil { 321 log.Warningf("SetStat ftruncate failed %q, err: %v", fd.Node().FilePath(), err) 322 failureMask |= unix.STATX_SIZE 323 failureErr = err 324 } 325 } 326 327 if stat.Mask&(unix.STATX_ATIME|unix.STATX_MTIME) != 0 { 328 utimes := [2]unix.Timespec{ 329 {Sec: 0, Nsec: unix.UTIME_OMIT}, 330 {Sec: 0, Nsec: unix.UTIME_OMIT}, 331 } 332 if stat.Mask&unix.STATX_ATIME != 0 { 333 utimes[0].Sec = stat.Atime.Sec 334 utimes[0].Nsec = stat.Atime.Nsec 335 } 336 if stat.Mask&unix.STATX_MTIME != 0 { 337 utimes[1].Sec = stat.Mtime.Sec 338 utimes[1].Nsec = stat.Mtime.Nsec 339 } 340 341 if fd.IsSymlink() { 342 // utimensat operates different that other syscalls. To operate on a 343 // symlink it *requires* AT_SYMLINK_NOFOLLOW with dirFD and a non-empty 344 // name. We need the parent FD. 345 parent, symlinkName, err := fd.getParentFD() 346 if err == nil { 347 err = fsutil.Utimensat(parent, symlinkName, utimes, unix.AT_SYMLINK_NOFOLLOW) 348 unix.Close(parent) 349 } 350 if err != nil { 351 failureMask |= (stat.Mask & (unix.STATX_ATIME | unix.STATX_MTIME)) 352 failureErr = err 353 } 354 } else { 355 hostFD := fd.hostFD 356 if fd.IsRegular() { 357 // For regular files, utimensat(2) requires the FD to be open for 358 // writing, see BUGS section. 359 if writableFD, err := fd.getWritableFD(); err == nil { 360 hostFD = writableFD 361 } else { 362 log.Warningf("SetStat getWritableFD failed %q, err: %v", fd.Node().FilePath(), err) 363 } 364 } 365 // Directories and regular files can operate directly on the fd 366 // using empty name. 367 err := fsutil.Utimensat(hostFD, "", utimes, 0) 368 if err != nil { 369 log.Warningf("SetStat utimens failed %q, err: %v", fd.Node().FilePath(), err) 370 failureMask |= (stat.Mask & (unix.STATX_ATIME | unix.STATX_MTIME)) 371 failureErr = err 372 } 373 } 374 } 375 376 if stat.Mask&(unix.STATX_UID|unix.STATX_GID) != 0 { 377 // "If the owner or group is specified as -1, then that ID is not changed" 378 // - chown(2) 379 uid := -1 380 if stat.Mask&unix.STATX_UID != 0 { 381 uid = int(stat.UID) 382 } 383 gid := -1 384 if stat.Mask&unix.STATX_GID != 0 { 385 gid = int(stat.GID) 386 } 387 if err := unix.Fchownat(fd.hostFD, "", uid, gid, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW); err != nil { 388 log.Warningf("SetStat fchown failed %q, err: %v", fd.Node().FilePath(), err) 389 failureMask |= stat.Mask & (unix.STATX_UID | unix.STATX_GID) 390 failureErr = err 391 } 392 } 393 394 return 395 } 396 397 // Walk implements lisafs.ControlFDImpl.Walk. 398 func (fd *controlFDLisa) Walk(name string) (*lisafs.ControlFD, linux.Statx, error) { 399 childHostFD, err := tryOpen(func(flags int) (int, error) { 400 return unix.Openat(fd.hostFD, name, flags, 0) 401 }) 402 if err != nil { 403 return nil, linux.Statx{}, err 404 } 405 406 stat, err := fstatTo(childHostFD) 407 if err != nil { 408 _ = unix.Close(childHostFD) 409 return nil, linux.Statx{}, err 410 } 411 412 if err := checkSupportedFileType(uint32(stat.Mode)); err != nil { 413 _ = unix.Close(childHostFD) 414 log.Warningf("Walk: checkSupportedFileType() failed for %q with mode %o: %v", name, stat.Mode, err) 415 return nil, linux.Statx{}, err 416 } 417 418 return newControlFDLisa(childHostFD, fd, name, linux.FileMode(stat.Mode)).FD(), stat, nil 419 } 420 421 // WalkStat implements lisafs.ControlFDImpl.WalkStat. 422 func (fd *controlFDLisa) WalkStat(path lisafs.StringArray, recordStat func(linux.Statx)) error { 423 // Note that while performing the walk below, we do not have read concurrency 424 // guarantee for any descendants. So files can be created/deleted inside fd 425 // while the walk is being performed. However, this should be fine from a 426 // security perspective as we are using host FDs to walk and checking that 427 // each opened path component is not a symlink. 428 curDirFD := fd.hostFD 429 closeCurDirFD := func() { 430 if curDirFD != fd.hostFD { 431 unix.Close(curDirFD) 432 } 433 } 434 defer closeCurDirFD() 435 if len(path) > 0 && len(path[0]) == 0 { 436 // Write stat results for dirFD if the first path component is "". 437 stat, err := fstatTo(fd.hostFD) 438 if err != nil { 439 return err 440 } 441 recordStat(stat) 442 path = path[1:] 443 } 444 445 // Don't attempt walking if parent is a symlink. 446 if fd.IsSymlink() { 447 return nil 448 } 449 for _, name := range path { 450 curFD, err := unix.Openat(curDirFD, name, unix.O_PATH|openFlags, 0) 451 if err == unix.ENOENT { 452 // No more path components exist on the filesystem. Return the partial 453 // walk to the client. 454 break 455 } 456 if err != nil { 457 return err 458 } 459 closeCurDirFD() 460 curDirFD = curFD 461 462 stat, err := fstatTo(curFD) 463 if err != nil { 464 return err 465 } 466 if err := checkSupportedFileType(uint32(stat.Mode)); err != nil { 467 log.Warningf("WalkStat: checkSupportedFileType() failed for file %q with mode %o while walking path %+v: %v", name, stat.Mode, path, err) 468 return err 469 } 470 recordStat(stat) 471 472 // Symlinks terminate walk. This client gets the symlink stat result, but 473 // will have to invoke Walk again with the resolved path. 474 if stat.Mode&unix.S_IFMT == unix.S_IFLNK { 475 break 476 } 477 } 478 479 return nil 480 } 481 482 // Used to log rejected fifo/uds operations, one time each. 483 var ( 484 logRejectedFifoOpenOnce sync.Once 485 logRejectedUdsOpenOnce sync.Once 486 logRejectedUdsCreateOnce sync.Once 487 logRejectedUdsConnectOnce sync.Once 488 ) 489 490 // Open implements lisafs.ControlFDImpl.Open. 491 func (fd *controlFDLisa) Open(flags uint32) (*lisafs.OpenFD, int, error) { 492 ftype := fd.FileType() 493 server := fd.Conn().ServerImpl().(*LisafsServer) 494 switch ftype { 495 case unix.S_IFIFO: 496 if !server.config.HostFifo.AllowOpen() { 497 logRejectedFifoOpenOnce.Do(func() { 498 log.Warningf("Rejecting attempt to open fifo/pipe from host filesystem: %q. If you want to allow this, set flag --host-fifo=open", fd.ControlFD.Node().FilePath()) 499 }) 500 return nil, -1, unix.EPERM 501 } 502 case unix.S_IFSOCK: 503 if !server.config.HostUDS.AllowOpen() { 504 logRejectedUdsOpenOnce.Do(func() { 505 log.Warningf("Rejecting attempt to open unix domain socket from host filesystem. If you want to allow this, set flag --host-uds=open", fd.ControlFD.Node().FilePath()) 506 }) 507 return nil, -1, unix.EPERM 508 } 509 } 510 flags |= openFlags 511 openHostFD, err := unix.Openat(int(procSelfFD.FD()), strconv.Itoa(fd.hostFD), int(flags)&^unix.O_NOFOLLOW, 0) 512 if err != nil { 513 return nil, -1, err 514 } 515 516 hostFDToDonate := -1 517 switch { 518 case ftype == unix.S_IFREG: 519 // Best effort to donate file to the Sentry (for performance only). 520 hostFDToDonate, _ = unix.Dup(openHostFD) 521 522 case ftype == unix.S_IFIFO, 523 ftype == unix.S_IFCHR, 524 fd.isMountPoint && fd.Conn().ServerImpl().(*LisafsServer).config.DonateMountPointFD: 525 // Character devices and pipes can block indefinitely during reads/writes, 526 // which is not allowed for gofer operations. Ensure that it donates an FD 527 // back to the caller, so it can wait on the FD when reads/writes return 528 // EWOULDBLOCK. For mount points, if DonateMountPointFD option is set, an 529 // FD must be donated. 530 var err error 531 hostFDToDonate, err = unix.Dup(openHostFD) 532 if err != nil { 533 return nil, 0, err 534 } 535 } 536 537 openFD := fd.newOpenFDLisa(openHostFD, flags) 538 return openFD.FD(), hostFDToDonate, nil 539 } 540 541 // OpenCreate implements lisafs.ControlFDImpl.OpenCreate. 542 func (fd *controlFDLisa) OpenCreate(mode linux.FileMode, uid lisafs.UID, gid lisafs.GID, name string, flags uint32) (*lisafs.ControlFD, linux.Statx, *lisafs.OpenFD, int, error) { 543 createFlags := unix.O_CREAT | unix.O_EXCL | unix.O_RDONLY | unix.O_NONBLOCK | openFlags 544 childHostFD, err := unix.Openat(fd.hostFD, name, createFlags, uint32(mode&^linux.FileTypeMask)) 545 if err != nil { 546 return nil, linux.Statx{}, nil, -1, err 547 } 548 549 cu := cleanup.Make(func() { 550 // Best effort attempt to remove the file in case of failure. 551 if err := unix.Unlinkat(fd.hostFD, name, 0); err != nil { 552 log.Warningf("error unlinking file %q after failure: %v", path.Join(fd.Node().FilePath(), name), err) 553 } 554 unix.Close(childHostFD) 555 }) 556 defer cu.Clean() 557 558 // Set the owners as requested by the client. 559 if err := unix.Fchownat(childHostFD, "", int(uid), int(gid), unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW); err != nil { 560 return nil, linux.Statx{}, nil, -1, err 561 } 562 563 // Get stat results. 564 childStat, err := fstatTo(childHostFD) 565 if err != nil { 566 return nil, linux.Statx{}, nil, -1, err 567 } 568 569 // Now open an FD to the newly created file with the flags requested by the client. 570 flags |= openFlags 571 newHostFD, err := unix.Openat(int(procSelfFD.FD()), strconv.Itoa(childHostFD), int(flags)&^unix.O_NOFOLLOW, 0) 572 if err != nil { 573 return nil, linux.Statx{}, nil, -1, err 574 } 575 cu.Release() 576 577 childFD := newControlFDLisa(childHostFD, fd, name, linux.ModeRegular) 578 newFD := childFD.newOpenFDLisa(newHostFD, uint32(flags)) 579 580 // Donate FD because open(O_CREAT|O_EXCL) always creates a regular file. 581 // Since FD donation is a destructive operation, we should duplicate the 582 // to-be-donated FD. Eat the error if one occurs, it is better to have an FD 583 // without a host FD, than failing the Open attempt. 584 hostOpenFD := -1 585 if dupFD, err := unix.Dup(newFD.hostFD); err == nil { 586 hostOpenFD = dupFD 587 } 588 589 return childFD.FD(), childStat, newFD.FD(), hostOpenFD, nil 590 } 591 592 // Mkdir implements lisafs.ControlFDImpl.Mkdir. 593 func (fd *controlFDLisa) Mkdir(mode linux.FileMode, uid lisafs.UID, gid lisafs.GID, name string) (*lisafs.ControlFD, linux.Statx, error) { 594 if err := unix.Mkdirat(fd.hostFD, name, uint32(mode&^linux.FileTypeMask)); err != nil { 595 return nil, linux.Statx{}, err 596 } 597 cu := cleanup.Make(func() { 598 // Best effort attempt to remove the dir in case of failure. 599 if err := unix.Unlinkat(fd.hostFD, name, unix.AT_REMOVEDIR); err != nil { 600 log.Warningf("error unlinking dir %q after failure: %v", path.Join(fd.Node().FilePath(), name), err) 601 } 602 }) 603 defer cu.Clean() 604 605 // Open directory to change ownership. 606 childDirFd, err := tryOpen(func(flags int) (int, error) { 607 return unix.Openat(fd.hostFD, name, flags|unix.O_DIRECTORY, 0) 608 }) 609 if err != nil { 610 return nil, linux.Statx{}, err 611 } 612 if err := unix.Fchownat(childDirFd, "", int(uid), int(gid), unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW); err != nil { 613 unix.Close(childDirFd) 614 return nil, linux.Statx{}, err 615 } 616 617 // Get stat results. 618 childDirStat, err := fstatTo(childDirFd) 619 if err != nil { 620 unix.Close(childDirFd) 621 return nil, linux.Statx{}, err 622 } 623 624 cu.Release() 625 return newControlFDLisa(childDirFd, fd, name, linux.ModeDirectory).FD(), childDirStat, nil 626 } 627 628 // Mknod implements lisafs.ControlFDImpl.Mknod. 629 func (fd *controlFDLisa) Mknod(mode linux.FileMode, uid lisafs.UID, gid lisafs.GID, name string, minor uint32, major uint32) (*lisafs.ControlFD, linux.Statx, error) { 630 // From mknod(2) man page: 631 // "EPERM: [...] if the filesystem containing pathname does not support 632 // the type of node requested." 633 if mode.FileType() != linux.ModeRegular { 634 return nil, linux.Statx{}, unix.EPERM 635 } 636 637 if err := unix.Mknodat(fd.hostFD, name, uint32(mode), 0); err != nil { 638 return nil, linux.Statx{}, err 639 } 640 cu := cleanup.Make(func() { 641 // Best effort attempt to remove the file in case of failure. 642 if err := unix.Unlinkat(fd.hostFD, name, 0); err != nil { 643 log.Warningf("error unlinking file %q after failure: %v", path.Join(fd.Node().FilePath(), name), err) 644 } 645 }) 646 defer cu.Clean() 647 648 // Open file to change ownership. 649 childFD, err := tryOpen(func(flags int) (int, error) { 650 return unix.Openat(fd.hostFD, name, flags, 0) 651 }) 652 if err != nil { 653 return nil, linux.Statx{}, err 654 } 655 if err := unix.Fchownat(childFD, "", int(uid), int(gid), unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW); err != nil { 656 unix.Close(childFD) 657 return nil, linux.Statx{}, err 658 } 659 660 // Get stat results. 661 childStat, err := fstatTo(childFD) 662 if err != nil { 663 unix.Close(childFD) 664 return nil, linux.Statx{}, err 665 } 666 cu.Release() 667 668 return newControlFDLisa(childFD, fd, name, mode).FD(), childStat, nil 669 } 670 671 // Symlink implements lisafs.ControlFDImpl.Symlink. 672 func (fd *controlFDLisa) Symlink(name string, target string, uid lisafs.UID, gid lisafs.GID) (*lisafs.ControlFD, linux.Statx, error) { 673 if err := unix.Symlinkat(target, fd.hostFD, name); err != nil { 674 return nil, linux.Statx{}, err 675 } 676 cu := cleanup.Make(func() { 677 // Best effort attempt to remove the symlink in case of failure. 678 if err := unix.Unlinkat(fd.hostFD, name, 0); err != nil { 679 log.Warningf("error unlinking file %q after failure: %v", path.Join(fd.Node().FilePath(), name), err) 680 } 681 }) 682 defer cu.Clean() 683 684 // Open symlink to change ownership. 685 symlinkFD, err := unix.Openat(fd.hostFD, name, unix.O_PATH|openFlags, 0) 686 if err != nil { 687 return nil, linux.Statx{}, err 688 } 689 if err := unix.Fchownat(symlinkFD, "", int(uid), int(gid), unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW); err != nil { 690 unix.Close(symlinkFD) 691 return nil, linux.Statx{}, err 692 } 693 694 symlinkStat, err := fstatTo(symlinkFD) 695 if err != nil { 696 unix.Close(symlinkFD) 697 return nil, linux.Statx{}, err 698 } 699 cu.Release() 700 return newControlFDLisa(symlinkFD, fd, name, linux.ModeSymlink).FD(), symlinkStat, nil 701 } 702 703 // Link implements lisafs.ControlFDImpl.Link. 704 func (fd *controlFDLisa) Link(dir lisafs.ControlFDImpl, name string) (*lisafs.ControlFD, linux.Statx, error) { 705 // Using linkat(targetFD, "", newdirfd, name, AT_EMPTY_PATH) requires 706 // CAP_DAC_READ_SEARCH in the *root* userns. The gofer process has 707 // CAP_DAC_READ_SEARCH in its own userns. But sometimes the gofer may be 708 // running in a different userns. So we can't use AT_EMPTY_PATH. Fallback 709 // to using olddirfd to call linkat(2). 710 oldDirFD, oldName, err := fd.getParentFD() 711 if err != nil { 712 return nil, linux.Statx{}, err 713 } 714 dirFD := dir.(*controlFDLisa) 715 if err := unix.Linkat(oldDirFD, oldName, dirFD.hostFD, name, 0); err != nil { 716 return nil, linux.Statx{}, err 717 } 718 cu := cleanup.Make(func() { 719 // Best effort attempt to remove the hard link in case of failure. 720 if err := unix.Unlinkat(dirFD.hostFD, name, 0); err != nil { 721 log.Warningf("error unlinking file %q after failure: %v", path.Join(dirFD.Node().FilePath(), name), err) 722 } 723 }) 724 defer cu.Clean() 725 726 linkFD, err := tryOpen(func(flags int) (int, error) { 727 return unix.Openat(dirFD.hostFD, name, flags, 0) 728 }) 729 if err != nil { 730 return nil, linux.Statx{}, err 731 } 732 733 linkStat, err := fstatTo(linkFD) 734 if err != nil { 735 return nil, linux.Statx{}, err 736 } 737 cu.Release() 738 return newControlFDLisa(linkFD, dirFD, name, linux.FileMode(linkStat.Mode)).FD(), linkStat, nil 739 } 740 741 // StatFS implements lisafs.ControlFDImpl.StatFS. 742 func (fd *controlFDLisa) StatFS() (lisafs.StatFS, error) { 743 var s unix.Statfs_t 744 if err := unix.Fstatfs(fd.hostFD, &s); err != nil { 745 return lisafs.StatFS{}, err 746 } 747 748 return lisafs.StatFS{ 749 Type: uint64(s.Type), 750 BlockSize: s.Bsize, 751 Blocks: s.Blocks, 752 BlocksFree: s.Bfree, 753 BlocksAvailable: s.Bavail, 754 Files: s.Files, 755 FilesFree: s.Ffree, 756 NameLength: uint64(s.Namelen), 757 }, nil 758 } 759 760 // Readlink implements lisafs.ControlFDImpl.Readlink. 761 func (fd *controlFDLisa) Readlink(getLinkBuf func(uint32) []byte) (uint16, error) { 762 // This is similar to what os.Readlink does. 763 for linkLen := 128; linkLen < math.MaxUint16; linkLen *= 2 { 764 b := getLinkBuf(uint32(linkLen)) 765 n, err := unix.Readlinkat(fd.hostFD, "", b) 766 if err != nil { 767 return 0, err 768 } 769 if n < int(linkLen) { 770 return uint16(n), nil 771 } 772 } 773 return 0, unix.ENOMEM 774 } 775 776 func isSockTypeSupported(sockType uint32) bool { 777 switch sockType { 778 case unix.SOCK_STREAM, unix.SOCK_DGRAM, unix.SOCK_SEQPACKET: 779 return true 780 default: 781 log.Debugf("socket type %d is not supported", sockType) 782 return false 783 } 784 } 785 786 // Connect implements lisafs.ControlFDImpl.Connect. 787 func (fd *controlFDLisa) Connect(sockType uint32) (int, error) { 788 if !fd.Conn().ServerImpl().(*LisafsServer).config.HostUDS.AllowOpen() { 789 logRejectedUdsConnectOnce.Do(func() { 790 log.Warningf("Rejecting attempt to connect to unix domain socket from host filesystem: %q. If you want to allow this, set flag --host-uds=open", fd.ControlFD.Node().FilePath()) 791 }) 792 return -1, unix.EPERM 793 } 794 795 // TODO(gvisor.dev/issue/1003): Due to different app vs replacement 796 // mappings, the app path may have fit in the sockaddr, but we can't fit 797 // hostPath in our sockaddr. We'd need to redirect through a shorter path 798 // in order to actually connect to this socket. 799 hostPath := fd.Node().FilePath() 800 if len(hostPath) >= linux.UnixPathMax { 801 return -1, unix.EINVAL 802 } 803 804 if !isSockTypeSupported(sockType) { 805 return -1, unix.ENXIO 806 } 807 808 sock, err := unix.Socket(unix.AF_UNIX, int(sockType), 0) 809 if err != nil { 810 return -1, err 811 } 812 813 sa := unix.SockaddrUnix{Name: hostPath} 814 if err := unix.Connect(sock, &sa); err != nil { 815 unix.Close(sock) 816 return -1, err 817 } 818 return sock, nil 819 } 820 821 // BindAt implements lisafs.ControlFDImpl.BindAt. 822 func (fd *controlFDLisa) BindAt(name string, sockType uint32, mode linux.FileMode, uid lisafs.UID, gid lisafs.GID) (*lisafs.ControlFD, linux.Statx, *lisafs.BoundSocketFD, int, error) { 823 if !fd.Conn().ServerImpl().(*LisafsServer).config.HostUDS.AllowCreate() { 824 logRejectedUdsCreateOnce.Do(func() { 825 log.Warningf("Rejecting attempt to create unix domain socket from host filesystem: %q. If you want to allow this, set flag --host-uds=create", name) 826 }) 827 return nil, linux.Statx{}, nil, -1, unix.EPERM 828 } 829 830 // Because there is no "bindat" syscall in Linux, we must create an 831 // absolute path to the socket we are creating, 832 socketPath := filepath.Join(fd.Node().FilePath(), name) 833 834 // TODO(gvisor.dev/issue/1003): Due to different app vs replacement 835 // mappings, the app path may have fit in the sockaddr, but we can't fit 836 // hostPath in our sockaddr. We'd need to redirect through a shorter path 837 // in order to actually connect to this socket. 838 if len(socketPath) >= linux.UnixPathMax { 839 log.Warningf("BindAt called with name too long: %q (len=%d)", socketPath, len(socketPath)) 840 return nil, linux.Statx{}, nil, -1, unix.EINVAL 841 } 842 843 // Only the following types are supported. 844 if !isSockTypeSupported(sockType) { 845 return nil, linux.Statx{}, nil, -1, unix.ENXIO 846 } 847 848 // Create and bind the socket using the sockPath which may be a 849 // symlink. 850 sockFD, err := unix.Socket(unix.AF_UNIX, int(sockType), 0) 851 if err != nil { 852 return nil, linux.Statx{}, nil, -1, err 853 } 854 cu := cleanup.Make(func() { 855 _ = unix.Close(sockFD) 856 }) 857 defer cu.Clean() 858 859 // fchmod(2) has to happen *before* the bind(2). sockFD's file mode will 860 // be used in creating the filesystem-object in bind(2). 861 if err := unix.Fchmod(sockFD, uint32(mode&^linux.FileTypeMask)); err != nil { 862 return nil, linux.Statx{}, nil, -1, err 863 } 864 865 if err := unix.Bind(sockFD, &unix.SockaddrUnix{Name: socketPath}); err != nil { 866 return nil, linux.Statx{}, nil, -1, err 867 } 868 cu.Add(func() { 869 _ = unix.Unlink(socketPath) 870 }) 871 872 sockFileFD, err := tryOpen(func(flags int) (int, error) { 873 return unix.Openat(fd.hostFD, name, flags, 0) 874 }) 875 if err != nil { 876 return nil, linux.Statx{}, nil, -1, err 877 } 878 cu.Add(func() { 879 _ = unix.Close(sockFileFD) 880 }) 881 882 if err := unix.Fchownat(sockFileFD, "", int(uid), int(gid), unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW); err != nil { 883 return nil, linux.Statx{}, nil, -1, err 884 } 885 886 // Stat the socket. 887 sockStat, err := fstatTo(sockFileFD) 888 if err != nil { 889 return nil, linux.Statx{}, nil, -1, err 890 } 891 892 // Create an FD that will be donated to the sandbox. 893 sockFDToDonate, err := unix.Dup(sockFD) 894 if err != nil { 895 return nil, linux.Statx{}, nil, -1, err 896 } 897 cu.Release() 898 899 socketControlFD := newControlFDLisa(sockFD, fd, name, linux.ModeSocket) 900 boundSocketFD := &boundSocketFDLisa{ 901 sock: os.NewFile(uintptr(sockFD), socketPath), 902 } 903 boundSocketFD.Init(socketControlFD.FD(), boundSocketFD) 904 905 return socketControlFD.FD(), sockStat, boundSocketFD.FD(), sockFDToDonate, nil 906 } 907 908 // Unlink implements lisafs.ControlFDImpl.Unlink. 909 func (fd *controlFDLisa) Unlink(name string, flags uint32) error { 910 return unix.Unlinkat(fd.hostFD, name, int(flags)) 911 } 912 913 // RenameAt implements lisafs.ControlFDImpl.RenameAt. 914 func (fd *controlFDLisa) RenameAt(oldName string, newDir lisafs.ControlFDImpl, newName string) error { 915 return fsutil.RenameAt(fd.hostFD, oldName, newDir.(*controlFDLisa).hostFD, newName) 916 } 917 918 // Renamed implements lisafs.ControlFDImpl.Renamed. 919 func (fd *controlFDLisa) Renamed() { 920 // controlFDLisa does not have any state to update on rename. 921 } 922 923 // GetXattr implements lisafs.ControlFDImpl.GetXattr. 924 func (fd *controlFDLisa) GetXattr(name string, size uint32, getValueBuf func(uint32) []byte) (uint16, error) { 925 data := getValueBuf(size) 926 xattrSize, err := unix.Fgetxattr(fd.hostFD, name, data) 927 return uint16(xattrSize), err 928 } 929 930 // SetXattr implements lisafs.ControlFDImpl.SetXattr. 931 func (fd *controlFDLisa) SetXattr(name string, value string, flags uint32) error { 932 return unix.EOPNOTSUPP 933 } 934 935 // ListXattr implements lisafs.ControlFDImpl.ListXattr. 936 func (fd *controlFDLisa) ListXattr(size uint64) (lisafs.StringArray, error) { 937 return nil, unix.EOPNOTSUPP 938 } 939 940 // RemoveXattr implements lisafs.ControlFDImpl.RemoveXattr. 941 func (fd *controlFDLisa) RemoveXattr(name string) error { 942 return unix.EOPNOTSUPP 943 } 944 945 // openFDLisa implements lisafs.OpenFDImpl. 946 type openFDLisa struct { 947 lisafs.OpenFD 948 949 // hostFD is the host file descriptor which can be used to make syscalls. 950 hostFD int 951 } 952 953 var _ lisafs.OpenFDImpl = (*openFDLisa)(nil) 954 955 func (fd *controlFDLisa) newOpenFDLisa(hostFD int, flags uint32) *openFDLisa { 956 newFD := &openFDLisa{ 957 hostFD: hostFD, 958 } 959 newFD.OpenFD.Init(fd.FD(), flags, newFD) 960 return newFD 961 } 962 963 // FD implements lisafs.OpenFDImpl.FD. 964 func (fd *openFDLisa) FD() *lisafs.OpenFD { 965 if fd == nil { 966 return nil 967 } 968 return &fd.OpenFD 969 } 970 971 // Close implements lisafs.OpenFDImpl.Close. 972 func (fd *openFDLisa) Close() { 973 if fd.hostFD >= 0 { 974 _ = unix.Close(fd.hostFD) 975 fd.hostFD = -1 976 } 977 } 978 979 // Stat implements lisafs.OpenFDImpl.Stat. 980 func (fd *openFDLisa) Stat() (linux.Statx, error) { 981 return fstatTo(fd.hostFD) 982 } 983 984 // Sync implements lisafs.OpenFDImpl.Sync. 985 func (fd *openFDLisa) Sync() error { 986 return unix.Fsync(fd.hostFD) 987 } 988 989 // Write implements lisafs.OpenFDImpl.Write. 990 func (fd *openFDLisa) Write(buf []byte, off uint64) (uint64, error) { 991 rw := rwfd.NewReadWriter(fd.hostFD) 992 n, err := rw.WriteAt(buf, int64(off)) 993 return uint64(n), err 994 } 995 996 // Read implements lisafs.OpenFDImpl.Read. 997 func (fd *openFDLisa) Read(buf []byte, off uint64) (uint64, error) { 998 rw := rwfd.NewReadWriter(fd.hostFD) 999 n, err := rw.ReadAt(buf, int64(off)) 1000 if err != nil && err != io.EOF { 1001 return 0, err 1002 } 1003 return uint64(n), nil 1004 } 1005 1006 // Allocate implements lisafs.OpenFDImpl.Allocate. 1007 func (fd *openFDLisa) Allocate(mode, off, length uint64) error { 1008 return unix.Fallocate(fd.hostFD, uint32(mode), int64(off), int64(length)) 1009 } 1010 1011 // Flush implements lisafs.OpenFDImpl.Flush. 1012 func (fd *openFDLisa) Flush() error { 1013 return nil 1014 } 1015 1016 // Getdent64 implements lisafs.OpenFDImpl.Getdent64. 1017 func (fd *openFDLisa) Getdent64(count uint32, seek0 bool, recordDirent func(lisafs.Dirent64)) error { 1018 if seek0 { 1019 if _, err := unix.Seek(fd.hostFD, 0, 0); err != nil { 1020 return err 1021 } 1022 } 1023 1024 var direntsBuf [8192]byte 1025 var bytesRead int 1026 for bytesRead < int(count) { 1027 bufEnd := len(direntsBuf) 1028 if remaining := int(count) - bytesRead; remaining < bufEnd { 1029 bufEnd = remaining 1030 } 1031 n, err := unix.Getdents(fd.hostFD, direntsBuf[:bufEnd]) 1032 if err != nil { 1033 if err == unix.EINVAL && bufEnd < fsutil.UnixDirentMaxSize { 1034 // getdents64(2) returns EINVAL is returned when the result 1035 // buffer is too small. If bufEnd is smaller than the max 1036 // size of unix.Dirent, then just break here to return all 1037 // dirents collected till now. 1038 break 1039 } 1040 return err 1041 } 1042 if n <= 0 { 1043 break 1044 } 1045 1046 fsutil.ParseDirents(direntsBuf[:n], func(ino uint64, off int64, ftype uint8, name string, reclen uint16) { 1047 dirent := lisafs.Dirent64{ 1048 Ino: primitive.Uint64(ino), 1049 Off: primitive.Uint64(off), 1050 Type: primitive.Uint8(ftype), 1051 Name: lisafs.SizedString(name), 1052 } 1053 1054 // The client also wants the device ID, which annoyingly incurs an 1055 // additional syscall per dirent. 1056 // TODO(gvisor.dev/issue/6665): Get rid of per-dirent stat. 1057 stat, err := fsutil.StatAt(fd.hostFD, name) 1058 if err != nil { 1059 log.Warningf("Getdent64: skipping file %q with failed stat, err: %v", path.Join(fd.ControlFD().FD().Node().FilePath(), name), err) 1060 return 1061 } 1062 dirent.DevMinor = primitive.Uint32(unix.Minor(stat.Dev)) 1063 dirent.DevMajor = primitive.Uint32(unix.Major(stat.Dev)) 1064 recordDirent(dirent) 1065 bytesRead += int(reclen) 1066 }) 1067 } 1068 return nil 1069 } 1070 1071 // Renamed implements lisafs.OpenFDImpl.Renamed. 1072 func (fd *openFDLisa) Renamed() { 1073 // openFDLisa does not have any state to update on rename. 1074 } 1075 1076 type boundSocketFDLisa struct { 1077 lisafs.BoundSocketFD 1078 1079 sock *os.File 1080 } 1081 1082 var _ lisafs.BoundSocketFDImpl = (*boundSocketFDLisa)(nil) 1083 1084 // Close implements lisafs.BoundSocketFD.Close. 1085 func (fd *boundSocketFDLisa) Close() { 1086 fd.sock.Close() 1087 } 1088 1089 // FD implements lisafs.BoundSocketFD.FD. 1090 func (fd *boundSocketFDLisa) FD() *lisafs.BoundSocketFD { 1091 if fd == nil { 1092 return nil 1093 } 1094 return &fd.BoundSocketFD 1095 } 1096 1097 // Listen implements lisafs.BoundSocketFD.Listen. 1098 func (fd *boundSocketFDLisa) Listen(backlog int32) error { 1099 return unix.Listen(int(fd.sock.Fd()), int(backlog)) 1100 } 1101 1102 // Listen implements lisafs.BoundSocketFD.Accept. 1103 func (fd *boundSocketFDLisa) Accept() (int, string, error) { 1104 flags := unix.O_NONBLOCK | unix.O_CLOEXEC 1105 nfd, _, err := unix.Accept4(int(fd.sock.Fd()), flags) 1106 if err != nil { 1107 return -1, "", err 1108 } 1109 // Return an empty peer address so that we don't leak the actual host 1110 // address. 1111 return nfd, "", err 1112 } 1113 1114 // tryOpen tries to open() with different modes as documented. 1115 func tryOpen(open func(int) (int, error)) (hostFD int, err error) { 1116 // Attempt to open file in the following in order: 1117 // 1. RDONLY | NONBLOCK: for all files, directories, ro mounts, FIFOs. 1118 // Use non-blocking to prevent getting stuck inside open(2) for 1119 // FIFOs. This option has no effect on regular files. 1120 // 2. PATH: for symlinks, sockets. 1121 flags := []int{ 1122 unix.O_RDONLY | unix.O_NONBLOCK, 1123 unix.O_PATH, 1124 } 1125 1126 for _, flag := range flags { 1127 hostFD, err = open(flag | openFlags) 1128 if err == nil { 1129 return 1130 } 1131 1132 if e := extractErrno(err); e == unix.ENOENT { 1133 // File doesn't exist, no point in retrying. 1134 return -1, e 1135 } 1136 } 1137 return 1138 } 1139 1140 func fstatTo(hostFD int) (linux.Statx, error) { 1141 var stat unix.Stat_t 1142 if err := unix.Fstat(hostFD, &stat); err != nil { 1143 return linux.Statx{}, err 1144 } 1145 1146 return linux.Statx{ 1147 Mask: unix.STATX_TYPE | unix.STATX_MODE | unix.STATX_INO | unix.STATX_NLINK | unix.STATX_UID | unix.STATX_GID | unix.STATX_SIZE | unix.STATX_BLOCKS | unix.STATX_ATIME | unix.STATX_MTIME | unix.STATX_CTIME, 1148 Mode: uint16(stat.Mode), 1149 DevMinor: unix.Minor(stat.Dev), 1150 DevMajor: unix.Major(stat.Dev), 1151 Ino: stat.Ino, 1152 Nlink: uint32(stat.Nlink), 1153 UID: stat.Uid, 1154 GID: stat.Gid, 1155 RdevMinor: unix.Minor(stat.Rdev), 1156 RdevMajor: unix.Major(stat.Rdev), 1157 Size: uint64(stat.Size), 1158 Blksize: uint32(stat.Blksize), 1159 Blocks: uint64(stat.Blocks), 1160 Atime: linux.StatxTimestamp{ 1161 Sec: stat.Atim.Sec, 1162 Nsec: uint32(stat.Atim.Nsec), 1163 }, 1164 Mtime: linux.StatxTimestamp{ 1165 Sec: stat.Mtim.Sec, 1166 Nsec: uint32(stat.Mtim.Nsec), 1167 }, 1168 Ctime: linux.StatxTimestamp{ 1169 Sec: stat.Ctim.Sec, 1170 Nsec: uint32(stat.Ctim.Nsec), 1171 }, 1172 }, nil 1173 } 1174 1175 func checkSupportedFileType(mode uint32) error { 1176 switch mode & unix.S_IFMT { 1177 case unix.S_IFREG, unix.S_IFDIR, unix.S_IFLNK, unix.S_IFCHR, unix.S_IFSOCK, unix.S_IFIFO: 1178 return nil 1179 1180 default: 1181 return unix.EPERM 1182 } 1183 } 1184 1185 // extractErrno tries to determine the errno. 1186 func extractErrno(err error) unix.Errno { 1187 if err == nil { 1188 // This should never happen. The likely result will be that 1189 // some user gets the frustrating "error: SUCCESS" message. 1190 log.Warningf("extractErrno called with nil error!") 1191 return 0 1192 } 1193 1194 switch err { 1195 case os.ErrNotExist: 1196 return unix.ENOENT 1197 case os.ErrExist: 1198 return unix.EEXIST 1199 case os.ErrPermission: 1200 return unix.EACCES 1201 case os.ErrInvalid: 1202 return unix.EINVAL 1203 } 1204 1205 // See if it's an errno or a common wrapped error. 1206 switch e := err.(type) { 1207 case unix.Errno: 1208 return e 1209 case *os.PathError: 1210 return extractErrno(e.Err) 1211 case *os.LinkError: 1212 return extractErrno(e.Err) 1213 case *os.SyscallError: 1214 return extractErrno(e.Err) 1215 } 1216 1217 // Fall back to EIO. 1218 log.Debugf("Unknown error: %v, defaulting to EIO", err) 1219 return unix.EIO 1220 } 1221 1222 // LINT.ThenChange(../../pkg/sentry/fsimpl/gofer/directfs_dentry.go)