summaryrefslogtreecommitdiffhomepage
path: root/pkg/fd
diff options
context:
space:
mode:
authorGoogler <noreply@google.com>2018-04-27 10:37:02 -0700
committerAdin Scannell <ascannell@google.com>2018-04-28 01:44:26 -0400
commitd02b74a5dcfed4bfc8f2f8e545bca4d2afabb296 (patch)
tree54f95eef73aee6bacbfc736fffc631be2605ed53 /pkg/fd
parentf70210e742919f40aa2f0934a22f1c9ba6dada62 (diff)
Check in gVisor.
PiperOrigin-RevId: 194583126 Change-Id: Ica1d8821a90f74e7e745962d71801c598c652463
Diffstat (limited to 'pkg/fd')
-rw-r--r--pkg/fd/BUILD17
-rw-r--r--pkg/fd/fd.go213
-rw-r--r--pkg/fd/fd_test.go136
3 files changed, 366 insertions, 0 deletions
diff --git a/pkg/fd/BUILD b/pkg/fd/BUILD
new file mode 100644
index 000000000..e69d83d06
--- /dev/null
+++ b/pkg/fd/BUILD
@@ -0,0 +1,17 @@
+package(licenses = ["notice"]) # Apache 2.0
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+ name = "fd",
+ srcs = ["fd.go"],
+ importpath = "gvisor.googlesource.com/gvisor/pkg/fd",
+ visibility = ["//visibility:public"],
+)
+
+go_test(
+ name = "fd_test",
+ size = "small",
+ srcs = ["fd_test.go"],
+ embed = [":fd"],
+)
diff --git a/pkg/fd/fd.go b/pkg/fd/fd.go
new file mode 100644
index 000000000..32d24c41b
--- /dev/null
+++ b/pkg/fd/fd.go
@@ -0,0 +1,213 @@
+// Copyright 2018 Google Inc.
+//
+// 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)
+}
diff --git a/pkg/fd/fd_test.go b/pkg/fd/fd_test.go
new file mode 100644
index 000000000..94b3eb7cc
--- /dev/null
+++ b/pkg/fd/fd_test.go
@@ -0,0 +1,136 @@
+// Copyright 2018 Google Inc.
+//
+// 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
+
+import (
+ "math"
+ "os"
+ "syscall"
+ "testing"
+)
+
+func TestSetNegOne(t *testing.T) {
+ type entry struct {
+ name string
+ file *FD
+ fn func() error
+ }
+ var tests []entry
+
+ fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
+ if err != nil {
+ t.Fatal("syscall.Socket:", err)
+ }
+ f1 := New(fd)
+ tests = append(tests, entry{
+ "Release",
+ f1,
+ func() error {
+ return syscall.Close(f1.Release())
+ },
+ })
+
+ fd, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
+ if err != nil {
+ t.Fatal("syscall.Socket:", err)
+ }
+ f2 := New(fd)
+ tests = append(tests, entry{
+ "Close",
+ f2,
+ f2.Close,
+ })
+
+ for _, test := range tests {
+ if err := test.fn(); err != nil {
+ t.Errorf("%s: %v", test.name, err)
+ continue
+ }
+ if fd := test.file.FD(); fd != -1 {
+ t.Errorf("%s: got FD() = %d, want = -1", test.name, fd)
+ }
+ }
+}
+
+func TestStartsNegOne(t *testing.T) {
+ type entry struct {
+ name string
+ file *FD
+ }
+
+ tests := []entry{
+ {"-1", New(-1)},
+ {"-2", New(-2)},
+ {"MinInt32", New(math.MinInt32)},
+ {"MinInt64", New(math.MinInt64)},
+ }
+
+ for _, test := range tests {
+ if fd := test.file.FD(); fd != -1 {
+ t.Errorf("%s: got FD() = %d, want = -1", test.name, fd)
+ }
+ }
+}
+
+func TestFileDotFile(t *testing.T) {
+ fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
+ if err != nil {
+ t.Fatal("syscall.Socket:", err)
+ }
+
+ f := New(fd)
+ of, err := f.File()
+ if err != nil {
+ t.Fatalf("File got err %v want nil", err)
+ }
+
+ if ofd, nfd := int(of.Fd()), f.FD(); ofd == nfd || ofd == -1 {
+ // Try not to double close the FD.
+ f.Release()
+
+ t.Fatalf("got %#v.File().Fd() = %d, want new FD", f, ofd)
+ }
+
+ f.Close()
+ of.Close()
+}
+
+func TestFileDotFileError(t *testing.T) {
+ f := &FD{ReadWriter{-2}}
+
+ if of, err := f.File(); err == nil {
+ t.Errorf("File %v got nil err want non-nil", of)
+ of.Close()
+ }
+}
+
+func TestNewFromFile(t *testing.T) {
+ f, err := NewFromFile(os.Stdin)
+ if err != nil {
+ t.Fatalf("NewFromFile got err %v want nil", err)
+ }
+ if nfd, ofd := f.FD(), int(os.Stdin.Fd()); nfd == -1 || nfd == ofd {
+ t.Errorf("got FD() = %d, want = new FD (old FD was %d)", nfd, ofd)
+ }
+ f.Close()
+}
+
+func TestNewFromFileError(t *testing.T) {
+ f, err := NewFromFile(nil)
+ if err == nil {
+ t.Errorf("NewFromFile got %v with nil err want non-nil", f)
+ f.Close()
+ }
+}