tableflip/fds.go

351 lines
7.1 KiB
Go

package tableflip
import (
"net"
"os"
"strings"
"sync"
"syscall"
"github.com/pkg/errors"
)
// Listener can be shared between processes.
type Listener interface {
net.Listener
syscall.Conn
}
// Conn can be shared between processes.
type Conn interface {
net.Conn
syscall.Conn
}
const (
listenKind = "listener"
connKind = "conn"
fdKind = "fd"
)
type fileName [3]string
func (name fileName) String() string {
return strings.Join(name[:], ":")
}
func (name fileName) isUnixListener() bool {
return name[0] == listenKind && (name[1] == "unix" || name[1] == "unixpacket")
}
// file works around the fact that it's not possible
// to get the fd from an os.File without putting it into
// blocking mode.
type file struct {
*os.File
fd uintptr
}
func newFile(fd uintptr, name fileName) *file {
f := os.NewFile(fd, name.String())
if f == nil {
return nil
}
return &file{
f,
fd,
}
}
// Fds holds all file descriptors inherited from the
// parent process.
type Fds struct {
mu sync.Mutex
// NB: Files in these maps may be in blocking mode.
inherited map[fileName]*file
used map[fileName]*file
}
func newFds(inherited map[fileName]*file) *Fds {
if inherited == nil {
inherited = make(map[fileName]*file)
}
return &Fds{
inherited: inherited,
used: make(map[fileName]*file),
}
}
// Listen returns a listener inherited from the parent process, or creates a new one.
func (f *Fds) Listen(network, addr string) (net.Listener, error) {
f.mu.Lock()
defer f.mu.Unlock()
ln, err := f.listenerLocked(network, addr)
if err != nil {
return nil, err
}
if ln != nil {
return ln, nil
}
ln, err = net.Listen(network, addr)
if err != nil {
return nil, errors.Wrap(err, "can't create new listener")
}
if _, ok := ln.(Listener); !ok {
ln.Close()
return nil, errors.Errorf("%T doesn't implement tableflip.Listener", ln)
}
err = f.addListenerLocked(network, addr, ln.(Listener))
if err != nil {
ln.Close()
return nil, err
}
return ln, nil
}
// Listener returns an inherited listener or nil.
//
// It is safe to close the returned listener.
func (f *Fds) Listener(network, addr string) (net.Listener, error) {
f.mu.Lock()
defer f.mu.Unlock()
return f.listenerLocked(network, addr)
}
func (f *Fds) listenerLocked(network, addr string) (net.Listener, error) {
key := fileName{listenKind, network, addr}
file := f.inherited[key]
if file == nil {
return nil, nil
}
ln, err := net.FileListener(file.File)
if err != nil {
return nil, errors.Wrapf(err, "can't inherit listener %s %s", network, addr)
}
delete(f.inherited, key)
f.used[key] = file
return ln, nil
}
// AddListener adds a listener.
//
// It is safe to close ln after calling the method.
// Any existing listener with the same address is overwitten.
func (f *Fds) AddListener(network, addr string, ln Listener) error {
f.mu.Lock()
defer f.mu.Unlock()
return f.addListenerLocked(network, addr, ln)
}
type unlinkOnCloser interface {
SetUnlinkOnClose(bool)
}
func (f *Fds) addListenerLocked(network, addr string, ln Listener) error {
if ifc, ok := ln.(unlinkOnCloser); ok {
ifc.SetUnlinkOnClose(false)
}
return f.addConnLocked(listenKind, network, addr, ln)
}
// Conn returns an inherited connection or nil.
//
// It is safe to close the returned Conn.
func (f *Fds) Conn(network, addr string) (net.Conn, error) {
f.mu.Lock()
defer f.mu.Unlock()
key := fileName{connKind, network, addr}
file := f.inherited[key]
if file == nil {
return nil, nil
}
conn, err := net.FileConn(file.File)
if err != nil {
return nil, errors.Wrapf(err, "can't inherit connection %s %s", network, addr)
}
delete(f.inherited, key)
f.used[key] = file
return conn, nil
}
// AddConn adds a connection.
//
// It is safe to close conn after calling this method.
func (f *Fds) AddConn(network, addr string, conn Conn) error {
f.mu.Lock()
defer f.mu.Unlock()
return f.addConnLocked(connKind, network, addr, conn)
}
func (f *Fds) addConnLocked(kind, network, addr string, conn syscall.Conn) error {
key := fileName{kind, network, addr}
file, err := dupConn(conn, key)
if err != nil {
return errors.Wrapf(err, "can't dup %s (%s %s)", kind, network, addr)
}
delete(f.inherited, key)
f.used[key] = file
return nil
}
// File returns an inherited file or nil.
//
// The descriptor may be in blocking mode.
func (f *Fds) File(name string) (*os.File, error) {
f.mu.Lock()
defer f.mu.Unlock()
key := fileName{fdKind, name}
file := f.inherited[key]
if file == nil {
return nil, nil
}
// Make a copy of the file, since we don't want to
// allow the caller to invalidate fds in f.inherited.
dup, err := dupFd(file.fd, key)
if err != nil {
return nil, err
}
delete(f.inherited, key)
f.used[key] = file
return dup.File, nil
}
// AddFile adds a file.
//
// Until Go 1.12, file will be in blocking mode
// after this call.
func (f *Fds) AddFile(name string, file *os.File) error {
key := fileName{fdKind, name}
dup, err := dupFile(file, key)
if err != nil {
return err
}
f.mu.Lock()
defer f.mu.Unlock()
delete(f.inherited, key)
f.used[key] = dup
return nil
}
func (f *Fds) copy() map[fileName]*file {
f.mu.Lock()
defer f.mu.Unlock()
files := make(map[fileName]*file, len(f.used))
for key, file := range f.used {
files[key] = file
}
return files
}
func (f *Fds) closeInherited() {
f.mu.Lock()
defer f.mu.Unlock()
for key, file := range f.inherited {
if key.isUnixListener() {
// Remove inherited but unused Unix sockets from the file system.
// This undoes the effect of SetUnlinkOnClose(false).
_ = unlinkUnixSocket(key[2])
}
_ = file.Close()
}
f.inherited = make(map[fileName]*file)
}
func unlinkUnixSocket(path string) error {
if strings.HasPrefix(path, "@") {
// Don't unlink sockets using the abstract namespace.
return nil
}
info, err := os.Stat(path)
if err != nil {
return err
}
if info.Mode()&os.ModeSocket == 0 {
return nil
}
return os.Remove(path)
}
func (f *Fds) closeUsed() {
f.mu.Lock()
defer f.mu.Unlock()
for _, file := range f.used {
_ = file.Close()
}
f.used = make(map[fileName]*file)
}
func (f *Fds) closeAndRemoveUsed() {
f.mu.Lock()
defer f.mu.Unlock()
for key, file := range f.used {
if key.isUnixListener() {
// Remove used Unix Domain Sockets if we are shutting
// down without having done an upgrade.
// This undoes the effect of SetUnlinkOnClose(false).
_ = unlinkUnixSocket(key[2])
}
_ = file.Close()
}
f.used = make(map[fileName]*file)
}
func dupConn(conn syscall.Conn, name fileName) (*file, error) {
// Use SyscallConn instead of File to avoid making the original
// fd non-blocking.
raw, err := conn.SyscallConn()
if err != nil {
return nil, err
}
var dup *file
var duperr error
err = raw.Control(func(fd uintptr) {
dup, duperr = dupFd(fd, name)
})
if err != nil {
return nil, errors.Wrap(err, "can't access fd")
}
return dup, duperr
}
func dupFd(fd uintptr, name fileName) (*file, error) {
dupfd, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_DUPFD_CLOEXEC, 0)
if errno != 0 {
return nil, errors.Wrap(errno, "can't dup fd using fcntl")
}
return newFile(dupfd, name), nil
}