From b461fe660d82b3f3f21cd4042e0f4d3f800aac6c Mon Sep 17 00:00:00 2001 From: Akshat Kumar Date: Tue, 26 Feb 2013 01:26:40 +0100 Subject: [PATCH] net: Implement FileListener, FileConn, and File methods for Plan 9 Functions for representing network connections as files and vice versa, on Plan 9. Representing network connections as files is not so straight-forward, because a network connection on Plan 9 is represented by a host of files rather than a single file descriptor (as is the case on UNIX). We use the type system to distinguish between listeners and connections, returning the control file in the former case and the data file in the latter case. R=rsc, rminnich, ality, akumar, bradfitz CC=golang-dev https://golang.org/cl/7235068 --- src/pkg/net/fd_plan9.go | 23 ++++++- src/pkg/net/file_plan9.go | 127 ++++++++++++++++++++++++++++++++++- src/pkg/net/tcpsock_plan9.go | 2 +- 3 files changed, 148 insertions(+), 4 deletions(-) diff --git a/src/pkg/net/fd_plan9.go b/src/pkg/net/fd_plan9.go index dc5e44ca441..169087999d5 100644 --- a/src/pkg/net/fd_plan9.go +++ b/src/pkg/net/fd_plan9.go @@ -83,8 +83,29 @@ func (fd *netFD) Close() error { return err } +// This method is only called via Conn. func (fd *netFD) dup() (*os.File, error) { - return nil, syscall.EPLAN9 + if !fd.ok() || fd.data == nil { + return nil, syscall.EINVAL + } + return fd.file(fd.data, fd.dir+"/data") +} + +func (l *TCPListener) dup() (*os.File, error) { + if !l.fd.ok() { + return nil, syscall.EINVAL + } + return l.fd.file(l.fd.ctl, l.fd.dir+"/ctl") +} + +func (fd *netFD) file(f *os.File, s string) (*os.File, error) { + syscall.ForkLock.RLock() + dfd, err := syscall.Dup(int(f.Fd()), -1) + syscall.ForkLock.RUnlock() + if err != nil { + return nil, &OpError{"dup", s, fd.laddr, err} + } + return os.NewFile(uintptr(dfd), s), nil } func setDeadline(fd *netFD, t time.Time) error { diff --git a/src/pkg/net/file_plan9.go b/src/pkg/net/file_plan9.go index ae3ac156b98..f6ee1c29e0f 100644 --- a/src/pkg/net/file_plan9.go +++ b/src/pkg/net/file_plan9.go @@ -5,16 +5,139 @@ package net import ( + "errors" + "io" "os" "syscall" ) +func (fd *netFD) status(ln int) (string, error) { + if !fd.ok() { + return "", syscall.EINVAL + } + + status, err := os.Open(fd.dir + "/status") + if err != nil { + return "", err + } + defer status.Close() + buf := make([]byte, ln) + n, err := io.ReadFull(status, buf[:]) + if err != nil { + return "", err + } + return string(buf[:n]), nil +} + +func newFileFD(f *os.File) (net *netFD, err error) { + var ctl *os.File + close := func(fd int) { + if err != nil { + syscall.Close(fd) + } + } + + path, err := syscall.Fd2path(int(f.Fd())) + if err != nil { + return nil, os.NewSyscallError("fd2path", err) + } + comp := splitAtBytes(path, "/") + n := len(comp) + if n < 3 || comp[0] != "net" { + return nil, syscall.EPLAN9 + } + + name := comp[2] + switch file := comp[n-1]; file { + case "ctl", "clone": + syscall.ForkLock.RLock() + fd, err := syscall.Dup(int(f.Fd()), -1) + syscall.ForkLock.RUnlock() + if err != nil { + return nil, os.NewSyscallError("dup", err) + } + defer close(fd) + + dir := "/net/" + comp[n-2] + ctl = os.NewFile(uintptr(fd), dir+"/"+file) + ctl.Seek(0, 0) + var buf [16]byte + n, err := ctl.Read(buf[:]) + if err != nil { + return nil, err + } + name = string(buf[:n]) + default: + if len(comp) < 4 { + return nil, errors.New("could not find control file for connection") + } + dir := "/net/" + comp[1] + "/" + name + ctl, err = os.OpenFile(dir+"/ctl", os.O_RDWR, 0) + if err != nil { + return nil, err + } + defer close(int(ctl.Fd())) + } + dir := "/net/" + comp[1] + "/" + name + laddr, err := readPlan9Addr(comp[1], dir+"/local") + if err != nil { + return nil, err + } + return newFD(comp[1], name, ctl, nil, laddr, nil), nil +} + +func newFileConn(f *os.File) (c Conn, err error) { + fd, err := newFileFD(f) + if err != nil { + return nil, err + } + if !fd.ok() { + return nil, syscall.EINVAL + } + + fd.data, err = os.OpenFile(fd.dir+"/data", os.O_RDWR, 0) + if err != nil { + return nil, err + } + + switch fd.laddr.(type) { + case *TCPAddr: + return newTCPConn(fd), nil + case *UDPAddr: + return newUDPConn(fd), nil + } + return nil, syscall.EPLAN9 +} + +func newFileListener(f *os.File) (l Listener, err error) { + fd, err := newFileFD(f) + if err != nil { + return nil, err + } + switch fd.laddr.(type) { + case *TCPAddr: + default: + return nil, syscall.EPLAN9 + } + + // check that file corresponds to a listener + s, err := fd.status(len("Listen")) + if err != nil { + return nil, err + } + if s != "Listen" { + return nil, errors.New("file does not represent a listener") + } + + return &TCPListener{fd}, nil +} + // FileConn returns a copy of the network connection corresponding to // the open file f. It is the caller's responsibility to close f when // finished. Closing c does not affect f, and closing f does not // affect c. func FileConn(f *os.File) (c Conn, err error) { - return nil, syscall.EPLAN9 + return newFileConn(f) } // FileListener returns a copy of the network listener corresponding @@ -22,7 +145,7 @@ func FileConn(f *os.File) (c Conn, err error) { // when finished. Closing l does not affect f, and closing f does not // affect l. func FileListener(f *os.File) (l Listener, err error) { - return nil, syscall.EPLAN9 + return newFileListener(f) } // FilePacketConn returns a copy of the packet network connection diff --git a/src/pkg/net/tcpsock_plan9.go b/src/pkg/net/tcpsock_plan9.go index 26da11a061f..e0dd37f40f3 100644 --- a/src/pkg/net/tcpsock_plan9.go +++ b/src/pkg/net/tcpsock_plan9.go @@ -161,7 +161,7 @@ func (l *TCPListener) SetDeadline(t time.Time) error { // File returns a copy of the underlying os.File, set to blocking // mode. It is the caller's responsibility to close f when finished. // Closing l does not affect f, and closing f does not affect l. -func (l *TCPListener) File() (f *os.File, err error) { return l.fd.dup() } +func (l *TCPListener) File() (f *os.File, err error) { return l.dup() } // ListenTCP announces on the TCP address laddr and returns a TCP // listener. Net must be "tcp", "tcp4", or "tcp6". If laddr has a