diff options
author | Googler <noreply@google.com> | 2018-04-27 10:37:02 -0700 |
---|---|---|
committer | Adin Scannell <ascannell@google.com> | 2018-04-28 01:44:26 -0400 |
commit | d02b74a5dcfed4bfc8f2f8e545bca4d2afabb296 (patch) | |
tree | 54f95eef73aee6bacbfc736fffc631be2605ed53 /pkg/fd | |
parent | f70210e742919f40aa2f0934a22f1c9ba6dada62 (diff) |
Check in gVisor.
PiperOrigin-RevId: 194583126
Change-Id: Ica1d8821a90f74e7e745962d71801c598c652463
Diffstat (limited to 'pkg/fd')
-rw-r--r-- | pkg/fd/BUILD | 17 | ||||
-rw-r--r-- | pkg/fd/fd.go | 213 | ||||
-rw-r--r-- | pkg/fd/fd_test.go | 136 |
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() + } +} |