ogle: framework for communicating with proxy
Interface definition for interacting with program:
        program/program.go
Beginnings of RPC server to provide access to program:
        program/server/server.go
Beginnings of client to talk to RPC server, plus SSH connection:
        program/client/client.go
Beginnings of ogleproxy, which implements the RPC service:
        cmd/ogleproxy/main.go
Transport definitions for RPC:
        program/proxyrpc/proxyrpc.go

No tests yet - they will come when there's a little more in place.
But it works, as tested by an external toy.

LGTM=nigeltao
R=nigeltao
https://golang.org/cl/77080043
diff --git a/cmd/ogleproxy/main.go b/cmd/ogleproxy/main.go
new file mode 100644
index 0000000..3ce343a
--- /dev/null
+++ b/cmd/ogleproxy/main.go
@@ -0,0 +1,75 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The ogleproxy connects to the target binary and serves an RPC
+// interface to access and control it.
+package main
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"net/rpc"
+	"os"
+
+	"code.google.com/p/ogle/program/server"
+)
+
+var (
+	textFlag = flag.String("text", "", "file name of binary being debugged")
+)
+
+func main() {
+	log.SetFlags(0)
+	log.SetPrefix("ogleproxy: ")
+	flag.Parse()
+	if *textFlag == "" {
+		fmt.Println("OGLE BAD\nUsage")
+		flag.Usage()
+		os.Exit(2)
+	}
+	fd, err := os.Open(*textFlag)
+	if err != nil {
+		fmt.Printf("OGLE BAD\n%s\n", err)
+		os.Exit(2)
+	}
+	fd.Close()
+	err = rpc.Register(&server.Server{})
+	if err != nil {
+		fmt.Printf("OGLE BAD\n%s\n", err)
+		os.Exit(2)
+	}
+	fmt.Println("OGLE OK")
+	log.Print("start server")
+	// TODO: Usually done in a go.
+	rpc.ServeConn(&rwc{
+		os.Stdin,
+		os.Stdout,
+	})
+	log.Print("finish server")
+}
+
+// rwc creates a single io.ReadWriteCloser from a read side and a write side.
+// It allows us to do RPC using standard in and standard out.
+type rwc struct {
+	r *os.File
+	w *os.File
+}
+
+func (rwc *rwc) Read(p []byte) (int, error) {
+	return rwc.r.Read(p)
+}
+
+func (rwc *rwc) Write(p []byte) (int, error) {
+	return rwc.w.Write(p)
+}
+
+func (rwc *rwc) Close() error {
+	rerr := rwc.r.Close()
+	werr := rwc.w.Close()
+	if rerr != nil {
+		return rerr
+	}
+	return werr
+}
diff --git a/program/client/client.go b/program/client/client.go
new file mode 100644
index 0000000..fea7c7f
--- /dev/null
+++ b/program/client/client.go
@@ -0,0 +1,221 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package client provides remote access to an ogle proxy.
+package client
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"net/rpc"
+	"os"
+	"os/exec"
+
+	"code.google.com/p/ogle/program"
+	"code.google.com/p/ogle/program/proxyrpc"
+)
+
+var _ program.Program = (*Program)(nil)
+var _ program.File = (*File)(nil)
+
+// New connects to the specified host using SSH, starts an ogle proxy
+// there, and creates a new program from the specified file with the specified
+// arguments, which include the program name the first argument.
+// The program is created but stops before executing the first instruction,
+// ready for debugging.
+func New(host string, textFile string, args ...string) (*Program, error) {
+	panic("unimplemented")
+}
+
+// Run connects to the specified host using SSH, starts an ogle proxy
+// there, and runs a new program from the specified file with the specified
+// arguments, which include the program name the first argument.
+// It is similar to New except that the program is allowed to run.
+func Run(host string, textFile string, args ...string) (*Program, error) {
+	// TODO: add args.
+	cmd := exec.Command("/usr/bin/ssh", host, "ogleproxy", "-text", textFile)
+	stdin, toStdin, err := os.Pipe()
+	if err != nil {
+		return nil, err
+	}
+	fromStdout, stdout, err := os.Pipe()
+	if err != nil {
+		return nil, err
+	}
+	cmd.Stdin = stdin
+	cmd.Stdout = stdout
+	cmd.Stderr = os.Stderr // Stderr from proxy appears on our stderr.
+	err = cmd.Start()
+	if err != nil {
+		return nil, err
+	}
+	stdout.Close()
+	// Read back one line. It must start "OGLE" and we hope says "OK".
+	msg, err := readLine(fromStdout)
+	if err != nil {
+		return nil, err
+	}
+	switch msg {
+	case "OGLE BAD":
+		// Error is on next line.
+		msg, err = readLine(fromStdout)
+		if err == nil {
+			err = errors.New(msg)
+		}
+		return nil, err
+	case "OGLE OK":
+	default:
+		// Communication error.
+		return nil, fmt.Errorf("unrecognized message %q", msg)
+	}
+	program := &Program{
+		client: rpc.NewClient(&rwc{
+			ssh: cmd,
+			r:   fromStdout,
+			w:   toStdin,
+		}),
+	}
+	return program, nil
+}
+
+// readLine reads one line of text from the reader. It does no buffering.
+// The trailing newline is read but not returned.
+func readLine(r io.Reader) (string, error) {
+	b := make([]byte, 0, 10)
+	var c [1]byte
+	for {
+		_, err := io.ReadFull(r, c[:])
+		if err != nil {
+			return "", err
+		}
+		if c[0] == '\n' {
+			break
+		}
+		b = append(b, c[0])
+	}
+	return string(b), nil
+}
+
+// rwc creates a single io.ReadWriteCloser from a read side and a write side.
+// It also holds the command object so we can wait for SSH to complete.
+// It allows us to do RPC over an SSH connection.
+type rwc struct {
+	ssh *exec.Cmd
+	r   *os.File
+	w   *os.File
+}
+
+func (rwc *rwc) Read(p []byte) (int, error) {
+	return rwc.r.Read(p)
+}
+
+func (rwc *rwc) Write(p []byte) (int, error) {
+	return rwc.w.Write(p)
+}
+
+func (rwc *rwc) Close() error {
+	rerr := rwc.r.Close()
+	werr := rwc.w.Close()
+	cerr := rwc.ssh.Wait()
+	if cerr != nil {
+		// Wait exit status is most important.
+		return cerr
+	}
+	if rerr != nil {
+		return rerr
+	}
+	return werr
+}
+
+// Program implements the similarly named ogle interface.
+// Through that interface it provides access to a program being
+// debugged on a possibly remote machine by communicating
+// with an ogle proxy adjacent to the target program.
+type Program struct {
+	client *rpc.Client
+}
+
+func (p *Program) Open(name string, mode string) (program.File, error) {
+	req := proxyrpc.OpenRequest{
+		Name: name,
+		Mode: mode,
+	}
+	var resp proxyrpc.OpenResponse
+	err := p.client.Call("Server.Open", &req, &resp)
+	if err != nil {
+		return nil, err
+	}
+	f := &File{
+		prog: p,
+		fd:   resp.FD,
+	}
+	return f, nil
+}
+
+func (p *Program) SetArguments(args ...string) {
+	panic("unimplemented")
+}
+
+func (p *Program) Run(start bool) (program.Status, error) {
+	panic("unimplemented")
+}
+
+func (p *Program) Stop() (program.Status, error) {
+	panic("unimplemented")
+}
+
+func (p *Program) Resume() (program.Status, error) {
+	panic("unimplemented")
+}
+
+func (p *Program) Kill() (program.Status, error) {
+	panic("unimplemented")
+}
+
+func (p *Program) Breakpoint(address string) error {
+	panic("unimplemented")
+}
+
+func (p *Program) DeleteBreakpoint(address string) error {
+	panic("unimplemented")
+}
+
+// File implements the program.File interface, providing access
+// to file-like resources associated with the target program.
+type File struct {
+	prog *Program // The Program associated with the file.
+	fd   int      // File descriptor.
+}
+
+func (f *File) ReadAt(p []byte, offset int64) (int, error) {
+	req := proxyrpc.ReadAtRequest{
+		FD:     f.fd,
+		Len:    len(p),
+		Offset: offset,
+	}
+	var resp proxyrpc.ReadAtResponse
+	err := f.prog.client.Call("Server.ReadAt", &req, &resp)
+	return copy(p, resp.Data), err
+}
+
+func (f *File) WriteAt(p []byte, offset int64) (int, error) {
+	req := proxyrpc.WriteAtRequest{
+		FD:     f.fd,
+		Data:   p,
+		Offset: offset,
+	}
+	var resp proxyrpc.WriteAtResponse
+	err := f.prog.client.Call("Server.WriteAt", &req, &resp)
+	return resp.Len, err
+}
+
+func (f *File) Close() error {
+	req := proxyrpc.CloseRequest{
+		FD: f.fd,
+	}
+	var resp proxyrpc.CloseResponse
+	err := f.prog.client.Call("Server.Close", &req, &resp)
+	return err
+}
diff --git a/program/program.go b/program/program.go
new file mode 100644
index 0000000..55664b4
--- /dev/null
+++ b/program/program.go
@@ -0,0 +1,87 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package program provides the portable interface to a program being debugged.
+package program
+
+import (
+	"io"
+)
+
+// Program is the interface to a (possibly remote) program being debugged.
+// The process (if any) and text file associated with it may change during
+// the session, but many resources are associated with the Program rather
+// than process or text file so they persist across debuggging runs.
+type Program interface {
+	// Open opens a virtual file associated with the process.
+	// Names are things like "text", "mem", "fd/2".
+	// Mode is one of "r", "w", "rw".
+	// Return values are open File and error.
+	// When the target binary is re-run, open files are
+	// automatically updated to refer to the corresponding
+	// file in the new process.
+	Open(name string, mode string) (File, error)
+
+	// SetArguments sets the command-line arguments for
+	// the next running of the target binary, excluding the
+	// target's binary name. That is, while debugging the
+	// echo command, to prepare a run of "echo hi" call
+	//	SetArguments("hi")
+	SetArguments(args ...string)
+
+	// Run abandons the current running process, if any,
+	// and execs a new instance of the target binary file
+	// (which may have changed underfoot).
+	// Breakpoints and open files are re-established.
+	// The flag specifies whether to run the program (true)
+	// or stop it before it executes any instructions (false).
+	// The call hangs until the program stops executing,
+	// at which point it returns the program status.
+	Run(start bool) (Status, error)
+
+	// Stop stops execution of the current process but
+	// does not kill it.
+	Stop() (Status, error)
+
+	// Resume resumes execution of a stopped process.
+	// The call hangs until the program stops executing,
+	// at which point it returns the program status.
+	Resume() (Status, error)
+
+	// TODO: Step(). Where does the granularity happen,
+	// on the proxy end or the debugging control end?
+
+	// Kill kills the current process.
+	Kill() (Status, error)
+
+	// Breakpoint sets a breakpoint at the specified address.
+	// When the target binary is re-run, breakpoints are
+	// automatically re-established in the new process by
+	// re-evaluating the address.
+	// Address syntax:
+	//	"main.main"  Start of function
+	//	"main.go:23" Line number
+	//	(more to follow; may want an expression grammar)
+	// It is an OK if two breakpoints evaluate to the same PC. (TODO: verify.)
+	Breakpoint(address string) error
+
+	// DeleteBreakpoint removes the breakpoint at the specified
+	// address.
+	DeleteBreakpoint(address string) error
+}
+
+// The File interface provides access to file-like resources in the program.
+// It implements only ReaderAt and WriterAt, not Reader and Writer, because
+// random access is a far more common pattern for things like symbol tables,
+// and because enormous address space of virtual memory makes routines
+// like io.Copy dangerous.
+type File interface {
+	io.ReaderAt
+	io.WriterAt
+	io.Closer
+}
+
+type Status struct {
+	// TBD
+}
diff --git a/program/proxyrpc/proxyrpc.go b/program/proxyrpc/proxyrpc.go
new file mode 100644
index 0000000..0017b8b
--- /dev/null
+++ b/program/proxyrpc/proxyrpc.go
@@ -0,0 +1,50 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package proxyrpc defines the types used to represent the RPC calls
+// used to the ogleproxy.
+package proxyrpc
+
+// For regularity, each method has a unique Request and a Response type even
+// when not strictly necessary.
+
+// File I/O, at the top because they're simple.
+
+type ReadAtRequest struct {
+	FD     int
+	Len    int
+	Offset int64
+}
+
+type ReadAtResponse struct {
+	Data []byte
+}
+
+type WriteAtRequest struct {
+	FD     int
+	Data   []byte
+	Offset int64
+}
+
+type WriteAtResponse struct {
+	Len int
+}
+
+type CloseRequest struct {
+	FD int
+}
+
+type CloseResponse struct {
+}
+
+// Program methods.
+
+type OpenRequest struct {
+	Name string
+	Mode string
+}
+
+type OpenResponse struct {
+	FD int
+}
diff --git a/program/server/server.go b/program/server/server.go
new file mode 100644
index 0000000..fcd54d7
--- /dev/null
+++ b/program/server/server.go
@@ -0,0 +1,84 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package server provides RPC access to a local program being debugged.
+// It is the remote end of the client implementation of the Program interface.
+package server
+
+import (
+	"fmt"
+	"os"
+
+	"code.google.com/p/ogle/program"
+	"code.google.com/p/ogle/program/proxyrpc"
+)
+
+type Server struct {
+	// TODO: Needs mutex.
+	files []*file // Index == file descriptor.
+}
+
+type file struct {
+	mode  string
+	index int
+	f     program.File
+}
+
+func (s *Server) Open(req *proxyrpc.OpenRequest, resp *proxyrpc.OpenResponse) error {
+	// TODO: Better simulation. For now we just open the named OS file.
+	var flag int
+	switch req.Mode {
+	case "r":
+		flag = os.O_RDONLY
+	case "w":
+		flag = os.O_WRONLY
+	case "rw":
+		flag = os.O_RDWR
+	default:
+		return fmt.Errorf("Open: bad open mode %q", req.Mode)
+	}
+	osFile, err := os.OpenFile(req.Name, flag, 0)
+	if err != nil {
+		return err
+	}
+	// Find a file descriptor (index) slot.
+	index := 0
+	for ; index < len(s.files) && s.files[index] != nil; index++ {
+	}
+	f := &file{
+		mode:  req.Mode,
+		index: index,
+		f:     osFile,
+	}
+	if index == len(s.files) {
+		s.files = append(s.files, f)
+	} else {
+		s.files[index] = f
+	}
+	return nil
+}
+
+func (s *Server) ReadAt(req *proxyrpc.ReadAtRequest, resp *proxyrpc.ReadAtResponse) error {
+	fd := req.FD
+	if fd < 0 || len(s.files) <= fd || s.files[fd] == nil {
+		return fmt.Errorf("ReadAt: bad file descriptor %d", fd)
+	}
+	f := s.files[fd]
+	buf := make([]byte, req.Len) // TODO: Don't allocate every time
+	n, err := f.f.ReadAt(buf, req.Offset)
+	resp.Data = buf[:n]
+	return err
+}
+
+func (s *Server) Close(req *proxyrpc.CloseRequest, resp *proxyrpc.CloseResponse) error {
+	fd := req.FD
+	if fd < 0 || fd >= len(s.files) || s.files[fd] == nil {
+		return fmt.Errorf("Close: bad file descriptor %d", fd)
+	}
+	err := s.files[fd].f.Close()
+	// Remove it regardless
+	s.files[fd] = nil
+	return err
+
+}