gvisor/pkg/fd/fd.go

214 lines
5.6 KiB
Go

// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package fd provides types for working with file descriptors.
package fd
import (
"fmt"
"io"
"os"
"runtime"
"sync/atomic"
"syscall"
)
// ReadWriter implements io.ReadWriter, io.ReaderAt, and io.WriterAt for fd. It
// does not take ownership of fd.
type ReadWriter struct {
// fd is accessed atomically so FD.Close/Release can swap it.
fd int64
}
var _ io.ReadWriter = (*ReadWriter)(nil)
var _ io.ReaderAt = (*ReadWriter)(nil)
var _ io.WriterAt = (*ReadWriter)(nil)
// NewReadWriter creates a ReadWriter for fd.
func NewReadWriter(fd int) *ReadWriter {
return &ReadWriter{int64(fd)}
}
func fixCount(n int, err error) (int, error) {
if n < 0 {
n = 0
}
return n, err
}
// Read implements io.Reader.
func (r *ReadWriter) Read(b []byte) (int, error) {
c, err := fixCount(syscall.Read(int(atomic.LoadInt64(&r.fd)), b))
if c == 0 && len(b) > 0 && err == nil {
return 0, io.EOF
}
return c, err
}
// ReadAt implements io.ReaderAt.
//
// ReadAt always returns a non-nil error when c < len(b).
func (r *ReadWriter) ReadAt(b []byte, off int64) (c int, err error) {
for len(b) > 0 {
var m int
m, err = fixCount(syscall.Pread(int(atomic.LoadInt64(&r.fd)), b, off))
if m == 0 && err == nil {
return c, io.EOF
}
if err != nil {
return c, err
}
c += m
b = b[m:]
off += int64(m)
}
return
}
// Write implements io.Writer.
func (r *ReadWriter) Write(b []byte) (int, error) {
var err error
var n, remaining int
for remaining = len(b); remaining > 0; {
woff := len(b) - remaining
n, err = syscall.Write(int(atomic.LoadInt64(&r.fd)), b[woff:])
if n > 0 {
// syscall.Write wrote some bytes. This is the common case.
remaining -= n
} else {
if err == nil {
// syscall.Write did not write anything nor did it return an error.
//
// There is no way to guarantee that a subsequent syscall.Write will
// make forward progress so just panic.
panic(fmt.Sprintf("syscall.Write returned %d with no error", n))
}
if err != syscall.EINTR {
// If the write failed for anything other than a signal, bail out.
break
}
}
}
return len(b) - remaining, err
}
// WriteAt implements io.WriterAt.
func (r *ReadWriter) WriteAt(b []byte, off int64) (c int, err error) {
for len(b) > 0 {
var m int
m, err = fixCount(syscall.Pwrite(int(atomic.LoadInt64(&r.fd)), b, off))
if err != nil {
break
}
c += m
b = b[m:]
off += int64(m)
}
return
}
// FD owns a host file descriptor.
//
// It is similar to os.File, with a few important distinctions:
//
// FD provies a Release() method which relinquishes ownership. Like os.File,
// FD adds a finalizer to close the backing FD. However, the finalizer cannot
// be removed from os.File, forever pinning the lifetime of an FD to its
// os.File.
//
// FD supports both blocking and non-blocking operation. os.File only
// supports blocking operation.
type FD struct {
ReadWriter
}
// New creates a new FD.
//
// New takes ownership of fd.
func New(fd int) *FD {
if fd < 0 {
return &FD{ReadWriter{-1}}
}
f := &FD{ReadWriter{int64(fd)}}
runtime.SetFinalizer(f, (*FD).Close)
return f
}
// NewFromFile creates a new FD from an os.File.
//
// NewFromFile does not transfer ownership of the file descriptor (it will be
// duplicated, so both the os.File and FD will eventually need to be closed
// and some (but not all) changes made to the FD will be applied to the
// os.File as well).
//
// The returned FD is always blocking (Go 1.9+).
func NewFromFile(file *os.File) (*FD, error) {
fd, err := syscall.Dup(int(file.Fd()))
if err != nil {
return &FD{ReadWriter{-1}}, err
}
return New(fd), nil
}
// Close closes the file descriptor contained in the FD.
//
// Close is safe to call multiple times, but will return an error after the
// first call.
//
// Concurrently calling Close and any other method is undefined.
func (f *FD) Close() error {
runtime.SetFinalizer(f, nil)
return syscall.Close(int(atomic.SwapInt64(&f.fd, -1)))
}
// Release relinquishes ownership of the contained file descriptor.
//
// Concurrently calling Release and any other method is undefined.
func (f *FD) Release() int {
runtime.SetFinalizer(f, nil)
return int(atomic.SwapInt64(&f.fd, -1))
}
// FD returns the file descriptor owned by FD. FD retains ownership.
func (f *FD) FD() int {
return int(atomic.LoadInt64(&f.fd))
}
// File converts the FD to an os.File.
//
// FD does not transfer ownership of the file descriptor (it will be
// duplicated, so both the FD and os.File will eventually need to be closed
// and some (but not all) changes made to the os.File will be applied to the
// FD as well).
//
// This operation is somewhat expensive, so care should be taken to minimize
// its use.
func (f *FD) File() (*os.File, error) {
fd, err := syscall.Dup(int(atomic.LoadInt64(&f.fd)))
if err != nil {
return nil, err
}
return os.NewFile(uintptr(fd), ""), nil
}
// ReleaseToFile returns an os.File that takes ownership of the FD.
//
// name is passed to os.NewFile.
func (f *FD) ReleaseToFile(name string) *os.File {
return os.NewFile(uintptr(f.Release()), name)
}