internal/safepath: Handle EINTR in unix syscalls

Handle EINTR by retrying the syscall.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski 2023-09-12 11:29:06 +02:00
parent 9a0cde66ba
commit 3784316d46
No known key found for this signature in database
GPG key ID: B85EFCFE26DEF92A
2 changed files with 93 additions and 8 deletions

View file

@ -8,7 +8,7 @@ import (
"strconv"
"github.com/containerd/log"
"github.com/moby/sys/mount"
"github.com/docker/docker/internal/unix_noeintr"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
@ -33,7 +33,7 @@ func Join(path, subpath string) (*SafePath, error) {
return nil, err
}
defer unix.Close(fd)
defer unix_noeintr.Close(fd)
tmpMount, err := tempMountPoint(fd)
if err != nil {
@ -47,7 +47,7 @@ func Join(path, subpath string) (*SafePath, error) {
// TODO(vvoland): Investigate.
mountSource := "/proc/" + pid + "/fd/" + strconv.Itoa(fd)
if err := unix.Mount(mountSource, tmpMount, "none", unix.MS_BIND, ""); err != nil {
if err := unix_noeintr.Mount(mountSource, tmpMount, "none", unix.MS_BIND, ""); err != nil {
os.Remove(tmpMount)
return nil, errors.Wrap(err, "failed to mount resolved path")
}
@ -69,14 +69,14 @@ func Join(path, subpath string) (*SafePath, error) {
// error was returned.
func safeOpenFd(path, subpath string) (int, error) {
// Open base volume path (_data directory).
prevFd, err := unix.Open(path, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
prevFd, err := unix_noeintr.Open(path, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
if err != nil {
return -1, &ErrNotAccessible{Path: path, Cause: err}
}
defer unix.Close(prevFd)
defer unix_noeintr.Close(prevFd)
// Try to use the Openat2 syscall first (available on Linux 5.6+).
fd, err := unix.Openat2(prevFd, subpath, &unix.OpenHow{
fd, err := unix_noeintr.Openat2(prevFd, subpath, &unix.OpenHow{
Flags: unix.O_PATH | unix.O_CLOEXEC,
Mode: 0,
Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_MAGICLINKS | unix.RESOLVE_NO_SYMLINKS,
@ -106,7 +106,7 @@ func softOpenat2(baseFd int, subpath string) (int, error) {
// point for the file descriptor.
func tempMountPoint(sourceFd int) (string, error) {
var stat unix.Stat_t
err := unix.Fstat(sourceFd, &stat)
err := unix_noeintr.Fstat(sourceFd, &stat)
if err != nil {
return "", errors.Wrap(err, "failed to Fstat mount source fd")
}
@ -134,7 +134,7 @@ func cleanupSafePath(path string) func() error {
return func() error {
log.G(context.TODO()).WithField("path", path).Debug("removing safe temp mount")
if err := unix.Unmount(path, unix.MNT_DETACH); err != nil {
if err := unix_noeintr.Unmount(path, unix.MNT_DETACH); err != nil {
if errors.Is(err, unix.EINVAL) {
log.G(context.TODO()).WithField("path", path).Warn("safe temp mount no longer exists?")
return nil

View file

@ -0,0 +1,85 @@
//go:build !windows
// Wrappers for unix syscalls that retry on EINTR
// TODO: Consider moving (for example to moby/sys) and making the wrappers
// auto-generated.
package unix_noeintr
import (
"errors"
"golang.org/x/sys/unix"
)
func Retry(f func() error) {
for {
err := f()
if !errors.Is(err, unix.EINTR) {
return
}
}
}
func Mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
Retry(func() error {
err = unix.Mount(source, target, fstype, flags, data)
return err
})
return
}
func Unmount(target string, flags int) (err error) {
Retry(func() error {
err = unix.Unmount(target, flags)
return err
})
return
}
func Open(path string, mode int, perm uint32) (fd int, err error) {
Retry(func() error {
fd, err = unix.Open(path, mode, perm)
return err
})
return
}
func Close(fd int) (err error) {
Retry(func() error {
err = unix.Close(fd)
return err
})
return
}
func Openat(dirfd int, path string, mode int, perms uint32) (fd int, err error) {
Retry(func() error {
fd, err = unix.Openat(dirfd, path, mode, perms)
return err
})
return
}
func Openat2(dirfd int, path string, how *unix.OpenHow) (fd int, err error) {
Retry(func() error {
fd, err = unix.Openat2(dirfd, path, how)
return err
})
return
}
func Fstat(fd int, stat *unix.Stat_t) (err error) {
Retry(func() error {
err = unix.Fstat(fd, stat)
return err
})
return
}
func Fstatat(fd int, path string, stat *unix.Stat_t, flags int) (err error) {
Retry(func() error {
err = unix.Fstatat(fd, path, stat, flags)
return err
})
return
}