TBR
v: renaming the v directory to go
Change-Id: I4fd9f6ee2895d8034c23b65927eb118980b3c17a
diff --git a/vsh/main.go b/vsh/main.go
new file mode 100644
index 0000000..943835e
--- /dev/null
+++ b/vsh/main.go
@@ -0,0 +1,213 @@
+package main
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "net"
+ "os"
+ "path"
+ "strings"
+ "time"
+
+ "veyron/examples/tunnel"
+ "veyron/examples/tunnel/lib"
+ "veyron/lib/signals"
+ "veyron2"
+ "veyron2/rt"
+ "veyron2/vlog"
+)
+
+var (
+ disablePty = flag.Bool("T", false, "Disable pseudo-terminal allocation.")
+ forcePty = flag.Bool("t", false, "Force allocation of pseudo-terminal.")
+ vname = flag.String("vname", "", "Veyron name (or endpoint) for tunneling service.")
+
+ portforward = flag.String("L", "", "localaddr,remoteaddr Forward local 'localaddr' to 'remoteaddr'")
+ lprotocol = flag.String("local_protocol", "tcp", "Local network protocol for port forwarding")
+ rprotocol = flag.String("remote_protocol", "tcp", "Remote network protocol for port forwarding")
+
+ noshell = flag.Bool("N", false, "Do not execute a shell. Only do port forwarding.")
+)
+
+func init() {
+ flag.Usage = func() {
+ bname := path.Base(os.Args[0])
+ fmt.Fprintf(os.Stderr, `%s: Veyron SHell.
+
+This tool is used to run shell commands or an interactive shell on a remote
+tunneld service.
+
+To open an interactive shell, use:
+ %s --host=<veyron name or endpoint>
+
+To run a shell command, use:
+ %s --host=<veyron name or endpoint> <command to run>
+
+The -L flag will forward connections from a local port to a remote address
+through the tunneld service. The flag value is localaddr,remoteaddr. E.g.
+ -L :14141,www.google.com:80
+
+%s can't be used directly with tools like rsync because veyron addresses don't
+look like traditional hostnames, which rsync doesn't understand. For
+compatibility with such tools, %s has a special feature that allows passing the
+veyron address via the VSH_NAME environment variable.
+
+ $ VSH_NAME=<veyron address> rsync -avh -e %s /foo/* veyron:/foo/
+
+In this example, the "veyron" host will be substituted with $VSH_NAME by %s
+and rsync will work as expected.
+
+Full flags:
+`, os.Args[0], bname, bname, bname, bname, os.Args[0], bname)
+ flag.PrintDefaults()
+ }
+}
+
+func main() {
+ // Work around the fact that os.Exit doesn't run deferred functions.
+ os.Exit(realMain())
+}
+
+func realMain() int {
+ r := rt.Init()
+ defer r.Shutdown()
+
+ host, cmd, err := veyronNameAndCommandLine()
+ if err != nil {
+ flag.Usage()
+ fmt.Fprintf(os.Stderr, "\n%v\n", err)
+ return 1
+ }
+
+ t, err := tunnel.BindTunnel(host)
+ if err != nil {
+ vlog.Fatalf("BindTunnel(%q) failed: %v", host, err)
+ }
+
+ if len(*portforward) > 0 {
+ go runPortForwarding(t, host)
+ }
+
+ if *noshell {
+ <-signals.ShutdownOnSignals()
+ return 0
+ }
+
+ opts := shellOptions(cmd)
+
+ stream, err := t.Shell(cmd, opts, veyron2.CallTimeout(24*time.Hour))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return 1
+ }
+ saved := lib.EnterRawTerminalMode()
+ defer lib.RestoreTerminalSettings(saved)
+ runIOManager(os.Stdin, os.Stdout, os.Stderr, stream)
+
+ exitMsg := fmt.Sprintf("Connection to %s closed.", host)
+ exitStatus, err := stream.Finish()
+ if err != nil {
+ exitMsg += fmt.Sprintf(" (%v)", err)
+ }
+ vlog.VI(1).Info(exitMsg)
+ // Only show the exit message on stdout for interactive shells.
+ // Otherwise, the exit message might get confused with the output
+ // of the command that was run.
+ if err != nil {
+ fmt.Fprintln(os.Stderr, exitMsg)
+ } else if len(cmd) == 0 {
+ fmt.Println(exitMsg)
+ }
+ return int(exitStatus)
+}
+
+func shellOptions(cmd string) (opts tunnel.ShellOpts) {
+ opts.UsePty = (len(cmd) == 0 || *forcePty) && !*disablePty
+ opts.Environment = environment()
+ ws, err := lib.GetWindowSize()
+ if err != nil {
+ vlog.VI(1).Infof("GetWindowSize failed: %v", err)
+ } else {
+ opts.Rows = uint32(ws.Row)
+ opts.Cols = uint32(ws.Col)
+ }
+ return
+}
+
+func environment() []string {
+ env := []string{}
+ for _, name := range []string{"TERM", "COLORTERM"} {
+ if value := os.Getenv(name); value != "" {
+ env = append(env, fmt.Sprintf("%s=%s", name, value))
+ }
+ }
+ return env
+}
+
+// veyronNameAndCommandLine extracts the veyron name and the remote command to
+// send to the server. The name can be specified with the --vname flag or as the
+// first non-flag argument. The command line is the concatenation of all the
+// non-flag arguments, minus the veyron name.
+func veyronNameAndCommandLine() (string, string, error) {
+ name := *vname
+ args := flag.Args()
+ if len(name) == 0 {
+ if len(args) > 0 {
+ name = args[0]
+ args = args[1:]
+ }
+ }
+ if len(name) == 0 {
+ return "", "", errors.New("veyron name missing")
+ }
+ // For compatibility with tools like rsync. Because veyron addresses
+ // don't look like traditional hostnames, tools that work with rsh and
+ // ssh can't work directly with vsh. This trick makes the following
+ // possible:
+ // $ VSH_NAME=<veyron address> rsync -avh -e vsh /foo/* veyron:/foo/
+ // The "veyron" host will be substituted with <veyron address>.
+ if envName := os.Getenv("VSH_NAME"); len(envName) > 0 && name == "veyron" {
+ name = envName
+ }
+ cmd := strings.Join(args, " ")
+ return name, cmd, nil
+}
+
+func runPortForwarding(t tunnel.Tunnel, host string) {
+ // *portforward is localaddr,remoteaddr
+ parts := strings.Split(*portforward, ",")
+ var laddr, raddr string
+ if len(parts) != 2 {
+ vlog.Fatalf("-L flag expects 2 values separated by a comma")
+ }
+ laddr = parts[0]
+ raddr = parts[1]
+
+ ln, err := net.Listen(*lprotocol, laddr)
+ if err != nil {
+ vlog.Fatalf("net.Listen(%q, %q) failed: %v", *lprotocol, laddr, err)
+ }
+ defer ln.Close()
+ vlog.VI(1).Infof("Listening on %q", ln.Addr())
+ for {
+ conn, err := ln.Accept()
+ if err != nil {
+ vlog.Infof("Accept failed: %v", err)
+ continue
+ }
+ stream, err := t.Forward(*rprotocol, raddr, veyron2.CallTimeout(24*time.Hour))
+ if err != nil {
+ vlog.Infof("Tunnel(%q, %q) failed: %v", *rprotocol, raddr, err)
+ conn.Close()
+ continue
+ }
+ name := fmt.Sprintf("%v-->%v-->(%v)-->%v", conn.RemoteAddr(), conn.LocalAddr(), host, raddr)
+ go func() {
+ vlog.VI(1).Infof("TUNNEL START: %v", name)
+ errf := lib.Forward(conn, stream)
+ err := stream.Finish()
+ vlog.VI(1).Infof("TUNNEL END : %v (%v, %v)", name, errf, err)
+ }()
+ }
+}