summaryrefslogtreecommitdiffhomepage
path: root/runsc/fsgofer/fsgofer.go
diff options
context:
space:
mode:
Diffstat (limited to 'runsc/fsgofer/fsgofer.go')
-rw-r--r--runsc/fsgofer/fsgofer.go224
1 files changed, 128 insertions, 96 deletions
diff --git a/runsc/fsgofer/fsgofer.go b/runsc/fsgofer/fsgofer.go
index 45b455430..60dad642f 100644
--- a/runsc/fsgofer/fsgofer.go
+++ b/runsc/fsgofer/fsgofer.go
@@ -27,6 +27,7 @@ import (
"os"
"path"
"path/filepath"
+ "runtime"
"sync"
"syscall"
@@ -122,13 +123,13 @@ func (a *attachPoint) Attach() (p9.File, error) {
if err != nil {
return nil, fmt.Errorf("stat file %q, err: %v", a.prefix, err)
}
- mode := os.O_RDWR
+ mode := syscall.O_RDWR
if a.conf.ROMount || stat.Mode&syscall.S_IFDIR != 0 {
- mode = os.O_RDONLY
+ mode = syscall.O_RDONLY
}
// Open the root directory.
- f, err := os.OpenFile(a.prefix, mode|openFlags, 0)
+ f, err := fd.Open(a.prefix, openFlags|mode, 0)
if err != nil {
return nil, fmt.Errorf("unable to open file %q, err: %v", a.prefix, err)
}
@@ -201,8 +202,9 @@ type localFile struct {
hostPath string
// file is opened when localFile is created and it's never nil. It may be
- // reopened...
- file *os.File
+ // reopened if the Open() mode is wider than the mode the file was originally
+ // opened with.
+ file *fd.FD
// mode is the mode in which the file was opened. Set to invalidMode
// if localFile isn't opened.
@@ -215,14 +217,10 @@ type localFile struct {
readDirMu sync.Mutex
}
-func openAnyFileFromParent(parent *localFile, name string) (*os.File, string, error) {
+func openAnyFileFromParent(parent *localFile, name string) (*fd.FD, string, error) {
path := path.Join(parent.hostPath, name)
- f, err := openAnyFile(path, func(mode int) (*os.File, error) {
- fd, err := syscall.Openat(parent.fd(), name, openFlags|mode, 0)
- if err != nil {
- return nil, err
- }
- return os.NewFile(uintptr(fd), path), nil
+ f, err := openAnyFile(path, func(mode int) (*fd.FD, error) {
+ return fd.OpenAt(parent.file, name, openFlags|mode, 0)
})
return f, path, err
}
@@ -230,7 +228,7 @@ func openAnyFileFromParent(parent *localFile, name string) (*os.File, string, er
// openAnyFile attempts to open the file in O_RDONLY and if it fails fallsback
// to O_PATH. 'path' is used for logging messages only. 'fn' is what does the
// actual file open and is customizable by the caller.
-func openAnyFile(path string, fn func(mode int) (*os.File, error)) (*os.File, error) {
+func openAnyFile(path string, fn func(mode int) (*fd.FD, error)) (*fd.FD, error) {
// Attempt to open file in the following mode in order:
// 1. RDONLY | NONBLOCK: for all files, works for directories and ro mounts too.
// Use non-blocking to prevent getting stuck inside open(2) for FIFOs. This option
@@ -239,7 +237,7 @@ func openAnyFile(path string, fn func(mode int) (*os.File, error)) (*os.File, er
modes := []int{syscall.O_RDONLY | syscall.O_NONBLOCK, unix.O_PATH}
var err error
- var file *os.File
+ var file *fd.FD
for i, mode := range modes {
file, err = fn(mode)
if err == nil {
@@ -279,7 +277,7 @@ func getSupportedFileType(stat syscall.Stat_t) (fileType, error) {
return ft, nil
}
-func newLocalFile(a *attachPoint, file *os.File, path string, stat syscall.Stat_t) (*localFile, error) {
+func newLocalFile(a *attachPoint, file *fd.FD, path string, stat syscall.Stat_t) (*localFile, error) {
ft, err := getSupportedFileType(stat)
if err != nil {
return nil, err
@@ -297,18 +295,22 @@ func newLocalFile(a *attachPoint, file *os.File, path string, stat syscall.Stat_
// newFDMaybe creates a fd.FD from a file, dup'ing the FD and setting it as
// non-blocking. If anything fails, returns nil. It's better to have a file
// without host FD, than to fail the operation.
-func newFDMaybe(file *os.File) *fd.FD {
- fd, err := fd.NewFromFile(file)
+func newFDMaybe(file *fd.FD) *fd.FD {
+ dupFD, err := syscall.Dup(file.FD())
+ // Technically, the runtime may call the finalizer on file as soon as
+ // FD() returns.
+ runtime.KeepAlive(file)
if err != nil {
return nil
}
+ dup := fd.New(dupFD)
// fd is blocking; non-blocking is required.
- if err := syscall.SetNonblock(fd.FD(), true); err != nil {
- fd.Close()
+ if err := syscall.SetNonblock(dup.FD(), true); err != nil {
+ dup.Close()
return nil
}
- return fd
+ return dup
}
func stat(fd int) (syscall.Stat_t, error) {
@@ -323,35 +325,30 @@ func fchown(fd int, uid p9.UID, gid p9.GID) error {
return syscall.Fchownat(fd, "", int(uid), int(gid), linux.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW)
}
-func (l *localFile) fd() int {
- return int(l.file.Fd())
-}
-
// Open implements p9.File.
func (l *localFile) Open(mode p9.OpenFlags) (*fd.FD, p9.QID, uint32, error) {
if l.isOpen() {
- panic(fmt.Sprintf("attempting to open already opened file: %q", l.file.Name()))
+ panic(fmt.Sprintf("attempting to open already opened file: %q", l.hostPath))
}
// Check if control file can be used or if a new open must be created.
- var newFile *os.File
+ var newFile *fd.FD
if mode == p9.ReadOnly {
- log.Debugf("Open reusing control file, mode: %v, %q", mode, l.file.Name())
+ log.Debugf("Open reusing control file, mode: %v, %q", mode, l.hostPath)
newFile = l.file
} else {
// Ideally reopen would call name_to_handle_at (with empty name) and
// open_by_handle_at to reopen the file without using 'hostPath'. However,
// name_to_handle_at and open_by_handle_at aren't supported by overlay2.
- log.Debugf("Open reopening file, mode: %v, %q", mode, l.file.Name())
+ log.Debugf("Open reopening file, mode: %v, %q", mode, l.hostPath)
var err error
-
- newFile, err = os.OpenFile(l.hostPath, openFlags|mode.OSFlags(), 0)
+ newFile, err = fd.Open(l.hostPath, openFlags|mode.OSFlags(), 0)
if err != nil {
return nil, p9.QID{}, 0, extractErrno(err)
}
}
- stat, err := stat(int(newFile.Fd()))
+ stat, err := stat(newFile.FD())
if err != nil {
if newFile != l.file {
newFile.Close()
@@ -368,7 +365,7 @@ func (l *localFile) Open(mode p9.OpenFlags) (*fd.FD, p9.QID, uint32, error) {
// Close old file in case a new one was created.
if newFile != l.file {
if err := l.file.Close(); err != nil {
- log.Warningf("Error closing file %q: %v", l.file.Name(), err)
+ log.Warningf("Error closing file %q: %v", l.hostPath, err)
}
l.file = newFile
}
@@ -396,33 +393,31 @@ func (l *localFile) Create(name string, mode p9.OpenFlags, perm p9.FileMode, uid
flags |= mode.OSFlags()
}
- fd, err := syscall.Openat(l.fd(), name, flags, uint32(perm.Permissions()))
+ child, err := fd.OpenAt(l.file, name, flags, uint32(perm.Permissions()))
if err != nil {
return nil, nil, p9.QID{}, 0, extractErrno(err)
}
cu := specutils.MakeCleanup(func() {
- syscall.Close(fd)
+ child.Close()
// Best effort attempt to remove the file in case of failure.
- if err := syscall.Unlinkat(l.fd(), name); err != nil {
+ if err := syscall.Unlinkat(l.file.FD(), name); err != nil {
log.Warningf("error unlinking file %q after failure: %v", path.Join(l.hostPath, name), err)
}
})
defer cu.Clean()
- if err := fchown(fd, uid, gid); err != nil {
+ if err := fchown(child.FD(), uid, gid); err != nil {
return nil, nil, p9.QID{}, 0, extractErrno(err)
}
- stat, err := stat(fd)
+ stat, err := stat(child.FD())
if err != nil {
return nil, nil, p9.QID{}, 0, extractErrno(err)
}
- cPath := path.Join(l.hostPath, name)
- f := os.NewFile(uintptr(fd), cPath)
c := &localFile{
attachPoint: l.attachPoint,
- hostPath: cPath,
- file: f,
+ hostPath: path.Join(l.hostPath, name),
+ file: child,
mode: mode,
}
@@ -440,12 +435,12 @@ func (l *localFile) Mkdir(name string, perm p9.FileMode, uid p9.UID, gid p9.GID)
return p9.QID{}, syscall.EBADF
}
- if err := syscall.Mkdirat(l.fd(), name, uint32(perm.Permissions())); err != nil {
+ if err := syscall.Mkdirat(l.file.FD(), name, uint32(perm.Permissions())); err != nil {
return p9.QID{}, extractErrno(err)
}
cu := specutils.MakeCleanup(func() {
// Best effort attempt to remove the dir in case of failure.
- if err := unix.Unlinkat(l.fd(), name, unix.AT_REMOVEDIR); err != nil {
+ if err := unix.Unlinkat(l.file.FD(), name, unix.AT_REMOVEDIR); err != nil {
log.Warningf("error unlinking dir %q after failure: %v", path.Join(l.hostPath, name), err)
}
})
@@ -453,16 +448,16 @@ func (l *localFile) Mkdir(name string, perm p9.FileMode, uid p9.UID, gid p9.GID)
// Open directory to change ownership and stat it.
flags := syscall.O_DIRECTORY | syscall.O_RDONLY | openFlags
- fd, err := syscall.Openat(l.fd(), name, flags, 0)
+ f, err := fd.OpenAt(l.file, name, flags, 0)
if err != nil {
return p9.QID{}, extractErrno(err)
}
- defer syscall.Close(fd)
+ defer f.Close()
- if err := fchown(fd, uid, gid); err != nil {
+ if err := fchown(f.FD(), uid, gid); err != nil {
return p9.QID{}, extractErrno(err)
}
- stat, err := stat(fd)
+ stat, err := stat(f.FD())
if err != nil {
return p9.QID{}, extractErrno(err)
}
@@ -475,25 +470,25 @@ func (l *localFile) Mkdir(name string, perm p9.FileMode, uid p9.UID, gid p9.GID)
func (l *localFile) Walk(names []string) ([]p9.QID, p9.File, error) {
// Duplicate current file if 'names' is empty.
if len(names) == 0 {
- var newFile *os.File
+ var newFile *fd.FD
if l.isOpen() {
// File mode may have changed when it was opened, so open a new one.
var err error
- newFile, err = openAnyFile(l.hostPath, func(mode int) (*os.File, error) {
- return os.OpenFile(l.hostPath, openFlags|mode, 0)
+ newFile, err = openAnyFile(l.hostPath, func(mode int) (*fd.FD, error) {
+ return fd.Open(l.hostPath, openFlags|mode, 0)
})
if err != nil {
return nil, nil, extractErrno(err)
}
} else {
- newFd, err := syscall.Dup(l.fd())
+ newFd, err := syscall.Dup(l.file.FD())
if err != nil {
return nil, nil, extractErrno(err)
}
- newFile = os.NewFile(uintptr(newFd), l.hostPath)
+ newFile = fd.New(newFd)
}
- stat, err := stat(int(newFile.Fd()))
+ stat, err := stat(int(newFile.FD()))
if err != nil {
newFile.Close()
return nil, nil, extractErrno(err)
@@ -515,7 +510,7 @@ func (l *localFile) Walk(names []string) ([]p9.QID, p9.File, error) {
if err != nil {
return nil, nil, extractErrno(err)
}
- stat, err := stat(int(f.Fd()))
+ stat, err := stat(f.FD())
if err != nil {
f.Close()
return nil, nil, extractErrno(err)
@@ -535,7 +530,7 @@ func (l *localFile) Walk(names []string) ([]p9.QID, p9.File, error) {
// StatFS implements p9.File.
func (l *localFile) StatFS() (p9.FSStat, error) {
var s syscall.Statfs_t
- if err := syscall.Fstatfs(l.fd(), &s); err != nil {
+ if err := syscall.Fstatfs(l.file.FD(), &s); err != nil {
return p9.FSStat{}, extractErrno(err)
}
@@ -557,7 +552,7 @@ func (l *localFile) FSync() error {
if !l.isOpen() {
return syscall.EBADF
}
- if err := l.file.Sync(); err != nil {
+ if err := syscall.Fsync(l.file.FD()); err != nil {
return extractErrno(err)
}
return nil
@@ -565,7 +560,7 @@ func (l *localFile) FSync() error {
// GetAttr implements p9.File.
func (l *localFile) GetAttr(_ p9.AttrMask) (p9.QID, p9.AttrMask, p9.Attr, error) {
- stat, err := stat(l.fd())
+ stat, err := stat(l.file.FD())
if err != nil {
return p9.QID{}, p9.AttrMask{}, p9.Attr{}, extractErrno(err)
}
@@ -633,20 +628,20 @@ func (l *localFile) SetAttr(valid p9.SetAttrMask, attr p9.SetAttr) error {
// Handle all the sanity checks up front so that the client gets a
// consistent result that is not attribute dependent.
if !valid.IsSubsetOf(allowed) {
- log.Warningf("SetAttr() failed for %q, mask: %v", l.file.Name(), valid)
+ log.Warningf("SetAttr() failed for %q, mask: %v", l.hostPath, valid)
return syscall.EPERM
}
// Check if it's possible to use cached file, or if another one needs to be
// opened for write.
- fd := l.fd()
+ f := l.file
if l.ft == regular && l.mode != p9.WriteOnly && l.mode != p9.ReadWrite {
- f, err := os.OpenFile(l.hostPath, openFlags|os.O_WRONLY, 0)
+ var err error
+ f, err = fd.Open(l.hostPath, openFlags|syscall.O_WRONLY, 0)
if err != nil {
return extractErrno(err)
}
defer f.Close()
- fd = int(f.Fd())
}
// The semantics are to either return an error if no changes were made,
@@ -661,14 +656,14 @@ func (l *localFile) SetAttr(valid p9.SetAttrMask, attr p9.SetAttr) error {
// over another.
var err error
if valid.Permissions {
- if cerr := syscall.Fchmod(fd, uint32(attr.Permissions)); cerr != nil {
+ if cerr := syscall.Fchmod(f.FD(), uint32(attr.Permissions)); cerr != nil {
log.Debugf("SetAttr fchmod failed %q, err: %v", l.hostPath, cerr)
err = extractErrno(cerr)
}
}
if valid.Size {
- if terr := syscall.Ftruncate(fd, int64(attr.Size)); terr != nil {
+ if terr := syscall.Ftruncate(f.FD(), int64(attr.Size)); terr != nil {
log.Debugf("SetAttr ftruncate failed %q, err: %v", l.hostPath, terr)
err = extractErrno(terr)
}
@@ -700,20 +695,20 @@ func (l *localFile) SetAttr(valid p9.SetAttrMask, attr p9.SetAttr) error {
// utimensat operates different that other syscalls. To operate on a
// symlink it *requires* AT_SYMLINK_NOFOLLOW with dirFD and a non-empty
// name.
- f, err := os.OpenFile(path.Dir(l.hostPath), openFlags|unix.O_PATH, 0)
+ parent, err := syscall.Open(path.Dir(l.hostPath), openFlags|unix.O_PATH, 0)
if err != nil {
return extractErrno(err)
}
- defer f.Close()
+ defer syscall.Close(parent)
- if terr := utimensat(int(f.Fd()), path.Base(l.hostPath), utimes, linux.AT_SYMLINK_NOFOLLOW); terr != nil {
+ if terr := utimensat(parent, path.Base(l.hostPath), utimes, linux.AT_SYMLINK_NOFOLLOW); terr != nil {
log.Debugf("SetAttr utimens failed %q, err: %v", l.hostPath, terr)
err = extractErrno(terr)
}
} else {
// Directories and regular files can operate directly on the fd
// using empty name.
- if terr := utimensat(fd, "", utimes, 0); terr != nil {
+ if terr := utimensat(f.FD(), "", utimes, 0); terr != nil {
log.Debugf("SetAttr utimens failed %q, err: %v", l.hostPath, terr)
err = extractErrno(terr)
}
@@ -729,7 +724,7 @@ func (l *localFile) SetAttr(valid p9.SetAttrMask, attr p9.SetAttr) error {
if valid.GID {
gid = int(attr.GID)
}
- if oerr := syscall.Fchownat(fd, "", uid, gid, linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW); oerr != nil {
+ if oerr := syscall.Fchownat(f.FD(), "", uid, gid, linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW); oerr != nil {
log.Debugf("SetAttr fchownat failed %q, err: %v", l.hostPath, oerr)
err = extractErrno(oerr)
}
@@ -754,7 +749,7 @@ func (l *localFile) RenameAt(oldName string, directory p9.File, newName string)
}
newParent := directory.(*localFile)
- if err := renameat(l.fd(), oldName, newParent.fd(), newName); err != nil {
+ if err := renameat(l.file.FD(), oldName, newParent.file.FD(), newName); err != nil {
return extractErrno(err)
}
return nil
@@ -804,28 +799,28 @@ func (l *localFile) Symlink(target, newName string, uid p9.UID, gid p9.GID) (p9.
return p9.QID{}, syscall.EBADF
}
- if err := unix.Symlinkat(target, l.fd(), newName); err != nil {
+ if err := unix.Symlinkat(target, l.file.FD(), newName); err != nil {
return p9.QID{}, extractErrno(err)
}
cu := specutils.MakeCleanup(func() {
// Best effort attempt to remove the symlink in case of failure.
- if err := syscall.Unlinkat(l.fd(), newName); err != nil {
+ if err := syscall.Unlinkat(l.file.FD(), newName); err != nil {
log.Warningf("error unlinking file %q after failure: %v", path.Join(l.hostPath, newName), err)
}
})
defer cu.Clean()
// Open symlink to change ownership and stat it.
- fd, err := syscall.Openat(l.fd(), newName, unix.O_PATH|openFlags, 0)
+ f, err := fd.OpenAt(l.file, newName, unix.O_PATH|openFlags, 0)
if err != nil {
return p9.QID{}, extractErrno(err)
}
- defer syscall.Close(fd)
+ defer f.Close()
- if err := fchown(fd, uid, gid); err != nil {
+ if err := fchown(f.FD(), uid, gid); err != nil {
return p9.QID{}, extractErrno(err)
}
- stat, err := stat(fd)
+ stat, err := stat(f.FD())
if err != nil {
return p9.QID{}, extractErrno(err)
}
@@ -845,7 +840,7 @@ func (l *localFile) Link(target p9.File, newName string) error {
}
targetFile := target.(*localFile)
- if err := unix.Linkat(targetFile.fd(), "", l.fd(), newName, linux.AT_EMPTY_PATH); err != nil {
+ if err := unix.Linkat(targetFile.file.FD(), "", l.file.FD(), newName, linux.AT_EMPTY_PATH); err != nil {
return extractErrno(err)
}
return nil
@@ -868,7 +863,7 @@ func (l *localFile) UnlinkAt(name string, flags uint32) error {
return syscall.EBADF
}
- if err := unix.Unlinkat(l.fd(), name, int(flags)); err != nil {
+ if err := unix.Unlinkat(l.file.FD(), name, int(flags)); err != nil {
return extractErrno(err)
}
return nil
@@ -887,30 +882,67 @@ func (l *localFile) Readdir(offset uint64, count uint32) ([]p9.Dirent, error) {
// reading all directory contents. Take a lock because this operation is
// stateful.
l.readDirMu.Lock()
- if _, err := l.file.Seek(0, 0); err != nil {
- l.readDirMu.Unlock()
+ defer l.readDirMu.Unlock()
+
+ if _, err := syscall.Seek(l.file.FD(), 0, 0); err != nil {
return nil, extractErrno(err)
}
- names, err := l.file.Readdirnames(-1)
- if err != nil {
- l.readDirMu.Unlock()
- return nil, extractErrno(err)
+
+ return l.readDirent(l.file.FD(), offset, count)
+}
+
+func (l *localFile) readDirent(f int, offset uint64, count uint32) ([]p9.Dirent, error) {
+ // Limit 'count' to cap the slice size that is returned.
+ const maxCount = 100000
+ if count > maxCount {
+ count = maxCount
}
- l.readDirMu.Unlock()
- var dirents []p9.Dirent
- for i := int(offset); i >= 0 && i < len(names); i++ {
- stat, err := statAt(l.fd(), names[i])
+ dirents := make([]p9.Dirent, 0, count)
+
+ // Pre-allocate buffers that will be reused to get partial results.
+ direntsBuf := make([]byte, 8192)
+ names := make([]string, 0, 100)
+
+ skip := offset // Tracks the number of entries to skip.
+ end := offset + uint64(count)
+ for offset < end {
+ dirSize, err := syscall.ReadDirent(f, direntsBuf)
if err != nil {
- continue
+ return dirents, err
+ }
+ if dirSize <= 0 {
+ return dirents, nil // EOF
+ }
+
+ names := names[:0]
+ _, _, names = syscall.ParseDirent(direntsBuf[:dirSize], -1, names)
+
+ // Skip over entries that the caller is not interested in.
+ if skip > 0 {
+ if skip > uint64(len(names)) {
+ skip -= uint64(len(names))
+ names = names[:0]
+ } else {
+ names = names[skip:]
+ skip = 0
+ }
+ }
+ for _, name := range names {
+ stat, err := statAt(l.file.FD(), name)
+ if err != nil {
+ log.Warningf("Readdir is skipping file with failed stat %q, err: %v", l.hostPath, err)
+ continue
+ }
+ qid := l.attachPoint.makeQID(stat)
+ offset++
+ dirents = append(dirents, p9.Dirent{
+ QID: qid,
+ Type: qid.Type,
+ Name: name,
+ Offset: offset,
+ })
}
- qid := l.attachPoint.makeQID(stat)
- dirents = append(dirents, p9.Dirent{
- QID: qid,
- Type: qid.Type,
- Name: names[i],
- Offset: uint64(i + 1),
- })
}
return dirents, nil
}
@@ -921,7 +953,7 @@ func (l *localFile) Readlink() (string, error) {
const limit = 1024 * 1024
for len := 128; len < limit; len *= 2 {
b := make([]byte, len)
- n, err := unix.Readlinkat(l.fd(), "", b)
+ n, err := unix.Readlinkat(l.file.FD(), "", b)
if err != nil {
return "", extractErrno(err)
}